[
  {
    "path": ".gitattributes",
    "content": ".gitignore merge=union text\ndocs/changelog.txt merge=union text\ndocs/cycles_and_commands.txt text\ndocs/idle_direction_oddities.txt text\ndocs/macro.txt text\ndocs/port.txt text\ndocs/push_and_transport.txt text\ndocs/WIPHelp.txt text\nunit/io/data/*.txt text eol=lf\nconfig.txt text\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Regression tests\n\nenv:\n  MZXDEPS_DEBIAN_SDL2:  \"zlib1g-dev libpng-dev libogg-dev libvorbis-dev libsdl2-dev\"\n  MZXDEPS_DEBIAN_MISC:  \"libsdl1.2-dev libegl1-mesa-dev libmikmod-dev libopenmpt-dev\"\n  MZXDEPS_MACOS:        \"libpng libogg libvorbis sdl2\"\n  MZX_MAKE:             \"make -j4\"\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\n  workflow_dispatch:\n\njobs:\n  #\n  # Platform variants and distribution packages.\n  #\n  unix:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install dependencies\n        run: sudo apt update && sudo apt install -y --no-install-recommends $MZXDEPS_DEBIAN_SDL2\n      - uses: actions/checkout@v4\n      - name: Configure\n        run: ./config.sh --platform unix --enable-release --enable-sdlaccel\n      - name: Build\n        run: $MZX_MAKE\n      - name: Run tests\n        run: $MZX_MAKE test\n      - name: Install\n        run: sudo $MZX_MAKE install V=1\n      - name: Check install\n        run: command -v megazeux && [ -f \"/usr/share/licenses/megazeux/LICENSE\" ] && [ -f \"/etc/megazeux-config\" ]\n      - name: Uninstall\n        run: sudo $MZX_MAKE uninstall V=1\n      - name: Check uninstall\n        run: true && [ ! -f \"/usr/bin/megazeux\" ] && [ ! -f \"/usr/share/licenses/megazeux/LICENSE\" ] && [ ! -f \"/etc/megazeux-config\" ]\n      - name: Package source .tar.xz\n        run: make source\n\n  unix-portable:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install dependencies\n        run: sudo apt update && sudo apt install -y --no-install-recommends $MZXDEPS_DEBIAN_SDL2\n      - uses: actions/checkout@v4\n      - name: Configure\n        run: ./config.sh --platform unix-devel --enable-release --enable-sdlaccel\n      - name: Build\n        run: $MZX_MAKE\n      - name: Run tests\n        run: $MZX_MAKE test\n      - name: Package linux-amd64 .zip\n        run: make archive\n\n  unix-misc:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install dependencies\n        run: sudo apt update && sudo apt install -y --no-install-recommends $MZXDEPS_DEBIAN_SDL2 $MZXDEPS_DEBIAN_MISC\n      - uses: actions/checkout@v4\n      - name: Configure (SDL 1.2)\n        run: ./config.sh --platform unix-devel --enable-release --enable-sdl1\n      - name: Build (SDL 1.2)\n        run: $MZX_MAKE\n      - name: Run tests (SDL 1.2)\n        run: $MZX_MAKE test\n      - name: Distclean (SDL 1.2)\n        run: $MZX_MAKE distclean\n      - name: Configure (EGL/X11)\n        run: ./config.sh --platform unix-devel --enable-release --disable-sdl --enable-egl\n      - name: Build (EGL/X11)\n        run: $MZX_MAKE\n      # Note: EGL mzxrun builds can't run headless, unit tests only.\n      - name: Run tests (EGL/X11)\n        run: $MZX_MAKE unit\n      - name: Distclean (EGL/X11)\n        run: $MZX_MAKE distclean\n      - name: Configure (ModPlug)\n        run: ./config.sh --platform unix-devel --enable-release --enable-modplug\n      - name: Build (Modplug)\n        run: $MZX_MAKE\n      - name: Run tests (Modplug)\n        run: $MZX_MAKE test\n      - name: Distclean (Modplug)\n        run: $MZX_MAKE distclean\n      - name: Configure (MikMod)\n        run: ./config.sh --platform unix-devel --enable-release --enable-mikmod\n      - name: Build (MikMod)\n        run: $MZX_MAKE\n      - name: Run tests (MikMod)\n        run: $MZX_MAKE test\n      - name: Distclean (MikMod)\n        run: $MZX_MAKE distclean\n      - name: Configure (OpenMPT)\n        run: ./config.sh --platform unix-devel --enable-release --enable-openmpt\n      - name: Build (OpenMPT)\n        run: $MZX_MAKE\n      - name: Run tests (OpenMPT)\n        run: $MZX_MAKE test\n      - name: Distclean (OpenMPT)\n        run: $MZX_MAKE distclean\n      - name: Configure (debug)\n        run: ./config.sh --platform unix-devel --disable-release --enable-trace --enable-fps\n      - name: Build (debug)\n        run: $MZX_MAKE\n      - name: Run tests (debug)\n        run: $MZX_MAKE test\n      - name: Distclean (debug)\n        run: $MZX_MAKE distclean\n\n  darwin-arm64:\n    runs-on: macos-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install dependencies\n        run: brew install $MZXDEPS_MACOS\n      - name: Configure (darwin-devel)\n        run: ./config.sh --platform darwin-devel --prefix /opt/homebrew --enable-release --enable-sdlaccel\n      - name: Build (darwin-devel)\n        run: $MZX_MAKE\n      - name: Run tests (darwin-devel)\n        run: $MZX_MAKE test\n\n  darwin-x86_64:\n    runs-on: macos-26-intel\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install dependencies\n        run: brew install $MZXDEPS_MACOS\n      - name: Configure (darwin-devel)\n        run: ./config.sh --platform darwin-devel --prefix /usr/local --enable-release --enable-sdlaccel\n      - name: Build (darwin-devel)\n        run: $MZX_MAKE\n      - name: Run tests (darwin-devel)\n        run: $MZX_MAKE test\n\n  MSYS2-MINGW64:\n    runs-on: windows-latest\n    defaults:\n      run:\n        shell: msys2 {0}\n    steps:\n      - name: Setup msys2\n        uses: msys2/setup-msys2@v2\n        with:\n          msystem: MINGW64\n          update: true\n          install: >-\n            base-devel git zip\n            mingw-w64-x86_64-gcc    mingw-w64-x86_64-zlib      mingw-w64-x86_64-libpng\n            mingw-w64-x86_64-libogg mingw-w64-x86_64-libvorbis mingw-w64-x86_64-SDL2\n      - uses: actions/checkout@v4\n      - name: Configure x64\n        run: ./config.sh --platform win64 --enable-release\n      - name: Build x64\n        run: $MZX_MAKE\n      - name: Test x64\n        run: $MZX_MAKE test\n      - name: Package x64 .zip\n        run: make archive\n\n  MSYS2-MINGW32:\n    runs-on: windows-latest\n    defaults:\n      run:\n        shell: msys2 {0}\n    steps:\n      - name: Setup msys2\n        uses: msys2/setup-msys2@v2\n        with:\n          msystem: MINGW32\n          update: true\n          install: >-\n            base-devel git zip\n            mingw-w64-i686-gcc      mingw-w64-i686-zlib        mingw-w64-i686-libpng\n            mingw-w64-i686-libogg   mingw-w64-i686-libvorbis   mingw-w64-i686-SDL2\n      - uses: actions/checkout@v4\n      - name: Configure x86\n        run: ./config.sh --platform win32 --enable-release\n      - name: Build x86\n        run: $MZX_MAKE\n      - name: Test x86\n        run: $MZX_MAKE test\n      - name: Package x86 .zip\n        run: make archive\n\n  AArch64-Switch:\n    runs-on: ubuntu-latest\n    container: devkitpro/devkita64\n    steps:\n      - run: echo \"PATH=$DEVKITPRO/devkitA64/bin:$PATH\" >> $GITHUB_ENV\n      - uses: actions/checkout@v4\n      - name: Configure Switch\n        run: arch/switch/CONFIG.SWITCH\n      - name: Build Switch\n        run: $MZX_MAKE\n      - name: Package Switch .zip\n        run: make archive\n\n  ARM-3DS:\n    runs-on: ubuntu-latest\n    container: devkitpro/devkitarm\n    steps:\n      - run: echo \"PATH=$DEVKITPRO/devkitARM/bin:$PATH\" >> $GITHUB_ENV\n      - uses: actions/checkout@v4\n      - name: Configure 3DS\n        run: arch/3ds/CONFIG.3DS\n      - name: Build 3DS\n        run: $MZX_MAKE\n      - name: Package 3DS .zip\n        run: make archive\n\n  ARM-NDS:\n    runs-on: ubuntu-latest\n    container: devkitpro/devkitarm:20241104\n    steps:\n      - run: echo \"PATH=$DEVKITPRO/devkitARM/bin:$PATH\" >> $GITHUB_ENV\n      - uses: actions/checkout@v4\n      - name: Configure NDS\n        run: arch/nds/CONFIG.NDS\n      - name: Build NDS\n        run: $MZX_MAKE\n      - name: Package NDS .zip\n        run: make archive\n\n  ARM-NDS-BlocksDS:\n    runs-on: ubuntu-latest\n    container: skylyrac/blocksds:slim-latest\n    steps:\n      - name: Install dependencies\n        run: apt update && apt install -y --no-install-recommends zip\n      - name: Install target dependencies\n        run: wf-pacman -S --noconfirm toolchain-gcc-arm-none-eabi-zlib\n      - uses: actions/checkout@v4\n      - name: Configure NDS/BlocksDS\n        run: arch/nds-blocksds/CONFIG.NDS\n      - name: Build NDS/BlocksDS\n        run: $MZX_MAKE\n      - name: Package NDS/BlocksDS .zip\n        run: make archive\n\n  PowerPC-Wii:\n    runs-on: ubuntu-latest\n    container: devkitpro/devkitppc\n    steps:\n      - run: echo \"PATH=$DEVKITPRO/devkitPPC/bin:$PATH\" >> $GITHUB_ENV\n      - uses: actions/checkout@v4\n      - name: Configure Wii\n        run: arch/wii/CONFIG.WII\n      - name: Build Wii\n        run: $MZX_MAKE\n      - name: Package Wii .zip\n        run: make archive\n\n  PowerPC-WiiU:\n    runs-on: ubuntu-latest\n    container: devkitpro/devkitppc\n    steps:\n      - run: echo \"PATH=$DEVKITPRO/devkitPPC/bin:$PATH\" >> $GITHUB_ENV\n      - uses: actions/checkout@v4\n      - name: Configure Wii U\n        run: arch/wiiu/CONFIG.WIIU\n      - name: Build Wii U\n        run: $MZX_MAKE\n      - name: Package Wii U .zip\n        run: make archive\n\n  Emscripten-HTML5:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Install Emscripten\n        uses: mymindstorm/setup-emsdk@v14\n        with:\n          version: 4.0.7\n#          actions-cache-folder: 'emsdk-cache'\n      - uses: actions/checkout@v4\n      - name: Configure HTML5\n        run: arch/emscripten/CONFIG.HTML5\n      - name: Build HTML5\n        run: $MZX_MAKE && make build\n      - name: Package HTML5 .zip\n        run: make archive\n\n  # FIXME: vitasdk hasn't bothered adding SDL3 to the container,\n  # vdpm and git are missing from the container.\n  Vita:\n    runs-on: ubuntu-latest\n    container: vitasdk/vitasdk\n    steps:\n      - name: Install dependencies\n        run: apk add --no-cache zip\n      - uses: actions/checkout@v4\n      - name: Configure Vita\n        run: arch/psvita/CONFIG.PSVITA --enable-sdl2 --disable-libpng --disable-lto\n      - name: Build Vita\n        run: $MZX_MAKE\n      - name: Package Vita\n        run: make archive\n\n  #\n  # Sanitizers.\n  #\n  AddressSanitizer:\n    runs-on: ubuntu-latest\n    env:\n      CC: clang\n      CXX: clang++\n    steps:\n      - name: Install dependencies\n        run: sudo apt update && sudo apt install -y --no-install-recommends $MZXDEPS_DEBIAN_SDL2\n      - uses: actions/checkout@v4\n      - name: Configure\n        run: ./config.sh --platform unix-devel --enable-asan --enable-extram\n      - name: Build\n        run: $MZX_MAKE\n      - name: Run tests\n        run: $MZX_MAKE test\n\n  # Note: requires all non-system/libc calls to be from instrumented libraries.\n  # For the regression tests, MZX can get away with just zlib.\n  # SDL calls into uninstrumented functions and is not built at all.\n  # libpng is temporarily disabled. libogg/libvorbis work with no issue.\n  MemorySanitizer:\n    runs-on: ubuntu-latest\n    env:\n      CC: clang\n      CXX: clang++\n    steps:\n      - uses: actions/checkout@v4\n      - name: Fetch dependencies\n        run: |\n          (cd scripts/deps;\n          wget https://github.com/AliceLR/megazeux-dependencies/releases/download/v2.93c-r1/megazeux-dependencies-2.93c-r1-linux-msan-mint22.tar.xz;\n          tar -xJf megazeux-dependencies-*-linux-msan-mint22.tar.xz)\n      - name: Configure\n        run: ./config.sh --platform unix-devel --prefix scripts/deps/linux-msan/x86_64 --enable-msan --disable-sdl --disable-libpng --disable-x11 --enable-extram\n      - name: Build\n        run: $MZX_MAKE V=1\n      - name: Run tests\n        run: $MZX_MAKE V=1 test\n\n  UndefinedBehaviorSanitizer:\n    runs-on: ubuntu-latest\n    env:\n      CC: clang\n      CXX: clang++\n      UBSAN_OPTIONS: print_stacktrace=1\n    steps:\n      - name: Install dependencies\n        run: sudo apt update && sudo apt install -y --no-install-recommends $MZXDEPS_DEBIAN_SDL2\n      - uses: actions/checkout@v4\n      - name: Configure\n        run: ./config.sh --platform unix-devel --enable-ubsan --enable-extram\n      - name: Build\n        run: $MZX_MAKE\n      - name: Run tests\n        run: $MZX_MAKE test\n"
  },
  {
    "path": ".gitignore",
    "content": "arch/3ds/*.d\narch/3ds/*.o\narch/3ds/*shbin*\narch/android/deps\narch/android/include\narch/android/project/.eclipse\narch/android/project/.gradle\narch/android/project/.idea\narch/android/project/*.iml\narch/android/project/*.ipr\narch/android/project/app/.cxx\narch/android/project/app/.externalNativeBuild\narch/android/project/app/jni/.gradle\narch/android/project/app/jni/SDL2\narch/android/project/app/jni/lib\narch/android/project/app/jni/libogg\narch/android/project/app/jni/libvorbis\narch/android/project/app/debug/\narch/android/project/app/release/\narch/android/project/app/src/main/res/raw/assets.zip\narch/android/project/app/*.iml\narch/android/project/build\narch/android/project/keystore.properties\narch/android/project/local.properties\narch/djgpp/*.d\narch/djgpp/*.o\narch/dreamcast/*.d\narch/dreamcast/*.o\narch/emscripten/web/mzxrun_web.js*\narch/emscripten/web/node_modules\narch/emscripten/web/package-lock.json\narch/mingw/pefix\narch/mingw/pefix.d\narch/mingw/pefix.exe\narch/mingw/pefix.o\narch/msvc/.vs\narch/msvc/*.user\narch/msvc/Deps/\narch/msvc/MegaZeux.opensdf\narch/msvc/MegaZeux.sdf\narch/msvc/MegaZeux.v12.suo\narch/msvc/obj\narch/msvc/version.h\narch/nds/mzxrun.arm7.elf\narch/nds/protected_palette_bin.c\narch/nds/protected_palette_bin.h\narch/nds/*.S\narch/nds/*.d\narch/nds/*.o\narch/psp/*.d\narch/psp/*.o\narch/psvita/*.d\narch/psvita/*.o\narch/wii/*.d\narch/wii/*.o\narch/xcode/version.h\nbuild\ncontrib/gdm2s3m/src/.build/\ncontrib/icons/.build/\ncontrib/libmodplug/src/.build/\ncontrib/libxmp/src/.build/\ncontrib/libxmp/src/win32/.build/\ncontrib/libxmp/src/loaders/.build/\ncontrib/unzip/src/.build/\n1ST_READ.BIN\nboot.dol\nCore.exp\nCore.ilk\nCore.iobj\nCore.ipdb\nCore.lib\nCore.pdb\ncore.dll\nlibcore.so\nlibcore.dylib\nEBOOT.PBP\neboot.bin\nEditor.exp\nEditor.ilk\nEditor.iobj\nEditor.ipdb\nEditor.lib\nEditor.map\nEditor.pdb\neditor.dll\nICON0.PNG\nlibeditor.so\nlibeditor.dylib\nmegazeux-cia.*\nmegazeux.3dsx\nmegazeux.bin\nmegazeux.dol\nmegazeux.elf\nmegazeux.exe\nmegazeux.exp\nMegaZeux.ilk\nMegaZeux.iobj\nMegaZeux.ipdb\nMegaZeux.lib\nmegazeux.nacp\nmegazeux.nro\nmegazeux.nso\nMegaZeux.pdb\nmegazeux.rpx\nmegazeux.smdh\nmegazeux.velf\nmegazeux.vpk\n/megazeux\nmzxrun.3dsx\nmzxrun.bin\nmzxrun.cia\nmzxrun.dol\nmzxrun.elf\nmzxrun.exe\nmzxrun.exp\nmzxrun.ilk\nmzxrun.iobj\nmzxrun.ipdb\nmzxrun.js\nmzxrun.js.orig.js\nmzxrun.js.mem\nmzxrun.lib\nmzxrun.nacp\nmzxrun.nds\nmzxrun.nro\nmzxrun.nso\nmzxrun.pdb\nmzxrun.rpx\nmzxrun.smdh\nmzxrun.velf\nmzxrun.vpk\nmzxrun.wasm\nmzxrun.wasm.map\n/mzxrun\nPARAM.SFO\nparam.sfo\nplatform.inc\nSDL.dll\nSDL2.dll\nsrc/.build/\nsrc/config.h\nsrc/audio/.build/\nsrc/editor/.build/\nsrc/io/.build/\nsrc/network/.build/\nsrc/utils/.build/\nsrc/utils/ccv.exe\nsrc/utils/ccv\nsrc/utils/checkres.exe\nsrc/utils/checkres\nsrc/utils/downver.exe\nsrc/utils/downver\nsrc/utils/hlp2html.exe\nsrc/utils/hlp2html\nsrc/utils/hlp2txt.exe\nsrc/utils/hlp2txt\nsrc/utils/txt2hlp.exe\nsrc/utils/txt2hlp\nsrc/utils/png2smzx.exe\nsrc/utils/png2smzx\nsrc/utils/y4m2smzx.exe\nsrc/utils/y4m2smzx\nsrc/utils/*.debug\nstdout.txt\nstderr.txt\nThumbs.db\nunit/.build/\nunit/audio/.build/\nunit/editor/.build/\nunit/io/.build/\nunit/network/.build/\nunit/utils/.build/\n/*.js\n/*.js.mem\n/*.js.orig.js\n/*.wasm\n/*.wasm.map\n*.code-workspace\n*.debug\n*.mzx\n*.sav\n*.mzm\n*~\n.clangd\n.vscode\n.DS_Store\nxcuserdata/\n\n# This MUST go after the *.mzx rule.\n!testworlds/*/*.mzx\ntestworlds/*/backup*\ntestworlds/backup*\ntestworlds/log\ntestworlds/next\ntestworlds/test\ntestworlds/temp/*\n!testworlds/temp/README.md\n!testworlds/data/*\n"
  },
  {
    "path": ".valgrindrc",
    "content": "--memcheck:leak-check=full\n--memcheck:track-origins=yes\n--suppressions=valgrind.supp\n"
  },
  {
    "path": "BUILDING.md",
    "content": "Building MegaZeux\n=================\n\nMegaZeux can currently be built for Microsoft Windows, Linux, macOS,\nNetBSD, FreeBSD, OpenBSD, Haiku, Amiga OS 4, Android, HTML5, MS-DOS (DJGPP),\nNDS, 3DS, Wii, Wii U, Switch, PSP, Vita, Dreamcast, GP2X, and Pandora.\n\nMegaZeux has three buildsystems--the GNU Make buildsystem for building most\nports, the Xcode project (`arch/xcode/`) for development and release builds\non macOS, and the MSVC project (`arch/msvc/`) for testing on Windows. This\ndocument applies primarily to the GNU Make buildsystem.\n\nFor architecture specific build instructions, please see `arch/[platform]/README`.\n[The DigitalMZX wiki](https://www.digitalmzx.com/wiki/Compiling_MegaZeux) has\nmore info, particularly for setting up console toolchains.\n\n\nRequirements\n------------\n\nFor the GNU Make buildsystem, most versions of these tools from the\npast 20 years work with little issue. You should almost always use the\nmost recent versions available.\n\n- POSIX-compatible base system e.g. Linux, macOS, BSD, MSYS2:\n  - **GNU coreutils**: <=4.5.3 or newer (packaging MinGW requires `sha256sum`, 6.0+)\n  - **BusyBox**: <=1.22 or newer (packaging MinGW requires `sha256sum`, 1.14.0+)\n  - (**BSD** `sha256` or **Perl** `shasum -a256` also works.)\n- GNU-compatible C and C++ compilers:\n  - **GCC**: 3.4 or newer\n  - **clang/LLVM**\n- **GNU binutils** or **LLVM** or equivalent\n- **GNU Make**: <=3.79 or newer\n- **pkgconf** or **pkg-config** (SDL 3 builds only)\n- **zip** or **7za** (packaging only)\n- **tar** and **xz** (packaging source only)\n- **ImageMagick** 7.0 or newer (rebuilding icons only)\n  - Older versions may work if `MAGICK=convert` is provided to Make.\n  - **GraphicsMagick** does not work as a substitute (no parentheses or clone).\n\nDependencies:\n\n- **SDL** (all ports except DJGPP, NDS, 3DS, Wii, and Dreamcast)\n  [(link)](https://www.libsdl.org/)\n  - **SDL 3** (with `--enable-sdl3`)\n  - **SDL 2** (default)\n  - **SDL 1.2**: <=1.2.5 or newer (with `--enable-sdl1`)\n    - Clipboard support for SDL 1.2 in Linux additionally requires X11 headers.\n- **zlib**: 1.2.0 or newer\n  [(link)](https://www.zlib.net/)\n- **libpng**: 1.2.3 or newer (or `--disable-libpng`)\n  [(link)](http://www.libpng.org/pub/png/libpng.html)\n- **libogg**: 1.0 or newer (or **stb_vorbis** or `--disable-vorbis`)\n  [(link)](https://xiph.org/downloads/)\n- **libvorbis**: 1.2.2 or newer (or **Tremor** or **stb_vorbis** or `--disable-vorbis`)\n  [(link)](https://xiph.org/downloads/)\n\nFor MinGW, DJGPP, and Darwin (macOS/Mac OS X), plus the Xcode and MSVC projects,\nprebuilt dependencies can be found [here](https://github.com/AliceLR/megazeux-dependencies).\nSee below or the platform-specific README for more info.\n\n\nConfiguring and building\n------------------------\n\nAll operations must be run within a POSIX-compatible shell environment\nsuch as Bash, BusyBox, ksh, MSYS2 terminal, or similar.\n\nConfigure MegaZeux with the script `config.sh` located in the source root:\n```sh\n# You may need to specify --prefix [dir] or other options.\n# For debug builds, omit --enable-release and --enable-lto.\n\n./config.sh --platform [platform] --enable-release --enable-lto\n\n# For a full list of supported options and platforms:\n./config.sh\n```\nSeveral platforms have shell scripts named `CONFIG.[arch]` located in their\n`arch/[platform]/` directory, which will run `config.sh` with appropriate\ndefault configurations.\n\nCompile MegaZeux with GNU Make:\n```sh\n# Use gmake on NetBSD et al.\n# For verbose output, provide V=1. Parallel builds (-j#) are supported.\nmake\n\n# For some platforms, it's safe to override the compilers if needed.\n# (Most platforms that prefer clang will automatically select it.)\nCC=clang CXX=clang++ make\n```\n\nMegaZeux comes with two regression test systems to verify that MegaZeux works\n(for platforms and build environments where executables can be run locally).\nThe unit tests located in `unit/` are compiled and executed individually.\nThe test worlds located in `testworlds/` are run together via MZXRun.\n```sh\n# Perform the unit tests and then run the test worlds. (-j# supported)\nmake test\n\n# Perform just the unit tests. (-j# supported)\nmake unit\n\n# Perform just the test worlds.\nmake testworlds\n```\n\n\nInstalling\n----------\n\nFor Linux/BSD/etc. using the `unix` platform and macOS/Mac OS X using the\n`darwin` platform, MegaZeux can be installed to the install root that was\nprovided to the `--prefix` option. (Note the default install root is `/usr`,\nso you may want to explicitly change that to `/usr/local` or similar):\n```sh\n# As superuser:\nmake install\n\n# Can be reversed:\nmake uninstall\n```\n\n\nPackaging\n---------\n\nFor all other platforms, MegaZeux is typically packaged instead.\nMegaZeux's packaging varies from platform to platform.\n\nFirst, for platforms that don't automatically separate or strip debug info\n(mainly MinGW and DJGPP), separate the debug info. (For MinGW, this also runs\nthe locally compiled tool `arch/mingw/pefix` to strip PE timestamps.)\n```sh\nmake debuglink -j8\n```\n\nPackage the MegaZeux build into a zip archive. The prepared build directory can\nbe found at `build/[platform]/` and the archive at `build/dist/[platform]/`:\n```sh\nmake archive\n```\n\nTo prepare a directory (in `build/[platform]/`) for packaging without archiving\nit, this can usually be used instead:\n```sh\nmake build\n```\n\nTo package a source tarball from a local Git repository:\n```sh\nmake source\n```\n\n\nPlatform-specific notes\n-----------------------\n\nCheck `arch/[platform]/README[.md]` for more detailed platform-specific\ninformation.\n\n### Debian-based distributions\n\n.deb packages can be generated with a single command.\nSee `debian/README` for more information.\n\n### RPM-based distributions\n\n.rpm packages can be generated with a single command:\n```sh\nrpmbuild -bb --build-in-place megazeux.spec\n```\n\n### Other Linux/BSD\n\nUse platform `unix` for `make install` or `unix-devel` for running directly\nfrom the source directory.\n\n### MinGW and DJGPP\n\nDownload the latest dependencies tarball from\n[here](https://github.com/AliceLR/megazeux-dependencies) and extract it into\n`scripts/deps/`. Provide the following to `config.sh`:\n\n- MINGW64: `--platform win64 --prefix scripts/deps/mingw/x64`\n- MINGW32: `--platform win32 --prefix scripts/deps/mingw/x86`\n- DJGPP: set `DJGPP` environment variable to `scripts/deps/djgpp/i386`\n  and run `arch/djgpp/CONFIG.DJGPP` from the base directory.\n\nWhen cross-compiling from Linux, use platforms `mingw64` and `mingw32` instead.\n\nAdditionally, DJGPP currently requires a copy of CWSDPMI.EXE to be placed in\n`arch/djgpp/` for packaging.\n\n### Darwin (macOS/Mac OS X via GNU Make)\n\nUse platform `darwin` for `make install`, `darwin-devel` for running from the\nsource directory, and `darwin-dist` to build a portable multi-architecture .app\nfor distribution (see `arch/darwin/README.md` for more info on `darwin-dist`).\n\nAll three variants require Xcode to be installed with available command line\ntools. It is easiest to get the dependency libraries from MacPorts (prefix\n`/opt/local`) or from the MegaZeux prebuilt dependencies.\n\nFor `darwin-dist` (and optionally `darwin-devel`), download the latest\ndependencies tarball from [here](https://github.com/AliceLR/megazeux-dependencies)\nand extract it into `scripts/deps/`. In addition to the requirements listed in\nthe general requirements section, `darwin-dist` also requires dylibbundler,\nwhich (along with other tools) is easiest to get via MacPorts.\n\n### Other notes\n\nAmiga OS 4, GP2X, and Pandora have not been tested recently and may have bitrot.\nOpenSolaris was previously mentioned in the list of working platforms, but\nneither old OpenSolaris nor modern Solaris can be verified currently.\n\nMegaZeux builds in ancient environments with a little manual effort, if\nfor some reason you actually need this. The oldest tested is CentOS 3:\n\n- Partial triage has been done on GCC 3.2 and 3.3, but this is low priority:\n  - 3.3: `-Wextra` added in 3.4 (trivial but left as a canary)\n  - 3.3: `-Wdeclaration-after-statement` added in 3.4\n  - 3.3: requires `--disable-stack-protector`\n  - 3.3: requires `--disable-modular`\n    (`CORE_LIBSPEC export struct graphics graphics_data` doesn't work)\n  - 3.2: `-std=gnu++98` added in 3.3\n  - 3.2: `-Wunused-macros` added in 3.3\n  - 3.2: `__attribute__(visibility(\"default\"))` also doesn't work\n- **zlib** <1.2.0 requires replacing/disabling `deflateBound` and `infback9`\n  - The numeric version macros didn't exist at this point; `ZLIB_VERNUM` was\n    added in 1.2.0.2 and the rest later.\n- **libpng** <1.2.3 works if `libpng-config` is backported or if\n  `LIBPNG_CFLAGS` and `LIBPNG_LDFLAGS` are manually supplied to `platform.inc`.\n- **libvorbis**: <1.2.2 works if `vorbis_version_string` is manually disabled.\n  libvorbis has never had numeric version macros to test.\n"
  },
  {
    "path": "LICENSE",
    "content": "\t\t    GNU GENERAL PUBLIC LICENSE\n\t\t       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.\n 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\t\t\t    Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n\t\t    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n\t\t\t    NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n\t\t     END OF TERMS AND CONDITIONS\n\n\t    How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program; if not, write to the Free Software\n    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "Makefile",
    "content": "##\n# MegaZeux Build System (GNU Make)\n#\n# NOTE: This build system is designed to not use recursive Makefiles.\n#       The rationale for this is documented here:\n#\n# https://web.archive.org/web/20200128044903/http://aegis.sourceforge.net/auug97.pdf\n##\n\n#\n# Remove all built-in rules.\n#\n.SUFFIXES:\nifeq ($(filter -r,$(MAKEFLAGS)),)\nMAKEFLAGS += -r\nendif\n\n.PHONY: all clean help_check mzx mzx.debug build build_clean source\n.PHONY: test testworlds unit unittest test_clean unit_clean\n\n#\n# Define this first so arch-specific rules don't hijack the default target.\n#\nall:\n\n-include platform.inc\ninclude version.inc\n\n#\n# ${build_root}: base location where builds are copied to be archived for release.\n# ${build}: location where the MegaZeux executable and data files should be placed.\n# Defaults to ${build_root}, but the architecture Makefile may override this to\n# use a subdirectory instead. This is useful when a platform expects a particular\n# path hierarchy within the archive (e.g. MacOS .app bundles).\n#\nbuild_root := build/${SUBPLATFORM}\nbuild := ${build_root}\n\nEXTRA_LICENSES ?=\nLICENSE_CC0    ?= arch/LICENSE.CC0\nLICENSE_DJGPP  ?= arch/LICENSE.DJGPP\nLICENSE_LGPL2  ?= arch/LICENSE.LGPL2\nLICENSE_MPL2   ?= arch/LICENSE.MPL2\nLICENSE_NEWLIB ?= arch/LICENSE.Newlib\n\n-include arch/${PLATFORM}/Makefile.in\n\nCC      ?= gcc\nCXX     ?= g++\nAR      ?= ar\nAS      ?= as\nSTRIP   ?= strip\nOBJCOPY ?= objcopy\nWINDRES ?= windres\nPEFIX   ?= true\n\nCHMOD   ?= chmod\nCP      ?= cp\nHOST_CC ?= gcc\nLN      ?= ln\nMKDIR   ?= mkdir\nMV      ?= mv\nRM      ?= rm\n\nifneq (${CROSS_COMPILE},)\nifeq (${CC},cc)\nCC      = gcc\nendif\nCC      := ${CROSS_COMPILE}${CC}\nCXX     := ${CROSS_COMPILE}${CXX}\nAR      := ${CROSS_COMPILE}${AR}\nAS      := ${CROSS_COMPILE}${AS}\nSTRIP   := ${CROSS_COMPILE}${STRIP}\nOBJCOPY := ${CROSS_COMPILE}${OBJCOPY}\nWINDRES := ${CROSS_COMPILE}${WINDRES}\nendif\n\ninclude arch/compat.inc\n\n#\n# Set up CFLAGS/LDFLAGS for all MegaZeux external dependencies.\n#\n\nifneq (${BUILD_SDL},)\n\n#\n# SDL 3\n#\nifeq (${BUILD_SDL},3)\n# Check SDL_PKG_CONFIG_PATH and PREFIX/lib/pkgconfig for sdl3.pc.\n# Note --with-path is a pkgconf extension.\nifneq ($(and ${SDL_PKG_CONFIG_PATH},$(wildcard ${SDL_PKG_CONFIG_PATH}/sdl3.pc)),)\n# nop\nelse\n# Check dependencies prefix instead for LIBDIR=. platforms.\n# This is useless for unix/darwin, which should have sdl3.pc in a\n# place pkgconf can find through normal means.\nifeq (${LIBDIR},.)\nifneq ($(wildcard ${PREFIX}/lib/pkgconfig/sdl3.pc),)\nSDL_PKG_CONFIG_PATH ?= ${PREFIX}/lib/pkgconfig\nendif\nendif # LIBDIR=.\nendif\nifneq (${SDL_PKG_CONFIG_PATH},)\nSDL_PKG_CONFIG_FLAGS = --with-path=${SDL_PKG_CONFIG_PATH}\nSDL_PKG_CONFIG_FORCE := PKG_CONFIG_PATH=\"${SDL_PKG_CONFIG_PATH}\"\nelse\nSDL_PKG_CONFIG_FORCE :=\nendif\n\nSDL_PREFIX  := $(shell ${SDL_PKG_CONFIG_FORCE} ${PKGCONF} ${SDL_PKG_CONFIG_FLAGS} sdl3 --variable=prefix)\nSDL_CFLAGS  ?= $(shell ${SDL_PKG_CONFIG_FORCE} ${PKGCONF} ${SDL_PKG_CONFIG_FLAGS} sdl3 --cflags)\nSDL_LDFLAGS ?= $(shell ${SDL_PKG_CONFIG_FORCE} ${PKGCONF} ${SDL_PKG_CONFIG_FLAGS} sdl3 --libs)\nendif # SDL3\n\n#\n# SDL 2\n#\nifeq (${BUILD_SDL},2)\n# Check PREFIX for sdl2-config.\nifneq ($(and ${SDL_PREFIX},$(wildcard ${SDL_PREFIX}/bin/sdl2-config)),)\nSDL_CONFIG  := ${SDL_PREFIX}/bin/sdl2-config\nelse\nifneq ($(wildcard ${PREFIX}/bin/sdl2-config),)\nSDL_CONFIG  := ${PREFIX}/bin/sdl2-config\nelse\nSDL_CONFIG  := sdl2-config\nendif\nendif\n\nSDL_PREFIX  ?= $(shell ${SDL_CONFIG} --prefix)\nSDL_CFLAGS  ?= $(shell ${SDL_CONFIG} --prefix=${SDL_PREFIX} --cflags | sed 's,-I,-isystem ,g')\nSDL_LDFLAGS ?= $(shell ${SDL_CONFIG} --prefix=${SDL_PREFIX} --libs)\nendif # SDL2\n\n#\n# SDL 1.2\n#\nifeq (${BUILD_SDL},1)\nEXTRA_LICENSES += ${LICENSE_LGPL2}\n\n# Check PREFIX for sdl-config.\nifneq ($(and ${SDL_PREFIX},$(wildcard ${SDL_PREFIX}/bin/sdl-config)),)\nSDL_CONFIG  := ${SDL_PREFIX}/bin/sdl-config\nelse\nifneq ($(wildcard ${PREFIX}/bin/sdl-config),)\nSDL_CONFIG  := ${PREFIX}/bin/sdl-config\nelse\nSDL_CONFIG  := sdl-config\nendif\nendif\n\nSDL_PREFIX  ?= $(shell ${SDL_CONFIG} --prefix)\nSDL_CFLAGS  ?= $(shell ${SDL_CONFIG} --prefix=${SDL_PREFIX} --cflags | sed 's,-I,-isystem ,g')\nSDL_LDFLAGS ?= $(shell ${SDL_CONFIG} --prefix=${SDL_PREFIX} --libs)\nendif # SDL1\n\n# Make these immediate so the scripts run only once.\nSDL_PREFIX  := $(SDL_PREFIX)\nSDL_CFLAGS  := $(SDL_CFLAGS)\nSDL_LDFLAGS := $(LINK_DYNAMIC_IF_MIXED) $(SDL_LDFLAGS)\n\nendif # SDL\n\n#\n# libvorbis/tremor/stb_vorbis\n#\n\nVORBIS_CFLAGS  ?= -I${PREFIX}/include -DOV_EXCLUDE_STATIC_CALLBACKS\nifeq (${VORBIS},vorbis)\nVORBIS_LDFLAGS ?= $(LINK_STATIC_IF_MIXED) -L${PREFIX}/lib -lvorbisfile -lvorbis -logg\nendif\nifeq (${VORBIS},tremor)\nVORBIS_LDFLAGS ?= $(LINK_STATIC_IF_MIXED) -L${PREFIX}/lib -lvorbisidec -logg\nendif\nifeq (${VORBIS},tremor-lowmem)\nVORBIS_LDFLAGS ?= $(LINK_STATIC_IF_MIXED) -L${PREFIX}/lib -lvorbisidec\nendif\nifeq (${VORBIS},stb_vorbis)\nVORBIS_LDFLAGS ?= -L${PREFIX}/lib\nendif\n\n#\n# MikMod (optional mod engine)\n#\n\nMIKMOD_CFLAGS  ?= -I${PREFIX}/include\nMIKMOD_LDFLAGS ?= $(LINK_STATIC_IF_MIXED) -L${PREFIX}/lib -lmikmod\nifneq (${BUILD_MIKMOD},)\nEXTRA_LICENSES += ${LICENSE_LGPL2}\nendif\n\n#\n# libopenmpt (optional mod engine)\n#\n\nOPENMPT_CFLAGS  ?= -I${PREFIX}/include\nOPENMPT_LDFLAGS ?= $(LINK_STATIC_IF_MIXED) -L${PREFIX}/lib -lopenmpt\n\n#\n# zlib\n#\n\nZLIB_CFLAGS  ?= -I${PREFIX}/include\nZLIB_LDFLAGS ?= $(LINK_STATIC_IF_MIXED) -L${PREFIX}/lib -lz\n\n#\n# libpng\n#\n\nifeq (${LIBPNG},1)\n\n# Check PREFIX for libpng-config.\nifneq ($(and ${LIBPNG_PREFIX},$(wildcard ${LIBPNG_PREFIX}/bin/libpng-config)),)\nLIBPNG_CONFIG  := ${LIBPNG_PREFIX}/bin/libpng-config\nelse\nifneq ($(wildcard ${PREFIX}/bin/libpng-config),)\nLIBPNG_CONFIG  := ${PREFIX}/bin/libpng-config\nelse\nLIBPNG_CONFIG  := libpng-config\nendif\nendif\n\nLIBPNG_CFLAGS  ?= $(shell ${LIBPNG_CONFIG} --cflags)\nLIBPNG_LDFLAGS ?= $(shell ${LIBPNG_CONFIG} --ldflags)\n\n# Make these immediate so the scripts run only once.\nLIBPNG_CFLAGS  := $(LIBPNG_CFLAGS)\nLIBPNG_LDFLAGS := $(LINK_STATIC_IF_MIXED) $(LIBPNG_LDFLAGS)\nendif\n\n#\n# X11\n#\n\nifneq (${X11DIR},)\n# BSD needs this but Fedora rpmbuild will whine about it and fail.\nifneq (${X11DIR},/usr)\nX11RPATH    ?= -Wl,-rpath,${X11LIBDIR}\nendif\nX11_CFLAGS  ?= -I${X11DIR}/include\nX11_LDFLAGS ?= -L${X11LIBDIR} -lX11 ${X11RPATH}\n# Make these immediate\nX11_CFLAGS := $(X11_CFLAGS)\nX11_LDFLAGS := $(X11_LDFLAGS)\nendif\n\n#\n# pthread\n#\n\nPTHREAD_LDFLAGS ?= -lpthread\n\n#\n# Set up general CFLAGS/LDFLAGS\n#\n\n#\n# Usually, just disable the optimizer for \"true\" debug builds and\n# define \"DEBUG\" to enable optional code at compile time.\n# Optimized builds have assert() compiled out.\n#\nifneq (${DEBUG},1)\nOPTIMIZE_FLAGS := -O3 ${OPTIMIZE_CFLAGS} ${OPTIMIZE_FLAGS}\nOPTIMIZE_DEF   ?= -DNDEBUG\nelse\nOPTIMIZE_FLAGS := -O0 ${DEBUG_CFLAGS} ${OPTIMIZE_FLAGS}\nOPTIMIZE_DEF   ?= -DDEBUG\nendif\n\n#\n# Enable a sanitizer and its corresponding extra options, if selected.\n#\nifeq (${SANITIZER},address)\nOPTIMIZE_FLAGS += -fsanitize=address -fno-omit-frame-pointer\nendif\nifeq (${SANITIZER},undefined)\n# Signed integer overflows (shift-base, signed-integer-overflow)\n# are pretty much inevitable in Robotic, so ignore them.\nOPTIMIZE_FLAGS += -fsanitize=undefined -fno-omit-frame-pointer \\\n -fno-sanitize-recover=all -fno-sanitize=shift-base,signed-integer-overflow\nendif\nifeq (${SANITIZER},thread)\nOPTIMIZE_FLAGS += -fsanitize=thread -fno-omit-frame-pointer -fPIE\nARCH_EXE_LDFLAGS += -pie\nendif\nifeq (${SANITIZER},memory)\n# Note: to be useful, this requires a fairly special build with most\n# external libraries turned off or re-built with instrumentation.\n# This sanitizer is only implemented by clang.\nOPTIMIZE_FLAGS += -fsanitize=memory -fno-omit-frame-pointer -fPIC \\\n -fsanitize-memory-track-origins=2\nARCH_EXE_LDFLAGS += -pie\nendif\n\n#\n# Enable link-time optimization.\n#\nifeq (${LTO},1)\nifeq (${HAS_F_LTO},1)\nOPTIMIZE_FLAGS += -flto\nelse\n$(warning link-time optimization not supported, ignoring.)\nendif\nendif\n\nCFLAGS   += ${OPTIMIZE_FLAGS} ${OPTIMIZE_DEF}\nCXXFLAGS += ${OPTIMIZE_FLAGS} ${OPTIMIZE_DEF}\nLDFLAGS  += ${OPTIMIZE_FLAGS}\n\n#\n# Enable C++11 for compilers that support it.\n# Anything actually using C++11 should be optional or platform-specific,\n# as features using C++11 reduce portability.\n#\nifeq (${HAS_CXX_11},1)\nCXX_STD = -std=gnu++11\nelse\nCXX_STD = -std=gnu++98\nendif\n\n#\n# Always generate debug information; this may end up being\n# stripped (on embedded platforms) or objcopy'ed out.\n#\nCFLAGS   += -std=gnu99 -g ${ARCH_CFLAGS}\nCXXFLAGS += ${CXX_STD} -g -fno-exceptions -fno-rtti ${ARCH_CXXFLAGS}\nLDFLAGS  += ${ARCH_LDFLAGS}\n\n#\n# Default warnings.\n# Note: -Wstrict-prototypes was previously turned off for Android/NDS/Wii/PSP.\n#\nwarnings := -Wall -Wextra -Wno-unused-parameter -Wwrite-strings\nwarnings += -Wundef -Wunused-macros -Wpointer-arith\nCFLAGS   += ${warnings} -Wdeclaration-after-statement -Wmissing-prototypes -Wstrict-prototypes\nCXXFLAGS += ${warnings}\n\n#\n# Optional compile flags.\n#\n\n#\n# Warn against global functions defined without a previous declaration (C++).\n#\nifeq (${HAS_W_MISSING_DECLARATIONS_CXX},1)\nCXXFLAGS += -Wmissing-declarations\nendif\n\n#\n# Warn against variable-length array (VLA) usage, which is technically valid\n# C99 but is in bad taste and isn't supported by MSVC.\n#\nifeq (${HAS_W_VLA},1)\nCFLAGS   += -Wvla\nCXXFLAGS += -Wvla\nendif\n\n#\n# Linux GCC gives spurious format truncation warnings. The snprintf\n# implementation on Linux will terminate even in the case of truncation,\n# making this largely useless. It does not trigger using mingw (where it\n# would actually matter).\n#\nifeq (${HAS_W_NO_FORMAT_TRUNCATION},1)\nCFLAGS   += -Wno-format-truncation\nCXXFLAGS += -Wno-format-truncation\nendif\n\n#\n# Old GCC versions emit false positive warnings for C++11 value initializers.\n#\nifeq (${HAS_BROKEN_W_MISSING_FIELD_INITIALIZERS},1)\nCXXFLAGS += -Wno-missing-field-initializers\nendif\n\n#\n# We enable pedantic warnings here, but this ends up turning on some things\n# we must disable by hand.\n#\n# Variadic macros are arguably less portable, but all the compilers we\n# support have them.\n#\nifeq (${HAS_PEDANTIC},1)\nCFLAGS   += -pedantic\nCXXFLAGS += -pedantic\n\nifeq (${HAS_W_NO_VARIADIC_MACROS},1)\nCFLAGS   += -Wno-variadic-macros\nCXXFLAGS += -Wno-variadic-macros\nendif\nendif\n\n#\n# KallistiOS has a pretty dire header situation\n#\nifeq (${BUILD_DREAMCAST},1)\nCFLAGS   += -Wno-strict-prototypes -Wno-pedantic\nCXXFLAGS += -Wno-pedantic\nendif\n\n#\n# As does BlocksDS, currently\n#\nifeq (${BUILD_NDS_BLOCKSDS},1)\nCFLAGS   += -Wno-strict-prototypes -Wno-pedantic -Wno-declaration-after-statement\nCXXFLAGS += -Wno-pedantic\nendif\n\n#\n# The following flags are not applicable to mingw or djgpp builds.\n#\nifneq (${PLATFORM},mingw)\nifneq (${PLATFORM},djgpp)\n\n#\n# Symbols in COFF binaries are implicitly hidden unless exported; this\n# flag just confuses GCC and must be disabled.\n#\nifeq (${HAS_F_VISIBILITY},1)\nCFLAGS   += -fvisibility=hidden\nCXXFLAGS += -fvisibility=hidden\nendif\n\nendif # PLATFORM=djgpp\nendif # PLATFORM=mingw\n\n#\n# The stack protector is optional and is generally only built for Linux/BSD and\n# Mac OS X. It also works on Windows. GCC's -fstack-protector-strong is\n# preferred when available due to better performance.\n#\nifeq (${BUILD_STACK_PROTECTOR},1)\nifeq (${HAS_F_STACK_PROTECTOR_STRONG},1)\nCFLAGS   += -fstack-protector-strong\nCXXFLAGS += -fstack-protector-strong\nelse\nifeq (${HAS_F_STACK_PROTECTOR},1)\nCFLAGS   += -fstack-protector-all\nCXXFLAGS += -fstack-protector-all\nelse\n$(warning stack protector not supported, ignoring.)\nendif\nendif\nendif\n\n#\n# Enable -fanalyzer if supported.\n#\nifeq (${BUILD_F_ANALYZER},1)\nifeq (${HAS_F_ANALYZER},1)\nCFLAGS   += -fanalyzer\nCXXFLAGS += -fanalyzer\nelse\n$(warning GCC 10+ is required for -fanalyzer, ignoring.)\nendif\nendif\n\n#\n# Enable position-independent code across the board for modular builds.\n#\nifeq (${BUILD_MODULAR},1)\nCFLAGS += -fPIC\nCXXFLAGS += -fPIC\nendif\n\n#\n# We don't want these commands to be echo'ed in non-verbose mode\n#\nifneq (${V},1)\noverride V:=\n\nCC      := @${CC}\nCXX     := @${CXX}\nAR      := @${AR}\nAS      := @${AS}\nSTRIP   := @${STRIP}\nOBJCOPY := @${OBJCOPY}\nWINDRES := @${WINDRES}\nPEFIX   := @${PEFIX}\n\nCHMOD   := @${CHMOD}\nCP      := @${CP}\nHOST_CC := @${HOST_CC}\nLN      := @${LN}\nMKDIR   := @${MKDIR}\nMV      := @${MV}\nRM      := @${RM}\nendif\n\nbuild_clean:\n\t$(if ${V},,@echo \"  RM      \" build)\n\t${RM} -r build\n\nsource: build/${TARGET}src\n\n#\n# Build source target\n# Targeting unix primarily, so turn off autocrlf if necessary.\n#\nbuild/${TARGET}src:\n\t${RM} -r build/${TARGET}\n\t${MKDIR} -p build/dist/source\n\t@git -c \"core.autocrlf=false\" checkout-index -a --prefix build/${TARGET}/\n\t${RM} -r build/${TARGET}/scripts\n\t${RM} build/${TARGET}/.gitignore build/${TARGET}/.gitattributes\n\t@cd build/${TARGET} && ${MAKE} distclean\n\t@tar -C build -Jcf build/dist/source/${TARGET}src.tar.xz ${TARGET}\n\n#\n# The SUPPRESS_ALL_TARGETS hack is required to allow the placebo \"dist\"\n# Makefile to provide an 'all:' target, which allows it to print\n# a message. Pulling in any other targets would confuse Make.\n#\n# Additionally, there are several other SUPPRESS flags that can be set to\n# conditionally disable rules. This is useful for cross-compiling builds that\n# have no use for host-based rules or for platforms that use meta targets.\n#\n# * SUPPRESS_CC_TARGETS prevents \"mzx\", etc from being added to \"all\".\n# * SUPPRESS_BUILD_TARGETS suppresses \"build\".\n# * SUPPRESS_HOST_TARGETS suppresses \"assets/help.fil\", \"test\", etc.\n#\nifneq (${SUPPRESS_ALL_TARGETS},1)\n\nmzxrun = mzxrun${BINEXT}\nmzx = megazeux${BINEXT}\n\nmzx: ${mzxrun} ${mzx}\nmzx.debug: ${mzxrun}.debug ${mzx}.debug\n\nifeq (${BUILD_MODPLUG},1)\nBUILD_GDM2S3M=1\nendif\n\n%/.build:\n\t$(if ${V},,@echo \"  MKDIR   \" $@)\n\t${MKDIR} $@\n\n%.debug: %\n\t$(if ${V},,@echo \"  OBJCOPY \" --only-keep-debug $< $@)\n\t${OBJCOPY} --only-keep-debug $< $@\n\t${PEFIX} $@\n\t${CHMOD} a-x $@\nifneq (${NO_STRIP_IN_DEBUGLINK},1)\n\t$(if ${V},,@echo \"  STRIP   \" --strip-unneeded $<)\n\t${STRIP} --strip-unneeded $<\nendif\n\t$(if ${V},,@echo \"  OBJCOPY \" --add-gnu-debuglink $@ $<)\n\t${OBJCOPY} --add-gnu-debuglink=$@ $<\n\t${PEFIX} $<\n\t@touch $@\n\ninclude src/Makefile.in\n\nclean: mzx_clean test_clean unit_clean\n\nifneq (${SUPPRESS_CC_TARGETS},1)\nall: mzx\ndebuglink: all mzx.debug\nendif\n\nifeq (${BUILD_UTILS},1)\ninclude src/utils/Makefile.in\nclean: utils_clean\nifneq (${SUPPRESS_CC_TARGETS},1)\ndebuglink: utils utils.debug\nall: utils\nendif\nendif\n\nifneq (${SUPPRESS_BUILD_TARGETS},1)\n\nifeq (${build},)\nbuild := ${build_root}\nendif\n\n.PHONY: ${build}\n\nbuild: ${build} ${build}/assets ${build}/docs\n\n${build}:\n\t${RM} -r ${build_root}\n\t${MKDIR} -p ${build}\n\t${CP} config.txt LICENSE arch/LICENSE.3rd ${EXTRA_LICENSES} ${build}\n\t@if test -f ${mzxrun}; then \\\n\t\tcp ${mzxrun} ${build}; \\\n\tfi\n\t@if test -f ${mzxrun}.debug; then \\\n\t\tcp ${mzxrun}.debug ${build}; \\\n\tfi\nifeq (${BUILD_EDITOR},1)\n\t@if test -f ${mzx}; then \\\n\t\tcp ${mzx} ${build}; \\\n\tfi\n\t@if test -f ${mzx}.debug; then \\\n\t\tcp ${mzx}.debug ${build}; \\\n\tfi\nendif\nifeq (${BUILD_MODULAR},1)\n\t${CP} ${core_target} ${editor_target} ${build}\n\t@if test -f ${core_target}.debug; then \\\n\t\tcp ${core_target}.debug ${build}; \\\n\tfi\n\t@if test -f ${editor_target}.debug; then \\\n\t\tcp ${editor_target}.debug ${build}; \\\n\tfi\nendif\nifeq (${BUILD_UTILS},1)\n\t${MKDIR} ${build}/utils\n\t${CP} ${checkres} ${downver} ${build}/utils\n\t${CP} ${hlp2txt} ${txt2hlp} ${build}/utils\n\t${CP} ${ccv} ${png2smzx} ${y4m2smzx} ${build}/utils\n\t@if [ -f \"${checkres}.debug\" ]; then cp ${checkres}.debug ${build}/utils; fi\n\t@if [ -f \"${downver}.debug\" ]; then cp ${downver}.debug ${build}/utils; fi\n\t@if [ -f \"${hlp2txt}.debug\" ]; then cp ${hlp2txt}.debug ${build}/utils; fi\n\t@if [ -f \"${txt2hlp}.debug\" ]; then cp ${txt2hlp}.debug ${build}/utils; fi\n\t@if [ -f \"${ccv}.debug\" ]; then cp ${ccv}.debug ${build}/utils; fi\n\t@if [ -f \"${png2smzx}.debug\" ]; then cp ${png2smzx}.debug ${build}/utils; fi\n\t@if [ -f \"${y4m2smzx}.debug\" ]; then cp ${y4m2smzx}.debug ${build}/utils; fi\nendif\n\n${build}/docs: ${build}\n\t${MKDIR} -p ${build}/docs\n\t${CP} docs/macro.txt docs/keycodes.html docs/mzxhelp.html ${build}/docs\n\t${CP} docs/joystick.html docs/cycles_and_commands.txt ${build}/docs\n\t${CP} docs/fileform.html ${build}/docs\n\t${CP} docs/changelog.txt docs/platform_matrix.html ${build}/docs\n\n${build}/assets: ${build}\n\t${MKDIR} -p ${build}/assets\n\t${CP} assets/default.chr assets/edit.chr ${build}/assets\n\t${CP} assets/smzx.pal ${build}/assets\nifeq (${BUILD_EDITOR},1)\n\t${CP} assets/ascii.chr assets/blank.chr ${build}/assets\n\t${CP} assets/smzx.chr assets/smzx2.chr ${build}/assets\nendif\nifeq (${BUILD_HELPSYS},1)\n\t${CP} assets/help.fil ${build}/assets\nendif\nifeq (${BUILD_RENDER_GL_PROGRAM},1)\n\t${MKDIR} -p ${build}/assets/glsl/scalers\n\t${CP} assets/glsl/*.vert ${build}/assets/glsl\n\t${CP} assets/glsl/*.frag ${build}/assets/glsl\n\t${CP} assets/glsl/README.md ${build}/assets/glsl\n\t${CP} assets/glsl/scalers/*.frag assets/glsl/scalers/*.vert \\\n\t\t${build}/assets/glsl/scalers\nendif\nifeq (${BUILD_GAMECONTROLLERDB},1)\n\t${CP} assets/gamecontrollerdb.txt \\\n\t ${build}/assets\nendif\nifeq (${BUILD_UTILS},1)\n\t${CP} contrib/mzvplay/mzvplay.txt \\\n\t ${build}/utils\nendif\n\nendif # !SUPPRESS_BUILD_TARGETS\n\ndistclean: clean\n\t@echo \"  DISTCLEAN\"\n\t@rm -f src/config.h\n\t@echo \"PLATFORM=none\" > platform.inc\n\nifneq (${SUPPRESS_HOST_TARGETS},1)\n\nassets/help.fil: ${txt2hlp} docs/WIPHelp.txt\n\t$(if ${V},,@echo \"  txt2hlp \" $@)\n\t@src/utils/txt2hlp docs/WIPHelp.txt $@\n\ndocs/mzxhelp.html: ${hlp2html} docs/WIPHelp.txt\n\t$(if ${V},,@echo \"  hlp2html\" $@)\n\t@src/utils/hlp2html docs/WIPHelp.txt docs/mzxhelp.html\n\nhelp_check: ${hlp2txt} assets/help.fil\n\t@src/utils/hlp2txt assets/help.fil help.txt\n\t@echo @ >> help.txt\n\t@diff --strip-trailing-cr -q docs/WIPHelp.txt help.txt\n\t@rm -f help.txt\n\n-include unit/Makefile.in\n\ntest: unit\n\ntest testworlds:\nifeq (${BUILD_MODULAR},1)\n\t@${SHELL} testworlds/run.sh ${PLATFORM} \"$(realpath ${core_target})\"\nelse\n\t@${SHELL} testworlds/run.sh ${PLATFORM}\nendif\n\nendif # !SUPPRESS_HOST_TARGETS\n\ntest_clean:\n\t@rm -rf testworlds/log\n\nendif # !SUPPRESS_ALL_TARGETS\n"
  },
  {
    "path": "README.md",
    "content": "# MegaZeux\n[Official MegaZeux git repository](https://github.com/AliceLR/megazeux)\n\nMegaZeux is a game creation system (GCS) created by game developer Alexis Janson in 1994.\nOriginally a DOS program, in 2005 a multi-platform port was released by Exophase. The most\nrecent version of MegaZeux is 2.93d, which was released on June 9th, 2025.\n\nMegaZeux is officially supported on Windows, Linux, macOS, BSD, Haiku, HTML5,\nand MS-DOS, as well as numerous console ports. See platform_matrix.html for a\nfull list of ports.\n\n## Downloads\n\nWindows, MS-DOS, and console releases are packaged with a copy of the game\nCaverns of Zeux. Older versions are available at [DigitalMZX](https://www.digitalmzx.com/)\nor [on the Github releases page](https://github.com/AliceLR/megazeux/releases).\n\n<!-- Download URLs. -->\n[megazeux-w64]: https://www.digitalmzx.com/download.php?latest=windows64\n[megazeux-w32]: https://www.digitalmzx.com/download.php?latest=windows32\n[megazeux-dos]: https://www.digitalmzx.com/download.php?latest=dos\n[megazeux-dos32]: https://www.digitalmzx.com/download.php?latest=dosdjgpp\n[megazeux-osx]: https://www.digitalmzx.com/download.php?latest=osx\n[megazeux-ppc]: https://www.digitalmzx.com/download.php?latest=osxppc\n[megazeux-u64]: https://www.digitalmzx.com/download.php?latest=ubuntu64\n[megazeux-ults]: https://www.digitalmzx.com/download.php?latest=ubuntu64lts\n[megazeux-d64]: https://www.digitalmzx.com/download.php?latest=debian64\n[megazeux-d32]: https://www.digitalmzx.com/download.php?latest=debian32\n[megazeux-f64]: https://www.digitalmzx.com/download.php?latest=fedora64\n[megazeux-rpi]: https://www.digitalmzx.com/download.php?latest=raspbian\n[megazeux-flat]: https://www.digitalmzx.com/download.php?latest=flatpak\n[megazeux-and]: https://www.digitalmzx.com/download.php?latest=android\n[megazeux-html]: https://www.digitalmzx.com/download.php?latest=html5\n[megazeux-nds]: https://www.digitalmzx.com/download.php?latest=nds\n[megazeux-3ds]: https://www.digitalmzx.com/download.php?latest=3ds\n[megazeux-wii]: https://www.digitalmzx.com/download.php?latest=wii\n[megazeux-wiiu]: https://www.digitalmzx.com/download.php?latest=wiiu\n[megazeux-swi]: https://www.digitalmzx.com/download.php?latest=switch\n[megazeux-psp]: https://www.digitalmzx.com/download.php?latest=psp\n[megazeux-vita]: https://www.digitalmzx.com/download.php?latest=psvita\n[megazeux-dc]: https://www.digitalmzx.com/download.php?latest=dreamcast\n[megazeux-src]: https://www.digitalmzx.com/download.php?latest=src\n\n<!-- Images for download links. -->\n[arch-w64]: contrib/archicons/windows64.png \"Windows x64\"\n[arch-w32]: contrib/archicons/windows32.png \"Windows x86\"\n[arch-dos]: contrib/archicons/dos.png       \"MS DOS (MZX 2.70)\"\n[arch-dos32]:contrib/archicons/dosdjgpp.png \"MS DOS (32-bit)\"\n[arch-osx]: contrib/archicons/osx.png       \"macOS\"\n[arch-ppc]: contrib/archicons/maccompat.png \"macOS (Compatible)\"\n[arch-u64]: contrib/archicons/ubuntu64.png  \"Ubuntu AMD64\"\n[arch-ults]: contrib/archicons/ubuntu64lts.png  \"Ubuntu AMD64 LTS\"\n[arch-d64]: contrib/archicons/debian64.png  \"Debian AMD64\"\n[arch-d32]: contrib/archicons/debian32.png  \"Debian i386\"\n[arch-f64]: contrib/archicons/fedora64.png  \"Fedora x86_64\"\n[arch-rpi]: contrib/archicons/raspbian.png  \"Raspbian\"\n[arch-flat]:contrib/archicons/flatpak.png   \"Flatpak\"\n[arch-and]: contrib/archicons/android.png   \"Android\"\n[arch-html]:contrib/archicons/html5.png     \"HTML5 (Emscripten)\"\n[arch-aur]: contrib/archicons/archlinux.png \"Arch Linux (via AUR)\"\n[arch-void]:contrib/archicons/voidlinux.png \"Void Linux\"\n[arch-gen]: contrib/archicons/gentoo.png    \"Gentoo\"\n[arch-alp]: contrib/archicons/alpine.png    \"Alpine Linux\"\n[arch-nds]: contrib/archicons/nds.png       \"Nintendo DS\"\n[arch-3ds]: contrib/archicons/3ds.png       \"Nintendo 3DS\"\n[arch-wii]: contrib/archicons/wii.png       \"Nintendo Wii\"\n[arch-wiiu]:contrib/archicons/wiiu.png      \"Nintendo Wii U\"\n[arch-swi]: contrib/archicons/switch.png    \"Nintendo Switch\"\n[arch-psp]: contrib/archicons/psp.png       \"PlayStation Portable\"\n[arch-vita]:contrib/archicons/psvita.png    \"PlayStation Vita\"\n[arch-dc]:  contrib/archicons/dreamcast.png \"Sega Dreamcast\"\n[arch-ami]: contrib/archicons/amiga.png     \"Amiga OS 4\"\n[arch-gp2x]:contrib/archicons/gp2x.png      \"GP2X\"\n[arch-pand]:contrib/archicons/pandora.png   \"Pandora\"\n[arch-src]: contrib/archicons/src.png       \"Source code\"\n\n<!-- Displays the download links as images. -->\n&nbsp; [![Windows x64         ][arch-w64]][megazeux-w64]\n&nbsp; [![Windows x86         ][arch-w32]][megazeux-w32]\n&nbsp; [![Mac OS X            ][arch-osx]][megazeux-osx]\n&nbsp; [![macOS (Compatible)) ][arch-ppc]][megazeux-ppc]\n&nbsp; [![Ubuntu AMD64        ][arch-u64]][megazeux-u64]\n&nbsp; [![Ubuntu AMD64 LTS    ][arch-ults]][megazeux-ults]\n&nbsp; [![Debian AMD64        ][arch-d64]][megazeux-d64]\n&nbsp; [![Debian i386         ][arch-d32]][megazeux-d32]\n&nbsp; [![Fedora x86_64       ][arch-f64]][megazeux-f64]\n&nbsp; [![Raspbian            ][arch-rpi]][megazeux-rpi]\n&nbsp; [![Flatpak             ][arch-flat]][megazeux-flat]\n&nbsp; [![Android             ][arch-and]][megazeux-and]\n&nbsp; [![HTML5 (Emscripten)  ][arch-html]][megazeux-html]\n&nbsp; [![Nintendo DS         ][arch-nds]][megazeux-nds]\n&nbsp; [![Nintendo 3DS        ][arch-3ds]][megazeux-3ds]\n&nbsp; [![Nintendo Wii        ][arch-wii]][megazeux-wii]\n&nbsp; [![Nintendo Wii U      ][arch-wiiu]][megazeux-wiiu]\n&nbsp; [![Nintendo Switch     ][arch-swi]][megazeux-swi]\n&nbsp; [![PlayStation Portable][arch-psp]][megazeux-psp]\n&nbsp; [![PlayStation Vita    ][arch-vita]][megazeux-vita]\n&nbsp; [![Sega Dreamcast      ][arch-dc]][megazeux-dc]\n&nbsp; [![DOS (32-bit)        ][arch-dos32]][megazeux-dos32]\n&nbsp; [![MS DOS (MZX 2.70)   ][arch-dos]][megazeux-dos]\n&nbsp; [![Source code         ][arch-src]][megazeux-src]\n\nThe following platforms have MegaZeux releases available via repository:\n\n&nbsp; [![Arch Linux (via AUR)][arch-aur]](https://aur.archlinux.org/packages/megazeux/)\n&nbsp; [![Void Linux][arch-void]](https://github.com/void-linux/void-packages/tree/master/srcpkgs/megazeux)\n&nbsp; [![Gentoo][arch-gen]](https://github.com/Spectere/megazeux-overlay)\n&nbsp; [![Alpine Linux][arch-alp]](https://pkgs.alpinelinux.org/packages?name=megazeux)\n&nbsp; [![Amiga OS 4 (outdated)][arch-ami]](http://aminet.net/package/game/misc/pfp-mgzx)\n&nbsp; [![GP2X (outdated)][arch-gp2x]](https://dl.openhandhelds.org/cgi-bin/gp2x.cgi?0,0,0,0,26,2920)\n&nbsp; [![Pandora (outdated)][arch-pand]](https://repo.openpandora.org/?page=detail&app=megazeux_ptitseb)\n\n## Building MegaZeux\n\nSee BUILDING.md for more information about compiling MegaZeux from source.\n\n## Credits\n\n| MegaZeux 2.9x           |                                                            |\n| ----------------------- | ---------------------------------------------------------- |\n| Alice Rowan             | Development/current maintainer                             |\n| Terryn                  | Help file; testing                                         |\n| Lancer X                | Development; testing                                       |\n| Adrian Siekierka        | Development; testing; various ports                        |\n| Dizzy O'Malley-Morrison | Development; testing; Ubuntu/Debian/Raspbian/Arch packages |\n| Spectere                | Development; testing; Xcode and MSVC projects              |\n| Alistair Strachan       | Development                                                |\n| Others                  | Various                                                    |\n\n#### MegaZeux Credits\n\nProgramming and Overall Design by Gilead Kutnick (Exophase),\nAlistair Strachan (ajs), Alice Rowan (Lachesis) and Lancer-X.\n\nBased off of original program and source code by Alexis Janson.\n\nHelp file by Terryn.\n\nDefault SMZX palette by Joel Lamontagne (LogiCow).\n\nccv utility by Lancer-X.\npng2smzx utility by Alan Williams (Mr_Alert).\ncheckres utility by Josh Matthews (Revvy), ajs and Lachesis.\n\nPort contributors: Adrian Siekierka (asiekierka) [3DS],\nMr_Alert [Wii], Kevin Vance [NDS], Simon Parzer [GP2X].\n\nRenderer code contributors: LogiCow, Mr_Alert.\nShader code contributors: David Cravens (astral), GreaseMonkey.\n\nIcon by Quantum P.; Extra icons by LogiCow.\nGDM conversion by ajs and MadBrain.\nOther past contributors: Spider124, Koji, JZig, Akwende, MenTaLguY.\n\n#### Special Thanks\n\nDizzy (Testing, .deb and Arch packages)\nSpectere (Testing, OS X Builds, MSVC project)\nTerryn (Testing)\nmzxgiant (MSVC Testing, Bug Fixes)\nmzxrules (Testing)\nQuantum P. (OS X Testing / Builds)\nWervyn (Testing)\n\n## License\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License as\npublished by the Free Software Foundation; either version 2 of\nthe License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\nGeneral Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n## Contact\n\nWant to talk about MegaZeux? Encountered a bug or would like a new feature?\nThe MegaZeux community and development team can be contacted through Discord,\nGitHub, or the DigitalMZX forums.\n\n[MegaZeux Discord](https://discord.gg/XJCvb4P) <br/>\n[DigitalMZX forums](https://www.digitalmzx.com/forums/) <br/>\n[MegaZeux Bug tracker](https://www.digitalmzx.com/forums/index.php?app=tracker&showproject=4) <br/>\n[MegaZeux Feature requests](https://www.digitalmzx.com/forums/index.php?app=tracker&showproject=9) <br/>\n[AliceLR on GitHub](https://github.com/AliceLR) <br/>\n\n## Resources\n\n[Official MegaZeux game archive](https://www.digitalmzx.com/) <br/>\n[MegaZeux help file](https://www.digitalmzx.com/mzx_help.html) <br/>\n[Development roadmap](https://www.digitalmzx.com/forums/index.php?showtopic=15226)\n"
  },
  {
    "path": "arch/3ds/CONFIG.3DS",
    "content": "#!/bin/sh\n\n./config.sh --platform 3ds --enable-release --enable-lto --enable-meter \\\n            --disable-sdl --enable-tremor --disable-screenshots --disable-utils \\\n            --enable-stdio-redirect \"$@\"\n\n"
  },
  {
    "path": "arch/3ds/Makefile.in",
    "content": "#\n# Nintendo 3DS Makefile\n#\n\ninclude $(DEVKITARM)/3ds_rules\n\n.PHONY: package clean\n\nifeq ($(strip $(DEVKITPRO)),)\n$(error \"DEVKITPRO must be set in your environment.\")\nendif\n\nifeq ($(strip $(DEVKITARM)),)\n$(error \"DEVKITARM must be set in your environment.\")\nendif\n\nifneq (${BUILD_EDITOR},)\nAPP_TITLE_MZX = MegaZeux\nAPP_TITLE_MZXRUN = MegaZeux (MZXRun)\nelse\nAPP_TITLE_MZXRUN = MegaZeux\nendif\n\nAPP_DESCRIPTION = Game creation system\nAPP_AUTHOR = MegaZeux Developers\nAPP_ICON = contrib/icons/generated/icon_48.png\n\nEXTRA_LICENSES += ${LICENSE_MPL2} ${LICENSE_NEWLIB}\n\n#\n# 3DS target rules\n#\n\n# Block --host, which will break things.\nCROSS_COMPILE =\n# Don't strip when generating debug symbols.\nNO_STRIP_IN_DEBUGLINK ?= 1\n# Disable rules that require target code to run natively.\nSUPPRESS_HOST_TARGETS ?= 1\n\n#\n# Override library paths.\n#\n\n#\n# As of devkitARM r51 $(PORTLIBS) has multiple paths...\n#\nPORTLIBS_INCLUDES := $(foreach dir, $(PORTLIBS), -isystem $(dir)/include)\nPORTLIBS_LIBS     := $(foreach dir, $(PORTLIBS), -L$(dir)/lib)\n\nEXTRA_INCLUDES := -isystem $(CTRULIB)/include ${PORTLIBS_INCLUDES}\nEXTRA_LIBS := -L$(CTRULIB)/lib ${PORTLIBS_LIBS}\nifeq (${DEBUG},1)\nEXTRA_LIBS += -lcitro3dd -lctrud -lpng16 -lz\nelse\nOPTIMIZE_FLAGS += -fomit-frame-pointer -ffunction-sections\nEXTRA_LIBS += -lcitro3d -lctru -lpng16 -lz\nendif\n\nMACHDEP = -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft -mword-relocations\n\nifneq (${BUILD_SDL},)\n# devkitPro's SDL 1.2 port accidentally defines key_press and key_release as\n# global symbols.\n3DS_CFLAGS    += -Dkey_press=mzx_key_press -Dkey_release=mzx_key_release\nendif\n\n3DS_CFLAGS    += ${EXTRA_INCLUDES} ${MACHDEP} -DARM11 -D__3DS__ -Iarch/3ds\n\nARCH_CFLAGS   += ${3DS_CFLAGS}\nARCH_CXXFLAGS += ${3DS_CFLAGS}\nARCH_LDFLAGS  += ${EXTRA_LIBS} ${MACHDEP} -specs=3dsx.specs\n\nLIBPNG_CFLAGS =\nLIBPNG_LDFLAGS = -lpng16\n\n#\n# Vile hack, remove me ASAP\n# (also, vile hack in a vile hack to get the shaders to build parallel)\n#\narch/3ds/%.o: arch/3ds/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} -Wno-unused-macros -c $< -o $@\n\narch/3ds/%.o: arch/3ds/%.cpp arch/3ds/shader_2d.shbin.o arch/3ds/shader_playfield.shbin.o\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CXX} -MD ${core_cxxflags} ${core_flags} -Wno-unused-macros -c $< -o $@\n\n# Do not delete these, since doing so will retrigger .o rebuilds above\n.SECONDARY: arch/3ds/shader_2d_shbin.h arch/3ds/shader_playfield_shbin.h\n\narch/3ds/%.shbin: arch/3ds/%.v.pica arch/3ds/%.g.pica\n\t$(if ${V},,@echo \"  PICASSO \" $^)\n\t@picasso -o $@ $^\n\narch/3ds/%.shbin.o: arch/3ds/%.shbin arch/3ds/%_shbin.h\n\t$(if ${V},,@echo \"  AS      \" $<)\n\t@bin2s $< > $<.S\n\t@# Shut up the assembler since bin2s doesn't emit a trailing newline:\n\t@echo '' >> $<.S\n\t$(AS) $<.S -o $@\n\narch/3ds/%_shbin.h: arch/3ds/%.shbin\n\t$(eval CURLOC := $(patsubst %_shbin.h,%.shbin,$@))\n\t$(eval CURBIN := $(patsubst %_shbin.h,%.shbin,$(notdir $@)))\n\t$(if ${V},,@echo \"  ECHO    \" $@)\n\t@echo \"extern const u8\" `(echo $(CURBIN) | sed -e 's/^\\([0-9]\\)/_\\1/' | tr . _)`\"_end[];\" > `(echo $(CURLOC) | tr . _)`.h\n\t@echo \"extern const u8\" `(echo $(CURBIN) | sed -e 's/^\\([0-9]\\)/_\\1/' | tr . _)`\"[];\" >> `(echo $(CURLOC) | tr . _)`.h\n\t@echo \"extern const u32\" `(echo $(CURBIN) | sed -e 's/^\\([0-9]\\)/_\\1/' | tr . _)`_size\";\" >> `(echo $(CURLOC) | tr . _)`.h\n\n#\n# Target-specific variables to force the .3dsx files to have different titles...\n#\nmzxrun.smdh: APP_TITLE=${APP_TITLE_MZXRUN}\nmegazeux.smdh: APP_TITLE=${APP_TITLE_MZX}\n\npackage: mzx mzxrun.smdh megazeux.smdh\n\t3dsxtool ${mzxrun} ${mzxrun}.3dsx --smdh=${mzxrun}.smdh\t--romfs=arch/3ds/romfs\nifneq (${BUILD_EDITOR},)\n\t3dsxtool ${mzx} ${mzx}.3dsx --smdh=${mzx}.smdh --romfs=arch/3ds/romfs\nendif\n\nclean:\n\t${RM} ${mzxrun}.smdh ${mzxrun}.3dsx ${mzx}.smdh ${mzx}.3dsx\n\t${RM} arch/3ds/*.d arch/3ds/*.o arch/3ds/*shbin*\n\nbuild := ${build_root}/3ds/megazeux\nbuild: package ${build}\nifeq ($(BUILD_SDL),)\n\t${CP} arch/3ds/pad.config ${build}\nelse\nifeq ($(BUILD_SDL),1)\n\t${CP} arch/3ds/pad.config.sdl12 ${build}/pad.config\nelse\n\t${CP} arch/3ds/pad.config.sdl2 ${build}/pad.config\nendif\nendif\n\t${CP} ${mzxrun}.3dsx ${build}\nifneq (${BUILD_EDITOR},)\n\t${CP} ${mzx}.3dsx ${build}\nendif\n\t${RM} ${build}/${mzxrun} ${build}/${mzxrun}.debug\n\t${RM} ${build}/${mzx} ${build}/${mzx}.debug\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/3ds/README.md",
    "content": "## Preparation\n\nYou need to install the latest versions of the devkitARM\ntoolchain, as well as the necessary 3DS tooling.\n\nYou also need to install zlib, libpng and libtremor. This\ncan be done with the following command:\n\n    pacman -S 3ds-libogg 3ds-libpng 3ds-libvorbisidec 3ds-zlib\n\n## Configuring\n\nSee CONFIG.3DS for an optimal `config.sh` configure line. You need\nto ensure DEVKITPRO and DEVKITARM are both defined and valid.\n\n## Building\n\nFor the moment, you need to build with:\n\n    make package\n\nThis will emit the \"mzxrun.3dsx\" file.\n\n## Packaging\n\nYou can then use the usual `make archive` to build a\nbuild/dist/3ds/mzxgit-3ds.zip file for distribution.\n"
  },
  {
    "path": "arch/3ds/audio.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2016 Adrian Siekierka <asiekierka@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/event.h\"\n#include \"../../src/platform.h\"\n#include \"../../src/graphics.h\"\n#include \"../../src/audio/audio.h\"\n#include \"../../src/audio/audio_struct.h\"\n\n#include <3ds.h>\n#include <string.h>\n#include \"platform.h\"\n\n#ifdef CONFIG_AUDIO\n\nstatic u8 *audio_buffer;\nstatic ndspWaveBuf ndsp_buffer[2];\nstatic unsigned buffer_frames;\nstatic unsigned buffer_size;\nstatic bool soundFillBlock = false;\n\nstatic void ndsp_callback(void *dud)\n{\n  if(ndsp_buffer[soundFillBlock].status == NDSP_WBUF_DONE)\n  {\n    audio_mixer_render_frames(ndsp_buffer[soundFillBlock].data_pcm16,\n     buffer_frames, 2, SAMPLE_S16);\n\n    DSP_FlushDataCache(ndsp_buffer[soundFillBlock].data_pcm8, buffer_size);\n    ndspChnWaveBufAdd(0, &ndsp_buffer[soundFillBlock]);\n    soundFillBlock = !soundFillBlock;\n  }\n}\n\nvoid init_audio_platform(struct config_info *conf)\n{\n  // buffer size must be multiple of 32 bytes(?), so samples must be multiple of 8\n  unsigned frames = (conf->audio_buffer_samples + 7) & ~7;\n  float mix[12];\n\n  audio_buffer = NULL;\n  if(!audio_mixer_init(conf->audio_sample_rate, frames, 2))\n    return;\n\n  buffer_frames = audio.buffer_frames;\n  buffer_size = buffer_frames * sizeof(int16_t) * 2 /* stereo */;\n\n  if(ndspInit() != 0)\n    return;\n\n  memset(mix, 0, sizeof(mix));\n  mix[0] = mix[1] = 1.0f;\n  ndspSetOutputMode(NDSP_OUTPUT_STEREO);\n  ndspSetOutputCount(1);\n  ndspSetMasterVol(1.0f);\n\n  audio_buffer = clinearAlloc(buffer_size * 2, 0x80);\n  memset(audio_buffer, 0, buffer_size * 2);\n  ndspSetCallback(ndsp_callback, audio_buffer);\n\n  memset(&ndsp_buffer[0], 0, sizeof(ndspWaveBuf));\n  memset(&ndsp_buffer[1], 0, sizeof(ndspWaveBuf));\n  ndspChnReset(0);\n  ndspChnSetInterp(0, NDSP_INTERP_LINEAR);\n  ndspChnSetRate(0, audio.output_frequency);\n  ndspChnSetFormat(0, NDSP_CHANNELS(2) | NDSP_ENCODING(NDSP_ENCODING_PCM16));\n  ndspChnSetMix(0, mix);\n\n  ndsp_buffer[0].data_vaddr = &audio_buffer[0];\n  ndsp_buffer[0].nsamples = buffer_frames;\n  ndsp_buffer[1].data_vaddr = &audio_buffer[buffer_size];\n  ndsp_buffer[1].nsamples = buffer_frames;\n\n  ndspChnWaveBufAdd(0, &ndsp_buffer[0]);\n  ndspChnWaveBufAdd(0, &ndsp_buffer[1]);\n}\n\nvoid quit_audio_platform(void)\n{\n  if(audio_buffer)\n  {\n    linearFree(audio_buffer);\n    audio_buffer = NULL;\n\n    ndspExit();\n  }\n}\n\n#endif // CONFIG_AUDIO\n"
  },
  {
    "path": "arch/3ds/event.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2016 Adrian Siekierka <asiekierka@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/event.h\"\n#include \"../../src/graphics.h\"\n#include \"../../src/platform.h\"\n#include \"../../src/util.h\"\n\n#include <3ds.h>\n#include <stdint.h>\n\n#include \"event.h\"\n#include \"keyboard.h\"\n\nextern struct input_status input;\nstatic enum bottom_screen_mode b_mode;\nstatic enum focus_mode allow_focus_changes = FOCUS_ALLOW;\nstatic boolean is_dragging = false;\n\nboolean update_hid(void);\n\nenum focus_mode get_allow_focus_changes(void)\n{\n  return allow_focus_changes;\n}\n\nenum bottom_screen_mode get_bottom_screen_mode(void)\n{\n  return b_mode;\n}\n\nboolean __update_event_status(void)\n{\n  boolean retval = false;\n  retval |= aptMainLoop();\n  retval |= update_hid();\n  return retval;\n}\n\nboolean __peek_exit_input(void)\n{\n  /* FIXME stub */\n  return false;\n}\n\nvoid __wait_event(void)\n{\n  while(!__update_event_status())\n    gspWaitForVBlank();\n}\n\nvoid __warp_mouse(int x, int y)\n{\n  // Since the touchscreen stylus can't be warped, focus there instead.\n  focus_pixel(x, y);\n}\n\n// Convert the 3DS axis ranges to the MZX axis ranges. The 3DS uses values in\n// (roughly) the -150 to 150 range, but MZX uses the full range of a int16_t.\n// The 3DS Y axis is also inverted from the typical axis direction, but that\n// is fixed in check_circle below.\nstatic inline int16_t axis_convert(s16 value)\n{\n  int new_value = ((int)value) * 32768 / 150;\n  return (int16_t)CLAMP(new_value, -32768, 32767);\n}\n\nstatic inline boolean check_circle(struct buffered_status *status,\n circlePosition *current, circlePosition *prev, int axis_x, int axis_y)\n{\n  boolean rval = false;\n\n  if(current->dx != prev->dx)\n  {\n    joystick_axis_update(status, 0, axis_x, axis_convert(current->dx));\n    rval = true;\n  }\n\n  if(current->dy != prev->dy)\n  {\n    // 3DS has an inverted Y axis on the circle pad vs. most controllers.\n    joystick_axis_update(status, 0, axis_y, axis_convert(-current->dy));\n    rval = true;\n  }\n\n  return rval;\n}\n\nstatic inline boolean check_hat(struct buffered_status *status,\n uint32_t down, uint32_t up, uint32_t key, enum joystick_hat dir)\n{\n  if(down & key)\n  {\n    joystick_hat_update(status, 0, dir, true);\n    return true;\n  }\n  else\n\n  if(up & key)\n  {\n    joystick_hat_update(status, 0, dir, false);\n    return true;\n  }\n\n  return false;\n}\n\nstatic inline boolean check_joy(struct buffered_status *status,\n uint32_t down, uint32_t up, uint32_t key, int button)\n{\n  if(down & key)\n  {\n    joystick_button_press(status, 0, button);\n    return true;\n  }\n  else\n\n  if(up & key)\n  {\n    joystick_button_release(status, 0, button);\n    return true;\n  }\n\n  return false;\n}\n\nstatic inline boolean ctr_is_mouse_area(touchPosition *touch)\n{\n  int mx, my;\n\n  if(b_mode == BOTTOM_SCREEN_MODE_KEYBOARD)\n  {\n    mx = touch->px * 4 - 320;\n    my = touch->py * 4 - (13 * 4);\n\n    if(mx < 0 || mx >= 640 || my < 0 || my >= 350)\n      return false;\n\n    else\n      return true;\n  }\n  else\n  {\n    return true;\n  }\n}\n\nstatic inline boolean ctr_update_touch(struct buffered_status *status,\n touchPosition *touch)\n{\n  int mx, my;\n\n  if(b_mode == BOTTOM_SCREEN_MODE_KEYBOARD)\n  {\n    mx = touch->px * 4 - 320;\n    my = touch->py * 4 - (13 * 4);\n\n    if(mx < 0 || mx >= 640 || my < 0 || my >= 350)\n      return false;\n  }\n  else\n  {\n    int s_height = ctr_get_subscreen_height();\n    mx = touch->px * 2;\n    my = ((int)(touch->py - ((240 - s_height) / 2)) * 350 / s_height);\n    if(mx < 0) mx = 0;\n    if(mx >= 640) mx = 639;\n    if(my < 0) my = 0;\n    if(my >= 350) my = 349;\n  }\n\n  if(mx != status->mouse_pixel_x || my != status->mouse_pixel_y)\n  {\n    status->mouse_pixel_x = mx;\n    status->mouse_pixel_y = my;\n    status->mouse_x = mx / 8;\n    status->mouse_y = my / 14;\n    status->mouse_moved = true;\n\n    allow_focus_changes = FOCUS_PASS;\n    focus_pixel(mx, my);\n    allow_focus_changes = FOCUS_FORBID;\n\n    return true;\n  }\n  else\n  {\n    return false;\n  }\n}\n\nstatic inline boolean ctr_update_cstick(struct buffered_status *status)\n{\n  circlePosition pos;\n  int dmx, dmy, nmx, nmy;\n\n  hidCstickRead(&pos);\n  dmx = pos.dx / 3;\n  dmy = pos.dy / 3;\n\n  if(dmx != 0 || dmy != 0)\n  {\n    nmx = status->mouse_pixel_x + dmx;\n    nmy = status->mouse_pixel_y + dmy;\n    if(nmx < 0) nmx = 0;\n    if(nmx >= 640) nmx = 639;\n    if(nmy < 0) nmy = 0;\n    if(nmy >= 350) nmy = 349;\n\n    if(nmx != status->mouse_pixel_x || nmy != status->mouse_pixel_y)\n    {\n      status->mouse_pixel_x = nmx;\n      status->mouse_pixel_y = nmy;\n      status->mouse_x = nmx / 8;\n      status->mouse_y = nmy / 14;\n      status->mouse_moved = true;\n\n      focus_pixel(nmx, nmy);\n\n      return true;\n    }\n  }\n\n  return false;\n}\n\nboolean update_hid(void)\n{\n  struct buffered_status *status = store_status();\n  uint32_t down, held, up;\n  boolean retval = false;\n  touchPosition touch;\n  circlePosition cpad;\n  static circlePosition last_cpad;\n\n  hidScanInput();\n  hidCircleRead(&cpad);\n  down = hidKeysDown();\n  held = hidKeysHeld();\n  up = hidKeysUp();\n\n  retval |= check_circle(status, &cpad, &last_cpad, 0, 1);\n  retval |= check_hat(status, down, up, KEY_DUP, JOYHAT_UP);\n  retval |= check_hat(status, down, up, KEY_DDOWN, JOYHAT_DOWN);\n  retval |= check_hat(status, down, up, KEY_DLEFT, JOYHAT_LEFT);\n  retval |= check_hat(status, down, up, KEY_DRIGHT, JOYHAT_RIGHT);\n  retval |= check_joy(status, down, up, KEY_A, 0);\n  retval |= check_joy(status, down, up, KEY_B, 1);\n  retval |= check_joy(status, down, up, KEY_X, 2);\n  retval |= check_joy(status, down, up, KEY_Y, 3);\n  retval |= check_joy(status, down, up, KEY_L, 4);\n  retval |= check_joy(status, down, up, KEY_R, 5);\n  retval |= check_joy(status, down, up, KEY_SELECT, 6);\n  retval |= check_joy(status, down, up, KEY_START, 7);\n  retval |= check_joy(status, down, up, KEY_ZL, 8);\n  retval |= check_joy(status, down, up, KEY_ZR, 9);\n\n  last_cpad = cpad;\n\n  if((down | held | up) & KEY_TOUCH)\n  {\n    hidTouchRead(&touch);\n\n    if((down & KEY_TOUCH) && ctr_is_mouse_area(&touch))\n    {\n      status->mouse_button = MOUSE_BUTTON_LEFT;\n      status->mouse_repeat = MOUSE_BUTTON_LEFT;\n      status->mouse_button_state |= MOUSE_BUTTON(MOUSE_BUTTON_LEFT);\n      status->mouse_repeat_state = 1;\n      status->mouse_drag_state = -1;\n      status->mouse_time = get_ticks();\n      is_dragging = true;\n      allow_focus_changes = FOCUS_FORBID;\n      retval = true;\n    }\n\n    if(is_dragging)\n    {\n      if(up & KEY_TOUCH)\n      {\n        status->mouse_button_state &= ~MOUSE_BUTTON(MOUSE_BUTTON_LEFT);\n        status->mouse_repeat = 0;\n        status->mouse_repeat_state = 0;\n        status->mouse_drag_state = -0;\n        allow_focus_changes = FOCUS_ALLOW;\n        is_dragging = false;\n        retval = true;\n      }\n      else\n      {\n        retval |= ctr_update_touch(status, &touch);\n      }\n    }\n\n    retval |= ctr_keyboard_update(status);\n  }\n\n//  retval |= ctr_update_cstick(status);\n\n  return retval;\n}\n\nint ctr_get_subscreen_height(void)\n{\n  switch(get_config()->video_ratio)\n  {\n    case RATIO_CLASSIC_4_3:\n    case RATIO_STRETCH:\n      return 240;\n    default:\n      return 175;\n  }\n}\n\nboolean platform_has_screen_keyboard(void)\n{\n  return true;\n}\n\nboolean platform_show_screen_keyboard(void)\n{\n  input.showing_screen_keyboard = true;\n  b_mode = BOTTOM_SCREEN_MODE_KEYBOARD;\n  return true;\n}\n\nboolean platform_hide_screen_keyboard(void)\n{\n  input.showing_screen_keyboard = false;\n  b_mode = BOTTOM_SCREEN_MODE_PREVIEW;\n  return true;\n}\n\nboolean platform_is_screen_keyboard_active(void)\n{\n  return input.showing_screen_keyboard;\n}\n\nvoid platform_init_event(void)\n{\n  struct buffered_status *status = store_status();\n  joystick_set_active(status, 0, true);\n  joystick_map_fallback_keyboard_button(0, 5);\n}\n"
  },
  {
    "path": "arch/3ds/event.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __3DS_EVENT_H__\n#define __3DS_EVENT_H__\n\n#include \"../../src/event.h\"\n\n__M_BEGIN_DECLS\n\nenum bottom_screen_mode\n{\n  BOTTOM_SCREEN_MODE_PREVIEW,\n  BOTTOM_SCREEN_MODE_KEYBOARD,\n  BOTTOM_SCREEN_MODE_MAX\n};\n\nenum focus_mode\n{\n  FOCUS_FORBID,\n  FOCUS_ALLOW, // checks if position changed\n  FOCUS_PASS // ignores all checks and check updates - for touchscreen\n};\n\nenum bottom_screen_mode get_bottom_screen_mode(void);\nenum focus_mode get_allow_focus_changes(void);\nint ctr_get_subscreen_height(void);\n\n__M_END_DECLS\n\n#endif /* __3DS_EVENT_H__ */\n"
  },
  {
    "path": "arch/3ds/keyboard.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/event.h\"\n#include \"../../src/util.h\"\n\n#include <3ds.h>\n#include <citro3d.h>\n\n#include \"event.h\"\n#include \"keyboard.h\"\n#include \"platform.h\"\n#include \"render.h\"\n\n#define MAX_KEYS_DOWN 8\n\nC3D_Tex *keyboard_tex;\nstatic enum keycode keys_down[] =\n{\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN\n};\nstatic u8 keys_down_count = 0;\nstatic boolean force_zoom_out = false;\n\nstatic touch_area_t touch_areas[] =\n{\n  { 2, 118, 22, 16, IKEY_ESCAPE, 0 },\n  { 23, 118, 22, 16, IKEY_F1, 0 },\n  { 44, 118, 22, 16, IKEY_F2, 0 },\n  { 65, 118, 22, 16, IKEY_F3, 0 },\n  { 86, 118, 22, 16, IKEY_F4, 0 },\n  { 107, 118, 22, 16, IKEY_F5, 0 },\n  { 128, 118, 22, 16, IKEY_F6, 0 },\n  { 149, 118, 22, 16, IKEY_F7, 0 },\n  { 170, 118, 22, 16, IKEY_F8, 0 },\n  { 191, 118, 22, 16, IKEY_F9, 0 },\n  { 212, 118, 22, 16, IKEY_F10, 0 },\n  { 233, 118, 22, 16, IKEY_F11, 0 },\n  { 254, 118, 22, 16, IKEY_F12, 0 },\n  { 275, 118, 22, 16, IKEY_INSERT, 0 },\n  { 296, 118, 22, 16, IKEY_DELETE, 0 },\n\n  { 2, 133, 22, 22, IKEY_BACKQUOTE, 0 },\n  { 23, 133, 22, 22, IKEY_1, 0 },\n  { 44, 133, 22, 22, IKEY_2, 0 },\n  { 65, 133, 22, 22, IKEY_3, 0 },\n  { 86, 133, 22, 22, IKEY_4, 0 },\n  { 107, 133, 22, 22, IKEY_5, 0 },\n  { 128, 133, 22, 22, IKEY_6, 0 },\n  { 149, 133, 22, 22, IKEY_7, 0 },\n  { 170, 133, 22, 22, IKEY_8, 0 },\n  { 191, 133, 22, 22, IKEY_9, 0 },\n  { 212, 133, 22, 22, IKEY_0, 0 },\n  { 233, 133, 22, 22, IKEY_MINUS, 0 },\n  { 254, 133, 22, 22, IKEY_EQUALS, 0 },\n  { 275, 133, 43, 22, IKEY_BACKSPACE, 0 },\n\n  { 2, 154, 34, 22, IKEY_TAB, 0 },\n  { 35, 154, 22, 22, IKEY_q, 0 },\n  { 56, 154, 22, 22, IKEY_w, 0 },\n  { 77, 154, 22, 22, IKEY_e, 0 },\n  { 98, 154, 22, 22, IKEY_r, 0 },\n  { 119, 154, 22, 22, IKEY_t, 0 },\n  { 140, 154, 22, 22, IKEY_y, 0 },\n  { 161, 154, 22, 22, IKEY_u, 0 },\n  { 182, 154, 22, 22, IKEY_i, 0 },\n  { 203, 154, 22, 22, IKEY_o, 0 },\n  { 224, 154, 22, 22, IKEY_p, 0 },\n  { 245, 154, 22, 22, IKEY_LEFTBRACKET, 0 },\n  { 266, 154, 22, 22, IKEY_RIGHTBRACKET, 0 },\n  { 287, 154, 31, 22, IKEY_BACKSLASH, 0 },\n\n  { 2, 175, 41, 22, IKEY_LCTRL, 0 },\n  { 42, 175, 22, 22, IKEY_a, 0 },\n  { 63, 175, 22, 22, IKEY_s, 0 },\n  { 84, 175, 22, 22, IKEY_d, 0 },\n  { 105, 175, 22, 22, IKEY_f, 0 },\n  { 126, 175, 22, 22, IKEY_g, 0 },\n  { 147, 175, 22, 22, IKEY_h, 0 },\n  { 168, 175, 22, 22, IKEY_j, 0 },\n  { 189, 175, 22, 22, IKEY_k, 0 },\n  { 210, 175, 22, 22, IKEY_l, 0 },\n  { 231, 175, 22, 22, IKEY_SEMICOLON, 0 },\n  { 252, 175, 22, 22, IKEY_QUOTE, 0 },\n  { 273, 175, 45, 22, IKEY_RETURN, 0 },\n\n  { 2, 196, 54, 22, IKEY_LSHIFT, 0 },\n  { 55, 196, 22, 22, IKEY_z, 0 },\n  { 76, 196, 22, 22, IKEY_x, 0 },\n  { 97, 196, 22, 22, IKEY_c, 0 },\n  { 118, 196, 22, 22, IKEY_v, 0 },\n  { 139, 196, 22, 22, IKEY_b, 0 },\n  { 160, 196, 22, 22, IKEY_n, 0 },\n  { 181, 196, 22, 22, IKEY_m, 0 },\n  { 202, 196, 22, 22, IKEY_COMMA, 0 },\n  { 223, 196, 22, 22, IKEY_PERIOD, 0 },\n  { 244, 196, 22, 22, IKEY_SLASH, 0 },\n  { 265, 196, 53, 22, IKEY_RSHIFT, 0 },\n\n  { 29, 217, 37, 22, IKEY_LALT, 0 },\n  { 65, 217, 190, 22, IKEY_SPACE, 0 },\n  { 254, 217, 37, 22, IKEY_RALT, 0 }\n};\n#define touch_areas_len (sizeof(touch_areas) / sizeof(touch_area_t))\n\nstatic inline boolean ctr_is_modifier(enum keycode keycode)\n{\n  return keycode >= IKEY_RSHIFT && keycode <= IKEY_RSUPER;\n}\n\nstatic inline boolean ctr_key_touched(touchPosition *pos, touch_area_t *area)\n{\n  return (pos->px >= area->x) && (pos->py >= area->y) &&\n   (pos->px < (area->x + area->w)) && (pos->py < (area->y + area->h));\n}\n\nboolean ctr_keyboard_force_zoom_out(void)\n{\n  return force_zoom_out;\n}\n\nvoid ctr_keyboard_init(struct ctr_render_data *render_data)\n{\n  keyboard_tex = ctr_load_png(\"romfs:/kbd_display.png\");\n}\n\nvoid ctr_keyboard_draw(struct ctr_render_data *render_data)\n{\n  size_t i, j;\n\n  ctr_draw_2d_texture(render_data, keyboard_tex, 0, 0, 320, 240, 0, 0, 320, 240,\n   4.0f, 0xffffffff, false);\n\n  if(ctr_is_2d())\n  {\n    ctr_draw_2d_texture(render_data, keyboard_tex, force_zoom_out ? 16 : 0, 240,\n     16, 16, 302, 2, 16, 16, 3.0f, 0xffffffff, false);\n  }\n\n  if(ctr_supports_wide())\n  {\n    ctr_draw_2d_texture(render_data, keyboard_tex, gfxIsWide() ? 48 : 32, 240,\n     16, 16, 284, 2, 16, 16, 3.0f, 0xffffffff, false);\n  }\n\n  if(keys_down_count > 0)\n  {\n    for(i = 0; i < touch_areas_len; i++)\n    {\n      touch_area_t *area = &touch_areas[i];\n      for(j = 0; j < keys_down_count; j++)\n      {\n        if(keys_down[j] == area->keycode)\n        {\n          ctr_draw_2d_texture(render_data, keyboard_tex, area->x,\n           240 - area->y - area->h, area->w, area->h,\n           area->x, area->y + 1, area->w, area->h - 1,\n           3.0f, 0x808080ff, false);\n          break;\n        }\n      }\n    }\n  }\n}\n\nboolean ctr_keyboard_update(struct buffered_status *status)\n{\n  touchPosition pos;\n  u32 down, up, i;\n  boolean retval = false;\n  u32 unicode;\n\n  if(get_bottom_screen_mode() != BOTTOM_SCREEN_MODE_KEYBOARD)\n    return retval;\n\n  down = hidKeysDown();\n  up = hidKeysUp();\n  hidTouchRead(&pos);\n\n  if(down & KEY_TOUCH)\n  {\n    if(ctr_is_2d() && pos.px >= 302 && pos.py >= 2 && pos.px < 318 &&\n     pos.py < 18)\n    {\n      force_zoom_out = !force_zoom_out;\n    }\n    else\n\n    if(ctr_supports_wide() && pos.px >= 284 && pos.py >= 2 &&\n     pos.px < 300 && pos.py < 18)\n    {\n      ctr_request_set_wide(!gfxIsWide());\n    }\n\n    for(i = 0; i < touch_areas_len; i++)\n    {\n      touch_area_t *area = &touch_areas[i];\n      if(ctr_key_touched(&pos, area))\n      {\n        unicode = convert_internal_unicode(area->keycode, false);\n\n        key_press(status, area->keycode);\n        key_press_unicode(status, unicode, true);\n\n        keys_down[keys_down_count++] = area->keycode;\n        retval = true;\n        break;\n      }\n    }\n  }\n\n  if(up & KEY_TOUCH && keys_down_count > 0 &&\n   !ctr_is_modifier(keys_down[keys_down_count - 1]))\n  {\n    for(; keys_down_count > 0; keys_down_count--)\n    {\n      key_release(status, keys_down[keys_down_count - 1]);\n      keys_down[keys_down_count - 1] = IKEY_UNKNOWN;\n    }\n    retval = true;\n  }\n\n  return retval;\n}\n"
  },
  {
    "path": "arch/3ds/keyboard.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __3DS_KEYBOARD_H__\n#define __3DS_KEYBOARD_H__\n\n#include \"../../src/event.h\"\n#include \"render.h\"\n\n__M_BEGIN_DECLS\n\ntypedef struct\n{\n  u16 x, y, w, h;\n  enum keycode keycode;\n  u8 flags;\n} touch_area_t;\n\nvoid ctr_keyboard_init(struct ctr_render_data *render_data);\nvoid ctr_keyboard_draw(struct ctr_render_data *render_data);\nboolean ctr_keyboard_update(struct buffered_status *status);\nboolean ctr_keyboard_force_zoom_out(void);\n\n__M_END_DECLS\n\n#endif /* __3DS_KEYBOARD_H__ */\n"
  },
  {
    "path": "arch/3ds/pad.config",
    "content": "# Axis 1/2: Circle pad x/y\r\n# Hat: Directional pad\r\n# Button1: A\r\n# Button2: B\r\n# Button3: X\r\n# Button4: Y\r\n# Button5: Left shoulder\r\n# Button6: Right shoulder\r\n# Button7: Select\r\n# Button8: Start\r\n# Button9: ZL (NOTE: New 3DS only)\r\n# Button10: ZR (NOTE: New 3DS only)\r\n\r\njoy_axis_threshold = 16000\r\n\r\njoy1hat = act_up, act_down, act_left, act_right\r\njoy1axis1 = act_l_left, act_l_right\r\njoy1axis2 = act_l_up, act_l_down\r\njoy1button1 = act_a\r\njoy1button2 = act_b\r\njoy1button3 = act_x\r\njoy1button4 = act_y\r\njoy1button5 = act_lshoulder\r\njoy1button6 = act_rshoulder\r\njoy1button7 = act_select\r\njoy1button8 = act_start\r\njoy1button9 = act_ltrigger\r\njoy1button10 = act_rtrigger\r\njoy1.axis_lx = 1\r\njoy1.axis_ly = 2\r\n"
  },
  {
    "path": "arch/3ds/pad.config.sdl12",
    "content": "# Axis 1/2: Circle pad x/y\n# Hat: Directional pad\n# Button1: Start\n# Button2: A\n# Button3: B\n# Button4: X\n# Button5: Y\n# Button6: Left shoulder\n# Button7: Right shoulder\n# Button8: Select\n# Button9: ZL (NOTE: New 3DS only)\n# Button10: ZR (NOTE: New 3DS only)\n\njoy1hat = act_up, act_down, act_left, act_right\njoy1axis1 = act_l_left, act_l_right\njoy1axis2 = act_l_up, act_l_down\njoy1button1 = act_start\njoy1button2 = act_a\njoy1button3 = act_b\njoy1button4 = act_x\njoy1button5 = act_y\njoy1button6 = act_lshoulder\njoy1button7 = act_rshoulder\njoy1button8 = act_select\njoy1button9 = act_ltrigger\njoy1button10 = act_rtrigger\njoy1.axis_lx = 1\njoy1.axis_ly = 2\n"
  },
  {
    "path": "arch/3ds/pad.config.sdl2",
    "content": "# Axis 1/2: Circle pad x/y\n# Button1: A\n# Button2: B\n# Button3: Select\n# Button4: Start\n# Button5: Right\n# Button6: Left\n# Button7: Up\n# Button8: Down\n# Button9: Right shoulder\n# Button10: Left shoulder\n# Button11: X\n# Button12: Y\n# Button15: ZL (NOTE: New 3DS only)\n# Button16: ZR (NOTE: New 3DS only)\n\njoy1axis1 = act_l_left, act_l_right\njoy1axis2 = act_l_up, act_l_down\njoy1button1 = act_a\njoy1button2 = act_b\njoy1button3 = act_select\njoy1button4 = act_start\njoy1button5 = act_right\njoy1button6 = act_left\njoy1button7 = act_up\njoy1button8 = act_down\njoy1button9 = act_rshoulder\njoy1button10 = act_lshoulder\njoy1button11 = act_x\njoy1button12 = act_y\njoy1button15 = act_ltrigger\njoy1button16 = act_rtrigger\njoy1.axis_lx = 1\njoy1.axis_ly = 2\n"
  },
  {
    "path": "arch/3ds/platform.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007-2009 Kevin Vance <kvance@kvance.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdarg.h>\n\n#include \"platform.h\"\n\n#include \"../../src/platform.h\"\n#undef main\n\n#include \"../../src/util.h\"\n#include \"../../src/event.h\"\n#include \"../../src/error.h\"\n#include \"../../src/graphics.h\"\n\n#include <unistd.h>\n#include <stdio.h>\n\n#include <3ds.h>\n#include <citro3d.h>\n\n#include \"keyboard.h\"\n\nstatic u8 isNot2DS, consoleModelId;\n\nFILE *popen(const char *command, const char *type)\n{\n  return NULL;\n}\n\nint pclose(FILE *stream)\n{\n  return 0;\n}\n\nvoid delay(uint32_t ms)\n{\n  if(ms > 0)\n  {\n    svcSleepThread(1e6 * ms);\n  }\n}\n\nboolean ctr_is_2d(void)\n{\n  return isNot2DS == 0;\n}\n\nboolean ctr_supports_wide(void)\n{\n  return consoleModelId != 3 /* O2DS */;\n}\n\nuint64_t get_ticks(void)\n{\n  return osGetTime();\n}\n\nboolean platform_init(void)\n{\n  cfguInit();\n  romfsInit();\n  osSetSpeedupEnable(1);\n  APT_SetAppCpuTimeLimit(30);\n\n  gfxInitDefault();\n  gfxSet3D(false);\n\n  CFGU_GetSystemModel(&consoleModelId);\n  CFGU_GetModelNintendo2DS(&isNot2DS);\n  return true;\n}\n\nvoid platform_quit(void)\n{\n  gfxExit();\n\n  romfsExit();\n  cfguExit();\n}\n\n#ifdef CONFIG_CHECK_ALLOC\n\nstatic void out_of_linear_memory_check(void *p, const char *file, int line)\n{\n  char msgbuf[128];\n  if(!p)\n  {\n    snprintf(msgbuf, sizeof(msgbuf), \"Out of linear memory in %s:%d\",\n     file, line);\n    msgbuf[sizeof(msgbuf)-1] = '\\0';\n    error(msgbuf, 2, 4, 0);\n  }\n}\n\nvoid *check_linearAlloc(size_t size, size_t alignment, const char *file,\n int line)\n{\n  void *result = linearMemAlign(size, alignment);\n  out_of_linear_memory_check(result, file, line);\n  return result;\n}\n\n#endif\n\n/**\n * argv[0] will either not exist (cia) or be the location of the 3dsx.\n * For the cia case we can't really do anything, so assume a SHAREDIR\n * startup location.\n */\n\nint main(int argc, char *argv[])\n{\n  static char _argv0[] = SHAREDIR \"/mzxrun.3dsx\";\n  static char *_argv[] = { _argv0 };\n\n  if(argc < 1 || argv == NULL || argv[0] == NULL)\n  {\n    iprintf(\"argv[0]: not found.\\n\"\n            \"using '%s'\\n\"\n            \"WARNING: Use of a loader that supports argv[0] is recommended.\\n\",\n            _argv0);\n\n    chdir(SHAREDIR);\n    real_main(1, _argv);\n  }\n  else\n  {\n    iprintf(\"argv[0]: '%s'\\n\", argv[0]);\n    real_main(argc, argv);\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "arch/3ds/platform.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __3DS_PLATFORM_H__\n#define __3DS_PLATFORM_H__\n\n#include \"../../src/compat.h\"\n\n#include <stdlib.h>\n#include <3ds.h>\n#include <citro3d.h>\n\n__M_BEGIN_DECLS\n\nboolean ctr_is_2d(void);\nboolean ctr_supports_wide(void);\n\n#ifdef CONFIG_CHECK_ALLOC\n\n#include <stddef.h>\n\n#define clinearAlloc(size, alignment) \\\n check_linearAlloc(size, alignment, __FILE__, __LINE__)\n\nvoid *check_linearAlloc(size_t size, size_t alignment, const char *file,\n int line);\n\n#else\n\nstatic inline void *clinearAlloc(size_t size)\n{\n  return linearAlloc(size);\n}\n\n#endif /* CONFIG_CHECK_ALLOC */\n\n__M_END_DECLS\n\n#endif /* __3DS_PLATFORM_H__ */\n"
  },
  {
    "path": "arch/3ds/render.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/graphics.h\"\n#include \"../../src/pngops.h\"\n#include \"../../src/render.h\"\n#include \"../../src/renderers.h\"\n#include \"../../src/util.h\"\n\n#include \"event.h\"\n#include \"keyboard.h\"\n#include \"platform.h\"\n#include \"render.h\"\n\n#include \"shader_2d_shbin.h\"\n#include \"shader_playfield_shbin.h\"\n\n//#define RDR_DEBUG\n\nstruct ctr_shader_data\n{\n  DVLB_s *dvlb;\n  shaderProgram_s program;\n  int proj_loc, offs_loc, geo_count;\n  C3D_AttrInfo attr;\n};\n\nstruct ctr_layer\n{\n  unsigned int w, h, mode;\n  float z;\n  int draw_order;\n  boolean has_background_texture;\n  struct v_char *foreground;\n  C3D_Tex background;\n};\n\nstruct v_char\n{\n  s16 x, y;\n  u32 uv;\n  u32 col;\n};\n\nstruct vertex\n{\n  float x, y, w, h;\n  float z;\n  float tx, ty, tw, th;\n  u32 color;\n};\n\nstruct linear_ptr_list_entry\n{\n  void *ptr;\n  struct linear_ptr_list_entry *next;\n};\n\n#define CTR_TOP_WIDTH (gfxIsWide() ? 800 : 400)\n#define CTR_TOP_HEIGHT 240\n#define CTR_CHAR_H 16\n#define CTR_TEXTURE_WIDTH 1024\n#define CTR_TEXTURE_CHARS_ROW (CTR_TEXTURE_WIDTH / CHAR_W)\n#define CTR_TEXTURE_HEIGHT ((FULL_CHARSET_SIZE / CTR_TEXTURE_CHARS_ROW) * CTR_CHAR_H)\n#define CTR_LAYER_MAX (LAYER_DRAWORDER_MAX + 101)\n#define CTR_LAYER_CURSOR (LAYER_DRAWORDER_MAX + 50)\n#define CTR_LAYER_MOUSE (LAYER_DRAWORDER_MAX + 75)\n\nstruct ctr_render_data\n{\n  C3D_Tex charset[5], charset_vram[5];\n  u8 charset_dirty_set;\n  u8 charset_dirty[NUM_CHARSETS * 2];\n  boolean rendering_frame, checked_frame;\n  struct ctr_shader_data shader_2d, shader_playfield;\n  C3D_Mtx projection;\n  C3D_Tex playfield_tex;\n  C3D_TexEnv env_normal, env_no_texture, env_playfield, env_playfield_inv;\n  C3D_RenderTarget *playfield, *target_top, *target_bottom;\n  u8 cursor_on, mouse_on;\n  u32 last_focus_x, last_focus_y;\n  u32 focus_x, focus_y;\n  u32 layer_num;\n};\n\nstatic u8 morton_lut[64] =\n{\n  0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15,\n  0x02, 0x03, 0x06, 0x07, 0x12, 0x13, 0x16, 0x17,\n  0x08, 0x09, 0x0c, 0x0d, 0x18, 0x19, 0x1c, 0x1d,\n  0x0a, 0x0b, 0x0e, 0x0f, 0x1a, 0x1b, 0x1e, 0x1f,\n  0x20, 0x21, 0x24, 0x25, 0x30, 0x31, 0x34, 0x35,\n  0x22, 0x23, 0x26, 0x27, 0x32, 0x33, 0x36, 0x37,\n  0x28, 0x29, 0x2c, 0x2d, 0x38, 0x39, 0x3c, 0x3d,\n  0x2a, 0x2b, 0x2e, 0x2f, 0x3a, 0x3b, 0x3e, 0x3f\n};\n\nstatic u8 morton_lut4[32] =\n{\n  0x00, 0x02, 0x08, 0x0a, 0x01, 0x03, 0x09, 0x0b,\n  0x04, 0x06, 0x0c, 0x0e, 0x05, 0x07, 0x0d, 0x0f,\n  0x10, 0x12, 0x18, 0x1a, 0x11, 0x13, 0x19, 0x1b,\n  0x14, 0x16, 0x1c, 0x1e, 0x15, 0x17, 0x1d, 0x1f\n};\n\n// 2 char bits -> 8 texture bits\nstatic u8 bitmask_mzx[4] = { 0x00, 0xf0, 0x0f, 0xff };\n\n// 4 char bits -> 8 texture bits\n// bitmask_smzx[layer, 0-3][bits]\nstatic u8 bitmask_smzx[4][16] =\n{\n  { 0xff, 0x0f, 0x0f, 0x0f, 0xf0, 0x00, 0x00, 0x00,\n    0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00 },\n  { 0x00, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0x0f,\n    0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00 },\n  { 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00,\n    0x0f, 0x0f, 0xff, 0x0f, 0x00, 0x00, 0xf0, 0x00 },\n  { 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0,\n    0x00, 0x00, 0x00, 0xf0, 0x0f, 0x0f, 0x0f, 0xff }\n};\n\n// texture PNG dimensions must be powers of two\nstatic boolean tex_w_h_constraint(png_uint_32 w, png_uint_32 h)\n{\n  return w > 0 && h > 0 && ((w & (w - 1)) == 0) && ((h & (h - 1)) == 0);\n}\n\nstatic inline int to_texture_size(int v)\n{\n  int i;\n  for(i = 8; i <= 65536; i *= 2)\n    if(i >= v)\n      return i;\n  return v;\n}\n\nstatic void *tex_alloc_png_surface(png_uint_32 w, png_uint_32 h,\n  png_uint_32 *stride, void **pixels)\n{\n  C3D_Tex *tex;\n\n  tex = (C3D_Tex *) ccalloc(1, sizeof(C3D_Tex));\n  *stride = w << 2;\n\n  if(!C3D_TexInit(tex, w, h, GPU_RGBA8))\n  {\n    free(tex);\n    return NULL;\n  }\n\n  *pixels = tex->data;\n  return tex;\n}\n\nstatic inline void ctr_set_2d_projection(struct ctr_render_data *render_data,\n int width, int height, boolean tilt)\n{\n  if(tilt)\n    Mtx_OrthoTilt(&render_data->projection, 0, width, height, 0, -1.0, CTR_LAYER_MAX,\n     true);\n\n  else\n    Mtx_Ortho(&render_data->projection, 0, width, 0, height, -1.0, CTR_LAYER_MAX,\n     true);\n}\n\nstatic inline void ctr_set_2d_projection_screen(\n struct ctr_render_data *render_data, boolean top_screen)\n{\n  ctr_set_2d_projection(render_data, top_screen ? 400 : 320, 240, true);\n}\n\nstatic void ctr_bind_shader(struct ctr_shader_data *shader)\n{\n  C3D_BindProgram(&shader->program);\n  C3D_SetAttrInfo(&shader->attr);\n}\n\nstatic inline void ctr_prepare_2d(struct ctr_render_data *render_data,\n struct vertex *array)\n{\n  C3D_BufInfo *bufInfo;\n\n  bufInfo = C3D_GetBufInfo();\n  BufInfo_Init(bufInfo);\n  BufInfo_Add(bufInfo, array, sizeof(struct vertex), 4, 0x3210);\n\n  ctr_bind_shader(&render_data->shader_2d);\n  C3D_FVUnifMtx4x4(GPU_GEOMETRY_SHADER, render_data->shader_2d.proj_loc,\n   &render_data->projection);\n}\n\nstatic inline void ctr_prepare_playfield(struct ctr_render_data *render_data,\n struct v_char *array, float xo, float yo, float z)\n{\n  C3D_BufInfo *bufInfo;\n\n  bufInfo = C3D_GetBufInfo();\n  BufInfo_Init(bufInfo);\n  BufInfo_Add(bufInfo, array, sizeof(struct v_char), 3, 0x210);\n\n  ctr_bind_shader(&render_data->shader_playfield);\n  C3D_FVUnifMtx4x4(GPU_GEOMETRY_SHADER, render_data->shader_playfield.proj_loc,\n   &render_data->projection);\n  C3D_FVUnifSet(GPU_GEOMETRY_SHADER, render_data->shader_playfield.offs_loc,\n   (float) xo, (float) yo, z, 0.0f);\n}\n\nC3D_Tex *ctr_load_png(const char *name)\n{\n  C3D_Tex *output;\n  u16 width, height;\n  u32 *data, *dataBuf;\n  int i;\n\n  output = (C3D_Tex *) png_read_file(name, NULL, NULL, tex_w_h_constraint,\n    tex_alloc_png_surface);\n\n  if(output != NULL)\n  {\n    C3D_TexSetWrap(output, GPU_CLAMP_TO_BORDER, GPU_CLAMP_TO_BORDER);\n    data = (u32 *)output->data;\n    dataBuf = (u32 *)clinearAlloc(output->size, 0x10);\n\n    for(i = 0; i < output->size >> 2; i++)\n      dataBuf[i] = __builtin_bswap32(data[i]);\n\n    width = output->width;\n    height = output->height;\n    C3D_TexDelete(output);\n    C3D_TexInitVRAM(output, width, height, GPU_RGBA8);\n    data = (u32*) output->data;\n\n    GSPGPU_FlushDataCache(dataBuf, output->size);\n    C3D_SyncDisplayTransfer(\n      dataBuf,\n      GX_BUFFER_DIM(output->width, output->height),\n      data,\n      GX_BUFFER_DIM(output->width, output->height),\n      (GX_TRANSFER_FLIP_VERT(0) |\n       GX_TRANSFER_OUT_TILED(1) |\n       GX_TRANSFER_RAW_COPY(0) |\n       GX_TRANSFER_IN_FORMAT(GPU_RGBA8) |\n       GX_TRANSFER_OUT_FORMAT(GPU_RGBA8) |\n       GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO))\n    );\n\n    linearFree(dataBuf);\n  }\n\n  return output;\n}\n\nstatic int vertex_heap_pos = 0, vertex_heap_len = 0;\nstatic struct vertex *vertex_heap;\n\nvoid ctr_draw_2d_texture(struct ctr_render_data *render_data, C3D_Tex *texture,\n int tx, int ty, int tw, int th,\n float x, float y, float w, float h, float z, u32 color, boolean flipy)\n{\n  struct vertex *vertices;\n\n  if(flipy && texture != NULL)\n  {\n    ty = texture->height - ty;\n    th = -th;\n  }\n\n  if(vertex_heap_len == 0)\n  {\n    vertex_heap_len = 64;\n    vertex_heap = (struct vertex *) clinearAlloc(sizeof(struct vertex) * vertex_heap_len, 0x80);\n  }\n  else\n\n  if(vertex_heap_pos + 1 > vertex_heap_len)\n  {\n    struct vertex *vertex_heap_new =\n     (struct vertex *) clinearAlloc(sizeof(struct vertex) * vertex_heap_len * 2, 0x80);\n    memcpy(vertex_heap_new, vertex_heap,\n     sizeof(struct vertex) * vertex_heap_len);\n    vertex_heap_len *= 2;\n    linearFree(vertex_heap);\n    vertex_heap = vertex_heap_new;\n  }\n\n  vertices = &vertex_heap[vertex_heap_pos];\n  vertex_heap_pos++;\n\n  vertices[0].x = x;\n  vertices[0].y = y;\n  vertices[0].w = w;\n  vertices[0].h = h;\n  vertices[0].z = z;\n  vertices[0].color = color;\n\n  ctr_prepare_2d(render_data, vertex_heap);\n  if(texture != NULL)\n  {\n    vertices[0].tx = tx / ((float) texture->width);\n    vertices[0].ty = ty / ((float) texture->height);\n    vertices[0].tw = tw / ((float) texture->width);\n    vertices[0].th = th / ((float) texture->height);\n\n    C3D_SetTexEnv(0, &(render_data->env_normal));\n    C3D_TexBind(0, texture);\n  }\n  else\n  {\n    vertices[0].tx = tx;\n    vertices[0].ty = ty;\n    vertices[0].tw = tw;\n    vertices[0].th = th;\n\n    C3D_SetTexEnv(0, &(render_data->env_no_texture));\n  }\n  C3D_DrawArrays(GPU_GEOMETRY_PRIM, vertex_heap_pos - 1, 1);\n}\n\nstatic void ctr_init_shader(struct ctr_shader_data *shader, const void *data,\n int size, int geo_count)\n{\n  shader->dvlb = DVLB_ParseFile((u32 *)data, size);\n  shader->geo_count = geo_count;\n  shaderProgramInit(&shader->program);\n  shaderProgramSetVsh(&shader->program, &shader->dvlb->DVLE[0]);\n  shaderProgramSetGsh(&shader->program, &shader->dvlb->DVLE[1], geo_count);\n  shader->proj_loc =\n   shaderInstanceGetUniformLocation(shader->program.geometryShader, \"projection\");\n  shader->offs_loc =\n   shaderInstanceGetUniformLocation(shader->program.geometryShader, \"offset\");\n  AttrInfo_Init(&shader->attr);\n}\n\nstatic boolean wide_requested = false, wide_new_state;\n\nvoid ctr_request_set_wide(bool wide)\n{\n  wide_requested = true;\n  wide_new_state = wide;\n}\n\nstatic void ctr_set_wide(struct ctr_render_data *render_data, bool wide)\n{\n  gfxSetWide(wide);\n\n  C3D_RenderTargetDelete(render_data->target_top);\n  render_data->target_top =\n   C3D_RenderTargetCreate(240, CTR_TOP_WIDTH, GPU_RB_RGB8, GPU_RB_DEPTH16);\n  C3D_RenderTargetClear(render_data->target_top, C3D_CLEAR_ALL, 0x000000, 0);\n  C3D_RenderTargetSetOutput(render_data->target_top, GFX_TOP, GFX_LEFT,\n    GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) |\n    GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));\n}\n\nstatic boolean ctr_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  static struct ctr_render_data render_data;\n  unsigned int bits_per_pixel = 16;\n\n#ifdef RDR_DEBUG\n  consoleInit(GFX_TOP, NULL);\n#endif\n\n  memset(&render_data, 0, sizeof(struct ctr_render_data));\n  graphics->render_data = &render_data;\n\n  gfxSet3D(false);\n  C3D_Init(0x40000);\n  C3D_CullFace(GPU_CULL_NONE);\n\n  render_data.rendering_frame = false;\n  render_data.checked_frame = false;\n\n  if(conf->force_bpp == 32)\n    bits_per_pixel = conf->force_bpp;\n\n  // 1024x512 is the smallest power of two texture which can fit a 640x350 playfield\n  if(bits_per_pixel == 32)\n    C3D_TexInitVRAM(&render_data.playfield_tex, 1024, 512, GPU_RGBA8);\n  else\n    C3D_TexInitVRAM(&render_data.playfield_tex, 1024, 512, GPU_RGB565);\n  C3D_TexSetFilter(&render_data.playfield_tex, GPU_LINEAR, GPU_LINEAR);\n\n  render_data.playfield =\n   C3D_RenderTargetCreateFromTex(&render_data.playfield_tex, GPU_TEXFACE_2D, 0,\n    GPU_RB_DEPTH16);\n  render_data.target_top =\n   C3D_RenderTargetCreate(240, 400, GPU_RB_RGB8, GPU_RB_DEPTH16);\n  render_data.target_bottom =\n   C3D_RenderTargetCreate(240, 320, GPU_RB_RGB8, GPU_RB_DEPTH16);\n\n  C3D_RenderTargetClear(render_data.playfield, C3D_CLEAR_ALL, 0x000000, 0);\n  C3D_RenderTargetClear(render_data.target_top, C3D_CLEAR_ALL, 0x000000, 0);\n  C3D_RenderTargetClear(render_data.target_bottom, C3D_CLEAR_ALL, 0x000000, 0);\n  C3D_RenderTargetSetOutput(render_data.target_top, GFX_TOP, GFX_LEFT,\n    GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) |\n    GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));\n  C3D_RenderTargetSetOutput(render_data.target_bottom, GFX_BOTTOM, GFX_LEFT,\n    GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) |\n    GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));\n\n  ctr_init_shader(&render_data.shader_2d,\n   shader_2d_shbin, shader_2d_shbin_size, 4);\n  ctr_init_shader(&render_data.shader_playfield,\n   shader_playfield_shbin, shader_playfield_shbin_size, 3);\n\n  // v0 = x,y,w,h\n  // v1 = z\n  // v2 = tx,ty,tw,th\n  // v3 = color\n  AttrInfo_AddLoader(&render_data.shader_2d.attr, 0, GPU_FLOAT, 4);\n  AttrInfo_AddLoader(&render_data.shader_2d.attr, 1, GPU_FLOAT, 1);\n  AttrInfo_AddLoader(&render_data.shader_2d.attr, 2, GPU_FLOAT, 4);\n  AttrInfo_AddLoader(&render_data.shader_2d.attr, 3, GPU_UNSIGNED_BYTE, 4);\n\n  // v0 = position\n  // v1 = texcoord\n  // v2 = color\n  AttrInfo_AddLoader(&render_data.shader_playfield.attr, 0, GPU_SHORT, 2);\n  AttrInfo_AddLoader(&render_data.shader_playfield.attr, 1, GPU_SHORT, 2);\n  AttrInfo_AddLoader(&render_data.shader_playfield.attr, 2, GPU_UNSIGNED_BYTE, 4);\n\n  for(int i = 0; i < 5; i++)\n  {\n    int tex_width = CTR_TEXTURE_WIDTH;\n    if(i > 0)\n      tex_width /= 2;\n\n    C3D_TexInit(&render_data.charset[i], tex_width,\n      CTR_TEXTURE_HEIGHT, GPU_A4);\n    C3D_TexSetFilter(&render_data.charset[i], GPU_NEAREST, GPU_NEAREST);\n    C3D_TexSetWrap(&render_data.charset[i], GPU_REPEAT, GPU_REPEAT);\n\n    C3D_TexInitVRAM(&render_data.charset_vram[i], tex_width,\n      CTR_TEXTURE_HEIGHT, GPU_A4);\n    C3D_TexSetFilter(&render_data.charset_vram[i], GPU_NEAREST, GPU_NEAREST);\n    C3D_TexSetWrap(&render_data.charset_vram[i], GPU_REPEAT, GPU_REPEAT);\n  }\n\n  C3D_TexEnvInit(&(render_data.env_normal));\n  C3D_TexEnvSrc(&(render_data.env_normal), C3D_Both, GPU_TEXTURE0, (GPU_TEVSRC) 0, (GPU_TEVSRC) 0);\n  C3D_TexEnvOpRgb(&(render_data.env_normal), (GPU_TEVOP_RGB) 0, (GPU_TEVOP_RGB) 0, (GPU_TEVOP_RGB) 0);\n  C3D_TexEnvOpAlpha(&(render_data.env_normal), (GPU_TEVOP_A) 0, (GPU_TEVOP_A) 0, (GPU_TEVOP_A) 0);\n  C3D_TexEnvFunc(&(render_data.env_normal), C3D_Both, GPU_MODULATE);\n\n  C3D_TexEnvInit(&(render_data.env_no_texture));\n  C3D_TexEnvSrc(&(render_data.env_no_texture), C3D_Both, GPU_PRIMARY_COLOR, (GPU_TEVSRC) 0, (GPU_TEVSRC) 0);\n  C3D_TexEnvOpRgb(&(render_data.env_no_texture), (GPU_TEVOP_RGB) 0, (GPU_TEVOP_RGB) 0, (GPU_TEVOP_RGB) 0);\n  C3D_TexEnvOpAlpha(&(render_data.env_no_texture), (GPU_TEVOP_A) 0, (GPU_TEVOP_A) 0, (GPU_TEVOP_A) 0);\n  C3D_TexEnvFunc(&(render_data.env_no_texture), C3D_Both, GPU_REPLACE);\n\n  C3D_TexEnvInit(&(render_data.env_playfield));\n  C3D_TexEnvSrc(&(render_data.env_playfield), C3D_RGB, (GPU_TEVSRC) 0, GPU_PRIMARY_COLOR, (GPU_TEVSRC) 0);\n  C3D_TexEnvSrc(&(render_data.env_playfield), C3D_Alpha, (GPU_TEVSRC) 0, GPU_TEXTURE0, (GPU_TEVSRC) 0);\n  C3D_TexEnvOpRgb(&(render_data.env_playfield), (GPU_TEVOP_RGB) 0, GPU_TEVOP_RGB_SRC_ALPHA, (GPU_TEVOP_RGB) 0);\n  C3D_TexEnvOpAlpha(&(render_data.env_playfield), (GPU_TEVOP_A) 0, GPU_TEVOP_A_SRC_ALPHA, (GPU_TEVOP_A) 0);\n  C3D_TexEnvFunc(&(render_data.env_playfield), C3D_Both, GPU_MODULATE);\n\n  C3D_TexEnvInit(&(render_data.env_playfield_inv));\n  C3D_TexEnvSrc(&(render_data.env_playfield_inv), C3D_RGB, (GPU_TEVSRC) 0, GPU_PRIMARY_COLOR, (GPU_TEVSRC) 0);\n  C3D_TexEnvSrc(&(render_data.env_playfield_inv), C3D_Alpha, (GPU_TEVSRC) 0, GPU_TEXTURE0, (GPU_TEVSRC) 0);\n  C3D_TexEnvOpRgb(&(render_data.env_playfield_inv), (GPU_TEVOP_RGB) 0, GPU_TEVOP_RGB_SRC_ALPHA, (GPU_TEVOP_RGB) 0);\n  C3D_TexEnvOpAlpha(&(render_data.env_playfield_inv), (GPU_TEVOP_A) 0, GPU_TEVOP_A_ONE_MINUS_SRC_ALPHA, (GPU_TEVOP_A) 0);\n  C3D_TexEnvFunc(&(render_data.env_playfield_inv), C3D_Both, GPU_MODULATE);\n\n  C3D_AlphaTest(false, GPU_GREATER, 0x80);\n  C3D_DepthTest(false, GPU_GEQUAL, GPU_WRITE_ALL);\n\n  graphics->allow_resize = 0;\n  graphics->bits_per_pixel = bits_per_pixel;\n\n  graphics->resolution_width = 640;\n  graphics->resolution_height = 350;\n  graphics->window_width = 640;\n  graphics->window_height = 350;\n\n  ctr_keyboard_init(&render_data);\n  return true;\n}\n\nstatic void ctr_free_video(struct graphics_data *graphics)\n{\n  // TODO: more freeing!\n  struct ctr_render_data *render_data = (struct ctr_render_data *) graphics->render_data;\n  int i;\n\n  for(i = 0; i < 5; i++)\n  {\n    C3D_TexDelete(&render_data->charset[i]);\n    C3D_TexDelete(&render_data->charset_vram[i]);\n  }\n  C3D_TexDelete(&render_data->playfield_tex);\n\n  C3D_RenderTargetDelete(render_data->playfield);\n  C3D_RenderTargetDelete(render_data->target_top);\n  C3D_RenderTargetDelete(render_data->target_bottom);\n\n  C3D_Fini();\n}\n\nstatic boolean ctr_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  return true;\n}\n\nstatic void ctr_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned int count)\n{\n  unsigned int i;\n\n  for(i = 0; i < count; i++)\n  {\n    graphics->flat_intensity_palette[i] =\n     (palette[i].r << 24) | (palette[i].g << 16) |\n     (palette[i].b <<  8) | 0x000000FF;\n  }\n}\n\nstatic inline u32 ctr_get_char_texture_row(u32 chr)\n{\n  return chr / CTR_TEXTURE_CHARS_ROW;\n}\n\n/**\n * This method takes a single character line \"c\" for character \"chr\" and\n * Y position \"y\" and writes it to all textures (both MZX and SMZX).\n */\nstatic inline void ctr_char_line_to_texture(\n struct ctr_render_data *render_data, u8 c, u32 chr, int y)\n{\n  u8 *p0, *p1, *p2, *p3;\n  u8 ch_low, ch_high;\n  u32 offset = ((chr%CTR_TEXTURE_CHARS_ROW) * (8 * 8 / 2));\n  u32 offset_smzx = (((chr & (~1)) % CTR_TEXTURE_CHARS_ROW) * (4 * 8 / 2));\n\n  offset += (chr/CTR_TEXTURE_CHARS_ROW) * (CTR_TEXTURE_WIDTH * CTR_CHAR_H / 2);\n  offset_smzx += (chr/CTR_TEXTURE_CHARS_ROW) * (CTR_TEXTURE_WIDTH/2 * CTR_CHAR_H / 2);\n\n  // Add the Y position to the offsets, taking into account the 8x8 swizzling\n  offset += ((y / 8) * (CTR_TEXTURE_WIDTH * 8 / 2));\n  offset += morton_lut4[(y & 7) * 4];\n\n  offset_smzx += ((y / 8) * (CTR_TEXTURE_WIDTH / 2 * 8 / 2));\n  offset_smzx += morton_lut4[(y & 7) * 4];\n  if(chr & 1)\n    offset_smzx += 8;\n\n  // Write line to MZX texture, taking into account the swizzling\n  p0 = ((u8*) render_data->charset[0].data) + offset;\n  p0[0] =  bitmask_mzx[(c >> 6)       ];\n  p0[2] =  bitmask_mzx[(c >> 4) & 0x03];\n  p0[8] =  bitmask_mzx[(c >> 2) & 0x03];\n  p0[10] = bitmask_mzx[(c     ) & 0x03];\n\n  // Write line to SMZX texture, taking into account the swizzling\n  p0 = ((u8*) render_data->charset[1].data) + offset_smzx;\n  p1 = ((u8*) render_data->charset[2].data) + offset_smzx;\n  p2 = ((u8*) render_data->charset[3].data) + offset_smzx;\n  p3 = ((u8*) render_data->charset[4].data) + offset_smzx;\n\n  ch_high = (c >> 4);\n  ch_low = (c & 15);\n\n  p0[0] = bitmask_smzx[0][ch_high];\n  p0[2] = bitmask_smzx[0][ch_low];\n  p1[0] = bitmask_smzx[1][ch_high];\n  p1[2] = bitmask_smzx[1][ch_low];\n  p2[0] = bitmask_smzx[2][ch_high];\n  p2[2] = bitmask_smzx[2][ch_low];\n  p3[0] = bitmask_smzx[3][ch_high];\n  p3[2] = bitmask_smzx[3][ch_low];\n}\n\nstatic void ctr_remap_char_range(struct graphics_data *graphics, uint16_t first,\n uint16_t count)\n{\n  struct ctr_render_data *render_data = (struct ctr_render_data *) graphics->render_data;\n  u16 end = first + count;\n  u8 *charset_pos;\n  u32 i, j;\n\n  if(end > FULL_CHARSET_SIZE)\n    end = FULL_CHARSET_SIZE;\n\n  charset_pos = graphics->charset;\n  charset_pos += first * CHAR_SIZE;\n\n  for(i = first; i < end; i++)\n  {\n    for(j = 0; j < 14; j++, charset_pos++)\n      ctr_char_line_to_texture(render_data, *charset_pos, i, j);\n  }\n\n  first = ctr_get_char_texture_row(first);\n  end = ctr_get_char_texture_row(end - 1);\n\n  for(i = first; i <= end; i++)\n    render_data->charset_dirty[i] = 1;\n  render_data->charset_dirty_set = 1;\n}\n\nstatic void ctr_remap_char(struct graphics_data *graphics, uint16_t chr)\n{\n  struct ctr_render_data *render_data = (struct ctr_render_data *) graphics->render_data;\n  u16 tex_row = ctr_get_char_texture_row(chr);\n  u8 *charset_pos;\n  u32 i;\n\n  charset_pos = graphics->charset;\n  charset_pos += chr * CHAR_SIZE;\n\n  for(i = 0; i < 14; i++, charset_pos++)\n    ctr_char_line_to_texture(render_data, *charset_pos, chr, i);\n\n  render_data->charset_dirty[tex_row] = 1;\n  render_data->charset_dirty_set = 1;\n}\n\nstatic void ctr_remap_charbyte(struct graphics_data *graphics, uint16_t chr,\n uint8_t byte)\n{\n  struct ctr_render_data *render_data = (struct ctr_render_data *) graphics->render_data;\n  u16 tex_row = ctr_get_char_texture_row(chr);\n  u8 *charset_pos;\n\n  charset_pos = graphics->charset;\n  charset_pos += chr * CHAR_SIZE + byte;\n\n  ctr_char_line_to_texture(render_data, *charset_pos, chr, byte);\n\n  render_data->charset_dirty[tex_row] = 1;\n  render_data->charset_dirty_set = 1;\n}\n\nstatic inline void ctr_refresh_charsets(struct ctr_render_data *render_data,\n int from, int to)\n{\n  u8 *charset_dirty;\n  int coffs = 0;\n  int csize = 0;\n  int cincr = (CTR_TEXTURE_WIDTH * CTR_CHAR_H) / 2;\n  int last_dirty = 0;\n  int i;\n\n  if(!render_data->charset_dirty_set)\n    return;\n\n  render_data->charset_dirty_set = 0;\n  charset_dirty = render_data->charset_dirty;\n\n  for(i = 0; i < NUM_CHARSETS * 2; i++)\n  {\n    if(charset_dirty[i])\n    {\n      coffs = cincr * i;\n      break;\n    }\n  }\n\n  for(; i < NUM_CHARSETS * 2; i++)\n  {\n    if(charset_dirty[i])\n    {\n      charset_dirty[i] = 0;\n      last_dirty = i;\n    }\n  }\n\n  csize = (last_dirty + 1) * cincr - coffs;\n  if(csize <= 0)\n    return;\n\n  for(i = from; i < to; i++)\n  {\n    if(i != 0)\n    {\n      GSPGPU_FlushDataCache((u8*)(render_data->charset[i].data) + coffs/2, csize/2);\n      C3D_SyncTextureCopy((u32*)((u8*)(render_data->charset[i].data) + coffs/2), 0,\n        (u32*)((u8*)(render_data->charset_vram[i].data) + coffs/2), 0, csize/2, 8);\n    }\n    else\n    {\n      GSPGPU_FlushDataCache((u8*)(render_data->charset[0].data) + coffs, csize);\n      C3D_SyncTextureCopy((u32*)((u8*)(render_data->charset[0].data) + coffs), 0,\n        (u32*)((u8*)(render_data->charset_vram[0].data) + coffs), 0, csize, 8);\n    }\n  }\n  return;\n}\n\nstatic boolean ctr_should_render(struct ctr_render_data *render_data)\n{\n  if(!render_data->rendering_frame)\n  {\n    // If we aren't currently in control of the GPU, do not draw video.\n    if(!gspHasGpuRight())\n      return false;\n\n    if(wide_requested)\n    {\n      ctr_set_wide(render_data, wide_new_state);\n      wide_requested = false;\n    }\n\n    if(render_data->checked_frame || !C3D_FrameBegin(C3D_FRAME_NONBLOCK))\n    {\n      render_data->checked_frame = true;\n      return false;\n    }\n    ctr_refresh_charsets(render_data, 0, 5);\n    render_data->rendering_frame = true;\n    render_data->layer_num = 0;\n    vertex_heap_pos = 0;\n    C3D_FrameDrawOn(render_data->playfield);\n    ctr_set_2d_projection(render_data, 1024, 512, false);\n  }\n  return true;\n}\n\nstatic inline u32 ctr_char_texture_uv(u32 ch)\n{\n  u16 u = (ch % CTR_TEXTURE_CHARS_ROW) * CHAR_W;\n  u16 v = CTR_TEXTURE_HEIGHT - ((ch / CTR_TEXTURE_CHARS_ROW) * CTR_CHAR_H);\n\n  return (v << 16) | u;\n}\n\ntemplate<int SMZX, int HAS_BACKGROUND_TEXTURE, int HAS_TRANSPARENT_COL>\nstatic void ctr_render_layer_inner(struct graphics_data *graphics,\n struct video_layer *vlayer, u32 bufsize)\n{\n  struct ctr_layer *layer = (struct ctr_layer *)vlayer->platform_layer_data;\n  struct ctr_render_data *render_data = (struct ctr_render_data *) graphics->render_data;\n\n  // Flags - dynamically set by the layer generation code.\n  boolean draw_background_texture = HAS_BACKGROUND_TEXTURE;\n  boolean has_content = false;\n\n  // Cached variables.\n  u32 transparent_col = vlayer->transparent_col;\n  int offset = vlayer->offset;\n  u32 protected_pal_position = graphics->protected_pal_position;\n\n  // Temporary variables for layer generation.\n  struct char_element *src = vlayer->data;\n  u32 ix, iy;\n  int pos0, pos1, pos2, pos3;\n  int pos_bg;\n  u32 ch, uv;\n  u32 col0, col1, col2, col3;\n\n  // SMZX = layer->mode == 0\n  // HAS_BACKGROUND_TEXTURE = draw_background_texture\n  pos0 = 0;\n  pos1 = layer->w * layer->h;\n  if(SMZX)\n  {\n    pos2 = pos1 * 2;\n    pos3 = pos1 * 3;\n  }\n\n  for(iy = 0; iy < layer->h; iy++)\n  {\n    for(ix = 0; ix < layer->w; ix++, src++)\n    {\n      if(HAS_BACKGROUND_TEXTURE)\n      {\n        pos_bg = morton_lut[(ix & 7) + ((iy & 7) << 3)]\n               + ((ix & (~7)) << 3) + ((iy & (~7)) * layer->background.width);\n      }\n\n      ch = src->char_value;\n\n      if(ch == INVISIBLE_CHAR)\n      {\n        if(HAS_BACKGROUND_TEXTURE)\n          ((u32*) layer->background.data)[pos_bg] = 0;\n        layer->foreground[pos0++].col = 0;\n        layer->foreground[pos1++].col = 0;\n        if(SMZX)\n        {\n          layer->foreground[pos2++].col = 0;\n          layer->foreground[pos3++].col = 0;\n        }\n        continue;\n      }\n      has_content = true;\n\n      if(ch >= 0x100)\n        ch = (ch & 0xFF) + PROTECTED_CHARSET_POSITION;\n      else\n        ch = (ch + offset) % PROTECTED_CHARSET_POSITION;\n\n      uv = ctr_char_texture_uv(ch);\n\n      if(!SMZX)\n      {\n        col0 = src->bg_color;\n        if(HAS_TRANSPARENT_COL && col0 == transparent_col)\n        {\n          col0 = 0;\n        }\n        else\n        {\n          if(col0 >= 16)\n            col0 = (col0 & 0xF) + protected_pal_position;\n\n          col0 = graphics->flat_intensity_palette[col0];\n        }\n\n        col1 = src->fg_color;\n        if(HAS_TRANSPARENT_COL && col1 == transparent_col)\n        {\n          col1 = 0;\n          draw_background_texture = false;\n        }\n        else\n        {\n          if(col1 >= 16)\n            col1 = (col1 & 0xF) + protected_pal_position;\n\n          col1 = graphics->flat_intensity_palette[col1];\n        }\n        if(HAS_BACKGROUND_TEXTURE)\n          ((u32*) layer->background.data)[pos_bg] = col0;\n        layer->foreground[pos0].uv = uv;\n        layer->foreground[pos0++].col = col0;\n\n        layer->foreground[pos1].uv = uv;\n        layer->foreground[pos1++].col = col1;\n      }\n      else\n      {\n        u32 idx = ((src->bg_color << 6) | (src->fg_color << 2));\n        u32 idx0 = graphics->smzx_indices[idx    ];\n        u32 idx1 = graphics->smzx_indices[idx + 1];\n        u32 idx2 = graphics->smzx_indices[idx + 2];\n        u32 idx3 = graphics->smzx_indices[idx + 3];\n\n        col0 = idx0 == transparent_col ? 0 : graphics->flat_intensity_palette[(u8) idx0];\n        col1 = idx1 == transparent_col ? 0 : graphics->flat_intensity_palette[(u8) idx1];\n        col2 = idx2 == transparent_col ? 0 : graphics->flat_intensity_palette[(u8) idx2];\n        col3 = idx3 == transparent_col ? 0 : graphics->flat_intensity_palette[(u8) idx3];\n\n        if((col1 & col2 & col3) == 0)\n          draw_background_texture = false;\n\n        if(HAS_BACKGROUND_TEXTURE)\n          ((u32*) layer->background.data)[pos_bg] = col0;\n        layer->foreground[pos0].uv = uv;\n        layer->foreground[pos0++].col = col0;\n        layer->foreground[pos1].uv = uv;\n        layer->foreground[pos1++].col = col1;\n        layer->foreground[pos2].uv = uv;\n        layer->foreground[pos2++].col = col2;\n        layer->foreground[pos3].uv = uv;\n        layer->foreground[pos3++].col = col3;\n      }\n    }\n  }\n\n  if(!has_content)\n    return;\n\n  if(draw_background_texture)\n  {\n    C3D_TexFlush(&layer->background);\n\n    ctr_draw_2d_texture(render_data, &layer->background, 0,\n     layer->background.height - layer->h, layer->w, layer->h,\n     vlayer->x, vlayer->y, layer->w * 8, layer->h * 14,\n     (LAYER_DRAWORDER_MAX - layer->draw_order) + 0.66f, 0xffffffff, false);\n  }\n\n  ctr_prepare_playfield(render_data, layer->foreground, vlayer->x, vlayer->y, layer->z);\n  GSPGPU_FlushDataCache(layer->foreground, sizeof(struct v_char) * bufsize);\n\n  if(!SMZX)\n  {\n    C3D_TexBind(0, &render_data->charset_vram[0]);\n    if(!draw_background_texture)\n    {\n      C3D_SetTexEnv(0, &(render_data->env_playfield_inv));\n      C3D_DrawArrays(GPU_GEOMETRY_PRIM, 0, layer->w * layer->h);\n    }\n    C3D_SetTexEnv(0, &(render_data->env_playfield));\n    C3D_DrawArrays(GPU_GEOMETRY_PRIM, layer->w * layer->h, layer->w * layer->h);\n  }\n  else\n  {\n    C3D_SetTexEnv(0, &(render_data->env_playfield));\n    if(!draw_background_texture)\n    {\n      C3D_TexBind(0, &render_data->charset_vram[1]);\n      C3D_DrawArrays(GPU_GEOMETRY_PRIM, 0, layer->w * layer->h);\n    }\n    C3D_TexBind(0, &render_data->charset_vram[2]);\n    C3D_DrawArrays(GPU_GEOMETRY_PRIM, layer->w * layer->h, layer->w * layer->h);\n    C3D_TexBind(0, &render_data->charset_vram[3]);\n    C3D_DrawArrays(GPU_GEOMETRY_PRIM, layer->w * layer->h * 2, layer->w * layer->h);\n    C3D_TexBind(0, &render_data->charset_vram[4]);\n    C3D_DrawArrays(GPU_GEOMETRY_PRIM, layer->w * layer->h * 3, layer->w * layer->h);\n  }\n}\n\ntemplate<int SMZX, int HAS_BACKGROUND_TEXTURE>\nstatic inline void ctr_render_layer_inner(struct graphics_data *graphics,\n struct video_layer *vlayer, u32 bufsize)\n{\n  if(vlayer->transparent_col != -1)\n    ctr_render_layer_inner<SMZX, HAS_BACKGROUND_TEXTURE, 1>(graphics, vlayer, bufsize);\n  else\n    ctr_render_layer_inner<SMZX, HAS_BACKGROUND_TEXTURE, 0>(graphics, vlayer, bufsize);\n}\n\ntemplate<int SMZX>\nstatic inline void ctr_render_layer_inner(struct graphics_data *graphics,\n struct video_layer *vlayer, u32 bufsize)\n{\n  struct ctr_layer *layer = (struct ctr_layer *)vlayer->platform_layer_data;\n  if(layer->has_background_texture)\n    ctr_render_layer_inner<SMZX, 1>(graphics, vlayer, bufsize);\n  else\n    ctr_render_layer_inner<SMZX, 0>(graphics, vlayer, bufsize);\n}\n\nstatic inline void ctr_render_layer_inner(struct graphics_data *graphics,\n struct video_layer *vlayer, u32 bufsize)\n{\n  struct ctr_layer *layer = (struct ctr_layer *)vlayer->platform_layer_data;\n  if(layer->mode > 0)\n    ctr_render_layer_inner<1>(graphics, vlayer, bufsize);\n  else\n    ctr_render_layer_inner<0>(graphics, vlayer, bufsize);\n}\n\nstatic void ctr_render_layer(struct graphics_data *graphics,\n struct video_layer *vlayer)\n{\n  struct ctr_layer *layer = (struct ctr_layer *)vlayer->platform_layer_data;\n  struct ctr_render_data *render_data = (struct ctr_render_data *) graphics->render_data;\n  u32 bufsize = vlayer->w * vlayer->h * (vlayer->mode > 0 ? 4 : 2);\n  u32 max_bufsize = vlayer->w * vlayer->h * 4;\n  u32 i;\n\n  if(!ctr_should_render(render_data))\n    return;\n\n  if(layer != NULL && (layer->w != vlayer->w || layer->h != vlayer->h))\n  {\n    linearFree(layer->foreground);\n    if(layer->has_background_texture)\n      C3D_TexDelete(&(layer->background));\n    free(layer);\n    layer = NULL;\n  }\n\n  if(layer != NULL && (layer->draw_order != vlayer->draw_order))\n  {\n    layer->draw_order = vlayer->draw_order;\n    layer->z = (LAYER_DRAWORDER_MAX - layer->draw_order) + 0.33f;\n  }\n\n  if(layer == NULL)\n  {\n    layer = (struct ctr_layer *) cmalloc(sizeof(struct ctr_layer));\n    layer->w = vlayer->w; layer->h = vlayer->h;\n    layer->draw_order = vlayer->draw_order;\n    layer->z = (LAYER_DRAWORDER_MAX - layer->draw_order) + 0.33f;\n    layer->foreground = (struct v_char *) clinearAlloc(sizeof(struct v_char) * max_bufsize, 0x80);\n\n    /**\n     * This renderer has two methods to draw char backgrounds. The quicker\n     * method is to draw them as single pixels on a texture stretched to the\n     * size of the layer. The slower method is to draw the layer an extra time\n     * for the background colors.\n     *\n     * The former method seems to consume a vast amount of buffer space to the\n     * point it causes crashes when too many sprites are active. Only use it for\n     * the default layers (these are the layers most likely to benefit anyway).\n     */\n    if((vlayer->draw_order == LAYER_DRAWORDER_BOARD) ||\n     (vlayer->draw_order == LAYER_DRAWORDER_OVERLAY) ||\n     (vlayer->draw_order == LAYER_DRAWORDER_GAME_UI) ||\n     (vlayer->draw_order == LAYER_DRAWORDER_UI))\n    {\n      layer->has_background_texture = true;\n      layer->background.data = NULL;\n      C3D_TexInit(&(layer->background), to_texture_size(layer->w),\n       to_texture_size(layer->h), GPU_RGBA8);\n      C3D_TexSetFilter(&(layer->background), GPU_NEAREST, GPU_NEAREST);\n    }\n    else\n      layer->has_background_texture = false;\n\n    vlayer->platform_layer_data = layer;\n\n    // Initialize the foreground data.\n    for(i = 0; i < max_bufsize; i++)\n    {\n      layer->foreground[i].x = (i % layer->w);\n      layer->foreground[i].y = ((i / layer->w) % layer->h);\n      layer->foreground[i].uv = 0xFFFFFFFF;\n    }\n  }\n\n  layer->mode = vlayer->mode;\n\n  ctr_render_layer_inner(graphics, vlayer, bufsize);\n\n  render_data->layer_num++;\n}\n\nstatic void ctr_render_cursor(struct graphics_data *graphics, unsigned int x,\n unsigned int y, uint16_t color, unsigned int lines, unsigned int offset)\n{\n  struct ctr_render_data *render_data = (struct ctr_render_data *) graphics->render_data;\n  uint32_t flatcolor = graphics->flat_intensity_palette[color];\n\n  if(ctr_should_render(render_data))\n  {\n    ctr_draw_2d_texture(render_data, NULL, 0, 0, 0, 0,\n     x * 8, y * 14 + offset, 8, lines, CTR_LAYER_CURSOR, flatcolor, false);\n  }\n}\n\nstatic void ctr_render_mouse(struct graphics_data *graphics,\n unsigned int x, unsigned int y, unsigned int w, unsigned int h)\n{\n  struct ctr_render_data *render_data = (struct ctr_render_data *) graphics->render_data;\n  uint32_t col = 0xFFFFFFFF;\n\n  if(ctr_should_render(render_data))\n  {\n    C3D_AlphaBlend(GPU_BLEND_SUBTRACT, GPU_BLEND_ADD,\n     GPU_SRC_COLOR, GPU_DST_COLOR, GPU_SRC_ALPHA, GPU_DST_ALPHA);\n    ctr_draw_2d_texture(render_data, NULL, 0, 0, 0, 0, x, y, w, h, CTR_LAYER_MOUSE, col,\n     false);\n    C3D_AlphaBlend(GPU_BLEND_ADD, GPU_BLEND_ADD, GPU_SRC_ALPHA,\n     GPU_ONE_MINUS_SRC_ALPHA, GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA);\n  }\n}\n\nstatic inline void ctr_draw_playfield(struct ctr_render_data *render_data,\n boolean top_screen)\n{\n  int x, y;\n  int width, height;\n  float slider;\n\n  ctr_set_2d_projection_screen(render_data, top_screen);\n  if(top_screen)\n  {\n    if(ctr_keyboard_force_zoom_out())\n    {\n      slider = 1.0f;\n    }\n    else\n    {\n      slider = (osGet3DSliderState() * 1.3f) - 0.15f;\n\n      if(slider < 0.0f) slider = 0.0f;\n      if(slider > 1.0f) slider = 1.0f;\n    }\n\n    width = 400 + (240 * slider);\n    height = 240 + (144 * slider);\n\n    x = render_data->focus_x - (width / 2);\n    y = render_data->focus_y - (height / 2);\n\n    if(x < 0)\n      x = 0;\n\n    if((x + width) > 640)\n      x = 640 - width;\n\n    if(height > 350)\n    {\n      ctr_draw_2d_texture(render_data, &render_data->playfield_tex,\n       x, 512 - 350, width, 350, 0, (240 - (240 * 350 / height)) / 2,\n       400, 240 * 350 / height, 2.0f, 0xffffffff, true);\n    }\n    else\n    {\n      if(y < 0)\n        y = 0;\n\n      if((y + height) > 350)\n        y = 350 - height;\n\n      ctr_draw_2d_texture(render_data, &render_data->playfield_tex,\n       x, 512 - y - height, width, height, 0, 0, 400, 240,\n       2.0f, 0xffffffff, true);\n    }\n  }\n  else\n  {\n    if(get_bottom_screen_mode() == BOTTOM_SCREEN_MODE_KEYBOARD)\n    {\n      ctr_draw_2d_texture(render_data, &render_data->playfield_tex,\n       0, 512 - 350, 640, 350, 80, 12.75, 160, 87.5, 2.0f, 0xffffffff, true);\n    }\n    else\n    {\n      int width = 320, height = ctr_get_subscreen_height();\n      ctr_draw_2d_texture(render_data, &render_data->playfield_tex,\n        0, 512 - 350, 640, 350,\n        (int) ((320 - width) / 2), (int) ((240 - height) / 2),\n        width, height, 2.0f, 0xffffffff, true\n      );\n    }\n  }\n}\n\nstatic void ctr_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct ctr_render_data *render_data = (struct ctr_render_data *) graphics->render_data;\n\n  if(!ctr_should_render(render_data))\n  {\n    render_data->checked_frame = false;\n    return;\n  }\n\n#ifndef RDR_DEBUG\n  C3D_RenderTargetClear(render_data->target_top, C3D_CLEAR_ALL, 0x000000, 0);\n  C3D_FrameDrawOn(render_data->target_top);\n  ctr_draw_playfield(render_data, true);\n#endif\n\n  C3D_RenderTargetClear(render_data->target_bottom, C3D_CLEAR_ALL, 0x000000, 0);\n  C3D_FrameDrawOn(render_data->target_bottom);\n  if(get_bottom_screen_mode() == BOTTOM_SCREEN_MODE_KEYBOARD)\n  {\n    ctr_set_2d_projection_screen(render_data, false);\n    ctr_keyboard_draw(render_data);\n  }\n\n  ctr_draw_playfield(render_data, false);\n\n  render_data->cursor_on = 0;\n  render_data->mouse_on = 0;\n\n  C3D_FrameEnd(0);\n  render_data->rendering_frame = false;\n  render_data->checked_frame = false;\n}\n\nstatic void ctr_focus_pixel(struct graphics_data *graphics,\n unsigned int x, unsigned int y)\n{\n  struct ctr_render_data *render_data = (struct ctr_render_data *) graphics->render_data;\n\n  switch(get_allow_focus_changes())\n  {\n    case FOCUS_FORBID:\n      return;\n    case FOCUS_ALLOW:\n      if(render_data->last_focus_x != x || render_data->last_focus_y != y)\n      {\n        render_data->last_focus_x = x;\n        render_data->last_focus_y = y;\n        render_data->focus_x = x;\n        render_data->focus_y = y;\n      }\n      break;\n    case FOCUS_PASS:\n      render_data->focus_x = x;\n      render_data->focus_y = y;\n      break;\n  }\n}\n\nvoid render_ctr_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = ctr_init_video;\n  renderer->free_video = ctr_free_video;\n  renderer->create_window = ctr_create_window;\n  renderer->set_viewport = set_window_viewport_centered;\n  renderer->update_colors = ctr_update_colors;\n  renderer->remap_char_range = ctr_remap_char_range;\n  renderer->remap_char = ctr_remap_char;\n  renderer->remap_charbyte = ctr_remap_charbyte;\n  renderer->render_layer = ctr_render_layer;\n  renderer->render_cursor = ctr_render_cursor;\n  renderer->render_mouse = ctr_render_mouse;\n  renderer->sync_screen = ctr_sync_screen;\n  renderer->focus_pixel = ctr_focus_pixel;\n}\n\n"
  },
  {
    "path": "arch/3ds/render.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __3DS_RENDER_H__\n#define __3DS_RENDER_H__\n\n#include <stdlib.h>\n#include <3ds.h>\n#include <citro3d.h>\n\n#include \"../../src/compat.h\"\n\n__M_BEGIN_DECLS\n\nstruct ctr_render_data;\n\nC3D_Tex *ctr_load_png(const char *name);\n\nvoid ctr_draw_2d_texture(struct ctr_render_data *render_data, C3D_Tex *texture,\n int tx, int ty, int tw, int th,\n float x, float y, float w, float h, float z, u32 color, boolean flipy);\n\nvoid ctr_request_set_wide(bool wide);\nint ctr_get_subscreen_height(void);\n\n__M_END_DECLS\n\n#endif /* __3DS_RENDER_H__ */\n"
  },
  {
    "path": "arch/3ds/shader_2d.g.pica",
    "content": "; MegaZeux\n;\n; Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n; Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n; Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n;\n; This program is free software; you can redistribute it and/or\n; modify it under the terms of the GNU General Public License as\n; published by the Free Software Foundation; either version 2 of\n; the License, or (at your option) any later version.\n;\n; This program is distributed in the hope that it will be useful,\n; but WITHOUT ANY WARRANTY; without even the implied warranty of\n; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n; General Public License for more details.\n;\n; You should have received a copy of the GNU General Public License\n; along with this program; if not, write to the Free Software\n; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n.gsh point c0\n\n.fvec projection[4]\n\n.out outpos position\n.out outtc0 texcoord0\n.out outclr color\n\n.constf CONSTS(1.0, 0.0, 0.0, 0.0)\n\n.entry gmain\n.proc gmain\n\tmov r0.xy, v0.xy\n\tmov r0.z, v1.x \n\tmov r0.w, CONSTS.x\n\tmov r4.xy, v0.zw\n\tmov r8.zw, r0.zw\n\n\tmov r6.xy, v2.xy\n\tmov r6.zw, CONSTS.zw\n\tmov r5.xy, v2.zw\n        mov r10.zw, r6.zw\n\n\tsetemit 0\n\tadd r8.xy, r0.xy, r4.xy\n\tmov r10.y, r6.y\n\tadd r10.x, r6.x, r5.x\n\tcall emit_vertex\n\temit\n\n\tsetemit 1\n\tmov r8.x, r0.x\n\tadd r8.y, r0.y, r4.y\n\tmov r10, r6\n\tcall emit_vertex\n\temit\n\n\tsetemit 2, prim\n\tmov r8.xy, r0.xy\n\tmov r10.x, r6.x\n\tadd r10.y, r6.y, r5.y\t\n\tcall emit_vertex\n\temit\n\n\tsetemit 1, prim inv\n\tmov r8.y, r0.y\n\tadd r8.x, r0.x, r4.x\n\tadd r10.xy, r6.xy, r5.xy\n\tcall emit_vertex\n\temit\n\n\tend\n.end\n\n.proc emit_vertex\n\tdp4 outpos.x, projection[0], r8\n\tdp4 outpos.y, projection[1], r8\n\tdp4 outpos.z, projection[2], r8\n\tdp4 outpos.w, projection[3], r8\n\n\tmov outclr, v3\n\tmov outtc0, r10\n.end\n\n"
  },
  {
    "path": "arch/3ds/shader_2d.v.pica",
    "content": "; MegaZeux\n;\n; Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n; Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n; Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n;\n; This program is free software; you can redistribute it and/or\n; modify it under the terms of the GNU General Public License as\n; published by the Free Software Foundation; either version 2 of\n; the License, or (at your option) any later version.\n;\n; This program is distributed in the hope that it will be useful,\n; but WITHOUT ANY WARRANTY; without even the implied warranty of\n; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n; General Public License for more details.\n;\n; You should have received a copy of the GNU General Public License\n; along with this program; if not, write to the Free Software\n; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n.constf RGBS(0.00392156862745098, 0.00392156862745098, 0.00392156862745098, 0.00392156862745098)\n\n.out - dummy o0\n.out - dummy o1\n.out - dummy o2\n.out - dummy o3\n\n.proc main\n\tmov o0, v0\n\tmov o1, v1\n\tmov o2, v2\n\n        mov r1, RGBS\n\tmul o3, v3.wzyx, r1.x\n\n\tend\n.end\n\n\n"
  },
  {
    "path": "arch/3ds/shader_playfield.g.pica",
    "content": "; MegaZeux\n;\n; Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n; Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n; Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n;\n; This program is free software; you can redistribute it and/or\n; modify it under the terms of the GNU General Public License as\n; published by the Free Software Foundation; either version 2 of\n; the License, or (at your option) any later version.\n;\n; This program is distributed in the hope that it will be useful,\n; but WITHOUT ANY WARRANTY; without even the implied warranty of\n; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n; General Public License for more details.\n;\n; You should have received a copy of the GNU General Public License\n; along with this program; if not, write to the Free Software\n; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n.gsh point c0\n\n.fvec projection[4]\n.fvec offset\n\n.out outpos position\n.out outtc0 texcoord0\n.out outclr color\n\n.alias inpos v0\n.alias intc0 v1\n.alias inclr v2\n\n.constf SCALES(0.0009765625, 0.001953125, -1, 1)\n.constf UVOFF(8, -14, 0, 0)\n.constf POSSCALE(8.0, 14.0, 1.0, 1.0)\n\n.entry gmain\n.proc gmain\n\tmov r4, POSSCALE\n\tmov r2, intc0\n\tmov r3.xy, inpos\n\tmov r3.z, UVOFF.z\n\tmad r0, r3, r4, offset\n\n\tmov r11, SCALES\n\tmov r0.w, POSSCALE.w\n\tmul r5.xy, UVOFF.xy, r11.xy\n\tmul r6, r2, r11\n\n\tsetemit 0\n\tadd r8.xy, r0.xy, r4.xy\n\tadd r10.xy, r6.xy, r5.xy\n\tcall emit_vertex\n\temit\n\n\tsetemit 1\n\tmov r8.y, r0.y\n\tadd r8.x, r0.x, r4.x\n\tmov r10.y, r6.y\n\tadd r10.x, r6.x, r5.x\n\tcall emit_vertex\n\temit\n\n\tsetemit 2, prim\n\tmov r8, r0\n\tmov r10, r6\n\tcall emit_vertex\n\temit\n\n\tsetemit 1, prim inv\n\tmov r8.x, r0.x\n\tadd r8.y, r0.y, r4.y\n\tmov r10.x, r6.x\n\tadd r10.y, r6.y, r5.y\n\tcall emit_vertex\n\temit\n\n\tend\n.end\n\n.proc emit_vertex\n\tdp4 outpos.x, projection[0], r8\n\tdp4 outpos.y, projection[1], r8\n\tdp4 outpos.z, projection[2], r8\n\tdp4 outpos.w, projection[3], r8\n\n\tmov outclr, inclr\n\tmov outtc0, r10\n.end\n\n"
  },
  {
    "path": "arch/3ds/shader_playfield.v.pica",
    "content": "; MegaZeux\n;\n; Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n; Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n; Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n;\n; This program is free software; you can redistribute it and/or\n; modify it under the terms of the GNU General Public License as\n; published by the Free Software Foundation; either version 2 of\n; the License, or (at your option) any later version.\n;\n; This program is distributed in the hope that it will be useful,\n; but WITHOUT ANY WARRANTY; without even the implied warranty of\n; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n; General Public License for more details.\n;\n; You should have received a copy of the GNU General Public License\n; along with this program; if not, write to the Free Software\n; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n.out outpos position\n.out outtc0 texcoord0\n.out outclr color\n\n.alias inpos v0\n.alias intc0 v1\n.alias inclr v2\n\n.constf RGBS(0.00392156862745098, 0.00392156862745098, 0.00392156862745098, 0.00392156862745098)\n\n.proc main\n\tmov outpos, inpos\n\tmov outtc0, intc0\n        mov r1, RGBS\n\tmul outclr, inclr.wzyx, r1.x\n\n\tend\n.end\n\n\n"
  },
  {
    "path": "arch/3ds/thread.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2016, 2018 Adrian Siekierka <asiekierka@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __THREAD_3DS_H\n#define __THREAD_3DS_H\n\n#include \"../../src/compat.h\"\n\n#define STACK_SIZE_CTR 8096\n\n__M_BEGIN_DECLS\n\n#include <3ds.h>\n\n#define THREAD_RES void\n#define THREAD_RETURN do { return; } while(0)\n\ntypedef CondVar platform_cond;\ntypedef LightLock platform_mutex;\ntypedef LightSemaphore platform_sem;\ntypedef Thread platform_thread;\ntypedef Thread platform_thread_id;\ntypedef ThreadFunc platform_thread_fn;\n\nstatic inline boolean platform_mutex_init(platform_mutex *mutex)\n{\n  LightLock_Init(mutex);\n  return true;\n}\n\nstatic inline boolean platform_mutex_destroy(platform_mutex *mutex)\n{\n  return true;\n}\n\nstatic inline boolean platform_mutex_lock(platform_mutex *mutex)\n{\n  LightLock_Lock(mutex);\n  return true;\n}\n\nstatic inline boolean platform_mutex_unlock(platform_mutex *mutex)\n{\n  LightLock_Unlock(mutex);\n  return true;\n}\n\nstatic inline boolean platform_cond_init(platform_cond *cond)\n{\n  CondVar_Init(cond);\n  return true;\n}\n\nstatic inline boolean platform_cond_destroy(platform_cond *cond)\n{\n  return true;\n}\n\nstatic inline boolean platform_cond_wait(platform_cond *cond,\n platform_mutex *mutex)\n{\n  CondVar_Wait(cond, mutex);\n  return true;\n}\n\nstatic inline boolean platform_cond_timedwait(platform_cond *cond,\n platform_mutex *mutex, unsigned int timeout_ms)\n{\n  s64 timeout_ns = (s64)timeout_ms * 1000000;\n  if(CondVar_WaitTimeout(cond, mutex, timeout_ns))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_signal(platform_cond *cond)\n{\n  CondVar_Signal(cond);\n  return true;\n}\n\nstatic inline boolean platform_cond_broadcast(platform_cond *cond)\n{\n  CondVar_Broadcast(cond);\n  return true;\n}\n\nstatic inline boolean platform_sem_init(platform_sem *sem, unsigned init_value)\n{\n  if(init_value > INT16_MAX)\n    return false;\n  LightSemaphore_Init(sem, init_value, INT16_MAX);\n  return true;\n}\n\nstatic inline boolean platform_sem_destroy(platform_sem *sem)\n{\n  return true;\n}\n\nstatic inline boolean platform_sem_wait(platform_sem *sem)\n{\n  LightSemaphore_Acquire(sem, 1);\n  return true;\n}\n\nstatic inline boolean platform_sem_post(platform_sem *sem)\n{\n  LightSemaphore_Release(sem, 1);\n  return true;\n}\n\nstatic inline boolean platform_thread_create(platform_thread *thread,\n platform_thread_fn start_function, void *data)\n{\n  Thread t;\n  s32 priority;\n\n  svcGetThreadPriority(&priority, CUR_THREAD_HANDLE);\n  t = threadCreate(start_function, data, STACK_SIZE_CTR, priority-1, -1,\n   true);\n  if(t)\n  {\n    *thread = t;\n    return true;\n  }\n  return false;\n}\n\nstatic inline boolean platform_thread_join(platform_thread *thread)\n{\n  // Return value not documented...\n  threadJoin(*thread, U64_MAX);\n  return true;\n}\n\nstatic inline platform_thread_id platform_get_thread_id(void)\n{\n  return threadGetCurrent();\n}\n\nstatic inline boolean platform_is_same_thread(platform_thread_id a,\n platform_thread_id b)\n{\n  return a == b;\n}\n\n__M_END_DECLS\n\n#endif // __THREAD_3DS_H\n"
  },
  {
    "path": "arch/LICENSE.3rd",
    "content": "----------------------------------------------------------------\n  Third party software licenses for MegaZeux\n----------------------------------------------------------------\n\nThe licenses here applicable to a given build of MegaZeux will\nvary. Typically one C standard library (glibc/Newlib/musl/etc.),\none module playback library (libxmp/libmodplug/MikMod/Maxmod),\nSDL, and several other libraries will be linked. The ports that\nuse each library by default will be indicated below. Not all\nlibcs are accounted for here, but the ones most often linked by\nMegaZeux are.\n\nSome particularly verbose licenses are only provided as URL.\nFor MegaZeux's license, see LICENSE.\n\n\n----------------------------------------------------------------\n  glibc\n----------------------------------------------------------------\n\nglibc is available under the GNU Lesser General Public License 2.1.\nSee LICENSE.LGPL2 or /usr/share/licenses/glibc for more information.\n\nUsed by ports: Most Linux distributions.\nHome page:     https://www.gnu.org/software/libc/\nLicense URL:   https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html\n\n\n----------------------------------------------------------------\n  MSVCRT.DLL, WS2_32.DLL, WSOCK.DLL\n----------------------------------------------------------------\n\nThese Microsoft Windows libraries are proprietary software.\nLinking them is permitted under the GPL system library exception.\n\nUsed by ports: MinGW and MSVC.\nHome page:     https://www.microsoft.com/\n\n\n----------------------------------------------------------------\n  Apple libc\n----------------------------------------------------------------\n\nApple libc is available under the Apple Public Source License 2.0.\n\nUsed by ports: Darwin and Xcode.\nHome page:     https://opensource.apple.com/source/Libc/\nLicense URL:   https://opensource.apple.com/license/apsl/\n\n\n----------------------------------------------------------------\n  Newlib\n----------------------------------------------------------------\n\nNewlib is available under a collection of BSD-like licenses.\nSee LICENSE.Newlib or for more information.\n\nUsed by ports: NDS, 3DS, Wii, Wii U, Switch, PSP, PS Vita,\n               Dreamcast, AmigaOS 4.\nHome page:     https://sourceware.org/newlib/\nLicense URL:   https://sourceware.org/newlib/COPYING.NEWLIB\n\n\n----------------------------------------------------------------\n  musl\n----------------------------------------------------------------\n\nmusl is available under the MIT license.\n\nUsed by ports: Emscripten (also the default libc on some distributions).\nHome page:     https://musl.libc.org/\nLicense URL:   https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT\n\nCopyright © 2005-2020 Rich Felker, et al.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n----------------------------------------------------------------\n  Bionic libc\n----------------------------------------------------------------\n\nBionic is available under a collection of BSD-like licenses.\n\nUsed by ports: Android.\nHome page:     https://android.googlesource.com/platform/bionic/\nLicense URL:\n  https://android.googlesource.com/platform/bionic/+/master/libc/NOTICE\n\n\n----------------------------------------------------------------\n  DJGPP libc, libm, and crt0\n----------------------------------------------------------------\n\nDJGPP libc and crt0 are available under the GNU Lesser General\nPublic License 2.1. DJGPP libm is *probably* available under the\nGNU General Public License 2.0. DJGPP also contains contributed\ncode under various permissive licenses. See LICENSE.DJGPP,\nLICENSE.LGPL2, and LICENSE for more information.\n\nUsed by ports: DJGPP.\nHome page:     http://www.delorie.com/djgpp/\nSource URL:    http://www.delorie.com/pub/djgpp/ (djlsr205.zip)\n\n\n----------------------------------------------------------------\n  SDL\n----------------------------------------------------------------\n\nSDL is available under the zlib license.\ngamecontrollerdb.txt is available under an identical license.\n\nUsed by ports: Most.\nHome page:     https://www.libsdl.org/\n\nCopyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>\n  \nThis software is provided 'as-is', without any express or implied\nwarranty.  In no event will the authors be held liable for any damages\narising from the use of this software.\n\nPermission is granted to anyone to use this software for any purpose,\nincluding commercial applications, and to alter it and redistribute it\nfreely, subject to the following restrictions:\n  \n1. The origin of this software must not be misrepresented; you must not\n   claim that you wrote the original software. If you use this software\n   in a product, an acknowledgment in the product documentation would be\n   appreciated but is not required. \n2. Altered source versions must be plainly marked as such, and must not be\n   misrepresented as being the original software.\n3. This notice may not be removed or altered from any source distribution.\n\n\n----------------------------------------------------------------\n  SDL 1.2\n----------------------------------------------------------------\n\nSDL 1.2 is available under the GNU Lesser General Public License 2.1.\nSee LICENSE.LGPL2 or /usr/share/licenses/SDL for more information.\n\nUsed by ports: Mac OS X PowerPC, PSP, GP2X, Pandora, AmigaOS 4.\nHome page:     https://www.libsdl.org/\nLicense URL:   https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html\n\n\n----------------------------------------------------------------\n  khash\n----------------------------------------------------------------\n\nMZX uses a heavily modified khash which is available under the MIT license.\n\nUsed by ports: All.\nHome page:     https://attractivechaos.github.io/klib/\n\n   The MIT License\n\n   Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>\n\n   Permission is hereby granted, free of charge, to any person obtaining\n   a copy of this software and associated documentation files (the\n   \"Software\"), to deal in the Software without restriction, including\n   without limitation the rights to use, copy, modify, merge, publish,\n   distribute, sublicense, and/or sell copies of the Software, and to\n   permit persons to whom the Software is furnished to do so, subject to\n   the following conditions:\n\n   The above copyright notice and this permission notice shall be\n   included in all copies or substantial portions of the Software.\n\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n   SOFTWARE.\n\n\n----------------------------------------------------------------\n  zlib\n----------------------------------------------------------------\n\nzlib is available under the zlib license.\n\nUsed by ports: All.\nHome page:     https://zlib.net/\n\n  zlib.h -- interface of the 'zlib' general purpose compression library\n  version 1.2.11, January 15th, 2017\n\n  Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler\n\n  This software is provided 'as-is', without any express or implied\n  warranty.  In no event will the authors be held liable for any damages\n  arising from the use of this software.\n\n  Permission is granted to anyone to use this software for any purpose,\n  including commercial applications, and to alter it and redistribute it\n  freely, subject to the following restrictions:\n\n  1. The origin of this software must not be misrepresented; you must not\n     claim that you wrote the original software. If you use this software\n     in a product, an acknowledgment in the product documentation would be\n     appreciated but is not required.\n  2. Altered source versions must be plainly marked as such, and must not be\n     misrepresented as being the original software.\n  3. This notice may not be removed or altered from any source distribution.\n\n  Jean-loup Gailly        Mark Adler\n  jloup@gzip.org          madler@alumni.caltech.edu\n\n\n----------------------------------------------------------------\n  libpng\n----------------------------------------------------------------\n\nlibpng is available under a zlib-like license.\n\nUsed by ports: Most.\nHome page:     https://www.libpng.org/\n\nPNG Reference Library License version 2\n---------------------------------------\n\n * Copyright (c) 1995-2019 The PNG Reference Library Authors.\n * Copyright (c) 2018-2019 Cosmin Truta.\n * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson.\n * Copyright (c) 1996-1997 Andreas Dilger.\n * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.\n\nThe software is supplied \"as is\", without warranty of any kind,\nexpress or implied, including, without limitation, the warranties\nof merchantability, fitness for a particular purpose, title, and\nnon-infringement.  In no event shall the Copyright owners, or\nanyone distributing the software, be liable for any damages or\nother liability, whether in contract, tort or otherwise, arising\nfrom, out of, or in connection with the software, or the use or\nother dealings in the software, even if advised of the possibility\nof such damage.\n\nPermission is hereby granted to use, copy, modify, and distribute\nthis software, or portions hereof, for any purpose, without fee,\nsubject to the following restrictions:\n\n 1. The origin of this software must not be misrepresented; you\n    must not claim that you wrote the original software.  If you\n    use this software in a product, an acknowledgment in the product\n    documentation would be appreciated, but is not required.\n\n 2. Altered source versions must be plainly marked as such, and must\n    not be misrepresented as being the original software.\n\n 3. This Copyright notice may not be removed or altered from any\n    source or altered source distribution.\n\n\n----------------------------------------------------------------\n  libogg, libvorbis, and Tremor\n----------------------------------------------------------------\n\nlibogg, libvorbis, and Tremor are all available under the\nBSD-3-clause license.\n\nUsed by ports: All except DJGPP, NDS.\nHome page:     https://xiph.org/ogg/\nHome page:     https://xiph.org/vorbis/\n\nCopyright (c) 2002-2020 Xiph.org Foundation\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n- Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n\n- Redistributions in binary form must reproduce the above copyright\nnotice, this list of conditions and the following disclaimer in the\ndocumentation and/or other materials provided with the distribution.\n\n- Neither the name of the Xiph.org Foundation nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION\nOR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n----------------------------------------------------------------\n  stb_vorbis\n----------------------------------------------------------------\n\nstb_vorbis is available under a dual MIT/Unlicense license.\nMegaZeux uses a fork of stb_vorbis maintained by sezero as some\nof the required patches have not been merged upstream yet.\n\nUsed by ports: DJGPP\nHome page:     https://github.com/nothings/stb\nsezero fork:   https://github.com/sezero/stb\n\n------------------------------------------------------------------------------\nThis software is available under 2 licenses -- choose whichever you prefer.\n------------------------------------------------------------------------------\nALTERNATIVE A - MIT License\nCopyright (c) 2017 Sean Barrett\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n------------------------------------------------------------------------------\nALTERNATIVE B - Public Domain (www.unlicense.org)\nThis is free and unencumbered software released into the public domain.\nAnyone is free to copy, modify, publish, use, compile, sell, or distribute this\nsoftware, either in source code form or as a compiled binary, for any purpose,\ncommercial or non-commercial, and by any means.\nIn jurisdictions that recognize copyright laws, the author or authors of this\nsoftware dedicate any and all copyright interest in the software to the public\ndomain. We make this dedication for the benefit of the public at large and to\nthe detriment of our heirs and successors. We intend this dedication to be an\novert act of relinquishment in perpetuity of all present and future rights to\nthis software under copyright law.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n------------------------------------------------------------------------------\n\n\n----------------------------------------------------------------\n  libxmp\n----------------------------------------------------------------\n\nThe subset of libxmp used by MegaZeux is available under the MIT license.\n\nUsed by ports: All except NDS.  \nHome page:     http://xmp.sourceforge.net/\n\nExtended Module Player\nCopyright (C) 1996-2021 Claudio Matsuoka and Hipolito Carraro Jr\n\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the \"Software\"),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n\n----------------------------------------------------------------\n  libmodplug\n----------------------------------------------------------------\n\nlibmodplug is available in the public domain.\n\nUsed by ports: None (non-default compile-time option).\nHome page:     https://github.com/Konstanty/libmodplug\n\n\n----------------------------------------------------------------\n  MikMod\n----------------------------------------------------------------\n\nlibmikmod is available under the GNU Lesser General Public License 2.1.\nSee LICENSE.LGPL2 for more information.\n\nUsed by ports: None (non-default compile-time option).\nHome page:     http://mikmod.sourceforge.net/\nLicense URL:   https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html\n\n\n----------------------------------------------------------------\n  OpenMPT\n----------------------------------------------------------------\n\nlibopenmpt is available under the BSD-3-clause license.\n\nUsed by ports: None (non-default compile-time option).\nHome page:     https://lib.openmpt.org/libopenmpt/\n\nCopyright (c) 2004-2022, OpenMPT Project Developers and Contributors\nCopyright (c) 1997-2003, Olivier Lapicque\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the OpenMPT project nor the\n      names of its contributors may be used to endorse or promote products\n      derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n----------------------------------------------------------------\n  Reality Adlib Tracker v2\n----------------------------------------------------------------\n\nThe Reality Adlib Tracker v2 playercode is available in the public domain.\n\nUsed by ports: All except NDS.\nHome page:     https://www.3eality.com/productions/reality-adlib-tracker\n\n\n----------------------------------------------------------------\n  Maxmod\n----------------------------------------------------------------\n\nMaxmod is available under the ISC license.\n\nUsed by ports: NDS.\nHome page:     https://maxmod.org\n\n/****************************************************************************\n *                                                          __              *\n *                ____ ___  ____ __  ______ ___  ____  ____/ /              *\n *               / __ `__ \\/ __ `/ |/ / __ `__ \\/ __ \\/ __  /               *\n *              / / / / / / /_/ />  </ / / / / / /_/ / /_/ /                *\n *             /_/ /_/ /_/\\__,_/_/|_/_/ /_/ /_/\\____/\\__,_/                 *\n *                                                                          *\n *               Nintendo DS & Gameboy Advance Sound System                 *\n *                                                                          *\n *         Copyright (c) 2008, Mukunda Johnson (mukunda@maxmod.org)         *\n *                                                                          *\n * Permission to use, copy, modify, and/or distribute this software for any *\n * purpose with or without fee is hereby granted, provided that the above   *\n * copyright notice and this permission notice appear in all copies.        *\n *                                                                          *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES *\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF         *\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR  *\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES   *\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN    *\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF  *\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.           *\n ****************************************************************************/\n\n\n----------------------------------------------------------------\n  libnds and FatFS\n----------------------------------------------------------------\n\nBoth variants of the libnds SDK supported by MegaZeux (devkitPro\nlibnds 1.x, BlocksDS libnds 1.x-blocks) are available under various\nzlib licenses and a zlib-like license for FatFS.\n\nUsed by ports: NDS.\nHome page:     https://github.com/devkitPro/libnds\nHome page:     https://github.com/blocksds/libnds\n\n  Copyright (C) 2005 - 2008\n   Michael Noland (joat)\n   Jason Rogers (dovoto)\n   Dave Murpy (WinterMute)\n\n  This software is provided 'as-is', without any express or implied\n  warranty.  In no event will the authors be held liable for any\n  damages arising from the use of this software.\n\n  Permission is granted to anyone to use this software for any\n  purpose, including commercial applications, and to alter it and\n  redistribute it freely, subject to the following restrictions:\n\n  1. The origin of this software must not be misrepresented; you\n     must not claim that you wrote the original software. If you use\n     this software in a product, an acknowledgment in the product\n     documentation would be appreciated but is not required.\n  2. Altered source versions must be plainly marked as such, and\n     must not be misrepresented as being the original software.\n  3. This notice may not be removed or altered from any source\n     distribution.\n\n/*----------------------------------------------------------------------------/\n/  FatFs - Generic FAT Filesystem Module  Rx.xx                               /\n/-----------------------------------------------------------------------------/\n/\n/ Copyright (C) 20xx, ChaN, all right reserved.\n/\n/ FatFs module is an open source software. Redistribution and use of FatFs in\n/ source and binary forms, with or without modification, are permitted provided\n/ that the following condition is met:\n/\n/ 1. Redistributions of source code must retain the above copyright notice,\n/    this condition and the following disclaimer.\n/\n/ This software is provided by the copyright holder and contributors \"AS IS\"\n/ and any warranties related to this software are DISCLAIMED.\n/ The copyright owner or contributors be NOT LIABLE for any damages caused\n/ by use of this software.\n/----------------------------------------------------------------------------*/\n\n\n----------------------------------------------------------------\n  libctru\n----------------------------------------------------------------\n\nThe libctru 3DS SDK is available under the zlib license.\n\nUsed by ports: 3DS.\nHome page:     https://github.com/devkitPro/libctru\n\n  This software is provided 'as-is', without any express or implied\n  warranty.  In no event will the authors be held liable for any\n  damages arising from the use of this software.\n\n  Permission is granted to anyone to use this software for any\n  purpose, including commercial applications, and to alter it and\n  redistribute it freely, subject to the following restrictions:\n\n  1. The origin of this software must not be misrepresented; you\n     must not claim that you wrote the original software. If you use\n     this software in a product, an acknowledgment in the product\n     documentation would be appreciated but is not required.\n  2. Altered source versions must be plainly marked as such, and\n     must not be misrepresented as being the original software.\n  3. This notice may not be removed or altered from any source\n     distribution.\n\n\n----------------------------------------------------------------\n  libogc\n----------------------------------------------------------------\n\nlibogc consists primarily of code from RTEMS (GPLv2-or-later with\nlinking exception plus various permissive licenses, partially attributed),\npossibly with a small portion of Linux code (GPLv2-only, unattributed).\nThe remainder is available under various zlib licenses.\n\nhttps://www.rtems.org/news/2025-05-06-rtems-devkit-libogc-response/\n\nUsed by ports: Wii.\nHome page:     https://github.com/devkitPro/libogc\nRTEMS license: https://gitlab.rtems.org/rtems/rtos/rtems/-/blob/main/LICENSE.md\n\n  Copyright (C) 2004 - 2009\n    Michael Wiedenbauer (shagkur)\n    Dave Murphy (WinterMute)\n\n  This software is provided 'as-is', without any express or implied\n  warranty.  In no event will the authors be held liable for any\n  damages arising from the use of this software.\n\n  Permission is granted to anyone to use this software for any\n  purpose, including commercial applications, and to alter it and\n  redistribute it freely, subject to the following restrictions:\n\n  1. The origin of this software must not be misrepresented; you\n     must not claim that you wrote the original software. If you use\n     this software in a product, an acknowledgment in the product\n     documentation would be appreciated but is not required.\n  2. Altered source versions must be plainly marked as such, and\n     must not be misrepresented as being the original software.\n  3. This notice may not be removed or altered from any source\n     distribution.\n\n\n----------------------------------------------------------------\n  libnx\n----------------------------------------------------------------\n\nThe libnx Switch SDK is available under the ISC license.\n\nUsed by ports: Switch.\nHome page:     https://github.com/switchbrew/libnx\n\nCopyright 2017-2018 libnx Authors\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n\n----------------------------------------------------------------\n  crt0\n----------------------------------------------------------------\n\nSome MZX ports use a crt0 licensed under the Mozilla Public License 2.0.\nSee LICENSE.MPL2 for more information.\n\nUsed by ports: NDS, 3DS.\nHome page:     https://github.com/devkitPro/devkitarm-crtls\nLicense URL:   https://mozilla.org/MPL/2.0/\n\n\n----------------------------------------------------------------\n  dlmalloc\n----------------------------------------------------------------\n\ndlmalloc is available under the Creative Commons CC0 1.0 License.\nSee LICENSE.CC0 for more information.\n\nUsed by ports: NDS.\nHome page:     http://gee.cs.oswego.edu/dl/html/malloc.html (?)\nLicense URL:   https://creativecommons.org/publicdomain/zero/1.0/\n\n\n----------------------------------------------------------------\n  KallistiOS\n----------------------------------------------------------------\n\nThe KallistiOS SDK (libkallisti et al.) is available under the\nBSD-3-clause license.\n\nUsed by ports: Dreamcast\nHome page:     https://sourceforge.net/projects/cadcdev/\n\nAll of the documentation and software included in the KallistiOS Releases\nis copyrighted (C) 1997-2020 by Dan Potter, Lawrence Sebald, and others (as\nnoted in each file).\n\nCopyright (C) 1997-2020 KallistiOS Contributors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n3. Neither the name of Cryptic Allusion nor the names of its contributors\n   may be used to endorse or promote products derived from this software\n   without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\nOR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\nHOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\nOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGE.\n\n\n----------------------------------------------------------------\n  Xlib\n----------------------------------------------------------------\n\nXlib is available under a collection of MIT-like licenses.\n\nUsed by ports: Linux/BSD/Unix (optional).\nHome page:     https://www.x.org/\nLicense URL:\n  https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/COPYING\n\n\n----------------------------------------------------------------\n  Mesa\n----------------------------------------------------------------\n\nMesa is available under the MIT license.\n\nUsed by ports: Linux (dynamic linked, optional), Switch (static linked).\nHome page:     https://www.mesa3d.org/\n\nCopyright (C) 1999-2007  Brian Paul   All Rights Reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the \"Software\"),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included\nin all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\nOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\nTHE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n\n----------------------------------------------------------------\n  Nouveau\n----------------------------------------------------------------\n\nlibdrm_nouveau is available under the MIT license.\n\nUsed by ports: Linux (dynamic linked, optional), Switch (static linked).\nHome page:     https://nouveau.freedesktop.org/\n\n * Copyright 2012 Red Hat Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice (including the next\n * paragraph) shall be included in all copies or substantial portions of the\n * Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n * DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "arch/LICENSE.CC0",
    "content": "Creative Commons Legal Code\n\nCC0 1.0 Universal\n\n    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE\n    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN\n    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS\n    INFORMATION ON AN \"AS-IS\" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES\n    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS\n    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM\n    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED\n    HEREUNDER.\n\nStatement of Purpose\n\nThe laws of most jurisdictions throughout the world automatically confer\nexclusive Copyright and Related Rights (defined below) upon the creator\nand subsequent owner(s) (each and all, an \"owner\") of an original work of\nauthorship and/or a database (each, a \"Work\").\n\nCertain owners wish to permanently relinquish those rights to a Work for\nthe purpose of contributing to a commons of creative, cultural and\nscientific works (\"Commons\") that the public can reliably and without fear\nof later claims of infringement build upon, modify, incorporate in other\nworks, reuse and redistribute as freely as possible in any form whatsoever\nand for any purposes, including without limitation commercial purposes.\nThese owners may contribute to the Commons to promote the ideal of a free\nculture and the further production of creative, cultural and scientific\nworks, or to gain reputation or greater distribution for their Work in\npart through the use and efforts of others.\n\nFor these and/or other purposes and motivations, and without any\nexpectation of additional consideration or compensation, the person\nassociating CC0 with a Work (the \"Affirmer\"), to the extent that he or she\nis an owner of Copyright and Related Rights in the Work, voluntarily\nelects to apply CC0 to the Work and publicly distribute the Work under its\nterms, with knowledge of his or her Copyright and Related Rights in the\nWork and the meaning and intended legal effect of CC0 on those rights.\n\n1. Copyright and Related Rights. A Work made available under CC0 may be\nprotected by copyright and related or neighboring rights (\"Copyright and\nRelated Rights\"). Copyright and Related Rights include, but are not\nlimited to, the following:\n\n  i. the right to reproduce, adapt, distribute, perform, display,\n     communicate, and translate a Work;\n ii. moral rights retained by the original author(s) and/or performer(s);\niii. publicity and privacy rights pertaining to a person's image or\n     likeness depicted in a Work;\n iv. rights protecting against unfair competition in regards to a Work,\n     subject to the limitations in paragraph 4(a), below;\n  v. rights protecting the extraction, dissemination, use and reuse of data\n     in a Work;\n vi. database rights (such as those arising under Directive 96/9/EC of the\n     European Parliament and of the Council of 11 March 1996 on the legal\n     protection of databases, and under any national implementation\n     thereof, including any amended or successor version of such\n     directive); and\nvii. other similar, equivalent or corresponding rights throughout the\n     world based on applicable law or treaty, and any national\n     implementations thereof.\n\n2. Waiver. To the greatest extent permitted by, but not in contravention\nof, applicable law, Affirmer hereby overtly, fully, permanently,\nirrevocably and unconditionally waives, abandons, and surrenders all of\nAffirmer's Copyright and Related Rights and associated claims and causes\nof action, whether now known or unknown (including existing as well as\nfuture claims and causes of action), in the Work (i) in all territories\nworldwide, (ii) for the maximum duration provided by applicable law or\ntreaty (including future time extensions), (iii) in any current or future\nmedium and for any number of copies, and (iv) for any purpose whatsoever,\nincluding without limitation commercial, advertising or promotional\npurposes (the \"Waiver\"). Affirmer makes the Waiver for the benefit of each\nmember of the public at large and to the detriment of Affirmer's heirs and\nsuccessors, fully intending that such Waiver shall not be subject to\nrevocation, rescission, cancellation, termination, or any other legal or\nequitable action to disrupt the quiet enjoyment of the Work by the public\nas contemplated by Affirmer's express Statement of Purpose.\n\n3. Public License Fallback. Should any part of the Waiver for any reason\nbe judged legally invalid or ineffective under applicable law, then the\nWaiver shall be preserved to the maximum extent permitted taking into\naccount Affirmer's express Statement of Purpose. In addition, to the\nextent the Waiver is so judged Affirmer hereby grants to each affected\nperson a royalty-free, non transferable, non sublicensable, non exclusive,\nirrevocable and unconditional license to exercise Affirmer's Copyright and\nRelated Rights in the Work (i) in all territories worldwide, (ii) for the\nmaximum duration provided by applicable law or treaty (including future\ntime extensions), (iii) in any current or future medium and for any number\nof copies, and (iv) for any purpose whatsoever, including without\nlimitation commercial, advertising or promotional purposes (the\n\"License\"). The License shall be deemed effective as of the date CC0 was\napplied by Affirmer to the Work. Should any part of the License for any\nreason be judged legally invalid or ineffective under applicable law, such\npartial invalidity or ineffectiveness shall not invalidate the remainder\nof the License, and in such case Affirmer hereby affirms that he or she\nwill not (i) exercise any of his or her remaining Copyright and Related\nRights in the Work or (ii) assert any associated claims and causes of\naction with respect to the Work, in either case contrary to Affirmer's\nexpress Statement of Purpose.\n\n4. Limitations and Disclaimers.\n\n a. No trademark or patent rights held by Affirmer are waived, abandoned,\n    surrendered, licensed or otherwise affected by this document.\n b. Affirmer offers the Work as-is and makes no representations or\n    warranties of any kind concerning the Work, express, implied,\n    statutory or otherwise, including without limitation warranties of\n    title, merchantability, fitness for a particular purpose, non\n    infringement, or the absence of latent or other defects, accuracy, or\n    the present or absence of errors, whether or not discoverable, all to\n    the greatest extent permissible under applicable law.\n c. Affirmer disclaims responsibility for clearing rights of other persons\n    that may apply to the Work or any use thereof, including without\n    limitation any person's Copyright and Related Rights in the Work.\n    Further, Affirmer disclaims responsibility for obtaining any necessary\n    consents, permissions or other rights required for any use of the\n    Work.\n d. Affirmer understands and acknowledges that Creative Commons is not a\n    party to this document and has no duty or obligation with respect to\n    this CC0 or use of the Work.\n"
  },
  {
    "path": "arch/LICENSE.DJGPP",
    "content": "Licenses for DJGPP, including most licenses in the relevant source code.\nThis *should* be all of them aside from slightly different copyright\ndates, but there are quite a few and some have very minor differences.\n\nNote \"COPYING\" is available as \"LICENSE\" and \"COPYING.LIB\" is available\nas \"LICENSE.LGPL2\" as-distributed by MegaZeux.\n\nDJGPP and its source code are available here: http://www.delorie.com/djgpp/\n\n\n1) copying.dj (crt0, libc, libm)\n\nThis is the file \"copying.dj\".  It does NOT apply to any sources or\nbinaries copyrighted by UCB Berkeley, the Free Software Foundation, or\nany other agency besides DJ Delorie and others who have agreed to\nallow their sources to be distributed under these terms.\n\n   Copyright Information for sources and executables that are marked\n   Copyright (C) DJ Delorie\n                 334 North Rd\n                 Deerfield NH 03037-1110\n\nThis document is Copyright (C) DJ Delorie and may be distributed\nverbatim, but changing it is not allowed.\n\nSource code copyright DJ Delorie is distributed under the terms of the\nGNU General Public Licence, with the following exceptions:\n\n* Sources used to build crt0.o, gcrt0.o, libc.a, libdbg.a, and\n  libemu.a are distributed under the terms of the GNU Library General\n  Public License, rather than the GNU GPL.\n\n* Any existing copyright or authorship information in any given source\n  file must remain intact.  If you modify a source file, a notice to that\n  effect must be added to the authorship information in the source file. \n\n* Runtime binaries, as provided by DJ in DJGPP, may be distributed\n  without sources ONLY if the recipient is given sufficient information\n  to obtain a copy of djgpp themselves.  This primarily applies to\n  go32-v2.exe, emu387.dxe, and stubedit.exe.\n\n* Runtime objects and libraries, as provided by DJ in DJGPP, when\n  linked into an application, may be distributed without sources ONLY\n  if the recipient is given sufficient information to obtain a copy of\n  djgpp themselves.  This primarily applies to crt0.o and libc.a.\n\n-----\n\nChanges to source code copyright BSD, FSF, or others, by DJ Delorie\nfall under the terms of the original copyright.  Such files usually\nhave multiple copyright notices in them.\n\nA copy of the files \"COPYING\" and \"COPYING.LIB\" are included with this\ndocument.  If you did not receive a copy of these files, you may\nobtain one from whence this document was obtained, or by writing:\n\n      Free Software Foundation\n      51 Franklin Street, Fifth Floor\n      Boston, MA 02110-1301\n      USA\n\n\n2) Sun Microsystems (libm)\n\n/*\n * ====================================================\n * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.\n *\n * Developed at SunPro, a Sun Microsystems, Inc. business.\n * Permission to use, copy, modify, and distribute this\n * software is freely granted, provided that this notice \n * is preserved.\n * ====================================================\n */\n\n\n3) Alexander S. Aganichev (locale)\n\n/*\n * setlocal.c: Set or read international environment\n *\n * Copyright (C) 2002, 2005 Alexander S. Aganichev <asa@users.sf.net>\n *\n * This software may be used freely so long as this copyright notice is\n * left intact.  There is no warranty on this software.\n */\n\n\n4) Regents of the University of California (stdlib)\n\n/*\n * Copyright (c) 1983 Regents of the University of California.\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms are permitted\n * provided that: (1) source distributions retain this entire copyright\n * notice and comment, and (2) distributions including binaries display\n * the following acknowledgement:  ``This product includes software\n * developed by the University of California, Berkeley and its contributors''\n * in the documentation or other materials provided with the distribution\n * and in all advertising materials mentioning features or use of this\n * software. Neither the name of the University nor the names of its\n * contributors may be used to endorse or promote products derived\n * from this software without specific prior written permission.\n * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR\n * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.\n */\n\n\n5) Regents of the University of California (ctime)\n\n/*\n * Copyright (c) 1987, 1989 Regents of the University of California.\n * All rights reserved.\n *\n * This code is derived from software contributed to Berkeley by\n * Arthur David Olson of the National Cancer Institute.\n *\n * Redistribution and use in source and binary forms are permitted provided\n * that: (1) source distributions retain this entire copyright notice and\n * comment, and (2) distributions including binaries display the following\n * acknowledgement:  ``This product includes software developed by the\n * University of California, Berkeley and its contributors'' in the\n * documentation or other materials provided with the distribution and in\n * all advertising materials mentioning features or use of this software.\n * Neither the name of the University nor the names of its contributors may\n * be used to endorse or promote products derived from this software without\n * specific prior written permission.\n * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED\n * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.\n */\n\n\n6) Martin Strömberg (libc)\n\n * Copyright (C) 2003 Martin Str@\"omberg <ams@ludd.luth.se>.\n *\n * This software may be used freely so long as this copyright notice is\n * left intact. There is no warranty on this software.\n\n\n7) Eli Zaretskii (libc)\n\n * Copyright (c) 1995-98 Eli Zaretskii <eliz@is.elta.co.il>\n *\n * This software may be used freely so long as this copyright notice is\n * left intact.  There is no warranty on this software.\n\n\n8) Morten Welinder (libc)\n\n   Copyright 1995 by Morten Welinder\n   This file maybe freely distributed and modified as long as the\n   copyright notice remains.\n\n\n9) Borca Daniel and Andrew Zabolotny (libc)\n\n/* Copyright (C) 2003 Borca Daniel <dborca@yahoo.com>\n * Copyright (C) 2000 Andrew Zabolotny <bit@eltech.ru>\n * Partly based on work by Charles Sandmann and DJ Delorie.\n * Usage of this library is not restricted in any way.  \n * ABSOLUTELY no warranties.  Contributed to the DJGPP project.\n */\n\n\n10) Charles Sandmann (libc)\n\n/* Copyright (C) 1995 Charles Sandmann (sandmann@clio.rice.edu)\n   This software may be freely distributed with above copyright, no warranty.\n   Based on code by DJ Delorie, it's really his, enhanced, bugs fixed. */\n\n/* Copyright (C) 1994, 1995 Charles Sandmann (sandmann@clio.rice.edu)\n * ...\n * This file maybe freely distributed, no warranty. */\n\n/* Copyright (C) 1994, 1995 Charles Sandmann (sandmann@clio.rice.edu)\n * This file maybe freely distributed and modified as long as copyright remains.\n */\n\n/* Copyright (C) 1995 Charles Sandmann (sandmann@clio.rice.edu)\n   This software may be freely distributed, no warranty.\n\n\n11) Henry Spencer (regex)\n\nCopyright 1992, 1993, 1994, 1997 Henry Spencer.  All rights reserved.\nThis software is not subject to any license of the American Telephone\nand Telegraph Company or of the Regents of the University of California.\n\nPermission is granted to anyone to use this software for any purpose on\nany computer system, and to alter it and redistribute it, subject\nto the following restrictions:\n\n1. The author is not responsible for the consequences of use of this\n   software, no matter how awful, even if they arise from flaws in it.\n\n2. The origin of this software must not be misrepresented, either by\n   explicit claim or by omission.  Since few users ever read sources,\n   credits must appear in the documentation.\n\n3. Altered versions must be plainly marked as such, and must not be\n   misrepresented as being the original software.  Since few users\n   ever read sources, credits must appear in the documentation.\n\n4. This notice may not be removed or altered.\n\n\n12) Eric Backus (xstat)\n\n/*\n  (c) Copyright 1992 Eric Backus\n\n  This software may be used freely so long as this copyright notice is\n  left intact.  There is no warranty on this software.\n*/\n"
  },
  {
    "path": "arch/LICENSE.LGPL2",
    "content": "                  GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 2.1, February 1999\n\n Copyright (C) 1991, 1999 Free Software Foundation, Inc.\n 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n[This is the first released version of the Lesser GPL.  It also counts\n as the successor of the GNU Library Public License, version 2, hence\n the version number 2.1.]\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicenses are intended to guarantee your freedom to share and change\nfree software--to make sure the software is free for all its users.\n\n  This license, the Lesser General Public License, applies to some\nspecially designated software packages--typically libraries--of the\nFree Software Foundation and other authors who decide to use it.  You\ncan use it too, but we suggest you first think carefully about whether\nthis license or the ordinary General Public License is the better\nstrategy to use in any particular case, based on the explanations below.\n\n  When we speak of free software, we are referring to freedom of use,\nnot price.  Our General Public Licenses are designed to make sure that\nyou have the freedom to distribute copies of free software (and charge\nfor this service if you wish); that you receive source code or can get\nit if you want it; that you can change the software and use pieces of\nit in new free programs; and that you are informed that you can do\nthese things.\n\n  To protect your rights, we need to make restrictions that forbid\ndistributors to deny you these rights or to ask you to surrender these\nrights.  These restrictions translate to certain responsibilities for\nyou if you distribute copies of the library or if you modify it.\n\n  For example, if you distribute copies of the library, whether gratis\nor for a fee, you must give the recipients all the rights that we gave\nyou.  You must make sure that they, too, receive or can get the source\ncode.  If you link other code with the library, you must provide\ncomplete object files to the recipients, so that they can relink them\nwith the library after making changes to the library and recompiling\nit.  And you must show them these terms so they know their rights.\n\n  We protect your rights with a two-step method: (1) we copyright the\nlibrary, and (2) we offer you this license, which gives you legal\npermission to copy, distribute and/or modify the library.\n\n  To protect each distributor, we want to make it very clear that\nthere is no warranty for the free library.  Also, if the library is\nmodified by someone else and passed on, the recipients should know\nthat what they have is not the original version, so that the original\nauthor's reputation will not be affected by problems that might be\nintroduced by others.\n\f\n  Finally, software patents pose a constant threat to the existence of\nany free program.  We wish to make sure that a company cannot\neffectively restrict the users of a free program by obtaining a\nrestrictive license from a patent holder.  Therefore, we insist that\nany patent license obtained for a version of the library must be\nconsistent with the full freedom of use specified in this license.\n\n  Most GNU software, including some libraries, is covered by the\nordinary GNU General Public License.  This license, the GNU Lesser\nGeneral Public License, applies to certain designated libraries, and\nis quite different from the ordinary General Public License.  We use\nthis license for certain libraries in order to permit linking those\nlibraries into non-free programs.\n\n  When a program is linked with a library, whether statically or using\na shared library, the combination of the two is legally speaking a\ncombined work, a derivative of the original library.  The ordinary\nGeneral Public License therefore permits such linking only if the\nentire combination fits its criteria of freedom.  The Lesser General\nPublic License permits more lax criteria for linking other code with\nthe library.\n\n  We call this license the \"Lesser\" General Public License because it\ndoes Less to protect the user's freedom than the ordinary General\nPublic License.  It also provides other free software developers Less\nof an advantage over competing non-free programs.  These disadvantages\nare the reason we use the ordinary General Public License for many\nlibraries.  However, the Lesser license provides advantages in certain\nspecial circumstances.\n\n  For example, on rare occasions, there may be a special need to\nencourage the widest possible use of a certain library, so that it becomes\na de-facto standard.  To achieve this, non-free programs must be\nallowed to use the library.  A more frequent case is that a free\nlibrary does the same job as widely used non-free libraries.  In this\ncase, there is little to gain by limiting the free library to free\nsoftware only, so we use the Lesser General Public License.\n\n  In other cases, permission to use a particular library in non-free\nprograms enables a greater number of people to use a large body of\nfree software.  For example, permission to use the GNU C Library in\nnon-free programs enables many more people to use the whole GNU\noperating system, as well as its variant, the GNU/Linux operating\nsystem.\n\n  Although the Lesser General Public License is Less protective of the\nusers' freedom, it does ensure that the user of a program that is\nlinked with the Library has the freedom and the wherewithal to run\nthat program using a modified version of the Library.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.  Pay close attention to the difference between a\n\"work based on the library\" and a \"work that uses the library\".  The\nformer contains code derived from the library, whereas the latter must\nbe combined with the library in order to run.\n\f\n                  GNU LESSER GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License Agreement applies to any software library or other\nprogram which contains a notice placed by the copyright holder or\nother authorized party saying it may be distributed under the terms of\nthis Lesser General Public License (also called \"this License\").\nEach licensee is addressed as \"you\".\n\n  A \"library\" means a collection of software functions and/or data\nprepared so as to be conveniently linked with application programs\n(which use some of those functions and data) to form executables.\n\n  The \"Library\", below, refers to any such software library or work\nwhich has been distributed under these terms.  A \"work based on the\nLibrary\" means either the Library or any derivative work under\ncopyright law: that is to say, a work containing the Library or a\nportion of it, either verbatim or with modifications and/or translated\nstraightforwardly into another language.  (Hereinafter, translation is\nincluded without limitation in the term \"modification\".)\n\n  \"Source code\" for a work means the preferred form of the work for\nmaking modifications to it.  For a library, complete source code means\nall the source code for all modules it contains, plus any associated\ninterface definition files, plus the scripts used to control compilation\nand installation of the library.\n\n  Activities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning a program using the Library is not restricted, and output from\nsuch a program is covered only if its contents constitute a work based\non the Library (independent of the use of the Library in a tool for\nwriting it).  Whether that is true depends on what the Library does\nand what the program that uses the Library does.\n\n  1. You may copy and distribute verbatim copies of the Library's\ncomplete source code as you receive it, in any medium, provided that\nyou conspicuously and appropriately publish on each copy an\nappropriate copyright notice and disclaimer of warranty; keep intact\nall the notices that refer to this License and to the absence of any\nwarranty; and distribute a copy of this License along with the\nLibrary.\n\n  You may charge a fee for the physical act of transferring a copy,\nand you may at your option offer warranty protection in exchange for a\nfee.\n\f\n  2. You may modify your copy or copies of the Library or any portion\nof it, thus forming a work based on the Library, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) The modified work must itself be a software library.\n\n    b) You must cause the files modified to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    c) You must cause the whole of the work to be licensed at no\n    charge to all third parties under the terms of this License.\n\n    d) If a facility in the modified Library refers to a function or a\n    table of data to be supplied by an application program that uses\n    the facility, other than as an argument passed when the facility\n    is invoked, then you must make a good faith effort to ensure that,\n    in the event an application does not supply such function or\n    table, the facility still operates, and performs whatever part of\n    its purpose remains meaningful.\n\n    (For example, a function in a library to compute square roots has\n    a purpose that is entirely well-defined independent of the\n    application.  Therefore, Subsection 2d requires that any\n    application-supplied function or table used by this function must\n    be optional: if the application does not supply it, the square\n    root function must still compute square roots.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Library,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Library, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote\nit.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Library.\n\nIn addition, mere aggregation of another work not based on the Library\nwith the Library (or with a work based on the Library) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may opt to apply the terms of the ordinary GNU General Public\nLicense instead of this License to a given copy of the Library.  To do\nthis, you must alter all the notices that refer to this License, so\nthat they refer to the ordinary GNU General Public License, version 2,\ninstead of to this License.  (If a newer version than version 2 of the\nordinary GNU General Public License has appeared, then you can specify\nthat version instead if you wish.)  Do not make any other change in\nthese notices.\n\f\n  Once this change is made in a given copy, it is irreversible for\nthat copy, so the ordinary GNU General Public License applies to all\nsubsequent copies and derivative works made from that copy.\n\n  This option is useful when you wish to copy part of the code of\nthe Library into a program that is not a library.\n\n  4. You may copy and distribute the Library (or a portion or\nderivative of it, under Section 2) in object code or executable form\nunder the terms of Sections 1 and 2 above provided that you accompany\nit with the complete corresponding machine-readable source code, which\nmust be distributed under the terms of Sections 1 and 2 above on a\nmedium customarily used for software interchange.\n\n  If distribution of object code is made by offering access to copy\nfrom a designated place, then offering equivalent access to copy the\nsource code from the same place satisfies the requirement to\ndistribute the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  5. A program that contains no derivative of any portion of the\nLibrary, but is designed to work with the Library by being compiled or\nlinked with it, is called a \"work that uses the Library\".  Such a\nwork, in isolation, is not a derivative work of the Library, and\ntherefore falls outside the scope of this License.\n\n  However, linking a \"work that uses the Library\" with the Library\ncreates an executable that is a derivative of the Library (because it\ncontains portions of the Library), rather than a \"work that uses the\nlibrary\".  The executable is therefore covered by this License.\nSection 6 states terms for distribution of such executables.\n\n  When a \"work that uses the Library\" uses material from a header file\nthat is part of the Library, the object code for the work may be a\nderivative work of the Library even though the source code is not.\nWhether this is true is especially significant if the work can be\nlinked without the Library, or if the work is itself a library.  The\nthreshold for this to be true is not precisely defined by law.\n\n  If such an object file uses only numerical parameters, data\nstructure layouts and accessors, and small macros and small inline\nfunctions (ten lines or less in length), then the use of the object\nfile is unrestricted, regardless of whether it is legally a derivative\nwork.  (Executables containing this object code plus portions of the\nLibrary will still fall under Section 6.)\n\n  Otherwise, if the work is a derivative of the Library, you may\ndistribute the object code for the work under the terms of Section 6.\nAny executables containing that work also fall under Section 6,\nwhether or not they are linked directly with the Library itself.\n\f\n  6. As an exception to the Sections above, you may also combine or\nlink a \"work that uses the Library\" with the Library to produce a\nwork containing portions of the Library, and distribute that work\nunder terms of your choice, provided that the terms permit\nmodification of the work for the customer's own use and reverse\nengineering for debugging such modifications.\n\n  You must give prominent notice with each copy of the work that the\nLibrary is used in it and that the Library and its use are covered by\nthis License.  You must supply a copy of this License.  If the work\nduring execution displays copyright notices, you must include the\ncopyright notice for the Library among them, as well as a reference\ndirecting the user to the copy of this License.  Also, you must do one\nof these things:\n\n    a) Accompany the work with the complete corresponding\n    machine-readable source code for the Library including whatever\n    changes were used in the work (which must be distributed under\n    Sections 1 and 2 above); and, if the work is an executable linked\n    with the Library, with the complete machine-readable \"work that\n    uses the Library\", as object code and/or source code, so that the\n    user can modify the Library and then relink to produce a modified\n    executable containing the modified Library.  (It is understood\n    that the user who changes the contents of definitions files in the\n    Library will not necessarily be able to recompile the application\n    to use the modified definitions.)\n\n    b) Use a suitable shared library mechanism for linking with the\n    Library.  A suitable mechanism is one that (1) uses at run time a\n    copy of the library already present on the user's computer system,\n    rather than copying library functions into the executable, and (2)\n    will operate properly with a modified version of the library, if\n    the user installs one, as long as the modified version is\n    interface-compatible with the version that the work was made with.\n\n    c) Accompany the work with a written offer, valid for at\n    least three years, to give the same user the materials\n    specified in Subsection 6a, above, for a charge no more\n    than the cost of performing this distribution.\n\n    d) If distribution of the work is made by offering access to copy\n    from a designated place, offer equivalent access to copy the above\n    specified materials from the same place.\n\n    e) Verify that the user has already received a copy of these\n    materials or that you have already sent this user a copy.\n\n  For an executable, the required form of the \"work that uses the\nLibrary\" must include any data and utility programs needed for\nreproducing the executable from it.  However, as a special exception,\nthe materials to be distributed need not include anything that is\nnormally distributed (in either source or binary form) with the major\ncomponents (compiler, kernel, and so on) of the operating system on\nwhich the executable runs, unless that component itself accompanies\nthe executable.\n\n  It may happen that this requirement contradicts the license\nrestrictions of other proprietary libraries that do not normally\naccompany the operating system.  Such a contradiction means you cannot\nuse both them and the Library together in an executable that you\ndistribute.\n\f\n  7. You may place library facilities that are a work based on the\nLibrary side-by-side in a single library together with other library\nfacilities not covered by this License, and distribute such a combined\nlibrary, provided that the separate distribution of the work based on\nthe Library and of the other library facilities is otherwise\npermitted, and provided that you do these two things:\n\n    a) Accompany the combined library with a copy of the same work\n    based on the Library, uncombined with any other library\n    facilities.  This must be distributed under the terms of the\n    Sections above.\n\n    b) Give prominent notice with the combined library of the fact\n    that part of it is a work based on the Library, and explaining\n    where to find the accompanying uncombined form of the same work.\n\n  8. You may not copy, modify, sublicense, link with, or distribute\nthe Library except as expressly provided under this License.  Any\nattempt otherwise to copy, modify, sublicense, link with, or\ndistribute the Library is void, and will automatically terminate your\nrights under this License.  However, parties who have received copies,\nor rights, from you under this License will not have their licenses\nterminated so long as such parties remain in full compliance.\n\n  9. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Library or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Library (or any work based on the\nLibrary), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Library or works based on it.\n\n  10. Each time you redistribute the Library (or any work based on the\nLibrary), the recipient automatically receives a license from the\noriginal licensor to copy, distribute, link with or modify the Library\nsubject to these terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties with\nthis License.\n\f\n  11. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Library at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Library by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Library.\n\nIf any portion of this section is held invalid or unenforceable under any\nparticular circumstance, the balance of the section is intended to apply,\nand the section as a whole is intended to apply in other circumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  12. If the distribution and/or use of the Library is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Library under this License may add\nan explicit geographical distribution limitation excluding those countries,\nso that distribution is permitted only in or among countries not thus\nexcluded.  In such case, this License incorporates the limitation as if\nwritten in the body of this License.\n\n  13. The Free Software Foundation may publish revised and/or new\nversions of the Lesser General Public License from time to time.\nSuch new versions will be similar in spirit to the present version,\nbut may differ in detail to address new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Library\nspecifies a version number of this License which applies to it and\n\"any later version\", you have the option of following the terms and\nconditions either of that version or of any later version published by\nthe Free Software Foundation.  If the Library does not specify a\nlicense version number, you may choose any version ever published by\nthe Free Software Foundation.\n\f\n  14. If you wish to incorporate parts of the Library into other free\nprograms whose distribution conditions are incompatible with these,\nwrite to the author to ask for permission.  For software which is\ncopyrighted by the Free Software Foundation, write to the Free\nSoftware Foundation; we sometimes make exceptions for this.  Our\ndecision will be guided by the two goals of preserving the free status\nof all derivatives of our free software and of promoting the sharing\nand reuse of software generally.\n\n                            NO WARRANTY\n\n  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO\nWARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.\nEXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR\nOTHER PARTIES PROVIDE THE LIBRARY \"AS IS\" WITHOUT WARRANTY OF ANY\nKIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE\nLIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME\nTHE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN\nWRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY\nAND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU\nFOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR\nCONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE\nLIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING\nRENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A\nFAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF\nSUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH\nDAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\f\n           How to Apply These Terms to Your New Libraries\n\n  If you develop a new library, and you want it to be of the greatest\npossible use to the public, we recommend making it free software that\neveryone can redistribute and change.  You can do so by permitting\nredistribution under these terms (or, alternatively, under the terms of the\nordinary General Public License).\n\n  To apply these terms, attach the following notices to the library.  It is\nsafest to attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least the\n\"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the library's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This library is free software; you can redistribute it and/or\n    modify it under the terms of the GNU Lesser General Public\n    License as published by the Free Software Foundation; either\n    version 2.1 of the License, or (at your option) any later version.\n\n    This library is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n    Lesser General Public License for more details.\n\n    You should have received a copy of the GNU Lesser General Public\n    License along with this library; if not, write to the Free Software\n    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n\nAlso add information on how to contact you by electronic and paper mail.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the library, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the\n  library `Frob' (a library for tweaking knobs) written by James Random Hacker.\n\n  <signature of Ty Coon>, 1 April 1990\n  Ty Coon, President of Vice\n\nThat's all there is to it!\n"
  },
  {
    "path": "arch/LICENSE.MPL2",
    "content": "Mozilla Public License Version 2.0\n==================================\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\n    means each individual or legal entity that creates, contributes to\n    the creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\n    means the combination of the Contributions of others (if any) used\n    by a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\n    means Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\n    means Source Code Form to which the initial Contributor has attached\n    the notice in Exhibit A, the Executable Form of such Source Code\n    Form, and Modifications of such Source Code Form, in each case\n    including portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\n    means\n\n    (a) that the initial Contributor has attached the notice described\n        in Exhibit B to the Covered Software; or\n\n    (b) that the Covered Software was made available under the terms of\n        version 1.1 or earlier of the License, but not also under the\n        terms of a Secondary License.\n\n1.6. \"Executable Form\"\n    means any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\n    means a work that combines Covered Software with other material, in \n    a separate file or files, that is not Covered Software.\n\n1.8. \"License\"\n    means this document.\n\n1.9. \"Licensable\"\n    means having the right to grant, to the maximum extent possible,\n    whether at the time of the initial grant or subsequently, any and\n    all of the rights conveyed by this License.\n\n1.10. \"Modifications\"\n    means any of the following:\n\n    (a) any file in Source Code Form that results from an addition to,\n        deletion from, or modification of the contents of Covered\n        Software; or\n\n    (b) any new file in Source Code Form that contains any Covered\n        Software.\n\n1.11. \"Patent Claims\" of a Contributor\n    means any patent claim(s), including without limitation, method,\n    process, and apparatus claims, in any patent Licensable by such\n    Contributor that would be infringed, but for the grant of the\n    License, by the making, using, selling, offering for sale, having\n    made, import, or transfer of either its Contributions or its\n    Contributor Version.\n\n1.12. \"Secondary License\"\n    means either the GNU General Public License, Version 2.0, the GNU\n    Lesser General Public License, Version 2.1, the GNU Affero General\n    Public License, Version 3.0, or any later versions of those\n    licenses.\n\n1.13. \"Source Code Form\"\n    means the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\n    means an individual or a legal entity exercising rights under this\n    License. For legal entities, \"You\" includes any entity that\n    controls, is controlled by, or is under common control with You. For\n    purposes of this definition, \"control\" means (a) the power, direct\n    or indirect, to cause the direction or management of such entity,\n    whether by contract or otherwise, or (b) ownership of more than\n    fifty percent (50%) of the outstanding shares or beneficial\n    ownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\n    Licensable by such Contributor to use, reproduce, make available,\n    modify, display, perform, distribute, and otherwise exploit its\n    Contributions, either on an unmodified basis, with Modifications, or\n    as part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\n    for sale, have made, import, and otherwise transfer either its\n    Contributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\n    or\n\n(b) for infringements caused by: (i) Your and any other third party's\n    modifications of Covered Software, or (ii) the combination of its\n    Contributions with other software (except as part of its Contributor\n    Version); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\n    its Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\n    Form, as described in Section 3.1, and You must inform recipients of\n    the Executable Form how they can obtain a copy of such Source Code\n    Form by reasonable means in a timely manner, at a charge no more\n    than the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\n    License, or sublicense it under different terms, provided that the\n    license for the Executable Form does not attempt to limit or alter\n    the recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\n  This Source Code Form is \"Incompatible With Secondary Licenses\", as\n  defined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "arch/LICENSE.Newlib",
    "content": "The newlib subdirectory is a collection of software from several sources.\n\nEach file may have its own copyright/license that is embedded in the source \nfile.  Unless otherwise noted in the body of the source file(s), the following copyright\nnotices will apply to the contents of the newlib subdirectory:\n\n(1) Red Hat Incorporated\n\nCopyright (c) 1994-2009  Red Hat, Inc. All rights reserved.\n\nThis copyrighted material is made available to anyone wishing to use,\nmodify, copy, or redistribute it subject to the terms and conditions\nof the BSD License.   This program is distributed in the hope that \nit will be useful, but WITHOUT ANY WARRANTY expressed or implied, \nincluding the implied warranties of MERCHANTABILITY or FITNESS FOR \nA PARTICULAR PURPOSE.  A copy of this license is available at \nhttp://www.opensource.org/licenses. Any Red Hat trademarks that are\nincorporated in the source code or documentation are not subject to\nthe BSD License and may only be used or replicated with the express\npermission of Red Hat, Inc.\n\n(2) University of California, Berkeley\n\nCopyright (c) 1981-2000 The Regents of the University of California.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright notice, \n      this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright notice,\n      this list of conditions and the following disclaimer in the documentation\n      and/or other materials provided with the distribution.\n    * Neither the name of the University nor the names of its contributors \n      may be used to endorse or promote products derived from this software \n      without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" \nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \nIN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \nINDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \nNOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \nWHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \nARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY\nOF SUCH DAMAGE.\n\n(3) David M. Gay (AT&T 1991, Lucent 1998)\n\nThe author of this software is David M. Gay.\n\nCopyright (c) 1991 by AT&T.\n\nPermission to use, copy, modify, and distribute this software for any\npurpose without fee is hereby granted, provided that this entire notice\nis included in all copies of any software which is or includes a copy\nor modification of this software and in all copies of the supporting\ndocumentation for such software.\n\nTHIS SOFTWARE IS BEING PROVIDED \"AS IS\", WITHOUT ANY EXPRESS OR IMPLIED\nWARRANTY.  IN PARTICULAR, NEITHER THE AUTHOR NOR AT&T MAKES ANY\nREPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY\nOF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.\n\n-------------------------------------------------------------------\n\nThe author of this software is David M. Gay.\n\nCopyright (C) 1998-2001 by Lucent Technologies\nAll Rights Reserved\n\nPermission to use, copy, modify, and distribute this software and\nits documentation for any purpose and without fee is hereby\ngranted, provided that the above copyright notice appear in all\ncopies and that both that the copyright notice and this\npermission notice and warranty disclaimer appear in supporting\ndocumentation, and that the name of Lucent or any of its entities\nnot be used in advertising or publicity pertaining to\ndistribution of the software without specific, written prior\npermission.\n\nLUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,\nINCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.\nIN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY\nSPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER\nIN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\nARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\nTHIS SOFTWARE.\n\n\n(4) Advanced Micro Devices\n\nCopyright 1989, 1990 Advanced Micro Devices, Inc.\n\nThis software is the property of Advanced Micro Devices, Inc  (AMD)  which\nspecifically  grants the user the right to modify, use and distribute this\nsoftware provided this notice is not removed or altered.  All other rights\nare reserved by AMD.\n\nAMD MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO THIS\nSOFTWARE.  IN NO EVENT SHALL AMD BE LIABLE FOR INCIDENTAL OR CONSEQUENTIAL\nDAMAGES IN CONNECTION WITH OR ARISING FROM THE FURNISHING, PERFORMANCE, OR\nUSE OF THIS SOFTWARE.\n\nSo that all may benefit from your experience, please report  any  problems\nor  suggestions about this software to the 29K Technical Support Center at\n800-29-29-AMD (800-292-9263) in the USA, or 0800-89-1131  in  the  UK,  or\n0031-11-1129 in Japan, toll free.  The direct dial number is 512-462-4118.\n\nAdvanced Micro Devices, Inc.\n29K Support Products\nMail Stop 573\n5900 E. Ben White Blvd.\nAustin, TX 78741\n800-292-9263\n\n(5) \n\n(6)\n\n(7) Sun Microsystems\n\nCopyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.\n\nDeveloped at SunPro, a Sun Microsystems, Inc. business.\nPermission to use, copy, modify, and distribute this\nsoftware is freely granted, provided that this notice is preserved.\n\n(8) Hewlett Packard\n\n(c) Copyright 1986 HEWLETT-PACKARD COMPANY\n\nTo anyone who acknowledges that this file is provided \"AS IS\"\nwithout any express or implied warranty:\n    permission to use, copy, modify, and distribute this file\nfor any purpose is hereby granted without fee, provided that\nthe above copyright notice and this notice appears in all\ncopies, and that the name of Hewlett-Packard Company not be\nused in advertising or publicity pertaining to distribution\nof the software without specific, written prior permission.\nHewlett-Packard Company makes no representations about the\nsuitability of this software for any purpose.\n\n(9) Hans-Peter Nilsson\n\nCopyright (C) 2001 Hans-Peter Nilsson\n\nPermission to use, copy, modify, and distribute this software is\nfreely granted, provided that the above copyright notice, this notice\nand the following disclaimer are preserved with no changes.\n\nTHIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR\nIMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.\n\n(10) Stephane Carrez (m68hc11-elf/m68hc12-elf targets only)\n\nCopyright (C) 1999, 2000, 2001, 2002 Stephane Carrez (stcarrez@nerim.fr)\n\nThe authors hereby grant permission to use, copy, modify, distribute,\nand license this software and its documentation for any purpose, provided\nthat existing copyright notices are retained in all copies and that this\nnotice is included verbatim in any distributions. No written agreement,\nlicense, or royalty fee is required for any of the authorized uses.\nModifications to this software may be copyrighted by their authors\nand need not follow the licensing terms described here, provided that\nthe new terms are clearly indicated on the first page of each file where\nthey apply.\n\n(11) Christopher G. Demetriou\n\nCopyright (c) 2001 Christopher G. Demetriou\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n3. The name of the author may not be used to endorse or promote products\n   derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\nIMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\nOF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\nIN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\nNOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\nTHIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n(12) SuperH, Inc.\n\nCopyright 2002 SuperH, Inc. All rights reserved\n\nThis software is the property of SuperH, Inc (SuperH) which specifically\ngrants the user the right to modify, use and distribute this software\nprovided this notice is not removed or altered.  All other rights are\nreserved by SuperH.\n\nSUPERH MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO\nTHIS SOFTWARE.  IN NO EVENT SHALL SUPERH BE LIABLE FOR INDIRECT, SPECIAL,\nINCIDENTAL OR CONSEQUENTIAL DAMAGES IN CONNECTION WITH OR ARISING FROM\nTHE FURNISHING, PERFORMANCE, OR USE OF THIS SOFTWARE.\n\nSo that all may benefit from your experience, please report any problems\nor suggestions about this software to the SuperH Support Center via\ne-mail at softwaresupport@superh.com .\n\nSuperH, Inc.\n405 River Oaks Parkway\nSan Jose\nCA 95134\nUSA\n\n(13) Royal Institute of Technology\n\nCopyright (c) 1999 Kungliga Tekniska Högskolan\n(Royal Institute of Technology, Stockholm, Sweden).\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\n3. Neither the name of KTH nor the names of its contributors may be\n   used to endorse or promote products derived from this software without\n   specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY\nEXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE\nLIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR\nBUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\nWHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\nOTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\nADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n(14) Alexey Zelkin\n\nCopyright (c) 2000, 2001 Alexey Zelkin <phantom@FreeBSD.org>\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\nOR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\nHOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\nOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGE.\n\n(15) Andrey A. Chernov\n\nCopyright (C) 1997 by Andrey A. Chernov, Moscow, Russia.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\nOR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\nHOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\nOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGE.\n\n(16) FreeBSD\n\nCopyright (c) 1997-2002 FreeBSD Project.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\nOR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\nHOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\nOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGE.\n\n(17) S. L. Moshier\n\nAuthor:  S. L. Moshier.\n\nCopyright (c) 1984,2000 S.L. Moshier\n\nPermission to use, copy, modify, and distribute this software for any\npurpose without fee is hereby granted, provided that this entire notice\nis included in all copies of any software which is or includes a copy\nor modification of this software and in all copies of the supporting\ndocumentation for such software.\n\nTHIS SOFTWARE IS BEING PROVIDED \"AS IS\", WITHOUT ANY EXPRESS OR IMPLIED\nWARRANTY.  IN PARTICULAR,  THE AUTHOR MAKES NO REPRESENTATION\nOR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS\nSOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.\n\n(18) Citrus Project\n\nCopyright (c)1999 Citrus Project,\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\nOR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\nHOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\nOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGE.\n\n(19) Todd C. Miller\n\nCopyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n3. The name of the author may not be used to endorse or promote products\n   derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,\nINCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL\nTHE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\nEXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\nPROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;\nOR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\nWHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\nOTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\nADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n(20) DJ Delorie (i386)\nCopyright (C) 1991 DJ Delorie\nAll rights reserved.\n\nRedistribution, modification, and use in source and binary forms is permitted\nprovided that the above copyright notice and following paragraph are\nduplicated in all such forms.\n\nThis file is distributed WITHOUT ANY WARRANTY; without even the implied\nwarranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n(21) Free Software Foundation LGPL License (*-linux* targets only)\n\n   Copyright (C) 1990-1999, 2000, 2001    Free Software Foundation, Inc.\n   This file is part of the GNU C Library.\n   Contributed by Mark Kettenis <kettenis@phys.uva.nl>, 1997.\n\n   The GNU C Library is free software; you can redistribute it and/or\n   modify it under the terms of the GNU Lesser General Public\n   License as published by the Free Software Foundation; either\n   version 2.1 of the License, or (at your option) any later version.\n\n   The GNU C Library is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n   Lesser General Public License for more details.\n\n   You should have received a copy of the GNU Lesser General Public\n   License along with the GNU C Library; if not, write to the Free\n   Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n   02110-1301 USA.\n\n(22) Xavier Leroy LGPL License (i[3456]86-*-linux* targets only)\n\nCopyright (C) 1996 Xavier Leroy (Xavier.Leroy@inria.fr)\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU Library General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU Library General Public License for more details.\n\n(23) Intel (i960)\n\nCopyright (c) 1993 Intel Corporation\n\nIntel hereby grants you permission to copy, modify, and distribute this\nsoftware and its documentation.  Intel grants this permission provided\nthat the above copyright notice appears in all copies and that both the\ncopyright notice and this permission notice appear in supporting\ndocumentation.  In addition, Intel grants this permission provided that\nyou prominently mark as \"not part of the original\" any modifications\nmade to this software or documentation, and that the name of Intel\nCorporation not be used in advertising or publicity pertaining to\ndistribution of the software or the documentation without specific,\nwritten prior permission.\n\nIntel Corporation provides this AS IS, WITHOUT ANY WARRANTY, EXPRESS OR\nIMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY\nOR FITNESS FOR A PARTICULAR PURPOSE.  Intel makes no guarantee or\nrepresentations regarding the use of, or the results of the use of,\nthe software and documentation in terms of correctness, accuracy,\nreliability, currentness, or otherwise; and you rely on the software,\ndocumentation and results solely at your own risk.\n\nIN NO EVENT SHALL INTEL BE LIABLE FOR ANY LOSS OF USE, LOSS OF BUSINESS,\nLOSS OF PROFITS, INDIRECT, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES\nOF ANY KIND.  IN NO EVENT SHALL INTEL'S TOTAL LIABILITY EXCEED THE SUM\nPAID TO INTEL FOR THE PRODUCT LICENSED HEREUNDER.\n\n(24) Hewlett-Packard  (hppa targets only)\n\n(c) Copyright 1986 HEWLETT-PACKARD COMPANY\n\nTo anyone who acknowledges that this file is provided \"AS IS\"\nwithout any express or implied warranty:\n    permission to use, copy, modify, and distribute this file\nfor any purpose is hereby granted without fee, provided that\nthe above copyright notice and this notice appears in all\ncopies, and that the name of Hewlett-Packard Company not be\nused in advertising or publicity pertaining to distribution\nof the software without specific, written prior permission.\nHewlett-Packard Company makes no representations about the\nsuitability of this software for any purpose.\n\n(25) Henry Spencer (only *-linux targets)\n\nCopyright 1992, 1993, 1994 Henry Spencer.  All rights reserved.\nThis software is not subject to any license of the American Telephone\nand Telegraph Company or of the Regents of the University of California.\n\nPermission is granted to anyone to use this software for any purpose on\nany computer system, and to alter it and redistribute it, subject\nto the following restrictions:\n\n1. The author is not responsible for the consequences of use of this\n   software, no matter how awful, even if they arise from flaws in it.\n\n2. The origin of this software must not be misrepresented, either by\n   explicit claim or by omission.  Since few users ever read sources,\n   credits must appear in the documentation.\n\n3. Altered versions must be plainly marked as such, and must not be\n   misrepresented as being the original software.  Since few users\n   ever read sources, credits must appear in the documentation.\n\n4. This notice may not be removed or altered.\n\n(26) Mike Barcroft\n\nCopyright (c) 2001 Mike Barcroft <mike@FreeBSD.org>\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\nOR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\nHOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\nOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGE.\n\n(27) Konstantin Chuguev (--enable-newlib-iconv)\n\nCopyright (c) 1999, 2000\n   Konstantin Chuguev.  All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\nOR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\nHOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\nOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGE.\n\n   iconv (Charset Conversion Library) v2.0\n\n(28) Artem Bityuckiy (--enable-newlib-iconv)\n\nCopyright (c) 2003, Artem B. Bityuckiy, SoftMine Corporation.\nRights transferred to Franklin Electronic Publishers.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\nOR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\nHOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\nOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGE.\n\n(29) IBM, Sony, Toshiba (only spu-* targets)\n\n  (C) Copyright 2001,2006,\n  International Business Machines Corporation,\n  Sony Computer Entertainment, Incorporated,\n  Toshiba Corporation,\n\n  All rights reserved.\n\n  Redistribution and use in source and binary forms, with or without\n  modification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright notice,\n      this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the names of the copyright holders nor the names of their\n      contributors may be used to endorse or promote products derived from this\n      software without specific prior written permission.\n\n  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\n  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n  POSSIBILITY OF SUCH DAMAGE.\n\n(30) - Alex Tatmanjants (targets using libc/posix)\n\n  Copyright (c) 1995 Alex Tatmanjants <alex@elvisti.kiev.ua>\n \t\tat Electronni Visti IA, Kiev, Ukraine.\n \t\t\tAll rights reserved.\n \n  Redistribution and use in source and binary forms, with or without\n  modification, are permitted provided that the following conditions\n  are met:\n  1. Redistributions of source code must retain the above copyright\n     notice, this list of conditions and the following disclaimer.\n  2. Redistributions in binary form must reproduce the above copyright\n     notice, this list of conditions and the following disclaimer in the\n     documentation and/or other materials provided with the distribution.\n \n  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND\n  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE\n  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n  SUCH DAMAGE.\n\n(31) - M. Warner Losh (targets using libc/posix)\n\n  Copyright (c) 1998, M. Warner Losh <imp@freebsd.org>\n  All rights reserved.\n \n  Redistribution and use in source and binary forms, with or without\n  modification, are permitted provided that the following conditions\n  are met:\n  1. Redistributions of source code must retain the above copyright\n     notice, this list of conditions and the following disclaimer.\n  2. Redistributions in binary form must reproduce the above copyright\n     notice, this list of conditions and the following disclaimer in the\n     documentation and/or other materials provided with the distribution.\n \n  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND\n  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE\n  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n  SUCH DAMAGE.\n\n(32) - Andrey A. Chernov (targets using libc/posix)\n\n  Copyright (C) 1996 by Andrey A. Chernov, Moscow, Russia.\n  All rights reserved.\n \n  Redistribution and use in source and binary forms, with or without\n  modification, are permitted provided that the following conditions\n  are met:\n  1. Redistributions of source code must retain the above copyright\n     notice, this list of conditions and the following disclaimer.\n  2. Redistributions in binary form must reproduce the above copyright\n     notice, this list of conditions and the following disclaimer in the\n     documentation and/or other materials provided with the distribution.\n \n  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND\n  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n  ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n  SUCH DAMAGE.\n\n(33) - Daniel Eischen (targets using libc/posix)\n\n  Copyright (c) 2001 Daniel Eischen <deischen@FreeBSD.org>.\n  All rights reserved.\n \n  Redistribution and use in source and binary forms, with or without\n  modification, are permitted provided that the following conditions\n  are met:\n  1. Redistributions of source code must retain the above copyright\n     notice, this list of conditions and the following disclaimer.\n  2. Redistributions in binary form must reproduce the above copyright\n     notice, this list of conditions and the following disclaimer in the\n     documentation and/or other materials provided with the distribution.\n \n  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND\n  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n  ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n  SUCH DAMAGE.\n\n\n(34) - Jon Beniston (only lm32-* targets)\n\n Contributed by Jon Beniston <jon@beniston.com>\n\n Redistribution and use in source and binary forms, with or without\n modification, are permitted provided that the following conditions\n are met:\n 1. Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n 2. Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the distribution.\n\n THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND\n ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE\n FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n SUCH DAMAGE.\n\n\n(35) - ARM Ltd (arm and thumb variant targets only)\n\n Copyright (c) 2009 ARM Ltd\n All rights reserved.\n \n Redistribution and use in source and binary forms, with or without\n modification, are permitted provided that the following conditions\n are met:\n 1. Redistributions of source code must retain the above copyright\n    notice, this list of conditions and the following disclaimer.\n 2. Redistributions in binary form must reproduce the above copyright\n    notice, this list of conditions and the following disclaimer in the\n    documentation and/or other materials provided with the distribution.\n 3. The name of the company may not be used to endorse or promote\n    products derived from this software without specific prior written\n    permission.\n\n THIS SOFTWARE IS PROVIDED BY ARM LTD ``AS IS'' AND ANY EXPRESS OR IMPLIED\n WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\n MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n IN NO EVENT SHALL ARM LTD BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n(36) - Xilinx, Inc. (microblaze-* and powerpc-* targets)\n\nCopyright (c) 2004, 2009 Xilinx, Inc.  All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n1.  Redistributions source code must retain the above copyright notice,\nthis list of conditions and the following disclaimer.\n\n2.  Redistributions in binary form must reproduce the above copyright\nnotice, this list of conditions and the following disclaimer in the\ndocumentation and/or other materials provided with the distribution.\n\n3.  Neither the name of Xilinx nor the names of its contributors may be\nused to endorse or promote products derived from this software without\nspecific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS \"AS\nIS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\nTO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\nPARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\nTO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n\n(37) Texas Instruments Incorporated (tic6x-*, *-tirtos targets)\n\nCopyright (c) 1996-2010,2014 Texas Instruments Incorporated\nhttp://www.ti.com/\n\n Redistribution and  use in source  and binary forms, with  or without\n modification,  are permitted provided  that the  following conditions\n are met:\n\n    Redistributions  of source  code must  retain the  above copyright\n    notice, this list of conditions and the following disclaimer.\n\n    Redistributions in binary form  must reproduce the above copyright\n    notice, this  list of conditions  and the following  disclaimer in\n    the  documentation  and/or   other  materials  provided  with  the\n    distribution.\n\n    Neither the  name of Texas Instruments Incorporated  nor the names\n    of its  contributors may  be used to  endorse or  promote products\n    derived  from   this  software  without   specific  prior  written\n    permission.\n\n THIS SOFTWARE  IS PROVIDED BY THE COPYRIGHT  HOLDERS AND CONTRIBUTORS\n \"AS IS\"  AND ANY  EXPRESS OR IMPLIED  WARRANTIES, INCLUDING,  BUT NOT\n LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT\n OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n SPECIAL,  EXEMPLARY,  OR CONSEQUENTIAL  DAMAGES  (INCLUDING, BUT  NOT\n LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n THEORY OF  LIABILITY, WHETHER IN CONTRACT, STRICT  LIABILITY, OR TORT\n (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n(38) National Semiconductor (cr16-* and crx-* targets)\n\nCopyright (c) 2004 National Semiconductor Corporation\n\nThe authors hereby grant permission to use, copy, modify, distribute,\nand license this software and its documentation for any purpose, provided\nthat existing copyright notices are retained in all copies and that this\nnotice is included verbatim in any distributions. No written agreement,\nlicense, or royalty fee is required for any of the authorized uses.\nModifications to this software may be copyrighted by their authors\nand need not follow the licensing terms described here, provided that\nthe new terms are clearly indicated on the first page of each file where\nthey apply. \n\n(39) - Adapteva, Inc. (epiphany-* targets)\n\nCopyright (c) 2011, Adapteva, Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n * Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n * Neither the name of Adapteva nor the names of its contributors may be used\n   to endorse or promote products derived from this software without specific\n   prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n(40) - Altera Corportion (nios2-* targets)\n\nCopyright (c) 2003 Altera Corporation\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n   o Redistributions of source code must retain the above copyright\n     notice, this list of conditions and the following disclaimer. \n   o Redistributions in binary form must reproduce the above copyright\n     notice, this list of conditions and the following disclaimer in the \n     documentation and/or other materials provided with the distribution. \n   o Neither the name of Altera Corporation nor the names of its \n     contributors may be used to endorse or promote products derived from\n     this software without specific prior written permission. \n \nTHIS SOFTWARE IS PROVIDED BY ALTERA CORPORATION, THE COPYRIGHT HOLDER,\nAND ITS CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\nINCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL\nTHE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\nBUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS\nOF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\nTORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE\nUSE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  \n\n(41) Ed Schouten - Free BSD\n\nCopyright (c) 2008 Ed Schouten <ed@FreeBSD.org>\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\nOR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\nHOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\nOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGE.\n"
  },
  {
    "path": "arch/README",
    "content": "MULTI-PLATFORM SUPPORT FOR MEGAZEUX\n\nThe arch/ directory structure contains any code/machinery which is platform\nspecific. Though is it dubiously named \"arch\" this refers to the platform\n\"architecture\" rather than the CPU architecture. For example, although GP2X\nand NDS both use ARM CPUs, they require different library and build system\nmachinery to work correctly.\n\nDIRECTORY LAYOUT\n\nMost directories here contain at least a README file describing what toolchain\nand dependencies they require to work correctly. Some might also define\nadditional targets, for post-processing binaries or installing them on the\nsystem.\n\nAdditionally, all of these directories must have a Makefile.in which the build\nsystem will automatically include. This file should initialize any variables\nthat can be tweaked, for example the location of libraries or compiler flags.\nThe existing Makefile.in files should be a reasonable guide to this\nfunctionality.\n\nADDING A PLATFORM TO MEGAZEUX\n\nAdding support for a new platform is trivial. Think of a short name for the\nplatform (for example, Nintendo Dual-Screen is \"nds\") then create a directory\nin arch/ with that name. Create a Makefile.in for your platform. Initially,\nyou may find configuring for \"unix\" and then manually altering the\n\"platform.inc\" in the top level is enough to get your build to succeed.\n\nThen, some changes must be made to config.sh to automatically generate both\nthe src/config.h header (which is fully generic) and the platform.inc. This\nscript is what users of your port will invoke to build the package.\n\nYou may also want to make sure the \"build\" and \"archive\" targets do something\nfor your platform. These targets make auto-builds and shipping binary\npackages much easier.\n\nLICENSES\n\nIf your port requires reporting extra software licenses, you should put a\nnotification in LICENSE.3rd. If the boilerplate isn't reasonably small, it\nshould go in this folder named in the format \"LICENSE.[license here]\". In this\nsituation, add the license filename to EXTRA_LICENSES so it will be shipped\nwith relevant builds.\n"
  },
  {
    "path": "arch/amiga/CONFIG.AMIGA",
    "content": "#!/bin/sh\n\n./config.sh --platform amiga --prefix /usr/local/amiga \\\n            --disable-libpng --disable-utils --enable-release \"$@\"\n"
  },
  {
    "path": "arch/amiga/MZXRun",
    "content": "STACK 1000000\n\nAssign >NIL: \"USR:\" EXISTS\nIF NOT WARN\nELSE\nAssign USR: RAM:\nENDIF\n\nAssign >NIL: \"ETC:\" EXISTS\nIF NOT WARN\nELSE\nAssign ETC: RAM:\nENDIF\n\nrun >NIL: mzxrun.exe;\n"
  },
  {
    "path": "arch/amiga/Makefile.in",
    "content": "#\n# amiga makefile generics\n#\n\nCC      = ppc-amigaos-gcc -mcrt=clib2 -I${PREFIX}/clib2/include\nCXX     = ppc-amigaos-g++ -mcrt=clib2 -I${PREFIX}/clib2/include\nSTRIP   = ppc-amigaos-strip\nOBJCOPY = true\nCHMOD   = true\n\nBINEXT  = .exe\n\nDSOLDFLAGS = -shared\nDSOPRE     = lib\nDSOPOST    = .so\nDSORPATH   = -Wl,-rpath,${LIBDIR}\nDSOSONAME  = -Wl,-soname,\n\n# Block --host, which will break things.\nCROSS_COMPILE =\n\nifeq (${BUILD_MODULAR},1)\nARCH_CFLAGS   += -fPIC\nARCH_CXXFLAGS += -fPIC\nARCH_LDFLAGS  += -use-dynld -lunix\nendif\n\n# MZX breaks this optimisation on Amiga\nARCH_CFLAGS   += -fno-strict-aliasing\nARCH_CXXFLAGS += -fno-strict-aliasing\n\n# Amiga needs PNG to be statically linked (inc. zlib)\nLIBPNG_LDFLAGS = $(shell libpng12-config --static --ldflags)\n\n#\n# Need to nest Amiga binaries in a subdir\n#\nbuild := ${build_root}/MegaZeux\nbuild: ${build}\n\t${CP} arch/amiga/MegaZeux.info ${build}\n\t${CP} arch/amiga/MegaZeux.info ${build}/MZXRun.info\n\t${CP} arch/amiga/MegaZeux.info ${build_root}\n\t${CP} arch/amiga/MegaZeux arch/amiga/MZXRun ${build}\n\t${RM} ${build}/*.debug\n\ninclude arch/lha.inc\n"
  },
  {
    "path": "arch/amiga/MegaZeux",
    "content": "STACK 1000000\n\nAssign >NIL: \"USR:\" EXISTS\nIF NOT WARN\nELSE\nAssign USR: RAM:\nENDIF\n\nAssign >NIL: \"ETC:\" EXISTS\nIF NOT WARN\nELSE\nAssign ETC: RAM:\nENDIF\n\nrun >NIL: megazeux.exe;\n"
  },
  {
    "path": "arch/amiga/README",
    "content": "MEGAZEUX ON AMIGA OS 4\n\nSince 2.82b, MegaZeux supports cross compilation to Amiga OS 4. You will\nrequire a valid copy of the operating system software in order to run the\nfinal result; this is not a bare-metal Amiga binary.\n\nThe only supported toolchain is for OS 4 and PPC Amigas.\nm68k Amigas are not supported.\n\nPREPARATION\n\nThe port requires that the Amiga OS 4 SDK and toolchain is installed. There\nare several pre-built toolchains available from:\n\n\thttp://cross.zerohero.se/os4.html\n\nThere is an excellent guide for linking your toolchain and the platform SDK\ntogether here:\n\n\thttp://utilitybase.com/article/show/2007/06/23/231/Installing+an+AmigaOS+4+cross+compiler\n\nThe SDL library for AmigaOS 4 (clib2) must be installed. I got my copy from:\n\n\thttp://www.rcdrummond.net/sdl/index.html\n\nBUILDING MEGAZEUX\n\nFirst, configure MZX. A suitable config.sh command line is provided in\nCONFIG.AMIGA present in this subdirectory. Please note that --prefix MUST\ncorrectly point to the AmigaOS 4 SDK (otherwise it tries to use /usr's\nstub headers and fails).\n\nThen start the build with \"make\".\n\nINSTALLING MEGAZEUX\n\nThe usual \"make archive\" works to generate an LHA compilation of MegaZeux\nbinaries and Amiga specific scripts. Icons are provided where appropriate.\n\n--ajs\n"
  },
  {
    "path": "arch/android/CONFIG.ANDROID",
    "content": "#!/bin/sh\n\n# TODO: GL fixed renderers don't work. Probably not worth fixing.\n\n[ -z \"$NDK_PATH\" ] && { echo \"Must define NDK_PATH!\"; exit 1; }\n\n./config.sh --platform android --enable-release --enable-lto \\\n            --disable-utils --disable-libpng --disable-gl-fixed \"$@\"\n"
  },
  {
    "path": "arch/android/Makefile.deps",
    "content": "#\n# Makefile fragment for building, installing, or uninstalling Android dependencies.\n# Dependencies are installed into the project JNI libs dir and arch/android/includes.\n#\n# This should not be used directly, but should be used from Makefile.in.\n# This file relies on several variables defined by Makefile.in.\n#\n\n.PHONY: deps-build deps-install deps-uninstall\n\nifeq (${NDK_PATH},)\n$(error \"Must define $$NDK_PATH!\")\nendif\n\n#\n# NOTE: when updating the SDL_VERSION field here, the SDL android project code\n# in arch/android/project/app/src/main/org/libsdl/app/ needs to be updated with\n# the android project from the new SDL release (SDL/android-project/app/src/...).\n#\n\nSDL_VERSION       ?= 2.30.9\nLIBOGG_VERSION    ?= 1.3.5\nLIBVORBIS_VERSION ?= 1.3.7\nWGET              ?= wget\nTAR               ?= tar\nPATCH             ?= patch\n\nDEPS              := arch/android/deps\nDEPS_BUILD        := arch/android/deps/.build\nNDK_BUILD         := ${NDK_PATH}/ndk-build\n\nifeq (${HOST},${HOST_WIN32})\nNDK_BUILD         := ${NDK_PATH}/ndk-build.cmd\nendif\nifeq (${HOST},${HOST_WIN64})\nNDK_BUILD         := ${NDK_PATH}/ndk-build.cmd\nendif\n\nifneq (${V},1)\nWGET              := @${WGET}\nTAR               := @${TAR}\nNDK_BUILD         := @${NDK_BUILD}\nendif\n\n${DEPS} ${JNI_INCLUDES}:\n\t$(if @{V},,@echo \"  MKDIR   \" $@)\n\t${MKDIR} -p $@\n\nifeq (${ANDROID_TARGET},)\n\n${DEPS}/SDL2-${SDL_VERSION}: | ${DEPS}\n\t$(if ${V},,@echo \"  WGET    \" $@)\n\t${RM} -f $@.tar.gz\n\t${WGET} https://www.libsdl.org/release/SDL2-${SDL_VERSION}.tar.gz -O$@.tar.gz\n\t${TAR} -xzf $@.tar.gz -C ${DEPS}/\n\t${PATCH} -i ../../SDL2-page-sizes.patch -p1 -d ${DEPS}/SDL2-${SDL_VERSION}\n\n${DEPS}/libogg-${LIBOGG_VERSION}: | ${DEPS}\n\t$(if ${V},,@echo \"  WGET    \" $@)\n\t${RM} -f $@.tar.gz\n\t${WGET} http://downloads.xiph.org/releases/ogg/libogg-${LIBOGG_VERSION}.tar.gz -O$@.tar.gz\n\t${TAR} -xzf $@.tar.gz -C ${DEPS}/\n\n${DEPS}/libvorbis-${LIBVORBIS_VERSION}: | ${DEPS}\n\t$(if ${V},,@echo \"  WGET    \" $@)\n\t${RM} -f $@.tar.gz\n\t${WGET} http://downloads.xiph.org/releases/vorbis/libvorbis-${LIBVORBIS_VERSION}.tar.gz -O$@.tar.gz\n\t${TAR} -xzf $@.tar.gz -C ${DEPS}/\n\n${DEPS_BUILD}:\n\t$(if ${V},,@echo \"  MKDIR   \" $@)\n\t${MKDIR} -p ${DEPS_BUILD}\n\n${DEPS_BUILD}/libogg: ${DEPS}/libogg-${LIBOGG_VERSION} | ${DEPS_BUILD}\n\t${RM} -rf $@\n\t${CP} -r $< $@\n\t${CP} arch/android/libogg-Android.mk \"$@/Android.mk\"\n\t${CP} arch/android/config_types.h \"$@/include/ogg/\"\n\n${DEPS_BUILD}/libvorbis: ${DEPS}/libvorbis-${LIBVORBIS_VERSION} | ${DEPS_BUILD}\n\t${RM} -rf $@\n\t${CP} -r $< $@\n\t${CP} arch/android/libvorbis-Android.mk \"$@/Android.mk\"\n\n${DEPS_BUILD}/SDL2: ${DEPS}/SDL2-${SDL_VERSION} | ${DEPS_BUILD}\n\t${RM} -rf $@\n\t${CP} -r $< $@\n\n${DEPS_BUILD}/out: ${DEPS_BUILD}/libogg ${DEPS_BUILD}/libvorbis ${DEPS_BUILD}/SDL2\n\t${CP} ${JNI_DIR}/Android.mk ${DEPS_BUILD}\n\t$(if ${V},,@echo \"  NDKBUILD\")\n\t${NDK_BUILD} \\\n\t  NDK_PROJECT_PATH=\"${DEPS_BUILD}\" \\\n\t  NDK_LIBS_OUT=\"${DEPS_BUILD}/out\" \\\n\t  APP_BUILD_SCRIPT=\"${DEPS_BUILD}/Android.mk\" \\\n\t  APP_ABI=\"armeabi-v7a arm64-v8a x86 x86_64\" \\\n\t  APP_PLATFORM=\"android-16\"\n\ndeps-build: ${DEPS_BUILD}/out\n\nelse\n\ndep_targets := \\\n  ${JNI_INCLUDES}/SDL2 \\\n  ${JNI_INCLUDES}/ogg \\\n  ${JNI_INCLUDES}/vorbis \\\n  ${JNI_LIBS}/libSDL2.so \\\n  ${JNI_LIBS}/libogg.so \\\n  ${JNI_LIBS}/libvorbis.so \\\n  ${JNI_LIBS}/libvorbisfile.so\n\n${DEPS_BUILD}/out/${ABI}/%.so:\n\t@test -f $@ || { echo \"Missing $@! Aborting...\"; exit 1; }\n\n${JNI_LIBS}/%.so: ${DEPS_BUILD}/out/${ABI}/%.so | ${JNI_LIBS}\n\t$(if ${V},,@echo \"  CP      \" $< \" \" $@)\n\t${CP} $< $@\n\n${JNI_INCLUDES}/SDL2: ${DEPS_BUILD}/SDL2 | ${JNI_INCLUDES}\n\t$(if ${V},,@echo \"  CP      \" \"$</include/*\" $@)\n\t${MKDIR} -p $@\n\t${CP} $</include/* $@\n\n${JNI_INCLUDES}/ogg: ${DEPS_BUILD}/libogg | ${JNI_INCLUDES}\n\t$(if ${V},,@echo \"  CP      \" $</include ${JNI_INCLUDES})\n\t${CP} -r \"$</include/ogg\" ${JNI_INCLUDES}\n\n${JNI_INCLUDES}/vorbis: ${DEPS_BUILD}/libvorbis | ${JNI_INCLUDES}\n\t$(if ${V},,@echo \"  CP      \" $</include ${JNI_INCLUDES})\n\t${CP} -r \"$</include/vorbis\" ${JNI_INCLUDES}\n\ndeps-install: ${dep_targets}\n\ndeps-uninstall:\n\t$(if ${V},,@echo \"  RM \" ${dep_targets})\n\t${RM} -rf ${dep_targets}\n\nendif\n"
  },
  {
    "path": "arch/android/Makefile.in",
    "content": "#\n# android makefile generics\n#\n\n.PHONY: dist build archive clean deps-build deps-install deps-uninstall deps-clean\n\nPROJECT_DIR := arch/android/project\nAPP_DIR     := ${PROJECT_DIR}/app\nASSETS_DIR  := ${APP_DIR}/src/main/res/raw\n\n#\n# NOTE: Android Studio produces APKs at ${APP_DIR}/debug and ${APP_DIR}/release.\n# These directories are for the `make apk` rule, which invokes gradle directly.\n#\n\nBUILD_DIR   := ${APP_DIR}/build\nDEBUG_DIR   := ${BUILD_DIR}/outputs/apk/debug\nDEBUG_APK   := ${DEBUG_DIR}/app-debug.apk\nRELEASE_DIR := ${BUILD_DIR}/outputs/apk/release\nRELEASE_APK := ${RELEASE_DIR}/app-release.apk\n\nJNI_DIR     := ${APP_DIR}/jni\nJNI_LIB_DIR := ${JNI_DIR}/lib\nJNI_LIB_SO  := libmain.so\n\nifeq (${NDK_PATH},)\n$(error \"Must define $$NDK_PATH!\")\nendif\n\n# Block --host, which will break things.\nCROSS_COMPILE =\n# Disable rules that require target code to run natively.\nSUPPRESS_HOST_TARGETS ?= 1\n\n#\n# Determine ${TOOLCHAIN} (the common NDK toolchain path) if not provided.\n# Requires determining ${HOST} since currently the NDK toolchain path relies on it.\n#\n\nifeq (${TOOLCHAIN},)\nTOOLCHAIN_BASE := ${NDK_PATH}/toolchains/llvm/prebuilt\n\nHOST_LINUX := linux-x86_64\nHOST_MACOS := darwin-x86_64\nHOST_WIN32 := windows\nHOST_WIN64 := windows-x86_64\n\nifneq ($(wildcard ${TOOLCHAIN_BASE}/${HOST_LINUX}/),)\nHOST    := ${HOST_LINUX}\nGRADLEW := gradlew\nelse\nifneq ($(wildcard ${TOOLCHAIN_BASE}/${HOST_MACOS}/),)\nHOST    := ${HOST_MACOS}\nGRADLEW := gradlew\nelse\nifneq ($(wildcard ${TOOLCHAIN_BASE}/${HOST_WIN64}/),)\nHOST    := ${HOST_WIN64}\nGRADLEW := gradlew.bat\nelse\nifneq ($(wildcard ${TOOLCHAIN_BASE}/${HOST_WIN32}/),)\nHOST    := ${HOST_WIN32}\nGRADLEW := gradlew.bat\nelse\n$(error \"No valid $$HOST for ${TOOLCHAIN_BASE} could be found, aborting.\")\nendif\nendif\nendif\nendif\n\nTOOLCHAIN := ${TOOLCHAIN_BASE}/${HOST}\nendif\n\n#\n# Determine the toolchain to use.\n# If none is defined, disable make all and enable the meta targets.\n#\n\nANDROID_CFLAGS  :=\nANDROID_LDFLAGS :=\n\nifeq (${ANDROID_TARGET},arm)\nANDROID_LIBS    := arm-linux-androideabi\nCLANG_PRE       := armv7a-linux-androideabi\nBINUTILS_PRE    := arm-linux-androideabi\nABI             := armeabi-v7a\nSDK             := 16\nANDROID_CFLAGS  += -march=armv7-a -mthumb\nANDROID_LDFLAGS += -march=armv7-a -Wl,--fix-cortex-a8\nelse\n\nifeq (${ANDROID_TARGET},arm64)\nANDROID_LIBS    := aarch64-linux-android\nCLANG_PRE       := ${ANDROID_LIBS}\nBINUTILS_PRE    := ${ANDROID_LIBS}\nABI             := arm64-v8a\nSDK             := 21\n# https://developer.android.com/guide/practices/page-sizes\nANDROID_LDFLAGS += -Wl,-z,max-page-size=16384\nelse\n\nifeq (${ANDROID_TARGET},i686)\nANDROID_LIBS    := i686-linux-android\nCLANG_PRE       := ${ANDROID_LIBS}\nBINUTILS_PRE    := ${ANDROID_LIBS}\nABI             := x86\nSDK             := 16\nelse\n\nifeq (${ANDROID_TARGET},x86_64)\nANDROID_LIBS    := x86_64-linux-android\nCLANG_PRE       := ${ANDROID_LIBS}\nBINUTILS_PRE    := ${ANDROID_LIBS}\nABI             := x86_64\nSDK             := 21\n# https://developer.android.com/guide/practices/page-sizes\nANDROID_LDFLAGS += -Wl,-z,max-page-size=16384\nelse\n\nANDROID_LIBS := none\n\nendif\nendif\nendif\nendif\n\nifneq (${ANDROID_LIBS},none)\n\nCC             := ${TOOLCHAIN}/bin/${CLANG_PRE}${SDK}-clang\nCXX            := ${TOOLCHAIN}/bin/${CLANG_PRE}${SDK}-clang++\nAR             := ${TOOLCHAIN}/bin/${BINUTILS_PRE}-ar\nOBJCOPY        := ${TOOLCHAIN}/bin/${BINUTILS_PRE}-objcopy\nSTRIP          := ${TOOLCHAIN}/bin/${BINUTILS_PRE}-strip --strip-unneeded\n\nPREFIX         := ${TOOLCHAIN}/sysroot/usr\nPREFIX_LIBS    := ${TOOLCHAIN}/sysroot/usr/lib/${ANDROID_LIBS}/${SDK}\n\nJNI_INCLUDES   := arch/android/include\nJNI_LIBS       := ${JNI_LIB_DIR}/${ABI}\n\nANDROID_CFLAGS += -DANDROID -DSUBPLATFORM='\"${ANDROID_TARGET}\"' -fPIE -fPIC\nARCH_CFLAGS    += ${ANDROID_CFLAGS}\nARCH_CXXFLAGS  += ${ANDROID_CFLAGS}\nARCH_LDFLAGS   += ${ANDROID_LDFLAGS} -shared -llog\n\nSDL_PREFIX     := ${PREFIX}\nSDL_CFLAGS     := -I${JNI_INCLUDES}/SDL2\nSDL_LDFLAGS    := -L${JNI_LIBS} -lSDL2\n\nZLIB_CFLAGS    := -I${PREFIX}/include\nZLIB_LDFLAGS   := -L${PREFIX_LIBS} -lz\n\nLIBPNG_CFLAGS  := -I${JNI_INCLUDES}\nLIBPNG_LDFLAGS := -L${JNI_LIBS} -lpng16\n\nVORBIS_CFLAGS  := -I${JNI_INCLUDES}\nVORBIS_LDFLAGS := -L${JNI_LIBS} -logg -lvorbis -lvorbisfile\n\n${JNI_LIB_DIR}/${ABI}:\n\t$(if ${V},,@echo \"  MKDIR   \" $@)\n\t${MKDIR} -p \"$@\"\n\npackage: all ${JNI_LIB_DIR}/${ABI}\nifneq (${BUILD_EDITOR},)\n\t$(if ${V},,@echo \"  MV      \" ${mzx} \"${JNI_LIB_DIR}/${ABI}/${JNI_LIB_SO}\")\n\t${MV} ${mzx} ${JNI_LIB_DIR}/${ABI}/${JNI_LIB_SO}\nelse\n\t$(if ${V},,@echo \"  MV      \" ${mzxrun} \"${JNI_LIB_DIR}/${ABI}/${JNI_LIB_SO}\")\n\t${MV} ${mzxrun} ${JNI_LIB_DIR}/${ABI}/${JNI_LIB_SO}\nendif\n\nelse\n\n#\n# Define meta targets.\n#\n\n#\n# Disable everything but the clean rules when no target ABI is selected so\n# nobody accidentally uses them, then add \"dist\" to \"all\" and \"apk\" to \"archive\"\n# and \"build\" for convenience.\n#\nSUPPRESS_CC_TARGETS ?= 1\nSUPPRESS_BUILD_TARGETS ?= 1\nall: dist\narchive build: apk\n\npackage:\n\t$(error \"Use 'make dist', 'make apk', 'make deps-install', or 'make deps-uninstall' instead.)\n\ndeps-install: deps-build\n\t@${MAKE} ANDROID_TARGET=arm    deps-install\n\t@${MAKE} ANDROID_TARGET=arm64  deps-install\n\t@${MAKE} ANDROID_TARGET=i686   deps-install\n\t@${MAKE} ANDROID_TARGET=x86_64 deps-install\n\ndeps-uninstall:\n\t@${MAKE} ANDROID_TARGET=arm    deps-uninstall\n\t@${MAKE} ANDROID_TARGET=arm64  deps-uninstall\n\t@${MAKE} ANDROID_TARGET=i686   deps-uninstall\n\t@${MAKE} ANDROID_TARGET=x86_64 deps-uninstall\n\n${ASSETS_DIR}:\n\t$(if ${V},,@echo \"  MKDIR   \" $@)\n\t${MKDIR} -p \"$@\"\n\n${ASSETS_DIR}/assets.zip: ${ASSETS_DIR}\n\t@${MAKE} ANDROID_TARGET=arm    build/android/assets\n\t@(cd build/android/ && zip -9 -r assets.zip LICENSE LICENSE.3rd config.txt assets/ && mv assets.zip ../../$@)\n\ndist:\n\t@${MAKE} ANDROID_TARGET=arm    clean\n\t@echo \"Building 'arm'.\"\n\t@${MAKE} ANDROID_TARGET=arm    package\n\t@${MAKE} ANDROID_TARGET=arm    clean\n\t@echo \"Building 'arm64'.\"\n\t@${MAKE} ANDROID_TARGET=arm64  package\n\t@${MAKE} ANDROID_TARGET=arm64  clean\n\t@echo \"Building 'i686'.\"\n\t@${MAKE} ANDROID_TARGET=i686   package\n\t@${MAKE} ANDROID_TARGET=i686   clean\n\t@echo \"Building 'x86_64'.\"\n\t@${MAKE} ANDROID_TARGET=x86_64 package\n\t@${MAKE} ANDROID_TARGET=x86_64 clean\n\napk: ${ASSETS_DIR}/assets.zip\n\t@(cd ${PROJECT_DIR} && ./${GRADLEW} build)\n\t${MKDIR} -p ${build}\n\t$(if ${V},,@echo \"  CP      \" ${RELEASE_APK} build/dist/${SUBPLATFORM}/)\n\t${RM} -r build/dist/${SUBPLATFORM}\n\t${MKDIR} -p build/dist/${SUBPLATFORM}\n\t${CP} ${RELEASE_APK} build/dist/${SUBPLATFORM}/${TARGET}-${SUBPLATFORM}.apk\n\nclean:\n\t$(if ${V},,@echo \"  RM      \" ${JNI_DIR}/lib/*/libmain.so)\n\t${RM} -r ${JNI_DIR}/lib/*/libmain.so\n\t$(if ${V},,@echo \"  RM      \" ${BUILD_DIR})\n\t${RM} -r ${BUILD_DIR}\n\t$(if ${V},,@echo \"  RM      \" ${APP_DIR}/debug)\n\t${RM} -r ${APP_DIR}/debug\n\t$(if ${V},,@echo \"  RM      \" ${APP_DIR}/release)\n\t${RM} -r ${APP_DIR}/release\n\t$(if ${V},,@echo \"  RM      \" ${ASSETS_DIR}/assets.zip)\n\t${RM} ${ASSETS_DIR}/assets.zip\n\ndeps-clean:\n\t$(if ${V},,@echo \"  RM      \" arch/android/deps/)\n\t${RM} -r arch/android/deps/\n\nendif\n\ninclude arch/android/Makefile.deps\n"
  },
  {
    "path": "arch/android/README.md",
    "content": "# Building MegaZeux for Android\n\nNOTE: Only tested on Linux.\n\nNOTE: Requires a few gigabytes of hard drive space and some patience.\n\nThis port is more complicated than the typical MegaZeux port and utilizes\na hybrid of MegaZeux's build system and a Gradle project. For best results,\nfollow this guide closely and ask for help on the MegaZeux Discord if you have\nany problems.\n\nWhile Android Studio can optionally be used to complete the final stages of the\nbuild process, all other stages currently involve invoking Makefile targets\nfrom the command line. This guide assumes you are comfortable with the command\nline and GNU Make.\n\n## Overview\n\nThe Android port of MegaZeux consists of the following components:\n\n* MegaZeux built into native code as `libmain`.\n* Native dependency libraries (`libSDL2`, `libogg`, `libvorbis`).\n* The SDL Android project, which performs most required interactions with\n  sensors/bluetooth/etc. and interfaces with the native code via JNI.\n* MegaZeux-specific Java glue code to extract assets from the APK resources.\n\nEach native library (MegaZeux + all dependency libraries) must be built for the\nfollowing ABIs:\n\n| ABI           | Description               | Minimum API version |\n|---------------|---------------------------|---------------------|\n| `arm64-v8a`   | Modern 64-bit ARM devices | Lollipop 5.0 (API 21)\n| `armeabi-v7a` | Older 32-bit ARM devices  | Jelly Bean 4.1 (API 16)\n| `x86_64`      | Modern 64-bit x86 devices | Lollipop 5.0 (API 21)\n| `x86`         | Older 32-bit x86 devices  | Jelly Bean 4.1 (API 16)\n\nThe build process for the MegaZeux port is as follows:\n\n1) If the dependency libraries have not been built and installed to the Gradle\n  project, they must be built and installed to `arch/android/project/app/jni/lib/${ABI}`.\n  This is performed using the Makefile.\n2) MegaZeux itself needs to be built as a library and installed to the Gradle project.\n  This is also performed using the Makefile. The assets ZIP containing default\n  charsets, shaders, etc. is also created and installed to\n  `arch/android/project/app/src/main/res/raw/assets.zip` at this stage.\n3) The Java portions of MZX/SDL are compiled and a debug or release APK is\n  generated via the Gradle project. This can be invoked either by the Makefile\n  or by Android Studio, but the prior two steps *must* have been performed\n  for either option to work.\n\n## Environment Setup\n\nThe Android build system currently targets Java SE 7 to support\nAndroid Jelly Bean (4.1). Make sure JDK <=17 is installed and selected by\nsetting `JAVA_HOME`. (Fedora users: As of Fedora 42, OpenJDK 17 must be\ninstalled from a 3rd party repository.)\n\n1. Install the latest version of both the Android SDK and NDK. The easiest way\n  to do this is to install these through Android Studio. MegaZeux requires\n  a version of the NDK between r19 and r23 (version r24 and above drop support\n  for Jelly Bean - APIs 16, 17, 18 - which we continue to support).\n2. `export NDK_PATH=[the NDK path]`. As a hint, the directory should contain \"ndk-build\", among others.\n  * For Android Studio users, this will be `/home/.../Android/Sdk/ndk-bundle/`\n    Create the file `arch/android/project/local.properties` with the following lines:\n```\nsdk.dir=[SDK path here]\n```\n3. If you haven't done so already, use `./config.sh --platform android` (or use\n  the default configuration at `arch/android/CONFIG.ANDROID` instead).\n\n\n## Building Dependencies\n\nThe Android port requires several libraries to be built before MegaZeux can be\nbuilt. Because each dependency needs to be built for four different ABIs,\nMegaZeux provides several make targets for handling these dependencies:\n\n| Target           | Description |\n|------------------|-------------|\n| `deps-build`     | Download and build dependency libraries as-needed.\n| `deps-install`   | Invokes `make deps-build` and then installs the libraries to project JNI libraries folder.\n| `deps-uninstall` | Removes the built dependency libraries from the project.\n| `deps-clean`     | Delete the intermediate dependency build directory and all downloaded files (`arch/android/deps`).\n\nThe Android port currently relies on the following libraries:\n\n* `libogg`\n* `libvorbis`\n* `libSDL2`\n\nCurrently, the Android port does not use libpng. The Android NDK contains\na pre-built static library of zlib, so it does not need to be built manually.\n\nTo update the downloaded version of any of the dependencies, see `arch/android/Makefile.deps`.\n\n\n## Building MegaZeux\n\nInstead of building with `make` or `make all`, MegaZeux uses meta targets to\nmanage the several builds of MegaZeux required for the Android port. The following\ntargets are provided:\n\n| Target           | Description |\n|------------------|-------------|\n| `dist`           | Build of MegaZeux for all supported Android ABIs and install each build into the project JNI libraries folder. This will also create the assets.zip resource in the project's resources folder. To make this faster, it can be safely run with the -j# flag.\n| `apk`            | Invoke 'gradlew build' to produce a release APK and copy it to `build/dist/android/`. This must be performed after `make dist`.\n| `clean`          | Remove any leftover intermediate build files and perform various cleanup operations on the Gradle project.\n\nAndroid Studio can be used in place of `make apk`. Release builds from Android\nStudio are placed in `arch/android/project/app/release`.\n\n## Known Issues\n\nSeveral issues with this MegaZeux port have been reported. As this port is\neffectively the same as every other SDL 2 port on the MZX side, most of these\nissues seem to be compatibility issues between Android and SDL.\n\nIssues **KNOWN** to be caused by MegaZeux bugs:\n\n* The GLSL renderer relies on precise addressing of a 512x1024 texture in the\n  tilemap fragment shaders, which is generally hard or impossible to do unless\n  highp floats are available (or alternatively, mediump floats have more than\n  10 bits of precision). This renderer is generally slower than software\n  rendering (`glslscale`, `softscale`) on Android, so it is not selected by default.\n\nIssues **PROBABLY** caused by compatibility issues between Android and SDL:\n\n* Hardware keyboards: space, but no other keys, will emit `SDL_EVENT_KEY_DOWN`,\n  `SDL_EVENT_KEY_UP`, and `SDL_EVENT_TEXT_INPUT` simultaneously. This breaks\n  built-in shooting, `spacepressed`, etc. Only encountered in Android 15 (so far).\n  (Google Pixel 8a, Android 15.0, arm64-v8a)\n* Hardware keyboards: any key which usually produces both a scancode and text\n  will emit `SDL_EVENT_KEY_DOWN`, `SDL_EVENT_KEY_UP`, and `SDL_EVENT_TEXT_INPUT`\n  simultaneously. This breaks built-in shooting, `KEY#`, and anything else that\n  relies on the held status of a key. (Nexus 7 (2013), Android 6.0, armeabi-v7a)\n  Note: at least one earlier version of android (4.3) does not have this issue\n  on the same hardware.\n* The function keys (Fn) may not work as expected. (Xiaomi Mi 8 SE, Android 8.1, arm64-v8a)\n* RGBA components may be reversed to ARBG, causing serious graphical issues.\n  This can be worked around by turning on the \"Disable HW Overlays\" developer\n  option. (Xiaomi Mi 8 SE, Android 8.1, arm64-v8a)\n* Switching applications and/or connecting new Bluetooth devices may cause\n  crashes. (Xiaomi Mi 8 SE, Android 8.1, arm64-v8a)\n"
  },
  {
    "path": "arch/android/SDL2-page-sizes.patch",
    "content": "diff -Nrup SDL2-2.30.9.orig/Android.mk SDL2-2.30.9/Android.mk\n--- SDL2-2.30.9.orig/Android.mk\t2023-06-04 08:55:56.000000000 +0200\n+++ SDL2-2.30.9/Android.mk\t2024-12-01 10:07:36.795771235 +0100\n@@ -83,6 +83,9 @@ LOCAL_LDLIBS := -ldl -lGLESv1_CM -lGLESv\n \n LOCAL_LDFLAGS := -Wl,--no-undefined\n \n+# https://developer.android.com/guide/practices/page-sizes\n+LOCAL_LDFLAGS += \"-Wl,-z,max-page-size=16384\"\n+\n ifeq ($(NDK_DEBUG),1)\n     cmd-strip :=\n endif\n@@ -104,7 +107,8 @@ LOCAL_MODULE_FILENAME := libSDL2\n \n LOCAL_LDLIBS :=\n \n-LOCAL_LDFLAGS :=\n+# https://developer.android.com/guide/practices/page-sizes\n+LOCAL_LDFLAGS := \"-Wl,-z,max-page-size=16384\"\n \n LOCAL_EXPORT_LDLIBS := -ldl -lGLESv1_CM -lGLESv2 -llog -landroid\n \n"
  },
  {
    "path": "arch/android/config_types.h",
    "content": "#ifndef __CONFIG_TYPES_H__\n#define __CONFIG_TYPES_H__\n\n#include <stdint.h>\n\ntypedef int16_t ogg_int16_t;\ntypedef uint16_t ogg_uint16_t;\ntypedef int32_t ogg_int32_t;\ntypedef uint32_t ogg_uint32_t;\ntypedef int64_t ogg_int64_t;\ntypedef uint64_t ogg_uint64_t;\n\n#endif\n"
  },
  {
    "path": "arch/android/libogg-Android.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := libogg\nLOCAL_C_INCLUDES := $(LOCAL_PATH)/include\nLOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)\n\n# https://developer.android.com/guide/practices/page-sizes\nLOCAL_LDFLAGS += \"-Wl,-z,max-page-size=16384\"\n\nLOCAL_SRC_FILES := src/bitwise.c src/framing.c\n\ninclude $(BUILD_SHARED_LIBRARY)\n"
  },
  {
    "path": "arch/android/libvorbis-Android.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := libvorbis\nLOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/lib\nLOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include\nLOCAL_SHARED_LIBRARIES := libogg\n\n# https://developer.android.com/guide/practices/page-sizes\nLOCAL_LDFLAGS += \"-Wl,-z,max-page-size=16384\"\n\nLOCAL_SRC_FILES := lib/mdct.c lib/smallft.c lib/block.c lib/envelope.c lib/window.c lib/lsp.c \\\n\t\t\tlib/lpc.c lib/analysis.c lib/synthesis.c lib/psy.c lib/info.c \\\n\t\t\tlib/floor1.c lib/floor0.c \\\n\t\t\tlib/res0.c lib/mapping0.c lib/registry.c lib/codebook.c lib/sharedbook.c \\\n\t\t\tlib/lookup.c lib/bitrate.c\n\ninclude $(BUILD_SHARED_LIBRARY)\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := libvorbisfile\nLOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/lib\nLOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include\nLOCAL_SHARED_LIBRARIES := libogg libvorbis\n\n# https://developer.android.com/guide/practices/page-sizes\nLOCAL_LDFLAGS += \"-Wl,-z,max-page-size=16384\"\n\nLOCAL_SRC_FILES := lib/vorbisfile.c\n\ninclude $(BUILD_SHARED_LIBRARY)\n"
  },
  {
    "path": "arch/android/project/app/build.gradle",
    "content": "def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY');\ndef buildAsApplication = !buildAsLibrary\nif (buildAsApplication) {\n    apply plugin: 'com.android.application'\n} else {\n    apply plugin: 'com.android.library'\n}\n\n// Create a variable called keystorePropertiesFile, and initialize it to your\n// keystore.properties file, in the rootProject folder.\ndef keystorePropertiesFile = rootProject.file(\"keystore.properties\")\n\n// Initialize a new Properties() object called keystoreProperties.\ndef keystoreProperties = new Properties()\n\n// Load your keystore.properties file into the keystoreProperties object.\nkeystoreProperties.load(new FileInputStream(keystorePropertiesFile))\n\nandroid {\n    signingConfigs {\n        release {\n            storeFile file(keystoreProperties['storeFile'])\n            storePassword keystoreProperties['storePassword']\n            keyAlias keystoreProperties['keyAlias']\n            keyPassword keystoreProperties['keyPassword']\n        }\n    }\n    namespace \"net.digitalmzx.megazeux\"\n    ndkVersion \"23.2.8568313\"\n    compileSdkVersion 34\n    buildToolsVersion \"34.0.0\"\n    defaultConfig {\n        if (buildAsApplication) {\n            applicationId \"net.digitalmzx.megazeux\"\n        }\n        minSdkVersion 16\n        targetSdkVersion 34\n        versionCode 1\n        versionName \"1.0\"\n        externalNativeBuild {\n            ndkBuild {\n                arguments \"APP_PLATFORM=android-16\"\n                abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'\n            }\n        }\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n            signingConfig signingConfigs.release\n        }\n    }\n    if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) {\n        sourceSets.main {\n            jniLibs.srcDir 'jni/lib'\n        }\n        externalNativeBuild {\n            ndkBuild {\n                path 'jni/Android.mk'\n            }\n        }\n\n    }\n    lint {\n        abortOnError false\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_7\n        targetCompatibility JavaVersion.VERSION_1_7\n    }\n\n    if (buildAsLibrary) {\n        libraryVariants.all { variant ->\n            variant.outputs.each { output ->\n                def outputFile = output.outputFile\n                if (outputFile != null && outputFile.name.endsWith(\".aar\")) {\n                    def fileName = \"org.libsdl.app.aar\";\n                    output.outputFile = new File(outputFile.parent, fileName);\n                }\n            }\n        }\n    }\n}\n\ndependencies {\n}\n"
  },
  {
    "path": "arch/android/project/app/jni/Android.mk",
    "content": "include $(call all-subdir-makefiles)\n"
  },
  {
    "path": "arch/android/project/app/jni/Application.mk",
    "content": "# Uncomment this if you're using STL in your project\n# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information\n# APP_STL := stlport_static\n\n# Min runtime API level\nAPP_PLATFORM=android-16\n"
  },
  {
    "path": "arch/android/project/app/jni/main/Android.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := main\nLOCAL_SRC_FILES := ../lib/$(TARGET_ARCH_ABI)/libmain.so\nLOCAL_SHARED_LIBRARIES := SDL2 ogg vorbis\n\ninclude $(PREBUILT_SHARED_LIBRARY)\n"
  },
  {
    "path": "arch/android/project/app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in [sdk]/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "arch/android/project/app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:installLocation=\"auto\">\n\n    <!-- OpenGL ES 2.0 -->\n    <uses-feature android:glEsVersion=\"0x00020000\" />\n\n    <!-- Touchscreen support -->\n    <uses-feature\n        android:name=\"android.hardware.touchscreen\"\n        android:required=\"false\" />\n\n    <!-- Game controller support -->\n    <uses-feature\n        android:name=\"android.hardware.bluetooth\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.gamepad\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.usb.host\"\n        android:required=\"false\" />\n\n    <!-- External mouse input events -->\n    <uses-feature\n        android:name=\"android.hardware.type.pc\"\n        android:required=\"false\" />\n\n    <!-- Allow writing to external storage -->\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\" />\n    <!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->\n    <!-- <uses-permission android:name=\"android.permission.BLUETOOTH\" android:maxSdkVersion=\"30\" /> -->\n    <!-- <uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" /> -->\n    <!-- Allow access to the vibrator -->\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n\n\n    <application\n        android:allowBackup=\"true\"\n        android:hardwareAccelerated=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\"\n        tools:ignore=\"GoogleAppIndexingWarning\">\n        <activity\n            android:name=\".GameActivity\"\n            android:alwaysRetainTaskState=\"true\"\n            android:configChanges=\"layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation\"\n            android:exported=\"false\"\n            android:screenOrientation=\"sensorLandscape\"\n            android:label=\"MegaZeux\"\n            android:preferMinimalPostProcessing=\"true\"\n            android:launchMode=\"singleInstance\">\n\n        </activity>\n        <!--\n         Example of setting SDL hints from AndroidManifest.xml:\n        <meta-data android:name=\"SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK\" android:value=\"0\"/>\n        -->\n        <activity\n            android:name=\".MainActivity\"\n            android:alwaysRetainTaskState=\"true\"\n            android:configChanges=\"layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation\"\n            android:exported=\"true\"\n            android:label=\"@string/app_name\"\n            android:preferMinimalPostProcessing=\"true\"\n            android:launchMode=\"singleInstance\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n            <!-- Drop file event -->\n            <!--\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <data android:mimeType=\"*/*\" />\n            </intent-filter>\n            -->\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "arch/android/project/app/src/main/java/net/digitalmzx/megazeux/GameActivity.java",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019 Adrian Siekierka <kontakt@asie.pl>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\npackage net.digitalmzx.megazeux;\n\nimport android.app.Activity;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.view.View;\n\nimport org.libsdl.app.SDLActivity;\n\nimport java.io.File;\n\npublic class GameActivity extends SDLActivity\n{\n  static File getAssetPath(Activity activity)\n  {\n    File extDir = activity.getExternalFilesDir(null);\n    if(extDir == null)\n    {\n      extDir = new File(Environment.getExternalStorageDirectory(), \"data/megazeux\");\n      if(!extDir.exists())\n      {\n        //noinspection StatementWithEmptyBody\n        if(!extDir.mkdirs())\n        {\n          // pass\n        }\n      }\n    }\n\n    return extDir;\n  }\n\n  @Override\n  protected String[] getArguments()\n  {\n    return new String[] { new File(getAssetPath(this), \"megazeux\").getAbsolutePath() };\n  }\n}\n"
  },
  {
    "path": "arch/android/project/app/src/main/java/net/digitalmzx/megazeux/MainActivity.java",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019 Adrian Siekierka <kontakt@asie.pl>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\npackage net.digitalmzx.megazeux;\n\nimport android.Manifest;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.app.Activity;\nimport android.os.Environment;\nimport android.provider.Settings;\nimport android.view.View;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOError;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipInputStream;\n\npublic class MainActivity extends Activity\n{\n  private static final int MANAGE_EXTERNAL_STORAGE_INTENT_CODE = 100;\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState)\n  {\n    super.onCreate(savedInstanceState);\n\n    // Ensure all necessary permissions are present\n    if(Build.VERSION.SDK_INT >= 30 /* Android 11.0 */)\n    {\n      if(!Environment.isExternalStorageManager())\n      {\n        try\n        {\n          Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.parse(\"package:\" + getPackageName()));\n          startActivityForResult(intent, MANAGE_EXTERNAL_STORAGE_INTENT_CODE);\n        }\n        catch (Exception e)\n        {\n          Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);\n          startActivityForResult(intent, MANAGE_EXTERNAL_STORAGE_INTENT_CODE);\n        }\n        return;\n      }\n    }\n    else\n\n    if(Build.VERSION.SDK_INT >= 23 /* Android 6.0 */)\n    {\n      if(checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)\n       != PackageManager.PERMISSION_GRANTED)\n      {\n        requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1000);\n        return;\n      }\n    }\n\n    launchGame();\n  }\n\n  @Override\n  public void onActivityResult(int requestCode, int resultCode, Intent data) {\n    super.onActivityResult(requestCode, resultCode, data);\n    if(Build.VERSION.SDK_INT >= 30 /* Android 11.0 */)\n    {\n      if(requestCode == MANAGE_EXTERNAL_STORAGE_INTENT_CODE)\n      {\n        if(Environment.isExternalStorageManager())\n          launchGame();\n      }\n    }\n  }\n\n  private void launchGame()\n  {\n    // unzip assets\n    File targetPath = GameActivity.getAssetPath(this);\n    InputStream assetStream = getResources().openRawResource(R.raw.assets);\n    ZipInputStream assetZip = new ZipInputStream(assetStream);\n    byte[] buffer = new byte[8192];\n    FileOutputStream output = null;\n\n    try\n    {\n      ZipEntry entry;\n      while((entry = assetZip.getNextEntry()) != null)\n      {\n        File target = new File(targetPath, entry.getName());\n\n        if(entry.isDirectory())\n        {\n          if(target.exists())\n            continue;\n\n          target.mkdirs();\n        }\n        else\n        {\n          if(target.exists())\n          {\n            if(target.lastModified() >= entry.getTime())\n              continue;\n          }\n\n          int read;\n          output = new FileOutputStream(target);\n\n          while((read = assetZip.read(buffer, 0, buffer.length)) >= 0)\n          {\n            output.write(buffer, 0, read);\n          }\n\n          output.close();\n          output = null;\n\n          target.setLastModified(entry.getTime());\n        }\n\n        assetZip.closeEntry();\n      }\n    }\n    catch(IOException e)\n    {\n      throw new RuntimeException(e);\n    }\n    finally\n    {\n      if(output != null)\n      {\n        try\n        {\n          output.close();\n        }\n        catch(IOException e)\n        {\n          // pass\n        }\n      }\n\n      try\n      {\n        assetZip.close();\n      }\n      catch(IOException e)\n      {\n        // pass\n      }\n\n      try\n      {\n        assetStream.close();\n      }\n      catch(IOException e)\n      {\n        // pass\n      }\n    }\n\n    // launch game\n    startActivity(new Intent(getApplicationContext(), GameActivity.class));\n  }\n\n  @Override\n  public void onRequestPermissionsResult(int requestCode, String[] permissions,\n   int[] grantResults)\n  {\n    super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n    if(grantResults.length > 0 &&\n     grantResults[0] == PackageManager.PERMISSION_GRANTED)\n    {\n      launchGame();\n    }\n  }\n}\n"
  },
  {
    "path": "arch/android/project/app/src/main/java/org/libsdl/app/HIDDevice.java",
    "content": "package org.libsdl.app;\n\nimport android.hardware.usb.UsbDevice;\n\ninterface HIDDevice\n{\n    public int getId();\n    public int getVendorId();\n    public int getProductId();\n    public String getSerialNumber();\n    public int getVersion();\n    public String getManufacturerName();\n    public String getProductName();\n    public UsbDevice getDevice();\n    public boolean open();\n    public int sendFeatureReport(byte[] report);\n    public int sendOutputReport(byte[] report);\n    public boolean getFeatureReport(byte[] report);\n    public void setFrozen(boolean frozen);\n    public void close();\n    public void shutdown();\n}\n"
  },
  {
    "path": "arch/android/project/app/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java",
    "content": "package org.libsdl.app;\n\nimport android.content.Context;\nimport android.bluetooth.BluetoothDevice;\nimport android.bluetooth.BluetoothGatt;\nimport android.bluetooth.BluetoothGattCallback;\nimport android.bluetooth.BluetoothGattCharacteristic;\nimport android.bluetooth.BluetoothGattDescriptor;\nimport android.bluetooth.BluetoothManager;\nimport android.bluetooth.BluetoothProfile;\nimport android.bluetooth.BluetoothGattService;\nimport android.hardware.usb.UsbDevice;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.Log;\nimport android.os.*;\n\n//import com.android.internal.util.HexDump;\n\nimport java.lang.Runnable;\nimport java.util.Arrays;\nimport java.util.LinkedList;\nimport java.util.UUID;\n\nclass HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {\n\n    private static final String TAG = \"hidapi\";\n    private HIDDeviceManager mManager;\n    private BluetoothDevice mDevice;\n    private int mDeviceId;\n    private BluetoothGatt mGatt;\n    private boolean mIsRegistered = false;\n    private boolean mIsConnected = false;\n    private boolean mIsChromebook = false;\n    private boolean mIsReconnecting = false;\n    private boolean mFrozen = false;\n    private LinkedList<GattOperation> mOperations;\n    GattOperation mCurrentOperation = null;\n    private Handler mHandler;\n\n    private static final int TRANSPORT_AUTO = 0;\n    private static final int TRANSPORT_BREDR = 1;\n    private static final int TRANSPORT_LE = 2;\n\n    private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;\n\n    static public final UUID steamControllerService = UUID.fromString(\"100F6C32-1735-4313-B402-38567131E5F3\");\n    static public final UUID inputCharacteristic = UUID.fromString(\"100F6C33-1735-4313-B402-38567131E5F3\");\n    static public final UUID reportCharacteristic = UUID.fromString(\"100F6C34-1735-4313-B402-38567131E5F3\");\n    static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };\n\n    static class GattOperation {\n        private enum Operation {\n            CHR_READ,\n            CHR_WRITE,\n            ENABLE_NOTIFICATION\n        }\n\n        Operation mOp;\n        UUID mUuid;\n        byte[] mValue;\n        BluetoothGatt mGatt;\n        boolean mResult = true;\n\n        private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {\n            mGatt = gatt;\n            mOp = operation;\n            mUuid = uuid;\n        }\n\n        private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {\n            mGatt = gatt;\n            mOp = operation;\n            mUuid = uuid;\n            mValue = value;\n        }\n\n        public void run() {\n            // This is executed in main thread\n            BluetoothGattCharacteristic chr;\n\n            switch (mOp) {\n                case CHR_READ:\n                    chr = getCharacteristic(mUuid);\n                    //Log.v(TAG, \"Reading characteristic \" + chr.getUuid());\n                    if (!mGatt.readCharacteristic(chr)) {\n                        Log.e(TAG, \"Unable to read characteristic \" + mUuid.toString());\n                        mResult = false;\n                        break;\n                    }\n                    mResult = true;\n                    break;\n                case CHR_WRITE:\n                    chr = getCharacteristic(mUuid);\n                    //Log.v(TAG, \"Writing characteristic \" + chr.getUuid() + \" value=\" + HexDump.toHexString(value));\n                    chr.setValue(mValue);\n                    if (!mGatt.writeCharacteristic(chr)) {\n                        Log.e(TAG, \"Unable to write characteristic \" + mUuid.toString());\n                        mResult = false;\n                        break;\n                    }\n                    mResult = true;\n                    break;\n                case ENABLE_NOTIFICATION:\n                    chr = getCharacteristic(mUuid);\n                    //Log.v(TAG, \"Writing descriptor of \" + chr.getUuid());\n                    if (chr != null) {\n                        BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString(\"00002902-0000-1000-8000-00805f9b34fb\"));\n                        if (cccd != null) {\n                            int properties = chr.getProperties();\n                            byte[] value;\n                            if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {\n                                value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;\n                            } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {\n                                value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;\n                            } else {\n                                Log.e(TAG, \"Unable to start notifications on input characteristic\");\n                                mResult = false;\n                                return;\n                            }\n\n                            mGatt.setCharacteristicNotification(chr, true);\n                            cccd.setValue(value);\n                            if (!mGatt.writeDescriptor(cccd)) {\n                                Log.e(TAG, \"Unable to write descriptor \" + mUuid.toString());\n                                mResult = false;\n                                return;\n                            }\n                            mResult = true;\n                        }\n                    }\n            }\n        }\n\n        public boolean finish() {\n            return mResult;\n        }\n\n        private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {\n            BluetoothGattService valveService = mGatt.getService(steamControllerService);\n            if (valveService == null)\n                return null;\n            return valveService.getCharacteristic(uuid);\n        }\n\n        static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {\n            return new GattOperation(gatt, Operation.CHR_READ, uuid);\n        }\n\n        static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {\n            return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);\n        }\n\n        static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {\n            return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);\n        }\n    }\n\n    public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {\n        mManager = manager;\n        mDevice = device;\n        mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());\n        mIsRegistered = false;\n        mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature(\"org.chromium.arc.device_management\");\n        mOperations = new LinkedList<GattOperation>();\n        mHandler = new Handler(Looper.getMainLooper());\n\n        mGatt = connectGatt();\n        // final HIDDeviceBLESteamController finalThis = this;\n        // mHandler.postDelayed(new Runnable() {\n        //     @Override\n        //     public void run() {\n        //         finalThis.checkConnectionForChromebookIssue();\n        //     }\n        // }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);\n    }\n\n    public String getIdentifier() {\n        return String.format(\"SteamController.%s\", mDevice.getAddress());\n    }\n\n    public BluetoothGatt getGatt() {\n        return mGatt;\n    }\n\n    // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead\n    // of TRANSPORT_LE.  Let's force ourselves to connect low energy.\n    private BluetoothGatt connectGatt(boolean managed) {\n        if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {\n            try {\n                return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);\n            } catch (Exception e) {\n                return mDevice.connectGatt(mManager.getContext(), managed, this);\n            }\n        } else {\n            return mDevice.connectGatt(mManager.getContext(), managed, this);\n        }\n    }\n\n    private BluetoothGatt connectGatt() {\n        return connectGatt(false);\n    }\n\n    protected int getConnectionState() {\n\n        Context context = mManager.getContext();\n        if (context == null) {\n            // We are lacking any context to get our Bluetooth information.  We'll just assume disconnected.\n            return BluetoothProfile.STATE_DISCONNECTED;\n        }\n\n        BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);\n        if (btManager == null) {\n            // This device doesn't support Bluetooth.  We should never be here, because how did\n            // we instantiate a device to start with?\n            return BluetoothProfile.STATE_DISCONNECTED;\n        }\n\n        return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);\n    }\n\n    public void reconnect() {\n\n        if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {\n            mGatt.disconnect();\n            mGatt = connectGatt();\n        }\n\n    }\n\n    protected void checkConnectionForChromebookIssue() {\n        if (!mIsChromebook) {\n            // We only do this on Chromebooks, because otherwise it's really annoying to just attempt\n            // over and over.\n            return;\n        }\n\n        int connectionState = getConnectionState();\n\n        switch (connectionState) {\n            case BluetoothProfile.STATE_CONNECTED:\n                if (!mIsConnected) {\n                    // We are in the Bad Chromebook Place.  We can force a disconnect\n                    // to try to recover.\n                    Log.v(TAG, \"Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback.  Forcing a reconnect.\");\n                    mIsReconnecting = true;\n                    mGatt.disconnect();\n                    mGatt = connectGatt(false);\n                    break;\n                }\n                else if (!isRegistered()) {\n                    if (mGatt.getServices().size() > 0) {\n                        Log.v(TAG, \"Chromebook: We are connected to a controller, but never got our registration.  Trying to recover.\");\n                        probeService(this);\n                    }\n                    else {\n                        Log.v(TAG, \"Chromebook: We are connected to a controller, but never discovered services.  Trying to recover.\");\n                        mIsReconnecting = true;\n                        mGatt.disconnect();\n                        mGatt = connectGatt(false);\n                        break;\n                    }\n                }\n                else {\n                    Log.v(TAG, \"Chromebook: We are connected, and registered.  Everything's good!\");\n                    return;\n                }\n                break;\n\n            case BluetoothProfile.STATE_DISCONNECTED:\n                Log.v(TAG, \"Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us.  Attempting a disconnect/reconnect, but we may not be able to recover.\");\n\n                mIsReconnecting = true;\n                mGatt.disconnect();\n                mGatt = connectGatt(false);\n                break;\n\n            case BluetoothProfile.STATE_CONNECTING:\n                Log.v(TAG, \"Chromebook: We're still trying to connect.  Waiting a bit longer.\");\n                break;\n        }\n\n        final HIDDeviceBLESteamController finalThis = this;\n        mHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                finalThis.checkConnectionForChromebookIssue();\n            }\n        }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);\n    }\n\n    private boolean isRegistered() {\n        return mIsRegistered;\n    }\n\n    private void setRegistered() {\n        mIsRegistered = true;\n    }\n\n    private boolean probeService(HIDDeviceBLESteamController controller) {\n\n        if (isRegistered()) {\n            return true;\n        }\n\n        if (!mIsConnected) {\n            return false;\n        }\n\n        Log.v(TAG, \"probeService controller=\" + controller);\n\n        for (BluetoothGattService service : mGatt.getServices()) {\n            if (service.getUuid().equals(steamControllerService)) {\n                Log.v(TAG, \"Found Valve steam controller service \" + service.getUuid());\n\n                for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {\n                    if (chr.getUuid().equals(inputCharacteristic)) {\n                        Log.v(TAG, \"Found input characteristic\");\n                        // Start notifications\n                        BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString(\"00002902-0000-1000-8000-00805f9b34fb\"));\n                        if (cccd != null) {\n                            enableNotification(chr.getUuid());\n                        }\n                    }\n                }\n                return true;\n            }\n        }\n\n        if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {\n            Log.e(TAG, \"Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.\");\n            mIsConnected = false;\n            mIsReconnecting = true;\n            mGatt.disconnect();\n            mGatt = connectGatt(false);\n        }\n\n        return false;\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    private void finishCurrentGattOperation() {\n        GattOperation op = null;\n        synchronized (mOperations) {\n            if (mCurrentOperation != null) {\n                op = mCurrentOperation;\n                mCurrentOperation = null;\n            }\n        }\n        if (op != null) {\n            boolean result = op.finish(); // TODO: Maybe in main thread as well?\n\n            // Our operation failed, let's add it back to the beginning of our queue.\n            if (!result) {\n                mOperations.addFirst(op);\n            }\n        }\n        executeNextGattOperation();\n    }\n\n    private void executeNextGattOperation() {\n        synchronized (mOperations) {\n            if (mCurrentOperation != null)\n                return;\n\n            if (mOperations.isEmpty())\n                return;\n\n            mCurrentOperation = mOperations.removeFirst();\n        }\n\n        // Run in main thread\n        mHandler.post(new Runnable() {\n            @Override\n            public void run() {\n                synchronized (mOperations) {\n                    if (mCurrentOperation == null) {\n                        Log.e(TAG, \"Current operation null in executor?\");\n                        return;\n                    }\n\n                    mCurrentOperation.run();\n                    // now wait for the GATT callback and when it comes, finish this operation\n                }\n            }\n        });\n    }\n\n    private void queueGattOperation(GattOperation op) {\n        synchronized (mOperations) {\n            mOperations.add(op);\n        }\n        executeNextGattOperation();\n    }\n\n    private void enableNotification(UUID chrUuid) {\n        GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);\n        queueGattOperation(op);\n    }\n\n    public void writeCharacteristic(UUID uuid, byte[] value) {\n        GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);\n        queueGattOperation(op);\n    }\n\n    public void readCharacteristic(UUID uuid) {\n        GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);\n        queueGattOperation(op);\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////  BluetoothGattCallback overridden methods\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {\n        //Log.v(TAG, \"onConnectionStateChange status=\" + status + \" newState=\" + newState);\n        mIsReconnecting = false;\n        if (newState == 2) {\n            mIsConnected = true;\n            // Run directly, without GattOperation\n            if (!isRegistered()) {\n                mHandler.post(new Runnable() {\n                    @Override\n                    public void run() {\n                        mGatt.discoverServices();\n                    }\n                });\n            }\n        }\n        else if (newState == 0) {\n            mIsConnected = false;\n        }\n\n        // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.\n    }\n\n    public void onServicesDiscovered(BluetoothGatt gatt, int status) {\n        //Log.v(TAG, \"onServicesDiscovered status=\" + status);\n        if (status == 0) {\n            if (gatt.getServices().size() == 0) {\n                Log.v(TAG, \"onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.\");\n                mIsReconnecting = true;\n                mIsConnected = false;\n                gatt.disconnect();\n                mGatt = connectGatt(false);\n            }\n            else {\n                probeService(this);\n            }\n        }\n    }\n\n    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {\n        //Log.v(TAG, \"onCharacteristicRead status=\" + status + \" uuid=\" + characteristic.getUuid());\n\n        if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {\n            mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());\n        }\n\n        finishCurrentGattOperation();\n    }\n\n    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {\n        //Log.v(TAG, \"onCharacteristicWrite status=\" + status + \" uuid=\" + characteristic.getUuid());\n\n        if (characteristic.getUuid().equals(reportCharacteristic)) {\n            // Only register controller with the native side once it has been fully configured\n            if (!isRegistered()) {\n                Log.v(TAG, \"Registering Steam Controller with ID: \" + getId());\n                mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0);\n                setRegistered();\n            }\n        }\n\n        finishCurrentGattOperation();\n    }\n\n    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {\n    // Enable this for verbose logging of controller input reports\n        //Log.v(TAG, \"onCharacteristicChanged uuid=\" + characteristic.getUuid() + \" data=\" + HexDump.dumpHexString(characteristic.getValue()));\n\n        if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {\n            mManager.HIDDeviceInputReport(getId(), characteristic.getValue());\n        }\n    }\n\n    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {\n        //Log.v(TAG, \"onDescriptorRead status=\" + status);\n    }\n\n    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {\n        BluetoothGattCharacteristic chr = descriptor.getCharacteristic();\n        //Log.v(TAG, \"onDescriptorWrite status=\" + status + \" uuid=\" + chr.getUuid() + \" descriptor=\" + descriptor.getUuid());\n\n        if (chr.getUuid().equals(inputCharacteristic)) {\n            boolean hasWrittenInputDescriptor = true;\n            BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);\n            if (reportChr != null) {\n                Log.v(TAG, \"Writing report characteristic to enter valve mode\");\n                reportChr.setValue(enterValveMode);\n                gatt.writeCharacteristic(reportChr);\n            }\n        }\n\n        finishCurrentGattOperation();\n    }\n\n    public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {\n        //Log.v(TAG, \"onReliableWriteCompleted status=\" + status);\n    }\n\n    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {\n        //Log.v(TAG, \"onReadRemoteRssi status=\" + status);\n    }\n\n    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {\n        //Log.v(TAG, \"onMtuChanged status=\" + status);\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////// Public API\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    @Override\n    public int getId() {\n        return mDeviceId;\n    }\n\n    @Override\n    public int getVendorId() {\n        // Valve Corporation\n        final int VALVE_USB_VID = 0x28DE;\n        return VALVE_USB_VID;\n    }\n\n    @Override\n    public int getProductId() {\n        // We don't have an easy way to query from the Bluetooth device, but we know what it is\n        final int D0G_BLE2_PID = 0x1106;\n        return D0G_BLE2_PID;\n    }\n\n    @Override\n    public String getSerialNumber() {\n        // This will be read later via feature report by Steam\n        return \"12345\";\n    }\n\n    @Override\n    public int getVersion() {\n        return 0;\n    }\n\n    @Override\n    public String getManufacturerName() {\n        return \"Valve Corporation\";\n    }\n\n    @Override\n    public String getProductName() {\n        return \"Steam Controller\";\n    }\n\n    @Override\n    public UsbDevice getDevice() {\n        return null;\n    }\n\n    @Override\n    public boolean open() {\n        return true;\n    }\n\n    @Override\n    public int sendFeatureReport(byte[] report) {\n        if (!isRegistered()) {\n            Log.e(TAG, \"Attempted sendFeatureReport before Steam Controller is registered!\");\n            if (mIsConnected) {\n                probeService(this);\n            }\n            return -1;\n        }\n\n        // We need to skip the first byte, as that doesn't go over the air\n        byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);\n        //Log.v(TAG, \"sendFeatureReport \" + HexDump.dumpHexString(actual_report));\n        writeCharacteristic(reportCharacteristic, actual_report);\n        return report.length;\n    }\n\n    @Override\n    public int sendOutputReport(byte[] report) {\n        if (!isRegistered()) {\n            Log.e(TAG, \"Attempted sendOutputReport before Steam Controller is registered!\");\n            if (mIsConnected) {\n                probeService(this);\n            }\n            return -1;\n        }\n\n        //Log.v(TAG, \"sendFeatureReport \" + HexDump.dumpHexString(report));\n        writeCharacteristic(reportCharacteristic, report);\n        return report.length;\n    }\n\n    @Override\n    public boolean getFeatureReport(byte[] report) {\n        if (!isRegistered()) {\n            Log.e(TAG, \"Attempted getFeatureReport before Steam Controller is registered!\");\n            if (mIsConnected) {\n                probeService(this);\n            }\n            return false;\n        }\n\n        //Log.v(TAG, \"getFeatureReport\");\n        readCharacteristic(reportCharacteristic);\n        return true;\n    }\n\n    @Override\n    public void close() {\n    }\n\n    @Override\n    public void setFrozen(boolean frozen) {\n        mFrozen = frozen;\n    }\n\n    @Override\n    public void shutdown() {\n        close();\n\n        BluetoothGatt g = mGatt;\n        if (g != null) {\n            g.disconnect();\n            g.close();\n            mGatt = null;\n        }\n        mManager = null;\n        mIsRegistered = false;\n        mIsConnected = false;\n        mOperations.clear();\n    }\n\n}\n\n"
  },
  {
    "path": "arch/android/project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java",
    "content": "package org.libsdl.app;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.PendingIntent;\nimport android.bluetooth.BluetoothAdapter;\nimport android.bluetooth.BluetoothDevice;\nimport android.bluetooth.BluetoothManager;\nimport android.bluetooth.BluetoothProfile;\nimport android.os.Build;\nimport android.util.Log;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.SharedPreferences;\nimport android.content.pm.PackageManager;\nimport android.hardware.usb.*;\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class HIDDeviceManager {\n    private static final String TAG = \"hidapi\";\n    private static final String ACTION_USB_PERMISSION = \"org.libsdl.app.USB_PERMISSION\";\n\n    private static HIDDeviceManager sManager;\n    private static int sManagerRefCount = 0;\n\n    public static HIDDeviceManager acquire(Context context) {\n        if (sManagerRefCount == 0) {\n            sManager = new HIDDeviceManager(context);\n        }\n        ++sManagerRefCount;\n        return sManager;\n    }\n\n    public static void release(HIDDeviceManager manager) {\n        if (manager == sManager) {\n            --sManagerRefCount;\n            if (sManagerRefCount == 0) {\n                sManager.close();\n                sManager = null;\n            }\n        }\n    }\n\n    private Context mContext;\n    private HashMap<Integer, HIDDevice> mDevicesById = new HashMap<Integer, HIDDevice>();\n    private HashMap<BluetoothDevice, HIDDeviceBLESteamController> mBluetoothDevices = new HashMap<BluetoothDevice, HIDDeviceBLESteamController>();\n    private int mNextDeviceId = 0;\n    private SharedPreferences mSharedPreferences = null;\n    private boolean mIsChromebook = false;\n    private UsbManager mUsbManager;\n    private Handler mHandler;\n    private BluetoothManager mBluetoothManager;\n    private List<BluetoothDevice> mLastBluetoothDevices;\n\n    private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            String action = intent.getAction();\n            if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {\n                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);\n                handleUsbDeviceAttached(usbDevice);\n            } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {\n                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);\n                handleUsbDeviceDetached(usbDevice);\n            } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {\n                UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);\n                handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));\n            }\n        }\n    };\n\n    private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            String action = intent.getAction();\n            // Bluetooth device was connected. If it was a Steam Controller, handle it\n            if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {\n                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);\n                Log.d(TAG, \"Bluetooth device connected: \" + device);\n\n                if (isSteamController(device)) {\n                    connectBluetoothDevice(device);\n                }\n            }\n\n            // Bluetooth device was disconnected, remove from controller manager (if any)\n            if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {\n                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);\n                Log.d(TAG, \"Bluetooth device disconnected: \" + device);\n\n                disconnectBluetoothDevice(device);\n            }\n        }\n    };\n\n    private HIDDeviceManager(final Context context) {\n        mContext = context;\n\n        HIDDeviceRegisterCallback();\n\n        mSharedPreferences = mContext.getSharedPreferences(\"hidapi\", Context.MODE_PRIVATE);\n        mIsChromebook = mContext.getPackageManager().hasSystemFeature(\"org.chromium.arc.device_management\");\n\n//        if (shouldClear) {\n//            SharedPreferences.Editor spedit = mSharedPreferences.edit();\n//            spedit.clear();\n//            spedit.commit();\n//        }\n//        else\n        {\n            mNextDeviceId = mSharedPreferences.getInt(\"next_device_id\", 0);\n        }\n    }\n\n    public Context getContext() {\n        return mContext;\n    }\n\n    public int getDeviceIDForIdentifier(String identifier) {\n        SharedPreferences.Editor spedit = mSharedPreferences.edit();\n\n        int result = mSharedPreferences.getInt(identifier, 0);\n        if (result == 0) {\n            result = mNextDeviceId++;\n            spedit.putInt(\"next_device_id\", mNextDeviceId);\n        }\n\n        spedit.putInt(identifier, result);\n        spedit.commit();\n        return result;\n    }\n\n    private void initializeUSB() {\n        mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);\n        if (mUsbManager == null) {\n            return;\n        }\n\n        /*\n        // Logging\n        for (UsbDevice device : mUsbManager.getDeviceList().values()) {\n            Log.i(TAG,\"Path: \" + device.getDeviceName());\n            Log.i(TAG,\"Manufacturer: \" + device.getManufacturerName());\n            Log.i(TAG,\"Product: \" + device.getProductName());\n            Log.i(TAG,\"ID: \" + device.getDeviceId());\n            Log.i(TAG,\"Class: \" + device.getDeviceClass());\n            Log.i(TAG,\"Protocol: \" + device.getDeviceProtocol());\n            Log.i(TAG,\"Vendor ID \" + device.getVendorId());\n            Log.i(TAG,\"Product ID: \" + device.getProductId());\n            Log.i(TAG,\"Interface count: \" + device.getInterfaceCount());\n            Log.i(TAG,\"---------------------------------------\");\n\n            // Get interface details\n            for (int index = 0; index < device.getInterfaceCount(); index++) {\n                UsbInterface mUsbInterface = device.getInterface(index);\n                Log.i(TAG,\"  *****     *****\");\n                Log.i(TAG,\"  Interface index: \" + index);\n                Log.i(TAG,\"  Interface ID: \" + mUsbInterface.getId());\n                Log.i(TAG,\"  Interface class: \" + mUsbInterface.getInterfaceClass());\n                Log.i(TAG,\"  Interface subclass: \" + mUsbInterface.getInterfaceSubclass());\n                Log.i(TAG,\"  Interface protocol: \" + mUsbInterface.getInterfaceProtocol());\n                Log.i(TAG,\"  Endpoint count: \" + mUsbInterface.getEndpointCount());\n\n                // Get endpoint details\n                for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)\n                {\n                    UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);\n                    Log.i(TAG,\"    ++++   ++++   ++++\");\n                    Log.i(TAG,\"    Endpoint index: \" + epi);\n                    Log.i(TAG,\"    Attributes: \" + mEndpoint.getAttributes());\n                    Log.i(TAG,\"    Direction: \" + mEndpoint.getDirection());\n                    Log.i(TAG,\"    Number: \" + mEndpoint.getEndpointNumber());\n                    Log.i(TAG,\"    Interval: \" + mEndpoint.getInterval());\n                    Log.i(TAG,\"    Packet size: \" + mEndpoint.getMaxPacketSize());\n                    Log.i(TAG,\"    Type: \" + mEndpoint.getType());\n                }\n            }\n        }\n        Log.i(TAG,\" No more devices connected.\");\n        */\n\n        // Register for USB broadcasts and permission completions\n        IntentFilter filter = new IntentFilter();\n        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);\n        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);\n        filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);\n        mContext.registerReceiver(mUsbBroadcast, filter);\n\n        for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {\n            handleUsbDeviceAttached(usbDevice);\n        }\n    }\n\n    UsbManager getUSBManager() {\n        return mUsbManager;\n    }\n\n    private void shutdownUSB() {\n        try {\n            mContext.unregisterReceiver(mUsbBroadcast);\n        } catch (Exception e) {\n            // We may not have registered, that's okay\n        }\n    }\n\n    private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) {\n        if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {\n            return true;\n        }\n        if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {\n            return true;\n        }\n        return false;\n    }\n\n    private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {\n        final int XB360_IFACE_SUBCLASS = 93;\n        final int XB360_IFACE_PROTOCOL = 1; // Wired\n        final int XB360W_IFACE_PROTOCOL = 129; // Wireless\n        final int[] SUPPORTED_VENDORS = {\n            0x0079, // GPD Win 2\n            0x044f, // Thrustmaster\n            0x045e, // Microsoft\n            0x046d, // Logitech\n            0x056e, // Elecom\n            0x06a3, // Saitek\n            0x0738, // Mad Catz\n            0x07ff, // Mad Catz\n            0x0e6f, // PDP\n            0x0f0d, // Hori\n            0x1038, // SteelSeries\n            0x11c9, // Nacon\n            0x12ab, // Unknown\n            0x1430, // RedOctane\n            0x146b, // BigBen\n            0x1532, // Razer Sabertooth\n            0x15e4, // Numark\n            0x162e, // Joytech\n            0x1689, // Razer Onza\n            0x1949, // Lab126, Inc.\n            0x1bad, // Harmonix\n            0x20d6, // PowerA\n            0x24c6, // PowerA\n            0x2c22, // Qanba\n            0x2dc8, // 8BitDo\n            0x9886, // ASTRO Gaming\n        };\n\n        if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&\n            usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&\n            (usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL ||\n             usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) {\n            int vendor_id = usbDevice.getVendorId();\n            for (int supportedVid : SUPPORTED_VENDORS) {\n                if (vendor_id == supportedVid) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {\n        final int XB1_IFACE_SUBCLASS = 71;\n        final int XB1_IFACE_PROTOCOL = 208;\n        final int[] SUPPORTED_VENDORS = {\n            0x03f0, // HP\n            0x044f, // Thrustmaster\n            0x045e, // Microsoft\n            0x0738, // Mad Catz\n            0x0b05, // ASUS\n            0x0e6f, // PDP\n            0x0f0d, // Hori\n            0x10f5, // Turtle Beach\n            0x1532, // Razer Wildcat\n            0x20d6, // PowerA\n            0x24c6, // PowerA\n            0x2dc8, // 8BitDo\n            0x2e24, // Hyperkin\n            0x3537, // Gamesir\n        };\n\n        if (usbInterface.getId() == 0 &&\n            usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&\n            usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&\n            usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {\n            int vendor_id = usbDevice.getVendorId();\n            for (int supportedVid : SUPPORTED_VENDORS) {\n                if (vendor_id == supportedVid) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private void handleUsbDeviceAttached(UsbDevice usbDevice) {\n        connectHIDDeviceUSB(usbDevice);\n    }\n\n    private void handleUsbDeviceDetached(UsbDevice usbDevice) {\n        List<Integer> devices = new ArrayList<Integer>();\n        for (HIDDevice device : mDevicesById.values()) {\n            if (usbDevice.equals(device.getDevice())) {\n                devices.add(device.getId());\n            }\n        }\n        for (int id : devices) {\n            HIDDevice device = mDevicesById.get(id);\n            mDevicesById.remove(id);\n            device.shutdown();\n            HIDDeviceDisconnected(id);\n        }\n    }\n\n    private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {\n        for (HIDDevice device : mDevicesById.values()) {\n            if (usbDevice.equals(device.getDevice())) {\n                boolean opened = false;\n                if (permission_granted) {\n                    opened = device.open();\n                }\n                HIDDeviceOpenResult(device.getId(), opened);\n            }\n        }\n    }\n\n    private void connectHIDDeviceUSB(UsbDevice usbDevice) {\n        synchronized (this) {\n            int interface_mask = 0;\n            for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) {\n                UsbInterface usbInterface = usbDevice.getInterface(interface_index);\n                if (isHIDDeviceInterface(usbDevice, usbInterface)) {\n                    // Check to see if we've already added this interface\n                    // This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive\n                    int interface_id = usbInterface.getId();\n                    if ((interface_mask & (1 << interface_id)) != 0) {\n                        continue;\n                    }\n                    interface_mask |= (1 << interface_id);\n\n                    HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index);\n                    int id = device.getId();\n                    mDevicesById.put(id, device);\n                    HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol());\n                }\n            }\n        }\n    }\n\n    private void initializeBluetooth() {\n        Log.d(TAG, \"Initializing Bluetooth\");\n\n        if (Build.VERSION.SDK_INT >= 31 /* Android 12  */ &&\n            mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {\n            Log.d(TAG, \"Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT\");\n            return;\n        }\n\n        if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&\n            mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {\n            Log.d(TAG, \"Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH\");\n            return;\n        }\n\n        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {\n            Log.d(TAG, \"Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE\");\n            return;\n        }\n\n        // Find bonded bluetooth controllers and create SteamControllers for them\n        mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);\n        if (mBluetoothManager == null) {\n            // This device doesn't support Bluetooth.\n            return;\n        }\n\n        BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();\n        if (btAdapter == null) {\n            // This device has Bluetooth support in the codebase, but has no available adapters.\n            return;\n        }\n\n        // Get our bonded devices.\n        for (BluetoothDevice device : btAdapter.getBondedDevices()) {\n\n            Log.d(TAG, \"Bluetooth device available: \" + device);\n            if (isSteamController(device)) {\n                connectBluetoothDevice(device);\n            }\n\n        }\n\n        // NOTE: These don't work on Chromebooks, to my undying dismay.\n        IntentFilter filter = new IntentFilter();\n        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);\n        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);\n        mContext.registerReceiver(mBluetoothBroadcast, filter);\n\n        if (mIsChromebook) {\n            mHandler = new Handler(Looper.getMainLooper());\n            mLastBluetoothDevices = new ArrayList<BluetoothDevice>();\n\n            // final HIDDeviceManager finalThis = this;\n            // mHandler.postDelayed(new Runnable() {\n            //     @Override\n            //     public void run() {\n            //         finalThis.chromebookConnectionHandler();\n            //     }\n            // }, 5000);\n        }\n    }\n\n    private void shutdownBluetooth() {\n        try {\n            mContext.unregisterReceiver(mBluetoothBroadcast);\n        } catch (Exception e) {\n            // We may not have registered, that's okay\n        }\n    }\n\n    // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.\n    // This function provides a sort of dummy version of that, watching for changes in the\n    // connected devices and attempting to add controllers as things change.\n    public void chromebookConnectionHandler() {\n        if (!mIsChromebook) {\n            return;\n        }\n\n        ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();\n        ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();\n\n        List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);\n\n        for (BluetoothDevice bluetoothDevice : currentConnected) {\n            if (!mLastBluetoothDevices.contains(bluetoothDevice)) {\n                connected.add(bluetoothDevice);\n            }\n        }\n        for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {\n            if (!currentConnected.contains(bluetoothDevice)) {\n                disconnected.add(bluetoothDevice);\n            }\n        }\n\n        mLastBluetoothDevices = currentConnected;\n\n        for (BluetoothDevice bluetoothDevice : disconnected) {\n            disconnectBluetoothDevice(bluetoothDevice);\n        }\n        for (BluetoothDevice bluetoothDevice : connected) {\n            connectBluetoothDevice(bluetoothDevice);\n        }\n\n        final HIDDeviceManager finalThis = this;\n        mHandler.postDelayed(new Runnable() {\n            @Override\n            public void run() {\n                finalThis.chromebookConnectionHandler();\n            }\n        }, 10000);\n    }\n\n    public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {\n        Log.v(TAG, \"connectBluetoothDevice device=\" + bluetoothDevice);\n        synchronized (this) {\n            if (mBluetoothDevices.containsKey(bluetoothDevice)) {\n                Log.v(TAG, \"Steam controller with address \" + bluetoothDevice + \" already exists, attempting reconnect\");\n\n                HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);\n                device.reconnect();\n\n                return false;\n            }\n            HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);\n            int id = device.getId();\n            mBluetoothDevices.put(bluetoothDevice, device);\n            mDevicesById.put(id, device);\n\n            // The Steam Controller will mark itself connected once initialization is complete\n        }\n        return true;\n    }\n\n    public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {\n        synchronized (this) {\n            HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);\n            if (device == null)\n                return;\n\n            int id = device.getId();\n            mBluetoothDevices.remove(bluetoothDevice);\n            mDevicesById.remove(id);\n            device.shutdown();\n            HIDDeviceDisconnected(id);\n        }\n    }\n\n    public boolean isSteamController(BluetoothDevice bluetoothDevice) {\n        // Sanity check.  If you pass in a null device, by definition it is never a Steam Controller.\n        if (bluetoothDevice == null) {\n            return false;\n        }\n\n        // If the device has no local name, we really don't want to try an equality check against it.\n        if (bluetoothDevice.getName() == null) {\n            return false;\n        }\n\n        return bluetoothDevice.getName().equals(\"SteamController\") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);\n    }\n\n    private void close() {\n        shutdownUSB();\n        shutdownBluetooth();\n        synchronized (this) {\n            for (HIDDevice device : mDevicesById.values()) {\n                device.shutdown();\n            }\n            mDevicesById.clear();\n            mBluetoothDevices.clear();\n            HIDDeviceReleaseCallback();\n        }\n    }\n\n    public void setFrozen(boolean frozen) {\n        synchronized (this) {\n            for (HIDDevice device : mDevicesById.values()) {\n                device.setFrozen(frozen);\n            }\n        }\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    private HIDDevice getDevice(int id) {\n        synchronized (this) {\n            HIDDevice result = mDevicesById.get(id);\n            if (result == null) {\n                Log.v(TAG, \"No device for id: \" + id);\n                Log.v(TAG, \"Available devices: \" + mDevicesById.keySet());\n            }\n            return result;\n        }\n    }\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    ////////// JNI interface functions\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    public boolean initialize(boolean usb, boolean bluetooth) {\n        Log.v(TAG, \"initialize(\" + usb + \", \" + bluetooth + \")\");\n\n        if (usb) {\n            initializeUSB();\n        }\n        if (bluetooth) {\n            initializeBluetooth();\n        }\n        return true;\n    }\n\n    public boolean openDevice(int deviceID) {\n        Log.v(TAG, \"openDevice deviceID=\" + deviceID);\n        HIDDevice device = getDevice(deviceID);\n        if (device == null) {\n            HIDDeviceDisconnected(deviceID);\n            return false;\n        }\n\n        // Look to see if this is a USB device and we have permission to access it\n        UsbDevice usbDevice = device.getDevice();\n        if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) {\n            HIDDeviceOpenPending(deviceID);\n            try {\n                final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31\n                int flags;\n                if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {\n                    flags = FLAG_MUTABLE;\n                } else {\n                    flags = 0;\n                }\n                if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {\n                    Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);\n                    intent.setPackage(mContext.getPackageName());\n                    mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));\n                } else {\n                    mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));\n                }\n            } catch (Exception e) {\n                Log.v(TAG, \"Couldn't request permission for USB device \" + usbDevice);\n                HIDDeviceOpenResult(deviceID, false);\n            }\n            return false;\n        }\n\n        try {\n            return device.open();\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return false;\n    }\n\n    public int sendOutputReport(int deviceID, byte[] report) {\n        try {\n            //Log.v(TAG, \"sendOutputReport deviceID=\" + deviceID + \" length=\" + report.length);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return -1;\n            }\n\n            return device.sendOutputReport(report);\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return -1;\n    }\n\n    public int sendFeatureReport(int deviceID, byte[] report) {\n        try {\n            //Log.v(TAG, \"sendFeatureReport deviceID=\" + deviceID + \" length=\" + report.length);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return -1;\n            }\n\n            return device.sendFeatureReport(report);\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return -1;\n    }\n\n    public boolean getFeatureReport(int deviceID, byte[] report) {\n        try {\n            //Log.v(TAG, \"getFeatureReport deviceID=\" + deviceID);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return false;\n            }\n\n            return device.getFeatureReport(report);\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n        return false;\n    }\n\n    public void closeDevice(int deviceID) {\n        try {\n            Log.v(TAG, \"closeDevice deviceID=\" + deviceID);\n            HIDDevice device;\n            device = getDevice(deviceID);\n            if (device == null) {\n                HIDDeviceDisconnected(deviceID);\n                return;\n            }\n\n            device.close();\n        } catch (Exception e) {\n            Log.e(TAG, \"Got exception: \" + Log.getStackTraceString(e));\n        }\n    }\n\n\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n    /////////////// Native methods\n    //////////////////////////////////////////////////////////////////////////////////////////////////////\n\n    private native void HIDDeviceRegisterCallback();\n    private native void HIDDeviceReleaseCallback();\n\n    native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol);\n    native void HIDDeviceOpenPending(int deviceID);\n    native void HIDDeviceOpenResult(int deviceID, boolean opened);\n    native void HIDDeviceDisconnected(int deviceID);\n\n    native void HIDDeviceInputReport(int deviceID, byte[] report);\n    native void HIDDeviceFeatureReport(int deviceID, byte[] report);\n}\n"
  },
  {
    "path": "arch/android/project/app/src/main/java/org/libsdl/app/HIDDeviceUSB.java",
    "content": "package org.libsdl.app;\n\nimport android.hardware.usb.*;\nimport android.os.Build;\nimport android.util.Log;\nimport java.util.Arrays;\n\nclass HIDDeviceUSB implements HIDDevice {\n\n    private static final String TAG = \"hidapi\";\n\n    protected HIDDeviceManager mManager;\n    protected UsbDevice mDevice;\n    protected int mInterfaceIndex;\n    protected int mInterface;\n    protected int mDeviceId;\n    protected UsbDeviceConnection mConnection;\n    protected UsbEndpoint mInputEndpoint;\n    protected UsbEndpoint mOutputEndpoint;\n    protected InputThread mInputThread;\n    protected boolean mRunning;\n    protected boolean mFrozen;\n\n    public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) {\n        mManager = manager;\n        mDevice = usbDevice;\n        mInterfaceIndex = interface_index;\n        mInterface = mDevice.getInterface(mInterfaceIndex).getId();\n        mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());\n        mRunning = false;\n    }\n\n    public String getIdentifier() {\n        return String.format(\"%s/%x/%x/%d\", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);\n    }\n\n    @Override\n    public int getId() {\n        return mDeviceId;\n    }\n\n    @Override\n    public int getVendorId() {\n        return mDevice.getVendorId();\n    }\n\n    @Override\n    public int getProductId() {\n        return mDevice.getProductId();\n    }\n\n    @Override\n    public String getSerialNumber() {\n        String result = null;\n        if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {\n            try {\n                result = mDevice.getSerialNumber();\n            }\n            catch (SecurityException exception) {\n                //Log.w(TAG, \"App permissions mean we cannot get serial number for device \" + getDeviceName() + \" message: \" + exception.getMessage());\n            }\n        }\n        if (result == null) {\n            result = \"\";\n        }\n        return result;\n    }\n\n    @Override\n    public int getVersion() {\n        return 0;\n    }\n\n    @Override\n    public String getManufacturerName() {\n        String result = null;\n        if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {\n            result = mDevice.getManufacturerName();\n        }\n        if (result == null) {\n            result = String.format(\"%x\", getVendorId());\n        }\n        return result;\n    }\n\n    @Override\n    public String getProductName() {\n        String result = null;\n        if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {\n            result = mDevice.getProductName();\n        }\n        if (result == null) {\n            result = String.format(\"%x\", getProductId());\n        }\n        return result;\n    }\n\n    @Override\n    public UsbDevice getDevice() {\n        return mDevice;\n    }\n\n    public String getDeviceName() {\n        return getManufacturerName() + \" \" + getProductName() + \"(0x\" + String.format(\"%x\", getVendorId()) + \"/0x\" + String.format(\"%x\", getProductId()) + \")\";\n    }\n\n    @Override\n    public boolean open() {\n        mConnection = mManager.getUSBManager().openDevice(mDevice);\n        if (mConnection == null) {\n            Log.w(TAG, \"Unable to open USB device \" + getDeviceName());\n            return false;\n        }\n\n        // Force claim our interface\n        UsbInterface iface = mDevice.getInterface(mInterfaceIndex);\n        if (!mConnection.claimInterface(iface, true)) {\n            Log.w(TAG, \"Failed to claim interfaces on USB device \" + getDeviceName());\n            close();\n            return false;\n        }\n\n        // Find the endpoints\n        for (int j = 0; j < iface.getEndpointCount(); j++) {\n            UsbEndpoint endpt = iface.getEndpoint(j);\n            switch (endpt.getDirection()) {\n            case UsbConstants.USB_DIR_IN:\n                if (mInputEndpoint == null) {\n                    mInputEndpoint = endpt;\n                }\n                break;\n            case UsbConstants.USB_DIR_OUT:\n                if (mOutputEndpoint == null) {\n                    mOutputEndpoint = endpt;\n                }\n                break;\n            }\n        }\n\n        // Make sure the required endpoints were present\n        if (mInputEndpoint == null || mOutputEndpoint == null) {\n            Log.w(TAG, \"Missing required endpoint on USB device \" + getDeviceName());\n            close();\n            return false;\n        }\n\n        // Start listening for input\n        mRunning = true;\n        mInputThread = new InputThread();\n        mInputThread.start();\n\n        return true;\n    }\n\n    @Override\n    public int sendFeatureReport(byte[] report) {\n        int res = -1;\n        int offset = 0;\n        int length = report.length;\n        boolean skipped_report_id = false;\n        byte report_number = report[0];\n\n        if (report_number == 0x0) {\n            ++offset;\n            --length;\n            skipped_report_id = true;\n        }\n\n        res = mConnection.controlTransfer(\n            UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,\n            0x09/*HID set_report*/,\n            (3/*HID feature*/ << 8) | report_number,\n            mInterface,\n            report, offset, length,\n            1000/*timeout millis*/);\n\n        if (res < 0) {\n            Log.w(TAG, \"sendFeatureReport() returned \" + res + \" on device \" + getDeviceName());\n            return -1;\n        }\n\n        if (skipped_report_id) {\n            ++length;\n        }\n        return length;\n    }\n\n    @Override\n    public int sendOutputReport(byte[] report) {\n        int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);\n        if (r != report.length) {\n            Log.w(TAG, \"sendOutputReport() returned \" + r + \" on device \" + getDeviceName());\n        }\n        return r;\n    }\n\n    @Override\n    public boolean getFeatureReport(byte[] report) {\n        int res = -1;\n        int offset = 0;\n        int length = report.length;\n        boolean skipped_report_id = false;\n        byte report_number = report[0];\n\n        if (report_number == 0x0) {\n            /* Offset the return buffer by 1, so that the report ID\n               will remain in byte 0. */\n            ++offset;\n            --length;\n            skipped_report_id = true;\n        }\n\n        res = mConnection.controlTransfer(\n            UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,\n            0x01/*HID get_report*/,\n            (3/*HID feature*/ << 8) | report_number,\n            mInterface,\n            report, offset, length,\n            1000/*timeout millis*/);\n\n        if (res < 0) {\n            Log.w(TAG, \"getFeatureReport() returned \" + res + \" on device \" + getDeviceName());\n            return false;\n        }\n\n        if (skipped_report_id) {\n            ++res;\n            ++length;\n        }\n\n        byte[] data;\n        if (res == length) {\n            data = report;\n        } else {\n            data = Arrays.copyOfRange(report, 0, res);\n        }\n        mManager.HIDDeviceFeatureReport(mDeviceId, data);\n\n        return true;\n    }\n\n    @Override\n    public void close() {\n        mRunning = false;\n        if (mInputThread != null) {\n            while (mInputThread.isAlive()) {\n                mInputThread.interrupt();\n                try {\n                    mInputThread.join();\n                } catch (InterruptedException e) {\n                    // Keep trying until we're done\n                }\n            }\n            mInputThread = null;\n        }\n        if (mConnection != null) {\n            UsbInterface iface = mDevice.getInterface(mInterfaceIndex);\n            mConnection.releaseInterface(iface);\n            mConnection.close();\n            mConnection = null;\n        }\n    }\n\n    @Override\n    public void shutdown() {\n        close();\n        mManager = null;\n    }\n\n    @Override\n    public void setFrozen(boolean frozen) {\n        mFrozen = frozen;\n    }\n\n    protected class InputThread extends Thread {\n        @Override\n        public void run() {\n            int packetSize = mInputEndpoint.getMaxPacketSize();\n            byte[] packet = new byte[packetSize];\n            while (mRunning) {\n                int r;\n                try\n                {\n                    r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);\n                }\n                catch (Exception e)\n                {\n                    Log.v(TAG, \"Exception in UsbDeviceConnection bulktransfer: \" + e);\n                    break;\n                }\n                if (r < 0) {\n                    // Could be a timeout or an I/O error\n                }\n                if (r > 0) {\n                    byte[] data;\n                    if (r == packetSize) {\n                        data = packet;\n                    } else {\n                        data = Arrays.copyOfRange(packet, 0, r);\n                    }\n\n                    if (!mFrozen) {\n                        mManager.HIDDeviceInputReport(mDeviceId, data);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "arch/android/project/app/src/main/java/org/libsdl/app/SDL.java",
    "content": "package org.libsdl.app;\n\nimport android.content.Context;\n\nimport java.lang.Class;\nimport java.lang.reflect.Method;\n\n/**\n    SDL library initialization\n*/\npublic class SDL {\n\n    // This function should be called first and sets up the native code\n    // so it can call into the Java classes\n    public static void setupJNI() {\n        SDLActivity.nativeSetupJNI();\n        SDLAudioManager.nativeSetupJNI();\n        SDLControllerManager.nativeSetupJNI();\n    }\n\n    // This function should be called each time the activity is started\n    public static void initialize() {\n        setContext(null);\n\n        SDLActivity.initialize();\n        SDLAudioManager.initialize();\n        SDLControllerManager.initialize();\n    }\n\n    // This function stores the current activity (SDL or not)\n    public static void setContext(Context context) {\n        SDLAudioManager.setContext(context);\n        mContext = context;\n    }\n\n    public static Context getContext() {\n        return mContext;\n    }\n\n    public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {\n        loadLibrary(libraryName, mContext);\n    }\n\n    public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException {\n        if (libraryName == null) {\n            throw new NullPointerException(\"No library name provided.\");\n        }\n\n        try {\n            // Let's see if we have ReLinker available in the project.  This is necessary for \n            // some projects that have huge numbers of local libraries bundled, and thus may \n            // trip a bug in Android's native library loader which ReLinker works around.  (If\n            // loadLibrary works properly, ReLinker will simply use the normal Android method\n            // internally.)\n            //\n            // To use ReLinker, just add it as a dependency.  For more information, see \n            // https://github.com/KeepSafe/ReLinker for ReLinker's repository.\n            //\n            Class<?> relinkClass = context.getClassLoader().loadClass(\"com.getkeepsafe.relinker.ReLinker\");\n            Class<?> relinkListenerClass = context.getClassLoader().loadClass(\"com.getkeepsafe.relinker.ReLinker$LoadListener\");\n            Class<?> contextClass = context.getClassLoader().loadClass(\"android.content.Context\");\n            Class<?> stringClass = context.getClassLoader().loadClass(\"java.lang.String\");\n\n            // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if \n            // they've changed during updates.\n            Method forceMethod = relinkClass.getDeclaredMethod(\"force\");\n            Object relinkInstance = forceMethod.invoke(null);\n            Class<?> relinkInstanceClass = relinkInstance.getClass();\n\n            // Actually load the library!\n            Method loadMethod = relinkInstanceClass.getDeclaredMethod(\"loadLibrary\", contextClass, stringClass, stringClass, relinkListenerClass);\n            loadMethod.invoke(relinkInstance, context, libraryName, null, null);\n        }\n        catch (final Throwable e) {\n            // Fall back\n            try {\n                System.loadLibrary(libraryName);\n            }\n            catch (final UnsatisfiedLinkError ule) {\n                throw ule;\n            }\n            catch (final SecurityException se) {\n                throw se;\n            }\n        }\n    }\n\n    protected static Context mContext;\n}\n"
  },
  {
    "path": "arch/android/project/app/src/main/java/org/libsdl/app/SDLActivity.java",
    "content": "package org.libsdl.app;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.Dialog;\nimport android.app.UiModeManager;\nimport android.content.ClipboardManager;\nimport android.content.ClipData;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.Intent;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.ApplicationInfo;\nimport android.content.pm.PackageManager;\nimport android.content.res.Configuration;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.PorterDuff;\nimport android.graphics.drawable.Drawable;\nimport android.hardware.Sensor;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Message;\nimport android.text.Editable;\nimport android.text.InputType;\nimport android.text.Selection;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport android.util.SparseArray;\nimport android.view.Display;\nimport android.view.Gravity;\nimport android.view.InputDevice;\nimport android.view.KeyEvent;\nimport android.view.PointerIcon;\nimport android.view.Surface;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.view.inputmethod.BaseInputConnection;\nimport android.view.inputmethod.EditorInfo;\nimport android.view.inputmethod.InputConnection;\nimport android.view.inputmethod.InputMethodManager;\nimport android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.LinearLayout;\nimport android.widget.RelativeLayout;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport java.util.Hashtable;\nimport java.util.Locale;\n\n\n/**\n    SDL Activity\n*/\npublic class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener {\n    private static final String TAG = \"SDL\";\n    private static final int SDL_MAJOR_VERSION = 2;\n    private static final int SDL_MINOR_VERSION = 30;\n    private static final int SDL_MICRO_VERSION = 9;\n/*\n    // Display InputType.SOURCE/CLASS of events and devices\n    //\n    // SDLActivity.debugSource(device.getSources(), \"device[\" + device.getName() + \"]\");\n    // SDLActivity.debugSource(event.getSource(), \"event\");\n    public static void debugSource(int sources, String prefix) {\n        int s = sources;\n        int s_copy = sources;\n        String cls = \"\";\n        String src = \"\";\n        int tst = 0;\n        int FLAG_TAINTED = 0x80000000;\n\n        if ((s & InputDevice.SOURCE_CLASS_BUTTON) != 0)     cls += \" BUTTON\";\n        if ((s & InputDevice.SOURCE_CLASS_JOYSTICK) != 0)   cls += \" JOYSTICK\";\n        if ((s & InputDevice.SOURCE_CLASS_POINTER) != 0)    cls += \" POINTER\";\n        if ((s & InputDevice.SOURCE_CLASS_POSITION) != 0)   cls += \" POSITION\";\n        if ((s & InputDevice.SOURCE_CLASS_TRACKBALL) != 0)  cls += \" TRACKBALL\";\n\n\n        int s2 = s_copy & ~InputDevice.SOURCE_ANY; // keep class bits\n        s2 &= ~(  InputDevice.SOURCE_CLASS_BUTTON\n                | InputDevice.SOURCE_CLASS_JOYSTICK\n                | InputDevice.SOURCE_CLASS_POINTER\n                | InputDevice.SOURCE_CLASS_POSITION\n                | InputDevice.SOURCE_CLASS_TRACKBALL);\n\n        if (s2 != 0) cls += \"Some_Unknown\";\n\n        s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class;\n\n        if (Build.VERSION.SDK_INT >= 23) {\n            tst = InputDevice.SOURCE_BLUETOOTH_STYLUS;\n            if ((s & tst) == tst) src += \" BLUETOOTH_STYLUS\";\n            s2 &= ~tst;\n        }\n\n        tst = InputDevice.SOURCE_DPAD;\n        if ((s & tst) == tst) src += \" DPAD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_GAMEPAD;\n        if ((s & tst) == tst) src += \" GAMEPAD\";\n        s2 &= ~tst;\n\n        if (Build.VERSION.SDK_INT >= 21) {\n            tst = InputDevice.SOURCE_HDMI;\n            if ((s & tst) == tst) src += \" HDMI\";\n            s2 &= ~tst;\n        }\n\n        tst = InputDevice.SOURCE_JOYSTICK;\n        if ((s & tst) == tst) src += \" JOYSTICK\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_KEYBOARD;\n        if ((s & tst) == tst) src += \" KEYBOARD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_MOUSE;\n        if ((s & tst) == tst) src += \" MOUSE\";\n        s2 &= ~tst;\n\n        if (Build.VERSION.SDK_INT >= 26) {\n            tst = InputDevice.SOURCE_MOUSE_RELATIVE;\n            if ((s & tst) == tst) src += \" MOUSE_RELATIVE\";\n            s2 &= ~tst;\n\n            tst = InputDevice.SOURCE_ROTARY_ENCODER;\n            if ((s & tst) == tst) src += \" ROTARY_ENCODER\";\n            s2 &= ~tst;\n        }\n        tst = InputDevice.SOURCE_STYLUS;\n        if ((s & tst) == tst) src += \" STYLUS\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_TOUCHPAD;\n        if ((s & tst) == tst) src += \" TOUCHPAD\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_TOUCHSCREEN;\n        if ((s & tst) == tst) src += \" TOUCHSCREEN\";\n        s2 &= ~tst;\n\n        if (Build.VERSION.SDK_INT >= 18) {\n            tst = InputDevice.SOURCE_TOUCH_NAVIGATION;\n            if ((s & tst) == tst) src += \" TOUCH_NAVIGATION\";\n            s2 &= ~tst;\n        }\n\n        tst = InputDevice.SOURCE_TRACKBALL;\n        if ((s & tst) == tst) src += \" TRACKBALL\";\n        s2 &= ~tst;\n\n        tst = InputDevice.SOURCE_ANY;\n        if ((s & tst) == tst) src += \" ANY\";\n        s2 &= ~tst;\n\n        if (s == FLAG_TAINTED) src += \" FLAG_TAINTED\";\n        s2 &= ~FLAG_TAINTED;\n\n        if (s2 != 0) src += \" Some_Unknown\";\n\n        Log.v(TAG, prefix + \"int=\" + s_copy + \" CLASS={\" + cls + \" } source(s):\" + src);\n    }\n*/\n\n    public static boolean mIsResumedCalled, mHasFocus;\n    public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24  /* Android 7.0 (N) */);\n\n    // Cursor types\n    // private static final int SDL_SYSTEM_CURSOR_NONE = -1;\n    private static final int SDL_SYSTEM_CURSOR_ARROW = 0;\n    private static final int SDL_SYSTEM_CURSOR_IBEAM = 1;\n    private static final int SDL_SYSTEM_CURSOR_WAIT = 2;\n    private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3;\n    private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4;\n    private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5;\n    private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6;\n    private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7;\n    private static final int SDL_SYSTEM_CURSOR_SIZENS = 8;\n    private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9;\n    private static final int SDL_SYSTEM_CURSOR_NO = 10;\n    private static final int SDL_SYSTEM_CURSOR_HAND = 11;\n\n    protected static final int SDL_ORIENTATION_UNKNOWN = 0;\n    protected static final int SDL_ORIENTATION_LANDSCAPE = 1;\n    protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2;\n    protected static final int SDL_ORIENTATION_PORTRAIT = 3;\n    protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4;\n\n    protected static int mCurrentOrientation;\n    protected static Locale mCurrentLocale;\n\n    // Handle the state of the native layer\n    public enum NativeState {\n           INIT, RESUMED, PAUSED\n    }\n\n    public static NativeState mNextNativeState;\n    public static NativeState mCurrentNativeState;\n\n    /** If shared libraries (e.g. SDL or the native application) could not be loaded. */\n    public static boolean mBrokenLibraries = true;\n\n    // Main components\n    protected static SDLActivity mSingleton;\n    protected static SDLSurface mSurface;\n    protected static DummyEdit mTextEdit;\n    protected static boolean mScreenKeyboardShown;\n    protected static ViewGroup mLayout;\n    protected static SDLClipboardHandler mClipboardHandler;\n    protected static Hashtable<Integer, PointerIcon> mCursors;\n    protected static int mLastCursorID;\n    protected static SDLGenericMotionListener_API12 mMotionListener;\n    protected static HIDDeviceManager mHIDDeviceManager;\n\n    // This is what SDL runs in. It invokes SDL_main(), eventually\n    protected static Thread mSDLThread;\n\n    protected static SDLGenericMotionListener_API12 getMotionListener() {\n        if (mMotionListener == null) {\n            if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {\n                mMotionListener = new SDLGenericMotionListener_API26();\n            } else if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n                mMotionListener = new SDLGenericMotionListener_API24();\n            } else {\n                mMotionListener = new SDLGenericMotionListener_API12();\n            }\n        }\n\n        return mMotionListener;\n    }\n\n    /**\n     * This method returns the name of the shared object with the application entry point\n     * It can be overridden by derived classes.\n     */\n    protected String getMainSharedObject() {\n        String library;\n        String[] libraries = SDLActivity.mSingleton.getLibraries();\n        if (libraries.length > 0) {\n            library = \"lib\" + libraries[libraries.length - 1] + \".so\";\n        } else {\n            library = \"libmain.so\";\n        }\n        return getContext().getApplicationInfo().nativeLibraryDir + \"/\" + library;\n    }\n\n    /**\n     * This method returns the name of the application entry point\n     * It can be overridden by derived classes.\n     */\n    protected String getMainFunction() {\n        return \"SDL_main\";\n    }\n\n    /**\n     * This method is called by SDL before loading the native shared libraries.\n     * It can be overridden to provide names of shared libraries to be loaded.\n     * The default implementation returns the defaults. It never returns null.\n     * An array returned by a new implementation must at least contain \"SDL2\".\n     * Also keep in mind that the order the libraries are loaded may matter.\n     * @return names of shared libraries to be loaded (e.g. \"SDL2\", \"main\").\n     */\n    protected String[] getLibraries() {\n        return new String[] {\n            \"SDL2\",\n            // \"SDL2_image\",\n            // \"SDL2_mixer\",\n            // \"SDL2_net\",\n            // \"SDL2_ttf\",\n            \"main\"\n        };\n    }\n\n    // Load the .so\n    public void loadLibraries() {\n       for (String lib : getLibraries()) {\n          SDL.loadLibrary(lib, this);\n       }\n    }\n\n    /**\n     * This method is called by SDL before starting the native application thread.\n     * It can be overridden to provide the arguments after the application name.\n     * The default implementation returns an empty array. It never returns null.\n     * @return arguments for the native application.\n     */\n    protected String[] getArguments() {\n        return new String[0];\n    }\n\n    public static void initialize() {\n        // The static nature of the singleton and Android quirkyness force us to initialize everything here\n        // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values\n        mSingleton = null;\n        mSurface = null;\n        mTextEdit = null;\n        mLayout = null;\n        mClipboardHandler = null;\n        mCursors = new Hashtable<Integer, PointerIcon>();\n        mLastCursorID = 0;\n        mSDLThread = null;\n        mIsResumedCalled = false;\n        mHasFocus = true;\n        mNextNativeState = NativeState.INIT;\n        mCurrentNativeState = NativeState.INIT;\n    }\n    \n    protected SDLSurface createSDLSurface(Context context) {\n        return new SDLSurface(context);\n    }\n\n    // Setup\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        Log.v(TAG, \"Device: \" + Build.DEVICE);\n        Log.v(TAG, \"Model: \" + Build.MODEL);\n        Log.v(TAG, \"onCreate()\");\n        super.onCreate(savedInstanceState);\n\n        try {\n            Thread.currentThread().setName(\"SDLActivity\");\n        } catch (Exception e) {\n            Log.v(TAG, \"modify thread properties failed \" + e.toString());\n        }\n\n        // Load shared libraries\n        String errorMsgBrokenLib = \"\";\n        try {\n            loadLibraries();\n            mBrokenLibraries = false; /* success */\n        } catch(UnsatisfiedLinkError e) {\n            System.err.println(e.getMessage());\n            mBrokenLibraries = true;\n            errorMsgBrokenLib = e.getMessage();\n        } catch(Exception e) {\n            System.err.println(e.getMessage());\n            mBrokenLibraries = true;\n            errorMsgBrokenLib = e.getMessage();\n        }\n\n        if (!mBrokenLibraries) {\n            String expected_version = String.valueOf(SDL_MAJOR_VERSION) + \".\" +\n                                      String.valueOf(SDL_MINOR_VERSION) + \".\" +\n                                      String.valueOf(SDL_MICRO_VERSION);\n            String version = nativeGetVersion();\n            if (!version.equals(expected_version)) {\n                mBrokenLibraries = true;\n                errorMsgBrokenLib = \"SDL C/Java version mismatch (expected \" + expected_version + \", got \" + version + \")\";\n            }\n        }\n\n        if (mBrokenLibraries) {\n            mSingleton = this;\n            AlertDialog.Builder dlgAlert  = new AlertDialog.Builder(this);\n            dlgAlert.setMessage(\"An error occurred while trying to start the application. Please try again and/or reinstall.\"\n                  + System.getProperty(\"line.separator\")\n                  + System.getProperty(\"line.separator\")\n                  + \"Error: \" + errorMsgBrokenLib);\n            dlgAlert.setTitle(\"SDL Error\");\n            dlgAlert.setPositiveButton(\"Exit\",\n                new DialogInterface.OnClickListener() {\n                    @Override\n                    public void onClick(DialogInterface dialog,int id) {\n                        // if this button is clicked, close current activity\n                        SDLActivity.mSingleton.finish();\n                    }\n                });\n           dlgAlert.setCancelable(false);\n           dlgAlert.create().show();\n\n           return;\n        }\n\n        // Set up JNI\n        SDL.setupJNI();\n\n        // Initialize state\n        SDL.initialize();\n\n        // So we can call stuff from static callbacks\n        mSingleton = this;\n        SDL.setContext(this);\n\n        mClipboardHandler = new SDLClipboardHandler();\n\n        mHIDDeviceManager = HIDDeviceManager.acquire(this);\n\n        // Set up the surface\n        mSurface = createSDLSurface(this);\n\n        mLayout = new RelativeLayout(this);\n        mLayout.addView(mSurface);\n\n        // Get our current screen orientation and pass it down.\n        mCurrentOrientation = SDLActivity.getCurrentOrientation();\n        // Only record current orientation\n        SDLActivity.onNativeOrientationChanged(mCurrentOrientation);\n\n        try {\n            if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {\n                mCurrentLocale = getContext().getResources().getConfiguration().locale;\n            } else {\n                mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0);\n            }\n        } catch(Exception ignored) {\n        }\n\n        setContentView(mLayout);\n\n        setWindowStyle(false);\n\n        getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this);\n\n        // Get filename from \"Open with\" of another application\n        Intent intent = getIntent();\n        if (intent != null && intent.getData() != null) {\n            String filename = intent.getData().getPath();\n            if (filename != null) {\n                Log.v(TAG, \"Got filename: \" + filename);\n                SDLActivity.onNativeDropFile(filename);\n            }\n        }\n    }\n\n    protected void pauseNativeThread() {\n        mNextNativeState = NativeState.PAUSED;\n        mIsResumedCalled = false;\n\n        if (SDLActivity.mBrokenLibraries) {\n            return;\n        }\n\n        SDLActivity.handleNativeState();\n    }\n\n    protected void resumeNativeThread() {\n        mNextNativeState = NativeState.RESUMED;\n        mIsResumedCalled = true;\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        SDLActivity.handleNativeState();\n    }\n\n    // Events\n    @Override\n    protected void onPause() {\n        Log.v(TAG, \"onPause()\");\n        super.onPause();\n\n        if (mHIDDeviceManager != null) {\n            mHIDDeviceManager.setFrozen(true);\n        }\n        if (!mHasMultiWindow) {\n            pauseNativeThread();\n        }\n    }\n\n    @Override\n    protected void onResume() {\n        Log.v(TAG, \"onResume()\");\n        super.onResume();\n\n        if (mHIDDeviceManager != null) {\n            mHIDDeviceManager.setFrozen(false);\n        }\n        if (!mHasMultiWindow) {\n            resumeNativeThread();\n        }\n    }\n\n    @Override\n    protected void onStop() {\n        Log.v(TAG, \"onStop()\");\n        super.onStop();\n        if (mHasMultiWindow) {\n            pauseNativeThread();\n        }\n    }\n\n    @Override\n    protected void onStart() {\n        Log.v(TAG, \"onStart()\");\n        super.onStart();\n        if (mHasMultiWindow) {\n            resumeNativeThread();\n        }\n    }\n\n    public static int getCurrentOrientation() {\n        int result = SDL_ORIENTATION_UNKNOWN;\n\n        Activity activity = (Activity)getContext();\n        if (activity == null) {\n            return result;\n        }\n        Display display = activity.getWindowManager().getDefaultDisplay();\n\n        switch (display.getRotation()) {\n            case Surface.ROTATION_0:\n                result = SDL_ORIENTATION_PORTRAIT;\n                break;\n\n            case Surface.ROTATION_90:\n                result = SDL_ORIENTATION_LANDSCAPE;\n                break;\n\n            case Surface.ROTATION_180:\n                result = SDL_ORIENTATION_PORTRAIT_FLIPPED;\n                break;\n\n            case Surface.ROTATION_270:\n                result = SDL_ORIENTATION_LANDSCAPE_FLIPPED;\n                break;\n        }\n\n        return result;\n    }\n\n    @Override\n    public void onWindowFocusChanged(boolean hasFocus) {\n        super.onWindowFocusChanged(hasFocus);\n        Log.v(TAG, \"onWindowFocusChanged(): \" + hasFocus);\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        mHasFocus = hasFocus;\n        if (hasFocus) {\n           mNextNativeState = NativeState.RESUMED;\n           SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded();\n\n           SDLActivity.handleNativeState();\n           nativeFocusChanged(true);\n\n        } else {\n           nativeFocusChanged(false);\n           if (!mHasMultiWindow) {\n               mNextNativeState = NativeState.PAUSED;\n               SDLActivity.handleNativeState();\n           }\n        }\n    }\n\n    @Override\n    public void onLowMemory() {\n        Log.v(TAG, \"onLowMemory()\");\n        super.onLowMemory();\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        SDLActivity.nativeLowMemory();\n    }\n\n    @Override\n    public void onConfigurationChanged(Configuration newConfig) {\n        Log.v(TAG, \"onConfigurationChanged()\");\n        super.onConfigurationChanged(newConfig);\n\n        if (SDLActivity.mBrokenLibraries) {\n           return;\n        }\n\n        if (mCurrentLocale == null || !mCurrentLocale.equals(newConfig.locale)) {\n            mCurrentLocale = newConfig.locale;\n            SDLActivity.onNativeLocaleChanged();\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        Log.v(TAG, \"onDestroy()\");\n\n        if (mHIDDeviceManager != null) {\n            HIDDeviceManager.release(mHIDDeviceManager);\n            mHIDDeviceManager = null;\n        }\n\n        SDLAudioManager.release(this);\n\n        if (SDLActivity.mBrokenLibraries) {\n           super.onDestroy();\n           return;\n        }\n\n        if (SDLActivity.mSDLThread != null) {\n\n            // Send Quit event to \"SDLThread\" thread\n            SDLActivity.nativeSendQuit();\n\n            // Wait for \"SDLThread\" thread to end\n            try {\n                SDLActivity.mSDLThread.join();\n            } catch(Exception e) {\n                Log.v(TAG, \"Problem stopping SDLThread: \" + e);\n            }\n        }\n\n        SDLActivity.nativeQuit();\n\n        super.onDestroy();\n    }\n\n    @Override\n    public void onBackPressed() {\n        // Check if we want to block the back button in case of mouse right click.\n        //\n        // If we do, the normal hardware back button will no longer work and people have to use home,\n        // but the mouse right click will work.\n        //\n        boolean trapBack = SDLActivity.nativeGetHintBoolean(\"SDL_ANDROID_TRAP_BACK_BUTTON\", false);\n        if (trapBack) {\n            // Exit and let the mouse handler handle this button (if appropriate)\n            return;\n        }\n\n        // Default system back button behavior.\n        if (!isFinishing()) {\n            super.onBackPressed();\n        }\n    }\n\n    // Called by JNI from SDL.\n    public static void manualBackButton() {\n        mSingleton.pressBackButton();\n    }\n\n    // Used to get us onto the activity's main thread\n    public void pressBackButton() {\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                if (!SDLActivity.this.isFinishing()) {\n                    SDLActivity.this.superOnBackPressed();\n                }\n            }\n        });\n    }\n\n    // Used to access the system back behavior.\n    public void superOnBackPressed() {\n        super.onBackPressed();\n    }\n\n    @Override\n    public boolean dispatchKeyEvent(KeyEvent event) {\n\n        if (SDLActivity.mBrokenLibraries) {\n           return false;\n        }\n\n        int keyCode = event.getKeyCode();\n        // Ignore certain special keys so they're handled by Android\n        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||\n            keyCode == KeyEvent.KEYCODE_VOLUME_UP ||\n            keyCode == KeyEvent.KEYCODE_CAMERA ||\n            keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */\n            keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */\n            ) {\n            return false;\n        }\n        return super.dispatchKeyEvent(event);\n    }\n\n    /* Transition to next state */\n    public static void handleNativeState() {\n\n        if (mNextNativeState == mCurrentNativeState) {\n            // Already in same state, discard.\n            return;\n        }\n\n        // Try a transition to init state\n        if (mNextNativeState == NativeState.INIT) {\n\n            mCurrentNativeState = mNextNativeState;\n            return;\n        }\n\n        // Try a transition to paused state\n        if (mNextNativeState == NativeState.PAUSED) {\n            if (mSDLThread != null) {\n                nativePause();\n            }\n            if (mSurface != null) {\n                mSurface.handlePause();\n            }\n            mCurrentNativeState = mNextNativeState;\n            return;\n        }\n\n        // Try a transition to resumed state\n        if (mNextNativeState == NativeState.RESUMED) {\n            if (mSurface.mIsSurfaceReady && mHasFocus && mIsResumedCalled) {\n                if (mSDLThread == null) {\n                    // This is the entry point to the C app.\n                    // Start up the C app thread and enable sensor input for the first time\n                    // FIXME: Why aren't we enabling sensor input at start?\n\n                    mSDLThread = new Thread(new SDLMain(), \"SDLThread\");\n                    mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);\n                    mSDLThread.start();\n\n                    // No nativeResume(), don't signal Android_ResumeSem\n                } else {\n                    nativeResume();\n                }\n                mSurface.handleResume();\n\n                mCurrentNativeState = mNextNativeState;\n            }\n        }\n    }\n\n    // Messages from the SDLMain thread\n    static final int COMMAND_CHANGE_TITLE = 1;\n    static final int COMMAND_CHANGE_WINDOW_STYLE = 2;\n    static final int COMMAND_TEXTEDIT_HIDE = 3;\n    static final int COMMAND_SET_KEEP_SCREEN_ON = 5;\n\n    protected static final int COMMAND_USER = 0x8000;\n\n    protected static boolean mFullscreenModeActive;\n\n    /**\n     * This method is called by SDL if SDL did not handle a message itself.\n     * This happens if a received message contains an unsupported command.\n     * Method can be overwritten to handle Messages in a different class.\n     * @param command the command of the message.\n     * @param param the parameter of the message. May be null.\n     * @return if the message was handled in overridden method.\n     */\n    protected boolean onUnhandledMessage(int command, Object param) {\n        return false;\n    }\n\n    /**\n     * A Handler class for Messages from native SDL applications.\n     * It uses current Activities as target (e.g. for the title).\n     * static to prevent implicit references to enclosing object.\n     */\n    protected static class SDLCommandHandler extends Handler {\n        @Override\n        public void handleMessage(Message msg) {\n            Context context = SDL.getContext();\n            if (context == null) {\n                Log.e(TAG, \"error handling message, getContext() returned null\");\n                return;\n            }\n            switch (msg.arg1) {\n            case COMMAND_CHANGE_TITLE:\n                if (context instanceof Activity) {\n                    ((Activity) context).setTitle((String)msg.obj);\n                } else {\n                    Log.e(TAG, \"error handling message, getContext() returned no Activity\");\n                }\n                break;\n            case COMMAND_CHANGE_WINDOW_STYLE:\n                if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {\n                    if (context instanceof Activity) {\n                        Window window = ((Activity) context).getWindow();\n                        if (window != null) {\n                            if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) {\n                                int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |\n                                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |\n                                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |\n                                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |\n                                        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |\n                                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;\n                                window.getDecorView().setSystemUiVisibility(flags);\n                                window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n                                window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);\n                                SDLActivity.mFullscreenModeActive = true;\n                            } else {\n                                int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE;\n                                window.getDecorView().setSystemUiVisibility(flags);\n                                window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);\n                                window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);\n                                SDLActivity.mFullscreenModeActive = false;\n                            }\n                        }\n                    } else {\n                        Log.e(TAG, \"error handling message, getContext() returned no Activity\");\n                    }\n                }\n                break;\n            case COMMAND_TEXTEDIT_HIDE:\n                if (mTextEdit != null) {\n                    // Note: On some devices setting view to GONE creates a flicker in landscape.\n                    // Setting the View's sizes to 0 is similar to GONE but without the flicker.\n                    // The sizes will be set to useful values when the keyboard is shown again.\n                    mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));\n\n                    InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);\n                    imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);\n\n                    mScreenKeyboardShown = false;\n\n                    mSurface.requestFocus();\n                }\n                break;\n            case COMMAND_SET_KEEP_SCREEN_ON:\n            {\n                if (context instanceof Activity) {\n                    Window window = ((Activity) context).getWindow();\n                    if (window != null) {\n                        if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) {\n                            window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\n                        } else {\n                            window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\n                        }\n                    }\n                }\n                break;\n            }\n            default:\n                if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {\n                    Log.e(TAG, \"error handling message, command is \" + msg.arg1);\n                }\n            }\n        }\n    }\n\n    // Handler for the messages\n    Handler commandHandler = new SDLCommandHandler();\n\n    // Send a message from the SDLMain thread\n    boolean sendCommand(int command, Object data) {\n        Message msg = commandHandler.obtainMessage();\n        msg.arg1 = command;\n        msg.obj = data;\n        boolean result = commandHandler.sendMessage(msg);\n\n        if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {\n            if (command == COMMAND_CHANGE_WINDOW_STYLE) {\n                // Ensure we don't return until the resize has actually happened,\n                // or 500ms have passed.\n\n                boolean bShouldWait = false;\n\n                if (data instanceof Integer) {\n                    // Let's figure out if we're already laid out fullscreen or not.\n                    Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();\n                    DisplayMetrics realMetrics = new DisplayMetrics();\n                    display.getRealMetrics(realMetrics);\n\n                    boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) &&\n                            (realMetrics.heightPixels == mSurface.getHeight()));\n\n                    if ((Integer) data == 1) {\n                        // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going\n                        // to change size and should wait for surfaceChanged() before we return, so the size\n                        // is right back in native code.  If we're already laid out fullscreen, though, we're\n                        // not going to change size even if we change decor modes, so we shouldn't wait for\n                        // surfaceChanged() -- which may not even happen -- and should return immediately.\n                        bShouldWait = !bFullscreenLayout;\n                    } else {\n                        // If we're laid out fullscreen (even if the status bar and nav bar are present),\n                        // or are actively in fullscreen, we're going to change size and should wait for\n                        // surfaceChanged before we return, so the size is right back in native code.\n                        bShouldWait = bFullscreenLayout;\n                    }\n                }\n\n                if (bShouldWait && (SDLActivity.getContext() != null)) {\n                    // We'll wait for the surfaceChanged() method, which will notify us\n                    // when called.  That way, we know our current size is really the\n                    // size we need, instead of grabbing a size that's still got\n                    // the navigation and/or status bars before they're hidden.\n                    //\n                    // We'll wait for up to half a second, because some devices\n                    // take a surprisingly long time for the surface resize, but\n                    // then we'll just give up and return.\n                    //\n                    synchronized (SDLActivity.getContext()) {\n                        try {\n                            SDLActivity.getContext().wait(500);\n                        } catch (InterruptedException ie) {\n                            ie.printStackTrace();\n                        }\n                    }\n                }\n            }\n        }\n\n        return result;\n    }\n\n    // C functions we call\n    public static native String nativeGetVersion();\n    public static native int nativeSetupJNI();\n    public static native int nativeRunMain(String library, String function, Object arguments);\n    public static native void nativeLowMemory();\n    public static native void nativeSendQuit();\n    public static native void nativeQuit();\n    public static native void nativePause();\n    public static native void nativeResume();\n    public static native void nativeFocusChanged(boolean hasFocus);\n    public static native void onNativeDropFile(String filename);\n    public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float rate);\n    public static native void onNativeResize();\n    public static native void onNativeKeyDown(int keycode);\n    public static native void onNativeKeyUp(int keycode);\n    public static native boolean onNativeSoftReturnKey();\n    public static native void onNativeKeyboardFocusLost();\n    public static native void onNativeMouse(int button, int action, float x, float y, boolean relative);\n    public static native void onNativeTouch(int touchDevId, int pointerFingerId,\n                                            int action, float x,\n                                            float y, float p);\n    public static native void onNativeAccel(float x, float y, float z);\n    public static native void onNativeClipboardChanged();\n    public static native void onNativeSurfaceCreated();\n    public static native void onNativeSurfaceChanged();\n    public static native void onNativeSurfaceDestroyed();\n    public static native String nativeGetHint(String name);\n    public static native boolean nativeGetHintBoolean(String name, boolean default_value);\n    public static native void nativeSetenv(String name, String value);\n    public static native void onNativeOrientationChanged(int orientation);\n    public static native void nativeAddTouch(int touchId, String name);\n    public static native void nativePermissionResult(int requestCode, boolean result);\n    public static native void onNativeLocaleChanged();\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setActivityTitle(String title) {\n        // Called from SDLMain() thread and can't directly affect the view\n        return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void setWindowStyle(boolean fullscreen) {\n        // Called from SDLMain() thread and can't directly affect the view\n        mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     * This is a static method for JNI convenience, it calls a non-static method\n     * so that is can be overridden\n     */\n    public static void setOrientation(int w, int h, boolean resizable, String hint)\n    {\n        if (mSingleton != null) {\n            mSingleton.setOrientationBis(w, h, resizable, hint);\n        }\n    }\n\n    /**\n     * This can be overridden\n     */\n    public void setOrientationBis(int w, int h, boolean resizable, String hint)\n    {\n        int orientation_landscape = -1;\n        int orientation_portrait = -1;\n\n        /* If set, hint \"explicitly controls which UI orientations are allowed\". */\n        if (hint.contains(\"LandscapeRight\") && hint.contains(\"LandscapeLeft\")) {\n            orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;\n        } else if (hint.contains(\"LandscapeLeft\")) {\n            orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;\n        } else if (hint.contains(\"LandscapeRight\")) {\n            orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;\n        }\n\n        /* exact match to 'Portrait' to distinguish with PortraitUpsideDown */\n        boolean contains_Portrait = hint.contains(\"Portrait \") || hint.endsWith(\"Portrait\");\n\n        if (contains_Portrait && hint.contains(\"PortraitUpsideDown\")) {\n            orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;\n        } else if (contains_Portrait) {\n            orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;\n        } else if (hint.contains(\"PortraitUpsideDown\")) {\n            orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;\n        }\n\n        boolean is_landscape_allowed = (orientation_landscape != -1);\n        boolean is_portrait_allowed = (orientation_portrait != -1);\n        int req; /* Requested orientation */\n\n        /* No valid hint, nothing is explicitly allowed */\n        if (!is_portrait_allowed && !is_landscape_allowed) {\n            if (resizable) {\n                /* All orientations are allowed, respecting user orientation lock setting */\n                req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;\n            } else {\n                /* Fixed window and nothing specified. Get orientation from w/h of created window */\n                req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);\n            }\n        } else {\n            /* At least one orientation is allowed */\n            if (resizable) {\n                if (is_portrait_allowed && is_landscape_allowed) {\n                    /* hint allows both landscape and portrait, promote to full user */\n                    req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;\n                } else {\n                    /* Use the only one allowed \"orientation\" */\n                    req = (is_landscape_allowed ? orientation_landscape : orientation_portrait);\n                }\n            } else {\n                /* Fixed window and both orientations are allowed. Choose one. */\n                if (is_portrait_allowed && is_landscape_allowed) {\n                    req = (w > h ? orientation_landscape : orientation_portrait);\n                } else {\n                    /* Use the only one allowed \"orientation\" */\n                    req = (is_landscape_allowed ? orientation_landscape : orientation_portrait);\n                }\n            }\n        }\n\n        Log.v(TAG, \"setOrientation() requestedOrientation=\" + req + \" width=\" + w +\" height=\"+ h +\" resizable=\" + resizable + \" hint=\" + hint);\n        mSingleton.setRequestedOrientation(req);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void minimizeWindow() {\n\n        if (mSingleton == null) {\n            return;\n        }\n\n        Intent startMain = new Intent(Intent.ACTION_MAIN);\n        startMain.addCategory(Intent.CATEGORY_HOME);\n        startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        mSingleton.startActivity(startMain);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean shouldMinimizeOnFocusLoss() {\n/*\n        if (Build.VERSION.SDK_INT >= 24) {\n            if (mSingleton == null) {\n                return true;\n            }\n\n            if (mSingleton.isInMultiWindowMode()) {\n                return false;\n            }\n\n            if (mSingleton.isInPictureInPictureMode()) {\n                return false;\n            }\n        }\n\n        return true;\n*/\n        return false;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isScreenKeyboardShown()\n    {\n        if (mTextEdit == null) {\n            return false;\n        }\n\n        if (!mScreenKeyboardShown) {\n            return false;\n        }\n\n        InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);\n        return imm.isAcceptingText();\n\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean supportsRelativeMouse()\n    {\n        // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under\n        // Android 7 APIs, and simply returns no data under Android 8 APIs.\n        //\n        // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and\n        // thus SDK version 27.  If we are in DeX mode and not API 27 or higher, as a result,\n        // we should stick to relative mode.\n        //\n        if (Build.VERSION.SDK_INT < 27 /* Android 8.1 (O_MR1) */ && isDeXMode()) {\n            return false;\n        }\n\n        return SDLActivity.getMotionListener().supportsRelativeMouse();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setRelativeMouseEnabled(boolean enabled)\n    {\n        if (enabled && !supportsRelativeMouse()) {\n            return false;\n        }\n\n        return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean sendMessage(int command, int param) {\n        if (mSingleton == null) {\n            return false;\n        }\n        return mSingleton.sendCommand(command, param);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static Context getContext() {\n        return SDL.getContext();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isAndroidTV() {\n        UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE);\n        if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {\n            return true;\n        }\n        if (Build.MANUFACTURER.equals(\"MINIX\") && Build.MODEL.equals(\"NEO-U1\")) {\n            return true;\n        }\n        if (Build.MANUFACTURER.equals(\"Amlogic\") && Build.MODEL.equals(\"X96-W\")) {\n            return true;\n        }\n        return Build.MANUFACTURER.equals(\"Amlogic\") && Build.MODEL.startsWith(\"TV\");\n    }\n\n    public static double getDiagonal()\n    {\n        DisplayMetrics metrics = new DisplayMetrics();\n        Activity activity = (Activity)getContext();\n        if (activity == null) {\n            return 0.0;\n        }\n        activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);\n\n        double dWidthInches = metrics.widthPixels / (double)metrics.xdpi;\n        double dHeightInches = metrics.heightPixels / (double)metrics.ydpi;\n\n        return Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches));\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isTablet() {\n        // If our diagonal size is seven inches or greater, we consider ourselves a tablet.\n        return (getDiagonal() >= 7.0);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isChromebook() {\n        if (getContext() == null) {\n            return false;\n        }\n        return getContext().getPackageManager().hasSystemFeature(\"org.chromium.arc.device_management\");\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean isDeXMode() {\n        if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {\n            return false;\n        }\n        try {\n            final Configuration config = getContext().getResources().getConfiguration();\n            final Class<?> configClass = config.getClass();\n            return configClass.getField(\"SEM_DESKTOP_MODE_ENABLED\").getInt(configClass)\n                    == configClass.getField(\"semDesktopModeEnabled\").getInt(config);\n        } catch(Exception ignored) {\n            return false;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static DisplayMetrics getDisplayDPI() {\n        return getContext().getResources().getDisplayMetrics();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean getManifestEnvironmentVariables() {\n        try {\n            if (getContext() == null) {\n                return false;\n            }\n\n            ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);\n            Bundle bundle = applicationInfo.metaData;\n            if (bundle == null) {\n                return false;\n            }\n            String prefix = \"SDL_ENV.\";\n            final int trimLength = prefix.length();\n            for (String key : bundle.keySet()) {\n                if (key.startsWith(prefix)) {\n                    String name = key.substring(trimLength);\n                    String value = bundle.get(key).toString();\n                    nativeSetenv(name, value);\n                }\n            }\n            /* environment variables set! */\n            return true;\n        } catch (Exception e) {\n           Log.v(TAG, \"exception \" + e.toString());\n        }\n        return false;\n    }\n\n    // This method is called by SDLControllerManager's API 26 Generic Motion Handler.\n    public static View getContentView() {\n        return mLayout;\n    }\n\n    static class ShowTextInputTask implements Runnable {\n        /*\n         * This is used to regulate the pan&scan method to have some offset from\n         * the bottom edge of the input region and the top edge of an input\n         * method (soft keyboard)\n         */\n        static final int HEIGHT_PADDING = 15;\n\n        public int x, y, w, h;\n\n        public ShowTextInputTask(int x, int y, int w, int h) {\n            this.x = x;\n            this.y = y;\n            this.w = w;\n            this.h = h;\n\n            /* Minimum size of 1 pixel, so it takes focus. */\n            if (this.w <= 0) {\n                this.w = 1;\n            }\n            if (this.h + HEIGHT_PADDING <= 0) {\n                this.h = 1 - HEIGHT_PADDING;\n            }\n        }\n\n        @Override\n        public void run() {\n            RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);\n            params.leftMargin = x;\n            params.topMargin = y;\n\n            if (mTextEdit == null) {\n                mTextEdit = new DummyEdit(SDL.getContext());\n\n                mLayout.addView(mTextEdit, params);\n            } else {\n                mTextEdit.setLayoutParams(params);\n            }\n\n            mTextEdit.setVisibility(View.VISIBLE);\n            mTextEdit.requestFocus();\n\n            InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);\n            imm.showSoftInput(mTextEdit, 0);\n\n            mScreenKeyboardShown = true;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean showTextInput(int x, int y, int w, int h) {\n        // Transfer the task to the main thread as a Runnable\n        return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));\n    }\n\n    public static boolean isTextInputEvent(KeyEvent event) {\n\n        // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT\n        if (event.isCtrlPressed()) {\n            return false;\n        }\n\n        return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;\n    }\n\n    public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputConnection ic) {\n        int deviceId = event.getDeviceId();\n        int source = event.getSource();\n\n        if (source == InputDevice.SOURCE_UNKNOWN) {\n            InputDevice device = InputDevice.getDevice(deviceId);\n            if (device != null) {\n                source = device.getSources();\n            }\n        }\n\n//        if (event.getAction() == KeyEvent.ACTION_DOWN) {\n//            Log.v(\"SDL\", \"key down: \" + keyCode + \", deviceId = \" + deviceId + \", source = \" + source);\n//        } else if (event.getAction() == KeyEvent.ACTION_UP) {\n//            Log.v(\"SDL\", \"key up: \" + keyCode + \", deviceId = \" + deviceId + \", source = \" + source);\n//        }\n\n        // Dispatch the different events depending on where they come from\n        // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD\n        // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD\n        //\n        // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and\n        // SOURCE_JOYSTICK, while its key events arrive from the keyboard source\n        // So, retrieve the device itself and check all of its sources\n        if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) {\n            // Note that we process events with specific key codes here\n            if (event.getAction() == KeyEvent.ACTION_DOWN) {\n                if (SDLControllerManager.onNativePadDown(deviceId, keyCode) == 0) {\n                    return true;\n                }\n            } else if (event.getAction() == KeyEvent.ACTION_UP) {\n                if (SDLControllerManager.onNativePadUp(deviceId, keyCode) == 0) {\n                    return true;\n                }\n            }\n        }\n\n        if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {\n            // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses\n            // they are ignored here because sending them as mouse input to SDL is messy\n            if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {\n                switch (event.getAction()) {\n                case KeyEvent.ACTION_DOWN:\n                case KeyEvent.ACTION_UP:\n                    // mark the event as handled or it will be handled by system\n                    // handling KEYCODE_BACK by system will call onBackPressed()\n                    return true;\n                }\n            }\n        }\n\n        if (event.getAction() == KeyEvent.ACTION_DOWN) {\n            if (isTextInputEvent(event)) {\n                if (ic != null) {\n                    ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);\n                } else {\n                    SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1);\n                }\n            }\n            onNativeKeyDown(keyCode);\n            return true;\n        } else if (event.getAction() == KeyEvent.ACTION_UP) {\n            onNativeKeyUp(keyCode);\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static Surface getNativeSurface() {\n        if (SDLActivity.mSurface == null) {\n            return null;\n        }\n        return SDLActivity.mSurface.getNativeSurface();\n    }\n\n    // Input\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void initTouch() {\n        int[] ids = InputDevice.getDeviceIds();\n\n        for (int id : ids) {\n            InputDevice device = InputDevice.getDevice(id);\n            /* Allow SOURCE_TOUCHSCREEN and also Virtual InputDevices because they can send TOUCHSCREEN events */\n            if (device != null && ((device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN\n                    || device.isVirtual())) {\n\n                int touchDevId = device.getId();\n                /*\n                 * Prevent id to be -1, since it's used in SDL internal for synthetic events\n                 * Appears when using Android emulator, eg:\n                 *  adb shell input mouse tap 100 100\n                 *  adb shell input touchscreen tap 100 100\n                 */\n                if (touchDevId < 0) {\n                    touchDevId -= 1;\n                }\n                nativeAddTouch(touchDevId, device.getName());\n            }\n        }\n    }\n\n    // Messagebox\n\n    /** Result of current messagebox. Also used for blocking the calling thread. */\n    protected final int[] messageboxSelection = new int[1];\n\n    /**\n     * This method is called by SDL using JNI.\n     * Shows the messagebox from UI thread and block calling thread.\n     * buttonFlags, buttonIds and buttonTexts must have same length.\n     * @param buttonFlags array containing flags for every button.\n     * @param buttonIds array containing id for every button.\n     * @param buttonTexts array containing text for every button.\n     * @param colors null for default or array of length 5 containing colors.\n     * @return button id or -1.\n     */\n    public int messageboxShowMessageBox(\n            final int flags,\n            final String title,\n            final String message,\n            final int[] buttonFlags,\n            final int[] buttonIds,\n            final String[] buttonTexts,\n            final int[] colors) {\n\n        messageboxSelection[0] = -1;\n\n        // sanity checks\n\n        if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {\n            return -1; // implementation broken\n        }\n\n        // collect arguments for Dialog\n\n        final Bundle args = new Bundle();\n        args.putInt(\"flags\", flags);\n        args.putString(\"title\", title);\n        args.putString(\"message\", message);\n        args.putIntArray(\"buttonFlags\", buttonFlags);\n        args.putIntArray(\"buttonIds\", buttonIds);\n        args.putStringArray(\"buttonTexts\", buttonTexts);\n        args.putIntArray(\"colors\", colors);\n\n        // trigger Dialog creation on UI thread\n\n        runOnUiThread(new Runnable() {\n            @Override\n            public void run() {\n                messageboxCreateAndShow(args);\n            }\n        });\n\n        // block the calling thread\n\n        synchronized (messageboxSelection) {\n            try {\n                messageboxSelection.wait();\n            } catch (InterruptedException ex) {\n                ex.printStackTrace();\n                return -1;\n            }\n        }\n\n        // return selected value\n\n        return messageboxSelection[0];\n    }\n\n    protected void messageboxCreateAndShow(Bundle args) {\n\n        // TODO set values from \"flags\" to messagebox dialog\n\n        // get colors\n\n        int[] colors = args.getIntArray(\"colors\");\n        int backgroundColor;\n        int textColor;\n        int buttonBorderColor;\n        int buttonBackgroundColor;\n        int buttonSelectedColor;\n        if (colors != null) {\n            int i = -1;\n            backgroundColor = colors[++i];\n            textColor = colors[++i];\n            buttonBorderColor = colors[++i];\n            buttonBackgroundColor = colors[++i];\n            buttonSelectedColor = colors[++i];\n        } else {\n            backgroundColor = Color.TRANSPARENT;\n            textColor = Color.TRANSPARENT;\n            buttonBorderColor = Color.TRANSPARENT;\n            buttonBackgroundColor = Color.TRANSPARENT;\n            buttonSelectedColor = Color.TRANSPARENT;\n        }\n\n        // create dialog with title and a listener to wake up calling thread\n\n        final AlertDialog dialog = new AlertDialog.Builder(this).create();\n        dialog.setTitle(args.getString(\"title\"));\n        dialog.setCancelable(false);\n        dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {\n            @Override\n            public void onDismiss(DialogInterface unused) {\n                synchronized (messageboxSelection) {\n                    messageboxSelection.notify();\n                }\n            }\n        });\n\n        // create text\n\n        TextView message = new TextView(this);\n        message.setGravity(Gravity.CENTER);\n        message.setText(args.getString(\"message\"));\n        if (textColor != Color.TRANSPARENT) {\n            message.setTextColor(textColor);\n        }\n\n        // create buttons\n\n        int[] buttonFlags = args.getIntArray(\"buttonFlags\");\n        int[] buttonIds = args.getIntArray(\"buttonIds\");\n        String[] buttonTexts = args.getStringArray(\"buttonTexts\");\n\n        final SparseArray<Button> mapping = new SparseArray<Button>();\n\n        LinearLayout buttons = new LinearLayout(this);\n        buttons.setOrientation(LinearLayout.HORIZONTAL);\n        buttons.setGravity(Gravity.CENTER);\n        for (int i = 0; i < buttonTexts.length; ++i) {\n            Button button = new Button(this);\n            final int id = buttonIds[i];\n            button.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    messageboxSelection[0] = id;\n                    dialog.dismiss();\n                }\n            });\n            if (buttonFlags[i] != 0) {\n                // see SDL_messagebox.h\n                if ((buttonFlags[i] & 0x00000001) != 0) {\n                    mapping.put(KeyEvent.KEYCODE_ENTER, button);\n                }\n                if ((buttonFlags[i] & 0x00000002) != 0) {\n                    mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */\n                }\n            }\n            button.setText(buttonTexts[i]);\n            if (textColor != Color.TRANSPARENT) {\n                button.setTextColor(textColor);\n            }\n            if (buttonBorderColor != Color.TRANSPARENT) {\n                // TODO set color for border of messagebox button\n            }\n            if (buttonBackgroundColor != Color.TRANSPARENT) {\n                Drawable drawable = button.getBackground();\n                if (drawable == null) {\n                    // setting the color this way removes the style\n                    button.setBackgroundColor(buttonBackgroundColor);\n                } else {\n                    // setting the color this way keeps the style (gradient, padding, etc.)\n                    drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY);\n                }\n            }\n            if (buttonSelectedColor != Color.TRANSPARENT) {\n                // TODO set color for selected messagebox button\n            }\n            buttons.addView(button);\n        }\n\n        // create content\n\n        LinearLayout content = new LinearLayout(this);\n        content.setOrientation(LinearLayout.VERTICAL);\n        content.addView(message);\n        content.addView(buttons);\n        if (backgroundColor != Color.TRANSPARENT) {\n            content.setBackgroundColor(backgroundColor);\n        }\n\n        // add content to dialog and return\n\n        dialog.setView(content);\n        dialog.setOnKeyListener(new Dialog.OnKeyListener() {\n            @Override\n            public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {\n                Button button = mapping.get(keyCode);\n                if (button != null) {\n                    if (event.getAction() == KeyEvent.ACTION_UP) {\n                        button.performClick();\n                    }\n                    return true; // also for ignored actions\n                }\n                return false;\n            }\n        });\n\n        dialog.show();\n    }\n\n    private final Runnable rehideSystemUi = new Runnable() {\n        @Override\n        public void run() {\n            if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {\n                int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |\n                        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |\n                        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |\n                        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |\n                        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |\n                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;\n\n                SDLActivity.this.getWindow().getDecorView().setSystemUiVisibility(flags);\n            }\n        }\n    };\n\n    public void onSystemUiVisibilityChange(int visibility) {\n        if (SDLActivity.mFullscreenModeActive && ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 || (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0)) {\n\n            Handler handler = getWindow().getDecorView().getHandler();\n            if (handler != null) {\n                handler.removeCallbacks(rehideSystemUi); // Prevent a hide loop.\n                handler.postDelayed(rehideSystemUi, 2000);\n            }\n\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean clipboardHasText() {\n        return mClipboardHandler.clipboardHasText();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static String clipboardGetText() {\n        return mClipboardHandler.clipboardGetText();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void clipboardSetText(String string) {\n        mClipboardHandler.clipboardSetText(string);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int createCustomCursor(int[] colors, int width, int height, int hotSpotX, int hotSpotY) {\n        Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);\n        ++mLastCursorID;\n\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mCursors.put(mLastCursorID, PointerIcon.create(bitmap, hotSpotX, hotSpotY));\n            } catch (Exception e) {\n                return 0;\n            }\n        } else {\n            return 0;\n        }\n        return mLastCursorID;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void destroyCustomCursor(int cursorID) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mCursors.remove(cursorID);\n            } catch (Exception e) {\n            }\n        }\n        return;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setCustomCursor(int cursorID) {\n\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mSurface.setPointerIcon(mCursors.get(cursorID));\n            } catch (Exception e) {\n                return false;\n            }\n        } else {\n            return false;\n        }\n        return true;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static boolean setSystemCursor(int cursorID) {\n        int cursor_type = 0; //PointerIcon.TYPE_NULL;\n        switch (cursorID) {\n        case SDL_SYSTEM_CURSOR_ARROW:\n            cursor_type = 1000; //PointerIcon.TYPE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_IBEAM:\n            cursor_type = 1008; //PointerIcon.TYPE_TEXT;\n            break;\n        case SDL_SYSTEM_CURSOR_WAIT:\n            cursor_type = 1004; //PointerIcon.TYPE_WAIT;\n            break;\n        case SDL_SYSTEM_CURSOR_CROSSHAIR:\n            cursor_type = 1007; //PointerIcon.TYPE_CROSSHAIR;\n            break;\n        case SDL_SYSTEM_CURSOR_WAITARROW:\n            cursor_type = 1004; //PointerIcon.TYPE_WAIT;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZENWSE:\n            cursor_type = 1017; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZENESW:\n            cursor_type = 1016; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZEWE:\n            cursor_type = 1014; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZENS:\n            cursor_type = 1015; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;\n            break;\n        case SDL_SYSTEM_CURSOR_SIZEALL:\n            cursor_type = 1020; //PointerIcon.TYPE_GRAB;\n            break;\n        case SDL_SYSTEM_CURSOR_NO:\n            cursor_type = 1012; //PointerIcon.TYPE_NO_DROP;\n            break;\n        case SDL_SYSTEM_CURSOR_HAND:\n            cursor_type = 1002; //PointerIcon.TYPE_HAND;\n            break;\n        }\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            try {\n                mSurface.setPointerIcon(PointerIcon.getSystemIcon(SDL.getContext(), cursor_type));\n            } catch (Exception e) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void requestPermission(String permission, int requestCode) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            nativePermissionResult(requestCode, true);\n            return;\n        }\n\n        Activity activity = (Activity)getContext();\n        if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {\n            activity.requestPermissions(new String[]{permission}, requestCode);\n        } else {\n            nativePermissionResult(requestCode, true);\n        }\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {\n        boolean result = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED);\n        nativePermissionResult(requestCode, result);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int openURL(String url)\n    {\n        try {\n            Intent i = new Intent(Intent.ACTION_VIEW);\n            i.setData(Uri.parse(url));\n\n            int flags = Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_MULTIPLE_TASK;\n            if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {\n                flags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;\n            } else {\n                flags |= Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET;\n            }\n            i.addFlags(flags);\n\n            mSingleton.startActivity(i);\n        } catch (Exception ex) {\n            return -1;\n        }\n        return 0;\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int showToast(String message, int duration, int gravity, int xOffset, int yOffset)\n    {\n        if(null == mSingleton) {\n            return - 1;\n        }\n\n        try\n        {\n            class OneShotTask implements Runnable {\n                String mMessage;\n                int mDuration;\n                int mGravity;\n                int mXOffset;\n                int mYOffset;\n\n                OneShotTask(String message, int duration, int gravity, int xOffset, int yOffset) {\n                    mMessage  = message;\n                    mDuration = duration;\n                    mGravity  = gravity;\n                    mXOffset  = xOffset;\n                    mYOffset  = yOffset;\n                }\n\n                public void run() {\n                    try\n                    {\n                        Toast toast = Toast.makeText(mSingleton, mMessage, mDuration);\n                        if (mGravity >= 0) {\n                            toast.setGravity(mGravity, mXOffset, mYOffset);\n                        }\n                        toast.show();\n                    } catch(Exception ex) {\n                        Log.e(TAG, ex.getMessage());\n                    }\n                }\n            }\n            mSingleton.runOnUiThread(new OneShotTask(message, duration, gravity, xOffset, yOffset));\n        } catch(Exception ex) {\n            return -1;\n        }\n        return 0;\n    }\n}\n\n/**\n    Simple runnable to start the SDL application\n*/\nclass SDLMain implements Runnable {\n    @Override\n    public void run() {\n        // Runs SDL_main()\n        String library = SDLActivity.mSingleton.getMainSharedObject();\n        String function = SDLActivity.mSingleton.getMainFunction();\n        String[] arguments = SDLActivity.mSingleton.getArguments();\n\n        try {\n            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DISPLAY);\n        } catch (Exception e) {\n            Log.v(\"SDL\", \"modify thread properties failed \" + e.toString());\n        }\n\n        Log.v(\"SDL\", \"Running main function \" + function + \" from library \" + library);\n\n        SDLActivity.nativeRunMain(library, function, arguments);\n\n        Log.v(\"SDL\", \"Finished main function\");\n\n        if (SDLActivity.mSingleton != null && !SDLActivity.mSingleton.isFinishing()) {\n            // Let's finish the Activity\n            SDLActivity.mSDLThread = null;\n            SDLActivity.mSingleton.finish();\n        }  // else: Activity is already being destroyed\n\n    }\n}\n\n/* This is a fake invisible editor view that receives the input and defines the\n * pan&scan region\n */\nclass DummyEdit extends View implements View.OnKeyListener {\n    InputConnection ic;\n\n    public DummyEdit(Context context) {\n        super(context);\n        setFocusableInTouchMode(true);\n        setFocusable(true);\n        setOnKeyListener(this);\n    }\n\n    @Override\n    public boolean onCheckIsTextEditor() {\n        return true;\n    }\n\n    @Override\n    public boolean onKey(View v, int keyCode, KeyEvent event) {\n        return SDLActivity.handleKeyEvent(v, keyCode, event, ic);\n    }\n\n    //\n    @Override\n    public boolean onKeyPreIme (int keyCode, KeyEvent event) {\n        // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event\n        // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639\n        // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not\n        // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout\n        // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android\n        // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)\n        if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {\n            if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {\n                SDLActivity.onNativeKeyboardFocusLost();\n            }\n        }\n        return super.onKeyPreIme(keyCode, event);\n    }\n\n    @Override\n    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {\n        ic = new SDLInputConnection(this, true);\n\n        outAttrs.inputType = InputType.TYPE_CLASS_TEXT |\n                             InputType.TYPE_TEXT_FLAG_MULTI_LINE;\n        outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI |\n                              EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;\n\n        return ic;\n    }\n}\n\nclass SDLInputConnection extends BaseInputConnection {\n\n    protected EditText mEditText;\n    protected String mCommittedText = \"\";\n\n    public SDLInputConnection(View targetView, boolean fullEditor) {\n        super(targetView, fullEditor);\n        mEditText = new EditText(SDL.getContext());\n    }\n\n    @Override\n    public Editable getEditable() {\n        return mEditText.getEditableText();\n    }\n\n    @Override\n    public boolean sendKeyEvent(KeyEvent event) {\n        /*\n         * This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard)\n         * However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses\n         * and so we need to generate them ourselves in commitText.  To avoid duplicates on the handful of keys\n         * that still do, we empty this out.\n         */\n\n        /*\n         * Return DOES still generate a key event, however.  So rather than using it as the 'click a button' key\n         * as we do with physical keyboards, let's just use it to hide the keyboard.\n         */\n\n        if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {\n            if (SDLActivity.onNativeSoftReturnKey()) {\n                return true;\n            }\n        }\n\n        return super.sendKeyEvent(event);\n    }\n\n    @Override\n    public boolean commitText(CharSequence text, int newCursorPosition) {\n        if (!super.commitText(text, newCursorPosition)) {\n            return false;\n        }\n        updateText();\n        return true;\n    }\n\n    @Override\n    public boolean setComposingText(CharSequence text, int newCursorPosition) {\n        if (!super.setComposingText(text, newCursorPosition)) {\n            return false;\n        }\n        updateText();\n        return true;\n    }\n\n    @Override\n    public boolean deleteSurroundingText(int beforeLength, int afterLength) {\n        if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) {\n            // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection\n            // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265\n            if (beforeLength > 0 && afterLength == 0) {\n                // backspace(s)\n                while (beforeLength-- > 0) {\n                    nativeGenerateScancodeForUnichar('\\b');\n                }\n                return true;\n           }\n        }\n\n        if (!super.deleteSurroundingText(beforeLength, afterLength)) {\n            return false;\n        }\n        updateText();\n        return true;\n    }\n\n    protected void updateText() {\n        final Editable content = getEditable();\n        if (content == null) {\n            return;\n        }\n\n        String text = content.toString();\n        int compareLength = Math.min(text.length(), mCommittedText.length());\n        int matchLength, offset;\n\n        /* Backspace over characters that are no longer in the string */\n        for (matchLength = 0; matchLength < compareLength; ) {\n            int codePoint = mCommittedText.codePointAt(matchLength);\n            if (codePoint != text.codePointAt(matchLength)) {\n                break;\n            }\n            matchLength += Character.charCount(codePoint);\n        }\n        /* FIXME: This doesn't handle graphemes, like '🌬️' */\n        for (offset = matchLength; offset < mCommittedText.length(); ) {\n            int codePoint = mCommittedText.codePointAt(offset);\n            nativeGenerateScancodeForUnichar('\\b');\n            offset += Character.charCount(codePoint);\n        }\n\n        if (matchLength < text.length()) {\n            String pendingText = text.subSequence(matchLength, text.length()).toString();\n            for (offset = 0; offset < pendingText.length(); ) {\n                int codePoint = pendingText.codePointAt(offset);\n                if (codePoint == '\\n') {\n                    if (SDLActivity.onNativeSoftReturnKey()) {\n                        return;\n                    }\n                }\n                /* Higher code points don't generate simulated scancodes */\n                if (codePoint < 128) {\n                    nativeGenerateScancodeForUnichar((char)codePoint);\n                }\n                offset += Character.charCount(codePoint);\n            }\n            SDLInputConnection.nativeCommitText(pendingText, 0);\n        }\n        mCommittedText = text;\n    }\n\n    public static native void nativeCommitText(String text, int newCursorPosition);\n\n    public static native void nativeGenerateScancodeForUnichar(char c);\n}\n\nclass SDLClipboardHandler implements\n    ClipboardManager.OnPrimaryClipChangedListener {\n\n    protected ClipboardManager mClipMgr;\n\n    SDLClipboardHandler() {\n       mClipMgr = (ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);\n       mClipMgr.addPrimaryClipChangedListener(this);\n    }\n\n    public boolean clipboardHasText() {\n       return mClipMgr.hasPrimaryClip();\n    }\n\n    public String clipboardGetText() {\n        ClipData clip = mClipMgr.getPrimaryClip();\n        if (clip != null) {\n            ClipData.Item item = clip.getItemAt(0);\n            if (item != null) {\n                CharSequence text = item.getText();\n                if (text != null) {\n                    return text.toString();\n                }\n            }\n        }\n        return null;\n    }\n\n    public void clipboardSetText(String string) {\n       mClipMgr.removePrimaryClipChangedListener(this);\n       ClipData clip = ClipData.newPlainText(null, string);\n       mClipMgr.setPrimaryClip(clip);\n       mClipMgr.addPrimaryClipChangedListener(this);\n    }\n\n    @Override\n    public void onPrimaryClipChanged() {\n        SDLActivity.onNativeClipboardChanged();\n    }\n}\n\n"
  },
  {
    "path": "arch/android/project/app/src/main/java/org/libsdl/app/SDLAudioManager.java",
    "content": "package org.libsdl.app;\n\nimport android.content.Context;\nimport android.media.AudioDeviceCallback;\nimport android.media.AudioDeviceInfo;\nimport android.media.AudioFormat;\nimport android.media.AudioManager;\nimport android.media.AudioRecord;\nimport android.media.AudioTrack;\nimport android.media.MediaRecorder;\nimport android.os.Build;\nimport android.util.Log;\n\nimport java.util.Arrays;\n\npublic class SDLAudioManager {\n    protected static final String TAG = \"SDLAudio\";\n\n    protected static AudioTrack mAudioTrack;\n    protected static AudioRecord mAudioRecord;\n    protected static Context mContext;\n\n    private static final int[] NO_DEVICES = {};\n\n    private static AudioDeviceCallback mAudioDeviceCallback;\n\n    public static void initialize() {\n        mAudioTrack = null;\n        mAudioRecord = null;\n        mAudioDeviceCallback = null;\n\n        if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)\n        {\n            mAudioDeviceCallback = new AudioDeviceCallback() {\n                @Override\n                public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {\n                    for (AudioDeviceInfo deviceInfo : addedDevices) {\n                        addAudioDevice(deviceInfo.isSink(), deviceInfo.getId());\n                    }\n                }\n\n                @Override\n                public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {\n                    for (AudioDeviceInfo deviceInfo : removedDevices) {\n                        removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId());\n                    }\n                }\n            };\n        }\n    }\n\n    public static void setContext(Context context) {\n        mContext = context;\n        if (context != null) {\n            registerAudioDeviceCallback();\n        }\n    }\n\n    public static void release(Context context) {\n        unregisterAudioDeviceCallback(context);\n    }\n\n    // Audio\n\n    protected static String getAudioFormatString(int audioFormat) {\n        switch (audioFormat) {\n            case AudioFormat.ENCODING_PCM_8BIT:\n                return \"8-bit\";\n            case AudioFormat.ENCODING_PCM_16BIT:\n                return \"16-bit\";\n            case AudioFormat.ENCODING_PCM_FLOAT:\n                return \"float\";\n            default:\n                return Integer.toString(audioFormat);\n        }\n    }\n\n    protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {\n        int channelConfig;\n        int sampleSize;\n        int frameSize;\n\n        Log.v(TAG, \"Opening \" + (isCapture ? \"capture\" : \"playback\") + \", requested \" + desiredFrames + \" frames of \" + desiredChannels + \" channel \" + getAudioFormatString(audioFormat) + \" audio at \" + sampleRate + \" Hz\");\n\n        /* On older devices let's use known good settings */\n        if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {\n            if (desiredChannels > 2) {\n                desiredChannels = 2;\n            }\n        }\n\n        /* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */\n        if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {\n            if (sampleRate < 8000) {\n                sampleRate = 8000;\n            } else if (sampleRate > 48000) {\n                sampleRate = 48000;\n            }\n        }\n\n        if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {\n            int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);\n            if (Build.VERSION.SDK_INT < minSDKVersion) {\n                audioFormat = AudioFormat.ENCODING_PCM_16BIT;\n            }\n        }\n        switch (audioFormat)\n        {\n        case AudioFormat.ENCODING_PCM_8BIT:\n            sampleSize = 1;\n            break;\n        case AudioFormat.ENCODING_PCM_16BIT:\n            sampleSize = 2;\n            break;\n        case AudioFormat.ENCODING_PCM_FLOAT:\n            sampleSize = 4;\n            break;\n        default:\n            Log.v(TAG, \"Requested format \" + audioFormat + \", getting ENCODING_PCM_16BIT\");\n            audioFormat = AudioFormat.ENCODING_PCM_16BIT;\n            sampleSize = 2;\n            break;\n        }\n\n        if (isCapture) {\n            switch (desiredChannels) {\n            case 1:\n                channelConfig = AudioFormat.CHANNEL_IN_MONO;\n                break;\n            case 2:\n                channelConfig = AudioFormat.CHANNEL_IN_STEREO;\n                break;\n            default:\n                Log.v(TAG, \"Requested \" + desiredChannels + \" channels, getting stereo\");\n                desiredChannels = 2;\n                channelConfig = AudioFormat.CHANNEL_IN_STEREO;\n                break;\n            }\n        } else {\n            switch (desiredChannels) {\n            case 1:\n                channelConfig = AudioFormat.CHANNEL_OUT_MONO;\n                break;\n            case 2:\n                channelConfig = AudioFormat.CHANNEL_OUT_STEREO;\n                break;\n            case 3:\n                channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;\n                break;\n            case 4:\n                channelConfig = AudioFormat.CHANNEL_OUT_QUAD;\n                break;\n            case 5:\n                channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;\n                break;\n            case 6:\n                channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;\n                break;\n            case 7:\n                channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;\n                break;\n            case 8:\n                if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {\n                    channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;\n                } else {\n                    Log.v(TAG, \"Requested \" + desiredChannels + \" channels, getting 5.1 surround\");\n                    desiredChannels = 6;\n                    channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;\n                }\n                break;\n            default:\n                Log.v(TAG, \"Requested \" + desiredChannels + \" channels, getting stereo\");\n                desiredChannels = 2;\n                channelConfig = AudioFormat.CHANNEL_OUT_STEREO;\n                break;\n            }\n\n/*\n            Log.v(TAG, \"Speaker configuration (and order of channels):\");\n\n            if ((channelConfig & 0x00000004) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_LEFT\");\n            }\n            if ((channelConfig & 0x00000008) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_RIGHT\");\n            }\n            if ((channelConfig & 0x00000010) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_CENTER\");\n            }\n            if ((channelConfig & 0x00000020) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_LOW_FREQUENCY\");\n            }\n            if ((channelConfig & 0x00000040) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_BACK_LEFT\");\n            }\n            if ((channelConfig & 0x00000080) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_BACK_RIGHT\");\n            }\n            if ((channelConfig & 0x00000100) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_LEFT_OF_CENTER\");\n            }\n            if ((channelConfig & 0x00000200) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_FRONT_RIGHT_OF_CENTER\");\n            }\n            if ((channelConfig & 0x00000400) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_BACK_CENTER\");\n            }\n            if ((channelConfig & 0x00000800) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_SIDE_LEFT\");\n            }\n            if ((channelConfig & 0x00001000) != 0) {\n                Log.v(TAG, \"   CHANNEL_OUT_SIDE_RIGHT\");\n            }\n*/\n        }\n        frameSize = (sampleSize * desiredChannels);\n\n        // Let the user pick a larger buffer if they really want -- but ye\n        // gods they probably shouldn't, the minimums are horrifyingly high\n        // latency already\n        int minBufferSize;\n        if (isCapture) {\n            minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);\n        } else {\n            minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);\n        }\n        desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);\n\n        int[] results = new int[4];\n\n        if (isCapture) {\n            if (mAudioRecord == null) {\n                mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,\n                        channelConfig, audioFormat, desiredFrames * frameSize);\n\n                // see notes about AudioTrack state in audioOpen(), above. Probably also applies here.\n                if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {\n                    Log.e(TAG, \"Failed during initialization of AudioRecord\");\n                    mAudioRecord.release();\n                    mAudioRecord = null;\n                    return null;\n                }\n\n                if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {\n                    mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));\n                }\n\n                mAudioRecord.startRecording();\n            }\n\n            results[0] = mAudioRecord.getSampleRate();\n            results[1] = mAudioRecord.getAudioFormat();\n            results[2] = mAudioRecord.getChannelCount();\n\n        } else {\n            if (mAudioTrack == null) {\n                mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);\n\n                // Instantiating AudioTrack can \"succeed\" without an exception and the track may still be invalid\n                // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java\n                // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()\n                if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {\n                    /* Try again, with safer values */\n\n                    Log.e(TAG, \"Failed during initialization of Audio Track\");\n                    mAudioTrack.release();\n                    mAudioTrack = null;\n                    return null;\n                }\n\n                if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {\n                    mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));\n                }\n\n                mAudioTrack.play();\n            }\n\n            results[0] = mAudioTrack.getSampleRate();\n            results[1] = mAudioTrack.getAudioFormat();\n            results[2] = mAudioTrack.getChannelCount();\n        }\n        results[3] = desiredFrames;\n\n        Log.v(TAG, \"Opening \" + (isCapture ? \"capture\" : \"playback\") + \", got \" + results[3] + \" frames of \" + results[2] + \" channel \" + getAudioFormatString(results[1]) + \" audio at \" + results[0] + \" Hz\");\n\n        return results;\n    }\n\n    private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            for (AudioDeviceInfo info : audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)) {\n                if (info.getId() == deviceId) {\n                    return info;\n                }\n            }\n            return null;\n        } else {\n            return null;\n        }\n    }\n\n    private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            for (AudioDeviceInfo info : audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)) {\n                if (info.getId() == deviceId) {\n                    return info;\n                }\n            }\n            return null;\n        } else {\n            return null;\n        }\n    }\n\n    private static void registerAudioDeviceCallback() {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);\n        }\n    }\n\n    private static void unregisterAudioDeviceCallback(Context context) {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);\n            audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int[] getAudioOutputDevices() {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);\n            int[] deviceIds = new int[deviceInfos.length];\n            for (int i = 0; i < deviceInfos.length; i++) {\n                deviceIds[i] = deviceInfos[i].getId();\n            }\n            return deviceIds;\n        } else {\n            return NO_DEVICES;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int[] getAudioInputDevices() {\n        if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);\n            AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);\n            int[] deviceIds = new int[deviceInfos.length];\n            for (int i = 0; i < deviceInfos.length; i++) {\n                deviceIds[i] = deviceInfos[i].getId();\n            }\n            return deviceIds;\n        } else {\n            return NO_DEVICES;\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {\n        return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void audioWriteFloatBuffer(float[] buffer) {\n        if (mAudioTrack == null) {\n            Log.e(TAG, \"Attempted to make audio call with uninitialized audio!\");\n            return;\n        }\n\n        if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {\n            Log.e(TAG, \"Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)\");\n            return;\n        }\n\n        for (int i = 0; i < buffer.length;) {\n            int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);\n            if (result > 0) {\n                i += result;\n            } else if (result == 0) {\n                try {\n                    Thread.sleep(1);\n                } catch(InterruptedException e) {\n                    // Nom nom\n                }\n            } else {\n                Log.w(TAG, \"SDL audio: error return from write(float)\");\n                return;\n            }\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void audioWriteShortBuffer(short[] buffer) {\n        if (mAudioTrack == null) {\n            Log.e(TAG, \"Attempted to make audio call with uninitialized audio!\");\n            return;\n        }\n\n        for (int i = 0; i < buffer.length;) {\n            int result = mAudioTrack.write(buffer, i, buffer.length - i);\n            if (result > 0) {\n                i += result;\n            } else if (result == 0) {\n                try {\n                    Thread.sleep(1);\n                } catch(InterruptedException e) {\n                    // Nom nom\n                }\n            } else {\n                Log.w(TAG, \"SDL audio: error return from write(short)\");\n                return;\n            }\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void audioWriteByteBuffer(byte[] buffer) {\n        if (mAudioTrack == null) {\n            Log.e(TAG, \"Attempted to make audio call with uninitialized audio!\");\n            return;\n        }\n\n        for (int i = 0; i < buffer.length; ) {\n            int result = mAudioTrack.write(buffer, i, buffer.length - i);\n            if (result > 0) {\n                i += result;\n            } else if (result == 0) {\n                try {\n                    Thread.sleep(1);\n                } catch(InterruptedException e) {\n                    // Nom nom\n                }\n            } else {\n                Log.w(TAG, \"SDL audio: error return from write(byte)\");\n                return;\n            }\n        }\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {\n        return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            return 0;\n        } else {\n            return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static int captureReadShortBuffer(short[] buffer, boolean blocking) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            return mAudioRecord.read(buffer, 0, buffer.length);\n        } else {\n            return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {\n        if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {\n            return mAudioRecord.read(buffer, 0, buffer.length);\n        } else {\n            return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static void audioClose() {\n        if (mAudioTrack != null) {\n            mAudioTrack.stop();\n            mAudioTrack.release();\n            mAudioTrack = null;\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static void captureClose() {\n        if (mAudioRecord != null) {\n            mAudioRecord.stop();\n            mAudioRecord.release();\n            mAudioRecord = null;\n        }\n    }\n\n    /** This method is called by SDL using JNI. */\n    public static void audioSetThreadPriority(boolean iscapture, int device_id) {\n        try {\n\n            /* Set thread name */\n            if (iscapture) {\n                Thread.currentThread().setName(\"SDLAudioC\" + device_id);\n            } else {\n                Thread.currentThread().setName(\"SDLAudioP\" + device_id);\n            }\n\n            /* Set thread priority */\n            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);\n\n        } catch (Exception e) {\n            Log.v(TAG, \"modify thread properties failed \" + e.toString());\n        }\n    }\n\n    public static native int nativeSetupJNI();\n\n    public static native void removeAudioDevice(boolean isCapture, int deviceId);\n\n    public static native void addAudioDevice(boolean isCapture, int deviceId);\n\n}\n"
  },
  {
    "path": "arch/android/project/app/src/main/java/org/libsdl/app/SDLControllerManager.java",
    "content": "package org.libsdl.app;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.VibrationEffect;\nimport android.os.Vibrator;\nimport android.util.Log;\nimport android.view.InputDevice;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.View;\n\n\npublic class SDLControllerManager\n{\n\n    public static native int nativeSetupJNI();\n\n    public static native int nativeAddJoystick(int device_id, String name, String desc,\n                                               int vendor_id, int product_id,\n                                               boolean is_accelerometer, int button_mask,\n                                               int naxes, int axis_mask, int nhats, int nballs);\n    public static native int nativeRemoveJoystick(int device_id);\n    public static native int nativeAddHaptic(int device_id, String name);\n    public static native int nativeRemoveHaptic(int device_id);\n    public static native int onNativePadDown(int device_id, int keycode);\n    public static native int onNativePadUp(int device_id, int keycode);\n    public static native void onNativeJoy(int device_id, int axis,\n                                          float value);\n    public static native void onNativeHat(int device_id, int hat_id,\n                                          int x, int y);\n\n    protected static SDLJoystickHandler mJoystickHandler;\n    protected static SDLHapticHandler mHapticHandler;\n\n    private static final String TAG = \"SDLControllerManager\";\n\n    public static void initialize() {\n        if (mJoystickHandler == null) {\n            if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {\n                mJoystickHandler = new SDLJoystickHandler_API19();\n            } else {\n                mJoystickHandler = new SDLJoystickHandler_API16();\n            }\n        }\n\n        if (mHapticHandler == null) {\n            if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {\n                mHapticHandler = new SDLHapticHandler_API26();\n            } else {\n                mHapticHandler = new SDLHapticHandler();\n            }\n        }\n    }\n\n    // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance\n    public static boolean handleJoystickMotionEvent(MotionEvent event) {\n        return mJoystickHandler.handleMotionEvent(event);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void pollInputDevices() {\n        mJoystickHandler.pollInputDevices();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void pollHapticDevices() {\n        mHapticHandler.pollHapticDevices();\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void hapticRun(int device_id, float intensity, int length) {\n        mHapticHandler.run(device_id, intensity, length);\n    }\n\n    /**\n     * This method is called by SDL using JNI.\n     */\n    public static void hapticStop(int device_id)\n    {\n        mHapticHandler.stop(device_id);\n    }\n\n    // Check if a given device is considered a possible SDL joystick\n    public static boolean isDeviceSDLJoystick(int deviceId) {\n        InputDevice device = InputDevice.getDevice(deviceId);\n        // We cannot use InputDevice.isVirtual before API 16, so let's accept\n        // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)\n        if ((device == null) || (deviceId < 0)) {\n            return false;\n        }\n        int sources = device.getSources();\n\n        /* This is called for every button press, so let's not spam the logs */\n        /*\n        if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {\n            Log.v(TAG, \"Input device \" + device.getName() + \" has class joystick.\");\n        }\n        if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {\n            Log.v(TAG, \"Input device \" + device.getName() + \" is a dpad.\");\n        }\n        if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {\n            Log.v(TAG, \"Input device \" + device.getName() + \" is a gamepad.\");\n        }\n        */\n\n        return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||\n                ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||\n                ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)\n        );\n    }\n\n}\n\nclass SDLJoystickHandler {\n\n    /**\n     * Handles given MotionEvent.\n     * @param event the event to be handled.\n     * @return if given event was processed.\n     */\n    public boolean handleMotionEvent(MotionEvent event) {\n        return false;\n    }\n\n    /**\n     * Handles adding and removing of input devices.\n     */\n    public void pollInputDevices() {\n    }\n}\n\n/* Actual joystick functionality available for API >= 12 devices */\nclass SDLJoystickHandler_API16 extends SDLJoystickHandler {\n\n    static class SDLJoystick {\n        public int device_id;\n        public String name;\n        public String desc;\n        public ArrayList<InputDevice.MotionRange> axes;\n        public ArrayList<InputDevice.MotionRange> hats;\n    }\n    static class RangeComparator implements Comparator<InputDevice.MotionRange> {\n        @Override\n        public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {\n            // Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL\n            int arg0Axis = arg0.getAxis();\n            int arg1Axis = arg1.getAxis();\n            if (arg0Axis == MotionEvent.AXIS_GAS) {\n                arg0Axis = MotionEvent.AXIS_BRAKE;\n            } else if (arg0Axis == MotionEvent.AXIS_BRAKE) {\n                arg0Axis = MotionEvent.AXIS_GAS;\n            }\n            if (arg1Axis == MotionEvent.AXIS_GAS) {\n                arg1Axis = MotionEvent.AXIS_BRAKE;\n            } else if (arg1Axis == MotionEvent.AXIS_BRAKE) {\n                arg1Axis = MotionEvent.AXIS_GAS;\n            }\n\n            // Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.\n            // This is because the usual pairing are:\n            // - AXIS_X + AXIS_Y (left stick).\n            // - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).\n            // - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).\n            // This sorts the axes in the above order, which tends to be correct\n            // for Xbox-ish game pads that have the right stick on RX/RY and the\n            // triggers on Z/RZ.\n            //\n            // Gamepads that don't have AXIS_Z/AXIS_RZ but use\n            // AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.\n            //\n            // References:\n            // - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input\n            // - https://www.kernel.org/doc/html/latest/input/gamepad.html\n            if (arg0Axis == MotionEvent.AXIS_Z) {\n                arg0Axis = MotionEvent.AXIS_RZ - 1;\n            } else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {\n                --arg0Axis;\n            }\n            if (arg1Axis == MotionEvent.AXIS_Z) {\n                arg1Axis = MotionEvent.AXIS_RZ - 1;\n            } else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {\n                --arg1Axis;\n            }\n\n            return arg0Axis - arg1Axis;\n        }\n    }\n\n    private final ArrayList<SDLJoystick> mJoysticks;\n\n    public SDLJoystickHandler_API16() {\n\n        mJoysticks = new ArrayList<SDLJoystick>();\n    }\n\n    @Override\n    public void pollInputDevices() {\n        int[] deviceIds = InputDevice.getDeviceIds();\n\n        for (int device_id : deviceIds) {\n            if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {\n                SDLJoystick joystick = getJoystick(device_id);\n                if (joystick == null) {\n                    InputDevice joystickDevice = InputDevice.getDevice(device_id);\n                    joystick = new SDLJoystick();\n                    joystick.device_id = device_id;\n                    joystick.name = joystickDevice.getName();\n                    joystick.desc = getJoystickDescriptor(joystickDevice);\n                    joystick.axes = new ArrayList<InputDevice.MotionRange>();\n                    joystick.hats = new ArrayList<InputDevice.MotionRange>();\n\n                    List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();\n                    Collections.sort(ranges, new RangeComparator());\n                    for (InputDevice.MotionRange range : ranges) {\n                        if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {\n                            if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {\n                                joystick.hats.add(range);\n                            } else {\n                                joystick.axes.add(range);\n                            }\n                        }\n                    }\n\n                    mJoysticks.add(joystick);\n                    SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,\n                            getVendorId(joystickDevice), getProductId(joystickDevice), false,\n                            getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);\n                }\n            }\n        }\n\n        /* Check removed devices */\n        ArrayList<Integer> removedDevices = null;\n        for (SDLJoystick joystick : mJoysticks) {\n            int device_id = joystick.device_id;\n            int i;\n            for (i = 0; i < deviceIds.length; i++) {\n                if (device_id == deviceIds[i]) break;\n            }\n            if (i == deviceIds.length) {\n                if (removedDevices == null) {\n                    removedDevices = new ArrayList<Integer>();\n                }\n                removedDevices.add(device_id);\n            }\n        }\n\n        if (removedDevices != null) {\n            for (int device_id : removedDevices) {\n                SDLControllerManager.nativeRemoveJoystick(device_id);\n                for (int i = 0; i < mJoysticks.size(); i++) {\n                    if (mJoysticks.get(i).device_id == device_id) {\n                        mJoysticks.remove(i);\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    protected SDLJoystick getJoystick(int device_id) {\n        for (SDLJoystick joystick : mJoysticks) {\n            if (joystick.device_id == device_id) {\n                return joystick;\n            }\n        }\n        return null;\n    }\n\n    @Override\n    public boolean handleMotionEvent(MotionEvent event) {\n        int actionPointerIndex = event.getActionIndex();\n        int action = event.getActionMasked();\n        if (action == MotionEvent.ACTION_MOVE) {\n            SDLJoystick joystick = getJoystick(event.getDeviceId());\n            if (joystick != null) {\n                for (int i = 0; i < joystick.axes.size(); i++) {\n                    InputDevice.MotionRange range = joystick.axes.get(i);\n                    /* Normalize the value to -1...1 */\n                    float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;\n                    SDLControllerManager.onNativeJoy(joystick.device_id, i, value);\n                }\n                for (int i = 0; i < joystick.hats.size() / 2; i++) {\n                    int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));\n                    int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));\n                    SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);\n                }\n            }\n        }\n        return true;\n    }\n\n    public String getJoystickDescriptor(InputDevice joystickDevice) {\n        String desc = joystickDevice.getDescriptor();\n\n        if (desc != null && !desc.isEmpty()) {\n            return desc;\n        }\n\n        return joystickDevice.getName();\n    }\n    public int getProductId(InputDevice joystickDevice) {\n        return 0;\n    }\n    public int getVendorId(InputDevice joystickDevice) {\n        return 0;\n    }\n    public int getAxisMask(List<InputDevice.MotionRange> ranges) {\n        return -1;\n    }\n    public int getButtonMask(InputDevice joystickDevice) {\n        return -1;\n    }\n}\n\nclass SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {\n\n    @Override\n    public int getProductId(InputDevice joystickDevice) {\n        return joystickDevice.getProductId();\n    }\n\n    @Override\n    public int getVendorId(InputDevice joystickDevice) {\n        return joystickDevice.getVendorId();\n    }\n\n    @Override\n    public int getAxisMask(List<InputDevice.MotionRange> ranges) {\n        // For compatibility, keep computing the axis mask like before,\n        // only really distinguishing 2, 4 and 6 axes.\n        int axis_mask = 0;\n        if (ranges.size() >= 2) {\n            // ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))\n            axis_mask |= 0x0003;\n        }\n        if (ranges.size() >= 4) {\n            // ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))\n            axis_mask |= 0x000c;\n        }\n        if (ranges.size() >= 6) {\n            // ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))\n            axis_mask |= 0x0030;\n        }\n        // Also add an indicator bit for whether the sorting order has changed.\n        // This serves to disable outdated gamecontrollerdb.txt mappings.\n        boolean have_z = false;\n        boolean have_past_z_before_rz = false;\n        for (InputDevice.MotionRange range : ranges) {\n            int axis = range.getAxis();\n            if (axis == MotionEvent.AXIS_Z) {\n                have_z = true;\n            } else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {\n                have_past_z_before_rz = true;\n            }\n        }\n        if (have_z && have_past_z_before_rz) {\n            // If both these exist, the compare() function changed sorting order.\n            // Set a bit to indicate this fact.\n            axis_mask |= 0x8000;\n        }\n        return axis_mask;\n    }\n\n    @Override\n    public int getButtonMask(InputDevice joystickDevice) {\n        int button_mask = 0;\n        int[] keys = new int[] {\n            KeyEvent.KEYCODE_BUTTON_A,\n            KeyEvent.KEYCODE_BUTTON_B,\n            KeyEvent.KEYCODE_BUTTON_X,\n            KeyEvent.KEYCODE_BUTTON_Y,\n            KeyEvent.KEYCODE_BACK,\n            KeyEvent.KEYCODE_MENU,\n            KeyEvent.KEYCODE_BUTTON_MODE,\n            KeyEvent.KEYCODE_BUTTON_START,\n            KeyEvent.KEYCODE_BUTTON_THUMBL,\n            KeyEvent.KEYCODE_BUTTON_THUMBR,\n            KeyEvent.KEYCODE_BUTTON_L1,\n            KeyEvent.KEYCODE_BUTTON_R1,\n            KeyEvent.KEYCODE_DPAD_UP,\n            KeyEvent.KEYCODE_DPAD_DOWN,\n            KeyEvent.KEYCODE_DPAD_LEFT,\n            KeyEvent.KEYCODE_DPAD_RIGHT,\n            KeyEvent.KEYCODE_BUTTON_SELECT,\n            KeyEvent.KEYCODE_DPAD_CENTER,\n\n            // These don't map into any SDL controller buttons directly\n            KeyEvent.KEYCODE_BUTTON_L2,\n            KeyEvent.KEYCODE_BUTTON_R2,\n            KeyEvent.KEYCODE_BUTTON_C,\n            KeyEvent.KEYCODE_BUTTON_Z,\n            KeyEvent.KEYCODE_BUTTON_1,\n            KeyEvent.KEYCODE_BUTTON_2,\n            KeyEvent.KEYCODE_BUTTON_3,\n            KeyEvent.KEYCODE_BUTTON_4,\n            KeyEvent.KEYCODE_BUTTON_5,\n            KeyEvent.KEYCODE_BUTTON_6,\n            KeyEvent.KEYCODE_BUTTON_7,\n            KeyEvent.KEYCODE_BUTTON_8,\n            KeyEvent.KEYCODE_BUTTON_9,\n            KeyEvent.KEYCODE_BUTTON_10,\n            KeyEvent.KEYCODE_BUTTON_11,\n            KeyEvent.KEYCODE_BUTTON_12,\n            KeyEvent.KEYCODE_BUTTON_13,\n            KeyEvent.KEYCODE_BUTTON_14,\n            KeyEvent.KEYCODE_BUTTON_15,\n            KeyEvent.KEYCODE_BUTTON_16,\n        };\n        int[] masks = new int[] {\n            (1 << 0),   // A -> A\n            (1 << 1),   // B -> B\n            (1 << 2),   // X -> X\n            (1 << 3),   // Y -> Y\n            (1 << 4),   // BACK -> BACK\n            (1 << 6),   // MENU -> START\n            (1 << 5),   // MODE -> GUIDE\n            (1 << 6),   // START -> START\n            (1 << 7),   // THUMBL -> LEFTSTICK\n            (1 << 8),   // THUMBR -> RIGHTSTICK\n            (1 << 9),   // L1 -> LEFTSHOULDER\n            (1 << 10),  // R1 -> RIGHTSHOULDER\n            (1 << 11),  // DPAD_UP -> DPAD_UP\n            (1 << 12),  // DPAD_DOWN -> DPAD_DOWN\n            (1 << 13),  // DPAD_LEFT -> DPAD_LEFT\n            (1 << 14),  // DPAD_RIGHT -> DPAD_RIGHT\n            (1 << 4),   // SELECT -> BACK\n            (1 << 0),   // DPAD_CENTER -> A\n            (1 << 15),  // L2 -> ??\n            (1 << 16),  // R2 -> ??\n            (1 << 17),  // C -> ??\n            (1 << 18),  // Z -> ??\n            (1 << 20),  // 1 -> ??\n            (1 << 21),  // 2 -> ??\n            (1 << 22),  // 3 -> ??\n            (1 << 23),  // 4 -> ??\n            (1 << 24),  // 5 -> ??\n            (1 << 25),  // 6 -> ??\n            (1 << 26),  // 7 -> ??\n            (1 << 27),  // 8 -> ??\n            (1 << 28),  // 9 -> ??\n            (1 << 29),  // 10 -> ??\n            (1 << 30),  // 11 -> ??\n            (1 << 31),  // 12 -> ??\n            // We're out of room...\n            0xFFFFFFFF,  // 13 -> ??\n            0xFFFFFFFF,  // 14 -> ??\n            0xFFFFFFFF,  // 15 -> ??\n            0xFFFFFFFF,  // 16 -> ??\n        };\n        boolean[] has_keys = joystickDevice.hasKeys(keys);\n        for (int i = 0; i < keys.length; ++i) {\n            if (has_keys[i]) {\n                button_mask |= masks[i];\n            }\n        }\n        return button_mask;\n    }\n}\n\nclass SDLHapticHandler_API26 extends SDLHapticHandler {\n    @Override\n    public void run(int device_id, float intensity, int length) {\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            Log.d(\"SDL\", \"Rtest: Vibe with intensity \" + intensity + \" for \" + length);\n            if (intensity == 0.0f) {\n                stop(device_id);\n                return;\n            }\n\n            int vibeValue = Math.round(intensity * 255);\n\n            if (vibeValue > 255) {\n                vibeValue = 255;\n            }\n            if (vibeValue < 1) {\n                stop(device_id);\n                return;\n            }\n            try {\n                haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));\n            }\n            catch (Exception e) {\n                // Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if\n                // something went horribly wrong with the Android 8.0 APIs.\n                haptic.vib.vibrate(length);\n            }\n        }\n    }\n}\n\nclass SDLHapticHandler {\n\n    static class SDLHaptic {\n        public int device_id;\n        public String name;\n        public Vibrator vib;\n    }\n\n    private final ArrayList<SDLHaptic> mHaptics;\n\n    public SDLHapticHandler() {\n        mHaptics = new ArrayList<SDLHaptic>();\n    }\n\n    public void run(int device_id, float intensity, int length) {\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            haptic.vib.vibrate(length);\n        }\n    }\n\n    public void stop(int device_id) {\n        SDLHaptic haptic = getHaptic(device_id);\n        if (haptic != null) {\n            haptic.vib.cancel();\n        }\n    }\n\n    public void pollHapticDevices() {\n\n        final int deviceId_VIBRATOR_SERVICE = 999999;\n        boolean hasVibratorService = false;\n\n        int[] deviceIds = InputDevice.getDeviceIds();\n        // It helps processing the device ids in reverse order\n        // For example, in the case of the XBox 360 wireless dongle,\n        // so the first controller seen by SDL matches what the receiver\n        // considers to be the first controller\n\n        for (int i = deviceIds.length - 1; i > -1; i--) {\n            SDLHaptic haptic = getHaptic(deviceIds[i]);\n            if (haptic == null) {\n                InputDevice device = InputDevice.getDevice(deviceIds[i]);\n                Vibrator vib = device.getVibrator();\n                if (vib != null) {\n                    if (vib.hasVibrator()) {\n                        haptic = new SDLHaptic();\n                        haptic.device_id = deviceIds[i];\n                        haptic.name = device.getName();\n                        haptic.vib = vib;\n                        mHaptics.add(haptic);\n                        SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);\n                    }\n                }\n            }\n        }\n\n        /* Check VIBRATOR_SERVICE */\n        Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);\n        if (vib != null) {\n            hasVibratorService = vib.hasVibrator();\n\n            if (hasVibratorService) {\n                SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);\n                if (haptic == null) {\n                    haptic = new SDLHaptic();\n                    haptic.device_id = deviceId_VIBRATOR_SERVICE;\n                    haptic.name = \"VIBRATOR_SERVICE\";\n                    haptic.vib = vib;\n                    mHaptics.add(haptic);\n                    SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);\n                }\n            }\n        }\n\n        /* Check removed devices */\n        ArrayList<Integer> removedDevices = null;\n        for (SDLHaptic haptic : mHaptics) {\n            int device_id = haptic.device_id;\n            int i;\n            for (i = 0; i < deviceIds.length; i++) {\n                if (device_id == deviceIds[i]) break;\n            }\n\n            if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {\n                if (i == deviceIds.length) {\n                    if (removedDevices == null) {\n                        removedDevices = new ArrayList<Integer>();\n                    }\n                    removedDevices.add(device_id);\n                }\n            }  // else: don't remove the vibrator if it is still present\n        }\n\n        if (removedDevices != null) {\n            for (int device_id : removedDevices) {\n                SDLControllerManager.nativeRemoveHaptic(device_id);\n                for (int i = 0; i < mHaptics.size(); i++) {\n                    if (mHaptics.get(i).device_id == device_id) {\n                        mHaptics.remove(i);\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    protected SDLHaptic getHaptic(int device_id) {\n        for (SDLHaptic haptic : mHaptics) {\n            if (haptic.device_id == device_id) {\n                return haptic;\n            }\n        }\n        return null;\n    }\n}\n\nclass SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {\n    // Generic Motion (mouse hover, joystick...) events go here\n    @Override\n    public boolean onGenericMotion(View v, MotionEvent event) {\n        float x, y;\n        int action;\n\n        switch ( event.getSource() ) {\n            case InputDevice.SOURCE_JOYSTICK:\n                return SDLControllerManager.handleJoystickMotionEvent(event);\n\n            case InputDevice.SOURCE_MOUSE:\n                action = event.getActionMasked();\n                switch (action) {\n                    case MotionEvent.ACTION_SCROLL:\n                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);\n                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    case MotionEvent.ACTION_HOVER_MOVE:\n                        x = event.getX(0);\n                        y = event.getY(0);\n\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    default:\n                        break;\n                }\n                break;\n\n            default:\n                break;\n        }\n\n        // Event was not managed\n        return false;\n    }\n\n    public boolean supportsRelativeMouse() {\n        return false;\n    }\n\n    public boolean inRelativeMode() {\n        return false;\n    }\n\n    public boolean setRelativeMouseEnabled(boolean enabled) {\n        return false;\n    }\n\n    public void reclaimRelativeMouseModeIfNeeded()\n    {\n\n    }\n\n    public float getEventX(MotionEvent event) {\n        return event.getX(0);\n    }\n\n    public float getEventY(MotionEvent event) {\n        return event.getY(0);\n    }\n\n}\n\nclass SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {\n    // Generic Motion (mouse hover, joystick...) events go here\n\n    private boolean mRelativeModeEnabled;\n\n    @Override\n    public boolean onGenericMotion(View v, MotionEvent event) {\n\n        // Handle relative mouse mode\n        if (mRelativeModeEnabled) {\n            if (event.getSource() == InputDevice.SOURCE_MOUSE) {\n                int action = event.getActionMasked();\n                if (action == MotionEvent.ACTION_HOVER_MOVE) {\n                    float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);\n                    float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);\n                    SDLActivity.onNativeMouse(0, action, x, y, true);\n                    return true;\n                }\n            }\n        }\n\n        // Event was not managed, call SDLGenericMotionListener_API12 method\n        return super.onGenericMotion(v, event);\n    }\n\n    @Override\n    public boolean supportsRelativeMouse() {\n        return true;\n    }\n\n    @Override\n    public boolean inRelativeMode() {\n        return mRelativeModeEnabled;\n    }\n\n    @Override\n    public boolean setRelativeMouseEnabled(boolean enabled) {\n        mRelativeModeEnabled = enabled;\n        return true;\n    }\n\n    @Override\n    public float getEventX(MotionEvent event) {\n        if (mRelativeModeEnabled) {\n            return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);\n        } else {\n            return event.getX(0);\n        }\n    }\n\n    @Override\n    public float getEventY(MotionEvent event) {\n        if (mRelativeModeEnabled) {\n            return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);\n        } else {\n            return event.getY(0);\n        }\n    }\n}\n\nclass SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {\n    // Generic Motion (mouse hover, joystick...) events go here\n    private boolean mRelativeModeEnabled;\n\n    @Override\n    public boolean onGenericMotion(View v, MotionEvent event) {\n        float x, y;\n        int action;\n\n        switch ( event.getSource() ) {\n            case InputDevice.SOURCE_JOYSTICK:\n                return SDLControllerManager.handleJoystickMotionEvent(event);\n\n            case InputDevice.SOURCE_MOUSE:\n            // DeX desktop mouse cursor is a separate non-standard input type.\n            case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:\n                action = event.getActionMasked();\n                switch (action) {\n                    case MotionEvent.ACTION_SCROLL:\n                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);\n                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    case MotionEvent.ACTION_HOVER_MOVE:\n                        x = event.getX(0);\n                        y = event.getY(0);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    default:\n                        break;\n                }\n                break;\n\n            case InputDevice.SOURCE_MOUSE_RELATIVE:\n                action = event.getActionMasked();\n                switch (action) {\n                    case MotionEvent.ACTION_SCROLL:\n                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);\n                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);\n                        SDLActivity.onNativeMouse(0, action, x, y, false);\n                        return true;\n\n                    case MotionEvent.ACTION_HOVER_MOVE:\n                        x = event.getX(0);\n                        y = event.getY(0);\n                        SDLActivity.onNativeMouse(0, action, x, y, true);\n                        return true;\n\n                    default:\n                        break;\n                }\n                break;\n\n            default:\n                break;\n        }\n\n        // Event was not managed\n        return false;\n    }\n\n    @Override\n    public boolean supportsRelativeMouse() {\n        return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);\n    }\n\n    @Override\n    public boolean inRelativeMode() {\n        return mRelativeModeEnabled;\n    }\n\n    @Override\n    public boolean setRelativeMouseEnabled(boolean enabled) {\n        if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {\n            if (enabled) {\n                SDLActivity.getContentView().requestPointerCapture();\n            } else {\n                SDLActivity.getContentView().releasePointerCapture();\n            }\n            mRelativeModeEnabled = enabled;\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    @Override\n    public void reclaimRelativeMouseModeIfNeeded()\n    {\n        if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {\n            SDLActivity.getContentView().requestPointerCapture();\n        }\n    }\n\n    @Override\n    public float getEventX(MotionEvent event) {\n        // Relative mouse in capture mode will only have relative for X/Y\n        return event.getX(0);\n    }\n\n    @Override\n    public float getEventY(MotionEvent event) {\n        // Relative mouse in capture mode will only have relative for X/Y\n        return event.getY(0);\n    }\n}\n"
  },
  {
    "path": "arch/android/project/app/src/main/java/org/libsdl/app/SDLSurface.java",
    "content": "package org.libsdl.app;\n\n\nimport android.content.Context;\nimport android.content.pm.ActivityInfo;\nimport android.hardware.Sensor;\nimport android.hardware.SensorEvent;\nimport android.hardware.SensorEventListener;\nimport android.hardware.SensorManager;\nimport android.os.Build;\nimport android.util.DisplayMetrics;\nimport android.util.Log;\nimport android.view.Display;\nimport android.view.InputDevice;\nimport android.view.KeyEvent;\nimport android.view.MotionEvent;\nimport android.view.Surface;\nimport android.view.SurfaceHolder;\nimport android.view.SurfaceView;\nimport android.view.View;\nimport android.view.WindowManager;\n\n\n/**\n    SDLSurface. This is what we draw on, so we need to know when it's created\n    in order to do anything useful.\n\n    Because of this, that's where we set up the SDL thread\n*/\npublic class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,\n    View.OnKeyListener, View.OnTouchListener, SensorEventListener  {\n\n    // Sensors\n    protected SensorManager mSensorManager;\n    protected Display mDisplay;\n\n    // Keep track of the surface size to normalize touch events\n    protected float mWidth, mHeight;\n\n    // Is SurfaceView ready for rendering\n    public boolean mIsSurfaceReady;\n\n    // Startup\n    public SDLSurface(Context context) {\n        super(context);\n        getHolder().addCallback(this);\n\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        requestFocus();\n        setOnKeyListener(this);\n        setOnTouchListener(this);\n\n        mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();\n        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);\n\n        setOnGenericMotionListener(SDLActivity.getMotionListener());\n\n        // Some arbitrary defaults to avoid a potential division by zero\n        mWidth = 1.0f;\n        mHeight = 1.0f;\n\n        mIsSurfaceReady = false;\n    }\n\n    public void handlePause() {\n        enableSensor(Sensor.TYPE_ACCELEROMETER, false);\n    }\n\n    public void handleResume() {\n        setFocusable(true);\n        setFocusableInTouchMode(true);\n        requestFocus();\n        setOnKeyListener(this);\n        setOnTouchListener(this);\n        enableSensor(Sensor.TYPE_ACCELEROMETER, true);\n    }\n\n    public Surface getNativeSurface() {\n        return getHolder().getSurface();\n    }\n\n    // Called when we have a valid drawing surface\n    @Override\n    public void surfaceCreated(SurfaceHolder holder) {\n        Log.v(\"SDL\", \"surfaceCreated()\");\n        SDLActivity.onNativeSurfaceCreated();\n    }\n\n    // Called when we lose the surface\n    @Override\n    public void surfaceDestroyed(SurfaceHolder holder) {\n        Log.v(\"SDL\", \"surfaceDestroyed()\");\n\n        // Transition to pause, if needed\n        SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;\n        SDLActivity.handleNativeState();\n\n        mIsSurfaceReady = false;\n        SDLActivity.onNativeSurfaceDestroyed();\n    }\n\n    // Called when the surface is resized\n    @Override\n    public void surfaceChanged(SurfaceHolder holder,\n                               int format, int width, int height) {\n        Log.v(\"SDL\", \"surfaceChanged()\");\n\n        if (SDLActivity.mSingleton == null) {\n            return;\n        }\n\n        mWidth = width;\n        mHeight = height;\n        int nDeviceWidth = width;\n        int nDeviceHeight = height;\n        try\n        {\n            if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {\n                DisplayMetrics realMetrics = new DisplayMetrics();\n                mDisplay.getRealMetrics( realMetrics );\n                nDeviceWidth = realMetrics.widthPixels;\n                nDeviceHeight = realMetrics.heightPixels;\n            }\n        } catch(Exception ignored) {\n        }\n\n        synchronized(SDLActivity.getContext()) {\n            // In case we're waiting on a size change after going fullscreen, send a notification.\n            SDLActivity.getContext().notifyAll();\n        }\n\n        Log.v(\"SDL\", \"Window size: \" + width + \"x\" + height);\n        Log.v(\"SDL\", \"Device size: \" + nDeviceWidth + \"x\" + nDeviceHeight);\n        SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());\n        SDLActivity.onNativeResize();\n\n        // Prevent a screen distortion glitch,\n        // for instance when the device is in Landscape and a Portrait App is resumed.\n        boolean skip = false;\n        int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();\n\n        if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {\n            if (mWidth > mHeight) {\n               skip = true;\n            }\n        } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {\n            if (mWidth < mHeight) {\n               skip = true;\n            }\n        }\n\n        // Special Patch for Square Resolution: Black Berry Passport\n        if (skip) {\n           double min = Math.min(mWidth, mHeight);\n           double max = Math.max(mWidth, mHeight);\n\n           if (max / min < 1.20) {\n              Log.v(\"SDL\", \"Don't skip on such aspect-ratio. Could be a square resolution.\");\n              skip = false;\n           }\n        }\n\n        // Don't skip in MultiWindow.\n        if (skip) {\n            if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {\n                if (SDLActivity.mSingleton.isInMultiWindowMode()) {\n                    Log.v(\"SDL\", \"Don't skip in Multi-Window\");\n                    skip = false;\n                }\n            }\n        }\n\n        if (skip) {\n           Log.v(\"SDL\", \"Skip .. Surface is not ready.\");\n           mIsSurfaceReady = false;\n           return;\n        }\n\n        /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */\n        SDLActivity.onNativeSurfaceChanged();\n\n        /* Surface is ready */\n        mIsSurfaceReady = true;\n\n        SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;\n        SDLActivity.handleNativeState();\n    }\n\n    // Key events\n    @Override\n    public boolean onKey(View v, int keyCode, KeyEvent event) {\n        return SDLActivity.handleKeyEvent(v, keyCode, event, null);\n    }\n\n    // Touch events\n    @Override\n    public boolean onTouch(View v, MotionEvent event) {\n        /* Ref: http://developer.android.com/training/gestures/multi.html */\n        int touchDevId = event.getDeviceId();\n        final int pointerCount = event.getPointerCount();\n        int action = event.getActionMasked();\n        int pointerFingerId;\n        int i = -1;\n        float x,y,p;\n\n        /*\n         * Prevent id to be -1, since it's used in SDL internal for synthetic events\n         * Appears when using Android emulator, eg:\n         *  adb shell input mouse tap 100 100\n         *  adb shell input touchscreen tap 100 100\n         */\n        if (touchDevId < 0) {\n            touchDevId -= 1;\n        }\n\n        // 12290 = Samsung DeX mode desktop mouse\n        // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN\n        // 0x2   = SOURCE_CLASS_POINTER\n        if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {\n            int mouseButton = 1;\n            try {\n                Object object = event.getClass().getMethod(\"getButtonState\").invoke(event);\n                if (object != null) {\n                    mouseButton = (Integer) object;\n                }\n            } catch(Exception ignored) {\n            }\n\n            // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values\n            // if we are.  We'll leverage our existing mouse motion listener\n            SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();\n            x = motionListener.getEventX(event);\n            y = motionListener.getEventY(event);\n\n            SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());\n        } else {\n            switch(action) {\n                case MotionEvent.ACTION_MOVE:\n                    for (i = 0; i < pointerCount; i++) {\n                        pointerFingerId = event.getPointerId(i);\n                        x = event.getX(i) / mWidth;\n                        y = event.getY(i) / mHeight;\n                        p = event.getPressure(i);\n                        if (p > 1.0f) {\n                            // may be larger than 1.0f on some devices\n                            // see the documentation of getPressure(i)\n                            p = 1.0f;\n                        }\n                        SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);\n                    }\n                    break;\n\n                case MotionEvent.ACTION_UP:\n                case MotionEvent.ACTION_DOWN:\n                    // Primary pointer up/down, the index is always zero\n                    i = 0;\n                    /* fallthrough */\n                case MotionEvent.ACTION_POINTER_UP:\n                case MotionEvent.ACTION_POINTER_DOWN:\n                    // Non primary pointer up/down\n                    if (i == -1) {\n                        i = event.getActionIndex();\n                    }\n\n                    pointerFingerId = event.getPointerId(i);\n                    x = event.getX(i) / mWidth;\n                    y = event.getY(i) / mHeight;\n                    p = event.getPressure(i);\n                    if (p > 1.0f) {\n                        // may be larger than 1.0f on some devices\n                        // see the documentation of getPressure(i)\n                        p = 1.0f;\n                    }\n                    SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);\n                    break;\n\n                case MotionEvent.ACTION_CANCEL:\n                    for (i = 0; i < pointerCount; i++) {\n                        pointerFingerId = event.getPointerId(i);\n                        x = event.getX(i) / mWidth;\n                        y = event.getY(i) / mHeight;\n                        p = event.getPressure(i);\n                        if (p > 1.0f) {\n                            // may be larger than 1.0f on some devices\n                            // see the documentation of getPressure(i)\n                            p = 1.0f;\n                        }\n                        SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);\n                    }\n                    break;\n\n                default:\n                    break;\n            }\n        }\n\n        return true;\n   }\n\n    // Sensor events\n    public void enableSensor(int sensortype, boolean enabled) {\n        // TODO: This uses getDefaultSensor - what if we have >1 accels?\n        if (enabled) {\n            mSensorManager.registerListener(this,\n                            mSensorManager.getDefaultSensor(sensortype),\n                            SensorManager.SENSOR_DELAY_GAME, null);\n        } else {\n            mSensorManager.unregisterListener(this,\n                            mSensorManager.getDefaultSensor(sensortype));\n        }\n    }\n\n    @Override\n    public void onAccuracyChanged(Sensor sensor, int accuracy) {\n        // TODO\n    }\n\n    @Override\n    public void onSensorChanged(SensorEvent event) {\n        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {\n\n            // Since we may have an orientation set, we won't receive onConfigurationChanged events.\n            // We thus should check here.\n            int newOrientation;\n\n            float x, y;\n            switch (mDisplay.getRotation()) {\n                case Surface.ROTATION_90:\n                    x = -event.values[1];\n                    y = event.values[0];\n                    newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;\n                    break;\n                case Surface.ROTATION_270:\n                    x = event.values[1];\n                    y = -event.values[0];\n                    newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;\n                    break;\n                case Surface.ROTATION_180:\n                    x = -event.values[0];\n                    y = -event.values[1];\n                    newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;\n                    break;\n                case Surface.ROTATION_0:\n                default:\n                    x = event.values[0];\n                    y = event.values[1];\n                    newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;\n                    break;\n            }\n\n            if (newOrientation != SDLActivity.mCurrentOrientation) {\n                SDLActivity.mCurrentOrientation = newOrientation;\n                SDLActivity.onNativeOrientationChanged(newOrientation);\n            }\n\n            SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,\n                                      y / SensorManager.GRAVITY_EARTH,\n                                      event.values[2] / SensorManager.GRAVITY_EARTH);\n\n\n        }\n    }\n\n    // Captured pointer events for API 26.\n    public boolean onCapturedPointerEvent(MotionEvent event)\n    {\n        int action = event.getActionMasked();\n\n        float x, y;\n        switch (action) {\n            case MotionEvent.ACTION_SCROLL:\n                x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);\n                y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);\n                SDLActivity.onNativeMouse(0, action, x, y, false);\n                return true;\n\n            case MotionEvent.ACTION_HOVER_MOVE:\n            case MotionEvent.ACTION_MOVE:\n                x = event.getX(0);\n                y = event.getY(0);\n                SDLActivity.onNativeMouse(0, action, x, y, true);\n                return true;\n\n            case MotionEvent.ACTION_BUTTON_PRESS:\n            case MotionEvent.ACTION_BUTTON_RELEASE:\n\n                // Change our action value to what SDL's code expects.\n                if (action == MotionEvent.ACTION_BUTTON_PRESS) {\n                    action = MotionEvent.ACTION_DOWN;\n                } else { /* MotionEvent.ACTION_BUTTON_RELEASE */\n                    action = MotionEvent.ACTION_UP;\n                }\n\n                x = event.getX(0);\n                y = event.getY(0);\n                int button = event.getButtonState();\n\n                SDLActivity.onNativeMouse(button, action, x, y, true);\n                return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "arch/android/project/app/src/main/res/raw/readme",
    "content": "The MZX build system should put assets.zip here.\n"
  },
  {
    "path": "arch/android/project/app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n</resources>\n"
  },
  {
    "path": "arch/android/project/app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">MegaZeux</string>\n    <string name=\"title_activity_main\">MainActivity</string>\n</resources>\n"
  },
  {
    "path": "arch/android/project/app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"android:Theme.NoTitleBar.Fullscreen\">\n        <!-- Customize your theme here. -->\n    </style>\n\n</resources>\n"
  },
  {
    "path": "arch/android/project/build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        mavenCentral()\n        google()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:8.7.2'\n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        mavenCentral()\n        google()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "arch/android/project/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.9-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "arch/android/project/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "arch/android/project/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "arch/android/project/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "arch/android/project/settings.gradle",
    "content": "include ':app'\n"
  },
  {
    "path": "arch/compat.inc",
    "content": "#\n# Makefile compiler compatibility checks.\n# Define flags for optional compiler flags or settings to keep most\n# of the compiler version check jank out of the main Makefile.\n#\n\n#\n# Assume GCC until proven wrong...\n#\nCOMPILER := ${shell \\\n compiler=\"gcc\"; \\\n command -v ${CC} >/dev/null || compiler=\"error\"; \\\n tmp=`${CC} --version`; \\\n echo $$tmp | grep -qi \"clang version\" && compiler=\"clang\"; \\\n echo $$tmp | grep -qi \"LLVM version\" && compiler=\"clang\"; \\\n echo $$tmp | grep -qi \"emcc\" && compiler=\"clang\"; \\\n echo $$compiler; }\n\nifeq (${COMPILER},error)\n$(error CC not found: ${CC})\nendif\n\n#\n# Get compiler version (GCC).\n#\nifeq (${COMPILER},gcc)\nGCC_VER := ${shell ${CC} -dumpfullversion -dumpversion |\\\n awk -F. '{print $$3+100*($$2+100*$$1)}'}\n\nGCC_VER_GE_4     := ${shell test ${GCC_VER} -ge 40000; echo $$?}\nendif\n\n#\n# Get compiler version (clang).\n#\nifeq (${COMPILER},clang)\nCLANG_VER_FLAG = \"--version\"\n\n#\n# Emscripten uses completely different version numbering and the easiest way\n# to get the clang version is to run it with verbose sanity checks instead.\n# Because of this, stderr needs to be redirected to stdout for this command.\n#\nifeq (${CC},emcc)\nCLANG_VER_FLAG = \"-v\"\nendif\n\nCLANG_VER := ${shell ${CC} ${CLANG_VER_FLAG} 2>&1 |\\\n grep -oi \"version.* [0-9][0-9]*\\.[0-9][0-9]*\\.[0-9][0-9]*\" |\\\n grep -o \"[0-9][0-9]*.*\" |\\\n awk -F. '{print $$3+100*($$2+100*$$1)}'}\n\nCOMPILERXX := ${shell \\\n compiler=\"gcc\"; \\\n tmp=`${CXX} --version`; \\\n echo $$tmp | grep -qi \"clang version\" && compiler=\"clang\"; \\\n echo $$tmp | grep -qi \"LLVM version\" && compiler=\"clang\"; \\\n echo $$tmp | grep -qi \"emcc\" && compiler=\"clang\"; \\\n echo $$compiler; }\n\nifneq (${COMPILERXX},clang)\n$(warning CC is clang but CXX isn't clang++; disabling compiler-specific checks.)\nCOMPILER := unknown\nendif\n\nGCC_VER      := 40201\nGCC_VER_GE_4 := 0\nendif\n\n#\n# Features requiring GCC 4 minimum and compatible with clang.\n#\nifeq (${GCC_VER_GE_4},0)\nHAS_PEDANTIC ?= 1\nHAS_F_VISIBILITY ?= 1\nHAS_W_NO_VARIADIC_MACROS ?= 1\n\nGCC_VER_GE_4_1   := ${shell test ${GCC_VER} -ge 40100; echo $$?}\nifeq (${GCC_VER_GE_4_1},0)\nHAS_F_STACK_PROTECTOR ?= 1\nendif\n\nGCC_VER_GE_4_3   := ${shell test ${GCC_VER} -ge 40300; echo $$?}\nifeq (${GCC_VER_GE_4_3},0)\nHAS_W_MISSING_DECLARATIONS_CXX ?= 1\nHAS_W_VLA ?= 1\nendif\nendif\n\n#\n# Features requiring higher versions of GCC.\n#\nifeq (${COMPILER},gcc)\n\nGCC_VER_GE_4_5   := ${shell test ${GCC_VER} -ge 40500; echo $$?}\nifeq ($(GCC_VER_GE_4_5),0)\nHAS_F_LTO ?= 1\nendif\n\nGCC_VER_GE_4_6   := ${shell test ${GCC_VER} -ge 40600; echo $$?}\nifeq ($(GCC_VER_GE_4_6),0)\nHAS_W_NO_UNUSED_BUT_SET_VARIABLE ?= 1\nendif\n\nGCC_VER_GE_4_9   := ${shell test ${GCC_VER} -ge 40900; echo $$?}\nifeq (${GCC_VER_GE_4_9},0)\nHAS_F_STACK_PROTECTOR_STRONG ?= 1\nendif\n\nGCC_VER_GE_7     := ${shell test ${GCC_VER} -ge 70000; echo $$?}\nifeq ($(GCC_VER_GE_7),0)\nHAS_W_NO_FORMAT_TRUNCATION ?= 1\nHAS_W_NO_IMPLICIT_FALLTHROUGH ?= 1\nendif\n\nGCC_VER_GE_10    := ${shell test ${GCC_VER} -ge 100000; echo $$?}\nifeq (${GCC_VER_GE_10},0)\nHAS_F_ANALYZER ?= 1\nendif\nendif\n\n#\n# C++11 support (GCC).\n# C++11 is optional or unused for all core MegaZeux features, but may be\n# required in the future for optional features (e.g. the network layer).\n#\nifeq (${COMPILER},gcc)\nGCC_VER_GE_4_8   := ${shell test ${GCC_VER} -ge 40800; echo $$?}\nifeq (${GCC_VER_GE_4_8},0)\nHAS_CXX_11 ?= 1\n\n#\n# -Wmissing-field-initializers doesn't properly support C++11 value\n# initialization in GCC versions prior to 5 and will emit spurious warnings.\n# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61489\n#\nGCC_VER_GE_5     := ${shell test ${GCC_VER} -ge 50000; echo $$?}\nifneq (${GCC_VER_GE_5},0)\nHAS_BROKEN_W_MISSING_FIELD_INITIALIZERS ?= 1\nendif\nendif\nendif # GCC\n\n#\n# C++11 support (clang).\n#\nifeq (${COMPILER},clang)\n# Not clear when this was added but it's old.\nHAS_F_LTO ?= 1\n\nCLANG_VER_GE_3_3 := ${shell test \"${CLANG_VER}\" -ge 30300; echo $$?}\nifeq (${CLANG_VER_GE_3_3},0)\nHAS_CXX_11 ?= 1\nendif\n\nCLANG_VER_GE_3_5 := ${shell test \"${CLANG_VER}\" -ge 30500; echo $$?}\nifeq (${CLANG_VER_GE_3_3},0)\nHAS_F_STACK_PROTECTOR_STRONG ?= 1\nendif\nendif # clang\n\n#\n# Get pkgconf or pkg-config (prefer pkgconf).\n#\nPKGCONF ?= ${shell \\\n result=false; \\\n command -v pkg-config >/dev/null && result=pkg-config; \\\n command -v pkgconf >/dev/null && result=pkgconf; \\\n echo $$result; }\n"
  },
  {
    "path": "arch/darwin/MZXRun.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleExecutable</key>\n\t<string>MZXRun</string>\n\t<key>CFBundleIconFile</key>\n\t<string>MegaZeux</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>MZXRun</string>\n\t<key>CFBundleName</key>\n\t<string>MZXRun</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "arch/darwin/Makefile.arch",
    "content": "#\n# MegaZeux Darwin multi-arch configuration.\n#\n\nALL_ARCH        = arm64 arm64e i686 x86_64 x86_64h ppc ppc64\n\n# Override these to match the compiler(s) being used.\n# For x86 and ARM, assume clang is being used by default.\nifeq (${XCODE_3},1)\nARM64_CC        ?= unsupported-architecture\nARM64E_CC       ?= unsupported-architecture\nX86_64H_CC      ?= unsupported-architecture\nX86_CC          ?= i686-apple-darwin10-gcc-4.2.1\nX86_CXX         ?= i686-apple-darwin10-g++-4.2.1\nX86_64_CC       ?= ${X86_CC} -m64\nX86_64_CXX      ?= ${X86_CXX} -m64\nSTDLIB          ?=\nelse\nARM64_CFLAGS    ?= -arch arm64\nARM64E_CFLAGS   ?= -arch arm64e\nX86_CFLAGS      ?= -arch i386\nX86_64_CFLAGS   ?= -arch x86_64\nX86_64H_CFLAGS  ?= -arch x86_64h\nendif\n\n# For PowerPC, assume the Xcode 3 compilers are being used by default.\nXCODE_3_PPC     ?= 1\nifeq (${XCODE_3_PPC},1)\nPPC_CC          ?= powerpc-apple-darwin10-gcc-4.0.1\nPPC_CXX         ?= powerpc-apple-darwin10-g++-4.0.1\nPPC64_CC        ?= powerpc-apple-darwin10-gcc-4.2.1 -m64\nPPC64_CXX       ?= powerpc-apple-darwin10-g++-4.2.1 -m64\nelse\nPPC_CC          ?= powerpc-apple-darwin10-gcc\nPPC_CXX         ?= powerpc-apple-darwin10-g++\nPPC64_CC        ?= powerpc64-apple-darwin10-gcc\nPPC64_CXX       ?= powerpc64-apple-darwin10-g++\nendif\n\nifeq (${ARCH},arm64)\nCC      := $(or ${ARM64_CC},${CC}) ${ARM64_CFLAGS}\nCXX     := $(or ${ARM64_CXX},${CXX}) ${ARM64_CFLAGS}\nMINVER  ?= 11.0\nendif\n\nifeq (${ARCH},arm64e)\nCC      := $(or ${ARM64E_CC},${CC}) ${ARM64E_CFLAGS}\nCXX     := $(or ${ARM64E_CXX},${CXX}) ${ARM64E_CFLAGS}\nMINVER  ?= 11.0\nendif\n\nifeq (${ARCH},i686)\nCC      := $(or ${X86_CC},${CC}) ${X86_CFLAGS}\nCXX     := $(or ${X86_CXX},${CXX}) ${X86_CFLAGS}\nSYSROOT ?= /Developer/SDKs/MacOSX10.13.sdk\nSTDLIB  ?= libstdc++\nMINVER  ?= 10.6\nendif\n\nifeq (${ARCH},x86_64)\nCC      := $(or ${X86_64_CC},${CC}) ${X86_64_CFLAGS}\nCXX     := $(or ${X86_64_CXX},${CXX}) ${X86_64_CFLAGS}\nSYSROOT ?= /Developer/SDKs/MacOSX10.13.sdk\nSTDLIB  ?= libstdc++\nMINVER  ?= 10.6\nendif\n\nifeq (${ARCH},x86_64h)\nCC      := $(or ${X86_64H_CC},${CC}) ${X86_64H_CFLAGS}\nCXX     := $(or ${X86_64H_CXX},${CXX}) ${X86_64H_CFLAGS}\nMINVER  ?= 10.9\nendif\n\nifeq (${ARCH},ppc)\nCC      := $(or ${PPC_CC},${CC}) ${PPC_CFLAGS}\nCXX     := $(or ${PPC_CXX},${CXX}) ${PPC_CFLAGS}\nSYSROOT ?= /Developer/SDKs/MacOSX10.4u.sdk\nMINVER  ?= 10.4\nendif\n\nifeq (${ARCH},ppc64)\nCC      := $(or ${PPC64_CC},${CC}) ${PPC64_CFLAGS}\nCXX     := $(or ${PPC64_CXX},${CXX}) ${PPC64_CFLAGS}\nSYSROOT ?= /Developer/SDKs/MacOSX10.5.sdk\nMINVER  ?= 10.5\nendif\n\nifneq (${ARCH},)\nifeq (${MINVER},)\n$(error invalid architecture ${ARCH})\nendif\nendif\n\nCC      += -mmacosx-version-min=${MINVER}\nCXX     += -mmacosx-version-min=${MINVER}\n\nifneq (${SYSROOT},)\nCC      += -isysroot \"${SYSROOT}\"\nCXX     += -isysroot \"${SYSROOT}\"\nendif\n"
  },
  {
    "path": "arch/darwin/Makefile.in",
    "content": "#\n# OS X makefile generics\n#\n\nDSOLDFLAGS = -dynamiclib\nDSOPRE     = lib\nDSOPOST    = .dylib\nDSORPATH   =\nDSOSONAME  = -install_name ${LIBDIR}/\n\nDYLIBBUNDLER ?= @arch/darwin/bundle.sh\nLIPO         ?= @arch/darwin/lipo.sh\n\nCODESIGN_ID  ?= -\nCODESIGN     ?= codesign --force --deep --preserve-metadata=entitlements,requirements,flags,runtime --sign ${CODESIGN_ID}\nCODESIGN_CHK ?= codesign --verify --deep --strict -vvvv\n\n#\n# darwin specific recipes.\n# Enable 'make install' and 'make uninstall' for darwin builds.\n#\n\nifeq (${SUBPLATFORM},darwin)\n\ninclude arch/install.inc\n\n# No platform-specific files to install\ninstall-arch:\nuninstall-arch:\n\nendif\n\n#\n# darwin-dist specific recipes.\n# These recipes are intended to produce portable Mac OS X .apps.\n# This setup is extremely old and for Intel Macs has been superceded\n# by the Xcode project folder (see: arch/xcode). However, this is still\n# required to build PowerPC Mac .apps.\n#\n\nifeq (${SUBPLATFORM},darwin-dist)\n\n.PHONY: dist lipo package package_clean\n\n# Make dylib path the same as the executable path instead\nDSOSONAME  = -install_name @executable_path/\n\n# Override these to determine which builds are included in the .app.\nPREFIX_I686   ?=\nPREFIX_AMD64  ?=\nPREFIX_AMD64H ?=\nPREFIX_PPC    ?=\nPREFIX_PPC64  ?=\nPREFIX_ARM64  ?=\nPREFIX_ARM64E ?=\n\nHAS_PREFIX=1\nifeq (${PREFIX_I686},)\nifeq (${PREFIX_AMD64},)\nifeq (${PREFIX_AMD64H},)\nifeq (${PREFIX_PPC},)\nifeq (${PREFIX_PPC64},)\nifeq (${PREFIX_ARM64},)\nifeq (${PREFIX_ARM64E},)\nHAS_PREFIX=0\nendif\nendif\nendif\nendif\nendif\nendif\nendif\n\n# Configure CC et al. for the selected architecture ARCH, if any.\ninclude arch/darwin/Makefile.arch\n\nifeq (${ARCH},)\nDARWIN_META_TARGETS := 1\nelse\nREAL_ARCH = ${ARCH}\nendif\n\n#\n# MegaZeux doesn't use a C++ standard library by default, but modern compilers\n# refuse to build without -stdlib. libstdc++ is required to target Snow Leopard,\n# and Apple libstdc++ never got C++11, so forcibly disable that too if needed.\n#\nifneq (${STDLIB},)\nARCH_CXXFLAGS ?= -stdlib=${STDLIB}\nifeq (${STDLIB},libstdc++)\nHAS_CXX_11 := 0\nendif\nendif\n\nifneq (${DARWIN_META_TARGETS},1)\n\n#\n# Per-arch targets.\n#\n\nCFLAGS   += -DSUBPLATFORM='\"${REAL_ARCH}\"'\nCXXFLAGS += -DSUBPLATFORM='\"${REAL_ARCH}\"'\nUTILS_ZLIB_LDFLAGS ?= ${PREFIX}/lib/libz.a\nUTILS_LIBPNG_LDFLAGS ?= ${PREFIX}/lib/libpng.a\nSDL_PREFIX := ${PREFIX}\nSDL_PKG_CONFIG_PATH := ${PREFIX}/lib/pkgconfig\n\npackage: all\n\tif [ -d \"bundles/libs-${REAL_ARCH}\" ]; then rm -rf \"bundles/libs-${REAL_ARCH}\"; fi\nifeq (${BUILD_EDITOR},1)\n\t${DYLIBBUNDLER} ${REAL_ARCH} ${mzx}\n\t${MV} ${mzx} ${mzx}.${REAL_ARCH}\nendif\nifeq (${BUILD_MZXRUN},1)\n\t${DYLIBBUNDLER} ${REAL_ARCH} ${mzxrun}\n\t${MV} ${mzxrun} ${mzxrun}.${REAL_ARCH}\nendif\nifeq (${BUILD_MODULAR},1)\n\t${DYLIBBUNDLER} ${REAL_ARCH} ${core_target}\n\t${MV} ${core_target} ${core_target}.${REAL_ARCH}\nifeq (${BUILD_EDITOR},1)\n\t${DYLIBBUNDLER} ${REAL_ARCH} ${editor_target}\n\t${MV} ${editor_target} ${editor_target}.${REAL_ARCH}\nendif\nendif\nifeq (${BUILD_UTILS},1)\n\t${MV} ${checkres} ${checkres}.${REAL_ARCH}\n\t${MV} ${downver} ${downver}.${REAL_ARCH}\n\t${MV} ${hlp2txt} ${hlp2txt}.${REAL_ARCH}\n\t${MV} ${txt2hlp} ${txt2hlp}.${REAL_ARCH}\n\t${MV} ${png2smzx} ${png2smzx}.${REAL_ARCH}\n\t${MV} ${y4m2smzx} ${y4m2smzx}.${REAL_ARCH}\n\t${MV} ${ccv} ${ccv}.${REAL_ARCH}\nendif\n\nelse # DARWIN_META_TARGETS=1\n\n#\n# Define meta targets.\n#\n\n#\n# Disable 'all' for the meta rules since no specific target arch is selected.\n# Then add 'dist' to the 'all' target for convenience.\n#\nSUPPRESS_CC_TARGETS ?= 1\nall: dist\n\nlipo:\nifeq (${BUILD_MZXRUN},1)\n\t${LIPO} ${mzxrun}\nendif\nifeq (${BUILD_EDITOR},1)\n\t${LIPO} ${mzx}\nendif\nifeq (${BUILD_MODULAR},1)\n\t${LIPO} ${core_target}\nifeq (${BUILD_EDITOR},1)\n\t${LIPO} ${editor_target}\nendif\nendif\nifeq (${BUILD_UTILS},1)\n\t${LIPO} ${checkres} ${downver} ${hlp2txt} ${txt2hlp} ${png2smzx} ${y4m2smzx} ${ccv}\nendif\n\ndist:\nifeq (${HAS_PREFIX},0)\n\t$(error No target prefix defined!\\\n\t Select build targets by defining one or more of PREFIX_I686,\\\n\t PREFIX_AMD64, PREFIX_AMD64H, PREFIX_PPC, PREFIX_PPC64,\\\n\t PREFIX_ARM64, or PREFIX_ARM64E,\\\n\t then build with \"make dist\")\nendif\n\t@${MAKE} ARCH=i686 clean\nifneq (${PREFIX_I686},)\n\t@echo \"Building 'i686' with PREFIX='${PREFIX_I686}'\"\n\t@${MAKE} PREFIX=${PREFIX_I686} ARCH=i686 package\n\t@${MAKE} PREFIX=${PREFIX_I686} ARCH=i686 clean\nendif\nifneq (${PREFIX_AMD64},)\n\t@echo \"Building 'x86_64' with PREFIX='${PREFIX_AMD64}'\"\n\t@${MAKE} PREFIX=${PREFIX_AMD64} ARCH=x86_64 package\n\t@${MAKE} PREFIX=${PREFIX_AMD64} ARCH=x86_64 clean\nendif\nifneq (${PREFIX_AMD64H},)\n\t@echo \"Building 'x86_64h' with PREFIX='${PREFIX_AMD64H}'\"\n\t@${MAKE} PREFIX=${PREFIX_AMD64H} ARCH=x86_64h package\n\t@${MAKE} PREFIX=${PREFIX_AMD64H} ARCH=x86_64h clean\nendif\nifneq (${PREFIX_PPC},)\n\t@echo \"Building 'ppc' with PREFIX='${PREFIX_PPC}'\"\n\t@${MAKE} PREFIX=${PREFIX_PPC} ARCH=ppc package\n\t@${MAKE} PREFIX=${PREFIX_PPC} ARCH=ppc clean\nendif\nifneq (${PREFIX_PPC64},)\n\t@echo \"Building 'ppc64' with PREFIX='${PREFIX_PPC64}'\"\n\t@${MAKE} PREFIX=${PREFIX_PPC64} ARCH=ppc64 package\n\t@${MAKE} PREFIX=${PREFIX_PPC64} ARCH=ppc64 clean\nendif\nifneq (${PREFIX_ARM64},)\n\t@echo \"Building 'arm64' with PREFIX='${PREFIX_ARM64}'\"\n\t@${MAKE} PREFIX=${PREFIX_ARM64} ARCH=arm64 package\n\t@${MAKE} PREFIX=${PREFIX_ARM64} ARCH=arm64 clean\nendif\nifneq (${PREFIX_ARM64E},)\n\t@echo \"Building 'arm64e' with PREFIX='${PREFIX_ARM64E}'\"\n\t@${MAKE} PREFIX=${PREFIX_ARM64E} ARCH=arm64e package\n\t@${MAKE} PREFIX=${PREFIX_ARM64E} ARCH=arm64e clean\nendif\n\t@${MAKE} lipo\n\nmzx_app := ${build_root}/MegaZeux.app/\nmzxrun_app := ${build_root}/MZXRun.app/\n\nbuild := ${mzx_app}/Contents/Resources\nbuild:\n\t${RM} ${build}/*.debug ${build}/utils/*.debug\n\tln -s /Applications               ${mzx_app}/../Applications\n\t${MKDIR}                          ${mzx_app}/Contents/MacOS\n\t${CP} contrib/icons/old/quantump.icns ${mzx_app}/Contents/Resources/MegaZeux.icns\n\t${CP} ${build}/LICENSE            ${build}/docs/\n\t${MV} ${build}/docs               ${mzx_app}/../Documentation\nifeq (${BUILD_UTILS},1)\n\t${MV} ${build}/utils/             ${mzx_app}/../Utilities\n\t${CODESIGN}                       ${mzx_app}/../Utilities/ccv\n\t${CODESIGN}                       ${mzx_app}/../Utilities/checkres\n\t${CODESIGN}                       ${mzx_app}/../Utilities/downver\n\t${CODESIGN}                       ${mzx_app}/../Utilities/png2smzx\n\t${CODESIGN}                       ${mzx_app}/../Utilities/y4m2smzx\n\t${CODESIGN}                       ${mzx_app}/../Utilities/hlp2txt\n\t${CODESIGN}                       ${mzx_app}/../Utilities/txt2hlp\nendif\n\t${MV} ${build}/${mzx}             ${mzx_app}/Contents/MacOS/MegaZeux\nifneq (${BUILD_MZXRUN},)\n\t${MV} ${build}/${mzxrun}          ${mzx_app}/Contents/MacOS/MZXRun\nendif\nifneq (${BUILD_MODULAR},)\n\t${MV} ${build}/${core_target}     ${mzx_app}/Contents/MacOS/\n\t${MV} ${build}/${editor_target}   ${mzx_app}/Contents/MacOS/\nendif\n\t${MKDIR}                          ${mzx_app}/Contents/Frameworks\n\t${CP} -r ./bundles/libs-*         ${mzx_app}/Contents/Frameworks\nifneq (${BUILD_MZXRUN},)\n\t${CP} -r ${mzx_app} ${mzxrun_app}\n\t${RM} ${mzx_app}/Contents/MacOS/MZXRun\n\t${RM} ${mzxrun_app}/Contents/MacOS/MegaZeux\nifneq (${BUILD_MODULAR},)\n\t${RM} ${mzxrun_app}/Contents/MacOS/${editor_target}\nendif\n\t${CP} arch/darwin/MZXRun.plist    ${mzxrun_app}/Contents/Info.plist\n\t${CODESIGN}                       ${mzxrun_app}/Contents/MacOS/MZXRun\n\t${CODESIGN_CHK}                   ${mzxrun_app}\nendif\n\t${CP} arch/darwin/MegaZeux.plist  ${mzx_app}/Contents/Info.plist\n\t${CODESIGN}                       ${mzx_app}/Contents/MacOS/MegaZeux\n\t${CODESIGN_CHK}                   ${mzx_app}\n\narchive: build\n\t@arch/darwin/dmg.sh ${TARGET}\n\t${CODESIGN} build/dist/darwin/${TARGET}.dmg\n\t${CODESIGN_CHK} build/dist/darwin/${TARGET}.dmg\n\nclean: package_clean\n\npackage_clean:\n\tfor arch in ${ALL_ARCH}; do \\\n\t\trm -f ${mzx}.$$arch; \\\n\t\trm -f ${mzxrun}.$$arch; \\\n\t\trm -f ${core_target}.$$arch; \\\n\t\trm -f ${editor_target}.$$arch; \\\n\t\trm -f ${checkres}.$$arch; \\\n\t\trm -f ${downver}.$$arch; \\\n\t\trm -f ${hlp2txt}.$$arch; \\\n\t\trm -f ${txt2hlp}.$$arch; \\\n\t\trm -f ${png2smzx}.$$arch; \\\n\t\trm -f ${y4m2smzx}.$$arch; \\\n\t\trm -f ${ccv}.$$arch; \\\n\tdone\n\nendif # DARWIN_META_TARGETS=1\nendif # darwin-dist\n"
  },
  {
    "path": "arch/darwin/MegaZeux.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleExecutable</key>\n\t<string>MegaZeux</string>\n\t<key>CFBundleIconFile</key>\n\t<string>MegaZeux</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>com.digitalmzx.MegaZeux</string>\n\t<key>CFBundleName</key>\n\t<string>MegaZeux</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "arch/darwin/README.md",
    "content": "# BUILDING MEGAZEUX VIA THIS MAKEFILE\n\nThis Makefile can be used to build a DMG targeting Mac OS X 10.4 (ppc, ppc64),\nMac OS X 10.6 (i686 and x86_64), Mac OS X 10.9 (x86_64h), and macOS 11 (arm64).\nThe process here is slightly involved and in most cases will require *multiple*\nversions of Xcode to target everything. Notarization is NOT currently supported;\nplease use the Xcode project for that.\n\nThe current \"official\" build environment for this port is a dual-booted Mac Mini\nwith macOS 10.13 High Sierra (Xcode 9.4.1 with 3.2.6 compilers and SDKs via\nXcodeLegacy) and macOS 12 Monterey (Xcode 14.2). However, this buildsystem should\ntechnically work in some capacity with most Xcode versions.\n\nTested versions of Xcode:\n\n- Xcode 3.2.6 has been used from MegaZeux 2.90 onward to produce PowerPC\n  binaries. To get the correct compiler names from Makefile.arch, define\n  XCODE_3=1. This is the **LAST** version of Xcode that targets PowerPC.\n  The 10.4 SDK should be used for PPC and the 10.5 SDK should be used for PPC64.\n  The current setup installs these SDKs alongside Xcode 9.4.1 via XcodeLegacy.\n- Xcode 4.1 has been tested in the past and should work.\n- Xcode 9.4.1 can target i686, x86_64, and x86_64h. It is the **LAST** version\n  of Xcode that targets i686. This is used to produce the Intel binaries from\n  from 2.93b onward. It is also capable of hosting the Xcode 3.2.6 SDKs and\n  compilers.\n- Xcode 14.2 is used to provide arm64/arm64e binaries from 2.93c onward. It\n  can also provide x86_64 and x86_64h with a minimum OS version of 10.13.\n  Versions as low as Xcode 12.2 should work for this (down to even 10.9),\n  but are untested.\n\nUntested and unsupported features:\n\n- Other versions of Xcode might require changing some variables (Makefile.arch).\n- Notarization will probably never be supported due to the nature of how\n  this platform builds application bundles. Use the Xcode port for this instead.\n- osxcross should work, but you'll need to override the compilers defined in\n  Makefile.arch. Only the official SDKs have been tested so far.\n\nOther required software:\n\n- [dylibbundler](https://github.com/auriamg/macdylibbundler/), which can be\n  installed via MacPorts: `sudo port install dylibbundler`.\n\n## PREREQUISITES\n\n1) Install the base version of Xcode that will be used (e.g. 9.4.1).\n   Most of these are available from the Apple Developers site or from the\n   OS install media (for older OS versions).\n2) Add extra compilers/SDKs as necessary via XcodeLegacy.\n3) Install dylibbundler via MacPorts or otherwise.\n4) Grab a prebuilt MegaZeux dependencies bundle from\n   [here](https://github.com/AliceLR/megazeux-dependencies) and extract the\n   tarball into scripts/deps/. Alternatively (if you are feeling brave), you\n   can use the Makefiles in scripts/deps/ to build these yourself. Manually\n   building the dependencies currently requires CMake and wget, which can\n   also be installed via MacPorts.\n\n## CONFIGURATION\n\nUse the following config.sh setup to prepare `darwin-dist`.\n```\n./config.sh --platform darwin-dist --enable-release --enable-lto\n```\n\nFor i686 builds and x86_64 builds targeting versions prior to 10.9, SDL2\nshould be explicitly supplied to future-proof configuring for them:\n```\n./config.sh --platform darwin-dist --enable-release --enable-lto --enable-sdl2\n```\n\nFor the PowerPC and PowerPC64 builds, using SDL 1.2 is recommended:\n```\n./config.sh --platform darwin-dist --enable-release --enable-sdl1\n```\n\nNote that you can reconfigure between individual architecture builds,\nbut invoking `make clean` manually at any point will clean up the temporary\npre-lipo binaries from previous builds.\n\n## BUILDING\n\nRun make with variables defining the dependencies folders of each architecture\nto be targeted (and optionally -j#, etc). Each architecture provided will be\ncompiled, e.g.\n\n```\nmake \\\n  PREFIX_I686=scripts/deps/macos/i686 \\\n  PREFIX_PPC=scripts/deps/macos/ppc\n```\n\nwill compile the x86 and PowerPC 32-bit builds.\n\nThe prefixes you supply will determine which binaries are built. Supported\nprefixes:\n\n| Arch    | Prefix          |\n|---------|-----------------|\n| arm64   | `PREFIX_ARM64`  |\n| arm64e  | `PREFIX_ARM64E` |\n| i686    | `PREFIX_I686`   |\n| x86_64  | `PREFIX_AMD64`  |\n| x86_64h | `PREFIX_AMD64H` |\n| ppc     | `PREFIX_PPC`    |\n| ppc64   | `PREFIX_PPC64`  |\n\nEach architecture's binaries will be output as \"mzxrun.${ARCH}\" et al.\nAfter all desired platforms are built, `make lipo` will be run automatically\nto merge them. Any pre-existing binaries from other architectures will also\nbe combined into the fat binary. This can be used to e.g. import PowerPC\nbuilds made with Xcode 3.2.6 into a binary otherwise built by Xcode 9.4.1.\nThe final lipo and archive steps should be performed on the newer of the\nenvironments involved to ensure correct codesigning et al.\n\nNOTE: OS X Mavericks (and presumably older versions) have linker issues when\na binary links against both x86_64 and x86_64h dylibs. This was found in\nOS X Mavericks 10.9.5 and is fixed in OS X Yosemite 10.10.5. Any binary\ncontaining both x86_64 and x86_64h should target 10.10 minimum *for both*.\n\nNOTE: arm64e builds are reportedly terminated by the kernel immediately in\nmacOS Monterey even with correct signage. The reason for this is unknown\nbut it is strongly recommended to use arm64 *only* for now.\n\n## PACKAGING\n\n**FIRST**: Make sure there is no currently mounted volume named \"MegaZeux\",\nand that /Volumes contains no folder named \"MegaZeux\". This will break the\nDMG creation step. You may have to manually clean up from prior failed builds.\n\nAfter `make` or `make lipo`, simply do the following:\n```\nmake archive\n```\n\nThis will output a file to the `build/dist/darwin` directory at the top level.\nOne of these is a redistributable DMG that contains both MegaZeux and MZXRun\napplications supporting the architectures you built.\n"
  },
  {
    "path": "arch/darwin/bin/otool",
    "content": "#!/bin/sh\n\n# Used to replace otool with otool-classic when bundling PowerPC64 binaries.\nxcrun otool-classic \"$@\"\n"
  },
  {
    "path": "arch/darwin/bundle.sh",
    "content": "#!/bin/sh\n\nARCH=$1\nshift\n\n# PPC64 hotfix--replace otool with otool-classic.\n# LLVM-based otool uses llvm-objdump, does not support PowerPC64 LC_UNIXTHREAD.\n# dylibbundler currently does not support replacing otool by other means.\nif [ \"$ARCH\" = \"ppc64\" ]; then\n\texport PATH=\"$(pwd)/arch/darwin/bin:$PATH\"\nfi\n\n# Note: do not codesign here since there's no guarantee the MZX binaries\n# are being processed by dylibbundler in the \"correct\" order for codesign.\n# Signing should instead be performed during the .app bundling stage,\n# after the executables have been combined with lipo.\n#\nfor exe in $@; do\n\tif [ -f \"$exe\" ]; then\n\t\tmkdir -p ./bundles\n\t\tdylibbundler -ns -cd -of -b -x $exe \\\n\t\t  -d \"./bundles/libs-$ARCH/\" -p \"@executable_path/../Frameworks/libs-$ARCH/\"\n\tfi\ndone\n"
  },
  {
    "path": "arch/darwin/dmg.sh",
    "content": "#!/bin/sh\n\nTARGET=$1\n\nBASEDIR=build/dist/darwin\nDMGNAME=${BASEDIR}/${TARGET}.dmg\nVOLNAME=MegaZeux\n\nmkdir -p ${BASEDIR}\nrm -f $DMGNAME\nhdiutil create $DMGNAME -size 100m -fs HFS+ \\\n                        -volname \"$VOLNAME\" -layout SPUD &&\nDEV_HANDLE=`hdid $DMGNAME | grep Apple_HFS | \\\n            perl -e '\\$_=<>; /^\\\\/dev\\\\/(disk.)/; print \\$1'`\nditto -rsrcFork build/darwin-dist /Volumes/$VOLNAME &&\nhdiutil detach $DEV_HANDLE &&\n\nhdiutil convert $DMGNAME -format UDZO -o $DMGNAME.compressed &&\nmv -f $DMGNAME.compressed.dmg $DMGNAME\n"
  },
  {
    "path": "arch/darwin/lipo.sh",
    "content": "#!/bin/sh\n\n#\n# Lipo all the binaries together, if not already done\n#\nfor FILE in \"$@\"; do\n\t[ -f ${FILE} ] && rm ${FILE}\n\n\tlist=\n\tfor ARCH in arm64 arm64e i686 x86_64 x86_64h ppc ppc64; do\n\t\tif [ -f \"${FILE}.${ARCH}\" ]; then\n\t\t\tlist=\"$list ${FILE}.${ARCH}\"\n\t\tfi\n\tdone\n\n\tif [ -z \"$list\" ]; then\n\t\techo \"No target binaries are present!\"\n\t\tbreakout 4\n\tfi\n\n\techo \"lipo -create $list -output ${FILE}\"\n\tif [ -f \"$list\" ]; then\n\t\tcp \"$list\" \"${FILE}\"\n\telse\n\t\tlipo -create $list -output ${FILE}\n\tfi\n\n\tif [ ! -f ${FILE} ]; then\n\t\techo \"Failed to compile '${FILE}'..\"\n\t\tbreakout 5\n\tfi\ndone\n"
  },
  {
    "path": "arch/djgpp/CONFIG.DJGPP",
    "content": "#!/bin/sh\n\n[ -z \"$DJGPP\" ] && { echo \"DJGPP not defined! Aborting.\"; exit 1; }\n\n./config.sh --platform djgpp --prefix \"$DJGPP\" --enable-release --enable-lto \\\n\t--enable-stb-vorbis --disable-libpng \\\n\t--enable-stdio-redirect --enable-meter --enable-extram \"$@\"\n"
  },
  {
    "path": "arch/djgpp/Makefile.in",
    "content": "#\n# DJGPP Makefile\n#\n\nCROSS_COMPILE ?= i586-pc-msdosdjgpp-\nBINEXT = .exe\n\nEXTRA_LICENSES += ${LICENSE_DJGPP} ${LICENSE_LGPL2}\n\n# 387 emulation for FPU not have\nEXTRA_LIBS = -lemu\n\n# pc.h and sys/farptr.h have functions declared extern __inline__\n# Use -fgnu89-inline to avoid multiple-definition errors\n#-fgnu89-inline\nARCH_CFLAGS   +=\nARCH_CXXFLAGS +=\nARCH_LDFLAGS  += ${EXTRA_LIBS}\n\nclean:\n\t${RM} -f arch/djgpp/*.d arch/djgpp/*.o\n\n#\n# The return of the revenge of the vile hack! remove me ASAP\n#\narch/djgpp/%.o: arch/djgpp/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} -c $< -o $@\n\narch/djgpp/%.o: arch/djgpp/%.S\n\t$(if ${V},,@echo \"  AS      \" $<)\n\t${AS} $< -o $@\n\narch/djgpp/CWSDPMI.EXE:\n\t@echo You must copy CWSDPMI.EXE to arch/djgpp for packaging.\n\t@false\n\nbuild: ${build} arch/djgpp/CWSDPMI.EXE\n\t${CP} arch/djgpp/CWSDPMI.EXE ${build}\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/djgpp/audio.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2010 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2019 Adrian Siekierka <kontakt@asie.pl>\n * Copyright (C) 2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#define delay delay_dos\n#include <bios.h>\n#include <dpmi.h>\n#include <go32.h>\n#include <sys/nearptr.h>\n#undef delay\n#include \"../../src/audio/audio.h\"\n#include \"../../src/audio/audio_struct.h\"\n#include \"../../src/util.h\"\n#include \"driver_sb.h\"\n#include \"platform_djgpp.h\"\n\n#ifdef CONFIG_AUDIO\n\n#define BUFFER_BLOCKS 2\n\nstruct sb_config\n{\n  // from environment variable, config, ...\n  uint16_t port;\n  uint8_t irq, dma8, dma16, type;\n  // from DSP\n  uint8_t version_major, version_minor;\n  // from allocation\n  int buffer_selector, buffer_segment;\n  uint8_t *buffer;\n  unsigned buffer_block;\n  unsigned buffer_channels;\n  unsigned buffer_format;\n  unsigned buffer_frames;\n  unsigned buffer_size;\n  unsigned active_dma;\n  unsigned active_dma_ack;\n  unsigned active_dma_size;\n  // driver-specific:\n  // - to restore on deinit\n  struct irq_state old_irq_state;\n  _go32_dpmi_seginfo old_irq_handler;\n  // - if nearptr enabled\n  boolean nearptr_buffer_enabled;\n  __dpmi_meminfo nearptr_buffer_mapping;\n};\n\nstatic struct sb_config sb_cfg;\n\nstatic void audio_sb_clear_buffer(void)\n{\n  int zero = (sb_cfg.buffer_format == SAMPLE_U8) ? 0x80 : 0;\n  memset(sb_cfg.buffer, zero, sb_cfg.buffer_size * BUFFER_BLOCKS);\n\n  if(!sb_cfg.nearptr_buffer_enabled)\n  {\n    dosmemput(sb_cfg.buffer, sb_cfg.buffer_size * BUFFER_BLOCKS,\n     sb_cfg.buffer_segment << 4);\n  }\n}\n\nstatic void audio_sb_fill_block(void)\n{\n  size_t offset = sb_cfg.buffer_block * sb_cfg.buffer_size;\n\n  audio_mixer_render_frames(sb_cfg.buffer + offset,\n   sb_cfg.buffer_frames, sb_cfg.buffer_channels, sb_cfg.buffer_format);\n\n  if(!sb_cfg.nearptr_buffer_enabled)\n  {\n    dosmemput(sb_cfg.buffer + offset, sb_cfg.buffer_size,\n     (sb_cfg.buffer_segment << 4) + offset);\n  }\n}\n\nstatic void audio_sb_next_block(void)\n{\n  audio_sb_fill_block();\n  sb_cfg.buffer_block = (sb_cfg.buffer_block + 1) % BUFFER_BLOCKS;\n}\n\nstatic void audio_sb_interrupt(void)\n{\n  uint8_t fpustate[108];\n  djgpp_save_x87(fpustate);\n\n  audio_sb_next_block();\n  inportb(sb_cfg.port + sb_cfg.active_dma_ack); // ack (sb)\n  djgpp_irq_ack(sb_cfg.irq); // ack (pic)\n\n  djgpp_restore_x87(fpustate);\n}\n\nstatic void audio_sb_parse_env(struct sb_config *conf, char *env)\n{\n  char *token;\n  while(env != NULL)\n  {\n    token = strsep(&env, \" \");\n    switch(token[0])\n    {\n      case 'A':\n        conf->port = strtol(token + 1, NULL, 16);\n        break;\n      case 'D':\n        conf->dma8 = strtol(token + 1, NULL, 10);\n        break;\n      case 'H':\n        conf->dma16 = strtol(token + 1, NULL, 10);\n        break;\n      case 'I':\n        conf->irq = strtol(token + 1, NULL, 10);\n        break;\n      case 'T':\n        conf->type = strtol(token + 1, NULL, 10);\n        break;\n    }\n  }\n}\n\nstatic uint8_t audio_sb_dsp_read(void)\n{\n  int i;\n  for(i = 0; i < 8192; i++)\n    if(inportb(sb_cfg.port + 0xE) & 0x80)\n      break;\n\n  return inportb(sb_cfg.port + 0xA);\n}\n\nstatic void audio_sb_dsp_write(uint8_t val)\n{\n  int i;\n  for(i = 0; i < 8192; i++)\n    if(!(inportb(sb_cfg.port + 0xC) & 0x80))\n      break;\n\n  outportb(sb_cfg.port + 0xC, val);\n}\n\nstatic boolean audio_sb_dsp_reset(void)\n{\n  uint16_t i;\n  outportb(sb_cfg.port + 0x6, 1);\n  usleep(3);\n  outportb(sb_cfg.port + 0x6, 0);\n  for(i = 65535; i > 0; i--)\n    if(inportb(sb_cfg.port + 0xE) & 0x80)\n      break;\n  if(i == 0)\n    return false;\n  for(i = 65535; i > 0; i--)\n    if(inportb(sb_cfg.port + 0xA) == 0xAA)\n      break;\n  return i > 0;\n}\n\nstatic void audio_sb_mixer_set_stereo(boolean enable)\n{\n  int tmp;\n  if(sb_cfg.version_major != 3)\n    return;\n\n  tmp = inportb(sb_cfg.port + 0x5);\n  tmp = enable ? (tmp | SBPRO_MIXER_STEREO) : (tmp & ~SBPRO_MIXER_STEREO);\n\n  outportb(sb_cfg.port + 0x4, 0xe);\n  outportb(sb_cfg.port + 0x5, tmp);\n}\n\nvoid init_audio_platform(struct config_info *conf)\n{\n  _go32_dpmi_seginfo new_irq_handler;\n\n  // Try to find a Sound Blaster\n  // TODO: manual configuration\n  char *sb_env = getenv(\"BLASTER\");\n  if(sb_env != NULL)\n    audio_sb_parse_env(&sb_cfg, sb_env);\n\n  if(sb_cfg.port == 0)\n    goto err;\n\n  if(!audio_sb_dsp_reset())\n    goto err;\n\n  audio_sb_dsp_write(SB_DSP_GET_VERSION);\n  sb_cfg.version_major = audio_sb_dsp_read();\n  sb_cfg.version_minor = audio_sb_dsp_read();\n\n  info(\"Sound Blaster DSP %d.%02d - using A%03x I%d D%d H%d\\n\",\n   sb_cfg.version_major, sb_cfg.version_minor,\n   sb_cfg.port, sb_cfg.irq, sb_cfg.dma8, sb_cfg.dma16);\n\n  if(!SB_VERSION_ATLEAST(sb_cfg, SB_VERSION_SB1_REV))\n    goto err;\n\n  if(sb_cfg.irq >= 16)\n    goto err;\n\n  if(sb_cfg.dma8 >= 4) // TODO: support DMA >3?\n    goto err;\n\n  if(SB_VERSION_ATLEAST(sb_cfg, SB_VERSION_SB16) &&\n   (sb_cfg.dma16 < 5 || sb_cfg.dma16 >= 8))\n    goto err;\n\n  {\n    unsigned frames = conf->audio_buffer_samples;\n    unsigned rate = conf->audio_sample_rate;\n    unsigned sb16_format = 0;\n    unsigned time_constant = 0;\n    unsigned irq_vector;\n    boolean is_16_bit = false;\n\n    if(frames > 4096) // TODO: seems arbitrary\n      frames = 4096;\n\n    rate = CLAMP(rate, 5000, 44100);\n\n    if(SB_VERSION_ATLEAST(sb_cfg, SB_VERSION_SB16))\n    {\n      sb_cfg.buffer_format = SAMPLE_S16;\n      sb_cfg.buffer_channels = CLAMP(conf->audio_output_channels, 1, 2);\n\n      if(sb_cfg.buffer_format == SAMPLE_S16)\n        is_16_bit = true;\n      if(sb_cfg.buffer_format != SAMPLE_U8)\n        sb16_format |= SB16_FORMAT_SIGNED;\n      if(sb_cfg.buffer_channels == 2)\n        sb16_format |= SB16_FORMAT_STEREO;\n    }\n    else // pre-SB16\n    {\n      sb_cfg.buffer_format = SAMPLE_U8;\n      sb_cfg.buffer_channels = 1;\n      // Sound Blaster Pro and up support stereo.\n      if(SB_VERSION_ATLEAST(sb_cfg, SB_VERSION_SBPRO1))\n        sb_cfg.buffer_channels = CLAMP(conf->audio_output_channels, 1, 2);\n\n      // Sound Blaster Pro stereo and all DSPs prior to 2.01 have a\n      // maximum rate of around 22050.\n      if(sb_cfg.buffer_channels == 2 || !SB_VERSION_ATLEAST(sb_cfg, SB_VERSION_SB2))\n        rate = CLAMP(rate, 5000, 22050);\n\n      time_constant = 256 - 1000000 / (sb_cfg.buffer_channels * rate);\n      rate = 1000000 / (sb_cfg.buffer_channels * (256 - time_constant));\n    }\n\n    sb_cfg.nearptr_buffer_enabled = djgpp_push_enable_nearptr();\n\n    if(!audio_mixer_init(rate, frames, sb_cfg.buffer_channels))\n      goto err;\n\n    sb_cfg.buffer_frames = audio.buffer_frames;\n    sb_cfg.buffer_size = sb_cfg.buffer_frames * sb_cfg.buffer_channels *\n     (is_16_bit ? sizeof(int16_t) : sizeof(uint8_t));\n\n    // allocate memory, without crossing 64K boundary, and clean it\n    sb_cfg.buffer_segment = djgpp_malloc_boundary(sb_cfg.buffer_size * BUFFER_BLOCKS,\n     65536, &sb_cfg.buffer_selector);\n    if(sb_cfg.nearptr_buffer_enabled)\n    {\n      sb_cfg.nearptr_buffer_mapping.address = sb_cfg.buffer_segment << 4;\n      sb_cfg.nearptr_buffer_mapping.size = sb_cfg.buffer_size;\n      if(__dpmi_physical_address_mapping(&sb_cfg.nearptr_buffer_mapping) != 0)\n      {\n        sb_cfg.nearptr_buffer_enabled = false;\n        djgpp_pop_enable_nearptr();\n      }\n      else\n      {\n        sb_cfg.buffer = (uint8_t *)(sb_cfg.nearptr_buffer_mapping.address +\n         __djgpp_conventional_base);\n      }\n    }\n\n    if(!sb_cfg.nearptr_buffer_enabled)\n    {\n      sb_cfg.buffer = (uint8_t *)cmalloc(sb_cfg.buffer_size * BUFFER_BLOCKS);\n      if(!sb_cfg.buffer)\n        goto err;\n    }\n\n    audio_sb_clear_buffer();\n\n    // lock C irq handler\n    // (TODO: rewrite handler in ASM?)\n    _go32_dpmi_lock_code(audio_sb_interrupt, 1024);\n    _go32_dpmi_lock_data(&sb_cfg, sizeof(struct sb_config));\n    sb_cfg.buffer_block = 0;\n\n    // configure irq\n    irq_vector = djgpp_irq_vector(sb_cfg.irq);\n    _go32_dpmi_get_protected_mode_interrupt_vector(irq_vector, &sb_cfg.old_irq_handler);\n    new_irq_handler.pm_offset = (int) audio_sb_interrupt;\n    new_irq_handler.pm_selector = _go32_my_cs();\n    _go32_dpmi_chain_protected_mode_interrupt_vector(irq_vector, &new_irq_handler);\n\n    djgpp_irq_enable(sb_cfg.irq, &sb_cfg.old_irq_state);\n\n    // configure dma\n    if(is_16_bit)\n    {\n      sb_cfg.active_dma = sb_cfg.dma16;\n      sb_cfg.active_dma_ack = SB_PORT_DMA16_ACK;\n    }\n    else\n    {\n      sb_cfg.active_dma = sb_cfg.dma8;\n      sb_cfg.active_dma_ack = SB_PORT_DMA8_ACK;\n    }\n    sb_cfg.active_dma_size = (sb_cfg.buffer_frames * sb_cfg.buffer_channels) - 1;\n\n    djgpp_enable_dma(sb_cfg.active_dma, DMA_WRITE | DMA_AUTOINIT,\n     sb_cfg.buffer_segment << 4, sb_cfg.buffer_size * BUFFER_BLOCKS);\n\n    audio_sb_dsp_write(SB_DSP_SPEAKER_ON);\n\n    // configure dsp\n    if(SB_VERSION_ATLEAST(sb_cfg, SB_VERSION_SB16))\n    {\n      audio_sb_dsp_write(SB_DSP_SET_SAMPLE_RATE);\n      audio_sb_dsp_write(rate >> 8);\n      audio_sb_dsp_write(rate & 0xFF);\n\n      // configure transfer\n      audio_sb_dsp_write(is_16_bit ? SB16_DSP_AUTOINIT_16_BIT : SB16_DSP_AUTOINIT_8_BIT);\n      audio_sb_dsp_write(sb16_format); // mono/stereo, signed/unsigned\n      audio_sb_dsp_write(sb_cfg.active_dma_size);\n      audio_sb_dsp_write(sb_cfg.active_dma_size >> 8);\n    }\n    else\n\n    if(SB_VERSION_ATLEAST(sb_cfg, SB_VERSION_SB1_REV))\n    {\n      audio_sb_mixer_set_stereo(sb_cfg.buffer_channels == 2);\n\n      audio_sb_dsp_write(SB_DSP_SET_TIME_CONSTANT);\n      audio_sb_dsp_write(time_constant);\n\n      audio_sb_dsp_write(SB_DSP_SET_DMA_BLOCK_SIZE);\n      audio_sb_dsp_write(sb_cfg.active_dma_size);\n      audio_sb_dsp_write(sb_cfg.active_dma_size >> 8);\n\n      // start transfer\n      if(SB_VERSION_ATLEAST(sb_cfg, SB_VERSION_SB2))\n        audio_sb_dsp_write(SB_DSP_AUTOINIT_8_BIT_HI);\n      else\n        audio_sb_dsp_write(SB_DSP_AUTOINIT_8_BIT);\n    }\n#if 0\n    else\n    {\n      audio_sb_dsp_write(SB_DSP_SET_TIME_CONSTANT);\n      audio_sb_dsp_write(time_constant);\n\n      audio_sb_dsp_write(SB_DSP_SINGLE_8_BIT); // single block transfer\n      audio_sb_dsp_write(sb_cfg.active_dma_size);\n      audio_sb_dsp_write(sb_cfg.active_dma_size >> 8);\n    }\n#endif\n  }\n  return;\n\nerr:\n  sb_cfg.port = 0;\n  return;\n}\n\nvoid quit_audio_platform(void)\n{\n  // Deinitialize audio\n  if(sb_cfg.port != 0)\n  {\n    unsigned irq_vector = djgpp_irq_vector(sb_cfg.irq);\n\n    audio_sb_dsp_write(SB_DSP_SPEAKER_OFF);\n\n    if(SB_VERSION_ATLEAST(sb_cfg, SB_VERSION_SB1_REV))\n    {\n      // Stop autoinit DMA.\n      // This is usually done in the interrupt with a nonblocking write.\n      if(sb_cfg.active_dma == sb_cfg.dma16)\n        audio_sb_dsp_write(SB_DSP_EXIT_AUTO_16_BIT);\n      else\n        audio_sb_dsp_write(SB_DSP_EXIT_AUTO_8_BIT);\n\n      // Disable stereo (SBPro only).\n      audio_sb_mixer_set_stereo(false);\n    }\n\n    // undo interrupt change\n    djgpp_irq_restore(&sb_cfg.old_irq_state);\n\n    // undo vector change\n    _go32_dpmi_set_protected_mode_interrupt_vector(irq_vector, &sb_cfg.old_irq_handler);\n\n    djgpp_disable_dma(sb_cfg.active_dma);\n\n    if(sb_cfg.nearptr_buffer_enabled)\n    {\n      __dpmi_free_physical_address_mapping(&(sb_cfg.nearptr_buffer_mapping));\n      djgpp_pop_enable_nearptr();\n    }\n    else\n    {\n      free(sb_cfg.buffer);\n    }\n    __dpmi_free_dos_memory(sb_cfg.buffer_selector);\n  }\n}\n\n#endif\n"
  },
  {
    "path": "arch/djgpp/driver_sb.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __AUDIO_DRIVER_SB_H\n#define __AUDIO_DRIVER_SB_H\n\n#define SB_VERSION_ATLEAST(cfg, ver) \\\n (((cfg.version_major << 8) | (cfg.version_minor)) >= (ver))\n\n#define SB_VERSION_SB1            0x100\n#define SB_VERSION_SB1_REV        0x200 /* SB 1.x with DSP 2.00 */\n#define SB_VERSION_SB2            0x201\n#define SB_VERSION_SBPRO1         0x300\n#define SB_VERSION_SBPRO2         0x302\n#define SB_VERSION_SB16           0x400\n\n#define SB_PORT_DMA8_ACK          0xe\n#define SB_PORT_DMA16_ACK         0xf\n\n#define SBPRO_MIXER_STEREO        0x02\n#define SBPRO_MIXER_FILTER        0x20\n\n#define SB_DSP_SET_TIME_CONSTANT  0x40\n#define SB_DSP_SET_SAMPLE_RATE    0x41\n#define SB_DSP_SET_DMA_BLOCK_SIZE 0x48\n#define SB_DSP_GET_VERSION        0xE1\n\n#define SB_DSP_SINGLE_8_BIT       0x14\n#define SB_DSP_AUTOINIT_8_BIT     0x1c\n#define SB_DSP_AUTOINIT_8_BIT_HI  0x90\n#define SB_DSP_PAUSE_8_BIT        0xD0\n#define SB_DSP_PAUSE_16_BIT       0xD5\n#define SB_DSP_CONTINUE_8_BIT     0xD4\n#define SB_DSP_CONTINUE_16_BIT    0xD6\n#define SB_DSP_EXIT_AUTO_8_BIT    0xDA\n#define SB_DSP_EXIT_AUTO_16_BIT   0xD9\n#define SB_DSP_SPEAKER_ON         0xD1\n#define SB_DSP_SPEAKER_OFF        0xD3\n\n#define SB16_DSP_AUTOINIT_16_BIT  0xB6\n#define SB16_DSP_AUTOINIT_8_BIT   0xC6\n#define SB16_FORMAT_SIGNED        0x10\n#define SB16_FORMAT_STEREO        0x20\n\n#endif /* __AUDIO_DRIVER_SB_H */\n"
  },
  {
    "path": "arch/djgpp/event.c",
    "content": "/* MegaZeux\n*\n* Copyright (C) 1996 Alexis Janson\n* Copyright (C) 2010 Alan Williams <mralert@gmail.com>\n*\n* This program is free software; you can redistribute it and/or\n* modify it under the terms of the GNU General Public License as\n* published by the Free Software Foundation; either version 2 of\n* the License, or (at your option) any later version.\n*\n* This program is distributed in the hope that it will be useful,\n* but WITHOUT ANY WARRANTY; without even the implied warranty of\n* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n* General Public License for more details.\n*\n* You should have received a copy of the GNU General Public License\n* along with this program; if not, write to the Free Software\n* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n*/\n\n#define delay delay_dos\n#include <bios.h>\n#include <dpmi.h>\n#include <sys/segments.h>\n#undef delay\n#include \"../../src/event.h\"\n#include \"../../src/graphics.h\"\n#include \"../../src/platform.h\"\n\nextern struct input_status input;\n\n// Defined in interrupt.S\nextern int kbd_handler;\nextern __dpmi_paddr kbd_old_handler;\nextern volatile uint8_t kbd_buffer[256];\nextern volatile uint8_t kbd_read;\nextern volatile uint8_t kbd_write;\n\nstatic enum\n{\n  KBD_RELEASED,\n  KBD_PRESSING,\n  KBD_PRESSED\n} kbd_statmap[0x80] = {0};\nstatic uint16_t kbd_unicode[0x80] = {0};\nstatic boolean kbd_init = false;\n\nstatic int read_kbd(void)\n{\n  int ret;\n  if(kbd_read == kbd_write)\n    return -1;\n  ret = kbd_buffer[kbd_read++];\n  return ret;\n}\n\nstatic const enum keycode xt_to_internal[0x80] =\n{\n  // 0x\n  IKEY_UNKNOWN, IKEY_ESCAPE, IKEY_1, IKEY_2,\n  IKEY_3, IKEY_4, IKEY_5, IKEY_6,\n  IKEY_7, IKEY_8, IKEY_9, IKEY_0,\n  IKEY_MINUS, IKEY_EQUALS, IKEY_BACKSPACE, IKEY_TAB,\n  // 1x\n  IKEY_q, IKEY_w, IKEY_e, IKEY_r,\n  IKEY_t, IKEY_y, IKEY_u, IKEY_i,\n  IKEY_o, IKEY_p, IKEY_LEFTBRACKET, IKEY_RIGHTBRACKET,\n  IKEY_RETURN, IKEY_RCTRL, IKEY_a, IKEY_s,\n  // 2x\n  IKEY_d, IKEY_f, IKEY_g, IKEY_h,\n  IKEY_j, IKEY_k, IKEY_l, IKEY_SEMICOLON,\n  IKEY_QUOTE, IKEY_BACKQUOTE, IKEY_LSHIFT, IKEY_BACKSLASH,\n  IKEY_z, IKEY_x, IKEY_c, IKEY_v,\n  // 3x\n  IKEY_b, IKEY_n, IKEY_m, IKEY_COMMA,\n  IKEY_PERIOD, IKEY_SLASH, IKEY_RSHIFT, IKEY_KP_MULTIPLY,\n  IKEY_RALT, IKEY_SPACE, IKEY_CAPSLOCK, IKEY_F1,\n  IKEY_F2, IKEY_F3, IKEY_F4, IKEY_F5,\n  // 4x\n  IKEY_F6, IKEY_F7, IKEY_F8, IKEY_F9,\n  IKEY_F10, IKEY_NUMLOCK, IKEY_SCROLLOCK, IKEY_KP7,\n  IKEY_KP8, IKEY_KP9, IKEY_KP_MINUS, IKEY_KP4,\n  IKEY_KP5, IKEY_KP6, IKEY_KP_PLUS, IKEY_KP1,\n  // 5x\n  IKEY_KP2, IKEY_KP3, IKEY_KP0, IKEY_KP_PERIOD,\n  IKEY_SYSREQ, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_F11,\n  IKEY_F12, IKEY_UNKNOWN\n};\n\nstatic const enum keycode extended_xt_to_internal[0x80] =\n{\n  // 0x\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  // 1x\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_KP_ENTER, IKEY_LCTRL, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  // 2x\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  // 3x\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_KP_DIVIDE, IKEY_UNKNOWN, IKEY_SYSREQ,\n  IKEY_LALT, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  // 4x\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_BREAK, IKEY_HOME,\n  IKEY_UP, IKEY_PAGEUP, IKEY_UNKNOWN, IKEY_LEFT,\n  IKEY_UNKNOWN, IKEY_RIGHT, IKEY_UNKNOWN, IKEY_END,\n  // 5x\n  IKEY_DOWN, IKEY_PAGEDOWN, IKEY_INSERT, IKEY_DELETE,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN,\n  IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_UNKNOWN, IKEY_LSUPER,\n  IKEY_RSUPER, IKEY_MENU, IKEY_UNKNOWN\n};\n\nstatic enum keycode convert_ext_internal(uint8_t key)\n{\n  if(key & 0x80)\n    return extended_xt_to_internal[key & 0x7F];\n  else\n    return xt_to_internal[key];\n}\n\nstatic int extbioskey(int cmd)\n{\n  __dpmi_regs reg;\n  if(cmd < 0 || cmd >= 3)\n    return -1;\n  reg.h.ah = cmd | 0x10;\n  __dpmi_int(0x16, &reg);\n  switch(cmd)\n  {\n    default:\n    case 0:\n      return reg.x.ax;\n    case 1:\n      if(reg.x.flags & 0x40)\n        return 0;\n      else\n        return reg.x.ax;\n    case 2:\n      return reg.h.al;\n  }\n}\n\nstatic void update_lock_status(struct buffered_status *status)\n{\n  unsigned short res;\n  res = extbioskey(2);\n  status->numlock_status = !!(res & 0x20);\n  status->caps_status = !!(res & 0x40);\n}\n\nstatic uint8_t convert_bios_xt(uint8_t key)\n{\n  switch(key)\n  {\n    case 0x54: return 0x3B; // Shift-F1\n    case 0x55: return 0x3C; // Shift-F2\n    case 0x56: return 0x3D; // Shift-F3\n    case 0x57: return 0x3E; // Shift-F4\n    case 0x58: return 0x3F; // Shift-F5\n    case 0x59: return 0x40; // Shift-F6\n    case 0x5A: return 0x41; // Shift-F7\n    case 0x5B: return 0x42; // Shift-F8\n    case 0x5C: return 0x43; // Shift-F9\n    case 0x5D: return 0x44; // Shift-F10\n    case 0x5E: return 0x3B; // Ctrl-F1\n    case 0x5F: return 0x3C; // Ctrl-F2\n    case 0x60: return 0x3D; // Ctrl-F3\n    case 0x61: return 0x3E; // Ctrl-F4\n    case 0x62: return 0x3F; // Ctrl-F5\n    case 0x63: return 0x40; // Ctrl-F6\n    case 0x64: return 0x41; // Ctrl-F7\n    case 0x65: return 0x42; // Ctrl-F8\n    case 0x66: return 0x43; // Ctrl-F9\n    case 0x67: return 0x44; // Ctrl-F10\n    case 0x68: return 0x3B; // Alt-F1\n    case 0x69: return 0x3C; // Alt-F2\n    case 0x6A: return 0x3D; // Alt-F3\n    case 0x6B: return 0x3E; // Alt-F4\n    case 0x6C: return 0x3F; // Alt-F5\n    case 0x6D: return 0x40; // Alt-F6\n    case 0x6E: return 0x41; // Alt-F7\n    case 0x6F: return 0x42; // Alt-F8\n    case 0x70: return 0x43; // Alt-F9\n    case 0x71: return 0x44; // Alt-F10\n    case 0x72: return 0x37; // Ctrl-PrtSc\n    case 0x73: return 0x4B; // Ctrl-Left\n    case 0x74: return 0x4D; // Ctrl-Right\n    case 0x75: return 0x4F; // Ctrl-End\n    case 0x76: return 0x51; // Ctrl-PgDn\n    case 0x77: return 0x47; // Ctrl-Home\n    case 0x78: return 0x02; // Alt-1\n    case 0x79: return 0x03; // Alt-2\n    case 0x7A: return 0x04; // Alt-3\n    case 0x7B: return 0x05; // Alt-4\n    case 0x7C: return 0x06; // Alt-5\n    case 0x7D: return 0x07; // Alt-6\n    case 0x7E: return 0x08; // Alt-7\n    case 0x7F: return 0x09; // Alt-8\n    case 0x80: return 0x0A; // Alt-9\n    case 0x81: return 0x0B; // Alt-0\n    case 0x82: return 0x0C; // Alt--\n    case 0x83: return 0x0D; // Alt-=\n    case 0x84: return 0x49; // Ctrl-PgUp\n    // Extended keycodes\n    case 0x85: return 0x57; // F11\n    case 0x86: return 0x58; // F12\n    case 0x87: return 0x57; // Shift-F11\n    case 0x88: return 0x58; // Shift-F12\n    case 0x89: return 0x57; // Ctrl-F11\n    case 0x8A: return 0x58; // Ctrl-F12\n    case 0x8B: return 0x57; // Alt-F11\n    case 0x8C: return 0x58; // Alt-F12\n    case 0x8D: return 0x48; // Ctrl-KP-8 (Up)\n    case 0x8E: return 0x4A; // Ctrl-KP--\n    case 0x8F: return 0x4C; // Ctrl-KP-5\n    case 0x90: return 0x4E; // Ctrl-KP-+\n    case 0x91: return 0x50; // Ctrl-KP-2 (Down)\n    case 0x92: return 0x52; // Ctrl-KP-0 (Insert)\n    case 0x93: return 0x53; // Ctrl-KP-. (Delete)\n    case 0x94: return 0x0F; // Ctrl-Tab\n    case 0x95: return 0x35; // Ctrl-KP-/\n    case 0x96: return 0x37; // Ctrl-KP-*\n    case 0x97: return 0x47; // Alt-Home\n    case 0x98: return 0x48; // Alt-Up\n    case 0x99: return 0x49; // Alt-PgUp\n    case 0x9B: return 0x4B; // Alt-Left\n    case 0x9D: return 0x4D; // Alt-Right\n    case 0x9F: return 0x4F; // Alt-End\n    case 0xA0: return 0x50; // Alt-Down\n    case 0xA1: return 0x51; // Alt-PgDn\n    case 0xA2: return 0x52; // Alt-Insert\n    case 0xA3: return 0x53; // Alt-Delete\n    case 0xA4: return 0x35; // Alt-KP-/\n    case 0xA5: return 0x0F; // Alt-Tab\n    case 0xA6: return 0x1C; // Alt-KP-Enter\n    default: return key & 0x7F;\n  }\n}\n\nstatic void poll_keyboard_bios(void)\n{\n  unsigned short res;\n  uint8_t scancode;\n  uint16_t unicode;\n\n  while(extbioskey(1))\n  {\n    res = extbioskey(0);\n    scancode = convert_bios_xt(res >> 8);\n    unicode = res & 0xFF;\n    kbd_unicode[scancode] = unicode;\n    if(kbd_statmap[scancode] == KBD_RELEASED)\n      kbd_statmap[scancode] = KBD_PRESSING;\n  }\n}\n\nstatic uint16_t convert_ext_unicode(uint8_t key)\n{\n  poll_keyboard_bios();\n  return kbd_unicode[key & 0x7F];\n}\n\nstatic int get_keystat(int key)\n{\n  return kbd_statmap[key & 0x7F];\n}\n\nstatic void set_keystat(int key, int stat)\n{\n  kbd_statmap[key & 0x7F] = stat;\n}\n\nstatic boolean non_bios_key(uint8_t key)\n{\n  switch(key)\n  {\n    case 0x1D: // Right Ctrl\n    case 0x9D: // Left Ctrl\n    case 0x38: // Right Alt\n    case 0xB8: // Left Alt\n    case 0x2A: // Left Shift\n    case 0x36: // Right Shift\n    case 0xDB: // Left Super (Windows)\n    case 0xDC: // Right Super (Windows)\n    case 0xDD: // Menu\n      return true;\n    // BIOS can't return keycodes >= 0x54 due to Alt/Ctrl keycode modification\n    // shenanigans, except for F11/F12 which are given alternate keycodes\n    case 0x57:\n    case 0x58:\n      return false;\n    default:\n      if((key & 0x7F) >= 0x54)\n        return true;\n      else\n        return false;\n  }\n}\n\nstatic boolean process_keypress(int key)\n{\n  struct buffered_status *status = store_status();\n  enum keycode ikey = convert_ext_internal(key);\n  uint16_t unicode = convert_ext_unicode(key);\n\n  if((get_keystat(key) != KBD_PRESSING) && !non_bios_key(key))\n    return false;\n  set_keystat(key, KBD_PRESSED);\n\n  if(!ikey)\n  {\n    if(unicode)\n      ikey = IKEY_UNICODE;\n    else\n      return false;\n  }\n\n  if(status->keymap[ikey])\n    return false;\n\n  if((ikey == IKEY_CAPSLOCK) || (ikey == IKEY_NUMLOCK))\n    update_lock_status(status);\n\n  if((ikey == IKEY_RETURN) &&\n   get_alt_status(keycode_internal) &&\n   get_ctrl_status(keycode_internal))\n  {\n    video_toggle_fullscreen();\n    return true;\n  }\n\n#ifdef CONFIG_ENABLE_SCREENSHOTS\n  if(ikey == IKEY_F12)\n  {\n    dump_screen();\n    return true;\n  }\n#endif\n\n  if(status->key_repeat &&\n   (status->key_repeat != IKEY_LSHIFT) &&\n   (status->key_repeat != IKEY_RSHIFT) &&\n   (status->key_repeat != IKEY_LALT) &&\n   (status->key_repeat != IKEY_RALT) &&\n   (status->key_repeat != IKEY_LCTRL) &&\n   (status->key_repeat != IKEY_RCTRL))\n  {\n    // Stack current repeat key if it isn't shift, alt, or ctrl\n    if(input.repeat_stack_pointer != KEY_REPEAT_STACK_SIZE)\n    {\n      input.key_repeat_stack[input.repeat_stack_pointer] =\n       status->key_repeat;\n      input.unicode_repeat_stack[input.repeat_stack_pointer] =\n       status->unicode_repeat;\n      input.repeat_stack_pointer++;\n    }\n  }\n\n  key_press(status, ikey);\n  key_press_unicode(status, unicode, true);\n  return true;\n}\n\nstatic boolean process_keyrelease(int key)\n{\n  struct buffered_status *status = store_status();\n  enum keycode ikey = convert_ext_internal(key);\n\n  if((get_keystat(key) != KBD_PRESSED) && !non_bios_key(key))\n    return false;\n  set_keystat(key, KBD_RELEASED);\n\n  if(!ikey)\n  {\n    if(status->keymap[IKEY_UNICODE])\n      ikey = IKEY_UNICODE;\n    else\n      return false;\n  }\n\n  status->keymap[ikey] = 0;\n  if(status->key_repeat == ikey)\n  {\n    status->key_repeat = IKEY_UNKNOWN;\n    status->unicode_repeat = 0;\n  }\n  status->key_release = ikey;\n  return true;\n}\n\nstatic boolean process_key(int key)\n{\n  static int extended = 0;\n  boolean ret;\n\n  if(key == 0xE0)\n  {\n    extended = 0x80;\n    return false;\n  }\n\n  if(key & 0x80)\n    ret = process_keyrelease((key & 0x7F) | extended);\n  else\n    ret = process_keypress(key | extended);\n  extended = 0;\n\n  return ret;\n}\n\n// Defined in interrupt.S\nextern void mouse_handler(__dpmi_regs *reg);\nextern _go32_dpmi_registers mouse_regs;\nextern volatile struct mouse_event\n{\n  uint16_t cond;\n  uint16_t button;\n  int16_t dx;\n  int16_t dy;\n} mouse_buffer[256];\nextern volatile uint8_t mouse_read;\nextern volatile uint8_t mouse_write;\n\nstatic struct mouse_event mouse_last = { 0, 0, 0, 0 };\nstatic boolean mouse_init = false;\n\nstatic boolean read_mouse(struct mouse_event *mev)\n{\n  if(mouse_read == mouse_write)\n    return false;\n  *mev = mouse_buffer[mouse_read++];\n  return true;\n}\n\nstatic boolean process_mouse(struct mouse_event *mev)\n{\n  struct buffered_status *status = store_status();\n  boolean rval = false;\n\n  if(mev->cond & 0x01)\n  {\n    int mx = status->mouse_pixel_x + mev->dx - mouse_last.dx;\n    int my = status->mouse_pixel_y + mev->dy - mouse_last.dy;\n\n    if(mx < 0)\n      mx = 0;\n    if(my < 0)\n      my = 0;\n    if(mx >= 640)\n      mx = 639;\n    if(my >= 350)\n      my = 349;\n\n    status->mouse_pixel_x = mx;\n    status->mouse_pixel_y = my;\n    status->mouse_x = mx / 8;\n    status->mouse_y = my / 14;\n    status->mouse_moved = true;\n    rval = true;\n\n    mouse_last.dx = mev->dx;\n    mouse_last.dy = mev->dy;\n  }\n\n  if(mev->cond & 0x7E)\n  {\n    const uint32_t buttons[] =\n    {\n      MOUSE_BUTTON_LEFT,\n      MOUSE_BUTTON_RIGHT,\n      MOUSE_BUTTON_MIDDLE\n    };\n    int changed = mev->button ^ mouse_last.button;\n    int i, j;\n    for(i = 0, j = 1; i < 3; i++, j <<= 1)\n    {\n      if(changed & j)\n      {\n        if(mev->button & j)\n        {\n          status->mouse_button = buttons[i];\n          status->mouse_repeat = buttons[i];\n          status->mouse_button_state |= MOUSE_BUTTON(buttons[i]);\n          status->mouse_repeat_state = 1;\n          status->mouse_drag_state = -1;\n          status->mouse_time = get_ticks();\n        }\n        else\n        {\n          status->mouse_button_state &= ~MOUSE_BUTTON(buttons[i]);\n          status->mouse_repeat = 0;\n          status->mouse_drag_state = 0;\n          status->mouse_repeat_state = 0;\n        }\n        rval = true;\n      }\n    }\n    mouse_last.button = mev->button;\n  }\n  return rval;\n}\n\nboolean __update_event_status(void)\n{\n  struct mouse_event mev;\n  boolean rval = false;\n  int key;\n\n  while((key = read_kbd()) != -1)\n    rval |= process_key(key);\n  while(read_mouse(&mev))\n    rval |= process_mouse(&mev);\n\n  return rval;\n}\n\nvoid __wait_event(void)\n{\n  struct mouse_event mev;\n  boolean ret;\n  int key;\n\n  if(!kbd_init && !mouse_init)\n    return;\n\n  while((key = read_kbd()) == -1 && !(ret = read_mouse(&mev)));\n  if(key != -1)\n    process_key(key);\n  if(ret)\n    process_mouse(&mev);\n}\n\nvoid __warp_mouse(int x, int y)\n{\n  // TODO?\n}\n\nboolean __peek_exit_input(void)\n{\n  // TODO stub\n  return false;\n}\n\nboolean platform_has_screen_keyboard(void)\n{\n  return false;\n}\n\nboolean platform_show_screen_keyboard(void)\n{\n  return false;\n}\n\nboolean platform_hide_screen_keyboard(void)\n{\n  return false;\n}\n\nboolean platform_is_screen_keyboard_active(void)\n{\n  return false;\n}\n\nstatic void init_kbd(void)\n{\n  __dpmi_paddr handler;\n  __dpmi_get_protected_mode_interrupt_vector(0x09, &kbd_old_handler);\n  handler.offset32 = (unsigned long)&kbd_handler;\n  handler.selector = _my_cs();\n  if(!__dpmi_set_protected_mode_interrupt_vector(0x09, &handler))\n    kbd_init = true;\n}\n\nstatic void init_mouse(void)\n{\n  __dpmi_regs reg;\n  _go32_dpmi_seginfo cb;\n\n  reg.x.ax = 0;\n  __dpmi_int(0x33, &reg);\n  if(reg.x.ax != 0xFFFF)\n    return;\n\n  // TODO: Free this callback on quit\n  cb.pm_offset = (unsigned long)mouse_handler;\n  if(_go32_dpmi_allocate_real_mode_callback_retf(&cb, &mouse_regs))\n    return;\n  reg.x.ax = 0x000C;\n  reg.x.cx = 0x007F;\n  reg.x.dx = cb.rm_offset;\n  reg.x.es = cb.rm_segment;\n  __dpmi_int(0x33, &reg);\n\n  mouse_init = true;\n}\n\nvoid platform_init_event(void)\n{\n  init_kbd();\n  init_mouse();\n}\n"
  },
  {
    "path": "arch/djgpp/interrupt.S",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2010 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n\t.global _int_lock_start\n_int_lock_start:\n\n/* Common variables */\n\t.balign 4\n\t.global _int_ds\n_int_ds:\t\t.word\t0\n\n/* Timer interrupt */\n\t.balign 8\n\t.global _timer_old_handler\n_timer_old_handler:\t.long\t0, 0\n\t.global _timer_ticks\n_timer_ticks:\t\t.long\t0\n\t.global _timer_offset\n_timer_offset:\t\t.long\t0\n\t.global _timer_length\n_timer_length:\t\t.long\t0\n\t.global _timer_count\n_timer_count:\t\t.long\t0\n\t.global _timer_normal\n_timer_normal:\t\t.long\t0\n\n\t.balign 16\n\t.global _timer_handler\n_timer_handler:\n\tpushl\t%eax\n\tpushl\t%ds\n\tmovw\t%cs:_int_ds, %ds\n\tmovl\t_timer_length, %eax\n\taddl\t%eax, _timer_ticks\t/* timer_ticks += timer_length; */\n\tmovl\t_timer_offset, %eax\n\taddl\t_timer_count, %eax\t/* timer_offset += timer_count; */\n\tcmpl\t_timer_normal, %eax\t/* if(timer_count >= timer_normal) */\n\tjae\t1f\t\t\t/*   goto chain_time; */\n\tmovl\t%eax, _timer_offset\n\tmovb\t$0x20, %al\n\toutb\t%al, $0x20\t\t/* Send EOI to 8259 PIC */\n\tpopl\t%ds\n\tpopl\t%eax\n\tsti\n\tiret\n1:\n\tsubl\t_timer_normal, %eax\t/* timer_offset -= timer_normal */\n\tmovl\t%eax, _timer_offset\n\tpopl\t%ds\n\tpopl\t%eax\n\tljmp\t*%cs:_timer_old_handler\n\n/* Keyboard interrupt */\n\t.balign 8\n\t.global _kbd_old_handler\n_kbd_old_handler:\t.long 0, 0\n\t.global _kbd_buffer\n_kbd_buffer:\t\t.fill 256, 1, 0\n\t.global _kbd_read\n_kbd_read:\t\t.byte 0\n\t.global _kbd_write\n_kbd_write:\t\t.byte 0\n\n\t.balign 16\n\t.global _kbd_handler\n_kbd_handler:\n\tpushl\t%eax\n\tpushl\t%ebx\n\tpushl\t%ds\n\tmovw\t%cs:_int_ds, %ds\n\tinb\t$0x60, %al\t\t/* Get scan code */\n\txorl\t%ebx, %ebx\n\tmovb\t_kbd_write, %bl\n\tmovb\t_kbd_read, %bh\n\tincb\t%bl\n\tcmpb\t%bh, %bl\n\tje\t2f\t\t\t/* Buffer got full somehow */\n\tdecb\t%bl\n\txorb\t%bh, %bh\n\tmovb\t%al, _kbd_buffer(%ebx)\n\tincb\t_kbd_write\n2:\n\tpopl\t%ds\n\tpopl\t%ebx\n\tpopl\t%eax\n\tljmp\t*%cs:_kbd_old_handler\n\n/* Mouse callback */\n\t.balign 4\n\t.global _mouse_regs\n_mouse_regs:\t\t.skip 68\t/* _go32_dpmi_registers mouse_regs; */\n\t.global _mouse_buffer\n_mouse_buffer:\t\t.fill 256, 8, 0\t/* struct mvt mouse_buffer[256] */\n\t.global _mouse_read\n_mouse_read:\t\t.byte 0\t\t/* Uint8 mouse_read */\n\t.global _mouse_write\n_mouse_write:\t\t.byte 0\t\t/* Uint8 mouse_write */\n\n\t.balign 16\n\t.global _mouse_handler\n_mouse_handler:\t\t\t/* void mouse_handler(__dpmi_regs *reg) */\n\tpushl\t%ebp\n\tmovl\t%esp, %ebp\t\t/* Frame pointer shenanigans */\n\tmovzxb\t_mouse_write, %ecx\n\tmovb\t_mouse_read, %ch\n\tincb\t%cl\n\tcmpb\t%ch, %cl\n\tje\t3f\t\t\t/* Buffer got full somehow */\n\tdecb\t%cl\n\txorb\t%ch, %ch\n\tmovl\t8(%ebp), %eax\t\t/* __dpmi_regs EAX = reg */\n\tmovl\t28(%eax), %edx\t\t/* EDX = EAX->x.ax */\n\tmovw\t%dx, _mouse_buffer(,%ecx,8)\n\tmovl\t16(%eax), %edx\t\t/* EDX = EAX->x.bx */\n\tmovw\t%dx, _mouse_buffer+2(,%ecx,8)\n\tmovl\t4(%eax), %edx\t\t/* EDX = EAX->x.si */\n\tmovw\t%dx, _mouse_buffer+4(,%ecx,8)\n\tmovl\t(%eax), %edx\t\t/* EDX = EAX->x.di */\n\tmovw\t%dx, _mouse_buffer+6(,%ecx,8)\n\tincb\t_mouse_write\n3:\n\tpopl\t%ebp\n\tret\n\n\t.global _int_lock_end\n_int_lock_end:\n\tret\n"
  },
  {
    "path": "arch/djgpp/platform.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2010 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2019 Adrian Siekierka <kontakt@asie.pl>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#define delay delay_dos\n#include <errno.h>\n#include <stdlib.h>\n#include <pc.h>\n#include <dos.h>\n#include <dpmi.h>\n#include <go32.h>\n#include <crt0.h>\n#include <sys/exceptn.h>\n#include <sys/nearptr.h>\n#include <sys/segments.h>\n#undef delay\n#include \"../../src/util.h\"\n#include \"../../src/platform.h\"\n#include \"platform_djgpp.h\"\n\n/* TODO: Most of the audio callback code can't currently be locked or moved\n * outside of the callback, which causes CWSDMPI to crash during paging.\n * Disable paging altogether for now.\n */\nint _crt0_startup_flags = _CRT0_FLAG_LOCK_MEMORY;\n\nstatic int djgpp_nearptr_cnt = 0;\n\nboolean djgpp_push_enable_nearptr(void)\n{\n  if(djgpp_nearptr_cnt > 0)\n    return true;\n  if(!__djgpp_nearptr_enable())\n    return false;\n  djgpp_nearptr_cnt++;\n  return true;\n}\n\nboolean djgpp_pop_enable_nearptr(void)\n{\n  if(djgpp_nearptr_cnt <= 0)\n    return false;\n  if(djgpp_nearptr_cnt == 1)\n    __djgpp_nearptr_disable();\n  djgpp_nearptr_cnt--;\n  return true;\n}\n\nint djgpp_display_adapter_detect(void)\n{\n  __dpmi_regs reg;\n\n  // VESA SuperVGA BIOS (VBE) - GET SuperVGA INFORMATION\n  // Generally supported by SVGA cards\n  reg.x.ax = 0x4F00;\n  reg.x.di = __tb & 0xF;\n  reg.x.es = (__tb >> 4);\n  __dpmi_int(0x10, &reg);\n\n  if(reg.x.ax == 0x004F)\n  {\n    struct vbe_info vbe;\n    dosmemget(__tb, sizeof(struct vbe_info), &vbe);\n    if(memcmp(vbe.signature, \"VESA\", 4) == 0)\n    {\n      if(vbe.version >= 0x300)\n        return DISPLAY_ADAPTER_VBE30;\n      else\n\n      if(vbe.version >= 0x200)\n        return DISPLAY_ADAPTER_VBE20;\n\n      return DISPLAY_ADAPTER_SVGA;\n    }\n  }\n\n  // VIDEO - GET DISPLAY COMBINATION CODE (PS,VGA/MCGA)\n  // Generally supported by VGA cards\n  reg.x.ax = 0x1A00;\n  __dpmi_int(0x10, &reg);\n\n  if(reg.h.al == 0x1A)\n  {\n    switch(reg.h.bl) {\n      case 0x04:\n      case 0x05:\n        return DISPLAY_ADAPTER_EGA;\n      case 0x07:\n      case 0x08:\n        return DISPLAY_ADAPTER_VGA;\n      default:\n        return DISPLAY_ADAPTER_UNSUPPORTED;\n    }\n  }\n\n  // VIDEO - ALTERNATE FUNCTION SELECT (PS, EGA, VGA, MCGA) - GET EGA INFO\n  // Generally supported by EGA cards\n  reg.h.ah = 0x12;\n  reg.x.bx = 0xFF10;\n  __dpmi_int(0x10, &reg);\n\n  if(reg.h.bh != 0xFF)\n    return DISPLAY_ADAPTER_EGA;\n\n  return DISPLAY_ADAPTER_UNSUPPORTED;\n}\n\nconst char *disp_adapter_names[] =\n{\n  \"Unsupported\",\n  \"EGA\",\n  \"VGA\",\n  \"SVGA\",\n  \"SVGA (VBE 2.0+)\"\n};\n\nconst char *djgpp_display_adapter_name(int adapter)\n{\n  return disp_adapter_names[adapter];\n}\n\nint djgpp_malloc_boundary(int len_bytes, int boundary_bytes, int *selector)\n{\n  int len_segment = (len_bytes + 15) >> 4;\n  int boundary_mask = ~((boundary_bytes - 1) >> 4);\n  int segment = __dpmi_allocate_dos_memory((len_bytes + 7) >> 3, selector);\n  if(((segment + len_segment - 1) & boundary_mask) != (segment & boundary_mask))\n    segment += len_segment;\n  return segment;\n}\n\nstatic void djgpp_enable_dma16(uint8_t port, uint8_t mode, int offset, int bytes)\n{\n  int words = (bytes + 1) >> 1;\n  outportb(0xD4, 0x04 | (port & 3));\n  outportb(0xD8, 0x00);\n  outportb(0xD6, (mode & (~3)) | (port & 3));\n  outportb(0xC0 + ((port & 3) << 2), (offset >> 1) & 0xFF);\n  outportb(0xC0 + ((port & 3) << 2), (offset >> 9) & 0xFF);\n  outportb(0xC2 + ((port & 3) << 2), (words - 1) & 0xFF);\n  outportb(0xC2 + ((port & 3) << 2), (words - 1) >> 8);\n  switch(port & 3)\n  {\n    case 1:\n      outportb(0x8B, (offset >> 17));\n      break;\n    case 2:\n      outportb(0x89, (offset >> 17));\n      break;\n    case 3:\n      outportb(0x8A, (offset >> 17));\n      break;\n  }\n  outportb(0xD4, (port & 3));\n}\n\nstatic void djgpp_enable_dma8(uint8_t port, uint8_t mode, int offset, int bytes)\n{\n  outportb(0x0A, 0x04 | (port & 3));\n  outportb(0x0C, 0x00);\n  outportb(0x0B, (mode & (~3)) | (port & 3));\n  outportb(0x00 + ((port & 3) << 1), (offset) & 0xFF);\n  outportb(0x00 + ((port & 3) << 1), (offset >> 8) & 0xFF);\n  outportb(0x01 + ((port & 3) << 1), (bytes - 1) & 0xFF);\n  outportb(0x01 + ((port & 3) << 1), (bytes - 1) >> 8);\n  switch(port & 3)\n  {\n    case 0:\n      outportb(0x87, (offset >> 16));\n      break;\n    case 1:\n      outportb(0x83, (offset >> 16));\n      break;\n    case 2:\n      outportb(0x81, (offset >> 16));\n      break;\n    case 3:\n      outportb(0x82, (offset >> 16));\n      break;\n  }\n  outportb(0x0A, (port & 3));\n}\n\nvoid djgpp_enable_dma(uint8_t port, uint8_t mode, int offset, int bytes)\n{\n  if(port >= 4)\n    djgpp_enable_dma16(port, mode, offset, bytes);\n  else\n    djgpp_enable_dma8(port, mode, offset, bytes);\n}\n\nvoid djgpp_disable_dma(uint8_t port)\n{\n  if(port >= 4)\n    outportb(0xD4, 0x04 | (port & 3));\n  else\n    outportb(0x0A, 0x04 | (port & 3));\n}\n\nvoid djgpp_irq_enable(int irq, struct irq_state *old_state)\n{\n  old_state->port_21h = inportb(0x21);\n  old_state->port_A1h = -1;\n  if(irq >= 8)\n  {\n    old_state->port_A1h = inportb(0xA1);\n    outportb(0x21, old_state->port_21h & (~(1 << 2)));\n    outportb(0xA1, old_state->port_A1h & (~(1 << (irq & 7))));\n  }\n  else\n    outportb(0x21, old_state->port_21h & (~(1 << irq)));\n}\n\nvoid djgpp_irq_restore(struct irq_state *old_state)\n{\n  outportb(0x21, old_state->port_21h);\n  if(old_state->port_A1h >= 0)\n    outportb(0xA1, old_state->port_A1h);\n}\n\nvoid djgpp_irq_ack(int irq)\n{\n  if(irq >= 8)\n    outportb(0xA0, 0x20);\n  outportb(0x20, 0x20);\n}\n\nint djgpp_irq_vector(int irq)\n{\n  if(irq >= 8)\n    return 0x70 + (irq - 8);\n  else\n    return 0x08 + irq;\n}\n\n#define TIMER_CLOCK  3579545\n#define TIMER_LENGTH 8\n#define TIMER_COUNT  (TIMER_LENGTH * TIMER_CLOCK / 3000)\n#define TIMER_NORMAL 65536\n\n// Defined in interrupt.S\nextern int int_lock_start, int_lock_end;\nextern unsigned short int_ds;\n\nextern int timer_handler;\nextern __dpmi_paddr timer_old_handler;\nextern volatile uint32_t timer_ticks;\nextern volatile uint32_t timer_offset;\nextern uint32_t timer_length;\nextern uint32_t timer_count;\nextern uint32_t timer_normal;\n\nextern __dpmi_paddr kbd_old_handler;\n\nstatic boolean yieldable;\n\nvoid delay(uint32_t ms)\n{\n  ms += timer_ticks;\n  while(timer_ticks < ms)\n  {\n    if(yieldable)\n      __dpmi_yield();\n  }\n}\n\nuint64_t get_ticks(void)\n{\n  return timer_ticks;\n}\n\nstatic void set_timer(uint32_t count)\n{\n  outportb(0x43, 0x34);\n  outportb(0x40, count & 0xFF);\n  outportb(0x40, count >> 8);\n}\n\nstatic void fix_timezone(void)\n{\n  // DJGPP, unlike normal SDKs, will return -1 for time functions\n  // unless TZ is initialized. Use UTC as a default.\n  if(!getenv(\"TZ\"))\n    setenv(\"TZ\", \"UTC\", 1);\n}\n\nboolean platform_init(void)\n{\n  __dpmi_meminfo region;\n  __dpmi_paddr handler;\n  unsigned long base;\n\n  // Disable exception on Ctrl-C\n  __djgpp_set_ctrl_c(0);\n\n  // Check if DPMI yield function is supported\n  errno = 0;\n  __dpmi_yield();\n  yieldable = (errno != ENOSYS);\n\n  int_ds = _my_ds();\n\n  if(__dpmi_get_segment_base_address(_my_ds(), &base))\n  {\n    warn(\"Failed to get segment base address.\");\n    return false;\n  }\n\n  region.address = base + (unsigned long)&int_lock_start;\n  region.size = (unsigned long)&int_lock_end - (unsigned long)&int_lock_start;\n  if(__dpmi_lock_linear_region(&region))\n  {\n    warn(\"Failed to lock interrupt handler region.\");\n    return false;\n  }\n\n  timer_length = TIMER_LENGTH;\n  timer_count = TIMER_COUNT;\n  timer_normal = TIMER_NORMAL;\n  __dpmi_get_protected_mode_interrupt_vector(0x08, &timer_old_handler);\n\n  handler.offset32 = (unsigned long)&timer_handler;\n  handler.selector = _my_cs();\n\n  disable();\n  if(__dpmi_set_protected_mode_interrupt_vector(0x08, &handler))\n  {\n    enable();\n    warn(\"Failed to hook timer interrupt.\");\n    return false;\n  }\n  set_timer(timer_count);\n  enable();\n\n  __dpmi_get_protected_mode_interrupt_vector(0x09, &kbd_old_handler);\n  fix_timezone();\n  return true;\n}\n\nvoid platform_quit(void)\n{\n  __dpmi_regs reg;\n\n  // TODO: Add deinit function for event system\n  // Unhook keyboard interrupt\n  if(__dpmi_set_protected_mode_interrupt_vector(0x09, &kbd_old_handler))\n    warn(\"Failed to unhook keyboard interrupt.\");\n  // Reset mouse driver\n  reg.x.ax = 0;\n  __dpmi_int(0x33, &reg);\n\n  disable();\n  if(__dpmi_set_protected_mode_interrupt_vector(0x08, &timer_old_handler))\n    warn(\"Failed to unhook timer interrupt.\");\n  set_timer(timer_normal);\n  enable();\n\n  while(djgpp_pop_enable_nearptr())\n    ;\n}\n"
  },
  {
    "path": "arch/djgpp/platform_djgpp.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2010 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2019 Adrian Siekierka <kontakt@asie.pl>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __PLATFORM_DJGPP_H\n#define __PLATFORM_DJGPP_H\n\n#include \"../../src/compat.h\"\n\n__M_BEGIN_DECLS\n\n#define DMA_AUTOINIT  0x10\n#define DMA_READ      0x44\n#define DMA_WRITE     0x48\n\nenum\n{\n  DISPLAY_ADAPTER_UNSUPPORTED,\n  DISPLAY_ADAPTER_EGA,\n  DISPLAY_ADAPTER_VGA,\n  DISPLAY_ADAPTER_SVGA,\n  DISPLAY_ADAPTER_VBE20,\n  DISPLAY_ADAPTER_VBE30\n};\n\nstruct vbe_info\n{\n  char signature[4];\n  uint16_t version;\n  uint32_t oem_name;\n  uint32_t capabilities;\n  uint32_t modes;\n  uint16_t memory_size;\n  uint16_t oem_version;\n  uint32_t vendor_name;\n  uint32_t product_name;\n  uint32_t product_revision;\n} __attribute__((packed));\n\nstruct vbe_mode_info\n{\n  uint16_t attr;\n  uint8_t window_a_attr;\n  uint8_t window_b_attr;\n  uint16_t window_granularity;\n  uint16_t window_size;\n  uint16_t window_a_start;\n  uint16_t window_b_start;\n  uint32_t window_positioning_func;\n  uint16_t pitch;\n  uint16_t width;\n  uint16_t height;\n  uint8_t char_width;\n  uint8_t char_height;\n  uint8_t memory_planes;\n  uint8_t bpp;\n  uint8_t memory_banks;\n  uint8_t memory_model_type;\n  uint8_t bank_size;\n  uint8_t image_pages;\n  uint8_t reserved1;\n  uint8_t red_mask_size;\n  uint8_t red_field_size;\n  uint8_t green_mask_size;\n  uint8_t green_field_size;\n  uint8_t blue_mask_size;\n  uint8_t blue_field_size;\n  uint16_t reserved2;\n  uint8_t direct_color_mode_info;\n  uint32_t linear_ptr;\n  uint32_t offscreen_ptr;\n  uint16_t offscreen_size;\n} __attribute__((packed));\n\nstruct irq_state\n{\n  int port_21h;\n  int port_A1h;\n};\n\nint djgpp_display_adapter_detect(void);\nconst char *djgpp_display_adapter_name(int adapter);\nint djgpp_malloc_boundary(int len_bytes, int boundary_bytes, int *selector);\nboolean djgpp_push_enable_nearptr(void);\nboolean djgpp_pop_enable_nearptr(void);\nvoid djgpp_enable_dma(uint8_t port, uint8_t mode, int offset, int bytes);\nvoid djgpp_disable_dma(uint8_t port);\n\nvoid djgpp_irq_enable(int irq, struct irq_state *old_state);\nvoid djgpp_irq_restore(struct irq_state *old_state);\nvoid djgpp_irq_ack(int irq);\nint djgpp_irq_vector(int irq);\n\n/* Because multiple sound engines rely on floating point, the x87 FPU\n * state needs to be saved at the beginning of and reloaded at the end\n * of every audio driver callback. Otherwise, these engines will clobber\n * floating point values from normal execution (especially noticeable\n * in stb_vorbis streams corrupted during their loading process).\n *\n * Affected engines: libxmp, libmodplug, libopenmpt, stb_vorbis.\n * Also affected: libvorbis, which is unusable for other reasons.\n */\nstatic inline void djgpp_save_x87(uint8_t fpustate[108])\n{\n  __asm__(\"fsave %0\" : \"=m\"(fpustate));\n}\nstatic inline void djgpp_restore_x87(const uint8_t fpustate[108])\n{\n  __asm__(\"fwait\\n\\t\"\n          \"frstor %0\" : : \"m\"(fpustate));\n}\n\n__M_END_DECLS\n\n#endif // __PLATFORM_DJGPP_H\n"
  },
  {
    "path": "arch/djgpp/render_ega.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2010 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2019 Adrian Siekierka <kontakt@asie.pl>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#define delay delay_dos\n#include <stdlib.h>\n#include <string.h>\n#include <pc.h>\n#include <dos.h>\n#include <dpmi.h>\n#include <sys/farptr.h>\n#include <sys/segments.h>\n#include <sys/movedata.h>\n#undef delay\n#include \"../../src/graphics.h\"\n#include \"../../src/render.h\"\n#include \"../../src/renderers.h\"\n#include \"platform_djgpp.h\"\n#include \"../../src/util.h\"\n\n// Address: 0x000B8000\n// Limit  : 0x03FFF\n// Type   : 0x3\n// Flags  : Application, Ring 3, Present, 32-bit\nstatic uint8_t ega_vb_desc[8] =\n{\n  0xFF, 0x3F, 0x00, 0x80, 0x0B, 0xF3, 0x40, 0x00\n};\n\n/*\n// Address: 0x000A0000\n// Limit  : 0x0FFFF\n// Type   : 0x3\n// Flags  : Application, Ring 3, Present, 32-bit\nstatic const uint8_t ega_vb_desc[8] =\n{\n  0xFF, 0xFF, 0x00, 0x00, 0x0A, 0xF3, 0x40, 0x00\n}\n*/\n\nstatic const unsigned long ega_vb_page[4] =\n{\n  0x0000,\n  0x1000,\n  0x2000,\n  0x3000\n};\n\n#define TEXT_FLAGS_CHR 1\n#define TEXT_FLAGS_VGA 2\n\nstruct ega_render_data\n{\n  unsigned char page;\n  unsigned char smzx_swap_nibbles;\n  unsigned char flags;\n  unsigned char oldmode;\n  unsigned short vbsel;\n  unsigned char curpages;\n  boolean is_ati_card;\n  uint8_t lines;\n  uint8_t offset;\n  uint32_t x;\n  uint32_t y;\n};\n\nstatic void ega_set_14p(void)\n{\n  __dpmi_regs reg;\n  reg.x.ax = 0x1201;\n  reg.h.bl = 0x30;\n  __dpmi_int(0x10, &reg);\n}\n\nstatic void ega_set_page(int page)\n{\n  __dpmi_regs reg;\n  reg.x.ax = 0x0500 | page;\n  __dpmi_int(0x10, &reg);\n}\n\nstatic void ega_set_smzx(boolean is_ati)\n{\n  // Super MegaZeux mode:\n  // In a nutshell, this sets bit 6 of the VGA Mode Control Register.\n  // Bit 6 controls the pixel width - if 1, the pixel width is doubled,\n  // creating one 8-bit pixel instead of two 4-bit pixels. HOWEVER,\n  // normally, this is only done in Mode 13h.\n  //\n  // nVidia and some Cirrus Logic cards support this; ATI cards\n  // also \"support\" it, but swap the order of joining the pixels\n  // and require a weird horizontal pixel shift value - see below.\n  outportb(0x3C0, 0x10);\n  outportb(0x3C0, 0x4C);\n\n  if(is_ati)\n  {\n    // set horizontal pixel shift to Undefined (0.5 pixels, in theory)\n    outportb(0x3C0, 0x13);\n    outportb(0x3C0, 0x01);\n  }\n}\n\nstatic void ega_set_16p(void)\n{\n  __dpmi_regs reg;\n  reg.x.ax = 0x1202;\n  reg.h.bl = 0x30;\n  __dpmi_int(0x10, &reg);\n}\n\nstatic void ega_blink_on(void)\n{\n  __dpmi_regs reg;\n  reg.x.ax = 0x1003;\n  reg.h.bl = 0x01;\n  __dpmi_int(0x10, &reg);\n}\n\nstatic void ega_blink_off(void)\n{\n  __dpmi_regs reg;\n  reg.x.ax = 0x1003;\n  reg.h.bl = 0x00;\n  __dpmi_int(0x10, &reg);\n}\n\nstatic void ega_cursor_off(void)\n{\n  __dpmi_regs reg;\n  reg.x.ax = 0x0103;\n  reg.x.cx = 0x3F00;\n  __dpmi_int(0x10, &reg);\n}\n\nstatic void ega_set_cursor_shape(uint8_t lines, uint8_t offset)\n{\n  __dpmi_regs reg;\n  if(!lines)\n  {\n    ega_cursor_off();\n    return;\n  }\n  reg.x.ax = 0x0103;\n  reg.h.ch = offset * 8 / 14;\n  reg.h.cl = (offset + lines) * 8 / 14 - 1;\n  __dpmi_int(0x10, &reg);\n}\n\nstatic void ega_set_cursor_pos(int page, uint32_t x, uint32_t y)\n{\n  __dpmi_regs reg;\n  reg.h.ah = 0x02;\n  reg.h.bh = page;\n  reg.h.dh = y;\n  reg.h.dl = x;\n  __dpmi_int(0x10, &reg);\n}\n\nstatic unsigned char ega_get_mode(void)\n{\n  __dpmi_regs reg;\n\n  reg.h.ah = 0x0F;\n  __dpmi_int(0x10, &reg);\n  return reg.h.al & 0x7F;\n}\n\nstatic void ega_set_mode(unsigned char mode)\n{\n  __dpmi_regs reg;\n  reg.h.ah = 0x00;\n  reg.h.al = mode;\n  __dpmi_int(0x10, &reg);\n}\n\nstatic void ega_bank_char(void)\n{\n  outportb(0x03CE, 0x05);\n  outportb(0x03CF, 0x00);\n  outportb(0x03CE, 0x06);\n  outportb(0x03CF, 0x0C);\n  outportb(0x03C4, 0x04);\n  outportb(0x03C5, 0x06);\n  outportb(0x03C4, 0x02);\n  outportb(0x03C5, 0x04);\n  outportb(0x03CE, 0x04);\n  outportb(0x03CF, 0x02);\n}\n\nstatic void ega_bank_text(void)\n{\n  outportb(0x03CE, 0x05);\n  outportb(0x03CF, 0x10);\n  outportb(0x03CE, 0x06);\n  outportb(0x03CF, 0x0E);\n  outportb(0x03C4, 0x04);\n  outportb(0x03C5, 0x02);\n  outportb(0x03C4, 0x02);\n  outportb(0x03C5, 0x03);\n  outportb(0x03CE, 0x04);\n  outportb(0x03CF, 0x00);\n}\n\nstatic void ega_vsync(void)\n{\n  while(inportb(0x03DA) & 0x08)\n    ;\n  while(!(inportb(0x03DA) & 0x08))\n    ;\n}\n\nstatic boolean ega_is_ati_card(void)\n{\n  // TODO: untested.\n  char ati_magic[9];\n  dosmemget(0xC0031, 9, ati_magic);\n  return memcmp(\"761295520\", ati_magic, 9) == 0;\n}\n\nstatic boolean ega_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  struct ega_render_data *render_data =\n   (struct ega_render_data *)cmalloc(sizeof(struct ega_render_data));\n  int display, sel;\n\n  if(!render_data)\n    return false;\n\n  display = djgpp_display_adapter_detect();\n  if(display == DISPLAY_ADAPTER_UNSUPPORTED)\n  {\n    warn(\"Could not find EGA-compatible graphics card!\");\n    free(render_data);\n    return false;\n  }\n\n  sel = __dpmi_allocate_ldt_descriptors(1);\n  if(__dpmi_set_descriptor(sel, ega_vb_desc) < 0)\n  {\n    warn(\"Failed to create VRAM selector.\");\n    free(render_data);\n    return false;\n  }\n  render_data->vbsel = sel;\n\n  graphics->resolution_width = 640;\n  graphics->resolution_height = 350;\n  graphics->window_width = 640;\n  graphics->window_height = 350;\n  graphics->bits_per_pixel = 1;\n\n  if(display >= DISPLAY_ADAPTER_VGA)\n    render_data->flags = TEXT_FLAGS_VGA;\n  else\n    render_data->flags = 0;\n\n  render_data->oldmode = ega_get_mode();\n  render_data->is_ati_card = ega_is_ati_card();\n  render_data->smzx_swap_nibbles = render_data->is_ati_card;\n\n  graphics->render_data = render_data;\n  return true;\n}\n\nstatic void ega_free_video(struct graphics_data *graphics)\n{\n  struct ega_render_data *render_data = graphics->render_data;\n  if(render_data->flags & TEXT_FLAGS_VGA)\n    ega_set_16p();\n  ega_set_mode(render_data->oldmode);\n  ega_blink_on();\n  __dpmi_free_ldt_descriptor(render_data->vbsel);\n  free(render_data);\n}\n\nstatic boolean ega_set_screen_mode(struct graphics_data *graphics, unsigned mode)\n{\n  struct ega_render_data *render_data = graphics->render_data;\n  int i;\n\n  render_data->page = 0;\n  render_data->lines = 255;\n  render_data->offset = 255;\n  render_data->x = 65535;\n  render_data->y = 65535;\n\n  if(render_data->flags & TEXT_FLAGS_VGA)\n    ega_set_14p();\n\n  ega_set_mode(0x03);\n  if(mode > 0)\n    ega_set_smzx(render_data->is_ati_card);\n  ega_blink_off();\n  ega_cursor_off();\n\n  // If VGA, set the EGA palette to point to first 16 VGA palette entries\n  if(render_data->flags & TEXT_FLAGS_VGA)\n  {\n    ega_vsync();\n    for(i = 0; i < 16; i++)\n    {\n      outportb(0x03C0, i);\n      outportb(0x03C0, i);\n    }\n    // Get attribute controller back to normal\n    outportb(0x03C0, 0x20);\n  }\n\n  render_data->flags |= TEXT_FLAGS_CHR;\n\n  // Unless someone makes a cursed VGA card specifically for MegaZeux,\n  // mode 3 does not exist in hardware and never has.\n  if(mode > 2)\n    return false;\n\n  return true;\n}\n\nstatic boolean ega_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  ega_set_screen_mode(graphics, graphics->screen_mode);\n  return true;\n}\n\nstatic void ega_remap_char_range(struct graphics_data *graphics, uint16_t first, uint16_t count)\n{\n  struct ega_render_data *render_data = graphics->render_data;\n  render_data->flags |= TEXT_FLAGS_CHR;\n}\n\nstatic void ega_remap_char(struct graphics_data *graphics, uint16_t chr)\n{\n  struct ega_render_data *render_data = graphics->render_data;\n  render_data->flags |= TEXT_FLAGS_CHR;\n}\n\nstatic void ega_remap_charbyte(struct graphics_data *graphics, uint16_t chr,\n uint8_t byte)\n{\n  struct ega_render_data *render_data = graphics->render_data;\n  render_data->flags |= TEXT_FLAGS_CHR;\n}\n\nstatic void ega_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned int count)\n{\n  struct ega_render_data *render_data = graphics->render_data;\n  unsigned int i, j, c, step;\n\n  if(render_data->flags & TEXT_FLAGS_VGA)\n  {\n    if(graphics->screen_mode && render_data->smzx_swap_nibbles)\n    {\n      for(i = 0; i < count; i++)\n      {\n        outportb(0x03C8, (i >> 4) | ((i & 0x0F) << 4));\n        outportb(0x03C9, palette[i].r >> 2);\n        outportb(0x03C9, palette[i].g >> 2);\n        outportb(0x03C9, palette[i].b >> 2);\n      }\n    }\n    else\n    {\n      for(i = 0; i < count; i++)\n      {\n        outportb(0x03C8, i);\n        outportb(0x03C9, palette[i].r >> 2);\n        outportb(0x03C9, palette[i].g >> 2);\n        outportb(0x03C9, palette[i].b >> 2);\n      }\n    }\n  }\n  else\n  {\n    if(graphics->screen_mode)\n      step = 17;\n    else\n      step = 1;\n\n    // Reset index/data flip-flop\n    inportb(0x03DA);\n    for(i = j = 0; i < 16 && j < count; i++, j += step)\n    {\n      c = (palette[j].b >> 7) & 0x01;\n      c |= (palette[j].g >> 6) & 0x02;\n      c |= (palette[j].r >> 5) & 0x04;\n      c |= (palette[j].b >> 3) & 0x08;\n      c |= (palette[j].g >> 2) & 0x10;\n      c |= (palette[j].r >> 1) & 0x20;\n      outportb(0x03C0, i);\n      outportb(0x03C0, c);\n    }\n    // Get attribute controller back to normal\n    outportb(0x03C0, 0x20);\n  }\n}\n\nstatic void ega_render_graph(struct graphics_data *graphics)\n{\n  struct ega_render_data *render_data = graphics->render_data;\n  struct char_element *src = graphics->text_video;\n  unsigned long dest = ega_vb_page[render_data->page];\n  int i;\n\n  _farsetsel(render_data->vbsel);\n\n  for(i = 0; i < (SCREEN_W * SCREEN_H); i++, src++, dest += 2)\n  {\n    _farnspokew(dest, (src->bg_color << 12) | ((src->fg_color & 0x0F) << 8)\n                 | (src->char_value & 0xFF));\n  }\n}\n\nstatic void ega_hardware_cursor(struct graphics_data *graphics,\n unsigned x, unsigned y, uint16_t color, unsigned lines, unsigned offset, boolean enable)\n{\n  struct ega_render_data *render_data = graphics->render_data;\n  unsigned long dest = ega_vb_page[render_data->page];\n\n  dest += x * 2 + 1;\n  dest += y * 160;\n\n  if((lines != render_data->lines) || (offset != render_data->offset))\n  {\n    ega_set_cursor_shape(lines, offset);\n    render_data->lines = lines;\n    render_data->offset = offset;\n    render_data->curpages = 4;\n  }\n\n  if(enable)\n  {\n    if((x != render_data->x) || (y != render_data->y))\n    {\n      render_data->x = x;\n      render_data->y = y;\n      render_data->curpages = 4;\n    }\n    if(render_data->curpages)\n    {\n      ega_set_cursor_pos(render_data->page, x, y);\n      render_data->curpages--;\n    }\n    /**\n     * Disable setting the cursor color for now. This changes the color of the\n     * color of the entire char which is generally not desirable; the point of\n     * having a different cursor color than the char color is to make it more\n     * visible.\n     */\n    //_farsetsel(render_data->vbsel);\n    //_farnspokeb(dest, (_farnspeekb(dest) & 0xF0) | (color & 0x0F));\n  }\n}\n\nstatic void ega_render_mouse(struct graphics_data *graphics,\n unsigned x, unsigned y, unsigned w, unsigned h)\n{\n  struct ega_render_data *render_data = graphics->render_data;\n  unsigned long dest = ega_vb_page[render_data->page];\n  dest += x / 8 * 2 + 1;\n  dest += y / 14 * 160;\n  _farsetsel(render_data->vbsel);\n  _farnspokeb(dest, _farnspeekb(dest) ^ 0xFF);\n}\n\nstatic void ega_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct ega_render_data *render_data = graphics->render_data;\n  uint8_t *src = graphics->charset;\n  unsigned int dest = 0;\n  int i;\n\n  if(render_data->flags & TEXT_FLAGS_CHR)\n  {\n    ega_bank_char();\n    for(i = 0; i < 256; i++, src += 14, dest += 32)\n      movedata(_my_ds(), (unsigned int)src, render_data->vbsel, dest, 14);\n    ega_bank_text();\n    render_data->flags &= ~TEXT_FLAGS_CHR;\n  }\n\n  // TODO: Character set page flips.\n  ega_set_page(render_data->page);\n  render_data->page = (render_data->page + 1) & 3;\n}\n\nvoid render_ega_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = ega_init_video;\n  renderer->free_video = ega_free_video;\n  renderer->create_window = ega_create_window;\n  renderer->set_viewport = set_window_viewport_centered;\n  renderer->set_screen_mode = ega_set_screen_mode;\n  renderer->update_colors = ega_update_colors;\n  renderer->remap_char_range = ega_remap_char_range;\n  renderer->remap_char = ega_remap_char;\n  renderer->remap_charbyte = ega_remap_charbyte;\n  renderer->render_graph = ega_render_graph;\n  renderer->hardware_cursor = ega_hardware_cursor;\n  renderer->render_mouse = ega_render_mouse;\n  renderer->sync_screen = ega_sync_screen;\n}\n"
  },
  {
    "path": "arch/djgpp/render_svga.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2018, 2019 Adrian Siekierka <kontakt@asie.pl>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#define delay delay_dos\n#include <sys/nearptr.h>\n#include <dos.h>\n#include <dpmi.h>\n#include <go32.h>\n#undef delay\n\n#include \"../../src/compat.h\"\n#include \"../../src/graphics.h\"\n#include \"../../src/render.h\"\n#include \"../../src/render_layer.h\"\n#include \"../../src/renderers.h\"\n#include \"../../src/util.h\"\n\n#include \"platform_djgpp.h\"\n\nstruct svga_render_data\n{\n  __dpmi_meminfo mapping;\n  uint8_t *ptr, *ptr2;\n  uint16_t line, line2;\n  uint16_t mode, pitch;\n  uint8_t display;\n  boolean page_flip_ok;\n  int update_colors;\n};\n\nstatic boolean svga_try_mode(struct graphics_data *graphics, uint16_t mode,\n uint16_t width, uint16_t height, uint16_t bpp)\n{\n  struct svga_render_data *render_data = graphics->render_data;\n  __dpmi_regs reg;\n\n  reg.x.ax = 0x4F02; // set video mode\n  reg.x.bx = mode;\n  __dpmi_int(0x10, &reg);\n\n  if(reg.x.ax != 0x004F)\n    return false;\n\n  graphics->resolution_width = graphics->window_width = width;\n  graphics->resolution_height = graphics->window_height = height;\n  graphics->bits_per_pixel = bpp;\n  render_data->mode = mode;\n\n  return true;\n}\n\nstatic boolean svga_try_modes(struct graphics_data *graphics)\n{\n  if(graphics->bits_per_pixel >= 16)\n  {\n    // 640x480x16 with LFB\n    if(svga_try_mode(graphics, 0x4111, 640, 480, 16))\n      return true;\n  }\n\n  if(graphics->bits_per_pixel >= 8)\n  {\n    // 640x400x8 with LFB\n    if(svga_try_mode(graphics, 0x4100, 640, 400, 8))\n      return true;\n    // 640x480x8 with LFB\n    if(svga_try_mode(graphics, 0x4101, 640, 480, 8))\n      return true;\n  }\n  return false;\n}\n\nstatic boolean svga_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  static struct svga_render_data render_data;\n  struct vbe_mode_info vbe;\n  int x_offset, y_offset;\n  __dpmi_regs reg;\n\n  memset(&render_data, 0, sizeof(struct svga_render_data));\n  graphics->render_data = &render_data;\n\n  graphics->allow_resize = 0;\n  graphics->bits_per_pixel = conf->force_bpp;\n  if(graphics->bits_per_pixel != 8 && graphics->bits_per_pixel != 16)\n    graphics->bits_per_pixel = 16;\n\n  if(!djgpp_push_enable_nearptr())\n    return false;\n\n  render_data.display = djgpp_display_adapter_detect();\n  if(render_data.display < DISPLAY_ADAPTER_VBE20)\n  {\n    warn(\"Could not find VBE 2.0+-compatible graphics card!\\n\");\n    return false;\n  }\n\n  if(!svga_try_modes(graphics))\n  {\n    warn(\"Could not find supported VESA graphics mode!\\n\");\n    return false;\n  }\n\n  reg.x.ax = 0x4F01; // get video mode information\n  reg.x.cx = render_data.mode;\n  reg.x.di = __tb & 0xF; // transfer block\n  reg.x.es = (__tb >> 4);\n  __dpmi_int(0x10, &reg);\n\n  if(reg.x.ax != 0x004F)\n  {\n    warn(\"Could not query VESA graphics mode!\\n\");\n    return false;\n  }\n\n  dosmemget(__tb, sizeof(struct vbe_mode_info), &vbe);\n  render_data.mapping.address = vbe.linear_ptr;\n  render_data.mapping.size = (graphics->resolution_height * vbe.pitch);\n  // TODO: detect if we have enough memory\n  render_data.page_flip_ok = true;\n  if(render_data.page_flip_ok)\n    render_data.mapping.size <<= 1;\n  // round up to 4KB\n  render_data.mapping.size = (render_data.mapping.size + 4095) & (~4095);\n  if(__dpmi_physical_address_mapping(&render_data.mapping) != 0)\n  {\n    // set text video mode, leave\n    reg.x.ax = 0x0010;\n    __dpmi_int(0x10, &reg);\n    warn(\"Could not map VESA video memory! (requested %ld bytes at %08lX for mode %04X)\\n\",\n     render_data.mapping.size, render_data.mapping.address, render_data.mode);\n    return false;\n  }\n\n  x_offset = (graphics->resolution_width - 640) / 2;\n  y_offset = (graphics->resolution_height - 350) / 2;\n\n  render_data.ptr = (uint8_t *)(render_data.mapping.address + __djgpp_conventional_base) +\n   (vbe.pitch * y_offset) + ((vbe.bpp >> 3) * x_offset);\n\n  render_data.pitch = vbe.pitch;\n  if(render_data.page_flip_ok)\n  {\n    render_data.ptr2 = render_data.ptr + (graphics->resolution_height * vbe.pitch);\n    render_data.line = 0;\n    render_data.line2 = graphics->resolution_height;\n  }\n\n  memset((uint8_t *)(render_data.mapping.address + __djgpp_conventional_base), 0,\n   vbe.pitch * vbe.height * (render_data.page_flip_ok ? 2 : 1));\n  return true;\n}\n\nstatic void svga_free_video(struct graphics_data *graphics)\n{\n  struct svga_render_data *render_data = graphics->render_data;\n  __dpmi_free_physical_address_mapping(&(render_data->mapping));\n  djgpp_pop_enable_nearptr();\n}\n\nstatic boolean svga_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  return true;\n}\n\nstatic void svga_upload_colors(uint32_t *palette, uint32_t count)\n{\n  __dpmi_regs reg;\n\n  if(count > 256)\n    count = 256;\n  dosmemput(palette, count << 2, __tb);\n\n  reg.x.ax = 0x4F08; // DAC palette width\n  reg.x.bx = 0x0600; // ... set 6 bits per color\n  __dpmi_int(0x10, &reg);\n\n  reg.x.ax = 0x4F09; // DAC palette entries\n  reg.x.bx = 0x0080; // ... set on next retrace\n  reg.x.cx = count;\n  reg.x.dx = 0;\n  reg.x.di = __tb & 0xF;\n  reg.x.es = (__tb >> 4);\n  __dpmi_int(0x10, &reg);\n\n  if(reg.x.ax != 0x004F)\n  {\n    reg.h.bl = 0x00;\n    __dpmi_int(0x10, &reg);\n  }\n}\n\nstatic void svga_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned int count)\n{\n  struct svga_render_data *render_data = graphics->render_data;\n  uint32_t i;\n\n  if(graphics->bits_per_pixel == 16)\n  {\n    for(i = 0; i < count; i++)\n    {\n      graphics->flat_intensity_palette[i] =\n          ((palette[i].r & 0xF8) << 8)\n        | ((palette[i].g & 0xFC) << 3)\n        | ((palette[i].b & 0xF8) >> 3);\n    }\n  }\n  else\n  {\n    for(i = 0; i < count; i++)\n    {\n      graphics->flat_intensity_palette[i] =\n          ((palette[i].r & 0xFC) << 14)\n        | ((palette[i].g & 0xFC) << 6)\n        | ((palette[i].b & 0xFC) >> 2);\n    }\n    render_data->update_colors = count;\n  }\n}\n\nstatic void svga_render_graph(struct graphics_data *graphics)\n{\n  struct svga_render_data *render_data = graphics->render_data;\n  if(graphics->bits_per_pixel == 16)\n  {\n    render_graph16((uint16_t *)render_data->ptr, render_data->pitch, graphics,\n     set_colors16[graphics->screen_mode]);\n  }\n  else\n\n  if(graphics->bits_per_pixel == 8)\n  {\n    render_graph8((uint8_t *)render_data->ptr, render_data->pitch, graphics,\n     set_colors8[graphics->screen_mode]);\n  }\n}\n\nstatic void svga_render_layer(struct graphics_data *graphics,\n struct video_layer *layer)\n{\n  struct svga_render_data *render_data = graphics->render_data;\n  render_layer(render_data->ptr, SCREEN_PIX_W, SCREEN_PIX_H,\n   render_data->pitch, graphics->bits_per_pixel, graphics, layer);\n}\n\nstatic void svga_render_cursor(struct graphics_data *graphics, unsigned int x,\n unsigned int y, uint16_t color, unsigned int lines, unsigned int offset)\n{\n  struct svga_render_data *render_data = graphics->render_data;\n  uint32_t flatcolor = 0;\n\n  if(graphics->bits_per_pixel == 16)\n  {\n    flatcolor = graphics->flat_intensity_palette[color] * 0x00010001;\n  }\n  else\n\n  if(graphics->bits_per_pixel == 8)\n    flatcolor = color * 0x01010101;\n\n  render_cursor((uint32_t *)render_data->ptr, render_data->pitch,\n   graphics->bits_per_pixel, x, y, flatcolor, lines, offset);\n}\n\nstatic void svga_render_mouse(struct graphics_data *graphics,\n unsigned int x, unsigned int y, unsigned int w, unsigned int h)\n{\n  struct svga_render_data *render_data = graphics->render_data;\n  uint32_t mask;\n\n  if((graphics->bits_per_pixel == 8) && !graphics->screen_mode)\n    mask = 0x0F0F0F0F;\n  else\n    mask = 0xFFFFFFFF;\n\n  render_mouse((uint32_t *)render_data->ptr, render_data->pitch,\n   graphics->bits_per_pixel, x, y, mask, 0, w, h);\n}\n\nstatic void svga_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct svga_render_data *render_data = graphics->render_data;\n  __dpmi_regs reg;\n  uint8_t *ptr;\n  uint16_t line;\n\n  if(render_data->update_colors > 0)\n  {\n    svga_upload_colors(graphics->flat_intensity_palette, render_data->update_colors);\n    render_data->update_colors = 0;\n  }\n\n  if(render_data->page_flip_ok)\n  {\n    reg.x.ax = 0x4F07; // set display start\n    reg.x.bx = 0x0080; // ...during vertical retrace\n    reg.x.cx = 0;\n    reg.x.dx = render_data->line;\n\n    __dpmi_int(0x10, &reg);\n    if(reg.x.ax != 0x004F)\n    {\n      render_data->page_flip_ok = false;\n      return;\n    }\n\n    ptr = render_data->ptr;\n    render_data->ptr = render_data->ptr2;\n    render_data->ptr2 = ptr;\n\n    line = render_data->line;\n    render_data->line = render_data->line2;\n    render_data->line2 = line;\n  }\n}\n\nvoid render_svga_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = svga_init_video;\n  renderer->free_video = svga_free_video;\n  renderer->create_window = svga_create_window;\n  renderer->set_viewport = set_window_viewport_centered;\n  renderer->update_colors = svga_update_colors;\n  renderer->render_graph = svga_render_graph;\n  renderer->render_layer = svga_render_layer;\n  renderer->render_cursor = svga_render_cursor;\n  renderer->render_mouse = svga_render_mouse;\n  renderer->sync_screen = svga_sync_screen;\n}\n\n"
  },
  {
    "path": "arch/djgpp/thread.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2010 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2019 Adrian Siekierka <kontakt@asie.pl>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// This isn't really a mutex. Audio is handled via interrupt.\n\n#ifndef __MUTEX_DJGPP_H\n#define __MUTEX_DJGPP_H\n\n#define delay delay_dos\n#include <dos.h>\n#undef delay\n#include \"../../src/compat.h\"\n\n__M_BEGIN_DECLS\n\n#define PLATFORM_NO_THREADING\n#define THREAD_RES void *\n#define THREAD_RETURN do { return NULL; } while(0)\n\ntypedef int platform_mutex;\ntypedef int platform_thread_id;\n\nstatic inline void platform_mutex_init(platform_mutex *mutex)\n{\n  *mutex = 0;\n}\n\nstatic inline void platform_mutex_destroy(platform_mutex *mutex)\n{\n  *mutex = 0;\n}\n\nstatic inline boolean platform_mutex_lock(platform_mutex *mutex)\n{\n  *mutex = disable();\n  return true;\n}\n\nstatic inline boolean platform_mutex_unlock(platform_mutex *mutex)\n{\n  if(*mutex)\n  {\n    enable();\n    *mutex = 0;\n  }\n  return true;\n}\n\nstatic inline platform_thread_id platform_get_thread_id(void)\n{\n  return 0;\n}\n\nstatic inline boolean platform_is_same_thread(platform_thread_id a,\n platform_thread_id b)\n{\n  return true;\n}\n\n__M_END_DECLS\n\n#endif // __MUTEX_DJGPP_H\n"
  },
  {
    "path": "arch/dreamcast/CONFIG.DC",
    "content": "#!/bin/sh\n\n[ -z \"$KOS_BASE\" ] && { echo \"\\$KOS_BASE is unset. Aborting\"; exit 1; }\n\n./config.sh --platform dreamcast --prefix \"$KOS_BASE\" --optimize-size --enable-lto \\\n            --disable-editor --disable-helpsys --disable-utils \\\n            --disable-screenshots --disable-stack-protector \\\n            --enable-meter --disable-libpng --enable-tremor --enable-release\n"
  },
  {
    "path": "arch/dreamcast/Makefile.in",
    "content": "#\n# Dreamcast Makefile\n#\n\n.PHONY: package clean\n\nifeq ($(strip ${KOS_BASE}),)\n$(error \"KOS_BASE must be set in your environment.\")\nendif\n\nifeq ($(strip ${KOS_PORTS}),)\n$(error \"KOS_PORTS must be set in your environment.\")\nendif\n\nEXTRA_LICENSES += ${LICENSE_NEWLIB}\n\n#\n# Dreamcast target rules\n#\n\nCC      := kos-cc\nCXX     := kos-c++\nAR      := kos-ar\nOBJCOPY := kos-objcopy\nSTRIP   := kos-strip\n\n# Disable rules that require target code to run natively.\nSUPPRESS_HOST_TARGETS ?= 1\n\n#\n# Override library paths\n#\n\nSDL_CFLAGS := -I\"${KOS_PORTS}\"/SDL/inst/include\nSDL_LDFLAGS := -L\"${KOS_PORTS}\"/SDL/inst/lib -lSDL\n\nZLIB_CFLAGS := -I\"${KOS_PORTS}\"/zlib/inst/include\nZLIB_LDFLAGS := -L\"${KOS_PORTS}\"/zlib/inst/lib -lz\n\nVORBIS_CFLAGS := -I\"${KOS_PORTS}\"/libtremor/inst/include \\\n  -I\"${KOS_PORTS}\"/libogg/inst/include -DOV_EXCLUDE_STATIC_CALLBACKS\nVORBIS_LDFLAGS := -L\"${KOS_PORTS}\"/libtremor/inst/lib -ltremor\n\nARCH_CFLAGS  +=\nARCH_CXXFLAGS+=\nARCH_LDFLAGS += -lkosext2fs\n\n#\n# Vile hack, remove me ASAP\n#\narch/dreamcast/%.o: arch/dreamcast/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} -Wno-unused-macros -c $< -o $@\n\n#arch/dreamcast/romdisk.img:\n#\t$(KOS_GENROMFS) -f arch/dreamcast/romdisk.img -d arch/dreamcast/romdisk -v\n\n#arch/dreamcast/romdisk.o: arch/dreamcast/romdisk.img\n#\t$(KOS_BASE)/utils/bin2o/bin2o arch/dreamcast/romdisk.img romdisk arch/dreamcast/romdisk.o\n\npackage: mzx\n\t${OBJCOPY} -O binary ${mzxrun} ${mzxrun}.bin\n\t${KOS_BASE}/utils/scramble/scramble ${mzxrun}.bin 1ST_READ.BIN\n\nclean:\n\t${RM} -f ${mzxrun}.bin 1ST_READ.BIN arch/dreamcast/*.d arch/dreamcast/*.o\n\nbuild: package ${build}\n\t${CP} 1ST_READ.BIN ${build}\n\t${CP} arch/dreamcast/pad.config ${build}\n\t${RM} ${build}/${mzxrun} ${build}/${mzxrun}.debug\n\n# TODO: this needs to be integrated into the packaging step.\nMAKEIP ?= \"${KOS_BASE}\"/utils3rd/mksdiso/src/makeip\nCDI4DC ?= \"${KOS_BASE}\"/utils3rd/img4dc/cdi4dc\n\ncdi: build\n\t${RM} -r build/dist/${SUBPLATFORM}\n\t${MKDIR} -p build/dist/${SUBPLATFORM}\n\tcp \"${MAKEIP}\"/IP.TMPL \"${MAKEIP}\"/ip.txt ${build}\n\tcd ${build} && \"${MAKEIP}\"/makeip ip.txt IP.BIN\n\tgenisoimage -V MegaZeux -G IP.BIN -joliet -rock -l -o build/dist/${SUBPLATFORM}/${TARGET}-${SUBARCH}.iso ${build}\n\t\"${CDI4DC}\"/cdi4dc \\\n\t\tbuild/dist/${SUBPLATFORM}/${TARGET}-${SUBARCH}.iso \\\n\t\tbuild/dist/${SUBPLATFORM}/${TARGET}-${SUBARCH}.cdi -d\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/dreamcast/audio.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2016, 2018 Adrian Siekierka <kontakt@asie.pl>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/event.h\"\n#include \"../../src/util.h\"\n#include \"../../src/platform.h\"\n#include \"../../src/graphics.h\"\n#include \"../../src/audio/audio.h\"\n#include \"../../src/audio/audio_struct.h\"\n\n#include <kos.h>\n#include <string.h>\n\n#ifdef CONFIG_AUDIO\n\nstatic int16_t *sound_buffer;\nstatic snd_stream_hnd_t stream;\nstatic unsigned buffer_frames;\nstatic unsigned buffer_size;\nstatic boolean running;\nstatic kthread_t *sound_thread;\n\n// `smp_req` and `smp_recv` are actually BYTES, not samples.\nstatic void *dc_audio_callback(snd_stream_hnd_t hnd, int smp_req, int *smp_recv)\n{\n  size_t frames = MAX(0, smp_req) / (sizeof(int16_t) * 2);\n  if(frames > buffer_frames)\n    frames = buffer_frames;\n\n  frames = audio_mixer_render_frames(sound_buffer, frames, 2, SAMPLE_S16);\n  *smp_recv = frames * sizeof(int16_t) * 2;\n  return sound_buffer;\n}\n\nstatic void *dc_audio_thread(void *dud)\n{\n  snd_stream_init();\n\n  sound_buffer = cmalloc(buffer_size);\n\n  stream = snd_stream_alloc(dc_audio_callback, buffer_size);\n\n  snd_stream_start(stream, audio.output_frequency, 1);\n\n  while(running)\n  {\n    snd_stream_poll(stream);\n    thd_sleep(10);\n  }\n\n  snd_stream_destroy(stream);\n  snd_stream_shutdown();\n\n  free(sound_buffer);\n\n  return NULL;\n}\n\nvoid init_audio_platform(struct config_info *conf)\n{\n  if(!audio_mixer_init(conf->audio_sample_rate, conf->audio_buffer_samples, 2))\n    return;\n\n  buffer_frames = audio.buffer_frames;\n  buffer_size = buffer_frames * sizeof(int16_t) * 2 /* stereo */;\n\n  sound_thread = thd_create(0, dc_audio_thread, NULL);\n  if(sound_thread)\n    running = true;\n}\n\nvoid quit_audio_platform(void)\n{\n  if(running)\n  {\n    running = false;\n    thd_join(sound_thread, NULL);\n  }\n}\n\n#endif // CONFIG_AUDIO\n"
  },
  {
    "path": "arch/dreamcast/event.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2016 Adrian Siekierka <asiekierka@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/event.h\"\n#include \"../../src/platform.h\"\n#include \"../../src/graphics.h\"\n#include \"../../src/util.h\"\n\n#include <kos.h>\n\nextern struct input_status input;\n\nvoid platform_init_event(void)\n{\n}\n\nboolean platform_has_screen_keyboard(void)\n{\n  return false;\n}\n\nboolean platform_show_screen_keyboard(void)\n{\n  return false;\n}\n\nboolean platform_hide_screen_keyboard(void)\n{\n  return false;\n}\n\nboolean platform_is_screen_keyboard_active(void)\n{\n  return false;\n}\n\nvoid __warp_mouse(int x, int y)\n{\n}\n\nboolean dc_update_input(void);\n\nboolean __update_event_status(void)\n{\n  boolean retval = false;\n  retval |= dc_update_input();\n  return retval;\n}\n\nboolean __peek_exit_input(void)\n{\n  /* FIXME stub */\n  return false;\n}\n\nvoid __wait_event(void)\n{\n  while(!__update_event_status())\n    thd_pass();\n}\n\nstatic inline boolean check_hat(struct buffered_status *status,\n uint32_t down, uint32_t up, uint32_t key, enum joystick_hat dir)\n{\n  if(down & key)\n  {\n    joystick_hat_update(status, 0, dir, true);\n    return true;\n  }\n  else\n\n  if(up & key)\n  {\n    joystick_hat_update(status, 0, dir, false);\n    return true;\n  }\n\n  return false;\n}\n\nstatic inline boolean check_joy(struct buffered_status *status,\n  uint32_t down, uint32_t up, uint32_t key, int code)\n{\n  if(down & key)\n  {\n    joystick_button_press(status, 0, code);\n    return true;\n  }\n  else\n\n  if(up & key)\n  {\n    joystick_button_release(status, 0, code);\n    return true;\n  }\n\n  else\n  {\n    return false;\n  }\n}\n\nstatic uint32_t last_buttons;\nstatic uint32_t last_ltrig, last_rtrig;\n\nboolean dc_update_input(void)\n{\n  struct buffered_status *status = store_status();\n  maple_device_t *maple_pad, *maple_kbd;\n  cont_state_t *pad;\n  uint32_t down, held, up;\n  boolean retval = false;\n\n  maple_pad = maple_enum_type(0, MAPLE_FUNC_CONTROLLER);\n  if(maple_pad && (pad = (cont_state_t *) maple_dev_status(maple_pad)))\n  {\n    down = (pad->buttons ^ last_buttons) & pad->buttons;\n    held = (pad->buttons & last_buttons);\n    up = (pad->buttons ^ last_buttons) & last_buttons;\n\n    retval |= check_hat(status, down, up, CONT_DPAD_UP, JOYHAT_UP);\n    retval |= check_hat(status, down, up, CONT_DPAD_DOWN, JOYHAT_DOWN);\n    retval |= check_hat(status, down, up, CONT_DPAD_LEFT, JOYHAT_LEFT);\n    retval |= check_hat(status, down, up, CONT_DPAD_RIGHT, JOYHAT_RIGHT);\n    retval |= check_joy(status, down, up, CONT_A, 0);\n    retval |= check_joy(status, down, up, CONT_B, 1);\n    retval |= check_joy(status, down, up, CONT_X, 2);\n    retval |= check_joy(status, down, up, CONT_Y, 3);\n    retval |= check_joy(status, down, up, CONT_START, 7);\n    retval |= ((last_ltrig ^ pad->ltrig) >= 0x80) || ((last_rtrig ^ pad->rtrig) >= 0x80);\n\n    if(last_ltrig >= 0x80 && pad->ltrig < 0x80)\n        joystick_button_release(status, 0, 4);\n    if(last_ltrig < 0x80 && pad->ltrig >= 0x80)\n        joystick_button_press(status, 0, 4);\n    if(last_rtrig >= 0x80 && pad->rtrig < 0x80)\n        joystick_button_release(status, 0, 5);\n    if(last_rtrig < 0x80 && pad->rtrig >= 0x80)\n        joystick_button_press(status, 0, 5);\n\n    last_buttons = pad->buttons;\n    last_ltrig = pad->ltrig;\n    last_rtrig = pad->rtrig;\n  }\n  else\n  {\n    last_buttons = last_ltrig = last_rtrig = 0;\n  }\n\n  return retval;\n}\n"
  },
  {
    "path": "arch/dreamcast/pad.config",
    "content": "# Hat: Directional pad\r\n# Button1: A\r\n# Button2: B\r\n# Button3: X\r\n# Button4: Y\r\n# Button5: Left shoulder\r\n# Button6: Right shoulder\r\n# Button8: Start\r\n\r\njoy1hat = act_up, act_down, act_left, act_right\r\njoy1button1 = act_a\r\njoy1button2 = act_b\r\njoy1button3 = act_x\r\njoy1button4 = act_y\r\njoy1button5 = act_lshoulder\r\njoy1button6 = act_rshoulder\r\njoy1button8 = act_start\r\n"
  },
  {
    "path": "arch/dreamcast/platform.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007-2009 Kevin Vance <kvance@kvance.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <sys/time.h>\n#include <kos.h>\n#include \"../../src/platform.h\"\n#undef main\n\n#include \"../../src/util.h\"\n\nKOS_INIT_FLAGS(INIT_DEFAULT);\nKOS_INIT_ROMDISK(KOS_INIT_ROMDISK_NONE);\n\n// extern uint8 romdisk[];\n// KOS_INIT_ROMDISK(romdisk);\n\nvoid delay(uint32_t ms)\n{\n  thd_sleep(ms);\n}\n\nuint64_t get_ticks(void)\n{\n  return timer_ms_gettime64();\n}\n\nboolean platform_init(void)\n{\n  return true;\n}\n\nvoid platform_quit(void)\n{\n}\n\nint main(int argc, char *argv[])\n{\n  static char _argv0[] = \"/cd/megazeux/megazeux.dummy\";\n  static char *_argv[] = {_argv0};\n  dbgio_enable();\n  chdir(\"/cd\");\n  real_main(1, _argv);\n  return 0;\n}\n"
  },
  {
    "path": "arch/dreamcast/render.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2018 Adrian Siekierka <kontakt@asie.pl>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/graphics.h\"\n#include \"../../src/render.h\"\n#include \"../../src/render_layer.h\"\n#include \"../../src/renderers.h\"\n#include \"../../src/util.h\"\n\n#include <kos.h>\n\n/*\n * Eventually, this file will house a PVR-accelerated renderer.\n *\n * TODO\n */\n\nstruct dc_render_data\n{\n  pvr_ptr_t texture;\n  pvr_poly_hdr_t header;\n};\n\n\nstatic pvr_init_params_t pvr_params =\n{\n  { PVR_BINSIZE_8, PVR_BINSIZE_0, PVR_BINSIZE_8, PVR_BINSIZE_0, PVR_BINSIZE_0 },\n  32 * 32768,\n  0, 0, 1\n};\n\nstatic boolean dc_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  static struct dc_render_data render_data;\n  pvr_poly_cxt_t cxt;\n\n  pvr_init(&pvr_params);\n  pvr_set_bg_color(0.0f, 0.0f, 0.0f);\n\n  memset(&render_data, 0, sizeof(struct dc_render_data));\n  graphics->render_data = &render_data;\n\n  render_data.texture = pvr_mem_malloc(1024 * 512 * 2);\n\n  // generate polygon header\n  pvr_poly_cxt_txr(&cxt, PVR_LIST_OP_POLY, PVR_TXRFMT_NONTWIDDLED | PVR_TXRFMT_RGB565,\n   1024, 512, render_data.texture, PVR_FILTER_NEAREST);\n  pvr_poly_compile(&(render_data.header), &cxt);\n\n  graphics->allow_resize = 0;\n  graphics->bits_per_pixel = 16;\n\n  graphics->resolution_width = 640;\n  graphics->resolution_height = 350;\n  graphics->window_width = 640;\n  graphics->window_height = 350;\n  return true;\n}\n\nstatic void dc_free_video(struct graphics_data *graphics)\n{\n  struct dc_render_data *render_data = graphics->render_data;\n  pvr_mem_free(render_data->texture);\n}\n\nstatic boolean dc_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  return true;\n}\n\nstatic void dc_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned count)\n{\n  unsigned i;\n\n  for(i = 0; i < count; i++)\n  {\n    graphics->flat_intensity_palette[i] =\n       ((palette[i].r >> 3) << 11)\n     | ((palette[i].g >> 2) << 5)\n     | (palette[i].b >> 3);\n  }\n}\n\nstatic void dc_render_graph(struct graphics_data *graphics)\n{\n  struct dc_render_data *render_data = graphics->render_data;\n  render_graph16(render_data->texture, 1024 * 2, graphics,\n   set_colors16[graphics->screen_mode]);\n}\n\nstatic void dc_render_layer(struct graphics_data *graphics,\n struct video_layer *layer)\n{\n  struct dc_render_data *render_data = graphics->render_data;\n  render_layer(render_data->texture, SCREEN_PIX_W, SCREEN_PIX_H,\n   1024 * 2, 16, graphics, layer);\n}\n\nstatic void dc_render_cursor(struct graphics_data *graphics,\n unsigned x, unsigned y, uint16_t color, unsigned lines, unsigned offset)\n{\n  struct dc_render_data *render_data = graphics->render_data;\n  uint32_t flatcolor = graphics->flat_intensity_palette[color] * 0x10001;\n\n  render_cursor((uint32_t *)render_data->texture, 1024 * 2, 16, x, y,\n   flatcolor, lines, offset);\n}\n\nstatic void dc_render_mouse(struct graphics_data *graphics,\n unsigned x, unsigned y, unsigned w, unsigned h)\n{\n  struct dc_render_data *render_data = graphics->render_data;\n  render_mouse((uint32_t *)render_data->texture, 1024 * 2, 16, x, y,\n   0xFFFFFFFF, 0, w, h);\n}\n\nstatic void dc_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct dc_render_data *render_data = graphics->render_data;\n  pvr_vertex_t vtx;\n\n  pvr_wait_ready();\n  pvr_scene_begin();\n  pvr_list_begin(PVR_LIST_OP_POLY);\n\n  pvr_prim(&(render_data->header), sizeof(pvr_poly_hdr_t));\n\n  vtx.argb = PVR_PACK_COLOR(1.0f, 1.0f, 1.0f, 1.0f);\n  vtx.oargb = 0;\n  vtx.flags = PVR_CMD_VERTEX;\n  vtx.z = 1;\n\n  vtx.x = 1;\n  vtx.y = 1;\n  vtx.u = 0;\n  vtx.v = 0;\n  pvr_prim(&vtx, sizeof(vtx));\n\n  vtx.x = 640;\n  vtx.y = 1;\n  vtx.u = 640.0/1024.0;\n  vtx.v = 0;\n  pvr_prim(&vtx, sizeof(vtx));\n\n  vtx.x = 1;\n  vtx.y = 480;\n  vtx.u = 0;\n  vtx.v = 350.0/512.0;\n  pvr_prim(&vtx, sizeof(vtx));\n\n  vtx.x = 640;\n  vtx.y = 480;\n  vtx.u = 640.0/1024.0;\n  vtx.v = 350.0/512.0;\n  vtx.flags = PVR_CMD_VERTEX_EOL;\n  pvr_prim(&vtx, sizeof(vtx));\n\n  pvr_list_finish();\n  pvr_scene_finish();\n}\n\nvoid render_dc_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = dc_init_video;\n  renderer->free_video = dc_free_video;\n  renderer->create_window = dc_create_window;\n  renderer->set_viewport = set_window_viewport_centered;\n  renderer->update_colors = dc_update_colors;\n  renderer->render_graph = dc_render_graph;\n  renderer->render_layer = dc_render_layer;\n  renderer->render_cursor = dc_render_cursor;\n  renderer->render_mouse = dc_render_mouse;\n  renderer->sync_screen = dc_sync_screen;\n}\n\n"
  },
  {
    "path": "arch/dreamcast/render_fb.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2018 Adrian Siekierka <kontakt@asie.pl>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/graphics.h\"\n#include \"../../src/render.h\"\n#include \"../../src/render_layer.h\"\n#include \"../../src/renderers.h\"\n#include \"../../src/util.h\"\n\n#include <kos.h>\n\nstruct dc_fb_render_data\n{\n};\n\nstatic boolean dc_fb_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  static struct dc_fb_render_data render_data;\n\n  vid_init(DM_640x480 | DM_MULTIBUFFER, PM_RGB565);\n\n  memset(&render_data, 0, sizeof(struct dc_fb_render_data));\n  graphics->render_data = &render_data;\n\n  graphics->allow_resize = 0;\n  graphics->bits_per_pixel = 16;\n\n  graphics->resolution_width = 640;\n  graphics->resolution_height = 350;\n  graphics->window_width = 640;\n  graphics->window_height = 350;\n  return true;\n}\n\nstatic inline uint16_t *dc_fb_vram_ptr()\n{\n  return vram_s + (640 * 65); // 0,65 - 639,415\n}\n\nstatic void dc_fb_free_video(struct graphics_data *graphics)\n{\n//  struct dc_fb_render_data *render_data = graphics->render_data;\n}\n\nstatic boolean dc_fb_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  return true;\n}\n\nstatic void dc_fb_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned count)\n{\n  unsigned i;\n\n  for(i = 0; i < count; i++)\n  {\n    graphics->flat_intensity_palette[i] =\n       ((palette[i].r >> 3) << 11)\n     | ((palette[i].g >> 2) << 5)\n     | (palette[i].b >> 3);\n  }\n}\n\nstatic void dc_fb_render_graph(struct graphics_data *graphics)\n{\n//  struct dc_fb_render_data *render_data = graphics->render_data;\n  render_graph16(dc_fb_vram_ptr(), 640 * 2, graphics,\n   set_colors16[graphics->screen_mode]);\n}\n\nstatic void dc_fb_render_layer(struct graphics_data *graphics,\n struct video_layer *vlayer)\n{\n//  struct dc_fb_render_data *render_data = graphics->render_data;\n  render_layer(dc_fb_vram_ptr(), 640, 350, 640 * 2, 16, graphics, vlayer);\n}\n\nstatic void dc_fb_render_cursor(struct graphics_data *graphics,\n unsigned x, unsigned y, uint16_t color, unsigned lines, unsigned offset)\n{\n//  struct dc_fb_render_data *render_data = graphics->render_data;\n  uint32_t flatcolor = graphics->flat_intensity_palette[color] * 0x10001;\n\n  render_cursor((uint32_t *)dc_fb_vram_ptr(), 640 * 2, 16, x, y,\n   flatcolor, lines, offset);\n}\n\nstatic void dc_fb_render_mouse(struct graphics_data *graphics,\n unsigned x, unsigned y, unsigned w, unsigned h)\n{\n//  struct dc_fb_render_data *render_data = graphics->render_data;\n  render_mouse((uint32_t *)dc_fb_vram_ptr(), 640 * 2, 16, x, y,\n   0xFFFFFFFF, 0, w, h);\n}\n\nstatic void dc_fb_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n//  struct dc_fb_render_data *render_data = graphics->render_data;\n  vid_flip(-1);\n}\n\nvoid render_dc_fb_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = dc_fb_init_video;\n  renderer->free_video = dc_fb_free_video;\n  renderer->create_window = dc_fb_create_window;\n  renderer->set_viewport = set_window_viewport_centered;\n  renderer->update_colors = dc_fb_update_colors;\n  renderer->render_graph = dc_fb_render_graph;\n  renderer->render_layer = dc_fb_render_layer;\n  renderer->render_cursor = dc_fb_render_cursor;\n  renderer->render_mouse = dc_fb_render_mouse;\n  renderer->sync_screen = dc_fb_sync_screen;\n}\n\n"
  },
  {
    "path": "arch/dreamcast/thread.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2016, 2018 Adrian Siekierka <asiekierka@gmail.com>\n * Copyright (C) 2022 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __THREAD_DREAMCAST_H\n#define __THREAD_DREAMCAST_H\n\n#include \"../../src/compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <kos.h>\n#include <stdbool.h>\n\n#define THREAD_RES void *\n#define THREAD_RETURN do { return NULL; } while(0)\n\ntypedef condvar_t platform_cond;\ntypedef mutex_t platform_mutex;\ntypedef semaphore_t platform_sem;\ntypedef kthread_t *platform_thread;\ntypedef kthread_t *platform_thread_id;\ntypedef void *(*platform_thread_fn)(void *);\n\nstatic inline boolean platform_mutex_init(platform_mutex *mutex)\n{\n  if(mutex_init(mutex, MUTEX_TYPE_NORMAL))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_mutex_destroy(platform_mutex *mutex)\n{\n  if(mutex_destroy(mutex))\n    return false;\n  return true;\n}\n\nstatic inline bool platform_mutex_lock(platform_mutex *mutex)\n{\n  if(mutex_trylock(mutex))\n    return false;\n  return true;\n}\n\nstatic inline bool platform_mutex_unlock(platform_mutex *mutex)\n{\n  if(mutex_unlock(mutex))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_init(platform_cond *cond)\n{\n  if(cond_init(cond))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_destroy(platform_cond *cond)\n{\n  if(cond_destroy(cond))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_wait(platform_cond *cond,\n platform_mutex *mutex)\n{\n  if(cond_wait(cond, mutex))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_timedwait(platform_cond *cond,\n platform_mutex *mutex, unsigned int timeout_ms)\n{\n  if(cond_wait_timed(cond, mutex, (int)timeout_ms))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_signal(platform_cond *cond)\n{\n  if(cond_signal(cond))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_broadcast(platform_cond *cond)\n{\n  if(cond_broadcast(cond))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_init(platform_sem *sem, unsigned init_value)\n{\n  if(sem_init(sem, init_value))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_destroy(platform_sem *sem)\n{\n  if(sem_destroy(sem))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_wait(platform_sem *sem)\n{\n  if(sem_wait(sem))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_post(platform_sem *sem)\n{\n  if(sem_signal(sem))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_thread_create(platform_thread *thread,\n platform_thread_fn start_function, void *data)\n{\n  platform_thread ret = thd_create(0, start_function, data);\n  if(ret)\n  {\n    *thread = ret;\n    return true;\n  }\n  return false;\n}\n\nstatic inline boolean platform_thread_join(platform_thread *thread)\n{\n  if(thd_join(*thread, NULL))\n    return false;\n  return true;\n}\n\nstatic inline platform_thread_id platform_get_thread_id(void)\n{\n  return thd_get_current();\n}\n\nstatic inline boolean platform_is_same_thread(platform_thread_id a,\n platform_thread_id b)\n{\n  return a == b;\n}\n\n__M_END_DECLS\n\n#endif // __THREAD_DREAMCAST_H\n"
  },
  {
    "path": "arch/emscripten/CONFIG.HTML5",
    "content": "#!/bin/sh\n\n# NOTE: disabling softscale to save a little space since it's redundant\n# with software + canvas scaling. These should probably have their performace\n# compared at some point though.\n\n./config.sh --platform emscripten --enable-release --enable-lto \\\n            --enable-sdl3 --disable-softscale \"$@\"\n"
  },
  {
    "path": "arch/emscripten/Makefile.in",
    "content": "CC      = emcc\nCXX     = em++\nAR      = emar\nOBJCOPY = /bin/true\nSTRIP   = /bin/true\n\n# Block --host, which will break things.\nCROSS_COMPILE =\n# Disable rules that require target code to run natively.\nSUPPRESS_HOST_TARGETS ?= 1\n\nifeq (${BUILD_SDL},3)\nSDL_CFLAGS = -s USE_SDL=3\nSDL_LDFLAGS = -s USE_SDL=3\nendif\nifeq (${BUILD_SDL},2)\nSDL_CFLAGS = -s USE_SDL=2\nSDL_LDFLAGS = -s USE_SDL=2\nendif\n\nVORBIS_CFLAGS = -s USE_OGG=1 -s USE_VORBIS=1\nVORBIS_LDFLAGS = -s USE_OGG=1 -s USE_VORBIS=1\n\nZLIB_CFLAGS = -s USE_ZLIB=1\nZLIB_LDFLAGS = -s USE_ZLIB=1\n\nLIBPNG_CFLAGS = -s USE_LIBPNG=1\nLIBPNG_LDFLAGS = -s USE_LIBPNG=1\n\nBINEXT = .js\n\nARCH_LDFLAGS += -s ALLOW_MEMORY_GROWTH=1\n# ARCH_LDFLAGS += -s TOTAL_MEMORY=134217728 -s ALLOW_MEMORY_GROWTH=0\n\nARCH_LDFLAGS += -s MODULARIZE=1 -s 'EXPORTED_RUNTIME_METHODS=[\"FS\"]'\n\nifeq (${BUILD_RENDER_GL_PROGRAM},1)\nARCH_LDFLAGS += -s FULL_ES2=1\nendif\n\nARCH_LDFLAGS += -s ASYNCIFY=1 -s ASYNCIFY_STACK_SIZE=1048576\n# TODO: Removing the whitelist should allow the editor to work.\nARCH_LDFLAGS += -s ASYNCIFY_WHITELIST=@arch/emscripten/whitelist.json\nARCH_LDFLAGS += -s 'ASYNCIFY_IMPORTS=[\"emscripten_sleep\"]'\n\nARCH_LDFLAGS += -s ENVIRONMENT=web\n\n# Fix warnings caused by broken Emscripten macros.\nARCH_CFLAGS += -Wno-gnu-zero-variadic-macro-arguments\n\n# Enable source map for debugging if this is supplied by the user.\nifneq (${SOURCE_MAP_BASE},)\nARCH_CFLAGS += -g4 --source-map-base ${SOURCE_MAP_BASE}\nARCH_CXXFLAGS += -g4 --source-map-base ${SOURCE_MAP_BASE}\nARCH_LDFLAGS += -g4 --source-map-base ${SOURCE_MAP_BASE}\nendif\n\nifeq (${DEBUG},1)\nARCH_LDFLAGS += -s ASSERTIONS=1 --profiling-funcs\nEMSCRIPTEN_FRONTEND_BUILD_PROFILE = build\nelse\nEMSCRIPTEN_FRONTEND_BUILD_PROFILE = build-release\nendif\n\nclean:\n\t$(if ${V},,@echo \"  RM      \" arch/emscripten/web/mzxrun_web.js\"*\")\n\t$(if ${V},,@echo \"  RM      \" mzxrun.js.orig.js)\n\t$(if ${V},,@echo \"  RM      \" mzxrun.js.mem)\n\t$(if ${V},,@echo \"  RM      \" mzxrun.wasm)\n\t$(if ${V},,@echo \"  RM      \" mzxrun.wasm.map)\n\t$(if ${V},,@echo \"  RM      \" emzip.js)\n\t$(if ${V},,@echo \"  RM      \" emzip.wasm)\n\t${RM} -f arch/emscripten/web/mzxrun_web.js*\n\t${RM} -f mzxrun.js.mem mzxrun.js.orig.js mzxrun.wasm mzxrun.wasm.map\n\t${RM} -f emzip.js* emzip.wasm\n\nall: emzip.js\n\n# NOTE: hack, core_obj and io_obj aren't defined yet...\nemzip_objs := \\\n  src/io/.build/vio_no_vfs.o \\\n  src/io/.build/zip.o \\\n  src/io/.build/zip_stream.o\n\nemzip.js: arch/emscripten/emzip.c ${emzip_objs}\n\t$(if ${V},,@echo \"  LINK    \" $@)\n\t${CC} -O3 \\\n\t-s ENVIRONMENT=web \\\n\t-s ALLOW_MEMORY_GROWTH=1 \\\n\t-s MODULARIZE=1 \\\n\t-s EXPORT_NAME=\"'emzip'\" \\\n\t-s \"EXPORTED_RUNTIME_METHODS=['_main', 'UTF8ToString']\" \\\n\t$< ${emzip_objs} ${ZLIB_LDFLAGS} -o $@\n\n${build}/mzxrun.js: ${build}\n\t@cp mzxrun.js ${build}/\n\n${build}/mzxrun.wasm: ${build}\n\t@cp mzxrun.wasm ${build}/\n\t@(if [ -f mzxrun.wasm.map ]; then cp mzxrun.wasm.map ${build}/ && rm -rf ${build}/src && cp -r src/ ${build}/; fi)\n\n${build}/emzip.js: ${build} emzip.js ${build}/emzip.wasm\n\t@cp emzip.js ${build}/\n\n${build}/emzip.wasm: ${build}\n\t@cp emzip.wasm ${build}/\n\nbuild: ${build} ${build}/mzxrun.js ${build}/mzxrun.wasm ${build}/assets ${build}/emzip.js\n\t@rm -rf ${build}/config.txt\n\t@(if [ -d ${build}/assets ]; then cd ${build} && rm -rf assets.zip && zip -9 -r assets.zip assets/ && rm -r assets/; fi)\n\t@(if [ ! -d \"arch/emscripten/web/node_modules\" ]; then cd arch/emscripten/web && npm install; fi)\n\t@(cd arch/emscripten/web && npm run $(EMSCRIPTEN_FRONTEND_BUILD_PROFILE))\n\t@cp -r arch/emscripten/web/res/* ${build}/\n\t@cp arch/emscripten/web/mzxrun_web.js ${build}/\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/emscripten/README",
    "content": "PREPARATION\n\nYou need to install the following versions of the following libraries/tools:\n\n  - Emscripten SDK (latest-upstream; https://emscripten.org/docs/getting_started/downloads.html)\n      - instead of \"emsdk install/activate latest\", please use \"latest-upstream\".\n  - node.js (tested on v12.7.0, a variety of versions should work)\n\nCONFIGURING\n\n\"./config.sh --platform emscripten --enable-release\" is optimal for a web build as of writing.\n\nBUILDING\n\nFor the moment, you need to build with:\n\nmake\nmake build\n\nThis will emit the necesary suite in build/emscripten/.\n\nPACKAGING THE BUILD\n\nYou can then use the usual \"make archive\" to build a\nbuild/dist/emscripten/mzxgit-emscripten.zip file for distribution.\nNote: you can omit the \"make build\" step if using \"make archive\".\n\nTROUBLESHOOTING\n\nIf \"assets.zip\" is missing from build/emscripten, that is because the current Makefile is a bit wonky.\nPlease re-build with \"make -B build\" or \"make -B archive\".\n\nIf \"unreachable\" errors start appearing at random moments in the web browser's console, please try disabling the\n\"ASYNCIFY_WHITELIST\" argument. That may fix it, in which case the whitelist must be updated.\n\n"
  },
  {
    "path": "arch/emscripten/emzip.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Emscripten-specific zip utility.\n * This probably uses a bit more memory than UZIP as the archive needs to be\n * copied into module memory, but supports more decompression methods than UZIP.\n */\n\n#include \"../../src/io/zip.h\"\n#include \"../../src/utils/utils_alloc.h\"\n#include <stdlib.h>\n#include <emscripten.h>\n\nint error(const char *message, unsigned int a, unsigned int b, unsigned int c)\n{\n  fprintf(stderr, \"%s\\n\", message);\n  exit(-1);\n}\n\nEMSCRIPTEN_KEEPALIVE\nstruct zip_archive *emzip_open(const void *src, size_t src_len)\n{\n  return zip_open_mem_read(src, src_len);\n}\n\nEMSCRIPTEN_KEEPALIVE\nsize_t emzip_length(struct zip_archive *zp)\n{\n  size_t u_size;\n  if(!zip_get_next_uncompressed_size(zp, &u_size))\n    return u_size;\n  return 0;\n}\n\nEMSCRIPTEN_KEEPALIVE\nconst char *emzip_filename(struct zip_archive *zp)\n{\n  char *filename = cmalloc(MAX_PATH);\n  if(zip_get_next_name(zp, filename, MAX_PATH-1))\n  {\n    free(filename);\n    return NULL;\n  }\n  return filename;\n}\n\nEMSCRIPTEN_KEEPALIVE\nconst char *emzip_extract(struct zip_archive *zp)\n{\n  size_t u_size;\n\n  if(!zip_get_next_uncompressed_size(zp, &u_size))\n  {\n    char *file = cmalloc(u_size);\n    if(!zip_read_file(zp, file, u_size, &u_size))\n      return file;\n\n    free(file);\n  }\n  return NULL;\n}\n\nEMSCRIPTEN_KEEPALIVE\nvoid emzip_skip(struct zip_archive *zp)\n{\n  zip_skip_file(zp);\n}\n\nEMSCRIPTEN_KEEPALIVE\nvoid emzip_close(struct zip_archive *zp)\n{\n  zip_close(zp, NULL);\n}\n\n// Emscripten optimization sometimes clobbers free() despite indicating it\n// should be kept alive, so just expose malloc and free functions here to be\n// safe.\nEMSCRIPTEN_KEEPALIVE\nvoid *emzip_malloc(size_t length)\n{\n  return cmalloc(length);\n}\n\nEMSCRIPTEN_KEEPALIVE\nvoid emzip_free(void *ptr)\n{\n  free(ptr);\n}\n\nint main(void)\n{\n  return 0;\n}\n"
  },
  {
    "path": "arch/emscripten/web/package.json",
    "content": "{\n  \"name\": \"megazeux-web\",\n  \"version\": \"0.1.0\",\n  \"description\": \"MegaZeux web frontend\",\n  \"type\": \"module\",\n  \"main\": \"src/index.js\",\n  \"scripts\": {\n    \"build\": \"node_modules/.bin/rollup -c\",\n    \"build-release\": \"node_modules/.bin/rollup -c --environment MINIFY\"\n  },\n  \"browserslist\": \"firefox >= 55, and_ff >= 55, chrome >= 60, and_chr >= 60, ios >= 12, safari >= 12, samsung >= 5, and_uc >= 11.8, opera >= 47, op_mob >= 47, baidu >= 7\",\n  \"author\": \"asie\",\n  \"license\": \"GPL-2.0-or-later\",\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.26.0\",\n    \"@babel/preset-env\": \"^7.26.0\",\n    \"rollup\": \"^4.24.3\",\n    \"@rollup/plugin-babel\": \"^6.0.4\",\n    \"@rollup/plugin-node-resolve\": \"^15.3.0\",\n    \"@rollup/plugin-terser\": \"^0.4.4\"\n  },\n  \"dependencies\": {\n    \"core-js\": \"^3.39.0\"\n  }\n}\n"
  },
  {
    "path": "arch/emscripten/web/res/docs/emscripten_readme.txt",
    "content": "MegaZeux HTML5 port: (very) rough instructions\n\nindex.html provides an effective template of a MegaZeux instance which will span the whole browser frame.\n\nBasic requirements:\n\n* A canvas object specified in options.render.canvas.\n* A presence of all the files herein other than \"index.html\" in a certain directory.\n\nThe entrypoint is \"MzxrunLoad(options);\".\n\nMandatory option keys:\n\n* render.canvas: the element of the desired canvas.\n* path: the (relative or absolute) path to the MegaZeux engine files.\n* files: an array containing file entries to be loaded.\n\nOptional option keys:\n\n* storage: define the settings for persistent storage. If not present, save files etc. will be stored in memory, and lost with as little as a page refresh.\n    * type: can be \"auto\" (preferred), \"localstorage\" or \"indexeddb\".\n    * database: for all of the above, a required database name. If you're hosting multiple games on the same domain, you may want to make this unique.\n* config: a string which will be appended to \"config.txt\".\n\nFile entries can be either a string (denoting the relative or absolute path to a .ZIP file), or an array of a string and an options object containing the following optional keys:\n\n* filenameFilter: function which returns true if a given filename should be accepted. This runs after filenameMap.\n* filenameMap: filename mapping - can be in one of three forms:\n    * string - describes the subdirectory the ZIP's data is loaded to\n    * object - describes a mapping of ZIP filenames to target filesystem filenames; no other files are loaded\n    * function - accepts a ZIP filename and returns a target filesystem filename; return \"undefined\" to not load a file\n"
  },
  {
    "path": "arch/emscripten/web/res/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<style type=\"text/css\">\n/* Note: the background color of the canvas needs to be black or WebGL blending won't work properly.\n * https://stackoverflow.com/questions/6160004/how-does-blending-work-with-glsl-es-fragment-shaders-in-webgl\n * https://www.digitalmzx.com/forums/index.php?app=tracker&showissue=788\n */\n* { border: 0; margin: 0; padding: 0; }\n#canvas {\n\twidth: 100%; height: 100%; display: block; position: absolute; top: 0; left: 0; overflow: hidden;\n\tbackground: url('play.png') center center / contain no-repeat #000;\n}\n</style>\n</head>\n<body>\n<canvas id=\"canvas\" width=\"640\" height=\"350\" oncontextmenu=\"event.preventDefault()\" tabindex=\"0\"></canvas>\n<script type=\"text/javascript\" src=\"./mzxrun_loader.js\"></script>\n<script type=\"text/javascript\">\n(function() {\n\t// NOTE: the canvas ID being \"canvas\" is a hardcoded requirement somewhere in Emscripten and/or SDL.\n\tvar mzxCanvas = document.getElementById(\"canvas\");\n\tvar mzxLoadAttempted = false;\n\n\t// The initialization process should be triggered by a click - audio enabling!\n\t// If you're embedding this on itch.io, this click makes the itch.io enabling click\n\t// redundant. If you can get it to work, the itch.io option \"Automatically start page\n\t// on load\" is supposed to skip the itch.io click. Notably, the click here is required\n\t// to make audio work for Safari and itch.io's click isn't good enough.\n\t//\n\t// However, if you can't get rid of the itch.io click and don't want users to click\n\t// twice, worst case you can just comment this line out.\n\tmzxCanvas.onclick = function()\n\t{\n\t\tif (mzxLoadAttempted) return;\n\t\tmzxLoadAttempted = true;\n\t\tmzxCanvas.style.backgroundImage = \"none\";\n\t\tMzxrunLoad({\n\t\t\trender: {\n\t\t\t\tcanvas: mzxCanvas\n\t\t\t},\n\t\t\tstorage: { // optional - if not present, saves to RAM (lost on page refresh)\n\t\t\t\ttype: \"auto\", // can also be \"localstorage\" or \"indexeddb\"; \"auto\" is recommended\n\t\t\t\tdatabase: \"test_database\"\n\t\t\t},\n\t\t\tpath: \"./\",\n\t\t\tfiles: [\n\t\t\t\t[\"assets.zip\", \"\"], // used by MegaZeux - do not touch!\n\t\t\t\t[\"my_game.zip\", \"game/\"] // edit this one - should contain your game\n\t\t\t],\n\t\t\tconfig: // edit this string to set config.txt options for your game.\n\t\t\t\t\"# Set the startup path and world.\\n\"+\n\t\t\t\t\"startup_path = /data/game\\n\"+\n\t\t\t\t\"startup_file = caverns.mzx\\n\"\n\t\t});\n\t}\n})();\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "arch/emscripten/web/res/mzxrun_loader.js",
    "content": "/*!\n * MegaZeux\n *\n * Copyright (C) 2018, 2019 Adrian Siekierka\n * Copyright (C) 2020 Ian Burgmyer <spectere@gmail.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n(function() {\n\tconst audioCtx = new (window.AudioContext || window.webkitAudioContext)();\n\tconst audioUnlockEvents = [ \"keydown\", \"mousedown\", \"touchstart\" ];\n\tlet swapSdlContext = false;\n\n\twindow.replaceSdlAudioContext = function(sdl)\n\t{\n\t\tif(!swapSdlContext || sdl.audioContext.state !== \"suspended\")\n\t\t\treturn;\n\n\t\t// Fetch some data from the original script processor node and create\n\t\t// a new one.\n\t\tlet node = sdl.audio.scriptProcessorNode;\n\t\tlet buffer = node.bufferSize;\n\t\tlet channels = node.channelCount;\n\t\tlet audioProcessFunc = node.onaudioprocess;\n\n\t\t// If any of the values is obviously invalid, abort.\n\t\tif(typeof(channels) !== \"number\" || channels < 1)\n\t\t\treturn;\n\n\t\t// Replace the forever-locked context that SDL2 creates in favor\n\t\t// of the unlocked one we created earlier.\n\t\tsdl.audioContext = audioCtx;\n\n\t\tsdl.audio.scriptProcessorNode =\n\t\t\tsdl.audioContext.createScriptProcessor(buffer, 0, channels);\n\n\t\t// Connect the script processor to the destination and migrate SDL's\n\t\t// onaudioprocess event function over so that it can use the new node.\n\t\tsdl.audio.scriptProcessorNode.onaudioprocess = audioProcessFunc;\n\t\tsdl.audio.scriptProcessorNode.connect(sdl.audioContext.destination);\n\t}\n\n\tfunction addAudioEventListeners()\n\t{\n\t\taudioUnlockEvents.forEach(\n\t\t\tev => window.addEventListener(ev, unlockAudioContext, false)\n\t\t);\n\t}\n\n\tfunction removeAudioEventListeners()\n\t{\n\t\taudioUnlockEvents.forEach(\n\t\t\tev => window.removeEventListener(ev, unlockAudioContext)\n\t\t);\n\t}\n\n\tfunction unlockAudioContext()\n\t{\n\t\t// Safari requires some special handling to unlock the audio context.\n\t\t// We start unlocking it here so that the audio will be ready when SDL\n\t\t// initializes.\n\n\t\t// Gecko seems to automatically start the audio context very shortly\n\t\t// after it's created, so this helps us detect that behavior.\n\t\tif(audioCtx.state !== \"suspended\")\n\t\t{\n\t\t\tremoveAudioEventListeners();\n\t\t\treturn;\n\t\t}\n\n\t\t// Create a short buffer, set up the context, and play it.\n\t\tconsole.log(\"Unlocking audio context\");\n\t\tvar buffer = audioCtx.createBuffer(1, 1, 22050);\n\t\tvar source = audioCtx.createBufferSource();\n\t\tsource.buffer = buffer;\n\n\t\tsource.connect(audioCtx.destination);\n\t\tsource.start(0);\n\n\t\t// The newly unlocked context is ready to be swapped in.\n\t\tswapSdlContext = true;\n\t\tremoveAudioEventListeners();\n\t}\n\n\tdocument.addEventListener(\"DOMContentLoaded\", addAudioEventListeners);\n})();\n\nMzxrunLoad = function(options, callback) {\n\tif (!options.path) throw \"Missing option: path!\";\n\n\tvar scripts_array = [];\n\tvar script_ldr = function() {\n\t\tif (scripts_array.length == 0) {\n\t\t\tif (callback) {\n\t\t\t\tcallback(MzxrunInitialize(options));\n\t\t\t} else {\n\t\t\t\tMzxrunInitialize(options);\n\t\t\t}\n\t\t} else {\n\t\t\tvar scrSrc = scripts_array.shift();\n\t\t\tvar scr = document.createElement(\"script\");\n\t\t\tscr.onload = script_ldr;\n\t\t\tscr.src = scrSrc;\n\t\t\tdocument.body.appendChild(scr);\n\t\t}\n\t}\n\n\tscripts_array = [\n\t\toptions.path+\"uzip.min.js\",\n\t\toptions.path+\"emzip.js\",\n\t\toptions.path+\"mzxrun.js\",\n\t\toptions.path+\"mzxrun_web.js\"\n\t];\n\n\tscript_ldr();\n}\n"
  },
  {
    "path": "arch/emscripten/web/rollup.config.js",
    "content": "import resolve from '@rollup/plugin-node-resolve';\nimport babel from '@rollup/plugin-babel';\nimport terser from '@rollup/plugin-terser';\n\nlet plugins = [\n    resolve(),\n    babel({\n      exclude: 'node_modules/**' // only transpile our source code\n    })\n];\n\nif (process.env.MINIFY) {\n    plugins.push(terser());\n}\n\nexport default {\n  input: './src/index.js',\n  plugins: plugins,\n  output: {\n    file: 'mzxrun_web.js',\n    format: 'iife', // use browser globals\n    sourcemap: true\n  }\n};\n"
  },
  {
    "path": "arch/emscripten/web/src/index.js",
    "content": "/*!\n * MegaZeux\n *\n * Copyright (C) 2018, 2019 Adrian Siekierka\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { getIndexedDB, getLocalStorage, drawErrorMessage } from \"./util.js\";\nimport { createInMemoryStorage, createBrowserBackedStorage, wrapAsyncStorage, createIndexedDbBackedAsyncStorage, createCompositeStorage, createZipStorage } from \"./storage.js\";\nimport { wrapStorageForEmscripten } from \"./storage_emscripten.js\";\nimport { zip } from \"./zip.js\";\n\nclass LoadingScreen {\n    constructor(canvas, ctx, options) {\n        this.canvas = canvas;\n        this.ctx = ctx;\n        this.loaded = false;\n        this.path = options.path;\n\n        const self = this;\n    }\n\n    _drawBackground() {\n        const self = this;\n        return new Promise((resolve, reject) => {\n            const loadingImage = new Image();\n            loadingImage.onload = function() {\n                const w = loadingImage.width;\n                const h = loadingImage.height;\n                self.ctx.drawImage(loadingImage,0,0,w,h,(self.canvas.width - w)/2,(self.canvas.height - h)/2,w,h);\n\n                self.loaded = true;\n                resolve(true);\n            };\n            loadingImage.src = self.path + \"loading.png\";\n        });\n    }\n\n    progress(p) {\n        if (!this.loaded) return;\n\n        const canvas = this.canvas;\n        const ctx = this.ctx;\n        const cx = (canvas.width - 640) / 2;\n        const cy = (canvas.height - 350) / 2;\n\n        ctx.fillStyle = \"#ff0000\";\n        ctx.fillRect(cx + 14*2, cy + 112*2, p * 292*2, 20);\n    }\n}\n\n/**\n * Initialize the MegaZeux frontend and run MegaZeux.\n * @param {Object} options Options to configure the frontend and MegaZeux.\n * @returns {Promise} Promise to run MegaZeux.\n */\nwindow.MzxrunInitialize = function(options)\n{\n    console.log(\"Initializing MegaZeux web frontend\");\n\n    if (!options.render) throw \"Missing option: render!\";\n    if (!options.render.canvas) throw \"Missing option: render.canvas!\";\n\n    let canvas = options.render.canvas;\n    canvas.contentEditable = true;\n    let ctx = canvas.getContext('2d', {alpha: false});\n    ctx.imageSmoothingEnabled = false;\n\n    canvas.addEventListener(\"webglcontextlost\", e => {\n        drawErrorMessage(canvas, ctx, 'Error: WebGL context lost!');\n        e.preventDefault();\n    }, false);\n\n    /* Disable the default functions for several common MegaZeux shortcuts.\n     * FIXME: Opera defaults for left alt and F3 can't be disabled this way (as of 63).\n     */\n    document.body.addEventListener('keydown', event => {\n        let key = event.key.toUpperCase();\n        if ((event.altKey && (\n                   key == 'C' // Select char\n                || key == 'D' // Delete file/directory\n                || key == 'N' // New directory\n                || key == 'R' // Rename file/directory\n            ))\n            || key == 'F1'  // Help\n            || key == 'F2'  // Settings\n            || key == 'F3'  // Save, Load World\n            || key == 'F4'  // Load save\n            || key == 'F9'  // Quicksave\n            || key == 'F10' // Quickload Save\n\n        ) event.preventDefault()\n    })\n\n    try {\n        if (!options.path) throw \"Missing option: path!\";\n        if (!options.files) throw \"Missing option: files!\";\n    } catch (e) {\n        drawErrorMessage(canvas, ctx, e);\n        return Promise.reject(e);\n    }\n\n    const loadingScreen = new LoadingScreen(canvas, ctx, options);\n\n    var vfsPromises = [];\n    var vfsProgresses = [];\n    var vfsObjects = [];\n\n  return zip.initialize().then(_ =>\n  {\n    for (var s in options.files) {\n        vfsProgresses.push(0);\n        const file = options.files[s];\n        const i = vfsProgresses.length - 1;\n        const progressUpdater = function(p) {\n            vfsProgresses[i] = Math.min(p, 1);\n            loadingScreen.progress(vfsProgresses.reduce((p, c) => p + c) / options.files.length);\n        }\n\n        if (Array.isArray(file)) {\n            var opts = file[1];\n            if (typeof(opts) == \"string\") {\n                opts = {filenameMap: opts};\n            }\n            if (opts && !opts.hasOwnProperty(\"readonly\")) {\n                opts.readonly = true;\n            }\n            vfsPromises.push(\n                createZipStorage(file[0], opts, progressUpdater)\n                    .then(o => vfsObjects.push(o))\n            );\n        } else {\n            vfsPromises.push(\n                createZipStorage(file, {\"readonly\": true}, progressUpdater)\n                    .then(o => vfsObjects.push(o))\n            );\n        }\n    }\n  }).then(_ => loadingScreen._drawBackground()).then(_ => Promise.all(vfsPromises)).then(_ =>\n  {\n        // add MegaZeux config.txt vfs\n        // Set startup_path first so user config will override it...\n        var configString = \"startup_path = /data/game\\n\";\n\n        configString += options.config + \"\\n\";\n\n        configString += \"audio_sample_rate = 48000\\n\";\n        if (navigator.userAgent.toLowerCase().indexOf('firefox') >= 0) {\n                // Firefox copes better with audio buffering, based on current testing.\n                configString += \"audio_buffer_samples = 2048\\n\";\n        } else {\n                configString += \"audio_buffer_samples = 4096\\n\";\n        }\n\n        vfsObjects.push(createInMemoryStorage({\"etc/config.txt\": new TextEncoder().encode(configString)}));\n\n        // add storage vfs\n        if (options.storage && options.storage.type == \"auto\") {\n            if (getIndexedDB() != null) {\n                options.storage.type = \"indexeddb\";\n            } else if (getLocalStorage() != null) {\n                options.storage.type = \"localstorage\";\n            } else {\n                console.log(\"Browser does not support any form of local storage! Storing to memory...\");\n                options.storage = undefined;\n            }\n        }\n\n        function initMemoryStorage() {\n            console.log(\"Using memory storage. FILES WILL NOT PERSIST BETWEEN SESSIONS!\");\n            vfsObjects.push(createInMemoryStorage({}, {\"readonly\": false}));\n            return true;\n        }\n\n        function initIndexedDBStorage() {\n            if (!options.storage.database) throw \"Missing option: storage.database!\";\n            return createIndexedDbBackedAsyncStorage(\"mzx_\" + options.storage.database)\n                .then(result => wrapAsyncStorage(result))\n                .then(result => vfsObjects.push(result))\n                .catch(reason => {\n                    console.log(\"Failed to initialize IndexedDB storage: \" + reason);\n                    initMemoryStorage();\n            });\n        }\n\n        function initLocalStorage() {\n            if (!options.storage.database) throw \"Missing option: storage.database!\";\n            let storageObj = window.localStorage;\n            if (options.storage.storage) storageObj = options.storage.storage;\n            if (storageObj == undefined) throw \"Could not find storage object!\";\n            vfsObjects.push(createBrowserBackedStorage(storageObj, \"mzx_\" + options.storage.database));\n            return true;\n        }\n\n        try {\n            if (options.storage && options.storage.type) {\n                if (options.storage.type == \"indexeddb\") {\n                    return initIndexedDBStorage();\n                } else if (options.storage.type == \"localstorage\") {\n                    return initLocalStorage();\n                } else {\n                    throw \"Unknown storage type: \" + options.storage.type;\n                }\n            }\n        } catch(reason) {\n            console.log(reason);\n        }\n\n        return initMemoryStorage();\n\n    }).then(_ => new Promise((resolve, reject) => {\n        const vfs = createCompositeStorage(vfsObjects);\n        console.log(vfs);\n\n        const oldCanvas = canvas;\n\n        // after using the canvas in 2D mode, it cannot be used in WebGL mode\n        // as such, create a new canvas\n        canvas = oldCanvas.cloneNode(true);\n        canvas.oncontextmenu = () => { event.preventDefault(); };\n        ctx = null;\n\n        oldCanvas.parentNode.replaceChild(canvas, oldCanvas);\n        options.render.canvas = canvas;\n\n        Module({\n          canvas: options.render.canvas,\n          preRun: function(module)\n          {\n            window.FS = module[\"FS\"];\n            FS.mkdir(\"/data\");\n            FS.mount(wrapStorageForEmscripten(vfs), null, \"/data\");\n            console.log(\"Filesystem initialization complete!\");\n          }\n        }).then((module) =>\n        {\n            // This event listener refocuses MZX when clicked if it's inside of\n            // an iframe (e.g. embedded on itch.io). This is necessary to regain\n            // keyboard control after focus is lost (though tab can be used too).\n            // This needs to be done HERE since something clobbers all event\n            // listeners added to the canvas.\n            var canvas = options.render.canvas;\n            canvas.addEventListener(\"mousedown\", function(){ canvas.focus(); });\n\n            resolve();\n        });\n    })).then(_ => true).catch(reason => {\n        drawErrorMessage(canvas, ctx, reason);\n    });\n}\n"
  },
  {
    "path": "arch/emscripten/web/src/storage.js",
    "content": "/*!\n * MegaZeux\n *\n * Copyright (C) 2018, 2019 Adrian Siekierka\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { getIndexedDB, getLocalStorage, drawErrorMessage, xhrFetchAsArrayBuffer } from \"./util\";\nimport { zip } from \"./zip.js\";\n\nfunction filterKeys(list, filter) {\n\tif (filter == null) return list;\n\n\tlet newList = [];\n\tfor (var i = 0; i < list.length; i++) {\n\t\tconst key = list[i];\n\t\tif (filter(key)) newList.push(key);\n\t}\n\treturn newList;\n}\n\nclass InMemoryStorage {\n\tconstructor(inputMap, options) {\n\t\tthis.map = {};\n\t\tfor (var key in inputMap) {\n\t\t\tthis.map[key] = inputMap[key];\n\t\t}\n\t\tthis.readonly = (options && options.readonly) || false;\n\t}\n\n\tcanSet(key) {\n\t\treturn this.readonly;\n\t}\n\n\tget(key) {\n\t\tif (!this.map.hasOwnProperty(key)) {\n\t\t\treturn null;\n\t\t}\n\t\treturn this.map[key].slice(0);\n\t}\n\n\tlist(filter) {\n\t\treturn filterKeys(Object.keys(this.map), filter);\n\t}\n\n\tset(key, value) {\n\t\tif (this.readonly) return false;\n\t\tthis.map[key] = value;\n\t\treturn true;\n\t}\n\n\tremove(key) {\n\t\tif (this.readonly) return false;\n\t\tdelete this.map[key];\n\t\treturn true;\n\t}\n}\n\nclass CompositeStorage {\n\tconstructor(providers) {\n\t\tthis.providers = providers;\n\t}\n\n\tcanSet(key) {\n\t\tfor (var p = 0; p < this.providers.length; p++) {\n\t\t\tlet provider = this.providers[p];\n\t\t\tif (provider.canSet(key)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tget(key) {\n\t\tfor (var p = this.providers.length - 1; p >= 0; p--) {\n\t\t\tlet provider = this.providers[p];\n\t\t\tconst result = provider.get(key);\n\t\t\tif (result != null) return result;\n\t\t}\n\t\treturn null;\n\t}\n\n\tlist(filter) {\n\t\tlet data = [];\n\t\tfor (var p = 0; p < this.providers.length; p++) {\n\t\t\tlet provider = this.providers[p];\n\t\t\tdata = data.concat(provider.list(filter));\n\t\t}\n\t\treturn data.sort().filter(function(item, i, sortedData) {\n\t\t\treturn (i <= 0) || (item != sortedData[i - 1]);\n\t\t});\n\t}\n\n\tset(key, value) {\n\t\tfor (var p = this.providers.length - 1; p >= 0; p--) {\n\t\t\tlet provider = this.providers[p];\n\t\t\tif (provider.set(key, value)) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tremove(key) {\n\t\tfor (var p = this.providers.length - 1; p >= 0; p--) {\n\t\t\tlet provider = this.providers[p];\n\t\t\tif (provider.remove(key)) return true;\n\t\t}\n\t\treturn false;\n\t}\n}\n\nclass AsyncStorageWrapper extends InMemoryStorage {\n\tconstructor(parent) {\n\t\tsuper({}, {});\n\t\tthis.parent = parent;\n\t}\n\n\t_populate() {\n\t\tconst self = this;\n\n\t\treturn this.parent.list(null).then(result => {\n\t\t\tself.map = {};\n\n\t\t\tlet getters = [];\n\t\t\tfor (var i = 0; i < result.length; i++) {\n\t\t\t\tconst key = result[i];\n\t\t\t\tgetters.push(self.parent.get(key).then(result => {\n\t\t\t\t\tself.map[key] = result;\n\t\t\t\t}));\n\t\t\t}\n\n\t\t\treturn Promise.all(getters);\n\t\t});\n\t}\n\n\tset(key, value) {\n\t\tif (super.set(key, value)) {\n\t\t\tthis.parent.set(key, value);\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tremove(key) {\n\t\tif (super.remove(key)) {\n\t\t\tthis.parent.remove(key);\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n\nclass BrowserBackedStorage {\n\tconstructor(localStorage, prefix) {\n\t\tthis.storage = localStorage;\n\t\tthis.prefix = prefix + \"_file_\";\n\t}\n\n\tcanSet(key) {\n\t\treturn true;\n\t}\n\n\tget(key) {\n\t\tconst result = this.storage.getItem(this.prefix + key);\n\t\tif (result !== null) {\n\t\t\treturn result.split(\",\").map(s => parseInt(s));\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tlist(filter) {\n\t\tvar list = [];\n\t\tfor (var i = 0; i < this.storage.length; i++) {\n\t\t\tconst key = this.storage.key(i);\n\t\t\tif (key.startsWith(this.prefix)) {\n\t\t\t\tlist.push(key.substring(this.prefix.length));\n\t\t\t}\n\t\t}\n\t\treturn filterKeys(list, filter);\n\t}\n\n\tset(key, value) {\n\t\tthis.storage.setItem(this.prefix + key, value.join(\",\"));\n\t\treturn true;\n\t}\n\n\tremove(key) {\n\t\tthis.storage.removeItem(this.prefix + key);\n\t\treturn true;\n\t}\n}\n\nclass IndexedDbBackedAsyncStorage {\n\tconstructor(indexedDB, dbName, options) {\n\t\tthis.indexedDB = indexedDB;\n\t\tthis.dbName = dbName;\n\t}\n\n\t_open() {\n\t\tconst self = this;\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tvar dbRequest = self.indexedDB.open(this.dbName, 1);\n\t\t\tdbRequest.onupgradeneeded = event => {\n\t\t\t\tself.database = event.target.result;\n\t\t\t\tself.files = self.database.createObjectStore(\"files\", {\"keyPath\": \"filename\"});\n\t\t\t}\n\t\t\tdbRequest.onsuccess = event => {\n\t\t\t\tself.database = dbRequest.result;\n\t\t\t\tresolve();\n\t\t\t}\n\t\t\tdbRequest.onerror = event => {\n\t\t\t\treject(\"Failed to open IndexedDB database (is this a private window?)\");\n\t\t\t}\n\t\t});\n\t}\n\n\tcanSet(key) {\n\t\treturn Promise.resolve(true);\n\t}\n\n\tget(key) {\n\t\tconst transaction = this.database.transaction([\"files\"], \"readonly\");\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst request = transaction.objectStore(\"files\").get(key);\n\t\t\trequest.onsuccess = event => {\n\t\t\t\tif (request.result && request.result.value) resolve(request.result.value);\n\t\t\t\telse resolve(null);\n\t\t\t}\n\t\t\trequest.onerror = event => {\n\t\t\t\tresolve(null);\n\t\t\t}\n\t\t});\n\t}\n\n\tlist(filter) {\n\t\tconst transaction = this.database.transaction([\"files\"], \"readonly\");\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst store = transaction.objectStore(\"files\");\n\t\t\tif (typeof store.getAllKeys === 'function') {\n\t\t\t\tconst request = store.getAllKeys();\n\t\t\t\trequest.onsuccess = event => {\n\t\t\t\t\tresolve(filterKeys(request.result, filter));\n\t\t\t\t}\n\t\t\t\trequest.onerror = event => {\n\t\t\t\t\tresolve([]);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar result = [];\n\t\t\t\tvar request;\n\t\t\t\tif (typeof store.openKeyCursor === 'function') {\n\t\t\t\t\trequest = store.openKeyCursor();\n\t\t\t\t} else {\n\t\t\t\t\trequest = store.openCursor();\n\t\t\t\t}\n\t\t\t\trequest.onsuccess = event => {\n\t\t\t\t\tvar cursor = event.target.result;\n\t\t\t\t\tif (cursor) {\n\t\t\t\t\t\tresult.push(cursor.key);\n\t\t\t\t\t\tcursor.continue();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresolve(filterKeys(result, filter));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trequest.onerror = event => {\n\t\t\t\t\tresolve(result);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tset(key, value) {\n\t\tconst transaction = this.database.transaction([\"files\"], \"readwrite\");\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst request = transaction.objectStore(\"files\").put({\n\t\t\t\t\"filename\": key,\n\t\t\t\t\"value\": value\n\t\t\t});\n\t\t\trequest.onsuccess = event => {\n\t\t\t\tresolve(true);\n\t\t\t}\n\t\t\trequest.onerror = event => {\n\t\t\t\tresolve(false);\n\t\t\t}\n\t\t});\n\t}\n\n\tremove(key) {\n\t\tconst transaction = this.database.transaction([\"files\"], \"readwrite\");\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst request = transaction.objectStore(\"files\").delete(key);\n\t\t\trequest.onsuccess = event => {\n\t\t\t\tresolve(true);\n\t\t\t}\n\t\t\trequest.onerror = event => {\n\t\t\t\tresolve(false);\n\t\t\t}\n\t\t});\n\t}\n}\n\nexport function createBrowserBackedStorage(storage, dbName) {\n\treturn new BrowserBackedStorage(storage, dbName);\n}\n\nexport function createIndexedDbBackedAsyncStorage(dbName) {\n\tconst indexedDB = getIndexedDB();\n\tif (!indexedDB) {\n\t\treturn Promise.reject(\"IndexedDB not supported!\");\n\t}\n\tconst dbObj = new IndexedDbBackedAsyncStorage(indexedDB, dbName);\n\treturn dbObj._open().then(_ => dbObj);\n}\n\nexport function wrapAsyncStorage(asyncVfs) {\n\tconst obj = new AsyncStorageWrapper(asyncVfs);\n\treturn obj._populate().then(_ => obj);\n}\n\nexport function createInMemoryStorage(inputMap, options) {\n\treturn new InMemoryStorage(inputMap, options);\n}\n\nexport function createCompositeStorage(providers) {\n\treturn new CompositeStorage(providers);\n}\n\nexport function createZipStorage(url, options, progressCallback) {\n\treturn xhrFetchAsArrayBuffer(url, progressCallback)\n\t\t.then(xhr => new Promise((resolve, reject) => {\n\t\t\tlet files;\n\t\t\tlet fileMap = {};\n\n\t\t\ttry\n\t\t\t{\n\t\t\t\tfiles = zip.extract(xhr.response);\n\t\t\t}\n\t\t\tcatch(e)\n\t\t\t{\n\t\t\t\treject(e);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfor (var key in files) {\n\t\t\t\tconst keyOrig = key;\n\n\t\t\t\tif (options && options.filenameMap) {\n\t\t\t\t\tif (typeof(options.filenameMap) === \"object\") {\n\t\t\t\t\t\tkey = options.filenameMap[key] || undefined;\n\t\t\t\t\t} else if (typeof(options.filenameMap) === \"string\") {\n\t\t\t\t\t\tkey = options.filenameMap + key;\n\t\t\t\t\t} else if (typeof(options.filenameMap) === \"function\") {\n\t\t\t\t\t\tkey = options.filenameMap(key);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (key) {\n\t\t\t\t\tif (!(options && options.filenameFilter) || options.filenameFilter(key)) {\n\t\t\t\t\t\tfileMap[key] = files[keyOrig];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresolve(new InMemoryStorage(fileMap, options));\n\t\t}));\n}\n"
  },
  {
    "path": "arch/emscripten/web/src/storage_emscripten.js",
    "content": "/*!\n * MegaZeux\n *\n * Copyright (C) 2018, 2019 Adrian Siekierka\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nconst EPERM = 1;\nconst ENOENT = 2;\nconst EINVAL = 22;\nconst ENOTEMPTY = 39;\nconst O_CREAT = 0x40;\nconst O_TRUNC = 0x200;\nconst S_IFDIR = 0x4000;\nconst S_IFREG = 0x8000;\nconst S_IFMT = 0xF000;\n\nfunction vfs_get_type(vfs, path) {\n    if (path.length == 0) return \"dir\";\n    let contents = vfs.get(path);\n    if (contents !== null) {\n        return \"file\";\n    } else {\n        if (!path.endsWith(\"/\"))\n            path += \"/\";\n        const list = vfs.list(a => a.startsWith(path));\n        if (list.length >= 1) return \"dir\";\n    }\n    return \"empty\";\n}\n\nfunction vfs_next_power_of_two(n) {\n    var i = 4096;\n    while (i < n) i *= 2;\n    return i;\n}\n\nfunction vfs_expand_array(array, newLength) {\n    if (newLength <= array.length) return array;\n    newLength = vfs_next_power_of_two(newLength);\n\n    var newArrayBuffer = new ArrayBuffer(newLength);\n    var newArray = new Uint8Array(newArrayBuffer);\n    newArray.set(array);\n    return newArray;\n}\n\nexport function wrapStorageForEmscripten(vfs) {\n    let wrap;\n    wrap = {\n        mount: (mount) => {\n            return wrap.createNode(null, '/', undefined, 0);\n        },\n        createNode: (parent, name, mode, dev) => {\n            var vfs_path = name.substring(1);\n            var vfs_type = vfs_get_type(vfs, vfs_path);\n            // console.log(\"FS createNode \" + name + \" (\" + vfs_type + \")\");\n\n            if (vfs_type == \"empty\") {\n                throw new FS.ErrnoError(ENOENT);\n            }\n\n            if (vfs_type == \"dir\" && vfs_path.length >= 1 && !vfs_path.endsWith(\"/\")) {\n                vfs_path += \"/\";\n            }\n\n            if (mode === undefined) {\n                if (vfs_type == \"dir\") mode = S_IFDIR | 0x1FF;\n                else if (vfs_type == \"file\") mode = S_IFREG | 0x1FF;\n            }\n\n            var node_name = name.split(\"/\");\n            node_name = node_name[node_name.length - 1];\n\n            var node = FS.createNode(parent, node_name, mode);\n            node.vfs_path = vfs_path;\n            node.vfs_type = vfs_type;\n            node.vfs_time = new Date();\n            node.node_ops = {};\n            node.stream_ops = {};\n\n            node.node_ops.getattr = (n) => {\n                var attr = {\n                    dev: 0,\n                    ino: n.id,\n                    mode: n.mode,\n                    nlink: 1,\n                    uid: 0,\n                    gid: 0,\n                    rdev: 0,\n                    ctime: n.vfs_time,\n                    mtime: n.vfs_time,\n                    atime: n.vfs_time,\n                    blksize: 4096\n                }\n\n                if (n.vfs_type == \"dir\") {\n                    attr.size = 4096;\n                } else {\n                    attr.size = vfs.get(n.vfs_path).length;\n                }\n\n                attr.blocks = Math.ceil(attr.size / attr.blksize);\n                return attr;\n            }\n\n            node.node_ops.setattr = (n, attr) => {\n                if (attr.mode !== undefined) n.mode = attr.mode;\n                if (attr.size !== undefined) {\n                    // Used to implement O_TRUNC by the Emscripten FS API.\n                    // console.log(\"FS setattr size \" + n.vfs_path + \" \" + attr.size);\n                    let old_data = vfs.get(n.vfs_path);\n                    let new_data = new Uint8Array(attr.size);\n                    if (attr.size > 0 && old_data) {\n                        // Note: not sure sizes > 0 will reach here from the FS API...\n                        new_data.set(old_data.slice(0, attr.size));\n                    }\n                    if (!vfs.set(n.vfs_path, new_data))\n                        throw new FS.ErrnoError(EPERM);\n                }\n            }\n\n            node.stream_ops.llseek = (stream, offset, whence) => {\n                switch (whence) {\n                    case 0:\n                        return offset;\n                    case 1:\n                        return offset + stream.position;\n                    case 2:\n                        if (stream.vfs_data)\n                            return offset + stream.vfs_data_length;\n                        else\n                            return offset;\n                    default:\n                        throw new FS.ErrnoError(EINVAL);\n                }\n            }\n\n            if (node.vfs_type == \"dir\") {\n                node.node_ops.lookup = (nparent, name) => {\n                    return wrap.createNode(node, '/' + node.vfs_path + name, undefined, 0);\n                };\n                node.node_ops.mknod = (parent, name, mode, dev) => {\n                    // console.log(\"FS mknod \" + name + \" \" + mode);\n                    if ((mode & S_IFMT) == S_IFREG) {\n                        vfs.set(node.vfs_path + name, new Uint8Array(0));\n                    } else {\n                        throw new FS.ErrnoError(EPERM);\n                    }\n                    return wrap.createNode(parent, '/' + node.vfs_path + name, mode, dev);\n                };\n                node.node_ops.rename = (oldNode, newDir, newName) => {\n                    console.log(\"FS FIXME rename \" + newName);\n                    throw new FS.ErrnoError(EPERM);\n                };\n                node.node_ops.unlink = (parent, name) => {\n                    // console.log(\"FS unlink \" + name);\n                    if (!vfs.remove(node.vfs_path + name))\n                        throw new FS.ErrnoError(EPERM);\n                };\n                node.node_ops.rmdir = (parent, name) => {\n                    // console.log(\"FS rmdir \" + name);\n                    const path = node.vfs_path + name;\n                    const list = vfs.list(a => a.startsWith(path));\n                    for (var i = 0; i < list.length; i++) {\n                        var entry = list[i].substring(path.length);\n                        if (entry.length != 0)\n                            throw new FS.ErrnoError(ENOTEMPTY);\n                    }\n                };\n                node.node_ops.readdir = (node) => {\n                    const path = node.vfs_path;\n                    const list = vfs.list(a => a.startsWith(path));\n                    let directories = {};\n                    let dirents = [\".\", \"..\"];\n                    for (var i = 0; i < list.length; i++) {\n                        var entry = list[i].substring(path.length);\n                        if (entry.length == 0) continue; // is empty\n                        if (entry.indexOf('/') >= 0) {\n                            // is directory\n                            const entryDirName = entry.split('/')[0];\n                            if (!directories.hasOwnProperty(entryDirName)) {\n                                directories[entryDirName] = true;\n                                dirents.push(entryDirName);\n                            }\n                        } else {\n                            // is file\n                            dirents.push(entry);\n                        }\n                    }\n                    // console.log(\"FS readdir \" + path + \" => \" + dirents.length + \" dirents\");\n                    // console.log(dirents);\n                    return dirents;\n                };\n            } else if (node.vfs_type == \"file\") {\n                node.stream_ops.open = (stream) => {\n                    // console.log(\"FS open \" + node.vfs_path);\n                    stream.vfs_modified = false;\n                    if ((stream.flags & O_TRUNC) != 0) {\n                        stream.vfs_data = new Uint8Array(0);\n                    } else {\n                        stream.vfs_data = vfs.get(node.vfs_path);\n                        if (!stream.vfs_data) {\n                            if ((stream.flags & O_CREAT) != 0) {\n                                stream.vfs_data = new Uint8Array(0);\n                            } else {\n                                throw new FS.ErrnoError(ENOENT);\n                            }\n                        }\n                    }\n                    stream.vfs_data_length = stream.vfs_data.length;\n                }\n\n                node.stream_ops.close = (stream) => {\n                    // console.log(\"FS close \" + node.vfs_path);\n                    if (stream.vfs_data && stream.vfs_modified) {\n                        let length = Math.min(stream.vfs_data.length, stream.vfs_data_length);\n                        vfs.set(node.vfs_path, stream.vfs_data.subarray(0, length));\n                    }\n                }\n\n                node.stream_ops.read = (stream, buffer, bufOffset, dataLength, dataOffset) => {\n                    // console.log(\"FS read \" + node.vfs_path + \" \" + dataOffset + \" \" + dataLength);\n                    const size = Math.min(dataLength, stream.vfs_data_length - dataOffset);\n                    if (size > 8 && buffer.set && stream.vfs_data.subarray) {\n                        buffer.set(stream.vfs_data.subarray(dataOffset, dataOffset + size), bufOffset);\n                    } else {\n                        for (var i = 0; i < size; i++) {\n                            buffer[bufOffset + i] = stream.vfs_data[dataOffset + i];\n                        }\n                    }\n                    return size;\n                }\n\n                node.stream_ops.write = (stream, buffer, bufOffset, dataLength, dataOffset) => {\n                    // console.log(\"FS write \" + node.vfs_path + \" \" + dataOffset + \" \" + dataLength);\n                    if (dataLength <= 0) return 0;\n                    stream.vfs_data = vfs_expand_array(stream.vfs_data, dataOffset + dataLength);\n                    const size = dataLength;\n                    if (size > 8 && stream.vfs_data.set && buffer.subarray) {\n                        stream.vfs_data.set(buffer.subarray(bufOffset, bufOffset + size), dataOffset);\n                    } else {\n                        for (var i = 0; i < size; i++) {\n                            stream.vfs_data[dataOffset + i] = buffer[bufOffset + i];\n                        }\n                    }\n                    stream.vfs_data_length = Math.max(dataOffset + dataLength, stream.vfs_data_length);\n                    stream.vfs_modified = true;\n                    return dataLength;\n                }\n\n                node.stream_ops.allocate = (stream, offset, length) => {\n                    stream.vfs_data = vfs_expand_array(stream.vfs_data, offset + length);\n                    if (offset + length > stream.vfs_data_length) {\n                        stream.vfs_data_length = offset + length;\n                        stream.vfs_modified = true;\n                    }\n                }\n            }\n            return node;\n        }\n    };\n    return wrap;\n}\n"
  },
  {
    "path": "arch/emscripten/web/src/util.js",
    "content": "/*!\n * MegaZeux\n *\n * Copyright (C) 2018, 2019 Adrian Siekierka\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nlet date_s = Date.now();\n\nexport function drawErrorMessage(canvas, ctx, text) {\n\tlet fontSize = 32;\n\twhile (fontSize > 8) {\n\t\tctx.font = \"bold \" + fontSize + \"px sans-serif\";\n\t\tif (ctx.measureText(text).width <= canvas.width - 4) break;\n\t\tfontSize--;\n\t}\n\n\tctx.fillStyle = \"rgba(0, 0, 0, 0.75)\";\n\tctx.fillRect(0, 0, canvas.width, canvas.height);\n\tctx.textBaseline = \"middle\";\n\n\tconst xOffset = (canvas.width - ctx.measureText(text).width) / 2;\n\tconst yOffset = canvas.height / 2;\n\n\tctx.fillStyle = \"#000000\";\n\tctx.fillText(text, xOffset + 2, yOffset + 2);\n\n\tctx.fillStyle = \"#ffffff\";\n\tctx.fillText(text, xOffset, yOffset);\n}\n\nexport function time_ms() {\n\treturn Date.now() - date_s;\n}\n\nexport function getIndexedDB() {\n\treturn window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;\n}\n\nexport function getLocalStorage() {\n\treturn window.localStorage;\n}\n\nexport function xhrFetchAsArrayBuffer(url, progressCallback) {\n\treturn new Promise((resolve, reject) => {\n\t\tvar xhr = new XMLHttpRequest();\n\t\txhr.open(\"GET\", url, true);\n\t\txhr.overrideMimeType('text/plain; charset=x-user-defined');\n\t\txhr.responseType = \"arraybuffer\";\n\n\t\txhr.onprogress = event => {\n\t\t\tif (progressCallback) progressCallback(event.loaded / event.total);\n\t\t};\n\n\t\txhr.onload = event => {\n\t\t\tif (progressCallback) progressCallback(1);\n\t\t\tif (xhr.status != 200) {\n\t\t\t\treject(\"Error downloading \" + url + \" (\" + xhr.status + \")\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(xhr);\n\t\t};\n\n\t\txhr.onerror = event => {\n\t\t\treject(\"Error downloading \" + url + \" (XHR)\");\n\t\t}\n\n\t\txhr.send();\n\t});\n}\n"
  },
  {
    "path": "arch/emscripten/web/src/zip.js",
    "content": "/*!\n * MegaZeux\n *\n * Copyright (C) 2020 Alice Rowan\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nexport var zip =\n{\n  emzip: undefined,\n  emzip_functions:\n  [\n    \"_emzip_open\",\n    \"_emzip_length\",\n    \"_emzip_filename\",\n    \"_emzip_extract\",\n    \"_emzip_skip\",\n    \"_emzip_close\",\n    \"_emzip_malloc\",\n    \"_emzip_free\",\n    \"UTF8ToString\"\n  ],\n\n  /**\n   * Initialize the zip utility.\n   * @returns {Promise} Promise to initialize the zip utility.\n   * @throws {string} Error string if an error occurred.\n   */\n  initialize: function()\n  {\n    if(zip.emzip === undefined)\n    {\n      // If emzip isn't loaded, silently fail (it might not be needed anyway).\n      if(typeof(emzip)!=='function')\n        return Promise.resolve();\n\n      return emzip().then(module =>\n      {\n        zip.emzip = module;\n        zip.emzip_functions.forEach(function(fn)\n        {\n          if(typeof(zip.emzip[fn])!=='function')\n          {\n            console.error('zip.initialize: typeof(zip.emzip[\"'+fn+'\"]) == ' +\n             typeof(zip.emzip[fn]));\n            throw \"zip.initialize: function emzip.\" + fn + \" not found!\";\n          }\n        });\n      });\n    }\n    else\n      throw \"zip.initialize: already initialized!\";\n  },\n\n  /**\n   * Extract files from a zip archive.\n   * @param {ArrayBuffer} bytes Zip archive to extract.\n   * @returns {Object.<string, Uint8Array>}\n   *   Object containing {filename => file data} for each file in the archive.\n   * @throws {string} Error string if an error occurred.\n   */\n  extract: function(bytes)\n  {\n    // Attempt UZIP first since it's lighter on memory usage. If that fails,\n    // fall back to emzip (a wrapper for MZX's internal zip handler).\n    try\n    {\n      return UZIP.parse(bytes);\n    }\n    catch(e)\n    {\n      console.error('UZIP: ' + e);\n      try\n      {\n        return zip.extract_emzip(bytes);\n      }\n      catch(e)\n      {\n        console.error('emzip: ' + e);\n      }\n    }\n    throw \"Failed to extract archive.\";\n  },\n  extract_emzip: function(bytes)\n  {\n    if(zip.emzip === undefined)\n      throw \"Not initialized!\";\n\n    var emzip = zip.emzip;\n    var bytes_array = new Uint8Array(bytes);\n\n    var src_len = bytes_array.length;\n    var src_ptr = emzip._emzip_malloc(src_len);\n\n    if(!src_ptr)\n      throw \"Failed to allocate buffer.\";\n\n    var src_array = new Uint8Array(emzip.HEAPU8.buffer, src_ptr, src_len);\n    src_array.set(bytes_array);\n\n    var zp = emzip._emzip_open(src_ptr, src_len);\n    var result = {};\n\n    if(!zp)\n      throw \"Failed to open archive.\";\n\n    while(true)\n    {\n      var filename_ptr = emzip._emzip_filename(zp);\n      if(filename_ptr)\n      {\n        var filename = emzip.UTF8ToString(filename_ptr);\n        emzip._emzip_free(filename_ptr);\n\n        var data_length = emzip._emzip_length(zp);\n        if(data_length)\n        {\n          var data_ptr = emzip._emzip_extract(zp);\n          if(data_ptr)\n          {\n            var data_array = new Uint8Array(emzip.HEAPU8.buffer, data_ptr, data_length);\n            var data_output = new Uint8Array(data_length);\n            data_output.set(data_array);\n            emzip._emzip_free(data_ptr);\n\n            result[filename] = data_output;\n            continue;\n          }\n          else\n            throw \"Failed to extract file: \" + filename;\n        }\n        else\n        {\n          emzip._emzip_skip(zp);\n\n          if(filename[filename.length - 1] != '/')\n            result[filename] = new Uint8Array(0);\n\n          continue;\n        }\n      }\n      break;\n    }\n\n    emzip._emzip_close(zp);\n    emzip._emzip_free(src_ptr);\n    return result;\n  }\n};\n"
  },
  {
    "path": "arch/emscripten/whitelist.json",
    "content": "[\n\t\"main\",\n\t\t\"core_run\",\n\t\t\t\"core_resume\",\n\t\t\t\"core_update\",\n\t\t\t\t\"help_system\",\n\t\t\t\"core_draw\",\n\t\t\t\t\"game_draw\",\n\n\t\"title_key\",\n\t\t\"load_savegame_selection\",\n\t\t\"error\",\n\n\t\"game_key\",\n\t\t\"new_file\",\n\t\t\t\"file_manager\",\n\t\t\"confirm\",\n\n\t\"confirm\",\n\t\t\"run_dialog\",\n\n\t\"game_settings\",\n\t\t\"run_dialog\",\n\n\t\"core_resume\",\n\t\t\"title_resume\",\n\t\t\t\"load_world_title_selection\",\n\t\t\"execute_next_context_callback\",\n\t\t\t\"game_menu_callback\",\n\t\t\t\t\"game_key\",\n\t\t\t\"main_menu_callback\",\n\t\t\t\t\"title_key\",\n\n\t\"menu_key\",\n\t\"menu_click\",\n\t\t\"menu_activate\",\n\t\t\t\"main_menu_activate\",\n\t\t\t\t\"about_megazeux\",\n\n\t\"load_world_title_selection\",\n\t\t\"load_world_title\",\n\t\t\t\"reload_world\",\n\t\t\t\t\"try_load_world\",\n\t\t\t\t\t\"try_load_legacy_world\",\n\t\t\t\t\t\t\"validate_legacy_world_file\",\n\t\t\t\t\t\t\t\"confirm\",\n\t\t\"choose_file_ch\",\n\t\"load_savegame_selection\",\n\t\t\"choose_file_ch\",\n\n\t\"choose_file_ch\",\n\t\t\"file_manager\",\n\t\t\t\"confirm_input\",\n\t\t\t\"idle_input_box\",\n\t\t\t\"run_dialog\",\n\t\t\t\"confirm\",\n\t\t\t\"ask_yes_no\",\n\t\t\t\"error\",\n\n\t\"run_dialog\",\n\t\t\"help_system\",\n\t\t\"key_file_selector\",\n\t\t\t\"run_dialog\",\n\n\t\"core_update\",\n\t\t\"game_idle\",\n\n\t\"update_event_status\",\n\t\"update_event_status_delay\",\n\t\t\"delay\",\n\t\"update_event_status_intake\",\n\t\t\"wait_event\",\n\n\t\"game_draw\",\n\t\t\"update_world\",\n\t\t\t\"update_player_input\",\n\t\t\t\"update_player_wind\",\n\t\t\t\"update_player_under\",\n\t\t\t\"update_board\",\n\t\t\t\t\"run_robot\",\n\n\t\"update_player_input\",\n\t\t\"move_player\",\n\t\"update_player_wind\",\n\t\t\"move_player\",\n\t\"update_player_under\",\n\t\t\"move_player\",\n\n\t\"move_player\",\n\t\t\"grab_item\",\n\t\"grab_item\",\n\t\t\"scroll_edit\",\n\n\t\"run_robot\",\n\t\t\"ask_yes_no\",\n\t\t\t\"run_dialog\",\n\t\t\"input_window\",\n\t\t\"move_player\",\n\t\t\"grab_item\",\n\t\t\"robot_box_display\",\n\t\t\"error\",\n\n\t\"confirm_input\",\n\t\t\"idle_input_box\",\n\n\t\"input_window\",\n\t\t\"intake\",\n\t\"click_input_box\",\n\t\t\"intake\",\n\t\"idle_input_box\",\n\t\t\"intake\",\n\n\t\"char_selection\",\n\t\t\"char_selection_ext\",\n\n\t\"intake\",\n\t\t\"char_selection\",\n\t\t\"intake_key\",\n\t\t\t\"char_selection\",\n\t\"scroll_edit\",\n\t\t\"intake\",\n\t\"help_system\",\n\t\t\"help_display\",\n\n\t\"vquick_fadeout\",\n\t\"vquick_fadein\",\n\n\t\"game_destroy\",\n\t\t\"vquick_fadeout\",\n\n\t\"game_idle\",\n\t\t\"update_resolve_target\",\n\t\t\t\"vquick_fadeout\",\n\n\t\"core_free\",\n\t\t\"destroy_context\",\n\t\t\t\"game_destroy\",\n\t\"game_idle\",\n\t\t\"destroy_context\",\n\t\t\t\"game_destroy\",\n\t\"game_key\",\n\t\t\"destroy_context\",\n\t\t\t\"game_destroy\"\n]\n"
  },
  {
    "path": "arch/gp2x/Makefile.in",
    "content": "#\n# gp2x makefile generics\n#\n\nCROSS_COMPILE ?= arm-open2x-linux-\nBINEXT = .gpe\n\n# Disable rules that require target code to run natively.\nSUPPRESS_HOST_TARGETS ?= 1\n\n#\n# GP2X binaries must be statically linked.\n#\nLIBPNG_LDFLAGS = $(shell libpng12-config --static --ldflags)\nARCH_EXE_LDFLAGS = -static\n\n#\n# There's a couple of other packaged files on GP2X\n#\nbuild: ${build}\n\t${RM} ${build}/${mzxrun}.debug\n\t${CP} arch/gp2x/pad.config ${build}\n\t${CP} contrib/icons/generated/icon_32.png ${build}/mzxrun.png\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/gp2x/README",
    "content": "MEGAZEUX ON GP2X\n\nAs of MegaZeux 2.81g, Simon Parzer donated a port of MegaZeux to the GP2X. It\nhas a few limitations, namely video (which has to be scaled down to 320x240),\nsome minor performance issues and missing keyboard controls.\n\nIt does however support playing MegaZeux games with support for sound effects\nand music. Several popular titles have been tested.\n\nCONFIGURATION\n\nThe GP2X port will override the default audio_buffer to 128 samples; there is\nno need to set it explicitly in the config.txt. Also, the gp2x renderer will\nbe automatically selected; there is no need to set it explicitly in the\nconfig.txt.\n\nThe file pad.config must be included in the distribution and copied from\narch/gp2x. This file contains necessary pad mappings for the GP2X controls.\n\nCOMPILATION\n\nThe only support toolchain for GP2X is the Open2X toolchain. It must be\ninstalled andadded to your environment's PATH variable. For example, if\nyou have `arm-open2x-linux-gcc' in /usr/arm-linux/bin, you should do\nthe following:\n\n  export PATH=/usr/arm-linux/bin:$PATH\n\nIt is now necessary to compile `SDL', `libmikmod' and `tremor'. Compilation \nof these libraries is not covered in this document. It may also be possible\nto use the pre-compiled libraries come with your toolchain.\n\n  http://libsdl.org/ (1.2.10 or later)\n  http://mikmod.raphnet.net/ (3.1.11 or later)\n  http://svn.xiph.org/trunk/Tremor/\n\nNow you must run `config.sh'. The configuration script (config.sh) for the GP2X\nplatform assumes that --prefix will specify the path to the GP2X SDK. For\nexample, if you have the SDK installed to $TOOLCHAIN, you should specify:\n\n  ./config.sh --platform gp2x --prefix $TOOLCHAIN --optimize-size \\\n              --disable-editor --disable-helpsys --disable-utils \\\n              --enable-mikmod --enable-release --enable-meter\n\nThe configuration script will automatically enable/disable other flags on this\nplatform; do not vary the command above, other than to change the prefix. The\nflag `--enable-mikmod' may be replaced with `--disable-audio' if you wish to\ndisable audio support.\n\nYou may now type `make' as normal to contruct a suitable binary for the GP2X.\n\nPACKAGING THE BUILD\n\nYou can then use \"make archive\" to build a build/dist/gp2x/mzxgit-gp2x.zip\nfile for distribution.\n\n--ajs.\n"
  },
  {
    "path": "arch/gp2x/pad.config",
    "content": "# 1 Up\r\n# 2 Up-Left\r\n# 3 Left\r\n# 4 Down-Left\r\n# 5 Down\r\n# 6 Down-Right\r\n# 7 Right\r\n# 8 Up-Right\r\n# 9 Start\r\n# 10 Select\r\n# 11 L\r\n# 12 R\r\n# 13 A\r\n# 14 B\r\n# 15 X\r\n# 16 Y\r\n# 17 Vol+\r\n# 18 Vol-\r\n# 19 Stick press\r\n\r\n# NOTE: bizarre button layout that the button names above don't do justice:\r\n#\r\n#   Y\r\n# A   B\r\n#   X\r\n\r\njoy1button1 = act_up\r\njoy1button5 = act_down\r\njoy1button3 = act_left\r\njoy1button7 = act_right\r\njoy1button9 = act_start\r\njoy1button10 = act_select\r\njoy1button11 = act_lshoulder\r\njoy1button12 = act_rshoulder\r\njoy1button13 = act_y\r\njoy1button14 = act_a\r\njoy1button15 = act_b\r\njoy1button16 = act_x\r\njoy1button19 = act_lstick\r\n# These are a little weird but what else is there to put here?\r\njoy1button17 = act_ltrigger\r\njoy1button18 = act_rtrigger\r\n"
  },
  {
    "path": "arch/install.inc",
    "content": "#\n# install/uninstall Makefile\n#\n# These recipes are intended primarily for use with the unix and darwin\n# platforms, but may be useful for other platforms. Include this in any arch\n# Makefile.in to enable 'make install' and 'make uninstall' for that platform.\n#\n# An 'install-arch' and 'uninstall-arch' need to be provided after including\n# this file to handle platform-specific install files (if any).\n#\n\n.PHONY: install install-arch uninstall uninstall-arch install-check\n\ngzip = gzip\nifeq (${USER},root)\ninstall = install -o root\nelse\ninstall = install\nendif\n\nifneq (${V},1)\ninstall := @${install}\ngzip    := @${gzip}\nendif\n\n#\n# Check to make sure 'make install' and 'make uninstall' aren't accidentally\n# used for platforms/subplatforms that don't support it.\n#\n\nERROR_PLATFORM = Attempted 'make install' or 'make uninstall' for invalid platform!\n\ninstall-check:\n\t@echo Checking ability to install/uninstall...\n\t@echo GAMESDIR: ${GAMESDIR}\nifeq (${GAMESDIR},.)\n\t$(error ${ERROR_PLATFORM} (GAMESDIR))\nendif\n\t@echo SYSCONFDIR: ${SYSCONFDIR}\nifeq (${SYSCONFDIR},.)\n\t$(error ${ERROR_PLATFORM} (SYSCONFDIR))\nendif\n\t@echo SHAREDIR: ${SHAREDIR}\nifeq (${SHAREDIR},.)\n\t$(error ${ERROR_PLATFORM} (SHAREDIR))\nendif\n\t@echo LICENSEDIR: ${LICENSEDIR}\nifeq (${LICENSEDIR},.)\n\t$(error ${ERROR_PLATFORM} (LICENSEDIR))\nendif\n\t@echo METAINFODIR: ${METAINFODIR}\nifeq (${METAINFODIR},.)\n\t$(error ${ERROR_PLATFORM} (METAINFODIR))\nendif\nifneq (${BUILD_MODULAR},)\n\t@echo LIBDIR: ${LIBDIR}\nifeq (${LIBDIR},.)\n\t$(error ${ERROR_PLATFORM} (LIBDIR, modular enabled))\nendif\nendif\nifneq (${BUILD_UTILS},)\n\t@echo BINDIR: ${BINDIR}\nifeq (${BINDIR},.)\n\t$(error ${ERROR_PLATFORM} (BINDIR, utils enabled))\nendif\nendif\n\n#\n# Install the current build to the system.\n#\n# Note regarding EXTRA_LICENSES: library licenses from Makefile aren't included\n# here as they're generally linked dynamically and have their own licenses\n# available for platforms that support make install.\n#\n\ninstall: install-check install-arch\n\t@echo Installing...\n\t${install} -m 0755 -d \\\n\t\t${DESTDIR}${SYSCONFDIR} \\\n\t\t${DESTDIR}${SHAREDIR} \\\n\t\t${DESTDIR}${SHAREDIR}/doc \\\n\t\t${DESTDIR}${SHAREDIR}/doc/megazeux \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux/assets \\\n\t\t${DESTDIR}${LICENSEDIR} \\\n\t\t${DESTDIR}${LICENSEDIR}/megazeux \\\n\t\t${DESTDIR}${METAINFODIR}\n\t${install} -m 0644 \\\n\t\tassets/default.chr \\\n\t\tassets/edit.chr \\\n\t\tassets/smzx.pal \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux/assets\n\t${install} -m 0644 \\\n\t\tconfig.txt \\\n\t\t${DESTDIR}${SYSCONFDIR}/megazeux-config\n\t${gzip} -c docs/changelog.txt > docs/changelog.txt.gz\n\t${gzip} -c9 docs/mzxhelp.html > docs/mzxhelp.html.gz\n\t${install} -m 0644 \\\n\t\tREADME.md \\\n\t\tdocs/changelog.txt.gz \\\n\t\tdocs/mzxhelp.html.gz \\\n\t\tdocs/macro.txt \\\n\t\tdocs/fileform.html \\\n\t\tdocs/keycodes.html \\\n\t\tdocs/joystick.html \\\n\t\tdocs/cycles_and_commands.txt \\\n\t\t${DESTDIR}${SHAREDIR}/doc/megazeux/\n\t${install} -m 0644 \\\n\t\tLICENSE \\\n\t\tarch/LICENSE.3rd \\\n\t\t${EXTRA_LICENSES} \\\n\t\t${DESTDIR}${LICENSEDIR}/megazeux/\n\t${RM} docs/changelog.txt.gz\n\t${RM} docs/mzxhelp.html.gz\nifeq (${BUILD_HELPSYS},1)\n\t${install} -m 0644 assets/help.fil \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux/assets\nendif\nifeq (${BUILD_MODULAR},1)\n\t${install} -m 0755 -d ${DESTDIR}${LIBDIR}\n\t${install} -m 0755 ${core_target} ${DESTDIR}${LIBDIR}\n\t${install} -m 0755 ${editor_target} ${DESTDIR}${LIBDIR}\nendif\nifeq (${BUILD_MZXRUN},1)\n\t${install} -m 0755 -d ${DESTDIR}${GAMESDIR}\n\t${install} -m 0755 ${mzxrun} ${DESTDIR}${GAMESDIR}/\nifneq (${BUILD_EDITOR},1)\n\t${install} -m 0644 -T \\\n\t\tarch/unix/megazeux.metainfo.xml \\\n\t\t${DESTDIR}${METAINFODIR}/mzxrun.metainfo.xml\nendif\nendif\nifeq (${BUILD_EDITOR},1)\n\t${install} -m 0755 -d ${DESTDIR}${GAMESDIR}\n\t${install} -m 0755 ${mzx} ${DESTDIR}${GAMESDIR}/\n\t${install} -m 0644 \\\n\t\tassets/ascii.chr \\\n\t\tassets/blank.chr \\\n\t\tassets/smzx.chr \\\n\t\tassets/smzx2.chr \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux/assets\n\t${install} -m 0644 \\\n\t\tarch/unix/megazeux.metainfo.xml \\\n\t\t${DESTDIR}${METAINFODIR}\nendif\nifeq (${BUILD_UTILS},1)\n\t${install} -m 0755 -d ${DESTDIR}${BINDIR}\n\t${install} -m 0755 \\\n\t\t${checkres} \\\n\t\t${downver} \\\n\t\t${hlp2txt} \\\n\t\t${txt2hlp} \\\n\t\t${png2smzx} \\\n\t\t${y4m2smzx} \\\n\t\t${ccv} \\\n\t\t${DESTDIR}${BINDIR}/\n\t${install} -m 0644 \\\n\t\tcontrib/mzvplay/mzvplay.txt \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux\nendif\nifeq (${BUILD_RENDER_GL_PROGRAM},1)\n\t${install} -m 0755 -d \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux/assets/glsl\n\t${install} -m 0644 assets/glsl/cursor.frag \\\n\t\tassets/glsl/cursor.vert \\\n\t\tassets/glsl/mouse.frag \\\n\t\tassets/glsl/mouse.vert \\\n\t\tassets/glsl/README.md \\\n\t\tassets/glsl/scaler.vert \\\n\t\tassets/glsl/tilemap.frag \\\n\t\tassets/glsl/tilemap.vert \\\n\t\tassets/glsl/tilemap.smzx.frag \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux/assets/glsl/\nendif\nifeq (${BUILD_RENDER_GL_PROGRAM},1)\n\t${install} -m 0755 -d \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux/assets/glsl/scalers\n\t${install} -m 0644 \\\n\t\tassets/glsl/scalers/crt-wave.frag \\\n\t\tassets/glsl/scalers/crt.frag \\\n\t\tassets/glsl/scalers/emboss.frag \\\n\t\tassets/glsl/scalers/epx.frag \\\n\t\tassets/glsl/scalers/greyscale.frag \\\n\t\tassets/glsl/scalers/hqscale.frag \\\n\t\tassets/glsl/scalers/hqscale.vert \\\n\t\tassets/glsl/scalers/nearest.frag \\\n\t\tassets/glsl/scalers/sai.frag \\\n\t\tassets/glsl/scalers/semisoft.frag \\\n\t\tassets/glsl/scalers/sepia.frag \\\n\t\tassets/glsl/scalers/simple.frag \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux/assets/glsl/scalers/\nendif\nifeq (${BUILD_GAMECONTROLLERDB},1)\n\t${install} -m 0644 \\\n\t\tassets/gamecontrollerdb.txt \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux/assets/\nendif\n\t@echo Done.\n\n#\n# Uninstall a build from the system.\n#\n\nuninstall: install-check uninstall-arch\n\t@echo Uninstalling...\n\t${RM} -r \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux \\\n\t\t${DESTDIR}${SHAREDIR}/megazeux/assets \\\n\t\t${DESTDIR}${SHAREDIR}/doc/megazeux \\\n\t\t${DESTDIR}${LICENSEDIR}/megazeux \\\n\t\t${DESTDIR}${SYSCONFDIR}/megazeux-config\nifeq (${BUILD_MODULAR},1)\n\t${RM} \\\n\t\t${DESTDIR}${LIBDIR}/${core_target} \\\n\t\t${DESTDIR}${LIBDIR}/${editor_target}\nendif\nifeq (${BUILD_MZXRUN},1)\n\t${RM} \\\n\t\t${DESTDIR}${GAMESDIR}/${mzxrun}\nifneq (${BUILD_EDITOR},1)\n\t${RM} -f ${DESTDIR}${METAINFODIR}/mzxrun.metainfo.xml\nendif\nendif\nifeq (${BUILD_EDITOR},1)\n\t${RM} \\\n\t\t${DESTDIR}${GAMESDIR}/${mzx}\n\t${RM} -f ${DESTDIR}${METAINFODIR}/megazeux.metainfo.xml\nendif\nifeq (${BUILD_UTILS},1)\n\t${RM} \\\n\t\t${DESTDIR}${BINDIR}/checkres \\\n\t\t${DESTDIR}${BINDIR}/downver \\\n\t\t${DESTDIR}${BINDIR}/hlp2txt \\\n\t\t${DESTDIR}${BINDIR}/txt2hlp \\\n\t\t${DESTDIR}${BINDIR}/png2smzx \\\n\t\t${DESTDIR}${BINDIR}/y4m2smzx \\\n\t\t${DESTDIR}${BINDIR}/ccv\nendif\n\t@echo Done.\n"
  },
  {
    "path": "arch/lha.inc",
    "content": "archive: build\n\t${RM} -r build/dist/${SUBPLATFORM}\n\t${MKDIR} -p build/dist/${SUBPLATFORM}\n\t@cd build/${SUBPLATFORM} && \\\n\t lha a ../dist/${SUBPLATFORM}/${TARGET}-${SUBPLATFORM}.lha *\n"
  },
  {
    "path": "arch/manifest.sh",
    "content": "#!/bin/sh\n\nset -e\nif [ \"$1\" = \"\" ]; then\n\techo \"usage: $0 [build dir]\"\n\texit 0\nfi\n\nif command -v sha256sum >/dev/null 2>/dev/null; then\n\texport sha256cmd=\"sha256sum -b\"\nelif command -v gsha256sum >/dev/null 2>/dev/null; then\n\texport sha256cmd=\"gsha256sum -b\"\nelif command -v sha256 >/dev/null 2>/dev/null; then\n\texport sha256cmd=\"sha256 -q\"\nelif command -v shasum >/dev/null 2>/dev/null; then\n\texport sha256cmd=\"shasum -a256 -b\"\nelse\n\techo \"error: install sha256sum (coreutils, BusyBox) or shasum (perl).\"\n\texit 1\nfi\n\necho \"Generating manifest...\"\n\ncd \"$1\"\n\nrm -f manifest.txt\n\n# -P24 is a GNU-ism but also the entire point of using xargs here.\n# Most BSD xargs seem to support it; BusyBox gracefully ignores it.\nfind . -mindepth 1 -type f\t\t\t\t\\\n\t! -name config.txt\t\t\t\t\\\n\t! -name manifest.txt\t\t\t\t\\\n\t! -name pad.config\t\t\t\t\\\n\t! -name '*.debug' \t\t\t\t\\\n\t-print0\t\t\t\t\t\t\\\n| xargs -0 -n1 -P24 /bin/sh -c\t\t\t\t\\\n\t'size=$(($(wc -c <\"$1\")));\t\t\t\\\n\t sha=$($sha256cmd \"$1\" | cut -f1 -d\" \");\t\\\n\t name=$(echo \"$1\" | sed \"s,\\\\.\\\\/,,\");\t\t\\\n\t echo \"$sha\" \"$size\" \"$name\"' \"$0\"\t\t\\\n| sort -k3\t\t\t\t\t\t\\\n>> manifest.txt\n"
  },
  {
    "path": "arch/mingw/Makefile.in",
    "content": "#\n# mingw makefile generics\n#\n\nPEFIX   = ${pefix}\nBINEXT  = .exe\n\nDSOLDFLAGS = -shared\nDSOPRE     =\nDSOPOST    = .dll\nDSORPATH   =\nDSOSONAME  = -Wl,-soname,\n\n# Don't use MSVCRT for stdio; it's slower and is not safe.\n# At one point (circa 2009) the MinGW implementations actually\n# caused performance issues, but this is no longer the case.\nARCH_CFLAGS   += -D__USE_MINGW_ANSI_STDIO\nARCH_CXXFLAGS += -D__USE_MINGW_ANSI_STDIO\n\n# Fix MINGW's shared library fanaticism\nARCH_LDFLAGS += -static-libstdc++ -static-libgcc\n\n# Thought that was enough? Nope, here's some vars for forcing mixed linking.\n# Prefix these to the linking flags for each library that will be linked to\n# MINGW builds. MINGW likes to link these however it wants otherwise.\nLINK_STATIC_IF_MIXED  ?= -Wl,-Bstatic\nLINK_DYNAMIC_IF_MIXED ?= -Wl,-Bdynamic\n\n# In Fedora, libmingwex dirent is compiled with stack protector now, so\n# libssp must manually be linked (and therefore libmingwex too).\nARCH_LIBS += ${LINK_STATIC_IF_MIXED} -lmingwex -lssp\n\n# OpenGL must be linked to MZX\ncore_ldflags += -lopengl32\n\n# Windows needs PNG to be statically linked (inc. zlib)\nLIBPNG_LDFLAGS = $(shell libpng-config --static --ldflags)\n\nifeq (${BUILD_SDL},1)\nSDL_DLL := SDL.dll\nendif\nifeq (${BUILD_SDL},2)\nSDL_DLL := SDL2.dll\nendif\nifeq (${BUILD_SDL},3)\nSDL_DLL := SDL3.dll\nendif\n\nifdef DEBUG\nARCH_LDFLAGS += -mconsole\nendif\n\n#\n# Don't use BINEXT here; if the host is MinGW it will automatically\n# append .exe, and if it isn't the extension would just be confusing.\n#\npefix := arch/mingw/pefix\n\n-include arch/mingw/pefix.d\n\narch/mingw/pefix.o: arch/mingw/pefix.c\n\t$(if ${V},,@echo \"  HOSTCC  \" $<)\n\t${HOST_CC} -MD ${CFLAGS} -c $< -o $@\n\n${pefix}: arch/mingw/pefix.o\n\t$(if ${V},,@echo \"  HOSTLINK\" ${pefix})\n\t${HOST_CC} arch/mingw/pefix.o -o ${pefix}\n\nall: ${pefix}\n\nclean: msvc_clean pefix_clean\n.PHONY: msvc_clean pefix_clean\n\npefix_clean:\n\t$(if ${V},,@echo \"  RM      \" ${pefix} arch/mingw/pefix.o arch/mingw/pefix.d)\n\t${RM} ${pefix} arch/mingw/pefix.o arch/mingw/pefix.d\n\n# Clean up MSVC files just in case; they break linking MinGW.\nmsvc_clean:\n\t$(if ${V},,@echo \"  RM      \" \"*.exp\" \"*.ilk\" \"*.lib\" \"*.pdb\")\n\t$(if ${V},,@echo \"  RM      \" arch/msvc/.vs arch/msvc/obj)\n\t${RM} -r arch/msvc/.vs arch/msvc/obj\n\t${RM} Core.exp Editor.exp MZXRun.exp MegaZeux.exp\n\t${RM} Core.ilk Editor.ilk MZXRun.ilk MegaZeux.ilk\n\t${RM} Core.lib Editor.lib MZXRun.lib MegaZeux.lib\n\t${RM} Core.pdb Editor.pdb MZXRun.pdb MegaZeux.pdb\n\n#\n# Windows builds must copy $SDL_DLL\n# For SDL 1.2 builds, we want directx.bat too\n#\n\nbuild: ${build}\n\t${CP} ${SDL_PREFIX}/bin/${SDL_DLL} ${build}\n#\tAt one point this actually did something useful.\n#\tThe SDL DLL will generally stay the same between releases,\n#\tso this is kind of pointless.\n#\t${PEFIX} ${build}/${SDL_DLL}\nifeq (${LIBSDL2},0)\n\t${CP} arch/mingw/directx.bat ${build}\nendif\nifeq (${BUILD_UTILS},1)\n\t${CP} arch/mingw/checkres* ${build}/utils\nendif\n\t@arch/manifest.sh ${build}\n\nSUBARCH := ${shell echo ${SUBPLATFORM} | sed 's,windows-,,'}\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/mingw/checkres-readme.txt",
    "content": "checkres overview:\r\n------------------\r\n\r\ncheckres is a utility that scans MZX worlds and finds all of the resources they\r\nrely on.  checkres will return a list of all palettes, character sets, sound\r\nfiles, and other files referenced in Robotic code and in board settings.  This\r\nincludes file commands like LOAD PALETTE, alternate functions of other commands\r\nlike PUT \"@file\" Image_file p00 [#] [#], and function counters like FREAD_OPEN.\r\n\r\ncheckres will attempt to match these references to real files in the directory\r\nof the MZX world--or to Robotic commands that may produce those files in gameplay.\r\n\r\n\r\ncheckres usage:\r\n---------------\r\n\r\ncheckres can be used two ways: by dragging and dropping files onto checkres.bat\r\nin Windows Explorer or by using the checkres executable directly on the command line.\r\n\r\n  C:\\MZX>utils\\checkres.exe mzm2ans.mzx\r\n\r\n  Required by   Resource path     Status     Found in\r\n  -----------   -------------     ------     --------\r\n  mzm2ans.mzx   ai/barrier.txt    NOT FOUND\r\n  mzm2ans.mzx   ai/ws-item.txt    NOT FOUND\r\n\r\n  Finished processing 'mzm2ans.mzx'.\r\n\r\n\r\nBoth methods will allow multiple inputs, e.g. dragging multiple files or\r\n\r\n  C:\\MZX>checkres.exe world1.mzx world2.mzx world3.mzx\r\n\r\n\r\ncheckres also supports MZX board files and ZIP archives.  checkres will scan\r\nevery .mzx and .mzb file found in a .zip archive.\r\n\r\ncheckres supports multiple options available from the command line only:\r\n\r\n  -h              Display usage info.\r\n  -a              Display all dependencies (found, not found, maybe created).\r\n  -A              Display all dependencies that do not exist (not found, maybe created).\r\n  -q              Prints resource paths only, with no extra formatting.\r\n\r\n  -extra [file]   Extra dependency path for the last provided base file.  This may be a\r\n                  folder or a zip archive.  For a .mzx or .mzb base file, the contents of\r\n                  this path will be treated as existing in the same folder as the base\r\n                  file.  For a zip archive, the contents of this path will be treated as\r\n                  existing at the root of the zip archive.\r\n\r\n  -in [path]      Provides a relative path for the extra dependency preceding it.\r\n\r\n\r\ncheckres examples:\r\n------------------\r\n\r\n  C:\\MZX>checkres.exe -a Labyrinth-Platinum.zip\r\n\r\n  Required by              Resource path               Status     Found in\r\n  -----------              -------------               ------     --------\r\n  Labyrinth/joymap.mzx     Labyrinth/joymap.cnf        FOUND      Labyrinth-Platinum.zip\r\n  Labyrinth/labyrinth.mzx  Labyrinth/temp/disc         CREATED\r\n  Labyrinth/labyrinth.mzx  Labyrinth/ZEUX/ascii.chr    FOUND      Labyrinth-Platinum.zip\r\n  Labyrinth/labyrinth.mzx  Labyrinth/ZEUX.CHR          FOUND      Labyrinth-Platinum.zip\r\n  Labyrinth/labyrinth.mzx  Labyrinth/ZEUX.PAL          FOUND      Labyrinth-Platinum.zip\r\n  Labyrinth/labyrinth.mzx  Labyrinth/__editor          CREATED\r\n  Labyrinth/labyrinth.mzx  Labyrinth/sfx/yeah.wav      FOUND      Labyrinth-Platinum.zip\r\n  Labyrinth/labyrinth.mzx  Labyrinth/sfx/bombdrop.ogg  FOUND      Labyrinth-Platinum.zip\r\n  Labyrinth/labyrinth.mzx  Labyrinth/sfx/bonus2.ogg    FOUND      Labyrinth-Platinum.zip\r\n  Labyrinth/labyrinth.mzx  Labyrinth/sfx/unlock.ogg    FOUND      Labyrinth-Platinum.zip\r\n  Labyrinth/labyrinth.mzx  Labyrinth/sfx/life.ogg      FOUND      Labyrinth-Platinum.zip\r\n  Labyrinth/labyrinth.mzx  Labyrinth/ZEUX.ZCFG         NOT FOUND\r\n  Labyrinth/labyrinth.mzx  Labyrinth/joymap.mzx        FOUND      Labyrinth-Platinum.zip\r\n  Labyrinth/labyrinth.mzx  Labyrinth/__menu            CREATED\r\n\r\n  Finished processing 'Labyrinth-Platinum.zip'.\r\n\r\n\r\n  C:\\MZX>checkres.exe -A -q DE_Game.zip -extra DE_Sound.zip\r\n\r\n  room1.mzm\r\n  room2.mzm\r\n  lightng2.sam\r\n\r\n\r\n  C:\\MZX>checkres.exe -A -q DE_Game.zip -extra DE_Sound.zip Taoyarin_v1.02.zip -extra ty_music.zip\r\n\r\n  room1.mzm\r\n  room2.mzm\r\n  lightng2.sam\r\n  ctree.raw\r\n  dither.raw\r\n  t2.dat\r\n\r\n\r\ncopyright:\r\n----------\r\n\r\ncheckres is a product of ajs and Revvy, with a massive rewrite by Lachesis.\r\nThe source code for checkres is available here:\r\n\r\nhttp://github.com/AliceLR/megazeux/raw/master/src/utils/checkres.c\r\n\r\nNote that checkres is dependent on the MegaZeux source code and will not build\r\nseparately from it.\r\n\r\nCopyright (c) 2007 Josh Matthews (mrlachatte@gmail.com)\r\nCopyright (C) 2017 Alice Rowan (petrifiedrowan@gmail.com)\r\n"
  },
  {
    "path": "arch/mingw/checkres.bat",
    "content": "@REM (C) 2007 Josh Matthews (mrlachatte@gmail.com)\r\n@echo off\r\nset flags=\r\n:args\r\nif \"%1\"==\"-a\" (\r\n  set flags=%flags% -a\r\n  shift\r\n)\r\nif \"%1\"==\"-q\" (\r\n  set flags=%flags% -q\r\n  shift\r\n)\r\nset ftemp=%1\r\nif \"%ftemp:~0,1%\"==\"-\" goto args\r\n\r\necho checkres.bat is intended to be used with file dragging in Windows Explorer.\r\necho For more options, use checkres.exe from the command line directly.\r\necho.\r\nif \"%1\"==\"\" goto nofile\r\n\r\n:: %~dp0 is the directory this batch file is in. This allows a\r\n:: .mzx/.zip to be dragged onto the batch file from another folder.\r\nset exec=%~dp0\\checkres.exe\r\n\r\n:loop\r\nif \"%1\"==\"\" goto end\r\necho Scanning %1...\r\n%exec% %flags% %1\r\nshift\r\npause\r\necho.\r\ngoto loop\r\n\r\n:nofile\r\necho No input files provided.\r\necho.\r\npause\r\n:end"
  },
  {
    "path": "arch/mingw/directx.bat",
    "content": "set SDL_VIDEODRIVER=directx\nstart \"\" \"%cd%\\megazeux.exe\"\n"
  },
  {
    "path": "arch/mingw/installer.nsi",
    "content": "; MegaZeux NSIS script\n\n!include \"MUI2.nsh\"\n\n!define srcdir \"dist/mzx282\"\n!define setup \"mzx282-x86.exe\"\n!define exec \"mzx282.exe\"\n\n!define prodname \"MegaZeux\"\n!define icon \"contrib/icons/quantump.ico\"\n\n!define regkey \"Software\\${prodname}\"\n!define uninstkey \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${prodname}\"\n\n!define startmenu \"$SMPROGRAMS\\${prodname}\"\n!define uninstaller \"uninstall.exe\"\n\n!define website \"https://www.digitalmzx.com/\"\n\n;--------------------------------\n\nXPStyle on\n\nShowInstDetails hide\nShowUninstDetails hide\n\nName \"${prodname}\"\nCaption \"${prodname}\"\nOutFile \"${setup}\"\n\nSetDateSave on\nCRCCheck on\nSilentInstall normal\n\nSetCompress Auto\nSetCompressor /SOLID lzma\nSetCompressorDictSize 32\nSetDatablockOptimize On\n\nInstallDir \"$PROGRAMFILES\\${prodname}\"\nInstallDirRegKey HKLM \"${regkey}\" \"\"\n\nRequestExecutionLevel admin\n\n!define MUI_ABORTWARNING\n\n!define MUI_ICON ${icon}\n!define MUI_UNICON ${icon}\n\n!insertmacro MUI_PAGE_DIRECTORY\n!insertmacro MUI_PAGE_INSTFILES\n\n!insertmacro MUI_UNPAGE_CONFIRM\n!insertmacro MUI_UNPAGE_INSTFILES\n\n!insertmacro MUI_LANGUAGE \"English\"\n\n;--------------------------------\n\n; Installer\n\nSection\n\n  WriteRegStr HKLM \"${regkey}\" \"Install_Dir\" \"$INSTDIR\"\n  WriteRegStr HKLM \"${uninstkey}\" \"DisplayName\" \"${prodname} (remove only)\"\n  WriteRegStr HKLM \"${uninstkey}\" \"UninstallString\" '\"$INSTDIR\\${uninstaller}\"'\n\n  SetOutPath $INSTDIR\n\n  ; package all files, recursively, preserving attributes\n  ; assume files are in the correct places\n  File /r \"${srcdir}/*\"\n\n  WriteUninstaller \"${uninstaller}\"\n\nSectionEnd\n\n; Start Menu\n\nSection\n\n  CreateDirectory \"${startmenu}\"\n  SetOutPath $INSTDIR ; for working directory\n  CreateShortCut \"${startmenu}\\${prodname}.lnk\" \"$INSTDIR\\${exec}\" \"\" \"$INSTDIR\\${exec}\"\n\n  WriteINIStr \"${startmenu}\\web site.url\" \"InternetShortcut\" \"URL\" ${website}\n\nSectionEnd\n\n; Uninstaller\n\nUninstallText \"This will uninstall ${prodname}.\"\n\nSection \"Uninstall\"\n\n  DeleteRegKey HKLM \"${uninstkey}\"\n  DeleteRegKey HKLM \"${regkey}\"\n\n  Delete \"${startmenu}\\*.*\"\n  Delete \"${startmenu}\"\n\n  RMDir /r \"$INSTDIR\"\n\nSectionEnd\n"
  },
  {
    "path": "arch/mingw/pefix.c",
    "content": "/**\n * This tool modifies standard Microsoft PE32 and PE32+ (Portable Executable)\n * files on the i386 and AMD64 platforms, such that two identical programs\n * compiled with identical compilers produce an identical binary. Generally,\n * PE binaries are not identical, because TimeDateStamp (which changes\n * continuously) is used in several mandatory executable sections.\n *\n * Apart from the main PE32 header, it is also necessary to modify the\n * optional .edata, .idata and .rsrc sections, since these sections' headers\n * (and in the case of .rsrc, contents) also contain TimeDateStamps.\n *\n * This program additionally works around a binutils bug which generates\n * random garbage between module names in the idata section. Normally,\n * this wouldn't affect program execution, but it does make the binaries\n * non-identical. Replacing this garbage with zeroes solves the problem.\n *\n * Finally, we take the opportunity to tweak DllCharacteristics so that\n * ASLR is enabled on newer Windows operating systems.\n *\n * Copyright (C) 2009-2010 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <inttypes.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n#include <time.h>\n\n#define SECTION_NAME_LEN 8\n\ntypedef enum\n{\n  SUCCESS,\n  OPEN_ERROR,\n  READ_ERROR,\n  WRITE_ERROR,\n  INVALID_MAGIC,\n} error_t;\n\nstatic const char *err_str(error_t err)\n{\n  switch(err)\n  {\n    case SUCCESS:\n      return \"Success\";\n    case OPEN_ERROR:\n      return \"Failed to open file\";\n    case READ_ERROR:\n      return \"Failure while reading from file\";\n    case WRITE_ERROR:\n      return \"Failure while writing to file\";\n    case INVALID_MAGIC:\n      return \"File magic was unrecognized or invalid\";\n    default:\n      return \"Unknown error\";\n  }\n}\n\nstatic long ftell_and_rewind(FILE *f)\n{\n  long size;\n\n  fseek(f, 0, SEEK_END);\n  size = ftell(f);\n  rewind(f);\n\n  return size;\n}\n\nstatic error_t pe_csum(FILE *f, uint32_t *csum)\n{\n  uint32_t sum = 0;\n  long count, len;\n  uint16_t w;\n\n  len = ftell_and_rewind(f);\n\n  for(count = len; count > 1; count -= 2)\n  {\n    if(fread(&w, sizeof(uint16_t), 1, f) != 1)\n      return READ_ERROR;\n    sum += w;\n  }\n\n  if(count == 1)\n  {\n    w = 0;\n    if(fread(&w, sizeof(uint8_t), 1, f) != 1)\n      return READ_ERROR;\n    sum += w;\n  }\n\n  sum = (sum >> 16) + (sum & 0xFFFF);\n  sum += (sum >> 16);\n\n  *csum = (uint32_t)((uint16_t)sum + len);\n  return SUCCESS;\n}\n\nstatic error_t process_rsrc(FILE *f, uint32_t rsrc_offset)\n{\n  uint16_t num_name, num_id, i;\n  error_t err = SUCCESS;\n  uint32_t ul, *dirs;\n\n  // Skip Characteristics\n  if(fseek(f, 4, SEEK_CUR))\n    return READ_ERROR;\n\n  // Re-write TimeDateStamp as zero\n  ul = 0;\n  if(fwrite(&ul, sizeof(uint32_t), 1, f) != 1)\n    return WRITE_ERROR;\n\n  // Skip MajorVersion, MinorVersion\n  if(fseek(f, 2 + 2, SEEK_CUR))\n    return READ_ERROR;\n\n  // Read NumberOfNamedEntries\n  if(fread(&num_name, sizeof(uint16_t), 1, f) != 1)\n    return READ_ERROR;\n\n  // Read NumberOfIdEntries\n  if(fread(&num_id, sizeof(uint16_t), 1, f) != 1)\n    return READ_ERROR;\n\n  dirs = malloc(sizeof(uint32_t) * (num_name + num_id));\n\n  for(i = 0; i < num_name + num_id; i++)\n  {\n    // Skip Name (never used)\n    if(fseek(f, 4, SEEK_CUR))\n    {\n      err = READ_ERROR;\n      goto err_free_dirs;\n    }\n\n    // Read OffsetToData\n    if(fread(&dirs[i], sizeof(uint32_t), 1, f) != 1)\n    {\n      err = READ_ERROR;\n      goto err_free_dirs;\n    }\n  }\n\n  for(i = 0; i < num_name + num_id; i++)\n  {\n    // Data is interesting because if the high bit is set on this,\n    // we need to read a subdirectory (recursively).\n\n    if(!(dirs[i] & 0x80000000))\n      continue;\n    dirs[i] &= ~0x80000000;\n\n    // Skip to the specified .rsrc subdirectory\n    if(fseek(f, rsrc_offset + dirs[i], SEEK_SET))\n    {\n      err = READ_ERROR;\n      goto err_free_dirs;\n    }\n\n    // Process the .rsrc subdirectory\n    err = process_rsrc(f, rsrc_offset);\n    if(err != SUCCESS)\n      goto err_free_dirs;\n  }\n\nerr_free_dirs:\n  free(dirs);\n  return err;\n}\n\nstatic error_t walk_rvas(FILE *f, uint16_t cpu_type,\n uint32_t virtaddr, uint32_t offset)\n{\n  uint32_t rva32, pad, phys, last_phys = 0;\n  unsigned int i;\n  uint64_t rva;\n  long pos;\n\n  while(1)\n  {\n    // RVAs are real virtual addresses, not just relative ones,\n    // so reading them is machine specific\n    if(cpu_type == 0x014c)\n    {\n      if(fread(&rva32, sizeof(uint32_t), 1, f) != 1)\n        return READ_ERROR;\n      rva = rva32;\n    }\n    else if(cpu_type == 0x8664)\n    {\n      if(fread(&rva, sizeof(uint64_t), 1, f) != 1)\n        return READ_ERROR;\n    }\n\n    // Zero RVA means \"stop reading\"\n    if(!rva)\n      break;\n\n    // Convert to file offset (physaddr)\n    phys = rva - virtaddr + offset;\n\n    // We need to have processed at least one phys\n    // before pad can be computed.\n\n    if(last_phys)\n    {\n      // Store current offset\n      pos = ftell(f);\n\n      // Seek to import name\n      if(fseek(f, last_phys, SEEK_SET))\n        return READ_ERROR;\n\n      // Compute string length for this RVA function\n      for(i = 0; ; i++)\n      {\n        int b = fgetc(f);\n        if(b < 0)\n          return READ_ERROR;\n\n        if(i >= 2 && !b)\n          break;\n      }\n\n      // Win32 I/O needs this between interleaved read/write\n      if(fseek(f, ftell(f), SEEK_SET))\n        return READ_ERROR;\n\n      // Compute and rewrite pad as zero\n      pad = phys - last_phys - (i + 1);\n      for(i = 0; i < pad; i++)\n        if(fputc(0, f) < 0)\n          return WRITE_ERROR;\n\n      // Seek back to where we were\n      if(fseek(f, pos, SEEK_SET))\n        return READ_ERROR;\n    }\n\n    last_phys = phys;\n  }\n\n  return SUCCESS;\n}\n\nstatic error_t modify_pe(FILE *f)\n{\n  uint32_t rsrc_offset = 0, edata_offset = 0, idata_offset = 0;\n  uint16_t cpu_type, num_sections, dll_characteristics;\n  char section_name[SECTION_NAME_LEN + 1];\n  uint32_t ul, idata_virtaddr = 0;\n  error_t err = SUCCESS;\n  long csum_offset;\n  int skip = 0;\n\n  // Check 'MZ' magic\n  if(fgetc(f) != 'M')\n    return INVALID_MAGIC;\n  if(fgetc(f) != 'Z')\n    return INVALID_MAGIC;\n\n  // Next 58 bytes of DOS header aren't interesting\n  if(fseek(f, 58, SEEK_CUR))\n    return READ_ERROR;\n\n  // Read so called 'lfanew' field; offset to PE header\n  if(fread(&ul, sizeof(uint32_t), 1, f) != 1)\n    return READ_ERROR;\n\n  // Skip to lfanew offset (move us to PE header)\n  if(fseek(f, ul, SEEK_SET))\n    return READ_ERROR;\n\n  // Check 'PE\\x00\\x00' magic\n  if(fgetc(f) != 'P')\n    return INVALID_MAGIC;\n  if(fgetc(f) != 'E')\n    return INVALID_MAGIC;\n  if(fgetc(f) != 0)\n    return INVALID_MAGIC;\n  if(fgetc(f) != 0)\n    return INVALID_MAGIC;\n\n  // Read in Machine (we support limited CPU types)\n  if(fread(&cpu_type, sizeof(uint16_t), 1, f) != 1)\n    return READ_ERROR;\n\n  // Can only support i386 and AMD64 binaries\n  if(cpu_type != 0x014c && cpu_type != 0x8664)\n    return INVALID_MAGIC;\n\n  // Read NumberOfSections (used below)\n  if(fread(&num_sections, sizeof(uint16_t), 1, f) != 1)\n    return READ_ERROR;\n\n  // Win32 I/O needs this between interleaved read/write\n  if(fseek(f, ftell(f), SEEK_SET))\n    return READ_ERROR;\n\n  // The checksum must be rewritten as a constant value. However,\n  // the logical choice of zero does not work correctly, as this\n  // exposes a Windows bug which leaves the EXE open after it has\n  // loaded, breaking the updater. Use the 2038 timestamp instead.\n  ul = 0x7fffffff;\n  if(fwrite(&ul, sizeof(uint32_t), 1, f) != 1)\n    return WRITE_ERROR;\n\n  // Skip PointerToSymbolTable, NumberOfSymbols, SizeOfOptionalHeader\n  // Characteristics\n  if(fseek(f, 4 + 4 + 2 + 2, SEEK_CUR))\n    return READ_ERROR;\n\n  // Skip Magic, MajorLinkerVersion, MinorLinkerVersion, SizeOfCode,\n  // SizeOfInitializedData, SizeOfUninitializedData, AddressOfEntryPoint,\n  // BaseOfCode, BaseOfData [i386], ImageBase*, SectionAlignment,\n  // FileAlignment, MajorOperatingSystemVersion, MinorOperatingSystemVersion,\n  // MajorImageVersion, MinorImageVersion, MajorSubsystemVersion,\n  // MinorSubsystemVersion, Reserved1, SizeOfImage, SizeOfHeaders\n  if(cpu_type == 0x014c)\n    skip = 2 + 1 + 1 + 4 + 4 + 4 + 4 + 4 + 4 +\n           4 + 4 + 4 + 2 + 2 + 2 + 2 + 2 + 2 + 4 + 4 + 4;\n  else if(cpu_type == 0x8664)\n    skip = 2 + 1 + 1 + 4 + 4 + 4 + 4 + 4 +\n           8 + 4 + 4 + 2 + 2 + 2 + 2 + 2 + 2 + 4 + 4 + 4;\n  if(fseek(f, skip, SEEK_CUR))\n    return READ_ERROR;\n\n  // Squash obsolete Checksum with zero\n  ul = 0;\n  csum_offset = ftell(f);\n  if(fwrite(&ul, sizeof(uint32_t), 1, f) != 1)\n    return WRITE_ERROR;\n\n  // Skip Subsystem\n  if(fseek(f, 2, SEEK_CUR))\n    return READ_ERROR;\n\n  // Read DllCharacteristics\n  if(fread(&dll_characteristics, sizeof(uint16_t), 1, f) != 1)\n    return READ_ERROR;\n\n  // Switch on DYNAMIC_BASE and NX_COMPAT\n  dll_characteristics |= (0x40 | 0x100);\n\n  // Roll back and re-write DllCharacteristics\n  if(fseek(f, -2, SEEK_CUR))\n    return READ_ERROR;\n  if(fwrite(&dll_characteristics, sizeof(uint16_t), 1, f) != 1)\n    return WRITE_ERROR;\n\n  // Skip SizeOfStackReserve*, SizeOfStackCommit*, SizeOfHeapReserve*,\n  // SizeOfHeapCommit*, LoaderFlags, NumberOfRvaAndSizes,\n  // DataDirectory (16*(4+4))\n  if(cpu_type == 0x014c)\n    skip = 4 + 4 + 4 + 4 + 4 + 4 + 16 * (4 + 4);\n  else if(cpu_type == 0x8664)\n    skip = 8 + 8 + 8 + 8 + 4 + 4 + 16 * (4 + 4);\n  if(fseek(f, skip, SEEK_CUR))\n    return READ_ERROR;\n\n  // PE Section Header processing (find .rsrc if present)\n  section_name[SECTION_NAME_LEN] = 0;\n  while(num_sections-- > 0)\n  {\n    uint32_t *offset = NULL;\n\n    // Read the Name\n    if(fread(&section_name, SECTION_NAME_LEN, 1, f) != 1)\n      return READ_ERROR;\n\n    // Skip Misc\n    if(fseek(f, 4, SEEK_CUR))\n      return READ_ERROR;\n\n    // Read VirtualAddress (only needed for .idata)\n    if(fread(&ul, sizeof(uint32_t), 1, f) != 1)\n      return READ_ERROR;\n\n    // Skip SizeOfRawData\n    if(fseek(f, 4, SEEK_CUR))\n      return READ_ERROR;\n\n    // This isn't .rsrc so we skip PointerToRawData\n    if(strcmp(section_name, \".rsrc\") == 0)\n      offset = &rsrc_offset;\n    else if(strcmp(section_name, \".edata\") == 0)\n      offset = &edata_offset;\n    else if(strcmp(section_name, \".idata\") == 0)\n    {\n      offset = &idata_offset;\n      idata_virtaddr = ul;\n    }\n\n    if(offset)\n    {\n      // Read the .rsrc PointerToRawData for this section\n      if(fread(offset, sizeof(uint32_t), 1, f) != 1)\n        return READ_ERROR;\n    }\n    else\n    {\n      // Skip the PointerToRawData for this section\n      if(fseek(f, 4, SEEK_CUR))\n        return READ_ERROR;\n    }\n\n    // Skip PointerToRelocations, PointerToLinenumbers,\n    // NumberOfRelocations, NumberOfLinenumbers, Characteristics\n    if(fseek(f, 4 + 4 + 2 + 2 + 4, SEEK_CUR))\n      return READ_ERROR;\n  }\n\n  // PE .rsrc section handling\n  if(rsrc_offset != 0)\n  {\n    // Reposition file pointer at (.rsrc)\n    if(fseek(f, rsrc_offset, SEEK_SET))\n      return READ_ERROR;\n\n    err = process_rsrc(f, rsrc_offset);\n    if(err != SUCCESS)\n      return err;\n  }\n\n  // PE .edata section handling\n  if(edata_offset != 0)\n  {\n    // Reposition file pointer at (.edata+4), Skip Characteristics\n    if(fseek(f, edata_offset + 4, SEEK_SET))\n      return READ_ERROR;\n\n    // Re-write TimeDateStamp as zero (new Checksum computed later)\n    ul = 0;\n    if(fwrite(&ul, sizeof(uint32_t), 1, f) != 1)\n      return WRITE_ERROR;\n  }\n\n  if(idata_offset != 0)\n  {\n    // Reposition file pointer at (.idata)\n    if(fseek(f, idata_offset, SEEK_SET))\n      return READ_ERROR;\n\n    while(1)\n    {\n      uint32_t fnlist;\n      long offset;\n\n      // dwRVAFunctionNameList\n      if(fread(&fnlist, sizeof(uint32_t), 1, f) != 1)\n        return READ_ERROR;\n\n      // Skip dwUseless1, dwUseless2, dwRVAModuleName, dwRVAFunctionAddressList\n      if(fseek(f, 4 + 4 + 4 + 4, SEEK_CUR))\n        return READ_ERROR;\n\n      // Zero dwRVAFunctionNameList means there are no more modules\n      if(!fnlist)\n        break;\n\n      fnlist = fnlist - idata_virtaddr + idata_offset;\n\n      // Store current file offset\n      offset = ftell(f);\n\n      // Seek to fnlist RVAs\n      if(fseek(f, fnlist, SEEK_SET))\n        return READ_ERROR;\n\n      // Walk fnlist RVAs\n      err = walk_rvas(f, cpu_type, idata_virtaddr, idata_offset);\n      if(err != SUCCESS)\n        return err;\n\n      // Move back to original position\n      if(fseek(f, offset, SEEK_SET))\n        return READ_ERROR;\n    }\n  }\n\n  // Compute and write back valid checksum\n  err = pe_csum(f, &ul);\n  if(err != SUCCESS)\n    return err;\n  if(fseek(f, csum_offset, SEEK_SET))\n    return READ_ERROR;\n  if(fwrite(&ul, sizeof(uint32_t), 1, f) != 1)\n    return WRITE_ERROR;\n\n  return SUCCESS;\n}\n\nint main(int argc, char *argv[])\n{\n  error_t err = SUCCESS;\n  FILE *f;\n\n  if(argc != 2)\n  {\n    fprintf(stderr, \"usage: %s [exe]\\n\", argv[0]);\n    return 0;\n  }\n\n  f = fopen(argv[1], \"r+b\");\n  if(f)\n  {\n    err = modify_pe(f);\n    fclose(f);\n  }\n  else\n    err = OPEN_ERROR;\n\n  if(err != SUCCESS)\n    fprintf(stderr, \"Failed: %s\\n\", err_str(err));\n\n  return err;\n}\n"
  },
  {
    "path": "arch/msvc/Core.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <ItemGroup Label=\"ProjectConfigurations\">\r\n    <ProjectConfiguration Include=\"Debug|Win32\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|Win32\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Debug|x64\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|x64\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n  </ItemGroup>\r\n  <PropertyGroup Label=\"Globals\">\r\n    <VCProjectVersion>15.0</VCProjectVersion>\r\n    <ProjectGuid>{48961A1D-DC1C-4714-93DA-C1E185E4DAAE}</ProjectGuid>\r\n    <RootNamespace>Core</RootNamespace>\r\n    <WindowsTargetPlatformVersion>7.0</WindowsTargetPlatformVersion>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\r\n  <ImportGroup Label=\"ExtensionSettings\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"Shared\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <PropertyGroup Label=\"UserMacros\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <IncludePath>$(SolutionDir);$(SolutionDir)\\Deps\\include;$(SolutionDir)\\Deps\\include\\SDL2;$(SolutionDir)\\..\\..\\contrib\\libxmp\\include;$(SolutionDir)\\..\\..\\contrib\\libxmp\\src;$(SolutionDir)\\..\\..\\contrib\\libxmp\\src\\win32;$(SolutionDir)\\..\\..\\contrib\\gdm2s3m\\src;$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)\\Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <IncludePath>$(SolutionDir);$(SolutionDir)\\Deps\\include;$(SolutionDir)\\Deps\\include\\SDL2;$(SolutionDir)\\..\\..\\contrib\\libxmp\\include;$(SolutionDir)\\..\\..\\contrib\\libxmp\\src;$(SolutionDir)\\..\\..\\contrib\\libxmp\\src\\win32;$(SolutionDir)\\..\\..\\contrib\\gdm2s3m\\src;$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)\\Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <IncludePath>$(SolutionDir);$(SolutionDir)\\Deps\\include;$(SolutionDir)\\Deps\\include\\SDL2;$(SolutionDir)\\..\\..\\contrib\\libxmp\\include;$(SolutionDir)\\..\\..\\contrib\\libxmp\\src;$(SolutionDir)\\..\\..\\contrib\\libxmp\\src\\win32;$(SolutionDir)\\..\\..\\contrib\\gdm2s3m\\src;$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)\\Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <IncludePath>$(SolutionDir);$(SolutionDir)\\Deps\\include;$(SolutionDir)\\Deps\\include\\SDL2;$(SolutionDir)\\..\\..\\contrib\\libxmp\\include;$(SolutionDir)\\..\\..\\contrib\\libxmp\\src;$(SolutionDir)\\..\\..\\contrib\\libxmp\\src\\win32;$(SolutionDir)\\..\\..\\contrib\\gdm2s3m\\src;$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)\\Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n  </PropertyGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>Disabled</Optimization>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>_ALLOW_KEYWORD_MACROS;CORE_LIBSPEC=__declspec(dllexport);PATH_MAX=1024;_USE_MATH_DEFINES;NEED_PNG_WRITE_SCREEN;LIBXMP_NO_DEPACKERS;_CRT_SECURE_NO_WARNINGS;__WIN32__;WIN32;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <AdditionalOptions>/J /Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n      <SDLCheck>false</SDLCheck>\r\n      <DisableSpecificWarnings>\r\n      </DisableSpecificWarnings>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n    </ClCompile>\r\n    <Link>\r\n      <AdditionalDependencies>libogg.lib;libpng.lib;libvorbis.lib;libvorbisfile.lib;SDL2.lib;SDL2main.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n    </Link>\r\n    <PreBuildEvent>\r\n      <Command>call $(SolutionDir)update_version.cmd\r\necho #define VERSION_DATE \" ($(BuildDate))\"&gt;&gt;$(SolutionDir)version.h</Command>\r\n    </PreBuildEvent>\r\n    <PreBuildEvent>\r\n      <Message>Updating version header</Message>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>Disabled</Optimization>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>_ALLOW_KEYWORD_MACROS;CORE_LIBSPEC=__declspec(dllexport);PATH_MAX=1024;_USE_MATH_DEFINES;LIBXMP_NO_DEPACKERS;LIBXMP_NO_PROWIZARD;_CRT_SECURE_NO_WARNINGS;__WIN32__;WIN32;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <AdditionalOptions>/J /Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n      <SDLCheck>false</SDLCheck>\r\n      <DisableSpecificWarnings>4018;4244;4267;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n    </ClCompile>\r\n    <Link>\r\n      <AdditionalDependencies>libogg.lib;libpng.lib;libvorbis.lib;libvorbisfile.lib;SDL2.lib;SDL2main.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n    </Link>\r\n    <PreBuildEvent>\r\n      <Command>call $(SolutionDir)update_version.cmd\r\necho #define VERSION_DATE \" ($(BuildDate))\"&gt;&gt;$(SolutionDir)version.h</Command>\r\n    </PreBuildEvent>\r\n    <PreBuildEvent>\r\n      <Message>Updating version header</Message>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>MaxSpeed</Optimization>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>_ALLOW_KEYWORD_MACROS;CORE_LIBSPEC=__declspec(dllexport);PATH_MAX=1024;_USE_MATH_DEFINES;NEED_PNG_WRITE_SCREEN;LIBXMP_NO_DEPACKERS;_CRT_SECURE_NO_WARNINGS;__WIN32__;WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <AdditionalOptions>/J /Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n      <SDLCheck>false</SDLCheck>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n    </ClCompile>\r\n    <Link>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <AdditionalDependencies>libogg.lib;libpng.lib;libvorbis.lib;libvorbisfile.lib;SDL2.lib;SDL2main.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n    </Link>\r\n    <PreBuildEvent>\r\n      <Command>call $(SolutionDir)update_version.cmd\r\necho #define VERSION_DATE \" ($(BuildDate))\"&gt;&gt;$(SolutionDir)version.h</Command>\r\n    </PreBuildEvent>\r\n    <PreBuildEvent>\r\n      <Message>Updating version header</Message>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>MaxSpeed</Optimization>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>_ALLOW_KEYWORD_MACROS;CORE_LIBSPEC=__declspec(dllexport);PATH_MAX=1024;_USE_MATH_DEFINES;LIBXMP_NO_DEPACKERS;LIBXMP_NO_PROWIZARD;_CRT_SECURE_NO_WARNINGS;__WIN32__;WIN32;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <AdditionalOptions>/J /Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n      <SDLCheck>false</SDLCheck>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n      <DisableSpecificWarnings>4018;4244;4267;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r\n    </ClCompile>\r\n    <Link>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <AdditionalDependencies>libogg.lib;libpng.lib;libvorbis.lib;libvorbisfile.lib;SDL2.lib;SDL2main.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n    </Link>\r\n    <PreBuildEvent>\r\n      <Command>call $(SolutionDir)update_version.cmd\r\necho #define VERSION_DATE \" ($(BuildDate))\"&gt;&gt;$(SolutionDir)version.h</Command>\r\n    </PreBuildEvent>\r\n    <PreBuildEvent>\r\n      <Message>Updating version header</Message>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemGroup>\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\control.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\dataio.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\effects.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\extras.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\far_extras.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\filetype.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\filter.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\flow.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\format.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\hio.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\hmn_extras.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\lfo.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\669_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\amf_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\asylum_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\common.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\far_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\flt_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\gdm_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\hmn_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\ice_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\iff.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\itsex.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\it_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\med2_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\med3_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\med4_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\mmd1_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\mmd3_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\mmd_common.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\mod_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\mtm_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\okt_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\s3m_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\sample.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\stm_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\st_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\ult_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\xm_load.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\load_helpers.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\md5.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\med_extras.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\memio.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\mixer.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\mix_all.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\period.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\player.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\read_event.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\rng.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\scan.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\smix.c\" />\r\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\virtual.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\about.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\audio\\audio.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\audio\\audio_pcs.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\audio\\audio_reality.cpp\" />\r\n    <ClCompile Include=\"..\\..\\src\\audio\\audio_sdl.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\audio\\audio_vorbis.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\audio\\audio_wav.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\audio\\audio_xmp.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\audio\\ext.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\audio\\sampled_stream.cpp\" />\r\n    <ClCompile Include=\"..\\..\\src\\audio\\sfx.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\block.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\board.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\caption.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\configure.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\core.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\core_task.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\counter.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\data.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\error.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\event.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\event_sdl.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\expr.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\game.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\game_menu.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\game_ops.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\game_player.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\game_update.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\game_update_board.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\graphics.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\helpsys.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\idarray.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\idput.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\intake.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\intake_num.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\io\\fsafeopen.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\io\\path.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\io\\vfs.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\io\\vio.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\io\\zip.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\io\\zip_stream.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\legacy_board.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\legacy_rasm.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\legacy_robot.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\legacy_world.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\mzm.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\platform_sdl.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\platform_time.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\pngops.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\render.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\render_gl.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\render_gl1.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\render_gl2.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\render_glsl.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\render_layer.cpp\" />\r\n    <ClCompile Include=\"..\\..\\src\\render_sdl.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\render_soft.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\render_softscale.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\robot.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\run_robot.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\scrdisp.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\settings.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\sprite.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\str.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\util.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\window.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\world.c\" />\r\n    <ClCompile Include=\"win32time.c\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\common.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\effects.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\extras.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\far_extras.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\format.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\hio.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\hmn_extras.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\lfo.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\list.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\iff.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\it.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\loader.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\med.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\mod.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\s3m.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\xm.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\md5.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\mdataio.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\med_extras.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\memio.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\mixer.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\period.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\player.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\precomp_lut.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\rng.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\virtual.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\win32\\osdcomm.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\win32\\ptpopen.h\" />\r\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\win32\\unistd.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\about.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\audio\\audio.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\audio\\audio_pcs.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\audio\\audio_reality.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\audio\\audio_vorbis.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\audio\\audio_wav.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\audio\\audio_xmp.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\audio\\ext.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\audio\\sampled_stream.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\audio\\sfx.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\block.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\board.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\caption.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\compat.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\configure.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\const.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\core.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\core_task.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\counter.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\data.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\error.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\event.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\expr.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\game.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\game_menu.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\game_ops.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\game_player.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\game_update.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\graphics.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\hashtable.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\helpsys.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\idarray.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\idput.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\intake.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\intake_num.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\bitstream.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\fsafeopen.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\memfile.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\path.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\vfile.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\vfs.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\vio.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\vio_win32.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\zip.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\zip_deflate.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\zip_deflate64.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\zip_dict.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\zip_implode.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\zip_reduce.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\zip_shrink.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\zip_stream.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\keysym.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\legacy_board.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\legacy_rasm.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\legacy_robot.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\legacy_world.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\mzm.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\platform.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\platform_endian.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\pngops.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\render.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\renderers.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\render_gl.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\render_layer.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\render_layer_code.hpp\" />\r\n    <ClInclude Include=\"..\\..\\src\\render_sdl.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\robot.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\robot_struct.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\scrdisp.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\SDLmzx.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\settings.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\sprite.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\sprite_struct.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\str.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\util.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\window.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\world.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\world_format.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\world_struct.h\" />\r\n    <ClInclude Include=\"config.h\" />\r\n    <ClInclude Include=\"dirent.h\" />\r\n    <ClInclude Include=\"msvc.h\" />\r\n    <ClInclude Include=\"win32time.h\" />\r\n  </ItemGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\r\n  <ImportGroup Label=\"ExtensionTargets\">\r\n  </ImportGroup>\r\n  <PropertyGroup>\r\n    <BuildDate>$([System.DateTime]::Now.ToString(yyyyMMdd))</BuildDate>\r\n  </PropertyGroup>\r\n</Project>"
  },
  {
    "path": "arch/msvc/Core.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <Filter Include=\"Source Files\">\n      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\n      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\n    </Filter>\n    <Filter Include=\"Header Files\">\n      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\n      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>\n    </Filter>\n    <Filter Include=\"Resource Files\">\n      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\n      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\n    </Filter>\n    <Filter Include=\"Header Files\\MSVC\">\n      <UniqueIdentifier>{d14bf040-4f3e-4535-80a4-5fb0ccf167c5}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Source Files\\contrib\">\n      <UniqueIdentifier>{8a9e0782-2347-4780-bc6c-0c3235801faa}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Source Files\\contrib\\libxmp\">\n      <UniqueIdentifier>{107eb3a2-bd41-481c-a305-4f34cfaca719}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Source Files\\contrib\\libxmp\\loaders\">\n      <UniqueIdentifier>{05fc379c-d617-4ea0-ad3f-444ccf74fba8}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Header Files\\contrib\">\n      <UniqueIdentifier>{c590945c-346c-429f-8553-b432ae68e7b7}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Header Files\\contrib\\libxmp\">\n      <UniqueIdentifier>{38d84df2-712f-43ad-bc11-fa34314f80d3}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Header Files\\contrib\\libxmp\\loaders\">\n      <UniqueIdentifier>{0d372dc8-2fe5-4af1-87a0-7bdb6ea3b8b8}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Header Files\\contrib\\libxmp\\win32\">\n      <UniqueIdentifier>{526cc8dd-11ed-4a21-8442-97a5e0f30d3a}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Source Files\\MSVC\">\n      <UniqueIdentifier>{36c07643-1016-4818-9590-bdcb3f5f7813}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Source Files\\audio\">\n      <UniqueIdentifier>{2924334e-e5f9-4e26-bf34-4bbe8f27bca3}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Source Files\\io\">\n      <UniqueIdentifier>{6f038785-3f01-4422-942d-4dc8c0d2df67}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Header Files\\audio\">\n      <UniqueIdentifier>{0ca3b10f-ed64-4953-931e-535abaf87bf2}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Header Files\\io\">\n      <UniqueIdentifier>{26dcbc3a-095f-45ba-9101-696dd1659803}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\src\\block.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\board.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\configure.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\counter.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\data.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\error.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\event.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\expr.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\game.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\graphics.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\idarray.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\idput.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\intake.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\legacy_board.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\legacy_rasm.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\legacy_robot.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\legacy_world.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\mzm.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\render.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\robot.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\run_robot.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\scrdisp.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\sprite.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\str.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\util.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\window.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\world.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\669_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\amf_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\asylum_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\common.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\far_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\gdm_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\iff.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\it_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\itsex.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\med2_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\med3_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\med4_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\mmd_common.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\mmd1_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\mmd3_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\mod_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\mtm_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\okt_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\s3m_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\sample.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\stm_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\ult_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\xm_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\control.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\dataio.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\effects.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\extras.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\filter.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\format.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\hio.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\hmn_extras.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\lfo.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\load.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\load_helpers.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\md5.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\med_extras.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\memio.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\mix_all.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\mixer.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\period.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\player.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\read_event.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\scan.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\smix.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\virtual.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\platform_sdl.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\event_sdl.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\render_sdl.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\render_gl.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\render_gl1.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\render_gl2.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\render_glsl.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\render_soft.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\helpsys.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\pngops.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\flt_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\hmn_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\st_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\audio\\ext.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\audio\\sfx.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\game_menu.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\game_ops.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\game_player.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\game_update.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\game_update_board.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\caption.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\settings.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\core.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\intake_num.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\render_softscale.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"win32time.c\">\n      <Filter>Source Files\\MSVC</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\render_layer.cpp\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\audio\\audio.c\">\n      <Filter>Source Files\\audio</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\audio\\audio_pcs.c\">\n      <Filter>Source Files\\audio</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\audio\\audio_reality.cpp\">\n      <Filter>Source Files\\audio</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\audio\\audio_sdl.c\">\n      <Filter>Source Files\\audio</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\audio\\audio_vorbis.c\">\n      <Filter>Source Files\\audio</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\audio\\audio_wav.c\">\n      <Filter>Source Files\\audio</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\audio\\audio_xmp.c\">\n      <Filter>Source Files\\audio</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\io\\fsafeopen.c\">\n      <Filter>Source Files\\io</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\io\\path.c\">\n      <Filter>Source Files\\io</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\io\\vio.c\">\n      <Filter>Source Files\\io</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\io\\zip.c\">\n      <Filter>Source Files\\io</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\io\\zip_stream.c\">\n      <Filter>Source Files\\io</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\ice_load.c\">\n      <Filter>Source Files\\contrib\\libxmp\\loaders</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\audio\\sampled_stream.cpp\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\far_extras.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\platform_time.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\about.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\filetype.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\core_task.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\rng.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\contrib\\libxmp\\src\\flow.c\">\n      <Filter>Source Files\\contrib\\libxmp</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\io\\vfs.c\">\n      <Filter>Source Files\\io</Filter>\n    </ClCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\..\\src\\block.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\board.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\configure.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\counter.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\data.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\error.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\event.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\expr.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\game.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\graphics.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\idarray.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\idput.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\intake.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\legacy_board.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\legacy_rasm.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\legacy_robot.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\legacy_world.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\mzm.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\render.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\render_layer.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\robot.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\scrdisp.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\sprite.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\str.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\util.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\window.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\world.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"dirent.h\">\n      <Filter>Header Files\\MSVC</Filter>\n    </ClInclude>\n    <ClInclude Include=\"msvc.h\">\n      <Filter>Header Files\\MSVC</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\common.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\effects.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\extras.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\format.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\hio.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\hmn_extras.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\lfo.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\list.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\md5.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\mdataio.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\med_extras.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\memio.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\mixer.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\period.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\player.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\precomp_lut.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\virtual.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\iff.h\">\n      <Filter>Header Files\\contrib\\libxmp\\loaders</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\it.h\">\n      <Filter>Header Files\\contrib\\libxmp\\loaders</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\loader.h\">\n      <Filter>Header Files\\contrib\\libxmp\\loaders</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\med.h\">\n      <Filter>Header Files\\contrib\\libxmp\\loaders</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\mod.h\">\n      <Filter>Header Files\\contrib\\libxmp\\loaders</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\s3m.h\">\n      <Filter>Header Files\\contrib\\libxmp\\loaders</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\loaders\\xm.h\">\n      <Filter>Header Files\\contrib\\libxmp\\loaders</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\win32\\osdcomm.h\">\n      <Filter>Header Files\\contrib\\libxmp\\win32</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\win32\\ptpopen.h\">\n      <Filter>Header Files\\contrib\\libxmp\\win32</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\win32\\unistd.h\">\n      <Filter>Header Files\\contrib\\libxmp\\win32</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\render_sdl.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\compat.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\render_gl.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\helpsys.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\pngops.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\const.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\keysym.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\platform.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\platform_endian.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\renderers.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\robot_struct.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\sprite_struct.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\world_struct.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\audio\\ext.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\audio\\sampled_stream.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\audio\\sfx.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\caption.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\core.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\game_menu.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\game_ops.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\game_player.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\game_update.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\intake_num.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\settings.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"win32time.h\">\n      <Filter>Header Files\\MSVC</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\render_layer_code.hpp\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\audio\\audio.h\">\n      <Filter>Header Files\\audio</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\audio\\audio_pcs.h\">\n      <Filter>Header Files\\audio</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\audio\\audio_reality.h\">\n      <Filter>Header Files\\audio</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\audio\\audio_vorbis.h\">\n      <Filter>Header Files\\audio</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\audio\\audio_wav.h\">\n      <Filter>Header Files\\audio</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\audio\\audio_xmp.h\">\n      <Filter>Header Files\\audio</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\bitstream.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\fsafeopen.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\memfile.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\path.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\vfile.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\vio.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\vio_win32.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\zip.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\zip_deflate.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\zip_deflate64.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\zip_dict.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\zip_implode.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\zip_reduce.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\zip_shrink.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\zip_stream.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\hashtable.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\SDLmzx.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"config.h\">\n      <Filter>Header Files\\MSVC</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\world_format.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\far_extras.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\about.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\core_task.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\contrib\\libxmp\\src\\rng.h\">\n      <Filter>Header Files\\contrib\\libxmp</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\vfs.h\">\n      <Filter>Header Files\\io</Filter>\n    </ClInclude>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "arch/msvc/Editor.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <ItemGroup Label=\"ProjectConfigurations\">\r\n    <ProjectConfiguration Include=\"Debug|Win32\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|Win32\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Debug|x64\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|x64\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n  </ItemGroup>\r\n  <PropertyGroup Label=\"Globals\">\r\n    <VCProjectVersion>15.0</VCProjectVersion>\r\n    <ProjectGuid>{F706F6E6-15C4-4748-B478-59105CD1189F}</ProjectGuid>\r\n    <RootNamespace>Editor</RootNamespace>\r\n    <WindowsTargetPlatformVersion>7.0</WindowsTargetPlatformVersion>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\r\n  <ImportGroup Label=\"ExtensionSettings\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"Shared\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <PropertyGroup Label=\"UserMacros\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <IncludePath>$(SolutionDir);$(SolutionDir)\\Deps\\include;$(SolutionDir)\\Deps\\include\\SDL2;$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)\\Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <IncludePath>$(SolutionDir);$(SolutionDir)\\Deps\\include;$(SolutionDir)\\Deps\\include\\SDL2;$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)\\Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <IncludePath>$(SolutionDir);$(SolutionDir)\\Deps\\include;$(SolutionDir)\\Deps\\include\\SDL2;$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)\\Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <IncludePath>$(SolutionDir);$(SolutionDir)\\Deps\\include;$(SolutionDir)\\Deps\\include\\SDL2;$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)\\Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n  </PropertyGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>Disabled</Optimization>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>EDITOR_LIBSPEC=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;__WIN32__;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <AdditionalOptions>/J /Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n      <SDLCheck>false</SDLCheck>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n    </ClCompile>\r\n    <Link>\r\n      <AdditionalDependencies>zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>Disabled</Optimization>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>EDITOR_LIBSPEC=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;__WIN32__;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <AdditionalOptions>/J /Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n      <SDLCheck>false</SDLCheck>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n      <DisableSpecificWarnings>4244;4267;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r\n    </ClCompile>\r\n    <Link>\r\n      <AdditionalDependencies>zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>MaxSpeed</Optimization>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>EDITOR_LIBSPEC=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;__WIN32__;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <AdditionalOptions>/J /Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n      <SDLCheck>false</SDLCheck>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n    </ClCompile>\r\n    <Link>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <AdditionalDependencies>zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>MaxSpeed</Optimization>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>EDITOR_LIBSPEC=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;__WIN32__;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <AdditionalOptions>/J /Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n      <SDLCheck>false</SDLCheck>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n      <DisableSpecificWarnings>4244;4267;%(DisableSpecificWarnings)</DisableSpecificWarnings>\r\n    </ClCompile>\r\n    <Link>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <AdditionalDependencies>zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemGroup>\r\n    <ClCompile Include=\"..\\..\\src\\editor\\ansi.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\block.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\board.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\buffer.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\char_ed.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\clipboard_win32.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\configure.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\debug.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\edit.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\edit_di.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\edit_export.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\edit_menu.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\fill.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\graphics.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\macro.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\pal_ed.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\param.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\robot.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\robo_debug.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\robo_ed.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\select.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\sfx_edit.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\stringsearch.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\undo.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\window.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\editor\\world.c\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClInclude Include=\"..\\..\\src\\audio.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\audio\\sfx.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\block.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\const.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\core_task.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\counter.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\data.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\ansi.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\block.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\board.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\buffer.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\char_ed.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\clipboard.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\configure.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\debug.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\edit.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\edit_di.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\edit_export.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\edit_menu.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\fill.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\graphics.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\macro.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\pal_ed.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\param.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\robot.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\robo_debug.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\robo_ed.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\select.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\sfx_edit.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\undo.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\window.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\editor\\world.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\error.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\event.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\game.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\graphics.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\helpsys.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\idarray.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\idput.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\intake.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\io\\zip.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\mzm.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\platform.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\scrdisp.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\SDLmzx.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\util.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\window.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\world.h\" />\r\n    <ClInclude Include=\"config.h\" />\r\n    <ClInclude Include=\"dirent.h\" />\r\n    <ClInclude Include=\"msvc.h\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ProjectReference Include=\"Core.vcxproj\">\r\n      <Project>{48961a1d-dc1c-4714-93da-c1e185e4daae}</Project>\r\n    </ProjectReference>\r\n  </ItemGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\r\n  <ImportGroup Label=\"ExtensionTargets\">\r\n  </ImportGroup>\r\n</Project>"
  },
  {
    "path": "arch/msvc/Editor.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <Filter Include=\"Source Files\">\n      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\n      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\n    </Filter>\n    <Filter Include=\"Header Files\">\n      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\n      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>\n    </Filter>\n    <Filter Include=\"Resource Files\">\n      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\n      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\n    </Filter>\n    <Filter Include=\"Header Files\\Core\">\n      <UniqueIdentifier>{0ac55d42-d96d-443f-b3a7-242380bcdce6}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Header Files\\MSVC\">\n      <UniqueIdentifier>{6131c0a7-9bce-4b74-aeb9-f3f30e620e77}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Header Files\\Core\\audio\">\n      <UniqueIdentifier>{d60cb56f-4c01-4e0e-8e69-73c5f5ca0dd6}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Header Files\\Core\\io\">\n      <UniqueIdentifier>{838adbd6-7cb7-4c4a-b278-40c07c44fed0}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\src\\editor\\world.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\window.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\undo.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\sfx_edit.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\select.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\robot.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\robo_ed.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\robo_debug.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\param.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\pal_ed.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\macro.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\graphics.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\fill.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\edit_di.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\edit.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\debug.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\configure.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\char_ed.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\board.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\block.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\clipboard_win32.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\buffer.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\edit_menu.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\stringsearch.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\ansi.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\editor\\edit_export.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\..\\src\\editor\\block.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\board.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\char_ed.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\clipboard.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\configure.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\debug.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\edit.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\edit_di.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\fill.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\graphics.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\macro.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\pal_ed.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\param.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\robo_debug.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\robo_ed.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\robot.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\select.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\sfx_edit.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\undo.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\window.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\world.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"config.h\">\n      <Filter>Header Files\\MSVC</Filter>\n    </ClInclude>\n    <ClInclude Include=\"dirent.h\">\n      <Filter>Header Files\\MSVC</Filter>\n    </ClInclude>\n    <ClInclude Include=\"msvc.h\">\n      <Filter>Header Files\\MSVC</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\block.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\const.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\counter.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\data.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\error.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\event.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\game.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\graphics.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\helpsys.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\idarray.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\idput.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\intake.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\mzm.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\platform.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\scrdisp.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\util.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\window.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\world.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\buffer.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\edit_menu.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\audio.h\">\n      <Filter>Header Files\\Core\\audio</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\io\\zip.h\">\n      <Filter>Header Files\\Core\\io</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\audio\\sfx.h\">\n      <Filter>Header Files\\Core\\audio</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\SDLmzx.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\ansi.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\editor\\edit_export.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\core_task.h\">\n      <Filter>Header Files\\Core</Filter>\n    </ClInclude>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "arch/msvc/MZXRun.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <ItemGroup Label=\"ProjectConfigurations\">\r\n    <ProjectConfiguration Include=\"Debug|Win32\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|Win32\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Debug|x64\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|x64\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n  </ItemGroup>\r\n  <PropertyGroup Label=\"Globals\">\r\n    <VCProjectVersion>15.0</VCProjectVersion>\r\n    <ProjectGuid>{D7A26D85-6ADE-4B05-9909-5B8DF338E578}</ProjectGuid>\r\n    <RootNamespace>MZXRun</RootNamespace>\r\n    <WindowsTargetPlatformVersion>7.0</WindowsTargetPlatformVersion>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\r\n  <ImportGroup Label=\"ExtensionSettings\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"Shared\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <PropertyGroup Label=\"UserMacros\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n    <IncludePath>$(SolutionDir)Deps\\include\\SDL2;$(SolutionDir);$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n    <IncludePath>$(SolutionDir)Deps\\include\\SDL2;$(SolutionDir);$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n    <IncludePath>$(SolutionDir)Deps\\include\\SDL2;$(SolutionDir);$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n    <IncludePath>$(SolutionDir)Deps\\include\\SDL2;$(SolutionDir);$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n  </PropertyGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>Disabled</Optimization>\r\n      <SDLCheck>false</SDLCheck>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;__WIN32__;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n      <AdditionalOptions>/Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n      <AdditionalDependencies>SDL2main.lib;SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>Disabled</Optimization>\r\n      <SDLCheck>false</SDLCheck>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;__WIN32__;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n      <AdditionalOptions>/Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n      <AdditionalDependencies>SDL2main.lib;SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>MaxSpeed</Optimization>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <SDLCheck>false</SDLCheck>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;__WIN32__;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n      <AdditionalOptions>/Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n    </ClCompile>\r\n    <Link>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <SubSystem>Windows</SubSystem>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n      <AdditionalDependencies>SDL2main.lib;SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>MaxSpeed</Optimization>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <SDLCheck>false</SDLCheck>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;__WIN32__;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n      <AdditionalOptions>/Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n    </ClCompile>\r\n    <Link>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <SubSystem>Windows</SubSystem>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n      <AdditionalDependencies>SDL2main.lib;SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemGroup>\r\n    <ClCompile Include=\"..\\..\\src\\main.c\" />\r\n    <ClCompile Include=\"..\\..\\src\\run_stubs.c\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClInclude Include=\"..\\..\\src\\run_stubs.h\" />\r\n    <ClInclude Include=\"..\\..\\src\\SDLmzx.h\" />\r\n    <ClInclude Include=\"config.h\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ProjectReference Include=\"Core.vcxproj\">\r\n      <Project>{48961a1d-dc1c-4714-93da-c1e185e4daae}</Project>\r\n    </ProjectReference>\r\n  </ItemGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\r\n  <ImportGroup Label=\"ExtensionTargets\">\r\n  </ImportGroup>\r\n</Project>"
  },
  {
    "path": "arch/msvc/MZXRun.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <Filter Include=\"Source Files\">\n      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\n      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\n    </Filter>\n    <Filter Include=\"Header Files\">\n      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\n      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>\n    </Filter>\n    <Filter Include=\"Resource Files\">\n      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\n      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\n    </Filter>\n    <Filter Include=\"Header Files\\MSVC\">\n      <UniqueIdentifier>{477252fd-08d8-49d2-914b-ee6b2bc38e41}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\src\\main.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\..\\src\\run_stubs.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\..\\src\\run_stubs.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    <ClInclude Include=\"config.h\">\n      <Filter>Header Files\\MSVC</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\SDLmzx.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "arch/msvc/MegaZeux.sln",
    "content": "﻿\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio 15\r\nVisualStudioVersion = 15.0.27130.2010\r\nMinimumVisualStudioVersion = 10.0.40219.1\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"MegaZeux\", \"MegaZeux.vcxproj\", \"{0280F856-7B8D-4574-B842-3F4B9D6A541D}\"\r\n\tProjectSection(ProjectDependencies) = postProject\r\n\t\t{48961A1D-DC1C-4714-93DA-C1E185E4DAAE} = {48961A1D-DC1C-4714-93DA-C1E185E4DAAE}\r\n\t\t{F706F6E6-15C4-4748-B478-59105CD1189F} = {F706F6E6-15C4-4748-B478-59105CD1189F}\r\n\tEndProjectSection\r\nEndProject\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"MZXRun\", \"MZXRun.vcxproj\", \"{D7A26D85-6ADE-4B05-9909-5B8DF338E578}\"\r\n\tProjectSection(ProjectDependencies) = postProject\r\n\t\t{48961A1D-DC1C-4714-93DA-C1E185E4DAAE} = {48961A1D-DC1C-4714-93DA-C1E185E4DAAE}\r\n\tEndProjectSection\r\nEndProject\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"Core\", \"Core.vcxproj\", \"{48961A1D-DC1C-4714-93DA-C1E185E4DAAE}\"\r\nEndProject\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"Editor\", \"Editor.vcxproj\", \"{F706F6E6-15C4-4748-B478-59105CD1189F}\"\r\n\tProjectSection(ProjectDependencies) = postProject\r\n\t\t{48961A1D-DC1C-4714-93DA-C1E185E4DAAE} = {48961A1D-DC1C-4714-93DA-C1E185E4DAAE}\r\n\tEndProjectSection\r\nEndProject\r\nGlobal\r\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n\t\tDebug|x64 = Debug|x64\r\n\t\tDebug|x86 = Debug|x86\r\n\t\tRelease|x64 = Release|x64\r\n\t\tRelease|x86 = Release|x86\r\n\tEndGlobalSection\r\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n\t\t{0280F856-7B8D-4574-B842-3F4B9D6A541D}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{0280F856-7B8D-4574-B842-3F4B9D6A541D}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{0280F856-7B8D-4574-B842-3F4B9D6A541D}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{0280F856-7B8D-4574-B842-3F4B9D6A541D}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{0280F856-7B8D-4574-B842-3F4B9D6A541D}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{0280F856-7B8D-4574-B842-3F4B9D6A541D}.Release|x64.Build.0 = Release|x64\r\n\t\t{0280F856-7B8D-4574-B842-3F4B9D6A541D}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{0280F856-7B8D-4574-B842-3F4B9D6A541D}.Release|x86.Build.0 = Release|Win32\r\n\t\t{D7A26D85-6ADE-4B05-9909-5B8DF338E578}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{D7A26D85-6ADE-4B05-9909-5B8DF338E578}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{D7A26D85-6ADE-4B05-9909-5B8DF338E578}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{D7A26D85-6ADE-4B05-9909-5B8DF338E578}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{D7A26D85-6ADE-4B05-9909-5B8DF338E578}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{D7A26D85-6ADE-4B05-9909-5B8DF338E578}.Release|x64.Build.0 = Release|x64\r\n\t\t{D7A26D85-6ADE-4B05-9909-5B8DF338E578}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{D7A26D85-6ADE-4B05-9909-5B8DF338E578}.Release|x86.Build.0 = Release|Win32\r\n\t\t{48961A1D-DC1C-4714-93DA-C1E185E4DAAE}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{48961A1D-DC1C-4714-93DA-C1E185E4DAAE}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{48961A1D-DC1C-4714-93DA-C1E185E4DAAE}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{48961A1D-DC1C-4714-93DA-C1E185E4DAAE}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{48961A1D-DC1C-4714-93DA-C1E185E4DAAE}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{48961A1D-DC1C-4714-93DA-C1E185E4DAAE}.Release|x64.Build.0 = Release|x64\r\n\t\t{48961A1D-DC1C-4714-93DA-C1E185E4DAAE}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{48961A1D-DC1C-4714-93DA-C1E185E4DAAE}.Release|x86.Build.0 = Release|Win32\r\n\t\t{F706F6E6-15C4-4748-B478-59105CD1189F}.Debug|x64.ActiveCfg = Debug|x64\r\n\t\t{F706F6E6-15C4-4748-B478-59105CD1189F}.Debug|x64.Build.0 = Debug|x64\r\n\t\t{F706F6E6-15C4-4748-B478-59105CD1189F}.Debug|x86.ActiveCfg = Debug|Win32\r\n\t\t{F706F6E6-15C4-4748-B478-59105CD1189F}.Debug|x86.Build.0 = Debug|Win32\r\n\t\t{F706F6E6-15C4-4748-B478-59105CD1189F}.Release|x64.ActiveCfg = Release|x64\r\n\t\t{F706F6E6-15C4-4748-B478-59105CD1189F}.Release|x64.Build.0 = Release|x64\r\n\t\t{F706F6E6-15C4-4748-B478-59105CD1189F}.Release|x86.ActiveCfg = Release|Win32\r\n\t\t{F706F6E6-15C4-4748-B478-59105CD1189F}.Release|x86.Build.0 = Release|Win32\r\n\tEndGlobalSection\r\n\tGlobalSection(SolutionProperties) = preSolution\r\n\t\tHideSolutionNode = FALSE\r\n\tEndGlobalSection\r\n\tGlobalSection(ExtensibilityGlobals) = postSolution\r\n\t\tSolutionGuid = {E22587B2-1688-41F0-B110-5C2265259BF5}\r\n\tEndGlobalSection\r\nEndGlobal\r\n"
  },
  {
    "path": "arch/msvc/MegaZeux.vcxproj",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <ItemGroup Label=\"ProjectConfigurations\">\r\n    <ProjectConfiguration Include=\"Debug|Win32\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|Win32\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Debug|x64\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|x64\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n  </ItemGroup>\r\n  <PropertyGroup Label=\"Globals\">\r\n    <VCProjectVersion>15.0</VCProjectVersion>\r\n    <ProjectGuid>{0280F856-7B8D-4574-B842-3F4B9D6A541D}</ProjectGuid>\r\n    <RootNamespace>MegaZeux</RootNamespace>\r\n    <WindowsTargetPlatformVersion>7.0</WindowsTargetPlatformVersion>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <PlatformToolset>v141_xp</PlatformToolset>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n    <CharacterSet>MultiByte</CharacterSet>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\r\n  <ImportGroup Label=\"ExtensionSettings\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"Shared\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <PropertyGroup Label=\"UserMacros\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n    <IncludePath>$(SolutionDir)Deps\\include\\SDL2;$(SolutionDir);$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n    <IncludePath>$(SolutionDir)Deps\\include\\SDL2;$(SolutionDir);$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n    <IncludePath>$(SolutionDir)Deps\\include\\SDL2;$(SolutionDir);$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <IntDir>$(SolutionDir)\\obj\\$(ProjectName)\\$(Configuration)\\$(PlatformTarget)\\</IntDir>\r\n    <OutDir>$(SolutionDir)\\..\\..\\</OutDir>\r\n    <IncludePath>$(SolutionDir)Deps\\include\\SDL2;$(SolutionDir);$(IncludePath)</IncludePath>\r\n    <LibraryPath>$(SolutionDir)Deps\\lib\\$(PlatformTarget);$(LibraryPath)</LibraryPath>\r\n  </PropertyGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>Disabled</Optimization>\r\n      <SDLCheck>false</SDLCheck>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;__WIN32__;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n      <AdditionalOptions>/Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n      <AdditionalDependencies>SDL2main.lib;SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>Disabled</Optimization>\r\n      <SDLCheck>false</SDLCheck>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;__WIN32__;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n      <AdditionalOptions>/Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n      <AdditionalDependencies>SDL2main.lib;SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>MaxSpeed</Optimization>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <SDLCheck>false</SDLCheck>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;__WIN32__;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n      <AdditionalOptions>/Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n    </ClCompile>\r\n    <Link>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <SubSystem>Windows</SubSystem>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n      <AdditionalDependencies>SDL2main.lib;SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n    <PostBuildEvent>\r\n      <Command>\r\n      </Command>\r\n    </PostBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <ClCompile>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <Optimization>MaxSpeed</Optimization>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <SDLCheck>false</SDLCheck>\r\n      <ConformanceMode>true</ConformanceMode>\r\n      <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;__WIN32__;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <LanguageStandard>stdcpp14</LanguageStandard>\r\n      <AdditionalOptions>/Zc:__cplusplus %(AdditionalOptions)</AdditionalOptions>\r\n    </ClCompile>\r\n    <Link>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <SubSystem>Windows</SubSystem>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n      <AdditionalDependencies>SDL2main.lib;SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n    <PostBuildEvent>\r\n      <Command>\r\n      </Command>\r\n    </PostBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemGroup>\r\n    <ClCompile Include=\"..\\..\\src\\main.c\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClInclude Include=\"..\\..\\src\\SDLmzx.h\" />\r\n    <ClInclude Include=\"config.h\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ProjectReference Include=\"Core.vcxproj\">\r\n      <Project>{48961a1d-dc1c-4714-93da-c1e185e4daae}</Project>\r\n    </ProjectReference>\r\n    <ProjectReference Include=\"Editor.vcxproj\">\r\n      <Project>{f706f6e6-15c4-4748-b478-59105cd1189f}</Project>\r\n    </ProjectReference>\r\n  </ItemGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\r\n  <ImportGroup Label=\"ExtensionTargets\">\r\n  </ImportGroup>\r\n</Project>"
  },
  {
    "path": "arch/msvc/MegaZeux.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <Filter Include=\"Source Files\">\n      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\n      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\n    </Filter>\n    <Filter Include=\"Header Files\">\n      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\n      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>\n    </Filter>\n    <Filter Include=\"Resource Files\">\n      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\n      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>\n    </Filter>\n    <Filter Include=\"Header Files\\MSVC\">\n      <UniqueIdentifier>{630b59fe-ad6e-40b4-841c-3e909678a726}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\..\\src\\main.c\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"config.h\">\n      <Filter>Header Files\\MSVC</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\..\\src\\SDLmzx.h\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "arch/msvc/README.txt",
    "content": "BUILDING MEGAZEUX WITH MICROSOFT VISUAL STUDIO\r\n\r\nThis document covers the rationale and caveats regarding MegaZeux\r\ncompilation with Microsoft's Visual C++ 2017 compiler (henceforth MSVC).\r\n\r\nRATIONALE\r\n\r\nThe primary advantage of compiler portability is the idea that program\r\ncorrectness may be enriched, by leveraging the different advantages of\r\ndifferent compilers. This has certainly been true of the ports to 64bit\r\nplatforms, and will hopefully be true of compiler diversity.\r\n\r\nPorting to MSVC increases MegaZeux's compiler portability.\r\n\r\nVISUAL STUDIO LIMITATIONS AND WORKAROUNDS\r\n\r\nMicrosoft's Visual C++ compiler has improved significantly in recent years,\r\nundoubtedly due to its rivalry with the free GCC compiler. Compatibility with\r\nUNIX and POSIX APIs has matured to the point where much software depending on\r\nthese APIs can be compiled without issue. MegaZeux is one such application.\r\n\r\nHowever, MSVC has the following constraints:\r\n\r\n  - No UNIX \"dirent\" API support\r\n      Fixed using Toni Ronkko's dirent.h implementation for MSVC\r\n      This header is BSD licensed and compatible with MegaZeux's GPL license\r\n      See \"dirent.h\" in this directory\r\n\r\n  - No unistd.h\r\n      MSVC internally defines most of this anyway, so we simply do not\r\n      #include it in MSVC builds.\r\n\r\n\r\nOPENING THE PROJECT\r\n\r\nThe pre-built project files can be found in this directory as \"MegaZeux.sln\".\r\nThe solution may not always work, as it is not tested often. At the time of\r\nwriting, Visual Studio 2017 was the minimum working version required to open\r\nit. It may work using Visual Studio 2013 and/or 2015, but those versions have\r\nnot been tested. You must also build MZX's dependencies into a \"Deps\" folder\r\nalongside the solution, or use the prebuilt Deps found in the releases here:\r\n\r\nhttps://github.com/AliceLR/megazeux-dependencies\r\n\r\nThere may be instability with the MSVC binary that is not present in the GCC\r\nbuilds. If you find such instability, please report it (fixes are also\r\nwelcome).\r\n\r\n--ajs (20130711) / spectere (20171123)\r\n"
  },
  {
    "path": "arch/msvc/config.h",
    "content": "#include \"version.h\"\n\n#define PLATFORM \"msvc\"\n#define CONFDIR \"./\"\n#define CONFFILE \"config.txt\"\n#define LICENSEDIR \"./\"\n#define SHAREDIR \"./\"\n#define CONFIG_SDL 2\n#define CONFIG_EDITOR\n#define CONFIG_HELPSYS\n#define CONFIG_RENDER_SOFT\n#define CONFIG_RENDER_GL_FIXED\n#define CONFIG_RENDER_GL_PROGRAM\n#define CONFIG_RENDER_SOFTSCALE\n#define CONFIG_XMP\n#define CONFIG_AUDIO\n#define CONFIG_AUDIO_MOD_SYSTEM\n#define CONFIG_REALITY\n#define CONFIG_VORBIS\n#define CONFIG_PNG\n#define CONFIG_ICON\n#define CONFIG_MODULAR\n#define CONFIG_CHECK_ALLOC\n#define CONFIG_VFS\n#define CONFIG_GAMECONTROLLERDB\n"
  },
  {
    "path": "arch/msvc/dirent.h",
    "content": "/*\n * Dirent interface for Microsoft Visual Studio\n *\n * Copyright (C) 1998-2019 Toni Ronkko\n * This file is part of dirent.  Dirent may be freely distributed\n * under the MIT license.  For all details and documentation, see\n * https://github.com/tronkko/dirent\n */\n#ifndef DIRENT_H\n#define DIRENT_H\n\n/* Hide warnings about unreferenced local functions */\n#if defined(__clang__)\n#   pragma clang diagnostic ignored \"-Wunused-function\"\n#elif defined(_MSC_VER)\n#   pragma warning(disable:4505)\n#elif defined(__GNUC__)\n#   pragma GCC diagnostic ignored \"-Wunused-function\"\n#endif\n\n/*\n * Include windows.h without Windows Sockets 1.1 to prevent conflicts with\n * Windows Sockets 2.0.\n */\n#ifndef WIN32_LEAN_AND_MEAN\n#   define WIN32_LEAN_AND_MEAN\n#endif\n#include <windows.h>\n\n#include <stdio.h>\n#include <stdarg.h>\n#include <wchar.h>\n#include <string.h>\n#include <stdlib.h>\n#include <malloc.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <errno.h>\n\n/* Indicates that d_type field is available in dirent structure */\n#define _DIRENT_HAVE_D_TYPE\n\n/* Indicates that d_namlen field is available in dirent structure */\n#define _DIRENT_HAVE_D_NAMLEN\n\n/* Entries missing from MSVC 6.0 */\n#if !defined(FILE_ATTRIBUTE_DEVICE)\n#   define FILE_ATTRIBUTE_DEVICE 0x40\n#endif\n\n/* File type and permission flags for stat(), general mask */\n#if !defined(S_IFMT)\n#   define S_IFMT _S_IFMT\n#endif\n\n/* Directory bit */\n#if !defined(S_IFDIR)\n#   define S_IFDIR _S_IFDIR\n#endif\n\n/* Character device bit */\n#if !defined(S_IFCHR)\n#   define S_IFCHR _S_IFCHR\n#endif\n\n/* Pipe bit */\n#if !defined(S_IFFIFO)\n#   define S_IFFIFO _S_IFFIFO\n#endif\n\n/* Regular file bit */\n#if !defined(S_IFREG)\n#   define S_IFREG _S_IFREG\n#endif\n\n/* Read permission */\n#if !defined(S_IREAD)\n#   define S_IREAD _S_IREAD\n#endif\n\n/* Write permission */\n#if !defined(S_IWRITE)\n#   define S_IWRITE _S_IWRITE\n#endif\n\n/* Execute permission */\n#if !defined(S_IEXEC)\n#   define S_IEXEC _S_IEXEC\n#endif\n\n/* Pipe */\n#if !defined(S_IFIFO)\n#   define S_IFIFO _S_IFIFO\n#endif\n\n/* Block device */\n#if !defined(S_IFBLK)\n#   define S_IFBLK 0\n#endif\n\n/* Link */\n#if !defined(S_IFLNK)\n#   define S_IFLNK 0\n#endif\n\n/* Socket */\n#if !defined(S_IFSOCK)\n#   define S_IFSOCK 0\n#endif\n\n/* Read user permission */\n#if !defined(S_IRUSR)\n#   define S_IRUSR S_IREAD\n#endif\n\n/* Write user permission */\n#if !defined(S_IWUSR)\n#   define S_IWUSR S_IWRITE\n#endif\n\n/* Execute user permission */\n#if !defined(S_IXUSR)\n#   define S_IXUSR 0\n#endif\n\n/* Read group permission */\n#if !defined(S_IRGRP)\n#   define S_IRGRP 0\n#endif\n\n/* Write group permission */\n#if !defined(S_IWGRP)\n#   define S_IWGRP 0\n#endif\n\n/* Execute group permission */\n#if !defined(S_IXGRP)\n#   define S_IXGRP 0\n#endif\n\n/* Read others permission */\n#if !defined(S_IROTH)\n#   define S_IROTH 0\n#endif\n\n/* Write others permission */\n#if !defined(S_IWOTH)\n#   define S_IWOTH 0\n#endif\n\n/* Execute others permission */\n#if !defined(S_IXOTH)\n#   define S_IXOTH 0\n#endif\n\n/* Maximum length of file name */\n#if !defined(PATH_MAX)\n#   define PATH_MAX MAX_PATH\n#endif\n#if !defined(FILENAME_MAX)\n#   define FILENAME_MAX MAX_PATH\n#endif\n#if !defined(NAME_MAX)\n#   define NAME_MAX FILENAME_MAX\n#endif\n\n/* File type flags for d_type */\n#define DT_UNKNOWN 0\n#define DT_REG S_IFREG\n#define DT_DIR S_IFDIR\n#define DT_FIFO S_IFIFO\n#define DT_SOCK S_IFSOCK\n#define DT_CHR S_IFCHR\n#define DT_BLK S_IFBLK\n#define DT_LNK S_IFLNK\n\n/* Macros for converting between st_mode and d_type */\n#define IFTODT(mode) ((mode) & S_IFMT)\n#define DTTOIF(type) (type)\n\n/*\n * File type macros.  Note that block devices, sockets and links cannot be\n * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are\n * only defined for compatibility.  These macros should always return false\n * on Windows.\n */\n#if !defined(S_ISFIFO)\n#   define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO)\n#endif\n#if !defined(S_ISDIR)\n#   define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)\n#endif\n#if !defined(S_ISREG)\n#   define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)\n#endif\n#if !defined(S_ISLNK)\n#   define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK)\n#endif\n#if !defined(S_ISSOCK)\n#   define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK)\n#endif\n#if !defined(S_ISCHR)\n#   define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR)\n#endif\n#if !defined(S_ISBLK)\n#   define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK)\n#endif\n\n/* Return the exact length of the file name without zero terminator */\n#define _D_EXACT_NAMLEN(p) ((p)->d_namlen)\n\n/* Return the maximum size of a file name */\n#define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1)\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n\n/* Wide-character version */\nstruct _wdirent {\n    /* Always zero */\n    long d_ino;\n\n    /* File position within stream */\n    long d_off;\n\n    /* Structure size */\n    unsigned short d_reclen;\n\n    /* Length of name without \\0 */\n    size_t d_namlen;\n\n    /* File type */\n    int d_type;\n\n    /* File name */\n    wchar_t d_name[PATH_MAX+1];\n};\ntypedef struct _wdirent _wdirent;\n\nstruct _WDIR {\n    /* Current directory entry */\n    struct _wdirent ent;\n\n    /* Private file data */\n    WIN32_FIND_DATAW data;\n\n    /* True if data is valid */\n    int cached;\n\n    /* Win32 search handle */\n    HANDLE handle;\n\n    /* Initial directory name */\n    wchar_t *patt;\n};\ntypedef struct _WDIR _WDIR;\n\n/* Multi-byte character version */\nstruct dirent {\n    /* Always zero */\n    long d_ino;\n\n    /* File position within stream */\n    long d_off;\n\n    /* Structure size */\n    unsigned short d_reclen;\n\n    /* Length of name without \\0 */\n    size_t d_namlen;\n\n    /* File type */\n    int d_type;\n\n    /* File name */\n    char d_name[PATH_MAX+1];\n};\ntypedef struct dirent dirent;\n\nstruct DIR {\n    struct dirent ent;\n    struct _WDIR *wdirp;\n};\ntypedef struct DIR DIR;\n\n\n/* Dirent functions */\nstatic DIR *opendir (const char *dirname);\nstatic _WDIR *_wopendir (const wchar_t *dirname);\n\nstatic struct dirent *readdir (DIR *dirp);\nstatic struct _wdirent *_wreaddir (_WDIR *dirp);\n\nstatic int readdir_r(\n    DIR *dirp, struct dirent *entry, struct dirent **result);\nstatic int _wreaddir_r(\n    _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result);\n\nstatic int closedir (DIR *dirp);\nstatic int _wclosedir (_WDIR *dirp);\n\nstatic void rewinddir (DIR* dirp);\nstatic void _wrewinddir (_WDIR* dirp);\n\nstatic int scandir (const char *dirname, struct dirent ***namelist,\n    int (*filter)(const struct dirent*),\n    int (*compare)(const struct dirent**, const struct dirent**));\n\nstatic int alphasort (const struct dirent **a, const struct dirent **b);\n\nstatic int versionsort (const struct dirent **a, const struct dirent **b);\n\n\n/* For compatibility with Symbian */\n#define wdirent _wdirent\n#define WDIR _WDIR\n#define wopendir _wopendir\n#define wreaddir _wreaddir\n#define wclosedir _wclosedir\n#define wrewinddir _wrewinddir\n\n\n/* Internal utility functions */\nstatic WIN32_FIND_DATAW *dirent_first (_WDIR *dirp);\nstatic WIN32_FIND_DATAW *dirent_next (_WDIR *dirp);\n\nstatic int dirent_mbstowcs_s(\n    size_t *pReturnValue,\n    wchar_t *wcstr,\n    size_t sizeInWords,\n    const char *mbstr,\n    size_t count);\n\nstatic int dirent_wcstombs_s(\n    size_t *pReturnValue,\n    char *mbstr,\n    size_t sizeInBytes,\n    const wchar_t *wcstr,\n    size_t count);\n\nstatic void dirent_set_errno (int error);\n\n\n/*\n * Open directory stream DIRNAME for read and return a pointer to the\n * internal working area that is used to retrieve individual directory\n * entries.\n */\nstatic _WDIR*\n_wopendir(\n    const wchar_t *dirname)\n{\n    _WDIR *dirp;\n    DWORD n;\n    wchar_t *p;\n\n    /* Must have directory name */\n    if (dirname == NULL  ||  dirname[0] == '\\0') {\n        dirent_set_errno (ENOENT);\n        return NULL;\n    }\n\n    /* Allocate new _WDIR structure */\n    dirp = (_WDIR*) malloc (sizeof (struct _WDIR));\n    if (!dirp) {\n        return NULL;\n    }\n\n    /* Reset _WDIR structure */\n    dirp->handle = INVALID_HANDLE_VALUE;\n    dirp->patt = NULL;\n    dirp->cached = 0;\n\n    /*\n     * Compute the length of full path plus zero terminator\n     *\n     * Note that on WinRT there's no way to convert relative paths\n     * into absolute paths, so just assume it is an absolute path.\n     */\n#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)\n    /* Desktop */\n    n = GetFullPathNameW (dirname, 0, NULL, NULL);\n#else\n    /* WinRT */\n    n = wcslen (dirname);\n#endif\n\n    /* Allocate room for absolute directory name and search pattern */\n    dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16);\n    if (dirp->patt == NULL) {\n        goto exit_closedir;\n    }\n\n    /*\n     * Convert relative directory name to an absolute one.  This\n     * allows rewinddir() to function correctly even when current\n     * working directory is changed between opendir() and rewinddir().\n     *\n     * Note that on WinRT there's no way to convert relative paths\n     * into absolute paths, so just assume it is an absolute path.\n     */\n#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)\n    /* Desktop */\n    n = GetFullPathNameW (dirname, n, dirp->patt, NULL);\n    if (n <= 0) {\n        goto exit_closedir;\n    }\n#else\n    /* WinRT */\n    wcsncpy_s (dirp->patt, n+1, dirname, n);\n#endif\n\n    /* Append search pattern \\* to the directory name */\n    p = dirp->patt + n;\n    switch (p[-1]) {\n    case '\\\\':\n    case '/':\n    case ':':\n        /* Directory ends in path separator, e.g. c:\\temp\\ */\n        /*NOP*/;\n        break;\n\n    default:\n        /* Directory name doesn't end in path separator */\n        *p++ = '\\\\';\n    }\n    *p++ = '*';\n    *p = '\\0';\n\n    /* Open directory stream and retrieve the first entry */\n    if (!dirent_first (dirp)) {\n        goto exit_closedir;\n    }\n\n    /* Success */\n    return dirp;\n\n    /* Failure */\nexit_closedir:\n    _wclosedir (dirp);\n    return NULL;\n}\n\n/*\n * Read next directory entry.\n *\n * Returns pointer to static directory entry which may be overwritten by\n * subsequent calls to _wreaddir().\n */\nstatic struct _wdirent*\n_wreaddir(\n    _WDIR *dirp)\n{\n    struct _wdirent *entry;\n\n    /*\n     * Read directory entry to buffer.  We can safely ignore the return value\n     * as entry will be set to NULL in case of error.\n     */\n    (void) _wreaddir_r (dirp, &dirp->ent, &entry);\n\n    /* Return pointer to statically allocated directory entry */\n    return entry;\n}\n\n/*\n * Read next directory entry.\n *\n * Returns zero on success.  If end of directory stream is reached, then sets\n * result to NULL and returns zero.\n */\nstatic int\n_wreaddir_r(\n    _WDIR *dirp,\n    struct _wdirent *entry,\n    struct _wdirent **result)\n{\n    WIN32_FIND_DATAW *datap;\n\n    /* Read next directory entry */\n    datap = dirent_next (dirp);\n    if (datap) {\n        size_t n;\n        DWORD attr;\n\n        /*\n         * Copy file name as wide-character string.  If the file name is too\n         * long to fit in to the destination buffer, then truncate file name\n         * to PATH_MAX characters and zero-terminate the buffer.\n         */\n        n = 0;\n        while (n < PATH_MAX  &&  datap->cFileName[n] != 0) {\n            entry->d_name[n] = datap->cFileName[n];\n            n++;\n        }\n        entry->d_name[n] = 0;\n\n        /* Length of file name excluding zero terminator */\n        entry->d_namlen = n;\n\n        /* File type */\n        attr = datap->dwFileAttributes;\n        if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) {\n            entry->d_type = DT_CHR;\n        } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {\n            entry->d_type = DT_DIR;\n        } else {\n            entry->d_type = DT_REG;\n        }\n\n        /* Reset dummy fields */\n        entry->d_ino = 0;\n        entry->d_off = 0;\n        entry->d_reclen = sizeof (struct _wdirent);\n\n        /* Set result address */\n        *result = entry;\n\n    } else {\n\n        /* Return NULL to indicate end of directory */\n        *result = NULL;\n\n    }\n\n    return /*OK*/0;\n}\n\n/*\n * Close directory stream opened by opendir() function.  This invalidates the\n * DIR structure as well as any directory entry read previously by\n * _wreaddir().\n */\nstatic int\n_wclosedir(\n    _WDIR *dirp)\n{\n    int ok;\n    if (dirp) {\n\n        /* Release search handle */\n        if (dirp->handle != INVALID_HANDLE_VALUE) {\n            FindClose (dirp->handle);\n        }\n\n        /* Release search pattern */\n        free (dirp->patt);\n\n        /* Release directory structure */\n        free (dirp);\n        ok = /*success*/0;\n\n    } else {\n\n        /* Invalid directory stream */\n        dirent_set_errno (EBADF);\n        ok = /*failure*/-1;\n\n    }\n    return ok;\n}\n\n/*\n * Rewind directory stream such that _wreaddir() returns the very first\n * file name again.\n */\nstatic void\n_wrewinddir(\n    _WDIR* dirp)\n{\n    if (dirp) {\n        /* Release existing search handle */\n        if (dirp->handle != INVALID_HANDLE_VALUE) {\n            FindClose (dirp->handle);\n        }\n\n        /* Open new search handle */\n        dirent_first (dirp);\n    }\n}\n\n/* Get first directory entry (internal) */\nstatic WIN32_FIND_DATAW*\ndirent_first(\n    _WDIR *dirp)\n{\n    WIN32_FIND_DATAW *datap;\n    DWORD error;\n\n    /* Open directory and retrieve the first entry */\n    dirp->handle = FindFirstFileExW(\n        dirp->patt, FindExInfoStandard, &dirp->data,\n        FindExSearchNameMatch, NULL, 0);\n    if (dirp->handle != INVALID_HANDLE_VALUE) {\n\n        /* a directory entry is now waiting in memory */\n        datap = &dirp->data;\n        dirp->cached = 1;\n\n    } else {\n\n        /* Failed to open directory: no directory entry in memory */\n        dirp->cached = 0;\n        datap = NULL;\n\n        /* Set error code */\n        error = GetLastError ();\n        switch (error) {\n        case ERROR_ACCESS_DENIED:\n            /* No read access to directory */\n            dirent_set_errno (EACCES);\n            break;\n\n        case ERROR_DIRECTORY:\n            /* Directory name is invalid */\n            dirent_set_errno (ENOTDIR);\n            break;\n\n        case ERROR_PATH_NOT_FOUND:\n        default:\n            /* Cannot find the file */\n            dirent_set_errno (ENOENT);\n        }\n\n    }\n    return datap;\n}\n\n/*\n * Get next directory entry (internal).\n *\n * Returns\n */\nstatic WIN32_FIND_DATAW*\ndirent_next(\n    _WDIR *dirp)\n{\n    WIN32_FIND_DATAW *p;\n\n    /* Get next directory entry */\n    if (dirp->cached != 0) {\n\n        /* A valid directory entry already in memory */\n        p = &dirp->data;\n        dirp->cached = 0;\n\n    } else if (dirp->handle != INVALID_HANDLE_VALUE) {\n\n        /* Get the next directory entry from stream */\n        if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) {\n            /* Got a file */\n            p = &dirp->data;\n        } else {\n            /* The very last entry has been processed or an error occurred */\n            FindClose (dirp->handle);\n            dirp->handle = INVALID_HANDLE_VALUE;\n            p = NULL;\n        }\n\n    } else {\n\n        /* End of directory stream reached */\n        p = NULL;\n\n    }\n\n    return p;\n}\n\n/*\n * Open directory stream using plain old C-string.\n */\nstatic DIR*\nopendir(\n    const char *dirname)\n{\n    struct DIR *dirp;\n\n    /* Must have directory name */\n    if (dirname == NULL  ||  dirname[0] == '\\0') {\n        dirent_set_errno (ENOENT);\n        return NULL;\n    }\n\n    /* Allocate memory for DIR structure */\n    dirp = (DIR*) malloc (sizeof (struct DIR));\n    if (!dirp) {\n        return NULL;\n    }\n    {\n        int error;\n        wchar_t wname[PATH_MAX + 1];\n        size_t n;\n\n        /* Convert directory name to wide-character string */\n        error = dirent_mbstowcs_s(\n            &n, wname, PATH_MAX + 1, dirname, PATH_MAX + 1);\n        if (error) {\n            /*\n             * Cannot convert file name to wide-character string.  This\n             * occurs if the string contains invalid multi-byte sequences or\n             * the output buffer is too small to contain the resulting\n             * string.\n             */\n            goto exit_free;\n        }\n\n\n        /* Open directory stream using wide-character name */\n        dirp->wdirp = _wopendir (wname);\n        if (!dirp->wdirp) {\n            goto exit_free;\n        }\n\n    }\n\n    /* Success */\n    return dirp;\n\n    /* Failure */\nexit_free:\n    free (dirp);\n    return NULL;\n}\n\n/*\n * Read next directory entry.\n */\nstatic struct dirent*\nreaddir(\n    DIR *dirp)\n{\n    struct dirent *entry;\n\n    /*\n     * Read directory entry to buffer.  We can safely ignore the return value\n     * as entry will be set to NULL in case of error.\n     */\n    (void) readdir_r (dirp, &dirp->ent, &entry);\n\n    /* Return pointer to statically allocated directory entry */\n    return entry;\n}\n\n/*\n * Read next directory entry into called-allocated buffer.\n *\n * Returns zero on success.  If the end of directory stream is reached, then\n * sets result to NULL and returns zero.\n */\nstatic int\nreaddir_r(\n    DIR *dirp,\n    struct dirent *entry,\n    struct dirent **result)\n{\n    WIN32_FIND_DATAW *datap;\n\n    /* Read next directory entry */\n    datap = dirent_next (dirp->wdirp);\n    if (datap) {\n        size_t n;\n        int error;\n\n        /* Attempt to convert file name to multi-byte string */\n        error = dirent_wcstombs_s(\n            &n, entry->d_name, PATH_MAX + 1, datap->cFileName, PATH_MAX + 1);\n\n        /*\n         * If the file name cannot be represented by a multi-byte string,\n         * then attempt to use old 8+3 file name.  This allows traditional\n         * Unix-code to access some file names despite of unicode\n         * characters, although file names may seem unfamiliar to the user.\n         *\n         * Be ware that the code below cannot come up with a short file\n         * name unless the file system provides one.  At least\n         * VirtualBox shared folders fail to do this.\n         */\n        if (error  &&  datap->cAlternateFileName[0] != '\\0') {\n            error = dirent_wcstombs_s(\n                &n, entry->d_name, PATH_MAX + 1,\n                datap->cAlternateFileName, PATH_MAX + 1);\n        }\n\n        if (!error) {\n            DWORD attr;\n\n            /* Length of file name excluding zero terminator */\n            entry->d_namlen = n - 1;\n\n            /* File attributes */\n            attr = datap->dwFileAttributes;\n            if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) {\n                entry->d_type = DT_CHR;\n            } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {\n                entry->d_type = DT_DIR;\n            } else {\n                entry->d_type = DT_REG;\n            }\n\n            /* Reset dummy fields */\n            entry->d_ino = 0;\n            entry->d_off = 0;\n            entry->d_reclen = sizeof (struct dirent);\n\n        } else {\n\n            /*\n             * Cannot convert file name to multi-byte string so construct\n             * an erroneous directory entry and return that.  Note that\n             * we cannot return NULL as that would stop the processing\n             * of directory entries completely.\n             */\n            entry->d_name[0] = '?';\n            entry->d_name[1] = '\\0';\n            entry->d_namlen = 1;\n            entry->d_type = DT_UNKNOWN;\n            entry->d_ino = 0;\n            entry->d_off = -1;\n            entry->d_reclen = 0;\n\n        }\n\n        /* Return pointer to directory entry */\n        *result = entry;\n\n    } else {\n\n        /* No more directory entries */\n        *result = NULL;\n\n    }\n\n    return /*OK*/0;\n}\n\n/*\n * Close directory stream.\n */\nstatic int\nclosedir(\n    DIR *dirp)\n{\n    int ok;\n    if (dirp) {\n\n        /* Close wide-character directory stream */\n        ok = _wclosedir (dirp->wdirp);\n        dirp->wdirp = NULL;\n\n        /* Release multi-byte character version */\n        free (dirp);\n\n    } else {\n\n        /* Invalid directory stream */\n        dirent_set_errno (EBADF);\n        ok = /*failure*/-1;\n\n    }\n    return ok;\n}\n\n/*\n * Rewind directory stream to beginning.\n */\nstatic void\nrewinddir(\n    DIR* dirp)\n{\n    /* Rewind wide-character string directory stream */\n    _wrewinddir (dirp->wdirp);\n}\n\n/*\n * Scan directory for entries.\n */\nstatic int\nscandir(\n    const char *dirname,\n    struct dirent ***namelist,\n    int (*filter)(const struct dirent*),\n    int (*compare)(const struct dirent**, const struct dirent**))\n{\n    struct dirent **files = NULL;\n    size_t size = 0;\n    size_t allocated = 0;\n    const size_t init_size = 1;\n    DIR *dir = NULL;\n    struct dirent *entry;\n    struct dirent *tmp = NULL;\n    size_t i;\n    int result = 0;\n\n    /* Open directory stream */\n    dir = opendir (dirname);\n    if (dir) {\n\n        /* Read directory entries to memory */\n        while (1) {\n\n            /* Enlarge pointer table to make room for another pointer */\n            if (size >= allocated) {\n                void *p;\n                size_t num_entries;\n\n                /* Compute number of entries in the enlarged pointer table */\n                if (size < init_size) {\n                    /* Allocate initial pointer table */\n                    num_entries = init_size;\n                } else {\n                    /* Double the size */\n                    num_entries = size * 2;\n                }\n\n                /* Allocate first pointer table or enlarge existing table */\n                p = realloc (files, sizeof (void*) * num_entries);\n                if (p != NULL) {\n                    /* Got the memory */\n                    files = (dirent**) p;\n                    allocated = num_entries;\n                } else {\n                    /* Out of memory */\n                    result = -1;\n                    break;\n                }\n\n            }\n\n            /* Allocate room for temporary directory entry */\n            if (tmp == NULL) {\n                tmp = (struct dirent*) malloc (sizeof (struct dirent));\n                if (tmp == NULL) {\n                    /* Cannot allocate temporary directory entry */\n                    result = -1;\n                    break;\n                }\n            }\n\n            /* Read directory entry to temporary area */\n            if (readdir_r (dir, tmp, &entry) == /*OK*/0) {\n\n                /* Did we get an entry? */\n                if (entry != NULL) {\n                    int pass;\n\n                    /* Determine whether to include the entry in result */\n                    if (filter) {\n                        /* Let the filter function decide */\n                        pass = filter (tmp);\n                    } else {\n                        /* No filter function, include everything */\n                        pass = 1;\n                    }\n\n                    if (pass) {\n                        /* Store the temporary entry to pointer table */\n                        files[size++] = tmp;\n                        tmp = NULL;\n\n                        /* Keep up with the number of files */\n                        result++;\n                    }\n\n                } else {\n\n                    /*\n                     * End of directory stream reached => sort entries and\n                     * exit.\n                     */\n                    qsort (files, size, sizeof (void*),\n                        (int (*) (const void*, const void*)) compare);\n                    break;\n\n                }\n\n            } else {\n                /* Error reading directory entry */\n                result = /*Error*/ -1;\n                break;\n            }\n\n        }\n\n    } else {\n        /* Cannot open directory */\n        result = /*Error*/ -1;\n    }\n\n    /* Release temporary directory entry */\n    free (tmp);\n\n    /* Release allocated memory on error */\n    if (result < 0) {\n        for (i = 0; i < size; i++) {\n            free (files[i]);\n        }\n        free (files);\n        files = NULL;\n    }\n\n    /* Close directory stream */\n    if (dir) {\n        closedir (dir);\n    }\n\n    /* Pass pointer table to caller */\n    if (namelist) {\n        *namelist = files;\n    }\n    return result;\n}\n\n/* Alphabetical sorting */\nstatic int\nalphasort(\n    const struct dirent **a, const struct dirent **b)\n{\n    return strcoll ((*a)->d_name, (*b)->d_name);\n}\n\n/* Sort versions */\nstatic int\nversionsort(\n    const struct dirent **a, const struct dirent **b)\n{\n    /* FIXME: implement strverscmp and use that */\n    return alphasort (a, b);\n}\n\n/* Convert multi-byte string to wide character string */\nstatic int\ndirent_mbstowcs_s(\n    size_t *pReturnValue,\n    wchar_t *wcstr,\n    size_t sizeInWords,\n    const char *mbstr,\n    size_t count)\n{\n    int error;\n\n#if defined(_MSC_VER)  &&  _MSC_VER >= 1400\n\n    /* Microsoft Visual Studio 2005 or later */\n    error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count);\n\n#else\n\n    /* Older Visual Studio or non-Microsoft compiler */\n    size_t n;\n\n    /* Convert to wide-character string (or count characters) */\n    n = mbstowcs (wcstr, mbstr, sizeInWords);\n    if (!wcstr  ||  n < count) {\n\n        /* Zero-terminate output buffer */\n        if (wcstr  &&  sizeInWords) {\n            if (n >= sizeInWords) {\n                n = sizeInWords - 1;\n            }\n            wcstr[n] = 0;\n        }\n\n        /* Length of resulting multi-byte string WITH zero terminator */\n        if (pReturnValue) {\n            *pReturnValue = n + 1;\n        }\n\n        /* Success */\n        error = 0;\n\n    } else {\n\n        /* Could not convert string */\n        error = 1;\n\n    }\n\n#endif\n    return error;\n}\n\n/* Convert wide-character string to multi-byte string */\nstatic int\ndirent_wcstombs_s(\n    size_t *pReturnValue,\n    char *mbstr,\n    size_t sizeInBytes, /* max size of mbstr */\n    const wchar_t *wcstr,\n    size_t count)\n{\n    int error;\n\n#if defined(_MSC_VER)  &&  _MSC_VER >= 1400\n\n    /* Microsoft Visual Studio 2005 or later */\n    error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count);\n\n#else\n\n    /* Older Visual Studio or non-Microsoft compiler */\n    size_t n;\n\n    /* Convert to multi-byte string (or count the number of bytes needed) */\n    n = wcstombs (mbstr, wcstr, sizeInBytes);\n    if (!mbstr  ||  n < count) {\n\n        /* Zero-terminate output buffer */\n        if (mbstr  &&  sizeInBytes) {\n            if (n >= sizeInBytes) {\n                n = sizeInBytes - 1;\n            }\n            mbstr[n] = '\\0';\n        }\n\n        /* Length of resulting multi-bytes string WITH zero-terminator */\n        if (pReturnValue) {\n            *pReturnValue = n + 1;\n        }\n\n        /* Success */\n        error = 0;\n\n    } else {\n\n        /* Cannot convert string */\n        error = 1;\n\n    }\n\n#endif\n    return error;\n}\n\n/* Set errno variable */\nstatic void\ndirent_set_errno(\n    int error)\n{\n#if defined(_MSC_VER)  &&  _MSC_VER >= 1400\n\n    /* Microsoft Visual Studio 2005 and later */\n    _set_errno (error);\n\n#else\n\n    /* Non-Microsoft compiler or older Microsoft compiler */\n    errno = error;\n\n#endif\n}\n\n\n#ifdef __cplusplus\n}\n#endif\n#endif /*DIRENT_H*/\n"
  },
  {
    "path": "arch/msvc/msvc.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Ian Burgmyer <spectere@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// all MSVC specific hacks should go here, if possible\n\n#ifndef MSVC_H\n#define MSVC_H\n\n#include <io.h>\n#include <ctype.h>\n#include <direct.h>\n#include <process.h>\n\n// unistd.h mode defines (required for access calls)\n#define X_OK   0\n#define W_OK   2\n#define R_OK   4\n\n#ifndef S_ISREG\n#define S_ISREG(mode) (mode & _S_IFREG)\n#endif\n#ifndef S_ISDIR\n#define S_ISDIR(mode) (mode & _S_IFDIR)\n#endif\n#ifndef S_IRUSR\n#define S_IRUSR _S_IREAD\n#endif\n#ifndef S_IWUSR\n#define S_IWUSR _S_IWRITE\n#endif\n#ifndef S_IXUSR\n#define S_IXUSR _S_IEXEC\n#endif\n#ifndef S_IRGRP\n#define S_IRGRP (S_IRUSR >> 3)\n#endif\n#ifndef S_IWGRP\n#define S_IWGRP (S_IWUSR >> 3)\n#endif\n#ifndef S_IXGRP\n#define S_IXGRP (S_IXUSR >> 3)\n#endif\n#ifndef S_IROTH\n#define S_IROTH (S_IRGRP >> 3)\n#endif\n#ifndef S_IWOTH\n#define S_IWOTH (S_IWGRP >> 3)\n#endif\n#ifndef S_IXOTH\n#define S_IXOTH (S_IXGRP >> 3)\n#endif\n#ifndef S_IRWXU\n#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR)\n#endif\n#ifndef S_IRWXG\n#define S_IRWXG (S_IRWXU >> 3)\n#endif\n#ifndef S_IRWXO\n#define S_IRWXO (S_IRWXG >> 3)\n#endif\n\n#define access      _access\n#define chdir       _chdir\n#define execv       _execv\n#define getcwd      _getcwd\n#define rmdir       _rmdir\n\n#define unlink      _unlink\n\n#ifndef strcasecmp\n#define strcasecmp  _stricmp\n#endif\n\n#ifndef strncasecmp\n#define strncasecmp _strnicmp\n#endif\n\n#define mkdir(name,mode) _mkdir(name)\n\n#define inline __inline\n\n#ifdef _WIN64\ntypedef __int64 ssize_t;\n#else\ntypedef _W64 int ssize_t;\n#endif\n\n#endif // MSVC_H\n"
  },
  {
    "path": "arch/msvc/update_version.cmd",
    "content": "setlocal enableextensions enabledelayedexpansion\ncd %~dp0\n\ngit describe --exact-match --tags>vertemp.txt\nif not \"%ERRORLEVEL%\" == \"0\" (\n\techo #define VERSION \"git\">version.h\n) else (\n\tfor /f \"delims=\" %%a in (vertemp.txt) do set VERSION=%%a\n\techo #define VERSION \"!VERSION!\">version.h\n)\n\ndel vertemp.txt\n"
  },
  {
    "path": "arch/msvc/win32time.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019 Ian Burgmyer <spectere@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"win32time.h\"\n#include <time.h>\n\nint gettimeofday(struct timeval *tv, void *tz)\n{\n  time_t sec;\n  SYSTEMTIME st;\n\n  time(&sec);\n  GetLocalTime(&st);\n\n  tv->tv_sec = sec;\n  tv->tv_usec = st.wMilliseconds * 1000;\n\n  return 0;\n}\n\n"
  },
  {
    "path": "arch/msvc/win32time.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019 Ian Burgmyer <spectere@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __WIN32TIME_H\n#define __WIN32TIME_H\n\n#include <Windows.h>\n\nint gettimeofday(struct timeval *tv, void *tz);\n\n#endif /* __WIN32TIME_H */\n"
  },
  {
    "path": "arch/nds/CONFIG.NDS",
    "content": "#!/bin/sh\n\n./config.sh --platform nds --prefix $DEVKITARM --optimize-size --enable-lto \\\n            --disable-editor --disable-helpsys --disable-utils \\\n            --disable-libpng --enable-release --enable-meter \\\n            --enable-extram --disable-screenshots --enable-stdio-redirect \"$@\"\n"
  },
  {
    "path": "arch/nds/Makefile",
    "content": "#---------------------------------------------------------------------------------\n.SUFFIXES:\n#---------------------------------------------------------------------------------\n\ninclude $(DEVKITARM)/ds_rules\n\nexport TOPDIR\t\t:=\t$(CURDIR)\n\n\n.PHONY: ${TARGET}.elf\n\n#---------------------------------------------------------------------------------\n# main targets\n#---------------------------------------------------------------------------------\nall: ${TARGET}.elf\n\n#---------------------------------------------------------------------------------\n${TARGET}.elf:\n\t$(MAKE) -C arm7\n\t\n#---------------------------------------------------------------------------------\nclean:\n\t$(MAKE) -C arm7 clean\n\trm -f ${TARGET}.arm7\n"
  },
  {
    "path": "arch/nds/Makefile.in",
    "content": "#\n# Nintendo DS Makefile\n#\n\n.PHONY: package clean\n\nifeq ($(strip $(DEVKITPRO)),)\n$(error \"DEVKITPRO must be set in your environment.\")\nendif\n\nifeq ($(strip $(DEVKITARM)),)\n$(error \"DEVKITARM must be set in your environment.\")\nendif\n\n# ds_rules may rely on this variable that was removed...\nPORTLIBS_PATH := $(DEVKITPRO)/portlibs\ninclude ${DEVKITARM}/ds_rules\n\nEXTRA_LICENSES += ${LICENSE_CC0} ${LICENSE_MPL2} ${LICENSE_NEWLIB}\n\n#\n# NDS target rules\n#\n\n# Block --host, which will break things.\nCROSS_COMPILE =\n# Don't strip when generating debug symbols.\nNO_STRIP_IN_DEBUGLINK ?= 1\n# Disable rules that require target code to run natively.\nSUPPRESS_HOST_TARGETS ?= 1\n\n#\n# Override library paths.\n#\n\n#\n# As of devkitARM r51 $(PORTLIBS) has multiple paths...\n#\nPORTLIBS_INCLUDES := $(foreach dir, $(PORTLIBS), -isystem $(dir)/include)\nPORTLIBS_LIBS     := $(foreach dir, $(PORTLIBS), -L$(dir)/lib)\n\nEXTRA_INCLUDES := ${PORTLIBS_INCLUDES} \\\n                 -isystem $(LIBNDS)/include\n\nEXTRA_LIBS := ${PORTLIBS_LIBS} \\\n             -L$(LIBNDS)/lib -lfat -lm -lnds9 -lmm9\n\nNDS_CFLAGS    := -marm -mthumb-interwork -march=armv5te -mtune=arm946e-s\nNDS_CFLAGS    += ${EXTRA_INCLUDES} -DARM9 -D__NDS__ -Iarch/nds\n\nARCH_CFLAGS   += ${NDS_CFLAGS}\nARCH_CXXFLAGS += ${NDS_CFLAGS}\nARCH_LDFLAGS  += ${EXTRA_LIBS} -specs=ds_arm9.specs\n\n#\n# Vile hack, remove me ASAP\n#\narch/nds/%.o: arch/nds/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} -Wno-unused-macros -c $< -o $@\n\narch/nds/render.o: arch/nds/protected_palette.bin.o\n\narch/nds/%.bin.o arch/nds/%_bin.h : arch/nds/%.bin\n\t$(if ${V},,@echo \"  AS      \" $<)\n\t@bin2s -a 32 -H arch/nds/`(echo $(<F) | tr . _)`.h $< > $<.S\n\t@# Shut up the assembler since bin2s doesn't emit a trailing newline:\n\t@echo '' >> $<.S\n\t$(AS) $<.S -o arch/nds/$(<F).o\n\t@# Remove dkP's badly constructed macro (-Wundef):\n\t@sed -i 's/^#if __cplusplus >= 201103L$$/#if 0/g' arch/nds/*_bin.h\n\npackage: mzx\n\t${MAKE} -C arch/nds TARGET=${mzxrun}\n\tndstool -c ${mzxrun}.nds -7 arch/nds/${mzxrun}.arm7.elf -9 ${mzxrun} -b contrib/icons/generated/icon_nds.bmp \"MegaZeux ${VERSION}\"\n\nclean:\n\t@${MAKE} -C arch/nds TARGET=${mzxrun} clean\n\t${RM} -f ${mzxrun}.nds arch/nds/*.d arch/nds/*.o arch/nds/*.elf\n\t${RM} -f arch/nds/protected_palette.bin.S arch/nds/protected_palette_bin.h\n\t${RM} -f arch/nds/protected_palette_bin.c\n\n#\n# We're only interested in our packaged binary; remove the ELF intermediaries\n#\nbuild := ${build_root}/games/megazeux\nbuild: package ${build}\n\t${CP} arch/nds/pad.config ${build}\n\t${CP} ${mzxrun}.nds ${build}\n\t${RM} ${build}/${mzxrun} ${build}/${mzxrun}.debug\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/nds/README",
    "content": "PREPARATION\n\nYou need to build or install the devkitARM toolchain.\n\nCONFIGURING\n\nSee CONFIG.NDS for an optimal `config.sh' configure line. You need\nto ensure DEVKITPRO and DEVKITARM are both defined and valid.\n\nBUILDING\n\nFor the moment, you need to build with:\n\nmake package\n\nThis will emit a \"mzxrun.nds\" which you should know how to launch.\n\nPACKAGING THE BUILD\n\nYou can then use the usual \"make archive\" to build a\nsuitable .ZIP file for distribution.\n"
  },
  {
    "path": "arch/nds/arm7/Makefile",
    "content": "#---------------------------------------------------------------------------------\n.SUFFIXES:\n#---------------------------------------------------------------------------------\n\ninclude $(DEVKITARM)/ds_rules\n\n#---------------------------------------------------------------------------------\n# BUILD is the directory where object files & intermediate files will be placed\n# SOURCES is a list of directories containing source code\n# INCLUDES is a list of directories containing extra header files\n# DATA is a list of directories containing binary files\n# all directories are relative to this makefile\n#---------------------------------------------------------------------------------\nBUILD\t\t:=\tbuild\nSOURCES\t\t:=\tsource  \nINCLUDES\t:=\tinclude build\nDATA\t\t:=\n \n#---------------------------------------------------------------------------------\n# options for code generation\n#---------------------------------------------------------------------------------\nARCH\t:=\t-mthumb-interwork\n\nCFLAGS\t:=\t-g -Wall -O2\\\n\t\t-mcpu=arm7tdmi -mtune=arm7tdmi -fomit-frame-pointer\\\n\t\t-ffast-math \\\n\t\t$(ARCH)\n\nCFLAGS\t+=\t$(INCLUDE) -DARM7\nCXXFLAGS\t:=\t$(CFLAGS) -fno-rtti -fno-exceptions -fno-rtti\n\n\nASFLAGS\t:=\t-g $(ARCH)\nLDFLAGS\t=\t-specs=ds_arm7.specs -g $(ARCH) -Wl,-Map,$(notdir $*).map\n\nLIBS\t:=\t-lnds7 -lmm7\n\n#---------------------------------------------------------------------------------\n# list of directories containing libraries, this must be the top level containing\n# include and lib\n#---------------------------------------------------------------------------------\nLIBDIRS\t:=\t$(LIBNDS)\n  \n#---------------------------------------------------------------------------------\n# no real need to edit anything past this point unless you need to add additional\n# rules for different file extensions\n#---------------------------------------------------------------------------------\nifneq ($(BUILD),$(notdir $(CURDIR)))\n#---------------------------------------------------------------------------------\n \nexport ARM7ELF\t:=\t$(TOPDIR)/$(TARGET).arm7.elf\nexport DEPSDIR\t:=\t$(CURDIR)/$(BUILD)\n\nexport VPATH\t:=\t$(foreach dir,$(SOURCES),$(CURDIR)/$(dir))\n \nCFILES\t\t:=\t$(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))\nCPPFILES\t:=\t$(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))\nSFILES\t\t:=\t$(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))\nBINFILES\t:=\t$(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))\n \nexport OFILES\t:=\t$(addsuffix .o,$(BINFILES)) \\\n\t\t\t$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)\n \nexport INCLUDE\t:=\t$(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \\\n\t\t\t$(foreach dir,$(LIBDIRS),-I$(dir)/include) \\\n\t\t\t-I$(CURDIR)/$(BUILD)\n \nexport LIBPATHS\t:=\t$(foreach dir,$(LIBDIRS),-L$(dir)/lib)\n\n#---------------------------------------------------------------------------------\n# use CXX for linking C++ projects, CC for standard C\n#---------------------------------------------------------------------------------\nifeq ($(strip $(CPPFILES)),)\n#---------------------------------------------------------------------------------\n\texport LD\t:=\t$(CC)\n#---------------------------------------------------------------------------------\nelse\n#---------------------------------------------------------------------------------\n\texport LD\t:=\t$(CXX)\n#---------------------------------------------------------------------------------\nendif\n#---------------------------------------------------------------------------------\n\n.PHONY: $(BUILD) clean\n \n#---------------------------------------------------------------------------------\n$(BUILD):\n\t@[ -d $@ ] || mkdir -p $@\n\t@make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile\n \n#---------------------------------------------------------------------------------\nclean:\n\t@echo clean ...\n\t@rm -fr $(BUILD) *.elf\n \n \n#---------------------------------------------------------------------------------\nelse\n \nDEPENDS\t:=\t$(OFILES:.o=.d)\n \n#---------------------------------------------------------------------------------\n# main targets\n#---------------------------------------------------------------------------------\n$(ARM7ELF)\t:\t$(OFILES)\n\t@echo linking $(notdir $@)\n\t@$(LD)  $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@\n\n\n#---------------------------------------------------------------------------------\n# you need a rule like this for each extension you use as binary data \n#---------------------------------------------------------------------------------\n%.bin.o\t:\t%.bin\n#---------------------------------------------------------------------------------\n\t@echo $(notdir $<)\n\t@$(bin2o)\n\n-include $(DEPENDS)\n \n#---------------------------------------------------------------------------------------\nendif\n#---------------------------------------------------------------------------------------\n"
  },
  {
    "path": "arch/nds/arm7/source/arm7main.c",
    "content": "/*---------------------------------------------------------------------------------\n\n\tdefault ARM7 core\n\n\t\tCopyright (C) 2005 - 2010\n\t\tMichael Noland (joat)\n\t\tJason Rogers (dovoto)\n\t\tDave Murphy (WinterMute)\n\n\tThis software is provided 'as-is', without any express or implied\n\twarranty.  In no event will the authors be held liable for any\n\tdamages arising from the use of this software.\n\n\tPermission is granted to anyone to use this software for any\n\tpurpose, including commercial applications, and to alter it and\n\tredistribute it freely, subject to the following restrictions:\n\n\t1.\tThe origin of this software must not be misrepresented; you\n\t\tmust not claim that you wrote the original software. If you use\n\t\tthis software in a product, an acknowledgment in the product\n\t\tdocumentation would be appreciated but is not required.\n\n\t2.\tAltered source versions must be plainly marked as such, and\n\t\tmust not be misrepresented as being the original software.\n\n\t3.\tThis notice may not be removed or altered from any source\n\t\tdistribution.\n\n---------------------------------------------------------------------------------*/\n#include <nds.h>\n#include <maxmod7.h>\n\n#define FIFO_MZX FIFO_USER_01\n#define CMD_MZX_PCS_TONE 0x01\n#define CMD_MZX_SOUND_VOLUME 0x02\n#define CMD_MZX_MM_GET_POSITION 0x03\n#define MZX_PCS_CHANNEL 8\n\nvoid mzxFifoCommandHandler(u32 command, void *userdata) {\n\tswitch (command & 0xF) {\n\t\tcase CMD_MZX_SOUND_VOLUME: {\n\t\t\tREG_MASTER_VOLUME = (command >> 24);\n\t\t} break;\n\t\tcase CMD_MZX_PCS_TONE: {\n\t\t\tint freq = (command >> 8) & 0xFFFF;\n\t\t\tint volume = (command >> 24);\n\t\t\tif(freq > 0) {\n\t\t\t\tSCHANNEL_CR(MZX_PCS_CHANNEL) = SCHANNEL_ENABLE | volume | SOUND_PAN(64) | SOUND_FORMAT_PSG | (3 << 24);\n\t\t\t\tSCHANNEL_TIMER(MZX_PCS_CHANNEL) = SOUND_FREQ(freq << 3);\n\t\t\t} else {\n\t\t\t\tSCHANNEL_CR(MZX_PCS_CHANNEL) = 0;\n\t\t\t}\n\t\t} break;\n\t\tcase CMD_MZX_MM_GET_POSITION: {\n#ifdef CONFIG_BLOCKSDS\n\t\t\tfifoSendValue32(FIFO_MZX, mmGetPosition());\n#else\n\t\t\tfifoSendValue32(FIFO_MZX, mmLayerMain.position);\n#endif\n\t\t} break;\n\t}\n}\n\nvoid VblankHandler(void) {\n\n}\n\nvoid VcountHandler() {\n\tinputGetAndSend();\n}\n\nvolatile bool exitflag = false;\n\nvoid powerButtonCB() {\n\texitflag = true;\n}\n\n//---------------------------------------------------------------------------------\nint main() {\n//---------------------------------------------------------------------------------\n\t// clear sound registers\n\tdmaFillWords(0, (void*)0x04000400, 0x100);\n\n\tREG_SOUNDCNT |= SOUND_ENABLE;\n\twritePowerManagement(PM_CONTROL_REG, ( readPowerManagement(PM_CONTROL_REG) & ~PM_SOUND_MUTE ) | PM_SOUND_AMP );\n\tpowerOn(POWER_SOUND);\n\n\treadUserSettings();\n\tledBlink(0);\n\n\tirqInit();\n\t// Start the RTC tracking IRQ\n#ifdef CONFIG_BLOCKSDS\n\tinitClockIRQTimer(3);\n#else\n\tinitClockIRQ();\n#endif\n\tfifoInit();\n\ttouchInit();\n\n\tmmInstall(FIFO_MAXMOD);\n\n\tSetYtrigger(80);\n\n\tinstallSystemFIFO();\n\tfifoSetValue32Handler(FIFO_MZX, mzxFifoCommandHandler, NULL);\n\n\tirqSet(IRQ_VCOUNT, VcountHandler);\n\tirqSet(IRQ_VBLANK, VblankHandler);\n\n\tirqEnable(IRQ_VBLANK | IRQ_VCOUNT | IRQ_NETWORK);\n\n\tsetPowerButtonCB(powerButtonCB);\n\n\t// Keep the ARM7 mostly idle\n\twhile(!exitflag) {\n\t\tif( 0 == (REG_KEYINPUT & (KEY_SELECT | KEY_START | KEY_L | KEY_R))) {\n\t\t\texitflag = true;\n\t\t}\n\t\tswiWaitForVBlank();\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "arch/nds/audio.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2016 Adrian Siekierka <asiekierka@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/configure.h\"\n#include \"../../src/event.h\"\n#include \"../../src/platform.h\"\n#include \"../../src/util.h\"\n#include \"../../src/io/fsafeopen.h\"\n#include \"../../src/audio/audio.h\"\n\n#include <nds.h>\n#include <maxmod9.h>\n#include \"platform.h\"\n#include \"../../src/audio/audio.h\"\n#include \"../../src/audio/sfx.h\"\n\n#ifdef CONFIG_AUDIO\n\n// Utility logic\n\nstatic u8 nds_vol_table[] =\n{\n\t0, 8, 16, 26, 36, 48, 61, 75, 91, 108, 127\n};\n\n#define NDS_VOLUME(vol) nds_vol_table[CLAMP((vol), 0, 10)]\n\n// FIFO calls\n\nstatic inline void nds_sound_volume(int volume)\n{\n  fifoSendValue32(FIFO_MZX, CMD_MZX_SOUND_VOLUME | (NDS_VOLUME(volume) << 24));\n}\n\nstatic inline void nds_pcs_sound(int freq, int volume)\n{\n  fifoSendValue32(FIFO_MZX, CMD_MZX_PCS_TONE | (((freq) & 0xFFFF) << 8) | (NDS_VOLUME(volume) << 24));\n}\n\nstatic inline int nds_mm_get_position(void)\n{\n  fifoSendValue32(FIFO_MZX, CMD_MZX_MM_GET_POSITION);\n  while(!fifoCheckValue32(FIFO_MZX));\n  return fifoGetValue32(FIFO_MZX);\n}\n\n// PC speaker implementation\n\n#define PCS_DURATION_PER_VBL 9\n\nstatic int pcs_playing;\nstatic int pcs_frequency;\nstatic int pcs_duration;\n\nstatic inline void nds_pcs_tick(int duration)\n{\n  int last_playing = pcs_playing;\n  int last_frequency = pcs_frequency;\n  int ticks = 0;\n  int ticked;\n\n  if(!audio_get_pcs_on() || sfx_should_cancel_note())\n    pcs_playing = 0;\n\n  while(ticks < duration)\n  {\n    if(pcs_playing)\n    {\n      if(pcs_duration <= 0)\n      {\n        pcs_playing = 0;\n      }\n      else\n      {\n        ticked = MIN(duration - ticks, pcs_duration);\n        ticks += ticked;\n        pcs_duration -= ticked;\n      }\n      continue;\n    }\n\n    // Update PC speaker.\n    sfx_next_note(&pcs_playing, &pcs_frequency, &pcs_duration);\n\n    if(!pcs_playing)\n      break;\n  }\n\n  if(pcs_playing != last_playing)\n  {\n    nds_pcs_sound(pcs_playing ? pcs_frequency : 0, NDS_VOLUME(audio_get_pcs_volume()));\n  }\n  else\n\n  if(pcs_playing && (pcs_frequency != last_frequency))\n  {\n    nds_pcs_sound(pcs_frequency, NDS_VOLUME(audio_get_pcs_volume()));\n  }\n}\n\n// Maxmod glue code\n\n#define MAX_NUM_SAMPLES 8\n\nstatic mm_ds_system maxmod_conf;\nstatic int maxmod_effective_frequency;\nstatic char *maxmod_sample_filenames[MAX_NUM_SAMPLES];\n\nstatic void nds_maxmod_init(void)\n{\n  u32 i;\n\n  maxmod_conf.mod_count = 1;\n  maxmod_conf.samp_count = isDSiMode() ? MAX_NUM_SAMPLES : 4;\n  maxmod_conf.mem_bank = malloc(sizeof(mm_word) * (maxmod_conf.mod_count + maxmod_conf.samp_count));\n  for(i = 0; i < (maxmod_conf.mod_count + maxmod_conf.samp_count); i++)\n  {\n    maxmod_conf.mem_bank[i] = 0;\n  }\n  for(i = 0; i < maxmod_conf.samp_count; i++)\n  {\n    maxmod_sample_filenames[i] = NULL;\n  }\n  maxmod_conf.fifo_channel = FIFO_MAXMOD;\n\n  DC_FlushAll();\n\n  mmInit(&maxmod_conf);\n  mmSelectMode(MM_MODE_C); // extended mixing\n  mmLockChannels(1 << 8); // sound channel 8 is used for PC speaker\n}\n\nvoid audio_set_module_order(int order)\n{\n  mmPosition(order);\n}\n\nint audio_get_module_order(void)\n{\n  return nds_mm_get_position();\n}\n\nvoid audio_set_module_position(int position)\n{\n  // FIXME: position is not supported by maxmod\n  mmPosition(position);\n}\n\nint audio_get_module_position(void)\n{\n  // TODO\n  return 0;\n}\n\nvoid audio_set_module_volume(int volume) // 0..255\n{\n  int base_volume = NDS_VOLUME(audio_get_music_volume()); // 0..127\n  mmSetModuleVolume((base_volume * volume) >> 5); // 0..1012\n}\n\nvoid audio_set_module_frequency(int frequency)\n{\n  int mm_freq;\n\n  if(frequency == 0)\n    frequency = 44100;\n  maxmod_effective_frequency = frequency;\n  mm_freq = CLAMP(div32(frequency * 0x400, 44100), 0x200, 0x800);\n\n  mmSetModuleTempo(mm_freq);\n  mmSetModulePitch(mm_freq);\n}\n\nint audio_get_module_frequency(void)\n{\n  return maxmod_effective_frequency;\n}\n\nint audio_get_module_length(void)\n{\n  // TODO\n  return 0;\n}\n\nvoid audio_set_module_loop_start(int loop_start)\n{\n  // TODO\n}\n\nint audio_get_module_loop_start(void)\n{\n  // TODO\n  return 0;\n}\n\nvoid audio_set_module_loop_end(int loop_end)\n{\n  // TODO\n}\n\nint audio_get_module_loop_end(void)\n{\n  // TODO\n  return 0;\n}\n\nstatic u8 *audio_load_mas_file(char *filename)\n{\n  u32 mas_size;\n  u8 *mas_buffer;\n  FILE *mas_file;\n\n  // load the maxmod soundbank file\n  mas_file = fopen_unsafe(filename, \"rb\");\n  if(mas_file == NULL)\n  {\n    return NULL;\n  }\n\n  // get soundbank filesize\n  fread(&mas_size, 4, 1, mas_file);\n\n  // read buffer\n  if(mas_size <= 0)\n  {\n    fclose(mas_file);\n    return NULL;\n  }\n\n  mas_buffer = malloc(mas_size + 8);\n  if(mas_buffer == NULL)\n  {\n    fclose(mas_file);\n    return NULL;\n  }\n\n  if(!fread(mas_buffer + 4, mas_size + 4, 1, mas_file))\n  {\n    free(mas_buffer);\n    fclose(mas_file);\n    return NULL;\n  }\n\n  fclose(mas_file);\n  return mas_buffer;\n}\n\n#define MAXMOD_SAMPLE_FREQ(f) (((f) * 1024 + (1 << 14)) >> 15)\n\nstatic u8 *audio_load_sam_file(char *filename)\n{\n  u32 sam_size;\n  u32 sam_size_aligned;\n  u8 *mas_buffer;\n  FILE *sam_file;\n\n  // load the SAM file\n  sam_file = fopen_unsafe(filename, \"rb\");\n  if(sam_file == NULL)\n  {\n    return NULL;\n  }\n\n  // get SAM filesize\n  fseek(sam_file, 0, SEEK_END);\n  sam_size = ftell(sam_file);\n  fseek(sam_file, 0, SEEK_SET);\n  if(sam_size <= 0)\n  {\n    fclose(sam_file);\n    return NULL;\n  }\n\n  // generate a .MAS-format sample on the fly\n  sam_size_aligned = (sam_size + 3) & (~3);\n  mas_buffer = malloc(8 /* preamble */ + 16 /* header */ + sam_size_aligned + 4 /* padded data */);\n  if(mas_buffer == NULL)\n  {\n    fclose(sam_file);\n    return NULL;\n  }\n\n  if(!fread(mas_buffer + 8 + 16, sam_size, 1, sam_file))\n  {\n    free(mas_buffer);\n    fclose(sam_file);\n    return NULL;\n  }\n\n  fclose(sam_file);\n\n  // clear up and convert data to signed 8-bit\n  memset(mas_buffer, 0, 8 + 16);\n  memset(mas_buffer + 8 + 16 + sam_size, 0, sam_size_aligned - sam_size + 4);\n\n  // generate header\n  mas_buffer[4] = 2; // type: NDS sample\n  mas_buffer[5] = 0x18; // version\n  *((u32*) &mas_buffer[8 + 4]) = sam_size_aligned >> 2; // length\n  mas_buffer[8 + 8] = 0; // format: signed 8-bit\n  mas_buffer[8 + 9] = 2; // repeat: no\n  *((u16*) &mas_buffer[8 + 10]) = MAXMOD_SAMPLE_FREQ(audio_get_real_frequency(SAM_DEFAULT_PERIOD)); // length\n\n  return mas_buffer;\n}\n\nint audio_play_module(char *filename, boolean safely, int volume)\n{\n  char mas_filename[MAX_PATH];\n  char translated_filename[MAX_PATH];\n  char *mas_ext_pos;\n  u8 *mas_buffer;\n\n  if(!audio_get_music_on())\n    return 1;\n\n  // we can only play pre-converted .MAS files\n  strcpy(mas_filename, filename);\n  mas_ext_pos = strrchr(mas_filename, '.');\n  if(mas_ext_pos == NULL)\n  {\n    return 0;\n  }\n  strcpy(mas_ext_pos, \".mas\");\n\n  if(safely)\n  {\n    if(fsafetranslate(mas_filename, translated_filename, MAX_PATH) != FSAFE_SUCCESS)\n    {\n      return 0;\n    }\n\n    strcpy(mas_filename, translated_filename);\n  }\n\n  mas_buffer = audio_load_mas_file(mas_filename);\n  if(mas_buffer == NULL)\n  {\n    return 0;\n  }\n  audio_end_module();\n\n  maxmod_conf.mem_bank[0] = (mm_word) mas_buffer;\n  DC_FlushAll();\n\n  // play module\n  mmStart(0, MM_PLAY_LOOP);\n\n  audio_set_module_volume(volume);\n  audio_set_module_frequency(0);\n\n  return 1;\n}\n\nvoid audio_end_module(void)\n{\n  if(!mmActive())\n    return;\n\n  mmStop();\n\n  while(mmActive())\n    delay(1);\n\n  if(maxmod_conf.mem_bank[0] != 0)\n  {\n    free((void*) maxmod_conf.mem_bank[0]);\n    maxmod_conf.mem_bank[0] = 0;\n  }\n}\n\nvoid audio_spot_sample(int period, int which)\n{\n  // not implemented\n}\n\n// Sample playback code\n\nvoid audio_play_sample(char *filename, boolean safely, int period)\n{\n  char mas_filename[MAX_PATH];\n  char translated_filename[MAX_PATH];\n  char *mas_ext_pos;\n  u8 *mas_buffer;\n  mm_sound_effect sfx;\n  int freq_desired, freq_real;\n  u32 sample_id, mas_fn_len;\n\n  if(!audio_get_music_on())\n    return;\n\n  // we can only play pre-converted .SAM files\n  strcpy(mas_filename, filename);\n  mas_ext_pos = strrchr(mas_filename, '.');\n  if(mas_ext_pos == NULL)\n  {\n    return;\n  }\n  strcpy(mas_ext_pos, \".sam\");\n\n  if(safely)\n  {\n    if(fsafetranslate(mas_filename, translated_filename, MAX_PATH) != FSAFE_SUCCESS)\n    {\n      return;\n    }\n\n    strcpy(mas_filename, translated_filename);\n  }\n\n  sample_id = 0;\n  for(sample_id = 0; sample_id < maxmod_conf.samp_count; sample_id++)\n  {\n    if(maxmod_sample_filenames[sample_id] != NULL)\n    {\n      if(!strcmp(maxmod_sample_filenames[sample_id], mas_filename))\n      {\n        break;\n      }\n    }\n  }\n\n  if(sample_id == maxmod_conf.samp_count)\n  {\n    // deallocate all samples\n    audio_end_sample();\n    sample_id = 0;\n  }\n\n  if(maxmod_sample_filenames[sample_id] == NULL)\n  {\n    mas_buffer = audio_load_sam_file(mas_filename);\n    if(mas_buffer == NULL)\n    {\n      return;\n    }\n\n    maxmod_conf.mem_bank[1 + sample_id] = (mm_word) mas_buffer;\n    DC_FlushAll();\n\n    mas_fn_len = strlen(mas_filename) + 1;\n    maxmod_sample_filenames[sample_id] = malloc(mas_fn_len);\n    strcpy(maxmod_sample_filenames[sample_id], mas_filename);\n  }\n\n  freq_real = audio_get_real_frequency(SAM_DEFAULT_PERIOD);\n  freq_desired = period == 0 ? freq_real : audio_get_real_frequency(period);\n\n  // play sample\n  sfx.id = 0;\n  sfx.rate = CLAMP(divf32(freq_desired << 12, freq_real << 12) >> 2, 0, 0xFFFF);\n  sfx.handle = 0;\n  sfx.volume = NDS_VOLUME(audio_get_sound_volume()) << 1;\n  sfx.panning = 128;\n\n  mmEffectEx(&sfx);\n}\n\nvoid audio_end_sample(void)\n{\n  u32 i;\n\n  // TODO: mmEffectActive() is not present on ARM9 currently\n  mmEffectCancelAll();\n  delay(1);\n\n  for(i = 0; i < maxmod_conf.samp_count; i++)\n  {\n    if(maxmod_conf.mem_bank[1 + i] != 0)\n    {\n      free((void*) maxmod_conf.mem_bank[1 + i]);\n      maxmod_conf.mem_bank[1 + i] = 0;\n\n      free(maxmod_sample_filenames[i]);\n      maxmod_sample_filenames[i] = NULL;\n    }\n  }\n}\n\n// Audio glue code\n\nint nds_max_samples;\n\nint audio_get_max_samples(void)\n{\n  return nds_max_samples;\n}\n\nvoid audio_set_max_samples(int max_samples)\n{\n  if(max_samples < 0 || (u32)max_samples > maxmod_conf.samp_count)\n    max_samples = maxmod_conf.samp_count;\n  nds_max_samples = max_samples;\n}\n\nvoid nds_audio_vblank(void)\n{\n  nds_pcs_tick(PCS_DURATION_PER_VBL);\n}\n\nvoid init_audio(struct config_info *conf)\n{\n  audio_set_music_volume(conf->music_volume);\n  audio_set_sound_volume(conf->sam_volume);\n  audio_set_music_on(conf->music_on);\n  audio_set_pcs_on(conf->pc_speaker_on);\n  audio_set_pcs_volume(conf->pc_speaker_volume);\n\n  init_audio_platform(conf);\n\n  audio_set_max_samples(-1);\n}\n\nvoid quit_audio(void)\n{\n  quit_audio_platform();\n}\n\nvoid init_audio_platform(struct config_info *conf)\n{\n  // PC speaker init\n  pcs_playing = 0;\n  pcs_frequency = 0;\n  pcs_duration = 0;\n\n  // global volume init\n  nds_sound_volume(10);\n\n  // maxmod init\n  nds_maxmod_init();\n}\n\nvoid quit_audio_platform(void)\n{\n  audio_end_module();\n\n  nds_sound_volume(0);\n  nds_pcs_sound(0, 0);\n}\n\n#endif // CONFIG_AUDIO\n"
  },
  {
    "path": "arch/nds/dlmalloc.c",
    "content": "#include \"dlmalloc.h\"\n\n/*\n  This is a version (aka dlmalloc) of malloc/free/realloc written by\n  Doug Lea and released to the public domain, as explained at\n  http://creativecommons.org/publicdomain/zero/1.0/ Send questions,\n  comments, complaints, performance data, etc to dl@cs.oswego.edu\n\n* Version 2.8.6 Wed Aug 29 06:57:58 2012  Doug Lea\n   Note: There may be an updated version of this malloc obtainable at\n           ftp://gee.cs.oswego.edu/pub/misc/malloc.c\n         Check before installing!\n\n* Quickstart\n\n  This library is all in one file to simplify the most common usage:\n  ftp it, compile it (-O3), and link it into another program. All of\n  the compile-time options default to reasonable values for use on\n  most platforms.  You might later want to step through various\n  compile-time and dynamic tuning options.\n\n  For convenience, an include file for code using this malloc is at:\n     ftp://gee.cs.oswego.edu/pub/misc/malloc-2.8.6.h\n  You don't really need this .h file unless you call functions not\n  defined in your system include files.  The .h file contains only the\n  excerpts from this file needed for using this malloc on ANSI C/C++\n  systems, so long as you haven't changed compile-time options about\n  naming and tuning parameters.  If you do, then you can create your\n  own malloc.h that does include all settings by cutting at the point\n  indicated below. Note that you may already by default be using a C\n  library containing a malloc that is based on some version of this\n  malloc (for example in linux). You might still want to use the one\n  in this file to customize settings or to avoid overheads associated\n  with library versions.\n\n* Vital statistics:\n\n  Supported pointer/size_t representation:       4 or 8 bytes\n       size_t MUST be an unsigned type of the same width as\n       pointers. (If you are using an ancient system that declares\n       size_t as a signed type, or need it to be a different width\n       than pointers, you can use a previous release of this malloc\n       (e.g. 2.7.2) supporting these.)\n\n  Alignment:                                     8 bytes (minimum)\n       This suffices for nearly all current machines and C compilers.\n       However, you can define MALLOC_ALIGNMENT to be wider than this\n       if necessary (up to 128bytes), at the expense of using more space.\n\n  Minimum overhead per allocated chunk:   4 or  8 bytes (if 4byte sizes)\n                                          8 or 16 bytes (if 8byte sizes)\n       Each malloced chunk has a hidden word of overhead holding size\n       and status information, and additional cross-check word\n       if FOOTERS is defined.\n\n  Minimum allocated size: 4-byte ptrs:  16 bytes    (including overhead)\n                          8-byte ptrs:  32 bytes    (including overhead)\n\n       Even a request for zero bytes (i.e., malloc(0)) returns a\n       pointer to something of the minimum allocatable size.\n       The maximum overhead wastage (i.e., number of extra bytes\n       allocated than were requested in malloc) is less than or equal\n       to the minimum size, except for requests >= mmap_threshold that\n       are serviced via mmap(), where the worst case wastage is about\n       32 bytes plus the remainder from a system page (the minimal\n       mmap unit); typically 4096 or 8192 bytes.\n\n  Security: static-safe; optionally more or less\n       The \"security\" of malloc refers to the ability of malicious\n       code to accentuate the effects of errors (for example, freeing\n       space that is not currently malloc'ed or overwriting past the\n       ends of chunks) in code that calls malloc.  This malloc\n       guarantees not to modify any memory locations below the base of\n       heap, i.e., static variables, even in the presence of usage\n       errors.  The routines additionally detect most improper frees\n       and reallocs.  All this holds as long as the static bookkeeping\n       for malloc itself is not corrupted by some other means.  This\n       is only one aspect of security -- these checks do not, and\n       cannot, detect all possible programming errors.\n\n       If FOOTERS is defined nonzero, then each allocated chunk\n       carries an additional check word to verify that it was malloced\n       from its space.  These check words are the same within each\n       execution of a program using malloc, but differ across\n       executions, so externally crafted fake chunks cannot be\n       freed. This improves security by rejecting frees/reallocs that\n       could corrupt heap memory, in addition to the checks preventing\n       writes to statics that are always on.  This may further improve\n       security at the expense of time and space overhead.  (Note that\n       FOOTERS may also be worth using with MSPACES.)\n\n       By default detected errors cause the program to abort (calling\n       \"abort()\"). You can override this to instead proceed past\n       errors by defining PROCEED_ON_ERROR.  In this case, a bad free\n       has no effect, and a malloc that encounters a bad address\n       caused by user overwrites will ignore the bad address by\n       dropping pointers and indices to all known memory. This may\n       be appropriate for programs that should continue if at all\n       possible in the face of programming errors, although they may\n       run out of memory because dropped memory is never reclaimed.\n\n       If you don't like either of these options, you can define\n       CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything\n       else. And if if you are sure that your program using malloc has\n       no errors or vulnerabilities, you can define INSECURE to 1,\n       which might (or might not) provide a small performance improvement.\n\n       It is also possible to limit the maximum total allocatable\n       space, using malloc_set_footprint_limit. This is not\n       designed as a security feature in itself (calls to set limits\n       are not screened or privileged), but may be useful as one\n       aspect of a secure implementation.\n\n  Thread-safety: NOT thread-safe unless USE_LOCKS defined non-zero\n       When USE_LOCKS is defined, each public call to malloc, free,\n       etc is surrounded with a lock. By default, this uses a plain\n       pthread mutex, win32 critical section, or a spin-lock if if\n       available for the platform and not disabled by setting\n       USE_SPIN_LOCKS=0.  However, if USE_RECURSIVE_LOCKS is defined,\n       recursive versions are used instead (which are not required for\n       base functionality but may be needed in layered extensions).\n       Using a global lock is not especially fast, and can be a major\n       bottleneck.  It is designed only to provide minimal protection\n       in concurrent environments, and to provide a basis for\n       extensions.  If you are using malloc in a concurrent program,\n       consider instead using nedmalloc\n       (http://www.nedprod.com/programs/portable/nedmalloc/) or\n       ptmalloc (See http://www.malloc.de), which are derived from\n       versions of this malloc.\n\n  System requirements: Any combination of MORECORE and/or MMAP/MUNMAP\n       This malloc can use unix sbrk or any emulation (invoked using\n       the CALL_MORECORE macro) and/or mmap/munmap or any emulation\n       (invoked using CALL_MMAP/CALL_MUNMAP) to get and release system\n       memory.  On most unix systems, it tends to work best if both\n       MORECORE and MMAP are enabled.  On Win32, it uses emulations\n       based on VirtualAlloc. It also uses common C library functions\n       like memset.\n\n  Compliance: I believe it is compliant with the Single Unix Specification\n       (See http://www.unix.org). Also SVID/XPG, ANSI C, and probably\n       others as well.\n\n* Overview of algorithms\n\n  This is not the fastest, most space-conserving, most portable, or\n  most tunable malloc ever written. However it is among the fastest\n  while also being among the most space-conserving, portable and\n  tunable.  Consistent balance across these factors results in a good\n  general-purpose allocator for malloc-intensive programs.\n\n  In most ways, this malloc is a best-fit allocator. Generally, it\n  chooses the best-fitting existing chunk for a request, with ties\n  broken in approximately least-recently-used order. (This strategy\n  normally maintains low fragmentation.) However, for requests less\n  than 256bytes, it deviates from best-fit when there is not an\n  exactly fitting available chunk by preferring to use space adjacent\n  to that used for the previous small request, as well as by breaking\n  ties in approximately most-recently-used order. (These enhance\n  locality of series of small allocations.)  And for very large requests\n  (>= 256Kb by default), it relies on system memory mapping\n  facilities, if supported.  (This helps avoid carrying around and\n  possibly fragmenting memory used only for large chunks.)\n\n  All operations (except malloc_stats and mallinfo) have execution\n  times that are bounded by a constant factor of the number of bits in\n  a size_t, not counting any clearing in calloc or copying in realloc,\n  or actions surrounding MORECORE and MMAP that have times\n  proportional to the number of non-contiguous regions returned by\n  system allocation routines, which is often just 1. In real-time\n  applications, you can optionally suppress segment traversals using\n  NO_SEGMENT_TRAVERSAL, which assures bounded execution even when\n  system allocators return non-contiguous spaces, at the typical\n  expense of carrying around more memory and increased fragmentation.\n\n  The implementation is not very modular and seriously overuses\n  macros. Perhaps someday all C compilers will do as good a job\n  inlining modular code as can now be done by brute-force expansion,\n  but now, enough of them seem not to.\n\n  Some compilers issue a lot of warnings about code that is\n  dead/unreachable only on some platforms, and also about intentional\n  uses of negation on unsigned types. All known cases of each can be\n  ignored.\n\n  For a longer but out of date high-level description, see\n     http://gee.cs.oswego.edu/dl/html/malloc.html\n\n* MSPACES\n  If MSPACES is defined, then in addition to malloc, free, etc.,\n  this file also defines mspace_malloc, mspace_free, etc. These\n  are versions of malloc routines that take an \"mspace\" argument\n  obtained using create_mspace, to control all internal bookkeeping.\n  If ONLY_MSPACES is defined, only these versions are compiled.\n  So if you would like to use this allocator for only some allocations,\n  and your system malloc for others, you can compile with\n  ONLY_MSPACES and then do something like...\n    static mspace mymspace = create_mspace(0,0); // for example\n    #define mymalloc(bytes)  mspace_malloc(mymspace, bytes)\n\n  (Note: If you only need one instance of an mspace, you can instead\n  use \"USE_DL_PREFIX\" to relabel the global malloc.)\n\n  You can similarly create thread-local allocators by storing\n  mspaces as thread-locals. For example:\n    static __thread mspace tlms = 0;\n    void*  tlmalloc(size_t bytes) {\n      if (tlms == 0) tlms = create_mspace(0, 0);\n      return mspace_malloc(tlms, bytes);\n    }\n    void  tlfree(void* mem) { mspace_free(tlms, mem); }\n\n  Unless FOOTERS is defined, each mspace is completely independent.\n  You cannot allocate from one and free to another (although\n  conformance is only weakly checked, so usage errors are not always\n  caught). If FOOTERS is defined, then each chunk carries around a tag\n  indicating its originating mspace, and frees are directed to their\n  originating spaces. Normally, this requires use of locks.\n\n -------------------------  Compile-time options ---------------------------\n\nBe careful in setting #define values for numerical constants of type\nsize_t. On some systems, literal values are not automatically extended\nto size_t precision unless they are explicitly casted. You can also\nuse the symbolic values MAX_SIZE_T, SIZE_T_ONE, etc below.\n\nWIN32                    default: defined if _WIN32 defined\n  Defining WIN32 sets up defaults for MS environment and compilers.\n  Otherwise defaults are for unix. Beware that there seem to be some\n  cases where this malloc might not be a pure drop-in replacement for\n  Win32 malloc: Random-looking failures from Win32 GDI API's (eg;\n  SetDIBits()) may be due to bugs in some video driver implementations\n  when pixel buffers are malloc()ed, and the region spans more than\n  one VirtualAlloc()ed region. Because dlmalloc uses a small (64Kb)\n  default granularity, pixel buffers may straddle virtual allocation\n  regions more often than when using the Microsoft allocator.  You can\n  avoid this by using VirtualAlloc() and VirtualFree() for all pixel\n  buffers rather than using malloc().  If this is not possible,\n  recompile this malloc with a larger DEFAULT_GRANULARITY. Note:\n  in cases where MSC and gcc (cygwin) are known to differ on WIN32,\n  conditions use _MSC_VER to distinguish them.\n\nDLMALLOC_EXPORT       default: extern\n  Defines how public APIs are declared. If you want to export via a\n  Windows DLL, you might define this as\n    #define DLMALLOC_EXPORT extern  __declspec(dllexport)\n  If you want a POSIX ELF shared object, you might use\n    #define DLMALLOC_EXPORT extern __attribute__((visibility(\"default\")))\n\nMALLOC_ALIGNMENT         default: (size_t)(2 * sizeof(void *))\n  Controls the minimum alignment for malloc'ed chunks.  It must be a\n  power of two and at least 8, even on machines for which smaller\n  alignments would suffice. It may be defined as larger than this\n  though. Note however that code and data structures are optimized for\n  the case of 8-byte alignment.\n\nMSPACES                  default: 0 (false)\n  If true, compile in support for independent allocation spaces.\n  This is only supported if HAVE_MMAP is true.\n\nONLY_MSPACES             default: 0 (false)\n  If true, only compile in mspace versions, not regular versions.\n\nUSE_LOCKS                default: 0 (false)\n  Causes each call to each public routine to be surrounded with\n  pthread or WIN32 mutex lock/unlock. (If set true, this can be\n  overridden on a per-mspace basis for mspace versions.) If set to a\n  non-zero value other than 1, locks are used, but their\n  implementation is left out, so lock functions must be supplied manually,\n  as described below.\n\nUSE_SPIN_LOCKS           default: 1 iff USE_LOCKS and spin locks available\n  If true, uses custom spin locks for locking. This is currently\n  supported only gcc >= 4.1, older gccs on x86 platforms, and recent\n  MS compilers.  Otherwise, posix locks or win32 critical sections are\n  used.\n\nUSE_RECURSIVE_LOCKS      default: not defined\n  If defined nonzero, uses recursive (aka reentrant) locks, otherwise\n  uses plain mutexes. This is not required for malloc proper, but may\n  be needed for layered allocators such as nedmalloc.\n\nLOCK_AT_FORK            default: not defined\n  If defined nonzero, performs pthread_atfork upon initialization\n  to initialize child lock while holding parent lock. The implementation\n  assumes that pthread locks (not custom locks) are being used. In other\n  cases, you may need to customize the implementation.\n\nFOOTERS                  default: 0\n  If true, provide extra checking and dispatching by placing\n  information in the footers of allocated chunks. This adds\n  space and time overhead.\n\nINSECURE                 default: 0\n  If true, omit checks for usage errors and heap space overwrites.\n\nUSE_DL_PREFIX            default: NOT defined\n  Causes compiler to prefix all public routines with the string 'dl'.\n  This can be useful when you only want to use this malloc in one part\n  of a program, using your regular system malloc elsewhere.\n\nMALLOC_INSPECT_ALL       default: NOT defined\n  If defined, compiles malloc_inspect_all and mspace_inspect_all, that\n  perform traversal of all heap space.  Unless access to these\n  functions is otherwise restricted, you probably do not want to\n  include them in secure implementations.\n\nABORT                    default: defined as abort()\n  Defines how to abort on failed checks.  On most systems, a failed\n  check cannot die with an \"assert\" or even print an informative\n  message, because the underlying print routines in turn call malloc,\n  which will fail again.  Generally, the best policy is to simply call\n  abort(). It's not very useful to do more than this because many\n  errors due to overwriting will show up as address faults (null, odd\n  addresses etc) rather than malloc-triggered checks, so will also\n  abort.  Also, most compilers know that abort() does not return, so\n  can better optimize code conditionally calling it.\n\nPROCEED_ON_ERROR           default: defined as 0 (false)\n  Controls whether detected bad addresses cause them to bypassed\n  rather than aborting. If set, detected bad arguments to free and\n  realloc are ignored. And all bookkeeping information is zeroed out\n  upon a detected overwrite of freed heap space, thus losing the\n  ability to ever return it from malloc again, but enabling the\n  application to proceed. If PROCEED_ON_ERROR is defined, the\n  static variable malloc_corruption_error_count is compiled in\n  and can be examined to see if errors have occurred. This option\n  generates slower code than the default abort policy.\n\nDEBUG                    default: NOT defined\n  The DEBUG setting is mainly intended for people trying to modify\n  this code or diagnose problems when porting to new platforms.\n  However, it may also be able to better isolate user errors than just\n  using runtime checks.  The assertions in the check routines spell\n  out in more detail the assumptions and invariants underlying the\n  algorithms.  The checking is fairly extensive, and will slow down\n  execution noticeably. Calling malloc_stats or mallinfo with DEBUG\n  set will attempt to check every non-mmapped allocated and free chunk\n  in the course of computing the summaries.\n\nABORT_ON_ASSERT_FAILURE   default: defined as 1 (true)\n  Debugging assertion failures can be nearly impossible if your\n  version of the assert macro causes malloc to be called, which will\n  lead to a cascade of further failures, blowing the runtime stack.\n  ABORT_ON_ASSERT_FAILURE cause assertions failures to call abort(),\n  which will usually make debugging easier.\n\nMALLOC_FAILURE_ACTION     default: sets errno to ENOMEM, or no-op on win32\n  The action to take before \"return 0\" when malloc fails to be able to\n  return memory because there is none available.\n\nHAVE_MORECORE             default: 1 (true) unless win32 or ONLY_MSPACES\n  True if this system supports sbrk or an emulation of it.\n\nMORECORE                  default: sbrk\n  The name of the sbrk-style system routine to call to obtain more\n  memory.  See below for guidance on writing custom MORECORE\n  functions. The type of the argument to sbrk/MORECORE varies across\n  systems.  It cannot be size_t, because it supports negative\n  arguments, so it is normally the signed type of the same width as\n  size_t (sometimes declared as \"intptr_t\").  It doesn't much matter\n  though. Internally, we only call it with arguments less than half\n  the max value of a size_t, which should work across all reasonable\n  possibilities, although sometimes generating compiler warnings.\n\nMORECORE_CONTIGUOUS       default: 1 (true) if HAVE_MORECORE\n  If true, take advantage of fact that consecutive calls to MORECORE\n  with positive arguments always return contiguous increasing\n  addresses.  This is true of unix sbrk. It does not hurt too much to\n  set it true anyway, since malloc copes with non-contiguities.\n  Setting it false when definitely non-contiguous saves time\n  and possibly wasted space it would take to discover this though.\n\nMORECORE_CANNOT_TRIM      default: NOT defined\n  True if MORECORE cannot release space back to the system when given\n  negative arguments. This is generally necessary only if you are\n  using a hand-crafted MORECORE function that cannot handle negative\n  arguments.\n\nNO_SEGMENT_TRAVERSAL       default: 0\n  If non-zero, suppresses traversals of memory segments\n  returned by either MORECORE or CALL_MMAP. This disables\n  merging of segments that are contiguous, and selectively\n  releasing them to the OS if unused, but bounds execution times.\n\nHAVE_MMAP                 default: 1 (true)\n  True if this system supports mmap or an emulation of it.  If so, and\n  HAVE_MORECORE is not true, MMAP is used for all system\n  allocation. If set and HAVE_MORECORE is true as well, MMAP is\n  primarily used to directly allocate very large blocks. It is also\n  used as a backup strategy in cases where MORECORE fails to provide\n  space from system. Note: A single call to MUNMAP is assumed to be\n  able to unmap memory that may have be allocated using multiple calls\n  to MMAP, so long as they are adjacent.\n\nHAVE_MREMAP               default: 1 on linux, else 0\n  If true realloc() uses mremap() to re-allocate large blocks and\n  extend or shrink allocation spaces.\n\nMMAP_CLEARS               default: 1 except on WINCE.\n  True if mmap clears memory so calloc doesn't need to. This is true\n  for standard unix mmap using /dev/zero and on WIN32 except for WINCE.\n\nUSE_BUILTIN_FFS            default: 0 (i.e., not used)\n  Causes malloc to use the builtin ffs() function to compute indices.\n  Some compilers may recognize and intrinsify ffs to be faster than the\n  supplied C version. Also, the case of x86 using gcc is special-cased\n  to an asm instruction, so is already as fast as it can be, and so\n  this setting has no effect. Similarly for Win32 under recent MS compilers.\n  (On most x86s, the asm version is only slightly faster than the C version.)\n\nmalloc_getpagesize         default: derive from system includes, or 4096.\n  The system page size. To the extent possible, this malloc manages\n  memory from the system in page-size units.  This may be (and\n  usually is) a function rather than a constant. This is ignored\n  if WIN32, where page size is determined using getSystemInfo during\n  initialization.\n\nUSE_DEV_RANDOM             default: 0 (i.e., not used)\n  Causes malloc to use /dev/random to initialize secure magic seed for\n  stamping footers. Otherwise, the current time is used.\n\nNO_MALLINFO                default: 0\n  If defined, don't compile \"mallinfo\". This can be a simple way\n  of dealing with mismatches between system declarations and\n  those in this file.\n\nMALLINFO_FIELD_TYPE        default: size_t\n  The type of the fields in the mallinfo struct. This was originally\n  defined as \"int\" in SVID etc, but is more usefully defined as\n  size_t. The value is used only if  HAVE_USR_INCLUDE_MALLOC_H is not set\n\nNO_MALLOC_STATS            default: 0\n  If defined, don't compile \"malloc_stats\". This avoids calls to\n  fprintf and bringing in stdio dependencies you might not want.\n\nREALLOC_ZERO_BYTES_FREES    default: not defined\n  This should be set if a call to realloc with zero bytes should\n  be the same as a call to free. Some people think it should. Otherwise,\n  since this malloc returns a unique pointer for malloc(0), so does\n  realloc(p, 0).\n\nLACKS_UNISTD_H, LACKS_FCNTL_H, LACKS_SYS_PARAM_H, LACKS_SYS_MMAN_H\nLACKS_STRINGS_H, LACKS_STRING_H, LACKS_SYS_TYPES_H,  LACKS_ERRNO_H\nLACKS_STDLIB_H LACKS_SCHED_H LACKS_TIME_H  default: NOT defined unless on WIN32\n  Define these if your system does not have these header files.\n  You might need to manually insert some of the declarations they provide.\n\nDEFAULT_GRANULARITY        default: page size if MORECORE_CONTIGUOUS,\n                                system_info.dwAllocationGranularity in WIN32,\n                                otherwise 64K.\n      Also settable using mallopt(M_GRANULARITY, x)\n  The unit for allocating and deallocating memory from the system.  On\n  most systems with contiguous MORECORE, there is no reason to\n  make this more than a page. However, systems with MMAP tend to\n  either require or encourage larger granularities.  You can increase\n  this value to prevent system allocation functions to be called so\n  often, especially if they are slow.  The value must be at least one\n  page and must be a power of two.  Setting to 0 causes initialization\n  to either page size or win32 region size.  (Note: In previous\n  versions of malloc, the equivalent of this option was called\n  \"TOP_PAD\")\n\nDEFAULT_TRIM_THRESHOLD    default: 2MB\n      Also settable using mallopt(M_TRIM_THRESHOLD, x)\n  The maximum amount of unused top-most memory to keep before\n  releasing via malloc_trim in free().  Automatic trimming is mainly\n  useful in long-lived programs using contiguous MORECORE.  Because\n  trimming via sbrk can be slow on some systems, and can sometimes be\n  wasteful (in cases where programs immediately afterward allocate\n  more large chunks) the value should be high enough so that your\n  overall system performance would improve by releasing this much\n  memory.  As a rough guide, you might set to a value close to the\n  average size of a process (program) running on your system.\n  Releasing this much memory would allow such a process to run in\n  memory.  Generally, it is worth tuning trim thresholds when a\n  program undergoes phases where several large chunks are allocated\n  and released in ways that can reuse each other's storage, perhaps\n  mixed with phases where there are no such chunks at all. The trim\n  value must be greater than page size to have any useful effect.  To\n  disable trimming completely, you can set to MAX_SIZE_T. Note that the trick\n  some people use of mallocing a huge space and then freeing it at\n  program startup, in an attempt to reserve system memory, doesn't\n  have the intended effect under automatic trimming, since that memory\n  will immediately be returned to the system.\n\nDEFAULT_MMAP_THRESHOLD       default: 256K\n      Also settable using mallopt(M_MMAP_THRESHOLD, x)\n  The request size threshold for using MMAP to directly service a\n  request. Requests of at least this size that cannot be allocated\n  using already-existing space will be serviced via mmap.  (If enough\n  normal freed space already exists it is used instead.)  Using mmap\n  segregates relatively large chunks of memory so that they can be\n  individually obtained and released from the host system. A request\n  serviced through mmap is never reused by any other request (at least\n  not directly; the system may just so happen to remap successive\n  requests to the same locations).  Segregating space in this way has\n  the benefits that: Mmapped space can always be individually released\n  back to the system, which helps keep the system level memory demands\n  of a long-lived program low.  Also, mapped memory doesn't become\n  `locked' between other chunks, as can happen with normally allocated\n  chunks, which means that even trimming via malloc_trim would not\n  release them.  However, it has the disadvantage that the space\n  cannot be reclaimed, consolidated, and then used to service later\n  requests, as happens with normal chunks.  The advantages of mmap\n  nearly always outweigh disadvantages for \"large\" chunks, but the\n  value of \"large\" may vary across systems.  The default is an\n  empirically derived value that works well in most systems. You can\n  disable mmap by setting to MAX_SIZE_T.\n\nMAX_RELEASE_CHECK_RATE   default: 4095 unless not HAVE_MMAP\n  The number of consolidated frees between checks to release\n  unused segments when freeing. When using non-contiguous segments,\n  especially with multiple mspaces, checking only for topmost space\n  doesn't always suffice to trigger trimming. To compensate for this,\n  free() will, with a period of MAX_RELEASE_CHECK_RATE (or the\n  current number of segments, if greater) try to release unused\n  segments to the OS when freeing chunks that result in\n  consolidation. The best value for this parameter is a compromise\n  between slowing down frees with relatively costly checks that\n  rarely trigger versus holding on to unused memory. To effectively\n  disable, set to MAX_SIZE_T. This may lead to a very slight speed\n  improvement at the expense of carrying around more memory.\n*/\n\n/* Version identifier to allow people to support multiple versions */\n#ifndef DLMALLOC_VERSION\n#define DLMALLOC_VERSION 20806\n#endif /* DLMALLOC_VERSION */\n\n#ifndef DLMALLOC_EXPORT\n#define DLMALLOC_EXPORT extern\n#endif\n\n#ifndef WIN32\n#ifdef _WIN32\n#define WIN32 1\n#endif  /* _WIN32 */\n#ifdef _WIN32_WCE\n#define LACKS_FCNTL_H\n#define WIN32 1\n#endif /* _WIN32_WCE */\n#endif  /* WIN32 */\n#ifdef WIN32\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n#include <tchar.h>\n#define HAVE_MMAP 1\n#define HAVE_MORECORE 0\n#define LACKS_UNISTD_H\n#define LACKS_SYS_PARAM_H\n#define LACKS_SYS_MMAN_H\n#define LACKS_STRING_H\n#define LACKS_STRINGS_H\n#define LACKS_SYS_TYPES_H\n#define LACKS_ERRNO_H\n#define LACKS_SCHED_H\n#ifndef MALLOC_FAILURE_ACTION\n#define MALLOC_FAILURE_ACTION\n#endif /* MALLOC_FAILURE_ACTION */\n#ifndef MMAP_CLEARS\n#ifdef _WIN32_WCE /* WINCE reportedly does not clear */\n#define MMAP_CLEARS 0\n#else\n#define MMAP_CLEARS 1\n#endif /* _WIN32_WCE */\n#endif /*MMAP_CLEARS */\n#endif  /* WIN32 */\n\n#if defined(DARWIN) || defined(_DARWIN)\n/* Mac OSX docs advise not to use sbrk; it seems better to use mmap */\n#ifndef HAVE_MORECORE\n#define HAVE_MORECORE 0\n#define HAVE_MMAP 1\n/* OSX allocators provide 16 byte alignment */\n#ifndef MALLOC_ALIGNMENT\n#define MALLOC_ALIGNMENT ((size_t)16U)\n#endif\n#endif  /* HAVE_MORECORE */\n#endif  /* DARWIN */\n\n#ifndef LACKS_SYS_TYPES_H\n#include <sys/types.h>  /* For size_t */\n#endif  /* LACKS_SYS_TYPES_H */\n\n/* The maximum possible size_t value has all bits set */\n#define MAX_SIZE_T           (~(size_t)0)\n\n#ifndef USE_LOCKS /* ensure true if spin or recursive locks set */\n#define USE_LOCKS  ((defined(USE_SPIN_LOCKS) && USE_SPIN_LOCKS != 0) || \\\n                    (defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0))\n#endif /* USE_LOCKS */\n\n#if USE_LOCKS /* Spin locks for gcc >= 4.1, older gcc on x86, MSC >= 1310 */\n#if ((defined(__GNUC__) &&                                              \\\n      ((__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) ||      \\\n       defined(__i386__) || defined(__x86_64__))) ||                    \\\n     (defined(_MSC_VER) && _MSC_VER>=1310))\n#ifndef USE_SPIN_LOCKS\n#define USE_SPIN_LOCKS 1\n#endif /* USE_SPIN_LOCKS */\n#elif USE_SPIN_LOCKS\n#error \"USE_SPIN_LOCKS defined without implementation\"\n#endif /* ... locks available... */\n#elif !defined(USE_SPIN_LOCKS)\n#define USE_SPIN_LOCKS 0\n#endif /* USE_LOCKS */\n\n#ifndef ONLY_MSPACES\n#define ONLY_MSPACES 0\n#endif  /* ONLY_MSPACES */\n#ifndef MSPACES\n#if ONLY_MSPACES\n#define MSPACES 1\n#else   /* ONLY_MSPACES */\n#define MSPACES 0\n#endif  /* ONLY_MSPACES */\n#endif  /* MSPACES */\n#ifndef MALLOC_ALIGNMENT\n#define MALLOC_ALIGNMENT ((size_t)(2 * sizeof(void *)))\n#endif  /* MALLOC_ALIGNMENT */\n#ifndef FOOTERS\n#define FOOTERS 0\n#endif  /* FOOTERS */\n#ifndef ABORT\n#define ABORT  abort()\n#endif  /* ABORT */\n#ifndef ABORT_ON_ASSERT_FAILURE\n#define ABORT_ON_ASSERT_FAILURE 1\n#endif  /* ABORT_ON_ASSERT_FAILURE */\n#ifndef PROCEED_ON_ERROR\n#define PROCEED_ON_ERROR 0\n#endif  /* PROCEED_ON_ERROR */\n\n#ifndef INSECURE\n#define INSECURE 0\n#endif  /* INSECURE */\n#ifndef MALLOC_INSPECT_ALL\n#define MALLOC_INSPECT_ALL 0\n#endif  /* MALLOC_INSPECT_ALL */\n#ifndef HAVE_MMAP\n#define HAVE_MMAP 1\n#endif  /* HAVE_MMAP */\n#ifndef MMAP_CLEARS\n#define MMAP_CLEARS 1\n#endif  /* MMAP_CLEARS */\n#ifndef HAVE_MREMAP\n#ifdef linux\n#define HAVE_MREMAP 1\n#define _GNU_SOURCE /* Turns on mremap() definition */\n#else   /* linux */\n#define HAVE_MREMAP 0\n#endif  /* linux */\n#endif  /* HAVE_MREMAP */\n#ifndef MALLOC_FAILURE_ACTION\n#define MALLOC_FAILURE_ACTION  errno = ENOMEM;\n#endif  /* MALLOC_FAILURE_ACTION */\n#ifndef HAVE_MORECORE\n#if ONLY_MSPACES\n#define HAVE_MORECORE 0\n#else   /* ONLY_MSPACES */\n#define HAVE_MORECORE 1\n#endif  /* ONLY_MSPACES */\n#endif  /* HAVE_MORECORE */\n#if !HAVE_MORECORE\n#define MORECORE_CONTIGUOUS 0\n#else   /* !HAVE_MORECORE */\n#define MORECORE_DEFAULT sbrk\n#ifndef MORECORE_CONTIGUOUS\n#define MORECORE_CONTIGUOUS 1\n#endif  /* MORECORE_CONTIGUOUS */\n#endif  /* HAVE_MORECORE */\n#ifndef DEFAULT_GRANULARITY\n#if (MORECORE_CONTIGUOUS || defined(WIN32))\n#define DEFAULT_GRANULARITY (0)  /* 0 means to compute in init_mparams */\n#else   /* MORECORE_CONTIGUOUS */\n#define DEFAULT_GRANULARITY ((size_t)64U * (size_t)1024U)\n#endif  /* MORECORE_CONTIGUOUS */\n#endif  /* DEFAULT_GRANULARITY */\n#ifndef DEFAULT_TRIM_THRESHOLD\n#ifndef MORECORE_CANNOT_TRIM\n#define DEFAULT_TRIM_THRESHOLD ((size_t)2U * (size_t)1024U * (size_t)1024U)\n#else   /* MORECORE_CANNOT_TRIM */\n#define DEFAULT_TRIM_THRESHOLD MAX_SIZE_T\n#endif  /* MORECORE_CANNOT_TRIM */\n#endif  /* DEFAULT_TRIM_THRESHOLD */\n#ifndef DEFAULT_MMAP_THRESHOLD\n#if HAVE_MMAP\n#define DEFAULT_MMAP_THRESHOLD ((size_t)256U * (size_t)1024U)\n#else   /* HAVE_MMAP */\n#define DEFAULT_MMAP_THRESHOLD MAX_SIZE_T\n#endif  /* HAVE_MMAP */\n#endif  /* DEFAULT_MMAP_THRESHOLD */\n#ifndef MAX_RELEASE_CHECK_RATE\n#if HAVE_MMAP\n#define MAX_RELEASE_CHECK_RATE 4095\n#else\n#define MAX_RELEASE_CHECK_RATE MAX_SIZE_T\n#endif /* HAVE_MMAP */\n#endif /* MAX_RELEASE_CHECK_RATE */\n#ifndef USE_BUILTIN_FFS\n#define USE_BUILTIN_FFS 0\n#endif  /* USE_BUILTIN_FFS */\n#ifndef USE_DEV_RANDOM\n#define USE_DEV_RANDOM 0\n#endif  /* USE_DEV_RANDOM */\n#ifndef NO_MALLINFO\n#define NO_MALLINFO 0\n#endif  /* NO_MALLINFO */\n#ifndef MALLINFO_FIELD_TYPE\n#define MALLINFO_FIELD_TYPE size_t\n#endif  /* MALLINFO_FIELD_TYPE */\n#ifndef NO_MALLOC_STATS\n#define NO_MALLOC_STATS 0\n#endif  /* NO_MALLOC_STATS */\n#ifndef NO_SEGMENT_TRAVERSAL\n#define NO_SEGMENT_TRAVERSAL 0\n#endif /* NO_SEGMENT_TRAVERSAL */\n\n/*\n  mallopt tuning options.  SVID/XPG defines four standard parameter\n  numbers for mallopt, normally defined in malloc.h.  None of these\n  are used in this malloc, so setting them has no effect. But this\n  malloc does support the following options.\n*/\n\n#define M_TRIM_THRESHOLD     (-1)\n#define M_GRANULARITY        (-2)\n#define M_MMAP_THRESHOLD     (-3)\n\n/* ------------------------ Mallinfo declarations ------------------------ */\n\n#if !NO_MALLINFO\n/*\n  This version of malloc supports the standard SVID/XPG mallinfo\n  routine that returns a struct containing usage properties and\n  statistics. It should work on any system that has a\n  /usr/include/malloc.h defining struct mallinfo.  The main\n  declaration needed is the mallinfo struct that is returned (by-copy)\n  by mallinfo().  The malloinfo struct contains a bunch of fields that\n  are not even meaningful in this version of malloc.  These fields are\n  are instead filled by mallinfo() with other numbers that might be of\n  interest.\n\n  HAVE_USR_INCLUDE_MALLOC_H should be set if you have a\n  /usr/include/malloc.h file that includes a declaration of struct\n  mallinfo.  If so, it is included; else a compliant version is\n  declared below.  These must be precisely the same for mallinfo() to\n  work.  The original SVID version of this struct, defined on most\n  systems with mallinfo, declares all fields as ints. But some others\n  define as unsigned long. If your system defines the fields using a\n  type of different width than listed here, you MUST #include your\n  system version and #define HAVE_USR_INCLUDE_MALLOC_H.\n*/\n\n/* #define HAVE_USR_INCLUDE_MALLOC_H */\n\n#ifdef HAVE_USR_INCLUDE_MALLOC_H\n#include \"/usr/include/malloc.h\"\n#else /* HAVE_USR_INCLUDE_MALLOC_H */\n#ifndef STRUCT_MALLINFO_DECLARED\n/* HP-UX (and others?) redefines mallinfo unless _STRUCT_MALLINFO is defined */\n#define _STRUCT_MALLINFO\n#define STRUCT_MALLINFO_DECLARED 1\nstruct mallinfo {\n  MALLINFO_FIELD_TYPE arena;    /* non-mmapped space allocated from system */\n  MALLINFO_FIELD_TYPE ordblks;  /* number of free chunks */\n  MALLINFO_FIELD_TYPE smblks;   /* always 0 */\n  MALLINFO_FIELD_TYPE hblks;    /* always 0 */\n  MALLINFO_FIELD_TYPE hblkhd;   /* space in mmapped regions */\n  MALLINFO_FIELD_TYPE usmblks;  /* maximum total allocated space */\n  MALLINFO_FIELD_TYPE fsmblks;  /* always 0 */\n  MALLINFO_FIELD_TYPE uordblks; /* total allocated space */\n  MALLINFO_FIELD_TYPE fordblks; /* total free space */\n  MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */\n};\n#endif /* STRUCT_MALLINFO_DECLARED */\n#endif /* HAVE_USR_INCLUDE_MALLOC_H */\n#endif /* NO_MALLINFO */\n\n/*\n  Try to persuade compilers to inline. The most critical functions for\n  inlining are defined as macros, so these aren't used for them.\n*/\n\n#ifndef FORCEINLINE\n  #if defined(__GNUC__)\n#define FORCEINLINE __inline __attribute__ ((always_inline))\n  #elif defined(_MSC_VER)\n    #define FORCEINLINE __forceinline\n  #endif\n#endif\n#ifndef NOINLINE\n  #if defined(__GNUC__)\n    #define NOINLINE __attribute__ ((noinline))\n  #elif defined(_MSC_VER)\n    #define NOINLINE __declspec(noinline)\n  #else\n    #define NOINLINE\n  #endif\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#ifndef FORCEINLINE\n #define FORCEINLINE inline\n#endif\n#endif /* __cplusplus */\n#ifndef FORCEINLINE\n #define FORCEINLINE\n#endif\n\n#if !ONLY_MSPACES\n\n/* ------------------- Declarations of public routines ------------------- */\n\n#ifndef USE_DL_PREFIX\n#define dlcalloc               calloc\n#define dlfree                 free\n#define dlmalloc               malloc\n#define dlmemalign             memalign\n#define dlposix_memalign       posix_memalign\n#define dlrealloc              realloc\n#define dlrealloc_in_place     realloc_in_place\n#define dlvalloc               valloc\n#define dlpvalloc              pvalloc\n#define dlmallinfo             mallinfo\n#define dlmallopt              mallopt\n#define dlmalloc_trim          malloc_trim\n#define dlmalloc_stats         malloc_stats\n#define dlmalloc_usable_size   malloc_usable_size\n#define dlmalloc_footprint     malloc_footprint\n#define dlmalloc_max_footprint malloc_max_footprint\n#define dlmalloc_footprint_limit malloc_footprint_limit\n#define dlmalloc_set_footprint_limit malloc_set_footprint_limit\n#define dlmalloc_inspect_all   malloc_inspect_all\n#define dlindependent_calloc   independent_calloc\n#define dlindependent_comalloc independent_comalloc\n#define dlbulk_free            bulk_free\n#endif /* USE_DL_PREFIX */\n\n/*\n  malloc(size_t n)\n  Returns a pointer to a newly allocated chunk of at least n bytes, or\n  null if no space is available, in which case errno is set to ENOMEM\n  on ANSI C systems.\n\n  If n is zero, malloc returns a minimum-sized chunk. (The minimum\n  size is 16 bytes on most 32bit systems, and 32 bytes on 64bit\n  systems.)  Note that size_t is an unsigned type, so calls with\n  arguments that would be negative if signed are interpreted as\n  requests for huge amounts of space, which will often fail. The\n  maximum supported value of n differs across systems, but is in all\n  cases less than the maximum representable value of a size_t.\n*/\nDLMALLOC_EXPORT void* dlmalloc(size_t);\n\n/*\n  free(void* p)\n  Releases the chunk of memory pointed to by p, that had been previously\n  allocated using malloc or a related routine such as realloc.\n  It has no effect if p is null. If p was not malloced or already\n  freed, free(p) will by default cause the current program to abort.\n*/\nDLMALLOC_EXPORT void  dlfree(void*);\n\n/*\n  calloc(size_t n_elements, size_t element_size);\n  Returns a pointer to n_elements * element_size bytes, with all locations\n  set to zero.\n*/\nDLMALLOC_EXPORT void* dlcalloc(size_t, size_t);\n\n/*\n  realloc(void* p, size_t n)\n  Returns a pointer to a chunk of size n that contains the same data\n  as does chunk p up to the minimum of (n, p's size) bytes, or null\n  if no space is available.\n\n  The returned pointer may or may not be the same as p. The algorithm\n  prefers extending p in most cases when possible, otherwise it\n  employs the equivalent of a malloc-copy-free sequence.\n\n  If p is null, realloc is equivalent to malloc.\n\n  If space is not available, realloc returns null, errno is set (if on\n  ANSI) and p is NOT freed.\n\n  if n is for fewer bytes than already held by p, the newly unused\n  space is lopped off and freed if possible.  realloc with a size\n  argument of zero (re)allocates a minimum-sized chunk.\n\n  The old unix realloc convention of allowing the last-free'd chunk\n  to be used as an argument to realloc is not supported.\n*/\nDLMALLOC_EXPORT void* dlrealloc(void*, size_t);\n\n/*\n  realloc_in_place(void* p, size_t n)\n  Resizes the space allocated for p to size n, only if this can be\n  done without moving p (i.e., only if there is adjacent space\n  available if n is greater than p's current allocated size, or n is\n  less than or equal to p's size). This may be used instead of plain\n  realloc if an alternative allocation strategy is needed upon failure\n  to expand space; for example, reallocation of a buffer that must be\n  memory-aligned or cleared. You can use realloc_in_place to trigger\n  these alternatives only when needed.\n\n  Returns p if successful; otherwise null.\n*/\nDLMALLOC_EXPORT void* dlrealloc_in_place(void*, size_t);\n\n/*\n  memalign(size_t alignment, size_t n);\n  Returns a pointer to a newly allocated chunk of n bytes, aligned\n  in accord with the alignment argument.\n\n  The alignment argument should be a power of two. If the argument is\n  not a power of two, the nearest greater power is used.\n  8-byte alignment is guaranteed by normal malloc calls, so don't\n  bother calling memalign with an argument of 8 or less.\n\n  Overreliance on memalign is a sure way to fragment space.\n*/\nDLMALLOC_EXPORT void* dlmemalign(size_t, size_t);\n\n/*\n  int posix_memalign(void** pp, size_t alignment, size_t n);\n  Allocates a chunk of n bytes, aligned in accord with the alignment\n  argument. Differs from memalign only in that it (1) assigns the\n  allocated memory to *pp rather than returning it, (2) fails and\n  returns EINVAL if the alignment is not a power of two (3) fails and\n  returns ENOMEM if memory cannot be allocated.\n*/\nDLMALLOC_EXPORT int dlposix_memalign(void**, size_t, size_t);\n\n/*\n  valloc(size_t n);\n  Equivalent to memalign(pagesize, n), where pagesize is the page\n  size of the system. If the pagesize is unknown, 4096 is used.\n*/\nDLMALLOC_EXPORT void* dlvalloc(size_t);\n\n/*\n  mallopt(int parameter_number, int parameter_value)\n  Sets tunable parameters The format is to provide a\n  (parameter-number, parameter-value) pair.  mallopt then sets the\n  corresponding parameter to the argument value if it can (i.e., so\n  long as the value is meaningful), and returns 1 if successful else\n  0.  To workaround the fact that mallopt is specified to use int,\n  not size_t parameters, the value -1 is specially treated as the\n  maximum unsigned size_t value.\n\n  SVID/XPG/ANSI defines four standard param numbers for mallopt,\n  normally defined in malloc.h.  None of these are use in this malloc,\n  so setting them has no effect. But this malloc also supports other\n  options in mallopt. See below for details.  Briefly, supported\n  parameters are as follows (listed defaults are for \"typical\"\n  configurations).\n\n  Symbol            param #  default    allowed param values\n  M_TRIM_THRESHOLD     -1   2*1024*1024   any   (-1 disables)\n  M_GRANULARITY        -2     page size   any power of 2 >= page size\n  M_MMAP_THRESHOLD     -3      256*1024   any   (or 0 if no MMAP support)\n*/\nDLMALLOC_EXPORT int dlmallopt(int, int);\n\n/*\n  malloc_footprint();\n  Returns the number of bytes obtained from the system.  The total\n  number of bytes allocated by malloc, realloc etc., is less than this\n  value. Unlike mallinfo, this function returns only a precomputed\n  result, so can be called frequently to monitor memory consumption.\n  Even if locks are otherwise defined, this function does not use them,\n  so results might not be up to date.\n*/\nDLMALLOC_EXPORT size_t dlmalloc_footprint(void);\n\n/*\n  malloc_max_footprint();\n  Returns the maximum number of bytes obtained from the system. This\n  value will be greater than current footprint if deallocated space\n  has been reclaimed by the system. The peak number of bytes allocated\n  by malloc, realloc etc., is less than this value. Unlike mallinfo,\n  this function returns only a precomputed result, so can be called\n  frequently to monitor memory consumption.  Even if locks are\n  otherwise defined, this function does not use them, so results might\n  not be up to date.\n*/\nDLMALLOC_EXPORT size_t dlmalloc_max_footprint(void);\n\n/*\n  malloc_footprint_limit();\n  Returns the number of bytes that the heap is allowed to obtain from\n  the system, returning the last value returned by\n  malloc_set_footprint_limit, or the maximum size_t value if\n  never set. The returned value reflects a permission. There is no\n  guarantee that this number of bytes can actually be obtained from\n  the system.\n*/\nDLMALLOC_EXPORT size_t dlmalloc_footprint_limit();\n\n/*\n  malloc_set_footprint_limit();\n  Sets the maximum number of bytes to obtain from the system, causing\n  failure returns from malloc and related functions upon attempts to\n  exceed this value. The argument value may be subject to page\n  rounding to an enforceable limit; this actual value is returned.\n  Using an argument of the maximum possible size_t effectively\n  disables checks. If the argument is less than or equal to the\n  current malloc_footprint, then all future allocations that require\n  additional system memory will fail. However, invocation cannot\n  retroactively deallocate existing used memory.\n*/\nDLMALLOC_EXPORT size_t dlmalloc_set_footprint_limit(size_t bytes);\n\n#if MALLOC_INSPECT_ALL\n/*\n  malloc_inspect_all(void(*handler)(void *start,\n                                    void *end,\n                                    size_t used_bytes,\n                                    void* callback_arg),\n                      void* arg);\n  Traverses the heap and calls the given handler for each managed\n  region, skipping all bytes that are (or may be) used for bookkeeping\n  purposes.  Traversal does not include include chunks that have been\n  directly memory mapped. Each reported region begins at the start\n  address, and continues up to but not including the end address.  The\n  first used_bytes of the region contain allocated data. If\n  used_bytes is zero, the region is unallocated. The handler is\n  invoked with the given callback argument. If locks are defined, they\n  are held during the entire traversal. It is a bad idea to invoke\n  other malloc functions from within the handler.\n\n  For example, to count the number of in-use chunks with size greater\n  than 1000, you could write:\n  static int count = 0;\n  void count_chunks(void* start, void* end, size_t used, void* arg) {\n    if (used >= 1000) ++count;\n  }\n  then:\n    malloc_inspect_all(count_chunks, NULL);\n\n  malloc_inspect_all is compiled only if MALLOC_INSPECT_ALL is defined.\n*/\nDLMALLOC_EXPORT void dlmalloc_inspect_all(void(*handler)(void*, void *, size_t, void*),\n                           void* arg);\n\n#endif /* MALLOC_INSPECT_ALL */\n\n#if !NO_MALLINFO\n/*\n  mallinfo()\n  Returns (by copy) a struct containing various summary statistics:\n\n  arena:     current total non-mmapped bytes allocated from system\n  ordblks:   the number of free chunks\n  smblks:    always zero.\n  hblks:     current number of mmapped regions\n  hblkhd:    total bytes held in mmapped regions\n  usmblks:   the maximum total allocated space. This will be greater\n                than current total if trimming has occurred.\n  fsmblks:   always zero\n  uordblks:  current total allocated space (normal or mmapped)\n  fordblks:  total free space\n  keepcost:  the maximum number of bytes that could ideally be released\n               back to system via malloc_trim. (\"ideally\" means that\n               it ignores page restrictions etc.)\n\n  Because these fields are ints, but internal bookkeeping may\n  be kept as longs, the reported values may wrap around zero and\n  thus be inaccurate.\n*/\nDLMALLOC_EXPORT struct mallinfo dlmallinfo(void);\n#endif /* NO_MALLINFO */\n\n/*\n  independent_calloc(size_t n_elements, size_t element_size, void* chunks[]);\n\n  independent_calloc is similar to calloc, but instead of returning a\n  single cleared space, it returns an array of pointers to n_elements\n  independent elements that can hold contents of size elem_size, each\n  of which starts out cleared, and can be independently freed,\n  realloc'ed etc. The elements are guaranteed to be adjacently\n  allocated (this is not guaranteed to occur with multiple callocs or\n  mallocs), which may also improve cache locality in some\n  applications.\n\n  The \"chunks\" argument is optional (i.e., may be null, which is\n  probably the most typical usage). If it is null, the returned array\n  is itself dynamically allocated and should also be freed when it is\n  no longer needed. Otherwise, the chunks array must be of at least\n  n_elements in length. It is filled in with the pointers to the\n  chunks.\n\n  In either case, independent_calloc returns this pointer array, or\n  null if the allocation failed.  If n_elements is zero and \"chunks\"\n  is null, it returns a chunk representing an array with zero elements\n  (which should be freed if not wanted).\n\n  Each element must be freed when it is no longer needed. This can be\n  done all at once using bulk_free.\n\n  independent_calloc simplifies and speeds up implementations of many\n  kinds of pools.  It may also be useful when constructing large data\n  structures that initially have a fixed number of fixed-sized nodes,\n  but the number is not known at compile time, and some of the nodes\n  may later need to be freed. For example:\n\n  struct Node { int item; struct Node* next; };\n\n  struct Node* build_list() {\n    struct Node** pool;\n    int n = read_number_of_nodes_needed();\n    if (n <= 0) return 0;\n    pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0);\n    if (pool == 0) die();\n    // organize into a linked list...\n    struct Node* first = pool[0];\n    for (i = 0; i < n-1; ++i)\n      pool[i]->next = pool[i+1];\n    free(pool);     // Can now free the array (or not, if it is needed later)\n    return first;\n  }\n*/\nDLMALLOC_EXPORT void** dlindependent_calloc(size_t, size_t, void**);\n\n/*\n  independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]);\n\n  independent_comalloc allocates, all at once, a set of n_elements\n  chunks with sizes indicated in the \"sizes\" array.    It returns\n  an array of pointers to these elements, each of which can be\n  independently freed, realloc'ed etc. The elements are guaranteed to\n  be adjacently allocated (this is not guaranteed to occur with\n  multiple callocs or mallocs), which may also improve cache locality\n  in some applications.\n\n  The \"chunks\" argument is optional (i.e., may be null). If it is null\n  the returned array is itself dynamically allocated and should also\n  be freed when it is no longer needed. Otherwise, the chunks array\n  must be of at least n_elements in length. It is filled in with the\n  pointers to the chunks.\n\n  In either case, independent_comalloc returns this pointer array, or\n  null if the allocation failed.  If n_elements is zero and chunks is\n  null, it returns a chunk representing an array with zero elements\n  (which should be freed if not wanted).\n\n  Each element must be freed when it is no longer needed. This can be\n  done all at once using bulk_free.\n\n  independent_comallac differs from independent_calloc in that each\n  element may have a different size, and also that it does not\n  automatically clear elements.\n\n  independent_comalloc can be used to speed up allocation in cases\n  where several structs or objects must always be allocated at the\n  same time.  For example:\n\n  struct Head { ... }\n  struct Foot { ... }\n\n  void send_message(char* msg) {\n    int msglen = strlen(msg);\n    size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) };\n    void* chunks[3];\n    if (independent_comalloc(3, sizes, chunks) == 0)\n      die();\n    struct Head* head = (struct Head*)(chunks[0]);\n    char*        body = (char*)(chunks[1]);\n    struct Foot* foot = (struct Foot*)(chunks[2]);\n    // ...\n  }\n\n  In general though, independent_comalloc is worth using only for\n  larger values of n_elements. For small values, you probably won't\n  detect enough difference from series of malloc calls to bother.\n\n  Overuse of independent_comalloc can increase overall memory usage,\n  since it cannot reuse existing noncontiguous small chunks that\n  might be available for some of the elements.\n*/\nDLMALLOC_EXPORT void** dlindependent_comalloc(size_t, size_t*, void**);\n\n/*\n  bulk_free(void* array[], size_t n_elements)\n  Frees and clears (sets to null) each non-null pointer in the given\n  array.  This is likely to be faster than freeing them one-by-one.\n  If footers are used, pointers that have been allocated in different\n  mspaces are not freed or cleared, and the count of all such pointers\n  is returned.  For large arrays of pointers with poor locality, it\n  may be worthwhile to sort this array before calling bulk_free.\n*/\nDLMALLOC_EXPORT size_t  dlbulk_free(void**, size_t n_elements);\n\n/*\n  pvalloc(size_t n);\n  Equivalent to valloc(minimum-page-that-holds(n)), that is,\n  round up n to nearest pagesize.\n */\nDLMALLOC_EXPORT void*  dlpvalloc(size_t);\n\n/*\n  malloc_trim(size_t pad);\n\n  If possible, gives memory back to the system (via negative arguments\n  to sbrk) if there is unused memory at the `high' end of the malloc\n  pool or in unused MMAP segments. You can call this after freeing\n  large blocks of memory to potentially reduce the system-level memory\n  requirements of a program. However, it cannot guarantee to reduce\n  memory. Under some allocation patterns, some large free blocks of\n  memory will be locked between two used chunks, so they cannot be\n  given back to the system.\n\n  The `pad' argument to malloc_trim represents the amount of free\n  trailing space to leave untrimmed. If this argument is zero, only\n  the minimum amount of memory to maintain internal data structures\n  will be left. Non-zero arguments can be supplied to maintain enough\n  trailing space to service future expected allocations without having\n  to re-obtain memory from the system.\n\n  Malloc_trim returns 1 if it actually released any memory, else 0.\n*/\nDLMALLOC_EXPORT int  dlmalloc_trim(size_t);\n\n/*\n  malloc_stats();\n  Prints on stderr the amount of space obtained from the system (both\n  via sbrk and mmap), the maximum amount (which may be more than\n  current if malloc_trim and/or munmap got called), and the current\n  number of bytes allocated via malloc (or realloc, etc) but not yet\n  freed. Note that this is the number of bytes allocated, not the\n  number requested. It will be larger than the number requested\n  because of alignment and bookkeeping overhead. Because it includes\n  alignment wastage as being in use, this figure may be greater than\n  zero even when no user-level chunks are allocated.\n\n  The reported current and maximum system memory can be inaccurate if\n  a program makes other calls to system memory allocation functions\n  (normally sbrk) outside of malloc.\n\n  malloc_stats prints only the most commonly interesting statistics.\n  More information can be obtained by calling mallinfo.\n*/\nDLMALLOC_EXPORT void  dlmalloc_stats(void);\n\n/*\n  malloc_usable_size(void* p);\n\n  Returns the number of bytes you can actually use in\n  an allocated chunk, which may be more than you requested (although\n  often not) due to alignment and minimum size constraints.\n  You can use this many bytes without worrying about\n  overwriting other allocated objects. This is not a particularly great\n  programming practice. malloc_usable_size can be more useful in\n  debugging and assertions, for example:\n\n  p = malloc(n);\n  assert(malloc_usable_size(p) >= 256);\n*/\nsize_t dlmalloc_usable_size(void*);\n\n#endif /* ONLY_MSPACES */\n\n#if MSPACES\n\n/*\n  create_mspace creates and returns a new independent space with the\n  given initial capacity, or, if 0, the default granularity size.  It\n  returns null if there is no system memory available to create the\n  space.  If argument locked is non-zero, the space uses a separate\n  lock to control access. The capacity of the space will grow\n  dynamically as needed to service mspace_malloc requests.  You can\n  control the sizes of incremental increases of this space by\n  compiling with a different DEFAULT_GRANULARITY or dynamically\n  setting with mallopt(M_GRANULARITY, value).\n*/\nDLMALLOC_EXPORT mspace create_mspace(size_t capacity, int locked);\n\n/*\n  destroy_mspace destroys the given space, and attempts to return all\n  of its memory back to the system, returning the total number of\n  bytes freed. After destruction, the results of access to all memory\n  used by the space become undefined.\n*/\nDLMALLOC_EXPORT size_t destroy_mspace(mspace msp);\n\n/*\n  create_mspace_with_base uses the memory supplied as the initial base\n  of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this\n  space is used for bookkeeping, so the capacity must be at least this\n  large. (Otherwise 0 is returned.) When this initial space is\n  exhausted, additional memory will be obtained from the system.\n  Destroying this space will deallocate all additionally allocated\n  space (if possible) but not the initial base.\n*/\nDLMALLOC_EXPORT mspace create_mspace_with_base(void* base, size_t capacity, int locked);\n\n/*\n  mspace_track_large_chunks controls whether requests for large chunks\n  are allocated in their own untracked mmapped regions, separate from\n  others in this mspace. By default large chunks are not tracked,\n  which reduces fragmentation. However, such chunks are not\n  necessarily released to the system upon destroy_mspace.  Enabling\n  tracking by setting to true may increase fragmentation, but avoids\n  leakage when relying on destroy_mspace to release all memory\n  allocated using this space.  The function returns the previous\n  setting.\n*/\nDLMALLOC_EXPORT int mspace_track_large_chunks(mspace msp, int enable);\n\n\n/*\n  mspace_malloc behaves as malloc, but operates within\n  the given space.\n*/\nDLMALLOC_EXPORT void* mspace_malloc(mspace msp, size_t bytes);\n\n/*\n  mspace_free behaves as free, but operates within\n  the given space.\n\n  If compiled with FOOTERS==1, mspace_free is not actually needed.\n  free may be called instead of mspace_free because freed chunks from\n  any space are handled by their originating spaces.\n*/\nDLMALLOC_EXPORT void mspace_free(mspace msp, void* mem);\n\n/*\n  mspace_realloc behaves as realloc, but operates within\n  the given space.\n\n  If compiled with FOOTERS==1, mspace_realloc is not actually\n  needed.  realloc may be called instead of mspace_realloc because\n  realloced chunks from any space are handled by their originating\n  spaces.\n*/\nDLMALLOC_EXPORT void* mspace_realloc(mspace msp, void* mem, size_t newsize);\n\n/*\n  mspace_calloc behaves as calloc, but operates within\n  the given space.\n*/\nDLMALLOC_EXPORT void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size);\n\n/*\n  mspace_memalign behaves as memalign, but operates within\n  the given space.\n*/\nDLMALLOC_EXPORT void* mspace_memalign(mspace msp, size_t alignment, size_t bytes);\n\n/*\n  mspace_independent_calloc behaves as independent_calloc, but\n  operates within the given space.\n*/\nDLMALLOC_EXPORT void** mspace_independent_calloc(mspace msp, size_t n_elements,\n                                 size_t elem_size, void* chunks[]);\n\n/*\n  mspace_independent_comalloc behaves as independent_comalloc, but\n  operates within the given space.\n*/\nDLMALLOC_EXPORT void** mspace_independent_comalloc(mspace msp, size_t n_elements,\n                                   size_t sizes[], void* chunks[]);\n\n/*\n  mspace_footprint() returns the number of bytes obtained from the\n  system for this space.\n*/\nDLMALLOC_EXPORT size_t mspace_footprint(mspace msp);\n\n/*\n  mspace_max_footprint() returns the peak number of bytes obtained from the\n  system for this space.\n*/\nDLMALLOC_EXPORT size_t mspace_max_footprint(mspace msp);\n\n\n#if !NO_MALLINFO\n/*\n  mspace_mallinfo behaves as mallinfo, but reports properties of\n  the given space.\n*/\nDLMALLOC_EXPORT struct mallinfo mspace_mallinfo(mspace msp);\n#endif /* NO_MALLINFO */\n\n/*\n  malloc_usable_size(void* p) behaves the same as malloc_usable_size;\n*/\nDLMALLOC_EXPORT size_t mspace_usable_size(const void* mem);\n\n/*\n  mspace_malloc_stats behaves as malloc_stats, but reports\n  properties of the given space.\n*/\nDLMALLOC_EXPORT void mspace_malloc_stats(mspace msp);\n\n/*\n  mspace_trim behaves as malloc_trim, but\n  operates within the given space.\n*/\nDLMALLOC_EXPORT int mspace_trim(mspace msp, size_t pad);\n\n/*\n  An alias for mallopt.\n*/\nDLMALLOC_EXPORT int mspace_mallopt(int, int);\n\n#endif /* MSPACES */\n\n#ifdef __cplusplus\n}  /* end of extern \"C\" */\n#endif /* __cplusplus */\n\n/*\n  ========================================================================\n  To make a fully customizable malloc.h header file, cut everything\n  above this line, put into file malloc.h, edit to suit, and #include it\n  on the next line, as well as in programs that use this malloc.\n  ========================================================================\n*/\n\n/* #include \"malloc.h\" */\n\n/*------------------------------ internal #includes ---------------------- */\n\n#ifdef _MSC_VER\n#pragma warning( disable : 4146 ) /* no \"unsigned\" warnings */\n#endif /* _MSC_VER */\n#if !NO_MALLOC_STATS\n#include <stdio.h>       /* for printing in malloc_stats */\n#endif /* NO_MALLOC_STATS */\n#ifndef LACKS_ERRNO_H\n#include <errno.h>       /* for MALLOC_FAILURE_ACTION */\n#endif /* LACKS_ERRNO_H */\n#ifdef DEBUG\n#if ABORT_ON_ASSERT_FAILURE\n#undef assert\n#define assert(x) if(!(x)) ABORT\n#else /* ABORT_ON_ASSERT_FAILURE */\n#include <assert.h>\n#endif /* ABORT_ON_ASSERT_FAILURE */\n#else  /* DEBUG */\n#ifndef assert\n#define assert(x)\n#endif\n#define DEBUG 0\n#endif /* DEBUG */\n#if !defined(WIN32) && !defined(LACKS_TIME_H)\n#include <time.h>        /* for magic initialization */\n#endif /* WIN32 */\n#ifndef LACKS_STDLIB_H\n#include <stdlib.h>      /* for abort() */\n#endif /* LACKS_STDLIB_H */\n#ifndef LACKS_STRING_H\n#include <string.h>      /* for memset etc */\n#endif  /* LACKS_STRING_H */\n#if USE_BUILTIN_FFS\n#ifndef LACKS_STRINGS_H\n#include <strings.h>     /* for ffs */\n#endif /* LACKS_STRINGS_H */\n#endif /* USE_BUILTIN_FFS */\n#if HAVE_MMAP\n#ifndef LACKS_SYS_MMAN_H\n/* On some versions of linux, mremap decl in mman.h needs __USE_GNU set */\n#if (defined(linux) && !defined(__USE_GNU))\n#define __USE_GNU 1\n#include <sys/mman.h>    /* for mmap */\n#undef __USE_GNU\n#else\n#include <sys/mman.h>    /* for mmap */\n#endif /* linux */\n#endif /* LACKS_SYS_MMAN_H */\n#ifndef LACKS_FCNTL_H\n#include <fcntl.h>\n#endif /* LACKS_FCNTL_H */\n#endif /* HAVE_MMAP */\n#ifndef LACKS_UNISTD_H\n#include <unistd.h>     /* for sbrk, sysconf */\n#else /* LACKS_UNISTD_H */\n#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__)\nextern void*     sbrk(ptrdiff_t);\n#endif /* FreeBSD etc */\n#endif /* LACKS_UNISTD_H */\n\n/* Declarations for locking */\n#if USE_LOCKS\n#ifndef WIN32\n#if defined (__SVR4) && defined (__sun)  /* solaris */\n#include <thread.h>\n#elif !defined(LACKS_SCHED_H)\n#include <sched.h>\n#endif /* solaris or LACKS_SCHED_H */\n#if (defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0) || !USE_SPIN_LOCKS\n#include <pthread.h>\n#endif /* USE_RECURSIVE_LOCKS ... */\n#elif defined(_MSC_VER)\n#ifndef _M_AMD64\n/* These are already defined on AMD64 builds */\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\nLONG __cdecl _InterlockedCompareExchange(LONG volatile *Dest, LONG Exchange, LONG Comp);\nLONG __cdecl _InterlockedExchange(LONG volatile *Target, LONG Value);\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* _M_AMD64 */\n#pragma intrinsic (_InterlockedCompareExchange)\n#pragma intrinsic (_InterlockedExchange)\n#define interlockedcompareexchange _InterlockedCompareExchange\n#define interlockedexchange _InterlockedExchange\n#elif defined(WIN32) && defined(__GNUC__)\n#define interlockedcompareexchange(a, b, c) __sync_val_compare_and_swap(a, c, b)\n#define interlockedexchange __sync_lock_test_and_set\n#endif /* Win32 */\n#else /* USE_LOCKS */\n#endif /* USE_LOCKS */\n\n#ifndef LOCK_AT_FORK\n#define LOCK_AT_FORK 0\n#endif\n\n/* Declarations for bit scanning on win32 */\n#if defined(_MSC_VER) && _MSC_VER>=1300\n#ifndef BitScanForward /* Try to avoid pulling in WinNT.h */\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\nunsigned char _BitScanForward(unsigned long *index, unsigned long mask);\nunsigned char _BitScanReverse(unsigned long *index, unsigned long mask);\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#define BitScanForward _BitScanForward\n#define BitScanReverse _BitScanReverse\n#pragma intrinsic(_BitScanForward)\n#pragma intrinsic(_BitScanReverse)\n#endif /* BitScanForward */\n#endif /* defined(_MSC_VER) && _MSC_VER>=1300 */\n\n#ifndef WIN32\n#ifndef malloc_getpagesize\n#  ifdef _SC_PAGESIZE         /* some SVR4 systems omit an underscore */\n#    ifndef _SC_PAGE_SIZE\n#      define _SC_PAGE_SIZE _SC_PAGESIZE\n#    endif\n#  endif\n#  ifdef _SC_PAGE_SIZE\n#    define malloc_getpagesize sysconf(_SC_PAGE_SIZE)\n#  else\n#    if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE)\n       extern size_t getpagesize();\n#      define malloc_getpagesize getpagesize()\n#    else\n#      ifdef WIN32 /* use supplied emulation of getpagesize */\n#        define malloc_getpagesize getpagesize()\n#      else\n#        ifndef LACKS_SYS_PARAM_H\n#          include <sys/param.h>\n#        endif\n#        ifdef EXEC_PAGESIZE\n#          define malloc_getpagesize EXEC_PAGESIZE\n#        else\n#          ifdef NBPG\n#            ifndef CLSIZE\n#              define malloc_getpagesize NBPG\n#            else\n#              define malloc_getpagesize (NBPG * CLSIZE)\n#            endif\n#          else\n#            ifdef NBPC\n#              define malloc_getpagesize NBPC\n#            else\n#              ifdef PAGESIZE\n#                define malloc_getpagesize PAGESIZE\n#              else /* just guess */\n#                define malloc_getpagesize ((size_t)4096U)\n#              endif\n#            endif\n#          endif\n#        endif\n#      endif\n#    endif\n#  endif\n#endif\n#endif\n\n/* ------------------- size_t and alignment properties -------------------- */\n\n/* The byte and bit size of a size_t */\n#define SIZE_T_SIZE         (sizeof(size_t))\n#define SIZE_T_BITSIZE      (sizeof(size_t) << 3)\n\n/* Some constants coerced to size_t */\n/* Annoying but necessary to avoid errors on some platforms */\n#define SIZE_T_ZERO         ((size_t)0)\n#define SIZE_T_ONE          ((size_t)1)\n#define SIZE_T_TWO          ((size_t)2)\n#define SIZE_T_FOUR         ((size_t)4)\n#define TWO_SIZE_T_SIZES    (SIZE_T_SIZE<<1)\n#define FOUR_SIZE_T_SIZES   (SIZE_T_SIZE<<2)\n#define SIX_SIZE_T_SIZES    (FOUR_SIZE_T_SIZES+TWO_SIZE_T_SIZES)\n#define HALF_MAX_SIZE_T     (MAX_SIZE_T / 2U)\n\n/* The bit mask value corresponding to MALLOC_ALIGNMENT */\n#define CHUNK_ALIGN_MASK    (MALLOC_ALIGNMENT - SIZE_T_ONE)\n\n/* True if address a has acceptable alignment */\n#define is_aligned(A)       (((size_t)((A)) & (CHUNK_ALIGN_MASK)) == 0)\n\n/* the number of bytes to offset an address to align it */\n#define align_offset(A)\\\n ((((size_t)(A) & CHUNK_ALIGN_MASK) == 0)? 0 :\\\n  ((MALLOC_ALIGNMENT - ((size_t)(A) & CHUNK_ALIGN_MASK)) & CHUNK_ALIGN_MASK))\n\n/* -------------------------- MMAP preliminaries ------------------------- */\n\n/*\n   If HAVE_MORECORE or HAVE_MMAP are false, we just define calls and\n   checks to fail so compiler optimizer can delete code rather than\n   using so many \"#if\"s.\n*/\n\n\n/* MORECORE and MMAP must return MFAIL on failure */\n#define MFAIL                ((void*)(MAX_SIZE_T))\n#define CMFAIL               ((char*)(MFAIL)) /* defined for convenience */\n\n#if HAVE_MMAP\n\n#ifndef WIN32\n#define MUNMAP_DEFAULT(a, s)  munmap((a), (s))\n#define MMAP_PROT            (PROT_READ|PROT_WRITE)\n#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)\n#define MAP_ANONYMOUS        MAP_ANON\n#endif /* MAP_ANON */\n#ifdef MAP_ANONYMOUS\n#define MMAP_FLAGS           (MAP_PRIVATE|MAP_ANONYMOUS)\n#define MMAP_DEFAULT(s)       mmap(0, (s), MMAP_PROT, MMAP_FLAGS, -1, 0)\n#else /* MAP_ANONYMOUS */\n/*\n   Nearly all versions of mmap support MAP_ANONYMOUS, so the following\n   is unlikely to be needed, but is supplied just in case.\n*/\n#define MMAP_FLAGS           (MAP_PRIVATE)\nstatic int dev_zero_fd = -1; /* Cached file descriptor for /dev/zero. */\n#define MMAP_DEFAULT(s) ((dev_zero_fd < 0) ? \\\n           (dev_zero_fd = open(\"/dev/zero\", O_RDWR), \\\n            mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) : \\\n            mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0))\n#endif /* MAP_ANONYMOUS */\n\n#define DIRECT_MMAP_DEFAULT(s) MMAP_DEFAULT(s)\n\n#else /* WIN32 */\n\n/* Win32 MMAP via VirtualAlloc */\nstatic FORCEINLINE void* win32mmap(size_t size) {\n  void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);\n  return (ptr != 0)? ptr: MFAIL;\n}\n\n/* For direct MMAP, use MEM_TOP_DOWN to minimize interference */\nstatic FORCEINLINE void* win32direct_mmap(size_t size) {\n  void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN,\n                           PAGE_READWRITE);\n  return (ptr != 0)? ptr: MFAIL;\n}\n\n/* This function supports releasing coalesed segments */\nstatic FORCEINLINE int win32munmap(void* ptr, size_t size) {\n  MEMORY_BASIC_INFORMATION minfo;\n  char* cptr = (char*)ptr;\n  while (size) {\n    if (VirtualQuery(cptr, &minfo, sizeof(minfo)) == 0)\n      return -1;\n    if (minfo.BaseAddress != cptr || minfo.AllocationBase != cptr ||\n        minfo.State != MEM_COMMIT || minfo.RegionSize > size)\n      return -1;\n    if (VirtualFree(cptr, 0, MEM_RELEASE) == 0)\n      return -1;\n    cptr += minfo.RegionSize;\n    size -= minfo.RegionSize;\n  }\n  return 0;\n}\n\n#define MMAP_DEFAULT(s)             win32mmap(s)\n#define MUNMAP_DEFAULT(a, s)        win32munmap((a), (s))\n#define DIRECT_MMAP_DEFAULT(s)      win32direct_mmap(s)\n#endif /* WIN32 */\n#endif /* HAVE_MMAP */\n\n#if HAVE_MREMAP\n#ifndef WIN32\n#define MREMAP_DEFAULT(addr, osz, nsz, mv) mremap((addr), (osz), (nsz), (mv))\n#endif /* WIN32 */\n#endif /* HAVE_MREMAP */\n\n/**\n * Define CALL_MORECORE\n */\n#if HAVE_MORECORE\n    #ifdef MORECORE\n        #define CALL_MORECORE(S)    MORECORE(S)\n    #else  /* MORECORE */\n        #define CALL_MORECORE(S)    MORECORE_DEFAULT(S)\n    #endif /* MORECORE */\n#else  /* HAVE_MORECORE */\n    #define CALL_MORECORE(S)        MFAIL\n#endif /* HAVE_MORECORE */\n\n/**\n * Define CALL_MMAP/CALL_MUNMAP/CALL_DIRECT_MMAP\n */\n#if HAVE_MMAP\n    #define USE_MMAP_BIT            (SIZE_T_ONE)\n\n    #ifdef MMAP\n        #define CALL_MMAP(s)        MMAP(s)\n    #else /* MMAP */\n        #define CALL_MMAP(s)        MMAP_DEFAULT(s)\n    #endif /* MMAP */\n    #ifdef MUNMAP\n        #define CALL_MUNMAP(a, s)   MUNMAP((a), (s))\n    #else /* MUNMAP */\n        #define CALL_MUNMAP(a, s)   MUNMAP_DEFAULT((a), (s))\n    #endif /* MUNMAP */\n    #ifdef DIRECT_MMAP\n        #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s)\n    #else /* DIRECT_MMAP */\n        #define CALL_DIRECT_MMAP(s) DIRECT_MMAP_DEFAULT(s)\n    #endif /* DIRECT_MMAP */\n#else  /* HAVE_MMAP */\n    #define USE_MMAP_BIT            (SIZE_T_ZERO)\n\n    #define MMAP(s)                 MFAIL\n    #define MUNMAP(a, s)            (-1)\n    #define DIRECT_MMAP(s)          MFAIL\n    #define CALL_DIRECT_MMAP(s)     DIRECT_MMAP(s)\n    #define CALL_MMAP(s)            MMAP(s)\n    #define CALL_MUNMAP(a, s)       MUNMAP((a), (s))\n#endif /* HAVE_MMAP */\n\n/**\n * Define CALL_MREMAP\n */\n#if HAVE_MMAP && HAVE_MREMAP\n    #ifdef MREMAP\n        #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP((addr), (osz), (nsz), (mv))\n    #else /* MREMAP */\n        #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP_DEFAULT((addr), (osz), (nsz), (mv))\n    #endif /* MREMAP */\n#else  /* HAVE_MMAP && HAVE_MREMAP */\n    #define CALL_MREMAP(addr, osz, nsz, mv)     MFAIL\n#endif /* HAVE_MMAP && HAVE_MREMAP */\n\n/* mstate bit set if continguous morecore disabled or failed */\n#define USE_NONCONTIGUOUS_BIT (4U)\n\n/* segment bit set in create_mspace_with_base */\n#define EXTERN_BIT            (8U)\n\n\n/* --------------------------- Lock preliminaries ------------------------ */\n\n/*\n  When locks are defined, there is one global lock, plus\n  one per-mspace lock.\n\n  The global lock_ensures that mparams.magic and other unique\n  mparams values are initialized only once. It also protects\n  sequences of calls to MORECORE.  In many cases sys_alloc requires\n  two calls, that should not be interleaved with calls by other\n  threads.  This does not protect against direct calls to MORECORE\n  by other threads not using this lock, so there is still code to\n  cope the best we can on interference.\n\n  Per-mspace locks surround calls to malloc, free, etc.\n  By default, locks are simple non-reentrant mutexes.\n\n  Because lock-protected regions generally have bounded times, it is\n  OK to use the supplied simple spinlocks. Spinlocks are likely to\n  improve performance for lightly contended applications, but worsen\n  performance under heavy contention.\n\n  if USE_LOCKS is > 1, the definitions of lock routines here are\n  bypassed, in which case you will need to define the type MLOCK_T,\n  and at least INITIAL_LOCK, DESTROY_LOCK, ACQUIRE_LOCK, RELEASE_LOCK\n  and TRY_LOCK.  You must also declare a\n    static MLOCK_T malloc_global_mutex = { initialization values };.\n\n*/\n\n#if !USE_LOCKS\n#define USE_LOCK_BIT               (0U)\n#define INITIAL_LOCK(l)            (0)\n#define DESTROY_LOCK(l)            (0)\n#define ACQUIRE_MALLOC_GLOBAL_LOCK()\n#define RELEASE_MALLOC_GLOBAL_LOCK()\n\n#else\n#if USE_LOCKS > 1\n/* -----------------------  User-defined locks ------------------------ */\n/* Define your own lock implementation here */\n/* #define INITIAL_LOCK(lk)  ... */\n/* #define DESTROY_LOCK(lk)  ... */\n/* #define ACQUIRE_LOCK(lk)  ... */\n/* #define RELEASE_LOCK(lk)  ... */\n/* #define TRY_LOCK(lk) ... */\n/* static MLOCK_T malloc_global_mutex = ... */\n\n#elif USE_SPIN_LOCKS\n\n/* First, define CAS_LOCK and CLEAR_LOCK on ints */\n/* Note CAS_LOCK defined to return 0 on success */\n\n#if defined(__GNUC__)&& (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1))\n#define CAS_LOCK(sl)     __sync_lock_test_and_set(sl, 1)\n#define CLEAR_LOCK(sl)   __sync_lock_release(sl)\n\n#elif (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)))\n/* Custom spin locks for older gcc on x86 */\nstatic FORCEINLINE int x86_cas_lock(int *sl) {\n  int ret;\n  int val = 1;\n  int cmp = 0;\n  __asm__ __volatile__  (\"lock; cmpxchgl %1, %2\"\n                         : \"=a\" (ret)\n                         : \"r\" (val), \"m\" (*(sl)), \"0\"(cmp)\n                         : \"memory\", \"cc\");\n  return ret;\n}\n\nstatic FORCEINLINE void x86_clear_lock(int* sl) {\n  assert(*sl != 0);\n  int prev = 0;\n  int ret;\n  __asm__ __volatile__ (\"lock; xchgl %0, %1\"\n                        : \"=r\" (ret)\n                        : \"m\" (*(sl)), \"0\"(prev)\n                        : \"memory\");\n}\n\n#define CAS_LOCK(sl)     x86_cas_lock(sl)\n#define CLEAR_LOCK(sl)   x86_clear_lock(sl)\n\n#else /* Win32 MSC */\n#define CAS_LOCK(sl)     interlockedexchange(sl, (LONG)1)\n#define CLEAR_LOCK(sl)   interlockedexchange (sl, (LONG)0)\n\n#endif /* ... gcc spins locks ... */\n\n/* How to yield for a spin lock */\n#define SPINS_PER_YIELD       63\n#if defined(_MSC_VER)\n#define SLEEP_EX_DURATION     50 /* delay for yield/sleep */\n#define SPIN_LOCK_YIELD  SleepEx(SLEEP_EX_DURATION, FALSE)\n#elif defined (__SVR4) && defined (__sun) /* solaris */\n#define SPIN_LOCK_YIELD   thr_yield();\n#elif !defined(LACKS_SCHED_H)\n#define SPIN_LOCK_YIELD   sched_yield();\n#else\n#define SPIN_LOCK_YIELD\n#endif /* ... yield ... */\n\n#if !defined(USE_RECURSIVE_LOCKS) || USE_RECURSIVE_LOCKS == 0\n/* Plain spin locks use single word (embedded in malloc_states) */\nstatic int spin_acquire_lock(int *sl) {\n  int spins = 0;\n  while (*(volatile int *)sl != 0 || CAS_LOCK(sl)) {\n    if ((++spins & SPINS_PER_YIELD) == 0) {\n      SPIN_LOCK_YIELD;\n    }\n  }\n  return 0;\n}\n\n#define MLOCK_T               int\n#define TRY_LOCK(sl)          !CAS_LOCK(sl)\n#define RELEASE_LOCK(sl)      CLEAR_LOCK(sl)\n#define ACQUIRE_LOCK(sl)      (CAS_LOCK(sl)? spin_acquire_lock(sl) : 0)\n#define INITIAL_LOCK(sl)      (*sl = 0)\n#define DESTROY_LOCK(sl)      (0)\nstatic MLOCK_T malloc_global_mutex = 0;\n\n#else /* USE_RECURSIVE_LOCKS */\n/* types for lock owners */\n#ifdef WIN32\n#define THREAD_ID_T           DWORD\n#define CURRENT_THREAD        GetCurrentThreadId()\n#define EQ_OWNER(X,Y)         ((X) == (Y))\n#else\n/*\n  Note: the following assume that pthread_t is a type that can be\n  initialized to (casted) zero. If this is not the case, you will need to\n  somehow redefine these or not use spin locks.\n*/\n#define THREAD_ID_T           pthread_t\n#define CURRENT_THREAD        pthread_self()\n#define EQ_OWNER(X,Y)         pthread_equal(X, Y)\n#endif\n\nstruct malloc_recursive_lock {\n  int sl;\n  unsigned int c;\n  THREAD_ID_T threadid;\n};\n\n#define MLOCK_T  struct malloc_recursive_lock\nstatic MLOCK_T malloc_global_mutex = { 0, 0, (THREAD_ID_T)0};\n\nstatic FORCEINLINE void recursive_release_lock(MLOCK_T *lk) {\n  assert(lk->sl != 0);\n  if (--lk->c == 0) {\n    CLEAR_LOCK(&lk->sl);\n  }\n}\n\nstatic FORCEINLINE int recursive_acquire_lock(MLOCK_T *lk) {\n  THREAD_ID_T mythreadid = CURRENT_THREAD;\n  int spins = 0;\n  for (;;) {\n    if (*((volatile int *)(&lk->sl)) == 0) {\n      if (!CAS_LOCK(&lk->sl)) {\n        lk->threadid = mythreadid;\n        lk->c = 1;\n        return 0;\n      }\n    }\n    else if (EQ_OWNER(lk->threadid, mythreadid)) {\n      ++lk->c;\n      return 0;\n    }\n    if ((++spins & SPINS_PER_YIELD) == 0) {\n      SPIN_LOCK_YIELD;\n    }\n  }\n}\n\nstatic FORCEINLINE int recursive_try_lock(MLOCK_T *lk) {\n  THREAD_ID_T mythreadid = CURRENT_THREAD;\n  if (*((volatile int *)(&lk->sl)) == 0) {\n    if (!CAS_LOCK(&lk->sl)) {\n      lk->threadid = mythreadid;\n      lk->c = 1;\n      return 1;\n    }\n  }\n  else if (EQ_OWNER(lk->threadid, mythreadid)) {\n    ++lk->c;\n    return 1;\n  }\n  return 0;\n}\n\n#define RELEASE_LOCK(lk)      recursive_release_lock(lk)\n#define TRY_LOCK(lk)          recursive_try_lock(lk)\n#define ACQUIRE_LOCK(lk)      recursive_acquire_lock(lk)\n#define INITIAL_LOCK(lk)      ((lk)->threadid = (THREAD_ID_T)0, (lk)->sl = 0, (lk)->c = 0)\n#define DESTROY_LOCK(lk)      (0)\n#endif /* USE_RECURSIVE_LOCKS */\n\n#elif defined(WIN32) /* Win32 critical sections */\n#define MLOCK_T               CRITICAL_SECTION\n#define ACQUIRE_LOCK(lk)      (EnterCriticalSection(lk), 0)\n#define RELEASE_LOCK(lk)      LeaveCriticalSection(lk)\n#define TRY_LOCK(lk)          TryEnterCriticalSection(lk)\n#define INITIAL_LOCK(lk)      (!InitializeCriticalSectionAndSpinCount((lk), 0x80000000|4000))\n#define DESTROY_LOCK(lk)      (DeleteCriticalSection(lk), 0)\n#define NEED_GLOBAL_LOCK_INIT\n\nstatic MLOCK_T malloc_global_mutex;\nstatic volatile LONG malloc_global_mutex_status;\n\n/* Use spin loop to initialize global lock */\nstatic void init_malloc_global_mutex() {\n  for (;;) {\n    long stat = malloc_global_mutex_status;\n    if (stat > 0)\n      return;\n    /* transition to < 0 while initializing, then to > 0) */\n    if (stat == 0 &&\n        interlockedcompareexchange(&malloc_global_mutex_status, (LONG)-1, (LONG)0) == 0) {\n      InitializeCriticalSection(&malloc_global_mutex);\n      interlockedexchange(&malloc_global_mutex_status, (LONG)1);\n      return;\n    }\n    SleepEx(0, FALSE);\n  }\n}\n\n#else /* pthreads-based locks */\n#define MLOCK_T               pthread_mutex_t\n#define ACQUIRE_LOCK(lk)      pthread_mutex_lock(lk)\n#define RELEASE_LOCK(lk)      pthread_mutex_unlock(lk)\n#define TRY_LOCK(lk)          (!pthread_mutex_trylock(lk))\n#define INITIAL_LOCK(lk)      pthread_init_lock(lk)\n#define DESTROY_LOCK(lk)      pthread_mutex_destroy(lk)\n\n#if defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0 && defined(linux) && !defined(PTHREAD_MUTEX_RECURSIVE)\n/* Cope with old-style linux recursive lock initialization by adding */\n/* skipped internal declaration from pthread.h */\nextern int pthread_mutexattr_setkind_np __P ((pthread_mutexattr_t *__attr,\n                                              int __kind));\n#define PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP\n#define pthread_mutexattr_settype(x,y) pthread_mutexattr_setkind_np(x,y)\n#endif /* USE_RECURSIVE_LOCKS ... */\n\nstatic MLOCK_T malloc_global_mutex = PTHREAD_MUTEX_INITIALIZER;\n\nstatic int pthread_init_lock (MLOCK_T *lk) {\n  pthread_mutexattr_t attr;\n  if (pthread_mutexattr_init(&attr)) return 1;\n#if defined(USE_RECURSIVE_LOCKS) && USE_RECURSIVE_LOCKS != 0\n  if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) return 1;\n#endif\n  if (pthread_mutex_init(lk, &attr)) return 1;\n  if (pthread_mutexattr_destroy(&attr)) return 1;\n  return 0;\n}\n\n#endif /* ... lock types ... */\n\n/* Common code for all lock types */\n#define USE_LOCK_BIT               (2U)\n\n#ifndef ACQUIRE_MALLOC_GLOBAL_LOCK\n#define ACQUIRE_MALLOC_GLOBAL_LOCK()  ACQUIRE_LOCK(&malloc_global_mutex);\n#endif\n\n#ifndef RELEASE_MALLOC_GLOBAL_LOCK\n#define RELEASE_MALLOC_GLOBAL_LOCK()  RELEASE_LOCK(&malloc_global_mutex);\n#endif\n\n#endif /* USE_LOCKS */\n\n/* -----------------------  Chunk representations ------------------------ */\n\n/*\n  (The following includes lightly edited explanations by Colin Plumb.)\n\n  The malloc_chunk declaration below is misleading (but accurate and\n  necessary).  It declares a \"view\" into memory allowing access to\n  necessary fields at known offsets from a given base.\n\n  Chunks of memory are maintained using a `boundary tag' method as\n  originally described by Knuth.  (See the paper by Paul Wilson\n  ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a survey of such\n  techniques.)  Sizes of free chunks are stored both in the front of\n  each chunk and at the end.  This makes consolidating fragmented\n  chunks into bigger chunks fast.  The head fields also hold bits\n  representing whether chunks are free or in use.\n\n  Here are some pictures to make it clearer.  They are \"exploded\" to\n  show that the state of a chunk can be thought of as extending from\n  the high 31 bits of the head field of its header through the\n  prev_foot and PINUSE_BIT bit of the following chunk header.\n\n  A chunk that's in use looks like:\n\n   chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n           | Size of previous chunk (if P = 0)                             |\n           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P|\n         | Size of this chunk                                         1| +-+\n   mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n         |                                                               |\n         +-                                                             -+\n         |                                                               |\n         +-                                                             -+\n         |                                                               :\n         +-      size - sizeof(size_t) available payload bytes          -+\n         :                                                               |\n chunk-> +-                                                             -+\n         |                                                               |\n         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1|\n       | Size of next chunk (may or may not be in use)               | +-+\n mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\n    And if it's free, it looks like this:\n\n   chunk-> +-                                                             -+\n           | User payload (must be in use, or we would have merged!)       |\n           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P|\n         | Size of this chunk                                         0| +-+\n   mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n         | Next pointer                                                  |\n         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n         | Prev pointer                                                  |\n         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n         |                                                               :\n         +-      size - sizeof(struct chunk) unused bytes               -+\n         :                                                               |\n chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n         | Size of this chunk                                            |\n         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0|\n       | Size of next chunk (must be in use, or we would have merged)| +-+\n mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n       |                                                               :\n       +- User payload                                                -+\n       :                                                               |\n       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                                                                     |0|\n                                                                     +-+\n  Note that since we always merge adjacent free chunks, the chunks\n  adjacent to a free chunk must be in use.\n\n  Given a pointer to a chunk (which can be derived trivially from the\n  payload pointer) we can, in O(1) time, find out whether the adjacent\n  chunks are free, and if so, unlink them from the lists that they\n  are on and merge them with the current chunk.\n\n  Chunks always begin on even word boundaries, so the mem portion\n  (which is returned to the user) is also on an even word boundary, and\n  thus at least double-word aligned.\n\n  The P (PINUSE_BIT) bit, stored in the unused low-order bit of the\n  chunk size (which is always a multiple of two words), is an in-use\n  bit for the *previous* chunk.  If that bit is *clear*, then the\n  word before the current chunk size contains the previous chunk\n  size, and can be used to find the front of the previous chunk.\n  The very first chunk allocated always has this bit set, preventing\n  access to non-existent (or non-owned) memory. If pinuse is set for\n  any given chunk, then you CANNOT determine the size of the\n  previous chunk, and might even get a memory addressing fault when\n  trying to do so.\n\n  The C (CINUSE_BIT) bit, stored in the unused second-lowest bit of\n  the chunk size redundantly records whether the current chunk is\n  inuse (unless the chunk is mmapped). This redundancy enables usage\n  checks within free and realloc, and reduces indirection when freeing\n  and consolidating chunks.\n\n  Each freshly allocated chunk must have both cinuse and pinuse set.\n  That is, each allocated chunk borders either a previously allocated\n  and still in-use chunk, or the base of its memory arena. This is\n  ensured by making all allocations from the `lowest' part of any\n  found chunk.  Further, no free chunk physically borders another one,\n  so each free chunk is known to be preceded and followed by either\n  inuse chunks or the ends of memory.\n\n  Note that the `foot' of the current chunk is actually represented\n  as the prev_foot of the NEXT chunk. This makes it easier to\n  deal with alignments etc but can be very confusing when trying\n  to extend or adapt this code.\n\n  The exceptions to all this are\n\n     1. The special chunk `top' is the top-most available chunk (i.e.,\n        the one bordering the end of available memory). It is treated\n        specially.  Top is never included in any bin, is used only if\n        no other chunk is available, and is released back to the\n        system if it is very large (see M_TRIM_THRESHOLD).  In effect,\n        the top chunk is treated as larger (and thus less well\n        fitting) than any other available chunk.  The top chunk\n        doesn't update its trailing size field since there is no next\n        contiguous chunk that would have to index off it. However,\n        space is still allocated for it (TOP_FOOT_SIZE) to enable\n        separation or merging when space is extended.\n\n     3. Chunks allocated via mmap, have both cinuse and pinuse bits\n        cleared in their head fields.  Because they are allocated\n        one-by-one, each must carry its own prev_foot field, which is\n        also used to hold the offset this chunk has within its mmapped\n        region, which is needed to preserve alignment. Each mmapped\n        chunk is trailed by the first two fields of a fake next-chunk\n        for sake of usage checks.\n\n*/\n\nstruct malloc_chunk {\n  size_t               prev_foot;  /* Size of previous chunk (if free).  */\n  size_t               head;       /* Size and inuse bits. */\n  struct malloc_chunk* fd;         /* double links -- used only if free. */\n  struct malloc_chunk* bk;\n};\n\ntypedef struct malloc_chunk  mchunk;\ntypedef struct malloc_chunk* mchunkptr;\ntypedef struct malloc_chunk* sbinptr;  /* The type of bins of chunks */\ntypedef unsigned int bindex_t;         /* Described below */\ntypedef unsigned int binmap_t;         /* Described below */\ntypedef unsigned int flag_t;           /* The type of various bit flag sets */\n\n/* ------------------- Chunks sizes and alignments ----------------------- */\n\n#define MCHUNK_SIZE         (sizeof(mchunk))\n\n#if FOOTERS\n#define CHUNK_OVERHEAD      (TWO_SIZE_T_SIZES)\n#else /* FOOTERS */\n#define CHUNK_OVERHEAD      (SIZE_T_SIZE)\n#endif /* FOOTERS */\n\n/* MMapped chunks need a second word of overhead ... */\n#define MMAP_CHUNK_OVERHEAD (TWO_SIZE_T_SIZES)\n/* ... and additional padding for fake next-chunk at foot */\n#define MMAP_FOOT_PAD       (FOUR_SIZE_T_SIZES)\n\n/* The smallest size we can malloc is an aligned minimal chunk */\n#define MIN_CHUNK_SIZE\\\n  ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK)\n\n/* conversion from malloc headers to user pointers, and back */\n#define chunk2mem(p)        ((void*)((char*)(p)       + TWO_SIZE_T_SIZES))\n#define mem2chunk(mem)      ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES))\n/* chunk associated with aligned address A */\n#define align_as_chunk(A)   (mchunkptr)((A) + align_offset(chunk2mem(A)))\n\n/* Bounds on request (not chunk) sizes. */\n#define MAX_REQUEST         ((-MIN_CHUNK_SIZE) << 2)\n#define MIN_REQUEST         (MIN_CHUNK_SIZE - CHUNK_OVERHEAD - SIZE_T_ONE)\n\n/* pad request bytes into a usable size */\n#define pad_request(req) \\\n   (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK)\n\n/* pad request, checking for minimum (but not maximum) */\n#define request2size(req) \\\n  (((req) < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(req))\n\n\n/* ------------------ Operations on head and foot fields ----------------- */\n\n/*\n  The head field of a chunk is or'ed with PINUSE_BIT when previous\n  adjacent chunk in use, and or'ed with CINUSE_BIT if this chunk is in\n  use, unless mmapped, in which case both bits are cleared.\n\n  FLAG4_BIT is not used by this malloc, but might be useful in extensions.\n*/\n\n#define PINUSE_BIT          (SIZE_T_ONE)\n#define CINUSE_BIT          (SIZE_T_TWO)\n#define FLAG4_BIT           (SIZE_T_FOUR)\n#define INUSE_BITS          (PINUSE_BIT|CINUSE_BIT)\n#define FLAG_BITS           (PINUSE_BIT|CINUSE_BIT|FLAG4_BIT)\n\n/* Head value for fenceposts */\n#define FENCEPOST_HEAD      (INUSE_BITS|SIZE_T_SIZE)\n\n/* extraction of fields from head words */\n#define cinuse(p)           ((p)->head & CINUSE_BIT)\n#define pinuse(p)           ((p)->head & PINUSE_BIT)\n#define flag4inuse(p)       ((p)->head & FLAG4_BIT)\n#define is_inuse(p)         (((p)->head & INUSE_BITS) != PINUSE_BIT)\n#define is_mmapped(p)       (((p)->head & INUSE_BITS) == 0)\n\n#define chunksize(p)        ((p)->head & ~(FLAG_BITS))\n\n#define clear_pinuse(p)     ((p)->head &= ~PINUSE_BIT)\n#define set_flag4(p)        ((p)->head |= FLAG4_BIT)\n#define clear_flag4(p)      ((p)->head &= ~FLAG4_BIT)\n\n/* Treat space at ptr +/- offset as a chunk */\n#define chunk_plus_offset(p, s)  ((mchunkptr)(((char*)(p)) + (s)))\n#define chunk_minus_offset(p, s) ((mchunkptr)(((char*)(p)) - (s)))\n\n/* Ptr to next or previous physical malloc_chunk. */\n#define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->head & ~FLAG_BITS)))\n#define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_foot) ))\n\n/* extract next chunk's pinuse bit */\n#define next_pinuse(p)  ((next_chunk(p)->head) & PINUSE_BIT)\n\n/* Get/set size at footer */\n#define get_foot(p, s)  (((mchunkptr)((char*)(p) + (s)))->prev_foot)\n#define set_foot(p, s)  (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s))\n\n/* Set size, pinuse bit, and foot */\n#define set_size_and_pinuse_of_free_chunk(p, s)\\\n  ((p)->head = (s|PINUSE_BIT), set_foot(p, s))\n\n/* Set size, pinuse bit, foot, and clear next pinuse */\n#define set_free_with_pinuse(p, s, n)\\\n  (clear_pinuse(n), set_size_and_pinuse_of_free_chunk(p, s))\n\n/* Get the internal overhead associated with chunk p */\n#define overhead_for(p)\\\n (is_mmapped(p)? MMAP_CHUNK_OVERHEAD : CHUNK_OVERHEAD)\n\n/* Return true if malloced space is not necessarily cleared */\n#if MMAP_CLEARS\n#define calloc_must_clear(p) (!is_mmapped(p))\n#else /* MMAP_CLEARS */\n#define calloc_must_clear(p) (1)\n#endif /* MMAP_CLEARS */\n\n/* ---------------------- Overlaid data structures ----------------------- */\n\n/*\n  When chunks are not in use, they are treated as nodes of either\n  lists or trees.\n\n  \"Small\"  chunks are stored in circular doubly-linked lists, and look\n  like this:\n\n    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n            |             Size of previous chunk                            |\n            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n    `head:' |             Size of chunk, in bytes                         |P|\n      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n            |             Forward pointer to next chunk in list             |\n            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n            |             Back pointer to previous chunk in list            |\n            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n            |             Unused space (may be 0 bytes long)                .\n            .                                                               .\n            .                                                               |\nnextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n    `foot:' |             Size of chunk, in bytes                           |\n            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\n  Larger chunks are kept in a form of bitwise digital trees (aka\n  tries) keyed on chunksizes.  Because malloc_tree_chunks are only for\n  free chunks greater than 256 bytes, their size doesn't impose any\n  constraints on user chunk sizes.  Each node looks like:\n\n    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n            |             Size of previous chunk                            |\n            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n    `head:' |             Size of chunk, in bytes                         |P|\n      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n            |             Forward pointer to next chunk of same size        |\n            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n            |             Back pointer to previous chunk of same size       |\n            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n            |             Pointer to left child (child[0])                  |\n            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n            |             Pointer to right child (child[1])                 |\n            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n            |             Pointer to parent                                 |\n            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n            |             bin index of this chunk                           |\n            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n            |             Unused space                                      .\n            .                                                               |\nnextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n    `foot:' |             Size of chunk, in bytes                           |\n            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\n  Each tree holding treenodes is a tree of unique chunk sizes.  Chunks\n  of the same size are arranged in a circularly-linked list, with only\n  the oldest chunk (the next to be used, in our FIFO ordering)\n  actually in the tree.  (Tree members are distinguished by a non-null\n  parent pointer.)  If a chunk with the same size an an existing node\n  is inserted, it is linked off the existing node using pointers that\n  work in the same way as fd/bk pointers of small chunks.\n\n  Each tree contains a power of 2 sized range of chunk sizes (the\n  smallest is 0x100 <= x < 0x180), which is is divided in half at each\n  tree level, with the chunks in the smaller half of the range (0x100\n  <= x < 0x140 for the top nose) in the left subtree and the larger\n  half (0x140 <= x < 0x180) in the right subtree.  This is, of course,\n  done by inspecting individual bits.\n\n  Using these rules, each node's left subtree contains all smaller\n  sizes than its right subtree.  However, the node at the root of each\n  subtree has no particular ordering relationship to either.  (The\n  dividing line between the subtree sizes is based on trie relation.)\n  If we remove the last chunk of a given size from the interior of the\n  tree, we need to replace it with a leaf node.  The tree ordering\n  rules permit a node to be replaced by any leaf below it.\n\n  The smallest chunk in a tree (a common operation in a best-fit\n  allocator) can be found by walking a path to the leftmost leaf in\n  the tree.  Unlike a usual binary tree, where we follow left child\n  pointers until we reach a null, here we follow the right child\n  pointer any time the left one is null, until we reach a leaf with\n  both child pointers null. The smallest chunk in the tree will be\n  somewhere along that path.\n\n  The worst case number of steps to add, find, or remove a node is\n  bounded by the number of bits differentiating chunks within\n  bins. Under current bin calculations, this ranges from 6 up to 21\n  (for 32 bit sizes) or up to 53 (for 64 bit sizes). The typical case\n  is of course much better.\n*/\n\nstruct malloc_tree_chunk {\n  /* The first four fields must be compatible with malloc_chunk */\n  size_t                    prev_foot;\n  size_t                    head;\n  struct malloc_tree_chunk* fd;\n  struct malloc_tree_chunk* bk;\n\n  struct malloc_tree_chunk* child[2];\n  struct malloc_tree_chunk* parent;\n  bindex_t                  index;\n};\n\ntypedef struct malloc_tree_chunk  tchunk;\ntypedef struct malloc_tree_chunk* tchunkptr;\ntypedef struct malloc_tree_chunk* tbinptr; /* The type of bins of trees */\n\n/* A little helper macro for trees */\n#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0] : (t)->child[1])\n\n/* ----------------------------- Segments -------------------------------- */\n\n/*\n  Each malloc space may include non-contiguous segments, held in a\n  list headed by an embedded malloc_segment record representing the\n  top-most space. Segments also include flags holding properties of\n  the space. Large chunks that are directly allocated by mmap are not\n  included in this list. They are instead independently created and\n  destroyed without otherwise keeping track of them.\n\n  Segment management mainly comes into play for spaces allocated by\n  MMAP.  Any call to MMAP might or might not return memory that is\n  adjacent to an existing segment.  MORECORE normally contiguously\n  extends the current space, so this space is almost always adjacent,\n  which is simpler and faster to deal with. (This is why MORECORE is\n  used preferentially to MMAP when both are available -- see\n  sys_alloc.)  When allocating using MMAP, we don't use any of the\n  hinting mechanisms (inconsistently) supported in various\n  implementations of unix mmap, or distinguish reserving from\n  committing memory. Instead, we just ask for space, and exploit\n  contiguity when we get it.  It is probably possible to do\n  better than this on some systems, but no general scheme seems\n  to be significantly better.\n\n  Management entails a simpler variant of the consolidation scheme\n  used for chunks to reduce fragmentation -- new adjacent memory is\n  normally prepended or appended to an existing segment. However,\n  there are limitations compared to chunk consolidation that mostly\n  reflect the fact that segment processing is relatively infrequent\n  (occurring only when getting memory from system) and that we\n  don't expect to have huge numbers of segments:\n\n  * Segments are not indexed, so traversal requires linear scans.  (It\n    would be possible to index these, but is not worth the extra\n    overhead and complexity for most programs on most platforms.)\n  * New segments are only appended to old ones when holding top-most\n    memory; if they cannot be prepended to others, they are held in\n    different segments.\n\n  Except for the top-most segment of an mstate, each segment record\n  is kept at the tail of its segment. Segments are added by pushing\n  segment records onto the list headed by &mstate.seg for the\n  containing mstate.\n\n  Segment flags control allocation/merge/deallocation policies:\n  * If EXTERN_BIT set, then we did not allocate this segment,\n    and so should not try to deallocate or merge with others.\n    (This currently holds only for the initial segment passed\n    into create_mspace_with_base.)\n  * If USE_MMAP_BIT set, the segment may be merged with\n    other surrounding mmapped segments and trimmed/de-allocated\n    using munmap.\n  * If neither bit is set, then the segment was obtained using\n    MORECORE so can be merged with surrounding MORECORE'd segments\n    and deallocated/trimmed using MORECORE with negative arguments.\n*/\n\nstruct malloc_segment {\n  char*        base;             /* base address */\n  size_t       size;             /* allocated size */\n  struct malloc_segment* next;   /* ptr to next segment */\n  flag_t       sflags;           /* mmap and extern flag */\n};\n\n#define is_mmapped_segment(S)  ((S)->sflags & USE_MMAP_BIT)\n#define is_extern_segment(S)   ((S)->sflags & EXTERN_BIT)\n\ntypedef struct malloc_segment  msegment;\ntypedef struct malloc_segment* msegmentptr;\n\n/* ---------------------------- malloc_state ----------------------------- */\n\n/*\n   A malloc_state holds all of the bookkeeping for a space.\n   The main fields are:\n\n  Top\n    The topmost chunk of the currently active segment. Its size is\n    cached in topsize.  The actual size of topmost space is\n    topsize+TOP_FOOT_SIZE, which includes space reserved for adding\n    fenceposts and segment records if necessary when getting more\n    space from the system.  The size at which to autotrim top is\n    cached from mparams in trim_check, except that it is disabled if\n    an autotrim fails.\n\n  Designated victim (dv)\n    This is the preferred chunk for servicing small requests that\n    don't have exact fits.  It is normally the chunk split off most\n    recently to service another small request.  Its size is cached in\n    dvsize. The link fields of this chunk are not maintained since it\n    is not kept in a bin.\n\n  SmallBins\n    An array of bin headers for free chunks.  These bins hold chunks\n    with sizes less than MIN_LARGE_SIZE bytes. Each bin contains\n    chunks of all the same size, spaced 8 bytes apart.  To simplify\n    use in double-linked lists, each bin header acts as a malloc_chunk\n    pointing to the real first node, if it exists (else pointing to\n    itself).  This avoids special-casing for headers.  But to avoid\n    waste, we allocate only the fd/bk pointers of bins, and then use\n    repositioning tricks to treat these as the fields of a chunk.\n\n  TreeBins\n    Treebins are pointers to the roots of trees holding a range of\n    sizes. There are 2 equally spaced treebins for each power of two\n    from TREE_SHIFT to TREE_SHIFT+16. The last bin holds anything\n    larger.\n\n  Bin maps\n    There is one bit map for small bins (\"smallmap\") and one for\n    treebins (\"treemap).  Each bin sets its bit when non-empty, and\n    clears the bit when empty.  Bit operations are then used to avoid\n    bin-by-bin searching -- nearly all \"search\" is done without ever\n    looking at bins that won't be selected.  The bit maps\n    conservatively use 32 bits per map word, even if on 64bit system.\n    For a good description of some of the bit-based techniques used\n    here, see Henry S. Warren Jr's book \"Hacker's Delight\" (and\n    supplement at http://hackersdelight.org/). Many of these are\n    intended to reduce the branchiness of paths through malloc etc, as\n    well as to reduce the number of memory locations read or written.\n\n  Segments\n    A list of segments headed by an embedded malloc_segment record\n    representing the initial space.\n\n  Address check support\n    The least_addr field is the least address ever obtained from\n    MORECORE or MMAP. Attempted frees and reallocs of any address less\n    than this are trapped (unless INSECURE is defined).\n\n  Magic tag\n    A cross-check field that should always hold same value as mparams.magic.\n\n  Max allowed footprint\n    The maximum allowed bytes to allocate from system (zero means no limit)\n\n  Flags\n    Bits recording whether to use MMAP, locks, or contiguous MORECORE\n\n  Statistics\n    Each space keeps track of current and maximum system memory\n    obtained via MORECORE or MMAP.\n\n  Trim support\n    Fields holding the amount of unused topmost memory that should trigger\n    trimming, and a counter to force periodic scanning to release unused\n    non-topmost segments.\n\n  Locking\n    if USE_LOCKS is defined, the \"mutex\" lock is acquired and released\n    around every public call using this mspace.\n\n  Extension support\n    A void* pointer and a size_t field that can be used to help implement\n    extensions to this malloc.\n*/\n\n/* Bin types, widths and sizes */\n#define NSMALLBINS        (32U)\n#define NTREEBINS         (32U)\n#define SMALLBIN_SHIFT    (3U)\n#define SMALLBIN_WIDTH    (SIZE_T_ONE << SMALLBIN_SHIFT)\n#define TREEBIN_SHIFT     (8U)\n#define MIN_LARGE_SIZE    (SIZE_T_ONE << TREEBIN_SHIFT)\n#define MAX_SMALL_SIZE    (MIN_LARGE_SIZE - SIZE_T_ONE)\n#define MAX_SMALL_REQUEST (MAX_SMALL_SIZE - CHUNK_ALIGN_MASK - CHUNK_OVERHEAD)\n\nstruct malloc_state {\n  binmap_t   smallmap;\n  binmap_t   treemap;\n  size_t     dvsize;\n  size_t     topsize;\n  char*      least_addr;\n  mchunkptr  dv;\n  mchunkptr  top;\n  size_t     trim_check;\n  size_t     release_checks;\n  size_t     magic;\n  mchunkptr  smallbins[(NSMALLBINS+1)*2];\n  tbinptr    treebins[NTREEBINS];\n  size_t     footprint;\n  size_t     max_footprint;\n  size_t     footprint_limit; /* zero means no limit */\n  flag_t     mflags;\n#if USE_LOCKS\n  MLOCK_T    mutex;     /* locate lock among fields that rarely change */\n#endif /* USE_LOCKS */\n  msegment   seg;\n  void*      extp;      /* Unused but available for extensions */\n  size_t     exts;\n};\n\ntypedef struct malloc_state*    mstate;\n\n/* ------------- Global malloc_state and malloc_params ------------------- */\n\n/*\n  malloc_params holds global properties, including those that can be\n  dynamically set using mallopt. There is a single instance, mparams,\n  initialized in init_mparams. Note that the non-zeroness of \"magic\"\n  also serves as an initialization flag.\n*/\n\nstruct malloc_params {\n  size_t magic;\n  size_t page_size;\n  size_t granularity;\n  size_t mmap_threshold;\n  size_t trim_threshold;\n  flag_t default_mflags;\n};\n\nstatic struct malloc_params mparams;\n\n/* Ensure mparams initialized */\n#define ensure_initialization() (void)(mparams.magic != 0 || init_mparams())\n\n#if !ONLY_MSPACES\n\n/* The global malloc_state used for all non-\"mspace\" calls */\nstatic struct malloc_state _gm_;\n#define gm                 (&_gm_)\n#define is_global(M)       ((M) == &_gm_)\n\n#endif /* !ONLY_MSPACES */\n\n#define is_initialized(M)  ((M)->top != 0)\n\n/* -------------------------- system alloc setup ------------------------- */\n\n/* Operations on mflags */\n\n#define use_lock(M)           ((M)->mflags &   USE_LOCK_BIT)\n#define enable_lock(M)        ((M)->mflags |=  USE_LOCK_BIT)\n#if USE_LOCKS\n#define disable_lock(M)       ((M)->mflags &= ~USE_LOCK_BIT)\n#else\n#define disable_lock(M)\n#endif\n\n#define use_mmap(M)           ((M)->mflags &   USE_MMAP_BIT)\n#define enable_mmap(M)        ((M)->mflags |=  USE_MMAP_BIT)\n#if HAVE_MMAP\n#define disable_mmap(M)       ((M)->mflags &= ~USE_MMAP_BIT)\n#else\n#define disable_mmap(M)\n#endif\n\n#define use_noncontiguous(M)  ((M)->mflags &   USE_NONCONTIGUOUS_BIT)\n#define disable_contiguous(M) ((M)->mflags |=  USE_NONCONTIGUOUS_BIT)\n\n#define set_lock(M,L)\\\n ((M)->mflags = (L)?\\\n  ((M)->mflags | USE_LOCK_BIT) :\\\n  ((M)->mflags & ~USE_LOCK_BIT))\n\n/* page-align a size */\n#define page_align(S)\\\n (((S) + (mparams.page_size - SIZE_T_ONE)) & ~(mparams.page_size - SIZE_T_ONE))\n\n/* granularity-align a size */\n#define granularity_align(S)\\\n  (((S) + (mparams.granularity - SIZE_T_ONE))\\\n   & ~(mparams.granularity - SIZE_T_ONE))\n\n\n/* For mmap, use granularity alignment on windows, else page-align */\n#ifdef WIN32\n#define mmap_align(S) granularity_align(S)\n#else\n#define mmap_align(S) page_align(S)\n#endif\n\n/* For sys_alloc, enough padding to ensure can malloc request on success */\n#define SYS_ALLOC_PADDING (TOP_FOOT_SIZE + MALLOC_ALIGNMENT)\n\n#define is_page_aligned(S)\\\n   (((size_t)(S) & (mparams.page_size - SIZE_T_ONE)) == 0)\n#define is_granularity_aligned(S)\\\n   (((size_t)(S) & (mparams.granularity - SIZE_T_ONE)) == 0)\n\n/*  True if segment S holds address A */\n#define segment_holds(S, A)\\\n  ((char*)(A) >= S->base && (char*)(A) < S->base + S->size)\n\n/* Return segment holding given address */\nstatic msegmentptr segment_holding(mstate m, char* addr) {\n  msegmentptr sp = &m->seg;\n  for (;;) {\n    if (addr >= sp->base && addr < sp->base + sp->size)\n      return sp;\n    if ((sp = sp->next) == 0)\n      return 0;\n  }\n}\n\n/* Return true if segment contains a segment link */\nstatic int has_segment_link(mstate m, msegmentptr ss) {\n  msegmentptr sp = &m->seg;\n  for (;;) {\n    if ((char*)sp >= ss->base && (char*)sp < ss->base + ss->size)\n      return 1;\n    if ((sp = sp->next) == 0)\n      return 0;\n  }\n}\n\n#ifndef MORECORE_CANNOT_TRIM\n#define should_trim(M,s)  ((s) > (M)->trim_check)\n#else  /* MORECORE_CANNOT_TRIM */\n#define should_trim(M,s)  (0)\n#endif /* MORECORE_CANNOT_TRIM */\n\n/*\n  TOP_FOOT_SIZE is padding at the end of a segment, including space\n  that may be needed to place segment records and fenceposts when new\n  noncontiguous segments are added.\n*/\n#define TOP_FOOT_SIZE\\\n  (align_offset(chunk2mem(0))+pad_request(sizeof(struct malloc_segment))+MIN_CHUNK_SIZE)\n\n\n/* -------------------------------  Hooks -------------------------------- */\n\n/*\n  PREACTION should be defined to return 0 on success, and nonzero on\n  failure. If you are not using locking, you can redefine these to do\n  anything you like.\n*/\n\n#if USE_LOCKS\n#define PREACTION(M)  ((use_lock(M))? ACQUIRE_LOCK(&(M)->mutex) : 0)\n#define POSTACTION(M) { if (use_lock(M)) RELEASE_LOCK(&(M)->mutex); }\n#else /* USE_LOCKS */\n\n#ifndef PREACTION\n#define PREACTION(M) (0)\n#endif  /* PREACTION */\n\n#ifndef POSTACTION\n#define POSTACTION(M)\n#endif  /* POSTACTION */\n\n#endif /* USE_LOCKS */\n\n/*\n  CORRUPTION_ERROR_ACTION is triggered upon detected bad addresses.\n  USAGE_ERROR_ACTION is triggered on detected bad frees and\n  reallocs. The argument p is an address that might have triggered the\n  fault. It is ignored by the two predefined actions, but might be\n  useful in custom actions that try to help diagnose errors.\n*/\n\n#if PROCEED_ON_ERROR\n\n/* A count of the number of corruption errors causing resets */\nint malloc_corruption_error_count;\n\n/* default corruption action */\nstatic void reset_on_error(mstate m);\n\n#define CORRUPTION_ERROR_ACTION(m)  reset_on_error(m)\n#define USAGE_ERROR_ACTION(m, p)\n\n#else /* PROCEED_ON_ERROR */\n\n#ifndef CORRUPTION_ERROR_ACTION\n#define CORRUPTION_ERROR_ACTION(m) ABORT\n#endif /* CORRUPTION_ERROR_ACTION */\n\n#ifndef USAGE_ERROR_ACTION\n#define USAGE_ERROR_ACTION(m,p) ABORT\n#endif /* USAGE_ERROR_ACTION */\n\n#endif /* PROCEED_ON_ERROR */\n\n\n/* -------------------------- Debugging setup ---------------------------- */\n\n#if ! DEBUG\n\n#define check_free_chunk(M,P)\n#define check_inuse_chunk(M,P)\n#define check_malloced_chunk(M,P,N)\n#define check_mmapped_chunk(M,P)\n#define check_malloc_state(M)\n#define check_top_chunk(M,P)\n\n#else /* DEBUG */\n#define check_free_chunk(M,P)       do_check_free_chunk(M,P)\n#define check_inuse_chunk(M,P)      do_check_inuse_chunk(M,P)\n#define check_top_chunk(M,P)        do_check_top_chunk(M,P)\n#define check_malloced_chunk(M,P,N) do_check_malloced_chunk(M,P,N)\n#define check_mmapped_chunk(M,P)    do_check_mmapped_chunk(M,P)\n#define check_malloc_state(M)       do_check_malloc_state(M)\n\nstatic void   do_check_any_chunk(mstate m, mchunkptr p);\nstatic void   do_check_top_chunk(mstate m, mchunkptr p);\nstatic void   do_check_mmapped_chunk(mstate m, mchunkptr p);\nstatic void   do_check_inuse_chunk(mstate m, mchunkptr p);\nstatic void   do_check_free_chunk(mstate m, mchunkptr p);\nstatic void   do_check_malloced_chunk(mstate m, void* mem, size_t s);\nstatic void   do_check_tree(mstate m, tchunkptr t);\nstatic void   do_check_treebin(mstate m, bindex_t i);\nstatic void   do_check_smallbin(mstate m, bindex_t i);\nstatic void   do_check_malloc_state(mstate m);\nstatic int    bin_find(mstate m, mchunkptr x);\nstatic size_t traverse_and_check(mstate m);\n#endif /* DEBUG */\n\n/* ---------------------------- Indexing Bins ---------------------------- */\n\n#define is_small(s)         (((s) >> SMALLBIN_SHIFT) < NSMALLBINS)\n#define small_index(s)      (bindex_t)((s)  >> SMALLBIN_SHIFT)\n#define small_index2size(i) ((i)  << SMALLBIN_SHIFT)\n#define MIN_SMALL_INDEX     (small_index(MIN_CHUNK_SIZE))\n\n/* addressing by index. See above about smallbin repositioning */\n/* MegaZeux: use offset instead of subscript+address to suppress -Wstrict-aliasing=2. */\n#define smallbin_at(M, i)   ((sbinptr)((char*)((M)->smallbins + ((i)<<1))))\n#define treebin_at(M,i)     (&((M)->treebins[i]))\n\n/* assign tree index for size S to variable I. Use x86 asm if possible  */\n#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))\n#define compute_tree_index(S, I)\\\n{\\\n  unsigned int X = S >> TREEBIN_SHIFT;\\\n  if (X == 0)\\\n    I = 0;\\\n  else if (X > 0xFFFF)\\\n    I = NTREEBINS-1;\\\n  else {\\\n    unsigned int K = (unsigned) sizeof(X)*__CHAR_BIT__ - 1 - (unsigned) __builtin_clz(X); \\\n    I =  (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\\\n  }\\\n}\n\n#elif defined (__INTEL_COMPILER)\n#define compute_tree_index(S, I)\\\n{\\\n  size_t X = S >> TREEBIN_SHIFT;\\\n  if (X == 0)\\\n    I = 0;\\\n  else if (X > 0xFFFF)\\\n    I = NTREEBINS-1;\\\n  else {\\\n    unsigned int K = _bit_scan_reverse (X); \\\n    I =  (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\\\n  }\\\n}\n\n#elif defined(_MSC_VER) && _MSC_VER>=1300\n#define compute_tree_index(S, I)\\\n{\\\n  size_t X = S >> TREEBIN_SHIFT;\\\n  if (X == 0)\\\n    I = 0;\\\n  else if (X > 0xFFFF)\\\n    I = NTREEBINS-1;\\\n  else {\\\n    unsigned int K;\\\n    _BitScanReverse((DWORD *) &K, (DWORD) X);\\\n    I =  (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\\\n  }\\\n}\n\n#else /* GNUC */\n#define compute_tree_index(S, I)\\\n{\\\n  size_t X = S >> TREEBIN_SHIFT;\\\n  if (X == 0)\\\n    I = 0;\\\n  else if (X > 0xFFFF)\\\n    I = NTREEBINS-1;\\\n  else {\\\n    unsigned int Y = (unsigned int)X;\\\n    unsigned int N = ((Y - 0x100) >> 16) & 8;\\\n    unsigned int K = (((Y <<= N) - 0x1000) >> 16) & 4;\\\n    N += K;\\\n    N += K = (((Y <<= K) - 0x4000) >> 16) & 2;\\\n    K = 14 - N + ((Y <<= K) >> 15);\\\n    I = (K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1));\\\n  }\\\n}\n#endif /* GNUC */\n\n/* Bit representing maximum resolved size in a treebin at i */\n#define bit_for_tree_index(i) \\\n   (i == NTREEBINS-1)? (SIZE_T_BITSIZE-1) : (((i) >> 1) + TREEBIN_SHIFT - 2)\n\n/* Shift placing maximum resolved bit in a treebin at i as sign bit */\n#define leftshift_for_tree_index(i) \\\n   ((i == NTREEBINS-1)? 0 : \\\n    ((SIZE_T_BITSIZE-SIZE_T_ONE) - (((i) >> 1) + TREEBIN_SHIFT - 2)))\n\n/* The size of the smallest chunk held in bin with index i */\n#define minsize_for_tree_index(i) \\\n   ((SIZE_T_ONE << (((i) >> 1) + TREEBIN_SHIFT)) |  \\\n   (((size_t)((i) & SIZE_T_ONE)) << (((i) >> 1) + TREEBIN_SHIFT - 1)))\n\n\n/* ------------------------ Operations on bin maps ----------------------- */\n\n/* bit corresponding to given index */\n#define idx2bit(i)              ((binmap_t)(1) << (i))\n\n/* Mark/Clear bits with given index */\n#define mark_smallmap(M,i)      ((M)->smallmap |=  idx2bit(i))\n#define clear_smallmap(M,i)     ((M)->smallmap &= ~idx2bit(i))\n#define smallmap_is_marked(M,i) ((M)->smallmap &   idx2bit(i))\n\n#define mark_treemap(M,i)       ((M)->treemap  |=  idx2bit(i))\n#define clear_treemap(M,i)      ((M)->treemap  &= ~idx2bit(i))\n#define treemap_is_marked(M,i)  ((M)->treemap  &   idx2bit(i))\n\n/* isolate the least set bit of a bitmap */\n#define least_bit(x)         ((x) & -(x))\n\n/* mask with all bits to left of least bit of x on */\n#define left_bits(x)         ((x<<1) | -(x<<1))\n\n/* mask with all bits to left of or equal to least bit of x on */\n#define same_or_left_bits(x) ((x) | -(x))\n\n/* index corresponding to given bit. Use x86 asm if possible */\n\n#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))\n#define compute_bit2idx(X, I)\\\n{\\\n  unsigned int J;\\\n  J = __builtin_ctz(X); \\\n  I = (bindex_t)J;\\\n}\n\n#elif defined (__INTEL_COMPILER)\n#define compute_bit2idx(X, I)\\\n{\\\n  unsigned int J;\\\n  J = _bit_scan_forward (X); \\\n  I = (bindex_t)J;\\\n}\n\n#elif defined(_MSC_VER) && _MSC_VER>=1300\n#define compute_bit2idx(X, I)\\\n{\\\n  unsigned int J;\\\n  _BitScanForward((DWORD *) &J, X);\\\n  I = (bindex_t)J;\\\n}\n\n#elif USE_BUILTIN_FFS\n#define compute_bit2idx(X, I) I = ffs(X)-1\n\n#else\n#define compute_bit2idx(X, I)\\\n{\\\n  unsigned int Y = X - 1;\\\n  unsigned int K = Y >> (16-4) & 16;\\\n  unsigned int N = K;        Y >>= K;\\\n  N += K = Y >> (8-3) &  8;  Y >>= K;\\\n  N += K = Y >> (4-2) &  4;  Y >>= K;\\\n  N += K = Y >> (2-1) &  2;  Y >>= K;\\\n  N += K = Y >> (1-0) &  1;  Y >>= K;\\\n  I = (bindex_t)(N + Y);\\\n}\n#endif /* GNUC */\n\n\n/* ----------------------- Runtime Check Support ------------------------- */\n\n/*\n  For security, the main invariant is that malloc/free/etc never\n  writes to a static address other than malloc_state, unless static\n  malloc_state itself has been corrupted, which cannot occur via\n  malloc (because of these checks). In essence this means that we\n  believe all pointers, sizes, maps etc held in malloc_state, but\n  check all of those linked or offsetted from other embedded data\n  structures.  These checks are interspersed with main code in a way\n  that tends to minimize their run-time cost.\n\n  When FOOTERS is defined, in addition to range checking, we also\n  verify footer fields of inuse chunks, which can be used guarantee\n  that the mstate controlling malloc/free is intact.  This is a\n  streamlined version of the approach described by William Robertson\n  et al in \"Run-time Detection of Heap-based Overflows\" LISA'03\n  http://www.usenix.org/events/lisa03/tech/robertson.html The footer\n  of an inuse chunk holds the xor of its mstate and a random seed,\n  that is checked upon calls to free() and realloc().  This is\n  (probabalistically) unguessable from outside the program, but can be\n  computed by any code successfully malloc'ing any chunk, so does not\n  itself provide protection against code that has already broken\n  security through some other means.  Unlike Robertson et al, we\n  always dynamically check addresses of all offset chunks (previous,\n  next, etc). This turns out to be cheaper than relying on hashes.\n*/\n\n#if !INSECURE\n/* Check if address a is at least as high as any from MORECORE or MMAP */\n#define ok_address(M, a) ((char*)(a) >= (M)->least_addr)\n/* Check if address of next chunk n is higher than base chunk p */\n#define ok_next(p, n)    ((char*)(p) < (char*)(n))\n/* Check if p has inuse status */\n#define ok_inuse(p)     is_inuse(p)\n/* Check if p has its pinuse bit on */\n#define ok_pinuse(p)     pinuse(p)\n\n#else /* !INSECURE */\n#define ok_address(M, a) (1)\n#define ok_next(b, n)    (1)\n#define ok_inuse(p)      (1)\n#define ok_pinuse(p)     (1)\n#endif /* !INSECURE */\n\n#if (FOOTERS && !INSECURE)\n/* Check if (alleged) mstate m has expected magic field */\n#define ok_magic(M)      ((M)->magic == mparams.magic)\n#else  /* (FOOTERS && !INSECURE) */\n#define ok_magic(M)      (1)\n#endif /* (FOOTERS && !INSECURE) */\n\n/* In gcc, use __builtin_expect to minimize impact of checks */\n#if !INSECURE\n#if defined(__GNUC__) && __GNUC__ >= 3\n#define RTCHECK(e)  __builtin_expect(e, 1)\n#else /* GNUC */\n#define RTCHECK(e)  (e)\n#endif /* GNUC */\n#else /* !INSECURE */\n#define RTCHECK(e)  (1)\n#endif /* !INSECURE */\n\n/* macros to set up inuse chunks with or without footers */\n\n#if !FOOTERS\n\n#define mark_inuse_foot(M,p,s)\n\n/* Macros for setting head/foot of non-mmapped chunks */\n\n/* Set cinuse bit and pinuse bit of next chunk */\n#define set_inuse(M,p,s)\\\n  ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\\\n  ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT)\n\n/* Set cinuse and pinuse of this chunk and pinuse of next chunk */\n#define set_inuse_and_pinuse(M,p,s)\\\n  ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\\\n  ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT)\n\n/* Set size, cinuse and pinuse bit of this chunk */\n#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\\\n  ((p)->head = (s|PINUSE_BIT|CINUSE_BIT))\n\n#else /* FOOTERS */\n\n/* Set foot of inuse chunk to be xor of mstate and seed */\n#define mark_inuse_foot(M,p,s)\\\n  (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic))\n\n#define get_mstate_for(p)\\\n  ((mstate)(((mchunkptr)((char*)(p) +\\\n    (chunksize(p))))->prev_foot ^ mparams.magic))\n\n#define set_inuse(M,p,s)\\\n  ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\\\n  (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT), \\\n  mark_inuse_foot(M,p,s))\n\n#define set_inuse_and_pinuse(M,p,s)\\\n  ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\\\n  (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT),\\\n mark_inuse_foot(M,p,s))\n\n#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\\\n  ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\\\n  mark_inuse_foot(M, p, s))\n\n#endif /* !FOOTERS */\n\n/* ---------------------------- setting mparams -------------------------- */\n\n#if LOCK_AT_FORK\nstatic void pre_fork(void)         { ACQUIRE_LOCK(&(gm)->mutex); }\nstatic void post_fork_parent(void) { RELEASE_LOCK(&(gm)->mutex); }\nstatic void post_fork_child(void)  { INITIAL_LOCK(&(gm)->mutex); }\n#endif /* LOCK_AT_FORK */\n\n/* Initialize mparams */\nstatic int init_mparams(void) {\n#ifdef NEED_GLOBAL_LOCK_INIT\n  if (malloc_global_mutex_status <= 0)\n    init_malloc_global_mutex();\n#endif\n\n  ACQUIRE_MALLOC_GLOBAL_LOCK();\n  if (mparams.magic == 0) {\n    size_t magic;\n    size_t psize;\n    size_t gsize;\n\n#ifndef WIN32\n    psize = malloc_getpagesize;\n    gsize = ((DEFAULT_GRANULARITY != 0)? DEFAULT_GRANULARITY : psize);\n#else /* WIN32 */\n    {\n      SYSTEM_INFO system_info;\n      GetSystemInfo(&system_info);\n      psize = system_info.dwPageSize;\n      gsize = ((DEFAULT_GRANULARITY != 0)?\n               DEFAULT_GRANULARITY : system_info.dwAllocationGranularity);\n    }\n#endif /* WIN32 */\n\n    /* Sanity-check configuration:\n       size_t must be unsigned and as wide as pointer type.\n       ints must be at least 4 bytes.\n       alignment must be at least 8.\n       Alignment, min chunk size, and page size must all be powers of 2.\n    */\n    if ((sizeof(size_t) != sizeof(char*)) ||\n        (MAX_SIZE_T < MIN_CHUNK_SIZE)  ||\n        (sizeof(int) < 4)  ||\n        (MALLOC_ALIGNMENT < (size_t)8U) ||\n        ((MALLOC_ALIGNMENT & (MALLOC_ALIGNMENT-SIZE_T_ONE)) != 0) ||\n        ((MCHUNK_SIZE      & (MCHUNK_SIZE-SIZE_T_ONE))      != 0) ||\n        ((gsize            & (gsize-SIZE_T_ONE))            != 0) ||\n        ((psize            & (psize-SIZE_T_ONE))            != 0))\n      ABORT;\n    mparams.granularity = gsize;\n    mparams.page_size = psize;\n    mparams.mmap_threshold = DEFAULT_MMAP_THRESHOLD;\n    mparams.trim_threshold = DEFAULT_TRIM_THRESHOLD;\n#if MORECORE_CONTIGUOUS\n    mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT;\n#else  /* MORECORE_CONTIGUOUS */\n    mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT|USE_NONCONTIGUOUS_BIT;\n#endif /* MORECORE_CONTIGUOUS */\n\n#if !ONLY_MSPACES\n    /* Set up lock for main malloc area */\n    gm->mflags = mparams.default_mflags;\n    (void)INITIAL_LOCK(&gm->mutex);\n#endif\n#if LOCK_AT_FORK\n    pthread_atfork(&pre_fork, &post_fork_parent, &post_fork_child);\n#endif\n\n    {\n#if USE_DEV_RANDOM\n      int fd;\n      unsigned char buf[sizeof(size_t)];\n      /* Try to use /dev/urandom, else fall back on using time */\n      if ((fd = open(\"/dev/urandom\", O_RDONLY)) >= 0 &&\n          read(fd, buf, sizeof(buf)) == sizeof(buf)) {\n        magic = *((size_t *) buf);\n        close(fd);\n      }\n      else\n#endif /* USE_DEV_RANDOM */\n#ifdef WIN32\n      magic = (size_t)(GetTickCount() ^ (size_t)0x55555555U);\n#elif defined(LACKS_TIME_H)\n      magic = (size_t)&magic ^ (size_t)0x55555555U;\n#else\n      magic = (size_t)(time(0) ^ (size_t)0x55555555U);\n#endif\n      magic |= (size_t)8U;    /* ensure nonzero */\n      magic &= ~(size_t)7U;   /* improve chances of fault for bad values */\n      /* Until memory modes commonly available, use volatile-write */\n      (*(volatile size_t *)(&(mparams.magic))) = magic;\n    }\n  }\n\n  RELEASE_MALLOC_GLOBAL_LOCK();\n  return 1;\n}\n\n/* support for mallopt */\nstatic int change_mparam(int param_number, int value) {\n  size_t val;\n  ensure_initialization();\n  val = (value == -1)? MAX_SIZE_T : (size_t)value;\n  switch(param_number) {\n  case M_TRIM_THRESHOLD:\n    mparams.trim_threshold = val;\n    return 1;\n  case M_GRANULARITY:\n    if (val >= mparams.page_size && ((val & (val-1)) == 0)) {\n      mparams.granularity = val;\n      return 1;\n    }\n    else\n      return 0;\n  case M_MMAP_THRESHOLD:\n    mparams.mmap_threshold = val;\n    return 1;\n  default:\n    return 0;\n  }\n}\n\n#if DEBUG\n/* ------------------------- Debugging Support --------------------------- */\n\n/* Check properties of any chunk, whether free, inuse, mmapped etc  */\nstatic void do_check_any_chunk(mstate m, mchunkptr p) {\n  assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD));\n  assert(ok_address(m, p));\n}\n\n/* Check properties of top chunk */\nstatic void do_check_top_chunk(mstate m, mchunkptr p) {\n  msegmentptr sp = segment_holding(m, (char*)p);\n  size_t  sz = p->head & ~INUSE_BITS; /* third-lowest bit can be set! */\n  assert(sp != 0);\n  assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD));\n  assert(ok_address(m, p));\n  assert(sz == m->topsize);\n  assert(sz > 0);\n  assert(sz == ((sp->base + sp->size) - (char*)p) - TOP_FOOT_SIZE);\n  assert(pinuse(p));\n  assert(!pinuse(chunk_plus_offset(p, sz)));\n}\n\n/* Check properties of (inuse) mmapped chunks */\nstatic void do_check_mmapped_chunk(mstate m, mchunkptr p) {\n  size_t  sz = chunksize(p);\n  size_t len = (sz + (p->prev_foot) + MMAP_FOOT_PAD);\n  assert(is_mmapped(p));\n  assert(use_mmap(m));\n  assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD));\n  assert(ok_address(m, p));\n  assert(!is_small(sz));\n  assert((len & (mparams.page_size-SIZE_T_ONE)) == 0);\n  assert(chunk_plus_offset(p, sz)->head == FENCEPOST_HEAD);\n  assert(chunk_plus_offset(p, sz+SIZE_T_SIZE)->head == 0);\n}\n\n/* Check properties of inuse chunks */\nstatic void do_check_inuse_chunk(mstate m, mchunkptr p) {\n  do_check_any_chunk(m, p);\n  assert(is_inuse(p));\n  assert(next_pinuse(p));\n  /* If not pinuse and not mmapped, previous chunk has OK offset */\n  assert(is_mmapped(p) || pinuse(p) || next_chunk(prev_chunk(p)) == p);\n  if (is_mmapped(p))\n    do_check_mmapped_chunk(m, p);\n}\n\n/* Check properties of free chunks */\nstatic void do_check_free_chunk(mstate m, mchunkptr p) {\n  size_t sz = chunksize(p);\n  mchunkptr next = chunk_plus_offset(p, sz);\n  do_check_any_chunk(m, p);\n  assert(!is_inuse(p));\n  assert(!next_pinuse(p));\n  assert (!is_mmapped(p));\n  if (p != m->dv && p != m->top) {\n    if (sz >= MIN_CHUNK_SIZE) {\n      assert((sz & CHUNK_ALIGN_MASK) == 0);\n      assert(is_aligned(chunk2mem(p)));\n      assert(next->prev_foot == sz);\n      assert(pinuse(p));\n      assert (next == m->top || is_inuse(next));\n      assert(p->fd->bk == p);\n      assert(p->bk->fd == p);\n    }\n    else  /* markers are always of size SIZE_T_SIZE */\n      assert(sz == SIZE_T_SIZE);\n  }\n}\n\n/* Check properties of malloced chunks at the point they are malloced */\nstatic void do_check_malloced_chunk(mstate m, void* mem, size_t s) {\n  if (mem != 0) {\n    mchunkptr p = mem2chunk(mem);\n    size_t sz = p->head & ~INUSE_BITS;\n    do_check_inuse_chunk(m, p);\n    assert((sz & CHUNK_ALIGN_MASK) == 0);\n    assert(sz >= MIN_CHUNK_SIZE);\n    assert(sz >= s);\n    /* unless mmapped, size is less than MIN_CHUNK_SIZE more than request */\n    assert(is_mmapped(p) || sz < (s + MIN_CHUNK_SIZE));\n  }\n}\n\n/* Check a tree and its subtrees.  */\nstatic void do_check_tree(mstate m, tchunkptr t) {\n  tchunkptr head = 0;\n  tchunkptr u = t;\n  bindex_t tindex = t->index;\n  size_t tsize = chunksize(t);\n  bindex_t idx;\n  compute_tree_index(tsize, idx);\n  assert(tindex == idx);\n  assert(tsize >= MIN_LARGE_SIZE);\n  assert(tsize >= minsize_for_tree_index(idx));\n  assert((idx == NTREEBINS-1) || (tsize < minsize_for_tree_index((idx+1))));\n\n  do { /* traverse through chain of same-sized nodes */\n    do_check_any_chunk(m, ((mchunkptr)u));\n    assert(u->index == tindex);\n    assert(chunksize(u) == tsize);\n    assert(!is_inuse(u));\n    assert(!next_pinuse(u));\n    assert(u->fd->bk == u);\n    assert(u->bk->fd == u);\n    if (u->parent == 0) {\n      assert(u->child[0] == 0);\n      assert(u->child[1] == 0);\n    }\n    else {\n      assert(head == 0); /* only one node on chain has parent */\n      head = u;\n      assert(u->parent != u);\n      assert (u->parent->child[0] == u ||\n              u->parent->child[1] == u ||\n              *((tbinptr*)(u->parent)) == u);\n      if (u->child[0] != 0) {\n        assert(u->child[0]->parent == u);\n        assert(u->child[0] != u);\n        do_check_tree(m, u->child[0]);\n      }\n      if (u->child[1] != 0) {\n        assert(u->child[1]->parent == u);\n        assert(u->child[1] != u);\n        do_check_tree(m, u->child[1]);\n      }\n      if (u->child[0] != 0 && u->child[1] != 0) {\n        assert(chunksize(u->child[0]) < chunksize(u->child[1]));\n      }\n    }\n    u = u->fd;\n  } while (u != t);\n  assert(head != 0);\n}\n\n/*  Check all the chunks in a treebin.  */\nstatic void do_check_treebin(mstate m, bindex_t i) {\n  tbinptr* tb = treebin_at(m, i);\n  tchunkptr t = *tb;\n  int empty = (m->treemap & (1U << i)) == 0;\n  if (t == 0)\n    assert(empty);\n  if (!empty)\n    do_check_tree(m, t);\n}\n\n/*  Check all the chunks in a smallbin.  */\nstatic void do_check_smallbin(mstate m, bindex_t i) {\n  sbinptr b = smallbin_at(m, i);\n  mchunkptr p = b->bk;\n  unsigned int empty = (m->smallmap & (1U << i)) == 0;\n  if (p == b)\n    assert(empty);\n  if (!empty) {\n    for (; p != b; p = p->bk) {\n      size_t size = chunksize(p);\n      mchunkptr q;\n      /* each chunk claims to be free */\n      do_check_free_chunk(m, p);\n      /* chunk belongs in bin */\n      assert(small_index(size) == i);\n      assert(p->bk == b || chunksize(p->bk) == chunksize(p));\n      /* chunk is followed by an inuse chunk */\n      q = next_chunk(p);\n      if (q->head != FENCEPOST_HEAD)\n        do_check_inuse_chunk(m, q);\n    }\n  }\n}\n\n/* Find x in a bin. Used in other check functions. */\nstatic int bin_find(mstate m, mchunkptr x) {\n  size_t size = chunksize(x);\n  if (is_small(size)) {\n    bindex_t sidx = small_index(size);\n    sbinptr b = smallbin_at(m, sidx);\n    if (smallmap_is_marked(m, sidx)) {\n      mchunkptr p = b;\n      do {\n        if (p == x)\n          return 1;\n      } while ((p = p->fd) != b);\n    }\n  }\n  else {\n    bindex_t tidx;\n    compute_tree_index(size, tidx);\n    if (treemap_is_marked(m, tidx)) {\n      tchunkptr t = *treebin_at(m, tidx);\n      size_t sizebits = size << leftshift_for_tree_index(tidx);\n      while (t != 0 && chunksize(t) != size) {\n        t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1];\n        sizebits <<= 1;\n      }\n      if (t != 0) {\n        tchunkptr u = t;\n        do {\n          if (u == (tchunkptr)x)\n            return 1;\n        } while ((u = u->fd) != t);\n      }\n    }\n  }\n  return 0;\n}\n\n/* Traverse each chunk and check it; return total */\nstatic size_t traverse_and_check(mstate m) {\n  size_t sum = 0;\n  if (is_initialized(m)) {\n    msegmentptr s = &m->seg;\n    sum += m->topsize + TOP_FOOT_SIZE;\n    while (s != 0) {\n      mchunkptr q = align_as_chunk(s->base);\n      mchunkptr lastq = 0;\n      assert(pinuse(q));\n      while (segment_holds(s, q) &&\n             q != m->top && q->head != FENCEPOST_HEAD) {\n        sum += chunksize(q);\n        if (is_inuse(q)) {\n          assert(!bin_find(m, q));\n          do_check_inuse_chunk(m, q);\n        }\n        else {\n          assert(q == m->dv || bin_find(m, q));\n          assert(lastq == 0 || is_inuse(lastq)); /* Not 2 consecutive free */\n          do_check_free_chunk(m, q);\n        }\n        lastq = q;\n        q = next_chunk(q);\n      }\n      s = s->next;\n    }\n  }\n  return sum;\n}\n\n\n/* Check all properties of malloc_state. */\nstatic void do_check_malloc_state(mstate m) {\n  bindex_t i;\n  size_t total;\n  /* check bins */\n  for (i = 0; i < NSMALLBINS; ++i)\n    do_check_smallbin(m, i);\n  for (i = 0; i < NTREEBINS; ++i)\n    do_check_treebin(m, i);\n\n  if (m->dvsize != 0) { /* check dv chunk */\n    do_check_any_chunk(m, m->dv);\n    assert(m->dvsize == chunksize(m->dv));\n    assert(m->dvsize >= MIN_CHUNK_SIZE);\n    assert(bin_find(m, m->dv) == 0);\n  }\n\n  if (m->top != 0) {   /* check top chunk */\n    do_check_top_chunk(m, m->top);\n    /*assert(m->topsize == chunksize(m->top)); redundant */\n    assert(m->topsize > 0);\n    assert(bin_find(m, m->top) == 0);\n  }\n\n  total = traverse_and_check(m);\n  assert(total <= m->footprint);\n  assert(m->footprint <= m->max_footprint);\n}\n#endif /* DEBUG */\n\n/* ----------------------------- statistics ------------------------------ */\n\n#if !NO_MALLINFO\nstatic struct mallinfo internal_mallinfo(mstate m) {\n  struct mallinfo nm = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };\n  ensure_initialization();\n  if (!PREACTION(m)) {\n    check_malloc_state(m);\n    if (is_initialized(m)) {\n      size_t nfree = SIZE_T_ONE; /* top always free */\n      size_t mfree = m->topsize + TOP_FOOT_SIZE;\n      size_t sum = mfree;\n      msegmentptr s = &m->seg;\n      while (s != 0) {\n        mchunkptr q = align_as_chunk(s->base);\n        while (segment_holds(s, q) &&\n               q != m->top && q->head != FENCEPOST_HEAD) {\n          size_t sz = chunksize(q);\n          sum += sz;\n          if (!is_inuse(q)) {\n            mfree += sz;\n            ++nfree;\n          }\n          q = next_chunk(q);\n        }\n        s = s->next;\n      }\n\n      nm.arena    = sum;\n      nm.ordblks  = nfree;\n      nm.hblkhd   = m->footprint - sum;\n      nm.usmblks  = m->max_footprint;\n      nm.uordblks = m->footprint - mfree;\n      nm.fordblks = mfree;\n      nm.keepcost = m->topsize;\n    }\n\n    POSTACTION(m);\n  }\n  return nm;\n}\n#endif /* !NO_MALLINFO */\n\n#if !NO_MALLOC_STATS\nstatic void internal_malloc_stats(mstate m) {\n  ensure_initialization();\n  if (!PREACTION(m)) {\n    size_t maxfp = 0;\n    size_t fp = 0;\n    size_t used = 0;\n    check_malloc_state(m);\n    if (is_initialized(m)) {\n      msegmentptr s = &m->seg;\n      maxfp = m->max_footprint;\n      fp = m->footprint;\n      used = fp - (m->topsize + TOP_FOOT_SIZE);\n\n      while (s != 0) {\n        mchunkptr q = align_as_chunk(s->base);\n        while (segment_holds(s, q) &&\n               q != m->top && q->head != FENCEPOST_HEAD) {\n          if (!is_inuse(q))\n            used -= chunksize(q);\n          q = next_chunk(q);\n        }\n        s = s->next;\n      }\n    }\n    POSTACTION(m); /* drop lock */\n    fprintf(stderr, \"max system bytes = %10lu\\n\", (unsigned long)(maxfp));\n    fprintf(stderr, \"system bytes     = %10lu\\n\", (unsigned long)(fp));\n    fprintf(stderr, \"in use bytes     = %10lu\\n\", (unsigned long)(used));\n  }\n}\n#endif /* NO_MALLOC_STATS */\n\n/* ----------------------- Operations on smallbins ----------------------- */\n\n/*\n  Various forms of linking and unlinking are defined as macros.  Even\n  the ones for trees, which are very long but have very short typical\n  paths.  This is ugly but reduces reliance on inlining support of\n  compilers.\n*/\n\n/* Link a free chunk into a smallbin  */\n#define insert_small_chunk(M, P, S) {\\\n  bindex_t I  = small_index(S);\\\n  mchunkptr B = smallbin_at(M, I);\\\n  mchunkptr F = B;\\\n  assert(S >= MIN_CHUNK_SIZE);\\\n  if (!smallmap_is_marked(M, I))\\\n    mark_smallmap(M, I);\\\n  else if (RTCHECK(ok_address(M, B->fd)))\\\n    F = B->fd;\\\n  else {\\\n    CORRUPTION_ERROR_ACTION(M);\\\n  }\\\n  B->fd = P;\\\n  F->bk = P;\\\n  P->fd = F;\\\n  P->bk = B;\\\n}\n\n/* Unlink a chunk from a smallbin  */\n#define unlink_small_chunk(M, P, S) {\\\n  mchunkptr F = P->fd;\\\n  mchunkptr B = P->bk;\\\n  bindex_t I = small_index(S);\\\n  assert(P != B);\\\n  assert(P != F);\\\n  assert(chunksize(P) == small_index2size(I));\\\n  if (RTCHECK(F == smallbin_at(M,I) || (ok_address(M, F) && F->bk == P))) { \\\n    if (B == F) {\\\n      clear_smallmap(M, I);\\\n    }\\\n    else if (RTCHECK(B == smallbin_at(M,I) ||\\\n                     (ok_address(M, B) && B->fd == P))) {\\\n      F->bk = B;\\\n      B->fd = F;\\\n    }\\\n    else {\\\n      CORRUPTION_ERROR_ACTION(M);\\\n    }\\\n  }\\\n  else {\\\n    CORRUPTION_ERROR_ACTION(M);\\\n  }\\\n}\n\n/* Unlink the first chunk from a smallbin */\n#define unlink_first_small_chunk(M, B, P, I) {\\\n  mchunkptr F = P->fd;\\\n  assert(P != B);\\\n  assert(P != F);\\\n  assert(chunksize(P) == small_index2size(I));\\\n  if (B == F) {\\\n    clear_smallmap(M, I);\\\n  }\\\n  else if (RTCHECK(ok_address(M, F) && F->bk == P)) {\\\n    F->bk = B;\\\n    B->fd = F;\\\n  }\\\n  else {\\\n    CORRUPTION_ERROR_ACTION(M);\\\n  }\\\n}\n\n/* Replace dv node, binning the old one */\n/* Used only when dvsize known to be small */\n#define replace_dv(M, P, S) {\\\n  size_t DVS = M->dvsize;\\\n  assert(is_small(DVS));\\\n  if (DVS != 0) {\\\n    mchunkptr DV = M->dv;\\\n    insert_small_chunk(M, DV, DVS);\\\n  }\\\n  M->dvsize = S;\\\n  M->dv = P;\\\n}\n\n/* ------------------------- Operations on trees ------------------------- */\n\n/* Insert chunk into tree */\n#define insert_large_chunk(M, X, S) {\\\n  tbinptr* H;\\\n  bindex_t I;\\\n  compute_tree_index(S, I);\\\n  H = treebin_at(M, I);\\\n  X->index = I;\\\n  X->child[0] = X->child[1] = 0;\\\n  if (!treemap_is_marked(M, I)) {\\\n    mark_treemap(M, I);\\\n    *H = X;\\\n    X->parent = (tchunkptr)H;\\\n    X->fd = X->bk = X;\\\n  }\\\n  else {\\\n    tchunkptr T = *H;\\\n    size_t K = S << leftshift_for_tree_index(I);\\\n    for (;;) {\\\n      if (chunksize(T) != S) {\\\n        tchunkptr* C = &(T->child[(K >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]);\\\n        K <<= 1;\\\n        if (*C != 0)\\\n          T = *C;\\\n        else if (RTCHECK(ok_address(M, C))) {\\\n          *C = X;\\\n          X->parent = T;\\\n          X->fd = X->bk = X;\\\n          break;\\\n        }\\\n        else {\\\n          CORRUPTION_ERROR_ACTION(M);\\\n          break;\\\n        }\\\n      }\\\n      else {\\\n        tchunkptr F = T->fd;\\\n        if (RTCHECK(ok_address(M, T) && ok_address(M, F))) {\\\n          T->fd = F->bk = X;\\\n          X->fd = F;\\\n          X->bk = T;\\\n          X->parent = 0;\\\n          break;\\\n        }\\\n        else {\\\n          CORRUPTION_ERROR_ACTION(M);\\\n          break;\\\n        }\\\n      }\\\n    }\\\n  }\\\n}\n\n/*\n  Unlink steps:\n\n  1. If x is a chained node, unlink it from its same-sized fd/bk links\n     and choose its bk node as its replacement.\n  2. If x was the last node of its size, but not a leaf node, it must\n     be replaced with a leaf node (not merely one with an open left or\n     right), to make sure that lefts and rights of descendents\n     correspond properly to bit masks.  We use the rightmost descendent\n     of x.  We could use any other leaf, but this is easy to locate and\n     tends to counteract removal of leftmosts elsewhere, and so keeps\n     paths shorter than minimally guaranteed.  This doesn't loop much\n     because on average a node in a tree is near the bottom.\n  3. If x is the base of a chain (i.e., has parent links) relink\n     x's parent and children to x's replacement (or null if none).\n*/\n\n#define unlink_large_chunk(M, X) {\\\n  tchunkptr XP = X->parent;\\\n  tchunkptr R;\\\n  if (X->bk != X) {\\\n    tchunkptr F = X->fd;\\\n    R = X->bk;\\\n    if (RTCHECK(ok_address(M, F) && F->bk == X && R->fd == X)) {\\\n      F->bk = R;\\\n      R->fd = F;\\\n    }\\\n    else {\\\n      CORRUPTION_ERROR_ACTION(M);\\\n    }\\\n  }\\\n  else {\\\n    tchunkptr* RP;\\\n    if (((R = *(RP = &(X->child[1]))) != 0) ||\\\n        ((R = *(RP = &(X->child[0]))) != 0)) {\\\n      tchunkptr* CP;\\\n      while ((*(CP = &(R->child[1])) != 0) ||\\\n             (*(CP = &(R->child[0])) != 0)) {\\\n        R = *(RP = CP);\\\n      }\\\n      if (RTCHECK(ok_address(M, RP)))\\\n        *RP = 0;\\\n      else {\\\n        CORRUPTION_ERROR_ACTION(M);\\\n      }\\\n    }\\\n  }\\\n  if (XP != 0) {\\\n    tbinptr* H = treebin_at(M, X->index);\\\n    if (X == *H) {\\\n      if ((*H = R) == 0) \\\n        clear_treemap(M, X->index);\\\n    }\\\n    else if (RTCHECK(ok_address(M, XP))) {\\\n      if (XP->child[0] == X) \\\n        XP->child[0] = R;\\\n      else \\\n        XP->child[1] = R;\\\n    }\\\n    else\\\n      CORRUPTION_ERROR_ACTION(M);\\\n    if (R != 0) {\\\n      if (RTCHECK(ok_address(M, R))) {\\\n        tchunkptr C0, C1;\\\n        R->parent = XP;\\\n        if ((C0 = X->child[0]) != 0) {\\\n          if (RTCHECK(ok_address(M, C0))) {\\\n            R->child[0] = C0;\\\n            C0->parent = R;\\\n          }\\\n          else\\\n            CORRUPTION_ERROR_ACTION(M);\\\n        }\\\n        if ((C1 = X->child[1]) != 0) {\\\n          if (RTCHECK(ok_address(M, C1))) {\\\n            R->child[1] = C1;\\\n            C1->parent = R;\\\n          }\\\n          else\\\n            CORRUPTION_ERROR_ACTION(M);\\\n        }\\\n      }\\\n      else\\\n        CORRUPTION_ERROR_ACTION(M);\\\n    }\\\n  }\\\n}\n\n/* Relays to large vs small bin operations */\n\n#define insert_chunk(M, P, S)\\\n  if (is_small(S)) insert_small_chunk(M, P, S)\\\n  else { tchunkptr TP = (tchunkptr)(P); insert_large_chunk(M, TP, S); }\n\n#define unlink_chunk(M, P, S)\\\n  if (is_small(S)) unlink_small_chunk(M, P, S)\\\n  else { tchunkptr TP = (tchunkptr)(P); unlink_large_chunk(M, TP); }\n\n\n/* Relays to internal calls to malloc/free from realloc, memalign etc */\n\n#if ONLY_MSPACES\n#define internal_malloc(m, b) mspace_malloc(m, b)\n#define internal_free(m, mem) mspace_free(m,mem);\n#else /* ONLY_MSPACES */\n#if MSPACES\n#define internal_malloc(m, b)\\\n  ((m == gm)? dlmalloc(b) : mspace_malloc(m, b))\n#define internal_free(m, mem)\\\n   if (m == gm) dlfree(mem); else mspace_free(m,mem);\n#else /* MSPACES */\n#define internal_malloc(m, b) dlmalloc(b)\n#define internal_free(m, mem) dlfree(mem)\n#endif /* MSPACES */\n#endif /* ONLY_MSPACES */\n\n/* -----------------------  Direct-mmapping chunks ----------------------- */\n\n/*\n  Directly mmapped chunks are set up with an offset to the start of\n  the mmapped region stored in the prev_foot field of the chunk. This\n  allows reconstruction of the required argument to MUNMAP when freed,\n  and also allows adjustment of the returned chunk to meet alignment\n  requirements (especially in memalign).\n*/\n\n/* Malloc using mmap */\nstatic void* mmap_alloc(mstate m, size_t nb) {\n  size_t mmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK);\n  if (m->footprint_limit != 0) {\n    size_t fp = m->footprint + mmsize;\n    if (fp <= m->footprint || fp > m->footprint_limit)\n      return 0;\n  }\n  if (mmsize > nb) {     /* Check for wrap around 0 */\n    char* mm = (char*)(CALL_DIRECT_MMAP(mmsize));\n    if (mm != CMFAIL) {\n      size_t offset = align_offset(chunk2mem(mm));\n      size_t psize = mmsize - offset - MMAP_FOOT_PAD;\n      mchunkptr p = (mchunkptr)(mm + offset);\n      p->prev_foot = offset;\n      p->head = psize;\n      mark_inuse_foot(m, p, psize);\n      chunk_plus_offset(p, psize)->head = FENCEPOST_HEAD;\n      chunk_plus_offset(p, psize+SIZE_T_SIZE)->head = 0;\n\n      if (m->least_addr == 0 || mm < m->least_addr)\n        m->least_addr = mm;\n      if ((m->footprint += mmsize) > m->max_footprint)\n        m->max_footprint = m->footprint;\n      assert(is_aligned(chunk2mem(p)));\n      check_mmapped_chunk(m, p);\n      return chunk2mem(p);\n    }\n  }\n  return 0;\n}\n\n/* Realloc using mmap */\nstatic mchunkptr mmap_resize(mstate m, mchunkptr oldp, size_t nb, int flags) {\n  size_t oldsize = chunksize(oldp);\n  (void)flags; /* placate people compiling -Wunused */\n  if (is_small(nb)) /* Can't shrink mmap regions below small size */\n    return 0;\n  /* Keep old chunk if big enough but not too big */\n  if (oldsize >= nb + SIZE_T_SIZE &&\n      (oldsize - nb) <= (mparams.granularity << 1))\n    return oldp;\n  else {\n    size_t offset = oldp->prev_foot;\n    size_t oldmmsize = oldsize + offset + MMAP_FOOT_PAD;\n    size_t newmmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK);\n    char* cp = (char*)CALL_MREMAP((char*)oldp - offset,\n                                  oldmmsize, newmmsize, flags);\n    if (cp != CMFAIL) {\n      mchunkptr newp = (mchunkptr)(cp + offset);\n      size_t psize = newmmsize - offset - MMAP_FOOT_PAD;\n      newp->head = psize;\n      mark_inuse_foot(m, newp, psize);\n      chunk_plus_offset(newp, psize)->head = FENCEPOST_HEAD;\n      chunk_plus_offset(newp, psize+SIZE_T_SIZE)->head = 0;\n\n      if (cp < m->least_addr)\n        m->least_addr = cp;\n      if ((m->footprint += newmmsize - oldmmsize) > m->max_footprint)\n        m->max_footprint = m->footprint;\n      check_mmapped_chunk(m, newp);\n      return newp;\n    }\n  }\n  return 0;\n}\n\n\n/* -------------------------- mspace management -------------------------- */\n\n/* Initialize top chunk and its size */\nstatic void init_top(mstate m, mchunkptr p, size_t psize) {\n  /* Ensure alignment */\n  size_t offset = align_offset(chunk2mem(p));\n  p = (mchunkptr)((char*)p + offset);\n  psize -= offset;\n\n  m->top = p;\n  m->topsize = psize;\n  p->head = psize | PINUSE_BIT;\n  /* set size of fake trailing chunk holding overhead space only once */\n  chunk_plus_offset(p, psize)->head = TOP_FOOT_SIZE;\n  m->trim_check = mparams.trim_threshold; /* reset on each update */\n}\n\n/* Initialize bins for a new mstate that is otherwise zeroed out */\nstatic void init_bins(mstate m) {\n  /* Establish circular links for smallbins */\n  bindex_t i;\n  for (i = 0; i < NSMALLBINS; ++i) {\n    sbinptr bin = smallbin_at(m,i);\n    bin->fd = bin->bk = bin;\n  }\n}\n\n#if PROCEED_ON_ERROR\n\n/* default corruption action */\nstatic void reset_on_error(mstate m) {\n  /* MegaZeux: use size_t instead of int to suppress -Wsign-compare. */\n  size_t i;\n  ++malloc_corruption_error_count;\n  /* Reinitialize fields to forget about all memory */\n  m->smallmap = m->treemap = 0;\n  m->dvsize = m->topsize = 0;\n  m->seg.base = 0;\n  m->seg.size = 0;\n  m->seg.next = 0;\n  m->top = m->dv = 0;\n  for (i = 0; i < NTREEBINS; ++i)\n    *treebin_at(m, i) = 0;\n  init_bins(m);\n}\n#endif /* PROCEED_ON_ERROR */\n\n/* Allocate chunk and prepend remainder with chunk in successor base. */\nstatic void* prepend_alloc(mstate m, char* newbase, char* oldbase,\n                           size_t nb) {\n  mchunkptr p = align_as_chunk(newbase);\n  mchunkptr oldfirst = align_as_chunk(oldbase);\n  size_t psize = (char*)oldfirst - (char*)p;\n  mchunkptr q = chunk_plus_offset(p, nb);\n  size_t qsize = psize - nb;\n  set_size_and_pinuse_of_inuse_chunk(m, p, nb);\n\n  assert((char*)oldfirst > (char*)q);\n  assert(pinuse(oldfirst));\n  assert(qsize >= MIN_CHUNK_SIZE);\n\n  /* consolidate remainder with first chunk of old base */\n  if (oldfirst == m->top) {\n    size_t tsize = m->topsize += qsize;\n    m->top = q;\n    q->head = tsize | PINUSE_BIT;\n    check_top_chunk(m, q);\n  }\n  else if (oldfirst == m->dv) {\n    size_t dsize = m->dvsize += qsize;\n    m->dv = q;\n    set_size_and_pinuse_of_free_chunk(q, dsize);\n  }\n  else {\n    if (!is_inuse(oldfirst)) {\n      size_t nsize = chunksize(oldfirst);\n      unlink_chunk(m, oldfirst, nsize);\n      oldfirst = chunk_plus_offset(oldfirst, nsize);\n      qsize += nsize;\n    }\n    set_free_with_pinuse(q, qsize, oldfirst);\n    insert_chunk(m, q, qsize);\n    check_free_chunk(m, q);\n  }\n\n  check_malloced_chunk(m, chunk2mem(p), nb);\n  return chunk2mem(p);\n}\n\n/* Add a segment to hold a new noncontiguous region */\nstatic void add_segment(mstate m, char* tbase, size_t tsize, flag_t mmapped) {\n  /* Determine locations and sizes of segment, fenceposts, old top */\n  char* old_top = (char*)m->top;\n  msegmentptr oldsp = segment_holding(m, old_top);\n  char* old_end = oldsp->base + oldsp->size;\n  size_t ssize = pad_request(sizeof(struct malloc_segment));\n  char* rawsp = old_end - (ssize + FOUR_SIZE_T_SIZES + CHUNK_ALIGN_MASK);\n  size_t offset = align_offset(chunk2mem(rawsp));\n  char* asp = rawsp + offset;\n  char* csp = (asp < (old_top + MIN_CHUNK_SIZE))? old_top : asp;\n  mchunkptr sp = (mchunkptr)csp;\n  msegmentptr ss = (msegmentptr)(chunk2mem(sp));\n  mchunkptr tnext = chunk_plus_offset(sp, ssize);\n  mchunkptr p = tnext;\n  int nfences = 0;\n\n  /* reset top to new space */\n  init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE);\n\n  /* Set up segment record */\n  assert(is_aligned(ss));\n  set_size_and_pinuse_of_inuse_chunk(m, sp, ssize);\n  *ss = m->seg; /* Push current record */\n  m->seg.base = tbase;\n  m->seg.size = tsize;\n  m->seg.sflags = mmapped;\n  m->seg.next = ss;\n\n  /* Insert trailing fenceposts */\n  for (;;) {\n    mchunkptr nextp = chunk_plus_offset(p, SIZE_T_SIZE);\n    p->head = FENCEPOST_HEAD;\n    ++nfences;\n    if ((char*)(&(nextp->head)) < old_end)\n      p = nextp;\n    else\n      break;\n  }\n  assert(nfences >= 2);\n\n  /* Insert the rest of old top into a bin as an ordinary free chunk */\n  if (csp != old_top) {\n    mchunkptr q = (mchunkptr)old_top;\n    size_t psize = csp - old_top;\n    mchunkptr tn = chunk_plus_offset(q, psize);\n    set_free_with_pinuse(q, psize, tn);\n    insert_chunk(m, q, psize);\n  }\n\n  check_top_chunk(m, m->top);\n}\n\n/* -------------------------- System allocation -------------------------- */\n\n/* Get memory from system using MORECORE or MMAP */\nstatic void* sys_alloc(mstate m, size_t nb) {\n  char* tbase = CMFAIL;\n  size_t tsize = 0;\n  flag_t mmap_flag = 0;\n  size_t asize; /* allocation size */\n\n  ensure_initialization();\n\n  /* Directly map large chunks, but only if already initialized */\n  if (use_mmap(m) && nb >= mparams.mmap_threshold && m->topsize != 0) {\n    void* mem = mmap_alloc(m, nb);\n    if (mem != 0)\n      return mem;\n  }\n\n  asize = granularity_align(nb + SYS_ALLOC_PADDING);\n  if (asize <= nb)\n    return 0; /* wraparound */\n  if (m->footprint_limit != 0) {\n    size_t fp = m->footprint + asize;\n    if (fp <= m->footprint || fp > m->footprint_limit)\n      return 0;\n  }\n\n  /*\n    Try getting memory in any of three ways (in most-preferred to\n    least-preferred order):\n    1. A call to MORECORE that can normally contiguously extend memory.\n       (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or\n       or main space is mmapped or a previous contiguous call failed)\n    2. A call to MMAP new space (disabled if not HAVE_MMAP).\n       Note that under the default settings, if MORECORE is unable to\n       fulfill a request, and HAVE_MMAP is true, then mmap is\n       used as a noncontiguous system allocator. This is a useful backup\n       strategy for systems with holes in address spaces -- in this case\n       sbrk cannot contiguously expand the heap, but mmap may be able to\n       find space.\n    3. A call to MORECORE that cannot usually contiguously extend memory.\n       (disabled if not HAVE_MORECORE)\n\n   In all cases, we need to request enough bytes from system to ensure\n   we can malloc nb bytes upon success, so pad with enough space for\n   top_foot, plus alignment-pad to make sure we don't lose bytes if\n   not on boundary, and round this up to a granularity unit.\n  */\n\n  if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) {\n    char* br = CMFAIL;\n    size_t ssize = asize; /* sbrk call size */\n    msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top);\n    ACQUIRE_MALLOC_GLOBAL_LOCK();\n\n    if (ss == 0) {  /* First time through or recovery */\n      char* base = (char*)CALL_MORECORE(0);\n      if (base != CMFAIL) {\n        size_t fp;\n        /* Adjust to end on a page boundary */\n        if (!is_page_aligned(base))\n          ssize += (page_align((size_t)base) - (size_t)base);\n        fp = m->footprint + ssize; /* recheck limits */\n        if (ssize > nb && ssize < HALF_MAX_SIZE_T &&\n            (m->footprint_limit == 0 ||\n             (fp > m->footprint && fp <= m->footprint_limit)) &&\n            (br = (char*)(CALL_MORECORE(ssize))) == base) {\n          tbase = base;\n          tsize = ssize;\n        }\n      }\n    }\n    else {\n      /* Subtract out existing available top space from MORECORE request. */\n      ssize = granularity_align(nb - m->topsize + SYS_ALLOC_PADDING);\n      /* Use mem here only if it did continuously extend old space */\n      if (ssize < HALF_MAX_SIZE_T &&\n          (br = (char*)(CALL_MORECORE(ssize))) == ss->base+ss->size) {\n        tbase = br;\n        tsize = ssize;\n      }\n    }\n\n    if (tbase == CMFAIL) {    /* Cope with partial failure */\n      if (br != CMFAIL) {    /* Try to use/extend the space we did get */\n        if (ssize < HALF_MAX_SIZE_T &&\n            ssize < nb + SYS_ALLOC_PADDING) {\n          size_t esize = granularity_align(nb + SYS_ALLOC_PADDING - ssize);\n          if (esize < HALF_MAX_SIZE_T) {\n            char* end = (char*)CALL_MORECORE(esize);\n            if (end != CMFAIL)\n              ssize += esize;\n            else {            /* Can't use; try to release */\n              (void) CALL_MORECORE(-ssize);\n              br = CMFAIL;\n            }\n          }\n        }\n      }\n      if (br != CMFAIL) {    /* Use the space we did get */\n        tbase = br;\n        tsize = ssize;\n      }\n      else\n        disable_contiguous(m); /* Don't try contiguous path in the future */\n    }\n\n    RELEASE_MALLOC_GLOBAL_LOCK();\n  }\n\n  if (HAVE_MMAP && tbase == CMFAIL) {  /* Try MMAP */\n    char* mp = (char*)(CALL_MMAP(asize));\n    if (mp != CMFAIL) {\n      tbase = mp;\n      tsize = asize;\n      mmap_flag = USE_MMAP_BIT;\n    }\n  }\n\n  if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */\n    if (asize < HALF_MAX_SIZE_T) {\n      char* br = CMFAIL;\n      char* end = CMFAIL;\n      ACQUIRE_MALLOC_GLOBAL_LOCK();\n      br = (char*)(CALL_MORECORE(asize));\n      end = (char*)(CALL_MORECORE(0));\n      RELEASE_MALLOC_GLOBAL_LOCK();\n      if (br != CMFAIL && end != CMFAIL && br < end) {\n        size_t ssize = end - br;\n        if (ssize > nb + TOP_FOOT_SIZE) {\n          tbase = br;\n          tsize = ssize;\n        }\n      }\n    }\n  }\n\n  if (tbase != CMFAIL) {\n\n    if ((m->footprint += tsize) > m->max_footprint)\n      m->max_footprint = m->footprint;\n\n    if (!is_initialized(m)) { /* first-time initialization */\n      if (m->least_addr == 0 || tbase < m->least_addr)\n        m->least_addr = tbase;\n      m->seg.base = tbase;\n      m->seg.size = tsize;\n      m->seg.sflags = mmap_flag;\n      m->magic = mparams.magic;\n      m->release_checks = MAX_RELEASE_CHECK_RATE;\n      init_bins(m);\n#if !ONLY_MSPACES\n      if (is_global(m))\n        init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE);\n      else\n#endif\n      {\n        /* Offset top by embedded malloc_state */\n        mchunkptr mn = next_chunk(mem2chunk(m));\n        init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE);\n      }\n    }\n\n    else {\n      /* Try to merge with an existing segment */\n      msegmentptr sp = &m->seg;\n      /* Only consider most recent segment if traversal suppressed */\n      while (sp != 0 && tbase != sp->base + sp->size)\n        sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next;\n      if (sp != 0 &&\n          !is_extern_segment(sp) &&\n          (sp->sflags & USE_MMAP_BIT) == mmap_flag &&\n          segment_holds(sp, m->top)) { /* append */\n        sp->size += tsize;\n        init_top(m, m->top, m->topsize + tsize);\n      }\n      else {\n        if (tbase < m->least_addr)\n          m->least_addr = tbase;\n        sp = &m->seg;\n        while (sp != 0 && sp->base != tbase + tsize)\n          sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next;\n        if (sp != 0 &&\n            !is_extern_segment(sp) &&\n            (sp->sflags & USE_MMAP_BIT) == mmap_flag) {\n          char* oldbase = sp->base;\n          sp->base = tbase;\n          sp->size += tsize;\n          return prepend_alloc(m, tbase, oldbase, nb);\n        }\n        else\n          add_segment(m, tbase, tsize, mmap_flag);\n      }\n    }\n\n    if (nb < m->topsize) { /* Allocate from new or extended top space */\n      size_t rsize = m->topsize -= nb;\n      mchunkptr p = m->top;\n      mchunkptr r = m->top = chunk_plus_offset(p, nb);\n      r->head = rsize | PINUSE_BIT;\n      set_size_and_pinuse_of_inuse_chunk(m, p, nb);\n      check_top_chunk(m, m->top);\n      check_malloced_chunk(m, chunk2mem(p), nb);\n      return chunk2mem(p);\n    }\n  }\n\n  MALLOC_FAILURE_ACTION;\n  return 0;\n}\n\n/* -----------------------  system deallocation -------------------------- */\n\n/* Unmap and unlink any mmapped segments that don't contain used chunks */\nstatic size_t release_unused_segments(mstate m) {\n  size_t released = 0;\n  int nsegs = 0;\n  msegmentptr pred = &m->seg;\n  msegmentptr sp = pred->next;\n  while (sp != 0) {\n    char* base = sp->base;\n    size_t size = sp->size;\n    msegmentptr next = sp->next;\n    ++nsegs;\n    if (is_mmapped_segment(sp) && !is_extern_segment(sp)) {\n      mchunkptr p = align_as_chunk(base);\n      size_t psize = chunksize(p);\n      /* Can unmap if first chunk holds entire segment and not pinned */\n      if (!is_inuse(p) && (char*)p + psize >= base + size - TOP_FOOT_SIZE) {\n        tchunkptr tp = (tchunkptr)p;\n        assert(segment_holds(sp, (char*)sp));\n        if (p == m->dv) {\n          m->dv = 0;\n          m->dvsize = 0;\n        }\n        else {\n          unlink_large_chunk(m, tp);\n        }\n        if (CALL_MUNMAP(base, size) == 0) {\n          released += size;\n          m->footprint -= size;\n          /* unlink obsoleted record */\n          sp = pred;\n          sp->next = next;\n        }\n        else { /* back out if cannot unmap */\n          insert_large_chunk(m, tp, psize);\n        }\n      }\n    }\n    if (NO_SEGMENT_TRAVERSAL) /* scan only first segment */\n      break;\n    pred = sp;\n    sp = next;\n  }\n  /* Reset check counter */\n  m->release_checks = (((size_t) nsegs > (size_t) MAX_RELEASE_CHECK_RATE)?\n                       (size_t) nsegs : (size_t) MAX_RELEASE_CHECK_RATE);\n  return released;\n}\n\nstatic int sys_trim(mstate m, size_t pad) {\n  size_t released = 0;\n  ensure_initialization();\n  if (pad < MAX_REQUEST && is_initialized(m)) {\n    pad += TOP_FOOT_SIZE; /* ensure enough room for segment overhead */\n\n    if (m->topsize > pad) {\n      /* Shrink top space in granularity-size units, keeping at least one */\n      size_t unit = mparams.granularity;\n      size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit -\n                      SIZE_T_ONE) * unit;\n      msegmentptr sp = segment_holding(m, (char*)m->top);\n\n      if (!is_extern_segment(sp)) {\n        if (is_mmapped_segment(sp)) {\n          if (HAVE_MMAP &&\n              sp->size >= extra &&\n              !has_segment_link(m, sp)) { /* can't shrink if pinned */\n            size_t newsize = sp->size - extra;\n            (void)newsize; /* placate people compiling -Wunused-variable */\n            /* Prefer mremap, fall back to munmap */\n            if ((CALL_MREMAP(sp->base, sp->size, newsize, 0) != MFAIL) ||\n                (CALL_MUNMAP(sp->base + newsize, extra) == 0)) {\n              released = extra;\n            }\n          }\n        }\n        else if (HAVE_MORECORE) {\n          if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */\n            extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit;\n          ACQUIRE_MALLOC_GLOBAL_LOCK();\n          {\n            /* Make sure end of memory is where we last set it. */\n            char* old_br = (char*)(CALL_MORECORE(0));\n            if (old_br == sp->base + sp->size) {\n              char* rel_br = (char*)(CALL_MORECORE(-extra));\n              char* new_br = (char*)(CALL_MORECORE(0));\n              if (rel_br != CMFAIL && new_br < old_br)\n                released = old_br - new_br;\n            }\n          }\n          RELEASE_MALLOC_GLOBAL_LOCK();\n        }\n      }\n\n      if (released != 0) {\n        sp->size -= released;\n        m->footprint -= released;\n        init_top(m, m->top, m->topsize - released);\n        check_top_chunk(m, m->top);\n      }\n    }\n\n    /* Unmap any unused mmapped segments */\n    if (HAVE_MMAP)\n      released += release_unused_segments(m);\n\n    /* On failure, disable autotrim to avoid repeated failed future calls */\n    if (released == 0 && m->topsize > m->trim_check)\n      m->trim_check = MAX_SIZE_T;\n  }\n\n  return (released != 0)? 1 : 0;\n}\n\n/* Consolidate and bin a chunk. Differs from exported versions\n   of free mainly in that the chunk need not be marked as inuse.\n*/\nstatic void dispose_chunk(mstate m, mchunkptr p, size_t psize) {\n  mchunkptr next = chunk_plus_offset(p, psize);\n  if (!pinuse(p)) {\n    mchunkptr prev;\n    size_t prevsize = p->prev_foot;\n    if (is_mmapped(p)) {\n      psize += prevsize + MMAP_FOOT_PAD;\n      if (CALL_MUNMAP((char*)p - prevsize, psize) == 0)\n        m->footprint -= psize;\n      return;\n    }\n    prev = chunk_minus_offset(p, prevsize);\n    psize += prevsize;\n    p = prev;\n    if (RTCHECK(ok_address(m, prev))) { /* consolidate backward */\n      if (p != m->dv) {\n        unlink_chunk(m, p, prevsize);\n      }\n      else if ((next->head & INUSE_BITS) == INUSE_BITS) {\n        m->dvsize = psize;\n        set_free_with_pinuse(p, psize, next);\n        return;\n      }\n    }\n    else {\n      CORRUPTION_ERROR_ACTION(m);\n      return;\n    }\n  }\n  if (RTCHECK(ok_address(m, next))) {\n    if (!cinuse(next)) {  /* consolidate forward */\n      if (next == m->top) {\n        size_t tsize = m->topsize += psize;\n        m->top = p;\n        p->head = tsize | PINUSE_BIT;\n        if (p == m->dv) {\n          m->dv = 0;\n          m->dvsize = 0;\n        }\n        return;\n      }\n      else if (next == m->dv) {\n        size_t dsize = m->dvsize += psize;\n        m->dv = p;\n        set_size_and_pinuse_of_free_chunk(p, dsize);\n        return;\n      }\n      else {\n        size_t nsize = chunksize(next);\n        psize += nsize;\n        unlink_chunk(m, next, nsize);\n        set_size_and_pinuse_of_free_chunk(p, psize);\n        if (p == m->dv) {\n          m->dvsize = psize;\n          return;\n        }\n      }\n    }\n    else {\n      set_free_with_pinuse(p, psize, next);\n    }\n    insert_chunk(m, p, psize);\n  }\n  else {\n    CORRUPTION_ERROR_ACTION(m);\n  }\n}\n\n/* ---------------------------- malloc --------------------------- */\n\n/* allocate a large request from the best fitting chunk in a treebin */\nstatic void* tmalloc_large(mstate m, size_t nb) {\n  tchunkptr v = 0;\n  size_t rsize = -nb; /* Unsigned negation */\n  tchunkptr t;\n  bindex_t idx;\n  compute_tree_index(nb, idx);\n  if ((t = *treebin_at(m, idx)) != 0) {\n    /* Traverse tree for this bin looking for node with size == nb */\n    size_t sizebits = nb << leftshift_for_tree_index(idx);\n    tchunkptr rst = 0;  /* The deepest untaken right subtree */\n    for (;;) {\n      tchunkptr rt;\n      size_t trem = chunksize(t) - nb;\n      if (trem < rsize) {\n        v = t;\n        if ((rsize = trem) == 0)\n          break;\n      }\n      rt = t->child[1];\n      t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1];\n      if (rt != 0 && rt != t)\n        rst = rt;\n      if (t == 0) {\n        t = rst; /* set t to least subtree holding sizes > nb */\n        break;\n      }\n      sizebits <<= 1;\n    }\n  }\n  if (t == 0 && v == 0) { /* set t to root of next non-empty treebin */\n    binmap_t leftbits = left_bits(idx2bit(idx)) & m->treemap;\n    if (leftbits != 0) {\n      bindex_t i;\n      binmap_t leastbit = least_bit(leftbits);\n      compute_bit2idx(leastbit, i);\n      t = *treebin_at(m, i);\n    }\n  }\n\n  while (t != 0) { /* find smallest of tree or subtree */\n    size_t trem = chunksize(t) - nb;\n    if (trem < rsize) {\n      rsize = trem;\n      v = t;\n    }\n    t = leftmost_child(t);\n  }\n\n  /*  If dv is a better fit, return 0 so malloc will use it */\n  if (v != 0 && rsize < (size_t)(m->dvsize - nb)) {\n    if (RTCHECK(ok_address(m, v))) { /* split */\n      mchunkptr r = chunk_plus_offset(v, nb);\n      assert(chunksize(v) == rsize + nb);\n      if (RTCHECK(ok_next(v, r))) {\n        unlink_large_chunk(m, v);\n        if (rsize < MIN_CHUNK_SIZE)\n          set_inuse_and_pinuse(m, v, (rsize + nb));\n        else {\n          set_size_and_pinuse_of_inuse_chunk(m, v, nb);\n          set_size_and_pinuse_of_free_chunk(r, rsize);\n          insert_chunk(m, r, rsize);\n        }\n        return chunk2mem(v);\n      }\n    }\n    CORRUPTION_ERROR_ACTION(m);\n  }\n  return 0;\n}\n\n/* allocate a small request from the best fitting chunk in a treebin */\nstatic void* tmalloc_small(mstate m, size_t nb) {\n  tchunkptr t, v;\n  size_t rsize;\n  bindex_t i;\n  binmap_t leastbit = least_bit(m->treemap);\n  compute_bit2idx(leastbit, i);\n  v = t = *treebin_at(m, i);\n  rsize = chunksize(t) - nb;\n\n  while ((t = leftmost_child(t)) != 0) {\n    size_t trem = chunksize(t) - nb;\n    if (trem < rsize) {\n      rsize = trem;\n      v = t;\n    }\n  }\n\n  if (RTCHECK(ok_address(m, v))) {\n    mchunkptr r = chunk_plus_offset(v, nb);\n    assert(chunksize(v) == rsize + nb);\n    if (RTCHECK(ok_next(v, r))) {\n      unlink_large_chunk(m, v);\n      if (rsize < MIN_CHUNK_SIZE)\n        set_inuse_and_pinuse(m, v, (rsize + nb));\n      else {\n        set_size_and_pinuse_of_inuse_chunk(m, v, nb);\n        set_size_and_pinuse_of_free_chunk(r, rsize);\n        replace_dv(m, r, rsize);\n      }\n      return chunk2mem(v);\n    }\n  }\n\n  CORRUPTION_ERROR_ACTION(m);\n  return 0;\n}\n\n#if !ONLY_MSPACES\n\nvoid* dlmalloc(size_t bytes) {\n  /*\n     Basic algorithm:\n     If a small request (< 256 bytes minus per-chunk overhead):\n       1. If one exists, use a remainderless chunk in associated smallbin.\n          (Remainderless means that there are too few excess bytes to\n          represent as a chunk.)\n       2. If it is big enough, use the dv chunk, which is normally the\n          chunk adjacent to the one used for the most recent small request.\n       3. If one exists, split the smallest available chunk in a bin,\n          saving remainder in dv.\n       4. If it is big enough, use the top chunk.\n       5. If available, get memory from system and use it\n     Otherwise, for a large request:\n       1. Find the smallest available binned chunk that fits, and use it\n          if it is better fitting than dv chunk, splitting if necessary.\n       2. If better fitting than any binned chunk, use the dv chunk.\n       3. If it is big enough, use the top chunk.\n       4. If request size >= mmap threshold, try to directly mmap this chunk.\n       5. If available, get memory from system and use it\n\n     The ugly goto's here ensure that postaction occurs along all paths.\n  */\n\n#if USE_LOCKS\n  ensure_initialization(); /* initialize in sys_alloc if not using locks */\n#endif\n\n  if (!PREACTION(gm)) {\n    void* mem;\n    size_t nb;\n    if (bytes <= MAX_SMALL_REQUEST) {\n      bindex_t idx;\n      binmap_t smallbits;\n      nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);\n      idx = small_index(nb);\n      smallbits = gm->smallmap >> idx;\n\n      if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */\n        mchunkptr b, p;\n        idx += ~smallbits & 1;       /* Uses next bin if idx empty */\n        b = smallbin_at(gm, idx);\n        p = b->fd;\n        assert(chunksize(p) == small_index2size(idx));\n        unlink_first_small_chunk(gm, b, p, idx);\n        set_inuse_and_pinuse(gm, p, small_index2size(idx));\n        mem = chunk2mem(p);\n        check_malloced_chunk(gm, mem, nb);\n        goto postaction;\n      }\n\n      else if (nb > gm->dvsize) {\n        if (smallbits != 0) { /* Use chunk in next nonempty smallbin */\n          mchunkptr b, p, r;\n          size_t rsize;\n          bindex_t i;\n          binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx));\n          binmap_t leastbit = least_bit(leftbits);\n          compute_bit2idx(leastbit, i);\n          b = smallbin_at(gm, i);\n          p = b->fd;\n          assert(chunksize(p) == small_index2size(i));\n          unlink_first_small_chunk(gm, b, p, i);\n          rsize = small_index2size(i) - nb;\n          /* Fit here cannot be remainderless if 4byte sizes */\n          if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE)\n            set_inuse_and_pinuse(gm, p, small_index2size(i));\n          else {\n            set_size_and_pinuse_of_inuse_chunk(gm, p, nb);\n            r = chunk_plus_offset(p, nb);\n            set_size_and_pinuse_of_free_chunk(r, rsize);\n            replace_dv(gm, r, rsize);\n          }\n          mem = chunk2mem(p);\n          check_malloced_chunk(gm, mem, nb);\n          goto postaction;\n        }\n\n        else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) {\n          check_malloced_chunk(gm, mem, nb);\n          goto postaction;\n        }\n      }\n    }\n    else if (bytes >= MAX_REQUEST)\n      nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */\n    else {\n      nb = pad_request(bytes);\n      if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) {\n        check_malloced_chunk(gm, mem, nb);\n        goto postaction;\n      }\n    }\n\n    if (nb <= gm->dvsize) {\n      size_t rsize = gm->dvsize - nb;\n      mchunkptr p = gm->dv;\n      if (rsize >= MIN_CHUNK_SIZE) { /* split dv */\n        mchunkptr r = gm->dv = chunk_plus_offset(p, nb);\n        gm->dvsize = rsize;\n        set_size_and_pinuse_of_free_chunk(r, rsize);\n        set_size_and_pinuse_of_inuse_chunk(gm, p, nb);\n      }\n      else { /* exhaust dv */\n        size_t dvs = gm->dvsize;\n        gm->dvsize = 0;\n        gm->dv = 0;\n        set_inuse_and_pinuse(gm, p, dvs);\n      }\n      mem = chunk2mem(p);\n      check_malloced_chunk(gm, mem, nb);\n      goto postaction;\n    }\n\n    else if (nb < gm->topsize) { /* Split top */\n      size_t rsize = gm->topsize -= nb;\n      mchunkptr p = gm->top;\n      mchunkptr r = gm->top = chunk_plus_offset(p, nb);\n      r->head = rsize | PINUSE_BIT;\n      set_size_and_pinuse_of_inuse_chunk(gm, p, nb);\n      mem = chunk2mem(p);\n      check_top_chunk(gm, gm->top);\n      check_malloced_chunk(gm, mem, nb);\n      goto postaction;\n    }\n\n    mem = sys_alloc(gm, nb);\n\n  postaction:\n    POSTACTION(gm);\n    return mem;\n  }\n\n  return 0;\n}\n\n/* ---------------------------- free --------------------------- */\n\nvoid dlfree(void* mem) {\n  /*\n     Consolidate freed chunks with preceeding or succeeding bordering\n     free chunks, if they exist, and then place in a bin.  Intermixed\n     with special cases for top, dv, mmapped chunks, and usage errors.\n  */\n\n  if (mem != 0) {\n    mchunkptr p  = mem2chunk(mem);\n#if FOOTERS\n    mstate fm = get_mstate_for(p);\n    if (!ok_magic(fm)) {\n      USAGE_ERROR_ACTION(fm, p);\n      return;\n    }\n#else /* FOOTERS */\n#define fm gm\n#endif /* FOOTERS */\n    if (!PREACTION(fm)) {\n      check_inuse_chunk(fm, p);\n      if (RTCHECK(ok_address(fm, p) && ok_inuse(p))) {\n        size_t psize = chunksize(p);\n        mchunkptr next = chunk_plus_offset(p, psize);\n        if (!pinuse(p)) {\n          size_t prevsize = p->prev_foot;\n          if (is_mmapped(p)) {\n            psize += prevsize + MMAP_FOOT_PAD;\n            if (CALL_MUNMAP((char*)p - prevsize, psize) == 0)\n              fm->footprint -= psize;\n            goto postaction;\n          }\n          else {\n            mchunkptr prev = chunk_minus_offset(p, prevsize);\n            psize += prevsize;\n            p = prev;\n            if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */\n              if (p != fm->dv) {\n                unlink_chunk(fm, p, prevsize);\n              }\n              else if ((next->head & INUSE_BITS) == INUSE_BITS) {\n                fm->dvsize = psize;\n                set_free_with_pinuse(p, psize, next);\n                goto postaction;\n              }\n            }\n            else\n              goto erroraction;\n          }\n        }\n\n        if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) {\n          if (!cinuse(next)) {  /* consolidate forward */\n            if (next == fm->top) {\n              size_t tsize = fm->topsize += psize;\n              fm->top = p;\n              p->head = tsize | PINUSE_BIT;\n              if (p == fm->dv) {\n                fm->dv = 0;\n                fm->dvsize = 0;\n              }\n              if (should_trim(fm, tsize))\n                sys_trim(fm, 0);\n              goto postaction;\n            }\n            else if (next == fm->dv) {\n              size_t dsize = fm->dvsize += psize;\n              fm->dv = p;\n              set_size_and_pinuse_of_free_chunk(p, dsize);\n              goto postaction;\n            }\n            else {\n              size_t nsize = chunksize(next);\n              psize += nsize;\n              unlink_chunk(fm, next, nsize);\n              set_size_and_pinuse_of_free_chunk(p, psize);\n              if (p == fm->dv) {\n                fm->dvsize = psize;\n                goto postaction;\n              }\n            }\n          }\n          else\n            set_free_with_pinuse(p, psize, next);\n\n          if (is_small(psize)) {\n            insert_small_chunk(fm, p, psize);\n            check_free_chunk(fm, p);\n          }\n          else {\n            tchunkptr tp = (tchunkptr)p;\n            insert_large_chunk(fm, tp, psize);\n            check_free_chunk(fm, p);\n            if (--fm->release_checks == 0)\n              release_unused_segments(fm);\n          }\n          goto postaction;\n        }\n      }\n    erroraction:\n      USAGE_ERROR_ACTION(fm, p);\n    postaction:\n      POSTACTION(fm);\n    }\n  }\n#if !FOOTERS\n#undef fm\n#endif /* FOOTERS */\n}\n\nvoid* dlcalloc(size_t n_elements, size_t elem_size) {\n  void* mem;\n  size_t req = 0;\n  if (n_elements != 0) {\n    req = n_elements * elem_size;\n    if (((n_elements | elem_size) & ~(size_t)0xffff) &&\n        (req / n_elements != elem_size))\n      req = MAX_SIZE_T; /* force downstream failure on overflow */\n  }\n  mem = dlmalloc(req);\n  if (mem != 0 && calloc_must_clear(mem2chunk(mem)))\n    memset(mem, 0, req);\n  return mem;\n}\n\n#endif /* !ONLY_MSPACES */\n\n/* ------------ Internal support for realloc, memalign, etc -------------- */\n\n/* Try to realloc; only in-place unless can_move true */\nstatic mchunkptr try_realloc_chunk(mstate m, mchunkptr p, size_t nb,\n                                   int can_move) {\n  mchunkptr newp = 0;\n  size_t oldsize = chunksize(p);\n  mchunkptr next = chunk_plus_offset(p, oldsize);\n  if (RTCHECK(ok_address(m, p) && ok_inuse(p) &&\n              ok_next(p, next) && ok_pinuse(next))) {\n    if (is_mmapped(p)) {\n      newp = mmap_resize(m, p, nb, can_move);\n    }\n    else if (oldsize >= nb) {             /* already big enough */\n      size_t rsize = oldsize - nb;\n      if (rsize >= MIN_CHUNK_SIZE) {      /* split off remainder */\n        mchunkptr r = chunk_plus_offset(p, nb);\n        set_inuse(m, p, nb);\n        set_inuse(m, r, rsize);\n        dispose_chunk(m, r, rsize);\n      }\n      newp = p;\n    }\n    else if (next == m->top) {  /* extend into top */\n      if (oldsize + m->topsize > nb) {\n        size_t newsize = oldsize + m->topsize;\n        size_t newtopsize = newsize - nb;\n        mchunkptr newtop = chunk_plus_offset(p, nb);\n        set_inuse(m, p, nb);\n        newtop->head = newtopsize |PINUSE_BIT;\n        m->top = newtop;\n        m->topsize = newtopsize;\n        newp = p;\n      }\n    }\n    else if (next == m->dv) { /* extend into dv */\n      size_t dvs = m->dvsize;\n      if (oldsize + dvs >= nb) {\n        size_t dsize = oldsize + dvs - nb;\n        if (dsize >= MIN_CHUNK_SIZE) {\n          mchunkptr r = chunk_plus_offset(p, nb);\n          mchunkptr n = chunk_plus_offset(r, dsize);\n          set_inuse(m, p, nb);\n          set_size_and_pinuse_of_free_chunk(r, dsize);\n          clear_pinuse(n);\n          m->dvsize = dsize;\n          m->dv = r;\n        }\n        else { /* exhaust dv */\n          size_t newsize = oldsize + dvs;\n          set_inuse(m, p, newsize);\n          m->dvsize = 0;\n          m->dv = 0;\n        }\n        newp = p;\n      }\n    }\n    else if (!cinuse(next)) { /* extend into next free chunk */\n      size_t nextsize = chunksize(next);\n      if (oldsize + nextsize >= nb) {\n        size_t rsize = oldsize + nextsize - nb;\n        unlink_chunk(m, next, nextsize);\n        if (rsize < MIN_CHUNK_SIZE) {\n          size_t newsize = oldsize + nextsize;\n          set_inuse(m, p, newsize);\n        }\n        else {\n          mchunkptr r = chunk_plus_offset(p, nb);\n          set_inuse(m, p, nb);\n          set_inuse(m, r, rsize);\n          dispose_chunk(m, r, rsize);\n        }\n        newp = p;\n      }\n    }\n  }\n  else {\n    USAGE_ERROR_ACTION(m, chunk2mem(p));\n  }\n  return newp;\n}\n\nstatic void* internal_memalign(mstate m, size_t alignment, size_t bytes) {\n  void* mem = 0;\n  if (alignment <  MIN_CHUNK_SIZE) /* must be at least a minimum chunk size */\n    alignment = MIN_CHUNK_SIZE;\n  if ((alignment & (alignment-SIZE_T_ONE)) != 0) {/* Ensure a power of 2 */\n    size_t a = MALLOC_ALIGNMENT << 1;\n    while (a < alignment) a <<= 1;\n    alignment = a;\n  }\n  if (bytes >= MAX_REQUEST - alignment) {\n    if (m != 0)  { /* Test isn't needed but avoids compiler warning */\n      MALLOC_FAILURE_ACTION;\n    }\n  }\n  else {\n    size_t nb = request2size(bytes);\n    size_t req = nb + alignment + MIN_CHUNK_SIZE - CHUNK_OVERHEAD;\n    mem = internal_malloc(m, req);\n    if (mem != 0) {\n      mchunkptr p = mem2chunk(mem);\n      if (PREACTION(m))\n        return 0;\n      if ((((size_t)(mem)) & (alignment - 1)) != 0) { /* misaligned */\n        /*\n          Find an aligned spot inside chunk.  Since we need to give\n          back leading space in a chunk of at least MIN_CHUNK_SIZE, if\n          the first calculation places us at a spot with less than\n          MIN_CHUNK_SIZE leader, we can move to the next aligned spot.\n          We've allocated enough total room so that this is always\n          possible.\n        */\n        char* br = (char*)mem2chunk((size_t)(((size_t)((char*)mem + alignment -\n                                                       SIZE_T_ONE)) &\n                                             -alignment));\n        char* pos = ((size_t)(br - (char*)(p)) >= MIN_CHUNK_SIZE)?\n          br : br+alignment;\n        mchunkptr newp = (mchunkptr)pos;\n        size_t leadsize = pos - (char*)(p);\n        size_t newsize = chunksize(p) - leadsize;\n\n        if (is_mmapped(p)) { /* For mmapped chunks, just adjust offset */\n          newp->prev_foot = p->prev_foot + leadsize;\n          newp->head = newsize;\n        }\n        else { /* Otherwise, give back leader, use the rest */\n          set_inuse(m, newp, newsize);\n          set_inuse(m, p, leadsize);\n          dispose_chunk(m, p, leadsize);\n        }\n        p = newp;\n      }\n\n      /* Give back spare room at the end */\n      if (!is_mmapped(p)) {\n        size_t size = chunksize(p);\n        if (size > nb + MIN_CHUNK_SIZE) {\n          size_t remainder_size = size - nb;\n          mchunkptr remainder = chunk_plus_offset(p, nb);\n          set_inuse(m, p, nb);\n          set_inuse(m, remainder, remainder_size);\n          dispose_chunk(m, remainder, remainder_size);\n        }\n      }\n\n      mem = chunk2mem(p);\n      assert (chunksize(p) >= nb);\n      assert(((size_t)mem & (alignment - 1)) == 0);\n      check_inuse_chunk(m, p);\n      POSTACTION(m);\n    }\n  }\n  return mem;\n}\n\n/*\n  Common support for independent_X routines, handling\n    all of the combinations that can result.\n  The opts arg has:\n    bit 0 set if all elements are same size (using sizes[0])\n    bit 1 set if elements should be zeroed\n*/\nstatic void** ialloc(mstate m,\n                     size_t n_elements,\n                     size_t* sizes,\n                     int opts,\n                     void* chunks[]) {\n\n  size_t    element_size;   /* chunksize of each element, if all same */\n  size_t    contents_size;  /* total size of elements */\n  size_t    array_size;     /* request size of pointer array */\n  void*     mem;            /* malloced aggregate space */\n  mchunkptr p;              /* corresponding chunk */\n  size_t    remainder_size; /* remaining bytes while splitting */\n  void**    marray;         /* either \"chunks\" or malloced ptr array */\n  mchunkptr array_chunk;    /* chunk for malloced ptr array */\n  flag_t    was_enabled;    /* to disable mmap */\n  size_t    size;\n  size_t    i;\n\n  ensure_initialization();\n  /* compute array length, if needed */\n  if (chunks != 0) {\n    if (n_elements == 0)\n      return chunks; /* nothing to do */\n    marray = chunks;\n    array_size = 0;\n  }\n  else {\n    /* if empty req, must still return chunk representing empty array */\n    if (n_elements == 0)\n      return (void**)internal_malloc(m, 0);\n    marray = 0;\n    array_size = request2size(n_elements * (sizeof(void*)));\n  }\n\n  /* compute total element size */\n  if (opts & 0x1) { /* all-same-size */\n    element_size = request2size(*sizes);\n    contents_size = n_elements * element_size;\n  }\n  else { /* add up all the sizes */\n    element_size = 0;\n    contents_size = 0;\n    for (i = 0; i != n_elements; ++i)\n      contents_size += request2size(sizes[i]);\n  }\n\n  size = contents_size + array_size;\n\n  /*\n     Allocate the aggregate chunk.  First disable direct-mmapping so\n     malloc won't use it, since we would not be able to later\n     free/realloc space internal to a segregated mmap region.\n  */\n  was_enabled = use_mmap(m);\n  disable_mmap(m);\n  mem = internal_malloc(m, size - CHUNK_OVERHEAD);\n  if (was_enabled)\n    enable_mmap(m);\n  if (mem == 0)\n    return 0;\n\n  if (PREACTION(m)) return 0;\n  p = mem2chunk(mem);\n  remainder_size = chunksize(p);\n\n  assert(!is_mmapped(p));\n\n  if (opts & 0x2) {       /* optionally clear the elements */\n    memset((size_t*)mem, 0, remainder_size - SIZE_T_SIZE - array_size);\n  }\n\n  /* If not provided, allocate the pointer array as final part of chunk */\n  if (marray == 0) {\n    size_t  array_chunk_size;\n    array_chunk = chunk_plus_offset(p, contents_size);\n    array_chunk_size = remainder_size - contents_size;\n    marray = (void**) (chunk2mem(array_chunk));\n    set_size_and_pinuse_of_inuse_chunk(m, array_chunk, array_chunk_size);\n    remainder_size = contents_size;\n  }\n\n  /* split out elements */\n  for (i = 0; ; ++i) {\n    marray[i] = chunk2mem(p);\n    if (i != n_elements-1) {\n      if (element_size != 0)\n        size = element_size;\n      else\n        size = request2size(sizes[i]);\n      remainder_size -= size;\n      set_size_and_pinuse_of_inuse_chunk(m, p, size);\n      p = chunk_plus_offset(p, size);\n    }\n    else { /* the final element absorbs any overallocation slop */\n      set_size_and_pinuse_of_inuse_chunk(m, p, remainder_size);\n      break;\n    }\n  }\n\n#if DEBUG\n  if (marray != chunks) {\n    /* final element must have exactly exhausted chunk */\n    if (element_size != 0) {\n      assert(remainder_size == element_size);\n    }\n    else {\n      assert(remainder_size == request2size(sizes[i]));\n    }\n    check_inuse_chunk(m, mem2chunk(marray));\n  }\n  for (i = 0; i != n_elements; ++i)\n    check_inuse_chunk(m, mem2chunk(marray[i]));\n\n#endif /* DEBUG */\n\n  POSTACTION(m);\n  return marray;\n}\n\n/* Try to free all pointers in the given array.\n   Note: this could be made faster, by delaying consolidation,\n   at the price of disabling some user integrity checks, We\n   still optimize some consolidations by combining adjacent\n   chunks before freeing, which will occur often if allocated\n   with ialloc or the array is sorted.\n*/\nstatic size_t internal_bulk_free(mstate m, void* array[], size_t nelem) {\n  size_t unfreed = 0;\n  if (!PREACTION(m)) {\n    void** a;\n    void** fence = &(array[nelem]);\n    for (a = array; a != fence; ++a) {\n      void* mem = *a;\n      if (mem != 0) {\n        mchunkptr p = mem2chunk(mem);\n        size_t psize = chunksize(p);\n#if FOOTERS\n        if (get_mstate_for(p) != m) {\n          ++unfreed;\n          continue;\n        }\n#endif\n        check_inuse_chunk(m, p);\n        *a = 0;\n        if (RTCHECK(ok_address(m, p) && ok_inuse(p))) {\n          void ** b = a + 1; /* try to merge with next chunk */\n          mchunkptr next = next_chunk(p);\n          if (b != fence && *b == chunk2mem(next)) {\n            size_t newsize = chunksize(next) + psize;\n            set_inuse(m, p, newsize);\n            *b = chunk2mem(p);\n          }\n          else\n            dispose_chunk(m, p, psize);\n        }\n        else {\n          CORRUPTION_ERROR_ACTION(m);\n          break;\n        }\n      }\n    }\n    if (should_trim(m, m->topsize))\n      sys_trim(m, 0);\n    POSTACTION(m);\n  }\n  return unfreed;\n}\n\n/* Traversal */\n#if MALLOC_INSPECT_ALL\nstatic void internal_inspect_all(mstate m,\n                                 void(*handler)(void *start,\n                                                void *end,\n                                                size_t used_bytes,\n                                                void* callback_arg),\n                                 void* arg) {\n  if (is_initialized(m)) {\n    mchunkptr top = m->top;\n    msegmentptr s;\n    for (s = &m->seg; s != 0; s = s->next) {\n      mchunkptr q = align_as_chunk(s->base);\n      while (segment_holds(s, q) && q->head != FENCEPOST_HEAD) {\n        mchunkptr next = next_chunk(q);\n        size_t sz = chunksize(q);\n        size_t used;\n        void* start;\n        if (is_inuse(q)) {\n          used = sz - CHUNK_OVERHEAD; /* must not be mmapped */\n          start = chunk2mem(q);\n        }\n        else {\n          used = 0;\n          if (is_small(sz)) {     /* offset by possible bookkeeping */\n            start = (void*)((char*)q + sizeof(struct malloc_chunk));\n          }\n          else {\n            start = (void*)((char*)q + sizeof(struct malloc_tree_chunk));\n          }\n        }\n        if (start < (void*)next)  /* skip if all space is bookkeeping */\n          handler(start, next, used, arg);\n        if (q == top)\n          break;\n        q = next;\n      }\n    }\n  }\n}\n#endif /* MALLOC_INSPECT_ALL */\n\n/* ------------------ Exported realloc, memalign, etc -------------------- */\n\n#if !ONLY_MSPACES\n\nvoid* dlrealloc(void* oldmem, size_t bytes) {\n  void* mem = 0;\n  if (oldmem == 0) {\n    mem = dlmalloc(bytes);\n  }\n  else if (bytes >= MAX_REQUEST) {\n    MALLOC_FAILURE_ACTION;\n  }\n#ifdef REALLOC_ZERO_BYTES_FREES\n  else if (bytes == 0) {\n    dlfree(oldmem);\n  }\n#endif /* REALLOC_ZERO_BYTES_FREES */\n  else {\n    size_t nb = request2size(bytes);\n    mchunkptr oldp = mem2chunk(oldmem);\n#if ! FOOTERS\n    mstate m = gm;\n#else /* FOOTERS */\n    mstate m = get_mstate_for(oldp);\n    if (!ok_magic(m)) {\n      USAGE_ERROR_ACTION(m, oldmem);\n      return 0;\n    }\n#endif /* FOOTERS */\n    if (!PREACTION(m)) {\n      mchunkptr newp = try_realloc_chunk(m, oldp, nb, 1);\n      POSTACTION(m);\n      if (newp != 0) {\n        check_inuse_chunk(m, newp);\n        mem = chunk2mem(newp);\n      }\n      else {\n        mem = internal_malloc(m, bytes);\n        if (mem != 0) {\n          size_t oc = chunksize(oldp) - overhead_for(oldp);\n          memcpy(mem, oldmem, (oc < bytes)? oc : bytes);\n          internal_free(m, oldmem);\n        }\n      }\n    }\n  }\n  return mem;\n}\n\nvoid* dlrealloc_in_place(void* oldmem, size_t bytes) {\n  void* mem = 0;\n  if (oldmem != 0) {\n    if (bytes >= MAX_REQUEST) {\n      MALLOC_FAILURE_ACTION;\n    }\n    else {\n      size_t nb = request2size(bytes);\n      mchunkptr oldp = mem2chunk(oldmem);\n#if ! FOOTERS\n      mstate m = gm;\n#else /* FOOTERS */\n      mstate m = get_mstate_for(oldp);\n      if (!ok_magic(m)) {\n        USAGE_ERROR_ACTION(m, oldmem);\n        return 0;\n      }\n#endif /* FOOTERS */\n      if (!PREACTION(m)) {\n        mchunkptr newp = try_realloc_chunk(m, oldp, nb, 0);\n        POSTACTION(m);\n        if (newp == oldp) {\n          check_inuse_chunk(m, newp);\n          mem = oldmem;\n        }\n      }\n    }\n  }\n  return mem;\n}\n\nvoid* dlmemalign(size_t alignment, size_t bytes) {\n  if (alignment <= MALLOC_ALIGNMENT) {\n    return dlmalloc(bytes);\n  }\n  return internal_memalign(gm, alignment, bytes);\n}\n\nint dlposix_memalign(void** pp, size_t alignment, size_t bytes) {\n  void* mem = 0;\n  if (alignment == MALLOC_ALIGNMENT)\n    mem = dlmalloc(bytes);\n  else {\n    size_t d = alignment / sizeof(void*);\n    size_t r = alignment % sizeof(void*);\n    if (r != 0 || d == 0 || (d & (d-SIZE_T_ONE)) != 0)\n      return EINVAL;\n    else if (bytes <= MAX_REQUEST - alignment) {\n      if (alignment <  MIN_CHUNK_SIZE)\n        alignment = MIN_CHUNK_SIZE;\n      mem = internal_memalign(gm, alignment, bytes);\n    }\n  }\n  if (mem == 0)\n    return ENOMEM;\n  else {\n    *pp = mem;\n    return 0;\n  }\n}\n\nvoid* dlvalloc(size_t bytes) {\n  size_t pagesz;\n  ensure_initialization();\n  pagesz = mparams.page_size;\n  return dlmemalign(pagesz, bytes);\n}\n\nvoid* dlpvalloc(size_t bytes) {\n  size_t pagesz;\n  ensure_initialization();\n  pagesz = mparams.page_size;\n  return dlmemalign(pagesz, (bytes + pagesz - SIZE_T_ONE) & ~(pagesz - SIZE_T_ONE));\n}\n\nvoid** dlindependent_calloc(size_t n_elements, size_t elem_size,\n                            void* chunks[]) {\n  size_t sz = elem_size; /* serves as 1-element array */\n  return ialloc(gm, n_elements, &sz, 3, chunks);\n}\n\nvoid** dlindependent_comalloc(size_t n_elements, size_t sizes[],\n                              void* chunks[]) {\n  return ialloc(gm, n_elements, sizes, 0, chunks);\n}\n\nsize_t dlbulk_free(void* array[], size_t nelem) {\n  return internal_bulk_free(gm, array, nelem);\n}\n\n#if MALLOC_INSPECT_ALL\nvoid dlmalloc_inspect_all(void(*handler)(void *start,\n                                         void *end,\n                                         size_t used_bytes,\n                                         void* callback_arg),\n                          void* arg) {\n  ensure_initialization();\n  if (!PREACTION(gm)) {\n    internal_inspect_all(gm, handler, arg);\n    POSTACTION(gm);\n  }\n}\n#endif /* MALLOC_INSPECT_ALL */\n\nint dlmalloc_trim(size_t pad) {\n  int result = 0;\n  ensure_initialization();\n  if (!PREACTION(gm)) {\n    result = sys_trim(gm, pad);\n    POSTACTION(gm);\n  }\n  return result;\n}\n\nsize_t dlmalloc_footprint(void) {\n  return gm->footprint;\n}\n\nsize_t dlmalloc_max_footprint(void) {\n  return gm->max_footprint;\n}\n\nsize_t dlmalloc_footprint_limit(void) {\n  size_t maf = gm->footprint_limit;\n  return maf == 0 ? MAX_SIZE_T : maf;\n}\n\nsize_t dlmalloc_set_footprint_limit(size_t bytes) {\n  size_t result;  /* invert sense of 0 */\n  if (bytes == 0)\n    result = granularity_align(1); /* Use minimal size */\n  if (bytes == MAX_SIZE_T)\n    result = 0;                    /* disable */\n  else\n    result = granularity_align(bytes);\n  return gm->footprint_limit = result;\n}\n\n#if !NO_MALLINFO\nstruct mallinfo dlmallinfo(void) {\n  return internal_mallinfo(gm);\n}\n#endif /* NO_MALLINFO */\n\n#if !NO_MALLOC_STATS\nvoid dlmalloc_stats() {\n  internal_malloc_stats(gm);\n}\n#endif /* NO_MALLOC_STATS */\n\nint dlmallopt(int param_number, int value) {\n  return change_mparam(param_number, value);\n}\n\nsize_t dlmalloc_usable_size(void* mem) {\n  if (mem != 0) {\n    mchunkptr p = mem2chunk(mem);\n    if (is_inuse(p))\n      return chunksize(p) - overhead_for(p);\n  }\n  return 0;\n}\n\n#endif /* !ONLY_MSPACES */\n\n/* ----------------------------- user mspaces ---------------------------- */\n\n#if MSPACES\n\nstatic mstate init_user_mstate(char* tbase, size_t tsize) {\n  size_t msize = pad_request(sizeof(struct malloc_state));\n  mchunkptr mn;\n  mchunkptr msp = align_as_chunk(tbase);\n  mstate m = (mstate)(chunk2mem(msp));\n  memset(m, 0, msize);\n  (void)INITIAL_LOCK(&m->mutex);\n  msp->head = (msize|INUSE_BITS);\n  m->seg.base = m->least_addr = tbase;\n  m->seg.size = m->footprint = m->max_footprint = tsize;\n  m->magic = mparams.magic;\n  m->release_checks = MAX_RELEASE_CHECK_RATE;\n  m->mflags = mparams.default_mflags;\n  m->extp = 0;\n  m->exts = 0;\n  disable_contiguous(m);\n  init_bins(m);\n  mn = next_chunk(mem2chunk(m));\n  init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) - TOP_FOOT_SIZE);\n  check_top_chunk(m, m->top);\n  return m;\n}\n\nmspace create_mspace(size_t capacity, int locked) {\n  mstate m = 0;\n  size_t msize;\n  ensure_initialization();\n  msize = pad_request(sizeof(struct malloc_state));\n  if (capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) {\n    size_t rs = ((capacity == 0)? mparams.granularity :\n                 (capacity + TOP_FOOT_SIZE + msize));\n    size_t tsize = granularity_align(rs);\n    char* tbase = (char*)(CALL_MMAP(tsize));\n    if (tbase != CMFAIL) {\n      m = init_user_mstate(tbase, tsize);\n      m->seg.sflags = USE_MMAP_BIT;\n      set_lock(m, locked);\n    }\n  }\n  return (mspace)m;\n}\n\nmspace create_mspace_with_base(void* base, size_t capacity, int locked) {\n  mstate m = 0;\n  size_t msize;\n  ensure_initialization();\n  msize = pad_request(sizeof(struct malloc_state));\n  if (capacity > msize + TOP_FOOT_SIZE &&\n      capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) {\n    m = init_user_mstate((char*)base, capacity);\n    m->seg.sflags = EXTERN_BIT;\n    set_lock(m, locked);\n  }\n  return (mspace)m;\n}\n\nint mspace_track_large_chunks(mspace msp, int enable) {\n  int ret = 0;\n  mstate ms = (mstate)msp;\n  if (!PREACTION(ms)) {\n    if (!use_mmap(ms)) {\n      ret = 1;\n    }\n    if (!enable) {\n      enable_mmap(ms);\n    } else {\n      disable_mmap(ms);\n    }\n    POSTACTION(ms);\n  }\n  return ret;\n}\n\nsize_t destroy_mspace(mspace msp) {\n  size_t freed = 0;\n  mstate ms = (mstate)msp;\n  if (ok_magic(ms)) {\n    msegmentptr sp = &ms->seg;\n    (void)DESTROY_LOCK(&ms->mutex); /* destroy before unmapped */\n    while (sp != 0) {\n      char* base = sp->base;\n      size_t size = sp->size;\n      flag_t flag = sp->sflags;\n      (void)base; /* placate people compiling -Wunused-variable */\n      sp = sp->next;\n      if ((flag & USE_MMAP_BIT) && !(flag & EXTERN_BIT) &&\n          CALL_MUNMAP(base, size) == 0)\n        freed += size;\n    }\n  }\n  else {\n    USAGE_ERROR_ACTION(ms,ms);\n  }\n  return freed;\n}\n\n/*\n  mspace versions of routines are near-clones of the global\n  versions. This is not so nice but better than the alternatives.\n*/\n\nvoid* mspace_malloc(mspace msp, size_t bytes) {\n  mstate ms = (mstate)msp;\n  if (!ok_magic(ms)) {\n    USAGE_ERROR_ACTION(ms,ms);\n    return 0;\n  }\n  if (!PREACTION(ms)) {\n    void* mem;\n    size_t nb;\n    if (bytes <= MAX_SMALL_REQUEST) {\n      bindex_t idx;\n      binmap_t smallbits;\n      nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes);\n      idx = small_index(nb);\n      smallbits = ms->smallmap >> idx;\n\n      if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */\n        mchunkptr b, p;\n        idx += ~smallbits & 1;       /* Uses next bin if idx empty */\n        b = smallbin_at(ms, idx);\n        p = b->fd;\n        assert(chunksize(p) == small_index2size(idx));\n        unlink_first_small_chunk(ms, b, p, idx);\n        set_inuse_and_pinuse(ms, p, small_index2size(idx));\n        mem = chunk2mem(p);\n        check_malloced_chunk(ms, mem, nb);\n        goto postaction;\n      }\n\n      else if (nb > ms->dvsize) {\n        if (smallbits != 0) { /* Use chunk in next nonempty smallbin */\n          mchunkptr b, p, r;\n          size_t rsize;\n          bindex_t i;\n          binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx));\n          binmap_t leastbit = least_bit(leftbits);\n          compute_bit2idx(leastbit, i);\n          b = smallbin_at(ms, i);\n          p = b->fd;\n          assert(chunksize(p) == small_index2size(i));\n          unlink_first_small_chunk(ms, b, p, i);\n          rsize = small_index2size(i) - nb;\n          /* Fit here cannot be remainderless if 4byte sizes */\n          if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE)\n            set_inuse_and_pinuse(ms, p, small_index2size(i));\n          else {\n            set_size_and_pinuse_of_inuse_chunk(ms, p, nb);\n            r = chunk_plus_offset(p, nb);\n            set_size_and_pinuse_of_free_chunk(r, rsize);\n            replace_dv(ms, r, rsize);\n          }\n          mem = chunk2mem(p);\n          check_malloced_chunk(ms, mem, nb);\n          goto postaction;\n        }\n\n        else if (ms->treemap != 0 && (mem = tmalloc_small(ms, nb)) != 0) {\n          check_malloced_chunk(ms, mem, nb);\n          goto postaction;\n        }\n      }\n    }\n    else if (bytes >= MAX_REQUEST)\n      nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */\n    else {\n      nb = pad_request(bytes);\n      if (ms->treemap != 0 && (mem = tmalloc_large(ms, nb)) != 0) {\n        check_malloced_chunk(ms, mem, nb);\n        goto postaction;\n      }\n    }\n\n    if (nb <= ms->dvsize) {\n      size_t rsize = ms->dvsize - nb;\n      mchunkptr p = ms->dv;\n      if (rsize >= MIN_CHUNK_SIZE) { /* split dv */\n        mchunkptr r = ms->dv = chunk_plus_offset(p, nb);\n        ms->dvsize = rsize;\n        set_size_and_pinuse_of_free_chunk(r, rsize);\n        set_size_and_pinuse_of_inuse_chunk(ms, p, nb);\n      }\n      else { /* exhaust dv */\n        size_t dvs = ms->dvsize;\n        ms->dvsize = 0;\n        ms->dv = 0;\n        set_inuse_and_pinuse(ms, p, dvs);\n      }\n      mem = chunk2mem(p);\n      check_malloced_chunk(ms, mem, nb);\n      goto postaction;\n    }\n\n    else if (nb < ms->topsize) { /* Split top */\n      size_t rsize = ms->topsize -= nb;\n      mchunkptr p = ms->top;\n      mchunkptr r = ms->top = chunk_plus_offset(p, nb);\n      r->head = rsize | PINUSE_BIT;\n      set_size_and_pinuse_of_inuse_chunk(ms, p, nb);\n      mem = chunk2mem(p);\n      check_top_chunk(ms, ms->top);\n      check_malloced_chunk(ms, mem, nb);\n      goto postaction;\n    }\n\n    mem = sys_alloc(ms, nb);\n\n  postaction:\n    POSTACTION(ms);\n    return mem;\n  }\n\n  return 0;\n}\n\nvoid mspace_free(mspace msp, void* mem) {\n  if (mem != 0) {\n    mchunkptr p  = mem2chunk(mem);\n#if FOOTERS\n    mstate fm = get_mstate_for(p);\n    (void)msp; /* placate people compiling -Wunused */\n#else /* FOOTERS */\n    mstate fm = (mstate)msp;\n#endif /* FOOTERS */\n    if (!ok_magic(fm)) {\n      USAGE_ERROR_ACTION(fm, p);\n      return;\n    }\n    if (!PREACTION(fm)) {\n      check_inuse_chunk(fm, p);\n      if (RTCHECK(ok_address(fm, p) && ok_inuse(p))) {\n        size_t psize = chunksize(p);\n        mchunkptr next = chunk_plus_offset(p, psize);\n        if (!pinuse(p)) {\n          size_t prevsize = p->prev_foot;\n          if (is_mmapped(p)) {\n            psize += prevsize + MMAP_FOOT_PAD;\n            if (CALL_MUNMAP((char*)p - prevsize, psize) == 0)\n              fm->footprint -= psize;\n            goto postaction;\n          }\n          else {\n            mchunkptr prev = chunk_minus_offset(p, prevsize);\n            psize += prevsize;\n            p = prev;\n            if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */\n              if (p != fm->dv) {\n                unlink_chunk(fm, p, prevsize);\n              }\n              else if ((next->head & INUSE_BITS) == INUSE_BITS) {\n                fm->dvsize = psize;\n                set_free_with_pinuse(p, psize, next);\n                goto postaction;\n              }\n            }\n            else\n              goto erroraction;\n          }\n        }\n\n        if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) {\n          if (!cinuse(next)) {  /* consolidate forward */\n            if (next == fm->top) {\n              size_t tsize = fm->topsize += psize;\n              fm->top = p;\n              p->head = tsize | PINUSE_BIT;\n              if (p == fm->dv) {\n                fm->dv = 0;\n                fm->dvsize = 0;\n              }\n              if (should_trim(fm, tsize))\n                sys_trim(fm, 0);\n              goto postaction;\n            }\n            else if (next == fm->dv) {\n              size_t dsize = fm->dvsize += psize;\n              fm->dv = p;\n              set_size_and_pinuse_of_free_chunk(p, dsize);\n              goto postaction;\n            }\n            else {\n              size_t nsize = chunksize(next);\n              psize += nsize;\n              unlink_chunk(fm, next, nsize);\n              set_size_and_pinuse_of_free_chunk(p, psize);\n              if (p == fm->dv) {\n                fm->dvsize = psize;\n                goto postaction;\n              }\n            }\n          }\n          else\n            set_free_with_pinuse(p, psize, next);\n\n          if (is_small(psize)) {\n            insert_small_chunk(fm, p, psize);\n            check_free_chunk(fm, p);\n          }\n          else {\n            tchunkptr tp = (tchunkptr)p;\n            insert_large_chunk(fm, tp, psize);\n            check_free_chunk(fm, p);\n            if (--fm->release_checks == 0)\n              release_unused_segments(fm);\n          }\n          goto postaction;\n        }\n      }\n    erroraction:\n      USAGE_ERROR_ACTION(fm, p);\n    postaction:\n      POSTACTION(fm);\n    }\n  }\n}\n\nvoid* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size) {\n  void* mem;\n  size_t req = 0;\n  mstate ms = (mstate)msp;\n  if (!ok_magic(ms)) {\n    USAGE_ERROR_ACTION(ms,ms);\n    return 0;\n  }\n  if (n_elements != 0) {\n    req = n_elements * elem_size;\n    if (((n_elements | elem_size) & ~(size_t)0xffff) &&\n        (req / n_elements != elem_size))\n      req = MAX_SIZE_T; /* force downstream failure on overflow */\n  }\n  mem = internal_malloc(ms, req);\n  if (mem != 0 && calloc_must_clear(mem2chunk(mem)))\n    memset(mem, 0, req);\n  return mem;\n}\n\nvoid* mspace_realloc(mspace msp, void* oldmem, size_t bytes) {\n  void* mem = 0;\n  if (oldmem == 0) {\n    mem = mspace_malloc(msp, bytes);\n  }\n  else if (bytes >= MAX_REQUEST) {\n    MALLOC_FAILURE_ACTION;\n  }\n#ifdef REALLOC_ZERO_BYTES_FREES\n  else if (bytes == 0) {\n    mspace_free(msp, oldmem);\n  }\n#endif /* REALLOC_ZERO_BYTES_FREES */\n  else {\n    size_t nb = request2size(bytes);\n    mchunkptr oldp = mem2chunk(oldmem);\n#if ! FOOTERS\n    mstate m = (mstate)msp;\n#else /* FOOTERS */\n    mstate m = get_mstate_for(oldp);\n    if (!ok_magic(m)) {\n      USAGE_ERROR_ACTION(m, oldmem);\n      return 0;\n    }\n#endif /* FOOTERS */\n    if (!PREACTION(m)) {\n      mchunkptr newp = try_realloc_chunk(m, oldp, nb, 1);\n      POSTACTION(m);\n      if (newp != 0) {\n        check_inuse_chunk(m, newp);\n        mem = chunk2mem(newp);\n      }\n      else {\n        mem = mspace_malloc(m, bytes);\n        if (mem != 0) {\n          size_t oc = chunksize(oldp) - overhead_for(oldp);\n          memcpy(mem, oldmem, (oc < bytes)? oc : bytes);\n          mspace_free(m, oldmem);\n        }\n      }\n    }\n  }\n  return mem;\n}\n\nvoid* mspace_realloc_in_place(mspace msp, void* oldmem, size_t bytes) {\n  void* mem = 0;\n  if (oldmem != 0) {\n    if (bytes >= MAX_REQUEST) {\n      MALLOC_FAILURE_ACTION;\n    }\n    else {\n      size_t nb = request2size(bytes);\n      mchunkptr oldp = mem2chunk(oldmem);\n#if ! FOOTERS\n      mstate m = (mstate)msp;\n#else /* FOOTERS */\n      mstate m = get_mstate_for(oldp);\n      (void)msp; /* placate people compiling -Wunused */\n      if (!ok_magic(m)) {\n        USAGE_ERROR_ACTION(m, oldmem);\n        return 0;\n      }\n#endif /* FOOTERS */\n      if (!PREACTION(m)) {\n        mchunkptr newp = try_realloc_chunk(m, oldp, nb, 0);\n        POSTACTION(m);\n        if (newp == oldp) {\n          check_inuse_chunk(m, newp);\n          mem = oldmem;\n        }\n      }\n    }\n  }\n  return mem;\n}\n\nvoid* mspace_memalign(mspace msp, size_t alignment, size_t bytes) {\n  mstate ms = (mstate)msp;\n  if (!ok_magic(ms)) {\n    USAGE_ERROR_ACTION(ms,ms);\n    return 0;\n  }\n  if (alignment <= MALLOC_ALIGNMENT)\n    return mspace_malloc(msp, bytes);\n  return internal_memalign(ms, alignment, bytes);\n}\n\nvoid** mspace_independent_calloc(mspace msp, size_t n_elements,\n                                 size_t elem_size, void* chunks[]) {\n  size_t sz = elem_size; /* serves as 1-element array */\n  mstate ms = (mstate)msp;\n  if (!ok_magic(ms)) {\n    USAGE_ERROR_ACTION(ms,ms);\n    return 0;\n  }\n  return ialloc(ms, n_elements, &sz, 3, chunks);\n}\n\nvoid** mspace_independent_comalloc(mspace msp, size_t n_elements,\n                                   size_t sizes[], void* chunks[]) {\n  mstate ms = (mstate)msp;\n  if (!ok_magic(ms)) {\n    USAGE_ERROR_ACTION(ms,ms);\n    return 0;\n  }\n  return ialloc(ms, n_elements, sizes, 0, chunks);\n}\n\nsize_t mspace_bulk_free(mspace msp, void* array[], size_t nelem) {\n  return internal_bulk_free((mstate)msp, array, nelem);\n}\n\n#if MALLOC_INSPECT_ALL\nvoid mspace_inspect_all(mspace msp,\n                        void(*handler)(void *start,\n                                       void *end,\n                                       size_t used_bytes,\n                                       void* callback_arg),\n                        void* arg) {\n  mstate ms = (mstate)msp;\n  if (ok_magic(ms)) {\n    if (!PREACTION(ms)) {\n      internal_inspect_all(ms, handler, arg);\n      POSTACTION(ms);\n    }\n  }\n  else {\n    USAGE_ERROR_ACTION(ms,ms);\n  }\n}\n#endif /* MALLOC_INSPECT_ALL */\n\nint mspace_trim(mspace msp, size_t pad) {\n  int result = 0;\n  mstate ms = (mstate)msp;\n  if (ok_magic(ms)) {\n    if (!PREACTION(ms)) {\n      result = sys_trim(ms, pad);\n      POSTACTION(ms);\n    }\n  }\n  else {\n    USAGE_ERROR_ACTION(ms,ms);\n  }\n  return result;\n}\n\n#if !NO_MALLOC_STATS\nvoid mspace_malloc_stats(mspace msp) {\n  mstate ms = (mstate)msp;\n  if (ok_magic(ms)) {\n    internal_malloc_stats(ms);\n  }\n  else {\n    USAGE_ERROR_ACTION(ms,ms);\n  }\n}\n#endif /* NO_MALLOC_STATS */\n\nsize_t mspace_footprint(mspace msp) {\n  size_t result = 0;\n  mstate ms = (mstate)msp;\n  if (ok_magic(ms)) {\n    result = ms->footprint;\n  }\n  else {\n    USAGE_ERROR_ACTION(ms,ms);\n  }\n  return result;\n}\n\nsize_t mspace_max_footprint(mspace msp) {\n  size_t result = 0;\n  mstate ms = (mstate)msp;\n  if (ok_magic(ms)) {\n    result = ms->max_footprint;\n  }\n  else {\n    USAGE_ERROR_ACTION(ms,ms);\n  }\n  return result;\n}\n\nsize_t mspace_footprint_limit(mspace msp) {\n  size_t result = 0;\n  mstate ms = (mstate)msp;\n  if (ok_magic(ms)) {\n    size_t maf = ms->footprint_limit;\n    result = (maf == 0) ? MAX_SIZE_T : maf;\n  }\n  else {\n    USAGE_ERROR_ACTION(ms,ms);\n  }\n  return result;\n}\n\nsize_t mspace_set_footprint_limit(mspace msp, size_t bytes) {\n  size_t result = 0;\n  mstate ms = (mstate)msp;\n  if (ok_magic(ms)) {\n    if (bytes == 0)\n      result = granularity_align(1); /* Use minimal size */\n    if (bytes == MAX_SIZE_T)\n      result = 0;                    /* disable */\n    else\n      result = granularity_align(bytes);\n    ms->footprint_limit = result;\n  }\n  else {\n    USAGE_ERROR_ACTION(ms,ms);\n  }\n  return result;\n}\n\n#if !NO_MALLINFO\nstruct mallinfo mspace_mallinfo(mspace msp) {\n  mstate ms = (mstate)msp;\n  if (!ok_magic(ms)) {\n    USAGE_ERROR_ACTION(ms,ms);\n  }\n  return internal_mallinfo(ms);\n}\n#endif /* NO_MALLINFO */\n\nsize_t mspace_usable_size(const void* mem) {\n  if (mem != 0) {\n    mchunkptr p = mem2chunk(mem);\n    if (is_inuse(p))\n      return chunksize(p) - overhead_for(p);\n  }\n  return 0;\n}\n\nint mspace_mallopt(int param_number, int value) {\n  return change_mparam(param_number, value);\n}\n\n#endif /* MSPACES */\n\n\n/* -------------------- Alternative MORECORE functions ------------------- */\n\n/*\n  Guidelines for creating a custom version of MORECORE:\n\n  * For best performance, MORECORE should allocate in multiples of pagesize.\n  * MORECORE may allocate more memory than requested. (Or even less,\n      but this will usually result in a malloc failure.)\n  * MORECORE must not allocate memory when given argument zero, but\n      instead return one past the end address of memory from previous\n      nonzero call.\n  * For best performance, consecutive calls to MORECORE with positive\n      arguments should return increasing addresses, indicating that\n      space has been contiguously extended.\n  * Even though consecutive calls to MORECORE need not return contiguous\n      addresses, it must be OK for malloc'ed chunks to span multiple\n      regions in those cases where they do happen to be contiguous.\n  * MORECORE need not handle negative arguments -- it may instead\n      just return MFAIL when given negative arguments.\n      Negative arguments are always multiples of pagesize. MORECORE\n      must not misinterpret negative args as large positive unsigned\n      args. You can suppress all such calls from even occurring by defining\n      MORECORE_CANNOT_TRIM,\n\n  As an example alternative MORECORE, here is a custom allocator\n  kindly contributed for pre-OSX macOS.  It uses virtually but not\n  necessarily physically contiguous non-paged memory (locked in,\n  present and won't get swapped out).  You can use it by uncommenting\n  this section, adding some #includes, and setting up the appropriate\n  defines above:\n\n      #define MORECORE osMoreCore\n\n  There is also a shutdown routine that should somehow be called for\n  cleanup upon program exit.\n\n  #define MAX_POOL_ENTRIES 100\n  #define MINIMUM_MORECORE_SIZE  (64 * 1024U)\n  static int next_os_pool;\n  void *our_os_pools[MAX_POOL_ENTRIES];\n\n  void *osMoreCore(int size)\n  {\n    void *ptr = 0;\n    static void *sbrk_top = 0;\n\n    if (size > 0)\n    {\n      if (size < MINIMUM_MORECORE_SIZE)\n         size = MINIMUM_MORECORE_SIZE;\n      if (CurrentExecutionLevel() == kTaskLevel)\n         ptr = PoolAllocateResident(size + RM_PAGE_SIZE, 0);\n      if (ptr == 0)\n      {\n        return (void *) MFAIL;\n      }\n      // save ptrs so they can be freed during cleanup\n      our_os_pools[next_os_pool] = ptr;\n      next_os_pool++;\n      ptr = (void *) ((((size_t) ptr) + RM_PAGE_MASK) & ~RM_PAGE_MASK);\n      sbrk_top = (char *) ptr + size;\n      return ptr;\n    }\n    else if (size < 0)\n    {\n      // we don't currently support shrink behavior\n      return (void *) MFAIL;\n    }\n    else\n    {\n      return sbrk_top;\n    }\n  }\n\n  // cleanup any allocated memory pools\n  // called as last thing before shutting down driver\n\n  void osCleanupMem(void)\n  {\n    void **ptr;\n\n    for (ptr = our_os_pools; ptr < &our_os_pools[MAX_POOL_ENTRIES]; ptr++)\n      if (*ptr)\n      {\n         PoolDeallocate(*ptr);\n         *ptr = 0;\n      }\n  }\n\n*/\n\n\n/* -----------------------------------------------------------------------\nHistory:\n    v2.8.6 Wed Aug 29 06:57:58 2012  Doug Lea\n      * fix bad comparison in dlposix_memalign\n      * don't reuse adjusted asize in sys_alloc\n      * add LOCK_AT_FORK -- thanks to Kirill Artamonov for the suggestion\n      * reduce compiler warnings -- thanks to all who reported/suggested these\n\n    v2.8.5 Sun May 22 10:26:02 2011  Doug Lea  (dl at gee)\n      * Always perform unlink checks unless INSECURE\n      * Add posix_memalign.\n      * Improve realloc to expand in more cases; expose realloc_in_place.\n        Thanks to Peter Buhr for the suggestion.\n      * Add footprint_limit, inspect_all, bulk_free. Thanks\n        to Barry Hayes and others for the suggestions.\n      * Internal refactorings to avoid calls while holding locks\n      * Use non-reentrant locks by default. Thanks to Roland McGrath\n        for the suggestion.\n      * Small fixes to mspace_destroy, reset_on_error.\n      * Various configuration extensions/changes. Thanks\n         to all who contributed these.\n\n    V2.8.4a Thu Apr 28 14:39:43 2011 (dl at gee.cs.oswego.edu)\n      * Update Creative Commons URL\n\n    V2.8.4 Wed May 27 09:56:23 2009  Doug Lea  (dl at gee)\n      * Use zeros instead of prev foot for is_mmapped\n      * Add mspace_track_large_chunks; thanks to Jean Brouwers\n      * Fix set_inuse in internal_realloc; thanks to Jean Brouwers\n      * Fix insufficient sys_alloc padding when using 16byte alignment\n      * Fix bad error check in mspace_footprint\n      * Adaptations for ptmalloc; thanks to Wolfram Gloger.\n      * Reentrant spin locks; thanks to Earl Chew and others\n      * Win32 improvements; thanks to Niall Douglas and Earl Chew\n      * Add NO_SEGMENT_TRAVERSAL and MAX_RELEASE_CHECK_RATE options\n      * Extension hook in malloc_state\n      * Various small adjustments to reduce warnings on some compilers\n      * Various configuration extensions/changes for more platforms. Thanks\n         to all who contributed these.\n\n    V2.8.3 Thu Sep 22 11:16:32 2005  Doug Lea  (dl at gee)\n      * Add max_footprint functions\n      * Ensure all appropriate literals are size_t\n      * Fix conditional compilation problem for some #define settings\n      * Avoid concatenating segments with the one provided\n        in create_mspace_with_base\n      * Rename some variables to avoid compiler shadowing warnings\n      * Use explicit lock initialization.\n      * Better handling of sbrk interference.\n      * Simplify and fix segment insertion, trimming and mspace_destroy\n      * Reinstate REALLOC_ZERO_BYTES_FREES option from 2.7.x\n      * Thanks especially to Dennis Flanagan for help on these.\n\n    V2.8.2 Sun Jun 12 16:01:10 2005  Doug Lea  (dl at gee)\n      * Fix memalign brace error.\n\n    V2.8.1 Wed Jun  8 16:11:46 2005  Doug Lea  (dl at gee)\n      * Fix improper #endif nesting in C++\n      * Add explicit casts needed for C++\n\n    V2.8.0 Mon May 30 14:09:02 2005  Doug Lea  (dl at gee)\n      * Use trees for large bins\n      * Support mspaces\n      * Use segments to unify sbrk-based and mmap-based system allocation,\n        removing need for emulation on most platforms without sbrk.\n      * Default safety checks\n      * Optional footer checks. Thanks to William Robertson for the idea.\n      * Internal code refactoring\n      * Incorporate suggestions and platform-specific changes.\n        Thanks to Dennis Flanagan, Colin Plumb, Niall Douglas,\n        Aaron Bachmann,  Emery Berger, and others.\n      * Speed up non-fastbin processing enough to remove fastbins.\n      * Remove useless cfree() to avoid conflicts with other apps.\n      * Remove internal memcpy, memset. Compilers handle builtins better.\n      * Remove some options that no one ever used and rename others.\n\n    V2.7.2 Sat Aug 17 09:07:30 2002  Doug Lea  (dl at gee)\n      * Fix malloc_state bitmap array misdeclaration\n\n    V2.7.1 Thu Jul 25 10:58:03 2002  Doug Lea  (dl at gee)\n      * Allow tuning of FIRST_SORTED_BIN_SIZE\n      * Use PTR_UINT as type for all ptr->int casts. Thanks to John Belmonte.\n      * Better detection and support for non-contiguousness of MORECORE.\n        Thanks to Andreas Mueller, Conal Walsh, and Wolfram Gloger\n      * Bypass most of malloc if no frees. Thanks To Emery Berger.\n      * Fix freeing of old top non-contiguous chunk im sysmalloc.\n      * Raised default trim and map thresholds to 256K.\n      * Fix mmap-related #defines. Thanks to Lubos Lunak.\n      * Fix copy macros; added LACKS_FCNTL_H. Thanks to Neal Walfield.\n      * Branch-free bin calculation\n      * Default trim and mmap thresholds now 256K.\n\n    V2.7.0 Sun Mar 11 14:14:06 2001  Doug Lea  (dl at gee)\n      * Introduce independent_comalloc and independent_calloc.\n        Thanks to Michael Pachos for motivation and help.\n      * Make optional .h file available\n      * Allow > 2GB requests on 32bit systems.\n      * new WIN32 sbrk, mmap, munmap, lock code from <Walter@GeNeSys-e.de>.\n        Thanks also to Andreas Mueller <a.mueller at paradatec.de>,\n        and Anonymous.\n      * Allow override of MALLOC_ALIGNMENT (Thanks to Ruud Waij for\n        helping test this.)\n      * memalign: check alignment arg\n      * realloc: don't try to shift chunks backwards, since this\n        leads to  more fragmentation in some programs and doesn't\n        seem to help in any others.\n      * Collect all cases in malloc requiring system memory into sysmalloc\n      * Use mmap as backup to sbrk\n      * Place all internal state in malloc_state\n      * Introduce fastbins (although similar to 2.5.1)\n      * Many minor tunings and cosmetic improvements\n      * Introduce USE_PUBLIC_MALLOC_WRAPPERS, USE_MALLOC_LOCK\n      * Introduce MALLOC_FAILURE_ACTION, MORECORE_CONTIGUOUS\n        Thanks to Tony E. Bennett <tbennett@nvidia.com> and others.\n      * Include errno.h to support default failure action.\n\n    V2.6.6 Sun Dec  5 07:42:19 1999  Doug Lea  (dl at gee)\n      * return null for negative arguments\n      * Added Several WIN32 cleanups from Martin C. Fong <mcfong at yahoo.com>\n         * Add 'LACKS_SYS_PARAM_H' for those systems without 'sys/param.h'\n          (e.g. WIN32 platforms)\n         * Cleanup header file inclusion for WIN32 platforms\n         * Cleanup code to avoid Microsoft Visual C++ compiler complaints\n         * Add 'USE_DL_PREFIX' to quickly allow co-existence with existing\n           memory allocation routines\n         * Set 'malloc_getpagesize' for WIN32 platforms (needs more work)\n         * Use 'assert' rather than 'ASSERT' in WIN32 code to conform to\n           usage of 'assert' in non-WIN32 code\n         * Improve WIN32 'sbrk()' emulation's 'findRegion()' routine to\n           avoid infinite loop\n      * Always call 'fREe()' rather than 'free()'\n\n    V2.6.5 Wed Jun 17 15:57:31 1998  Doug Lea  (dl at gee)\n      * Fixed ordering problem with boundary-stamping\n\n    V2.6.3 Sun May 19 08:17:58 1996  Doug Lea  (dl at gee)\n      * Added pvalloc, as recommended by H.J. Liu\n      * Added 64bit pointer support mainly from Wolfram Gloger\n      * Added anonymously donated WIN32 sbrk emulation\n      * Malloc, calloc, getpagesize: add optimizations from Raymond Nijssen\n      * malloc_extend_top: fix mask error that caused wastage after\n        foreign sbrks\n      * Add linux mremap support code from HJ Liu\n\n    V2.6.2 Tue Dec  5 06:52:55 1995  Doug Lea  (dl at gee)\n      * Integrated most documentation with the code.\n      * Add support for mmap, with help from\n        Wolfram Gloger (Gloger@lrz.uni-muenchen.de).\n      * Use last_remainder in more cases.\n      * Pack bins using idea from  colin@nyx10.cs.du.edu\n      * Use ordered bins instead of best-fit threshhold\n      * Eliminate block-local decls to simplify tracing and debugging.\n      * Support another case of realloc via move into top\n      * Fix error occuring when initial sbrk_base not word-aligned.\n      * Rely on page size for units instead of SBRK_UNIT to\n        avoid surprises about sbrk alignment conventions.\n      * Add mallinfo, mallopt. Thanks to Raymond Nijssen\n        (raymond@es.ele.tue.nl) for the suggestion.\n      * Add `pad' argument to malloc_trim and top_pad mallopt parameter.\n      * More precautions for cases where other routines call sbrk,\n        courtesy of Wolfram Gloger (Gloger@lrz.uni-muenchen.de).\n      * Added macros etc., allowing use in linux libc from\n        H.J. Lu (hjl@gnu.ai.mit.edu)\n      * Inverted this history list\n\n    V2.6.1 Sat Dec  2 14:10:57 1995  Doug Lea  (dl at gee)\n      * Re-tuned and fixed to behave more nicely with V2.6.0 changes.\n      * Removed all preallocation code since under current scheme\n        the work required to undo bad preallocations exceeds\n        the work saved in good cases for most test programs.\n      * No longer use return list or unconsolidated bins since\n        no scheme using them consistently outperforms those that don't\n        given above changes.\n      * Use best fit for very large chunks to prevent some worst-cases.\n      * Added some support for debugging\n\n    V2.6.0 Sat Nov  4 07:05:23 1995  Doug Lea  (dl at gee)\n      * Removed footers when chunks are in use. Thanks to\n        Paul Wilson (wilson@cs.texas.edu) for the suggestion.\n\n    V2.5.4 Wed Nov  1 07:54:51 1995  Doug Lea  (dl at gee)\n      * Added malloc_trim, with help from Wolfram Gloger\n        (wmglo@Dent.MED.Uni-Muenchen.DE).\n\n    V2.5.3 Tue Apr 26 10:16:01 1994  Doug Lea  (dl at g)\n\n    V2.5.2 Tue Apr  5 16:20:40 1994  Doug Lea  (dl at g)\n      * realloc: try to expand in both directions\n      * malloc: swap order of clean-bin strategy;\n      * realloc: only conditionally expand backwards\n      * Try not to scavenge used bins\n      * Use bin counts as a guide to preallocation\n      * Occasionally bin return list chunks in first scan\n      * Add a few optimizations from colin@nyx10.cs.du.edu\n\n    V2.5.1 Sat Aug 14 15:40:43 1993  Doug Lea  (dl at g)\n      * faster bin computation & slightly different binning\n      * merged all consolidations to one part of malloc proper\n         (eliminating old malloc_find_space & malloc_clean_bin)\n      * Scan 2 returns chunks (not just 1)\n      * Propagate failure in realloc if malloc returns 0\n      * Add stuff to allow compilation on non-ANSI compilers\n          from kpv@research.att.com\n\n    V2.5 Sat Aug  7 07:41:59 1993  Doug Lea  (dl at g.oswego.edu)\n      * removed potential for odd address access in prev_chunk\n      * removed dependency on getpagesize.h\n      * misc cosmetics and a bit more internal documentation\n      * anticosmetics: mangled names in macros to evade debugger strangeness\n      * tested on sparc, hp-700, dec-mips, rs6000\n          with gcc & native cc (hp, dec only) allowing\n          Detlefs & Zorn comparison study (in SIGPLAN Notices.)\n\n    Trial version Fri Aug 28 13:14:29 1992  Doug Lea  (dl at g.oswego.edu)\n      * Based loosely on libg++-1.2X malloc. (It retains some of the overall\n         structure of old version,  but most details differ.)\n\n*/\n"
  },
  {
    "path": "arch/nds/dlmalloc.h",
    "content": "#include \"malloc_opts.h\"\n\n/*\n  Default header file for malloc-2.8.x, written by Doug Lea\n  and released to the public domain, as explained at\n  http://creativecommons.org/publicdomain/zero/1.0/ \n \n  This header is for ANSI C/C++ only.  You can set any of\n  the following #defines before including:\n\n  * If USE_DL_PREFIX is defined, it is assumed that malloc.c \n    was also compiled with this option, so all routines\n    have names starting with \"dl\".\n\n  * If HAVE_USR_INCLUDE_MALLOC_H is defined, it is assumed that this\n    file will be #included AFTER <malloc.h>. This is needed only if\n    your system defines a struct mallinfo that is incompatible with the\n    standard one declared here.  Otherwise, you can include this file\n    INSTEAD of your system system <malloc.h>.  At least on ANSI, all\n    declarations should be compatible with system versions\n\n  * If MSPACES is defined, declarations for mspace versions are included.\n*/\n\n#ifndef MALLOC_280_H\n#define MALLOC_280_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include <stddef.h>   /* for size_t */\n\n#ifndef ONLY_MSPACES\n#define ONLY_MSPACES 0     /* define to a value */\n#elif ONLY_MSPACES != 0\n#define ONLY_MSPACES 1\n#endif  /* ONLY_MSPACES */\n#ifndef NO_MALLINFO\n#define NO_MALLINFO 0\n#endif  /* NO_MALLINFO */\n\n#ifndef MSPACES\n#if ONLY_MSPACES\n#define MSPACES 1\n#else   /* ONLY_MSPACES */\n#define MSPACES 0\n#endif  /* ONLY_MSPACES */\n#endif  /* MSPACES */\n\n#if !ONLY_MSPACES\n\n#ifndef USE_DL_PREFIX\n#define dlcalloc               calloc\n#define dlfree                 free\n#define dlmalloc               malloc\n#define dlmemalign             memalign\n#define dlposix_memalign       posix_memalign\n#define dlrealloc              realloc\n#define dlvalloc               valloc\n#define dlpvalloc              pvalloc\n#define dlmallinfo             mallinfo\n#define dlmallopt              mallopt\n#define dlmalloc_trim          malloc_trim\n#define dlmalloc_stats         malloc_stats\n#define dlmalloc_usable_size   malloc_usable_size\n#define dlmalloc_footprint     malloc_footprint\n#define dlmalloc_max_footprint malloc_max_footprint\n#define dlmalloc_footprint_limit malloc_footprint_limit\n#define dlmalloc_set_footprint_limit malloc_set_footprint_limit\n#define dlmalloc_inspect_all   malloc_inspect_all\n#define dlindependent_calloc   independent_calloc\n#define dlindependent_comalloc independent_comalloc\n#define dlbulk_free            bulk_free\n#endif /* USE_DL_PREFIX */\n\n#if !NO_MALLINFO \n#ifndef HAVE_USR_INCLUDE_MALLOC_H\n#ifndef _MALLOC_H\n#ifndef MALLINFO_FIELD_TYPE\n#define MALLINFO_FIELD_TYPE size_t\n#endif /* MALLINFO_FIELD_TYPE */\n#ifndef STRUCT_MALLINFO_DECLARED\n#define STRUCT_MALLINFO_DECLARED 1\nstruct mallinfo {\n  MALLINFO_FIELD_TYPE arena;    /* non-mmapped space allocated from system */\n  MALLINFO_FIELD_TYPE ordblks;  /* number of free chunks */\n  MALLINFO_FIELD_TYPE smblks;   /* always 0 */\n  MALLINFO_FIELD_TYPE hblks;    /* always 0 */\n  MALLINFO_FIELD_TYPE hblkhd;   /* space in mmapped regions */\n  MALLINFO_FIELD_TYPE usmblks;  /* maximum total allocated space */\n  MALLINFO_FIELD_TYPE fsmblks;  /* always 0 */\n  MALLINFO_FIELD_TYPE uordblks; /* total allocated space */\n  MALLINFO_FIELD_TYPE fordblks; /* total free space */\n  MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */\n};\n#endif /* STRUCT_MALLINFO_DECLARED */\n#endif  /* _MALLOC_H */\n#endif  /* HAVE_USR_INCLUDE_MALLOC_H */\n#endif  /* !NO_MALLINFO */\n\n/*\n  malloc(size_t n)\n  Returns a pointer to a newly allocated chunk of at least n bytes, or\n  null if no space is available, in which case errno is set to ENOMEM\n  on ANSI C systems.\n\n  If n is zero, malloc returns a minimum-sized chunk. (The minimum\n  size is 16 bytes on most 32bit systems, and 32 bytes on 64bit\n  systems.)  Note that size_t is an unsigned type, so calls with\n  arguments that would be negative if signed are interpreted as\n  requests for huge amounts of space, which will often fail. The\n  maximum supported value of n differs across systems, but is in all\n  cases less than the maximum representable value of a size_t.\n*/\nvoid* dlmalloc(size_t);\n\n/*\n  free(void* p)\n  Releases the chunk of memory pointed to by p, that had been previously\n  allocated using malloc or a related routine such as realloc.\n  It has no effect if p is null. If p was not malloced or already\n  freed, free(p) will by default cuase the current program to abort.\n*/\nvoid  dlfree(void*);\n\n/*\n  calloc(size_t n_elements, size_t element_size);\n  Returns a pointer to n_elements * element_size bytes, with all locations\n  set to zero.\n*/\nvoid* dlcalloc(size_t, size_t);\n\n/*\n  realloc(void* p, size_t n)\n  Returns a pointer to a chunk of size n that contains the same data\n  as does chunk p up to the minimum of (n, p's size) bytes, or null\n  if no space is available.\n\n  The returned pointer may or may not be the same as p. The algorithm\n  prefers extending p in most cases when possible, otherwise it\n  employs the equivalent of a malloc-copy-free sequence.\n\n  If p is null, realloc is equivalent to malloc.\n\n  If space is not available, realloc returns null, errno is set (if on\n  ANSI) and p is NOT freed.\n\n  if n is for fewer bytes than already held by p, the newly unused\n  space is lopped off and freed if possible.  realloc with a size\n  argument of zero (re)allocates a minimum-sized chunk.\n\n  The old unix realloc convention of allowing the last-free'd chunk\n  to be used as an argument to realloc is not supported.\n*/\nvoid* dlrealloc(void*, size_t);\n\n/*\n  realloc_in_place(void* p, size_t n)\n  Resizes the space allocated for p to size n, only if this can be\n  done without moving p (i.e., only if there is adjacent space\n  available if n is greater than p's current allocated size, or n is\n  less than or equal to p's size). This may be used instead of plain\n  realloc if an alternative allocation strategy is needed upon failure\n  to expand space; for example, reallocation of a buffer that must be\n  memory-aligned or cleared. You can use realloc_in_place to trigger\n  these alternatives only when needed.\n\n  Returns p if successful; otherwise null.\n*/\nvoid* dlrealloc_in_place(void*, size_t);\n\n/*\n  memalign(size_t alignment, size_t n);\n  Returns a pointer to a newly allocated chunk of n bytes, aligned\n  in accord with the alignment argument.\n\n  The alignment argument should be a power of two. If the argument is\n  not a power of two, the nearest greater power is used.\n  8-byte alignment is guaranteed by normal malloc calls, so don't\n  bother calling memalign with an argument of 8 or less.\n\n  Overreliance on memalign is a sure way to fragment space.\n*/\nvoid* dlmemalign(size_t, size_t);\n\n/*\n  int posix_memalign(void** pp, size_t alignment, size_t n);\n  Allocates a chunk of n bytes, aligned in accord with the alignment\n  argument. Differs from memalign only in that it (1) assigns the\n  allocated memory to *pp rather than returning it, (2) fails and\n  returns EINVAL if the alignment is not a power of two (3) fails and\n  returns ENOMEM if memory cannot be allocated.\n*/\nint dlposix_memalign(void**, size_t, size_t);\n\n/*\n  valloc(size_t n);\n  Equivalent to memalign(pagesize, n), where pagesize is the page\n  size of the system. If the pagesize is unknown, 4096 is used.\n*/\nvoid* dlvalloc(size_t);\n\n/*\n  mallopt(int parameter_number, int parameter_value)\n  Sets tunable parameters The format is to provide a\n  (parameter-number, parameter-value) pair.  mallopt then sets the\n  corresponding parameter to the argument value if it can (i.e., so\n  long as the value is meaningful), and returns 1 if successful else\n  0.  SVID/XPG/ANSI defines four standard param numbers for mallopt,\n  normally defined in malloc.h.  None of these are use in this malloc,\n  so setting them has no effect. But this malloc also supports other\n  options in mallopt:\n\n  Symbol            param #  default    allowed param values\n  M_TRIM_THRESHOLD     -1   2*1024*1024   any   (-1U disables trimming)\n  M_GRANULARITY        -2     page size   any power of 2 >= page size\n  M_MMAP_THRESHOLD     -3      256*1024   any   (or 0 if no MMAP support)\n*/\nint dlmallopt(int, int);\n\n#define M_TRIM_THRESHOLD     (-1)\n#define M_GRANULARITY        (-2)\n#define M_MMAP_THRESHOLD     (-3)\n\n\n/*\n  malloc_footprint();\n  Returns the number of bytes obtained from the system.  The total\n  number of bytes allocated by malloc, realloc etc., is less than this\n  value. Unlike mallinfo, this function returns only a precomputed\n  result, so can be called frequently to monitor memory consumption.\n  Even if locks are otherwise defined, this function does not use them,\n  so results might not be up to date.\n*/\nsize_t dlmalloc_footprint(void);\n\n/*\n  malloc_max_footprint();\n  Returns the maximum number of bytes obtained from the system. This\n  value will be greater than current footprint if deallocated space\n  has been reclaimed by the system. The peak number of bytes allocated\n  by malloc, realloc etc., is less than this value. Unlike mallinfo,\n  this function returns only a precomputed result, so can be called\n  frequently to monitor memory consumption.  Even if locks are\n  otherwise defined, this function does not use them, so results might\n  not be up to date.\n*/\nsize_t dlmalloc_max_footprint(void);\n\n/*\n  malloc_footprint_limit();\n  Returns the number of bytes that the heap is allowed to obtain from\n  the system, returning the last value returned by\n  malloc_set_footprint_limit, or the maximum size_t value if\n  never set. The returned value reflects a permission. There is no\n  guarantee that this number of bytes can actually be obtained from\n  the system.  \n*/\nsize_t dlmalloc_footprint_limit(void);\n\n/*\n  malloc_set_footprint_limit();\n  Sets the maximum number of bytes to obtain from the system, causing\n  failure returns from malloc and related functions upon attempts to\n  exceed this value. The argument value may be subject to page\n  rounding to an enforceable limit; this actual value is returned.\n  Using an argument of the maximum possible size_t effectively\n  disables checks. If the argument is less than or equal to the\n  current malloc_footprint, then all future allocations that require\n  additional system memory will fail. However, invocation cannot\n  retroactively deallocate existing used memory.\n*/\nsize_t dlmalloc_set_footprint_limit(size_t bytes);\n\n/*\n  malloc_inspect_all(void(*handler)(void *start,\n                                    void *end,\n                                    size_t used_bytes,\n                                    void* callback_arg),\n                      void* arg);\n  Traverses the heap and calls the given handler for each managed\n  region, skipping all bytes that are (or may be) used for bookkeeping\n  purposes.  Traversal does not include include chunks that have been\n  directly memory mapped. Each reported region begins at the start\n  address, and continues up to but not including the end address.  The\n  first used_bytes of the region contain allocated data. If\n  used_bytes is zero, the region is unallocated. The handler is\n  invoked with the given callback argument. If locks are defined, they\n  are held during the entire traversal. It is a bad idea to invoke\n  other malloc functions from within the handler.\n\n  For example, to count the number of in-use chunks with size greater\n  than 1000, you could write:\n  static int count = 0;\n  void count_chunks(void* start, void* end, size_t used, void* arg) {\n    if (used >= 1000) ++count;\n  }\n  then:\n    malloc_inspect_all(count_chunks, NULL);\n\n  malloc_inspect_all is compiled only if MALLOC_INSPECT_ALL is defined.\n*/\nvoid dlmalloc_inspect_all(void(*handler)(void*, void *, size_t, void*),\n                           void* arg);\n\n#if !NO_MALLINFO\n/*\n  mallinfo()\n  Returns (by copy) a struct containing various summary statistics:\n\n  arena:     current total non-mmapped bytes allocated from system\n  ordblks:   the number of free chunks\n  smblks:    always zero.\n  hblks:     current number of mmapped regions\n  hblkhd:    total bytes held in mmapped regions\n  usmblks:   the maximum total allocated space. This will be greater\n                than current total if trimming has occurred.\n  fsmblks:   always zero\n  uordblks:  current total allocated space (normal or mmapped)\n  fordblks:  total free space\n  keepcost:  the maximum number of bytes that could ideally be released\n               back to system via malloc_trim. (\"ideally\" means that\n               it ignores page restrictions etc.)\n\n  Because these fields are ints, but internal bookkeeping may\n  be kept as longs, the reported values may wrap around zero and\n  thus be inaccurate.\n*/\n\nstruct mallinfo dlmallinfo(void);\n#endif  /* NO_MALLINFO */\n\n/*\n  independent_calloc(size_t n_elements, size_t element_size, void* chunks[]);\n\n  independent_calloc is similar to calloc, but instead of returning a\n  single cleared space, it returns an array of pointers to n_elements\n  independent elements that can hold contents of size elem_size, each\n  of which starts out cleared, and can be independently freed,\n  realloc'ed etc. The elements are guaranteed to be adjacently\n  allocated (this is not guaranteed to occur with multiple callocs or\n  mallocs), which may also improve cache locality in some\n  applications.\n\n  The \"chunks\" argument is optional (i.e., may be null, which is\n  probably the most typical usage). If it is null, the returned array\n  is itself dynamically allocated and should also be freed when it is\n  no longer needed. Otherwise, the chunks array must be of at least\n  n_elements in length. It is filled in with the pointers to the\n  chunks.\n\n  In either case, independent_calloc returns this pointer array, or\n  null if the allocation failed.  If n_elements is zero and \"chunks\"\n  is null, it returns a chunk representing an array with zero elements\n  (which should be freed if not wanted).\n\n  Each element must be freed when it is no longer needed. This can be\n  done all at once using bulk_free.\n\n  independent_calloc simplifies and speeds up implementations of many\n  kinds of pools.  It may also be useful when constructing large data\n  structures that initially have a fixed number of fixed-sized nodes,\n  but the number is not known at compile time, and some of the nodes\n  may later need to be freed. For example:\n\n  struct Node { int item; struct Node* next; };\n\n  struct Node* build_list() {\n    struct Node** pool;\n    int n = read_number_of_nodes_needed();\n    if (n <= 0) return 0;\n    pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0);\n    if (pool == 0) die();\n    // organize into a linked list...\n    struct Node* first = pool[0];\n    for (i = 0; i < n-1; ++i)\n      pool[i]->next = pool[i+1];\n    free(pool);     // Can now free the array (or not, if it is needed later)\n    return first;\n  }\n*/\nvoid** dlindependent_calloc(size_t, size_t, void**);\n\n/*\n  independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]);\n\n  independent_comalloc allocates, all at once, a set of n_elements\n  chunks with sizes indicated in the \"sizes\" array.    It returns\n  an array of pointers to these elements, each of which can be\n  independently freed, realloc'ed etc. The elements are guaranteed to\n  be adjacently allocated (this is not guaranteed to occur with\n  multiple callocs or mallocs), which may also improve cache locality\n  in some applications.\n\n  The \"chunks\" argument is optional (i.e., may be null). If it is null\n  the returned array is itself dynamically allocated and should also\n  be freed when it is no longer needed. Otherwise, the chunks array\n  must be of at least n_elements in length. It is filled in with the\n  pointers to the chunks.\n\n  In either case, independent_comalloc returns this pointer array, or\n  null if the allocation failed.  If n_elements is zero and chunks is\n  null, it returns a chunk representing an array with zero elements\n  (which should be freed if not wanted).\n\n  Each element must be freed when it is no longer needed. This can be\n  done all at once using bulk_free.\n\n  independent_comallac differs from independent_calloc in that each\n  element may have a different size, and also that it does not\n  automatically clear elements.\n\n  independent_comalloc can be used to speed up allocation in cases\n  where several structs or objects must always be allocated at the\n  same time.  For example:\n\n  struct Head { ... }\n  struct Foot { ... }\n\n  void send_message(char* msg) {\n    int msglen = strlen(msg);\n    size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) };\n    void* chunks[3];\n    if (independent_comalloc(3, sizes, chunks) == 0)\n      die();\n    struct Head* head = (struct Head*)(chunks[0]);\n    char*        body = (char*)(chunks[1]);\n    struct Foot* foot = (struct Foot*)(chunks[2]);\n    // ...\n  }\n\n  In general though, independent_comalloc is worth using only for\n  larger values of n_elements. For small values, you probably won't\n  detect enough difference from series of malloc calls to bother.\n\n  Overuse of independent_comalloc can increase overall memory usage,\n  since it cannot reuse existing noncontiguous small chunks that\n  might be available for some of the elements.\n*/\nvoid** dlindependent_comalloc(size_t, size_t*, void**);\n\n/*\n  bulk_free(void* array[], size_t n_elements)\n  Frees and clears (sets to null) each non-null pointer in the given\n  array.  This is likely to be faster than freeing them one-by-one.\n  If footers are used, pointers that have been allocated in different\n  mspaces are not freed or cleared, and the count of all such pointers\n  is returned.  For large arrays of pointers with poor locality, it\n  may be worthwhile to sort this array before calling bulk_free.\n*/\nsize_t  dlbulk_free(void**, size_t n_elements);\n\n/*\n  pvalloc(size_t n);\n  Equivalent to valloc(minimum-page-that-holds(n)), that is,\n  round up n to nearest pagesize.\n */\nvoid*  dlpvalloc(size_t);\n\n/*\n  malloc_trim(size_t pad);\n\n  If possible, gives memory back to the system (via negative arguments\n  to sbrk) if there is unused memory at the `high' end of the malloc\n  pool or in unused MMAP segments. You can call this after freeing\n  large blocks of memory to potentially reduce the system-level memory\n  requirements of a program. However, it cannot guarantee to reduce\n  memory. Under some allocation patterns, some large free blocks of\n  memory will be locked between two used chunks, so they cannot be\n  given back to the system.\n\n  The `pad' argument to malloc_trim represents the amount of free\n  trailing space to leave untrimmed. If this argument is zero, only\n  the minimum amount of memory to maintain internal data structures\n  will be left. Non-zero arguments can be supplied to maintain enough\n  trailing space to service future expected allocations without having\n  to re-obtain memory from the system.\n\n  Malloc_trim returns 1 if it actually released any memory, else 0.\n*/\nint  dlmalloc_trim(size_t);\n\n/*\n  malloc_stats();\n  Prints on stderr the amount of space obtained from the system (both\n  via sbrk and mmap), the maximum amount (which may be more than\n  current if malloc_trim and/or munmap got called), and the current\n  number of bytes allocated via malloc (or realloc, etc) but not yet\n  freed. Note that this is the number of bytes allocated, not the\n  number requested. It will be larger than the number requested\n  because of alignment and bookkeeping overhead. Because it includes\n  alignment wastage as being in use, this figure may be greater than\n  zero even when no user-level chunks are allocated.\n\n  The reported current and maximum system memory can be inaccurate if\n  a program makes other calls to system memory allocation functions\n  (normally sbrk) outside of malloc.\n\n  malloc_stats prints only the most commonly interesting statistics.\n  More information can be obtained by calling mallinfo.\n  \n  malloc_stats is not compiled if NO_MALLOC_STATS is defined.\n*/\nvoid  dlmalloc_stats(void);\n\n#endif /* !ONLY_MSPACES */\n\n/*\n  malloc_usable_size(void* p);\n\n  Returns the number of bytes you can actually use in\n  an allocated chunk, which may be more than you requested (although\n  often not) due to alignment and minimum size constraints.\n  You can use this many bytes without worrying about\n  overwriting other allocated objects. This is not a particularly great\n  programming practice. malloc_usable_size can be more useful in\n  debugging and assertions, for example:\n\n  p = malloc(n);\n  assert(malloc_usable_size(p) >= 256);\n*/\nsize_t dlmalloc_usable_size(const void*);\n\n#if MSPACES\n\n/*\n  mspace is an opaque type representing an independent\n  region of space that supports mspace_malloc, etc.\n*/\ntypedef void* mspace;\n\n/*\n  create_mspace creates and returns a new independent space with the\n  given initial capacity, or, if 0, the default granularity size.  It\n  returns null if there is no system memory available to create the\n  space.  If argument locked is non-zero, the space uses a separate\n  lock to control access. The capacity of the space will grow\n  dynamically as needed to service mspace_malloc requests.  You can\n  control the sizes of incremental increases of this space by\n  compiling with a different DEFAULT_GRANULARITY or dynamically\n  setting with mallopt(M_GRANULARITY, value).\n*/\nmspace create_mspace(size_t capacity, int locked);\n\n/*\n  destroy_mspace destroys the given space, and attempts to return all\n  of its memory back to the system, returning the total number of\n  bytes freed. After destruction, the results of access to all memory\n  used by the space become undefined.\n*/\nsize_t destroy_mspace(mspace msp);\n\n/*\n  create_mspace_with_base uses the memory supplied as the initial base\n  of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this\n  space is used for bookkeeping, so the capacity must be at least this\n  large. (Otherwise 0 is returned.) When this initial space is\n  exhausted, additional memory will be obtained from the system.\n  Destroying this space will deallocate all additionally allocated\n  space (if possible) but not the initial base.\n*/\nmspace create_mspace_with_base(void* base, size_t capacity, int locked);\n\n/*\n  mspace_track_large_chunks controls whether requests for large chunks\n  are allocated in their own untracked mmapped regions, separate from\n  others in this mspace. By default large chunks are not tracked,\n  which reduces fragmentation. However, such chunks are not\n  necessarily released to the system upon destroy_mspace.  Enabling\n  tracking by setting to true may increase fragmentation, but avoids\n  leakage when relying on destroy_mspace to release all memory\n  allocated using this space.  The function returns the previous\n  setting.\n*/\nint mspace_track_large_chunks(mspace msp, int enable);\n\n#if !NO_MALLINFO\n/*\n  mspace_mallinfo behaves as mallinfo, but reports properties of\n  the given space.\n*/\nstruct mallinfo mspace_mallinfo(mspace msp);\n#endif /* NO_MALLINFO */\n\n/*\n  An alias for mallopt.\n*/\nint mspace_mallopt(int, int);\n\n/*\n  The following operate identically to their malloc counterparts\n  but operate only for the given mspace argument\n*/\nvoid* mspace_malloc(mspace msp, size_t bytes);\nvoid mspace_free(mspace msp, void* mem);\nvoid* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size);\nvoid* mspace_realloc(mspace msp, void* mem, size_t newsize);\nvoid* mspace_realloc_in_place(mspace msp, void* mem, size_t newsize);\nvoid* mspace_memalign(mspace msp, size_t alignment, size_t bytes);\nvoid** mspace_independent_calloc(mspace msp, size_t n_elements,\n                                 size_t elem_size, void* chunks[]);\nvoid** mspace_independent_comalloc(mspace msp, size_t n_elements,\n                                   size_t sizes[], void* chunks[]);\nsize_t mspace_bulk_free(mspace msp, void**, size_t n_elements);\nsize_t mspace_usable_size(const void* mem);\nvoid mspace_malloc_stats(mspace msp);\nint mspace_trim(mspace msp, size_t pad);\nsize_t mspace_footprint(mspace msp);\nsize_t mspace_max_footprint(mspace msp);\nsize_t mspace_footprint_limit(mspace msp);\nsize_t mspace_set_footprint_limit(mspace msp, size_t bytes);\nvoid mspace_inspect_all(mspace msp, \n                        void(*handler)(void *, void *, size_t, void*),\n                        void* arg);\n#endif  /* MSPACES */\n\n#ifdef __cplusplus\n};  /* end of extern \"C\" */\n#endif\n\n#endif /* MALLOC_280_H */\n"
  },
  {
    "path": "arch/nds/event.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007-2009 Kevin Vance <kvance@kvance.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/event.h\"\n#include \"../../src/graphics.h\"\n#include \"../../src/platform.h\"\n\n#include <nds/arm9/keyboard.h>\n#include \"event.h\"\n#include \"render.h\"\n#include \"evq.h\"\n\nvoid nds_update_input(void);\nstatic boolean process_event(NDSEvent *event);\n\nextern struct input_status input;\nextern u16 subscreen_height;\nstatic enum focus_mode allow_focus_changes = FOCUS_ALLOW;\n\nstatic boolean keyboard_allow_release = true;\n\nboolean __update_event_status(void)\n{\n  boolean retval = false;\n  NDSEvent event;\n\n  while(nds_event_poll(&event))\n    retval |= process_event(&event);\n\n  return retval;\n}\n\nboolean __peek_exit_input(void)\n{\n  /* FIXME stub */\n  return false;\n}\n\nvoid __wait_event(void)\n{\n  NDSEvent event;\n\n  while(!nds_event_poll(&event))\n    swiWaitForVBlank();\n  process_event(&event);\n}\n\nvoid __warp_mouse(int x, int y)\n{\n  // Since the touchscreen stylus can't be warped, focus there instead.\n  focus_pixel(x, y);\n}\n\nenum focus_mode get_allow_focus_changes(void)\n{\n  return allow_focus_changes;\n}\n\nboolean platform_has_screen_keyboard(void)\n{\n  return true;\n}\n\nboolean platform_show_screen_keyboard(void)\n{\n  if(nds_subscreen_keyboard())\n    input.showing_screen_keyboard = true;\n  // Always treat as handled, even if the state doesn't change.\n  return true;\n}\n\nboolean platform_hide_screen_keyboard(void)\n{\n  if(nds_subscreen_preview())\n    input.showing_screen_keyboard = false;\n  // Always treat as handled, even if the state doesn't change.\n  return true;\n}\n\nboolean platform_is_screen_keyboard_active(void)\n{\n  return input.showing_screen_keyboard;\n}\n\nvoid platform_init_event(void)\n{\n  struct buffered_status *status = store_status();\n  joystick_set_active(status, 0, true);\n  joystick_map_fallback_keyboard_button(0, 5);\n}\n\nstatic int nds_map_joystick(int nds_button, boolean *is_hat)\n{\n  // Determine how to handle this particular joystick input.\n  // Map the directional pad to the hat and everything else to buttons.\n  *is_hat = false;\n\n  switch(nds_button)\n  {\n    case KEY_UP:      *is_hat = true; return JOYHAT_UP;\n    case KEY_DOWN:    *is_hat = true; return JOYHAT_DOWN;\n    case KEY_LEFT:    *is_hat = true; return JOYHAT_LEFT;\n    case KEY_RIGHT:   *is_hat = true; return JOYHAT_RIGHT;\n    case KEY_A:       return 0;\n    case KEY_B:       return 1;\n    case KEY_X:       return 2;\n    case KEY_Y:       return 3;\n    case KEY_L:       return 4;\n    case KEY_R:       return 5;\n    case KEY_SELECT:  return 6;\n    case KEY_START:   return 7;\n  }\n  return -1;\n}\n\nstatic void convert_nds_internal(int key, int *internal_code, int *unicode)\n{\n  // Actually ASCIIish, but close enough.\n  if(key > 0)\n    *unicode = key;\n  else\n    *unicode = 0;\n\n  // Uppercase letters\n  if(key >= 65 && key <= 90)\n    *internal_code = key + 32;\n\n  // Arrow keys\n  else if(key == -17)\n    *internal_code = IKEY_UP;\n  else if(key == -19)\n    *internal_code = IKEY_DOWN;\n  else if(key == -20)\n    *internal_code = IKEY_LEFT;\n  else if(key == -18)\n    *internal_code = IKEY_RIGHT;\n\n  // Other keys\n  else if(key == -23)\n    *internal_code = IKEY_ESCAPE;\n  else if(key == -15)\n    *internal_code = IKEY_CAPSLOCK;\n  else if(key == 10)\n    *internal_code = IKEY_RETURN;\n  else if(key == -14)\n    *internal_code = IKEY_LSHIFT;\n  else if(key == -16)\n    *internal_code = IKEY_LCTRL;\n  else if(key == -26)\n    *internal_code = IKEY_LALT;\n  else if(key == -5)\n    *internal_code = IKEY_MENU;\n\n  // ASCIIish keys\n  else if(key > 0 && key < 512)\n    *internal_code = key;\n\n  // Unknown key\n  else\n    *internal_code = IKEY_UNKNOWN;\n}\n\nstatic boolean process_event(NDSEvent *event)\n{\n  struct buffered_status *status = store_status();\n  boolean retval = true;\n\n  switch(event->type)\n  {\n    // Hardware key pressed\n    case NDS_EVENT_KEY_DOWN:\n    {\n      boolean is_hat;\n      int button;\n\n      button = nds_map_joystick(event->key, &is_hat);\n      if(button >= 0)\n      {\n        if(is_hat)\n          joystick_hat_update(status, 0, button, true);\n        else\n          joystick_button_press(status, 0, button);\n      }\n      break;\n    }\n\n    case NDS_EVENT_KEY_UP:\n    {\n      boolean is_hat;\n      int button;\n\n      button = nds_map_joystick(event->key, &is_hat);\n      if(button >= 0)\n      {\n        if(is_hat)\n          joystick_hat_update(status, 0, button, false);\n        else\n          joystick_button_release(status, 0, button);\n      }\n      break;\n    }\n\n    // Software key down\n    case NDS_EVENT_KEYBOARD_DOWN:\n    {\n      int internal_code, unicode;\n      convert_nds_internal(event->key, &internal_code, &unicode);\n      key_press(status, internal_code);\n      key_press_unicode(status, unicode, true);\n\n      keyboard_allow_release = true;\n      break;\n    }\n\n    case NDS_EVENT_KEYBOARD_UP:\n    {\n      int internal_code, unicode;\n      convert_nds_internal(event->key, &internal_code, &unicode);\n      key_release(status, internal_code);\n      break;\n    }\n\n    // Touchscreen stylus down\n    case NDS_EVENT_TOUCH_DOWN:\n    {\n      enum mouse_button button = MOUSE_BUTTON_LEFT;\n      status->mouse_button = button;\n      status->mouse_repeat = button;\n      status->mouse_button_state |= MOUSE_BUTTON(button);\n      status->mouse_repeat_state = 1;\n      status->mouse_drag_state = -1;\n      status->mouse_time = get_ticks();\n      allow_focus_changes = FOCUS_FORBID;\n      break;\n    }\n\n    // Touchscreen stylus up\n    case NDS_EVENT_TOUCH_UP:\n    {\n      enum mouse_button button = MOUSE_BUTTON_LEFT;\n      status->mouse_button_state &= ~MOUSE_BUTTON(button);\n      status->mouse_repeat = 0;\n      status->mouse_drag_state = 0;\n      status->mouse_repeat_state = 0;\n      allow_focus_changes = FOCUS_ALLOW;\n      break;\n    }\n\n    // Touchscreen stylus drag\n    case NDS_EVENT_TOUCH_MOVE:\n    {\n      int mx = event->x * 640 / 256;\n      int my = (event->y - ((192 - subscreen_height)/2)) * 350 / subscreen_height;\n      if(my < 0 || my >= 350)\n        break;\n\n      // Update the MZX mouse state.\n      status->mouse_pixel_x = mx;\n      status->mouse_pixel_y = my;\n      status->mouse_x = mx / 8;\n      status->mouse_y = my / 14;\n      status->mouse_moved = true;\n\n      // Update our internal focus.\n      allow_focus_changes = FOCUS_PASS;\n      focus_pixel(mx, my);\n      allow_focus_changes = FOCUS_FORBID;\n      break;\n    }\n\n    // Unknown event?\n    default:\n    {\n      retval = false;\n      break;\n    }\n  };\n\n  return retval;\n}\n\nstatic void nds_update_hw_keys(void)\n{\n  static int last_keys_mask = 0;\n  int keys = keysHeld();\n  int check[] = { KEY_A, KEY_B, KEY_SELECT, KEY_START, KEY_RIGHT, KEY_LEFT,\n                  KEY_UP, KEY_DOWN, KEY_R, KEY_L, KEY_X, KEY_Y };\n  NDSEvent event;\n\n  for(size_t i = 0; i < sizeof(check)/sizeof(*check); i++)\n  {\n    int keybit = check[i];\n    if(keybit & keys)\n    {\n      if(!(keybit & last_keys_mask))\n      {\n        // Key pressed\n        nds_event_fill_key_down(&event, keybit);\n        nds_event_push(&event);\n        last_keys_mask |= keybit;\n      }\n    }\n    else if(keybit & last_keys_mask)\n    {\n      // Key released\n      nds_event_fill_key_up(&event, keybit);\n      nds_event_push(&event);\n      last_keys_mask &= ~keybit;\n    }\n  }\n}\n\nstatic void nds_update_sw_keyboard(void)\n{\n  static int last_key = -1;\n  NDSEvent event;\n\n  // Release last key\n  if(keyboard_allow_release && last_key != -1)\n  {\n    nds_event_fill_keyboard_up(&event, last_key);\n    nds_event_push(&event);\n    last_key = -1;\n  }\n\n  // Press new key (only allow if the keyboard is active)\n  if(subscreen_mode == SUBSCREEN_KEYBOARD && last_key == -1)\n  {\n    int c = keyboardUpdate();\n\n    if(c != -1)\n    {\n      nds_event_fill_keyboard_down(&event, c);\n      nds_event_push(&event);\n      keyboard_allow_release = false;\n      last_key = c;\n    }\n  }\n}\n\nstatic void nds_update_mouse(void)\n{\n  static boolean last_touch_press = false;\n  static int last_touch_x = -1;\n  static int last_touch_y = -1;\n  NDSEvent new_event;\n\n  if(is_scaled_mode(subscreen_mode))\n  {\n    // Inject mouse motion events.\n    if(keysHeld() & KEY_TOUCH)\n    {\n      touchPosition touch;\n      touchRead(&touch);\n\n      if(touch.px != last_touch_x || touch.py != last_touch_y)\n      {\n        // Add the event.\n        nds_event_fill_touch_move(&new_event, touch.px, touch.py);\n        nds_event_push(&new_event);\n\n        last_touch_x = touch.px;\n        last_touch_y = touch.py;\n      }\n\n      if(!last_touch_press)\n      {\n        nds_event_fill_touch_down(&new_event);\n        nds_event_push(&new_event);\n\n        last_touch_press = true;\n      }\n    }\n    else if(last_touch_press)\n    {\n      nds_event_fill_touch_up(&new_event);\n      nds_event_push(&new_event);\n\n      last_touch_press = false;\n    }\n  }\n}\n\nvoid nds_update_input(void)\n{\n  scanKeys();\n  nds_update_hw_keys();\n  nds_update_sw_keyboard();\n  nds_update_mouse();\n}\n"
  },
  {
    "path": "arch/nds/event.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __NDS_EVENT_H__\n#define __NDS_EVENT_H__\n\n#include \"../../src/event.h\"\n\nenum focus_mode\n{\n  FOCUS_FORBID,\n  FOCUS_ALLOW, // checks if position changed\n  FOCUS_PASS // ignores all checks and check updates - for touchscreen\n};\n\nenum focus_mode get_allow_focus_changes(void);\n\n#endif /* __NDS_EVENT_H__ */\n"
  },
  {
    "path": "arch/nds/evq.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2009 Kevin Vance <kvance@kvance.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Simple event queue for NDS input events */\n\n#include \"../../src/compat.h\"\n#include \"evq.h\"\n\nstatic NDSEvent *evlist_front = NULL;\nstatic NDSEvent *evlist_back = NULL;\n\nbool nds_event_poll(NDSEvent *dest)\n{\n  NDSEvent *curr_event;\n  bool retval = false;\n\n  // Mask new input events while modifying the queue.\n  irqDisable(IRQ_TIMER2);\n\n  curr_event = evlist_front;\n  if(curr_event)\n  {\n    // Copy the event to the destination.\n    memcpy(dest, curr_event, sizeof(NDSEvent));\n    dest->_next = NULL;\n\n    // Advance to the next event.\n    if(evlist_front != evlist_back)\n    {\n      evlist_front = evlist_front->_next;\n    }\n    else\n    {\n      evlist_front = NULL;\n      evlist_back = NULL;\n    }\n\n    // Dispose of the old event.\n    free(curr_event);\n    retval = true;\n  }\n\n  // Allow new events again.\n  irqEnable(IRQ_TIMER2);\n\n  return retval;\n}\n\nvoid nds_event_push(NDSEvent *src)\n{\n  // Copy the source event.\n  NDSEvent *new_event = cmalloc(sizeof(NDSEvent));\n  memcpy(new_event, src, sizeof(NDSEvent));\n  new_event->_next = NULL;\n\n  // Add it to the list.\n  if(evlist_back)\n  {\n    evlist_back->_next = new_event;\n    evlist_back = new_event;\n  }\n  else\n  {\n    evlist_front = new_event;\n    evlist_back = new_event;\n  }\n}\n\n// Fill in a key down event.\nvoid nds_event_fill_key_down(NDSEvent *dest, int key)\n{\n  dest->type = NDS_EVENT_KEY_DOWN;\n  dest->key = key;\n}\n\n// Fill in a key up event.\nvoid nds_event_fill_key_up(NDSEvent *dest, int key)\n{\n  dest->type = NDS_EVENT_KEY_UP;\n  dest->key = key;\n}\n\n// Fill in a software keyboard key down event.\nvoid nds_event_fill_keyboard_down(NDSEvent *dest, int key)\n{\n  dest->type = NDS_EVENT_KEYBOARD_DOWN;\n  dest->key = key;\n}\n\n// Fill in a software keyboard key up event.\nvoid nds_event_fill_keyboard_up(NDSEvent *dest, int key)\n{\n  dest->type = NDS_EVENT_KEYBOARD_UP;\n  dest->key = key;\n}\n\n// Fill in a touch event.\nvoid nds_event_fill_touch_down(NDSEvent *dest)\n{\n  dest->type = NDS_EVENT_TOUCH_DOWN;\n}\n\n// Fill in a touch event.\nvoid nds_event_fill_touch_move(NDSEvent *dest, int x, int y)\n{\n  dest->type = NDS_EVENT_TOUCH_MOVE;\n  dest->x = x;\n  dest->y = y;\n}\n\n// Fill in a touch event.\nvoid nds_event_fill_touch_up(NDSEvent *dest)\n{\n  dest->type = NDS_EVENT_TOUCH_UP;\n}\n\n"
  },
  {
    "path": "arch/nds/evq.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2009 Kevin Vance <kvance@kvance.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Simple event queue for NDS input events */\n\n#ifndef __ARCH_NDS_EVQ_H\n#define __ARCH_NDS_EVQ_H\n\n#include \"../../src/compat.h\"\n\nenum NDSEventType\n{\n  NDS_EVENT_NOP = 0,\n  NDS_EVENT_KEY_DOWN,           // Hardware key down\n  NDS_EVENT_KEY_UP,             // Hardware key up\n  NDS_EVENT_KEYBOARD_DOWN,      // Software key down\n  NDS_EVENT_KEYBOARD_UP,        // Software key up\n  NDS_EVENT_TOUCH_DOWN,         // Touchscreen pen down\n  NDS_EVENT_TOUCH_MOVE,         // Touchscreen pen move\n  NDS_EVENT_TOUCH_UP,           // Touchscreen pen up\n};\n\ntypedef struct NDSEvent_tag\n{\n  enum NDSEventType type;\n\n  // On NDS_EVENT_KEY_DOWN and NDS_EVENT_KEY_UP, a keyboard bit e.g. KEY_LEFT.\n  // On NDS_EVENT_KEYBOARD_PRESSED, the ASCII character pressed (not held).\n  int key;\n\n  // NDS_EVENT_TOUCH_MOVE touch coordinates.\n  int x;\n  int y;\n\n  // Implementation: next event in the list.\n  struct NDSEvent_tag *_next;\n} NDSEvent;\n\n\n// Return true if an event is available, fill dest with this event.\nbool nds_event_poll(NDSEvent *dest);\n\n// Push a copy of this event onto the queue.\nvoid nds_event_push(NDSEvent *src);\n\n// Fill in different event structures.\nvoid nds_event_fill_key_down(NDSEvent *dest, int key);\nvoid nds_event_fill_key_up(NDSEvent *dest, int key);\nvoid nds_event_fill_keyboard_down(NDSEvent *dest, int key);\nvoid nds_event_fill_keyboard_up(NDSEvent *dest, int key);\nvoid nds_event_fill_touch_down(NDSEvent *dest);\nvoid nds_event_fill_touch_move(NDSEvent *dest, int x, int y);\nvoid nds_event_fill_touch_up(NDSEvent *dest);\n\n#endif /* __ARCH_NDS_EVQ_H */\n"
  },
  {
    "path": "arch/nds/extmem.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Kevin Vance <kvance@kvance.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* This file is only used on NDS, and is not even compiled on\n * other systems. It handles transferral of main-memory data structures\n * into slower external memory to reduce memory pressure.\n */\n\n#include \"../../src/error.h\"\n#include \"../../src/extmem.h\"\n#include \"../../src/robot.h\"\n#include \"../../src/util.h\"\n\n#include \"dlmalloc.h\"\n#include \"ram.h\"\n#include \"extmem.h\"\n\n#define ALLOW_VRAM_MSPACE\n\nstruct extram_mspace_def\n{\n  u32 start;\n  u32 end;\n};\n\nenum extram_mspace\n{\n  MSPACE_VRAM = 0,\n  MSPACE_SLOT_2,\n  MSPACE_COUNT\n};\n\nstatic u16 *slot2_base;\nstatic size_t slot2_capacity;\nstatic boolean has_extmem;\nstatic mspace nds_mspace[MSPACE_COUNT];\nstatic struct extram_mspace_def nds_mspace_def[MSPACE_COUNT];\n\nstatic void nds_ext_print_info(void)\n{\n  static boolean has_printed = false;\n  if(has_printed) return;\n  has_printed = true;\n\n#ifdef ALLOW_VRAM_MSPACE\n  if(nds_mspace_def[MSPACE_VRAM].start != 0)\n    info(\"Using extra VRAM for board storage.\\n\");\n#endif\n\n#ifdef CONFIG_NDS_BLOCKSDS\n#define SLOT2_NAME() peripheralSlot2GetName()\n#else\n#define SLOT2_NAME() ram_type_string()\n#endif\n\n  if(nds_mspace_def[MSPACE_SLOT_2].start != 0)\n    info(\"Using '%s' RAM expansion with capacity of %d (base %p).\\n\",\n     SLOT2_NAME(), slot2_capacity, (void *)slot2_base);\n  else\n    info(\"No RAM expansion detected.\\n\");\n}\n\nstatic void *nds_ext_malloc(size_t bytes)\n{\n  void *retval = NULL;\n  int i;\n\n  for(i = 0; i < MSPACE_COUNT; i++)\n  {\n    if(nds_mspace_def[i].start != 0)\n    {\n      retval = mspace_malloc(nds_mspace[i], bytes);\n      if(retval != NULL)\n        break;\n    }\n  }\n\n  return retval;\n}\n\n// TODO: Support reallocation from one mspace to another?\nstatic inline void *nds_ext_realloc(void *mem, size_t bytes)\n{\n  int i;\n\n  for(i = 0; i < MSPACE_COUNT; i++)\n    if(((u32)mem) >= nds_mspace_def[i].start && ((u32)mem) < nds_mspace_def[i].end)\n      return mspace_realloc(nds_mspace[i], mem, bytes);\n\n  return NULL;\n}\n\nstatic void nds_ext_free(void *mem)\n{\n  int i;\n\n  for(i = 0; i < MSPACE_COUNT; i++)\n    if(((u32)mem) >= nds_mspace_def[i].start && ((u32)mem) < nds_mspace_def[i].end)\n      mspace_free(nds_mspace[i], mem);\n}\n\nstatic void nds_ext_unlock(void)\n{\n  if(nds_mspace_def[MSPACE_SLOT_2].start != 0)\n#ifdef CONFIG_NDS_BLOCKSDS\n    peripheralSlot2Open(SLOT2_PERIPHERAL_EXTRAM);\n#else\n    ram_unlock();\n#endif\n}\n\nstatic void nds_ext_lock(void)\n{\n  if(nds_mspace_def[MSPACE_SLOT_2].start != 0)\n#ifdef CONFIG_NDS_BLOCKSDS\n    peripheralSlot2Close();\n#else\n    ram_lock();\n#endif\n}\n\n/* Attempt to allocate a memory mapped buffer in extra RAM. */\nuint32_t *platform_extram_alloc(size_t len)\n{\n  return nds_ext_malloc(len);\n}\n\n/* Attempt to reallocate a memory mapped buffer in extra RAM. */\nuint32_t *platform_extram_resize(void *buffer, size_t len)\n{\n  return nds_ext_realloc(buffer, len);\n}\n\n/* Free a memory mapped buffer in extra RAM. */\nvoid platform_extram_free(void *buffer)\n{\n  nds_ext_free(buffer);\n}\n\nvoid platform_extram_lock(void)\n{\n  nds_ext_print_info();\n  nds_ext_unlock();\n}\n\nvoid platform_extram_unlock(void)\n{\n  nds_ext_lock();\n}\n\nstatic void nds_create_mspace_with_base(enum extram_mspace id, void *base, size_t capacity)\n{\n  nds_mspace_def[id].start = (u32)base;\n  nds_mspace_def[id].end = (u32)base + capacity;\n  nds_mspace[id] = create_mspace_with_base(base, capacity, 0);\n}\n\nboolean nds_ram_init(void)\n{\n  int i;\n\n  // Clear mspace flags.\n  memset(nds_mspace_def, 0, sizeof(nds_mspace_def));\n\n  // Allocate VRAM mspace.\n#ifdef ALLOW_VRAM_MSPACE\n  // Use VRAM banks C-G as mspace.\n  // See internals_notes.txt for details.\n  vramSetBankC(VRAM_C_LCD);\n  vramSetBankD(VRAM_D_LCD);\n  vramSetBankE(VRAM_E_LCD);\n  vramSetBankF(VRAM_F_LCD);\n  vramSetBankG(VRAM_G_LCD);\n  nds_create_mspace_with_base(MSPACE_VRAM, (u16 *)0x06840000, 0x06898000 - 0x06840000);\n#endif\n\n#ifdef CONFIG_NDS_BLOCKSDS\n  // Check for RAM expansion.\n  if(peripheralSlot2Init(SLOT2_PERIPHERAL_EXTRAM) && peripheralSlot2RamStart())\n  {\n    // Initialize an mspace for this RAM.\n    slot2_base = peripheralSlot2RamStart();\n    slot2_capacity = peripheralSlot2RamSize();\n    nds_create_mspace_with_base(MSPACE_SLOT_2, slot2_base, slot2_capacity);\n    nds_ext_lock();\n  }\n#else\n  // Check for RAM expansion.\n  if(ram_init(DETECT_RAM))\n  {\n    // Initialize an mspace for this RAM.\n    slot2_base = (u16 *)ram_unlock();\n    slot2_capacity = ram_size();\n    nds_create_mspace_with_base(MSPACE_SLOT_2, slot2_base, slot2_capacity);\n    nds_ext_lock();\n  }\n#endif\n\n  has_extmem = false;\n  for(i = 0; i < MSPACE_COUNT; i++)\n  {\n    if(nds_mspace_def[i].start > 0)\n    {\n      has_extmem = true;\n      break;\n    }\n  }\n\n  return has_extmem;\n}\n"
  },
  {
    "path": "arch/nds/extmem.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Kevin Vance <kvance@kvance.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/compat.h\"\n\n#ifndef NDS_EXTMEM_H\n#define NDS_EXTMEM_H\n\n__M_BEGIN_DECLS\n\nboolean nds_ram_init(void);\n\nuint32_t *platform_extram_alloc(size_t len);\nuint32_t *platform_extram_resize(void *buffer, size_t len);\nvoid platform_extram_free(void *buffer);\nvoid platform_extram_lock(void);\nvoid platform_extram_unlock(void);\n\n__M_END_DECLS\n\n#endif /* NDS_EXTMEM_H */\n"
  },
  {
    "path": "arch/nds/gen_protected_palette.py",
    "content": "#!/usr/bin/env python3\n\n# MegaZeux\n#\n# Copyright (C) 2020 Adrian Siekierka <asiekierka@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\nfrom PIL import Image\nimport struct, sys\n\nCOLORS = [\n\t(0, 0, 0),\n\t(0, 0, 170),\n\t(0, 170, 0),\n\t(0, 170, 170),\n\t(170, 0, 0),\n\t(170, 0, 170),\n\t(170, 85, 0),\n\t(170, 170, 170),\n\t(85, 85, 85),\n\t(85, 85, 255),\n\t(85, 255, 85),\n\t(85, 255, 255),\n\t(255, 85, 85),\n\t(255, 85, 255),\n\t(255, 255, 85),\n\t(255, 255, 255)\n]\n\n# overlay size (16) + solid palette size (16) + blends size ((16+1)*8)\nBLENDING_LENGTH = int((15+1)*15/2)\nPALETTE_OFFSET = 16 + 16 + BLENDING_LENGTH\nPALETTE_LEN = 256 - PALETTE_OFFSET\nPALETTE_SOLID_LEN = 16\nPALETTE_BLEND_LEN = PALETTE_LEN - 16\nIDX_TABLE = [PALETTE_OFFSET] * 256\n\ndef xy2idx(x, y):\n\treturn (y * 16) + x\n\ndef blend(c1, c2):\n\treturn (\n\t\t(c1[0] + c2[0]) >> 1,\n\t\t(c1[1] + c2[1]) >> 1,\n\t\t(c1[2] + c2[2]) >> 1\n\t)\n\ndef col2nds(c):\n\tcr = (c[0] >> 3)\n\tcg = (c[1] >> 3)\n\tcb = (c[2] >> 3)\n\treturn (1 << 15) | (cb << 10) | (cg << 5) | cr\n\n# populate IDX_TABLE with solid colors\nfor a in range(0, 16):\n\tIDX_TABLE[xy2idx(a, a)] = PALETTE_OFFSET + a\n\n# generate texture with all blending colors\ni = 0\nblend_tex = Image.new('RGB', (BLENDING_LENGTH, 1))\n\nfor a in range(0, 16):\n\tfor b in range(a + 1, 16):\n\t\tblend_tex.putpixel((i, 0), blend(COLORS[a], COLORS[b]))\n\t\ti += 1\nprint(\"%d -> %d colors (expected %d)\" % (i, PALETTE_BLEND_LEN, BLENDING_LENGTH))\nassert i == BLENDING_LENGTH\n\n# palettize texture\n#blend_tex = blend_tex.convert('P', dither=Image.NONE, palette=Image.ADAPTIVE, colors=PALETTE_BLEND_LEN)\nblend_tex = blend_tex.quantize(colors=PALETTE_BLEND_LEN, method=Image.MAXCOVERAGE, dither=Image.NONE)\n\n# populate IDX_TABLE\ni = 0\nfor a in range(0, 16):\n\tfor b in range(a + 1, 16):\n\t\tpal_idx = PALETTE_OFFSET + 16 + blend_tex.getpixel((i, 0))\n\t\tIDX_TABLE[xy2idx(a, b)] = pal_idx\n\t\tIDX_TABLE[xy2idx(b, a)] = pal_idx\n\t\ti += 1\n\n# generate binary data\nwith open(sys.argv[1], \"wb\") as fp:\n\t# write IDX_TABLE\n\tfor i in range(0, 256):\n\t\tfp.write(struct.pack(\"<B\", IDX_TABLE[i]))\n\t# write palette data\n\tfor i in range(0, 16):\n\t\tfp.write(struct.pack(\"<H\", col2nds(COLORS[i])))\n\tblend_pal = blend_tex.getpalette()\n\tfor i in range(0, PALETTE_BLEND_LEN):\n\t\tfp.write(struct.pack(\"<H\", col2nds(blend_pal[i*3:(i+1)*3])))\n"
  },
  {
    "path": "arch/nds/internals_notes.txt",
    "content": "VRAM layout\n--\nNOTE: This is for memory usage planning. All addresses given in ARM9 access.\n\nVRAM banks A, B (sub screen)\n\n06800000 - 0680F7FF => keyboard data; 45056/63488 bytes used\n0680F800 - 06813FFF => console data; most space used\n06814000 - 0683FBFF => 512x350 8-bit bitmap for scaled screen; 320 bytes per each 512 byte block used)\n0683FC00 - 0683FFFF => unused\n\nVRAM banks C, D, E, F, G\n\n06840000 - 06897FFF => unused\n\nVRAM banks H, I (main screen)\n\n06898000 - 06898FFF => foreground character map\n06899000 - 06899FFF => background character map\n0689A000 - 0689A03F => background character tiles (1 x 2 tiles - mind the offset)\n0689A040 - 0689BFFF => unused\n0689C000 - 068A3FFF => foreground character tiles (512 x 2 tiles)\n"
  },
  {
    "path": "arch/nds/malloc_opts.h",
    "content": "#ifndef NDS_RAM_OPTS_H\n#define NDS_RAM_OPTS_H\n\n#define INSECURE\t\t0\n#define ONLY_MSPACES    \t1\n#define HAVE_MORECORE\t\t0\n#define HAVE_MMAP\t\t0\n#define MSPACES\t\t\t1\n#define LACKS_SYS_MMAN_H \t1\n#define LACKS_SCHED_H\t\t1\n#define FOOTERS\t\t\t0\n#define NO_MALLOC_STATS\t\t1\n#define PROCEED_ON_ERROR\t1\n#define USE_LOCKS\t\t0\n\n#define malloc_getpagesize\t4096\n#undef  DEBUG\n\n#endif\n"
  },
  {
    "path": "arch/nds/pad.config",
    "content": "# Hat: Directional pad\r\n# Button1: A\r\n# Button2: B\r\n# Button3: X\r\n# Button4: Y\r\n# Button5: Left shoulder\r\n# Button6: Right shoulder\r\n# Button7: Select\r\n# Button8: Start\r\n\r\njoy1hat = act_up, act_down, act_left, act_right\r\njoy1button1 = act_a\r\njoy1button2 = act_b\r\njoy1button3 = act_x\r\njoy1button4 = act_y\r\njoy1button5 = act_lshoulder\r\njoy1button6 = act_rshoulder\r\njoy1button7 = act_select\r\njoy1button8 = act_start\r\n"
  },
  {
    "path": "arch/nds/platform.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007-2009 Kevin Vance <kvance@kvance.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdarg.h>\n\n#include \"../../src/platform.h\"\n#undef main\n\n#include \"../../src/graphics.h\"\n#include \"../../src/util.h\"\n\n#include <fat.h>\n#include \"platform.h\"\n#include \"render.h\"\n#include \"extmem.h\"\n#include \"dlmalloc.h\"\n\n// from arch/nds/event.c\nextern void nds_update_input(void);\n\n// Timer code stolen from SDL (LGPL)\n\n#define timers2ms(tlow,thigh) ((tlow) | ((thigh)<<16)) >> 7\n#define timers2ticks(tlow,thigh) ((tlow) | ((thigh)<<16))\n\nvoid delay(uint32_t ms)\n{\n  uint32_t now;\n  now = timers2ms(TIMER0_DATA, TIMER1_DATA);\n  while((uint32_t)timers2ms(TIMER0_DATA, TIMER1_DATA) < now + ms)\n    ;\n}\n\nuint64_t get_ticks(void)\n{\n  return timers2ms(TIMER0_DATA, TIMER1_DATA);\n}\n\nstatic void timer_init(void)\n{\n  // TIMER0, TIMER1: Tick timer\n  TIMER0_DATA = 0;\n  TIMER1_DATA = 0;\n  TIMER0_CR = TIMER_ENABLE | TIMER_DIV_256;\n  TIMER1_CR = TIMER_ENABLE | TIMER_CASCADE;\n\n  // TIMER2: Input handler IRQ\n  // We could do this in vblank, but sometimes we need to block new input\n  // events when the event queue is in use.  Using a hardware timer, we can\n  // keep the graphics flicker running.\n  TIMER2_DATA = TIMER_FREQ_1024(60);\n  TIMER2_CR = TIMER_ENABLE | TIMER_DIV_1024 | TIMER_IRQ_REQ;\n  irqSet(IRQ_TIMER2, nds_update_input);\n  irqEnable(IRQ_TIMER2);\n}\n\n#ifdef BUILD_PROFILING\n\n#define PROFILE_QUEUE_DEPTH 4\n\nstatic const char *profile_names[PROFILE_QUEUE_DEPTH];\nstatic u32 profile_times[PROFILE_QUEUE_DEPTH];\nstatic int profile_pos = 0;\n\nITCM_CODE\nvoid profile_start(const char *name)\n{\n  if(profile_pos < PROFILE_QUEUE_DEPTH)\n  {\n    profile_names[profile_pos] = name;\n    profile_times[profile_pos] = timers2ticks(TIMER0_DATA, TIMER1_DATA);\n  }\n\n  profile_pos++;\n}\n\nITCM_CODE\nvoid profile_end(void)\n{\n  u32 ctime;\n\n  if(profile_pos <= 0)\n    return;\n\n  if(--profile_pos < PROFILE_QUEUE_DEPTH)\n  {\n    ctime = timers2ticks(TIMER0_DATA, TIMER1_DATA);\n    printf(\"%s: %lld cycles\\n\", profile_names[profile_pos], (u64) (ctime - profile_times[profile_pos]) << 8);\n  }\n}\n\n#endif\n\n// pulled in from libnds - we need to wrap it to undo some of our\n// graphics changes\nextern void guruMeditationDump(void);\n\nstatic void mzxExceptionHandler(void)\n{\n  // stop vblank handler\n  irqClear(IRQ_VBLANK);\n\n  // clear sub screen (incl. DMA to it)\n  DMA0_CR = 0;\n  DMA1_CR = 0;\n  DMA2_CR = 0;\n  DMA3_CR = 0;\n  REG_BG0HOFS_SUB = 0;\n  REG_BG0VOFS_SUB = 0;\n\n  videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE);\n  vramSetBankC(VRAM_C_SUB_BG);\n\n#ifdef CONFIG_STDIO_REDIRECT\n  // The log files can get corrupted if they aren't explicitly closed here.\n  redirect_stdio_exit();\n#endif\n\n  guruMeditationDump();\n  while(1)\n  {\n  }\n}\n\nboolean platform_init(void)\n{\n  powerOn(POWER_ALL_2D);\n  setExceptionHandler(mzxExceptionHandler);\n\n  if(!fatInitDefault())\n  {\n    printf(\"Unable to initialize FAT.\\n\"\n            \"Check your DLDI driver.\\n\");\n    return false;\n  }\n\n#ifdef CONFIG_EXTRAM\n  if(!isDSiMode())\n    nds_ram_init();\n#endif\n  timer_init();\n\n  // Enable vblank interrupts, but don't install the handler until the\n  // graphics have initialized.\n  irqEnable(IRQ_VBLANK);\n\n  return true;\n}\n\nvoid platform_quit(void)\n{\n}\n\n// argc/argv is unreliable on NDS and varies between cards/launchers.\n\nint main(int argc, char *argv[])\n{\n  static char _argv0[] = SHAREDIR \"/mzxrun.nds\";\n  static char *_argv[] = {_argv0};\n  consoleDemoInit();\n\n  if(argc < 1 || argv == NULL || argv[0] == NULL)\n  {\n    printf(\"argv[0]: not found.\\n\"\n            \"using '%s'\\n\"\n            \"WARNING: Use of a loader that supports argv[0] is recommended.\\n\",\n            _argv0);\n\n    real_main(1, _argv);\n  }\n  else\n  {\n    printf(\"argv[0]: '%s'\\n\", argv[0]);\n    real_main(argc, argv);\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "arch/nds/platform.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __3DS_PLATFORM_H__\n#define __3DS_PLATFORM_H__\n\n#include \"../../src/compat.h\"\n\n#include <nds.h>\n\n// #define BUILD_PROFILING\n\n#ifdef BUILD_PROFILING\nvoid profile_start(const char *name);\nvoid profile_end(void);\n#else\n#define profile_start(name) {}\n#define profile_end() {}\n#endif\n\n// FIFO definitions.\n#define FIFO_MZX FIFO_USER_01\n#define CMD_MZX_PCS_TONE 0x01\n#define CMD_MZX_SOUND_VOLUME 0x02\n#define CMD_MZX_MM_GET_POSITION 0x03\n\n#ifdef CONFIG_AUDIO\nvoid nds_audio_vblank(void);\n#endif\n\n#endif /* __3DS_PLATFORM_H__ */\n"
  },
  {
    "path": "arch/nds/ram.c",
    "content": "#ifndef CONFIG_NDS_BLOCKSDS\n/**********************************\n  Copyright (C) Rick Wong (Lick)\n\n Credits: Amadeus, Chishm, Cory1492, Lazy1, Pepsiman, Viruseb\n\nDual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) \nand GPLv2 (http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt) licenses. \n\n***********************************/\n#include \"ram.h\"\n\n#include \"../../src/compat.h\"\n\n// Disable annoying GCC warnings.\n#pragma GCC diagnostic ignored \"-Wunused-but-set-variable\"\n\n\n//===================================//\n//                                   //\n//       Device Ram Drivers !        //\n//                                   //\n//===================================//\n\n//========================\nstatic vu16 *_sc_unlock (void)\n//========================\n{\n    *(vu16*)0x9FFFFFE = 0xA55A;\n    *(vu16*)0x9FFFFFE = 0xA55A;\n    *(vu16*)0x9FFFFFE = 0x5; // RAM_RW\n    *(vu16*)0x9FFFFFE = 0x5;\n\n    return (vu16*)0x8000000;\n}\n\n//========================\nstatic void _sc_lock (void)\n//========================\n{\n    *(vu16*)0x9FFFFFE = 0xA55A;\n    *(vu16*)0x9FFFFFE = 0xA55A;\n    *(vu16*)0x9FFFFFE = 0x3; // MEDIA\n    *(vu16*)0x9FFFFFE = 0x3;\n}\n\n//========================\nstatic vu16 *_m3_unlock (void)\n//========================\n{\n    u32 mode = 0x00400006; // RAM_RW\n    vu16 tmp;\n    tmp = *(vu16*)0x08E00002;\n    tmp = *(vu16*)0x0800000E;\n    tmp = *(vu16*)0x08801FFC;\n    tmp = *(vu16*)0x0800104A;\n    tmp = *(vu16*)0x08800612;\n    tmp = *(vu16*)0x08000000;\n    tmp = *(vu16*)0x08801B66;\n    tmp = *(vu16*)(0x08000000 + (mode << 1));\n    tmp = *(vu16*)0x0800080E;\n    tmp = *(vu16*)0x08000000;\n    tmp = *(vu16*)0x080001E4;\n    tmp = *(vu16*)0x080001E4;\n    tmp = *(vu16*)0x08000188;\n    tmp = *(vu16*)0x08000188;\n\n    return (vu16*)0x8000000;\n}\n\n//========================\nstatic void _m3_lock (void)\n//========================\n{\n    u32 mode = 0x00400003; // MEDIA\n    vu16 tmp;\n    tmp = *(vu16*)0x08E00002;\n    tmp = *(vu16*)0x0800000E;\n    tmp = *(vu16*)0x08801FFC;\n    tmp = *(vu16*)0x0800104A;\n    tmp = *(vu16*)0x08800612;\n    tmp = *(vu16*)0x08000000;\n    tmp = *(vu16*)0x08801B66;\n    tmp = *(vu16*)(0x08000000 + (mode << 1));\n    tmp = *(vu16*)0x0800080E;\n    tmp = *(vu16*)0x08000000;\n    tmp = *(vu16*)0x080001E4;\n    tmp = *(vu16*)0x080001E4;\n    tmp = *(vu16*)0x08000188;\n    tmp = *(vu16*)0x08000188;\n}\n\n//========================\nstatic vu16 *_opera_unlock (void)\n//========================\n{\n    *(vu16*)0x8240000 = 1;\n\n    return (vu16*)0x9000000;\n}\n\n//========================\nstatic void _opera_lock (void)\n//========================\n{\n    *(vu16*)0x8240000 = 0;\n}\n\n\n//========================\nstatic vu16 *_g6_unlock (void)\n//========================\n{\n    u32 mode = 6; // RAM_RW\n    vu16 tmp;\n\ttmp = *(vu16*)0x09000000;\n\ttmp = *(vu16*)0x09FFFFE0;\n\ttmp = *(vu16*)0x09FFFFEC;\n\ttmp = *(vu16*)0x09FFFFEC;\n\ttmp = *(vu16*)0x09FFFFEC;\n\ttmp = *(vu16*)0x09FFFFFC;\n\ttmp = *(vu16*)0x09FFFFFC;\n\ttmp = *(vu16*)0x09FFFFFC;\n\ttmp = *(vu16*)0x09FFFF4A;\n\ttmp = *(vu16*)0x09FFFF4A;\n\ttmp = *(vu16*)0x09FFFF4A;\n\ttmp = *(vu16*)(0x09200000 + (mode << 1));\n\ttmp = *(vu16*)0x09FFFFF0;\n\ttmp = *(vu16*)0x09FFFFE8;\n\n    return (vu16*)0x8000000;\n}\n\n//========================\nstatic void _g6_lock (void)\n//========================\n{\n    u32 mode = 3; // MEDIA\n    vu16 tmp;\n\ttmp = *(vu16*)0x09000000;\n\ttmp = *(vu16*)0x09FFFFE0;\n\ttmp = *(vu16*)0x09FFFFEC;\n\ttmp = *(vu16*)0x09FFFFEC;\n\ttmp = *(vu16*)0x09FFFFEC;\n\ttmp = *(vu16*)0x09FFFFFC;\n\ttmp = *(vu16*)0x09FFFFFC;\n\ttmp = *(vu16*)0x09FFFFFC;\n\ttmp = *(vu16*)0x09FFFF4A;\n\ttmp = *(vu16*)0x09FFFF4A;\n\ttmp = *(vu16*)0x09FFFF4A;\n\ttmp = *(vu16*)(0x09200000 + (mode << 1));\n\ttmp = *(vu16*)0x09FFFFF0;\n\ttmp = *(vu16*)0x09FFFFE8;\n}\n\n//========================\nstatic vu16 *_ez_unlock (void)\n//========================\n{\n    *(vu16*)0x9FE0000 = 0xD200; // SD_Disable\n    *(vu16*)0x8000000 = 0x1500;\n    *(vu16*)0x8020000 = 0xD200;\n    *(vu16*)0x8040000 = 0x1500;\n    *(vu16*)0x9400000 = 0;\n    *(vu16*)0x9C40000 = 0xD200;\n    *(vu16*)0x9FC0000 = 0x1500;\n\n    *(vu16*)0x9FE0000 = 0xD200; // SetRompage (OS mode)\n    *(vu16*)0x8000000 = 0x1500;\n    *(vu16*)0x8020000 = 0xD200;\n    *(vu16*)0x8040000 = 0x1500;\n    *(vu16*)0x9880000 = 0x8000;\n    *(vu16*)0x9FC0000 = 0x1500;\n\n    *(vu16*)0x9FE0000 = 0xD200; // OpenNorWrite\n    *(vu16*)0x8000000 = 0x1500;\n    *(vu16*)0x8020000 = 0xD200;\n    *(vu16*)0x8040000 = 0x1500;\n    *(vu16*)0x9C40000 = 0x1500;\n    *(vu16*)0x9FC0000 = 0x1500;\n\n    return (vu16*)0x8400000;\n}\n\n//========================\nstatic void _ez_lock (void)\n//========================\n{\n    *(vu16*)0x9FE0000 = 0xD200; // CloseNorWrite\n    *(vu16*)0x8000000 = 0x1500;\n    *(vu16*)0x8020000 = 0xD200;\n    *(vu16*)0x8040000 = 0x1500;\n    *(vu16*)0x9C40000 = 0xD200;\n    *(vu16*)0x9FC0000 = 0x1500;\n\n    *(vu16*)0x9FE0000 = 0xD200; // SetRompage (game mode)\n    *(vu16*)0x8000000 = 0x1500;\n    *(vu16*)0x8020000 = 0xD200;\n    *(vu16*)0x8040000 = 0x1500;\n    *(vu16*)0x9880000 = 0x0000;\n    *(vu16*)0x9FC0000 = 0x1500;\n\n    *(vu16*)0x9FE0000 = 0xD200; // SD_Enable\n    *(vu16*)0x8000000 = 0x1500;\n    *(vu16*)0x8020000 = 0xD200;\n    *(vu16*)0x8040000 = 0x1500;\n    *(vu16*)0x9400000 = 1;\n    *(vu16*)0x9C40000 = 0x1500;\n    *(vu16*)0x9FC0000 = 0x1500;\n}\n\n\n//===================================//\n//                                   //\n//              Ram API !            //\n//                                   //\n//===================================//\n\nstatic vu16* (*_unlock) (void) = 0;\nstatic void  (*_lock) (void) = 0;\nstatic u32  _size = 0;\nstatic RAM_TYPE _type = DETECT_RAM;\nconst char *_types[] = {\"Unknown\", \"Supercard\", \"M3\", \"Opera\", \"G6\", \"EZ\"};\n\n//==========================================================\nstatic bool  _ram_test (void)\n//==========================================================\n{\n    vu16 *ram = _unlock();\n\n    ram[0] = 0x1234;\n    if(ram[0] == 0x1234)        // test writability\n    {\n        _lock();\n\n        ram[0] = 0x4321;\n        if(ram[0] != 0x4321)    // test non-writability\n        {\n            return true;\n        }\n    }\n\n    return false;\n}\n\n\n//==========================================================\nstatic void  _ram_precalc_size (void)\n//==========================================================\n{\n    vu16 *ram;\n\n    if(_unlock == 0 || _lock == 0)\n        return;\n        \n    ram = _unlock();\n    _size = 0;\n\n    ram[0] = 0x2468;\n    while(ram[_size] == 0x2468)\n    {\n        ram[_size] = 0;\n        _size += 512;\n        ram[_size] = 0x2468;\n    }\n    _size<<=1;\n\n    _lock();\n}\n\n\n//==========================================================\nbool  ram_init (RAM_TYPE type)\n//==========================================================\n{\n    sysSetCartOwner(BUS_OWNER_ARM9);\n\n    switch(type)\n    {\n        case SC_RAM:\n        {\n            _unlock = _sc_unlock;\n            _lock   = _sc_lock;\n            _type   = SC_RAM;\n        }\n        break;\n\n        case M3_RAM:\n        {\n            _unlock = _m3_unlock;\n            _lock   = _m3_lock;\n            _type   = M3_RAM;\n        }\n        break;\n\n        case OPERA_RAM:\n        {\n            _unlock = _opera_unlock;\n            _lock   = _opera_lock;\n            _type   = OPERA_RAM;\n        }\n        break;\n\n        case G6_RAM:\n        {\n            _unlock = _g6_unlock;\n            _lock   = _g6_lock;\n            _type   = G6_RAM;\n        }\n        break;\n\n        case EZ_RAM:\n        {\n            _unlock = _ez_unlock;\n            _lock   = _ez_lock;\n            _type   = EZ_RAM;\n        }\n        break;\n\n        case DETECT_RAM:\n        default:\n        {\n            // try ez\n            _unlock = _ez_unlock;\n            _lock   = _ez_lock;\n            _type   = EZ_RAM;\n            \n            if(_ram_test())\n            {\n                break;\n            }\n\n            // try supercard\n            _unlock = _sc_unlock;\n            _lock   = _sc_lock;\n            _type   = SC_RAM;\n\n            if(_ram_test())\n            {\n                break;\n            }\n\n            // try m3\n            _unlock = _m3_unlock;\n            _lock   = _m3_lock;\n            _type   = M3_RAM;\n\n            if(_ram_test())\n            {\n                break;\n            }\n\n            // try opera\n            _unlock = _opera_unlock;\n            _lock   = _opera_lock;\n            _type   = OPERA_RAM;\n\n            if(_ram_test())\n            {\n                break;\n            }\n\n            // try g6\n            _unlock = _g6_unlock;\n            _lock   = _g6_lock;\n            _type   = G6_RAM;\n            \n            if(_ram_test())\n            {\n                break;\n            }\n\n            // fail\n            _unlock = 0;\n            _lock   = 0;\n            _type   = DETECT_RAM;\n\n            return false;\n        }\n        break;\n    }\n    \n    _ram_precalc_size();\n    \n    return true;\n}\n\n\n//==========================================================\nRAM_TYPE   ram_type (void)\n//==========================================================\n{\n    return _type;\n}\n\n\n//==========================================================\nconst char*   ram_type_string (void)\n//==========================================================\n{\n    return _types[(int)_type];\n}\n\n\n//==========================================================\nu32   ram_size (void)\n//==========================================================\n{\n    return _size;\n}\n\n\n//==========================================================\nvu16* ram_unlock (void)\n//==========================================================\n{\n    sysSetCartOwner(BUS_OWNER_ARM9);\n\n    if(_unlock)\n        return _unlock();\n    return 0;\n}\n\n\n//==========================================================\nvoid  ram_lock (void)\n//==========================================================\n{\n    sysSetCartOwner(BUS_OWNER_ARM9);\n\n    if(_lock)\n        _lock();\n}\n\n\n//==========================================================\nvoid  ram_turbo (bool enable)\n//==========================================================\n{\n    if(enable)\n        REG_EXMEMCNT |= 0x001A;\n    else\n        REG_EXMEMCNT &= ~0x001A;\n}\n\n\n#endif\n"
  },
  {
    "path": "arch/nds/ram.h",
    "content": "#ifndef CONFIG_NDS_BLOCKSDS\n/**********************************\n  Copyright (C) Rick Wong (Lick)\n\n Credits: Amadeus, Chishm, Cory1492, Lazy1, Pepsiman, Viruseb\n\nDual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) \nand GPLv2 (http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt) licenses. \n\n***********************************/\n#ifndef __RAM\n#define __RAM\n\n#include <nds/ndstypes.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef enum { DETECT_RAM=0, SC_RAM, M3_RAM, OPERA_RAM, G6_RAM, EZ_RAM } RAM_TYPE;\n\n//  Call this before the others\nbool  ram_init (RAM_TYPE);\n\n//  Returns the type of the RAM device\nRAM_TYPE   ram_type (void);\n\n//  Returns the type of the RAM device in a string\nconst char*   ram_type_string (void);\n\n//  Returns the total amount of RAM in bytes\nu32   ram_size (void);\n\n\n//  Unlocks the RAM and returns a pointer to the begin\nvu16* ram_unlock (void);\n\n//  Locks the RAM\nvoid  ram_lock (void);\n\n//  enable = set lowest waitstates, disable = set default waitstates\nvoid  ram_turbo (bool enable);\n\n\n#ifdef __cplusplus\n}\n#endif\n#endif\n#endif\n"
  },
  {
    "path": "arch/nds/render.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007-2009 Kevin Vance <kvance@kvance.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// LIMITATIONS:\n// - No SMZX mode support.\n// - No extended charset support. In theory, 512 characters could be\n//   supported, after fixing the issue below.\n// - The \"1-to-1\" renderer assumes that non-protected characters always use\n//   the non-protected palette, and vice versa. This can be fixed by adding\n//   a separate background layer for protected characters with its own\n//   tileset, but it will also be a mild performance detriment.\n// - Runtime changes to the protected palette will only display on the\n//   \"1-to-1\" screen, not the \"scaled\" screen.\n\n/* NOMENCLATURE NOTICE:\n * What we call the \"subscreen\" is called the \"main screen\" by libnds.\n * But in terms of functionality, it only contains auxiliary screen modes.\n */\n\n#include <string.h>\n\n#include \"event.h\"\n#include \"render.h\"\n#include \"platform.h\"\n\n#include \"../../src/renderers.h\"\n#include \"../../src/graphics.h\"\n#include \"../../src/render.h\"\n\n#include <nds/arm9/keyboard.h>\n\n#include \"protected_palette_bin.h\"\n\n#define SCALED_USE_CELL_CACHE /* 5% penalty on full redraws, up to 20x speed increase on no redraws */\n\n// These variables control the panning along the 1:1 \"main\" screen.\nDTCM_BSS\nstatic int cell_pan_x = 0;\nDTCM_BSS\nstatic int cell_pan_y = 0;\nDTCM_BSS\nstatic int mainscr_x_33rd = 0;\nDTCM_BSS\nstatic int mainscr_y_max = 0;\n\n// When set to >= 0, the upper screen will attempt to focus to this position\n// the next  time it is drawn and then reset them to -1.\nstatic int last_focus_x = -1;\nstatic int last_focus_y = -1;\nstatic int focus_x = -1;\nstatic int focus_y = -1;\n\n// This table stores scroll register values for each scanline.\nstatic u16 scroll_table[192];\n\n// This table maps palette color combinations to their index in BG_PALETTE.\nstatic u8 palette_idx_table[256];\n\n#define PALETTE_NORMAL_START (16)\n#define PALETTE_PROTECTED_START (PALETTE_NORMAL_START + 16 + 120)\n#define palette_protected_idx_table protected_palette_bin\n\n// Y offset information for subscreen\nstatic s16 subscreen_offset_y = 0;\nu16 subscreen_height;\n\n// The current transition state.\nstatic struct {\n  enum { TRANSITION_NONE = 0, TRANSITION_IN, TRANSITION_OUT } state;\n  int time;\n} transition;\n\n// The subscreen can display different information.\nenum Subscreen_Mode last_subscreen_mode;\nenum Subscreen_Mode subscreen_mode;\n\n// Initialized with a pointer to static libnds memory via keyboardInit.\nstatic Keyboard *kb;\n\n// Forward declarations\nstatic void nds_keyboard_scroll_in(void);\nstatic void nds_keyboard_scroll_out(void);\n\n// Do this every vblank irq:\nstatic void nds_on_vblank(void)\n{\n  /* Handle sleep mode. */\n  nds_sleep_check();\n\n  /* Do all special video handling. */\n  nds_video_rasterhack();\n  nds_video_jitter();\n  nds_video_do_transition();\n\n#ifdef CONFIG_AUDIO\n  /* Handle PC speaker audio. */\n  nds_audio_vblank();\n#endif\n}\n\nboolean is_scaled_mode(enum Subscreen_Mode mode)\n{\n  return (mode == SUBSCREEN_SCALED);\n}\n\nstatic void palette_idx_table_init(void)\n{\n  // Start the palette at 16, reserving colors 0-16 for the overlay.\n  const int offset = PALETTE_NORMAL_START;\n  int idx;\n\n  // Store the normal palette sequentially.\n  for(idx = 0; idx < 16; idx++)\n    palette_idx_table[idx * 17] = idx + offset;\n\n  // Store the blends after that.\n  idx += offset;\n  for(int a = 0; a < 16; a++)\n  {\n    for(int b = a+1; b < 16; b++)\n    {\n      palette_idx_table[(a << 4) | b] = idx;\n      palette_idx_table[(b << 4) | a] = idx;\n      idx++;\n    }\n  }\n}\n\nstatic void nds_subscreen_scaled_init(void)\n{\n  int xscale = (int)(320 * 256 / 256);\n  int yscale = (int)(350 * 256 / subscreen_height);\n\n  /* Use banks A and B for the MZX screen. */\n  videoSetMode(MODE_5_2D | DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE | DISPLAY_BG2_ACTIVE | DISPLAY_BG3_ACTIVE);\n  vramSetBankA(VRAM_A_MAIN_BG);\n  vramSetBankB(VRAM_B_MAIN_BG);\n\n  /* Use flickered alpha lerp to scale 320x350 to 256x192. */\n  /* (Bump the BG up to base 5 (+0x14000) to make room for the keyboard and console.) */\n  REG_BG2CNT = BG_BMP8_512x512 | BG_BMP_BASE(5) | BG_PRIORITY(1);\n  REG_BG2PA  = xscale;\n  REG_BG2PB  = 0;\n  REG_BG2PC  = 0;\n  REG_BG2PD  = yscale;\n  REG_BG2X   = 0;\n  REG_BG2Y   = subscreen_offset_y;\n\n  REG_BG3CNT = BG_BMP8_512x512 | BG_BMP_BASE(5) | BG_PRIORITY(1);\n  REG_BG3PA  = xscale;\n  REG_BG3PB  = 0;\n  REG_BG3PC  = 0;\n  REG_BG3PD  = yscale;\n  REG_BG3X   = 0;\n  REG_BG3Y   = subscreen_offset_y;\n\n  /* Enable BG2/BG3 blending. */\n  REG_BLDCNT   = BLEND_ALPHA | BLEND_SRC_BG2 | BLEND_DST_BG3;\n  REG_BLDALPHA = (8 << 8) | 8; /* 50% / 50% */\n\n  /* Enable the console. */\n  consoleInit(NULL, 1, BgType_Text4bpp, BgSize_T_256x256, 31, 4, true, true);\n  bgSetPriority(1, 1);\n\n  update_palette();\n  update_screen();\n}\n\nstatic void nds_subscreen_keyboard_init(void)\n{\n  // BG0: Keyboard 256x512 text, map following tiles (45056 bytes total)\n  // Clear the keyboard area before drawing it.\n  dmaFillWords(0, BG_MAP_RAM(20), 4096);\n  kb = keyboardInit(NULL, 0, BgType_Text4bpp, BgSize_T_256x512, 20, 0, true, true);\n  bgSetPriority(0, 1);\n  kb->scrollSpeed = 7;\n  transition.time = 0;\n  transition.state = TRANSITION_IN;\n}\n\nstatic void nds_subscreen_keyboard_exit(void)\n{\n  transition.time = 0;\n  transition.state = TRANSITION_OUT;\n}\n\nstatic void nds_mainscreen_init(struct graphics_data *graphics)\n{\n  u16 *vram;\n  int i;\n\n  graphics->fullscreen = true;\n  graphics->resolution_width = 640;\n  graphics->resolution_height = 350;\n\n  /* Use banks H and I for the text screens. */\n  videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE);\n  vramSetBankH(VRAM_H_SUB_BG);\n  vramSetBankI(VRAM_I_SUB_BG_0x06208000);\n\n  /* BG0: foreground characters. */\n  REG_BG0CNT_SUB  = BG_64x32 | BG_COLOR_16 | BG_MAP_BASE(0) |\n                    BG_TILE_BASE(1);\n  REG_BG0HOFS_SUB = 0;\n  REG_BG0VOFS_SUB = 0;\n\n  /* BG1: background characters. */\n  REG_BG1CNT_SUB  = BG_64x32 | BG_COLOR_16 | BG_MAP_BASE(2) |\n                    BG_TILE_BASE(0);\n  REG_BG1HOFS_SUB = 0;\n  REG_BG1VOFS_SUB = 0;\n\n  // By default, pan to the center of the screen.\n  focus_x = 640/2;\n  focus_y = 350/2;\n\n  // Add a solid tile for background colors.\n  vram = (u16*)BG_TILE_RAM_SUB(0) + 256*16;\n  for(i = 0; i < 32; i++)\n    *(vram++) = (1 << 12 | 1 << 8 | 1 << 4 | 1) * (1+(i >> 4));\n}\n\nvoid nds_video_jitter(void)\n{\n  const u16 jitterF4[] = {\n    0x060, 0x000,   /* 0.375, 0.000 */\n    0x020, 0x100,   /* 0.125, 1.000 */\n    0x0e0, 0x000,   /* 0.875, 0.000 */\n    0x0a0, 0x100,   /* 0.625, 1.000 */\n  };\n  static size_t jidx = 0;    /* jitter table index */\n\n  /* Jitter the backgrounds for scaled mode. */\n//  if(jidx >= sizeof(jitterF4)/sizeof(*jitterF4))\n//    jidx = 0;\n\n  REG_BG2X = jitterF4[jidx];\n  REG_BG2Y = jitterF4[jidx+1] + subscreen_offset_y;\n  REG_BG3X = jitterF4[jidx+2];\n  REG_BG3Y = jitterF4[jidx+3] + subscreen_offset_y;\n  jidx ^= 4;\n}\n\n/* Use HBlank DMA to load row scroll values in for each line. */\nvoid nds_video_rasterhack(void)\n{\n  /* Prepare DMA transfer. */\n#ifdef CONFIG_NDS_BLOCKSDS\n  dmaStopSafe(1);\n  REG_BG0VOFS_SUB = scroll_table[0];\n  dmaSetParams(1, (scroll_table + 1), (void*) &REG_BG0VOFS_SUB,\n                    DMA_DST_FIX | DMA_SRC_INC | DMA_REPEAT | DMA_16_BIT |\n                    DMA_START_HBL | DMA_ENABLE | 1);\n\n  dmaStopSafe(2);\n  REG_BG1VOFS_SUB = scroll_table[0];\n  dmaSetParams(2, (scroll_table + 1), (void*) &REG_BG1VOFS_SUB,\n                    DMA_DST_FIX | DMA_SRC_INC | DMA_REPEAT | DMA_16_BIT |\n                    DMA_START_HBL | DMA_ENABLE | 1);\n#else\n  DMA1_CR         = 0;\n  REG_BG0VOFS_SUB = scroll_table[0];\n  DMA1_SRC        = (u32)(scroll_table + 1);\n  DMA1_DEST       = (u32)&REG_BG0VOFS_SUB;\n  DMA1_CR         = DMA_DST_FIX | DMA_SRC_INC | DMA_REPEAT | DMA_16_BIT |\n                    DMA_START_HBL | DMA_ENABLE | 1;\n\n  DMA2_CR         = 0;\n  REG_BG1VOFS_SUB = scroll_table[0];\n  DMA2_SRC        = (u32)(scroll_table + 1);\n  DMA2_DEST       = (u32)&REG_BG1VOFS_SUB;\n  DMA2_CR         = DMA_DST_FIX | DMA_SRC_INC | DMA_REPEAT | DMA_16_BIT |\n                    DMA_START_HBL | DMA_ENABLE | 1;\n#endif\n}\n\n// Handle the transition animation.\nvoid nds_video_do_transition(void)\n{\n  // On transitioning in, use the current screen.  Out, use the last screen.\n  enum Subscreen_Mode mode;\n  if(transition.state == TRANSITION_NONE)\n    return;\n  if(transition.state == TRANSITION_IN)\n    mode = subscreen_mode;\n  else                // TRANSITION_OUT\n    mode = last_subscreen_mode;\n\n  if(mode == SUBSCREEN_KEYBOARD)\n  {\n    if(transition.state == TRANSITION_IN)\n      nds_keyboard_scroll_in();\n    else\n      nds_keyboard_scroll_out();\n  }\n}\n\nstatic void nds_keyboard_scroll_in(void)\n{\n  if(!kb)\n    return;\n  if(transition.time == 0)\n  {\n    // Show the background.\n    kb->visible = 1;\n    bgSetScroll(kb->background, 0, -192);\n    bgUpdate();\n    bgShow(kb->background);\n  }\n  else\n  {\n    // Scroll 80 pixels.\n    if(transition.time < 80)\n    {\n      bgSetScroll(kb->background, 0, -192 + transition.time);\n    }\n    else\n    {\n      bgSetScroll(kb->background, 0, kb->offset_y);\n      transition.state = TRANSITION_NONE;\n    }\n    bgUpdate();\n  }\n\n  transition.time += kb->scrollSpeed;\n}\n\nstatic void nds_keyboard_scroll_out(void)\n{\n  if(!kb)\n    return;\n  if(transition.time == 0)\n    kb->visible = 0;\n\n  if(transition.time >= 80)\n  {\n    // Hide the background.\n    bgHide(kb->background);\n    transition.state = TRANSITION_NONE;\n  }\n  else\n  {\n    // Scroll 80 pixels.\n    bgSetScroll(kb->background, 0, kb->offset_y - transition.time);\n    bgUpdate();\n  }\n\n  transition.time += kb->scrollSpeed;\n}\n\nvoid nds_sleep_check(void)\n{\n  static boolean asleep = false;\n\n  // Check if we were just woken up.\n  if(asleep)\n  {\n    asleep = false;\n\n    // Wait a bit until returning power.\n    while(REG_VCOUNT != 0);\n    while(REG_VCOUNT == 0);\n    while(REG_VCOUNT != 0);\n    powerOn(POWER_ALL);\n  }\n\n  // Check if it's time to go to sleep.\n  if(keysDown() & KEY_LID)\n  {\n    // Cancel DMA from raster effects.\n#ifdef CONFIG_NDS_BLOCKSDS\n    dmaStopSafe(1);\n    dmaStopSafe(2);\n#else\n    DMA1_CR = 0;\n    DMA2_CR = 0;\n#endif\n\n    // Power everything off.\n    powerOff(POWER_ALL);\n    asleep = true;\n  }\n}\n\n#ifdef SCALED_USE_CELL_CACHE\nstatic u32 graph_cache[80*25];\nstatic bool graph_cache_invalid = false;\n\nstatic void nds_clear_graph_cache(void)\n{\n  graph_cache_invalid = true;\n}\n#else\n#define nds_clear_graph_cache() {}\n#endif\n\nstatic boolean nds_init_video(struct graphics_data *graphics,\n struct config_info *config)\n{\n  u32 i;\n\n  lcdMainOnBottom();\n  nds_clear_graph_cache();\n\n  // Build the palette blend mapping table.\n  palette_idx_table_init();\n\n  // Copy protected palette to BG_PALETTE.\n  for(i = 0; i < protected_palette_bin_size - 256; i += 2)\n    BG_PALETTE[PALETTE_PROTECTED_START + (i>>1)] = *((u16*) &protected_palette_bin[i + 256]);\n\n  // Start with scaled mode.\n  switch(config->video_ratio)\n  {\n    case RATIO_CLASSIC_4_3:\n    case RATIO_STRETCH:\n      subscreen_height = 192;\n      break;\n    default:\n      subscreen_height = 140;\n      break;\n  }\n  subscreen_offset_y = -((192 - subscreen_height) * 320 /* 1/2 * 256 * (1/0.4) */);\n\n  subscreen_mode = SUBSCREEN_SCALED;\n  last_subscreen_mode = SUBSCREEN_MODE_INVALID;\n  nds_subscreen_scaled_init();\n\n  // Initialize the 1:1 scaled \"main\" screen.\n  nds_mainscreen_init(graphics);\n\n  transition.state = TRANSITION_NONE;\n  transition.time = 0;\n\n  // Now that we're initialized, install the vblank handler.\n  irqSet(IRQ_VBLANK, nds_on_vblank);\n\n  graphics->bits_per_pixel = 8;\n  return true;\n}\n\nstatic boolean nds_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  return true;\t// stub\n}\n\n// Focus on a given screen position (in pixels up to 640x350).\nstatic void nds_mainscreen_focus(int x, int y)\n{\n  static int old_x = -1;\n  static int old_y = -1;\n  int scroll_x, scroll_y, i, ypos, ycounter;\n  u16 *sptr;\n\n  if(x == old_x && y == old_y)\n  {\n    // Already focused there, quick abort.\n    return;\n  }\n  old_x = x;\n  old_y = y;\n\n  // Clamp the coordinates so we stay within the screen.\n  if(x < 132) x = 132;\n  if(x > 508) x = 508;\n  if(y <  96) y =  96;\n  if(y > 253) y = 253;\n\n  // Move to the top-left corner.\n  x -= 264/2;\n  y -= 192/2;\n\n  // Find the relevant cell coordinates and scroll offsets into those cells.\n  cell_pan_x = x / 8;\n  scroll_x   = x % 8;\n  cell_pan_y = y / 14;\n  scroll_y   = y % 14;\n\n  mainscr_x_33rd = scroll_x > 0 ? 1 : 0;\n  mainscr_y_max = scroll_y >= 4 ? 15 : 14;\n\n  // Adjust the X scroll registers now.\n  REG_BG0HOFS_SUB = scroll_x;\n  REG_BG1HOFS_SUB = scroll_x;\n\n  // Recalculate the scroll table.\n  sptr     = scroll_table;\n  ypos     = scroll_y;\n  ycounter = scroll_y;\n  for(i = 0; i < 192; i++)\n  {\n    (*sptr++) = ypos;\n\n    ycounter++;\n    if(ycounter == 14)\n    {\n      ycounter = 0;\n      ypos += 2;\n    }\n  }\n}\n\n// Render the scaled screen.\n__attribute__((optimize(\"-O3\")))\nITCM_CODE\nstatic void nds_render_graph_scaled(struct graphics_data *graphics)\n{\n  int const WIDTH_IN_PIXELS  = 80 * 4;  // Screen width in pixels\n  int const WIDTH_IN_CELLS   = 80;      // Screen width in chars\n  int const CHARACTER_HEIGHT = 14;      // Character height in pixels\n  int const VRAM_WIDTH       = 512;     // HW surface width in pixels\n\n  struct char_element *text_cell = graphics->text_video;\n  u32 *vram_ptr  = (u32*)BG_BMP_RAM(5);\n  int chars, lines, columns;\n  int chr, bg, fg;\n  u8 *line;\n  u32 fourpx;\n\n  DTCM_BSS static u8 pal_tbl[4];\n\n#ifdef SCALED_USE_CELL_CACHE\n  if(graph_cache_invalid)\n  {\n    dmaFillWords(0xFFFFFFFF, graph_cache, 80*25*4);\n    graph_cache_invalid = false;\n  }\n#endif\n\n  /* Iterate over every character in text memory. */\n  columns = 0;\n  for(chars = 0; chars < 80*25; chars++)\n  {\n    u32* vram_next = vram_ptr + 1;\n#ifdef SCALED_USE_CELL_CACHE\n    u32 key = *((u32*) text_cell);\n\n    if(graph_cache[chars] != key)\n    {\n      graph_cache[chars] = key;\n      // Avoid reading from RAM to cache twice.\n      chr = key & 0x1FF;\n      bg  = (key >> 16) & 0x0F;\n      fg  = (key >> 24) & 0x0F;\n#else\n    {\n      chr = (*text_cell).char_value & 0x1FF;\n      bg  = (*text_cell).bg_color   & 0x0F;\n      fg  = (*text_cell).fg_color   & 0x0F;\n#endif\n\n      // Construct a table mapping charset two-bit pairs to palette entries.\n      if(chr & 0x100) // Protected palette\n      {\n        u8 bg_idx = bg + PALETTE_PROTECTED_START;\n        u8 fg_idx = fg + PALETTE_PROTECTED_START;\n        u8 mix_idx = palette_protected_idx_table[(bg << 4) | fg];\n        pal_tbl[0] = bg_idx;\n        pal_tbl[1] = mix_idx;\n        pal_tbl[2] = mix_idx;\n        pal_tbl[3] = fg_idx;\n      }\n      else // Regular palette\n      {\n        u8 bg_idx = bg + PALETTE_NORMAL_START;\n        u8 fg_idx = fg + PALETTE_NORMAL_START;\n        u8 mix_idx = palette_idx_table[(bg << 4) | fg];\n        pal_tbl[0] = bg_idx;\n        pal_tbl[1] = mix_idx;\n        pal_tbl[2] = mix_idx;\n        pal_tbl[3] = fg_idx;\n      }\n\n      /* Iterate over every line in the character. */\n      line = graphics->charset + CHARACTER_HEIGHT * chr;\n      #pragma GCC unroll CHARACTER_HEIGHT\n      for(lines = 0; lines < CHARACTER_HEIGHT; lines++)\n      {\n        /* Plot four pixels using the two-bit pair table. */\n        fourpx = pal_tbl[((*line) & 0xC0) >> 6]      ;\n        fourpx    |= pal_tbl[((*line) & 0x30) >> 4] <<  8;\n        fourpx    |= pal_tbl[((*line) & 0x0C) >> 2] << 16;\n        fourpx    |= pal_tbl[((*line) & 0x03)     ] << 24;\n        *((u32*)vram_ptr) = fourpx;\n\n        /* Advance to the next line. */\n        line++;\n        vram_ptr += VRAM_WIDTH/4;\n      }\n    }\n\n    /* Advance to the next character. */\n    text_cell++;\n    vram_ptr = vram_next;\n\n    /* Insert padding at the end of a line. */\n    columns++;\n    if(columns == WIDTH_IN_CELLS)\n    {\n      columns = 0;\n      vram_ptr += (VRAM_WIDTH - WIDTH_IN_PIXELS)/4;\n      vram_ptr += VRAM_WIDTH * 13 / 4;\n    }\n  }\n}\n\n__attribute__((optimize(\"-O3\")))\nstatic void nds_render_graph_1to1(struct graphics_data *graphics)\n{\n  struct char_element *text_start = graphics->text_video;\n  struct char_element *text_cell;\n  u16 *vram_bg, *vram_fg;\n  int x, y;\n\n  // Before drawing the screen, check if the focus needs to be updated!\n  if(focus_x > -1 && focus_y > -1)\n  {\n    nds_mainscreen_focus(focus_x, focus_y);\n    focus_x = -1;\n    focus_y = -1;\n  }\n\n  text_start += cell_pan_x + 80*cell_pan_y;\n  text_cell = text_start;\n\n  /* Plot the foreground and background tiles. */\n  vram_fg = (u16*)BG_MAP_RAM_SUB(0);\n  vram_bg = (u16*)BG_MAP_RAM_SUB(2);\n\n  for(y = 0; y < mainscr_y_max; y++)\n  {\n    // Draw the top halves of this line for tile_offset=0, then the bottom\n    // halves for tile_offset=1.\n    for(int tile_offset = 0; tile_offset < 2; tile_offset++)\n    {\n      int chr;\n      int bg;\n      int fg;\n\n      for(x = 0; x < 32; x++)\n      {\n        // Extract the cell data.\n        chr = (*text_cell).char_value & 0x1FF;\n        // Colors are not explicitly \"& 0x0F\" - this is done by the shift below.\n        bg  = (*text_cell).bg_color;\n        fg  = (*text_cell).fg_color;\n\n        *vram_fg = (2*chr + tile_offset) | (fg << 12);\n        *vram_bg = (bg << 12) | (chr >> 8) | 0x100;\n\n        text_cell++;\n        vram_bg++;\n        vram_fg++;\n      }\n\n      if(mainscr_x_33rd)\n      {\n        // Plot the 33rd column (in the next plane)\n        chr = (*text_cell).char_value & 0x1FF;\n        bg  = (*text_cell).bg_color;\n        fg  = (*text_cell).fg_color;\n        *(vram_fg+992) = (2*chr + tile_offset) | (fg << 12);\n        *(vram_bg+992) = (bg << 12) | (chr >> 8) | 0x100;\n      }\n\n      // Move back.\n      text_cell -= 32;\n    }\n\n    // Move to the next row.\n    text_cell += 80;\n  }\n}\n\nstatic void nds_render_graph(struct graphics_data *graphics)\n{\n  // Always render the scaled screen.\n  nds_render_graph_scaled(graphics);\n\n  // Always render the 1:1 \"main\" screen.\n  nds_render_graph_1to1(graphics);\n}\n\nstatic void nds_update_palette_entry(struct rgb_color *palette, unsigned int idx)\n{\n  struct rgb_color color1 = palette[idx];\n  int idx2;\n\n  if(idx < 16)\n  {\n    // Update the mainscreen palette.\n    BG_PALETTE_SUB[16*idx + 1] = RGB15(color1.r/8, color1.g/8, color1.b/8);\n\n    // Update the subscreen palette.\n    /* Iterate over an entire column of the palette, blending this color\n     * with all 16 other colors (including itself). */\n    for(idx2 = 0; idx2 < 16; idx2++)\n    {\n      /* Average the colors while reducing their accuracy to 5 bits. */\n      struct rgb_color color2 = palette[idx2];\n      u16 r = (color1.r + color2.r) / 16;\n      u16 g = (color1.g + color2.g) / 16;\n      u16 b = (color1.b + color2.b) / 16;\n\n      BG_PALETTE[ palette_idx_table[(idx << 4) | idx2] ] = RGB15(r, g, b);\n    }\n  }\n  else\n  {\n    // Update the mainscreen protected palette.\n    BG_PALETTE_SUB[16*(idx & 0x0F) + 2] = RGB15(color1.r/8, color1.g/8, color1.b/8);\n  }\n}\n\nstatic void nds_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned int count)\n{\n  unsigned int i;\n\n  for(i = 0; i < count; i++)\n    nds_update_palette_entry(palette, i);\n}\n\nstatic void nds_render_cursor(struct graphics_data *graphics, unsigned int x,\n unsigned int y, uint16_t color, unsigned int lines, unsigned int offset)\n{\n  // stub\n}\n\nstatic void nds_render_mouse(struct graphics_data *graphics,\n unsigned int x, unsigned int y, unsigned int w, unsigned int h)\n{\n  // stub\n}\n\nstatic void nds_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  // stub\n}\n\nstatic void nds_remap_char(struct graphics_data *graphics, uint16_t chr)\n{\n  if(chr < 512)\n  {\n    /* Each character is 64 bytes.  Advance the vram pointer. */\n    u16* vram = (u16*)BG_TILE_RAM_SUB(1) + 32*chr;\n    u8* charset = graphics->charset + 14*chr;\n    u8 cshift = chr >= 256 ? 1 : 0;\n\n    /* Iterate over every line. */\n    u8 *end = charset + 14;\n    for(; charset < end; charset++)\n    {\n      u16 line = *charset;\n\n      /* Plot 8 pixels, 4 pixels at a time. */\n      *(vram++) = (((line >> 7) & 1)      |\n                  ((line >> 6) & 1) <<  4 |\n                  ((line >> 5) & 1) <<  8 |\n                  ((line >> 4) & 1) << 12) << cshift;\n      *(vram++) = (((line >> 3) & 1)      |\n                  ((line >> 2) & 1) <<  4 |\n                  ((line >> 1) & 1) <<  8 |\n                  ((line     ) & 1) << 12) << cshift;\n    }\n\n    nds_clear_graph_cache();\n  }\n}\n\nstatic void nds_remap_charbyte(struct graphics_data *graphics, uint16_t chr,\n uint8_t byte)\n{\n  if(chr < 512)\n  {\n    /* Each character is 64 bytes.  Advance the vram pointer. */\n    u16* vram = (u16*)BG_TILE_RAM_SUB(1) + 32*chr + 2*byte;\n    u8* charset = graphics->charset + 14*chr + byte;\n    u16 line = *charset;\n    u8 cshift = chr >= 256 ? 1 : 0;\n\n    /* Plot 8 pixels, 4 pixels at a time. */\n    *(vram++) = (((line >> 7) & 1)      |\n                ((line >> 6) & 1) <<  4 |\n                ((line >> 5) & 1) <<  8 |\n                ((line >> 4) & 1) << 12) << cshift;\n    *(vram++) = (((line >> 3) & 1)      |\n                ((line >> 2) & 1) <<  4 |\n                ((line >> 1) & 1) <<  8 |\n                ((line     ) & 1) << 12) << cshift;\n\n    nds_clear_graph_cache();\n  }\n}\n\nstatic void nds_remap_char_range(struct graphics_data *graphics, uint16_t first,\n uint16_t count)\n{\n  int stop = first + count;\n  int chr;\n\n  if(stop > 512)\n    stop = 512;\n\n  for(chr = first; chr < stop; chr++)\n    nds_remap_char(graphics, chr);\n}\n\nstatic void nds_focus_pixel(struct graphics_data *graphics,\n unsigned int x, unsigned int y)\n{\n  switch(get_allow_focus_changes())\n  {\n    case FOCUS_FORBID:\n      return;\n    case FOCUS_ALLOW:\n      if(last_focus_x != (int)x || last_focus_y != (int)y)\n      {\n        last_focus_x = x;\n        last_focus_y = y;\n        focus_x = x;\n        focus_y = y;\n      }\n      break;\n    case FOCUS_PASS:\n      focus_x = x;\n      focus_y = y;\n      break;\n  }\n}\n\nvoid render_nds_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = nds_init_video;\n  renderer->create_window = nds_create_window;\n  renderer->update_colors = nds_update_colors;\n  renderer->set_viewport = set_window_viewport_centered;\n  renderer->remap_char_range = nds_remap_char_range;\n  renderer->remap_char = nds_remap_char;\n  renderer->remap_charbyte = nds_remap_charbyte;\n  renderer->render_graph = nds_render_graph;\n  renderer->render_cursor = nds_render_cursor;\n  renderer->render_mouse = nds_render_mouse;\n  renderer->sync_screen = nds_sync_screen;\n  renderer->focus_pixel = nds_focus_pixel;\n}\n\nboolean nds_subscreen_preview(void)\n{\n  // Don't switch during a transition.\n  if(transition.state != TRANSITION_NONE)\n    return false;\n\n  // Call appropriate exit function.\n  if(subscreen_mode != SUBSCREEN_SCALED)\n  {\n    last_subscreen_mode = subscreen_mode;\n    subscreen_mode = SUBSCREEN_SCALED;\n    nds_subscreen_keyboard_exit();\n  }\n  return true;\n}\n\nboolean nds_subscreen_keyboard(void)\n{\n  // Don't switch during a transition.\n  if(transition.state != TRANSITION_NONE)\n    return false;\n\n  if(subscreen_mode != SUBSCREEN_KEYBOARD)\n  {\n    last_subscreen_mode = subscreen_mode;\n    subscreen_mode = SUBSCREEN_KEYBOARD;\n    nds_subscreen_keyboard_init();\n  }\n  return true;\n}\n"
  },
  {
    "path": "arch/nds/render.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Kevin Vance <kvance@kvance.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __RENDER_NDS_H\n#define __RENDER_NDS_H\n\n#include \"../../src/compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../../src/graphics.h\"\n\n// The subscreen can display different information.\nenum Subscreen_Mode\n{\n  SUBSCREEN_SCALED = 0,\n  SUBSCREEN_KEYBOARD,\n  SUBSCREEN_MODE_COUNT,\n  SUBSCREEN_MODE_INVALID\n};\n\nextern enum Subscreen_Mode subscreen_mode;\n\nboolean is_scaled_mode(enum Subscreen_Mode mode);\n\n// Call these 4 functions every vblank.\nvoid nds_sleep_check(void);\nvoid nds_video_jitter(void);\nvoid nds_video_rasterhack(void);\nvoid nds_video_do_transition(void);\n\n// Toggle to the next subscreen mode.\nboolean nds_subscreen_preview(void);\nboolean nds_subscreen_keyboard(void);\n\n__M_END_DECLS\n\n#endif // __RENDER_NDS_H\n"
  },
  {
    "path": "arch/nds-blocksds/CONFIG.NDS",
    "content": "#!/bin/sh\n\n[ -z \"$BLOCKSDS\" ] && { echo \"\\$BLOCKSDS is unset, aborting\"; exit 1; }\n[ -z \"$WONDERFUL_TOOLCHAIN\" ] && { echo \"\\$WONDERFUL_TOOLCHAIN is unset, aborting\"; exit 1; }\n\n./config.sh --platform nds-blocksds --prefix \"$BLOCKSDS\" --optimize-size --enable-lto \\\n            --disable-editor --disable-helpsys --disable-utils \\\n            --disable-libpng --enable-release --enable-meter \\\n            --enable-extram --disable-screenshots --enable-stdio-redirect \\\n            --disable-stack-protector \"$@\"\n"
  },
  {
    "path": "arch/nds-blocksds/Makefile.in",
    "content": "#\n# Nintendo DS Makefile\n#\n\n.PHONY: package clean\n\nifeq ($(strip $(BLOCKSDS)),)\n$(error \"BLOCKSDS must be set in your environment.\")\nendif\n\nifeq ($(strip $(WONDERFUL_TOOLCHAIN)),)\n$(error \"WONDERFUL_TOOLCHAIN must be set in your environment.\")\nendif\n\nEXTRA_LICENSES += ${LICENSE_CC0} ${LICENSE_MPL2} ${LICENSE_NEWLIB}\n\n#\n# NDS target rules\n#\nARM_NONE_EABI_PATH\t?= $(WONDERFUL_TOOLCHAIN)/toolchain/gcc-arm-none-eabi/bin/\nCROSS_COMPILE := ${ARM_NONE_EABI_PATH}arm-none-eabi-\n\n# Don't strip when generating debug symbols.\nNO_STRIP_IN_DEBUGLINK ?= 1\n# Disable rules that require target code to run natively.\nSUPPRESS_HOST_TARGETS ?= 1\n\n#\n# Override library paths.\n#\n\nEXTRA_LIBS_DIRS\t:= $(BLOCKSDS)/libs/maxmod \\\n\t\t   $(BLOCKSDS)/libs/libnds\nEXTRA_LIBS_NAMES := -lmm9 -lnds9 -lc -lgcc\n\nEXTRA_INCLUDES := $(foreach path,$(EXTRA_LIBS_DIRS),-I$(path)/include)\nEXTRA_LIBS := $(foreach path,$(EXTRA_LIBS_DIRS),-L$(path)/lib) \\\n              -Wl,--start-group $(EXTRA_LIBS_NAMES) -Wl,--end-group\n\nNDS_CFLAGS    += -marm -mcpu=arm946e-s+nofp\nNDS_CFLAGS    += ${EXTRA_INCLUDES} -Iarch/nds -ffunction-sections -fdata-sections\n\nNDS_LDFLAGS   += -marm -mcpu=arm946e-s+nofp\nNDS_LDFLAGS   += ${EXTRA_LIBS}\n\nNDS_CFLAGS    += -DPICOLIBC_LONG_LONG_PRINTF_SCANF -specs=$(BLOCKSDS)/sys/crts/ds_arm9.specs\nNDS_LDFLAGS   += -DPICOLIBC_LONG_LONG_PRINTF_SCANF -specs=$(BLOCKSDS)/sys/crts/ds_arm9.specs\n\nARCH_CFLAGS   += ${NDS_CFLAGS}\nARCH_CXXFLAGS += ${NDS_CFLAGS}\nARCH_LDFLAGS  += ${NDS_LDFLAGS}\n\n# BlocksDS does not support building with pre-C11 standards for its header files.\n# Use gnu17, as that's the standard currently used by the official template files.\nARCH_CFLAGS   += -std=gnu17\n\nARM7_BINARY := arch/nds-blocksds/megazeux.arm7.elf\n\n#\n# Vile hack, remove me ASAP\n#\narch/nds/%.o: arch/nds/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} -Wno-unused-macros -c $< -o $@\n\narch/nds/render.o: arch/nds/protected_palette.bin.o\n\narch/nds/%.bin.o arch/nds/%_bin.h : arch/nds/%.bin\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t@$(BLOCKSDS)/tools/bin2c/bin2c $< $(@D)\n\t@$(CC) $(CFLAGS) -MMD -MP -c -o arch/nds/$*.bin.o arch/nds/$*_bin.c\n\npackage: mzx $(ARM7_BINARY)\n\t$(BLOCKSDS)/tools/ndstool/ndstool -c ${mzxrun}.nds -7 $(ARM7_BINARY) -9 ${mzxrun} -b contrib/icons/generated/icon_nds.png \"MegaZeux ${VERSION} (B)\"\n\n#\n# Build the ARM7 binary.\n#\nARM7_SOURCE_FILES := $(shell find -L arch/nds/arm7/source -name \"*.c\")\nARM7_DEPEND_FILES := $(addsuffix .d,$(ARM7_SOURCE_FILES))\nARM7_OBJECT_FILES := $(addsuffix .o,$(ARM7_SOURCE_FILES))\n\nARM7_LIBS_DIRS\t:= $(BLOCKSDS)/libs/maxmod \\\n\t\t   $(BLOCKSDS)/libs/libnds\nARM7_LIBS_NAMES := -lmm7 -lnds7 -lc -lgcc\n\nARM7_INCLUDES := $(foreach path,$(ARM7_LIBS_DIRS),-I$(path)/include)\nARM7_LIBS := $(foreach path,$(ARM7_LIBS_DIRS),-L$(path)/lib) \\\n              -Wl,--start-group $(ARM7_LIBS_NAMES) -Wl,--end-group\n\n$(ARM7_BINARY): $(ARM7_OBJECT_FILES)\n\t$(if ${V},,@echo \"  LD.7    \" $@)\n\t${CC} -o $@ $< -mthumb -specs=$(BLOCKSDS)/sys/crts/ds_arm7.specs \\\n\t\t-Wl,--start-group $(ARM7_LIBS) -Wl,--end-group\n\narch/nds/arm7/source/%.c.o: arch/nds/arm7/source/%.c\n\t$(if ${V},,@echo \"  CC.7    \" $<)\n\t${CC} -MD -Os -std=gnu17 -specs=$(BLOCKSDS)/sys/crts/ds_arm7.specs \\\n\t\t-Wall -Wno-unused-macros -mcpu=arm7tdmi \\\n\t\t-mthumb $(ARM7_INCLUDES) -DCONFIG_BLOCKSDS \\\n\t\t-ffunction-sections -fdata-sections -fomit-frame-pointer \\\n\t\t-c $< -o $@\n#\n# Misc. helpers.\n#\nclean:\n\t${RM} -f ${mzxrun}.nds arch/nds/*.d arch/nds/*.o arch/nds/*.elf\n\t${RM} -f ${ARM7_DEPEND_FILES} ${ARM7_OBJECT_FILES} ${ARM7_BINARY}\n\t${RM} -f arch/nds/protected_palette.bin.S arch/nds/protected_palette_bin.h\n\t${RM} -f arch/nds/protected_palette_bin.c\n\n#\n# We're only interested in our packaged binary; remove the ELF intermediaries\n#\nbuild := ${build_root}/games/megazeux\nbuild: package ${build}\n\t${CP} arch/nds/pad.config ${build}\n\t${CP} ${mzxrun}.nds ${build}\n\t${RM} ${build}/${mzxrun} ${build}/${mzxrun}.debug\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/none/Makefile.in",
    "content": "# placebo makefile\n\nSUPPRESS_ALL_TARGETS=1\n\nall:\n\t@echo \"Please run config.sh before make!\"\n\t@echo\n\t@echo \"Type ./config.sh for more information.\"\n\ndistclean:\n\nutils:\n"
  },
  {
    "path": "arch/pandora/Makefile.in",
    "content": "TOOLCHAIN       ?= /usr/local/pandora/arm-2009q3\nCROSS_COMPILE   ?= $(TOOLCHAIN)/bin/arm-none-linux-gnueabi-\nPANDORA_LIBPATH ?= $(PREFIX)/lib\nPANDORA_INCPATH ?= $(PREFIX)/include\n\nPANDORA_CFLAGS  = -march=armv7-a -mtune=cortex-a8 -fPIC -I$(PANDORA_INCPATH)\n\nARCH_CFLAGS     += ${PANDORA_CFLAGS}\nARCH_CXXFLAGS   += ${PANDORA_CFLAGS}\nARCH_LDFLAGS    += -L$(PANDORA_LIBPATH)\n\nLIBPNG_CFLAGS   = -I$(PANDORA_INCPATH)/libpng12\nLIBPNG_LDFLAGS  = -lpng12 -lz\n\nifeq (${BUILD_SDL},1)\nSDL_CFLAGS      = $(shell $(TOOLCHAIN)/usr/bin/sdl-config --cflags)\nSDL_LDFLAGS     = $(shell $(TOOLCHAIN)/usr/bin/sdl-config --libs)\nendif\nifeq (${BUILD_SDL},2)\nSDL_CFLAGS      = $(shell $(TOOLCHAIN)/usr/bin/sdl2-config --cflags)\nSDL_LDFLAGS     = $(shell $(TOOLCHAIN)/usr/bin/sdl2-config --libs)\nendif\n\nDSOLDFLAGS      = -shared\nDSOPRE          = lib\nDSOPOST         = .so\nDSORPATH        = -Wl,-rpath,$(LIBDIR)\nDSOSONAME       = -Wl,-soname,\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/pandora/README",
    "content": "WRITE ME\n\n./config.sh --platform pandora --prefix /usr/local/pandora/arm-2009q3/usr \\\n            --disable-gl --disable-modular --enable-tremor --enable-release\n"
  },
  {
    "path": "arch/psp/CONFIG.PSP",
    "content": "#!/bin/sh\n\n[ -z $PSPDEV ] && { echo \"\\$PSPDEV is unset. Aborting\"; exit 1; }\n\n./config.sh --platform psp --prefix $PSPDEV/psp --optimize-size \\\n            --disable-editor --disable-helpsys --disable-utils \\\n            --enable-sdl1 --enable-release --enable-meter \\\n            --enable-extram --enable-tremor-lowmem --enable-stdio-redirect \"$@\"\n"
  },
  {
    "path": "arch/psp/Makefile.in",
    "content": "#\n# psp makefile generics\n#\n\n.PHONY: package-pbp\n\nEXTRA_LICENSES += ${LICENSE_NEWLIB}\n\n#\n# PSP toolchain overrides\n#\nCROSS_COMPILE = psp-\n\n# Don't strip when generating debug symbols.\nNO_STRIP_IN_DEBUGLINK ?= 1\n# Disable rules that require target code to run natively.\nSUPPRESS_HOST_TARGETS ?= 1\n\n# flag PSP build and link in c/psppower\nPSP_CFLAGS    += -G0 -isystem ${PREFIX}/sdk/include -DPATH_MAX=4096\n\nARCH_CFLAGS   += ${PSP_CFLAGS}\nARCH_CXXFLAGS += ${PSP_CFLAGS}\nARCH_LDFLAGS  += -L${PREFIX}/sdk/lib\n\nifeq (${BUILD_SDL},3)\nSDL_CFLAGS = -I${PREFIX}/include/SDL3\nSDL_LDFLAGS = -lSDL3 -lpspvram\nendif\nifeq (${BUILD_SDL},2)\nSDL_CFLAGS = -I${PREFIX}/include/SDL2\nSDL_LDFLAGS = -lSDL2 -lSDL2main -lpspvram\nendif\nifeq (${BUILD_SDL},1)\nSDL_CFLAGS = -I${PREFIX}/include/SDL\nSDL_LDFLAGS = -lSDL\nendif\n\n# HACK: Append the rest of the dependencies to SDL_LDFLAGS for now.\n# psp-g++ seems to either remove the first -lc it finds or repositions it to\n# after the rest of the includes. Specify it twice so psp-g++ really knows it\n# needs to go there. Otherwise, linking fails. This issue was encountered with\n# the devkitPSP r16 build of PSPDEV.\nSDL_LDFLAGS += -lGL -lpspirkeyb -lm -lc -lc -lpspvfpu -lpsprtc -lpsppower -lpspaudio \\\n  -lpspgu -lpspge -lpspdisplay -lpsphprm -lpspctrl -lpspnet_inet -lpspnet_resolver -lpspuser\nLIBPNG_CFLAGS =\nLIBPNG_LDFLAGS = -lpng\n\npackage: mzx\n\tpsp-fixup-imports ${mzxrun}\n\tmksfo 'MegaZeux '${VERSION} PARAM.SFO\n\t${STRIP} ${mzxrun} -o ${mzxrun}.strip\n\tcp contrib/icons/generated/icon_psp.png ICON0.PNG\n\tpack-pbp EBOOT.PBP PARAM.SFO ICON0.PNG NULL \\\n\t\t NULL NULL NULL ${mzxrun}.strip NULL\n\trm -f ${mzxrun}.strip\n\nclean:\n\t${RM} -f EBOOT.PBP PARAM.SFO ICON0.PNG arch/psp/*.o arch/psp/*.d\n\nbuild: package ${build}\n\t${CP} arch/psp/pad.config ${build}\n\t${CP} EBOOT.PBP ${build}\n\t${RM} ${build}/${mzxrun} ${build}/${mzxrun}.debug\n\n#\n# Vile hack, remove me ASAP\n#\narch/psp/%.o: arch/psp/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} -c $< -o $@\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/psp/README",
    "content": "BUILDING MEGAZEUX FOR PSP\n\nAs of 2.81d, MegaZeux can be built for PSP. The only supported toolchain for\nthis is pspdev.\n\ndevkitPSP, a build of pspdev previously distributed by devkitPro, is no longer\nsupported by devkitPro.\n\nPREPARING TO BUILD\n\nBefore starting, the $PSPDEV variable needs to be defined correctly and the\npath to the PSP toolchain needs to be added to $PATH variable. Both of these\nsteps are required for the following instructions to work. Example (add this\nto your .profile or .bashrc file):\n\nexport PSPDEV=/opt/pspdev # Note: This var is equivalent to $DEVKITPSP.\nexport PATH=$PSPDEV/bin:$PATH\n\nBUILDING DEPENDENCIES\n\nInstall the following library collection:\n\ngit clone https://github.com/pspdev/psplibraries.git\ncd psplibraries\n./libraries.sh\n\nYou might need to run the script twice, as it doesn't exactly do installation ordering correctly.\n\nBUILDING MEGAZEUX FROM SOURCES\n\nExtract the latest version of MegaZeux, or obtain it from Git.\n\ncd megazeux\narch/psp/CONFIG.PSP\nmake package\n\nThis will create an EBOOT.PBP.\n\nPACKAGING THE BUILD\n\nmake archive\n\nTo construct a bootable zip archive.\n\n--ajs\n"
  },
  {
    "path": "arch/psp/pad.config",
    "content": "# 1 Triangle\r\n# 2 Circle\r\n# 3 Cross\r\n# 4 Square\r\n# 5 Left shoulder\r\n# 6 Right shoulder\r\n# 7 Down\r\n# 8 Left\r\n# 9 Up\r\n# 10 Right\r\n# 11 Select\r\n# 12 Start\r\n# 13 Home\r\n# 14 Hold\r\n\r\njoy1axis1 = act_l_left, act_l_right\r\njoy1axis2 = act_l_up, act_l_down\r\njoy1.axis_lx = 1\r\njoy1.axis_ly = 2\r\n\r\njoy1button1 = act_x\r\njoy1button2 = act_a\r\njoy1button3 = act_b\r\njoy1button4 = act_y\r\njoy1button5 = act_lshoulder\r\njoy1button6 = act_rshoulder\r\njoy1button7 = act_down\r\njoy1button8 = act_left\r\njoy1button9 = act_up\r\njoy1button10 = act_right\r\njoy1button11 = act_select\r\njoy1button12 = act_start\r\n"
  },
  {
    "path": "arch/psp/platform.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007-2009 Kevin Vance <kvance@kvance.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdio.h>\n\n#include <pspsdk.h>\n#include <pspkernel.h>\n#include <pspdebug.h>\n#include <pspctrl.h>\n\n#include \"../../src/config.h\"\n\n#if CONFIG_SDL < 2\nPSP_MODULE_INFO(\"MegaZeux\", 0, 1, 1);\nPSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU);\n#endif\nPSP_MAIN_THREAD_STACK_SIZE_KB(512);\n"
  },
  {
    "path": "arch/psvita/CONFIG.PSVITA",
    "content": "#!/bin/sh\n\n[ -z \"$VITASDK\" ] && { echo \"\\$VITASDK is unset. Aborting\"; exit 1; }\n\n./config.sh --platform psvita --prefix \"$VITASDK\" --enable-release --enable-lto \\\n            --enable-sdl3 --disable-utils --enable-stdio-redirect \\\n            --enable-meter --disable-gl \"$@\"\n"
  },
  {
    "path": "arch/psvita/Makefile.in",
    "content": ".PHONY: clean package build\n\nBINEXT = .elf\nCROSS_COMPILE = arm-vita-eabi-\n\n# Disable rules that require target code to run natively.\nSUPPRESS_HOST_TARGETS ?= 1\n\nVITA_TITLEID = VMZX00001\nVITA_TITLE = MegaZeux\n\nVITA_MKSFOEX_FLAGS = -d PARENTAL_LEVEL=1\n\nVITA_PORTLIBS = \"${PREFIX}\"/arm-vita-eabi\n\nVITA_LIBS = -lSceDisplay_stub \\\n            -lSceCtrl_stub \\\n            -lSceAudio_stub \\\n            -lSceAudioIn_stub \\\n            -lSceMotion_stub \\\n            -lSceSysmodule_stub \\\n            -lSceGxm_stub \\\n            -lSceCommonDialog_stub \\\n            -lSceTouch_stub \\\n            -lSceHid_stub \\\n            -lSceCamera_stub \\\n            -lc\n\nVITA_CFLAGS = -DPATH_MAX=4096 -DOV_EXCLUDE_STATIC_CALLBACKS\nVITA_LDFLAGS = -L${VITA_PORTLIBS}/lib -Wl,-q -Wl,-z,nocopyreloc\n\nARCH_CFLAGS += ${VITA_CFLAGS} ${VITA_INCLUDES}\nARCH_CXXFLAGS += ${VITA_CFLAGS} ${VITA_INCLUDES}\nARCH_LDFLAGS += ${VITA_LDFLAGS}\nARCH_LIBS += ${VITA_LIBS}\n\n# VitaSDK config and pkg-config scripts hardcode /usr/local, override them\nSDL_PREFIX = ${VITA_PORTLIBS}\nifeq (${BUILD_SDL},3)\nSDL_CFLAGS = -isystem ${VITA_PORTLIBS}/include/SDL3\nSDL_LDFLAGS = -L${VITA_PORTLIBS}/lib -lSDL3\nendif\nifeq (${BUILD_SDL},2)\nSDL_CFLAGS = -isystem ${VITA_PORTLIBS}/include/SDL2\nSDL_LDFLAGS = -L${VITA_PORTLIBS}/lib -lSDL2\nendif\nifeq (${BUILD_SDL},1)\nSDL_CFLAGS = -isystem ${VITA_PORTLIBS}/include/SDL\nSDL_LDFLAGS = -L${VITA_PORTLIBS}/lib -lSDL\nendif\n\nLIBPNG_CFLAGS = -isystem ${VITA_PORTLIBS}/include/libpng16\nLIBPNG_LDFLAGS = -L${VITA_PORTLIBS}/lib -lpng16\n\nSCE_SYS = \"arch/psvita/sce_sys\"\n\npackage: mzx\nifneq ($(BUILD_EDITOR),)\n\tvita-elf-create ${mzx} megazeux.velf\n\tvita-make-fself -s -c megazeux.velf eboot.bin\n\tvita-mksfoex ${VITA_MKSFOEX_FLAGS} -s TITLE_ID=\"${VITA_TITLEID}\" \\\n\t\t\"${VITA_TITLE}\" param.sfo\n\tvita-pack-vpk -s param.sfo -b eboot.bin \\\n\t\t--add ${SCE_SYS}/icon0.png=sce_sys/icon0.png \\\n\t\t--add ${SCE_SYS}/livearea/contents/bg.png=sce_sys/livearea/contents/bg.png \\\n\t\t--add ${SCE_SYS}/livearea/contents/startup.png=sce_sys/livearea/contents/startup.png \\\n\t\t--add ${SCE_SYS}/livearea/contents/template.xml=sce_sys/livearea/contents/template.xml \\\n\t\tmegazeux.vpk\nendif\n\tvita-elf-create ${mzxrun} mzxrun.velf\n\tvita-make-fself -s -c mzxrun.velf eboot.bin\n\tvita-mksfoex ${VITA_MKSFOEX_FLAGS} -s TITLE_ID=\"${VITA_TITLEID}\" \\\n\t\t\"${VITA_TITLE}\" param.sfo\n\tvita-pack-vpk -s param.sfo -b eboot.bin \\\n\t\t--add ${SCE_SYS}/icon0.png=sce_sys/icon0.png \\\n\t\t--add ${SCE_SYS}/livearea/contents/bg.png=sce_sys/livearea/contents/bg.png \\\n\t\t--add ${SCE_SYS}/livearea/contents/startup.png=sce_sys/livearea/contents/startup.png \\\n\t\t--add ${SCE_SYS}/livearea/contents/template.xml=sce_sys/livearea/contents/template.xml \\\n\t\tmzxrun.vpk\n\nclean:\n\t${RM} -f arch/psvita/*.o arch/psvita/*.d eboot.bin param.sfo\n\t${RM} -f mzxrun.velf mzxrun.vpk\n\t${RM} -f megazeux.velf megazeux.vpk\n\nbuild: package ${build}\n\t${CP} megazeux.vpk ${build}\n\t${CP} mzxrun.vpk ${build}\n\t${CP} arch/psvita/README.md ${build}/VITA-README.txt\nifeq (${BUILD_SDL},1)\n\t${CP} arch/psvita/pad.config.sdl12 ${build}/pad.config\nendif\n\t${RM} ${build}/${mzxrun} \\\n\t      ${build}/${mzxrun}.debug \\\n\t      ${build}/${mzx} \\\n\t      ${build}/${mzx}.debug\n\n#\n# Vile hack, remove me ASAP\n#\narch/psvita/%.o: arch/psvita/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} -c $< -o $@\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/psvita/README.md",
    "content": "# MegaZeux for the PlayStation Vita\n\nMegaZeux is now fully supported on the PS Vita handheld. The editor is fully\nsupported - the world can be edited using a Bluetooth keyboard.\n\nAs of this writing, MegaZeux for Vita has been successfully built with Gentoo\nLinux, Kubuntu 19.10, and macOS 10.15 Catalina and has been tested on a\nsoftmodded PSVita PCH-1000 running the 3.71 firmware.\n\n## Building\n\n### Prerequisites\n\n[Vita SDK](https://vitasdk.org/) is required to build MegaZeux. Follow the\ninstructions on their web site to install the SDK. No additional packages need\nto be installed in order to build MegaZeux.\n\nYou will need a modified PS Vita or PSTV console in order to use MegaZeux.\nModifying your console to run homebrew is outside the scope of this guide.\n\n### Build Instructions\n\nBefore building MegaZeux, please ensure that the `VITASDK` environment variable\nis set and that the SDK has been added to your system path.\n\nAfter cloning the MegaZeux repository, change into its directory and type the\nfollowing in your terminal emulator of choice:\n\n`sh arch/psvita/CONFIG.PSVITA`\n\nThis will invoke the `config.sh` script with the default settings for this\nplatform. Now, type `make` and press ENTER. If you wish to run a parallel make\noperation, use `make -jX`, replacing X with the number of threads that you wish\nto use. The number of hardware threads available for your CPU is a good starting\npoint.\n\nIf the build completes successfully, type `make package` to build an installable\nVPK package.\n\n### Installation\n\nMove the generated `mzxrun.vpk` or `megazeux.vpk` package to your console and\nuse VitaShell or a similar application to install it to your LiveArea. If you\nonly plan to play games, install `mzxrun.vpk`. If you plan to use the editor,\ninstall `megazeux.vpk`. Please note that a Bluetooth keyboard will be required\nin order to use the Robotic editor.\n\nMegaZeux expects all games and assets to be stored in `ux0:/data/megazeux`\n(case-sensitive for Linux Vita3K users). Create this directory on your device,\nthen transfer the included files `config.txt`, `LICENSE`, `LICENSE.3rd`, the\n`assets` directory, and any games that you wish to run into that directory.\n\nAfter this is done, MegaZeux should be fully playable. Have fun!\n"
  },
  {
    "path": "arch/psvita/pad.config.sdl12",
    "content": "# Axis 1/2: left stick\r\n# Axis 3/4: right stick\r\n# Button1: Triangle\r\n# Button2: Circle\r\n# Button3: Cross\r\n# Button4: Square\r\n# Button5: L\r\n# Button6: R\r\n# Button7: Down\r\n# Button8: Left\r\n# Button9: Up\r\n# Button10: Right\r\n# Button11: Select\r\n# Button12: Start\r\n\r\njoy1axis1 = act_l_left, act_l_right\r\njoy1axis2 = act_l_up, act_l_down\r\njoy1axis3 = act_r_left, act_r_right\r\njoy1axis4 = acr_r_up, act_r_down\r\njoy1button1 = act_y\r\njoy1button2 = act_b\r\njoy1button3 = act_a\r\njoy1button4 = act_x\r\njoy1button5 = act_lshoulder\r\njoy1button6 = act_rshoulder\r\njoy1button7 = act_down\r\njoy1button8 = act_left\r\njoy1button9 = act_up\r\njoy1button10 = act_right\r\njoy1button11 = act_select\r\njoy1button12 = act_start\r\njoy1.axis_lx = 1\r\njoy1.axis_ly = 2\r\njoy1.axis_rx = 3\r\njoy1.axis_ry = 4\r\n"
  },
  {
    "path": "arch/psvita/sce_sys/livearea/contents/template.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<livearea style=\"a2\" format-ver=\"01.00\" content-rev=\"1\">\n\t<livearea-background>\n\t\t<image>bg.png</image>\n\t</livearea-background>\n\t\n\t<gate>\n\t\t<startup-image>startup.png</startup-image>\n\t</gate>\n</livearea>\n"
  },
  {
    "path": "arch/switch/CONFIG.SWITCH",
    "content": "#!/bin/sh\n\n# NOTE: GL renderers currently cause system crashes on exit and are disabled\n# by default. They don't perform as well as the softscale renderer on the\n# Switch anyway.\n\n./config.sh --platform switch --prefix $DEVKITPRO/devkitA64 --enable-release --enable-lto \\\n            --disable-utils --enable-meter --enable-stdio-redirect \\\n            --disable-gl $@\n"
  },
  {
    "path": "arch/switch/Makefile.in",
    "content": "#\n# Nintendo Switch Makefile\n#\n\n.PHONY: package clean\n\nifeq ($(strip $(DEVKITPRO)),)\n$(error \"DEVKITPRO must be set in your environment.\")\nendif\n\nEXTRA_LICENSES += ${LICENSE_MPL2} ${LICENSE_NEWLIB}\n\n# NOTE: there is no $DEVKITA64. Don't use it.\n\n#\n# Switch target rules\n#\n\ninclude $(DEVKITPRO)/libnx/switch_rules\n\nBINEXT     := .elf\n\nAPP_TITLE  := MegaZeux\nAPP_AUTHOR := \\\"MegaZeux Developers\\\"\nAPP_ICON   := contrib/icons/generated/icon_switch.jpg\n\n# Block --host, which will break things.\nCROSS_COMPILE =\n# Don't strip when generating debug symbols.\nNO_STRIP_IN_DEBUGLINK ?= 1\n# Disable rules that require target code to run natively.\nSUPPRESS_HOST_TARGETS ?= 1\n\n#\n# Override library paths.\n#\n\nMACHDEP    := -D__SWITCH__ -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE\n\nifeq (${DEBUG},1)\n#DRM_NOUVEAU := drm_nouveaud\nNX := nxd\nelse\nOPTIMIZE_FLAGS += -ffunction-sections\n#DRM_NOUVEAU := drm_nouveau\nNX := nx\nendif\n\nMESA_LIBS :=\n\nifneq ($(or ${BUILD_RENDER_SOFTSCALE}, ${BUILD_RENDER_GL_PROGRAM}),)\nMESA_LIBS += -lGLESv2\nendif\n\nifneq (${BUILD_RENDER_GL_FIXED},)\nMESA_LIBS += -lGLESv1_CM\nendif\n\nifneq (${MESA_LIBS},)\nMESA_LIBS += -lEGL -lglapi -ldrm_nouveau\nendif\n\nSDL_PREFIX := ${PORTLIBS}\nLIBPNG_PREFIX := ${PORTLIBS}\n\nEXTRA_INCLUDES := -isystem ${LIBNX}/include -isystem ${PORTLIBS}/include\nEXTRA_LIBS := -L${LIBNX}/lib -L${PORTLIBS}/lib -L$(DEVKITPRO)/devkitA64/aarch64-none-elf/lib/pic \\\n              -lSDL2 ${MESA_LIBS} -l${NX} -lpng16 -lz\n\nARCH_CFLAGS   += ${EXTRA_INCLUDES} ${MACHDEP}\nARCH_CXXFLAGS += ${EXTRA_INCLUDES} ${MACHDEP}\nARCH_LDFLAGS  += ${EXTRA_LIBS} ${MACHDEP} -specs=$(DEVKITPRO)/libnx/switch.specs\n\n# Unknown purpose, copied from dkp templates(?)\n# Only relevant search result: https://devkitpro.org/viewtopic.php?f=42&t=9552\nARCH_CXXFLAGS += -fno-rtti -fno-exceptions\n\npackage: mzx mzxrun.nro megazeux.nro mzxrun.nso megazeux.nso mzxrun.nacp megazeux.nacp\n\nclean:\n\t${RM} -f mzxrun.nacp mzxrun.nro mzxrun.nso mzxrun.pfs0 mzxrun.elf\n\t${RM} -f megazeux.nacp megazeux.nro megazeux.nso megazeux.pfs0 megazeux.elf\n\nbuild := ${build_root}/switch/megazeux\nbuild: package ${build}\n\t${CP} arch/switch/pad.config ${build}\n\t${CP} megazeux.nro ${build}\n\t${RM} ${build}/${mzxrun} ${build}/${mzx} ${build}/*.debug\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/switch/pad.config",
    "content": "# Axis1:   -left +right (left stick)\r\n# Axis2:   -up   +down\r\n# Axis3:   -left +right (right stick)\r\n# Axis4:   -up   +down\r\n#\r\n# Button1: A\r\n# Button2: B\r\n# Button3: X\r\n# Button4: Y\r\n# Button5: LStick\r\n# Button6: RStick\r\n# Button7: L\r\n# Button8: R\r\n# Button9: ZL\r\n# Button10: ZR\r\n# Button11: Plus\r\n# Button12: Minus\r\n# Button13: Dpad left\r\n# Button14: Dpad up\r\n# Button15: Dpad right\r\n# Button16: Dpad down\r\n# Button25: SL (left)\r\n# Button26: SR (left)\r\n# Button27: SL (right)\r\n# Button28: SR (right)\r\n\r\n# Buttons 17-24 are directional axis \"buttons\" ignored by MZX because MZX does\r\n# its own internal analog-to-digital conversion.\r\n\r\n# Because the Switch uses SDL, MZX can generate mappings automatically with\r\n# the SDL gamepad API. However, this API is based on XInput and has\r\n# the face buttons inverted from where they'd be expected on a Nintendo system.\r\n\r\ngamepad.a = act_b\r\ngamepad.b = act_a\r\ngamepad.x = act_y\r\ngamepad.y = act_x\r\n"
  },
  {
    "path": "arch/unix/Makefile.in",
    "content": "#\n# linux makefile generics\n#\n\nDSOLDFLAGS = -shared\nDSOPRE     = lib\nDSOPOST    = .so\nDSORPATH   = -Wl,-rpath,${LIBDIR}\nDSOSONAME  = -Wl,-soname,\n\n# Enable 64-bit off_t and ino_t types for fseeko, ftello, readdir, and *stat.\n# This fixes issues with readdir() failing with EOVERFLOW when called from a\n# 32-bit executable running on a large filesystem.\n# This also enables 64-bit file support on 32-bit platforms.\n# _LARGEFILE_SOURCE enables fseeko/ftello for very old glibc versions.\nARCH_CFLAGS   = -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE\nARCH_CXXFLAGS = -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE\n\n# Allow ZIP for portable unix-devel packages.\ninclude arch/zip.inc\ninclude arch/install.inc\n\n# linux os specific install files\ninstall-arch: install-check\n\t${install} -m 0755 -d \\\n\t\t${DESTDIR}${SHAREDIR} \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/16x16/apps \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/22x22/apps \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/24x24/apps \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/32x32/apps \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/48x48/apps \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/64x64/apps \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/128x128/apps \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/256x256/apps \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/scalable/apps \\\n\t\t${DESTDIR}${SHAREDIR}/applications\n\t${install} -m 0644 contrib/icons/generated/icon_16.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/16x16/apps/megazeux.png\n\t${install} -m 0644 contrib/icons/generated/icon_22.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/22x22/apps/megazeux.png\n\t${install} -m 0644 contrib/icons/generated/icon_24.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/24x24/apps/megazeux.png\n\t${install} -m 0644 contrib/icons/generated/icon_32.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/32x32/apps/megazeux.png\n\t${install} -m 0644 contrib/icons/generated/icon_48.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/48x48/apps/megazeux.png\n\t${install} -m 0644 contrib/icons/generated/icon_64.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/64x64/apps/megazeux.png\n\t${install} -m 0644 contrib/icons/generated/icon_128.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/128x128/apps/megazeux.png\n\t${install} -m 0644 contrib/icons/generated/icon_256.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/256x256/apps/megazeux.png\n\t${install} -m 0644 contrib/icons/icon.svg \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/scalable/apps/megazeux.svg\n\t${install} -m 0644 arch/unix/megazeux.desktop \\\n\t\t${DESTDIR}${SHAREDIR}/applications/megazeux.desktop\nifeq (${BUILD_MZXRUN},1)\n\t@${install} -m 0644 arch/unix/mzxrun.desktop \\\n\t\t${DESTDIR}${SHAREDIR}/applications/mzxrun.desktop\nendif\n\n# linux os specific install files\nuninstall-arch: install-check\n\t${RM} -f \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/16x16/apps/megazeux.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/22x22/apps/megazeux.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/24x24/apps/megazeux.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/32x32/apps/megazeux.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/48x48/apps/megazeux.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/64x64/apps/megazeux.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/128x128/apps/megazeux.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/256x256/apps/megazeux.png \\\n\t\t${DESTDIR}${SHAREDIR}/icons/hicolor/scalable/apps/megazeux.svg \\\n\t\t${DESTDIR}${SHAREDIR}/applications/megazeux.desktop\nifeq (${BUILD_MZXRUN},1)\n\t${RM} -f \\\n\t\t${DESTDIR}${SHAREDIR}/applications/mzxrun.desktop\nendif\n"
  },
  {
    "path": "arch/unix/README",
    "content": "MEGAZEUX ON UNIX / UNIX CLONES\n\nMegaZeux has been tested as compiling on the following UNIX / UNIX clones:\n\n - Linux (most architectures tested on hardware or QEMU via Alpine and Debian)\n - BSD (NetBSD, FreeBSD, and OpenBSD are tested)\n - Haiku (R1 Beta 5 x86-64 most recently tested; x86 needs the newer compiler)\n - Solaris (OpenSolaris 2008.3 is tested)\n\nSome of these platforms may have additional dependencies or installation\ninstructions, which will be added to this document over time.\n\nMEGAZEUX ON OPENSOLARIS\n\nThe package `SUNWxorg-headers' must be installed for X11 clipboard support.\n"
  },
  {
    "path": "arch/unix/megazeux.desktop",
    "content": "[Desktop Entry]\nName=MegaZeux\nExec=megazeux\nIcon=megazeux\nTerminal=false\nType=Application\nCategories=Game;\nGenericName=Game Creation System\nComment=A simple, cross-platform game creation system.\n"
  },
  {
    "path": "arch/unix/megazeux.metainfo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright 2025 Lachesis -->\n<component type=\"desktop-application\">\n  <id>megazeux</id>\n  <metadata_license>CC-BY-SA-4.0</metadata_license>\n  <project_license>GPL-2.0-or-later</project_license>\n  <name>MegaZeux</name>\n  <summary>Text mode game creation system</summary>\n  <developer id=\"com.digitalmzx\">\n    <name>MegaZeux developers</name>\n  </developer>\n  <description>\n    <p>\n      MegaZeux is a spiritual successor to ZZT originally written for DOS\n      in 1994. MegaZeux allows playing and creating games based\n      on&#x2104;but no longer limited to&#x2104;VGA 80x25 text mode graphics.\n      MegaZeux includes a built-in editor and supports rudimentary scripting.\n    </p>\n    <p>\n      Hundreds of games have been created for MegaZeux,\n      and are available for download at no charge from DigitalMZX.\n    </p>\n    <p>\n      Supported platforms include Linux/BSD, Windows, macOS, MS-DOS, Wasm,\n      Android, Haiku, and numerous games consoles and handhelds.\n    </p>\n  </description>\n\n  <launchable type=\"desktop-id\">megazeux.desktop</launchable>\n\n  <branding>\n    <color type=\"primary\" scheme_preference=\"light\">#70d0d0</color>\n    <color type=\"primary\" scheme_preference=\"dark\">#000080</color>\n  </branding>\n  <content_rating type=\"oars-1.1\" />\n\n  <url type=\"homepage\">https://www.digitalmzx.com</url>\n  <url type=\"bugtracker\">https://github.com/AliceLR/megazeux</url>\n  <url type=\"help\">https://www.digitalmzx.com/mzx_help.html</url>\n\n  <screenshots>\n    <screenshot type=\"default\">\n      <caption>\n        A typical MegaZeux game with text mode graphics (Weirdness, Chapter I by Alexis Janson)\n      </caption>\n      <image>https://www.digitalmzx.com/metainfo/gameplay.png</image>\n    </screenshot>\n\n    <screenshot>\n      <caption>\n        Example of a game using extended graphical features (Pandemonium Buster by Lancer-X and Maxim)\n      </caption>\n      <image>https://www.digitalmzx.com/metainfo/gameplay2.png</image>\n    </screenshot>\n\n    <screenshot>\n      <caption>\n        MegaZeux editor with a game open for editing (MegaZeux Tutorial by Alexis Janson)\n      </caption>\n      <image>https://www.digitalmzx.com/metainfo/editor.png</image>\n    </screenshot>\n\n    <screenshot>\n      <caption>\n        MegaZeux graphics are based on editing text mode characters (xxy by Lachesis)\n      </caption>\n      <image>https://www.digitalmzx.com/metainfo/charedit.png</image>\n    </screenshot>\n\n    <screenshot>\n      <caption>\n        Robot editor showing an example Robotic script (Zeux II: Caverns of Zeux by Alexis Janson)\n      </caption>\n      <image>https://www.digitalmzx.com/metainfo/robotic.png</image>\n    </screenshot>\n  </screenshots>\n\n  <provides>\n    <binary>megazeux</binary>\n    <binary>mzxrun</binary>\n  </provides>\n\n  <recommends>\n    <control>keyboard</control>\n    <control>gamepad</control>\n  </recommends>\n  <supports>\n    <control>pointing</control>\n    <control>touch</control>\n  </supports>\n\n  <releases>\n    <release version=\"2.93d\" date=\"2025-06-09\">\n      <description>\n        <p>\n          Adds screen keyboard support for Android and Vita, plus software\n          rendering improvements for all platforms and bugfixes for various\n          renderers. KEY? and KEYENTER are now properly version gated,\n          Spitting Tiger logic for &lt;2.80 worlds has been fixed, and\n          general module playback enhancements.\n        </p>\n      </description>\n    </release>\n    <release version=\"2.93c\" date=\"2025-02-28\">\n      <description>\n        <p>\n          A bugfix release focused primarily on portability fixes. Longstanding\n          bugs related to window resizing that affect Linux in particular have\n          been fixed. Regressions involving the editor \"show thing\" shortcuts\n          and text input have been fixed. New features include SDL3 support and\n          restored support for some older Sound Blaster cards in the DOS port.\n        </p>\n      </description>\n    </release>\n\n    <!-- See the MegaZeux help file or docs/changelog.txt for all releases\n         from Aug. 2004 onward.\n\n         See the MegaZeux help file or release notes bundled with old\n         version archives for releases from Dec. 1994 to July 2004. -->\n  </releases>\n\n  <update_contact>petrifiedrowan_AT_gmail.com</update_contact>\n</component>\n"
  },
  {
    "path": "arch/unix/mzxrun.desktop",
    "content": "[Desktop Entry]\nName=MegaZeux (Runtime Only)\nExec=mzxrun\nIcon=megazeux\nTerminal=false\nType=Application\nCategories=Game;\nGenericName=Game Creation System\nComment=A simple, cross-platform game creation system. This executable does not load the game editor.\n"
  },
  {
    "path": "arch/wii/CONFIG.WII",
    "content": "#!/bin/sh\n\n./config.sh --platform wii --prefix $DEVKITPPC --enable-release --enable-lto \\\n            --disable-sdl --disable-utils --enable-tremor --enable-meter \\\n            --enable-stdio-redirect \"$@\"\n"
  },
  {
    "path": "arch/wii/Makefile.in",
    "content": "#\n# Wii Makefile\n#\n\n.PHONY: package\n\nifeq ($(strip $(DEVKITPRO)),)\n$(error \"DEVKITPRO must be set in your environment.\")\nendif\n\nifeq ($(strip $(DEVKITPPC)),)\n$(error \"DEVKITPPC must be set in your environment.\")\nendif\n\nEXTRA_LICENSES += ${LICENSE_MPL2} ${LICENSE_NEWLIB}\n\n#\n# Wii target rules\n#\n\ninclude $(DEVKITPPC)/wii_rules\n\nELF2DOL := elf2dol\n\n# Block --host, which will break things.\nCROSS_COMPILE =\n# Don't strip when generating debug symbols.\nNO_STRIP_IN_DEBUGLINK ?= 1\n# Disable rules that require target code to run natively.\nSUPPRESS_HOST_TARGETS ?= 1\n\n#\n# Override library paths\n#\n\nSDL_PREFIX     = ${DEVKITPRO}/portlibs/wii\nLIBPNG_PREFIX  := ${PORTLIBS_PATH}/ppc\n\nPORTLIBS_INCLUDES := $(foreach dir, $(PORTLIBS), -isystem $(dir)/include)\nPORTLIBS_LIBS     := $(foreach dir, $(PORTLIBS), -L$(dir)/lib)\n\nEXTRA_INCLUDES := -isystem $(LIBOGC_INC) ${PORTLIBS_INCLUDES}\nEXTRA_LIBS     := -L$(LIBOGC_LIB) ${PORTLIBS_LIBS} \\\n                 -lfat -lasnd -lwiikeyboard -lwiiuse -lbte -logc -lstdc++ -lm\n\nARCH_CFLAGS   += ${EXTRA_INCLUDES} ${MACHDEP}\nARCH_CXXFLAGS += ${EXTRA_INCLUDES} ${MACHDEP}\nARCH_LDFLAGS  += ${EXTRA_LIBS} ${MACHDEP}\n\n# Boot MegaZeux by default if the editor is enabled; otherwise, boot MZXRun.\npackage: mzx\nifneq ($(BUILD_EDITOR),)\n\t${ELF2DOL} ${mzx} boot.dol\n\t${ELF2DOL} ${mzxrun} mzxrun.dol\nelse\n\t${ELF2DOL} ${mzxrun} boot.dol\nendif\n\nclean:\n\t${RM} -f boot.dol mzxrun.dol arch/wii/*.d arch/wii/*.o\n\n#\n# Vile hack, remove me ASAP\n#\narch/wii/%.o: arch/wii/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} -c $< -o $@\n\narch/wii/%.o: arch/wii/%.cpp\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CXX} -MD ${core_cxxflags} ${core_flags} -c $< -o $@\n\n#\n# Need to nest Wii binaries in a subdir\n#\nbuild := ${build_root}/apps/megazeux\nbuild: package ${build}\nifeq ($(BUILD_SDL),)\n\t${CP} arch/wii/pad.config ${build}\nelse\nifeq (${BUILD_SDL},1)\n\t${CP} arch/wii/pad.config.sdl ${build}/pad.config\nelse\n\t${CP} arch/wii/pad.config.sdl2 ${build}/pad.config\nendif\nendif\n\t${CP} contrib/icons/generated/icon_wii.png ${build}/icon.png\n\t${CP} boot.dol ${build}\nifneq ($(BUILD_EDITOR),)\n\t${CP} mzxrun.dol ${build}\nendif\n\t@sed \"s/%VERSION%/${VERSION}/g;s/%DATE%/`date -u +%Y%m%d%H%M`/g\" \\\n\t    arch/wii/meta.xml > ${build}/meta.xml\n\t${RM} ${build}/${mzxrun} ${build}/${mzxrun}.debug\n\t${RM} ${build}/${mzx} ${build}/${mzx}.debug\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/wii/README",
    "content": "BUILDING MEGAZEUX FOR WII\n\nAs of 2.82b, MegaZeux can be built for the Wii. You will need the devkitPPC\ntoolchain, libogc and libfat-ogc. You will also need the following in this\ndirectory:\n\n  - libogg (audio only)\n  - libvorbis (audio only)\n\nMake sure DEVKITPRO and DEVKITPPC are both defined and valid, and use\nconfig.sh to configure. You can get the default configuration by running:\n\narch/wii/CONFIG.WII\n\nSDLWii builds can be enabled by providing the following flag to this script:\n\n  --enable-sdl1\n\nThen build with the command:\n\nmake package\n\nThis will create a \"boot.dol\" file which you should know how to launch.\n\nPACKAGING\n\nUse \"make archive\" to build a build/dist/wii/mzxgit-wii.zip file for\ndistribution.\n"
  },
  {
    "path": "arch/wii/audio.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/audio/audio.h\"\n#include \"../../src/audio/audio_struct.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <malloc.h>\n\n#define BOOL _BOOL\n#include <asndlib.h>\n#include <gctypes.h>\n#include <ogc/lwp.h>\n#include <ogc/cache.h>\n#include <ogc/system.h>\n#undef BOOL\n\n#define STACKSIZE 8192\n\nstatic lwpq_t audio_queue;\nstatic lwp_t audio_thread;\nstatic u8 audio_stack[STACKSIZE];\nstatic int16_t *audio_buffer[3];\nstatic volatile int current = 0;\nstatic volatile int audio_stop = 0;\nstatic unsigned buffer_frames;\nstatic unsigned buffer_size;\nstatic boolean active = false;\n\nstatic void *wii_audio_thread(void *dud)\n{\n  while(!audio_stop)\n  {\n    LWP_ThreadSleep(audio_queue);\n    audio_mixer_render_frames(audio_buffer[current], buffer_frames, 2, SAMPLE_S16);\n    DCFlushRange(audio_buffer[current], buffer_size);\n    current = (current + 1) % 3;\n  }\n\n  return 0;\n}\n\nstatic void voice_callback(s32 voice)\n{\n  ASND_AddVoice(voice, audio_buffer[(current + 2) % 3], buffer_size);\n  LWP_ThreadSignal(audio_queue);\n}\n\nvoid init_audio_platform(struct config_info *conf)\n{\n  // buffer size must be multiple of 32 bytes, so samples must be multiple of 8\n  unsigned frames = (conf->audio_buffer_samples + 7) & ~7;\n  int i;\n\n  if(!audio_mixer_init(conf->audio_sample_rate, frames, 2))\n    return;\n\n  buffer_frames = audio.buffer_frames;\n  buffer_size = buffer_frames * sizeof(int16_t) * 2 /* stereo */;\n\n  for(i = 0; i < 3; i++)\n  {\n    audio_buffer[i] = memalign(32, buffer_size);\n    memset(audio_buffer[i], 0, buffer_size);\n    DCFlushRange(audio_buffer[i], buffer_size);\n  }\n\n  ASND_Init();\n  LWP_InitQueue(&audio_queue);\n  if(LWP_CreateThread(&audio_thread, wii_audio_thread, NULL, audio_stack,\n   STACKSIZE, 80) >= 0)\n  {\n    ASND_SetVoice(0, VOICE_STEREO_16BIT, audio.output_frequency, 0,\n     audio_buffer[0], buffer_size, 255, 255, voice_callback);\n    ASND_Pause(0);\n    active = true;\n  }\n}\n\nvoid quit_audio_platform(void)\n{\n  void *dud;\n\n  if(!active)\n    return;\n\n  active = false;\n  audio_stop = 1;\n  LWP_JoinThread(audio_thread, &dud);\n  ASND_Pause(1);\n  ASND_End();\n  LWP_CloseQueue(audio_queue);\n  // Don't free hardware audio buffers\n  // Memory allocated with memalign() can't neccessarily be free()'d\n}\n"
  },
  {
    "path": "arch/wii/event.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/event.h\"\n#include \"../../src/graphics.h\"\n#include \"../../src/platform.h\"\n\n#include <stdint.h>\n#include <stdlib.h>\n#include <stdio.h>\n\n#define BOOL _BOOL\n#include <gcutil.h>\n#include <ogc/lwp.h>\n#include <ogc/message.h>\n#include <ogc/pad.h>\n#include <ogc/ipc.h>\n#include <ogc/video.h>\n#include <ogc/usbmouse.h>\n#include <wiikeyboard/keyboard.h>\n#include <wiiuse/wpad.h>\n#undef BOOL\n\n// Shut an annoying warning up\n#if WPAD_CLASSIC_BUTTON_RIGHT == (0x8000 << 16)\n#undef WPAD_CLASSIC_BUTTON_RIGHT\n#define WPAD_CLASSIC_BUTTON_RIGHT (((uint32_t)0x8000) << 16u)\n#endif\n\n#define PTR_BORDER_W 128\n#define PTR_BORDER_H 70\n\n#define USB_MOUSE_BTN_LEFT   0x01\n#define USB_MOUSE_BTN_RIGHT  0x02\n#define USB_MOUSE_BTN_MIDDLE 0x04\n\n#define USB_MOUSE_BTN_MASK (USB_MOUSE_BTN_LEFT | USB_MOUSE_BTN_RIGHT | \\\n USB_MOUSE_BTN_MIDDLE)\n\n#define HOME_BUTTON (MAX_JOYSTICK_BUTTONS - 1)\n\nextern struct input_status input;\n\nenum event_type\n{\n  EVENT_BUTTON_DOWN,\n  EVENT_BUTTON_UP,\n  EVENT_AXIS_MOVE,\n  EVENT_CHANGE_EXT,\n  EVENT_POINTER_MOVE,\n  EVENT_POINTER_OUT,\n  EVENT_KEY_DOWN,\n  EVENT_KEY_UP,\n  EVENT_KEY_LOCKS,\n  EVENT_MOUSE_MOVE,\n  EVENT_MOUSE_BUTTON_DOWN,\n  EVENT_MOUSE_BUTTON_UP\n};\n\nstruct button_event\n{\n  enum event_type type;\n  unsigned int pad;\n  uint32_t button;\n};\n\nstruct axis_event\n{\n  enum event_type type;\n  unsigned int pad;\n  unsigned int axis;\n  int16_t pos;\n};\n\nstruct ext_event\n{\n  enum event_type type;\n  unsigned int pad;\n  int ext;\n};\n\nstruct pointer_event\n{\n  enum event_type type;\n  int x;\n  int y;\n};\n\nstruct key_event\n{\n  enum event_type type;\n  unsigned int key;\n  uint16_t unicode;\n};\n\nstruct locks_event\n{\n  enum event_type type;\n  uint16_t locks;\n};\n\nstruct mouse_move_event\n{\n  enum event_type type;\n  int dx;\n  int dy;\n};\n\nstruct mouse_button_event\n{\n  enum event_type type;\n  unsigned int button;\n};\n\nunion event\n{\n  enum event_type type;\n  struct button_event button;\n  struct axis_event axis;\n  struct ext_event ext;\n  struct pointer_event pointer;\n  struct key_event key;\n  struct locks_event locks;\n  struct mouse_move_event mmove;\n  struct mouse_button_event mbutton;\n};\n\n#define STACKSIZE 8192\nstatic u8 poll_stack[STACKSIZE];\nstatic lwp_t poll_thread;\n\n#define EVENT_QUEUE_SIZE 64\nstatic mqbox_t eq;\nstatic int eq_inited = 0;\n\nstatic int pointing = 0;\nstatic int ext_type[4] =\n{\n  WPAD_EXP_NONE, WPAD_EXP_NONE,\n  WPAD_EXP_NONE, WPAD_EXP_NONE\n};\n\nstatic int write_eq(union event *ev)\n{\n  union event *new_ev;\n  new_ev = malloc(sizeof(union event));\n  if(!new_ev)\n    return false;\n  *new_ev = *ev;\n  return MQ_Send(eq, (mqmsg_t)(new_ev), MQ_MSG_NOBLOCK);\n}\n\nstatic int read_eq(union event *ev)\n{\n  mqmsg_t new_ev = NULL;\n  if(MQ_Receive(eq, &new_ev, MQ_MSG_NOBLOCK))\n  {\n    *ev = *(union event *)new_ev;\n    free(new_ev);\n    return true;\n  }\n  else\n    return false;\n}\n\nstatic void scan_buttons(unsigned int pad, uint32_t old_btns, uint32_t new_btns)\n{\n  union event ev;\n  uint32_t chg_btns;\n  uint32_t i;\n\n  if(old_btns != new_btns)\n  {\n    chg_btns = old_btns ^ new_btns;\n    ev.button.pad = pad;\n    for(i = 1; i != 0; i <<= 1)\n    {\n      if(chg_btns & i)\n      {\n        ev.button.button = i;\n        if(new_btns & i)\n          ev.type = EVENT_BUTTON_DOWN;\n        else\n          ev.type = EVENT_BUTTON_UP;\n        write_eq(&ev);\n      }\n    }\n  }\n}\n\nstatic int16_t adjust_axis(int pos, int min, int cen, int max)\n{\n  int32_t temp;\n  temp = (pos - cen) * 32767 / ((cen - min) + (max - cen) / 2);\n  // FIXME hack because Wii stick axis ranges seem to cap out near +-22000\n  // universally, but +-32767 is desired.\n  temp += temp >> 1;\n  if(temp < -32767) temp = -32767;\n  if(temp > 32767) temp = 32767;\n  return temp;\n}\n\nstatic void scan_joystick(unsigned int pad, unsigned int xaxis, joystick_t js,\n int16_t axes[])\n{\n  union event ev;\n  int16_t temp;\n\n  ev.type = EVENT_AXIS_MOVE;\n  ev.axis.pad = pad;\n  temp = adjust_axis(js.pos.x, js.min.x, js.center.x, js.max.x);\n  if(temp != axes[0])\n  {\n    ev.axis.axis = xaxis;\n    ev.axis.pos = temp;\n    write_eq(&ev);\n    axes[0] = temp;\n  }\n  temp = -adjust_axis(js.pos.y, js.min.y, js.center.y, js.max.y);\n  if(temp != axes[1])\n  {\n    ev.axis.axis = xaxis + 1;\n    ev.axis.pos = temp;\n    write_eq(&ev);\n    axes[1] = temp;\n  }\n}\n\nstatic int16_t adjust_axis_single(float mag, int16_t minval, int16_t maxval)\n{\n  int32_t temp = (int32_t)(mag * 32767.0);\n  temp = (temp - minval) * 32767 / (maxval - minval);\n  if(temp < 0) temp = 0;\n  if(temp > 32767) temp = 32767;\n  return temp;\n}\n\nstatic void scan_axis_single(unsigned int pad, unsigned int axis, float mag,\n int16_t *prev)\n{\n  // NOTE adjusting the range to ignore <3200 because the minimum values\n  // received from the triggers seem generally questionable.\n  int16_t temp = adjust_axis_single(mag, 3200, 32767);\n  union event ev;\n\n  if(temp != *prev)\n  {\n    ev.type = EVENT_AXIS_MOVE;\n    ev.axis.pad = pad;\n    ev.axis.axis = axis;\n    ev.axis.pos = temp;\n    write_eq(&ev);\n    *prev = temp;\n  }\n}\n\nstatic void poll_input(void)\n{\n  static int old_x = 1000, old_y = 1000;\n  static boolean old_point = false;\n  static uint32_t old_btns[4] = {0};\n  static int16_t old_axes[4][6] = {{0}};\n  static u32 old_type[4] =\n  {\n    WPAD_EXP_NONE, WPAD_EXP_NONE,\n    WPAD_EXP_NONE, WPAD_EXP_NONE\n  };\n  static uint32_t old_gcbtns[4] = {0};\n  static int8_t old_gcaxes[4][4] = {{0}};\n  static int8_t old_gctriggers[4][2] = {{0}};\n  static uint16_t old_modifiers = 0;\n  static uint8_t old_mousebtns = 0;\n\n  WPADData *wd;\n  PADStatus pad[4];\n  keyboard_event ke;\n  mouse_event me;\n  u32 type;\n  union event ev;\n  unsigned int i, j;\n\n  WPAD_ScanPads();\n  for(i = 0; i < 4; i++)\n  {\n    if(WPAD_Probe(i, &type) == WPAD_ERR_NONE)\n    {\n      wd = WPAD_Data(i);\n      if(type != old_type[i])\n      {\n        scan_buttons(i, old_btns[i], 0);\n        old_btns[i] = 0;\n        ev.type = EVENT_AXIS_MOVE;\n        ev.axis.pad = i;\n        ev.axis.pos = 0;\n        for(j = 0; j < 4; j++)\n        {\n          if(old_axes[i][j])\n          {\n            ev.axis.axis = j;\n            write_eq(&ev);\n            old_axes[i][j] = 0;\n          }\n        }\n        ev.type = EVENT_CHANGE_EXT;\n        ev.ext.pad = i;\n        old_type[i] = type;\n        ev.ext.ext = type;\n        write_eq(&ev);\n      }\n      if(i == 0)\n      {\n        if(wd->ir.valid)\n        {\n          ev.pointer.x = wd->ir.x - PTR_BORDER_W;\n          ev.pointer.y = wd->ir.y - PTR_BORDER_H;\n          if(ev.pointer.x < 0)\n            ev.pointer.x = 0;\n          if(ev.pointer.y < 0)\n            ev.pointer.y = 0;\n          if(ev.pointer.x >= 640)\n            ev.pointer.x = 639;\n          if(ev.pointer.y >= 350)\n            ev.pointer.y = 349;\n          if((ev.pointer.x != old_x) || (ev.pointer.y != old_y))\n          {\n            ev.type = EVENT_POINTER_MOVE;\n            write_eq(&ev);\n            old_point = true;\n            old_x = ev.pointer.x;\n            old_y = ev.pointer.y;\n          }\n        }\n        else\n        {\n          if(old_point)\n          {\n            ev.type = EVENT_POINTER_OUT;\n            write_eq(&ev);\n            old_point = false;\n            old_x = old_y = 1000;\n          }\n        }\n      }\n      scan_buttons(i, old_btns[i], wd->btns_h);\n      old_btns[i] = wd->btns_h;\n      switch(type)\n      {\n        case WPAD_EXP_NUNCHUK:\n        {\n          scan_joystick(i, 0, wd->exp.nunchuk.js, old_axes[i]);\n          break;\n        }\n        case WPAD_EXP_CLASSIC:\n        {\n          scan_joystick(i, 0, wd->exp.classic.ljs, old_axes[i]);\n          scan_joystick(i, 2, wd->exp.classic.rjs, old_axes[i] + 2);\n          scan_axis_single(i, 4, wd->exp.classic.l_shoulder, old_axes[i] + 4);\n          scan_axis_single(i, 5, wd->exp.classic.r_shoulder, old_axes[i] + 5);\n          break;\n        }\n        case WPAD_EXP_GUITARHERO3:\n        {\n          scan_joystick(i, 0, wd->exp.gh3.js, old_axes[i]);\n          scan_axis_single(i, 2, wd->exp.gh3.whammy_bar, old_axes[i] + 2);\n          break;\n        }\n        default: break;\n      }\n    }\n    else\n    {\n      scan_buttons(i, old_btns[i], 0);\n      old_btns[i] = 0;\n      ev.type = EVENT_AXIS_MOVE;\n      ev.axis.pad = i;\n      ev.axis.pos = 0;\n      for(j = 0; j < 4; j++)\n      {\n        if(old_axes[i][j])\n        {\n          ev.axis.axis = j;\n          write_eq(&ev);\n          old_axes[i][j] = 0;\n        }\n      }\n    }\n  }\n\n  PAD_Read(pad);\n  for(i = 0; i < 4; i++)\n  {\n    if(pad[i].err == PAD_ERR_NONE)\n    {\n      scan_buttons(i + 4, old_gcbtns[i], pad[i].button);\n      old_gcbtns[i] = pad[i].button;\n      ev.type = EVENT_AXIS_MOVE;\n      ev.axis.pad = i + 4;\n      // TODO all 6 of these have low min/max magnitudes but not as bad as the\n      // nunchuck and classic controller, maybe address this eventually.\n      if(pad[i].stickX != old_gcaxes[i][0])\n      {\n        ev.axis.axis = 0;\n        ev.axis.pos = pad[i].stickX << 8;\n        write_eq(&ev);\n        old_gcaxes[i][0] = pad[i].stickX;\n      }\n      if(pad[i].stickY != old_gcaxes[i][1])\n      {\n        ev.axis.axis = 1;\n        ev.axis.pos = -(pad[i].stickY << 8);\n        write_eq(&ev);\n        old_gcaxes[i][1] = pad[i].stickY;\n      }\n      if(pad[i].substickX != old_gcaxes[i][2])\n      {\n        ev.axis.axis = 2;\n        ev.axis.pos = pad[i].substickX << 8;\n        write_eq(&ev);\n        old_gcaxes[i][0] = pad[i].substickX;\n      }\n      if(pad[i].substickY != old_gcaxes[i][3])\n      {\n        ev.axis.axis = 3;\n        ev.axis.pos = -(pad[i].substickY << 8);\n        write_eq(&ev);\n        old_gcaxes[i][1] = pad[i].substickY;\n      }\n      if(pad[i].triggerL != old_gctriggers[i][0])\n      {\n        ev.axis.axis = 4;\n        ev.axis.pos = pad[i].triggerL << 7;\n        write_eq(&ev);\n        old_gctriggers[i][0] = pad[i].triggerL;\n      }\n      if(pad[i].triggerR != old_gctriggers[i][1])\n      {\n        ev.axis.axis = 5;\n        ev.axis.pos = pad[i].triggerR << 7;\n        write_eq(&ev);\n        old_gctriggers[i][1] = pad[i].triggerR;\n      }\n    }\n    else\n    {\n      scan_buttons(i + 4, old_gcbtns[i], 0);\n      old_gcbtns[i] = 0;\n      ev.type = EVENT_AXIS_MOVE;\n      ev.axis.pad = i + 4;\n      ev.axis.pos = 0;\n      for(j = 0; j < 4; j++)\n      {\n        if(old_gcaxes[i][j])\n        {\n          ev.axis.axis = j;\n          write_eq(&ev);\n          old_gcaxes[i][j] = 0;\n        }\n      }\n    }\n  }\n\n  while(KEYBOARD_GetEvent(&ke))\n  {\n    ke.modifiers &= MOD_CAPSLOCK | MOD_NUMLOCK;\n    if(ke.modifiers != old_modifiers)\n    {\n      ev.type = EVENT_KEY_LOCKS;\n      ev.locks.locks = ke.modifiers;\n      write_eq(&ev);\n      old_modifiers = ke.modifiers;\n    }\n    ev.key.key = ke.keycode;\n    // Non-character keys mapped to the private use area\n    if((ke.symbol >= 0xE000) && (ke.symbol < 0xF900))\n      ev.key.unicode = 0;\n    else\n      ev.key.unicode = ke.symbol;\n    switch(ke.type)\n    {\n      default:\n      case KEYBOARD_CONNECTED:\n      case KEYBOARD_DISCONNECTED:\n        break;\n      case KEYBOARD_PRESSED:\n        ev.type = EVENT_KEY_DOWN;\n        write_eq(&ev);\n        break;\n      case KEYBOARD_RELEASED:\n        ev.type = EVENT_KEY_UP;\n        write_eq(&ev);\n        break;\n    }\n  }\n\n  ev.type = EVENT_MOUSE_MOVE;\n  ev.mmove.dx = ev.mmove.dy = 0;\n  while(MOUSE_GetEvent(&me))\n  {\n    ev.mmove.dx += me.rx;\n    ev.mmove.dy += me.ry;\n    me.button &= USB_MOUSE_BTN_MASK;\n    if(me.button != old_mousebtns)\n    {\n      if(ev.mmove.dx || ev.mmove.dy)\n        write_eq(&ev);\n      for(i = 1; i <= (me.button | old_mousebtns); i <<= 1)\n      {\n        if(i & (me.button ^ old_mousebtns))\n        {\n          if(i & me.button)\n            ev.type = EVENT_MOUSE_BUTTON_DOWN;\n          else\n            ev.type = EVENT_MOUSE_BUTTON_UP;\n          ev.mbutton.button = i;\n          write_eq(&ev);\n        }\n      }\n      old_mousebtns = me.button;\n      ev.type = EVENT_MOUSE_MOVE;\n      ev.mmove.dx = ev.mmove.dy = 0;\n    }\n  }\n  if(ev.mmove.dx || ev.mmove.dy)\n    write_eq(&ev);\n}\n\nstatic void *wii_poll_thread(void *dud)\n{\n  WPAD_Init();\n  WPAD_SetDataFormat(WPAD_CHAN_ALL, WPAD_FMT_BTNS);\n  WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC_IR);\n  WPAD_SetVRes(0, 640 + PTR_BORDER_W * 2, 350 + PTR_BORDER_H * 2);\n\n  PAD_Init();\n\n  KEYBOARD_Init(NULL);\n  MOUSE_Init();\n\n  while(1)\n  {\n    VIDEO_WaitVSync();\n    poll_input();\n  }\n\n  return 0;\n}\n\nstatic int wii_map_button(unsigned int pad, uint32_t button)\n{\n  int rval = -1;\n\n  if(pad < 4)\n  {\n    switch(button)\n    {\n      case WPAD_BUTTON_A:     rval = 0; break;\n      case WPAD_BUTTON_B:     rval = 1; break;\n      case WPAD_BUTTON_1:     rval = 2; break;\n      case WPAD_BUTTON_2:     rval = 3; break;\n      case WPAD_BUTTON_LEFT:  rval = 4; break;\n      case WPAD_BUTTON_RIGHT: rval = 5; break;\n      case WPAD_BUTTON_UP:    rval = 6; break;\n      case WPAD_BUTTON_DOWN:  rval = 7; break;\n      case WPAD_BUTTON_MINUS: rval = 8; break;\n      case WPAD_BUTTON_PLUS:  rval = 9; break;\n      case WPAD_BUTTON_HOME:  return HOME_BUTTON;\n    }\n\n    switch(ext_type[pad])\n    {\n      case WPAD_EXP_NONE:\n        break;\n\n      case WPAD_EXP_NUNCHUK:\n      {\n        switch(button)\n        {\n          case WPAD_NUNCHUK_BUTTON_C: rval = 10; break;\n          case WPAD_NUNCHUK_BUTTON_Z: rval = 11; break;\n          default: break;\n        }\n        rval += 10;\n        break;\n      }\n\n      case WPAD_EXP_CLASSIC:\n      {\n        switch(button)\n        {\n          case WPAD_CLASSIC_BUTTON_A:       rval = 10; break;\n          case WPAD_CLASSIC_BUTTON_B:       rval = 11; break;\n          case WPAD_CLASSIC_BUTTON_X:       rval = 12; break;\n          case WPAD_CLASSIC_BUTTON_Y:       rval = 13; break;\n          case WPAD_CLASSIC_BUTTON_FULL_L:  rval = 14; break;\n          case WPAD_CLASSIC_BUTTON_FULL_R:  rval = 15; break;\n          case WPAD_CLASSIC_BUTTON_ZL:      rval = 16; break;\n          case WPAD_CLASSIC_BUTTON_ZR:      rval = 17; break;\n          case WPAD_CLASSIC_BUTTON_LEFT:    rval = 18; break;\n          case WPAD_CLASSIC_BUTTON_RIGHT:   rval = 19; break;\n          case WPAD_CLASSIC_BUTTON_UP:      rval = 20; break;\n          case WPAD_CLASSIC_BUTTON_DOWN:    rval = 21; break;\n          case WPAD_CLASSIC_BUTTON_MINUS:   rval = 22; break;\n          case WPAD_CLASSIC_BUTTON_PLUS:    rval = 23; break;\n          case WPAD_CLASSIC_BUTTON_HOME:    return HOME_BUTTON;\n        }\n        rval += 22;\n        break;\n      }\n\n      case WPAD_EXP_GUITARHERO3:\n      {\n        switch(button)\n        {\n          case WPAD_GUITAR_HERO_3_BUTTON_GREEN:       rval = 10; break;\n          case WPAD_GUITAR_HERO_3_BUTTON_RED:         rval = 11; break;\n          case WPAD_GUITAR_HERO_3_BUTTON_YELLOW:      rval = 12; break;\n          case WPAD_GUITAR_HERO_3_BUTTON_BLUE:        rval = 13; break;\n          case WPAD_GUITAR_HERO_3_BUTTON_ORANGE:      rval = 14; break;\n          case WPAD_GUITAR_HERO_3_BUTTON_STRUM_UP:    rval = 15; break;\n          case WPAD_GUITAR_HERO_3_BUTTON_STRUM_DOWN:  rval = 16; break;\n          case WPAD_GUITAR_HERO_3_BUTTON_MINUS:       rval = 17; break;\n          case WPAD_GUITAR_HERO_3_BUTTON_PLUS:        rval = 18; break;\n        }\n        rval += 46;\n        break;\n      }\n\n      default:\n        // Unsupported extension controller.\n        rval = -1;\n        break;\n    }\n  }\n  else\n  {\n    switch(button)\n    {\n      case PAD_BUTTON_A:      rval = 0; break;\n      case PAD_BUTTON_B:      rval = 1; break;\n      case PAD_BUTTON_X:      rval = 2; break;\n      case PAD_BUTTON_Y:      rval = 3; break;\n      case PAD_TRIGGER_L:     rval = 4; break;\n      case PAD_TRIGGER_R:     rval = 5; break;\n      case PAD_TRIGGER_Z:     rval = 6; break;\n      case PAD_BUTTON_LEFT:   rval = 7; break;\n      case PAD_BUTTON_RIGHT:  rval = 8; break;\n      case PAD_BUTTON_UP:     rval = 9; break;\n      case PAD_BUTTON_DOWN:   rval = 10; break;\n      case PAD_BUTTON_START:  rval = 11; break;\n    }\n  }\n\n  return rval;\n}\n\nstatic int wii_map_axis(unsigned int pad, unsigned int axis)\n{\n  if(pad < 4)\n  {\n    switch(ext_type[pad])\n    {\n      case WPAD_EXP_NUNCHUK:      return axis;\n      case WPAD_EXP_CLASSIC:      return axis + 2;\n      case WPAD_EXP_GUITARHERO3:  return axis + 8;\n      default:                    return -1; // Not supposed to happen\n    }\n  }\n  return axis;\n}\n\n/**\n * Convert generic USB HID keycodes to internal keycodes.\n */\nstatic enum keycode convert_USB_internal(unsigned int usb_hid_key)\n{\n  switch(usb_hid_key)\n  {\n    case 0x04: return IKEY_a;\n    case 0x05: return IKEY_b;\n    case 0x06: return IKEY_c;\n    case 0x07: return IKEY_d;\n    case 0x08: return IKEY_e;\n    case 0x09: return IKEY_f;\n    case 0x0A: return IKEY_g;\n    case 0x0B: return IKEY_h;\n    case 0x0C: return IKEY_i;\n    case 0x0D: return IKEY_j;\n    case 0x0E: return IKEY_k;\n    case 0x0F: return IKEY_l;\n    case 0x10: return IKEY_m;\n    case 0x11: return IKEY_n;\n    case 0x12: return IKEY_o;\n    case 0x13: return IKEY_p;\n    case 0x14: return IKEY_q;\n    case 0x15: return IKEY_r;\n    case 0x16: return IKEY_s;\n    case 0x17: return IKEY_t;\n    case 0x18: return IKEY_u;\n    case 0x19: return IKEY_v;\n    case 0x1A: return IKEY_w;\n    case 0x1B: return IKEY_x;\n    case 0x1C: return IKEY_y;\n    case 0x1D: return IKEY_z;\n    case 0x1E: return IKEY_1;\n    case 0x1F: return IKEY_2;\n    case 0x20: return IKEY_3;\n    case 0x21: return IKEY_4;\n    case 0x22: return IKEY_5;\n    case 0x23: return IKEY_6;\n    case 0x24: return IKEY_7;\n    case 0x25: return IKEY_8;\n    case 0x26: return IKEY_9;\n    case 0x27: return IKEY_0;\n    case 0x28: return IKEY_RETURN;\n    case 0x29: return IKEY_ESCAPE;\n    case 0x2A: return IKEY_BACKSPACE;\n    case 0x2B: return IKEY_TAB;\n    case 0x2C: return IKEY_SPACE;\n    case 0x2D: return IKEY_MINUS;\n    case 0x2E: return IKEY_EQUALS;\n    case 0x2F: return IKEY_LEFTBRACKET;\n    case 0x30: return IKEY_RIGHTBRACKET;\n    case 0x31: return IKEY_BACKSLASH;\n    case 0x33: return IKEY_SEMICOLON;\n    case 0x34: return IKEY_QUOTE;\n    case 0x35: return IKEY_BACKQUOTE;\n    case 0x36: return IKEY_COMMA;\n    case 0x37: return IKEY_PERIOD;\n    case 0x38: return IKEY_SLASH;\n    case 0x39: return IKEY_CAPSLOCK;\n    case 0x3A: return IKEY_F1;\n    case 0x3B: return IKEY_F2;\n    case 0x3C: return IKEY_F3;\n    case 0x3D: return IKEY_F4;\n    case 0x3E: return IKEY_F5;\n    case 0x3F: return IKEY_F6;\n    case 0x40: return IKEY_F7;\n    case 0x41: return IKEY_F8;\n    case 0x42: return IKEY_F9;\n    case 0x43: return IKEY_F10;\n    case 0x44: return IKEY_F11;\n    case 0x45: return IKEY_F12;\n    case 0x46: return IKEY_SYSREQ;\n    case 0x47: return IKEY_SCROLLOCK;\n    case 0x48: return IKEY_BREAK;\n    case 0x49: return IKEY_INSERT;\n    case 0x4A: return IKEY_HOME;\n    case 0x4B: return IKEY_PAGEUP;\n    case 0x4C: return IKEY_DELETE;\n    case 0x4D: return IKEY_END;\n    case 0x4E: return IKEY_PAGEDOWN;\n    case 0x4F: return IKEY_RIGHT;\n    case 0x50: return IKEY_LEFT;\n    case 0x51: return IKEY_DOWN;\n    case 0x52: return IKEY_UP;\n    case 0x53: return IKEY_NUMLOCK;\n    case 0x54: return IKEY_KP_DIVIDE;\n    case 0x55: return IKEY_KP_MULTIPLY;\n    case 0x56: return IKEY_KP_MINUS;\n    case 0x57: return IKEY_KP_PLUS;\n    case 0x58: return IKEY_KP_ENTER;\n    case 0x59: return IKEY_KP1;\n    case 0x5A: return IKEY_KP2;\n    case 0x5B: return IKEY_KP3;\n    case 0x5C: return IKEY_KP4;\n    case 0x5D: return IKEY_KP5;\n    case 0x5E: return IKEY_KP6;\n    case 0x5F: return IKEY_KP7;\n    case 0x60: return IKEY_KP8;\n    case 0x61: return IKEY_KP9;\n    case 0x62: return IKEY_KP0;\n    case 0x63: return IKEY_KP_PERIOD;\n    case 0x65: return IKEY_MENU;\n    case 0xE0: return IKEY_LCTRL;\n    case 0xE1: return IKEY_LSHIFT;\n    case 0xE2: return IKEY_LALT;\n    case 0xE3: return IKEY_LSUPER;\n    case 0xE4: return IKEY_RCTRL;\n    case 0xE5: return IKEY_RSHIFT;\n    case 0xE6: return IKEY_RALT;\n    case 0xE7: return IKEY_RSUPER;\n    default: return IKEY_UNKNOWN;\n  }\n}\n\nstatic boolean process_event(union event *ev)\n{\n  struct buffered_status *status = store_status();\n\n  switch(ev->type)\n  {\n    case EVENT_BUTTON_DOWN:\n    {\n      int button = wii_map_button(ev->button.pad, ev->button.button);\n\n      if((ev->button.pad == 0) && pointing)\n      {\n        enum mouse_button mousebutton;\n        switch(ev->button.button)\n        {\n          case WPAD_BUTTON_A: mousebutton = MOUSE_BUTTON_LEFT; break;\n          case WPAD_BUTTON_B: mousebutton = MOUSE_BUTTON_RIGHT; break;\n          default: mousebutton = MOUSE_NO_BUTTON; break;\n        }\n        if(mousebutton)\n        {\n          status->mouse_button = mousebutton;\n          status->mouse_repeat = mousebutton;\n          status->mouse_button_state |= MOUSE_BUTTON(mousebutton);\n          status->mouse_repeat_state = 1;\n          status->mouse_drag_state = -1;\n          status->mouse_time = get_ticks();\n          return true;\n        }\n      }\n\n      if(button >= 0)\n      {\n        if(button == HOME_BUTTON)\n        {\n          // HACK: force the home button mapping to always be select.\n          int joystick = ev->button.pad;\n          input.joystick_global_map.button[joystick][button] = -JOY_SELECT;\n          input.joystick_game_map.button[joystick][button] = 0;\n        }\n        joystick_button_press(status, ev->button.pad, button);\n        return true;\n      }\n      break;\n    }\n\n    case EVENT_BUTTON_UP:\n    {\n      int button = wii_map_button(ev->button.pad, ev->button.button);\n\n      if((ev->button.pad == 0) && status->mouse_button_state)\n      {\n        enum mouse_button mousebutton;\n        switch(ev->button.button)\n        {\n          case WPAD_BUTTON_A: mousebutton = MOUSE_BUTTON_LEFT; break;\n          case WPAD_BUTTON_B: mousebutton = MOUSE_BUTTON_RIGHT; break;\n          default: mousebutton = MOUSE_NO_BUTTON; break;\n        }\n        if(mousebutton &&\n         (status->mouse_button_state & MOUSE_BUTTON(mousebutton)))\n        {\n          status->mouse_button_state &= ~MOUSE_BUTTON(mousebutton);\n          status->mouse_repeat = 0;\n          status->mouse_drag_state = 0;\n          status->mouse_repeat_state = 0;\n          return true;\n        }\n      }\n\n      if(button >= 0)\n      {\n        joystick_button_release(status, ev->button.pad, button);\n        return true;\n      }\n      break;\n    }\n\n    case EVENT_AXIS_MOVE:\n    {\n      int axis = wii_map_axis(ev->axis.pad, ev->axis.axis);\n\n      if(axis > 0)\n      {\n        joystick_axis_update(status, ev->axis.pad, axis, ev->axis.pos);\n        return true;\n      }\n      break;\n    }\n\n    case EVENT_CHANGE_EXT:\n    {\n      ext_type[ev->ext.pad] = ev->ext.ext;\n      return true;\n    }\n\n    case EVENT_POINTER_MOVE:\n    {\n      pointing = 1;\n      status->mouse_moved = true;\n      status->mouse_pixel_x = ev->pointer.x;\n      status->mouse_pixel_y = ev->pointer.y;\n      status->mouse_x = ev->pointer.x / 8;\n      status->mouse_y = ev->pointer.y / 14;\n      return true;\n    }\n\n    case EVENT_POINTER_OUT:\n    {\n      pointing = 0;\n      return true;\n    }\n\n    case EVENT_KEY_DOWN:\n    {\n      enum keycode ckey = convert_USB_internal(ev->key.key);\n      if(!ckey)\n      {\n        if(ev->key.unicode)\n          ckey = IKEY_UNICODE;\n        else\n          break;\n      }\n\n      if((ckey == IKEY_RETURN) &&\n       get_alt_status(keycode_internal) &&\n       get_ctrl_status(keycode_internal))\n      {\n        video_toggle_fullscreen();\n        return true;\n      }\n\n#ifdef CONFIG_ENABLE_SCREENSHOTS\n      if(ckey == IKEY_F12 && enable_f12_hack)\n      {\n        dump_screen();\n        return true;\n      }\n#endif\n\n      if(status->key_repeat &&\n       (status->key_repeat != IKEY_LSHIFT) &&\n       (status->key_repeat != IKEY_RSHIFT) &&\n       (status->key_repeat != IKEY_LALT) &&\n       (status->key_repeat != IKEY_RALT) &&\n       (status->key_repeat != IKEY_LCTRL) &&\n       (status->key_repeat != IKEY_RCTRL))\n      {\n        // Stack current repeat key if it isn't shift, alt, or ctrl\n        if(input.repeat_stack_pointer != KEY_REPEAT_STACK_SIZE)\n        {\n          input.key_repeat_stack[input.repeat_stack_pointer] =\n           status->key_repeat;\n          input.unicode_repeat_stack[input.repeat_stack_pointer] =\n           status->unicode_repeat;\n          input.repeat_stack_pointer++;\n        }\n      }\n\n      key_press(status, ckey);\n      key_press_unicode(status, ev->key.unicode, true);\n      return true;\n    }\n\n    case EVENT_KEY_UP:\n    {\n      enum keycode ckey = convert_USB_internal(ev->key.key);\n      if(!ckey)\n      {\n        if(status->keymap[IKEY_UNICODE])\n          ckey = IKEY_UNICODE;\n        else\n          break;\n      }\n\n      status->keymap[ckey] = 0;\n      if(status->key_repeat == ckey)\n      {\n        status->key_repeat = IKEY_UNKNOWN;\n        status->unicode_repeat = 0;\n      }\n      status->key_release = ckey;\n      return true;\n    }\n\n    case EVENT_KEY_LOCKS:\n    {\n      status->numlock_status = !!(ev->locks.locks & MOD_NUMLOCK);\n      status->caps_status = !!(ev->locks.locks & MOD_CAPSLOCK);\n      return true;\n    }\n\n    case EVENT_MOUSE_MOVE:\n    {\n      int mx = status->mouse_pixel_x + ev->mmove.dx;\n      int my = status->mouse_pixel_y + ev->mmove.dy;\n\n      if(mx < 0)\n        mx = 0;\n      if(my < 0)\n        my = 0;\n      if(mx >= 640)\n        mx = 639;\n      if(my >= 350)\n        my = 349;\n\n      status->mouse_pixel_x = mx;\n      status->mouse_pixel_y = my;\n      status->mouse_x = mx / 8;\n      status->mouse_y = my / 14;\n      status->mouse_moved = true;\n      return true;\n    }\n\n    case EVENT_MOUSE_BUTTON_DOWN:\n    {\n      enum mouse_button button = MOUSE_NO_BUTTON;\n      switch (ev->mbutton.button)\n      {\n        case USB_MOUSE_BTN_LEFT:\n          button = MOUSE_BUTTON_LEFT;\n          break;\n        case USB_MOUSE_BTN_RIGHT:\n          button = MOUSE_BUTTON_RIGHT;\n          break;\n        case USB_MOUSE_BTN_MIDDLE:\n          button = MOUSE_BUTTON_MIDDLE;\n          break;\n        default:\n          break;\n      }\n\n      if(!button)\n        break;\n\n      status->mouse_button = button;\n      status->mouse_repeat = button;\n      status->mouse_button_state |= MOUSE_BUTTON(button);\n      status->mouse_repeat_state = 1;\n      status->mouse_drag_state = -1;\n      status->mouse_time = get_ticks();\n      return true;\n    }\n\n    case EVENT_MOUSE_BUTTON_UP:\n    {\n      enum mouse_button button = MOUSE_NO_BUTTON;\n      switch (ev->mbutton.button)\n      {\n        case USB_MOUSE_BTN_LEFT:\n          button = MOUSE_BUTTON_LEFT;\n          break;\n        case USB_MOUSE_BTN_RIGHT:\n          button = MOUSE_BUTTON_RIGHT;\n          break;\n        case USB_MOUSE_BTN_MIDDLE:\n          button = MOUSE_BUTTON_MIDDLE;\n          break;\n        default:\n          break;\n      }\n\n      if(!button)\n        break;\n\n      status->mouse_button_state &= ~MOUSE_BUTTON(button);\n      status->mouse_repeat = 0;\n      status->mouse_drag_state = 0;\n      status->mouse_repeat_state = 0;\n      return true;\n    }\n  }\n\n  return false;\n}\n\nboolean __update_event_status(void)\n{\n  boolean rval = false;\n  union event ev;\n\n  if(!eq_inited)\n    return false;\n\n  while(read_eq(&ev))\n    rval |= process_event(&ev);\n\n  return rval;\n}\n\nboolean __peek_exit_input(void)\n{\n  /* FIXME stub */\n  return false;\n}\n\nvoid __wait_event(void)\n{\n  mqmsg_t ev;\n\n  if(!eq_inited)\n    return;\n\n  if(MQ_Receive(eq, &ev, MQ_MSG_BLOCK))\n  {\n    process_event((union event *)ev);\n    free(ev);\n  }\n}\n\nvoid __warp_mouse(int x, int y)\n{\n  // Mouse warping doesn't work too well with the Wiimote\n}\n\nboolean platform_has_screen_keyboard(void)\n{\n  return false;\n}\n\nboolean platform_show_screen_keyboard(void)\n{\n  return false;\n}\n\nboolean platform_hide_screen_keyboard(void)\n{\n  return false;\n}\n\nboolean platform_is_screen_keyboard_active(void)\n{\n  return false;\n}\n\nvoid platform_init_event(void)\n{\n  struct buffered_status *status = store_status();\n  int i;\n\n  MQ_Init(&eq, EVENT_QUEUE_SIZE);\n  eq_inited = 1;\n  LWP_CreateThread(&poll_thread, wii_poll_thread, NULL, poll_stack, STACKSIZE,\n   40);\n\n  // TODO: should be able to actually detect pad plugging/removal?\n  for(i = 0; i < 8; i++)\n    joystick_set_active(status, i, true);\n}\n"
  },
  {
    "path": "arch/wii/meta.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<app version=\"1\">\n\t<name>MegaZeux</name>\n\t<version>%VERSION%</version>\n\t<release_date>%DATE%</release_date>\n\t<short_description>A simple game creation system (GCS)</short_description>\n\t<long_description>MegaZeux is a Game Creation System (GCS) inspired by Epic MegaGames' ZZT, first released by Alexis Janson in 1994. Originally written for DOS, MegaZeux is now available on a wide variety of platforms.&#xA;&#xA;Many features have been added since the original DOS version, and with the help of an intuitive editor and a simple but powerful scripting language, MegaZeux allows you to create your own ANSI-esque games. It is actively maintained by a thriving community. See https://www.digitalmzx.com/ for more information.</long_description>\n</app>\n"
  },
  {
    "path": "arch/wii/network.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/network/Socket.hpp\"\n#include \"../../src/util.h\"\n\n#include <network.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <stdlib.h>\n\n#ifdef IS_CXX_11\n#include <type_traits>\n#endif\n\n/**\n * Wii essentially uses standard BSD socket functions with different names.\n * Note while BSD socket fd functions are compatible with other operations for\n * fds, the Wii socket functions are not. That doesn't really matter as far\n * as MegaZeux is concerned, though.\n */\n\n/**\n * Track whether or not the internal Wii network layer is initialized.\n */\nstatic boolean net_is_initialized = false;\n\n/**\n * Set this thread's errno value.\n * If the provided value is negative, errno will be set to `-value`.\n * If the provided value is zero or positive, errno will be set to `0`.\n *\n * @param value   Return value from a network.h function.\n * @return        `value`.\n */\nstatic int set_net_errno(int value)\n{\n  errno = value < 0 ? -value : 0;\n  return value;\n}\n\nboolean Socket::platform_init(struct config_info *conf)\n{\n  return true;\n}\n\nboolean Socket::platform_init_late()\n{\n  if(!net_is_initialized)\n  {\n    int res = net_init();\n    set_net_errno(res);\n    if(res != 0)\n    {\n      Socket::perror(\"platform_socket_init_late() -> net_init()\");\n      return false;\n    }\n  }\n  net_is_initialized = true;\n  return true;\n}\n\nvoid Socket::platform_exit()\n{\n  if(net_is_initialized)\n  {\n    net_is_initialized = false;\n    net_deinit();\n  }\n}\n\nstruct hostent *Socket::gethostbyname(const char *name)\n{\n  // This one actually does set errno...\n  return net_gethostbyname(name);\n}\n\nint Socket::get_errno()\n{\n  return errno;\n}\n\nvoid Socket::perror(const char *message)\n{\n  char buf[256];\n  buf[0] = '\\0';\n\n  // NOTE: return value not portable.\n  strerror_r(errno, buf, ARRAY_SIZE(buf));\n  warn(\"--SOCKET-- %s: %s\\n\", message, buf);\n}\n\nint Socket::accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)\n{\n  int res = net_accept(sockfd, addr, addrlen);\n  return set_net_errno(res);\n}\n\nint Socket::bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)\n{\n  int res = net_bind(sockfd, (struct sockaddr *)addr, addrlen);\n  return set_net_errno(res);\n}\n\nvoid Socket::close(int fd)\n{\n  int res = net_close(fd);\n  set_net_errno(res);\n}\n\nint Socket::connect(int sockfd, const struct sockaddr *serv_addr,\n socklen_t addrlen)\n{\n  int res = net_connect(sockfd, const_cast<struct sockaddr *>(serv_addr), addrlen);\n  return set_net_errno(res);\n}\n\nint Socket::getsockopt(int sockfd, int level, int optname, void *optval,\n socklen_t *optlen)\n{\n  warn(\"--SOCKET-- getsockopt() is not implemented by libogc.\\n\");\n  if(level == SOL_SOCKET && optname == SO_ERROR && optval && optlen &&\n   *optlen == sizeof(int))\n  {\n    // Just pretend everything is ok since there's no way to get the error ;-(\n    int *optval_i = reinterpret_cast<int *>(optval);\n    *optval_i = 0;\n    return set_net_errno(0);\n  }\n  return set_net_errno(-EINVAL);\n}\n\nuint16_t Socket::hton_short(uint16_t hostshort)\n{\n  return htons(hostshort);\n}\n\nint Socket::listen(int sockfd, int backlog)\n{\n  int res = net_listen(sockfd, backlog);\n  return set_net_errno(res);\n}\n\nint Socket::poll(struct pollfd *fds, unsigned int nfds, int timeout_ms)\n{\n  /**\n   * libogc supports poll but uses its own struct with u32s instead of shorts\n   * for some reason. Note \"pollsd\" instead of \"pollfd\". Socket.hpp defines a\n   * pollfd that should be compatible with \"pollsd\".\n   */\n#ifdef IS_CXX_11\n  struct pollfd tmpf;\n  struct pollsd tmps;\n  // Make sure pollsd wasn't modified in libogc...\n  static_assert(sizeof(pollfd) == sizeof(pollsd), \"\");\n  static_assert(sizeof(tmpf.fd) == sizeof(tmps.socket), \"\");\n  static_assert(sizeof(tmpf.events) == sizeof(tmps.events), \"\");\n  static_assert(sizeof(tmpf.revents) == sizeof(tmps.revents), \"\");\n  static_assert(offsetof(pollfd, fd) == offsetof(pollsd, socket), \"\");\n  static_assert(offsetof(pollfd, events) == offsetof(pollsd, events), \"\");\n  static_assert(offsetof(pollfd, revents) == offsetof(pollsd, revents), \"\");\n#endif\n\n  int res = net_poll(reinterpret_cast<struct pollsd *>(fds), nfds, timeout_ms);\n  return set_net_errno(res);\n}\n\nint Socket::select(int nfds, fd_set *readfds, fd_set *writefds,\n fd_set *exceptfds, struct timeval *timeout)\n{\n  warn(\"--SOCKET-- select() is not implemented by libogc.\\n\");\n  int res = net_select(nfds, readfds, writefds, exceptfds, timeout);\n  return set_net_errno(res);\n}\n\nssize_t Socket::send(int sockfd, const void *buf, size_t len, int flags)\n{\n  int res = net_send(sockfd, buf, len, flags);\n  return set_net_errno(res);\n}\n\nssize_t Socket::sendto(int sockfd, const void *buf, size_t len, int flags,\n const struct sockaddr *to, socklen_t tolen)\n{\n  int res = net_sendto(sockfd, buf, len, flags, (struct sockaddr *)to, tolen);\n  return set_net_errno(res);\n}\n\nint Socket::setsockopt(int sockfd, int level, int optname,\n const void *optval, socklen_t optlen)\n{\n  int res = net_setsockopt(sockfd, level, optname, optval, optlen);\n  return set_net_errno(res);\n}\n\nint Socket::socket(int af, int type, int protocol)\n{\n  /**\n   * Doesn't recognize IPPROTO_TCP, but IPPROTO_IP is fine... :/\n   * https://github.com/devkitPro/wii-examples/blob/master/devices/network/sockettest/source/sockettest.c\n   */\n  if((type == SOCK_STREAM && protocol == IPPROTO_TCP) ||\n   (type == SOCK_DGRAM && protocol == IPPROTO_UDP))\n    protocol = IPPROTO_IP;\n\n  int res = net_socket(af, type, protocol);\n  return set_net_errno(res);\n}\n\nint Socket::recv(int sockfd, void *buf, size_t len, int flags)\n{\n  int res = net_recv(sockfd, buf, len, flags);\n  return set_net_errno(res);\n}\n\nint Socket::recvfrom(int sockfd, void *buf, size_t len, int flags,\n struct sockaddr *from, socklen_t *fromlen)\n{\n  int res = net_recvfrom(sockfd, buf, len, flags, from, fromlen);\n  return set_net_errno(res);\n}\n\nboolean Socket::is_last_error_fatal()\n{\n  return is_last_errno_fatal();\n}\n\nvoid Socket::set_blocking(int sockfd, boolean blocking)\n{\n  int flags = net_fcntl(sockfd, F_GETFL, 0);\n\n  if(!blocking)\n    flags |= O_NONBLOCK;\n  else\n    flags &= ~O_NONBLOCK;\n\n  net_fcntl(sockfd, F_SETFL, flags);\n}\n"
  },
  {
    "path": "arch/wii/pad.config",
    "content": "### Wiimotes (1 to 4) ###\n\n# The button mappings differ depending on the extension controller attached.\n#\n# Button      None  Nunchuk  Classic  Guitar Hero 3\n# ---------------------------------------------------\n# A           1     11       23       47\n# B           2     12       24       48\n# 1           3     13       25       49\n# 2           4     14       26       50\n# Left        5     15       27       51\n# Right       6     16       28       52\n# Up          7     17       29       53\n# Down        8     18       30       54\n# Minus       9     19       31       55\n# Plus        10    20       32       56\n#\n# C           -     21       -        -\n# Z           -     22       -        -\n#\n# a           -     -        33       -\n# b           -     -        34       -\n# x           -     -        35       -\n# y           -     -        36       -\n# L           -     -        37       -\n# R           -     -        38       -\n# ZL          -     -        39       -\n# ZR          -     -        40       -\n# Left        -     -        41       -\n# Right       -     -        42       -\n# Up          -     -        43       -\n# Down        -     -        44       -\n# Select      -     -        45       -\n# Start       -     -        46       -\n#\n# Green       -     -        -        57\n# Red         -     -        -        58\n# Yellow      -     -        -        59\n# Blue        -     -        -        60\n# Orange      -     -        -        61\n# Strum Up    -     -        -        62\n# Strum Down  -     -        -        63\n# Minus       -     -        -        64\n# Plus        -     -        -        65\n#\n# The home buttons on the Wiimote and the Classic Controller are currently\n# hardcoded to the \"select\" action.\n#\n# 1/2  Nunchuk\n# 3/4  Classic L\n# 5/6  Classic R\n# 7/8  Classic left/right triggers (positive only)\n# 9/10 Guitar stick\n# 11   Guitar whammy bar (positive only)\n\n# None\n# This control scheme assumes the Wii remote is held horizontally by default.\n# These assignments are fairly arbitrary and may change in future versions.\njoy[1,4]button1  = act_rshoulder\njoy[1,4]button2  = act_lshoulder\njoy[1,4]button3  = act_b\njoy[1,4]button4  = act_a\njoy[1,4]button5  = act_down\njoy[1,4]button6  = act_up\njoy[1,4]button7  = act_left\njoy[1,4]button8  = act_right\njoy[1,4]button9  = act_y\njoy[1,4]button10 = act_x\n\n# Nunchuk\n# This control scheme assumes the Wii remote is held vertically by default.\n# These assignments are fairly arbitrary and may change in future versions.\njoy[1,4]axis1 = act_l_left, act_l_right\njoy[1,4]axis2 = act_l_up, act_l_down\njoy[1,4]button11 = act_rshoulder\njoy[1,4]button12 = act_rtrigger\njoy[1,4]button13 = act_b\njoy[1,4]button14 = act_a\njoy[1,4]button15 = act_left\njoy[1,4]button16 = act_right\njoy[1,4]button17 = act_up\njoy[1,4]button18 = act_down\njoy[1,4]button19 = act_y\njoy[1,4]button20 = act_x\njoy[1,4]button21 = act_lshoulder\njoy[1,4]button22 = act_ltrigger\njoy[1,4].axis_lx = 1\njoy[1,4].axis_ly = 2\n\n# Classic\n# This control scheme ignores the Wii remote inputs by default.\njoy[1,4]axis3 = act_l_left, act_l_right\njoy[1,4]axis4 = act_l_up, act_l_down\njoy[1,4]axis5 = act_r_left, act_r_right\njoy[1,4]axis6 = act_r_up, act_r_down\njoy[1,4]button33 = act_a\njoy[1,4]button34 = act_b\njoy[1,4]button35 = act_x\njoy[1,4]button36 = act_y\njoy[1,4]button37 = act_ltrigger\njoy[1,4]button38 = act_rtrigger\njoy[1,4]button39 = act_lshoulder\njoy[1,4]button40 = act_rshoulder\njoy[1,4]button41 = act_left\njoy[1,4]button42 = act_right\njoy[1,4]button43 = act_up\njoy[1,4]button44 = act_down\njoy[1,4]button45 = act_select\njoy[1,4]button46 = act_start\njoy[1,4].axis_lx = 3\njoy[1,4].axis_ly = 4\njoy[1,4].axis_rx = 5\njoy[1,4].axis_ry = 6\njoy[1,4].axis_ltrigger = 7\njoy[1,4].axis_rtrigger = 8\n\n# Guitar Hero 3\n# This control scheme ignores the Wii remote inputs by default.\n# These assignments are fairly arbitrary and may change in future versions.\njoy[1,4]axis9 = act_l_left, act_l_right\njoy[1,4]axis10 = act_l_up, act_l_down\njoy[1,4]axis11 = 0, act_rshoulder\njoy[1,4]button57 = act_a\njoy[1,4]button58 = act_b\njoy[1,4]button59 = act_lshoulder\njoy[1,4]button60 = act_x\njoy[1,4]button61 = act_y\njoy[1,4]button62 = act_ltrigger\njoy[1,4]button63 = act_rtrigger\njoy[1,4]button64 = act_select\njoy[1,4]button65 = act_start\njoy[1,4].axis_lx = 9\njoy[1,4].axis_ly = 10\n\n### GameCube controllers (5 to 8) ###\n\n#  1 A\n#  2 B\n#  3 X\n#  4 Y\n#  5 L\n#  6 R\n#  7 Z\n#  8 Left\n#  9 Right\n# 10 Up\n# 11 Down\n# 12 Start/Pause\n\n# 1/2 Analog stick\n# 3/4 C stick\n# 5/6 Left/right triggers (positive only)\n\njoy[5,8]axis1 = act_l_left, act_l_right\njoy[5,8]axis2 = act_l_up, act_l_down\njoy[5,8]axis3 = act_r_left, act_r_right\njoy[5,8]axis4 = act_r_up, act_r_down\njoy[5,8]button1 = act_a\njoy[5,8]button2 = act_b\njoy[5,8]button3 = act_x\njoy[5,8]button4 = act_y\njoy[5,8]button5 = act_ltrigger\njoy[5,8]button6 = act_rtrigger\njoy[5,8]button7 = act_lshoulder\njoy[5,8]button8 = act_left\njoy[5,8]button9 = act_right\njoy[5,8]button10 = act_up\njoy[5,8]button11 = act_down\njoy[5,8]button12 = act_start\njoy[5,8].axis_lx = 1\njoy[5,8].axis_ly = 2\njoy[5,8].axis_rx = 3\njoy[5,8].axis_ry = 4\njoy[5,8].axis_ltrigger = 5\njoy[5,8].axis_rtrigger = 6\n"
  },
  {
    "path": "arch/wii/pad.config.sdl",
    "content": "### Wiimotes (1 to 4) ###\n\n# The mappings for SDL Wii work different than the normal MZX Wii mappings.\n#\n# Button      None  Nunchuk  Classic  \n# ------------------------------------\n# A           1     -        -\n# B           2     -        -\n# 1           3     -        -\n# 2           4     -        -\n# Minus       5     -        -\n# Plus        6     -        -\n# Home        7     -        -\n#\n# Z           -     8        -\n# C           -     9        -\n#\n# a           -     -        10\n# b           -     -        11\n# x           -     -        12\n# y           -     -        13\n# L           -     -        14\n# R           -     -        15\n# ZL          -     -        16\n# ZR          -     -        17\n# Minus       -     -        18\n# Plus        -     -        19\n# Home        -     -        20\n#\n# Axes:\n# 1/2 Nunchuk\n# 1/2 Classic Left stick\n# 3/4 Classic Right stick\n# 5/6 Classic L/R (positive values only)\n#\n# When the Classic Controller is plugged in, the hat is the Classic Controller d-pad.\n# Otherwise, the hat is the Wii Remote d-pad.\n\n# General\njoy1hat = 273, 274, 276, 275\njoy1axis1 = 276, 275\njoy1axis2 = 273, 274\n\n# Wii Remote\njoy1button1 = 284    # A     -> F3 (load world/save game)\njoy1button2 = 285    # B     -> F4 (load saved game)\njoy1button3 = 13     # 1     -> Enter\njoy1button4 = 32     # 2     -> Space\njoy1button5 = 283    # Minus -> F2 (settings)\njoy1button6 = 286    # Plus  -> F5 (play/switch bomb type)\njoy1button7 = 27     # Home  -> Escape\n\n# Nunchuk\njoy1button8 = 32     # Z     -> Space\njoy1button9 = 127    # C     -> Delete\n\n# Classic Controller\njoy1axis3 = 276, 275\njoy1axis4 = 273, 274\njoy1button10 = 13    # a     -> Enter\njoy1button11 = 32    # b     -> Space\njoy1button12 = 115   # x     -> S\njoy1button13 = 127   # y     -> Delete\njoy1button14 = 284   # L     -> F3 (load world/save game)\njoy1button15 = 285   # R     -> F4 (load saved game)\n#joy1button16 =      # ZL    -> unmapped\n#joy1button17 =      # ZR    -> unmapped\njoy1button18 = 283   # Minus -> F2 (settings)\njoy1button19 = 286   # Plus  -> F5 (play/switch bomb type)\njoy1button20 = 27    # Home  -> Escape\n\n### GameCube controllers (5 to 8) ###\n\n#  1 A\n#  2 B\n#  3 X\n#  4 Y\n#  5 Z\n#  6 L\n#  7 R\n#  8 Start\n\n# 1/2 Analog stick\n# 3/4 C stick\n# 5/6 L/R (positive values only)\n\njoy5axis1 = 276, 275\njoy5axis2 = 273, 274\njoy5axis3 = 276, 275\njoy5axis4 = 273, 274\njoy5button1 = 32     # A     -> Space\njoy5button2 = 13     # B     -> Enter\njoy5button3 = 115    # X     -> S\njoy5button4 = 127    # Y     -> Delete\njoy5button5 = 27     # Z     -> Escape\njoy5button6 = 284    # L     -> F3 (load world/save)\njoy5button7 = 285    # R     -> F4 (load save)\njoy5button8 = 286    # Start -> F5 (play/switch bomb type)\n"
  },
  {
    "path": "arch/wii/pad.config.sdl2",
    "content": "# Wii Remote (+ nunchuck or classic)\n#\n# Axis1:   -left +right (left stick, nunchuck stick)\n# Axis2:   -up   +down\n# Axis3:   -left +right (right stick)\n# Axis4:   -up   +down\n#\n# As of 2024-01-31, no axes are assigned to the triggers for some reason.\n#\n# Button1: A\n# Button2: B\n# Button3: 1\n# Button4: 2\n# Button5: -\n# Button6: +\n# Button7: Home\n# Button8: Z (nunchuck)\n# Button9: C (nunchuck)\n# Button10: X\n# Button11: Y\n# Button12: L\n# Button13: R\n# Button14: ZL\n# Button15: ZR\n# Hat1: dpad\n\n# Gamecube controller\n#\n# Axis1:   -left +right (left stick, nunchuck stick)\n# Axis2:   -up   +down\n# Axis3:   -left +right (right stick)\n# Axis4:   -up   +down\n# Axis5:   +L\n# Axis6:   +R\n#\n# Button1: A\n# Button2: B\n# Button3: X\n# Button4: Y\n# Button5: L\n# Button6: R\n# Button7: Z\n# Button8: Start\n# Hat1: dpad\n\n# You might need to take the mappings printed in stdout.txt and modify them\n# to suit you:\n\n#gamepad_add = 050000007e0500000305000001000000,Wiimote 0 + Classic,a:b1,b:b0,x:b10,y:b9,back:b4,guide:b6,start:b5,leftshoulder:b13,rightshoulder:b14,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b11,righttrigger:b12,\n\n# Because this build uses SDL, MZX can generate mappings automatically with\n# the SDL gamepad API. However, this API is based on XInput and has\n# the face buttons inverted from where they'd be expected on a Nintendo system.\n# Uncommenting this \"fixes\" the classic controller but breaks gamecube controllers.\n\n#gamepad.a = act_b\n#gamepad.b = act_a\n#gamepad.x = act_y\n#gamepad.y = act_x\n"
  },
  {
    "path": "arch/wii/platform.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/platform.h\"\n#undef main\n\n#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n#include <limits.h>\n#include <time.h>\n#include <unistd.h>\n#include <sys/iosupport.h>\n\n#define BOOL _BOOL\n#include <ogc/system.h>\n#include <ogc/lwp.h>\n#include <ogc/lwp_watchdog.h>\n#include <ogc/message.h> // Suppress unused BOOL warning.\n#include <fat.h>\n#undef BOOL\n\n#define STACKSIZE 8192\n\nstatic lwpq_t reset_queue;\nstatic lwp_t reset_thread;\nstatic u8 reset_stack[STACKSIZE];\n\n//void c_default_exceptionhandler(frame_context *pCtx);\n\n// Emergency exit\nstatic void *wii_reset_thread(void *dud)\n{\n  LWP_ThreadSleep(reset_queue);\n  platform_quit();\n  delay(1000);\n  //c_default_exceptionhandler(&_thr_main->context);\n  exit(0);\n  return 0;\n}\n\nstatic void reset_callback(u32 irq, void *ctx)\n{\n  static volatile int callcount = 0;\n  callcount++;\n  if(callcount > 5)\n    *(int *)NULL = 0xDEADCAFE; // Try to cause an exception if pressed 6 times\n  LWP_ThreadSignal(reset_queue);\n}\n\nstatic u64 timebase_offset;\n\nvoid delay(uint32_t ms)\n{\n  // TODO use nanosleep instead?\n  usleep(ms * 1000);\n}\n\nuint64_t get_ticks(void)\n{\n  return ticks_to_millisecs(gettime() - timebase_offset);\n}\n\nboolean platform_init(void)\n{\n  timebase_offset = gettime();\n\n  LWP_InitQueue(&reset_queue);\n  LWP_CreateThread(&reset_thread, wii_reset_thread, NULL, reset_stack,\n   STACKSIZE, 127);\n  SYS_SetResetCallback(reset_callback);\n\n  if(!fatInitDefault())\n    return false;\n\n  return true;\n}\n\nvoid platform_quit(void)\n{\n  int i;\n\n  for(i = 0; i < STD_MAX; i++)\n    if(devoptab_list[i] && devoptab_list[i]->chdir_r)\n      fatUnmount(devoptab_list[i]->name);\n}\n\n// argc may be 0 if the loader doesn't set args\nint main(int argc, char *argv[])\n{\n  static char _argv0[] = SHAREDIR \"megazeux\";\n  static char *_argv[] = {_argv0};\n\n  if(!argc)\n    real_main(1, _argv);\n  else\n    real_main(argc, argv);\n}\n"
  },
  {
    "path": "arch/wii/render_gx.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/graphics.h\"\n#include \"../../src/render.h\"\n#include \"../../src/renderers.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <malloc.h>\n#define BOOL _BOOL\n#include <ogc/system.h>\n#include <ogc/conf.h>\n#include <ogc/cache.h>\n#include <ogc/video.h>\n#include <ogc/gx.h>\n#include <ogc/gu.h>\n#undef BOOL\n\n#define DEFAULT_FIFO_SIZE (1024 * 1024)\n\n#define TEX_SCALE_W 1024\n#define TEX_SCALE_H 512\n\n#define SCALE_TEX_SIZE (sizeof(u32) * TEX_SCALE_W * TEX_SCALE_H)\n#define SCALE_TEX_X0 (0.0 / TEX_SCALE_W)\n#define SCALE_TEX_Y0 (1.0 / TEX_SCALE_H)\n#define SCALE_TEX_X1 (640.0 / TEX_SCALE_W)\n#define SCALE_TEX_Y1 (351.0 / TEX_SCALE_H)\n\n/**\n * Char packing format (I4)\n *\n * 1) Two versions of each char are stored in the texture (MZX and SMZX).\n * 2) Chars are packed into 8x8 tiles, meaning they need to be split into\n *    two tiles when drawn to the charset texture. This also means that in\n *    this texture they occupy 8x16 pixels rather than the usual 8x14 pixels.\n */\n\n#define CHAR_VARIANTS 2\n#define CHAR_VAR_W (CHAR_VARIANTS * 8)\n#define CHAR_VAR_H 16\n#define SMZX_OFFSET CHAR_W\n\n#define CHARSET_COLS 64\n#define CHARSET_ROWS (FULL_CHARSET_SIZE / CHARSET_COLS)\n\n#define TEX_DATA_W (CHARSET_COLS * CHAR_VAR_W)\n#define TEX_DATA_H (CHARSET_ROWS * CHAR_VAR_H)\n\n#define TEX_DATA_W_F (TEX_DATA_W * 1.0f)\n#define TEX_DATA_H_F (TEX_DATA_H * 1.0f)\n\n#define TEX_DATA_SIZE \\\n (sizeof(u32) * CHAR_VAR_H * CHAR_VARIANTS * FULL_CHARSET_SIZE)\n\n// (likely wasteful) tlut breakdown:\n#define TLUT_MZX_OFFSET   (256 * 0) // Standard MZX\n#define TLUT_SMZX_OFFSET  (256 * 1) // SMZX (separate because of SMZX_MESSAGE)\n#define TLUT_T0_OFFSET    (256 * 2) // MZX/UI/SMZX tcol is idx 0\n#define TLUT_T1_OFFSET    (256 * 3) // MZX/UI/SMZX tcol is idx 1\n#define TLUT_T2_OFFSET    (256 * 4) // SMZX tcol is idx 2 / MZX fg + UI bg\n#define TLUT_T3_OFFSET    (256 * 5) // SMZX tcol is idx 3 / MZX bg + UI fg\n#define TLUT_UI_OFFSET    (256 * 6) // UI\n#define NUM_TLUT          (256 * 7)\n\n#define TLUT_MZX_BOTH_TRANSPARENT (-1)\n\n// RGB5A3 transparent color for layer rendering.\n#define NO_COLOR 0x0000\n\n// Must be multiple of 32 bytes\nstruct ci4tlut\n{\n  u16 pal[16];\n};\n\nstruct gx_render_data\n{\n  void *xfb[2];\n  void *fifo;\n  GXRModeObj *rmode;\n  struct ci4tlut *mzxtlut;\n  void *charimg;\n  void *scaleimg;\n  GXTlutObj mzxtlutobj[NUM_TLUT];\n  GXTexObj chartex;\n  GXTexObj scaletex;\n  GXColor palette[FULL_PAL_SIZE];\n  u16 tlutpal[FULL_PAL_SIZE];\n  u8 chrdirty[FULL_CHARSET_SIZE];\n  int chrdirty_all;\n  int chrdirty_set;\n  int paldirty;\n  boolean invalidate;\n  int current_xfb;\n  s16 sx0, sy0, sx1, sy1;\n};\n\n// Lookup table for generating CI4 charset textures\nstatic u32 mzxtexline[256] =\n{\n  0x00000000, 0x0000000F, 0x000000F0, 0x000000FF,\n  0x00000F00, 0x00000F0F, 0x00000FF0, 0x00000FFF,\n  0x0000F000, 0x0000F00F, 0x0000F0F0, 0x0000F0FF,\n  0x0000FF00, 0x0000FF0F, 0x0000FFF0, 0x0000FFFF,\n  0x000F0000, 0x000F000F, 0x000F00F0, 0x000F00FF,\n  0x000F0F00, 0x000F0F0F, 0x000F0FF0, 0x000F0FFF,\n  0x000FF000, 0x000FF00F, 0x000FF0F0, 0x000FF0FF,\n  0x000FFF00, 0x000FFF0F, 0x000FFFF0, 0x000FFFFF,\n  0x00F00000, 0x00F0000F, 0x00F000F0, 0x00F000FF,\n  0x00F00F00, 0x00F00F0F, 0x00F00FF0, 0x00F00FFF,\n  0x00F0F000, 0x00F0F00F, 0x00F0F0F0, 0x00F0F0FF,\n  0x00F0FF00, 0x00F0FF0F, 0x00F0FFF0, 0x00F0FFFF,\n  0x00FF0000, 0x00FF000F, 0x00FF00F0, 0x00FF00FF,\n  0x00FF0F00, 0x00FF0F0F, 0x00FF0FF0, 0x00FF0FFF,\n  0x00FFF000, 0x00FFF00F, 0x00FFF0F0, 0x00FFF0FF,\n  0x00FFFF00, 0x00FFFF0F, 0x00FFFFF0, 0x00FFFFFF,\n  0x0F000000, 0x0F00000F, 0x0F0000F0, 0x0F0000FF,\n  0x0F000F00, 0x0F000F0F, 0x0F000FF0, 0x0F000FFF,\n  0x0F00F000, 0x0F00F00F, 0x0F00F0F0, 0x0F00F0FF,\n  0x0F00FF00, 0x0F00FF0F, 0x0F00FFF0, 0x0F00FFFF,\n  0x0F0F0000, 0x0F0F000F, 0x0F0F00F0, 0x0F0F00FF,\n  0x0F0F0F00, 0x0F0F0F0F, 0x0F0F0FF0, 0x0F0F0FFF,\n  0x0F0FF000, 0x0F0FF00F, 0x0F0FF0F0, 0x0F0FF0FF,\n  0x0F0FFF00, 0x0F0FFF0F, 0x0F0FFFF0, 0x0F0FFFFF,\n  0x0FF00000, 0x0FF0000F, 0x0FF000F0, 0x0FF000FF,\n  0x0FF00F00, 0x0FF00F0F, 0x0FF00FF0, 0x0FF00FFF,\n  0x0FF0F000, 0x0FF0F00F, 0x0FF0F0F0, 0x0FF0F0FF,\n  0x0FF0FF00, 0x0FF0FF0F, 0x0FF0FFF0, 0x0FF0FFFF,\n  0x0FFF0000, 0x0FFF000F, 0x0FFF00F0, 0x0FFF00FF,\n  0x0FFF0F00, 0x0FFF0F0F, 0x0FFF0FF0, 0x0FFF0FFF,\n  0x0FFFF000, 0x0FFFF00F, 0x0FFFF0F0, 0x0FFFF0FF,\n  0x0FFFFF00, 0x0FFFFF0F, 0x0FFFFFF0, 0x0FFFFFFF,\n  0xF0000000, 0xF000000F, 0xF00000F0, 0xF00000FF,\n  0xF0000F00, 0xF0000F0F, 0xF0000FF0, 0xF0000FFF,\n  0xF000F000, 0xF000F00F, 0xF000F0F0, 0xF000F0FF,\n  0xF000FF00, 0xF000FF0F, 0xF000FFF0, 0xF000FFFF,\n  0xF00F0000, 0xF00F000F, 0xF00F00F0, 0xF00F00FF,\n  0xF00F0F00, 0xF00F0F0F, 0xF00F0FF0, 0xF00F0FFF,\n  0xF00FF000, 0xF00FF00F, 0xF00FF0F0, 0xF00FF0FF,\n  0xF00FFF00, 0xF00FFF0F, 0xF00FFFF0, 0xF00FFFFF,\n  0xF0F00000, 0xF0F0000F, 0xF0F000F0, 0xF0F000FF,\n  0xF0F00F00, 0xF0F00F0F, 0xF0F00FF0, 0xF0F00FFF,\n  0xF0F0F000, 0xF0F0F00F, 0xF0F0F0F0, 0xF0F0F0FF,\n  0xF0F0FF00, 0xF0F0FF0F, 0xF0F0FFF0, 0xF0F0FFFF,\n  0xF0FF0000, 0xF0FF000F, 0xF0FF00F0, 0xF0FF00FF,\n  0xF0FF0F00, 0xF0FF0F0F, 0xF0FF0FF0, 0xF0FF0FFF,\n  0xF0FFF000, 0xF0FFF00F, 0xF0FFF0F0, 0xF0FFF0FF,\n  0xF0FFFF00, 0xF0FFFF0F, 0xF0FFFFF0, 0xF0FFFFFF,\n  0xFF000000, 0xFF00000F, 0xFF0000F0, 0xFF0000FF,\n  0xFF000F00, 0xFF000F0F, 0xFF000FF0, 0xFF000FFF,\n  0xFF00F000, 0xFF00F00F, 0xFF00F0F0, 0xFF00F0FF,\n  0xFF00FF00, 0xFF00FF0F, 0xFF00FFF0, 0xFF00FFFF,\n  0xFF0F0000, 0xFF0F000F, 0xFF0F00F0, 0xFF0F00FF,\n  0xFF0F0F00, 0xFF0F0F0F, 0xFF0F0FF0, 0xFF0F0FFF,\n  0xFF0FF000, 0xFF0FF00F, 0xFF0FF0F0, 0xFF0FF0FF,\n  0xFF0FFF00, 0xFF0FFF0F, 0xFF0FFFF0, 0xFF0FFFFF,\n  0xFFF00000, 0xFFF0000F, 0xFFF000F0, 0xFFF000FF,\n  0xFFF00F00, 0xFFF00F0F, 0xFFF00FF0, 0xFFF00FFF,\n  0xFFF0F000, 0xFFF0F00F, 0xFFF0F0F0, 0xFFF0F0FF,\n  0xFFF0FF00, 0xFFF0FF0F, 0xFFF0FFF0, 0xFFF0FFFF,\n  0xFFFF0000, 0xFFFF000F, 0xFFFF00F0, 0xFFFF00FF,\n  0xFFFF0F00, 0xFFFF0F0F, 0xFFFF0FF0, 0xFFFF0FFF,\n  0xFFFFF000, 0xFFFFF00F, 0xFFFFF0F0, 0xFFFFF0FF,\n  0xFFFFFF00, 0xFFFFFF0F, 0xFFFFFFF0, 0xFFFFFFFF\n};\n\n// Lookup table for generating CI4 SMZX charset textures\nstatic u32 smzxtexline[256] =\n{\n  0x00000000, 0x00000055, 0x000000AA, 0x000000FF,\n  0x00005500, 0x00005555, 0x000055AA, 0x000055FF,\n  0x0000AA00, 0x0000AA55, 0x0000AAAA, 0x0000AAFF,\n  0x0000FF00, 0x0000FF55, 0x0000FFAA, 0x0000FFFF,\n  0x00550000, 0x00550055, 0x005500AA, 0x005500FF,\n  0x00555500, 0x00555555, 0x005555AA, 0x005555FF,\n  0x0055AA00, 0x0055AA55, 0x0055AAAA, 0x0055AAFF,\n  0x0055FF00, 0x0055FF55, 0x0055FFAA, 0x0055FFFF,\n  0x00AA0000, 0x00AA0055, 0x00AA00AA, 0x00AA00FF,\n  0x00AA5500, 0x00AA5555, 0x00AA55AA, 0x00AA55FF,\n  0x00AAAA00, 0x00AAAA55, 0x00AAAAAA, 0x00AAAAFF,\n  0x00AAFF00, 0x00AAFF55, 0x00AAFFAA, 0x00AAFFFF,\n  0x00FF0000, 0x00FF0055, 0x00FF00AA, 0x00FF00FF,\n  0x00FF5500, 0x00FF5555, 0x00FF55AA, 0x00FF55FF,\n  0x00FFAA00, 0x00FFAA55, 0x00FFAAAA, 0x00FFAAFF,\n  0x00FFFF00, 0x00FFFF55, 0x00FFFFAA, 0x00FFFFFF,\n  0x55000000, 0x55000055, 0x550000AA, 0x550000FF,\n  0x55005500, 0x55005555, 0x550055AA, 0x550055FF,\n  0x5500AA00, 0x5500AA55, 0x5500AAAA, 0x5500AAFF,\n  0x5500FF00, 0x5500FF55, 0x5500FFAA, 0x5500FFFF,\n  0x55550000, 0x55550055, 0x555500AA, 0x555500FF,\n  0x55555500, 0x55555555, 0x555555AA, 0x555555FF,\n  0x5555AA00, 0x5555AA55, 0x5555AAAA, 0x5555AAFF,\n  0x5555FF00, 0x5555FF55, 0x5555FFAA, 0x5555FFFF,\n  0x55AA0000, 0x55AA0055, 0x55AA00AA, 0x55AA00FF,\n  0x55AA5500, 0x55AA5555, 0x55AA55AA, 0x55AA55FF,\n  0x55AAAA00, 0x55AAAA55, 0x55AAAAAA, 0x55AAAAFF,\n  0x55AAFF00, 0x55AAFF55, 0x55AAFFAA, 0x55AAFFFF,\n  0x55FF0000, 0x55FF0055, 0x55FF00AA, 0x55FF00FF,\n  0x55FF5500, 0x55FF5555, 0x55FF55AA, 0x55FF55FF,\n  0x55FFAA00, 0x55FFAA55, 0x55FFAAAA, 0x55FFAAFF,\n  0x55FFFF00, 0x55FFFF55, 0x55FFFFAA, 0x55FFFFFF,\n  0xAA000000, 0xAA000055, 0xAA0000AA, 0xAA0000FF,\n  0xAA005500, 0xAA005555, 0xAA0055AA, 0xAA0055FF,\n  0xAA00AA00, 0xAA00AA55, 0xAA00AAAA, 0xAA00AAFF,\n  0xAA00FF00, 0xAA00FF55, 0xAA00FFAA, 0xAA00FFFF,\n  0xAA550000, 0xAA550055, 0xAA5500AA, 0xAA5500FF,\n  0xAA555500, 0xAA555555, 0xAA5555AA, 0xAA5555FF,\n  0xAA55AA00, 0xAA55AA55, 0xAA55AAAA, 0xAA55AAFF,\n  0xAA55FF00, 0xAA55FF55, 0xAA55FFAA, 0xAA55FFFF,\n  0xAAAA0000, 0xAAAA0055, 0xAAAA00AA, 0xAAAA00FF,\n  0xAAAA5500, 0xAAAA5555, 0xAAAA55AA, 0xAAAA55FF,\n  0xAAAAAA00, 0xAAAAAA55, 0xAAAAAAAA, 0xAAAAAAFF,\n  0xAAAAFF00, 0xAAAAFF55, 0xAAAAFFAA, 0xAAAAFFFF,\n  0xAAFF0000, 0xAAFF0055, 0xAAFF00AA, 0xAAFF00FF,\n  0xAAFF5500, 0xAAFF5555, 0xAAFF55AA, 0xAAFF55FF,\n  0xAAFFAA00, 0xAAFFAA55, 0xAAFFAAAA, 0xAAFFAAFF,\n  0xAAFFFF00, 0xAAFFFF55, 0xAAFFFFAA, 0xAAFFFFFF,\n  0xFF000000, 0xFF000055, 0xFF0000AA, 0xFF0000FF,\n  0xFF005500, 0xFF005555, 0xFF0055AA, 0xFF0055FF,\n  0xFF00AA00, 0xFF00AA55, 0xFF00AAAA, 0xFF00AAFF,\n  0xFF00FF00, 0xFF00FF55, 0xFF00FFAA, 0xFF00FFFF,\n  0xFF550000, 0xFF550055, 0xFF5500AA, 0xFF5500FF,\n  0xFF555500, 0xFF555555, 0xFF5555AA, 0xFF5555FF,\n  0xFF55AA00, 0xFF55AA55, 0xFF55AAAA, 0xFF55AAFF,\n  0xFF55FF00, 0xFF55FF55, 0xFF55FFAA, 0xFF55FFFF,\n  0xFFAA0000, 0xFFAA0055, 0xFFAA00AA, 0xFFAA00FF,\n  0xFFAA5500, 0xFFAA5555, 0xFFAA55AA, 0xFFAA55FF,\n  0xFFAAAA00, 0xFFAAAA55, 0xFFAAAAAA, 0xFFAAAAFF,\n  0xFFAAFF00, 0xFFAAFF55, 0xFFAAFFAA, 0xFFAAFFFF,\n  0xFFFF0000, 0xFFFF0055, 0xFFFF00AA, 0xFFFF00FF,\n  0xFFFF5500, 0xFFFF5555, 0xFFFF55AA, 0xFFFF55FF,\n  0xFFFFAA00, 0xFFFFAA55, 0xFFFFAAAA, 0xFFFFAAFF,\n  0xFFFFFF00, 0xFFFFFF55, 0xFFFFFFAA, 0xFFFFFFFF\n};\n\nstatic boolean gx_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  const GXColor black = {0, 0, 0, 255};\n\n  struct gx_render_data *render_data;\n  f32 yscale;\n  u32 xfbHeight;\n  Mtx identmtx;\n  GXRModeObj *rmode;\n  Mtx44 projmtx;\n  u16 sw, sh;\n  int i;\n\n  graphics->resolution_width = 640;\n  graphics->resolution_height = 350;\n  graphics->window_width = 640;\n  graphics->window_height = 350;\n\n  render_data = cmalloc(sizeof(struct gx_render_data));\n  graphics->render_data = render_data;\n  graphics->ratio = conf->video_ratio;\n  graphics->gl_vsync = conf->gl_vsync;\n  graphics->bits_per_pixel = 16;\n\n  VIDEO_Init();\n\n  rmode = render_data->rmode = VIDEO_GetPreferredMode(NULL);\n  render_data->xfb[0] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));\n  render_data->xfb[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));\n  render_data->current_xfb = 0;\n\n  render_data->charimg = memalign(32, TEX_DATA_SIZE);\n  memset(render_data->charimg, 0, TEX_DATA_SIZE);\n  render_data->scaleimg = memalign(32, SCALE_TEX_SIZE);\n  memset(render_data->scaleimg, 0, SCALE_TEX_SIZE);\n  render_data->mzxtlut = memalign(32, sizeof(struct ci4tlut) * NUM_TLUT);\n  memset(render_data->mzxtlut, 0, sizeof(struct ci4tlut) * NUM_TLUT);\n\n  for(i = 0; i < NUM_TLUT; i++)\n    GX_InitTlutObj(render_data->mzxtlutobj + i, render_data->mzxtlut + i,\n     GX_TL_RGB5A3, 16);\n\n  GX_InitTexObjCI(&render_data->chartex,\n   render_data->charimg, TEX_DATA_W, TEX_DATA_H,\n   GX_TF_CI4, GX_REPEAT, GX_REPEAT, GX_FALSE, GX_TLUT0);\n  GX_InitTexObjLOD(&render_data->chartex, GX_NEAR, GX_NEAR, 0, 0, 0, GX_FALSE,\n   GX_TRUE, GX_ANISO_1);\n\n  GX_InitTexObj(&render_data->scaletex,\n   render_data->scaleimg, TEX_SCALE_W, TEX_SCALE_H,\n   GX_TF_RGBA8, GX_REPEAT, GX_REPEAT, GX_FALSE);\n\n  VIDEO_Configure(rmode);\n  VIDEO_SetNextFramebuffer(render_data->xfb[0]);\n  VIDEO_SetBlack(FALSE);\n  VIDEO_Flush();\n  VIDEO_WaitVSync();\n  if(rmode->viTVMode & VI_NON_INTERLACE)\n    VIDEO_WaitVSync();\n\n  render_data->fifo = memalign(32, DEFAULT_FIFO_SIZE);\n  memset(render_data->fifo, 0, DEFAULT_FIFO_SIZE);\n  GX_Init(render_data->fifo, DEFAULT_FIFO_SIZE);\n\n  yscale = GX_GetYScaleFactor(rmode->efbHeight, rmode->xfbHeight);\n  xfbHeight = GX_SetDispCopyYScale(yscale);\n  sw = ((rmode->fbWidth > 640) ? rmode->fbWidth : 640);\n  sh = ((rmode->efbHeight > 352) ? rmode->efbHeight : 352);\n  GX_SetScissor(0, 0, sw, sh);\n  GX_SetDispCopySrc(0, 0, rmode->fbWidth, rmode->efbHeight);\n  GX_SetDispCopyDst(rmode->fbWidth, xfbHeight);\n  GX_SetCopyFilter(rmode->aa, rmode->sample_pattern, GX_TRUE, rmode->vfilter);\n  GX_SetFieldMode(rmode->field_rendering,\n   ((rmode->viHeight == 2 * rmode->xfbHeight) ? GX_ENABLE : GX_DISABLE));\n  if(rmode->aa)\n    GX_SetPixelFmt(GX_PF_RGB565_Z16, GX_ZC_LINEAR);\n  else\n    GX_SetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR);\n  GX_SetDispCopyGamma(GX_GM_1_0);\n  GX_SetCopyClear(black, GX_MAX_Z24);\n  GX_SetTexCopySrc(0, 0, 640, 352);\n  GX_SetTexCopyDst(1024, 512, GX_TF_RGBA8, GX_FALSE);\n\n  GX_ClearVtxDesc();\n  GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);\n  GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);\n\n  GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);\n  GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);\n\n  GX_SetNumChans(1);\n  GX_SetNumTexGens(1);\n  GX_SetChanCtrl(GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHTNULL,\n   GX_DF_NONE, GX_AF_NONE);\n  GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);\n  GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);\n  GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);\n\n  guMtxIdentity(identmtx);\n  GX_LoadPosMtxImm(identmtx, GX_PNMTX0);\n\n  GX_SetViewport(0, 0, sw, sh, 0, 1);\n  GX_SetAlphaUpdate(GX_TRUE);\n  GX_SetCullMode(GX_CULL_NONE);\n\n  GX_CopyDisp(render_data->xfb[1], GX_TRUE);\n  GX_CopyDisp(render_data->xfb[0], GX_FALSE);\n  GX_Flush();\n\n  render_data->chrdirty_set = 1;\n  render_data->chrdirty_all = 1;\n  render_data->paldirty = 1;\n\n  guOrtho(projmtx, -1, sh - 1, 0, sw, -1.0, 1.0);\n  GX_LoadProjectionMtx(projmtx, GX_ORTHOGRAPHIC);\n  return true;\n}\n\nstatic void gx_free_video(struct graphics_data *graphics)\n{\n  free(graphics->render_data);\n  graphics->render_data = NULL;\n}\n\nstatic boolean gx_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  float x, y, w, h, scale, xscale, yscale;\n  int fh;\n\n  if(window->is_fullscreen)\n    scale = 8.0 / 9.0;\n  else\n    scale = 8.0 / 10.0;\n\n  if(render_data->rmode->viTVMode == VI_TVMODE_PAL_INT)\n    fh = 574;\n  else\n    fh = 480;\n\n  if(CONF_GetAspectRatio() == CONF_ASPECT_16_9)\n  {\n    // 4:3 stretched to 16:9; correct ratio for non-square pixels.\n    window->ratio_numerator *= 3;\n    window->ratio_denominator *= 4;\n  }\n\n  window->width_px = 720;\n  window->height_px = fh;\n  video_window_update_viewport(window);\n\n  if(render_data->rmode->viWidth > render_data->rmode->fbWidth)\n    xscale = (float)render_data->rmode->fbWidth / render_data->rmode->viWidth;\n  else\n    xscale = 1.0;\n  yscale = (float)render_data->rmode->efbHeight / render_data->rmode->viHeight;\n\n  w = window->viewport_width * scale;\n  h = window->viewport_height * scale;\n  x = (window->width_px - w) / 2 - render_data->rmode->viXOrigin;\n  y = (window->height_px - h) / 2 - render_data->rmode->viYOrigin;\n  w *= xscale; h *= yscale; x *= xscale; y *= yscale;\n\n  render_data->sx0 = x;\n  render_data->sy0 = y + 1;\n  render_data->sx1 = x + w;\n  render_data->sy1 = y + h + 1;\n\n  return true;\n}\n\nstatic void gx_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned int count)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  unsigned int i;\n\n  render_data->paldirty = 1;\n  for(i = 0; i < count; i++)\n  {\n    // 32-bit color palette (used for cursor)\n    render_data->palette[i].r = palette[i].r;\n    render_data->palette[i].g = palette[i].g;\n    render_data->palette[i].b = palette[i].b;\n    render_data->palette[i].a = 255;\n\n    // RGB5A3 (1 RRRRR GGGGG BBBBB) or (0 RRRR GGGG BBBB AAA)\n    render_data->tlutpal[i] = 0x8000 |\n     (palette[i].b >> 3)             |\n     ((palette[i].g & 0xF8) << 2)    |\n     ((palette[i].r & 0xF8) << 7);\n  }\n}\n\nstatic void gx_remap_char_range(struct graphics_data *graphics, uint16_t first,\n uint16_t count)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n\n  if(first + count > FULL_CHARSET_SIZE)\n    count = FULL_CHARSET_SIZE - first;\n\n  if(count <= 256)\n    memset(render_data->chrdirty + first, 1, count);\n\n  else\n    render_data->chrdirty_all = 1;\n\n  render_data->chrdirty_set = 1;\n}\n\nstatic void gx_remap_char(struct graphics_data *graphics, uint16_t chr)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  render_data->chrdirty[chr] = 1;\n  render_data->chrdirty_set = 1;\n}\n\nstatic void gx_remap_charbyte(struct graphics_data *graphics,\n uint16_t chr, uint8_t byte)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  render_data->chrdirty[chr] = 1;\n  render_data->chrdirty_set = 1;\n}\n\n/**\n * Draw a single char (MZX and SMZX) to the charset texture.\n * Because the texture is stored in 8x8 blocks, we need to draw the first\n * eight lines of the char, then skip ahead to the next row in the texture.\n */\n\nstatic void gx_draw_char(struct graphics_data *graphics, uint16_t chr)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  uint8_t *src = graphics->charset;\n  u32 *dest = render_data->charimg;\n  int byte;\n\n  src += chr * CHAR_SIZE;\n\n  dest += (chr / CHARSET_COLS) * CHAR_VAR_W * CHARSET_COLS * 2;\n  dest += (chr % CHARSET_COLS) * CHAR_VAR_W;\n\n  for(byte = 0; byte < 8; byte++, src++, dest++)\n    *dest = mzxtexline[*src];\n\n  src -= 8;\n  for(byte = 0; byte < 8; byte++, src++, dest++)\n    *dest = smzxtexline[*src];\n\n  dest += (CHARSET_COLS - 1) * CHAR_VAR_W;\n\n  for(byte = 0; byte < 6; byte++, src++, dest++)\n    *dest = mzxtexline[*src];\n\n  src -= 6;\n  dest += 2;\n  for(byte = 0; byte < 6; byte++, src++, dest++)\n    *dest = smzxtexline[*src];\n}\n\n/**\n * Redraw the entire charset texture.\n * Because chars are stored in 8x8 blocks, the first 8 lines of each char in\n * a row need to be drawn and then the last 6 lines need to be drawn in the\n * next row of 8x8 blocks.\n */\n\nstatic void gx_draw_charsets(struct graphics_data *graphics)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  uint8_t *src = graphics->charset;\n  u32 *dest = render_data->charimg;\n  int x;\n  int y;\n  int byte;\n\n  for(y = 0; y < CHARSET_ROWS; y++)\n  {\n    for(x = 0; x < CHARSET_COLS; x++)\n    {\n      for(byte = 0; byte < 8; byte++, src++, dest++)\n        *dest = mzxtexline[*src];\n\n      src -= 8;\n      for(byte = 0; byte < 8; byte++, src++, dest++)\n        *dest = smzxtexline[*src];\n\n      src += 6;\n    }\n\n    src -= CHARSET_COLS * CHAR_SIZE;\n\n    for(x = 0; x < CHARSET_COLS; x++)\n    {\n      src += 8;\n      for(byte = 0; byte < 6; byte++, src++, dest++)\n        *dest = mzxtexline[*src];\n\n      src -= 6;\n      dest += 2;\n      for(byte = 0; byte < 6; byte++, src++, dest++)\n        *dest = smzxtexline[*src];\n\n      dest += 2;\n    }\n  }\n}\n\nstatic void gx_check_remap_chars(struct graphics_data *graphics)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  int i;\n\n  if(!render_data->chrdirty_set)\n    return;\n\n  if(render_data->chrdirty_all)\n  {\n    gx_draw_charsets(graphics);\n    render_data->chrdirty_all = false;\n    memset(render_data->chrdirty, 0, FULL_CHARSET_SIZE);\n    render_data->invalidate = true;\n  }\n  else\n  {\n    for(i = 0; i < FULL_CHARSET_SIZE; i++)\n    {\n      if(render_data->chrdirty[i])\n      {\n        gx_draw_char(graphics, i);\n        render_data->chrdirty[i] = 0;\n        render_data->invalidate = true;\n      }\n    }\n  }\n\n  render_data->chrdirty_set = 0;\n}\n\nstatic void gx_check_remap_palettes(struct graphics_data *graphics)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  int i;\n\n  if(render_data->paldirty)\n  {\n    for(i = 0; i < NUM_TLUT; i++)\n      render_data->mzxtlut[i].pal[1] = 0;\n\n    render_data->paldirty = 0;\n    render_data->invalidate = true;\n  }\n}\n\nstatic int gx_get_tlut_id_mzx(struct graphics_data *graphics,\n struct video_layer *layer, uint8_t bg_color, uint8_t fg_color)\n{\n  int tcol = layer->transparent_col;\n  int tlut_id;\n\n  if((tcol == (int)bg_color) && (tcol == (int)fg_color))\n    return TLUT_MZX_BOTH_TRANSPARENT;\n\n  if(tcol == (int)bg_color)\n    return fg_color + TLUT_T0_OFFSET;\n\n  if(tcol == (int)fg_color)\n    return bg_color + TLUT_T1_OFFSET;\n\n  tlut_id = ((bg_color & 0xF) << 4) | (fg_color & 0xF);\n\n  if(bg_color >= 16 && fg_color >= 16)\n    return tlut_id + TLUT_UI_OFFSET;\n\n  // Mixed UI/game colors only occur during normal MZX mode, so it should\n  // be safe to reuse the SMZX TCOL mappings for these.\n  else\n  if(bg_color >= 16)\n    return tlut_id + TLUT_T2_OFFSET;\n\n  else\n  if(fg_color >= 16)\n    return tlut_id + TLUT_T3_OFFSET;\n\n  return tlut_id + TLUT_MZX_OFFSET;\n}\n\nstatic int gx_get_tlut_id_smzx(struct graphics_data *graphics,\n struct video_layer *layer, uint8_t bg_color, uint8_t fg_color)\n{\n  int palette_id = ((bg_color & 0xF) << 4) | (fg_color & 0xF);\n  int idx0 = graphics->smzx_indices[palette_id * 4 + 0];\n  int idx1 = graphics->smzx_indices[palette_id * 4 + 1];\n  int idx2 = graphics->smzx_indices[palette_id * 4 + 2];\n  int idx3 = graphics->smzx_indices[palette_id * 4 + 3];\n  int tcol = layer->transparent_col;\n\n  if(tcol == idx0)\n    return palette_id + TLUT_T0_OFFSET;\n\n  if(tcol == idx1)\n    return palette_id + TLUT_T1_OFFSET;\n\n  if(tcol == idx2)\n    return palette_id + TLUT_T2_OFFSET;\n\n  if(tcol == idx3)\n    return palette_id + TLUT_T3_OFFSET;\n\n  return palette_id + TLUT_SMZX_OFFSET;\n}\n\nstatic void gx_set_tlut_mzx(struct graphics_data *graphics,\n struct video_layer *layer, struct ci4tlut *tlut, int bg_color, int fg_color)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  int tcol = layer->transparent_col;\n\n  if(bg_color >= 16)\n    bg_color = graphics->protected_pal_position + (bg_color & 0xF);\n  else\n    bg_color &= 0xF;\n\n  if(fg_color >= 16)\n    fg_color = graphics->protected_pal_position + (fg_color & 0xF);\n  else\n    fg_color &= 0xF;\n\n  tlut->pal[0] =  bg_color != tcol ? render_data->tlutpal[bg_color] : NO_COLOR;\n  tlut->pal[15] = fg_color != tcol ? render_data->tlutpal[fg_color] : NO_COLOR;\n  tlut->pal[1] = 0xFFFF;\n}\n\nstatic void gx_set_tlut_smzx(struct graphics_data *graphics,\n struct video_layer *layer, struct ci4tlut *tlut, int bg_color, int fg_color)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  int palette_id = ((bg_color & 0xF) << 4) | (fg_color & 0xF);\n  int idx0 = graphics->smzx_indices[palette_id * 4 + 0];\n  int idx1 = graphics->smzx_indices[palette_id * 4 + 1];\n  int idx2 = graphics->smzx_indices[palette_id * 4 + 2];\n  int idx3 = graphics->smzx_indices[palette_id * 4 + 3];\n  int tcol = layer->transparent_col;\n\n  tlut->pal[0] =  idx0 != tcol ? render_data->tlutpal[idx0] : NO_COLOR;\n  tlut->pal[5] =  idx1 != tcol ? render_data->tlutpal[idx1] : NO_COLOR;\n  tlut->pal[10] = idx2 != tcol ? render_data->tlutpal[idx2] : NO_COLOR;\n  tlut->pal[15] = idx3 != tcol ? render_data->tlutpal[idx3] : NO_COLOR;\n  tlut->pal[1] = 0xFFFF;\n}\n\n\nstatic uint16_t gx_get_char_value(struct video_layer *layer, uint16_t char_value)\n{\n  if(char_value == INVISIBLE_CHAR)\n    return INVISIBLE_CHAR;\n\n  if(char_value > 0xFF)\n    return (char_value & 0xFF) + PROTECTED_CHARSET_POSITION;\n\n  return (layer->offset + char_value) % PROTECTED_CHARSET_POSITION;\n}\n\nstatic void gx_render_layer(struct graphics_data *graphics,\n struct video_layer *layer)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  struct char_element *src = layer->data;\n  struct ci4tlut *tlut;\n  int last_tlut_id;\n  int cur_tlut_id;\n  uint16_t char_value;\n  uint8_t bg_color;\n  uint8_t fg_color;\n\n  unsigned int x, y;\n  int x1, x2, y1, y2;\n  float u, v;\n  float u2, v2;\n\n  render_data->invalidate = false;\n\n  gx_check_remap_chars(graphics);\n\n  if(render_data->invalidate)\n  {\n    DCFlushRange(render_data->charimg, TEX_DATA_SIZE);\n    GX_InvalidateTexAll();\n    render_data->invalidate = false;\n  }\n\n  gx_check_remap_palettes(graphics);\n\n  if(render_data->invalidate)\n    GX_InvalidateTexAll();\n\n  GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);\n\n  GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE);\n\n  last_tlut_id = -1;\n\n  if(!layer->mode)\n  {\n    for(y = 0; y < layer->h; y++)\n    {\n      for(x = 0; x < layer->w; x++, src++)\n      {\n        char_value = gx_get_char_value(layer, src->char_value);\n        if(char_value == INVISIBLE_CHAR)\n          continue;\n\n        bg_color = src->bg_color;\n        fg_color = src->fg_color;\n        cur_tlut_id = gx_get_tlut_id_mzx(graphics, layer, bg_color, fg_color);\n\n        if(cur_tlut_id == TLUT_MZX_BOTH_TRANSPARENT)\n          continue;\n\n        if(cur_tlut_id != last_tlut_id)\n        {\n          tlut = &(render_data->mzxtlut[cur_tlut_id]);\n\n          if(!tlut->pal[1])\n          {\n            gx_set_tlut_mzx(graphics, layer, tlut, bg_color, fg_color);\n            DCFlushRange(tlut, sizeof(struct ci4tlut));\n          }\n          GX_LoadTlut(&render_data->mzxtlutobj[cur_tlut_id], GX_TLUT0);\n          GX_LoadTexObj(&render_data->chartex, GX_TEXMAP0);\n          last_tlut_id = cur_tlut_id;\n        }\n\n        u = (char_value % CHARSET_COLS) * CHAR_VAR_W / TEX_DATA_W_F;\n        v = (char_value / CHARSET_COLS) * CHAR_VAR_H / TEX_DATA_H_F;\n        u2 = u + CHAR_W / TEX_DATA_W_F;\n        v2 = v + CHAR_H / TEX_DATA_H_F;\n\n        x1 = (int)x * CHAR_W + layer->x;\n        y1 = (int)y * CHAR_H + layer->y;\n        x2 = ((int)x + 1) * CHAR_W + layer->x;\n        y2 = ((int)y + 1) * CHAR_H + layer->y;\n\n        GX_Begin(GX_QUADS, GX_VTXFMT0, 4);\n          GX_Position2s16(x1, y1);\n          GX_TexCoord2f32(u,  v );\n          GX_Position2s16(x2, y1);\n          GX_TexCoord2f32(u2, v );\n          GX_Position2s16(x2, y2);\n          GX_TexCoord2f32(u2, v2);\n          GX_Position2s16(x1, y2);\n          GX_TexCoord2f32(u,  v2);\n        GX_End();\n      }\n    }\n  }\n  else\n  {\n    for(y = 0; y < layer->h; y++)\n    {\n      for(x = 0; x < layer->w; x++, src++)\n      {\n        char_value = gx_get_char_value(layer, src->char_value);\n        if(char_value == INVISIBLE_CHAR)\n          continue;\n\n        bg_color = src->bg_color;\n        fg_color = src->fg_color;\n        cur_tlut_id = gx_get_tlut_id_smzx(graphics, layer, bg_color, fg_color);\n\n        if(cur_tlut_id != last_tlut_id)\n        {\n          tlut = &(render_data->mzxtlut[cur_tlut_id]);\n\n          if(!tlut->pal[1])\n          {\n            gx_set_tlut_smzx(graphics, layer, tlut, bg_color, fg_color);\n            DCFlushRange(tlut, sizeof(struct ci4tlut));\n          }\n          GX_LoadTlut(&render_data->mzxtlutobj[cur_tlut_id], GX_TLUT0);\n          GX_LoadTexObj(&render_data->chartex, GX_TEXMAP0);\n          last_tlut_id = cur_tlut_id;\n        }\n\n        u = (char_value % CHARSET_COLS) * CHAR_VAR_W / TEX_DATA_W_F;\n        v = (char_value / CHARSET_COLS) * CHAR_VAR_H / TEX_DATA_H_F;\n        u += SMZX_OFFSET / TEX_DATA_W_F;\n        u2 = u + CHAR_W / TEX_DATA_W_F;\n        v2 = v + CHAR_H / TEX_DATA_H_F;\n\n        x1 = (int)x * CHAR_W + layer->x;\n        y1 = (int)y * CHAR_H + layer->y;\n        x2 = ((int)x + 1) * CHAR_W + layer->x;\n        y2 = ((int)y + 1) * CHAR_H + layer->y;\n\n        GX_Begin(GX_QUADS, GX_VTXFMT0, 4);\n          GX_Position2s16(x1, y1);\n          GX_TexCoord2f32(u,  v );\n          GX_Position2s16(x2, y1);\n          GX_TexCoord2f32(u2, v );\n          GX_Position2s16(x2, y2);\n          GX_TexCoord2f32(u2, v2);\n          GX_Position2s16(x1, y2);\n          GX_TexCoord2f32(u,  v2);\n        GX_End();\n      }\n    }\n  }\n\n  GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);\n}\n\nstatic void gx_render_cursor(struct graphics_data *graphics, unsigned int x,\n unsigned int y, uint16_t color, unsigned int lines, unsigned int offset)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  GXColor *pal = render_data->palette;\n\n  GX_SetChanMatColor(GX_COLOR0A0, pal[color & 0xFF]);\n  GX_Begin(GX_QUADS, GX_VTXFMT0, 4);\n    GX_Position2s16(x * 8, y * 14 + offset);\n    GX_TexCoord2f32(0, 0);\n    GX_Position2s16(x * 8 + 8, y * 14 + offset);\n    GX_TexCoord2f32(0, 0);\n    GX_Position2s16(x * 8 + 8, y * 14 + offset + lines);\n    GX_TexCoord2f32(0, 0);\n    GX_Position2s16(x * 8, y * 14 + offset + lines);\n    GX_TexCoord2f32(0, 0);\n  GX_End();\n}\n\nstatic void gx_render_mouse(struct graphics_data *graphics,\n unsigned int x, unsigned int y, unsigned int w, unsigned int h)\n{\n  const GXColor white = {255, 255, 255, 255};\n\n  GX_SetBlendMode(GX_BM_BLEND, GX_BL_INVDSTCLR, GX_BL_ZERO, GX_LO_CLEAR);\n\n  GX_SetChanMatColor(GX_COLOR0A0, white);\n  GX_Begin(GX_QUADS, GX_VTXFMT0, 4);\n    GX_Position2s16(x, y);\n    GX_TexCoord2f32(0, 0);\n    GX_Position2s16(x + w, y);\n    GX_TexCoord2f32(0, 0);\n    GX_Position2s16(x + w, y + h);\n    GX_TexCoord2f32(0, 0);\n    GX_Position2s16(x, y + h);\n    GX_TexCoord2f32(0, 0);\n  GX_End();\n}\n\nstatic void gx_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct gx_render_data *render_data = graphics->render_data;\n  static u8 tex_ptn[12][2] = {\n    {6, 6}, {6, 6}, {6, 6},\n    {6, 6}, {6, 6}, {6, 6},\n    {6, 6}, {6, 6}, {6, 6},\n    {6, 6}, {6, 6}, {6, 6},\n  };\n  static u8 tex_vf[7] = {0, 0, 21, 22, 21, 0, 0};\n  GX_SetCopyFilter(GX_FALSE, tex_ptn, GX_FALSE, tex_vf);\n  GX_CopyTex(render_data->scaleimg, GX_TRUE);\n  GX_Flush();\n  GX_PixModeSync();\n  GX_SetTevOp(GX_TEVSTAGE0, GX_REPLACE);\n  GX_LoadTexObj(&render_data->scaletex, GX_TEXMAP0);\n  GX_Begin(GX_QUADS, GX_VTXFMT0, 4);\n    GX_Position2s16(render_data->sx0, render_data->sy0);\n    GX_TexCoord2f32(SCALE_TEX_X0, SCALE_TEX_Y0);\n    GX_Position2s16(render_data->sx1, render_data->sy0);\n    GX_TexCoord2f32(SCALE_TEX_X1, SCALE_TEX_Y0);\n    GX_Position2s16(render_data->sx1, render_data->sy1);\n    GX_TexCoord2f32(SCALE_TEX_X1, SCALE_TEX_Y1);\n    GX_Position2s16(render_data->sx0, render_data->sy1);\n    GX_TexCoord2f32(SCALE_TEX_X0, SCALE_TEX_Y1);\n  GX_End();\n  GX_DrawDone();\n  GX_SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);\n  GX_SetColorUpdate(GX_TRUE);\n  GX_SetCopyFilter(render_data->rmode->aa, render_data->rmode->sample_pattern,\n    GX_TRUE, render_data->rmode->vfilter);\n  GX_CopyDisp(render_data->xfb[render_data->current_xfb], GX_TRUE);\n  GX_Flush();\n  VIDEO_SetNextFramebuffer(render_data->xfb[render_data->current_xfb]);\n  VIDEO_Flush();\n\n  if(graphics->gl_vsync)\n    VIDEO_WaitVSync();\n\n  render_data->current_xfb ^= 1;\n}\n\nvoid render_gx_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = gx_init_video;\n  renderer->free_video = gx_free_video;\n  renderer->create_window = gx_create_window;\n  renderer->resize_window = gx_create_window;\n  renderer->set_viewport = set_window_viewport_scaled;\n  renderer->update_colors = gx_update_colors;\n  renderer->remap_char_range = gx_remap_char_range;\n  renderer->remap_char = gx_remap_char;\n  renderer->remap_charbyte = gx_remap_charbyte;\n  renderer->render_layer = gx_render_layer;\n  renderer->render_cursor = gx_render_cursor;\n  renderer->render_mouse = gx_render_mouse;\n  renderer->sync_screen = gx_sync_screen;\n}\n"
  },
  {
    "path": "arch/wii/render_xfb.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../../src/graphics.h\"\n#include \"../../src/render.h\"\n#include \"../../src/render_layer.h\"\n#include \"../../src/renderers.h\"\n#include \"../../src/yuv.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <malloc.h>\n#define BOOL _BOOL\n#include <ogc/system.h>\n#include <ogc/conf.h>\n#include <ogc/cache.h>\n#include <ogc/video.h>\n#include <ogc/gx.h>\n#include <ogc/gu.h>\n#undef BOOL\n\n/**\n * This software renderer works by drawing directly to the Wii's xfb, which\n * is typically a convenient resolution of 640x480 (NTSC) or 640x572 (PAL).\n * This method generally works pretty fast. As the xfb uses YUY2 color packing,\n * this requires special color mixing in normal MZX mode (the same the overlay2\n * renderer uses).\n *\n * There are two problems with this strategy, though: 240p and 288p video modes\n * mean there needs to be a vertical downscaling fallback, and layer rendering\n * is not compatible with the YUY2 mixing. To facilitate both, an intermediate\n * buffer is drawn to and the final result is mixed and copied into the xfb.\n * Both of these situations make the renderer slower than usual.\n */\n\nstruct xfb_render_data\n{\n  GXRModeObj *rmode;\n  u32 *xfb[2];\n  u32 current_xfb;\n  u32 pitch;\n  u32 skip;\n  u32 intermediate_fb[SCREEN_PIX_W * SCREEN_PIX_H];\n  u8 intermediate_active;\n  u8 intermediate_bpp;\n  u8 require_240p_mode;\n};\n\nstatic boolean xfb_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  struct xfb_render_data *render_data;\n  GXRModeObj *rmode;\n  int skip_height;\n  int render_height;\n  u32 black = rgb_to_yuy2(0, 0, 0);\n\n  graphics->resolution_width = SCREEN_PIX_W;\n  graphics->resolution_height = SCREEN_PIX_H;\n  graphics->window_width = SCREEN_PIX_W;\n  graphics->window_height = SCREEN_PIX_H;\n\n  render_data = cmalloc(sizeof(struct xfb_render_data));\n  graphics->render_data = render_data;\n  graphics->ratio = conf->video_ratio;\n  graphics->bits_per_pixel = 16;\n\n  VIDEO_Init();\n\n  rmode = VIDEO_GetPreferredMode(NULL);\n\n  render_height = SCREEN_PIX_H;\n  if(rmode->xfbHeight < render_height)\n    render_height /= 2;\n\n  skip_height = (rmode->xfbHeight - render_height) / 2;\n\n  if(skip_height < 0)\n    return false;\n\n  render_data->rmode = rmode;\n  render_data->pitch =\n   VIDEO_PadFramebufferWidth(rmode->fbWidth) * VI_DISPLAY_PIX_SZ;\n  render_data->skip = render_data->pitch * skip_height;\n\n  render_data->require_240p_mode = (render_height < SCREEN_PIX_H);\n  render_data->intermediate_active = false;\n  render_data->intermediate_bpp = 16;\n\n  render_data->xfb[0] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));\n  render_data->xfb[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));\n  render_data->current_xfb = 0;\n\n  VIDEO_Configure(rmode);\n  VIDEO_ClearFrameBuffer(rmode, render_data->xfb[0], black);\n  VIDEO_ClearFrameBuffer(rmode, render_data->xfb[1], black);\n  VIDEO_SetNextFramebuffer(render_data->xfb[0]);\n  VIDEO_SetBlack(FALSE);\n  VIDEO_Flush();\n  VIDEO_WaitVSync();\n  if(rmode->viTVMode & VI_NON_INTERLACE)\n    VIDEO_WaitVSync();\n\n  return true;\n}\n\nstatic void xfb_free_video(struct graphics_data *graphics)\n{\n  free(graphics->render_data);\n  graphics->render_data = NULL;\n}\n\nstatic boolean xfb_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  return true;\n}\n\nstatic void xfb_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned int count)\n{\n  unsigned int i;\n\n  for(i = 0; i < count; i++)\n  {\n    graphics->flat_intensity_palette[i] =\n     rgb_to_yuy2(palette[i].r, palette[i].g, palette[i].b);\n  }\n}\n\nstatic void xfb_render_graph(struct graphics_data *graphics)\n{\n  struct xfb_render_data *render_data = graphics->render_data;\n  int mode = graphics->screen_mode;\n  unsigned int pitch = render_data->pitch;\n  unsigned int skip = render_data->skip;\n  uint32_t *pixels;\n\n  if(render_data->require_240p_mode)\n  {\n    render_data->intermediate_active = true;\n    render_data->intermediate_bpp = 16;\n    pixels = render_data->intermediate_fb;\n  }\n  else\n  {\n    render_data->intermediate_active = false;\n    pixels = render_data->xfb[render_data->current_xfb];\n    pixels += skip / sizeof(uint32_t);\n  }\n\n  if(!mode)\n    render_graph16((uint16_t *)pixels, pitch, graphics,\n     yuy2_subsample_set_colors_mzx);\n  else\n    render_graph16((uint16_t *)pixels, pitch, graphics, set_colors32[mode]);\n}\n\nstatic void xfb_render_layer(struct graphics_data *graphics,\n struct video_layer *layer)\n{\n  struct xfb_render_data *render_data = graphics->render_data;\n  unsigned int pitch = render_data->pitch * 2;\n  uint32_t *pixels;\n\n  render_data->intermediate_active = true;\n  render_data->intermediate_bpp = 32;\n  pixels = render_data->intermediate_fb;\n\n  render_layer(pixels, SCREEN_PIX_W, SCREEN_PIX_H, pitch, 32, graphics, layer);\n}\n\nstatic void xfb_render_cursor(struct graphics_data *graphics, unsigned int x,\n unsigned int y, uint16_t color, unsigned int lines, unsigned int offset)\n{\n  struct xfb_render_data *render_data = graphics->render_data;\n  unsigned int pitch = render_data->pitch;\n  unsigned int skip = render_data->skip;\n  unsigned int bpp = 16;\n\n  uint32_t flatcolor = graphics->flat_intensity_palette[color];\n  uint32_t *pixels;\n\n  if(render_data->intermediate_active)\n  {\n    pixels = render_data->intermediate_fb;\n\n    if(render_data->intermediate_bpp == 32)\n    {\n      pitch *= 2;\n      bpp = 32;\n    }\n  }\n  else\n  {\n    pixels = render_data->xfb[render_data->current_xfb];\n    pixels += skip / sizeof(uint32_t);\n  }\n\n  render_cursor(pixels, pitch, bpp, x, y, flatcolor, lines, offset);\n}\n\nstatic void xfb_render_mouse(struct graphics_data *graphics,\n unsigned int x, unsigned int y, unsigned int w, unsigned int h)\n{\n  struct xfb_render_data *render_data = graphics->render_data;\n  unsigned int pitch = render_data->pitch;\n  unsigned int skip = render_data->skip;\n  unsigned int bpp = 16;\n  uint32_t *pixels;\n\n  uint32_t mask = 0xFFFFFFFF;\n  uint32_t alpha_mask = 0x0;\n\n  if(render_data->intermediate_active)\n  {\n    pixels = render_data->intermediate_fb;\n\n    if(render_data->intermediate_bpp == 32)\n    {\n      pitch *= 2;\n      bpp = 32;\n    }\n  }\n  else\n  {\n    pixels = render_data->xfb[render_data->current_xfb];\n    pixels += skip / sizeof(uint32_t);\n  }\n\n  render_mouse(pixels, pitch, bpp, x, y, mask, alpha_mask, w, h);\n}\n\n#define Y1_MASK YUY2_Y1_MASK\n#define Y2_MASK YUY2_Y2_MASK\n#define UV_MASK YUY2_UV_MASK\n\nstatic inline uint32_t yuy_mix_x(uint32_t a, uint32_t b)\n{\n  // Mixing two pixels horizontally. Keep Y values but average the UV values.\n  uint32_t y1 = (a & Y1_MASK);\n  uint32_t y2 = (b & Y2_MASK);\n  uint32_t uv = (((a & UV_MASK) / 2) + ((b & UV_MASK) / 2)) & UV_MASK;\n  return y1 | y2 | uv;\n}\n\nstatic inline uint32_t yuy_mix_y(uint32_t a, uint32_t b)\n{\n  // Mixing two pixels vertically. Average all components.\n  uint32_t y1 = (((a & Y1_MASK) / 2) + ((b & Y1_MASK) / 2)) & Y1_MASK;\n  uint32_t y2 = (((a & Y2_MASK) / 2) + ((b & Y2_MASK) / 2)) & Y2_MASK;\n  uint32_t uv = (((a & UV_MASK) / 2) + ((b & UV_MASK) / 2)) & UV_MASK;\n  return y1 | y2 | uv;\n}\n\nstatic void xfb_copy_buffer(struct graphics_data *graphics)\n{\n  struct xfb_render_data *render_data = graphics->render_data;\n  u32 pitch = render_data->pitch;\n  u32 skip = render_data->skip;\n  u32 a, b, c, d;\n  int x, y;\n\n  u32 *src = render_data->intermediate_fb;\n  u32 *src_b;\n\n  u32 *dest = render_data->xfb[render_data->current_xfb];\n  dest += skip / sizeof(u32);\n\n  if(render_data->intermediate_bpp == 32)\n  {\n    if(render_data->require_240p_mode)\n    {\n      for(y = 0; y < SCREEN_PIX_H / 2; y++)\n      {\n        src_b = src + pitch * 2 / sizeof(u32);\n\n        for(x = 0; x < SCREEN_PIX_W / 2; x++)\n        {\n          a = *(src++);\n          b = *(src++);\n          c = *(src_b++);\n          d = *(src_b++);\n          *(dest++) = yuy_mix_y(yuy_mix_x(a, b), yuy_mix_x(c, d));\n        }\n        src = src_b;\n      }\n    }\n    else\n    {\n      for(x = 0; x < (SCREEN_PIX_W / 2) * SCREEN_PIX_H; x++)\n      {\n        a = *(src++);\n        b = *(src++);\n        *(dest++) = yuy_mix_x(a, b);\n      }\n    }\n  }\n  else\n  {\n    if(render_data->require_240p_mode)\n    {\n      for(y = 0; y < SCREEN_PIX_H / 2; y++)\n      {\n        src_b = src + pitch / sizeof(u32);\n\n        for(x = 0; x < SCREEN_PIX_W / 2; x++)\n        {\n          a = *(src++);\n          b = *(src_b++);\n          *(dest++) = yuy_mix_y(a, b);\n        }\n        src = src_b;\n      }\n    }\n    else\n    {\n      // NOTE: should never, ever happen. Draw directly to the xfb in this case\n      memcpy(dest, src, (SCREEN_PIX_W / 2) * SCREEN_PIX_H * sizeof(u32));\n    }\n  }\n}\n\nstatic void xfb_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct xfb_render_data *render_data = graphics->render_data;\n\n  if(render_data->intermediate_active)\n  {\n    xfb_copy_buffer(graphics);\n    render_data->intermediate_active = false;\n  }\n\n  VIDEO_SetNextFramebuffer(render_data->xfb[render_data->current_xfb]);\n  VIDEO_Flush();\n\n  // FIXME: input will freeze under load without this, but this can cause\n  // slow frames to half the framerate or worse at speed 2. This problem does\n  // not seem to affect the GX renderer. Look into possible workarounds.\n  VIDEO_WaitVSync();\n\n  render_data->current_xfb ^= 1;\n}\n\nvoid render_xfb_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = xfb_init_video;\n  renderer->free_video = xfb_free_video;\n  renderer->create_window = xfb_create_window;\n  renderer->set_viewport = set_window_viewport_centered;\n  renderer->update_colors = xfb_update_colors;\n  renderer->render_graph = xfb_render_graph;\n  renderer->render_layer = xfb_render_layer;\n  renderer->render_cursor = xfb_render_cursor;\n  renderer->render_mouse = xfb_render_mouse;\n  renderer->sync_screen = xfb_sync_screen;\n}\n"
  },
  {
    "path": "arch/wii/thread.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __MUTEX_WII_H\n#define __MUTEX_WII_H\n\n#include \"../../src/compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <limits.h>\n#include <time.h>\n\n#define BOOL _BOOL\n#include <gctypes.h>\n#include <ogc/mutex.h>\n#include <ogc/cond.h>\n#include <ogc/semaphore.h>\n#include <ogc/lwp.h>\n#include <ogc/lwp_watchdog.h>\n#undef BOOL\n\n#define THREAD_RES void *\n#define THREAD_RETURN do { return NULL; } while(0)\n\ntypedef cond_t platform_cond;\ntypedef mutex_t platform_mutex;\ntypedef sem_t platform_sem;\ntypedef lwp_t platform_thread;\ntypedef lwp_t platform_thread_id;\ntypedef THREAD_RES (*platform_thread_fn)(void *);\n\nstatic inline boolean platform_mutex_init(platform_mutex *mutex)\n{\n  if(LWP_MutexInit(mutex, false))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_mutex_destroy(platform_mutex *mutex)\n{\n  if(LWP_MutexDestroy(*mutex))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_mutex_lock(platform_mutex *mutex)\n{\n  if(LWP_MutexLock(*mutex))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_mutex_unlock(platform_mutex *mutex)\n{\n  if(LWP_MutexUnlock(*mutex))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_init(platform_cond *cond)\n{\n  if(LWP_CondInit(cond))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_destroy(platform_cond *cond)\n{\n  if(LWP_CondDestroy(*cond))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_wait(platform_cond *cond,\n platform_mutex *mutex)\n{\n  if(LWP_CondWait(*cond, *mutex))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_timedwait(platform_cond *cond,\n platform_mutex *mutex, unsigned int timeout_ms)\n{\n  struct timespec timeout;\n\n  /**\n   * FIXME: LWP_CondTimedWait, despite its documentation, currently takes a\n   * relative timeout. Apparently this behavior is going to get changed in\n   * libogc at some point to match the documentation, so until then, the\n   * correct handling of this is commented out.\n   *\n   * https://github.com/devkitPro/libogc/issues/101\n   */\n  /*\n  u64 ticks = gettime() + millisecs_to_ticks(timeout_ms);\n\n  timeout.tv_sec = ticks_to_secs(ticks);\n  timeout.tv_nsec = ticks_to_nanosecs(ticks) % 1000000000;\n  */\n  timeout.tv_sec = timeout_ms / 1000;\n  timeout.tv_nsec = (timeout_ms % 1000) / 1000000;\n\n  if(LWP_CondTimedWait(*cond, *mutex, &timeout))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_signal(platform_cond *cond)\n{\n  if(LWP_CondSignal(*cond))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_broadcast(platform_cond *cond)\n{\n  if(LWP_CondBroadcast(*cond))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_init(platform_sem *sem, unsigned init_value)\n{\n  if(LWP_SemInit(sem, init_value, INT_MAX))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_destroy(platform_sem *sem)\n{\n  if(LWP_SemDestroy(*sem))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_wait(platform_sem *sem)\n{\n  if(LWP_SemWait(*sem))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_post(platform_sem *sem)\n{\n  if(LWP_SemPost(*sem))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_thread_create(platform_thread *thread,\n platform_thread_fn start_function, void *data)\n{\n  if(LWP_CreateThread(thread, start_function, data, NULL, 0, 64))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_thread_join(platform_thread *thread)\n{\n  if(LWP_JoinThread(*thread, NULL))\n    return false;\n  return true;\n}\n\nstatic inline platform_thread_id platform_get_thread_id(void)\n{\n  return LWP_GetSelf();\n}\n\nstatic inline boolean platform_is_same_thread(platform_thread_id a,\n platform_thread_id b)\n{\n  return a == b;\n}\n\nstatic inline void platform_yield(void)\n{\n  LWP_YieldThread();\n}\n\n__M_END_DECLS\n\n#endif // __MUTEX_WII_H\n"
  },
  {
    "path": "arch/wiiu/CONFIG.WIIU",
    "content": "#!/bin/sh\n\n./config.sh --platform wiiu --prefix \"$DEVKITPPC\" --enable-release --enable-lto \\\n            --disable-utils --enable-meter --enable-stdio-redirect \\\n            --disable-gl \"$@\"\n"
  },
  {
    "path": "arch/wiiu/Makefile.in",
    "content": "#\n# Nintendo Wii U Makefile\n#\n\n.PHONY: package clean\n\nifeq ($(strip $(DEVKITPRO)),)\n$(error \"DEVKITPRO must be set in your environment.\")\nendif\n\nifeq ($(strip $(DEVKITPPC)),)\n$(error \"DEVKITPPC must be set in your environment.\")\nendif\n\nEXTRA_LICENSES += ${LICENSE_MPL2} ${LICENSE_NEWLIB}\n\n#\n# Switch target rules\n#\n\ninclude $(DEVKITPRO)/wut/share/wut_rules\n\nBINEXT     := .elf\n# overridden by base_tools include\nPREFIX     := $(DEVKITPPC)\n\n# Block --host, which will break things.\nCROSS_COMPILE =\n# Disable rules that require target code to run natively.\nSUPPRESS_HOST_TARGETS ?= 1\n\n#\n# Override library paths.\n#\n\nMACHDEP    := -DESPRESSO -mcpu=750 -meabi -mhard-float\n\nifeq (${DEBUG},1)\nWUT := wutd\nelse\nOPTIMIZE_FLAGS += -ffunction-sections\nWUT := wut\nendif\n\nSDL_PREFIX := ${PORTLIBS_PATH}/wiiu\nLIBPNG_PREFIX := ${PORTLIBS_PATH}/ppc\n\nPORTLIBS_INCLUDES := $(foreach dir, $(PORTLIBS), -isystem $(dir)/include)\nPORTLIBS_LIBS     := $(foreach dir, $(PORTLIBS), -L$(dir)/lib)\n\nEXTRA_INCLUDES := -isystem ${WUT_ROOT}/include ${PORTLIBS_INCLUDES}\nEXTRA_LIBS := -L${WUT_ROOT}/lib ${PORTLIBS_LIBS} -lSDL2 -l${WUT}\n\nARCH_CFLAGS   += ${EXTRA_INCLUDES} ${MACHDEP}\nARCH_CXXFLAGS += ${EXTRA_INCLUDES} ${MACHDEP}\nARCH_LDFLAGS  += ${EXTRA_LIBS} ${MACHDEP} ${RPXSPECS}\n\n# Unknown purpose, copied from dkp templates(?)\n# Only relevant search result: https://devkitpro.org/viewtopic.php?f=42&t=9552\nARCH_CXXFLAGS += -fno-rtti -fno-exceptions\n\npackage: mzx mzxrun.rpx megazeux.rpx\n\nclean:\n\t${RM} -f mzxrun.rpx mzxrun.elf megazeux.rpx megazeux.elf\n\nbuild := build/${SUBPLATFORM}/wiiu/apps/megazeux\nbuild: package ${build}\n\t${CP} arch/wiiu/pad.config ${build}\n\t${CP} megazeux.rpx ${build}\n\t${CP} contrib/icons/generated/icon_wiiu.png ${build}/icon.png\n\t@sed \"s/%VERSION%/${VERSION}/g;s/%DATE%/`date -u +%Y%m%d%H%M`/g\" \\\n\t    arch/wiiu/meta.xml > ${build}/meta.xml\n\t${RM} ${build}/${mzxrun} ${build}/${mzx} ${build}/*.debug\n\ninclude arch/zip.inc\n"
  },
  {
    "path": "arch/wiiu/meta.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<app version=\"1\">\n\t<name>MegaZeux</name>\n\t<version>%VERSION%</version>\n\t<release_date>%DATE%</release_date>\n\t<short_description>A simple game creation system (GCS)</short_description>\n\t<long_description>MegaZeux is a Game Creation System (GCS) inspired by Epic MegaGames' ZZT, first released by Alexis Janson in 1994. Originally written for DOS, MegaZeux is now available on a wide variety of platforms.&#xA;&#xA;Many features have been added since the original DOS version, and with the help of an intuitive editor and a simple but powerful scripting language, MegaZeux allows you to create your own ANSI-esque games. It is actively maintained by a thriving community. See https://www.digitalmzx.com/ for more information.</long_description>\n</app>\n"
  },
  {
    "path": "arch/wiiu/pad.config",
    "content": "### Wii U controllers ###\r\n\r\n# The Wii U SDL2 port tries to map all controllers to a common layout.\r\n#\r\n# There are no hats.\r\n\r\njoy[1,4]axis1 = act_l_left, act_l_right\r\njoy[1,4]axis2 = act_l_up, act_l_down\r\njoy[1,4]axis3 = act_r_left, act_r_right\r\njoy[1,4]axis4 = act_r_up, act_r_down\r\n\r\njoy[1,4]button1 = act_a\r\njoy[1,4]button2 = act_b\r\njoy[1,4]button3 = act_x\r\njoy[1,4]button4 = act_y\r\njoy[1,4]button5 = act_lstick\r\njoy[1,4]button6 = act_rstick\r\njoy[1,4]button7 = act_lshoulder\r\njoy[1,4]button8 = act_rshoulder\r\njoy[1,4]button9 = act_ltrigger\r\njoy[1,4]button10 = act_rtrigger\r\njoy[1,4]button11 = act_start\r\njoy[1,4]button12 = act_select\r\njoy[1,4]button13 = act_left\r\njoy[1,4]button14 = act_up\r\njoy[1,4]button15 = act_right\r\njoy[1,4]button16 = act_down\r\n\r\njoy[1,4].axis_lx = 1\r\njoy[1,4].axis_ly = 2\r\njoy[1,4].axis_rx = 3\r\njoy[1,4].axis_ry = 4\r\n"
  },
  {
    "path": "arch/xcode/MZXRun/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"16x16\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"16x16\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"32x32\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"32x32\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"128x128\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"quantump.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"128x128\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"256x256\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"256x256\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"512x512\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"512x512\",\n      \"scale\" : \"2x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "arch/xcode/MZXRun/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIconFile</key>\n\t<string></string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>2.93d-GIT</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>LSApplicationCategoryType</key>\n\t<string>public.app-category.games</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>$(MACOSX_DEPLOYMENT_TARGET)</string>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>Copyright © 1994-2025 MegaZeux Dev Team. All rights reserved.</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "arch/xcode/MegaZeux/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"16x16\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"16x16\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"32x32\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"32x32\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"128x128\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"quantump.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"128x128\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"256x256\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"256x256\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"512x512\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"mac\",\n      \"size\" : \"512x512\",\n      \"scale\" : \"2x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "arch/xcode/MegaZeux/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "arch/xcode/MegaZeux/Base.lproj/MainMenu.xib",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion=\"11134\" systemVersion=\"15F34\" targetRuntime=\"MacOSX.Cocoa\" propertyAccessControl=\"none\" useAutolayout=\"YES\" customObjectInstantitationMethod=\"direct\">\n    <dependencies>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.CocoaPlugin\" version=\"11134\"/>\n    </dependencies>\n    <objects>\n        <customObject id=\"-2\" userLabel=\"File's Owner\" customClass=\"NSApplication\">\n            <connections>\n                <outlet property=\"delegate\" destination=\"Voe-Tx-rLC\" id=\"GzC-gU-4Uq\"/>\n            </connections>\n        </customObject>\n        <customObject id=\"-1\" userLabel=\"First Responder\" customClass=\"FirstResponder\"/>\n        <customObject id=\"-3\" userLabel=\"Application\" customClass=\"NSApplication\"/>\n        <customObject id=\"Voe-Tx-rLC\" customClass=\"AppDelegate\" customModuleProvider=\"\">\n            <connections>\n                <outlet property=\"window\" destination=\"QvC-M9-y7g\" id=\"gIp-Ho-8D9\"/>\n            </connections>\n        </customObject>\n        <customObject id=\"YLy-65-1bz\" customClass=\"NSFontManager\"/>\n        <menu title=\"Main Menu\" systemMenu=\"main\" id=\"AYu-sK-qS6\">\n            <items>\n                <menuItem title=\"MegaZeux\" id=\"1Xt-HY-uBw\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"MegaZeux\" systemMenu=\"apple\" id=\"uQy-DD-JDr\">\n                        <items>\n                            <menuItem title=\"About MegaZeux\" id=\"5kV-Vb-QxS\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <connections>\n                                    <action selector=\"orderFrontStandardAboutPanel:\" target=\"-1\" id=\"Exp-CZ-Vem\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"VOq-y0-SEH\"/>\n                            <menuItem title=\"Preferences…\" keyEquivalent=\",\" id=\"BOF-NM-1cW\"/>\n                            <menuItem isSeparatorItem=\"YES\" id=\"wFC-TO-SCJ\"/>\n                            <menuItem title=\"Services\" id=\"NMo-om-nkz\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Services\" systemMenu=\"services\" id=\"hz9-B4-Xy5\"/>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"4je-JR-u6R\"/>\n                            <menuItem title=\"Hide MegaZeux\" keyEquivalent=\"h\" id=\"Olw-nP-bQN\">\n                                <connections>\n                                    <action selector=\"hide:\" target=\"-1\" id=\"PnN-Uc-m68\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Hide Others\" keyEquivalent=\"h\" id=\"Vdr-fp-XzO\">\n                                <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                <connections>\n                                    <action selector=\"hideOtherApplications:\" target=\"-1\" id=\"VT4-aY-XCT\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Show All\" id=\"Kd2-mp-pUS\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <connections>\n                                    <action selector=\"unhideAllApplications:\" target=\"-1\" id=\"Dhg-Le-xox\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"kCx-OE-vgT\"/>\n                            <menuItem title=\"Quit MegaZeux\" keyEquivalent=\"q\" id=\"4sb-4s-VLi\">\n                                <connections>\n                                    <action selector=\"terminate:\" target=\"-1\" id=\"Te7-pn-YzF\"/>\n                                </connections>\n                            </menuItem>\n                        </items>\n                    </menu>\n                </menuItem>\n                <menuItem title=\"File\" id=\"dMs-cI-mzQ\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"File\" id=\"bib-Uj-vzu\">\n                        <items>\n                            <menuItem title=\"New\" keyEquivalent=\"n\" id=\"Was-JA-tGl\">\n                                <connections>\n                                    <action selector=\"newDocument:\" target=\"-1\" id=\"4Si-XN-c54\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Open…\" keyEquivalent=\"o\" id=\"IAo-SY-fd9\">\n                                <connections>\n                                    <action selector=\"openDocument:\" target=\"-1\" id=\"bVn-NM-KNZ\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Open Recent\" id=\"tXI-mr-wws\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Open Recent\" systemMenu=\"recentDocuments\" id=\"oas-Oc-fiZ\">\n                                    <items>\n                                        <menuItem title=\"Clear Menu\" id=\"vNY-rz-j42\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"clearRecentDocuments:\" target=\"-1\" id=\"Daa-9d-B3U\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"m54-Is-iLE\"/>\n                            <menuItem title=\"Close\" keyEquivalent=\"w\" id=\"DVo-aG-piG\">\n                                <connections>\n                                    <action selector=\"performClose:\" target=\"-1\" id=\"HmO-Ls-i7Q\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Save…\" keyEquivalent=\"s\" id=\"pxx-59-PXV\">\n                                <connections>\n                                    <action selector=\"saveDocument:\" target=\"-1\" id=\"teZ-XB-qJY\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Save As…\" keyEquivalent=\"S\" id=\"Bw7-FT-i3A\">\n                                <connections>\n                                    <action selector=\"saveDocumentAs:\" target=\"-1\" id=\"mDf-zr-I0C\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Revert to Saved\" keyEquivalent=\"r\" id=\"KaW-ft-85H\">\n                                <connections>\n                                    <action selector=\"revertDocumentToSaved:\" target=\"-1\" id=\"iJ3-Pv-kwq\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"aJh-i4-bef\"/>\n                            <menuItem title=\"Page Setup…\" keyEquivalent=\"P\" id=\"qIS-W8-SiK\">\n                                <modifierMask key=\"keyEquivalentModifierMask\" shift=\"YES\" command=\"YES\"/>\n                                <connections>\n                                    <action selector=\"runPageLayout:\" target=\"-1\" id=\"Din-rz-gC5\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Print…\" keyEquivalent=\"p\" id=\"aTl-1u-JFS\">\n                                <connections>\n                                    <action selector=\"print:\" target=\"-1\" id=\"qaZ-4w-aoO\"/>\n                                </connections>\n                            </menuItem>\n                        </items>\n                    </menu>\n                </menuItem>\n                <menuItem title=\"Edit\" id=\"5QF-Oa-p0T\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"Edit\" id=\"W48-6f-4Dl\">\n                        <items>\n                            <menuItem title=\"Undo\" keyEquivalent=\"z\" id=\"dRJ-4n-Yzg\">\n                                <connections>\n                                    <action selector=\"undo:\" target=\"-1\" id=\"M6e-cu-g7V\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Redo\" keyEquivalent=\"Z\" id=\"6dh-zS-Vam\">\n                                <connections>\n                                    <action selector=\"redo:\" target=\"-1\" id=\"oIA-Rs-6OD\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"WRV-NI-Exz\"/>\n                            <menuItem title=\"Cut\" keyEquivalent=\"x\" id=\"uRl-iY-unG\">\n                                <connections>\n                                    <action selector=\"cut:\" target=\"-1\" id=\"YJe-68-I9s\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Copy\" keyEquivalent=\"c\" id=\"x3v-GG-iWU\">\n                                <connections>\n                                    <action selector=\"copy:\" target=\"-1\" id=\"G1f-GL-Joy\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Paste\" keyEquivalent=\"v\" id=\"gVA-U4-sdL\">\n                                <connections>\n                                    <action selector=\"paste:\" target=\"-1\" id=\"UvS-8e-Qdg\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Paste and Match Style\" keyEquivalent=\"V\" id=\"WeT-3V-zwk\">\n                                <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                <connections>\n                                    <action selector=\"pasteAsPlainText:\" target=\"-1\" id=\"cEh-KX-wJQ\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Delete\" id=\"pa3-QI-u2k\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <connections>\n                                    <action selector=\"delete:\" target=\"-1\" id=\"0Mk-Ml-PaM\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Select All\" keyEquivalent=\"a\" id=\"Ruw-6m-B2m\">\n                                <connections>\n                                    <action selector=\"selectAll:\" target=\"-1\" id=\"VNm-Mi-diN\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"uyl-h8-XO2\"/>\n                            <menuItem title=\"Find\" id=\"4EN-yA-p0u\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Find\" id=\"1b7-l0-nxx\">\n                                    <items>\n                                        <menuItem title=\"Find…\" tag=\"1\" keyEquivalent=\"f\" id=\"Xz5-n4-O0W\">\n                                            <connections>\n                                                <action selector=\"performFindPanelAction:\" target=\"-1\" id=\"cD7-Qs-BN4\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Find and Replace…\" tag=\"12\" keyEquivalent=\"f\" id=\"YEy-JH-Tfz\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"performFindPanelAction:\" target=\"-1\" id=\"WD3-Gg-5AJ\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Find Next\" tag=\"2\" keyEquivalent=\"g\" id=\"q09-fT-Sye\">\n                                            <connections>\n                                                <action selector=\"performFindPanelAction:\" target=\"-1\" id=\"NDo-RZ-v9R\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Find Previous\" tag=\"3\" keyEquivalent=\"G\" id=\"OwM-mh-QMV\">\n                                            <connections>\n                                                <action selector=\"performFindPanelAction:\" target=\"-1\" id=\"HOh-sY-3ay\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Use Selection for Find\" tag=\"7\" keyEquivalent=\"e\" id=\"buJ-ug-pKt\">\n                                            <connections>\n                                                <action selector=\"performFindPanelAction:\" target=\"-1\" id=\"U76-nv-p5D\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Jump to Selection\" keyEquivalent=\"j\" id=\"S0p-oC-mLd\">\n                                            <connections>\n                                                <action selector=\"centerSelectionInVisibleArea:\" target=\"-1\" id=\"IOG-6D-g5B\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Spelling and Grammar\" id=\"Dv1-io-Yv7\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Spelling\" id=\"3IN-sU-3Bg\">\n                                    <items>\n                                        <menuItem title=\"Show Spelling and Grammar\" keyEquivalent=\":\" id=\"HFo-cy-zxI\">\n                                            <connections>\n                                                <action selector=\"showGuessPanel:\" target=\"-1\" id=\"vFj-Ks-hy3\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Check Document Now\" keyEquivalent=\";\" id=\"hz2-CU-CR7\">\n                                            <connections>\n                                                <action selector=\"checkSpelling:\" target=\"-1\" id=\"fz7-VC-reM\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"bNw-od-mp5\"/>\n                                        <menuItem title=\"Check Spelling While Typing\" id=\"rbD-Rh-wIN\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleContinuousSpellChecking:\" target=\"-1\" id=\"7w6-Qz-0kB\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Check Grammar With Spelling\" id=\"mK6-2p-4JG\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleGrammarChecking:\" target=\"-1\" id=\"muD-Qn-j4w\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Correct Spelling Automatically\" id=\"78Y-hA-62v\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleAutomaticSpellingCorrection:\" target=\"-1\" id=\"2lM-Qi-WAP\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Substitutions\" id=\"9ic-FL-obx\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Substitutions\" id=\"FeM-D8-WVr\">\n                                    <items>\n                                        <menuItem title=\"Show Substitutions\" id=\"z6F-FW-3nz\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"orderFrontSubstitutionsPanel:\" target=\"-1\" id=\"oku-mr-iSq\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"gPx-C9-uUO\"/>\n                                        <menuItem title=\"Smart Copy/Paste\" id=\"9yt-4B-nSM\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleSmartInsertDelete:\" target=\"-1\" id=\"3IJ-Se-DZD\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Smart Quotes\" id=\"hQb-2v-fYv\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleAutomaticQuoteSubstitution:\" target=\"-1\" id=\"ptq-xd-QOA\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Smart Dashes\" id=\"rgM-f4-ycn\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleAutomaticDashSubstitution:\" target=\"-1\" id=\"oCt-pO-9gS\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Smart Links\" id=\"cwL-P1-jid\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleAutomaticLinkDetection:\" target=\"-1\" id=\"Gip-E3-Fov\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Data Detectors\" id=\"tRr-pd-1PS\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleAutomaticDataDetection:\" target=\"-1\" id=\"R1I-Nq-Kbl\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Text Replacement\" id=\"HFQ-gK-NFA\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleAutomaticTextReplacement:\" target=\"-1\" id=\"DvP-Fe-Py6\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Transformations\" id=\"2oI-Rn-ZJC\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Transformations\" id=\"c8a-y6-VQd\">\n                                    <items>\n                                        <menuItem title=\"Make Upper Case\" id=\"vmV-6d-7jI\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"uppercaseWord:\" target=\"-1\" id=\"sPh-Tk-edu\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Make Lower Case\" id=\"d9M-CD-aMd\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"lowercaseWord:\" target=\"-1\" id=\"iUZ-b5-hil\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Capitalize\" id=\"UEZ-Bs-lqG\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"capitalizeWord:\" target=\"-1\" id=\"26H-TL-nsh\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Speech\" id=\"xrE-MZ-jX0\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Speech\" id=\"3rS-ZA-NoH\">\n                                    <items>\n                                        <menuItem title=\"Start Speaking\" id=\"Ynk-f8-cLZ\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"startSpeaking:\" target=\"-1\" id=\"654-Ng-kyl\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Stop Speaking\" id=\"Oyz-dy-DGm\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"stopSpeaking:\" target=\"-1\" id=\"dX8-6p-jy9\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                        </items>\n                    </menu>\n                </menuItem>\n                <menuItem title=\"Format\" id=\"jxT-CU-nIS\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"Format\" id=\"GEO-Iw-cKr\">\n                        <items>\n                            <menuItem title=\"Font\" id=\"Gi5-1S-RQB\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Font\" systemMenu=\"font\" id=\"aXa-aM-Jaq\">\n                                    <items>\n                                        <menuItem title=\"Show Fonts\" keyEquivalent=\"t\" id=\"Q5e-8K-NDq\">\n                                            <connections>\n                                                <action selector=\"orderFrontFontPanel:\" target=\"YLy-65-1bz\" id=\"WHr-nq-2xA\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Bold\" tag=\"2\" keyEquivalent=\"b\" id=\"GB9-OM-e27\">\n                                            <connections>\n                                                <action selector=\"addFontTrait:\" target=\"YLy-65-1bz\" id=\"hqk-hr-sYV\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Italic\" tag=\"1\" keyEquivalent=\"i\" id=\"Vjx-xi-njq\">\n                                            <connections>\n                                                <action selector=\"addFontTrait:\" target=\"YLy-65-1bz\" id=\"IHV-OB-c03\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Underline\" keyEquivalent=\"u\" id=\"WRG-CD-K1S\">\n                                            <connections>\n                                                <action selector=\"underline:\" target=\"-1\" id=\"FYS-2b-JAY\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"5gT-KC-WSO\"/>\n                                        <menuItem title=\"Bigger\" tag=\"3\" keyEquivalent=\"+\" id=\"Ptp-SP-VEL\">\n                                            <connections>\n                                                <action selector=\"modifyFont:\" target=\"YLy-65-1bz\" id=\"Uc7-di-UnL\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Smaller\" tag=\"4\" keyEquivalent=\"-\" id=\"i1d-Er-qST\">\n                                            <connections>\n                                                <action selector=\"modifyFont:\" target=\"YLy-65-1bz\" id=\"HcX-Lf-eNd\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"kx3-Dk-x3B\"/>\n                                        <menuItem title=\"Kern\" id=\"jBQ-r6-VK2\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Kern\" id=\"tlD-Oa-oAM\">\n                                                <items>\n                                                    <menuItem title=\"Use Default\" id=\"GUa-eO-cwY\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"useStandardKerning:\" target=\"-1\" id=\"6dk-9l-Ckg\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Use None\" id=\"cDB-IK-hbR\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"turnOffKerning:\" target=\"-1\" id=\"U8a-gz-Maa\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Tighten\" id=\"46P-cB-AYj\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"tightenKerning:\" target=\"-1\" id=\"hr7-Nz-8ro\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Loosen\" id=\"ogc-rX-tC1\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"loosenKerning:\" target=\"-1\" id=\"8i4-f9-FKE\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem title=\"Ligatures\" id=\"o6e-r0-MWq\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Ligatures\" id=\"w0m-vy-SC9\">\n                                                <items>\n                                                    <menuItem title=\"Use Default\" id=\"agt-UL-0e3\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"useStandardLigatures:\" target=\"-1\" id=\"7uR-wd-Dx6\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Use None\" id=\"J7y-lM-qPV\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"turnOffLigatures:\" target=\"-1\" id=\"iX2-gA-Ilz\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Use All\" id=\"xQD-1f-W4t\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"useAllLigatures:\" target=\"-1\" id=\"KcB-kA-TuK\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem title=\"Baseline\" id=\"OaQ-X3-Vso\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Baseline\" id=\"ijk-EB-dga\">\n                                                <items>\n                                                    <menuItem title=\"Use Default\" id=\"3Om-Ey-2VK\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"unscript:\" target=\"-1\" id=\"0vZ-95-Ywn\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Superscript\" id=\"Rqc-34-cIF\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"superscript:\" target=\"-1\" id=\"3qV-fo-wpU\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Subscript\" id=\"I0S-gh-46l\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"subscript:\" target=\"-1\" id=\"Q6W-4W-IGz\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Raise\" id=\"2h7-ER-AoG\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"raiseBaseline:\" target=\"-1\" id=\"4sk-31-7Q9\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem title=\"Lower\" id=\"1tx-W0-xDw\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"lowerBaseline:\" target=\"-1\" id=\"OF1-bc-KW4\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"Ndw-q3-faq\"/>\n                                        <menuItem title=\"Show Colors\" keyEquivalent=\"C\" id=\"bgn-CT-cEk\">\n                                            <connections>\n                                                <action selector=\"orderFrontColorPanel:\" target=\"-1\" id=\"mSX-Xz-DV3\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"iMs-zA-UFJ\"/>\n                                        <menuItem title=\"Copy Style\" keyEquivalent=\"c\" id=\"5Vv-lz-BsD\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"copyFont:\" target=\"-1\" id=\"GJO-xA-L4q\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Paste Style\" keyEquivalent=\"v\" id=\"vKC-jM-MkH\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"pasteFont:\" target=\"-1\" id=\"JfD-CL-leO\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Text\" id=\"Fal-I4-PZk\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Text\" id=\"d9c-me-L2H\">\n                                    <items>\n                                        <menuItem title=\"Align Left\" keyEquivalent=\"{\" id=\"ZM1-6Q-yy1\">\n                                            <connections>\n                                                <action selector=\"alignLeft:\" target=\"-1\" id=\"zUv-R1-uAa\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Center\" keyEquivalent=\"|\" id=\"VIY-Ag-zcb\">\n                                            <connections>\n                                                <action selector=\"alignCenter:\" target=\"-1\" id=\"spX-mk-kcS\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Justify\" id=\"J5U-5w-g23\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"alignJustified:\" target=\"-1\" id=\"ljL-7U-jND\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Align Right\" keyEquivalent=\"}\" id=\"wb2-vD-lq4\">\n                                            <connections>\n                                                <action selector=\"alignRight:\" target=\"-1\" id=\"r48-bG-YeY\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"4s2-GY-VfK\"/>\n                                        <menuItem title=\"Writing Direction\" id=\"H1b-Si-o9J\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <menu key=\"submenu\" title=\"Writing Direction\" id=\"8mr-sm-Yjd\">\n                                                <items>\n                                                    <menuItem title=\"Paragraph\" enabled=\"NO\" id=\"ZvO-Gk-QUH\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                    </menuItem>\n                                                    <menuItem id=\"YGs-j5-SAR\">\n                                                        <string key=\"title\">\tDefault</string>\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"makeBaseWritingDirectionNatural:\" target=\"-1\" id=\"qtV-5e-UBP\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem id=\"Lbh-J2-qVU\">\n                                                        <string key=\"title\">\tLeft to Right</string>\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"makeBaseWritingDirectionLeftToRight:\" target=\"-1\" id=\"S0X-9S-QSf\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem id=\"jFq-tB-4Kx\">\n                                                        <string key=\"title\">\tRight to Left</string>\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"makeBaseWritingDirectionRightToLeft:\" target=\"-1\" id=\"5fk-qB-AqJ\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem isSeparatorItem=\"YES\" id=\"swp-gr-a21\"/>\n                                                    <menuItem title=\"Selection\" enabled=\"NO\" id=\"cqv-fj-IhA\">\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                    </menuItem>\n                                                    <menuItem id=\"Nop-cj-93Q\">\n                                                        <string key=\"title\">\tDefault</string>\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"makeTextWritingDirectionNatural:\" target=\"-1\" id=\"lPI-Se-ZHp\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem id=\"BgM-ve-c93\">\n                                                        <string key=\"title\">\tLeft to Right</string>\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"makeTextWritingDirectionLeftToRight:\" target=\"-1\" id=\"caW-Bv-w94\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                    <menuItem id=\"RB4-Sm-HuC\">\n                                                        <string key=\"title\">\tRight to Left</string>\n                                                        <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                                        <connections>\n                                                            <action selector=\"makeTextWritingDirectionRightToLeft:\" target=\"-1\" id=\"EXD-6r-ZUu\"/>\n                                                        </connections>\n                                                    </menuItem>\n                                                </items>\n                                            </menu>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"fKy-g9-1gm\"/>\n                                        <menuItem title=\"Show Ruler\" id=\"vLm-3I-IUL\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleRuler:\" target=\"-1\" id=\"FOx-HJ-KwY\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Copy Ruler\" keyEquivalent=\"c\" id=\"MkV-Pr-PK5\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"copyRuler:\" target=\"-1\" id=\"71i-fW-3W2\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Paste Ruler\" keyEquivalent=\"v\" id=\"LVM-kO-fVI\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"pasteRuler:\" target=\"-1\" id=\"cSh-wd-qM2\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                        </items>\n                    </menu>\n                </menuItem>\n                <menuItem title=\"View\" id=\"H8h-7b-M4v\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"View\" id=\"HyV-fh-RgO\">\n                        <items>\n                            <menuItem title=\"Show Toolbar\" keyEquivalent=\"t\" id=\"snW-S8-Cw5\">\n                                <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                <connections>\n                                    <action selector=\"toggleToolbarShown:\" target=\"-1\" id=\"BXY-wc-z0C\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Customize Toolbar…\" id=\"1UK-8n-QPP\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <connections>\n                                    <action selector=\"runToolbarCustomizationPalette:\" target=\"-1\" id=\"pQI-g3-MTW\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"hB3-LF-h0Y\"/>\n                            <menuItem title=\"Show Sidebar\" keyEquivalent=\"s\" id=\"kIP-vf-haE\">\n                                <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                <connections>\n                                    <action selector=\"toggleSourceList:\" target=\"-1\" id=\"iwa-gc-5KM\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Enter Full Screen\" keyEquivalent=\"f\" id=\"4J7-dP-txa\">\n                                <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                <connections>\n                                    <action selector=\"toggleFullScreen:\" target=\"-1\" id=\"dU3-MA-1Rq\"/>\n                                </connections>\n                            </menuItem>\n                        </items>\n                    </menu>\n                </menuItem>\n                <menuItem title=\"Window\" id=\"aUF-d1-5bR\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"Window\" systemMenu=\"window\" id=\"Td7-aD-5lo\">\n                        <items>\n                            <menuItem title=\"Minimize\" keyEquivalent=\"m\" id=\"OY7-WF-poV\">\n                                <connections>\n                                    <action selector=\"performMiniaturize:\" target=\"-1\" id=\"VwT-WD-YPe\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Zoom\" id=\"R4o-n2-Eq4\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <connections>\n                                    <action selector=\"performZoom:\" target=\"-1\" id=\"DIl-cC-cCs\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"eu3-7i-yIM\"/>\n                            <menuItem title=\"Bring All to Front\" id=\"LE2-aR-0XJ\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <connections>\n                                    <action selector=\"arrangeInFront:\" target=\"-1\" id=\"DRN-fu-gQh\"/>\n                                </connections>\n                            </menuItem>\n                        </items>\n                    </menu>\n                </menuItem>\n                <menuItem title=\"Help\" id=\"wpr-3q-Mcd\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"Help\" systemMenu=\"help\" id=\"F2S-fz-NVQ\">\n                        <items>\n                            <menuItem title=\"MegaZeux Help\" keyEquivalent=\"?\" id=\"FKE-Sm-Kum\">\n                                <connections>\n                                    <action selector=\"showHelp:\" target=\"-1\" id=\"y7X-2Q-9no\"/>\n                                </connections>\n                            </menuItem>\n                        </items>\n                    </menu>\n                </menuItem>\n            </items>\n        </menu>\n        <window title=\"MegaZeux\" allowsToolTipsWhenApplicationIsInactive=\"NO\" autorecalculatesKeyViewLoop=\"NO\" releasedWhenClosed=\"NO\" animationBehavior=\"default\" id=\"QvC-M9-y7g\">\n            <windowStyleMask key=\"styleMask\" titled=\"YES\" closable=\"YES\" miniaturizable=\"YES\" resizable=\"YES\"/>\n            <windowPositionMask key=\"initialPositionMask\" leftStrut=\"YES\" rightStrut=\"YES\" topStrut=\"YES\" bottomStrut=\"YES\"/>\n            <rect key=\"contentRect\" x=\"335\" y=\"390\" width=\"480\" height=\"360\"/>\n            <rect key=\"screenRect\" x=\"0.0\" y=\"0.0\" width=\"1680\" height=\"1027\"/>\n            <view key=\"contentView\" wantsLayer=\"YES\" id=\"EiT-Mj-1SZ\">\n                <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"480\" height=\"360\"/>\n                <autoresizingMask key=\"autoresizingMask\"/>\n            </view>\n        </window>\n    </objects>\n</document>\n"
  },
  {
    "path": "arch/xcode/MegaZeux/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIconFile</key>\n\t<string></string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>2.93d-GIT</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>LSApplicationCategoryType</key>\n\t<string>public.app-category.games</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>$(MACOSX_DEPLOYMENT_TARGET)</string>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>Copyright © 1994-2025 MegaZeux Dev Team. All rights reserved.</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "arch/xcode/MegaZeux.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 48;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t482C0E8F2617D38E002D9030 /* vio.h in Headers */ = {isa = PBXBuildFile; fileRef = 482C0E8E2617D38E002D9030 /* vio.h */; };\n\t\t482C0E912617D49D002D9030 /* clipboard_cocoa.m in Sources */ = {isa = PBXBuildFile; fileRef = 482C0E902617D49C002D9030 /* clipboard_cocoa.m */; };\n\t\t4836B2DB261860D300F802DD /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4836B2DA261860D300F802DD /* AppKit.framework */; };\n\t\t4836B2E0261860FE00F802DD /* ansi.c in Sources */ = {isa = PBXBuildFile; fileRef = 4836B2DE261860FE00F802DD /* ansi.c */; };\n\t\t4836B2E1261860FE00F802DD /* ansi.h in Headers */ = {isa = PBXBuildFile; fileRef = 4836B2DF261860FE00F802DD /* ansi.h */; };\n\t\t48E8C34C2B411516006177C3 /* vfs.h in Headers */ = {isa = PBXBuildFile; fileRef = 48E8C34A2B411516006177C3 /* vfs.h */; };\n\t\t48E8C34D2B411516006177C3 /* vfs.c in Sources */ = {isa = PBXBuildFile; fileRef = 48E8C34B2B411516006177C3 /* vfs.c */; };\n\t\t48E8C3532B411561006177C3 /* tempfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 48E8C34E2B411560006177C3 /* tempfile.h */; };\n\t\t48E8C3542B411561006177C3 /* far_extras.h in Headers */ = {isa = PBXBuildFile; fileRef = 48E8C34F2B411560006177C3 /* far_extras.h */; };\n\t\t48E8C3552B411561006177C3 /* filetype.c in Sources */ = {isa = PBXBuildFile; fileRef = 48E8C3502B411560006177C3 /* filetype.c */; };\n\t\t48E8C3562B411561006177C3 /* far_extras.c in Sources */ = {isa = PBXBuildFile; fileRef = 48E8C3512B411560006177C3 /* far_extras.c */; };\n\t\t48E8C3572B411561006177C3 /* callbackio.h in Headers */ = {isa = PBXBuildFile; fileRef = 48E8C3522B411561006177C3 /* callbackio.h */; };\n\t\t48E8C35C2B4115BE006177C3 /* platform_time.c in Sources */ = {isa = PBXBuildFile; fileRef = 48E8C3582B4115BD006177C3 /* platform_time.c */; };\n\t\t48E8C35D2B4115BE006177C3 /* about.h in Headers */ = {isa = PBXBuildFile; fileRef = 48E8C3592B4115BD006177C3 /* about.h */; };\n\t\t48E8C35E2B4115BE006177C3 /* about.c in Sources */ = {isa = PBXBuildFile; fileRef = 48E8C35A2B4115BD006177C3 /* about.c */; };\n\t\t48E8C35F2B4115BE006177C3 /* platform_attribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 48E8C35B2B4115BD006177C3 /* platform_attribute.h */; };\n\t\t48E8C3612B411680006177C3 /* sampled_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 48E8C3602B411680006177C3 /* sampled_stream.cpp */; };\n\t\t48E8C3642B411D7F006177C3 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 48E8C3622B411D68006177C3 /* LICENSE */; };\n\t\t48E8C3652B411D7F006177C3 /* LICENSE.3rd in Resources */ = {isa = PBXBuildFile; fileRef = 48E8C3632B411D75006177C3 /* LICENSE.3rd */; };\n\t\t48E8C3662B411D98006177C3 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 48E8C3622B411D68006177C3 /* LICENSE */; };\n\t\t48E8C3672B411D98006177C3 /* LICENSE.3rd in Resources */ = {isa = PBXBuildFile; fileRef = 48E8C3632B411D75006177C3 /* LICENSE.3rd */; };\n\t\t48F5B9962C7EFBB200137776 /* core_task.c in Sources */ = {isa = PBXBuildFile; fileRef = 48F5B9942C7EFBB000137776 /* core_task.c */; };\n\t\t48F5B9972C7EFBB200137776 /* core_task.h in Headers */ = {isa = PBXBuildFile; fileRef = 48F5B9952C7EFBB100137776 /* core_task.h */; };\n\t\t48F5B99E2C7EFC0A00137776 /* edit_export.c in Sources */ = {isa = PBXBuildFile; fileRef = 48F5B99C2C7EFC0900137776 /* edit_export.c */; };\n\t\t48F5B99F2C7EFC0A00137776 /* edit_export.h in Headers */ = {isa = PBXBuildFile; fileRef = 48F5B99D2C7EFC0900137776 /* edit_export.h */; };\n\t\t731E16482C8AE37600E3D730 /* rng.c in Sources */ = {isa = PBXBuildFile; fileRef = 731E16462C8AE37500E3D730 /* rng.c */; };\n\t\t731E16492C8AE37600E3D730 /* rng.h in Headers */ = {isa = PBXBuildFile; fileRef = 731E16472C8AE37500E3D730 /* rng.h */; };\n\t\t737459D82D719052006CEE24 /* flow.c in Sources */ = {isa = PBXBuildFile; fileRef = 737459D72D719052006CEE24 /* flow.c */; };\n\t\tBF10127F1FCCA7C2008EEDB6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF10127E1FCCA7C2008EEDB6 /* Assets.xcassets */; };\n\t\tBF1012A61FCCA993008EEDB6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF1012A51FCCA993008EEDB6 /* Assets.xcassets */; };\n\t\tBF1012BA1FCCABB5008EEDB6 /* Vorbis.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012B71FCCABB4008EEDB6 /* Vorbis.framework */; };\n\t\tBF1012BB1FCCABB5008EEDB6 /* Vorbis.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012B71FCCABB4008EEDB6 /* Vorbis.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\tBF1012BE1FCCABB5008EEDB6 /* Ogg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012B91FCCABB5008EEDB6 /* Ogg.framework */; };\n\t\tBF1012BF1FCCABB5008EEDB6 /* Ogg.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012B91FCCABB5008EEDB6 /* Ogg.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\tBF1012C11FCCABF0008EEDB6 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012C01FCCABF0008EEDB6 /* SDL2.framework */; };\n\t\tBF1012C21FCCABF0008EEDB6 /* SDL2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012C01FCCABF0008EEDB6 /* SDL2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\tBF1012C41FCCB02F008EEDB6 /* png.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012C31FCCB02F008EEDB6 /* png.framework */; };\n\t\tBF1012C51FCCB02F008EEDB6 /* png.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012C31FCCB02F008EEDB6 /* png.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\tBF3C7A7C22E689020024C630 /* render_softscale.c in Sources */ = {isa = PBXBuildFile; fileRef = BF3C7A7A22E688000024C630 /* render_softscale.c */; };\n\t\tBF6058B2216B3683001B738C /* edit_menu.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6058AC216B3682001B738C /* edit_menu.c */; };\n\t\tBF6058B3216B3683001B738C /* buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058AD216B3682001B738C /* buffer.h */; };\n\t\tBF6058B4216B3683001B738C /* edit_menu.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058AE216B3682001B738C /* edit_menu.h */; };\n\t\tBF6058B5216B3683001B738C /* buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6058AF216B3682001B738C /* buffer.c */; };\n\t\tBF6058B6216B3683001B738C /* buffer_struct.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058B0216B3682001B738C /* buffer_struct.h */; };\n\t\tBF6058B7216B3683001B738C /* macro_struct.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058B1216B3683001B738C /* macro_struct.h */; };\n\t\tBF6058DB216B3725001B738C /* extmem.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058B8216B371F001B738C /* extmem.h */; };\n\t\tBF6058DC216B3725001B738C /* core.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6058B9216B371F001B738C /* core.c */; };\n\t\tBF6058DD216B3725001B738C /* game_menu.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058BA216B371F001B738C /* game_menu.h */; };\n\t\tBF6058DE216B3725001B738C /* board_struct.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058BB216B371F001B738C /* board_struct.h */; };\n\t\tBF6058DF216B3725001B738C /* game_update.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6058BC216B371F001B738C /* game_update.c */; };\n\t\tBF6058E0216B3725001B738C /* compat.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058BD216B3720001B738C /* compat.h */; };\n\t\tBF6058E1216B3725001B738C /* game_menu.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6058BE216B3720001B738C /* game_menu.c */; };\n\t\tBF6058E2216B3725001B738C /* counter_struct.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058BF216B3720001B738C /* counter_struct.h */; };\n\t\tBF6058E3216B3725001B738C /* memcasecmp.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058C0216B3720001B738C /* memcasecmp.h */; };\n\t\tBF6058E4216B3725001B738C /* game_player.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6058C1216B3720001B738C /* game_player.c */; };\n\t\tBF6058E5216B3725001B738C /* intake_num.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6058C2216B3720001B738C /* intake_num.c */; };\n\t\tBF6058E6216B3725001B738C /* rasm.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058C3216B3720001B738C /* rasm.h */; };\n\t\tBF6058E7216B3725001B738C /* game_ops.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058C4216B3720001B738C /* game_ops.h */; };\n\t\tBF6058E8216B3725001B738C /* world_format.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058C5216B3721001B738C /* world_format.h */; };\n\t\tBF6058E9216B3725001B738C /* rasm.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6058C6216B3721001B738C /* rasm.c */; };\n\t\tBF6058EA216B3725001B738C /* settings.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058C7216B3721001B738C /* settings.h */; };\n\t\tBF6058EB216B3725001B738C /* caption.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058C8216B3721001B738C /* caption.h */; };\n\t\tBF6058EC216B3725001B738C /* core.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058C9216B3722001B738C /* core.h */; };\n\t\tBF6058ED216B3725001B738C /* game_update_board.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6058CA216B3722001B738C /* game_update_board.c */; };\n\t\tBF6058EE216B3725001B738C /* world_struct.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058CB216B3722001B738C /* world_struct.h */; };\n\t\tBF6058EF216B3725001B738C /* SDLmzx.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058CC216B3722001B738C /* SDLmzx.h */; };\n\t\tBF6058F0216B3725001B738C /* renderers.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058CD216B3723001B738C /* renderers.h */; };\n\t\tBF6058F1216B3725001B738C /* caption.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6058CE216B3723001B738C /* caption.c */; };\n\t\tBF6058F2216B3725001B738C /* game_ops.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6058CF216B3723001B738C /* game_ops.c */; };\n\t\tBF6058F3216B3725001B738C /* intake_num.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058D0216B3723001B738C /* intake_num.h */; };\n\t\tBF6058F5216B3725001B738C /* sprite_struct.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058D2216B3724001B738C /* sprite_struct.h */; };\n\t\tBF6058F6216B3725001B738C /* game_update.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058D3216B3724001B738C /* game_update.h */; };\n\t\tBF6058F7216B3725001B738C /* game_player.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058D4216B3724001B738C /* game_player.h */; };\n\t\tBF6058F8216B3725001B738C /* robot_struct.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058D5216B3724001B738C /* robot_struct.h */; };\n\t\tBF6058F9216B3725001B738C /* thread_sdl.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058D6216B3724001B738C /* thread_sdl.h */; };\n\t\tBF6058FA216B3725001B738C /* const.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058D7216B3724001B738C /* const.h */; };\n\t\tBF6058FB216B3725001B738C /* keysym.h in Headers */ = {isa = PBXBuildFile; fileRef = BF6058D8216B3724001B738C /* keysym.h */; };\n\t\tBF6058FC216B3725001B738C /* settings.c in Sources */ = {isa = PBXBuildFile; fileRef = BF6058D9216B3725001B738C /* settings.c */; };\n\t\tBFA1FB072536752600BB429F /* ice_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFA1FB062536752500BB429F /* ice_load.c */; };\n\t\tBFA52FD2233AC48100A90CB4 /* audio_reality.h in Headers */ = {isa = PBXBuildFile; fileRef = BFA52FD0233AC48100A90CB4 /* audio_reality.h */; };\n\t\tBFA52FD3233AC48100A90CB4 /* audio_reality.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFA52FD1233AC48100A90CB4 /* audio_reality.cpp */; };\n\t\tBFC0B26B20681ACC00D28296 /* flt_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFC0B26620681A9000D28296 /* flt_load.c */; };\n\t\tBFC0B26C20681ACC00D28296 /* hmn_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFC0B26520681A9000D28296 /* hmn_load.c */; };\n\t\tBFC0B26D20681ACC00D28296 /* st_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFC0B26720681A9000D28296 /* st_load.c */; };\n\t\tBFC1C05B214258CE00378A4C /* audio.c in Sources */ = {isa = PBXBuildFile; fileRef = BFC1C0472142551800378A4C /* audio.c */; };\n\t\tBFC1C05C214258CE00378A4C /* audio_pcs.c in Sources */ = {isa = PBXBuildFile; fileRef = BFC1C0582142585300378A4C /* audio_pcs.c */; };\n\t\tBFC1C05D214258CE00378A4C /* audio_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = BFC1C0482142551800378A4C /* audio_sdl.c */; };\n\t\tBFC1C05E214258CE00378A4C /* audio_vorbis.c in Sources */ = {isa = PBXBuildFile; fileRef = BFC1C04A2142551900378A4C /* audio_vorbis.c */; };\n\t\tBFC1C05F214258CE00378A4C /* audio_wav.c in Sources */ = {isa = PBXBuildFile; fileRef = BFC1C0452142551800378A4C /* audio_wav.c */; };\n\t\tBFC1C060214258CE00378A4C /* audio_xmp.c in Sources */ = {isa = PBXBuildFile; fileRef = BFC1C0412142551800378A4C /* audio_xmp.c */; };\n\t\tBFC1C061214258CE00378A4C /* ext.c in Sources */ = {isa = PBXBuildFile; fileRef = BFC1C0462142551800378A4C /* ext.c */; };\n\t\tBFC1C063214258CE00378A4C /* sfx.c in Sources */ = {isa = PBXBuildFile; fileRef = BFC1C0542142552800378A4C /* sfx.c */; };\n\t\tBFD5B0422465AFAE00BC91E9 /* zip_deflate64.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B02D2465AFAB00BC91E9 /* zip_deflate64.h */; };\n\t\tBFD5B0432465AFAE00BC91E9 /* zip_reduce.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B02E2465AFAB00BC91E9 /* zip_reduce.h */; };\n\t\tBFD5B0442465AFAE00BC91E9 /* zip.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD5B02F2465AFAB00BC91E9 /* zip.c */; };\n\t\tBFD5B0452465AFAE00BC91E9 /* zip.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0302465AFAC00BC91E9 /* zip.h */; };\n\t\tBFD5B0462465AFAE00BC91E9 /* fsafeopen.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD5B0312465AFAC00BC91E9 /* fsafeopen.c */; };\n\t\tBFD5B0472465AFAE00BC91E9 /* vfile.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0322465AFAC00BC91E9 /* vfile.h */; };\n\t\tBFD5B0482465AFAE00BC91E9 /* zip_stream.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0332465AFAC00BC91E9 /* zip_stream.h */; };\n\t\tBFD5B04A2465AFAE00BC91E9 /* zip_stream.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD5B0352465AFAC00BC91E9 /* zip_stream.c */; };\n\t\tBFD5B04B2465AFAE00BC91E9 /* vio.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD5B0362465AFAC00BC91E9 /* vio.c */; };\n\t\tBFD5B04C2465AFAE00BC91E9 /* path.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD5B0372465AFAC00BC91E9 /* path.c */; };\n\t\tBFD5B04D2465AFAE00BC91E9 /* zip_deflate.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0382465AFAC00BC91E9 /* zip_deflate.h */; };\n\t\tBFD5B04E2465AFAE00BC91E9 /* fsafeopen.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0392465AFAC00BC91E9 /* fsafeopen.h */; };\n\t\tBFD5B04F2465AFAE00BC91E9 /* path.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B03A2465AFAD00BC91E9 /* path.h */; };\n\t\tBFD5B0502465AFAE00BC91E9 /* bitstream.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B03B2465AFAD00BC91E9 /* bitstream.h */; };\n\t\tBFD5B0512465AFAE00BC91E9 /* zip_implode.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B03C2465AFAD00BC91E9 /* zip_implode.h */; };\n\t\tBFD5B0522465AFAE00BC91E9 /* memfile.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B03D2465AFAD00BC91E9 /* memfile.h */; };\n\t\tBFD5B0532465AFAE00BC91E9 /* zip_shrink.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B03E2465AFAD00BC91E9 /* zip_shrink.h */; };\n\t\tBFD5B0542465AFAE00BC91E9 /* vio_posix.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B03F2465AFAD00BC91E9 /* vio_posix.h */; };\n\t\tBFD5B0552465AFAE00BC91E9 /* zip_dict.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0402465AFAD00BC91E9 /* zip_dict.h */; };\n\t\tBFD5B0582465AFEB00BC91E9 /* hashtable.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0572465AFEB00BC91E9 /* hashtable.h */; };\n\t\tBFD5B05B2465B0C400BC91E9 /* stringsearch.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0592465B0C400BC91E9 /* stringsearch.h */; };\n\t\tBFD5B05C2465B0C400BC91E9 /* stringsearch.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD5B05A2465B0C400BC91E9 /* stringsearch.c */; };\n\t\tBFD67B3D2410ABE200AF114E /* render_layer_code.hpp in Headers */ = {isa = PBXBuildFile; fileRef = BFD67B3B2410ABE200AF114E /* render_layer_code.hpp */; };\n\t\tBFD67B3E2410ABE200AF114E /* render_layer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFD67B3C2410ABE200AF114E /* render_layer.cpp */; };\n\t\tBFFF16281FCDC55000BDEC58 /* png.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012C31FCCB02F008EEDB6 /* png.framework */; };\n\t\tBFFF16291FCDC55000BDEC58 /* png.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012C31FCCB02F008EEDB6 /* png.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\tBFFF162A1FCDC55000BDEC58 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012C01FCCABF0008EEDB6 /* SDL2.framework */; };\n\t\tBFFF162B1FCDC55000BDEC58 /* SDL2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012C01FCCABF0008EEDB6 /* SDL2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\tBFFF162C1FCDC55000BDEC58 /* Ogg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012B91FCCABB5008EEDB6 /* Ogg.framework */; };\n\t\tBFFF162D1FCDC55000BDEC58 /* Ogg.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012B91FCCABB5008EEDB6 /* Ogg.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\tBFFF162E1FCDC55000BDEC58 /* Vorbis.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012B71FCCABB4008EEDB6 /* Vorbis.framework */; };\n\t\tBFFF162F1FCDC55000BDEC58 /* Vorbis.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012B71FCCABB4008EEDB6 /* Vorbis.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\tBFFF16341FCDC57B00BDEC58 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012C61FCCB0F9008EEDB6 /* libz.tbd */; };\n\t\tBFFF16BD1FCDC76900BDEC58 /* block.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF163E1FCDC64700BDEC58 /* block.c */; };\n\t\tBFFF16BE1FCDC76900BDEC58 /* block.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16741FCDC64E00BDEC58 /* block.h */; };\n\t\tBFFF16BF1FCDC76900BDEC58 /* board.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF163A1FCDC64600BDEC58 /* board.c */; };\n\t\tBFFF16C01FCDC76900BDEC58 /* board.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16381FCDC64600BDEC58 /* board.h */; };\n\t\tBFFF16C11FCDC76900BDEC58 /* configure.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF164E1FCDC64800BDEC58 /* configure.c */; };\n\t\tBFFF16C21FCDC76900BDEC58 /* configure.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF165A1FCDC64A00BDEC58 /* configure.h */; };\n\t\tBFFF16C31FCDC76900BDEC58 /* counter.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16731FCDC64D00BDEC58 /* counter.c */; };\n\t\tBFFF16C41FCDC76900BDEC58 /* counter.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF166C1FCDC64D00BDEC58 /* counter.h */; };\n\t\tBFFF16C51FCDC76900BDEC58 /* data.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF163D1FCDC64700BDEC58 /* data.c */; };\n\t\tBFFF16C61FCDC76900BDEC58 /* data.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16441FCDC64700BDEC58 /* data.h */; };\n\t\tBFFF16C71FCDC76900BDEC58 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF164B1FCDC64800BDEC58 /* error.c */; };\n\t\tBFFF16C81FCDC76900BDEC58 /* error.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF163C1FCDC64600BDEC58 /* error.h */; };\n\t\tBFFF16C91FCDC76900BDEC58 /* event_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16961FCDC6D000BDEC58 /* event_sdl.c */; };\n\t\tBFFF16CA1FCDC76900BDEC58 /* event.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16401FCDC64700BDEC58 /* event.c */; };\n\t\tBFFF16CB1FCDC76900BDEC58 /* event.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF166F1FCDC64D00BDEC58 /* event.h */; };\n\t\tBFFF16CC1FCDC76900BDEC58 /* expr.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF165B1FCDC64A00BDEC58 /* expr.c */; };\n\t\tBFFF16CD1FCDC76900BDEC58 /* expr.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16461FCDC64700BDEC58 /* expr.h */; };\n\t\tBFFF16D01FCDC76900BDEC58 /* game.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16681FCDC64C00BDEC58 /* game.c */; };\n\t\tBFFF16D11FCDC76900BDEC58 /* game.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF164D1FCDC64800BDEC58 /* game.h */; };\n\t\tBFFF16D41FCDC76900BDEC58 /* graphics.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16561FCDC64900BDEC58 /* graphics.c */; };\n\t\tBFFF16D51FCDC76900BDEC58 /* graphics.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16711FCDC64D00BDEC58 /* graphics.h */; };\n\t\tBFFF16D61FCDC76900BDEC58 /* idarray.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16721FCDC64D00BDEC58 /* idarray.c */; };\n\t\tBFFF16D71FCDC76900BDEC58 /* idarray.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF163F1FCDC64700BDEC58 /* idarray.h */; };\n\t\tBFFF16D81FCDC76900BDEC58 /* idput.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16541FCDC64900BDEC58 /* idput.c */; };\n\t\tBFFF16D91FCDC76900BDEC58 /* idput.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF165C1FCDC64A00BDEC58 /* idput.h */; };\n\t\tBFFF16DA1FCDC76900BDEC58 /* intake.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16661FCDC64C00BDEC58 /* intake.c */; };\n\t\tBFFF16DB1FCDC76900BDEC58 /* intake.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF166E1FCDC64D00BDEC58 /* intake.h */; };\n\t\tBFFF16DC1FCDC76900BDEC58 /* legacy_board.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16501FCDC64900BDEC58 /* legacy_board.c */; };\n\t\tBFFF16DD1FCDC76900BDEC58 /* legacy_board.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16431FCDC64700BDEC58 /* legacy_board.h */; };\n\t\tBFFF16DE1FCDC76900BDEC58 /* legacy_rasm.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF166A1FCDC64C00BDEC58 /* legacy_rasm.c */; };\n\t\tBFFF16DF1FCDC76900BDEC58 /* legacy_rasm.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16611FCDC64B00BDEC58 /* legacy_rasm.h */; };\n\t\tBFFF16E01FCDC76900BDEC58 /* legacy_robot.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF165D1FCDC64A00BDEC58 /* legacy_robot.c */; };\n\t\tBFFF16E11FCDC76900BDEC58 /* legacy_robot.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF164C1FCDC64800BDEC58 /* legacy_robot.h */; };\n\t\tBFFF16E21FCDC76900BDEC58 /* legacy_world.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16471FCDC64700BDEC58 /* legacy_world.c */; };\n\t\tBFFF16E31FCDC76900BDEC58 /* legacy_world.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16521FCDC64900BDEC58 /* legacy_world.h */; };\n\t\tBFFF16E41FCDC76900BDEC58 /* mzm.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16421FCDC64700BDEC58 /* mzm.c */; };\n\t\tBFFF16E51FCDC76900BDEC58 /* mzm.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16641FCDC64B00BDEC58 /* mzm.h */; };\n\t\tBFFF16E61FCDC76900BDEC58 /* platform_endian.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16A81FCDC6D300BDEC58 /* platform_endian.h */; };\n\t\tBFFF16E71FCDC76900BDEC58 /* platform_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16A31FCDC6D200BDEC58 /* platform_sdl.c */; };\n\t\tBFFF16E81FCDC76900BDEC58 /* platform.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16A91FCDC6D300BDEC58 /* platform.h */; };\n\t\tBFFF16E91FCDC76900BDEC58 /* render_gl.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16991FCDC6D100BDEC58 /* render_gl.c */; };\n\t\tBFFF16EA1FCDC76900BDEC58 /* render_gl.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16A61FCDC6D300BDEC58 /* render_gl.h */; };\n\t\tBFFF16EB1FCDC76900BDEC58 /* render_gl1.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF169D1FCDC6D200BDEC58 /* render_gl1.c */; };\n\t\tBFFF16EC1FCDC76900BDEC58 /* render_gl2.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16951FCDC6D000BDEC58 /* render_gl2.c */; };\n\t\tBFFF16ED1FCDC76900BDEC58 /* render_glsl.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF169E1FCDC6D200BDEC58 /* render_glsl.c */; };\n\t\tBFFF16EF1FCDC76900BDEC58 /* render_layer.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16571FCDC64A00BDEC58 /* render_layer.h */; };\n\t\tBFFF16F01FCDC76900BDEC58 /* render_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16A71FCDC6D300BDEC58 /* render_sdl.c */; };\n\t\tBFFF16F11FCDC76900BDEC58 /* render_sdl.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16971FCDC6D000BDEC58 /* render_sdl.h */; };\n\t\tBFFF16F21FCDC76900BDEC58 /* render_soft.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16A41FCDC6D300BDEC58 /* render_soft.c */; };\n\t\tBFFF16F71FCDC76900BDEC58 /* render.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF166D1FCDC64D00BDEC58 /* render.c */; };\n\t\tBFFF16F81FCDC76900BDEC58 /* render.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16691FCDC64C00BDEC58 /* render.h */; };\n\t\tBFFF16F91FCDC76900BDEC58 /* robot.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16451FCDC64700BDEC58 /* robot.c */; };\n\t\tBFFF16FA1FCDC76900BDEC58 /* robot.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16391FCDC64600BDEC58 /* robot.h */; };\n\t\tBFFF16FB1FCDC76900BDEC58 /* run_robot.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16671FCDC64C00BDEC58 /* run_robot.c */; };\n\t\tBFFF16FC1FCDC76900BDEC58 /* scrdisp.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16511FCDC64900BDEC58 /* scrdisp.c */; };\n\t\tBFFF16FD1FCDC76900BDEC58 /* scrdisp.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16581FCDC64A00BDEC58 /* scrdisp.h */; };\n\t\tBFFF17001FCDC76900BDEC58 /* sprite.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16601FCDC64B00BDEC58 /* sprite.c */; };\n\t\tBFFF17011FCDC76900BDEC58 /* sprite.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF165E1FCDC64B00BDEC58 /* sprite.h */; };\n\t\tBFFF17021FCDC76900BDEC58 /* str.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16411FCDC64700BDEC58 /* str.c */; };\n\t\tBFFF17031FCDC76900BDEC58 /* str.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16621FCDC64B00BDEC58 /* str.h */; };\n\t\tBFFF17041FCDC76900BDEC58 /* util.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16651FCDC64C00BDEC58 /* util.c */; };\n\t\tBFFF17051FCDC76900BDEC58 /* util.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF16361FCDC64600BDEC58 /* util.h */; };\n\t\tBFFF17061FCDC76900BDEC58 /* window.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16531FCDC64900BDEC58 /* window.c */; };\n\t\tBFFF17071FCDC76900BDEC58 /* window.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF164F1FCDC64800BDEC58 /* window.h */; };\n\t\tBFFF17081FCDC76900BDEC58 /* world.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF16591FCDC64A00BDEC58 /* world.c */; };\n\t\tBFFF17091FCDC76900BDEC58 /* world.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF165F1FCDC64B00BDEC58 /* world.h */; };\n\t\tBFFF170C1FCDC7AC00BDEC58 /* png.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012C31FCCB02F008EEDB6 /* png.framework */; };\n\t\tBFFF170D1FCDC7AC00BDEC58 /* Ogg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012B91FCCABB5008EEDB6 /* Ogg.framework */; };\n\t\tBFFF170E1FCDC7AC00BDEC58 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012C01FCCABF0008EEDB6 /* SDL2.framework */; };\n\t\tBFFF170F1FCDC7AC00BDEC58 /* Vorbis.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012B71FCCABB4008EEDB6 /* Vorbis.framework */; };\n\t\tBFFF17111FCDC81000BDEC58 /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17101FCDC81000BDEC58 /* config.h */; };\n\t\tBFFF17AB1FCDCCF300BDEC58 /* control.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF174A1FCDCBE500BDEC58 /* control.c */; };\n\t\tBFFF17AC1FCDCCF300BDEC58 /* dataio.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF174D1FCDCBE600BDEC58 /* dataio.c */; };\n\t\tBFFF17AD1FCDCCF300BDEC58 /* effects.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17211FCDCBDB00BDEC58 /* effects.c */; };\n\t\tBFFF17AE1FCDCCF300BDEC58 /* extras.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF173C1FCDCBE300BDEC58 /* extras.c */; };\n\t\tBFFF17AF1FCDCCF300BDEC58 /* filter.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17221FCDCBDB00BDEC58 /* filter.c */; };\n\t\tBFFF17B11FCDCCF300BDEC58 /* format.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF173A1FCDCBE300BDEC58 /* format.c */; };\n\t\tBFFF17B21FCDCCF300BDEC58 /* hio.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17401FCDCBE400BDEC58 /* hio.c */; };\n\t\tBFFF17B31FCDCCF300BDEC58 /* hmn_extras.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17351FCDCBE200BDEC58 /* hmn_extras.c */; };\n\t\tBFFF17B41FCDCCF300BDEC58 /* lfo.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17301FCDCBE000BDEC58 /* lfo.c */; };\n\t\tBFFF17B51FCDCCF300BDEC58 /* load_helpers.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17451FCDCBE400BDEC58 /* load_helpers.c */; };\n\t\tBFFF17B61FCDCCF300BDEC58 /* load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17391FCDCBE300BDEC58 /* load.c */; };\n\t\tBFFF17B71FCDCCF300BDEC58 /* md5.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF172E1FCDCBDF00BDEC58 /* md5.c */; };\n\t\tBFFF17B81FCDCCF300BDEC58 /* med_extras.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17461FCDCBE400BDEC58 /* med_extras.c */; };\n\t\tBFFF17B91FCDCCF300BDEC58 /* memio.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17291FCDCBDC00BDEC58 /* memio.c */; };\n\t\tBFFF17BA1FCDCCF300BDEC58 /* mix_all.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17411FCDCBE400BDEC58 /* mix_all.c */; };\n\t\tBFFF17BC1FCDCCF300BDEC58 /* mixer.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17331FCDCBE200BDEC58 /* mixer.c */; };\n\t\tBFFF17BE1FCDCCF300BDEC58 /* period.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17441FCDCBE400BDEC58 /* period.c */; };\n\t\tBFFF17BF1FCDCCF300BDEC58 /* player.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF172C1FCDCBDF00BDEC58 /* player.c */; };\n\t\tBFFF17C01FCDCCF300BDEC58 /* read_event.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17241FCDCBDB00BDEC58 /* read_event.c */; };\n\t\tBFFF17C11FCDCCF300BDEC58 /* scan.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17251FCDCBDB00BDEC58 /* scan.c */; };\n\t\tBFFF17C21FCDCCF300BDEC58 /* smix.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17341FCDCBE200BDEC58 /* smix.c */; };\n\t\tBFFF17C31FCDCCF300BDEC58 /* virtual.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF172D1FCDCBDF00BDEC58 /* virtual.c */; };\n\t\tBFFF17C41FCDCCF300BDEC58 /* common.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17421FCDCBE400BDEC58 /* common.h */; };\n\t\tBFFF17C51FCDCCF300BDEC58 /* effects.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17321FCDCBE000BDEC58 /* effects.h */; };\n\t\tBFFF17C61FCDCCF300BDEC58 /* extras.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF173D1FCDCBE300BDEC58 /* extras.h */; };\n\t\tBFFF17C81FCDCCF300BDEC58 /* format.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17311FCDCBE000BDEC58 /* format.h */; };\n\t\tBFFF17C91FCDCCF300BDEC58 /* hio.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF173F1FCDCBE300BDEC58 /* hio.h */; };\n\t\tBFFF17CA1FCDCCF300BDEC58 /* hmn_extras.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17361FCDCBE200BDEC58 /* hmn_extras.h */; };\n\t\tBFFF17CB1FCDCCF300BDEC58 /* lfo.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17271FCDCBDC00BDEC58 /* lfo.h */; };\n\t\tBFFF17CC1FCDCCF300BDEC58 /* list.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17491FCDCBE500BDEC58 /* list.h */; };\n\t\tBFFF17CD1FCDCCF300BDEC58 /* md5.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17201FCDCBDB00BDEC58 /* md5.h */; };\n\t\tBFFF17CE1FCDCCF300BDEC58 /* mdataio.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF174B1FCDCBE500BDEC58 /* mdataio.h */; };\n\t\tBFFF17CF1FCDCCF300BDEC58 /* med_extras.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17281FCDCBDC00BDEC58 /* med_extras.h */; };\n\t\tBFFF17D01FCDCCF300BDEC58 /* memio.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF173B1FCDCBE300BDEC58 /* memio.h */; };\n\t\tBFFF17D11FCDCCF300BDEC58 /* mixer.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17481FCDCBE500BDEC58 /* mixer.h */; };\n\t\tBFFF17D31FCDCCF300BDEC58 /* period.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF172A1FCDCBDC00BDEC58 /* period.h */; };\n\t\tBFFF17D41FCDCCF300BDEC58 /* player.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF172F1FCDCBDF00BDEC58 /* player.h */; };\n\t\tBFFF17D61FCDCCF300BDEC58 /* precomp_lut.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17371FCDCBE200BDEC58 /* precomp_lut.h */; };\n\t\tBFFF17D71FCDCCF300BDEC58 /* virtual.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17381FCDCBE200BDEC58 /* virtual.h */; };\n\t\tBFFF17F11FCDCD1200BDEC58 /* 669_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17821FCDCC0000BDEC58 /* 669_load.c */; };\n\t\tBFFF17F21FCDCD1200BDEC58 /* amf_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17811FCDCC0000BDEC58 /* amf_load.c */; };\n\t\tBFFF17F41FCDCD1200BDEC58 /* asylum_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17681FCDCBFD00BDEC58 /* asylum_load.c */; };\n\t\tBFFF17F51FCDCD1200BDEC58 /* common.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF177D1FCDCBFF00BDEC58 /* common.c */; };\n\t\tBFFF17F61FCDCD1200BDEC58 /* far_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF176B1FCDCBFE00BDEC58 /* far_load.c */; };\n\t\tBFFF17F71FCDCD1200BDEC58 /* gdm_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17731FCDCBFE00BDEC58 /* gdm_load.c */; };\n\t\tBFFF17F81FCDCD1200BDEC58 /* iff.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF176C1FCDCBFE00BDEC58 /* iff.c */; };\n\t\tBFFF17F91FCDCD1200BDEC58 /* it_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17751FCDCBFF00BDEC58 /* it_load.c */; };\n\t\tBFFF17FA1FCDCD1200BDEC58 /* itsex.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF176D1FCDCBFE00BDEC58 /* itsex.c */; };\n\t\tBFFF17FB1FCDCD1200BDEC58 /* med2_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17851FCDCC0000BDEC58 /* med2_load.c */; };\n\t\tBFFF17FC1FCDCD1200BDEC58 /* med3_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17801FCDCC0000BDEC58 /* med3_load.c */; };\n\t\tBFFF17FD1FCDCD1200BDEC58 /* med4_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF177F1FCDCC0000BDEC58 /* med4_load.c */; };\n\t\tBFFF17FE1FCDCD1200BDEC58 /* mmd_common.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF176A1FCDCBFE00BDEC58 /* mmd_common.c */; };\n\t\tBFFF17FF1FCDCD1200BDEC58 /* mmd1_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17791FCDCBFF00BDEC58 /* mmd1_load.c */; };\n\t\tBFFF18001FCDCD1200BDEC58 /* mmd3_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF176E1FCDCBFE00BDEC58 /* mmd3_load.c */; };\n\t\tBFFF18011FCDCD1200BDEC58 /* mod_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17861FCDCC0100BDEC58 /* mod_load.c */; };\n\t\tBFFF18021FCDCD1200BDEC58 /* mtm_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF176F1FCDCBFE00BDEC58 /* mtm_load.c */; };\n\t\tBFFF18031FCDCD1200BDEC58 /* okt_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17841FCDCC0000BDEC58 /* okt_load.c */; };\n\t\tBFFF18041FCDCD1200BDEC58 /* s3m_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17781FCDCBFF00BDEC58 /* s3m_load.c */; };\n\t\tBFFF18051FCDCD1200BDEC58 /* sample.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17881FCDCC0100BDEC58 /* sample.c */; };\n\t\tBFFF18061FCDCD1200BDEC58 /* stm_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF177C1FCDCBFF00BDEC58 /* stm_load.c */; };\n\t\tBFFF18071FCDCD1200BDEC58 /* ult_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17741FCDCBFF00BDEC58 /* ult_load.c */; };\n\t\tBFFF18091FCDCD1200BDEC58 /* xm_load.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF17871FCDCC0100BDEC58 /* xm_load.c */; };\n\t\tBFFF180B1FCDCD1200BDEC58 /* iff.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17761FCDCBFF00BDEC58 /* iff.h */; };\n\t\tBFFF180C1FCDCD1200BDEC58 /* it.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17711FCDCBFE00BDEC58 /* it.h */; };\n\t\tBFFF180D1FCDCD1200BDEC58 /* loader.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF177A1FCDCBFF00BDEC58 /* loader.h */; };\n\t\tBFFF180E1FCDCD1200BDEC58 /* med.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17831FCDCC0000BDEC58 /* med.h */; };\n\t\tBFFF180F1FCDCD1200BDEC58 /* mod.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17771FCDCBFF00BDEC58 /* mod.h */; };\n\t\tBFFF18101FCDCD1200BDEC58 /* s3m.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF17721FCDCBFE00BDEC58 /* s3m.h */; };\n\t\tBFFF18111FCDCD1200BDEC58 /* xm.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF177B1FCDCBFF00BDEC58 /* xm.h */; };\n\t\tBFFF18181FCDCD8500BDEC58 /* helpsys.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18141FCDCD7B00BDEC58 /* helpsys.c */; };\n\t\tBFFF18191FCDCD8500BDEC58 /* helpsys.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18151FCDCD7B00BDEC58 /* helpsys.h */; };\n\t\tBFFF181A1FCDCD8500BDEC58 /* pngops.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18121FCDCD7A00BDEC58 /* pngops.c */; };\n\t\tBFFF181B1FCDCD8500BDEC58 /* pngops.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18131FCDCD7B00BDEC58 /* pngops.h */; };\n\t\tBFFF185C1FCDCF7C00BDEC58 /* block.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF182E1FCDCF6400BDEC58 /* block.c */; };\n\t\tBFFF185D1FCDCF7C00BDEC58 /* block.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18461FCDCF6800BDEC58 /* block.h */; };\n\t\tBFFF185E1FCDCF7C00BDEC58 /* board.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF183F1FCDCF6600BDEC58 /* board.c */; };\n\t\tBFFF185F1FCDCF7C00BDEC58 /* board.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18311FCDCF6400BDEC58 /* board.h */; };\n\t\tBFFF18601FCDCF7C00BDEC58 /* char_ed.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18401FCDCF6600BDEC58 /* char_ed.c */; };\n\t\tBFFF18611FCDCF7C00BDEC58 /* char_ed.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18271FCDCF6300BDEC58 /* char_ed.h */; };\n\t\tBFFF18631FCDCF7C00BDEC58 /* clipboard.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18211FCDCF6200BDEC58 /* clipboard.h */; };\n\t\tBFFF18641FCDCF7C00BDEC58 /* configure.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18241FCDCF6300BDEC58 /* configure.c */; };\n\t\tBFFF18651FCDCF7C00BDEC58 /* configure.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18351FCDCF6500BDEC58 /* configure.h */; };\n\t\tBFFF18661FCDCF7C00BDEC58 /* debug.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF183A1FCDCF6600BDEC58 /* debug.c */; };\n\t\tBFFF18671FCDCF7C00BDEC58 /* debug.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF182B1FCDCF6400BDEC58 /* debug.h */; };\n\t\tBFFF18681FCDCF7C00BDEC58 /* edit_di.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF182A1FCDCF6300BDEC58 /* edit_di.c */; };\n\t\tBFFF18691FCDCF7C00BDEC58 /* edit_di.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18331FCDCF6500BDEC58 /* edit_di.h */; };\n\t\tBFFF186A1FCDCF7C00BDEC58 /* edit.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18301FCDCF6400BDEC58 /* edit.c */; };\n\t\tBFFF186B1FCDCF7C00BDEC58 /* edit.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF182F1FCDCF6400BDEC58 /* edit.h */; };\n\t\tBFFF186C1FCDCF7C00BDEC58 /* fill.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18251FCDCF6300BDEC58 /* fill.c */; };\n\t\tBFFF186D1FCDCF7C00BDEC58 /* fill.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18361FCDCF6500BDEC58 /* fill.h */; };\n\t\tBFFF186E1FCDCF7C00BDEC58 /* graphics.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF183D1FCDCF6600BDEC58 /* graphics.c */; };\n\t\tBFFF186F1FCDCF7C00BDEC58 /* graphics.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18451FCDCF6700BDEC58 /* graphics.h */; };\n\t\tBFFF18701FCDCF7C00BDEC58 /* macro.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18371FCDCF6500BDEC58 /* macro.c */; };\n\t\tBFFF18711FCDCF7C00BDEC58 /* macro.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF181F1FCDCF6200BDEC58 /* macro.h */; };\n\t\tBFFF18721FCDCF7C00BDEC58 /* pal_ed.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18261FCDCF6300BDEC58 /* pal_ed.c */; };\n\t\tBFFF18731FCDCF7C00BDEC58 /* pal_ed.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF181D1FCDCF6200BDEC58 /* pal_ed.h */; };\n\t\tBFFF18741FCDCF7C00BDEC58 /* param.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18411FCDCF6700BDEC58 /* param.c */; };\n\t\tBFFF18751FCDCF7C00BDEC58 /* param.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18381FCDCF6500BDEC58 /* param.h */; };\n\t\tBFFF18761FCDCF7C00BDEC58 /* robo_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF183E1FCDCF6600BDEC58 /* robo_debug.c */; };\n\t\tBFFF18771FCDCF7C00BDEC58 /* robo_debug.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18321FCDCF6400BDEC58 /* robo_debug.h */; };\n\t\tBFFF18781FCDCF7C00BDEC58 /* robo_ed.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18341FCDCF6500BDEC58 /* robo_ed.c */; };\n\t\tBFFF18791FCDCF7C00BDEC58 /* robo_ed.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18431FCDCF6700BDEC58 /* robo_ed.h */; };\n\t\tBFFF187A1FCDCF7C00BDEC58 /* robot.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF182C1FCDCF6400BDEC58 /* robot.c */; };\n\t\tBFFF187B1FCDCF7C00BDEC58 /* robot.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18231FCDCF6300BDEC58 /* robot.h */; };\n\t\tBFFF187C1FCDCF7C00BDEC58 /* select.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18391FCDCF6500BDEC58 /* select.c */; };\n\t\tBFFF187D1FCDCF7C00BDEC58 /* select.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18421FCDCF6700BDEC58 /* select.h */; };\n\t\tBFFF187E1FCDCF7C00BDEC58 /* sfx_edit.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18221FCDCF6300BDEC58 /* sfx_edit.c */; };\n\t\tBFFF187F1FCDCF7C00BDEC58 /* sfx_edit.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF182D1FCDCF6400BDEC58 /* sfx_edit.h */; };\n\t\tBFFF18801FCDCF7C00BDEC58 /* undo.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18281FCDCF6300BDEC58 /* undo.c */; };\n\t\tBFFF18811FCDCF7C00BDEC58 /* undo.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18441FCDCF6700BDEC58 /* undo.h */; };\n\t\tBFFF18821FCDCF7C00BDEC58 /* window.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18291FCDCF6300BDEC58 /* window.c */; };\n\t\tBFFF18831FCDCF7C00BDEC58 /* window.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF181E1FCDCF6200BDEC58 /* window.h */; };\n\t\tBFFF18841FCDCF7C00BDEC58 /* world.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF183B1FCDCF6600BDEC58 /* world.c */; };\n\t\tBFFF18851FCDCF7C00BDEC58 /* world.h in Headers */ = {isa = PBXBuildFile; fileRef = BFFF18201FCDCF6200BDEC58 /* world.h */; };\n\t\tBFFF188A1FCDD2DC00BDEC58 /* libCore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012901FCCA957008EEDB6 /* libCore.dylib */; };\n\t\tBFFF188C1FCDE74B00BDEC58 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF188B1FCDE74B00BDEC58 /* main.c */; };\n\t\tBFFF188D1FCDE81600BDEC58 /* libCore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012901FCCA957008EEDB6 /* libCore.dylib */; };\n\t\tBFFF188E1FCDE81900BDEC58 /* libEditor.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012981FCCA97D008EEDB6 /* libEditor.dylib */; };\n\t\tBFFF188F1FCDE81D00BDEC58 /* libCore.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012901FCCA957008EEDB6 /* libCore.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n\t\tBFFF18921FCDE82100BDEC58 /* libEditor.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012981FCCA97D008EEDB6 /* libEditor.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n\t\tBFFF18961FCDE8B400BDEC58 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = BFFF18951FCDE8B300BDEC58 /* assets */; };\n\t\tBFFF18971FCDE8B400BDEC58 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = BFFF18951FCDE8B300BDEC58 /* assets */; };\n\t\tBFFF18991FCDE8FE00BDEC58 /* config.txt in Resources */ = {isa = PBXBuildFile; fileRef = BFFF18981FCDE8FD00BDEC58 /* config.txt */; };\n\t\tBFFF189A1FCDE8FE00BDEC58 /* config.txt in Resources */ = {isa = PBXBuildFile; fileRef = BFFF18981FCDE8FD00BDEC58 /* config.txt */; };\n\t\tBFFF189B1FCDE93900BDEC58 /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF188B1FCDE74B00BDEC58 /* main.c */; };\n\t\tBFFF189C1FCDEA5700BDEC58 /* libCore.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012901FCCA957008EEDB6 /* libCore.dylib */; };\n\t\tBFFF189E1FCDEA5D00BDEC58 /* libCore.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012901FCCA957008EEDB6 /* libCore.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n\t\tBFFF18A71FCDEAF400BDEC58 /* run_stubs.c in Sources */ = {isa = PBXBuildFile; fileRef = BFFF18A41FCDEAF400BDEC58 /* run_stubs.c */; };\n\t\tBFFF18A81FCE146E00BDEC58 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012C01FCCABF0008EEDB6 /* SDL2.framework */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\tBFFF18901FCDE81D00BDEC58 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = BF1012701FCCA7C1008EEDB6 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = BF10128F1FCCA957008EEDB6;\n\t\t\tremoteInfo = Core;\n\t\t};\n\t\tBFFF18931FCDE82100BDEC58 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = BF1012701FCCA7C1008EEDB6 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = BF1012971FCCA97D008EEDB6;\n\t\t\tremoteInfo = Editor;\n\t\t};\n\t\tBFFF189F1FCDEA5D00BDEC58 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = BF1012701FCCA7C1008EEDB6 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = BF10128F1FCCA957008EEDB6;\n\t\t\tremoteInfo = Core;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\tBF1012B61FCCAB81008EEDB6 /* Embed Frameworks */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t\tBF1012C51FCCB02F008EEDB6 /* png.framework in Embed Frameworks */,\n\t\t\t\tBFFF188F1FCDE81D00BDEC58 /* libCore.dylib in Embed Frameworks */,\n\t\t\t\tBF1012BF1FCCABB5008EEDB6 /* Ogg.framework in Embed Frameworks */,\n\t\t\t\tBFFF18921FCDE82100BDEC58 /* libEditor.dylib in Embed Frameworks */,\n\t\t\t\tBF1012BB1FCCABB5008EEDB6 /* Vorbis.framework in Embed Frameworks */,\n\t\t\t\tBF1012C21FCCABF0008EEDB6 /* SDL2.framework in Embed Frameworks */,\n\t\t\t);\n\t\t\tname = \"Embed Frameworks\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tBFFF16301FCDC55000BDEC58 /* Embed Frameworks */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t\tBFFF162D1FCDC55000BDEC58 /* Ogg.framework in Embed Frameworks */,\n\t\t\t\tBFFF189E1FCDEA5D00BDEC58 /* libCore.dylib in Embed Frameworks */,\n\t\t\t\tBFFF162F1FCDC55000BDEC58 /* Vorbis.framework in Embed Frameworks */,\n\t\t\t\tBFFF16291FCDC55000BDEC58 /* png.framework in Embed Frameworks */,\n\t\t\t\tBFFF162B1FCDC55000BDEC58 /* SDL2.framework in Embed Frameworks */,\n\t\t\t);\n\t\t\tname = \"Embed Frameworks\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\t482C0E8E2617D38E002D9030 /* vio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vio.h; path = ../../src/io/vio.h; sourceTree = \"<group>\"; };\n\t\t482C0E902617D49C002D9030 /* clipboard_cocoa.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = clipboard_cocoa.m; path = ../../src/editor/clipboard_cocoa.m; sourceTree = \"<group>\"; };\n\t\t4836B2DA261860D300F802DD /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };\n\t\t4836B2DE261860FE00F802DD /* ansi.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ansi.c; path = ../../src/editor/ansi.c; sourceTree = \"<group>\"; };\n\t\t4836B2DF261860FE00F802DD /* ansi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ansi.h; path = ../../src/editor/ansi.h; sourceTree = \"<group>\"; };\n\t\t48E8C3482B4114EE006177C3 /* audio_struct.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = audio_struct.h; path = ../../src/audio/audio_struct.h; sourceTree = \"<group>\"; };\n\t\t48E8C34A2B411516006177C3 /* vfs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vfs.h; path = ../../src/io/vfs.h; sourceTree = \"<group>\"; };\n\t\t48E8C34B2B411516006177C3 /* vfs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = vfs.c; path = ../../src/io/vfs.c; sourceTree = \"<group>\"; };\n\t\t48E8C34E2B411560006177C3 /* tempfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = tempfile.h; path = ../../contrib/libxmp/src/tempfile.h; sourceTree = \"<group>\"; };\n\t\t48E8C34F2B411560006177C3 /* far_extras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = far_extras.h; path = ../../contrib/libxmp/src/far_extras.h; sourceTree = \"<group>\"; };\n\t\t48E8C3502B411560006177C3 /* filetype.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = filetype.c; path = ../../contrib/libxmp/src/filetype.c; sourceTree = \"<group>\"; };\n\t\t48E8C3512B411560006177C3 /* far_extras.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = far_extras.c; path = ../../contrib/libxmp/src/far_extras.c; sourceTree = \"<group>\"; };\n\t\t48E8C3522B411561006177C3 /* callbackio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = callbackio.h; path = ../../contrib/libxmp/src/callbackio.h; sourceTree = \"<group>\"; };\n\t\t48E8C3582B4115BD006177C3 /* platform_time.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = platform_time.c; path = ../../src/platform_time.c; sourceTree = \"<group>\"; };\n\t\t48E8C3592B4115BD006177C3 /* about.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = about.h; path = ../../src/about.h; sourceTree = \"<group>\"; };\n\t\t48E8C35A2B4115BD006177C3 /* about.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = about.c; path = ../../src/about.c; sourceTree = \"<group>\"; };\n\t\t48E8C35B2B4115BD006177C3 /* platform_attribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = platform_attribute.h; path = ../../src/platform_attribute.h; sourceTree = \"<group>\"; };\n\t\t48E8C3602B411680006177C3 /* sampled_stream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sampled_stream.cpp; path = ../../src/audio/sampled_stream.cpp; sourceTree = \"<group>\"; };\n\t\t48E8C3622B411D68006177C3 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE; path = ../../LICENSE; sourceTree = \"<group>\"; };\n\t\t48E8C3632B411D75006177C3 /* LICENSE.3rd */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE.3rd; path = ../LICENSE.3rd; sourceTree = \"<group>\"; };\n\t\t48F5B9942C7EFBB000137776 /* core_task.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = core_task.c; path = ../../src/core_task.c; sourceTree = \"<group>\"; };\n\t\t48F5B9952C7EFBB100137776 /* core_task.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = core_task.h; path = ../../src/core_task.h; sourceTree = \"<group>\"; };\n\t\t48F5B99C2C7EFC0900137776 /* edit_export.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = edit_export.c; path = ../../src/editor/edit_export.c; sourceTree = \"<group>\"; };\n\t\t48F5B99D2C7EFC0900137776 /* edit_export.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = edit_export.h; path = ../../src/editor/edit_export.h; sourceTree = \"<group>\"; };\n\t\t731E16462C8AE37500E3D730 /* rng.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = rng.c; path = ../../contrib/libxmp/src/rng.c; sourceTree = \"<group>\"; };\n\t\t731E16472C8AE37500E3D730 /* rng.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rng.h; path = ../../contrib/libxmp/src/rng.h; sourceTree = \"<group>\"; };\n\t\t737459D72D719052006CEE24 /* flow.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = flow.c; path = ../../contrib/libxmp/src/flow.c; sourceTree = \"<group>\"; };\n\t\tBF1012781FCCA7C2008EEDB6 /* MegaZeux.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MegaZeux.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tBF10127E1FCCA7C2008EEDB6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\tBF1012831FCCA7C2008EEDB6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\tBF1012901FCCA957008EEDB6 /* libCore.dylib */ = {isa = PBXFileReference; explicitFileType = \"compiled.mach-o.dylib\"; includeInIndex = 0; path = libCore.dylib; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tBF1012981FCCA97D008EEDB6 /* libEditor.dylib */ = {isa = PBXFileReference; explicitFileType = \"compiled.mach-o.dylib\"; includeInIndex = 0; path = libEditor.dylib; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tBF1012A01FCCA993008EEDB6 /* MZXRun.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MZXRun.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tBF1012A51FCCA993008EEDB6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\tBF1012AA1FCCA993008EEDB6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\tBF1012B71FCCABB4008EEDB6 /* Vorbis.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Vorbis.framework; sourceTree = \"<group>\"; };\n\t\tBF1012B91FCCABB5008EEDB6 /* Ogg.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Ogg.framework; sourceTree = \"<group>\"; };\n\t\tBF1012C01FCCABF0008EEDB6 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SDL2.framework; sourceTree = \"<group>\"; };\n\t\tBF1012C31FCCB02F008EEDB6 /* png.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = png.framework; sourceTree = \"<group>\"; };\n\t\tBF1012C61FCCB0F9008EEDB6 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = \"sourcecode.text-based-dylib-definition\"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };\n\t\tBF3C7A7A22E688000024C630 /* render_softscale.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = render_softscale.c; path = ../../src/render_softscale.c; sourceTree = \"<group>\"; };\n\t\tBF3C7A7D22E6B6A20024C630 /* yuv.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = yuv.h; path = ../../src/yuv.h; sourceTree = \"<group>\"; };\n\t\tBF3C7A7E22E6B7A20024C630 /* gamecontrollerdb.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = gamecontrollerdb.txt; path = ../../assets/gamecontrollerdb.txt; sourceTree = \"<group>\"; };\n\t\tBF6058AC216B3682001B738C /* edit_menu.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = edit_menu.c; path = ../../src/editor/edit_menu.c; sourceTree = \"<group>\"; };\n\t\tBF6058AD216B3682001B738C /* buffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = buffer.h; path = ../../src/editor/buffer.h; sourceTree = \"<group>\"; };\n\t\tBF6058AE216B3682001B738C /* edit_menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = edit_menu.h; path = ../../src/editor/edit_menu.h; sourceTree = \"<group>\"; };\n\t\tBF6058AF216B3682001B738C /* buffer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = buffer.c; path = ../../src/editor/buffer.c; sourceTree = \"<group>\"; };\n\t\tBF6058B0216B3682001B738C /* buffer_struct.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = buffer_struct.h; path = ../../src/editor/buffer_struct.h; sourceTree = \"<group>\"; };\n\t\tBF6058B1216B3683001B738C /* macro_struct.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = macro_struct.h; path = ../../src/editor/macro_struct.h; sourceTree = \"<group>\"; };\n\t\tBF6058B8216B371F001B738C /* extmem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = extmem.h; path = ../../src/extmem.h; sourceTree = \"<group>\"; };\n\t\tBF6058B9216B371F001B738C /* core.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = core.c; path = ../../src/core.c; sourceTree = \"<group>\"; };\n\t\tBF6058BA216B371F001B738C /* game_menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = game_menu.h; path = ../../src/game_menu.h; sourceTree = \"<group>\"; };\n\t\tBF6058BB216B371F001B738C /* board_struct.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = board_struct.h; path = ../../src/board_struct.h; sourceTree = \"<group>\"; };\n\t\tBF6058BC216B371F001B738C /* game_update.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = game_update.c; path = ../../src/game_update.c; sourceTree = \"<group>\"; };\n\t\tBF6058BD216B3720001B738C /* compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = compat.h; path = ../../src/compat.h; sourceTree = \"<group>\"; };\n\t\tBF6058BE216B3720001B738C /* game_menu.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = game_menu.c; path = ../../src/game_menu.c; sourceTree = \"<group>\"; };\n\t\tBF6058BF216B3720001B738C /* counter_struct.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = counter_struct.h; path = ../../src/counter_struct.h; sourceTree = \"<group>\"; };\n\t\tBF6058C0216B3720001B738C /* memcasecmp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = memcasecmp.h; path = ../../src/memcasecmp.h; sourceTree = \"<group>\"; };\n\t\tBF6058C1216B3720001B738C /* game_player.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = game_player.c; path = ../../src/game_player.c; sourceTree = \"<group>\"; };\n\t\tBF6058C2216B3720001B738C /* intake_num.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = intake_num.c; path = ../../src/intake_num.c; sourceTree = \"<group>\"; };\n\t\tBF6058C3216B3720001B738C /* rasm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rasm.h; path = ../../src/rasm.h; sourceTree = \"<group>\"; };\n\t\tBF6058C4216B3720001B738C /* game_ops.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = game_ops.h; path = ../../src/game_ops.h; sourceTree = \"<group>\"; };\n\t\tBF6058C5216B3721001B738C /* world_format.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = world_format.h; path = ../../src/world_format.h; sourceTree = \"<group>\"; };\n\t\tBF6058C6216B3721001B738C /* rasm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = rasm.c; path = ../../src/rasm.c; sourceTree = \"<group>\"; };\n\t\tBF6058C7216B3721001B738C /* settings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = settings.h; path = ../../src/settings.h; sourceTree = \"<group>\"; };\n\t\tBF6058C8216B3721001B738C /* caption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = caption.h; path = ../../src/caption.h; sourceTree = \"<group>\"; };\n\t\tBF6058C9216B3722001B738C /* core.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = core.h; path = ../../src/core.h; sourceTree = \"<group>\"; };\n\t\tBF6058CA216B3722001B738C /* game_update_board.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = game_update_board.c; path = ../../src/game_update_board.c; sourceTree = \"<group>\"; };\n\t\tBF6058CB216B3722001B738C /* world_struct.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = world_struct.h; path = ../../src/world_struct.h; sourceTree = \"<group>\"; };\n\t\tBF6058CC216B3722001B738C /* SDLmzx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLmzx.h; path = ../../src/SDLmzx.h; sourceTree = \"<group>\"; };\n\t\tBF6058CD216B3723001B738C /* renderers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = renderers.h; path = ../../src/renderers.h; sourceTree = \"<group>\"; };\n\t\tBF6058CE216B3723001B738C /* caption.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = caption.c; path = ../../src/caption.c; sourceTree = \"<group>\"; };\n\t\tBF6058CF216B3723001B738C /* game_ops.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = game_ops.c; path = ../../src/game_ops.c; sourceTree = \"<group>\"; };\n\t\tBF6058D0216B3723001B738C /* intake_num.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = intake_num.h; path = ../../src/intake_num.h; sourceTree = \"<group>\"; };\n\t\tBF6058D2216B3724001B738C /* sprite_struct.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sprite_struct.h; path = ../../src/sprite_struct.h; sourceTree = \"<group>\"; };\n\t\tBF6058D3216B3724001B738C /* game_update.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = game_update.h; path = ../../src/game_update.h; sourceTree = \"<group>\"; };\n\t\tBF6058D4216B3724001B738C /* game_player.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = game_player.h; path = ../../src/game_player.h; sourceTree = \"<group>\"; };\n\t\tBF6058D5216B3724001B738C /* robot_struct.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = robot_struct.h; path = ../../src/robot_struct.h; sourceTree = \"<group>\"; };\n\t\tBF6058D6216B3724001B738C /* thread_sdl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = thread_sdl.h; path = ../../src/thread_sdl.h; sourceTree = \"<group>\"; };\n\t\tBF6058D7216B3724001B738C /* const.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = const.h; path = ../../src/const.h; sourceTree = \"<group>\"; };\n\t\tBF6058D8216B3724001B738C /* keysym.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = keysym.h; path = ../../src/keysym.h; sourceTree = \"<group>\"; };\n\t\tBF6058D9216B3725001B738C /* settings.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = settings.c; path = ../../src/settings.c; sourceTree = \"<group>\"; };\n\t\tBFA1FB062536752500BB429F /* ice_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ice_load.c; path = ../../contrib/libxmp/src/loaders/ice_load.c; sourceTree = \"<group>\"; };\n\t\tBFA52FD0233AC48100A90CB4 /* audio_reality.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = audio_reality.h; path = ../../src/audio/audio_reality.h; sourceTree = \"<group>\"; };\n\t\tBFA52FD1233AC48100A90CB4 /* audio_reality.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.cpp; fileEncoding = 4; name = audio_reality.cpp; path = ../../src/audio/audio_reality.cpp; sourceTree = \"<group>\"; };\n\t\tBFC0B26520681A9000D28296 /* hmn_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = hmn_load.c; path = ../../contrib/libxmp/src/loaders/hmn_load.c; sourceTree = \"<group>\"; };\n\t\tBFC0B26620681A9000D28296 /* flt_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = flt_load.c; path = ../../contrib/libxmp/src/loaders/flt_load.c; sourceTree = \"<group>\"; };\n\t\tBFC0B26720681A9000D28296 /* st_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = st_load.c; path = ../../contrib/libxmp/src/loaders/st_load.c; sourceTree = \"<group>\"; };\n\t\tBFC1C0412142551800378A4C /* audio_xmp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = audio_xmp.c; path = ../../src/audio/audio_xmp.c; sourceTree = \"<group>\"; };\n\t\tBFC1C0422142551800378A4C /* audio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = audio.h; path = ../../src/audio/audio.h; sourceTree = \"<group>\"; };\n\t\tBFC1C0432142551800378A4C /* audio_xmp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = audio_xmp.h; path = ../../src/audio/audio_xmp.h; sourceTree = \"<group>\"; };\n\t\tBFC1C0442142551800378A4C /* audio_wav.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = audio_wav.h; path = ../../src/audio/audio_wav.h; sourceTree = \"<group>\"; };\n\t\tBFC1C0452142551800378A4C /* audio_wav.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = audio_wav.c; path = ../../src/audio/audio_wav.c; sourceTree = \"<group>\"; };\n\t\tBFC1C0462142551800378A4C /* ext.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ext.c; path = ../../src/audio/ext.c; sourceTree = \"<group>\"; };\n\t\tBFC1C0472142551800378A4C /* audio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = audio.c; path = ../../src/audio/audio.c; sourceTree = \"<group>\"; };\n\t\tBFC1C0482142551800378A4C /* audio_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = audio_sdl.c; path = ../../src/audio/audio_sdl.c; sourceTree = \"<group>\"; };\n\t\tBFC1C0492142551800378A4C /* audio_vorbis.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = audio_vorbis.h; path = ../../src/audio/audio_vorbis.h; sourceTree = \"<group>\"; };\n\t\tBFC1C04A2142551900378A4C /* audio_vorbis.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = audio_vorbis.c; path = ../../src/audio/audio_vorbis.c; sourceTree = \"<group>\"; };\n\t\tBFC1C0512142552700378A4C /* sfx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sfx.h; path = ../../src/audio/sfx.h; sourceTree = \"<group>\"; };\n\t\tBFC1C0522142552700378A4C /* sampled_stream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sampled_stream.h; path = ../../src/audio/sampled_stream.h; sourceTree = \"<group>\"; };\n\t\tBFC1C0532142552800378A4C /* ext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ext.h; path = ../../src/audio/ext.h; sourceTree = \"<group>\"; };\n\t\tBFC1C0542142552800378A4C /* sfx.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sfx.c; path = ../../src/audio/sfx.c; sourceTree = \"<group>\"; };\n\t\tBFC1C0582142585300378A4C /* audio_pcs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = audio_pcs.c; path = ../../src/audio/audio_pcs.c; sourceTree = \"<group>\"; };\n\t\tBFC1C0592142585300378A4C /* audio_pcs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = audio_pcs.h; path = ../../src/audio/audio_pcs.h; sourceTree = \"<group>\"; };\n\t\tBFD5B02D2465AFAB00BC91E9 /* zip_deflate64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip_deflate64.h; path = ../../src/io/zip_deflate64.h; sourceTree = \"<group>\"; };\n\t\tBFD5B02E2465AFAB00BC91E9 /* zip_reduce.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip_reduce.h; path = ../../src/io/zip_reduce.h; sourceTree = \"<group>\"; };\n\t\tBFD5B02F2465AFAB00BC91E9 /* zip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip.c; path = ../../src/io/zip.c; sourceTree = \"<group>\"; };\n\t\tBFD5B0302465AFAC00BC91E9 /* zip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip.h; path = ../../src/io/zip.h; sourceTree = \"<group>\"; };\n\t\tBFD5B0312465AFAC00BC91E9 /* fsafeopen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = fsafeopen.c; path = ../../src/io/fsafeopen.c; sourceTree = \"<group>\"; };\n\t\tBFD5B0322465AFAC00BC91E9 /* vfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vfile.h; path = ../../src/io/vfile.h; sourceTree = \"<group>\"; };\n\t\tBFD5B0332465AFAC00BC91E9 /* zip_stream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip_stream.h; path = ../../src/io/zip_stream.h; sourceTree = \"<group>\"; };\n\t\tBFD5B0352465AFAC00BC91E9 /* zip_stream.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_stream.c; path = ../../src/io/zip_stream.c; sourceTree = \"<group>\"; };\n\t\tBFD5B0362465AFAC00BC91E9 /* vio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = vio.c; path = ../../src/io/vio.c; sourceTree = \"<group>\"; };\n\t\tBFD5B0372465AFAC00BC91E9 /* path.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = path.c; path = ../../src/io/path.c; sourceTree = \"<group>\"; };\n\t\tBFD5B0382465AFAC00BC91E9 /* zip_deflate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip_deflate.h; path = ../../src/io/zip_deflate.h; sourceTree = \"<group>\"; };\n\t\tBFD5B0392465AFAC00BC91E9 /* fsafeopen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fsafeopen.h; path = ../../src/io/fsafeopen.h; sourceTree = \"<group>\"; };\n\t\tBFD5B03A2465AFAD00BC91E9 /* path.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = path.h; path = ../../src/io/path.h; sourceTree = \"<group>\"; };\n\t\tBFD5B03B2465AFAD00BC91E9 /* bitstream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bitstream.h; path = ../../src/io/bitstream.h; sourceTree = \"<group>\"; };\n\t\tBFD5B03C2465AFAD00BC91E9 /* zip_implode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip_implode.h; path = ../../src/io/zip_implode.h; sourceTree = \"<group>\"; };\n\t\tBFD5B03D2465AFAD00BC91E9 /* memfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = memfile.h; path = ../../src/io/memfile.h; sourceTree = \"<group>\"; };\n\t\tBFD5B03E2465AFAD00BC91E9 /* zip_shrink.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip_shrink.h; path = ../../src/io/zip_shrink.h; sourceTree = \"<group>\"; };\n\t\tBFD5B03F2465AFAD00BC91E9 /* vio_posix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vio_posix.h; path = ../../src/io/vio_posix.h; sourceTree = \"<group>\"; };\n\t\tBFD5B0402465AFAD00BC91E9 /* zip_dict.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip_dict.h; path = ../../src/io/zip_dict.h; sourceTree = \"<group>\"; };\n\t\tBFD5B0572465AFEB00BC91E9 /* hashtable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = hashtable.h; path = ../../src/hashtable.h; sourceTree = \"<group>\"; };\n\t\tBFD5B0592465B0C400BC91E9 /* stringsearch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stringsearch.h; path = ../../src/editor/stringsearch.h; sourceTree = \"<group>\"; };\n\t\tBFD5B05A2465B0C400BC91E9 /* stringsearch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = stringsearch.c; path = ../../src/editor/stringsearch.c; sourceTree = \"<group>\"; };\n\t\tBFD67B3B2410ABE200AF114E /* render_layer_code.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = render_layer_code.hpp; path = ../../src/render_layer_code.hpp; sourceTree = \"<group>\"; };\n\t\tBFD67B3C2410ABE200AF114E /* render_layer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = render_layer.cpp; path = ../../src/render_layer.cpp; sourceTree = \"<group>\"; };\n\t\tBFE5EB622006DB4300E15CC0 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = \"<group>\"; };\n\t\tBFFF16361FCDC64600BDEC58 /* util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = util.h; path = ../../src/util.h; sourceTree = \"<group>\"; };\n\t\tBFFF16381FCDC64600BDEC58 /* board.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = board.h; path = ../../src/board.h; sourceTree = \"<group>\"; };\n\t\tBFFF16391FCDC64600BDEC58 /* robot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = robot.h; path = ../../src/robot.h; sourceTree = \"<group>\"; };\n\t\tBFFF163A1FCDC64600BDEC58 /* board.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = board.c; path = ../../src/board.c; sourceTree = \"<group>\"; };\n\t\tBFFF163C1FCDC64600BDEC58 /* error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = error.h; path = ../../src/error.h; sourceTree = \"<group>\"; };\n\t\tBFFF163D1FCDC64700BDEC58 /* data.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = data.c; path = ../../src/data.c; sourceTree = \"<group>\"; };\n\t\tBFFF163E1FCDC64700BDEC58 /* block.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = block.c; path = ../../src/block.c; sourceTree = \"<group>\"; };\n\t\tBFFF163F1FCDC64700BDEC58 /* idarray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = idarray.h; path = ../../src/idarray.h; sourceTree = \"<group>\"; };\n\t\tBFFF16401FCDC64700BDEC58 /* event.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = event.c; path = ../../src/event.c; sourceTree = \"<group>\"; };\n\t\tBFFF16411FCDC64700BDEC58 /* str.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = str.c; path = ../../src/str.c; sourceTree = \"<group>\"; };\n\t\tBFFF16421FCDC64700BDEC58 /* mzm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mzm.c; path = ../../src/mzm.c; sourceTree = \"<group>\"; };\n\t\tBFFF16431FCDC64700BDEC58 /* legacy_board.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = legacy_board.h; path = ../../src/legacy_board.h; sourceTree = \"<group>\"; };\n\t\tBFFF16441FCDC64700BDEC58 /* data.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = data.h; path = ../../src/data.h; sourceTree = \"<group>\"; };\n\t\tBFFF16451FCDC64700BDEC58 /* robot.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = robot.c; path = ../../src/robot.c; sourceTree = \"<group>\"; };\n\t\tBFFF16461FCDC64700BDEC58 /* expr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = expr.h; path = ../../src/expr.h; sourceTree = \"<group>\"; };\n\t\tBFFF16471FCDC64700BDEC58 /* legacy_world.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = legacy_world.c; path = ../../src/legacy_world.c; sourceTree = \"<group>\"; };\n\t\tBFFF164B1FCDC64800BDEC58 /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = error.c; path = ../../src/error.c; sourceTree = \"<group>\"; };\n\t\tBFFF164C1FCDC64800BDEC58 /* legacy_robot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = legacy_robot.h; path = ../../src/legacy_robot.h; sourceTree = \"<group>\"; };\n\t\tBFFF164D1FCDC64800BDEC58 /* game.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = game.h; path = ../../src/game.h; sourceTree = \"<group>\"; };\n\t\tBFFF164E1FCDC64800BDEC58 /* configure.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = configure.c; path = ../../src/configure.c; sourceTree = \"<group>\"; };\n\t\tBFFF164F1FCDC64800BDEC58 /* window.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = window.h; path = ../../src/window.h; sourceTree = \"<group>\"; };\n\t\tBFFF16501FCDC64900BDEC58 /* legacy_board.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = legacy_board.c; path = ../../src/legacy_board.c; sourceTree = \"<group>\"; };\n\t\tBFFF16511FCDC64900BDEC58 /* scrdisp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = scrdisp.c; path = ../../src/scrdisp.c; sourceTree = \"<group>\"; };\n\t\tBFFF16521FCDC64900BDEC58 /* legacy_world.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = legacy_world.h; path = ../../src/legacy_world.h; sourceTree = \"<group>\"; };\n\t\tBFFF16531FCDC64900BDEC58 /* window.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = window.c; path = ../../src/window.c; sourceTree = \"<group>\"; };\n\t\tBFFF16541FCDC64900BDEC58 /* idput.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = idput.c; path = ../../src/idput.c; sourceTree = \"<group>\"; };\n\t\tBFFF16561FCDC64900BDEC58 /* graphics.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = graphics.c; path = ../../src/graphics.c; sourceTree = \"<group>\"; };\n\t\tBFFF16571FCDC64A00BDEC58 /* render_layer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = render_layer.h; path = ../../src/render_layer.h; sourceTree = \"<group>\"; };\n\t\tBFFF16581FCDC64A00BDEC58 /* scrdisp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = scrdisp.h; path = ../../src/scrdisp.h; sourceTree = \"<group>\"; };\n\t\tBFFF16591FCDC64A00BDEC58 /* world.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = world.c; path = ../../src/world.c; sourceTree = \"<group>\"; };\n\t\tBFFF165A1FCDC64A00BDEC58 /* configure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = configure.h; path = ../../src/configure.h; sourceTree = \"<group>\"; };\n\t\tBFFF165B1FCDC64A00BDEC58 /* expr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = expr.c; path = ../../src/expr.c; sourceTree = \"<group>\"; };\n\t\tBFFF165C1FCDC64A00BDEC58 /* idput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = idput.h; path = ../../src/idput.h; sourceTree = \"<group>\"; };\n\t\tBFFF165D1FCDC64A00BDEC58 /* legacy_robot.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = legacy_robot.c; path = ../../src/legacy_robot.c; sourceTree = \"<group>\"; };\n\t\tBFFF165E1FCDC64B00BDEC58 /* sprite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sprite.h; path = ../../src/sprite.h; sourceTree = \"<group>\"; };\n\t\tBFFF165F1FCDC64B00BDEC58 /* world.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = world.h; path = ../../src/world.h; sourceTree = \"<group>\"; };\n\t\tBFFF16601FCDC64B00BDEC58 /* sprite.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sprite.c; path = ../../src/sprite.c; sourceTree = \"<group>\"; };\n\t\tBFFF16611FCDC64B00BDEC58 /* legacy_rasm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = legacy_rasm.h; path = ../../src/legacy_rasm.h; sourceTree = \"<group>\"; };\n\t\tBFFF16621FCDC64B00BDEC58 /* str.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = str.h; path = ../../src/str.h; sourceTree = \"<group>\"; };\n\t\tBFFF16641FCDC64B00BDEC58 /* mzm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mzm.h; path = ../../src/mzm.h; sourceTree = \"<group>\"; };\n\t\tBFFF16651FCDC64C00BDEC58 /* util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = util.c; path = ../../src/util.c; sourceTree = \"<group>\"; };\n\t\tBFFF16661FCDC64C00BDEC58 /* intake.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = intake.c; path = ../../src/intake.c; sourceTree = \"<group>\"; };\n\t\tBFFF16671FCDC64C00BDEC58 /* run_robot.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = run_robot.c; path = ../../src/run_robot.c; sourceTree = \"<group>\"; };\n\t\tBFFF16681FCDC64C00BDEC58 /* game.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = game.c; path = ../../src/game.c; sourceTree = \"<group>\"; };\n\t\tBFFF16691FCDC64C00BDEC58 /* render.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = render.h; path = ../../src/render.h; sourceTree = \"<group>\"; };\n\t\tBFFF166A1FCDC64C00BDEC58 /* legacy_rasm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = legacy_rasm.c; path = ../../src/legacy_rasm.c; sourceTree = \"<group>\"; };\n\t\tBFFF166C1FCDC64D00BDEC58 /* counter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = counter.h; path = ../../src/counter.h; sourceTree = \"<group>\"; };\n\t\tBFFF166D1FCDC64D00BDEC58 /* render.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = render.c; path = ../../src/render.c; sourceTree = \"<group>\"; };\n\t\tBFFF166E1FCDC64D00BDEC58 /* intake.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = intake.h; path = ../../src/intake.h; sourceTree = \"<group>\"; };\n\t\tBFFF166F1FCDC64D00BDEC58 /* event.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = event.h; path = ../../src/event.h; sourceTree = \"<group>\"; };\n\t\tBFFF16711FCDC64D00BDEC58 /* graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = graphics.h; path = ../../src/graphics.h; sourceTree = \"<group>\"; };\n\t\tBFFF16721FCDC64D00BDEC58 /* idarray.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = idarray.c; path = ../../src/idarray.c; sourceTree = \"<group>\"; };\n\t\tBFFF16731FCDC64D00BDEC58 /* counter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = counter.c; path = ../../src/counter.c; sourceTree = \"<group>\"; };\n\t\tBFFF16741FCDC64E00BDEC58 /* block.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = block.h; path = ../../src/block.h; sourceTree = \"<group>\"; };\n\t\tBFFF16951FCDC6D000BDEC58 /* render_gl2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = render_gl2.c; path = ../../src/render_gl2.c; sourceTree = \"<group>\"; };\n\t\tBFFF16961FCDC6D000BDEC58 /* event_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = event_sdl.c; path = ../../src/event_sdl.c; sourceTree = \"<group>\"; };\n\t\tBFFF16971FCDC6D000BDEC58 /* render_sdl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = render_sdl.h; path = ../../src/render_sdl.h; sourceTree = \"<group>\"; };\n\t\tBFFF16991FCDC6D100BDEC58 /* render_gl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = render_gl.c; path = ../../src/render_gl.c; sourceTree = \"<group>\"; };\n\t\tBFFF169D1FCDC6D200BDEC58 /* render_gl1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = render_gl1.c; path = ../../src/render_gl1.c; sourceTree = \"<group>\"; };\n\t\tBFFF169E1FCDC6D200BDEC58 /* render_glsl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = render_glsl.c; path = ../../src/render_glsl.c; sourceTree = \"<group>\"; };\n\t\tBFFF16A31FCDC6D200BDEC58 /* platform_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = platform_sdl.c; path = ../../src/platform_sdl.c; sourceTree = \"<group>\"; };\n\t\tBFFF16A41FCDC6D300BDEC58 /* render_soft.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = render_soft.c; path = ../../src/render_soft.c; sourceTree = \"<group>\"; };\n\t\tBFFF16A61FCDC6D300BDEC58 /* render_gl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = render_gl.h; path = ../../src/render_gl.h; sourceTree = \"<group>\"; };\n\t\tBFFF16A71FCDC6D300BDEC58 /* render_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = render_sdl.c; path = ../../src/render_sdl.c; sourceTree = \"<group>\"; };\n\t\tBFFF16A81FCDC6D300BDEC58 /* platform_endian.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = platform_endian.h; path = ../../src/platform_endian.h; sourceTree = \"<group>\"; };\n\t\tBFFF16A91FCDC6D300BDEC58 /* platform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = platform.h; path = ../../src/platform.h; sourceTree = \"<group>\"; };\n\t\tBFFF17101FCDC81000BDEC58 /* config.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = \"<group>\"; };\n\t\tBFFF17201FCDCBDB00BDEC58 /* md5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = md5.h; path = ../../contrib/libxmp/src/md5.h; sourceTree = \"<group>\"; };\n\t\tBFFF17211FCDCBDB00BDEC58 /* effects.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = effects.c; path = ../../contrib/libxmp/src/effects.c; sourceTree = \"<group>\"; };\n\t\tBFFF17221FCDCBDB00BDEC58 /* filter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = filter.c; path = ../../contrib/libxmp/src/filter.c; sourceTree = \"<group>\"; };\n\t\tBFFF17241FCDCBDB00BDEC58 /* read_event.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = read_event.c; path = ../../contrib/libxmp/src/read_event.c; sourceTree = \"<group>\"; };\n\t\tBFFF17251FCDCBDB00BDEC58 /* scan.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = scan.c; path = ../../contrib/libxmp/src/scan.c; sourceTree = \"<group>\"; };\n\t\tBFFF17271FCDCBDC00BDEC58 /* lfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lfo.h; path = ../../contrib/libxmp/src/lfo.h; sourceTree = \"<group>\"; };\n\t\tBFFF17281FCDCBDC00BDEC58 /* med_extras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = med_extras.h; path = ../../contrib/libxmp/src/med_extras.h; sourceTree = \"<group>\"; };\n\t\tBFFF17291FCDCBDC00BDEC58 /* memio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = memio.c; path = ../../contrib/libxmp/src/memio.c; sourceTree = \"<group>\"; };\n\t\tBFFF172A1FCDCBDC00BDEC58 /* period.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = period.h; path = ../../contrib/libxmp/src/period.h; sourceTree = \"<group>\"; };\n\t\tBFFF172C1FCDCBDF00BDEC58 /* player.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = player.c; path = ../../contrib/libxmp/src/player.c; sourceTree = \"<group>\"; };\n\t\tBFFF172D1FCDCBDF00BDEC58 /* virtual.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = virtual.c; path = ../../contrib/libxmp/src/virtual.c; sourceTree = \"<group>\"; };\n\t\tBFFF172E1FCDCBDF00BDEC58 /* md5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = md5.c; path = ../../contrib/libxmp/src/md5.c; sourceTree = \"<group>\"; };\n\t\tBFFF172F1FCDCBDF00BDEC58 /* player.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = player.h; path = ../../contrib/libxmp/src/player.h; sourceTree = \"<group>\"; };\n\t\tBFFF17301FCDCBE000BDEC58 /* lfo.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = lfo.c; path = ../../contrib/libxmp/src/lfo.c; sourceTree = \"<group>\"; };\n\t\tBFFF17311FCDCBE000BDEC58 /* format.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = format.h; path = ../../contrib/libxmp/src/format.h; sourceTree = \"<group>\"; };\n\t\tBFFF17321FCDCBE000BDEC58 /* effects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = effects.h; path = ../../contrib/libxmp/src/effects.h; sourceTree = \"<group>\"; };\n\t\tBFFF17331FCDCBE200BDEC58 /* mixer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mixer.c; path = ../../contrib/libxmp/src/mixer.c; sourceTree = \"<group>\"; };\n\t\tBFFF17341FCDCBE200BDEC58 /* smix.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = smix.c; path = ../../contrib/libxmp/src/smix.c; sourceTree = \"<group>\"; };\n\t\tBFFF17351FCDCBE200BDEC58 /* hmn_extras.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = hmn_extras.c; path = ../../contrib/libxmp/src/hmn_extras.c; sourceTree = \"<group>\"; };\n\t\tBFFF17361FCDCBE200BDEC58 /* hmn_extras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = hmn_extras.h; path = ../../contrib/libxmp/src/hmn_extras.h; sourceTree = \"<group>\"; };\n\t\tBFFF17371FCDCBE200BDEC58 /* precomp_lut.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = precomp_lut.h; path = ../../contrib/libxmp/src/precomp_lut.h; sourceTree = \"<group>\"; };\n\t\tBFFF17381FCDCBE200BDEC58 /* virtual.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = virtual.h; path = ../../contrib/libxmp/src/virtual.h; sourceTree = \"<group>\"; };\n\t\tBFFF17391FCDCBE300BDEC58 /* load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = load.c; path = ../../contrib/libxmp/src/load.c; sourceTree = \"<group>\"; };\n\t\tBFFF173A1FCDCBE300BDEC58 /* format.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = format.c; path = ../../contrib/libxmp/src/format.c; sourceTree = \"<group>\"; };\n\t\tBFFF173B1FCDCBE300BDEC58 /* memio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = memio.h; path = ../../contrib/libxmp/src/memio.h; sourceTree = \"<group>\"; };\n\t\tBFFF173C1FCDCBE300BDEC58 /* extras.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = extras.c; path = ../../contrib/libxmp/src/extras.c; sourceTree = \"<group>\"; };\n\t\tBFFF173D1FCDCBE300BDEC58 /* extras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = extras.h; path = ../../contrib/libxmp/src/extras.h; sourceTree = \"<group>\"; };\n\t\tBFFF173F1FCDCBE300BDEC58 /* hio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = hio.h; path = ../../contrib/libxmp/src/hio.h; sourceTree = \"<group>\"; };\n\t\tBFFF17401FCDCBE400BDEC58 /* hio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = hio.c; path = ../../contrib/libxmp/src/hio.c; sourceTree = \"<group>\"; };\n\t\tBFFF17411FCDCBE400BDEC58 /* mix_all.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mix_all.c; path = ../../contrib/libxmp/src/mix_all.c; sourceTree = \"<group>\"; };\n\t\tBFFF17421FCDCBE400BDEC58 /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = common.h; path = ../../contrib/libxmp/src/common.h; sourceTree = \"<group>\"; };\n\t\tBFFF17441FCDCBE400BDEC58 /* period.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = period.c; path = ../../contrib/libxmp/src/period.c; sourceTree = \"<group>\"; };\n\t\tBFFF17451FCDCBE400BDEC58 /* load_helpers.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = load_helpers.c; path = ../../contrib/libxmp/src/load_helpers.c; sourceTree = \"<group>\"; };\n\t\tBFFF17461FCDCBE400BDEC58 /* med_extras.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = med_extras.c; path = ../../contrib/libxmp/src/med_extras.c; sourceTree = \"<group>\"; };\n\t\tBFFF17481FCDCBE500BDEC58 /* mixer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mixer.h; path = ../../contrib/libxmp/src/mixer.h; sourceTree = \"<group>\"; };\n\t\tBFFF17491FCDCBE500BDEC58 /* list.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = list.h; path = ../../contrib/libxmp/src/list.h; sourceTree = \"<group>\"; };\n\t\tBFFF174A1FCDCBE500BDEC58 /* control.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = control.c; path = ../../contrib/libxmp/src/control.c; sourceTree = \"<group>\"; };\n\t\tBFFF174B1FCDCBE500BDEC58 /* mdataio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mdataio.h; path = ../../contrib/libxmp/src/mdataio.h; sourceTree = \"<group>\"; };\n\t\tBFFF174D1FCDCBE600BDEC58 /* dataio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dataio.c; path = ../../contrib/libxmp/src/dataio.c; sourceTree = \"<group>\"; };\n\t\tBFFF17681FCDCBFD00BDEC58 /* asylum_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = asylum_load.c; path = ../../contrib/libxmp/src/loaders/asylum_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF176A1FCDCBFE00BDEC58 /* mmd_common.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mmd_common.c; path = ../../contrib/libxmp/src/loaders/mmd_common.c; sourceTree = \"<group>\"; };\n\t\tBFFF176B1FCDCBFE00BDEC58 /* far_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = far_load.c; path = ../../contrib/libxmp/src/loaders/far_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF176C1FCDCBFE00BDEC58 /* iff.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = iff.c; path = ../../contrib/libxmp/src/loaders/iff.c; sourceTree = \"<group>\"; };\n\t\tBFFF176D1FCDCBFE00BDEC58 /* itsex.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = itsex.c; path = ../../contrib/libxmp/src/loaders/itsex.c; sourceTree = \"<group>\"; };\n\t\tBFFF176E1FCDCBFE00BDEC58 /* mmd3_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mmd3_load.c; path = ../../contrib/libxmp/src/loaders/mmd3_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF176F1FCDCBFE00BDEC58 /* mtm_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mtm_load.c; path = ../../contrib/libxmp/src/loaders/mtm_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17711FCDCBFE00BDEC58 /* it.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = it.h; path = ../../contrib/libxmp/src/loaders/it.h; sourceTree = \"<group>\"; };\n\t\tBFFF17721FCDCBFE00BDEC58 /* s3m.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = s3m.h; path = ../../contrib/libxmp/src/loaders/s3m.h; sourceTree = \"<group>\"; };\n\t\tBFFF17731FCDCBFE00BDEC58 /* gdm_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = gdm_load.c; path = ../../contrib/libxmp/src/loaders/gdm_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17741FCDCBFF00BDEC58 /* ult_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ult_load.c; path = ../../contrib/libxmp/src/loaders/ult_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17751FCDCBFF00BDEC58 /* it_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = it_load.c; path = ../../contrib/libxmp/src/loaders/it_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17761FCDCBFF00BDEC58 /* iff.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iff.h; path = ../../contrib/libxmp/src/loaders/iff.h; sourceTree = \"<group>\"; };\n\t\tBFFF17771FCDCBFF00BDEC58 /* mod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mod.h; path = ../../contrib/libxmp/src/loaders/mod.h; sourceTree = \"<group>\"; };\n\t\tBFFF17781FCDCBFF00BDEC58 /* s3m_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = s3m_load.c; path = ../../contrib/libxmp/src/loaders/s3m_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17791FCDCBFF00BDEC58 /* mmd1_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mmd1_load.c; path = ../../contrib/libxmp/src/loaders/mmd1_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF177A1FCDCBFF00BDEC58 /* loader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = loader.h; path = ../../contrib/libxmp/src/loaders/loader.h; sourceTree = \"<group>\"; };\n\t\tBFFF177B1FCDCBFF00BDEC58 /* xm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = xm.h; path = ../../contrib/libxmp/src/loaders/xm.h; sourceTree = \"<group>\"; };\n\t\tBFFF177C1FCDCBFF00BDEC58 /* stm_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = stm_load.c; path = ../../contrib/libxmp/src/loaders/stm_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF177D1FCDCBFF00BDEC58 /* common.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = common.c; path = ../../contrib/libxmp/src/loaders/common.c; sourceTree = \"<group>\"; };\n\t\tBFFF177F1FCDCC0000BDEC58 /* med4_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = med4_load.c; path = ../../contrib/libxmp/src/loaders/med4_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17801FCDCC0000BDEC58 /* med3_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = med3_load.c; path = ../../contrib/libxmp/src/loaders/med3_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17811FCDCC0000BDEC58 /* amf_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = amf_load.c; path = ../../contrib/libxmp/src/loaders/amf_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17821FCDCC0000BDEC58 /* 669_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = 669_load.c; path = ../../contrib/libxmp/src/loaders/669_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17831FCDCC0000BDEC58 /* med.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = med.h; path = ../../contrib/libxmp/src/loaders/med.h; sourceTree = \"<group>\"; };\n\t\tBFFF17841FCDCC0000BDEC58 /* okt_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = okt_load.c; path = ../../contrib/libxmp/src/loaders/okt_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17851FCDCC0000BDEC58 /* med2_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = med2_load.c; path = ../../contrib/libxmp/src/loaders/med2_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17861FCDCC0100BDEC58 /* mod_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mod_load.c; path = ../../contrib/libxmp/src/loaders/mod_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17871FCDCC0100BDEC58 /* xm_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = xm_load.c; path = ../../contrib/libxmp/src/loaders/xm_load.c; sourceTree = \"<group>\"; };\n\t\tBFFF17881FCDCC0100BDEC58 /* sample.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sample.c; path = ../../contrib/libxmp/src/loaders/sample.c; sourceTree = \"<group>\"; };\n\t\tBFFF18121FCDCD7A00BDEC58 /* pngops.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = pngops.c; path = ../../src/pngops.c; sourceTree = \"<group>\"; };\n\t\tBFFF18131FCDCD7B00BDEC58 /* pngops.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = pngops.h; path = ../../src/pngops.h; sourceTree = \"<group>\"; };\n\t\tBFFF18141FCDCD7B00BDEC58 /* helpsys.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = helpsys.c; path = ../../src/helpsys.c; sourceTree = \"<group>\"; };\n\t\tBFFF18151FCDCD7B00BDEC58 /* helpsys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = helpsys.h; path = ../../src/helpsys.h; sourceTree = \"<group>\"; };\n\t\tBFFF181D1FCDCF6200BDEC58 /* pal_ed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = pal_ed.h; path = ../../src/editor/pal_ed.h; sourceTree = \"<group>\"; };\n\t\tBFFF181E1FCDCF6200BDEC58 /* window.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = window.h; path = ../../src/editor/window.h; sourceTree = \"<group>\"; };\n\t\tBFFF181F1FCDCF6200BDEC58 /* macro.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = macro.h; path = ../../src/editor/macro.h; sourceTree = \"<group>\"; };\n\t\tBFFF18201FCDCF6200BDEC58 /* world.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = world.h; path = ../../src/editor/world.h; sourceTree = \"<group>\"; };\n\t\tBFFF18211FCDCF6200BDEC58 /* clipboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = clipboard.h; path = ../../src/editor/clipboard.h; sourceTree = \"<group>\"; };\n\t\tBFFF18221FCDCF6300BDEC58 /* sfx_edit.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sfx_edit.c; path = ../../src/editor/sfx_edit.c; sourceTree = \"<group>\"; };\n\t\tBFFF18231FCDCF6300BDEC58 /* robot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = robot.h; path = ../../src/editor/robot.h; sourceTree = \"<group>\"; };\n\t\tBFFF18241FCDCF6300BDEC58 /* configure.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = configure.c; path = ../../src/editor/configure.c; sourceTree = \"<group>\"; };\n\t\tBFFF18251FCDCF6300BDEC58 /* fill.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = fill.c; path = ../../src/editor/fill.c; sourceTree = \"<group>\"; };\n\t\tBFFF18261FCDCF6300BDEC58 /* pal_ed.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = pal_ed.c; path = ../../src/editor/pal_ed.c; sourceTree = \"<group>\"; };\n\t\tBFFF18271FCDCF6300BDEC58 /* char_ed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = char_ed.h; path = ../../src/editor/char_ed.h; sourceTree = \"<group>\"; };\n\t\tBFFF18281FCDCF6300BDEC58 /* undo.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = undo.c; path = ../../src/editor/undo.c; sourceTree = \"<group>\"; };\n\t\tBFFF18291FCDCF6300BDEC58 /* window.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = window.c; path = ../../src/editor/window.c; sourceTree = \"<group>\"; };\n\t\tBFFF182A1FCDCF6300BDEC58 /* edit_di.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = edit_di.c; path = ../../src/editor/edit_di.c; sourceTree = \"<group>\"; };\n\t\tBFFF182B1FCDCF6400BDEC58 /* debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = debug.h; path = ../../src/editor/debug.h; sourceTree = \"<group>\"; };\n\t\tBFFF182C1FCDCF6400BDEC58 /* robot.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = robot.c; path = ../../src/editor/robot.c; sourceTree = \"<group>\"; };\n\t\tBFFF182D1FCDCF6400BDEC58 /* sfx_edit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sfx_edit.h; path = ../../src/editor/sfx_edit.h; sourceTree = \"<group>\"; };\n\t\tBFFF182E1FCDCF6400BDEC58 /* block.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = block.c; path = ../../src/editor/block.c; sourceTree = \"<group>\"; };\n\t\tBFFF182F1FCDCF6400BDEC58 /* edit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = edit.h; path = ../../src/editor/edit.h; sourceTree = \"<group>\"; };\n\t\tBFFF18301FCDCF6400BDEC58 /* edit.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = edit.c; path = ../../src/editor/edit.c; sourceTree = \"<group>\"; };\n\t\tBFFF18311FCDCF6400BDEC58 /* board.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = board.h; path = ../../src/editor/board.h; sourceTree = \"<group>\"; };\n\t\tBFFF18321FCDCF6400BDEC58 /* robo_debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = robo_debug.h; path = ../../src/editor/robo_debug.h; sourceTree = \"<group>\"; };\n\t\tBFFF18331FCDCF6500BDEC58 /* edit_di.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = edit_di.h; path = ../../src/editor/edit_di.h; sourceTree = \"<group>\"; };\n\t\tBFFF18341FCDCF6500BDEC58 /* robo_ed.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = robo_ed.c; path = ../../src/editor/robo_ed.c; sourceTree = \"<group>\"; };\n\t\tBFFF18351FCDCF6500BDEC58 /* configure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = configure.h; path = ../../src/editor/configure.h; sourceTree = \"<group>\"; };\n\t\tBFFF18361FCDCF6500BDEC58 /* fill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fill.h; path = ../../src/editor/fill.h; sourceTree = \"<group>\"; };\n\t\tBFFF18371FCDCF6500BDEC58 /* macro.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = macro.c; path = ../../src/editor/macro.c; sourceTree = \"<group>\"; };\n\t\tBFFF18381FCDCF6500BDEC58 /* param.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = param.h; path = ../../src/editor/param.h; sourceTree = \"<group>\"; };\n\t\tBFFF18391FCDCF6500BDEC58 /* select.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = select.c; path = ../../src/editor/select.c; sourceTree = \"<group>\"; };\n\t\tBFFF183A1FCDCF6600BDEC58 /* debug.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = debug.c; path = ../../src/editor/debug.c; sourceTree = \"<group>\"; };\n\t\tBFFF183B1FCDCF6600BDEC58 /* world.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = world.c; path = ../../src/editor/world.c; sourceTree = \"<group>\"; };\n\t\tBFFF183D1FCDCF6600BDEC58 /* graphics.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = graphics.c; path = ../../src/editor/graphics.c; sourceTree = \"<group>\"; };\n\t\tBFFF183E1FCDCF6600BDEC58 /* robo_debug.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = robo_debug.c; path = ../../src/editor/robo_debug.c; sourceTree = \"<group>\"; };\n\t\tBFFF183F1FCDCF6600BDEC58 /* board.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = board.c; path = ../../src/editor/board.c; sourceTree = \"<group>\"; };\n\t\tBFFF18401FCDCF6600BDEC58 /* char_ed.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = char_ed.c; path = ../../src/editor/char_ed.c; sourceTree = \"<group>\"; };\n\t\tBFFF18411FCDCF6700BDEC58 /* param.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = param.c; path = ../../src/editor/param.c; sourceTree = \"<group>\"; };\n\t\tBFFF18421FCDCF6700BDEC58 /* select.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = select.h; path = ../../src/editor/select.h; sourceTree = \"<group>\"; };\n\t\tBFFF18431FCDCF6700BDEC58 /* robo_ed.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = robo_ed.h; path = ../../src/editor/robo_ed.h; sourceTree = \"<group>\"; };\n\t\tBFFF18441FCDCF6700BDEC58 /* undo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = undo.h; path = ../../src/editor/undo.h; sourceTree = \"<group>\"; };\n\t\tBFFF18451FCDCF6700BDEC58 /* graphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = graphics.h; path = ../../src/editor/graphics.h; sourceTree = \"<group>\"; };\n\t\tBFFF18461FCDCF6800BDEC58 /* block.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = block.h; path = ../../src/editor/block.h; sourceTree = \"<group>\"; };\n\t\tBFFF188B1FCDE74B00BDEC58 /* main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../../src/main.c; sourceTree = \"<group>\"; };\n\t\tBFFF18951FCDE8B300BDEC58 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = ../../assets; sourceTree = \"<group>\"; };\n\t\tBFFF18981FCDE8FD00BDEC58 /* config.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = config.txt; path = ../../config.txt; sourceTree = \"<group>\"; };\n\t\tBFFF18A41FCDEAF400BDEC58 /* run_stubs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = run_stubs.c; path = ../../../src/run_stubs.c; sourceTree = \"<group>\"; };\n\t\tBFFF18A51FCDEAF400BDEC58 /* run_stubs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = run_stubs.h; path = ../../../src/run_stubs.h; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\tBF1012751FCCA7C2008EEDB6 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tBFFF188E1FCDE81900BDEC58 /* libEditor.dylib in Frameworks */,\n\t\t\t\tBFFF188D1FCDE81600BDEC58 /* libCore.dylib in Frameworks */,\n\t\t\t\tBF1012C41FCCB02F008EEDB6 /* png.framework in Frameworks */,\n\t\t\t\tBF1012BE1FCCABB5008EEDB6 /* Ogg.framework in Frameworks */,\n\t\t\t\tBF1012BA1FCCABB5008EEDB6 /* Vorbis.framework in Frameworks */,\n\t\t\t\tBF1012C11FCCABF0008EEDB6 /* SDL2.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tBF10128D1FCCA957008EEDB6 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tBFFF170C1FCDC7AC00BDEC58 /* png.framework in Frameworks */,\n\t\t\t\tBFFF170D1FCDC7AC00BDEC58 /* Ogg.framework in Frameworks */,\n\t\t\t\tBFFF170E1FCDC7AC00BDEC58 /* SDL2.framework in Frameworks */,\n\t\t\t\tBFFF170F1FCDC7AC00BDEC58 /* Vorbis.framework in Frameworks */,\n\t\t\t\tBFFF16341FCDC57B00BDEC58 /* libz.tbd in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tBF1012951FCCA97D008EEDB6 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t4836B2DB261860D300F802DD /* AppKit.framework in Frameworks */,\n\t\t\t\tBFFF18A81FCE146E00BDEC58 /* SDL2.framework in Frameworks */,\n\t\t\t\tBFFF188A1FCDD2DC00BDEC58 /* libCore.dylib in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tBF10129D1FCCA993008EEDB6 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tBFFF189C1FCDEA5700BDEC58 /* libCore.dylib in Frameworks */,\n\t\t\t\tBFFF162C1FCDC55000BDEC58 /* Ogg.framework in Frameworks */,\n\t\t\t\tBFFF162E1FCDC55000BDEC58 /* Vorbis.framework in Frameworks */,\n\t\t\t\tBFFF16281FCDC55000BDEC58 /* png.framework in Frameworks */,\n\t\t\t\tBFFF162A1FCDC55000BDEC58 /* SDL2.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\tBF10126F1FCCA7C1008EEDB6 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t48E8C3622B411D68006177C3 /* LICENSE */,\n\t\t\t\t48E8C3632B411D75006177C3 /* LICENSE.3rd */,\n\t\t\t\tBFFF17101FCDC81000BDEC58 /* config.h */,\n\t\t\t\tBFFF18981FCDE8FD00BDEC58 /* config.txt */,\n\t\t\t\tBF3C7A7E22E6B7A20024C630 /* gamecontrollerdb.txt */,\n\t\t\t\tBFE5EB622006DB4300E15CC0 /* version.h */,\n\t\t\t\tBFFF18951FCDE8B300BDEC58 /* assets */,\n\t\t\t\tBFFF16351FCDC5AE00BDEC58 /* Core */,\n\t\t\t\tBFFF181C1FCDCEE800BDEC58 /* Editor */,\n\t\t\t\tBF10127A1FCCA7C2008EEDB6 /* MegaZeux */,\n\t\t\t\tBF1012A11FCCA993008EEDB6 /* MZXRun */,\n\t\t\t\tBF1012C31FCCB02F008EEDB6 /* png.framework */,\n\t\t\t\tBF1012C01FCCABF0008EEDB6 /* SDL2.framework */,\n\t\t\t\tBF1012B91FCCABB5008EEDB6 /* Ogg.framework */,\n\t\t\t\tBF1012B71FCCABB4008EEDB6 /* Vorbis.framework */,\n\t\t\t\tBF1012791FCCA7C2008EEDB6 /* Products */,\n\t\t\t\tBF1012B11FCCAB0D008EEDB6 /* Frameworks */,\n\t\t\t);\n\t\t\tindentWidth = 2;\n\t\t\tsourceTree = \"<group>\";\n\t\t\ttabWidth = 2;\n\t\t};\n\t\tBF1012791FCCA7C2008EEDB6 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tBF1012781FCCA7C2008EEDB6 /* MegaZeux.app */,\n\t\t\t\tBF1012901FCCA957008EEDB6 /* libCore.dylib */,\n\t\t\t\tBF1012981FCCA97D008EEDB6 /* libEditor.dylib */,\n\t\t\t\tBF1012A01FCCA993008EEDB6 /* MZXRun.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBF10127A1FCCA7C2008EEDB6 /* MegaZeux */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tBFFF188B1FCDE74B00BDEC58 /* main.c */,\n\t\t\t\tBF10127E1FCCA7C2008EEDB6 /* Assets.xcassets */,\n\t\t\t\tBF1012831FCCA7C2008EEDB6 /* Info.plist */,\n\t\t\t);\n\t\t\tpath = MegaZeux;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBF1012A11FCCA993008EEDB6 /* MZXRun */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tBF1012A51FCCA993008EEDB6 /* Assets.xcassets */,\n\t\t\t\tBF1012AA1FCCA993008EEDB6 /* Info.plist */,\n\t\t\t\tBFFF18A41FCDEAF400BDEC58 /* run_stubs.c */,\n\t\t\t\tBFFF18A51FCDEAF400BDEC58 /* run_stubs.h */,\n\t\t\t);\n\t\t\tpath = MZXRun;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBF1012B11FCCAB0D008EEDB6 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t4836B2DA261860D300F802DD /* AppKit.framework */,\n\t\t\t\tBF1012C61FCCB0F9008EEDB6 /* libz.tbd */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBFC1C040214254F500378A4C /* audio */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tBFC1C0472142551800378A4C /* audio.c */,\n\t\t\t\tBFC1C0422142551800378A4C /* audio.h */,\n\t\t\t\tBFC1C0582142585300378A4C /* audio_pcs.c */,\n\t\t\t\tBFC1C0592142585300378A4C /* audio_pcs.h */,\n\t\t\t\tBFA52FD1233AC48100A90CB4 /* audio_reality.cpp */,\n\t\t\t\tBFA52FD0233AC48100A90CB4 /* audio_reality.h */,\n\t\t\t\tBFC1C0482142551800378A4C /* audio_sdl.c */,\n\t\t\t\t48E8C3482B4114EE006177C3 /* audio_struct.h */,\n\t\t\t\tBFC1C04A2142551900378A4C /* audio_vorbis.c */,\n\t\t\t\tBFC1C0492142551800378A4C /* audio_vorbis.h */,\n\t\t\t\tBFC1C0452142551800378A4C /* audio_wav.c */,\n\t\t\t\tBFC1C0442142551800378A4C /* audio_wav.h */,\n\t\t\t\tBFC1C0412142551800378A4C /* audio_xmp.c */,\n\t\t\t\tBFC1C0432142551800378A4C /* audio_xmp.h */,\n\t\t\t\tBFC1C0462142551800378A4C /* ext.c */,\n\t\t\t\tBFC1C0532142552800378A4C /* ext.h */,\n\t\t\t\t48E8C3602B411680006177C3 /* sampled_stream.cpp */,\n\t\t\t\tBFC1C0522142552700378A4C /* sampled_stream.h */,\n\t\t\t\tBFC1C0542142552800378A4C /* sfx.c */,\n\t\t\t\tBFC1C0512142552700378A4C /* sfx.h */,\n\t\t\t);\n\t\t\tname = audio;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBFD5B02C2465AF6000BC91E9 /* io */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tBFD5B03B2465AFAD00BC91E9 /* bitstream.h */,\n\t\t\t\tBFD5B0312465AFAC00BC91E9 /* fsafeopen.c */,\n\t\t\t\tBFD5B0392465AFAC00BC91E9 /* fsafeopen.h */,\n\t\t\t\tBFD5B03D2465AFAD00BC91E9 /* memfile.h */,\n\t\t\t\tBFD5B0372465AFAC00BC91E9 /* path.c */,\n\t\t\t\tBFD5B03A2465AFAD00BC91E9 /* path.h */,\n\t\t\t\tBFD5B0322465AFAC00BC91E9 /* vfile.h */,\n\t\t\t\t48E8C34B2B411516006177C3 /* vfs.c */,\n\t\t\t\t48E8C34A2B411516006177C3 /* vfs.h */,\n\t\t\t\tBFD5B0362465AFAC00BC91E9 /* vio.c */,\n\t\t\t\t482C0E8E2617D38E002D9030 /* vio.h */,\n\t\t\t\tBFD5B03F2465AFAD00BC91E9 /* vio_posix.h */,\n\t\t\t\tBFD5B02F2465AFAB00BC91E9 /* zip.c */,\n\t\t\t\tBFD5B0302465AFAC00BC91E9 /* zip.h */,\n\t\t\t\tBFD5B0382465AFAC00BC91E9 /* zip_deflate.h */,\n\t\t\t\tBFD5B02D2465AFAB00BC91E9 /* zip_deflate64.h */,\n\t\t\t\tBFD5B0402465AFAD00BC91E9 /* zip_dict.h */,\n\t\t\t\tBFD5B03C2465AFAD00BC91E9 /* zip_implode.h */,\n\t\t\t\tBFD5B02E2465AFAB00BC91E9 /* zip_reduce.h */,\n\t\t\t\tBFD5B03E2465AFAD00BC91E9 /* zip_shrink.h */,\n\t\t\t\tBFD5B0352465AFAC00BC91E9 /* zip_stream.c */,\n\t\t\t\tBFD5B0332465AFAC00BC91E9 /* zip_stream.h */,\n\t\t\t);\n\t\t\tname = io;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBFFF16351FCDC5AE00BDEC58 /* Core */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tBFC1C040214254F500378A4C /* audio */,\n\t\t\t\tBFD5B02C2465AF6000BC91E9 /* io */,\n\t\t\t\tBFFF171F1FCDCB6D00BDEC58 /* libxmp */,\n\t\t\t\t48E8C35A2B4115BD006177C3 /* about.c */,\n\t\t\t\t48E8C3592B4115BD006177C3 /* about.h */,\n\t\t\t\tBFFF163E1FCDC64700BDEC58 /* block.c */,\n\t\t\t\tBFFF16741FCDC64E00BDEC58 /* block.h */,\n\t\t\t\tBFFF163A1FCDC64600BDEC58 /* board.c */,\n\t\t\t\tBFFF16381FCDC64600BDEC58 /* board.h */,\n\t\t\t\tBF6058BB216B371F001B738C /* board_struct.h */,\n\t\t\t\tBF6058CE216B3723001B738C /* caption.c */,\n\t\t\t\tBF6058C8216B3721001B738C /* caption.h */,\n\t\t\t\tBF6058BD216B3720001B738C /* compat.h */,\n\t\t\t\tBF6058CC216B3722001B738C /* SDLmzx.h */,\n\t\t\t\tBFFF164E1FCDC64800BDEC58 /* configure.c */,\n\t\t\t\tBFFF165A1FCDC64A00BDEC58 /* configure.h */,\n\t\t\t\tBF6058D7216B3724001B738C /* const.h */,\n\t\t\t\tBF6058B9216B371F001B738C /* core.c */,\n\t\t\t\tBF6058C9216B3722001B738C /* core.h */,\n\t\t\t\t48F5B9942C7EFBB000137776 /* core_task.c */,\n\t\t\t\t48F5B9952C7EFBB100137776 /* core_task.h */,\n\t\t\t\tBFFF16731FCDC64D00BDEC58 /* counter.c */,\n\t\t\t\tBFFF166C1FCDC64D00BDEC58 /* counter.h */,\n\t\t\t\tBF6058BF216B3720001B738C /* counter_struct.h */,\n\t\t\t\tBFFF163D1FCDC64700BDEC58 /* data.c */,\n\t\t\t\tBFFF16441FCDC64700BDEC58 /* data.h */,\n\t\t\t\tBFFF164B1FCDC64800BDEC58 /* error.c */,\n\t\t\t\tBFFF163C1FCDC64600BDEC58 /* error.h */,\n\t\t\t\tBFFF16961FCDC6D000BDEC58 /* event_sdl.c */,\n\t\t\t\tBFFF16401FCDC64700BDEC58 /* event.c */,\n\t\t\t\tBFFF166F1FCDC64D00BDEC58 /* event.h */,\n\t\t\t\tBFFF165B1FCDC64A00BDEC58 /* expr.c */,\n\t\t\t\tBFFF16461FCDC64700BDEC58 /* expr.h */,\n\t\t\t\tBF6058B8216B371F001B738C /* extmem.h */,\n\t\t\t\tBFFF16681FCDC64C00BDEC58 /* game.c */,\n\t\t\t\tBFFF164D1FCDC64800BDEC58 /* game.h */,\n\t\t\t\tBF6058BE216B3720001B738C /* game_menu.c */,\n\t\t\t\tBF6058BA216B371F001B738C /* game_menu.h */,\n\t\t\t\tBF6058CF216B3723001B738C /* game_ops.c */,\n\t\t\t\tBF6058C4216B3720001B738C /* game_ops.h */,\n\t\t\t\tBF6058C1216B3720001B738C /* game_player.c */,\n\t\t\t\tBF6058D4216B3724001B738C /* game_player.h */,\n\t\t\t\tBF6058BC216B371F001B738C /* game_update.c */,\n\t\t\t\tBF6058D3216B3724001B738C /* game_update.h */,\n\t\t\t\tBF6058CA216B3722001B738C /* game_update_board.c */,\n\t\t\t\tBFFF16561FCDC64900BDEC58 /* graphics.c */,\n\t\t\t\tBFFF16711FCDC64D00BDEC58 /* graphics.h */,\n\t\t\t\tBFD5B0572465AFEB00BC91E9 /* hashtable.h */,\n\t\t\t\tBFFF18141FCDCD7B00BDEC58 /* helpsys.c */,\n\t\t\t\tBFFF18151FCDCD7B00BDEC58 /* helpsys.h */,\n\t\t\t\tBFFF16721FCDC64D00BDEC58 /* idarray.c */,\n\t\t\t\tBFFF163F1FCDC64700BDEC58 /* idarray.h */,\n\t\t\t\tBFFF16541FCDC64900BDEC58 /* idput.c */,\n\t\t\t\tBFFF165C1FCDC64A00BDEC58 /* idput.h */,\n\t\t\t\tBFFF16661FCDC64C00BDEC58 /* intake.c */,\n\t\t\t\tBFFF166E1FCDC64D00BDEC58 /* intake.h */,\n\t\t\t\tBF6058C2216B3720001B738C /* intake_num.c */,\n\t\t\t\tBF6058D0216B3723001B738C /* intake_num.h */,\n\t\t\t\tBF6058D8216B3724001B738C /* keysym.h */,\n\t\t\t\tBFFF16501FCDC64900BDEC58 /* legacy_board.c */,\n\t\t\t\tBFFF16431FCDC64700BDEC58 /* legacy_board.h */,\n\t\t\t\tBFFF166A1FCDC64C00BDEC58 /* legacy_rasm.c */,\n\t\t\t\tBFFF16611FCDC64B00BDEC58 /* legacy_rasm.h */,\n\t\t\t\tBFFF165D1FCDC64A00BDEC58 /* legacy_robot.c */,\n\t\t\t\tBFFF164C1FCDC64800BDEC58 /* legacy_robot.h */,\n\t\t\t\tBFFF16471FCDC64700BDEC58 /* legacy_world.c */,\n\t\t\t\tBFFF16521FCDC64900BDEC58 /* legacy_world.h */,\n\t\t\t\tBF6058C0216B3720001B738C /* memcasecmp.h */,\n\t\t\t\tBFFF16421FCDC64700BDEC58 /* mzm.c */,\n\t\t\t\tBFFF16641FCDC64B00BDEC58 /* mzm.h */,\n\t\t\t\tBFFF16A91FCDC6D300BDEC58 /* platform.h */,\n\t\t\t\t48E8C35B2B4115BD006177C3 /* platform_attribute.h */,\n\t\t\t\tBFFF16A81FCDC6D300BDEC58 /* platform_endian.h */,\n\t\t\t\tBFFF16A31FCDC6D200BDEC58 /* platform_sdl.c */,\n\t\t\t\t48E8C3582B4115BD006177C3 /* platform_time.c */,\n\t\t\t\tBFFF18121FCDCD7A00BDEC58 /* pngops.c */,\n\t\t\t\tBFFF18131FCDCD7B00BDEC58 /* pngops.h */,\n\t\t\t\tBF6058C6216B3721001B738C /* rasm.c */,\n\t\t\t\tBF6058C3216B3720001B738C /* rasm.h */,\n\t\t\t\tBFFF166D1FCDC64D00BDEC58 /* render.c */,\n\t\t\t\tBFFF16691FCDC64C00BDEC58 /* render.h */,\n\t\t\t\tBFFF16991FCDC6D100BDEC58 /* render_gl.c */,\n\t\t\t\tBFFF16A61FCDC6D300BDEC58 /* render_gl.h */,\n\t\t\t\tBFFF169D1FCDC6D200BDEC58 /* render_gl1.c */,\n\t\t\t\tBFFF16951FCDC6D000BDEC58 /* render_gl2.c */,\n\t\t\t\tBFFF169E1FCDC6D200BDEC58 /* render_glsl.c */,\n\t\t\t\tBFD67B3C2410ABE200AF114E /* render_layer.cpp */,\n\t\t\t\tBFFF16571FCDC64A00BDEC58 /* render_layer.h */,\n\t\t\t\tBFD67B3B2410ABE200AF114E /* render_layer_code.hpp */,\n\t\t\t\tBFFF16A71FCDC6D300BDEC58 /* render_sdl.c */,\n\t\t\t\tBFFF16971FCDC6D000BDEC58 /* render_sdl.h */,\n\t\t\t\tBFFF16A41FCDC6D300BDEC58 /* render_soft.c */,\n\t\t\t\tBF3C7A7A22E688000024C630 /* render_softscale.c */,\n\t\t\t\tBF6058CD216B3723001B738C /* renderers.h */,\n\t\t\t\tBFFF16451FCDC64700BDEC58 /* robot.c */,\n\t\t\t\tBFFF16391FCDC64600BDEC58 /* robot.h */,\n\t\t\t\tBF6058D5216B3724001B738C /* robot_struct.h */,\n\t\t\t\tBFFF16671FCDC64C00BDEC58 /* run_robot.c */,\n\t\t\t\tBFFF16511FCDC64900BDEC58 /* scrdisp.c */,\n\t\t\t\tBFFF16581FCDC64A00BDEC58 /* scrdisp.h */,\n\t\t\t\tBF6058D9216B3725001B738C /* settings.c */,\n\t\t\t\tBF6058C7216B3721001B738C /* settings.h */,\n\t\t\t\tBFFF16601FCDC64B00BDEC58 /* sprite.c */,\n\t\t\t\tBFFF165E1FCDC64B00BDEC58 /* sprite.h */,\n\t\t\t\tBF6058D2216B3724001B738C /* sprite_struct.h */,\n\t\t\t\tBFFF16411FCDC64700BDEC58 /* str.c */,\n\t\t\t\tBFFF16621FCDC64B00BDEC58 /* str.h */,\n\t\t\t\tBF6058D6216B3724001B738C /* thread_sdl.h */,\n\t\t\t\tBFFF16651FCDC64C00BDEC58 /* util.c */,\n\t\t\t\tBFFF16361FCDC64600BDEC58 /* util.h */,\n\t\t\t\tBFFF16531FCDC64900BDEC58 /* window.c */,\n\t\t\t\tBFFF164F1FCDC64800BDEC58 /* window.h */,\n\t\t\t\tBFFF16591FCDC64A00BDEC58 /* world.c */,\n\t\t\t\tBFFF165F1FCDC64B00BDEC58 /* world.h */,\n\t\t\t\tBF6058C5216B3721001B738C /* world_format.h */,\n\t\t\t\tBF6058CB216B3722001B738C /* world_struct.h */,\n\t\t\t\tBF3C7A7D22E6B6A20024C630 /* yuv.h */,\n\t\t\t);\n\t\t\tname = Core;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBFFF171F1FCDCB6D00BDEC58 /* libxmp */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tBFFF172B1FCDCBDF00BDEC58 /* loaders */,\n\t\t\t\tBFFF17421FCDCBE400BDEC58 /* common.h */,\n\t\t\t\tBFFF174A1FCDCBE500BDEC58 /* control.c */,\n\t\t\t\tBFFF174D1FCDCBE600BDEC58 /* dataio.c */,\n\t\t\t\tBFFF17211FCDCBDB00BDEC58 /* effects.c */,\n\t\t\t\tBFFF17321FCDCBE000BDEC58 /* effects.h */,\n\t\t\t\tBFFF173C1FCDCBE300BDEC58 /* extras.c */,\n\t\t\t\tBFFF173D1FCDCBE300BDEC58 /* extras.h */,\n\t\t\t\t48E8C3512B411560006177C3 /* far_extras.c */,\n\t\t\t\t48E8C34F2B411560006177C3 /* far_extras.h */,\n\t\t\t\t48E8C3502B411560006177C3 /* filetype.c */,\n\t\t\t\tBFFF17221FCDCBDB00BDEC58 /* filter.c */,\n\t\t\t\t737459D72D719052006CEE24 /* flow.c */,\n\t\t\t\tBFFF173A1FCDCBE300BDEC58 /* format.c */,\n\t\t\t\tBFFF17311FCDCBE000BDEC58 /* format.h */,\n\t\t\t\tBFFF17401FCDCBE400BDEC58 /* hio.c */,\n\t\t\t\tBFFF173F1FCDCBE300BDEC58 /* hio.h */,\n\t\t\t\tBFFF17351FCDCBE200BDEC58 /* hmn_extras.c */,\n\t\t\t\tBFFF17361FCDCBE200BDEC58 /* hmn_extras.h */,\n\t\t\t\tBFFF17301FCDCBE000BDEC58 /* lfo.c */,\n\t\t\t\tBFFF17271FCDCBDC00BDEC58 /* lfo.h */,\n\t\t\t\tBFFF17491FCDCBE500BDEC58 /* list.h */,\n\t\t\t\tBFFF17451FCDCBE400BDEC58 /* load_helpers.c */,\n\t\t\t\tBFFF17391FCDCBE300BDEC58 /* load.c */,\n\t\t\t\tBFFF172E1FCDCBDF00BDEC58 /* md5.c */,\n\t\t\t\tBFFF17201FCDCBDB00BDEC58 /* md5.h */,\n\t\t\t\tBFFF174B1FCDCBE500BDEC58 /* mdataio.h */,\n\t\t\t\tBFFF17461FCDCBE400BDEC58 /* med_extras.c */,\n\t\t\t\tBFFF17281FCDCBDC00BDEC58 /* med_extras.h */,\n\t\t\t\tBFFF17291FCDCBDC00BDEC58 /* memio.c */,\n\t\t\t\tBFFF173B1FCDCBE300BDEC58 /* memio.h */,\n\t\t\t\tBFFF17411FCDCBE400BDEC58 /* mix_all.c */,\n\t\t\t\tBFFF17331FCDCBE200BDEC58 /* mixer.c */,\n\t\t\t\tBFFF17481FCDCBE500BDEC58 /* mixer.h */,\n\t\t\t\tBFFF17441FCDCBE400BDEC58 /* period.c */,\n\t\t\t\tBFFF172A1FCDCBDC00BDEC58 /* period.h */,\n\t\t\t\tBFFF172C1FCDCBDF00BDEC58 /* player.c */,\n\t\t\t\tBFFF172F1FCDCBDF00BDEC58 /* player.h */,\n\t\t\t\tBFFF17371FCDCBE200BDEC58 /* precomp_lut.h */,\n\t\t\t\tBFFF17241FCDCBDB00BDEC58 /* read_event.c */,\n\t\t\t\t731E16462C8AE37500E3D730 /* rng.c */,\n\t\t\t\t731E16472C8AE37500E3D730 /* rng.h */,\n\t\t\t\tBFFF17251FCDCBDB00BDEC58 /* scan.c */,\n\t\t\t\tBFFF17341FCDCBE200BDEC58 /* smix.c */,\n\t\t\t\t48E8C34E2B411560006177C3 /* tempfile.h */,\n\t\t\t\tBFFF172D1FCDCBDF00BDEC58 /* virtual.c */,\n\t\t\t\tBFFF17381FCDCBE200BDEC58 /* virtual.h */,\n\t\t\t);\n\t\t\tname = libxmp;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBFFF172B1FCDCBDF00BDEC58 /* loaders */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t48E8C3522B411561006177C3 /* callbackio.h */,\n\t\t\t\tBFA1FB062536752500BB429F /* ice_load.c */,\n\t\t\t\tBFFF17821FCDCC0000BDEC58 /* 669_load.c */,\n\t\t\t\tBFFF17811FCDCC0000BDEC58 /* amf_load.c */,\n\t\t\t\tBFFF17681FCDCBFD00BDEC58 /* asylum_load.c */,\n\t\t\t\tBFFF177D1FCDCBFF00BDEC58 /* common.c */,\n\t\t\t\tBFFF176B1FCDCBFE00BDEC58 /* far_load.c */,\n\t\t\t\tBFC0B26620681A9000D28296 /* flt_load.c */,\n\t\t\t\tBFFF17731FCDCBFE00BDEC58 /* gdm_load.c */,\n\t\t\t\tBFC0B26520681A9000D28296 /* hmn_load.c */,\n\t\t\t\tBFFF176C1FCDCBFE00BDEC58 /* iff.c */,\n\t\t\t\tBFFF17761FCDCBFF00BDEC58 /* iff.h */,\n\t\t\t\tBFFF17751FCDCBFF00BDEC58 /* it_load.c */,\n\t\t\t\tBFFF17711FCDCBFE00BDEC58 /* it.h */,\n\t\t\t\tBFFF176D1FCDCBFE00BDEC58 /* itsex.c */,\n\t\t\t\tBFFF177A1FCDCBFF00BDEC58 /* loader.h */,\n\t\t\t\tBFFF17831FCDCC0000BDEC58 /* med.h */,\n\t\t\t\tBFFF17851FCDCC0000BDEC58 /* med2_load.c */,\n\t\t\t\tBFFF17801FCDCC0000BDEC58 /* med3_load.c */,\n\t\t\t\tBFFF177F1FCDCC0000BDEC58 /* med4_load.c */,\n\t\t\t\tBFFF176A1FCDCBFE00BDEC58 /* mmd_common.c */,\n\t\t\t\tBFFF17791FCDCBFF00BDEC58 /* mmd1_load.c */,\n\t\t\t\tBFFF176E1FCDCBFE00BDEC58 /* mmd3_load.c */,\n\t\t\t\tBFFF17861FCDCC0100BDEC58 /* mod_load.c */,\n\t\t\t\tBFFF17771FCDCBFF00BDEC58 /* mod.h */,\n\t\t\t\tBFFF176F1FCDCBFE00BDEC58 /* mtm_load.c */,\n\t\t\t\tBFFF17841FCDCC0000BDEC58 /* okt_load.c */,\n\t\t\t\tBFFF17781FCDCBFF00BDEC58 /* s3m_load.c */,\n\t\t\t\tBFFF17721FCDCBFE00BDEC58 /* s3m.h */,\n\t\t\t\tBFFF17881FCDCC0100BDEC58 /* sample.c */,\n\t\t\t\tBFC0B26720681A9000D28296 /* st_load.c */,\n\t\t\t\tBFFF177C1FCDCBFF00BDEC58 /* stm_load.c */,\n\t\t\t\tBFFF17741FCDCBFF00BDEC58 /* ult_load.c */,\n\t\t\t\tBFFF17871FCDCC0100BDEC58 /* xm_load.c */,\n\t\t\t\tBFFF177B1FCDCBFF00BDEC58 /* xm.h */,\n\t\t\t);\n\t\t\tname = loaders;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tBFFF181C1FCDCEE800BDEC58 /* Editor */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t4836B2DE261860FE00F802DD /* ansi.c */,\n\t\t\t\t4836B2DF261860FE00F802DD /* ansi.h */,\n\t\t\t\tBFFF182E1FCDCF6400BDEC58 /* block.c */,\n\t\t\t\tBFFF18461FCDCF6800BDEC58 /* block.h */,\n\t\t\t\tBFFF183F1FCDCF6600BDEC58 /* board.c */,\n\t\t\t\tBFFF18311FCDCF6400BDEC58 /* board.h */,\n\t\t\t\tBF6058AF216B3682001B738C /* buffer.c */,\n\t\t\t\tBF6058AD216B3682001B738C /* buffer.h */,\n\t\t\t\tBF6058B0216B3682001B738C /* buffer_struct.h */,\n\t\t\t\tBFFF18401FCDCF6600BDEC58 /* char_ed.c */,\n\t\t\t\tBFFF18271FCDCF6300BDEC58 /* char_ed.h */,\n\t\t\t\tBFFF18211FCDCF6200BDEC58 /* clipboard.h */,\n\t\t\t\t482C0E902617D49C002D9030 /* clipboard_cocoa.m */,\n\t\t\t\tBFFF18241FCDCF6300BDEC58 /* configure.c */,\n\t\t\t\tBFFF18351FCDCF6500BDEC58 /* configure.h */,\n\t\t\t\tBFFF183A1FCDCF6600BDEC58 /* debug.c */,\n\t\t\t\tBFFF182B1FCDCF6400BDEC58 /* debug.h */,\n\t\t\t\tBFFF182A1FCDCF6300BDEC58 /* edit_di.c */,\n\t\t\t\tBFFF18331FCDCF6500BDEC58 /* edit_di.h */,\n\t\t\t\tBFFF18301FCDCF6400BDEC58 /* edit.c */,\n\t\t\t\tBFFF182F1FCDCF6400BDEC58 /* edit.h */,\n\t\t\t\t48F5B99C2C7EFC0900137776 /* edit_export.c */,\n\t\t\t\t48F5B99D2C7EFC0900137776 /* edit_export.h */,\n\t\t\t\tBF6058AC216B3682001B738C /* edit_menu.c */,\n\t\t\t\tBF6058AE216B3682001B738C /* edit_menu.h */,\n\t\t\t\tBFFF18251FCDCF6300BDEC58 /* fill.c */,\n\t\t\t\tBFFF18361FCDCF6500BDEC58 /* fill.h */,\n\t\t\t\tBFFF183D1FCDCF6600BDEC58 /* graphics.c */,\n\t\t\t\tBFFF18451FCDCF6700BDEC58 /* graphics.h */,\n\t\t\t\tBFFF18371FCDCF6500BDEC58 /* macro.c */,\n\t\t\t\tBFFF181F1FCDCF6200BDEC58 /* macro.h */,\n\t\t\t\tBF6058B1216B3683001B738C /* macro_struct.h */,\n\t\t\t\tBFFF18261FCDCF6300BDEC58 /* pal_ed.c */,\n\t\t\t\tBFFF181D1FCDCF6200BDEC58 /* pal_ed.h */,\n\t\t\t\tBFFF18411FCDCF6700BDEC58 /* param.c */,\n\t\t\t\tBFFF18381FCDCF6500BDEC58 /* param.h */,\n\t\t\t\tBFFF183E1FCDCF6600BDEC58 /* robo_debug.c */,\n\t\t\t\tBFFF18321FCDCF6400BDEC58 /* robo_debug.h */,\n\t\t\t\tBFFF18341FCDCF6500BDEC58 /* robo_ed.c */,\n\t\t\t\tBFFF18431FCDCF6700BDEC58 /* robo_ed.h */,\n\t\t\t\tBFFF182C1FCDCF6400BDEC58 /* robot.c */,\n\t\t\t\tBFFF18231FCDCF6300BDEC58 /* robot.h */,\n\t\t\t\tBFFF18391FCDCF6500BDEC58 /* select.c */,\n\t\t\t\tBFFF18421FCDCF6700BDEC58 /* select.h */,\n\t\t\t\tBFFF18221FCDCF6300BDEC58 /* sfx_edit.c */,\n\t\t\t\tBFFF182D1FCDCF6400BDEC58 /* sfx_edit.h */,\n\t\t\t\tBFD5B05A2465B0C400BC91E9 /* stringsearch.c */,\n\t\t\t\tBFD5B0592465B0C400BC91E9 /* stringsearch.h */,\n\t\t\t\tBFFF18281FCDCF6300BDEC58 /* undo.c */,\n\t\t\t\tBFFF18441FCDCF6700BDEC58 /* undo.h */,\n\t\t\t\tBFFF18291FCDCF6300BDEC58 /* window.c */,\n\t\t\t\tBFFF181E1FCDCF6200BDEC58 /* window.h */,\n\t\t\t\tBFFF183B1FCDCF6600BDEC58 /* world.c */,\n\t\t\t\tBFFF18201FCDCF6200BDEC58 /* world.h */,\n\t\t\t);\n\t\t\tname = Editor;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXHeadersBuildPhase section */\n\t\tBF10128E1FCCA957008EEDB6 /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tBF6058FB216B3725001B738C /* keysym.h in Headers */,\n\t\t\t\tBF6058EF216B3725001B738C /* SDLmzx.h in Headers */,\n\t\t\t\tBFFF17CE1FCDCCF300BDEC58 /* mdataio.h in Headers */,\n\t\t\t\tBFD5B0422465AFAE00BC91E9 /* zip_deflate64.h in Headers */,\n\t\t\t\tBF6058EA216B3725001B738C /* settings.h in Headers */,\n\t\t\t\tBFFF17CC1FCDCCF300BDEC58 /* list.h in Headers */,\n\t\t\t\tBF6058F7216B3725001B738C /* game_player.h in Headers */,\n\t\t\t\tBFFF17CA1FCDCCF300BDEC58 /* hmn_extras.h in Headers */,\n\t\t\t\tBFFF17031FCDC76900BDEC58 /* str.h in Headers */,\n\t\t\t\t482C0E8F2617D38E002D9030 /* vio.h in Headers */,\n\t\t\t\tBFFF17011FCDC76900BDEC58 /* sprite.h in Headers */,\n\t\t\t\tBFD5B04D2465AFAE00BC91E9 /* zip_deflate.h in Headers */,\n\t\t\t\tBFFF16BE1FCDC76900BDEC58 /* block.h in Headers */,\n\t\t\t\tBF6058E3216B3725001B738C /* memcasecmp.h in Headers */,\n\t\t\t\tBFFF16C01FCDC76900BDEC58 /* board.h in Headers */,\n\t\t\t\tBF6058EB216B3725001B738C /* caption.h in Headers */,\n\t\t\t\tBFD5B0482465AFAE00BC91E9 /* zip_stream.h in Headers */,\n\t\t\t\tBFD5B0472465AFAE00BC91E9 /* vfile.h in Headers */,\n\t\t\t\tBFFF181B1FCDCD8500BDEC58 /* pngops.h in Headers */,\n\t\t\t\tBFD5B0452465AFAE00BC91E9 /* zip.h in Headers */,\n\t\t\t\tBFFF16E11FCDC76900BDEC58 /* legacy_robot.h in Headers */,\n\t\t\t\tBFFF17051FCDC76900BDEC58 /* util.h in Headers */,\n\t\t\t\tBFFF16DF1FCDC76900BDEC58 /* legacy_rasm.h in Headers */,\n\t\t\t\tBF6058E2216B3725001B738C /* counter_struct.h in Headers */,\n\t\t\t\t48E8C35D2B4115BE006177C3 /* about.h in Headers */,\n\t\t\t\tBF6058DD216B3725001B738C /* game_menu.h in Headers */,\n\t\t\t\tBFFF180B1FCDCD1200BDEC58 /* iff.h in Headers */,\n\t\t\t\tBFFF17D11FCDCCF300BDEC58 /* mixer.h in Headers */,\n\t\t\t\tBF6058E0216B3725001B738C /* compat.h in Headers */,\n\t\t\t\t731E16492C8AE37600E3D730 /* rng.h in Headers */,\n\t\t\t\tBFFF180E1FCDCD1200BDEC58 /* med.h in Headers */,\n\t\t\t\tBFFF17C41FCDCCF300BDEC58 /* common.h in Headers */,\n\t\t\t\tBFD5B04E2465AFAE00BC91E9 /* fsafeopen.h in Headers */,\n\t\t\t\tBFFF16D91FCDC76900BDEC58 /* idput.h in Headers */,\n\t\t\t\t48E8C3572B411561006177C3 /* callbackio.h in Headers */,\n\t\t\t\tBFFF17CB1FCDCCF300BDEC58 /* lfo.h in Headers */,\n\t\t\t\tBFD5B0532465AFAE00BC91E9 /* zip_shrink.h in Headers */,\n\t\t\t\tBF6058F3216B3725001B738C /* intake_num.h in Headers */,\n\t\t\t\tBFFF17071FCDC76900BDEC58 /* window.h in Headers */,\n\t\t\t\tBF6058F5216B3725001B738C /* sprite_struct.h in Headers */,\n\t\t\t\tBF6058F9216B3725001B738C /* thread_sdl.h in Headers */,\n\t\t\t\tBF6058E7216B3725001B738C /* game_ops.h in Headers */,\n\t\t\t\tBFFF17D61FCDCCF300BDEC58 /* precomp_lut.h in Headers */,\n\t\t\t\tBFFF16CB1FCDC76900BDEC58 /* event.h in Headers */,\n\t\t\t\tBFFF16C41FCDC76900BDEC58 /* counter.h in Headers */,\n\t\t\t\tBFFF17D41FCDCCF300BDEC58 /* player.h in Headers */,\n\t\t\t\tBF6058DE216B3725001B738C /* board_struct.h in Headers */,\n\t\t\t\tBFFF17091FCDC76900BDEC58 /* world.h in Headers */,\n\t\t\t\tBF6058F6216B3725001B738C /* game_update.h in Headers */,\n\t\t\t\tBFFF16DD1FCDC76900BDEC58 /* legacy_board.h in Headers */,\n\t\t\t\tBF6058F8216B3725001B738C /* robot_struct.h in Headers */,\n\t\t\t\tBFFF16DB1FCDC76900BDEC58 /* intake.h in Headers */,\n\t\t\t\tBFD5B04F2465AFAE00BC91E9 /* path.h in Headers */,\n\t\t\t\tBFFF16CD1FCDC76900BDEC58 /* expr.h in Headers */,\n\t\t\t\tBFFF17C81FCDCCF300BDEC58 /* format.h in Headers */,\n\t\t\t\tBFD5B0432465AFAE00BC91E9 /* zip_reduce.h in Headers */,\n\t\t\t\tBFFF180D1FCDCD1200BDEC58 /* loader.h in Headers */,\n\t\t\t\tBFFF16E51FCDC76900BDEC58 /* mzm.h in Headers */,\n\t\t\t\tBFFF16F11FCDC76900BDEC58 /* render_sdl.h in Headers */,\n\t\t\t\t48F5B9972C7EFBB200137776 /* core_task.h in Headers */,\n\t\t\t\tBFD67B3D2410ABE200AF114E /* render_layer_code.hpp in Headers */,\n\t\t\t\tBFFF16EA1FCDC76900BDEC58 /* render_gl.h in Headers */,\n\t\t\t\t48E8C3532B411561006177C3 /* tempfile.h in Headers */,\n\t\t\t\tBFFF16F81FCDC76900BDEC58 /* render.h in Headers */,\n\t\t\t\tBFD5B0582465AFEB00BC91E9 /* hashtable.h in Headers */,\n\t\t\t\tBFFF16C61FCDC76900BDEC58 /* data.h in Headers */,\n\t\t\t\tBFFF16EF1FCDC76900BDEC58 /* render_layer.h in Headers */,\n\t\t\t\tBFFF16D51FCDC76900BDEC58 /* graphics.h in Headers */,\n\t\t\t\tBFFF16E61FCDC76900BDEC58 /* platform_endian.h in Headers */,\n\t\t\t\tBFFF17CF1FCDCCF300BDEC58 /* med_extras.h in Headers */,\n\t\t\t\tBFFF17C61FCDCCF300BDEC58 /* extras.h in Headers */,\n\t\t\t\tBFFF16FA1FCDC76900BDEC58 /* robot.h in Headers */,\n\t\t\t\tBF6058E8216B3725001B738C /* world_format.h in Headers */,\n\t\t\t\tBFD5B0552465AFAE00BC91E9 /* zip_dict.h in Headers */,\n\t\t\t\tBFFF18101FCDCD1200BDEC58 /* s3m.h in Headers */,\n\t\t\t\t48E8C34C2B411516006177C3 /* vfs.h in Headers */,\n\t\t\t\tBFA52FD2233AC48100A90CB4 /* audio_reality.h in Headers */,\n\t\t\t\tBFFF16FD1FCDC76900BDEC58 /* scrdisp.h in Headers */,\n\t\t\t\tBF6058F0216B3725001B738C /* renderers.h in Headers */,\n\t\t\t\tBFFF17D71FCDCCF300BDEC58 /* virtual.h in Headers */,\n\t\t\t\tBFFF16C21FCDC76900BDEC58 /* configure.h in Headers */,\n\t\t\t\tBF6058DB216B3725001B738C /* extmem.h in Headers */,\n\t\t\t\tBFFF18191FCDCD8500BDEC58 /* helpsys.h in Headers */,\n\t\t\t\tBF6058EE216B3725001B738C /* world_struct.h in Headers */,\n\t\t\t\tBFFF16C81FCDC76900BDEC58 /* error.h in Headers */,\n\t\t\t\tBFFF16D11FCDC76900BDEC58 /* game.h in Headers */,\n\t\t\t\tBFFF18111FCDCD1200BDEC58 /* xm.h in Headers */,\n\t\t\t\tBFD5B0542465AFAE00BC91E9 /* vio_posix.h in Headers */,\n\t\t\t\tBFFF17CD1FCDCCF300BDEC58 /* md5.h in Headers */,\n\t\t\t\tBF6058EC216B3725001B738C /* core.h in Headers */,\n\t\t\t\t48E8C35F2B4115BE006177C3 /* platform_attribute.h in Headers */,\n\t\t\t\tBFD5B0512465AFAE00BC91E9 /* zip_implode.h in Headers */,\n\t\t\t\t48E8C3542B411561006177C3 /* far_extras.h in Headers */,\n\t\t\t\tBFFF180F1FCDCD1200BDEC58 /* mod.h in Headers */,\n\t\t\t\tBFD5B0522465AFAE00BC91E9 /* memfile.h in Headers */,\n\t\t\t\tBFFF16E31FCDC76900BDEC58 /* legacy_world.h in Headers */,\n\t\t\t\tBF6058FA216B3725001B738C /* const.h in Headers */,\n\t\t\t\tBFFF17111FCDC81000BDEC58 /* config.h in Headers */,\n\t\t\t\tBFFF17C51FCDCCF300BDEC58 /* effects.h in Headers */,\n\t\t\t\tBF6058E6216B3725001B738C /* rasm.h in Headers */,\n\t\t\t\tBFFF16E81FCDC76900BDEC58 /* platform.h in Headers */,\n\t\t\t\tBFFF17D01FCDCCF300BDEC58 /* memio.h in Headers */,\n\t\t\t\tBFD5B0502465AFAE00BC91E9 /* bitstream.h in Headers */,\n\t\t\t\tBFFF17C91FCDCCF300BDEC58 /* hio.h in Headers */,\n\t\t\t\tBFFF16D71FCDC76900BDEC58 /* idarray.h in Headers */,\n\t\t\t\tBFFF180C1FCDCD1200BDEC58 /* it.h in Headers */,\n\t\t\t\tBFFF17D31FCDCCF300BDEC58 /* period.h in Headers */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tBF1012961FCCA97D008EEDB6 /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tBFFF18771FCDCF7C00BDEC58 /* robo_debug.h in Headers */,\n\t\t\t\tBFFF18691FCDCF7C00BDEC58 /* edit_di.h in Headers */,\n\t\t\t\tBFFF18811FCDCF7C00BDEC58 /* undo.h in Headers */,\n\t\t\t\tBF6058B6216B3683001B738C /* buffer_struct.h in Headers */,\n\t\t\t\tBFFF18711FCDCF7C00BDEC58 /* macro.h in Headers */,\n\t\t\t\tBFD5B05B2465B0C400BC91E9 /* stringsearch.h in Headers */,\n\t\t\t\t48F5B99F2C7EFC0A00137776 /* edit_export.h in Headers */,\n\t\t\t\tBFFF187B1FCDCF7C00BDEC58 /* robot.h in Headers */,\n\t\t\t\tBFFF18671FCDCF7C00BDEC58 /* debug.h in Headers */,\n\t\t\t\t4836B2E1261860FE00F802DD /* ansi.h in Headers */,\n\t\t\t\tBFFF186B1FCDCF7C00BDEC58 /* edit.h in Headers */,\n\t\t\t\tBFFF187D1FCDCF7C00BDEC58 /* select.h in Headers */,\n\t\t\t\tBFFF18611FCDCF7C00BDEC58 /* char_ed.h in Headers */,\n\t\t\t\tBFFF18631FCDCF7C00BDEC58 /* clipboard.h in Headers */,\n\t\t\t\tBFFF186F1FCDCF7C00BDEC58 /* graphics.h in Headers */,\n\t\t\t\tBFFF18731FCDCF7C00BDEC58 /* pal_ed.h in Headers */,\n\t\t\t\tBFFF18851FCDCF7C00BDEC58 /* world.h in Headers */,\n\t\t\t\tBFFF18751FCDCF7C00BDEC58 /* param.h in Headers */,\n\t\t\t\tBFFF187F1FCDCF7C00BDEC58 /* sfx_edit.h in Headers */,\n\t\t\t\tBFFF185D1FCDCF7C00BDEC58 /* block.h in Headers */,\n\t\t\t\tBFFF185F1FCDCF7C00BDEC58 /* board.h in Headers */,\n\t\t\t\tBF6058B4216B3683001B738C /* edit_menu.h in Headers */,\n\t\t\t\tBFFF18791FCDCF7C00BDEC58 /* robo_ed.h in Headers */,\n\t\t\t\tBF6058B3216B3683001B738C /* buffer.h in Headers */,\n\t\t\t\tBFFF18651FCDCF7C00BDEC58 /* configure.h in Headers */,\n\t\t\t\tBFFF18831FCDCF7C00BDEC58 /* window.h in Headers */,\n\t\t\t\tBF6058B7216B3683001B738C /* macro_struct.h in Headers */,\n\t\t\t\tBFFF186D1FCDCF7C00BDEC58 /* fill.h in Headers */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXHeadersBuildPhase section */\n\n/* Begin PBXNativeTarget section */\n\t\tBF1012771FCCA7C2008EEDB6 /* MegaZeux */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = BF1012891FCCA7C2008EEDB6 /* Build configuration list for PBXNativeTarget \"MegaZeux\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tBF1012741FCCA7C2008EEDB6 /* Sources */,\n\t\t\t\tBF1012751FCCA7C2008EEDB6 /* Frameworks */,\n\t\t\t\tBF1012761FCCA7C2008EEDB6 /* Resources */,\n\t\t\t\tBF1012B61FCCAB81008EEDB6 /* Embed Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tBFFF18911FCDE81D00BDEC58 /* PBXTargetDependency */,\n\t\t\t\tBFFF18941FCDE82100BDEC58 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = MegaZeux;\n\t\t\tproductName = MegaZeux;\n\t\t\tproductReference = BF1012781FCCA7C2008EEDB6 /* MegaZeux.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n\t\tBF10128F1FCCA957008EEDB6 /* Core */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = BF1012911FCCA957008EEDB6 /* Build configuration list for PBXNativeTarget \"Core\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tBFE5EB632006E09B00E15CC0 /* Update Version Header */,\n\t\t\t\tBF10128C1FCCA957008EEDB6 /* Sources */,\n\t\t\t\tBF10128D1FCCA957008EEDB6 /* Frameworks */,\n\t\t\t\tBF10128E1FCCA957008EEDB6 /* Headers */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = Core;\n\t\t\tproductName = Core;\n\t\t\tproductReference = BF1012901FCCA957008EEDB6 /* libCore.dylib */;\n\t\t\tproductType = \"com.apple.product-type.library.dynamic\";\n\t\t};\n\t\tBF1012971FCCA97D008EEDB6 /* Editor */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = BF1012991FCCA97D008EEDB6 /* Build configuration list for PBXNativeTarget \"Editor\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tBF1012941FCCA97D008EEDB6 /* Sources */,\n\t\t\t\tBF1012951FCCA97D008EEDB6 /* Frameworks */,\n\t\t\t\tBF1012961FCCA97D008EEDB6 /* Headers */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = Editor;\n\t\t\tproductName = Editor;\n\t\t\tproductReference = BF1012981FCCA97D008EEDB6 /* libEditor.dylib */;\n\t\t\tproductType = \"com.apple.product-type.library.dynamic\";\n\t\t};\n\t\tBF10129F1FCCA993008EEDB6 /* MZXRun */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = BF1012AE1FCCA993008EEDB6 /* Build configuration list for PBXNativeTarget \"MZXRun\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tBF10129C1FCCA993008EEDB6 /* Sources */,\n\t\t\t\tBF10129D1FCCA993008EEDB6 /* Frameworks */,\n\t\t\t\tBF10129E1FCCA993008EEDB6 /* Resources */,\n\t\t\t\tBFFF16301FCDC55000BDEC58 /* Embed Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tBFFF18A01FCDEA5D00BDEC58 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = MZXRun;\n\t\t\tproductName = MZXRun;\n\t\t\tproductReference = BF1012A01FCCA993008EEDB6 /* MZXRun.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\tBF1012701FCCA7C1008EEDB6 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1420;\n\t\t\t\tORGANIZATIONNAME = \"MegaZeux Dev Team\";\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\tBF1012771FCCA7C2008EEDB6 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 9.1;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t\tSystemCapabilities = {\n\t\t\t\t\t\t\tcom.apple.Sandbox = {\n\t\t\t\t\t\t\t\tenabled = 0;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t\t\tBF10128F1FCCA957008EEDB6 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 9.1;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t};\n\t\t\t\t\tBF1012971FCCA97D008EEDB6 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 9.1;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t};\n\t\t\t\t\tBF10129F1FCCA993008EEDB6 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 9.1;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t\tSystemCapabilities = {\n\t\t\t\t\t\t\tcom.apple.Sandbox = {\n\t\t\t\t\t\t\t\tenabled = 0;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = BF1012731FCCA7C1008EEDB6 /* Build configuration list for PBXProject \"MegaZeux\" */;\n\t\t\tcompatibilityVersion = \"Xcode 8.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = BF10126F1FCCA7C1008EEDB6;\n\t\t\tproductRefGroup = BF1012791FCCA7C2008EEDB6 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\tBF1012771FCCA7C2008EEDB6 /* MegaZeux */,\n\t\t\t\tBF10128F1FCCA957008EEDB6 /* Core */,\n\t\t\t\tBF1012971FCCA97D008EEDB6 /* Editor */,\n\t\t\t\tBF10129F1FCCA993008EEDB6 /* MZXRun */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\tBF1012761FCCA7C2008EEDB6 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tBFFF18961FCDE8B400BDEC58 /* assets in Resources */,\n\t\t\t\tBF10127F1FCCA7C2008EEDB6 /* Assets.xcassets in Resources */,\n\t\t\t\t48E8C3642B411D7F006177C3 /* LICENSE in Resources */,\n\t\t\t\t48E8C3652B411D7F006177C3 /* LICENSE.3rd in Resources */,\n\t\t\t\tBFFF18991FCDE8FE00BDEC58 /* config.txt in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tBF10129E1FCCA993008EEDB6 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tBFFF18971FCDE8B400BDEC58 /* assets in Resources */,\n\t\t\t\tBF1012A61FCCA993008EEDB6 /* Assets.xcassets in Resources */,\n\t\t\t\t48E8C3662B411D98006177C3 /* LICENSE in Resources */,\n\t\t\t\t48E8C3672B411D98006177C3 /* LICENSE.3rd in Resources */,\n\t\t\t\tBFFF189A1FCDE8FE00BDEC58 /* config.txt in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\tBFE5EB632006E09B00E15CC0 /* Update Version Header */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Update Version Header\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = ./update_version.sh;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\tBF1012741FCCA7C2008EEDB6 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tBFFF188C1FCDE74B00BDEC58 /* main.c in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tBF10128C1FCCA957008EEDB6 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tBF3C7A7C22E689020024C630 /* render_softscale.c in Sources */,\n\t\t\t\tBF6058E9216B3725001B738C /* rasm.c in Sources */,\n\t\t\t\t48E8C3562B411561006177C3 /* far_extras.c in Sources */,\n\t\t\t\tBF6058E5216B3725001B738C /* intake_num.c in Sources */,\n\t\t\t\tBFC1C05B214258CE00378A4C /* audio.c in Sources */,\n\t\t\t\tBFC1C05C214258CE00378A4C /* audio_pcs.c in Sources */,\n\t\t\t\tBFC1C05D214258CE00378A4C /* audio_sdl.c in Sources */,\n\t\t\t\tBFC1C05E214258CE00378A4C /* audio_vorbis.c in Sources */,\n\t\t\t\tBFC1C05F214258CE00378A4C /* audio_wav.c in Sources */,\n\t\t\t\tBFC1C060214258CE00378A4C /* audio_xmp.c in Sources */,\n\t\t\t\tBFC1C061214258CE00378A4C /* ext.c in Sources */,\n\t\t\t\tBFD5B04C2465AFAE00BC91E9 /* path.c in Sources */,\n\t\t\t\tBFC1C063214258CE00378A4C /* sfx.c in Sources */,\n\t\t\t\tBFFF16FB1FCDC76900BDEC58 /* run_robot.c in Sources */,\n\t\t\t\tBFFF16DA1FCDC76900BDEC58 /* intake.c in Sources */,\n\t\t\t\tBFFF16C91FCDC76900BDEC58 /* event_sdl.c in Sources */,\n\t\t\t\tBFFF17021FCDC76900BDEC58 /* str.c in Sources */,\n\t\t\t\tBFFF17041FCDC76900BDEC58 /* util.c in Sources */,\n\t\t\t\tBFFF18071FCDCD1200BDEC58 /* ult_load.c in Sources */,\n\t\t\t\tBFFF18061FCDCD1200BDEC58 /* stm_load.c in Sources */,\n\t\t\t\tBFFF16E01FCDC76900BDEC58 /* legacy_robot.c in Sources */,\n\t\t\t\t731E16482C8AE37600E3D730 /* rng.c in Sources */,\n\t\t\t\tBFFF17AC1FCDCCF300BDEC58 /* dataio.c in Sources */,\n\t\t\t\tBFFF17AF1FCDCCF300BDEC58 /* filter.c in Sources */,\n\t\t\t\tBFFF18001FCDCD1200BDEC58 /* mmd3_load.c in Sources */,\n\t\t\t\tBFFF17B81FCDCCF300BDEC58 /* med_extras.c in Sources */,\n\t\t\t\tBFFF16DC1FCDC76900BDEC58 /* legacy_board.c in Sources */,\n\t\t\t\tBFFF16C51FCDC76900BDEC58 /* data.c in Sources */,\n\t\t\t\tBFFF17FD1FCDCD1200BDEC58 /* med4_load.c in Sources */,\n\t\t\t\tBFFF17B61FCDCCF300BDEC58 /* load.c in Sources */,\n\t\t\t\tBFFF17061FCDC76900BDEC58 /* window.c in Sources */,\n\t\t\t\tBFFF16FC1FCDC76900BDEC58 /* scrdisp.c in Sources */,\n\t\t\t\tBFFF17F81FCDCD1200BDEC58 /* iff.c in Sources */,\n\t\t\t\tBFFF17BE1FCDCCF300BDEC58 /* period.c in Sources */,\n\t\t\t\tBFFF16F91FCDC76900BDEC58 /* robot.c in Sources */,\n\t\t\t\tBFFF16BF1FCDC76900BDEC58 /* board.c in Sources */,\n\t\t\t\tBFFF16C71FCDC76900BDEC58 /* error.c in Sources */,\n\t\t\t\t48E8C3552B411561006177C3 /* filetype.c in Sources */,\n\t\t\t\tBFFF17FB1FCDCD1200BDEC58 /* med2_load.c in Sources */,\n\t\t\t\tBF6058DF216B3725001B738C /* game_update.c in Sources */,\n\t\t\t\tBFD5B0462465AFAE00BC91E9 /* fsafeopen.c in Sources */,\n\t\t\t\tBFFF17B91FCDCCF300BDEC58 /* memio.c in Sources */,\n\t\t\t\tBFFF17F61FCDCD1200BDEC58 /* far_load.c in Sources */,\n\t\t\t\tBFFF17F71FCDCD1200BDEC58 /* gdm_load.c in Sources */,\n\t\t\t\tBFFF18051FCDCD1200BDEC58 /* sample.c in Sources */,\n\t\t\t\tBF6058FC216B3725001B738C /* settings.c in Sources */,\n\t\t\t\tBF6058ED216B3725001B738C /* game_update_board.c in Sources */,\n\t\t\t\tBFFF17B21FCDCCF300BDEC58 /* hio.c in Sources */,\n\t\t\t\tBFFF17BA1FCDCCF300BDEC58 /* mix_all.c in Sources */,\n\t\t\t\tBFFF17F91FCDCD1200BDEC58 /* it_load.c in Sources */,\n\t\t\t\t48F5B9962C7EFBB200137776 /* core_task.c in Sources */,\n\t\t\t\tBFFF17F11FCDCD1200BDEC58 /* 669_load.c in Sources */,\n\t\t\t\tBFFF17C11FCDCCF300BDEC58 /* scan.c in Sources */,\n\t\t\t\tBF6058E1216B3725001B738C /* game_menu.c in Sources */,\n\t\t\t\tBFFF16C11FCDC76900BDEC58 /* configure.c in Sources */,\n\t\t\t\tBFFF16C31FCDC76900BDEC58 /* counter.c in Sources */,\n\t\t\t\tBFFF16DE1FCDC76900BDEC58 /* legacy_rasm.c in Sources */,\n\t\t\t\tBFFF16F71FCDC76900BDEC58 /* render.c in Sources */,\n\t\t\t\t48E8C3612B411680006177C3 /* sampled_stream.cpp in Sources */,\n\t\t\t\tBFD5B04A2465AFAE00BC91E9 /* zip_stream.c in Sources */,\n\t\t\t\tBFFF16E21FCDC76900BDEC58 /* legacy_world.c in Sources */,\n\t\t\t\tBFFF17B51FCDCCF300BDEC58 /* load_helpers.c in Sources */,\n\t\t\t\tBFFF18181FCDCD8500BDEC58 /* helpsys.c in Sources */,\n\t\t\t\tBFFF17B11FCDCCF300BDEC58 /* format.c in Sources */,\n\t\t\t\tBFFF18021FCDCD1200BDEC58 /* mtm_load.c in Sources */,\n\t\t\t\tBFFF17FF1FCDCD1200BDEC58 /* mmd1_load.c in Sources */,\n\t\t\t\tBFFF17B41FCDCCF300BDEC58 /* lfo.c in Sources */,\n\t\t\t\tBFFF16E91FCDC76900BDEC58 /* render_gl.c in Sources */,\n\t\t\t\tBFFF16EB1FCDC76900BDEC58 /* render_gl1.c in Sources */,\n\t\t\t\tBFFF16EC1FCDC76900BDEC58 /* render_gl2.c in Sources */,\n\t\t\t\t737459D82D719052006CEE24 /* flow.c in Sources */,\n\t\t\t\tBFFF16D01FCDC76900BDEC58 /* game.c in Sources */,\n\t\t\t\tBFFF17F41FCDCD1200BDEC58 /* asylum_load.c in Sources */,\n\t\t\t\tBF6058F1216B3725001B738C /* caption.c in Sources */,\n\t\t\t\tBFFF17C01FCDCCF300BDEC58 /* read_event.c in Sources */,\n\t\t\t\tBFFF17BC1FCDCCF300BDEC58 /* mixer.c in Sources */,\n\t\t\t\tBFFF16D81FCDC76900BDEC58 /* idput.c in Sources */,\n\t\t\t\tBFFF17C21FCDCCF300BDEC58 /* smix.c in Sources */,\n\t\t\t\tBFA52FD3233AC48100A90CB4 /* audio_reality.cpp in Sources */,\n\t\t\t\tBFD5B04B2465AFAE00BC91E9 /* vio.c in Sources */,\n\t\t\t\tBFFF18031FCDCD1200BDEC58 /* okt_load.c in Sources */,\n\t\t\t\tBFFF16ED1FCDC76900BDEC58 /* render_glsl.c in Sources */,\n\t\t\t\tBFFF17BF1FCDCCF300BDEC58 /* player.c in Sources */,\n\t\t\t\tBFFF16E41FCDC76900BDEC58 /* mzm.c in Sources */,\n\t\t\t\tBFFF16D41FCDC76900BDEC58 /* graphics.c in Sources */,\n\t\t\t\t48E8C35E2B4115BE006177C3 /* about.c in Sources */,\n\t\t\t\tBFFF17081FCDC76900BDEC58 /* world.c in Sources */,\n\t\t\t\tBFD5B0442465AFAE00BC91E9 /* zip.c in Sources */,\n\t\t\t\tBFFF17B71FCDCCF300BDEC58 /* md5.c in Sources */,\n\t\t\t\tBFFF18091FCDCD1200BDEC58 /* xm_load.c in Sources */,\n\t\t\t\tBF6058DC216B3725001B738C /* core.c in Sources */,\n\t\t\t\tBFFF17FE1FCDCD1200BDEC58 /* mmd_common.c in Sources */,\n\t\t\t\tBFA1FB072536752600BB429F /* ice_load.c in Sources */,\n\t\t\t\tBFFF17B31FCDCCF300BDEC58 /* hmn_extras.c in Sources */,\n\t\t\t\tBFFF18011FCDCD1200BDEC58 /* mod_load.c in Sources */,\n\t\t\t\tBFFF16F21FCDC76900BDEC58 /* render_soft.c in Sources */,\n\t\t\t\t48E8C34D2B411516006177C3 /* vfs.c in Sources */,\n\t\t\t\tBFFF17AD1FCDCCF300BDEC58 /* effects.c in Sources */,\n\t\t\t\tBFFF17F21FCDCD1200BDEC58 /* amf_load.c in Sources */,\n\t\t\t\tBFFF16E71FCDC76900BDEC58 /* platform_sdl.c in Sources */,\n\t\t\t\tBFFF181A1FCDCD8500BDEC58 /* pngops.c in Sources */,\n\t\t\t\tBFFF16CA1FCDC76900BDEC58 /* event.c in Sources */,\n\t\t\t\tBFFF17C31FCDCCF300BDEC58 /* virtual.c in Sources */,\n\t\t\t\tBF6058E4216B3725001B738C /* game_player.c in Sources */,\n\t\t\t\tBFFF17F51FCDCD1200BDEC58 /* common.c in Sources */,\n\t\t\t\tBF6058F2216B3725001B738C /* game_ops.c in Sources */,\n\t\t\t\tBFFF16CC1FCDC76900BDEC58 /* expr.c in Sources */,\n\t\t\t\tBFFF17AB1FCDCCF300BDEC58 /* control.c in Sources */,\n\t\t\t\t48E8C35C2B4115BE006177C3 /* platform_time.c in Sources */,\n\t\t\t\tBFFF17FC1FCDCD1200BDEC58 /* med3_load.c in Sources */,\n\t\t\t\tBFFF17001FCDC76900BDEC58 /* sprite.c in Sources */,\n\t\t\t\tBFFF16BD1FCDC76900BDEC58 /* block.c in Sources */,\n\t\t\t\tBFFF18041FCDCD1200BDEC58 /* s3m_load.c in Sources */,\n\t\t\t\tBFFF17AE1FCDCCF300BDEC58 /* extras.c in Sources */,\n\t\t\t\tBFFF16D61FCDC76900BDEC58 /* idarray.c in Sources */,\n\t\t\t\tBFFF16F01FCDC76900BDEC58 /* render_sdl.c in Sources */,\n\t\t\t\tBFFF17FA1FCDCD1200BDEC58 /* itsex.c in Sources */,\n\t\t\t\tBFD67B3E2410ABE200AF114E /* render_layer.cpp in Sources */,\n\t\t\t\tBFC0B26B20681ACC00D28296 /* flt_load.c in Sources */,\n\t\t\t\tBFC0B26C20681ACC00D28296 /* hmn_load.c in Sources */,\n\t\t\t\tBFC0B26D20681ACC00D28296 /* st_load.c in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tBF1012941FCCA97D008EEDB6 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t4836B2E0261860FE00F802DD /* ansi.c in Sources */,\n\t\t\t\tBFFF18801FCDCF7C00BDEC58 /* undo.c in Sources */,\n\t\t\t\tBFFF18741FCDCF7C00BDEC58 /* param.c in Sources */,\n\t\t\t\tBFFF186E1FCDCF7C00BDEC58 /* graphics.c in Sources */,\n\t\t\t\tBFFF18601FCDCF7C00BDEC58 /* char_ed.c in Sources */,\n\t\t\t\tBFFF185E1FCDCF7C00BDEC58 /* board.c in Sources */,\n\t\t\t\tBFFF18661FCDCF7C00BDEC58 /* debug.c in Sources */,\n\t\t\t\tBFFF18701FCDCF7C00BDEC58 /* macro.c in Sources */,\n\t\t\t\tBFFF18721FCDCF7C00BDEC58 /* pal_ed.c in Sources */,\n\t\t\t\t482C0E912617D49D002D9030 /* clipboard_cocoa.m in Sources */,\n\t\t\t\tBFFF18681FCDCF7C00BDEC58 /* edit_di.c in Sources */,\n\t\t\t\tBF6058B5216B3683001B738C /* buffer.c in Sources */,\n\t\t\t\tBFFF186A1FCDCF7C00BDEC58 /* edit.c in Sources */,\n\t\t\t\tBFFF185C1FCDCF7C00BDEC58 /* block.c in Sources */,\n\t\t\t\tBFD5B05C2465B0C400BC91E9 /* stringsearch.c in Sources */,\n\t\t\t\tBFFF18761FCDCF7C00BDEC58 /* robo_debug.c in Sources */,\n\t\t\t\tBFFF18821FCDCF7C00BDEC58 /* window.c in Sources */,\n\t\t\t\t48F5B99E2C7EFC0A00137776 /* edit_export.c in Sources */,\n\t\t\t\tBFFF18781FCDCF7C00BDEC58 /* robo_ed.c in Sources */,\n\t\t\t\tBF6058B2216B3683001B738C /* edit_menu.c in Sources */,\n\t\t\t\tBFFF187C1FCDCF7C00BDEC58 /* select.c in Sources */,\n\t\t\t\tBFFF186C1FCDCF7C00BDEC58 /* fill.c in Sources */,\n\t\t\t\tBFFF187A1FCDCF7C00BDEC58 /* robot.c in Sources */,\n\t\t\t\tBFFF18841FCDCF7C00BDEC58 /* world.c in Sources */,\n\t\t\t\tBFFF18641FCDCF7C00BDEC58 /* configure.c in Sources */,\n\t\t\t\tBFFF187E1FCDCF7C00BDEC58 /* sfx_edit.c in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tBF10129C1FCCA993008EEDB6 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tBFFF189B1FCDE93900BDEC58 /* main.c in Sources */,\n\t\t\t\tBFFF18A71FCDEAF400BDEC58 /* run_stubs.c in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\tBFFF18911FCDE81D00BDEC58 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = BF10128F1FCCA957008EEDB6 /* Core */;\n\t\t\ttargetProxy = BFFF18901FCDE81D00BDEC58 /* PBXContainerItemProxy */;\n\t\t};\n\t\tBFFF18941FCDE82100BDEC58 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = BF1012971FCCA97D008EEDB6 /* Editor */;\n\t\t\ttargetProxy = BFFF18931FCDE82100BDEC58 /* PBXContainerItemProxy */;\n\t\t};\n\t\tBFFF18A01FCDEA5D00BDEC58 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = BF10128F1FCCA957008EEDB6 /* Core */;\n\t\t\ttargetProxy = BFFF189F1FCDEA5D00BDEC58 /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin XCBuildConfiguration section */\n\t\tBF1012871FCCA7C2008EEDB6 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tARCHS = \"$(ARCHS_STANDARD)\";\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_ENABLE_MODULES = NO;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = NO;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = NO;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = NO;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_CHAR_IS_UNSIGNED_CHAR = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_ENABLE_CPP_EXCEPTIONS = NO;\n\t\t\t\tGCC_ENABLE_CPP_RTTI = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = NO;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = NO;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.11;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tBF1012881FCCA7C2008EEDB6 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tARCHS = \"$(ARCHS_STANDARD)\";\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_ENABLE_MODULES = NO;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = NO;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = NO;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = NO;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_CHAR_IS_UNSIGNED_CHAR = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_ENABLE_CPP_EXCEPTIONS = NO;\n\t\t\t\tGCC_ENABLE_CPP_RTTI = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = NO;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = NO;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tLLVM_LTO = YES_THIN;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.11;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tBF10128A1FCCA7C2008EEDB6 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(LOCAL_LIBRARY_DIR)/Frameworks\",\n\t\t\t\t\t\"$(PROJECT_DIR)\",\n\t\t\t\t);\n\t\t\t\tHEADER_SEARCH_PATHS = (\n\t\t\t\t\tSDL2.framework/Headers,\n\t\t\t\t\t../../contrib/libxmp/include,\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = MegaZeux/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = net.digitalmzx.MegaZeux;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tBF10128B1FCCA7C2008EEDB6 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(LOCAL_LIBRARY_DIR)/Frameworks\",\n\t\t\t\t\t\"$(PROJECT_DIR)\",\n\t\t\t\t);\n\t\t\t\tHEADER_SEARCH_PATHS = (\n\t\t\t\t\tSDL2.framework/Headers,\n\t\t\t\t\t../../contrib/libxmp/include,\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = MegaZeux/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = net.digitalmzx.MegaZeux;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tBF1012921FCCA957008EEDB6 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tEXECUTABLE_PREFIX = lib;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(LOCAL_LIBRARY_DIR)/Frameworks\",\n\t\t\t\t\t\"$(PROJECT_DIR)\",\n\t\t\t\t);\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\tLIBXMP_NO_DEPACKERS,\n\t\t\t\t\tLIBXMP_NO_PROWIZARD,\n\t\t\t\t);\n\t\t\t\tHEADER_SEARCH_PATHS = (\n\t\t\t\t\tSDL2.framework/Headers,\n\t\t\t\t\tpng.framework/Headers,\n\t\t\t\t\tOgg.framework/Headers,\n\t\t\t\t\tVorbis.framework/Headers,\n\t\t\t\t\t../../contrib/libxmp/include,\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/MegaZeux/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"@rpath\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tUSER_HEADER_SEARCH_PATHS = \"../../contrib/libxmp/src ../../contrib/libxmp/src/loaders\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tBF1012931FCCA957008EEDB6 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tEXECUTABLE_PREFIX = lib;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(LOCAL_LIBRARY_DIR)/Frameworks\",\n\t\t\t\t\t\"$(PROJECT_DIR)\",\n\t\t\t\t);\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\tLIBXMP_NO_DEPACKERS,\n\t\t\t\t\tLIBXMP_NO_PROWIZARD,\n\t\t\t\t);\n\t\t\t\tHEADER_SEARCH_PATHS = (\n\t\t\t\t\tSDL2.framework/Headers,\n\t\t\t\t\tpng.framework/Headers,\n\t\t\t\t\tOgg.framework/Headers,\n\t\t\t\t\tVorbis.framework/Headers,\n\t\t\t\t\t../../contrib/libxmp/include,\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/MegaZeux/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"@rpath\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tUSER_HEADER_SEARCH_PATHS = \"../../contrib/libxmp/src ../../contrib/libxmp/src/loaders\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tBF10129A1FCCA97D008EEDB6 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tEXECUTABLE_PREFIX = lib;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)\",\n\t\t\t\t);\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = \"DEBUG=1\";\n\t\t\t\tHEADER_SEARCH_PATHS = SDL2.framework/Headers;\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/MegaZeux/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"@rpath\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tUSER_HEADER_SEARCH_PATHS = .;\n\t\t\t\tUSE_HEADERMAP = NO;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tBF10129B1FCCA97D008EEDB6 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tEXECUTABLE_PREFIX = lib;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)\",\n\t\t\t\t);\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = \"\";\n\t\t\t\tHEADER_SEARCH_PATHS = SDL2.framework/Headers;\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/MegaZeux/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"@rpath\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tUSER_HEADER_SEARCH_PATHS = .;\n\t\t\t\tUSE_HEADERMAP = NO;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tBF1012AF1FCCA993008EEDB6 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)\",\n\t\t\t\t);\n\t\t\t\tHEADER_SEARCH_PATHS = SDL2.framework/Headers;\n\t\t\t\tINFOPLIST_FILE = MZXRun/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = net.digitalmzx.MZXRun;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tBF1012B01FCCA993008EEDB6 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)\",\n\t\t\t\t);\n\t\t\t\tHEADER_SEARCH_PATHS = SDL2.framework/Headers;\n\t\t\t\tINFOPLIST_FILE = MZXRun/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = \"$(inherited) @executable_path/../Frameworks\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = net.digitalmzx.MZXRun;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\tBF1012731FCCA7C1008EEDB6 /* Build configuration list for PBXProject \"MegaZeux\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tBF1012871FCCA7C2008EEDB6 /* Debug */,\n\t\t\t\tBF1012881FCCA7C2008EEDB6 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tBF1012891FCCA7C2008EEDB6 /* Build configuration list for PBXNativeTarget \"MegaZeux\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tBF10128A1FCCA7C2008EEDB6 /* Debug */,\n\t\t\t\tBF10128B1FCCA7C2008EEDB6 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tBF1012911FCCA957008EEDB6 /* Build configuration list for PBXNativeTarget \"Core\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tBF1012921FCCA957008EEDB6 /* Debug */,\n\t\t\t\tBF1012931FCCA957008EEDB6 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tBF1012991FCCA97D008EEDB6 /* Build configuration list for PBXNativeTarget \"Editor\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tBF10129A1FCCA97D008EEDB6 /* Debug */,\n\t\t\t\tBF10129B1FCCA97D008EEDB6 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tBF1012AE1FCCA993008EEDB6 /* Build configuration list for PBXNativeTarget \"MZXRun\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tBF1012AF1FCCA993008EEDB6 /* Debug */,\n\t\t\t\tBF1012B01FCCA993008EEDB6 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = BF1012701FCCA7C1008EEDB6 /* Project object */;\n}\n"
  },
  {
    "path": "arch/xcode/MegaZeux.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:MegaZeux.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "arch/xcode/MegaZeux.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "arch/xcode/README.md",
    "content": "# Building MegaZeux with Xcode\n\n## Instructions\n\nThe process of building MegaZeux using Xcode should be fairly straightforward and mostly painless. All required third-party frameworks are prebuilt and provided in the releases of this Github repository:\n\nhttps://github.com/AliceLR/megazeux-dependencies\n\nThe following steps should produce a usable binary:\n\n1. Fetch the latest frameworks tarball and extract it into arch/xcode/.\n2. After cloning the repository, open the Xcode project contained in arch/xcode/.\n3. Make sure that the desired build target (probably MegaZeux or MZXRun) is selected in the bar on the top of the screen.\n4. Press Cmd+B to build the project or Cmd+R to build and run the project.\n\nThat will generate a debug build. To generate a release build, either build an archive (Product > Archive) and export the app bundle (recommended) or edit the Run scheme (Cmd+Shift+, or Product > Scheme > Edit Scheme...) and change the build configuration in the Info tab.\n\nTo change the build features, modify config.h. Please note that changing certain features, such as the module playback engine, will require additional files to be added to the project. The required modules can be found in the Makefile.in files in the src/ directory.\n\n## Compatibility\n\nAt this time, MegaZeux has been successfully built using Xcode 9.1 on a system running macOS 10.13. The resulting binary has been tested on OS X 10.7 and macOS 10.13, though it should work on versions as low as OS X 10.6.\n\n"
  },
  {
    "path": "arch/xcode/config.h",
    "content": "#include \"version.h\"\n#define PLATFORM \"xcode-\" SUBPLATFORM\n#define CONFDIR \"../Resources\"\n#define CONFFILE \"config.txt\"\n#define USERCONFFILE \".megazeux-config\"\n#define SHAREDIR \"../Resources\"\n#define LICENSEDIR \"../Resources\"\n#define CONFIG_SDL 2\n#define CONFIG_EDITOR\n#define CONFIG_HELPSYS\n#define CONFIG_RENDER_SOFT\n#define CONFIG_RENDER_SOFTSCALE\n#define CONFIG_RENDER_GL_FIXED\n#define CONFIG_RENDER_GL_PROGRAM\n#define CONFIG_ENABLE_SCREENSHOTS\n#define CONFIG_XMP\n#define CONFIG_AUDIO\n#define CONFIG_AUDIO_MOD_SYSTEM\n#define CONFIG_REALITY\n#define CONFIG_VORBIS\n#define CONFIG_PNG\n#define CONFIG_ICON\n#define CONFIG_MODULAR\n#define CONFIG_CHECK_ALLOC\n#define CONFIG_COUNTER_HASH_TABLES\n#define CONFIG_GAMECONTROLLERDB\n\n/* TODO: do this from Xcode? */\n#if defined(__x86_64h__)\n#define SUBPLATFORM \"x86_64h\"\n#elif defined(__x86_64__)\n#define SUBPLATFORM \"x86_64\"\n#elif defined(__x86__)\n#define SUBPLATFORM \"i386\"\n#elif defined(__arm64e__)\n#define SUBPLATFORM \"arm64e\"\n#elif defined(__arm64__)\n#define SUBPLATFORM \"arm64\"\n#else\n#define SUBPLATFORM \"unknown\"\n#endif\n"
  },
  {
    "path": "arch/xcode/update_version.sh",
    "content": "#!/bin/sh\n\nVERSION=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' MegaZeux/Info.plist)\necho \"#define VERSION \\\"${VERSION}\\\"\" > version.h\necho \"#define VERSION_DATE \\\" (`date -u +%Y%m%d`)\\\"\" >> version.h\n\n"
  },
  {
    "path": "arch/zip.inc",
    "content": "ifeq (${SUBARCH},)\nSUBARCH := ${SUBPLATFORM}\nendif\n\narchive: build\n\t${RM} -r build/dist/${SUBPLATFORM}\n\t${MKDIR} -p build/dist/${SUBPLATFORM}\n\t@cd build/${SUBPLATFORM} && \\\n\tif command -v 7za >/dev/null 2>/dev/null; then \\\n\t\t7za a -mx9 -tzip \"../dist/${SUBPLATFORM}/${TARGET}-${SUBARCH}.zip\" *; \\\n\telse \\\n\t\tzip -r9 \"../dist/${SUBPLATFORM}/${TARGET}-${SUBARCH}.zip\" *; \\\n\tfi\n"
  },
  {
    "path": "assets/gamecontrollerdb.txt",
    "content": "# Game Controller DB for SDL in 2.0.16 format\n# Source: https://github.com/gabomdq/SDL_GameControllerDB\n\n# Windows\n03000000300f00000a01000000000000,3 In 1 Conversion Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b8,x:b3,y:b0,platform:Windows,\n03000000fa190000918d000000000000,3 In 1 Conversion Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b8,x:b3,y:b0,platform:Windows,\n03000000fa2d00000100000000000000,3dRudder Foot Motion Controller,leftx:a0,lefty:a1,rightx:a5,righty:a2,platform:Windows,\n03000000d0160000040d000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows,\n03000000d0160000050d000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows,\n03000000d0160000060d000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows,\n03000000d0160000070d000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows,\n03000000d0160000600a000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows,\n03000000c82d00000031000000000000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00000531000000000000,8BitDo Adapter 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00000951000000000000,8BitDo Dogbone,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a2,rightx:a3,righty:a5,start:b11,platform:Windows,\n03000000008000000210000000000000,8BitDo F30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n030000003512000011ab000000000000,8BitDo F30 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000c82d00001028000000000000,8BitDo F30 Arcade Joystick,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d000011ab000000000000,8BitDo F30 Arcade Joystick,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000801000000900000000000000,8BitDo F30 Arcade Stick,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00001038000000000000,8BitDo F30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000090000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00006a28000000000000,8BitDo GameCube,a:b0,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b9,paddle2:b8,rightshoulder:b10,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b1,y:b4,platform:Windows,\n03000000c82d00001251000000000000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00001151000000000000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000150000000000000,8BitDo M30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a3,righty:a5,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000151000000000000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a2,rightshoulder:b6,righttrigger:b7,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00000650000000000000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00005106000000000000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,guide:b2,leftshoulder:b8,lefttrigger:b9,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00002090000000000000,8BitDo Micro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000310000000000000,8BitDo N30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00000451000000000000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a2,rightx:a3,righty:a5,start:b11,platform:Windows,\n03000000c82d00002028000000000000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00008010000000000000,8BitDo N30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d0000e002000000000000,8BitDo N30,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,start:b6,platform:Windows,\n03000000c82d00000190000000000000,8BitDo N30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00001590000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00006528000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000290000000000000,8BitDo N64,+rightx:b9,+righty:b3,-rightx:b4,-righty:b8,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,platform:Windows,\n03000000c82d00003038000000000000,8BitDo N64,+rightx:b9,+righty:b3,-rightx:b4,-righty:b8,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,platform:Windows,\n03000000c82d00006928000000000000,8BitDo N64,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b11,platform:Windows,\n03000000c82d00002590000000000000,8BitDo NEOGEO,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n030000003512000012ab000000000000,8BitDo NES30,a:b2,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Windows,\n03000000c82d000012ab000000000000,8BitDo NES30,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,\n03000000022000000090000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000203800000900000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00002038000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000751000000000000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00000851000000000000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00000360000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000361000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000660000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000131000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000231000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000331000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000431000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00002867000000000000,8BitDo S30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a2,rightshoulder:b6,righttrigger:b7,rightx:a3,righty:a5,start:b10,x:b3,y:b4,platform:Windows,\n03000000c82d00000130000000000000,8BitDo SF30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000060000000000000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000061000000000000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,\n03000000102800000900000000000000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d000021ab000000000000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00003028000000000000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,\n030000003512000020ab000000000000,8BitDo SN30,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000030000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000351000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a2,rightshoulder:b7,rightx:a3,righty:a5,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00001290000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d000020ab000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00004028000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00006228000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000021000000000000,8BitDo SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000260000000000000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00000261000000000000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00001230000000000000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00001b30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00001d30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00001530000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00001630000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00001730000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00001130000000000000,8BitDo Ultimate Wired,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00001330000000000000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00000121000000000000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000a00500003232000000000000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,\n03000000c82d00001890000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,\n03000000c82d00003032000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,\n030000008f0e00001200000000000000,Acme GA02,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,\n03000000c01100000355000000000000,Acrux,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000fa190000f0ff000000000000,Acteck AGJ 3200,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000d1180000402c000000000000,ADT1,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a3,rightx:a2,righty:a5,x:b3,y:b4,platform:Windows,\n030000006f0e00008801000000000000,Afterglow Deluxe Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000341a00003608000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00000263000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00001101000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00001401000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00001402000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00001901000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00001a01000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00001301000000000000,Afterglow Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000006f0e00001302000000000000,Afterglow Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000006f0e00001304000000000000,Afterglow Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000006f0e00001413000000000000,Afterglow Xbox Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00003901000000000000,Afterglow Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000ab1200000103000000000000,Afterglow Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000ad1b000000f9000000000000,Afterglow Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000100000008200000000000000,Akishop Customs PS360,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n030000007c1800000006000000000000,Alienware Dual Compatible PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,\n03000000491900001904000000000000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Windows,\n03000000710100001904000000000000,Amazon Luna Controller,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b8,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b4,rightstick:b7,rightx:a3,righty:a4,start:b6,x:b3,y:b2,platform:Windows,\n0300000008100000e501000000000000,Anbernic Game Pad,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000020500000913000000000000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000373500000710000000000000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000373500004610000000000000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000830500000160000000000000,Arcade,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b3,x:b4,y:b4,platform:Windows,\n03000000120c0000100e000000000000,Armor 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000490b00004406000000000000,ASCII Seamic Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,\n03000000869800002500000000000000,Astro C40 TR PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000a30c00002700000000000000,Astro City Mini,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows,\n03000000a30c00002800000000000000,Astro City Mini,a:b2,b:b1,back:b8,leftx:a3,lefty:a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows,\n03000000050b00000579000000000000,ASUS ROG Kunai 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000050b00000679000000000000,ASUS ROG Kunai 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000503200000110000000000000,Atari VCS Classic Controller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,start:b3,platform:Windows,\n03000000503200000210000000000000,Atari VCS Modern Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows,\n03000000380800001889000000000000,AtGames Legends Gamer Pro,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b13,lefttrigger:b14,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n030000008a3500000102000000000000,Backbone One,a:b4,b:b5,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b10,leftstick:b17,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b18,righttrigger:b13,rightx:a3,righty:a4,start:b15,x:b7,y:b8,platform:Windows,\n030000008a3500000201000000000000,Backbone One,a:b4,b:b5,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b10,leftstick:b17,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b18,righttrigger:b13,rightx:a3,righty:a4,start:b15,x:b7,y:b8,platform:Windows,\n030000008a3500000302000000000000,Backbone One,a:b4,b:b5,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b10,leftstick:b17,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b18,righttrigger:b13,rightx:a3,righty:a4,start:b15,x:b7,y:b8,platform:Windows,\n030000008a3500000402000000000000,Backbone One,a:b4,b:b5,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b10,leftstick:b17,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b18,righttrigger:b13,rightx:a3,righty:a4,start:b15,x:b7,y:b8,platform:Windows,\n03000000e4150000103f000000000000,Batarang,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000d6200000e557000000000000,Batarang PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,platform:Windows,\n030000006f0e00003201000000000000,Battlefield 4 PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000ad1b000001f9000000000000,BB 070,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000d62000002a79000000000000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000bc2000005250000000000000,Beitong G3,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b5,righttrigger:b9,rightx:a3,righty:a4,start:b15,x:b3,y:b4,platform:Windows,\n030000000d0500000208000000000000,Belkin Nostromo N40,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,\n03000000bc2000006012000000000000,Betop 2126F,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000bc2000000055000000000000,Betop BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000bc2000006312000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000bc2000006321000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000bc2000006412000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000c01100000555000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000c01100000655000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000790000000700000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,\n03000000808300000300000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,\n030000006f0e00006401000000000000,BF One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Windows,\n03000000300f00000202000000000000,Bigben,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a5,righty:a2,start:b7,x:b2,y:b3,platform:Windows,\n030000006b1400000209000000000000,Bigben,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006b1400000055000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n030000006b1400000103000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,\n03000000120c0000200e000000000000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000210e000000000000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000f10e000000000000,Brook PS2 Adapter,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000120c0000310c000000000000,Brook Super Converter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows,\n03000000d81d00000b00000000000000,Buffalo BSGP1601 Series,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Windows,\n030000005a1c00002400000000000000,Capcom Home Arcade Controller,a:b3,b:b4,back:b7,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b6,x:b0,y:b1,platform:Windows,\n030000005b1c00002400000000000000,Capcom Home Arcade Controller,a:b3,b:b4,back:b7,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b6,x:b0,y:b1,platform:Windows,\n030000005b1c00002500000000000000,Capcom Home Arcade Controller,a:b3,b:b4,back:b7,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b6,x:b0,y:b1,platform:Windows,\n030000006d04000042c2000000000000,ChillStream,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000e82000006058000000000000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000457500000401000000000000,Cobra,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000b0400003365000000000000,Competition Pro,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Windows,\n030000004c050000c505000000000000,CronusMax Adapter,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000d814000007cd000000000000,Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000d8140000cefa000000000000,Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Windows,\n030000003807000002cb000000000000,Cyborg,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000a306000022f6000000000000,Cyborg V.3 Rumble,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,\n03000000f806000000a3000000000000,DA Leader,a:b7,b:b6,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b0,leftstick:b8,lefttrigger:b1,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:b3,rightx:a2,righty:a3,start:b12,x:b4,y:b5,platform:Windows,\n030000001a1c00000001000000000000,Datel Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000451300000830000000000000,Defender Game Racer X7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n03000000791d00000103000000000000,Dual Box Wii,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000c0160000e105000000000000,Dual Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,\n030000004f040000070f000000000000,Dual Power,a:b8,b:b9,back:b4,dpdown:b1,dpleft:b2,dpright:b3,dpup:b0,leftshoulder:b13,leftstick:b6,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b12,rightstick:b7,righttrigger:b15,start:b5,x:b10,y:b11,platform:Windows,\n030000004f04000012b3000000000000,Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,\n030000004f04000020b3000000000000,Dual Trigger,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,\n03000000bd12000002e0000000000000,Dual Vibration Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Windows,\n03000000ff1100003133000000000000,DualForce,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b1,platform:Windows,\n030000006f0e00003001000000000000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000fc0400000250000000000000,Easy Grip,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,\n03000000bc2000000091000000000000,EasySMX Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n030000006e0500000a20000000000000,Elecom DUX60 MMO,a:b2,b:b3,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b14,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b15,righttrigger:b13,rightx:a3,righty:a4,start:b20,x:b0,y:b1,platform:Windows,\n03000000b80500000410000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows,\n03000000b80500000610000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows,\n03000095090000010000000000000000,Elecom JC-U609,a:b0,b:b1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b8,x:b3,y:b4,platform:Windows,\n0300004112000000e500000000000000,Elecom JC-U909Z,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b8,x:b3,y:b4,platform:Windows,\n03000041120000001050000000000000,Elecom JC-U911,a:b1,b:b2,back:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,start:b0,x:b4,y:b5,platform:Windows,\n030000006e0500000520000000000000,Elecom P301U PlayStation Controller Adapter,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,\n03000000411200004450000000000000,Elecom U1012,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,\n030000006e0500000320000000000000,Elecom U3613M,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,\n030000006e0500000e20000000000000,Elecom U3912T,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,\n030000006e0500000f20000000000000,Elecom U4013S,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,\n030000006e0500001320000000000000,Elecom U4113,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006e0500001020000000000000,Elecom U4113S,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Windows,\n030000006e0500000720000000000000,Elecom W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows,\n030000007d0400000640000000000000,Eliminator AfterShock,a:b1,b:b2,back:b9,dpdown:+a3,dpleft:-a5,dpright:+a5,dpup:-a3,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a4,righty:a2,start:b8,x:b0,y:b3,platform:Windows,\n03000000120c0000f61c000000000000,Elite,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000430b00000300000000000000,EMS Production PS2 Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,\n03000000062000001801000000000000,EMS TrioLinker Plus II,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b2,y:b3,platform:Windows,\n03000000242f000000b7000000000000,ESM 9110,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Windows,\n03000000101c0000181c000000000000,Essential,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b4,leftx:a1,lefty:a0,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,\n030000008f0e00000f31000000000000,EXEQ,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,\n03000000341a00000108000000000000,EXEQ RF Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n030000006f0e00008401000000000000,Faceoff Deluxe Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00008101000000000000,Faceoff Deluxe Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00008001000000000000,Faceoff Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000021000000090000000000000,FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,\n0300000011040000c600000000000000,FC801,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows,\n03000000852100000201000000000000,FF GP1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000ad1b000028f0000000000000,Fightpad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000ad1b00002ef0000000000000,Fightpad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000ad1b000038f0000000000000,Fightpad TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows,\n03005036852100000000000000000000,Final Fantasy XIV Online Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000f806000001a3000000000000,Firestorm,a:b9,b:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b0,leftstick:b10,lefttrigger:b1,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b11,righttrigger:b3,start:b12,x:b8,y:b4,platform:Windows,\n03000000b50700000399000000000000,Firestorm 2,a:b2,b:b4,back:b10,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,righttrigger:b9,start:b11,x:b3,y:b5,platform:Windows,\n03000000b50700001302000000000000,Firestorm D3,a:b0,b:b2,leftshoulder:b4,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,x:b1,y:b3,platform:Windows,\n03000000b40400001024000000000000,Flydigi Apex,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000151900004000000000000000,Flydigi Vader 2,a:b27,b:b26,back:b19,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b23,leftstick:b17,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b22,rightstick:b16,righttrigger:b20,rightx:a3,righty:a4,start:b18,x:b25,y:b24,platform:Windows,\n03000000b40400001124000000000000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b14,paddle1:b4,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b2,y:b3,platform:Windows,\n03000000b40400001224000000000000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b2,paddle1:b16,paddle2:b17,paddle3:b14,paddle4:b15,rightshoulder:b7,rightstick:b13,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n030000008305000000a0000000000000,G08XU,a:b0,b:b1,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b5,x:b2,y:b3,platform:Windows,\n0300000066f700000100000000000000,Game VIB Joystick,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Windows,\n03000000260900002625000000000000,GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Windows,\n03000000341a000005f7000000000000,GameCube Controller,a:b2,b:b3,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b1,y:b0,platform:Windows,\n03000000430b00000500000000000000,GameCube Controller,a:b0,b:b2,dpdown:b10,dpleft:b8,dpright:b9,dpup:b11,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a3,rightx:a5,righty:a2,start:b7,x:b1,y:b3,platform:Windows,\n03000000790000004718000000000000,GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,\n03000000790000004618000000000000,GameCube Controller Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,\n030000008f0e00000d31000000000000,Gamepad 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000ac0500003d03000000000000,GameSir G3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000ac0500005b05000000000000,GameSir G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000ac0500002d02000000000000,GameSir G4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000ac0500004d04000000000000,GameSir G4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000ac0500001a06000000000000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n030000004c0e00001035000000000000,Gamester,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,\n030000000d0f00001110000000000000,GameStick Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n0300000047530000616d000000000000,GameStop,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n03000000c01100000140000000000000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000b62500000100000000000000,Gametel GT004 01,a:b3,b:b0,dpdown:b10,dpleft:b9,dpright:b8,dpup:b11,leftshoulder:b4,rightshoulder:b5,start:b7,x:b1,y:b2,platform:Windows,\n030000008f0e00001411000000000000,Gamo2 Divaller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000120c0000a857000000000000,Gator Claw,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000c9110000f055000000000000,GC100XF,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n030000008305000009a0000000000000,Genius,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n030000008305000031b0000000000000,Genius Maxfire Blaze 3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n03000000451300000010000000000000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n030000005c1a00003330000000000000,Genius MaxFire Grandias 12V,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,\n03000000300f00000b01000000000000,GGE909 Recoil,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,\n03000000f0250000c283000000000000,Gioteck PlayStation Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000f025000021c1000000000000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000f025000031c1000000000000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000f0250000c383000000000000,Gioteck VX2 PlayStation Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000f0250000c483000000000000,Gioteck VX2 PlayStation Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000d11800000094000000000000,Google Stadia Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b11,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows,\n030000004f04000026b3000000000000,GP XID,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n0300000079000000d418000000000000,GPD Win,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000c6240000025b000000000000,GPX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000007d0400000840000000000000,Gravis Destroyer Tilt,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,x:b0,y:b3,platform:Windows,\n030000007d0400000540000000000000,Gravis Eliminator Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n03000000280400000140000000000000,Gravis GamePad Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a3,dpup:-a4,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n030000008f0e00000610000000000000,GreenAsia,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a5,righty:a2,start:b11,x:b3,y:b0,platform:Windows,\n03000000ac0500006b05000000000000,GT2a,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000341a00000302000000000000,Hama Scorpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00004900000000000000,Hatsune Miku Sho PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000001008000001e1000000000000,Havit HV G60,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b0,platform:Windows,\n030000000d0f00000c00000000000000,HEXT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000d81400000862000000000000,HitBox Edition Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,\n03000000632500002605000000000000,HJD X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n030000000d0f00000a00000000000000,Hori DOA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000000d0f00008500000000000000,Hori Fighting Commander 2016 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00002500000000000000,Hori Fighting Commander 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00002d00000000000000,Hori Fighting Commander 3 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00005f00000000000000,Hori Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00005e00000000000000,Hori Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000000d0f00008400000000000000,Hori Fighting Commander 5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00006201000000000000,Hori Fighting Commander Octa,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00006401000000000000,Hori Fighting Commander Octa,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,start:b7,x:b2,y:b3,platform:Windows,\n030000000d0f00005100000000000000,Hori Fighting Commander PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00008600000000000000,Hori Fighting Commander Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000000d0f0000ba00000000000000,Hori Fighting Commander Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000000d0f00008800000000000000,Hori Fighting Stick mini 4 PS3,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,\n030000000d0f00008700000000000000,Hori Fighting Stick mini 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000000d0f00001000000000000000,Hori Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00003200000000000000,Hori Fightstick 3W,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f0000c000000000000000,Hori Fightstick 4,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000000d0f00000d00000000000000,Hori Fightstick EX2,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,\n030000000d0f00003701000000000000,Hori Fightstick Mini,a:b1,b:b0,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Windows,\n030000000d0f00004000000000000000,Hori Fightstick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00002100000000000000,Hori Fightstick V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00002700000000000000,Hori Fightstick V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f0000a000000000000000,Hori Grip TAC4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b13,x:b0,y:b3,platform:Windows,\n030000000d0f0000a500000000000000,Hori Miku Project Diva X HD PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000000d0f0000a600000000000000,Hori Miku Project Diva X HD PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000000d0f00000101000000000000,Hori Mini Hatsune Miku FT,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00005400000000000000,Hori Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00000900000000000000,Hori Pad 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00004d00000000000000,Hori Pad A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00003801000000000000,Hori PC Engine Mini Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,platform:Windows,\n030000000d0f00009200000000000000,Hori Pokken Tournament DX Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00002301000000000000,Hori PS4 Controller Light,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n030000000d0f00001100000000000000,Hori Real Arcade Pro 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00002600000000000000,Hori Real Arcade Pro 3P,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00004b00000000000000,Hori Real Arcade Pro 3W,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00006a00000000000000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00006b00000000000000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00008a00000000000000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00008b00000000000000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00006f00000000000000,Hori Real Arcade Pro 4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00007000000000000000,Hori Real Arcade Pro 4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00003d00000000000000,Hori Real Arcade Pro N3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b10,leftstick:b4,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b6,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f0000ae00000000000000,Hori Real Arcade Pro N4,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000000d0f00008c00000000000000,Hori Real Arcade Pro P4,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000000d0f0000aa00000000000000,Hori Real Arcade Pro S,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f0000d800000000000000,Hori Real Arcade Pro S,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Windows,\n030000000d0f00002200000000000000,Hori Real Arcade Pro V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00005b00000000000000,Hori Real Arcade Pro V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00005c00000000000000,Hori Real Arcade Pro V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f0000af00000000000000,Hori Real Arcade Pro VHS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00001b00000000000000,Hori Real Arcade Pro VX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000ad1b000002f5000000000000,Hori Real Arcade Pro VX,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b11,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Windows,\n030000000d0f00009c00000000000000,Hori TAC Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f0000c900000000000000,Hori Taiko Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00006400000000000000,Horipad 3TP,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00001300000000000000,Horipad 3W,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00005500000000000000,Horipad 4 FPS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00006e00000000000000,Horipad 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f00006600000000000000,Horipad 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000000d0f00004200000000000000,Horipad A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000ad1b000001f5000000000000,Horipad EXT2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000000d0f0000ee00000000000000,Horipad Mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f0000c100000000000000,Horipad Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000000d0f0000f600000000000000,Horipad Nintendo Switch Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n030000000d0f00006700000000000000,Horipad One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000000d0f00009601000000000000,Horipad Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc2:b2,paddle1:b5,paddle2:b15,paddle3:b18,paddle4:b19,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n030000000d0f0000dc00000000000000,Horipad Switch,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000242e00000b20000000000000,Hyperkin Admiral N64 Controller,+rightx:b11,+righty:b13,-rightx:b8,-righty:b12,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,platform:Windows,\n03000000242e0000ff0b000000000000,Hyperkin N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,platform:Windows,\n03000000790000004e95000000000000,Hyperkin N64 Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a5,righty:a2,start:b9,platform:Windows,\n03000000242e00006a48000000000000,Hyperkin RetroN Sq,a:b3,b:b7,back:b5,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b0,rightshoulder:b1,start:b4,x:b2,y:b6,platform:Windows,\n03000000242f00000a20000000000000,Hyperkin Scout,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,\n03000000242e00000a20000000000000,Hyperkin Scout Premium SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,\n03000000242e00006a38000000000000,Hyperkin Trooper 2,a:b0,b:b1,back:b4,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b3,start:b5,platform:Windows,\n03000000d81d00000e00000000000000,iBuffalo AC02 Arcade Joystick,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b11,righttrigger:b3,rightx:a2,righty:a5,start:b8,x:b4,y:b5,platform:Windows,\n03000000d81d00000f00000000000000,iBuffalo BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000d81d00001000000000000000,iBuffalo BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n030000005c0a00000285000000000000,iDroidCon,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b6,platform:Windows,\n03000000696400006964000000000000,iDroidCon Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000511d00000230000000000000,iGUGU Gamecore,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b1,leftstick:b4,lefttrigger:b3,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b2,platform:Windows,\n03000000b50700001403000000000000,Impact Black,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,\n030000006f0e00002401000000000000,Injustice Fightstick PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n03000000830500005130000000000000,InterAct ActionPad,a:b0,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,\n03000000ef0500000300000000000000,InterAct AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Windows,\n03000000fd0500000230000000000000,InterAct AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a5,start:b11,x:b0,y:b1,platform:Windows,\n03000000fd0500000030000000000000,Interact GoPad,a:b3,b:b4,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,x:b0,y:b1,platform:Windows,\n03000000fd0500003902000000000000,InterAct Hammerhead,a:b3,b:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b2,lefttrigger:b8,rightshoulder:b7,rightstick:b5,righttrigger:b9,start:b10,x:b0,y:b1,platform:Windows,\n03000000fd0500002a26000000000000,InterAct Hammerhead FX,a:b3,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b0,y:b1,platform:Windows,\n03000000fd0500002f26000000000000,InterAct Hammerhead FX,a:b4,b:b5,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b1,y:b2,platform:Windows,\n03000000fd0500005302000000000000,InterAct ProPad,a:b3,b:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,x:b0,y:b1,platform:Windows,\n03000000ac0500002c02000000000000,Ipega Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000491900000204000000000000,Ipega PG9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000491900000304000000000000,Ipega PG9087,+righty:+a5,-righty:-a4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,start:b11,x:b3,y:b4,platform:Windows,\n030000007e0500000620000000000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Windows,\n030000007e0500000720000000000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,\n03000000250900000017000000000000,Joypad Adapter,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,platform:Windows,\n03000000bd12000003c0000000000000,Joypad Alpha Shock,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000ff1100004033000000000000,JPD FFB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a2,start:b15,x:b3,y:b0,platform:Windows,\n03000000242f00002d00000000000000,JYS Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000242f00008a00000000000000,JYS Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,\n03000000c4100000c082000000000000,KADE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000828200000180000000000000,Keio,a:b4,b:b5,back:b8,leftshoulder:b2,lefttrigger:b3,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b9,x:b0,y:b1,platform:Windows,\n03000000790000000200000000000000,King PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,\n03000000bd12000001e0000000000000,Leadership,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,\n030000006f0e00000103000000000000,Logic3,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000006f0e00000104000000000000,Logic3,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000008f0e00001300000000000000,Logic3,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006d040000d2ca000000000000,Logitech Cordless Precision,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows,\n030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006d0400001dc2000000000000,Logitech F310,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000006d04000018c2000000000000,Logitech F510,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006d0400001ec2000000000000,Logitech F510,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000006d04000019c2000000000000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006d0400001fc2000000000000,Logitech F710,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000006d0400001ac2000000000000,Logitech Precision,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n030000006d04000009c2000000000000,Logitech WingMan,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,\n030000006d0400000bc2000000000000,Logitech WingMan Action Pad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b8,lefttrigger:a5~,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b5,righttrigger:a2~,start:b8,x:b3,y:b4,platform:Windows,\n030000006d0400000ac2000000000000,Logitech WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Windows,\n03000000380700005645000000000000,Lynx,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000222200006000000000000000,Macally,a:b1,b:b2,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b33,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700003888000000000000,Mad Catz Arcade Fightstick TE S Plus PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700008532000000000000,Mad Catz Arcade Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700006352000000000000,Mad Catz CTRLR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700006652000000000000,Mad Catz CTRLR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700005032000000000000,Mad Catz Fightpad Pro PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700005082000000000000,Mad Catz Fightpad Pro PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000380700008031000000000000,Mad Catz FightStick Alpha PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000003807000038b7000000000000,Mad Catz Fightstick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows,\n03000000380700008433000000000000,Mad Catz Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700008483000000000000,Mad Catz Fightstick TE S PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000380700008134000000000000,Mad Catz Fightstick TE2 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700008184000000000000,Mad Catz Fightstick TE2 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000380700006252000000000000,Mad Catz Micro CTRLR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700008232000000000000,Mad Catz PlayStation Brawlpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700008731000000000000,Mad Catz PlayStation Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000003807000056a8000000000000,Mad Catz PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700001888000000000000,Mad Catz SFIV Fightstick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n03000000380700008081000000000000,Mad Catz SFV Arcade Fightstick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000380700001847000000000000,Mad Catz Street Fighter 4 Xbox 360 FightStick,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows,\n03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000002a0600001024000000000000,Matricom,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows,\n030000009f000000adbb000000000000,MaxJoypad Virtual Controller,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,platform:Windows,\n030000008f0e00001330000000000000,Mayflash Controller Adapter,a:b1,b:b2,back:b8,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3~,righty:a2,start:b9,x:b0,y:b3,platform:Windows,\n03000000242f00003700000000000000,Mayflash F101,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n03000000790000003018000000000000,Mayflash F300 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n03000000242f00003900000000000000,Mayflash F300 Elite Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,\n03000000790000004318000000000000,Mayflash GameCube Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,\n03000000242f00007300000000000000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,\n0300000079000000d218000000000000,Mayflash Magic NS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000d620000010a7000000000000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000242f0000f500000000000000,Mayflash N64 Adapter,a:b2,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a5,start:b9,platform:Windows,\n03000000242f0000f400000000000000,Mayflash N64 Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a5,start:b9,platform:Windows,\n03000000790000007918000000000000,Mayflash N64 Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,righttrigger:b7,rightx:a3,righty:a2,start:b8,platform:Windows,\n030000008f0e00001030000000000000,Mayflash Saturn Adapter,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:b7,rightshoulder:b6,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,\n0300000025090000e803000000000000,Mayflash Wii Classic Adapter,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:a5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,\n03000000790000000318000000000000,Mayflash Wii DolphinBar,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows,\n03000000790000000018000000000000,Mayflash Wii U Pro Adapter,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000790000002418000000000000,Mega Drive Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b2,start:b9,x:b3,y:b4,platform:Windows,\n0300000079000000ae18000000000000,Mega Drive Controller,a:b0,b:b1,back:b7,dpdown:b14,dpleft:b15,dpright:b13,dpup:b2,rightshoulder:b6,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,\n03000000c0160000990a000000000000,Mega Drive Controller,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,righttrigger:b2,start:b3,platform:Windows,\n030000005e0400002800000000000000,Microsoft Dual Strike,a:b3,b:b2,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,rightx:a0,righty:a1~,start:b5,x:b1,y:b0,platform:Windows,\n030000005e0400000300000000000000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,\n030000005e0400000700000000000000,Microsoft SideWinder,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,\n030000005e0400000e00000000000000,Microsoft SideWinder Freestyle Pro,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b8,x:b3,y:b4,platform:Windows,\n030000005e0400002700000000000000,Microsoft SideWinder Plug and Play,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b4,righttrigger:b5,x:b2,y:b3,platform:Windows,\n03000000280d00000202000000000000,Miller Lite Cantroller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,start:b5,x:b2,y:b3,platform:Windows,\n03000000ad1b000023f0000000000000,MLG,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a6,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,\n03000000ad1b00003ef0000000000000,MLG Fightstick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows,\n03000000380700006382000000000000,MLG PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000004523000015e0000000000000,Mobapad Chitu HD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n03000000491900000904000000000000,Mobapad Chitu HD,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000ffff00000000000000000000,Mocute M053,a:b3,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b11,leftstick:b7,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b6,righttrigger:b4,rightx:a3,righty:a4,start:b8,x:b1,y:b0,platform:Windows,\n03000000d6200000e589000000000000,Moga 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Windows,\n03000000d62000007162000000000000,Moga Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Windows,\n03000000d6200000ad0d000000000000,Moga Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000c62400002a89000000000000,Moga XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c62400002b89000000000000,Moga XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c62400001a89000000000000,Moga XP5X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c62400001b89000000000000,Moga XP5X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000250900006688000000000000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n03000000091200004488000000000000,MUSIA PlayStation 2 Input Display,a:b0,b:b2,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b6,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b7,righttrigger:b11,rightx:a2,righty:a3,start:b5,x:b1,y:b3,platform:Windows,\n03000000f70600000100000000000000,N64 Adaptoid,+rightx:b2,+righty:b1,-rightx:b4,-righty:b5,a:b0,b:b3,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,platform:Windows,\n030000006b140000010c000000000000,Nacon GC 400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n030000006b1400001106000000000000,Nacon Revolution 3 PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n0300000085320000170d000000000000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n0300000085320000190d000000000000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000006b140000100d000000000000,Nacon Revolution Infinity PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000006b140000080d000000000000,Nacon Revolution Unlimited Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000bd12000001c0000000000000,Nebular,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b3,y:b0,platform:Windows,\n03000000eb0300000000000000000000,NeGcon Adapter,a:a2,b:b13,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,lefttrigger:a4,leftx:a1,righttrigger:b11,start:b3,x:a3,y:b12,platform:Windows,\n0300000038070000efbe000000000000,NEO SE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n0300000092120000474e000000000000,NeoGeo X Arcade Stick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b3,y:b2,platform:Windows,\n03000000921200004b46000000000000,NES 2 port Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b11,platform:Windows,\n03000000000f00000100000000000000,NES Controller,a:b1,b:b0,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Windows,\n03000000921200004346000000000000,NES Controller,a:b0,b:b1,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Windows,\n03000000790000004518000000000000,NEXILUX GameCube Controller Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,\n030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Windows,\n03000000050b00000045000000000000,Nexus,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Windows,\n03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows,\n030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Windows,\n030000007e0500001920000000000000,NSO N64 Controller,+rightx:b8,+righty:b2,-rightx:b3,-righty:b7,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Windows,\n030000007e0500001720000000000000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b15,start:b9,x:b2,y:b3,platform:Windows,\n03000000550900001472000000000000,NVIDIA Controller,a:b11,b:b10,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b5,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b4,righttrigger:a5,rightx:a3,righty:a6,start:b3,x:b9,y:b8,platform:Windows,\n03000000550900001072000000000000,NVIDIA Shield,a:b9,b:b8,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b3,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b2,righttrigger:a4,rightx:a2,righty:a5,start:b0,x:b7,y:b6,platform:Windows,\n030000005509000000b4000000000000,NVIDIA Virtual,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000120c00000288000000000000,Nyko Air Flo Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,\n030000004b120000014d000000000000,NYKO Airflo EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,\n03000000d62000001d57000000000000,Nyko Airflo PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000791d00000900000000000000,Nyko Playpad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000782300000a10000000000000,Onlive Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,platform:Windows,\n030000000d0f00000401000000000000,Onyx,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000008916000001fd000000000000,Onza CE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a3,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000008916000000fd000000000000,Onza TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000d62000006d57000000000000,OPP PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,\n0300000009120000072f000000000000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:-a2,leftx:a0,lefty:a1,righttrigger:-a5,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000362800000100000000000000,OUYA Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows,\n03000000120c0000f60e000000000000,P4 Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,\n03000000790000002201000000000000,PC Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n030000006f0e00008501000000000000,PDP Fightpad Pro GameCube Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n030000006f0e00000901000000000000,PDP PS3 Versus Fighting,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00008901000000000000,PDP Realmz Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000008f0e00004100000000000000,PlaySega,a:b1,b:b0,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b8,x:b4,y:b3,platform:Windows,\n03000000d620000011a7000000000000,PowerA Core Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000dd62000015a7000000000000,PowerA Fusion Nintendo Switch Arcade Stick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000d620000012a7000000000000,PowerA Fusion Nintendo Switch Fight Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000dd62000016a7000000000000,PowerA Fusion Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000d620000013a7000000000000,PowerA Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000d62000003340000000000000,PowerA OPS Pro Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000d62000002640000000000000,PowerA OPS Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n0300000062060000d570000000000000,PowerA PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000d620000014a7000000000000,PowerA Spectra Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006d04000084ca000000000000,Precision,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,\n03000000d62000009557000000000000,Pro Elite PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000c62400001a53000000000000,Pro Ex Mini,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000d62000009f31000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000d6200000c757000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000120c0000110e000000000000,Pro5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000100800000100000000000000,PS1 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,\n030000008f0e00007530000000000000,PS1 Controller,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000100800000300000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,\n03000000250900000088000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n03000000250900006888000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b6,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n03000000250900008888000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n030000006b1400000303000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n030000009d0d00001330000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n03000000151a00006222000000000000,PS2 Dual Plus Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n03000000120a00000100000000000000,PS3 Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000120c00001307000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000120c00001cf1000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000120c0000f90e000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000250900000118000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n03000000250900000218000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n03000000250900000500000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows,\n030000004c0500006802000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b10,lefttrigger:a3~,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:a4~,rightx:a2,righty:a5,start:b8,x:b3,y:b0,platform:Windows,\n030000004f1f00000800000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,\n03000000632500007505000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b0,platform:Windows,\n03000000888800000804000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Windows,\n030000008f0e00000300000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows,\n030000008f0e00001431000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000ba2200002010000000000000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b3,y:b2,platform:Windows,\n03000000120c00000807000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000111e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000121e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000130e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000150e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000180e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000181e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000191e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c00001e0e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000a957000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000aa57000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000f21c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000f31c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000f41c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000f51c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120c0000f70e000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000120e0000120c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000160e0000120c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000001a1e0000120c000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000004c050000a00b000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000004c050000cc09000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000004c0500005f0e000000000000,PS5 Access Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000004c050000e60c000000000000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000004c050000f20d000000000000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000830500005020000000000000,PSX,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b2,y:b3,platform:Windows,\n03000000300f00000111000000000000,Qanba 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000300f00000211000000000000,Qanba 2P,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n03000000300f00000011000000000000,Qanba Arcade Stick 1008,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b10,x:b0,y:b3,platform:Windows,\n03000000300f00001611000000000000,Qanba Arcade Stick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,\n03000000222c00000025000000000000,Qanba Dragon Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000222c00000020000000000000,Qanba Drone Arcade Stick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,platform:Windows,\n03000000300f00001211000000000000,Qanba Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000300f00001210000000000000,Qanba Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,\n03000000341a00000104000000000000,Qanba Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,platform:Windows,\n03000000222c00000223000000000000,Qanba Obsidian Arcade Stick PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000222c00000023000000000000,Qanba Obsidian Arcade Stick PS4,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000008a2400006682000000000000,R1 Mobile Controller,a:b3,b:b1,back:b7,leftx:a0,lefty:a1,start:b6,x:b4,y:b0,platform:Windows,\n03000000086700006626000000000000,RadioShack,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows,\n03000000ff1100004733000000000000,Ramox FPS Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b0,platform:Windows,\n030000009b2800002300000000000000,Raphnet 3DO Adapter,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b2,start:b3,platform:Windows,\n030000009b2800006900000000000000,Raphnet 3DO Adapter,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b2,start:b3,platform:Windows,\n030000009b2800000800000000000000,Raphnet Dreamcast Adapter,a:b2,b:b1,dpdown:b5,dpleft:b6,dpright:b7,dpup:b4,lefttrigger:a2,leftx:a0,righttrigger:a3,righty:a1,start:b3,x:b10,y:b9,platform:Windows,\n030000009b280000d000000000000000,Raphnet Dreamcast Adapter,a:b1,b:b0,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,lefttrigger:+a5,leftx:a0,lefty:a1,righttrigger:+a2,start:b3,x:b5,y:b4,platform:Windows,\n030000009b2800006200000000000000,Raphnet GameCube Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows,\n030000009b2800003200000000000000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows,\n030000009b2800006000000000000000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows,\n030000009b2800001800000000000000,Raphnet Jaguar Adapter,a:b2,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b10,start:b3,x:b11,y:b12,platform:Windows,\n030000009b2800003c00000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows,\n030000009b2800006100000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows,\n030000009b2800006300000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows,\n030000009b2800006400000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows,\n030000009b2800000200000000000000,Raphnet NES Adapter,a:b7,b:b6,back:b5,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,start:b4,platform:Windows,\n030000009b2800004400000000000000,Raphnet PS1 and PS2 Adapter,a:b1,b:b2,back:b5,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b9,rightx:a3,righty:a4,start:b4,x:b0,y:b3,platform:Windows,\n030000009b2800004300000000000000,Raphnet Saturn,a:b0,b:b1,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,\n030000009b2800000500000000000000,Raphnet Saturn Adapter 2.0,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,\n030000009b2800000300000000000000,Raphnet SNES Adapter,a:b0,b:b4,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows,\n030000009b2800002600000000000000,Raphnet SNES Adapter,a:b1,b:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Windows,\n030000009b2800002e00000000000000,Raphnet SNES Adapter,a:b1,b:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Windows,\n030000009b2800002f00000000000000,Raphnet SNES Adapter,a:b1,b:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Windows,\n030000009b2800005600000000000000,Raphnet SNES Adapter,a:b1,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Windows,\n030000009b2800005700000000000000,Raphnet SNES Adapter,a:b1,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Windows,\n030000009b2800001e00000000000000,Raphnet Vectrex Adapter,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a1,lefty:a2,x:b2,y:b3,platform:Windows,\n030000009b2800002b00000000000000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b3,x:b0,y:b5,platform:Windows,\n030000009b2800002c00000000000000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b3,x:b0,y:b5,platform:Windows,\n030000009b2800008000000000000000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b3,x:b0,y:b5,platform:Windows,\n03000000790000008f18000000000000,Rapoo Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows,\n0300000032150000a602000000000000,Razer Huntsman V3 Pro,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b12,dpright:b13,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000f8270000bf0b000000000000,Razer Kishi,a:b6,b:b7,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b18,leftshoulder:b12,leftstick:b19,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b13,rightstick:b20,righttrigger:b15,rightx:a3,righty:a4,start:b17,x:b9,y:b10,platform:Windows,\n03000000321500000204000000000000,Razer Panthera PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000321500000104000000000000,Razer Panthera PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000321500000010000000000000,Razer Raiju,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000321500000507000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000321500000707000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000321500000710000000000000,Razer Raiju TE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000321500000a10000000000000,Razer Raiju TE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000321500000410000000000000,Razer Raiju UE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000321500000910000000000000,Razer Raiju UE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000321500000011000000000000,Razer Raion PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000321500000009000000000000,Razer Serval,+lefty:+a2,-lefty:-a1,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,leftx:a0,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000921200004547000000000000,Retro Bit Sega Genesis Controller Adapter,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b6,x:b3,y:b4,platform:Windows,\n03000000790000001100000000000000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,\n03000000830500006020000000000000,Retro Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b8,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows,\n03000000632500007805000000000000,Retro Fighters Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n0300000003040000c197000000000000,Retrode Adapter,a:b0,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows,\n03000000bd12000013d0000000000000,Retrolink Sega Saturn Classic Controller,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b5,lefttrigger:b6,rightshoulder:b2,righttrigger:b7,start:b8,x:b3,y:b4,platform:Windows,\n03000000bd12000015d0000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,\n03000000341200000400000000000000,RetroUSB N64 RetroPort,+rightx:b8,+righty:b10,-rightx:b9,-righty:b11,a:b7,b:b6,dpdown:b2,dpleft:b1,dpright:b0,dpup:b3,leftshoulder:b13,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b12,start:b4,platform:Windows,\n0300000000f000000300000000000000,RetroUSB RetroPad,a:b1,b:b5,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,\n0300000000f00000f100000000000000,RetroUSB Super RetroPort,a:b1,b:b5,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,\n03000000830500000960000000000000,Revenger,a:b0,b:b1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b3,x:b4,y:b5,platform:Windows,\n030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000006b140000020d000000000000,Revolution Pro Controller 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000006b140000130d000000000000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00001f01000000000000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000006f0e00004601000000000000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000c6240000fefa000000000000,Rock Candy Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000006f0e00008701000000000000,Rock Candy Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00001e01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00002801000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00002f01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000830500007030000000000000,Rockfire Space Ranger,a:b0,b:b1,back:b5,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b9,righttrigger:b8,start:b2,x:b3,y:b4,platform:Windows,\n03000000050b0000e318000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,\n03000000050b0000e518000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,\n03000000050b00005819000000000000,ROG Chakram Core,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,\n03000000050b0000181a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,\n03000000050b00001a1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,\n03000000050b00001c1a000000000000,ROG Chakram X,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,\n030000004f04000001d0000000000000,Rumble Force,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,\n030000000d0f0000ad00000000000000,RX Gamepad,a:b0,b:b4,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b3,rightshoulder:b6,start:b9,x:b2,y:b1,platform:Windows,\n030000008916000000fe000000000000,Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000c6240000045d000000000000,Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000006f0e00001311000000000000,Saffun Controller,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b0,platform:Windows,\n03000000a30600001af5000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,\n03000000a306000023f6000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,\n03000000300f00001201000000000000,Saitek Dual Analog,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,\n03000000a30600000701000000000000,Saitek P220,a:b2,b:b3,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,x:b0,y:b1,platform:Windows,\n03000000a30600000cff000000000000,Saitek P2500 Force Rumble,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b0,y:b1,platform:Windows,\n03000000a30600000d5f000000000000,Saitek P2600,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Windows,\n03000000a30600000dff000000000000,Saitek P2600,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b8,x:b0,y:b3,platform:Windows,\n03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,\n03000000a306000018f5000000000000,Saitek P3200,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,\n03000000300f00001001000000000000,Saitek P480 Rumble,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,\n03000000a30600000901000000000000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b8,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b5,rightx:a3,righty:a2,x:b0,y:b1,platform:Windows,\n03000000a30600000b04000000000000,Saitek P990,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,\n03000000a30600002106000000000000,Saitek PS1000 PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,\n03000000a306000020f6000000000000,Saitek PS2700 PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,\n03000000300f00001101000000000000,Saitek Rumble,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,\n03000000e804000000a0000000000000,Samsung EIGP20,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000c01100000252000000000000,Sanwa Easy Grip,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,\n03000000c01100004350000000000000,Sanwa Micro Grip P3,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,x:b3,y:b2,platform:Windows,\n03000000411200004550000000000000,Sanwa Micro Grip Pro,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a1,righty:a2,start:b9,x:b1,y:b3,platform:Windows,\n03000000c01100004150000000000000,Sanwa Micro Grip Pro,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,\n03000000c01100004450000000000000,Sanwa Online Grip,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b11,righttrigger:b9,rightx:a3,righty:a2,start:b14,x:b3,y:b4,platform:Windows,\n03000000730700000401000000000000,Sanwa PlayOnline Mobile,a:b0,b:b1,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Windows,\n03000000830500006120000000000000,Sanwa Smart Grip II,a:b0,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,x:b1,y:b3,platform:Windows,\n03000000c01100000051000000000000,Satechi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n030000004f04000028b3000000000000,Score A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000952e00002577000000000000,Scuf PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000a30c00002500000000000000,Sega Genesis Mini 3B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,righttrigger:b5,start:b9,platform:Windows,\n03000000a30c00002400000000000000,Sega Mega Drive Mini 6B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows,\n03000000d804000086e6000000000000,Sega Multi Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:a2,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,\n0300000000050000289b000000000000,Sega Saturn Adapter,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,\n0300000000f000000800000000000000,Sega Saturn Controller,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b7,righttrigger:b3,start:b0,x:b5,y:b6,platform:Windows,\n03000000730700000601000000000000,Sega Saturn Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,\n03000000b40400000a01000000000000,Sega Saturn Controller,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,\n030000003b07000004a1000000000000,SFX,a:b0,b:b2,back:b7,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b5,start:b8,x:b1,y:b3,platform:Windows,\n03000000f82100001900000000000000,Shogun Bros Chameleon X1,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n03000000120c00001c1e000000000000,SnakeByte 4S PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000140300000918000000000000,SNES Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows,\n0300000081170000960a000000000000,SNES Controller,a:b4,b:b0,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b5,y:b1,platform:Windows,\n03000000811700009d0a000000000000,SNES Controller,a:b0,b:b4,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows,\n030000008b2800000300000000000000,SNES Controller,a:b0,b:b4,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows,\n03000000921200004653000000000000,SNES Controller,a:b0,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows,\n030000008f0e00000910000000000000,Sony DualShock 2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Windows,\n03000000317300000100000000000000,Sony DualShock 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000666600006706000000000000,Sony PlayStation Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Windows,\n03000000e30500009605000000000000,Sony PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n03000000fe1400002a23000000000000,Sony PlayStation Adapter,a:b0,b:b1,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,x:b2,y:b3,platform:Windows,\n030000004c050000da0c000000000000,Sony PlayStation Classic Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows,\n03000000632500002306000000000000,Sony PlayStation Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows,\n03000000f0250000c183000000000000,Sony PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000d9040000160f000000000000,Sony PlayStation Controller Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,\n03000000ff000000cb01000000000000,Sony PlayStation Portable,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows,\n030000004c0500003713000000000000,Sony PlayStation Vita,a:b1,b:b2,back:b8,dpdown:b13,dpleft:b15,dpright:b14,dpup:b12,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,\n03000000341a00000208000000000000,Speedlink 6555,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,platform:Windows,\n03000000341a00000908000000000000,Speedlink 6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n03000000380700001722000000000000,Speedlink Competition Pro,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,x:b2,y:b3,platform:Windows,\n030000008f0e00000800000000000000,Speedlink Strike FX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000c01100000591000000000000,Speedlink Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000de280000fc11000000000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000de280000ff11000000000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000120c0000160e000000000000,Steel Play Metaltech PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n03000000110100001914000000000000,SteelSeries,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000381000001214000000000000,SteelSeries Free,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows,\n03000000110100003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000381000003014000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000381000003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000381000001814000000000000,SteelSeries Stratus XL,a:b0,b:b1,back:b18,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b19,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b2,y:b3,platform:Windows,\n03000000380700003847000000000000,Street Fighter Fightstick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b11,start:b7,x:b2,y:b3,platform:Windows,\n030000001f08000001e4000000000000,Super Famicom Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,\n03000000790000000418000000000000,Super Famicom Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b33,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows,\n03000000341200001300000000000000,Super Racer,a:b2,b:b3,back:b8,leftshoulder:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b7,x:b0,y:b1,platform:Windows,\n03000000457500002211000000000000,Szmy Power PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000004f0400000ab1000000000000,T16000M,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b10,x:b2,y:b3,platform:Windows,\n030000000d0f00007b00000000000000,TAC GEAR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000e40a00000307000000000000,Taito Egret II Mini Control Panel,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Windows,\n03000000e40a00000207000000000000,Taito Egret II Mini Controller,a:b4,b:b2,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b9,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Windows,\n03000000d814000001a0000000000000,TE Kitty,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,\n03000000ba2200000701000000000000,Technology Innovation PS2 Adapter,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b2,platform:Windows,\n03000000c61100001000000000000000,Tencent Xianyou Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,x:b3,y:b4,platform:Windows,\n03000000790000001c18000000000000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000790000002601000000000000,TGZ Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows,\n03000000591c00002400000000000000,THEC64 Joystick,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows,\n03000000591c00002600000000000000,THEGamepad,a:b2,b:b1,back:b6,leftx:a0,lefty:a1,start:b7,x:b3,y:b0,platform:Windows,\n030000004f04000015b3000000000000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,\n030000004f04000023b3000000000000,Thrustmaster Dual Trigger PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000004f0400000ed0000000000000,Thrustmaster eSwap Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000004f04000008d0000000000000,Thrustmaster Ferrari 150 PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Windows,\n030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,\n030000004f04000003d0000000000000,Thrustmaster Run N Drive PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000004f04000009d0000000000000,Thrustmaster Run N Drive PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n030000006d04000088ca000000000000,Thunderpad,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,\n03000000666600000288000000000000,TigerGame PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n03000000666600000488000000000000,TigerGame PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n030000004f04000007d0000000000000,TMini,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000571d00002100000000000000,Tomee NES Controller Adapter,a:b1,b:b0,back:b2,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,start:b3,platform:Windows,\n03000000571d00002000000000000000,Tomee SNES Controller Adapter,a:b0,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows,\n03000000d62000006000000000000000,Tournament PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000c01100000055000000000000,Tronsmart,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n030000005f140000c501000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000b80500000210000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n030000004f04000087b6000000000000,TWCS Throttle,dpdown:b8,dpleft:b9,dpright:b7,dpup:b6,leftstick:b5,lefttrigger:-a5,leftx:a0,lefty:a1,righttrigger:+a5,platform:Windows,\n03000000411200000450000000000000,Twin Shock,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a4,start:b11,x:b3,y:b0,platform:Windows,\n03000000d90400000200000000000000,TwinShock PS2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,\n03000000151900005678000000000000,Uniplay U6,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000101c0000171c000000000000,uRage Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n030000000b0400003065000000000000,USB Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows,\n03000000242f00006e00000000000000,USB Controller,a:b1,b:b4,back:b10,leftshoulder:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b7,rightx:a2,righty:a5,start:b11,x:b0,y:b3,platform:Windows,\n03000000300f00000701000000000000,USB Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,\n03000000341a00002308000000000000,USB Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n03000000666600000188000000000000,USB Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,\n030000006b1400000203000000000000,USB Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n03000000790000000a00000000000000,USB Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,\n03000000b404000081c6000000000000,USB Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b3,y:b0,platform:Windows,\n03000000b50700001503000000000000,USB Controller,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b0,y:b1,platform:Windows,\n03000000bd12000012d0000000000000,USB Controller,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows,\n03000000ff1100004133000000000000,USB Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,\n03000000632500002305000000000000,USB Vibration Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,\n03000000882800000305000000000000,V5 Game Pad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,x:b2,y:b3,platform:Windows,\n03000000790000001a18000000000000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,\n03000000790000001b18000000000000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000006f0e00000302000000000000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n030000006f0e00000702000000000000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,\n0300000034120000adbe000000000000,vJoy Device,a:b0,b:b1,back:b15,dpdown:b6,dpleft:b7,dpright:b8,dpup:b5,guide:b16,leftshoulder:b9,leftstick:b13,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b14,righttrigger:b12,rightx:a3,righty:a4,start:b4,x:b2,y:b3,platform:Windows,\n03000000120c0000ab57000000000000,Warrior Joypad JS083,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n030000007e0500003003000000000000,Wii U Pro,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,leftshoulder:b6,leftstick:b11,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b12,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,\n0300000032150000030a000000000000,Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n0300000032150000140a000000000000,Wolverine,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000002e160000efbe000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b10,rightshoulder:b5,righttrigger:b11,start:b7,x:b2,y:b3,platform:Windows,\n03000000380700001647000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000380700002045000000000000,Xbox 360 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n03000000380700002644000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a2,righty:a5,start:b8,x:b2,y:b3,platform:Windows,\n03000000380700002647000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000003807000026b7000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000380700003647000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a7,righty:a5,start:b7,x:b2,y:b3,platform:Windows,\n030000005e0400001907000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e0400008e02000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e0400009102000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000ad1b000000fd000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000ad1b000001fd000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000ad1b000016f0000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000ad1b00008e02000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000c62400000053000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000c6240000fdfa000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000380700002847000000000000,Xbox 360 Fightpad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e040000a102000000000000,Xbox 360 Wireless Receiver,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e0400000a0b000000000000,Xbox Adaptive Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000120c00000a88000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a2,righty:a4,start:b6,x:b2,y:b3,platform:Windows,\n03000000120c00001088000000000000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2~,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5~,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000002a0600002000000000000000,Xbox Controller,a:b0,b:b1,back:b13,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,leftshoulder:b5,leftstick:b14,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b15,righttrigger:b7,rightx:a2,righty:a5,start:b12,x:b2,y:b3,platform:Windows,\n03000000380700001645000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,\n03000000380700002645000000000000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000380700003645000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,\n03000000380700008645000000000000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e0400000202000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,\n030000005e0400008502000000000000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e0400008702000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b7,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,\n030000005e0400008902000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b10,leftstick:b8,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b9,righttrigger:b4,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,\n030000005e0400000c0b000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e040000d102000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e040000dd02000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e040000e002000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e040000e302000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e040000ea02000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e040000fd02000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e040000ff02000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000006f0e0000a802000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000006f0e0000c802000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000c62400003a54000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000005e040000130b000000000000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n03000000341a00000608000000000000,Xeox,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n03000000450c00002043000000000000,Xeox SL6556BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,\n030000006f0e00000300000000000000,XGear,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a5,righty:a2,start:b9,x:b3,y:b0,platform:Windows,\n03000000e0ff00000201000000000000,Xiaomi Black Shark (L),back:b0,dpdown:b11,dpleft:b9,dpright:b10,dpup:b8,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,platform:Windows,\n03000000172700004431000000000000,Xiaomi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,\n03000000172700003350000000000000,Xiaomi XMGP01YM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000bc2000005060000000000000,Xiaomi XMGP01YM,+lefty:+a2,+righty:+a5,-lefty:-a1,-righty:-a4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,start:b11,x:b3,y:b4,platform:Windows,\nxinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n030000007d0400000340000000000000,Xterminator Digital Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:-a4,lefttrigger:+a4,leftx:a0,lefty:a1,paddle1:b7,paddle2:b6,rightshoulder:b5,rightstick:b9,righttrigger:b2,rightx:a3,righty:a5,start:b8,x:b3,y:b4,platform:Windows,\n030000002c3600000100000000000000,Yawman Arrow,+rightx:h0.2,+righty:h0.4,-rightx:h0.8,-righty:h0.1,a:b4,b:b5,back:b6,dpdown:b15,dpleft:b14,dpright:b16,dpup:b13,leftshoulder:b10,leftstick:b0,lefttrigger:-a4,leftx:a0,lefty:a1,paddle1:b11,paddle2:b12,rightshoulder:b8,rightstick:b9,righttrigger:+a4,start:b3,x:b1,y:b2,platform:Windows,\n03000000790000004f18000000000000,ZDT Android Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,\n03000000120c00000500000000000000,Zeroplus Adapter,a:b2,b:b1,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b0,righttrigger:b5,rightx:a3,righty:a2,start:b8,x:b3,y:b0,platform:Windows,\n03000000120c0000101e000000000000,Zeroplus P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,\n\n# Mac OS X\n030000008f0e00000300000009010000,2 In 1 Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,\n03000000c82d00000031000001000000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00000531000000020000,8BitDo Adapter 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00000951000000010000,8BitDo Dogbone,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Mac OS X,\n03000000c82d00000090000001000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00006a28000000010000,8BitDo GameCube,a:b0,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b9,paddle2:b8,rightshoulder:b10,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b1,y:b4,platform:Mac OS X,\n03000000c82d00001251000000010000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00001251000000020000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00001151000000010000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00001151000000020000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000a30c00002400000006020000,8BitDo M30,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,guide:b9,leftshoulder:b6,lefttrigger:b5,rightshoulder:b4,righttrigger:b7,start:b8,x:b3,y:b0,platform:Mac OS X,\n03000000c82d00000151000000010000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00000650000001000000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00005106000000010000,8BitDo M30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b2,leftshoulder:b6,lefttrigger:a5,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00002090000000010000,8BitDo Micro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000451000000010000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Mac OS X,\n03000000c82d00001590000001000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00006928000000010000,8BitDo N64,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X,\n03000000c82d00002590000000010000,8BitDo NEOGEO,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00002590000001000000,8BitDo NEOGEO,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00002690000000010000,8BitDo NEOGEO,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b10,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000003512000012ab000001000000,8BitDo NES30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d000012ab000001000000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00002028000000010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000022000000090000001000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000190000001000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000751000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00000851000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000660000000020000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000131000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000231000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000331000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000431000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00002867000000010000,8BitDo S30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00003028000000010000,8Bitdo SFC30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000102800000900000000000000,8BitDo SFC30 Joystick,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000351000000010000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00001290000001000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00004028000000010000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000260000001000000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00000261000000010000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00001230000000010000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00001b30000001000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00001d30000001000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00001530000001000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00001630000001000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00001730000001000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00001130000000020000,8BitDo Ultimate Wired,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00001330000001000000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00001330000000020000,8BitDo Ultimate Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000a00500003232000008010000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000a00500003232000009010000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c82d00001890000001000000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a31,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000491900001904000001010000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Mac OS X,\n03000000710100001904000000010000,Amazon Luna Controller,a:b0,b:b1,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Mac OS X,\n0300000008100000e501000019040000,Anbernic Handheld,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a4,start:b11,x:b4,y:b3,platform:Mac OS X,\n03000000373500004610000001000000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000a30c00002700000003030000,Astro City Mini,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Mac OS X,\n03000000a30c00002800000003030000,Astro City Mini,a:b2,b:b1,back:b8,leftx:a3,lefty:a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Mac OS X,\n03000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,\n03000000050b00000579000000010000,ASUS ROG Kunai 3,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b42,paddle1:b9,paddle2:b11,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,platform:Mac OS X,\n03000000050b00000679000000010000,ASUS ROG Kunai 3,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b23,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,platform:Mac OS X,\n03000000503200000110000045010000,Atari VCS Classic,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b3,start:b2,platform:Mac OS X,\n03000000503200000110000047010000,Atari VCS Classic Controller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b3,start:b2,platform:Mac OS X,\n03000000503200000210000047010000,Atari VCS Modern Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,platform:Mac OS X,\n030000008a3500000102000000010000,Backbone One,a:b0,b:b1,back:b16,dpdown:b11,dpleft:b13,dpright:b12,dpup:b10,guide:b17,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3~,start:b15,x:b2,y:b3,platform:Mac OS X,\n030000008a3500000201000000010000,Backbone One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000008a3500000202000000010000,Backbone One,a:b0,b:b1,back:b16,dpdown:b11,dpleft:b13,dpright:b12,dpup:b10,guide:b17,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3~,start:b15,x:b2,y:b3,platform:Mac OS X,\n030000008a3500000402000000010000,Backbone One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000008a3500000302000000010000,Backbone One PlayStation Edition,a:b0,b:b1,back:b16,dpdown:b11,dpleft:b13,dpright:b12,dpup:b10,guide:b17,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3~,start:b15,x:b2,y:b3,platform:Mac OS X,\n03000000c62400001a89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,platform:Mac OS X,\n03000000c62400001b89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000d62000002a79000000010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n03000000120c0000200e000000010000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n03000000120c0000210e000000010000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000d8140000cecf000000000000,Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Mac OS X,\n03000000a306000022f6000001030000,Cyborg V3 Rumble Pad PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000791d00000103000009010000,Dual Box Wii Classic Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,\n030000006e0500000720000010020000,Elecom JC-W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Mac OS X,\n030000006f0e00008401000003010000,Faceoff Deluxe Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b13,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000151900004000000001000000,Flydigi Vader 2,a:b14,b:b15,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Mac OS X,\n03000000b40400001124000001040000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b14,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000b40400001224000003030000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b2,paddle1:b16,paddle2:b17,paddle3:b14,paddle4:b15,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000790000004618000000010000,GameCube Controller Adapter,a:b4,b:b0,dpdown:b56,dpleft:b60,dpright:b52,dpup:b48,lefttrigger:a12,leftx:a0,lefty:a4,rightshoulder:b28,righttrigger:a16,rightx:a20,righty:a8,start:b36,x:b8,y:b12,platform:Mac OS X,\n03000000ac0500001a06000002020000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000ad1b000001f9000000000000,Gamestop BB070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,\n03000000c01100000140000000010000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000006f0e00000102000000000000,GameStop Xbox 360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n03000000ff1100003133000007010000,GameWare PC Control Pad,a:b2,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b0,platform:Mac OS X,\n03000000d11800000094000000010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X,\n030000007d0400000540000001010000,Gravis Eliminator Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000280400000140000000020000,Gravis GamePad Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000008f0e00000300000007010000,GreenAsia Joystick,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Mac OS X,\n030000000d0f00002d00000000100000,Hori Fighting Commander 3 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00005f00000000000000,Hori Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00005f00000000010000,Hori Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00005e00000000000000,Hori Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00005e00000000010000,Hori Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00008400000000010000,Hori Fighting Commander PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00008500000000010000,Hori Fighting Commander PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000341a00000302000014010000,Hori Fighting Stick Mini,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00008800000000010000,Hori Fighting Stick mini 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00008700000000010000,Hori Fighting Stick mini 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00004d00000000000000,Hori Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00003801000008010000,Hori PC Engine Mini Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,platform:Mac OS X,\n030000000d0f00009200000000010000,Hori Pokken Tournament DX Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f0000aa00000072050000,Hori Real Arcade Pro for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,\n030000000d0f00000002000017010000,Hori Split Pad Fit,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00000002000015010000,Hori Switch Split Pad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00006e00000000010000,Horipad 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00006600000000010000,Horipad 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000000d0f00006600000000000000,Horipad FPS Plus 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f0000ee00000000010000,Horipad Mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000000d0f0000c100000072050000,Horipad Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000242e0000ff0b000000010000,Hyperkin N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,platform:Mac OS X,\n03000000790000004e95000000010000,Hyperkin N64 Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a5,righty:a2,start:b9,platform:Mac OS X,\n03000000830500006020000000000000,iBuffalo Super Famicom Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,\n03000000ef0500000300000000020000,InterAct AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Mac OS X,\n03000000fd0500000030000010010000,Interact GoPad,a:b3,b:b4,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,x:b0,y:b1,platform:Mac OS X,\n030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Mac OS X,\n030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Mac OS X,\n03000000242f00002d00000007010000,JYS Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,\n030000006d04000019c2000000000000,Logitech Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000006d04000016c2000000000000,Logitech F310,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000006d04000018c2000000000000,Logitech F510,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000006d04000019c2000005030000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000006d0400001fc2000000000000,Logitech F710,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n030000006d04000018c2000000010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000380700005032000000010000,Mad Catz PS3 Fightpad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000380700008433000000010000,Mad Catz PS3 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000380700005082000000010000,Mad Catz PS4 Fightpad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n03000000380700008483000000010000,Mad Catz PS4 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n0300000049190000020400001b010000,Manba One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000790000000600000007010000,Marvo GT-004,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,\n030000008f0e00001330000011010000,Mayflash Controller Adapter,a:b2,b:b4,back:b16,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b12,lefttrigger:b16,leftx:a0,lefty:a2,rightshoulder:b14,rightx:a6~,righty:a4,start:b18,x:b0,y:b6,platform:Mac OS X,\n03000000790000004318000000010000,Mayflash GameCube Adapter,a:b4,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a12,leftx:a0,lefty:a4,rightshoulder:b28,righttrigger:a16,rightx:a20,righty:a8,start:b36,x:b8,y:b12,platform:Mac OS X,\n03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000242f00007300000000020000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Mac OS X,\n0300000079000000d218000026010000,Mayflash Magic NS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,\n03000000d620000010a7000003010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000008f0e00001030000011010000,Mayflash Saturn Adapter,a:b0,b:b2,dpdown:b28,dpleft:b30,dpright:b26,dpup:b24,leftshoulder:b10,lefttrigger:b14,rightshoulder:b12,righttrigger:b4,start:b18,x:b6,y:b8,platform:Mac OS X,\n0300000025090000e803000000000000,Mayflash Wii Classic Adapter,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Mac OS X,\n03000000790000000318000000010000,Mayflash Wii DolphinBar,a:b8,b:b12,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b44,leftshoulder:b16,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b4,platform:Mac OS X,\n03000000790000000018000000000000,Mayflash Wii U Pro Adapter,a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X,\n03000000790000000018000000010000,Mayflash Wii U Pro Adapter,a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X,\n030000005e0400002800000002010000,Microsoft Dual Strike,a:b3,b:b2,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,rightx:a0,righty:a1~,start:b5,x:b1,y:b0,platform:Mac OS X,\n030000005e0400000300000006010000,Microsoft SideWinder,a:b0,b:b1,back:b9,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Mac OS X,\n030000005e0400000700000006010000,Microsoft SideWinder,a:b0,b:b1,back:b8,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Mac OS X,\n030000005e0400002700000001010000,Microsoft SideWinder Plug and Play,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b4,righttrigger:b5,x:b2,y:b3,platform:Mac OS X,\n030000004523000015e0000072050000,Mobapad Chitu HD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,\n03000000d62000007162000001000000,Moga Pro 2,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Mac OS X,\n03000000c62400002a89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c62400002b89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000632500007505000000020000,NeoGeo mini PAD Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b2,y:b3,platform:Mac OS X,\n03000000921200004b46000003020000,NES 2-port Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b11,platform:Mac OS X,\n030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Mac OS X,\n030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,\n030000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,\n030000007e0500000920000010020000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:Mac OS X,\n050000007e05000009200000ff070000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:Mac OS X,\n030000007e0500001920000001000000,NSO N64 Controller,+rightx:b8,+righty:b7,-rightx:b3,-righty:b2,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Mac OS X,\n030000007e0500001720000001000000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b15,start:b9,x:b2,y:b3,platform:Mac OS X,\n03000000550900001472000025050000,NVIDIA Controller,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Mac OS X,\n030000004b120000014d000000010000,Nyko Airflo EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Mac OS X,\n0300000009120000072f000000010000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a2,leftx:a0,lefty:a1,righttrigger:a5,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000006f0e00000901000002010000,PDP PS3 Versus Fighting,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000008f0e00000300000000000000,Piranha Xtreme PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X,\n03000000d620000011a7000000020000,PowerA Core Plus Gamecube Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,\n03000000d620000011a7000010050000,PowerA Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000d62000006dca000000010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000100800000300000006010000,PS2 Adapter,a:b2,b:b1,back:b8,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,\n030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,\n030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,\n030000004c0500006802000072050000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,\n030000004c050000a00b000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n0300004b4c0500005f0e000000010000,PS5 Access Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000004c050000f20d000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n050000004c050000f20d000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000005e040000e002000001000000,PXN P30 Pro Mobile,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,\n03000000222c00000225000000010000,Qanba Dragon Arcade Joystick PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000222c00000020000000010000,Qanba Drone Arcade Stick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000009b2800005600000020020000,Raphnet SNES Adapter,a:b1,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Mac OS X,\n030000009b2800008000000022020000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b3,x:b0,y:b5,platform:Mac OS X,\n030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n03000000321500000204000000010000,Razer Panthera PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000321500000104000000010000,Razer Panthera PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n03000000321500000010000000010000,Razer Raiju,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000321500000507000001010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000321500000011000000010000,Razer Raion PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n03000000321500000009000000020000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X,\n030000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X,\n0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n03000000632500008005000000010000,Redgear,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,\n03000000632500002305000000010000,Redragon Saturn,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,\n03000000921200004547000000020000,Retro Bit Sega Genesis Controller Adapter,a:b0,b:b2,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,lefttrigger:b14,rightshoulder:b10,righttrigger:b4,start:b12,x:b6,y:b8,platform:Mac OS X,\n03000000790000001100000000000000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000790000001100000005010000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b4,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000830500006020000000010000,Retro Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b8,righttrigger:b9,start:b7,x:b2,y:b3,platform:Mac OS X,\n0300000003040000c197000000000000,Retrode Adapter,a:b0,b:b4,back:b2,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Mac OS X,\n03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,\n03000000341200000400000000000000,RetroUSB N64 RetroPort,+rightx:b8,+righty:b10,-rightx:b9,-righty:b11,a:b7,b:b6,dpdown:b2,dpleft:b1,dpright:b0,dpup:b3,leftshoulder:b13,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b12,start:b4,platform:Mac OS X,\n030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000006b140000130d000000010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000004c0500006802000002100000,Rii RK707,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b2,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b3,righttrigger:b9,rightx:a2,righty:a3,start:b1,x:b15,y:b12,platform:Mac OS X,\n030000006f0e00008701000005010000,Rock Candy Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000c6240000fefa000000000000,Rock Candy PS3,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n03000000e804000000a000001b010000,Samsung EIGP20,a:b1,b:b3,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b11,leftx:a1,lefty:a3,rightshoulder:b12,rightx:a4,righty:a5,start:b16,x:b7,y:b9,platform:Mac OS X,\n03000000730700000401000000010000,Sanwa PlayOnline Mobile,a:b0,b:b1,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Mac OS X,\n03000000a30c00002500000006020000,Sega Genesis Mini 3B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,righttrigger:b5,start:b9,platform:Mac OS X,\n03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,platform:Mac OS X,\n03000000b40400000a01000000000000,Sega Saturn,a:b0,b:b1,back:b5,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b2,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Mac OS X,\n030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,\n0300000000f00000f100000000000000,SNES RetroPort,a:b2,b:b3,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b5,rightshoulder:b7,start:b6,x:b0,y:b1,platform:Mac OS X,\n030000004c050000a00b000000000000,Sony DualShock 4 Adapter,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n03000000666600006706000088020000,Sony PlayStation Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Mac OS X,\n030000004c050000da0c000000010000,Sony PlayStation Classic Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Mac OS X,\n030000004c0500003713000000010000,Sony PlayStation Vita,a:b1,b:b2,back:b8,dpdown:b13,dpleft:b15,dpright:b14,dpup:b12,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000005e0400008e02000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,\n03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,\n03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,\n05000000484944204465766963650000,SteelSeries Nimbus Plus,a:b0,b:b1,back:b15,dpdown:b11,dpleft:b13,dpright:b12,dpup:b10,guide:b16,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3~,start:b14,x:b2,y:b3,platform:Mac OS X,\n050000004e696d6275732b0000000000,SteelSeries Nimbus Plus,a:b0,b:b1,back:b15,dpdown:b11,dpleft:b13,dpright:b12,dpup:b10,guide:b16,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3~,start:b14,x:b2,y:b3,platform:Mac OS X,\n03000000381000003014000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n03000000381000003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,\n03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,\n030000000d0f0000f600000000010000,Switch Hori Pad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,\n03000000457500002211000000010000,SZMY Power PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000e40a00000307000001000000,Taito Egret II Mini Control Panel,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Mac OS X,\n03000000e40a00000207000001000000,Taito Egret II Mini Controller,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Mac OS X,\n03000000790000001c18000000010000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000790000001c18000003100000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000591c00002400000021000000,THEC64 Joystick,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Mac OS X,\n03000000591c00002600000021000000,THEGamepad,a:b2,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Mac OS X,\n030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Mac OS X,\n030000004f0400000ed0000000020000,Thrustmaster eSwap Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,\n030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X,\n03000000571d00002100000021000000,Tomee NES Controller Adapter,a:b1,b:b0,back:b2,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,start:b3,platform:Mac OS X,\n03000000bd12000015d0000000010000,Tomee Retro Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,\n03000000bd12000015d0000000000000,Tomee SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,\n03000000571d00002000000021000000,Tomee SNES Controller Adapter,a:b0,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Mac OS X,\n030000005f140000c501000000020000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,\n03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X,\n03000000632500002605000000010000,Uberwith Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000151900005678000010010000,Uniplay U6,a:b3,b:b6,back:b25,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b17,leftstick:b31,lefttrigger:b21,leftx:a1,lefty:a3,rightshoulder:b19,rightstick:b33,righttrigger:b23,rightx:a4,righty:a5,start:b27,x:b11,y:b13,platform:Mac OS X,\n030000006f0e00000302000025040000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n030000006f0e00000702000003060000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,\n050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,platform:Mac OS X,\n050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,platform:Mac OS X,\n030000005e0400008e02000000000000,Xbox 360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n030000005e0400008e02000010010000,Xbox 360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4~,start:b8,x:b2,y:b3,platform:Mac OS X,\n030000006f0e00000104000000000000,Xbox 360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n03000000c6240000045d000000000000,Xbox 360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n030000005e0400000a0b000000000000,Xbox Adaptive Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n030000005e040000050b000003090000,Xbox Elite Controller Series 2,a:b0,b:b1,back:b31,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b53,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000130b000011050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000200b000011050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000200b000013050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000200b000015050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000d102000000000000,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n030000005e040000dd02000000000000,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n030000005e040000e002000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,\n030000005e040000e002000003090000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,\n030000005e040000e302000000000000,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n030000005e040000ea02000000000000,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n030000005e040000fd02000003090000,Xbox One Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000c62400003a54000000000000,Xbox One PowerA Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,\n030000005e040000130b000001050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000130b000009050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000130b000013050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000130b000015050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000130b000007050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000130b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000130b000022050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n030000005e040000220b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000172700004431000029010000,XiaoMi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Mac OS X,\n03000000120c0000100e000000010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,\n03000000120c0000101e000000010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,\n\n# Linux\n03000000c82d00000031000011010000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00000631000000010000,8BitDo Adapter 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c82d00000951000000010000,8BitDo Dogbone,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Linux,\n03000000021000000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00006a28000000010000,8BitDo GameCube,a:b0,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b9,paddle2:b8,rightshoulder:b10,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b1,y:b4,platform:Linux,\n03000000c82d00001251000011010000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00001251000000010000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00001151000011010000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00001151000000010000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000151000000010000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00000650000011010000,8BitDo M30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000c82d00005106000000010000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00000a20000000020000,8BitDo M30 Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux,\n03000000c82d00002090000011010000,8BitDo Micro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00002090000000010000,8BitDo Micro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000451000000010000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Linux,\n03000000c82d00001590000011010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00006928000011010000,8BitDo N64,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Linux,\n05000000c82d00006928000000010000,8BitDo N64,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Linux,\n05000000c82d00002590000001000000,8BitDo NEOGEO,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000008000000210000011010000,8BitDo NES30,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n03000000c82d00000310000011010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b9,righttrigger:b8,start:b11,x:b3,y:b4,platform:Linux,\n05000000c82d00008010000000010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b9,righttrigger:b8,start:b11,x:b3,y:b4,platform:Linux,\n03000000022000000090000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000190000011010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000203800000900000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00002038000000010000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000751000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:a8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000c82d00000851000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:a8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00000660000011010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00001030000011010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000020000000000000,8BitDo Pro 2 for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n06000000c82d00000020000006010000,8BitDo Pro 2 for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c82d00000131000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000231000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000331000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000431000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00002867000000010000,8BitDo S30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b3,y:b4,platform:Linux,\n03000000c82d00000060000011010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00000060000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00000061000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n030000003512000012ab000010010000,8BitDo SFC30,a:b2,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Linux,\n030000003512000021ab000010010000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d000021ab000010010000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,\n05000000102800000900000000010000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00003028000000010000,8BitDo SFC30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00000351000000010000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00001290000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00006228000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000260000011010000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00000261000000010000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000202800000900000000010000,8BitDo SNES30,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00001230000000010000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00000a31000014010000,8BitDo Ultimate 2C,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c82d00001d30000011010000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000c82d00001b30000001000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00001530000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00001630000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00001730000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00001130000011010000,8BitDo Ultimate Wired,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00000631000010010000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c82d00000760000011010000,8BitDo Ultimate Wireless,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00001230000011010000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00001330000011010000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00000631000014010000,8BitDo Ultimate Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c82d00000121000011010000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000c82d00000121000000010000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000a00500003232000001000000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,\n05000000a00500003232000008010000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00001890000011010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c01100000355000011010000,Acrux Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00008801000011010000,Afterglow Deluxe Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00003901000013020000,Afterglow Prismatic Controller 048-007-NA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00001302000000010000,Afterglow Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00003901000020060000,Afterglow Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000100000008200000011010000,Akishop Customs PS360,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000007c1800000006000010010000,Alienware Dual Compatible Game PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Linux,\n05000000491900000204000021000000,Amazon Fire Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b17,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000491900001904000011010000,Amazon Luna Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b9,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Linux,\n05000000710100001904000000010000,Amazon Luna Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,\n0300000008100000e501000001010000,Anbernic Handheld,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a4,start:b11,x:b3,y:b4,platform:Linux,\n03000000020500000913000010010000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000373500000710000010010000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000373500004610000001000000,Anbernic RG P01,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000790000003018000011010000,Arcade Fightstick F300,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n03000000a30c00002700000011010000,Astro City Mini,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,\n03000000a30c00002800000011010000,Astro City Mini,a:b2,b:b1,back:b8,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,\n05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux,\n05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux,\n03000000050b00000579000011010000,ASUS ROG Kunai 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b36,paddle1:b52,paddle2:b53,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000050b00000679000000010000,ASUS ROG Kunai 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b21,paddle1:b22,paddle2:b23,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000503200000110000000000000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux,\n03000000503200000110000011010000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux,\n05000000503200000110000000000000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux,\n05000000503200000110000044010000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux,\n05000000503200000110000046010000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux,\n03000000503200000210000000000000,Atari VCS Modern Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,platform:Linux,\n03000000503200000210000011010000,Atari VCS Modern Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux,\n05000000503200000210000000000000,Atari VCS Modern Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux,\n05000000503200000210000045010000,Atari VCS Modern Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux,\n05000000503200000210000046010000,Atari VCS Modern Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux,\n05000000503200000210000047010000,Atari VCS Modern Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:-a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,platform:Linux,\n030000008a3500000201000011010000,Backbone One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000008a3500000202000011010000,Backbone One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000008a3500000302000011010000,Backbone One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000008a3500000402000011010000,Backbone One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c62400001b89000011010000,BDA MOGA XP5X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000d62000002a79000011010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n03000000c21100000791000011010000,Be1 GC101 Controller 1.03,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000c31100000791000011010000,Be1 GC101 Controller 1.03,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000005e0400008e02000003030000,Be1 GC101 Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000bc2000004d50000011010000,Beitong A1T2 BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000bc2000000055000001000000,Betop AX1 BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000bc2000006412000011010000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b30,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n030000006b1400000209000011010000,Bigben,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000120c0000300e000011010000,Brook Audio Fighting Board PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000120c0000310e000011010000,Brook Audio Fighting Board PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n03000000120c0000200e000011010000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n03000000120c0000210e000011010000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n03000000120c0000f70e000011010000,Brook Universal Fighting Board,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n03000000d81d00000b00000010010000,Buffalo BSGP1601,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Linux,\n03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000af1e00002400000010010000,Clockwork Pi DevTerm,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b9,x:b3,y:b0,platform:Linux,\n030000000b0400003365000000010000,Competition Pro,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Linux,\n03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux,\n03000000a306000022f6000011010000,Cyborg V3 Rumble,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,\n030000005e0400008e02000002010000,Data Frog S80,a:b1,b:b0,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux,\n03000000791d00000103000010010000,Dual Box Wii Classic Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000c11100000191000011010000,EasySMX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000242f00009100000000010000,EasySMX ESM-9101,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006e0500000320000010010000,Elecom U3613M,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux,\n030000006e0500000720000010010000,Elecom W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Linux,\n030000007d0400000640000010010000,Eliminator AfterShock,a:b1,b:b2,back:b9,dpdown:+a3,dpleft:-a5,dpright:+a5,dpup:-a3,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a4,righty:a2,start:b8,x:b0,y:b3,platform:Linux,\n03000000430b00000300000000010000,EMS Production PS2 Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a5,righty:a2,start:b9,x:b3,y:b0,platform:Linux,\n030000006f0e00008401000011010000,Faceoff Deluxe Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00008101000011010000,Faceoff Deluxe Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00008001000011010000,Faceoff Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000852100000201000010010000,FF GP1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n05000000b40400001224000001010000,Flydigi APEX 4,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b4,leftstick:b10,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b20,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n03000000b40400001124000011010000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b14,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000b40400001224000011010000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b2,paddle1:b16,paddle2:b17,paddle3:b14,paddle4:b15,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000151900004000000001000000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b14,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000007e0500003703000000000000,GameCube Adapter,a:b0,b:b1,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux,\n19000000030000000300000002030000,GameForce Controller,a:b1,b:b0,back:b8,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b16,leftshoulder:b4,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b5,rightstick:b15,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,\n03000000ac0500005b05000010010000,GameSir G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000558500001b06000010010000,GameSir G4 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000ac0500002d0200001b010000,GameSir G4s,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b33,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000ac0500007a05000011010000,GameSir G5,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b16,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000373500009710000001020000,GameSir Kaleid Flux,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000bc2000005656000011010000,GameSir T4w,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000ac0500001a06000011010000,GameSir-T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000008f0e00000800000010010000,Gasia PlayStation Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000451300000010000010010000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n03000000f0250000c283000010010000,Gioteck VX2 PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n190000004b4800000010000000010000,GO-Advance Controller,a:b1,b:b0,back:b10,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,leftshoulder:b4,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b13,start:b15,x:b2,y:b3,platform:Linux,\n190000004b4800000010000001010000,GO-Advance Controller,a:b1,b:b0,back:b12,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,leftshoulder:b4,leftstick:b13,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b16,righttrigger:b15,start:b17,x:b2,y:b3,platform:Linux,\n190000004b4800000011000000010000,GO-Super Gamepad,a:b0,b:b1,back:b12,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b16,leftshoulder:b4,leftstick:b14,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b3,y:b2,platform:Linux,\n03000000f0250000c183000010010000,Goodbetterbest Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000d11800000094000011010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,\n05000000d11800000094000000010000,Google Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,\n0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400008e02000001010000,GPD Win Max 2 6800U Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000007d0400000540000000010000,Gravis Eliminator Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n03000000280400000140000000010000,Gravis GamePad Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000008f0e00000610000000010000,GreenAsia Electronics Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Linux,\n030000008f0e00001200000010010000,GreenAsia Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,\n0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n03000000f0250000c383000010010000,GT VX2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n06000000adde0000efbe000002010000,Hidromancer Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000d81400000862000011010000,HitBox PS3 PC Analog Mode,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,platform:Linux,\n03000000c9110000f055000011010000,HJC Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n030000000d0f00006d00000020010000,Hori EDGE 301,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:+a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000000d0f00008400000011010000,Hori Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00005f00000011010000,Hori Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00005e00000011010000,Hori Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000000d0f00005001000009040000,Hori Fighting Commander Octa Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000000d0f00008500000010010000,Hori Fighting Commander PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00008600000002010000,Hori Fighting Commander Xbox 360,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n030000000d0f00003701000013010000,Hori Fighting Stick Mini,a:b1,b:b0,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b3,y:b2,platform:Linux,\n030000000d0f00008800000011010000,Hori Fighting Stick mini 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00008700000011010000,Hori Fighting Stick mini 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,rightshoulder:b5,rightstick:b11,righttrigger:a4,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000000d0f00001000000011010000,Hori Fightstick 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n03000000ad1b000003f5000033050000,Hori Fightstick VX,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b8,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,\n030000000d0f00004d00000011010000,Hori Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000000d0f00003801000011010000,Hori PC Engine Mini Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,platform:Linux,\n030000000d0f00009200000011010000,Hori Pokken Tournament DX Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00001100000011010000,Hori Real Arcade Pro 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00002200000011010000,Hori Real Arcade Pro 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00006a00000011010000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00006b00000011010000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00001600000000010000,Hori Real Arcade Pro EXSE,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,\n030000000d0f0000aa00000011010000,Hori Real Arcade Pro for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n030000000d0f00008501000017010000,Hori Split Pad Fit,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000000d0f00008501000015010000,Hori Switch Split Pad Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000000d0f00006e00000011010000,Horipad 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00006600000011010000,Horipad 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000000d0f0000ee00000011010000,Horipad Mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f0000c100000011010000,Horipad Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00006700000001010000,Horipad One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000000d0f0000ab01000011010000,Horipad Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000000d0f00009601000091000000,Horipad Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000000d0f0000f600000001000000,Horipad Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n03000000341a000005f7000010010000,HuiJia GameCube Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,\n05000000242e00000b20000001000000,Hyperkin Admiral N64 Controller,+rightx:b11,+righty:b13,-rightx:b8,-righty:b12,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,platform:Linux,\n03000000242e0000ff0b000011010000,Hyperkin N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,platform:Linux,\n03000000242e00006a38000010010000,Hyperkin Trooper 2,a:b0,b:b1,back:b4,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b3,start:b5,platform:Linux,\n03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000f00300008d03000011010000,HyperX Clutch,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000830500006020000010010000,iBuffalo Super Famicom Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,\n030000008f0e00001330000001010000,iCode Retro Adapter,b:b3,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b9,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b1,start:b7,x:b2,y:b0,platform:Linux,\n050000006964726f69643a636f6e0000,idroidcon Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000b50700001503000010010000,Impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,\n03000000d80400008200000003000000,IMS PCU0,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b5,x:b3,y:b2,platform:Linux,\n03000000120c00000500000010010000,InterAct AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Linux,\n03000000ef0500000300000000010000,InterAct AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Linux,\n03000000fd0500000030000000010000,InterAct GoPad,a:b3,b:b4,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,x:b0,y:b1,platform:Linux,\n03000000fd0500002a26000000010000,InterAct HammerHead FX,a:b3,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b2,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b5,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux,\n0500000049190000020400001b010000,Ipega PG 9069,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b161,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000632500007505000011010000,Ipega PG 9099,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n0500000049190000030400001b010000,Ipega PG9099,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000491900000204000000000000,Ipega PG9118,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000300f00001101000010010000,Jess Tech Colour Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,\n03000000300f00001001000010010000,Jess Tech Dual Analog Rumble,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,\n03000000300f00000b01000010010000,Jess Tech GGE909 PC Recoil,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,\n03000000ba2200002010000001010000,Jess Technology Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,\n030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux,\n050000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux,\n030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux,\n050000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux,\n03000000bd12000003c0000010010000,Joypad Alpha Shock,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000242f00002d00000011010000,JYS Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000242f00008a00000011010000,JYS Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux,\n030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006d040000d1ca000000000000,Logitech Chillstream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d040000d1ca000011010000,Logitech Chillstream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d0400001dc2000014400000,Logitech F310,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006d0400001ec2000019200000,Logitech F510,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006d0400001ec2000020200000,Logitech F510,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006d04000019c2000011010000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d0400001fc2000005030000,Logitech F710,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux,\n030000006d0400000ac2000010010000,Logitech WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Linux,\n05000000380700006652000025010000,Mad Catz CTRLR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700008532000010010000,Mad Catz Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700005032000011010000,Mad Catz Fightpad Pro PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700005082000011010000,Mad Catz Fightpad Pro PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux,\n03000000380700008031000011010000,Mad Catz FightStick Alpha PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700008081000011010000,Mad Catz FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700008034000011010000,Mad Catz Fightstick PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700008084000011010000,Mad Catz Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n03000000380700008433000011010000,Mad Catz Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700008483000011010000,Mad Catz Fightstick TE S PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n03000000380700001888000010010000,Mad Catz Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700003888000010010000,Mad Catz Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700001647000010040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000380700003847000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000120c00000500000000010000,Manta DualShock 2,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,\n030000008f0e00001330000010010000,Mayflash Controller Adapter,a:b1,b:b2,back:b8,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3~,righty:a2,start:b9,x:b0,y:b3,platform:Linux,\n03000000790000004318000010010000,Mayflash GameCube Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Linux,\n03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Linux,\n03000000242f00007300000011010000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux,\n0300000079000000d218000011010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000d620000010a7000011010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000242f0000f700000001010000,Mayflash Magic S Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000008f0e00001030000010010000,Mayflash Saturn Adapter,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:b7,rightshoulder:b6,righttrigger:b2,start:b9,x:b3,y:b4,platform:Linux,\n0300000025090000e803000001010000,Mayflash Wii Classic Adapter,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:a5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,\n03000000790000000318000011010000,Mayflash Wii DolphinBar,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Linux,\n03000000790000000018000011010000,Mayflash Wii U Pro Adapter,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000b50700001203000010010000,Mega World Logic 3 Controller,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,\n03000000b50700004f00000000010000,Mega World Logic 3 Controller,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Linux,\n03000000780000000600000010010000,Microntek Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,\n030000005e0400002800000000010000,Microsoft Dual Strike,a:b3,b:b2,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,rightx:a0,righty:a1~,start:b5,x:b1,y:b0,platform:Linux,\n030000005e0400000300000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Linux,\n030000005e0400000700000000010000,Microsoft SideWinder,a:b0,b:b1,back:b8,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Linux,\n030000005e0400000e00000000010000,Microsoft SideWinder Freestyle Pro,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux,\n030000005e0400002700000000010000,Microsoft SideWinder Plug and Play,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b4,righttrigger:b5,x:b2,y:b3,platform:Linux,\n030000005e0400008502000000010000,Microsoft Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,\n030000005e0400008902000021010000,Microsoft Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,\n030000005e0400008e02000001000000,Microsoft Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.1,dpleft:h0.2,dpright:h0.8,dpup:h0.4,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400008e02000004010000,Microsoft Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400008e02000056210000,Microsoft Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400008e02000062230000,Microsoft Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000d102000001010000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000d102000003020000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000dd02000003020000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000ea02000008040000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000ea0200000f050000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n060000005e040000120b000009050000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000e302000003020000,Microsoft Xbox One Elite,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000000b000007040000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b12,paddle2:b14,paddle3:b13,paddle4:b15,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000000b000008040000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b12,paddle2:b14,paddle3:b13,paddle4:b15,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n050000005e040000050b000003090000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e0400008e02000030110000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b00000b050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b000016050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b000017050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n060000005e040000120b000001050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000030000000300000002000000,Miroof,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,\n03000000790000001c18000010010000,Mobapad Chitu HD,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000004d4f435554452d3035335800,Mocute 053X,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n05000000e80400006e0400001b010000,Mocute 053X M59,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000004d4f435554452d3035305800,Mocute 054X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000d6200000e589000001000000,Moga 2,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,\n05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,\n05000000d62000007162000001000000,Moga Pro 2,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,\n03000000c62400002b89000011010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000c62400002a89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000c62400001a89000000010000,MOGA XP5X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000250900006688000000010000,MP8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,\n030000005e0400008e02000010020000,MSI GC20 V2,a:b0,b:b1,back:b6,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000f70600000100000000010000,N64 Adaptoid,+rightx:b2,+righty:b1,-rightx:b4,-righty:b5,a:b0,b:b3,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,platform:Linux,\n030000006b1400000906000014010000,Nacon Asymmetric Wireless PS4 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006b140000010c000010010000,Nacon GC 400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n03000000853200000706000012010000,Nacon GC-100,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n05000000853200000503000000010000,Nacon MG-X Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n0300000085320000170d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n0300000085320000190d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000004f1f00000800000011010000,NeoGeo PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n0300000092120000474e000000010000,NeoGeo X Arcade Stick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b3,y:b2,platform:Linux,\n03000000790000004518000010010000,Nexilux GameCube Controller Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Linux,\n030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Linux,\n060000007e0500003713000000000000,Nintendo 3DS,a:b0,b:b1,back:b8,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,\n030000007e0500003703000000016800,Nintendo GameCube Controller,a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,platform:Linux,\n03000000790000004618000010010000,Nintendo GameCube Controller Adapter,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a5~,righty:a2~,start:b9,x:b2,y:b3,platform:Linux,\n060000004e696e74656e646f20537700,Nintendo Switch Combined Joy-Cons,a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,\n060000007e0500000620000000000000,Nintendo Switch Combined Joy-Cons,a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,\n060000007e0500000820000000000000,Nintendo Switch Combined Joy-Cons,a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,\n050000004c69632050726f20436f6e00,Nintendo Switch Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n050000007e0500000620000001800000,Nintendo Switch Left Joy-Con,a:b16,b:b15,back:b4,leftshoulder:b6,leftstick:b12,leftx:a1,lefty:a0~,rightshoulder:b8,start:b9,x:b14,y:b17,platform:Linux,\n030000007e0500000920000000026803,Nintendo Switch Pro Controller,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Linux,\n030000007e0500000920000011810000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,\n050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n050000007e0500000920000001800000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,\n050000007e0500000720000001800000,Nintendo Switch Right Joy-Con,a:b1,b:b2,back:b9,leftshoulder:b4,leftstick:b10,leftx:a1~,lefty:a0,rightshoulder:b6,start:b8,x:b0,y:b3,platform:Linux,\n05000000010000000100000003000000,Nintendo Wii Remote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n050000007e0500003003000001000000,Nintendo Wii U Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,\n050000005a1d00000218000003000000,Nokia GC 5000,a:b9,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000000d0500000308000010010000,Nostromo n45 Dual Analog,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,\n030000007e0500001920000011810000,NSO N64 Controller,+rightx:b2,+righty:b3,-rightx:b4,-righty:b10,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b9,start:b11,platform:Linux,\n050000007e0500001920000001000000,NSO N64 Controller,+rightx:b8,+righty:b7,-rightx:b3,-righty:b2,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Linux,\n050000007e0500001920000001800000,NSO N64 Controller,+rightx:b2,+righty:b3,-rightx:b4,-righty:b10,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b9,start:b11,platform:Linux,\n030000007e0500001e20000011810000,NSO Sega Genesis Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,misc1:b3,rightshoulder:b2,righttrigger:b4,start:b5,platform:Linux,\n030000007e0500001720000011810000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux,\n050000007e0500001720000001000000,NSO SNES Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:b7,rightshoulder:b6,righttrigger:b8,start:b10,x:b3,y:b2,platform:Linux,\n050000007e0500001720000001800000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux,\n03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,\n03000000550900001472000011010000,NVIDIA Controller,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b8,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux,\n05000000550900001472000001000000,NVIDIA Controller,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b8,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux,\n030000004b120000014d000000010000,NYKO Airflo EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,\n03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n19000000010000000100000001010000,ODROID Go 2,a:b1,b:b0,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,guide:b10,leftshoulder:b4,leftstick:b12,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b13,righttrigger:b14,start:b15,x:b2,y:b3,platform:Linux,\n19000000010000000200000011000000,ODROID Go 2,a:b1,b:b0,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b12,leftshoulder:b4,leftstick:b14,lefttrigger:b13,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:b16,start:b17,x:b2,y:b3,platform:Linux,\n05000000362800000100000002010000,OUYA Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,\n05000000362800000100000003010000,OUYA Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,\n05000000362800000100000004010000,OUYA Controller,a:b0,b:b3,back:b14,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,leftshoulder:b4,leftstick:b6,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,start:b16,x:b1,y:b2,platform:Linux,\n03000000830500005020000010010000,Padix Rockfire PlayStation Bridge,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b2,y:b3,platform:Linux,\n03000000ff1100003133000010010000,PC Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n030000006f0e0000b802000001010000,PDP Afterglow Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e0000b802000013020000,PDP Afterglow Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e0000d702000006640000,PDP Black Camo Wired Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:b13,dpleft:b14,dpright:b13,dpup:b14,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00003101000000010000,PDP EA Sports Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00008501000011010000,PDP Fightpad Pro Gamecube Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n030000006f0e0000c802000012010000,PDP Kingdom Hearts Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00002801000011010000,PDP PS3 Rock Candy Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00000901000011010000,PDP PS3 Versus Fighting,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00002f01000011010000,PDP Wired PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000ad1b000004f9000000010000,PDP Xbox 360 Versus Fighting,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e0000f102000000000000,PDP Xbox Atomic,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e0000a802000023020000,PDP Xbox One Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n030000006f0e0000a702000023020000,PDP Xbox One Raven Black,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e0000d802000006640000,PDP Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e0000ef02000007640000,PDP Xbox Series Kinetic Wired Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c62400003a54000001010000,PowerA 1428124-01,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000d62000000540000001010000,PowerA Advantage Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000d620000011a7000011010000,PowerA Core Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000dd62000015a7000011010000,PowerA Fusion Nintendo Switch Arcade Stick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000d620000012a7000011010000,PowerA Fusion Nintendo Switch Fight Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000d62000000140000001010000,PowerA Fusion Pro 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000dd62000016a7000000000000,PowerA Fusion Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000c62400001a53000000010000,PowerA Mini Pro Ex,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000d620000013a7000011010000,PowerA Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000d62000006dca000011010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000d620000014a7000011010000,PowerA Spectra Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000c62400001a58000001010000,PowerA Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000d62000000220000001010000,PowerA Xbox One Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux,\n03000000d62000000228000001010000,PowerA Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c62400001a54000001010000,PowerA Xbox One Mini Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000d62000000240000001010000,PowerA Xbox One Spectra Infinity,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000d62000000520000050010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000d62000000b20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000d62000000f20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000250900000017000010010000,PS/SS/N64 Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b5,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2~,righty:a3,start:b8,platform:Linux,\n03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,\n03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,\n030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,\n030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n030000005f1400003102000010010000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n030000006f0e00001402000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000008f0e00000300000010010000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n050000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,\n050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,\n050000004c0500006802000000800000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n05000000504c415953544154494f4e00,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,\n060000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,\n030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n030000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n03000000c01100000140000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n050000004c050000c405000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n050000004c050000cc09000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n0300004b4c0500005f0e000011010000,PS5 Access Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000004c050000e60c000011010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000004c050000e60c000011810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n030000004c050000f20d000011010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b14,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000004c050000f20d000011810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n050000004c050000e60c000000810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n050000004c050000f20d000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n050000004c050000f20d000000810000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n03000000300f00001211000011010000,Qanba Arcade Joystick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,platform:Linux,\n03000000222c00000225000011010000,Qanba Dragon Arcade Joystick PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000222c00000025000011010000,Qanba Dragon Arcade Joystick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n03000000222c00001220000011010000,Qanba Drone 2 Arcade Joystick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000222c00001020000011010000,Qanba Drone 2 Arcade Joystick PS5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000222c00000020000011010000,Qanba Drone Arcade PS4 Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n03000000300f00001210000010010000,Qanba Joystick Plus,a:b0,b:b1,back:b8,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,start:b9,x:b2,y:b3,platform:Linux,\n03000000222c00000223000011010000,Qanba Obsidian Arcade Joystick PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000222c00000023000011010000,Qanba Obsidian Arcade Joystick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000009b2800000300000001010000,Raphnet 4nes4snes,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux,\n030000009b2800004200000001010000,Raphnet Dual NES Adapter,a:b0,b:b1,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Linux,\n0300132d9b2800006500000000000000,Raphnet GameCube Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,\n0300132d9b2800006500000001010000,Raphnet GameCube Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,\n030000009b2800003200000001010000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,\n030000009b2800006000000001010000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,\n030000009b2800006100000001010000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Linux,\n030000009b2800008000000020020000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Linux,\n030000009b2800008000000001010000,Raphnet Wii Classic Adapter V3,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Linux,\n03000000f8270000bf0b000011010000,Razer Kishi,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000321500000204000011010000,Razer Panthera PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000321500000104000011010000,Razer Panthera PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n03000000321500000810000011010000,Razer Panthera PS4 Evo Arcade Stick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n03000000321500000010000011010000,Razer Raiju,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000321500000507000000010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000321500000a10000001000000,Razer Raiju Tournament Edition,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000321500000011000011010000,Razer Raion PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,\n050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,\n0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000321500000b10000011010000,Razer Wolverine PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n0300000032150000140a000001010000,Razer Wolverine Ultimate Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000000d0f0000c100000010010000,Retro Bit Legacy16,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b12,leftshoulder:b4,lefttrigger:b6,misc1:b13,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f0000c100000072056800,Retro Bit Legacy16,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b5,leftshoulder:b9,lefttrigger:+a4,misc1:b11,rightshoulder:b10,righttrigger:+a5,start:b6,x:b3,y:b2,platform:Linux,\n03000000790000001100000010010000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Linux,\n0300000003040000c197000011010000,Retrode Adapter,a:b0,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux,\n190000004b4800000111000000010000,RetroGame Joypad,a:b1,b:b0,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n0300000081170000990a000001010000,Retronic Adapter,a:b0,leftx:a0,lefty:a1,platform:Linux,\n0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,\n00000000526574726f53746f6e653200,RetroStone 2 Controller,a:b1,b:b0,back:b10,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,platform:Linux,\n03000000341200000400000000010000,RetroUSB N64 RetroPort,+rightx:b8,+righty:b10,-rightx:b9,-righty:b11,a:b7,b:b6,dpdown:b2,dpleft:b1,dpright:b0,dpup:b3,leftshoulder:b13,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b12,start:b4,platform:Linux,\n030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000006b140000130d000011010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00008701000011010000,Rock Candy Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00001e01000011010000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000c6240000fefa000000010000,Rock Candy Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00001311000011010000,Saffun Controller,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b0,platform:Linux,\n03000000a306000023f6000011010000,Saitek Cyborg PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,\n03000000a30600001005000000010000,Saitek P150,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b2,righttrigger:b5,x:b3,y:b4,platform:Linux,\n03000000a30600000701000000010000,Saitek P220,a:b2,b:b3,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,x:b0,y:b1,platform:Linux,\n03000000a30600000cff000010010000,Saitek P2500 Force Rumble,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b0,y:b1,platform:Linux,\n03000000a30600000d5f000010010000,Saitek P2600,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux,\n03000000a30600000c04000011010000,Saitek P2900,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,platform:Linux,\n03000000a306000018f5000010010000,Saitek P3200 Rumble,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux,\n03000000300f00001201000010010000,Saitek P380,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,\n03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,\n03000000a30600000b04000000010000,Saitek P990 Dual Analog,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux,\n03000000a306000020f6000011010000,Saitek PS2700 Rumble,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,\n05000000e804000000a000001b010000,Samsung EIGP20,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000d81d00000e00000010010000,Savior,a:b0,b:b1,back:b8,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b11,righttrigger:b3,start:b9,x:b4,y:b5,platform:Linux,\n03000000952e00004b43000011010000,Scuf Envision,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Linux,\n03000000952e00004d43000011010000,Scuf Envision,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Linux,\n03000000952e00004e43000011010000,Scuf Envision,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Linux,\n03000000a30c00002500000011010000,Sega Genesis Mini 3B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,righttrigger:b5,start:b9,platform:Linux,\n03000000790000001100000011010000,Sega Saturn,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b4,start:b9,x:b0,y:b3,platform:Linux,\n03000000790000002201000011010000,Sega Saturn,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,\n03000000b40400000a01000000010000,Sega Saturn,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Linux,\n03000000632500002305000010010000,ShanWan Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000632500002605000010010000,Shanwan Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000632500007505000010010000,Shanwan Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000bc2000000055000010010000,Shanwan Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000f025000021c1000010010000,Shanwan Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000341a00000908000010010000,SL6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n030000004b2900000430000011000000,Snakebyte Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n050000004c050000cc09000001000000,Sony DualShock 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n03000000666600006706000000010000,Sony PlayStation Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Linux,\n030000004c050000da0c000011010000,Sony PlayStation Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,\n03000000d9040000160f000000010000,Sony PlayStation Controller Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,\n03000000ff000000cb01000010010000,Sony PlayStation Portable,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Linux,\n030000004c0500003713000011010000,Sony PlayStation Vita,a:b1,b:b2,back:b8,dpdown:b13,dpleft:b15,dpright:b14,dpup:b12,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux,\n03000000250900000500000000010000,Sony PS2 pad with SmartJoy Adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,\n030000005e0400008e02000073050000,Speedlink Torid,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400008e02000020200000,SpeedLink Xeox Pro Analog,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n03000000de2800000112000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux,\n03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n03000000de2800000211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b16,paddle2:b15,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux,\n03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n03000000de2800004211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b16,paddle2:b15,rightshoulder:b7,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux,\n03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n03000000de2800000512000010010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,platform:Linux,\n03000000de2800000512000011010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,misc1:b2,paddle1:b21,paddle2:b20,paddle3:b23,paddle4:b22,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,platform:Linux,\n03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n050000004e696d6275732b0000000000,SteelSeries Nimbus Plus,a:b0,b:b1,back:b10,guide:b11,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,platform:Linux,\n03000000381000003014000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000381000003114000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000110100001914000009010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000ad1b000038f0000090040000,Street Fighter IV Fightstick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000003b07000004a1000000010000,Suncom SFX Plus,a:b0,b:b2,back:b7,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b9,righttrigger:b5,start:b8,x:b1,y:b3,platform:Linux,\n030000001f08000001e4000010010000,Super Famicom Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,\n03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,\n0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,\n030000008f0e00000d31000010010000,SZMY Power 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000457500000401000011010000,SZMY Power DS4 Wired Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000457500002211000010010000,SZMY Power Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n030000008f0e00001431000010010000,SZMY Power PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000e40a00000307000011010000,Taito Egret II Mini Control Panel,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Linux,\n03000000e40a00000207000011010000,Taito Egret II Mini Controller,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Linux,\n03000000ba2200000701000001010000,Technology Innovation PS2 Adapter,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b3,y:b2,platform:Linux,\n03000000790000001c18000011010000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000591c00002400000010010000,THEC64 Joystick,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Linux,\n03000000591c00002600000010010000,THEGamepad,a:b2,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Linux,\n030000004f04000015b3000001010000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,\n030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,\n030000004f04000020b3000010010000,Thrustmaster Dual Trigger,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,\n030000004f04000023b3000000010000,Thrustmaster Dual Trigger PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000004f0400000ed0000011010000,Thrustmaster eSwap Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000b50700000399000000010000,Thrustmaster Firestorm Digital 2,a:b2,b:b4,back:b11,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b0,righttrigger:b9,start:b1,x:b3,y:b5,platform:Linux,\n030000004f04000003b3000010010000,Thrustmaster Firestorm Dual Analog 2,a:b0,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b9,rightx:a2,righty:a3,x:b1,y:b3,platform:Linux,\n030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Linux,\n030000004f04000004b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,\n030000004f04000026b3000002040000,Thrustmaster GP XID,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c6240000025b000002020000,Thrustmaster GPX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000004f04000008d0000000010000,Thrustmaster Run N Drive PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000004f04000009d0000000010000,Thrustmaster Run N Drive PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000004f04000007d0000000010000,Thrustmaster T Mini,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000004f04000012b3000010010000,Thrustmaster Vibrating Gamepad,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,\n03000000571d00002000000010010000,Tomee SNES Adapter,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Linux,\n03000000bd12000015d0000010010000,Tomee SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,\n03000000d814000007cd000011010000,Toodles 2008 Chimp PC PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux,\n030000005e0400008e02000070050000,Torid,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c01100000591000011010000,Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000680a00000300000003000000,TRBot Virtual Joypad,a:b11,b:b12,back:b15,dpdown:b6,dpleft:b3,dpright:b4,dpup:b5,leftshoulder:b17,leftstick:b21,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b22,righttrigger:a2,rightx:a3,righty:a4,start:b16,x:b13,y:b14,platform:Linux,\n03000000780300000300000003000000,TRBot Virtual Joypad,a:b11,b:b12,back:b15,dpdown:b6,dpleft:b3,dpright:b4,dpup:b5,leftshoulder:b17,leftstick:b21,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b22,righttrigger:a2,rightx:a3,righty:a4,start:b16,x:b13,y:b14,platform:Linux,\n03000000e00d00000300000003000000,TRBot Virtual Joypad,a:b11,b:b12,back:b15,dpdown:b6,dpleft:b3,dpright:b4,dpup:b5,leftshoulder:b17,leftstick:b21,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b22,righttrigger:a2,rightx:a3,righty:a4,start:b16,x:b13,y:b14,platform:Linux,\n03000000f00600000300000003000000,TRBot Virtual Joypad,a:b11,b:b12,back:b15,dpdown:b6,dpleft:b3,dpright:b4,dpup:b5,leftshoulder:b17,leftstick:b21,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b22,righttrigger:a2,rightx:a3,righty:a4,start:b16,x:b13,y:b14,platform:Linux,\n030000005f140000c501000010010000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n06000000f51000000870000003010000,Turtle Beach Recon,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000100800000100000010010000,Twin PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,\n03000000151900005678000010010000,Uniplay U6,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,\n03000000790000000600000007010000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,\n03000000790000001100000000010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux,\n03000000790000001a18000011010000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000790000001b18000011010000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00000302000011010000,Victrix Pro Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n030000006f0e00000702000011010000,Victrix Pro Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,\n05000000ac0500003232000001000000,VR Box Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,\n05000000434f4d4d414e440000000000,VX Gaming Command Series,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n0000000058626f782033363020576900,Xbox 360 Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux,\n030000005e0400001907000000010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400008e02000010010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400008e02000014010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400009102000007010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000a102000000010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000a102000007010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000a102000030060000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00001503000000020000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400008e02000000010000,Xbox 360 EasySMX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000a102000014010000,Xbox 360 Receiver,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n0000000058626f782047616d65706100,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400000202000000010000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,\n030000005e0400008e02000072050000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00001304000000010000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000ffff0000ffff000000010000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,\n030000005e0400000a0b000005040000,Xbox One Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,\n030000005e040000d102000002010000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000ea02000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000ea02000001030000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n050000005e040000e002000003090000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n050000005e040000fd02000003090000,Xbox One Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000fd02000030110000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n060000005e040000dd02000003020000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n050000005e040000e302000002090000,Xbox One Elite,a:b0,b:b1,back:b136,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000220b000013050000,Xbox One Elite 2 Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000050b000002090000,Xbox One Elite Series 2,a:b0,b:b1,back:b136,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000005e040000ea02000011050000,Xbox One S Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000ea02000015050000,Xbox One S Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n060000005e040000ea0200000b050000,Xbox One S Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n060000005e040000ea0200000d050000,Xbox One S Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n060000005e040000ea02000016050000,Xbox One S Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b000001050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b000005050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b000007050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b000009050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b00000d050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b000011050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b000014050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b000015050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000130b000001050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000130b000007050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000130b000009050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000130b000011050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000130b000013050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000130b000015050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000130b000017050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n060000005e040000120b000007050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n060000005e040000120b00000b050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n060000005e040000120b00000d050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n060000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n050000005e040000130b000022050000,Xbox Series X Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000200b000013050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000200b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000220b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000450c00002043000010010000,XEOX SL6556 BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n05000000172700004431000029010000,XiaoMi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux,\n03000000c0160000e105000001010000,XinMo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux,\n030000005e0400008e02000020010000,XInput Adapter,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\nxinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000120c0000100e000011010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000120c0000101e000011010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n\n# Android\n38653964633230666463343334313533,8BitDo Adapter,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n36666264316630653965636634386234,8BitDo Adapter 2,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b19,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n38426974446f20417263616465205374,8BitDo Arcade Stick,a:b0,b:b1,back:b15,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b5,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n61393962646434393836356631636132,8BitDo Arcade Stick,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b19,y:b2,platform:Android,\n64323139346131306233636562663738,8BitDo Arcade Stick,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android,\n64643565386136613265663236636564,8BitDo Arcade Stick,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b19,y:b2,platform:Android,\n33313433353539306634656436353432,8BitDo Dogbone,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n38426974446f20446f67626f6e65204d,8BitDo Dogbone,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b6,platform:Android,\n34343439373236623466343934376233,8BitDo FC30 Pro,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b28,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b29,righttrigger:b7,start:b5,x:b30,y:b2,platform:Android,\n38426974446f204e4743204d6f646b69,8BitDo GameCube,a:b0,b:b2,back:b4,dpdown:b12,dpleft:b13,dpright:b14,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,paddle1:b18,paddle2:b17,rightshoulder:b15,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b1,y:b3,platform:Android,\n38426974446f2038426974446f204c69,8BitDo Lite,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n30643332373663313263316637356631,8BitDo Lite 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38426974446f204c6974652032000000,8BitDo Lite 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n62656331626461363634633735353032,8BitDo Lite 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38393936616436383062666232653338,8BitDo Lite SE,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38426974446f204c6974652053450000,8BitDo Lite SE,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n39356430616562366466646636643435,8BitDo Lite SE,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n05000000c82d000006500000ffff3f00,8BitDo M30,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b17,leftshoulder:b9,lefttrigger:a5,rightshoulder:b10,righttrigger:a4,start:b6,x:b3,y:b2,platform:Android,\n05000000c82d000051060000ffff3f00,8BitDo M30,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b17,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b3,y:b2,platform:Android,\n32323161363037623637326438643634,8BitDo M30,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n33656266353630643966653238646264,8BitDo M30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:a5,start:b10,x:b19,y:b2,platform:Android,\n38426974446f204d3330204d6f646b69,8BitDo M30,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n39366630663062373237616566353437,8BitDo M30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,start:b6,x:b2,y:b3,platform:Android,\n64653533313537373934323436343563,8BitDo M30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:a4,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b10,start:b6,x:b2,y:b3,platform:Android,\n66356438346136366337386437653934,8BitDo M30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:b10,start:b18,x:b19,y:b2,platform:Android,\n66393064393162303732356665666366,8BitDo M30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,platform:Android,\n38426974446f204d6963726f2067616d,8BitDo Micro,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:a4,leftx:b0,lefty:b1,rightshoulder:b10,righttrigger:a5,rightx:b2,righty:b3,start:b6,x:b3,y:b2,platform:Android,\n61653365323561356263373333643266,8BitDo Micro,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:a4,leftx:b0,lefty:b1,rightshoulder:b10,righttrigger:a5,rightx:b2,righty:b3,start:b6,x:b3,y:b2,platform:Android,\n62613137616239666338343866326336,8BitDo Micro,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:a4,leftx:b0,lefty:b1,rightshoulder:b10,righttrigger:a5,rightx:b2,righty:b3,start:b6,x:b3,y:b2,platform:Android,\n33663431326134333366393233616633,8BitDo N30,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b6,platform:Android,\n38426974446f204e3330204d6f646b69,8BitDo N30,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b6,platform:Android,\n05000000c82d000015900000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n05000000c82d000065280000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38323035343766666239373834336637,8BitDo N64,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,rightx:a2,righty:a3,start:b6,platform:Android,\n38426974446f204e3634204d6f646b69,8BitDo N64,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,rightx:a2,righty:a3,start:b6,platform:Android,\n32363135613966656338666638666237,8BitDo NEOGEO,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n35363534633333373639386466346631,8BitDo NEOGEO,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n38426974446f204e454f47454f204750,8BitDo NEOGEO,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n39383963623932353561633733306334,8BitDo NEOGEO,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000000220000000900000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n050000002038000009000000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38313433643131656262306631373166,8BitDo P30,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n38326536643339353865323063616339,8BitDo P30,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n38426974446f2050333020636c617373,8BitDo P30,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n35376664343164386333616535333434,8BitDo Pro 2,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b17,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b18,righttrigger:a5,rightx:a3,start:b10,x:b19,y:b2,platform:Android,\n38426974446f2038426974446f205072,8BitDo Pro 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38426974446f2050726f203200000000,8BitDo Pro 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n61333362366131643730353063616330,8BitDo Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n62373739366537363166326238653463,8BitDo Pro 2,a:b1,b:b0,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b3,y:b2,platform:Android,\n38386464613034326435626130396565,8BitDo Receiver,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b19,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38426974446f2038426974446f205265,8BitDo Receiver,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b19,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n66303230343038613365623964393766,8BitDo Receiver,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b19,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38426974446f20533330204d6f646b69,8BitDo S30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:a4,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n66316462353561376330346462316137,8BitDo S30,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:a4,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b10,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n05000000c82d000000600000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n05000000c82d000000610000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38426974646f20534633302050726f00,8BitDo SF30 Pro,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b17,platform:Android,\n61623334636338643233383735326439,8BitDo SFC30,a:b0,b:b1,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b3,rightshoulder:b31,start:b5,x:b30,y:b2,platform:Android,\n05000000c82d000012900000ffff3f00,8BitDo SN30,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android,\n05000000c82d000062280000ffff3f00,8BitDo SN30,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android,\n38316230613931613964356666353839,8BitDo SN30,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38426974446f20534e3330204d6f646b,8BitDo SN30,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n65323563303231646531383162646335,8BitDo SN30,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n35383531346263653330306238353131,8BitDo SN30 PP,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n05000000c82d000001600000ffff3f00,8BitDo SN30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n05000000c82d000002600000ffff0f00,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n36653638656632326235346264663661,8BitDo SN30 Pro Plus,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android,\n38303232393133383836366330346462,8BitDo SN30 Pro Plus,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b17,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b18,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b19,y:b2,platform:Android,\n38346630346135363335366265656666,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38426974446f20534e33302050726f2b,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b19,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n536f6e7920436f6d707574657220456e,8BitDo SN30 Pro Plus,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n66306331643531333230306437353936,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n050000002028000009000000ffff3f00,8BitDo SNES30,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n050000003512000020ab000000780f00,8BitDo SNES30,a:b21,b:b20,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b24,y:b23,platform:Android,\n33666663316164653937326237613331,8BitDo Zero,a:b0,b:b1,back:b15,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b2,y:b3,platform:Android,\n38426974646f205a65726f2047616d65,8BitDo Zero,a:b0,b:b1,back:b15,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b2,y:b3,platform:Android,\n05000000c82d000018900000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android,\n05000000c82d000030320000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android,\n33663434393362303033616630346337,8BitDo Zero 2,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftx:a0,lefty:a1,rightshoulder:b20,start:b18,x:b19,y:b2,platform:Android,\n34656330626361666438323266633963,8BitDo Zero 2,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b20,start:b10,x:b19,y:b2,platform:Android,\n63396666386564393334393236386630,8BitDo Zero 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android,\n63633435623263373466343461646430,8BitDo Zero 2,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,platform:Android,\n32333634613735616163326165323731,Amazon Luna Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android,\n4c696e757820342e31392e3137322077,Anbernic Handheld,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a4,start:b6,x:b2,y:b3,platform:Android,\n417374726f2063697479206d696e6920,Astro City Mini,a:b23,b:b22,back:b29,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b25,righttrigger:b26,start:b30,x:b24,y:b21,platform:Android,\n35643263313264386134376362363435,Atari VCS Classic Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,start:b6,platform:Android,\n32353831643566306563643065356239,Atari VCS Modern Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n4f64696e20436f6e74726f6c6c657200,AYN Odin,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b14,dpright:b13,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:+a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n32303165626138343962363666346165,Brook Mars PS4 Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,\n38383337343564366131323064613561,Brook Mars PS4 Controller,a:b1,b:b19,back:b17,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,\n34313430343161653665353737323365,Elecom JC-W01U,a:b23,b:b24,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,rightx:a2,righty:a3,start:b30,x:b21,y:b22,platform:Android,\n4875694a6961204a432d573031550000,Elecom JC-W01U,a:b23,b:b24,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,rightx:a2,righty:a3,start:b30,x:b21,y:b22,platform:Android,\n30363230653635633863366338623265,Evo VR,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftx:a0,lefty:a1,x:b2,y:b3,platform:Android,\n05000000b404000011240000dfff3f00,Flydigi Vader 2,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,paddle1:b17,paddle2:b18,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n05000000bc20000000550000ffff3f00,GameSir G3w,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n34323662653333636330306631326233,Google Nexus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n35383633353935396534393230616564,Google Stadia Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n476f6f676c65204c4c43205374616469,Google Stadia Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n5374616469614e3848532d6532633400,Google Stadia Controller,a:b0,b:b1,back:b15,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n05000000d6020000e5890000dfff3f00,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android,\n05000000d6020000e5890000dfff3f80,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a3,rightx:a4,righty:a5,start:b6,x:b2,y:b3,platform:Android,\n66633030656131663837396562323935,Hori Battle,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android,\n35623466343433653739346434636330,Hori Fighting Commander 3 Pro,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,\n484f524920434f2e2c4c54442e203130,Hori Fighting Commander 3 Pro,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b20,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b9,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,\n484f524920434f2e2c4c544420205041,Hori Gem Pad 3,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b0,y:b2,platform:Android,\n65656436646661313232656661616130,Hori PC Engine Mini Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,start:b18,platform:Android,\n31303433326562636431653534636633,Hori Real Arcade Pro 3,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,\n32656664353964393561366362333636,Hori Switch Split Pad Pro,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android,\n30306539356238653637313730656134,HORIPAD Switch Pro Controller,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android,\n48797065726b696e2050616400000000,Hyperkin Admiral N64 Controller,+rightx:b6,+righty:b7,-rightx:b17,-righty:b5,a:b1,b:b0,leftshoulder:b3,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b20,start:b18,platform:Android,\n62333331353131353034386136626636,Hyperkin Admiral N64 Controller,+rightx:b6,+righty:b7,-rightx:b17,-righty:b5,a:b1,b:b0,leftshoulder:b3,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b20,start:b18,platform:Android,\n31306635363562663834633739396333,Hyperkin N64 Adapter,a:b1,b:b19,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightx:a2,righty:a3,start:b18,platform:Android,\n5368616e57616e202020202048797065,Hyperkin N64 Adapter,a:b1,b:b19,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightx:a2,righty:a3,start:b18,platform:Android,\n0500000083050000602000000ffe0000,iBuffalo SNES Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b15,rightshoulder:b16,start:b10,x:b2,y:b3,platform:Android,\n5553422c322d6178697320382d627574,iBuffalo Super Famicom Controller,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b17,rightshoulder:b18,start:b10,x:b3,y:b2,platform:Android,\n64306137363261396266353433303531,InterAct GoPad,a:b24,b:b25,leftshoulder:b23,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,x:b21,y:b22,platform:Android,\n532e542e442e20496e74657261637420,InterAct HammerHead FX,a:b23,b:b24,back:b30,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b26,leftstick:b22,lefttrigger:b28,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b25,righttrigger:b29,rightx:a2,righty:a3,start:b31,x:b20,y:b21,platform:Android,\n65346535636333663931613264643164,Joy-Con,a:b21,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,rightx:a2,righty:a3,start:b30,x:b23,y:b24,platform:Android,\n33346566643039343630376565326335,Joy-Con (L),a:b0,b:b1,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,rightshoulder:b20,start:b17,x:b19,y:b2,platform:Android,\n35313531613435623366313835326238,Joy-Con (L),a:b0,b:b1,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,rightshoulder:b20,start:b17,x:b19,y:b2,platform:Android,\n4a6f792d436f6e20284c290000000000,Joy-Con (L),a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,rightshoulder:b20,start:b17,x:b19,y:b2,platform:Android,\n38383665633039363066383334653465,Joy-Con (R),a:b0,b:b1,back:b5,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,rightshoulder:b20,start:b18,x:b19,y:b2,platform:Android,\n39363561613936303237333537383931,Joy-Con (R),a:b0,b:b1,back:b5,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,rightshoulder:b20,start:b18,x:b19,y:b2,platform:Android,\n39373064396565646338333134303131,Joy-Con (R),a:b1,b:b2,back:b5,leftstick:b8,leftx:a1~,lefty:a0,start:b6,x:b0,y:b3,platform:Android,\n4a6f792d436f6e202852290000000000,Joy-Con (R),a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,rightshoulder:b20,start:b18,x:b19,y:b2,platform:Android,\n39656136363638323036303865326464,JYS Aapter,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,\n63316564383539663166353034616434,JYS Adapter,a:b1,b:b3,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b0,y:b2,platform:Android,\n64623163333561643339623235373232,Logitech F310,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n35623364393661626231343866613337,Logitech F710,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n4c6f6769746563682047616d65706164,Logitech F710,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n64396331333230326333313330336533,Logitech F710,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n39653365373864633935383236363438,Logitech G Cloud,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n416d617a6f6e2047616d6520436f6e74,Luna Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android,\n4c756e612047616d6570616400000000,Luna Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n30363066623539323534363639323363,Magic NS,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,\n31353762393935386662336365626334,Magic NS,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,\n39623565346366623931666633323530,Magic NS,a:b1,b:b3,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b0,y:b2,platform:Android,\n6d6179666c617368206c696d69746564,Mayflash GameCube Adapter,a:b22,b:b21,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a5,righty:a2,start:b30,x:b23,y:b24,platform:Android,\n436f6e74726f6c6c6572000000000000,Mayflash N64 Adapter,a:b1,b:b19,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightx:a2,righty:a3,start:b18,platform:Android,\n65666330633838383061313633326461,Mayflash N64 Adapter,a:b1,b:b19,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightx:a2,righty:a3,start:b18,platform:Android,\n37316565396364386635383230353365,Mayflash Saturn Adapter,a:b21,b:b22,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b26,lefttrigger:b28,rightshoulder:b27,righttrigger:b23,start:b30,x:b24,y:b25,platform:Android,\n4875694a696120205553422047616d65,Mayflash Saturn Adapter,a:b21,b:b22,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b26,lefttrigger:b28,rightshoulder:b27,righttrigger:b23,start:b30,x:b24,y:b25,platform:Android,\n535a4d792d706f776572204c54442043,Mayflash Wii Classic Adapter,a:b23,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b31,leftshoulder:b27,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a2,righty:a3,start:b30,x:b24,y:b21,platform:Android,\n30653962643666303631376438373532,Mayflash Wii DolphinBar,a:b23,b:b24,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b0,leftshoulder:b25,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,rightx:a2,righty:a3,start:b30,x:b21,y:b22,platform:Android,\n39346131396233376535393665363161,Mayflash Wii U Pro Adapter,a:b22,b:b23,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,leftstick:b31,lefttrigger:b27,rightshoulder:b26,rightstick:b0,righttrigger:b28,rightx:a0,righty:a1,start:b30,x:b21,y:b24,platform:Android,\n31323564663862633234646330373138,Mega Drive,a:b23,b:b22,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b25,righttrigger:b26,start:b30,x:b24,y:b21,platform:Android,\n37333564393261653735306132613061,Mega Drive,a:b21,b:b22,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b26,lefttrigger:b28,rightshoulder:b27,righttrigger:b23,start:b30,x:b24,y:b25,platform:Android,\n64363363336633363736393038313464,Mega Drive,a:b1,b:b0,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,start:b9,x:b2,y:b3,platform:Android,\n33323763323132376537376266393366,Microsoft Dual Strike,a:b24,b:b23,back:b25,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b27,lefttrigger:b29,rightshoulder:b78,rightx:a0,righty:a1~,start:b26,x:b22,y:b21,platform:Android,\n30306461613834333439303734316539,Microsoft SideWinder Pro,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b20,lefttrigger:b9,rightshoulder:b19,righttrigger:b10,start:b17,x:b2,y:b3,platform:Android,\n32386235353630393033393135613831,Microsoft Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n4d4f42415041442050726f2d48440000,Mobapad Chitu HD,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n4d4f435554452d303533582d4d35312d,Mocute 053X,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n33343361376163623438613466616531,Mocute M053,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n39306635663061636563316166303966,Mocute M053,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n7573622067616d657061642020202020,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Android,\n050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b17,y:b2,platform:Android,\n31316661666466633938376335383661,Nintendo Switch Pro Controller,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,start:b6,x:b3,y:b2,platform:Android,\n34323437396534643531326161633738,Nintendo Switch Pro Controller,a:b0,b:b1,back:b15,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,leftstick:b7,lefttrigger:b17,misc1:b5,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n50726f20436f6e74726f6c6c65720000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b2,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b10,rightx:a2,righty:a3,start:b18,y:b3,platform:Android,\n36326533353166323965623661303933,NSO N64 Controller,+rightx:b17,+righty:b10,-rightx:b2,-righty:b19,a:b1,b:b0,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,misc1:b7,rightshoulder:b20,righttrigger:b15,start:b18,platform:Android,\n4e363420436f6e74726f6c6c65720000,NSO N64 Controller,+rightx:b17,+righty:b10,-rightx:b2,-righty:b19,a:b1,b:b0,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,misc1:b7,rightshoulder:b20,righttrigger:b15,start:b18,platform:Android,\n534e455320436f6e74726f6c6c657200,NSO SNES Controller,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,rightshoulder:b20,start:b18,x:b19,y:b2,platform:Android,\n64623863346133633561626136366634,NSO SNES Controller,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,rightshoulder:b20,start:b18,x:b19,y:b2,platform:Android,\n050000005509000003720000cf7f3f00,NVIDIA Controller,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000005509000010720000ffff3f00,NVIDIA Controller,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000005509000014720000df7f3f00,NVIDIA Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android,\n050000005509000014720000df7f3f80,NVIDIA Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a3,rightx:a4,righty:a5,start:b6,x:b2,y:b3,platform:Android,\n37336435666338653565313731303834,NVIDIA Controller,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n61363931656135336130663561616264,NVIDIA Controller,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n39383335313438623439373538343266,OUYA Controller,a:b0,b:b2,dpdown:b18,dpleft:b15,dpright:b16,dpup:b17,leftshoulder:b3,leftstick:b9,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,x:b1,y:b19,platform:Android,\n4f5559412047616d6520436f6e74726f,OUYA Controller,a:b0,b:b2,dpdown:b18,dpleft:b15,dpright:b6,dpup:b17,leftshoulder:b3,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b19,platform:Android,\n506572666f726d616e63652044657369,PDP PS3 Rock Candy Controller,a:b1,b:b17,back:h0.2,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b0,y:b2,platform:Android,\n61653962353232366130326530363061,Pokken,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,rightshoulder:b20,righttrigger:b10,start:b18,x:b0,y:b2,platform:Android,\n32666633663735353234363064386132,PS2,a:b23,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b27,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a3,righty:a2,start:b30,x:b24,y:b21,platform:Android,\n050000004c05000068020000dfff3f00,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n536f6e7920504c415953544154494f4e,PS3 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n61363034663839376638653463633865,PS3 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n66366539656564653432353139356536,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n66383132326164626636313737373037,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000004c050000c405000000783f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000004c050000c4050000fffe3f00,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,\n050000004c050000c4050000fffe3f80,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a3,rightx:a4,righty:a5,start:b16,x:b0,y:b2,platform:Android,\n050000004c050000c4050000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000004c050000cc090000fffe3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000004c050000cc090000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n30303839663330346632363232623138,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,\n31326235383662333266633463653332,PS4 Controller,a:b1,b:b16,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b17,x:b0,y:b2,platform:Android,\n31373231336561636235613666323035,PS4 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n31663838336334393132303338353963,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n34613139376634626133336530386430,PS4 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n35643031303033326130316330353564,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,\n37626233336235343937333961353732,PS4 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n37626464343430636562316661643863,PS4 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n38393161636261653636653532386639,PS4 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n63313733393535663339656564343962,PS4 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n63393662363836383439353064663939,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n65366465656364636137653363376531,PS4 Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,\n66613532303965383534396638613230,PS4 Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android,\n050000004c050000e60c0000fffe3f00,PS5 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,\n050000004c050000e60c0000fffe3f80,PS5 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a3,rightx:a4,righty:a5,start:b16,x:b2,y:b17,platform:Android,\n050000004c050000e60c0000ffff3f00,PS5 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n32346465346533616263386539323932,PS5 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n32633532643734376632656664383733,PS5 Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android,\n37363764353731323963323639666565,PS5 Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android,\n61303162353165316365336436343139,PS5 Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,misc1:b8,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android,\n64336263393933626535303339616332,Qanba 4RAF,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b20,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b9,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android,\n36626666353861663864336130363137,Razer Junglecat,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n05000000f8270000bf0b0000ffff3f00,Razer Kishi,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n62653861643333663663383332396665,Razer Kishi,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000003215000005070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000003215000007070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000003215000000090000bf7f3f00,Razer Serval,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android,\n5a6869587520526574726f2042697420,Retro Bit Saturn Controller,a:b21,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,lefttrigger:b26,rightshoulder:b27,righttrigger:b28,start:b30,x:b23,y:b24,platform:Android,\n32417865732031314b6579732047616d,Retro Bit SNES Controller,a:b0,b:b1,back:b15,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b2,y:b3,platform:Android,\n36313938306539326233393732613361,Retro Bit SNES Controller,a:b0,b:b1,back:b15,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b6,x:b2,y:b3,platform:Android,\n526574726f466c616720576972656420,Retro Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b17,rightshoulder:b18,start:b10,x:b2,y:b3,platform:Android,\n61343739353764363165343237303336,Retro Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b17,lefttrigger:b18,leftx:a0,lefty:a1,start:b10,x:b2,y:b3,platform:Android,\n526574726f696420506f636b65742043,Retroid Pocket,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b17,paddle2:b18,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n582d426f7820436f6e74726f6c6c6572,Retroid Pocket,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b17,paddle2:b18,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n64633735616665613536653363336132,Retroid Pocket,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,paddle1:b19,paddle2:b20,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38653130373365613538333235303036,Retroid Pocket 2,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n64363363336633363736393038313463,Retrolink,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,start:b6,platform:Android,\n37393234373533633333323633646531,RetroUSB N64 RetroPort,+rightx:b17,+righty:b15,-rightx:b18,-righty:b6,a:b10,b:b9,dpdown:b19,dpleft:b1,dpright:b0,dpup:b2,leftshoulder:b7,lefttrigger:b20,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Android,\n5365616c6965436f6d707574696e6720,RetroUSB N64 RetroPort,+rightx:b17,+righty:b15,-rightx:b18,-righty:b6,a:b10,b:b9,dpdown:b19,dpleft:b1,dpright:b0,dpup:b2,leftshoulder:b7,lefttrigger:b20,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Android,\n526574726f5553422e636f6d20534e45,RetroUSB SNES RetroPort,a:b1,b:b20,back:b19,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b2,x:b0,y:b3,platform:Android,\n64643037633038386238303966376137,RetroUSB SNES RetroPort,a:b1,b:b20,back:b19,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b2,x:b0,y:b3,platform:Android,\n37656564346533643138636436356230,Rock Candy Switch Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,misc1:b7,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,\n33373336396634316434323337666361,RumblePad 2,a:b22,b:b23,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,rightx:a2,righty:a3,start:b30,x:b21,y:b24,platform:Android,\n36363537303435333566386638366333,Samsung EIGP20,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n53616d73756e672047616d6520506164,Samsung EIGP20,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n66386565396238363534313863353065,Sanwa PlayOnline Mobile,a:b21,b:b22,back:b23,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b24,platform:Android,\n32383165316333383766336338373261,Saturn,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:a4,righttrigger:a5,x:b2,y:b3,platform:Android,\n38613865396530353338373763623431,Saturn,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,lefttrigger:b10,rightshoulder:b20,righttrigger:b19,start:b17,x:b2,y:b3,platform:Android,\n61316232336262373631343137633631,Saturn,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:a4,righttrigger:a5,x:b2,y:b3,platform:Android,\n30353835333338613130373363646337,SG H510,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b17,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b18,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b19,y:b2,platform:Android,\n66386262366536653765333235343634,SG H510,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android,\n66633132393363353531373465633064,SG H510,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b17,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b18,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b19,y:b2,platform:Android,\n62653761636366393366613135366338,SN30 PP,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,\n38376662666661636265313264613039,SNES,a:b0,b:b1,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b3,rightshoulder:b20,start:b10,x:b19,y:b2,platform:Android,\n5346432f555342205061640000000000,SNES Adapter,a:b0,b:b1,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b3,rightshoulder:b20,start:b10,x:b19,y:b2,platform:Android,\n5553422047616d657061642000000000,SNES Controller,a:b1,b:b0,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android,\n62653335326261303663356263626339,Sony PlayStation Classic Controller,a:b19,b:b1,back:b17,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,lefttrigger:b3,rightshoulder:b10,righttrigger:b20,start:b18,x:b2,y:b0,platform:Android,\n536f6e7920496e746572616374697665,Sony PlayStation Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b8,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n576972656c65737320436f6e74726f6c,Sony PlayStation Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n63303964303462366136616266653561,Sony PSP,a:b21,b:b22,back:b27,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,leftx:a0,lefty:a1,rightshoulder:b26,start:b28,x:b23,y:b24,platform:Android,\n63376637643462343766333462383235,Sony Vita,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftx:a0,lefty:a1,rightshoulder:b20,rightx:a3,righty:a4,start:b18,x:b0,y:b2,platform:Android,\n05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,\n05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,\n0500000011010000201400000f7e0f00,SteelSeries Nimbus,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:b10,rightx:a2,righty:a3,x:b19,y:b2,platform:Android,\n35306436396437373135383665646464,SteelSeries Nimbus Plus,a:b0,b:b1,leftshoulder:b3,leftstick:b17,lefttrigger:b9,leftx:a0,rightshoulder:b20,rightstick:b18,righttrigger:b10,rightx:a2,x:b19,y:b2,platform:Android,\n33313930373536613937326534303931,Taito Egret II Mini Control Panel,a:b25,b:b23,back:b27,guide:b30,leftx:a0,lefty:a1,rightshoulder:b21,righttrigger:b22,start:b28,x:b29,y:b24,platform:Android,\n54475a20436f6e74726f6c6c65720000,TGZ Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n62363434353532386238336663643836,TGZ Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n37323236633763666465316365313236,THEC64 Joystick,a:b21,b:b22,back:b27,leftshoulder:b25,leftx:a0,lefty:a1,rightshoulder:b26,start:b27,x:b23,y:b24,platform:Android,\n38346162326232346533316164363336,THEGamepad,a:b23,b:b22,back:b27,leftshoulder:b25,leftx:a0,lefty:a1,rightshoulder:b26,start:b28,x:b24,y:b21,platform:Android,\n050000004f0400000ed00000fffe3f00,Thrustmaster eSwap Pro Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n5477696e20555342204a6f7973746963,Twin Joystick,a:b22,b:b21,back:b28,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b26,leftstick:b30,lefttrigger:b24,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b31,righttrigger:b25,rightx:a3,righty:a2,start:b29,x:b23,y:b20,platform:Android,\n30623739343039643830333266346439,Valve Steam Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,paddle1:b24,paddle2:b23,rightshoulder:b10,rightstick:b8,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n31643365666432386133346639383937,Valve Steam Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,paddle1:b24,paddle2:b23,rightshoulder:b10,rightstick:b8,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n30386438313564306161393537333663,Wii Classic Adapter,a:b23,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b27,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a2,righty:a3,start:b30,x:b24,y:b21,platform:Android,\n33333034646336346339646538643633,Wii Classic Adapter,a:b23,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b27,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a2,righty:a3,start:b30,x:b24,y:b21,platform:Android,\n050000005e0400008e02000000783f00,Xbox 360 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n30396232393162346330326334636566,Xbox 360 Controller,a:b0,b:b1,back:b4,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n38313038323730383864666463383533,Xbox 360 Controller,a:b0,b:b1,back:b4,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n58626f782033363020576972656c6573,Xbox 360 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n65353331386662343338643939643636,Xbox 360 Controller,a:b0,b:b1,back:b4,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n65613532386633373963616462363038,Xbox 360 Controller,a:b0,b:b1,back:b4,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n47656e6572696320582d426f78207061,Xbox Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n4d6963726f736f667420582d426f7820,Xbox Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n64633436313965656664373634323364,Xbox Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b19,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000005e04000091020000ff073f00,Xbox One Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android,\n050000005e04000091020000ff073f80,Xbox One Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000005e040000e00200000ffe3f00,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b17,y:b2,platform:Android,\n050000005e040000e00200000ffe3f80,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a2,righty:a3,start:b10,x:b17,y:b2,platform:Android,\n050000005e040000e0020000ffff3f00,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b4,leftshoulder:b3,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b17,y:b2,platform:Android,\n050000005e040000e0020000ffff3f80,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b4,leftshoulder:b3,leftstick:b8,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b7,righttrigger:a5,rightx:a2,righty:a3,start:b10,x:b17,y:b2,platform:Android,\n050000005e040000fd020000ffff3f00,Xbox One Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n33356661323266333733373865656366,Xbox One Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n34356136633366613530316338376136,Xbox One Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,x:b17,y:b2,platform:Android,\n35623965373264386238353433656138,Xbox One Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n36616131643361333337396261666433,Xbox One Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n58626f7820576972656c65737320436f,Xbox One Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n65316262316265373335666131623538,Xbox One Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000005e040000000b000000783f00,Xbox One Elite 2 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android,\n050000005e040000000b000000783f80,Xbox One Elite 2 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000005e040000050b0000ffff3f00,Xbox One Elite 2 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000005e040000e002000000783f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000005e040000ea02000000783f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000005e040000fd020000ff7f3f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000005e040000120b000000783f00,Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android,\n050000005e040000120b000000783f80,Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000005e040000130b0000ffff3f00,Xbox Series Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n65633038363832353634653836396239,Xbox Series Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,\n050000001727000044310000ffff3f00,XiaoMi Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android,\n\n# iOS\n05000000ac0500000100000000006d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS,\n05000000ac050000010000004f066d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS,\n05000000ac05000001000000cf076d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS,\n05000000ac05000001000000df076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,\n05000000ac05000001000000ff076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,\n05000000ac0500000200000000006d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,platform:iOS,\n05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,platform:iOS,\n05000000ac05000004000000a8986d04,8BitDo Micro gamepad,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,lefttrigger:b12,rightshoulder:b13,righttrigger:b14,start:b3,x:b6,y:b5,platform:iOS,\n05000000ac050000040000003b8a6d04,8BitDo SN30 Pro+,a:b1,b:b0,back:b4,dpdown:b7,dpleft:b8,dpright:b9,dpup:b10,guide:b2,leftshoulder:b11,leftstick:b12,lefttrigger:b13,leftx:a0,lefty:a1~,rightshoulder:b14,rightstick:b15,righttrigger:b16,rightx:a2,righty:a3~,start:b3,x:b6,y:b5,platform:iOS,\n050000008a35000003010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,\n050000008a35000004010000ff070000,Backbone One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,\n4d466947616d65706164010000000000,MFi Extended Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:iOS,\n4d466947616d65706164020000000000,MFi Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b6,x:b2,y:b3,platform:iOS,\n050000007e050000062000000f060000,Nintendo Switch Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,platform:iOS,\n050000007e050000062000004f060000,Nintendo Switch Joy-Con (L),+leftx:h0.1,+lefty:h0.2,-leftx:h0.4,-lefty:h0.8,dpdown:b2,dpleft:b0,dpright:b3,dpup:b1,leftshoulder:b4,misc1:b6,rightshoulder:b5,platform:iOS,\n050000007e05000008200000df070000,Nintendo Switch Joy-Con (L/R),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,\n050000007e0500000e200000df070000,Nintendo Switch Joy-Con (L/R),a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:iOS,\n050000007e050000072000004f060000,Nintendo Switch Joy-Con (R),+rightx:h0.4,+righty:h0.8,-rightx:h0.1,-righty:h0.2,a:b1,b:b0,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b3,y:b2,platform:iOS,\n050000007e05000009200000df870000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b10,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:iOS,\n050000007e05000009200000ff870000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,\n050000004c050000cc090000df070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,\n050000004c050000cc090000df870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,\n050000004c050000cc090000ff070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,\n050000004c050000cc090000ff870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,platform:iOS,\n050000004c050000cc090000ff876d01,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,\n050000004c050000e60c0000df870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,touchpad:b10,x:b2,y:b3,platform:iOS,\n050000004c050000e60c0000ff870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,platform:iOS,\n05000000ac0500000300000000006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS,\n05000000ac0500000300000043006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS,\n05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,\n05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,\n050000005e040000050b0000df070001,Xbox Elite Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b10,paddle2:b12,paddle3:b11,paddle4:b13,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,\n050000005e040000050b0000ff070001,Xbox Elite Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,\n050000005e040000e0020000df070000,Xbox One Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,\n050000005e040000e0020000ff070000,Xbox One Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,\n050000005e040000130b0000df870001,Xbox Series Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b10,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,\n050000005e040000130b0000ff870001,Xbox Series Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,\n"
  },
  {
    "path": "assets/glsl/README.md",
    "content": "# MegaZeux GLSL Shaders\n\nThis folder contains the shaders used by MegaZeux's GLSL renderer.\n\nThe subfolder 'scalers' contains various scaling shaders which can be\nused (default: semisoft.frag). Use the F2 menu in MegaZeux to select one\nof the shaders from this folder.\n"
  },
  {
    "path": "assets/glsl/cursor.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\nvarying vec3 vColor;\n\nvoid main(void)\n{\n  gl_FragColor = vec4(vColor.x, vColor.y, vColor.z, 1.0);\n}\n"
  },
  {
    "path": "assets/glsl/cursor.vert",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\nattribute vec2 Position;\nattribute vec3 Color;\n\nvarying vec3 vColor;\n\nvoid main(void)\n{\n    gl_Position = vec4(Position.x, Position.y, 0.0, 1.0);\n    vColor = Color;\n}\n"
  },
  {
    "path": "assets/glsl/mouse.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\nvoid main(void)\n{\n  gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n}\n"
  },
  {
    "path": "assets/glsl/mouse.vert",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\nattribute vec2 Position;\n\nvoid main(void)\n{\n  gl_Position = vec4(Position.x, Position.y, 0.0, 1.0);\n}\n"
  },
  {
    "path": "assets/glsl/scaler.vert",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\nattribute vec2 Position;\nattribute vec2 Texcoord;\n\nvarying vec2 vTexcoord;\n\nvoid main(void)\n{\n  gl_Position = vec4(Position.x, Position.y, 0.0, 1.0);\n  vTexcoord = Texcoord;\n}\n"
  },
  {
    "path": "assets/glsl/scalers/crt-wave.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017 David (astral) Cravens (decravens@gmail.com)\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/*\n * crt-wave.frag - a fragment shader that generates scanlines with a sine wave\n *                 based on semisoft.frag\n *\n * settings:\n *     DARKEN         - how dark the gaps get\n *     BOOST          - how bright the scanlines are\n *     SCAN_FREQUENCY - how rapidly down Y the wave oscillates\n */\n\n#version 110\n\nuniform sampler2D baseMap;\nvarying vec2 vTexcoord;\n\n#define XS 1024.0\n#define YS 512.0\n#define AX 0.5/XS\n#define AY 0.5/YS\n\n#define TS vec2(XS, YS)\n\n// -- settings ---------------\n#define DARKEN 0.8\n#define BOOST 1.4\n#define SCAN_FREQUENCY 3.0\n// ---------------------------\n\n#define HALFERS ((BOOST - DARKEN) * 0.5)\n\nfloat calc_scanline( float y )\n{\n    return DARKEN + HALFERS + sin(2.0*3.14*y/SCAN_FREQUENCY) * HALFERS;\n}\n\nvoid main( void )\n{\n    vec2 tcbase = (floor(vTexcoord * TS + 0.5) + 0.5) / TS;\n    vec2 tcdiff = vTexcoord - tcbase;\n    vec2 sdiff = sign(tcdiff);\n    vec2 adiff = pow(abs(tcdiff) * TS, vec2(3.0));\n\n    gl_FragColor = texture2D(baseMap, tcbase + sdiff * adiff / TS) * calc_scanline(gl_FragCoord.y);\n}\n"
  },
  {
    "path": "assets/glsl/scalers/crt.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017 David (astral) Cravens (decravens@gmail.com)\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/*\n * crt.frag - a fragment shader that generates scanlines, based on crt-pi from\n *            libretro and semisoft.frag\n */\n\n#version 110\n\nuniform sampler2D baseMap;\n\nvarying vec2 vTexcoord;\n\n#define MASK_BRIGHTNESS 0.70\n#define SCANLINE_WEIGHT 6.0\n#define SCANLINE_GAP_BRIGHTNESS 0.12\n#define BLOOM_FACTOR 1.5\n#define INPUT_GAMMA 2.4\n#define OUTPUT_GAMMA 2.2\n\n#define XS 1024.0\n#define YS 512.0\n#define TS vec2(XS,YS)\n\nfloat CalcScanLineWeight(float dist)\n{\n\treturn max(1.0-dist*dist*SCANLINE_WEIGHT, SCANLINE_GAP_BRIGHTNESS);\n}\n\nvoid main()\n{\n    vec2 texcoordInPixels = vTexcoord * TS;\n    vec2 tempCoord = floor(texcoordInPixels) + 0.5;\n\n    vec2 coord = tempCoord / TS;\n    vec2 deltas = texcoordInPixels - tempCoord;\n    float scanLineWeight = CalcScanLineWeight(deltas.y);\n\n    vec2 tcbase = (tempCoord + 0.5)/TS;\n    vec2 tcdiff = vTexcoord-tcbase;\n    vec2 sdiff = sign(tcdiff);\n    vec2 adiff = pow(abs(tcdiff)*TS, vec2(2.0));\n\n    vec3 color = texture2D(baseMap, tcbase + sdiff*adiff/TS).rgb;\n\n    color = pow(color, vec3(INPUT_GAMMA));\n\n    scanLineWeight *= BLOOM_FACTOR;\n    color *= scanLineWeight;\n\n    color = pow(color, vec3(1.0/OUTPUT_GAMMA));\n\n    float whichMask = fract(gl_FragCoord.x * 0.3333333);\n    vec3 mask = vec3(MASK_BRIGHTNESS, MASK_BRIGHTNESS, MASK_BRIGHTNESS);\n    if (whichMask < 0.3333333)\n        mask.x = 1.0;\n    else if (whichMask < 0.6666666)\n        mask.y = 1.0;\n    else\n        mask.z = 1.0;\n\n    gl_FragColor = vec4(color * mask, 1.0) * 1.75;\n}\n"
  },
  {
    "path": "assets/glsl/scalers/emboss.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\nuniform sampler2D baseMap;\n\nvarying vec2 vTexcoord;\n\nvoid main( void )\n{\n  vec2 pos = vec2(vTexcoord.x, vTexcoord.y);\n  vec2 posA = vec2(vTexcoord.x - 0.0005, vTexcoord.y + 0.0007);\n  vec2 posB = vec2(vTexcoord.x + 0.0006, vTexcoord.y - 0.001);\n\n  gl_FragColor =\n   texture2D(baseMap, pos) * 2.0 -\n   texture2D(baseMap, posA) * 2.0 +\n   texture2D(baseMap, posB);\n}\n"
  },
  {
    "path": "assets/glsl/scalers/epx.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017 GreaseMonkey\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\n// vim:syntax=c:sts=3:sw=3:et:\n\n// EPX scaler by GreaseMonkey\nuniform sampler2D baseMap;\n\nvarying vec2 vTexcoord;\n\n#define XS 1024.0\n#define YS 512.0\n\nvoid main(void)\n{\n  vec2 tcstepx = vec2(1.0/XS, 0.0);\n  vec2 tcstepy = vec2(0.0, 1.0/YS);\n  vec2 tcbase = (floor(vTexcoord*vec2(XS, YS)) + vec2(0.5, 0.5))/vec2(XS, YS);\n  vec4 c0 = texture2D(baseMap, tcbase);\n  vec4 c1 = texture2D(baseMap, tcbase-tcstepy);\n  vec4 c2 = texture2D(baseMap, tcbase-tcstepx);\n  vec4 c3 = texture2D(baseMap, tcbase+tcstepy);\n  vec4 c4 = texture2D(baseMap, tcbase+tcstepx);\n  vec4 final = c0;\n  ivec2 subpos = ivec2(mod(vTexcoord*vec2(XS, YS)*2.0, 2.0));\n  int subpos_i = subpos.x + (subpos.y*2);\n\n  if(subpos_i == 0)\n  {\n    if(c1 == c2 && c1 != c3 && c1 != c4)\n      final = c1;\n  }\n  else\n\n  if(subpos_i == 2)\n  {\n    if(c2 == c3 && c2 != c4 && c2 != c1)\n      final = c2;\n  }\n  else\n\n  if(subpos_i == 3)\n  {\n    if(c3 == c4 && c3 != c1 && c3 != c2)\n      final = c3;\n  }\n  else\n\n  if(subpos_i == 1)\n  {\n    if(c4 == c1 && c4 != c2 && c4 != c3)\n      final = c4;\n  }\n\n  gl_FragColor = final;\n}\n"
  },
  {
    "path": "assets/glsl/scalers/greyscale.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\n/* Greyscale scaling shader based on nearest.frag */\n\nuniform sampler2D baseMap;\n\nvarying vec2 vTexcoord;\n\n#define TEX_SCREEN_WIDTH  1024.0\n#define TEX_SCREEN_HEIGHT 512.0\n#define HALF_PIXEL_X      0.5 / TEX_SCREEN_WIDTH\n#define HALF_PIXEL_Y      0.5 / TEX_SCREEN_HEIGHT\n\nvoid main(void)\n{\n  const vec4 weight = vec4(0.30, 0.59, 0.11, 0);\n\n  vec2 src = vec2(\n    floor(vTexcoord.x * TEX_SCREEN_WIDTH) / TEX_SCREEN_WIDTH + HALF_PIXEL_X,\n    floor(vTexcoord.y * TEX_SCREEN_HEIGHT) / TEX_SCREEN_HEIGHT + HALF_PIXEL_Y\n  );\n\n  vec4 color = texture2D(baseMap, src);\n\n  float lum = dot(color, weight);\n\n  gl_FragColor = vec4(lum, lum, lum, 1.0);\n}\n"
  },
  {
    "path": "assets/glsl/scalers/hqscale.frag",
    "content": "/*\n   4xGLSLHqFilter shader\n\n   Copyright (C) 2005 guest(r) - guest.r@gmail.com\n\n   This program is free software; you can redistribute it and/or\n   modify it under the terms of the GNU General Public License\n   as published by the Free Software Foundation; either version 2\n   of the License, or (at your option) any later version.\n\n   This program is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n   GNU General Public License for more details.\n\n   You should have received a copy of the GNU General Public License\n   along with this program; if not, write to the Free Software\n   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#version 110\n\nuniform sampler2D baseMap;\n\nvarying vec2 vTexcoord;\n\nvarying vec4 vT1;\nvarying vec4 vT2;\nvarying vec4 vT3;\nvarying vec4 vT4;\nvarying vec4 vT5;\nvarying vec4 vT6;\n\n/* Default Vertex shader */\nconst vec3 dt = vec3(1.0, 1.0, 1.0);\n\nconst float mx = 1.0;        // start smoothing wt.\nconst float k = -1.10;       // wt. decrease factor\nconst float max_w = 0.75;    // max filter weigth\nconst float min_w = 0.03;    // min filter weigth\nconst float lum_add = 0.33;  // effects smoothing\n\n#define XS 1024.0\n#define YS 512.0\n#define AX 0.5/XS\n#define AY 0.5/YS\n\nvoid main(void)\n{\n    /* FIXME: Would be faster if we just disabled GL_LINEAR,\n     *        then we don't need this floor() junk..\n     */\n    vec3 c  = texture2D(baseMap, vec2(floor(vTexcoord.x*XS)/XS+AX, floor(vTexcoord.y*YS)/YS+AY)).xyz;\n    vec3 i1 = texture2D(baseMap, vec2(floor(vT1.x*XS)/XS+AX, floor(vT1.y*YS)/YS+AY)).xyz;\n    vec3 i2 = texture2D(baseMap, vec2(floor(vT2.x*XS)/XS+AX, floor(vT2.y*YS)/YS+AY)).xyz;\n    vec3 i3 = texture2D(baseMap, vec2(floor(vT3.x*XS)/XS+AX, floor(vT3.y*YS)/YS+AY)).xyz;\n    vec3 i4 = texture2D(baseMap, vec2(floor(vT4.x*XS)/XS+AX, floor(vT4.y*YS)/YS+AY)).xyz;\n    vec3 o1 = texture2D(baseMap, vec2(floor(vT5.x*XS)/XS+AX, floor(vT5.y*YS)/YS+AY)).xyz;\n    vec3 o3 = texture2D(baseMap, vec2(floor(vT6.x*XS)/XS+AX, floor(vT6.y*YS)/YS+AY)).xyz;\n    vec3 o2 = texture2D(baseMap, vec2(floor(vT5.z*XS)/XS+AX, floor(vT5.w*YS)/YS+AY)).xyz;\n    vec3 o4 = texture2D(baseMap, vec2(floor(vT6.z*XS)/XS+AX, floor(vT6.w*YS)/YS+AY)).xyz;\n    vec3 s1 = texture2D(baseMap, vec2(floor(vT1.z*XS)/XS+AX, floor(vT1.w*YS)/YS+AY)).xyz;\n    vec3 s2 = texture2D(baseMap, vec2(floor(vT2.z*XS)/XS+AX, floor(vT2.w*YS)/YS+AY)).xyz;\n    vec3 s3 = texture2D(baseMap, vec2(floor(vT3.z*XS)/XS+AX, floor(vT3.w*YS)/YS+AY)).xyz;\n    vec3 s4 = texture2D(baseMap, vec2(floor(vT4.z*XS)/XS+AX, floor(vT4.w*YS)/YS+AY)).xyz;\n\n    float ko1=dot(abs(o1-c),dt);\n    float ko2=dot(abs(o2-c),dt);\n    float ko3=dot(abs(o3-c),dt);\n    float ko4=dot(abs(o4-c),dt);\n\n    float k1=min(dot(abs(i1-i3),dt),max(ko1,ko3));\n    float k2=min(dot(abs(i2-i4),dt),max(ko2,ko4));\n\n    float w1 = k2; if(ko3<ko1) w1*=ko3/ko1;\n    float w2 = k1; if(ko4<ko2) w2*=ko4/ko2;\n    float w3 = k2; if(ko1<ko3) w3*=ko1/ko3;\n    float w4 = k1; if(ko2<ko4) w4*=ko2/ko4;\n\n    c=(w1*o1+w2*o2+w3*o3+w4*o4+0.001*c)/(w1+w2+w3+w4+0.001);\n    w1 = k*dot(abs(i1-c)+abs(i3-c),dt)/(0.125*dot(i1+i3,dt)+lum_add);\n    w2 = k*dot(abs(i2-c)+abs(i4-c),dt)/(0.125*dot(i2+i4,dt)+lum_add);\n    w3 = k*dot(abs(s1-c)+abs(s3-c),dt)/(0.125*dot(s1+s3,dt)+lum_add);\n    w4 = k*dot(abs(s2-c)+abs(s4-c),dt)/(0.125*dot(s2+s4,dt)+lum_add);\n\n    w1 = clamp(w1+mx,min_w,max_w);\n    w2 = clamp(w2+mx,min_w,max_w);\n    w3 = clamp(w3+mx,min_w,max_w);\n    w4 = clamp(w4+mx,min_w,max_w);\n\n    gl_FragColor = vec4((w1*(i1+i3)+w2*(i2+i4)+w3*(s1+s3)+w4*(s2+s4)+c)/(2.0*(w1+w2+w3+w4)+1.0), 1.0);\n}\n"
  },
  {
    "path": "assets/glsl/scalers/hqscale.vert",
    "content": "/*\n   4xGLSLHqFilter shader\n\n   Copyright (C) 2005 guest(r) - guest.r@gmail.com\n\n   This program is free software; you can redistribute it and/or\n   modify it under the terms of the GNU General Public License\n   as published by the Free Software Foundation; either version 2\n   of the License, or (at your option) any later version.\n\n   This program is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n   GNU General Public License for more details.\n\n   You should have received a copy of the GNU General Public License\n   along with this program; if not, write to the Free Software\n   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n*/\n\n#version 110\n\nattribute vec2 Position;\nattribute vec2 Texcoord;\n\nvarying vec2 vTexcoord;\n\nvarying vec4 vT1;\nvarying vec4 vT2;\nvarying vec4 vT3;\nvarying vec4 vT4;\nvarying vec4 vT5;\nvarying vec4 vT6;\n\n#define XS 1024.0\n#define YS 512.0\n\nvoid main(void)\n{\n    float x = 0.5 * (1.0 / XS);\n    float y = 0.5 * (1.0 / YS);\n    vec2 dg1 = vec2(x, y);\n    vec2 dg2 = vec2(-x, y);\n    vec2 sd1 = dg1*0.5;\n    vec2 sd2 = dg2*0.5;\n    vec2 ddx = vec2(x,0.0);\n    vec2 ddy = vec2(0.0,y);\n\n    gl_Position = vec4(Position.x, Position.y, 0.0, 1.0);\n    vTexcoord = Texcoord;\n\n    vT1 = vec4(vTexcoord-sd1,vTexcoord-ddy);\n    vT2 = vec4(vTexcoord-sd2,vTexcoord+ddx);\n    vT3 = vec4(vTexcoord+sd1,vTexcoord+ddy);\n    vT4 = vec4(vTexcoord+sd2,vTexcoord-ddx);\n    vT5 = vec4(vTexcoord-dg1,vTexcoord-dg2);\n    vT6 = vec4(vTexcoord+dg1,vTexcoord+dg2);\n}\n"
  },
  {
    "path": "assets/glsl/scalers/nearest.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\nuniform sampler2D baseMap;\n\nvarying vec2 vTexcoord;\n\n#define TEX_SCREEN_WIDTH  1024.0\n#define TEX_SCREEN_HEIGHT 512.0\n#define HALF_PIXEL_X      0.5 / TEX_SCREEN_WIDTH\n#define HALF_PIXEL_Y      0.5 / TEX_SCREEN_HEIGHT\n\nvoid main(void)\n{\n  vec2 src = vec2(\n    floor(vTexcoord.x * TEX_SCREEN_WIDTH) / TEX_SCREEN_WIDTH + HALF_PIXEL_X,\n    floor(vTexcoord.y * TEX_SCREEN_HEIGHT) / TEX_SCREEN_HEIGHT + HALF_PIXEL_Y\n  );\n\n  gl_FragColor = texture2D(baseMap, src);\n}\n"
  },
  {
    "path": "assets/glsl/scalers/sai.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// This is adapted fairly closely from the 2xSaI C++ source code.\n// Specifically, this is based on the \"Scale 2xSaI\" mode designed for an\n// arbitrary destination size.\n//\n// 2xSaI is Copyright (c) 1999-2001 by Derek Liauw Kie Fa.\n// 2xSaI is free under GPL. I hope you'll give me appropriate credit.\n// If you want another license for your (free) project, contact me.\n//\n// The author confirmed in October 2009 that this means specifically GPL 2+.\n// https://www.redhat.com/archives/fedora-legal-list/2009-October/msg00025.html\n\n#version 110\n\nuniform sampler2D baseMap;\n\nvarying vec2 vTexcoord;\n\n// The mix() wrapper used here utilizes a sigmoid function to weigh the\n// interpolation towards the closer source color. This is done to make this\n// scaling method look better with MegaZeux-style graphics.\n//\n// A higher MULTIPLIER value produces sharper edges, but too high of a value\n// will make things blocky. Comment out the SHARPEN_EDGES line to disable this\n// (making this shader blurry but fairly accurate to the original Scale 2xSaI).\n\n#define SHARPEN_EDGES     1\n#define MULTIPLIER        10.0\n\n#define TEX_SCREEN_WIDTH  1024.0\n#define TEX_SCREEN_HEIGHT 512.0\n#define PIXEL_X           1.0 / TEX_SCREEN_WIDTH\n#define PIXEL_Y           1.0 / TEX_SCREEN_HEIGHT\n#define HALF_PIXEL_X      0.5 / TEX_SCREEN_WIDTH\n#define HALF_PIXEL_Y      0.5 / TEX_SCREEN_HEIGHT\n\nvec4 _mix(vec4 A, vec4 B, float x)\n{\n#ifdef SHARPEN_EDGES\n  x = (x - 0.5) * MULTIPLIER;\n  x = 1.0 / (exp(-x) + 1.0);\n#endif\n\n  return mix(A, B, x);\n}\n\nvoid main(void)\n{\n  /**\n   *   E F\n   * G A B I\n   * H C D J\n   *   K L\n   */\n\n  vec4 A, B, C, D, E, F, G, H, I, J, K, L;\n\n  float x = floor(vTexcoord.x * TEX_SCREEN_WIDTH) / TEX_SCREEN_WIDTH + HALF_PIXEL_X;\n  float y = floor(vTexcoord.y * TEX_SCREEN_HEIGHT) / TEX_SCREEN_HEIGHT + HALF_PIXEL_Y;\n\n  float x_fr = fract(vTexcoord.x * TEX_SCREEN_WIDTH);\n  float y_fr = fract(vTexcoord.y * TEX_SCREEN_HEIGHT);\n  float x_fr2;\n  float y_fr2;\n  float f1;\n  float f2;\n  vec4 res;\n\n  A = texture2D(baseMap, vec2(x,                  y));\n  B = texture2D(baseMap, vec2(x + PIXEL_X,        y));\n  C = texture2D(baseMap, vec2(x,                  y + PIXEL_Y));\n  D = texture2D(baseMap, vec2(x + PIXEL_X,        y + PIXEL_Y));\n\n  if(A == B && C == D && A == C)\n  {\n    res = A;\n  }\n  else\n\n  if(A == D && B != C)\n  {\n    E = texture2D(baseMap, vec2(x,                  y - PIXEL_Y));\n    G = texture2D(baseMap, vec2(x - PIXEL_X,        y));\n    L = texture2D(baseMap, vec2(x + PIXEL_X,        y + PIXEL_Y * 2.0));\n    J = texture2D(baseMap, vec2(x + PIXEL_X * 2.0,  y + PIXEL_Y));\n\n    f1 = x_fr / 2.0 + 0.25;\n    f2 = y_fr / 2.0 + 0.25;\n\n    if(y_fr <= f1 && A == J && A != E) // Close to B\n      res = _mix(A, B, f1 - y_fr);\n\n    else\n    if(y_fr >= f1 && A == G && A != L) // Close to C\n      res = _mix(A, C, y_fr - f1);\n\n    else\n    if(x_fr >= f2 && A == E && A != J) // Close to B\n      res = _mix(A, B, x_fr - f2);\n\n    else\n    if(x_fr <= f2 && A == L && A != G) // Close to C\n      res = _mix(A, C, f2 - x_fr);\n\n    else\n    if(y_fr >= x_fr) // Close to C\n      res = _mix(A, C, y_fr - x_fr);\n\n    else\n    //if(y_fr <= x_fr) // Close to B\n      res = _mix(A, B, x_fr - y_fr);\n  }\n  else\n\n  if(B == C && A != D)\n  {\n    F = texture2D(baseMap, vec2(x + PIXEL_X,        y - PIXEL_Y));\n    H = texture2D(baseMap, vec2(x - PIXEL_X,        y + PIXEL_Y));\n    I = texture2D(baseMap, vec2(x + PIXEL_X * 2.0,  y));\n    K = texture2D(baseMap, vec2(x,                  y + PIXEL_Y * 2.0));\n\n    f1 = x_fr / 2.0 + 0.25;\n    f2 = y_fr / 2.0 + 0.25;\n    x_fr2 = 1.0 - x_fr;\n    y_fr2 = 1.0 - y_fr;\n\n    if(y_fr2 >= f1 && B == H && B != F) // Close to A\n      res = _mix(B, A, y_fr2 - f1);\n\n    else\n    if(y_fr2 <= f1 && B == I && B != K) // Close to D\n      res = _mix(B, D, f1 - y_fr2);\n\n    else\n    if(x_fr2 >= f2 && B == F && B != H) // Close to A\n      res = _mix(B, A, x_fr2 - f2);\n\n    else\n    if(x_fr2 <= f2 && B == K && B != I) // Close to D\n      res = _mix(B, D, f2 - x_fr2);\n\n    else\n    if(y_fr2 >= x_fr) // Close to A\n      res = _mix(B, A, y_fr2 - x_fr);\n\n    else\n    //if(y_fr2 <= x_fr) // Close to D\n      res = _mix(B, D, x_fr - y_fr2);\n  }\n\n  else\n  {\n    res = _mix(_mix(A, B, x_fr), _mix(C, D, x_fr), y_fr);\n  }\n\n  gl_FragColor = res;\n}\n"
  },
  {
    "path": "assets/glsl/scalers/semisoft.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017 GreaseMonkey\n * Copyright (C) 2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\nuniform sampler2D baseMap;\n\nvarying vec2 vTexcoord;\n\n/* Larger values result in cleaner edges but will cause stretching\n * artifacts to become more apparent. */\n#define SHARPNESS         3.0\n\n#define TEX_SCREEN_WIDTH  1024.0\n#define TEX_SCREEN_HEIGHT 512.0\n#define HALF_PIXEL_X      0.5 / TEX_SCREEN_WIDTH\n#define HALF_PIXEL_Y      0.5 / TEX_SCREEN_HEIGHT\n\nvoid main(void)\n{\n  const vec2 resolution = vec2(TEX_SCREEN_WIDTH, TEX_SCREEN_HEIGHT);\n\n  /* Select a source texture pixel to shift the current fragment towards.\n   *\n   * This is NOT the nearest pixel: to get consistent results, every\n   * fragment needs to be smoothed in the same direction, so the right and\n   * bottom quadrants of a source pixel mix toward the next pixel in their\n   * respective directions. Unfortunately, this means parts of the top row\n   * and left column of source pixels get clipped.\n   */\n  vec2 pos = vTexcoord * resolution;\n  vec2 mix_point = floor(pos + 0.5) + 0.5;\n  vec2 dist = pos - mix_point;\n\n  dist = sign(dist) * pow(abs(dist), vec2(SHARPNESS));\n  gl_FragColor = texture2D(baseMap, (mix_point + dist) / resolution);\n}\n"
  },
  {
    "path": "assets/glsl/scalers/sepia.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\n/* Sepia scaling shader based on simple.frag */\n\nuniform sampler2D baseMap;\n\nvarying vec2 vTexcoord;\n\nvoid main(void)\n{\n  const vec4 r_weight = vec4(0.393, 0.769, 0.189, 0);\n  const vec4 g_weight = vec4(0.349, 0.686, 0.168, 0);\n  const vec4 b_weight = vec4(0.272, 0.534, 0.131, 0);\n\n  vec4 color = texture2D(baseMap, vTexcoord);\n\n  gl_FragColor = vec4(\n    min(1.0, dot(color, r_weight)),\n    min(1.0, dot(color, g_weight)),\n    min(1.0, dot(color, b_weight)),\n    1.0\n  );\n}\n"
  },
  {
    "path": "assets/glsl/scalers/simple.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\nuniform sampler2D baseMap;\n\nvarying vec2 vTexcoord;\n\nvoid main(void)\n{\n  gl_FragColor = texture2D( baseMap, vTexcoord );\n}\n"
  },
  {
    "path": "assets/glsl/tilemap.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\n#ifdef GL_ES\n#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\n#endif\n\n// Keep these the same as in render_glsl.c\n#define CHARSET_COLS      64.0\n#define CHARSET_ROWS_EACH 4.0\n#define TEX_DATA_WIDTH    512.0\n#define TEX_DATA_HEIGHT   1024.0\n#define TEX_DATA_PAL_Y    896.0\n#define TEX_DATA_LAYER_X  0.0\n#define TEX_DATA_LAYER_Y  901.0\n\n// This has to be slightly less than 14.0 to avoid propagating error\n// with some very old driver/video card combos.\n#define CHAR_H            13.99999\n\n// Relative position of the protected palette seen by this shader.\n#define PROTECTED_PALETTE 16.0\n\nuniform sampler2D baseMap;\nuniform float protected_pal_position;\n\nvarying vec2 vTexcoord;\n\nfloat fract_(float v)\n{\n  return clamp(fract(v + 0.001) - 0.001, 0.000, 0.999);\n}\n\nfloat floor_(float v)\n{\n  return floor(v + 0.001);\n}\n\nint int_(float v)\n{\n  return int(v + 0.01);\n}\n\n// NOTE: Layer data packing scheme\n// This is intentionally wasteful so the components don't interfere with each\n// other on old and embedded cards with poor float precision. The colors are\n// encoded to skip SMZX exclusive colors so they only require one byte each.\n// C = character\n// B = background color (0-15 normal; >=16 protected)\n// F = foreground color (0-15 normal; >=16 protected)\n// w        z        y        x\n// 00000000 00000000 00000000 00000000\n// CCCCCCCC CCCCCCCC BBBBBBBB FFFFFFFF\n#define PACK_COLOR_FG  x\n#define PACK_COLOR_BG  y\n#define PACK_CHAR      z\n#define PACK_CHARSET   w\n\n// Some older cards/drivers tend to be slightly off; slight variations\n// in values here are intentional.\n\nfloat layer_unpack(float layer_data)\n{\n  return layer_data * 255.001;\n}\n\nfloat layer_unpack_color(float color_data)\n{\n  if(color_data * 255.001 >= (PROTECTED_PALETTE - 0.001))\n    return (protected_pal_position - PROTECTED_PALETTE) + color_data * 255.001;\n\n  return color_data * 255.001;\n}\n\nvoid main(void)\n{\n  /**\n   * Get the packed char/color data for this position from the current layer.\n   * vTexcoord will be provided in the range of x=[0..layer.w), y=[0..layer.h).\n   * Note that floor() is not required on vTexcoord since the texture filtering\n   * is set to GL_NEAREST (floor() also causes bugs here on some old drivers).\n   */\n  float layer_x = (vTexcoord.x + TEX_DATA_LAYER_X) / TEX_DATA_WIDTH;\n  float layer_y = (vTexcoord.y + TEX_DATA_LAYER_Y) / TEX_DATA_HEIGHT;\n  vec4 layer_data = texture2D(baseMap, vec2(layer_x, layer_y));\n\n  /**\n   * Get the current char and its base position in the texture charset.\n   * The x position will be normalized to the width of the texture,\n   * but for the y position it's easier to get the pixel position and\n   * normalize afterward.\n   */\n  float char_num = layer_unpack(layer_data.PACK_CHAR);\n  float char_set = layer_unpack(layer_data.PACK_CHARSET);\n  float char_x = fract_(char_num / CHARSET_COLS);\n  float char_y = floor_(char_num / CHARSET_COLS) + char_set * CHARSET_ROWS_EACH;\n\n  /**\n   * Get the current pixel value of the current char from the texture.\n   */\n  float char_tex_x = char_x + fract_(vTexcoord.x) / CHARSET_COLS;\n  float char_tex_y = (char_y + fract_(vTexcoord.y)) * CHAR_H / TEX_DATA_HEIGHT;\n  vec4 char_pix = texture2D(baseMap, vec2(char_tex_x, char_tex_y));\n\n  /**\n   * Determine whether this is the foreground or background color of the char,\n   * get that color from the texture, and output it.\n   */\n  float color;\n\n  // We could actually check any component here.\n  if(char_pix.x > 0.5)\n  {\n    color = layer_unpack_color(layer_data.PACK_COLOR_FG);\n  }\n  else\n  {\n    color = layer_unpack_color(layer_data.PACK_COLOR_BG);\n  }\n\n  gl_FragColor = texture2D(baseMap,\n   vec2(color / TEX_DATA_WIDTH, TEX_DATA_PAL_Y / TEX_DATA_HEIGHT));\n}\n"
  },
  {
    "path": "assets/glsl/tilemap.smzx.frag",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\n#ifdef GL_ES\n#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\n#endif\n\nuniform sampler2D baseMap;\n\nvarying vec2 vTexcoord;\n\n// Keep these the same as in render_glsl.c\n#define CHARSET_COLS      64.0\n#define CHARSET_ROWS_EACH 4.0\n#define TEX_DATA_WIDTH    512.0\n#define TEX_DATA_HEIGHT   1024.0\n#define TEX_DATA_PAL_Y    896.0\n#define TEX_DATA_IDX_Y    897.0\n#define TEX_DATA_LAYER_X  0.0\n#define TEX_DATA_LAYER_Y  901.0\n\n#define COLOR_TRANSPARENT (272.0 - 0.001)\n#define PIXEL_X           1.0 / TEX_DATA_WIDTH\n\n// This has to be slightly less than 14.0 to avoid propagating error\n// with some very old driver/video card combos.\n#define CHAR_W            8.0\n#define CHAR_H            13.99999\n#define CHAR_W_I          8\n#define CHAR_H_I          14\n\nfloat fract_(float v)\n{\n  return clamp(fract(v + 0.001) - 0.001, 0.000, 0.999);\n}\n\nfloat floor_(float v)\n{\n  return floor(v + 0.001);\n}\n\nfloat mod_(float v, float r)\n{\n  return clamp(mod(v + 0.01, r) - 0.01, 0.0, r);\n}\n\nint int_(float v)\n{\n  return int(v + 0.01);\n}\n\n// NOTE: Layer data packing scheme.\n// This is intentionally wasteful so the components don't interfere with each\n// other on old and embedded cards with poor float precision.\n// C = char.\n// P = subpalette (or a fully transparent char if any high bits are set).\n// w        z        y        x\n// 00000000 00000000 00000000 00000000\n// CCCCCCCC CCCCCCCC PPPPPPPP PPPPPPPP\n#define PACK_SUBPAL_LO x\n#define PACK_SUBPAL_HI y\n#define PACK_CHAR      z\n#define PACK_CHARSET   w\n\n// Some older cards/drivers tend to be slightly off; slight variations\n// in values here are intentional.\n\nfloat layer_unpack(float layer_data)\n{\n  return layer_data * 255.001;\n}\n\nvoid main( void )\n{\n  /**\n   * Get the packed char/color data for this position from the current layer.\n   * vTexcoord will be provided in the range of x=[0..layer.w), y=[0..layer.h).\n   * Note that floor() is not required on vTexcoord since the texture filtering\n   * is set to GL_NEAREST (floor() also causes bugs here on some old drivers).\n   */\n  float layer_x = (vTexcoord.x + TEX_DATA_LAYER_X) / TEX_DATA_WIDTH;\n  float layer_y = (vTexcoord.y + TEX_DATA_LAYER_Y) / TEX_DATA_HEIGHT;\n  vec4 layer_data = texture2D(baseMap, vec2(layer_x, layer_y));\n\n  // If any of the high subpalette bits are set, display a transparent pixel...\n  if(layer_unpack(layer_data.PACK_SUBPAL_HI) >= 0.999)\n  {\n    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);\n  }\n  else\n  {\n    /**\n     * The math in this shader is more complex than the math in the regular\n     * shader, so it's handled partly in integers instead of floats. Older cards\n     * seem less bad at calculating ints than floats (albeit slower too).\n     *\n     * Get the current char and:\n     *\n     * 1) determine the pixel position in the char we should be looking at.\n     * We're considering pairs of two pixels horizontally adjacent, so get\n     * the position of the left pixel in the pair specifically.\n     *\n     * 2) Calculate the position on the texture in pixel scale where the\n     * current char is and add the offset from step 1.\n     */\n    float char_num = layer_unpack(layer_data.PACK_CHAR);\n    float char_set = layer_unpack(layer_data.PACK_CHARSET);\n\n    int px = int_(fract_(vTexcoord.x) * CHAR_W) / 2 * 2;\n\n    int char_x = int_(mod_(char_num, CHARSET_COLS) * CHAR_W) + px;\n    float char_y = floor_(char_num / CHARSET_COLS) + (char_set * CHARSET_ROWS_EACH);\n\n    /**\n     * Get the current pixels of the current char from the texture.\n     * Together, these determine which color of the current subpalette to use.\n     */\n    float char_tex_x = float(char_x) / TEX_DATA_WIDTH;\n    float char_tex_y = (char_y + fract_(vTexcoord.y)) * CHAR_H / TEX_DATA_HEIGHT;\n\n    vec4 char_pix_l = texture2D(baseMap, vec2(char_tex_x, char_tex_y));\n    vec4 char_pix_r = texture2D(baseMap, vec2(char_tex_x + PIXEL_X, char_tex_y));\n\n    /**\n     * Determine the SMZX subpalette and SMZX index of the current pixel,\n     * get that color from the texture, and output it.\n     */\n\n    int smzx_col;\n\n    // We could actually check any component here.\n    if(char_pix_l.x < 0.5)\n    {\n      if(char_pix_r.x < 0.5)\n      {\n        smzx_col = 0;\n      }\n      else\n      {\n        smzx_col = 1;\n      }\n    }\n    else\n    {\n      if(char_pix_r.x < 0.5)\n      {\n        smzx_col = 2;\n      }\n      else\n      {\n        smzx_col = 3;\n      }\n    }\n\n    float subpalette = layer_unpack(layer_data.PACK_SUBPAL_LO);\n    float smzx_tex_x = subpalette / TEX_DATA_WIDTH;\n    float smzx_tex_y = (float(smzx_col) + TEX_DATA_IDX_Y) / TEX_DATA_HEIGHT;\n\n    // NOTE: This must use the x component.\n    float real_col = texture2D(baseMap, vec2(smzx_tex_x, smzx_tex_y)).x * 255.001;\n\n    gl_FragColor = texture2D(baseMap,\n     vec2(real_col / TEX_DATA_WIDTH, TEX_DATA_PAL_Y / TEX_DATA_HEIGHT));\n  }\n}\n"
  },
  {
    "path": "assets/glsl/tilemap.vert",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#version 110\n\nattribute vec2 Position;\nattribute vec2 Texcoord;\n\nvarying vec2 vTexcoord;\n\nvoid main(void)\n{\n  gl_Position = vec4(Position.x, Position.y, 0.0, 1.0);\n  vTexcoord = Texcoord;\n}\n"
  },
  {
    "path": "config.sh",
    "content": "#!/bin/sh\n\n### CONFIG.SH HELP TEXT #######################################################\n\nusage() {\n\techo \"usage: ./config.sh --platform [platform] <--prefix [dir]> <--sysconfdir [dir]>\"\n\techo \"                                         <--gamesdir [dir]> <--bindir [dir]>\"\n\techo \"                                         <--sharedir [dir]> <--licensedir [dir]>\"\n\techo \"                                         <--host [name]> <options..>\"\n\techo\n\techo \"  --prefix       Install prefix and where dependencies should be found. (/usr)\"\n\techo \"  --sysconfdir   Where the config should be read from. (/etc)\"\n\techo \"  --gamesdir     Where binaries should be installed. ([prefix]/games)\"\n\techo \"  --libdir       Where libraries should be installed. ([prefix]/lib)\"\n\techo \"  --bindir       Where utilities should be installed. ([prefix]/bin)\"\n\techo \"  --sharedir     Where resources should be installed. ([prefix]/share)\"\n\techo \"  --licensedir   Where licenses should be installed. ([sharedir]/licenses)\"\n\techo \"  --metainfodir  Where metainfo should be installed. ([sharedir]/metainfo)\"\n\techo \"  --host         Specify cross toolchain prefix for Linux et al. (none)\"\n\techo\n\techo \"  Install directories can be disregarded for builds for platforms\"\n\techo \"  with a monolithic directory structure e.g. Windows.\"\n\techo\n\techo \"Supported [platform] values:\"\n\techo\n\techo \"  win32          Microsoft Windows (x86)\"\n\techo \"  win64          Microsoft Windows (x64)\"\n\techo \"  mingw32        Use MinGW32 on Linux, to build for win32\"\n\techo \"  mingw64        Use MinGW64 on Linux, to build for win64\"\n\techo \"  unix           Unix-like / Linux / Solaris / BSD / Embedded\"\n\techo \"  unix-devel     As above, but for running from current dir\"\n\techo \"  darwin         Mac OS X Unix-like install\"\n\techo \"  darwin-devel   Mac OS X running from current dir\"\n\techo \"  darwin-dist    Mac OS X multiarchitecture .app (see arch/darwin/README.md)\"\n\techo \"  psp            Experimental PSP port\"\n\techo \"  gp2x           Experimental GP2X port\"\n\techo \"  nds            Experimental NDS port\"\n\techo \"  nds-blocksds   Experimental NDS (BlocksDS) port\"\n\techo \"  3ds            Experimental 3DS port\"\n\techo \"  switch         Experimental Switch port\"\n\techo \"  wii            Experimental Wii port\"\n\techo \"  wiiu           Experimental Wii U port\"\n\techo \"  psvita         Experimental PS Vita port\"\n\techo \"  dreamcast      Experimental Dreamcast port\"\n\techo \"  amiga          Experimental AmigaOS 4 port\"\n\techo \"  android        Experimental Android port\"\n\techo \"  pandora        Experimental Pandora port\"\n\techo \"  emscripten     Experimental HTML5 (Emscripten) port\"\n\techo \"  djgpp          Experimental DOS port\"\n\techo\n\techo \"Supported <option> values (negatives can be used):\"\n\techo\n\techo \"Optimization and debug options:\"\n\techo \"  --as-needed-hack          Pass --as-needed through to GNU ld.\"\n\techo \"  --enable-release          Optimize and remove debugging code.\"\n\techo \"  --enable-verbose          Build system is always verbose (V=1).\"\n\techo \"  --optimize-speed          Perform speed optimizations (-O3, default).\"\n\techo \"  --optimize-size           Perform size optimizations (-Os).\"\n\techo \"  --enable-lto              Perform link-time optimizations (-flto).\"\n\techo \"  --enable-asan             Enable AddressSanitizer for debug builds.\"\n\techo \"  --enable-msan             Enable MemorySanitizer for debug builds.\"\n\techo \"  --enable-tsan             Enable ThreadSanitizer for debug builds.\"\n\techo \"  --enable-ubsan            Enable UndefinedBehaviorSanitizer for debug builds.\"\n\techo \"  --enable-analyzer         Enable -fanalyzer (requires GCC 10+).\"\n\techo \"  --enable-trace            Enable trace logging for debug builds.\"\n\techo \"  --enable-stdio-redirect   Redirect console logging to stdout.txt/stderr.txt.\"\n\techo \"  --disable-stack-protector Disable stack protector safety checks.\"\n\techo\n\techo \"Platform-dependent options:\"\n\techo \"  --enable-sdl              Enable SDL backend, typically SDL2 (default).\"\n\techo \"  --enable-sdl3             Enable SDL3 backend (any version).\"\n\techo \"  --enable-sdl2             Enable SDL2 backend (any version).\"\n\techo \"  --enable-sdl1             Enable SDL1 backend (1.2.x).\"\n\techo \"  --disable-sdl             Disable SDL dependencies and features.\"\n\techo \"  --enable-egl              Enable EGL backend (disables SDL).\"\n\techo \"  --disable-x11             Disable X11, removing binary dependency.\"\n\techo \"  --disable-pthread         Use SDL's threads/locking instead of pthread.\"\n\techo \"  --disable-icon            Disable runtime window icon loading.\"\n\techo \"  --disable-modular         Disable dynamically shared objects.\"\n\techo \"  --enable-pledge           Enable experimental OpenBSD pledge(2) support\"\n\techo\n\techo \"General options:\"\n\techo \"  --disable-datestamp       Disable adding date to version.\"\n\techo \"  --disable-editor          Disable the built-in editor.\"\n\techo \"  --disable-mzxrun          Disable generation of separate MZXRun.\"\n\techo \"  --disable-helpsys         Disable the built-in help system.\"\n\techo \"  --disable-utils           Disable compilation of utils.\"\n\techo \"  --disable-check-alloc     Disables memory allocator error handling.\"\n\techo \"  --disable-counter-hash    Disables hash tables for counter/string lookups.\"\n\techo \"  --disable-vfs             Disables MZX's built-in virtual filesystem.\"\n\techo \"  --enable-extram           Enable board memory compression and storage hacks.\"\n\techo \"  --enable-meter            Enable load/save meter display.\"\n\techo \"  --enable-debytecode       Enable experimental 'debytecode' transform.\"\n\techo\n\techo \"Graphics options:\"\n\techo \"  --enable-gles             Enable hacks for OpenGL ES platforms.\"\n\techo \"  --disable-software        Disable software renderer.\"\n\techo \"  --disable-softscale       Disable SDL accelerated software renderer.\"\n\techo \"  --enable-sdlaccel         Enable SDL accelerated hardware renderer.\"\n\techo \"  --disable-gl              Disable all GL renderers.\"\n\techo \"  --disable-gl-fixed        Disable GL renderers for fixed-function h/w.\"\n\techo \"  --disable-gl-prog         Disable GL renderers for programmable h/w.\"\n\techo \"  --disable-overlay         Disable SDL 1.2 overlay renderers.\"\n\techo \"  --enable-gp2x             Enables half-res software renderer.\"\n\techo \"  --disable-dos-svga        On the DOS platform, disable SVGA software renderer.\"\n\techo \"  --disable-libpng          Disable PNG screendump support.\"\n\techo \"  --disable-screenshots     Disable the screenshot hotkey.\"\n\techo \"  --enable-fps              Enable frames-per-second counter.\"\n\techo\n\techo \"Audio options:\"\n\techo \"  --disable-audio           Disable all audio (sound + music).\"\n\techo \"  --disable-xmp             Disable XMP music engine.\"\n\techo \"  --enable-modplug          Enables ModPlug music engine.\"\n\techo \"  --enable-mikmod           Enables MikMod music engine.\"\n\techo \"  --enable-openmpt          Enables OpenMPT music engine.\"\n\techo \"  --disable-rad             Disables Reality Adlib Tracker (RAD) support.\"\n\techo \"  --disable-vorbis          Disable ogg/vorbis support.\"\n\techo \"  --enable-tremor           Use libvorbisidec instead of libvorbis.\"\n\techo \"  --enable-tremor-lowmem    Use libvorbisidec (lowmem) instead of libvorbis.\"\n\techo \"  --enable-stb-vorbis       Use stb_vorbis instead of libvorbis.\"\n\techo\n\techo \"Network options:\"\n\techo \"  --disable-network         Disable networking abilities.\"\n\techo \"  --disable-updater         Disable built-in updater.\"\n\techo \"  --disable-getaddrinfo     Disable getaddrinfo for name resolution.\"\n\techo \"  --disable-poll            Disable poll for socket monitoring.\"\n\techo \"  --disable-ipv6            Disable IPv6 support.\"\n\techo\n\techo \"e.g.: ./config.sh --platform unix --prefix /usr\"\n\techo \"                  --sysconfdir /etc --disable-x11\"\n\techo \"e.g.: ./config.sh --platform win32\"\n\techo\n}\n\n### CHOMP CONFIG.SH FLAGS #####################################################\n\n#\n# Default settings for flags (used if unspecified)\n#\nPLATFORM=\"\"\nTOOL_PREFIX=\"\"\nPREFIX=\"/usr\"\nPREFIX_IS_SET=\"false\"\nSYSCONFDIR=\"/etc\"\nSYSCONFDIR_IS_SET=\"false\"\nGAMESDIR_IS_SET=\"false\"\nGAMESDIR_IN_PREFIX=\"/games\"\nGAMESDIR=\"${PREFIX}${GAMESDIR_IN_PREFIX}\"\nLIBDIR_IS_SET=\"false\"\nLIBDIR_IN_PREFIX=\"/lib\"\nLIBDIR=\"${PREFIX}${LIBDIR_IN_PREFIX}\"\nBINDIR_IS_SET=\"false\"\nBINDIR_IN_PREFIX=\"/bin\"\nBINDIR=\"${PREFIX}${BINDIR_IN_PREFIX}\"\nSHAREDIR_IS_SET=\"false\"\nSHAREDIR_IN_PREFIX=\"/share\"\nSHAREDIR=\"${PREFIX}${SHAREDIR_IN_PREFIX}\"\nLICENSEDIR_IS_SET=\"false\"\nLICENSEDIR_IN_SHAREDIR=\"/licenses\"\nLICENSEDIR=\"${SHAREDIR}${LICENSEDIR_IN_SHAREDIR}\"\nMETAINFODIR_IS_SET=\"false\"\nMETAINFODIR_IN_SHAREDIR=\"/metainfo\"\nMETAINFODIR=\"${SHAREDIR}${METAINFODIR_IN_SHAREDIR}\"\nUSERCONFFILE=\"\"\nDATE_STAMP=\"true\"\nAS_NEEDED=\"false\"\nRELEASE=\"false\"\nOPT_SIZE=\"false\"\nLTO=\"false\"\nSANITIZER=\"false\"\nANALYZER=\"false\"\nSTACK_PROTECTOR=\"true\"\nPLEDGE=\"false\"\nPLEDGE_UTILS=\"true\"\nEDITOR=\"true\"\nMZXRUN=\"true\"\nHELPSYS=\"true\"\nUTILS=\"true\"\nX11=\"true\"\nSOFTWARE=\"true\"\nSOFTSCALE=\"true\"\nSDLACCEL=\"false\"\nGL=\"true\"\nGL_FIXED=\"true\"\nGL_PROGRAM=\"true\"\nOVERLAY=\"true\"\nGP2X=\"false\"\nSCREENSHOTS=\"true\"\nXMP=\"true\"\nMODPLUG=\"false\"\nMIKMOD=\"false\"\nOPENMPT=\"false\"\nREALITY=\"true\"\nLIBPNG=\"true\"\nAUDIO=\"true\"\nVORBIS=\"true\"\nPTHREAD=\"true\"\nICON=\"true\"\nMODULAR=\"true\"\nUPDATER=\"true\"\nNETWORK=\"true\"\nGETADDRINFO=\"true\"\nPOLL=\"true\"\nIPV6=\"true\"\nVERBOSE=\"false\"\nEXTRAM=\"false\"\nMETER=\"false\"\nSDL=\"true\"\nEGL=\"false\"\nGLES=\"false\"\nCHECK_ALLOC=\"true\"\nCOUNTER_HASH=\"true\"\nDEBYTECODE=\"false\"\nTRACE_LOGGING=\"false\"\nSTDIO_REDIRECT=\"false\"\nGAMECONTROLLERDB=\"true\"\nFPSCOUNTER=\"false\"\nLAYER_RENDERING=\"true\"\nDOS_SVGA=\"true\"\nDOS_ROOTS=\"false\"\nVFS=\"true\"\n\n#\n# User may override above settings\n#\nwhile [ \"$1\" != \"\" ]; do\n\t# e.g. --platform unix-devel\n\tif [ \"$1\" = \"--platform\" ]; then\n\t\tshift\n\t\tPLATFORM=\"$1\"\n\tfi\n\n\t# e.g. --prefix /usr\n\tif [ \"$1\" = \"--prefix\" ]; then\n\t\tshift\n\t\tPREFIX=\"$1\"\n\t\tPREFIX_IS_SET=\"true\"\n\tfi\n\n\t# e.g. --sysconfdir /etc\n\tif [ \"$1\" = \"--sysconfdir\" ]; then\n\t\tshift\n\t\tSYSCONFDIR=\"$1\"\n\t\tSYSCONFDIR_IS_SET=\"true\"\n\tfi\n\n\t# e.g. --gamesdir /usr/games\n\tif [ \"$1\" = \"--gamesdir\" ]; then\n\t\tshift\n\t\tGAMESDIR=\"$1\"\n\t\tGAMESDIR_IS_SET=\"true\"\n\tfi\n\n\t# e.g. --libdir /usr/lib\n\tif [ \"$1\" = \"--libdir\" ]; then\n\t\tshift\n\t\tLIBDIR=\"$1\"\n\t\tLIBDIR_IS_SET=\"true\"\n\tfi\n\n\t# e.g. --bindir /usr/bin\n\tif [ \"$1\" = \"--bindir\" ]; then\n\t\tshift\n\t\tBINDIR=\"$1\"\n\t\tBINDIR_IS_SET=\"true\"\n\tfi\n\n\t# e.g. --sharedir /usr/share\n\tif [ \"$1\" = \"--sharedir\" ]; then\n\t\tshift\n\t\tSHAREDIR=\"$1\"\n\t\tSHAREDIR_IS_SET=\"true\"\n\tfi\n\n\t# e.g. --licensedir /usr/share/licenses\n\tif [ \"$1\" = \"--licensedir\" ]; then\n\t\tshift\n\t\tLICENSEDIR=\"$1\"\n\t\tLICENSEDIR_IS_SET=\"true\"\n\tfi\n\n\t# e.g. --metainfodir /usr/share/metainfo\n\tif [ \"$1\" = \"--metainfodir\" ]; then\n\t\tshift\n\t\tMETAINFODIR=\"$1\"\n\t\tMETAINFODIR_IS_SET=\"true\"\n\tfi\n\n\t# e.g. --host arm-none-eabi\n\tif [ \"$1\" = \"--host\" ]; then\n\t\tshift\n\t\tTOOL_PREFIX=\"$1\"\n\tfi\n\n\t[ \"$1\" = \"--as-needed-hack\" ] && AS_NEEDED=\"true\"\n\n\t[ \"$1\" = \"--enable-release\" ]  && RELEASE=\"true\"\n\t[ \"$1\" = \"--disable-release\" ] && RELEASE=\"false\"\n\n\t[ \"$1\" = \"--optimize-size\" ]  && OPT_SIZE=\"true\"\n\t[ \"$1\" = \"--optimize-speed\" ] && OPT_SIZE=\"false\"\n\n\t[ \"$1\" = \"--enable-lto\" ]  && LTO=\"true\"\n\t[ \"$1\" = \"--disable-lto\" ] && LTO=\"false\"\n\n\t[ \"$1\" = \"--enable-asan\" ]  && SANITIZER=\"address\"\n\t[ \"$1\" = \"--disable-asan\" ] && SANITIZER=\"false\"\n\n\t[ \"$1\" = \"--enable-msan\" ]  && SANITIZER=\"memory\"\n\t[ \"$1\" = \"--disable-msan\" ] && SANITIZER=\"false\"\n\n\t[ \"$1\" = \"--enable-tsan\" ] &&  SANITIZER=\"thread\"\n\t[ \"$1\" = \"--disable-tsan\" ] && SANITIZER=\"false\"\n\n\t[ \"$1\" = \"--enable-ubsan\" ] &&  SANITIZER=\"undefined\"\n\t[ \"$1\" = \"--disable-ubsan\" ] && SANITIZER=\"false\"\n\n\t[ \"$1\" = \"--enable-analyzer\" ] &&  ANALYZER=\"true\"\n\t[ \"$1\" = \"--disable-analyzer\" ] && ANALYZER=\"false\"\n\n\t[ \"$1\" = \"--enable-stack-protector\" ]  && STACK_PROTECTOR=\"true\"\n\t[ \"$1\" = \"--disable-stack-protector\" ] && STACK_PROTECTOR=\"false\"\n\n\t[ \"$1\" = \"--enable-pledge\" ] &&  PLEDGE=\"true\"  && PLEDGE_UTILS=\"true\"\n\t[ \"$1\" = \"--disable-pledge\" ] && PLEDGE=\"false\" && PLEDGE_UTILS=\"false\"\n\n\t[ \"$1\" = \"--disable-datestamp\" ] && DATE_STAMP=\"false\"\n\t[ \"$1\" = \"--enable-datestamp\" ]  && DATE_STAMP=\"true\"\n\n\t[ \"$1\" = \"--disable-editor\" ] && EDITOR=\"false\"\n\t[ \"$1\" = \"--enable-editor\" ]  && EDITOR=\"true\"\n\n\t[ \"$1\" = \"--disable-mzxrun\" ] && MZXRUN=\"false\"\n\t[ \"$1\" = \"--enable-mzxrun\" ]  && MZXRUN=\"true\"\n\n\t[ \"$1\" = \"--disable-helpsys\" ] && HELPSYS=\"false\"\n\t[ \"$1\" = \"--enable-helpsys\" ]  && HELPSYS=\"true\"\n\n\t[ \"$1\" = \"--disable-utils\" ] && UTILS=\"false\"\n\t[ \"$1\" = \"--enable-utils\" ] && UTILS=\"true\"\n\n\t[ \"$1\" = \"--disable-x11\" ] && X11=\"false\"\n\t[ \"$1\" = \"--enable-x11\" ]  && X11=\"true\"\n\n\t[ \"$1\" = \"--disable-software\" ] && SOFTWARE=\"false\"\n\t[ \"$1\" = \"--enable-software\" ]  && SOFTWARE=\"true\"\n\n\t[ \"$1\" = \"--disable-softscale\" ] && SOFTSCALE=\"false\"\n\t[ \"$1\" = \"--enable-softscale\" ]  && SOFTSCALE=\"true\"\n\n\t[ \"$1\" = \"--disable-sdlaccel\" ] && SDLACCEL=\"false\"\n\t[ \"$1\" = \"--enable-sdlaccel\" ]  && SDLACCEL=\"true\"\n\n\t[ \"$1\" = \"--disable-gl\" ] && GL=\"false\"\n\t[ \"$1\" = \"--enable-gl\" ]  && GL=\"true\"\n\n\t[ \"$1\" = \"--disable-gl-fixed\" ] && GL_FIXED=\"false\"\n\t[ \"$1\" = \"--enable-gl-fixed\" ]  && GL_FIXED=\"true\"\n\n\t[ \"$1\" = \"--disable-gl-prog\" ] && GL_PROGRAM=\"false\"\n\t[ \"$1\" = \"--enable-gl-prog\" ]  && GL_PROGRAM=\"true\"\n\n\t[ \"$1\" = \"--disable-overlay\" ] && OVERLAY=\"false\"\n\t[ \"$1\" = \"--enable-overlay\" ]  && OVERLAY=\"true\"\n\n\t[ \"$1\" = \"--disable-gp2x\" ] && GP2X=\"false\"\n\t[ \"$1\" = \"--enable-gp2x\" ]  && GP2X=\"true\"\n\n\t[ \"$1\" = \"--disable-screenshots\" ] && SCREENSHOTS=\"false\"\n\t[ \"$1\" = \"--enable-screenshots\" ]  && SCREENSHOTS=\"true\"\n\n\t[ \"$1\" = \"--disable-modplug\" ] && MODPLUG=\"false\"\n\t[ \"$1\" = \"--enable-modplug\" ]  && MODPLUG=\"true\"\n\n\t[ \"$1\" = \"--disable-mikmod\" ] && MIKMOD=\"false\"\n\t[ \"$1\" = \"--enable-mikmod\" ]  && MIKMOD=\"true\"\n\n\t[ \"$1\" = \"--disable-xmp\" ] && XMP=\"false\"\n\t[ \"$1\" = \"--enable-xmp\" ]  && XMP=\"true\"\n\n\t[ \"$1\" = \"--disable-openmpt\" ] && OPENMPT=\"false\"\n\t[ \"$1\" = \"--enable-openmpt\" ]  && OPENMPT=\"true\"\n\n\t[ \"$1\" = \"--disable-rad\" ] && REALITY=\"false\"\n\t[ \"$1\" = \"--enable-rad\" ]  && REALITY=\"true\"\n\n\t[ \"$1\" = \"--disable-libpng\" ] && LIBPNG=\"false\"\n\t[ \"$1\" = \"--enable-libpng\" ]  && LIBPNG=\"true\"\n\n\t[ \"$1\" = \"--disable-audio\" ] && AUDIO=\"false\"\n\t[ \"$1\" = \"--enable-audio\" ]  && AUDIO=\"true\"\n\n\t[ \"$1\" = \"--disable-vorbis\" ] && VORBIS=\"false\"\n\t[ \"$1\" = \"--enable-vorbis\" ]  && VORBIS=\"true\"\n\n\t[ \"$1\" = \"--disable-tremor\" ] && VORBIS=\"true\"\n\t[ \"$1\" = \"--enable-tremor\" ]  && VORBIS=\"tremor\"\n\n\t[ \"$1\" = \"--disable-tremor-lowmem\" ] && VORBIS=\"true\"\n\t[ \"$1\" = \"--enable-tremor-lowmem\" ]  && VORBIS=\"tremor-lowmem\"\n\n\t[ \"$1\" = \"--disable-stb-vorbis\" ] && VORBIS=\"true\"\n\t[ \"$1\" = \"--enable-stb-vorbis\" ]  && VORBIS=\"stb_vorbis\"\n\n\t[ \"$1\" = \"--disable-pthread\" ] && PTHREAD=\"false\"\n\t[ \"$1\" = \"--enable-pthread\" ]  && PTHREAD=\"true\"\n\n\t[ \"$1\" = \"--disable-icon\" ] && ICON=\"false\"\n\t[ \"$1\" = \"--enable-icon\" ]  && ICON=\"true\"\n\n\t[ \"$1\" = \"--disable-modular\" ] && MODULAR=\"false\"\n\t[ \"$1\" = \"--enable-modular\" ]  && MODULAR=\"true\"\n\n\t[ \"$1\" = \"--disable-updater\" ] && UPDATER=\"false\"\n\t[ \"$1\" = \"--enable-updater\" ]  && UPDATER=\"true\"\n\n\t[ \"$1\" = \"--disable-network\" ] && NETWORK=\"false\"\n\t[ \"$1\" = \"--enable-network\" ]  && NETWORK=\"true\"\n\n\t[ \"$1\" = \"--disable-getaddrinfo\" ] && GETADDRINFO=\"false\"\n\t[ \"$1\" = \"--enable-getaddrinfo\" ]  && GETADDRINFO=\"true\"\n\n\t[ \"$1\" = \"--disable-poll\" ] && POLL=\"false\"\n\t[ \"$1\" = \"--enable-poll\" ]  && POLL=\"true\"\n\n\t[ \"$1\" = \"--disable-ipv6\" ] && IPV6=\"false\"\n\t[ \"$1\" = \"--enable-ipv6\" ]  && IPV6=\"true\"\n\n\t[ \"$1\" = \"--disable-verbose\" ] && VERBOSE=\"false\"\n\t[ \"$1\" = \"--enable-verbose\" ]  && VERBOSE=\"true\"\n\n\t[ \"$1\" = \"--enable-extram\" ]  && EXTRAM=\"true\"\n\t[ \"$1\" = \"--disable-extram\" ] && EXTRAM=\"false\"\n\n\t[ \"$1\" = \"--enable-meter\" ]  && METER=\"true\"\n\t[ \"$1\" = \"--disable-meter\" ] && METER=\"false\"\n\n\t[ \"$1\" = \"--disable-sdl\" ] && SDL=\"false\"\n\t[ \"$1\" = \"--enable-sdl\" ]  && SDL=\"true\"\n\t[ \"$1\" = \"--enable-sdl1\" ] && SDL=\"1\"\n\t[ \"$1\" = \"--enable-sdl2\" ] && SDL=\"2\"\n\t[ \"$1\" = \"--enable-sdl3\" ] && SDL=\"3\"\n\n\t[ \"$1\" = \"--enable-egl\" ]  && EGL=\"true\"\n\t[ \"$1\" = \"--disable-egl\" ] && EGL=\"false\"\n\n\t[ \"$1\" = \"--enable-gles\" ]  && GLES=\"true\"\n\t[ \"$1\" = \"--disable-gles\" ] && GLES=\"false\"\n\n\t[ \"$1\" = \"--disable-check-alloc\" ] && CHECK_ALLOC=\"false\"\n\t[ \"$1\" = \"--enable-check-alloc\" ]  && CHECK_ALLOC=\"true\"\n\n\t[ \"$1\" = \"--enable-counter-hash\" ]  && COUNTER_HASH=\"true\"\n\t[ \"$1\" = \"--disable-counter-hash\" ] && COUNTER_HASH=\"false\"\n\n\t[ \"$1\" = \"--enable-vfs\" ]  && VFS=\"true\"\n\t[ \"$1\" = \"--disable-vfs\" ] && VFS=\"false\"\n\n\t[ \"$1\" = \"--enable-debytecode\" ]  && DEBYTECODE=\"true\"\n\t[ \"$1\" = \"--disable-debytecode\" ] && DEBYTECODE=\"false\"\n\n\t# TODO: compatibility options, eventually remove.\n\t[ \"$1\" = \"--enable-libsdl2\" ]  && SDL=\"2\"\n\t[ \"$1\" = \"--disable-libsdl2\" ] && SDL=\"1\"\n\n\t[ \"$1\" = \"--enable-trace\" ]  && TRACE_LOGGING=\"true\"\n\t[ \"$1\" = \"--disable-trace\" ] && TRACE_LOGGING=\"false\"\n\n\t[ \"$1\" = \"--enable-stdio-redirect\" ]  && STDIO_REDIRECT=\"true\"\n\t[ \"$1\" = \"--disable-stdio-redirect\" ] && STDIO_REDIRECT=\"false\"\n\n\t[ \"$1\" = \"--enable-fps\" ]  && FPSCOUNTER=\"true\"\n\t[ \"$1\" = \"--disable-fps\" ] && FPSCOUNTER=\"false\"\n\n\t[ \"$1\" = \"--enable-dos-svga\" ]  && DOS_SVGA=\"true\"\n\t[ \"$1\" = \"--disable-dos-svga\" ] && DOS_SVGA=\"false\"\n\n\tif [ \"$1\" = \"--help\" ]; then\n\t\tusage\n\t\texit 0\n\tfi\n\n\tshift\ndone\n\n#\n# Platform cannot be sanely omitted\n#\nif [ \"$PLATFORM\" = \"\" ]; then\n\tusage\n\texit 0\nfi\n\n#\n# Derive unspecified install dirs from specified dirs.\n#\nif [ \"$BINDIR_IS_SET\" = \"false\" ]; then\n\tBINDIR=\"${PREFIX}${BINDIR_IN_PREFIX}\"\nfi\nif [ \"$GAMESDIR_IS_SET\" = \"false\" ]; then\n\tGAMESDIR=\"${PREFIX}${GAMESDIR_IN_PREFIX}\"\nfi\nif [ \"$LIBDIR_IS_SET\" = \"false\" ]; then\n\tLIBDIR=\"${PREFIX}${LIBDIR_IN_PREFIX}\"\nfi\nif [ \"$SHAREDIR_IS_SET\" = \"false\" ]; then\n\tSHAREDIR=\"${PREFIX}${SHAREDIR_IN_PREFIX}\"\nfi\nif [ \"$LICENSEDIR_IS_SET\" = \"false\" ]; then\n\tLICENSEDIR=\"${SHAREDIR}${LICENSEDIR_IN_SHAREDIR}\"\nfi\nif [ \"$METAINFODIR_IS_SET\" = \"false\" ]; then\n\tMETAINFODIR=\"${SHAREDIR}${METAINFODIR_IN_SHAREDIR}\"\nfi\n\n### PLATFORM DEFINITION #######################################################\n\nrm -f platform.inc\n\nif [ \"$PLATFORM\" = \"win32\"   ] || [ \"$PLATFORM\" = \"win64\" ] ||\n   [ \"$PLATFORM\" = \"mingw32\" ] || [ \"$PLATFORM\" = \"mingw64\" ]; then\n\t# Auto-prefix for the MSYS2 MINGW32/MINGW64 environments if a prefix wasn't\n\t# provided. This helps avoid errors that occur when gcc or libs exist in\n\t# /usr, which is used by MSYS2 for the MSYS environment.\n\tif [ \"$PREFIX_IS_SET\" = \"false\" ] && [ -n \"$MSYSTEM\" ] &&\n\t   [ \"$(uname -o)\" = \"Msys\" ] && [ \"$MSYSTEM\" != \"MSYS\" ]; then\n\t\tcase \"$MSYSTEM\" in\n\t\t  \"MINGW32\"|\"MINGW64\")\n\t\t\t[ \"$PLATFORM\" = \"win32\" ] && PREFIX=\"/mingw32\"\n\t\t\t[ \"$PLATFORM\" = \"win64\" ] && PREFIX=\"/mingw64\"\n\t\t\t;;\n\t\t  \"CLANG32\"|\"CLANG64\")\n\t\t\t[ \"$PLATFORM\" = \"win32\" ] && PREFIX=\"/clang32\"\n\t\t\t[ \"$PLATFORM\" = \"win64\" ] && PREFIX=\"/clang64\"\n\t\t\t;;\n\t\t  \"CLANGARM64\")\n\t\t\t[ \"$PLATFORM\" = \"win64\" ] && PREFIX=\"/clangarm64\"\n\t\t\t;;\n\t\t  \"UCRT64\")\n\t\t\t[ \"$PLATFORM\" = \"win64\" ] && PREFIX=\"/ucrt64\"\n\t\t\t;;\n\t\t  *)\n\t\t\techo \"WARNING: Unknown MSYSTEM '$MSYSTEM'!\"\n\t\t\t;;\n\t\tesac\n\tfi\n\n\t[ \"$PLATFORM\" = \"win32\" ] || [ \"$PLATFORM\" = \"mingw32\" ] && ARCHNAME=x86\n\t[ \"$PLATFORM\" = \"win64\" ] || [ \"$PLATFORM\" = \"mingw64\" ] && ARCHNAME=x64\n\t[ \"$PLATFORM\" = \"mingw32\" ] && [ \"$TOOL_PREFIX\" = \"\" ] && TOOL_PREFIX=i686-w64-mingw32\n\t[ \"$PLATFORM\" = \"mingw64\" ] && [ \"$TOOL_PREFIX\" = \"\" ] && TOOL_PREFIX=x86_64-w64-mingw32\n\tPLATFORM=\"mingw\"\n\techo \"#define PLATFORM \\\"windows-$ARCHNAME\\\"\" > src/config.h\n\techo \"SUBPLATFORM=windows-$ARCHNAME\"         >> platform.inc\n\techo \"PLATFORM=$PLATFORM\"                    >> platform.inc\nelif [ \"$PLATFORM\" = \"unix\" ] || [ \"$PLATFORM\" = \"unix-devel\" ] ||\n     [ \"$PLATFORM\" = \"darwin\" ] || [ \"$PLATFORM\" = \"darwin-devel\" ]; then\n\tOS=\"$(uname -s)\"\n\tMACH=\"$(uname -m)\"\n\tDIRNAME=unix\n\n\tcase \"$OS\" in\n\t\t\"Linux\")\n\t\t\tUNIX=\"linux\"\n\t\t\t;;\n\t\t\"FreeBSD\")\n\t\t\tUNIX=\"freebsd\"\n\t\t\t;;\n\t\t\"OpenBSD\")\n\t\t\tUNIX=\"openbsd\"\n\t\t\t;;\n\t\t\"NetBSD\")\n\t\t\tUNIX=\"netbsd\"\n\t\t\t;;\n\t\t\"DragonFly\")\n\t\t\tUNIX=\"dragonflybsd\"\n\t\t\t;;\n\t\t\"Haiku\")\n\t\t\tUNIX=\"haiku\"\n\t\t\t;;\n\t\t\"Darwin\")\n\t\t\tUNIX=\"darwin\"\n\t\t\tDIRNAME=\"darwin\"\n\t\t\t;;\n\t\t*)\n\t\t\techo \"WARNING: Should define proper UNIX name here!\"\n\t\t\tUNIX=\"unix\"\n\t\t\t;;\n\tesac\n\n\tif [ \"$MACH\" = \"x86_64\" ] || [ \"$MACH\" = \"amd64\" ]; then\n\t\tARCHNAME=amd64\n\t\t#RAWLIBDIR=lib64\n\t\t# FreeBSD amd64 hack\n\t\t#[ \"$UNIX\" = \"freebsd\" ] && RAWLIBDIR=lib\n\telif [ \"$(echo \"$MACH\" | sed 's,i.86,x86,')\" = \"x86\" ]; then\n\t\tARCHNAME=x86\n\t\t#RAWLIBDIR=lib\n\telif [ \"$MACH\" = \"aarch64\" ] || [ \"$MACH\" = \"arm64\" ]; then\n\t\tARCHNAME=aarch64\n\telif [ \"$(echo \"$MACH\" | sed 's,^arm.*,arm,')\" = \"arm\" ]; then\n\t\tARCHNAME=arm\n\t\t#RAWLIBDIR=lib\n\telif [ \"$MACH\" = \"ppc\" ]; then\n\t\tARCHNAME=ppc\n\t\t#RAWLIBDIR=lib\n\telif [ \"$MACH\" = \"ppc64\" ]; then\n\t\tARCHNAME=ppc64\n\telif [ \"$MACH\" = \"mips\" ]; then\n\t\tARCHNAME=mips\n\telif [ \"$MACH\" = \"mips64\" ]; then\n\t\tARCHNAME=mips64\n\telif [ \"$MACH\" = \"m68k\" ]; then\n\t\tARCHNAME=m68k\n\telif [ \"$MACH\" = \"alpha\" ]; then\n\t\tARCHNAME=alpha\n\telif [ \"$MACH\" = \"hppa\" ] || [ \"$MACH\" = \"parisc\" ]; then\n\t\tARCHNAME=hppa\n\telif [ \"$MACH\" = \"sh4\" ]; then\n\t\tARCHNAME=sh4\n\telif [ \"$MACH\" = \"sparc\" ]; then\n\t\tARCHNAME=sparc\n\telif [ \"$MACH\" = \"sparc64\" ]; then\n\t\tARCHNAME=sparc64\n\telif [ \"$MACH\" = \"riscv64\" ]; then\n\t\tARCHNAME=riscv64\n\telif [ \"$MACH\" = \"s390\" ]; then\n\t\tARCHNAME=s390\n\telif [ \"$MACH\" = \"s390x\" ]; then\n\t\tARCHNAME=s390x\n\telif [ \"$MACH\" = \"loong64\" ] || [ \"$MACH\" = \"loongarch64\" ]; then\n\t\tARCHNAME=loong64\n\telse\n\t\tARCHNAME=$MACH\n\t\t#RAWLIBDIR=lib\n\t\techo \"WARNING: Compiling on an unsupported architecture. Add a friendly MACH to config.sh.\"\n\tfi\n\n\techo \"#define PLATFORM \\\"$UNIX-$ARCHNAME\\\"\" > src/config.h\n\techo \"SUBPLATFORM=$UNIX-$ARCHNAME\"         >> platform.inc\n\techo \"PLATFORM=$DIRNAME\"                   >> platform.inc\nelif [ \"$PLATFORM\" = \"darwin-dist\" ]; then\n\t# Multiarchitecture build--let the Makefile patch in a subplatform.\n\n\techo \"#define PLATFORM \\\"darwin-\\\" SUBPLATFORM\"  > src/config.h\n\techo \"SUBPLATFORM=$PLATFORM\"                    >> platform.inc\n\techo \"PLATFORM=darwin\"                          >> platform.inc\nelif [ \"$PLATFORM\" = \"android\" ]; then\n\t# Multiarchitecture build--let the Makefile patch in a subplatform.\n\n\techo \"#define PLATFORM \\\"android-\\\" SUBPLATFORM\" > src/config.h\n\techo \"SUBPLATFORM=$PLATFORM\"                    >> platform.inc\n\techo \"PLATFORM=$PLATFORM\"                       >> platform.inc\nelse\n\tif [ ! -d \"arch/$PLATFORM\" ]; then\n\t\techo \"Invalid platform selection (see arch/).\"\n\t\texit 1\n\tfi\n\n\techo \"#define PLATFORM \\\"$PLATFORM\\\"\" > src/config.h\n\techo \"SUBPLATFORM=$PLATFORM\"         >> platform.inc\n\techo \"PLATFORM=$PLATFORM\"            >> platform.inc\nfi\n\necho \"PREFIX:=$PREFIX\" >> platform.inc\n\nif [ \"$TOOL_PREFIX\" != \"\" ]; then\n\techo \"CROSS_COMPILE?=$TOOL_PREFIX-\" >> platform.inc\nfi\n\n### SYSTEM DIRECTORIES ########################################################\n\n#\n# Some platforms (currently \"unix\", \"darwin\") are system installations.\n# Their assets and licenses are located in subdirectories of their\n# respective filesystem hierarchies. For other platforms, these files are\n# located in the base directory of the archive (the executable location).\n# These locations need to be hardcoded for various console platforms.\n#\ncase \"$PLATFORM\" in\n\t# Set default directories for non-install platforms.\n\t# Most directories are irrelevant outside of install platforms.\n\t# Most of the time, only SHAREDIR needs to be overriden.\n\t\"unix\" | \"darwin\")\n\t\t;;\n\t*)\n\t\tGAMESDIR=\".\"\n\t\tBINDIR=\".\"\n\t\tLIBDIR=\".\"\n\t\tSHAREDIR=\".\"\n\t\tLICENSEDIR=\".\"\n\t\tMETAINFODIR=\".\"\n\t\tif [ \"$SYSCONFDIR_IS_SET\" != \"true\" ]; then\n\t\t\tSYSCONFDIR=\".\"\n\t\tfi\n\t\t;;\nesac\ncase \"$PLATFORM\" in\n\t# Set custom per-platform paths.\n\t\"unix\" | \"darwin\")\n\t\tLIBDIR=\"${LIBDIR}/megazeux\"\n\t\tUSERCONFFILE=\".megazeux-config\"\n\t\t;;\n\t\"nds\" | \"nds-blocksds\")\n\t\tSHAREDIR=/games/megazeux\n\t\t;;\n\t\"3ds\")\n\t\tSHAREDIR=/3ds/megazeux\n\t\t;;\n\t\"dreamcast\")\n\t\tSHAREDIR=/cd\n\t\t;;\n\t\"wii\")\n\t\tSHAREDIR=/apps/megazeux\n\t\t;;\n\t\"wiiu\")\n\t\tSHAREDIR=fs:/vol/external01/wiiu/apps/megazeux\n\t\t;;\n\t\"switch\")\n\t\tSHAREDIR=/switch/megazeux\n\t\t;;\n\t\"psvita\")\n\t\tSHAREDIR=\"ux0:/data/megazeux\"\n\t\t;;\n\t\"darwin-dist\")\n\t\tSHAREDIR=\"../Resources\"\n\t\tLICENSEDIR=\"../Resources\"\n\t\tSYSCONFDIR=\"../Resources\"\n\t\tUSERCONFFILE=\".megazeux-config\"\n\t\t;;\n\t\"emscripten\")\n\t\tSHAREDIR=\"/data\"\n\t\tSYSCONFDIR=\"/data/etc\"\n\t\t;;\n\t\"android\")\n\t\tSTARTUPDIR=\"/storage/emulated/0\"\n\t\t;;\nesac\n\n### SUMMARY OF OPTIONS ########################################################\n\necho \"Building for platform:   $PLATFORM\"\necho \"Using prefix:            $PREFIX\"\necho \"      sharedir:          $SHAREDIR\"\necho \"      licensedir:        $LICENSEDIR\"\nif [ \"$METAINFODIR\" != \".\" ]; then\necho \"      metainfodir:       $METAINFODIR\"\nfi\necho \"      sysconfdir:        $SYSCONFDIR\"\nif [ \"$GAMESDIR\" != \".\" ]; then\necho \"      gamesdir (mzx):    $GAMESDIR\"\nfi\nif [ \"$BINDIR\" != \".\" ]; then\necho \"      bindir (utils):    $BINDIR\"\nfi\nif [ \"$LIBDIR\" != \".\" ]; then\necho \"      libdir:            $LIBDIR\"\nfi\nif [ -n \"$STARTUPDIR\" ]; then\necho \"      startupdir:        $STARTUPDIR\"\nfi\nif [ \"$TOOL_PREFIX\" != \"\" ]; then\necho \"Using host:              $TOOL_PREFIX\"\nfi\necho\n\n### GENERATE CONFIG.H HEADER ##################################################\n\n. ./version.inc\n\n#\n# Set the version to build with\n#\n\necho \"#define VERSION \\\"$VERSION\\\"\" >> src/config.h\n\nif [ \"$DATE_STAMP\" = \"true\" ]; then\n\techo \"Stamping version with today's date.\"\n\techo \"#define VERSION_DATE \\\" ($(date -u +%Y%m%d))\\\"\" >> src/config.h\nelse\n\techo \"Not stamping version with today's date.\"\nfi\n\nif command -v git >/dev/null 2>&1 && git rev-parse --short HEAD >/dev/null 2>&1; then\n\techo \"#define VERSION_HEAD \\\"$(git rev-parse --short HEAD)\\\"\" >> src/config.h\nfi\n\nif [ \"$PRERELEASE\" = \"1\" ]; then\n\techo \"#define VERSION_PRERELEASE\" >> src/config.h\nfi\n\necho \"#define CONFDIR \\\"$SYSCONFDIR/\\\"\" >> src/config.h\n\nif [ \"$PLATFORM\" = \"unix\" ] || [ \"$PLATFORM\" = \"darwin\" ]; then\n\techo \"#define CONFFILE \\\"megazeux-config\\\"\"\t\t>> src/config.h\n\techo \"#define SHAREDIR \\\"$SHAREDIR/megazeux/\\\"\"\t\t>> src/config.h\n\techo \"#define LICENSEDIR \\\"$LICENSEDIR/megazeux/\\\"\"\t>> src/config.h\nelse\n\techo \"#define CONFFILE \\\"config.txt\\\"\"\t\t\t>> src/config.h\n\techo \"#define SHAREDIR \\\"$SHAREDIR\\\"\"\t\t\t>> src/config.h\n\techo \"#define LICENSEDIR \\\"$LICENSEDIR\\\"\"\t\t>> src/config.h\nfi\n\nif [ -n \"$USERCONFFILE\" ]; then\n\techo \"#define USERCONFFILE \\\"$USERCONFFILE\\\"\"\t\t>> src/config.h\nfi\n\nif [ -n \"$STARTUPDIR\" ]; then\n\techo \"#define STARTUPDIR \\\"$STARTUPDIR\\\"\"\t\t>> src/config.h\nfi\n\n#\n# LIBDIR is required by several platforms that support modular builds.\n# Some architectures define an \"install\" target, and need the rest.\n#\necho \"SYSCONFDIR=$SYSCONFDIR\" >> platform.inc\necho \"GAMESDIR=$GAMESDIR\"     >> platform.inc\necho \"LIBDIR=$LIBDIR\"         >> platform.inc\necho \"BINDIR=$BINDIR\"         >> platform.inc\necho \"SHAREDIR=$SHAREDIR\"     >> platform.inc\necho \"LICENSEDIR=$LICENSEDIR\" >> platform.inc\necho \"METAINFODIR=$METAINFODIR\" >> platform.inc\n\n\n### PLATFORM-SPECIFIC FEATURE CONFIG ##########################################\n\ncase \"$PLATFORM\" in\n\"darwin\"*)\n\techo \"Force-disabling runtime icon loading (macOS).\"\n\tICON=\"false\"\n\n\techo \"Force-disabling poll (macOS).\"\n\tPOLL=\"false\"\n\t;;\n\n\"amiga\")\n\techo \"Force-disabling getaddrinfo, poll, and IPv6 (Amiga).\"\n\tGETADDRINFO=\"false\"\n\tPOLL=\"false\"\n\tIPV6=\"false\"\n\t;;\n\n\"android\")\n\techo \"Force-disabling modular build (Android).\"\n\tMODULAR=\"false\"\n\n\techo \"Force-enabling OpenGL ES support (Android).\"\n\tGLES=\"true\"\n\n\techo \"Force-disabling runtime icon loading (Android).\"\n\tICON=\"false\"\n\t;;\n\n\"emscripten\")\n\techo \"Enabling Emscripten-specific hacks.\"\n\techo \"BUILD_EMSCRIPTEN=1\" >> platform.inc\n\n\techo \"Force-disabling modular build (Emscripten).\"\n\tMODULAR=\"false\"\n\n\techo \"Force-disabling stack protector (Emscripten).\"\n\tSTACK_PROTECTOR=\"false\"\n\n\techo \"Force-disabling runtime icon loading (Emscripten).\"\n\tICON=\"false\"\n\n\techo \"Force-disabling virtual filesystem (Emscripten).\"\n\tVFS=\"false\"\n\n\tEDITOR=\"false\"\n\tSCREENSHOTS=\"false\"\n\tUPDATER=\"false\"\n\tUTILS=\"false\"\n\n\tGLES=\"true\"\n\tGL_FIXED=\"false\"\n\t;;\n\n\"nds\"*)\n\techo \"Enabling NDS-specific hacks.\"\n\techo \"#define CONFIG_NDS\" >> src/config.h\n\techo \"BUILD_NDS=1\" >> platform.inc\n\tDOS_ROOTS=\"true\"\n\n\tif [ \"$PLATFORM\" = \"nds-blocksds\" ]; then\n\t\techo \"Enabling BlocksDS-specific hacks.\"\n\t\techo \"#define CONFIG_NDS_BLOCKSDS\" >> src/config.h\n\t\techo \"BUILD_NDS_BLOCKSDS=1\" >> platform.inc\n\tfi\n\n\techo \"Force-disabling modular build on NDS.\"\n\tMODULAR=\"false\"\n\n\techo \"Building custom NDS renderer.\"\n\n\techo \"Force-disabling SDL on NDS.\"\n\tSDL=\"false\"\n\tSOFTWARE=\"false\"\n\n\techo \"Force-disabling runtime icon loading on NDS.\"\n\tICON=\"false\"\n\n\techo \"Force-disabling hash tables on NDS.\"\n\tCOUNTER_HASH=\"false\"\n\n\techo \"Force-disabling layer rendering on NDS.\"\n\tLAYER_RENDERING=\"false\"\n\n\techo \"Force-disabling virtual filesystem on NDS.\"\n\tVFS=\"false\"\n\n\techo \"Force-disabling existing music playback libraries on NDS.\"\n\tMODPLUG=\"false\"\n\tMIKMOD=\"false\"\n\tXMP=\"false\"\n\tOPENMPT=\"false\"\n\tREALITY=\"false\"\n\tVORBIS=\"false\"\n\n\tif [ \"$PLATFORM\" = \"nds\" ]; then\n\t\techo \"Force-disabling stack protector on NDS.\"\n\t\tSTACK_PROTECTOR=\"false\"\n\tfi\n\n\techo \"Force-disabling networking on NDS.\"\n\tNETWORK=\"false\"\n\t;;\n\n\"3ds\")\n\techo \"Enabling 3DS-specific hacks.\"\n\techo \"#define CONFIG_3DS\" >> src/config.h\n\techo \"BUILD_3DS=1\" >> platform.inc\n\tDOS_ROOTS=\"true\"\n\n\techo \"Force-disabling modular build on 3DS.\"\n\tMODULAR=\"false\"\n\n\t#\n\t# If the 3DS arch is enabled and SDL 1.2 is used, softscale is not\n\t# available. On SDL 2.0, due to the rendering pipeline not supporting\n\t# hardware acceleration as of writing, it is available, but with\n\t# unsatisfactory performance. As a workaround, use the GP2X\n\t# 320x240 renderer.\n\t#\n\tif [ \"$SDL\" != \"false\" ]; then\n\t\techo \"Force-disabling overlay and OpenGL renderers (3DS).\"\n\t\tOVERLAY=\"false\"\n\t\tGL=\"false\"\n\n\t\techo \"Force-enabling GP2X 320x240 renderer (SDL on 3DS).\"\n\t\tGP2X=\"true\"\n\telse\n\t\techo \"Force-disabling software renderer on 3DS.\"\n\t\techo \"Building custom 3DS renderer.\"\n\t\tSOFTWARE=\"false\"\n\tfi\n\n\techo \"Force-disabling stack protector on 3DS.\"\n\tSTACK_PROTECTOR=\"false\"\n\n\techo \"Force-disabling runtime icon loading on 3DS.\"\n\tICON=\"false\"\n\n\techo \"Disabling utils on 3DS.\"\n\tUTILS=\"false\"\n\n\techo \"Force-disabling IPv6 on 3DS (not implemented).\"\n\tIPV6=\"false\"\n\t;;\n\n\"wii\")\n\techo \"Enabling Wii-specific hacks.\"\n\techo \"#define CONFIG_WII\" >> src/config.h\n\techo \"BUILD_WII=1\" >> platform.inc\n\tDOS_ROOTS=\"true\"\n\n\techo \"Force-disabling modular build on Wii.\"\n\tMODULAR=\"false\"\n\n\tif [ \"$SDL\" = \"false\" ]; then\n\t\techo \"Force-disabling software renderer on Wii.\"\n\t\techo \"Building custom Wii renderers.\"\n\t\tSOFTWARE=\"false\"\n\telse\n\t\techo \"Force-disabling overlay and OpenGL renderers on Wii.\"\n\t\tOVERLAY=\"false\"\n\t\tGL=\"false\"\n\tfi\n\n\techo \"Force-disabling stack protector on Wii.\"\n\tSTACK_PROTECTOR=\"false\"\n\n\techo \"Force-disabling runtime icon loading on Wii.\"\n\tICON=\"false\"\n\n\techo \"Force-disabling utils on Wii.\"\n\tUTILS=\"false\"\n\n\techo \"Force-disabling getaddrinfo and IPv6 on Wii.\"\n\tGETADDRINFO=\"false\"\n\tIPV6=\"false\"\n\t;;\n\n\"wiiu\")\n\techo \"Enabling Wii U-specific hacks.\"\n\techo \"#define CONFIG_WIIU\" >> src/config.h\n\techo \"BUILD_WIIU=1\" >> platform.inc\n\tDOS_ROOTS=\"true\"\n\n\techo \"Force-disabling modular build on Wii U.\"\n\tMODULAR=\"false\"\n\n\techo \"Force-disabling overlay and OpenGL renderers on Wii U.\"\n\tOVERLAY=\"false\"\n\tGL=\"false\"\n\n\techo \"Force-disabling runtime icon loading on Wii U.\"\n\tICON=\"false\"\n\n\techo \"Disabling utils on Wii U.\"\n\tUTILS=\"false\"\n\n\t# Doesn't seem to be fully populated on the Wii U.\n\tGAMECONTROLLERDB=\"false\"\n\t;;\n\n\"switch\")\n\techo \"Enabling Switch-specific hacks.\"\n\techo \"#define CONFIG_SWITCH\" >> src/config.h\n\techo \"BUILD_SWITCH=1\" >> platform.inc\n\tDOS_ROOTS=\"true\"\n\n\techo \"Force-disabling modular build on Switch.\"\n\tMODULAR=\"false\"\n\n\techo \"Force-disabling runtime icon loading on Switch.\"\n\tICON=\"false\"\n\n\techo \"Disabling utils on Switch.\"\n\tUTILS=\"false\"\n\n\techo \"Force-enabling OpenGL ES support (Switch).\"\n\tGLES=\"true\"\n\n\techo \"Force-disabling IPv6 on Switch (FIXME getaddrinfo seems to not support it).\"\n\tIPV6=\"false\"\n\n\t# This may or may not be totally useless for the Switch, disable it for now.\n\tGAMECONTROLLERDB=\"false\"\n\t;;\n\n\"psp\")\n\techo \"Enabling PSP-specific hacks.\"\n\techo \"#define CONFIG_PSP\" >> src/config.h\n\techo \"BUILD_PSP=1\" >> platform.inc\n\tDOS_ROOTS=\"true\"\n\n\techo \"Force-disabling modular build on PSP.\"\n\tMODULAR=\"false\"\n\n\techo \"Force-disabling OpenGL and overlay renderers on PSP.\"\n\tGL=\"false\"\n\tOVERLAY=\"false\"\n\n\techo \"Force-disabling stack protector on PSP.\"\n\tSTACK_PROTECTOR=\"false\"\n\n\techo \"Force-disabling runtime icon loading on PSP.\"\n\tICON=\"false\"\n\n\techo \"Force-disabling getaddrinfo, poll, and IPv6 on PSP.\"\n\tGETADDRINFO=\"false\"\n\tPOLL=\"false\"\n\tIPV6=\"false\"\n\t;;\n\n\"psvita\")\n\techo \"Enabling PS Vita-specific hacks.\"\n\techo \"#define CONFIG_PSVITA\" >> src/config.h\n\techo \"BUILD_PSVITA=1\" >> platform.inc\n\tDOS_ROOTS=\"true\"\n\n\techo \"Force-disabling modular build on PS Vita.\"\n\tMODULAR=\"false\"\n\n\techo \"Force-disabling utils on PS Vita.\"\n\tUTILS=\"false\"\n\n\techo \"Force-disabling stack protector on PS Vita.\"\n\tSTACK_PROTECTOR=\"false\"\n\n\techo \"Force-disabling runtime icon loading on PS Vita.\"\n\tICON=\"false\"\n\t;;\n\n\"djgpp\")\n\techo \"Enabling DOS-specific hacks.\"\n\techo \"#define CONFIG_DJGPP\" >> src/config.h\n\techo \"BUILD_DJGPP=1\" >> platform.inc\n\n\techo \"Force-disabling modular build (DOS).\"\n\tMODULAR=\"false\"\n\n\techo \"Force-disabling SDL (DOS).\"\n\tSDL=\"false\"\n\tSOFTWARE=\"false\"\n\n\techo \"Force-disabling stack protector (DOS).\"\n\tSTACK_PROTECTOR=\"false\"\n\n\techo \"Force-disabling runtime icon loading (DOS).\"\n\tICON=\"false\"\n\n\tif [ \"$VORBIS\" != \"false\" ] && [ \"$VORBIS\" != \"stb_vorbis\" ]; then\n\t\techo \"Force-switching ogg/vorbis to stb_vorbis.\"\n\t\tVORBIS=\"stb_vorbis\"\n\tfi\n\n\tif [ \"$DOS_SVGA\" = \"true\" ]; then\n\t\techo \"#define CONFIG_DOS_SVGA\" >> src/config.h\n\t\techo \"BUILD_DOS_SVGA=1\" >> platform.inc\n\t\techo \"SVGA software renderer enabled.\"\n\telse\n\t\techo \"SVGA software renderer disabled.\"\n\tfi\n\n\techo \"Force-disabling networking (DOS).\"\n\tNETWORK=\"false\"\n\t;;\n\n\"dreamcast\")\n\techo \"Enabling Dreamcast-specific hacks.\"\n\techo \"#define CONFIG_DREAMCAST\" >> src/config.h\n\techo \"BUILD_DREAMCAST=1\" >> platform.inc\n\n\techo \"Force-disabling modular build (Dreamcast).\"\n\tMODULAR=\"false\"\n\n\techo \"Force-disabling SDL (Dreamcast).\"\n\tSDL=\"false\"\n\tSOFTWARE=\"false\"\n\n\techo \"Force-disabling runtime icon loading (Dreamcast).\"\n\tICON=\"false\"\n\t;;\n\n\"gp2x\")\n\techo \"Enabling GP2X-specific hacks.\"\n\techo \"#define CONFIG_GP2X\" >> src/config.h\n\techo \"BUILD_GP2X=1\" >> platform.inc\n\n\techo \"Force-disabling modular build on GP2X.\"\n\tMODULAR=\"false\"\n\n\techo \"Force-disabling stack protector on GP2X.\"\n\tSTACK_PROTECTOR=\"false\"\n\n\techo \"Force-disabling runtime icon loading on GP2X.\"\n\tICON=\"false\"\n\n\techo \"Force disabling software, overlay, and OpenGL renderers on GP2X.\"\n\tSOFTWARE=\"false\"\n\tOVERLAY=\"false\"\n\tGL=\"false\"\n\n\techo \"Force-enabling GP2X 320x240 renderer.\"\n\tGP2X=\"true\"\n\n\techo \"Force-disabling Modplug audio.\"\n\tMODPLUG=\"false\"\n\n\tif [ \"$VORBIS\" != \"false\" ] && [ \"$VORBIS\" != \"tremor-lowmem\" ]; then\n\t\techo \"Force-switching ogg/vorbis to tremor-lowmem.\"\n\t\tVORBIS=\"tremor-lowmem\"\n\tfi\n\t;;\n\n\"pandora\")\n\techo \"Enabling Pandora-specific hacks.\"\n\techo \"#define CONFIG_PANDORA\" >> src/config.h\n\techo \"BUILD_PANDORA=1\" >> platform.inc\n\t;;\nesac\n\n\n### RESOLVE CONFLICTING OPTIONS ###############################################\n\n#\n# SDL was disabled above; must also disable SDL-dependent modules\n#\nif [ \"$SDL\" = \"false\" ]; then\n\techo \"Force-disabling SDL dependent components:\"\n\techo \" -> SOFTSCALE, OVERLAY\"\n\tSOFTSCALE=\"false\"\n\tSDLACCEL=\"false\"\n\tOVERLAY=\"false\"\n#\n# If no specific version of SDL is selected, select SDL2.\n#\nelif [ \"$SDL\" != \"1\" ] && [ \"$SDL\" != \"2\" ] && [ \"$SDL\" != \"3\" ]; then\n\tSDL=\"2\"\nfi\n\n#\n# For now, force-enable OpenGL ES hacks, since that's most\n# likely what is being used with EGL.\n#\nif [ \"$EGL\" = \"true\" ]; then\n\techo \"Force-enabling OpenGL ES support (EGL).\"\n\tGLES=\"true\"\nfi\n\n#\n# Force-disable OpenGL renderers for non-SDL, non-EGL.\n#\nif [ \"$SDL\" = \"false\" ] && [ \"$EGL\" = \"false\" ] && [ \"$GL\" = \"true\" ]; then\n\techo \"Force-disabling OpenGL (no SDL or EGL backend).\"\n\tGL=\"false\"\nfi\n\n#\n# Force-disable the softscale renderer for SDL 1.2 (requires SDL_Renderer).\n#\nif [ \"$SDL\" = \"1\" ] && [ \"$SOFTSCALE\" = \"true\" ]; then\n\techo \"Force-disabling softscale renderer (requires SDL 2+).\"\n\tSOFTSCALE=\"false\"\n\tSDLACCEL=\"false\"\nfi\n\n#\n# Force-disable overlay renderers for SDL 2. The SDL 2 answer to SDL_Overlay\n# involves planar YUV modes which don't mesh well with MZX's internal rendering.\n#\nif [ \"$SDL\" != \"false\" ] && [ \"$SDL\" -ge \"2\" ] && [ \"$OVERLAY\" = \"true\" ]; then\n\techo \"Force-disabling overlay renderers (requires SDL 1.2).\"\n\tOVERLAY=\"false\"\nfi\n\n#\n# Must have at least one OpenGL renderer enabled\n#\nif [ \"$GL_FIXED\" = \"false\" ] && [ \"$GL_PROGRAM\" = \"false\" ] && [ \"$GL\" = \"true\" ]; then\n\techo \"Force-disabling OpenGL (no OpenGL renderers enabled)\"\n\tGL=\"false\"\nfi\n\n#\n# If OpenGL is globally disabled, disable all renderers\n#\nif [ \"$GL_FIXED\" = \"true\" ] || [ \"$GL_PROGRAM\" = \"true\" ] && [ \"$GL\" = \"false\" ]; then\n\techo \"Force-disabling OpenGL renderers (OpenGL disabled).\"\n\tGL_FIXED=\"false\"\n\tGL_PROGRAM=\"false\"\nfi\n\n#\n# Runtime icon loading requires MinGW or SDL + libpng.\n#\nif [ \"$SDL\" = \"false\" ] || [ \"$LIBPNG\" = \"false\" ] &&\n   [ \"$PLATFORM\" != \"mingw\" ] && [ \"$ICON\" = \"true\" ]; then\n\techo \"Force-disabling runtime icon loading (requires MinGW or SDL + libpng)\"\n\tICON=\"false\"\nfi\n\n#\n# Force-disable PNG support on platforms without screenshots, utils, icon enabled.\n# The 3DS port requires PNG for other purposes.\n#\nif [ \"$SCREENSHOTS\" = \"false\" ] &&\n   [ \"$UTILS\" = \"false\" ] &&\n   [ \"$ICON\" = \"false\" ] &&\n   [ \"$LIBPNG\" = \"true\" ] &&\n   [ \"$PLATFORM\" != \"3ds\" ]; then\n\techo \"Force-disabling PNG support (screenshots, utils, icon disabled)\"\n\tLIBPNG=\"false\"\nfi\n\n#\n# Force-disable all music playback libraries if audio is disabled\n#\nif [ \"$AUDIO\" = \"false\" ]; then\n\techo \"Force-disabling music playback libraries (audio disabled)\"\n\tMODPLUG=\"false\"\n\tMIKMOD=\"false\"\n\tXMP=\"false\"\n\tOPENMPT=\"false\"\n\tREALITY=\"false\"\n\tVORBIS=\"false\"\nfi\n\n#\n# Force-disable pthread on non-POSIX platforms\n#\nif [ \"$PLATFORM\" != \"unix\" ] &&\n   [ \"$PLATFORM\" != \"unix-devel\" ] &&\n   [ \"$PLATFORM\" != \"gp2x\" ] &&\n   [ \"$PLATFORM\" != \"android\" ]; then\n\techo \"Force-disabling pthread on non-POSIX platforms.\"\n\tPTHREAD=\"false\"\nfi\n\n#\n# Force disable updater (non-MinGW or non-editor)\n#\nif [ \"$PLATFORM\" != \"mingw\" ] || [ \"$EDITOR\" = \"false\" ]; then\n\techo \"Force-disabling updater (currently requires MinGW and editor).\"\n\tUPDATER=\"false\"\nfi\n\n#\n# Force disable network applications (network disabled)\n#\nif [ \"$NETWORK\" = \"false\" ]; then\n\techo \"Force-disabling network-dependent features (networking disabled)\"\n\tUPDATER=\"false\"\nfi\n\n#\n# Force disable networking (no applications enabled)\n#\nif [ \"$NETWORK\" = \"true\" ] && [ \"$UPDATER\" = \"false\" ]; then\n\techo \"Force-disabling networking (no network-dependent features enabled).\"\n\tNETWORK=\"false\"\nfi\n\n#\n# Force disable utils.\n#\nif [ \"$DEBYTECODE\" = \"true\" ]; then\n\techo \"Force-disabling utils (debytecode).\"\n\tUTILS=\"false\"\nfi\n\n\n### EMIT OPTIONS ##############################################################\n\n#\n# Enable SDL.\n#\nif [ \"$SDL\" != \"false\" ]; then\n\techo \"SDL $SDL.x enabled.\"\n\techo \"#define CONFIG_SDL $SDL\" >> src/config.h\n\techo \"BUILD_SDL=$SDL\" >> platform.inc\nelse\n\techo \"SDL disabled.\"\nfi\n\n#\n# Enable EGL.\n#\nif [ \"$EGL\" = \"true\" ]; then\n\techo \"EGL enabled.\"\n\techo \"#define CONFIG_EGL\" >> src/config.h\n\techo \"BUILD_EGL=1\" >> platform.inc\nfi\n\n#\n# As GNU ld supports recursive dylib dependency tracking, we don't need to\n# explicitly link to as many libraries as the authors would have us provide.\n#\n# Instead, pass the linker --as-needed to discard libraries explicitly\n# passed through but not directly used. Fixes warnings with the Debian\n# packaging infrastructure.\n#\nif [ \"$AS_NEEDED\" = \"true\" ]; then\n\techo \"Assuming GNU ld and passing --as-needed through.\"\n\techo \"LDFLAGS+=-Wl,--as-needed\" >> platform.inc\nfi\n\n#\n# Enable -fanalyzer support.\n#\nif [ \"$ANALYZER\" = \"true\" ]; then\n\techo \"Enabling -fanalyzer.\"\n\techo \"\"\n\techo \"  *** WARNING ***: this is an experimental GCC feature! Its output\"\n\techo \"  currently isn't very reliable, but it did help find some places\"\n\techo \"  check_alloc wasn't being used and one legitimate memory leak.\"\n\techo \"\"\n\techo \"BUILD_F_ANALYZER=1\" >> platform.inc\nfi\n\n#\n# Enable pledge(2) support (OpenBSD only)\n#\nif [ \"$UNIX\" = \"openbsd\" ]; then\n\tif [ \"$PLEDGE\" = \"true\" ]; then\n\t\techo \"Enabling OpenBSD pledge(2) support for main executable(s).\"\n\t\techo \"#define CONFIG_PLEDGE\" >> src/config.h\n\telse\n\t\techo \"OpenBSD pledge(2) support disabled for main executable(s).\"\n\tfi\n\tif [ \"$UTILS\" = \"true\" ]; then\n\t\tif [ \"$PLEDGE_UTILS\" = \"true\" ]; then\n\t\t\techo \"Enabling OpenBSD pledge(2) support for utils\"\n\t\t\techo \"#define CONFIG_PLEDGE_UTILS\" >> src/config.h\n\t\telse\n\t\t\techo \"OpenBSD pledge(2) support disabled for utils\"\n\t\tfi\n\tfi\nelse\n\tPLEDGE=\"false\"\nfi\n\n#\n# Users may enable release mode\n#\nif [ \"$RELEASE\" = \"true\" ]; then\n\t#\n\t# Users may want size optimizations\n\t#\n\tif [ \"$OPT_SIZE\" = \"true\" ]; then\n\t\techo \"Optimizing for size.\"\n\t\techo \"OPTIMIZE_FLAGS=-Os\" >> platform.inc\n\telse\n\t\techo \"Optimizing for speed.\"\n\tfi\n\t#\n\t# Enable link-time optimizations.\n\t#\n\tif [ \"$LTO\" = \"true\" ]; then\n\t\techo \"Link-time optimizations enabled.\"\n\t\techo \"LTO=1\" >> platform.inc\n\telse\n\t\techo \"Link-time optimizations disabled.\"\n\tfi\nelse\n\techo \"Disabling optimization, debug enabled.\"\n\techo \"DEBUG=1\" >> platform.inc\n\t#\n\t# Trace logging for debug builds, if enabled.\n\t#\n\tif [ \"$TRACE_LOGGING\" = \"true\" ]; then\n\t\techo \"Enabling trace logging.\"\n\t\techo \"#define DEBUG_TRACE\" >> src/config.h\n\tfi\nfi\n\n#\n# Enable sanitizers.\n#\nif [ \"$SANITIZER\" != \"false\" ]; then\n\techo \"Enabling $SANITIZER sanitizer (may enable some optimizations).\"\n\techo \"SANITIZER=$SANITIZER\" >> platform.inc\nfi\n\n#\n# Enable stack protector.\n#\nif [ \"$STACK_PROTECTOR\" = \"true\" ]; then\n\techo \"Stack protector enabled.\"\n\techo \"BUILD_STACK_PROTECTOR=1\" >> platform.inc\nelse\n\techo \"Stack protector disabled.\"\nfi\n\n#\n# User may disable the built-in editor\n#\nif [ \"$EDITOR\" = \"true\" ]; then\n\techo \"Built-in editor enabled.\"\n\techo \"BUILD_EDITOR=1\" >> platform.inc\n\techo \"#define CONFIG_EDITOR\" >> src/config.h\nelse\n\techo \"Built-in editor disabled.\"\nfi\n\n#\n# User may disable `MZXRun' component\n#\nif [ \"$MZXRUN\" = \"true\" ]; then\n\techo \"Building MZXRun executable.\"\n\techo \"BUILD_MZXRUN=1\" >> platform.inc\nelse\n\techo \"Not building MZXRun executable.\"\nfi\n\n#\n# User may disable the built-in help system.\n#\nif [ \"$HELPSYS\" = \"true\" ]; then\n\techo \"Built-in help system enabled.\"\n\techo \"BUILD_HELPSYS=1\" >> platform.inc\n\techo \"#define CONFIG_HELPSYS\" >> src/config.h\nelse\n\techo \"Built-in help system disabled.\"\nfi\n\n#\n# User may want to compile utils (checkres, downver, txt2hlp)\n#\nif [ \"$UTILS\" = \"true\" ]; then\n\techo \"Building utils (checkres, downver, png2smzx, hlp2txt, txt2hlp).\"\n\techo \"BUILD_UTILS=1\" >> platform.inc\n\techo \"#define CONFIG_UTILS\" >> src/config.h\nelse\n\techo \"Disabled utils (checkres, downver, png2smzx, hlp2txt, txt2hlp).\"\nfi\n\n#\n# X11 support (linked against and needs headers installed)\n#\nif [ \"$PLATFORM\" = \"unix\" ] ||\n   [ \"$PLATFORM\" = \"unix-devel\" ] ||\n   [ \"$PLATFORM\" = \"pandora\" ]; then\n\t#\n\t# Confirm the user's selection of X11, if they enabled it\n\t#\n\tif [ \"$X11\" = \"true\" ]; then\n\t\t#\n\t\t# Locate X11 binary path.\n\t\t#\n\t\tX11=\"false\"\n\t\tfor XBIN in X Xorg; do\n\t\t\t# check if X exists\n\t\t\tX11PATH=$(command -v \"$XBIN\" 2>/dev/null)\n\t\t\tif [ -n \"$X11PATH\" ]; then\n\t\t\t\tX11=\"true\"\n\t\t\t\tbreak\n\t\t\tfi\n\t\tdone\n\n\t\t#\n\t\t# Locate X11 lib dir. \"*/lib\" should be the last element in the\n\t\t# list so it will be used as the default in case of failure.\n\t\t#\n\t\tif [ \"$X11\" = \"true\" ]; then\n\t\t\tX11DIR=$(dirname \"$(dirname \"$X11PATH\")\")\n\n\t\t\tfor X11LIBDIR in \"$X11DIR/lib64\" \"$X11DIR/lib\"; do\n\t\t\t\tfor EXT in \"so\" \"dylib\"; do\n\t\t\t\t\tif [ -f \"$X11LIBDIR/libX11.$EXT\" ] || \\\n\t\t\t\t\t   [ -L \"$X11LIBDIR/libX11.$EXT\" ]; then\n\t\t\t\t\t\tbreak 2\n\t\t\t\t\tfi\n\t\t\t\tdone\n\t\t\tdone\n\t\tfi\n\n\t\tif [ \"$X11\" = \"false\" ]; then\n\t\t\techo \"Force-disabling X11 (could not be queried).\"\n\t\tfi\n\tfi\nelse\n\techo \"Force-disabling X11 (unsupported platform).\"\n\tX11=\"false\"\nfi\n\nif [ \"$X11\" = \"true\" ]; then\n\techo \"X11 support enabled.\"\n\techo \"#define CONFIG_X11\" >> src/config.h\n\techo \"X11DIR=${X11DIR}\" >> platform.inc\n\techo \"X11LIBDIR=${X11LIBDIR}\" >> platform.inc\nfi\n\n#\n# Enable OpenGL ES hacks if required.\n#\nif [ \"$GL\" = \"true\" ] && [ \"$GLES\" = \"true\" ]; then\n\techo \"OpenGL ES support enabled.\"\n\techo \"#define CONFIG_GLES\" >> src/config.h\nelse\n\techo \"OpenGL ES support disabled.\"\nfi\n\n#\n# Software renderer\n#\nif [ \"$SOFTWARE\" = \"true\" ]; then\n\techo \"Software renderer enabled.\"\n\techo \"#define CONFIG_RENDER_SOFT\" >> src/config.h\n\techo \"BUILD_RENDER_SOFT=1\" >> platform.inc\nelse\n\techo \"Software renderer disabled.\"\nfi\n\n#\n# Softscale renderer (SDL 2+)\n#\nif [ \"$SOFTSCALE\" = \"true\" ]; then\n\techo \"Softscale renderer enabled.\"\n\techo \"#define CONFIG_RENDER_SOFTSCALE\" >> src/config.h\n\techo \"BUILD_RENDER_SOFTSCALE=1\" >> platform.inc\nelse\n\techo \"Softscale renderer disabled.\"\nfi\n\n#\n# SDL accelerated hardware renderer (SDL 2+)\n#\nif [ \"$SDLACCEL\" = \"true\" ]; then\n\techo \"SDL accelerated hardware renderer enabled.\"\n\techo \"#define CONFIG_RENDER_SDLACCEL\" >> src/config.h\n\techo \"BUILD_RENDER_SDLACCEL=1\" >> platform.inc\nelse\n\techo \"SDL accelerated hardware renderer disabled.\"\nfi\n\n#\n# Fixed-function H/W OpenGL renderers\n#\nif [ \"$GL_FIXED\" = \"true\" ]; then\n\techo \"Fixed-function H/W OpenGL renderers enabled.\"\n\techo \"#define CONFIG_RENDER_GL_FIXED\" >> src/config.h\n\techo \"BUILD_RENDER_GL_FIXED=1\" >> platform.inc\nelse\n\techo \"Fixed-function OpenGL renderers disabled.\"\nfi\n\n#\n# Programmable H/W OpenGL renderers\n#\nif [ \"$GL_PROGRAM\" = \"true\" ]; then\n\techo \"Programmable H/W OpenGL renderer enabled.\"\n\techo \"#define CONFIG_RENDER_GL_PROGRAM\" >> src/config.h\n\techo \"BUILD_RENDER_GL_PROGRAM=1\" >> platform.inc\nelse\n\techo \"Programmable H/W OpenGL renderer disabled.\"\nfi\n\n#\n# Overlay renderers\n#\nif [ \"$OVERLAY\" = \"true\" ]; then\n\techo \"Overlay renderers enabled.\"\n\techo \"#define CONFIG_RENDER_YUV\" >> src/config.h\n\techo \"BUILD_RENDER_YUV=1\" >> platform.inc\nelse\n\techo \"Overlay renderers disabled.\"\nfi\n\n#\n# GX renderer (Wii and GameCube)\n#\nif [ \"$PLATFORM\" = \"wii\" ] && [ \"$SDL\" = \"false\" ]; then\n\techo \"Building custom GX renderer.\"\n\techo \"#define CONFIG_RENDER_GX\" >> src/config.h\n\techo \"BUILD_RENDER_GX=1\" >> platform.inc\nfi\n\n#\n# CTR renderer (3DS)\n#\nif [ \"$PLATFORM\" = \"3ds\" ] && [ \"$SDL\" = \"false\" ]; then\n\techo \"Building custom CTR renderer.\"\n\techo \"#define CONFIG_RENDER_CTR\" >> src/config.h\n\techo \"BUILD_RENDER_CTR=1\" >> platform.inc\nfi\n\n#\n# GP2X renderer\n#\nif [ \"$GP2X\" = \"true\" ]; then\n\techo \"GP2X half-res renderer enabled.\"\n\techo \"#define CONFIG_RENDER_GP2X\" >> src/config.h\n\techo \"BUILD_RENDER_GP2X=1\" >> platform.inc\nelse\n\techo \"GP2X half-res renderer disabled.\"\nfi\n\n#\n# Screenshot hotkey\n#\nif [ \"$SCREENSHOTS\" = \"true\" ]; then\n\techo \"Screenshot hotkey enabled.\"\n\techo \"#define CONFIG_ENABLE_SCREENSHOTS\" >> src/config.h\n\techo \"BUILD_ENABLE_SCREENSHOTS=1\" >> platform.inc\nelse\n\techo \"Screenshot hotkey disabled.\"\nfi\n\n#\n# Frames-per-second counter\n#\nif [ \"$FPSCOUNTER\" = \"true\" ]; then\n\techo \"fps counter enabled.\"\n\techo \"#define CONFIG_FPS\" >> src/config.h\nelse\n\techo \"fps counter disabled.\"\nfi\n\n#\n# Layer rendering, if enabled\n#\nif [ \"$LAYER_RENDERING\" = \"true\" ]; then\n\techo \"Layer rendering enabled.\"\nelse\n\techo \"Layer rendering disabled.\"\n\techo \"#define CONFIG_NO_LAYER_RENDERING\" >> src/config.h\nfi\n\n#\n# GP2X needs Mikmod, other platforms can pick\n# Keep the default at the bottom so it doesn't override others.\n#\n\nif [ \"$MODPLUG\" = \"true\" ]; then\n\techo \"Selected Modplug music engine.\"\n\techo \"#define CONFIG_AUDIO_MOD_SYSTEM\" >> src/config.h\n\techo \"#define CONFIG_MODPLUG\" >> src/config.h\n\techo \"BUILD_MODPLUG=1\" >> platform.inc\nelif [ \"$MIKMOD\" = \"true\" ]; then\n\techo \"Selected Mikmod music engine.\"\n\techo \"#define CONFIG_AUDIO_MOD_SYSTEM\" >> src/config.h\n\techo \"#define CONFIG_MIKMOD\" >> src/config.h\n\techo \"BUILD_MIKMOD=1\" >> platform.inc\nelif [ \"$OPENMPT\" = \"true\" ]; then\n\techo \"Selected OpenMPT music engine.\"\n\techo \"#define CONFIG_AUDIO_MOD_SYSTEM\" >> src/config.h\n\techo \"#define CONFIG_OPENMPT\" >> src/config.h\n\techo \"BUILD_OPENMPT=1\" >> platform.inc\nelif [ \"$XMP\" = \"true\" ]; then\n\techo \"Selected XMP music engine.\"\n\techo \"#define CONFIG_AUDIO_MOD_SYSTEM\" >> src/config.h\n\techo \"#define CONFIG_XMP\" >> src/config.h\n\techo \"BUILD_XMP=1\" >> platform.inc\nelse\n\techo \"Music engine disabled.\"\nfi\n\n#\n# Handle RAD support, if enabled\n#\n\nif [ \"$REALITY\" = \"true\" ]; then\n\techo \"Reality Adlib Tracker (RAD) support enabled.\"\n\techo \"#define CONFIG_REALITY\" >> src/config.h\n\techo \"BUILD_REALITY=1\" >> platform.inc\nelse\n\techo \"Reality Adlib Tracker (RAD) support disabled.\"\nfi\n\n#\n# Handle audio subsystem, if enabled\n#\nif [ \"$AUDIO\" = \"true\" ]; then\n\techo \"Audio subsystem enabled.\"\n\techo \"#define CONFIG_AUDIO\" >> src/config.h\n\techo \"BUILD_AUDIO=1\" >> platform.inc\nelse\n\techo \"Audio subsystem disabled.\"\nfi\n\n#\n# Handle PNG support, if enabled\n#\nif [ \"$LIBPNG\" = \"true\" ]; then\n\techo \"PNG support enabled.\"\n\techo \"#define CONFIG_PNG\" >> src/config.h\n\techo \"LIBPNG=1\" >> platform.inc\nelse\n\techo \"PNG support disabled.\"\nfi\n\n#\n# Handle vorbis support, if enabled\n#\nif [ \"$VORBIS\" = \"true\" ]; then\n\techo \"Using ogg/vorbis.\"\n\techo \"#define CONFIG_VORBIS\" >> src/config.h\n\techo \"VORBIS=vorbis\" >> platform.inc\n\nelif [ \"$VORBIS\" = \"tremor\" ]; then\n\techo \"Using tremor in place of ogg/vorbis.\"\n\techo \"#define CONFIG_VORBIS\" >> src/config.h\n\techo \"#define CONFIG_TREMOR\" >> src/config.h\n\techo \"VORBIS=tremor\" >> platform.inc\n\nelif [ \"$VORBIS\" = \"tremor-lowmem\" ]; then\n\techo \"Using tremor (lowmem) in place of ogg/vorbis.\"\n\techo \"#define CONFIG_VORBIS\" >> src/config.h\n\techo \"#define CONFIG_TREMOR\" >> src/config.h\n\techo \"VORBIS=tremor-lowmem\" >> platform.inc\n\nelif [ \"$VORBIS\" = \"stb_vorbis\" ]; then\n\techo \"Using stb_vorbis in place of ogg/vorbis.\"\n\techo \"#define CONFIG_VORBIS\" >> src/config.h\n\techo \"#define CONFIG_STB_VORBIS\" >> src/config.h\n\techo \"VORBIS=stb_vorbis\" >> platform.inc\nelse\n\techo \"Ogg/vorbis disabled.\"\nfi\n\n#\n# Handle pthread mutexes, if enabled\n#\nif [ \"$PTHREAD\" = \"true\" ]; then\n\techo \"Using pthread for threads/locking primitives.\"\n\techo \"#define CONFIG_PTHREAD\" >> src/config.h\n\techo \"PTHREAD=1\" >> platform.inc\nelse\n\techo \"Not using pthread for threads/locking primitives.\"\nfi\n\n#\n# Handle runtime icon loading (SDL_SetWindowIcon or LoadIcon), if enabled\n# For MinGW, this also disables embedding the icon.\n#\nif [ \"$ICON\" = \"true\" ]; then\n\techo \"Runtime window icon loading enabled.\"\n\techo \"#define CONFIG_ICON\" >> src/config.h\n\n\t#\n\t# On Windows we want the icons to be compiled in\n\t#\n\tif [ \"$PLATFORM\" = \"mingw\" ]; then\n\t\techo \"Embedded application icon enabled.\"\n\t\techo \"EMBED_ICONS=1\" >> platform.inc\n\telse\n\t\t#\n\t\t# Also get the (probable) icon path...\n\t\t#\n\t\tICONFILE=\"megazeux.png\"\n\t\tICONDIR=\"$SHAREDIR/icons/hicolor/128x128/apps\"\n\n\t\tif [ \"$SHAREDIR\" = \".\" ]; then\n\t\t\tICONFILE=\"icon_256.png\"\n\t\t\tICONDIR=\"contrib/icons/generated\"\n\t\telif [ \"$SHAREDIR\" = \"/app/share\" ]; then\n\t\t\t# TODO: flatpak hack\n\t\t\tICONFILE=\"com.digitalmzx.MegaZeux.png\"\n\t\tfi\n\t\techo \"#define ICONFILE \\\"$ICONDIR/$ICONFILE\\\"\" >> src/config.h\n\tfi\nelse\n\techo \"Runtime window icon loading disabled.\"\n\t[ \"$PLATFORM\" = \"mingw\" ] && echo \"Embedded application icon disabled.\"\nfi\n\n#\n# Inform build system of modular build, if appropriate\n#\nif [ \"$MODULAR\" = \"true\" ]; then\n\techo \"Modular build enabled.\"\n\techo \"BUILD_MODULAR=1\" >> platform.inc\n\techo \"#define CONFIG_MODULAR\" >> src/config.h\n\nelse\n\techo \"Modular build disabled.\"\nfi\n\n#\n# Handle networking, if enabled\n#\nif [ \"$NETWORK\" = \"true\" ]; then\n\techo \"Networking enabled.\"\n\techo \"#define CONFIG_NETWORK\" >> src/config.h\n\techo \"BUILD_NETWORK=1\" >> platform.inc\n\n\t#\n\t# Handle networking options.\n\t#\n\tif [ \"$GETADDRINFO\" = \"true\" ]; then\n\t\techo \"getaddrinfo name resolution enabled.\"\n\t\techo \"#define CONFIG_GETADDRINFO\" >> src/config.h\n\telse\n\t\techo \"getaddrinfo name resolution disabled.\"\n\tfi\n\n\tif [ \"$POLL\" = \"true\" ]; then\n\t\techo \"poll enabled.\"\n\t\techo \"#define CONFIG_POLL\" >> src/config.h\n\telse\n\t\techo \"poll disabled; falling back to select.\"\n\tfi\n\n\tif [ \"$IPV6\" = \"true\" ]; then\n\t\techo \"IPv6 support enabled.\"\n\t\techo \"#define CONFIG_IPV6\" >> src/config.h\n\telse\n\t\techo \"IPv6 support disabled.\"\n\tfi\nelse\n\techo \"Networking disabled.\"\nfi\n\n#\n# Handle built-in updater, if enabled\n#\nif [ \"$UPDATER\" = \"true\" ]; then\n\techo \"Built-in updater enabled.\"\n\techo \"#define CONFIG_UPDATER\" >> src/config.h\n\techo \"BUILD_UPDATER=1\" >> platform.inc\nelse\n\techo \"Built-in updater disabled.\"\nfi\n\n#\n# Some users may prefer the build system to ALWAYS be verbose\n#\nif [ \"$VERBOSE\" = \"true\" ]; then\n\techo \"Verbose build system (always).\"\n\techo \"export V=1\" >> platform.inc\nfi\n\n#\n# Enable extra memory hacks and memory compression, if enabled.\n#\nif [ \"$EXTRAM\" = \"true\" ]; then\n\techo \"Board memory compression and extra memory hacks enabled.\"\n\techo \"#define CONFIG_EXTRAM\" >> src/config.h\n\techo \"BUILD_EXTRAM=1\" >> platform.inc\nelse\n\techo \"Board memory compression and extra memory hacks disabled.\"\nfi\n\n#\n# Users may enable the load/save meter display.\n#\nif [ \"$METER\" = \"true\" ]; then\n\techo \"Load/save meter display enabled.\"\n\techo \"#define CONFIG_LOADSAVE_METER\" >> src/config.h\nelse\n\techo \"Load/save meter display disabled.\"\nfi\n\n#\n# Memory allocation error handling, if enabled\n#\nif [ \"$CHECK_ALLOC\" = \"true\" ]; then\n\techo \"Memory allocation error checking enabled.\"\n\techo \"#define CONFIG_CHECK_ALLOC\" >> src/config.h\nelse\n\techo \"Memory allocation error checking disabled.\"\nfi\n\n#\n# Allow use of hash table counter/string lookups, if enabled\n#\nif [ \"$COUNTER_HASH\" = \"true\" ]; then\n\techo \"Hash table counter/string lookups enabled.\"\n\techo \"#define CONFIG_COUNTER_HASH_TABLES\" >> src/config.h\n\techo \"BUILD_COUNTER_HASH_TABLES=1\" >> platform.inc\nelse\n\techo \"Hash table counter/string lookups disabled (using binary search).\"\nfi\n\n#\n# DOS-style roots in POSIX-like environments, if enabled\n#\nif [ \"$DOS_ROOTS\" = \"true\" ]; then\n\techo \"Enabling DOS-style roots.\"\n\techo \"#define CONFIG_DOS_STYLE_ROOTS\" >> src/config.h\nfi\n\n#\n# Virtual filesystem, if enabled\n#\nif [ \"$VFS\" = \"true\" ]; then\n\techo \"Virtual filesystem enabled.\"\n\techo \"#define CONFIG_VFS\" >> src/config.h\nelse\n\techo \"Virtual filesystem disabled.\"\nfi\n\n#\n# Experimental 'debytecode' transformation, if enabled\n#\nif [ \"$DEBYTECODE\" = \"true\" ]; then\n\techo \"Experimental 'debytecode' transform enabled.\"\n\techo \"#define CONFIG_DEBYTECODE\" >> src/config.h\n\techo \"BUILD_DEBYTECODE=1\" >> platform.inc\nelse\n\techo \"Experimental 'debytecode' transform disabled.\"\nfi\n\n#\n# stdio redirect, if enabled\n#\nif [ \"$SDL\" = \"1\" ]; then\n\techo \"Using SDL 1.x default stdio redirect behavior.\"\nelif [ \"$STDIO_REDIRECT\" = \"true\" ]; then\n\techo \"Redirecting stdio to stdout.txt and stderr.txt.\"\n\techo \"#define CONFIG_STDIO_REDIRECT\" >> src/config.h\nelse\n\techo \"stdio redirect disabled.\"\nfi\n\n#\n# SDL_GameControllerDB, if enabled. This depends on SDL 2+.\n#\nif [ \"$SDL\" != \"false\" ] && [ \"$SDL\" -ge \"2\" ] && \\\n   [ \"$GAMECONTROLLERDB\" = \"true\" ]; then\n\techo \"SDL_GameControllerDB enabled.\"\n\techo \"#define CONFIG_GAMECONTROLLERDB\" >> src/config.h\n\techo \"BUILD_GAMECONTROLLERDB=1\" >> platform.inc\nelse\n\techo \"SDL_GameControllerDB disabled.\"\nfi\n\n#\n# Pledge(2) on main executable warning\n#\nif [ \"$PLEDGE\" = \"true\" ]; then\n\techo\n\techo \"  WARNING: pledge will probably: break renderer switching; crash when\"\n\techo \"  switching to fullscreen (use fullscreen=1) or exiting when using\"\n\techo \"  the software renderer; crash when switching to fullscreen with the\"\n\techo \"  scaling renderers (use fullscreen=1 and/or fullscreen_windowed=1);\"\n\techo \"  crash when using any scaling renderer with some Mesa versions.\"\n\techo \"  You've been warned!\"\nfi\n\necho\necho \"Now type \\\"make\\\" (or \\\"gmake\\\" on BSD).\"\n"
  },
  {
    "path": "config.txt",
    "content": "# Configuration file for MegaZeux 2.93d\n# June 9th, 2025\n\n# Comments begin with #. Whitespace is ignored. Empty lines are\n# ignored as well.\n# Options are set in the following format:\n# option = value\n# If value is not given, then it is assumed to be 1.\n# You can include spaces in values by escaping them with \\s.\n# (see the default macros for examples)\n# For directories, use forward slashes (/) instead of back-slashes,\n# as they work more reliably.\n# Note that even if the option is given to change the values\n# in MZX, this text file will NOT be modified, and hence the\n# values will revert to what they are here when you restart MZX.\n\n# NOTE: All of the options below have sane defaults. Changes to\n# these options will only take effect if you uncomment them.\n\n# In many cases, the current default value has been written in,\n# but this is not guaranteed (for example with video_output).\n\n### Video options ###\n\n# Rendering engine to use to display MegaZeux. The simplest (but not\n# necessarily fastest) renderer is \"software\". Due to MegaZeux's strange\n# default resolution (640x350), many video cards have difficulty\n# supporting it correctly in fullscreen mode.\n#\n# The software renderer is the fallback renderer if another renderer fails\n# to initialize, and has generally adequate performance. Additionally, it\n# supports 8 bit indexed color and requires no overlay or 3D hardware support.\n# If you're on a slow computer or a handheld portable, definitely use this.\n# To use it, uncomment the line below.\n\n# video_output = software\n\n# A much faster option for almost anything with a graphics card is the\n# \"softscale\" renderer. This renderer uses SDL2's built-in rendering API\n# to stream the output of the software renderer to a hardware-accelerated\n# texture (if available) for extremely fast scaling and display. This (or\n# \"glslscale\", see below) is usually the fastest scaling renderer for devices\n# using integrated graphics e.g. Intel HD, phones, tablets, Raspberry Pi.\n#\n# Either 32 bit or 16 bit rendering can be enabled (see force_bpp), but\n# 32 bit is recommended. Nearest neighbor or linear scaling will be chosen\n# automatically based on the scaling ratio and size of the window. This\n# renderer is a replacement for the YUV overlay renderer.\n\n# video_output = softscale\n\n# The SDL renderer API used by softscale will use the following underlying\n# drivers by default: Direct3D (Windows), Metal (Mac OS 10.11+), or OpenGL.\n# To request a different driver, the following option can be changed.\n# See https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER for more info.\n\n# sdl_render_driver = opengl\n\n# If you built MegaZeux yourself with SDL 1.2 support enabled, the YUV overlay\n# renderer is available instead of softscale. This renderer is good if you\n# want scaling and can't use OpenGL. The quality of scaling is hardware\n# dependent and often looks bad at non-integer scaling.\n#\n# Uncomment either of the lines below (but not both) if you want to try it.\n# The \"overlay2\" mode is cheaper, but will be less accurate due to the use\n# of chroma subsampling instead of a full YUV encode. The \"overlay2\" mode\n# also does not support newer graphical features such as unbound sprites.\n# If uncommented for SDL2 builds, these lines will enable softscale instead.\n\n# video_output = overlay1\n# video_output = overlay2\n\n# An OpenGL renderer is a good choice if you want a configurable, cheap\n# renderer and have a video card. This method provides the highest quality\n# scaler and should work on all platforms with OpenGL or OpenGL ES support.\n#\n# MegaZeux provides two fixed-function scalers. The first renders the whole\n# screen to a texture and uses OpenGL to stretch it. This method is generally\n# fast but may be slow on very old PCs. This renderer is called \"opengl1\".\n#\n# The second fixed-function scaler draws entirely in OpenGL by remapping the\n# character sets to a texture. This yields a performance increase for some\n# games, but at the cost of compatibility. This renderer is called \"opengl2\".\n#\n# MegaZeux also provides a scaler for programmable hardware. If you have\n# OpenGL 2.0 or OpenGL ES 2.0 capable hardware, you should use this renderer.\n# This renderer uses GLSL shaders to render and scale the screen, and several\n# different scaling shaders can be selected at runtime using the settings menu.\n# This renderer is called \"glsl\". It is the highest quality and best performing\n# renderer for most PCs with a dedicated video card.\n#\n# A variant of the GLSL renderer called \"glslscale\" is available that uses the\n# software renderer internally with GLSL shader scaling. This performs better\n# than the original GLSL renderer on PCs and other devices that don't have a\n# video card, and is currently the default renderer for most platforms.\n#\n# Uncomment ONE of the lines below:\n\n# video_output = opengl1\n# video_output = opengl2\n# video_output = glsl\n# video_output = glslscale\n\n# The scaling renderers can be configured to preserve aspect ratio where\n# possible in window and fullscreen modes. The supported ratios are\n# \"classic\" (4:3) and \"modern\" (64:35). The ratio type \"stretch\" disables\n# any aspect ratio preservation. The default is \"modern\" (except for consoles\n# where \"classic\" makes more sense, including the 3DS, NDS, and Wii).\n\n# video_ratio = modern\n\n# OpenGL will, by default, linearly interpolate pixels as it upscales\n# to your requested resolution. This creates an output with more \"blur\",\n# which some people dislike. If you are using a sufficiently high target\n# resolution, preferably an integer multiple of 640x350, you may find\n# \"nearest\" more pleasing, which will have no \"blur\". The default\n# setting is \"linear\".\n\n# gl_filter_method = nearest\n\n# The GLSL renderer uses scaling shaders to determine the scaling method\n# instead of gl_filter_method. The default shader to be loaded when MZX\n# starts is assets/glsl/scalers/semisoft.frag.\n# Change this config option to specify a different shader to load on startup.\n# For a list of valid options, see the F2 menu or the GLSL scalers folder.\n# Do not include an extension.\n\n# gl_scaling_shader = semisoft\n\n# Some OpenGL driver settings may default vertical sync on, which looks\n# visually better on CRTs but can cause \"speed 1\" to run noticably slower.\n# For consistency with other renderers, and for correctness, MegaZeux will\n# attempt to disable vsync when gl_vsync is the default value of 0.\n# Conversely, a value of 1 forces MegaZeux to override the driver settings\n# and enable vsync, which decreases tearing. A value of -1 can be used to\n# tell MegaZeux to obey the driver default, which is unlikely to be useful.\n\n# gl_vsync = 1\n\n# Resolution MZX uses for fullscreen. With the software renderer, this\n# will center MZX on the screen, and will not scale it. All other renderers\n# listed above will attempt to scale the screen to match this resolution.\n# Best results are obtained by using your current desktop resolution or a\n# well-supported resolution (like 1280,720) in conjunction with the modern\n# aspect ratio.\n#\n# Give in x,y format. Use this if the defaults are giving you problems,\n# especially in the software renderer.\n# The current desktop resolution is the default for all scalable renderers;\n# \"640,480\" is the default for the software renderer.\n\n# fullscreen_resolution = 640,480\n\n# If this setting is enabled, instead of regular fullscreen MZX will create\n# a borderless window with the current desktop resolution when fullscreen\n# is enabled. This overrides fullscreen_resolution. Disabled by default\n# except on macOS, where it is enabled by default.\n\n# fullscreen_windowed = 0\n\n# The resolution MZX uses in a window. If your renderer supports\n# it, MZX will be scaled to fit this specification. Otherwise\n# the setting is ignored.\n\n# window_resolution = 640,350\n\n# Use this to toggle whether MZX can be scaled while in window\n# mode, if your renderer supports it. If you use a renderer which\n# supports scaling, but don't want to accidentally scale it (which\n# may distort the image), you can switch this off here.\n\n# enable_resizing = 1\n\n# MZX is limited internally to 8bit colour, but it can upmix its\n# rendering to 32bit if your video card has difficulty with 8bit\n# resolutions. Using 32bit colour is slower, but not by much.\n#\n# NOTE: This option may be ignored by one or more of the above renderers.\n# force_bpp is guaranteed to apply to \"software\". This might not always be\n# useful, however; SDL 2 picks a native window pixel format automatically\n# and non-native BPPs are handled with an extra surface blit. The \"softscale\"\n# renderer can use this setting to select different native pixel formats.\n#\n# Valid settings are usually '8', '16', or '32'; a value of '0' or 'auto'\n# will instruct the renderer to automatically pick the best available\n# BPP (if applicable). Default is 'auto'.\n\n# force_bpp = auto\n\n# Whether MZX should start up in fullscreen or not.\n# Press ctrl-alt-enter to toggle fullscreen as MZX runs.\n\n# fullscreen = 1\n\n# Whether MZX should disable the screensaver (or similar) while running.\n# By default, MZX will instruct SDL to leave the screensaver on. This setting\n# currently only affects SDL 2+ ports.\n\n# disable_screensaver = 0\n\n# MZX can optionally display the text cursor to hint at the currently selected\n# UI element when applicable. Valid settings are 0 or \"off\" (doesn't display\n# or update the cursor position), 1 or \"hidden\" (doesn't display, but updates\n# the cursor position for platforms with hardware cursors), \"underline\"\n# (displays the underline cursor), and \"solid\" (displays the filled cursor).\n# The default value is 1.\n\n# dialog_cursor_hints = 1\n\n# Whether MZX should allow a screenshot to be taken when F12 is pressed.\n# Defaults to enabled.\n\n# allow_screenshots = 1\n\n### Audio options ###\n\n# Sampling rate to output audio at. Higher values will sound\n# better but use more resources and may not be supported by\n# your audio card/drivers. Typical values include 22050, 44100,\n# and 48000. If your card supports it you might get away with\n# 96000, or even 192000.\n\n# audio_sample_rate = 44100\n\n# Audio buffer size in sample frames. Setting this to lower can improve\n# latency, so audio will be heard sooner after it is set to be\n# changed. It is also possible to change PC speaker effects more\n# rapidly with lower values. Lower buffer sizes need more CPU power\n# to process. 2048 is adequate for PC speaker playback every\n# command at speed 4.\n\n# audio_buffer_samples = 1024\n\n# Number of audio channels. The normal value here is 2 for stereo, but\n# in rare cases, 1 (mono) may be preferable. This setting is ignored\n# by most ports and for some DOS sound devices. MegaZeux currently\n# does not support any type of surround sound.\n\n# audio_output_channels = 2\n\n# Allow music to be sampled at higher precision. Increases CPU\n# usage but increases audio quality as well. (Modplug only.)\n\n# enable_oversampling = 0\n\n# Set global resample mode. This affects how OGGs, SAMs, WAVs, RADs, and\n# frequency shifted modules sound. Choices are:\n# none (fastest, poor quality)\n# linear (fast, good quality)\n# cubic (slow, great quality)\n\n# resample_mode = linear\n\n# Set resample mode for xmp (or Modplug, depending on build settings).\n# This affects how modules sound. Choices are:\n# none (fastest, poor quality)\n# linear (fast, good quality)\n# cubic (slow, great quality)\n# fir (very slow, excellent quality) (Modplug only)\n\n# module_resample_mode = cubic\n\n# Whether music/samples should be played or not.\n# Does not include PC speaker effects.\n\n# music_on = 1\n\n# Whether PC speaker effects should be played or not.\n\n# pc_speaker_on = 1\n\n# Volume music plays at (0 through 10)\n\n# music_volume = 8\n\n# Volume samples play at (0 through 10)\n\n# sample_volume = 8\n\n# Volume PC speaker SFX play at (0 through 10)\n\n# pc_speaker_volume = 8\n\n# The maximum number of samples that can be played simultaneously.\n# Set to -1 for an unlimited number of samples.\n\n# max_simultaneous_samples = -1\n\n\n### Game options ###\n\n# Name of the directory where MegaZeux should start.\n\n# startup_path = /home/username/MegaZeux\n\n# Name of world file that should be loaded when MZX starts.\n\n# startup_file = caverns.mzx\n\n# Default save file name given when you first try to save.\n\n# save_file = saved.sav\n\n# Speed MZX should start at (1 through 16)\n\n# mzx_speed = 4\n\n# Start in the editor by default\n\n# startup_editor = 1\n\n# Allow the use of editing cheats in regular gameplay. Set to 1\n# to enable the cheats for either executable, \"mzxrun\" to enable\n# them for MZXRun only, or to 0 to disable them for both. This\n# feature is disabled by default.\n\n# allow_cheats = 0\n\n# Allow MegaZeux to automatically decrypt worlds without confirmation.\n# Instead of replacing the world, the world will be decrypted either in\n# memory or to a temporary file. Enabled by default.\n\n# auto_decrypt_worlds = 1\n\n# Enable standalone mode. This is a game mode meant to be used\n# for packaged standalone MegaZeux games. In standalone mode:\n# - ENTER_MENU, ESCAPE_MENU, HELP_MENU, F2_MENU and LOAD_MENU\n#   can be used on the title screen to disable these menus.\n# - HELP_MENU prevents the help menu from being opened even\n#   when other builtin dialogs are open\n# - EXIT_GAME can be used on the title screen to exit MegaZeux\n# - The default title screen message does not appear\n# - PLAY_GAME can be set to 1 to start a game\n# - Using ALT-F4, closing the window or using other OS-specific\n#   ways of closing an application will exit MZX entirely,\n#   without asking for confirmation first\n# Standalone mode can only be used from MZXRun.\n\n# standalone_mode = 1\n\n# Enable no titlescreen mode. This mode only works if standalone\n# mode is enabled and causes the following:\n# - The title screen is skipped and the game begins on the first\n#   board.\n# - Exiting to the title screen exits MegaZeux\n\n# no_titlescreen = 1\n\n### Board editor options ###\n\n# Whether or not the spacebar can be used to toggle between\n# your active buffer and the meta character \"SPACE\". If you\n# set this, it will behave like older MegaZeux versions, but it\n# will allow you to overwrite robots, for instance.\n#\n# If the setting is disabled (the default), replacing params\n# will your new buffer will not require a toggle, and robots\n# can only be deleted with the Delete key.\n\n# editor_space_toggles = 1\n\n# Whether or not the editor should center the viewport on the\n# cursor in tab draw mode. Disabled by default; uncomment the\n# following line to enable.\n\n# editor_tab_focuses_view = 1\n\n# Whether or not the editor will load board char sets and\n# palettes when the board is changed. Disabled by default.\n\n# editor_load_board_assets = 1\n\n# Whether or not the thing menus (F3-F10) will place an object on\n# the board when that object is selected. Enabled by default.\n\n# editor_thing_menu_places = 1\n\n# By default, the show thing keys (e.g. \"Shift+F2: Show Robots\") will block\n# editing until a key or mouse button is pressed, after which they are turned\n# off. If this option is set to 1, these keys will instead toggle on until\n# the same key is pressed again, another show thing key is pressed, or until\n# escape is pressed. This allows scrolling and other editing without having to\n# press these keys repeatedly.\n\n# editor_show_thing_toggles = 0\n\n# This option controls the blinking speed when a show thing key is used.\n# Lower numbers mean faster blinking; for example, when set to 4 (default),\n# the character displayed by the blinking will alternate every 4 frames (~8 Hz).\n# When set to 0, no blinking will occur and only the first character will display.\n\n# editor_show_thing_blink_speed = 4\n\n# If this is set to 1 (the default), enter can be used to split\n# a line in the robot editor. However, the classic behaviour is\n# that enter moves the cursor down a line. If you would like to\n# restore the old behaviour, please set this to 0.\n\n# editor_enter_splits = 1\n\n# Set to 0 if you want the board editor to show the hotkey help\n# by default.\n\n# board_editor_hide_help = 1\n\n# Set to 1 if you want the palette editor to hide the hotkey help\n# when MegaZeux starts (default: don't hide).\n\n# palette_editor_hide_help = 0\n\n# The size of the board, overlay, vlayer, and char editor undo history\n# stacks. Valid values are between 0 and 1000. Defaults to 100.\n\n# undo_history_size = 100\n\n# Defaults for new boards. Put these in [filename].editor.cnf to customize\n# for each individual game, E.G. caverns.editor.cnf.  Viewport width/height\n# must be set before offset, and board width/height must be set before any\n# viewport vars.\n\n# Options for the majority of these are 0 or 1, for off and on.\n# Options for explosions_leave: space, ash, fire\n# Options for saving: enabled, disabled, sensoronly\n# Options for overlay: enabled, disabled, static, transparent\n\n# board_default_width = 100\n# board_default_height = 100\n# board_default_viewport_w = 80\n# board_default_viewport_h = 25\n# board_default_viewport_x = 0\n# board_default_viewport_y = 0\n# board_default_can_shoot = 1\n# board_default_can_bomb = 1\n# board_default_fire_burns_spaces = 0\n# board_default_fire_burns_fakes = 1\n# board_default_fire_burns_trees = 1\n# board_default_fire_burns_brown = 0\n# board_default_fire_burns_forever = 0\n# board_default_forest_to_floor = 0\n# board_default_collect_bombs = 0\n# board_default_restart_if_hurt = 0\n# board_default_reset_on_entry = 0\n# board_default_player_locked_ns = 0\n# board_default_player_locked_ew = 0\n# board_default_player_locked_att = 0\n# board_default_time_limit = 0\n# board_default_explosions_leave = ash\n# board_default_saving = enabled\n# board_default_overlay = enabled\n# board_default_charset_path =\n# board_default_palette_path =\n\n\n### Robot editor options ###\n\n# Enable or disable color coding in the editor.\n# If color coding is off, ccode_string is used for the entire\n# line.\n\n# color_coding_on = 0\n\n# Color codes for various types.\n# For colors, use 255 to show a color box.\n\n# ccode_chars = 14\n# ccode_colors = 255\n# ccode_commands = 15\n# ccode_conditions = 15\n# ccode_current_line = 11\n# ccode_directions = 3\n# ccode_equalities = 0\n# ccode_extras = 7\n# ccode_immediates = 10\n# ccode_items = 11\n# ccode_params = 2\n# ccode_strings = 14\n# ccode_things = 11\n\n# Whether or not \"extra words\" should be displayed.\n# For instance, if turned on, a line like \"wait 1\" will\n# become \"wait for 1.\"\n\n# disassemble_extras = 0\n\n# The base numbers should be displayed as. Valid options\n# are 10 and 16 (decimal and hexidecimal). Hex numbers are\n# prefixed with a dollar sign ($).\n\n# disassemble_base = 16\n\n# Changing this will allow invalid comments to automatically\n# be marked to \"delete\" or \"comment\" instead of \"ignore\".\n# Don't change this unless you know what you're doing.\n\n# default_invalid_status = ignore\n\n# Single line macros\n# These are the traditional MZX macros; they may only\n# occupy one line in their definition in this file. They\n# correspond to parameter-less macros: when you press\n# F6 through F10 the following strings are printed to\n# the editor, verbatim. For instance if you set macro_1 to\n# * \"hello\", * \"hello\" will be inserted every time you\n# press F6 in the robot editor.\n# The following are the default singe-line macros.\n\n# macro_1 = char\\s\n# macro_2 = color\\s\n# macro_3 = goto\\s\n# macro_4 = send\\s\n# macro_5 = :\\splayershot^\n\n# Extended macros:\n# See macro.txt.\n\n# The following is an example extended macro.\n# It sets up a sprite. If you find it useful, remove the #'s.\n\n#macro_sprite = Sprite\\sSetup\n# (number255 spr_num)\n# (number255 w, h, rx, ry)\n# (number255 cx, cy, cw, ch)\n# (number255 x, y)\n# set spr!spr_num!_width !w!\n# set spr!spr_num!_height !h!\n# set spr!spr_num!_refx !rx!\n# set spr!spr_num!_refy !ry!\n# set spr!spr_num!_cwidth !cw!\n# set spr!spr_num!_cheight !ch!\n# set spr!spr_num!_cx !cx!\n# set spr!spr_num!_cy !cy!\n# put !x1! sprite p!#spr_num! !x! !y!\n\n# Whether or not the palette should be reverted to default\n# when the robot editor is load. If set, SMZX mode 0 will\n# also be enforced.\n\n# robot_editor_default_palette = 0\n\n# Set to 0 if you want the robot editor to show the hotkey help\n# and horizontal border by default.\n\n# robot_editor_hide_help = 1\n\n\n# Backup options\n\n# Backups are save files made in the editor. They will only be made\n# while in the main editor, not while in a sub-editor or a dialogue\n# box of sorts. Note that this can cause slight pauses while editing,\n# as the game saves.\n# If you have multiple backups you should check the modified date in\n# a console or file properties to determine which was most recently\n# modified. The number will not indicate most recently saved.\n\n# How many backups to be made. Set to zero to disable backups.\n\n# backup_count = 3\n\n# How often (in seconds) backups should be made\n\n# backup_interval = 60\n\n# The name of the backup file. If you choose \"backup\" with 3 backups\n# the backups will be named backup1.mzx, backup2.mzx, and backup3.mzx.\n\n# backup_name = backup\n\n# The extension of backup files (keep this .mzx if you want to be able\n# to easily load them)\n\n# backup_ext = .mzx\n\n\n# Joystick options\n\n# It is possible to emulate key presses using a joystick. This can be\n# achieved with the following config options globally or (for options\n# starting with \"joyX\" only) on a per-game basis. To configure joysticks\n# for a particular game, place the applicable settings in a file called\n# [game].cnf (e.g. \"caverns.cnf\" is the settings file for \"caverns.mzx\").\n# This will configure joysticks for that world and all saves loaded from\n# that world only. Games utilizing swaps may require multiple .cnf files.\n\n# Each joystick in MegaZeux is assigned a number between 1 and 16. When\n# a joystick is detected, it is given the lowest available number, so\n# typically the joystick you are using is joystick 1. When multiple\n# joysticks are plugged in, you may have to experiment to determine which\n# joystick is which.\n\n# The old method for mapping joystick controls is to manually assign axes,\n# buttons, and/or a POV hat to keys to simulate keypresses. While this is no\n# longer recommended, the settings for this are\n\n# joyXbuttonY = A\n# joyXaxisY = A, B\n# joyXhat = A, B, C, D\n\n# where X is the number of the joystick, Y is the number of the button or axis,\n# and A, B, C, and D are any of the following:\n#\n# 1) a number representing an internal MZX keycode (see keycodes.html);\n# 2) 'key_NAME' (without quotes), representing the key NAME (see keycodes.html);\n# 3) or 'act_NAME' (without quotes), representing the action NAME (see below).\n\n# The threshold to trigger a joystick axis press can be globally defined with\n# the setting\n\n# joy_axis_threshold = 10000\n\n# where lower values (0 minimum) will press the mapped key with less\n# movement and higher values (32767 maximum) will press the mapped key\n# with more movement.\n\n# As of MegaZeux 2.92, it is also possible to bind joystick controls to\n# named joystick \"actions\". These actions correspond directly to positional\n# button names common on popular game controllers, are more UI-friendly than\n# using keys directly, they have default gameplay keys pre-assigned, and these\n# gameplay keys can be changed to different values without altering UI behavior.\n# Actions can also be detected directly through Robotic. Finally, for most\n# platforms, these actions can be assigned automatically for many controllers\n# rather than by config options.\n\n# The available joystick actions and their typical behaviors are:\n#\n#  Action         Title screen    Window          Game key (default)\n# -------------  --------------  --------------  ----------------------\n#  up (1)                         Cursor up       Up (move)\n#  down (1)                       Cursor down     Down (move)\n#  left (1)                       Cursor left     Left (move)\n#  right (1)                      Cursor right    Right (move)\n#  a              Play game       Select          Space (shoot)\n#  b              Main menu       Cancel          Delete (lay bomb)\n#  x              Load world      Select char     Enter (game menu)\n#  y              Reload save     Backspace       S (Caverns: spells)\n#  select         Main menu       Cancel          Game menu (2)\n#  start          Play game       Select          Game menu (2)\n#  lshoulder      Settings        Next element    Insert (switch bombs)\n#  rshoulder(3)   Settings        Settings        P (Caverns: altar)\n#  ltrigger       Load world      Page up         F3 (save game)\n#  rtrigger       Reload save     Page down       F4 (load save)\n#  lstick                         Home\n#  rstick                         End\n#\n# (1) Actions \"up\"/\"down\"/\"left\"/\"right\" are intended for a dpad. Actions\n# \"l_up\"/\"l_down\"/\"l_left\"/\"l_right\" and \"r_up\"/\"r_down\"/\"r_left\"/\"r_right\"\n# also exist for analog sticks, and have the same default mappings.\n#\n# (2) These buttons will always open the game menu directly unless both the\n# enter menu and the escape menu have been disabled through Robotic.\n#\n# (3) By default, this action will be intercepted and used to show/hide the\n# onscreen keyboard for platforms that support it. This is supported on Android,\n# Vita, NDS, and 3DS, and can also be enabled for Linux machines with no\n# hardware keyboard active (e.g. Steam Deck). This will globally override\n# any press of this action.\n\n# To map a joystick action to a different key for gameplay, use the setting\n\n# joyX.NAME = A\n\n# where X is the number of the joystick, NAME is the name of the action\n# (e.g. \"lshoulder\"), and A is the number (or name) of the key to assign. The\n# joystick number 'X' can be substituted with a range '[Z,W]', which will\n# assign the control for all joysticks numbered Z through W (inclusive).\n\n# To change the action that shows/hides the onscreen keyboard (0 to disable),\n# use the setting\n\n# joy[1,16].show_screen_keyboard = act_rshoulder\n\n# Automatic mapping for actions is handled using SDL's gamepad API.\n# Supported gamepads should generally \"just work\" when they're plugged in.\n# This setting can be used to globally disable the automatic mapping feature\n# entirely. If 1, it will be enabled (default). If 0, it will be disabled.\n\n# gamepad_enable = 1\n\n# Gamepads that aren't supported can be fixed by adding SDL mapping strings.\n# This config option adds a new mapping string and can be used multiple times;\n# each line adds a new mapping. See docs/joystick.html for more information.\n\n# gamepad_add =\n\n# The following settings can be used to globally customize the automated\n# joystick mappings generated from SDL mapping strings.\n# See docs/joystick.html for more information.\n\n# gamepad.a = act_a\n# gamepad.b = act_b\n# gamepad.x = act_x\n# gamepad.y = act_y\n# gamepad.back = act_select\n# gamepad.start = act_start\n# gamepad.leftstick = act_lstick\n# gamepad.rightstick = act_rstick\n# gamepad.leftshoulder = act_lshoulder\n# gamepad.rightshoulder = act_rshoulder\n# gamepad.dpup = act_up\n# gamepad.dpdown = act_down\n# gamepad.dpleft = act_left\n# gamepad.dpright = act_right\n# gamepad.-leftx = act_l_left\n# gamepad.+leftx = act_l_right\n# gamepad.-lefty = act_l_up\n# gamepad.+lefty = act_l_down\n# gamepad.-rightx = act_r_left\n# gamepad.+rightx = act_r_right\n# gamepad.-righty = act_r_up\n# gamepad.+righty = act_r_down\n# gamepad.lefttrigger = act_ltrigger\n# gamepad.righttrigger = act_rtrigger\n\n\n# Misc Options\n\n# Set to 1 if you want MZX to pause when key focus is lost. Useful for\n# when another app (such as someone IM'ing you) steals the screen. This\n# might cause MZX to lock up sometimes on some machines - if you have\n# problems with it don't enable it.\n\n# pause_on_unfocus = 0\n\n# Set to 1 to treat the left Alt key and/or the right Alt key as AltGr for\n# the purposes of UI keyboard combinations. This can be used to prevent AltGr\n# keyboard combinations--typically involving right Alt--from triggering UI\n# shortcuts. These settings do not alter the internal or PC XT keycodes\n# generated by any keypress. If you don't know what AltGr is, ignore this.\n#\n# Windows: Ctrl+Alt can be used instead of this feature.\n# macOS: Command is already treated as Alt for these purposes. Option behaves\n#        more like AltGr, so this will be enabled by default in the future.\n# Wayland/X11: this is unnecessary as AltGr has a dedicated keysym.\n\n# key_left_alt_is_altgr = 0\n# key_right_alt_is_altgr = 0\n\n# Set to 0 if you want input boxes and strings in the robot editor\n# to use the game set for characters 32-126, as opposed to the protected\n# GUI set.\n\n# mask_midchars = 1\n\n# Set to 1/\"on\" to display the system mouse; set to \"only\" to display ONLY\n# the system mouse (i.e. hide the software mouse cursor). MegaZeux versions\n# prior to 2.93c treated a setting of 1 like \"only\".\n\n# system_mouse = off\n\n# Set to 1 to let MZX grab the mouse pointer while active.\n\n# grab_mouse = 0\n\n# Set to 1 to use a save slot system (similar to emulator save states)\n# instead of allowing for freeform text entry.\n\n# save_slots = 0\n\n# The filename of the saved games used by the save slot system. The world\n# name can be inserted into this name by including \"%w\" in the value below.\n# To use a \"%\" symbol as part of the target filename, use \"%%\". The slot\n# number will immediately follow this value.\n\n# save_slots_name = %w.\n\n# The extension of the saved games used by the save slot system. Keep this\n# set to .sav if you wish to be able to easily load slot saves using the\n# traditional load dialog.\n\n# save_slots_ext = .sav\n\n# Set to 1 to start MZX in testing mode, exactly as if Alt+T was pressed in\n# the editor. MegaZeux will exit after gameplay ends. This is intended to be\n# used with the command line or exec(), and only works with the \"megazeux\"\n# executable. Testing will start in the world's first board; a different\n# board can be chosen by setting the second option to a valid board number.\n\n# test_mode = 0\n# test_mode_start_board = 0\n\n# Set to 1 to enable MegaZeux's virtual filesystem (VFS). Currently, this\n# feature is used only to implement file caching in memory. This is disabled by\n# default for all platforms except 3DS and Vita. This feature is not available\n# on the NDS.\n\n# vfs_enable = 0\n\n# Set to 0 to disable file caching in memory when the VFS is active.\n# This will make MegaZeux use more memory. For platforms where the stdlib\n# memory allocation functions properly return NULL, MegaZeux will attempt to\n# reclaim memory from the file cache before an out of memory error occurs.\n# Enabled by default, but only takes effect if the VFS is also enabled.\n\n# vfs_enable_auto_cache = 1\n\n# This setting controls the total amount of memory that can be used by the\n# VFS file cache. This is a soft limit and can be exceeded in some situations.\n\n# vfs_max_cache_size = 16777216\n\n# This setting controls the largest size of an individual file that can be\n# cached within the VFS file cache. Larger files will not be cached to memory.\n\n# vfs_max_cache_file_size = 4194304\n\n\n###################\n# NETWORK OPTIONS #\n###################\n\n# If networking is enabled, MZX is permitted to bind ports and connect\n# out to the network or Internet. This option is a global kill switch\n# for the paranoid and allows the entire network layer to be disabled.\n\n# network_enabled = 1\n\n# By default, MZX will allow IPv4 and IPv6 connections and name resolution based\n# on whether the system has an IPv4 address and/or an IPv6 address configured.\n# This setting can be changed to \"ipv4\" to force IPv4 connections/lookups or\n# \"ipv6\" to force IPv6 connections/lookups (and IPv4-mapped IPv6 addresses if\n# no real IPv6 addresses are found). Default value is \"any\".\n\n# network_address_family = any\n\n# MegaZeux can support connections via a SOCKS4/5 proxy to punch through\n# connections that would otherwise require some sort of pipe intermediary.\n\n# socks_host = localhost\n# socks_port = 1080\n\n# For SOCKS5 proxies, a username and password can optionally be provided.\n# Note that if your socks host is remote, these will be sent unencrypted over\n# your network connection.\n\n# socks_username = your_username\n# socks_password = your_password\n\n# By default, MZX builds with networking enabled will allow the user to check\n# for updates (either manually or automatically; see update_auto_check below).\n# For platforms that support it, a full update can also be performed. This\n# option allows the updater system (both update checks and the full update\n# process if applicable) to be turned off entirely.\n\n# updater_enabled = 1\n\n# The update host is an HTTP server containing all data and metadata\n# associated with updates for MegaZeux. You normally won't want to\n# change this, but it may be useful if you are a third party distributing\n# unofficial updates. You may specify this up to times 16; connections will\n# be attempted in the order they are defined. Please note, this is not a\n# URL and should not contain protocol specifiers such as http://\n\n# By default, MZX will attempt to connect to updates.digitalmzx.com,\n# followed by updates.megazeux.org and updates.megazeux.net.\n\n# update_host = updates.digitalmzx.com\n# update_host = updates.digitalmzx.net\n# update_host = updates.megazeux.org\n# update_host = updates.megazeux.net\n\n# The update \"branch\" is consulted each time the updater runs. Typically,\n# there are only two branches, \"Stable\" and \"Unstable\". Please note, this\n# option is case sensitive. The \"Stable\" branch is the default, but if\n# you are interested in helping find bugs in the latest development\n# version, or are instructed to follow development, you will need to\n# change this to \"Unstable\". Third parties may use different branch names.\n\n# update_branch_pin = Stable\n\n# By default, MZX will check for updates on startup. There are three modes:\n# \"on\", which will display most messages; \"silent\", which will only display\n# certain errors and a message in the window caption; and \"off\", which disables\n# this feature altogether. The default setting is \"silent\" for Windows and\n# \"off\" for any other platform.\n\n# update_auto_check = silent\n\ninclude pad.config\n"
  },
  {
    "path": "contrib/archicons/README",
    "content": "These icons are intended for use on DigitalMZX and the MegaZeux README.md file only.\r\n\r\nThe icons for Windows, Mac OS X, Ubuntu, and source are based off of 1bpp logos made by Maxim in 2011.\r\nAll colorization/bordering to those icons and all other icons were made by Lachesis between 2011-2017.\r\n\r\nAll logos represented by these icons are property of their respective owners.\r\n\r\n--Lachesis\r\n"
  },
  {
    "path": "contrib/gdm2s3m/COPYING",
    "content": "\t\t    GNU GENERAL PUBLIC LICENSE\n\t\t       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.\n 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\t\t\t    Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n\t\t    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n\t\t\t    NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n\t\t     END OF TERMS AND CONDITIONS\n\n\t    How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program; if not, write to the Free Software\n    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "contrib/gdm2s3m/CREDITS",
    "content": "-- Alistair John Strachan (ajs)  <alistair@devzero.co.uk>\n\nPrinciple developer of the utility.\n\n-- Gilead Kutnick (Exophase)  <exophase@adelphia.net>\n\nFor porting MegaZeux from DOS to Windows/Linux, and for convincing me to\nfinish this utility.\n\n-- Hubert Lamontagne (MadBrain)  <madbrain@videotron.ca>\n\nFor supporting me during the writing of this utility and for providing\ndocumentation on s3m's effects and the included test GDMs.\n\n-- MenTaLguY  <>\n\nFor writing the gdm.txt reverse engineered GDM specification. Find the\nfile included herein.\n"
  },
  {
    "path": "contrib/gdm2s3m/README",
    "content": "CONTACT\n\nName:       Alistair John Strachan\nEmail:      <alistair@devzero.co.uk>\nSecondary:  <ajs@bitpack.net>\nWebsite:    http://www.bitpack.net/\n\nGDM2S3M UTILITY V1.7\n\nThis utility is designed to convert GDM, a format developed by the ALSA\ndeveloper Edward Schlunder, many years ago, into the ScreamTracker 3 file\nformat (S3M).\n\nIt is likely to contain bugs. See the CREDITS file for contact details\nshould you isolate a bug.\n\nBUILDING AND INSTALLING\n\nBuild the software with \"make\":\n\nOn Linux:\nmake BUILD=linux\n\nOn Windows MINGW:\nmake\n\nAs of version 1.3, compiling with Microsoft's free Visual C++ 2003 toolkit\ncompiler is supported. You can obtain this from:\n\nhttp://msdn.microsoft.com/visualc/vctoolkit2003/\n\nAt the time of writing, this compiler was free. A crappy batch file is\nprovided for building both a static library, and the standalone EXE.\n\nUnder the toolkit shell, type:\nbuild.bat\n\nUSAGE\n\ncd src\n./gdm2s3m <file.gdm> <file.s3m>\n\nSOFTWARE LICENSE\n\nThe entire contents of this archive are released under the terms of the\nGNU General Public License as published by the Free Software Foundation;\neither version 2, or (at your option) any later version.\n\nThe only exceptions to this are the provided GDM/S3M/MTM files under the\ntest/ directory, which are \"borrowed\" from various music archives and\nare believed to be unlicensed.\n\nAdditionally, the document gdm.txt is released under the OpenContent\nLicense (OPL), version 1.0.\n\nAll redistributions of this package must contain the above boilerplate text.\n"
  },
  {
    "path": "contrib/gdm2s3m/build.bat",
    "content": "@echo off\n\nrem clean up afterwards ;p\ndel src\\*.obj lib\\gdm2s3m.lib bin\\gdm2s3m.exe\n\nrem build it!\ncd src\ncl /c /O2 /I . error.c gdm.c s3m.c utility.c gdm2s3m.c\nlink /LIB /OUT:..\\lib\\gdm2s3m.lib error.obj gdm.obj s3m.obj utility.obj\nlink /OUT:..\\bin\\gdm2s3m.exe error.obj gdm.obj s3m.obj utility.obj gdm2s3m.obj\ncd .."
  },
  {
    "path": "contrib/gdm2s3m/doc/gdm.txt",
    "content": "GDM (General Digital Music) version 1.0\nFile Format Specification - Revision 2\n---------------------------------------------------------------------------\n\nSee the section near the end entitled \"License Information\" for terms\nof distribution and modification.\n\n= Document Status =========================================================\n\nThis document is in its second revision.  It is accurate to the best of the\nknowledge of the author(s), but may still contain innacuracies or\nomissions.  If you have corrections, additions, or general comments, please\nsee the section at the end entitled \"Contact Information\" for information\non how to contact the author(s).\n\n= Notes ===================================================================\n\nByte ordering is little-endian, unless otherwise indicated.\n\n== Module Header ==========================================================\n \n                                 byte offset\n        0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n0000: |'G'|'D'|'M'|xFE| Song title ....................................\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n      .................................................................\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n0020: ............... | Song musician .................................\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n      .................................................................\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n0040: ............... |x0D|x0A|x1A|'G'|'M'|'F'|'S'|Version|Tracker|TVer\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n0050: ... | Panning map ...............................................\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n      .................................................................\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n0070: ... |GV |Tpo|BPM|OrigFmt| Order Offset  |Ord|Pattern Offset |Pat|\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n0080: |SampHdr Offset |SampData Offset|Sam|Mesage Offset  | Message Off\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n0090: ... |Message Length |Scrolly Offset |ScrLen |TextGrph Offset|TGLn\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n00A0: ....|\n      +---+\n\n\tSong title - 32-char song title, null- terminated and filled\n\tSong musician - 32 chars, null- terminated and filled\n\tVersion - LSB is major format version, MSB is minor format version\n\tTracker - identification number for the tracker that generated\n\t          the file\n\t              00 - 2gdm\n\tTVer    - Tracker version; LSB is major version, MSB is minor\n\t          version\n\tPanning map - each byte indicates panning/usage information for\n\t              a channel:\n\t          0 -> 15: full left -> full right (8 = center)\n\t               16: surround sound\n\t              255: channel unused\n\tGV      - Global volume (0 to 64)\n\tTpo     - Tempo (speed; i.e. frames per row) (1 to 255)\n\tBPM     - Beats (1 beat = 24 frames) per minute (32 to 255)\n\tOrigFmt - Original module format (unreliable for 2gdm < 1.15):\n\t\t  1: MOD\n\t          2: MTM\n\t          3: S3M\n\t          4: 669\n\t          5: FAR\n\t          6: ULT\n\t          7: STM\n\t          8: MED\n\tOrder Offset - offset of order table in file\n\tOrd - length of order table \n\tPattern Offset - offset of pattern data in file\n\tPat - number of patterns\n\tSampHdr Offset - offset of sample header array in file\n\tSampData Offset - offset of sample data in file\n\tSam - number of samples\n\tMessage Off - offset of song message in file\n\tMessage Length - length of song message in bytes\n\tScrolly Offset - offset of scrolly script (huh?) in file\n\tScrLen - length of scrolly script in bytes\n\tTextGrph Offset - offset of text graphic (huh?) in file\n\tTGLn - length of text graphic in bytes\n\n== Sample Header ==========================================================\n \n                                 byte offset\n        0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n0000: | Sample Name....................................................\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n      ............................................................... |\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n0020: | Sample Filename ............................. |EMS|Length .....\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n0030: ....|Loop Begin.....|Loop End.......|Flg|C4Rate |DV |DP |\n      +---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n\n\tSample Name - null- terminated and filled sample name\n\tSample Filename - null-filled sample filename (8.3 convention)\n\tEMS - ems handle (ignore; should be 0)\n\tLength - sample length (in bytes?)\n\tLoop Begin - offset in sample of loop start (in samples?)\n\tLoop End - offset in sample of loop end (in samples?)\n\tFlg - flags:\n\t\tbit: 0 - enable loop\n\t\t     1 - 16 bit sample\n\t\t     2 - use default volume\n\t\t     3 - use default panning\n\t\t     4 - sample is LZW compressed\n\t\t     5 - stereo sample (left-right order)\n\t\t   6-7 - reserved (should be 0)\t\n\tC4Rate - playback rate in Hz (samples/sec) for C4 (typical is 8363)\n\tDV - default volume (0-64, 255 = no default volume)\n\tDP - default panning\n\t   0 -> 15: full left -> full right (8 = center)\n\t        16: surround sound\n\t       255: no default panning\n\n== Packed Pattern Format ==================================================\n\n                                 byte offset\n        0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\n      +---+---+-- --- -- -  -   -    -\n0000: |Length | Data....................\n      +---+---+-- --- -- -  -   -    -\n\n\tLength - the length of the pattern, including the two bytes\n\t         for the pattern length\n\tData   - the packed pattern data\n\nPattern data is stored in rows, each row terminated by a zero byte.  Every \npattern cell has a maximum of four effects in as many \"effect channels\".\n\n\tEach pattern cell in a row is encoded as follows:\n\n   <channel>[<note><sample>][<effect><effect data>[<effect><effect data>[...]]]\n\n\tchannel - bits 0-4: channel (0-31)\n\t                 5: note and sample follows\n\t                 6: effect(s) follow\n\t                 7: reserved (should be 0)\n\n\t   note - bits 0-6: note\n\t                 7: no retrig\n\n\t sample - sample (0-255)\n\n\t effect - bits 0-4: effect number\n\t                 5: another effect follows\n\t               6-7: effect channel (0-3)\n\n\teffect data - data for preceding effect\n\n== Effects ================================================================\n\nNote on parameters:\n    xx - indicates effect data byte as a whole\n     x - indicates the high-order nybble of the effect data byte\n     y - indicates the low-order nybble of the effect data byte\n\nEffect\tDescription/Parameters\n-------------------------------------------------------------------------\n0x00\tNo Effect\n0x01\tPortamento up - aka glissando or slide up\n\t  xx - rate in periods/frame\n0x02\tPortamento down - aka glissando or slide down\n\t  xx - rate in periods/frame\n0x03\tPortamento to note aka glissando or slide to note\n\t  xx - rate in periods/frame\n0x04\tVibrato - modulate note period\n\t  x - speed\n\t  y - depth\n0x05\tContinue portamento to note with volume slide\n\t  x - speed if up, 0 otherwise\n\t  y - speed if down, 0 otherwise\n0x06\tContinue vibrato with volume slide\n\t  x - speed\n\t  y - depth\n0x07\tTremolo - modulate note volume\n\t  x - speed\n\t  y - depth\n0x08\tTremor - \"strobe\" note volume\n\t  x - note volume normal for x frames\n\t  y - note volume 0 for y frames\n0x09\tSample Offset\n\t  xx - start playing sample at offset xx << 8 (samples)\n0x0A\tVolume Slide\n\t  x - speed if up, 0 otherwise\n\t  y - speed if down, 0 otherwise\n0x0B\tJump to Order\n\t  xx - order\n0x0C\tSet Volume\n\t  xx - new note volume (0 - 64)\n0x0D\tPattern break - end current pattern early and jump into the next\n\t                pattern in the order table\n\t  xx - row in next pattern to jump to\n0x0E\tProtracker Extended Effects\n\t  x - effect:\n\t\t0\tLow-pass Filter - originally to mitigate aliasing\n\t\t\tdue to resampling, now no effect\n\t\t\t  y - 0 = off, 1 = on\n\t\t1\tFine Portamento Up - increment note period by\n\t\t\t                     specified amount\n\t\t\t  y - amount (periods)\n\t\t2\tFine Portamento Down - decrement note period by\n\t\t\t                       specified amount\n\t\t\t  y - amount (periods)\n\t\t3\tGlissando Control\n\t\t\t  y - 0 = portamentos in period increments,\n\t\t\t      1 = portamentos in semitone increments (rates\n\t\t\t          still in periods)\n\t\t4\tSet Vibrato Waveform\n\t\t\t  y - 0 = sine,\n\t\t\t      1 = ramp down,\n\t\t\t      2 = square,\n\t\t\t      3 = random waveform\n\t\t5\tSet C4 Tuning (i.e. finetune)\n\t\t\t  y - rate in Hz:\n\t\t\t\t0 = 8363 Hz\n\t\t\t\t1 = 8424 Hz\n\t\t\t\t2 = 8485 Hz\n\t\t\t\t3 = 8547 Hz\n\t\t\t\t4 = 8608 Hz\n\t\t\t\t5 = 8671 Hz\n\t\t\t\t6 = 8734 Hz\n\t\t\t\t7 = 8797 Hz\n\t\t\t\t8 = 7894 Hz\n\t\t\t\t9 = 7951 Hz\n\t\t\t\tA = 8009 Hz\n\t\t\t\tB = 8067 Hz\n\t\t\t\tC = 8125 Hz\n\t\t\t\tD = 8184 Hz\n\t\t\t\tE = 8244 Hz\n\t\t\t\tF = 8303 Hz\n\t\t6\tLoop - control intra-pattern loops\n\t\t\t  y - 0 = start loop, else loop y times and end\n\t\t\t          loop\n\t\t7\tSet Tremolo Waveform\n\t\t\t  y - 0 = sine,\n\t\t\t      1 = ramp down,\n\t\t\t      2 = square,\n\t\t\t      3 = random waveform\n\t\t8\tExtra Fine Portamento Up\n\t\t\t  y - amount (quarter periods)\n\t\t9\tExtra Fine Portamento Down\n\t\t\t  y - amount (quarter periods)\n\t\tA\tFine Volume Slide Up\n\t\t\t  y - volume increment\n\t\tB\tFine Volume Slide Down\n\t\t\t  y - volume increment\n\t\tC\tCut Current Note\n\t\t\t  y - cut at frame y of current row\n\t\tD\tDelay Note Trigger\n\t\t\t  y - trigger at frame y of current row\n\t\tE\tExtend Current Row\n\t\t\t  y - extend row by y frames\n\t\tF\tInvert Sample Loop (?)\n\t\t\t  y - speed (?)\n0x0F\tSet Tempo\n\t  xx - 0 = stop/end song, 1-255 = set tempo (speed)\n0x10\tArpeggio - alternate between three notes, one every two frames\n\t  x - second note (primary note + x semitones)\n\t  y - third note (primary note + y semitones)\n0x11\tSet Internal Flag\n\t  x - flag number\n\t  y - new value\n0x12\tRetrigger Note + Volume Slide\n\t  x - volume slide by (2 * (signed)x) per frame\n\t  y - retrig at frame y of current row\n0x13\tSet Global Volume\n\t  xx - volume (0-64)\n0x14\tFine Vibrato\n\t  x - speed\n\t  y - depth (1/4 of normal vibrato depth)\n0x1E\tSpecial\n\t  x - sub-effect\n\t\t0 - general (sample control)\n\t\t  y - one of:\n\t\t\t0 - Surround Off\n\t\t\t1 - Surround On\n\t\t\t2 - Select Unidrectional Sample Loop\n\t\t\t3 - Select Bidirectional (ping-pong) Sample Loop\n\t\t\t4 - Play sample forward\n\t\t\t5 - Play sample backward\n\t\t\t6 - Monaural Sample\n\t\t\t7 - Stereo Sample\n\t\t\t8 - Stop Sample on End\n\t\t\t9 - Loop Sample on End\n\t\t8 - set pan position\n\t\t\ty - pan position\n\t\tD - adjust frequency\n\t\t\ty - increment in Hz\t\n0x1F\tSet BPM\n\t  xx - new BPM (32-255)\n\n== License Information ====================================================\n\nThis document is Copyright 1999 by MenTaLguY, and can be copied,\nmodified and subsequently redistributed under the terms of the OpenContent\nPublic License as indicated below:  \n\n   OpenContent License (OPL)\n   Version 1.0, July 14, 1998.\n   This document outlines the principles underlying the OpenContent (OC)\n   movement and may be redistributed provided it remains unaltered. For\n   legal purposes, this document is the license under which OpenContent\n   is made available for use.\n   The original version of this document may be found at\n   http://www.opencontent.org/opl.shtml\n\n   LICENSE\n   Terms and Conditions for Copying, Distributing, and Modifying\n   Items other than copying, distributing, and modifying the Content with\n   which this license was distributed (such as using, etc.) are outside\n   the scope of this license.\n\n   1. You may copy and distribute exact replicas of the OpenContent (OC)\n   as you receive it, in any medium, provided that you conspicuously and\n   appropriately publish on each copy an appropriate copyright notice and\n   disclaimer of warranty; keep intact all the notices that refer to this\n   License and to the absence of any warranty; and give any other\n   recipients of the OC a copy of this License along with the OC. You may\n   at your option charge a fee for the media and/or handling involved in\n   creating a unique copy of the OC for use offline, you may at your\n   option offer instructional support for the OC in exchange for a fee,\n   or you may at your option offer warranty in exchange for a fee. You\n   may not charge a fee for the OC itself. You may not charge a fee for\n   the sole service of providing access to and/or use of the OC via a\n   network (e.g. the Internet), whether it be via the world wide web,\n   FTP, or any other method.\n\n   2. You may modify your copy or copies of the OpenContent or any\n   portion of it, thus forming works based on the Content, and distribute\n   such modifications or work under the terms of Section 1 above,\n   provided that you also meet all of these conditions:\n\n     a) You must cause the modified content to carry prominent notices\n     stating that you changed it, the exact nature and content of the\n     changes, and the date of any change.\n\n     b) You must cause any work that you distribute or publish, that in\n     whole or in part contains or is derived from the OC or any part\n     thereof, to be licensed as a whole at no charge to all third parties\n     under the terms of this License, unless otherwise permitted under\n     applicable Fair Use law.\n\n   These requirements apply to the modified work as a whole. If\n   identifiable sections of that work are not derived from the OC, and\n   can be reasonably considered independent and separate works in\n   themselves, then this License, and its terms, do not apply to those\n   sections when you distribute them as separate works. But when you\n   distribute the same sections as part of a whole which is a work based\n   on the OC, the distribution of the whole must be on the terms of this\n   License, whose permissions for other licensees extend to the entire\n   whole, and thus to each and every part regardless of who wrote it.\n   Exceptions are made to this requirement to release modified works free\n   of charge under this license only in compliance with Fair Use law\n   where applicable.\n\n   3. You are not required to accept this License, since you have not\n   signed it. However, nothing else grants you permission to copy,\n   distribute or modify the OC. These actions are prohibited by law if\n   you do not accept this License. Therefore, by distributing or\n   translating the OC, or by deriving works herefrom, you indicate your\n   acceptance of this License to do so, and all its terms and conditions\n   for copying, distributing or translating the OC.\n   NO WARRANTY\n\n   4. BECAUSE THE OPENCONTENT (OC) IS LICENSED FREE OF CHARGE, THERE IS\n   NO WARRANTY FOR THE OC, TO THE EXTENT PERMITTED BY APPLICABLE LAW.\n   EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR\n   OTHER PARTIES PROVIDE THE OC \"AS IS\" WITHOUT WARRANTY OF ANY KIND,\n   EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE\n   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n   PURPOSE. THE ENTIRE RISK OF USE OF THE OC IS WITH YOU. SHOULD THE OC\n   PROVE FAULTY, INACCURATE, OR OTHERWISE UNACCEPTABLE YOU ASSUME THE\n   COST OF ALL NECESSARY REPAIR OR CORRECTION.\n\n   5. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN\n   WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MIRROR\n   AND/OR REDISTRIBUTE THE OC AS PERMITTED ABOVE, BE LIABLE TO YOU FOR\n   DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL\n   DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE OC, EVEN IF\n   SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH\n   DAMAGES.\n\n== Contact Information ====================================================\n\nMenTaLguY can be reached at mental@rydia.net\n"
  },
  {
    "path": "contrib/gdm2s3m/doc/s3m-form.txt",
    "content": "          Scream Tracker 3.01 BETA File Formats And Mixing Info\n          =====================================================\n\nThis document finally containts the OFFICIAL information on s3m format and\nmuch more. There might be some errors though I've even checked this a few\ntimes, so if something seems weird, don't just blindly believe it but think\nfirst if it could be just a typo or something.\n\n\n-----------------------------------------------------------------------------\nWhat is the S3M file format?\nWhat is the samplefile format?\nWhat is the adlib instrument format?\n\n\n        The first table describes the S3M header. All other blocks are\n        pointer to by pointers, so in theory they could be anywhere in\n        the file. However, the practical standard order is:\n        - header\n        - instruments in order\n        - patterns in order\n        - samples in order\n\n        Next the instrument header is described. It is stored to S3M\n        for each instrument and also saved to the start of all samples\n        saved from ST3. Same header is also used by Advance Digiplayer.\n\n        The third part is the description of the packed pattern format.\n\n\n                                S3M Module header\n          0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\n        Ŀ\n  0000:  Song name, max 28 chars (end with NUL (0))                    \n        Ĵ\n  0010:                                                1AhTyp x  x \n        Ĵ\n  0020: OrdNum InsNum PatNum  Flags  Cwt/v   Ffv  'S''C''R''M'\n        Ĵ\n  0030: g.vi.si.tm.v x  x  x  x  x  x  x  x  x  x Special\n        Ĵ\n  0040: Channel settings for 32 channels, 255=unused,+128=disabled     \n        Ĵ\n  0050:                                                                \n        Ĵ\n  0060: Orders; length=OrdNum (should be even)                         \n        Ĵ\n  xxx1: Parapointers to instruments; length=InsNum*2                   \n        Ĵ\n  xxx2: Parapointers to patterns; length=PatNum*2                      \n        Ĵ\n        xxx1=70h+orders\n        xxx2=70h+orders+instruments*2\n\n        Parapointers to file offset Y is (Y-Offset of file header)/16.\n        You could think of parapointers as segments relative to the\n        start of the S3M file.\n\n        Type    = File type: 16=ST3 module\n        Ordnum  = Number of orders in file (should be even!)\n\tInsnum\t= Number of instruments in file\n\tPatnum\t= Number of patterns in file\n        Cwt/v   = Created with tracker / version: &0xfff=version, >>12=tracker\n                        ST3.00:0x1300\n                        ST3.01:0x1301\n\tFfv\t= File format version;\n                        1=old version used long ago (samples signed)\n                        2=standard (samples unsigned)\n        Flags   =  [ These are old flags for Ffv1. Not supported in ST3.01\n                   |  +1:st2vibrato\n                   |  +2:st2tempo\n                   |  +4:amigaslides\n                   | +32:enable filter/sfx with sb\n                   ]\n                    +8: 0vol optimizations\n                          Automatically turn off looping notes whose volume\n                          is zero for >2 note rows.\n                   +16: amiga limits\n                          Disallow any notes that go beond the amiga hardware\n                          limits (like amiga does). This means that sliding\n                          up stops at B#5 etc. Also affects some minor amiga\n                          compatibility issues.\n                  +128: special custom data in file\n        Special = pointer to special custom data (not used by ST3.01)\n\tg.v\t= global volume (see next section)\n\tm.v\t= master volume (see next section) 7 lower bits\n                  bit 8: stereo(1) / mono(0)\n\ti.s\t= initial speed (command A)\n\ti.t    \t= initial tempo (command T)\n\n        Channel settings:\n          bit 8: channel enabled\n        bit 0-7: channel type\n                 0..7   : Left Sample Channel 1-8\n                 8..15  : Right Sample Channel 1-8\n                 16..31 : Adlib channels (9 melody + 5 drums)\n\n        Global volume directly divides the volume numbers used. So\n        if the module has a note with volume 48 and master volume\n        is 32, the note will be played with volume 24. This affects\n        both Gravis & SoundBlasters.\n\n        Master volume only affects the SoundBlaster. It controls\n        the amount of sample multiplication (see mixing section\n        of this doc). The bigger the value the bigger the output\n        volume (and thus quality) will be. However if the value\n        is too big, the mixer may have to clip the output to\n        fit the 8 bit output stream. The default value works\n        pretty well. Note that in stereo, the mastermul is\n        internally multiplied by 11/8 inside the player since\n        there is generally more room in the output stream.\n\n        Order list lists the order in which to play the patterns. 255=--\n        is the end of tune mark and 254=++ is just a marker that is\n        skipped.\n\n\n\n                        Digiplayer/ST3 samplefileformat\n          0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\n        Ŀ\n  0000: [T] Dos filename (12345678.ABC)                       MemSeg \n        Ĵ\n  0010: Length HI:lengLoopBegHI:LBegLoopEndHI:LendVol x [P][F]\n        Ĵ\n  0020: C2Spd  HI:C2sp x  x  x  x Int:Gp Int:512Int:lastused   \n        Ĵ\n  0030:  Sample name, 28 characters max... (incl. NUL)                 \n        Ĵ\n  0040:  ...sample name...                             'S''C''R''S'\n        Ĵ\n  xxxx:\tsampledata\n\n        Length / LoopBegin / LoopEnd are all 32 bit parameters although\n        ST3 only support file sizes up to 64,000 bytes. Files bigger\n        than that are clipped to 64,000 bytes when loaded to ST3. NOTE\n        that LoopEnd points to one byte AFTER the end of the sample,\n        so LoopEnd=100 means that byte 99.9999 (fixed) is the last one\n        played.\n        C2Spd  = Herz for middle C. ST3 only uses lower 16 bits.\n        Vol    = Default volume 0..64\n        Memseg = Pointer to sampledata\n                 Inside a sample or S3M, MemSeg tells the parapointer to\n                 the actual sampledata. In files all 24 bits are used.\n                 In memory the value points to the actual sample segment\n                 or Fxxx if sample is in EMS under handle xxx. In memory\n                 the first memseg byte is overwritten with 0 to create\n                 the dos filename terminator nul.\n        Int:Gp = Internal: Address of sample in gravis memory /32\n                 (only used while module in memory)\n        Int:512= Internal: flags for soundblaster loop expansion\n                 (only used while module in memory)\n        Int:las= Internal: last used position (only works with sb)\n                 (only used while module in memory)\n        [T]ype   1=Sample, 2=adlib melody, 3+=adlib drum (see below for\n                 adlib structure)\n\t[F]lags, +1=loop on\n\t\t +2=stereo (after Length bytes for LEFT channel,\n\t\t \t  another Length bytes for RIGHT channel)\n                 +4=16-bit sample (intel LO-HI byteorder)\n                 (+2/+4 not supported by ST3.01)\n        [P]ack   0=unpacked, 1=DP30ADPCM packing (not used by ST3.01)\n\n\n\n                              adlib instrument format\n          0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\n        Ŀ\n  0000: [T] Dos filename (12345678.123)                   00h00h00h\n        Ĵ\n  0010: D00D01D02D03D04D05D06D07D08D09D0AD0BVolDsk x  x \n        Ĵ\n  0020: C2Spd  HI:C2sp x  x  x  x  x  x  x  x  x  x  x  x \n        Ĵ\n  0030:  Sample name, 28 characters max... (incl. NUL)                 \n        Ĵ\n  0040:  ...sample name...                             'S''C''R''I'\n        Ĵ\n\n        [T]ype  = 2:amel 3:abd 4:asnare 5:atom 6:acym 7:ahihat\n        C2Spd   = 'Herz' for middle C. ST3 only uses lower 16 bits.\n                  Actually this is a modifier since there is no\n                  clear frequency for adlib instruments. It scales\n                  the note freq sent to adlib.\n        D00..D0B contains the adlib instrument specs packed like this:\n        modulator:                                              carrier:\n        D00=[freq.muliplier]+[?scale env.]*16+[?sustain]*32+    =D01\n                [?pitch vib]*64+[?vol.vib]*128\n\tD02=[63-volume]+[levelscale&1]*128+[l.s.&2]*64\t\t=D03\n\tD04=[attack]*16+[decay]\t\t\t\t\t=D05\n\tD06=[15-sustain]*16+[release]\t\t\t\t=D07\n\tD08=[wave select]\t\t\t\t\t=D09\n\tD0A=[modulation feedback]*2+[?additive synthesis]\n\tD0B=unused\n\n\n\n                          packed pattern format\n          0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\n        Ŀ\n  0000: Length  packed data, see below...\n        Ĵ\n\n        Length = length of packed pattern\n\n        Unpacked pattern is always 32 channels by 64 rows. Below\n        is the unpacked format st uses for reference:\n          Unpacked Internal memoryformat for patterns (not used in files):\n          NOTE: each channel takes 320 bytes, rows for each channel are\n                sequential, so one unpacked pattern takes 10K.\n          byte 0 - Note; hi=oct, lo=note, 255=empty note,\n                   254=key off (used with adlib, with samples stops smp)\n          byte 1 - Instrument      ;0=..\n          byte 2 - Volume          ;255=..\n          byte 3 - Special command ;255=..\n          byte 4 - Command info    ;\n\n        Packed data consits of following entries:\n        BYTE:what  0=end of row\n                   &31=channel\n                   &32=follows;  BYTE:note, BYTE:instrument\n                   &64=follows;  BYTE:volume\n                   &128=follows; BYTE:command, BYTE:info\n\n        So to unpack, first read one byte. If it's zero, this row is\n        done (64 rows in entire pattern). If nonzero, the channel\n        this entry belongs to is in BYTE AND 31. Then if bit 32\n        is set, read NOTE and INSTRUMENT (2 bytes). Then if bit\n        64 is set read VOLUME (1 byte). Then if bit 128 is set\n        read COMMAND and INFO (2 bytes).\n\n        For information on commands / how st3 plays them, see the\n        manual.\n\t\n\n-----------------------------------------------------------------------------\nWhat is the Stmik300old format?\nWhat is the STIMPORT file format?\nWhat is the SIMPLEXFILE format?\n\n        The old stmik 300 (never published because it has no\n        usable interface) want's that the samples in the module\n        are pre-extended for soundblaster. This means that\n        samples have an extra 512 bytes of loop after their\n        loopend (or silence if no loop). The save option for\n        old stmik does this extension. Otherwise the fileformat\n        is the same as for a normal S3M.\n\n        STIMPORT file format is supposed to be an easy way of\n        imputting weird data to st3. That is, it should be\n        easy to convert something to STIMPORT format. The format\n        goes like this:\n\n          0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F\n        Ŀ\n  0000: 'S''T''I''M''P''O''R''T'i.s x  x  x  x  x  x  x \n        Ĵ\n  0010: Notedata...\n        Ĵ\n  xxxx: Instruments...\n        Ĵ\n\n        There are no patterns or orders, just a continuos note stream\n        of following structures:\n          0    1    2    3    4\n        Ŀ\n        ChanNoteInstCmndInfo\n        \n        Chan is the channel number for the note. Value 0 stands\n        for next row. Value 255 stands for end of note stream.\n        Note/Inst/Cmnd/Info are like in the unpacked memory pattern\n        (see previous section)\n\n        After the notedata there can be instruments (optional)\n        Every instrument consits of the following block:\n          0    1    2    3    4\n        \n        InstLoopLoopBeg  LoopEnd  Length    Sampledata...\n        \n        Inst = number of instrument to load (0=end of instruments)\n        Loop = 1=loop enabled\n        LoopBeg/LoopEnd/Length = guess\n        Sampledata = raw sampledata for Length bytes.\n\n\n        SIMPLEXFORMAT has been used for some adlib musics. Dunno\n        if it's useful to you, but the st3 saves it like this:\n\n        First is stores the 32 first instruments (80 bytes each,\n        just like in a S3M). Then it stores the raw notedata\n        for the entire song. This means it goes through the\n        order list and saves all the patererns in that order\n        unpacked. The resulting file will be BIG. The idea\n        is that it should be fairly easy to convert to something\n        from this.\n\n\n-----------------------------------------------------------------------------\nWhat is C2SPD?\nHow to calculate the note frequencies like ST3?\nHow does ST3 mix depending on master volume?\n\n\n        Finetuning (C2SPD) is actually the frequency in herz for\n        the note C4. Why is it C2SPD? Well, originally in ST2\n        the middle note was C2 and the name stuck. Later in ST3\n        the middle note was raised to C4 for more octaves... So\n        actually C2SPD should be called C4SPD...\n\n\n        Table for note frequencies used by ST3:\n\n\t  note:  C    C#   D    D#   E    F    F#   G    G#   A    A#   B\n\tperiod: 1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,0960,0907\n\t\n\tmiddle octave is 4.\n\t\n\t\t\t 8363 * 16 * ( period(NOTE) >> octave(NOTE) )\n\tnote_st3period = --------------------------------------------\n\t \t\t middle_c_finetunevalue(INSTRUMENT)\n\t\t\t \n\tnote_amigaperiod = note_st3period / 4\n\t\t\t \n\tnote_herz=14317056 / note_st3period\n\n        Note that ST3 uses period values that are 4 times larger than the\n        amiga to allow for extra fine slides (which are 4 times finer\n        than normal fine slides).\n\n\n        How ST3 mixes:\n\n        1) volumetable is created in the following way:\n\n        > volumetable[volume][sampledata]=volume*(sampledata-128)/64;\n\n        NOTE: sampledata in memory is unsigned in ST3, so the -128 in the\n              formula converts it so that the volumetable output is signed.\n\n        2) postprocessing table is created with this pseudocode:\n\n        > z=mastervol&127;\n        > if(z<0x10) z=0x10;\n        > c=2048*16/z;\n        > a=(2048-c)/2;\n        > b=a+c;\n        >                     { 0                , if x < a\n        > posttable[x+1024] = { (x-a)*256/(b-a)  , if a <= x < b\n        >                     { 255              , if x > b\n\n        3) mixing the samples\n\n        output=1024\n        for i=0 to number of channels\n                output+=volumetable[volume*globalvolume/64][sampledata];\n        next\n        realoutput=posttable[output]\n\n\n        This is how the mixing is done in theory. In practice it's a bit\n        different for speed reasons, but the result is the same.\n\n\n-----------------------------------------------------------------------------\nThat's it. If there are any more questions, that's too bad :-) If you have\nproblems with the S3M format, try to contact somone who already supports\nit (there is quite a lot of support for the S3M already, so that shouldn't\nbe too hard...) Good luck for reading / writing Scream Tracker files.\n\n\n"
  },
  {
    "path": "contrib/gdm2s3m/doc/s3m_effects.txt",
    "content": "Axx Set speed to xx (default 06)\nBxx Jump to order xx\nCxx Break pattern to row xx\nD0y.Volume slide down by y\nDx0.Volume slide up by x\nDFy.Fine volume slide down by y(!=0,F)\nDxF.Fine volume slide up by x(!=0,F)\nExx.Slide down by xx (range 00..DF)\nEFx.Fine slide down by x\nEEx.Extra fine slide down by x\nFxx.Slide up by xx (range 00..DF)\nFFx.Fine slide up by x\nFEx.Extra fine slide up by x\nGxx.Tone portamento, speed xx\nHxy.Vibrato, speed x and depth y\nIxy Tremor with ontime x and offtime y\nJxy.Arpeggio; adds halfnotes: x,y,0\nKxy.Dual command: H00 & Dxy\nLxy.Dual command: G00 & Dxy\nMxx --\nNxx --\nOxx Set SampleOffset=xx00h\nPxx --\nQxy.Retrig note (see right)\nRxy.Tremolo with speed x and depth y\nSxy Special commands (see top-right)\nTxx Tempo=xx (range 20..FF, def:7dh)\nUxx Fine vibrato (see Hxx)\nVxx Set global volume (0..40)\nWxx --\nXxx unused amiga command 8xx ( Set panning in modern software (X00 left, XFF right) )\nZxx --\n\nS0x Set filter (1/0/F=on/off/for) (ignored)\nS1x Set glissando control x=0/1\nS2x Set finetune\nS3x Set vibrato waveform to x\nS4x Set tremolo waveform to x\nS5x --\nS6x --\nS7x --\nS8x Set pan position (GRAVIS only in original ST3, ok in modern software)\nS9x --\nSAx old stereo control (ignored)\nSBx Loop (x=cnt)/StartLoop (x=0)\nSCx NoteCut in x frames\nSDx NoteDelay for x frames\nSEx PatternDelay for x notes\nSFx FunkRepeat, x=speed (ignored)\n\nVibrato waveforms (S3x & S4x)\n    0:sinewave    1:ramp down\n    2:squarewave  3:random\n    +4:don't reset waveform\n\nRetrig note infobyte (Qxy)\n    y=frames between retrigs\n    x=volume change per retrig:\n    0:0    4:-8   8:?   C:8\n    1:-1   5:-16  9:1   D:16\n    2:-2   6:*2/3 A:2   E:*3/2\n    3:-4   7:*1/2 B:4   F:*2\n"
  },
  {
    "path": "contrib/gdm2s3m/src/Makefile.in",
    "content": "##\n# gdm2s3m Makefile fragment\n##\n\n.PHONY: ${gdm2s3m}_clean\n\n.SUFFIXES: .c\n\ngdm2s3m_src = contrib/gdm2s3m/src\ngdm2s3m_obj = contrib/gdm2s3m/src/.build\n\ngdm2s3m_cflags := -W -Wmissing-format-attribute -Wpointer-arith -Wcast-align\ngdm2s3m_cflags += -Wwrite-strings -I${gdm2s3m_src}\n\ngdm2s3m_objs = \\\n    ${gdm2s3m_obj}/error.o ${gdm2s3m_obj}/gdm.o \\\n    ${gdm2s3m_obj}/s3m.o ${gdm2s3m_obj}/utility.o \\\n    ${gdm2s3m_obj}/gdm2s3m.o\n\n${gdm2s3m_obj}/%.o: ${gdm2s3m_src}/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${CFLAGS} ${gdm2s3m_cflags} -c $< -o $@\n\n-include $(gdm2s3m_objs:.o=.d)\n\n${gdm2s3m_objs}: $(filter-out $(wildcard ${gdm2s3m_obj}), ${gdm2s3m_obj})\n\ngdm2s3m_clean:\n\t$(if ${V},,@echo \"  RM      \" ${gdm2s3m_obj})\n\t${RM} -r ${gdm2s3m_obj}\n"
  },
  {
    "path": "contrib/gdm2s3m/src/error.c",
    "content": "/**\n *  Copyright (C) 2003-2004  Alistair John Strachan  (alistair@devzero.co.uk)\n *\n *  This is free software; you can redistribute it and/or modify it under\n *  the terms of the GNU General Public License as published by the Free\n *  Software Foundation; either version 2, or (at your option) any later\n *  version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <stdio.h>\n\n#include \"error.h\"\n\nvoid error_handle (const char *file, int line, error_code error)\n{\n  char msg[256];\n\n  switch (error) {\n    case FILE_OPEN:\n      sprintf (msg, \"problem opening file\");\n      break;\n\n    case FILE_INVALID:\n      sprintf (msg, \"invalid file format\");\n      break;\n\n    case OUT_OF_DATA:\n      sprintf (msg, \"conversion failure\");\n      break;\n\n    case CONVERT_ERROR:\n      sprintf (msg, \"convertor ran out of data\");\n      break;\n\n    default:\n      sprintf (msg, \"unknown error\");\n      break;\n  }\n\n  /* echo error string to output */\n  fprintf (stderr, \"%s:%d: %s.\\n\", file, line, msg);\n\n  /* abnormal quit */\n  exit (-1);\n}\n"
  },
  {
    "path": "contrib/gdm2s3m/src/error.h",
    "content": "/**\n *  Copyright (C) 2003-2004  Alistair John Strachan  (alistair@devzero.co.uk)\n *\n *  This is free software; you can redistribute it and/or modify it under\n *  the terms of the GNU General Public License as published by the Free\n *  Software Foundation; either version 2, or (at your option) any later\n *  version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __ERROR_H\n#define __ERROR_H\n\n#include \"types.h\"\n\n__G_BEGIN_DECLS\n\n#define error(x) error_handle(__FILE__, __LINE__, x);\n\n/**\n * possible errors\n */\n\ntypedef enum {\n  FILE_OPEN,\n  FILE_INVALID,\n  CONVERT_ERROR,\n  OUT_OF_DATA\n} error_code;\n\n/* function prototypes */\nvoid error_handle (const char *file, int line, error_code error);\n\n__G_END_DECLS\n\n#endif /* __ERROR_H */\n"
  },
  {
    "path": "contrib/gdm2s3m/src/gdm.c",
    "content": "/**\n *  Copyright (C) 2003-2004  Alistair John Strachan  (alistair@devzero.co.uk)\n *\n *  This is free software; you can redistribute it and/or modify it under\n *  the terms of the GNU General Public License as published by the Free\n *  Software Foundation; either version 2, or (at your option) any later\n *  version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n\n#include \"gdm.h\"\n#include \"error.h\"\n#include \"utility.h\"\n\nstruct GDM_file *load_gdm (uint8_t *stream, size_t stream_len)\n{\n  uint8_t *backup = stream, *trace, *trace2, magic[4];\n  struct GDM_header *header;\n  struct GDM_pattern *pattern;\n  struct GDM_samhdr *sample;\n  struct GDM_file *gdm;\n  uint32_t address;\n  int i;\n\n  /* allocate gdm file structure */\n  gdm = malloc (sizeof (struct GDM_file));\n  memset (gdm, 0, sizeof (struct GDM_file));\n\n  /* level of indirection */\n  header = &gdm->header;\n\n  /* extract header */\n  check_s_to_a (backup, stream_len, magic, &stream, 4);\n\n  /* check it's a valid file */\n  if (strncmp ((char *)magic, GDM_MAGIC, 4) != 0)\n    error (FILE_INVALID);\n\n  /* get artist and title */\n  check_s_to_a (backup, stream_len, header->title, &stream, 32);\n  check_s_to_a (backup, stream_len, header->artist, &stream, 32);\n\n  /* skip junk */\n  stream += 3 + 4;\n\n  /* get file version */\n  check_s_to_a (backup, stream_len, &header->version, &stream, 2);\n  CHECK_ENDIAN_16 (&header->version);\n\n  /* get tracker and version */\n  check_s_to_a (backup, stream_len, &header->tracker, &stream, 2);\n  check_s_to_a (backup, stream_len, &header->tracker_ver, &stream, 2);\n  CHECK_ENDIAN_16 (&header->tracker);\n  CHECK_ENDIAN_16 (&header->tracker_ver);\n\n  /* get panning map */\n  check_s_to_a (backup, stream_len, &header->panning, &stream, 32);\n\n  /* get global volume, tempo and bpm */\n  check_s_to_a (backup, stream_len, &header->global_vol, &stream, 1);\n  check_s_to_a (backup, stream_len, &header->bpm, &stream, 1);\n  check_s_to_a (backup, stream_len, &header->tempo, &stream, 1);\n\n  /* get original format */\n  check_s_to_a (backup, stream_len, &header->origfmt, &stream, 2);\n  CHECK_ENDIAN_16 (&header->origfmt);\n\n  /* ORDER HEADER ---------------------------------------------------------- */\n  check_s_to_a (backup, stream_len, &address, &stream, 4);\n  check_s_to_a (backup, stream_len, &gdm->order_len, &stream, 1);\n  CHECK_ENDIAN_32 (&address);\n\n  /* stupid gdm bug */\n  gdm->order_len++;\n\n  /* put stream at order offset */\n  trace = (uint8_t *) (backup + address);\n\n  /* allocate space for orders */\n  gdm->orders.patterns = malloc (gdm->order_len);\n\n  /* copy orders */\n  memcpy (gdm->orders.patterns, trace, gdm->order_len);\n\n  /* PATTERN SEGMENT ------------------------------------------------------- */\n  check_s_to_a (backup, stream_len, &address, &stream, 4);\n  check_s_to_a (backup, stream_len, &gdm->numpatterns, &stream, 1);\n  CHECK_ENDIAN_32 (&address);\n\n  /* stupid gdm bug */\n  gdm->numpatterns++;\n\n  /* put stream at pattern offset */\n  trace = (uint8_t *) (backup + address);\n\n  /* allocate pattern space */\n  gdm->patterns = malloc (sizeof (struct GDM_pattern) * gdm->numpatterns);\n\n  /* read in encoded pattern data */\n  for (i = 0; i < gdm->numpatterns; i++) {\n    pattern = &gdm->patterns[i];\n\n    /* get length of data */\n    check_s_to_a (backup, stream_len, &pattern->length, &trace, 2);\n    CHECK_ENDIAN_16 (&pattern->length);\n\n    /* remove top two bytes */\n    pattern->length -= 2;\n\n    /* allocate data space */\n    pattern->data = malloc (pattern->length);\n\n    /* get pattern data */\n    memcpy (pattern->data, trace, pattern->length);\n\n    /* move along */\n    trace += pattern->length;\n  }\n\n  /* SAMPLE HEADERS -------------------------------------------------------- */\n  check_s_to_a (backup, stream_len, &address, &stream, 4);\n  CHECK_ENDIAN_32 (&address);\n\n  /* move stream along to sample header */\n  trace = (uint8_t *) (backup + address);\n\n  /* get sample data segment */\n  check_s_to_a (backup, stream_len, &address, &stream, 4);\n  check_s_to_a (backup, stream_len, &gdm->numsamples, &stream, 1);\n  CHECK_ENDIAN_32 (&address);\n\n  /* stupid gdm bug */\n  gdm->numsamples++;\n\n  /* set up initial offset */\n  trace2 = (uint8_t *) (backup + address);\n\n  /* allocate sample space */\n  gdm->samples = malloc (sizeof (struct GDM_sample) * gdm->numsamples);\n  memset (gdm->samples, 0, sizeof (struct GDM_sample) * gdm->numsamples);\n\n  /* read in sample headers and copy sample data */\n  for (i = 0; i < gdm->numsamples; i++) {\n    sample = &gdm->samples[i].header;\n\n    /* get sample name */\n    check_s_to_a (backup, stream_len, sample->name, &trace, 32);\n\n    /* get sample filename */\n    check_s_to_a (backup, stream_len, sample->filename, &trace, 12);\n\n    /* skip EMS handle (should be zero) */\n    trace++;\n\n    /* get sample data length */\n    check_s_to_a (backup, stream_len, &sample->length, &trace, 4);\n    CHECK_ENDIAN_32 (&sample->length);\n\n    /* get loop beginning/end */\n    check_s_to_a (backup, stream_len, &sample->begin, &trace, 4);\n    check_s_to_a (backup, stream_len, &sample->end, &trace, 4);\n    CHECK_ENDIAN_32 (&sample->begin);\n    CHECK_ENDIAN_32 (&sample->end);\n\n    /* get sample flags */\n    check_s_to_a (backup, stream_len, &sample->flags, &trace, 1);\n\n    /* get rate data */\n    check_s_to_a (backup, stream_len, &sample->rate, &trace, 2);\n    CHECK_ENDIAN_16 (&sample->rate);\n\n    /* get volume */\n    check_s_to_a (backup, stream_len, &sample->volume, &trace, 1);\n\n    /* get pan */\n    check_s_to_a (backup, stream_len, &sample->pan, &trace, 1);\n\n    /* allocate space for sample data */\n    gdm->samples[i].data = malloc (sample->length);\n\n    /* copy sample data */\n    memcpy (gdm->samples[i].data, trace2, sample->length);\n\n    /* move sample data pointer along */\n    trace2 += sample->length;\n  }\n\n  /* gdm structure */\n  return gdm;\n}\n\n#if 0\nvoid info_gdm (struct GDM_file *gdm)\n{\n  struct GDM_header *header = &gdm->header;\n  struct GDM_samhdr *samhdr;\n  int i;\n\n  union version {\n    uint16_t v;\n    struct {\n      uint8_t maj;\n      uint8_t min;\n    } ver;\n  } version;\n\n  /* prepare version dump */\n  memcpy (&version, &header->tracker_ver, 2);\n\n  /* basic information */\n  PRINT_OUT (\"title:\\t\\t\\t%s\\n\", header->title);\n  PRINT_OUT (\"artist:\\t\\t\\t%s\\n\", header->artist);\n  PRINT_DEBUG (\"version:\\t\\t%u\\n\", header->version);\n  PRINT_DEBUG (\"tracker:\\t\\t%u\\n\", header->tracker);\n  PRINT_DEBUG (\"tracker version:\\t%u.%u\\n\", version.ver.maj, version.ver.min);\n  PRINT_OUT (\"global vol:\\t\\t%u\\n\", header->global_vol);\n  PRINT_OUT (\"tempo/speed:\\t\\t%u/%u\\n\", header->tempo, header->bpm);\n\n  /* dump originating format */\n  PRINT_OUT (\"original format:\\t\");\n\n  switch (header->origfmt) {\n    case 1: PRINT_OUT (\"MOD\\n\"); break;\n    case 2: PRINT_OUT (\"MTM\\n\"); break;\n    case 3: PRINT_OUT (\"S3M\\n\"); break;\n    case 4: PRINT_OUT (\"669\\n\"); break;\n    case 5: PRINT_OUT (\"FAR\\n\"); break;\n    case 6: PRINT_OUT (\"ULT\\n\"); break;\n    case 7: PRINT_OUT (\"STM\\n\"); break;\n    case 8: PRINT_OUT (\"MED\\n\"); break;\n  }\n\n#ifdef DEBUG\n  /* panning map */\n  PRINT_OUT (\"\\n\\t\\tL      MM      R  S?:\\n\");\n\n  for (i = 0; i < 32; i++)\n    if (header->panning[i] != 255) {\n      /* channel's active */\n      PRINT_OUT (\" chan %d:\\t\", i);\n\n      /* padding */\n      if (header->panning[i] != 16)\n      {\n        for (int j = 0; j < header->panning[i]; j++)\n          PRINT_OUT (\" \");\n      }\n      else\n        PRINT_OUT (\"                  \");\n\n      /* and a marker.. */\n      PRINT_OUT (\"|\\n\");\n    }\n#endif\n\n  PRINT_OUT (\"\\n\");\n\n  /* dump sample information */\n  for (i = 0; i < gdm->numsamples; i++) {\n    samhdr = &gdm->samples[i].header;\n\n    PRINT_OUT (\"name:\\t\\t\\t%s\\n\", samhdr->name);\n    PRINT_OUT (\"filename:\\t\\t%s\\n\", samhdr->filename);\n    PRINT_OUT (\"data length:\\t\\t%d\\n\", samhdr->length);\n    PRINT_DEBUG (\"loop begin:\\t\\t%d\\n\", samhdr->begin);\n    PRINT_DEBUG (\"loop end:\\t\\t%d\\n\", samhdr->end);\n    PRINT_DEBUG (\"flags:\\t\\t%d\\n\", samhdr->flags);\n    PRINT_DEBUG (\"rate:\\t\\t%d\\n\", samhdr->rate);\n    PRINT_OUT (\"volume:\\t\\t\\t%d\\n\", samhdr->volume);\n    PRINT_OUT (\"pan:\\t\\t\\t%d\\n\", samhdr->pan);\n    PRINT_OUT (\"\\n\");\n  }\n\n  /* dump order information */\n  PRINT_OUT (\"%d orders:\\n\", gdm->order_len);\n\n  for (i = 0; i < gdm->order_len; i++)\n    PRINT_OUT (\"%d \", gdm->orders.patterns[i]);\n\n  PRINT_OUT (\"\\n\\n\");\n\n  /* dump pattern information */\n  PRINT_OUT (\"%d patterns:\\n\", gdm->numpatterns);\n\n  for (i = 0; i < gdm->numpatterns; i++)\n    PRINT_OUT (\"  pattern %d, %d bytes.\\n\", i, gdm->patterns[i].length);\n\n  PRINT_OUT (\"\\n\");\n}\n#endif // 0\n\nvoid free_gdm (struct GDM_file *gdm)\n{\n  int i;\n\n  /* free up order data */\n  free (gdm->orders.patterns);\n\n  /* free up pattern data */\n  for (i = 0; i < gdm->numpatterns; i++)\n    free (gdm->patterns[i].data);\n  free (gdm->patterns);\n\n  /* free up sample data */\n  for (i = 0; i < gdm->numsamples; i++)\n    free (gdm->samples[i].data);\n  free (gdm->samples);\n\n  /* free it */\n  free (gdm);\n}\n"
  },
  {
    "path": "contrib/gdm2s3m/src/gdm.h",
    "content": "/**\n *  Copyright (C) 2003-2004  Alistair John Strachan  (alistair@devzero.co.uk)\n *\n *  This is free software; you can redistribute it and/or modify it under\n *  the terms of the GNU General Public License as published by the Free\n *  Software Foundation; either version 2, or (at your option) any later\n *  version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __GDM_H\n#define __GDM_H\n\n#include \"types.h\"\n\n__G_BEGIN_DECLS\n\n/**\n * below are the file structures used by the GDM file format\n */\n\n#define GDM_MAGIC \"GDM\\xFE\"\n\nstruct GDM_header {\n  uint8_t title[33];\n  uint8_t artist[33];\n  uint16_t version;        /* GDM format version (always 1.0) */\n  uint16_t tracker;        /* tracker used to generate file */\n  uint16_t tracker_ver;    /* version of tracker used */\n  uint8_t panning[32];     /* panning map, 1 byte per channel */\n  uint8_t global_vol;      /* global volume */\n  uint8_t tempo;\n  uint8_t bpm;\n  uint16_t origfmt;        /* original format */\n};\n\nstruct GDM_order {\n  uint8_t *patterns;\n};\n\nstruct GDM_pattern {\n  uint16_t length;\n  uint8_t *data;\n};\n\nstruct GDM_samhdr {\n  uint8_t name[33];\n  uint8_t filename[13];    /* original filename */\n  uint32_t length;         /* length of sample, in bytes */\n  uint32_t begin;          /* loop beginning, in samples */\n  uint32_t end;            /* loop ending, in samples */\n  uint8_t flags;           /* sample type flags */\n  uint16_t rate;           /* playback speed in Hz, should be 8363 */\n  uint8_t volume;          /* default volume */\n  uint8_t pan;             /* default panning */\n};\n\n/**\n * end of file structures\n */\n\nstruct GDM_file {\n  struct GDM_header header;\n  struct GDM_order orders;\n  struct GDM_pattern *patterns;\n  struct GDM_sample *samples;\n  uint8_t order_len;\n  uint8_t numpatterns;\n  uint8_t numsamples;\n};\n\nstruct GDM_sample {\n  struct GDM_samhdr header;  /* file structure */\n  uint8_t *data;             /* sample data */\n};\n\n/* function prototypes */\nstruct GDM_file *load_gdm (uint8_t *stream, size_t stream_len);\nvoid info_gdm (struct GDM_file *gdm);\nvoid free_gdm (struct GDM_file *gdm);\n\n__G_END_DECLS\n\n#endif /* __GDM_H */\n"
  },
  {
    "path": "contrib/gdm2s3m/src/gdm2s3m.c",
    "content": "/**\n *  This software is a \"lite\" version of gdm2s3m, for Exophase's MegaZeux.\n *\n *  Copyright (C) 2003-2005  Alistair John Strachan  (alistair@devzero.co.uk)\n *\n *  This is free software; you can redistribute it and/or modify it under\n *  the terms of the GNU General Public License as published by the Free\n *  Software Foundation; either version 2, or (at your option) any later\n *  version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"types.h\"\n#include \"gdm.h\"\n#include \"s3m.h\"\n#include \"error.h\"\n#include \"utility.h\"\n#include \"gdm2s3m.h\"\n\nint convert_gdm_s3m (const char *gdmfile, const char *s3mfile)\n{\n  struct GDM_file *gdm;\n  struct S3M_file *s3m;\n  size_t filesize;\n  uint8_t *stream;\n  FILE *handle;\n\n  /* open module */\n  if ((handle = fopen (gdmfile, \"rb\")) == NULL)\n    return -1;\n\n  /* get file size */\n  fseek (handle, 0, SEEK_END);\n  filesize = (uint32_t) ftell (handle);\n  fseek (handle, 0, SEEK_SET);\n\n  /* allocate space */\n  stream = malloc (filesize);\n\n  /* stream into memory */\n  if (fread (stream, 1, filesize, handle) != filesize)\n    return -2;\n\n  /* close file */\n  fclose (handle);\n\n  /* try to load it */\n  gdm = load_gdm (stream, filesize);\n\n  /* free up stream */\n  free (stream);\n\n  /* do convert routine */\n  s3m = convert_gdm_to_s3m (gdm);\n\n  /* free up gdm */\n  free_gdm (gdm);\n\n  /* open up s3m file */\n  if ((handle = fopen (s3mfile, \"wb\")) == NULL)\n    return -1;\n\n  /* save s3m file */\n  stream = save_s3m (s3m, &filesize);\n\n  /* free up s3m */\n  free_s3m (s3m);\n\n  /* write out file */\n  if (fwrite (stream, 1, filesize, handle) != filesize)\n    return -3;\n\n  /* close file */\n  fclose (handle);\n\n  /* free up the stream */\n  free (stream);\n\n  /* all ok */\n  return 0;\n}\n"
  },
  {
    "path": "contrib/gdm2s3m/src/gdm2s3m.h",
    "content": "/**\n *  Copyright (C) 2003-2004  Alistair John Strachan  (alistair@devzero.co.uk)\n *\n *  This is free software; you can redistribute it and/or modify it under\n *  the terms of the GNU General Public License as published by the Free\n *  Software Foundation; either version 2, or (at your option) any later\n *  version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __GDM2S3M_H\n#define __GDM2S3M_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* function prototypes */\nint convert_gdm_s3m (const char *gdmfile, const char *s3mfile);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* __GDM2S3M_H */\n"
  },
  {
    "path": "contrib/gdm2s3m/src/s3m.c",
    "content": "/**\n *  Copyright (C) 2003-2004  Alistair John Strachan  (alistair@devzero.co.uk)\n *\n *  This is free software; you can redistribute it and/or modify it under\n *  the terms of the GNU General Public License as published by the Free\n *  Software Foundation; either version 2, or (at your option) any later\n *  version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n\n#include \"s3m.h\"\n#include \"gdm2s3m.h\"\n#include \"error.h\"\n#include \"utility.h\"\n\nuint8_t *save_s3m (struct S3M_file *s3m, size_t *stream_len)\n{\n  const uint8_t junk1[4] = { 0x1A, 16, 0, 0 };\n  const uint8_t junk2[6] = { 0, 0, 0x17, 0x32, 2, 0 };\n  const uint8_t junk3[13] = { 0xb0, 0, 0xfc, 0, 0, 0xe, 0x79,\n                              0x10, 0xe4, 0, 0, 0, 0 };\n  const uint8_t junk4[1] = { 1 };\n  const uint8_t junk5[2] = { 0, 0 };\n  const uint8_t junk6[14] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };\n\n  uint8_t gap[16], *gap2, memseg[3], *backup = NULL, *stream = NULL;\n  uint32_t offset = 0, spare, segpad, sample_table, pattern_table;\n  struct S3M_order *orders = &s3m->orders;\n  struct S3M_header *hdr = &s3m->header;\n  char s3m_magic[4], sam_magic[4];\n  uint16_t segment;\n  int i;\n\n  /* copy magics */\n  memcpy (s3m_magic, S3M_MAGIC, 4);\n  memcpy (sam_magic, S3M_SAMPLE_MAGIC, 4);\n\n  /* zero gap */\n  memset (gap, 0, 16);\n\n  /* zero memseg dummy */\n  memset (memseg, 0, 3);\n\n  /* initialise stream_len */\n  (*stream_len) = 0;\n\n  /* dump song title */\n  check_a_to_s (&backup, stream_len, &hdr->title, &stream, 28);\n\n  /* pad with junk */\n  check_a_to_s (&backup, stream_len, (uint8_t *)&junk1, &stream, 4);\n\n  /* dump quantities */\n  CHECK_ENDIAN_16 (&hdr->numorders);\n  CHECK_ENDIAN_16 (&hdr->numinst);\n  CHECK_ENDIAN_16 (&hdr->numpatterns);\n  check_a_to_s (&backup, stream_len, &hdr->numorders, &stream, 2);\n  check_a_to_s (&backup, stream_len, &hdr->numinst, &stream, 2);\n  check_a_to_s (&backup, stream_len, &hdr->numpatterns, &stream, 2);\n  CHECK_ENDIAN_16 (&hdr->numorders);\n  CHECK_ENDIAN_16 (&hdr->numinst);\n  CHECK_ENDIAN_16 (&hdr->numpatterns);\n\n  /* pad with more junk */\n  check_a_to_s (&backup, stream_len, (uint8_t *)&junk2, &stream, 6);\n\n  /* dump magic bytes */\n  check_a_to_s (&backup, stream_len, s3m_magic, &stream, 4);\n\n  /* dump global volume, speed, tempo */\n  check_a_to_s (&backup, stream_len, &hdr->global_vol, &stream, 1);\n  check_a_to_s (&backup, stream_len, &hdr->speed, &stream, 1);\n  check_a_to_s (&backup, stream_len, &hdr->tempo, &stream, 1);\n\n  /* pad with remaining junk */\n  check_a_to_s (&backup, stream_len, (uint8_t *)&junk3, &stream, 13);\n\n  /* channel settings */\n  check_a_to_s (&backup, stream_len, &hdr->chansett, &stream, 32);\n\n  /* ORDER HEADER ---------------------------------------------------------- */\n  check_a_to_s (&backup, stream_len, orders->patterns, &stream,\n                  hdr->numorders);\n\n  /* update offset */\n  offset = 96 + hdr->numorders;\n\n  /* figure out how much space we've used up */\n  sample_table = offset;\n  pattern_table = offset + 2 * hdr->numinst;\n\n  /* calculate padding size */\n  segpad = hdr->numorders + 2 * hdr->numinst + 2 * hdr->numpatterns;\n  segpad += (segpad % 16) ? 16 - segpad % 16 : 0;\n  segpad -= hdr->numorders;\n  segment = 2 * hdr->numinst + 2 * hdr->numpatterns;\n\n  /* allocate space for sample table, pattern table and padding */\n  gap2 = malloc (segpad);\n  memset (gap2, 0, segpad);\n\n  /* pad up to segment boundary */\n  check_a_to_s (&backup, stream_len, gap2, &stream, segment);\n\n  /* write channel pan values */\n  check_a_to_s (&backup, stream_len, &s3m->panmap, &stream, 32);\n\n  /* pad up to segment boundary */\n  check_a_to_s (&backup, stream_len, &gap2[segment], &stream,\n                 segpad - segment);\n\n  /* free padding */\n  free (gap2);\n\n  /* update offset */\n  offset += 32 + segpad;\n\n  /* SAMPLE HEADERS -------------------------------------------------------- */\n\n  /* dump sample headers */\n  for (i = 0; i < hdr->numinst; i++) {\n    struct S3M_samhdr *sample = &s3m->samples[i].header;\n\n    /* write out junk byte */\n    check_a_to_s (&backup, stream_len, (uint8_t *)&junk4, &stream, 1);\n\n    /* dos filename */\n    check_a_to_s (&backup, stream_len, &sample->filename, &stream, 12);\n\n    /* memseg (dummy) */\n    check_a_to_s (&backup, stream_len, memseg, &stream, 3);\n\n    /* sample length */\n    CHECK_ENDIAN_32 (&sample->length);\n    check_a_to_s (&backup, stream_len, &sample->length, &stream, 4);\n    CHECK_ENDIAN_32 (&sample->length);\n\n    /* loop beginning */\n    CHECK_ENDIAN_32 (&sample->begin);\n    check_a_to_s (&backup, stream_len, &sample->begin, &stream, 4);\n\n    /* loop ending */\n    CHECK_ENDIAN_32 (&sample->end);\n    check_a_to_s (&backup, stream_len, &sample->end, &stream, 4);\n\n    /* volume */\n    check_a_to_s (&backup, stream_len, &sample->volume, &stream, 1);\n\n    /* more junk */\n    check_a_to_s (&backup, stream_len, (uint8_t *)&junk5, &stream, 2);\n\n    /* flags */\n    check_a_to_s (&backup, stream_len, &sample->flags, &stream, 1);\n\n    /* rate */\n    CHECK_ENDIAN_16 (&sample->rate);\n    check_a_to_s (&backup, stream_len, &sample->rate, &stream, 2);\n\n    /* last bit of junk */\n    check_a_to_s (&backup, stream_len, (uint8_t *)&junk6, &stream, 14);\n\n    /* sample name */\n    check_a_to_s (&backup, stream_len, &sample->name, &stream, 28);\n\n    /* sample magic */\n    check_a_to_s (&backup, stream_len, sam_magic, &stream, 4);\n\n    /* work out segment */\n    segment = (uint16_t)(offset / 16);\n\n    /* update sample parapointer */\n    CHECK_ENDIAN_16 (&segment);\n    memcpy (&backup[sample_table], &segment, 2);\n\n    /* update sample_table pointer */\n    sample_table += 2;\n\n    /* update offset */\n    offset += 80;\n  }\n\n  /* PATTERN SEGMENT ------------------------------------------------------- */\n\n  /* dump pattern lengths followed by pattern data */\n  for (i = 0; i < hdr->numpatterns; i++) {\n    struct S3M_pattern *pattern = &s3m->patterns[i];\n\n    /* add on size of length data to length */\n    pattern->length += 2;\n\n    /* write out length of pattern */\n    CHECK_ENDIAN_16 (&pattern->length);\n    check_a_to_s (&backup, stream_len, &pattern->length, &stream, 2);\n    CHECK_ENDIAN_16 (&pattern->length);\n\n    /* write out pattern data */\n    check_a_to_s (&backup, stream_len, pattern->data, &stream,\n                    pattern->length - 2);\n\n    /* work out segment */\n    segment = (uint16_t)(offset / 16);\n\n    /* update pattern parapointer */\n    CHECK_ENDIAN_16 (&segment);\n    memcpy (&backup[pattern_table], &segment, 2);\n\n    /* update pattern_table pointer */\n    pattern_table += 2;\n\n    /* update offset */\n    offset += pattern->length;\n\n    /* work out padding */\n    segpad = (offset % 16) ? 16 - offset % 16 : 0;\n\n    /* write out padding */\n    check_a_to_s (&backup, stream_len, gap, &stream, segpad);\n\n    /* update offset */\n    offset += segpad;\n  }\n\n  /* SAMPLE DATA -------------------------------------------------------- */\n\n  /**\n   * we can use the parapointers we've been storing to re-access the samples\n   * saved before at the appropriate file offsets. Quite useful.\n   */\n\n  sample_table = 96 + hdr->numorders;\n\n  /* dump sample data */\n  for (i = 0; i < hdr->numinst; i++) {\n    struct S3M_sample *sample = &s3m->samples[i];\n\n    /* write out sample data */\n    check_a_to_s (&backup, stream_len, sample->data, &stream,\n                   sample->header.length);\n\n    /* extract offset from parapointer table */\n    memcpy (&segment, &backup[sample_table], 2);\n    CHECK_ENDIAN_16 (&segment);\n\n    /* update sample table pointer */\n    sample_table += 2;\n\n    /* real byte offset */\n    spare = segment * 16 + 13;\n\n    /* work out segment */\n    segment = (uint16_t)(offset / 16);\n\n    /* stick it into that awkward 24bit parapointer */\n    CHECK_ENDIAN_16 (&segment);\n    memcpy (&backup[spare + 1], &segment, 2);\n\n    /* update offset! */\n    offset += sample->header.length;\n\n    /* work out padding */\n    segpad = (offset % 16) ? 16 - offset % 16 : 0;\n\n    /* write out padding */\n    check_a_to_s (&backup, stream_len, gap, &stream, segpad);\n\n    /* update offset */\n    offset += segpad;\n  }\n\n  /* return entire stream */\n  return backup;\n}\n\n/**\n * written by madbrain; contact him if something here isn't right\n */\nstatic void remap_effects (uint8_t gdm_effect, uint8_t gdm_param,\n                           uint8_t *dest_effect, uint8_t *dest_param)\n{\n  uint8_t s3m_effect = 0;\n  uint8_t s3m_param = 0;\n\n  switch(gdm_effect)\n  {\n    case 0: // \"No Effect\"\n      s3m_effect=0;\n      s3m_param=gdm_param;\n      break;\n\n    case 1: // Portamento up\n      s3m_effect='F'-'@';\n      s3m_param=gdm_param;\n      if(s3m_param>=0xE0)\n        s3m_param=0xDF;\n      break;\n\n    case 2: // Portamento down\n      s3m_effect='E'-'@';\n      s3m_param=gdm_param;\n      if(s3m_param>=0xE0)\n        s3m_param=0xDF;\n      break;\n\n    case 3: // Glissando\n      s3m_effect='G'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 4: // Vibrato\n      s3m_effect='H'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 5: // Portamento + volume slide\n      s3m_effect='L'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 6: // Vibrato + volume slide\n      s3m_effect='K'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 7: // Tremolo\n      s3m_effect='R'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 8: // Tremor/stupid effect nobody uses\n      s3m_effect='I'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 9: // Sample offset\n      s3m_effect='O'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 0xa: // Volume slide\n      s3m_effect='D'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 0xb: // Order jump\n      s3m_effect='B'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 0xd: // Pattern break\n      s3m_effect='C'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 0xe: // EXTENDED EFFECTS, DO THIS LATER\n      switch(gdm_param>>4)\n      {\n        case 0: // Set filter (AMIGA!)\n          s3m_effect='S'-'@';\n          s3m_param= 0x00 + (gdm_param & 15);\n          break;\n        case 1: // Fine slide up\n          s3m_effect='F'-'@';\n          s3m_param= 0xF0 + (gdm_param & 15);\n          break;\n        case 2: // Fine slide down\n          s3m_effect='E'-'@';\n          s3m_param= 0xF0 + (gdm_param & 15);\n          break;\n        case 3: // Glissando control\n          s3m_effect='S'-'@';\n          s3m_param= 0x10 + (gdm_param & 15);\n          break;\n        case 4: // Vibrato waveform\n          s3m_effect='S'-'@';\n          s3m_param= 0x30 + (gdm_param & 15);\n          break;\n        case 5: // Set finetune\n          s3m_effect='S'-'@';\n          s3m_param= 0x20 + (gdm_param & 15);\n          break;\n        case 6: // Pattern loop\n          s3m_effect='S'-'@';\n          s3m_param= 0xB0 + (gdm_param & 15);\n          break;\n        case 7: // Tremolo waveform\n          s3m_effect='S'-'@';\n          s3m_param= 0x40 + (gdm_param & 15);\n          break;\n        case 8: // Extra Fine slide up\n          s3m_effect='F'-'@';\n          s3m_param= 0xE0 + (gdm_param & 15);\n          break;\n        case 9: // Extra Fine slide down\n          s3m_effect='E'-'@';\n          s3m_param= 0xE0 + (gdm_param & 15);\n          break;\n        case 0xA: // Fine volume up\n          s3m_effect='D'-'@';\n          s3m_param= 0x0F + ((gdm_param & 15) << 4);\n          break;\n        case 0xB: // Fine volume down\n          s3m_effect='D'-'@';\n          s3m_param= 0xF0 + (gdm_param & 15);\n          break;\n        case 0xC: // Cut note after x ticks\n          s3m_effect='S'-'@';\n          s3m_param= 0xC0 + (gdm_param & 15);\n          break;\n        case 0xD: // Delay note for x ticks\n          s3m_effect='S'-'@';\n          s3m_param= 0xD0 + (gdm_param & 15);\n          break;\n        case 0xE: // Delay whole pattern, in ticks???!?\n          s3m_effect='S'-'@';\n          s3m_param= 0xE0 + (gdm_param & 15);\n          break;\n        case 0xF: // Invert loop?!? Funk repeat?!?\n          s3m_effect='S'-'@';\n          s3m_param= 0xF0 + (gdm_param & 15);\n          break;\n      }\n      break;\n\n    case 0xf: // \"Set tempo\", apparently sets speed\n      s3m_effect='A'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 0x10: // Arpeggio\n      s3m_effect='J'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 0x11: // Set internal flag, no idea what this is\n      s3m_effect=0; // Convert to no effect\n      s3m_param=gdm_param;\n      break;\n\n    case 0x12: // Retrigger note\n      s3m_effect='Q'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 0x13: // Global volume, 0-63 in both s3m and gdm\n      s3m_effect='V'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 0x14: // Fine vibrato, crashes mzx\n      s3m_effect='U'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    case 0x1e: // Special\n      if((gdm_param>>4)==8)\n      {\n        s3m_effect='S'-'@';\n        s3m_param= 0x80 + (gdm_param & 15);\n      }\n      else\n      {\n        s3m_effect=0;\n        s3m_param=gdm_param;\n      }\n      break;\n\n    case 0x1f: // Set bpm, ie set tempo?!?\n      s3m_effect='T'-'@';\n      s3m_param=gdm_param;\n      break;\n\n    default:\n      fprintf (stderr, \"gdm2s3m: implemented effect! (contact ajs)\\n\");\n      break;\n  }\n\n  *dest_effect = s3m_effect;\n  *dest_param = s3m_param;\n}\n\nstruct S3M_file *convert_gdm_to_s3m (struct GDM_file *gdm)\n{\n  struct S3M_file *s3m;\n  int i, j;\n\n  /* optimal channel settings for ST3 */\n  uint8_t chansett[] = { 0,   8,   1,   9,   2,   10,  3,   11,\n                         4,   12,  5,   13,  6,   14,  7,   15,\n                         255, 255, 255, 255, 255, 255, 255, 255,\n                         255, 255, 255, 255, 255, 255, 255, 255 };\n\n  /* allocate s3m struct */\n  s3m = malloc (sizeof (struct S3M_file));\n\n  /* clear it */\n  memset (s3m, 0, sizeof (struct S3M_file));\n\n  /* copy module title (as much as possible) */\n  memcpy (s3m->header.title, gdm->header.title, 27);\n\n  /* pass over order, sample, pattern counts */\n  if (gdm->order_len & 1)\n    s3m->header.numorders = gdm->order_len;\n  else\n    s3m->header.numorders = gdm->order_len + 1;\n  s3m->header.numinst = gdm->numsamples;\n  s3m->header.numpatterns = gdm->numpatterns;\n\n  /* pass over global volume, speed, tempo */\n  s3m->header.global_vol = gdm->header.global_vol;\n  s3m->header.speed = gdm->header.bpm;\n  s3m->header.tempo = gdm->header.tempo;\n\n  /* channel settings have no appreciable function */\n  memcpy (s3m->header.chansett, chansett, 32);\n\n  /* handle pan map */\n  for (i = 0; i < 32; i++) {\n    uint8_t g = gdm->header.panning[i];\n    uint8_t *s = &s3m->panmap[i];\n\n    if (g == 16) {     /* surround sound */\n      *s = 8 + 0x20;\n    }\n    else if (g > 16) { /* disabled */\n      *s = 0;\n    }\n    else {             /* normal panning */\n      *s = g + 0x20;\n    }\n  }\n\n  /* allocate order space */\n  s3m->orders.patterns = malloc (s3m->header.numorders);\n  memset (s3m->orders.patterns, 254, s3m->header.numorders);\n  s3m->orders.patterns[s3m->header.numorders - 1] = 255;  // FIXME\n\n  /* copy over order data */\n  memcpy (s3m->orders.patterns, gdm->orders.patterns, gdm->order_len);\n\n  /* allocate pattern space */\n  s3m->patterns =\n    malloc (sizeof (struct S3M_pattern) * s3m->header.numpatterns);\n\n  /* copy over patterns */\n  for (i = 0; i < s3m->header.numpatterns; i++) {\n    struct S3M_pattern *pattern = &s3m->patterns[i];\n    struct GDM_pattern *gpattern = &gdm->patterns[i];\n    uint8_t *pdata = NULL, *backup = NULL;\n    size_t plen = 0;\n\n    /* big pattern expander and collapser */\n    for (j = 0; j < gpattern->length; j++) {\n      uint8_t vol = 0, norm = 0, note = 0, effect = 0;\n      uint8_t b = 0, n, s, e, ep, se, sep, sv;\n\n      /* it's a zero byte */\n      if (gpattern->data[j] != 0) {\n        /* copy first 5 bits over */\n        b = gpattern->data[j] & 0x1F;\n\n        /* see if we've got a note on */\n        if (gpattern->data[j] & 0x20)\n          note = 1;\n\n        /* see if we've got an effect */\n        if (gpattern->data[j] & 0x40)\n          effect = 1;\n      }\n\n      /* copy over note data */\n      if (note) {\n        /* get note and sample */\n        n = (gpattern->data[j + 1] & 0x7F) - 1;\n        s = gpattern->data[j + 2];\n\n        /* mask in note on */\n        b |= 0x20;\n\n        /* increment j */\n        j += 2;\n      }\n\n      /* copy over effect */\n      if (effect) {\n        do\n        {\n          /* get effect number */\n          e = gpattern->data[j + 1];\n\n          /* get effect parameter */\n          ep = gpattern->data[j + 2];\n\n          /* check for volume event */\n          if ((e & 0x1F) == 0xC) {\n            /* we can't have more than one volume effect per note */\n            if (vol == 1)\n              break;\n            vol = 1;\n\n            /* mask on that we've got one */\n            b |= 0x40;\n\n            /* se for the volume */\n            sv = ep;\n          } else {\n            /* we can't have more than one of these */\n            if (norm == 1)\n              break;\n            norm = 1;\n\n            /* mask on that we've got a real effect */\n            b |= 0x80;\n\n            /* convert effect */\n            remap_effects (e & 0x1F, ep, &se, &sep);\n          }\n\n          /* move along */\n          j += 2;\n        }\n        while (e & 0x20);\n      }\n\n      /* copy over */\n      check_a_to_s (&backup, &plen, &b, &pdata, 1);\n\n      /* write out note and sample */\n      if (note) {\n        check_a_to_s (&backup, &plen, &n, &pdata, 1);\n        check_a_to_s (&backup, &plen, &s, &pdata, 1);\n      }\n\n      /* volume event */\n      if (effect && vol) {\n        check_a_to_s (&backup, &plen, &sv, &pdata, 1);\n      }\n\n      /* normal effect */\n      if (effect && norm) {\n        check_a_to_s (&backup, &plen, &se, &pdata, 1);\n        check_a_to_s (&backup, &plen, &sep, &pdata, 1);\n      }\n    }\n\n    /* copy over length */\n    pattern->length = (uint16_t)plen;\n\n    /* copy over data */\n    pattern->data = backup;\n  }\n\n  /* allocate sample space */\n  s3m->samples = malloc (sizeof (struct S3M_sample) * s3m->header.numinst);\n\n  /* zero it all */\n  memset (s3m->samples, 0, sizeof (struct S3M_sample) * s3m->header.numinst);\n\n  /* copy over samples */\n  for (i = 0; i < s3m->header.numinst; i++) {\n    struct S3M_sample *sample = &s3m->samples[i];\n    struct GDM_sample *gsample = &gdm->samples[i];\n\n    /* ------------ HEADER ------------ */\n\n    /* copy filename 8.3 format */\n    memcpy (sample->header.filename, gsample->header.filename, 13);\n\n    /* copy sample attributes */\n    sample->header.length = gsample->header.length;\n    sample->header.begin = gsample->header.begin;\n    sample->header.end = gsample->header.end - 1;\n    sample->header.volume = gsample->header.volume;\n    sample->header.flags = gsample->header.flags & 1;\n    sample->header.rate = gsample->header.rate;\n\n    /* 16 bit sample */\n    if (gsample->header.flags & 0x2)\n      sample->header.flags |= 0x4;\n\n    /* stereo sample */\n    if (gsample->header.flags & 0x20)\n      sample->header.flags |= 0x2;\n\n    /* sample name (truncated) */\n    memcpy (sample->header.name, gsample->header.name, 27);\n\n    /* --------- SAMPLE DATA ---------- */\n\n    /* allocate sample space */\n    sample->data = malloc (sample->header.length);\n\n    /* copy sample data */\n    memcpy (sample->data, gsample->data, sample->header.length);\n  }\n\n  /* return new s3m struct */\n  return s3m;\n}\n\nvoid free_s3m (struct S3M_file *s3m)\n{\n  int i;\n\n  /* free up order data */\n  free (s3m->orders.patterns);\n\n  /* free up pattern data */\n  for (i = 0; i < s3m->header.numpatterns; i++)\n    free (s3m->patterns[i].data);\n  free (s3m->patterns);\n\n  /* free up sample data */\n  for (i = 0; i < s3m->header.numinst; i++)\n    free (s3m->samples[i].data);\n  free (s3m->samples);\n\n  /* free it */\n  free (s3m);\n}\n"
  },
  {
    "path": "contrib/gdm2s3m/src/s3m.h",
    "content": "/**\n *  Copyright (C) 2003-2004  Alistair John Strachan  (alistair@devzero.co.uk)\n *\n *  This is free software; you can redistribute it and/or modify it under\n *  the terms of the GNU General Public License as published by the Free\n *  Software Foundation; either version 2, or (at your option) any later\n *  version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __S3M_H\n#define __S3M_H\n\n#include \"types.h\"\n#include \"gdm.h\"\n\n__G_BEGIN_DECLS\n\n/**\n * below are the file structures used by the GDM file format\n */\n\n#define S3M_MAGIC         \"SCRM\"\n#define S3M_SAMPLE_MAGIC  \"SCRS\"\n\nstruct S3M_header {\n  uint8_t title[28];      /* song title, last byte must be \\0 */\n  uint16_t numorders;     /* number of orders */\n  uint16_t numinst;       /* number of adlib instruments */\n  uint16_t numpatterns;   /* number of wav samples */\n  uint8_t global_vol;     /* global volume */\n  uint8_t speed;\n  uint8_t tempo;\n  uint8_t chansett[32];   /* channel settings */\n};\n\nstruct S3M_order {\n  uint8_t *patterns;\n};\n\nstruct S3M_samhdr {\n  uint8_t filename[13];    /* original filename */\n  uint32_t length;         /* length of sample, in bytes */\n  uint32_t begin;          /* loop beginning, in samples */\n  uint32_t end;            /* loop ending, in samples */\n  uint8_t volume;          /* default volume */\n  uint8_t flags;           /* sample type flags */\n  uint16_t rate;           /* playback speed in Hz, should be 8363 */\n  uint8_t name[28];\n};\n\nstruct S3M_pattern {\n  uint16_t length;\n  uint8_t *data;\n};\n\n/**\n * end of file structures\n */\n\nstruct S3M_file {\n  struct S3M_header header;\n  struct S3M_order orders;\n  struct S3M_sample *samples;\n  struct S3M_pattern *patterns;\n  uint8_t panmap[32];\n};\n\nstruct S3M_sample {\n  struct S3M_samhdr header;  /* file structure */\n  uint8_t *data;             /* sample data */\n};\n\n/* function prototypes */\nuint8_t *save_s3m (struct S3M_file *s3m, size_t *stream_len);\nstruct S3M_file *convert_gdm_to_s3m (struct GDM_file *gdm);\nvoid free_s3m (struct S3M_file *s3m);\n\n__G_END_DECLS\n\n#endif /* __S3M_H */\n"
  },
  {
    "path": "contrib/gdm2s3m/src/types.h",
    "content": "/**\n *  Copyright (C) 2003-2004  Alistair John Strachan  (alistair@devzero.co.uk)\n *\n *  This is free software; you can redistribute it and/or modify it under\n *  the terms of the GNU General Public License as published by the Free\n *  Software Foundation; either version 2, or (at your option) any later\n *  version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __TYPES_H\n#define __TYPES_H\n\n#ifdef __cplusplus\n#define __G_BEGIN_DECLS extern \"C\" {\n#define __G_END_DECLS   }\n#else\n#define __G_BEGIN_DECLS\n#define __G_END_DECLS\n#endif /* __cplusplus */\n\n__G_BEGIN_DECLS\n\n#ifndef _MSC_VER\n\n#include <inttypes.h>\n\n#else // _MSC_VER\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n/* MSVC can use windows types from windows.h */\ntypedef BYTE uint8_t;\ntypedef USHORT uint16_t;\ntypedef ULONG uint32_t;\n\n/* MSVC doesn't understand inline */\n#define inline\n\n#endif // !_MSC_VER\n\n__G_END_DECLS\n\n#endif /* __TYPES_H */\n"
  },
  {
    "path": "contrib/gdm2s3m/src/utility.c",
    "content": "/**\n *  Copyright (C) 2003-2004  Alistair John Strachan  (alistair@devzero.co.uk)\n *\n *  This is free software; you can redistribute it and/or modify it under\n *  the terms of the GNU General Public License as published by the Free\n *  Software Foundation; either version 2, or (at your option) any later\n *  version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n\n#include \"utility.h\"\n#include \"error.h\"\n\nstatic void stream_to_alloc (void *dest, uint8_t **src, uint32_t n)\n{\n  /* copy count blocks */\n  memcpy (dest, *src, n);\n\n  /* increment stream offset */\n  *src += n;\n}\n\nstatic void alloc_to_stream (void *src, uint8_t **dest, uint32_t n)\n{\n  /* copy count blocks */\n  memcpy (*dest, src, n);\n\n  /* increment stream offset */\n  *dest += n;\n}\n\nvoid check_s_to_a (uint8_t *start, size_t size, void *dest, uint8_t **src,\n                   uint32_t n)\n{\n  const size_t diff = *src - start;\n\n  /* check we've got enough of the stream */\n  if (diff + n > size)\n    error (OUT_OF_DATA);\n\n  /* otherwise, call on */\n  stream_to_alloc (dest, src, n);\n}\n\nvoid check_a_to_s (uint8_t **start, size_t *size, void *src, uint8_t **dest, uint32_t n)\n{\n  const size_t diff = *dest - *start;\n\n  /* check we've got enough allocated */\n  if (diff + n > *size) {\n    /* new size */\n    *size = diff + n;\n\n    /* increase allocation size */\n    *start = realloc (*start, *size);\n\n    /* reassign pointer */\n    *dest = (uint8_t *) (*start + diff);\n  }\n\n  /* otherwise, call on */\n  alloc_to_stream (src, dest, n);\n}\n"
  },
  {
    "path": "contrib/gdm2s3m/src/utility.h",
    "content": "/**\n *  Copyright (C) 2003-2004  Alistair John Strachan  (alistair@devzero.co.uk)\n *\n *  This is free software; you can redistribute it and/or modify it under\n *  the terms of the GNU General Public License as published by the Free\n *  Software Foundation; either version 2, or (at your option) any later\n *  version.\n *\n *  This program is distributed in the hope that it will be useful,\n *  but WITHOUT ANY WARRANTY; without even the implied warranty of\n *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n *  GNU General Public License for more details.\n *\n *  You should have received a copy of the GNU General Public License\n *  along with this program; if not, write to the Free Software\n *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __UTILITY_H\n#define __UTILITY_H\n\n#include \"types.h\"\n\n__G_BEGIN_DECLS\n\n#if  defined(WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \\\n     defined(__i386__) || defined(__ia64__) || \\\n    (defined(__alpha__) || defined(__alpha)) || \\\n     defined(__arm__) || \\\n    (defined(__mips__) && defined(__MIPSEL__)) || \\\n     defined(__SYMBIAN32__) || \\\n     defined(__x86_64__) || \\\n     defined(__LITTLE_ENDIAN__)\n\n/* little endian systems; no magic necessary */\n\n#define CHECK_ENDIAN_16(x)\n#define CHECK_ENDIAN_32(x)\n\n#else // !LITTLE_ENDIAN\n\n/* big endian systems; we need to swap data before storage */\n\nstatic inline void gdm2s3m_swap16(uint16_t *var)\n{\n  *var = ((*var << 8) | (*var >> 8));\n}\n\nstatic inline void gdm2s3m_swap32(uint32_t *var)\n{\n  *var = ((*var << 24) | ((*var << 8) & 0x00FF0000) |\n         ((*var >> 8) & 0x0000FF00) | (*var >> 24));\n}\n\n#define CHECK_ENDIAN_16(x) gdm2s3m_swap16(x)\n#define CHECK_ENDIAN_32(x) gdm2s3m_swap32(x)\n\n#endif // LITTLE_ENDIAN\n\n/**\n * We support MSVC and gcc; MSVC's macro support is awful\n */\n\n#ifdef _MSC_VER\n#define PRINT_OUT   printf\n#define PRINT_ERR   printf\n#define PRINT_DEBUG printf\n#else /* _MSC_VER */\n#if defined(WIN32) || defined(__WIN32__)\n/**\n * An MSYS console flushing bug requires fflush after\n * fprintf (contrary to spec).\n */\n#define PRINT_OUT(...) \\\n  { \\\n    fprintf(stdout, __VA_ARGS__); \\\n    fflush(stdout); \\\n  }\n#define PRINT_ERR(...) \\\n  { \\\n    fprintf(stderr, __VA_ARGS__); \\\n    fflush(stderr); \\\n  }\n#else /* __WIN32__ */\n/**\n * LINUX, etc. does not require fflush().\n */\n#define PRINT_OUT(...) \\\n  { \\\n    fprintf(stdout, __VA_ARGS__); \\\n  }\n#define PRINT_ERR(...) \\\n  { \\\n    fprintf(stderr, __VA_ARGS__); \\\n  }\n#endif /* __linux__ */\n\n#ifdef DEBUG\n#define PRINT_DEBUG(...) \\\n  { \\\n    PRINT_OUT(\"D: \" __VA_ARGS__); \\\n  }\n#else\n#define PRINT_DEBUG(...)\n#endif /* DEBUG */\n#endif /* !_MSC_VER */\n\n/* function prototypes */\nvoid check_s_to_a (uint8_t *start, size_t size, void *dest,\n                   uint8_t **src, uint32_t n);\nvoid check_a_to_s (uint8_t **start, size_t *size, void *src,\n                   uint8_t **dest, uint32_t n);\n\n__G_END_DECLS\n\n#endif /* __UTILITY_H */\n"
  },
  {
    "path": "contrib/hlp2html/README.md",
    "content": "# hlp2html resources\n\nThese are the extra resources required by hlp2html. They should not be\ndistributed with MegaZeux builds as they will instead be embedded into the\ngenerated HTML help files.\n\nMost of these files are derivatives of files originally created for the\nDigitalMZX Online Help Viewer in 2012.\n\n## Credits:\n\n* Lachesis: this README, the css files, and hlp2html.\n* Dr. Lancer-X: MegaZeux fonts.\n"
  },
  {
    "path": "contrib/hlp2html/fonts.css",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2012 Alice Rowan <petrifiedrowan@gmail.com>\n * Copyright (C) 2012 Dr. Lancer-X\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * fonts.css: Embeddable MZX fonts.\n */\n\n@font-face\n{\n  font-family: 'mzxfont';\n\n  src: /* mzxfont.eot */\n       url(data:application/vnd.ms-fontobject;charset=utf-8;base64,uoEAAAiBAAABAAIAAAAAAAIABgkAAAAAAAABAPQBAAAAAExQAQAAAEAAABAAAAAAAAAAAAEAAAAAAAAA6VMSAgAAAAAAAAAAAAAAAAAAAAAAABAAbQBlAGcAYQB6AGUAdQB4AAAADABNAGUAZABpAHUAbQAAACAAVgBlAHIAcwBpAG8AbgAgADAAMAAxAC4AMAAwADAAIAAAABIAbQBlAGcAYQB6AGUAdQB4ACAAAAAAAAABAAAADgCAAAMAYEZGVE1hme1gAACA7AAAABxHREVGAToAJAAAgMQAAAAoT1MvMlcsUroAAAFoAAAAYGNtYXDByceQAAAD6AAAAYpjdnQgACECeQAABXQAAAAEZ2FzcP//AAMAAIC8AAAACGdseWbOkpDHAAAHkAAAbVxoZWFk+IPf3AAAAOwAAAA2aGhlYQQ2AUQAAAEkAAAAJGhtdHgPAA19AAAByAAAAh5sb2Nh7tAIzAAABXgAAAIYbWF4cAFpAfEAAAFIAAAAIG5hbWW5N82IAAB07AAAAltwb3N0JecELAAAd0gAAAl0AAEAAAABAAACElPpXw889QAfAjAAAAAAzAhPCwAAAADMCE8LAAD//wFAApoAAAAIAAIAAAAAAAAAAQAAApr//wBaAUAAAAAAAUAAAQAAAAAAAAAAAAAAAAAAAAQAAQAAAQsBwAAcAAAAAAACAAAAAQABAAAAQAAuAAAAAAAEAUAB9AAFAAACigK7AAAAjAKKArsAAAHfADEBAgAAAgAGCQAAAAAAAAAAAAEQAABAAAAAAAAAAABQZkVkAMAAIOD/AjAAAABaApoAAQAAAAEAAAAAAWcB3wAAACAAAQFAACEAAAAAAUAAAAFAAAAAUAAoABQAFAAUABQAZABQAFAAAAAoAGQAFAB4ABQAFAAoABQAFAAUABQAFAAUABQAFAB4AGQAKAAoACgAFAAUABQAFAAUABQAFAAUABQAFABQABQAFAAUABQAFAAUABQAFAAUABQAKAAUABQAFAAUACgAFABQABQAUAAUAAAAZAAUABQAFAAUABQAKAAUABQAUAAoABQAUAAUABQAFAAUABQAFAAUABQAFAAoABQAFAAUABQAKAB4ACgAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAAABQAAAAKAAAAAAAKAAAAAAAAAAUABQAKAAAAAAAFAAAAAAAKAAoABQAFAAAABQAFAAUABQAKAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8ADwAKAAAAHgAeAB4AHgAAAAoAAAAAAAAAAAAAAAAAAAAKAAUACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAAAAAAeAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAB4AAAAAAB4AAAAAAAAAAAAUABQAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAACgAAAAAAAUAAAAFAAAAAAAAAAAAAAAFAAoACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AIwAAABQACgAPABQAAAAAAADAAAAAwAAABwAAQAAAAAAhAADAAEAAAAcAAQAaAAAABYAEAADAAYAfiIaIiAiKyI1IlIiYSKl4B/g////AAAAICIaIiAiKSI1IlIiYSKl4AHgf////+PeSN5D3jveMt4W3gjdxSBqIAsAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGAAABAAAAAAAAAAECAAAAAgAAAAAAAAAAAAAAAAAAAAEAAAMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmAAAAAAAAAABiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQJ5AAAAKgAqACoAKgBMAGwAqgEAAVIBtgHOAgQCOgJyAoYCngKsArgDBANMA2wDwAP6BD4EaASgBM4FDgVEBVYFcgXSBeQGRAZ4BqQG5gceB24HrgfyCC4IfgiUCLgI4AkSCT4JbgmcCfAKIApWCogK1gsACx4LWgt8C8wL+gxSDGQMsAzCDQQNEA0oDVwNjA26DfoOKg5sDqoO2g78DyQPaA+GD6gPzA/0ECwQZBCaENwRDBEwEVYRfhHaEgYSSBJwEoISqhLWEtYS1hLWEtYS1hLWEtYS1hLWEx4TYBOmFBQUdhTMFO4VGBVWFY4V/hZOFoIWqBboFxgXSBeMF7AXzhhkGIYYzBj0GRwZRhlwGbYZ/BocGjwaYhqoGu4bLBtqG2obahtqG2objhuwG9Qb+BwyHH4c1h0oHeId7h36HgYeEh6yHtwfJB90H7YgFiBkILIhACFOIagh2iIUIjAiUCKKIv4jaiPcJGIk3iVuJfomXiaiJwwndiiwKuwsHiwsLD4sPiw+LD4sPixaLG4shiyeLJ4sniyuLL4s0CziLPQtAC0WLRYtFi0wLUotaC2ILawtvi3sLewt7C3sLewt7C3sLewt7C3sLewt/C4MLhouKC40LkIuUC6SLugvFi9sL5ovyC/0MCAwxjE0MXwx8jKuMtwzCDM0M2IzojPWNBY0dDSyNTI1djW4NcQ11jYWNkI2djaCNq4AAgAhAAABKgKaAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDtMgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIREnMxEjIQEJ6MfHApr9ZiECWAAAAgBQAHcA8AHfAAMAFQAANzUzFQM1MxUUFjsBFSMVIzUjNTMyNnhQUFAFDxQoUCgUDwV3UFABVBQUDwV4UFB4BQAAAAACACgBZwEYAgcACQATAAATNTMVIyIGFAYjJzUzFSMiJjQmI8hQFA8FBQ+0UBQPBQUPAWegeAUeBSh4oAUeBQAAAgAUAHcBLAHfACsALwAAEzUzFTM1MxUzMhYUBisBFTMyFhQGKwEVIzUjFSM1IyImNDY7ATUjIiY0NjMXNSMVPFAoUBQPBQUPFBQPBQUPFFAoUBQPBQUPFBQPBQUPjCgBj1BQUFAFHgV4BR4FUFBQUAUeBXgFHgWgeHgAAAEAFAAnASwCLwBFAAATNTMVMzIWFBY7ARUjIiY0JiImPQEjFTMVFBY7ARUjIgYUBisBFSM1IzU0JisBNTMyFhQWMhYdATM1IzU0JisBNTMyNj0BjFAUDwUFDxQUDwUFHgV4oAUPFBQPBQUPFFBQBQ8UFA8FBR4FeKAFDxQUDwUB31BQBR4FUAUeBQUPFHgUDwV4BR4FUFAUDwVQBR4FBQ8UeBQPBXgFDxQAAAADABQAdwEsAY8AAwA3ADsAADc1MxUmNDY7ARUjIgYUBiIGFAYiBhQGIgYUBiIGHQEjNTQ2MjY0NjI2NDYyNjQ2MjY0NjI2NDYyBzUzFdxQKAUPFBQPBQUeBQUeBQUeBQUeBVAFHgUFHgUFHgUFHgUFHgUFHutQd1BQ9R4FUAUeBQUeBQUeBQUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeBShQUAAAAAQAFAB3ASwB3wA3ADsAQwBNAAATNTMVFBY7ARUjIgYdATMVFAYrARUzMhYdASM1NCYiBh0BIzU0JisBNTMyNjQ2MjY0JisBNTMyNhc1IxUWNCYiBhQWMgY0JisBFTM1IyJkeAUPFBQPBVAFDxQUDwVQBR4FeAUPFBQPBQUeBQUPFBQPBVAoUAUeBQUeSwUPFFAUDwHLFBQPBVAFDxQUDwV4BQ8UFA8FBQ8UFA8FeAUeBQUeBVAFVVBQSx4FBR4FIx4FeFAAAAAAAQBkAWcA3AIHAA0AABM1MxUjIgYdASM1NDYzjFAUDwVQBQ8Bj3h4BQ8UFA8FAAAAAQBQAHcA8AHfACcAABM1MxUUBiIGFAYrARUzMhYUFjIWHQEjNTQmIiY0JisBNTMyNjQ2MjagUAUeBQUPFBQPBQUeBVAFHgUFDxQUDwUFHgUByxQUDwUFHgXIBR4FBQ8UFA8FBR4FyAUeBQUAAQBQAHcA8AHfACcAABM1MxUUFjIWFBY7ARUjIgYUBiIGHQEjNTQ2MjY0NjsBNSMiJjQmIiZQUAUeBQUPFBQPBQUeBVAFHgUFDxQUDwUFHgUByxQUDwUFHgXIBR4FBQ8UFA8FBR4FyAUeBQUAAQAAAMcBQAGPACsAABM1MxUzNTMVFAYiBh0BMxUjFRQWMhYdASM1IxUjNTQ2MjY9ASM1MzU0JiImKFBQUAUeBVBQBR4FUFBQBR4FUFAFHgUBexQoKBQPBQUPFCgUDwUFDxQoKBQPBQUPFCgUDwUFAAEAKADHARgBjwALAAATNTMVMxUjFSM1IzV4UFBQUFABP1BQKFBQKAABAGQATwDcAO8ADQAANzUzFSMiBh0BIzU0NjOMUBQPBVAFD3d4eAUPFBQPBQAAAAABABQBFwEsAT8AAwAAEzUhFRQBGAEXKCgAAAAAAQB4AHcAyADHAAMAADc1MxV4UHdQUAAAAQAUAJ8BLAHfADcAAAA0NjsBFSMiBhQGIgYUBiIGFAYiBhQGIgYUBiIGFAYrATUzMjY0NjI2NDYyNjQ2MjY0NjI2NDYyAQQFDxQUDwUFHgUFHgUFHgUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgG8HgVQBR4FBR4FBR4FBR4FBR4FBR4FUAUeBQUeBQUeBQUeBQUeBQADABQAdwEsAd8AFwAmADQAABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhc1IxUzMjY0NjI2NDYyNhU1IyIGFAYiBhQGKwEVPMgFDxQUDwXIBQ8UFA8FoHgUDwUFHgUFHgUUDwUFHgUFDxQByxQUDwX+6AUPFBQPBQEYBRkUeAUeBQUeBQX1oAUeBQUeBVAAAAABACgAdwEYAd8AEwAAEzUzETMVIzUzNSM1NDYyNjQ2MjZ4UFDwUFAFHgUFHgUByxT+wCgoyBQPBQUeBQUAAAAAAQAUAHcBLAHfAD8AABM1MxUUFjsBFSMiBhQGIgYUBiIGFAYiBhQGIgYdATM1MxUhNTMyNjQ2MjY0NjI2NDYyNjQ2OwE1IxUjNTQ2MjY8yAUPFBQPBQUeBQUeBQUeBQUeBXhQ/ugUDwUFHgUFHgUFHgUFDxR4UAUeBQHLFBQPBVAFHgUFHgUFHgUFHgUFDxQoUFAFHgUFHgUFHgUFHgVQKBQPBQUAAAAAAQAUAHcBLAHfAC0AABM1MxUUFjsBFSMiBhQWOwEVIyIGHQEjNTQmIiY9ATMVMzUjNTM1IxUjNTQ2MjY8yAUPFBQPBQUPFBQPBcgFHgVQeHh4eFAFHgUByxQUDwV4BR4FeAUPFBQPBQUPFCh4KHgoFA8FBQACABQAdwEsAd8AKAAzAAATNTMVMzIWFAYrARUzMhYdASM1NDY7ATUjNTMyNjQ2MjY0NjI2NDYyNhU1IyIGFAYiBh0BtFAUDwUFDxQUDwWgBQ8UoBQPBQUeBQUeBQUeBRQPBQUeBQHLFMgFHgVQBQ8UFA8FUFAFHgUFHgUFHgUFpVAFHgUFDxQAAQAUAHcBLAHfAB0AABM1IRUjFTMVFBY7ARUjIgYdASM1NCYiJj0BMxUzNRQBGMigBQ8UFA8FyAUeBVB4ARfIKHgUDwV4BQ8UFA8FBQ8UKHgAAAAAAgAUAHcBLAHfACQAKAAAEzUzFSMVFAYrARUzFRQWOwEVIyIGHQEjNTQmKwE1MzI2NDYyNhM1IxVkeFAFDxSgBQ8UFA8FyAUPFBQPBQUeBXh4AcsUKBQPBVAUDwV4BQ8UFA8F8AUeBQX+43h4AAAAAAEAFAB3ASwB3wAfAAATNSEVIyIGFAYiBhQGKwEVIzUzMjY0NjI2NDY7ATUjFRQBGBQPBQUeBQUPFFAUDwUFHgUFDxR4AY9QeAUeBQUeBaCgBR4FBR4FUCgAAAAAAwAUAHcBLAHfACcAKwAvAAATNTMVFBY7ARUjIgYUFjsBFSMiBh0BIzU0JisBNTMyNjQmKwE1MzI2FzUjFRc1IxU8yAUPFBQPBQUPFBQPBcgFDxQUDwUFDxQUDwWgeHh4AcsUFA8FeAUeBXgFDxQUDwV4BR4FeAV9eHigeHgAAAACABQAdwEsAd8AJAAoAAATNTMVFBY7ARUjIgYUBiIGHQEjNTM1NDY7ATUjNTQmKwE1MzI2FzUjFTzIBQ8UFA8FBR4FoHgFDxSgBQ8UFA8FoHgByxQUDwXwBR4FBQ8UKBQPBVAUDwV4BX14eAACAHgAnwDIAbcAAwAHAAA3NTMVJzUzFXhQUFCfUFDIUFAAAAACAGQAdwDcAbcADQARAAA3NTMVIyIGHQEjNTQ2Mzc1MxWMUBQPBVAFDxRQn1BQBQ8UFA8FyFBQAAEAKAB3ARgB3wBHAAATNTMVFAYiBhQGIgYUBiIGFAYiBhQWMhYUFjIWFBYyFhQWMhYdASM1NCYiJjQmIiY0JiImNCYiJjQ2MjY0NjI2NDYyNjQ2MjbIUAUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBVAFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUByxQUDwUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFAAIAKADHARgBZwADAAcAADc1MxUnNTMVKPDw8McoKHgoKAAAAAEAKAB3ARgB3wBHAAATNTMVFBYyFhQWMhYUFjIWFBYyFhQGIgYUBiIGFAYiBhQGIgYdASM1NDYyNjQ2MjY0NjI2NDYyNjQmIiY0JiImNCYiJjQmIiYoUAUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBVAFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUByxQUDwUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFAAIAFAB3ASwB3wADACUAADc1MxUDNTMVFBY7ARUjIgYUBisBFSM1MzI2NDY7ATUjFSM1MzI2jFCgyAUPFBQPBQUPFFAUDwUFDxR4UBQPBXdQUAFUFBQPBVAFHgVQUAUeBVBQUAUAAAAAAQAUAHcBLAHfAB8AABM1MxUUFjsBFSMiBh0BIzUzNSMRMxUjNTQmKwERMzI2PMgFDxQUDwV4UHigyAUPFBQPBQHLFBQPBcgFDxSgUP7oKBQPBQEYBQAAAAIAFAB3ASwB3wAhAC8AABI0NjIWFBYyFhQWMhYUFjsBFSM1IxUjNTMyNjQ2MjY0NjIWNCYiBhQGKwEVMzUjIowFHgUFHgUFHgUFDxRQeFAUDwUFHgUFHi0FHgUFDxR4FA8BvB4FBR4FBR4FBR4F8Hh48AUeBQUeBUseBQUeBVBQAAAAAAMAFAB3ASwB3wAfACMAJwAAEzUzFRQWOwEVIyIGFBY7ARUjIgYdASM1NDY7AREjIiYXNSMVFzUjFRTwBQ8UFA8FBQ8UFA8F8AUPFBQPBchQUFAByxQUDwV4BR4FeAUPFBQPBQEYBX14eKB4eAAAAAABABQAdwEsAd8APwAAEzUzFRQWOwEVIyImNCYiJj0BIxUUBisBFTMyFh0BMzU0NjI2NDY7ARUjIgYdASM1NCYiJjQmKwE1MzI2NDYyNmSgBQ8UFA8FBR4FUAUPFBQPBVAFHgUFDxQUDwWgBR4FBQ8UFA8FBR4FAcsUFA8FUAUeBQUPFBQPBcgFDxQUDwUFHgVQBQ8UFA8FBR4FyAUeBQUAAgAUAHcBLAHfAB8ALwAAEzUzFRQWMhYUFjsBFSMiBhQGIgYdASM1NDY7AREjIiYWNCYrAREzMjY0NjsBNSMiFMgFHgUFDxQUDwUFHgXIBQ8UFA8FoAUPFBQPBQUPFBQPAcsUFA8FBR4FyAUeBQUPFBQPBQEYBSgeBf7oBR4FyAABABQAdwEsAd8AMwAAEzUhFSMiJjQmIiY9ASMVMzI2NDY7ARUjIiY0JisBFTM1NDYyNjQ2OwEVITU0NjsBESMiJhQBGBQPBQUeBVAUDwUFDxQUDwUFDxRQBR4FBQ8U/ugFDxQUDwUByxR4BR4FBQ8UeAUeBXgFHgV4FA8FBR4FeBQPBQEYBQAAAQAUAHcBLAHfAC0AABM1IRUjIiY0JiImPQEjFTMyNjQ2OwEVIyImNCYrARUzMhYdASM1NDY7AREjIiYUARgUDwUFHgVQFA8FBQ8UFA8FBQ8UFA8FoAUPFBQPBQHLFHgFHgUFDxR4BR4FeAUeBXgFDxQUDwUBGAUAAQAUAHcBLAHfAD4AABM1MxUUFjsBFSMiJjQmIiY9ASMVFAYrARUzMhYdATM1IzUzFSMiJjQmIgYdASM1NCYiJjQmKwE1MzI2NDYyNmSgBQ8UFA8FBR4FUAUPFBQPBVBQoBQPBQUeBXgFHgUFDxQUDwUFHgUByxQUDwVQBR4FBQ8UFA8FyAUPFFAooAUeBQUPFBQPBQUeBcgFHgUFAAAAAAEAFAB3ASwB3wALAAA3ETMVMzUzESM1IxUUUHhQUHh3AWigoP6YoKAAAAAAAQBQAHcA8AHfABcAABM1MxUUBisBETMyFh0BIzU0NjsBESMiJlCgBQ8UFA8FoAUPFBQPBQHLFBQPBf7oBQ8UFA8FARgFAAAAAQAUAHcBLAHfABsAABM1MxUUBisBESMiBh0BIzU0JisBNTMVMxEjIiaMoAUPFBQPBaAFDxRQUBQPBQHLFBQPBf7oBQ8UFA8FUFABGAUAAAABABQAdwEsAd8AJQAAEzUzFTM1MzUzFSMVIyIGFBY7ARUzFSM1IzUjFSM1NDY7AREjIiYUeCgoUCgUDwUFDxQoUCgoeAUPFBQPBQHLFKBQUFBQBR4FUFBQUKAUDwUBGAUAAAAAAQAUAHcBLAHfAB0AABM1MxUUBisBETM1NDYyNjQ2OwEVITU0NjsBESMiJhSgBQ8UUAUeBQUPFP7oBQ8UFA8FAcsUFA8F/ugUDwUFHgV4FA8FARgFAAAAAAEAFAB3ASwB3wAhAAA3ETMVFBYyFhQWMjY0NjI2PQEzESM1IyIGFAYiJjQmKwEVFFAFHgUFHgUFHgVQUBQPBQUeBQUPFHcBaBQPBQUeBQUeBQUPFP6YyAUeBQUeBcgAAAEAFAB3ASwB3wAgAAA3ETMVFBYyFhQWMhYUFjsBNTMRIzUjIiY0JiImNCYrARUUUAUeBQUeBQUPFFBQFA8FBR4FBQ8UdwFoFA8FBR4FBR4FeP6YeAUeBQUeBcgAAgAUAHcBLAHfACcAPwAAEzUzFRQWMhYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNhY0JiIGFAYrARUzMhYUFjI2NDY7ATUjImR4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBVAFHgUFDxQUDwUFHgUFDxQUDwHLFBQPBQUeBcgFHgUFDxQUDwUFHgXIBR4FBSgeBQUeBcgFHgUFHgXIAAAAAgAUAHcBLAHfAB4AIgAAEzUzFRQWOwEVIyIGHQEjFTMyFh0BIzU0NjsBESMiJhc1IxUU8AUPFBQPBXgUDwWgBQ8UFA8FyFAByxQUDwV4BQ8UeAUPFBQPBQEYBX14eAAAAgAUAE8BLAHfABsAJwAAEzUzFRQWOwEVIxUzMhYdASM1IzU0JisBNTMyNhc1IxUzNTMyFhQWMzzIBQ8UKBQPBXh4BQ8UFA8FoHgoFA8FBQ8ByxQUDwXwUAUPFFAUDwXwBc3I8FAFHgUAAAAAAgAUAHcBLAHfACAAJAAAEzUzFRQWOwEVIxUzFSM1IyImNCYrARUjNTQ2OwERIyImFzUjFRTwBQ8UKChQFA8FBQ8UeAUPFBQPBchQAcsUFA8FeFB4eAUeBaAUDwUBGAV9eHgAAAEAFAB3ASwB3wA/AAATNTMVFBY7ARUjNSMVMzIWHQEzFRQWMhYUFjsBFSMiBh0BIzU0JisBNTMVMzUjIiY9ASM1NCYiJjQmKwE1MzI2PMgFDxRQeBQPBVAFHgUFDxQUDwXIBQ8UUHgUDwVQBR4FBQ8UFA8FAcsUFA8FUFBQBQ8UFA8FBR4FUAUPFBQPBVBQUAUPFBQPBQUeBVAFAAEAKAB3ARgB3wAdAAATNTMVIyImNCYrARUzMhYdASM1NDY7ATUjIgYUBiMo8BQPBQUPFBQPBaAFDxQUDwUFDwFneHgFHgXwBQ8UFA8F8AUeBQAAAAEAFAB3ASwB3wARAAA3ETMRMxEzESMiBh0BIzU0JiMUUHhQFA8FyAUPnwFA/sABQP7ABQ8UFA8FAAAAAAEAFAB3ASwB3wArAAA3NTMVMzIWFBYyNjQ2OwE1MxUjIgYUBiIGFAYiBhQGIiY0JiImNCYiJjQmIxRQFA8FBR4FBQ8UUBQPBQUeBQUeBQUeBQUeBQUeBQUP7/DwBR4FBR4F8PAFHgUFHgUFHgUFHgUFHgUFHgUAAAEAFAB3ASwB3wAXAAA3ETMVMzUzFTM1MxEjFSM1NCYiBh0BIzUUUCgoKFAoUAUeBVDHARjwUFDw/uhQFA8FBQ8UUAAAAQAUAHcBLAHfAD8AABM1MxUzMhYUFjI2NDY7ATUzFSMiBhQGKwEVMzIWFBY7ARUjNSMiJjQmIgYUBisBFSM1MzI2NDY7ATUjIiY0JiMUUBQPBQUeBQUPFFAUDwUFDxQUDwUFDxRQFA8FBR4FBQ8UUBQPBQUPFBQPBQUPAY9QUAUeBQUeBVBQBR4FeAUeBVBQBR4FBR4FUFAFHgV4BR4FAAEAKAB3ARgB3wAhAAATNTMVMzUzFSMiBhQGKwEVMzIWHQEjNTQ2OwE1IyImNCYjKFBQUBQPBQUPFBQPBaAFDxQUDwUFDwE/oKCgoAUeBXgFDxQUDwV4BR4FAAAAAQAUAHcBLAHfAEIAABM1IRUjIgYUBiIGFAYiBhQGIgYUBisBFTM1NDYyNjQ2OwEVITUzMjY0NjI2NDYyNjQ2MjY0NjI2PQEjFRQGIgYUBiMUARgUDwUFHgUFHgUFHgUFDxR4BR4FBQ8U/ugUDwUFHgUFHgUFHgUFHgV4BR4FBQ8BZ3hQBR4FBR4FBR4FBR4FUBQPBQUeBXh4BR4FBR4FBR4FBR4FBQ8UFA8FBR4FAAABAFAAdwDwAd8ABwAANxEzFSMRMxVQoFBQdwFoKP7oKAAAAAABABQAdwEsAd8ANwAAEzUzMhYUFjIWFBYyFhQWMhYUFjIWFBYyFhQWOwEVIyImNCYiJjQmIiY0JiImNCYiJjQmIiY0JiMUFA8FBR4FBR4FBR4FBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FBQ8BZ3gFHgUFHgUFHgUFHgUFHgUFHgV4BR4FBR4FBR4FBR4FBR4FBR4FAAEAUAB3APAB3wAHAAATNTMRIzUzEVCgoFABtyj+mCgBGAAAAAEAFAGPASwCLwAvAAASNDYyFhQWMhYUFjIWFBYyFh0BIzU0JiImNCYiBhQGIgYdASM1NDYyNjQ2MjY0NjKMBR4FBR4FBR4FBR4FUAUeBQUeBQUeBVAFHgUFHgUFHgIMHgUFHgUFHgUFHgUFDxQUDwUFHgUFHgUFDxQUDwUFHgUFHgUAAAEAAAB3AUAAnwADAAA9ASEVAUB3KCgAAAEAZAG3ANwCLwANAAATNTMVMzIWHQEjNTQmI2RQFA8FUAUPAd9QUAUPFBQPBQAAAAIAFAB3ASwBZwAjACcAABM1MxUUFjsBFTMyFh0BIzU0JiIGHQEjNTQmKwE1MzI2PQEzNRU1IxU8oAUPFBQPBVAFHgV4BQ8UFA8FeFABPygUDwWgBQ8UFA8FBQ8UFA8FUAUPFCigUFAAAgAUAHcBLAHfABgAIgAAEzUzFTMVFBYyFhQWOwEVIyIGHQEjESMiJhY0JisBFTM1IyIUeFAFHgUFDxQUDwXIFA8FoAUPFFAUDwHLFHgUDwUFHgV4BQ8UAUAFoB4FoHgAAQAUAHcBLAFnACEAABM1MxUUFjIWHQEjNSMVMzUzFRQGIgYdASM1NCYrATUzMjY8yAUeBVB4eFAFHgXIBQ8UFA8FAVMUFA8FBQ8UKKAoFA8FBQ8UFA8FoAUAAAACABQAdwEsAd8AJQAvAAATNTMRMzIWHQEjNTQmIgYdASM1NCYrATUzMjY0NjI2PQEzNSMiJhM1IyIGFAYrARWMeBQPBVAFHgV4BQ8UFA8FBR4FUBQPBSgUDwUFDxQByxT+wAUPFBQPBQUPFBQPBXgFHgUFDxRQBf7joAUeBXgAAAIAFAB3ASwBZwAeACIAABM1MxUUFjsBFSMVMzUzFRQGIgYdASM1NCYrATUzMjYXNSMVPMgFDxTIeFAFHgXIBQ8UFA8FoHgBUxQUDwVQUCgUDwUFDxQUDwWgBS0oKAAAAAEAKAB3ARgB3wAzAAATNTMVFBY7ARUjIiY0JiImNCYrARUzMhYUBisBFTMyFh0BIzU0NjsBNSMiJjQ2OwE1MzI2eHgFDxQUDwUFHgUFDxQUDwUFDxQUDwWgBQ8UFA8FBQ8UFA8FAcsUFA8FUAUeBQUeBXgFHgV4BQ8UFA8FeAUeBXgFAAIAFAAnASwBZwArAC8AABM1MxUUFjI2PQEzFRQGKwEVIyIGHQEjNTQmIiY9ATMVMzUjNTQmKwE1MzI2FzUjFTx4BR4FUAUPFBQPBaAFHgVQUHgFDxQUDwV4UAFTFBQPBQUPFBQPBfAFDxQUDwUFDxQoUBQPBXgFfXh4AAABABQAdwEsAd8AIwAAEzUzFTMyNj0BMxUUFjsBFSM1IyIGFAYrARUjNTQ2OwERIyImFHgUDwVQBQ8UUBQPBQUPFHgFDxQUDwUByxSgBQ8UFA8FyMgFHgWgFA8FARgFAAACAFAAdwDwAd8AEgAWAAATNTMVMzIWHQEjNTQ2OwE1IyImNzUzFVB4FA8FoAUPFBQPBShQAVMUyAUPFBQPBaAFS1BQAAIAKAAnARgB3wAWABoAABM1MxEjIgYdASM1NCYrATUzFTM1IyImNzUzFaB4FA8FoAUPFFBQFA8FKFABUxT+6AUPFBQPBVBQ8AVLUFAAAAAAAQAUAHcBLAHfADMAABM1MxUzMjY0NjI2PQEzFRQGIgYUBiIGFBYyFhQWOwEVIzUjIiY0JisBFSM1NDY7AREjIiYUeBQPBQUeBVAFHgUFHgUFHgUFDxRQFA8FBQ8UeAUPFBQPBQHLFMgFHgUFDxQUDwUFHgUFHgUFHgVQUAUeBXgUDwUBGAUAAAEAUAB3APAB3wASAAATNTMRMzIWHQEjNTQ2OwERIyImUHgUDwWgBQ8UFA8FAcsU/sAFDxQUDwUBGAUAAAEAFAB3ASwBZwAYAAA3NTMVFBYyNj0BMxUUFjsBFSM1IxUjNSMVFHgFHgVQBQ8UUCgoKHfwFA8FBQ8UFA8FyKB4eKAAAQAUAHcBLAFnABkAABM1MxUUFjI2PQEzFRQWOwEVIzUjFSM1IyImFFAFHgV4BQ8UUFBQFA8FAVMUFA8FBQ8UFA8FyMjIyAUAAgAUAHcBLAFnABcAGwAAEzUzFRQWOwEVIyIGHQEjNTQmKwE1MzI2FzUjFTzIBQ8UFA8FyAUPFBQPBaB4AVMUFA8FoAUPFBQPBaAFpaCgAAACABQAJwEsAWcAJgAqAAATNTMVFBYyNj0BMxUUFjsBFSMiBh0BIxUzMhYdASM1NDY7ATUjIiYXNSMVFFAFHgV4BQ8UFA8FeBQPBaAFDxQUDwXIUAFTFBQPBQUPFBQPBXgFDxRQBQ8UFA8F8AV9eHgAAgAUACcBLAFnACYAKgAAEzUzFRQWMjY9ATMVFAYrARUzMhYdASM1NDY7ATUjNTQmKwE1MzI2FzUjFTx4BR4FUAUPFBQPBaAFDxR4BQ8UFA8FeFABUxQUDwUFDxQUDwXwBQ8UFA8FUBQPBXgFfXh4AAEAFAB3ASwBZwApAAATNTMVFBYyNj0BMxUUFjsBFSM1IyIGFAYrARUzMhYdASM1NDY7ATUjIiYUUAUeBXgFDxRQFA8FBQ8UFA8FoAUPFBQPBQFTFBQPBQUPFBQPBVBQBR4FeAUPFBQPBaAFAAEAFAB3ASwBZwAzAAATNTMVFBYyFh0BIzUjFTMVMxUUFjIWFAYiBh0BIzU0JiImPQEzFTM1IzUjNTQmIiY0NjI2PMgFHgVQeFBQBR4FBR4FyAUeBVB4UFAFHgUFHgUBUxQUDwUFDxQoKCgUDwUFHgUFDxQUDwUFDxQoKCgUDwUFHgUFAAEAFAB3ASwB3wAjAAASNDY7ARUzFSMVMzI2PQEzFRQGIgYdASM1NCYrATUjNTM1MzKMBQ8UUFAUDwVQBR4FeAUPFFBQFA8BvB4FeCigBQ8UFA8FBQ8UFA8FoChQAAAAAAEAFAB3ASwBZwAZAAA3NTMVMzUzFTMyFh0BIzU0JiIGHQEjNTQmIxRQUFAUDwVQBR4FeAUPn8jIyMgFDxQUDwUFDxQUDwUAAAEAKAB3ARgBZwAZAAA3NTMVMzUzFSMiBhQGIgYdASM1NCYiJjQmIyhQUFAUDwUFHgVQBR4FBQ/HoKCgoAUeBQUPFBQPBQUeBQAAAAABABQAdwEsAWcAHQAANzUzFTM1MxUzNTMVIyIGHQEjNTQmIgYdASM1NCYjFFAoKChQFA8FUAUeBVAFD5/IoFBQoMgFDxQUDwUFDxQUDwUAAAEAFAB3ASwBZwBHAAATNTMVFBYyFhQWMjY0NjI2PQEzFRQGIgYUBisBFTMyFhQWMhYdASM1NCYiJjQmIgYUBiIGHQEjNTQ2MjY0NjsBNSMiJjQmIiYUUAUeBQUeBQUeBVAFHgUFDxQUDwUFHgVQBR4FBR4FBR4FUAUeBQUPFBQPBQUeBQFTFBQPBQUeBQUeBQUPFBQPBQUeBVAFHgUFDxQUDwUFHgUFHgUFDxQUDwUFHgVQBR4FBQABABQAJwEsAWcAHwAANzUzFTM1MxUjIgYUBiIGHQEjNTM1NDYyNj0BIzU0JiMUUHhQFA8FBR4FyKAFHgWgBQ/HoKCg8AUeBQUPFCgUDwUFDxQUDwUAAAAAAQAUAHcBLAFnAC8AABM1IRUUBiIGFAYiBhQGIgYUBiIGHQEzNTMVITU0NjI2NDYyNjQ2MjY0NjI2PQEjFRQBGAUeBQUeBQUeBQUeBVBQ/ugFHgUFHgUFHgUFHgVQARdQFA8FBR4FBR4FBR4FBQ8UKFAUDwUFHgUFHgUFHgUFDxQoAAAAAQAoAHcBGAHfAB0AABM1MxUjFSMiBhQWOwEVMxUjNTQmKwE1IzUzNTMyNqB4UBQPBQUPFFB4BQ8UUFAUDwUByxQoeAUeBXgoFA8FeCh4BQACAHgAdwDIAd8AAwAHAAA3NTMVJzUzFXhQUFB3oKDIoKAAAAABACgAdwEYAd8AHQAAEzUzFRQWOwEVMxUjFSMiBh0BIzUzNTMyNjQmKwE1KHgFDxRQUBQPBXhQFA8FBQ8UAbcoFA8FeCh4BQ8UKHgFHgV4AAEAFAGPASwB3wAfAAATNTMVFBYyNj0BMxUUBiIGHQEjNTQmIgYdASM1NDYyNjx4BR4FUAUeBXgFHgVQBR4FAcsUFA8FBQ8UFA8FBQ8UFA8FBQ8UFA8FBQAFAAAATwFAAd8ADwATABcALwAzAAA3NTMVFAYiBh0BIzU0JiImNzUzFSM1MxUnNTMVFBY7AREjIgYdASM1NCYrAREzMjYTESMRUKAFHgVQBR4FeCigKFDwBQ8UFA8F8AUPFBQPBfDw2xQUDwUFDxQUDwUFc1BQUFCMFBQPBf7ABQ8UFA8FAUAF/rsBQP7AAAAEAAAATwFAAd8AFwAbAB8ALwAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzUjFTM1IxUXNSMVFBYyFh0BMzU0NjI2KPAFDxQUDwXwBQ8UFA8FUCigKCigBR4FUAUeBQHLFBQPBf7ABQ8UFA8FAUAFfVBQUFBkFBQPBQUPFBQPBQUAAAACABQAdwEsAbcALwAzAAATNTMVFBYyNj0BMxUUFjsBFSMiBhQGIgYUBiIGFAYiJjQmIiY0JiImNCYrATUzMjYXNSMVPFAFHgVQBQ8UFA8FBR4FBR4FBR4FBR4FBR4FBQ8UFA8FUCgBoxQUDwUFDxQUDwWgBR4FBR4FBR4FBR4FBR4FBR4FoAVVUFAAAAMAFACfASwBtwA3AEcATwAAEjQ2MhYUFjIWFBYyFhQWMhYUBiIGFAYiBhQGIgYUBiImNCYiJjQmIiY0JiImNDYyNjQ2MjY0NjIWNCYiBhQGIgYUFjI2NDYyFjQmIgYUFjKMBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4tBR4FBR4FBR4FBR4tBR4FBR4BlB4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FIx4FBR4FBR4FBR4Fcx4FBR4FAAADABQATwEsAgcAOwBDAEsAABM1MxUUFjsBFTMVIxUUFjsBFTMyFh0BIzUjFSM1MzI2NDYyNjQmIiY0JiImNDYyNjQ2MjY0JisBNTMyNhY0JiIGFBYyFjQmIgYUFjJkUAUPFFBQBQ8UFA8FeFBQFA8FBR4FBR4FBR4FBR4FBR4FBQ8UFA8FKAUeBQUeLQUeBQUeAfMUFA8FeHgUDwVQBQ8UUFBQBR4FBR4FBR4FBR4FBR4FBR4FUAUoHgUFHgWbHgUFHgUAAgAAAHcBQAIHADkAQQAAEzUzMhYUFjIWFBYyFhQWMhYUFjsBFSMiBh0BIxUzMhYdASM1NDY7ATUjNTQmKwE1MzI2NDYyNjQ2MxY0JiIGFBYyeBQPBQUeBQUeBQUeBQUPFBQPBVAUDwWgBQ8UUAUPFBQPBQUeBQUPPAUeBQUeAbdQBR4FBR4FBR4FBR4FUAUPFFAFDxQUDwVQFA8FUAUeBQUeBSMeBQUeBQAAAAEAUADHAPABZwAXAAATNTMVFBY7ARUjIgYdASM1NCYrATUzMjZ4UAUPFBQPBVAFDxQUDwUBUxQUDwVQBQ8UFA8FUAUAAgAA//8BQAIvAAMAGwAAFREhEQM1IxUUBisBFTMyFh0BMzU0NjsBNSMiJgFAeFAFDxQUDwVQBQ8UFA8FAQIw/dABVBQUDwVQBQ8UFA8FUAUAAAAAAgAoAJ8BGAGPABcALwAAEzUzFRQWOwEVIyIGHQEjNTQmKwE1MzI2FzUjFRQGKwEVMzIWHQEzNTQ2OwE1IyImUKAFDxQUDwWgBQ8UFA8FeFAFDxQUDwVQBQ8UFA8FAXsUFA8FoAUPFBQPBaAFGRQUDwVQBQ8UFA8FUAUAAAIAAP//AUACLwADACgAABURIREDNSMVFAYrARUzFTMyFhQGIgYdATM1IyImNDYyNjQ2OwE1IyImAUB4UAUPFCgUDwUFHgVQFA8FBR4FBQ8UFA8FAQIw/dABpBQUDwVQeAUeBQUPFHgFHgUFHgVQBQACAAAATwFAAgcARgBWAAATNTMVFBY7ARUjIgYUBiIGFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjY9ATMVFBYyNjQ2OwE1IxUUBiImNDYyNgY0JiIGFAYiBhQWMjY0NjKgeAUPFBQPBQUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UeAUeBQUeBSgFHgUFHgUFHgUFHgHzFBQPBXgFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgUDwUFHgUFyB4FBR4FBR4FBR4FAAAAAAIAKAAnARgCBwA3ADsAABM1MxUUFjsBFSMiBhQGIgYdATMVFAYiBhQGIgYUFjIWFAYiBhQGIgYUBisBESMiJjQmKwE1MzI2FzUjFVCgBQ8UFA8FBR4FUAUeBQUeBQUeBQUeBQUeBQUPFBQPBQUPFBQPBXhQAfMUFA8FeAUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeBQEYBR4FeAV9eHgAAAACAAAAdwFAAd8AHAAkAAA3NTMVFAYiBhQGIgYdASMVIyIGHQEjNTMyNjQ2MzY0JiIGFBYyUPAFHgUFHgVQFA8FeBQPBQUPjAUeBQUe7/AUDwUFHgUFDxTIBQ8UUAUeBaUeBQUeBQAAAAIAAABPAUAB3wAVABkAADcRIREjIgYdASM1MzUjFSMiBh0BIzU3NSMVKAEYFA8FUCh4FA8FUPB4xwEY/sAFDxR4ePAFDxR4yCgoAAACAAAAnwFAAbcALwAzAAATNTMVMzI2PQEzFSMVMxUjFTMVIzU0JisBFSM1IyIGHQEjNTM1IzUzNSM1MxUUFjMXNSMVeFAUDwVQUFBQUFAFDxRQFA8FUFBQUFBQBQ9kUAFnUFAFDxQoKCgoKBQPBVBQBQ8UKCgoKCgUDwVQKCgAAAEAFAB3ASwB3wAhAAA3ETMyFhQWMhYUFjIWHQEzFTMVIxUjFRQGIgYUBiIGFAYjFBQPBQUeBQUeBVBQUFAFHgUFHgUFD3cBaAUeBQUeBQUPFCgoKBQPBQUeBQUeBQAAAAEAFAB3ASwB3wAhAAAANDY7AREjIiY0JiImNCYiJj0BIzUjNTM1MzU0NjI2NDYyAQQFDxQUDwUFHgUFHgVQUFBQBR4FBR4BvB4F/pgFHgUFHgUFDxQoKCgUDwUFHgUAAAEAKAB3ARgB3wAzAAATNTMVFBYyFhQWMhYdASMVMxUUBiIGFAYiBh0BIzU0JiImNCYiJj0BMzUjNTQ2MjY0NjI2eFAFHgUFHgVQUAUeBQUeBVAFHgUFHgVQUAUeBQUeBQHLFBQPBQUeBQUPFHgUDwUFHgUFDxQUDwUFHgUFDxR4FA8FBR4FBQABAAD//wFAAi8AGAAAETUhFRQGKwEVMzIWHQEjFSM1IyImPQEzNQFABQ8UFA8FyFAUDwXIAgcoFA8F8AUPFPDwBQ8U8AAAAAAEAAD//wFAAgcAAwAHAAsADwAAFzUzFSE1MxUTNTMVITUzFXjI/sAo8Cj+wMgB8PDw8AEY8PDw8AAAAAADABQAJwEsAgcAWwBrAHMAABM1MxUUFjsBFSMiJj0BIxUUFjIWFBY7ARUjIgYUFjIWFBYyFhQGIgYdASM1NCYiJjQ2MhYUFjI2NDYyNjQmIiY0JiImNCYrATUzMjY0NjI2NCYiJjQmIiY0NjI2FjQmIgYUFjIWFBYyNjQmIjY0JiIGFBYyPMgFDxQUDwVQBR4FBQ8UFA8FBR4FBR4FBR4FyAUeBQUeBQUeBQUeBQUeBQUeBQUPFBQPBQUeBQUeBQUeBQUeBVAFHgUFHgUFHgUFHnMFHgUFHgHzFBQPBXgFDxQUDwUFHgV4BR4FBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FBR4FUAUeBQUeBQUeBQUeBQUoHgUFHgUFHgUFHgUFHgUFHgUAAAADAAAA7wFAAWcAAwALABMAAD0BIRUmNCYiBhQWMjY0JiIGFBYyAUDIBR4FBR59BR4FBR7veHgtHgUFHgUFHgUFHgUAAAEAAP//AUACLwA3AAAANDY7ARUjFSMiBhQGKwEVIxUjIgYUBisBFSMiBhQGKwE1MzUzMjY0NjsBNTM1MzI2NDY7ATUzMgEYBQ8UKBQPBQUPFCgUDwUFDxQUDwUFDxQoFA8FBQ8UKBQPBQUPFBQPAgweBXhQBR4FUFAFHgVQBR4FeFAFHgVQUAUeBVAAAQAoAHcBGAHfABsAABM1MxUUFjIWFBYyFh0BIxUjNSM1NDYyNjQ2MjZ4UAUeBQUeBVBQUAUeBQUeBQHLFBQPBQUeBQUPFPDwFA8FBR4FBQABACgAdwEYAd8AGwAANzUzFTMVFAYiBhQGIgYdASM1NCYiJjQmIiY9AXhQUAUeBQUeBVAFHgUFHgXv8PAUDwUFHgUFDxQUDwUFHgUFDxQAAAEAFADHASwBjwAbAAATNTMyFhQWMhYUFjIWFAYiBhQGIgYUBisBNSM1tBQPBQUeBQUeBQUeBQUeBQUPFKABP1AFHgUFHgUFHgUFHgUFHgVQKAAAAAEAFADHASwBjwAbAAASNDY7ARUzFSMVIyImNCYiJjQmIiY0NjI2NDYyZAUPFKCgFA8FBR4FBR4FBR4FBR4BbB4FUChQBR4FBR4FBR4FBR4FAAAAAAEAAP//AUACLwA3AAARNTMyFhQWOwEVMzIWFBY7ARUzFTMyFhQWOwEVMxUjIiY0JisBNSMiJjQmKwE1IzUjIiY0JisBNRQPBQUPFBQPBQUPFCgUDwUFDxQoFA8FBQ8UFA8FBQ8UKBQPBQUPFAG3eAUeBVAFHgVQUAUeBVB4BR4FUAUeBVBQBR4FUAAAAQAUAMcBLAGPADMAABI0NjsBFTM1MzIWFBYyFhQWMhYUBiIGFAYiBhQGKwE1IxUjIiY0JiImNCYiJjQ2MjY0NjJkBQ8UKBQPBQUeBQUeBQUeBQUeBQUPFCgUDwUFHgUFHgUFHgUFHgFsHgVQUAUeBQUeBQUeBQUeBQUeBVBQBR4FBR4FBR4FBR4FAAABABQAnwEsAbcAFQAAEzUzFTMVMxUzMhYdASE1NDY7ATUzNYwoKCgUDwX+6AUPFCgBZ1BQUFAFDxQUDwVQUAAAAQAUAJ8BLAG3ABUAABM1IRUUBisBFSMVIxUjNSM1IzUjIiYUARgFDxQoKCgoKBQPBQGjFBQPBVBQUFBQUAUAAAEAFADHASwBZwAZAAASNDY7ARUzMhYdATMVITU0NjI2NDYyNjQ2MowFDxQUDwVQ/ugFHgUFHgUFHgFEHgVQBQ8UKBQPBQUeBQUeBQABACj//wEYAi8ANwAAEzUzFRQGKwEVIxUzMhYUFjIWFBY7ARUzFSMiBh0BIzU0NjsBNSM1IyImNCYiJjQmKwE1MzUzMjZ4UAUPFCgUDwUFHgUFDxQoFA8FUAUPFCgUDwUFHgUFDxQoFA8FAhsUFA8FUHgFHgUFHgVQeAUPFBQPBXhQBR4FBR4FeFAFAAEAKP//ARgCLwA3AAATNTMVFBY7ARUzFSMiBhQGIgYUBisBFSMVMzIWHQEjNTQmKwE1MzUzMjY0NjI2NDY7ATUjNSMiJnhQBQ8UKBQPBQUeBQUPFCgUDwVQBQ8UKBQPBQUeBQUPFCgUDwUCGxQUDwVQeAUeBQUeBVB4BQ8UFA8FeFAFHgUFHgV4UAUAAQAAAMcBQAGPAC8AABM1MxUUFjsBFSMiJj0BIxUUBiIGFAYiBh0BIzU0JisBNTMyFh0BMzU0NjI2NDYyNshQBQ8UFA8FUAUeBQUeBVAFDxQUDwVQBR4FBR4FAXsUFA8FUAUPFBQPBQUeBQUPFBQPBVAFDxQUDwUFHgUFAAEAAADHAUABjwAvAAATNTMVFBYyFhQWMhYdATM1NDY7ARUjIgYdASM1NCYiJjQmIiY9ASMVFAYrATUzMjYoUAUeBQUeBVAFDxQUDwVQBR4FBR4FUAUPFBQPBQF7FBQPBQUeBQUPFBQPBVAFDxQUDwUFHgUFDxQUDwVQBQABAAD//wFAAi8AGAAAATUzESE1NDY7ATUzNTM1MzI2NDY7ATUzNQEYKP7ABQ8UKCgUDwUFDxQoAd9Q/dAUDwVQUFAFHgVQUAABAAD//wFAAi8AGAAAFREzFTMVMxUzMhYUFjsBFTMVMxUzMhYdASgoKBQPBQUPFCgoFA8FAQIwUFBQBR4FUFBQBQ8UAAEAAP//AUACLwAYAAARNSERIzUjNSM1IyImNCYrATUjNSM1IyImAUAoKCgUDwUFDxQoKBQPBQIbFP3QUFBQBR4FUFBQBQAAAAEAAP//AUACLwAYAAAVESEVFAYrARUjFSMVIyIGFAYrARUjFSMVAUAFDxQoKBQPBQUPFCgoAQIwFA8FUFBQBR4FUFBQAAAAAAEAAAFnAUACBwArAAARNTMVFBYyFhQWMjY0NjI2NDY7ARUjNSMiBhQGIiY0JisBFSM1IxUjNSMiJqAFHgUFHgUFHgUFDxQoFA8FBR4FBQ8UKCgoFA8FAfMUFA8FBR4FBR4FBR4FoFAFHgUFHgVQeHh4BQAAAgA8AHcBBAHfABcAOwAAEzUzFRQGKwEVMzIWHQEjNTQmKwE1MzI2JzUzFSMVFAYrARUzMhYdATMVIzU0JiImNCYrATUzMjY0NjI2tFAFDxQUDwVQBQ8UFA8FKHh4BQ8UFA8FeHgFHgUFDxQUDwUFHgUBexQUDwV4BQ8UFA8FeAVfFCgUDwXIBQ8UKBQPBQUeBcgFHgUFAAADADwAdwEEAd8ADwAfAEMAABM1MxUUBiIGHQEjNTQ2MjYnNTMVFBYyFh0BIzU0JiImPQEzFRQWMhYUFjsBFSMiBhQGIgYdASM1MzU0NjsBNSMiJj0BZFAFHgVQBR4FKFAFHgVQBR4FeAUeBQUPFBQPBQUeBXh4BQ8UFA8FAQMUFA8FBQ8UFA8FBYcUFA8FBQ8UFA8FBUsoFA8FBR4FyAUeBQUPFCgUDwXIBQ8UAAAAAAIAKACfARgBjwAnAD0AABM1MxUUFjIWFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjYXNSMiBhQGIgYdATMVMzI2NDYyNj0BeFAFHgUFDxQUDwUFHgVQBR4FBQ8UFA8FBR4FKBQPBQUeBVAUDwUFHgUBexQUDwUFHgVQBR4FBQ8UFA8FBR4FUAUeBQVVUAUeBQUPFFAFHgUFDxQAAAAABAAAAE8BQAHfAAcADwA/AJUAADY0NjIWFAYiNjQ2MhYUBiInNTMVFBYyFhQWOwEVIxUzFSMiBhQGIgYdASM1NCYiJjQmKwE1MzUjNTMyNjQ2MjYXNSMVFAYiBhQGIgYUFjI2NDYyNj0BMxUUBisBFSMVFAYiBhQWMhYUFjIWHQEzNTQ2MjY0NjI2NCYiBhQGIgYdASM1NDY7ATUzNTQ2MjY0JiImNCYiJlAFHgUFHnMFHgUFHn2gBR4FBQ8UKCgUDwUFHgWgBR4FBQ8UKCgUDwUFHgV4UAUeBQUeBQUeBQUeBVAFDxRQBR4FBR4FBR4FUAUeBQUeBQUeBQUeBVAFDxRQBR4FBR4FBR4FzB4FBR4FfR4FBR4FjBQUDwUFHgVQUFAFHgUFDxQUDwUFHgVQUFAFHgUFGRQUDwUFHgUFHgUFHgUFDxQUDwVQFA8FBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBQ8UFA8FUBQPBQUeBQUeBQUAAQB4ARcAyAFnAAMAABM1MxV4UAEXUFAAAQB4AMcAyAEXAAMAADc1MxV4UMdQUAAAAQB4AO8AyAE/AAMAADc1MxV4UO9QUAAAAQB4AO8AyAE/AAMAADc1MxV4UO9QUAAABQAAAHcBQAG3AEkAUQBZAGkAeQAAEjQ2MhYUFjI2PQEzFRQWMjY0NjIWFBYyFhQGIgYUFjIWFAYiBhQWOwEVIzUjFSM1IxUjNSMVIzUzMjY0JiImNDYyNjQmIiY0NjIWNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjIWFBYyNjQmIjY0JiIGFAYiBhQWMjY0NjIoBR4FBR4FUAUeBQUeBQUeBQUeBQUeBQUeBQUPFCgoKFAoKCgUDwUFHgUFHgUFHgUFHi0FHgUFHs0FHgUFHsMFHgUFHgUFHgUFHsMFHgUFHgUFHgUFHgGUHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgUFHgVQUHh4eHhQUAUeBQUeBQUeBQUeBSMeBQUeBQUeBQUeBUseBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQAAAAABACgATwEYAd8AHwAAEzUzFRQGKwEVMxUzFSMiBh0BIzU0JisBNTM1MzUjIiZQoAUPFCgoFA8FoAUPFCgoFA8FAcsUFA8FeFB4BQ8UFA8FeFB4BQAEAAAAJwFAAgcAEQAZACEAMwAANzUzFSMVIzU0JiImNDYyFh0BPAE2MhYUBiI2NDYyFhQGIic1MxUUFjIWFAYiJj0BIxUjNfBQUKAFHgUFHgUFHgUFHnMFHgUFHn2gBR4FBR4FoFBPUFAoFA8FBR4FBQ8UfR4FBR4FfR4FBR4FoCgUDwUFHgUFDxRQUAAAAAAEAAAAJwFAAgcABwAdACUAOwAANjQ2MhYUBiI3NTMVIyIGFAYiBh0BIzUzNTQ2OwE1JjQ2MhYUBiInNTMVIxUUBisBFTMVIzUzMjY0NjI2oAUeBQUeS1AUDwUFHgV4eAUPFKAFHgUFHi14eAUPFChQFA8FBR4FpB4FBR4FKFCgBR4FBQ8UKBQPBVClHgUFHgWMFCgUDwVQUKAFHgUFAAAAAgAAAHcBQAG3ABcALwAAEjQ2MhYUFjIWFAYiJjQmKwEVMxUjNTMyMzUzFSMiBhQGIiY0JiImNDYyFhQWOwE1KAUeBQUeBQUeBQUPFChQFA/NUBQPBQUeBQUeBQUeBQUPFAFsHgUFHgUFHgUFHgWgUPBQ8AUeBQUeBQUeBQUeBaAAAAAABAAAAE8BQAHfAAcADwAsAEkAADY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGKwEVMzIWHQEzFSM1IyImNCYrATUzMjc1MxUzMhYUFjsBFSMiBhQGIiY0NjsBNSMiJj0BUAUeBQUecwUeBQUepQUeBQUPFBQPBVBQFA8FBQ8UFA99UBQPBQUPFBQPBQUeBQUPFBQPBfQeBQUeBS0eBQUeBS0eBQUeBXgFDxRQUAUeBXhQUFAFHgV4BR4FBR4FeAUPFAAABQAAAHcBQAG3AAcADwAnAC8ANwAANjQ2MhYUBiI2NDYyFhQGIic1MxUUFjsBFSMiBh0BIzU0JisBNTMyNiI0NjIWFAYiNjQ2MhYUBiJQBR4FBR7DBR4FBR6lUAUPFBQPBVAFDxQUDwV4BR4FBR7DBR4FBR58HgUFHgVVHgUFHgWMFBQPBVAFDxQUDwVQBR4FBR4FVR4FBR4FAAAABQAAAHcBQAG3AAcADwAXAC8ANwAANjQ2MhYUBiImNDYyFhQGIiQ0NjIWFAYiJzUzFRQWOwEVIyIGHQEjNTQmKwE1MzI2PAE2MhYUBiKgBR4FBR6lBR4FBR4BEwUeBQUepVAFDxQUDwVQBQ8UFA8FBR4FBR58HgUFHgV9HgUFHgUtHgUFHgU8FBQPBVAFDxQUDwVQBVAeBQUeBQAABQAAAHcBQAG3AAcADwAXAC8ANwAANjQ2MhYUBiImNDYyFhQGIiQ0NjIWFAYiJzUzFRQWOwEVIyIGHQEjNTQmKwE1MzI2JjQ2MhYUBiLIBR4FBR7NBR4FBR4BEwUeBQUepVAFDxQUDwVQBQ8UFA8FKAUeBQUefB4FBR4FVR4FBR4FfR4FBR4FFBQUDwVQBQ8UFA8FUAVQHgUFHgUABQAoAJ8BGAGPAAcADwAnAC8ANwAANjQ2MhYUBiImNDYyFhQGIjc1MxUUFjsBFSMiBh0BIzU0JisBNTMyPgE0NjIWFAYiJjQ2MhYUBiLwBR4FBR7NBR4FBR5LUAUPFBQPBVAFDxQUDwV4BR4FBR7NBR4FBR6kHgUFHgUFHgUFHgW0FBQPBVAFDxQUDwVQBSgeBQUeBQUeBQUeBQAAAgAUAE8BLAG3ADUARQAAEzUzFSMVMxUUFjIWFAYiJj0BIxUUFjIWFBY7ARUjIgYdASM1NCYrATUzMjY9ATM1IzU0JiImFjQmIgYUBiIGFBYyNjQ2MhTwUFAFHgUFHgVQBR4FBQ8UFA8FoAUPFBQPBXh4BR4FyAUeBQUeBQUeBQUeAaMUKCgUDwUFHgUFDxQUDwUFHgV4BQ8UFA8FoAUPFCgUDwUF8B4FBR4FBR4FBR4FAAAAAgAoAHcBGAGPAB0AIQAAEzUzFRQWMhYUBiIGFBY7ARUjNTMyNjQmIiY0NjI2FzUjFVCgBR4FBR4FBQ8U8BQPBQUeBQUeBXhQAXsUFA8FBR4FBR4FoKAFHgUFHgUFVSgoAAAAAAIAAAAnAUACBwAlACkAABM1MxUUFjIWFBY7AREhNSMiJjQ2OwE1IyImNDY7ATUzMjY0NjI2FzUjFXh4BR4FBQ8U/ugUDwUFDxQUDwUFDxQUDwUFHgWgUAHzFBQPBQUeBf5wUAUeBaAFHgVQBR4FBfUoKAAABAAAACcBQAHfAAMABwALAA8AAD0BIRUnNTMVJzUzFSc1MxUBQPDwoKBQUCdQUHhQUHhQUHhQUAACAAAA7wFAAWcAAwATAAA3NTMVJDQ2OwEVIyImNCYiJjQ2MnjI/ugFDxQUDwUFHgUFHu94eFUeBXgFHgUFHgUABAAAAJ8BQAG3AAMAEwAXACcAADc1MxUkNDY7ARUjIiY0JiImNDYyNzUzFSQ0NjsBFSMiJjQmIiY0NjJ4yP7oBQ8UFA8FBR4FBR5VyP7oBQ8UFA8FBR4FBR6feHhVHgV4BR4FBR4FUHh4VR4FeAUeBQUeBQADAAAATwFAAY8ANwBHAFcAABM0NjIWFBYyNjQ2MhYUBiIGFBYyFhQGKwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEzFjQmIgYUFjIWFBYyNjQmIgY0JiIGFAYiBhQWMjY0NjLIBR4FBR4FBR4FBR4FBR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgoBR4FBR4FBR4FBR59BR4FBR4FBR4FBR4Bew8FBR4FBR4FBR4FBR4FBR4FeAUeBQUPFBQPBQUeBXgFHgUFDxQjHgUFHgUFHgUFHgUjHgUFHgUFHgUFHgUAAgAAAE8BQAHfAEMAUwAAEjQ2MhYUFjIWFAYiBhQWMhYdASMVFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjY9ATMVFBY7ATUzMjY0JiIGNCYiBhQGIgYUFjI2NDYy8AUeBQUeBQUeBQUeBVAFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUPFBQPBQUefQUeBQUeBQUeBQUeAbweBQUeBQUeBQUeBQUPFBQPBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FUAUeBZseBQUeBQUeBQUeBQAAAgAAAE8BQAIHAEcAVwAAEjQ2MhYUFjsBFSMiBhQGIgYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNj0BMxUUFjI2NDYyNjQmIiY0NjI2NCYiBjQmIgYUBiIGFBYyNjQ2MvAFHgUFDxQUDwUFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUeBQUeBQUeBQUefQUeBQUeBQUeBQUeAeQeBQUeBXgFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBQUeBQUeBQUeBcMeBQUeBQUeBQUeBQAAAgAAAE8BQAIvAFcAZwAAEjQ2MhYUFjI2NDYyFhQGIgYUFjsBFSMiBhQGIgYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNj0BMxUUFjI2NDY7ATUjIgYUBiImNDYyNjQmIgY0JiIGFAYiBhQWMjY0NjLIBR4FBR4FBR4FBR4FBQ8UFA8FBR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgUFHgUFHlUFHgUFHgUFHgUFHgIMHgUFHgUFHgUFHgUFHgV4BR4FBR4FeAUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BR4FBR4FBR4F6x4FBR4FBR4FBR4FAAAAAAIAAABPAUACLwBPAF8AABI0NjIWFBYyFhQWOwEVIyIGFAYiBhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEzFRQWMjY0NjsBNSMiBhQGIiY0JiImNDYyBjQmIgYUBiIGFBYyNjQ2MsgFHgUFHgUFDxQUDwUFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBQUeBQUeSwUeBQUeBQUeBQUeAgweBQUeBQUeBXgFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgFHgUFHgUFHgXrHgUFHgUFHgUFHgUAAAAAAgAAAE8BQAIvAF8AbwAAEjQ2MhYUFjI2NDYyFhQWMhYUFjsBFSMiBhQGIgYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNj0BMxUUFjI2NDY7ATUjIgYUBiImNCYiBhQGIiY0NjI2NCYiBjQmIgYUBiIGFBYyNjQ2MngFHgUFHgUFHgUFHgUFDxQUDwUFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeAgweBQUeBQUeBQUeBQUeBXgFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgFHgUFHgUFHgUFHgUFHgXrHgUFHgUFHgUFHgUAAAAAAgAAAE8BQAIHAF4AbgAAEjQ2MhYUFjI2PQEzFRQWOwEVIyIGFAYiBhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEzFRQWMjY0NjsBNSMVFBYyFhQGIiY0JiIGFAYiJjQ2MjY0JiIWNCYiBhQGIgYUFjI2NDYyUAUeBQUeBXgFDxQUDwUFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFHgFHgUFHgUFHgUFHgUFHgUFHiMFHgUFHgUFHgUFHgHkHgUFHgUFDxQUDwV4BR4FBR4FeAUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4FA8FBR4FBR4FBR4FBR4FBR4Fwx4FBR4FBR4FBR4FAAMAFAB3ASwCBwAvAD0ATQAAEzUzFTM1MxUjFRQWMhYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNj0BFjQmKwEVMzUjIgYUBiIWNCYiBhQGIgYdATM1NCYiFFB4UFAFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FKAUPFHgUDwUFHiMFHgUFHgV4BR4Bt1BQUFAUDwUFHgWgBR4FBQ8UFA8FBR4FoAUeBQUPFEseBVBQBR4Fcx4FBR4FBQ8UFA8FAAAAAgAUAHcBLAG3ACEAMwAAEzUzFTM1MxUjIgYUFjsBFSMiBh0BIzU0JisBNTMyNjQmIxc1IxUzMhYUFjI2NDY7ATUjFRRQeFAUDwUFDxQUDwXIBQ8UFA8FBQ9kKBQPBQUeBQUPFCgBZ1BQUFAFHgWgBQ8UFA8FoAUeBXhQUAUeBQUeBVBQAAAAAAIAFADHASwBjwA3AE8AABM1MxUUFjI2PQEzFRQGIgYUBiIGFBYyFhQWMhYdASM1NCYiBh0BIzU0JiImNCYiJjQ2MjY0NjI2FjQmIgYUBiIGFBYyFhQWMjY0JiImNDYyZFAFHgVQBR4FBR4FBR4FBR4FUAUeBVAFHgUFHgUFHgUFHgVQBR4FBR4FBR4FBR4FBR4FBR4BexQUDwUFDxQUDwUFHgUFHgUFHgUFDxQUDwUFDxQUDwUFHgUFHgUFHgUFKB4FBR4FBR4FBR4FBR4FBR4FAAAAAgAUAMcBLAGPADcATwAAEzUzFRQWMjY9ATMVFBYyFhQWMhYUBiIGFAYiBh0BIzU0JiIGHQEjNTQ2MjY0NjI2NCYiJjQmIiYWNCYiBhQWMhYUBiIGFBYyNjQ2MjY0JiIUUAUeBVAFHgUFHgUFHgUFHgVQBR4FUAUeBQUeBQUeBQUeBaAFHgUFHgUFHgUFHgUFHgUFHgF7FBQPBQUPFBQPBQUeBQUeBQUeBQUPFBQPBQUPFBQPBQUeBQUeBQUeBQUoHgUFHgUFHgUFHgUFHgUFHgUAAAAcABT//wEsAi8ABwAPABcAHwAnAC8ANwA/AEcATwBXAF8AZwBvAHcAfwCHAI8AlwCfAKcArwC3AL8AxwDPANcA3wAANjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiK0BR4FBR6lBR4FBR7rBR4FBR6lBR4FBR5LBR4FBR6lBR4FBR7rBR4FBR6lBR4FBR5LBR4FBR6lBR4FBR7rBR4FBR6lBR4FBR5LBR4FBR6lBR4FBR7rBR4FBR6lBR4FBR5LBR4FBR6lBR4FBR7rBR4FBR6lBR4FBR5LBR4FBR6lBR4FBR7rBR4FBR6lBR4FBR5LBR4FBR6lBR4FBR7rBR4FBR6lBR4FBR4EHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUABAAA//8BQAIvAF8AnwE/Ab8AABI0NjIWFBYyNjQ2MhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUBiImNCYiBhQGIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MhY0JiIGFBYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQWMjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyFhQWMjY0NjIWFBYyNjQ2MhYUFjI2NDYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUBiImNCYiBhQGIiY0JiIGFAYiJjQmIgYUBiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyFjQmIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQWMjY0NjIWFBYyNjQ2MhYUFjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0JiIGFAYiJjQmIgYUBiJ4BR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4tBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR59BR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4tBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4BvB4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FIx4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FfR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FIx4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FAAAAABMAAP//AUACLwBSAFoAYgBqAHIAegCCAIoAkgCaAKIAqgCyALoAwgDKANIA2gDiAAARNTMVFBYyNj0BMxUUFjI2NDY7AREjNTQmIgYdASM1NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJhY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMgY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMgY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMgY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMgY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMgY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMlAFHgV4BR4FBQ8UeAUeBXgFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgXIBR4FBR5LBR4FBR6lBR4FBR5LBR4FBR5LBR4FBR6lBR4FBR5LBR4FBR5LBR4FBR6lBR4FBR5LBR4FBR5LBR4FBR6lBR4FBR5LBR4FBR5LBR4FBR6lBR4FBR5LBR4FBR5LBR4FBR6lBR4FBR4CGxQUDwUFDxQUDwUFHgX90BQPBQUPFBQPBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUoHgUFHgUjHgUFHgUFHgUFHgUjHgUFHgUjHgUFHgUFHgUFHgUjHgUFHgUjHgUFHgUFHgUFHgUjHgUFHgUjHgUFHgUFHgUFHgUjHgUFHgUjHgUFHgUFHgUFHgUjHgUFHgUjHgUFHgUFHgUFHgUAAAEAeP//AMgCLwADAAAXETMReFABAjD90AAAAAABAAD//wDIAi8ABwAAEzUzESMRIzV4UFB4AT/w/dABGCgAAAADAAD//wEYAi8ABQAJAA8AAD0BMxEjNRcRMxEDNTMVIzWgUHhQyFCg7yj+6PDwAjD90AFoyPAoAAIAUP//ARgCLwADAAcAABcRMxEjETMRyFDIUAECMP3QAjD90AAAAAACAAD//wEYAWcABQALAAA9ATMRIzUnNSERIxGgUFABGFDvKP7o8FAo/pgBQAAAAAACAAAA7wEYAi8ABQALAAATETMRITU3NTMVIzXIUP7oUFCgARcBGP7AKFDI8CgAAAABAAD//wDIAT8ABQAAETUzESMRyFABFyj+wAEYAAAAAAEAeAEXAUACLwAFAAATETMVMxV4UHgBFwEY8CgAAAAAAQAAARcBQAIvAAcAABM1MxUzFSE1eFB4/sABP/DwKCgAAAAAAQAA//8BQAE/AAcAABE1IRUjESMRAUB4UAEXKCj+6AEYAAAAAQB4//8BQAIvAAcAABcRMxUzFSMReFB4eAECMPAo/ugAAAAAAQAAARcBQAE/AAMAABE1IRUBQAEXKCgAAQAA//8BQAIvAAsAABM1MxUzFSMRIxEjNXhQeHhQeAE/8PAo/ugBGCgAAAACAFAA7wFAAi8ACAAOAAATNTMVMzIWHQEHETMRMxXIUBQPBfBQoAE/8MgFDxRQAUD+6CgAAgBQ//8BQAFnAAgADgAAFxEzFRQGKwEVIxEzFSMRyHgFDxTI8KABARgUDwXwAWgo/sAAAAMAAADvAUACLwADAAwAEgAAPQEhFSc1MxUzMhYdASc1MxUjNQFAeFAUDwXwUKDvKChQ8MgFDxQoyPAoAAMAAP//AUABZwAIAA4AEgAAFxEzFRQGKwEVJTUzESM1JzUhFch4BQ8U/uigUFABQAEBGBQPBfDwKP7o8FAoKAADAFD//wFAAi8ACAARABUAABcRMxUUBisBFQM1MxUzMhYdAQMRMxHIeAUPFFBQFA8F8FABARgUDwXwAUDwyAUPFP7AAjD90AACAAAA7wFAAWcAAwAHAAA9ASEVJTUhFQFA/sABQO8oKFAoKAAEAAD//wFAAi8ACAAOABcAHQAAFxEzFRQGKwEVJTUzESM1NzUzFTMyFh0BJzUzFSM1yHgFDxT+6KBQeFAUDwXwUKABARgUDwXw8Cj+6PBQ8MgFDxQoyPAoAAAAAAEAAAEXAMgCLwAFAAATNTMRIzV4UMgBP/D+6CgAAAAAAQB4//8BQAE/AAUAABcRMxUjEXjIeAEBQCj+6AAAAAABAAD//wFAAi8AAwAAFREhEQFAAQIw/dAAAAAAAQAA//8BQAEXAAMAABURIREBQAEBGP7oAAAAAAEAAP//AKACLwADAAAVETMRoAECMP3QAAEAoP//AUACLwADAAAXETMRoKABAjD90AAAAAABAAABFwFAAi8AAwAAGQEhEQFAARcBGP7oAAAAAgAAAJ8BQAGPACcALwAAADQ2OwEVIyImNCYiJjQmIgYdASM1NCYrATUzMjY9ATMVFBYyNjQ2MgY0JiIGFBYyARgFDxQUDwUFHgUFHgWgBQ8UFA8FoAUeBQUewwUeBQUeAWweBfAFHgUFHgUFDxQUDwVQBQ8UFA8FBR4FSx4FBR4FAAAAAwAUAJ8BLAG3ACcANwA/AAATNTMVFBYyFhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2FjQmIgYUBiIGFBYyNjQ2MhY0JiIGFBYyZHgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FKAUeBQUeBQUeBQUeVQUeBQUeAaMUFA8FBR4FeAUeBQUPFBQPBQUeBXgFHgUFKB4FBR4FBR4FBR4Fmx4FBR4FAAMAAAB3AUAB3wANABUAHQAANREhFSMVMxUjNTM1IxE2NCYiBhQWMhY0JiIGFBYyAUBQUMhQeHgFHgUFHlUFHgUFHncBaCgooKAo/sDNHgUFHgUjHgUFHgUAAAAABAAUAHcBLAHfACUALQA1AD0AABI0NjIWFBYyNjQ2MhYUFjsBESM1IxUjNSMVIzUzMjY0NjI2NDYyFjQmIgYUFjI2NCYiBhQWMgY0JiIGFBYyjAUeBQUeBQUeBQUPFFAoUCgoFA8FBR4FBR4tBR4FBR5VBR4FBR4jBR4FBR4BvB4FBR4FBR4FBR4F/sB4eKBQoAUeBQUeBUseBQUeBQUeBQUeBUseBQUeBQAAAAEAAP//AUABFwAeAAAkNDY7AREhNTQ2MjY0NjI2NDYyNj0BMzU0NjI2NDYyARgFDxT+wAUeBQUeBQUeBVAFHgUFHvQeBf7oFA8FBR4FBR4FBQ8UFA8FBR4FAAAAAQAA//8BQAIvAB0AAAA0NjsBESERMzI2NDYyNjQ2MjY9ATM1NDYyNjQ2MgEYBQ8U/sAUDwUFHgUFHgVQBR4FBR4CDB4F/dABQAUeBQUeBQUPFBQPBQUeBQAAAAABAAD//wFAAi8AHQAAFREzMhYUFjIWFBYyFh0BMxUUFjIWFBYyFhQWOwERFA8FBR4FBR4FUAUeBQUeBQUPFAECMAUeBQUeBQUPFBQPBQUeBQUeBf7AAAAAAQAA//8BQAEXAB4AABURMzIWFBYyFhQWMhYdATMVFBYyFhQWMhYUFjIWHQEUDwUFHgUFHgVQBR4FBR4FBR4FAQEYBR4FBR4FBQ8UFA8FBR4FBR4FBQ8UAAsAAABPAUACBwA3AD8AQwBLAFMAVwBfAGcAbwBzAHsAABE1MxUUFjIWFBY7ARUjIgYUBisBFTMyFhQWMhYdASM1NCYiJjQmKwE1MzI2NDY7ATUjIiY0JiImFjQmIgYUFjIzNSMVFjQmIgYUFjI2NCYiBhQWMgc1IxU2NCYiBhQWMgY0JiIGFBYyNjQmIgYUFjIHNSMVNjQmIgYUFjLwBR4FBQ8UFA8FBQ8UFA8FBR4F8AUeBQUPFBQPBQUPFBQPBQUeBXgFHgUFHn1QKAUeBQUeVQUeBQUec1CgBR4FBR6bBR4FBR59BR4FBR4jUKAFHgUFHgHzFBQPBQUeBXgFHgV4BR4FBQ8UFA8FBR4FeAUeBXgFHgUFKB4FBR4FKChLHgUFHgUFHgUFHgVQKCgFHgUFHgVLHgUFHgUFHgUFHgVQKCgFHgUFHgUAAAAABgAUAHcBLAHfACcALwA3AD8ARwBPAAATNTMVFBYyFhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2FjQmIgYUFjIWNCYiBhQWMgY0JiIGFBYyFjQmIgYUFjIGNCYiBhQWMmR4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBSgFHgUFHlUFHgUFHnMFHgUFHqUFHgUFHksFHgUFHgHLFBQPBQUeBcgFHgUFDxQUDwUFHgXIBR4FBSgeBQUeBUseBQUeBSMeBQUeBSMeBQUeBUseBQUeBQAAAAQAKAB3ARgB3wAjACcAKwAzAAATNTMVFBY7AREjIiY0JiIGFAYiJjQmIgYUBiImNCYrAREzMjYXNSMVMzUjFRQ0JiIGFBYyUKAFDxQUDwUFHgUFHgUFHgUFHgUFDxQUDwUoKHgoBR4FBR4ByxQUDwX+wAUeBQUeBQUeBQUeBQUeBQEYBX1QUFBQSx4FBR4FAAEAKAB3ARgCBwBcAAATNTMVMxUUBiIGFBYyFhQGIiY0JiImNCYrARUzMhYUFjIWFBY7ARUjIgYdASM1NCYrATUzMjY0JiImNDYyFhQWMhYUBiIGFBYyFh0BMzUjIiY0JiImNCYrATUzMjZ4UFAFHgUFHgUFHgUFHgUFDxQUDwUFHgUFDxQUDwWgBQ8UFA8FBR4FBR4FBR4FBR4FBR4FUBQPBQUeBQUPFBQPBQHzFCgUDwUFHgUFHgUFHgUFHgVQBR4FBR4FeAUPFBQPBVAFHgUFHgUFHgUFHgUFHgUFDxR4BR4FBR4FeAUAAAAABQAAAE8BQAIvAAMAUABwAIoAkgAANzUzFQI0NjIWFBYyFhQWOwEVMzIWFBYyFhQGIgYUFjIWFAYiBhQGIgYdASM1NCYiJjQmIiY0NjI2NCYiJjQ2OwE1MxUzNTQmKwE1IyImNDYyFjQmIgYUFjsBFTMyFhQWMhYUFjI2NCYiJjQmKwE1IyIHNCYiBhQWMhYUBiIGFBYyFh0BMzUzNSM1IxI0NjIWFAYiUFAoBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FoAUeBQUeBQUeBQUeBQUPFChQBQ8UFA8FBR4tBR4FBQ8UFA8FBR4FBR4FBR4FBQ8UFA9VBR4FBR4FBR4FBR4FUFBQUCgFHgUFHp8oKAEdHgUFHgUFHgVQBR4FBR4FBR4FBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBR4FUFAUDwVQBR4FIx4FBR4FUAUeBQUeBQUeBQUeBVCMDwUFHgUFHgUFHgUFDxQoKCgBHR4FBR4FAAAAAAEAAAEXAUACLwAeAAARNSERIyImNCYiJjQmIiY9ASM1NCYiJjQmIiY0JiImAUAUDwUFHgUFHgVQBR4FBR4FBR4FAhsU/ugFHgUFHgUFDxQUDwUFHgUFHgUFAAAAAQAA//8BQAIvAB0AADURIREjIiY0JiImNCYiJj0BIzU0JiImNCYiJjQmIwFAFA8FBR4FBR4FUAUeBQUeBQUP7wFA/dAFHgUFHgUFDxQUDwUFHgUFHgUAAAEAAP//AUACLwAdAAAVESERIyIGFAYiBhQGIgYdASMVFAYiBhQGIgYUBiMBQBQPBQUeBQUeBVAFHgUFHgUFDwECMP7ABR4FBR4FBQ8UFA8FBR4FBR4FAAABAAABFwFAAi8AHgAAGQEhFRQGIgYUBiIGFAYiBh0BIxUUBiIGFAYiBhQGIwFABR4FBR4FBR4FUAUeBQUeBQUPARcBGBQPBQUeBQUeBQUPFBQPBQUeBQUeBQAAAAQAAABPAUAB3wAXABsAHwAtAAATNTMVFBY7AREjIgYdASM1NCYrAREzMjYXNSMVMzUjFQc0JiIGFBYyFh0BMzUjKPAFDxQUDwXwBQ8UFA8FUCigKFAFHgUFHgVQUAHLFBQPBf7ABQ8UFA8FAUAFfVBQUFBkDwUFHgUFDxQoAAAABAAAAE8BQAHfABcAGwAfACMAABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhc1IxUzNSMdATUjFSjwBQ8UFA8F8AUPFBQPBVAooChQAcsUFA8F/sAFDxQUDwUBQAV9UFBQUKBQUAAAAAAEAAAATwFAAd8AFwAbAB8ALQAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzUjFTM1Ix0BNSMVFAYiBhQWMjY9ASjwBQ8UFA8F8AUPFBQPBVAooChQBR4FBR4FAcsUFA8F/sAFDxQUDwUBQAV9UFBQUKAoFA8FBR4FBQ8UAAAAAAMAAABPAUAB3wAXADkARwAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzQmIgYUFjIWFAYrARUzNTMVMzUjIiY0NjI2NCYiBh0BIxc1IxUUBiIGFBYyNj0BKPAFDxQUDwXwBQ8UFA8FUAUeBQUeBQUPFChQKBQPBQUeBQUeBVBQUAUeBQUeBQHLFBQPBf7ABQ8UFA8FAUAFGQ8FBR4FBR4FUFBQUAUeBQUeBQUPFPAoFA8FBR4FBQ8UAAAABAAAAE8BQAHfABcAHwAjACsAABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhY0JiIGFBYyMzUjFRQ0JiIGFBYyKPAFDxQUDwXwBQ8UFA8FUAUeBQUefVAFHgUFHgHLFBQPBf7ABQ8UFA8FAUAFeB4FBR4FKCibHgUFHgUAAAQAAABPAUAB3wAXADcAVwBfAAATNTMVFBY7AREjIgYdASM1NCYrAREzMjYWNCYiBhQWMhYUBiIGFBYyNjQ2MhYUFjI2NCYiJjQmIjY0JiIGFBYyFhQWMhYUFjI2NCYiJjQ2MjY0JiIGFAYiBjQmIgYUFjIo8AUPFBQPBfAFDxQUDwUoBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR5zBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR5VBR4FBR4ByxQUDwX+wAUPFBQPBQFABVAeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBeseBQUeBQADAAAATwFAAd8AFwApADIAABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhc1IxUzNTQ2MhYdATM1NDYyNgc0JisBFTM1IyjwBQ8UFA8F8AUPFBQPBfDwUAUeBVAFHgWgBQ8UeFAByxQUDwX+wAUPFBQPBQFABUEUUBQPBQUPFBQPBQWRDwVQKAAABAAAAE8BQAHfABcAGwAfAC8AABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhc1IxUzNSMdATUjFRQGIgYdATM1NCYiJijwBQ8UFA8F8AUPFBQPBVAooChQBR4FoAUeBQHLFBQPBf7ABQ8UFA8FAUAFfVBQUFBkFBQPBQUPFBQPBQUAAAAAAQB4AO8AyAE/AAMAADc1MxV4UO9QUAAAAQCMAO8AtAEXAAcAADY0NjIWFAYijAUeBQUe9B4FBR4FAAAAAQAAAE8BQAIHADEAAAA0NjIWFAYrARUjFSMVIxUjIgYUBiImNCYrATUjNTMyFhQWOwEVMzUzNTM1MzI2NDYyARgFHgUFDxQoKCgUDwUFHgUFDxQoFA8FBQ8UKCgoFA8FBR4B5B4FBR4FUHhQUAUeBQUeBVB4BR4FUFBQUAUeBQABAFABPwDwAi8AHQAAEjQ2MhYUBisBFSMVIyImNCYrATUzMjY0NjI2NDYyyAUeBQUPFCgUDwUFDxQUDwUFHgUFHgIMHgUFHgVQeAUeBVAFHgUFHgUAAAAAAQAoAHcBGAG3ACcAABI0NjsBFTMyFh0BMxUUFjsBFSMiBhQGKwE1IyImPQEjNTQmKwE1MzJQBQ8UFA8FUAUPFBQPBQUPFBQPBVAFDxQUDwGUHgV4BQ8UFA8FUAUeBXgFDxQUDwVQAAABADwAnwEEAY8AAwAANzUzFTzIn/DwAAABAFD//wDwAO8AHQAANzUzMhYUFjsBFSMiBhQGIgYUBiIGFAYiJjQ2OwE1oBQPBQUPFBQPBQUeBQUeBQUeBQUPFHd4BR4FUAUeBQUeBQUeBQUeBVAAAAAAAAAOAK4AAQAAAAAAAAA0AGoAAQAAAAAAAQAIALEAAQAAAAAAAgAGAMgAAQAAAAAAAwAlARsAAQAAAAAABAAJAVUAAQAAAAAABQAQAYEAAQAAAAAABgAIAaQAAwABBAkAAABoAAAAAwABBAkAAQAQAJ8AAwABBAkAAgAMALoAAwABBAkAAwBKAM8AAwABBAkABAASAUEAAwABBAkABQAgAV8AAwABBAkABgAQAZIAQwByAGUAYQB0AGUAZAAgAHcAaQB0AGgAIABGAG8AbgB0AEYAbwByAGcAZQAgADIALgAwACAAKABoAHQAdABwADoALwAvAGYAbwBuAHQAZgBvAHIAZwBlAC4AcwBmAC4AbgBlAHQAKQAAQ3JlYXRlZCB3aXRoIEZvbnRGb3JnZSAyLjAgKGh0dHA6Ly9mb250Zm9yZ2Uuc2YubmV0KQAAbQBlAGcAYQB6AGUAdQB4AABtZWdhemV1eAAATQBlAGQAaQB1AG0AAE1lZGl1bQAARgBvAG4AdABGAG8AcgBnAGUAIAAyAC4AMAAgADoAIABtAGUAZwBhAHoAZQB1AHgAIAAgADoAIAAyADEALQA2AC0AMgAwADEAMgAARm9udEZvcmdlIDIuMCA6IG1lZ2F6ZXV4ICA6IDIxLTYtMjAxMgAAbQBlAGcAYQB6AGUAdQB4ACAAAG1lZ2F6ZXV4IAAAVgBlAHIAcwBpAG8AbgAgADAAMAAxAC4AMAAwADAAIAAAVmVyc2lvbiAwMDEuMDAwIAAAbQBlAGcAYQB6AGUAdQB4AABtZWdhemV1eAAAAAIAAAAAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAAAAABCwAAAAEAAgECAQMBBAEFAQYBBwEIAQkBCgELAQwBDQEOAQ8BEAERARIBEwEUARUBFgEXARgBGQEaARsBHAEdAR4BHwEgASEBIgEjASQBJQEmAScBKAEpASoBKwEsAS0BLgEvATABMQEyATMBNAE1ATYBNwE4ATkBOgE7ATwBPQE+AT8BQAFBAUIBQwFEAUUBRgFHAUgBSQFKAUsBTAFNAU4BTwFQAVEBUgFTAVQBVQFWAVcBWAFZAVoBWwFcAV0BXgFfAWAApQFhAWIBYwCcAWQBZQFmAWcBaAFpAWoBawFsAW0BbgFvAXABcQFyAXMBdAF1AXYBdwF4AXkBegF7AXwBfQF+AX8BgAGBAYIBgwGEAYUBhgGHAYgBiQGKAYsBjAGNAY4BjwGQAZEBkgGTAZQBlQGWAZcBmAGZAZoBmwGcAZ0BngGfAaABoQGiAaMBpAGlAaYBpwGoAakBqgGrAawBrQGuAa8BsAGxAbIBswG0AbUBtgG3AbgBuQG6AbsBvAG9Ab4BvwHAAcEBwgHDAcQBxQHGAccByAHJAcoBywHMAc0BzgHPAdAB0QHSAdMB1AHVAdYB1wHYAdkB2gHbAdwB3QHeAd8B4AHhAeIB4wHkAeUB5gHnAegB6QHqAesB7AHtAe4B7wHwAfEB8gHzAfQB9QH2AfcB+AH5AfoB+wH8Af0B/gH/AgACAQICAgMCBAIFAgYCBwZVKzAwMjAGVSswMDIxBlUrMDAyMgZVKzAwMjMGVSswMDI0BlUrMDAyNQZVKzAwMjYGVSswMDI3BlUrMDAyOAZVKzAwMjkGVSswMDJhBlUrMDAyYgZVKzAwMmMGVSswMDJkBlUrMDAyZQZVKzAwMmYGVSswMDMwBlUrMDAzMQZVKzAwMzIGVSswMDMzBlUrMDAzNAZVKzAwMzUGVSswMDM2BlUrMDAzNwZVKzAwMzgGVSswMDM5BlUrMDAzYQZVKzAwM2IGVSswMDNjBlUrMDAzZAZVKzAwM2UGVSswMDNmBlUrMDA0MAZVKzAwNDEGVSswMDQyBlUrMDA0MwZVKzAwNDQGVSswMDQ1BlUrMDA0NgZVKzAwNDcGVSswMDQ4BlUrMDA0OQZVKzAwNGEGVSswMDRiBlUrMDA0YwZVKzAwNGQGVSswMDRlBlUrMDA0ZgZVKzAwNTAGVSswMDUxBlUrMDA1MgZVKzAwNTMGVSswMDU0BlUrMDA1NQZVKzAwNTYGVSswMDU3BlUrMDA1OAZVKzAwNTkGVSswMDVhBlUrMDA1YgZVKzAwNWMGVSswMDVkBlUrMDA1ZQZVKzAwNWYGVSswMDYwBlUrMDA2MQZVKzAwNjIGVSswMDYzBlUrMDA2NAZVKzAwNjUGVSswMDY2BlUrMDA2NwZVKzAwNjgGVSswMDY5BlUrMDA2YQZVKzAwNmIGVSswMDZjBlUrMDA2ZAZVKzAwNmUGVSswMDZmBlUrMDA3MAZVKzAwNzEGVSswMDcyBlUrMDA3MwZVKzAwNzQGVSswMDc1BlUrMDA3NgZVKzAwNzcGVSswMDc4BlUrMDA3OQZVKzAwN2EGVSswMDdiBlUrMDA3YwZVKzAwN2QGVSswMDdlBWFuZ2xlDGludGVyc2VjdGlvbgV1bmlvbgd1bmkyMjM1B3VuaTIyNTILZXF1aXZhbGVuY2UNcGVycGVuZGljdWxhcgZVK2UwMDEGVStlMDAyBlUrZTAwMwZVK2UwMDQGVStlMDA1BlUrZTAwNgZVK2UwMDcGVStlMDA4BlUrZTAwOQZVK2UwMGEGVStlMDBiBlUrZTAwYwZVK2UwMGQGVStlMDBlBlUrZTAwZgZVK2UwMTAGVStlMDExBlUrZTAxMgZVK2UwMTMGVStlMDE0BlUrZTAxNQZVK2UwMTYGVStlMDE3BlUrZTAxOAZVK2UwMTkGVStlMDFhBlUrZTAxYgZVK2UwMWMGVStlMDFkBlUrZTAxZQZVK2UwMWYGVStlMDdmBlUrZTA4MAZVK2UwODEGVStlMDgyBlUrZTA4MwZVK2UwODQGVStlMDg1BlUrZTA4NgZVK2UwODcGVStlMDg4BlUrZTA4OQZVK2UwOGEGVStlMDhiBlUrZTA4YwZVK2UwOGQGVStlMDhlBlUrZTA4ZgZVK2UwOTAGVStlMDkxBlUrZTA5MgZVK2UwOTMGVStlMDk0BlUrZTA5NQZVK2UwOTYGVStlMDk3BlUrZTA5OAZVK2UwOTkGVStlMDlhBlUrZTA5YgZVK2UwOWMGVStlMDlkBlUrZTA5ZQZVK2UwOWYGVStlMGEwBlUrZTBhMQZVK2UwYTIGVStlMGEzBlUrZTBhNAZVK2UwYTUGVStlMGE2BlUrZTBhNwZVK2UwYTgGVStlMGE5BlUrZTBhYQZVK2UwYWIGVStlMGFjBlUrZTBhZAZVK2UwYWUGVStlMGFmBlUrZTBiMAZVK2UwYjEGVStlMGIyBlUrZTBiMwZVK2UwYjQGVStlMGI1BlUrZTBiNgZVK2UwYjcGVStlMGI4BlUrZTBiOQZVK2UwYmEGVStlMGJiBlUrZTBiYwZVK2UwYmQGVStlMGJlBlUrZTBiZgZVK2UwYzAGVStlMGMxBlUrZTBjMgZVK2UwYzMGVStlMGM0BlUrZTBjNQZVK2UwYzYGVStlMGM3BlUrZTBjOAZVK2UwYzkGVStlMGNhBlUrZTBjYgZVK2UwY2MGVStlMGNkBlUrZTBjZQZVK2UwY2YGVStlMGQwBlUrZTBkMQZVK2UwZDIGVStlMGQzBlUrZTBkNAZVK2UwZDUGVStlMGQ2BlUrZTBkNwZVK2UwZDgGVStlMGQ5BlUrZTBkYQZVK2UwZGIGVStlMGRjBlUrZTBkZAZVK2UwZGUGVStlMGRmBlUrZTBlMAZVK2UwZTEGVStlMGUyBlUrZTBlMwZVK2UwZTQGVStlMGU1BlUrZTBlNgZVK2UwZTcGVStlMGU4BlUrZTBlOQZVK2UwZWEGVStlMGViBlUrZTBlYwZVK2UwZWQGVStlMGVlBlUrZTBlZgZVK2UwZjAGVStlMGYxBlUrZTBmMgZVK2UwZjMGVStlMGY0BlUrZTBmNQZVK2UwZjYGVStlMGY3BlUrZTBmOAZVK2UwZjkGVStlMGZhBlUrZTBmYgZVK2UwZmMGVStlMGZkBlUrZTBmZQZVK2UwZmYAAAAB//8AAgABAAAADgAAABgAIAAAAAIAAQABAQoAAQAEAAAAAgAAAAEAAAABAAAAAAABAAAAAMmJbzEAAAAAzAgvTwAAAADMCE7f);\n\n  src: /* mzxfont.woff */\n       url(data:application/x-font-woff;charset=utf-8;base64,d09GRk9UVE8AAC+UAAsAAAAB7AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAAD4AAAKtcAAeIMsz6ifUZGVE0AAC7cAAAAHAAAABxe5KzIR0RFRgAALrgAAAAkAAAAKAE6ACRPUy8yAAABYAAAAE4AAABgVyxS2mNtYXAAAAL4AAAAzgAAAYqPj5ZRaGVhZAAAAQgAAAAwAAAANvhv3/hoaGVhAAABOAAAAB4AAAAkBDYCS2htdHgAAC74AAAAnAAABCxMgBi9bWF4cAAAAVgAAAAGAAAABgELUABuYW1lAAABsAAAAUUAAAJbuTfNiHBvc3QAAAPIAAAAFgAAACD/hgAzeNpjYGRgYADieLM/S+P5bb4ycDMZAEUYznD4SyLo//8ZHZhmAbkcDEwgUQAS1glyeNpjYGRgYJr1/z9DFKMDAxAASUYGFMDIDQBivAOFAAAAAFAAAQsAAHjaY2BhdGD8wsDKwMDUxbSbgYGhB0Iz3mcwZGQCijKwcTLAAKMAA4MDjBOQ5prCcIBB4cF/JgMgN4ppFgMjSA1YYTpQvwIQMgIAdQ0MpQAAeNp9kM1Kw0AUhc+0aWlBpLh1MxuhLjqZDNJFdlLoQujSbqXQyY/QpKRTKt35Cr6AG9eCW5/Ana/kSRwLijTDzP3uyb03JwPgFK8Q+H6ucO9ZoIc3zy108eG5jQtx7jlAX9x67mAgHj130RMvrBRBn1nWdNUsMMCz5xZO8O65jRt8eg5wJq49dyDFnecu5z9hggoWCzieS0jskJMz0hQlCnIdK6R8L2GgoBmHrHBca8QIuRJfmxxqFTbMFFVL/RKYVHbh7FLucpfJaVm4aVmlVhql5TBzbh2HYUI1qVW1SVRhHbtWbE9pb8+4xQMFmy72dkuaNY5zyismdplvGY+Zjrn/zpNeN4gwwpjbsDbiid8eY/nzZUk20Wg8Mjoy+GfiwSNxTrXiVeSNL8nZ9XTVxNoT5rba5GUhtY6U1loe++Uv3NRk+AAAAHjaY2BgYGaAYBkGRgYQaAHyGMF8FoYMIC3GIAAUYWOoU5JSUlDSVjJVClJKVFr6QP7B////gfIKYHFNuDjjg3qgxMN7bvcc71neM7gnco/t7mGFDAVOqPlYACMbA25JmBomZhZWNnYOTi5uHl4+fgFBIWERUTFxCUkpaRlZOXkFRSVlFVU1dQ1NLW0dXT19A0MjYxNTM3MLSytrG1s7ewdHJ2cXVzd3D08vbx9fP/+AwKDgkNCw8IjIqOiY2Lh4BvJBCoyRQIEhDABO1S3lAAB42mNgZgCD/80MRkCKkQENAAAoVQG5AAB42u19C3wcVb3/ObOzm+1mCFiYIF6F8Cgtj5ammbwY3igUK9RHUvGyqMlkA4VSSilEmgBCF4oNClSBXq8QBK+PZqVcB/ByQa+Pan3rlcAtjH/13j+Kj17hetUJTm3vOb99ZHezm53f7JzNbEk/n063m8l3Zs6c8zu/5/dHiSwTSum8qxKX9W1MXPchQiVCyYrJg8lrC+hrx0uvLQxNHi7fqdAjY+Tw267ZPzqa+6A0jG6xJ15bEn4LIfOOOZgdCTmEHRs738A/H/EWQr//9vn0IA7YQA4ih5IjyFFkATmJtJJOcjo5l1xAVpJecgnpI5eRq8i15AZyM7mNjJJ7yP3kAfII+QLZQZ4gT5Ovk13kh+RZ8gL5BfkV+T35H2KTv9EQnUcPpir9O3o0XUgX0zbaTc+kb6Mr6Lvoe+ml1KCr6dX0OjpMb6Gb6Ufpx+k/0DH6T3Sc/jP9Mv0K/Sb9Lv0xfY5a9D/py/S/6f/S1+h+KSw1Sm+QDpfeIh0rnSCdIrVLunSOdL70Huli6YPSZdJa6QbpdulOaau0TXpQ+oy0XXpMelJ6RvqG9B3pR9KE9KL0S+nX0h7pj9KktC8kh2KhQ0LNoTeHjgktCi0JaaFTQ2eFzgu9I/Tu0MWh94cGQleE1oWuD42Ebg3dEfpY6BOhT4YeCn02lAp9KfQvoa+Gdoa+F/pJ6PnQz0L/FfpN6A+hP4X+KhM5IivyfPmN8pHycfKJ8lK5Qz5NPkdeLl8k98h/L39QHpTXyOvlD8k3yUl5i3y3fJ/8Kflh+fPyo/Lj8r/KX5O/Lf9A/qm8W/65/JL8O/lV+S/y3rAUjoabwoeF3xRuCR8fPjm8LNwVPiP81vDbw+8MrwrHw/3hy8NrwxvCG8MfDt8evjO8Nbwt/GD4M+Ht4cfCT4afCX8j/J3wj8IT4RfDvwz/Orwn/MfwZHhfRI7EIodEmiNvjhwTWRRZEtEip0bOipwXWRG5JHJXZFvkod6Tli5dthSOrXBcBsc2OGpwbIdjBxw74dgFx2449sGxH44GHAfgmIDjID+2AX4b4LcBfhvgtwF+G+C3AX4b4LcBfhvgtwF+G+C3AX4b4LcBfhvga4CvAb4G+Brga4CvAb4G+Brga4CvAb4G+Brga4CvAb4G+BrgtwN+O+C3A3474LcDfjvgtwN+O+C3A3474LcDfjvgtwN+O+C3A3474HcAfgfgdwB+B+B3AH4H4HcAfgfgdwB+B+B3AH4H4HcAfgfgdwB+B+B3An4n4HcCfifgdwJ+J+B3An4n4HcCfifgdwJ+J+B3An4n4Hcm1vcNrDb61vStvWxNYvXaDYn11yaMDauvXnvdWnbgX1y2vm8N+8+yZW3t8E/7ssQ1162+vm9NYq2RWJdYvy6xliFct6Zvfe9JiaVLW+G4DI5tcNTg2A7HDjh2wrELjt1w7INjPxwNOA7AMQHHQX5sXQpHwG8F/FbAbwX8VsBvBfxWwG8F/FbAbwX8VsBvBfxWwG8F/FbA74RjF1ylC67SBVfpgqt0wVW64CpdcJUuuEoXXKULrtIFV+mCq3TBVbrgKl1wlS7A7wb8bsDvBvxuwO8G/G7A7wb8bsDvBvxuwO8G/G7A7wb8bsDvBvxuwO8D/D7A7wP8PsDvA/w+wO8D/D7A7wP8PsDvA/w+wO8D/D7A7wP8PsDvB/x+wO8H/H7A7wf8fsDvB/x+wO8H/H7A7wf8fsDvB/x+wO8H/H7ANwDfAHwD8A3ANwDfAHwD8A3ANwDfAHwD8A3ANwDfAHwD8A3AHwD8AcAfAPwBwB8A/AHAHwD8AcAfAPwBwB8A/AHAHwD8AcAfAPwBwE8AfgLwE4CfAPwE4CcAPwH4CcBPAH4C8BOAnwD8BOAnAD8B+AnAHwT8QcAfBPxBwB8E/EHAHwT8QcAfBPxBwB8E/EHAHwT8QcAfBPzBwSVrr1uzZu3Va6/qW3/l6rWXrU9suG792nPXJ/o2JAZahlZvuLzlvKvXbjjv6vWXJVqWLVnasujyDRvWnXrKKYPs20H+7ZJrGUZiwwlZLaglpw4RwnSHO+hH6BY6Su9kWsTH6F30bnoP3cr0iU/Qe+l99H66jWkWn6T/SD9FH6APMh3jIfpp+jB9hH6GaRufpZ+jn6dfoNuZ3pGiX6SP0h30MaaBfIma9HH6BH2S6SL/Qp+i/0qfps8wreSr9N/o1+jX6TeYfrKTfot+m+6i32Gayvfo9+kP6A/pj5jO8hP67/Sn9Fk6wbSX5+l/0N30Bfoi02N+Rv8f/Tn9Bf0l02j+i/5/+hL9Ff01021+Q39Lf0d/T/cwLecP9BX6Kv0f+kem7/yJ/pn+hdp0kmk+f6UO3Uv/RvcxHYhIVJKkkCQzbSgiNUhRaZ4UY3qRIh0kNUkHS4cwDWm+dKh0mKRKzUxXeqN0hPQm6e+kNzOt6UjpKKlFOlo6hulPx0kLpOOlhdIipkmdKJ0knSwtlpYwnWqp1Cotk9okjWlXHVKn1CV1S6cyPes06XTpDOlM6SzpbKZznSu9VXqbdB7TvZZLF0hvl1ZI75AulC6SVkrvlN4lvZtpZD1Sr7RKei/TzN4n/b10iRSXLpXeL32A6Wl9Ur9kSANSQhpkOtvl0mrpCulKaY10FdPfrpbWSddI66VrpQ3SddL10pD0IabTbZSGpRHpRukm6Wbpw9It0q3SJikp3cZ0vc3SHdJHpC3SKNP6Pip9TLpLups2htjEIKSZkMMJ+SihFxPptyR8D5l3M1GWEeVU0pQgh9xF5h9K5i8g8xeT+V1k/jmk+Qbyxt+SI3aQt5xPjtxNjr6CHHM2OfbTZMFR5HiTLDqMLFpAFt1HlpxClpxD2l4k7Y2k/SXS9T1yKiGnvUrOXEzOvpKcexE57zBy3snkvEly/nfJ8kfIBd8jKz5FLnwruegZsvKb5J17ybvDpOdC0nsd6X2ZXPx18r4OEl9ALn036VtL+m4iicdJ4ltk9UVkdQ9Z/RVyxZ/JmveQq24mV19G1l1N1j9KNpxFrltHrj+UXP80uWEFueFlsnGUDL+PjLSSGz9BbrLJh28mtz5FNv2GJJ8ktz1Pbv8c2fJ7cmcz+dj/krt2kbteIHd/kWxdSLYuIltPIFtPJFtPIltPJlsXk61LyNZTyNal5OOHkI+/RO77LnlAJQ81kof3kUci5JH55JGXyD99nHzhcpIaJ48S8uitZMca8s86MQ8iT7xKnryPPPll8tVF5KtnkK/dSL7+J/LNK8m3OskuiXz3dPKDb5MfdpMfbic/Por8+wfJs58mzz1PdsfI7kayWyG7DyK7m8juHeSFd5AXfkde3Ex+9hT5hUz+cx/51Zlkz0/InhfJnpfInj+QPZPkT9eQP59P/vIimdxAnIvJvh1k34/Jvj+Q/QdRto7CW2nDajrvAjrvShq7nSpP0Tc8SdXH6JvitKWZLjiKnvQR2no9bb+Rdq6mpyv0nMfoBTfSdVfQjU/Rjd+kG5+jG5+nG/+DbtxNN75AN75Kh2U63EyHT6bDi+nwEjp8Gh1eQYcvpcNr6fDNdPg2OvwgHR6jww/R4Sfp8Pfp8M/p8Ct0RKYjTXRkER05gY6cSEdOoiMn05HFdGQJHTmFjiylI610ZBkdOZuOrKQjPXTk/XTkcjqygY7cTG96gt56Ib31Gpr8Ir39c/SOIbrlPHqnTu/+Cr3nLvqJG+knG+jD36afeYZ+9g76+ffQ7RfS8aPp+Fl0/FL6xU76qETNT9PHH6ZPDNEnbqFPfIR+eSd96h769OH06SPpM7+jz+yhz/x3qtl+4LUlzgOR1N8OUif32Jfs3dPQ1GQrdqNqWXrE3iaP3rJ///4Tt+/f/1d+2Ne7atoBfsBOuWU0OuQopm5Zpq0MRW4Z5T8a30/Ic3Bm735Ct2/P/7Sf/zT83KrRe6NjekOTHbcddSxy7yj/sjf9e+zE8fTZeZ8I/yn83ui9U/8rfW4W6d7R6Jit6BFHc5TcRValHy17X6vyP8Fds0cbvYWNQO5/pc/NIsFFND3StGvyWHVqBNLjx587O2rZT3wE8i/Sm7vIeO4i47mL9OYu4igF6OM5zIJDBn28AH08h9mb/ymDPs7R+Rtkf6dGKT3GRSMLn+Adsd+FV5EeTEAqunf4BE/GkPg8sZUC9FU5zIJDBn1VAfqqacMPnzLoqzi6pesGn72Kk1QNWzEjTXbL5LWqPhQpP5t7c7N5PG82T03H7EwZn2GmsOdi04v9VgNmNNIvMjOvC87NvqHMQplxlHtzuOO5UdYLbr/ozWeXYub2x/ntW/y1N5RfgSVfTG4SlJ4i2QmUXoHZebs9Jxemv8rsi+ZywdGYMFKGGjDrA/PKcIvbwt1++Vnbm5u143mzls3TdRnJa9rLVcxkRcqa3P9Kn5u3CEQBY3CjecBlZF1WEk4tp6xcXDUNPTOm49kb3p7DnX5uFik3EqWASwrcMsDlbtidIGezBISL6zWXt5Z7c6cVHDL7Z2/Bblrm3CwSWwuigDG4Ucz+71GqldmashtXkVTIzubpWlJ2IbN36CRXwhpvemXyAypOv9LRqotr8PKPUHZBetxHXWxEtpKd5rhZMx19PIfeW7SGBKorTIcquH8XyqmF1ZgdpcFiuqxhmRHTaVFbYrqhIvU83Z5RwRnPzafe7HvnitSx6tGxJlvLzV2MvHe3QpA2R7xgUYxPG7Wi2ZW35Zc5N4vkeV6N5+ZV7wwKWt77LnNuseLnDpfN17g4OwqDC3adlZ4prtUscbaZEy+Q1L6qBxjcvF3c/00RtYuP2XGc3Mmbh2XOnVJp0R4Ac/JgtbyiX91MYXunYYEd4cMrLamYmZZhebH83AkAtkOYyE0FuWMZusm9MIr9rMrNZwu8ANwmaWE2CRP5Y/a4aqdMJxWZchA12dtm1GFex4bK7O2IGE0OY6gUOBSmi6KSPqEyoqicoeLO1xStgQVUWWULxAZbnxZQ0y6k1GBaXgptQ8RxpjLbDFNo91S8wdZWqqLkSSAEIDd/bCXipGRHsWYSANWtJ2HTPjrGNyykqR0I4cyUCieuG3bS1O0JtjfvErjXcgXGVvShhtfLPjp7Dj+2j9opSze4W6SefXkH+E7megeowbrkUSUj/Y+pF0RDKvp5hG+aCjoogngfHiJG7IXYyz15p4Rtyv7vIwK9tbqt6bjwMo97Yl2q3sPLLgLAdlyPOHFZL5CvFWebuMyDSNp4jmfWb6Se128mJhs3nVQDOoLh0TR1Yx8ka2AfeBiqGePppTymlukoDUzUT6jHxJrGJo9V03pZpO70MvZONIsJhlnwRJfwb2G3He7m8mClIjNGhMfRDoTt3NYcjS0GZoCyFSH+nfDdrNAOcrEBcvVM88upX86T5vodJr2PMvfttqWT/xTZyvh/21Rc4AQd4GBiL++C8dev9lbCWSZwf3UdaYl6DMq5DLC6C9xGRUVuUWFzoakGwkLCooBRISx88FDAHnQAue2rNEuQ8cGGJtO+RLWTppOMWHL63yYzF9z3P2SLCu6LAkalx/iTpToduBZxAF8TK6OiUieEOXDnMiurzKwUkMgfiMQUUcAY3ChOm+dpHC2ZDA3d3oa0l3TLEupwgCvM5BWoCx++t4i06Wh2io2uzn1oaNM87skX3hKUTE2Rzj+Lx6gdxcKavpgSjXoVGy59e6au4qrQ9OJ8ACH6tLu85LEZ9dCSNXQpdJAjWffOtGSDHXc09VjuTDs2FiT3fRwnTMub7FX6GIam3Hhu37LlV1ZxtbVBOi7/HJ8nzp+1PkvxeCimoTZSIh4YN3A8IHthXpocZujj6CJn17Yse00pbOlLTSZPihe7BkSacA0V6aVAVB/ZikgTw1asgjD7LEqqdCyxJrNHQ28AyAKwuVc8wyue06ZEFnLOWMjnipjD0QxLT+cEzelI5XUk7jiyNTalNcveq3PCFOA0wZbx5Qswt1FjDwIshX07/BmzPhGUNNZh8iA9Pho2Dj71QG4DMzVTbNk8MHXODMML6ZA+C0wltMn5W3SDc6lEXodaVwqbMTa1auOed3PXUkpAeYYT54ImS84TkCoaMTEHhAe/Zm8UG9l0ofhxRyzbOpSA1BYLfU9c6zsuZgXJuvWalBEQZoEAmeMeDCpbwWWdV+fH9RK3wmdKK7pdSGrmhoTN9JqGmFQL5AYmX6lySGSI19PZtVIsFD6DxBnYXM4qRj7fYk1mkI5OchZE6WDhSqHSISsPWny8ipDAdFq/VbkZvL1idUvVrBoFAszF8uCxf8tTBN3Mo+90nQyCEJNOsgbmZFKcvgVuoiQvBWpTa5DHP6E7E8x0n9D5FTepAXEI1SCFt/KL8KjACMuwmtVSAX1qXdWXJpqEmX2JaupWVuMSQhbHttcUGyTD4j6JJLfMJo9Vxe2A+ey+QmrPLe8kDv4OLZqV2POm6oIrNz8SIiAlTa9BBZ/vtnCak9a0nxXKmD3zdPdpX1eEEotruqPp4CUfq8BhXm4ilbpUuQplt47hDG/ygctME5QyKoT0ZRpZylaCkjDEE/gaDiD2D18KYoTIfx4ftuxGlYdQ7JRlOlqxrJhVS1pURYywyjp/GBSqVEoEBquVgDgQApXGPKsxGYz3szxwuRt22ySAx+FXqEyc8PiovZeLEbtlckd+gn8qJ+178j9ldoVUwe6Wyu0nPfmfMrtNKiAJ/ki+pAJXyeyxvgQibldUgFzFzK2yvQVqDe8n8/bvK7GOU/mfMmg9BYKnp/S5GaSeQonW4xY4u1Kmr6rsmgNu9EZ5Ac+pv1ZFppYWEYe7tMt2MbvMVgxuPnlmca1PNn4NzZWvcUaduKpblqfAzwQPewov/RAVAuCFUTyuthz72IqeT9/jLiy7y34ZG4/URHvDUdzCY5yYkee/GQJFZ2FpHqbmasYmVjXNMa37ph4TjhIxnQlVF+sdihwga8Ky49ZUlhwioK+rjmLykDuahZfPspmopasktUF72mvkekQoAlUk8AtLEHMhxrx0u/GyjDzIKF0vYlj1NYF7WkqwG/dyEt19yJvj1/Wr4MnhPHWk9jk0/otnnn2KLqxHDnC6MST3AL6MzaHXahAcifON0Mw09NBy9+j6nSbzU+Pd5u54WAepvPus0dQrLIHzl0lMbEgTG3qfffLtmsg4AXVocVjawSyPmYAainRJjwlqmMnrekQLPPzmPoYXO1xwW57Et+MpMopICg+ixs/j1N4ScuNq2iKOeHlLSVziw7RZ7i5nUbR7RvycyaXj1kbLxTX6mMbw7oaEPbhqK34Wa7WtxfZfzfWS1Bs0KYncw/EJoe4sOMuwjKD14fDYS6GymwyjnrKXaupmLT2nGNI53bBm3mRLeRWxLG1It1rBAnPDWqJZpo6lVYh6SzkPmLc2rjtxnTf2ABpa0S1/3fIbBKeRa34OIv5tW+JfNoq5SOdkDVAEruO1TnGp2bq4zGycehE0stpZzdnyxrIuIotcIK9pfXpHAlROfwl+C9TEjSK3+I0irrhZ7HvH64CyTC+8V5VlWjUhZKis89S+YXsJeStOkEMnSD0gWkTtU8Gr7NSEZMpJMctBQ1sOjsJNBwVvOoCqLpApx1Z4s7BsO3a+dk0Z/mkyIUkUHxtGjsqU3821mqoUGTCI8K/YLpveskbd5fcVWZHB2Ovdj3zuj2VfrdalLZVJAbRU0zIibObCv8Yu9fiYnXT2qk7SnrAz3UyOj1mOphqWGbHZefxfZ7nspeYVydnrv+rKjUK+bDoFhyLmisVfL8XiWqDChnoD2+JghXJ/YOeB3eQA26ZTtLOk4kqPinXQCSznOgA6+pRwNovSPzx09JlrzYFtzWEEqx+m370shJcKu1LTmS2lLuQFPJuRSco6mh5h9vu8ByKWKlJzFOIIQlOtWHrGRsYw5+Ho76JjFhD04TwNXH0yV6qLYiazdlp429/JFepMufaiOt5WOY/RbHS8eA5H9uslmcOysCyQll/1YuXi6wdsxTeyWjQ6FDEXsonP3WYvqyfEmrbIJ8bsuLNOPSnWZE0ejK1Q0XCxaE+k4N4qHlfyJ8o8XQu20Ew8XxHWlHFFiKnjMgGRqjkqncuwsAlwFvinN4u0ZLEddd2SkPMiU1xOoUifZdbtUG9FkuKoUovSeUSR7LgKAaBpwYW0oo0KZ2lWGgy2q5zMd5XN6LpHoZRrqYoUgHXfB929qEZyTQfFw+s6ybAmC99VuNIbbwPkbFv2JlUoLaaHGoJ4IfGo342yRVVOce5vphsyo49nUhrQxRWbLcK3+2m/6YqW2ElF7HheSbjtqOioAqQGwl9sZmEB+6nL5DJTNzN/sVWflh7hwUT2u+kiLM/MkGXcux7zjkUpyNC7h7cIwhXwiDJ7oyJpuOxtAtMRhLFr8vYHfOVYftUuVFkCK5BBby/OmRXFs1MEIobIhIyjzFiKUp0rFROZi9Zn7jxSnbIVT+Uts+wlgyTYyX+U7biT5L2HkH6VpMX2/DjWk11UAtiTG9NU/qfM6PaAbjLRwBbiFnVxzFGcpGraSSNip8z0ByfFvlrMO5ZsVus51uE7d2sAPB36gZ8zhI+TioqiuFFool7i57NcWVlU9Vh3eQrCSDnEuNXEdNub6n/rke3YXQ0osuOesETooJATi0ldtpXmJbEme5NsTzCTfyLCHQenxNi3PGScyhEPb88RD08nE85SDcMuIbIhODNt/CLWn54lwOEdZSa3abnsyrIGfHEA2HPzLDcNyUxLJLtAYQs6N8TS9WxvcWW4bm0AW7E3qfpMBsCcweizwcibRtnL7WdVXvKn6eLbL80lhArsiRRpegWZJCLM6hKltqO09kAIkjHgN9MsrNNvcoVs6iJDrbqp4wIOyDSdivdexZjyFBbPrXjdJMkZApPe8vIot+c6A6yaoTNAHniZcwtyyOakQBClgDm3u9b37qobaBlut9iOauRZf+5r77EsRJz6yzI4+2wnNp+Sx0VNHh9FZwVC2XpLjiutPgvcfKdNMPGtrbG0WcxCRTs4DJGZW157urmQnLrpVKYVK0dCVFZ+TssAqkKh8NdLiaXZL/Kf9OQeMZX/KTMUPTlbOz2Bsu8hlbtIT/6nzHtIuX4PNe+YiXSPuVNGi/selZoMld5DfXSsLDHzKzeqggkUzymZASn9czVzilgNXCTU6J4J64QSoFXTM7RUljw6NIImbWWTxpzRMKkQvHVF0YN9jkC0DPXAQ4hVRYc8Zb4J8woj5gz/Y6cmr52uR/sfpHFxgWJtb2Jyp9HQtEW2JwpSUEpp7MVOAMMyLHGR0SmTYHKHnDd1U7mb6sn/lJmrqZxF4Ll7j5v2EmByTO50JiJs6AptDwGScGy6ceMy4Wdys4ztTAeNDAqUAVHdjwQoA7ppa7hoGd7P5L68TkgRlsAYlqPxaApSL6pUioVmFcUzuJek7mowdgXDrB9y4qI5lRHEeY6Cb2KEi29zldzk5Gav2PerwngjxXKasdnziiquZNASp8wA+4BsKwVhaFc9X7A+PkcxvDKZCyDpjgclR4vPfFvxLYJRrfDR5/jci6aKFZSZwhaqo6iFbMC+ciDiOiBYOq5YTVjN3VgE6DCXxniXOLU15vB/ApKLbXJK4aAUnmte6k+F5MAZwGodCJaVLEGHwBRWbJK2KPIq15VpaFqeoOWDudy6kKqmS02nLilqfWpFhKnTr7Znj2XhutHmkaAGJM3VZTlsGYldJVu1OBpsC3JbxzNtbBX7ktynTcWfTCQnclAKyOqOLRZDwGBa6QalVtq7p5uRwnbzboSJmKklqpeZMI4HYc3XvOhDs9pTxrP0FUZG4KrIwDTUZTE73twWc+K6KipFBBXg97itVzH/SmdtoLclV2YKG2tRmq2HbjdoLqLZZmVyOcpeumQrBj6nylN3ecWwDGwP1kw0IvmiymkgvUx7l9a/+x512HEymUbU4Gi7CjwZLaquWxaPUuGVA2F5rKgAkWafr2oxa0w1dWwD6uku49ljduJ9HSOO8oraHjMC419yNMvCk1tm4i347BQnyS5na+LTKmdbGRSkTNjxIdV0NHGVHFg/RVB4h1y3S8YQnCV17hi+31OfJGT35mrmSYnsJbZAufTXTLVmJKeVu4hwxja9YtJPcbNgBV1LLWTjZkO6KxM6QWW34IKc+BUlgCcOz+KALOaCZXWU2sHMIovpKo7GtsMTmK4ypHbCV60xvukfpXalf9Qd47RBp8J0PiG2S25nP46nf8xO1tlP2Un8x80nxIxd7BPT4pjKIGosPTg6XcpTBywXdO2VsEbxrvIimGrJ3gpbpmqg9EtX+mHhgLuOlgkr7nIliNAVS0jiHk4GOpOjxUWfOTeX8WDVBaTexFvK9CzXBPJURcvkfLFxZ5tagwQA3+l2gtOOz1XuG7r/RCDcCNiGGWyn1YLDAS8ywO8oafJfW0EywAtMtUJn92NUV82z6uqmhtZOTR4XmAQOpcHWnFT6/X5JticsZyJimaqdtJwkfNAsR+MfpoKPp8X0IfWA7tJVaAa4U03Y0LRNGxpf64UFcxILGxprV/1PmcpbjZcpw+sih+b6vAS7z4t4J4wgcsSaWmt+UMIK7O2GaFCKNuR9Vy4DkTItrEGpoxi8m5CdzEVRZ3m485XJudZcr7/WXJy0tGEuO0p0dhRb+I6WWfmbgyVoxXCOCWO+n/VstDn5N9eacNZbE051zC2SVan8T1nexIK1k8pNp578T5lJmErLqjjIqniO+8gdI0LeZCxzbhZJYIOkgDAEiOeAmhNYcwKr3gTW9CsU0IpkSUcKbr0yBUl53tdykhDN2VGZOBDDDJZnuPZUanaOEt26k8yX3Lkflf7FLCzuIj65HAPL7TInueck95zkrlrVdMWQKFwQYuk7Bauwrr3HaGI+Yc26BdWqi+hhMye55yT3nOSub8mN0VoF69wu22A5SZntNFbOryu0yrMadnc/NhphHAGzv9EIyhrlKWTZKvi5bWluW3rdb0v50xBHmSli04uiZL2Z9oyPTa6Y4lOb/VrOgDRuHvKPprHqGj7NryTTqosGLF13FEvnqcQtqigXF5K9mN0QjnY5yhkERdV9wm4QrF4Arsakacx2VFwBjNiVPa39tpiVgtgbMpM/YvOQommZOjq5HF2v4rYxN6bLnTnXK642veI8MwL6wAfuGw9e5WYpYrU8l9Tl4njcdC/qnXs223TfeFENdYTFtoRLMl8b0nvI3fNfVUFux2kRLYoJb/Yr71AVFuI693ls3ITs8CPUhKj7dNSqOcHFWeBcRNuaUGJkYZljwmS0KPeSmBYt4mS0vfxBT3akS0vPfSNCVPsqR2v256YxDB3V3rSdNCtFJDC8IrUpnJoaasxNl50f1S4XlzdtpWmXmjtjfNA7YpUfwnMMH2W5osjY2UNo/M51eIilRQ/hukOQDyu1uoc4nd95NzwEsAO1xwpfT4Uvhbnoco7pohlYrkE9Lmdba/bnxkus9Rk96tXeeJGUcpmg4y4xFJMXiosDsOE+A7KLPiCSE8I9yWrtLY7S3YbrNvztbxMif7qRTRdvqIbgHntazP6c8J2Cte6SkAUrBGhyat+5l9DEpH6zaQcjOeR1YRi6XR1CeA1EMpOh/a6uJKYnIobZ95wIueFoDfz8/rYoF9bDSSDpsvAF4lLKuwu0RQUmCuCNUjFdbYQJzdmP/Qi5YWH5ptHinpBFpmEgKukOrEZT9ZMBIZrQzXeiOzHxQH92JoxNWqdzotwQV3Tle5oT7iOiZXssYpoxTXOAiGqdMNPejzL7Pfee8JNoGBNKC0SzjHJpQRVDRnXdg8/XVDEPGqxAtv+5voxzFJdemOXnGD1qwujhhcXD7xuOihrivPlW5jVnJ4Fvc6LEEOc8bgLCFMhFEkXyDwgKVeTuusjTm8r/lIHrQZMm1Flb+zm2xhqwNeJ1WWRY02UcVpjN638cti7mRMkhLhPM8xxfifoYEcJkeVZLze0Llziqx3OVnTUE9nj22yCbYSQwoe7qDLKoPyYkJtQdCPtmduZENfYNJtQdCJu3xA3PqLWV1TVX5dS26Wl32aQ8nrk9uUP2hwt2Ovic978W7VzqNiJUMVwRFdtr1WXxdQ1aHx0Yc6LcEJe6YTzDhGePOkef3Jml6hHl7HBJr1VM1eOvK4VXaMW9VdG6Sv1A+g9QXo+obqinxGytuS3GPrXEZvrvQn7wkoyLdIpX5llApQ/bkMyPtlz9TurE3TQb6TP5cJ8VK7x9X/sfCb39lqLZ4tKedW1s4SMSrizwysNdjRGOzah1edNNtrJFtSZ3pvsqPqs6imErTir9Df9jb5LPjtlxJ5U+C3oSnMOZrbdkv9i7MwvATzVldvKEZe91UhHeJ3xcdeKGnbInpn6XgY7LcBV7wonDhcdVO246ip3Mv4+J3DfZ01O2YjoTkfRNp/+XO2NBrPAJ8n/cxO/kWfXcGLuFCdVOmo5mp/ivsad4K/+SPZ09YWumk4THWMCe2Eyfnntadl7cUPkDLsh+Ycr8lPznX5D/4/T5hUhTf3I3G0/fytQjsSHZIp8YYwd2Tyn+vOyTNrnT0dhp2hY193k8d4Kd8kSLMvtNXDRsq2sdTcWjiWMyEekwdV9gMmYnZ+r6WKq4M66ry2JNr6Q7mdc3DeGBy52mBIU7zVEaTC9aqoCmy0HxHInJcrUcjSlvTXajzLcjxdDZPhW3NWgdPxFhG2sLU44sQ21j57Tkah4D8kpEMCryB9cs2MjZiIhfD27NajHmmqhQS9TU+bxpXhIz+AemLdj3q6JicpjKiyiO8Eb3izU3ODPecy4q26mY7lfgJam873M9cYfqxVmCpAz3t6zpgHzzxbJuwp6Y3Fmgx1Uuf0+bCL7JxfK9NYX1RHbJkiSwN6pXnuLpDr5yyV2lUqVKJHcNgZ03MRapEKws96i+xpmD0f+56MWLIor0PYjtnoGSvfTJzXIg2PLy2rYc4EzzSaGtlt3PKm5sRoSxLfhoQFa5MYw5yQZbWckDAsqQyqyZiLEQFNK2mKMshC+sIfaFo+k+RQmqSy1BRgmU5jNjRu4xBFoHWJe7S67HpleYKXlcjJn4i2IWNxAcxVCXxWxtpphNdVR8PpGBl1jdus4taAtJCMTEU2pqKSI9dMJatvhK6VmL3mD+0kSj7nhsSg1273xuMB1NNSwzwqY9/8dItwZAMgf4X7uD5L+3lXTTI8x0Vwqmu5skaTSNnltmCcRLdhS9on5cjUaIDyQj68FqxUtbHtilZiecZbni7p4X63HtvEfGejz1+hFGQeTe+TeEbBsikhcGnc4uKJt0lns1jemm3tBk2W2qZeoR094WDELCMR3ZYGZItwwd3SetbjP5fOd2EGWzal66o6GtSmH7Xt0VbvtcpIsBHrLqcE8Sya2GlmK1qLLyOx1QGFEgRq0Q14MKtQnpzCwbsgy2Exm+eYB9cgnVTu6hGhigeWIK57Vpp9QzYoJrS4SZFELYjvlqaBDIACms+hqhHGaSBzfJAcm+EkYEPYvvsmwSYEX2JlwS4OTOdKbo1KsUxTzllmVIUFcz3zoTzdij0n1auw9kTsWZfxOwLMcL32XlBgHByXfz9i5F9Xx2nzMvgksERbgz5kw0NJmTH1CPj1kZ37StpJ3TjuIs51qR2Ji4oFKaMs9kOElVt6zIDD9+Wyz7w5XqkMf5UjekzMzSMSyd/UXHhYPDhuVKubKcbXlv1mnJiw3rbJrXIAjjdoK7DaKmH8VcqApsv4FtROL/zJ+F8MQBkjEuhmvAC/+Eu2o2pTkQTdLnqKdq0xVhlnM8xXWH15kOcVZ2p7GTC4WVyfF4ZYMwl+mQbtnJBkdL64COwjQhfxSw5aqolga8gMNLi1qXnD5jVgMvotykWhavn2yx71dP57UNyFwIYZWQQzp3Xxoz+lTLrVy32aOOYuqiEzkEGIns3ZmWaSsmVs9FJmvjkunteK44AsfzKag4QoAyNCRwtvBkCtMqcFn0lGacyAh6kXStTRaythU9NMU+xcqR5LH8FGi/M+vYkiqIGlbuhjyG3K54ujKyzrhpl92mLmZCWbMbVUOv+Nuem+xhyeCQzOqu+9oK63vmHhg7D1DtdoYiTaNNTfbEYaOq3ThqJ0dtZTTypdGGUSX2ZnLEoeR4iVDyOH0TPYYuCr0z9F75KPmn4dsiF0Te1aBGb4jeMk+b93Lsk40XKkQxDxpsOqLpBwfffMiyQ371hm3zV8x/z6GRQ586bI16lHqCukxdrT7U/L4aJFP7rG42IpcI1qXFpaweaeT9cGwl0qjbihVpDEgRf3yuiH+aKz8eoCL+Rk6pMjHTdCldmThT8sUMBTaYUhVkDSOUlDR6afJWw+qEUtJBlK9NXHZ5YzValJvILDZl18Lm+et84kN4tBHpi5+6kssCdVxoJi+P13XArKGxBoXQlXOzRZHkCo9LulLtG+24nXTiOEk5C/pHqVv3Mj2EtXJxN9oCu1ThS3zd9P+ONgr0jQviAPFpYtQyBtFYA/pCn/n0GkU1MPPUXtzVrtXopbZL0Fx2aTQ1CmxnH2yJgYn9+yExRAs5MXO52uWHqQzwaZSFzmX3xQyY5Sd0lMtolZ4nBtPolIxGJ1o+C9i4c4nUpfsDZLsHFNyyr853VOuxaC0sc59JEGqhN/ucU9doOUnLTjlapFE3bI0z3TZyRhFcsZEdZ79m2ckCS9RF9JX7k5iBHKhhcxs3bvw/7Zog+AB42mNgZGBg4ANiCQYFIMnEwMjAyMgFJFnAPAYGRggGAApeAGAAAAABAAAAAMbULpkAAAAAzAgvTwAAAADMCE7feNqdVMENhDAMMzt0AX4ZggcjZIgbIkMwxI1KETkpstIe4WEVJbbrNBLLjnXZgQ7tkI5G+HhPnSdeu3oWeJmWYa6VgPYQWuDKpKZ0Iswz89HE420OCz0UEH1AO0HyDbo31iJfyDfLLMWsm+OnMwIeeEqSvfpOjea1iU4HdfujV9JqMe/I+5vsrE1mfrOnePdB/4JrfwrcnBMROVxI)\n       format('woff'),\n\n       /* mzxfont.ttf */\n       url(data:application/font-sfnt;charset=utf-8;base64,AAEAAAAOAIAAAwBgRkZUTWGZ7WAAAIDsAAAAHEdERUYBOgAkAACAxAAAAChPUy8yVyxSugAAAWgAAABgY21hcMHJx5AAAAPoAAABimN2dCAAIQJ5AAAFdAAAAARnYXNw//8AAwAAgLwAAAAIZ2x5Zs6SkMcAAAeQAABtXGhlYWT4g9/cAAAA7AAAADZoaGVhBDYBRAAAASQAAAAkaG10eA8ADX0AAAHIAAACHmxvY2Hu0AjMAAAFeAAAAhhtYXhwAWkB8QAAAUgAAAAgbmFtZbk3zYgAAHTsAAACW3Bvc3Ql5wQsAAB3SAAACXQAAQAAAAEAAAISU+lfDzz1AB8CMAAAAADMCE8LAAAAAMwITwsAAP//AUACmgAAAAgAAgAAAAAAAAABAAACmv//AFoBQAAAAAABQAABAAAAAAAAAAAAAAAAAAAABAABAAABCwHAABwAAAAAAAIAAAABAAEAAABAAC4AAAAAAAQBQAH0AAUAAAKKArsAAACMAooCuwAAAd8AMQECAAACAAYJAAAAAAAAAAAAARAAAEAAAAAAAAAAAFBmRWQAwAAg4P8CMAAAAFoCmgABAAAAAQAAAAABZwHfAAAAIAABAUAAIQAAAAABQAAAAUAAAABQACgAFAAUABQAFABkAFAAUAAAACgAZAAUAHgAFAAUACgAFAAUABQAFAAUABQAFAAUAHgAZAAoACgAKAAUABQAFAAUABQAFAAUABQAFAAUAFAAFAAUABQAFAAUABQAFAAUABQAFAAoABQAFAAUABQAKAAUAFAAFABQABQAAABkABQAFAAUABQAFAAoABQAFABQACgAFABQABQAFAAUABQAFAAUABQAFAAUACgAFAAUABQAFAAoAHgAKAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAAAFAAAAAoAAAAAAAoAAAAAAAAABQAFAAoAAAAAAAUAAAAAAAoACgAFAAUAAAAFAAUABQAFAAoACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAPAAoAAAAeAB4AHgAeAAAACgAAAAAAAAAAAAAAAAAAAAoABQAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUAAAAAAB4AAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAHgAAAAAAHgAAAAAAAAAAABQAFAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAAAAKAAAAAAABQAAAAUAAAAAAAAAAAAAAAUACgAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAjAAAAFAAKAA8AFAAAAAAAAMAAAADAAAAHAABAAAAAACEAAMAAQAAABwABABoAAAAFgAQAAMABgB+IhoiICIrIjUiUiJhIqXgH+D///8AAAAgIhoiICIpIjUiUiJhIqXgAeB/////495I3kPeO94y3hbeCN3FIGogCwABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGYAAAAAAAAAAGIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAnkAAAAqACoAKgAqAEwAbACqAQABUgG2Ac4CBAI6AnIChgKeAqwCuAMEA0wDbAPAA/oEPgRoBKAEzgUOBUQFVgVyBdIF5AZEBngGpAbmBx4HbgeuB/IILgh+CJQIuAjgCRIJPgluCZwJ8AogClYKiArWCwALHgtaC3wLzAv6DFIMZAywDMINBA0QDSgNXA2MDboN+g4qDmwOqg7aDvwPJA9oD4YPqA/MD/QQLBBkEJoQ3BEMETARVhF+EdoSBhJIEnASghKqEtYS1hLWEtYS1hLWEtYS1hLWEtYTHhNgE6YUFBR2FMwU7hUYFVYVjhX+Fk4WghaoFugXGBdIF4wXsBfOGGQYhhjMGPQZHBlGGXAZthn8GhwaPBpiGqga7hssG2obahtqG2obahuOG7Ab1Bv4HDIcfhzWHSgd4h3uHfoeBh4SHrIe3B8kH3QftiAWIGQgsiEAIU4hqCHaIhQiMCJQIooi/iNqI9wkYiTeJW4l+iZeJqInDCd2KLAq7CweLCwsPiw+LD4sPiw+LFosbiyGLJ4sniyeLK4svizQLOIs9C0ALRYtFi0WLTAtSi1oLYgtrC2+Lewt7C3sLewt7C3sLewt7C3sLewt7C38LgwuGi4oLjQuQi5QLpIu6C8WL2wvmi/IL/QwIDDGMTQxfDHyMq4y3DMIMzQzYjOiM9Y0FjR0NLI1MjV2Nbg1xDXWNhY2QjZ2NoI2rgACACEAAAEqApoAAwAHAC6xAQAvPLIHBADtMrEGBdw8sgMCAO0yALEDAC88sgUEAO0ysgcGAfw8sgECAO0yMxEhESczESMhAQnox8cCmv1mIQJYAAACAFAAdwDwAd8AAwAVAAA3NTMVAzUzFRQWOwEVIxUjNSM1MzI2eFBQUAUPFChQKBQPBXdQUAFUFBQPBXhQUHgFAAAAAAIAKAFnARgCBwAJABMAABM1MxUjIgYUBiMnNTMVIyImNCYjyFAUDwUFD7RQFA8FBQ8BZ6B4BR4FKHigBR4FAAACABQAdwEsAd8AKwAvAAATNTMVMzUzFTMyFhQGKwEVMzIWFAYrARUjNSMVIzUjIiY0NjsBNSMiJjQ2Mxc1IxU8UChQFA8FBQ8UFA8FBQ8UUChQFA8FBQ8UFA8FBQ+MKAGPUFBQUAUeBXgFHgVQUFBQBR4FeAUeBaB4eAAAAQAUACcBLAIvAEUAABM1MxUzMhYUFjsBFSMiJjQmIiY9ASMVMxUUFjsBFSMiBhQGKwEVIzUjNTQmKwE1MzIWFBYyFh0BMzUjNTQmKwE1MzI2PQGMUBQPBQUPFBQPBQUeBXigBQ8UFA8FBQ8UUFAFDxQUDwUFHgV4oAUPFBQPBQHfUFAFHgVQBR4FBQ8UeBQPBXgFHgVQUBQPBVAFHgUFDxR4FA8FeAUPFAAAAAMAFAB3ASwBjwADADcAOwAANzUzFSY0NjsBFSMiBhQGIgYUBiIGFAYiBhQGIgYdASM1NDYyNjQ2MjY0NjI2NDYyNjQ2MjY0NjIHNTMV3FAoBQ8UFA8FBR4FBR4FBR4FBR4FUAUeBQUeBQUeBQUeBQUeBQUe61B3UFD1HgVQBR4FBR4FBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FKFBQAAAABAAUAHcBLAHfADcAOwBDAE0AABM1MxUUFjsBFSMiBh0BMxUUBisBFTMyFh0BIzU0JiIGHQEjNTQmKwE1MzI2NDYyNjQmKwE1MzI2FzUjFRY0JiIGFBYyBjQmKwEVMzUjImR4BQ8UFA8FUAUPFBQPBVAFHgV4BQ8UFA8FBR4FBQ8UFA8FUChQBR4FBR5LBQ8UUBQPAcsUFA8FUAUPFBQPBXgFDxQUDwUFDxQUDwV4BR4FBR4FUAVVUFBLHgUFHgUjHgV4UAAAAAABAGQBZwDcAgcADQAAEzUzFSMiBh0BIzU0NjOMUBQPBVAFDwGPeHgFDxQUDwUAAAABAFAAdwDwAd8AJwAAEzUzFRQGIgYUBisBFTMyFhQWMhYdASM1NCYiJjQmKwE1MzI2NDYyNqBQBR4FBQ8UFA8FBR4FUAUeBQUPFBQPBQUeBQHLFBQPBQUeBcgFHgUFDxQUDwUFHgXIBR4FBQABAFAAdwDwAd8AJwAAEzUzFRQWMhYUFjsBFSMiBhQGIgYdASM1NDYyNjQ2OwE1IyImNCYiJlBQBR4FBQ8UFA8FBR4FUAUeBQUPFBQPBQUeBQHLFBQPBQUeBcgFHgUFDxQUDwUFHgXIBR4FBQABAAAAxwFAAY8AKwAAEzUzFTM1MxUUBiIGHQEzFSMVFBYyFh0BIzUjFSM1NDYyNj0BIzUzNTQmIiYoUFBQBR4FUFAFHgVQUFAFHgVQUAUeBQF7FCgoFA8FBQ8UKBQPBQUPFCgoFA8FBQ8UKBQPBQUAAQAoAMcBGAGPAAsAABM1MxUzFSMVIzUjNXhQUFBQUAE/UFAoUFAoAAEAZABPANwA7wANAAA3NTMVIyIGHQEjNTQ2M4xQFA8FUAUPd3h4BQ8UFA8FAAAAAAEAFAEXASwBPwADAAATNSEVFAEYARcoKAAAAAABAHgAdwDIAMcAAwAANzUzFXhQd1BQAAABABQAnwEsAd8ANwAAADQ2OwEVIyIGFAYiBhQGIgYUBiIGFAYiBhQGIgYUBisBNTMyNjQ2MjY0NjI2NDYyNjQ2MjY0NjIBBAUPFBQPBQUeBQUeBQUeBQUeBQUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeAbweBVAFHgUFHgUFHgUFHgUFHgUFHgVQBR4FBR4FBR4FBR4FBR4FAAMAFAB3ASwB3wAXACYANAAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzUjFTMyNjQ2MjY0NjI2FTUjIgYUBiIGFAYrARU8yAUPFBQPBcgFDxQUDwWgeBQPBQUeBQUeBRQPBQUeBQUPFAHLFBQPBf7oBQ8UFA8FARgFGRR4BR4FBR4FBfWgBR4FBR4FUAAAAAEAKAB3ARgB3wATAAATNTMRMxUjNTM1IzU0NjI2NDYyNnhQUPBQUAUeBQUeBQHLFP7AKCjIFA8FBR4FBQAAAAABABQAdwEsAd8APwAAEzUzFRQWOwEVIyIGFAYiBhQGIgYUBiIGFAYiBh0BMzUzFSE1MzI2NDYyNjQ2MjY0NjI2NDY7ATUjFSM1NDYyNjzIBQ8UFA8FBR4FBR4FBR4FBR4FeFD+6BQPBQUeBQUeBQUeBQUPFHhQBR4FAcsUFA8FUAUeBQUeBQUeBQUeBQUPFChQUAUeBQUeBQUeBQUeBVAoFA8FBQAAAAABABQAdwEsAd8ALQAAEzUzFRQWOwEVIyIGFBY7ARUjIgYdASM1NCYiJj0BMxUzNSM1MzUjFSM1NDYyNjzIBQ8UFA8FBQ8UFA8FyAUeBVB4eHh4UAUeBQHLFBQPBXgFHgV4BQ8UFA8FBQ8UKHgoeCgUDwUFAAIAFAB3ASwB3wAoADMAABM1MxUzMhYUBisBFTMyFh0BIzU0NjsBNSM1MzI2NDYyNjQ2MjY0NjI2FTUjIgYUBiIGHQG0UBQPBQUPFBQPBaAFDxSgFA8FBR4FBR4FBR4FFA8FBR4FAcsUyAUeBVAFDxQUDwVQUAUeBQUeBQUeBQWlUAUeBQUPFAABABQAdwEsAd8AHQAAEzUhFSMVMxUUFjsBFSMiBh0BIzU0JiImPQEzFTM1FAEYyKAFDxQUDwXIBR4FUHgBF8goeBQPBXgFDxQUDwUFDxQoeAAAAAACABQAdwEsAd8AJAAoAAATNTMVIxUUBisBFTMVFBY7ARUjIgYdASM1NCYrATUzMjY0NjI2EzUjFWR4UAUPFKAFDxQUDwXIBQ8UFA8FBR4FeHgByxQoFA8FUBQPBXgFDxQUDwXwBR4FBf7jeHgAAAAAAQAUAHcBLAHfAB8AABM1IRUjIgYUBiIGFAYrARUjNTMyNjQ2MjY0NjsBNSMVFAEYFA8FBR4FBQ8UUBQPBQUeBQUPFHgBj1B4BR4FBR4FoKAFHgUFHgVQKAAAAAADABQAdwEsAd8AJwArAC8AABM1MxUUFjsBFSMiBhQWOwEVIyIGHQEjNTQmKwE1MzI2NCYrATUzMjYXNSMVFzUjFTzIBQ8UFA8FBQ8UFA8FyAUPFBQPBQUPFBQPBaB4eHgByxQUDwV4BR4FeAUPFBQPBXgFHgV4BX14eKB4eAAAAAIAFAB3ASwB3wAkACgAABM1MxUUFjsBFSMiBhQGIgYdASM1MzU0NjsBNSM1NCYrATUzMjYXNSMVPMgFDxQUDwUFHgWgeAUPFKAFDxQUDwWgeAHLFBQPBfAFHgUFDxQoFA8FUBQPBXgFfXh4AAIAeACfAMgBtwADAAcAADc1MxUnNTMVeFBQUJ9QUMhQUAAAAAIAZAB3ANwBtwANABEAADc1MxUjIgYdASM1NDYzNzUzFYxQFA8FUAUPFFCfUFAFDxQUDwXIUFAAAQAoAHcBGAHfAEcAABM1MxUUBiIGFAYiBhQGIgYUBiIGFBYyFhQWMhYUFjIWFBYyFh0BIzU0JiImNCYiJjQmIiY0JiImNDYyNjQ2MjY0NjI2NDYyNshQBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FUAUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQHLFBQPBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUAAgAoAMcBGAFnAAMABwAANzUzFSc1MxUo8PDwxygoeCgoAAAAAQAoAHcBGAHfAEcAABM1MxUUFjIWFBYyFhQWMhYUFjIWFAYiBhQGIgYUBiIGFAYiBh0BIzU0NjI2NDYyNjQ2MjY0NjI2NCYiJjQmIiY0JiImNCYiJihQBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FUAUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQHLFBQPBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUAAgAUAHcBLAHfAAMAJQAANzUzFQM1MxUUFjsBFSMiBhQGKwEVIzUzMjY0NjsBNSMVIzUzMjaMUKDIBQ8UFA8FBQ8UUBQPBQUPFHhQFA8Fd1BQAVQUFA8FUAUeBVBQBR4FUFBQBQAAAAABABQAdwEsAd8AHwAAEzUzFRQWOwEVIyIGHQEjNTM1IxEzFSM1NCYrAREzMjY8yAUPFBQPBXhQeKDIBQ8UFA8FAcsUFA8FyAUPFKBQ/ugoFA8FARgFAAAAAgAUAHcBLAHfACEALwAAEjQ2MhYUFjIWFBYyFhQWOwEVIzUjFSM1MzI2NDYyNjQ2MhY0JiIGFAYrARUzNSMijAUeBQUeBQUeBQUPFFB4UBQPBQUeBQUeLQUeBQUPFHgUDwG8HgUFHgUFHgUFHgXweHjwBR4FBR4FSx4FBR4FUFAAAAAAAwAUAHcBLAHfAB8AIwAnAAATNTMVFBY7ARUjIgYUFjsBFSMiBh0BIzU0NjsBESMiJhc1IxUXNSMVFPAFDxQUDwUFDxQUDwXwBQ8UFA8FyFBQUAHLFBQPBXgFHgV4BQ8UFA8FARgFfXh4oHh4AAAAAAEAFAB3ASwB3wA/AAATNTMVFBY7ARUjIiY0JiImPQEjFRQGKwEVMzIWHQEzNTQ2MjY0NjsBFSMiBh0BIzU0JiImNCYrATUzMjY0NjI2ZKAFDxQUDwUFHgVQBQ8UFA8FUAUeBQUPFBQPBaAFHgUFDxQUDwUFHgUByxQUDwVQBR4FBQ8UFA8FyAUPFBQPBQUeBVAFDxQUDwUFHgXIBR4FBQACABQAdwEsAd8AHwAvAAATNTMVFBYyFhQWOwEVIyIGFAYiBh0BIzU0NjsBESMiJhY0JisBETMyNjQ2OwE1IyIUyAUeBQUPFBQPBQUeBcgFDxQUDwWgBQ8UFA8FBQ8UFA8ByxQUDwUFHgXIBR4FBQ8UFA8FARgFKB4F/ugFHgXIAAEAFAB3ASwB3wAzAAATNSEVIyImNCYiJj0BIxUzMjY0NjsBFSMiJjQmKwEVMzU0NjI2NDY7ARUhNTQ2OwERIyImFAEYFA8FBR4FUBQPBQUPFBQPBQUPFFAFHgUFDxT+6AUPFBQPBQHLFHgFHgUFDxR4BR4FeAUeBXgUDwUFHgV4FA8FARgFAAABABQAdwEsAd8ALQAAEzUhFSMiJjQmIiY9ASMVMzI2NDY7ARUjIiY0JisBFTMyFh0BIzU0NjsBESMiJhQBGBQPBQUeBVAUDwUFDxQUDwUFDxQUDwWgBQ8UFA8FAcsUeAUeBQUPFHgFHgV4BR4FeAUPFBQPBQEYBQABABQAdwEsAd8APgAAEzUzFRQWOwEVIyImNCYiJj0BIxUUBisBFTMyFh0BMzUjNTMVIyImNCYiBh0BIzU0JiImNCYrATUzMjY0NjI2ZKAFDxQUDwUFHgVQBQ8UFA8FUFCgFA8FBR4FeAUeBQUPFBQPBQUeBQHLFBQPBVAFHgUFDxQUDwXIBQ8UUCigBR4FBQ8UFA8FBR4FyAUeBQUAAAAAAQAUAHcBLAHfAAsAADcRMxUzNTMRIzUjFRRQeFBQeHcBaKCg/pigoAAAAAABAFAAdwDwAd8AFwAAEzUzFRQGKwERMzIWHQEjNTQ2OwERIyImUKAFDxQUDwWgBQ8UFA8FAcsUFA8F/ugFDxQUDwUBGAUAAAABABQAdwEsAd8AGwAAEzUzFRQGKwERIyIGHQEjNTQmKwE1MxUzESMiJoygBQ8UFA8FoAUPFFBQFA8FAcsUFA8F/ugFDxQUDwVQUAEYBQAAAAEAFAB3ASwB3wAlAAATNTMVMzUzNTMVIxUjIgYUFjsBFTMVIzUjNSMVIzU0NjsBESMiJhR4KChQKBQPBQUPFChQKCh4BQ8UFA8FAcsUoFBQUFAFHgVQUFBQoBQPBQEYBQAAAAABABQAdwEsAd8AHQAAEzUzFRQGKwERMzU0NjI2NDY7ARUhNTQ2OwERIyImFKAFDxRQBR4FBQ8U/ugFDxQUDwUByxQUDwX+6BQPBQUeBXgUDwUBGAUAAAAAAQAUAHcBLAHfACEAADcRMxUUFjIWFBYyNjQ2MjY9ATMRIzUjIgYUBiImNCYrARUUUAUeBQUeBQUeBVBQFA8FBR4FBQ8UdwFoFA8FBR4FBR4FBQ8U/pjIBR4FBR4FyAAAAQAUAHcBLAHfACAAADcRMxUUFjIWFBYyFhQWOwE1MxEjNSMiJjQmIiY0JisBFRRQBR4FBR4FBQ8UUFAUDwUFHgUFDxR3AWgUDwUFHgUFHgV4/ph4BR4FBR4FyAACABQAdwEsAd8AJwA/AAATNTMVFBYyFhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2FjQmIgYUBisBFTMyFhQWMjY0NjsBNSMiZHgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FUAUeBQUPFBQPBQUeBQUPFBQPAcsUFA8FBR4FyAUeBQUPFBQPBQUeBcgFHgUFKB4FBR4FyAUeBQUeBcgAAAACABQAdwEsAd8AHgAiAAATNTMVFBY7ARUjIgYdASMVMzIWHQEjNTQ2OwERIyImFzUjFRTwBQ8UFA8FeBQPBaAFDxQUDwXIUAHLFBQPBXgFDxR4BQ8UFA8FARgFfXh4AAACABQATwEsAd8AGwAnAAATNTMVFBY7ARUjFTMyFh0BIzUjNTQmKwE1MzI2FzUjFTM1MzIWFBYzPMgFDxQoFA8FeHgFDxQUDwWgeCgUDwUFDwHLFBQPBfBQBQ8UUBQPBfAFzcjwUAUeBQAAAAACABQAdwEsAd8AIAAkAAATNTMVFBY7ARUjFTMVIzUjIiY0JisBFSM1NDY7AREjIiYXNSMVFPAFDxQoKFAUDwUFDxR4BQ8UFA8FyFAByxQUDwV4UHh4BR4FoBQPBQEYBX14eAAAAQAUAHcBLAHfAD8AABM1MxUUFjsBFSM1IxUzMhYdATMVFBYyFhQWOwEVIyIGHQEjNTQmKwE1MxUzNSMiJj0BIzU0JiImNCYrATUzMjY8yAUPFFB4FA8FUAUeBQUPFBQPBcgFDxRQeBQPBVAFHgUFDxQUDwUByxQUDwVQUFAFDxQUDwUFHgVQBQ8UFA8FUFBQBQ8UFA8FBR4FUAUAAQAoAHcBGAHfAB0AABM1MxUjIiY0JisBFTMyFh0BIzU0NjsBNSMiBhQGIyjwFA8FBQ8UFA8FoAUPFBQPBQUPAWd4eAUeBfAFDxQUDwXwBR4FAAAAAQAUAHcBLAHfABEAADcRMxEzETMRIyIGHQEjNTQmIxRQeFAUDwXIBQ+fAUD+wAFA/sAFDxQUDwUAAAAAAQAUAHcBLAHfACsAADc1MxUzMhYUFjI2NDY7ATUzFSMiBhQGIgYUBiIGFAYiJjQmIiY0JiImNCYjFFAUDwUFHgUFDxRQFA8FBR4FBR4FBR4FBR4FBR4FBQ/v8PAFHgUFHgXw8AUeBQUeBQUeBQUeBQUeBQUeBQAAAQAUAHcBLAHfABcAADcRMxUzNTMVMzUzESMVIzU0JiIGHQEjNRRQKCgoUChQBR4FUMcBGPBQUPD+6FAUDwUFDxRQAAABABQAdwEsAd8APwAAEzUzFTMyFhQWMjY0NjsBNTMVIyIGFAYrARUzMhYUFjsBFSM1IyImNCYiBhQGKwEVIzUzMjY0NjsBNSMiJjQmIxRQFA8FBR4FBQ8UUBQPBQUPFBQPBQUPFFAUDwUFHgUFDxRQFA8FBQ8UFA8FBQ8Bj1BQBR4FBR4FUFAFHgV4BR4FUFAFHgUFHgVQUAUeBXgFHgUAAQAoAHcBGAHfACEAABM1MxUzNTMVIyIGFAYrARUzMhYdASM1NDY7ATUjIiY0JiMoUFBQFA8FBQ8UFA8FoAUPFBQPBQUPAT+goKCgBR4FeAUPFBQPBXgFHgUAAAABABQAdwEsAd8AQgAAEzUhFSMiBhQGIgYUBiIGFAYiBhQGKwEVMzU0NjI2NDY7ARUhNTMyNjQ2MjY0NjI2NDYyNjQ2MjY9ASMVFAYiBhQGIxQBGBQPBQUeBQUeBQUeBQUPFHgFHgUFDxT+6BQPBQUeBQUeBQUeBQUeBXgFHgUFDwFneFAFHgUFHgUFHgUFHgVQFA8FBR4FeHgFHgUFHgUFHgUFHgUFDxQUDwUFHgUAAAEAUAB3APAB3wAHAAA3ETMVIxEzFVCgUFB3AWgo/ugoAAAAAAEAFAB3ASwB3wA3AAATNTMyFhQWMhYUFjIWFBYyFhQWMhYUFjIWFBY7ARUjIiY0JiImNCYiJjQmIiY0JiImNCYiJjQmIxQUDwUFHgUFHgUFHgUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgUFDwFneAUeBQUeBQUeBQUeBQUeBQUeBXgFHgUFHgUFHgUFHgUFHgUFHgUAAQBQAHcA8AHfAAcAABM1MxEjNTMRUKCgUAG3KP6YKAEYAAAAAQAUAY8BLAIvAC8AABI0NjIWFBYyFhQWMhYUFjIWHQEjNTQmIiY0JiIGFAYiBh0BIzU0NjI2NDYyNjQ2MowFHgUFHgUFHgUFHgVQBR4FBR4FBR4FUAUeBQUeBQUeAgweBQUeBQUeBQUeBQUPFBQPBQUeBQUeBQUPFBQPBQUeBQUeBQAAAQAAAHcBQACfAAMAAD0BIRUBQHcoKAAAAQBkAbcA3AIvAA0AABM1MxUzMhYdASM1NCYjZFAUDwVQBQ8B31BQBQ8UFA8FAAAAAgAUAHcBLAFnACMAJwAAEzUzFRQWOwEVMzIWHQEjNTQmIgYdASM1NCYrATUzMjY9ATM1FTUjFTygBQ8UFA8FUAUeBXgFDxQUDwV4UAE/KBQPBaAFDxQUDwUFDxQUDwVQBQ8UKKBQUAACABQAdwEsAd8AGAAiAAATNTMVMxUUFjIWFBY7ARUjIgYdASMRIyImFjQmKwEVMzUjIhR4UAUeBQUPFBQPBcgUDwWgBQ8UUBQPAcsUeBQPBQUeBXgFDxQBQAWgHgWgeAABABQAdwEsAWcAIQAAEzUzFRQWMhYdASM1IxUzNTMVFAYiBh0BIzU0JisBNTMyNjzIBR4FUHh4UAUeBcgFDxQUDwUBUxQUDwUFDxQooCgUDwUFDxQUDwWgBQAAAAIAFAB3ASwB3wAlAC8AABM1MxEzMhYdASM1NCYiBh0BIzU0JisBNTMyNjQ2MjY9ATM1IyImEzUjIgYUBisBFYx4FA8FUAUeBXgFDxQUDwUFHgVQFA8FKBQPBQUPFAHLFP7ABQ8UFA8FBQ8UFA8FeAUeBQUPFFAF/uOgBR4FeAAAAgAUAHcBLAFnAB4AIgAAEzUzFRQWOwEVIxUzNTMVFAYiBh0BIzU0JisBNTMyNhc1IxU8yAUPFMh4UAUeBcgFDxQUDwWgeAFTFBQPBVBQKBQPBQUPFBQPBaAFLSgoAAAAAQAoAHcBGAHfADMAABM1MxUUFjsBFSMiJjQmIiY0JisBFTMyFhQGKwEVMzIWHQEjNTQ2OwE1IyImNDY7ATUzMjZ4eAUPFBQPBQUeBQUPFBQPBQUPFBQPBaAFDxQUDwUFDxQUDwUByxQUDwVQBR4FBR4FeAUeBXgFDxQUDwV4BR4FeAUAAgAUACcBLAFnACsALwAAEzUzFRQWMjY9ATMVFAYrARUjIgYdASM1NCYiJj0BMxUzNSM1NCYrATUzMjYXNSMVPHgFHgVQBQ8UFA8FoAUeBVBQeAUPFBQPBXhQAVMUFA8FBQ8UFA8F8AUPFBQPBQUPFChQFA8FeAV9eHgAAAEAFAB3ASwB3wAjAAATNTMVMzI2PQEzFRQWOwEVIzUjIgYUBisBFSM1NDY7AREjIiYUeBQPBVAFDxRQFA8FBQ8UeAUPFBQPBQHLFKAFDxQUDwXIyAUeBaAUDwUBGAUAAAIAUAB3APAB3wASABYAABM1MxUzMhYdASM1NDY7ATUjIiY3NTMVUHgUDwWgBQ8UFA8FKFABUxTIBQ8UFA8FoAVLUFAAAgAoACcBGAHfABYAGgAAEzUzESMiBh0BIzU0JisBNTMVMzUjIiY3NTMVoHgUDwWgBQ8UUFAUDwUoUAFTFP7oBQ8UFA8FUFDwBUtQUAAAAAABABQAdwEsAd8AMwAAEzUzFTMyNjQ2MjY9ATMVFAYiBhQGIgYUFjIWFBY7ARUjNSMiJjQmKwEVIzU0NjsBESMiJhR4FA8FBR4FUAUeBQUeBQUeBQUPFFAUDwUFDxR4BQ8UFA8FAcsUyAUeBQUPFBQPBQUeBQUeBQUeBVBQBR4FeBQPBQEYBQAAAQBQAHcA8AHfABIAABM1MxEzMhYdASM1NDY7AREjIiZQeBQPBaAFDxQUDwUByxT+wAUPFBQPBQEYBQAAAQAUAHcBLAFnABgAADc1MxUUFjI2PQEzFRQWOwEVIzUjFSM1IxUUeAUeBVAFDxRQKCgod/AUDwUFDxQUDwXIoHh4oAABABQAdwEsAWcAGQAAEzUzFRQWMjY9ATMVFBY7ARUjNSMVIzUjIiYUUAUeBXgFDxRQUFAUDwUBUxQUDwUFDxQUDwXIyMjIBQACABQAdwEsAWcAFwAbAAATNTMVFBY7ARUjIgYdASM1NCYrATUzMjYXNSMVPMgFDxQUDwXIBQ8UFA8FoHgBUxQUDwWgBQ8UFA8FoAWloKAAAAIAFAAnASwBZwAmACoAABM1MxUUFjI2PQEzFRQWOwEVIyIGHQEjFTMyFh0BIzU0NjsBNSMiJhc1IxUUUAUeBXgFDxQUDwV4FA8FoAUPFBQPBchQAVMUFA8FBQ8UFA8FeAUPFFAFDxQUDwXwBX14eAACABQAJwEsAWcAJgAqAAATNTMVFBYyNj0BMxUUBisBFTMyFh0BIzU0NjsBNSM1NCYrATUzMjYXNSMVPHgFHgVQBQ8UFA8FoAUPFHgFDxQUDwV4UAFTFBQPBQUPFBQPBfAFDxQUDwVQFA8FeAV9eHgAAQAUAHcBLAFnACkAABM1MxUUFjI2PQEzFRQWOwEVIzUjIgYUBisBFTMyFh0BIzU0NjsBNSMiJhRQBR4FeAUPFFAUDwUFDxQUDwWgBQ8UFA8FAVMUFA8FBQ8UFA8FUFAFHgV4BQ8UFA8FoAUAAQAUAHcBLAFnADMAABM1MxUUFjIWHQEjNSMVMxUzFRQWMhYUBiIGHQEjNTQmIiY9ATMVMzUjNSM1NCYiJjQ2MjY8yAUeBVB4UFAFHgUFHgXIBR4FUHhQUAUeBQUeBQFTFBQPBQUPFCgoKBQPBQUeBQUPFBQPBQUPFCgoKBQPBQUeBQUAAQAUAHcBLAHfACMAABI0NjsBFTMVIxUzMjY9ATMVFAYiBh0BIzU0JisBNSM1MzUzMowFDxRQUBQPBVAFHgV4BQ8UUFAUDwG8HgV4KKAFDxQUDwUFDxQUDwWgKFAAAAAAAQAUAHcBLAFnABkAADc1MxUzNTMVMzIWHQEjNTQmIgYdASM1NCYjFFBQUBQPBVAFHgV4BQ+fyMjIyAUPFBQPBQUPFBQPBQAAAQAoAHcBGAFnABkAADc1MxUzNTMVIyIGFAYiBh0BIzU0JiImNCYjKFBQUBQPBQUeBVAFHgUFD8egoKCgBR4FBQ8UFA8FBR4FAAAAAAEAFAB3ASwBZwAdAAA3NTMVMzUzFTM1MxUjIgYdASM1NCYiBh0BIzU0JiMUUCgoKFAUDwVQBR4FUAUPn8igUFCgyAUPFBQPBQUPFBQPBQAAAQAUAHcBLAFnAEcAABM1MxUUFjIWFBYyNjQ2MjY9ATMVFAYiBhQGKwEVMzIWFBYyFh0BIzU0JiImNCYiBhQGIgYdASM1NDYyNjQ2OwE1IyImNCYiJhRQBR4FBR4FBR4FUAUeBQUPFBQPBQUeBVAFHgUFHgUFHgVQBR4FBQ8UFA8FBR4FAVMUFA8FBR4FBR4FBQ8UFA8FBR4FUAUeBQUPFBQPBQUeBQUeBQUPFBQPBQUeBVAFHgUFAAEAFAAnASwBZwAfAAA3NTMVMzUzFSMiBhQGIgYdASM1MzU0NjI2PQEjNTQmIxRQeFAUDwUFHgXIoAUeBaAFD8egoKDwBR4FBQ8UKBQPBQUPFBQPBQAAAAABABQAdwEsAWcALwAAEzUhFRQGIgYUBiIGFAYiBhQGIgYdATM1MxUhNTQ2MjY0NjI2NDYyNjQ2MjY9ASMVFAEYBR4FBR4FBR4FBR4FUFD+6AUeBQUeBQUeBQUeBVABF1AUDwUFHgUFHgUFHgUFDxQoUBQPBQUeBQUeBQUeBQUPFCgAAAABACgAdwEYAd8AHQAAEzUzFSMVIyIGFBY7ARUzFSM1NCYrATUjNTM1MzI2oHhQFA8FBQ8UUHgFDxRQUBQPBQHLFCh4BR4FeCgUDwV4KHgFAAIAeAB3AMgB3wADAAcAADc1MxUnNTMVeFBQUHegoMigoAAAAAEAKAB3ARgB3wAdAAATNTMVFBY7ARUzFSMVIyIGHQEjNTM1MzI2NCYrATUoeAUPFFBQFA8FeFAUDwUFDxQBtygUDwV4KHgFDxQoeAUeBXgAAQAUAY8BLAHfAB8AABM1MxUUFjI2PQEzFRQGIgYdASM1NCYiBh0BIzU0NjI2PHgFHgVQBR4FeAUeBVAFHgUByxQUDwUFDxQUDwUFDxQUDwUFDxQUDwUFAAUAAABPAUAB3wAPABMAFwAvADMAADc1MxUUBiIGHQEjNTQmIiY3NTMVIzUzFSc1MxUUFjsBESMiBh0BIzU0JisBETMyNhMRIxFQoAUeBVAFHgV4KKAoUPAFDxQUDwXwBQ8UFA8F8PDbFBQPBQUPFBQPBQVzUFBQUIwUFA8F/sAFDxQUDwUBQAX+uwFA/sAAAAQAAABPAUAB3wAXABsAHwAvAAATNTMVFBY7AREjIgYdASM1NCYrAREzMjYXNSMVMzUjFRc1IxUUFjIWHQEzNTQ2MjYo8AUPFBQPBfAFDxQUDwVQKKAoKKAFHgVQBR4FAcsUFA8F/sAFDxQUDwUBQAV9UFBQUGQUFA8FBQ8UFA8FBQAAAAIAFAB3ASwBtwAvADMAABM1MxUUFjI2PQEzFRQWOwEVIyIGFAYiBhQGIgYUBiImNCYiJjQmIiY0JisBNTMyNhc1IxU8UAUeBVAFDxQUDwUFHgUFHgUFHgUFHgUFHgUFDxQUDwVQKAGjFBQPBQUPFBQPBaAFHgUFHgUFHgUFHgUFHgUFHgWgBVVQUAAAAwAUAJ8BLAG3ADcARwBPAAASNDYyFhQWMhYUFjIWFBYyFhQGIgYUBiIGFAYiBhQGIiY0JiImNCYiJjQmIiY0NjI2NDYyNjQ2MhY0JiIGFAYiBhQWMjY0NjIWNCYiBhQWMowFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHi0FHgUFHgUFHgUFHi0FHgUFHgGUHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUjHgUFHgUFHgUFHgVzHgUFHgUAAAMAFABPASwCBwA7AEMASwAAEzUzFRQWOwEVMxUjFRQWOwEVMzIWHQEjNSMVIzUzMjY0NjI2NCYiJjQmIiY0NjI2NDYyNjQmKwE1MzI2FjQmIgYUFjIWNCYiBhQWMmRQBQ8UUFAFDxQUDwV4UFAUDwUFHgUFHgUFHgUFHgUFHgUFDxQUDwUoBR4FBR4tBR4FBR4B8xQUDwV4eBQPBVAFDxRQUFAFHgUFHgUFHgUFHgUFHgUFHgVQBSgeBQUeBZseBQUeBQACAAAAdwFAAgcAOQBBAAATNTMyFhQWMhYUFjIWFBYyFhQWOwEVIyIGHQEjFTMyFh0BIzU0NjsBNSM1NCYrATUzMjY0NjI2NDYzFjQmIgYUFjJ4FA8FBR4FBR4FBR4FBQ8UFA8FUBQPBaAFDxRQBQ8UFA8FBR4FBQ88BR4FBR4Bt1AFHgUFHgUFHgUFHgVQBQ8UUAUPFBQPBVAUDwVQBR4FBR4FIx4FBR4FAAAAAQBQAMcA8AFnABcAABM1MxUUFjsBFSMiBh0BIzU0JisBNTMyNnhQBQ8UFA8FUAUPFBQPBQFTFBQPBVAFDxQUDwVQBQACAAD//wFAAi8AAwAbAAAVESERAzUjFRQGKwEVMzIWHQEzNTQ2OwE1IyImAUB4UAUPFBQPBVAFDxQUDwUBAjD90AFUFBQPBVAFDxQUDwVQBQAAAAACACgAnwEYAY8AFwAvAAATNTMVFBY7ARUjIgYdASM1NCYrATUzMjYXNSMVFAYrARUzMhYdATM1NDY7ATUjIiZQoAUPFBQPBaAFDxQUDwV4UAUPFBQPBVAFDxQUDwUBexQUDwWgBQ8UFA8FoAUZFBQPBVAFDxQUDwVQBQAAAgAA//8BQAIvAAMAKAAAFREhEQM1IxUUBisBFTMVMzIWFAYiBh0BMzUjIiY0NjI2NDY7ATUjIiYBQHhQBQ8UKBQPBQUeBVAUDwUFHgUFDxQUDwUBAjD90AGkFBQPBVB4BR4FBQ8UeAUeBQUeBVAFAAIAAABPAUACBwBGAFYAABM1MxUUFjsBFSMiBhQGIgYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNj0BMxUUFjI2NDY7ATUjFRQGIiY0NjI2BjQmIgYUBiIGFBYyNjQ2MqB4BQ8UFA8FBR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgFHgUFDxR4BR4FBR4FKAUeBQUeBQUeBQUeAfMUFA8FeAUeBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeBQPBQUeBQXIHgUFHgUFHgUFHgUAAAAAAgAoACcBGAIHADcAOwAAEzUzFRQWOwEVIyIGFAYiBh0BMxUUBiIGFAYiBhQWMhYUBiIGFAYiBhQGKwERIyImNCYrATUzMjYXNSMVUKAFDxQUDwUFHgVQBR4FBR4FBR4FBR4FBR4FBQ8UFA8FBQ8UFA8FeFAB8xQUDwV4BR4FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FARgFHgV4BX14eAAAAAIAAAB3AUAB3wAcACQAADc1MxUUBiIGFAYiBh0BIxUjIgYdASM1MzI2NDYzNjQmIgYUFjJQ8AUeBQUeBVAUDwV4FA8FBQ+MBR4FBR7v8BQPBQUeBQUPFMgFDxRQBR4FpR4FBR4FAAAAAgAAAE8BQAHfABUAGQAANxEhESMiBh0BIzUzNSMVIyIGHQEjNTc1IxUoARgUDwVQKHgUDwVQ8HjHARj+wAUPFHh48AUPFHjIKCgAAAIAAACfAUABtwAvADMAABM1MxUzMjY9ATMVIxUzFSMVMxUjNTQmKwEVIzUjIgYdASM1MzUjNTM1IzUzFRQWMxc1IxV4UBQPBVBQUFBQUAUPFFAUDwVQUFBQUFAFD2RQAWdQUAUPFCgoKCgoFA8FUFAFDxQoKCgoKBQPBVAoKAAAAQAUAHcBLAHfACEAADcRMzIWFBYyFhQWMhYdATMVMxUjFSMVFAYiBhQGIgYUBiMUFA8FBR4FBR4FUFBQUAUeBQUeBQUPdwFoBR4FBR4FBQ8UKCgoFA8FBR4FBR4FAAAAAQAUAHcBLAHfACEAAAA0NjsBESMiJjQmIiY0JiImPQEjNSM1MzUzNTQ2MjY0NjIBBAUPFBQPBQUeBQUeBVBQUFAFHgUFHgG8HgX+mAUeBQUeBQUPFCgoKBQPBQUeBQAAAQAoAHcBGAHfADMAABM1MxUUFjIWFBYyFh0BIxUzFRQGIgYUBiIGHQEjNTQmIiY0JiImPQEzNSM1NDYyNjQ2MjZ4UAUeBQUeBVBQBR4FBR4FUAUeBQUeBVBQBR4FBR4FAcsUFA8FBR4FBQ8UeBQPBQUeBQUPFBQPBQUeBQUPFHgUDwUFHgUFAAEAAP//AUACLwAYAAARNSEVFAYrARUzMhYdASMVIzUjIiY9ATM1AUAFDxQUDwXIUBQPBcgCBygUDwXwBQ8U8PAFDxTwAAAAAAQAAP//AUACBwADAAcACwAPAAAXNTMVITUzFRM1MxUhNTMVeMj+wCjwKP7AyAHw8PDwARjw8PDwAAAAAAMAFAAnASwCBwBbAGsAcwAAEzUzFRQWOwEVIyImPQEjFRQWMhYUFjsBFSMiBhQWMhYUFjIWFAYiBh0BIzU0JiImNDYyFhQWMjY0NjI2NCYiJjQmIiY0JisBNTMyNjQ2MjY0JiImNCYiJjQ2MjYWNCYiBhQWMhYUFjI2NCYiNjQmIgYUFjI8yAUPFBQPBVAFHgUFDxQUDwUFHgUFHgUFHgXIBR4FBR4FBR4FBR4FBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBR4FUAUeBQUeBQUeBQUecwUeBQUeAfMUFA8FeAUPFBQPBQUeBXgFHgUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgUFHgVQBR4FBR4FBR4FBR4FBSgeBQUeBQUeBQUeBQUeBQUeBQAAAAMAAADvAUABZwADAAsAEwAAPQEhFSY0JiIGFBYyNjQmIgYUFjIBQMgFHgUFHn0FHgUFHu94eC0eBQUeBQUeBQUeBQAAAQAA//8BQAIvADcAAAA0NjsBFSMVIyIGFAYrARUjFSMiBhQGKwEVIyIGFAYrATUzNTMyNjQ2OwE1MzUzMjY0NjsBNTMyARgFDxQoFA8FBQ8UKBQPBQUPFBQPBQUPFCgUDwUFDxQoFA8FBQ8UFA8CDB4FeFAFHgVQUAUeBVAFHgV4UAUeBVBQBR4FUAABACgAdwEYAd8AGwAAEzUzFRQWMhYUFjIWHQEjFSM1IzU0NjI2NDYyNnhQBR4FBR4FUFBQBR4FBR4FAcsUFA8FBR4FBQ8U8PAUDwUFHgUFAAEAKAB3ARgB3wAbAAA3NTMVMxUUBiIGFAYiBh0BIzU0JiImNCYiJj0BeFBQBR4FBR4FUAUeBQUeBe/w8BQPBQUeBQUPFBQPBQUeBQUPFAAAAQAUAMcBLAGPABsAABM1MzIWFBYyFhQWMhYUBiIGFAYiBhQGKwE1IzW0FA8FBR4FBR4FBR4FBR4FBQ8UoAE/UAUeBQUeBQUeBQUeBQUeBVAoAAAAAQAUAMcBLAGPABsAABI0NjsBFTMVIxUjIiY0JiImNCYiJjQ2MjY0NjJkBQ8UoKAUDwUFHgUFHgUFHgUFHgFsHgVQKFAFHgUFHgUFHgUFHgUAAAAAAQAA//8BQAIvADcAABE1MzIWFBY7ARUzMhYUFjsBFTMVMzIWFBY7ARUzFSMiJjQmKwE1IyImNCYrATUjNSMiJjQmKwE1FA8FBQ8UFA8FBQ8UKBQPBQUPFCgUDwUFDxQUDwUFDxQoFA8FBQ8UAbd4BR4FUAUeBVBQBR4FUHgFHgVQBR4FUFAFHgVQAAABABQAxwEsAY8AMwAAEjQ2OwEVMzUzMhYUFjIWFBYyFhQGIgYUBiIGFAYrATUjFSMiJjQmIiY0JiImNDYyNjQ2MmQFDxQoFA8FBR4FBR4FBR4FBR4FBQ8UKBQPBQUeBQUeBQUeBQUeAWweBVBQBR4FBR4FBR4FBR4FBR4FUFAFHgUFHgUFHgUFHgUAAAEAFACfASwBtwAVAAATNTMVMxUzFTMyFh0BITU0NjsBNTM1jCgoKBQPBf7oBQ8UKAFnUFBQUAUPFBQPBVBQAAABABQAnwEsAbcAFQAAEzUhFRQGKwEVIxUjFSM1IzUjNSMiJhQBGAUPFCgoKCgoFA8FAaMUFA8FUFBQUFBQBQAAAQAUAMcBLAFnABkAABI0NjsBFTMyFh0BMxUhNTQ2MjY0NjI2NDYyjAUPFBQPBVD+6AUeBQUeBQUeAUQeBVAFDxQoFA8FBR4FBR4FAAEAKP//ARgCLwA3AAATNTMVFAYrARUjFTMyFhQWMhYUFjsBFTMVIyIGHQEjNTQ2OwE1IzUjIiY0JiImNCYrATUzNTMyNnhQBQ8UKBQPBQUeBQUPFCgUDwVQBQ8UKBQPBQUeBQUPFCgUDwUCGxQUDwVQeAUeBQUeBVB4BQ8UFA8FeFAFHgUFHgV4UAUAAQAo//8BGAIvADcAABM1MxUUFjsBFTMVIyIGFAYiBhQGKwEVIxUzMhYdASM1NCYrATUzNTMyNjQ2MjY0NjsBNSM1IyImeFAFDxQoFA8FBR4FBQ8UKBQPBVAFDxQoFA8FBR4FBQ8UKBQPBQIbFBQPBVB4BR4FBR4FUHgFDxQUDwV4UAUeBQUeBXhQBQABAAAAxwFAAY8ALwAAEzUzFRQWOwEVIyImPQEjFRQGIgYUBiIGHQEjNTQmKwE1MzIWHQEzNTQ2MjY0NjI2yFAFDxQUDwVQBR4FBR4FUAUPFBQPBVAFHgUFHgUBexQUDwVQBQ8UFA8FBR4FBQ8UFA8FUAUPFBQPBQUeBQUAAQAAAMcBQAGPAC8AABM1MxUUFjIWFBYyFh0BMzU0NjsBFSMiBh0BIzU0JiImNCYiJj0BIxUUBisBNTMyNihQBR4FBR4FUAUPFBQPBVAFHgUFHgVQBQ8UFA8FAXsUFA8FBR4FBQ8UFA8FUAUPFBQPBQUeBQUPFBQPBVAFAAEAAP//AUACLwAYAAABNTMRITU0NjsBNTM1MzUzMjY0NjsBNTM1ARgo/sAFDxQoKBQPBQUPFCgB31D90BQPBVBQUAUeBVBQAAEAAP//AUACLwAYAAAVETMVMxUzFTMyFhQWOwEVMxUzFTMyFh0BKCgoFA8FBQ8UKCgUDwUBAjBQUFAFHgVQUFAFDxQAAQAA//8BQAIvABgAABE1IREjNSM1IzUjIiY0JisBNSM1IzUjIiYBQCgoKBQPBQUPFCgoFA8FAhsU/dBQUFAFHgVQUFAFAAAAAQAA//8BQAIvABgAABURIRUUBisBFSMVIxUjIgYUBisBFSMVIxUBQAUPFCgoFA8FBQ8UKCgBAjAUDwVQUFAFHgVQUFAAAAAAAQAAAWcBQAIHACsAABE1MxUUFjIWFBYyNjQ2MjY0NjsBFSM1IyIGFAYiJjQmKwEVIzUjFSM1IyImoAUeBQUeBQUeBQUPFCgUDwUFHgUFDxQoKCgUDwUB8xQUDwUFHgUFHgUFHgWgUAUeBQUeBVB4eHgFAAACADwAdwEEAd8AFwA7AAATNTMVFAYrARUzMhYdASM1NCYrATUzMjYnNTMVIxUUBisBFTMyFh0BMxUjNTQmIiY0JisBNTMyNjQ2Mja0UAUPFBQPBVAFDxQUDwUoeHgFDxQUDwV4eAUeBQUPFBQPBQUeBQF7FBQPBXgFDxQUDwV4BV8UKBQPBcgFDxQoFA8FBR4FyAUeBQUAAAMAPAB3AQQB3wAPAB8AQwAAEzUzFRQGIgYdASM1NDYyNic1MxUUFjIWHQEjNTQmIiY9ATMVFBYyFhQWOwEVIyIGFAYiBh0BIzUzNTQ2OwE1IyImPQFkUAUeBVAFHgUoUAUeBVAFHgV4BR4FBQ8UFA8FBR4FeHgFDxQUDwUBAxQUDwUFDxQUDwUFhxQUDwUFDxQUDwUFSygUDwUFHgXIBR4FBQ8UKBQPBcgFDxQAAAAAAgAoAJ8BGAGPACcAPQAAEzUzFRQWMhYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNhc1IyIGFAYiBh0BMxUzMjY0NjI2PQF4UAUeBQUPFBQPBQUeBVAFHgUFDxQUDwUFHgUoFA8FBR4FUBQPBQUeBQF7FBQPBQUeBVAFHgUFDxQUDwUFHgVQBR4FBVVQBR4FBQ8UUAUeBQUPFAAAAAAEAAAATwFAAd8ABwAPAD8AlQAANjQ2MhYUBiI2NDYyFhQGIic1MxUUFjIWFBY7ARUjFTMVIyIGFAYiBh0BIzU0JiImNCYrATUzNSM1MzI2NDYyNhc1IxUUBiIGFAYiBhQWMjY0NjI2PQEzFRQGKwEVIxUUBiIGFBYyFhQWMhYdATM1NDYyNjQ2MjY0JiIGFAYiBh0BIzU0NjsBNTM1NDYyNjQmIiY0JiImUAUeBQUecwUeBQUefaAFHgUFDxQoKBQPBQUeBaAFHgUFDxQoKBQPBQUeBXhQBR4FBR4FBR4FBR4FUAUPFFAFHgUFHgUFHgVQBR4FBR4FBR4FBR4FUAUPFFAFHgUFHgUFHgXMHgUFHgV9HgUFHgWMFBQPBQUeBVBQUAUeBQUPFBQPBQUeBVBQUAUeBQUZFBQPBQUeBQUeBQUeBQUPFBQPBVAUDwUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFDxQUDwVQFA8FBR4FBR4FBQABAHgBFwDIAWcAAwAAEzUzFXhQARdQUAABAHgAxwDIARcAAwAANzUzFXhQx1BQAAABAHgA7wDIAT8AAwAANzUzFXhQ71BQAAABAHgA7wDIAT8AAwAANzUzFXhQ71BQAAAFAAAAdwFAAbcASQBRAFkAaQB5AAASNDYyFhQWMjY9ATMVFBYyNjQ2MhYUFjIWFAYiBhQWMhYUBiIGFBY7ARUjNSMVIzUjFSM1IxUjNTMyNjQmIiY0NjI2NCYiJjQ2MhY0JiIGFBYyNjQmIgYUFjIGNCYiBhQWMhYUFjI2NCYiNjQmIgYUBiIGFBYyNjQ2MigFHgUFHgVQBR4FBR4FBR4FBR4FBR4FBR4FBQ8UKCgoUCgoKBQPBQUeBQUeBQUeBQUeLQUeBQUezQUeBQUewwUeBQUeBQUeBQUewwUeBQUeBQUeBQUeAZQeBQUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeBQUeBVBQeHh4eFBQBR4FBR4FBR4FBR4FIx4FBR4FBR4FBR4FSx4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FAAAAAAEAKABPARgB3wAfAAATNTMVFAYrARUzFTMVIyIGHQEjNTQmKwE1MzUzNSMiJlCgBQ8UKCgUDwWgBQ8UKCgUDwUByxQUDwV4UHgFDxQUDwV4UHgFAAQAAAAnAUACBwARABkAIQAzAAA3NTMVIxUjNTQmIiY0NjIWHQE8ATYyFhQGIjY0NjIWFAYiJzUzFRQWMhYUBiImPQEjFSM18FBQoAUeBQUeBQUeBQUecwUeBQUefaAFHgUFHgWgUE9QUCgUDwUFHgUFDxR9HgUFHgV9HgUFHgWgKBQPBQUeBQUPFFBQAAAAAAQAAAAnAUACBwAHAB0AJQA7AAA2NDYyFhQGIjc1MxUjIgYUBiIGHQEjNTM1NDY7ATUmNDYyFhQGIic1MxUjFRQGKwEVMxUjNTMyNjQ2MjagBR4FBR5LUBQPBQUeBXh4BQ8UoAUeBQUeLXh4BQ8UKFAUDwUFHgWkHgUFHgUoUKAFHgUFDxQoFA8FUKUeBQUeBYwUKBQPBVBQoAUeBQUAAAACAAAAdwFAAbcAFwAvAAASNDYyFhQWMhYUBiImNCYrARUzFSM1MzIzNTMVIyIGFAYiJjQmIiY0NjIWFBY7ATUoBR4FBR4FBR4FBQ8UKFAUD81QFA8FBR4FBR4FBR4FBQ8UAWweBQUeBQUeBQUeBaBQ8FDwBR4FBR4FBR4FBR4FoAAAAAAEAAAATwFAAd8ABwAPACwASQAANjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYrARUzMhYdATMVIzUjIiY0JisBNTMyNzUzFTMyFhQWOwEVIyIGFAYiJjQ2OwE1IyImPQFQBR4FBR5zBR4FBR6lBR4FBQ8UFA8FUFAUDwUFDxQUD31QFA8FBQ8UFA8FBR4FBQ8UFA8F9B4FBR4FLR4FBR4FLR4FBR4FeAUPFFBQBR4FeFBQUAUeBXgFHgUFHgV4BQ8UAAAFAAAAdwFAAbcABwAPACcALwA3AAA2NDYyFhQGIjY0NjIWFAYiJzUzFRQWOwEVIyIGHQEjNTQmKwE1MzI2IjQ2MhYUBiI2NDYyFhQGIlAFHgUFHsMFHgUFHqVQBQ8UFA8FUAUPFBQPBXgFHgUFHsMFHgUFHnweBQUeBVUeBQUeBYwUFA8FUAUPFBQPBVAFHgUFHgVVHgUFHgUAAAAFAAAAdwFAAbcABwAPABcALwA3AAA2NDYyFhQGIiY0NjIWFAYiJDQ2MhYUBiInNTMVFBY7ARUjIgYdASM1NCYrATUzMjY8ATYyFhQGIqAFHgUFHqUFHgUFHgETBR4FBR6lUAUPFBQPBVAFDxQUDwUFHgUFHnweBQUeBX0eBQUeBS0eBQUeBTwUFA8FUAUPFBQPBVAFUB4FBR4FAAAFAAAAdwFAAbcABwAPABcALwA3AAA2NDYyFhQGIiY0NjIWFAYiJDQ2MhYUBiInNTMVFBY7ARUjIgYdASM1NCYrATUzMjYmNDYyFhQGIsgFHgUFHs0FHgUFHgETBR4FBR6lUAUPFBQPBVAFDxQUDwUoBR4FBR58HgUFHgVVHgUFHgV9HgUFHgUUFBQPBVAFDxQUDwVQBVAeBQUeBQAFACgAnwEYAY8ABwAPACcALwA3AAA2NDYyFhQGIiY0NjIWFAYiNzUzFRQWOwEVIyIGHQEjNTQmKwE1MzI+ATQ2MhYUBiImNDYyFhQGIvAFHgUFHs0FHgUFHktQBQ8UFA8FUAUPFBQPBXgFHgUFHs0FHgUFHqQeBQUeBQUeBQUeBbQUFA8FUAUPFBQPBVAFKB4FBR4FBR4FBR4FAAACABQATwEsAbcANQBFAAATNTMVIxUzFRQWMhYUBiImPQEjFRQWMhYUFjsBFSMiBh0BIzU0JisBNTMyNj0BMzUjNTQmIiYWNCYiBhQGIgYUFjI2NDYyFPBQUAUeBQUeBVAFHgUFDxQUDwWgBQ8UFA8FeHgFHgXIBR4FBR4FBR4FBR4BoxQoKBQPBQUeBQUPFBQPBQUeBXgFDxQUDwWgBQ8UKBQPBQXwHgUFHgUFHgUFHgUAAAACACgAdwEYAY8AHQAhAAATNTMVFBYyFhQGIgYUFjsBFSM1MzI2NCYiJjQ2MjYXNSMVUKAFHgUFHgUFDxTwFA8FBR4FBR4FeFABexQUDwUFHgUFHgWgoAUeBQUeBQVVKCgAAAAAAgAAACcBQAIHACUAKQAAEzUzFRQWMhYUFjsBESE1IyImNDY7ATUjIiY0NjsBNTMyNjQ2MjYXNSMVeHgFHgUFDxT+6BQPBQUPFBQPBQUPFBQPBQUeBaBQAfMUFA8FBR4F/nBQBR4FoAUeBVAFHgUF9SgoAAAEAAAAJwFAAd8AAwAHAAsADwAAPQEhFSc1MxUnNTMVJzUzFQFA8PCgoFBQJ1BQeFBQeFBQeFBQAAIAAADvAUABZwADABMAADc1MxUkNDY7ARUjIiY0JiImNDYyeMj+6AUPFBQPBQUeBQUe73h4VR4FeAUeBQUeBQAEAAAAnwFAAbcAAwATABcAJwAANzUzFSQ0NjsBFSMiJjQmIiY0NjI3NTMVJDQ2OwEVIyImNCYiJjQ2MnjI/ugFDxQUDwUFHgUFHlXI/ugFDxQUDwUFHgUFHp94eFUeBXgFHgUFHgVQeHhVHgV4BR4FBR4FAAMAAABPAUABjwA3AEcAVwAAEzQ2MhYUFjI2NDYyFhQGIgYUFjIWFAYrARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjY9ATMWNCYiBhQWMhYUFjI2NCYiBjQmIgYUBiIGFBYyNjQ2MsgFHgUFHgUFHgUFHgUFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeCgFHgUFHgUFHgUFHn0FHgUFHgUFHgUFHgF7DwUFHgUFHgUFHgUFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFCMeBQUeBQUeBQUeBSMeBQUeBQUeBQUeBQACAAAATwFAAd8AQwBTAAASNDYyFhQWMhYUBiIGFBYyFh0BIxUUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNj0BMxUUFjsBNTMyNjQmIgY0JiIGFAYiBhQWMjY0NjLwBR4FBR4FBR4FBR4FUAUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BQ8UFA8FBR59BR4FBR4FBR4FBR4BvB4FBR4FBR4FBR4FBQ8UFA8FeAUeBQUPFBQPBQUeBXgFHgUFDxQUDwVQBR4Fmx4FBR4FBR4FBR4FAAACAAAATwFAAgcARwBXAAASNDYyFhQWOwEVIyIGFAYiBhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEzFRQWMjY0NjI2NCYiJjQ2MjY0JiIGNCYiBhQGIgYUFjI2NDYy8AUeBQUPFBQPBQUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BR4FBR4FBR4FBR4FBR59BR4FBR4FBR4FBR4B5B4FBR4FeAUeBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FBR4FBR4FBR4Fwx4FBR4FBR4FBR4FAAACAAAATwFAAi8AVwBnAAASNDYyFhQWMjY0NjIWFAYiBhQWOwEVIyIGFAYiBhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEzFRQWMjY0NjsBNSMiBhQGIiY0NjI2NCYiBjQmIgYUBiIGFBYyNjQ2MsgFHgUFHgUFHgUFHgUFDxQUDwUFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBQUeBQUeVQUeBQUeBQUeBQUeAgweBQUeBQUeBQUeBQUeBXgFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgFHgUFHgUFHgXrHgUFHgUFHgUFHgUAAAAAAgAAAE8BQAIvAE8AXwAAEjQ2MhYUFjIWFBY7ARUjIgYUBiIGFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjY9ATMVFBYyNjQ2OwE1IyIGFAYiJjQmIiY0NjIGNCYiBhQGIgYUFjI2NDYyyAUeBQUeBQUPFBQPBQUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FBR4FBR5LBR4FBR4FBR4FBR4CDB4FBR4FBR4FeAUeBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUeBQUeBeseBQUeBQUeBQUeBQAAAAACAAAATwFAAi8AXwBvAAASNDYyFhQWMjY0NjIWFBYyFhQWOwEVIyIGFAYiBhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEzFRQWMjY0NjsBNSMiBhQGIiY0JiIGFAYiJjQ2MjY0JiIGNCYiBhQGIgYUFjI2NDYyeAUeBQUeBQUeBQUeBQUPFBQPBQUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4CDB4FBR4FBR4FBR4FBR4FeAUeBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUeBQUeBQUeBQUeBeseBQUeBQUeBQUeBQAAAAACAAAATwFAAgcAXgBuAAASNDYyFhQWMjY9ATMVFBY7ARUjIgYUBiIGFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjY9ATMVFBYyNjQ2OwE1IxUUFjIWFAYiJjQmIgYUBiImNDYyNjQmIhY0JiIGFAYiBhQWMjY0NjJQBR4FBR4FeAUPFBQPBQUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UeAUeBQUeBQUeBQUeBQUeBQUeIwUeBQUeBQUeBQUeAeQeBQUeBQUPFBQPBXgFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgUDwUFHgUFHgUFHgUFHgUFHgXDHgUFHgUFHgUFHgUAAwAUAHcBLAIHAC8APQBNAAATNTMVMzUzFSMVFBYyFhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEWNCYrARUzNSMiBhQGIhY0JiIGFAYiBh0BMzU0JiIUUHhQUAUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgUoBQ8UeBQPBQUeIwUeBQUeBXgFHgG3UFBQUBQPBQUeBaAFHgUFDxQUDwUFHgWgBR4FBQ8USx4FUFAFHgVzHgUFHgUFDxQUDwUAAAACABQAdwEsAbcAIQAzAAATNTMVMzUzFSMiBhQWOwEVIyIGHQEjNTQmKwE1MzI2NCYjFzUjFTMyFhQWMjY0NjsBNSMVFFB4UBQPBQUPFBQPBcgFDxQUDwUFD2QoFA8FBR4FBQ8UKAFnUFBQUAUeBaAFDxQUDwWgBR4FeFBQBR4FBR4FUFAAAAAAAgAUAMcBLAGPADcATwAAEzUzFRQWMjY9ATMVFAYiBhQGIgYUFjIWFBYyFh0BIzU0JiIGHQEjNTQmIiY0JiImNDYyNjQ2MjYWNCYiBhQGIgYUFjIWFBYyNjQmIiY0NjJkUAUeBVAFHgUFHgUFHgUFHgVQBR4FUAUeBQUeBQUeBQUeBVAFHgUFHgUFHgUFHgUFHgUFHgF7FBQPBQUPFBQPBQUeBQUeBQUeBQUPFBQPBQUPFBQPBQUeBQUeBQUeBQUoHgUFHgUFHgUFHgUFHgUFHgUAAAACABQAxwEsAY8ANwBPAAATNTMVFBYyNj0BMxUUFjIWFBYyFhQGIgYUBiIGHQEjNTQmIgYdASM1NDYyNjQ2MjY0JiImNCYiJhY0JiIGFBYyFhQGIgYUFjI2NDYyNjQmIhRQBR4FUAUeBQUeBQUeBQUeBVAFHgVQBR4FBR4FBR4FBR4FoAUeBQUeBQUeBQUeBQUeBQUeAXsUFA8FBQ8UFA8FBR4FBR4FBR4FBQ8UFA8FBQ8UFA8FBR4FBR4FBR4FBSgeBQUeBQUeBQUeBQUeBQUeBQAAABwAFP//ASwCLwAHAA8AFwAfACcALwA3AD8ARwBPAFcAXwBnAG8AdwB/AIcAjwCXAJ8ApwCvALcAvwDHAM8A1wDfAAA2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIrQFHgUFHqUFHgUFHusFHgUFHqUFHgUFHksFHgUFHqUFHgUFHusFHgUFHqUFHgUFHksFHgUFHqUFHgUFHusFHgUFHqUFHgUFHksFHgUFHqUFHgUFHusFHgUFHqUFHgUFHksFHgUFHqUFHgUFHusFHgUFHqUFHgUFHksFHgUFHqUFHgUFHusFHgUFHqUFHgUFHksFHgUFHqUFHgUFHusFHgUFHqUFHgUFHgQeBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBQAEAAD//wFAAi8AXwCfAT8BvwAAEjQ2MhYUFjI2NDYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQGIiY0JiIGFAYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyFjQmIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFBYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjIWFBYyNjQ2MhYUFjI2NDYyFhQWMjY0NjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQGIiY0JiIGFAYiJjQmIgYUBiImNCYiBhQGIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjIWNCYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFBYyNjQ2MhYUFjI2NDYyFhQWMjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQmIgYUBiImNCYiBhQGIngFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHi0FHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHn0FHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHi0FHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgG8HgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUjHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgV9HgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUjHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUAAAAAEwAA//8BQAIvAFIAWgBiAGoAcgB6AIIAigCSAJoAogCqALIAugDCAMoA0gDaAOIAABE1MxUUFjI2PQEzFRQWMjY0NjsBESM1NCYiBh0BIzU0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImFjQmIgYUFjIGNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjIGNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjIGNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjIGNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjIGNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjIGNCYiBhQWMjY0JiIGFBYyUAUeBXgFHgUFDxR4BR4FeAUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBcgFHgUFHksFHgUFHqUFHgUFHksFHgUFHksFHgUFHqUFHgUFHksFHgUFHksFHgUFHqUFHgUFHksFHgUFHksFHgUFHqUFHgUFHksFHgUFHksFHgUFHqUFHgUFHksFHgUFHksFHgUFHqUFHgUFHgIbFBQPBQUPFBQPBQUeBf3QFA8FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBSgeBQUeBSMeBQUeBQUeBQUeBSMeBQUeBSMeBQUeBQUeBQUeBSMeBQUeBSMeBQUeBQUeBQUeBSMeBQUeBSMeBQUeBQUeBQUeBSMeBQUeBSMeBQUeBQUeBQUeBSMeBQUeBSMeBQUeBQUeBQUeBQAAAQB4//8AyAIvAAMAABcRMxF4UAECMP3QAAAAAAEAAP//AMgCLwAHAAATNTMRIxEjNXhQUHgBP/D90AEYKAAAAAMAAP//ARgCLwAFAAkADwAAPQEzESM1FxEzEQM1MxUjNaBQeFDIUKDvKP7o8PACMP3QAWjI8CgAAgBQ//8BGAIvAAMABwAAFxEzESMRMxHIUMhQAQIw/dACMP3QAAAAAAIAAP//ARgBZwAFAAsAAD0BMxEjNSc1IREjEaBQUAEYUO8o/ujwUCj+mAFAAAAAAAIAAADvARgCLwAFAAsAABMRMxEhNTc1MxUjNchQ/uhQUKABFwEY/sAoUMjwKAAAAAEAAP//AMgBPwAFAAARNTMRIxHIUAEXKP7AARgAAAAAAQB4ARcBQAIvAAUAABMRMxUzFXhQeAEXARjwKAAAAAABAAABFwFAAi8ABwAAEzUzFTMVITV4UHj+wAE/8PAoKAAAAAABAAD//wFAAT8ABwAAETUhFSMRIxEBQHhQARcoKP7oARgAAAABAHj//wFAAi8ABwAAFxEzFTMVIxF4UHh4AQIw8Cj+6AAAAAABAAABFwFAAT8AAwAAETUhFQFAARcoKAABAAD//wFAAi8ACwAAEzUzFTMVIxEjESM1eFB4eFB4AT/w8Cj+6AEYKAAAAAIAUADvAUACLwAIAA4AABM1MxUzMhYdAQcRMxEzFchQFA8F8FCgAT/wyAUPFFABQP7oKAACAFD//wFAAWcACAAOAAAXETMVFAYrARUjETMVIxHIeAUPFMjwoAEBGBQPBfABaCj+wAAAAwAAAO8BQAIvAAMADAASAAA9ASEVJzUzFTMyFh0BJzUzFSM1AUB4UBQPBfBQoO8oKFDwyAUPFCjI8CgAAwAA//8BQAFnAAgADgASAAAXETMVFAYrARUlNTMRIzUnNSEVyHgFDxT+6KBQUAFAAQEYFA8F8PAo/ujwUCgoAAMAUP//AUACLwAIABEAFQAAFxEzFRQGKwEVAzUzFTMyFh0BAxEzEch4BQ8UUFAUDwXwUAEBGBQPBfABQPDIBQ8U/sACMP3QAAIAAADvAUABZwADAAcAAD0BIRUlNSEVAUD+wAFA7ygoUCgoAAQAAP//AUACLwAIAA4AFwAdAAAXETMVFAYrARUlNTMRIzU3NTMVMzIWHQEnNTMVIzXIeAUPFP7ooFB4UBQPBfBQoAEBGBQPBfDwKP7o8FDwyAUPFCjI8CgAAAAAAQAAARcAyAIvAAUAABM1MxEjNXhQyAE/8P7oKAAAAAABAHj//wFAAT8ABQAAFxEzFSMReMh4AQFAKP7oAAAAAAEAAP//AUACLwADAAAVESERAUABAjD90AAAAAABAAD//wFAARcAAwAAFREhEQFAAQEY/ugAAAAAAQAA//8AoAIvAAMAABURMxGgAQIw/dAAAQCg//8BQAIvAAMAABcRMxGgoAECMP3QAAAAAAEAAAEXAUACLwADAAAZASERAUABFwEY/ugAAAACAAAAnwFAAY8AJwAvAAAANDY7ARUjIiY0JiImNCYiBh0BIzU0JisBNTMyNj0BMxUUFjI2NDYyBjQmIgYUFjIBGAUPFBQPBQUeBQUeBaAFDxQUDwWgBR4FBR7DBR4FBR4BbB4F8AUeBQUeBQUPFBQPBVAFDxQUDwUFHgVLHgUFHgUAAAADABQAnwEsAbcAJwA3AD8AABM1MxUUFjIWFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjYWNCYiBhQGIgYUFjI2NDYyFjQmIgYUFjJkeAUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgUoBR4FBR4FBR4FBR5VBR4FBR4BoxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUoHgUFHgUFHgUFHgWbHgUFHgUAAwAAAHcBQAHfAA0AFQAdAAA1ESEVIxUzFSM1MzUjETY0JiIGFBYyFjQmIgYUFjIBQFBQyFB4eAUeBQUeVQUeBQUedwFoKCigoCj+wM0eBQUeBSMeBQUeBQAAAAAEABQAdwEsAd8AJQAtADUAPQAAEjQ2MhYUFjI2NDYyFhQWOwERIzUjFSM1IxUjNTMyNjQ2MjY0NjIWNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjKMBR4FBR4FBR4FBQ8UUChQKCgUDwUFHgUFHi0FHgUFHlUFHgUFHiMFHgUFHgG8HgUFHgUFHgUFHgX+wHh4oFCgBR4FBR4FSx4FBR4FBR4FBR4FSx4FBR4FAAAAAQAA//8BQAEXAB4AACQ0NjsBESE1NDYyNjQ2MjY0NjI2PQEzNTQ2MjY0NjIBGAUPFP7ABR4FBR4FBR4FUAUeBQUe9B4F/ugUDwUFHgUFHgUFDxQUDwUFHgUAAAABAAD//wFAAi8AHQAAADQ2OwERIREzMjY0NjI2NDYyNj0BMzU0NjI2NDYyARgFDxT+wBQPBQUeBQUeBVAFHgUFHgIMHgX90AFABR4FBR4FBQ8UFA8FBR4FAAAAAAEAAP//AUACLwAdAAAVETMyFhQWMhYUFjIWHQEzFRQWMhYUFjIWFBY7AREUDwUFHgUFHgVQBR4FBR4FBQ8UAQIwBR4FBR4FBQ8UFA8FBR4FBR4F/sAAAAABAAD//wFAARcAHgAAFREzMhYUFjIWFBYyFh0BMxUUFjIWFBYyFhQWMhYdARQPBQUeBQUeBVAFHgUFHgUFHgUBARgFHgUFHgUFDxQUDwUFHgUFHgUFDxQACwAAAE8BQAIHADcAPwBDAEsAUwBXAF8AZwBvAHMAewAAETUzFRQWMhYUFjsBFSMiBhQGKwEVMzIWFBYyFh0BIzU0JiImNCYrATUzMjY0NjsBNSMiJjQmIiYWNCYiBhQWMjM1IxUWNCYiBhQWMjY0JiIGFBYyBzUjFTY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMgc1IxU2NCYiBhQWMvAFHgUFDxQUDwUFDxQUDwUFHgXwBR4FBQ8UFA8FBQ8UFA8FBR4FeAUeBQUefVAoBR4FBR5VBR4FBR5zUKAFHgUFHpsFHgUFHn0FHgUFHiNQoAUeBQUeAfMUFA8FBR4FeAUeBXgFHgUFDxQUDwUFHgV4BR4FeAUeBQUoHgUFHgUoKEseBQUeBQUeBQUeBVAoKAUeBQUeBUseBQUeBQUeBQUeBVAoKAUeBQUeBQAAAAAGABQAdwEsAd8AJwAvADcAPwBHAE8AABM1MxUUFjIWFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjYWNCYiBhQWMhY0JiIGFBYyBjQmIgYUFjIWNCYiBhQWMgY0JiIGFBYyZHgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FKAUeBQUeVQUeBQUecwUeBQUepQUeBQUeSwUeBQUeAcsUFA8FBR4FyAUeBQUPFBQPBQUeBcgFHgUFKB4FBR4FSx4FBR4FIx4FBR4FIx4FBR4FSx4FBR4FAAAABAAoAHcBGAHfACMAJwArADMAABM1MxUUFjsBESMiJjQmIgYUBiImNCYiBhQGIiY0JisBETMyNhc1IxUzNSMVFDQmIgYUFjJQoAUPFBQPBQUeBQUeBQUeBQUeBQUPFBQPBSgoeCgFHgUFHgHLFBQPBf7ABR4FBR4FBR4FBR4FBR4FARgFfVBQUFBLHgUFHgUAAQAoAHcBGAIHAFwAABM1MxUzFRQGIgYUFjIWFAYiJjQmIiY0JisBFTMyFhQWMhYUFjsBFSMiBh0BIzU0JisBNTMyNjQmIiY0NjIWFBYyFhQGIgYUFjIWHQEzNSMiJjQmIiY0JisBNTMyNnhQUAUeBQUeBQUeBQUeBQUPFBQPBQUeBQUPFBQPBaAFDxQUDwUFHgUFHgUFHgUFHgUFHgVQFA8FBR4FBQ8UFA8FAfMUKBQPBQUeBQUeBQUeBQUeBVAFHgUFHgV4BQ8UFA8FUAUeBQUeBQUeBQUeBQUeBQUPFHgFHgUFHgV4BQAAAAAFAAAATwFAAi8AAwBQAHAAigCSAAA3NTMVAjQ2MhYUFjIWFBY7ARUzMhYUFjIWFAYiBhQWMhYUBiIGFAYiBh0BIzU0JiImNCYiJjQ2MjY0JiImNDY7ATUzFTM1NCYrATUjIiY0NjIWNCYiBhQWOwEVMzIWFBYyFhQWMjY0JiImNCYrATUjIgc0JiIGFBYyFhQGIgYUFjIWHQEzNTM1IzUjEjQ2MhYUBiJQUCgFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgWgBR4FBR4FBR4FBR4FBQ8UKFAFDxQUDwUFHi0FHgUFDxQUDwUFHgUFHgUFHgUFDxQUD1UFHgUFHgUFHgUFHgVQUFBQKAUeBQUenygoAR0eBQUeBQUeBVAFHgUFHgUFHgUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgVQUBQPBVAFHgUjHgUFHgVQBR4FBR4FBR4FBR4FUIwPBQUeBQUeBQUeBQUPFCgoKAEdHgUFHgUAAAAAAQAAARcBQAIvAB4AABE1IREjIiY0JiImNCYiJj0BIzU0JiImNCYiJjQmIiYBQBQPBQUeBQUeBVAFHgUFHgUFHgUCGxT+6AUeBQUeBQUPFBQPBQUeBQUeBQUAAAABAAD//wFAAi8AHQAANREhESMiJjQmIiY0JiImPQEjNTQmIiY0JiImNCYjAUAUDwUFHgUFHgVQBR4FBR4FBQ/vAUD90AUeBQUeBQUPFBQPBQUeBQUeBQAAAQAA//8BQAIvAB0AABURIREjIgYUBiIGFAYiBh0BIxUUBiIGFAYiBhQGIwFAFA8FBR4FBR4FUAUeBQUeBQUPAQIw/sAFHgUFHgUFDxQUDwUFHgUFHgUAAAEAAAEXAUACLwAeAAAZASEVFAYiBhQGIgYUBiIGHQEjFRQGIgYUBiIGFAYjAUAFHgUFHgUFHgVQBR4FBR4FBQ8BFwEYFA8FBR4FBR4FBQ8UFA8FBR4FBR4FAAAABAAAAE8BQAHfABcAGwAfAC0AABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhc1IxUzNSMVBzQmIgYUFjIWHQEzNSMo8AUPFBQPBfAFDxQUDwVQKKAoUAUeBQUeBVBQAcsUFA8F/sAFDxQUDwUBQAV9UFBQUGQPBQUeBQUPFCgAAAAEAAAATwFAAd8AFwAbAB8AIwAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzUjFTM1Ix0BNSMVKPAFDxQUDwXwBQ8UFA8FUCigKFAByxQUDwX+wAUPFBQPBQFABX1QUFBQoFBQAAAAAAQAAABPAUAB3wAXABsAHwAtAAATNTMVFBY7AREjIgYdASM1NCYrAREzMjYXNSMVMzUjHQE1IxUUBiIGFBYyNj0BKPAFDxQUDwXwBQ8UFA8FUCigKFAFHgUFHgUByxQUDwX+wAUPFBQPBQFABX1QUFBQoCgUDwUFHgUFDxQAAAAAAwAAAE8BQAHfABcAOQBHAAATNTMVFBY7AREjIgYdASM1NCYrAREzMjYXNCYiBhQWMhYUBisBFTM1MxUzNSMiJjQ2MjY0JiIGHQEjFzUjFRQGIgYUFjI2PQEo8AUPFBQPBfAFDxQUDwVQBR4FBR4FBQ8UKFAoFA8FBR4FBR4FUFBQBR4FBR4FAcsUFA8F/sAFDxQUDwUBQAUZDwUFHgUFHgVQUFBQBR4FBR4FBQ8U8CgUDwUFHgUFDxQAAAAEAAAATwFAAd8AFwAfACMAKwAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FjQmIgYUFjIzNSMVFDQmIgYUFjIo8AUPFBQPBfAFDxQUDwVQBR4FBR59UAUeBQUeAcsUFA8F/sAFDxQUDwUBQAV4HgUFHgUoKJseBQUeBQAABAAAAE8BQAHfABcANwBXAF8AABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhY0JiIGFBYyFhQGIgYUFjI2NDYyFhQWMjY0JiImNCYiNjQmIgYUFjIWFBYyFhQWMjY0JiImNDYyNjQmIgYUBiIGNCYiBhQWMijwBQ8UFA8F8AUPFBQPBSgFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHnMFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHlUFHgUFHgHLFBQPBf7ABQ8UFA8FAUAFUB4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4F6x4FBR4FAAMAAABPAUAB3wAXACkAMgAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzUjFTM1NDYyFh0BMzU0NjI2BzQmKwEVMzUjKPAFDxQUDwXwBQ8UFA8F8PBQBR4FUAUeBaAFDxR4UAHLFBQPBf7ABQ8UFA8FAUAFQRRQFA8FBQ8UFA8FBZEPBVAoAAAEAAAATwFAAd8AFwAbAB8ALwAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzUjFTM1Ix0BNSMVFAYiBh0BMzU0JiImKPAFDxQUDwXwBQ8UFA8FUCigKFAFHgWgBR4FAcsUFA8F/sAFDxQUDwUBQAV9UFBQUGQUFA8FBQ8UFA8FBQAAAAABAHgA7wDIAT8AAwAANzUzFXhQ71BQAAABAIwA7wC0ARcABwAANjQ2MhYUBiKMBR4FBR70HgUFHgUAAAABAAAATwFAAgcAMQAAADQ2MhYUBisBFSMVIxUjFSMiBhQGIiY0JisBNSM1MzIWFBY7ARUzNTM1MzUzMjY0NjIBGAUeBQUPFCgoKBQPBQUeBQUPFCgUDwUFDxQoKCgUDwUFHgHkHgUFHgVQeFBQBR4FBR4FUHgFHgVQUFBQBR4FAAEAUAE/APACLwAdAAASNDYyFhQGKwEVIxUjIiY0JisBNTMyNjQ2MjY0NjLIBR4FBQ8UKBQPBQUPFBQPBQUeBQUeAgweBQUeBVB4BR4FUAUeBQUeBQAAAAABACgAdwEYAbcAJwAAEjQ2OwEVMzIWHQEzFRQWOwEVIyIGFAYrATUjIiY9ASM1NCYrATUzMlAFDxQUDwVQBQ8UFA8FBQ8UFA8FUAUPFBQPAZQeBXgFDxQUDwVQBR4FeAUPFBQPBVAAAAEAPACfAQQBjwADAAA3NTMVPMif8PAAAAEAUP//APAA7wAdAAA3NTMyFhQWOwEVIyIGFAYiBhQGIgYUBiImNDY7ATWgFA8FBQ8UFA8FBR4FBR4FBR4FBQ8Ud3gFHgVQBR4FBR4FBR4FBR4FUAAAAAAAAA4ArgABAAAAAAAAADQAagABAAAAAAABAAgAsQABAAAAAAACAAYAyAABAAAAAAADACUBGwABAAAAAAAEAAkBVQABAAAAAAAFABABgQABAAAAAAAGAAgBpAADAAEECQAAAGgAAAADAAEECQABABAAnwADAAEECQACAAwAugADAAEECQADAEoAzwADAAEECQAEABIBQQADAAEECQAFACABXwADAAEECQAGABABkgBDAHIAZQBhAHQAZQBkACAAdwBpAHQAaAAgAEYAbwBuAHQARgBvAHIAZwBlACAAMgAuADAAIAAoAGgAdAB0AHAAOgAvAC8AZgBvAG4AdABmAG8AcgBnAGUALgBzAGYALgBuAGUAdAApAABDcmVhdGVkIHdpdGggRm9udEZvcmdlIDIuMCAoaHR0cDovL2ZvbnRmb3JnZS5zZi5uZXQpAABtAGUAZwBhAHoAZQB1AHgAAG1lZ2F6ZXV4AABNAGUAZABpAHUAbQAATWVkaXVtAABGAG8AbgB0AEYAbwByAGcAZQAgADIALgAwACAAOgAgAG0AZQBnAGEAegBlAHUAeAAgACAAOgAgADIAMQAtADYALQAyADAAMQAyAABGb250Rm9yZ2UgMi4wIDogbWVnYXpldXggIDogMjEtNi0yMDEyAABtAGUAZwBhAHoAZQB1AHgAIAAAbWVnYXpldXggAABWAGUAcgBzAGkAbwBuACAAMAAwADEALgAwADAAMAAgAABWZXJzaW9uIDAwMS4wMDAgAABtAGUAZwBhAHoAZQB1AHgAAG1lZ2F6ZXV4AAAAAgAAAAAAAP+DADIAAAABAAAAAAAAAAAAAAAAAAAAAAELAAAAAQACAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAClAWEBYgFjAJwBZAFlAWYBZwFoAWkBagFrAWwBbQFuAW8BcAFxAXIBcwF0AXUBdgF3AXgBeQF6AXsBfAF9AX4BfwGAAYEBggGDAYQBhQGGAYcBiAGJAYoBiwGMAY0BjgGPAZABkQGSAZMBlAGVAZYBlwGYAZkBmgGbAZwBnQGeAZ8BoAGhAaIBowGkAaUBpgGnAagBqQGqAasBrAGtAa4BrwGwAbEBsgGzAbQBtQG2AbcBuAG5AboBuwG8Ab0BvgG/AcABwQHCAcMBxAHFAcYBxwHIAckBygHLAcwBzQHOAc8B0AHRAdIB0wHUAdUB1gHXAdgB2QHaAdsB3AHdAd4B3wHgAeEB4gHjAeQB5QHmAecB6AHpAeoB6wHsAe0B7gHvAfAB8QHyAfMB9AH1AfYB9wH4AfkB+gH7AfwB/QH+Af8CAAIBAgICAwIEAgUCBgIHBlUrMDAyMAZVKzAwMjEGVSswMDIyBlUrMDAyMwZVKzAwMjQGVSswMDI1BlUrMDAyNgZVKzAwMjcGVSswMDI4BlUrMDAyOQZVKzAwMmEGVSswMDJiBlUrMDAyYwZVKzAwMmQGVSswMDJlBlUrMDAyZgZVKzAwMzAGVSswMDMxBlUrMDAzMgZVKzAwMzMGVSswMDM0BlUrMDAzNQZVKzAwMzYGVSswMDM3BlUrMDAzOAZVKzAwMzkGVSswMDNhBlUrMDAzYgZVKzAwM2MGVSswMDNkBlUrMDAzZQZVKzAwM2YGVSswMDQwBlUrMDA0MQZVKzAwNDIGVSswMDQzBlUrMDA0NAZVKzAwNDUGVSswMDQ2BlUrMDA0NwZVKzAwNDgGVSswMDQ5BlUrMDA0YQZVKzAwNGIGVSswMDRjBlUrMDA0ZAZVKzAwNGUGVSswMDRmBlUrMDA1MAZVKzAwNTEGVSswMDUyBlUrMDA1MwZVKzAwNTQGVSswMDU1BlUrMDA1NgZVKzAwNTcGVSswMDU4BlUrMDA1OQZVKzAwNWEGVSswMDViBlUrMDA1YwZVKzAwNWQGVSswMDVlBlUrMDA1ZgZVKzAwNjAGVSswMDYxBlUrMDA2MgZVKzAwNjMGVSswMDY0BlUrMDA2NQZVKzAwNjYGVSswMDY3BlUrMDA2OAZVKzAwNjkGVSswMDZhBlUrMDA2YgZVKzAwNmMGVSswMDZkBlUrMDA2ZQZVKzAwNmYGVSswMDcwBlUrMDA3MQZVKzAwNzIGVSswMDczBlUrMDA3NAZVKzAwNzUGVSswMDc2BlUrMDA3NwZVKzAwNzgGVSswMDc5BlUrMDA3YQZVKzAwN2IGVSswMDdjBlUrMDA3ZAZVKzAwN2UFYW5nbGUMaW50ZXJzZWN0aW9uBXVuaW9uB3VuaTIyMzUHdW5pMjI1MgtlcXVpdmFsZW5jZQ1wZXJwZW5kaWN1bGFyBlUrZTAwMQZVK2UwMDIGVStlMDAzBlUrZTAwNAZVK2UwMDUGVStlMDA2BlUrZTAwNwZVK2UwMDgGVStlMDA5BlUrZTAwYQZVK2UwMGIGVStlMDBjBlUrZTAwZAZVK2UwMGUGVStlMDBmBlUrZTAxMAZVK2UwMTEGVStlMDEyBlUrZTAxMwZVK2UwMTQGVStlMDE1BlUrZTAxNgZVK2UwMTcGVStlMDE4BlUrZTAxOQZVK2UwMWEGVStlMDFiBlUrZTAxYwZVK2UwMWQGVStlMDFlBlUrZTAxZgZVK2UwN2YGVStlMDgwBlUrZTA4MQZVK2UwODIGVStlMDgzBlUrZTA4NAZVK2UwODUGVStlMDg2BlUrZTA4NwZVK2UwODgGVStlMDg5BlUrZTA4YQZVK2UwOGIGVStlMDhjBlUrZTA4ZAZVK2UwOGUGVStlMDhmBlUrZTA5MAZVK2UwOTEGVStlMDkyBlUrZTA5MwZVK2UwOTQGVStlMDk1BlUrZTA5NgZVK2UwOTcGVStlMDk4BlUrZTA5OQZVK2UwOWEGVStlMDliBlUrZTA5YwZVK2UwOWQGVStlMDllBlUrZTA5ZgZVK2UwYTAGVStlMGExBlUrZTBhMgZVK2UwYTMGVStlMGE0BlUrZTBhNQZVK2UwYTYGVStlMGE3BlUrZTBhOAZVK2UwYTkGVStlMGFhBlUrZTBhYgZVK2UwYWMGVStlMGFkBlUrZTBhZQZVK2UwYWYGVStlMGIwBlUrZTBiMQZVK2UwYjIGVStlMGIzBlUrZTBiNAZVK2UwYjUGVStlMGI2BlUrZTBiNwZVK2UwYjgGVStlMGI5BlUrZTBiYQZVK2UwYmIGVStlMGJjBlUrZTBiZAZVK2UwYmUGVStlMGJmBlUrZTBjMAZVK2UwYzEGVStlMGMyBlUrZTBjMwZVK2UwYzQGVStlMGM1BlUrZTBjNgZVK2UwYzcGVStlMGM4BlUrZTBjOQZVK2UwY2EGVStlMGNiBlUrZTBjYwZVK2UwY2QGVStlMGNlBlUrZTBjZgZVK2UwZDAGVStlMGQxBlUrZTBkMgZVK2UwZDMGVStlMGQ0BlUrZTBkNQZVK2UwZDYGVStlMGQ3BlUrZTBkOAZVK2UwZDkGVStlMGRhBlUrZTBkYgZVK2UwZGMGVStlMGRkBlUrZTBkZQZVK2UwZGYGVStlMGUwBlUrZTBlMQZVK2UwZTIGVStlMGUzBlUrZTBlNAZVK2UwZTUGVStlMGU2BlUrZTBlNwZVK2UwZTgGVStlMGU5BlUrZTBlYQZVK2UwZWIGVStlMGVjBlUrZTBlZAZVK2UwZWUGVStlMGVmBlUrZTBmMAZVK2UwZjEGVStlMGYyBlUrZTBmMwZVK2UwZjQGVStlMGY1BlUrZTBmNgZVK2UwZjcGVStlMGY4BlUrZTBmOQZVK2UwZmEGVStlMGZiBlUrZTBmYwZVK2UwZmQGVStlMGZlBlUrZTBmZgAAAAH//wACAAEAAAAOAAAAGAAgAAAAAgABAAEBCgABAAQAAAACAAAAAQAAAAEAAAAAAAEAAAAAyYlvMQAAAADMCC9PAAAAAMwITt8=)\n       format('truetype');\n\n  /* The original thing that was here:\n  src: url('mzxfont.eot');\n  src: url('mzxfont.eot?#iefix') format('embedded-opentype'),\n       url('mzxfont.woff') format('woff'),\n       url('mzxfont.tff') format('truetype');\n      */\n  font-weight: normal;\n  font-style: normal;\n\n  /* Try to fix the ugly blur. */\n  /*\n  font-smooth: never;\n  font-smoothing: unset;\n  -webkit-font-smoothing: none;\n  -moz-osx-font-smoothing: unset;\n  */\n}\n"
  },
  {
    "path": "contrib/hlp2html/style.css",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * style.css: Embeddable formatting CSS for HTML help file generation.\n */\n\nbody\n{\n  margin: 0px;\n}\n\n#helpnav\n{\n  font-family: Arial, Helvetica, sans-serif;\n  width: 100%;\n  height: 5em;\n  padding: .5em;\n  padding-left: 0px;\n  padding-right: 0px;\n  border-bottom: 5px solid;\n  text-align: center;\n\n  /* Floating navigation bar. */\n  position: fixed;\n  top: 0;\n}\n\n#helpnav h1\n{\n  display: block;\n  margin: .5em;\n  padding: 0px;\n  font-size: 1.5em;\n}\n\n.helpnavcentered\n{\n  display: inline;\n  text-align: center;\n}\n\n.helpnavcentered ul\n{\n  padding: 0px;\n  margin: .25em;\n}\n\n.helpnavcentered li\n{\n  display: inline-block;\n  margin: 1px;\n  list-style-type: none;\n  text-align: center;\n  font-size: .95em;\n}\n\n.helpnavcentered li a\n{\n  background-color: #222;\n  border-color: #222;\n  border-radius: 4px 4px;\n  -moz-border-radius: 4px 4px;\n  padding: 3px 5px;\n  color: #FFF;\n  text-decoration: none;\n}\n\n.helpnavcentered li a:hover\n{\n  background-color: #666;\n  border-color: #666;\n}\n\n#helpcontainer\n{\n  font-family: 'mzxfont';\n  width: 512px;\n  height: auto;\n  padding: 3px 76px;\n  margin: auto;\n  overflow: auto;\n  white-space: pre;\n  line-height: 14px;\n  font-size: 14px;\n\n  /* Extra top padding to help fix anchors. */\n  padding-top: 6em;\n}\n\n/* Help file div. */\n.hF\n{\n  page-break-after: always;\n}\n\n.hF hr\n{\n  background-color: #000;\n  height: 2px;\n  margin: 14px 8px;\n  border: none;\n}\n\n/* Help file centered text. */\n.hC\n{\n  display: block;\n  text-align: center;\n  margin: 0px;\n}\n\n/* Help file anchor (link destination). */\n.hA\n{\n  /* This forces the page to jump above the anchor so it doesn't display\n   * under the navigation bar. */\n  padding-top: 8em;\n  margin-top: -8em;\n\n  /* This is a fix for the fix above so the now gigantic anchor doesn't\n   * cover links up. */\n  pointer-events: none;\n}\n\n/* Help file link. */\n.hL\n{\n  text-decoration: none;\n}\n\n.hL:before\n{\n  padding-right: 8px;\n  font-family: 'mzxfont';\n  content: \"\\E010\";\n}\n\n@media print\n{\n  #helpnav\n  {\n    /* Disable the floating navigation bar in print mode. */\n    position: absolute;\n    border-bottom: 3px solid;\n  }\n\n  .helpnavcentered\n  {\n    /* Hide navigation buttons when in print mode. */\n    display: none;\n  }\n}\n\n@media only screen and (max-width: 680px),\n       only screen and (max-device-width: 1080px)\n{\n  /* Disable big margins on small windows, small displays, or phones. */\n\n  #helpcontainer\n  {\n    padding: 3px 8px;\n    padding-top: 6em;\n  }\n}\n\n@media only screen and (max-device-width: 800px)\n{\n  /* Probably a phone. Scale up navigation and text. */\n\n  #helpnav\n  {\n    height: 7em;\n  }\n\n  #helpnav h1\n  {\n    font-size: 2em;\n  }\n\n  .helpnavcentered li\n  {\n    font-size: 1.5em;\n  }\n\n  #helpcontainer\n  {\n    width: 768px;\n    line-height: 21px;\n    font-size: 21px;\n  }\n}\n\n@media only screen and (max-device-width: 1080px)\n{\n  /* Probably a phone. Scale up navigation and text. */\n\n  #helpnav\n  {\n    height: 8em;\n  }\n\n  #helpnav h1\n  {\n    font-size: 2.5em;\n  }\n\n  .helpnavcentered li\n  {\n    font-size: 1.75em;\n  }\n\n  #helpcontainer\n  {\n    width: 960px;\n    line-height: 26px;\n    font-size: 26px;\n  }\n}\n"
  },
  {
    "path": "contrib/hlp2html/style_color.css",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * style_color.css: Embeddable color CSS for HTML help files.\n * Should only style outside of printing mode.\n *\n * TODO: possibly invert the query here.\n */\n\n@media not print\n{\n  body\n  {\n    background-color: #333;\n  }\n\n  #helpnav\n  {\n    background-color: #444;\n    color: #FFF;\n    border-bottom-color: #333;\n  }\n\n  #helpcontainer\n  {\n    background-color: #555;\n    color: #FFF;\n  }\n\n  .hL { color: #FFFFFF; }\n  .hL:before { color: #FFFF55; }\n  .hL:hover { color: #FF55FF; }\n\n  .f0 { color: #000000; }\n  .f1 { color: #0000AA; }\n  .f2 { color: #00AA00; }\n  .f3 { color: #00AAAA; }\n  .f4 { color: #AA0000; }\n  .f5 { color: #AA00AA; }\n  .f6 { color: #AA5500; }\n  .f7 { color: #AAAAAA; }\n  .f8 { color: #555555; }\n  .f9 { color: #7777FF; } /* Should be #5555FF, but that's ugly on dk grey. */\n  .fa, .fA { color: #55FF55; }\n  .fb, .fB { color: #55FFFF; }\n  .fc, .fC { color: #FF5555; }\n  .fd, .fD { color: #FF55FF; }\n  .fe, .fE { color: #FFFF55; }\n  .ff, .fF { color: #FFFFFF; }\n\n  .b0 { background-color: #000000; }\n  .b1 { background-color: #0000AA; }\n  .b2 { background-color: #00AA00; }\n  .b3 { background-color: #00AAAA; }\n  .b4 { background-color: #AA0000; }\n  .b5 { background-color: #AA00AA; }\n  .b6 { background-color: #AA5500; }\n  .b7 { background-color: #AAAAAA; }\n  .b8 { background-color: #555555; }\n  .b9 { background-color: #5555FF; }\n  .ba, .bA { background-color: #55FF55; }\n  .bb, .bB { background-color: #55FFFF; }\n  .bc, .bC { background-color: #FF5555; }\n  .bd, .bD { background-color: #FF55FF; }\n  .be, .bE { background-color: #FFFF55; }\n  .bf, .bF { background-color: #FFFFFF; }\n}\n"
  },
  {
    "path": "contrib/icons/Makefile.in",
    "content": "##\n# icons Makefile fragment\n##\n\n.PHONY: icons_clean icons_rebuild\n\n.SUFFIXES: .rc\n\nicons_src = contrib/icons\nicons_obj = contrib/icons/.build\nicons_gen = contrib/icons/generated\nicons_windows = ${icons_gen}\nicons_nds = ${icons_gen}\nicons_wii = ${icons_gen}\nicons_wiiu = ${icons_gen}\nicons_switch = ${icons_gen}\nicons_psp = ${icons_gen}\nicons_vita = arch/psvita/sce_sys\nicons_emscripten = arch/emscripten/web/res\nicons_android = arch/android/project/app/src/main/res\n\nicon_svg = ${icons_src}/icon.svg\nicon_name_svg = ${icons_src}/icon-name.svg\n\nicons_pngs = \\\n\t${icons_gen}/icon_16.png \\\n\t${icons_gen}/icon_20.png \\\n\t${icons_gen}/icon_22.png \\\n\t${icons_gen}/icon_24.png \\\n\t${icons_gen}/icon_30.png \\\n\t${icons_gen}/icon_32.png \\\n\t${icons_gen}/icon_36.png \\\n\t${icons_gen}/icon_40.png \\\n\t${icons_gen}/icon_48.png \\\n\t${icons_gen}/icon_60.png \\\n\t${icons_gen}/icon_64.png \\\n\t${icons_gen}/icon_72.png \\\n\t${icons_gen}/icon_80.png \\\n\t${icons_gen}/icon_96.png \\\n\t${icons_gen}/icon_128.png \\\n\t${icons_gen}/icon_144.png \\\n\t${icons_gen}/icon_192.png \\\n\t${icons_gen}/icon_256.png \\\n\t${icons_gen}/icon_256.jpg \\\n\t${icons_gen}/icon_512.png \\\n\t${icons_gen}/icon_1024.png \\\n\t${icons_windows}/icon_win_32.png \\\n\t${icons_windows}/icon_win_48.png \\\n\t${icons_windows}/icon_win_16_8bpp.png \\\n\t${icons_windows}/icon_win_32_8bpp.png \\\n\t${icons_windows}/icon_win_48_8bpp.png \\\n\t${icons_windows}/icon.ico \\\n\t${icons_nds}/icon_nds.bmp \\\n\t${icons_nds}/icon_nds.png \\\n\t${icons_wii}/icon_wii.png \\\n\t${icons_wiiu}/icon_wiiu.png \\\n\t${icons_switch}/icon_switch.jpg \\\n\t${icons_psp}/icon_psp.png \\\n\t${icons_vita}/icon0.png \\\n\t${icons_vita}/livearea/contents/startup.png \\\n\t${icons_emscripten}/loading.png \\\n\t${icons_android}/mipmap-mdpi/ic_launcher.png \\\n\t${icons_android}/mipmap-hdpi/ic_launcher.png \\\n\t${icons_android}/mipmap-xhdpi/ic_launcher.png \\\n\t${icons_android}/mipmap-xxhdpi/ic_launcher.png \\\n\t${icons_android}/mipmap-xxxhdpi/ic_launcher.png \\\n\nicons = ${icons_obj}/icons.o\n\nicons_objs = ${icons_obj}/icon.o\n\n${icons_obj}/%.o: ${icons_src}/%.rc\n\t$(if ${V},,@echo \"  WINDRES \" $<)\n\t${WINDRES} -i $< -o $@\n\nifeq (${EMBED_ICONS},1)\n${icons}: ${icons_obj} ${icons_objs}\n\t$(if ${V},,@echo \"  CP      \" ${icons_objs} ${icons})\n\t${CP} ${icons_objs} ${icons}\nelse\n${icons}:\n\t@echo \"--> Embedding of icon branding disabled.\"\nendif\n\n${icons_objs}: $(filter-out $(wildcard ${icons_obj}), ${icons_obj})\n\nicons_clean:\n\t$(if ${V},,@echo \"  RM      \" ${icons_obj})\n\t${RM} -r ${icons_obj}\n\n#\n# Rebuild raster icons from SVG.\n#\nicons_rebuild: ${icons_pngs}\n\n${icons_pngs}: contrib/icons/Makefile.in\n\nMAGICK ?= magick\nifneq ($V,1)\nMAGICK := @${MAGICK}\nendif\nMAGICK += -define png:compression-level=9\n\n# Base icon has a nominal size of 128px with 8px minimum margins.\n# This meets KDE's Colorful guidelines for 32px (2px margin) and\n# 64px (4px margin) but not 48px (4px margin), so scale down slightly\n# for 48px.\n\n# Expected DPI 12, but the icon looks better when scaled to ~1x.\n# Icon is 96 DPI with the smiley scaled 9x -> want approx 10.667\n${icons_gen}/icon_16.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 10.667 \"$<\" \\\n\t  -gravity center -extent 16x16 png32:\"$@\"\n\n${icons_gen}/icon_20.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 15 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_22.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 16.5 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_24.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 18 \"$<\" png32:\"$@\"\n\n# Expected DPI 22.5. Slightly downscale to get nicer filtering.\n${icons_gen}/icon_30.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 21.333 \"$<\" \\\n\t  -gravity center -extent 30x30 png32:\"$@\"\n\n# DPI 25 has nicer filtering but the 2px border is required by Colorful.\n${icons_gen}/icon_32.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 24 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_36.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 27 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_40.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 30 \"$<\" png32:\"$@\"\n\n# Expected DPI 36, use 33 (44px) to increase margin to >=Colorful spec.\n${icons_gen}/icon_48.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 33 \"$<\" \\\n\t  -gravity center -extent 48x48 png32:\"$@\"\n\n${icons_gen}/icon_60.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 45 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_64.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 48 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_72.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 54 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_80.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 60 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_96.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 72 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_128.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 96 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_144.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 108 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_192.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 144 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_256.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 192 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_512.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 384 \"$<\" png32:\"$@\"\n\n${icons_gen}/icon_1024.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 768 \"$<\" png32:\"$@\"\n\n#\n# Windows: combine a variety of sizes into a single .ico.\n# MSDN lists: 16, 20, 24, 30, 32, 36, 40, 48, 60, 64, 72, 80, 96, 256\n# The minimum set is supposed to be 16, 24, 32, 48, and 256, but\n# these need to be explicitly RGBA or Windows XP onward will pick\n# the wrong icons and resize them. (Include 8bpp too just in case.)\n#\nicons_in_ico = \\\n\t${icons_gen}/icon_256.png \\\n\t${icons_windows}/icon_win_48.png \\\n\t${icons_windows}/icon_win_32.png \\\n\t${icons_windows}/icon_24.png \\\n\t${icons_windows}/icon_16.png \\\n\t${icons_windows}/icon_win_48_8bpp.png \\\n\t${icons_windows}/icon_win_32_8bpp.png \\\n\t${icons_windows}/icon_win_16_8bpp.png\n\n${icons_windows}/icon_win_16_8bpp.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 10.667 \"$<\" \\\n\t  -gravity center -extent 16x16 -colors 256 png8:\"$@\"\n\n# Expected DPI 24, use 25 for nicer scaling.\n${icons_windows}/icon_win_32.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 25 \"$<\" \\\n\t  -gravity center -extent 32x32 png32:\"$@\"\n\n${icons_windows}/icon_win_32_8bpp.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 25 \"$<\" \\\n\t  -gravity center -extent 32x32 -colors 256 png8:\"$@\"\n\n# Expected DPI 36, scale up as much as possible to help fix downscales.\n# This clips slightly but not in an especially noticeable way.\n${icons_windows}/icon_win_48.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 40 \"$<\" \\\n\t  -gravity center -extent 48x48 png32:\"$@\"\n\n${icons_windows}/icon_win_48_8bpp.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 40 \"$<\" \\\n\t  -gravity center -extent 48x48 -colors 256 png8:\"$@\"\n\n${icons_windows}/icon.ico: ${icons_in_ico}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} ${icons_in_ico} $@\n\n#\n# NDS: icons are 32px 16 colors.\n# Expected DPI 24, but 25 has nicer filtering properties.\n# The BMP used by the original ndstool uses index 0 for alpha\n# and for some reason is hardcoded to the BITMAPINFOHEADER DIB.\n# The PNG used by the BlocksDS nsdtool uses a transparent color.\n#\n${icons_nds}/icon_nds.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 25 \"$<\" \\\n\t  -gravity center -extent 32x32 -colors 16 \"$@\"\n\n${icons_nds}/icon_nds.bmp: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background black -density 25 \"$<\" \\\n\t  -gravity center -extent 32x32 -colors 16 -type palette BMP3:\"$@\"\n\n#\n# Wii: icons are 128x48.\n# Compose \"MegaZeux\" onto the main icon.\n# Drop shadow needed to provide bordering in Homebrew Channel.\n#\n${icons_wii}/icon_wii.png: ${icon_svg} ${icon_name_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none \\\n\t  -density 36            \"${icon_svg}\" \\\n\t  \\( +clone -background black -shadow 100x4+0+0 \\) \\\n\t  +swap -background none -layers merge +repage \\\n\t  -gravity center        -extent  40x48 \\\n\t  -gravity west          -extent 128x48 \\\n\t  \\( -filter point \\\n\t     -density 144        \"${icon_name_svg}\" \\\n\t     -gravity center     -extent 98x44 \\\n\t     -gravity southeast  -extent 128x48 \\) \\\n\t  -layers merge +repage  \"$@\"\n\n#\n# Wii U: icons are 256x96.\n# Compose \"MegaZeux\" onto the main icon.\n#\n${icons_wiiu}/icon_wiiu.png: ${icon_svg} ${icon_name_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none \\\n\t  -density 72            \"${icon_svg}\" \\\n\t  \\( +clone -background black -shadow 100x4+0+0 \\) \\\n\t  +swap -background none -layers merge +repage \\\n\t  -gravity center        -extent  76x96 \\\n\t  -gravity west          -extent 256x96 \\\n\t  \\( -density 288        \"${icon_name_svg}\" \\\n\t     -gravity center     -extent 197x88 \\\n\t     -gravity southeast  -extent 256x96 \\) \\\n\t  -layers merge +repage  \"$@\"\n\n#\n# Switch: icon is the standard 256px, except is a JPEG for some reason.\n#\n${icons_switch}/icon_switch.jpg: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background \"#000080\" -density 192 \"$<\" \"$@\"\n\n#\n# PSP: icons are 144x80.\n# Background is blue, give it a drop shadow.\n#\n${icons_psp}/icon_psp.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 60 \"$<\" \\\n\t  \\( +clone -background black -shadow 100x4+0+0 \\) \\\n\t  +swap -background none -layers merge +repage \\\n\t  -gravity center -extent 144x80 \"$@\"\n\n#\n# PlayStation Vita: several of these, generate directly into arch tree.\n# This icon needs to be 128px, downscaled to work around the\n# forced circle crop, and needs a solid color background as\n# the stock Vita shell does not support transparency (Vita3K does).\n#\n${icons_vita}/icon0.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background none -density 78 \"$<\" \\\n\t  \\( +clone -background black -shadow 100x6+0+0 \\) \\\n\t  +swap -background '#000040' -layers merge +repage \\\n\t  -gravity center -extent 128x128 \"$@\"\n\n# Vita launch screen logo image, 280x158, transparency allowed.\n${icons_vita}/livearea/contents/startup.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -background '#202024' -density 81 \"$<\" \\\n\t  -gravity north -extent 280x140 \\\n\t  -gravity center -extent 280x158 \"$@\"\n\n#\n# Emscripten: no real \"icon\" but the loading splash uses the icon.\n#\n${icons_emscripten}/loading.png: ${icon_svg}\n\t$(if ${V},,@echo \"  MAGICK  \" $@)\n\t${MAGICK} -size 640x350 xc:black \\\n\t  -fill '#aaaaaa' -draw 'rectangle 25,221 614,246' \\\n\t  -fill black     -draw 'rectangle 26,222 613,245' \\\n\t  \\( -background none -density 96 \"$<\"   \\\n\t     -gravity center  -extent 640x220    \\\n\t     -gravity north   -extent 640x350 \\) \\\n\t  -layers merge +repage png24:\"$@\"\n\n#\n# Android: icons are standard sizes, just copy them into project tree.\n#\n${icons_android}/mipmap-mdpi/ic_launcher.png: ${icons_gen}/icon_48.png\n\t$(if ${V},,@echo \"  CP      \" $< $@)\n\t${CP} \"$<\" \"$@\"\n\n${icons_android}/mipmap-hdpi/ic_launcher.png: ${icons_gen}/icon_72.png\n\t$(if ${V},,@echo \"  CP      \" $< $@)\n\t${CP} \"$<\" \"$@\"\n\n${icons_android}/mipmap-xhdpi/ic_launcher.png: ${icons_gen}/icon_96.png\n\t$(if ${V},,@echo \"  CP      \" $< $@)\n\t${CP} \"$<\" \"$@\"\n\n${icons_android}/mipmap-xxhdpi/ic_launcher.png: ${icons_gen}/icon_144.png\n\t$(if ${V},,@echo \"  CP      \" $< $@)\n\t${CP} \"$<\" \"$@\"\n\n${icons_android}/mipmap-xxxhdpi/ic_launcher.png: ${icons_gen}/icon_192.png\n\t$(if ${V},,@echo \"  CP      \" $< $@)\n\t${CP} \"$<\" \"$@\"\n"
  },
  {
    "path": "contrib/icons/icon.rc",
    "content": "1 ICON \"contrib/icons/generated/icon.ico\"\n"
  },
  {
    "path": "contrib/icons/old/logicow/ghostblue.rc",
    "content": "1 ICON \"contrib/icons/ghostblue.ico\"\n"
  },
  {
    "path": "contrib/icons/old/logicow/ghostred.rc",
    "content": "1 ICON \"contrib/icons/ghostred.ico\"\n"
  },
  {
    "path": "contrib/icons/old/quantump/quantump.rc",
    "content": "1 ICON \"contrib/icons/quantump.ico\"\n"
  },
  {
    "path": "contrib/infback9/LICENSE",
    "content": "/* zlib.h -- interface of the 'zlib' general purpose compression library\n  version 1.2.11, January 15th, 2017\n\n  Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler\n\n  This software is provided 'as-is', without any express or implied\n  warranty.  In no event will the authors be held liable for any damages\n  arising from the use of this software.\n\n  Permission is granted to anyone to use this software for any purpose,\n  including commercial applications, and to alter it and redistribute it\n  freely, subject to the following restrictions:\n\n  1. The origin of this software must not be misrepresented; you must not\n     claim that you wrote the original software. If you use this software\n     in a product, an acknowledgment in the product documentation would be\n     appreciated but is not required.\n  2. Altered source versions must be plainly marked as such, and must not be\n     misrepresented as being the original software.\n  3. This notice may not be removed or altered from any source distribution.\n\n  Jean-loup Gailly        Mark Adler\n  jloup@gzip.org          madler@alumni.caltech.edu\n\n\n  The data format used by the zlib library is described by RFCs (Request for\n  Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950\n  (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format).\n*/\n\n"
  },
  {
    "path": "contrib/infback9/README",
    "content": "See infback9.h for what this is and how to use it.\n\nOriginal code available here (zlib 1.3):\nhttps://github.com/madler/zlib/tree/09155eaa2f9270dc4ed1fa13e2b4b2613e6e4851/contrib/infback9\n\nThis copy of zlib 1.3's unsupported infback9 code has been modified as follows:\n* Added this notice to README.\n* Added \"LICENSE\" (the license section of zlib.h) from the main zlib code.\n* Added a stripped-down copy of zutil.h sufficient to build infback9 against.\n* Added include guards to headers.\n* Fixed a fall-through warning.\n* Replaced references to zcalloc and zcfree with inline implementations. This\n  is due to older versions of zlib seemingly not exposing these.\n* Removed OF() wrapper for function declarations (unnecessary).\n"
  },
  {
    "path": "contrib/infback9/infback9.c",
    "content": "/* infback9.c -- inflate deflate64 data using a call-back interface\n * Copyright (C) 1995-2008 Mark Adler\n * For conditions of distribution and use, see copyright notice in zlib.h\n */\n\n#include \"zutil.h\"\n#include \"infback9.h\"\n#include \"inftree9.h\"\n#include \"inflate9.h\"\n\n#define WSIZE 65536UL\n\n// Older versions of zlib may not expose zcalloc and zcfree, causing linking\n// to fail. Replace them with wrappers for calloc and stdlib free...\nstatic inline voidpf _zcalloc(voidpf opaque, unsigned nmemb, unsigned size)\n{ return (voidpf)calloc(nmemb, size); }\nstatic inline void _zcfree(voidpf opaque, voidpf ptr) { free(ptr); }\n\n/*\n   strm provides memory allocation functions in zalloc and zfree, or\n   Z_NULL to use the library memory allocation functions.\n\n   window is a user-supplied window and output buffer that is 64K bytes.\n */\nint ZEXPORT inflateBack9Init_(z_stream FAR *strm, unsigned char FAR *window,\n                              const char *version, int stream_size) {\n    struct inflate_state FAR *state;\n\n    if (version == Z_NULL || version[0] != ZLIB_VERSION[0] ||\n        stream_size != (int)(sizeof(z_stream)))\n        return Z_VERSION_ERROR;\n    if (strm == Z_NULL || window == Z_NULL)\n        return Z_STREAM_ERROR;\n    strm->msg = Z_NULL;                 /* in case we return an error */\n    if (strm->zalloc == (alloc_func)0) {\n        strm->zalloc = _zcalloc;\n        strm->opaque = (voidpf)0;\n    }\n    if (strm->zfree == (free_func)0) strm->zfree = _zcfree;\n    state = (struct inflate_state FAR *)ZALLOC(strm, 1,\n                                               sizeof(struct inflate_state));\n    if (state == Z_NULL) return Z_MEM_ERROR;\n    Tracev((stderr, \"inflate: allocated\\n\"));\n    strm->state = (voidpf)state;\n    state->window = window;\n    return Z_OK;\n}\n\n/*\n   Build and output length and distance decoding tables for fixed code\n   decoding.\n */\n#ifdef MAKEFIXED\n#include <stdio.h>\n\nvoid makefixed9(void) {\n    unsigned sym, bits, low, size;\n    code *next, *lenfix, *distfix;\n    struct inflate_state state;\n    code fixed[544];\n\n    /* literal/length table */\n    sym = 0;\n    while (sym < 144) state.lens[sym++] = 8;\n    while (sym < 256) state.lens[sym++] = 9;\n    while (sym < 280) state.lens[sym++] = 7;\n    while (sym < 288) state.lens[sym++] = 8;\n    next = fixed;\n    lenfix = next;\n    bits = 9;\n    inflate_table9(LENS, state.lens, 288, &(next), &(bits), state.work);\n\n    /* distance table */\n    sym = 0;\n    while (sym < 32) state.lens[sym++] = 5;\n    distfix = next;\n    bits = 5;\n    inflate_table9(DISTS, state.lens, 32, &(next), &(bits), state.work);\n\n    /* write tables */\n    puts(\"    /* inffix9.h -- table for decoding deflate64 fixed codes\");\n    puts(\"     * Generated automatically by makefixed9().\");\n    puts(\"     */\");\n    puts(\"\");\n    puts(\"    /* WARNING: this file should *not* be used by applications.\");\n    puts(\"       It is part of the implementation of this library and is\");\n    puts(\"       subject to change. Applications should only use zlib.h.\");\n    puts(\"     */\");\n    puts(\"\");\n    size = 1U << 9;\n    printf(\"    static const code lenfix[%u] = {\", size);\n    low = 0;\n    for (;;) {\n        if ((low % 6) == 0) printf(\"\\n        \");\n        printf(\"{%u,%u,%d}\", lenfix[low].op, lenfix[low].bits,\n               lenfix[low].val);\n        if (++low == size) break;\n        putchar(',');\n    }\n    puts(\"\\n    };\");\n    size = 1U << 5;\n    printf(\"\\n    static const code distfix[%u] = {\", size);\n    low = 0;\n    for (;;) {\n        if ((low % 5) == 0) printf(\"\\n        \");\n        printf(\"{%u,%u,%d}\", distfix[low].op, distfix[low].bits,\n               distfix[low].val);\n        if (++low == size) break;\n        putchar(',');\n    }\n    puts(\"\\n    };\");\n}\n#endif /* MAKEFIXED */\n\n/* Macros for inflateBack(): */\n\n/* Clear the input bit accumulator */\n#define INITBITS() \\\n    do { \\\n        hold = 0; \\\n        bits = 0; \\\n    } while (0)\n\n/* Assure that some input is available.  If input is requested, but denied,\n   then return a Z_BUF_ERROR from inflateBack(). */\n#define PULL() \\\n    do { \\\n        if (have == 0) { \\\n            have = in(in_desc, &next); \\\n            if (have == 0) { \\\n                next = Z_NULL; \\\n                ret = Z_BUF_ERROR; \\\n                goto inf_leave; \\\n            } \\\n        } \\\n    } while (0)\n\n/* Get a byte of input into the bit accumulator, or return from inflateBack()\n   with an error if there is no input available. */\n#define PULLBYTE() \\\n    do { \\\n        PULL(); \\\n        have--; \\\n        hold += (unsigned long)(*next++) << bits; \\\n        bits += 8; \\\n    } while (0)\n\n/* Assure that there are at least n bits in the bit accumulator.  If there is\n   not enough available input to do that, then return from inflateBack() with\n   an error. */\n#define NEEDBITS(n) \\\n    do { \\\n        while (bits < (unsigned)(n)) \\\n            PULLBYTE(); \\\n    } while (0)\n\n/* Return the low n bits of the bit accumulator (n <= 16) */\n#define BITS(n) \\\n    ((unsigned)hold & ((1U << (n)) - 1))\n\n/* Remove n bits from the bit accumulator */\n#define DROPBITS(n) \\\n    do { \\\n        hold >>= (n); \\\n        bits -= (unsigned)(n); \\\n    } while (0)\n\n/* Remove zero to seven bits as needed to go to a byte boundary */\n#define BYTEBITS() \\\n    do { \\\n        hold >>= bits & 7; \\\n        bits -= bits & 7; \\\n    } while (0)\n\n/* Assure that some output space is available, by writing out the window\n   if it's full.  If the write fails, return from inflateBack() with a\n   Z_BUF_ERROR. */\n#define ROOM() \\\n    do { \\\n        if (left == 0) { \\\n            put = window; \\\n            left = WSIZE; \\\n            wrap = 1; \\\n            if (out(out_desc, put, (unsigned)left)) { \\\n                ret = Z_BUF_ERROR; \\\n                goto inf_leave; \\\n            } \\\n        } \\\n    } while (0)\n\n/*\n   strm provides the memory allocation functions and window buffer on input,\n   and provides information on the unused input on return.  For Z_DATA_ERROR\n   returns, strm will also provide an error message.\n\n   in() and out() are the call-back input and output functions.  When\n   inflateBack() needs more input, it calls in().  When inflateBack() has\n   filled the window with output, or when it completes with data in the\n   window, it calls out() to write out the data.  The application must not\n   change the provided input until in() is called again or inflateBack()\n   returns.  The application must not change the window/output buffer until\n   inflateBack() returns.\n\n   in() and out() are called with a descriptor parameter provided in the\n   inflateBack() call.  This parameter can be a structure that provides the\n   information required to do the read or write, as well as accumulated\n   information on the input and output such as totals and check values.\n\n   in() should return zero on failure.  out() should return non-zero on\n   failure.  If either in() or out() fails, than inflateBack() returns a\n   Z_BUF_ERROR.  strm->next_in can be checked for Z_NULL to see whether it\n   was in() or out() that caused in the error.  Otherwise,  inflateBack()\n   returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format\n   error, or Z_MEM_ERROR if it could not allocate memory for the state.\n   inflateBack() can also return Z_STREAM_ERROR if the input parameters\n   are not correct, i.e. strm is Z_NULL or the state was not initialized.\n */\nint ZEXPORT inflateBack9(z_stream FAR *strm, in_func in, void FAR *in_desc,\n                         out_func out, void FAR *out_desc) {\n    struct inflate_state FAR *state;\n    z_const unsigned char FAR *next;    /* next input */\n    unsigned char FAR *put;     /* next output */\n    unsigned have;              /* available input */\n    unsigned long left;         /* available output */\n    inflate_mode mode;          /* current inflate mode */\n    int lastblock;              /* true if processing last block */\n    int wrap;                   /* true if the window has wrapped */\n    unsigned char FAR *window;  /* allocated sliding window, if needed */\n    unsigned long hold;         /* bit buffer */\n    unsigned bits;              /* bits in bit buffer */\n    unsigned extra;             /* extra bits needed */\n    unsigned long length;       /* literal or length of data to copy */\n    unsigned long offset;       /* distance back to copy string from */\n    unsigned long copy;         /* number of stored or match bytes to copy */\n    unsigned char FAR *from;    /* where to copy match bytes from */\n    code const FAR *lencode;    /* starting table for length/literal codes */\n    code const FAR *distcode;   /* starting table for distance codes */\n    unsigned lenbits;           /* index bits for lencode */\n    unsigned distbits;          /* index bits for distcode */\n    code here;                  /* current decoding table entry */\n    code last;                  /* parent table entry */\n    unsigned len;               /* length to copy for repeats, bits to drop */\n    int ret;                    /* return code */\n    static const unsigned short order[19] = /* permutation of code lengths */\n        {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};\n#include \"inffix9.h\"\n\n    /* Check that the strm exists and that the state was initialized */\n    if (strm == Z_NULL || strm->state == Z_NULL)\n        return Z_STREAM_ERROR;\n    state = (struct inflate_state FAR *)strm->state;\n\n    /* Reset the state */\n    strm->msg = Z_NULL;\n    mode = TYPE;\n    lastblock = 0;\n    wrap = 0;\n    window = state->window;\n    next = strm->next_in;\n    have = next != Z_NULL ? strm->avail_in : 0;\n    hold = 0;\n    bits = 0;\n    put = window;\n    left = WSIZE;\n    lencode = Z_NULL;\n    distcode = Z_NULL;\n\n    /* Inflate until end of block marked as last */\n    for (;;)\n        switch (mode) {\n        case TYPE:\n            /* determine and dispatch block type */\n            if (lastblock) {\n                BYTEBITS();\n                mode = DONE;\n                break;\n            }\n            NEEDBITS(3);\n            lastblock = BITS(1);\n            DROPBITS(1);\n            switch (BITS(2)) {\n            case 0:                             /* stored block */\n                Tracev((stderr, \"inflate:     stored block%s\\n\",\n                        lastblock ? \" (last)\" : \"\"));\n                mode = STORED;\n                break;\n            case 1:                             /* fixed block */\n                lencode = lenfix;\n                lenbits = 9;\n                distcode = distfix;\n                distbits = 5;\n                Tracev((stderr, \"inflate:     fixed codes block%s\\n\",\n                        lastblock ? \" (last)\" : \"\"));\n                mode = LEN;                     /* decode codes */\n                break;\n            case 2:                             /* dynamic block */\n                Tracev((stderr, \"inflate:     dynamic codes block%s\\n\",\n                        lastblock ? \" (last)\" : \"\"));\n                mode = TABLE;\n                break;\n            case 3:\n                strm->msg = (char *)\"invalid block type\";\n                mode = BAD;\n            }\n            DROPBITS(2);\n            break;\n\n        case STORED:\n            /* get and verify stored block length */\n            BYTEBITS();                         /* go to byte boundary */\n            NEEDBITS(32);\n            if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) {\n                strm->msg = (char *)\"invalid stored block lengths\";\n                mode = BAD;\n                break;\n            }\n            length = (unsigned)hold & 0xffff;\n            Tracev((stderr, \"inflate:       stored length %lu\\n\",\n                    length));\n            INITBITS();\n\n            /* copy stored block from input to output */\n            while (length != 0) {\n                copy = length;\n                PULL();\n                ROOM();\n                if (copy > have) copy = have;\n                if (copy > left) copy = left;\n                zmemcpy(put, next, copy);\n                have -= copy;\n                next += copy;\n                left -= copy;\n                put += copy;\n                length -= copy;\n            }\n            Tracev((stderr, \"inflate:       stored end\\n\"));\n            mode = TYPE;\n            break;\n\n        case TABLE:\n            /* get dynamic table entries descriptor */\n            NEEDBITS(14);\n            state->nlen = BITS(5) + 257;\n            DROPBITS(5);\n            state->ndist = BITS(5) + 1;\n            DROPBITS(5);\n            state->ncode = BITS(4) + 4;\n            DROPBITS(4);\n            if (state->nlen > 286) {\n                strm->msg = (char *)\"too many length symbols\";\n                mode = BAD;\n                break;\n            }\n            Tracev((stderr, \"inflate:       table sizes ok\\n\"));\n\n            /* get code length code lengths (not a typo) */\n            state->have = 0;\n            while (state->have < state->ncode) {\n                NEEDBITS(3);\n                state->lens[order[state->have++]] = (unsigned short)BITS(3);\n                DROPBITS(3);\n            }\n            while (state->have < 19)\n                state->lens[order[state->have++]] = 0;\n            state->next = state->codes;\n            lencode = (code const FAR *)(state->next);\n            lenbits = 7;\n            ret = inflate_table9(CODES, state->lens, 19, &(state->next),\n                                &(lenbits), state->work);\n            if (ret) {\n                strm->msg = (char *)\"invalid code lengths set\";\n                mode = BAD;\n                break;\n            }\n            Tracev((stderr, \"inflate:       code lengths ok\\n\"));\n\n            /* get length and distance code code lengths */\n            state->have = 0;\n            while (state->have < state->nlen + state->ndist) {\n                for (;;) {\n                    here = lencode[BITS(lenbits)];\n                    if ((unsigned)(here.bits) <= bits) break;\n                    PULLBYTE();\n                }\n                if (here.val < 16) {\n                    NEEDBITS(here.bits);\n                    DROPBITS(here.bits);\n                    state->lens[state->have++] = here.val;\n                }\n                else {\n                    if (here.val == 16) {\n                        NEEDBITS(here.bits + 2);\n                        DROPBITS(here.bits);\n                        if (state->have == 0) {\n                            strm->msg = (char *)\"invalid bit length repeat\";\n                            mode = BAD;\n                            break;\n                        }\n                        len = (unsigned)(state->lens[state->have - 1]);\n                        copy = 3 + BITS(2);\n                        DROPBITS(2);\n                    }\n                    else if (here.val == 17) {\n                        NEEDBITS(here.bits + 3);\n                        DROPBITS(here.bits);\n                        len = 0;\n                        copy = 3 + BITS(3);\n                        DROPBITS(3);\n                    }\n                    else {\n                        NEEDBITS(here.bits + 7);\n                        DROPBITS(here.bits);\n                        len = 0;\n                        copy = 11 + BITS(7);\n                        DROPBITS(7);\n                    }\n                    if (state->have + copy > state->nlen + state->ndist) {\n                        strm->msg = (char *)\"invalid bit length repeat\";\n                        mode = BAD;\n                        break;\n                    }\n                    while (copy--)\n                        state->lens[state->have++] = (unsigned short)len;\n                }\n            }\n\n            /* handle error breaks in while */\n            if (mode == BAD) break;\n\n            /* check for end-of-block code (better have one) */\n            if (state->lens[256] == 0) {\n                strm->msg = (char *)\"invalid code -- missing end-of-block\";\n                mode = BAD;\n                break;\n            }\n\n            /* build code tables -- note: do not change the lenbits or distbits\n               values here (9 and 6) without reading the comments in inftree9.h\n               concerning the ENOUGH constants, which depend on those values */\n            state->next = state->codes;\n            lencode = (code const FAR *)(state->next);\n            lenbits = 9;\n            ret = inflate_table9(LENS, state->lens, state->nlen,\n                            &(state->next), &(lenbits), state->work);\n            if (ret) {\n                strm->msg = (char *)\"invalid literal/lengths set\";\n                mode = BAD;\n                break;\n            }\n            distcode = (code const FAR *)(state->next);\n            distbits = 6;\n            ret = inflate_table9(DISTS, state->lens + state->nlen,\n                            state->ndist, &(state->next), &(distbits),\n                            state->work);\n            if (ret) {\n                strm->msg = (char *)\"invalid distances set\";\n                mode = BAD;\n                break;\n            }\n            Tracev((stderr, \"inflate:       codes ok\\n\"));\n            mode = LEN;\n            // fall-through\n\n        case LEN:\n            /* get a literal, length, or end-of-block code */\n            for (;;) {\n                here = lencode[BITS(lenbits)];\n                if ((unsigned)(here.bits) <= bits) break;\n                PULLBYTE();\n            }\n            if (here.op && (here.op & 0xf0) == 0) {\n                last = here;\n                for (;;) {\n                    here = lencode[last.val +\n                            (BITS(last.bits + last.op) >> last.bits)];\n                    if ((unsigned)(last.bits + here.bits) <= bits) break;\n                    PULLBYTE();\n                }\n                DROPBITS(last.bits);\n            }\n            DROPBITS(here.bits);\n            length = (unsigned)here.val;\n\n            /* process literal */\n            if (here.op == 0) {\n                Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?\n                        \"inflate:         literal '%c'\\n\" :\n                        \"inflate:         literal 0x%02x\\n\", here.val));\n                ROOM();\n                *put++ = (unsigned char)(length);\n                left--;\n                mode = LEN;\n                break;\n            }\n\n            /* process end of block */\n            if (here.op & 32) {\n                Tracevv((stderr, \"inflate:         end of block\\n\"));\n                mode = TYPE;\n                break;\n            }\n\n            /* invalid code */\n            if (here.op & 64) {\n                strm->msg = (char *)\"invalid literal/length code\";\n                mode = BAD;\n                break;\n            }\n\n            /* length code -- get extra bits, if any */\n            extra = (unsigned)(here.op) & 31;\n            if (extra != 0) {\n                NEEDBITS(extra);\n                length += BITS(extra);\n                DROPBITS(extra);\n            }\n            Tracevv((stderr, \"inflate:         length %lu\\n\", length));\n\n            /* get distance code */\n            for (;;) {\n                here = distcode[BITS(distbits)];\n                if ((unsigned)(here.bits) <= bits) break;\n                PULLBYTE();\n            }\n            if ((here.op & 0xf0) == 0) {\n                last = here;\n                for (;;) {\n                    here = distcode[last.val +\n                            (BITS(last.bits + last.op) >> last.bits)];\n                    if ((unsigned)(last.bits + here.bits) <= bits) break;\n                    PULLBYTE();\n                }\n                DROPBITS(last.bits);\n            }\n            DROPBITS(here.bits);\n            if (here.op & 64) {\n                strm->msg = (char *)\"invalid distance code\";\n                mode = BAD;\n                break;\n            }\n            offset = (unsigned)here.val;\n\n            /* get distance extra bits, if any */\n            extra = (unsigned)(here.op) & 15;\n            if (extra != 0) {\n                NEEDBITS(extra);\n                offset += BITS(extra);\n                DROPBITS(extra);\n            }\n            if (offset > WSIZE - (wrap ? 0: left)) {\n                strm->msg = (char *)\"invalid distance too far back\";\n                mode = BAD;\n                break;\n            }\n            Tracevv((stderr, \"inflate:         distance %lu\\n\", offset));\n\n            /* copy match from window to output */\n            do {\n                ROOM();\n                copy = WSIZE - offset;\n                if (copy < left) {\n                    from = put + copy;\n                    copy = left - copy;\n                }\n                else {\n                    from = put - offset;\n                    copy = left;\n                }\n                if (copy > length) copy = length;\n                length -= copy;\n                left -= copy;\n                do {\n                    *put++ = *from++;\n                } while (--copy);\n            } while (length != 0);\n            break;\n\n        case DONE:\n            /* inflate stream terminated properly -- write leftover output */\n            ret = Z_STREAM_END;\n            if (left < WSIZE) {\n                if (out(out_desc, window, (unsigned)(WSIZE - left)))\n                    ret = Z_BUF_ERROR;\n            }\n            goto inf_leave;\n\n        case BAD:\n            ret = Z_DATA_ERROR;\n            goto inf_leave;\n\n        default:                /* can't happen, but makes compilers happy */\n            ret = Z_STREAM_ERROR;\n            goto inf_leave;\n        }\n\n    /* Return unused input */\n  inf_leave:\n    strm->next_in = next;\n    strm->avail_in = have;\n    return ret;\n}\n\nint ZEXPORT inflateBack9End(z_stream FAR *strm) {\n    if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0)\n        return Z_STREAM_ERROR;\n    ZFREE(strm, strm->state);\n    strm->state = Z_NULL;\n    Tracev((stderr, \"inflate: end\\n\"));\n    return Z_OK;\n}\n"
  },
  {
    "path": "contrib/infback9/infback9.h",
    "content": "/* infback9.h -- header for using inflateBack9 functions\n * Copyright (C) 2003 Mark Adler\n * For conditions of distribution and use, see copyright notice in zlib.h\n */\n\n#ifndef __INFBACK9_H\n#define __INFBACK9_H\n\n/*\n * This header file and associated patches provide a decoder for PKWare's\n * undocumented deflate64 compression method (method 9).  Use with infback9.c,\n * inftree9.h, inftree9.c, and inffix9.h.  These patches are not supported.\n * This should be compiled with zlib, since it uses zutil.h and zutil.o.\n * This code has not yet been tested on 16-bit architectures.  See the\n * comments in zlib.h for inflateBack() usage.  These functions are used\n * identically, except that there is no windowBits parameter, and a 64K\n * window must be provided.  Also if int's are 16 bits, then a zero for\n * the third parameter of the \"out\" function actually means 65536UL.\n * zlib.h must be included before this header file.\n */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nZEXTERN int ZEXPORT inflateBack9(z_stream FAR *strm,\n                                 in_func in, void FAR *in_desc,\n                                 out_func out, void FAR *out_desc);\nZEXTERN int ZEXPORT inflateBack9End(z_stream FAR *strm);\nZEXTERN int ZEXPORT inflateBack9Init_(z_stream FAR *strm,\n                                      unsigned char FAR *window,\n                                      const char *version,\n                                      int stream_size);\n#define inflateBack9Init(strm, window) \\\n        inflateBack9Init_((strm), (window), \\\n        ZLIB_VERSION, sizeof(z_stream))\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "contrib/infback9/inffix9.h",
    "content": "    /* inffix9.h -- table for decoding deflate64 fixed codes\n     * Generated automatically by makefixed9().\n     */\n\n    /* WARNING: this file should *not* be used by applications.\n       It is part of the implementation of this library and is\n       subject to change. Applications should only use zlib.h.\n     */\n\n    static const code lenfix[512] = {\n        {96,7,0},{0,8,80},{0,8,16},{132,8,115},{130,7,31},{0,8,112},\n        {0,8,48},{0,9,192},{128,7,10},{0,8,96},{0,8,32},{0,9,160},\n        {0,8,0},{0,8,128},{0,8,64},{0,9,224},{128,7,6},{0,8,88},\n        {0,8,24},{0,9,144},{131,7,59},{0,8,120},{0,8,56},{0,9,208},\n        {129,7,17},{0,8,104},{0,8,40},{0,9,176},{0,8,8},{0,8,136},\n        {0,8,72},{0,9,240},{128,7,4},{0,8,84},{0,8,20},{133,8,227},\n        {131,7,43},{0,8,116},{0,8,52},{0,9,200},{129,7,13},{0,8,100},\n        {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},\n        {128,7,8},{0,8,92},{0,8,28},{0,9,152},{132,7,83},{0,8,124},\n        {0,8,60},{0,9,216},{130,7,23},{0,8,108},{0,8,44},{0,9,184},\n        {0,8,12},{0,8,140},{0,8,76},{0,9,248},{128,7,3},{0,8,82},\n        {0,8,18},{133,8,163},{131,7,35},{0,8,114},{0,8,50},{0,9,196},\n        {129,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2},{0,8,130},\n        {0,8,66},{0,9,228},{128,7,7},{0,8,90},{0,8,26},{0,9,148},\n        {132,7,67},{0,8,122},{0,8,58},{0,9,212},{130,7,19},{0,8,106},\n        {0,8,42},{0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},\n        {128,7,5},{0,8,86},{0,8,22},{65,8,0},{131,7,51},{0,8,118},\n        {0,8,54},{0,9,204},{129,7,15},{0,8,102},{0,8,38},{0,9,172},\n        {0,8,6},{0,8,134},{0,8,70},{0,9,236},{128,7,9},{0,8,94},\n        {0,8,30},{0,9,156},{132,7,99},{0,8,126},{0,8,62},{0,9,220},\n        {130,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142},\n        {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{133,8,131},\n        {130,7,31},{0,8,113},{0,8,49},{0,9,194},{128,7,10},{0,8,97},\n        {0,8,33},{0,9,162},{0,8,1},{0,8,129},{0,8,65},{0,9,226},\n        {128,7,6},{0,8,89},{0,8,25},{0,9,146},{131,7,59},{0,8,121},\n        {0,8,57},{0,9,210},{129,7,17},{0,8,105},{0,8,41},{0,9,178},\n        {0,8,9},{0,8,137},{0,8,73},{0,9,242},{128,7,4},{0,8,85},\n        {0,8,21},{144,8,3},{131,7,43},{0,8,117},{0,8,53},{0,9,202},\n        {129,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},\n        {0,8,69},{0,9,234},{128,7,8},{0,8,93},{0,8,29},{0,9,154},\n        {132,7,83},{0,8,125},{0,8,61},{0,9,218},{130,7,23},{0,8,109},\n        {0,8,45},{0,9,186},{0,8,13},{0,8,141},{0,8,77},{0,9,250},\n        {128,7,3},{0,8,83},{0,8,19},{133,8,195},{131,7,35},{0,8,115},\n        {0,8,51},{0,9,198},{129,7,11},{0,8,99},{0,8,35},{0,9,166},\n        {0,8,3},{0,8,131},{0,8,67},{0,9,230},{128,7,7},{0,8,91},\n        {0,8,27},{0,9,150},{132,7,67},{0,8,123},{0,8,59},{0,9,214},\n        {130,7,19},{0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},\n        {0,8,75},{0,9,246},{128,7,5},{0,8,87},{0,8,23},{77,8,0},\n        {131,7,51},{0,8,119},{0,8,55},{0,9,206},{129,7,15},{0,8,103},\n        {0,8,39},{0,9,174},{0,8,7},{0,8,135},{0,8,71},{0,9,238},\n        {128,7,9},{0,8,95},{0,8,31},{0,9,158},{132,7,99},{0,8,127},\n        {0,8,63},{0,9,222},{130,7,27},{0,8,111},{0,8,47},{0,9,190},\n        {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},\n        {0,8,16},{132,8,115},{130,7,31},{0,8,112},{0,8,48},{0,9,193},\n        {128,7,10},{0,8,96},{0,8,32},{0,9,161},{0,8,0},{0,8,128},\n        {0,8,64},{0,9,225},{128,7,6},{0,8,88},{0,8,24},{0,9,145},\n        {131,7,59},{0,8,120},{0,8,56},{0,9,209},{129,7,17},{0,8,104},\n        {0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72},{0,9,241},\n        {128,7,4},{0,8,84},{0,8,20},{133,8,227},{131,7,43},{0,8,116},\n        {0,8,52},{0,9,201},{129,7,13},{0,8,100},{0,8,36},{0,9,169},\n        {0,8,4},{0,8,132},{0,8,68},{0,9,233},{128,7,8},{0,8,92},\n        {0,8,28},{0,9,153},{132,7,83},{0,8,124},{0,8,60},{0,9,217},\n        {130,7,23},{0,8,108},{0,8,44},{0,9,185},{0,8,12},{0,8,140},\n        {0,8,76},{0,9,249},{128,7,3},{0,8,82},{0,8,18},{133,8,163},\n        {131,7,35},{0,8,114},{0,8,50},{0,9,197},{129,7,11},{0,8,98},\n        {0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229},\n        {128,7,7},{0,8,90},{0,8,26},{0,9,149},{132,7,67},{0,8,122},\n        {0,8,58},{0,9,213},{130,7,19},{0,8,106},{0,8,42},{0,9,181},\n        {0,8,10},{0,8,138},{0,8,74},{0,9,245},{128,7,5},{0,8,86},\n        {0,8,22},{65,8,0},{131,7,51},{0,8,118},{0,8,54},{0,9,205},\n        {129,7,15},{0,8,102},{0,8,38},{0,9,173},{0,8,6},{0,8,134},\n        {0,8,70},{0,9,237},{128,7,9},{0,8,94},{0,8,30},{0,9,157},\n        {132,7,99},{0,8,126},{0,8,62},{0,9,221},{130,7,27},{0,8,110},\n        {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},\n        {96,7,0},{0,8,81},{0,8,17},{133,8,131},{130,7,31},{0,8,113},\n        {0,8,49},{0,9,195},{128,7,10},{0,8,97},{0,8,33},{0,9,163},\n        {0,8,1},{0,8,129},{0,8,65},{0,9,227},{128,7,6},{0,8,89},\n        {0,8,25},{0,9,147},{131,7,59},{0,8,121},{0,8,57},{0,9,211},\n        {129,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9},{0,8,137},\n        {0,8,73},{0,9,243},{128,7,4},{0,8,85},{0,8,21},{144,8,3},\n        {131,7,43},{0,8,117},{0,8,53},{0,9,203},{129,7,13},{0,8,101},\n        {0,8,37},{0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},\n        {128,7,8},{0,8,93},{0,8,29},{0,9,155},{132,7,83},{0,8,125},\n        {0,8,61},{0,9,219},{130,7,23},{0,8,109},{0,8,45},{0,9,187},\n        {0,8,13},{0,8,141},{0,8,77},{0,9,251},{128,7,3},{0,8,83},\n        {0,8,19},{133,8,195},{131,7,35},{0,8,115},{0,8,51},{0,9,199},\n        {129,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131},\n        {0,8,67},{0,9,231},{128,7,7},{0,8,91},{0,8,27},{0,9,151},\n        {132,7,67},{0,8,123},{0,8,59},{0,9,215},{130,7,19},{0,8,107},\n        {0,8,43},{0,9,183},{0,8,11},{0,8,139},{0,8,75},{0,9,247},\n        {128,7,5},{0,8,87},{0,8,23},{77,8,0},{131,7,51},{0,8,119},\n        {0,8,55},{0,9,207},{129,7,15},{0,8,103},{0,8,39},{0,9,175},\n        {0,8,7},{0,8,135},{0,8,71},{0,9,239},{128,7,9},{0,8,95},\n        {0,8,31},{0,9,159},{132,7,99},{0,8,127},{0,8,63},{0,9,223},\n        {130,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},\n        {0,8,79},{0,9,255}\n    };\n\n    static const code distfix[32] = {\n        {128,5,1},{135,5,257},{131,5,17},{139,5,4097},{129,5,5},\n        {137,5,1025},{133,5,65},{141,5,16385},{128,5,3},{136,5,513},\n        {132,5,33},{140,5,8193},{130,5,9},{138,5,2049},{134,5,129},\n        {142,5,32769},{128,5,2},{135,5,385},{131,5,25},{139,5,6145},\n        {129,5,7},{137,5,1537},{133,5,97},{141,5,24577},{128,5,4},\n        {136,5,769},{132,5,49},{140,5,12289},{130,5,13},{138,5,3073},\n        {134,5,193},{142,5,49153}\n    };\n"
  },
  {
    "path": "contrib/infback9/inflate9.h",
    "content": "/* inflate9.h -- internal inflate state definition\n * Copyright (C) 1995-2003 Mark Adler\n * For conditions of distribution and use, see copyright notice in zlib.h\n */\n\n#ifndef __INFLATE9_H\n#define __INFLATE9_H\n\n/* WARNING: this file should *not* be used by applications. It is\n   part of the implementation of the compression library and is\n   subject to change. Applications should only use zlib.h.\n */\n\n/* Possible inflate modes between inflate() calls */\ntypedef enum {\n        TYPE,       /* i: waiting for type bits, including last-flag bit */\n        STORED,     /* i: waiting for stored size (length and complement) */\n        TABLE,      /* i: waiting for dynamic block table lengths */\n            LEN,        /* i: waiting for length/lit code */\n    DONE,       /* finished check, done -- remain here until reset */\n    BAD         /* got a data error -- remain here until reset */\n} inflate_mode;\n\n/*\n    State transitions between above modes -\n\n    (most modes can go to the BAD mode -- not shown for clarity)\n\n    Read deflate blocks:\n            TYPE -> STORED or TABLE or LEN or DONE\n            STORED -> TYPE\n            TABLE -> LENLENS -> CODELENS -> LEN\n    Read deflate codes:\n                LEN -> LEN or TYPE\n */\n\n/* state maintained between inflate() calls.  Approximately 7K bytes. */\nstruct inflate_state {\n        /* sliding window */\n    unsigned char FAR *window;  /* allocated sliding window, if needed */\n        /* dynamic table building */\n    unsigned ncode;             /* number of code length code lengths */\n    unsigned nlen;              /* number of length code lengths */\n    unsigned ndist;             /* number of distance code lengths */\n    unsigned have;              /* number of code lengths in lens[] */\n    code FAR *next;             /* next available space in codes[] */\n    unsigned short lens[320];   /* temporary storage for code lengths */\n    unsigned short work[288];   /* work area for code table building */\n    code codes[ENOUGH];         /* space for code tables */\n};\n\n#endif\n"
  },
  {
    "path": "contrib/infback9/inftree9.c",
    "content": "/* inftree9.c -- generate Huffman trees for efficient decoding\n * Copyright (C) 1995-2023 Mark Adler\n * For conditions of distribution and use, see copyright notice in zlib.h\n */\n\n#include \"zutil.h\"\n#include \"inftree9.h\"\n\n#define MAXBITS 15\n\nconst char inflate9_copyright[] =\n   \" inflate9 1.3 Copyright 1995-2023 Mark Adler \";\n/*\n  If you use the zlib library in a product, an acknowledgment is welcome\n  in the documentation of your product. If for some reason you cannot\n  include such an acknowledgment, I would appreciate that you keep this\n  copyright string in the executable of your product.\n */\n\n/*\n   Build a set of tables to decode the provided canonical Huffman code.\n   The code lengths are lens[0..codes-1].  The result starts at *table,\n   whose indices are 0..2^bits-1.  work is a writable array of at least\n   lens shorts, which is used as a work area.  type is the type of code\n   to be generated, CODES, LENS, or DISTS.  On return, zero is success,\n   -1 is an invalid code, and +1 means that ENOUGH isn't enough.  table\n   on return points to the next available entry's address.  bits is the\n   requested root table index bits, and on return it is the actual root\n   table index bits.  It will differ if the request is greater than the\n   longest code or if it is less than the shortest code.\n */\nint inflate_table9(codetype type, unsigned short FAR *lens, unsigned codes,\n                   code FAR * FAR *table, unsigned FAR *bits,\n                   unsigned short FAR *work) {\n    unsigned len;               /* a code's length in bits */\n    unsigned sym;               /* index of code symbols */\n    unsigned min, max;          /* minimum and maximum code lengths */\n    unsigned root;              /* number of index bits for root table */\n    unsigned curr;              /* number of index bits for current table */\n    unsigned drop;              /* code bits to drop for sub-table */\n    int left;                   /* number of prefix codes available */\n    unsigned used;              /* code entries in table used */\n    unsigned huff;              /* Huffman code */\n    unsigned incr;              /* for incrementing code, index */\n    unsigned fill;              /* index for replicating entries */\n    unsigned low;               /* low bits for current root entry */\n    unsigned mask;              /* mask for low root bits */\n    code this;                  /* table entry for duplication */\n    code FAR *next;             /* next available space in table */\n    const unsigned short FAR *base;     /* base value table to use */\n    const unsigned short FAR *extra;    /* extra bits table to use */\n    int end;                    /* use base and extra for symbol > end */\n    unsigned short count[MAXBITS+1];    /* number of codes of each length */\n    unsigned short offs[MAXBITS+1];     /* offsets in table for each length */\n    static const unsigned short lbase[31] = { /* Length codes 257..285 base */\n        3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17,\n        19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115,\n        131, 163, 195, 227, 3, 0, 0};\n    static const unsigned short lext[31] = { /* Length codes 257..285 extra */\n        128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129,\n        130, 130, 130, 130, 131, 131, 131, 131, 132, 132, 132, 132,\n        133, 133, 133, 133, 144, 198, 203};\n    static const unsigned short dbase[32] = { /* Distance codes 0..31 base */\n        1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49,\n        65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073,\n        4097, 6145, 8193, 12289, 16385, 24577, 32769, 49153};\n    static const unsigned short dext[32] = { /* Distance codes 0..31 extra */\n        128, 128, 128, 128, 129, 129, 130, 130, 131, 131, 132, 132,\n        133, 133, 134, 134, 135, 135, 136, 136, 137, 137, 138, 138,\n        139, 139, 140, 140, 141, 141, 142, 142};\n\n    /*\n       Process a set of code lengths to create a canonical Huffman code.  The\n       code lengths are lens[0..codes-1].  Each length corresponds to the\n       symbols 0..codes-1.  The Huffman code is generated by first sorting the\n       symbols by length from short to long, and retaining the symbol order\n       for codes with equal lengths.  Then the code starts with all zero bits\n       for the first code of the shortest length, and the codes are integer\n       increments for the same length, and zeros are appended as the length\n       increases.  For the deflate format, these bits are stored backwards\n       from their more natural integer increment ordering, and so when the\n       decoding tables are built in the large loop below, the integer codes\n       are incremented backwards.\n\n       This routine assumes, but does not check, that all of the entries in\n       lens[] are in the range 0..MAXBITS.  The caller must assure this.\n       1..MAXBITS is interpreted as that code length.  zero means that that\n       symbol does not occur in this code.\n\n       The codes are sorted by computing a count of codes for each length,\n       creating from that a table of starting indices for each length in the\n       sorted table, and then entering the symbols in order in the sorted\n       table.  The sorted table is work[], with that space being provided by\n       the caller.\n\n       The length counts are used for other purposes as well, i.e. finding\n       the minimum and maximum length codes, determining if there are any\n       codes at all, checking for a valid set of lengths, and looking ahead\n       at length counts to determine sub-table sizes when building the\n       decoding tables.\n     */\n\n    /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */\n    for (len = 0; len <= MAXBITS; len++)\n        count[len] = 0;\n    for (sym = 0; sym < codes; sym++)\n        count[lens[sym]]++;\n\n    /* bound code lengths, force root to be within code lengths */\n    root = *bits;\n    for (max = MAXBITS; max >= 1; max--)\n        if (count[max] != 0) break;\n    if (root > max) root = max;\n    if (max == 0) return -1;            /* no codes! */\n    for (min = 1; min <= MAXBITS; min++)\n        if (count[min] != 0) break;\n    if (root < min) root = min;\n\n    /* check for an over-subscribed or incomplete set of lengths */\n    left = 1;\n    for (len = 1; len <= MAXBITS; len++) {\n        left <<= 1;\n        left -= count[len];\n        if (left < 0) return -1;        /* over-subscribed */\n    }\n    if (left > 0 && (type == CODES || max != 1))\n        return -1;                      /* incomplete set */\n\n    /* generate offsets into symbol table for each length for sorting */\n    offs[1] = 0;\n    for (len = 1; len < MAXBITS; len++)\n        offs[len + 1] = offs[len] + count[len];\n\n    /* sort symbols by length, by symbol order within each length */\n    for (sym = 0; sym < codes; sym++)\n        if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym;\n\n    /*\n       Create and fill in decoding tables.  In this loop, the table being\n       filled is at next and has curr index bits.  The code being used is huff\n       with length len.  That code is converted to an index by dropping drop\n       bits off of the bottom.  For codes where len is less than drop + curr,\n       those top drop + curr - len bits are incremented through all values to\n       fill the table with replicated entries.\n\n       root is the number of index bits for the root table.  When len exceeds\n       root, sub-tables are created pointed to by the root entry with an index\n       of the low root bits of huff.  This is saved in low to check for when a\n       new sub-table should be started.  drop is zero when the root table is\n       being filled, and drop is root when sub-tables are being filled.\n\n       When a new sub-table is needed, it is necessary to look ahead in the\n       code lengths to determine what size sub-table is needed.  The length\n       counts are used for this, and so count[] is decremented as codes are\n       entered in the tables.\n\n       used keeps track of how many table entries have been allocated from the\n       provided *table space.  It is checked for LENS and DIST tables against\n       the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in\n       the initial root table size constants.  See the comments in inftree9.h\n       for more information.\n\n       sym increments through all symbols, and the loop terminates when\n       all codes of length max, i.e. all codes, have been processed.  This\n       routine permits incomplete codes, so another loop after this one fills\n       in the rest of the decoding tables with invalid code markers.\n     */\n\n    /* set up for code type */\n    switch (type) {\n    case CODES:\n        base = extra = work;    /* dummy value--not used */\n        end = 19;\n        break;\n    case LENS:\n        base = lbase;\n        base -= 257;\n        extra = lext;\n        extra -= 257;\n        end = 256;\n        break;\n    default:            /* DISTS */\n        base = dbase;\n        extra = dext;\n        end = -1;\n    }\n\n    /* initialize state for loop */\n    huff = 0;                   /* starting code */\n    sym = 0;                    /* starting code symbol */\n    len = min;                  /* starting code length */\n    next = *table;              /* current table to fill in */\n    curr = root;                /* current table index bits */\n    drop = 0;                   /* current bits to drop from code for index */\n    low = (unsigned)(-1);       /* trigger new sub-table when len > root */\n    used = 1U << root;          /* use root table entries */\n    mask = used - 1;            /* mask for comparing low */\n\n    /* check available table space */\n    if ((type == LENS && used >= ENOUGH_LENS) ||\n        (type == DISTS && used >= ENOUGH_DISTS))\n        return 1;\n\n    /* process all codes and make table entries */\n    for (;;) {\n        /* create table entry */\n        this.bits = (unsigned char)(len - drop);\n        if ((int)(work[sym]) < end) {\n            this.op = (unsigned char)0;\n            this.val = work[sym];\n        }\n        else if ((int)(work[sym]) > end) {\n            this.op = (unsigned char)(extra[work[sym]]);\n            this.val = base[work[sym]];\n        }\n        else {\n            this.op = (unsigned char)(32 + 64);         /* end of block */\n            this.val = 0;\n        }\n\n        /* replicate for those indices with low len bits equal to huff */\n        incr = 1U << (len - drop);\n        fill = 1U << curr;\n        do {\n            fill -= incr;\n            next[(huff >> drop) + fill] = this;\n        } while (fill != 0);\n\n        /* backwards increment the len-bit code huff */\n        incr = 1U << (len - 1);\n        while (huff & incr)\n            incr >>= 1;\n        if (incr != 0) {\n            huff &= incr - 1;\n            huff += incr;\n        }\n        else\n            huff = 0;\n\n        /* go to next symbol, update count, len */\n        sym++;\n        if (--(count[len]) == 0) {\n            if (len == max) break;\n            len = lens[work[sym]];\n        }\n\n        /* create new sub-table if needed */\n        if (len > root && (huff & mask) != low) {\n            /* if first time, transition to sub-tables */\n            if (drop == 0)\n                drop = root;\n\n            /* increment past last table */\n            next += 1U << curr;\n\n            /* determine length of next table */\n            curr = len - drop;\n            left = (int)(1 << curr);\n            while (curr + drop < max) {\n                left -= count[curr + drop];\n                if (left <= 0) break;\n                curr++;\n                left <<= 1;\n            }\n\n            /* check for enough space */\n            used += 1U << curr;\n            if ((type == LENS && used >= ENOUGH_LENS) ||\n                (type == DISTS && used >= ENOUGH_DISTS))\n                return 1;\n\n            /* point entry in root table to sub-table */\n            low = huff & mask;\n            (*table)[low].op = (unsigned char)curr;\n            (*table)[low].bits = (unsigned char)root;\n            (*table)[low].val = (unsigned short)(next - *table);\n        }\n    }\n\n    /*\n       Fill in rest of table for incomplete codes.  This loop is similar to the\n       loop above in incrementing huff for table indices.  It is assumed that\n       len is equal to curr + drop, so there is no loop needed to increment\n       through high index bits.  When the current sub-table is filled, the loop\n       drops back to the root table to fill in any remaining entries there.\n     */\n    this.op = (unsigned char)64;                /* invalid code marker */\n    this.bits = (unsigned char)(len - drop);\n    this.val = (unsigned short)0;\n    while (huff != 0) {\n        /* when done with sub-table, drop back to root table */\n        if (drop != 0 && (huff & mask) != low) {\n            drop = 0;\n            len = root;\n            next = *table;\n            curr = root;\n            this.bits = (unsigned char)len;\n        }\n\n        /* put invalid code marker in table */\n        next[huff >> drop] = this;\n\n        /* backwards increment the len-bit code huff */\n        incr = 1U << (len - 1);\n        while (huff & incr)\n            incr >>= 1;\n        if (incr != 0) {\n            huff &= incr - 1;\n            huff += incr;\n        }\n        else\n            huff = 0;\n    }\n\n    /* set return parameters */\n    *table += used;\n    *bits = root;\n    return 0;\n}\n"
  },
  {
    "path": "contrib/infback9/inftree9.h",
    "content": "/* inftree9.h -- header to use inftree9.c\n * Copyright (C) 1995-2008 Mark Adler\n * For conditions of distribution and use, see copyright notice in zlib.h\n */\n\n#ifndef __INFTREE9_H\n#define __INFTREE9_H\n\n/* WARNING: this file should *not* be used by applications. It is\n   part of the implementation of the compression library and is\n   subject to change. Applications should only use zlib.h.\n */\n\n/* Structure for decoding tables.  Each entry provides either the\n   information needed to do the operation requested by the code that\n   indexed that table entry, or it provides a pointer to another\n   table that indexes more bits of the code.  op indicates whether\n   the entry is a pointer to another table, a literal, a length or\n   distance, an end-of-block, or an invalid code.  For a table\n   pointer, the low four bits of op is the number of index bits of\n   that table.  For a length or distance, the low four bits of op\n   is the number of extra bits to get after the code.  bits is\n   the number of bits in this code or part of the code to drop off\n   of the bit buffer.  val is the actual byte to output in the case\n   of a literal, the base length or distance, or the offset from\n   the current table to the next table.  Each entry is four bytes. */\ntypedef struct {\n    unsigned char op;           /* operation, extra bits, table bits */\n    unsigned char bits;         /* bits in this part of the code */\n    unsigned short val;         /* offset in table or code value */\n} code;\n\n/* op values as set by inflate_table():\n    00000000 - literal\n    0000tttt - table link, tttt != 0 is the number of table index bits\n    100eeeee - length or distance, eeee is the number of extra bits\n    01100000 - end of block\n    01000000 - invalid code\n */\n\n/* Maximum size of the dynamic table.  The maximum number of code structures is\n   1446, which is the sum of 852 for literal/length codes and 594 for distance\n   codes.  These values were found by exhaustive searches using the program\n   examples/enough.c found in the zlib distribution.  The arguments to that\n   program are the number of symbols, the initial root table size, and the\n   maximum bit length of a code.  \"enough 286 9 15\" for literal/length codes\n   returns returns 852, and \"enough 32 6 15\" for distance codes returns 594.\n   The initial root table size (9 or 6) is found in the fifth argument of the\n   inflate_table() calls in infback9.c.  If the root table size is changed,\n   then these maximum sizes would be need to be recalculated and updated. */\n#define ENOUGH_LENS 852\n#define ENOUGH_DISTS 594\n#define ENOUGH (ENOUGH_LENS+ENOUGH_DISTS)\n\n/* Type of code to build for inflate_table9() */\ntypedef enum {\n    CODES,\n    LENS,\n    DISTS\n} codetype;\n\nextern int inflate_table9(codetype type, unsigned short FAR *lens,\n                          unsigned codes, code FAR * FAR *table,\n                          unsigned FAR *bits, unsigned short FAR *work);\n#endif\n"
  },
  {
    "path": "contrib/infback9/zutil.h",
    "content": "/* zutil.h -- internal interface and configuration of the compression library\n * Copyright (C) 1995-2022 Jean-loup Gailly, Mark Adler\n * For conditions of distribution and use, see copyright notice in zlib.h\n */\n\n/* NOTE: heavily stripped down, read README. */\n\n#ifndef ZUTIL_H\n#define ZUTIL_H\n\n#include <zlib.h>\n\n#if defined(STDC) && !defined(Z_SOLO)\n#  if !(defined(_WIN32_WCE) && defined(_MSC_VER))\n#    include <stddef.h>\n#  endif\n#  include <string.h>\n#  include <stdlib.h>\n#endif\n\n#ifndef z_const\n#define z_const\n#endif\n\n#    define zmemcpy memcpy\n#    define zmemcmp memcmp\n#    define zmemzero(dest, len) memset(dest, 0, len)\n\n#  define Assert(cond,msg)\n#  define Trace(x)\n#  define Tracev(x)\n#  define Tracevv(x)\n#  define Tracec(c,x)\n#  define Tracecv(c,x)\n\n#define ZALLOC(strm, items, size) \\\n           (*((strm)->zalloc))((strm)->opaque, (items), (size))\n#define ZFREE(strm, addr)  (*((strm)->zfree))((strm)->opaque, (voidpf)(addr))\n#define TRY_FREE(s, p) {if (p) ZFREE(s, p);}\n\n/* Reverse the bytes in a 32-bit value */\n#define ZSWAP32(q) ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \\\n                    (((q) & 0xff00) << 8) + (((q) & 0xff) << 24))\n\n#endif /* ZUTIL_H */\n"
  },
  {
    "path": "contrib/khash/khash.h",
    "content": "/* The MIT License\n\n   Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>\n\n   Permission is hereby granted, free of charge, to any person obtaining\n   a copy of this software and associated documentation files (the\n   \"Software\"), to deal in the Software without restriction, including\n   without limitation the rights to use, copy, modify, merge, publish,\n   distribute, sublicense, and/or sell copies of the Software, and to\n   permit persons to whom the Software is furnished to do so, subject to\n   the following conditions:\n\n   The above copyright notice and this permission notice shall be\n   included in all copies or substantial portions of the Software.\n\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n   SOFTWARE.\n*/\n\n/*\n  An example:\n\n#include \"khash.h\"\nKHASH_MAP_INIT_INT(32, char)\nint main() {\n\tint ret, is_missing;\n\tkhiter_t k;\n\tkhash_t(32) *h = kh_init(32);\n\tk = kh_put(32, h, 5, &ret);\n\tkh_value(h, k) = 10;\n\tk = kh_get(32, h, 10);\n\tis_missing = (k == kh_end(h));\n\tk = kh_get(32, h, 5);\n\tkh_del(32, h, k);\n\tfor (k = kh_begin(h); k != kh_end(h); ++k)\n\t\tif (kh_exist(h, k)) kh_value(h, k) = 1;\n\tkh_destroy(32, h);\n\treturn 0;\n}\n*/\n\n/*\n  2013-05-02 (0.2.8):\n\n\t* Use quadratic probing. When the capacity is power of 2, stepping function\n\t  i*(i+1)/2 guarantees to traverse each bucket. It is better than double\n\t  hashing on cache performance and is more robust than linear probing.\n\n\t  In theory, double hashing should be more robust than quadratic probing.\n\t  However, my implementation is probably not for large hash tables, because\n\t  the second hash function is closely tied to the first hash function,\n\t  which reduce the effectiveness of double hashing.\n\n\tReference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php\n\n  2011-12-29 (0.2.7):\n\n    * Minor code clean up; no actual effect.\n\n  2011-09-16 (0.2.6):\n\n\t* The capacity is a power of 2. This seems to dramatically improve the\n\t  speed for simple keys. Thank Zilong Tan for the suggestion. Reference:\n\n\t   - http://code.google.com/p/ulib/\n\t   - http://nothings.org/computer/judy/\n\n\t* Allow to optionally use linear probing which usually has better\n\t  performance for random input. Double hashing is still the default as it\n\t  is more robust to certain non-random input.\n\n\t* Added Wang's integer hash function (not used by default). This hash\n\t  function is more robust to certain non-random input.\n\n  2011-02-14 (0.2.5):\n\n    * Allow to declare global functions.\n\n  2009-09-26 (0.2.4):\n\n    * Improve portability\n\n  2008-09-19 (0.2.3):\n\n\t* Corrected the example\n\t* Improved interfaces\n\n  2008-09-11 (0.2.2):\n\n\t* Improved speed a little in kh_put()\n\n  2008-09-10 (0.2.1):\n\n\t* Added kh_clear()\n\t* Fixed a compiling error\n\n  2008-09-02 (0.2.0):\n\n\t* Changed to token concatenation which increases flexibility.\n\n  2008-08-31 (0.1.2):\n\n\t* Fixed a bug in kh_get(), which has not been tested previously.\n\n  2008-08-31 (0.1.1):\n\n\t* Added destructor\n*/\n\n\n#ifndef __AC_KHASH_H\n#define __AC_KHASH_H\n\n/*!\n  @header\n\n  Generic hash table library.\n */\n\n#define AC_VERSION_KHASH_H \"0.2.8\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <limits.h>\n\n/* compiler specific configuration */\n\n#if UINT_MAX == 0xffffffffu\ntypedef unsigned int khint32_t;\n#elif ULONG_MAX == 0xffffffffu\ntypedef unsigned long khint32_t;\n#endif\n\n#if ULONG_MAX == ULLONG_MAX\ntypedef unsigned long khint64_t;\n#else\ntypedef unsigned long long khint64_t;\n#endif\n\n#ifndef kh_inline\n#ifdef _MSC_VER\n#define kh_inline __inline\n#else\n#define kh_inline inline\n#endif\n#endif /* kh_inline */\n\n#ifndef klib_unused\n#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3)\n#define klib_unused __attribute__ ((__unused__))\n#else\n#define klib_unused\n#endif\n#endif /* klib_unused */\n\ntypedef khint32_t khint_t;\ntypedef khint_t khiter_t;\n\n#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)\n#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)\n#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)\n#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))\n#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))\n#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))\n#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))\n\n#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)\n\n#ifndef kroundup32\n#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))\n#endif\n\n#ifndef kcalloc\n#define kcalloc(N,Z) calloc(N,Z)\n#endif\n#ifndef kmalloc\n#define kmalloc(Z) malloc(Z)\n#endif\n#ifndef krealloc\n#define krealloc(P,Z) realloc(P,Z)\n#endif\n#ifndef kfree\n#define kfree(P) free(P)\n#endif\n\nstatic const double __ac_HASH_UPPER = 0.77;\n\n#define __KHASH_TYPE(name, khkey_t, khval_t) \\\n\ttypedef struct kh_##name##_s { \\\n\t\tkhint_t n_buckets, size, n_occupied, upper_bound; \\\n\t\tkhint32_t *flags; \\\n\t\tkhkey_t *keys; \\\n\t\tkhval_t *vals; \\\n\t} kh_##name##_t;\n\n#define __KHASH_PROTOTYPES(name, khkey_t, khval_t)\t \t\t\t\t\t\\\n\textern kh_##name##_t *kh_init_##name(void);\t\t\t\t\t\t\t\\\n\textern void kh_destroy_##name(kh_##name##_t *h);\t\t\t\t\t\\\n\textern void kh_clear_##name(kh_##name##_t *h);\t\t\t\t\t\t\\\n\textern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \t\\\n\textern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \\\n\textern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \\\n\textern void kh_del_##name(kh_##name##_t *h, khint_t x);\n\n#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \\\n\tSCOPE kh_##name##_t *kh_init_##name(void) {\t\t\t\t\t\t\t\\\n\t\treturn (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t));\t\t\\\n\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tSCOPE void kh_destroy_##name(kh_##name##_t *h)\t\t\t\t\t\t\\\n\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tif (h) {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tkfree((void *)h->keys); kfree(h->flags);\t\t\t\t\t\\\n\t\t\tkfree((void *)h->vals);\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tkfree(h);\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tSCOPE void kh_clear_##name(kh_##name##_t *h)\t\t\t\t\t\t\\\n\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tif (h && h->flags) {\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tmemset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \\\n\t\t\th->size = h->n_occupied = 0;\t\t\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tSCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \t\\\n\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tif (h->n_buckets) {\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tkhint_t k, i, last, mask, step = 0; \\\n\t\t\tmask = h->n_buckets - 1;\t\t\t\t\t\t\t\t\t\\\n\t\t\tk = __hash_func(key); i = k & mask;\t\t\t\t\t\t\t\\\n\t\t\tlast = i; \\\n\t\t\twhile (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \\\n\t\t\t\ti = (i + (++step)) & mask; \\\n\t\t\t\tif (i == last) return h->n_buckets;\t\t\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\treturn __ac_iseither(h->flags, i)? h->n_buckets : i;\t\t\\\n\t\t} else return 0;\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tSCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \\\n\t{ /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \\\n\t\tkhint32_t *new_flags = 0;\t\t\t\t\t\t\t\t\t\t\\\n\t\tkhint_t j = 1;\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tkroundup32(new_n_buckets); \t\t\t\t\t\t\t\t\t\\\n\t\t\tif (new_n_buckets < 4) new_n_buckets = 4;\t\t\t\t\t\\\n\t\t\tif (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0;\t/* requested size is too small */ \\\n\t\t\telse { /* hash table size to be changed (shrink or expand); rehash */ \\\n\t\t\t\tnew_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t));\t\\\n\t\t\t\tif (!new_flags) return -1;\t\t\t\t\t\t\t\t\\\n\t\t\t\tmemset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \\\n\t\t\t\tif (h->n_buckets < new_n_buckets) {\t/* expand */\t\t\\\n\t\t\t\t\tkhkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \\\n\t\t\t\t\tif (!new_keys) { kfree(new_flags); return -1; }\t\t\\\n\t\t\t\t\th->keys = new_keys;\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tif (kh_is_map) {\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\tkhval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \\\n\t\t\t\t\t\tif (!new_vals) { kfree(new_flags); return -1; }\t\\\n\t\t\t\t\t\th->vals = new_vals;\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t} /* otherwise shrink */\t\t\t\t\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tif (j) { /* rehashing is needed */\t\t\t\t\t\t\t\t\\\n\t\t\tfor (j = 0; j != h->n_buckets; ++j) {\t\t\t\t\t\t\\\n\t\t\t\tif (__ac_iseither(h->flags, j) == 0) {\t\t\t\t\t\\\n\t\t\t\t\tkhkey_t key = h->keys[j];\t\t\t\t\t\t\t\\\n\t\t\t\t\tkhval_t val;\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tkhint_t new_mask;\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tnew_mask = new_n_buckets - 1; \t\t\t\t\t\t\\\n\t\t\t\t\tif (kh_is_map) val = h->vals[j];\t\t\t\t\t\\\n\t\t\t\t\t__ac_set_isdel_true(h->flags, j);\t\t\t\t\t\\\n\t\t\t\t\twhile (1) { /* kick-out process; sort of like in Cuckoo hashing */ \\\n\t\t\t\t\t\tkhint_t k, i, step = 0; \\\n\t\t\t\t\t\tk = __hash_func(key);\t\t\t\t\t\t\t\\\n\t\t\t\t\t\ti = k & new_mask;\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\twhile (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \\\n\t\t\t\t\t\t__ac_set_isempty_false(new_flags, i);\t\t\t\\\n\t\t\t\t\t\tif (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \\\n\t\t\t\t\t\t\t{ khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \\\n\t\t\t\t\t\t\tif (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \\\n\t\t\t\t\t\t\t__ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \\\n\t\t\t\t\t\t} else { /* write the element and jump out of the loop */ \\\n\t\t\t\t\t\t\th->keys[i] = key;\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t\tif (kh_is_map) h->vals[i] = val;\t\t\t\\\n\t\t\t\t\t\t\tbreak;\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tif (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \\\n\t\t\t\th->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \\\n\t\t\t\tif (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \\\n\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tkfree(h->flags); /* free the working space */\t\t\t\t\\\n\t\t\th->flags = new_flags;\t\t\t\t\t\t\t\t\t\t\\\n\t\t\th->n_buckets = new_n_buckets;\t\t\t\t\t\t\t\t\\\n\t\t\th->n_occupied = h->size;\t\t\t\t\t\t\t\t\t\\\n\t\t\th->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \\\n\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\treturn 0;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tSCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \\\n\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tkhint_t x;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tif (h->n_occupied >= h->upper_bound) { /* update the hash table */ \\\n\t\t\tif (h->n_buckets > (h->size<<1)) {\t\t\t\t\t\t\t\\\n\t\t\t\tif (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear \"deleted\" elements */ \\\n\t\t\t\t\t*ret = -1; return h->n_buckets;\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t} else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \\\n\t\t\t\t*ret = -1; return h->n_buckets;\t\t\t\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t} /* TODO: to implement automatically shrinking; resize() already support shrinking */ \\\n\t\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\tkhint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \\\n\t\t\tx = site = h->n_buckets; k = __hash_func(key); i = k & mask; \\\n\t\t\tif (__ac_isempty(h->flags, i)) x = i; /* for speed up */\t\\\n\t\t\telse {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tlast = i; \\\n\t\t\t\twhile (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \\\n\t\t\t\t\tif (__ac_isdel(h->flags, i)) site = i;\t\t\t\t\\\n\t\t\t\t\ti = (i + (++step)) & mask; \\\n\t\t\t\t\tif (i == last) { x = site; break; }\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\tif (x == h->n_buckets) {\t\t\t\t\t\t\t\t\\\n\t\t\t\t\tif (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \\\n\t\t\t\t\telse x = i;\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tif (__ac_isempty(h->flags, x)) { /* not present at all */\t\t\\\n\t\t\th->keys[x] = key;\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t__ac_set_isboth_false(h->flags, x);\t\t\t\t\t\t\t\\\n\t\t\t++h->size; ++h->n_occupied;\t\t\t\t\t\t\t\t\t\\\n\t\t\t*ret = 1;\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t} else if (__ac_isdel(h->flags, x)) { /* deleted */\t\t\t\t\\\n\t\t\th->keys[x] = key;\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t__ac_set_isboth_false(h->flags, x);\t\t\t\t\t\t\t\\\n\t\t\t++h->size;\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t\t*ret = 2;\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t} else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \\\n\t\treturn x;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\tSCOPE void kh_del_##name(kh_##name##_t *h, khint_t x)\t\t\t\t\\\n\t{\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\tif (x != h->n_buckets && !__ac_iseither(h->flags, x)) {\t\t\t\\\n\t\t\t__ac_set_isdel_true(h->flags, x);\t\t\t\t\t\t\t\\\n\t\t\t--h->size;\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t\t}\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t}\n\n#define KHASH_DECLARE(name, khkey_t, khval_t)\t\t \t\t\t\t\t\\\n\t__KHASH_TYPE(name, khkey_t, khval_t) \t\t\t\t\t\t\t\t\\\n\t__KHASH_PROTOTYPES(name, khkey_t, khval_t)\n\n#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \\\n\t__KHASH_TYPE(name, khkey_t, khval_t) \t\t\t\t\t\t\t\t\\\n\t__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)\n\n#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \\\n\tKHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)\n\n/* --- BEGIN OF HASH FUNCTIONS --- */\n\n/*! @function\n  @abstract     Integer hash function\n  @param  key   The integer [khint32_t]\n  @return       The hash value [khint_t]\n */\n#define kh_int_hash_func(key) (khint32_t)(key)\n/*! @function\n  @abstract     Integer comparison function\n */\n#define kh_int_hash_equal(a, b) ((a) == (b))\n/*! @function\n  @abstract     64-bit integer hash function\n  @param  key   The integer [khint64_t]\n  @return       The hash value [khint_t]\n */\n#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)\n/*! @function\n  @abstract     64-bit integer comparison function\n */\n#define kh_int64_hash_equal(a, b) ((a) == (b))\n/*! @function\n  @abstract     const char* hash function\n  @param  s     Pointer to a null terminated string\n  @return       The hash value\n */\nstatic kh_inline khint_t __ac_X31_hash_string(const char *s)\n{\n\tkhint_t h = (khint_t)*s;\n\tif (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;\n\treturn h;\n}\n/*! @function\n  @abstract     Another interface to const char* hash function\n  @param  key   Pointer to a null terminated string [const char*]\n  @return       The hash value [khint_t]\n */\n#define kh_str_hash_func(key) __ac_X31_hash_string(key)\n/*! @function\n  @abstract     Const char* comparison function\n */\n#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)\n\nstatic kh_inline khint_t __ac_Wang_hash(khint_t key)\n{\n    key += ~(key << 15);\n    key ^=  (key >> 10);\n    key +=  (key << 3);\n    key ^=  (key >> 6);\n    key += ~(key << 11);\n    key ^=  (key >> 16);\n    return key;\n}\n#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key)\n\n/* --- END OF HASH FUNCTIONS --- */\n\n/* Other convenient macros... */\n\n/*!\n  @abstract Type of the hash table.\n  @param  name  Name of the hash table [symbol]\n */\n#define khash_t(name) kh_##name##_t\n\n/*! @function\n  @abstract     Initiate a hash table.\n  @param  name  Name of the hash table [symbol]\n  @return       Pointer to the hash table [khash_t(name)*]\n */\n#define kh_init(name) kh_init_##name()\n\n/*! @function\n  @abstract     Destroy a hash table.\n  @param  name  Name of the hash table [symbol]\n  @param  h     Pointer to the hash table [khash_t(name)*]\n */\n#define kh_destroy(name, h) kh_destroy_##name(h)\n\n/*! @function\n  @abstract     Reset a hash table without deallocating memory.\n  @param  name  Name of the hash table [symbol]\n  @param  h     Pointer to the hash table [khash_t(name)*]\n */\n#define kh_clear(name, h) kh_clear_##name(h)\n\n/*! @function\n  @abstract     Resize a hash table.\n  @param  name  Name of the hash table [symbol]\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  s     New size [khint_t]\n */\n#define kh_resize(name, h, s) kh_resize_##name(h, s)\n\n/*! @function\n  @abstract     Insert a key to the hash table.\n  @param  name  Name of the hash table [symbol]\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  k     Key [type of keys]\n  @param  r     Extra return code: -1 if the operation failed;\n                0 if the key is present in the hash table;\n                1 if the bucket is empty (never used); 2 if the element in\n\t\t\t\tthe bucket has been deleted [int*]\n  @return       Iterator to the inserted element [khint_t]\n */\n#define kh_put(name, h, k, r) kh_put_##name(h, k, r)\n\n/*! @function\n  @abstract     Retrieve a key from the hash table.\n  @param  name  Name of the hash table [symbol]\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  k     Key [type of keys]\n  @return       Iterator to the found element, or kh_end(h) if the element is absent [khint_t]\n */\n#define kh_get(name, h, k) kh_get_##name(h, k)\n\n/*! @function\n  @abstract     Remove a key from the hash table.\n  @param  name  Name of the hash table [symbol]\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  k     Iterator to the element to be deleted [khint_t]\n */\n#define kh_del(name, h, k) kh_del_##name(h, k)\n\n/*! @function\n  @abstract     Test whether a bucket contains data.\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  x     Iterator to the bucket [khint_t]\n  @return       1 if containing data; 0 otherwise [int]\n */\n#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))\n\n/*! @function\n  @abstract     Get key given an iterator\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  x     Iterator to the bucket [khint_t]\n  @return       Key [type of keys]\n */\n#define kh_key(h, x) ((h)->keys[x])\n\n/*! @function\n  @abstract     Get value given an iterator\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  x     Iterator to the bucket [khint_t]\n  @return       Value [type of values]\n  @discussion   For hash sets, calling this results in segfault.\n */\n#define kh_val(h, x) ((h)->vals[x])\n\n/*! @function\n  @abstract     Alias of kh_val()\n */\n#define kh_value(h, x) ((h)->vals[x])\n\n/*! @function\n  @abstract     Get the start iterator\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @return       The start iterator [khint_t]\n */\n#define kh_begin(h) (khint_t)(0)\n\n/*! @function\n  @abstract     Get the end iterator\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @return       The end iterator [khint_t]\n */\n#define kh_end(h) ((h)->n_buckets)\n\n/*! @function\n  @abstract     Get the number of elements in the hash table\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @return       Number of elements in the hash table [khint_t]\n */\n#define kh_size(h) ((h)->size)\n\n/*! @function\n  @abstract     Get the number of buckets in the hash table\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @return       Number of buckets in the hash table [khint_t]\n */\n#define kh_n_buckets(h) ((h)->n_buckets)\n\n/*! @function\n  @abstract     Iterate over the entries in the hash table\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  kvar  Variable to which key will be assigned\n  @param  vvar  Variable to which value will be assigned\n  @param  code  Block of code to execute\n */\n#define kh_foreach(h, kvar, vvar, code) { khint_t __i;\t\t\\\n\tfor (__i = kh_begin(h); __i != kh_end(h); ++__i) {\t\t\\\n\t\tif (!kh_exist(h,__i)) continue;\t\t\t\t\t\t\\\n\t\t(kvar) = kh_key(h,__i);\t\t\t\t\t\t\t\t\\\n\t\t(vvar) = kh_val(h,__i);\t\t\t\t\t\t\t\t\\\n\t\tcode;\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t} }\n\n/*! @function\n  @abstract     Iterate over the values in the hash table\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  vvar  Variable to which value will be assigned\n  @param  code  Block of code to execute\n */\n#define kh_foreach_value(h, vvar, code) { khint_t __i;\t\t\\\n\tfor (__i = kh_begin(h); __i != kh_end(h); ++__i) {\t\t\\\n\t\tif (!kh_exist(h,__i)) continue;\t\t\t\t\t\t\\\n\t\t(vvar) = kh_val(h,__i);\t\t\t\t\t\t\t\t\\\n\t\tcode;\t\t\t\t\t\t\t\t\t\t\t\t\\\n\t} }\n\n/* More conenient interfaces */\n\n/*! @function\n  @abstract     Instantiate a hash set containing integer keys\n  @param  name  Name of the hash table [symbol]\n */\n#define KHASH_SET_INIT_INT(name)\t\t\t\t\t\t\t\t\t\t\\\n\tKHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)\n\n/*! @function\n  @abstract     Instantiate a hash map containing integer keys\n  @param  name  Name of the hash table [symbol]\n  @param  khval_t  Type of values [type]\n */\n#define KHASH_MAP_INIT_INT(name, khval_t)\t\t\t\t\t\t\t\t\\\n\tKHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)\n\n/*! @function\n  @abstract     Instantiate a hash map containing 64-bit integer keys\n  @param  name  Name of the hash table [symbol]\n */\n#define KHASH_SET_INIT_INT64(name)\t\t\t\t\t\t\t\t\t\t\\\n\tKHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)\n\n/*! @function\n  @abstract     Instantiate a hash map containing 64-bit integer keys\n  @param  name  Name of the hash table [symbol]\n  @param  khval_t  Type of values [type]\n */\n#define KHASH_MAP_INIT_INT64(name, khval_t)\t\t\t\t\t\t\t\t\\\n\tKHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)\n\ntypedef const char *kh_cstr_t;\n/*! @function\n  @abstract     Instantiate a hash map containing const char* keys\n  @param  name  Name of the hash table [symbol]\n */\n#define KHASH_SET_INIT_STR(name)\t\t\t\t\t\t\t\t\t\t\\\n\tKHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)\n\n/*! @function\n  @abstract     Instantiate a hash map containing const char* keys\n  @param  name  Name of the hash table [symbol]\n  @param  khval_t  Type of values [type]\n */\n#define KHASH_MAP_INIT_STR(name, khval_t)\t\t\t\t\t\t\t\t\\\n\tKHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)\n\n#endif /* __AC_KHASH_H */\n"
  },
  {
    "path": "contrib/libmodplug/AUTHORS",
    "content": "XMMS plugin:\n  Kenton Varda <temporal@gauge3d.org>\n  Konstanty Bialkowski <konstanty@ieee.org>\n\nGeneral Maintainence:\n  Konstanty Bialkowski <konstanty@ieee.org>\n\nSound Engine:\n  Olivier Lapicque <olivierl@jps.net>\n\nBZip2 support:\n  Colin DeVilbiss <crdevilb@mtu.edu>\nSpline and Fir resamplers:\n  Markus Fick <marf@gmx.net>\nEndianness Fixes:\n  Adam Goode <adam@evdebs.org>\nEndianness Fixes + Implementation of C 24bit,32bit functions:\n  Marco Trillo <toad@arsystel.com>\nFixes to AGC/Clipping, Frequency Limit, Other Fixes:\n  Alistair John Strachan <s0348365@sms.ed.ac.uk>\nAMD64 Fix (long long vs long vs int)\n  Tyler Montbriand <tsm@accesscomm.ca>\n"
  },
  {
    "path": "contrib/libmodplug/COPYING",
    "content": "ModPlug-XMMS and libmodplug are now in the public domain."
  },
  {
    "path": "contrib/libmodplug/ChangeLog",
    "content": "changes:\r\ndate = 24-apr-2017 [Konstanty Bialkowski/Lionel Debroux/SEZERO]\r\n Version 0.8.9.0\r\n  OOB Write and Read fixes + a number of divide by zero fixes.\r\n\t (ABC, PAT, AMF, MDL, PSM, XM, IT, MMCMP, MID)\r\n There were some patches 2010-2016 which were recorded here.\r\ndate = 21-apr-2009 [Konstanty Bialkowski/OpenMPT/Novell (Stanislav Brabec)]\r\n->file: src/load_amf.cpp\r\n\t\twhere: whole file\r\n\t\twhat: added const declarations to read only variables\r\n\t\twhat: fixed delete function\r\n->file: src/fastmix.cpp\r\n\t\twhere: X86_Convert32To24\r\n\t\twhat: conversion to 24bit was incorrect\r\n->file: src/load_mdl.cpp\r\n\t\twhere: init of m_lpszSongComments\r\n\t\twhat: fixed delete function to be array version (Reported by\r\n\t\tDavid Binderman / Stanislav Brabec)\r\n->file: src/load_pat.cpp\r\n\t\twhere: memcpy to .reserved\r\n\t\twhat: changed fixed valid of 36 to sizeof(reserved)\r\n\t\t(Reported by Manfred Tremmel / Stanislav Brabec)\r\ndate = 20-apr-2009 [Konstanty Bialkowski]\r\n->file: src/load_meb.cpp\r\n\t\twhere: LoadMED\r\n\t\twhat: fixed integer boundary condition checking code (fixing\r\n\t\texploit)\r\n\t\tDetails of exploit (and creator of test.s3m for exploit)\r\n\t\thttp://www.securityfocus.com/bid/30801/info\r\n\t\thttp://www.15897.com/blog/post/QianQianJingTing-mod-buffer-overflow-POC.html\r\n->file: src/load_abc.cpp\r\n\t\twhere: TestABC\r\n\t\twhat: Made ABC detection code more robust\r\n->file: src/load_abc.cpp, sndfile.cpp, snd_fx.cpp\r\n\t\twhere: various\r\n\t\twhat: change constant variables to explicitly use const\r\n\t\tdefinition\r\n\t\t(Thanks to Leandro Nini/Diego \"Flameeyes\" Pettenò)\r\n->file: src/table.cpp -> src/table.h\r\n\t\twhere: rename of file\r\n\t\twhat: moved tables to separate file\n\t\t(Thanks to Leandro Nini/Diego \"Flameeyes\" Pettenò)\r\n\r\ndate = 15-apr-2009 [Konstanty Bialkowski]\r\n->file: src/load_abc.cpp\r\n\t\twhere: TestABC\r\n\t\twhat: made sure obviously binary files do not try to get\r\n\t\tloaded as ABC\r\n\t\twhere: abc_addchordname\r\n\t\twhat: made sure first argument was const char * (not just char\r\n\t\t*)\r\n->file: src/sndfile.cpp\r\n\t\twhere: format conversion routines\r\n\t\twhat: made sure (*a++) = func(*a), is executed properly, by\r\n\t\tsplitting into two lines\r\n-file: src/libmodplug/sndfile.h\r\n\t\twhere: IMixPlugin\r\n\t\twhat: made sure there is a virtual destructor (to avoid\r\n\t\twarnings)\r\n->file: src/load_ptm.cpp\r\n\t\twhere: mixing routine\r\n\t\twhat: bswap32 was used on a uint16_t [2] array, and when gcc\r\n\t\tuses -O2 (or greater) optimization, this may not do what is\r\n\t\tintended.\r\ndate = 7-apr-2009 [Konstanty Bialkowski/Anthony Ramine]\r\n-> file: src/load_abc.cpp,load_mid.cpp,load_pat.cpp\r\n\t\twhere: whole file(s)\r\n\t\twhat: removed use of ULONG, and changed to uint32_t\r\n\t\twhere: few functions\r\n\t\twhat: removed uint, and replaced with uint32_t\r\ndate = 7-apr-2009 [Zed Pobre/Debian]\r\n-> file: src/libmodplug.pc\r\n\t\twhere: file.\r\n\t\twhat: split Libs into Libs.private\r\ndate = 2-nov-2006 [Alec Berryman/CVE-2006-4192]\r\n-> file: src/sndfile.cpp\r\n\t\twhere: ReadSample\r\n\t\twhat: prevent buffer overflow [as reported in CVE-2006-4192]\r\ndate = 2-nov-2006 [Konstanty Bialkowski/Macro Trillo]\r\n-> file: configure.in,config.h,load_abc.cpp,fastmix.cpp\r\n\t\twhere: Automake 2.60 used\r\n\t\twhat: In order to properly use stdint.h we should make use of the new macros for its detection, on systems with automake 2.59 but where stdint.h is available, this can be removed. (Patch by Macro Trillo)\r\n-> file: src/load_amf.cpp, src/fastmix.cpp\r\n\t\twhere: Licensing\r\n\t\twhat: all old code was relicensed for public domain, and somehow an old version was included with GPL notices\r\ndate = 20-jul-2006 [Peter Grootswagers]\r\r\n-> file: src/load_abc.cpp\r\r\n\t\twhere: instrument loader functions\r\r\n\t\twhat: replaced with correspondig functions in load_pat.cpp\r\r\n-> file: src/load_mid.cpp\r\r\n\t\twhere: whole source\r\r\n\t\twhat: new loader for midi files\r\r\n-> file: src/load_pat.cpp\r\r\n\t\twhere: whole source\r\r\n\t\twhat: new loader for GUS instrument patch files (pat)\r\r\n-> file: src/load_pat.h\r\r\n\t\twhere: whole source\r\r\n\t\twhat: new header declaring reuseable GUS instrument patch functions (pat)\r\r\n-> file: README\r\r\n\t\twhere: 2. Features\r\r\n\t\twhat: added description of load_mid.cpp and load_pat.cpp\r\r\n-> file: src/Makefile.am\r\r\n\t\twhere: libmodplug_la_SOURCES\r\r\n\t\twhat: added load_mid.cpp and load_pat.cpp\r\r\n\t\twhere: libmodpluginclude_HEADERS\r\r\n\t\twhat: added load_pat.h\r\r\n-> file: src/sndfile.cpp\r\r\n\t\twhere: function CSoundFile::Create()\r\r\n\t\twhat: added call to ReadMID and ReadPAT\r\r\n-> file: src/libmodplug/sndfile.h\r\r\n\t\twhere: #define\r\r\n\t\twhat: added MOD_TYPE_PAT (MOD_TYPE_MID already there...)\r\r\n\t\twhere: class CSoundFile\r\r\n\t\twhat: added public function members ReadMID, TestMID, ReadPat, TestPAT and PATsample\r\r\ndate = 24-jun-2006 [Peter Grootswagers]\r\r\n-> file: src/load_abc.cpp\r\r\n\t\twhere: whole source\r\r\n\t\twhat: new loader for abc files\r\n-> file: README\r\n\t\twhere: 2. Features\r\n\t\twhat: added description of load_abc.cpp\r\n-> file: src/Makefile.am\r\n\t\twhere: libmodplug_la_SOURCES\r\n\t\twhat: added load_abc.cpp\r\n-> file: src/sndfile.cpp\r\n\t\twhere: function CSoundFile::Create()\r\n\t\twhat: added call to ReadABC\r\n-> file: src/libmodplug/sndfile.h\r\n\t\twhere: #define\r\n\t\twhat: added MOD_TYPE_ABC\r\n\t\twhere: class CSoundFile\r\n\t\twhat: added public function members ReadABC and TestABC\r\ndate = 20-mar-2006 [Macro Trillo / \"Custom libmodplug project\"]\r\n-> file : src/load_s3m.cpp\r\n-> file : src/load_far.cpp\r\n\t\twhat: fixed endianness\r\ndate = 20-mar-2006 [Alistair John Strachan]\r\n....\r\n\r\nMany other changes need to be documented here...\r\n\r\nGCC3 fixes, GCC4 fixes, More Archive Types.\r\n\r\ndate = 09-feb-2001 [Markus Fick]\r\n-> file: fastmix.cpp\r\n\t\twhere: spline creation, spline macros\r\n\t\twhat: added unity gain clamp code, added Quantizer_Bits(shift) preprocessor constants\r\n\t\twhere: fir creation, fir macros\r\n\t\twhat: - removed x<pi/2 condition in coef creation\r\n\t\t      - added quantizer_bits(shift) preprocessor constants\r\n\t\t      - set default quantizer bits to 15 instead 14 (scale now 32768 instead 16384)\r\n\t\t        there should not occure any overflows during fir response calculation because\r\n\t\t        of the symmetric form of filter and the position of the negative fir coefs\r\n\t\t      - changed final volume calculation for 16bit samples (quality enhancement)\r\n\r\ndate = 08-feb-2001 [Markus Fick]\r\n-> file: sndmix.cpp\r\n\t\twhere: function ReadNote()\r\n\t\twhat: modified behaviour of modplug so that interpolation is only deactivated if\r\n\t\t      a) the user selects \"no interpolation\"\r\n\t\t\t  b) linear interpolation is set and speed incr. > 0xff00\r\n\t\t\t => if spline or fir is active then we use always interpolation\r\n\r\n-> file: fastmix.cpp\r\n\t\twhere: spline macros\r\n\t\twhat: changed spline macros to use precalculated tables (way faster)\r\n\t\twhere: file\r\n\t\twhat: - implemented spline table precalculator\r\n\t\t      - changed fir precalculator + macros (for higher quality and clearer source)\r\n\t\t      - added some comments and documentation\r\n\t\tcomment:\r\n\t\t  - preprocessor constant: SPLINE_FRACBITS\r\n\t\t     ) controls quality/memory usage\r\n\t\t\t   range is [4..14] inclusive\r\n\t\t\t   4 = low quality, low memory usage\r\n\t\t\t   14 = highest quality, highest memory usage (1L<<14)*4*2 bytes\r\n\t\t  - preprocessor constant: WFIR_FRACBITS\r\n\t\t     ) controls quality/memory usage\r\n\t\t\t   range is [4..12] inclusive\r\n\t\t\t   4 = low quality, low mu\r\n\t\t\t   12 = highest quality, highest memory usage ((1L<<(12+1))+1)*8*2 bytes\r\n\r\ndate = 07-feb-2001 [Markus Fick]\r\n-> file: fastmix.cpp\r\n\t\twhere: spline macros\r\n\t\twhat: fixed error in coef calculation\r\n\r\ndate = 07-feb-2001 [Markus Fick]\r\n-> file: sndfile.h\r\n\t\twhere: class definition of soundfile\r\n\t\twhat: removed InitFIR + DoneFIR function prototypes\r\n-> file: sndfile.cpp\r\n\t\tfunction:CSoundFile::CSoundFile()\r\n\t\twhat: [modify] removed call to CSoundFile::InitFIRMixer( )\r\n\r\n\t\tfunction:CSoundFile::~CSoundFile()\r\n\t\twhat: [modify] removed call to CSoundFile::DoneFIRMixer( )\r\n-> file: fastmix.cpp\r\n\t\twhere: spline macros\r\n\t\twhat: changed formula + added some guard bits to calculation\r\n\r\n\t\twhere: fir macros + implementation\r\n\t\twhat: - moved CSoundfile::FIR funtions to CzFIR (single instance sfir)\r\n\t\t      - changed fir macros to support CzFIR class\r\n\r\ndate = 06-feb-2001 [Markus Fick]\r\n-> file: fastmix.cpp\r\n         where: macros\r\n\t\t what: - removed fir filter with coef interpolation\r\n\t\t       - add spline interpolation\r\n\t\t\tRM: now modplug->select( SPLINE ) selects spline and\r\n\t\t\t        modplug->select( POLYPHASE ) selects 8tap fir filter\r\n\r\ndate = 05-feb-2001 [Markus Fick]\r\n-> file: fastmix.cpp\r\n         where: macros + filter order\r\n         what: [modify] changed filter order to 8 instead of 10\r\n\r\n-> file: fastmix.cpp\r\n         what: new macros+switch for fir-interpolator with coef interpolation\r\n\r\ndate = 04-feb-2001 [Markus Fick]\r\n\r\n-> file: sndfile.h\r\n\t where: class CSoundFile (bottom)\r\n\t what: [add] methods for FIR mixer support\r\n\t             1. int InitFIRInterpolator( );\r\n\t\t\t\t 2. int DoneFIRInterpolator( );\r\n\r\n-> file: sndfile.cpp\r\n     function:CSoundFile::CSoundFile()\r\n\t what: [modify] add call to CSoundFile::InitFIRMixer( )\r\n\r\n\t function:CSoundFile::~CSoundFile()\r\n\t what: [modify] add call to CSoundFile::DoneFIRMixer( )\r\n\r\n-> file:  fastmix.cpp\r\n\t new include: <math.h>\r\n\t\twhy: need it for fir-coef calculation\r\n     new function: CSoundFile::InitFIRMixer( ) // initializes fir filter lookup (if necessary)\r\n\t new function: CSoundFile::DoneFIRMixer( ) // decrements ReferenceCounter (for static vars) and deinitializes fir struct (if possible).\r\n\t new defs:\r\n\t\t#define FIRCPWBN 10\t\t\t\t\t\t\t// log2 of number of precalculated wings (-(1L<<FIRCPWBN)..(1L<<FIRCPWBN))\r\n\t\t#define FIRLOPOSSHIFT (16-(FIRCPWBN+1))\t\t// shift for lopos of sampleposition -> (16 - FIRCPWBN - 1)\r\n\t\t#define FIRLEN 9\t\t\t\t\t\t\t// number(-1) of multiplications per sample\r\n\t\t#define FIRCUT 0.90f\t\t\t\t\t\t// cutoff of filter\r\n\t\t#define MIXNDX_FIRMIXERSRC  0x20\t\t\t// src-type for firfilter\r\n\t new vars:\r\n\t\tstatic signed short *cFirLut;\t\t\t\t// lulines\r\n\t\tstatic int bFirInitialized\t= 0;\t\t\t// initialized?\r\n\t\tstatic int nFirOrder\t\t= FIRLEN;\t\t// order (modplug has 4smps pre/post extension, so limit this to 9)\r\n\t\tstatic float nFirFC\t\t\t= FIRCUT;\t\t// cutoff (normalized to pi/2)\r\n\t\tstatic int nFirCpw\t\t\t= (1L<<FIRCPWBN);\t// number of precalculted filter lines\r\n\t\tstatic int nFirUsers\t\t= 0;\t\t\t// reference counter\r\n\t new macros:\r\n\t\t#define SNDMIX_GETMONOVOL8FIRFILTER\r\n\t\t#define SNDMIX_GETMONOVOL16FIRFILTER\r\n\t\t#define SNDMIX_GETSTEREOVOL8FIRFILTER\r\n\t\t#define SNDMIX_GETSTEREOVOL16FIRFILTER\r\n\tnew mixer interface macros:\r\n\t\tBEGIN_MIX_INTERFACE(Mono8BitFirFilterMix)\r\n\t\tBEGIN_MIX_INTERFACE(Mono16BitFirFilterMix)\r\n\t\tBEGIN_RAMPMIX_INTERFACE(Mono8BitFirFilterRampMix)\r\n\t\tBEGIN_RAMPMIX_INTERFACE(Mono16BitFirFilterRampMix)\r\n\t\tBEGIN_MIX_INTERFACE(FastMono8BitFirFilterMix)\r\n\t\tBEGIN_MIX_INTERFACE(FastMono16BitFirFilterMix)\r\n\t\tBEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitFirFilterRampMix)\r\n\t\tBEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitFirFilterRampMix)\r\n\t\tBEGIN_MIX_INTERFACE(Stereo8BitFirFilterMix)\r\n\t\tBEGIN_MIX_INTERFACE(Stereo16BitFirFilterMix)\r\n\t\tBEGIN_RAMPMIX_INTERFACE(Stereo8BitFirFilterRampMix)\r\n\t\tBEGIN_RAMPMIX_INTERFACE(Stereo16BitFirFilterRampMix)\r\n\t\tBEGIN_MIX_FLT_INTERFACE(FilterMono8BitFirFilterMix)\r\n\t\tBEGIN_MIX_FLT_INTERFACE(FilterMono16BitFirFilterMix)\r\n\t\tBEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitFirFilterRampMix)\r\n\t\tBEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitFirFilterRampMix)\r\n\t\tBEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitFirFilterMix)\r\n\t\tBEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitFirFilterMix)\r\n\t\tBEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitFirFilterRampMix)\r\n\t\tBEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitFirFilterRampMix)\r\n\tmodified:\r\n\t\tconst LPMIXINTERFACE gpMixFunctionTable[2*2*16] // to hold new fir mixer interface\r\n\t\tconst LPMIXINTERFACE gpFastMixFunctionTable[2*2*16] // to hold new fir mixer interface\r\n\t\tfunctioN: UINT CSoundFile::CreateStereoMix(int count)\r\n\t\t\tnew:\r\n\t\t\t\tif (!(pChannel->dwFlags & CHN_NOIDO))\r\n\t\t\t\t{\r\n\t\t\t\t\t// use hq-fir mixer?\r\n\t\t\t\t\tif( ((gdwSoundSetup & (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE)) == (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE)) ||\r\n\t\t\t\t\t\t((gdwSoundSetup & (SNDMIX_HQRESAMPLER)) == (SNDMIX_HQRESAMPLER)) )\r\n\t\t\t\t\t\tnFlags += MIXNDX_FIRMIXERSRC;\r\n\t\t\t\t\telse\r\n\t\t\t\t\t\tnFlags += MIXNDX_LINEARSRC; // use\r\n\t\t\t\t}\r\n\t\t\twas:\r\n\t\t\t\tif (!(pChannel->dwFlags & CHN_NOIDO))\r\n\t\t\t\t{\r\n\t\t\t\t\tnFlags += MIXNDX_LINEARSRC; // use\r\n\t\t\t\t}\r\n"
  },
  {
    "path": "contrib/libmodplug/NEWS",
    "content": "Konstanty Bialkowski <konstanty@ieee.org> Tue Apr  7 15:00:00 AEST 2009\n\tNew release of small fixes from contributors and downstream distributions. (Package config Libs.private addin (from Debian)), usage of uint32_t vs uint for load_abc.cpp,load_mid.cpp,load_pat.cpp.\n\nKonstanty Bialkowski <konstanty@ieee.org> Mon Mar 20 10:00:00 AEST 2006\n\tNew Release based on fixes contributed since last release.\n\t\n"
  },
  {
    "path": "contrib/libmodplug/README",
    "content": "libmodplug - the library which was part of the Modplug-xmms project\nWeb page: http://modplug-xmms.sf.net/\n\n Based on the ModPlug sound engine by Olivier Lapicque <olivierl@jps.net>\n XMMS plugin by Kenton Varda <temporal@gauge3d.org> (~2002)\n Maintainer is now Konstanty Bialkowski <konstanty@ieee.org> (~2006)\n\nOn Wed 14 Aug 2013 the repository was forked / cloned to GitHub.\nThe current release is libmodplug v0.8.9.0.\n\nHistory\n-------\nOlivier Lapicque, author of Modplug, which is arguably the best quality\nMOD-playing software available, has placed his sound rendering code in the\npublic domain.  This library and plugin is based on that code.\n\nThis code was originally part of modplug-xmms, and was split into a library - libmodplug\nand the modplug-xmms code. Also since then an example rendering project called modplugplay and\nmodplug123 were introduced. They are still available on the sourceforge website.\n\nFor more information on libmodplug, the library for decoding mod-like music\nformats, see libmodplug/README.\n\nContents\n--------\n 1. Requirements\n 2. Features\n 3. Options\n 4. Troubleshooting\n\n---------------\n1. Requirements\n---------------\n- POSIX OS (Linux or other unix*)\n- XMMS 1.0.0 or higher (only for modplug-xmms plugin).\n\n* This library is only guaranteed to work on Linux.  I have received\n  conflicting reports on whether or not it will work on Solaris x86.\n  One person reported that the plugin compiled fine with the\n  \"-fpermissive\" compiler flag, which I have added.  Others had far\n  more trouble.  Note that a recent change to the library should allow\n  it to work on PPC and other big-endian systems.\n\n* Under linux there is also modplugplay contributed, which allows command\n line playing of mod files under Linux. (Available at http://modplug-xmms.sf.net/)\n\n-----------\n2. Features\n-----------\n- Plays 22 different mod formats, including:\n    MOD, S3M, XM, IT, 669, AMF (both of them), AMS, DBM, DMF, DSM, FAR,\n    MDL, MED, MTM, OKT, PTM, STM, ULT, UMX, MT2, PSM\n- Plays zip, rar, gzip, and bzip2 compressed mods.  The following\n    extensions are recognized: (Only in modplug-xmms)\n    zip:  MDZ,  S3Z,  XMZ,  ITZ\n    rar:  MDR,  S3R,  XMR,  ITR\n    gzip: MDGZ, S3GZ, XMGZ, ITGZ\n   You can also load plain old ZIP, RAR, and GZ files.  If ModPlug finds\n   a mod in them, it will play it.\n   Note: To play these formats, you need to have the associated\n    decompression utilities (unzip, gunzip, unrar) installed.\n   Note(2): The format of the mod is NOT determined from the extension on\n    compressed mods.  For example, if you zipped a UMX mod and gave it the\n    extension MDZ, it would work fine.\n- plays timidity's GUS patch files (*.pat):\n   a multi sample pat file with n samples can be played with a Frere Jacques\n   canon with n voices.\n- plays all types of MIDI files (*.mid):\n   uses the timidity .pat files for samples (when available)\n   recognizes environment variables:\n\n     MMPAT_PATH_TO_CFG\tset to the directory where the file \"timidity.cfg\" and\n      the subdirectory \"instruments\" can be found,\n       default: \"/usr/local/share/timidity\".\n      MMMID_SPEED for experimenting with the mod speed (1 thru 9)\n      MMMID_VERBOSE for feedback on the conversion process\n      MMMID_DEBUG for sake of completeness, only useful for maintainers\n\n- plays textfiles written in the ABC music notation (*.abc):\n   uses the timidity .pat files for samples (when available)\n   recognizes environment variables:\n\n      MMPAT_PATH_TO_CFG\tset to the directory where the file \"timidity.cfg\" and\n       the subdirectory \"instruments\" can be found,\n       default: \"/usr/local/share/timidity\".\n\n      MMABC_NO_RANDOM_PICK when not set and the abc file contains multiple songs\n       (X:n) the first song to be played will be picked at random another click\n       on the play button advances to the next\n       song in the file (or the first when the last song has been\n       played), when set it can be 0 (zero) or not numeric\n       to let it play all songs in the file, a positive number n to\n       let it play the n-th song in the file, a negative number -n to\n       let it play the n-th song in the file and advancing to the next\n       song when the play button is clicked.\n\n      MMABC_DUMPTRACKS when set it gives diagnostic information on stdout,\n       values can be:\n\n        all - every event is printed\n        nonotes - only the control events (looping, breaks etc.) are printed\n           any other value prints the control events and every note event\n           immediately succeeding the control events.\n- Slightly better sound quality than Mikmod.  Vastly superior quality\n   over Winamp.\n- All XMMS calls are supported except for the band gains on the\n   equalizer.  The preamp is supported, but MOD music is not anywhere\n   near as cheap to equalize as MP3.  Thus, equalization does is not\n   supported in this version.  However, a variable bass boost option\n   is available in the configuration dialog (see below).\n- Tons of playback options (see below).\n\n----------\n3. Options\n----------\nAll of the following items are configurable from the plugin\nconfiguration dialog box.\n\nSampling rate: Higher is better.  Note that the sound is rendered at a\n higher sampling rate and converted down to increase quality.\n\nBits per sample: 8-bit or 16-bit sound.  Note that all computations are\n done at 32-bit and converted down to the sampling rate you specify.\n\nChannels: mono/stereo.  Note that all computations are done in stereo.\n If you choose mono, the channels will be mixed.\n\nResampling: Method used to convert samples to different sampling rates.\n \"Nearest\" is the fastest setting (but sounds terrible), while\n \"8-tap fir\" is the best-quality setting.\n\nNoise Reduction: Reduces noise. :)\n\nFast Playlist Info: When this option is on, names of songs in your\n playlist will load considerably faster, but song lengths will not be\n shown and only MOD, S3M, XM, and IT formats will have their names shown.\n Don't worry, though, because all the data which is skipped will still be\n loaded when you actually play the song.  This should probably always be\n on.\n\nReverb: A nice reverb effect.  The depth and delay of the reverb can be\n tuned to your liking using the sliders.\n\nBass boost: Variable bass boost effect.  The \"range\" slider controls the\n frequency range of the bass boost.  If you increase this value, higher\n frequencies will be boosted, but the overall volume increase will be\n less.  (you can compensate by using the volume slider:)\n\nSurround: Dolby Pro-Logic surround effect.  Depth and delay can be fine\n tuned.\n\nPreamp: A global volume boost.  Note that setting the preamp too high\n will cause clipping (nasty clicks and pops).\n\nLooping: Some mods have loops built-in.  Normally, these loops are\n ignored because otherwise the same mod would play forever.  However,\n you can choose to respect the loops, or even set a number of times to\n follow a loop.\n\n------------------\n4. Troubleshooting\n------------------\n\nProblem:\n Some of my files load up, but show garbled info in the playlist and/or\n don't play correctly.\n\nPossible cause:\n The mod is in a different format than its file format suggests.\n Modplug-XMMS uses a combination of file extension and contents to figure\n out what format a mod is in, and can be thrown off if a mod is\n incorrectly labeled.\n\nSolution:\n Turning off \"fast info\" in the configuration may fix the problem.  This\n will cause Modplug-XMMS to detect all basic mod types by content, but\n archive types will still be detected by extension.  If this doesn't\n solve the problem, then you probably have files which are actually\n compressed archives but are not labeled as such.  For example, you may\n have a file \"aws_anew.xm\" which is actually a ZIP archive.  You will\n have to either unzip these files or rename them to have an extension\n associated with their type.  In the case of a ZIP, you can use any of\n the extensions \"ZIP, MDZ, S3Z, XMZ, ITZ\".  (Note that these five types\n are all treated exactly the same -- the actual format of the mod is\n detected by contents.)\n\n\nProblem:\n Everything appears to be working, but no sound is being generated.\n MP3's play just fine.\n\nPossible cause:\n Modplug has a relatively low default volume, and you may just not be\n hearing it.  (Note:  Yes, more that one person has e-mailed me with\n this problem.)\n\nSolution:\n Turn up your volume.  You may wish to do this via the \"preamp\"\n setting in the ModPlug configuration.  This way, you won't have to\n turn down your volume again when you play an MP3.\n\n\nProblem:\n You have a mod which is rendered incorrectly by ModPlug-XMMS.\n\nPossible cause:\n This could be our fault. :)\n\nSolution:\n First, test the mod using the Windows version of ModPlug, if you can.\n If it sounds wrong there, then send the mod and a bug report to\n Olivier Lapicque <olivierl@jps.net>.  If the mod plays correctly in\n Windows, however, then the bug is my fault.  In that case, e-mail\n me (Konstanty) <konstanty@ieee.org>. (previously Kenton Varda at\n <temporal@gauge3d.org>).\n\n\nProblem:\n I have a problem which is not listed here, or an idea for a cool\n feature.\n\nSolution:\n E-mail me (Konstanty) at <konstanty@ieee.org>.  I would be\n happy to hear any suggestions or problems you have.\n"
  },
  {
    "path": "contrib/libmodplug/TODO",
    "content": "Fix and check endian-ness issues.\nAdd looping flag to sound file / API function for this.\n"
  },
  {
    "path": "contrib/libmodplug/src/Makefile.in",
    "content": "##\n# libmodplug Makefile fragment\n##\n\n.PHONY: ${libmodplug}_clean\n\n.SUFFIXES: .cpp\n\nlibmodplug_src = contrib/libmodplug/src\nlibmodplug_obj = contrib/libmodplug/src/.build\n\nlibmodplug_cflags = -DHAVE_INTTYPES_H -I${libmodplug_src} \\\n                    -I${libmodplug_src}/libmodplug \\\n                    -include src/platform_endian.h\n\nlibmodplug_cflags += -Wno-missing-declarations\n\n#\n# Rather than bloat the patches to fix these warnings, just disable them.\n#\nifneq (${HAS_W_NO_IMPLICIT_FALLTHROUGH},)\nlibmodplug_cflags += -Wno-implicit-fallthrough\nendif\n\nlibmodplug_objs = \\\n    ${libmodplug_obj}/fastmix.o \\\n    ${libmodplug_obj}/load_669.o \\\n    ${libmodplug_obj}/load_amf.o \\\n    ${libmodplug_obj}/load_dsm.o \\\n    ${libmodplug_obj}/load_far.o \\\n    ${libmodplug_obj}/load_gdm.o \\\n    ${libmodplug_obj}/load_it.o \\\n    ${libmodplug_obj}/load_med.o \\\n    ${libmodplug_obj}/load_mod.o \\\n    ${libmodplug_obj}/load_mtm.o \\\n    ${libmodplug_obj}/load_okt.o \\\n    ${libmodplug_obj}/load_s3m.o \\\n    ${libmodplug_obj}/load_stm.o \\\n    ${libmodplug_obj}/load_ult.o \\\n    ${libmodplug_obj}/load_wav.o \\\n    ${libmodplug_obj}/load_xm.o \\\n    ${libmodplug_obj}/mmcmp.o \\\n    ${libmodplug_obj}/modplug.o \\\n    ${libmodplug_obj}/snd_dsp.o \\\n    ${libmodplug_obj}/sndfile.o \\\n    ${libmodplug_obj}/snd_flt.o \\\n    ${libmodplug_obj}/snd_fx.o \\\n    ${libmodplug_obj}/sndmix.o\n\n${libmodplug_obj}/%.o: ${libmodplug_src}/%.cpp src/config.h\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CXX} -MD ${CXXFLAGS} ${libmodplug_cflags} -c $< -o $@\n\n-include $(libmodplug_objs:.o=.d)\n\n${libmodplug_objs}: $(filter-out $(wildcard ${libmodplug_obj}), ${libmodplug_obj})\n\nlibmodplug_clean:\n\t$(if ${V},,@echo \"  RM      \" ${libmodplug_obj})\n\t${RM} -r ${libmodplug_obj}\n"
  },
  {
    "path": "contrib/libmodplug/src/fastmix.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n *          Markus Fick <webmaster@mark-f.de> spline + fir-resampler\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n#include <math.h>\n\n#ifdef MSC_VER\n#pragma bss_seg(\".modplug\")\n#endif\n\n// Front Mix Buffer (Also room for interleaved rear mix)\nint MixSoundBuffer[MIXBUFFERSIZE*4];\n\n// Reverb Mix Buffer\n#ifndef MODPLUG_NO_REVERB\nint MixReverbBuffer[MIXBUFFERSIZE*2];\nextern UINT gnReverbSend;\n#endif\n\n#ifndef MODPLUG_FASTSOUNDLIB\nint MixRearBuffer[MIXBUFFERSIZE*2];\nfloat MixFloatBuffer[MIXBUFFERSIZE*2];\n#endif\n\n#ifdef MSC_VER\n#pragma bss_seg()\n#endif\n\n\nextern LONG gnDryROfsVol;\nextern LONG gnDryLOfsVol;\nextern LONG gnRvbROfsVol;\nextern LONG gnRvbLOfsVol;\n\n// 4x256 taps polyphase FIR resampling filter\nextern short int gFastSinc[];\nextern short int gKaiserSinc[]; // 8-taps polyphase\n/*\n *-----------------------------------------------------------------------------\n cubic spline interpolation doc,\n   (derived from \"digital image warping\", g. wolberg)\n\n   interpolation polynomial: f(x) = A3*(x-floor(x))**3 + A2*(x-floor(x))**2 +\n     A1*(x-floor(x)) + A0\n\n   with Y = equispaced data points (dist=1), YD = first derivates of data points and IP = floor(x)\n   the A[0..3] can be found by solving\n     A0  = Y[IP]\n     A1  = YD[IP]\n     A2  = 3*(Y[IP+1]-Y[IP])-2.0*YD[IP]-YD[IP+1]\n     A3  = -2.0 * (Y[IP+1]-Y[IP]) + YD[IP] - YD[IP+1]\n\n   with the first derivates as\n     YD[IP]    = 0.5 * (Y[IP+1] - Y[IP-1]);\n     YD[IP+1]  = 0.5 * (Y[IP+2] - Y[IP])\n\n   the coefs becomes\n     A0 = Y[IP]\n     A1 = YD[IP]\n        =  0.5*(Y[IP+1] - Y[IP-1]);\n     A2 =  3.0*(Y[IP+1]-Y[IP])-2.0*YD[IP]-YD[IP+1]\n        =  3.0*(Y[IP+1]-Y[IP]) - 0.5*2.0*(Y[IP+1]-Y[IP-1]) - 0.5*(Y[IP+2]-Y[IP])\n        =  3.0*Y[IP+1] - 3.0*Y[IP] - Y[IP+1] + Y[IP-1] - 0.5*Y[IP+2] + 0.5*Y[IP]\n        = -0.5*Y[IP+2] + 2.0 * Y[IP+1] - 2.5*Y[IP] + Y[IP-1]\n        = Y[IP-1] + 2 * Y[IP+1] - 0.5 * (5.0 * Y[IP] + Y[IP+2])\n     A3 = -2.0*(Y[IP+1]-Y[IP]) + YD[IP] + YD[IP+1]\n        = -2.0*Y[IP+1] + 2.0*Y[IP] + 0.5*(Y[IP+1]-Y[IP-1]) + 0.5*(Y[IP+2]-Y[IP])\n        = -2.0*Y[IP+1] + 2.0*Y[IP] + 0.5*Y[IP+1] - 0.5*Y[IP-1] + 0.5*Y[IP+2] - 0.5*Y[IP]\n        =  0.5 * Y[IP+2] - 1.5 * Y[IP+1] + 1.5 * Y[IP] - 0.5 * Y[IP-1]\n\t=  0.5 * (3.0 * (Y[IP] - Y[IP+1]) - Y[IP-1] + YP[IP+2])\n\n   then interpolated data value is (horner rule)\n     out = (((A3*x)+A2)*x+A1)*x+A0\n\n   this gives parts of data points Y[IP-1] to Y[IP+2] of\n     part       x**3    x**2    x**1    x**0\n      Y[IP-1]    -0.5     1      -0.5    0\n      Y[IP]       1.5    -2.5     0      1\n      Y[IP+1]    -1.5     2       0.5    0\n      Y[IP+2]     0.5    -0.5     0      0\n *---------------------------------------------------------------------------\n */\n// number of bits used to scale spline coefs\n#define SPLINE_QUANTBITS\t14\n#define SPLINE_QUANTSCALE\t(1L<<SPLINE_QUANTBITS)\n#define SPLINE_8SHIFT\t\t(SPLINE_QUANTBITS-8)\n#define SPLINE_16SHIFT\t\t(SPLINE_QUANTBITS)\n// forces coefsset to unity gain\n#define SPLINE_CLAMPFORUNITY\n// log2(number) of precalculated splines (range is [4..14])\n#define SPLINE_FRACBITS 10\n\nclass CzCUBICSPLINE\n{\tpublic:\n\t\tCzCUBICSPLINE( );\n\t\t~CzCUBICSPLINE( );\n\t\tstatic signed short lut[4*(1L<<SPLINE_FRACBITS)];\n};\n\nsigned short CzCUBICSPLINE::lut[4*(1L<<SPLINE_FRACBITS)];\n\nCzCUBICSPLINE::CzCUBICSPLINE( )\n{\tint _LIi;\n\tint _LLen\t\t= (1L<<SPLINE_FRACBITS);\n\tfloat _LFlen\t= 1.0f / (float)_LLen;\n\tfloat _LScale\t= (float)SPLINE_QUANTSCALE;\n\tfor(_LIi=0;_LIi<_LLen;_LIi++)\n\t{\tfloat _LCm1, _LC0, _LC1, _LC2;\n\t\tfloat _LX = ((float)_LIi)*_LFlen;\n\t\tint _LSum,_LIdx\t= _LIi<<2;\n\t\t_LCm1 = (float)floor( 0.5 + _LScale*(-0.5*_LX*_LX*_LX + 1.0*_LX*_LX - 0.5*_LX ) );\n\t\t_LC0 = (float)floor( 0.5 + _LScale*( 1.5*_LX*_LX*_LX - 2.5*_LX*_LX + 1.0 ) );\n\t\t_LC1 = (float)floor( 0.5 + _LScale*(-1.5*_LX*_LX*_LX + 2.0*_LX*_LX + 0.5*_LX ) );\n\t\t_LC2 = (float)floor( 0.5 + _LScale*( 0.5*_LX*_LX*_LX - 0.5*_LX*_LX) );\n\t\tlut[_LIdx+0] = (signed short)( (_LCm1 < -_LScale) ? -_LScale : ((_LCm1 > _LScale) ? _LScale : _LCm1) );\n\t\tlut[_LIdx+1] = (signed short)( (_LC0  < -_LScale) ? -_LScale : ((_LC0  > _LScale) ? _LScale : _LC0 ) );\n\t\tlut[_LIdx+2] = (signed short)( (_LC1  < -_LScale) ? -_LScale : ((_LC1  > _LScale) ? _LScale : _LC1 ) );\n\t\tlut[_LIdx+3] = (signed short)( (_LC2  < -_LScale) ? -_LScale : ((_LC2  > _LScale) ? _LScale : _LC2 ) );\n#ifdef SPLINE_CLAMPFORUNITY\n\t\t_LSum = lut[_LIdx+0]+lut[_LIdx+1]+lut[_LIdx+2]+lut[_LIdx+3];\n\t\tif( _LSum != SPLINE_QUANTSCALE )\n\t\t{\tint _LMax = _LIdx;\n\t\t\tif( lut[_LIdx+1]>lut[_LMax] ) _LMax = _LIdx+1;\n\t\t\tif( lut[_LIdx+2]>lut[_LMax] ) _LMax = _LIdx+2;\n\t\t\tif( lut[_LIdx+3]>lut[_LMax] ) _LMax = _LIdx+3;\n\t\t\tlut[_LMax] += ((signed short)SPLINE_QUANTSCALE-_LSum);\n\t\t}\n#endif\n\t}\n}\n\nCzCUBICSPLINE::~CzCUBICSPLINE( )\n{\t// nothing todo\n}\n\nCzCUBICSPLINE sspline;\n\n/*\n  ------------------------------------------------------------------------------\n   fir interpolation doc,\n     (derived from \"an engineer's guide to fir digital filters\", n.j. loy)\n\n     calculate coefficients for ideal lowpass filter (with cutoff = fc in\n\t0..1 (mapped to 0..nyquist))\n     c[-N..N] = (i==0) ? fc : sin(fc*pi*i)/(pi*i)\n\n     then apply selected window to coefficients\n      c[-N..N] *= w(0..N)\n     with n in 2*N and w(n) being a window function (see loy)\n\n     then calculate gain and scale filter coefs to have unity gain.\n  ------------------------------------------------------------------------------\n*/\n// quantizer scale of window coefs\n#define WFIR_QUANTBITS\t\t15\n#define WFIR_QUANTSCALE\t\t(1L<<WFIR_QUANTBITS)\n#define WFIR_8SHIFT\t\t\t(WFIR_QUANTBITS-8)\n#define WFIR_16BITSHIFT\t\t(WFIR_QUANTBITS)\n// log2(number)-1 of precalculated taps range is [4..12]\n#define WFIR_FRACBITS\t\t10\n#define WFIR_LUTLEN\t\t\t((1L<<(WFIR_FRACBITS+1))+1)\n// number of samples in window\n#define WFIR_LOG2WIDTH\t\t3\n#define WFIR_WIDTH\t\t\t(1L<<WFIR_LOG2WIDTH)\n// cutoff (1.0 == pi/2)\n#define WFIR_CUTOFF\t\t0.90f\n// wfir type\n#define WFIR_HANN\t\t0\n#define WFIR_HAMMING\t\t1\n#define WFIR_BLACKMANEXACT\t2\n#define WFIR_BLACKMAN3T61\t3\n#define WFIR_BLACKMAN3T67\t4\n#define WFIR_BLACKMAN4T92\t5\n#define WFIR_BLACKMAN4T74\t6\n#define WFIR_KAISER4T\t\t7\n#define WFIR_TYPE\t\tWFIR_BLACKMANEXACT\n// wfir help\n#ifndef M_zPI\n#define M_zPI\t\t3.1415926535897932384626433832795\n#endif\n#define M_zEPS\t\t1e-8\n\nclass CzWINDOWEDFIR\n{\npublic:\n\tCzWINDOWEDFIR( );\n\t~CzWINDOWEDFIR( );\n\tfloat coef( int _PCnr, float _POfs, float _PCut, int _PWidth, int _PType )\n//OLD args to coef: float _PPos, float _PFc, int _PLen )\n\t{\n\t\tdouble\t_LWidthM1       = _PWidth-1;\n\t\tdouble\t_LWidthM1Half   = 0.5*_LWidthM1;\n\t\tdouble\t_LPosU          = ((double)_PCnr - _POfs);\n\t\tdouble\t_LPos           = _LPosU-_LWidthM1Half;\n\t\tdouble\t_LPIdl          = 2.0*M_zPI/_LWidthM1;\n\t\tdouble\t_LWc,_LSi;\n\t\tif( fabs(_LPos)<M_zEPS ) {\n\t\t\t_LWc\t= 1.0;\n\t\t\t_LSi\t= _PCut;\n\t\t} else {\n\t\t\tswitch( _PType )\n\t\t\t{\n\t\t\tcase WFIR_HANN:\n\t\t\t\t_LWc = 0.50 - 0.50 * cos(_LPIdl*_LPosU);\n\t\t\t\tbreak;\n\t\t\tcase WFIR_HAMMING:\n\t\t\t\t_LWc = 0.54 - 0.46 * cos(_LPIdl*_LPosU);\n\t\t\t\tbreak;\n\t\t\tcase WFIR_BLACKMANEXACT:\n\t\t\t\t_LWc = 0.42 - 0.50 * cos(_LPIdl*_LPosU) +\n\t\t\t\t\t0.08 * cos(2.0*_LPIdl*_LPosU);\n\t\t\t\tbreak;\n\t\t\tcase WFIR_BLACKMAN3T61:\n\t\t\t\t_LWc = 0.44959 - 0.49364 * cos(_LPIdl*_LPosU) +\n\t\t\t\t\t0.05677 * cos(2.0*_LPIdl*_LPosU);\n\t\t\t\tbreak;\n\t\t\tcase WFIR_BLACKMAN3T67:\n\t\t\t\t_LWc = 0.42323 - 0.49755 * cos(_LPIdl*_LPosU) +\n\t\t\t\t\t0.07922 * cos(2.0*_LPIdl*_LPosU);\n\t\t\t\tbreak;\n\t\t\tcase WFIR_BLACKMAN4T92:\n\t\t\t\t_LWc = 0.35875 - 0.48829 * cos(_LPIdl*_LPosU) +\n\t\t\t\t\t0.14128 * cos(2.0*_LPIdl*_LPosU) -\n\t\t\t\t\t0.01168 * cos(3.0*_LPIdl*_LPosU);\n\t\t\t\tbreak;\n\t\t\tcase WFIR_BLACKMAN4T74:\n\t\t\t\t_LWc = 0.40217 - 0.49703 * cos(_LPIdl*_LPosU) +\n\t\t\t\t\t0.09392 * cos(2.0*_LPIdl*_LPosU) -\n\t\t\t\t\t0.00183 * cos(3.0*_LPIdl*_LPosU);\n\t\t\t\tbreak;\n\t\t\tcase WFIR_KAISER4T:\n\t\t\t\t_LWc = 0.40243 - 0.49804 * cos(_LPIdl*_LPosU) +\n\t\t\t\t\t0.09831 * cos(2.0*_LPIdl*_LPosU) -\n\t\t\t\t\t0.00122 * cos(3.0*_LPIdl*_LPosU);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t_LWc = 1.0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t_LPos\t *= M_zPI;\n\t\t\t_LSi\t = sin(_PCut*_LPos)/_LPos;\n\t\t}\n\t\treturn (float)(_LWc*_LSi);\n\t}\n\tstatic signed short lut[WFIR_LUTLEN*WFIR_WIDTH];\n};\n\nsigned short CzWINDOWEDFIR::lut[WFIR_LUTLEN*WFIR_WIDTH];\n\nCzWINDOWEDFIR::CzWINDOWEDFIR()\n{\n\tint _LPcl;\n\tfloat _LPcllen\t= (float)(1L<<WFIR_FRACBITS);\t// number of precalculated lines for 0..1 (-1..0)\n\tfloat _LNorm\t= 1.0f / (float)(2.0f * _LPcllen);\n\tfloat _LCut\t\t= WFIR_CUTOFF;\n\tfloat _LScale\t= (float)WFIR_QUANTSCALE;\n\tfor( _LPcl=0;_LPcl<WFIR_LUTLEN;_LPcl++ )\n\t{\n\t\tfloat _LGain,_LCoefs[WFIR_WIDTH];\n\t\tfloat _LOfs\t\t= ((float)_LPcl-_LPcllen)*_LNorm;\n\t\tint _LCc,_LIdx\t= _LPcl<<WFIR_LOG2WIDTH;\n\t\tfor( _LCc=0,_LGain=0.0f;_LCc<WFIR_WIDTH;_LCc++ )\n\t\t{\t_LGain\t+= (_LCoefs[_LCc] = coef( _LCc, _LOfs, _LCut, WFIR_WIDTH, WFIR_TYPE ));\n\t\t}\n\t\t_LGain = 1.0f/_LGain;\n\t\tfor( _LCc=0;_LCc<WFIR_WIDTH;_LCc++ )\n\t\t{\tfloat _LCoef = (float)floor( 0.5 + _LScale*_LCoefs[_LCc]*_LGain );\n\t\tlut[_LIdx+_LCc] = (signed short)( (_LCoef<-_LScale)?-_LScale:((_LCoef>_LScale)?_LScale:_LCoef) );\n\t\t}\n\t}\n}\n\nCzWINDOWEDFIR::~CzWINDOWEDFIR()\n{\t// nothing todo\n}\n\nCzWINDOWEDFIR sfir;\n\n// ----------------------------------------------------------------------------\n// MIXING MACROS\n// ----------------------------------------------------------------------------\n#define SNDMIX_BEGINSAMPLELOOP8\\\n\tregister MODCHANNEL * const pChn = pChannel;\\\n\tnPos = pChn->nPosLo;\\\n\tconst signed char *p = (signed char *)(pChn->pCurrentSample+pChn->nPos);\\\n\tif (pChn->dwFlags & CHN_STEREO) p += pChn->nPos;\\\n\tint *pvol = pbuffer;\\\n\tdo {\n\n#define SNDMIX_BEGINSAMPLELOOP16\\\n\tregister MODCHANNEL * const pChn = pChannel;\\\n\tnPos = pChn->nPosLo;\\\n\tconst signed short *p = (signed short *)(pChn->pCurrentSample+(pChn->nPos*2));\\\n\tif (pChn->dwFlags & CHN_STEREO) p += pChn->nPos;\\\n\tint *pvol = pbuffer;\\\n\tdo {\n\n#define SNDMIX_ENDSAMPLELOOP\\\n\t\tnPos += pChn->nInc;\\\n\t} while (pvol < pbufmax);\\\n\tpChn->nPos += nPos >> 16;\\\n\tpChn->nPosLo = nPos & 0xFFFF;\n\n//////////////////////////////////////////////////////////////////////////////\n// Mono\n\n// No interpolation\n#define SNDMIX_GETMONOVOL8NOIDO\\\n\tint vol = p[nPos >> 16] << 8;\n\n#define SNDMIX_GETMONOVOL16NOIDO\\\n\tint vol = p[nPos >> 16];\n\n// Linear Interpolation\n#define SNDMIX_GETMONOVOL8LINEAR\\\n\tint poshi = nPos >> 16;\\\n\tint poslo = (nPos >> 8) & 0xFF;\\\n\tint srcvol = p[poshi];\\\n\tint destvol = p[poshi+1];\\\n\tint vol = (srcvol<<8) + ((int)(poslo * (destvol - srcvol)));\n\n#define SNDMIX_GETMONOVOL16LINEAR\\\n\tint poshi = nPos >> 16;\\\n\tint poslo = (nPos >> 8) & 0xFF;\\\n\tint srcvol = p[poshi];\\\n\tint destvol = p[poshi+1];\\\n\tint vol = srcvol + ((int)(poslo * (destvol - srcvol)) >> 8);\n\n// spline interpolation (2 guard bits should be enough???)\n#define SPLINE_FRACSHIFT ((16-SPLINE_FRACBITS)-2)\n#define SPLINE_FRACMASK  (((1L<<(16-SPLINE_FRACSHIFT))-1)&~3)\n\n#define SNDMIX_GETMONOVOL8SPLINE \\\n\tint poshi\t= nPos >> 16; \\\n\tint poslo\t= (nPos >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \\\n\tint vol\t\t= (CzCUBICSPLINE::lut[poslo  ]*(int)p[poshi-1] + \\\n\t               CzCUBICSPLINE::lut[poslo+1]*(int)p[poshi  ] + \\\n\t               CzCUBICSPLINE::lut[poslo+3]*(int)p[poshi+2] + \\\n\t               CzCUBICSPLINE::lut[poslo+2]*(int)p[poshi+1]) >> SPLINE_8SHIFT;\n\n#define SNDMIX_GETMONOVOL16SPLINE \\\n\tint poshi\t= nPos >> 16; \\\n\tint poslo\t= (nPos >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \\\n\tint vol\t\t= (CzCUBICSPLINE::lut[poslo  ]*(int)p[poshi-1] + \\\n\t               CzCUBICSPLINE::lut[poslo+1]*(int)p[poshi  ] + \\\n\t               CzCUBICSPLINE::lut[poslo+3]*(int)p[poshi+2] + \\\n\t               CzCUBICSPLINE::lut[poslo+2]*(int)p[poshi+1]) >> SPLINE_16SHIFT;\n\n\n// fir interpolation\n#define WFIR_FRACSHIFT\t(16-(WFIR_FRACBITS+1+WFIR_LOG2WIDTH))\n#define WFIR_FRACMASK\t((((1L<<(17-WFIR_FRACSHIFT))-1)&~((1L<<WFIR_LOG2WIDTH)-1)))\n#define WFIR_FRACHALVE\t(1L<<(16-(WFIR_FRACBITS+2)))\n\n#define SNDMIX_GETMONOVOL8FIRFILTER \\\n\tint poshi  = nPos >> 16;\\\n\tint poslo  = (nPos & 0xFFFF);\\\n\tint firidx = ((poslo+WFIR_FRACHALVE)>>WFIR_FRACSHIFT) & WFIR_FRACMASK; \\\n\tint vol    = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[poshi+1-4]);\t\\\n            vol   += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[poshi+2-4]);\t\\\n            vol   += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[poshi+3-4]);\t\\\n            vol   += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[poshi+4-4]);\t\\\n            vol   += (CzWINDOWEDFIR::lut[firidx+4]*(int)p[poshi+5-4]);\t\\\n            vol   += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[poshi+6-4]);\t\\\n            vol   += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[poshi+7-4]);\t\\\n            vol   += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[poshi+8-4]);\t\\\n            vol  >>= WFIR_8SHIFT;\n\n#define SNDMIX_GETMONOVOL16FIRFILTER \\\n    int poshi  = nPos >> 16;\\\n    int poslo  = (nPos & 0xFFFF);\\\n    int firidx = ((poslo+WFIR_FRACHALVE)>>WFIR_FRACSHIFT) & WFIR_FRACMASK; \\\n    int vol1   = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[poshi+1-4]);\t\\\n        vol1  += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[poshi+2-4]);\t\\\n        vol1  += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[poshi+3-4]);\t\\\n        vol1  += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[poshi+4-4]);\t\\\n    int vol2   = (CzWINDOWEDFIR::lut[firidx+4]*(int)p[poshi+5-4]);\t\\\n\tvol2  += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[poshi+6-4]);\t\\\n\tvol2  += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[poshi+7-4]);\t\\\n\tvol2  += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[poshi+8-4]);\t\\\n    int vol    = ((vol1>>1)+(vol2>>1)) >> (WFIR_16BITSHIFT-1);\n\n/////////////////////////////////////////////////////////////////////////////\n// Stereo\n\n// No interpolation\n#define SNDMIX_GETSTEREOVOL8NOIDO\\\n    int vol_l = p[(nPos>>16)*2] << 8;\\\n    int vol_r = p[(nPos>>16)*2+1] << 8;\n\n#define SNDMIX_GETSTEREOVOL16NOIDO\\\n    int vol_l = p[(nPos>>16)*2];\\\n    int vol_r = p[(nPos>>16)*2+1];\n\n// Linear Interpolation\n#define SNDMIX_GETSTEREOVOL8LINEAR\\\n    int poshi = nPos >> 16;\\\n    int poslo = (nPos >> 8) & 0xFF;\\\n    int srcvol_l = p[poshi*2];\\\n    int vol_l = (srcvol_l<<8) + ((int)(poslo * (p[poshi*2+2] - srcvol_l)));\\\n    int srcvol_r = p[poshi*2+1];\\\n    int vol_r = (srcvol_r<<8) + ((int)(poslo * (p[poshi*2+3] - srcvol_r)));\n\n#define SNDMIX_GETSTEREOVOL16LINEAR\\\n    int poshi = nPos >> 16;\\\n    int poslo = (nPos >> 8) & 0xFF;\\\n    int srcvol_l = p[poshi*2];\\\n    int vol_l = srcvol_l + ((int)(poslo * (p[poshi*2+2] - srcvol_l)) >> 8);\\\n    int srcvol_r = p[poshi*2+1];\\\n    int vol_r = srcvol_r + ((int)(poslo * (p[poshi*2+3] - srcvol_r)) >> 8);\\\n\n// Spline Interpolation\n#define SNDMIX_GETSTEREOVOL8SPLINE \\\n    int poshi\t= nPos >> 16; \\\n    int poslo\t= (nPos >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \\\n    int vol_l\t= (CzCUBICSPLINE::lut[poslo  ]*(int)p[(poshi-1)*2  ] + \\\n\t           CzCUBICSPLINE::lut[poslo+1]*(int)p[(poshi  )*2  ] + \\\n\t           CzCUBICSPLINE::lut[poslo+2]*(int)p[(poshi+1)*2  ] + \\\n\t           CzCUBICSPLINE::lut[poslo+3]*(int)p[(poshi+2)*2  ]) >> SPLINE_8SHIFT; \\\n    int vol_r\t= (CzCUBICSPLINE::lut[poslo  ]*(int)p[(poshi-1)*2+1] + \\\n\t           CzCUBICSPLINE::lut[poslo+1]*(int)p[(poshi  )*2+1] + \\\n\t           CzCUBICSPLINE::lut[poslo+2]*(int)p[(poshi+1)*2+1] + \\\n\t           CzCUBICSPLINE::lut[poslo+3]*(int)p[(poshi+2)*2+1]) >> SPLINE_8SHIFT;\n\n#define SNDMIX_GETSTEREOVOL16SPLINE \\\n    int poshi\t= nPos >> 16; \\\n    int poslo\t= (nPos >> SPLINE_FRACSHIFT) & SPLINE_FRACMASK; \\\n    int vol_l\t= (CzCUBICSPLINE::lut[poslo  ]*(int)p[(poshi-1)*2  ] + \\\n\t           CzCUBICSPLINE::lut[poslo+1]*(int)p[(poshi  )*2  ] + \\\n\t           CzCUBICSPLINE::lut[poslo+2]*(int)p[(poshi+1)*2  ] + \\\n\t           CzCUBICSPLINE::lut[poslo+3]*(int)p[(poshi+2)*2  ]) >> SPLINE_16SHIFT; \\\n    int vol_r\t= (CzCUBICSPLINE::lut[poslo  ]*(int)p[(poshi-1)*2+1] + \\\n\t           CzCUBICSPLINE::lut[poslo+1]*(int)p[(poshi  )*2+1] + \\\n\t           CzCUBICSPLINE::lut[poslo+2]*(int)p[(poshi+1)*2+1] + \\\n\t           CzCUBICSPLINE::lut[poslo+3]*(int)p[(poshi+2)*2+1]) >> SPLINE_16SHIFT;\n\n// fir interpolation\n#define SNDMIX_GETSTEREOVOL8FIRFILTER \\\n    int poshi   = nPos >> 16;\\\n    int poslo   = (nPos & 0xFFFF);\\\n    int firidx  = ((poslo+WFIR_FRACHALVE)>>WFIR_FRACSHIFT) & WFIR_FRACMASK; \\\n    int vol_l   = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[(poshi+1-4)*2  ]);   \\\n\tvol_l  += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[(poshi+2-4)*2  ]);   \\\n\tvol_l  += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[(poshi+3-4)*2  ]);   \\\n        vol_l  += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[(poshi+4-4)*2  ]);   \\\n        vol_l  += (CzWINDOWEDFIR::lut[firidx+4]*(int)p[(poshi+5-4)*2  ]);   \\\n\tvol_l  += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[(poshi+6-4)*2  ]);   \\\n\tvol_l  += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[(poshi+7-4)*2  ]);   \\\n        vol_l  += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[(poshi+8-4)*2  ]);   \\\n\tvol_l >>= WFIR_8SHIFT; \\\n    int vol_r   = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[(poshi+1-4)*2+1]);   \\\n\tvol_r  += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[(poshi+2-4)*2+1]);   \\\n\tvol_r  += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[(poshi+3-4)*2+1]);   \\\n\tvol_r  += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[(poshi+4-4)*2+1]);   \\\n\tvol_r  += (CzWINDOWEDFIR::lut[firidx+4]*(int)p[(poshi+5-4)*2+1]);   \\\n        vol_r  += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[(poshi+6-4)*2+1]);   \\\n        vol_r  += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[(poshi+7-4)*2+1]);   \\\n        vol_r  += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[(poshi+8-4)*2+1]);   \\\n        vol_r >>= WFIR_8SHIFT;\n\n#define SNDMIX_GETSTEREOVOL16FIRFILTER \\\n    int poshi   = nPos >> 16;\\\n    int poslo   = (nPos & 0xFFFF);\\\n    int firidx  = ((poslo+WFIR_FRACHALVE)>>WFIR_FRACSHIFT) & WFIR_FRACMASK; \\\n    int vol1_l  = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[(poshi+1-4)*2  ]);   \\\n\tvol1_l += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[(poshi+2-4)*2  ]);   \\\n        vol1_l += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[(poshi+3-4)*2  ]);   \\\n\tvol1_l += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[(poshi+4-4)*2  ]);   \\\n   int vol2_l  = (CzWINDOWEDFIR::lut[firidx+4]*(int)p[(poshi+5-4)*2  ]);    \\\n       vol2_l += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[(poshi+6-4)*2  ]);    \\\n       vol2_l += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[(poshi+7-4)*2  ]);    \\\n       vol2_l += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[(poshi+8-4)*2  ]);    \\\n   int vol_l   = ((vol1_l>>1)+(vol2_l>>1)) >> (WFIR_16BITSHIFT-1); \\\n   int vol1_r  = (CzWINDOWEDFIR::lut[firidx+0]*(int)p[(poshi+1-4)*2+1]);    \\\n       vol1_r += (CzWINDOWEDFIR::lut[firidx+1]*(int)p[(poshi+2-4)*2+1]);    \\\n       vol1_r += (CzWINDOWEDFIR::lut[firidx+2]*(int)p[(poshi+3-4)*2+1]);    \\\n       vol1_r += (CzWINDOWEDFIR::lut[firidx+3]*(int)p[(poshi+4-4)*2+1]);    \\\n   int vol2_r  = (CzWINDOWEDFIR::lut[firidx+4]*(int)p[(poshi+5-4)*2+1]);    \\\n       vol2_r += (CzWINDOWEDFIR::lut[firidx+5]*(int)p[(poshi+6-4)*2+1]);    \\\n       vol2_r += (CzWINDOWEDFIR::lut[firidx+6]*(int)p[(poshi+7-4)*2+1]);    \\\n       vol2_r += (CzWINDOWEDFIR::lut[firidx+7]*(int)p[(poshi+8-4)*2+1]);    \\\n   int vol_r   = ((vol1_r>>1)+(vol2_r>>1)) >> (WFIR_16BITSHIFT-1);\n\n/////////////////////////////////////////////////////////////////////////////\n\n#define SNDMIX_STOREMONOVOL\\\n\tpvol[0] += vol * pChn->nRightVol;\\\n\tpvol[1] += vol * pChn->nLeftVol;\\\n\tpvol += 2;\n\n#define SNDMIX_STORESTEREOVOL\\\n\tpvol[0] += vol_l * pChn->nRightVol;\\\n\tpvol[1] += vol_r * pChn->nLeftVol;\\\n\tpvol += 2;\n\n#define SNDMIX_STOREFASTMONOVOL\\\n\tint v = vol * pChn->nRightVol;\\\n\tpvol[0] += v;\\\n\tpvol[1] += v;\\\n\tpvol += 2;\n\n#define SNDMIX_RAMPMONOVOL\\\n\tnRampLeftVol += pChn->nLeftRamp;\\\n\tnRampRightVol += pChn->nRightRamp;\\\n\tpvol[0] += vol * (nRampRightVol >> VOLUMERAMPPRECISION);\\\n\tpvol[1] += vol * (nRampLeftVol >> VOLUMERAMPPRECISION);\\\n\tpvol += 2;\n\n#define SNDMIX_RAMPFASTMONOVOL\\\n\tnRampRightVol += pChn->nRightRamp;\\\n\tint fastvol = vol * (nRampRightVol >> VOLUMERAMPPRECISION);\\\n\tpvol[0] += fastvol;\\\n\tpvol[1] += fastvol;\\\n\tpvol += 2;\n\n#define SNDMIX_RAMPSTEREOVOL\\\n\tnRampLeftVol += pChn->nLeftRamp;\\\n\tnRampRightVol += pChn->nRightRamp;\\\n\tpvol[0] += vol_l * (nRampRightVol >> VOLUMERAMPPRECISION);\\\n\tpvol[1] += vol_r * (nRampLeftVol >> VOLUMERAMPPRECISION);\\\n\tpvol += 2;\n\n\n///////////////////////////////////////////////////\n// Resonant Filters\n\n// Mono\n#define MIX_BEGIN_FILTER\\\n\tint fy1 = pChannel->nFilter_Y1;\\\n\tint fy2 = pChannel->nFilter_Y2;\\\n\n#define MIX_END_FILTER\\\n\tpChannel->nFilter_Y1 = fy1;\\\n\tpChannel->nFilter_Y2 = fy2;\n\n#define SNDMIX_PROCESSFILTER\\\n\tvol = (vol * pChn->nFilter_A0 + fy1 * pChn->nFilter_B0 + fy2 * pChn->nFilter_B1 + 4096) >> 13;\\\n\tfy2 = fy1;\\\n\tfy1 = vol;\\\n\n// Stereo\n#define MIX_BEGIN_STEREO_FILTER\\\n\tint fy1 = pChannel->nFilter_Y1;\\\n\tint fy2 = pChannel->nFilter_Y2;\\\n\tint fy3 = pChannel->nFilter_Y3;\\\n\tint fy4 = pChannel->nFilter_Y4;\\\n\n#define MIX_END_STEREO_FILTER\\\n\tpChannel->nFilter_Y1 = fy1;\\\n\tpChannel->nFilter_Y2 = fy2;\\\n\tpChannel->nFilter_Y3 = fy3;\\\n\tpChannel->nFilter_Y4 = fy4;\\\n\n#define SNDMIX_PROCESSSTEREOFILTER\\\n\tvol_l = (vol_l * pChn->nFilter_A0 + fy1 * pChn->nFilter_B0 + fy2 * pChn->nFilter_B1 + 4096) >> 13;\\\n\tvol_r = (vol_r * pChn->nFilter_A0 + fy3 * pChn->nFilter_B0 + fy4 * pChn->nFilter_B1 + 4096) >> 13;\\\n\tfy2 = fy1; fy1 = vol_l;\\\n\tfy4 = fy3; fy3 = vol_r;\\\n\n//////////////////////////////////////////////////////////\n// Interfaces\n\ntypedef VOID (MPPASMCALL * LPMIXINTERFACE)(MODCHANNEL *, int *, int *);\n\n#define BEGIN_MIX_INTERFACE(func)\\\n\tstatic VOID MPPASMCALL func(MODCHANNEL *pChannel, int *pbuffer, int *pbufmax)\\\n\t{\\\n\t\tLONG nPos;\n\n#define END_MIX_INTERFACE()\\\n\t\tSNDMIX_ENDSAMPLELOOP\\\n\t}\n\n// Volume Ramps\n#define BEGIN_RAMPMIX_INTERFACE(func)\\\n\tBEGIN_MIX_INTERFACE(func)\\\n\t\tLONG nRampRightVol = pChannel->nRampRightVol;\\\n\t\tLONG nRampLeftVol = pChannel->nRampLeftVol;\n\n#define END_RAMPMIX_INTERFACE()\\\n\t\tSNDMIX_ENDSAMPLELOOP\\\n\t\tpChannel->nRampRightVol = nRampRightVol;\\\n\t\tpChannel->nRightVol = nRampRightVol >> VOLUMERAMPPRECISION;\\\n\t\tpChannel->nRampLeftVol = nRampLeftVol;\\\n\t\tpChannel->nLeftVol = nRampLeftVol >> VOLUMERAMPPRECISION;\\\n\t}\n\n#define BEGIN_FASTRAMPMIX_INTERFACE(func)\\\n\tBEGIN_MIX_INTERFACE(func)\\\n\t\tLONG nRampRightVol = pChannel->nRampRightVol;\n\n#define END_FASTRAMPMIX_INTERFACE()\\\n\t\tSNDMIX_ENDSAMPLELOOP\\\n\t\tpChannel->nRampRightVol = nRampRightVol;\\\n\t\tpChannel->nRampLeftVol = nRampRightVol;\\\n\t\tpChannel->nRightVol = nRampRightVol >> VOLUMERAMPPRECISION;\\\n\t\tpChannel->nLeftVol = pChannel->nRightVol;\\\n\t}\n\n\n// Mono Resonant Filters\n#define BEGIN_MIX_FLT_INTERFACE(func)\\\n\tBEGIN_MIX_INTERFACE(func)\\\n\tMIX_BEGIN_FILTER\n\n\n#define END_MIX_FLT_INTERFACE()\\\n\tSNDMIX_ENDSAMPLELOOP\\\n\tMIX_END_FILTER\\\n\t}\n\n#define BEGIN_RAMPMIX_FLT_INTERFACE(func)\\\n\tBEGIN_MIX_INTERFACE(func)\\\n\t\tLONG nRampRightVol = pChannel->nRampRightVol;\\\n\t\tLONG nRampLeftVol = pChannel->nRampLeftVol;\\\n\t\tMIX_BEGIN_FILTER\n\n#define END_RAMPMIX_FLT_INTERFACE()\\\n\t\tSNDMIX_ENDSAMPLELOOP\\\n\t\tMIX_END_FILTER\\\n\t\tpChannel->nRampRightVol = nRampRightVol;\\\n\t\tpChannel->nRightVol = nRampRightVol >> VOLUMERAMPPRECISION;\\\n\t\tpChannel->nRampLeftVol = nRampLeftVol;\\\n\t\tpChannel->nLeftVol = nRampLeftVol >> VOLUMERAMPPRECISION;\\\n\t}\n\n// Stereo Resonant Filters\n#define BEGIN_MIX_STFLT_INTERFACE(func)\\\n\tBEGIN_MIX_INTERFACE(func)\\\n\tMIX_BEGIN_STEREO_FILTER\n\n\n#define END_MIX_STFLT_INTERFACE()\\\n\tSNDMIX_ENDSAMPLELOOP\\\n\tMIX_END_STEREO_FILTER\\\n\t}\n\n#define BEGIN_RAMPMIX_STFLT_INTERFACE(func)\\\n\tBEGIN_MIX_INTERFACE(func)\\\n\t\tLONG nRampRightVol = pChannel->nRampRightVol;\\\n\t\tLONG nRampLeftVol = pChannel->nRampLeftVol;\\\n\t\tMIX_BEGIN_STEREO_FILTER\n\n#define END_RAMPMIX_STFLT_INTERFACE()\\\n\t\tSNDMIX_ENDSAMPLELOOP\\\n\t\tMIX_END_STEREO_FILTER\\\n\t\tpChannel->nRampRightVol = nRampRightVol;\\\n\t\tpChannel->nRightVol = nRampRightVol >> VOLUMERAMPPRECISION;\\\n\t\tpChannel->nRampLeftVol = nRampLeftVol;\\\n\t\tpChannel->nLeftVol = nRampLeftVol >> VOLUMERAMPPRECISION;\\\n\t}\n\n\n/////////////////////////////////////////////////////\n//\n\nstatic void MPPASMCALL X86_InitMixBuffer(int *pBuffer, UINT nSamples);\nstatic void MPPASMCALL X86_EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples);\nvoid MPPASMCALL X86_StereoFill(int *pBuffer, UINT nSamples, LPLONG lpROfs, LPLONG lpLOfs);\nvoid X86_StereoMixToFloat(const int *, float *, float *, UINT nCount);\nvoid X86_FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount);\n\n/////////////////////////////////////////////////////\n// Mono samples functions\n\nBEGIN_MIX_INTERFACE(Mono8BitMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8NOIDO\n\tSNDMIX_STOREMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Mono16BitMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16NOIDO\n\tSNDMIX_STOREMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Mono8BitLinearMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8LINEAR\n\tSNDMIX_STOREMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Mono16BitLinearMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16LINEAR\n\tSNDMIX_STOREMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Mono8BitSplineMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8SPLINE\n\tSNDMIX_STOREMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Mono16BitSplineMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16SPLINE\n\tSNDMIX_STOREMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Mono8BitFirFilterMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8FIRFILTER\n\tSNDMIX_STOREMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Mono16BitFirFilterMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16FIRFILTER\n\tSNDMIX_STOREMONOVOL\nEND_MIX_INTERFACE()\n\n\n// Volume Ramps\nBEGIN_RAMPMIX_INTERFACE(Mono8BitRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8NOIDO\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Mono16BitRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16NOIDO\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Mono8BitLinearRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8LINEAR\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Mono16BitLinearRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16LINEAR\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Mono8BitSplineRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8SPLINE\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Mono16BitSplineRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16SPLINE\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Mono8BitFirFilterRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8FIRFILTER\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Mono16BitFirFilterRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16FIRFILTER\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_INTERFACE()\n\n\n//////////////////////////////////////////////////////\n// Fast mono mix for leftvol=rightvol (1 less imul)\n\nBEGIN_MIX_INTERFACE(FastMono8BitMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8NOIDO\n\tSNDMIX_STOREFASTMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(FastMono16BitMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16NOIDO\n\tSNDMIX_STOREFASTMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(FastMono8BitLinearMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8LINEAR\n\tSNDMIX_STOREFASTMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(FastMono16BitLinearMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16LINEAR\n\tSNDMIX_STOREFASTMONOVOL\nEND_MIX_INTERFACE()\n\n#ifdef MODPLUG_DEADCODE\n\nBEGIN_MIX_INTERFACE(FastMono8BitSplineMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8SPLINE\n\tSNDMIX_STOREFASTMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(FastMono16BitSplineMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16SPLINE\n\tSNDMIX_STOREFASTMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(FastMono8BitFirFilterMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8FIRFILTER\n\tSNDMIX_STOREFASTMONOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(FastMono16BitFirFilterMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16FIRFILTER\n\tSNDMIX_STOREFASTMONOVOL\nEND_MIX_INTERFACE()\n\n#endif // MODPLUG_DEADCODE\n\n// Fast Ramps\nBEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8NOIDO\n\tSNDMIX_RAMPFASTMONOVOL\nEND_FASTRAMPMIX_INTERFACE()\n\nBEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16NOIDO\n\tSNDMIX_RAMPFASTMONOVOL\nEND_FASTRAMPMIX_INTERFACE()\n\nBEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitLinearRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8LINEAR\n\tSNDMIX_RAMPFASTMONOVOL\nEND_FASTRAMPMIX_INTERFACE()\n\nBEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitLinearRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16LINEAR\n\tSNDMIX_RAMPFASTMONOVOL\nEND_FASTRAMPMIX_INTERFACE()\n\n#ifdef MODPLUG_DEADCODE\n\nBEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitSplineRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8SPLINE\n\tSNDMIX_RAMPFASTMONOVOL\nEND_FASTRAMPMIX_INTERFACE()\n\nBEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitSplineRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16SPLINE\n\tSNDMIX_RAMPFASTMONOVOL\nEND_FASTRAMPMIX_INTERFACE()\n\nBEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitFirFilterRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8FIRFILTER\n\tSNDMIX_RAMPFASTMONOVOL\nEND_FASTRAMPMIX_INTERFACE()\n\nBEGIN_FASTRAMPMIX_INTERFACE(FastMono16BitFirFilterRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16FIRFILTER\n\tSNDMIX_RAMPFASTMONOVOL\nEND_FASTRAMPMIX_INTERFACE()\n\n#endif // MODPLUG_DEADCODE\n\n//////////////////////////////////////////////////////\n// Stereo samples\n\nBEGIN_MIX_INTERFACE(Stereo8BitMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8NOIDO\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Stereo16BitMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16NOIDO\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Stereo8BitLinearMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8LINEAR\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Stereo16BitLinearMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16LINEAR\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Stereo8BitSplineMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8SPLINE\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Stereo16BitSplineMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16SPLINE\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Stereo8BitFirFilterMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8FIRFILTER\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_INTERFACE()\n\nBEGIN_MIX_INTERFACE(Stereo16BitFirFilterMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16FIRFILTER\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_INTERFACE()\n\n\n// Volume Ramps\nBEGIN_RAMPMIX_INTERFACE(Stereo8BitRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8NOIDO\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Stereo16BitRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16NOIDO\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Stereo8BitLinearRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8LINEAR\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Stereo16BitLinearRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16LINEAR\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Stereo8BitSplineRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8SPLINE\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Stereo16BitSplineRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16SPLINE\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Stereo8BitFirFilterRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8FIRFILTER\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_INTERFACE()\n\nBEGIN_RAMPMIX_INTERFACE(Stereo16BitFirFilterRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16FIRFILTER\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_INTERFACE()\n\n\n\n//////////////////////////////////////////////////////\n// Resonant Filter Mix\n\n#ifndef NO_FILTER\n\n// Mono Filter Mix\nBEGIN_MIX_FLT_INTERFACE(FilterMono8BitMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8NOIDO\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_STOREMONOVOL\nEND_MIX_FLT_INTERFACE()\n\nBEGIN_MIX_FLT_INTERFACE(FilterMono16BitMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16NOIDO\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_STOREMONOVOL\nEND_MIX_FLT_INTERFACE()\n\nBEGIN_MIX_FLT_INTERFACE(FilterMono8BitLinearMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8LINEAR\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_STOREMONOVOL\nEND_MIX_FLT_INTERFACE()\n\nBEGIN_MIX_FLT_INTERFACE(FilterMono16BitLinearMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16LINEAR\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_STOREMONOVOL\nEND_MIX_FLT_INTERFACE()\n\nBEGIN_MIX_FLT_INTERFACE(FilterMono8BitSplineMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8SPLINE\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_STOREMONOVOL\nEND_MIX_FLT_INTERFACE()\n\nBEGIN_MIX_FLT_INTERFACE(FilterMono16BitSplineMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16SPLINE\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_STOREMONOVOL\nEND_MIX_FLT_INTERFACE()\n\nBEGIN_MIX_FLT_INTERFACE(FilterMono8BitFirFilterMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8FIRFILTER\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_STOREMONOVOL\nEND_MIX_FLT_INTERFACE()\n\nBEGIN_MIX_FLT_INTERFACE(FilterMono16BitFirFilterMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16FIRFILTER\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_STOREMONOVOL\nEND_MIX_FLT_INTERFACE()\n\n// Filter + Ramp\nBEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8NOIDO\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_FLT_INTERFACE()\n\nBEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16NOIDO\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_FLT_INTERFACE()\n\nBEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitLinearRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8LINEAR\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_FLT_INTERFACE()\n\nBEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitLinearRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16LINEAR\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_FLT_INTERFACE()\n\nBEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitSplineRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8SPLINE\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_FLT_INTERFACE()\n\nBEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitSplineRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16SPLINE\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_FLT_INTERFACE()\n\nBEGIN_RAMPMIX_FLT_INTERFACE(FilterMono8BitFirFilterRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETMONOVOL8FIRFILTER\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_FLT_INTERFACE()\n\nBEGIN_RAMPMIX_FLT_INTERFACE(FilterMono16BitFirFilterRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETMONOVOL16FIRFILTER\n\tSNDMIX_PROCESSFILTER\n\tSNDMIX_RAMPMONOVOL\nEND_RAMPMIX_FLT_INTERFACE()\n\n\n// Stereo Filter Mix\nBEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8NOIDO\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_STFLT_INTERFACE()\n\nBEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16NOIDO\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_STFLT_INTERFACE()\n\nBEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitLinearMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8LINEAR\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_STFLT_INTERFACE()\n\nBEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitLinearMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16LINEAR\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_STFLT_INTERFACE()\n\nBEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitSplineMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8SPLINE\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_STFLT_INTERFACE()\n\nBEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitSplineMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16SPLINE\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_STFLT_INTERFACE()\n\nBEGIN_MIX_STFLT_INTERFACE(FilterStereo8BitFirFilterMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8FIRFILTER\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_STFLT_INTERFACE()\n\nBEGIN_MIX_STFLT_INTERFACE(FilterStereo16BitFirFilterMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16FIRFILTER\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_STORESTEREOVOL\nEND_MIX_STFLT_INTERFACE()\n\n// Stereo Filter + Ramp\nBEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8NOIDO\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_STFLT_INTERFACE()\n\nBEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16NOIDO\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_STFLT_INTERFACE()\n\nBEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitLinearRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8LINEAR\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_STFLT_INTERFACE()\n\nBEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitLinearRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16LINEAR\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_STFLT_INTERFACE()\n\nBEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitSplineRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8SPLINE\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_STFLT_INTERFACE()\n\nBEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitSplineRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16SPLINE\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_STFLT_INTERFACE()\n\nBEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo8BitFirFilterRampMix)\n\tSNDMIX_BEGINSAMPLELOOP8\n\tSNDMIX_GETSTEREOVOL8FIRFILTER\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_STFLT_INTERFACE()\n\nBEGIN_RAMPMIX_STFLT_INTERFACE(FilterStereo16BitFirFilterRampMix)\n\tSNDMIX_BEGINSAMPLELOOP16\n\tSNDMIX_GETSTEREOVOL16FIRFILTER\n\tSNDMIX_PROCESSSTEREOFILTER\n\tSNDMIX_RAMPSTEREOVOL\nEND_RAMPMIX_STFLT_INTERFACE()\n\n\n#else\n// Mono\n#define FilterMono8BitMix\t\t\t\t\tMono8BitMix\n#define FilterMono16BitMix\t\t\t\t\tMono16BitMix\n#define FilterMono8BitLinearMix\t\t\t\tMono8BitLinearMix\n#define FilterMono16BitLinearMix\t\t\tMono16BitLinearMix\n#define FilterMono8BitSplineMix\t\t\t\tMono8BitSplineMix\n#define FilterMono16BitSplineMix\t\t\tMono16BitSplineMix\n#define FilterMono8BitFirFilterMix\t\t\tMono8BitFirFilterMix\n#define FilterMono16BitFirFilterMix\t\t\tMono16BitFirFilterMix\n#define FilterMono8BitRampMix\t\t\t\tMono8BitRampMix\n#define FilterMono16BitRampMix\t\t\t\tMono16BitRampMix\n#define FilterMono8BitLinearRampMix\t\t\tMono8BitLinearRampMix\n#define FilterMono16BitLinearRampMix\t\tMono16BitLinearRampMix\n#define FilterMono8BitSplineRampMix\t\t\tMono8BitSplineRampMix\n#define FilterMono16BitSplineRampMix\t\tMono16BitSplineRampMix\n#define FilterMono8BitFirFilterRampMix\t\tMono8BitFirFilterRampMix\n#define FilterMono16BitFirFilterRampMix\t\tMono16BitFirFilterRampMix\n// Stereo\n#define FilterStereo8BitMix\t\t\t\t\tStereo8BitMix\n#define FilterStereo16BitMix\t\t\t\tStereo16BitMix\n#define FilterStereo8BitLinearMix\t\t\tStereo8BitLinearMix\n#define FilterStereo16BitLinearMix\t\t\tStereo16BitLinearMix\n#define FilterStereo8BitSplineMix\t\t\tStereo8BitSplineMix\n#define FilterStereo16BitSplineMix\t\t\tStereo16BitSplineMix\n#define FilterStereo8BitFirFilterMix\t\tStereo8BitFirFilterMix\n#define FilterStereo16BitFirFilterMix\t\tStereo16BitFirFilterMix\n#define FilterStereo8BitRampMix\t\t\t\tStereo8BitRampMix\n#define FilterStereo16BitRampMix\t\t\tStereo16BitRampMix\n#define FilterStereo8BitLinearRampMix\t\tStereo8BitLinearRampMix\n#define FilterStereo16BitLinearRampMix\t\tStereo16BitLinearRampMix\n#define FilterStereo8BitSplineRampMix\t\tStereo8BitSplineRampMix\n#define FilterStereo16BitSplineRampMix\t\tStereo16BitSplineRampMix\n#define FilterStereo8BitFirFilterRampMix\tStereo8BitFirFilterRampMix\n#define FilterStereo16BitFirFilterRampMix\tStereo16BitFirFilterRampMix\n\n#endif\n\n///////////////////////////////////////////////////////////////////////////////\n//\n// Mix function tables\n//\n//\n// Index is as follow:\n//\t[b1-b0]\tformat (8-bit-mono, 16-bit-mono, 8-bit-stereo, 16-bit-stereo)\n//\t[b2]\tramp\n//\t[b3]\tfilter\n//\t[b5-b4]\tsrc type\n//\n\n#define MIXNDX_16BIT        0x01\n#define MIXNDX_STEREO       0x02\n#define MIXNDX_RAMP         0x04\n#define MIXNDX_FILTER       0x08\n#define MIXNDX_LINEARSRC    0x10\n#define MIXNDX_SPLINESRC    0x20\n#define MIXNDX_FIRSRC       0x30\n\nconst LPMIXINTERFACE gpMixFunctionTable[2*2*16] =\n{\n\t// No SRC\n\tMono8BitMix, Mono16BitMix, Stereo8BitMix, Stereo16BitMix,\n\tMono8BitRampMix, Mono16BitRampMix, Stereo8BitRampMix,\n            Stereo16BitRampMix,\n\t// No SRC, Filter\n\tFilterMono8BitMix, FilterMono16BitMix, FilterStereo8BitMix,\n        FilterStereo16BitMix, FilterMono8BitRampMix, FilterMono16BitRampMix,\n\tFilterStereo8BitRampMix, FilterStereo16BitRampMix,\n\t// Linear SRC\n\tMono8BitLinearMix, Mono16BitLinearMix, Stereo8BitLinearMix,\n\tStereo16BitLinearMix, Mono8BitLinearRampMix, Mono16BitLinearRampMix,\n\tStereo8BitLinearRampMix,Stereo16BitLinearRampMix,\n\t// Linear SRC, Filter\n\tFilterMono8BitLinearMix, FilterMono16BitLinearMix,\n        FilterStereo8BitLinearMix, FilterStereo16BitLinearMix,\n\tFilterMono8BitLinearRampMix, FilterMono16BitLinearRampMix,\n        FilterStereo8BitLinearRampMix, FilterStereo16BitLinearRampMix,\n\n\t// FirFilter SRC\n\tMono8BitSplineMix, Mono16BitSplineMix, Stereo8BitSplineMix,\n        Stereo16BitSplineMix, Mono8BitSplineRampMix, Mono16BitSplineRampMix,\n\tStereo8BitSplineRampMix,Stereo16BitSplineRampMix,\n\t// Spline SRC, Filter\n\tFilterMono8BitSplineMix, FilterMono16BitSplineMix,\n        FilterStereo8BitSplineMix, FilterStereo16BitSplineMix,\n\tFilterMono8BitSplineRampMix, FilterMono16BitSplineRampMix,\n        FilterStereo8BitSplineRampMix, FilterStereo16BitSplineRampMix,\n\n\t// FirFilter  SRC\n\tMono8BitFirFilterMix, Mono16BitFirFilterMix, Stereo8BitFirFilterMix,\n\tStereo16BitFirFilterMix, Mono8BitFirFilterRampMix,\n        Mono16BitFirFilterRampMix, Stereo8BitFirFilterRampMix,\n        Stereo16BitFirFilterRampMix,\n\t// FirFilter  SRC, Filter\n\tFilterMono8BitFirFilterMix, FilterMono16BitFirFilterMix,\n        FilterStereo8BitFirFilterMix, FilterStereo16BitFirFilterMix,\n\tFilterMono8BitFirFilterRampMix, FilterMono16BitFirFilterRampMix,\n        FilterStereo8BitFirFilterRampMix, FilterStereo16BitFirFilterRampMix\n};\n\nconst LPMIXINTERFACE gpFastMixFunctionTable[2*2*16] =\n{\n\t// No SRC\n\tFastMono8BitMix, FastMono16BitMix, Stereo8BitMix, Stereo16BitMix,\n\tFastMono8BitRampMix, FastMono16BitRampMix, Stereo8BitRampMix,\n        Stereo16BitRampMix,\n\t// No SRC, Filter\n\tFilterMono8BitMix, FilterMono16BitMix, FilterStereo8BitMix,\n        FilterStereo16BitMix, FilterMono8BitRampMix, FilterMono16BitRampMix,\n        FilterStereo8BitRampMix, FilterStereo16BitRampMix,\n\t// Linear SRC\n\tFastMono8BitLinearMix, FastMono16BitLinearMix, Stereo8BitLinearMix,\n        Stereo16BitLinearMix, FastMono8BitLinearRampMix,\n        FastMono16BitLinearRampMix, Stereo8BitLinearRampMix,\n        Stereo16BitLinearRampMix,\n\t// Linear SRC, Filter\n\tFilterMono8BitLinearMix, FilterMono16BitLinearMix,\n        FilterStereo8BitLinearMix, FilterStereo16BitLinearMix,\n\tFilterMono8BitLinearRampMix, FilterMono16BitLinearRampMix,\n        FilterStereo8BitLinearRampMix, FilterStereo16BitLinearRampMix,\n\n\t// Spline SRC\n\tMono8BitSplineMix, Mono16BitSplineMix, Stereo8BitSplineMix,\n        Stereo16BitSplineMix, Mono8BitSplineRampMix, Mono16BitSplineRampMix,\n        Stereo8BitSplineRampMix, Stereo16BitSplineRampMix,\n\t// Spline SRC, Filter\n\tFilterMono8BitSplineMix, FilterMono16BitSplineMix,\n        FilterStereo8BitSplineMix, FilterStereo16BitSplineMix,\n\tFilterMono8BitSplineRampMix, FilterMono16BitSplineRampMix,\n        FilterStereo8BitSplineRampMix, FilterStereo16BitSplineRampMix,\n\n\t// FirFilter SRC\n\tMono8BitFirFilterMix, Mono16BitFirFilterMix, Stereo8BitFirFilterMix,\n        Stereo16BitFirFilterMix, Mono8BitFirFilterRampMix,\n        Mono16BitFirFilterRampMix, Stereo8BitFirFilterRampMix,\n        Stereo16BitFirFilterRampMix,\n\t// FirFilter SRC, Filter\n\tFilterMono8BitFirFilterMix, FilterMono16BitFirFilterMix,\n        FilterStereo8BitFirFilterMix, FilterStereo16BitFirFilterMix,\n\tFilterMono8BitFirFilterRampMix, FilterMono16BitFirFilterRampMix,\n        FilterStereo8BitFirFilterRampMix, FilterStereo16BitFirFilterRampMix,\n};\n\n\n/////////////////////////////////////////////////////////////////////////\n\nstatic LONG MPPFASTCALL GetSampleCount(MODCHANNEL *pChn, LONG nSamples)\n//---------------------------------------------------------------------\n{\n\tLONG nLoopStart = (pChn->dwFlags & CHN_LOOP) ? pChn->nLoopStart : 0;\n\tLONG nInc = pChn->nInc;\n\n\tif ((nSamples <= 0) || (!nInc) || (!pChn->nLength)) return 0;\n\t// Under zero ?\n\tif ((LONG)pChn->nPos < nLoopStart)\n\t{\n\t\tif (nInc < 0)\n\t\t{\n\t\t\t// Invert loop for bidi loops\n\t\t\tLONG nDelta = ((nLoopStart - pChn->nPos) << 16) - (pChn->nPosLo & 0xffff);\n\t\t\tpChn->nPos = nLoopStart | (nDelta>>16);\n\t\t\tpChn->nPosLo = nDelta & 0xffff;\n\t\t\tif (((LONG)pChn->nPos < nLoopStart) ||\n\t\t\t\t(pChn->nPos >= (nLoopStart+pChn->nLength)/2))\n\t\t\t{\n\t\t\t\tpChn->nPos = nLoopStart; pChn->nPosLo = 0;\n\t\t\t}\n\t\t\tnInc = -nInc;\n\t\t\tpChn->nInc = nInc;\n\t\t\tpChn->dwFlags &= ~(CHN_PINGPONGFLAG); // go forward\n\t\t\tif ((!(pChn->dwFlags & CHN_LOOP)) || (pChn->nPos >= pChn->nLength))\n\t\t\t{\n\t\t\t\tpChn->nPos = pChn->nLength;\n\t\t\t\tpChn->nPosLo = 0;\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t} else\n\t\t{\n\t\t\t// We probably didn't hit the loop end yet\n\t\t\t// (first loop), so we do nothing\n\t\t\tif ((LONG)pChn->nPos < 0) pChn->nPos = 0;\n\t\t}\n\t} else\n\t// Past the end\n\tif (pChn->nPos >= pChn->nLength)\n\t{\n\t\tif (!(pChn->dwFlags & CHN_LOOP)) return 0; // not looping -> stop this channel\n\t\tif (pChn->dwFlags & CHN_PINGPONGLOOP)\n\t\t{\n\t\t\t// Invert loop\n\t\t\tif (nInc > 0)\n\t\t\t{\n\t\t\t\tnInc = -nInc;\n\t\t\t\tpChn->nInc = nInc;\n\t\t\t}\n\t\t\tpChn->dwFlags |= CHN_PINGPONGFLAG;\n\t\t\t// adjust loop position\n\t\t\tLONG nDeltaHi = (pChn->nPos - pChn->nLength);\n\t\t\tLONG nDeltaLo = 0x10000 - (pChn->nPosLo & 0xffff);\n\t\t\tpChn->nPos = pChn->nLength - nDeltaHi - (nDeltaLo>>16);\n\t\t\tpChn->nPosLo = nDeltaLo & 0xffff;\n\t\t\tif ((pChn->nPos <= pChn->nLoopStart) ||\n\t\t\t(pChn->nPos >= pChn->nLength))\n\t\t\t\tpChn->nPos = pChn->nLength-1;\n\t\t} else\n\t\t{\n\t\t\tif (nInc < 0) // This is a bug\n\t\t\t{\n\t\t\t\tnInc = -nInc;\n\t\t\t\tpChn->nInc = nInc;\n\t\t\t}\n\t\t\t// Restart at loop start\n\t\t\tpChn->nPos += nLoopStart - pChn->nLength;\n\t\t\tif ((LONG)pChn->nPos < nLoopStart)\n\t\t\t\tpChn->nPos = pChn->nLoopStart;\n\t\t}\n\t}\n\tLONG nPos = pChn->nPos;\n\t// too big increment, and/or too small loop length\n\tif (nPos < nLoopStart)\n\t{\n\t\tif ((nPos < 0) || (nInc < 0)) return 0;\n\t}\n\tif ((nPos < 0) || (nPos >= (LONG)pChn->nLength)) return 0;\n\tLONG nPosLo = (USHORT)pChn->nPosLo, nSmpCount = nSamples;\n\tif (nInc < 0)\n\t{\n\t\tLONG nInv = -nInc;\n\t\tLONG maxsamples = 16384 / ((nInv>>16)+1);\n\t\tif (maxsamples < 2) maxsamples = 2;\n\t\tif (nSamples > maxsamples) nSamples = maxsamples;\n\t\tLONG nDeltaHi = (nInv>>16) * (nSamples - 1);\n\t\tLONG nDeltaLo = (nInv&0xffff) * (nSamples - 1);\n\t\tLONG nPosDest = nPos - nDeltaHi + ((nPosLo - nDeltaLo) >> 16);\n\t\tif (nPosDest < nLoopStart)\n\t\t{\n\t\t\tnSmpCount = (ULONG)(((((LONGLONG)nPos - nLoopStart) << 16) + nPosLo - 1) / nInv) + 1;\n\t\t}\n\t} else\n\t{\n\t\tLONG maxsamples = 16384 / ((nInc>>16)+1);\n\t\tif (maxsamples < 2) maxsamples = 2;\n\t\tif (nSamples > maxsamples) nSamples = maxsamples;\n\t\tLONG nDeltaHi = (nInc>>16) * (nSamples - 1);\n\t\tLONG nDeltaLo = (nInc&0xffff) * (nSamples - 1);\n\t\tLONG nPosDest = nPos + nDeltaHi + ((nPosLo + nDeltaLo)>>16);\n\t\tif (nPosDest >= (LONG)pChn->nLength)\n\t\t{\n\t\t\tnSmpCount = (ULONG)(((((LONGLONG)pChn->nLength - nPos) << 16) - nPosLo - 1) / nInc) + 1;\n\t\t}\n\t}\n\tif (nSmpCount <= 1) return 1;\n\tif (nSmpCount > nSamples) return nSamples;\n\treturn nSmpCount;\n}\n\n\nUINT CSoundFile::CreateStereoMix(int count)\n//-----------------------------------------\n{\n\tLPLONG pOfsL, pOfsR;\n\tDWORD nchused, nchmixed;\n\n\tif (!count) return 0;\n#ifndef MODPLUG_FASTSOUNDLIB\n\tif (gnChannels > 2) X86_InitMixBuffer(MixRearBuffer, count*2);\n#endif\n\tnchused = nchmixed = 0;\n\tfor (UINT nChn=0; nChn<m_nMixChannels; nChn++)\n\t{\n\t\tconst LPMIXINTERFACE *pMixFuncTable;\n\t\tMODCHANNEL * const pChannel = &Chn[ChnMix[nChn]];\n\t\tUINT nFlags;\n\t\tLONG nSmpCount;\n\t\tint nsamples;\n\t\tint *pbuffer;\n\n\t\tif (!pChannel->pCurrentSample) continue;\n\t\tpOfsR = &gnDryROfsVol;\n\t\tpOfsL = &gnDryLOfsVol;\n\t\tnFlags = 0;\n\t\tif (pChannel->dwFlags & CHN_16BIT) nFlags |= MIXNDX_16BIT;\n\t\tif (pChannel->dwFlags & CHN_STEREO) nFlags |= MIXNDX_STEREO;\n\t#ifndef NO_FILTER\n\t\tif (pChannel->dwFlags & CHN_FILTER) nFlags |= MIXNDX_FILTER;\n\t#endif\n\t\tif (!(pChannel->dwFlags & CHN_NOIDO))\n\t\t{\n\t\t\t// use hq-fir mixer?\n\t\t\tif( (gdwSoundSetup & (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE)) ==\n\t\t\t\t(SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE) )\n\t\t\t\tnFlags += MIXNDX_FIRSRC;\n\t\t\telse if( (gdwSoundSetup & (SNDMIX_HQRESAMPLER)) == SNDMIX_HQRESAMPLER )\n\t\t\t\tnFlags += MIXNDX_SPLINESRC;\n\t\t\telse\n\t\t\t\tnFlags += MIXNDX_LINEARSRC; // use\n\t\t}\n\t\tif ((nFlags < 0x40) && (pChannel->nLeftVol == pChannel->nRightVol)\n\t\t && ((!pChannel->nRampLength) || (pChannel->nLeftRamp == pChannel->nRightRamp)))\n\t\t{\n\t\t\tpMixFuncTable = gpFastMixFunctionTable;\n\t\t} else\n\t\t{\n\t\t\tpMixFuncTable = gpMixFunctionTable;\n\t\t}\n\t\tnsamples = count;\n#ifndef MODPLUG_NO_REVERB\n\t\tpbuffer = (gdwSoundSetup & SNDMIX_REVERB) ? MixReverbBuffer : MixSoundBuffer;\n\t\tif (pChannel->dwFlags & CHN_NOREVERB) pbuffer = MixSoundBuffer;\n\t\tif (pChannel->dwFlags & CHN_REVERB) pbuffer = MixReverbBuffer;\n\t\tif (pbuffer == MixReverbBuffer)\n\t\t{\n\t\t\tif (!gnReverbSend) memset(MixReverbBuffer, 0, count * 8);\n\t\t\tgnReverbSend += count;\n\t\t}\n#else\n\t\tpbuffer = MixSoundBuffer;\n#endif\n\t\tnchused++;\n\t\t////////////////////////////////////////////////////\n\tSampleLooping:\n\t\tUINT nrampsamples = nsamples;\n\t\tif (pChannel->nRampLength > 0)\n\t\t{\n\t\t\tif ((LONG)nrampsamples > pChannel->nRampLength) nrampsamples = pChannel->nRampLength;\n\t\t}\n\t\tif ((nSmpCount = GetSampleCount(pChannel, nrampsamples)) <= 0)\n\t\t{\n\t\t\t// Stopping the channel\n\t\t\tpChannel->pCurrentSample = NULL;\n\t\t\tpChannel->nLength = 0;\n\t\t\tpChannel->nPos = 0;\n\t\t\tpChannel->nPosLo = 0;\n\t\t\tpChannel->nRampLength = 0;\n\t\t\tX86_EndChannelOfs(pChannel, pbuffer, nsamples);\n\t\t\t*pOfsR += pChannel->nROfs;\n\t\t\t*pOfsL += pChannel->nLOfs;\n\t\t\tpChannel->nROfs = pChannel->nLOfs = 0;\n\t\t\tpChannel->dwFlags &= ~CHN_PINGPONGFLAG;\n\t\t\tcontinue;\n\t\t}\n\t\t// Should we mix this channel ?\n\t\tUINT naddmix;\n\t\tif (((nchmixed >= m_nMaxMixChannels) && (!(gdwSoundSetup & SNDMIX_DIRECTTODISK)))\n\t\t || ((!pChannel->nRampLength) && (!(pChannel->nLeftVol|pChannel->nRightVol))))\n\t\t{\n\t\t\tLONG delta = (pChannel->nInc * (LONG)nSmpCount) + (LONG)pChannel->nPosLo;\n\t\t\tpChannel->nPosLo = delta & 0xFFFF;\n\t\t\tpChannel->nPos += (delta >> 16);\n\t\t\tpChannel->nROfs = pChannel->nLOfs = 0;\n\t\t\tpbuffer += nSmpCount*2;\n\t\t\tnaddmix = 0;\n\t\t} else\n\t\t// Do mixing\n\t\t{\n\t\t\t// Choose function for mixing\n\t\t\tLPMIXINTERFACE pMixFunc;\n\t\t\tpMixFunc = (pChannel->nRampLength) ? pMixFuncTable[nFlags|MIXNDX_RAMP] : pMixFuncTable[nFlags];\n\t\t\tint *pbufmax = pbuffer + (nSmpCount*2);\n\t\t\tpChannel->nROfs = - *(pbufmax-2);\n\t\t\tpChannel->nLOfs = - *(pbufmax-1);\n\t\t\tpMixFunc(pChannel, pbuffer, pbufmax);\n\t\t\tpChannel->nROfs += *(pbufmax-2);\n\t\t\tpChannel->nLOfs += *(pbufmax-1);\n\t\t\tpbuffer = pbufmax;\n\t\t\tnaddmix = 1;\n\n\t\t}\n\t\tnsamples -= nSmpCount;\n\t\tif (pChannel->nRampLength)\n\t\t{\n\t\t\tpChannel->nRampLength -= nSmpCount;\n\t\t\tif (pChannel->nRampLength <= 0)\n\t\t\t{\n\t\t\t\tpChannel->nRampLength = 0;\n\t\t\t\tpChannel->nRightVol = pChannel->nNewRightVol;\n\t\t\t\tpChannel->nLeftVol = pChannel->nNewLeftVol;\n\t\t\t\tpChannel->nRightRamp = pChannel->nLeftRamp = 0;\n\t\t\t\tif ((pChannel->dwFlags & CHN_NOTEFADE) && (!(pChannel->nFadeOutVol)))\n\t\t\t\t{\n\t\t\t\t\tpChannel->nLength = 0;\n\t\t\t\t\tpChannel->pCurrentSample = NULL;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (nsamples > 0) goto SampleLooping;\n\t\tnchmixed += naddmix;\n\t}\n\treturn nchused;\n}\n\n\n#ifdef MSC_VER\n#pragma warning (disable:4100)\n#endif\n\n// Clip and convert to 8 bit\n#ifdef MSC_VER\n__declspec(naked) DWORD MPPASMCALL X86_Convert32To8(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax)\n//------------------------------------------------------------------------------\n{\n    _asm {\n        push ebx\n\tpush esi\n\tpush edi\n\tmov ebx, 16[esp]\t\t// ebx = 8-bit buffer\n\tmov esi, 20[esp]\t\t// esi = pBuffer\n\tmov edi, 24[esp]\t\t// edi = lSampleCount\n\tmov eax, 28[esp]\n\tmov ecx, dword ptr [eax]\t// ecx = clipmin\n\tmov eax, 32[esp]\n\tmov edx, dword ptr [eax]\t// edx = clipmax\ncliploop:\n\tmov eax, dword ptr [esi]\n\tinc ebx\n\tcdq\n\tand edx, (1 << (24-MIXING_ATTENUATION)) - 1\n\tadd eax, edx\n\tcmp eax, MIXING_CLIPMIN\n\tjl cliplow\n\tcmp eax, MIXING_CLIPMAX\n\tjg cliphigh\n\tcmp eax, ecx\n\tjl updatemin\n\tcmp eax, edx\n\tjg updatemax\ncliprecover:\n\tadd esi, 4\n\tsar eax, 24-MIXING_ATTENUATION\n\txor eax, 0x80\n\tdec edi\n\tmov byte ptr [ebx-1], al\n\tjnz cliploop\n\tmov eax, 28[esp]\n\tmov dword ptr [eax], ecx\n\tmov eax, 32[esp]\n\tmov dword ptr [eax], edx\n\tmov eax, 24[esp]\n\tpop edi\n\tpop esi\n\tpop ebx\n\tret\nupdatemin:\n\tmov ecx, eax\n\tjmp cliprecover\nupdatemax:\n\tmov edx, eax\n\tjmp cliprecover\ncliplow:\n\tmov ecx, MIXING_CLIPMIN\n\tmov edx, MIXING_CLIPMAX\n\tmov eax, MIXING_CLIPMIN\n\tjmp cliprecover\ncliphigh:\n\tmov ecx, MIXING_CLIPMIN\n\tmov edx, MIXING_CLIPMAX\n\tmov eax, MIXING_CLIPMAX\n\tjmp cliprecover\n\t}\n}\n#else //MSC_VER\n//---GCCFIX: Asm replaced with C function\n// The C version was written by Rani Assaf <rani@magic.metawire.com>, I believe\nDWORD MPPASMCALL X86_Convert32To8(LPVOID lp8, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax)\n{\n\tint vumin = *lpMin, vumax = *lpMax;\n\tunsigned char *p = (unsigned char *)lp8;\n\tfor (UINT i=0; i<lSampleCount; i++)\n\t{\n\t\tint n = pBuffer[i];\n\t\tif (n < MIXING_CLIPMIN)\n\t\t\tn = MIXING_CLIPMIN;\n\t\telse if (n > MIXING_CLIPMAX)\n\t\t\tn = MIXING_CLIPMAX;\n\t\tif (n < vumin)\n\t\t\tvumin = n;\n\t\telse if (n > vumax)\n\t\t\tvumax = n;\n\t\tp[i] = (n >> (24-MIXING_ATTENUATION)) ^ 0x80;\t// 8-bit unsigned\n\t}\n\t*lpMin = vumin;\n\t*lpMax = vumax;\n\treturn lSampleCount;\n}\n#endif //MSC_VER, else\n\n\n#ifdef MSC_VER\n// Clip and convert to 16 bit\n__declspec(naked) DWORD MPPASMCALL X86_Convert32To16(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax)\n//------------------------------------------------------------------------------\n{\n\t_asm {\n\tpush ebx\n\tpush esi\n\tpush edi\n\tmov ebx, 16[esp]\t\t// ebx = 16-bit buffer\n\tmov eax, 28[esp]\n\tmov esi, 20[esp]\t\t// esi = pBuffer\n\tmov ecx, dword ptr [eax]\t// ecx = clipmin\n\tmov edi, 24[esp]\t\t// edi = lSampleCount\n\tmov eax, 32[esp]\n\tpush ebp\n\tmov ebp, dword ptr [eax]\t// edx = clipmax\ncliploop:\n\tmov eax, dword ptr [esi]\n\tadd ebx, 2\n\tcdq\n\tand edx, (1 << (16-MIXING_ATTENUATION)) - 1\n\tadd esi, 4\n\tadd eax, edx\n\tcmp eax, MIXING_CLIPMIN\n\tjl cliplow\n\tcmp eax, MIXING_CLIPMAX\n\tjg cliphigh\n\tcmp eax, ecx\n\tjl updatemin\n\tcmp eax, ebp\n\tjg updatemax\ncliprecover:\n\tsar eax, 16-MIXING_ATTENUATION\n\tdec edi\n\tmov word ptr [ebx-2], ax\n\tjnz cliploop\n\tmov edx, ebp\n\tpop ebp\n\tmov eax, 28[esp]\n\tmov dword ptr [eax], ecx\n\tmov eax, 32[esp]\n\tmov dword ptr [eax], edx\n\tmov eax, 24[esp]\n\tpop edi\n\tshl eax, 1\n\tpop esi\n\tpop ebx\n\tret\nupdatemin:\n\tmov ecx, eax\n\tjmp cliprecover\nupdatemax:\n\tmov ebp, eax\n\tjmp cliprecover\ncliplow:\n\tmov ecx, MIXING_CLIPMIN\n\tmov ebp, MIXING_CLIPMAX\n\tmov eax, MIXING_CLIPMIN\n\tjmp cliprecover\ncliphigh:\n\tmov ecx, MIXING_CLIPMIN\n\tmov ebp, MIXING_CLIPMAX\n\tmov eax, MIXING_CLIPMAX\n\tjmp cliprecover\n\t}\n}\n#else //MSC_VER\n//---GCCFIX: Asm replaced with C function\n// The C version was written by Rani Assaf <rani@magic.metawire.com>, I believe\nDWORD MPPASMCALL X86_Convert32To16(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax)\n{\n\tint vumin = *lpMin, vumax = *lpMax;\n\tsigned short *p = (signed short *)lp16;\n\tfor (UINT i=0; i<lSampleCount; i++)\n\t{\n\t\tint n = pBuffer[i];\n\t\tif (n < MIXING_CLIPMIN)\n\t\t\tn = MIXING_CLIPMIN;\n\t\telse if (n > MIXING_CLIPMAX)\n\t\t\tn = MIXING_CLIPMAX;\n\t\tif (n < vumin)\n\t\t\tvumin = n;\n\t\telse if (n > vumax)\n\t\t\tvumax = n;\n\t\tp[i] = n >> (16-MIXING_ATTENUATION);\t// 16-bit signed\n\t}\n\t*lpMin = vumin;\n\t*lpMax = vumax;\n\treturn lSampleCount * 2;\n}\n#endif //MSC_VER, else\n\n#ifdef MSC_VER\n// Clip and convert to 24 bit\n__declspec(naked) DWORD MPPASMCALL X86_Convert32To24(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax)\n//------------------------------------------------------------------------------\n{\n\t_asm {\n\tpush ebx\n\tpush esi\n\tpush edi\n\tmov ebx, 16[esp]\t\t// ebx = 8-bit buffer\n\tmov esi, 20[esp]\t\t// esi = pBuffer\n\tmov edi, 24[esp]\t\t// edi = lSampleCount\n\tmov eax, 28[esp]\n\tmov ecx, dword ptr [eax]\t// ecx = clipmin\n\tmov eax, 32[esp]\n\tpush ebp\n\tmov edx, dword ptr [eax]\t// edx = clipmax\ncliploop:\n\tmov eax, dword ptr [esi]\n\tmov ebp, eax\n\tsar ebp, 31\n\tand ebp, (1 << (8-MIXING_ATTENUATION)) - 1\n\tadd eax, ebp\n\tcmp eax, MIXING_CLIPMIN\n\tjl cliplow\n\tcmp eax, MIXING_CLIPMAX\n\tjg cliphigh\n\tcmp eax, ecx\n\tjl updatemin\n\tcmp eax, edx\n\tjg updatemax\ncliprecover:\n\tadd ebx, 3\n\tsar eax, 8-MIXING_ATTENUATION\n\tadd esi, 4\n\tmov word ptr [ebx-3], ax\n\tshr eax, 16\n\tdec edi\n\tmov byte ptr [ebx-1], al\n\tjnz cliploop\n\tpop ebp\n\tmov eax, 28[esp]\n\tmov dword ptr [eax], ecx\n\tmov eax, 32[esp]\n\tmov dword ptr [eax], edx\n\tmov edx, 24[esp]\n\tmov eax, edx\n\tpop edi\n\tshl eax, 1\n\tpop esi\n\tadd eax, edx\n\tpop ebx\n\tret\nupdatemin:\n\tmov ecx, eax\n\tjmp cliprecover\nupdatemax:\n\tmov edx, eax\n\tjmp cliprecover\ncliplow:\n\tmov ecx, MIXING_CLIPMIN\n\tmov edx, MIXING_CLIPMAX\n\tmov eax, MIXING_CLIPMIN\n\tjmp cliprecover\ncliphigh:\n\tmov ecx, MIXING_CLIPMIN\n\tmov edx, MIXING_CLIPMAX\n\tmov eax, MIXING_CLIPMAX\n\tjmp cliprecover\n\t}\n}\n#else //MSC_VER\n//---GCCFIX: Asm replaced with C function\nDWORD MPPASMCALL X86_Convert32To24(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax)\n{\n\tUINT i ;\n\tint vumin = *lpMin, vumax = *lpMax;\n\tint n,p ;\n\tunsigned char* buf = (unsigned char*)lp16 ;\n\n\tfor ( i=0; i<lSampleCount; i++)\n\t{\n\t\tn = pBuffer[i];\n\t\tif (n < MIXING_CLIPMIN)\n\t\t\tn = MIXING_CLIPMIN;\n\t\telse if (n > MIXING_CLIPMAX)\n\t\t\tn = MIXING_CLIPMAX;\n\t\tif (n < vumin)\n\t\t\tvumin = n;\n\t\telse if (n > vumax)\n\t\t\tvumax = n;\n\t\tp = n >> (8-MIXING_ATTENUATION) ; // 24-bit signed\n#if defined(WORDS_BIGENDIAN)\n\t\tbuf[i*3+0] = (p >> 16) & 0xFF;\n\t\tbuf[i*3+1] = (p >> 8)  & 0xFF;\n\t\tbuf[i*3+2] = (p >> 0)  & 0xFF;\n#else\n\t\tbuf[i*3+0] = (p >> 0)  & 0xFF;\n\t\tbuf[i*3+1] = (p >> 8)  & 0xFF;\n\t\tbuf[i*3+2] = (p >> 16) & 0xFF;\n#endif\n\t}\n\t*lpMin = vumin;\n\t*lpMax = vumax;\n\treturn lSampleCount * 3;\n}\n#endif\n\n#ifdef MSC_VER\n// Clip and convert to 32 bit\n__declspec(naked) DWORD MPPASMCALL X86_Convert32To32(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax)\n//------------------------------------------------------------------------------\n{\n\t_asm {\n\tpush ebx\n\tpush esi\n\tpush edi\n\tmov ebx, 16[esp]\t\t\t// ebx = 32-bit buffer\n\tmov esi, 20[esp]\t\t\t// esi = pBuffer\n\tmov edi, 24[esp]\t\t\t// edi = lSampleCount\n\tmov eax, 28[esp]\n\tmov ecx, dword ptr [eax]\t// ecx = clipmin\n\tmov eax, 32[esp]\n\tmov edx, dword ptr [eax]\t// edx = clipmax\ncliploop:\n\tmov eax, dword ptr [esi]\n\tadd ebx, 4\n\tadd esi, 4\n\tcmp eax, MIXING_CLIPMIN\n\tjl cliplow\n\tcmp eax, MIXING_CLIPMAX\n\tjg cliphigh\n\tcmp eax, ecx\n\tjl updatemin\n\tcmp eax, edx\n\tjg updatemax\ncliprecover:\n\tshl eax, MIXING_ATTENUATION\n\tdec edi\n\tmov dword ptr [ebx-4], eax\n\tjnz cliploop\n\tmov eax, 28[esp]\n\tmov dword ptr [eax], ecx\n\tmov eax, 32[esp]\n\tmov dword ptr [eax], edx\n\tmov edx, 24[esp]\n\tpop edi\n\tmov eax, edx\n\tpop esi\n\tshl eax, 2\n\tpop ebx\n\tret\nupdatemin:\n\tmov ecx, eax\n\tjmp cliprecover\nupdatemax:\n\tmov edx, eax\n\tjmp cliprecover\ncliplow:\n\tmov ecx, MIXING_CLIPMIN\n\tmov edx, MIXING_CLIPMAX\n\tmov eax, MIXING_CLIPMIN\n\tjmp cliprecover\ncliphigh:\n\tmov ecx, MIXING_CLIPMIN\n\tmov edx, MIXING_CLIPMAX\n\tmov eax, MIXING_CLIPMAX\n\tjmp cliprecover\n\t}\n}\n#else\n//---GCCFIX: Asm replaced with C function\nDWORD MPPASMCALL X86_Convert32To32(LPVOID lp16, int *pBuffer, DWORD lSampleCount, LPLONG lpMin, LPLONG lpMax)\n{\n\tUINT i ;\n\tint vumin = *lpMin, vumax = *lpMax;\n\tint32_t *p = (int32_t *)lp16;\n\n\tfor ( i=0; i<lSampleCount; i++)\n\t{\n\t\tint n = pBuffer[i];\n\t\tif (n < MIXING_CLIPMIN)\n\t\t\tn = MIXING_CLIPMIN;\n\t\telse if (n > MIXING_CLIPMAX)\n\t\t\tn = MIXING_CLIPMAX;\n\t\tif (n < vumin)\n\t\t\tvumin = n;\n\t\telse if (n > vumax)\n\t\t\tvumax = n;\n\t\tp[i] = n << MIXING_ATTENUATION;\t// 32-bit signed\n\t}\n\t*lpMin = vumin;\n\t*lpMax = vumax;\n\treturn lSampleCount * 4;\n}\n#endif\n\n\n#ifdef MSC_VER\nstatic void MPPASMCALL X86_InitMixBuffer(int *pBuffer, UINT nSamples)\n//------------------------------------------------------------\n{\n\t_asm {\n\tmov ecx, nSamples\n\tmov esi, pBuffer\n\txor eax, eax\n\tmov edx, ecx\n\tshr ecx, 2\n\tand edx, 3\n\tjz unroll4x\nloop1x:\n\tadd esi, 4\n\tdec edx\n\tmov dword ptr [esi-4], eax\n\tjnz loop1x\nunroll4x:\n\tor ecx, ecx\n\tjnz loop4x\n\tjmp done\nloop4x:\n\tadd esi, 16\n\tdec ecx\n\tmov dword ptr [esi-16], eax\n\tmov dword ptr [esi-12], eax\n\tmov dword ptr [esi-8], eax\n\tmov dword ptr [esi-4], eax\n\tjnz loop4x\ndone:;\n\t}\n}\n#else\n//---GCCFIX: Asm replaced with C function\n// Will fill in later.\nstatic void MPPASMCALL X86_InitMixBuffer(int *pBuffer, UINT nSamples)\n{\n\tmemset(pBuffer, 0, nSamples * sizeof(int));\n}\n#endif\n\n\n#ifdef MSC_VER\n__declspec(naked) void MPPASMCALL X86_InterleaveFrontRear(int *pFrontBuf, int *pRearBuf, DWORD nSamples)\n//------------------------------------------------------------------------------\n{\n\t_asm {\n\tpush ebx\n\tpush ebp\n\tpush esi\n\tpush edi\n\tmov ecx, 28[esp] // ecx = samplecount\n\tmov esi, 20[esp] // esi = front buffer\n\tmov edi, 24[esp] // edi = rear buffer\n\tlea esi, [esi+ecx*4]\t// esi = &front[N]\n\tlea edi, [edi+ecx*4]\t// edi = &rear[N]\n\tlea ebx, [esi+ecx*4]\t// ebx = &front[N*2]\ninterleaveloop:\n\tmov eax, dword ptr [esi-8]\n\tmov edx, dword ptr [esi-4]\n\tsub ebx, 16\n\tmov ebp, dword ptr [edi-8]\n\tmov dword ptr [ebx], eax\n\tmov dword ptr [ebx+4], edx\n\tmov eax, dword ptr [edi-4]\n\tsub esi, 8\n\tsub edi, 8\n\tdec ecx\n\tmov dword ptr [ebx+8], ebp\n\tmov dword ptr [ebx+12], eax\n\tjnz interleaveloop\n\tpop edi\n\tpop esi\n\tpop ebp\n\tpop ebx\n\tret\n\t}\n}\n#else\n//---GCCFIX: Asm replaced with C function\n// Multichannel not supported.\nvoid MPPASMCALL X86_InterleaveFrontRear(int *pFrontBuf, int *pRearBuf, DWORD nSamples)\n{\n}\n#endif\n\n\n#ifdef MSC_VER\nVOID MPPASMCALL X86_MonoFromStereo(int *pMixBuf, UINT nSamples)\n//-------------------------------------------------------------\n{\n\t_asm {\n\tmov ecx, nSamples\n\tmov esi, pMixBuf\n\tmov edi, esi\nstloop:\n\tmov eax, dword ptr [esi]\n\tmov edx, dword ptr [esi+4]\n\tadd edi, 4\n\tadd esi, 8\n\tadd eax, edx\n\tsar eax, 1\n\tdec ecx\n\tmov dword ptr [edi-4], eax\n\tjnz stloop\n\t}\n}\n#else\n//---GCCFIX: Asm replaced with C function\nVOID MPPASMCALL X86_MonoFromStereo(int *pMixBuf, UINT nSamples)\n{\n\tUINT j;\n\tfor(UINT i = 0; i < nSamples; i++)\n\t{\n\t\tj = i << 1;\n\t\tpMixBuf[i] = (pMixBuf[j] + pMixBuf[j + 1]) >> 1;\n\t}\n}\n#endif\n\n\n#ifdef MSC_VER\nvoid MPPASMCALL X86_StereoFill(int *pBuffer, UINT nSamples, LPLONG lpROfs, LPLONG lpLOfs)\n//------------------------------------------------------------------------------\n{\n\t_asm {\n\tmov edi, pBuffer\n\tmov ecx, nSamples\n\tmov eax, lpROfs\n\tmov edx, lpLOfs\n\tmov eax, [eax]\n\tmov edx, [edx]\n\tor ecx, ecx\n\tjz fill_loop\n\tmov ebx, eax\n\tor ebx, edx\n\tjz fill_loop\nofsloop:\n\tmov ebx, eax\n\tmov esi, edx\n\tneg ebx\n\tneg esi\n\tsar ebx, 31\n\tsar esi, 31\n\tand ebx, OFSDECAYMASK\n\tand esi, OFSDECAYMASK\n\tadd ebx, eax\n\tadd esi, edx\n\tsar ebx, OFSDECAYSHIFT\n\tsar esi, OFSDECAYSHIFT\n\tsub eax, ebx\n\tsub edx, esi\n\tmov ebx, eax\n\tor ebx, edx\n\tjz fill_loop\n\tadd edi, 8\n\tdec ecx\n\tmov [edi-8], eax\n\tmov [edi-4], edx\n\tjnz ofsloop\nfill_loop:\n\tmov ebx, ecx\n\tand ebx, 3\n\tjz fill4x\nfill1x:\n\tmov [edi], eax\n\tmov [edi+4], edx\n\tadd edi, 8\n\tdec ebx\n\tjnz fill1x\nfill4x:\n\tshr ecx, 2\n\tor ecx, ecx\n\tjz done\nfill4xloop:\n\tmov [edi], eax\n\tmov [edi+4], edx\n\tmov [edi+8], eax\n\tmov [edi+12], edx\n\tadd edi, 8*4\n\tdec ecx\n\tmov [edi-16], eax\n\tmov [edi-12], edx\n\tmov [edi-8], eax\n\tmov [edi-4], edx\n\tjnz fill4xloop\ndone:\n\tmov esi, lpROfs\n\tmov edi, lpLOfs\n\tmov [esi], eax\n\tmov [edi], edx\n\t}\n}\n#else\n//---GCCFIX: Asm replaced with C function\n#define OFSDECAYSHIFT    8\n#define OFSDECAYMASK     0xFF\nvoid MPPASMCALL X86_StereoFill(int *pBuffer, UINT nSamples, LPLONG lpROfs, LPLONG lpLOfs)\n//----------------------------------------------------------------------------\n{\n\tint rofs = *lpROfs;\n\tint lofs = *lpLOfs;\n\n\tif ((!rofs) && (!lofs))\n\t{\n\t\tX86_InitMixBuffer(pBuffer, nSamples*2);\n\t\treturn;\n\t}\n\tfor (UINT i=0; i<nSamples; i++)\n\t{\n\t\tint x_r = (rofs + (((-rofs)>>31) & OFSDECAYMASK)) >> OFSDECAYSHIFT;\n\t\tint x_l = (lofs + (((-lofs)>>31) & OFSDECAYMASK)) >> OFSDECAYSHIFT;\n\t\trofs -= x_r;\n\t\tlofs -= x_l;\n\t\tpBuffer[i*2] = x_r;\n\t\tpBuffer[i*2+1] = x_l;\n\t}\n\t*lpROfs = rofs;\n\t*lpLOfs = lofs;\n}\n#endif\n\n#ifdef MSC_VER\nstatic void MPPASMCALL X86_EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples)\n//------------------------------------------------------------------------------\n{\n\t_asm {\n\tmov esi, pChannel\n\tmov edi, pBuffer\n\tmov ecx, nSamples\n\tmov eax, dword ptr [esi+MODCHANNEL.nROfs]\n\tmov edx, dword ptr [esi+MODCHANNEL.nLOfs]\n\tor ecx, ecx\n\tjz brkloop\nofsloop:\n\tmov ebx, eax\n\tmov esi, edx\n\tneg ebx\n\tneg esi\n\tsar ebx, 31\n\tsar esi, 31\n\tand ebx, OFSDECAYMASK\n\tand esi, OFSDECAYMASK\n\tadd ebx, eax\n\tadd esi, edx\n\tsar ebx, OFSDECAYSHIFT\n\tsar esi, OFSDECAYSHIFT\n\tsub eax, ebx\n\tsub edx, esi\n\tmov ebx, eax\n\tadd dword ptr [edi], eax\n\tadd dword ptr [edi+4], edx\n\tor ebx, edx\n\tjz brkloop\n\tadd edi, 8\n\tdec ecx\n\tjnz ofsloop\nbrkloop:\n\tmov esi, pChannel\n\tmov dword ptr [esi+MODCHANNEL.nROfs], eax\n\tmov dword ptr [esi+MODCHANNEL.nLOfs], edx\n\t}\n}\n#else\n//---GCCFIX: Asm replaced with C function\n// Will fill in later.\nstatic void MPPASMCALL X86_EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples)\n{\n\tint rofs = pChannel->nROfs;\n\tint lofs = pChannel->nLOfs;\n\n\tif ((!rofs) && (!lofs)) return;\n\tfor (UINT i=0; i<nSamples; i++)\n\t{\n\t\tint x_r = (rofs + (((-rofs)>>31) & OFSDECAYMASK)) >> OFSDECAYSHIFT;\n\t\tint x_l = (lofs + (((-lofs)>>31) & OFSDECAYMASK)) >> OFSDECAYSHIFT;\n\t\trofs -= x_r;\n\t\tlofs -= x_l;\n\t\tpBuffer[i*2] += x_r;\n\t\tpBuffer[i*2+1] += x_l;\n\t}\n\tpChannel->nROfs = rofs;\n\tpChannel->nLOfs = lofs;\n}\n#endif\n\n\n//////////////////////////////////////////////////////////////////////////////////\n// Automatic Gain Control\n\n#ifndef NO_AGC\n\n// Limiter\n#define MIXING_LIMITMAX\t\t(0x08100000)\n#define MIXING_LIMITMIN\t\t(-MIXING_LIMITMAX)\n\n#ifdef MSC_VER\n__declspec(naked) UINT MPPASMCALL X86_AGC(int *pBuffer, UINT nSamples, UINT nAGC)\n//------------------------------------------------------------------------------\n{\n\t__asm {\n\tpush ebx\n\tpush ebp\n\tpush esi\n\tpush edi\n\tmov esi, 20[esp]\t// esi = pBuffer+i\n\tmov ecx, 24[esp]\t// ecx = i\n\tmov edi, 28[esp]\t// edi = AGC (0..256)\nagcloop:\n\tmov eax, dword ptr [esi]\n\timul edi\n\tshrd eax, edx, AGC_PRECISION\n\tadd esi, 4\n\tcmp eax, MIXING_LIMITMIN\n\tjl agcupdate\n\tcmp eax, MIXING_LIMITMAX\n\tjg agcupdate\nagcrecover:\n\tdec ecx\n\tmov dword ptr [esi-4], eax\n\tjnz agcloop\n\tmov eax, edi\n\tpop edi\n\tpop esi\n\tpop ebp\n\tpop ebx\n\tret\nagcupdate:\n\tdec edi\n\tjmp agcrecover\n\t}\n}\n\n#pragma warning (default:4100)\n#else\n// Version for GCC\nUINT MPPASMCALL X86_AGC(int *pBuffer, UINT nSamples, UINT nAGC)\n{\n\tint x;\n\n\twhile(nSamples)\n\t{\n\t\tx = ((int64_t)(*pBuffer) * nAGC) >> AGC_PRECISION;\n\n\t\tif((x < MIXING_LIMITMIN) || (x > MIXING_LIMITMAX))\n\t\tnAGC--;\n\n\t\t*pBuffer = x;\n\n\t\tpBuffer++;\n\t\tnSamples--;\n\t}\n\n\treturn nAGC;\n}\n#endif\n\nvoid CSoundFile::ProcessAGC(int count)\n//------------------------------------\n{\n\tstatic DWORD gAGCRecoverCount = 0;\n\tUINT agc = X86_AGC(MixSoundBuffer, count, gnAGC);\n\t// Some kind custom law, so that the AGC stays quite stable, but slowly\n\t// goes back up if the sound level stays below a level inversely\n\t// proportional to the AGC level. (J'me comprends)\n\tif ((agc >= gnAGC) && (gnAGC < AGC_UNITY) && (gnVUMeter < (0xFF - (gnAGC >> (AGC_PRECISION-7))) ))\n\t{\n\t\tgAGCRecoverCount += count;\n\t\tUINT agctimeout = gdwMixingFreq + gnAGC;\n\t\tif (gnChannels >= 2) agctimeout <<= 1;\n\t\tif (gAGCRecoverCount >= agctimeout)\n\t\t{\n\t\t\tgAGCRecoverCount = 0;\n\t\t\tgnAGC++;\n\t\t}\n\t} else\n\t{\n\t\tgnAGC = agc;\n\t\tgAGCRecoverCount = 0;\n\t}\n}\n\n\n\nvoid CSoundFile::ResetAGC()\n//-------------------------\n{\n\tgnAGC = AGC_UNITY;\n}\n\n#endif // NO_AGC\n"
  },
  {
    "path": "contrib/libmodplug/src/libmodplug/it_defs.h",
    "content": "#ifndef _ITDEFS_H_\n#define _ITDEFS_H_\n\n#pragma pack(1)\n\ntypedef struct tagITFILEHEADER\n{\n\tDWORD id;\t\t\t// 0x4D504D49\n\tCHAR songname[26];\n\tWORD reserved1;\t\t// 0x1004\n\tWORD ordnum;\n\tWORD insnum;\n\tWORD smpnum;\n\tWORD patnum;\n\tWORD cwtv;\n\tWORD cmwt;\n\tWORD flags;\n\tWORD special;\n\tBYTE globalvol;\n\tBYTE mv;\n\tBYTE speed;\n\tBYTE tempo;\n\tBYTE sep;\n\tBYTE zero;\n\tWORD msglength;\n\tDWORD msgoffset;\n\tDWORD reserved2;\n\tBYTE chnpan[64];\n\tBYTE chnvol[64];\n} ITFILEHEADER;\n\n\ntypedef struct tagITENVELOPE\n{\n\tBYTE flags;\n\tBYTE num;\n\tBYTE lpb;\n\tBYTE lpe;\n\tBYTE slb;\n\tBYTE sle;\n\tBYTE data[25*3];\n\tBYTE reserved;\n} ITENVELOPE;\n\n// Old Impulse Instrument Format (cmwt < 0x200)\ntypedef struct tagITOLDINSTRUMENT\n{\n\tDWORD id;\t\t\t// IMPI = 0x49504D49\n\tCHAR filename[12];\t// DOS file name\n\tBYTE zero;\n\tBYTE flags;\n\tBYTE vls;\n\tBYTE vle;\n\tBYTE sls;\n\tBYTE sle;\n\tWORD reserved1;\n\tWORD fadeout;\n\tBYTE nna;\n\tBYTE dnc;\n\tWORD trkvers;\n\tBYTE nos;\n\tBYTE reserved2;\n\tCHAR name[26];\n\tWORD reserved3[3];\n\tBYTE keyboard[240];\n\tBYTE volenv[200];\n\tBYTE nodes[50];\n} ITOLDINSTRUMENT;\n\n\n// Impulse Instrument Format\ntypedef struct tagITINSTRUMENT\n{\n\tDWORD id;\n\tCHAR filename[12];\n\tBYTE zero;\n\tBYTE nna;\n\tBYTE dct;\n\tBYTE dca;\n\tWORD fadeout;\n\tsigned char pps;\n\tBYTE ppc;\n\tBYTE gbv;\n\tBYTE dfp;\n\tBYTE rv;\n\tBYTE rp;\n\tWORD trkvers;\n\tBYTE nos;\n\tBYTE reserved1;\n\tCHAR name[26];\n\tBYTE ifc;\n\tBYTE ifr;\n\tBYTE mch;\n\tBYTE mpr;\n\tWORD mbank;\n\tBYTE keyboard[240];\n\tITENVELOPE volenv;\n\tITENVELOPE panenv;\n\tITENVELOPE pitchenv;\n\tBYTE dummy[4]; // was 7, but IT v2.17 saves 554 bytes\n} ITINSTRUMENT;\n\n\n// IT Sample Format\ntypedef struct ITSAMPLESTRUCT\n{\n\tDWORD id;\t\t// 0x53504D49\n\tCHAR filename[12];\n\tBYTE zero;\n\tBYTE gvl;\n\tBYTE flags;\n\tBYTE vol;\n\tCHAR name[26];\n\tBYTE cvt;\n\tBYTE dfp;\n\tDWORD length;\n\tDWORD loopbegin;\n\tDWORD loopend;\n\tDWORD C5Speed;\n\tDWORD susloopbegin;\n\tDWORD susloopend;\n\tDWORD samplepointer;\n\tBYTE vis;\n\tBYTE vid;\n\tBYTE vir;\n\tBYTE vit;\n} ITSAMPLESTRUCT;\n\n#pragma pack()\n\n#endif\n"
  },
  {
    "path": "contrib/libmodplug/src/libmodplug/sndfile.h",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>,\n *          Adam Goode       <adam@evdebs.org> (endian and char fixes for PPC)\n*/\n\n#if defined(HAVE_CONFIG_H) && !defined(CONFIG_H_INCLUDED)\n#include \"config.h\"\n#define CONFIG_H_INCLUDED 1\n#endif\n\n#ifndef __SNDFILE_H\n#define __SNDFILE_H\n\n#ifdef UNDER_CE\nint _strnicmp(const char *str1,const char *str2, int n);\n#endif\n\n#ifndef LPCBYTE\ntypedef const BYTE * LPCBYTE;\n#endif\n\n#define MOD_AMIGAC2\t\t\t0x1AB\n#define MAX_SAMPLE_LENGTH\t16000000\n#define MAX_SAMPLE_RATE\t\t192000\n#define MAX_ORDERS\t\t\t256\n#define MAX_PATTERNS\t\t240\n#define MAX_SAMPLES\t\t\t240\n#define MAX_INSTRUMENTS\t\tMAX_SAMPLES\n#ifdef MODPLUG_FASTSOUNDLIB\n#define MAX_CHANNELS\t\t80\n#else\n#define MAX_CHANNELS\t\t128\n#endif\n#define MAX_BASECHANNELS\t64\n#define MAX_ENVPOINTS\t\t32\n#define MIN_PERIOD\t\t\t0x0020\n#define MAX_PERIOD\t\t\t0xFFFF\n#define MAX_PATTERNNAME\t\t32\n#define MAX_CHANNELNAME\t\t20\n#define MAX_INFONAME\t\t80\n#define MAX_EQ_BANDS\t\t6\n#define MAX_MIXPLUGINS\t\t8\n\n\n#define MOD_TYPE_NONE\t\t0x00\n#define MOD_TYPE_MOD\t\t0x01\n#define MOD_TYPE_S3M\t\t0x02\n#define MOD_TYPE_XM\t\t\t0x04\n#define MOD_TYPE_MED\t\t0x08\n#define MOD_TYPE_MTM\t\t0x10\n#define MOD_TYPE_IT\t\t\t0x20\n#define MOD_TYPE_669\t\t0x40\n#define MOD_TYPE_ULT\t\t0x80\n#define MOD_TYPE_STM\t\t0x100\n#define MOD_TYPE_FAR\t\t0x200\n#define MOD_TYPE_WAV\t\t0x400\n#define MOD_TYPE_AMF\t\t0x800\n#define MOD_TYPE_AMS\t\t0x1000\n#define MOD_TYPE_DSM\t\t0x2000\n#define MOD_TYPE_MDL\t\t0x4000\n#define MOD_TYPE_OKT\t\t0x8000\n#define MOD_TYPE_MID\t\t0x10000\n#define MOD_TYPE_DMF\t\t0x20000\n#define MOD_TYPE_PTM\t\t0x40000\n#define MOD_TYPE_DBM\t\t0x80000\n#define MOD_TYPE_MT2\t\t0x100000\n#define MOD_TYPE_AMF0\t\t0x200000\n#define MOD_TYPE_PSM\t\t0x400000\n#define MOD_TYPE_J2B\t\t0x800000\n#define MOD_TYPE_ABC\t\t0x1000000\n#define MOD_TYPE_PAT\t\t0x2000000\n#define MOD_TYPE_GDM\t\t0x40000000 // Fake type\n#define MOD_TYPE_UMX\t\t0x80000000 // Fake type\n#define MAX_MODTYPE\t\t24\n\n\n\n// Channel flags:\n// Bits 0-7:\tSample Flags\n#define CHN_16BIT               0x01\n#define CHN_LOOP                0x02\n#define CHN_PINGPONGLOOP        0x04\n#define CHN_SUSTAINLOOP         0x08\n#define CHN_PINGPONGSUSTAIN     0x10\n#define CHN_PANNING             0x20\n#define CHN_STEREO              0x40\n#define CHN_PINGPONGFLAG\t0x80\n// Bits 8-31:\tChannel Flags\n#define CHN_MUTE                0x100\n#define CHN_KEYOFF              0x200\n#define CHN_NOTEFADE\t\t0x400\n#define CHN_SURROUND            0x800\n#define CHN_NOIDO               0x1000\n#define CHN_HQSRC               0x2000\n#define CHN_FILTER              0x4000\n#define CHN_VOLUMERAMP\t\t0x8000\n#define CHN_VIBRATO             0x10000\n#define CHN_TREMOLO             0x20000\n#define CHN_PANBRELLO\t\t0x40000\n#define CHN_PORTAMENTO\t\t0x80000\n#define CHN_GLISSANDO\t\t0x100000\n#define CHN_VOLENV              0x200000\n#define CHN_PANENV              0x400000\n#define CHN_PITCHENV\t\t0x800000\n#define CHN_FASTVOLRAMP\t\t0x1000000\n#define CHN_EXTRALOUD\t\t0x2000000\n#define CHN_REVERB              0x4000000\n#define CHN_NOREVERB\t\t0x8000000\n\n\n#define ENV_VOLUME              0x0001\n#define ENV_VOLSUSTAIN\t\t0x0002\n#define ENV_VOLLOOP             0x0004\n#define ENV_PANNING             0x0008\n#define ENV_PANSUSTAIN\t\t0x0010\n#define ENV_PANLOOP             0x0020\n#define ENV_PITCH               0x0040\n#define ENV_PITCHSUSTAIN\t0x0080\n#define ENV_PITCHLOOP\t\t0x0100\n#define ENV_SETPANNING\t\t0x0200\n#define ENV_FILTER              0x0400\n#define ENV_VOLCARRY\t\t0x0800\n#define ENV_PANCARRY\t\t0x1000\n#define ENV_PITCHCARRY\t\t0x2000\n\n#define CMD_NONE                        0\n#define CMD_ARPEGGIO\t\t\t1\n#define CMD_PORTAMENTOUP\t\t2\n#define CMD_PORTAMENTODOWN\t\t3\n#define CMD_TONEPORTAMENTO\t\t4\n#define CMD_VIBRATO                     5\n#define CMD_TONEPORTAVOL\t\t6\n#define CMD_VIBRATOVOL\t\t\t7\n#define CMD_TREMOLO                     8\n#define CMD_PANNING8\t\t\t9\n#define CMD_OFFSET                      10\n#define CMD_VOLUMESLIDE\t\t\t11\n#define CMD_POSITIONJUMP\t\t12\n#define CMD_VOLUME                      13\n#define CMD_PATTERNBREAK\t\t14\n#define CMD_RETRIG                      15\n#define CMD_SPEED                       16\n#define CMD_TEMPO                       17\n#define CMD_TREMOR                      18\n#define CMD_MODCMDEX\t\t\t19\n#define CMD_S3MCMDEX\t\t\t20\n#define CMD_CHANNELVOLUME\t\t21\n#define CMD_CHANNELVOLSLIDE\t\t22\n#define CMD_GLOBALVOLUME\t\t23\n#define CMD_GLOBALVOLSLIDE\t\t24\n#define CMD_KEYOFF                      25\n#define CMD_FINEVIBRATO\t\t\t26\n#define CMD_PANBRELLO\t\t\t27\n#define CMD_XFINEPORTAUPDOWN            28\n#define CMD_PANNINGSLIDE\t\t29\n#define CMD_SETENVPOSITION\t\t30\n#define CMD_MIDI                        31\n\n\n// Volume Column commands\n#define VOLCMD_VOLUME\t\t\t1\n#define VOLCMD_PANNING\t\t\t2\n#define VOLCMD_VOLSLIDEUP\t\t3\n#define VOLCMD_VOLSLIDEDOWN\t\t4\n#define VOLCMD_FINEVOLUP\t\t5\n#define VOLCMD_FINEVOLDOWN\t\t6\n#define VOLCMD_VIBRATOSPEED\t\t7\n#define VOLCMD_VIBRATO\t\t\t8\n#define VOLCMD_PANSLIDELEFT\t\t9\n#define VOLCMD_PANSLIDERIGHT\t        10\n#define VOLCMD_TONEPORTAMENTO\t        11\n#define VOLCMD_PORTAUP\t\t\t12\n#define VOLCMD_PORTADOWN\t\t13\n\n#define RSF_16BIT\t\t0x04\n#define RSF_STEREO\t\t0x08\n\n#define RS_PCM8S\t\t0\t// 8-bit signed\n#define RS_PCM8U\t\t1\t// 8-bit unsigned\n#define RS_PCM8D\t\t2\t// 8-bit delta values\n#define RS_ADPCM4\t\t3\t// 4-bit ADPCM-packed\n#define RS_PCM16D\t\t4\t// 16-bit delta values\n#define RS_PCM16S\t\t5\t// 16-bit signed\n#define RS_PCM16U\t\t6\t// 16-bit unsigned\n#define RS_PCM16M\t\t7\t// 16-bit motorola order\n#define RS_STPCM8S\t\t(RS_PCM8S|RSF_STEREO)  // stereo 8-bit signed\n#define RS_STPCM8U\t\t(RS_PCM8U|RSF_STEREO)  // stereo 8-bit unsigned\n#define RS_STPCM8D\t\t(RS_PCM8D|RSF_STEREO)  // stereo 8-bit delta values\n#define RS_STPCM16S\t\t(RS_PCM16S|RSF_STEREO) // stereo 16-bit signed\n#define RS_STPCM16U\t\t(RS_PCM16U|RSF_STEREO) // stereo 16-bit unsigned\n#define RS_STPCM16D\t\t(RS_PCM16D|RSF_STEREO) // stereo 16-bit delta values\n#define RS_STPCM16M\t\t(RS_PCM16M|RSF_STEREO) // stereo 16-bit signed big endian\n// IT 2.14 compressed samples\n#define RS_IT2148\t\t0x10\n#define RS_IT21416\t\t0x14\n#define RS_IT2158\t\t0x12\n#define RS_IT21516\t\t0x16\n// AMS Packed Samples\n#define RS_AMS8\t\t\t0x11\n#define RS_AMS16\t\t0x15\n// DMF Huffman compression\n#define RS_DMF8\t\t\t0x13\n#define RS_DMF16\t\t0x17\n// MDL Huffman compression\n#define RS_MDL8\t\t\t0x20\n#define RS_MDL16\t\t0x24\n#define RS_PTM8DTO16\t0x25\n// Stereo Interleaved Samples\n#define RS_STIPCM8S\t\t(RS_PCM8S|0x40|RSF_STEREO)\t// stereo 8-bit signed\n#define RS_STIPCM8U\t\t(RS_PCM8U|0x40|RSF_STEREO)\t// stereo 8-bit unsigned\n#define RS_STIPCM16S\t(RS_PCM16S|0x40|RSF_STEREO)\t// stereo 16-bit signed\n#define RS_STIPCM16U\t(RS_PCM16U|0x40|RSF_STEREO)\t// stereo 16-bit unsigned\n#define RS_STIPCM16M\t(RS_PCM16M|0x40|RSF_STEREO)\t// stereo 16-bit signed big endian\n// 24-bit signed\n#define RS_PCM24S\t\t(RS_PCM16S|0x80)\t\t\t// mono 24-bit signed\n#define RS_STIPCM24S\t(RS_PCM16S|0x80|RSF_STEREO)\t// stereo 24-bit signed\n#define RS_PCM32S\t\t(RS_PCM16S|0xC0)\t\t\t// mono 24-bit signed\n#define RS_STIPCM32S\t(RS_PCM16S|0xC0|RSF_STEREO)\t// stereo 24-bit signed\n\n// NNA types\n#define NNA_NOTECUT\t\t0\n#define NNA_CONTINUE\t1\n#define NNA_NOTEOFF\t\t2\n#define NNA_NOTEFADE\t3\n\n// DCT types\n#define DCT_NONE\t\t0\n#define DCT_NOTE\t\t1\n#define DCT_SAMPLE\t\t2\n#define DCT_INSTRUMENT\t3\n\n// DNA types\n#define DNA_NOTECUT\t\t0\n#define DNA_NOTEOFF\t\t1\n#define DNA_NOTEFADE\t2\n\n// Mixer Hardware-Dependent features\n#define SYSMIX_ENABLEMMX\t0x01\n#define SYSMIX_WINDOWSNT\t0x02\n#define SYSMIX_SLOWCPU\t\t0x04\n#define SYSMIX_FASTCPU\t\t0x08\n\n// Module flags\n#define SONG_EMBEDMIDICFG\t0x0001\n#define SONG_FASTVOLSLIDES\t0x0002\n#define SONG_ITOLDEFFECTS\t0x0004\n#define SONG_ITCOMPATMODE\t0x0008\n#define SONG_LINEARSLIDES\t0x0010\n#define SONG_PATTERNLOOP\t0x0020\n#define SONG_STEP\t\t\t0x0040\n#define SONG_PAUSED\t\t\t0x0080\n#define SONG_FADINGSONG\t\t0x0100\n#define SONG_ENDREACHED\t\t0x0200\n#define SONG_GLOBALFADE\t\t0x0400\n#define SONG_CPUVERYHIGH\t0x0800\n#define SONG_FIRSTTICK\t\t0x1000\n#define SONG_MPTFILTERMODE\t0x2000\n#define SONG_SURROUNDPAN\t0x4000\n#define SONG_EXFILTERRANGE\t0x8000\n#define SONG_AMIGALIMITS\t0x10000\n\n// Global Options (Renderer)\n#define SNDMIX_REVERSESTEREO\t0x0001\n#define SNDMIX_NOISEREDUCTION\t0x0002\n#define SNDMIX_AGC\t\t\t\t0x0004\n#define SNDMIX_NORESAMPLING\t\t0x0008\n#define SNDMIX_HQRESAMPLER\t\t0x0010\n#define SNDMIX_MEGABASS\t\t\t0x0020\n#define SNDMIX_SURROUND\t\t\t0x0040\n#define SNDMIX_REVERB\t\t\t0x0080\n#define SNDMIX_EQ\t\t\t\t0x0100\n#define SNDMIX_SOFTPANNING\t\t0x0200\n#define SNDMIX_ULTRAHQSRCMODE\t0x0400\n// Misc Flags (can safely be turned on or off)\n#define SNDMIX_DIRECTTODISK\t\t0x10000\n#define SNDMIX_ENABLEMMX\t\t0x20000\n#define SNDMIX_NOBACKWARDJUMPS\t0x40000\n#define SNDMIX_MAXDEFAULTPAN\t0x80000\t// Used by the MOD loader\n\n\n// Reverb Types (GM2 Presets)\nenum {\n\tREVERBTYPE_SMALLROOM,\n\tREVERBTYPE_MEDIUMROOM,\n\tREVERBTYPE_LARGEROOM,\n\tREVERBTYPE_SMALLHALL,\n\tREVERBTYPE_MEDIUMHALL,\n\tREVERBTYPE_LARGEHALL,\n\tNUM_REVERBTYPES\n};\n\n\nenum {\n\tSRCMODE_NEAREST,\n\tSRCMODE_LINEAR,\n\tSRCMODE_SPLINE,\n\tSRCMODE_POLYPHASE,\n\tNUM_SRC_MODES\n};\n\n\n// Sample Struct\ntypedef struct _MODINSTRUMENT\n{\n\tUINT nLength,nLoopStart,nLoopEnd;\n\tUINT nSustainStart, nSustainEnd;\n\tsigned char *pSample;\n\tUINT nC4Speed;\n\tWORD nPan;\n\tWORD nVolume;\n\tWORD nGlobalVol;\n\tWORD uFlags;\n\tsigned char RelativeTone;\n\tsigned char nFineTune;\n\tBYTE nVibType;\n\tBYTE nVibSweep;\n\tBYTE nVibDepth;\n\tBYTE nVibRate;\n\tCHAR name[22];\n} MODINSTRUMENT;\n\n\n// Instrument Struct\ntypedef struct _INSTRUMENTHEADER\n{\n\tUINT nFadeOut;\n\tDWORD dwFlags;\n\tWORD nGlobalVol;\n\tWORD nPan;\n\tWORD VolPoints[MAX_ENVPOINTS];\n\tWORD PanPoints[MAX_ENVPOINTS];\n\tWORD PitchPoints[MAX_ENVPOINTS];\n\tBYTE VolEnv[MAX_ENVPOINTS];\n\tBYTE PanEnv[MAX_ENVPOINTS];\n\tBYTE PitchEnv[MAX_ENVPOINTS];\n\tBYTE Keyboard[128];\n\tBYTE NoteMap[128];\n\n\tBYTE nVolEnv;\n\tBYTE nPanEnv;\n\tBYTE nPitchEnv;\n\tBYTE nVolLoopStart;\n\tBYTE nVolLoopEnd;\n\tBYTE nVolSustainBegin;\n\tBYTE nVolSustainEnd;\n\tBYTE nPanLoopStart;\n\tBYTE nPanLoopEnd;\n\tBYTE nPanSustainBegin;\n\tBYTE nPanSustainEnd;\n\tBYTE nPitchLoopStart;\n\tBYTE nPitchLoopEnd;\n\tBYTE nPitchSustainBegin;\n\tBYTE nPitchSustainEnd;\n\tBYTE nNNA;\n\tBYTE nDCT;\n\tBYTE nDNA;\n\tBYTE nPanSwing;\n\tBYTE nVolSwing;\n\tBYTE nIFC;\n\tBYTE nIFR;\n\tWORD wMidiBank;\n\tBYTE nMidiProgram;\n\tBYTE nMidiChannel;\n\tBYTE nMidiDrumKey;\n\tsigned char nPPS;\n\tunsigned char nPPC;\n\tCHAR name[32];\n\tCHAR filename[12];\n} INSTRUMENTHEADER;\n\n\n// Channel Struct\ntypedef struct _MODCHANNEL\n{\n\t// First 32-bytes: Most used mixing information: don't change it\n\tsigned char * pCurrentSample;\n\tDWORD nPos;\n\tDWORD nPosLo;\t// actually 16-bit\n\tLONG nInc;\t\t// 16.16\n\tLONG nRightVol;\n\tLONG nLeftVol;\n\tLONG nRightRamp;\n\tLONG nLeftRamp;\n\t// 2nd cache line\n\tDWORD nLength;\n\tDWORD dwFlags;\n\tDWORD nLoopStart;\n\tDWORD nLoopEnd;\n\tLONG nRampRightVol;\n\tLONG nRampLeftVol;\n\tLONG nFilter_Y1, nFilter_Y2, nFilter_Y3, nFilter_Y4;\n\tLONG nFilter_A0, nFilter_B0, nFilter_B1;\n\tLONG nROfs, nLOfs;\n\tLONG nRampLength;\n\t// Information not used in the mixer\n\tsigned char * pSample;\n\tLONG nNewRightVol, nNewLeftVol;\n\tLONG nRealVolume, nRealPan;\n\tLONG nVolume, nPan, nFadeOutVol;\n\tLONG nPeriod, nC4Speed, nPortamentoDest;\n\tINSTRUMENTHEADER *pHeader;\n\tMODINSTRUMENT *pInstrument;\n\tDWORD nVolEnvPosition, nPanEnvPosition, nPitchEnvPosition;\n\tDWORD nMasterChn, nVUMeter;\n\tLONG nGlobalVol, nInsVol;\n\tLONG nFineTune, nTranspose;\n\tLONG nPortamentoSlide, nAutoVibDepth;\n\tUINT nAutoVibPos, nVibratoPos, nTremoloPos, nPanbrelloPos;\n\t// 16-bit members\n\tsigned short nVolSwing, nPanSwing;\n\t// 8-bit members\n\tBYTE nNote, nNNA;\n\tBYTE nNewNote, nNewIns, nCommand, nArpeggio;\n\tBYTE nOldVolumeSlide, nOldFineVolUpDown;\n\tBYTE nOldPortaUpDown, nOldFinePortaUpDown;\n\tBYTE nOldPanSlide, nOldChnVolSlide;\n\tBYTE nVibratoType, nVibratoSpeed, nVibratoDepth;\n\tBYTE nTremoloType, nTremoloSpeed, nTremoloDepth;\n\tBYTE nPanbrelloType, nPanbrelloSpeed, nPanbrelloDepth;\n\tBYTE nOldCmdEx, nOldVolParam, nOldTempo;\n\tBYTE nOldOffset, nOldHiOffset;\n\tBYTE nCutOff, nResonance;\n\tBYTE nRetrigCount, nRetrigParam;\n\tBYTE nTremorCount, nTremorParam;\n\tBYTE nPatternLoop, nPatternLoopCount;\n\tBYTE nRowNote, nRowInstr;\n\tBYTE nRowVolCmd, nRowVolume;\n\tBYTE nRowCommand, nRowParam;\n\tBYTE nLeftVU, nRightVU;\n\tBYTE nActiveMacro, nPadding;\n} MODCHANNEL;\n\n\ntypedef struct _MODCHANNELSETTINGS\n{\n\tUINT nPan;\n\tUINT nVolume;\n\tDWORD dwFlags;\n\tUINT nMixPlugin;\n        char szName[MAX_CHANNELNAME];        // changed from CHAR\n} MODCHANNELSETTINGS;\n\n\ntypedef struct _MODCOMMAND\n{\n\tBYTE note;\n\tBYTE instr;\n\tBYTE volcmd;\n\tBYTE command;\n\tBYTE vol;\n\tBYTE param;\n} MODCOMMAND, *LPMODCOMMAND;\n\n////////////////////////////////////////////////////////////////////\n// Mix Plugins\n#define MIXPLUG_MIXREADY\t\t\t0x01\t// Set when cleared\n\nclass MODPLUG_EXPORT IMixPlugin\n{\npublic:\n\tvirtual ~IMixPlugin() {};\n\tvirtual int AddRef() = 0;\n\tvirtual int Release() = 0;\n\tvirtual void SaveAllParameters() = 0;\n\tvirtual void RestoreAllParameters() = 0;\n\tvirtual void Process(float *pOutL, float *pOutR, unsigned long nSamples) = 0;\n\tvirtual void Init(unsigned long nFreq, int bReset) = 0;\n\tvirtual void MidiSend(DWORD dwMidiCode) = 0;\n\tvirtual void MidiCommand(UINT nMidiCh, UINT nMidiProg, UINT note, UINT vol) = 0;\n};\n\n\n#define MIXPLUG_INPUTF_MASTEREFFECT\t\t0x01\t// Apply to master mix\n#define MIXPLUG_INPUTF_BYPASS\t\t\t0x02\t// Bypass effect\n#define MIXPLUG_INPUTF_WETMIX\t\t\t0x04\t// Wet Mix (dry added)\n\ntypedef struct _SNDMIXPLUGINSTATE\n{\n\tDWORD dwFlags;\t\t\t\t\t// MIXPLUG_XXXX\n\tLONG nVolDecayL, nVolDecayR;\t// Buffer click removal\n\tint *pMixBuffer;\t\t\t\t// Stereo effect send buffer\n\tfloat *pOutBufferL;\t\t\t\t// Temp storage for int -> float conversion\n\tfloat *pOutBufferR;\n} SNDMIXPLUGINSTATE, *PSNDMIXPLUGINSTATE;\n\ntypedef struct _SNDMIXPLUGININFO\n{\n\tDWORD dwPluginId1;\n\tDWORD dwPluginId2;\n\tDWORD dwInputRouting;\t// MIXPLUG_INPUTF_XXXX\n\tDWORD dwOutputRouting;\t// 0=mix 0x80+=fx\n\tDWORD dwReserved[4];\t// Reserved for routing info\n\tCHAR szName[32];\n\tCHAR szLibraryName[64];\t// original DLL name\n} SNDMIXPLUGININFO, *PSNDMIXPLUGININFO; // Size should be 128\n\ntypedef struct _SNDMIXPLUGIN\n{\n\tIMixPlugin *pMixPlugin;\n\tPSNDMIXPLUGINSTATE pMixState;\n\tULONG nPluginDataSize;\n\tPVOID pPluginData;\n\tSNDMIXPLUGININFO Info;\n} SNDMIXPLUGIN, *PSNDMIXPLUGIN;\n\ntypedef\tBOOL (*PMIXPLUGINCREATEPROC)(PSNDMIXPLUGIN);\n\n////////////////////////////////////////////////////////////////////\n\nenum {\n\tMIDIOUT_START=0,\n\tMIDIOUT_STOP,\n\tMIDIOUT_TICK,\n\tMIDIOUT_NOTEON,\n\tMIDIOUT_NOTEOFF,\n\tMIDIOUT_VOLUME,\n\tMIDIOUT_PAN,\n\tMIDIOUT_BANKSEL,\n\tMIDIOUT_PROGRAM\n};\n\n\ntypedef struct MODMIDICFG\n{\n        char szMidiGlb[9*32];      // changed from CHAR\n        char szMidiSFXExt[16*32];  // changed from CHAR\n        char szMidiZXXExt[128*32]; // changed from CHAR\n} MODMIDICFG, *LPMODMIDICFG;\n\n#define NOTE_MAX                        120 //Defines maximum notevalue as well as maximum number of notes.\n\ntypedef VOID (* LPSNDMIXHOOKPROC)(int *, unsigned long, unsigned long); // buffer, samples, channels\n\n\n\n//==============\nclass MODPLUG_EXPORT CSoundFile\n//==============\n{\npublic:\t// Static Members\n\tstatic UINT m_nXBassDepth;\n\tstatic UINT m_nXBassRange;\n\tstatic UINT m_nReverbDepth;\n\tstatic UINT m_nReverbDelay;\n\tstatic UINT gnReverbType;\n\tstatic UINT m_nProLogicDepth;\n\tstatic UINT m_nProLogicDelay;\n\tstatic UINT m_nStereoSeparation;\n\tstatic UINT m_nMaxMixChannels;\n\tstatic LONG m_nStreamVolume;\n\tstatic DWORD gdwSysInfo;\n\tstatic DWORD gdwSoundSetup;\n\tstatic DWORD gdwMixingFreq;\n\tstatic DWORD gnBitsPerSample;\n\tstatic DWORD gnChannels;\n\tstatic UINT gnAGC;\n\tstatic UINT gnVolumeRampSamples;\n\tstatic UINT gnVUMeter;\n\tstatic UINT gnCPUUsage;\n\tstatic LPSNDMIXHOOKPROC gpSndMixHook;\n\tstatic PMIXPLUGINCREATEPROC gpMixPluginCreateProc;\n\npublic:\t// for Editing\n\tMODCHANNEL Chn[MAX_CHANNELS];\t\t\t\t\t// Channels\n\tUINT ChnMix[MAX_CHANNELS];\t\t\t\t\t\t// Channels to be mixed\n\tMODINSTRUMENT Ins[MAX_SAMPLES];\t\t\t\t\t// Instruments\n\tINSTRUMENTHEADER *Headers[MAX_INSTRUMENTS];\t\t// Instrument Headers\n\tMODCHANNELSETTINGS ChnSettings[MAX_BASECHANNELS]; // Channels settings\n\tMODCOMMAND *Patterns[MAX_PATTERNS];\t\t\t\t// Patterns\n\tWORD PatternSize[MAX_PATTERNS];\t\t\t\t\t// Patterns Lengths\n\tBYTE Order[MAX_ORDERS];\t\t\t\t\t\t\t// Pattern Orders\n\tMODMIDICFG m_MidiCfg;\t\t\t\t\t\t\t// Midi macro config table\n\tSNDMIXPLUGIN m_MixPlugins[MAX_MIXPLUGINS];\t\t// Mix plugins\n\tUINT m_nDefaultSpeed, m_nDefaultTempo, m_nDefaultGlobalVolume;\n\tDWORD m_dwSongFlags;\t\t\t\t\t\t\t// Song flags SONG_XXXX\n\tUINT m_nChannels, m_nMixChannels, m_nMixStat, m_nBufferCount;\n\tUINT m_nType, m_nSamples, m_nInstruments;\n\tUINT m_nTickCount, m_nTotalCount, m_nPatternDelay, m_nFrameDelay;\n\tUINT m_nMusicSpeed, m_nMusicTempo;\n\tUINT m_nNextRow, m_nRow, m_nNextStartRow;\n\tUINT m_nPattern,m_nCurrentPattern,m_nNextPattern,m_nRestartPos;\n\tUINT m_nMasterVolume, m_nGlobalVolume, m_nSongPreAmp;\n\tUINT m_nFreqFactor, m_nTempoFactor, m_nOldGlbVolSlide;\n\tLONG m_nMinPeriod, m_nMaxPeriod, m_nRepeatCount, m_nInitialRepeatCount;\n\tDWORD m_nGlobalFadeSamples, m_nGlobalFadeMaxSamples;\n\tUINT m_nMaxOrderPosition;\n\tUINT m_nPatternNames;\n\tLPSTR m_lpszSongComments, m_lpszPatternNames;\n\tchar m_szNames[MAX_INSTRUMENTS][32];    // changed from CHAR\n\tCHAR CompressionTable[16];\n\npublic:\n\tCSoundFile();\n\t~CSoundFile();\n\npublic:\n\tBOOL Create(LPCBYTE lpStream, DWORD dwMemLength=0);\n\tBOOL Destroy();\n\tUINT GetType() const { return m_nType; }\n\tUINT GetNumChannels() const;\n\tUINT GetLogicalChannels() const { return m_nChannels; }\n\tBOOL SetMasterVolume(UINT vol, BOOL bAdjustAGC=FALSE);\n\tUINT GetMasterVolume() const { return m_nMasterVolume; }\n\tUINT GetNumPatterns() const;\n\tUINT GetNumInstruments() const;\n\tUINT GetNumSamples() const { return m_nSamples; }\n\tUINT GetCurrentPos() const;\n\tUINT GetCurrentPattern() const { return m_nPattern; }\n\tUINT GetCurrentOrder() const { return m_nCurrentPattern; }\n\tUINT GetSongComments(LPSTR s, UINT cbsize, UINT linesize=32);\n\tUINT GetRawSongComments(LPSTR s, UINT cbsize, UINT linesize=32);\n\tUINT GetMaxPosition() const;\n\tvoid SetCurrentPos(UINT nPos);\n\tvoid SetCurrentOrder(UINT nOrder);\n\tvoid GetTitle(LPSTR s) const { lstrcpyn(s,m_szNames[0],32); }\n\tLPCSTR GetTitle() const { return m_szNames[0]; }\n\tUINT GetSampleName(UINT nSample,LPSTR s=NULL) const;\n\tUINT GetInstrumentName(UINT nInstr,LPSTR s=NULL) const;\n\tUINT GetMusicSpeed() const { return m_nMusicSpeed; }\n\tUINT GetMusicTempo() const { return m_nMusicTempo; }\n\tDWORD GetLength(BOOL bAdjust, BOOL bTotal=FALSE);\n\tDWORD GetSongTime() { return GetLength(FALSE, TRUE); }\n\tvoid SetRepeatCount(int n) { m_nRepeatCount = n; m_nInitialRepeatCount = n; }\n\tint GetRepeatCount() const { return m_nRepeatCount; }\n\tBOOL IsPaused() const {\treturn (m_dwSongFlags & SONG_PAUSED) ? TRUE : FALSE; }\n\tvoid LoopPattern(int nPat, int nRow=0);\n\tvoid CheckCPUUsage(UINT nCPU);\n\tBOOL SetPatternName(UINT nPat, LPCSTR lpszName);\n\tBOOL GetPatternName(UINT nPat, LPSTR lpszName, UINT cbSize=MAX_PATTERNNAME) const;\n\t// Module Loaders\n\tBOOL ReadXM(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadS3M(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadMod(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadMed(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadMTM(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadSTM(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadIT(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL Read669(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadUlt(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadWav(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadDSM(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadFAR(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadAMS(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadAMS2(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadMDL(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadOKT(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadDMF(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadPTM(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadDBM(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadAMF(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadMT2(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadPSM(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadJ2B(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadGDM(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadUMX(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadABC(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL TestABC(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadMID(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL TestMID(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL ReadPAT(LPCBYTE lpStream, DWORD dwMemLength);\n\tBOOL TestPAT(LPCBYTE lpStream, DWORD dwMemLength);\n\t// Save Functions\n#ifndef MODPLUG_NO_FILESAVE\n\tUINT WriteSample(FILE *f, MODINSTRUMENT *pins, UINT nFlags, UINT nMaxLen=0);\n\tBOOL SaveXM(LPCSTR lpszFileName, UINT nPacking=0);\n\tBOOL SaveS3M(LPCSTR lpszFileName, UINT nPacking=0);\n\tBOOL SaveMod(LPCSTR lpszFileName, UINT nPacking=0);\n\tBOOL SaveIT(LPCSTR lpszFileName, UINT nPacking=0);\n#endif // MODPLUG_NO_FILESAVE\n\t// MOD Convert function\n\tUINT GetBestSaveFormat() const;\n\tUINT GetSaveFormats() const;\n\tvoid ConvertModCommand(MODCOMMAND *) const;\n\tvoid S3MConvert(MODCOMMAND *m, BOOL bIT) const;\n\tvoid S3MSaveConvert(UINT *pcmd, UINT *pprm, BOOL bIT) const;\n\tWORD ModSaveCommand(const MODCOMMAND *m, BOOL bXM) const;\n\npublic:\n\t// Real-time sound functions\n\tVOID ResetChannels();\n\n\tUINT Read(LPVOID lpBuffer, UINT cbBuffer);\n\tUINT CreateStereoMix(int count);\n\tBOOL FadeSong(UINT msec);\n\tBOOL GlobalFadeSong(UINT msec);\n\tUINT GetTotalTickCount() const { return m_nTotalCount; }\n\tVOID ResetTotalTickCount() { m_nTotalCount = 0; }\n\npublic:\n\t// Mixer Config\n\tstatic BOOL InitPlayer(BOOL bReset=FALSE);\n\tstatic BOOL SetMixConfig(UINT nStereoSeparation, UINT nMaxMixChannels);\n\tstatic BOOL SetWaveConfig(UINT nRate,UINT nBits,UINT nChannels,BOOL bMMX=FALSE);\n\tstatic BOOL SetResamplingMode(UINT nMode); // SRCMODE_XXXX\n\tstatic BOOL IsStereo() { return (gnChannels > 1) ? TRUE : FALSE; }\n\tstatic DWORD GetSampleRate() { return gdwMixingFreq; }\n\tstatic DWORD GetBitsPerSample() { return gnBitsPerSample; }\n\tstatic DWORD InitSysInfo();\n\tstatic DWORD GetSysInfo() { return gdwSysInfo; }\n\t// AGC\n\tstatic BOOL GetAGC() { return (gdwSoundSetup & SNDMIX_AGC) ? TRUE : FALSE; }\n\tstatic void SetAGC(BOOL b);\n\tstatic void ResetAGC();\n\tstatic void ProcessAGC(int count);\n\n\t//GCCFIX -- added these functions back in!\n\tstatic BOOL SetWaveConfigEx(BOOL bSurround,BOOL bNoOverSampling,BOOL bReverb,BOOL hqido,BOOL bMegaBass,BOOL bNR,BOOL bEQ);\n\t// DSP Effects\n\tstatic void InitializeDSP(BOOL bReset);\n\tstatic void ProcessStereoDSP(int count);\n\tstatic void ProcessMonoDSP(int count);\n\t// [Reverb level 0(quiet)-100(loud)], [delay in ms, usually 40-200ms]\n\tstatic BOOL SetReverbParameters(UINT nDepth, UINT nDelay);\n\t// [XBass level 0(quiet)-100(loud)], [cutoff in Hz 10-100]\n\tstatic BOOL SetXBassParameters(UINT nDepth, UINT nRange);\n\t// [Surround level 0(quiet)-100(heavy)] [delay in ms, usually 5-40ms]\n\tstatic BOOL SetSurroundParameters(UINT nDepth, UINT nDelay);\npublic:\n\tBOOL ReadNote();\n\tBOOL ProcessRow();\n\tBOOL ProcessEffects();\n\tUINT GetNNAChannel(UINT nChn) const;\n\tvoid CheckNNA(UINT nChn, UINT instr, int note, BOOL bForceCut);\n\tvoid NoteChange(UINT nChn, int note, BOOL bPorta=FALSE, BOOL bResetEnv=TRUE);\n\tvoid InstrumentChange(MODCHANNEL *pChn, UINT instr, BOOL bPorta=FALSE,BOOL bUpdVol=TRUE,BOOL bResetEnv=TRUE);\n\t// Channel Effects\n\tvoid PortamentoUp(MODCHANNEL *pChn, UINT param);\n\tvoid PortamentoDown(MODCHANNEL *pChn, UINT param);\n\tvoid FinePortamentoUp(MODCHANNEL *pChn, UINT param);\n\tvoid FinePortamentoDown(MODCHANNEL *pChn, UINT param);\n\tvoid ExtraFinePortamentoUp(MODCHANNEL *pChn, UINT param);\n\tvoid ExtraFinePortamentoDown(MODCHANNEL *pChn, UINT param);\n\tvoid TonePortamento(MODCHANNEL *pChn, UINT param);\n\tvoid Vibrato(MODCHANNEL *pChn, UINT param);\n\tvoid FineVibrato(MODCHANNEL *pChn, UINT param);\n\tvoid VolumeSlide(MODCHANNEL *pChn, UINT param);\n\tvoid PanningSlide(MODCHANNEL *pChn, UINT param);\n\tvoid ChannelVolSlide(MODCHANNEL *pChn, UINT param);\n\tvoid FineVolumeUp(MODCHANNEL *pChn, UINT param);\n\tvoid FineVolumeDown(MODCHANNEL *pChn, UINT param);\n\tvoid Tremolo(MODCHANNEL *pChn, UINT param);\n\tvoid Panbrello(MODCHANNEL *pChn, UINT param);\n\tvoid RetrigNote(UINT nChn, UINT param);\n\tvoid NoteCut(UINT nChn, UINT nTick);\n\tvoid KeyOff(UINT nChn);\n\tint PatternLoop(MODCHANNEL *, UINT param);\n\tvoid ExtendedMODCommands(UINT nChn, UINT param);\n\tvoid ExtendedS3MCommands(UINT nChn, UINT param);\n\tvoid ExtendedChannelEffect(MODCHANNEL *, UINT param);\n\tvoid ProcessMidiMacro(UINT nChn, LPCSTR pszMidiMacro, UINT param=0);\n\tvoid SetupChannelFilter(MODCHANNEL *pChn, BOOL bReset, int flt_modifier=256) const;\n\t// Low-Level effect processing\n\tvoid DoFreqSlide(MODCHANNEL *pChn, LONG nFreqSlide);\n\t// Global Effects\n\tvoid SetTempo(UINT param);\n\tvoid SetSpeed(UINT param);\n\tvoid GlobalVolSlide(UINT param);\n\tDWORD IsSongFinished(UINT nOrder, UINT nRow) const;\n\tBOOL IsValidBackwardJump(UINT nStartOrder, UINT nStartRow, UINT nJumpOrder, UINT nJumpRow) const;\n\t// Read/Write sample functions\n\tsigned char GetDeltaValue(signed char prev, UINT n) const { return (signed char)(prev + CompressionTable[n & 0x0F]); }\n\tUINT PackSample(int &sample, int next);\n\tBOOL CanPackSample(LPSTR pSample, UINT nLen, UINT nPacking, BYTE *result=NULL);\n\tUINT ReadSample(MODINSTRUMENT *pIns, UINT nFlags, LPCSTR pMemFile, DWORD dwMemLength);\n\tBOOL DestroySample(UINT nSample);\n\tBOOL DestroyInstrument(UINT nInstr);\n\tBOOL IsSampleUsed(UINT nSample);\n\tBOOL IsInstrumentUsed(UINT nInstr);\n\tBOOL RemoveInstrumentSamples(UINT nInstr);\n\tUINT DetectUnusedSamples(BOOL *);\n\tBOOL RemoveSelectedSamples(BOOL *);\n\tvoid AdjustSampleLoop(MODINSTRUMENT *pIns);\n\t// I/O from another sound file\n\tBOOL ReadInstrumentFromSong(UINT nInstr, CSoundFile *, UINT nSrcInstrument);\n\tBOOL ReadSampleFromSong(UINT nSample, CSoundFile *, UINT nSrcSample);\n\t// Period/Note functions\n\tUINT GetNoteFromPeriod(UINT period) const;\n\tUINT GetPeriodFromNote(UINT note, int nFineTune, UINT nC4Speed) const;\n\tUINT GetFreqFromPeriod(UINT period, UINT nC4Speed, int nPeriodFrac=0) const;\n\t// Misc functions\n\tMODINSTRUMENT *GetSample(UINT n) { return Ins+n; }\n\tvoid ResetMidiCfg();\n\tUINT MapMidiInstrument(DWORD dwProgram, UINT nChannel, UINT nNote);\n\tBOOL ITInstrToMPT(const void *p, INSTRUMENTHEADER *penv, UINT trkvers);\n\tUINT SaveMixPlugins(FILE *f=NULL, BOOL bUpdate=TRUE);\n\tUINT LoadMixPlugins(const void *pData, UINT nLen);\n#ifndef NO_FILTER\n\tDWORD CutOffToFrequency(UINT nCutOff, int flt_modifier=256) const; // [0-255] => [1-10KHz]\n#endif\n\n\t// Static helper functions\npublic:\n\tstatic DWORD TransposeToFrequency(int transp, int ftune=0);\n\tstatic int FrequencyToTranspose(DWORD freq);\n\tstatic void FrequencyToTranspose(MODINSTRUMENT *psmp);\n\n\t// System-Dependant functions\npublic:\n\tstatic MODCOMMAND *AllocatePattern(UINT rows, UINT nchns);\n\tstatic signed char* AllocateSample(UINT nbytes);\n\tstatic void FreePattern(LPVOID pat);\n\tstatic void FreeSample(LPVOID p);\n\tstatic UINT Normalize24BitBuffer(LPBYTE pbuffer, UINT cbsizebytes, DWORD lmax24, DWORD dwByteInc);\n};\n\n\n// inline DWORD BigEndian(DWORD x) { return ((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | ((x & 0xFF0000) >> 8) | ((x & 0xFF000000) >> 24); }\n// inline WORD BigEndianW(WORD x) { return (WORD)(((x >> 8) & 0xFF) | ((x << 8) & 0xFF00)); }\n\n\n//////////////////////////////////////////////////////////\n// WAVE format information\n\n#pragma pack(1)\n\n// Standard IFF chunks IDs\n#define IFFID_FORM\t\t0x4d524f46\n#define IFFID_RIFF\t\t0x46464952\n#define IFFID_WAVE\t\t0x45564157\n#define IFFID_LIST\t\t0x5453494C\n#define IFFID_INFO\t\t0x4F464E49\n\n// IFF Info fields\n#define IFFID_ICOP\t\t0x504F4349\n#define IFFID_IART\t\t0x54524149\n#define IFFID_IPRD\t\t0x44525049\n#define IFFID_INAM\t\t0x4D414E49\n#define IFFID_ICMT\t\t0x544D4349\n#define IFFID_IENG\t\t0x474E4549\n#define IFFID_ISFT\t\t0x54465349\n#define IFFID_ISBJ\t\t0x4A425349\n#define IFFID_IGNR\t\t0x524E4749\n#define IFFID_ICRD\t\t0x44524349\n\n// Wave IFF chunks IDs\n#define IFFID_wave\t\t0x65766177\n#define IFFID_fmt\t\t0x20746D66\n#define IFFID_wsmp\t\t0x706D7377\n#define IFFID_pcm\t\t0x206d6370\n#define IFFID_data\t\t0x61746164\n#define IFFID_smpl\t\t0x6C706D73\n#define IFFID_xtra\t\t0x61727478\n\ntypedef struct WAVEFILEHEADER\n{\n\tDWORD id_RIFF;\t\t// \"RIFF\"\n\tDWORD filesize;\t\t// file length-8\n\tDWORD id_WAVE;\n} WAVEFILEHEADER;\n\n\ntypedef struct WAVEFORMATHEADER\n{\n\tDWORD id_fmt;\t\t// \"fmt \"\n\tDWORD hdrlen;\t\t// 16\n\tWORD format;\t\t// 1\n\tWORD channels;\t\t// 1:mono, 2:stereo\n\tDWORD freqHz;\t\t// sampling freq\n\tDWORD bytessec;\t\t// bytes/sec=freqHz*samplesize\n\tWORD samplesize;\t// sizeof(sample)\n\tWORD bitspersample;\t// bits per sample (8/16)\n} WAVEFORMATHEADER;\n\n\ntypedef struct WAVEDATAHEADER\n{\n\tDWORD id_data;\t\t// \"data\"\n\tDWORD length;\t\t// length of data\n} WAVEDATAHEADER;\n\n\ntypedef struct WAVESMPLHEADER\n{\n\t// SMPL\n\tDWORD smpl_id;\t\t// \"smpl\"\t-> 0x6C706D73\n\tDWORD smpl_len;\t\t// length of smpl: 3Ch\t(54h with sustain loop)\n\tDWORD dwManufacturer;\n\tDWORD dwProduct;\n\tDWORD dwSamplePeriod;\t// 1000000000/freqHz\n\tDWORD dwBaseNote;\t// 3Ch = C-4 -> 60 + RelativeTone\n\tDWORD dwPitchFraction;\n\tDWORD dwSMPTEFormat;\n\tDWORD dwSMPTEOffset;\n\tDWORD dwSampleLoops;\t// number of loops\n\tDWORD cbSamplerData;\n} WAVESMPLHEADER;\n\n\ntypedef struct SAMPLELOOPSTRUCT\n{\n\tDWORD dwIdentifier;\n\tDWORD dwLoopType;\t\t// 0=normal, 1=bidi\n\tDWORD dwLoopStart;\n\tDWORD dwLoopEnd;\t\t// Byte offset ?\n\tDWORD dwFraction;\n\tDWORD dwPlayCount;\t\t// Loop Count, 0=infinite\n} SAMPLELOOPSTRUCT;\n\n\ntypedef struct WAVESAMPLERINFO\n{\n\tWAVESMPLHEADER wsiHdr;\n\tSAMPLELOOPSTRUCT wsiLoops[2];\n} WAVESAMPLERINFO;\n\n\ntypedef struct WAVELISTHEADER\n{\n\tDWORD list_id;\t// \"LIST\" -> 0x5453494C\n\tDWORD list_len;\n\tDWORD info;\t\t// \"INFO\"\n} WAVELISTHEADER;\n\n\ntypedef struct WAVEEXTRAHEADER\n{\n\tDWORD xtra_id;\t// \"xtra\"\t-> 0x61727478\n\tDWORD xtra_len;\n\tDWORD dwFlags;\n\tWORD  wPan;\n\tWORD  wVolume;\n\tWORD  wGlobalVol;\n\tWORD  wReserved;\n\tBYTE nVibType;\n\tBYTE nVibSweep;\n\tBYTE nVibDepth;\n\tBYTE nVibRate;\n} WAVEEXTRAHEADER;\n\n#pragma pack()\n\n///////////////////////////////////////////////////////////\n// Low-level Mixing functions\n\n#define MIXBUFFERSIZE\t\t512\n#define MIXING_ATTENUATION\t4\n#define MIXING_CLIPMIN\t\t(-0x08000000)\n#define MIXING_CLIPMAX\t\t(0x07FFFFFF)\n#define VOLUMERAMPPRECISION\t12\n#define FADESONGDELAY\t\t100\n#define EQ_BUFFERSIZE\t\t(MIXBUFFERSIZE)\n#define AGC_PRECISION\t\t9\n#define AGC_UNITY\t\t\t(1 << AGC_PRECISION)\n\n// Calling conventions\n#ifdef MSC_VER\n#define MPPASMCALL\t__cdecl\n#define MPPFASTCALL\t__fastcall\n#else\n#define MPPASMCALL\n#define MPPFASTCALL\n#endif\n\n#define MOD2XMFineTune(k)\t((int)( (signed char)((k)<<4) ))\n#define XM2MODFineTune(k)\t((int)( (k>>4)&0x0f ))\n\nint _muldiv(long a, long b, long c);\nint _muldivr(long a, long b, long c);\n\n\n// Byte swapping functions from the GNU C Library and libsdl\n\n/* Swap bytes in 16 bit value.  */\n#ifdef __GNUC__\n# define bswap_16(x) \\\n    (__extension__\t\t\t\t\t\t\t      \\\n     ({ unsigned short int __bsx = (x);\t\t\t\t\t      \\\n        ((((__bsx) >> 8) & 0xff) | (((__bsx) & 0xff) << 8)); }))\n#else\nstatic __inline unsigned short int\nbswap_16 (unsigned short int __bsx)\n{\n  return ((((__bsx) >> 8) & 0xff) | (((__bsx) & 0xff) << 8));\n}\n#endif\n\n/* Swap bytes in 32 bit value.  */\n#ifdef __GNUC__\n# define bswap_32(x) \\\n    (__extension__\t\t\t\t\t\t\t      \\\n     ({ unsigned int __bsx = (x);\t\t\t\t\t      \\\n        ((((__bsx) & 0xff000000) >> 24) | (((__bsx) & 0x00ff0000) >>  8) |    \\\n\t (((__bsx) & 0x0000ff00) <<  8) | (((__bsx) & 0x000000ff) << 24)); }))\n#else\nstatic __inline unsigned int\nbswap_32 (unsigned int __bsx)\n{\n  return ((((__bsx) & 0xff000000) >> 24) | (((__bsx) & 0x00ff0000) >>  8) |\n\t  (((__bsx) & 0x0000ff00) <<  8) | (((__bsx) & 0x000000ff) << 24));\n}\n#endif\n\n#if (defined ARM) && (defined _WIN32_WCE)\nstatic __inline unsigned short int\nARM_get16(const void *data)\n{\n\tunsigned short int s;\n\tmemcpy(&s,data,sizeof(s));\n\treturn s;\n}\n\nstatic __inline unsigned int\nARM_get32(const void *data)\n{\n\tunsigned int s;\n\tmemcpy(&s,data,sizeof(s));\n\treturn s;\n}\n\n#define bswapLE16(X) ARM_get16(&X)\n#define bswapLE32(X) ARM_get32(&X)\n#define bswapBE16(X) bswap_16(ARM_get16(&X))\n#define bswapBE32(X) bswap_32(ARM_get32(&X))\n\n// From libsdl\n#elif defined(WORDS_BIGENDIAN)\n#define bswapLE16(X) bswap_16(X)\n#define bswapLE32(X) bswap_32(X)\n#define bswapBE16(X) (X)\n#define bswapBE32(X) (X)\n#else\n#define bswapLE16(X) (X)\n#define bswapLE32(X) (X)\n#define bswapBE16(X) bswap_16(X)\n#define bswapBE32(X) bswap_32(X)\n#endif\n\n#endif\n"
  },
  {
    "path": "contrib/libmodplug/src/libmodplug/stdafx.h",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Rani Assaf <rani@magic.metawire.com>,\n *          Olivier Lapicque <olivierl@jps.net>,\n *          Adam Goode       <adam@evdebs.org> (endian and char fixes for PPC)\n */\n\n#ifndef _STDAFX_H_\n#define _STDAFX_H_\n\n/* Autoconf detection of stdint/inttypes */\n#if defined(HAVE_CONFIG_H) && !defined(CONFIG_H_INCLUDED)\n# include \"config.h\"\n# define CONFIG_H_INCLUDED 1\n#endif\n#ifdef HAVE_INTTYPES_H\n# include <inttypes.h>\n#endif\n#ifdef HAVE_STDINT_H\n# include <stdint.h>\n#endif\n\n/* disable AGC and FILESAVE for all targets for uniformity. */\n#define NO_AGC\n#define MODPLUG_NO_FILESAVE\n\n#ifdef _WIN32\n\n#ifdef MSC_VER\n#pragma warning (disable:4201)\n#pragma warning (disable:4514)\n#endif\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n#include <windowsx.h>\n#include <mmsystem.h>\n#include <stdio.h>\n#include <malloc.h>\n#include <stdint.h>\n\n#define srandom(_seed)  srand(_seed)\n#define random()        rand()\n#define sleep(_ms)      Sleep(_ms)\n\ninline void ProcessPlugins(int n) {}\n\n#undef strcasecmp\n#undef strncasecmp\n#define strcasecmp(a,b)     _stricmp(a,b)\n#define strncasecmp(a,b,c)  _strnicmp(a,b,c)\n\n#define HAVE_SINF 1\n\n#ifndef isblank\n#define isblank(c) ((c) == ' ' || (c) == '\\t')\n#endif\n\n#else\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#ifdef HAVE_MALLOC_H\n#include <malloc.h>\n#endif\n\ntypedef int8_t CHAR;\ntypedef uint8_t UCHAR;\ntypedef uint8_t* PUCHAR;\ntypedef uint16_t USHORT;\ntypedef uint32_t ULONG;\ntypedef uint32_t UINT;\ntypedef uint32_t DWORD;\ntypedef int32_t LONG;\ntypedef int64_t LONGLONG;\ntypedef int32_t* LPLONG;\ntypedef uint32_t* LPDWORD;\ntypedef uint16_t WORD;\ntypedef uint8_t BYTE;\ntypedef uint8_t* LPBYTE;\ntypedef bool BOOL;\ntypedef char* LPSTR;\ntypedef void* LPVOID;\ntypedef uint16_t* LPWORD;\ntypedef const char* LPCSTR;\ntypedef void* PVOID;\ntypedef void VOID;\n\nstatic inline LONG MulDiv (long a, long b, long c)\n{\n/*if (!c) return 0;*/\n  return ((uint64_t) a * (uint64_t) b ) / c;\n}\n\n#define LPCTSTR LPCSTR\n#define lstrcpyn strncpy\n#define lstrcpy strcpy\n#define lstrcmp strcmp\n#define wsprintf sprintf\n\n#define WAVE_FORMAT_PCM 1\n\n#define  GHND   0\n#define GlobalFreePtr(p) free((void *)(p))\nstatic inline int8_t * GlobalAllocPtr(unsigned int, size_t size)\n{\n  int8_t * p = (int8_t *) malloc(size);\n\n  if (p != NULL) memset(p, 0, size);\n  return p;\n}\n\nstatic inline void ProcessPlugins(int n) {}\n\n#ifndef FALSE\n#define FALSE\tfalse\n#endif\n\n#ifndef TRUE\n#define TRUE\ttrue\n#endif\n\n#endif /* _WIN32 */\n\n#define MODPLUG_EXPORT\n\n#endif\n"
  },
  {
    "path": "contrib/libmodplug/src/load_669.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>,\n *          Adam Goode       <adam@evdebs.org> (endian and char fixes for PPC)\n*/\n\n////////////////////////////////////////////////////////////\n// 669 Composer / UNIS 669 module loader\n////////////////////////////////////////////////////////////\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n//#pragma warning(disable:4244)\n\ntypedef struct tagFILEHEADER669\n{\n\tWORD sig;\t\t\t\t// 'if' or 'JN'\n        signed char songmessage[108];\t// Song Message\n\tBYTE samples;\t\t\t// number of samples (1-64)\n\tBYTE patterns;\t\t\t// number of patterns (1-128)\n\tBYTE restartpos;\n\tBYTE orders[128];\n\tBYTE tempolist[128];\n\tBYTE breaks[128];\n} FILEHEADER669;\n\n\ntypedef struct tagSAMPLE669\n{\n\tBYTE filename[13];\n\tBYTE length[4];\t// when will somebody think about DWORD align ???\n\tBYTE loopstart[4];\n\tBYTE loopend[4];\n} SAMPLE669;\n\nDWORD lengthArrayToDWORD(const BYTE length[4]) {\n\tDWORD len = (length[3] << 24) +\n\t\t(length[2] << 16) +\n\t\t(length[1] << 8) +\n\t\t(length[0]);\n\n\treturn(len);\n}\n\n\nBOOL CSoundFile::Read669(const BYTE *lpStream, DWORD dwMemLength)\n//---------------------------------------------------------------\n{\n\tconst FILEHEADER669 *pfh = (const FILEHEADER669 *)lpStream;\n\tconst SAMPLE669 *psmp = (const SAMPLE669 *)(lpStream + 0x1F1);\n\tDWORD dwMemPos = 0;\n\n\tif ((!lpStream) || (dwMemLength < sizeof(FILEHEADER669))) return FALSE;\n\tif ((bswapLE16(pfh->sig) != 0x6669) && (bswapLE16(pfh->sig) != 0x4E4A)) return FALSE;\n\tif ((!pfh->samples) || (pfh->samples > 64) || (pfh->restartpos >= 128)\n\t || (!pfh->patterns) || (pfh->patterns > 128)) return FALSE;\n\tDWORD dontfuckwithme = 0x1F1 + pfh->samples * sizeof(SAMPLE669) + pfh->patterns * 0x600;\n\tif (dontfuckwithme > dwMemLength) return FALSE;\n\tfor (UINT ichk=0; ichk<pfh->samples; ichk++)\n\t{\n\t\tDWORD len = lengthArrayToDWORD(psmp[ichk].length);\n\t\tdontfuckwithme += len;\n\t}\n\tif (dontfuckwithme > dwMemLength) return FALSE;\n\t// That should be enough checking: this must be a 669 module.\n\tm_nType = MOD_TYPE_669;\n\tm_dwSongFlags |= SONG_LINEARSLIDES;\n\tm_nMinPeriod = 28 << 2;\n\tm_nMaxPeriod = 1712 << 3;\n\tm_nDefaultTempo = 78;\n\tm_nDefaultSpeed = 6;\n\tm_nChannels = 8;\n\tmemcpy(m_szNames[0], pfh->songmessage, 16);\n\tm_nSamples = pfh->samples;\n\tfor (UINT nins=1; nins<=m_nSamples; nins++, psmp++)\n\t{\n\t\tDWORD len = lengthArrayToDWORD(psmp->length);\n\t\tDWORD loopstart = lengthArrayToDWORD(psmp->loopstart);\n\t\tDWORD loopend = lengthArrayToDWORD(psmp->loopend);\n\t\tif (len > MAX_SAMPLE_LENGTH) len = MAX_SAMPLE_LENGTH;\n\t\tif ((loopend > len) && (!loopstart)) loopend = 0;\n\t\tif (loopend > len) loopend = len;\n\t\tif (loopstart + 4 >= loopend) loopstart = loopend = 0;\n\t\tIns[nins].nLength = len;\n\t\tIns[nins].nLoopStart = loopstart;\n\t\tIns[nins].nLoopEnd = loopend;\n\t\tif (loopend) Ins[nins].uFlags |= CHN_LOOP;\n\t\tmemcpy(m_szNames[nins], psmp->filename, 13);\n\t\tIns[nins].nVolume = 256;\n\t\tIns[nins].nGlobalVol = 64;\n\t\tIns[nins].nPan = 128;\n\t}\n\t// Song Message\n\tm_lpszSongComments = new char[109];\n\tmemcpy(m_lpszSongComments, pfh->songmessage, 108);\n\tm_lpszSongComments[108] = 0;\n\t// Reading Orders\n\tmemcpy(Order, pfh->orders, 128);\n\tm_nRestartPos = pfh->restartpos;\n\tif (Order[m_nRestartPos] >= pfh->patterns) m_nRestartPos = 0;\n\t// Reading Pattern Break Locations\n\tfor (UINT npan=0; npan<8; npan++)\n\t{\n\t\tChnSettings[npan].nPan = (npan & 1) ? 0x30 : 0xD0;\n\t\tChnSettings[npan].nVolume = 64;\n\t}\n\t// Reading Patterns\n\tdwMemPos = 0x1F1 + pfh->samples * 25;\n\tfor (UINT npat=0; npat<pfh->patterns; npat++)\n\t{\n\t\tPatterns[npat] = AllocatePattern(64, m_nChannels);\n\t\tif (!Patterns[npat]) break;\n\t\tPatternSize[npat] = 64;\n\t\tMODCOMMAND *m = Patterns[npat];\n\t\tconst BYTE *p = lpStream + dwMemPos;\n\t\tfor (UINT row=0; row<64; row++)\n\t\t{\n\t\t\tMODCOMMAND *mspeed = m;\n\t\t\tif ((row == pfh->breaks[npat]) && (row != 63))\n\t\t\t{\n\t\t\t\tfor (UINT i=0; i<8; i++)\n\t\t\t\t{\n\t\t\t\t\tm[i].command = CMD_PATTERNBREAK;\n\t\t\t\t\tm[i].param = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (UINT n=0; n<8; n++, m++, p+=3)\n\t\t\t{\n\t\t\t\tUINT note = p[0] >> 2;\n\t\t\t\tUINT instr = ((p[0] & 0x03) << 4) | (p[1] >> 4);\n\t\t\t\tUINT vol = p[1] & 0x0F;\n\t\t\t\tif (p[0] < 0xFE)\n\t\t\t\t{\n\t\t\t\t\tm->note = note + 37;\n\t\t\t\t\tm->instr = instr + 1;\n\t\t\t\t}\n\t\t\t\tif (p[0] <= 0xFE)\n\t\t\t\t{\n\t\t\t\t\tm->volcmd = VOLCMD_VOLUME;\n\t\t\t\t\tm->vol = (vol << 2) + 2;\n\t\t\t\t}\n\t\t\t\tif (p[2] != 0xFF)\n\t\t\t\t{\n\t\t\t\t\tUINT command = p[2] >> 4;\n\t\t\t\t\tUINT param = p[2] & 0x0F;\n\t\t\t\t\tswitch(command)\n\t\t\t\t\t{\n\t\t\t\t\tcase 0x00:\tcommand = CMD_PORTAMENTOUP; break;\n\t\t\t\t\tcase 0x01:\tcommand = CMD_PORTAMENTODOWN; break;\n\t\t\t\t\tcase 0x02:\tcommand = CMD_TONEPORTAMENTO; break;\n\t\t\t\t\tcase 0x03:\tcommand = CMD_MODCMDEX; param |= 0x50; break;\n\t\t\t\t\tcase 0x04:\tcommand = CMD_VIBRATO; param |= 0x40; break;\n\t\t\t\t\tcase 0x05:\tif (param) command = CMD_SPEED; else command = 0; break;\n\t\t\t\t\tcase 0x06:\tif (param == 0) { command = CMD_PANNINGSLIDE; param = 0xFE; }\n\t\t\t\t\t\t\t\telse if (param == 1) { command = CMD_PANNINGSLIDE; param = 0xEF; }\n\t\t\t\t\t\t\t\telse command = 0;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\tcommand = 0;\n\t\t\t\t\t}\n\t\t\t\t\tif (command)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (command == CMD_SPEED) mspeed = NULL;\n\t\t\t\t\t\tm->command = command;\n\t\t\t\t\t\tm->param = param;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ((!row) && (mspeed))\n\t\t\t{\n\t\t\t\tfor (UINT i=0; i<8; i++) if (!mspeed[i].command)\n\t\t\t\t{\n\t\t\t\t\tmspeed[i].command = CMD_SPEED;\n\t\t\t\t\tmspeed[i].param = pfh->tempolist[npat];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tdwMemPos += 0x600;\n\t}\n\t// Reading Samples\n\tfor (UINT n=1; n<=m_nSamples; n++)\n\t{\n\t\tUINT len = Ins[n].nLength;\n\t\tif (dwMemPos >= dwMemLength) break;\n\t\tif (len > 4) ReadSample(&Ins[n], RS_PCM8U, (LPSTR)(lpStream+dwMemPos), dwMemLength - dwMemPos);\n\t\tdwMemPos += len;\n\t}\n\treturn TRUE;\n}\n"
  },
  {
    "path": "contrib/libmodplug/src/load_amf.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n*/\n\n///////////////////////////////////////////////////\n//\n// AMF module loader\n//\n// There is 2 types of AMF files:\n// - ASYLUM Music Format\n// - Advanced Music Format(DSM)\n//\n///////////////////////////////////////////////////\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n//#define AMFLOG\n\n//#pragma warning(disable:4244)\n\n#pragma pack(1)\n\ntypedef struct _AMFFILEHEADER\n{\n\tUCHAR szAMF[3];\n\tUCHAR version;\n\tCHAR title[32];\n\tUCHAR numsamples;\n\tUCHAR numorders;\n\tUSHORT numtracks;\n\tUCHAR numchannels;\n} AMFFILEHEADER;\n\ntypedef struct _AMFSAMPLE\n{\n\tUCHAR type;\n\tCHAR  samplename[32];\n\tCHAR  filename[13];\n\tULONG offset;\n\tULONG length;\n\tUSHORT c2spd;\n\tUCHAR volume;\n} AMFSAMPLE;\n\n\n#pragma pack()\n\n\n#ifdef AMFLOG\nextern void Log(LPCSTR, ...);\n#endif\n\nstatic VOID AMF_Unpack(MODCOMMAND *pPat, const BYTE *pTrack, UINT nRows, UINT nChannels)\n//-------------------------------------------------------------------------------\n{\n\tUINT lastinstr = 0;\n\tUINT nTrkSize = bswapLE16(*(USHORT *)pTrack);\n\tnTrkSize += (UINT)pTrack[2] << 16;\n\tpTrack += 3;\n\twhile (nTrkSize--)\n\t{\n\t\tUINT row = pTrack[0];\n\t\tUINT cmd = pTrack[1];\n\t\tUINT arg = pTrack[2];\n\t\tif (row >= nRows) break;\n\t\tMODCOMMAND *m = pPat + row * nChannels;\n\t\tif (cmd < 0x7F) // note+vol\n\t\t{\n\t\t\tm->note = cmd+1;\n\t\t\tif (!m->instr) m->instr = lastinstr;\n\t\t\tm->volcmd = VOLCMD_VOLUME;\n\t\t\tm->vol = arg;\n\t\t} else\n\t\tif (cmd == 0x7F) // duplicate row\n\t\t{\n\t\t\tsigned char rdelta = (signed char)arg;\n\t\t\tint rowsrc = (int)row + (int)rdelta;\n\t\t\tif ((rowsrc >= 0) && (rowsrc < (int)nRows)) memcpy(m, &pPat[rowsrc*nChannels],sizeof(pPat[rowsrc*nChannels]));\n\t\t} else\n\t\tif (cmd == 0x80) // instrument\n\t\t{\n\t\t\tm->instr = arg+1;\n\t\t\tlastinstr = m->instr;\n\t\t} else\n\t\tif (cmd == 0x83) // volume\n\t\t{\n\t\t\tm->volcmd = VOLCMD_VOLUME;\n\t\t\tm->vol = arg;\n\t\t} else\n\t\t// effect\n\t\t{\n\t\t\tUINT command = cmd & 0x7F;\n\t\t\tUINT param = arg;\n\t\t\tswitch(command)\n\t\t\t{\n\t\t\t// 0x01: Set Speed\n\t\t\tcase 0x01:\tcommand = CMD_SPEED; break;\n\t\t\t// 0x02: Volume Slide\n\t\t\t// 0x0A: Tone Porta + Vol Slide\n\t\t\t// 0x0B: Vibrato + Vol Slide\n\t\t\tcase 0x02:\tcommand = CMD_VOLUMESLIDE;\n\t\t\tcase 0x0A:\tif (command == 0x0A) command = CMD_TONEPORTAVOL;\n\t\t\tcase 0x0B:\tif (command == 0x0B) command = CMD_VIBRATOVOL;\n\t\t\t\t\t\tif (param & 0x80) param = (-(signed char)param)&0x0F;\n\t\t\t\t\t\telse param = (param&0x0F)<<4;\n\t\t\t\t\t\tbreak;\n\t\t\t// 0x04: Porta Up/Down\n\t\t\tcase 0x04:\tif (param & 0x80) { command = CMD_PORTAMENTOUP; param = (-(signed char)param)&0x7F; }\n\t\t\t\t\t\telse { command = CMD_PORTAMENTODOWN; } break;\n\t\t\t// 0x06: Tone Portamento\n\t\t\tcase 0x06:\tcommand = CMD_TONEPORTAMENTO; break;\n\t\t\t// 0x07: Tremor\n\t\t\tcase 0x07:\tcommand = CMD_TREMOR; break;\n\t\t\t// 0x08: Arpeggio\n\t\t\tcase 0x08:\tcommand = CMD_ARPEGGIO; break;\n\t\t\t// 0x09: Vibrato\n\t\t\tcase 0x09:\tcommand = CMD_VIBRATO; break;\n\t\t\t// 0x0C: Pattern Break\n\t\t\tcase 0x0C:\tcommand = CMD_PATTERNBREAK; break;\n\t\t\t// 0x0D: Position Jump\n\t\t\tcase 0x0D:\tcommand = CMD_POSITIONJUMP; break;\n\t\t\t// 0x0F: Retrig\n\t\t\tcase 0x0F:\tcommand = CMD_RETRIG; break;\n\t\t\t// 0x10: Offset\n\t\t\tcase 0x10:\tcommand = CMD_OFFSET; break;\n\t\t\t// 0x11: Fine Volume Slide\n\t\t\tcase 0x11:\tif (param) { command = CMD_VOLUMESLIDE;\n\t\t\t\t\t\t\tif (param & 0x80) param = 0xF0|((-(signed char)param)&0x0F);\n\t\t\t\t\t\t\telse param = 0x0F|((param&0x0F)<<4);\n\t\t\t\t\t\t} else command = 0; break;\n\t\t\t// 0x12: Fine Portamento\n\t\t\t// 0x16: Extra Fine Portamento\n\t\t\tcase 0x12:\n\t\t\tcase 0x16:\tif (param) { int mask = (command == 0x16) ? 0xE0 : 0xF0;\n\t\t\t\t\t\t\tcommand = (param & 0x80) ? CMD_PORTAMENTOUP : CMD_PORTAMENTODOWN;\n\t\t\t\t\t\t\tif (param & 0x80) param = mask|((-(signed char)param)&0x0F);\n\t\t\t\t\t\t\telse param |= mask;\n\t\t\t\t\t\t} else command = 0; break;\n\t\t\t// 0x13: Note Delay\n\t\t\tcase 0x13:\tcommand = CMD_S3MCMDEX; param = 0xD0|(param & 0x0F); break;\n\t\t\t// 0x14: Note Cut\n\t\t\tcase 0x14:\tcommand = CMD_S3MCMDEX; param = 0xC0|(param & 0x0F); break;\n\t\t\t// 0x15: Set Tempo\n\t\t\tcase 0x15:\tcommand = CMD_TEMPO; break;\n\t\t\t// 0x17: Panning\n\t\t\tcase 0x17:\tparam = (param+64)&0x7F;\n\t\t\t\t\t\tif (m->command) { if (!m->volcmd) { m->volcmd = VOLCMD_PANNING;  m->vol = param/2; } command = 0; }\n\t\t\t\t\t\telse { command = CMD_PANNING8; }\n\t\t\t\tbreak;\n\t\t\t// Unknown effects\n\t\t\tdefault:\tcommand = param = 0;\n\t\t\t}\n\t\t\tif (command)\n\t\t\t{\n\t\t\t\tm->command = command;\n\t\t\t\tm->param = param;\n\t\t\t}\n\t\t}\n\t\tpTrack += 3;\n\t}\n}\n\n\n\nBOOL CSoundFile::ReadAMF(LPCBYTE lpStream, const DWORD dwMemLength)\n//-----------------------------------------------------------\n{\n\tconst AMFFILEHEADER *pfh = (AMFFILEHEADER *)lpStream;\n\tDWORD dwMemPos;\n\n\tif ((!lpStream) || (dwMemLength < 2048)) return FALSE;\n\tif ((!strncmp((LPCTSTR)lpStream, \"ASYLUM Music Format V1.0\", 25)) && (dwMemLength > 4096))\n\t{\n\t\tUINT numorders, numpats, numsamples;\n\n\t\tdwMemPos = 32;\n\t\tnumpats = lpStream[dwMemPos+3];\n\t\tnumorders = lpStream[dwMemPos+4];\n\t\tnumsamples = 64;\n\t\tdwMemPos += 6;\n\t\tif ((!numpats) || (numpats > MAX_PATTERNS) || (!numorders)\n\t\t || (numpats*64*32 + 294 + 37*64 >= dwMemLength)) return FALSE;\n\t\tm_nType = MOD_TYPE_AMF0;\n\t\tm_nChannels = 8;\n\t\tm_nInstruments = 0;\n\t\tm_nSamples = 31;\n\t\tm_nDefaultTempo = 125;\n\t\tm_nDefaultSpeed = 6;\n\t\tfor (UINT iOrd=0; iOrd<MAX_ORDERS; iOrd++)\n\t\t{\n\t\t\tOrder[iOrd] = (iOrd < numorders) ? lpStream[dwMemPos+iOrd] : 0xFF;\n\t\t}\n\t\tdwMemPos = 294; // ???\n\t\tfor (UINT iSmp=0; iSmp<numsamples; iSmp++)\n\t\t{\n\t\t\tMODINSTRUMENT *psmp = &Ins[iSmp+1];\n\t\t\tmemcpy(m_szNames[iSmp+1], lpStream+dwMemPos, 22);\n\t\t\tm_szNames[iSmp+1][21] = '\\0';\n\t\t\tpsmp->nFineTune = MOD2XMFineTune(lpStream[dwMemPos+22]);\n\t\t\tpsmp->nVolume = lpStream[dwMemPos+23];\n\t\t\tpsmp->nGlobalVol = 64;\n\t\t\tif (psmp->nVolume > 0x40) psmp->nVolume = 0x40;\n\t\t\tpsmp->nVolume <<= 2;\n\t\t\tpsmp->nLength = bswapLE32(*((LPDWORD)(lpStream+dwMemPos+25)));\n\t\t\tpsmp->nLoopStart = bswapLE32(*((LPDWORD)(lpStream+dwMemPos+29)));\n\t\t\tpsmp->nLoopEnd = psmp->nLoopStart + bswapLE32(*((LPDWORD)(lpStream+dwMemPos+33)));\n\t\t\tif ((psmp->nLoopEnd > psmp->nLoopStart) && (psmp->nLoopEnd <= psmp->nLength))\n\t\t\t{\n\t\t\t\tpsmp->uFlags = CHN_LOOP;\n\t\t\t} else\n\t\t\t{\n\t\t\t\tpsmp->nLoopStart = psmp->nLoopEnd = 0;\n\t\t\t}\n\t\t\tif ((psmp->nLength) && (iSmp>31)) m_nSamples = iSmp+1;\n\t\t\tdwMemPos += 37;\n\t\t}\n\t\tfor (UINT iPat=0; iPat<numpats; iPat++)\n\t\t{\n\t\t\tMODCOMMAND *p = AllocatePattern(64, m_nChannels);\n\t\t\tif (!p) break;\n\t\t\tPatterns[iPat] = p;\n\t\t\tPatternSize[iPat] = 64;\n\t\t\tconst UCHAR *pin = lpStream + dwMemPos;\n\t\t\tfor (UINT i=0; i<8*64; i++)\n\t\t\t{\n\t\t\t\tp->note = 0;\n\n\t\t\t\tif (pin[0])\n\t\t\t\t{\n\t\t\t\t\tp->note = pin[0] + 13;\n\t\t\t\t}\n\t\t\t\tp->instr = pin[1];\n\t\t\t\tp->command = pin[2];\n\t\t\t\tp->param = pin[3];\n\t\t\t\tif (p->command > 0x0F)\n\t\t\t\t{\n\t\t\t\t#ifdef AMFLOG\n\t\t\t\t\tLog(\"0x%02X.0x%02X ?\", p->command, p->param);\n\t\t\t\t#endif\n\t\t\t\t\tp->command = 0;\n\t\t\t\t}\n\t\t\t\tConvertModCommand(p);\n\t\t\t\tpin += 4;\n\t\t\t\tp++;\n\t\t\t}\n\t\t\tdwMemPos += 64*32;\n\t\t}\n\t\t// Read samples\n\t\tfor (UINT iData=0; iData<m_nSamples; iData++)\n\t\t{\n\t\t\tMODINSTRUMENT *psmp = &Ins[iData+1];\n\t\t\tif (psmp->nLength)\n\t\t\t{\n\t\t\t\tif (dwMemPos > dwMemLength) return FALSE;\n\t\t\t\tdwMemPos += ReadSample(psmp, RS_PCM8S, (LPCSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos);\n\t\t\t}\n\t\t}\n\t\treturn TRUE;\n\t}\n\t////////////////////////////\n\t// DSM/AMF\n\tUSHORT *ptracks[MAX_PATTERNS];\n\tDWORD sampleseekpos[MAX_SAMPLES];\n\n\tif ((pfh->szAMF[0] != 'A') || (pfh->szAMF[1] != 'M') || (pfh->szAMF[2] != 'F')\n\t || (pfh->version < 10) || (pfh->version > 14) || (!bswapLE16(pfh->numtracks))\n\t || (!pfh->numorders) || (pfh->numorders > MAX_PATTERNS)\n\t || (!pfh->numsamples) || (pfh->numsamples >= MAX_SAMPLES)\n\t || (pfh->numchannels < 4) || (pfh->numchannels > 32))\n\t\treturn FALSE;\n\tmemcpy(m_szNames[0], pfh->title, 32);\n\tm_szNames[0][31] = '\\0';\n\tdwMemPos = sizeof(AMFFILEHEADER);\n\tm_nType = MOD_TYPE_AMF;\n\tm_nChannels = pfh->numchannels;\n\tm_nSamples = pfh->numsamples;\n\tm_nInstruments = 0;\n\t// Setup Channel Pan Positions\n\tif (pfh->version >= 11)\n\t{\n\t\tsigned char *panpos = (signed char *)(lpStream + dwMemPos);\n\t\tUINT nchannels = (pfh->version >= 13) ? 32 : 16;\n\t\tfor (UINT i=0; i<nchannels; i++)\n\t\t{\n\t\t\tint pan = (panpos[i] + 64) * 2;\n\t\t\tif (pan < 0) pan = 0;\n\t\t\tif (pan > 256) { pan = 128; ChnSettings[i].dwFlags |= CHN_SURROUND; }\n\t\t\tChnSettings[i].nPan = pan;\n\t\t}\n\t\tdwMemPos += nchannels;\n\t} else\n\t{\n\t\tfor (UINT i=0; i<16; i++)\n\t\t{\n\t\t\tChnSettings[i].nPan = (lpStream[dwMemPos+i] & 1) ? 0x30 : 0xD0;\n\t\t}\n\t\tdwMemPos += 16;\n\t}\n\t// Get Tempo/Speed\n\tm_nDefaultTempo = 125;\n\tm_nDefaultSpeed = 6;\n\tif (pfh->version >= 13)\n\t{\n\t\tif (lpStream[dwMemPos] >= 32) m_nDefaultTempo = lpStream[dwMemPos];\n\t\tif (lpStream[dwMemPos+1] <= 32) m_nDefaultSpeed = lpStream[dwMemPos+1];\n\t\tdwMemPos += 2;\n\t}\n\t// Setup sequence list\n\tfor (UINT iOrd=0; iOrd<MAX_ORDERS; iOrd++)\n\t{\n\t\tOrder[iOrd] = 0xFF;\n\t\tif (iOrd < pfh->numorders)\n\t\t{\n\t\t\tOrder[iOrd] = iOrd;\n\t\t\tPatternSize[iOrd] = 64;\n\t\t\tif (pfh->version >= 14)\n\t\t\t{\n\t\t\t\tPatternSize[iOrd] = bswapLE16(*(USHORT *)(lpStream+dwMemPos));\n\t\t\t\tdwMemPos += 2;\n\t\t\t}\n\t\t\tptracks[iOrd] = (USHORT *)(lpStream+dwMemPos);\n\t\t\tdwMemPos += m_nChannels * sizeof(USHORT);\n\t\t}\n\t}\n\tif (dwMemPos + m_nSamples * (sizeof(AMFSAMPLE)+8) > dwMemLength) return TRUE;\n\t// Read Samples\n\tUINT maxsampleseekpos = 0;\n\tfor (UINT iIns=0; iIns<m_nSamples; iIns++)\n\t{\n\t\tMODINSTRUMENT *pins = &Ins[iIns+1];\n\t\tconst AMFSAMPLE *psh = (AMFSAMPLE *)(lpStream + dwMemPos);\n\n\t\tdwMemPos += sizeof(AMFSAMPLE);\n\t\tmemcpy(m_szNames[iIns+1], psh->samplename, 32);\n\t\tm_szNames[iIns+1][31] = '\\0';\n\t\tmemcpy(pins->name, psh->filename, 13);\n\t\tpins->name[12] = '\\0';\n\t\tpins->nLength = bswapLE32(psh->length);\n\t\tpins->nC4Speed = bswapLE16(psh->c2spd);\n\t\tpins->nGlobalVol = 64;\n\t\tpins->nVolume = psh->volume * 4;\n\t\tif (pfh->version >= 11)\n\t\t{\n\t\t\tpins->nLoopStart = bswapLE32(*(DWORD *)(lpStream+dwMemPos));\n\t\t\tpins->nLoopEnd = bswapLE32(*(DWORD *)(lpStream+dwMemPos+4));\n\t\t\tdwMemPos += 8;\n\t\t} else\n\t\t{\n\t\t\tpins->nLoopStart = bswapLE16(*(WORD *)(lpStream+dwMemPos));\n\t\t\tpins->nLoopEnd = pins->nLength;\n\t\t\tdwMemPos += 2;\n\t\t}\n\t\tsampleseekpos[iIns] = 0;\n\t\tif ((psh->type) && (bswapLE32(psh->offset) < dwMemLength-1))\n\t\t{\n\t\t\tsampleseekpos[iIns] = bswapLE32(psh->offset);\n\t\t\tif (bswapLE32(psh->offset) > maxsampleseekpos)\n\t\t\t\tmaxsampleseekpos = bswapLE32(psh->offset);\n\t\t\tif ((pins->nLoopEnd > pins->nLoopStart + 2)\n\t\t\t && (pins->nLoopEnd <= pins->nLength)) pins->uFlags |= CHN_LOOP;\n\t\t}\n\t}\n\t// Read Track Mapping Table\n\tUSHORT *pTrackMap = (USHORT *)(lpStream+dwMemPos);\n\tUINT realtrackcnt = 0;\n\tdwMemPos += pfh->numtracks * sizeof(USHORT);\n\tif (dwMemPos >= dwMemLength)\n\t\treturn TRUE;\n\n\tfor (UINT iTrkMap=0; iTrkMap<pfh->numtracks; iTrkMap++)\n\t{\n\t\tif (realtrackcnt < pTrackMap[iTrkMap]) realtrackcnt = pTrackMap[iTrkMap];\n\t}\n\t// Store tracks positions\n\tBYTE **pTrackData = new BYTE *[realtrackcnt];\n\tmemset(pTrackData, 0, sizeof(BYTE *) * realtrackcnt);\n\tfor (UINT iTrack=0; iTrack<realtrackcnt; iTrack++) if (dwMemPos <= dwMemLength - 3)\n\t{\n\t\tUINT nTrkSize = bswapLE16(*(USHORT *)(lpStream+dwMemPos));\n\t\tnTrkSize += (UINT)lpStream[dwMemPos+2] << 16;\n\t\tif (dwMemPos + nTrkSize * 3 + 3 <= dwMemLength)\n\t\t{\n\t\t\tpTrackData[iTrack] = (BYTE *)(lpStream + dwMemPos);\n\t\t}\n\t\tdwMemPos += nTrkSize * 3 + 3;\n\t}\n\t// Create the patterns from the list of tracks\n\tfor (UINT iPat=0; iPat<pfh->numorders; iPat++)\n\t{\n\t\tMODCOMMAND *p = AllocatePattern(PatternSize[iPat], m_nChannels);\n\t\tif (!p) break;\n\t\tPatterns[iPat] = p;\n\t\tfor (UINT iChn=0; iChn<m_nChannels; iChn++)\n\t\t{\n\t\t\tUINT nTrack = bswapLE16(ptracks[iPat][iChn]);\n\t\t\tif ((nTrack) && (nTrack <= pfh->numtracks))\n\t\t\t{\n\t\t\t\tUINT realtrk = bswapLE16(pTrackMap[nTrack-1]);\n\t\t\t\tif (realtrk)\n\t\t\t\t{\n\t\t\t\t\trealtrk--;\n\t\t\t\t\tif ((realtrk < realtrackcnt) && (pTrackData[realtrk]))\n\t\t\t\t\t{\n\t\t\t\t\t\tAMF_Unpack(p+iChn, pTrackData[realtrk], PatternSize[iPat], m_nChannels);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tdelete[] pTrackData;\n\t// Read Sample Data\n\tfor (UINT iSeek=1; iSeek<=maxsampleseekpos; iSeek++)\n\t{\n\t\tif (dwMemPos >= dwMemLength) break;\n\t\tfor (UINT iSmp=0; iSmp<m_nSamples; iSmp++) if (iSeek == sampleseekpos[iSmp])\n\t\t{\n\t\t\tMODINSTRUMENT *pins = &Ins[iSmp+1];\n\t\t\tdwMemPos += ReadSample(pins, RS_PCM8U, (LPCSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos);\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn TRUE;\n}\n"
  },
  {
    "path": "contrib/libmodplug/src/load_dsm.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n*/\n\n//////////////////////////////////////////////\n// DSIK Internal Format (DSM) module loader //\n//////////////////////////////////////////////\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n#pragma pack(1)\n\n#define DSMID_RIFF\t0x46464952\t// \"RIFF\"\n#define DSMID_DSMF\t0x464d5344\t// \"DSMF\"\n#define DSMID_SONG\t0x474e4f53\t// \"SONG\"\n#define DSMID_INST\t0x54534e49\t// \"INST\"\n#define DSMID_PATT\t0x54544150\t// \"PATT\"\n\n\ntypedef struct DSMNOTE\n{\n\tBYTE note,ins,vol,cmd,inf;\n} DSMNOTE;\n\n\ntypedef struct DSMINST\n{\n\tDWORD id_INST;\n\tDWORD inst_len;\n\tCHAR filename[13];\n\tBYTE flags;\n\tBYTE flags2;\n\tBYTE volume;\n\tDWORD length;\n\tDWORD loopstart;\n\tDWORD loopend;\n\tDWORD reserved1;\n\tWORD c2spd;\n\tWORD reserved2;\n\tCHAR samplename[28];\n} DSMINST;\n\n\ntypedef struct DSMFILEHEADER\n{\n\tDWORD id_RIFF;\t// \"RIFF\"\n\tDWORD riff_len;\n\tDWORD id_DSMF;\t// \"DSMF\"\n\tDWORD id_SONG;\t// \"SONG\"\n\tDWORD song_len;\n} DSMFILEHEADER;\n\n\ntypedef struct DSMSONG\n{\n\tCHAR songname[28];\n\tWORD reserved1;\n\tWORD flags;\n\tDWORD reserved2;\n\tWORD numord;\n\tWORD numsmp;\n\tWORD numpat;\n\tWORD numtrk;\n\tBYTE globalvol;\n\tBYTE mastervol;\n\tBYTE speed;\n\tBYTE bpm;\n\tBYTE panpos[16];\n\tBYTE orders[128];\n} DSMSONG;\n\ntypedef struct DSMPATT\n{\n\tDWORD id_PATT;\n\tDWORD patt_len;\n\tBYTE dummy1;\n\tBYTE dummy2;\n} DSMPATT;\n\n#pragma pack()\n\n\nBOOL CSoundFile::ReadDSM(LPCBYTE lpStream, DWORD dwMemLength)\n//-----------------------------------------------------------\n{\n\tDSMFILEHEADER *pfh = (DSMFILEHEADER *)lpStream;\n\tDSMSONG *psong;\n\tDWORD dwMemPos;\n\tUINT nPat, nSmp;\n\n\tif ((!lpStream) || (dwMemLength < 1024) || (pfh->id_RIFF != DSMID_RIFF)\n\t || (pfh->riff_len + 8 > dwMemLength) || (pfh->riff_len < 1024)\n\t || (pfh->id_DSMF != DSMID_DSMF) || (pfh->id_SONG != DSMID_SONG)\n\t || (pfh->song_len > dwMemLength)) return FALSE;\n\tpsong = (DSMSONG *)(lpStream + sizeof(DSMFILEHEADER));\n\tdwMemPos = sizeof(DSMFILEHEADER) + pfh->song_len;\n\tm_nType = MOD_TYPE_DSM;\n\tm_nChannels = psong->numtrk;\n\tif (m_nChannels < 4) m_nChannels = 4;\n\tif (m_nChannels > 16) m_nChannels = 16;\n\tm_nSamples = psong->numsmp;\n\tif (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES - 1;\n\tm_nDefaultSpeed = psong->speed;\n\tm_nDefaultTempo = psong->bpm;\n\tm_nDefaultGlobalVolume = psong->globalvol << 2;\n\tif ((!m_nDefaultGlobalVolume) || (m_nDefaultGlobalVolume > 256)) m_nDefaultGlobalVolume = 256;\n\tm_nSongPreAmp = psong->mastervol & 0x7F;\n\tfor (UINT iOrd=0; iOrd<sizeof(psong->orders); iOrd++)\n\t{\n\t\tOrder[iOrd] = (BYTE)((iOrd < psong->numord) ? psong->orders[iOrd] : 0xFF);\n\t}\n\tfor (UINT iPan=0; iPan<16; iPan++)\n\t{\n\t\tChnSettings[iPan].nPan = 0x80;\n\t\tif (psong->panpos[iPan] <= 0x80)\n\t\t{\n\t\t\tChnSettings[iPan].nPan = psong->panpos[iPan] << 1;\n\t\t}\n\t}\n\tmemcpy(m_szNames[0], psong->songname, 28);\n\tnPat = 0;\n\tnSmp = 1;\n\twhile (dwMemPos < dwMemLength - 8)\n\t{\n\t\tDSMPATT *ppatt = (DSMPATT *)(lpStream + dwMemPos);\n\t\tDSMINST *pins = (DSMINST *)(lpStream+dwMemPos);\n\t\t// Reading Patterns\n\t\tif (ppatt->id_PATT == DSMID_PATT)\n\t\t{\n\t\t\tdwMemPos += 8;\n\t\t\tif (dwMemPos + ppatt->patt_len >= dwMemLength) break;\n\t\t\tDWORD dwPos = dwMemPos;\n\t\t\tdwMemPos += ppatt->patt_len;\n\t\t\tMODCOMMAND *m = AllocatePattern(64, m_nChannels);\n\t\t\tif (!m) break;\n\t\t\tPatternSize[nPat] = 64;\n\t\t\tPatterns[nPat] = m;\n\t\t\tUINT row = 0;\n\t\t\twhile ((row < 64) && (dwPos + 2 <= dwMemPos))\n\t\t\t{\n\t\t\t\tUINT flag = lpStream[dwPos++];\n\t\t\t\tif (flag)\n\t\t\t\t{\n\t\t\t\t\tUINT ch = (flag & 0x0F) % m_nChannels;\n\t\t\t\t\tif (flag & 0x80)\n\t\t\t\t\t{\n\t\t\t\t\t\tUINT note = lpStream[dwPos++];\n\t\t\t\t\t\tif (note)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (note <= 12*9) note += 12;\n\t\t\t\t\t\t\tm[ch].note = (BYTE)note;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (flag & 0x40)\n\t\t\t\t\t{\n\t\t\t\t\t\tm[ch].instr = lpStream[dwPos++];\n\t\t\t\t\t}\n\t\t\t\t\tif (flag & 0x20)\n\t\t\t\t\t{\n\t\t\t\t\t\tm[ch].volcmd = VOLCMD_VOLUME;\n\t\t\t\t\t\tm[ch].vol = lpStream[dwPos++];\n\t\t\t\t\t}\n\t\t\t\t\tif (flag & 0x10)\n\t\t\t\t\t{\n\t\t\t\t\t\tUINT command = lpStream[dwPos++];\n\t\t\t\t\t\tUINT param = lpStream[dwPos++];\n\t\t\t\t\t\tswitch(command)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t// 4-bit Panning\n\t\t\t\t\t\tcase 0x08:\n\t\t\t\t\t\t\tswitch(param & 0xF0)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcase 0x00: param <<= 4; break;\n\t\t\t\t\t\t\tcase 0x10: command = 0x0A; param = (param & 0x0F) << 4; break;\n\t\t\t\t\t\t\tcase 0x20: command = 0x0E; param = (param & 0x0F) | 0xA0; break;\n\t\t\t\t\t\t\tcase 0x30: command = 0x0E; param = (param & 0x0F) | 0x10; break;\n\t\t\t\t\t\t\tcase 0x40: command = 0x0E; param = (param & 0x0F) | 0x20; break;\n\t\t\t\t\t\t\tdefault: command = 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t// Portamentos\n\t\t\t\t\t\tcase 0x11:\n\t\t\t\t\t\tcase 0x12:\n\t\t\t\t\t\t\tcommand &= 0x0F;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t// 3D Sound (?)\n\t\t\t\t\t\tcase 0x13:\n\t\t\t\t\t\t\tcommand = 'X' - 55;\n\t\t\t\t\t\t\tparam = 0x91;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t// Volume + Offset (?)\n\t\t\t\t\t\t\tcommand = ((command & 0xF0) == 0x20) ? 0x09 : 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tm[ch].command = (BYTE)command;\n\t\t\t\t\t\tm[ch].param = (BYTE)param;\n\t\t\t\t\t\tif (command) ConvertModCommand(&m[ch]);\n\t\t\t\t\t}\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tm += m_nChannels;\n\t\t\t\t\trow++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tnPat++;\n\t\t} else\n\t\t// Reading Samples\n\t\tif ((nSmp <= m_nSamples) && (pins->id_INST == DSMID_INST))\n\t\t{\n\t\t\tif (dwMemPos + pins->inst_len >= dwMemLength - 8) break;\n\t\t\tDWORD dwPos = dwMemPos + sizeof(DSMINST);\n\t\t\tdwMemPos += 8 + pins->inst_len;\n\t\t\tmemcpy(m_szNames[nSmp], pins->samplename, 28);\n\t\t\tMODINSTRUMENT *psmp = &Ins[nSmp];\n\t\t\tmemcpy(psmp->name, pins->filename, 13);\n\t\t\tpsmp->nGlobalVol = 64;\n\t\t\tpsmp->nC4Speed = pins->c2spd;\n\t\t\tpsmp->uFlags = (WORD)((pins->flags & 1) ? CHN_LOOP : 0);\n\t\t\tpsmp->nLength = pins->length;\n\t\t\tpsmp->nLoopStart = pins->loopstart;\n\t\t\tpsmp->nLoopEnd = pins->loopend;\n\t\t\tpsmp->nVolume = (WORD)(pins->volume << 2);\n\t\t\tif (psmp->nVolume > 256) psmp->nVolume = 256;\n\t\t\tUINT smptype = (pins->flags & 2) ? RS_PCM8S : RS_PCM8U;\n\t\t\tReadSample(psmp, smptype, (LPCSTR)(lpStream+dwPos), dwMemLength - dwPos);\n\t\t\tnSmp++;\n\t\t} else\n\t\t{\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn TRUE;\n}\n\n"
  },
  {
    "path": "contrib/libmodplug/src/load_far.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n*/\n\n////////////////////////////////////////\n// Farandole (FAR) module loader\t  //\n////////////////////////////////////////\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n//#pragma warning(disable:4244)\n\n#define FARFILEMAGIC\t0xFE524146\t// \"FAR\"\n\n#pragma pack(1)\n\ntypedef struct FARHEADER1\n{\n\tDWORD id;\t\t\t\t// file magic FAR=\n\tCHAR songname[40];\t\t// songname\n\tCHAR magic2[3];\t\t\t// 13,10,26\n\tWORD headerlen;\t\t\t// remaining length of header in bytes\n\tBYTE version;\t\t\t// 0xD1\n\tBYTE onoff[16];\n\tBYTE edit1[9];\n\tBYTE speed;\n\tBYTE panning[16];\n\tBYTE edit2[4];\n\tWORD stlen;\n} FARHEADER1;\n\ntypedef struct FARHEADER2\n{\n\tBYTE orders[256];\n\tBYTE numpat;\n\tBYTE snglen;\n\tBYTE loopto;\n\tWORD patsiz[256];\n} FARHEADER2;\n\ntypedef struct FARSAMPLE\n{\n\tCHAR samplename[32];\n\tDWORD length;\n\tBYTE finetune;\n\tBYTE volume;\n\tDWORD reppos;\n\tDWORD repend;\n\tBYTE type;\n\tBYTE loop;\n} FARSAMPLE;\n\n#pragma pack()\n\n\nBOOL CSoundFile::ReadFAR(const BYTE *lpStream, DWORD dwMemLength)\n//---------------------------------------------------------------\n{\n\tconst FARHEADER1 *pmh1 = (const FARHEADER1 *)lpStream;\n\tconst FARHEADER2 *pmh2;\n\tDWORD dwMemPos = sizeof(FARHEADER1);\n\tUINT headerlen, stlen;\n\tBYTE samplemap[8];\n\n\tif ((!lpStream) || (dwMemLength < 1024) || (bswapLE32(pmh1->id) != FARFILEMAGIC)\n\t || (pmh1->magic2[0] != 13) || (pmh1->magic2[1] != 10) || (pmh1->magic2[2] != 26)) return FALSE;\n\theaderlen = bswapLE16(pmh1->headerlen);\n\tstlen = bswapLE16( pmh1->stlen );\n\tif ((headerlen >= dwMemLength) || (dwMemPos + stlen + sizeof(FARHEADER2) >= dwMemLength)) return FALSE;\n\t// Globals\n\tm_nType = MOD_TYPE_FAR;\n\tm_nChannels = 16;\n\tm_nInstruments = 0;\n\tm_nSamples = 0;\n\tm_nSongPreAmp = 0x20;\n\tm_nDefaultSpeed = pmh1->speed;\n\tm_nDefaultTempo = 80;\n\tm_nDefaultGlobalVolume = 256;\n\n\tmemcpy(m_szNames[0], pmh1->songname, 32);\n\t// Channel Setting\n\tfor (UINT nchpan=0; nchpan<16; nchpan++)\n\t{\n\t\tChnSettings[nchpan].dwFlags = 0;\n\t\tChnSettings[nchpan].nPan = ((pmh1->panning[nchpan] & 0x0F) << 4) + 8;\n\t\tChnSettings[nchpan].nVolume = 64;\n\t}\n\t// Reading comment\n\tif (stlen)\n\t{\n\t\tUINT szLen = stlen;\n\t\tif (szLen > dwMemLength - dwMemPos) szLen = dwMemLength - dwMemPos;\n\t\tif ((m_lpszSongComments = new char[szLen + 1]) != NULL)\n\t\t{\n\t\t\tmemcpy(m_lpszSongComments, lpStream+dwMemPos, szLen);\n\t\t\tm_lpszSongComments[szLen] = 0;\n\t\t}\n\t\tdwMemPos += stlen;\n\t}\n\t// Reading orders\n\tif (sizeof(FARHEADER2) > dwMemLength - dwMemPos) return TRUE;\n\tpmh2 = (const FARHEADER2 *)(lpStream + dwMemPos);\n\tdwMemPos += sizeof(FARHEADER2);\n\tif (dwMemPos >= dwMemLength) return TRUE;\n\tfor (UINT iorder=0; iorder<MAX_ORDERS; iorder++)\n\t{\n\t\tOrder[iorder] = (iorder <= pmh2->snglen) ? pmh2->orders[iorder] : 0xFF;\n\t}\n\tm_nRestartPos = pmh2->loopto;\n\t// Reading Patterns\t\n\tdwMemPos += headerlen - (869 + stlen);\n\tif (dwMemPos >= dwMemLength) return TRUE;\n\n\t// end byteswap of pattern data\n\n\tWORD *patsiz = (WORD *)pmh2->patsiz;\n\tfor (UINT ipat=0; ipat<256; ipat++) if (patsiz[ipat])\n\t{\n\t\tUINT patlen = bswapLE16(patsiz[ipat]);\n\t\tif ((ipat >= MAX_PATTERNS) || (patlen < 2))\n\t\t{\n\t\t\tdwMemPos += patlen;\n\t\t\tcontinue;\n\t\t}\n\t\tif (dwMemPos + patlen >= dwMemLength) return TRUE;\n\t\tUINT rows = (patlen - 2) >> 6;\n\t\tif (!rows)\n\t\t{\n\t\t\tdwMemPos += patlen;\n\t\t\tcontinue;\n\t\t}\n\t\tif (rows > 256) rows = 256;\n\t\tif (rows < 16) rows = 16;\n\t\tPatternSize[ipat] = rows;\n\t\tif ((Patterns[ipat] = AllocatePattern(rows, m_nChannels)) == NULL) return TRUE;\n\t\tMODCOMMAND *m = Patterns[ipat];\n\t\tUINT patbrk = lpStream[dwMemPos];\n\t\tconst BYTE *p = lpStream + dwMemPos + 2;\n\t\tUINT max = rows*16*4;\n\t\tif (max > patlen-2) max = patlen-2;\n\t\tfor (UINT len=0; len<max; len += 4, m++)\n\t\t{\n\t\t\tBYTE note = p[len];\n\t\t\tBYTE ins = p[len+1];\n\t\t\tBYTE vol = p[len+2];\n\t\t\tBYTE eff = p[len+3];\n\t\t\tif (note)\n\t\t\t{\n\t\t\t\tm->instr = ins + 1;\n\t\t\t\tm->note = note + 36;\n\t\t\t}\n\t\t\tif (vol >= 0x01 && vol <= 0x10)\n\t\t\t{\n\t\t\t\tm->volcmd = VOLCMD_VOLUME;\n\t\t\t\tm->vol = (vol - 1) << 2;\n\t\t\t}\n\t\t\tswitch(eff & 0xF0)\n\t\t\t{\n\t\t\t// 1.x: Portamento Up\n\t\t\tcase 0x10:\n\t\t\t\tm->command = CMD_PORTAMENTOUP;\n\t\t\t\tm->param = eff & 0x0F;\n\t\t\t\tbreak;\n\t\t\t// 2.x: Portamento Down\n\t\t\tcase 0x20:\n\t\t\t\tm->command = CMD_PORTAMENTODOWN;\n\t\t\t\tm->param = eff & 0x0F;\n\t\t\t\tbreak;\n\t\t\t// 3.x: Tone-Portamento\n\t\t\tcase 0x30:\n\t\t\t\tm->command = CMD_TONEPORTAMENTO;\n\t\t\t\tm->param = (eff & 0x0F) << 2;\n\t\t\t\tbreak;\n\t\t\t// 4.x: Retrigger\n\t\t\tcase 0x40:\n\t\t\t\tm->command = CMD_RETRIG;\n\t\t\t\tm->param = 6 / (1+(eff&0x0F)) + 1;\n\t\t\t\tbreak;\n\t\t\t// 5.x: Set Vibrato Depth\n\t\t\tcase 0x50:\n\t\t\t\tm->command = CMD_VIBRATO;\n\t\t\t\tm->param = (eff & 0x0F);\n\t\t\t\tbreak;\n\t\t\t// 6.x: Set Vibrato Speed\n\t\t\tcase 0x60:\n\t\t\t\tm->command = CMD_VIBRATO;\n\t\t\t\tm->param = (eff & 0x0F) << 4;\n\t\t\t\tbreak;\n\t\t\t// 7.x: Vol Slide Up\n\t\t\tcase 0x70:\n\t\t\t\tm->command = CMD_VOLUMESLIDE;\n\t\t\t\tm->param = (eff & 0x0F) << 4;\n\t\t\t\tbreak;\n\t\t\t// 8.x: Vol Slide Down\n\t\t\tcase 0x80:\n\t\t\t\tm->command = CMD_VOLUMESLIDE;\n\t\t\t\tm->param = (eff & 0x0F);\n\t\t\t\tbreak;\n\t\t\t// A.x: Port to vol\n\t\t\tcase 0xA0:\n\t\t\t\tm->volcmd = VOLCMD_VOLUME;\n\t\t\t\tm->vol = ((eff & 0x0F) << 2) + 4;\n\t\t\t\tbreak;\n\t\t\t// B.x: Set Balance\n\t\t\tcase 0xB0:\n\t\t\t\tm->command = CMD_PANNING8;\n\t\t\t\tm->param = (eff & 0x0F) << 4;\n\t\t\t\tbreak;\n\t\t\t// F.x: Set Speed\n\t\t\tcase 0xF0:\n\t\t\t\tm->command = CMD_SPEED;\n\t\t\t\tm->param = eff & 0x0F;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tif ((patbrk) &&\t(patbrk+1 == (len >> 6)) && (patbrk+1 != rows-1))\n\t\t\t\t{\n\t\t\t\t\tm->command = CMD_PATTERNBREAK;\n\t\t\t\t\tpatbrk = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tdwMemPos += patlen;\n\t}\n\t// Reading samples\n\tif (dwMemPos + 8 >= dwMemLength) return TRUE;\n\tmemcpy(samplemap, lpStream+dwMemPos, 8);\n\tdwMemPos += 8;\n\tMODINSTRUMENT *pins = &Ins[1];\n\tfor (UINT ismp=0; ismp<64; ismp++, pins++) if (samplemap[ismp >> 3] & (1 << (ismp & 7)))\n\t{\n\t\tif (dwMemPos + sizeof(FARSAMPLE) > dwMemLength) return TRUE;\n\t\tconst FARSAMPLE *pfs = reinterpret_cast<const FARSAMPLE*>(lpStream + dwMemPos);\n\t\tdwMemPos += sizeof(FARSAMPLE);\n\t\tm_nSamples = ismp + 1;\n\t\tmemcpy(m_szNames[ismp+1], pfs->samplename, 32);\n\t\tconst DWORD length = bswapLE32( pfs->length ) ; /* endian fix - Toad */\n\t\tpins->nLength = length ;\n\t\tpins->nLoopStart = bswapLE32(pfs->reppos) ;\n\t\tpins->nLoopEnd = bswapLE32(pfs->repend) ;\n\t\tpins->nFineTune = 0;\n\t\tpins->nC4Speed = 8363*2;\n\t\tpins->nGlobalVol = 64;\n\t\tpins->nVolume = pfs->volume << 4;\n\t\tpins->uFlags = 0;\n\t\tif ((pins->nLength > 3) && (dwMemPos + 4 < dwMemLength))\n\t\t{\n\t\t\tif (pfs->type & 1)\n\t\t\t{\n\t\t\t\tpins->uFlags |= CHN_16BIT;\n\t\t\t\tpins->nLength >>= 1;\n\t\t\t\tpins->nLoopStart >>= 1;\n\t\t\t\tpins->nLoopEnd >>= 1;\n\t\t\t}\n\t\t\tif ((pfs->loop & 8) && (pins->nLoopEnd > 4)) pins->uFlags |= CHN_LOOP;\n\t\t\tReadSample(pins, (pins->uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S,\n\t\t\t\t\t\t(LPSTR)(lpStream+dwMemPos), dwMemLength - dwMemPos);\n\t\t}\n\t\tdwMemPos += length;\n\t}\n\treturn TRUE;\n}\n\n"
  },
  {
    "path": "contrib/libmodplug/src/load_gdm.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Alice Rowan <petrifiedrowan@gmail.com>\n*/\n\n////////////////////////////////////////////////////////////\n// General Digital Music module loader\n////////////////////////////////////////////////////////////\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\nstatic const DWORD GDM_SIG = 0xfe4d4447; // GDM\\xFE\nstatic const DWORD GMFS_SIG = 0x53464d47; // GMFS\n\ntypedef struct tagFILEHEADERGDM\n{\n\tBYTE sig[4];\t\t// GDM\\xFE\n\tchar name[32];\n\tchar author[32];\n\tBYTE eof[3];\t\t// \\x0D\\x0A\\x1A\n\tBYTE sig2[4];\t\t// GMFS\n\tBYTE gdm_ver_major;\n\tBYTE gdm_ver_minor;\n\tBYTE tracker_id[2];\n\tBYTE tracker_ver_major;\n\tBYTE tracker_ver_minor;\n\tBYTE panning[32];\n\tBYTE globalvol;\n\tBYTE default_speed;\n\tBYTE default_bpm;\n\tBYTE origfmt[2];\n\tBYTE ordersPos[4];\n\tBYTE nOrders;\n\tBYTE patternsPos[4];\n\tBYTE nPatterns;\n\tBYTE samplesPos[4];\n\tBYTE sampleDataPos[4];\n\tBYTE nSamples;\n\tBYTE messagePos[4];\n\tBYTE messageLength[4];\n\tBYTE ignore[12];\n} FILEHEADERGDM;\n\ntypedef struct tagSAMPLEGDM\n{\n\tenum SAMPLEGDMFLAGS\n\t{\n\t\tS_LOOP = (1<<0),\n\t\tS_16BIT = (1<<1),\n\t\tS_VOLUME = (1<<2),\n\t\tS_PAN = (1<<3),\n\t\tS_LZW = (1<<4),\n\t\tS_STEREO = (1<<5)\n\t};\n\n\tchar name[32];\n\tchar dosname[12];\n\tBYTE ignore;\n\tBYTE length[4];\n\tBYTE loopstart[4];\n\tBYTE loopend[4];\n\tBYTE flags;\n\tBYTE c4rate[2];\n\tBYTE volume;\n\tBYTE panning;\n} SAMPLEGDM;\n\nstatic WORD fixu16(const BYTE *val)\n{\n\treturn (val[1] << 8) | val[0];\n}\n\nstatic DWORD fixu32(const BYTE val[4])\n{\n\treturn (val[3] << 24) | (val[2] << 16) | (val[1] << 8) | val[0];\n}\n\nstatic void GDM_TranslateEffect(const FILEHEADERGDM *pfh, MODCOMMAND &ev, UINT channel, UINT effect, UINT param)\n//--------------------------------------------------------------------------------------------------------------\n{\n\t// Note due to the limitations of libmodplug's volume commands,\n\t// anything that relied on multiple simultaneous non-volume effects\n\t// is not going to work. Only UltraTracker GDMs should really cause\n\t// this, and it's likely that none exist in the wild.\n\tstatic const BYTE translate_effects[32] =\n\t{\n\t\tCMD_NONE,\n\t\tCMD_PORTAMENTOUP,\n\t\tCMD_PORTAMENTODOWN,\n\t\tCMD_TONEPORTAMENTO,\n\t\tCMD_VIBRATO,\n\t\tCMD_TONEPORTAVOL,\n\t\tCMD_VIBRATOVOL,\n\t\tCMD_TREMOLO,\n\t\tCMD_TREMOR,\n\t\tCMD_OFFSET,\n\t\tCMD_VOLUMESLIDE,\n\t\tCMD_POSITIONJUMP,\n\t\tCMD_VOLUME,\n\t\tCMD_PATTERNBREAK,\n\t\tCMD_MODCMDEX,\n\t\tCMD_SPEED,\n\n\t\tCMD_ARPEGGIO,\n\t\tCMD_NONE, // Set Internal Flag\n\t\tCMD_RETRIG,\n\t\tCMD_GLOBALVOLUME,\n\t\tCMD_FINEVIBRATO,\n\t\tCMD_NONE,\n\t\tCMD_NONE,\n\t\tCMD_NONE,\n\t\tCMD_NONE,\n\t\tCMD_NONE,\n\t\tCMD_NONE,\n\t\tCMD_NONE,\n\t\tCMD_NONE,\n\t\tCMD_NONE,\n\t\tCMD_NONE, // Special-- default to nothing.\n\t\tCMD_TEMPO\n\t};\n\n\tif (effect >= 32) return;\n\n\tswitch (effect)\n\t{\n\tcase 0x0c: // Volume\n\t\t// Prefer volume command over regular command.\n\t\tif (!ev.volcmd)\n\t\t{\n\t\t\tev.volcmd = VOLCMD_VOLUME;\n\t\t\tev.vol = (param > 64) ? 64 : param;\n\t\t} else\n\t\t{\n\t\t\tev.command = CMD_VOLUME;\n\t\t\tev.param = (param > 64) ? 64 : param;\n\t\t}\n\t\tbreak;\n\n\tcase 0x0e: // Extended\n\t\t// Most of these are effectively MOD extended commands, but\n\t\t// the fine volslide and portamento commands need to be\n\t\t// converted back to their S3M equivalents.\n\t\tswitch ((param & 0xf0) >> 4)\n\t\t{\n\t\tcase 0x01: // Fine porta up.\n\t\t\tev.command = CMD_PORTAMENTOUP;\n\t\t\tev.param = 0xf0 | (param & 0x0f);\n\t\t\tbreak;\n\n\t\tcase 0x02: // Fine porta down.\n\t\t\tev.command = CMD_PORTAMENTODOWN;\n\t\t\tev.param = 0xf0 | (param & 0x0f);\n\t\t\tbreak;\n\n\t\tcase 0x08: // Extra fine porta up.\n\t\t\tev.command = CMD_PORTAMENTOUP;\n\t\t\tev.param = 0xe0 | (param & 0x0f);\n\t\t\tbreak;\n\n\t\tcase 0x09: // Extra fine porta down.\n\t\t\tev.command = CMD_PORTAMENTODOWN;\n\t\t\tev.param = 0xe0 | (param & 0x0f);\n\t\t\tbreak;\n\n\t\tcase 0x0a: // Fine volslide up.\n\t\t\tif (param & 0x0f)\n\t\t\t{\n\t\t\t\tev.command = CMD_VOLUMESLIDE;\n\t\t\t\tev.param = 0x0f | ((param & 0x0f) << 4);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 0x0b: // Fine volslide down.\n\t\t\tif (param & 0x0f)\n\t\t\t{\n\t\t\t\tev.command = CMD_VOLUMESLIDE;\n\t\t\t\tev.param = 0xf0 | (param & 0x0f);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tev.command = CMD_MODCMDEX;\n\t\t\tev.param = param;\n\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\n\tcase 0x1e: // Special\n\t\tswitch ((param & 0xf0) >> 4)\n\t\t{\n\t\tcase 0x00: // Sample control.\n\t\t\t// Only surround on is emitted by 2GDM.\n\t\t\t// This comes from XA4, so convert it back.\n\t\t\tif ((param & 0x0f) == 1)\n\t\t\t{\n\t\t\t\tev.command = CMD_PANNING8;\n\t\t\t\tev.param = 0xA4;\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 0x08: // Set Pan Position\n\t\t\t// Prefer volume command over regular command if this\n\t\t\t// this comes from effect channel 0.\n\t\t\tif (!ev.volcmd && channel == 0)\n\t\t\t{\n\t\t\t\tev.volcmd = VOLCMD_PANNING;\n\t\t\t\tev.vol = ((param & 0x0f) << 2) + 2;\n\t\t\t} else\n\t\t\t{\n\t\t\t\tev.command = CMD_PANNING8;\n\t\t\t\tev.param = ((param & 0x0f) << 3) + 4;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\n\tdefault:\n\t\tev.command = translate_effects[effect];\n\t\tev.param = param;\n\t\tbreak;\n\t}\n}\n\nBOOL CSoundFile::ReadGDM(const BYTE *lpStream, DWORD dwMemLength)\n//---------------------------------------------------------------\n{\n\tconst FILEHEADERGDM *pfh = (const FILEHEADERGDM *)lpStream;\n\tconst SAMPLEGDM *psmp;\n\tBYTE sflags[256];\n\tDWORD pos;\n\tUINT npat;\n\n\tif ((!lpStream) || (dwMemLength < sizeof(FILEHEADERGDM))) return FALSE;\n\tif ((fixu32(pfh->sig) != GDM_SIG) || (fixu32(pfh->sig2) != GMFS_SIG)) return FALSE;\n\n\tDWORD ordersPos = fixu32(pfh->ordersPos);\n\tDWORD patternsPos = fixu32(pfh->patternsPos);\n\tDWORD samplesPos = fixu32(pfh->samplesPos);\n\tDWORD sampleDataPos = fixu32(pfh->sampleDataPos);\n\tDWORD messagePos = fixu32(pfh->messagePos);\n\tDWORD messageLen = fixu32(pfh->messageLength);\n\tUINT nPatterns = pfh->nPatterns + 1;\n\tUINT nOrders = pfh->nOrders + 1;\n\tUINT nSamples = pfh->nSamples + 1;\n\n\tif (nPatterns > MAX_PATTERNS) nPatterns = MAX_PATTERNS;\n\tif (nOrders > MAX_ORDERS) nOrders = MAX_ORDERS;\n\tif (nSamples >= MAX_SAMPLES) nSamples = MAX_SAMPLES - 1;\n\n\tif ((ordersPos < sizeof(FILEHEADERGDM)) || (ordersPos >= dwMemLength) || (ordersPos + nOrders > dwMemLength) ||\n\t    (patternsPos < sizeof(FILEHEADERGDM)) || (patternsPos >= dwMemLength) ||\n\t    (samplesPos < sizeof(FILEHEADERGDM)) || (samplesPos >= dwMemLength) ||\n\t    (samplesPos + sizeof(SAMPLEGDM) * nSamples > dwMemLength) ||\n\t    (sampleDataPos < sizeof(FILEHEADERGDM)) || (sampleDataPos >= dwMemLength))\n\t\treturn TRUE;\n\n\t// Most GDMs were converted from S3M and BWSB generally behaves like an S3M\n\t// player, so assuming S3M behavior and quirks is a fairly safe bet.\n\tm_nType = MOD_TYPE_GDM | MOD_TYPE_S3M;\n\tm_nMinPeriod = 64;\n\tm_nMaxPeriod = 32767;\n\tm_nDefaultGlobalVolume = pfh->globalvol << 2;\n\tm_nDefaultTempo = pfh->default_bpm;\n\tm_nDefaultSpeed = pfh->default_speed;\n\tm_nChannels = 0;\n\tm_nSamples = nSamples;\n\n\t// Get initial panning.\n\tfor (UINT i = 0; i < 32; i++)\n\t{\n\t\tif (pfh->panning[i] < 16)\n\t\t{\n\t\t\tChnSettings[i].nPan = (pfh->panning[i] << 4) + 8;\n\t\t}\n\t\t// TODO 16=surround\n\t}\n\n\t// Title and message.\n\tmemcpy(m_szNames[0], pfh->name, 32);\n\tm_szNames[0][31] = '\\0';\n\n\tif ((messagePos < dwMemLength) && (messageLen <= dwMemLength - messagePos))\n\t{\n\t\tm_lpszSongComments = new char[messageLen + 1];\n\t\tmemcpy(m_lpszSongComments, lpStream + messagePos, messageLen);\n\t\tm_lpszSongComments[messageLen] = '\\0';\n\t}\n\n\t// Samples.\n\tpsmp = (const SAMPLEGDM *)(lpStream + samplesPos);\n\tfor (UINT nins = 0; nins < nSamples; nins++)\n\t{\n\t\tconst SAMPLEGDM &smp = psmp[nins];\n\t\tMODINSTRUMENT &ins = Ins[nins + 1];\n\n\t\tDWORD len = fixu32(smp.length);\n\t\tDWORD loopstart = fixu32(smp.loopstart);\n\t\tDWORD loopend = fixu32(smp.loopend);\n\n\t\tUINT flags = smp.flags;\n\n\t\t// Note: BWSB and 2GDM don't support LZW, stereo samples, sample panning.\n\t\tif (flags & SAMPLEGDM::S_16BIT)\n\t\t{\n\t\t\tsflags[nins] = RS_PCM16U;\n\t\t\t// Due to a 2GDM bug, the sample size is halved.\n\t\t\t// (Note BWSB doesn't even check for these anyway.)\n\t\t\tlen /= 2;\n\t\t\tloopstart /= 2;\n\t\t\tloopend /= 2;\n\t\t}\n\t\telse\n\t\t\tsflags[nins] = RS_PCM8U;\n\n\t\tif (len > MAX_SAMPLE_LENGTH) len = MAX_SAMPLE_LENGTH;\n\t\tif (loopend > len) loopend = len;\n\t\tif (loopstart > loopend) loopstart = loopend = 0;\n\n\t\tins.nLength = len;\n\t\tins.nLoopStart = loopstart;\n\t\tins.nLoopEnd = loopend;\n\t\tif (loopend && (flags & SAMPLEGDM::S_LOOP)) ins.uFlags |= CHN_LOOP;\n\n\t\tins.nC4Speed = fixu16(smp.c4rate);\n\t\tins.nVolume = (flags & SAMPLEGDM::S_VOLUME) && (smp.volume <= 64) ? (smp.volume << 2) : 256;\n\t\tins.nGlobalVol = 64;\n\t\tins.nPan = 128;\n\n\t\tmemcpy(m_szNames[nins], smp.name, 32);\n\t\tm_szNames[nins][31] = '\\0';\n\n\t\tmemcpy(ins.name, smp.dosname, 12);\n\t\tins.name[13] = '\\0';\n\t}\n\n\t// Order table.\n\tmemcpy(Order, lpStream + ordersPos, pfh->nOrders + 1);\n\n\t// Scan patterns to get pattern row counts and the real module channel count.\n\t// Also do bounds checks to make sure the patterns can be safely loaded.\n\tpos = patternsPos;\n\tfor (npat = 0; npat < nPatterns && pos + 2 <= dwMemLength; npat++)\n\t{\n\t\tUINT patLen = fixu16(lpStream + pos);\n\t\tDWORD patEnd = pos + patLen;\n\t\tUINT rows = 0;\n\t\tUINT channel;\n\n\t\tif (patEnd > dwMemLength) break;\n\n\t\tpos += 2;\n\t\twhile (pos < patEnd)\n\t\t{\n\t\t\trows++;\n\n\t\t\twhile (pos < patEnd)\n\t\t\t{\n\t\t\t\tBYTE dat = lpStream[pos++];\n\t\t\t\tif (!dat)\n\t\t\t\t\tbreak;\n\n\t\t\t\tchannel = dat & 0x1f;\n\t\t\t\tif (channel >= m_nChannels)\n\t\t\t\t\tm_nChannels = channel + 1;\n\n\t\t\t\t// Note and sample.\n\t\t\t\tif (dat & 0x20)\n\t\t\t\t{\n\t\t\t\t\tif (pos + 2 > patEnd) goto BadPattern;\n\t\t\t\t\tpos += 2;\n\t\t\t\t}\n\n\t\t\t\t// Effects.\n\t\t\t\tif (dat & 0x40)\n\t\t\t\t{\n\t\t\t\t\tdo\n\t\t\t\t\t{\n\t\t\t\t\t\tif (pos + 2 > patEnd) goto BadPattern;\n\t\t\t\t\t\tdat = lpStream[pos];\n\t\t\t\t\t\tpos += 2;\n\t\t\t\t\t} while (dat & 0x20);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tPatternSize[npat] = rows;\n\t}\nBadPattern:\n\t// Discard truncated/corrupted patterns (if any).\n\tif (npat < nPatterns)\n\t\tnPatterns = npat;\n\n\t// Load patterns.\n\tpos = patternsPos;\n\tfor (npat = 0; npat < nPatterns && pos + 2 <= dwMemLength; npat++)\n\t{\n\t\tUINT patLen = fixu16(lpStream + pos);\n\t\tDWORD patEnd = pos + patLen;\n\t\tUINT row = 0;\n\n\t\tpos += 2;\n\n\t\tPatterns[npat] = AllocatePattern(PatternSize[npat], m_nChannels);\n\t\tif (!Patterns[npat]) break;\n\n\t\tMODCOMMAND *m = Patterns[npat];\n\t\twhile (pos < patEnd)\n\t\t{\n\t\t\twhile (pos < patEnd)\n\t\t\t{\n\t\t\t\tBYTE dat = lpStream[pos++];\n\t\t\t\tif (!dat)\n\t\t\t\t\tbreak;\n\n\t\t\t\tBYTE channel = dat & 0x1f;\n\t\t\t\tMODCOMMAND &ev = m[row * m_nChannels + channel];\n\n\t\t\t\tif (dat & 0x20)\n\t\t\t\t{\n\t\t\t\t\tBYTE note = lpStream[pos++];\n\t\t\t\t\tBYTE smpl = lpStream[pos++];\n\n\t\t\t\t\tif (note) // This can be 0, indicating no note (see STARDSTM.GDM).\n\t\t\t\t\t{\n\t\t\t\t\t\tBYTE octave = (note & 0x70) >> 4;\n\t\t\t\t\t\tnote = octave * 12 + (note & 0x0f) + 12;\n\t\t\t\t\t}\n\n\t\t\t\t\tev.note = note;\n\t\t\t\t\tev.instr = smpl;\n\t\t\t\t}\n\n\t\t\t\tif (dat & 0x40)\n\t\t\t\t{\n\t\t\t\t\tBYTE cmd, param;\n\t\t\t\t\tdo\n\t\t\t\t\t{\n\t\t\t\t\t\tcmd = lpStream[pos++];\n\t\t\t\t\t\tparam = lpStream[pos++];\n\t\t\t\t\t\tGDM_TranslateEffect(pfh, ev, (cmd & 0xc0) >> 6, (cmd & 0x01f), param);\n\n\t\t\t\t\t} while (cmd & 0x20);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trow++;\n\t\t}\n\t}\n\n\t// Reading Samples\n\tpos = sampleDataPos;\n\tfor (UINT n = 0; n < nSamples; n++)\n\t{\n\t\tMODINSTRUMENT &ins = Ins[n + 1];\n\t\tUINT len = ins.nLength;\n\n\t\tif (pos >= dwMemLength) break;\n\t\tif (len)\n\t\t{\n\t\t\tlen = ReadSample(&ins, sflags[n], (LPSTR)(lpStream + pos), dwMemLength - pos);\n\t\t\tpos += len;\n\t\t}\n\t}\n\n\treturn TRUE;\n}\n"
  },
  {
    "path": "contrib/libmodplug/src/load_it.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>,\n *          Adam Goode       <adam@evdebs.org> (Endian and char fixes for PPC)\n *          Marco Trillo     <toad@arsystel.com> (Endian fixes for SaveIT, XM->IT Sample Converter)\n *\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n#include \"it_defs.h\"\n\n#ifdef _MSC_VER\n#pragma warning(disable:4244)\n#endif\n\nstatic BYTE autovibit2xm[8] =\n{ 0, 3, 1, 4, 2, 0, 0, 0 };\n\n#ifndef MODPLUG_NO_FILESAVE\nstatic BYTE autovibxm2it[8] =\n{ 0, 2, 4, 1, 3, 0, 0, 0 };\n#endif\n\n//////////////////////////////////////////////////////////\n// Impulse Tracker IT file support\n\n// for conversion of XM samples\nextern WORD XMPeriodTable[96+8];\nextern UINT XMLinearTable[768];\n\nstatic inline UINT ConvertVolParam(UINT value)\n//--------------------------------------------\n{\n\treturn (value > 9)  ? 9 : value;\n}\n\n\nBOOL CSoundFile::ITInstrToMPT(const void *p, INSTRUMENTHEADER *penv, UINT trkvers)\n//--------------------------------------------------------------------------------\n{\n\tif (trkvers < 0x0200)\n\t{\n\t\tconst ITOLDINSTRUMENT *pis = (const ITOLDINSTRUMENT *)p;\n\t\tmemcpy(penv->name, pis->name, 26);\n\t\tmemcpy(penv->filename, pis->filename, 12);\n\t\tpenv->nFadeOut = bswapLE16(pis->fadeout) << 6;\n\t\tpenv->nGlobalVol = 64;\n\t\tfor (UINT j=0; j<NOTE_MAX; j++)\n\t\t{\n\t\t\tUINT note = pis->keyboard[j*2];\n\t\t\tUINT ins = pis->keyboard[j*2+1];\n\t\t\tif (ins < MAX_SAMPLES) penv->Keyboard[j] = ins;\n\t\t\tif (note < 128) penv->NoteMap[j] = note+1;\n\t\t\telse if (note >= 0xFE) penv->NoteMap[j] = note;\n\t\t}\n\t\tif (pis->flags & 0x01) penv->dwFlags |= ENV_VOLUME;\n\t\tif (pis->flags & 0x02) penv->dwFlags |= ENV_VOLLOOP;\n\t\tif (pis->flags & 0x04) penv->dwFlags |= ENV_VOLSUSTAIN;\n\t\tpenv->nVolLoopStart = pis->vls;\n\t\tpenv->nVolLoopEnd = pis->vle;\n\t\tpenv->nVolSustainBegin = pis->sls;\n\t\tpenv->nVolSustainEnd = pis->sle;\n\t\tpenv->nVolEnv = 25;\n\t\tfor (UINT ev=0; ev<25; ev++)\n\t\t{\n\t\t\tif ((penv->VolPoints[ev] = pis->nodes[ev*2]) == 0xFF)\n\t\t\t{\n\t\t\t\tpenv->nVolEnv = ev;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tpenv->VolEnv[ev] = pis->nodes[ev*2+1];\n\t\t}\n\t\tpenv->nNNA = pis->nna;\n\t\tpenv->nDCT = pis->dnc;\n\t\tpenv->nPan = 0x80;\n\t} else\n\t{\n\t\tconst ITINSTRUMENT *pis = (const ITINSTRUMENT *)p;\n\t\tmemcpy(penv->name, pis->name, 26);\n\t\tmemcpy(penv->filename, pis->filename, 12);\n\t\tpenv->nMidiProgram = pis->mpr;\n\t\tpenv->nMidiChannel = pis->mch;\n\t\tpenv->wMidiBank = bswapLE16(pis->mbank);\n\t\tpenv->nFadeOut = bswapLE16(pis->fadeout) << 5;\n\t\tpenv->nGlobalVol = pis->gbv >> 1;\n\t\tif (penv->nGlobalVol > 64) penv->nGlobalVol = 64;\n\t\tfor (UINT j=0; j<NOTE_MAX; j++)\n\t\t{\n\t\t\tUINT note = pis->keyboard[j*2];\n\t\t\tUINT ins = pis->keyboard[j*2+1];\n\t\t\tif (ins < MAX_SAMPLES) penv->Keyboard[j] = ins;\n\t\t\tif (note < 128) penv->NoteMap[j] = note+1;\n\t\t\telse if (note >= 0xFE) penv->NoteMap[j] = note;\n\t\t}\n\t\t// Volume Envelope\n\t\tif (pis->volenv.flags & 1) penv->dwFlags |= ENV_VOLUME;\n\t\tif (pis->volenv.flags & 2) penv->dwFlags |= ENV_VOLLOOP;\n\t\tif (pis->volenv.flags & 4) penv->dwFlags |= ENV_VOLSUSTAIN;\n\t\tif (pis->volenv.flags & 8) penv->dwFlags |= ENV_VOLCARRY;\n\t\tpenv->nVolEnv = pis->volenv.num;\n\t\tif (penv->nVolEnv > 25) penv->nVolEnv = 25;\n\n\t\tpenv->nVolLoopStart = pis->volenv.lpb;\n\t\tpenv->nVolLoopEnd = pis->volenv.lpe;\n\t\tpenv->nVolSustainBegin = pis->volenv.slb;\n\t\tpenv->nVolSustainEnd = pis->volenv.sle;\n\t\t// Panning Envelope\n\t\tif (pis->panenv.flags & 1) penv->dwFlags |= ENV_PANNING;\n\t\tif (pis->panenv.flags & 2) penv->dwFlags |= ENV_PANLOOP;\n\t\tif (pis->panenv.flags & 4) penv->dwFlags |= ENV_PANSUSTAIN;\n\t\tif (pis->panenv.flags & 8) penv->dwFlags |= ENV_PANCARRY;\n\t\tpenv->nPanEnv = pis->panenv.num;\n\t\tif (penv->nPanEnv > 25) penv->nPanEnv = 25;\n\t\tpenv->nPanLoopStart = pis->panenv.lpb;\n\t\tpenv->nPanLoopEnd = pis->panenv.lpe;\n\t\tpenv->nPanSustainBegin = pis->panenv.slb;\n\t\tpenv->nPanSustainEnd = pis->panenv.sle;\n\t\t// Pitch Envelope\n\t\tif (pis->pitchenv.flags & 1) penv->dwFlags |= ENV_PITCH;\n\t\tif (pis->pitchenv.flags & 2) penv->dwFlags |= ENV_PITCHLOOP;\n\t\tif (pis->pitchenv.flags & 4) penv->dwFlags |= ENV_PITCHSUSTAIN;\n\t\tif (pis->pitchenv.flags & 8) penv->dwFlags |= ENV_PITCHCARRY;\n\t\tif (pis->pitchenv.flags & 0x80) penv->dwFlags |= ENV_FILTER;\n\t\tpenv->nPitchEnv = pis->pitchenv.num;\n\t\tif (penv->nPitchEnv > 25) penv->nPitchEnv = 25;\n\t\tpenv->nPitchLoopStart = pis->pitchenv.lpb;\n\t\tpenv->nPitchLoopEnd = pis->pitchenv.lpe;\n\t\tpenv->nPitchSustainBegin = pis->pitchenv.slb;\n\t\tpenv->nPitchSustainEnd = pis->pitchenv.sle;\n\t\t// Envelopes Data\n\t\tfor (UINT ev=0; ev<25; ev++)\n\t\t{\n\t\t\tpenv->VolEnv[ev] = pis->volenv.data[ev*3];\n\t\t\tpenv->VolPoints[ev] = (pis->volenv.data[ev*3+2] << 8) | (pis->volenv.data[ev*3+1]);\n\t\t\tpenv->PanEnv[ev] = pis->panenv.data[ev*3] + 32;\n\t\t\tpenv->PanPoints[ev] = (pis->panenv.data[ev*3+2] << 8) | (pis->panenv.data[ev*3+1]);\n\t\t\tpenv->PitchEnv[ev] = pis->pitchenv.data[ev*3] + 32;\n\t\t\tpenv->PitchPoints[ev] = (pis->pitchenv.data[ev*3+2] << 8) | (pis->pitchenv.data[ev*3+1]);\n\t\t}\n\t\tpenv->nNNA = pis->nna;\n\t\tpenv->nDCT = pis->dct;\n\t\tpenv->nDNA = pis->dca;\n\t\tpenv->nPPS = pis->pps;\n\t\tpenv->nPPC = pis->ppc;\n\t\tpenv->nIFC = pis->ifc;\n\t\tpenv->nIFR = pis->ifr;\n\t\tpenv->nVolSwing = pis->rv;\n\t\tpenv->nPanSwing = pis->rp;\n\t\tpenv->nPan = (pis->dfp & 0x7F) << 2;\n\t\tif (penv->nPan > 256) penv->nPan = 128;\n\t\tif (pis->dfp < 0x80) penv->dwFlags |= ENV_SETPANNING;\n\t}\n\tif ((penv->nVolLoopStart >= 25) || (penv->nVolLoopEnd >= 25)) penv->dwFlags &= ~ENV_VOLLOOP;\n\tif ((penv->nVolSustainBegin >= 25) || (penv->nVolSustainEnd >= 25)) penv->dwFlags &= ~ENV_VOLSUSTAIN;\n\treturn TRUE;\n}\n\n\nBOOL CSoundFile::ReadIT(const BYTE *lpStream, DWORD dwMemLength)\n//--------------------------------------------------------------\n{\n\tDWORD dwMemPos = sizeof(ITFILEHEADER);\n\tDWORD inspos[MAX_INSTRUMENTS];\n\tDWORD smppos[MAX_SAMPLES];\n\tDWORD patpos[MAX_PATTERNS];\n\tBYTE chnmask[64];\n\tMODCOMMAND lastvalue[64];\n\n\tif ((!lpStream) || (dwMemLength < sizeof(ITFILEHEADER))) return FALSE;\n\tITFILEHEADER pifh = *(ITFILEHEADER *)lpStream;\n\n\tpifh.id = bswapLE32(pifh.id);\n\tpifh.reserved1 = bswapLE16(pifh.reserved1);\n\tpifh.ordnum = bswapLE16(pifh.ordnum);\n\tpifh.insnum = bswapLE16(pifh.insnum);\n\tpifh.smpnum = bswapLE16(pifh.smpnum);\n\tpifh.patnum = bswapLE16(pifh.patnum);\n\tpifh.cwtv = bswapLE16(pifh.cwtv);\n\tpifh.cmwt = bswapLE16(pifh.cmwt);\n\tpifh.flags = bswapLE16(pifh.flags);\n\tpifh.special = bswapLE16(pifh.special);\n\tpifh.msglength = bswapLE16(pifh.msglength);\n\tpifh.msgoffset = bswapLE32(pifh.msgoffset);\n\tpifh.reserved2 = bswapLE32(pifh.reserved2);\n\n\tif ((pifh.id != 0x4D504D49) || (pifh.insnum >= MAX_INSTRUMENTS)\n\t || (!pifh.smpnum) || (pifh.smpnum >= MAX_INSTRUMENTS) || (!pifh.ordnum)) return FALSE;\n\tif (dwMemPos + pifh.ordnum + pifh.insnum*4\n\t + pifh.smpnum*4 + pifh.patnum*4 > dwMemLength) return FALSE;\n\tm_nType = MOD_TYPE_IT;\n\tif (pifh.flags & 0x08) m_dwSongFlags |= SONG_LINEARSLIDES;\n\tif (pifh.flags & 0x10) m_dwSongFlags |= SONG_ITOLDEFFECTS;\n\tif (pifh.flags & 0x20) m_dwSongFlags |= SONG_ITCOMPATMODE;\n\tif (pifh.flags & 0x80) m_dwSongFlags |= SONG_EMBEDMIDICFG;\n\tif (pifh.flags & 0x1000) m_dwSongFlags |= SONG_EXFILTERRANGE;\n\tmemcpy(m_szNames[0], pifh.songname, 26);\n\tm_szNames[0][26] = 0;\n\t// Global Volume\n\tif (pifh.globalvol)\n\t{\n\t\tm_nDefaultGlobalVolume = pifh.globalvol << 1;\n\t\tif (!m_nDefaultGlobalVolume) m_nDefaultGlobalVolume = 256;\n\t\tif (m_nDefaultGlobalVolume > 256) m_nDefaultGlobalVolume = 256;\n\t}\n\tif (pifh.speed) m_nDefaultSpeed = pifh.speed;\n\tif (pifh.tempo) m_nDefaultTempo = pifh.tempo;\n\tm_nSongPreAmp = pifh.mv & 0x7F;\n\t// Reading Channels Pan Positions\n\tfor (int ipan=0; ipan<64; ipan++) if (pifh.chnpan[ipan] != 0xFF)\n\t{\n\t\tChnSettings[ipan].nVolume = pifh.chnvol[ipan];\n\t\tChnSettings[ipan].nPan = 128;\n\t\tif (pifh.chnpan[ipan] & 0x80) ChnSettings[ipan].dwFlags |= CHN_MUTE;\n\t\tUINT n = pifh.chnpan[ipan] & 0x7F;\n\t\tif (n <= 64) ChnSettings[ipan].nPan = n << 2;\n\t\tif (n == 100) ChnSettings[ipan].dwFlags |= CHN_SURROUND;\n\t}\n\tif (m_nChannels < 4) m_nChannels = 4;\n\t// Reading Song Message\n\tif ((pifh.special & 0x01) && (pifh.msglength) && (pifh.msglength <= dwMemLength) && (pifh.msgoffset < dwMemLength - pifh.msglength))\n\t{\n\t\tm_lpszSongComments = new char[pifh.msglength+1];\n\t\tif (m_lpszSongComments)\n\t\t{\n\t\t\tmemcpy(m_lpszSongComments, lpStream+pifh.msgoffset, pifh.msglength);\n\t\t\tm_lpszSongComments[pifh.msglength] = 0;\n\t\t}\n\t}\n\t// Reading orders\n\tUINT nordsize = pifh.ordnum;\n\tif (nordsize > MAX_ORDERS) nordsize = MAX_ORDERS;\n\tmemcpy(Order, lpStream+dwMemPos, nordsize);\n\tdwMemPos += pifh.ordnum;\n\t// Reading Instrument Offsets\n\tmemset(inspos, 0, sizeof(inspos));\n\tUINT inspossize = pifh.insnum;\n\tif (inspossize > MAX_INSTRUMENTS) inspossize = MAX_INSTRUMENTS;\n\tinspossize <<= 2;\n\tmemcpy(inspos, lpStream+dwMemPos, inspossize);\n\tfor (UINT j=0; j < (inspossize>>2); j++)\n\t{\n\t       inspos[j] = bswapLE32(inspos[j]);\n\t}\n\tdwMemPos += pifh.insnum * 4;\n\t// Reading Samples Offsets\n\tmemset(smppos, 0, sizeof(smppos));\n\tUINT smppossize = pifh.smpnum;\n\tif (smppossize > MAX_SAMPLES) smppossize = MAX_SAMPLES;\n\tsmppossize <<= 2;\n\tmemcpy(smppos, lpStream+dwMemPos, smppossize);\n\tfor (UINT j=0; j < (smppossize>>2); j++)\n\t{\n\t       smppos[j] = bswapLE32(smppos[j]);\n\t}\n\tdwMemPos += pifh.smpnum * 4;\n\t// Reading Patterns Offsets\n\tmemset(patpos, 0, sizeof(patpos));\n\tUINT patpossize = pifh.patnum;\n\tif (patpossize > MAX_PATTERNS) patpossize = MAX_PATTERNS;\n\tpatpossize <<= 2;\n\tmemcpy(patpos, lpStream+dwMemPos, patpossize);\n\tfor (UINT j=0; j < (patpossize>>2); j++)\n\t{\n\t       patpos[j] = bswapLE32(patpos[j]);\n\t}\n\tdwMemPos += pifh.patnum * 4;\n\t// Reading IT Extra Info\n\tif (dwMemPos + 2 < dwMemLength)\n\t{\n\t\tUINT nflt = bswapLE16(*((WORD *)(lpStream + dwMemPos)));\n\t\tdwMemPos += 2;\n\t\tif (dwMemPos + nflt * 8 < dwMemLength) dwMemPos += nflt * 8;\n\t}\n\t// Reading Midi Output & Macros\n\tif (m_dwSongFlags & SONG_EMBEDMIDICFG)\n\t{\n\t\tif (dwMemPos + sizeof(MODMIDICFG) < dwMemLength)\n\t\t{\n\t\t\tmemcpy(&m_MidiCfg, lpStream+dwMemPos, sizeof(MODMIDICFG));\n\t\t\tdwMemPos += sizeof(MODMIDICFG);\n\t\t}\n\t}\n\t// Read pattern names: \"PNAM\"\n\tif ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4d414e50))\n\t{\n\t\tUINT len = bswapLE32(*((DWORD *)(lpStream+dwMemPos+4)));\n\t\tdwMemPos += 8;\n\t\tif ((dwMemPos + len <= dwMemLength) && (len <= MAX_PATTERNS*MAX_PATTERNNAME) && (len >= MAX_PATTERNNAME))\n\t\t{\n\t\t\tm_lpszPatternNames = new char[len];\n\t\t\tif (m_lpszPatternNames)\n\t\t\t{\n\t\t\t\tm_nPatternNames = len / MAX_PATTERNNAME;\n\t\t\t\tmemcpy(m_lpszPatternNames, lpStream+dwMemPos, len);\n\t\t\t}\n\t\t\tdwMemPos += len;\n\t\t}\n\t}\n\t// 4-channels minimum\n\tm_nChannels = 4;\n\t// Read channel names: \"CNAM\"\n\tif ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4d414e43))\n\t{\n\t\tUINT len = bswapLE32(*((DWORD *)(lpStream+dwMemPos+4)));\n\t\tdwMemPos += 8;\n\t\tif ((dwMemPos + len <= dwMemLength) && (len <= 64*MAX_CHANNELNAME))\n\t\t{\n\t\t\tUINT n = len / MAX_CHANNELNAME;\n\t\t\tif (n > m_nChannels) m_nChannels = n;\n\t\t\tfor (UINT i=0; i<n; i++)\n\t\t\t{\n\t\t\t\tmemcpy(ChnSettings[i].szName, (lpStream+dwMemPos+i*MAX_CHANNELNAME), MAX_CHANNELNAME);\n\t\t\t\tChnSettings[i].szName[MAX_CHANNELNAME-1] = 0;\n\t\t\t}\n\t\t\tdwMemPos += len;\n\t\t}\n\t}\n\t// Read mix plugins information\n\tif (dwMemPos + 8 < dwMemLength)\n\t{\n\t\tdwMemPos += LoadMixPlugins(lpStream+dwMemPos, dwMemLength-dwMemPos);\n\t}\n\t// Checking for unused channels\n\tUINT npatterns = pifh.patnum;\n\tif (npatterns > MAX_PATTERNS) npatterns = MAX_PATTERNS;\n\tfor (UINT patchk=0; patchk<npatterns; patchk++)\n\t{\n\t\tmemset(chnmask, 0, sizeof(chnmask));\n\t\tif ((!patpos[patchk]) || ((DWORD)patpos[patchk] >= dwMemLength - 4)) continue;\n\t\tUINT len = bswapLE16(*((WORD *)(lpStream+patpos[patchk])));\n\t\tUINT rows = bswapLE16(*((WORD *)(lpStream+patpos[patchk]+2)));\n\t\tif ((rows < 4) || (rows > 256)) continue;\n\t\tif (8+len > dwMemLength || patpos[patchk] > dwMemLength - (8+len)) continue;\n\t\tUINT i = 0;\n\t\tconst BYTE *p = lpStream+patpos[patchk]+8;\n\t\tUINT nrow = 0;\n\t\twhile (nrow<rows)\n\t\t{\n\t\t\tif (i >= len) break;\n\t\t\tBYTE b = p[i++];\n\t\t\tif (!b)\n\t\t\t{\n\t\t\t\tnrow++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tUINT ch = b & 0x7F;\n\t\t\tif (ch) ch = (ch - 1) & 0x3F;\n\t\t\tif (b & 0x80)\n\t\t\t{\n\t\t\t\tif (i >= len) break;\n\t\t\t\tchnmask[ch] = p[i++];\n\t\t\t}\n\t\t\t// Channel used\n\t\t\tif (chnmask[ch] & 0x0F)\n\t\t\t{\n\t\t\t\tif ((ch >= m_nChannels) && (ch < 64)) m_nChannels = ch+1;\n\t\t\t}\n\t\t\t// Note\n\t\t\tif (chnmask[ch] & 1) i++;\n\t\t\t// Instrument\n\t\t\tif (chnmask[ch] & 2) i++;\n\t\t\t// Volume\n\t\t\tif (chnmask[ch] & 4) i++;\n\t\t\t// Effect\n\t\t\tif (chnmask[ch] & 8) i += 2;\n\t\t\tif (i >= len) break;\n\t\t}\n\t}\n\t// Reading Instruments\n\tm_nInstruments = 0;\n\tif (pifh.flags & 0x04) m_nInstruments = pifh.insnum;\n\tif (m_nInstruments >= MAX_INSTRUMENTS) m_nInstruments = MAX_INSTRUMENTS-1;\n\tfor (UINT nins=0; nins<m_nInstruments; nins++)\n\t{\n\t\tif ((inspos[nins] > 0) && dwMemLength > sizeof(ITOLDINSTRUMENT) &&\n\t\t\t(inspos[nins] < dwMemLength - sizeof(ITOLDINSTRUMENT)))\n\t\t{\n\t\t\tINSTRUMENTHEADER *penv = new INSTRUMENTHEADER;\n\t\t\tif (!penv) continue;\n\t\t\tHeaders[nins+1] = penv;\n\t\t\tmemset(penv, 0, sizeof(INSTRUMENTHEADER));\n\t\t\tITInstrToMPT(lpStream + inspos[nins], penv, pifh.cmwt);\n\t\t}\n\t}\n\t// Reading Samples\n\tm_nSamples = pifh.smpnum;\n\tif (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES-1;\n\tfor (UINT nsmp=0; nsmp<pifh.smpnum; nsmp++) if ((smppos[nsmp]) && (smppos[nsmp] <= dwMemLength - sizeof(ITSAMPLESTRUCT)))\n\t{\n\t\tITSAMPLESTRUCT pis = *(ITSAMPLESTRUCT *)(lpStream+smppos[nsmp]);\n\t\tpis.id = bswapLE32(pis.id);\n\t\tpis.length = bswapLE32(pis.length);\n\t\tpis.loopbegin = bswapLE32(pis.loopbegin);\n\t\tpis.loopend = bswapLE32(pis.loopend);\n\t\tpis.C5Speed = bswapLE32(pis.C5Speed);\n\t\tpis.susloopbegin = bswapLE32(pis.susloopbegin);\n\t\tpis.susloopend = bswapLE32(pis.susloopend);\n\t\tpis.samplepointer = bswapLE32(pis.samplepointer);\n\n\t\tif (pis.id == 0x53504D49)\n\t\t{\n\t\t\tMODINSTRUMENT *pins = &Ins[nsmp+1];\n\t\t\tmemcpy(pins->name, pis.filename, 12);\n\t\t\tpins->uFlags = 0;\n\t\t\tpins->nLength = 0;\n\t\t\tpins->nLoopStart = pis.loopbegin;\n\t\t\tpins->nLoopEnd = pis.loopend;\n\t\t\tpins->nSustainStart = pis.susloopbegin;\n\t\t\tpins->nSustainEnd = pis.susloopend;\n\t\t\tpins->nC4Speed = pis.C5Speed;\n\t\t\tif (!pins->nC4Speed) pins->nC4Speed = 8363;\n\t\t\tif (pis.C5Speed < 256) pins->nC4Speed = 256;\n\t\t\tpins->nVolume = pis.vol << 2;\n\t\t\tif (pins->nVolume > 256) pins->nVolume = 256;\n\t\t\tpins->nGlobalVol = pis.gvl;\n\t\t\tif (pins->nGlobalVol > 64) pins->nGlobalVol = 64;\n\t\t\tif (pis.flags & 0x10) pins->uFlags |= CHN_LOOP;\n\t\t\tif (pis.flags & 0x20) pins->uFlags |= CHN_SUSTAINLOOP;\n\t\t\tif (pis.flags & 0x40) pins->uFlags |= CHN_PINGPONGLOOP;\n\t\t\tif (pis.flags & 0x80) pins->uFlags |= CHN_PINGPONGSUSTAIN;\n\t\t\tpins->nPan = (pis.dfp & 0x7F) << 2;\n\t\t\tif (pins->nPan > 256) pins->nPan = 256;\n\t\t\tif (pis.dfp & 0x80) pins->uFlags |= CHN_PANNING;\n\t\t\tpins->nVibType = autovibit2xm[pis.vit & 7];\n\t\t\tpins->nVibRate = pis.vis;\n\t\t\tpins->nVibDepth = pis.vid & 0x7F;\n\t\t\tpins->nVibSweep = (pis.vir + 3) / 4;\n\t\t\tif ((pis.samplepointer) && (pis.samplepointer < dwMemLength) && (pis.length))\n\t\t\t{\n\t\t\t\tpins->nLength = pis.length;\n\t\t\t\tif (pins->nLength > MAX_SAMPLE_LENGTH) pins->nLength = MAX_SAMPLE_LENGTH;\n\t\t\t\tUINT flags = (pis.cvt & 1) ? RS_PCM8S : RS_PCM8U;\n\t\t\t\tif (pis.flags & 2)\n\t\t\t\t{\n\t\t\t\t\tflags += 5;\n\t\t\t\t\tif (pis.flags & 4) flags |= RSF_STEREO;\n\t\t\t\t\tpins->uFlags |= CHN_16BIT;\n\t\t\t\t\t// IT 2.14 16-bit packed sample ?\n\t\t\t\t\tif (pis.flags & 8) flags = ((pifh.cmwt >= 0x215) && (pis.cvt & 4)) ? RS_IT21516 : RS_IT21416;\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tif (pis.flags & 4) flags |= RSF_STEREO;\n\t\t\t\t\tif (pis.cvt == 0xFF) flags = RS_ADPCM4; else\n\t\t\t\t\t// IT 2.14 8-bit packed sample ?\n\t\t\t\t\tif (pis.flags & 8)\tflags =\t((pifh.cmwt >= 0x215) && (pis.cvt & 4)) ? RS_IT2158 : RS_IT2148;\n\t\t\t\t}\n\t\t\t\tReadSample(&Ins[nsmp+1], flags, (LPSTR)(lpStream+pis.samplepointer), dwMemLength - pis.samplepointer);\n\t\t\t}\n\t\t}\n\t\tmemcpy(m_szNames[nsmp+1], pis.name, 26);\n\t}\n\t// Reading Patterns\n\tfor (UINT npat=0; npat<npatterns; npat++)\n\t{\n\t\tif ((!patpos[npat]) || ((DWORD)patpos[npat] >= dwMemLength - 4))\n\t\t{\n\t\t\tPatternSize[npat] = 64;\n\t\t\tPatterns[npat] = AllocatePattern(64, m_nChannels);\n\t\t\tcontinue;\n\t\t}\n\n\t\tUINT len = bswapLE16(*((WORD *)(lpStream+patpos[npat])));\n\t\tUINT rows = bswapLE16(*((WORD *)(lpStream+patpos[npat]+2)));\n\t\tif ((rows < 4) || (rows > 256)) continue;\n\t\tif (8+len > dwMemLength || patpos[npat] > dwMemLength - (8+len)) continue;\n\t\tPatternSize[npat] = rows;\n\t\tif ((Patterns[npat] = AllocatePattern(rows, m_nChannels)) == NULL) continue;\n\t\tmemset(lastvalue, 0, sizeof(lastvalue));\n\t\tmemset(chnmask, 0, sizeof(chnmask));\n\t\tMODCOMMAND *m = Patterns[npat];\n\t\tUINT i = 0;\n\t\tconst BYTE *p = lpStream+patpos[npat]+8;\n\t\tUINT nrow = 0;\n\t\twhile (nrow<rows)\n\t\t{\n\t\t\tif (i >= len) break;\n\t\t\tBYTE b = p[i++];\n\t\t\tif (!b)\n\t\t\t{\n\t\t\t\tnrow++;\n\t\t\t\tm+=m_nChannels;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tUINT ch = b & 0x7F;\n\t\t\tif (ch) ch = (ch - 1) & 0x3F;\n\t\t\tif (b & 0x80)\n\t\t\t{\n\t\t\t\tif (i >= len) break;\n\t\t\t\tchnmask[ch] = p[i++];\n\t\t\t}\n\t\t\tif ((chnmask[ch] & 0x10) && (ch < m_nChannels))\n\t\t\t{\n\t\t\t\tm[ch].note = lastvalue[ch].note;\n\t\t\t}\n\t\t\tif ((chnmask[ch] & 0x20) && (ch < m_nChannels))\n\t\t\t{\n\t\t\t\tm[ch].instr = lastvalue[ch].instr;\n\t\t\t}\n\t\t\tif ((chnmask[ch] & 0x40) && (ch < m_nChannels))\n\t\t\t{\n\t\t\t\tm[ch].volcmd = lastvalue[ch].volcmd;\n\t\t\t\tm[ch].vol = lastvalue[ch].vol;\n\t\t\t}\n\t\t\tif ((chnmask[ch] & 0x80) && (ch < m_nChannels))\n\t\t\t{\n\t\t\t\tm[ch].command = lastvalue[ch].command;\n\t\t\t\tm[ch].param = lastvalue[ch].param;\n\t\t\t}\n\t\t\tif (chnmask[ch] & 1)\t// Note\n\t\t\t{\n\t\t\t\tif (i >= len) break;\n\t\t\t\tUINT note = p[i++];\n\t\t\t\tif (ch < m_nChannels)\n\t\t\t\t{\n\t\t\t\t\tif (note < 0x80) note++;\n\t\t\t\t\tm[ch].note = note;\n\t\t\t\t\tlastvalue[ch].note = note;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (chnmask[ch] & 2)\n\t\t\t{\n\t\t\t\tif (i >= len) break;\n\t\t\t\tUINT instr = p[i++];\n\t\t\t\tif (ch < m_nChannels)\n\t\t\t\t{\n\t\t\t\t\tm[ch].instr = instr;\n\t\t\t\t\tlastvalue[ch].instr = instr;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (chnmask[ch] & 4)\n\t\t\t{\n\t\t\t\tif (i >= len) break;\n\t\t\t\tUINT vol = p[i++];\n\t\t\t\tif (ch < m_nChannels)\n\t\t\t\t{\n\t\t\t\t\t// 0-64: Set Volume\n\t\t\t\t\tif (vol <= 64) { m[ch].volcmd = VOLCMD_VOLUME; m[ch].vol = vol; } else\n\t\t\t\t\t// 128-192: Set Panning\n\t\t\t\t\tif ((vol >= 128) && (vol <= 192)) { m[ch].volcmd = VOLCMD_PANNING; m[ch].vol = vol - 128; } else\n\t\t\t\t\t// 65-74: Fine Volume Up\n\t\t\t\t\tif (vol < 75) { m[ch].volcmd = VOLCMD_FINEVOLUP; m[ch].vol = vol - 65; } else\n\t\t\t\t\t// 75-84: Fine Volume Down\n\t\t\t\t\tif (vol < 85) { m[ch].volcmd = VOLCMD_FINEVOLDOWN; m[ch].vol = vol - 75; } else\n\t\t\t\t\t// 85-94: Volume Slide Up\n\t\t\t\t\tif (vol < 95) { m[ch].volcmd = VOLCMD_VOLSLIDEUP; m[ch].vol = vol - 85; } else\n\t\t\t\t\t// 95-104: Volume Slide Down\n\t\t\t\t\tif (vol < 105) { m[ch].volcmd = VOLCMD_VOLSLIDEDOWN; m[ch].vol = vol - 95; } else\n\t\t\t\t\t// 105-114: Pitch Slide Up\n\t\t\t\t\tif (vol < 115) { m[ch].volcmd = VOLCMD_PORTADOWN; m[ch].vol = vol - 105; } else\n\t\t\t\t\t// 115-124: Pitch Slide Down\n\t\t\t\t\tif (vol < 125) { m[ch].volcmd = VOLCMD_PORTAUP; m[ch].vol = vol - 115; } else\n\t\t\t\t\t// 193-202: Portamento To\n\t\t\t\t\tif ((vol >= 193) && (vol <= 202)) { m[ch].volcmd = VOLCMD_TONEPORTAMENTO; m[ch].vol = vol - 193; } else\n\t\t\t\t\t// 203-212: Vibrato\n\t\t\t\t\tif ((vol >= 203) && (vol <= 212)) { m[ch].volcmd = VOLCMD_VIBRATOSPEED; m[ch].vol = vol - 203; }\n\t\t\t\t\tlastvalue[ch].volcmd = m[ch].volcmd;\n\t\t\t\t\tlastvalue[ch].vol = m[ch].vol;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Reading command/param\n\t\t\tif (chnmask[ch] & 8)\n\t\t\t{\n\t\t\t\tif (i > len - 2) break;\n\t\t\t\tUINT cmd = p[i++];\n\t\t\t\tUINT param = p[i++];\n\t\t\t\tif (ch < m_nChannels)\n\t\t\t\t{\n\t\t\t\t\tif (cmd)\n\t\t\t\t\t{\n\t\t\t\t\t\tm[ch].command = cmd;\n\t\t\t\t\t\tm[ch].param = param;\n\t\t\t\t\t\tS3MConvert(&m[ch], TRUE);\n\t\t\t\t\t\tlastvalue[ch].command = m[ch].command;\n\t\t\t\t\t\tlastvalue[ch].param = m[ch].param;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tfor (UINT ncu=0; ncu<MAX_BASECHANNELS; ncu++)\n\t{\n\t\tif (ncu>=m_nChannels)\n\t\t{\n\t\t\tChnSettings[ncu].nVolume = 64;\n\t\t\tChnSettings[ncu].dwFlags &= ~CHN_MUTE;\n\t\t}\n\t}\n\tm_nMinPeriod = 8;\n\tm_nMaxPeriod = 0xF000;\n\treturn TRUE;\n}\n\n\n#ifndef MODPLUG_NO_FILESAVE\n//#define SAVEITTIMESTAMP\n#ifdef _MSC_VER\n#pragma warning(disable:4100)\n#endif\n\nBOOL CSoundFile::SaveIT(LPCSTR lpszFileName, UINT nPacking)\n//---------------------------------------------------------\n{\n\tDWORD dwPatNamLen, dwChnNamLen;\n\tITFILEHEADER header, writeheader;\n\tITINSTRUMENT iti, writeiti;\n\tITSAMPLESTRUCT itss;\n\tBYTE smpcount[MAX_SAMPLES];\n\tDWORD inspos[MAX_INSTRUMENTS];\n\tDWORD patpos[MAX_PATTERNS];\n\tDWORD smppos[MAX_SAMPLES];\n\tDWORD dwPos = 0, dwHdrPos = 0, dwExtra = 2;\n\tWORD patinfo[4];\n\tBYTE chnmask[64];\n\tBYTE buf[512];\n\tMODCOMMAND lastvalue[64];\n\tFILE *f;\n\n\n\tif ((!lpszFileName) || ((f = fopen(lpszFileName, \"wb\")) == NULL)) return FALSE;\n\tmemset(inspos, 0, sizeof(inspos));\n\tmemset(patpos, 0, sizeof(patpos));\n\tmemset(smppos, 0, sizeof(smppos));\n\t// Writing Header\n\tmemset(&header, 0, sizeof(header));\n\tdwPatNamLen = 0;\n\tdwChnNamLen = 0;\n\theader.id = 0x4D504D49; // IMPM\n\tlstrcpyn((char *)header.songname, m_szNames[0], 27);\n\theader.reserved1 = 0x1004;\n\theader.ordnum = 0;\n\twhile ((header.ordnum < MAX_ORDERS) && (Order[header.ordnum] < 0xFF)) header.ordnum++;\n\tif (header.ordnum < MAX_ORDERS) Order[header.ordnum++] = 0xFF;\n\theader.insnum = m_nInstruments;\n\theader.smpnum = m_nSamples;\n\theader.patnum = MAX_PATTERNS;\n\twhile ((header.patnum > 0) && (!Patterns[header.patnum-1])) header.patnum--;\n\theader.cwtv = 0x217;\n\theader.cmwt = 0x200;\n\theader.flags = 0x0001;\n\theader.special = 0x0006;\n\tif (m_nInstruments) header.flags |= 0x04;\n\tif (m_dwSongFlags & SONG_LINEARSLIDES) header.flags |= 0x08;\n\tif (m_dwSongFlags & SONG_ITOLDEFFECTS) header.flags |= 0x10;\n\tif (m_dwSongFlags & SONG_ITCOMPATMODE) header.flags |= 0x20;\n\tif (m_dwSongFlags & SONG_EXFILTERRANGE) header.flags |= 0x1000;\n\theader.globalvol = m_nDefaultGlobalVolume >> 1;\n\theader.mv = m_nSongPreAmp;\n\t// clip song pre-amp values (between 0x20 and 0x7f)\n\tif (header.mv < 0x20) header.mv = 0x20;\n\tif (header.mv > 0x7F) header.mv = 0x7F;\n\theader.speed = m_nDefaultSpeed;\n\theader.tempo = m_nDefaultTempo;\n\theader.sep = m_nStereoSeparation;\n\tdwHdrPos = sizeof(header) + header.ordnum;\n\t// Channel Pan and Volume\n\tmemset(header.chnpan, 0xFF, 64);\n\tmemset(header.chnvol, 64, 64);\n\tfor (UINT ich=0; ich<m_nChannels; ich++)\n\t{\n\t\theader.chnpan[ich] = ChnSettings[ich].nPan >> 2;\n\t\tif (ChnSettings[ich].dwFlags & CHN_SURROUND) header.chnpan[ich] = 100;\n\t\theader.chnvol[ich] = ChnSettings[ich].nVolume;\n\t\tif (ChnSettings[ich].dwFlags & CHN_MUTE) header.chnpan[ich] |= 0x80;\n\t\tif (ChnSettings[ich].szName[0])\n\t\t{\n\t\t\tdwChnNamLen = (ich+1) * MAX_CHANNELNAME;\n\t\t}\n\t}\n\tif (dwChnNamLen) dwExtra += dwChnNamLen + 8;\n#ifdef SAVEITTIMESTAMP\n\tdwExtra += 8; // Time Stamp\n#endif\n\tif (m_dwSongFlags & SONG_EMBEDMIDICFG)\n\t{\n\t\theader.flags |= 0x80;\n\t\theader.special |= 0x08;\n\t\tdwExtra += sizeof(MODMIDICFG);\n\t}\n\t// Pattern Names\n\tif ((m_nPatternNames) && (m_lpszPatternNames))\n\t{\n\t\tdwPatNamLen = m_nPatternNames * MAX_PATTERNNAME;\n\t\twhile ((dwPatNamLen >= MAX_PATTERNNAME) && (!m_lpszPatternNames[dwPatNamLen-MAX_PATTERNNAME])) dwPatNamLen -= MAX_PATTERNNAME;\n\t\tif (dwPatNamLen < MAX_PATTERNNAME) dwPatNamLen = 0;\n\t\tif (dwPatNamLen) dwExtra += dwPatNamLen + 8;\n\t}\n\t// Mix Plugins\n\tdwExtra += SaveMixPlugins(NULL, TRUE);\n\t// Comments\n\tif (m_lpszSongComments)\n\t{\n\t\theader.special |= 1;\n\t\theader.msglength = strlen(m_lpszSongComments)+1;\n\t\theader.msgoffset = dwHdrPos + dwExtra + header.insnum*4 + header.patnum*4 + header.smpnum*4;\n\t}\n\t// Write file header\n\tmemcpy(&writeheader, &header, sizeof(header));\n\n\t// Byteswap header information\n\twriteheader.id = bswapLE32(writeheader.id);\n\twriteheader.reserved1 = bswapLE16(writeheader.reserved1);\n\twriteheader.ordnum = bswapLE16(writeheader.ordnum);\n\twriteheader.insnum = bswapLE16(writeheader.insnum);\n\twriteheader.smpnum = bswapLE16(writeheader.smpnum);\n\twriteheader.patnum = bswapLE16(writeheader.patnum);\n\twriteheader.cwtv = bswapLE16(writeheader.cwtv);\n\twriteheader.cmwt = bswapLE16(writeheader.cmwt);\n\twriteheader.flags = bswapLE16(writeheader.flags);\n\twriteheader.special = bswapLE16(writeheader.special);\n\twriteheader.msglength = bswapLE16(writeheader.msglength);\n\twriteheader.msgoffset = bswapLE32(writeheader.msgoffset);\n\twriteheader.reserved2 = bswapLE32(writeheader.reserved2);\n\n\tfwrite(&writeheader, 1, sizeof(writeheader), f);\n\n\tfwrite(Order, 1, header.ordnum, f);\n\tif (header.insnum) fwrite(inspos, 4, header.insnum, f);\n\tif (header.smpnum) fwrite(smppos, 4, header.smpnum, f);\n\tif (header.patnum) fwrite(patpos, 4, header.patnum, f);\n\t// Writing editor history information\n\t{\n#ifdef SAVEITTIMESTAMP\n\t\tSYSTEMTIME systime;\n\t\tFILETIME filetime;\n\t\tWORD timestamp[4];\n\t\tWORD nInfoEx = 1;\n\t\tmemset(timestamp, 0, sizeof(timestamp));\n\t\tfwrite(&nInfoEx, 1, 2, f);\n\t\tGetSystemTime(&systime);\n\t\tSystemTimeToFileTime(&systime, &filetime);\n\t\tFileTimeToDosDateTime(&filetime, &timestamp[0], &timestamp[1]);\n\t\tfwrite(timestamp, 1, 8, f);\n#else\n\t\tWORD nInfoEx = 0;\n\t\tfwrite(&nInfoEx, 1, 2, f);\n#endif\n\t}\n\t// Writing midi cfg\n\tif (header.flags & 0x80)\n\t{\n\t\tfwrite(&m_MidiCfg, 1, sizeof(MODMIDICFG), f);\n\t}\n\t// Writing pattern names\n\tif (dwPatNamLen)\n\t{\n\t\tDWORD d = bswapLE32(0x4d414e50);\n\t\tUINT len= bswapLE32(dwPatNamLen);\n\t\tfwrite(&d, 1, 4, f);\n\t\tfwrite(&len, 1, 4, f);\n\t\tfwrite(m_lpszPatternNames, 1, dwPatNamLen, f);\n\t}\n\t// Writing channel Names\n\tif (dwChnNamLen)\n\t{\n\t\tDWORD d = bswapLE32(0x4d414e43);\n\t\tUINT len= bswapLE32(dwChnNamLen);\n\t\tfwrite(&d, 1, 4, f);\n\t\tfwrite(&len, 1, 4, f);\n\t\tUINT nChnNames = dwChnNamLen / MAX_CHANNELNAME;\n\t\tfor (UINT inam=0; inam<nChnNames; inam++)\n\t\t{\n\t\t\tfwrite(ChnSettings[inam].szName, 1, MAX_CHANNELNAME, f);\n\t\t}\n\t}\n\t// Writing mix plugins info\n\tSaveMixPlugins(f, FALSE);\n\t// Writing song message\n\tdwPos = dwHdrPos + dwExtra + (header.insnum + header.smpnum + header.patnum) * 4;\n\tif (header.special & 1)\n\t{\n\t\tdwPos += strlen(m_lpszSongComments) + 1;\n\t\tfwrite(m_lpszSongComments, 1, strlen(m_lpszSongComments)+1, f);\n\t}\n\t// Writing instruments\n\tfor (UINT nins=1; nins<=header.insnum; nins++)\n\t{\n\t\tmemset(&iti, 0, sizeof(iti));\n\t\titi.id = 0x49504D49;\t// \"IMPI\"\n\t\titi.trkvers = 0x211;\n\t\tif (Headers[nins])\n\t\t{\n\t\t\tINSTRUMENTHEADER *penv = Headers[nins];\n\t\t\tmemset(smpcount, 0, sizeof(smpcount));\n\t\t\tmemcpy(iti.filename, penv->filename, 12);\n\t\t\tmemcpy(iti.name, penv->name, 26);\n\t\t\titi.mbank = penv->wMidiBank;\n\t\t\titi.mpr = penv->nMidiProgram;\n\t\t\titi.mch = penv->nMidiChannel;\n\t\t\titi.nna = penv->nNNA;\n\t\t\titi.dct = penv->nDCT;\n\t\t\titi.dca = penv->nDNA;\n\t\t\titi.fadeout = penv->nFadeOut >> 5;\n\t\t\titi.pps = penv->nPPS;\n\t\t\titi.ppc = penv->nPPC;\n\t\t\titi.gbv = (BYTE)(penv->nGlobalVol << 1);\n\t\t\titi.dfp = (BYTE)penv->nPan >> 2;\n\t\t\tif (!(penv->dwFlags & ENV_SETPANNING)) iti.dfp |= 0x80;\n\t\t\titi.rv = penv->nVolSwing;\n\t\t\titi.rp = penv->nPanSwing;\n\t\t\titi.ifc = penv->nIFC;\n\t\t\titi.ifr = penv->nIFR;\n\t\t\titi.nos = 0;\n\t\t\tfor (UINT i=0; i<NOTE_MAX; i++) if (penv->Keyboard[i] < MAX_SAMPLES)\n\t\t\t{\n\t\t\t\tUINT smp = penv->Keyboard[i];\n\t\t\t\tif ((smp) && (!smpcount[smp]))\n\t\t\t\t{\n\t\t\t\t\tsmpcount[smp] = 1;\n\t\t\t\t\titi.nos++;\n\t\t\t\t}\n\t\t\t\titi.keyboard[i*2] = penv->NoteMap[i] - 1;\n\t\t\t\titi.keyboard[i*2+1] = smp;\n\t\t\t}\n\t\t\t// Writing Volume envelope\n\t\t\tif (penv->dwFlags & ENV_VOLUME) iti.volenv.flags |= 0x01;\n\t\t\tif (penv->dwFlags & ENV_VOLLOOP) iti.volenv.flags |= 0x02;\n\t\t\tif (penv->dwFlags & ENV_VOLSUSTAIN) iti.volenv.flags |= 0x04;\n\t\t\tif (penv->dwFlags & ENV_VOLCARRY) iti.volenv.flags |= 0x08;\n\t\t\titi.volenv.num = (BYTE)penv->nVolEnv;\n\t\t\titi.volenv.lpb = (BYTE)penv->nVolLoopStart;\n\t\t\titi.volenv.lpe = (BYTE)penv->nVolLoopEnd;\n\t\t\titi.volenv.slb = penv->nVolSustainBegin;\n\t\t\titi.volenv.sle = penv->nVolSustainEnd;\n\t\t\t// Writing Panning envelope\n\t\t\tif (penv->dwFlags & ENV_PANNING) iti.panenv.flags |= 0x01;\n\t\t\tif (penv->dwFlags & ENV_PANLOOP) iti.panenv.flags |= 0x02;\n\t\t\tif (penv->dwFlags & ENV_PANSUSTAIN) iti.panenv.flags |= 0x04;\n\t\t\tif (penv->dwFlags & ENV_PANCARRY) iti.panenv.flags |= 0x08;\n\t\t\titi.panenv.num = (BYTE)penv->nPanEnv;\n\t\t\titi.panenv.lpb = (BYTE)penv->nPanLoopStart;\n\t\t\titi.panenv.lpe = (BYTE)penv->nPanLoopEnd;\n\t\t\titi.panenv.slb = penv->nPanSustainBegin;\n\t\t\titi.panenv.sle = penv->nPanSustainEnd;\n\t\t\t// Writing Pitch Envelope\n\t\t\tif (penv->dwFlags & ENV_PITCH) iti.pitchenv.flags |= 0x01;\n\t\t\tif (penv->dwFlags & ENV_PITCHLOOP) iti.pitchenv.flags |= 0x02;\n\t\t\tif (penv->dwFlags & ENV_PITCHSUSTAIN) iti.pitchenv.flags |= 0x04;\n\t\t\tif (penv->dwFlags & ENV_PITCHCARRY) iti.pitchenv.flags |= 0x08;\n\t\t\tif (penv->dwFlags & ENV_FILTER) iti.pitchenv.flags |= 0x80;\n\t\t\titi.pitchenv.num = (BYTE)penv->nPitchEnv;\n\t\t\titi.pitchenv.lpb = (BYTE)penv->nPitchLoopStart;\n\t\t\titi.pitchenv.lpe = (BYTE)penv->nPitchLoopEnd;\n\t\t\titi.pitchenv.slb = (BYTE)penv->nPitchSustainBegin;\n\t\t\titi.pitchenv.sle = (BYTE)penv->nPitchSustainEnd;\n\t\t\t// Writing Envelopes data\n\t\t\tfor (UINT ev=0; ev<25; ev++)\n\t\t\t{\n\t\t\t\titi.volenv.data[ev*3] = penv->VolEnv[ev];\n\t\t\t\titi.volenv.data[ev*3+1] = penv->VolPoints[ev] & 0xFF;\n\t\t\t\titi.volenv.data[ev*3+2] = penv->VolPoints[ev] >> 8;\n\t\t\t\titi.panenv.data[ev*3] = penv->PanEnv[ev] - 32;\n\t\t\t\titi.panenv.data[ev*3+1] = penv->PanPoints[ev] & 0xFF;\n\t\t\t\titi.panenv.data[ev*3+2] = penv->PanPoints[ev] >> 8;\n\t\t\t\titi.pitchenv.data[ev*3] = penv->PitchEnv[ev] - 32;\n\t\t\t\titi.pitchenv.data[ev*3+1] = penv->PitchPoints[ev] & 0xFF;\n\t\t\t\titi.pitchenv.data[ev*3+2] = penv->PitchPoints[ev] >> 8;\n\t\t\t}\n\t\t} else\n\t\t// Save Empty Instrument\n\t\t{\n\t\t\tfor (UINT i=0; i<NOTE_MAX; i++) iti.keyboard[i*2] = i;\n\t\t\titi.ppc = 5*12;\n\t\t\titi.gbv = 128;\n\t\t\titi.dfp = 0x20;\n\t\t\titi.ifc = 0xFF;\n\t\t}\n\t\tif (!iti.nos) iti.trkvers = 0;\n\t\t// Writing instrument\n\t\tinspos[nins-1] = dwPos;\n\t\tdwPos += sizeof(ITINSTRUMENT);\n\n\t\tmemcpy(&writeiti, &iti, sizeof(ITINSTRUMENT));\n\n\t\twriteiti.fadeout = bswapLE16(writeiti.fadeout);\n\t\twriteiti.id = bswapLE32(writeiti.id);\n\t\twriteiti.trkvers = bswapLE16(writeiti.trkvers);\n\t\twriteiti.mbank = bswapLE16(writeiti.mbank);\n\n\t\tfwrite(&writeiti, 1, sizeof(ITINSTRUMENT), f);\n\t}\n\t// Writing sample headers\n\tmemset(&itss, 0, sizeof(itss));\n\tfor (UINT hsmp=0; hsmp<header.smpnum; hsmp++)\n\t{\n\t\tsmppos[hsmp] = dwPos;\n\t\tdwPos += sizeof(ITSAMPLESTRUCT);\n\t\tfwrite(&itss, 1, sizeof(ITSAMPLESTRUCT), f);\n\t}\n\t// Writing Patterns\n\tfor (UINT npat=0; npat<header.patnum; npat++)\n\t{\n\t\tDWORD dwPatPos = dwPos;\n\t\tUINT len;\n\t\tif (!Patterns[npat]) continue;\n\t\tpatpos[npat] = dwPos;\n\t\tpatinfo[0] = 0;\n\t\tpatinfo[1] = bswapLE16(PatternSize[npat]);\n\t\tpatinfo[2] = 0;\n\t\tpatinfo[3] = 0;\n\t\t// Check for empty pattern\n\t\tif (PatternSize[npat] == 64)\n\t\t{\n\t\t\tMODCOMMAND *pzc = Patterns[npat];\n\t\t\tUINT iz, nz = PatternSize[npat] * m_nChannels;\n\t\t\tfor (iz=0; iz<nz; iz++)\n\t\t\t{\n\t\t\t\tif ((pzc[iz].note) || (pzc[iz].instr)\n\t\t\t\t || (pzc[iz].volcmd) || (pzc[iz].command)) break;\n\t\t\t}\n\t\t\tif (iz == nz)\n\t\t\t{\n\t\t\t\tpatpos[npat] = 0;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tfwrite(patinfo, 8, 1, f);\n\t\tdwPos += 8;\n\t\tmemset(chnmask, 0xFF, sizeof(chnmask));\n\t\tmemset(lastvalue, 0, sizeof(lastvalue));\n\t\tMODCOMMAND *m = Patterns[npat];\n\t\tfor (UINT row=0; row<PatternSize[npat]; row++)\n\t\t{\n\t\t\tlen = 0;\n\t\t\tfor (UINT ch=0; ch<m_nChannels; ch++, m++)\n\t\t\t{\n\t\t\t\tBYTE b = 0;\n\t\t\t\tUINT command = m->command;\n\t\t\t\tUINT param = m->param;\n\t\t\t\tUINT vol = 0xFF;\n\t\t\t\tUINT note = m->note;\n\t\t\t\tif (note) b |= 1;\n\t\t\t\tif ((note) && (note < 0x80)) note--; // 0xfe->0x80 --Toad\n\t\t\t\tif (m->instr) b |= 2;\n\t\t\t\tif (m->volcmd)\n\t\t\t\t{\n\t\t\t\t\tUINT volcmd = m->volcmd;\n\t\t\t\t\tswitch(volcmd)\n\t\t\t\t\t{\n\t\t\t\t\tcase VOLCMD_VOLUME:\t\t\tvol = m->vol; if (vol > 64) vol = 64; break;\n\t\t\t\t\tcase VOLCMD_PANNING:\t\tvol = m->vol + 128; if (vol > 192) vol = 192; break;\n\t\t\t\t\tcase VOLCMD_VOLSLIDEUP:\t\tvol = 85 + ConvertVolParam(m->vol); break;\n\t\t\t\t\tcase VOLCMD_VOLSLIDEDOWN:\tvol = 95 + ConvertVolParam(m->vol); break;\n\t\t\t\t\tcase VOLCMD_FINEVOLUP:\t\tvol = 65 + ConvertVolParam(m->vol); break;\n\t\t\t\t\tcase VOLCMD_FINEVOLDOWN:\tvol = 75 + ConvertVolParam(m->vol); break;\n\t\t\t\t\tcase VOLCMD_VIBRATOSPEED:\tvol = 203 + ConvertVolParam(m->vol); break;\n\t\t\t\t\tcase VOLCMD_VIBRATO:\t\tvol = 203; break;\n\t\t\t\t\tcase VOLCMD_TONEPORTAMENTO:\tvol = 193 + ConvertVolParam(m->vol); break;\n\t\t\t\t\tcase VOLCMD_PORTADOWN:\t\tvol = 105 + ConvertVolParam(m->vol); break;\n\t\t\t\t\tcase VOLCMD_PORTAUP:\t\tvol = 115 + ConvertVolParam(m->vol); break;\n\t\t\t\t\tdefault:\t\t\t\t\tvol = 0xFF;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (vol != 0xFF) b |= 4;\n\t\t\t\tif (command)\n\t\t\t\t{\n\t\t\t\t\tS3MSaveConvert(&command, &param, TRUE);\n\t\t\t\t\tif (command) b |= 8;\n\t\t\t\t}\n\t\t\t\t// Packing information\n\t\t\t\tif (b)\n\t\t\t\t{\n\t\t\t\t\t// Same note ?\n\t\t\t\t\tif (b & 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tif ((note == lastvalue[ch].note) && (lastvalue[ch].volcmd & 1))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tb &= ~1;\n\t\t\t\t\t\t\tb |= 0x10;\n\t\t\t\t\t\t} else\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlastvalue[ch].note = note;\n\t\t\t\t\t\t\tlastvalue[ch].volcmd |= 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Same instrument ?\n\t\t\t\t\tif (b & 2)\n\t\t\t\t\t{\n\t\t\t\t\t\tif ((m->instr == lastvalue[ch].instr) && (lastvalue[ch].volcmd & 2))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tb &= ~2;\n\t\t\t\t\t\t\tb |= 0x20;\n\t\t\t\t\t\t} else\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlastvalue[ch].instr = m->instr;\n\t\t\t\t\t\t\tlastvalue[ch].volcmd |= 2;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Same volume column byte ?\n\t\t\t\t\tif (b & 4)\n\t\t\t\t\t{\n\t\t\t\t\t\tif ((vol == lastvalue[ch].vol) && (lastvalue[ch].volcmd & 4))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tb &= ~4;\n\t\t\t\t\t\t\tb |= 0x40;\n\t\t\t\t\t\t} else\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlastvalue[ch].vol = vol;\n\t\t\t\t\t\t\tlastvalue[ch].volcmd |= 4;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Same command / param ?\n\t\t\t\t\tif (b & 8)\n\t\t\t\t\t{\n\t\t\t\t\t\tif ((command == lastvalue[ch].command) && (param == lastvalue[ch].param) && (lastvalue[ch].volcmd & 8))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tb &= ~8;\n\t\t\t\t\t\t\tb |= 0x80;\n\t\t\t\t\t\t} else\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlastvalue[ch].command = command;\n\t\t\t\t\t\t\tlastvalue[ch].param = param;\n\t\t\t\t\t\t\tlastvalue[ch].volcmd |= 8;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (b != chnmask[ch])\n\t\t\t\t\t{\n\t\t\t\t\t\tchnmask[ch] = b;\n\t\t\t\t\t\tbuf[len++] = (ch+1) | 0x80;\n\t\t\t\t\t\tbuf[len++] = b;\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tbuf[len++] = ch+1;\n\t\t\t\t\t}\n\t\t\t\t\tif (b & 1) buf[len++] = note;\n\t\t\t\t\tif (b & 2) buf[len++] = m->instr;\n\t\t\t\t\tif (b & 4) buf[len++] = vol;\n\t\t\t\t\tif (b & 8)\n\t\t\t\t\t{\n\t\t\t\t\t\tbuf[len++] = command;\n\t\t\t\t\t\tbuf[len++] = param;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbuf[len++] = 0;\n\t\t\tdwPos += len;\n\t\t\tpatinfo[0] += len;\n\t\t\tfwrite(buf, 1, len, f);\n\t\t}\n\t\tfseek(f, dwPatPos, SEEK_SET);\n\t\tpatinfo[0] = bswapLE16(patinfo[0]); // byteswap -- Toad\n\t\tfwrite(patinfo, 8, 1, f);\n\t\tfseek(f, dwPos, SEEK_SET);\n\t}\n\t// Writing Sample Data\n\tfor (UINT nsmp=1; nsmp<=header.smpnum; nsmp++)\n\t{\n\t\tMODINSTRUMENT *psmp = &Ins[nsmp];\n\t\tmemset(&itss, 0, sizeof(itss));\n\t\tmemcpy(itss.filename, psmp->name, 12);\n\t\tmemcpy(itss.name, m_szNames[nsmp], 26);\n\t\titss.id = 0x53504D49;\n\t\titss.gvl = (BYTE)psmp->nGlobalVol;\n\t\tif (m_nInstruments)\n\t\t{\n\t\t\tfor (UINT iu=1; iu<=m_nInstruments; iu++) if (Headers[iu])\n\t\t\t{\n\t\t\t\tINSTRUMENTHEADER *penv = Headers[iu];\n\t\t\t\tfor (UINT ju=0; ju<128; ju++) if (penv->Keyboard[ju] == nsmp)\n\t\t\t\t{\n\t\t\t\t\titss.flags = 0x01;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t} else\n\t\t{\n\t\t\titss.flags = 0x01;\n\t\t}\n\t\tif (psmp->uFlags & CHN_LOOP) itss.flags |= 0x10;\n\t\tif (psmp->uFlags & CHN_SUSTAINLOOP) itss.flags |= 0x20;\n\t\tif (psmp->uFlags & CHN_PINGPONGLOOP) itss.flags |= 0x40;\n\t\tif (psmp->uFlags & CHN_PINGPONGSUSTAIN) itss.flags |= 0x80;\n\t\titss.C5Speed = psmp->nC4Speed;\n\t\tif (!itss.C5Speed) // if no C5Speed assume it is XM Sample\n\t\t{\n\t\t\tUINT period;\n\n\t\t\t/**\n\t\t\t * C5 note => number 61, but in XM samples:\n\t\t\t * RealNote = Note + RelativeTone\n\t\t\t */\n\t\t\tperiod = GetPeriodFromNote(61+psmp->RelativeTone, psmp->nFineTune, 0);\n\n\t\t\tif (period)\n\t\t\t\titss.C5Speed = GetFreqFromPeriod(period, 0, 0);\n\t\t\t/**\n\t\t\t * If it didn`t work, it may not be a XM file;\n\t\t\t * so put the default C5Speed, 8363Hz.\n\t\t\t */\n\t \t\tif (!itss.C5Speed) itss.C5Speed = 8363;\n\t\t}\n\n\t\titss.length = psmp->nLength;\n\t\titss.loopbegin = psmp->nLoopStart;\n\t\titss.loopend = psmp->nLoopEnd;\n\t\titss.susloopbegin = psmp->nSustainStart;\n\t\titss.susloopend = psmp->nSustainEnd;\n\t\titss.vol = psmp->nVolume >> 2;\n\t\titss.dfp = psmp->nPan >> 2;\n\t\titss.vit = autovibxm2it[psmp->nVibType & 7];\n\t\titss.vis = psmp->nVibRate;\n\t\titss.vid = psmp->nVibDepth;\n\t\titss.vir = (psmp->nVibSweep < 64) ? psmp->nVibSweep * 4 : 255;\n\t\tif (psmp->uFlags & CHN_PANNING) itss.dfp |= 0x80;\n\t\tif ((psmp->pSample) && (psmp->nLength)) itss.cvt = 0x01;\n\t\tUINT flags = RS_PCM8S;\n#ifndef NO_PACKING\n\t\tif (nPacking)\n\t\t{\n\t\t\tif ((!(psmp->uFlags & (CHN_16BIT|CHN_STEREO)))\n\t\t\t && (CanPackSample((char *)psmp->pSample, psmp->nLength, nPacking)))\n\t\t\t{\n\t\t\t\tflags = RS_ADPCM4;\n\t\t\t\titss.cvt = 0xFF;\n\t\t\t}\n\t\t} else\n#endif // NO_PACKING\n\t\t{\n\t\t\tif (psmp->uFlags & CHN_STEREO)\n\t\t\t{\n\t\t\t\tflags = RS_STPCM8S;\n\t\t\t\titss.flags |= 0x04;\n\t\t\t}\n\t\t\tif (psmp->uFlags & CHN_16BIT)\n\t\t\t{\n\t\t\t\titss.flags |= 0x02;\n\t\t\t\tflags = (psmp->uFlags & CHN_STEREO) ? RS_STPCM16S : RS_PCM16S;\n\t\t\t}\n\t\t}\n\t\titss.samplepointer = dwPos;\n\t\tfseek(f, smppos[nsmp-1], SEEK_SET);\n\n\t\titss.id = bswapLE32(itss.id);\n\t\titss.length = bswapLE32(itss.length);\n\t\titss.loopbegin = bswapLE32(itss.loopbegin);\n\t\titss.loopend = bswapLE32(itss.loopend);\n\t\titss.C5Speed = bswapLE32(itss.C5Speed);\n\t\titss.susloopbegin = bswapLE32(itss.susloopbegin);\n\t\titss.susloopend = bswapLE32(itss.susloopend);\n\t\titss.samplepointer = bswapLE32(itss.samplepointer);\n\n\t\tfwrite(&itss, 1, sizeof(ITSAMPLESTRUCT), f);\n\t\tfseek(f, dwPos, SEEK_SET);\n\t\tif ((psmp->pSample) && (psmp->nLength))\n\t\t{\n\t\t\tdwPos += WriteSample(f, psmp, flags);\n\t\t}\n\t}\n\t// Updating offsets\n\tfseek(f, dwHdrPos, SEEK_SET);\n\n\t/* <Toad> Now we can byteswap them ;-) */\n\tUINT WW;\n\tUINT WX;\n\tWX = (UINT)header.insnum;\n\tWX <<= 2;\n\tfor (WW=0; WW < (WX>>2); WW++)\n\t       inspos[WW] = bswapLE32(inspos[WW]);\n\n\tWX = (UINT)header.smpnum;\n\tWX <<= 2;\n\tfor (WW=0; WW < (WX>>2); WW++)\n\t       smppos[WW] = bswapLE32(smppos[WW]);\n\n\tWX=(UINT)header.patnum;\n\tWX <<= 2;\n\tfor (WW=0; WW < (WX>>2); WW++)\n\t       patpos[WW] = bswapLE32(patpos[WW]);\n\n\tif (header.insnum) fwrite(inspos, 4, header.insnum, f);\n\tif (header.smpnum) fwrite(smppos, 4, header.smpnum, f);\n\tif (header.patnum) fwrite(patpos, 4, header.patnum, f);\n\tfclose(f);\n\treturn TRUE;\n}\n\n#ifdef _MSC_VER\n//#pragma warning(default:4100)\n#endif\n#endif // MODPLUG_NO_FILESAVE\n\n//////////////////////////////////////////////////////////////////////////////\n// IT 2.14 compression\n\nstatic DWORD ITReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n)\n//-----------------------------------------------------------------\n{\n\tDWORD retval = 0;\n\tUINT i = n;\n\n\t// explicit if read 0 bits, then return 0\n\tif (i == 0)\n\t\treturn(0);\n\n\tif (n > 0)\n\t{\n\t\tdo\n\t\t{\n\t\t\tif (!bitnum)\n\t\t\t{\n\t\t\t\tbitbuf = *ibuf++;\n\t\t\t\tbitnum = 8;\n\t\t\t}\n\t\t\tretval >>= 1;\n\t\t\tretval |= bitbuf << 31;\n\t\t\tbitbuf >>= 1;\n\t\t\tbitnum--;\n\t\t\ti--;\n\t\t} while (i);\n\t\ti = n;\n\t}\n\treturn (retval >> (32-i));\n}\n\n#define IT215_SUPPORT\nvoid ITUnpack8Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215)\n//-------------------------------------------------------------------------------------------\n{\n\tsigned char *pDst = pSample;\n\tLPBYTE pSrc = lpMemFile;\n\tDWORD wCount = 0;\n\tDWORD bitbuf = 0;\n\tUINT bitnum = 0;\n\tBYTE bLeft = 0, bTemp = 0, bTemp2 = 0;\n\n\twhile (dwLen)\n\t{\n\t\tif (!wCount)\n\t\t{\n\t\t\twCount = 0x8000;\n\t\t\tpSrc += 2;\n\t\t\tbLeft = 9;\n\t\t\tbTemp = bTemp2 = 0;\n\t\t\tbitbuf = bitnum = 0;\n\t\t}\n\t\tDWORD d = wCount;\n\t\tif (d > dwLen) d = dwLen;\n\t\t// Unpacking\n\t\tDWORD dwPos = 0;\n\t\tdo\n\t\t{\n\t\t\tWORD wBits = (WORD)ITReadBits(bitbuf, bitnum, pSrc, bLeft);\n\t\t\tif (bLeft < 7)\n\t\t\t{\n\t\t\t\tDWORD i = 1 << (bLeft-1);\n\t\t\t\tDWORD j = wBits & 0xFFFF;\n\t\t\t\tif (i != j) goto UnpackByte;\n\t\t\t\twBits = (WORD)(ITReadBits(bitbuf, bitnum, pSrc, 3) + 1) & 0xFF;\n\t\t\t\tbLeft = ((BYTE)wBits < bLeft) ? (BYTE)wBits : (BYTE)((wBits+1) & 0xFF);\n\t\t\t\tgoto Next;\n\t\t\t}\n\t\t\tif (bLeft < 9)\n\t\t\t{\n\t\t\t\tWORD i = (0xFF >> (9 - bLeft)) + 4;\n\t\t\t\tWORD j = i - 8;\n\t\t\t\tif ((wBits <= j) || (wBits > i)) goto UnpackByte;\n\t\t\t\twBits -= j;\n\t\t\t\tbLeft = ((BYTE)(wBits & 0xFF) < bLeft) ? (BYTE)(wBits & 0xFF) : (BYTE)((wBits+1) & 0xFF);\n\t\t\t\tgoto Next;\n\t\t\t}\n\t\t\tif (bLeft >= 10) goto SkipByte;\n\t\t\tif (wBits >= 256)\n\t\t\t{\n\t\t\t\tbLeft = (BYTE)(wBits + 1) & 0xFF;\n\t\t\t\tgoto Next;\n\t\t\t}\n\t\tUnpackByte:\n\t\t\tif (bLeft < 8)\n\t\t\t{\n\t\t\t\tBYTE shift = 8 - bLeft;\n\t\t\t\tsigned char c = (signed char)(wBits << shift);\n\t\t\t\tc >>= shift;\n\t\t\t\twBits = (WORD)c;\n\t\t\t}\n\t\t\twBits += bTemp;\n\t\t\tbTemp = (BYTE)wBits;\n\t\t\tbTemp2 += bTemp;\n#ifdef IT215_SUPPORT\n\t\t\tpDst[dwPos] = (b215) ? bTemp2 : bTemp;\n#else\n\t\t\tpDst[dwPos] = bTemp;\n#endif\n\t\tSkipByte:\n\t\t\tdwPos++;\n\t\tNext:\n\t\t\tif (pSrc >= lpMemFile+dwMemLength+1) return;\n\t\t} while (dwPos < d);\n\t\t// Move On\n\t\twCount -= d;\n\t\tdwLen -= d;\n\t\tpDst += d;\n\t}\n}\n\n\nvoid ITUnpack16Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215)\n//--------------------------------------------------------------------------------------------\n{\n\tsigned short *pDst = (signed short *)pSample;\n\tLPBYTE pSrc = lpMemFile;\n\tDWORD wCount = 0;\n\tDWORD bitbuf = 0;\n\tUINT bitnum = 0;\n\tBYTE bLeft = 0;\n\tsigned short wTemp = 0, wTemp2 = 0;\n\n\twhile (dwLen)\n\t{\n\t\tif (!wCount)\n\t\t{\n\t\t\twCount = 0x4000;\n\t\t\tpSrc += 2;\n\t\t\tbLeft = 17;\n\t\t\twTemp = wTemp2 = 0;\n\t\t\tbitbuf = bitnum = 0;\n\t\t}\n\t\tDWORD d = wCount;\n\t\tif (d > dwLen) d = dwLen;\n\t\t// Unpacking\n\t\tDWORD dwPos = 0;\n\t\tdo\n\t\t{\n\t\t\tDWORD dwBits = ITReadBits(bitbuf, bitnum, pSrc, bLeft);\n\t\t\tif (bLeft < 7)\n\t\t\t{\n\t\t\t\tDWORD i = 1 << (bLeft-1);\n\t\t\t\tDWORD j = dwBits;\n\t\t\t\tif (i != j) goto UnpackByte;\n\t\t\t\tdwBits = ITReadBits(bitbuf, bitnum, pSrc, 4) + 1;\n\t\t\t\tbLeft = ((BYTE)(dwBits & 0xFF) < bLeft) ? (BYTE)(dwBits & 0xFF) : (BYTE)((dwBits+1) & 0xFF);\n\t\t\t\tgoto Next;\n\t\t\t}\n\t\t\tif (bLeft < 17)\n\t\t\t{\n\t\t\t\tDWORD i = (0xFFFF >> (17 - bLeft)) + 8;\n\t\t\t\tDWORD j = (i - 16) & 0xFFFF;\n\t\t\t\tif ((dwBits <= j) || (dwBits > (i & 0xFFFF))) goto UnpackByte;\n\t\t\t\tdwBits -= j;\n\t\t\t\tbLeft = ((BYTE)(dwBits & 0xFF) < bLeft) ? (BYTE)(dwBits & 0xFF) : (BYTE)((dwBits+1) & 0xFF);\n\t\t\t\tgoto Next;\n\t\t\t}\n\t\t\tif (bLeft >= 18) goto SkipByte;\n\t\t\tif (dwBits >= 0x10000)\n\t\t\t{\n\t\t\t\tbLeft = (BYTE)(dwBits + 1) & 0xFF;\n\t\t\t\tgoto Next;\n\t\t\t}\n\t\tUnpackByte:\n\t\t\tif (bLeft < 16)\n\t\t\t{\n\t\t\t\tBYTE shift = 16 - bLeft;\n\t\t\t\tsigned short c = (signed short)(dwBits << shift);\n\t\t\t\tc >>= shift;\n\t\t\t\tdwBits = (DWORD)c;\n\t\t\t}\n\t\t\tdwBits += wTemp;\n\t\t\twTemp = (signed short)dwBits;\n\t\t\twTemp2 += wTemp;\n#ifdef IT215_SUPPORT\n\t\t\tpDst[dwPos] = (b215) ? wTemp2 : wTemp;\n#else\n\t\t\tpDst[dwPos] = wTemp;\n#endif\n\t\tSkipByte:\n\t\t\tdwPos++;\n\t\tNext:\n\t\t\tif (pSrc >= lpMemFile+dwMemLength+1) return;\n\t\t} while (dwPos < d);\n\t\t// Move On\n\t\twCount -= d;\n\t\tdwLen -= d;\n\t\tpDst += d;\n\t\tif (pSrc >= lpMemFile+dwMemLength) break;\n\t}\n}\n\n\nUINT CSoundFile::SaveMixPlugins(FILE *f, BOOL bUpdate)\n//----------------------------------------------------\n{\n\tDWORD chinfo[64];\n\tCHAR s[32];\n\tDWORD nPluginSize, writeSwapDWORD;\n\tSNDMIXPLUGININFO writePluginInfo;\n\tUINT nTotalSize = 0;\n\tUINT nChInfo = 0;\n\n\tfor (UINT i=0; i<MAX_MIXPLUGINS; i++)\n\t{\n\t\tPSNDMIXPLUGIN p = &m_MixPlugins[i];\n\t\tif ((p->Info.dwPluginId1) || (p->Info.dwPluginId2))\n\t\t{\n\t\t\tnPluginSize = sizeof(SNDMIXPLUGININFO)+4; // plugininfo+4 (datalen)\n\t\t\tif ((p->pMixPlugin) && (bUpdate))\n\t\t\t{\n\t\t\t\tp->pMixPlugin->SaveAllParameters();\n\t\t\t}\n\t\t\tif (p->pPluginData)\n\t\t\t{\n\t\t\t\tnPluginSize += p->nPluginDataSize;\n\t\t\t}\n\t\t\tif (f)\n\t\t\t{\n\t\t\t\ts[0] = 'F';\n\t\t\t\ts[1] = 'X';\n\t\t\t\ts[2] = '0' + (i/10);\n\t\t\t\ts[3] = '0' + (i%10);\n\t\t\t\tfwrite(s, 1, 4, f);\n\t\t\t\twriteSwapDWORD = bswapLE32(nPluginSize);\n\t\t\t\tfwrite(&writeSwapDWORD, 1, 4, f);\n\n\t\t\t\t// Copy Information To Be Written for ByteSwapping\n\t\t\t\tmemcpy(&writePluginInfo, &p->Info, sizeof(SNDMIXPLUGININFO));\n\t\t\t\twritePluginInfo.dwPluginId1 = bswapLE32(p->Info.dwPluginId1);\n\t\t\t\twritePluginInfo.dwPluginId2 = bswapLE32(p->Info.dwPluginId2);\n\t\t\t\twritePluginInfo.dwInputRouting = bswapLE32(p->Info.dwInputRouting);\n\t\t\t\twritePluginInfo.dwOutputRouting = bswapLE32(p->Info.dwOutputRouting);\n\t\t\t\tfor (UINT j=0; j<4; j++)\n\t\t\t\t{\n\t\t\t\t        writePluginInfo.dwReserved[j] = bswapLE32(p->Info.dwReserved[j]);\n\t\t\t\t}\n\n\t\t\t\tfwrite(&writePluginInfo, 1, sizeof(SNDMIXPLUGININFO), f);\n\t\t\t\twriteSwapDWORD = bswapLE32(m_MixPlugins[i].nPluginDataSize);\n\t\t\t\tfwrite(&writeSwapDWORD, 1, 4, f);\n\t\t\t\tif (m_MixPlugins[i].pPluginData)\n\t\t\t\t{\n\t\t\t\t\tfwrite(m_MixPlugins[i].pPluginData, 1, m_MixPlugins[i].nPluginDataSize, f);\n\t\t\t\t}\n\t\t\t}\n\t\t\tnTotalSize += nPluginSize + 8;\n\t\t}\n\t}\n\tfor (UINT j=0; j<m_nChannels; j++)\n\t{\n\t\tif (j < 64)\n\t\t{\n\t\t\tif ((chinfo[j] = ChnSettings[j].nMixPlugin) != 0)\n\t\t\t{\n\t\t\t\tnChInfo = j+1;\n\t\t\t\tchinfo[j] = bswapLE32(chinfo[j]); // inplace BS\n\t\t\t}\n\t\t}\n\t}\n\tif (nChInfo)\n\t{\n\t\tif (f)\n\t\t{\n\t\t\tnPluginSize = bswapLE32(0x58464843);\n\t\t\tfwrite(&nPluginSize, 1, 4, f);\n\t\t\tnPluginSize = nChInfo*4;\n\t\t\twriteSwapDWORD = bswapLE32(nPluginSize);\n\t\t\tfwrite(&writeSwapDWORD, 1, 4, f);\n\t\t\tfwrite(chinfo, 1, nPluginSize, f);\n\t\t}\n\t\tnTotalSize += nChInfo*4 + 8;\n\t}\n\treturn nTotalSize;\n}\n\n\nUINT CSoundFile::LoadMixPlugins(const void *pData, UINT nLen)\n//-----------------------------------------------------------\n{\n\tconst BYTE *p = (const BYTE *)pData;\n\tUINT nPos = 0;\n\n\twhile (nPos+8 < nLen)\n\t{\n\t\tDWORD nPluginSize;\n\t\tUINT nPlugin;\n\n\t\tnPluginSize = bswapLE32(*(DWORD *)(p+nPos+4));\n\t\tif (nPluginSize > nLen-nPos-8) break;;\n\t\tif ((bswapLE32(*(DWORD *)(p+nPos))) == 0x58464843)\n\t\t{\n\t\t\tfor (UINT ch=0; ch<64; ch++) if (ch*4 < nPluginSize)\n\t\t\t{\n\t\t\t\tChnSettings[ch].nMixPlugin = bswapLE32(*(DWORD *)(p+nPos+8+ch*4));\n\t\t\t}\n\t\t} else\n\t\t{\n\t\t\tif ((p[nPos] != 'F') || (p[nPos+1] != 'X')\n\t\t\t || (p[nPos+2] < '0') || (p[nPos+3] < '0'))\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tnPlugin = (p[nPos+2]-'0')*10 + (p[nPos+3]-'0');\n\t\t\tif ((nPlugin < MAX_MIXPLUGINS) && (nPluginSize >= sizeof(SNDMIXPLUGININFO)+4))\n\t\t\t{\n\t\t\t\tDWORD dwExtra = bswapLE32(*(DWORD *)(p+nPos+8+sizeof(SNDMIXPLUGININFO)));\n\t\t\t\tm_MixPlugins[nPlugin].Info = *(const SNDMIXPLUGININFO *)(p+nPos+8);\n\t\t\t\tm_MixPlugins[nPlugin].Info.dwPluginId1 = bswapLE32(m_MixPlugins[nPlugin].Info.dwPluginId1);\n\t\t\t\tm_MixPlugins[nPlugin].Info.dwPluginId2 = bswapLE32(m_MixPlugins[nPlugin].Info.dwPluginId2);\n\t\t\t\tm_MixPlugins[nPlugin].Info.dwInputRouting = bswapLE32(m_MixPlugins[nPlugin].Info.dwInputRouting);\n\t\t\t\tm_MixPlugins[nPlugin].Info.dwOutputRouting = bswapLE32(m_MixPlugins[nPlugin].Info.dwOutputRouting);\n\t\t\t\tfor (UINT j=0; j<4; j++)\n\t\t\t\t{\n\t\t\t\t        m_MixPlugins[nPlugin].Info.dwReserved[j] = bswapLE32(m_MixPlugins[nPlugin].Info.dwReserved[j]);\n\t\t\t\t}\n\t\t\t\tif ((dwExtra) && (dwExtra <= nPluginSize-sizeof(SNDMIXPLUGININFO)-4))\n\t\t\t\t{\n\t\t\t\t\tm_MixPlugins[nPlugin].nPluginDataSize = 0;\n\t\t\t\t\tm_MixPlugins[nPlugin].pPluginData = new signed char [dwExtra];\n\t\t\t\t\tif (m_MixPlugins[nPlugin].pPluginData)\n\t\t\t\t\t{\n\t\t\t\t\t\tm_MixPlugins[nPlugin].nPluginDataSize = dwExtra;\n\t\t\t\t\t\tmemcpy(m_MixPlugins[nPlugin].pPluginData, p+nPos+8+sizeof(SNDMIXPLUGININFO)+4, dwExtra);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tnPos += nPluginSize + 8;\n\t}\n\treturn nPos;\n}\n"
  },
  {
    "path": "contrib/libmodplug/src/load_med.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>,\n *          Adam Goode       <adam@evdebs.org> (endian and char fixes for PPC)\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n//#define MED_LOG\n\n#ifdef MED_LOG\nextern void Log(LPCSTR s, ...);\n#endif\n\n//////////////////////////////////////////////////////////\n// OctaMed MED file support (import only)\n//\n// Lookup table for bpm values.\nstatic const BYTE bpmvals[10] = { 179,164,152,141,131,123,116,110,104,99 };\n\n\n// flags\n#define\tMMD_FLAG_VOLHEX\t\t0x10\n#define MMD_FLAG_8CHANNEL\t0x40 // OctaMED 8 channel song\n// flags2\n#define MMD_FLAG2_BMASK\t\t0x1F\n#define MMD_FLAG2_BPM\t\t0x20\n\n\n// generic MMD tags\n#define\tMMDTAG_END\t\t0\n#define\tMMDTAG_PTR\t\t0x80000000\t// data needs relocation\n\n// ExpData tags\n// # of effect groups, including the global group (will\n// override settings in MMDSong struct), default = 1\n#define\tMMDTAG_TRK_NAME\t\t(MMDTAG_PTR|1)\t// trackinfo tags\n#define\tMMDTAG_TRK_NAMELEN\t2\t\t\t\t// namelen includes zero term.\n\n#pragma pack(1)\n\ntypedef struct tagMEDMODULEHEADER\n{\n\tDWORD id;\t\t// MMD1-MMD3\n\tDWORD modlen;\t// Size of file\n\tDWORD song;\t\t// Position in file for this song\n\tWORD psecnum;\n\tWORD pseq;\n\tDWORD blockarr;\t// Position in file for blocks\n\tDWORD mmdflags;\n\tDWORD smplarr;\t// Position in file for samples\n\tDWORD reserved;\n\tDWORD expdata;\t// Absolute offset in file for ExpData (0 if not present)\n\tDWORD reserved2;\n\tWORD pstate;\n\tWORD pblock;\n\tWORD pline;\n\tWORD pseqnum;\n\tWORD actplayline;\n\tBYTE counter;\n\tBYTE extra_songs;\t// # of songs - 1\n} MEDMODULEHEADER;\n\n\ntypedef struct tagMMD0SAMPLE\n{\n\tWORD rep, replen;\n\tBYTE midich;\n\tBYTE midipreset;\n\tBYTE svol;\n\tsigned char strans;\n} MMD0SAMPLE;\n\n\n// Sample header is immediately followed by sample data...\ntypedef struct tagMMDSAMPLEHEADER\n{\n\tDWORD length;     // length of *one* *unpacked* channel in *bytes*\n\tWORD type;\n\t\t\t\t// if non-negative\n\t\t\t\t\t// bits 0-3 reserved for multi-octave instruments, not supported on the PC\n\t\t\t\t\t// 0x10: 16 bit (otherwise 8 bit)\n\t\t\t\t\t// 0x20: Stereo (otherwise mono)\n\t\t\t\t\t// 0x40: Uses DeltaCode\n\t\t\t\t\t// 0x80: Packed data\n\t\t\t\t// -1: Synth\n\t\t\t\t// -2: Hybrid\n\t// if type indicates packed data, these fields follow, otherwise we go right to the data\n\tWORD packtype;\t// Only 1 = ADPCM is supported\n\tWORD subtype;\t// Packing subtype\n\t\t// ADPCM subtype\n\t\t// 1: g723_40\n\t\t// 2: g721\n\t\t// 3: g723_24\n\tBYTE commonflags;\t// flags common to all packtypes (none defined so far)\n\tBYTE packerflags;\t// flags for the specific packtype\n\tULONG leftchlen;\t// packed length of left channel in bytes\n\tULONG rightchlen;\t// packed length of right channel in bytes (ONLY PRESENT IN STEREO SAMPLES)\n\tBYTE SampleData[1];\t// Sample Data\n} MMDSAMPLEHEADER;\n\n\n// MMD0/MMD1 song header\ntypedef struct tagMMD0SONGHEADER\n{\n\tMMD0SAMPLE sample[63];\n\tWORD numblocks;\t\t// # of blocks\n\tWORD songlen;\t\t// # of entries used in playseq\n\tBYTE playseq[256];\t// Play sequence\n\tWORD deftempo;\t\t// BPM tempo\n\tsigned char playtransp;\t// Play transpose\n\tBYTE flags;\t\t\t// 0x10: Hex Volumes | 0x20: ST/NT/PT Slides | 0x40: 8 Channels song\n\tBYTE flags2;\t\t// [b4-b0]+1: Tempo LPB, 0x20: tempo mode, 0x80: mix_conv=on\n\tBYTE tempo2;\t\t// tempo TPL\n\tBYTE trkvol[16];\t// track volumes\n\tBYTE mastervol;\t\t// master volume\n\tBYTE numsamples;\t// # of samples (max=63)\n} MMD0SONGHEADER;\n\n\n// MMD2/MMD3 song header\ntypedef struct tagMMD2SONGHEADER\n{\n\tMMD0SAMPLE sample[63];\n\tWORD numblocks;\t\t// # of blocks\n\tWORD numsections;\t// # of sections\n\tDWORD playseqtable;\t// filepos of play sequence\n\tDWORD sectiontable;\t// filepos of sections table (WORD array)\n\tDWORD trackvols;\t// filepos of tracks volume (BYTE array)\n\tWORD numtracks;\t\t// # of tracks (max 64)\n\tWORD numpseqs;\t\t// # of play sequences\n\tDWORD trackpans;\t// filepos of tracks pan values (BYTE array)\n\tLONG flags3;\t\t// 0x1:stereo_mix, 0x2:free_panning, 0x4:GM/XG compatibility\n\tWORD voladj;\t\t// vol_adjust (set to 100 if 0)\n\tWORD channels;\t\t// # of channels (4 if =0)\n\tBYTE mix_echotype;\t// 1:normal,2:xecho\n\tBYTE mix_echodepth;\t// 1..6\n\tWORD mix_echolen;\t// > 0\n\tsigned char mix_stereosep;\t// -4..4\n\tBYTE pad0[223];\n\tWORD deftempo;\t\t// BPM tempo\n\tsigned char playtransp;\t// play transpose\n\tBYTE flags;\t\t\t// 0x1:filteron, 0x2:jumpingon, 0x4:jump8th, 0x8:instr_attached, 0x10:hex_vol, 0x20:PT_slides, 0x40:8ch_conv,0x80:hq slows playing speed\n\tBYTE flags2;\t\t// 0x80:mix_conv=on, [b4-b0]+1:tempo LPB, 0x20:tempo_mode\n\tBYTE tempo2;\t\t// tempo TPL\n\tBYTE pad1[16];\n\tBYTE mastervol;\t\t// master volume\n\tBYTE numsamples;\t// # of samples (max 63)\n} MMD2SONGHEADER;\n\n// For MMD0 the note information is held in 3 bytes, byte0, byte1, byte2.  For reference we\n// number the bits in each byte 0..7, where 0 is the low bit.\n// The note is held as bits 5..0 of byte0\n// The instrument is encoded in 6 bits,  bits 7 and 6 of byte0 and bits 7,6,5,4 of byte1\n// The command number is bits 3,2,1,0 of byte1, command data is in byte2:\n// For command 0, byte2 represents the second data byte, otherwise byte2\n// represents the first data byte.\ntypedef struct tagMMD0BLOCK\n{\n\tBYTE numtracks;\n\tBYTE lines;\t\t// File value is 1 less than actual, so 0 -> 1 line\n} MMD0BLOCK;\t\t// BYTE data[lines+1][tracks][3];\n\n\n// For MMD1,MMD2,MMD3 the note information is carried in 4 bytes, byte0, byte1,\n// byte2 and byte3\n// The note is held as byte0 (values above 0x84 are ignored)\n// The instrument is held as byte1\n// The command number is held as byte2, command data is in byte3\n// For commands 0 and 0x19 byte3 represents the second data byte,\n// otherwise byte2 represents the first data byte.\ntypedef struct tagMMD1BLOCK\n{\n\tWORD numtracks;\t// Number of tracks, may be > 64, but then that data is skipped.\n\tWORD lines;\t\t// Stored value is 1 less than actual, so 0 -> 1 line\n\tDWORD info;\t\t// Offset of BlockInfo (if 0, no block_info is present)\n} MMD1BLOCK;\n\n\ntypedef struct tagMMD1BLOCKINFO\n{\n\tDWORD hlmask;\t\t// Unimplemented - ignore\n\tDWORD blockname;\t// file offset of block name\n\tDWORD blocknamelen;\t// length of block name (including term. 0)\n\tDWORD pagetable;\t// file offset of command page table\n\tDWORD cmdexttable;\t// file offset of command extension table\n\tDWORD reserved[4];\t// future expansion\n} MMD1BLOCKINFO;\n\n\n// A set of play sequences is stored as an array of ULONG files offsets\n// Each offset points to the play sequence itself.\ntypedef struct tagMMD2PLAYSEQ\n{\n\tCHAR name[32];\n\tDWORD command_offs;\t// filepos of command table\n\tDWORD reserved;\n\tWORD length;\n\tWORD seq[512];\t// skip if > 0x8000\n} MMD2PLAYSEQ;\n\n\n// A command table contains commands that effect a particular play sequence\n// entry.  The only commands read in are STOP or POSJUMP, all others are ignored\n// POSJUMP is presumed to have extra bytes containing a WORD for the position\ntypedef struct tagMMDCOMMAND\n{\n\tWORD offset;\t\t// Offset within current sequence entry\n\tBYTE cmdnumber;\t\t// STOP (537) or POSJUMP (538) (others skipped)\n\tBYTE extra_count;\n\tBYTE extra_bytes[4];// [extra_count];\n} MMDCOMMAND;  // Last entry has offset == 0xFFFF, cmd_number == 0 and 0 extrabytes\n\n\ntypedef struct tagMMD0EXP\n{\n\tDWORD nextmod;\t\t\t// File offset of next Hdr\n\tDWORD exp_smp;\t\t\t// Pointer to extra instrument data\n\tWORD s_ext_entries;\t\t// Number of extra instrument entries\n\tWORD s_ext_entrsz;\t\t// Size of extra instrument data\n\tDWORD annotxt;\n\tDWORD annolen;\n\tDWORD iinfo;\t\t\t// Instrument names\n\tWORD i_ext_entries;\n\tWORD i_ext_entrsz;\n\tDWORD jumpmask;\n\tDWORD rgbtable;\n\tBYTE channelsplit[4];\t// Only used if 8ch_conv (extra channel for every nonzero entry)\n\tDWORD n_info;\n\tDWORD songname;\t\t\t// Song name\n\tDWORD songnamelen;\n\tDWORD dumps;\n\tDWORD mmdinfo;\n\tDWORD mmdrexx;\n\tDWORD mmdcmd3x;\n\tDWORD trackinfo_ofs;\t// ptr to song->numtracks ptrs to tag lists\n\tDWORD effectinfo_ofs;\t// ptr to group ptrs\n\tDWORD tag_end;\n} MMD0EXP;\n\n#pragma pack()\n\n\n\nstatic void MedConvert(MODCOMMAND *p, const MMD0SONGHEADER *pmsh)\n//---------------------------------------------------------------\n{\n\tUINT command = p->command;\n\tUINT param = p->param;\n\tswitch(command)\n\t{\n\tcase 0x00:\tif (param) command = CMD_ARPEGGIO; else command = 0; break;\n\tcase 0x01:\tcommand = CMD_PORTAMENTOUP; break;\n\tcase 0x02:\tcommand = CMD_PORTAMENTODOWN; break;\n\tcase 0x03:\tcommand = CMD_TONEPORTAMENTO; break;\n\tcase 0x04:\tcommand = CMD_VIBRATO; break;\n\tcase 0x05:\tcommand = CMD_TONEPORTAVOL; break;\n\tcase 0x06:\tcommand = CMD_VIBRATOVOL; break;\n\tcase 0x07:\tcommand = CMD_TREMOLO; break;\n\tcase 0x0A:\tif (param & 0xF0) param &= 0xF0; command = CMD_VOLUMESLIDE; if (!param) command = 0; break;\n\tcase 0x0B:\tcommand = CMD_POSITIONJUMP; break;\n\tcase 0x0C:\tcommand = CMD_VOLUME;\n\t\t\t\tif (pmsh->flags & MMD_FLAG_VOLHEX)\n\t\t\t\t{\n\t\t\t\t\tif (param < 0x80)\n\t\t\t\t\t{\n\t\t\t\t\t\tparam = (param+1) / 2;\n\t\t\t\t\t} else command = 0;\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tif (param <= 0x99)\n\t\t\t\t\t{\n\t\t\t\t\t\tparam = (param >> 4)*10+((param & 0x0F) % 10);\n\t\t\t\t\t\tif (param > 64) param = 64;\n\t\t\t\t\t} else command = 0;\n\t\t\t\t}\n\t\t\t\tbreak;\n\tcase 0x09:\tcommand = (param < 0x20) ? CMD_SPEED : CMD_TEMPO; break;\n\tcase 0x0D:\tif (param & 0xF0) param &= 0xF0; command = CMD_VOLUMESLIDE; if (!param) command = 0; break;\n\tcase 0x0F:\t// Set Tempo / Special\n\t\t// F.00 = Pattern Break\n\t\tif (!param)\tcommand = CMD_PATTERNBREAK;\telse\n\t\t// F.01 - F.F0: Set tempo/speed\n\t\tif (param <= 0xF0)\n\t\t{\n\t\t\tif (pmsh->flags & MMD_FLAG_8CHANNEL)\n\t\t\t{\n\t\t\t\tparam = (param > 10) ? 99 : bpmvals[param-1];\n\t\t\t} else\n\t\t\t// F.01 - F.0A: Set Speed\n\t\t\tif (param <= 0x0A)\n\t\t\t{\n\t\t\t\tcommand = CMD_SPEED;\n\t\t\t} else\n\t\t\t// Old tempo\n\t\t\tif (!(pmsh->flags2 & MMD_FLAG2_BPM))\n\t\t\t{\n\t\t\t\tparam = _muldiv(param, 5*715909, 2*474326);\n\t\t\t}\n\t\t\t// F.0B - F.F0: Set Tempo (assumes LPB=4)\n\t\t\tif (param > 0x0A)\n\t\t\t{\n\t\t\t\tcommand = CMD_TEMPO;\n\t\t\t\tif (param < 0x21) param = 0x21;\n\t\t\t\tif (param > 240) param = 240;\n\t\t\t}\n\t\t} else\n\t\tswitch(param)\n\t\t{\n\t\t// F.F1: Retrig 2x\n\t\tcase 0xF1:\n\t\t\tcommand = CMD_MODCMDEX;\n\t\t\tparam = 0x93;\n\t\t\tbreak;\n\t\t// F.F2: Note Delay 2x\n\t\tcase 0xF2:\n\t\t\tcommand = CMD_MODCMDEX;\n\t\t\tparam = 0xD3;\n\t\t\tbreak;\n\t\t// F.F3: Retrig 3x\n\t\tcase 0xF3:\n\t\t\tcommand = CMD_MODCMDEX;\n\t\t\tparam = 0x92;\n\t\t\tbreak;\n\t\t// F.F4: Note Delay 1/3\n\t\tcase 0xF4:\n\t\t\tcommand = CMD_MODCMDEX;\n\t\t\tparam = 0xD2;\n\t\t\tbreak;\n\t\t// F.F5: Note Delay 2/3\n\t\tcase 0xF5:\n\t\t\tcommand = CMD_MODCMDEX;\n\t\t\tparam = 0xD4;\n\t\t\tbreak;\n\t\t// F.F8: Filter Off\n\t\tcase 0xF8:\n\t\t\tcommand = CMD_MODCMDEX;\n\t\t\tparam = 0x00;\n\t\t\tbreak;\n\t\t// F.F9: Filter On\n\t\tcase 0xF9:\n\t\t\tcommand = CMD_MODCMDEX;\n\t\t\tparam = 0x01;\n\t\t\tbreak;\n\t\t// F.FD: Very fast tone-portamento\n\t\tcase 0xFD:\n\t\t\tcommand = CMD_TONEPORTAMENTO;\n\t\t\tparam = 0xFF;\n\t\t\tbreak;\n\t\t// F.FE: End Song\n\t\tcase 0xFE:\n\t\t\tcommand = CMD_SPEED;\n\t\t\tparam = 0;\n\t\t\tbreak;\n\t\t// F.FF: Note Cut\n\t\tcase 0xFF:\n\t\t\tcommand = CMD_MODCMDEX;\n\t\t\tparam = 0xC0;\n\t\t\tbreak;\n\t\tdefault:\n#ifdef MED_LOG\n\t\t\tLog(\"Unknown Fxx command: cmd=0x%02X param=0x%02X\\n\", command, param);\n#endif\n\t\t\tparam = command = 0;\n\t\t}\n\t\tbreak;\n\t// 11.0x: Fine Slide Up\n\tcase 0x11:\n\t\tcommand = CMD_MODCMDEX;\n\t\tif (param > 0x0F) param = 0x0F;\n\t\tparam |= 0x10;\n\t\tbreak;\n\t// 12.0x: Fine Slide Down\n\tcase 0x12:\n\t\tcommand = CMD_MODCMDEX;\n\t\tif (param > 0x0F) param = 0x0F;\n\t\tparam |= 0x20;\n\t\tbreak;\n\t// 14.xx: Vibrato\n\tcase 0x14:\n\t\tcommand = CMD_VIBRATO;\n\t\tbreak;\n\t// 15.xx: FineTune\n\tcase 0x15:\n\t\tcommand = CMD_MODCMDEX;\n\t\tparam &= 0x0F;\n\t\tparam |= 0x50;\n\t\tbreak;\n\t// 16.xx: Pattern Loop\n\tcase 0x16:\n\t\tcommand = CMD_MODCMDEX;\n\t\tif (param > 0x0F) param = 0x0F;\n\t\tparam |= 0x60;\n\t\tbreak;\n\t// 18.xx: Note Cut\n\tcase 0x18:\n\t\tcommand = CMD_MODCMDEX;\n\t\tif (param > 0x0F) param = 0x0F;\n\t\tparam |= 0xC0;\n\t\tbreak;\n\t// 19.xx: Sample Offset\n\tcase 0x19:\n\t\tcommand = CMD_OFFSET;\n\t\tbreak;\n\t// 1A.0x: Fine Volume Up\n\tcase 0x1A:\n\t\tcommand = CMD_MODCMDEX;\n\t\tif (param > 0x0F) param = 0x0F;\n\t\tparam |= 0xA0;\n\t\tbreak;\n\t// 1B.0x: Fine Volume Down\n\tcase 0x1B:\n\t\tcommand = CMD_MODCMDEX;\n\t\tif (param > 0x0F) param = 0x0F;\n\t\tparam |= 0xB0;\n\t\tbreak;\n\t// 1D.xx: Pattern Break\n\tcase 0x1D:\n\t\tcommand = CMD_PATTERNBREAK;\n\t\tbreak;\n\t// 1E.0x: Pattern Delay\n\tcase 0x1E:\n\t\tcommand = CMD_MODCMDEX;\n\t\tif (param > 0x0F) param = 0x0F;\n\t\tparam |= 0xE0;\n\t\tbreak;\n\t// 1F.xy: Retrig\n\tcase 0x1F:\n\t\tcommand = CMD_RETRIG;\n\t\tparam &= 0x0F;\n\t\tbreak;\n\t// 2E.xx: set panning\n\tcase 0x2E:\n\t\tcommand = CMD_MODCMDEX;\n\t\tparam = ((param + 0x10) & 0xFF) >> 1;\n\t\tif (param > 0x0F) param = 0x0F;\n\t\tparam |= 0x80;\n\t\tbreak;\n\tdefault:\n#ifdef MED_LOG\n\t\t// 0x2E ?\n\t\tLog(\"Unknown command: cmd=0x%02X param=0x%02X\\n\", command, param);\n#endif\n\t\tcommand = param = 0;\n\t}\n\tp->command = command;\n\tp->param = param;\n}\n\n\nBOOL CSoundFile::ReadMed(const BYTE *lpStream, DWORD dwMemLength)\n//---------------------------------------------------------------\n{\n\tconst MEDMODULEHEADER *pmmh;\n\tconst MMD0SONGHEADER *pmsh;\n\tconst MMD2SONGHEADER *pmsh2;\n\tconst MMD0EXP *pmex;\n\tDWORD dwBlockArr, dwSmplArr, dwExpData, wNumBlocks;\n\tLPDWORD pdwTable;\n\tCHAR version;\n\tUINT deftempo;\n\tint playtransp = 0;\n\n\tif ((!lpStream) || (dwMemLength < 0x200)) return FALSE;\n\tpmmh = (MEDMODULEHEADER *)lpStream;\n\tif (((pmmh->id & 0x00FFFFFF) != 0x444D4D) || (!pmmh->song)) return FALSE;\n\t// Check for 'MMDx'\n\tDWORD dwSong = bswapBE32(pmmh->song);\n\tif ((dwSong >= dwMemLength) || (dwSong + sizeof(MMD0SONGHEADER) >= dwMemLength)) return FALSE;\n\tversion = (signed char)((pmmh->id >> 24) & 0xFF);\n\tif ((version < '0') || (version > '3')) return FALSE;\n#ifdef MED_LOG\n\tLog(\"\\nLoading MMD%c module (flags=0x%02X)...\\n\", version, bswapBE32(pmmh->mmdflags));\n\tLog(\"  modlen   = %d\\n\", bswapBE32(pmmh->modlen));\n\tLog(\"  song     = 0x%08X\\n\", bswapBE32(pmmh->song));\n\tLog(\"  psecnum  = %d\\n\", bswapBE16(pmmh->psecnum));\n\tLog(\"  pseq     = %d\\n\", bswapBE16(pmmh->pseq));\n\tLog(\"  blockarr = 0x%08X\\n\", bswapBE32(pmmh->blockarr));\n\tLog(\"  mmdflags = 0x%08X\\n\", bswapBE32(pmmh->mmdflags));\n\tLog(\"  smplarr  = 0x%08X\\n\", bswapBE32(pmmh->smplarr));\n\tLog(\"  reserved = 0x%08X\\n\", bswapBE32(pmmh->reserved));\n\tLog(\"  expdata  = 0x%08X\\n\", bswapBE32(pmmh->expdata));\n\tLog(\"  reserved2= 0x%08X\\n\", bswapBE32(pmmh->reserved2));\n\tLog(\"  pstate   = %d\\n\", bswapBE16(pmmh->pstate));\n\tLog(\"  pblock   = %d\\n\", bswapBE16(pmmh->pblock));\n\tLog(\"  pline    = %d\\n\", bswapBE16(pmmh->pline));\n\tLog(\"  pseqnum  = %d\\n\", bswapBE16(pmmh->pseqnum));\n\tLog(\"  actplayline=%d\\n\", bswapBE16(pmmh->actplayline));\n\tLog(\"  counter  = %d\\n\", pmmh->counter);\n\tLog(\"  extra_songs = %d\\n\", pmmh->extra_songs);\n\tLog(\"\\n\");\n#endif\n\tm_nType = MOD_TYPE_MED;\n\tm_nSongPreAmp = 0x20;\n\tdwBlockArr = bswapBE32(pmmh->blockarr);\n\tdwSmplArr = bswapBE32(pmmh->smplarr);\n\tdwExpData = bswapBE32(pmmh->expdata);\n\tif ((dwExpData) && (dwExpData < dwMemLength - sizeof(MMD0EXP)))\n\t\tpmex = (MMD0EXP *)(lpStream+dwExpData);\n\telse\n\t\tpmex = NULL;\n\tpmsh = (MMD0SONGHEADER *)(lpStream + dwSong);\n\tpmsh2 = (MMD2SONGHEADER *)pmsh;\n#ifdef MED_LOG\n\tif (version < '2')\n\t{\n\t\tLog(\"MMD0 Header:\\n\");\n\t\tLog(\"  numblocks  = %d\\n\", bswapBE16(pmsh->numblocks));\n\t\tLog(\"  songlen    = %d\\n\", bswapBE16(pmsh->songlen));\n\t\tLog(\"  playseq    = \");\n\t\tfor (UINT idbg1=0; idbg1<16; idbg1++) Log(\"%2d, \", pmsh->playseq[idbg1]);\n\t\tLog(\"...\\n\");\n\t\tLog(\"  deftempo   = 0x%04X\\n\", bswapBE16(pmsh->deftempo));\n\t\tLog(\"  playtransp = %d\\n\", (signed char)pmsh->playtransp);\n\t\tLog(\"  flags(1,2) = 0x%02X, 0x%02X\\n\", pmsh->flags, pmsh->flags2);\n\t\tLog(\"  tempo2     = %d\\n\", pmsh->tempo2);\n\t\tLog(\"  trkvol     = \");\n\t\tfor (UINT idbg2=0; idbg2<16; idbg2++) Log(\"0x%02X, \", pmsh->trkvol[idbg2]);\n\t\tLog(\"...\\n\");\n\t\tLog(\"  mastervol  = 0x%02X\\n\", pmsh->mastervol);\n\t\tLog(\"  numsamples = %d\\n\", pmsh->numsamples);\n\t} else\n\t{\n\t\tLog(\"MMD2 Header:\\n\");\n\t\tLog(\"  numblocks  = %d\\n\", bswapBE16(pmsh2->numblocks));\n\t\tLog(\"  numsections= %d\\n\", bswapBE16(pmsh2->numsections));\n\t\tLog(\"  playseqptr = 0x%04X\\n\", bswapBE32(pmsh2->playseqtable));\n\t\tLog(\"  sectionptr = 0x%04X\\n\", bswapBE32(pmsh2->sectiontable));\n\t\tLog(\"  trackvols  = 0x%04X\\n\", bswapBE32(pmsh2->trackvols));\n\t\tLog(\"  numtracks  = %d\\n\", bswapBE16(pmsh2->numtracks));\n\t\tLog(\"  numpseqs   = %d\\n\", bswapBE16(pmsh2->numpseqs));\n\t\tLog(\"  trackpans  = 0x%04X\\n\", bswapBE32(pmsh2->trackpans));\n\t\tLog(\"  flags3     = 0x%08X\\n\", bswapBE32(pmsh2->flags3));\n\t\tLog(\"  voladj     = %d\\n\", bswapBE16(pmsh2->voladj));\n\t\tLog(\"  channels   = %d\\n\", bswapBE16(pmsh2->channels));\n\t\tLog(\"  echotype   = %d\\n\", pmsh2->mix_echotype);\n\t\tLog(\"  echodepth  = %d\\n\", pmsh2->mix_echodepth);\n\t\tLog(\"  echolen    = %d\\n\", bswapBE16(pmsh2->mix_echolen));\n\t\tLog(\"  stereosep  = %d\\n\", (signed char)pmsh2->mix_stereosep);\n\t\tLog(\"  deftempo   = 0x%04X\\n\", bswapBE16(pmsh2->deftempo));\n\t\tLog(\"  playtransp = %d\\n\", (signed char)pmsh2->playtransp);\n\t\tLog(\"  flags(1,2) = 0x%02X, 0x%02X\\n\", pmsh2->flags, pmsh2->flags2);\n\t\tLog(\"  tempo2     = %d\\n\", pmsh2->tempo2);\n\t\tLog(\"  mastervol  = 0x%02X\\n\", pmsh2->mastervol);\n\t\tLog(\"  numsamples = %d\\n\", pmsh->numsamples);\n\t}\n\tLog(\"\\n\");\n#endif\n\twNumBlocks = bswapBE16(pmsh->numblocks);\n\tm_nChannels = 4;\n\tm_nSamples = pmsh->numsamples;\n\tif (m_nSamples > 63) m_nSamples = 63;\n\t// Tempo\n\tm_nDefaultTempo = 125;\n\tdeftempo = bswapBE16(pmsh->deftempo);\n\tif (!deftempo) deftempo = 125;\n\tif (pmsh->flags2 & MMD_FLAG2_BPM)\n\t{\n\t\tUINT tempo_tpl = (pmsh->flags2 & MMD_FLAG2_BMASK) + 1;\n\t\tif (!tempo_tpl) tempo_tpl = 4;\n\t\tdeftempo *= tempo_tpl;\n\t\tdeftempo /= 4;\n\t#ifdef MED_LOG\n\t\tLog(\"newtempo: %3d bpm (bpm=%3d lpb=%2d)\\n\", deftempo, bswapBE16(pmsh->deftempo), (pmsh->flags2 & MMD_FLAG2_BMASK)+1);\n\t#endif\n\t} else\n\t{\n\t\tif (pmsh->flags & MMD_FLAG_8CHANNEL && deftempo > 0 && deftempo <= 10)\n\t\t{\n\t\t\tdeftempo = bpmvals[deftempo-1];\n\t\t} else {\n\t\t\tdeftempo = _muldiv(deftempo, 5*715909, 2*474326);\n\t\t}\n\t#ifdef MED_LOG\n\t\tLog(\"oldtempo: %3d bpm (bpm=%3d)\\n\", deftempo, bswapBE16(pmsh->deftempo));\n\t#endif\n\t}\n\t// Speed\n\tm_nDefaultSpeed = pmsh->tempo2;\n\tif (!m_nDefaultSpeed) m_nDefaultSpeed = 6;\n\tif (deftempo < 0x21) deftempo = 0x21;\n\tif (deftempo > 255)\n\t{\n\t\twhile ((m_nDefaultSpeed > 3) && (deftempo > 260))\n\t\t{\n\t\t\tdeftempo = (deftempo * (m_nDefaultSpeed - 1)) / m_nDefaultSpeed;\n\t\t\tm_nDefaultSpeed--;\n\t\t}\n\t\tif (deftempo > 255) deftempo = 255;\n\t}\n\tm_nDefaultTempo = deftempo;\n\t// Reading Samples\n\tfor (UINT iSHdr=0; iSHdr<m_nSamples; iSHdr++)\n\t{\n\t\tMODINSTRUMENT *pins = &Ins[iSHdr+1];\n\t\tpins->nLoopStart = bswapBE16(pmsh->sample[iSHdr].rep) << 1;\n\t\tpins->nLoopEnd = pins->nLoopStart + (bswapBE16(pmsh->sample[iSHdr].replen) << 1);\n\t\tpins->nVolume = (pmsh->sample[iSHdr].svol << 2);\n\t\tpins->nGlobalVol = 64;\n\t\tif (pins->nVolume > 256) pins->nVolume = 256;\n\t\tpins->RelativeTone = -12 * pmsh->sample[iSHdr].strans;\n\t\tpins->nPan = 128;\n\t\tif (pins->nLoopEnd) pins->uFlags |= CHN_LOOP;\n\t}\n\t// Common Flags\n\tif (!(pmsh->flags & 0x20)) m_dwSongFlags |= SONG_FASTVOLSLIDES;\n\t// Reading play sequence\n\tif (version < '2')\n\t{\n\t\tUINT nbo = pmsh->songlen >> 8;\n\t\tif (nbo >= MAX_ORDERS) nbo = MAX_ORDERS-1;\n\t\tif (!nbo) nbo = 1;\n\t\tmemcpy(Order, pmsh->playseq, nbo);\n\t\tplaytransp = pmsh->playtransp;\n\t} else\n\t{\n\t\tUINT nOrders, nSections;\n\t\tUINT nTrks = bswapBE16(pmsh2->numtracks);\n\t\tif ((nTrks >= 4) && (nTrks <= 32)) m_nChannels = nTrks;\n\t\tDWORD playseqtable = bswapBE32(pmsh2->playseqtable);\n\t\tUINT numplayseqs = bswapBE16(pmsh2->numpseqs);\n\t\tif (!numplayseqs) numplayseqs = 1;\n\t\tnOrders = 0;\n\t\tnSections = bswapBE16(pmsh2->numsections);\n\t\tDWORD sectiontable = bswapBE32(pmsh2->sectiontable);\n\t\tif ((!nSections) || (!sectiontable) || (sectiontable >= dwMemLength-2)) nSections = 1;\n\t\tnOrders = 0;\n\t\tfor (UINT iSection=0; iSection<nSections; iSection++)\n\t\t{\n\t\t\tUINT nplayseq = 0;\n\t\t\tif ((sectiontable) && (sectiontable < dwMemLength-2))\n\t\t\t{\n\t\t\t\tnplayseq = lpStream[sectiontable+1];\n\t\t\t\tsectiontable += 2; // WORDs\n\t\t\t} else\n\t\t\t{\n\t\t\t\tnSections = 0;\n\t\t\t}\n\t\t\tUINT pseq = 0;\n\n\t\t\tif ((playseqtable) && (playseqtable < dwMemLength) && (nplayseq*4 < dwMemLength - playseqtable))\n\t\t\t{\n\t\t\t\tpseq = bswapBE32(((LPDWORD)(lpStream+playseqtable))[nplayseq]);\n\t\t\t}\n\t\t\tif ((pseq) && dwMemLength > sizeof(MMD2PLAYSEQ) &&\n\t\t\t\t(pseq < dwMemLength - sizeof(MMD2PLAYSEQ)))\n\t\t\t{\n\t\t\t\tconst MMD2PLAYSEQ *pmps = (MMD2PLAYSEQ *)(lpStream + pseq);\n\t\t\t\tif (!m_szNames[0][0]) memcpy(m_szNames[0], pmps->name, 31);\n\t\t\t\tUINT n = bswapBE16(pmps->length);\n\t\t\t\tif (n < (dwMemLength - (pseq + sizeof(*pmps)) + sizeof(pmps->seq)) / sizeof(pmps->seq[0]))\n\t\t\t\t{\n\t\t\t\t\tfor (UINT i=0; i<n; i++)\n\t\t\t\t\t{\n\t\t\t\t\t\tUINT seqval = pmps->seq[i] >> 8;\n\t\t\t\t\t\tif ((seqval < wNumBlocks) && (nOrders < MAX_ORDERS-1))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tOrder[nOrders++] = seqval;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tplaytransp = pmsh2->playtransp;\n\t\twhile (nOrders < MAX_ORDERS) Order[nOrders++] = 0xFF;\n\t}\n\t// Reading Expansion structure\n\tif (pmex)\n\t{\n\t\t// Channel Split\n\t\tif ((m_nChannels == 4) && (pmsh->flags & MMD_FLAG_8CHANNEL))\n\t\t{\n\t\t\tfor (UINT i8ch=0; i8ch<4; i8ch++)\n\t\t\t{\n\t\t\t\tif (pmex->channelsplit[i8ch]) m_nChannels++;\n\t\t\t}\n\t\t}\n\t\t// Song Comments\n\t\tuint32_t annotxt = bswapBE32(pmex->annotxt);\n\t\tuint32_t annolen = bswapBE32(pmex->annolen);\n\t\tif ((annotxt) && (annolen) && (annotxt + annolen > annotxt) // overflow checks.\n\t\t\t\t&& (annotxt+annolen <= dwMemLength))\n\t\t{\n\t\t\tm_lpszSongComments = new char[annolen+1];\n\t\t\tmemcpy(m_lpszSongComments, lpStream+annotxt, annolen);\n\t\t\tm_lpszSongComments[annolen] = 0;\n\t\t}\n\t\t// Song Name\n\t\tuint32_t songname = bswapBE32(pmex->songname);\n\t\tuint32_t songnamelen = bswapBE32(pmex->songnamelen);\n\t\tif ((songname) && (songnamelen) && (songname+songnamelen > songname)\n\t\t\t\t&& (songname+songnamelen <= dwMemLength))\n\t\t{\n\t\t\tif (songnamelen > 31) songnamelen = 31;\n\t\t\tmemcpy(m_szNames[0], lpStream+songname, songnamelen);\n\t\t\tm_szNames[0][31] = '\\0';\n\t\t}\n\t\t// Sample Names\n\t\tDWORD smpinfoex = bswapBE32(pmex->iinfo);\n\t\tif (smpinfoex)\n\t\t{\n\t\t\tDWORD iinfoptr = bswapBE32(pmex->iinfo);\n\t\t\tUINT ientries = bswapBE16(pmex->i_ext_entries);\n\t\t\tUINT ientrysz = bswapBE16(pmex->i_ext_entrsz);\n\n\t\t\tif ((iinfoptr) && (ientrysz < 256) &&\n\t\t\t (ientries*ientrysz < dwMemLength) &&\n\t\t\t (iinfoptr < dwMemLength - (ientries*ientrysz)))\n\t\t\t{\n\t\t\t\tLPCSTR psznames = (LPCSTR)(lpStream + iinfoptr);\n\t\t\t\tUINT maxnamelen = ientrysz;\n\t\t\t\t// copy a max of 32 bytes.\n\t\t\t\tif (maxnamelen > 32) maxnamelen = 32;\n\t\t\t\tfor (UINT i=0; i<ientries; i++) if (i < m_nSamples)\n\t\t\t\t{\n\t\t\t\t\tlstrcpyn(m_szNames[i+1], psznames + i*ientrysz, maxnamelen);\n\t\t\t\t\tm_szNames[i+1][31] = '\\0';\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Track Names\n\t\tDWORD trackinfo_ofs = bswapBE32(pmex->trackinfo_ofs);\n\t\tif ((trackinfo_ofs) && (trackinfo_ofs < dwMemLength) && (m_nChannels * 4 < dwMemLength - trackinfo_ofs))\n\t\t{\n\t\t\tDWORD *ptrktags = (DWORD *)(lpStream + trackinfo_ofs);\n\t\t\tfor (UINT i=0; i<m_nChannels; i++)\n\t\t\t{\n\t\t\t\tDWORD trknameofs = 0, trknamelen = 0;\n\t\t\t\tDWORD trktagofs = bswapBE32(ptrktags[i]);\n\t\t\t\tif (trktagofs)\n\t\t\t\t{\n\t\t\t\t\twhile (trktagofs < dwMemLength - 8)\n\t\t\t\t\t{\n\t\t\t\t\t\tDWORD ntag = bswapBE32(*(DWORD *)(lpStream + trktagofs));\n\t\t\t\t\t\tif (ntag == MMDTAG_END) break;\n\t\t\t\t\t\tDWORD tagdata = bswapBE32(*(DWORD *)(lpStream + trktagofs + 4));\n\t\t\t\t\t\tswitch(ntag)\n\t\t\t\t\t\t{\n\t\t\t\t\t\tcase MMDTAG_TRK_NAMELEN:\ttrknamelen = tagdata; break;\n\t\t\t\t\t\tcase MMDTAG_TRK_NAME:\t\ttrknameofs = tagdata; break;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttrktagofs += 8;\n\t\t\t\t\t}\n\t\t\t\t\tif (trknamelen > MAX_CHANNELNAME) trknamelen = MAX_CHANNELNAME;\n\t\t\t\t\tif ((trknameofs) && (trknamelen < dwMemLength) && (trknameofs < dwMemLength - trknamelen))\n\t\t\t\t\t{\n\t\t\t\t\t\tlstrcpyn(ChnSettings[i].szName, (LPCSTR)(lpStream+trknameofs), MAX_CHANNELNAME);\n\t\t\t\t\t\tChnSettings[i].szName[MAX_CHANNELNAME-1] = '\\0';\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Reading samples\n\tif (dwSmplArr > dwMemLength - 4*m_nSamples) return TRUE;\n\tpdwTable = (LPDWORD)(lpStream + dwSmplArr);\n\tfor (UINT iSmp=0; iSmp<m_nSamples; iSmp++) if (pdwTable[iSmp])\n\t{\n\t\tUINT dwPos = bswapBE32(pdwTable[iSmp]);\n\t\tif ((dwPos >= dwMemLength) || (dwPos + sizeof(MMDSAMPLEHEADER) >= dwMemLength)) continue;\n\t\tMMDSAMPLEHEADER *psdh = (MMDSAMPLEHEADER *)(lpStream + dwPos);\n\t\tUINT len = bswapBE32(psdh->length);\n\t#ifdef MED_LOG\n\t\tLog(\"SampleData %d: stype=0x%02X len=%d\\n\", iSmp, bswapBE16(psdh->type), len);\n\t#endif\n\t\tif ((len > MAX_SAMPLE_LENGTH) || (dwPos + len + 6 > dwMemLength)) len = 0;\n\t\tUINT flags = RS_PCM8S, stype = bswapBE16(psdh->type);\n\t\tLPSTR psdata = (LPSTR)(lpStream + dwPos + 6);\n\t\tif (stype & 0x80)\n\t\t{\n\t\t\tpsdata += (stype & 0x20) ? 14 : 6;\n\t\t} else\n\t\t{\n\t\t\tif (stype & 0x10)\n\t\t\t{\n\t\t\t\tIns[iSmp+1].uFlags |= CHN_16BIT;\n\t\t\t\tlen /= 2;\n\t\t\t\tflags = (stype & 0x20) ? RS_STPCM16M : RS_PCM16M;\n\t\t\t} else\n\t\t\t{\n\t\t\t\tflags = (stype & 0x20) ? RS_STPCM8S : RS_PCM8S;\n\t\t\t}\n\t\t\tif (stype & 0x20) len /= 2;\n\t\t}\n\t\tIns[iSmp+1].nLength = len;\n\t\tReadSample(&Ins[iSmp+1], flags, psdata, dwMemLength - dwPos - 6);\n\t}\n\t// Reading patterns (blocks)\n\tif (wNumBlocks > MAX_PATTERNS) wNumBlocks = MAX_PATTERNS;\n\tif ((!dwBlockArr) || (dwMemLength < 4*wNumBlocks) ||\n\t\t(dwBlockArr > dwMemLength - 4*wNumBlocks)) return TRUE;\n\tpdwTable = (LPDWORD)(lpStream + dwBlockArr);\n\tplaytransp += (version == '3') ? 24 : 48;\n\tfor (UINT iBlk=0; iBlk<wNumBlocks; iBlk++)\n\t{\n\t\tUINT dwPos = bswapBE32(pdwTable[iBlk]);\n\t\tif ((!dwPos) || (dwPos >= dwMemLength) || (dwPos >= dwMemLength - 8)) continue;\n\t\tUINT lines = 64, tracks = 4;\n\t\tif (version == '0')\n\t\t{\n\t\t\tconst MMD0BLOCK *pmb = (const MMD0BLOCK *)(lpStream + dwPos);\n\t\t\tlines = pmb->lines + 1;\n\t\t\ttracks = pmb->numtracks;\n\t\t\tif (!tracks) tracks = m_nChannels;\n\t\t\tif ((Patterns[iBlk] = AllocatePattern(lines, m_nChannels)) == NULL) continue;\n\t\t\tPatternSize[iBlk] = lines;\n\t\t\tMODCOMMAND *p = Patterns[iBlk];\n\t\t\tLPBYTE s = (LPBYTE)(lpStream + dwPos + 2);\n\t\t\tUINT maxlen = tracks*lines*3;\n\t\t\tif (maxlen + dwPos > dwMemLength - 2) break;\n\t\t\tfor (UINT y=0; y<lines; y++)\n\t\t\t{\n\t\t\t\tfor (UINT x=0; x<tracks; x++, s+=3) if (x < m_nChannels)\n\t\t\t\t{\n\t\t\t\t\tBYTE note = s[0] & 0x3F;\n\t\t\t\t\tBYTE instr = s[1] >> 4;\n\t\t\t\t\tif (s[0] & 0x80) instr |= 0x10;\n\t\t\t\t\tif (s[0] & 0x40) instr |= 0x20;\n\t\t\t\t\tif ((note) && (note <= 132)) p->note = note + playtransp;\n\t\t\t\t\tp->instr = instr;\n\t\t\t\t\tp->command = s[1] & 0x0F;\n\t\t\t\t\tp->param = s[2];\n\t\t\t\t\t// if (!iBlk) Log(\"%02X.%02X.%02X | \", s[0], s[1], s[2]);\n\t\t\t\t\tMedConvert(p, pmsh);\n\t\t\t\t\tp++;\n\t\t\t\t}\n\t\t\t\t//if (!iBlk) Log(\"\\n\");\n\t\t\t}\n\t\t} else\n\t\t{\n\t\t\tconst MMD1BLOCK *pmb = (MMD1BLOCK *)(lpStream + dwPos);\n\t\t#ifdef MED_LOG\n\t\t\tLog(\"MMD1BLOCK:   lines=%2d, tracks=%2d, offset=0x%04X\\n\",\n\t\t\t\tbswapBE16(pmb->lines), bswapBE16(pmb->numtracks), bswapBE32(pmb->info));\n\t\t#endif\n\t\t\tconst MMD1BLOCKINFO *pbi = NULL;\n\t\t\tBYTE *pcmdext = NULL;\n\t\t\tlines = (pmb->lines >> 8) + 1;\n\t\t\ttracks = pmb->numtracks >> 8;\n\t\t\tif (!tracks) tracks = m_nChannels;\n\t\t\tif ((Patterns[iBlk] = AllocatePattern(lines, m_nChannels)) == NULL) continue;\n\t\t\tPatternSize[iBlk] = (WORD)lines;\n\t\t\tDWORD dwBlockInfo = bswapBE32(pmb->info);\n\t\t\tif ((dwBlockInfo) && (dwBlockInfo < dwMemLength - sizeof(MMD1BLOCKINFO)))\n\t\t\t{\n\t\t\t\tpbi = (MMD1BLOCKINFO *)(lpStream + dwBlockInfo);\n\t\t\t#ifdef MED_LOG\n\t\t\t\tLog(\"  BLOCKINFO: blockname=0x%04X namelen=%d pagetable=0x%04X &cmdexttable=0x%04X\\n\",\n\t\t\t\t\tbswapBE32(pbi->blockname), bswapBE32(pbi->blocknamelen), bswapBE32(pbi->pagetable), bswapBE32(pbi->cmdexttable));\n\t\t\t#endif\n\t\t\t\tif ((pbi->blockname) && (pbi->blocknamelen))\n\t\t\t\t{\n\t\t\t\t\tDWORD nameofs = bswapBE32(pbi->blockname);\n\t\t\t\t\tUINT namelen = bswapBE32(pbi->blocknamelen);\n\t\t\t\t\tif ((nameofs < dwMemLength) && (namelen < dwMemLength + nameofs))\n\t\t\t\t\t{\n\t\t\t\t\t\tSetPatternName(iBlk, (LPCSTR)(lpStream+nameofs));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (pbi->cmdexttable)\n\t\t\t\t{\n\t\t\t\t\tDWORD cmdexttable = bswapBE32(pbi->cmdexttable);\n\t\t\t\t\tif (cmdexttable < dwMemLength - 4)\n\t\t\t\t\t{\n\t\t\t\t\t\tcmdexttable = bswapBE32(*(DWORD *)(lpStream + cmdexttable));\n\t\t\t\t\t\tif ((cmdexttable) && (cmdexttable <= dwMemLength - lines*tracks))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpcmdext = (BYTE *)(lpStream + cmdexttable);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tMODCOMMAND *p = Patterns[iBlk];\n\t\t\tLPBYTE s = (LPBYTE)(lpStream + dwPos + 8);\n\t\t\tUINT maxlen = tracks*lines*4;\n\t\t\tif (maxlen + dwPos > dwMemLength - 8) break;\n\t\t\tfor (UINT y=0; y<lines; y++)\n\t\t\t{\n\t\t\t\tfor (UINT x=0; x<tracks; x++, s+=4) if (x < m_nChannels)\n\t\t\t\t{\n\t\t\t\t\tBYTE note = s[0];\n\t\t\t\t\tif ((note) && (note <= 132))\n\t\t\t\t\t{\n\t\t\t\t\t\tint rnote = note + playtransp;\n\t\t\t\t\t\tif (rnote < 1) rnote = 1;\n\t\t\t\t\t\tif (rnote > NOTE_MAX) rnote = NOTE_MAX;\n\t\t\t\t\t\tp->note = (BYTE)rnote;\n\t\t\t\t\t}\n\t\t\t\t\tp->instr = s[1];\n\t\t\t\t\tp->command = s[2];\n\t\t\t\t\tp->param = s[3];\n\t\t\t\t\tif (pcmdext) p->vol = pcmdext[x];\n\t\t\t\t\tMedConvert(p, pmsh);\n\t\t\t\t\tp++;\n\t\t\t\t}\n\t\t\t\tif (pcmdext) pcmdext += tracks;\n\t\t\t}\n\t\t}\n\t}\n\t// Setup channel pan positions\n\tfor (UINT iCh=0; iCh<m_nChannels; iCh++)\n\t{\n\t\tChnSettings[iCh].nPan = (((iCh&3) == 1) || ((iCh&3) == 2)) ? 0xC0 : 0x40;\n\t\tChnSettings[iCh].nVolume = 64;\n\t}\n\treturn TRUE;\n}\n"
  },
  {
    "path": "contrib/libmodplug/src/load_mod.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>,\n *          Adam Goode       <adam@evdebs.org> (endian and char fixes for PPC)\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n#include \"tables.h\"\n\n#ifdef _MSC_VER\n//#pragma warning(disable:4244)\n#endif\n\n//////////////////////////////////////////////////////////\n// ProTracker / NoiseTracker MOD/NST file support\n\nvoid CSoundFile::ConvertModCommand(MODCOMMAND *m) const\n//-----------------------------------------------------\n{\n\tUINT command = m->command, param = m->param;\n\n\tswitch(command)\n\t{\n\tcase 0x00:\tif (param) command = CMD_ARPEGGIO; break;\n\tcase 0x01:\tcommand = CMD_PORTAMENTOUP; break;\n\tcase 0x02:\tcommand = CMD_PORTAMENTODOWN; break;\n\tcase 0x03:\tcommand = CMD_TONEPORTAMENTO; break;\n\tcase 0x04:\tcommand = CMD_VIBRATO; break;\n\tcase 0x05:\tcommand = CMD_TONEPORTAVOL; if (param & 0xF0) param &= 0xF0; break;\n\tcase 0x06:\tcommand = CMD_VIBRATOVOL; if (param & 0xF0) param &= 0xF0; break;\n\tcase 0x07:\tcommand = CMD_TREMOLO; break;\n\tcase 0x08:\tcommand = CMD_PANNING8; break;\n\tcase 0x09:\tcommand = CMD_OFFSET; break;\n\tcase 0x0A:\tcommand = CMD_VOLUMESLIDE; if (param & 0xF0) param &= 0xF0; break;\n\tcase 0x0B:\tcommand = CMD_POSITIONJUMP; break;\n\tcase 0x0C:\tcommand = CMD_VOLUME; break;\n\tcase 0x0D:\tcommand = CMD_PATTERNBREAK; param = ((param >> 4) * 10) + (param & 0x0F); break;\n\tcase 0x0E:\tcommand = CMD_MODCMDEX; break;\n\tcase 0x0F:\tcommand = (param <= (UINT)((m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) ? 0x1F : 0x20)) ? CMD_SPEED : CMD_TEMPO;\n\t\t\t\tif ((param == 0xFF) && (m_nSamples == 15)) command = 0;\n\t\t\t\tbreak;\n\t// Extension for XM extended effects\n\tcase 'G' - 55:\tcommand = CMD_GLOBALVOLUME; break;\n\tcase 'H' - 55:\tcommand = CMD_GLOBALVOLSLIDE; if (param & 0xF0) param &= 0xF0; break;\n\tcase 'K' - 55:\tcommand = CMD_KEYOFF; break;\n\tcase 'L' - 55:\tcommand = CMD_SETENVPOSITION; break;\n\tcase 'M' - 55:\tcommand = CMD_CHANNELVOLUME; break;\n\tcase 'N' - 55:\tcommand = CMD_CHANNELVOLSLIDE; break;\n\tcase 'P' - 55:\tcommand = CMD_PANNINGSLIDE; if (param & 0xF0) param &= 0xF0; break;\n\tcase 'R' - 55:\tcommand = CMD_RETRIG; break;\n\tcase 'T' - 55:\tcommand = CMD_TREMOR; break;\n\tcase 'X' - 55:\tcommand = CMD_XFINEPORTAUPDOWN;\tbreak;\n\tcase 'Y' - 55:\tcommand = CMD_PANBRELLO; break;\n\tcase 'Z' - 55:\tcommand = CMD_MIDI;\tbreak;\n\tdefault:\tcommand = 0;\n\t}\n\tm->command = command;\n\tm->param = param;\n}\n\n\nWORD CSoundFile::ModSaveCommand(const MODCOMMAND *m, BOOL bXM) const\n//------------------------------------------------------------------\n{\n\tUINT command = m->command & 0x3F, param = m->param;\n\n\tswitch(command)\n\t{\n\tcase 0:\t\t\t\t\t\tcommand = param = 0; break;\n\tcase CMD_ARPEGGIO:\t\t\tcommand = 0; break;\n\tcase CMD_PORTAMENTOUP:\n\t\tif (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM))\n\t\t{\n\t\t\tif ((param & 0xF0) == 0xE0) { command=0x0E; param=((param & 0x0F) >> 2)|0x10; break; }\n\t\t\telse if ((param & 0xF0) == 0xF0) { command=0x0E; param &= 0x0F; param|=0x10; break; }\n\t\t}\n\t\tcommand = 0x01;\n\t\tbreak;\n\tcase CMD_PORTAMENTODOWN:\n\t\tif (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM))\n\t\t{\n\t\t\tif ((param & 0xF0) == 0xE0) { command=0x0E; param=((param & 0x0F) >> 2)|0x20; break; }\n\t\t\telse if ((param & 0xF0) == 0xF0) { command=0x0E; param &= 0x0F; param|=0x20; break; }\n\t\t}\n\t\tcommand = 0x02;\n\t\tbreak;\n\tcase CMD_TONEPORTAMENTO:\tcommand = 0x03; break;\n\tcase CMD_VIBRATO:\t\t\tcommand = 0x04; break;\n\tcase CMD_TONEPORTAVOL:\t\tcommand = 0x05; break;\n\tcase CMD_VIBRATOVOL:\t\tcommand = 0x06; break;\n\tcase CMD_TREMOLO:\t\t\tcommand = 0x07; break;\n\tcase CMD_PANNING8:\t\t\t\n\t\tcommand = 0x08;\n\t\tif (bXM)\n\t\t{\n\t\t\tif ((m_nType != MOD_TYPE_IT) && (m_nType != MOD_TYPE_XM) && (param <= 0x80))\n\t\t\t{\n\t\t\t\tparam <<= 1;\n\t\t\t\tif (param > 255) param = 255;\n\t\t\t}\n\t\t} else\n\t\t{\n\t\t\tif ((m_nType == MOD_TYPE_IT) || (m_nType == MOD_TYPE_XM)) param >>= 1;\n\t\t}\n\t\tbreak;\n\tcase CMD_OFFSET:\t\t\tcommand = 0x09; break;\n\tcase CMD_VOLUMESLIDE:\t\tcommand = 0x0A; break;\n\tcase CMD_POSITIONJUMP:\t\tcommand = 0x0B; break;\n\tcase CMD_VOLUME:\t\t\tcommand = 0x0C; break;\n\tcase CMD_PATTERNBREAK:\t\tcommand = 0x0D; param = ((param / 10) << 4) | (param % 10); break;\n\tcase CMD_MODCMDEX:\t\t\tcommand = 0x0E; break;\n\tcase CMD_SPEED:\t\t\t\tcommand = 0x0F; if (param > 0x20) param = 0x20; break;\n\tcase CMD_TEMPO:\t\t\t\tif (param > 0x20) { command = 0x0F; break; }\n\tcase CMD_GLOBALVOLUME:\t\tcommand = 'G' - 55; break;\n\tcase CMD_GLOBALVOLSLIDE:\tcommand = 'H' - 55; break;\n\tcase CMD_KEYOFF:\t\t\tcommand = 'K' - 55; break;\n\tcase CMD_SETENVPOSITION:\tcommand = 'L' - 55; break;\n\tcase CMD_CHANNELVOLUME:\t\tcommand = 'M' - 55; break;\n\tcase CMD_CHANNELVOLSLIDE:\tcommand = 'N' - 55; break;\n\tcase CMD_PANNINGSLIDE:\t\tcommand = 'P' - 55; break;\n\tcase CMD_RETRIG:\t\t\tcommand = 'R' - 55; break;\n\tcase CMD_TREMOR:\t\t\tcommand = 'T' - 55; break;\n\tcase CMD_XFINEPORTAUPDOWN:\tcommand = 'X' - 55; break;\n\tcase CMD_PANBRELLO:\t\t\tcommand = 'Y' - 55; break;\n\tcase CMD_MIDI:\t\t\t\tcommand = 'Z' - 55; break;\n\tcase CMD_S3MCMDEX:\n\t\tswitch(param & 0xF0)\n\t\t{\n\t\tcase 0x10:\tcommand = 0x0E; param = (param & 0x0F) | 0x30; break;\n\t\tcase 0x20:\tcommand = 0x0E; param = (param & 0x0F) | 0x50; break;\n\t\tcase 0x30:\tcommand = 0x0E; param = (param & 0x0F) | 0x40; break;\n\t\tcase 0x40:\tcommand = 0x0E; param = (param & 0x0F) | 0x70; break;\n\t\tcase 0x90:\tcommand = 'X' - 55; break;\n\t\tcase 0xB0:\tcommand = 0x0E; param = (param & 0x0F) | 0x60; break;\n\t\tcase 0xA0:\n\t\tcase 0x50:\n\t\tcase 0x70:\n\t\tcase 0x60:\tcommand = param = 0; break;\n\t\tdefault:\tcommand = 0x0E; break;\n\t\t}\n\t\tbreak;\n\tdefault:\t\tcommand = param = 0;\n\t}\n\treturn (WORD)((command << 8) | (param));\n}\n\n\n#pragma pack(1)\n\ntypedef struct _MODSAMPLE\n{\n\tCHAR name[22];\n\tWORD length;\n\tBYTE finetune;\n\tBYTE volume;\n\tWORD loopstart;\n\tWORD looplen;\n} MODSAMPLE, *PMODSAMPLE;\n\ntypedef struct _MODMAGIC\n{\n\tBYTE nOrders;\n\tBYTE nRestartPos;\n\tBYTE Orders[128];\n        char Magic[4];          // changed from CHAR\n} MODMAGIC, *PMODMAGIC;\n\n#pragma pack()\n\nstatic BOOL IsValidName(LPCSTR s, int length, CHAR minChar)\n//-----------------------------------------------------------------\n{\n\tint i, nt;\n\tfor (i = 0, nt = 0; i < length; i++)\n\t{\n\t\tif(s[i])\n\t\t{\n\t\t\tif (nt) return FALSE;// garbage after null\n\t\t\tif (s[i] < minChar) return FALSE;// caller says it's garbage\n\t\t}\n\t\telse if (!nt) nt = i;// found null terminator\n\t}\n\treturn TRUE;\n}\n\nstatic BOOL IsMagic(LPCSTR s1, LPCSTR s2)\n{\n\treturn ((*(DWORD *)s1) == (*(DWORD *)s2)) ? TRUE : FALSE;\n}\n\n\nBOOL CSoundFile::ReadMod(const BYTE *lpStream, DWORD dwMemLength)\n//---------------------------------------------------------------\n{\n        char s[1024];          // changed from CHAR\n\tDWORD dwMemPos, dwTotalSampleLen;\n\tPMODMAGIC pMagic;\n\tUINT nErr;\n\n\tif ((!lpStream) || (dwMemLength < 0x600)) return FALSE;\n\tdwMemPos = 20;\n\tm_nSamples = 31;\n\tm_nChannels = 4;\n\tpMagic = (PMODMAGIC)(lpStream+dwMemPos+sizeof(MODSAMPLE)*31);\n\t// Check Mod Magic\n\tmemcpy(s, pMagic->Magic, 4);\n\tif ((IsMagic(s, \"M.K.\")) || (IsMagic(s, \"M!K!\"))\n\t || (IsMagic(s, \"M&K!\")) || (IsMagic(s, \"N.T.\"))) m_nChannels = 4; else\n\tif ((IsMagic(s, \"CD81\")) || (IsMagic(s, \"OKTA\"))) m_nChannels = 8; else\n\tif (IsMagic(s, \"CD61\")) m_nChannels = 6; else\n\tif ((s[0]=='F') && (s[1]=='L') && (s[2]=='T') && (s[3]>='4') && (s[3]<='9')) m_nChannels = s[3] - '0'; else\n\tif ((s[0]>='2') && (s[0]<='9') && (s[1]=='C') && (s[2]=='H') && (s[3]=='N')) m_nChannels = s[0] - '0'; else\n\tif ((s[0]=='1') && (s[1]>='0') && (s[1]<='9') && (s[2]=='C') && (s[3]=='H')) m_nChannels = s[1] - '0' + 10; else\n\tif ((s[0]=='2') && (s[1]>='0') && (s[1]<='9') && (s[2]=='C') && (s[3]=='H')) m_nChannels = s[1] - '0' + 20; else\n\tif ((s[0]=='3') && (s[1]>='0') && (s[1]<='2') && (s[2]=='C') && (s[3]=='H')) m_nChannels = s[1] - '0' + 30; else\n\tif ((s[0]=='T') && (s[1]=='D') && (s[2]=='Z') && (s[3]>='4') && (s[3]<='9')) m_nChannels = s[3] - '0'; else\n\tif (IsMagic(s,\"16CN\")) m_nChannels = 16; else\n\tif (IsMagic(s,\"32CN\")) m_nChannels = 32;\n\telse {\n\t\tif (!IsValidName((LPCSTR)lpStream, 20, ' '))\n\t\t\treturn FALSE;\n\t\tm_nSamples = 15;\n\t}\n\t// Load Samples\n\tnErr = 0;\n\tdwTotalSampleLen = 0;\n\tfor\t(UINT i=1; i<=m_nSamples; i++)\n\t{\n\t\tPMODSAMPLE pms = (PMODSAMPLE)(lpStream+dwMemPos);\n\t\tMODINSTRUMENT *psmp = &Ins[i];\n\t\tUINT loopstart, looplen;\n\n\t\tif (m_nSamples == 15)\n\t\t{\n\t\t\tif (!IsValidName((LPCSTR)pms->name, 22, 14)) return FALSE;\n\t\t\tif (pms->finetune>>4) return FALSE;\n\t\t\tif (pms->volume > 64) return FALSE;\n\t\t\tif (bswapBE16(pms->length) > 32768) return FALSE;\n\t\t}\n\n\t\tmemcpy(m_szNames[i], pms->name, 22);\n\t\tm_szNames[i][22] = 0;\n\t\tpsmp->uFlags = 0;\n\t\tpsmp->nLength = bswapBE16(pms->length)*2;\n\t\tdwTotalSampleLen += psmp->nLength;\n\t\tpsmp->nFineTune = MOD2XMFineTune(pms->finetune & 0x0F);\n\t\tpsmp->nVolume = 4*pms->volume;\n\t\tif (psmp->nVolume > 256) { psmp->nVolume = 256; nErr++; }\n\t\tpsmp->nGlobalVol = 64;\n\t\tpsmp->nPan = 128;\n\t\tloopstart = bswapBE16(pms->loopstart)*2;\n\t\tlooplen = bswapBE16(pms->looplen)*2;\n\t\t// Fix loops\n\t\tif ((looplen > 2) && (loopstart+looplen > psmp->nLength)\n\t\t && (loopstart/2+looplen <= psmp->nLength))\n\t\t{\n\t\t\tloopstart /= 2;\n\t\t}\n\t\tpsmp->nLoopStart = loopstart;\n\t\tpsmp->nLoopEnd = loopstart + looplen;\n\t\tif (psmp->nLength < 4) psmp->nLength = 0;\n\t\tif (psmp->nLength)\n\t\t{\n\t\t\tif (psmp->nLoopStart >= psmp->nLength) { psmp->nLoopStart = psmp->nLength-1; }\n\t\t\tif (psmp->nLoopEnd > psmp->nLength) { psmp->nLoopEnd = psmp->nLength; }\n\n\t\t\tif ((psmp->nLoopStart > psmp->nLoopEnd) || (psmp->nLoopEnd <= 8)\n\t\t\t || (psmp->nLoopEnd - psmp->nLoopStart <= 4))\n\t\t\t{\n\t\t\t\tpsmp->nLoopStart = 0;\n\t\t\t\tpsmp->nLoopEnd = 0;\n\t\t\t}\n\t\t\tif (psmp->nLoopEnd > psmp->nLoopStart)\n\t\t\t{\n\t\t\t\tpsmp->uFlags |= CHN_LOOP;\n\t\t\t}\n\t\t}\n\t\tdwMemPos += sizeof(MODSAMPLE);\n\t}\n\tif ((m_nSamples == 15) && (dwTotalSampleLen > dwMemLength * 4)) return FALSE;\n\tpMagic = (PMODMAGIC)(lpStream+dwMemPos);\n\tdwMemPos += sizeof(MODMAGIC);\n\tif (m_nSamples == 15) {\n\t\tdwMemPos -= 4;\n\t\tif (pMagic->nOrders > 128) return FALSE;\n\t}\n\tmemset(Order, 0,sizeof(Order));\n\tmemcpy(Order, pMagic->Orders, 128);\n\n\tUINT nbp, nbpbuggy, nbpbuggy2, norders;\n\n\tnorders = pMagic->nOrders;\n\tif ((!norders) || (norders > 0x80))\n\t{\n\t\tnorders = 0x80;\n\t\twhile ((norders > 1) && (!Order[norders-1])) norders--;\n\t}\n\tnbpbuggy = 0;\n\tnbpbuggy2 = 0;\n\tnbp = 0;\n\tfor (UINT iord=0; iord<128; iord++)\n\t{\n\t\tUINT i = Order[iord];\n\t\tif ((i < 0x80) && (nbp <= i))\n\t\t{\n\t\t\tnbp = i+1;\n\t\t\tif (iord<norders) nbpbuggy = nbp;\n\t\t}\n\t\tif (i >= nbpbuggy2) nbpbuggy2 = i+1;\n\t}\n\tfor (UINT iend=norders; iend<MAX_ORDERS; iend++) Order[iend] = 0xFF;\n\tnorders--;\n\tm_nRestartPos = pMagic->nRestartPos;\n\tif (m_nRestartPos >= 0x78) m_nRestartPos = 0;\n\tif (m_nRestartPos + 1 >= (UINT)norders) m_nRestartPos = 0;\n\tif (!nbp) return FALSE;\n\tDWORD dwWowTest = dwTotalSampleLen+dwMemPos;\n\tif ((IsMagic(pMagic->Magic, \"M.K.\")) && (dwWowTest + nbp*8*256 == dwMemLength)) m_nChannels = 8;\n\tif ((nbp != nbpbuggy) && (dwWowTest + nbp*m_nChannels*256 != dwMemLength))\n\t{\n\t\tif (dwWowTest + nbpbuggy*m_nChannels*256 == dwMemLength) nbp = nbpbuggy;\n\t\telse nErr += 8;\n\t} else\n\tif ((nbpbuggy2 > nbp) && (dwWowTest + nbpbuggy2*m_nChannels*256 == dwMemLength))\n\t{\n\t\tnbp = nbpbuggy2;\n\t}\n\tif ((dwWowTest < 0x600) || (dwWowTest > dwMemLength)) nErr += 8;\n\tif ((m_nSamples == 15) && (nErr >= 16)) return FALSE;\n\t// Default settings\t\n\tm_nType = MOD_TYPE_MOD;\n\tm_nDefaultSpeed = 6;\n\tm_nDefaultTempo = 125;\n\tm_nMinPeriod = 14 << 2;\n\tm_nMaxPeriod = 3424 << 2;\n\tmemcpy(m_szNames, lpStream, 20);\n\t// Setting channels pan\n\tfor (UINT ich=0; ich<m_nChannels; ich++)\n\t{\n\t\tChnSettings[ich].nVolume = 64;\n\t\tif (gdwSoundSetup & SNDMIX_MAXDEFAULTPAN)\n\t\t\tChnSettings[ich].nPan = (((ich&3)==1) || ((ich&3)==2)) ? 256 : 0;\n\t\telse\n\t\t\tChnSettings[ich].nPan = (((ich&3)==1) || ((ich&3)==2)) ? 0xC0 : 0x40;\n\t}\n\t// Reading channels\n\tfor (UINT ipat=0; ipat<nbp; ipat++)\n\t{\n\t\tif (ipat < MAX_PATTERNS)\n\t\t{\n\t\t\tif ((Patterns[ipat] = AllocatePattern(64, m_nChannels)) == NULL) break;\n\t\t\tPatternSize[ipat] = 64;\n\t\t\tif (dwMemPos + m_nChannels*256 >= dwMemLength) break;\n\t\t\tMODCOMMAND *m = Patterns[ipat];\n\t\t\tLPCBYTE p = lpStream + dwMemPos;\n\t\t\tfor (UINT j=m_nChannels*64; j; m++,p+=4,j--)\n\t\t\t{\n\t\t\t\tBYTE A0=p[0], A1=p[1], A2=p[2], A3=p[3];\n\t\t\t\tUINT n = ((((UINT)A0 & 0x0F) << 8) | (A1));\n\t\t\t\tif ((n) && (n != 0xFFF)) m->note = GetNoteFromPeriod(n << 2);\n\t\t\t\tm->instr = ((UINT)A2 >> 4) | (A0 & 0x10);\n\t\t\t\tm->command = A2 & 0x0F;\n\t\t\t\tm->param = A3;\n\t\t\t\tif ((m->command) || (m->param)) ConvertModCommand(m);\n\t\t\t}\n\t\t}\n\t\tdwMemPos += m_nChannels*256;\n\t}\n\t// Reading instruments\n\tDWORD dwErrCheck = 0;\n\tfor (UINT ismp=1; ismp<=m_nSamples; ismp++) if (Ins[ismp].nLength)\n\t{\n\t\tLPSTR p = (LPSTR)(lpStream+dwMemPos);\n\t\tUINT flags = 0;\n\t\tif (dwMemPos + 5 >= dwMemLength) break;\n\t\tif (! strncmp(p, \"ADPCM\", 5))\n\t\t{\n\t\t\tflags = 3;\n\t\t\tp += 5;\n\t\t\tdwMemPos += 5;\n\t\t}\n\t\tDWORD dwSize = ReadSample(&Ins[ismp], flags, p, dwMemLength - dwMemPos);\n\t\tif (dwSize)\n\t\t{\n\t\t\tdwMemPos += dwSize;\n\t\t\tdwErrCheck++;\n\t\t}\n\t}\n#ifdef MODPLUG_TRACKER\n\treturn TRUE;\n#else\n\treturn (dwErrCheck) ? TRUE : FALSE;\n#endif\n}\n\n\n#ifndef MODPLUG_NO_FILESAVE\n\n#ifdef _MSC_VER\n#pragma warning(disable:4100)\n#endif\n\nBOOL CSoundFile::SaveMod(LPCSTR lpszFileName, UINT nPacking)\n//----------------------------------------------------------\n{\n\tBYTE insmap[32];\n\tUINT inslen[32];\n\tBYTE bTab[32];\n\tBYTE ord[128];\n\tFILE *f;\n\n\tif ((!m_nChannels) || (!lpszFileName)) return FALSE;\n\tif ((f = fopen(lpszFileName, \"wb\")) == NULL) return FALSE;\n\tmemset(ord, 0, sizeof(ord));\n\tmemset(inslen, 0, sizeof(inslen));\n\tif (m_nInstruments)\n\t{\n\t\tmemset(insmap, 0, sizeof(insmap));\n\t\tfor (UINT i=1; i<32; i++) if (Headers[i])\n\t\t{\n\t\t\tfor (UINT j=0; j<128; j++) if (Headers[i]->Keyboard[j])\n\t\t\t{\n\t\t\t\tinsmap[i] = Headers[i]->Keyboard[j];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t} else\n\t{\n\t\tfor (UINT i=0; i<32; i++) insmap[i] = (BYTE)i;\n\t}\n\t// Writing song name\n\tfwrite(m_szNames, 20, 1, f);\n\t// Writing instrument definition\n\tfor (UINT iins=1; iins<=31; iins++)\n\t{\n\t\tMODINSTRUMENT *pins = &Ins[insmap[iins]];\n\t\tmemcpy(bTab, m_szNames[iins],22);\n\t\tinslen[iins] = pins->nLength;\n\t\tif (inslen[iins] > 0x1fff0) inslen[iins] = 0x1fff0;\n\t\tbTab[22] = inslen[iins] >> 9;\n\t\tbTab[23] = inslen[iins] >> 1;\n\t\tif (pins->RelativeTone < 0) bTab[24] = 0x08; else\n\t\tif (pins->RelativeTone > 0) bTab[24] = 0x07; else\n\t\tbTab[24] = (BYTE)XM2MODFineTune(pins->nFineTune);\n\t\tbTab[25] = pins->nVolume >> 2;\n\t\tbTab[26] = pins->nLoopStart >> 9;\n\t\tbTab[27] = pins->nLoopStart >> 1;\n\t\tbTab[28] = (pins->nLoopEnd - pins->nLoopStart) >> 9;\n\t\tbTab[29] = (pins->nLoopEnd - pins->nLoopStart) >> 1;\n\t\tfwrite(bTab, 30, 1, f);\n\t}\n\t// Writing number of patterns\n\tUINT nbp=0, norders=128;\n\tfor (UINT iord=0; iord<128; iord++)\n\t{\n\t\tif (Order[iord] == 0xFF)\n\t\t{\n\t\t\tnorders = iord;\n\t\t\tbreak;\n\t\t}\n\t\tif ((Order[iord] < 0x80) && (nbp<=Order[iord])) nbp = Order[iord]+1;\n\t}\n\tbTab[0] = norders;\n\tbTab[1] = m_nRestartPos;\n\tfwrite(bTab, 2, 1, f);\n\t// Writing pattern list\n\tif (norders) memcpy(ord, Order, norders);\n\tfwrite(ord, 128, 1, f);\n\t// Writing signature\n\tif (m_nChannels == 4)\n\t\tlstrcpy((LPSTR)&bTab, \"M.K.\");\n\telse\n\t\twsprintf((LPSTR)&bTab, \"%luCHN\", m_nChannels);\n\tfwrite(bTab, 4, 1, f);\n\t// Writing patterns\n\tfor (UINT ipat=0; ipat<nbp; ipat++) if (Patterns[ipat])\n\t{\n\t\tBYTE s[64*4];\n\t\tMODCOMMAND *m = Patterns[ipat];\n\t\tfor (UINT i=0; i<64; i++) if (i < PatternSize[ipat])\n\t\t{\n\t\t\tLPBYTE p=s;\n\t\t\tfor (UINT c=0; c<m_nChannels; c++,p+=4,m++)\n\t\t\t{\n\t\t\t\tUINT param = ModSaveCommand(m, FALSE);\n\t\t\t\tUINT command = param >> 8;\n\t\t\t\tparam &= 0xFF;\n\t\t\t\tif (command > 0x0F) command = param = 0;\n\t\t\t\tif ((m->vol >= 0x10) && (m->vol <= 0x50) && (!command) && (!param)) { command = 0x0C; param = m->vol - 0x10; }\n\t\t\t\tUINT period = m->note;\n\t\t\t\tif (period)\n\t\t\t\t{\n\t\t\t\t\tif (period < 37) period = 37;\n\t\t\t\t\tperiod -= 37;\n\t\t\t\t\tif (period >= 6*12) period = 6*12-1;\n\t\t\t\t\tperiod = ProTrackerPeriodTable[period];\n\t\t\t\t}\n\t\t\t\tUINT instr = (m->instr > 31) ? 0 : m->instr;\n\t\t\t\tp[0] = ((period >> 8) & 0x0F) | (instr & 0x10);\n\t\t\t\tp[1] = period & 0xFF;\n\t\t\t\tp[2] = ((instr & 0x0F) << 4) | (command & 0x0F);\n\t\t\t\tp[3] = param;\n\t\t\t}\n\t\t\tfwrite(s, m_nChannels, 4, f);\n\t\t} else\n\t\t{\n\t\t\tmemset(s, 0, m_nChannels*4);\n\t\t\tfwrite(s, m_nChannels, 4, f);\n\t\t}\n\t}\n\t// Writing instruments\n\tfor (UINT ismpd=1; ismpd<=31; ismpd++) if (inslen[ismpd])\n\t{\n\t\tMODINSTRUMENT *pins = &Ins[insmap[ismpd]];\n\t\tUINT flags = RS_PCM8S;\n#ifndef NO_PACKING\n\t\tif (!(pins->uFlags & (CHN_16BIT|CHN_STEREO)))\n\t\t{\n\t\t\tif ((nPacking) && (CanPackSample((char *)pins->pSample, inslen[ismpd], nPacking)))\n\t\t\t{\n\t\t\t\tfwrite(\"ADPCM\", 1, 5, f);\n\t\t\t\tflags = RS_ADPCM4;\n\t\t\t}\n\t\t}\n#endif\n\t\tWriteSample(f, pins, flags, inslen[ismpd]);\n\t}\n\tfclose(f);\n\treturn TRUE;\n}\n\n#ifdef _MSC_VER\n#pragma warning(default:4100)\n#endif\n\n#endif // MODPLUG_NO_FILESAVE\n"
  },
  {
    "path": "contrib/libmodplug/src/load_mtm.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n//#pragma warning(disable:4244)\n\n//////////////////////////////////////////////////////////\n// MTM file support (import only)\n\n#pragma pack(1)\n\n\ntypedef struct tagMTMSAMPLE\n{\n        char samplename[22];      // changed from CHAR\n\tDWORD length;\n\tDWORD reppos;\n\tDWORD repend;\n\tCHAR finetune;\n\tBYTE volume;\n\tBYTE attribute;\n} MTMSAMPLE;\n\n\ntypedef struct tagMTMHEADER\n{\n\tchar id[4];\t    // MTM file marker + version // changed from CHAR\n\tchar songname[20];  // ASCIIZ songname  // changed from CHAR\n\tWORD numtracks;\t    // number of tracks saved\n\tBYTE lastpattern;   // last pattern number saved\n\tBYTE lastorder;\t    // last order number to play (songlength-1)\n\tWORD commentsize;   // length of comment field\n\tBYTE numsamples;    // number of samples saved\n\tBYTE attribute;\t    // attribute byte (unused)\n\tBYTE beatspertrack;\n\tBYTE numchannels;   // number of channels used\n\tBYTE panpos[32];    // voice pan positions\n} MTMHEADER;\n\n\n#pragma pack()\n\n\nBOOL CSoundFile::ReadMTM(LPCBYTE lpStream, DWORD dwMemLength)\n//-----------------------------------------------------------\n{\n\tMTMHEADER *pmh = (MTMHEADER *)lpStream;\n\tDWORD dwMemPos = 66;\n\n\tif ((!lpStream) || (dwMemLength < 0x100)) return FALSE;\n\tif ((strncmp(pmh->id, \"MTM\", 3)) || (pmh->numchannels > 32)\n\t || (pmh->numsamples >= MAX_SAMPLES) || (!pmh->numsamples)\n\t || (!pmh->numtracks) || (!pmh->numchannels)\n\t || (!pmh->lastpattern) || (pmh->lastpattern >= MAX_PATTERNS))\n\t\treturn FALSE;\n\tstrncpy(m_szNames[0], pmh->songname, 20);\n\tm_szNames[0][20] = 0;\n\tif (dwMemPos + 37*pmh->numsamples + 128 + 192*pmh->numtracks\n\t + 64 * (pmh->lastpattern+1) + pmh->commentsize >= dwMemLength) \n\t\treturn FALSE;\n\tm_nType = MOD_TYPE_MTM;\n\tm_nSamples = pmh->numsamples;\n\tm_nChannels = pmh->numchannels;\n\t// Reading instruments\n\tfor\t(UINT i=1; i<=m_nSamples; i++)\n\t{\n\t\tMTMSAMPLE *pms = (MTMSAMPLE *)(lpStream + dwMemPos);\n\t\tstrncpy(m_szNames[i], pms->samplename, 22);\n\t\tm_szNames[i][22] = 0;\n\t\tIns[i].nVolume = pms->volume << 2;\n\t\tIns[i].nGlobalVol = 64;\n\t\tDWORD len = pms->length;\n\t\tif ((len > 4) && (len <= MAX_SAMPLE_LENGTH))\n\t\t{\n\t\t\tIns[i].nLength = len;\n\t\t\tIns[i].nLoopStart = pms->reppos;\n\t\t\tIns[i].nLoopEnd = pms->repend;\n\t\t\tif (Ins[i].nLoopEnd > Ins[i].nLength) \n\t\t\t\tIns[i].nLoopEnd = Ins[i].nLength;\n\t\t\tif (Ins[i].nLoopStart + 4 >= Ins[i].nLoopEnd) \n\t\t\t\tIns[i].nLoopStart = Ins[i].nLoopEnd = 0;\n\t\t\tif (Ins[i].nLoopEnd) Ins[i].uFlags |= CHN_LOOP;\n\t\t\tIns[i].nFineTune = MOD2XMFineTune(pms->finetune);\n\t\t\tif (pms->attribute & 0x01)\n\t\t\t{\n\t\t\t\tIns[i].uFlags |= CHN_16BIT;\n\t\t\t\tIns[i].nLength >>= 1;\n\t\t\t\tIns[i].nLoopStart >>= 1;\n\t\t\t\tIns[i].nLoopEnd >>= 1;\n\t\t\t}\n\t\t\tIns[i].nPan = 128;\n\t\t}\n\t\tdwMemPos += 37;\n\t}\n\t// Setting Channel Pan Position\n\tfor (UINT ich=0; ich<m_nChannels; ich++)\n\t{\n\t\tChnSettings[ich].nPan = ((pmh->panpos[ich] & 0x0F) << 4) + 8;\n\t\tChnSettings[ich].nVolume = 64;\n\t}\n\t// Reading pattern order\n\tmemcpy(Order, lpStream + dwMemPos, pmh->lastorder+1);\n\tdwMemPos += 128;\n\t// Reading Patterns\n\tLPCBYTE pTracks = lpStream + dwMemPos;\n\tdwMemPos += 192 * pmh->numtracks;\n\tLPWORD pSeq = (LPWORD)(lpStream + dwMemPos);\n\tfor (UINT pat=0; pat<=pmh->lastpattern; pat++)\n\t{\n\t\tPatternSize[pat] = 64;\n\t\tif ((Patterns[pat] = AllocatePattern(64, m_nChannels)) == NULL) break;\n\t\tfor (UINT n=0; n<32; n++) if ((pSeq[n]) && (pSeq[n] <= pmh->numtracks) && (n < m_nChannels))\n\t\t{\n\t\t\tLPCBYTE p = pTracks + 192 * (pSeq[n]-1);\n\t\t\tMODCOMMAND *m = Patterns[pat] + n;\n\t\t\tfor (UINT i=0; i<64; i++, m+=m_nChannels, p+=3)\n\t\t\t{\n\t\t\t\tif (p[0] & 0xFC) m->note = (p[0] >> 2) + 37;\n\t\t\t\tm->instr = ((p[0] & 0x03) << 4) | (p[1] >> 4);\n\t\t\t\tUINT cmd = p[1] & 0x0F;\n\t\t\t\tUINT param = p[2];\n\t\t\t\tif (cmd == 0x0A)\n\t\t\t\t{\n\t\t\t\t\tif (param & 0xF0) param &= 0xF0; else param &= 0x0F;\n\t\t\t\t}\n\t\t\t\tm->command = cmd;\n\t\t\t\tm->param = param;\n\t\t\t\tif ((cmd) || (param)) ConvertModCommand(m);\n\t\t\t}\n\t\t}\n\t\tpSeq += 32;\n\t}\n\tdwMemPos += 64*(pmh->lastpattern+1);\n\tif ((pmh->commentsize) && (dwMemPos + pmh->commentsize < dwMemLength))\n\t{\n\t\tUINT n = pmh->commentsize;\n\t\tm_lpszSongComments = new char[n+1];\n\t\tif (m_lpszSongComments)\n\t\t{\n\t\t\tmemcpy(m_lpszSongComments, lpStream+dwMemPos, n);\n\t\t\tm_lpszSongComments[n] = 0;\n\t\t\tfor (UINT i=0; i<n; i++)\n\t\t\t{\n\t\t\t\tif (!m_lpszSongComments[i])\n\t\t\t\t{\n\t\t\t\t\tm_lpszSongComments[i] = ((i+1) % 40) ? 0x20 : 0x0D;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tdwMemPos += pmh->commentsize;\n\t// Reading Samples\n\tfor (UINT ismp=1; ismp<=m_nSamples; ismp++)\n\t{\n\t\tif (dwMemPos >= dwMemLength) break;\n\t\tdwMemPos += ReadSample(&Ins[ismp], (Ins[ismp].uFlags & CHN_16BIT) ? RS_PCM16U : RS_PCM8U,\n\t\t\t\t\t\t\t\t(LPSTR)(lpStream + dwMemPos), dwMemLength - dwMemPos);\n\t}\n\tm_nMinPeriod = 64;\n\tm_nMaxPeriod = 32767;\n\treturn TRUE;\n}\n\n"
  },
  {
    "path": "contrib/libmodplug/src/load_okt.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>,\n *          Adam Goode       <adam@evdebs.org> (endian and char fixes for PPC)\n*/\n\n//////////////////////////////////////////////\n// Oktalyzer (OKT) module loader            //\n//////////////////////////////////////////////\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n//#pragma warning(disable:4244)\n\ntypedef struct OKTFILEHEADER\n{\n\tDWORD okta;\t\t// \"OKTA\"\n\tDWORD song;\t\t// \"SONG\"\n\tDWORD cmod;\t\t// \"CMOD\"\n\tDWORD fixed8;\n\tBYTE chnsetup[8];\n\tDWORD samp;\t\t// \"SAMP\"\n\tDWORD samplen;\n} OKTFILEHEADER;\n\n\ntypedef struct OKTSAMPLE\n{\n\tCHAR name[20];\n\tDWORD length;\n\tWORD loopstart;\n\tWORD looplen;\n\tBYTE pad1;\n\tBYTE volume;\n\tBYTE pad2;\n\tBYTE pad3;\n} OKTSAMPLE;\n\n\nBOOL CSoundFile::ReadOKT(const BYTE *lpStream, DWORD dwMemLength)\n//---------------------------------------------------------------\n{\n\tconst OKTFILEHEADER *pfh = (OKTFILEHEADER *)lpStream;\n\tDWORD dwMemPos = sizeof(OKTFILEHEADER);\n\tUINT nsamples = 0, norders = 0;\n\n\tif ((!lpStream) || (dwMemLength < 1024)) return FALSE;\n\tif ((pfh->okta != 0x41544B4F) || (pfh->song != 0x474E4F53)\n\t || (pfh->cmod != 0x444F4D43) || (pfh->chnsetup[0]) || (pfh->chnsetup[2])\n\t || (pfh->chnsetup[4]) || (pfh->chnsetup[6]) || (pfh->fixed8 != 0x08000000)\n\t || (pfh->samp != 0x504D4153)) return FALSE;\n\tm_nType = MOD_TYPE_OKT;\n\tm_nChannels = 4 + pfh->chnsetup[1] + pfh->chnsetup[3] + pfh->chnsetup[5] + pfh->chnsetup[7];\n\tif (m_nChannels > MAX_CHANNELS) m_nChannels = MAX_CHANNELS;\n\tnsamples = bswapBE32(pfh->samplen) >> 5;\n\tm_nSamples = nsamples;\n\tif (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES-1;\n\t// Reading samples\n\tfor (UINT smp=1; smp <= nsamples; smp++)\n\t{\n\t\tif (dwMemPos >= dwMemLength) return TRUE;\n\t\tif (smp < MAX_SAMPLES)\n\t\t{\n\t\t\tOKTSAMPLE *psmp = (OKTSAMPLE *)(lpStream + dwMemPos);\n\t\t\tMODINSTRUMENT *pins = &Ins[smp];\n\n\t\t\tmemcpy(m_szNames[smp], psmp->name, 20);\n\t\t\tpins->uFlags = 0;\n\t\t\tpins->nLength = bswapBE32(psmp->length) & ~1;\n\t\t\tpins->nLoopStart = bswapBE16(psmp->loopstart);\n\t\t\tpins->nLoopEnd = pins->nLoopStart + bswapBE16(psmp->looplen);\n\t\t\tif (pins->nLoopStart + 2 < pins->nLoopEnd) pins->uFlags |= CHN_LOOP;\n\t\t\tpins->nGlobalVol = 64;\n\t\t\tpins->nVolume = psmp->volume << 2;\n\t\t\tpins->nC4Speed = 8363;\n\t\t}\n\t\tdwMemPos += sizeof(OKTSAMPLE);\n\t}\n\t// SPEE\n\tif (dwMemPos >= dwMemLength) return TRUE;\n\tif (*((DWORD *)(lpStream + dwMemPos)) == 0x45455053)\n\t{\n\t\tm_nDefaultSpeed = lpStream[dwMemPos+9];\n\t\tdwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8;\n\t}\n\t// SLEN\n\tif (dwMemPos >= dwMemLength) return TRUE;\n\tif (*((DWORD *)(lpStream + dwMemPos)) == 0x4E454C53)\n\t{\n\t\tdwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8;\n\t}\n\t// PLEN\n\tif (dwMemPos >= dwMemLength) return TRUE;\n\tif (*((DWORD *)(lpStream + dwMemPos)) == 0x4E454C50)\n\t{\n\t\tnorders = lpStream[dwMemPos+9];\n\t\tdwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8;\n\t}\n\t// PATT\n\tif (dwMemPos >= dwMemLength) return TRUE;\n\tif (*((DWORD *)(lpStream + dwMemPos)) == 0x54544150)\n\t{\n\t\tUINT orderlen = norders;\n\t\tif (orderlen >= MAX_ORDERS) orderlen = MAX_ORDERS-1;\n\t\tfor (UINT i=0; i<orderlen; i++) Order[i] = lpStream[dwMemPos+8+i];\n\t\tfor (UINT j=orderlen; j>1; j--) { if (Order[j-1]) break; Order[j-1] = 0xFF; }\n\t\tdwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8;\n\t}\n\t// PBOD\n\tUINT npat = 0;\n\twhile ((dwMemPos+10 < dwMemLength) && (*((DWORD *)(lpStream + dwMemPos)) == 0x444F4250))\n\t{\n\t\tDWORD dwPos = dwMemPos + 10;\n\t\tUINT rows = lpStream[dwMemPos+9];\n\t\tif (!rows) rows = 64;\n\t\tif (npat < MAX_PATTERNS)\n\t\t{\n\t\t\tif ((Patterns[npat] = AllocatePattern(rows, m_nChannels)) == NULL) return TRUE;\n\t\t\tMODCOMMAND *m = Patterns[npat];\n\t\t\tPatternSize[npat] = rows;\n\t\t\tUINT imax = m_nChannels*rows;\n\t\t\tfor (UINT i=0; i<imax; i++, m++, dwPos+=4)\n\t\t\t{\n\t\t\t\tif (dwPos+4 > dwMemLength) break;\n\t\t\t\tconst BYTE *p = lpStream+dwPos;\n\t\t\t\tUINT note = p[0];\n\t\t\t\tif (note)\n\t\t\t\t{\n\t\t\t\t\tm->note = note + 48;\n\t\t\t\t\tm->instr = p[1] + 1;\n\t\t\t\t}\n\t\t\t\tUINT command = p[2];\n\t\t\t\tUINT param = p[3];\n\t\t\t\tm->param = param;\n\t\t\t\tswitch(command)\n\t\t\t\t{\n\t\t\t\t// 0: no effect\n\t\t\t\tcase 0:\n\t\t\t\t\tbreak;\n\t\t\t\t// 1: Portamento Up\n\t\t\t\tcase 1:\n\t\t\t\tcase 17:\n\t\t\t\tcase 30:\n\t\t\t\t\tif (param) m->command = CMD_PORTAMENTOUP;\n\t\t\t\t\tbreak;\n\t\t\t\t// 2: Portamento Down\n\t\t\t\tcase 2:\n\t\t\t\tcase 13:\n\t\t\t\tcase 21:\n\t\t\t\t\tif (param) m->command = CMD_PORTAMENTODOWN;\n\t\t\t\t\tbreak;\n\t\t\t\t// 10: Arpeggio\n\t\t\t\tcase 10:\n\t\t\t\tcase 11:\n\t\t\t\tcase 12:\n\t\t\t\t\tm->command = CMD_ARPEGGIO;\n\t\t\t\t\tbreak;\n\t\t\t\t// 15: Filter\n\t\t\t\tcase 15:\n\t\t\t\t\tm->command = CMD_MODCMDEX;\n\t\t\t\t\tm->param = param & 0x0F;\n\t\t\t\t\tbreak;\n\t\t\t\t// 25: Position Jump\n\t\t\t\tcase 25:\n\t\t\t\t\tm->command = CMD_POSITIONJUMP;\n\t\t\t\t\tbreak;\n\t\t\t\t// 28: Set Speed\n\t\t\t\tcase 28:\n\t\t\t\t\tm->command = CMD_SPEED;\n\t\t\t\t\tbreak;\n\t\t\t\t// 31: Volume Control\n\t\t\t\tcase 31:\n\t\t\t\t\tif (param <= 0x40) m->command = CMD_VOLUME; else\n\t\t\t\t\tif (param <= 0x50) { m->command = CMD_VOLUMESLIDE; m->param &= 0x0F; if (!m->param) m->param = 0x0F; } else\n\t\t\t\t\tif (param <= 0x60) { m->command = CMD_VOLUMESLIDE; m->param = (param & 0x0F) << 4; if (!m->param) m->param = 0xF0; } else\n\t\t\t\t\tif (param <= 0x70) { m->command = CMD_MODCMDEX; m->param = 0xB0 | (param & 0x0F); if (!(param & 0x0F)) m->param = 0xBF; } else\n\t\t\t\t\tif (param <= 0x80) { m->command = CMD_MODCMDEX; m->param = 0xA0 | (param & 0x0F); if (!(param & 0x0F)) m->param = 0xAF; }\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tnpat++;\n\t\tdwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8;\n\t}\n\t// SBOD\n\tUINT nsmp = 1;\n\twhile ((dwMemPos+10 < dwMemLength) && (*((DWORD *)(lpStream + dwMemPos)) == 0x444F4253))\n\t{\n\t\tif (nsmp < MAX_SAMPLES) ReadSample(&Ins[nsmp], RS_PCM8S, (LPSTR)(lpStream+dwMemPos+8), dwMemLength-dwMemPos-8);\n\t\tdwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8;\n\t\tnsmp++;\n\t}\n\treturn TRUE;\n}\n\n"
  },
  {
    "path": "contrib/libmodplug/src/load_s3m.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>,\n *          Adam Goode       <adam@evdebs.org> (endian and char fixes for PPC)\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n#include \"tables.h\"\n\n#ifdef _MSC_VER\n//#pragma warning(disable:4244)\n#endif\n\n//////////////////////////////////////////////////////\n// ScreamTracker S3M file support\n\n#pragma pack(1)\ntypedef struct tagS3MSAMPLESTRUCT\n{\n\tBYTE type;\n\tCHAR dosname[12];\n\tBYTE hmem;\n\tWORD memseg;\n\tDWORD length;\n\tDWORD loopbegin;\n\tDWORD loopend;\n\tBYTE vol;\n\tBYTE bReserved;\n\tBYTE pack;\n\tBYTE flags;\n\tDWORD finetune;\n\tDWORD dwReserved;\n\tWORD intgp;\n\tWORD int512;\n\tDWORD lastused;\n\tCHAR name[28];\n\tCHAR scrs[4];\n} S3MSAMPLESTRUCT;\n\n\ntypedef struct tagS3MFILEHEADER\n{\n\tCHAR name[28];\n\tBYTE b1A;\n\tBYTE type;\n\tWORD reserved1;\n\tWORD ordnum;\n\tWORD insnum;\n\tWORD patnum;\n\tWORD flags;\n\tWORD cwtv;\n\tWORD version;\n\tDWORD scrm;\t// \"SCRM\" = 0x4D524353\n\tBYTE globalvol;\n\tBYTE speed;\n\tBYTE tempo;\n\tBYTE mastervol;\n\tBYTE ultraclicks;\n\tBYTE panning_present;\n\tBYTE reserved2[8];\n\tWORD special;\n\tBYTE channels[32];\n} S3MFILEHEADER;\n\n\nvoid CSoundFile::S3MConvert(MODCOMMAND *m, BOOL bIT) const\n//--------------------------------------------------------\n{\n\tUINT command = m->command;\n\tUINT param = m->param;\n\tswitch (command + 0x40)\n\t{\n\tcase 'A':\tcommand = CMD_SPEED; break;\n\tcase 'B':\tcommand = CMD_POSITIONJUMP; break;\n\tcase 'C':\tcommand = CMD_PATTERNBREAK; if (!bIT) param = (param >> 4) * 10 + (param & 0x0F); break;\n\tcase 'D':\tcommand = CMD_VOLUMESLIDE; break;\n\tcase 'E':\tcommand = CMD_PORTAMENTODOWN; break;\n\tcase 'F':\tcommand = CMD_PORTAMENTOUP; break;\n\tcase 'G':\tcommand = CMD_TONEPORTAMENTO; break;\n\tcase 'H':\tcommand = CMD_VIBRATO; break;\n\tcase 'I':\tcommand = CMD_TREMOR; break;\n\tcase 'J':\tcommand = CMD_ARPEGGIO; break;\n\tcase 'K':\tcommand = CMD_VIBRATOVOL; break;\n\tcase 'L':\tcommand = CMD_TONEPORTAVOL; break;\n\tcase 'M':\tcommand = CMD_CHANNELVOLUME; break;\n\tcase 'N':\tcommand = CMD_CHANNELVOLSLIDE; break;\n\tcase 'O':\tcommand = CMD_OFFSET; break;\n\tcase 'P':\tcommand = CMD_PANNINGSLIDE; break;\n\tcase 'Q':\tcommand = CMD_RETRIG; break;\n\tcase 'R':\tcommand = CMD_TREMOLO; break;\n\tcase 'S':\tcommand = CMD_S3MCMDEX; break;\n\tcase 'T':\tcommand = CMD_TEMPO; break;\n\tcase 'U':\tcommand = CMD_FINEVIBRATO; break;\n\tcase 'V':\tcommand = CMD_GLOBALVOLUME; break;\n\tcase 'W':\tcommand = CMD_GLOBALVOLSLIDE; break;\n\tcase 'X':\tcommand = CMD_PANNING8; break;\n\tcase 'Y':\tcommand = CMD_PANBRELLO; break;\n\tcase 'Z':\tcommand = CMD_MIDI; break;\n\tdefault:\tcommand = 0;\n\t}\n\tm->command = command;\n\tm->param = param;\n}\n\n\nvoid CSoundFile::S3MSaveConvert(UINT *pcmd, UINT *pprm, BOOL bIT) const\n//---------------------------------------------------------------------\n{\n\tUINT command = *pcmd;\n\tUINT param = *pprm;\n\tswitch(command)\n\t{\n\tcase CMD_SPEED:\t\t\t\tcommand = 'A'; break;\n\tcase CMD_POSITIONJUMP:\t\tcommand = 'B'; break;\n\tcase CMD_PATTERNBREAK:\t\tcommand = 'C'; if (!bIT) param = ((param / 10) << 4) + (param % 10); break;\n\tcase CMD_VOLUMESLIDE:\t\tcommand = 'D'; break;\n\tcase CMD_PORTAMENTODOWN:\tcommand = 'E'; if ((param >= 0xE0) && (m_nType & (MOD_TYPE_MOD|MOD_TYPE_XM))) param = 0xDF; break;\n\tcase CMD_PORTAMENTOUP:\t\tcommand = 'F'; if ((param >= 0xE0) && (m_nType & (MOD_TYPE_MOD|MOD_TYPE_XM))) param = 0xDF; break;\n\tcase CMD_TONEPORTAMENTO:\tcommand = 'G'; break;\n\tcase CMD_VIBRATO:\t\t\tcommand = 'H'; break;\n\tcase CMD_TREMOR:\t\t\tcommand = 'I'; break;\n\tcase CMD_ARPEGGIO:\t\t\tcommand = 'J'; break;\n\tcase CMD_VIBRATOVOL:\t\tcommand = 'K'; break;\n\tcase CMD_TONEPORTAVOL:\t\tcommand = 'L'; break;\n\tcase CMD_CHANNELVOLUME:\t\tcommand = 'M'; break;\n\tcase CMD_CHANNELVOLSLIDE:\tcommand = 'N'; break;\n\tcase CMD_OFFSET:\t\t\tcommand = 'O'; break;\n\tcase CMD_PANNINGSLIDE:\t\tcommand = 'P'; break;\n\tcase CMD_RETRIG:\t\t\tcommand = 'Q'; break;\n\tcase CMD_TREMOLO:\t\t\tcommand = 'R'; break;\n\tcase CMD_S3MCMDEX:\t\t\tcommand = 'S'; break;\n\tcase CMD_TEMPO:\t\t\t\tcommand = 'T'; break;\n\tcase CMD_FINEVIBRATO:\t\tcommand = 'U'; break;\n\tcase CMD_GLOBALVOLUME:\t\tcommand = 'V'; break;\n\tcase CMD_GLOBALVOLSLIDE:\tcommand = 'W'; break;\n\tcase CMD_PANNING8:\n\t\tcommand = 'X';\n\t\tif ((bIT) && (m_nType != MOD_TYPE_IT) && (m_nType != MOD_TYPE_XM))\n\t\t{\n\t\t\tif (param == 0xA4) { command = 'S'; param = 0x91; }\telse\n\t\t\tif (param <= 0x80) { param <<= 1; if (param > 255) param = 255; } else\n\t\t\tcommand = param = 0;\n\t\t} else\n\t\tif ((!bIT) && ((m_nType == MOD_TYPE_IT) || (m_nType == MOD_TYPE_XM)))\n\t\t{\n\t\t\tparam >>= 1;\n\t\t}\n\t\tbreak;\n\tcase CMD_PANBRELLO:\t\t\tcommand = 'Y'; break;\n\tcase CMD_MIDI:\t\t\t\tcommand = 'Z'; break;\n\tcase CMD_XFINEPORTAUPDOWN:\n\t\tif (param & 0x0F) switch(param & 0xF0)\n\t\t{\n\t\tcase 0x10:\tcommand = 'F'; param = (param & 0x0F) | 0xE0; break;\n\t\tcase 0x20:\tcommand = 'E'; param = (param & 0x0F) | 0xE0; break;\n\t\tcase 0x90:\tcommand = 'S'; break;\n\t\tdefault:\tcommand = param = 0;\n\t\t} else command = param = 0;\n\t\tbreak;\n\tcase CMD_MODCMDEX:\n\t\tcommand = 'S';\n\t\tswitch(param & 0xF0)\n\t\t{\n\t\tcase 0x00:\tcommand = param = 0; break;\n\t\tcase 0x10:\tcommand = 'F'; param |= 0xF0; break;\n\t\tcase 0x20:\tcommand = 'E'; param |= 0xF0; break;\n\t\tcase 0x30:\tparam = (param & 0x0F) | 0x10; break;\n\t\tcase 0x40:\tparam = (param & 0x0F) | 0x30; break;\n\t\tcase 0x50:\tparam = (param & 0x0F) | 0x20; break;\n\t\tcase 0x60:\tparam = (param & 0x0F) | 0xB0; break;\n\t\tcase 0x70:\tparam = (param & 0x0F) | 0x40; break;\n\t\tcase 0x90:\tcommand = 'Q'; param &= 0x0F; break;\n\t\tcase 0xA0:\tif (param & 0x0F) { command = 'D'; param = (param << 4) | 0x0F; } else command=param=0; break;\n\t\tcase 0xB0:\tif (param & 0x0F) { command = 'D'; param |= 0xF0; } else command=param=0; break;\n\t\t}\n\t\tbreak;\n\tdefault:\tcommand = param = 0;\n\t}\n\tcommand &= ~0x40;\n\t*pcmd = command;\n\t*pprm = param;\n}\n\nstatic DWORD boundInput(DWORD input, DWORD smin, DWORD smax)\n{\n\tif (input > smax) input = smax;\n\telse if (input < smin) input = 0;\n\treturn(input);\n}\n\n\nBOOL CSoundFile::ReadS3M(const BYTE *lpStream, DWORD dwMemLength)\n//---------------------------------------------------------------\n{\n\tUINT insnum,patnum,nins,npat;\n\tDWORD insfile[MAX_SAMPLES];\n\tWORD ptr[256];\n\tDWORD dwMemPos;\n\tBYTE insflags[MAX_SAMPLES], inspack[MAX_SAMPLES];\n\n\tif ((!lpStream) || (dwMemLength <= sizeof(S3MFILEHEADER)+sizeof(S3MSAMPLESTRUCT)+64)) return FALSE;\n\tS3MFILEHEADER psfh = *(S3MFILEHEADER *)lpStream;\n\n\tpsfh.reserved1 = bswapLE16(psfh.reserved1);\n\tpsfh.ordnum = bswapLE16(psfh.ordnum);\n\tpsfh.insnum = bswapLE16(psfh.insnum);\n\tpsfh.patnum = bswapLE16(psfh.patnum);\n\tpsfh.flags = bswapLE16(psfh.flags);\n\tpsfh.cwtv = bswapLE16(psfh.cwtv);\n\tpsfh.version = bswapLE16(psfh.version);\n\tpsfh.scrm = bswapLE32(psfh.scrm);\n\tpsfh.special = bswapLE16(psfh.special);\n\n\tif (psfh.scrm != 0x4D524353) return FALSE;\n\tdwMemPos = 0x60;\n\tm_nType = MOD_TYPE_S3M;\n\tmemset(m_szNames,0,sizeof(m_szNames));\n\tmemcpy(m_szNames[0], psfh.name, 28);\n\t// Speed\n\tm_nDefaultSpeed = psfh.speed;\n\tif (m_nDefaultSpeed < 1) m_nDefaultSpeed = 6;\n\tif (m_nDefaultSpeed > 0x1F) m_nDefaultSpeed = 0x1F;\n\t// Tempo\n\tm_nDefaultTempo = psfh.tempo;\n\tif (m_nDefaultTempo < 40) m_nDefaultTempo = 40;\n\tif (m_nDefaultTempo > 240) m_nDefaultTempo = 240;\n\t// Global Volume\n\tm_nDefaultGlobalVolume = psfh.globalvol << 2;\n\tif ((!m_nDefaultGlobalVolume) || (m_nDefaultGlobalVolume > 256)) m_nDefaultGlobalVolume = 256;\n\tm_nSongPreAmp = psfh.mastervol & 0x7F;\n\t// Channels\n\tm_nChannels = 4;\n\tfor (UINT ich=0; ich<32; ich++)\n\t{\n\t\tChnSettings[ich].nPan = 128;\n\t\tChnSettings[ich].nVolume = 64;\n\n\t\tChnSettings[ich].dwFlags = CHN_MUTE;\n\t\tif (psfh.channels[ich] != 0xFF)\n\t\t{\n\t\t\tm_nChannels = ich+1;\n\t\t\tUINT b = psfh.channels[ich] & 0x0F;\n\t\t\tChnSettings[ich].nPan = (b & 8) ? 0xC0 : 0x40;\n\t\t\tChnSettings[ich].dwFlags = 0;\n\t\t}\n\t}\n\tif (m_nChannels < 4) m_nChannels = 4;\n\tif ((psfh.cwtv < 0x1320) || (psfh.flags & 0x40)) m_dwSongFlags |= SONG_FASTVOLSLIDES;\n\t// Reading pattern order\n\tUINT iord = psfh.ordnum;\n\tif (iord<1) iord = 1;\n\tif (iord > MAX_ORDERS) iord = MAX_ORDERS;\n\tif (iord)\n\t{\n\t\tmemcpy(Order, lpStream+dwMemPos, iord);\n\t\tdwMemPos += iord;\n\t}\n\tif ((iord & 1) && (lpStream[dwMemPos] == 0xFF)) dwMemPos++;\n\t// Reading file pointers\n\tinsnum = nins = psfh.insnum;\n\tif (insnum >= MAX_SAMPLES) insnum = MAX_SAMPLES-1;\n\tm_nSamples = insnum;\n\tpatnum = npat = psfh.patnum;\n\tif (patnum > MAX_PATTERNS) patnum = MAX_PATTERNS;\n\tmemset(ptr, 0, sizeof(ptr));\n\n\t// Ignore file if it has a corrupted header.\n\tif (nins+npat > 256) return FALSE;\n\n\tif (nins+npat)\n\t{\n\t\tmemcpy(ptr, lpStream+dwMemPos, 2*(nins+npat));\n\t\tdwMemPos += 2*(nins+npat);\n\t\tfor (UINT j = 0; j < (nins+npat); ++j) {\n\t\t        ptr[j] = bswapLE16(ptr[j]);\n\t\t}\n\t\tif (psfh.panning_present == 252)\n\t\t{\n\t\t\tconst BYTE *chnpan = lpStream+dwMemPos;\n\t\t\tfor (UINT i=0; i<32; i++) if (chnpan[i] & 0x20)\n\t\t\t{\n\t\t\t\tChnSettings[i].nPan = ((chnpan[i] & 0x0F) << 4) + 8;\n\t\t\t}\n\t\t}\n\t}\n\tif (!m_nChannels) return TRUE;\n\t// Reading instrument headers\n\tmemset(insfile, 0, sizeof(insfile));\n\tfor (UINT iSmp=1; iSmp<=insnum; iSmp++)\n\t{\n\t\tUINT nInd = ((DWORD)ptr[iSmp-1])*16;\n\t\tif ((!nInd) || (nInd + 0x50 > dwMemLength)) {\n\t\t\t// initialize basic variables.\n\t\t\tinsflags[iSmp-1] = 0;\n\t\t\tinspack[iSmp-1] = 0;\n\t\t\tcontinue;\n\t\t}\n\t\tS3MSAMPLESTRUCT pSmp;\n\t\tmemcpy(&pSmp, lpStream+nInd, 0x50);\n\t\tmemcpy(Ins[iSmp].name, &pSmp.dosname, 12);\n\t\tinsflags[iSmp-1] = pSmp.flags;\n\t\tinspack[iSmp-1] = pSmp.pack;\n\t\tmemcpy(m_szNames[iSmp], pSmp.name, 28);\n\t\tm_szNames[iSmp][28] = 0;\n\t\tif ((pSmp.type==1) && (pSmp.scrs[2]=='R') && (pSmp.scrs[3]=='S'))\n\t\t{\n\t\t\tIns[iSmp].nLength = boundInput(bswapLE32(pSmp.length), 4, MAX_SAMPLE_LENGTH);\n\t\t\tIns[iSmp].nLoopStart = boundInput(bswapLE32(pSmp.loopbegin), 4, Ins[iSmp].nLength - 1);\n\t\t\tIns[iSmp].nLoopEnd = boundInput(bswapLE32(pSmp.loopend), 4, Ins[iSmp].nLength);\n\t\t\tIns[iSmp].nVolume = boundInput(pSmp.vol, 0, 64) << 2;\n\t\t\tIns[iSmp].nGlobalVol = 64;\n\t\t\tif (pSmp.flags&1) Ins[iSmp].uFlags |= CHN_LOOP;\n\t\t\tUINT j = bswapLE32(pSmp.finetune);\n\t\t\tif (!j) j = 8363;\n\t\t\tif (j < 1024) j = 1024;\n\t\t\tIns[iSmp].nC4Speed = j;\n\t\t\tinsfile[iSmp] = (pSmp.hmem << 20) + (bswapLE16(pSmp.memseg) << 4);\n\t\t\t// offset is invalid - ignore this sample.\n\t\t\tif (insfile[iSmp] > dwMemLength) insfile[iSmp] = 0;\n\t\t\telse if (insfile[iSmp]) {\n\t\t\t\t// ignore duplicate samples.\n\t\t\t\tfor (int z=iSmp-1; z>=0; z--)\n\t\t\t\t\tif (insfile[iSmp] == insfile[z])\n\t\t\t\t\t\tinsfile[iSmp] = 0;\n\t\t\t}\n\t\t\tif ((Ins[iSmp].nLoopStart >= Ins[iSmp].nLoopEnd) || (Ins[iSmp].nLoopEnd - Ins[iSmp].nLoopStart < 8))\n\t\t\t\tIns[iSmp].nLoopStart = Ins[iSmp].nLoopEnd = 0;\n\t\t\tIns[iSmp].nPan = 0x80;\n\t\t}\n\t}\n\t// Reading patterns\n\tfor (UINT iPat=0; iPat<patnum; iPat++)\n\t{\n\t\tUINT nInd = ((DWORD)ptr[nins+iPat]) << 4;\n\t\tif (nInd + 0x40 > dwMemLength) continue;\n\t\tWORD len = bswapLE16(*((WORD *)(lpStream+nInd)));\n\t\tnInd += 2;\n\t\tPatternSize[iPat] = 64;\n\t\tif ((!len) || (nInd + len > dwMemLength - 6)\n\t\t || ((Patterns[iPat] = AllocatePattern(64, m_nChannels)) == NULL)) continue;\n\t\tLPBYTE src = (LPBYTE)(lpStream+nInd);\n\t\t// Unpacking pattern\n\t\tMODCOMMAND *p = Patterns[iPat];\n\t\tUINT row = 0;\n\t\tUINT j = 0;\n\t\twhile (j < len)\n\t\t{\n\t\t\tBYTE b = src[j++];\n\t\t\tif (!b)\n\t\t\t{\n\t\t\t\tif (++row >= 64) break;\n\t\t\t} else\n\t\t\t{\n\t\t\t\tUINT chn = b & 0x1F;\n\t\t\t\tif (chn < m_nChannels)\n\t\t\t\t{\n\t\t\t\t\tMODCOMMAND *m = &p[row*m_nChannels+chn];\n\t\t\t\t\tif (b & 0x20)\n\t\t\t\t\t{\n\t\t\t\t\t\tm->note = src[j++];\n\t\t\t\t\t\tif (m->note < 0xF0) m->note = (m->note & 0x0F) + 12*(m->note >> 4) + 13;\n\t\t\t\t\t\telse if (m->note == 0xFF) m->note = 0;\n\t\t\t\t\t\tm->instr = src[j++];\n\t\t\t\t\t}\n\t\t\t\t\tif (b & 0x40)\n\t\t\t\t\t{\n\t\t\t\t\t\tUINT vol = src[j++];\n\t\t\t\t\t\tif ((vol >= 128) && (vol <= 192))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvol -= 128;\n\t\t\t\t\t\t\tm->volcmd = VOLCMD_PANNING;\n\t\t\t\t\t\t} else\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (vol > 64) vol = 64;\n\t\t\t\t\t\t\tm->volcmd = VOLCMD_VOLUME;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tm->vol = vol;\n\t\t\t\t\t}\n\t\t\t\t\tif (b & 0x80)\n\t\t\t\t\t{\n\t\t\t\t\t\tm->command = src[j++];\n\t\t\t\t\t\tm->param = src[j++];\n\t\t\t\t\t\tif (m->command) S3MConvert(m, FALSE);\n\t\t\t\t\t}\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tif (b & 0x20) j += 2;\n\t\t\t\t\tif (b & 0x40) j++;\n\t\t\t\t\tif (b & 0x80) j += 2;\n\t\t\t\t}\n\t\t\t\tif (j >= len) break;\n\t\t\t}\n\t\t}\n\t}\n\t// Reading samples\n\tfor (UINT iRaw=1; iRaw<=insnum; iRaw++) if ((Ins[iRaw].nLength) && (insfile[iRaw]))\n\t{\n\t\tUINT flags = (psfh.version == 1) ? RS_PCM8S : RS_PCM8U;\n\t\tif (insflags[iRaw-1] & 4) flags += 5;\n\t\tif (insflags[iRaw-1] & 2) flags |= RSF_STEREO;\n\t\tif (inspack[iRaw-1] == 4) flags = RS_ADPCM4;\n\t\tdwMemPos = insfile[iRaw];\n\t\tif (dwMemPos < dwMemLength)\n\t\t\tReadSample(&Ins[iRaw], flags, (LPSTR)(lpStream + dwMemPos), dwMemLength - dwMemPos);\n\t}\n\tm_nMinPeriod = 64;\n\tm_nMaxPeriod = 32767;\n\tif (psfh.flags & 0x10) m_dwSongFlags |= SONG_AMIGALIMITS;\n\treturn TRUE;\n}\n\n\n#ifndef MODPLUG_NO_FILESAVE\n\n#ifdef _MSC_VER\n#pragma warning(disable:4100)\n#endif\n\nstatic BYTE S3MFiller[16] =\n{\n\t0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,\n\t0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80\n};\n\n\nBOOL CSoundFile::SaveS3M(LPCSTR lpszFileName, UINT nPacking)\n//----------------------------------------------------------\n{\n\tFILE *f;\n\tBYTE header[0x60];\n\tUINT nbo,nbi,nbp,i;\n\tWORD patptr[128];\n\tWORD insptr[128];\n\tBYTE buffer[5*1024];\n\tS3MSAMPLESTRUCT insex[128];\n\n\tif ((!m_nChannels) || (!lpszFileName)) return FALSE;\n\tif ((f = fopen(lpszFileName, \"wb\")) == NULL) return FALSE;\n\t// Writing S3M header\n\tmemset(header, 0, sizeof(header));\n\tmemset(insex, 0, sizeof(insex));\n\tmemcpy(header, m_szNames[0], 0x1C);\n\theader[0x1B] = 0;\n\theader[0x1C] = 0x1A;\n\theader[0x1D] = 0x10;\n\tnbo = (GetNumPatterns() + 15) & 0xF0;\n\tif (!nbo) nbo = 16;\n\theader[0x20] = nbo & 0xFF;\n\theader[0x21] = nbo >> 8;\n\tnbi = m_nInstruments;\n\tif (!nbi) nbi = m_nSamples;\n\tif (nbi > 99) nbi = 99;\n\theader[0x22] = nbi & 0xFF;\n\theader[0x23] = nbi >> 8;\n\tnbp = 0;\n\tfor (i=0; Patterns[i]; i++) { nbp = i+1; if (nbp >= MAX_PATTERNS) break; }\n\tfor (i=0; i<MAX_ORDERS; i++) if ((Order[i] < MAX_PATTERNS) && (Order[i] >= nbp)) nbp = Order[i] + 1;\n\theader[0x24] = nbp & 0xFF;\n\theader[0x25] = nbp >> 8;\n\tif (m_dwSongFlags & SONG_FASTVOLSLIDES) header[0x26] |= 0x40;\n\tif ((m_nMaxPeriod < 20000) || (m_dwSongFlags & SONG_AMIGALIMITS)) header[0x26] |= 0x10;\n\theader[0x28] = 0x20;\n\theader[0x29] = 0x13;\n\theader[0x2A] = 0x02; // Version = 1 => Signed samples\n\theader[0x2B] = 0x00;\n\theader[0x2C] = 'S';\n\theader[0x2D] = 'C';\n\theader[0x2E] = 'R';\n\theader[0x2F] = 'M';\n\theader[0x30] = m_nDefaultGlobalVolume >> 2;\n\theader[0x31] = m_nDefaultSpeed;\n\theader[0x32] = m_nDefaultTempo;\n\theader[0x33] = ((m_nSongPreAmp < 0x20) ? 0x20 : m_nSongPreAmp) | 0x80;\t// Stereo\n\theader[0x35] = 0xFC;\n\tfor (i=0; i<32; i++)\n\t{\n\t\tif (i < m_nChannels)\n\t\t{\n\t\t\tUINT tmp = (i & 0x0F) >> 1;\n\t\t\theader[0x40+i] = (i & 0x10) | ((i & 1) ? 8+tmp : tmp);\n\t\t} else header[0x40+i] = 0xFF;\n\t}\n\tfwrite(header, 0x60, 1, f);\n\tfwrite(Order, nbo, 1, f);\n\tmemset(patptr, 0, sizeof(patptr));\n\tmemset(insptr, 0, sizeof(insptr));\n\tUINT ofs0 = 0x60 + nbo;\n\tUINT ofs1 = ((0x60 + nbo + nbi*2 + nbp*2 + 15) & 0xFFF0) + 0x20;\n\tUINT ofs = ofs1;\n\n\tfor (i=0; i<nbi; i++) insptr[i] = (WORD)((ofs + i*0x50) / 16);\n\tfor (i=0; i<nbp; i++) patptr[i] = (WORD)((ofs + nbi*0x50) / 16);\n\tfwrite(insptr, nbi, 2, f);\n\tfwrite(patptr, nbp, 2, f);\n\tif (header[0x35] == 0xFC)\n\t{\n\t\tBYTE chnpan[32];\n\t\tfor (i=0; i<32; i++)\n\t\t{\n\t\t\tchnpan[i] = 0x20 | (ChnSettings[i].nPan >> 4);\n\t\t}\n\t\tfwrite(chnpan, 0x20, 1, f);\n\t}\n\tif ((nbi*2+nbp*2) & 0x0F)\n\t{\n\t\tfwrite(S3MFiller, 0x10 - ((nbi*2+nbp*2) & 0x0F), 1, f);\n\t}\n\tofs1 = ftell(f);\n\tfwrite(insex, nbi, 0x50, f);\n\t// Packing patterns\n\tofs += nbi*0x50;\n\tfor (i=0; i<nbp; i++)\n\t{\n\t\tWORD len = 64;\n\t\tmemset(buffer, 0, sizeof(buffer));\n\t\tpatptr[i] = ofs / 16;\n\t\tif (Patterns[i])\n\t\t{\n\t\t\tlen = 2;\n\t\t\tMODCOMMAND *p = Patterns[i];\n\t\t\tfor (int row=0; row<64; row++) if (row < PatternSize[i])\n\t\t\t{\n\t\t\t\tfor (UINT j=0; j<m_nChannels; j++)\n\t\t\t\t{\n\t\t\t\t\tUINT b = j;\n\t\t\t\t\tMODCOMMAND *m = &p[row*m_nChannels+j];\n\t\t\t\t\tUINT note = m->note;\n\t\t\t\t\tUINT volcmd = m->volcmd;\n\t\t\t\t\tUINT vol = m->vol;\n\t\t\t\t\tUINT command = m->command;\n\t\t\t\t\tUINT param = m->param;\n\n\t\t\t\t\tif ((note) || (m->instr)) b |= 0x20;\n\t\t\t\t\tif (!note) note = 0xFF; else\n\t\t\t\t\tif (note >= 0xFE) note = 0xFE; else\n\t\t\t\t\tif (note < 13) note = 0; else note -= 13;\n\t\t\t\t\tif (note < 0xFE) note = (note % 12) + ((note / 12) << 4);\n\t\t\t\t\tif (command == CMD_VOLUME)\n\t\t\t\t\t{\n\t\t\t\t\t\tcommand = 0;\n\t\t\t\t\t\tif (param > 64) param = 64;\n\t\t\t\t\t\tvolcmd = VOLCMD_VOLUME;\n\t\t\t\t\t\tvol = param;\n\t\t\t\t\t}\n\t\t\t\t\tif (volcmd == VOLCMD_VOLUME) b |= 0x40; else\n\t\t\t\t\tif (volcmd == VOLCMD_PANNING) { vol |= 0x80; b |= 0x40; }\n\t\t\t\t\tif (command)\n\t\t\t\t\t{\n\t\t\t\t\t\tS3MSaveConvert(&command, &param, FALSE);\n\t\t\t\t\t\tif (command) b |= 0x80;\n\t\t\t\t\t}\n\t\t\t\t\tif (b & 0xE0)\n\t\t\t\t\t{\n\t\t\t\t\t\tbuffer[len++] = b;\n\t\t\t\t\t\tif (b & 0x20)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbuffer[len++] = note;\n\t\t\t\t\t\t\tbuffer[len++] = m->instr;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (b & 0x40)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbuffer[len++] = vol;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (b & 0x80)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbuffer[len++] = command;\n\t\t\t\t\t\t\tbuffer[len++] = param;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (len > sizeof(buffer) - 20) break;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbuffer[len++] = 0;\n\t\t\t\tif (len > sizeof(buffer) - 20) break;\n\t\t\t}\n\t\t}\n\t\tbuffer[0] = (len - 2) & 0xFF;\n\t\tbuffer[1] = (len - 2) >> 8;\n\t\tlen = (len+15) & (~0x0F);\n\t\tfwrite(buffer, len, 1, f);\n\t\tofs += len;\n\t}\n\t// Writing samples\n\tfor (i=1; i<=nbi; i++)\n\t{\n\t\tMODINSTRUMENT *pins = &Ins[i];\n\t\tif (m_nInstruments)\n\t\t{\n\t\t\tpins = Ins;\n\t\t\tif (Headers[i])\n\t\t\t{\n\t\t\t\tfor (UINT j=0; j<128; j++)\n\t\t\t\t{\n\t\t\t\t\tUINT n = Headers[i]->Keyboard[j];\n\t\t\t\t\tif ((n) && (n < MAX_INSTRUMENTS))\n\t\t\t\t\t{\n\t\t\t\t\t\tpins = &Ins[n];\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tmemcpy(insex[i-1].dosname, pins->name, 12);\n\t\tmemcpy(insex[i-1].name, m_szNames[i], 28);\n\t\tmemcpy(insex[i-1].scrs, \"SCRS\", 4);\n\t\tinsex[i-1].hmem = (BYTE)((DWORD)ofs >> 20);\n\t\tinsex[i-1].memseg = (WORD)((DWORD)ofs >> 4);\n\t\tif (pins->pSample)\n\t\t{\n\t\t\tinsex[i-1].type = 1;\n\t\t\tinsex[i-1].length = pins->nLength;\n\t\t\tinsex[i-1].loopbegin = pins->nLoopStart;\n\t\t\tinsex[i-1].loopend = pins->nLoopEnd;\n\t\t\tinsex[i-1].vol = pins->nVolume / 4;\n\t\t\tinsex[i-1].flags = (pins->uFlags & CHN_LOOP) ? 1 : 0;\n\t\t\tif (pins->nC4Speed)\n\t\t\t\tinsex[i-1].finetune = pins->nC4Speed;\n\t\t\telse\n\t\t\t\tinsex[i-1].finetune = TransposeToFrequency(pins->RelativeTone, pins->nFineTune);\n\t\t\tUINT flags = RS_PCM8U;\n#ifndef NO_PACKING\n\t\t\tif (nPacking)\n\t\t\t{\n\t\t\t\tif ((!(pins->uFlags & (CHN_16BIT|CHN_STEREO)))\n\t\t\t\t && (CanPackSample((char *)pins->pSample, pins->nLength, nPacking)))\n\t\t\t\t{\n\t\t\t\t\tinsex[i-1].pack = 4;\n\t\t\t\t\tflags = RS_ADPCM4;\n\t\t\t\t}\n\t\t\t} else\n#endif // NO_PACKING\n\t\t\t{\n\t\t\t\tif (pins->uFlags & CHN_16BIT)\n\t\t\t\t{\n\t\t\t\t\tinsex[i-1].flags |= 4;\n\t\t\t\t\tflags = RS_PCM16U;\n\t\t\t\t}\n\t\t\t\tif (pins->uFlags & CHN_STEREO)\n\t\t\t\t{\n\t\t\t\t\tinsex[i-1].flags |= 2;\n\t\t\t\t\tflags = (pins->uFlags & CHN_16BIT) ? RS_STPCM16U : RS_STPCM8U;\n\t\t\t\t}\n\t\t\t}\n\t\t\tDWORD len = WriteSample(f, pins, flags);\n\t\t\tif (len & 0x0F)\n\t\t\t{\n\t\t\t\tfwrite(S3MFiller, 0x10 - (len & 0x0F), 1, f);\n\t\t\t}\n\t\t\tofs += (len + 15) & (~0x0F);\n\t\t} else\n\t\t{\n\t\t\tinsex[i-1].length = 0;\n\t\t}\n\t}\n\t// Updating parapointers\n\tfseek(f, ofs0, SEEK_SET);\n\tfwrite(insptr, nbi, 2, f);\n\tfwrite(patptr, nbp, 2, f);\n\tfseek(f, ofs1, SEEK_SET);\n\tfwrite(insex, 0x50, nbi, f);\n\tfclose(f);\n\treturn TRUE;\n}\n\n#ifdef _MSC_VER\n#pragma warning(default:4100)\n#endif\n\n#endif // MODPLUG_NO_FILESAVE\n"
  },
  {
    "path": "contrib/libmodplug/src/load_stm.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n//#pragma warning(disable:4244)\n\n#pragma pack(1)\n\ntypedef struct tagSTMNOTE\n{\n\tBYTE note;\n\tBYTE insvol;\n\tBYTE volcmd;\n\tBYTE cmdinf;\n} STMNOTE;\n\n\n// Raw STM sampleinfo struct:\ntypedef struct tagSTMSAMPLE\n{\n\tCHAR filename[14];\t// Can't have long comments - just filename comments :)\n\tWORD reserved;\t\t// ISA in memory when in ST 2\n\tWORD length;\t\t// Sample length\n\tWORD loopbeg;\t\t// Loop start point\n\tWORD loopend;\t\t// Loop end point\n\tBYTE volume;\t\t// Volume\n\tBYTE reserved2;\t\t// More reserved crap\n\tWORD c2spd;\t\t\t// Good old c2spd\n\tBYTE reserved3[6];\t// Yet more of PSi's reserved crap\n} STMSAMPLE;\n\n\n// Raw STM header struct:\ntypedef struct tagSTMHEADER\n{\n\tchar songname[20];      // changed from CHAR\n\tchar trackername[8];\t// !SCREAM! for ST 2.xx  // changed from CHAR\n\tCHAR unused;\t\t\t// 0x1A\n\tCHAR filetype;\t\t\t// 1=song, 2=module (only 2 is supported, of course) :)\n\tCHAR ver_major;\t\t\t// Like 2\n\tCHAR ver_minor;\t\t\t// \"ditto\"\n\tBYTE inittempo;\t\t\t// initspeed= stm inittempo>>4\n\tBYTE numpat;\t\t\t// number of patterns\n\tBYTE globalvol;\t\t\t// <- WoW! a RiGHT TRiANGLE =8*)\n\tBYTE reserved[13];\t\t// More of PSi's internal crap\n\tSTMSAMPLE sample[31];\t// STM sample data\n\tBYTE patorder[128];\t\t// Docs say 64 - actually 128\n} STMHEADER;\n\n#pragma pack()\n\n\n\nBOOL CSoundFile::ReadSTM(const BYTE *lpStream, DWORD dwMemLength)\n//---------------------------------------------------------------\n{\n\tconst STMHEADER *phdr = (STMHEADER *)lpStream;\n\tDWORD dwMemPos = 0;\n\t\n\tif ((!lpStream) || (dwMemLength < sizeof(STMHEADER))) return FALSE;\n\tif ((phdr->filetype != 2) || (phdr->unused != 0x1A)\n\t || ((strncasecmp(phdr->trackername, \"!SCREAM!\", 8))\n\t  && (strncasecmp(phdr->trackername, \"BMOD2STM\", 8)))) return FALSE;\n\tmemcpy(m_szNames[0], phdr->songname, 20);\n\t// Read STM header\n\tm_nType = MOD_TYPE_STM;\n\tm_nSamples = 31;\n\tm_nChannels = 4;\n\tm_nInstruments = 0;\n\tm_nMinPeriod = 64;\n\tm_nMaxPeriod = 0x7FFF;\n\tm_nDefaultSpeed = phdr->inittempo >> 4;\n\tif (m_nDefaultSpeed < 1) m_nDefaultSpeed = 1;\n\tm_nDefaultTempo = 125;\n\tm_nDefaultGlobalVolume = phdr->globalvol << 2;\n\tif (m_nDefaultGlobalVolume > 256) m_nDefaultGlobalVolume = 256;\n\tmemcpy(Order, phdr->patorder, 128);\n\t// Setting up channels\n\tfor (UINT nSet=0; nSet<4; nSet++)\n\t{\n\t\tChnSettings[nSet].dwFlags = 0;\n\t\tChnSettings[nSet].nVolume = 64;\n\t\tChnSettings[nSet].nPan = (nSet & 1) ? 0x40 : 0xC0;\n\t}\n\t// Reading samples\n\tfor (UINT nIns=0; nIns<31; nIns++)\n\t{\n\t\tMODINSTRUMENT *pIns = &Ins[nIns+1];\n\t\tconst STMSAMPLE *pStm = &phdr->sample[nIns];  // STM sample data\n\t\tmemcpy(pIns->name, pStm->filename, 13);\n\t\tmemcpy(m_szNames[nIns+1], pStm->filename, 12);\n\t\tpIns->nC4Speed = bswapLE16(pStm->c2spd);\n\t\tpIns->nGlobalVol = 64;\n\t\tpIns->nVolume = pStm->volume << 2;\n\t\tif (pIns->nVolume > 256) pIns->nVolume = 256;\n\t\tpIns->nLength = bswapLE16(pStm->length);\n\t\tif ((pIns->nLength < 4) || (!pIns->nVolume)) pIns->nLength = 0;\n\t\tpIns->nLoopStart = bswapLE16(pStm->loopbeg);\n\t\tpIns->nLoopEnd = bswapLE16(pStm->loopend);\n\t\tif ((pIns->nLoopEnd > pIns->nLoopStart) && (pIns->nLoopEnd != 0xFFFF)) pIns->uFlags |= CHN_LOOP;\n\t}\n\tdwMemPos = sizeof(STMHEADER);\n\tfor (UINT nOrd=0; nOrd<MAX_ORDERS; nOrd++) if (Order[nOrd] >= 99) Order[nOrd] = 0xFF;\n\tUINT nPatterns = phdr->numpat;\n\tfor (UINT nPat=0; nPat<nPatterns; nPat++)\n\t{\n\t\tif (dwMemPos + 64*4*4 > dwMemLength) return TRUE;\n\t\tPatternSize[nPat] = 64;\n\t\tif ((Patterns[nPat] = AllocatePattern(64, m_nChannels)) == NULL) return TRUE;\n\t\tMODCOMMAND *m = Patterns[nPat];\n\t\tconst STMNOTE *p = (const STMNOTE *)(lpStream + dwMemPos);\n\t\tfor (UINT n=0; n<64*4; n++, p++, m++)\n\t\t{\n\t\t\tUINT note,ins,vol,cmd;\n\t\t\t// extract the various information from the 4 bytes that\n\t\t\t// make up a single note\n\t\t\tnote = p->note;\n\t\t\tins = p->insvol >> 3;\n\t\t\tvol = (p->insvol & 0x07) + (p->volcmd >> 1);\n\t\t\tcmd = p->volcmd & 0x0F;\n\t\t\tif ((ins) && (ins < 32)) m->instr = ins;\n\t\t\t// special values of [SBYTE0] are handled here ->\n\t\t\t// we have no idea if these strange values will ever be encountered\n\t\t\t// but it appears as though stms sound correct.\n\t\t\tif ((note == 0xFE) || (note == 0xFC)) m->note = 0xFE; else\n\t\t\t// if note < 251, then all three bytes are stored in the file\n\t\t\tif (note < 0xFC) m->note = (note >> 4)*12 + (note&0xf) + 37;\n\t\t\tif (vol <= 64) { m->volcmd = VOLCMD_VOLUME; m->vol = vol; }\n\t\t\tm->param = p->cmdinf;\n\t\t\tswitch(cmd)\n\t\t\t{\n\t\t\t// Axx set speed to xx\n\t\t\tcase 1:\tm->command = CMD_SPEED; m->param >>= 4; break;\n\t\t\t// Bxx position jump\n\t\t\tcase 2:\tm->command = CMD_POSITIONJUMP; break;\n\t\t\t// Cxx patternbreak to row xx\n\t\t\tcase 3:\tm->command = CMD_PATTERNBREAK; m->param = (m->param & 0xF0) * 10 + (m->param & 0x0F);\tbreak;\n\t\t\t// Dxy volumeslide\n\t\t\tcase 4:\tm->command = CMD_VOLUMESLIDE; break;\n\t\t\t// Exy toneslide down\n\t\t\tcase 5:\tm->command = CMD_PORTAMENTODOWN; break;\n\t\t\t// Fxy toneslide up\n\t\t\tcase 6:\tm->command = CMD_PORTAMENTOUP; break;\n\t\t\t// Gxx Tone portamento,speed xx\n\t\t\tcase 7:\tm->command = CMD_TONEPORTAMENTO; break;\n\t\t\t// Hxy vibrato\n\t\t\tcase 8:\tm->command = CMD_VIBRATO; break;\n\t\t\t// Ixy tremor, ontime x, offtime y\n\t\t\tcase 9:\tm->command = CMD_TREMOR; break;\n\t\t\t// Jxy arpeggio\n\t\t\tcase 10: m->command = CMD_ARPEGGIO; break;\n\t\t\t// Kxy Dual command H00 & Dxy\n\t\t\tcase 11: m->command = CMD_VIBRATOVOL; break;\n\t\t\t// Lxy Dual command G00 & Dxy\n\t\t\tcase 12: m->command = CMD_TONEPORTAVOL; break;\n\t\t\t// Xxx amiga command 8xx\n\t\t\tcase 0x18:\tm->command = CMD_PANNING8; break;\n\t\t\tdefault:\n\t\t\t\tm->command = m->param = 0;\n\t\t\t}\n\t\t}\n\t\tdwMemPos += 64*4*4;\n\t}\n\t// Reading Samples\n\tfor (UINT nSmp=1; nSmp<=31; nSmp++)\n\t{\n\t\tMODINSTRUMENT *pIns = &Ins[nSmp];\n\t\tdwMemPos = (dwMemPos + 15) & (~15);\n\t\tif (pIns->nLength)\n\t\t{\n\t\t\tUINT nPos = ((UINT)phdr->sample[nSmp-1].reserved) << 4;\n\t\t\tif ((nPos >= sizeof(STMHEADER)) && (nPos+pIns->nLength <= dwMemLength)) dwMemPos = nPos;\n\t\t\tif (dwMemPos < dwMemLength)\n\t\t\t{\n\t\t\t\tdwMemPos += ReadSample(pIns, RS_PCM8S, (LPSTR)(lpStream+dwMemPos),dwMemLength-dwMemPos);\n\t\t\t}\n\t\t}\n\t}\n\treturn TRUE;\n}\n\n"
  },
  {
    "path": "contrib/libmodplug/src/load_ult.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n//#pragma warning(disable:4244)\n\n#define ULT_16BIT   0x04\n#define ULT_LOOP    0x08\n#define ULT_BIDI    0x10\n\n#pragma pack(1)\n\n// Raw ULT header struct:\ntypedef struct tagULTHEADER\n{\n        char id[15];             // changed from CHAR\n        char songtitle[32];      // changed from CHAR\n\tBYTE reserved;\n} ULTHEADER;\n\n\n// Raw ULT sampleinfo struct:\ntypedef struct tagULTSAMPLE\n{\n\tCHAR samplename[32];\n\tCHAR dosname[12];\n\tLONG loopstart;\n\tLONG loopend;\n\tLONG sizestart;\n\tLONG sizeend;\n\tBYTE volume;\n\tBYTE flags;\n\tWORD finetune;\n} ULTSAMPLE;\n\n#pragma pack()\n\n\nBOOL CSoundFile::ReadUlt(const BYTE *lpStream, DWORD dwMemLength)\n//---------------------------------------------------------------\n{\n\tULTHEADER *pmh = (ULTHEADER *)lpStream;\n\tULTSAMPLE *pus;\n\tUINT nos, nop;\n\tDWORD dwMemPos = 0;\n\n\t// try to read module header\n\tif ((!lpStream) || (dwMemLength < 0x100)) return FALSE;\n\tif (strncmp(pmh->id,\"MAS_UTrack_V00\",14)) return FALSE;\n\t// Warning! Not supported ULT format, trying anyway\n\t// if ((pmh->id[14] < '1') || (pmh->id[14] > '4')) return FALSE;\n\tm_nType = MOD_TYPE_ULT;\n\tm_nDefaultSpeed = 6;\n\tm_nDefaultTempo = 125;\n\tmemcpy(m_szNames[0], pmh->songtitle, 32);\n\tm_szNames[0][31] = '\\0';\n\t// read songtext\n\tdwMemPos = sizeof(ULTHEADER);\n\tif ((pmh->reserved) && (dwMemPos + pmh->reserved * 32 < dwMemLength))\n\t{\n\t\tUINT len = pmh->reserved * 32;\n\t\tm_lpszSongComments = new char[len + 1 + pmh->reserved];\n\t\tif (m_lpszSongComments)\n\t\t{\n\t\t\tfor (UINT l=0; l<pmh->reserved; l++)\n\t\t\t{\n\t\t\t\tmemcpy(m_lpszSongComments+l*33, lpStream+dwMemPos+l*32, 32);\n\t\t\t\tm_lpszSongComments[l*33+32] = 0x0D;\n\t\t\t}\n\t\t\tm_lpszSongComments[len] = 0;\n\t\t}\n\t\tdwMemPos += len;\n\t}\n\tif (dwMemPos >= dwMemLength) return TRUE;\n\tnos = lpStream[dwMemPos++];\n\tm_nSamples = nos;\n\tif (m_nSamples >= MAX_SAMPLES) m_nSamples = MAX_SAMPLES-1;\n\tUINT smpsize = 64;\n\tif (pmh->id[14] >= '4')\tsmpsize += 2;\n\tif (dwMemPos + nos*smpsize + 256 + 2 > dwMemLength) return TRUE;\n\tfor (UINT ins=1; ins<=nos; ins++, dwMemPos+=smpsize) if (ins<=m_nSamples)\n\t{\n\t\tpus\t= (ULTSAMPLE *)(lpStream+dwMemPos);\n\t\tMODINSTRUMENT *pins = &Ins[ins];\n\t\tmemcpy(m_szNames[ins], pus->samplename, 32);\n\t\tm_szNames[ins][31] = '\\0';\n\t\tmemcpy(pins->name, pus->dosname, 12);\n\t\tpins->nLoopStart = pus->loopstart;\n\t\tpins->nLoopEnd = pus->loopend;\n\t\tpins->nLength = pus->sizeend - pus->sizestart;\n\t\tpins->nVolume = pus->volume;\n\t\tpins->nGlobalVol = 64;\n\t\tpins->nC4Speed = 8363;\n\t\tif (pmh->id[14] >= '4')\n\t\t{\n\t\t\tpins->nC4Speed = pus->finetune;\n\t\t}\n\t\tif (pus->flags & ULT_LOOP) pins->uFlags |= CHN_LOOP;\n\t\tif (pus->flags & ULT_BIDI) pins->uFlags |= CHN_PINGPONGLOOP;\n\t\tif (pus->flags & ULT_16BIT)\n\t\t{\n\t\t\tpins->uFlags |= CHN_16BIT;\n\t\t\tpins->nLoopStart >>= 1;\n\t\t\tpins->nLoopEnd >>= 1;\n\t\t}\n\t}\n\tmemcpy(Order, lpStream+dwMemPos, 256);\n\tdwMemPos += 256;\n\tm_nChannels = lpStream[dwMemPos] + 1;\n\tnop = lpStream[dwMemPos+1] + 1;\n\tdwMemPos += 2;\n\tif (m_nChannels > 32) m_nChannels = 32;\n\t// Default channel settings\n\tfor (UINT nSet=0; nSet<m_nChannels; nSet++)\n\t{\n\t\tChnSettings[nSet].nVolume = 64;\n\t\tChnSettings[nSet].nPan = (nSet & 1) ? 0x40 : 0xC0;\n\t}\n\t// read pan position table for v1.5 and higher\n\tif(pmh->id[14]>='3')\n\t{\n\t\tif (dwMemPos + m_nChannels > dwMemLength) return TRUE;\n\t\tfor(UINT t=0; t<m_nChannels; t++)\n\t\t{\n\t\t\tChnSettings[t].nPan = (lpStream[dwMemPos++] << 4) + 8;\n\t\t\tif (ChnSettings[t].nPan > 256) ChnSettings[t].nPan = 256;\n\t\t}\n\t}\n\t// Allocating Patterns\n\tfor (UINT nAllocPat=0; nAllocPat<nop; nAllocPat++)\n\t{\n\t\tif (nAllocPat < MAX_PATTERNS)\n\t\t{\n\t\t\tPatternSize[nAllocPat] = 64;\n\t\t\tPatterns[nAllocPat] = AllocatePattern(64, m_nChannels);\n\t\t}\n\t}\n\t// Reading Patterns\n\tfor (UINT nChn=0; nChn<m_nChannels; nChn++)\n\t{\n\t\tfor (UINT nPat=0; nPat<nop; nPat++)\n\t\t{\n\t\t\tMODCOMMAND *pat = NULL;\n\t\t\t\n\t\t\tif (nPat < MAX_PATTERNS)\n\t\t\t{\n\t\t\t\tpat = Patterns[nPat];\n\t\t\t\tif (pat) pat += nChn;\n\t\t\t}\n\t\t\tUINT row = 0;\n\t\t\twhile (row < 64)\n\t\t\t{\n\t\t\t\tif (dwMemPos + 6 > dwMemLength) return TRUE;\n\t\t\t\tUINT rep = 1;\n\t\t\t\tUINT note = lpStream[dwMemPos++];\n\t\t\t\tif (note == 0xFC)\n\t\t\t\t{\n\t\t\t\t\trep = lpStream[dwMemPos];\n\t\t\t\t\tnote = lpStream[dwMemPos+1];\n\t\t\t\t\tdwMemPos += 2;\n\t\t\t\t}\n\t\t\t\tUINT instr = lpStream[dwMemPos++];\n\t\t\t\tUINT eff = lpStream[dwMemPos++];\n\t\t\t\tUINT dat1 = lpStream[dwMemPos++];\n\t\t\t\tUINT dat2 = lpStream[dwMemPos++];\n\t\t\t\tUINT cmd1 = eff & 0x0F;\n\t\t\t\tUINT cmd2 = eff >> 4;\n\t\t\t\tif (cmd1 == 0x0C) dat1 >>= 2; else\n\t\t\t\tif (cmd1 == 0x0B) { cmd1 = dat1 = 0; }\n\t\t\t\tif (cmd2 == 0x0C) dat2 >>= 2; else\n\t\t\t\tif (cmd2 == 0x0B) { cmd2 = dat2 = 0; }\n\t\t\t\twhile ((rep != 0) && (row < 64))\n\t\t\t\t{\n\t\t\t\t\tif (pat)\n\t\t\t\t\t{\n\t\t\t\t\t\tpat->instr = instr;\n\t\t\t\t\t\tif (note) pat->note = note + 36;\n\t\t\t\t\t\tif (cmd1 | dat1)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (cmd1 == 0x0C)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpat->volcmd = VOLCMD_VOLUME;\n\t\t\t\t\t\t\t\tpat->vol = dat1;\n\t\t\t\t\t\t\t} else\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpat->command = cmd1;\n\t\t\t\t\t\t\t\tpat->param = dat1;\n\t\t\t\t\t\t\t\tConvertModCommand(pat);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (cmd2 == 0x0C)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpat->volcmd = VOLCMD_VOLUME;\n\t\t\t\t\t\t\tpat->vol = dat2;\n\t\t\t\t\t\t} else\n\t\t\t\t\t\tif ((cmd2 | dat2) && (!pat->command))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpat->command = cmd2;\n\t\t\t\t\t\t\tpat->param = dat2;\n\t\t\t\t\t\t\tConvertModCommand(pat);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpat += m_nChannels;\n\t\t\t\t\t}\n\t\t\t\t\trow++;\n\t\t\t\t\trep--;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Reading Instruments\n\tfor (UINT smp=1; smp<=m_nSamples; smp++) if (Ins[smp].nLength)\n\t{\n\t\tif (dwMemPos >= dwMemLength) return TRUE;\n\t\tUINT flags = (Ins[smp].uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S;\n\t\tdwMemPos += ReadSample(&Ins[smp], flags, (LPSTR)(lpStream+dwMemPos), dwMemLength - dwMemPos);\n\t}\n\treturn TRUE;\n}\n\n"
  },
  {
    "path": "contrib/libmodplug/src/load_wav.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n#ifndef WAVE_FORMAT_EXTENSIBLE\n#define WAVE_FORMAT_EXTENSIBLE\t0xFFFE\n#endif\n\n/////////////////////////////////////////////////////////////\n// WAV file support\n\nBOOL CSoundFile::ReadWav(const BYTE *lpStream, DWORD dwMemLength)\n//---------------------------------------------------------------\n{\n\tDWORD dwMemPos = 0;\n\tWAVEFILEHEADER *phdr = (WAVEFILEHEADER *)lpStream;\n\tWAVEFORMATHEADER *pfmt = (WAVEFORMATHEADER *)(lpStream + sizeof(WAVEFILEHEADER));\n\tif ((!lpStream) || (dwMemLength < (DWORD)sizeof(WAVEFILEHEADER))) return FALSE;\n\tif ((phdr->id_RIFF != IFFID_RIFF) || (phdr->id_WAVE != IFFID_WAVE)\n\t || (pfmt->id_fmt != IFFID_fmt)) return FALSE;\n\tdwMemPos = sizeof(WAVEFILEHEADER) + 8 + pfmt->hdrlen;\n\tif ((dwMemPos >= dwMemLength - 8)\n\t || ((pfmt->format != WAVE_FORMAT_PCM) && (pfmt->format != WAVE_FORMAT_EXTENSIBLE))\n\t || (pfmt->channels > 4)\n\t || (!pfmt->channels)\n\t || (!pfmt->freqHz)\n\t || (pfmt->bitspersample & 7)\n\t || (pfmt->bitspersample < 8)\n\t || (pfmt->bitspersample > 32))  return FALSE;\n\tWAVEDATAHEADER *pdata;\n\tfor (;;)\n\t{\n\t\tpdata = (WAVEDATAHEADER *)(lpStream + dwMemPos);\n\t\tif (pdata->id_data == IFFID_data) break;\n\t\tdwMemPos += pdata->length + 8;\n\t\tif (dwMemPos >= dwMemLength - 8) return FALSE;\n\t}\n\tm_nType = MOD_TYPE_WAV;\n\tm_nSamples = 0;\n\tm_nInstruments = 0;\n\tm_nChannels = 4;\n\tm_nDefaultSpeed = 8;\n\tm_nDefaultTempo = 125;\n\tm_dwSongFlags |= SONG_LINEARSLIDES; // For no resampling\n\tOrder[0] = 0;\n\tOrder[1] = 0xFF;\n\tPatternSize[0] = PatternSize[1] = 64;\n\tif ((Patterns[0] = AllocatePattern(64, 4)) == NULL) return TRUE;\n\tif ((Patterns[1] = AllocatePattern(64, 4)) == NULL) return TRUE;\n\tUINT samplesize = (pfmt->channels * pfmt->bitspersample) >> 3;\n\tUINT len = pdata->length, bytelen;\n\tif (len > dwMemLength - 8 - dwMemPos) len = dwMemLength - dwMemPos - 8;\n\tlen /= samplesize;\n\tbytelen = len;\n\tif (pfmt->bitspersample >= 16) bytelen *= 2;\n\tif (len > MAX_SAMPLE_LENGTH) len = MAX_SAMPLE_LENGTH;\n\tif (!len) return TRUE;\n\t// Setting up module length\n\tDWORD dwTime = ((len * 50) / pfmt->freqHz) + 1;\n\tDWORD framesperrow = (dwTime + 63) / 63;\n\tif (framesperrow < 4) framesperrow = 4;\n\tUINT norders = 1;\n\twhile (framesperrow >= 0x20)\n\t{\n\t\tOrder[norders++] = 1;\n\t\tOrder[norders] = 0xFF;\n\t\tframesperrow = (dwTime + (64 * norders - 1)) / (64 * norders);\n\t\tif (norders >= MAX_ORDERS-1) break;\n\t}\n\tm_nDefaultSpeed = framesperrow;\n\tfor (UINT iChn=0; iChn<4; iChn++)\n\t{\n\t\tChnSettings[iChn].nPan = (iChn & 1) ? 256 : 0;\n\t\tChnSettings[iChn].nVolume = 64;\n\t\tChnSettings[iChn].dwFlags = 0;\n\t}\n\t// Setting up speed command\n\tMODCOMMAND *pcmd = Patterns[0];\n\tpcmd[0].command = CMD_SPEED;\n\tpcmd[0].param = (BYTE)m_nDefaultSpeed;\n\tpcmd[0].note = 5*12+1;\n\tpcmd[0].instr = 1;\n\tpcmd[1].note = pcmd[0].note;\n\tpcmd[1].instr = pcmd[0].instr;\n\tm_nSamples = pfmt->channels;\n\t// Support for Multichannel Wave\n\tfor (UINT nChn=0; nChn<m_nSamples; nChn++)\n\t{\n\t\tMODINSTRUMENT *pins = &Ins[nChn+1];\n\t\tpcmd[nChn].note = pcmd[0].note;\n\t\tpcmd[nChn].instr = (BYTE)(nChn+1);\n\t\tpins->nLength = len;\n\t\tpins->nC4Speed = pfmt->freqHz;\n\t\tpins->nVolume = 256;\n\t\tpins->nPan = 128;\n\t\tpins->nGlobalVol = 64;\n\t\tpins->uFlags = (WORD)((pfmt->bitspersample >= 16) ? CHN_16BIT : 0);\n\t\tpins->uFlags |= CHN_PANNING;\n\t\tif (m_nSamples > 1)\n\t\t{\n\t\t\tswitch(nChn)\n\t\t\t{\n\t\t\tcase 0:\tpins->nPan = 0; break;\n\t\t\tcase 1:\tpins->nPan = 256; break;\n\t\t\tcase 2: pins->nPan = (WORD)((m_nSamples == 3) ? 128 : 64); pcmd[nChn].command = CMD_S3MCMDEX; pcmd[nChn].param = 0x91; break;\n\t\t\tcase 3: pins->nPan = 192; pcmd[nChn].command = CMD_S3MCMDEX; pcmd[nChn].param = 0x91; break;\n\t\t\tdefault: pins->nPan = 128; break;\n\t\t\t}\n\t\t}\n\t\tif ((pins->pSample = AllocateSample(bytelen+8)) == NULL) return TRUE;\n\t\tif (pfmt->bitspersample >= 16)\n\t\t{\n\t\t\tint slsize = pfmt->bitspersample >> 3;\n\t\t\tsigned short *p = (signed short *)pins->pSample;\n\t\t\tsigned char *psrc = (signed char *)(lpStream+dwMemPos+8+nChn*slsize+slsize-2);\n\t\t\tfor (UINT i=0; i<len; i++)\n\t\t\t{\n\t\t\t\tp[i] = *((signed short *)psrc);\n\t\t\t\tpsrc += samplesize;\n\t\t\t}\n\t\t\tp[len+1] = p[len] = p[len-1];\n\t\t} else\n\t\t{\n\t\t\tsigned char *p = (signed char *)pins->pSample;\n\t\t\tsigned char *psrc = (signed char *)(lpStream+dwMemPos+8+nChn);\n\t\t\tfor (UINT i=0; i<len; i++)\n\t\t\t{\n\t\t\t\tp[i] = (signed char)((*psrc) + 0x80);\n\t\t\t\tpsrc += samplesize;\n\t\t\t}\n\t\t\tp[len+1] = p[len] = p[len-1];\n\t\t}\n\t}\n\treturn TRUE;\n}\n\n\n////////////////////////////////////////////////////////////////////////\n// IMA ADPCM Support\n\n#pragma pack(1)\n\ntypedef struct IMAADPCMBLOCK\n{\n\tWORD sample;\n\tBYTE index;\n\tBYTE Reserved;\n} DVI_ADPCMBLOCKHEADER;\n\n#pragma pack()\n\nstatic const int gIMAUnpackTable[90] =\n{\n  7,     8,     9,    10,    11,    12,    13,    14,\n  16,    17,    19,    21,    23,    25,    28,    31,\n  34,    37,    41,    45,    50,    55,    60,    66,\n  73,    80,    88,    97,   107,   118,   130,   143,\n  157,   173,   190,   209,   230,   253,   279,   307,\n  337,   371,   408,   449,   494,   544,   598,   658,\n  724,   796,   876,   963,  1060,  1166,  1282,  1411,\n  1552,  1707,  1878,  2066,  2272,  2499,  2749,  3024,\n  3327,  3660,  4026,  4428,  4871,  5358,  5894,  6484,\n  7132,  7845,  8630,  9493, 10442, 11487, 12635, 13899,\n  15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,\n  32767, 0\n};\n\n\nBOOL IMAADPCMUnpack16(signed short *pdest, UINT nLen, LPBYTE psrc, DWORD dwBytes, UINT pkBlkAlign)\n//------------------------------------------------------------------------------------------------\n{\n\tstatic const int gIMAIndexTab[8] =  { -1, -1, -1, -1, 2, 4, 6, 8 };\n\tUINT nPos;\n\tint value;\n\n\tif ((nLen < 4) || (!pdest) || (!psrc)\n\t || (pkBlkAlign < 5) || (pkBlkAlign > dwBytes)) return FALSE;\n\tnPos = 0;\n\twhile ((nPos < nLen) && (dwBytes > 4))\n\t{\n\t\tint nIndex;\n\t\tvalue = *((short int *)psrc);\n\t\tnIndex = psrc[2];\n\t\tpsrc += 4;\n\t\tdwBytes -= 4;\n\t\tpdest[nPos++] = (short int)value;\n\t\tfor (UINT i=0; ((i<(pkBlkAlign-4)*2) && (nPos < nLen) && (dwBytes)); i++)\n\t\t{\n\t\t\tBYTE delta;\n\t\t\tif (i & 1)\n\t\t\t{\n\t\t\t\tdelta = (BYTE)(((*(psrc++)) >> 4) & 0x0F);\n\t\t\t\tdwBytes--;\n\t\t\t} else\n\t\t\t{\n\t\t\t\tdelta = (BYTE)((*psrc) & 0x0F);\n\t\t\t}\n\t\t\tint v = gIMAUnpackTable[nIndex] >> 3;\n\t\t\tif (delta & 1) v += gIMAUnpackTable[nIndex] >> 2;\n\t\t\tif (delta & 2) v += gIMAUnpackTable[nIndex] >> 1;\n\t\t\tif (delta & 4) v += gIMAUnpackTable[nIndex];\n\t\t\tif (delta & 8) value -= v; else value += v;\n\t\t\tnIndex += gIMAIndexTab[delta & 7];\n\t\t\tif (nIndex < 0) nIndex = 0; else\n\t\t\tif (nIndex > 88) nIndex = 88;\n\t\t\tif (value > 32767) value = 32767; else\n\t\t\tif (value < -32768) value = -32768;\n\t\t\tpdest[nPos++] = (short int)value;\n\t\t}\n\t}\n\treturn TRUE;\n}\n"
  },
  {
    "path": "contrib/libmodplug/src/load_xm.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>,\n *          Adam Goode       <adam@evdebs.org> (endian and char fixes for PPC)\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n////////////////////////////////////////////////////////\n// FastTracker II XM file support\n\n#ifdef MSC_VER\n#pragma warning(disable:4244)\n#endif\n\n#pragma pack(1)\ntypedef struct tagXMFILEHEADER\n{\n\tDWORD size;\n\tWORD norder;\n\tWORD restartpos;\n\tWORD channels;\n\tWORD patterns;\n\tWORD instruments;\n\tWORD flags;\n\tWORD speed;\n\tWORD tempo;\n\tBYTE order[256];\n} XMFILEHEADER;\n\n\ntypedef struct tagXMINSTRUMENTHEADER\n{\n\tDWORD size;\n\tCHAR name[22];\n\tBYTE type;\n\tBYTE samples;\n\tBYTE samplesh;\n} XMINSTRUMENTHEADER;\n\n\ntypedef struct tagXMSAMPLEHEADER\n{\n\tDWORD shsize;\n\tBYTE snum[96];\n\tWORD venv[24];\n\tWORD penv[24];\n\tBYTE vnum, pnum;\n\tBYTE vsustain, vloops, vloope, psustain, ploops, ploope;\n\tBYTE vtype, ptype;\n\tBYTE vibtype, vibsweep, vibdepth, vibrate;\n\tWORD volfade;\n\tWORD res;\n\tBYTE reserved1[20];\n} XMSAMPLEHEADER;\n\ntypedef struct tagXMSAMPLESTRUCT\n{\n\tDWORD samplen;\n\tDWORD loopstart;\n\tDWORD looplen;\n\tBYTE vol;\n\tsigned char finetune;\n\tBYTE type;\n\tBYTE pan;\n\tsigned char relnote;\n\tBYTE res;\n\tchar name[22];\n} XMSAMPLESTRUCT;\n#pragma pack()\n\n\nBOOL CSoundFile::ReadXM(const BYTE *lpStream, DWORD dwMemLength)\n//--------------------------------------------------------------\n{\n\tXMSAMPLEHEADER xmsh;\n\tXMSAMPLESTRUCT xmss;\n\tDWORD dwMemPos, dwHdrSize;\n\tWORD norders=0, restartpos=0, channels=0, patterns=0, instruments=0;\n\tWORD xmflags=0, deftempo=125, defspeed=6;\n\tBOOL InstUsed[256];\n\tBYTE channels_used[MAX_CHANNELS];\n\tBYTE pattern_map[256];\n\tBOOL samples_used[MAX_SAMPLES];\n\tUINT unused_samples;\n\ttagXMFILEHEADER xmhead;\n\n\tm_nChannels = 0;\n\tif ((!lpStream) || (dwMemLength < 0x200)) return FALSE;\n\tif (strncmp((LPCSTR)lpStream, \"Extended Module:\", 16)) return FALSE;\n\n\tmemcpy(m_szNames[0], lpStream+17, 20);\n\txmhead = *(tagXMFILEHEADER *)(lpStream+60);\n\tdwHdrSize = bswapLE32(xmhead.size);\n\tnorders = bswapLE16(xmhead.norder);\n\trestartpos = bswapLE16(xmhead.restartpos);\n\tchannels = bswapLE16(xmhead.channels);\n\n\tif ((!dwHdrSize) || dwHdrSize > dwMemLength - 60) return FALSE;\n\tif ((!norders) || (norders > MAX_ORDERS)) return FALSE;\n\tif ((!channels) || (channels > 64)) return FALSE;\n\tm_nType = MOD_TYPE_XM;\n\tm_nMinPeriod = 27;\n\tm_nMaxPeriod = 54784;\n\tm_nChannels = channels;\n\tif (restartpos < norders) m_nRestartPos = restartpos;\n\tpatterns = bswapLE16(xmhead.patterns);\n\tif (patterns > 256) patterns = 256;\n\tinstruments = bswapLE16(xmhead.instruments);\n\tif (instruments >= MAX_INSTRUMENTS) instruments = MAX_INSTRUMENTS-1;\n\tm_nInstruments = instruments;\n\tm_nSamples = 0;\n\txmflags = bswapLE16(xmhead.flags);\n\tif (xmflags & 1) m_dwSongFlags |= SONG_LINEARSLIDES;\n\tif (xmflags & 0x1000) m_dwSongFlags |= SONG_EXFILTERRANGE;\n\tdefspeed = bswapLE16(xmhead.speed);\n\tdeftempo = bswapLE16(xmhead.tempo);\n\tif ((deftempo >= 32) && (deftempo < 256)) m_nDefaultTempo = deftempo;\n\tif ((defspeed > 0) && (defspeed < 40)) m_nDefaultSpeed = defspeed;\n\tmemcpy(Order, lpStream+80, norders);\n\tmemset(InstUsed, 0, sizeof(InstUsed));\n\tif (patterns > MAX_PATTERNS)\n\t{\n\t\tUINT i, j;\n\t\tfor (i=0; i<norders; i++)\n\t\t{\n\t\t\tif (Order[i] < patterns) InstUsed[Order[i]] = TRUE;\n\t\t}\n\t\tj = 0;\n\t\tfor (i=0; i<256; i++)\n\t\t{\n\t\t\tif (InstUsed[i]) pattern_map[i] = j++;\n\t\t}\n\t\tfor (i=0; i<256; i++)\n\t\t{\n\t\t\tif (!InstUsed[i])\n\t\t\t{\n\t\t\t\tpattern_map[i] = (j < MAX_PATTERNS) ? j : 0xFE;\n\t\t\t\tj++;\n\t\t\t}\n\t\t}\n\t\tfor (i=0; i<norders; i++)\n\t\t{\n\t\t\tOrder[i] = pattern_map[Order[i]];\n\t\t}\n\t} else\n\t{\n\t\tfor (UINT i=0; i<256; i++) pattern_map[i] = i;\n\t}\n\tmemset(InstUsed, 0, sizeof(InstUsed));\n\tdwMemPos = dwHdrSize + 60;\n\tif (dwMemPos + 8 >= dwMemLength) return TRUE;\n\t// Reading patterns\n\tmemset(channels_used, 0, sizeof(channels_used));\n\tfor (UINT ipat=0; ipat<patterns; ipat++)\n\t{\n\t\tUINT ipatmap = pattern_map[ipat];\n\t\tDWORD dwSize = 0;\n\t\tWORD rows=64, packsize=0;\n\t\tdwSize = bswapLE32(*((DWORD *)(lpStream+dwMemPos)));\n\t\twhile ((dwMemPos + dwSize >= dwMemLength) || (dwSize & 0xFFFFFF00))\n\t\t{\n\t\t\tif (dwMemPos + 4 >= dwMemLength) break;\n\t\t\tdwMemPos++;\n\t\t\tdwSize = bswapLE32(*((DWORD *)(lpStream+dwMemPos)));\n\t\t}\n\t\tif (dwMemPos + 9 > dwMemLength) return TRUE;\t\t\n\t\trows = bswapLE16(*((WORD *)(lpStream+dwMemPos+5)));\n\t\tif ((!rows) || (rows > 256)) rows = 64;\n\t\tpacksize = bswapLE16(*((WORD *)(lpStream+dwMemPos+7)));\n\t\tif (dwMemPos + dwSize + 4 > dwMemLength) return TRUE;\n\t\tdwMemPos += dwSize;\n\t\tif (dwMemPos + packsize + 4 > dwMemLength) return TRUE;\n\t\tMODCOMMAND *p;\n\t\tif (ipatmap < MAX_PATTERNS)\n\t\t{\n\t\t\tPatternSize[ipatmap] = rows;\n\t\t\tif ((Patterns[ipatmap] = AllocatePattern(rows, m_nChannels)) == NULL) return TRUE;\n\t\t\tif (!packsize) continue;\n\t\t\tp = Patterns[ipatmap];\n\t\t} else p = NULL;\n\t\tconst BYTE *src = lpStream+dwMemPos;\n\t\tUINT j=0;\n\t\tfor (UINT row=0; row<rows; row++)\n\t\t{\n\t\t\tfor (UINT chn=0; chn<m_nChannels; chn++)\n\t\t\t{\n\t\t\t\tif ((p) && (j < packsize))\n\t\t\t\t{\n\t\t\t\t\tBYTE b = src[j++];\n\t\t\t\t\tUINT vol = 0;\n\t\t\t\t\tif (b & 0x80)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (b & 1) p->note = src[j++];\n\t\t\t\t\t\tif (b & 2) p->instr = src[j++];\n\t\t\t\t\t\tif (b & 4) vol = src[j++];\n\t\t\t\t\t\tif (b & 8) p->command = src[j++];\n\t\t\t\t\t\tif (b & 16) p->param = src[j++];\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tp->note = b;\n\t\t\t\t\t\tp->instr = src[j++];\n\t\t\t\t\t\tvol = src[j++];\n\t\t\t\t\t\tp->command = src[j++];\n\t\t\t\t\t\tp->param = src[j++];\n\t\t\t\t\t}\n\t\t\t\t\tif (p->note == 97) p->note = 0xFF; else\n\t\t\t\t\tif ((p->note) && (p->note < 97)) p->note += 12;\n\t\t\t\t\tif (p->note) channels_used[chn] = 1;\n\t\t\t\t\tif (p->command | p->param) ConvertModCommand(p);\n\t\t\t\t\tif (p->instr == 0xff) p->instr = 0;\n\t\t\t\t\tif (p->instr) InstUsed[p->instr] = TRUE;\n\t\t\t\t\tif ((vol >= 0x10) && (vol <= 0x50))\n\t\t\t\t\t{\n\t\t\t\t\t\tp->volcmd = VOLCMD_VOLUME;\n\t\t\t\t\t\tp->vol = vol - 0x10;\n\t\t\t\t\t} else\n\t\t\t\t\tif (vol >= 0x60)\n\t\t\t\t\t{\n\t\t\t\t\t\tUINT v = vol & 0xF0;\n\t\t\t\t\t\tvol &= 0x0F;\n\t\t\t\t\t\tp->vol = vol;\n\t\t\t\t\t\tswitch(v)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t// 60-6F: Volume Slide Down\n\t\t\t\t\t\tcase 0x60:\tp->volcmd = VOLCMD_VOLSLIDEDOWN; break;\n\t\t\t\t\t\t// 70-7F: Volume Slide Up:\n\t\t\t\t\t\tcase 0x70:\tp->volcmd = VOLCMD_VOLSLIDEUP; break;\n\t\t\t\t\t\t// 80-8F: Fine Volume Slide Down\n\t\t\t\t\t\tcase 0x80:\tp->volcmd = VOLCMD_FINEVOLDOWN; break;\n\t\t\t\t\t\t// 90-9F: Fine Volume Slide Up\n\t\t\t\t\t\tcase 0x90:\tp->volcmd = VOLCMD_FINEVOLUP; break;\n\t\t\t\t\t\t// A0-AF: Set Vibrato Speed\n\t\t\t\t\t\tcase 0xA0:\tp->volcmd = VOLCMD_VIBRATOSPEED; break;\n\t\t\t\t\t\t// B0-BF: Vibrato\n\t\t\t\t\t\tcase 0xB0:\tp->volcmd = VOLCMD_VIBRATO; break;\n\t\t\t\t\t\t// C0-CF: Set Panning\n\t\t\t\t\t\tcase 0xC0:\tp->volcmd = VOLCMD_PANNING; p->vol = (vol << 2) + 2; break;\n\t\t\t\t\t\t// D0-DF: Panning Slide Left\n\t\t\t\t\t\tcase 0xD0:\tp->volcmd = VOLCMD_PANSLIDELEFT; break;\n\t\t\t\t\t\t// E0-EF: Panning Slide Right\n\t\t\t\t\t\tcase 0xE0:\tp->volcmd = VOLCMD_PANSLIDERIGHT; break;\n\t\t\t\t\t\t// F0-FF: Tone Portamento\n\t\t\t\t\t\tcase 0xF0:\tp->volcmd = VOLCMD_TONEPORTAMENTO; break;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tp++;\n\t\t\t\t} else\n\t\t\t\tif (j < packsize)\n\t\t\t\t{\n\t\t\t\t\tBYTE b = src[j++];\n\t\t\t\t\tif (b & 0x80)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (b & 1) j++;\n\t\t\t\t\t\tif (b & 2) j++;\n\t\t\t\t\t\tif (b & 4) j++;\n\t\t\t\t\t\tif (b & 8) j++;\n\t\t\t\t\t\tif (b & 16) j++;\n\t\t\t\t\t} else j += 4;\n\t\t\t\t} else break;\n\t\t\t}\n\t\t}\n\t\tdwMemPos += packsize;\n\t}\n\t// Wrong offset check\n\twhile (dwMemPos + 4 < dwMemLength)\n\t{\n\t\tDWORD d = bswapLE32(*((DWORD *)(lpStream+dwMemPos)));\n\t\tif (d < 0x300) break;\n\t\tdwMemPos++;\n\t}\n\tmemset(samples_used, 0, sizeof(samples_used));\n\tunused_samples = 0;\n\t// Reading instruments\n\tfor (UINT iIns=1; iIns<=instruments; iIns++)\n\t{\n\t\tXMINSTRUMENTHEADER *pih;\n\t\tBYTE flags[32];\n\t\tDWORD samplesize[32];\n\t\tUINT samplemap[32];\n\t\tWORD nsamples;\n\n\t\tif (dwMemPos + sizeof(XMINSTRUMENTHEADER) >= dwMemLength) return TRUE;\n\t\tpih = (XMINSTRUMENTHEADER *)(lpStream+dwMemPos);\n\t\tif (dwMemPos + bswapLE32(pih->size) > dwMemLength) return TRUE;\n\t\tif ((Headers[iIns] = new INSTRUMENTHEADER) == NULL) continue;\n\t\tmemset(Headers[iIns], 0, sizeof(INSTRUMENTHEADER));\n\t\tmemcpy(Headers[iIns]->name, pih->name, 22);\n\t\tif ((nsamples = pih->samples) > 0)\n\t\t{\n\t\t\tif (dwMemPos + sizeof(XMSAMPLEHEADER) > dwMemLength) return TRUE;\n\t\t\tmemcpy(&xmsh, lpStream+dwMemPos+sizeof(XMINSTRUMENTHEADER), sizeof(XMSAMPLEHEADER));\n\t\t\txmsh.shsize = bswapLE32(xmsh.shsize);\n\t\t\tfor (int i = 0; i < 24; ++i) {\n\t\t\t  xmsh.venv[i] = bswapLE16(xmsh.venv[i]);\n\t\t\t  xmsh.penv[i] = bswapLE16(xmsh.penv[i]);\n\t\t\t}\n\t\t\txmsh.volfade = bswapLE16(xmsh.volfade);\n\t\t\txmsh.res = bswapLE16(xmsh.res);\n\t\t\tdwMemPos += bswapLE32(pih->size);\n\t\t} else\n\t\t{\n\t\t\tif (bswapLE32(pih->size)) dwMemPos += bswapLE32(pih->size);\n\t\t\telse dwMemPos += sizeof(XMINSTRUMENTHEADER);\n\t\t\tcontinue;\n\t\t}\n\t\tmemset(samplemap, 0, sizeof(samplemap));\n\t\tif (nsamples > 32) return TRUE;\n\t\tUINT newsamples = m_nSamples;\n\t\tfor (UINT nmap=0; nmap<nsamples; nmap++)\n\t\t{\n\t\t\tUINT n = m_nSamples+nmap+1;\n\t\t\tif (n >= MAX_SAMPLES)\n\t\t\t{\n\t\t\t\tn = m_nSamples;\n\t\t\t\twhile (n > 0)\n\t\t\t\t{\n\t\t\t\t\tif (!Ins[n].pSample)\n\t\t\t\t\t{\n\t\t\t\t\t\tfor (UINT xmapchk=0; xmapchk < nmap; xmapchk++)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (samplemap[xmapchk] == n) goto alreadymapped;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (UINT clrs=1; clrs<iIns; clrs++) if (Headers[clrs])\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tINSTRUMENTHEADER *pks = Headers[clrs];\n\t\t\t\t\t\t\tfor (UINT ks=0; ks<128; ks++)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif (pks->Keyboard[ks] == n) pks->Keyboard[ks] = 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\talreadymapped:\n\t\t\t\t\tn--;\n\t\t\t\t}\n#ifndef MODPLUG_FASTSOUNDLIB\n\t\t\t\t// Damn! more than 200 samples: look for duplicates\n\t\t\t\tif (!n)\n\t\t\t\t{\n\t\t\t\t\tif (!unused_samples)\n\t\t\t\t\t{\n\t\t\t\t\t\tunused_samples = DetectUnusedSamples(samples_used);\n\t\t\t\t\t\tif (!unused_samples) unused_samples = 0xFFFF;\n\t\t\t\t\t}\n\t\t\t\t\tif ((unused_samples) && (unused_samples != 0xFFFF))\n\t\t\t\t\t{\n\t\t\t\t\t\tfor (UINT iext=m_nSamples; iext>=1; iext--) if (!samples_used[iext])\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tunused_samples--;\n\t\t\t\t\t\t\tsamples_used[iext] = TRUE;\n\t\t\t\t\t\t\tDestroySample(iext);\n\t\t\t\t\t\t\tn = iext;\n\t\t\t\t\t\t\tfor (UINT mapchk=0; mapchk<nmap; mapchk++)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif (samplemap[mapchk] == n) samplemap[mapchk] = 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor (UINT clrs=1; clrs<iIns; clrs++) if (Headers[clrs])\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tINSTRUMENTHEADER *pks = Headers[clrs];\n\t\t\t\t\t\t\t\tfor (UINT ks=0; ks<128; ks++)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tif (pks->Keyboard[ks] == n) pks->Keyboard[ks] = 0;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tmemset(&Ins[n], 0, sizeof(Ins[0]));\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n#endif // MODPLUG_FASTSOUNDLIB\n\t\t\t}\n\t\t\tif (newsamples < n) newsamples = n;\n\t\t\tsamplemap[nmap] = n;\n\t\t}\n\t\tm_nSamples = newsamples;\n\t\t// Reading Volume Envelope\n\t\tINSTRUMENTHEADER *penv = Headers[iIns];\n\t\tpenv->nMidiProgram = pih->type;\n\t\tpenv->nFadeOut = xmsh.volfade;\n\t\tpenv->nPan = 128;\n\t\tpenv->nPPC = 5*12;\n\t\tif (xmsh.vtype & 1) penv->dwFlags |= ENV_VOLUME;\n\t\tif (xmsh.vtype & 2) penv->dwFlags |= ENV_VOLSUSTAIN;\n\t\tif (xmsh.vtype & 4) penv->dwFlags |= ENV_VOLLOOP;\n\t\tif (xmsh.ptype & 1) penv->dwFlags |= ENV_PANNING;\n\t\tif (xmsh.ptype & 2) penv->dwFlags |= ENV_PANSUSTAIN;\n\t\tif (xmsh.ptype & 4) penv->dwFlags |= ENV_PANLOOP;\n\t\tif (xmsh.vnum > 12) xmsh.vnum = 12;\n\t\tif (xmsh.pnum > 12) xmsh.pnum = 12;\n\t\tpenv->nVolEnv = xmsh.vnum;\n\t\tif (!xmsh.vnum) penv->dwFlags &= ~ENV_VOLUME;\n\t\tif (!xmsh.pnum) penv->dwFlags &= ~ENV_PANNING;\n\t\tpenv->nPanEnv = xmsh.pnum;\n\t\tpenv->nVolSustainBegin = penv->nVolSustainEnd = xmsh.vsustain;\n\t\tif (xmsh.vsustain >= 12) penv->dwFlags &= ~ENV_VOLSUSTAIN;\n\t\tpenv->nVolLoopStart = xmsh.vloops;\n\t\tpenv->nVolLoopEnd = xmsh.vloope;\n\t\tif (penv->nVolLoopEnd >= 12) penv->nVolLoopEnd = 0;\n\t\tif (penv->nVolLoopStart >= penv->nVolLoopEnd) penv->dwFlags &= ~ENV_VOLLOOP;\n\t\tpenv->nPanSustainBegin = penv->nPanSustainEnd = xmsh.psustain;\n\t\tif (xmsh.psustain >= 12) penv->dwFlags &= ~ENV_PANSUSTAIN;\n\t\tpenv->nPanLoopStart = xmsh.ploops;\n\t\tpenv->nPanLoopEnd = xmsh.ploope;\n\t\tif (penv->nPanLoopEnd >= 12) penv->nPanLoopEnd = 0;\n\t\tif (penv->nPanLoopStart >= penv->nPanLoopEnd) penv->dwFlags &= ~ENV_PANLOOP;\n\t\tpenv->nGlobalVol = 64;\n\t\tfor (UINT ienv=0; ienv<12; ienv++)\n\t\t{\n\t\t\tpenv->VolPoints[ienv] = (WORD)xmsh.venv[ienv*2];\n\t\t\tpenv->VolEnv[ienv] = (BYTE)xmsh.venv[ienv*2+1];\n\t\t\tpenv->PanPoints[ienv] = (WORD)xmsh.penv[ienv*2];\n\t\t\tpenv->PanEnv[ienv] = (BYTE)xmsh.penv[ienv*2+1];\n\t\t\tif (ienv)\n\t\t\t{\n\t\t\t\tif (penv->VolPoints[ienv] < penv->VolPoints[ienv-1])\n\t\t\t\t{\n\t\t\t\t\tpenv->VolPoints[ienv] &= 0xFF;\n\t\t\t\t\tpenv->VolPoints[ienv] += penv->VolPoints[ienv-1] & 0xFF00;\n\t\t\t\t\tif (penv->VolPoints[ienv] < penv->VolPoints[ienv-1]) penv->VolPoints[ienv] += 0x100;\n\t\t\t\t}\n\t\t\t\tif (penv->PanPoints[ienv] < penv->PanPoints[ienv-1])\n\t\t\t\t{\n\t\t\t\t\tpenv->PanPoints[ienv] &= 0xFF;\n\t\t\t\t\tpenv->PanPoints[ienv] += penv->PanPoints[ienv-1] & 0xFF00;\n\t\t\t\t\tif (penv->PanPoints[ienv] < penv->PanPoints[ienv-1]) penv->PanPoints[ienv] += 0x100;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (UINT j=0; j<96; j++)\n\t\t{\n\t\t\tpenv->NoteMap[j+12] = j+1+12;\n\t\t\tif (xmsh.snum[j] < nsamples)\n\t\t\t\tpenv->Keyboard[j+12] = samplemap[xmsh.snum[j]];\n\t\t}\n\t\t// Reading samples\n\t\tfor (UINT ins=0; ins<nsamples; ins++)\n\t\t{\n\t\t\tif ((dwMemPos + sizeof(xmss) > dwMemLength)\n\t\t\t || (dwMemPos + xmsh.shsize > dwMemLength)) return TRUE;\n\t\t\tmemcpy(&xmss, lpStream+dwMemPos, sizeof(xmss));\n\t\t\txmss.samplen = bswapLE32(xmss.samplen);\n\t\t\txmss.loopstart = bswapLE32(xmss.loopstart);\n\t\t\txmss.looplen = bswapLE32(xmss.looplen);\n\t\t\tdwMemPos += xmsh.shsize;\n\t\t\tflags[ins] = (xmss.type & 0x10) ? RS_PCM16D : RS_PCM8D;\n\t\t\tif (xmss.type & 0x20) flags[ins] = (xmss.type & 0x10) ? RS_STPCM16D : RS_STPCM8D;\n\t\t\tsamplesize[ins] = xmss.samplen;\n\t\t\tif (!samplemap[ins]) continue;\n\t\t\tif (xmss.type & 0x10)\n\t\t\t{\n\t\t\t\txmss.looplen >>= 1;\n\t\t\t\txmss.loopstart >>= 1;\n\t\t\t\txmss.samplen >>= 1;\n\t\t\t}\n\t\t\tif (xmss.type & 0x20)\n\t\t\t{\n\t\t\t\txmss.looplen >>= 1;\n\t\t\t\txmss.loopstart >>= 1;\n\t\t\t\txmss.samplen >>= 1;\n\t\t\t}\n\t\t\tif (xmss.samplen > MAX_SAMPLE_LENGTH) xmss.samplen = MAX_SAMPLE_LENGTH;\n\t\t\tif (xmss.loopstart >= xmss.samplen) xmss.type &= ~3;\n\t\t\txmss.looplen += xmss.loopstart;\n\t\t\tif (xmss.looplen > xmss.samplen) xmss.looplen = xmss.samplen;\n\t\t\tif (!xmss.looplen) xmss.type &= ~3;\n\t\t\tUINT imapsmp = samplemap[ins];\n\t\t\tmemcpy(m_szNames[imapsmp], xmss.name, 22);\n\t\t\tm_szNames[imapsmp][22] = 0;\n\t\t\tMODINSTRUMENT *pins = &Ins[imapsmp];\n\t\t\tpins->nLength = (xmss.samplen > MAX_SAMPLE_LENGTH) ? MAX_SAMPLE_LENGTH : xmss.samplen;\n\t\t\tpins->nLoopStart = xmss.loopstart;\n\t\t\tpins->nLoopEnd = xmss.looplen;\n\t\t\tif (pins->nLoopEnd > pins->nLength) pins->nLoopEnd = pins->nLength;\n\t\t\tif (pins->nLoopStart >= pins->nLoopEnd)\n\t\t\t{\n\t\t\t\tpins->nLoopStart = pins->nLoopEnd = 0;\n\t\t\t}\n\t\t\tif (xmss.type & 3) pins->uFlags |= CHN_LOOP;\n\t\t\tif (xmss.type & 2) pins->uFlags |= CHN_PINGPONGLOOP;\n\t\t\tpins->nVolume = xmss.vol << 2;\n\t\t\tif (pins->nVolume > 256) pins->nVolume = 256;\n\t\t\tpins->nGlobalVol = 64;\n\t\t\tif ((xmss.res == 0xAD) && (!(xmss.type & 0x30)))\n\t\t\t{\n\t\t\t\tflags[ins] = RS_ADPCM4;\n\t\t\t\tsamplesize[ins] = (samplesize[ins]+1)/2 + 16;\n\t\t\t}\n\t\t\tpins->nFineTune = xmss.finetune;\n\t\t\tpins->RelativeTone = (int)xmss.relnote;\n\t\t\tpins->nPan = xmss.pan;\n\t\t\tpins->uFlags |= CHN_PANNING;\n\t\t\tpins->nVibType = xmsh.vibtype;\n\t\t\tpins->nVibSweep = xmsh.vibsweep;\n\t\t\tpins->nVibDepth = xmsh.vibdepth;\n\t\t\tpins->nVibRate = xmsh.vibrate;\n\t\t\tmemcpy(pins->name, xmss.name, 22);\n\t\t\tpins->name[21] = 0;\n\t\t}\n#if 0\n\t\tif ((xmsh.reserved2 > nsamples) && (xmsh.reserved2 <= 16))\n\t\t{\n\t\t\tdwMemPos += (((UINT)xmsh.reserved2) - nsamples) * xmsh.shsize;\n\t\t}\n#endif\n\t\tfor (UINT ismpd=0; ismpd<nsamples; ismpd++)\n\t\t{\n\t\t\tif ((samplemap[ismpd]) && (samplesize[ismpd]) && (dwMemPos < dwMemLength))\n\t\t\t{\n\t\t\t\tReadSample(&Ins[samplemap[ismpd]], flags[ismpd], (LPSTR)(lpStream + dwMemPos), dwMemLength - dwMemPos);\n\t\t\t}\n\t\t\tdwMemPos += samplesize[ismpd];\n\t\t\tif (dwMemPos >= dwMemLength) break;\n\t\t}\n\t}\n\t// Read song comments: \"TEXT\"\n\tif ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x74786574))\n\t{\n\t\tUINT len = *((DWORD *)(lpStream+dwMemPos+4));\n\t\tdwMemPos += 8;\n\t\tif ((dwMemPos + len <= dwMemLength) && (len < 16384))\n\t\t{\n\t\t\tm_lpszSongComments = new char[len+1];\n\t\t\tif (m_lpszSongComments)\n\t\t\t{\n\t\t\t\tmemcpy(m_lpszSongComments, lpStream+dwMemPos, len);\n\t\t\t\tm_lpszSongComments[len] = 0;\n\t\t\t}\n\t\t\tdwMemPos += len;\n\t\t}\n\t}\n\t// Read midi config: \"MIDI\"\n\tif ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4944494D))\n\t{\n\t\tUINT len = *((DWORD *)(lpStream+dwMemPos+4));\n\t\tdwMemPos += 8;\n\t\tif (len == sizeof(MODMIDICFG))\n\t\t{\n\t\t\tmemcpy(&m_MidiCfg, lpStream+dwMemPos, len);\n\t\t\tm_dwSongFlags |= SONG_EMBEDMIDICFG;\n\t\t}\n\t}\n\t// Read pattern names: \"PNAM\"\n\tif ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4d414e50))\n\t{\n\t\tUINT len = *((DWORD *)(lpStream+dwMemPos+4));\n\t\tdwMemPos += 8;\n\t\tif ((dwMemPos + len <= dwMemLength) && (len <= MAX_PATTERNS*MAX_PATTERNNAME) && (len >= MAX_PATTERNNAME))\n\t\t{\n\t\t\tm_lpszPatternNames = new char[len];\n\n\t\t\tif (m_lpszPatternNames)\n\t\t\t{\n\t\t\t\tm_nPatternNames = len / MAX_PATTERNNAME;\n\t\t\t\tmemcpy(m_lpszPatternNames, lpStream+dwMemPos, len);\n\t\t\t}\n\t\t\tdwMemPos += len;\n\t\t}\n\t}\n\t// Read channel names: \"CNAM\"\n\tif ((dwMemPos + 8 < dwMemLength) && (bswapLE32(*((DWORD *)(lpStream+dwMemPos))) == 0x4d414e43))\n\t{\n\t\tUINT len = *((DWORD *)(lpStream+dwMemPos+4));\n\t\tdwMemPos += 8;\n\t\tif ((dwMemPos + len <= dwMemLength) && (len <= MAX_BASECHANNELS*MAX_CHANNELNAME))\n\t\t{\n\t\t\tUINT n = len / MAX_CHANNELNAME;\n\t\t\tfor (UINT i=0; i<n; i++)\n\t\t\t{\n\t\t\t\tmemcpy(ChnSettings[i].szName, (lpStream+dwMemPos+i*MAX_CHANNELNAME), MAX_CHANNELNAME);\n\t\t\t\tChnSettings[i].szName[MAX_CHANNELNAME-1] = 0;\n\t\t\t}\n\t\t\tdwMemPos += len;\n\t\t}\n\t}\n\t// Read mix plugins information\n\tif (dwMemPos + 8 < dwMemLength)\n\t{\n\t\tdwMemPos += LoadMixPlugins(lpStream+dwMemPos, dwMemLength-dwMemPos);\n\t}\n\treturn TRUE;\n}\n\n\n#ifndef MODPLUG_NO_FILESAVE\n\nBOOL CSoundFile::SaveXM(LPCSTR lpszFileName, UINT nPacking)\n//---------------------------------------------------------\n{\n\tBYTE s[64*64*5];\n\tXMFILEHEADER header;\n\tXMINSTRUMENTHEADER xmih;\n\tXMSAMPLEHEADER xmsh;\n\tXMSAMPLESTRUCT xmss;\n\tBYTE smptable[32];\n\tBYTE xmph[9];\n\tFILE *f;\n\tint i;\n\n\tif ((!m_nChannels) || (!lpszFileName)) return FALSE;\n\tif ((f = fopen(lpszFileName, \"wb\")) == NULL) return FALSE;\n\tfwrite(\"Extended Module: \", 17, 1, f);\n\tfwrite(m_szNames[0], 20, 1, f);\n\ts[0] = 0x1A;\n\tlstrcpy((LPSTR)&s[1], (nPacking) ? \"MOD Plugin packed   \" : \"FastTracker v2.00   \");\n\ts[21] = 0x04;\n\ts[22] = 0x01;\n\tfwrite(s, 23, 1, f);\n\t// Writing song header\n\tmemset(&header, 0, sizeof(header));\n\theader.size = sizeof(XMFILEHEADER);\n\theader.norder = 0;\n\theader.restartpos = m_nRestartPos;\n\theader.channels = m_nChannels;\n\theader.patterns = 0;\n\tfor (i=0; i<MAX_ORDERS; i++)\n\t{\n\t\tif (Order[i] == 0xFF) break;\n\t\theader.norder++;\n\t\tif ((Order[i] >= header.patterns) && (Order[i] < MAX_PATTERNS)) header.patterns = Order[i]+1;\n\t}\n\theader.instruments = m_nInstruments;\n\tif (!header.instruments) header.instruments = m_nSamples;\n\theader.flags = (m_dwSongFlags & SONG_LINEARSLIDES) ? 0x01 : 0x00;\n\tif (m_dwSongFlags & SONG_EXFILTERRANGE) header.flags |= 0x1000;\n\theader.tempo = m_nDefaultTempo;\n\theader.speed = m_nDefaultSpeed;\n\tmemcpy(header.order, Order, header.norder);\n\tfwrite(&header, 1, sizeof(header), f);\n\t// Writing patterns\n\tfor (i=0; i<header.patterns; i++) if (Patterns[i])\n\t{\n\t\tMODCOMMAND *p = Patterns[i];\n\t\tUINT len = 0;\n\n\t\tmemset(&xmph, 0, sizeof(xmph));\n\t\txmph[0] = 9;\n\t\txmph[5] = (BYTE)(PatternSize[i] & 0xFF);\n\t\txmph[6] = (BYTE)(PatternSize[i] >> 8);\n\t\tfor (UINT j=m_nChannels*PatternSize[i]; j; j--,p++)\n\t\t{\n\t\t\tUINT note = p->note;\n\t\t\tUINT param = ModSaveCommand(p, TRUE);\n\t\t\tUINT command = param >> 8;\n\t\t\tparam &= 0xFF;\n\t\t\tif (note >= 0xFE) note = 97; else\n\t\t\tif ((note <= 12) || (note > 96+12)) note = 0; else\n\t\t\tnote -= 12;\n\t\t\tUINT vol = 0;\n\t\t\tif (p->volcmd)\n\t\t\t{\n\t\t\t\tUINT volcmd = p->volcmd;\n\t\t\t\tswitch(volcmd)\n\t\t\t\t{\n\t\t\t\tcase VOLCMD_VOLUME:\t\t\tvol = 0x10 + p->vol; break;\n\t\t\t\tcase VOLCMD_VOLSLIDEDOWN:\tvol = 0x60 + (p->vol & 0x0F); break;\n\t\t\t\tcase VOLCMD_VOLSLIDEUP:\t\tvol = 0x70 + (p->vol & 0x0F); break;\n\t\t\t\tcase VOLCMD_FINEVOLDOWN:\tvol = 0x80 + (p->vol & 0x0F); break;\n\t\t\t\tcase VOLCMD_FINEVOLUP:\t\tvol = 0x90 + (p->vol & 0x0F); break;\n\t\t\t\tcase VOLCMD_VIBRATOSPEED:\tvol = 0xA0 + (p->vol & 0x0F); break;\n\t\t\t\tcase VOLCMD_VIBRATO:\t\tvol = 0xB0 + (p->vol & 0x0F); break;\n\t\t\t\tcase VOLCMD_PANNING:\t\tvol = 0xC0 + (p->vol >> 2); if (vol > 0xCF) vol = 0xCF; break;\n\t\t\t\tcase VOLCMD_PANSLIDELEFT:\tvol = 0xD0 + (p->vol & 0x0F); break;\n\t\t\t\tcase VOLCMD_PANSLIDERIGHT:\tvol = 0xE0 + (p->vol & 0x0F); break;\n\t\t\t\tcase VOLCMD_TONEPORTAMENTO:\tvol = 0xF0 + (p->vol & 0x0F); break;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ((note) && (p->instr) && (vol > 0x0F) && (command) && (param))\n\t\t\t{\n\t\t\t\ts[len++] = note;\n\t\t\t\ts[len++] = p->instr;\n\t\t\t\ts[len++] = vol;\n\t\t\t\ts[len++] = command;\n\t\t\t\ts[len++] = param;\n\t\t\t} else\n\t\t\t{\n\t\t\t\tBYTE b = 0x80;\n\t\t\t\tif (note) b |= 0x01;\n\t\t\t\tif (p->instr) b |= 0x02;\n\t\t\t\tif (vol >= 0x10) b |= 0x04;\n\t\t\t\tif (command) b |= 0x08;\n\t\t\t\tif (param) b |= 0x10;\n\t\t\t\ts[len++] = b;\n\t\t\t\tif (b & 1) s[len++] = note;\n\t\t\t\tif (b & 2) s[len++] = p->instr;\n\t\t\t\tif (b & 4) s[len++] = vol;\n\t\t\t\tif (b & 8) s[len++] = command;\n\t\t\t\tif (b & 16) s[len++] = param;\n\t\t\t}\n\t\t\tif (len > sizeof(s) - 5) break;\n\t\t}\n\t\txmph[7] = (BYTE)(len & 0xFF);\n\t\txmph[8] = (BYTE)(len >> 8);\n\t\tfwrite(xmph, 1, 9, f);\n\t\tfwrite(s, 1, len, f);\n\t} else\n\t{\n\t\tmemset(&xmph, 0, sizeof(xmph));\n\t\txmph[0] = 9;\n\t\txmph[5] = (BYTE)(PatternSize[i] & 0xFF);\n\t\txmph[6] = (BYTE)(PatternSize[i] >> 8);\n\t\tfwrite(xmph, 1, 9, f);\n\t}\n\t// Writing instruments\n\tfor (i=1; i<=header.instruments; i++)\n\t{\n\t\tMODINSTRUMENT *pins;\n\t\tBYTE flags[32];\n\n\t\tmemset(&xmih, 0, sizeof(xmih));\n\t\tmemset(&xmsh, 0, sizeof(xmsh));\n\t\txmih.size = sizeof(xmih) + sizeof(xmsh);\n\t\tmemcpy(xmih.name, m_szNames[i], 22);\n\t\txmih.type = 0;\n\t\txmih.samples = 0;\n\t\tif (m_nInstruments)\n\t\t{\n\t\t\tINSTRUMENTHEADER *penv = Headers[i];\n\t\t\tif (penv)\n\t\t\t{\n\t\t\t\tmemcpy(xmih.name, penv->name, 22);\n\t\t\t\txmih.type = penv->nMidiProgram;\n\t\t\t\txmsh.volfade = penv->nFadeOut;\n\t\t\t\txmsh.vnum = (BYTE)penv->nVolEnv;\n\t\t\t\txmsh.pnum = (BYTE)penv->nPanEnv;\n\t\t\t\tif (xmsh.vnum > 12) xmsh.vnum = 12;\n\t\t\t\tif (xmsh.pnum > 12) xmsh.pnum = 12;\n\t\t\t\tfor (UINT ienv=0; ienv<12; ienv++)\n\t\t\t\t{\n\t\t\t\t\txmsh.venv[ienv*2] = penv->VolPoints[ienv];\n\t\t\t\t\txmsh.venv[ienv*2+1] = penv->VolEnv[ienv];\n\t\t\t\t\txmsh.penv[ienv*2] = penv->PanPoints[ienv];\n\t\t\t\t\txmsh.penv[ienv*2+1] = penv->PanEnv[ienv];\n\t\t\t\t}\n\t\t\t\tif (penv->dwFlags & ENV_VOLUME) xmsh.vtype |= 1;\n\t\t\t\tif (penv->dwFlags & ENV_VOLSUSTAIN) xmsh.vtype |= 2;\n\t\t\t\tif (penv->dwFlags & ENV_VOLLOOP) xmsh.vtype |= 4;\n\t\t\t\tif (penv->dwFlags & ENV_PANNING) xmsh.ptype |= 1;\n\t\t\t\tif (penv->dwFlags & ENV_PANSUSTAIN) xmsh.ptype |= 2;\n\t\t\t\tif (penv->dwFlags & ENV_PANLOOP) xmsh.ptype |= 4;\n\t\t\t\txmsh.vsustain = (BYTE)penv->nVolSustainBegin;\n\t\t\t\txmsh.vloops = (BYTE)penv->nVolLoopStart;\n\t\t\t\txmsh.vloope = (BYTE)penv->nVolLoopEnd;\n\t\t\t\txmsh.psustain = (BYTE)penv->nPanSustainBegin;\n\t\t\t\txmsh.ploops = (BYTE)penv->nPanLoopStart;\n\t\t\t\txmsh.ploope = (BYTE)penv->nPanLoopEnd;\n\t\t\t\tfor (UINT j=0; j<96; j++) if (penv->Keyboard[j+12])\n\t\t\t\t{\n\t\t\t\t\tUINT k;\n\t\t\t\t\tfor (k=0; k<xmih.samples; k++)\tif (smptable[k] == penv->Keyboard[j+12]) break;\n\t\t\t\t\tif (k == xmih.samples)\n\t\t\t\t\t{\n\t\t\t\t\t\tsmptable[xmih.samples++] = penv->Keyboard[j+12];\n\t\t\t\t\t}\n\t\t\t\t\tif (xmih.samples >= 32) break;\n\t\t\t\t\txmsh.snum[j] = k;\n\t\t\t\t}\n//\t\t\t\txmsh.reserved2 = xmih.samples;\n\t\t\t}\n\t\t} else\n\t\t{\n\t\t\txmih.samples = 1;\n//\t\t\txmsh.reserved2 = 1;\n\t\t\tsmptable[0] = i;\n\t\t}\n\t\txmsh.shsize = (xmih.samples) ? 40 : 0;\n\t\tfwrite(&xmih, 1, sizeof(xmih), f);\n\t\tif (smptable[0])\n\t\t{\n\t\t\tMODINSTRUMENT *pvib = &Ins[smptable[0]];\n\t\t\txmsh.vibtype = pvib->nVibType;\n\t\t\txmsh.vibsweep = pvib->nVibSweep;\n\t\t\txmsh.vibdepth = pvib->nVibDepth;\n\t\t\txmsh.vibrate = pvib->nVibRate;\n\t\t}\n\t\tfwrite(&xmsh, 1, xmih.size - sizeof(xmih), f);\n\t\tif (!xmih.samples) continue;\n\t\tfor (UINT ins=0; ins<xmih.samples; ins++)\n\t\t{\n\t\t\tmemset(&xmss, 0, sizeof(xmss));\n\t\t\tif (smptable[ins]) memcpy(xmss.name, m_szNames[smptable[ins]], 22);\n\t\t\tpins = &Ins[smptable[ins]];\n\t\t\txmss.samplen = pins->nLength;\n\t\t\txmss.loopstart = pins->nLoopStart;\n\t\t\txmss.looplen = pins->nLoopEnd - pins->nLoopStart;\n\t\t\txmss.vol = pins->nVolume / 4;\n\t\t\txmss.finetune = (char)pins->nFineTune;\n\t\t\txmss.type = 0;\n\t\t\tif (pins->uFlags & CHN_LOOP) xmss.type = (pins->uFlags & CHN_PINGPONGLOOP) ? 2 : 1;\n\t\t\tflags[ins] = RS_PCM8D;\n#ifndef NO_PACKING\n\t\t\tif (nPacking)\n\t\t\t{\n\t\t\t\tif ((!(pins->uFlags & (CHN_16BIT|CHN_STEREO)))\n\t\t\t\t && (CanPackSample((char *)pins->pSample, pins->nLength, nPacking)))\n\t\t\t\t{\n\t\t\t\t\tflags[ins] = RS_ADPCM4;\n\t\t\t\t\txmss.res = 0xAD;\n\t\t\t\t}\n\t\t\t} else\n#endif\n\t\t\t{\n\t\t\t\tif (pins->uFlags & CHN_16BIT)\n\t\t\t\t{\n\t\t\t\t\tflags[ins] = RS_PCM16D;\n\t\t\t\t\txmss.type |= 0x10;\n\t\t\t\t\txmss.looplen *= 2;\n\t\t\t\t\txmss.loopstart *= 2;\n\t\t\t\t\txmss.samplen *= 2;\n\t\t\t\t}\n\t\t\t\tif (pins->uFlags & CHN_STEREO)\n\t\t\t\t{\n\t\t\t\t\tflags[ins] = (pins->uFlags & CHN_16BIT) ? RS_STPCM16D : RS_STPCM8D;\n\t\t\t\t\txmss.type |= 0x20;\n\t\t\t\t\txmss.looplen *= 2;\n\t\t\t\t\txmss.loopstart *= 2;\n\t\t\t\t\txmss.samplen *= 2;\n\t\t\t\t}\n\t\t\t}\n\t\t\txmss.pan = 255;\n\t\t\tif (pins->nPan < 256) xmss.pan = (BYTE)pins->nPan;\n\t\t\txmss.relnote = (signed char)pins->RelativeTone;\n\t\t\tfwrite(&xmss, 1, xmsh.shsize, f);\n\t\t}\n\t\tfor (UINT ismpd=0; ismpd<xmih.samples; ismpd++)\n\t\t{\n\t\t\tpins = &Ins[smptable[ismpd]];\n\t\t\tif (pins->pSample)\n\t\t\t{\n#ifndef NO_PACKING\n\t\t\t\tif ((flags[ismpd] == RS_ADPCM4) && (xmih.samples>1)) CanPackSample((char *)pins->pSample, pins->nLength, nPacking);\n#endif // NO_PACKING\n\t\t\t\tWriteSample(f, pins, flags[ismpd]);\n\t\t\t}\n\t\t}\n\t}\n\t// Writing song comments\n\tif ((m_lpszSongComments) && (m_lpszSongComments[0]))\n\t{\n\t\tDWORD d = 0x74786574;\n\t\tfwrite(&d, 1, 4, f);\n\t\td = strlen(m_lpszSongComments);\n\t\tfwrite(&d, 1, 4, f);\n\t\tfwrite(m_lpszSongComments, 1, d, f);\n\t}\n\t// Writing midi cfg\n\tif (m_dwSongFlags & SONG_EMBEDMIDICFG)\n\t{\n\t\tDWORD d = 0x4944494D;\n\t\tfwrite(&d, 1, 4, f);\n\t\td = sizeof(MODMIDICFG);\n\t\tfwrite(&d, 1, 4, f);\n\t\tfwrite(&m_MidiCfg, 1, sizeof(MODMIDICFG), f);\n\t}\n\t// Writing Pattern Names\n\tif ((m_nPatternNames) && (m_lpszPatternNames))\n\t{\n\t\tDWORD dwLen = m_nPatternNames * MAX_PATTERNNAME;\n\t\twhile ((dwLen >= MAX_PATTERNNAME) && (!m_lpszPatternNames[dwLen-MAX_PATTERNNAME])) dwLen -= MAX_PATTERNNAME;\n\t\tif (dwLen >= MAX_PATTERNNAME)\n\t\t{\n\t\t\tDWORD d = 0x4d414e50;\n\t\t\tfwrite(&d, 1, 4, f);\n\t\t\tfwrite(&dwLen, 1, 4, f);\n\t\t\tfwrite(m_lpszPatternNames, 1, dwLen, f);\n\t\t}\n\t}\n\t// Writing Channel Names\n\t{\n\t\tUINT nChnNames = 0;\n\t\tfor (UINT inam=0; inam<m_nChannels; inam++)\n\t\t{\n\t\t\tif (ChnSettings[inam].szName[0]) nChnNames = inam+1;\n\t\t}\n\t\t// Do it!\n\t\tif (nChnNames)\n\t\t{\n\t\t\tDWORD dwLen = nChnNames * MAX_CHANNELNAME;\n\t\t\tDWORD d = 0x4d414e43;\n\t\t\tfwrite(&d, 1, 4, f);\n\t\t\tfwrite(&dwLen, 1, 4, f);\n\t\t\tfor (UINT inam=0; inam<nChnNames; inam++)\n\t\t\t{\n\t\t\t\tfwrite(ChnSettings[inam].szName, 1, MAX_CHANNELNAME, f);\n\t\t\t}\n\t\t}\n\t}\n\t// Save mix plugins information\n\tSaveMixPlugins(f);\n\tfclose(f);\n\treturn TRUE;\n}\n\n#endif // MODPLUG_NO_FILESAVE\n"
  },
  {
    "path": "contrib/libmodplug/src/mmcmp.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Handles unpacking of Powerpack PP20\n * Authors: Olivier Lapicque <olivierl@jps.net>\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\nstatic BOOL PP20_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength);\n\n#pragma pack(1)\ntypedef struct MMCMPFILEHEADER\n{\n\tchar id[8];\t// \"ziRCONia\"\n\tWORD hdrsize;\n} MMCMPFILEHEADER, *LPMMCMPFILEHEADER;\n\ntypedef struct MMCMPHEADER\n{\n\tWORD version;\n\tWORD nblocks;\n\tDWORD filesize;\n\tDWORD blktable;\n\tBYTE glb_comp;\n\tBYTE fmt_comp;\n} MMCMPHEADER, *LPMMCMPHEADER;\n\ntypedef struct MMCMPBLOCK\n{\n\tDWORD unpk_size;\n\tDWORD pk_size;\n\tDWORD xor_chk;\n\tWORD sub_blk;\n\tWORD flags;\n\tWORD tt_entries;\n\tUSHORT num_bits;\n} MMCMPBLOCK, *LPMMCMPBLOCK;\n\ntypedef struct MMCMPSUBBLOCK\n{\n\tDWORD unpk_pos;\n\tDWORD unpk_size;\n} MMCMPSUBBLOCK, *LPMMCMPSUBBLOCK;\n#pragma pack()\n\n// make sure of structure sizes\ntypedef int chk_MMCMPFILEHEADER[(sizeof(struct MMCMPFILEHEADER) == 10) * 2 - 1];\ntypedef int chk_MMCMPHEADER[(sizeof(struct MMCMPHEADER) == 14) * 2 - 1];\ntypedef int chk_MMCMPBLOCK[(sizeof(struct MMCMPBLOCK) == 20) * 2 - 1];\ntypedef int chk_MMCMPSUBBLOCK[(sizeof(struct MMCMPSUBBLOCK) == 8) * 2 - 1];\n\n#define MMCMP_COMP\t\t0x0001\n#define MMCMP_DELTA\t\t0x0002\n#define MMCMP_16BIT\t\t0x0004\n#define MMCMP_ABS16\t\t0x0200\n\ntypedef struct MMCMPBITBUFFER\n{\n\tUINT bitcount;\n\tDWORD bitbuffer;\n\tLPCBYTE pSrc;\n\tLPCBYTE pEnd;\n\n\tDWORD GetBits(UINT nBits);\n} MMCMPBITBUFFER;\n\n\nDWORD MMCMPBITBUFFER::GetBits(UINT nBits)\n//---------------------------------------\n{\n\tDWORD d;\n\tif (!nBits) return 0;\n\twhile (bitcount < 24)\n\t{\n\t\tbitbuffer |= ((pSrc < pEnd) ? *pSrc++ : 0) << bitcount;\n\t\tbitcount += 8;\n\t}\n\td = bitbuffer & ((1 << nBits) - 1);\n\tbitbuffer >>= nBits;\n\tbitcount -= nBits;\n\treturn d;\n}\n\n//#define MMCMP_LOG\n\n#ifdef MMCMP_LOG\nextern void Log(LPCSTR s, ...);\n#endif\n\nstatic const DWORD MMCMP8BitCommands[8] =\n{\n\t0x01, 0x03,\t0x07, 0x0F,\t0x1E, 0x3C,\t0x78, 0xF8\n};\n\nstatic const UINT MMCMP8BitFetch[8] =\n{\n\t3, 3, 3, 3, 2, 1, 0, 0\n};\n\nstatic const DWORD MMCMP16BitCommands[16] =\n{\n\t0x01, 0x03,\t0x07, 0x0F,\t0x1E, 0x3C,\t0x78, 0xF0,\n\t0x1F0, 0x3F0, 0x7F0, 0xFF0, 0x1FF0, 0x3FF0, 0x7FF0, 0xFFF0\n};\n\nstatic const UINT MMCMP16BitFetch[16] =\n{\n\t4, 4, 4, 4, 3, 2, 1, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0\n};\n\n\nstatic void swap_mfh(LPMMCMPFILEHEADER fh)\n{\n\tfh->hdrsize = bswapLE16(fh->hdrsize);\n}\n\nstatic void swap_mmh(LPMMCMPHEADER mh)\n{\n\tmh->version = bswapLE16(mh->version);\n\tmh->nblocks = bswapLE16(mh->nblocks);\n\tmh->filesize = bswapLE32(mh->filesize);\n\tmh->blktable = bswapLE32(mh->blktable);\n}\n\nstatic void swap_block (LPMMCMPBLOCK blk)\n{\n\tblk->unpk_size = bswapLE32(blk->unpk_size);\n\tblk->pk_size = bswapLE32(blk->pk_size);\n\tblk->xor_chk = bswapLE32(blk->xor_chk);\n\tblk->sub_blk = bswapLE16(blk->sub_blk);\n\tblk->flags = bswapLE16(blk->flags);\n\tblk->tt_entries = bswapLE16(blk->tt_entries);\n\tblk->num_bits = bswapLE16(blk->num_bits);\n}\n\nstatic void swap_subblock (LPMMCMPSUBBLOCK sblk)\n{\n\tsblk->unpk_pos = bswapLE32(sblk->unpk_pos);\n\tsblk->unpk_size = bswapLE32(sblk->unpk_size);\n}\n\n\nBOOL MMCMP_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength)\n//---------------------------------------------------------\n{\n\tDWORD dwMemLength;\n\tLPCBYTE lpMemFile;\n\tLPBYTE pBuffer;\n\tLPMMCMPFILEHEADER pmfh;\n\tLPMMCMPHEADER pmmh;\n\tconst DWORD *pblk_table;\n\tDWORD dwFileSize;\n\tBYTE tmp0[32], tmp1[32];\n\n\tif (PP20_Unpack(ppMemFile, pdwMemLength))\n\t{\n\t\treturn TRUE;\n\t}\n\n\tdwMemLength = *pdwMemLength;\n\tlpMemFile = *ppMemFile;\n\tif ((dwMemLength < 256) || (!lpMemFile)) return FALSE;\n\tmemcpy(tmp0, lpMemFile, 24);\n\tpmfh = (LPMMCMPFILEHEADER)(tmp0);\n\tpmmh = (LPMMCMPHEADER)(tmp0+10);\n\tswap_mfh(pmfh);\n\tswap_mmh(pmmh);\n\n\tif ((memcmp(pmfh->id,\"ziRCONia\",8) != 0) || (pmfh->hdrsize < 14)\n\t || (!pmmh->nblocks) || (pmmh->filesize < 16) || (pmmh->filesize > 0x8000000)\n\t || (pmmh->blktable >= dwMemLength) || (pmmh->blktable + 4*pmmh->nblocks > dwMemLength)) return FALSE;\n\tdwFileSize = pmmh->filesize;\n\tif ((pBuffer = (LPBYTE)GlobalAllocPtr(GHND, (dwFileSize + 31) & ~15)) == NULL) return FALSE;\n\tpblk_table = (const DWORD *)(lpMemFile+pmmh->blktable);\n\tfor (UINT nBlock=0; nBlock<pmmh->nblocks; nBlock++)\n\t{\n\t\tDWORD dwMemPos = bswapLE32(pblk_table[nBlock]);\n\t\tDWORD dwSubPos;\n\t\tLPMMCMPBLOCK pblk;\n\t\tLPMMCMPSUBBLOCK psubblk;\n\n\t\tif (dwMemPos >= dwMemLength - 20) break;\n\t\tmemcpy(tmp1, lpMemFile+dwMemPos, 28);\n\t\tpblk = (LPMMCMPBLOCK)(tmp1);\n\t\tpsubblk = (LPMMCMPSUBBLOCK)(tmp1+20);\n\t\tswap_block(pblk);\n\t\tswap_subblock(psubblk);\n\n\t\tif (dwMemPos + 20 + pblk->sub_blk*8 >= dwMemLength) break;\n\t\tdwSubPos = dwMemPos + 20;\n\t\tdwMemPos += 20 + pblk->sub_blk*8;\n#ifdef MMCMP_LOG\n\t\tLog(\"block %d: flags=%04X sub_blocks=%d\", nBlock, (UINT)pblk->flags, (UINT)pblk->sub_blk);\n\t\tLog(\" pksize=%d unpksize=%d\", pblk->pk_size, pblk->unpk_size);\n\t\tLog(\" tt_entries=%d num_bits=%d\\n\", pblk->tt_entries, pblk->num_bits);\n#endif\n\t\t// Data is not packed\n\t\tif (!(pblk->flags & MMCMP_COMP))\n\t\t{\n\t\t\tfor (UINT i=0; i<pblk->sub_blk; i++)\n\t\t\t{\n\t\t\t\tif ((psubblk->unpk_pos >= dwFileSize) ||\n\t\t\t\t\t(psubblk->unpk_size >= dwFileSize) ||\n\t\t\t\t\t(psubblk->unpk_size > dwFileSize - psubblk->unpk_pos) ||\n\t\t\t\t\tpsubblk->unpk_size > dwMemLength - dwMemPos) break;\n#ifdef MMCMP_LOG\n\t\t\t\tLog(\"  Unpacked sub-block %d: offset %d, size=%d\\n\", i, psubblk->unpk_pos, psubblk->unpk_size);\n#endif\n\t\t\t\tmemcpy(pBuffer+psubblk->unpk_pos, lpMemFile+dwMemPos, psubblk->unpk_size);\n\t\t\t\tdwMemPos += psubblk->unpk_size;\n\t\t\t\tmemcpy(tmp1+20,lpMemFile+dwSubPos+i*8,8);\n\t\t\t\tswap_subblock(psubblk);\n\t\t\t}\n\t\t} else\n\t\t// Data is 16-bit packed\n\t\tif (pblk->flags & MMCMP_16BIT && pblk->num_bits < 16)\n\t\t{\n\t\t\tMMCMPBITBUFFER bb;\n\t\t\tLPWORD pDest = (LPWORD)(pBuffer + psubblk->unpk_pos);\n\t\t\tDWORD dwSize = psubblk->unpk_size >> 1;\n\t\t\tDWORD dwPos = 0;\n\t\t\tUINT numbits = pblk->num_bits;\n\t\t\tUINT subblk = 0, oldval = 0;\n\n\t\t\tif (dwSize * 2 > dwFileSize-psubblk->unpk_pos ||\n\t\t\t\tpsubblk->unpk_pos > dwMemLength-dwMemPos)\n\t\t\t\tbreak;\n\n#ifdef MMCMP_LOG\n\t\t\tLog(\"  16-bit block: pos=%d size=%d \", psubblk->unpk_pos, psubblk->unpk_size);\n\t\t\tif (pblk->flags & MMCMP_DELTA) Log(\"DELTA \");\n\t\t\tif (pblk->flags & MMCMP_ABS16) Log(\"ABS16 \");\n\t\t\tLog(\"\\n\");\n#endif\n\t\t\tbb.bitcount = 0;\n\t\t\tbb.bitbuffer = 0;\n\t\t\tbb.pSrc = lpMemFile+dwMemPos+pblk->tt_entries;\n\t\t\tbb.pEnd = lpMemFile+dwMemPos+pblk->pk_size;\n\t\t\tif (bb.pEnd > lpMemFile+dwMemLength)\n\t\t\t\tbb.pEnd = lpMemFile+dwMemLength;\n\t\t\twhile (subblk < pblk->sub_blk)\n\t\t\t{\n\t\t\t\tUINT newval = 0x10000;\n\t\t\t\tDWORD d = bb.GetBits(numbits+1);\n\n\t\t\t\tif ((psubblk->unpk_pos >= dwFileSize) ||\n\t\t\t\t\t(psubblk->unpk_size >= dwFileSize) ||\n\t\t\t\t\t(psubblk->unpk_size > dwFileSize - psubblk->unpk_pos))\n\t\t\t\t\tdwPos = dwSize;\n\n\t\t\t\tif (d >= MMCMP16BitCommands[numbits])\n\t\t\t\t{\n\t\t\t\t\tUINT nFetch = MMCMP16BitFetch[numbits];\n\t\t\t\t\tUINT newbits = bb.GetBits(nFetch) + ((d - MMCMP16BitCommands[numbits]) << nFetch);\n\t\t\t\t\tif (newbits != numbits)\n\t\t\t\t\t{\n\t\t\t\t\t\tnumbits = newbits & 0x0F;\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tif ((d = bb.GetBits(4)) == 0x0F)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (bb.GetBits(1)) break;\n\t\t\t\t\t\t\tnewval = 0xFFFF;\n\t\t\t\t\t\t} else\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewval = 0xFFF0 + d;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tnewval = d;\n\t\t\t\t}\n\t\t\t\tif (newval < 0x10000 && dwPos < dwSize)\n\t\t\t\t{\n\t\t\t\t\tnewval = (newval & 1) ? (UINT)(-(LONG)((newval+1) >> 1)) : (UINT)(newval >> 1);\n\t\t\t\t\tif (pblk->flags & MMCMP_DELTA)\n\t\t\t\t\t{\n\t\t\t\t\t\tnewval += oldval;\n\t\t\t\t\t\toldval = newval;\n\t\t\t\t\t} else\n\t\t\t\t\tif (!(pblk->flags & MMCMP_ABS16))\n\t\t\t\t\t{\n\t\t\t\t\t\tnewval ^= 0x8000;\n\t\t\t\t\t}\n\t\t\t\t\tWORD swapped = (WORD)newval;\n\t\t\t\t\tpDest[dwPos++] = bswapLE16(swapped);\n\t\t\t\t}\n\t\t\t\tif (dwPos >= dwSize)\n\t\t\t\t{\n\t\t\t\t\tsubblk++;\n\t\t\t\t\tmemcpy(tmp1+20,lpMemFile+dwSubPos+subblk*8,8);\n\t\t\t\t\tswap_subblock(psubblk);\n\t\t\t\t\tdwPos = 0;\n\t\t\t\t\tdwSize = psubblk->unpk_size >> 1;\n\t\t\t\t\tif ( psubblk->unpk_pos >= dwFileSize ||\n\t\t\t\t\t \tdwSize * 2 > dwFileSize ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tpDest = (LPWORD)(pBuffer + psubblk->unpk_pos);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (pblk->num_bits < 8)\n\t\t// Data is 8-bit packed\n\t\t{\n\t\t\tMMCMPBITBUFFER bb;\n\t\t\tLPBYTE pDest = pBuffer + psubblk->unpk_pos;\n\t\t\tDWORD dwSize = psubblk->unpk_size;\n\t\t\tDWORD dwPos = 0;\n\t\t\tUINT numbits = pblk->num_bits;\n\t\t\tUINT subblk = 0, oldval = 0;\n\t\t\tLPCBYTE ptable = lpMemFile+dwMemPos;\n\n\t\t\tif (dwSize > dwFileSize-psubblk->unpk_pos ||\n\t\t\t\tpsubblk->unpk_pos > dwMemLength-dwMemPos)\n\t\t\t\tbreak;\n\n\t\t\tbb.bitcount = 0;\n\t\t\tbb.bitbuffer = 0;\n\t\t\tbb.pSrc = lpMemFile+dwMemPos+pblk->tt_entries;\n\t\t\tbb.pEnd = lpMemFile+dwMemPos+pblk->pk_size;\n\t\t\tif (bb.pEnd > lpMemFile+dwMemLength)\n\t\t\t\tbb.pEnd = lpMemFile+dwMemLength;\t\n\t\t\twhile (subblk < pblk->sub_blk)\n\t\t\t{\n\t\t\t\tUINT newval = 0x100;\n\t\t\t\tDWORD d = bb.GetBits(numbits+1);\n\n\t\t\t\tif ((psubblk->unpk_pos >= dwFileSize) ||\n\t\t\t\t\t(psubblk->unpk_size >= dwFileSize) ||\n\t\t\t\t\t(psubblk->unpk_size > dwFileSize - (psubblk->unpk_pos)))\n\t\t\t\t\tdwPos = dwSize;\n\n\t\t\t\tif (d >= MMCMP8BitCommands[numbits])\n\t\t\t\t{\n\t\t\t\t\tUINT nFetch = MMCMP8BitFetch[numbits];\n\t\t\t\t\tUINT newbits = bb.GetBits(nFetch) + ((d - MMCMP8BitCommands[numbits]) << nFetch);\n\t\t\t\t\tif (newbits != numbits)\n\t\t\t\t\t{\n\t\t\t\t\t\tnumbits = newbits & 0x07;\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tif ((d = bb.GetBits(3)) == 7)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (bb.GetBits(1)) break;\n\t\t\t\t\t\t\tnewval = 0xFF;\n\t\t\t\t\t\t} else\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewval = 0xF8 + d;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tnewval = d;\n\t\t\t\t}\n\t\t\t\tif (newval < 0x100 && dwPos < dwSize && dwMemPos < dwMemLength - newval)\n\t\t\t\t{\n\t\t\t\t\tint n = ptable[newval];\n\t\t\t\t\tif (pblk->flags & MMCMP_DELTA)\n\t\t\t\t\t{\n\t\t\t\t\t\tn += oldval;\n\t\t\t\t\t\toldval = n;\n\t\t\t\t\t}\n\t\t\t\t\tpDest[dwPos++] = (BYTE)n;\n\t\t\t\t}\n\t\t\t\tif (dwPos >= dwSize)\n\t\t\t\t{\n\t\t\t\t\tsubblk++;\n\t\t\t\t\tmemcpy(tmp1+20,lpMemFile+dwSubPos+subblk*8,8);\n\t\t\t\t\tswap_subblock(psubblk);\n\t\t\t\t\tdwPos = 0;\n\t\t\t\t\tdwSize = psubblk->unpk_size;\n\t\t\t\t\tif ( psubblk->unpk_pos >= dwFileSize ||\n\t\t\t\t\t \tdwSize > dwFileSize )\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tpDest = pBuffer + psubblk->unpk_pos;\n\t\t\t\t}\n\t\t\t}\n\t\t} else\n\t\t{\n\t\t\tGlobalFreePtr(pBuffer);\n\t\t\treturn FALSE;\n\t\t}\n\t}\n\t*ppMemFile = pBuffer;\n\t*pdwMemLength = dwFileSize;\n\treturn TRUE;\n}\n\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// PowerPack PP20 Unpacker\n//\n\ntypedef struct _PPBITBUFFER\n{\n\tUINT bitcount;\n\tULONG bitbuffer;\n\tLPCBYTE pStart;\n\tLPCBYTE pSrc;\n\n\tULONG GetBits(UINT n);\n} PPBITBUFFER;\n\n\nULONG PPBITBUFFER::GetBits(UINT n)\n{\n\tULONG result = 0;\n\n\tfor (UINT i=0; i<n; i++)\n\t{\n\t\tif (!bitcount)\n\t\t{\n\t\t\tbitcount = 8;\n\t\t\tif (pSrc != pStart) pSrc--;\n\t\t\tbitbuffer = *pSrc;\n\t\t}\n\t\tresult = (result<<1) | (bitbuffer&1);\n\t\tbitbuffer >>= 1;\n\t\tbitcount--;\n\t}\n\treturn result;\n}\n\n\nstatic VOID PP20_DoUnpack(const BYTE *pSrc, UINT nSrcLen, BYTE *pDst, UINT nDstLen)\n{\n\tPPBITBUFFER BitBuffer;\n\tULONG nBytesLeft;\n\n\tBitBuffer.pStart = pSrc;\n\tBitBuffer.pSrc = pSrc + nSrcLen - 4;\n\tBitBuffer.bitbuffer = 0;\n\tBitBuffer.bitcount = 0;\n\tBitBuffer.GetBits(pSrc[nSrcLen-1]);\n\tnBytesLeft = nDstLen;\n\twhile (nBytesLeft > 0)\n\t{\n\t\tif (!BitBuffer.GetBits(1))\n\t\t{\n\t\t\tUINT n = 1;\n\t\t\twhile (n < nBytesLeft)\n\t\t\t{\n\t\t\t\tUINT code = BitBuffer.GetBits(2);\n\t\t\t\tn += code;\n\t\t\t\tif (code != 3) break;\n\t\t\t}\n\t\t\tfor (UINT i=0; i<n; i++)\n\t\t\t{\n\t\t\t\tpDst[nBytesLeft - 1] = (BYTE)BitBuffer.GetBits(8);\n\t\t\t\tif (!--nBytesLeft) break;\n\t\t\t}\n\t\t\tif (!nBytesLeft) break;\n\t\t}\n\t\t{\n\t\t\tUINT n = BitBuffer.GetBits(2)+1;\n\t\t\tUINT nbits = pSrc[n-1];\n\t\t\tUINT nofs;\n\t\t\tif (n==4)\n\t\t\t{\n\t\t\t\tnofs = BitBuffer.GetBits( (BitBuffer.GetBits(1)) ? nbits : 7 );\n\t\t\t\twhile (n < nBytesLeft)\n\t\t\t\t{\n\t\t\t\t\tUINT code = BitBuffer.GetBits(3);\n\t\t\t\t\tn += code;\n\t\t\t\t\tif (code != 7) break;\n\t\t\t\t}\n\t\t\t} else\n\t\t\t{\n\t\t\t\tnofs = BitBuffer.GetBits(nbits);\n\t\t\t}\n\t\t\tfor (UINT i=0; i<=n; i++)\n\t\t\t{\n\t\t\t\tpDst[nBytesLeft-1] = (nBytesLeft+nofs < nDstLen) ? pDst[nBytesLeft+nofs] : 0;\n\t\t\t\tif (!--nBytesLeft) break;\n\t\t\t}\n\t\t}\n\t}\n}\n\n\nstatic BOOL PP20_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength)\n{\n\tDWORD dwMemLength = *pdwMemLength;\n\tLPCBYTE lpMemFile = *ppMemFile;\n\tDWORD dwDstLen;\n\tLPBYTE pBuffer;\n\n\tif ((!lpMemFile) || (dwMemLength < 256) || (memcmp(lpMemFile,\"PP20\",4) != 0)) return FALSE;\n\tdwDstLen = (lpMemFile[dwMemLength-4]<<16) | (lpMemFile[dwMemLength-3]<<8) | (lpMemFile[dwMemLength-2]);\n\t//Log(\"PP20 detected: Packed length=%d, Unpacked length=%d\\n\", dwMemLength, dwDstLen);\n\tif ((dwDstLen < 512) || (dwDstLen > 0x400000) || (dwDstLen > 16*dwMemLength)) return FALSE;\n\tif ((pBuffer = (LPBYTE)GlobalAllocPtr(GHND, (dwDstLen + 31) & ~15)) == NULL) return FALSE;\n\tPP20_DoUnpack(lpMemFile+4, dwMemLength-4, pBuffer, dwDstLen);\n\t*ppMemFile = pBuffer;\n\t*pdwMemLength = dwDstLen;\n\treturn TRUE;\n}\n"
  },
  {
    "path": "contrib/libmodplug/src/modplug.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Kenton Varda <temporal@gauge3d.org> (C interface wrapper)\n */\n\n#include \"stdafx.h\"\n#include \"modplug.h\"\n#include \"sndfile.h\"\n\nstruct _ModPlugFile\n{\n\tCSoundFile mSoundFile;\n};\n\nnamespace ModPlug\n{\n\tstatic ModPlug_Settings gSettings =\n\t{\n\t\tMODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION,\n\n\t\t2, // mChannels\n\t\t16, // mBits\n\t\t44100, // mFrequency\n\t\tMODPLUG_RESAMPLE_LINEAR, //mResamplingMode\n\n\t\t128, // mStereoSeparation\n\t\t32, // mMaxMixChannels\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t0,\n\t\t0\n\t};\n\n\tstatic int gSampleSize;\n\n\tstatic void UpdateSettings(bool updateBasicConfig)\n\t{\n\t\tif(gSettings.mFlags & MODPLUG_ENABLE_REVERB)\n\t\t{\n\t\t\tCSoundFile::SetReverbParameters(gSettings.mReverbDepth,\n\t\t\t                                gSettings.mReverbDelay);\n\t\t}\n\n\t\tif(gSettings.mFlags & MODPLUG_ENABLE_MEGABASS)\n\t\t{\n\t\t\tCSoundFile::SetXBassParameters(gSettings.mBassAmount,\n\t\t\t                               gSettings.mBassRange);\n\t\t}\n\t\telse // modplug seems to ignore the SetWaveConfigEx() setting for bass boost\n\t\t\tCSoundFile::SetXBassParameters(0, 0);\n\n\t\tif(gSettings.mFlags & MODPLUG_ENABLE_SURROUND)\n\t\t{\n\t\t\tCSoundFile::SetSurroundParameters(gSettings.mSurroundDepth,\n\t\t\t                                  gSettings.mSurroundDelay);\n\t\t}\n\n\t\tif(updateBasicConfig)\n\t\t{\n\t\t\tCSoundFile::SetWaveConfig(gSettings.mFrequency,\n                                                  gSettings.mBits,\n\t\t\t                          gSettings.mChannels);\n\t\t\tCSoundFile::SetMixConfig(gSettings.mStereoSeparation,\n                                                 gSettings.mMaxMixChannels);\n\n\t\t\tgSampleSize = gSettings.mBits / 8 * gSettings.mChannels;\n\t\t}\n\n\t\tCSoundFile::SetWaveConfigEx(gSettings.mFlags & MODPLUG_ENABLE_SURROUND,\n\t\t                            !(gSettings.mFlags & MODPLUG_ENABLE_OVERSAMPLING),\n\t\t                            gSettings.mFlags & MODPLUG_ENABLE_REVERB,\n\t\t                            true,\n\t\t                            gSettings.mFlags & MODPLUG_ENABLE_MEGABASS,\n\t\t                            gSettings.mFlags & MODPLUG_ENABLE_NOISE_REDUCTION,\n\t\t                            false);\n\t\tCSoundFile::SetResamplingMode(gSettings.mResamplingMode);\n\t}\n}\n\nModPlugFile* ModPlug_Load(const void* data, int size)\n{\n\tModPlugFile* result = new ModPlugFile;\n\tModPlug::UpdateSettings(true);\n\tif(result->mSoundFile.Create((const BYTE*)data, size))\n\t{\n\t\tresult->mSoundFile.SetRepeatCount(ModPlug::gSettings.mLoopCount);\n\t\treturn result;\n\t}\n\telse\n\t{\n\t\tdelete result;\n\t\treturn NULL;\n\t}\n}\n\nvoid ModPlug_Unload(ModPlugFile* file)\n{\n\tfile->mSoundFile.Destroy();\n\tdelete file;\n}\n\nint ModPlug_Read(ModPlugFile* file, void* buffer, int size)\n{\n\treturn file->mSoundFile.Read(buffer, size) * ModPlug::gSampleSize;\n}\n\n#ifdef MODPLUG_DEADCODE\n\nconst char* ModPlug_GetName(ModPlugFile* file)\n{\n\treturn file->mSoundFile.GetTitle();\n}\n\nint ModPlug_GetLength(ModPlugFile* file)\n{\n\treturn file->mSoundFile.GetSongTime() * 1000;\n}\n\n#endif // MODPLUG_DEADCODE\n\nvoid ModPlug_InitMixerCallback(ModPlugFile* file,ModPlugMixerProc proc)\n{\n\tfile->mSoundFile.gpSndMixHook = (LPSNDMIXHOOKPROC)proc ;\n\treturn;\n}\n\nvoid ModPlug_UnloadMixerCallback(ModPlugFile* file)\n{\n\tfile->mSoundFile.gpSndMixHook = NULL;\n\treturn ;\n}\n\nunsigned int ModPlug_GetMasterVolume(ModPlugFile* file)\n{\n\treturn (unsigned int)file->mSoundFile.m_nMasterVolume;\n}\n\nvoid ModPlug_SetMasterVolume(ModPlugFile* file,unsigned int cvol)\n{\n\t(void)file->mSoundFile.SetMasterVolume( (UINT)cvol,\n\t\t\t\t\t\tFALSE );\n\treturn ;\n}\n\nint ModPlug_GetCurrentSpeed(ModPlugFile* file)\n{\n\treturn file->mSoundFile.m_nMusicSpeed;\n}\n\nint ModPlug_GetCurrentTempo(ModPlugFile* file)\n{\n\treturn file->mSoundFile.m_nMusicTempo;\n}\n\nint ModPlug_GetCurrentOrder(ModPlugFile* file)\n{\n\treturn file->mSoundFile.GetCurrentOrder();\n}\n\nint ModPlug_GetCurrentPattern(ModPlugFile* file)\n{\n\treturn file->mSoundFile.GetCurrentPattern();\n}\n\nint ModPlug_GetCurrentRow(ModPlugFile* file)\n{\n\treturn file->mSoundFile.m_nRow;\n}\n\nint ModPlug_GetPlayingChannels(ModPlugFile* file)\n{\n\treturn ( file->mSoundFile.m_nMixChannels < file->mSoundFile.m_nMaxMixChannels ? file->mSoundFile.m_nMixChannels : file->mSoundFile.m_nMaxMixChannels );\n}\n\nvoid ModPlug_SeekOrder(ModPlugFile* file,int order)\n{\n\tfile->mSoundFile.SetCurrentOrder(order);\n}\n\nint ModPlug_GetModuleType(ModPlugFile* file)\n{\n\treturn file->mSoundFile.m_nType;\n}\n\nchar* ModPlug_GetMessage(ModPlugFile* file)\n{\n\treturn file->mSoundFile.m_lpszSongComments;\n}\n\n#ifndef MODPLUG_NO_FILESAVE\nchar ModPlug_ExportS3M(ModPlugFile* file,const char* filepath)\n{\n\treturn (char)file->mSoundFile.SaveS3M(filepath,0);\n}\n\nchar ModPlug_ExportXM(ModPlugFile* file,const char* filepath)\n{\n\treturn (char)file->mSoundFile.SaveXM(filepath,0);\n}\n\nchar ModPlug_ExportMOD(ModPlugFile* file,const char* filepath)\n{\n\treturn (char)file->mSoundFile.SaveMod(filepath,0);\n}\n\nchar ModPlug_ExportIT(ModPlugFile* file,const char* filepath)\n{\n\treturn (char)file->mSoundFile.SaveIT(filepath,0);\n}\n#endif // MODPLUG_NO_FILESAVE\n\nunsigned int ModPlug_NumInstruments(ModPlugFile* file)\n{\n\treturn file->mSoundFile.m_nInstruments;\n}\n\nunsigned int ModPlug_NumSamples(ModPlugFile* file)\n{\n\treturn file->mSoundFile.m_nSamples;\n}\n\nunsigned int ModPlug_NumPatterns(ModPlugFile* file)\n{\n\treturn file->mSoundFile.GetNumPatterns();\n}\n\nunsigned int ModPlug_NumChannels(ModPlugFile* file)\n{\n\treturn file->mSoundFile.GetNumChannels();\n}\n\nunsigned int ModPlug_SampleName(ModPlugFile* file,unsigned int qual,char* buff)\n{\n\treturn file->mSoundFile.GetSampleName(qual,buff);\n}\n\nunsigned int ModPlug_InstrumentName(ModPlugFile* file,unsigned int qual,char* buff)\n{\n\treturn file->mSoundFile.GetInstrumentName(qual,buff);\n}\n\nModPlugNote* ModPlug_GetPattern(ModPlugFile* file,int pattern,unsigned int* numrows) {\n\tif ( pattern<MAX_PATTERNS && pattern >= 0) {\n\t\tif (file->mSoundFile.Patterns[pattern]) {\n\t\t\tif (numrows) *numrows=(unsigned int)file->mSoundFile.PatternSize[pattern];\n\t\t\treturn (ModPlugNote*)file->mSoundFile.Patterns[pattern];\n\t\t}\n\t}\n\treturn NULL;\n}\n\nvoid ModPlug_Seek(ModPlugFile* file, int millisecond)\n{\n\tint maxpos;\n\tint maxtime = file->mSoundFile.GetSongTime() * 1000;\n\tfloat postime;\n\n\tif(millisecond > maxtime)\n\t\tmillisecond = maxtime;\n\tmaxpos = file->mSoundFile.GetMaxPosition();\n\tpostime = 0.0f;\n\tif (maxtime != 0)\n\t\tpostime = (float)maxpos / (float)maxtime;\n\n\tfile->mSoundFile.SetCurrentPos((int)(millisecond * postime));\n}\n\nvoid ModPlug_GetSettings(ModPlug_Settings* settings)\n{\n\tmemcpy(settings, &ModPlug::gSettings, sizeof(ModPlug_Settings));\n}\n\nvoid ModPlug_SetSettings(const ModPlug_Settings* settings)\n{\n\tmemcpy(&ModPlug::gSettings, settings, sizeof(ModPlug_Settings));\n\tModPlug::UpdateSettings(false); // do not update basic config.\n}\n"
  },
  {
    "path": "contrib/libmodplug/src/modplug.h",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Kenton Varda <temporal@gauge3d.org> (C interface wrapper)\n */\n\n#ifndef MODPLUG_H__INCLUDED\n#define MODPLUG_H__INCLUDED\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define MODPLUG_EXPORT\n\nstruct _ModPlugFile;\ntypedef struct _ModPlugFile ModPlugFile;\n\nstruct _ModPlugNote {\n\tunsigned char Note;\n\tunsigned char Instrument;\n\tunsigned char VolumeEffect;\n\tunsigned char Effect;\n\tunsigned char Volume;\n\tunsigned char Parameter;\n};\ntypedef struct _ModPlugNote ModPlugNote;\n\ntypedef void (*ModPlugMixerProc)(int*, unsigned long, unsigned long);\n\n/* Load a mod file.  [data] should point to a block of memory containing the complete\n * file, and [size] should be the size of that block.\n * Return the loaded mod file on success, or NULL on failure. */\nMODPLUG_EXPORT ModPlugFile* ModPlug_Load(const void* data, int size);\n/* Unload a mod file. */\nMODPLUG_EXPORT void ModPlug_Unload(ModPlugFile* file);\n\n/* Read sample data into the buffer.  Returns the number of bytes read.  If the end\n * of the mod has been reached, zero is returned. */\nMODPLUG_EXPORT int  ModPlug_Read(ModPlugFile* file, void* buffer, int size);\n\n#ifdef MODPLUG_DEADCODE\n\n/* Get the name of the mod.  The returned buffer is stored within the ModPlugFile\n * structure and will remain valid until you unload the file. */\nMODPLUG_EXPORT const char* ModPlug_GetName(ModPlugFile* file);\n\n/* Get the length of the mod, in milliseconds.  Note that this result is not always\n * accurate, especially in the case of mods with loops. */\nMODPLUG_EXPORT int ModPlug_GetLength(ModPlugFile* file);\n\n/* Seek to a particular position in the song.  Note that seeking and MODs don't mix very\n * well.  Some mods will be missing instruments for a short time after a seek, as ModPlug\n * does not scan the sequence backwards to find out which instruments were supposed to be\n * playing at that time.  (Doing so would be difficult and not very reliable.)  Also,\n * note that seeking is not very exact in some mods -- especially those for which\n * ModPlug_GetLength() does not report the full length. */\nMODPLUG_EXPORT void ModPlug_Seek(ModPlugFile* file, int millisecond);\n\n#endif // MODPLUG_DEADCODE\n\nenum _ModPlug_Flags\n{\n\tMODPLUG_ENABLE_OVERSAMPLING     = 1 << 0,  /* Enable oversampling (*highly* recommended) */\n\tMODPLUG_ENABLE_NOISE_REDUCTION  = 1 << 1,  /* Enable noise reduction */\n\tMODPLUG_ENABLE_REVERB           = 1 << 2,  /* Enable reverb */\n\tMODPLUG_ENABLE_MEGABASS         = 1 << 3,  /* Enable megabass */\n\tMODPLUG_ENABLE_SURROUND         = 1 << 4   /* Enable surround sound. */\n};\n\nenum _ModPlug_ResamplingMode\n{\n\tMODPLUG_RESAMPLE_NEAREST = 0,  /* No interpolation (very fast, extremely bad sound quality) */\n\tMODPLUG_RESAMPLE_LINEAR  = 1,  /* Linear interpolation (fast, good quality) */\n\tMODPLUG_RESAMPLE_SPLINE  = 2,  /* Cubic spline interpolation (high quality) */\n\tMODPLUG_RESAMPLE_FIR     = 3   /* 8-tap fir filter (extremely high quality) */\n};\n\ntypedef struct _ModPlug_Settings\n{\n\tint mFlags;  /* One or more of the MODPLUG_ENABLE_* flags above, bitwise-OR'ed */\n\n\t/* Note that ModPlug always decodes sound at 44100kHz, 32 bit, stereo and then\n\t * down-mixes to the settings you choose. */\n\tint mChannels;       /* Number of channels - 1 for mono or 2 for stereo */\n\tint mBits;           /* Bits per sample - 8, 16, or 32 */\n\tint mFrequency;      /* Sampling rate - 11025, 22050, or 44100 */\n\tint mResamplingMode; /* One of MODPLUG_RESAMPLE_*, above */\n\n\tint mStereoSeparation; /* Stereo separation, 1 - 256 */\n\tint mMaxMixChannels; /* Maximum number of mixing channels (polyphony), 32 - 256 */\n\n\tint mReverbDepth;    /* Reverb level 0(quiet)-100(loud)      */\n\tint mReverbDelay;    /* Reverb delay in ms, usually 40-200ms */\n\tint mBassAmount;     /* XBass level 0(quiet)-100(loud)       */\n\tint mBassRange;      /* XBass cutoff in Hz 10-100            */\n\tint mSurroundDepth;  /* Surround level 0(quiet)-100(heavy)   */\n\tint mSurroundDelay;  /* Surround delay in ms, usually 5-40ms */\n\tint mLoopCount;      /* Number of times to loop.  Zero prevents looping.\n\t\t\t      * -1 loops forever. */\n} ModPlug_Settings;\n\n/* Get and set the mod decoder settings.  All options, except for channels, bits-per-sample,\n * sampling rate, and loop count, will take effect immediately.  Those options which don't\n * take effect immediately will take effect the next time you load a mod. */\nMODPLUG_EXPORT void ModPlug_GetSettings(ModPlug_Settings* settings);\nMODPLUG_EXPORT void ModPlug_SetSettings(const ModPlug_Settings* settings);\n\n/* New ModPlug API Functions */\n/* NOTE: Master Volume (1-512) */\nMODPLUG_EXPORT unsigned int ModPlug_GetMasterVolume(ModPlugFile* file) ;\nMODPLUG_EXPORT void ModPlug_SetMasterVolume(ModPlugFile* file,unsigned int cvol) ;\n\nMODPLUG_EXPORT int ModPlug_GetCurrentSpeed(ModPlugFile* file);\nMODPLUG_EXPORT int ModPlug_GetCurrentTempo(ModPlugFile* file);\nMODPLUG_EXPORT int ModPlug_GetCurrentOrder(ModPlugFile* file);\nMODPLUG_EXPORT int ModPlug_GetCurrentPattern(ModPlugFile* file);\nMODPLUG_EXPORT int ModPlug_GetCurrentRow(ModPlugFile* file);\nMODPLUG_EXPORT int ModPlug_GetPlayingChannels(ModPlugFile* file);\n\nMODPLUG_EXPORT void ModPlug_SeekOrder(ModPlugFile* file,int order);\nMODPLUG_EXPORT int ModPlug_GetModuleType(ModPlugFile* file);\nMODPLUG_EXPORT char* ModPlug_GetMessage(ModPlugFile* file);\n\n#define MODPLUG_NO_FILESAVE /* experimental yet.  must match stdafx.h. */\n#ifndef MODPLUG_NO_FILESAVE\n/*\n * EXPERIMENTAL Export Functions\n */\n/*Export to a Scream Tracker 3 S3M module. EXPERIMENTAL (only works on Little-Endian platforms)*/\nMODPLUG_EXPORT char ModPlug_ExportS3M(ModPlugFile* file, const char* filepath);\n\n/*Export to a Extended Module (XM). EXPERIMENTAL (only works on Little-Endian platforms)*/\nMODPLUG_EXPORT char ModPlug_ExportXM(ModPlugFile* file, const char* filepath);\n\n/*Export to a Amiga MOD file. EXPERIMENTAL.*/\nMODPLUG_EXPORT char ModPlug_ExportMOD(ModPlugFile* file, const char* filepath);\n\n/*Export to a Impulse Tracker IT file. Should work OK in Little-Endian & Big-Endian platforms :-) */\nMODPLUG_EXPORT char ModPlug_ExportIT(ModPlugFile* file, const char* filepath);\n#endif /* MODPLUG_NO_FILESAVE */\n\nMODPLUG_EXPORT unsigned int ModPlug_NumInstruments(ModPlugFile* file);\nMODPLUG_EXPORT unsigned int ModPlug_NumSamples(ModPlugFile* file);\nMODPLUG_EXPORT unsigned int ModPlug_NumPatterns(ModPlugFile* file);\nMODPLUG_EXPORT unsigned int ModPlug_NumChannels(ModPlugFile* file);\nMODPLUG_EXPORT unsigned int ModPlug_SampleName(ModPlugFile* file, unsigned int qual, char* buff);\nMODPLUG_EXPORT unsigned int ModPlug_InstrumentName(ModPlugFile* file, unsigned int qual, char* buff);\n\n/*\n * Retrieve pattern note-data\n */\nMODPLUG_EXPORT ModPlugNote* ModPlug_GetPattern(ModPlugFile* file, int pattern, unsigned int* numrows);\n\n/*\n * =================\n * Mixer callback\n * =================\n *\n * Use this callback if you want to 'modify' the mixed data of LibModPlug.\n * \n * void proc(int* buffer,unsigned long channels,unsigned long nsamples) ;\n *\n * 'buffer': A buffer of mixed samples\n * 'channels': N. of channels in the buffer\n * 'nsamples': N. of samples in the buffeer (without taking care of n.channels)\n *\n * (Samples are signed 32-bit integers)\n */\nMODPLUG_EXPORT void ModPlug_InitMixerCallback(ModPlugFile* file,ModPlugMixerProc proc) ;\nMODPLUG_EXPORT void ModPlug_UnloadMixerCallback(ModPlugFile* file) ;\n\n#ifdef __cplusplus\n} /* extern \"C\" */\n#endif\n\n#endif\n"
  },
  {
    "path": "contrib/libmodplug/src/snd_dsp.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n#ifdef MODPLUG_FASTSOUNDLIB\n#define MODPLUG_NO_REVERB\n#endif\n\n\n// Delayed Surround Filters\n#ifndef MODPLUG_FASTSOUNDLIB\n#define nDolbyHiFltAttn\t\t6\n#define nDolbyHiFltMask\t\t3\n#define DOLBYATTNROUNDUP\t31\n#else\n#define nDolbyHiFltAttn\t\t3\n#define nDolbyHiFltMask\t\t3\n#define DOLBYATTNROUNDUP\t3\n#endif\n\n// Bass Expansion\n#define XBASS_DELAY\t\t\t14\t// 2.5 ms\n\n// Buffer Sizes\n#define XBASSBUFFERSIZE\t\t64\t\t// 2 ms at 50KHz\n#define FILTERBUFFERSIZE\t64\t\t// 1.25 ms\n#define SURROUNDBUFFERSIZE\t((MAX_SAMPLE_RATE * 50) / 1000)\n#define REVERBBUFFERSIZE\t((MAX_SAMPLE_RATE * 200) / 1000)\n#define REVERBBUFFERSIZE2\t((REVERBBUFFERSIZE*13) / 17)\n#define REVERBBUFFERSIZE3\t((REVERBBUFFERSIZE*7) / 13)\n#define REVERBBUFFERSIZE4\t((REVERBBUFFERSIZE*7) / 19)\n\n\n// DSP Effects: PUBLIC members\nUINT CSoundFile::m_nXBassDepth = 6;\nUINT CSoundFile::m_nXBassRange = XBASS_DELAY;\nUINT CSoundFile::m_nReverbDepth = 1;\nUINT CSoundFile::m_nReverbDelay = 100;\nUINT CSoundFile::m_nProLogicDepth = 12;\nUINT CSoundFile::m_nProLogicDelay = 20;\n\n////////////////////////////////////////////////////////////////////\n// DSP Effects internal state\n\n// Bass Expansion: low-pass filter\nstatic LONG nXBassSum = 0;\nstatic LONG nXBassBufferPos = 0;\nstatic LONG nXBassDlyPos = 0;\nstatic LONG nXBassMask = 0;\n\n// Noise Reduction: simple low-pass filter\nstatic LONG nLeftNR = 0;\nstatic LONG nRightNR = 0;\n\n// Surround Encoding: 1 delay line + low-pass filter + high-pass filter\nstatic LONG nSurroundSize = 0;\nstatic LONG nSurroundPos = 0;\nstatic LONG nDolbyDepth = 0;\nstatic LONG nDolbyLoDlyPos = 0;\nstatic LONG nDolbyLoFltPos = 0;\nstatic LONG nDolbyLoFltSum = 0;\nstatic LONG nDolbyHiFltPos = 0;\nstatic LONG nDolbyHiFltSum = 0;\n\n// Reverb: 4 delay lines + high-pass filter + low-pass filter\n#ifndef MODPLUG_NO_REVERB\nstatic LONG nReverbSize = 0;\nstatic LONG nReverbBufferPos = 0;\nstatic LONG nReverbSize2 = 0;\nstatic LONG nReverbBufferPos2 = 0;\nstatic LONG nReverbSize3 = 0;\nstatic LONG nReverbBufferPos3 = 0;\nstatic LONG nReverbSize4 = 0;\nstatic LONG nReverbBufferPos4 = 0;\nstatic LONG nReverbLoFltSum = 0;\nstatic LONG nReverbLoFltPos = 0;\nstatic LONG nReverbLoDlyPos = 0;\nstatic LONG nFilterAttn = 0;\nstatic LONG gRvbLowPass[8];\nstatic LONG gRvbLPPos = 0;\nstatic LONG gRvbLPSum = 0;\nstatic LONG ReverbLoFilterBuffer[XBASSBUFFERSIZE];\nstatic LONG ReverbLoFilterDelay[XBASSBUFFERSIZE];\nstatic LONG ReverbBuffer[REVERBBUFFERSIZE];\nstatic LONG ReverbBuffer2[REVERBBUFFERSIZE2];\nstatic LONG ReverbBuffer3[REVERBBUFFERSIZE3];\nstatic LONG ReverbBuffer4[REVERBBUFFERSIZE4];\n#endif\nstatic LONG XBassBuffer[XBASSBUFFERSIZE];\nstatic LONG XBassDelay[XBASSBUFFERSIZE];\nstatic LONG DolbyLoFilterBuffer[XBASSBUFFERSIZE];\nstatic LONG DolbyLoFilterDelay[XBASSBUFFERSIZE];\nstatic LONG DolbyHiFilterBuffer[FILTERBUFFERSIZE];\nstatic LONG SurroundBuffer[SURROUNDBUFFERSIZE];\n\n// Access the main temporary mix buffer directly: avoids an extra pointer\nextern int MixSoundBuffer[MIXBUFFERSIZE*2];\n//cextern int MixReverbBuffer[MIXBUFFERSIZE*2];\nextern int MixReverbBuffer[MIXBUFFERSIZE*2];\n\nstatic UINT GetMaskFromSize(UINT len)\n//-----------------------------------\n{\n\tUINT n = 2;\n\twhile (n <= len) n <<= 1;\n\treturn ((n >> 1) - 1);\n}\n\n\nvoid CSoundFile::InitializeDSP(BOOL bReset)\n//-----------------------------------------\n{\n\tif (!m_nReverbDelay) m_nReverbDelay = 100;\n\tif (!m_nXBassRange) m_nXBassRange = XBASS_DELAY;\n\tif (!m_nProLogicDelay) m_nProLogicDelay = 20;\n\tif (m_nXBassDepth > 8) m_nXBassDepth = 8;\n\tif (m_nXBassDepth < 2) m_nXBassDepth = 2;\n\tif (bReset)\n\t{\n\t\t// Noise Reduction\n\t\tnLeftNR = nRightNR = 0;\n\t}\n\t// Pro-Logic Surround\n\tnSurroundPos = nSurroundSize = 0;\n\tnDolbyLoFltPos = nDolbyLoFltSum = nDolbyLoDlyPos = 0;\n\tnDolbyHiFltPos = nDolbyHiFltSum = 0;\n\tif (gdwSoundSetup & SNDMIX_SURROUND)\n\t{\n\t\tmemset(DolbyLoFilterBuffer, 0, sizeof(DolbyLoFilterBuffer));\n\t\tmemset(DolbyHiFilterBuffer, 0, sizeof(DolbyHiFilterBuffer));\n\t\tmemset(DolbyLoFilterDelay, 0, sizeof(DolbyLoFilterDelay));\n\t\tmemset(SurroundBuffer, 0, sizeof(SurroundBuffer));\n\t\tnSurroundSize = (gdwMixingFreq * m_nProLogicDelay) / 1000;\n\t\tif (nSurroundSize > SURROUNDBUFFERSIZE) nSurroundSize = SURROUNDBUFFERSIZE;\n\t\tif (m_nProLogicDepth < 8) nDolbyDepth = (32 >> m_nProLogicDepth) + 32;\n\t\telse nDolbyDepth = (m_nProLogicDepth < 16) ? (8 + (m_nProLogicDepth - 8) * 7) : 64;\n\t\tnDolbyDepth >>= 2;\n\t}\n\t// Reverb Setup\n#ifndef MODPLUG_NO_REVERB\n\tif (gdwSoundSetup & SNDMIX_REVERB)\n\t{\n\t\tUINT nrs = (gdwMixingFreq * m_nReverbDelay) / 1000;\n\t\tUINT nfa = m_nReverbDepth+1;\n\t\tif (nrs > REVERBBUFFERSIZE) nrs = REVERBBUFFERSIZE;\n\t\tif ((bReset) || (nrs != (UINT)nReverbSize) || (nfa != (UINT)nFilterAttn))\n\t\t{\n\t\t\tnFilterAttn = nfa;\n\t\t\tnReverbSize = nrs;\n\t\t\tnReverbBufferPos = nReverbBufferPos2 = nReverbBufferPos3 = nReverbBufferPos4 = 0;\n\t\t\tnReverbLoFltSum = nReverbLoFltPos = nReverbLoDlyPos = 0;\n\t\t\tgRvbLPSum = gRvbLPPos = 0;\n\t\t\tnReverbSize2 = (nReverbSize * 13) / 17;\n\t\t\tif (nReverbSize2 > REVERBBUFFERSIZE2) nReverbSize2 = REVERBBUFFERSIZE2;\n\t\t\tnReverbSize3 = (nReverbSize * 7) / 13;\n\t\t\tif (nReverbSize3 > REVERBBUFFERSIZE3) nReverbSize3 = REVERBBUFFERSIZE3;\n\t\t\tnReverbSize4 = (nReverbSize * 7) / 19;\n\t\t\tif (nReverbSize4 > REVERBBUFFERSIZE4) nReverbSize4 = REVERBBUFFERSIZE4;\n\t\t\tmemset(ReverbLoFilterBuffer, 0, sizeof(ReverbLoFilterBuffer));\n\t\t\tmemset(ReverbLoFilterDelay, 0, sizeof(ReverbLoFilterDelay));\n\t\t\tmemset(ReverbBuffer, 0, sizeof(ReverbBuffer));\n\t\t\tmemset(ReverbBuffer2, 0, sizeof(ReverbBuffer2));\n\t\t\tmemset(ReverbBuffer3, 0, sizeof(ReverbBuffer3));\n\t\t\tmemset(ReverbBuffer4, 0, sizeof(ReverbBuffer4));\n\t\t\tmemset(gRvbLowPass, 0, sizeof(gRvbLowPass));\n\t\t}\n\t} else nReverbSize = 0;\n#endif\n\tBOOL bResetBass = FALSE;\n\t// Bass Expansion Reset\n\tif (gdwSoundSetup & SNDMIX_MEGABASS)\n\t{\n\t\tUINT nXBassSamples = (gdwMixingFreq * m_nXBassRange) / 10000;\n\t\tif (nXBassSamples > XBASSBUFFERSIZE) nXBassSamples = XBASSBUFFERSIZE;\n\t\tUINT mask = GetMaskFromSize(nXBassSamples);\n\t\tif ((bReset) || (mask != (UINT)nXBassMask))\n\t\t{\n\t\t\tnXBassMask = mask;\n\t\t\tbResetBass = TRUE;\n\t\t}\n\t} else\n\t{\n\t\tnXBassMask = 0;\n\t\tbResetBass = TRUE;\n\t}\n\tif (bResetBass)\n\t{\n\t\tnXBassSum = nXBassBufferPos = nXBassDlyPos = 0;\n\t\tmemset(XBassBuffer, 0, sizeof(XBassBuffer));\n\t\tmemset(XBassDelay, 0, sizeof(XBassDelay));\n\t}\n}\n\n\nvoid CSoundFile::ProcessStereoDSP(int count)\n//------------------------------------------\n{\n#ifndef MODPLUG_NO_REVERB\n\t// Reverb\n\tif (gdwSoundSetup & SNDMIX_REVERB)\n\t{\n\t\tint *pr = MixSoundBuffer, *pin = MixReverbBuffer, rvbcount = count;\n\t\tdo\n\t\t{\n\t\t\tint echo = ReverbBuffer[nReverbBufferPos] + ReverbBuffer2[nReverbBufferPos2]\n\t\t\t\t\t+ ReverbBuffer3[nReverbBufferPos3] + ReverbBuffer4[nReverbBufferPos4];\t// echo = reverb signal\n\t\t\t// Delay line and remove Low Frequencies\t\t\t// v = original signal\n\t\t\tint echodly = ReverbLoFilterDelay[nReverbLoDlyPos];\t// echodly = delayed signal\n\t\t\tReverbLoFilterDelay[nReverbLoDlyPos] = echo >> 1;\n\t\t\tnReverbLoDlyPos++;\n\t\t\tnReverbLoDlyPos &= 0x1F;\n\t\t\tint n = nReverbLoFltPos;\n\t\t\tnReverbLoFltSum -= ReverbLoFilterBuffer[n];\n\t\t\tint tmp = echo / 128;\n\t\t\tReverbLoFilterBuffer[n] = tmp;\n\t\t\tnReverbLoFltSum += tmp;\n\t\t\techodly -= nReverbLoFltSum;\n\t\t\tnReverbLoFltPos = (n + 1) & 0x3F;\n\t\t\t// Reverb\n\t\t\tint v = (pin[0]+pin[1]) >> nFilterAttn;\n\t\t\tpr[0] += pin[0] + echodly;\n\t\t\tpr[1] += pin[1] + echodly;\n\t\t\tv += echodly >> 2;\n\t\t\tReverbBuffer3[nReverbBufferPos3] = v;\n\t\t\tReverbBuffer4[nReverbBufferPos4] = v;\n\t\t\tv += echodly >> 4;\n\t\t\tv >>= 1;\n\t\t\tgRvbLPSum -= gRvbLowPass[gRvbLPPos];\n\t\t\tgRvbLPSum += v;\n\t\t\tgRvbLowPass[gRvbLPPos] = v;\n\t\t\tgRvbLPPos++;\n\t\t\tgRvbLPPos &= 7;\n\t\t\tint vlp = gRvbLPSum >> 2;\n\t\t\tReverbBuffer[nReverbBufferPos] = vlp;\n\t\t\tReverbBuffer2[nReverbBufferPos2] = vlp;\n\t\t\tif (++nReverbBufferPos >= nReverbSize) nReverbBufferPos = 0;\n\t\t\tif (++nReverbBufferPos2 >= nReverbSize2) nReverbBufferPos2 = 0;\n\t\t\tif (++nReverbBufferPos3 >= nReverbSize3) nReverbBufferPos3 = 0;\n\t\t\tif (++nReverbBufferPos4 >= nReverbSize4) nReverbBufferPos4 = 0;\n\t\t\tpr += 2;\n\t\t\tpin += 2;\n\t\t} while (--rvbcount);\n\t}\n#endif\n\t// Dolby Pro-Logic Surround\n\tif (gdwSoundSetup & SNDMIX_SURROUND)\n\t{\n\t\tint *pr = MixSoundBuffer, n = nDolbyLoFltPos;\n\t\tfor (int r=count; r; r--)\n\t\t{\n\t\t\tint v = (pr[0]+pr[1]+DOLBYATTNROUNDUP) >> (nDolbyHiFltAttn+1);\n#ifndef MODPLUG_FASTSOUNDLIB\n\t\t\tv *= (int)nDolbyDepth;\n#endif\n\t\t\t// Low-Pass Filter\n\t\t\tnDolbyHiFltSum -= DolbyHiFilterBuffer[nDolbyHiFltPos];\n\t\t\tDolbyHiFilterBuffer[nDolbyHiFltPos] = v;\n\t\t\tnDolbyHiFltSum += v;\n\t\t\tv = nDolbyHiFltSum;\n\t\t\tnDolbyHiFltPos++;\n\t\t\tnDolbyHiFltPos &= nDolbyHiFltMask;\n\t\t\t// Surround\n\t\t\tint secho = SurroundBuffer[nSurroundPos];\n\t\t\tSurroundBuffer[nSurroundPos] = v;\n\t\t\t// Delay line and remove low frequencies\n\t\t\tv = DolbyLoFilterDelay[nDolbyLoDlyPos];\t\t// v = delayed signal\n\t\t\tDolbyLoFilterDelay[nDolbyLoDlyPos] = secho;\t// secho = signal\n\t\t\tnDolbyLoDlyPos++;\n\t\t\tnDolbyLoDlyPos &= 0x1F;\n\t\t\tnDolbyLoFltSum -= DolbyLoFilterBuffer[n];\n\t\t\tint tmp = secho / 64;\n\t\t\tDolbyLoFilterBuffer[n] = tmp;\n\t\t\tnDolbyLoFltSum += tmp;\n\t\t\tv -= nDolbyLoFltSum;\n\t\t\tn++;\n\t\t\tn &= 0x3F;\n\t\t\t// Add echo\n\t\t\tpr[0] += v;\n\t\t\tpr[1] -= v;\n\t\t\tif (++nSurroundPos >= nSurroundSize) nSurroundPos = 0;\n\t\t\tpr += 2;\n\t\t}\n\t\tnDolbyLoFltPos = n;\n\t}\n\t// Bass Expansion\n\tif (gdwSoundSetup & SNDMIX_MEGABASS)\n\t{\n\t\tint *px = MixSoundBuffer;\n\t\tint xba = m_nXBassDepth+1, xbamask = (1 << xba) - 1;\n\t\tint n = nXBassBufferPos;\n\t\tfor (int x=count; x; x--)\n\t\t{\n\t\t\tnXBassSum -= XBassBuffer[n];\n\t\t\tint tmp0 = px[0] + px[1];\n\t\t\tint tmp = (tmp0 + ((tmp0 >> 31) & xbamask)) >> xba;\n\t\t\tXBassBuffer[n] = tmp;\n\t\t\tnXBassSum += tmp;\n\t\t\tint v = XBassDelay[nXBassDlyPos];\n\t\t\tXBassDelay[nXBassDlyPos] = px[0];\n\t\t\tpx[0] = v + nXBassSum;\n\t\t\tv = XBassDelay[nXBassDlyPos+1];\n\t\t\tXBassDelay[nXBassDlyPos+1] = px[1];\n\t\t\tpx[1] = v + nXBassSum;\n\t\t\tnXBassDlyPos = (nXBassDlyPos + 2) & nXBassMask;\n\t\t\tpx += 2;\n\t\t\tn++;\n\t\t\tn &= nXBassMask;\n\t\t}\n\t\tnXBassBufferPos = n;\n\t}\n\t// Noise Reduction\n\tif (gdwSoundSetup & SNDMIX_NOISEREDUCTION)\n\t{\n\t\tint n1 = nLeftNR, n2 = nRightNR;\n\t\tint *pnr = MixSoundBuffer;\n\t\tfor (int nr=count; nr; nr--)\n\t\t{\n\t\t\tint vnr = pnr[0] >> 1;\n\t\t\tpnr[0] = vnr + n1;\n\t\t\tn1 = vnr;\n\t\t\tvnr = pnr[1] >> 1;\n\t\t\tpnr[1] = vnr + n2;\n\t\t\tn2 = vnr;\n\t\t\tpnr += 2;\n\t\t}\n\t\tnLeftNR = n1;\n\t\tnRightNR = n2;\n\t}\n}\n\n\nvoid CSoundFile::ProcessMonoDSP(int count)\n//----------------------------------------\n{\n#ifndef MODPLUG_NO_REVERB\n\t// Reverb\n\tif (gdwSoundSetup & SNDMIX_REVERB)\n\t{\n\t\tint *pr = MixSoundBuffer, rvbcount = count, *pin = MixReverbBuffer;\n\t\tdo\n\t\t{\n\t\t\tint echo = ReverbBuffer[nReverbBufferPos] + ReverbBuffer2[nReverbBufferPos2]\n\t\t\t\t\t+ ReverbBuffer3[nReverbBufferPos3] + ReverbBuffer4[nReverbBufferPos4];\t// echo = reverb signal\n\t\t\t// Delay line and remove Low Frequencies\t\t\t// v = original signal\n\t\t\tint echodly = ReverbLoFilterDelay[nReverbLoDlyPos];\t// echodly = delayed signal\n\t\t\tReverbLoFilterDelay[nReverbLoDlyPos] = echo >> 1;\n\t\t\tnReverbLoDlyPos++;\n\t\t\tnReverbLoDlyPos &= 0x1F;\n\t\t\tint n = nReverbLoFltPos;\n\t\t\tnReverbLoFltSum -= ReverbLoFilterBuffer[n];\n\t\t\tint tmp = echo / 128;\n\t\t\tReverbLoFilterBuffer[n] = tmp;\n\t\t\tnReverbLoFltSum += tmp;\n\t\t\techodly -= nReverbLoFltSum;\n\t\t\tnReverbLoFltPos = (n + 1) & 0x3F;\n\t\t\t// Reverb\n\t\t\tint v = pin[0] >> (nFilterAttn-1);\n\t\t\t*pr++ += pin[0] + echodly;\n\t\t\tpin++;\n\t\t\tv += echodly >> 2;\n\t\t\tReverbBuffer3[nReverbBufferPos3] = v;\n\t\t\tReverbBuffer4[nReverbBufferPos4] = v;\n\t\t\tv += echodly >> 4;\n\t\t\tv >>= 1;\n\t\t\tgRvbLPSum -= gRvbLowPass[gRvbLPPos];\n\t\t\tgRvbLPSum += v;\n\t\t\tgRvbLowPass[gRvbLPPos] = v;\n\t\t\tgRvbLPPos++;\n\t\t\tgRvbLPPos &= 7;\n\t\t\tint vlp = gRvbLPSum >> 2;\n\t\t\tReverbBuffer[nReverbBufferPos] = vlp;\n\t\t\tReverbBuffer2[nReverbBufferPos2] = vlp;\n\t\t\tif (++nReverbBufferPos >= nReverbSize) nReverbBufferPos = 0;\n\t\t\tif (++nReverbBufferPos2 >= nReverbSize2) nReverbBufferPos2 = 0;\n\t\t\tif (++nReverbBufferPos3 >= nReverbSize3) nReverbBufferPos3 = 0;\n\t\t\tif (++nReverbBufferPos4 >= nReverbSize4) nReverbBufferPos4 = 0;\n\t\t} while (--rvbcount);\n\t}\n#endif\n\t// Bass Expansion\n\tif (gdwSoundSetup & SNDMIX_MEGABASS)\n\t{\n\t\tint *px = MixSoundBuffer;\n\t\tint xba = m_nXBassDepth, xbamask = (1 << xba)-1;\n\t\tint n = nXBassBufferPos;\n\t\tfor (int x=count; x; x--)\n\t\t{\n\t\t\tnXBassSum -= XBassBuffer[n];\n\t\t\tint tmp0 = *px;\n\t\t\tint tmp = (tmp0 + ((tmp0 >> 31) & xbamask)) >> xba;\n\t\t\tXBassBuffer[n] = tmp;\n\t\t\tnXBassSum += tmp;\n\t\t\tint v = XBassDelay[nXBassDlyPos];\n\t\t\tXBassDelay[nXBassDlyPos] = *px;\n\t\t\t*px++ = v + nXBassSum;\n\t\t\tnXBassDlyPos = (nXBassDlyPos + 2) & nXBassMask;\n\t\t\tn++;\n\t\t\tn &= nXBassMask;\n\t\t}\n\t\tnXBassBufferPos = n;\n\t}\n\t// Noise Reduction\n\tif (gdwSoundSetup & SNDMIX_NOISEREDUCTION)\n\t{\n\t\tint n = nLeftNR;\n\t\tint *pnr = MixSoundBuffer;\n\t\tfor (int nr=count; nr; pnr++, nr--)\n\t\t{\n\t\t\tint vnr = *pnr >> 1;\n\t\t\t*pnr = vnr + n;\n\t\t\tn = vnr;\n\t\t}\n\t\tnLeftNR = n;\n\t}\n}\n\n\n/////////////////////////////////////////////////////////////////\n// Clean DSP Effects interface\n\n// [Reverb level 0(quiet)-100(loud)], [delay in ms, usually 40-200ms]\nBOOL CSoundFile::SetReverbParameters(UINT nDepth, UINT nDelay)\n//------------------------------------------------------------\n{\n\tif (nDepth > 100) nDepth = 100;\n\tUINT gain = nDepth / 20;\n\tif (gain > 4) gain = 4;\n\tm_nReverbDepth = 4 - gain;\n\tif (nDelay < 40) nDelay = 40;\n\tif (nDelay > 250) nDelay = 250;\n\tm_nReverbDelay = nDelay;\n\treturn TRUE;\n}\n\n\n// [XBass level 0(quiet)-100(loud)], [cutoff in Hz 20-100]\nBOOL CSoundFile::SetXBassParameters(UINT nDepth, UINT nRange)\n//-----------------------------------------------------------\n{\n\tif (nDepth > 100) nDepth = 100;\n\tUINT gain = nDepth / 20;\n\tif (gain > 4) gain = 4;\n\tm_nXBassDepth = 8 - gain;\t// filter attenuation 1/256 .. 1/16\n\tUINT range = nRange / 5;\n\tif (range > 5) range -= 5; else range = 0;\n\tif (nRange > 16) nRange = 16;\n\tm_nXBassRange = 21 - range;\t// filter average on 0.5-1.6ms\n\treturn TRUE;\n}\n\n\n// [Surround level 0(quiet)-100(heavy)] [delay in ms, usually 5-50ms]\nBOOL CSoundFile::SetSurroundParameters(UINT nDepth, UINT nDelay)\n//--------------------------------------------------------------\n{\n\tUINT gain = (nDepth * 16) / 100;\n\tif (gain > 16) gain = 16;\n\tif (gain < 1) gain = 1;\n\tm_nProLogicDepth = gain;\n\tif (nDelay < 4) nDelay = 4;\n\tif (nDelay > 50) nDelay = 50;\n\tm_nProLogicDelay = nDelay;\n\treturn TRUE;\n}\n\nBOOL CSoundFile::SetWaveConfigEx(BOOL bSurround,BOOL bNoOverSampling,BOOL bReverb,BOOL hqido,BOOL bMegaBass,BOOL bNR,BOOL bEQ)\n//----------------------------------------------------------------------------------------------------------------------------\n{\n\tDWORD d = gdwSoundSetup & ~(SNDMIX_SURROUND | SNDMIX_NORESAMPLING | SNDMIX_REVERB | SNDMIX_HQRESAMPLER | SNDMIX_MEGABASS | SNDMIX_NOISEREDUCTION | SNDMIX_EQ);\n\tif (bSurround) d |= SNDMIX_SURROUND;\n\tif (bNoOverSampling) d |= SNDMIX_NORESAMPLING;\n\tif (bReverb) d |= SNDMIX_REVERB;\n\tif (hqido) d |= SNDMIX_HQRESAMPLER;\n\tif (bMegaBass) d |= SNDMIX_MEGABASS;\n\tif (bNR) d |= SNDMIX_NOISEREDUCTION;\n\tif (bEQ) d |= SNDMIX_EQ;\n\tgdwSoundSetup = d;\n\tInitPlayer(FALSE);\n\treturn TRUE;\n}\n"
  },
  {
    "path": "contrib/libmodplug/src/snd_flt.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n*/\n\n#include \"stdafx.h\"\n#include \"sndfile.h\"\n\n// AWE32: cutoff = reg[0-255] * 31.25 + 100 -> [100Hz-8060Hz]\n// EMU10K1 docs: cutoff = reg[0-127]*62+100\n#define FILTER_PRECISION\t8192\n\n#ifndef NO_FILTER\n\n#ifdef MSC_VER\n#define _ASM_MATH\n#endif\n\n#ifdef _ASM_MATH\n\n// pow(a,b) returns a^^b -> 2^^(b.log2(a))\nstatic float pow(float a, float b)\n{\n\tlong tmpint;\n\tfloat result;\n\t_asm {\n\tfld b\t\t\t\t// Load b\n\tfld a\t\t\t\t// Load a\n\tfyl2x\t\t\t\t// ST(0) = b.log2(a)\n\tfist tmpint\t\t\t// Store integer exponent\n\tfisub tmpint\t\t// ST(0) = -1 <= (b*log2(a)) <= 1\n\tf2xm1\t\t\t\t// ST(0) = 2^(x)-1\n\tfild tmpint\t\t\t// load integer exponent\n\tfld1\t\t\t\t// Load 1\n\tfscale\t\t\t\t// ST(0) = 2^ST(1)\n\tfstp ST(1)\t\t\t// Remove the integer from the stack\n\tfmul ST(1), ST(0)\t// multiply with fractional part\n\tfaddp ST(1), ST(0)\t// add integer_part\n\tfstp result\t\t\t// Store the result\n\t}\n\treturn result;\n}\n\n\n#else\n\n#include <math.h>\n\n#endif // _ASM_MATH\n\n\nDWORD CSoundFile::CutOffToFrequency(UINT nCutOff, int flt_modifier) const\n//-----------------------------------------------------------------------\n{\n\tfloat Fc;\n\n\tif (m_dwSongFlags & SONG_EXFILTERRANGE)\n\t\tFc = 110.0f * pow(2.0f, 0.25f + ((float)(nCutOff*(flt_modifier+256)))/(21.0f*512.0f));\n\telse\n\t\tFc = 110.0f * pow(2.0f, 0.25f + ((float)(nCutOff*(flt_modifier+256)))/(24.0f*512.0f));\n\tLONG freq = (LONG)Fc;\n\tif (freq < 120) return 120;\n\tif (freq > 10000) return 10000;\n\tif (freq*2 > (LONG)gdwMixingFreq) freq = gdwMixingFreq>>1;\n\treturn (DWORD)freq;\n}\n\n\n// Simple 2-poles resonant filter\nvoid CSoundFile::SetupChannelFilter(MODCHANNEL *pChn, BOOL bReset, int flt_modifier) const\n//----------------------------------------------------------------------------------------\n{\n\tfloat fc = (float)CutOffToFrequency(pChn->nCutOff, flt_modifier);\n\tfloat fs = (float)gdwMixingFreq;\n\tfloat fg, fb0, fb1;\n\n\tfc *= (float)(2.0*3.14159265358/fs);\n\tfloat dmpfac = pow(10.0f, -((24.0f / 128.0f)*(float)pChn->nResonance) / 20.0f);\n\tfloat d = (1.0f-2.0f*dmpfac)* fc;\n\tif (d>2.0) d = 2.0;\n\td = (2.0f*dmpfac - d)/fc;\n\tfloat e = pow(1.0f/fc,2.0f);\n\n\tfg=1/(1+d+e);\n\tfb0=(d+e+e)/(1+d+e);\n\tfb1=-e/(1+d+e);\n\n\tpChn->nFilter_A0 = (int)(fg * FILTER_PRECISION);\n\tpChn->nFilter_B0 = (int)(fb0 * FILTER_PRECISION);\n\tpChn->nFilter_B1 = (int)(fb1 * FILTER_PRECISION);\n\n\tif (bReset)\n\t{\n\t\tpChn->nFilter_Y1 = pChn->nFilter_Y2 = 0;\n\t\tpChn->nFilter_Y3 = pChn->nFilter_Y4 = 0;\n\t}\n\tpChn->dwFlags |= CHN_FILTER;\n}\n\n#endif // NO_FILTER\n"
  },
  {
    "path": "contrib/libmodplug/src/snd_fx.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n*/\n\n#include \"stdafx.h\"\n#include <stdlib.h>\n#include \"sndfile.h\"\n#include \"tables.h\"\n\n#ifdef MSC_VER\n#pragma warning(disable:4244)\n#endif\n\n////////////////////////////////////////////////////////////\n// Length\n\nDWORD CSoundFile::GetLength(BOOL bAdjust, BOOL bTotal)\n//----------------------------------------------------\n{\n\tUINT dwElapsedTime=0, nRow=0, nCurrentPattern=0, nNextPattern=0, nPattern=0;\n\tUINT nMusicSpeed=m_nDefaultSpeed, nMusicTempo=m_nDefaultTempo, nNextRow=0;\n\tUINT nMaxRow = 0, nMaxPattern = 0, nNextStartRow = 0;\n\tLONG nGlbVol = m_nDefaultGlobalVolume, nOldGlbVolSlide = 0;\n\tBYTE samples[MAX_CHANNELS];\n\tBYTE instr[MAX_CHANNELS];\n\tBYTE notes[MAX_CHANNELS];\n\tBYTE vols[MAX_CHANNELS];\n\tBYTE oldparam[MAX_CHANNELS];\n\tBYTE chnvols[MAX_CHANNELS];\n\tDWORD patloop[MAX_CHANNELS];\n\n\tmemset(instr, 0, sizeof(instr));\n\tmemset(notes, 0, sizeof(notes));\n\tmemset(vols, 0xFF, sizeof(vols));\n\tmemset(patloop, 0, sizeof(patloop));\n\tmemset(oldparam, 0, sizeof(oldparam));\n\tmemset(chnvols, 64, sizeof(chnvols));\n\tmemset(samples, 0, sizeof(samples));\n\tfor (UINT icv=0; icv<m_nChannels; icv++)\n\t\tchnvols[icv] = ChnSettings[icv].nVolume;\n\tnMaxRow = m_nNextRow;\n\tnMaxPattern = m_nNextPattern;\n\tfor (;;)\n\t{\n\t\tUINT nSpeedCount = 0;\n\t\tnRow = nNextRow;\n\t\tnCurrentPattern = nNextPattern;\n\t\t// Check if pattern is valid\n\t\tnPattern = (nCurrentPattern < MAX_ORDERS) ? Order[nCurrentPattern] : 0xFF;\n\t\twhile (nPattern >= MAX_PATTERNS)\n\t\t{\n\t\t\t// End of song ?\n\t\t\tif ((nPattern == 0xFF) || (nCurrentPattern >= MAX_ORDERS))\n\t\t\t{\n\t\t\t\tgoto EndMod;\n\t\t\t} else\n\t\t\t{\n\t\t\t\tnCurrentPattern++;\n\t\t\t\tnPattern = (nCurrentPattern < MAX_ORDERS) ? Order[nCurrentPattern] : 0xFF;\n\t\t\t}\n\t\t\tnNextPattern = nCurrentPattern;\n\t\t}\n\t\t// Weird stuff?\n\t\tif ((nPattern >= MAX_PATTERNS) || (!Patterns[nPattern]) ||\n\t\t\tPatternSize[nPattern] == 0) break;\n\t\t// Should never happen\n\t\tif (nRow >= PatternSize[nPattern]) nRow = 0;\n\t\t// Update next position\n\t\tnNextRow = nRow + 1;\n\t\tif (nNextRow >= PatternSize[nPattern])\n\t\t{\n\t\t\tnNextPattern = nCurrentPattern + 1;\n\t\t\tnNextRow = nNextStartRow;\n\t\t\tnNextStartRow = 0;\n\t\t}\n\t\tif (!nRow)\n\t\t{\n\t\t\tfor (UINT ipck=0; ipck<m_nChannels; ipck++) patloop[ipck] = dwElapsedTime;\n\t\t}\n\t\tif (!bTotal)\n\t\t{\n\t\t\tif ((nCurrentPattern > nMaxPattern) || ((nCurrentPattern == nMaxPattern) && (nRow >= nMaxRow)))\n\t\t\t{\n\t\t\t\tif (bAdjust)\n\t\t\t\t{\n\t\t\t\t\tm_nMusicSpeed = nMusicSpeed;\n\t\t\t\t\tm_nMusicTempo = nMusicTempo;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tMODCHANNEL *pChn = Chn;\n\t\tMODCOMMAND *p = Patterns[nPattern] + nRow * m_nChannels;\n\t\tfor (UINT nChn=0; nChn<m_nChannels; p++,pChn++, nChn++) if (*((DWORD *)p))\n\t\t{\n\t\t\tUINT command = p->command;\n\t\t\tUINT param = p->param;\n\t\t\tUINT note = p->note;\n\t\t\tif (p->instr) { instr[nChn] = p->instr; notes[nChn] = 0; vols[nChn] = 0xFF; }\n\t\t\tif ((note) && (note <= NOTE_MAX)) notes[nChn] = note;\n\t\t\tif (p->volcmd == VOLCMD_VOLUME)\t{ vols[nChn] = p->vol; }\n\t\t\tif (command) switch (command)\n\t\t\t{\n\t\t\t// Position Jump\n\t\t\tcase CMD_POSITIONJUMP:\n\t\t\t\tif (param <= nCurrentPattern) goto EndMod;\n\t\t\t\tnNextPattern = param;\n\t\t\t\tnNextRow = 0;\n\t\t\t\tnNextStartRow = 0;\n\t\t\t\tif (bAdjust)\n\t\t\t\t{\n\t\t\t\t\tpChn->nPatternLoopCount = 0;\n\t\t\t\t\tpChn->nPatternLoop = 0;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t// Pattern Break\n\t\t\tcase CMD_PATTERNBREAK:\n\t\t\t\tnNextRow = param;\n\t\t\t\tnNextPattern = nCurrentPattern + 1;\n\t\t\t\tnNextStartRow = 0;\n\t\t\t\tif (bAdjust)\n\t\t\t\t{\n\t\t\t\t\tpChn->nPatternLoopCount = 0;\n\t\t\t\t\tpChn->nPatternLoop = 0;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t// Set Speed\n\t\t\tcase CMD_SPEED:\n\t\t\t\tif (!param) break;\n\t\t\t\tif ((param <= 0x20) || (m_nType != MOD_TYPE_MOD))\n\t\t\t\t{\n\t\t\t\t\tif (param < 128) nMusicSpeed = param;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t// Set Tempo\n\t\t\tcase CMD_TEMPO:\n\t\t\t\tif ((bAdjust) && (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)))\n\t\t\t\t{\n\t\t\t\t\tif (param) pChn->nOldTempo = param; else param = pChn->nOldTempo;\n\t\t\t\t}\n\t\t\t\tif (param >= 0x20) nMusicTempo = param; else\n\t\t\t\t// Tempo Slide\n\t\t\t\tif ((param & 0xF0) == 0x10)\n\t\t\t\t{\n\t\t\t\t\tnMusicTempo += param & 0x0F;\n\t\t\t\t\tif (nMusicTempo > 255) nMusicTempo = 255;\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tnMusicTempo -= param & 0x0F;\n\t\t\t\t\tif (nMusicTempo < 32) nMusicTempo = 32;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t// Pattern Delay\n\t\t\tcase CMD_S3MCMDEX:\n\t\t\t\tif ((param & 0xF0) == 0x60) { nSpeedCount = param & 0x0F; break; } else\n\t\t\t\tif ((param & 0xF0) == 0xB0) { param &= 0x0F; param |= 0x60; }\n\t\t\tcase CMD_MODCMDEX:\n\t\t\t\tif ((param & 0xF0) == 0xE0) nSpeedCount = (param & 0x0F) * nMusicSpeed; else\n\t\t\t\tif ((param & 0xF0) == 0x60)\n\t\t\t\t{\n\t\t\t\t\tif (param & 0x0F) dwElapsedTime += (dwElapsedTime - patloop[nChn]) * (param & 0x0F);\n\t\t\t\t\telse {\n\t\t\t\t\t\tpatloop[nChn] = dwElapsedTime;\n\t\t\t\t\t\tif (m_nType & MOD_TYPE_XM) nNextStartRow = nRow;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!bAdjust) continue;\n\t\t\tswitch(command)\n\t\t\t{\n\t\t\t// Portamento Up/Down\n\t\t\tcase CMD_PORTAMENTOUP:\n\t\t\tcase CMD_PORTAMENTODOWN:\n\t\t\t\tif (param) pChn->nOldPortaUpDown = param;\n\t\t\t\tbreak;\n\t\t\t// Tone-Portamento\n\t\t\tcase CMD_TONEPORTAMENTO:\n\t\t\t\tif (param) pChn->nPortamentoSlide = param << 2;\n\t\t\t\tbreak;\n\t\t\t// Offset\n\t\t\tcase CMD_OFFSET:\n\t\t\t\tif (param) pChn->nOldOffset = param;\n\t\t\t\tbreak;\n\t\t\t// Volume Slide\n\t\t\tcase CMD_VOLUMESLIDE:\n\t\t\tcase CMD_TONEPORTAVOL:\n\t\t\tcase CMD_VIBRATOVOL:\n\t\t\t\tif (param) pChn->nOldVolumeSlide = param;\n\t\t\t\tbreak;\n\t\t\t// Set Volume\n\t\t\tcase CMD_VOLUME:\n\t\t\t\tvols[nChn] = param;\n\t\t\t\tbreak;\n\t\t\t// Global Volume\n\t\t\tcase CMD_GLOBALVOLUME:\n\t\t\t\tif (!(m_nType & (MOD_TYPE_IT))) param <<= 1;\n\t\t\t\tif (param > 128) param = 128;\n\t\t\t\tnGlbVol = param << 1;\n\t\t\t\tbreak;\n\t\t\t// Global Volume Slide\n\t\t\tcase CMD_GLOBALVOLSLIDE:\n\t\t\t\tif (param) nOldGlbVolSlide = param; else param = nOldGlbVolSlide;\n\t\t\t\tif (((param & 0x0F) == 0x0F) && (param & 0xF0))\n\t\t\t\t{\n\t\t\t\t\tparam >>= 4;\n\t\t\t\t\tif (m_nType != MOD_TYPE_IT) param <<= 1;\n\t\t\t\t\tnGlbVol += param << 1;\n\t\t\t\t} else\n\t\t\t\tif (((param & 0xF0) == 0xF0) && (param & 0x0F))\n\t\t\t\t{\n\t\t\t\t\tparam = (param & 0x0F) << 1;\n\t\t\t\t\tif (m_nType != MOD_TYPE_IT) param <<= 1;\n\t\t\t\t\tnGlbVol -= param;\n\t\t\t\t} else\n\t\t\t\tif (param & 0xF0)\n\t\t\t\t{\n\t\t\t\t\tparam >>= 4;\n\t\t\t\t\tparam <<= 1;\n\t\t\t\t\tif (m_nType != MOD_TYPE_IT) param <<= 1;\n\t\t\t\t\tnGlbVol += param * nMusicSpeed;\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tparam = (param & 0x0F) << 1;\n\t\t\t\t\tif (m_nType != MOD_TYPE_IT) param <<= 1;\n\t\t\t\t\tnGlbVol -= param * nMusicSpeed;\n\t\t\t\t}\n\t\t\t\tif (nGlbVol < 0) nGlbVol = 0;\n\t\t\t\tif (nGlbVol > 256) nGlbVol = 256;\n\t\t\t\tbreak;\n\t\t\tcase CMD_CHANNELVOLUME:\n\t\t\t\tif (param <= 64) chnvols[nChn] = param;\n\t\t\t\tbreak;\n\t\t\tcase CMD_CHANNELVOLSLIDE:\n\t\t\t\tif (param) oldparam[nChn] = param; else param = oldparam[nChn];\n\t\t\t\tpChn->nOldChnVolSlide = param;\n\t\t\t\tif (((param & 0x0F) == 0x0F) && (param & 0xF0))\n\t\t\t\t{\n\t\t\t\t\tparam = (param >> 4) + chnvols[nChn];\n\t\t\t\t} else\n\t\t\t\tif (((param & 0xF0) == 0xF0) && (param & 0x0F))\n\t\t\t\t{\n\t\t\t\t\tif (chnvols[nChn] > (int)(param & 0x0F)) param = chnvols[nChn] - (param & 0x0F);\n\t\t\t\t\telse param = 0;\n\t\t\t\t} else\n\t\t\t\tif (param & 0x0F)\n\t\t\t\t{\n\t\t\t\t\tparam = (param & 0x0F) * nMusicSpeed;\n\t\t\t\t\tparam = (chnvols[nChn] > param) ? chnvols[nChn] - param : 0;\n\t\t\t\t} else param = ((param & 0xF0) >> 4) * nMusicSpeed + chnvols[nChn];\n\t\t\t\tif (param > 64) param = 64;\n\t\t\t\tchnvols[nChn] = param;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tnSpeedCount += nMusicSpeed;\n\t\tdwElapsedTime += (2500 * nSpeedCount) / nMusicTempo;\n\t}\nEndMod:\n\tif ((bAdjust) && (!bTotal))\n\t{\n\t\tm_nGlobalVolume = nGlbVol;\n\t\tm_nOldGlbVolSlide = nOldGlbVolSlide;\n\t\tfor (UINT n=0; n<m_nChannels; n++)\n\t\t{\n\t\t\tChn[n].nGlobalVol = chnvols[n];\n\t\t\tif (notes[n]) Chn[n].nNewNote = notes[n];\n\t\t\tif (instr[n]) Chn[n].nNewIns = instr[n];\n\t\t\tif (vols[n] != 0xFF)\n\t\t\t{\n\t\t\t\tif (vols[n] > 64) vols[n] = 64;\n\t\t\t\tChn[n].nVolume = vols[n] << 2;\n\t\t\t}\n\t\t}\n\t}\n\treturn (dwElapsedTime+500) / 1000;\n}\n\n\n//////////////////////////////////////////////////////////////////////////////////////////////////\n// Effects\n\nvoid CSoundFile::InstrumentChange(MODCHANNEL *pChn, UINT instr, BOOL bPorta, BOOL bUpdVol, BOOL bResetEnv)\n//--------------------------------------------------------------------------------------------------------\n{\n\tBOOL bInstrumentChanged = FALSE;\n\n\tif (instr >= MAX_INSTRUMENTS) return;\n\tINSTRUMENTHEADER *penv = Headers[instr];\n\tMODINSTRUMENT *psmp = &Ins[instr];\n\tUINT note = pChn->nNewNote;\n\tif ((penv) && (note) && (note <= 128))\n\t{\n\t\tif (penv->NoteMap[note-1] >= 0xFE) return;\n\t\tUINT n = penv->Keyboard[note-1];\n\t\tpsmp = ((n) && (n < MAX_SAMPLES)) ? &Ins[n] : NULL;\n\t} else\n\tif (m_nInstruments)\n\t{\n\t\tif (note >= 0xFE) return;\n\t\tpsmp = NULL;\n\t}\n\t// Update Volume\n\tif (bUpdVol) pChn->nVolume = (psmp) ? psmp->nVolume : 0;\n\t// bInstrumentChanged is used for IT carry-on env option\n\tif (penv != pChn->pHeader)\n\t{\n\t\tbInstrumentChanged = TRUE;\n\t\tpChn->pHeader = penv;\n\t} else\n\t{\n\t\t// Special XM hack\n\t\tif ((bPorta) && (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) && (penv)\n\t\t&& (pChn->pInstrument) && (psmp != pChn->pInstrument))\n\t\t{\n\t\t\t// FT2 doesn't change the sample in this case,\n\t\t\t// but still uses the sample info from the old one (bug?)\n\t\t\treturn;\n\t\t}\n\t}\n\t// Instrument adjust\n\tpChn->nNewIns = 0;\n\tif (psmp)\n\t{\n\t\tif (penv)\n\t\t{\n\t\t\tpChn->nInsVol = (psmp->nGlobalVol * penv->nGlobalVol) >> 6;\n\t\t\tif (penv->dwFlags & ENV_SETPANNING) pChn->nPan = penv->nPan;\n\t\t\tpChn->nNNA = penv->nNNA;\n\t\t} else\n\t\t{\n\t\t\tpChn->nInsVol = psmp->nGlobalVol;\n\t\t}\n\t\tif (psmp->uFlags & CHN_PANNING) pChn->nPan = psmp->nPan;\n\t}\n\t// Reset envelopes\n\tif (bResetEnv)\n\t{\n\t\tif ((!bPorta) || (!(m_nType & MOD_TYPE_IT)) || (m_dwSongFlags & SONG_ITCOMPATMODE)\n\t\t || (!pChn->nLength) || ((pChn->dwFlags & CHN_NOTEFADE) && (!pChn->nFadeOutVol)))\n\t\t{\n\t\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t\t\tif ((m_nType & MOD_TYPE_IT) && (!bInstrumentChanged) && (penv) && (!(pChn->dwFlags & (CHN_KEYOFF|CHN_NOTEFADE))))\n\t\t\t{\n\t\t\t\tif (!(penv->dwFlags & ENV_VOLCARRY)) pChn->nVolEnvPosition = 0;\n\t\t\t\tif (!(penv->dwFlags & ENV_PANCARRY)) pChn->nPanEnvPosition = 0;\n\t\t\t\tif (!(penv->dwFlags & ENV_PITCHCARRY)) pChn->nPitchEnvPosition = 0;\n\t\t\t} else\n\t\t\t{\n\t\t\t\tpChn->nVolEnvPosition = 0;\n\t\t\t\tpChn->nPanEnvPosition = 0;\n\t\t\t\tpChn->nPitchEnvPosition = 0;\n\t\t\t}\n\t\t\tpChn->nAutoVibDepth = 0;\n\t\t\tpChn->nAutoVibPos = 0;\n\t\t} else\n\t\tif ((penv) && (!(penv->dwFlags & ENV_VOLUME)))\n\t\t{\n\t\t\tpChn->nVolEnvPosition = 0;\n\t\t\tpChn->nAutoVibDepth = 0;\n\t\t\tpChn->nAutoVibPos = 0;\n\t\t}\n\t}\n\t// Invalid sample ?\n\tif (!psmp)\n\t{\n\t\tpChn->pInstrument = NULL;\n\t\tpChn->nInsVol = 0;\n\t\treturn;\n\t}\n\t// Tone-Portamento doesn't reset the pingpong direction flag\n\tif ((bPorta) && (psmp == pChn->pInstrument))\n\t{\n\t\tif (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)) return;\n\t\tpChn->dwFlags &= ~(CHN_KEYOFF|CHN_NOTEFADE);\n\t\tpChn->dwFlags = (pChn->dwFlags & (0xFFFFFF00 | CHN_PINGPONGFLAG)) | (psmp->uFlags);\n\t} else\n\t{\n\t\tpChn->dwFlags &= ~(CHN_KEYOFF|CHN_NOTEFADE|CHN_VOLENV|CHN_PANENV|CHN_PITCHENV);\n\t\tpChn->dwFlags = (pChn->dwFlags & 0xFFFFFF00) | (psmp->uFlags);\n\t\tif (penv)\n\t\t{\n\t\t\tif (penv->dwFlags & ENV_VOLUME) pChn->dwFlags |= CHN_VOLENV;\n\t\t\tif (penv->dwFlags & ENV_PANNING) pChn->dwFlags |= CHN_PANENV;\n\t\t\tif (penv->dwFlags & ENV_PITCH) pChn->dwFlags |= CHN_PITCHENV;\n\t\t\tif ((penv->dwFlags & ENV_PITCH) && (penv->dwFlags & ENV_FILTER))\n\t\t\t{\n\t\t\t\tif (!pChn->nCutOff) pChn->nCutOff = 0x7F;\n\t\t\t}\n\t\t\tif (penv->nIFC & 0x80) pChn->nCutOff = penv->nIFC & 0x7F;\n\t\t\tif (penv->nIFR & 0x80) pChn->nResonance = penv->nIFR & 0x7F;\n\t\t}\n\t\tpChn->nVolSwing = pChn->nPanSwing = 0;\n\t}\n\tpChn->pInstrument = psmp;\n\tpChn->nLength = psmp->nLength;\n\tpChn->nLoopStart = psmp->nLoopStart;\n\tpChn->nLoopEnd = psmp->nLoopEnd;\n\tpChn->nC4Speed = psmp->nC4Speed;\n\tpChn->pSample = psmp->pSample;\n\tpChn->nTranspose = psmp->RelativeTone;\n\tpChn->nFineTune = psmp->nFineTune;\n\tif (pChn->dwFlags & CHN_SUSTAINLOOP)\n\t{\n\t\tpChn->nLoopStart = psmp->nSustainStart;\n\t\tpChn->nLoopEnd = psmp->nSustainEnd;\n\t\tpChn->dwFlags |= CHN_LOOP;\n\t\tif (pChn->dwFlags & CHN_PINGPONGSUSTAIN) pChn->dwFlags |= CHN_PINGPONGLOOP;\n\t}\n\tif ((pChn->dwFlags & CHN_LOOP) && (pChn->nLoopEnd < pChn->nLength)) pChn->nLength = pChn->nLoopEnd;\n}\n\n\nvoid CSoundFile::NoteChange(UINT nChn, int note, BOOL bPorta, BOOL bResetEnv)\n//---------------------------------------------------------------------------\n{\n\tif (note < 1) return;\n\tMODCHANNEL * const pChn = &Chn[nChn];\n\tMODINSTRUMENT *pins = pChn->pInstrument;\n\tINSTRUMENTHEADER *penv = pChn->pHeader;\n\tif ((penv) && (note <= 0x80))\n\t{\n\t\tUINT n = penv->Keyboard[note - 1];\n\t\tif ((n) && (n < MAX_SAMPLES)) pins = &Ins[n];\n\t\tnote = penv->NoteMap[note-1];\n\t}\n\t// Key Off\n\tif (note >= 0x80)\t// 0xFE or invalid note => key off\n\t{\n\t\t// Key Off\n\t\tKeyOff(nChn);\n\t\t// Note Cut\n\t\tif (note == 0xFE)\n\t\t{\n\t\t\tpChn->dwFlags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP);\n\t\t\tif ((!(m_nType & MOD_TYPE_IT)) || (m_nInstruments)) pChn->nVolume = 0;\n\t\t\tpChn->nFadeOutVol = 0;\n\t\t}\n\t\treturn;\n\t}\n\tif (!pins) return;\n\tif ((!bPorta) && (m_nType & (MOD_TYPE_XM|MOD_TYPE_MED|MOD_TYPE_MT2)))\n\t{\n\t\tpChn->nTranspose = pins->RelativeTone;\n\t\tpChn->nFineTune = pins->nFineTune;\n\t}\n\tif (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2|MOD_TYPE_MED)) note += pChn->nTranspose;\n\tif (note < 1) note = 1;\n\tif (note > 132) note = 132;\n\tpChn->nNote = note;\n\tif ((!bPorta) || (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) pChn->nNewIns = 0;\n\tUINT period = GetPeriodFromNote(note, pChn->nFineTune, pChn->nC4Speed);\n\tif (period)\n\t{\n\t\tif ((!bPorta) || (!pChn->nPeriod)) pChn->nPeriod = period;\n\t\tpChn->nPortamentoDest = period;\n\t\tif ((!bPorta) || ((!pChn->nLength) && (!(m_nType & MOD_TYPE_S3M))))\n\t\t{\n\t\t\tpChn->pInstrument = pins;\n\t\t\tpChn->pSample = pins->pSample;\n\t\t\tpChn->nLength = pins->nLength;\n\t\t\tpChn->nLoopEnd = pins->nLength;\n\t\t\tpChn->nLoopStart = 0;\n\t\t\tpChn->dwFlags = (pChn->dwFlags & 0xFFFFFF00) | (pins->uFlags);\n\t\t\tif (pChn->dwFlags & CHN_SUSTAINLOOP)\n\t\t\t{\n\t\t\t\tpChn->nLoopStart = pins->nSustainStart;\n\t\t\t\tpChn->nLoopEnd = pins->nSustainEnd;\n\t\t\t\tpChn->dwFlags &= ~CHN_PINGPONGLOOP;\n\t\t\t\tpChn->dwFlags |= CHN_LOOP;\n\t\t\t\tif (pChn->dwFlags & CHN_PINGPONGSUSTAIN) pChn->dwFlags |= CHN_PINGPONGLOOP;\n\t\t\t\tif (pChn->nLength > pChn->nLoopEnd) pChn->nLength = pChn->nLoopEnd;\n\t\t\t} else\n\t\t\tif (pChn->dwFlags & CHN_LOOP)\n\t\t\t{\n\t\t\t\tpChn->nLoopStart = pins->nLoopStart;\n\t\t\t\tpChn->nLoopEnd = pins->nLoopEnd;\n\t\t\t\tif (pChn->nLength > pChn->nLoopEnd) pChn->nLength = pChn->nLoopEnd;\n\t\t\t}\n\t\t\tpChn->nPos = 0;\n\t\t\tpChn->nPosLo = 0;\n\t\t\tif (pChn->nVibratoType < 4) pChn->nVibratoPos = ((m_nType & MOD_TYPE_IT) && (!(m_dwSongFlags & SONG_ITOLDEFFECTS))) ? 0x10 : 0;\n\t\t\tif (pChn->nTremoloType < 4) pChn->nTremoloPos = 0;\n\t\t}\n\t\tif (pChn->nPos >= pChn->nLength) pChn->nPos = pChn->nLoopStart;\n\t} else bPorta = FALSE;\n\tif ((!bPorta) || (!(m_nType & MOD_TYPE_IT))\n\t || ((pChn->dwFlags & CHN_NOTEFADE) && (!pChn->nFadeOutVol))\n\t || ((m_dwSongFlags & SONG_ITCOMPATMODE) && (pChn->nRowInstr)))\n\t{\n\t\tif ((m_nType & MOD_TYPE_IT) && (pChn->dwFlags & CHN_NOTEFADE) && (!pChn->nFadeOutVol))\n\t\t{\n\t\t\tpChn->nVolEnvPosition = 0;\n\t\t\tpChn->nPanEnvPosition = 0;\n\t\t\tpChn->nPitchEnvPosition = 0;\n\t\t\tpChn->nAutoVibDepth = 0;\n\t\t\tpChn->nAutoVibPos = 0;\n\t\t\tpChn->dwFlags &= ~CHN_NOTEFADE;\n\t\t\tpChn->nFadeOutVol = 65536;\n\t\t}\n\t\tif ((!bPorta) || (!(m_dwSongFlags & SONG_ITCOMPATMODE)) || (pChn->nRowInstr))\n\t\t{\n\t\t\tif ((!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) || (pChn->nRowInstr))\n\t\t\t{\n\t\t\t\tpChn->dwFlags &= ~CHN_NOTEFADE;\n\t\t\t\tpChn->nFadeOutVol = 65536;\n\t\t\t}\n\t\t}\n\t}\n\tpChn->dwFlags &= ~(CHN_EXTRALOUD|CHN_KEYOFF);\n\t// Enable Ramping\n\tif (!bPorta)\n\t{\n\t\tpChn->nVUMeter = 0x100;\n\t\tpChn->nLeftVU = pChn->nRightVU = 0xFF;\n\t\tpChn->dwFlags &= ~CHN_FILTER;\n\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t\tpChn->nRetrigCount = 0;\n\t\tpChn->nTremorCount = 0;\n\t\tif (bResetEnv)\n\t\t{\n\t\t\tpChn->nVolSwing = pChn->nPanSwing = 0;\n\t\t\tif (penv)\n\t\t\t{\n\t\t\t\tif (!(penv->dwFlags & ENV_VOLCARRY)) pChn->nVolEnvPosition = 0;\n\t\t\t\tif (!(penv->dwFlags & ENV_PANCARRY)) pChn->nPanEnvPosition = 0;\n\t\t\t\tif (!(penv->dwFlags & ENV_PITCHCARRY)) pChn->nPitchEnvPosition = 0;\n\t\t\t\tif (m_nType & MOD_TYPE_IT)\n\t\t\t\t{\n\t\t\t\t\t// Volume Swing\n\t\t\t\t\tif (penv->nVolSwing)\n\t\t\t\t\t{\n\t\t\t\t\t\tint d = ((LONG)penv->nVolSwing*(LONG)((rand() & 0xFF) - 0x7F)) / 128;\n\t\t\t\t\t\tpChn->nVolSwing = (signed short)((d * pChn->nVolume + 1)/128);\n\t\t\t\t\t}\n\t\t\t\t\t// Pan Swing\n\t\t\t\t\tif (penv->nPanSwing)\n\t\t\t\t\t{\n\t\t\t\t\t\tint d = ((LONG)penv->nPanSwing*(LONG)((rand() & 0xFF) - 0x7F)) / 128;\n\t\t\t\t\t\tpChn->nPanSwing = (signed short)d;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tpChn->nAutoVibDepth = 0;\n\t\t\tpChn->nAutoVibPos = 0;\n\t\t}\n\t\tpChn->nLeftVol = pChn->nRightVol = 0;\n\t\tBOOL bFlt = (m_dwSongFlags & SONG_MPTFILTERMODE) ? FALSE : TRUE;\n\t\t// Setup Initial Filter for this note\n\t\tif (penv)\n\t\t{\n\t\t\tif (penv->nIFR & 0x80) { pChn->nResonance = penv->nIFR & 0x7F; bFlt = TRUE; }\n\t\t\tif (penv->nIFC & 0x80) { pChn->nCutOff = penv->nIFC & 0x7F; bFlt = TRUE; }\n\t\t} else\n\t\t{\n\t\t\tpChn->nVolSwing = pChn->nPanSwing = 0;\n\t\t}\n#ifndef NO_FILTER\n\t\tif ((pChn->nCutOff < 0x7F) && (bFlt)) SetupChannelFilter(pChn, TRUE);\n#endif // NO_FILTER\n\t}\n}\n\n\nUINT CSoundFile::GetNNAChannel(UINT nChn) const\n//---------------------------------------------\n{\n\tconst MODCHANNEL *pChn = &Chn[nChn];\n\t// Check for empty channel\n\tconst MODCHANNEL *pi = &Chn[m_nChannels];\n\tfor (UINT i=m_nChannels; i<MAX_CHANNELS; i++, pi++) if (!pi->nLength) return i;\n\tif (!pChn->nFadeOutVol) return 0;\n\t// All channels are used: check for lowest volume\n\tUINT result = 0;\n\tDWORD vol = 64*65536;\t// 25%\n\tDWORD envpos = 0xFFFFFF;\n\tconst MODCHANNEL *pj = &Chn[m_nChannels];\n\tfor (UINT j=m_nChannels; j<MAX_CHANNELS; j++, pj++)\n\t{\n\t\tif (!pj->nFadeOutVol) return j;\n\t\tDWORD v = pj->nVolume;\n\t\tif (pj->dwFlags & CHN_NOTEFADE)\n\t\t\tv = v * pj->nFadeOutVol;\n\t\telse\n\t\t\tv <<= 16;\n\t\tif (pj->dwFlags & CHN_LOOP) v >>= 1;\n\t\tif ((v < vol) || ((v == vol) && (pj->nVolEnvPosition > envpos)))\n\t\t{\n\t\t\tenvpos = pj->nVolEnvPosition;\n\t\t\tvol = v;\n\t\t\tresult = j;\n\t\t}\n\t}\n\treturn result;\n}\n\n\nvoid CSoundFile::CheckNNA(UINT nChn, UINT instr, int note, BOOL bForceCut)\n//------------------------------------------------------------------------\n{\n\tMODCHANNEL *pChn = &Chn[nChn];\n\tINSTRUMENTHEADER *penv = pChn->pHeader, *pHeader = 0;\n\tsigned char *pSample;\n\tif (note > 0x80) note = 0;\n\tif (note < 1) return;\n\t// Always NNA cut - using\n\tif ((!(m_nType & (MOD_TYPE_IT|MOD_TYPE_MT2))) || (!m_nInstruments) || (bForceCut))\n\t{\n\t\tif ((m_dwSongFlags & SONG_CPUVERYHIGH)\n\t\t || (!pChn->nLength) || (pChn->dwFlags & CHN_MUTE)\n\t\t || ((!pChn->nLeftVol) && (!pChn->nRightVol))) return;\n\t\tUINT n = GetNNAChannel(nChn);\n\t\tif (!n) return;\n\t\tMODCHANNEL *p = &Chn[n];\n\t\t// Copy Channel\n\t\t*p = *pChn;\n\t\tp->dwFlags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PANBRELLO|CHN_MUTE|CHN_PORTAMENTO);\n\t\tp->nMasterChn = nChn+1;\n\t\tp->nCommand = 0;\n\t\t// Cut the note\n\t\tp->nFadeOutVol = 0;\n\t\tp->dwFlags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP);\n\t\t// Stop this channel\n\t\tpChn->nLength = pChn->nPos = pChn->nPosLo = 0;\n\t\tpChn->nROfs = pChn->nLOfs = 0;\n\t\tpChn->nLeftVol = pChn->nRightVol = 0;\n\t\treturn;\n\t}\n\tif (instr >= MAX_INSTRUMENTS) instr = 0;\n\tpSample = pChn->pSample;\n\tpHeader = pChn->pHeader;\n\tif ((instr) && (note))\n\t{\n\t\tpHeader = Headers[instr];\n\t\tif (pHeader)\n\t\t{\n\t\t\tUINT n = 0;\n\t\t\tif (note <= 0x80)\n\t\t\t{\n\t\t\t\tn = pHeader->Keyboard[note-1];\n\t\t\t\tnote = pHeader->NoteMap[note-1];\n\t\t\t\tif ((n) && (n < MAX_SAMPLES)) pSample = Ins[n].pSample;\n\t\t\t}\n\t\t} else pSample = NULL;\n\t}\n\tif (!penv) return;\n\tMODCHANNEL *p = pChn;\n\tfor (UINT i=nChn; i<MAX_CHANNELS; p++, i++)\n\tif ((i >= m_nChannels) || (p == pChn))\n\t{\n\t\tif (((p->nMasterChn == nChn+1) || (p == pChn)) && (p->pHeader))\n\t\t{\n\t\t\tBOOL bOk = FALSE;\n\t\t\t// Duplicate Check Type\n\t\t\tswitch(p->pHeader->nDCT)\n\t\t\t{\n\t\t\t// Note\n\t\t\tcase DCT_NOTE:\n\t\t\t\tif ((note) && (p->nNote == note) && (pHeader == p->pHeader)) bOk = TRUE;\n\t\t\t\tbreak;\n\t\t\t// Sample\n\t\t\tcase DCT_SAMPLE:\n\t\t\t\tif ((pSample) && (pSample == p->pSample)) bOk = TRUE;\n\t\t\t\tbreak;\n\t\t\t// Instrument\n\t\t\tcase DCT_INSTRUMENT:\n\t\t\t\tif (pHeader == p->pHeader) bOk = TRUE;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// Duplicate Note Action\n\t\t\tif (bOk)\n\t\t\t{\n\t\t\t\tswitch(p->pHeader->nDNA)\n\t\t\t\t{\n\t\t\t\t// Cut\n\t\t\t\tcase DNA_NOTECUT:\n\t\t\t\t\tKeyOff(i);\n\t\t\t\t\tp->nVolume = 0;\n\t\t\t\t\tbreak;\n\t\t\t\t// Note Off\n\t\t\t\tcase DNA_NOTEOFF:\n\t\t\t\t\tKeyOff(i);\n\t\t\t\t\tbreak;\n\t\t\t\t// Note Fade\n\t\t\t\tcase DNA_NOTEFADE:\n\t\t\t\t\tp->dwFlags |= CHN_NOTEFADE;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (!p->nVolume)\n\t\t\t\t{\n\t\t\t\t\tp->nFadeOutVol = 0;\n\t\t\t\t\tp->dwFlags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif (pChn->dwFlags & CHN_MUTE) return;\n\t// New Note Action\n\tif ((pChn->nVolume) && (pChn->nLength))\n\t{\n\t\tUINT n = GetNNAChannel(nChn);\n\t\tif (n)\n\t\t{\n\t\t\tMODCHANNEL *p = &Chn[n];\n\t\t\t// Copy Channel\n\t\t\t*p = *pChn;\n\t\t\tp->dwFlags &= ~(CHN_VIBRATO|CHN_TREMOLO|CHN_PANBRELLO|CHN_MUTE|CHN_PORTAMENTO);\n\t\t\tp->nMasterChn = nChn+1;\n\t\t\tp->nCommand = 0;\n\t\t\t// Key Off the note\n\t\t\tswitch(pChn->nNNA)\n\t\t\t{\n\t\t\tcase NNA_NOTEOFF:\tKeyOff(n); break;\n\t\t\tcase NNA_NOTECUT:\n\t\t\t\tp->nFadeOutVol = 0;\n\t\t\tcase NNA_NOTEFADE:\tp->dwFlags |= CHN_NOTEFADE; break;\n\t\t\t}\n\t\t\tif (!p->nVolume)\n\t\t\t{\n\t\t\t\tp->nFadeOutVol = 0;\n\t\t\t\tp->dwFlags |= (CHN_NOTEFADE|CHN_FASTVOLRAMP);\n\t\t\t}\n\t\t\t// Stop this channel\n\t\t\tpChn->nLength = pChn->nPos = pChn->nPosLo = 0;\n\t\t\tpChn->nROfs = pChn->nLOfs = 0;\n\t\t}\n\t}\n}\n\n\nBOOL CSoundFile::ProcessEffects()\n//-------------------------------\n{\n\tMODCHANNEL *pChn = Chn;\n\tint nBreakRow = -1, nPosJump = -1, nPatLoopRow = -1;\n\n\tfor (UINT nChn=0; nChn<m_nChannels; nChn++, pChn++)\n\t{\n\t\tUINT instr = pChn->nRowInstr;\n\t\tUINT volcmd = pChn->nRowVolCmd;\n\t\tUINT vol = pChn->nRowVolume;\n\t\tUINT cmd = pChn->nRowCommand;\n\t\tUINT param = pChn->nRowParam;\n\t\tbool bPorta = ((cmd != CMD_TONEPORTAMENTO) && (cmd != CMD_TONEPORTAVOL) && (volcmd != VOLCMD_TONEPORTAMENTO)) ? FALSE : TRUE;\n\t\tUINT nStartTick = 0;\n\n\t\tpChn->dwFlags &= ~CHN_FASTVOLRAMP;\n\t\t// Process special effects (note delay, pattern delay, pattern loop)\n\t\tif ((cmd == CMD_MODCMDEX) || (cmd == CMD_S3MCMDEX))\n\t\t{\n\t\t\tif ((!param) && (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) param = pChn->nOldCmdEx; else pChn->nOldCmdEx = param;\n\t\t\t// Note Delay ?\n\t\t\tif ((param & 0xF0) == 0xD0)\n\t\t\t{\n\t\t\t\tnStartTick = param & 0x0F;\n\t\t\t} else\n\t\t\tif (!m_nTickCount)\n\t\t\t{\n\t\t\t\t// Pattern Loop ?\n\t\t\t\tif ((((param & 0xF0) == 0x60) && (cmd == CMD_MODCMDEX))\n\t\t\t\t || (((param & 0xF0) == 0xB0) && (cmd == CMD_S3MCMDEX)))\n\t\t\t\t{\n\t\t\t\t\tint nloop = PatternLoop(pChn, param & 0x0F);\n\t\t\t\t\tif (nloop >= 0) nPatLoopRow = nloop;\n\t\t\t\t} else\n\t\t\t\t// Pattern Delay\n\t\t\t\tif ((param & 0xF0) == 0xE0)\n\t\t\t\t{\n\t\t\t\t\tm_nPatternDelay = param & 0x0F;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Handles note/instrument/volume changes\n\t\tif (m_nTickCount == nStartTick) // can be delayed by a note delay effect\n\t\t{\n\t\t\tUINT note = pChn->nRowNote;\n\t\t\tif (instr) pChn->nNewIns = instr;\n\t\t\t// XM: Key-Off + Sample == Note Cut\n\t\t\tif (m_nType & (MOD_TYPE_MOD|MOD_TYPE_XM|MOD_TYPE_MT2))\n\t\t\t{\n\t\t\t\tif ((note == 0xFF) && ((!pChn->pHeader) || (!(pChn->pHeader->dwFlags & ENV_VOLUME))))\n\t\t\t\t{\n\t\t\t\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t\t\t\t\tpChn->nVolume = 0;\n\t\t\t\t\tnote = instr = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ((!note) && (instr))\n\t\t\t{\n\t\t\t\tif (m_nInstruments)\n\t\t\t\t{\n\t\t\t\t\tif (pChn->pInstrument) pChn->nVolume = pChn->pInstrument->nVolume;\n\t\t\t\t\tif (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))\n\t\t\t\t\t{\n\t\t\t\t\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t\t\t\t\t\tpChn->nVolEnvPosition = 0;\n\t\t\t\t\t\tpChn->nPanEnvPosition = 0;\n\t\t\t\t\t\tpChn->nPitchEnvPosition = 0;\n\t\t\t\t\t\tpChn->nAutoVibDepth = 0;\n\t\t\t\t\t\tpChn->nAutoVibPos = 0;\n\t\t\t\t\t\tpChn->dwFlags &= ~CHN_NOTEFADE;\n\t\t\t\t\t\tpChn->nFadeOutVol = 65536;\n\t\t\t\t\t}\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tif (instr < MAX_SAMPLES) pChn->nVolume = Ins[instr].nVolume;\n\t\t\t\t}\n\t\t\t\tif (!(m_nType & MOD_TYPE_IT)) instr = 0;\n\t\t\t}\n\t\t\t// Invalid Instrument ?\n\t\t\tif (instr >= MAX_INSTRUMENTS) instr = 0;\n\t\t\t// Note Cut/Off => ignore instrument\n\t\t\tif (note >= 0xFE) instr = 0;\n\t\t\tif ((note) && (note <= 128)) pChn->nNewNote = note;\n\t\t\t// New Note Action ?\n\t\t\tif ((note) && (note <= 128) && (!bPorta))\n\t\t\t{\n\t\t\t\tCheckNNA(nChn, instr, note, FALSE);\n\t\t\t}\n\t\t\t// Instrument Change ?\n\t\t\tif (instr)\n\t\t\t{\n\t\t\t\tMODINSTRUMENT *psmp = pChn->pInstrument;\n\t\t\t\tInstrumentChange(pChn, instr, bPorta, TRUE);\n\t\t\t\tpChn->nNewIns = 0;\n\t\t\t\t// Special IT case: portamento+note causes sample change -> ignore portamento\n\t\t\t\tif ((m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))\n\t\t\t\t && (psmp != pChn->pInstrument) && (note) && (note < 0x80))\n\t\t\t\t{\n\t\t\t\t\tbPorta = FALSE;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// New Note ?\n\t\t\tif (note)\n\t\t\t{\n\t\t\t\tif ((!instr) && (pChn->nNewIns) && (note < 0x80))\n\t\t\t\t{\n\t\t\t\t\tInstrumentChange(pChn, pChn->nNewIns, bPorta, FALSE, (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) ? FALSE : TRUE);\n\t\t\t\t\tpChn->nNewIns = 0;\n\t\t\t\t}\n\t\t\t\tNoteChange(nChn, note, bPorta, (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) ? FALSE : TRUE);\n\t\t\t\tif ((bPorta) && (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) && (instr))\n\t\t\t\t{\n\t\t\t\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t\t\t\t\tpChn->nVolEnvPosition = 0;\n\t\t\t\t\tpChn->nPanEnvPosition = 0;\n\t\t\t\t\tpChn->nPitchEnvPosition = 0;\n\t\t\t\t\tpChn->nAutoVibDepth = 0;\n\t\t\t\t\tpChn->nAutoVibPos = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Tick-0 only volume commands\n\t\t\tif (volcmd == VOLCMD_VOLUME)\n\t\t\t{\n\t\t\t\tif (vol > 64) vol = 64;\n\t\t\t\tpChn->nVolume = vol << 2;\n\t\t\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t\t\t} else\n\t\t\tif (volcmd == VOLCMD_PANNING)\n\t\t\t{\n\t\t\t\tif (vol > 64) vol = 64;\n\t\t\t\tpChn->nPan = vol << 2;\n\t\t\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t\t\t}\n\t\t}\n\n\t\t// Volume Column Effect (except volume & panning)\n\t\tif ((volcmd > VOLCMD_PANNING) && (m_nTickCount >= nStartTick))\n\t\t{\n\t\t\tif (volcmd == VOLCMD_TONEPORTAMENTO)\n\t\t\t{\n\t\t\t\tif (m_nType & MOD_TYPE_IT)\n\t\t\t\t\tTonePortamento(pChn, ImpulseTrackerPortaVolCmd[vol & 0x0F]);\n\t\t\t\telse\n\t\t\t\t\tTonePortamento(pChn, vol * 16);\n\t\t\t} else\n\t\t\t{\n\t\t\t\tif (vol) pChn->nOldVolParam = vol; else vol = pChn->nOldVolParam;\n\t\t\t\tswitch(volcmd)\n\t\t\t\t{\n\t\t\t\tcase VOLCMD_VOLSLIDEUP:\n\t\t\t\t\tVolumeSlide(pChn, vol << 4);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase VOLCMD_VOLSLIDEDOWN:\n\t\t\t\t\tVolumeSlide(pChn, vol);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase VOLCMD_FINEVOLUP:\n\t\t\t\t\tif (m_nType & MOD_TYPE_IT)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (m_nTickCount == nStartTick) VolumeSlide(pChn, (vol << 4) | 0x0F);\n\t\t\t\t\t} else\n\t\t\t\t\t\tFineVolumeUp(pChn, vol);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase VOLCMD_FINEVOLDOWN:\n\t\t\t\t\tif (m_nType & MOD_TYPE_IT)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (m_nTickCount == nStartTick) VolumeSlide(pChn, 0xF0 | vol);\n\t\t\t\t\t} else\n\t\t\t\t\t\tFineVolumeDown(pChn, vol);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase VOLCMD_VIBRATOSPEED:\n\t\t\t\t\tVibrato(pChn, vol << 4);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase VOLCMD_VIBRATO:\n\t\t\t\t\tVibrato(pChn, vol);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase VOLCMD_PANSLIDELEFT:\n\t\t\t\t\tPanningSlide(pChn, vol);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase VOLCMD_PANSLIDERIGHT:\n\t\t\t\t\tPanningSlide(pChn, vol << 4);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase VOLCMD_PORTAUP:\n\t\t\t\t\tPortamentoUp(pChn, vol << 2);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase VOLCMD_PORTADOWN:\n\t\t\t\t\tPortamentoDown(pChn, vol << 2);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Effects\n\t\tif (cmd) switch (cmd)\n\t\t{\n\t\t// Set Volume\n\t\tcase CMD_VOLUME:\n\t\t\tif (!m_nTickCount)\n\t\t\t{\n\t\t\t\tpChn->nVolume = (param < 64) ? param*4 : 256;\n\t\t\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t\t\t}\n\t\t\tbreak;\n\n\t\t// Portamento Up\n\t\tcase CMD_PORTAMENTOUP:\n\t\t\tif ((!param) && (m_nType & MOD_TYPE_MOD)) break;\n\t\t\tPortamentoUp(pChn, param);\n\t\t\tbreak;\n\n\t\t// Portamento Down\n\t\tcase CMD_PORTAMENTODOWN:\n\t\t\tif ((!param) && (m_nType & MOD_TYPE_MOD)) break;\n\t\t\tPortamentoDown(pChn, param);\n\t\t\tbreak;\n\n\t\t// Volume Slide\n\t\tcase CMD_VOLUMESLIDE:\n\t\t\tif ((param) || (m_nType != MOD_TYPE_MOD)) VolumeSlide(pChn, param);\n\t\t\tbreak;\n\n\t\t// Tone-Portamento\n\t\tcase CMD_TONEPORTAMENTO:\n\t\t\tTonePortamento(pChn, param);\n\t\t\tbreak;\n\n\t\t// Tone-Portamento + Volume Slide\n\t\tcase CMD_TONEPORTAVOL:\n\t\t\tif ((param) || (m_nType != MOD_TYPE_MOD)) VolumeSlide(pChn, param);\n\t\t\tTonePortamento(pChn, 0);\n\t\t\tbreak;\n\n\t\t// Vibrato\n\t\tcase CMD_VIBRATO:\n\t\t\tVibrato(pChn, param);\n\t\t\tbreak;\n\n\t\t// Vibrato + Volume Slide\n\t\tcase CMD_VIBRATOVOL:\n\t\t\tif ((param) || (m_nType != MOD_TYPE_MOD)) VolumeSlide(pChn, param);\n\t\t\tVibrato(pChn, 0);\n\t\t\tbreak;\n\n\t\t// Set Speed\n\t\tcase CMD_SPEED:\n\t\t\tif (!m_nTickCount) SetSpeed(param);\n\t\t\tbreak;\n\n\t\t// Set Tempo\n\t\tcase CMD_TEMPO:\n\t\t\tif (!m_nTickCount)\n\t\t\t{\n\t\t\t\tif (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))\n\t\t\t\t{\n\t\t\t\t\tif (param) pChn->nOldTempo = param; else param = pChn->nOldTempo;\n\t\t\t\t}\n\t\t\t\tSetTempo(param);\n\t\t\t}\n\t\t\tbreak;\n\n\t\t// Set Offset\n\t\tcase CMD_OFFSET:\n\t\t\tif (m_nTickCount) break;\n\t\t\tif (param) pChn->nOldOffset = param; else param = pChn->nOldOffset;\n\t\t\tparam <<= 8;\n\t\t\tparam |= (UINT)(pChn->nOldHiOffset) << 16;\n\t\t\tif ((pChn->nRowNote) && (pChn->nRowNote < 0x80))\n\t\t\t{\n\t\t\t\tif (bPorta)\n\t\t\t\t\tpChn->nPos = param;\n\t\t\t\telse\n\t\t\t\t\tpChn->nPos += param;\n\t\t\t\tif (pChn->nPos >= pChn->nLength)\n\t\t\t\t{\n\t\t\t\t\tif (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)))\n\t\t\t\t\t{\n\t\t\t\t\t\tpChn->nPos = pChn->nLoopStart;\n\t\t\t\t\t\tif ((m_dwSongFlags & SONG_ITOLDEFFECTS) && (pChn->nLength > 4))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpChn->nPos = pChn->nLength - 2;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else\n\t\t\tif ((param < pChn->nLength) && (m_nType & (MOD_TYPE_MTM|MOD_TYPE_DMF)))\n\t\t\t{\n\t\t\t\tpChn->nPos = param;\n\t\t\t}\n\t\t\tbreak;\n\n\t\t// Arpeggio\n\t\tcase CMD_ARPEGGIO:\n\t\t\tif ((m_nTickCount) || (!pChn->nPeriod) || (!pChn->nNote)) break;\n\t\t\tif ((!param) && (!(m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)))) break;\n\t\t\tpChn->nCommand = CMD_ARPEGGIO;\n\t\t\tif (param) pChn->nArpeggio = param;\n\t\t\tbreak;\n\n\t\t// Retrig\n\t\tcase CMD_RETRIG:\n\t\t\tif (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))\n\t\t\t{\n\t\t\t\tif (!(param & 0xF0)) param |= pChn->nRetrigParam & 0xF0;\n\t\t\t\tif (!(param & 0x0F)) param |= pChn->nRetrigParam & 0x0F;\n\t\t\t\tparam |= 0x100; // increment retrig count on first row\n\t\t\t}\n\t\t\tif (param) pChn->nRetrigParam = (BYTE)(param & 0xFF); else param = pChn->nRetrigParam;\n\t\t\tRetrigNote(nChn, param);\n\t\t\tbreak;\n\n\t\t// Tremor\n\t\tcase CMD_TREMOR:\n\t\t\tif (m_nTickCount) break;\n\t\t\tpChn->nCommand = CMD_TREMOR;\n\t\t\tif (param) pChn->nTremorParam = param;\n\t\t\tbreak;\n\n\t\t// Set Global Volume\n\t\tcase CMD_GLOBALVOLUME:\n\t\t\tif (m_nTickCount) break;\n\t\t\tif (m_nType != MOD_TYPE_IT) param <<= 1;\n\t\t\tif (param > 128) param = 128;\n\t\t\tm_nGlobalVolume = param << 1;\n\t\t\tbreak;\n\n\t\t// Global Volume Slide\n\t\tcase CMD_GLOBALVOLSLIDE:\n\t\t\tGlobalVolSlide(param);\n\t\t\tbreak;\n\n\t\t// Set 8-bit Panning\n\t\tcase CMD_PANNING8:\n\t\t\tif (m_nTickCount) break;\n\t\t\tif (!(m_dwSongFlags & SONG_SURROUNDPAN)) pChn->dwFlags &= ~CHN_SURROUND;\n\t\t\tif (m_nType & (MOD_TYPE_IT|MOD_TYPE_XM|MOD_TYPE_MT2))\n\t\t\t{\n\t\t\t\tpChn->nPan = param;\n\t\t\t} else\n\t\t\tif (param <= 0x80)\n\t\t\t{\n\t\t\t\tpChn->nPan = param << 1;\n\t\t\t} else\n\t\t\tif (param == 0xA4)\n\t\t\t{\n\t\t\t\tpChn->dwFlags |= CHN_SURROUND;\n\t\t\t\tpChn->nPan = 0x80;\n\t\t\t}\n\t\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t\t\tbreak;\n\n\t\t// Panning Slide\n\t\tcase CMD_PANNINGSLIDE:\n\t\t\tPanningSlide(pChn, param);\n\t\t\tbreak;\n\n\t\t// Tremolo\n\t\tcase CMD_TREMOLO:\n\t\t\tTremolo(pChn, param);\n\t\t\tbreak;\n\n\t\t// Fine Vibrato\n\t\tcase CMD_FINEVIBRATO:\n\t\t\tFineVibrato(pChn, param);\n\t\t\tbreak;\n\n\t\t// MOD/XM Exx Extended Commands\n\t\tcase CMD_MODCMDEX:\n\t\t\tExtendedMODCommands(nChn, param);\n\t\t\tbreak;\n\n\t\t// S3M/IT Sxx Extended Commands\n\t\tcase CMD_S3MCMDEX:\n\t\t\tExtendedS3MCommands(nChn, param);\n\t\t\tbreak;\n\n\t\t// Key Off\n\t\tcase CMD_KEYOFF:\n\t\t\tif (!m_nTickCount) KeyOff(nChn);\n\t\t\tbreak;\n\n\t\t// Extra-fine porta up/down\n\t\tcase CMD_XFINEPORTAUPDOWN:\n\t\t\tswitch(param & 0xF0)\n\t\t\t{\n\t\t\tcase 0x10: ExtraFinePortamentoUp(pChn, param & 0x0F); break;\n\t\t\tcase 0x20: ExtraFinePortamentoDown(pChn, param & 0x0F); break;\n\t\t\t// Modplug XM Extensions\n\t\t\tcase 0x50:\n\t\t\tcase 0x60:\n\t\t\tcase 0x70:\n\t\t\tcase 0x90:\n\t\t\tcase 0xA0: ExtendedS3MCommands(nChn, param); break;\n\t\t\t}\n\t\t\tbreak;\n\n\t\t// Set Channel Global Volume\n\t\tcase CMD_CHANNELVOLUME:\n\t\t\tif (m_nTickCount) break;\n\t\t\tif (param <= 64)\n\t\t\t{\n\t\t\t\tpChn->nGlobalVol = param;\n\t\t\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t\t\t}\n\t\t\tbreak;\n\n\t\t// Channel volume slide\n\t\tcase CMD_CHANNELVOLSLIDE:\n\t\t\tChannelVolSlide(pChn, param);\n\t\t\tbreak;\n\n\t\t// Panbrello (IT)\n\t\tcase CMD_PANBRELLO:\n\t\t\tPanbrello(pChn, param);\n\t\t\tbreak;\n\n\t\t// Set Envelope Position\n\t\tcase CMD_SETENVPOSITION:\n\t\t\tif (!m_nTickCount)\n\t\t\t{\n\t\t\t\tpChn->nVolEnvPosition = param;\n\t\t\t\tpChn->nPanEnvPosition = param;\n\t\t\t\tpChn->nPitchEnvPosition = param;\n\t\t\t\tif (pChn->pHeader)\n\t\t\t\t{\n\t\t\t\t\tINSTRUMENTHEADER *penv = pChn->pHeader;\n\t\t\t\t\tif ((pChn->dwFlags & CHN_PANENV) && (penv->nPanEnv) && (param > penv->PanPoints[penv->nPanEnv-1]))\n\t\t\t\t\t{\n\t\t\t\t\t\tpChn->dwFlags &= ~CHN_PANENV;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\t// Position Jump\n\t\tcase CMD_POSITIONJUMP:\n\t\t\tnPosJump = param;\n\t\t\tm_nNextStartRow = 0;\n\t\t\tbreak;\n\n\t\t// Pattern Break\n\t\tcase CMD_PATTERNBREAK:\n\t\t\tnBreakRow = param;\n\t\t\tm_nNextStartRow = 0;\n\t\t\tbreak;\n\n\t\t// Midi Controller\n\t\tcase CMD_MIDI:\n\t\t\tif (m_nTickCount) break;\n\t\t\tif (param < 0x80){\n\t\t\t\tProcessMidiMacro(nChn, &m_MidiCfg.szMidiSFXExt[pChn->nActiveMacro << 5], param);\n\t\t\t} else {\n\t\t\t\tProcessMidiMacro(nChn, &m_MidiCfg.szMidiZXXExt[(param & 0x7F) << 5], 0);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Navigation Effects\n\tif (!m_nTickCount)\n\t{\n\t\t// Pattern Loop\n\t\tif (nPatLoopRow >= 0)\n\t\t{\n\t\t\tm_nNextPattern = m_nCurrentPattern;\n\t\t\tm_nNextRow = nPatLoopRow;\n\t\t\tif (m_nPatternDelay) m_nNextRow++;\n\t\t} else\n\t\t// Pattern Break / Position Jump only if no loop running\n\t\tif ((nBreakRow >= 0) || (nPosJump >= 0))\n\t\t{\n\t\t\tBOOL bNoLoop = FALSE;\n\t\t\tif (nPosJump < 0) nPosJump = m_nCurrentPattern+1;\n\t\t\tif (nBreakRow < 0) nBreakRow = 0;\n\t\t\t// Modplug Tracker & ModPlugin allow backward jumps\n\t\t#ifndef MODPLUG_FASTSOUNDLIB\n\t\t\tif ((nPosJump < (int)m_nCurrentPattern)\n\t\t\t || ((nPosJump == (int)m_nCurrentPattern) && (nBreakRow <= (int)m_nRow)))\n\t\t\t{\n\t\t\t\tif (!IsValidBackwardJump(m_nCurrentPattern, m_nRow, nPosJump, nBreakRow))\n\t\t\t\t{\n\t\t\t\t\tif (m_nRepeatCount)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (m_nRepeatCount > 0) m_nRepeatCount--;\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t#ifdef MODPLUG_TRACKER\n\t\t\t\t\t\tif (gdwSoundSetup & SNDMIX_NOBACKWARDJUMPS)\n\t\t\t\t\t#endif\n\t\t\t\t\t\t// Backward jump disabled\n\t\t\t\t\t\tbNoLoop = TRUE;\n\t\t\t\t\t\t//reset repeat count incase there are multiple loops.\n\t\t\t\t\t\t//(i.e. Unreal tracks)\n\t\t\t\t\t\tm_nRepeatCount = m_nInitialRepeatCount;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t#endif\t// MODPLUG_FASTSOUNDLIB\n\t\t\tif (((!bNoLoop) && (nPosJump < MAX_ORDERS))\n\t\t\t && ((nPosJump != (int)m_nCurrentPattern) || (nBreakRow != (int)m_nRow)))\n\t\t\t{\n\t\t\t\tif (nPosJump != (int)m_nCurrentPattern)\n\t\t\t\t{\n\t\t\t\t\tfor (UINT i=0; i<m_nChannels; i++) Chn[i].nPatternLoopCount = 0;\n\t\t\t\t}\n\t\t\t\tm_nNextPattern = nPosJump;\n\t\t\t\tm_nNextRow = (UINT)nBreakRow;\n\t\t\t}\n\t\t}\n\t}\n\treturn TRUE;\n}\n\n\n////////////////////////////////////////////////////////////\n// Channels effects\n\nvoid CSoundFile::PortamentoUp(MODCHANNEL *pChn, UINT param)\n//---------------------------------------------------------\n{\n\tif (param) pChn->nOldPortaUpDown = param; else param = pChn->nOldPortaUpDown;\n\tif ((m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM)) && ((param & 0xF0) >= 0xE0))\n\t{\n\t\tif (param & 0x0F)\n\t\t{\n\t\t\tif ((param & 0xF0) == 0xF0)\n\t\t\t{\n\t\t\t\tFinePortamentoUp(pChn, param & 0x0F);\n\t\t\t} else\n\t\t\tif ((param & 0xF0) == 0xE0)\n\t\t\t{\n\t\t\t\tExtraFinePortamentoUp(pChn, param & 0x0F);\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\t// Regular Slide\n\tif (!(m_dwSongFlags & SONG_FIRSTTICK) || (m_nMusicSpeed == 1))  //rewbs.PortaA01fix\n\t{\n\t\tDoFreqSlide(pChn, -(int)(param * 4));\n\t}\n}\n\n\nvoid CSoundFile::PortamentoDown(MODCHANNEL *pChn, UINT param)\n//-----------------------------------------------------------\n{\n\tif (param) pChn->nOldPortaUpDown = param; else param = pChn->nOldPortaUpDown;\n\tif ((m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM)) && ((param & 0xF0) >= 0xE0))\n\t{\n\t\tif (param & 0x0F)\n\t\t{\n\t\t\tif ((param & 0xF0) == 0xF0)\n\t\t\t{\n\t\t\t\tFinePortamentoDown(pChn, param & 0x0F);\n\t\t\t} else\n\t\t\tif ((param & 0xF0) == 0xE0)\n\t\t\t{\n\t\t\t\tExtraFinePortamentoDown(pChn, param & 0x0F);\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\tif (!(m_dwSongFlags & SONG_FIRSTTICK) || (m_nMusicSpeed == 1)) { //rewbs.PortaA01fix\n\t\tDoFreqSlide(pChn, (int)(param << 2));\n\t}\n}\n\n\nvoid CSoundFile::FinePortamentoUp(MODCHANNEL *pChn, UINT param)\n//-------------------------------------------------------------\n{\n\tif (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))\n\t{\n\t\tif (param) pChn->nOldFinePortaUpDown = param; else param = pChn->nOldFinePortaUpDown;\n\t}\n\tif (m_dwSongFlags & SONG_FIRSTTICK)\n\t{\n\t\tif ((pChn->nPeriod) && (param))\n\t\t{\n\t\t\tif ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))))\n\t\t\t{\n\t\t\t\tpChn->nPeriod = _muldivr(pChn->nPeriod, LinearSlideDownTable[param & 0x0F], 65536);\n\t\t\t} else\n\t\t\t{\n\t\t\t\tpChn->nPeriod -= (int)(param * 4);\n\t\t\t}\n\t\t\tif (pChn->nPeriod < 1) pChn->nPeriod = 1;\n\t\t}\n\t}\n}\n\n\nvoid CSoundFile::FinePortamentoDown(MODCHANNEL *pChn, UINT param)\n//---------------------------------------------------------------\n{\n\tif (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))\n\t{\n\t\tif (param) pChn->nOldFinePortaUpDown = param; else param = pChn->nOldFinePortaUpDown;\n\t}\n\tif (m_dwSongFlags & SONG_FIRSTTICK)\n\t{\n\t\tif ((pChn->nPeriod) && (param))\n\t\t{\n\t\t\tif ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))))\n\t\t\t{\n\t\t\t\tpChn->nPeriod = _muldivr(pChn->nPeriod, LinearSlideUpTable[param & 0x0F], 65536);\n\t\t\t} else\n\t\t\t{\n\t\t\t\tpChn->nPeriod += (int)(param * 4);\n\t\t\t}\n\t\t\tif (pChn->nPeriod > 0xFFFF) pChn->nPeriod = 0xFFFF;\n\t\t}\n\t}\n}\n\n\nvoid CSoundFile::ExtraFinePortamentoUp(MODCHANNEL *pChn, UINT param)\n//------------------------------------------------------------------\n{\n\tif (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))\n\t{\n\t\tif (param) pChn->nOldFinePortaUpDown = param; else param = pChn->nOldFinePortaUpDown;\n\t}\n\tif (m_dwSongFlags & SONG_FIRSTTICK)\n\t{\n\t\tif ((pChn->nPeriod) && (param))\n\t\t{\n\t\t\tif ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))))\n\t\t\t{\n\t\t\t\tpChn->nPeriod = _muldivr(pChn->nPeriod, FineLinearSlideDownTable[param & 0x0F], 65536);\n\t\t\t} else\n\t\t\t{\n\t\t\t\tpChn->nPeriod -= (int)(param);\n\t\t\t}\n\t\t\tif (pChn->nPeriod < 1) pChn->nPeriod = 1;\n\t\t}\n\t}\n}\n\n\nvoid CSoundFile::ExtraFinePortamentoDown(MODCHANNEL *pChn, UINT param)\n//--------------------------------------------------------------------\n{\n\tif (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))\n\t{\n\t\tif (param) pChn->nOldFinePortaUpDown = param; else param = pChn->nOldFinePortaUpDown;\n\t}\n\tif (m_dwSongFlags & SONG_FIRSTTICK)\n\t{\n\t\tif ((pChn->nPeriod) && (param))\n\t\t{\n\t\t\tif ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))))\n\t\t\t{\n\t\t\t\tpChn->nPeriod = _muldivr(pChn->nPeriod, FineLinearSlideUpTable[param & 0x0F], 65536);\n\t\t\t} else\n\t\t\t{\n\t\t\t\tpChn->nPeriod += (int)(param);\n\t\t\t}\n\t\t\tif (pChn->nPeriod > 0xFFFF) pChn->nPeriod = 0xFFFF;\n\t\t}\n\t}\n}\n\n\n// Portamento Slide\nvoid CSoundFile::TonePortamento(MODCHANNEL *pChn, UINT param)\n//-----------------------------------------------------------\n{\n\tif (param) pChn->nPortamentoSlide = param * 4;\n\tpChn->dwFlags |= CHN_PORTAMENTO;\n\tif ((pChn->nPeriod) && (pChn->nPortamentoDest) && (!(m_dwSongFlags & SONG_FIRSTTICK)))\n\t{\n\t\tif (pChn->nPeriod < pChn->nPortamentoDest)\n\t\t{\n\t\t\tLONG delta = (int)pChn->nPortamentoSlide;\n\t\t\tif ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))))\n\t\t\t{\n\t\t\t\tUINT n = pChn->nPortamentoSlide >> 2;\n\t\t\t\tif (n > 255) n = 255;\n\t\t\t\tdelta = _muldivr(pChn->nPeriod, LinearSlideUpTable[n], 65536) - pChn->nPeriod;\n\t\t\t\tif (delta < 1) delta = 1;\n\t\t\t}\n\t\t\tpChn->nPeriod += delta;\n\t\t\tif (pChn->nPeriod > pChn->nPortamentoDest) pChn->nPeriod = pChn->nPortamentoDest;\n\t\t} else\n\t\tif (pChn->nPeriod > pChn->nPortamentoDest)\n\t\t{\n\t\t\tLONG delta = - (int)pChn->nPortamentoSlide;\n\t\t\tif ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))))\n\t\t\t{\n\t\t\t\tUINT n = pChn->nPortamentoSlide >> 2;\n\t\t\t\tif (n > 255) n = 255;\n\t\t\t\tdelta = _muldivr(pChn->nPeriod, LinearSlideDownTable[n], 65536) - pChn->nPeriod;\n\t\t\t\tif (delta > -1) delta = -1;\n\t\t\t}\n\t\t\tpChn->nPeriod += delta;\n\t\t\tif (pChn->nPeriod < pChn->nPortamentoDest) pChn->nPeriod = pChn->nPortamentoDest;\n\t\t}\n\t}\n}\n\n\nvoid CSoundFile::Vibrato(MODCHANNEL *p, UINT param)\n//-------------------------------------------------\n{\n\tif (param & 0x0F) p->nVibratoDepth = (param & 0x0F) * 4;\n\tif (param & 0xF0) p->nVibratoSpeed = (param >> 4) & 0x0F;\n\tp->dwFlags |= CHN_VIBRATO;\n}\n\n\nvoid CSoundFile::FineVibrato(MODCHANNEL *p, UINT param)\n//-----------------------------------------------------\n{\n\tif (param & 0x0F) p->nVibratoDepth = param & 0x0F;\n\tif (param & 0xF0) p->nVibratoSpeed = (param >> 4) & 0x0F;\n\tp->dwFlags |= CHN_VIBRATO;\n}\n\n\nvoid CSoundFile::Panbrello(MODCHANNEL *p, UINT param)\n//---------------------------------------------------\n{\n\tif (param & 0x0F) p->nPanbrelloDepth = param & 0x0F;\n\tif (param & 0xF0) p->nPanbrelloSpeed = (param >> 4) & 0x0F;\n\tp->dwFlags |= CHN_PANBRELLO;\n}\n\n\nvoid CSoundFile::VolumeSlide(MODCHANNEL *pChn, UINT param)\n//--------------------------------------------------------\n{\n\tif (param) pChn->nOldVolumeSlide = param; else param = pChn->nOldVolumeSlide;\n\tLONG newvolume = pChn->nVolume;\n\tif (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM|MOD_TYPE_AMF))\n\t{\n\t\tif ((param & 0x0F) == 0x0F)\n\t\t{\n\t\t\tif (param & 0xF0)\n\t\t\t{\n\t\t\t\tFineVolumeUp(pChn, (param >> 4));\n\t\t\t\treturn;\n\t\t\t} else\n\t\t\t{\n\t\t\t\tif ((m_dwSongFlags & SONG_FIRSTTICK) && (!(m_dwSongFlags & SONG_FASTVOLSLIDES)))\n\t\t\t\t{\n\t\t\t\t\tnewvolume -= 0x0F * 4;\n\t\t\t\t}\n\t\t\t}\n\t\t} else\n\t\tif ((param & 0xF0) == 0xF0)\n\t\t{\n\t\t\tif (param & 0x0F)\n\t\t\t{\n\t\t\t\tFineVolumeDown(pChn, (param & 0x0F));\n\t\t\t\treturn;\n\t\t\t} else\n\t\t\t{\n\t\t\t\tif ((m_dwSongFlags & SONG_FIRSTTICK) && (!(m_dwSongFlags & SONG_FASTVOLSLIDES)))\n\t\t\t\t{\n\t\t\t\t\tnewvolume += 0x0F * 4;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ((!(m_dwSongFlags & SONG_FIRSTTICK)) || (m_dwSongFlags & SONG_FASTVOLSLIDES))\n\t{\n\t\tif (param & 0x0F) newvolume -= (int)((param & 0x0F) * 4);\n\t\telse newvolume += (int)((param & 0xF0) >> 2);\n\t\tif (m_nType & MOD_TYPE_MOD) pChn->dwFlags |= CHN_FASTVOLRAMP;\n\t}\n\tif (newvolume < 0) newvolume = 0;\n\tif (newvolume > 256) newvolume = 256;\n\tpChn->nVolume = newvolume;\n}\n\n\nvoid CSoundFile::PanningSlide(MODCHANNEL *pChn, UINT param)\n//---------------------------------------------------------\n{\n\tLONG nPanSlide = 0;\n\tif (param) pChn->nOldPanSlide = param; else param = pChn->nOldPanSlide;\n\tif (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM))\n\t{\n\t\tif (((param & 0x0F) == 0x0F) && (param & 0xF0))\n\t\t{\n\t\t\tif (m_dwSongFlags & SONG_FIRSTTICK)\n\t\t\t{\n\t\t\t\tparam = (param & 0xF0) >> 2;\n\t\t\t\tnPanSlide = - (int)param;\n\t\t\t}\n\t\t} else\n\t\tif (((param & 0xF0) == 0xF0) && (param & 0x0F))\n\t\t{\n\t\t\tif (m_dwSongFlags & SONG_FIRSTTICK)\n\t\t\t{\n\t\t\t\tnPanSlide = (param & 0x0F) << 2;\n\t\t\t}\n\t\t} else\n\t\t{\n\t\t\tif (!(m_dwSongFlags & SONG_FIRSTTICK))\n\t\t\t{\n\t\t\t\tif (param & 0x0F) nPanSlide = (int)((param & 0x0F) << 2);\n\t\t\t\telse nPanSlide = -(int)((param & 0xF0) >> 2);\n\t\t\t}\n\t\t}\n\t} else\n\t{\n\t\tif (!(m_dwSongFlags & SONG_FIRSTTICK))\n\t\t{\n\t\t\tif (param & 0x0F) nPanSlide = -(int)((param & 0x0F) << 2);\n\t\t\telse nPanSlide = (int)((param & 0xF0) >> 2);\n\t\t}\n\t}\n\tif (nPanSlide)\n\t{\n\t\tnPanSlide += pChn->nPan;\n\t\tif (nPanSlide < 0) nPanSlide = 0;\n\t\tif (nPanSlide > 256) nPanSlide = 256;\n\t\tpChn->nPan = nPanSlide;\n\t}\n}\n\n\nvoid CSoundFile::FineVolumeUp(MODCHANNEL *pChn, UINT param)\n//---------------------------------------------------------\n{\n\tif (param) pChn->nOldFineVolUpDown = param; else param = pChn->nOldFineVolUpDown;\n\tif (m_dwSongFlags & SONG_FIRSTTICK)\n\t{\n\t\tpChn->nVolume += param * 4;\n\t\tif (pChn->nVolume > 256) pChn->nVolume = 256;\n\t\tif (m_nType & MOD_TYPE_MOD) pChn->dwFlags |= CHN_FASTVOLRAMP;\n\t}\n}\n\n\nvoid CSoundFile::FineVolumeDown(MODCHANNEL *pChn, UINT param)\n//-----------------------------------------------------------\n{\n\tif (param) pChn->nOldFineVolUpDown = param; else param = pChn->nOldFineVolUpDown;\n\tif (m_dwSongFlags & SONG_FIRSTTICK)\n\t{\n\t\tpChn->nVolume -= param * 4;\n\t\tif (pChn->nVolume < 0) pChn->nVolume = 0;\n\t\tif (m_nType & MOD_TYPE_MOD) pChn->dwFlags |= CHN_FASTVOLRAMP;\n\t}\n}\n\n\nvoid CSoundFile::Tremolo(MODCHANNEL *p, UINT param)\n//-------------------------------------------------\n{\n\tif (param & 0x0F) p->nTremoloDepth = (param & 0x0F) << 2;\n\tif (param & 0xF0) p->nTremoloSpeed = (param >> 4) & 0x0F;\n\tp->dwFlags |= CHN_TREMOLO;\n}\n\n\nvoid CSoundFile::ChannelVolSlide(MODCHANNEL *pChn, UINT param)\n//------------------------------------------------------------\n{\n\tLONG nChnSlide = 0;\n\tif (param) pChn->nOldChnVolSlide = param; else param = pChn->nOldChnVolSlide;\n\tif (((param & 0x0F) == 0x0F) && (param & 0xF0))\n\t{\n\t\tif (m_dwSongFlags & SONG_FIRSTTICK) nChnSlide = param >> 4;\n\t} else\n\tif (((param & 0xF0) == 0xF0) && (param & 0x0F))\n\t{\n\t\tif (m_dwSongFlags & SONG_FIRSTTICK) nChnSlide = - (int)(param & 0x0F);\n\t} else\n\t{\n\t\tif (!(m_dwSongFlags & SONG_FIRSTTICK))\n\t\t{\n\t\t\tif (param & 0x0F) nChnSlide = -(int)(param & 0x0F);\n\t\t\telse nChnSlide = (int)((param & 0xF0) >> 4);\n\t\t}\n\t}\n\tif (nChnSlide)\n\t{\n\t\tnChnSlide += pChn->nGlobalVol;\n\t\tif (nChnSlide < 0) nChnSlide = 0;\n\t\tif (nChnSlide > 64) nChnSlide = 64;\n\t\tpChn->nGlobalVol = nChnSlide;\n\t}\n}\n\n\nvoid CSoundFile::ExtendedMODCommands(UINT nChn, UINT param)\n//---------------------------------------------------------\n{\n\tMODCHANNEL *pChn = &Chn[nChn];\n\tUINT command = param & 0xF0;\n\tparam &= 0x0F;\n\tswitch(command)\n\t{\n\t// E0x: Set Filter\n\t// E1x: Fine Portamento Up\n\tcase 0x10:\tif ((param) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) FinePortamentoUp(pChn, param); break;\n\t// E2x: Fine Portamento Down\n\tcase 0x20:\tif ((param) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) FinePortamentoDown(pChn, param); break;\n\t// E3x: Set Glissando Control\n\tcase 0x30:\tpChn->dwFlags &= ~CHN_GLISSANDO; if (param) pChn->dwFlags |= CHN_GLISSANDO; break;\n\t// E4x: Set Vibrato WaveForm\n\tcase 0x40:\tpChn->nVibratoType = param & 0x07; break;\n\t// E5x: Set FineTune\n\tcase 0x50:\tif (m_nTickCount) break;\n\t\t\t\tpChn->nC4Speed = S3MFineTuneTable[param];\n\t\t\t\tif (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))\n\t\t\t\t\tpChn->nFineTune = param*2;\n\t\t\t\telse\n\t\t\t\t\tpChn->nFineTune = MOD2XMFineTune(param);\n\t\t\t\tif (pChn->nPeriod) pChn->nPeriod = GetPeriodFromNote(pChn->nNote, pChn->nFineTune, pChn->nC4Speed);\n\t\t\t\tbreak;\n\t// E6x: Pattern Loop\n\t// E7x: Set Tremolo WaveForm\n\tcase 0x70:\tpChn->nTremoloType = param & 0x07; break;\n\t// E8x: Set 4-bit Panning\n\tcase 0x80:\tif (!m_nTickCount) { pChn->nPan = (param << 4) + 8; pChn->dwFlags |= CHN_FASTVOLRAMP; } break;\n\t// E9x: Retrig\n\tcase 0x90:\tRetrigNote(nChn, param); break;\n\t// EAx: Fine Volume Up\n\tcase 0xA0:\tif ((param) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) FineVolumeUp(pChn, param); break;\n\t// EBx: Fine Volume Down\n\tcase 0xB0:\tif ((param) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) FineVolumeDown(pChn, param); break;\n\t// ECx: Note Cut\n\tcase 0xC0:\tNoteCut(nChn, param); break;\n\t// EDx: Note Delay\n\t// EEx: Pattern Delay\n\t// EFx: MOD: Invert Loop, XM: Set Active Midi Macro\n\tcase 0xF0:\tpChn->nActiveMacro = param;\tbreak;\n\t}\n}\n\n\nvoid CSoundFile::ExtendedS3MCommands(UINT nChn, UINT param)\n//---------------------------------------------------------\n{\n\tMODCHANNEL *pChn = &Chn[nChn];\n\tUINT command = param & 0xF0;\n\tparam &= 0x0F;\n\tswitch(command)\n\t{\n\t// S0x: Set Filter\n\t// S1x: Set Glissando Control\n\tcase 0x10:\tpChn->dwFlags &= ~CHN_GLISSANDO; if (param) pChn->dwFlags |= CHN_GLISSANDO; break;\n\t// S2x: Set FineTune\n\tcase 0x20:\tif (m_nTickCount) break;\n\t\t\t\tpChn->nC4Speed = S3MFineTuneTable[param & 0x0F];\n\t\t\t\tpChn->nFineTune = MOD2XMFineTune(param);\n\t\t\t\tif (pChn->nPeriod) pChn->nPeriod = GetPeriodFromNote(pChn->nNote, pChn->nFineTune, pChn->nC4Speed);\n\t\t\t\tbreak;\n\t// S3x: Set Vibrato WaveForm\n\tcase 0x30:\tpChn->nVibratoType = param & 0x07; break;\n\t// S4x: Set Tremolo WaveForm\n\tcase 0x40:\tpChn->nTremoloType = param & 0x07; break;\n\t// S5x: Set Panbrello WaveForm\n\tcase 0x50:\tpChn->nPanbrelloType = param & 0x07; break;\n\t// S6x: Pattern Delay for x frames\n\tcase 0x60:\tm_nFrameDelay = param; break;\n\t// S7x: Envelope Control\n\tcase 0x70:\tif (m_nTickCount) break;\n\t\t\t\tswitch(param)\n\t\t\t\t{\n\t\t\t\tcase 0:\n\t\t\t\tcase 1:\n\t\t\t\tcase 2:\n\t\t\t\t\t{\n\t\t\t\t\t\tMODCHANNEL *bkp = &Chn[m_nChannels];\n\t\t\t\t\t\tfor (UINT i=m_nChannels; i<MAX_CHANNELS; i++, bkp++)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (bkp->nMasterChn == nChn+1)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif (param == 1) KeyOff(i); else\n\t\t\t\t\t\t\t\tif (param == 2) bkp->dwFlags |= CHN_NOTEFADE; else\n\t\t\t\t\t\t\t\t\t{ bkp->dwFlags |= CHN_NOTEFADE; bkp->nFadeOutVol = 0; }\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\t\tpChn->nNNA = NNA_NOTECUT; break;\n\t\t\t\tcase 4:\t\tpChn->nNNA = NNA_CONTINUE; break;\n\t\t\t\tcase 5:\t\tpChn->nNNA = NNA_NOTEOFF; break;\n\t\t\t\tcase 6:\t\tpChn->nNNA = NNA_NOTEFADE; break;\n\t\t\t\tcase 7:\t\tpChn->dwFlags &= ~CHN_VOLENV; break;\n\t\t\t\tcase 8:\t\tpChn->dwFlags |= CHN_VOLENV; break;\n\t\t\t\tcase 9:\t\tpChn->dwFlags &= ~CHN_PANENV; break;\n\t\t\t\tcase 10:\tpChn->dwFlags |= CHN_PANENV; break;\n\t\t\t\tcase 11:\tpChn->dwFlags &= ~CHN_PITCHENV; break;\n\t\t\t\tcase 12:\tpChn->dwFlags |= CHN_PITCHENV; break;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t// S8x: Set 4-bit Panning\n\tcase 0x80:\tif (!m_nTickCount) { pChn->nPan = (param << 4) + 8; pChn->dwFlags |= CHN_FASTVOLRAMP; } break;\n\t// S9x: Set Surround\n\tcase 0x90:\tExtendedChannelEffect(pChn, param & 0x0F); break;\n\t// SAx: Set 64k Offset\n\tcase 0xA0:\tif (!m_nTickCount)\n\t\t\t\t{\n\t\t\t\t\tpChn->nOldHiOffset = param;\n\t\t\t\t\tif ((pChn->nRowNote) && (pChn->nRowNote < 0x80))\n\t\t\t\t\t{\n\t\t\t\t\t\tDWORD pos = param << 16;\n\t\t\t\t\t\tif (pos < pChn->nLength) pChn->nPos = pos;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t// SBx: Pattern Loop\n\t// SCx: Note Cut\n\tcase 0xC0:\tNoteCut(nChn, param); break;\n\t// SDx: Note Delay\n\t// case 0xD0:\tbreak;\n\t// SEx: Pattern Delay for x rows\n\t// SFx: S3M: Funk Repeat, IT: Set Active Midi Macro\n\tcase 0xF0:\tpChn->nActiveMacro = param; break;\n\t}\n}\n\n\nvoid CSoundFile::ExtendedChannelEffect(MODCHANNEL *pChn, UINT param)\n//------------------------------------------------------------------\n{\n\t// S9x and X9x commands (S3M/XM/IT only)\n\tif (m_nTickCount) return;\n\tswitch(param & 0x0F)\n\t{\n\t// S90: Surround Off\n\tcase 0x00:\tpChn->dwFlags &= ~CHN_SURROUND;\tbreak;\n\t// S91: Surround On\n\tcase 0x01:\tpChn->dwFlags |= CHN_SURROUND; pChn->nPan = 128; break;\n\t////////////////////////////////////////////////////////////\n\t// Modplug Extensions\n\t// S98: Reverb Off\n\tcase 0x08:\n\t\tpChn->dwFlags &= ~CHN_REVERB;\n\t\tpChn->dwFlags |= CHN_NOREVERB;\n\t\tbreak;\n\t// S99: Reverb On\n\tcase 0x09:\n\t\tpChn->dwFlags &= ~CHN_NOREVERB;\n\t\tpChn->dwFlags |= CHN_REVERB;\n\t\tbreak;\n\t// S9A: 2-Channels surround mode\n\tcase 0x0A:\n\t\tm_dwSongFlags &= ~SONG_SURROUNDPAN;\n\t\tbreak;\n\t// S9B: 4-Channels surround mode\n\tcase 0x0B:\n\t\tm_dwSongFlags |= SONG_SURROUNDPAN;\n\t\tbreak;\n\t// S9C: IT Filter Mode\n\tcase 0x0C:\n\t\tm_dwSongFlags &= ~SONG_MPTFILTERMODE;\n\t\tbreak;\n\t// S9D: MPT Filter Mode\n\tcase 0x0D:\n\t\tm_dwSongFlags |= SONG_MPTFILTERMODE;\n\t\tbreak;\n\t// S9E: Go forward\n\tcase 0x0E:\n\t\tpChn->dwFlags &= ~(CHN_PINGPONGFLAG);\n\t\tbreak;\n\t// S9F: Go backward (set position at the end for non-looping samples)\n\tcase 0x0F:\n\t\tif ((!(pChn->dwFlags & CHN_LOOP)) && (!pChn->nPos) && (pChn->nLength))\n\t\t{\n\t\t\tpChn->nPos = pChn->nLength - 1;\n\t\t\tpChn->nPosLo = 0xFFFF;\n\t\t}\n\t\tpChn->dwFlags |= CHN_PINGPONGFLAG;\n\t\tbreak;\n\t}\n}\n\n\nvoid CSoundFile::ProcessMidiMacro(UINT nChn, LPCSTR pszMidiMacro, UINT param)\n//---------------------------------------------------------------------------\n{\n\tMODCHANNEL *pChn = &Chn[nChn];\n\tDWORD dwMacro = (*((LPDWORD)pszMidiMacro)) & 0x7F5F7F5F;\n\t// Not Internal Device ?\n\tif (dwMacro != 0x30463046 && dwMacro != 0x31463046)\n\t{\n\t\tUINT pos = 0, nNib = 0, nBytes = 0;\n\t\tDWORD dwMidiCode = 0, dwByteCode = 0;\n\t\twhile (pos+6 <= 32)\n\t\t{\n\t\t\tCHAR cData = pszMidiMacro[pos++];\n\t\t\tif (!cData) break;\n\t\t\tif ((cData >= '0') && (cData <= '9')) { dwByteCode = (dwByteCode<<4) | (cData-'0'); nNib++; } else\n\t\t\tif ((cData >= 'A') && (cData <= 'F')) { dwByteCode = (dwByteCode<<4) | (cData-'A'+10); nNib++; } else\n\t\t\tif ((cData >= 'a') && (cData <= 'f')) { dwByteCode = (dwByteCode<<4) | (cData-'a'+10); nNib++; } else\n\t\t\tif ((cData == 'z') || (cData == 'Z')) { dwByteCode = param & 0x7f; nNib = 2; } else\n\t\t\tif ((cData == 'x') || (cData == 'X')) { dwByteCode = param & 0x70; nNib = 2; } else\n\t\t\tif ((cData == 'y') || (cData == 'Y')) { dwByteCode = (param & 0x0f)<<3; nNib = 2; } else\n\t\t\tif (nNib >= 2)\n\t\t\t{\n\t\t\t\tnNib = 0;\n\t\t\t\tdwMidiCode |= dwByteCode << (nBytes*8);\n\t\t\t\tdwByteCode = 0;\n\t\t\t\tnBytes++;\n\t\t\t\tif (nBytes >= 3)\n\t\t\t\t{\n\t\t\t\t\tUINT nMasterCh = (nChn < m_nChannels) ? nChn+1 : pChn->nMasterChn;\n\t\t\t\t\tif ((nMasterCh) && (nMasterCh <= m_nChannels))\n\t\t\t\t\t{\n\t\t\t\t\t\tUINT nPlug = ChnSettings[nMasterCh-1].nMixPlugin;\n\t\t\t\t\t\tif ((nPlug) && (nPlug <= MAX_MIXPLUGINS))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tIMixPlugin *pPlugin = m_MixPlugins[nPlug-1].pMixPlugin;\n\t\t\t\t\t\t\tif ((pPlugin) && (m_MixPlugins[nPlug-1].pMixState))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tpPlugin->MidiSend(dwMidiCode);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tnBytes = 0;\n\t\t\t\t\tdwMidiCode = 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t\treturn;\n\t}\n\t// Internal device\n\tpszMidiMacro += 4;\n\t// Filter ?\n\tif (pszMidiMacro[0] == '0')\n\t{\n\t\tCHAR cData1 = pszMidiMacro[2];\n\t\tDWORD dwParam = 0;\n\t\tif ((cData1 == 'z') || (cData1 == 'Z'))\n\t\t{\n\t\t\tdwParam = param;\n\t\t} else\n\t\t{\n\t\t\tCHAR cData2 = pszMidiMacro[3];\n\t\t\tif ((cData1 >= '0') && (cData1 <= '9')) dwParam += (cData1 - '0') << 4; else\n\t\t\tif ((cData1 >= 'A') && (cData1 <= 'F')) dwParam += (cData1 - 'A' + 0x0A) << 4;\n\t\t\tif ((cData2 >= '0') && (cData2 <= '9')) dwParam += (cData2 - '0'); else\n\t\t\tif ((cData2 >= 'A') && (cData2 <= 'F')) dwParam += (cData2 - 'A' + 0x0A);\n\t\t}\n\t\tswitch(pszMidiMacro[1])\n\t\t{\n\t\t// F0.F0.00.xx: Set CutOff\n\t\tcase '0':\n\t\t\t{\n\t\t\t\tint oldcutoff = pChn->nCutOff;\n\t\t\t\tif (dwParam < 0x80) pChn->nCutOff = (BYTE)dwParam;\n#ifndef NO_FILTER\n\t\t\t\toldcutoff -= pChn->nCutOff;\n\n\t\t\t\tif (oldcutoff < 0) oldcutoff = -oldcutoff;\n\t\t\t\tif ((pChn->nVolume > 0) || (oldcutoff < 0x10)\n\t\t\t\t || (!(pChn->dwFlags & CHN_FILTER)) || (!(pChn->nLeftVol|pChn->nRightVol)))\n\t\t\t\t\tSetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) ? FALSE : TRUE);\n#endif // NO_FILTER\n\t\t\t}\n\t\t\tbreak;\n\n\t\t// F0.F0.01.xx: Set Resonance\n\t\tcase '1':\n\t\t\tif (dwParam < 0x80) pChn->nResonance = (BYTE)dwParam;\n#ifndef NO_FILTER\n\t\t\tSetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) ? FALSE : TRUE);\n#endif // NO_FILTER\n\n\t\t\tbreak;\n\t\t}\n\n\t}\n}\n\n\nvoid CSoundFile::RetrigNote(UINT nChn, UINT param)\n//------------------------------------------------\n{\n\t// Retrig: bit 8 is set if it's the new XM retrig\n\tMODCHANNEL *pChn = &Chn[nChn];\n\tUINT nRetrigSpeed = param & 0x0F;\n\tUINT nRetrigCount = pChn->nRetrigCount;\n\tBOOL bDoRetrig = FALSE;\n\n\tif (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))\n\t{\n\t\tif (!nRetrigSpeed) nRetrigSpeed = 1;\n\t\tif ((nRetrigCount) && (!(nRetrigCount % nRetrigSpeed))) bDoRetrig = TRUE;\n\t\tnRetrigCount++;\n\t} else\n\t{\n\t\tUINT realspeed = nRetrigSpeed;\n\t\tif ((param & 0x100) && (pChn->nRowVolCmd == VOLCMD_VOLUME) && (pChn->nRowParam & 0xF0)) realspeed++;\n\t\tif ((m_nTickCount) || (param & 0x100))\n\t\t{\n\t\t\tif (!realspeed) realspeed = 1;\n\t\t\tif ((!(param & 0x100)) && (m_nMusicSpeed) && (!(m_nTickCount % realspeed))) bDoRetrig = TRUE;\n\t\t\tnRetrigCount++;\n\t\t} else if (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) nRetrigCount = 0;\n\t\tif (nRetrigCount >= realspeed)\n\t\t{\n\t\t\tif ((m_nTickCount) || ((param & 0x100) && (!pChn->nRowNote))) bDoRetrig = TRUE;\n\t\t}\n\t}\n\tif (bDoRetrig)\n\t{\n\t\tUINT dv = (param >> 4) & 0x0F;\n\t\tif (dv)\n\t\t{\n\t\t\tint vol = pChn->nVolume;\n\t\t\tif (retrigTable1[dv])\n\t\t\t\tvol = (vol * retrigTable1[dv]) >> 4;\n\t\t\telse\n\t\t\t\tvol += ((int)retrigTable2[dv]) << 2;\n\t\t\tif (vol < 0) vol = 0;\n\t\t\tif (vol > 256) vol = 256;\n\t\t\tpChn->nVolume = vol;\n\t\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t\t}\n\t\tUINT nNote = pChn->nNewNote;\n\t\tLONG nOldPeriod = pChn->nPeriod;\n\t\tif ((nNote) && (nNote <= NOTE_MAX) && (pChn->nLength)) CheckNNA(nChn, 0, nNote, TRUE);\n\t\tBOOL bResetEnv = FALSE;\n\t\tif (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))\n\t\t{\n\t\t\tif ((pChn->nRowInstr) && (param < 0x100)) { InstrumentChange(pChn, pChn->nRowInstr, FALSE, FALSE); bResetEnv = TRUE; }\n\t\t\tif (param < 0x100) bResetEnv = TRUE;\n\t\t}\n\t\tNoteChange(nChn, nNote, FALSE, bResetEnv);\n\t\tif ((m_nType & MOD_TYPE_IT) && (!pChn->nRowNote) && (nOldPeriod)) pChn->nPeriod = nOldPeriod;\n\t\tif (!(m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT))) nRetrigCount = 0;\n\t}\n\tpChn->nRetrigCount = (BYTE)nRetrigCount;\n}\n\n\nvoid CSoundFile::DoFreqSlide(MODCHANNEL *pChn, LONG nFreqSlide)\n//-------------------------------------------------------------\n{\n\t// IT Linear slides\n\tif (!pChn->nPeriod) return;\n\tif ((m_dwSongFlags & SONG_LINEARSLIDES) && (!(m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))))\n\t{\n\t\tif (nFreqSlide < 0)\n\t\t{\n\t\t\tUINT n = (- nFreqSlide) >> 2;\n\t\t\tif (n > 255) n = 255;\n\t\t\tpChn->nPeriod = _muldivr(pChn->nPeriod, LinearSlideDownTable[n], 65536);\n\t\t} else\n\t\t{\n\t\t\tUINT n = (nFreqSlide) >> 2;\n\n\t\t\tif (n > 255) n = 255;\n\t\t\tpChn->nPeriod = _muldivr(pChn->nPeriod, LinearSlideUpTable[n], 65536);\n\t\t}\n\t} else\n\t{\n\t\tpChn->nPeriod += nFreqSlide;\n\t}\n\tif (pChn->nPeriod < 1)\n\t{\n\t\tpChn->nPeriod = 1;\n\t\tif (m_nType & MOD_TYPE_IT)\n\t\t{\n\t\t\tpChn->dwFlags |= CHN_NOTEFADE;\n\t\t\tpChn->nFadeOutVol = 0;\n\t\t}\n\t}\n}\n\n\nvoid CSoundFile::NoteCut(UINT nChn, UINT nTick)\n//---------------------------------------------\n{\n\tif (m_nTickCount == nTick)\n\t{\n\t\tMODCHANNEL *pChn = &Chn[nChn];\n\t\t// if (m_nInstruments) KeyOff(pChn); ?\n\t\tpChn->nVolume = 0;\n\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t}\n}\n\n\nvoid CSoundFile::KeyOff(UINT nChn)\n//--------------------------------\n{\n\tMODCHANNEL *pChn = &Chn[nChn];\n\tBOOL bKeyOn = (pChn->dwFlags & CHN_KEYOFF) ? FALSE : TRUE;\n\tpChn->dwFlags |= CHN_KEYOFF;\n\t//if ((!pChn->pHeader) || (!(pChn->dwFlags & CHN_VOLENV)))\n\tif ((pChn->pHeader) && (!(pChn->dwFlags & CHN_VOLENV)))\n\t{\n\t\tpChn->dwFlags |= CHN_NOTEFADE;\n\t}\n\tif (!pChn->nLength) return;\n\tif ((pChn->dwFlags & CHN_SUSTAINLOOP) && (pChn->pInstrument) && (bKeyOn))\n\t{\n\t\tMODINSTRUMENT *psmp = pChn->pInstrument;\n\t\tif (psmp->uFlags & CHN_LOOP)\n\t\t{\n\t\t\tif (psmp->uFlags & CHN_PINGPONGLOOP)\n\t\t\t\tpChn->dwFlags |= CHN_PINGPONGLOOP;\n\t\t\telse\n\t\t\t\tpChn->dwFlags &= ~(CHN_PINGPONGLOOP|CHN_PINGPONGFLAG);\n\t\t\tpChn->dwFlags |= CHN_LOOP;\n\t\t\tpChn->nLength = psmp->nLength;\n\t\t\tpChn->nLoopStart = psmp->nLoopStart;\n\t\t\tpChn->nLoopEnd = psmp->nLoopEnd;\n\t\t\tif (pChn->nLength > pChn->nLoopEnd) pChn->nLength = pChn->nLoopEnd;\n\t\t} else\n\t\t{\n\t\t\tpChn->dwFlags &= ~(CHN_LOOP|CHN_PINGPONGLOOP|CHN_PINGPONGFLAG);\n\t\t\tpChn->nLength = psmp->nLength;\n\t\t}\n\t}\n\tif (pChn->pHeader)\n\t{\n\t\tINSTRUMENTHEADER *penv = pChn->pHeader;\n\t\tif (((penv->dwFlags & ENV_VOLLOOP) || (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))) && (penv->nFadeOut))\n\t\t\tpChn->dwFlags |= CHN_NOTEFADE;\n\t}\n}\n\n\n//////////////////////////////////////////////////////////\n// CSoundFile: Global Effects\n\n\nvoid CSoundFile::SetSpeed(UINT param)\n//-----------------------------------\n{\n\tUINT max = (m_nType == MOD_TYPE_IT) ? 256 : 128;\n\t// Modplug Tracker and Mod-Plugin don't do this check\n#ifndef MODPLUG_TRACKER\n#ifndef MODPLUG_FASTSOUNDLIB\n\t// Big Hack!!!\n\tif ((!param) || (param >= 0x80) || ((m_nType & (MOD_TYPE_MOD|MOD_TYPE_XM|MOD_TYPE_MT2)) && (param >= 0x1E)))\n\t{\n\t\tif (IsSongFinished(m_nCurrentPattern, m_nRow+1))\n\t\t{\n\t\t\tGlobalFadeSong(1000);\n\t\t}\n\t}\n#endif // MODPLUG_FASTSOUNDLIB\n#endif // MODPLUG_TRACKER\n\tif ((m_nType & MOD_TYPE_S3M) && (param > 0x80)) param -= 0x80;\n\tif ((param) && (param <= max)) m_nMusicSpeed = param;\n}\n\n\nvoid CSoundFile::SetTempo(UINT param)\n//-----------------------------------\n{\n\tif (param < 0x20)\n\t{\n\t\t// Tempo Slide\n\t\tif ((param & 0xF0) == 0x10)\n\t\t{\n\t\t\tm_nMusicTempo += (param & 0x0F) * 2;\n\t\t\tif (m_nMusicTempo > 255) m_nMusicTempo = 255;\n\t\t} else\n\t\t{\n\t\t\tm_nMusicTempo -= (param & 0x0F) * 2;\n\t\t\tif ((LONG)m_nMusicTempo < 32) m_nMusicTempo = 32;\n\t\t}\n\t} else\n\t{\n\t\tm_nMusicTempo = param;\n\t}\n}\n\n\nint CSoundFile::PatternLoop(MODCHANNEL *pChn, UINT param)\n//-------------------------------------------------------\n{\n\tif (param)\n\t{\n\t\tif (pChn->nPatternLoopCount)\n\t\t{\n\t\t\tpChn->nPatternLoopCount--;\n\t\t\tif (!pChn->nPatternLoopCount) return -1;\n\t\t} else\n\t\t{\n\t\t\tMODCHANNEL *p = Chn;\n\t\t\tfor (UINT i=0; i<m_nChannels; i++, p++) if (p != pChn)\n\t\t\t{\n\t\t\t\t// Loop already done\n\t\t\t\tif (p->nPatternLoopCount) return -1;\n\t\t\t}\n\t\t\tpChn->nPatternLoopCount = param;\n\t\t}\n\t\treturn pChn->nPatternLoop;\n\t} else\n\t{\n\t\tpChn->nPatternLoop = m_nRow;\n\t\tif (m_nType & MOD_TYPE_XM) m_nNextStartRow = m_nRow;\n\t}\n\treturn -1;\n}\n\n\nvoid CSoundFile::GlobalVolSlide(UINT param)\n//-----------------------------------------\n{\n\tLONG nGlbSlide = 0;\n\tif (param) m_nOldGlbVolSlide = param; else param = m_nOldGlbVolSlide;\n\tif (((param & 0x0F) == 0x0F) && (param & 0xF0))\n\t{\n\t\tif (m_dwSongFlags & SONG_FIRSTTICK) nGlbSlide = (param >> 4) * 2;\n\t} else\n\tif (((param & 0xF0) == 0xF0) && (param & 0x0F))\n\t{\n\t\tif (m_dwSongFlags & SONG_FIRSTTICK) nGlbSlide = - (int)((param & 0x0F) * 2);\n\t} else\n\t{\n\t\tif (!(m_dwSongFlags & SONG_FIRSTTICK))\n\t\t{\n\t\t\tif (param & 0xF0) nGlbSlide = (int)((param & 0xF0) >> 4) * 2;\n\t\t\telse nGlbSlide = -(int)((param & 0x0F) * 2);\n\t\t}\n\t}\n\tif (nGlbSlide)\n\t{\n\t\tif (m_nType != MOD_TYPE_IT) nGlbSlide *= 2;\n\t\tnGlbSlide += m_nGlobalVolume;\n\t\tif (nGlbSlide < 0) nGlbSlide = 0;\n\t\tif (nGlbSlide > 256) nGlbSlide = 256;\n\t\tm_nGlobalVolume = nGlbSlide;\n\t}\n}\n\n\nDWORD CSoundFile::IsSongFinished(UINT nStartOrder, UINT nStartRow) const\n//----------------------------------------------------------------------\n{\n\tUINT nOrd;\n\n\tfor (nOrd=nStartOrder; nOrd<MAX_ORDERS; nOrd++)\n\t{\n\t\tUINT nPat = Order[nOrd];\n\t\tif (nPat != 0xFE)\n\t\t{\n\t\t\tMODCOMMAND *p;\n\n\t\t\tif (nPat >= MAX_PATTERNS) break;\n\t\t\tp = Patterns[nPat];\n\t\t\tif (p)\n\t\t\t{\n\t\t\t\tUINT len = PatternSize[nPat] * m_nChannels;\n\t\t\t\tUINT pos = (nOrd == nStartOrder) ? nStartRow : 0;\n\t\t\t\tpos *= m_nChannels;\n\t\t\t\twhile (pos < len)\n\t\t\t\t{\n\t\t\t\t\tUINT cmd;\n\t\t\t\t\tif ((p[pos].note) || (p[pos].volcmd)) return 0;\n\t\t\t\t\tcmd = p[pos].command;\n\t\t\t\t\tif (cmd == CMD_MODCMDEX)\n\t\t\t\t\t{\n\t\t\t\t\t\tUINT cmdex = p[pos].param & 0xF0;\n\t\t\t\t\t\tif ((!cmdex) || (cmdex == 0x60) || (cmdex == 0xE0) || (cmdex == 0xF0)) cmd = 0;\n\t\t\t\t\t}\n\t\t\t\t\tif ((cmd) && (cmd != CMD_SPEED) && (cmd != CMD_TEMPO)) return 0;\n\t\t\t\t\tpos++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn (nOrd < MAX_ORDERS) ? nOrd : MAX_ORDERS-1;\n}\n\n\nBOOL CSoundFile::IsValidBackwardJump(UINT nStartOrder, UINT nStartRow, UINT nJumpOrder, UINT nJumpRow) const\n//----------------------------------------------------------------------------------------------------------\n{\n\twhile ((nJumpOrder < MAX_PATTERNS) && (Order[nJumpOrder] == 0xFE)) nJumpOrder++;\n\tif ((nStartOrder >= MAX_PATTERNS) || (nJumpOrder >= MAX_PATTERNS)) return FALSE;\n\t// Treat only case with jumps in the same pattern\n\tif (nJumpOrder > nStartOrder) return TRUE;\n\tif ((nJumpOrder < nStartOrder) || (nJumpRow >= PatternSize[nStartOrder])\n\t || (!Patterns[nStartOrder]) || (nStartRow >= 256) || (nJumpRow >= 256)) return FALSE;\n\t// See if the pattern is being played backward\n\tBYTE row_hist[256];\n\tmemset(row_hist, 0, sizeof(row_hist));\n\tUINT nRows = PatternSize[nStartOrder], row = nJumpRow;\n\tif (nRows > 256) nRows = 256;\n\trow_hist[nStartRow] = TRUE;\n\twhile ((row < 256) && (!row_hist[row]))\n\t{\n\t\tif (row >= nRows) return TRUE;\n\t\trow_hist[row] = TRUE;\n\t\tMODCOMMAND *p = Patterns[nStartOrder] + row * m_nChannels;\n\t\trow++;\n\t\tint breakrow = -1, posjump = 0;\n\t\tfor (UINT i=0; i<m_nChannels; i++, p++)\n\t\t{\n\t\t\tif (p->command == CMD_POSITIONJUMP)\n\t\t\t{\n\t\t\t\tif (p->param < nStartOrder) return FALSE;\n\t\t\t\tif (p->param > nStartOrder) return TRUE;\n\t\t\t\tposjump = TRUE;\n\t\t\t} else\n\t\t\tif (p->command == CMD_PATTERNBREAK)\n\t\t\t{\n\t\t\t\tbreakrow = p->param;\n\t\t\t}\n\t\t}\n\t\tif (breakrow >= 0)\n\t\t{\n\t\t\tif (!posjump) return TRUE;\n\t\t\trow = breakrow;\n\t\t}\n\t\tif (row >= nRows) return TRUE;\n\t}\n\treturn FALSE;\n}\n\n\n//////////////////////////////////////////////////////\n// Note/Period/Frequency functions\n\nUINT CSoundFile::GetNoteFromPeriod(UINT period) const\n//---------------------------------------------------\n{\n\tif (!period) return 0;\n\tif (m_nType & (MOD_TYPE_MED|MOD_TYPE_MOD|MOD_TYPE_MTM|MOD_TYPE_669|MOD_TYPE_OKT|MOD_TYPE_AMF0))\n\t{\n\t\tperiod >>= 2;\n\t\tfor (UINT i=0; i<6*12; i++)\n\t\t{\n\t\t\tif (period >= ProTrackerPeriodTable[i])\n\t\t\t{\n\t\t\t\tif ((period != ProTrackerPeriodTable[i]) && (i))\n\t\t\t\t{\n\t\t\t\t\tUINT p1 = ProTrackerPeriodTable[i-1];\n\t\t\t\t\tUINT p2 = ProTrackerPeriodTable[i];\n\t\t\t\t\tif (p1 - period < (period - p2)) return i+36;\n\t\t\t\t}\n\t\t\t\treturn i+1+36;\n\t\t\t}\n\t\t}\n\t\treturn 6*12+36;\n\t} else\n\t{\n\t\tfor (UINT i=1; i<NOTE_MAX; i++)\n\t\t{\n\t\t\tLONG n = GetPeriodFromNote(i, 0, 0);\n\t\t\tif ((n > 0) && (n <= (LONG)period)) return i;\n\t\t}\n\t\treturn NOTE_MAX;\n\t}\n}\n\n\n\nUINT CSoundFile::GetPeriodFromNote(UINT note, int nFineTune, UINT nC4Speed) const\n//-------------------------------------------------------------------------------\n{\n\tif ((!note) || (note > 0xF0)) return 0;\n\tif (m_nType & (MOD_TYPE_IT|MOD_TYPE_S3M|MOD_TYPE_STM|MOD_TYPE_MDL|MOD_TYPE_ULT|MOD_TYPE_WAV\n\t\t\t\t|MOD_TYPE_FAR|MOD_TYPE_DMF|MOD_TYPE_PTM|MOD_TYPE_AMS|MOD_TYPE_DBM|MOD_TYPE_AMF|MOD_TYPE_PSM))\n\t{\n\t\tnote--;\n\t\tif (m_dwSongFlags & SONG_LINEARSLIDES)\n\t\t{\n\t\t\treturn (FreqS3MTable[note % 12] << 5) >> (note / 12);\n\t\t} else\n\t\t{\n\t\t\tint divider;\n\t\t\tif (!nC4Speed) nC4Speed = 8363;\n\t\t\t// if C4Speed is large, then up shifting may produce a zero divider\n\t\t\tdivider = nC4Speed << (note / 12);\n\t\t\tif (!divider) divider = 1e6;\n\t\t\treturn _muldiv(8363, (FreqS3MTable[note % 12] << 5), divider);\n\t\t}\n\t} else\n\tif (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))\n\t{\n\t\tif (note < 13) note = 13;\n\t\tnote -= 13;\n\t\tif (m_dwSongFlags & SONG_LINEARSLIDES)\n\t\t{\n\t\t\tLONG l = ((NOTE_MAX - note) << 6) - (nFineTune / 2);\n\t\t\tif (l < 1) l = 1;\n\t\t\treturn (UINT)l;\n\t\t} else\n\t\t{\n\t\t\tint finetune = nFineTune;\n\t\t\tUINT rnote = (note % 12) << 3;\n\t\t\tUINT roct = note / 12;\n\t\t\tint rfine = finetune / 16;\n\t\t\tint i = rnote + rfine + 8;\n\t\t\tif (i < 0) i = 0;\n\t\t\tif (i >= 104) i = 103;\n\t\t\tUINT per1 = XMPeriodTable[i];\n\t\t\tif ( finetune < 0 )\n\t\t\t{\n\t\t\t\trfine--;\n\t\t\t\tfinetune = -finetune;\n\t\t\t} else rfine++;\n\t\t\ti = rnote+rfine+8;\n\t\t\tif (i < 0) i = 0;\n\t\t\tif (i >= 104) i = 103;\n\t\t\tUINT per2 = XMPeriodTable[i];\n\t\t\trfine = finetune & 0x0F;\n\t\t\tper1 *= 16-rfine;\n\t\t\tper2 *= rfine;\n\t\t\treturn ((per1 + per2) << 1) >> roct;\n\t\t}\n\t} else\n\t{\n\t\tnote--;\n\t\tnFineTune = XM2MODFineTune(nFineTune);\n\t\tif ((nFineTune) || (note < 36) || (note >= 36+6*12))\n\t\t\treturn (ProTrackerTunedPeriods[nFineTune*12 + note % 12] << 5) >> (note / 12);\n\t\telse\n\t\t\treturn (ProTrackerPeriodTable[note-36] << 2);\n\t}\n}\n\n\nUINT CSoundFile::GetFreqFromPeriod(UINT period, UINT nC4Speed, int nPeriodFrac) const\n//-----------------------------------------------------------------------------------\n{\n\tif (!period) return 0;\n\tif (m_nType & (MOD_TYPE_MED|MOD_TYPE_MOD|MOD_TYPE_MTM|MOD_TYPE_669|MOD_TYPE_OKT|MOD_TYPE_AMF0))\n\t{\n\t\treturn (3546895L*4) / period;\n\t} else\n\tif (m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2))\n\t{\n\t\tif (m_dwSongFlags & SONG_LINEARSLIDES)\n\t\t\treturn XMLinearTable[period % 768] >> (period / 768);\n\t\telse\n\t\t\treturn 8363 * 1712L / period;\n\t} else\n\t{\n\t\tif (m_dwSongFlags & SONG_LINEARSLIDES)\n\t\t{\n\t\t\tif (!nC4Speed) nC4Speed = 8363;\n\t\t\treturn _muldiv(nC4Speed, 1712L << 8, (period << 8)+nPeriodFrac);\n\t\t} else\n\t\t{\n\t\t\treturn _muldiv(8363, 1712L << 8, (period << 8)+nPeriodFrac);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "contrib/libmodplug/src/sndfile.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>,\n *          Adam Goode       <adam@evdebs.org> (endian and char fixes for PPC)\n*/\n\n#include <math.h> //for GCCFIX\n#include \"libmodplug/stdafx.h\"\n#include \"libmodplug/sndfile.h\"\n\n#define MMCMP_SUPPORT\n\n#ifdef MMCMP_SUPPORT\nextern BOOL MMCMP_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength);\n#endif\n\n// External decompressors\n#if 0\nextern void AMSUnpack(const char *psrc, UINT inputlen, char *pdest, UINT dmax, char packcharacter);\nextern WORD MDLReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n);\nextern int DMFUnpack(LPBYTE psample, LPBYTE ibuf, LPBYTE ibufmax, UINT maxlen);\n#endif\nextern DWORD ITReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n);\nextern void ITUnpack8Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215);\nextern void ITUnpack16Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215);\n\n\n#define MAX_PACK_TABLES\t\t3\n\n\n// Compression table\nstatic const signed char UnpackTable[MAX_PACK_TABLES][16] =\n//--------------------------------------------\n{\n\t// CPU-generated dynamic table\n\t{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},\n\t// u-Law table\n\t{0, 1, 2, 4, 8, 16, 32, 64,\n\t-1, -2, -4, -8, -16, -32, -48, -64},\n\t// Linear table\n\t{0, 1, 2, 3, 5, 7, 12, 19,\n\t-1, -2, -3, -5, -7, -12, -19, -31}\n};\n\n\n//////////////////////////////////////////////////////////\n// CSoundFile\n\nCSoundFile::CSoundFile()\n//----------------------\n{\n\tm_nType = MOD_TYPE_NONE;\n\tm_dwSongFlags = 0;\n\tm_nChannels = 0;\n\tm_nMixChannels = 0;\n\tm_nSamples = 0;\n\tm_nInstruments = 0;\n\tm_nPatternNames = 0;\n\tm_lpszPatternNames = NULL;\n\tm_lpszSongComments = NULL;\n\tm_nFreqFactor = m_nTempoFactor = 128;\n\tm_nMasterVolume = 128;\n\tm_nMinPeriod = 0x20;\n\tm_nMaxPeriod = 0x7FFF;\n\tm_nRepeatCount = 0;\n\tmemset(Chn, 0, sizeof(Chn));\n\tmemset(ChnMix, 0, sizeof(ChnMix));\n\tmemset(Ins, 0, sizeof(Ins));\n\tmemset(ChnSettings, 0, sizeof(ChnSettings));\n\tmemset(Headers, 0, sizeof(Headers));\n\tmemset(Order, 0xFF, sizeof(Order));\n\tmemset(Patterns, 0, sizeof(Patterns));\n\tmemset(m_szNames, 0, sizeof(m_szNames));\n\tmemset(m_MixPlugins, 0, sizeof(m_MixPlugins));\n}\n\n\nCSoundFile::~CSoundFile()\n//-----------------------\n{\n\tDestroy();\n}\n\n\nBOOL CSoundFile::Create(LPCBYTE lpStream, DWORD dwMemLength)\n//----------------------------------------------------------\n{\n\tint i;\n\n\tm_nType = MOD_TYPE_NONE;\n\tm_dwSongFlags = 0;\n\tm_nChannels = 0;\n\tm_nMixChannels = 0;\n\tm_nSamples = 0;\n\tm_nInstruments = 0;\n\tm_nFreqFactor = m_nTempoFactor = 128;\n\tm_nMasterVolume = 128;\n\tm_nDefaultGlobalVolume = 256;\n\tm_nGlobalVolume = 256;\n\tm_nOldGlbVolSlide = 0;\n\tm_nDefaultSpeed = 6;\n\tm_nDefaultTempo = 125;\n\tm_nPatternDelay = 0;\n\tm_nFrameDelay = 0;\n\tm_nNextRow = 0;\n\tm_nRow = 0;\n\tm_nNextStartRow = 0;\n\tm_nPattern = 0;\n\tm_nCurrentPattern = 0;\n\tm_nNextPattern = 0;\n\tm_nRestartPos = 0;\n\tm_nMinPeriod = 16;\n\tm_nMaxPeriod = 32767;\n\tm_nSongPreAmp = 0x30;\n\tm_nPatternNames = 0;\n\tm_nMaxOrderPosition = 0;\n\tm_lpszPatternNames = NULL;\n\tm_lpszSongComments = NULL;\n\tmemset(Ins, 0, sizeof(Ins));\n\tmemset(ChnMix, 0, sizeof(ChnMix));\n\tmemset(Chn, 0, sizeof(Chn));\n\tmemset(Headers, 0, sizeof(Headers));\n\tmemset(Order, 0xFF, sizeof(Order));\n\tmemset(Patterns, 0, sizeof(Patterns));\n\tmemset(m_szNames, 0, sizeof(m_szNames));\n\tmemset(m_MixPlugins, 0, sizeof(m_MixPlugins));\n\tResetMidiCfg();\n\tfor (UINT npt=0; npt<MAX_PATTERNS; npt++) PatternSize[npt] = 64;\n\tfor (UINT nch=0; nch<MAX_BASECHANNELS; nch++)\n\t{\n\t\tChnSettings[nch].nPan = 128;\n\t\tChnSettings[nch].nVolume = 64;\n\t\tChnSettings[nch].dwFlags = 0;\n\t\tChnSettings[nch].szName[0] = 0;\n\t}\n\tif (lpStream)\n\t{\n#ifdef MMCMP_SUPPORT\n\t\tBOOL bMMCmp = MMCMP_Unpack(&lpStream, &dwMemLength);\n#endif\n\t\tif ((!ReadXM(lpStream, dwMemLength))\n\t\t && (!ReadS3M(lpStream, dwMemLength))\n\t\t && (!ReadIT(lpStream, dwMemLength))\n\t\t && (!ReadWav(lpStream, dwMemLength))\n#ifndef MODPLUG_BASIC_SUPPORT\n/* Sequencer File Format Support */\n\t\t //&& (!ReadABC(lpStream, dwMemLength))\n\t\t //&& (!ReadMID(lpStream, dwMemLength))\n\t\t //&& (!ReadPAT(lpStream, dwMemLength))\n\t\t && (!ReadSTM(lpStream, dwMemLength))\n\t\t && (!ReadMed(lpStream, dwMemLength))\n\t\t && (!ReadMTM(lpStream, dwMemLength))\n\t\t //&& (!ReadMDL(lpStream, dwMemLength))\n\t\t //&& (!ReadDBM(lpStream, dwMemLength))\n\t\t && (!Read669(lpStream, dwMemLength))\n\t\t && (!ReadFAR(lpStream, dwMemLength))\n\t\t //&& (!ReadAMS(lpStream, dwMemLength))\n\t\t && (!ReadOKT(lpStream, dwMemLength))\n\t\t //&& (!ReadPTM(lpStream, dwMemLength))\n\t\t && (!ReadUlt(lpStream, dwMemLength))\n\t\t //&& (!ReadDMF(lpStream, dwMemLength))\n\t\t && (!ReadDSM(lpStream, dwMemLength))\n\t\t && (!ReadGDM(lpStream, dwMemLength))\n\t\t //&& (!ReadUMX(lpStream, dwMemLength))\n\t\t && (!ReadAMF(lpStream, dwMemLength))\n\t\t //&& (!ReadPSM(lpStream, dwMemLength))\n\t\t //&& (!ReadMT2(lpStream, dwMemLength))\n#endif // MODPLUG_BASIC_SUPPORT\n\t\t && (!ReadMod(lpStream, dwMemLength))) m_nType = MOD_TYPE_NONE;\n#ifdef MMCMP_SUPPORT\n\t\tif (bMMCmp)\n\t\t{\n\t\t\tGlobalFreePtr(lpStream);\n\t\t\tlpStream = NULL;\n\t\t}\n#endif\n\t}\n\t// Adjust song names\n\tfor (i=0; i<MAX_SAMPLES; i++)\n\t{\n\t\tLPSTR p = m_szNames[i];\n\t\tint j = 31;\n\t\tp[j] = 0;\n\t\twhile ((j>=0) && (p[j]<=' ')) p[j--] = 0;\n\t\twhile (j>=0)\n\t\t{\n\t\t\tif (((BYTE)p[j]) < ' ') p[j] = ' ';\n\t\t\tj--;\n\t\t}\n\t}\n\t// Adjust channels\n\tfor (i=0; i<MAX_BASECHANNELS; i++)\n\t{\n\t\tif (ChnSettings[i].nVolume > 64) ChnSettings[i].nVolume = 64;\n\t\tif (ChnSettings[i].nPan > 256) ChnSettings[i].nPan = 128;\n\t\tChn[i].nPan = ChnSettings[i].nPan;\n\t\tChn[i].nGlobalVol = ChnSettings[i].nVolume;\n\t\tChn[i].dwFlags = ChnSettings[i].dwFlags;\n\t\tChn[i].nVolume = 256;\n\t\tChn[i].nCutOff = 0x7F;\n\t}\n\t// Checking instruments\n\tMODINSTRUMENT *pins = Ins;\n\n\tfor (i=0; i<MAX_INSTRUMENTS; i++, pins++)\n\t{\n\t\tif (pins->pSample)\n\t\t{\n\t\t\tif (pins->nLoopEnd > pins->nLength) pins->nLoopEnd = pins->nLength;\n\t\t\tif (pins->nLoopStart + 3 >= pins->nLoopEnd)\n\t\t\t{\n\t\t\t\tpins->nLoopStart = 0;\n\t\t\t\tpins->nLoopEnd = 0;\n\t\t\t}\n\t\t\tif (pins->nSustainEnd > pins->nLength) pins->nSustainEnd = pins->nLength;\n\t\t\tif (pins->nSustainStart + 3 >= pins->nSustainEnd)\n\t\t\t{\n\t\t\t\tpins->nSustainStart = 0;\n\t\t\t\tpins->nSustainEnd = 0;\n\t\t\t}\n\t\t} else\n\t\t{\n\t\t\tpins->nLength = 0;\n\t\t\tpins->nLoopStart = 0;\n\t\t\tpins->nLoopEnd = 0;\n\t\t\tpins->nSustainStart = 0;\n\t\t\tpins->nSustainEnd = 0;\n\t\t}\n\t\tif (!pins->nLoopEnd) pins->uFlags &= ~CHN_LOOP;\n\t\tif (!pins->nSustainEnd) pins->uFlags &= ~CHN_SUSTAINLOOP;\n\t\tif (pins->nGlobalVol > 64) pins->nGlobalVol = 64;\n\t}\n\t// Check invalid instruments\n\twhile ((m_nInstruments > 0) && (!Headers[m_nInstruments]))\n\t\tm_nInstruments--;\n\t// Set default values\n\tif (m_nSongPreAmp < 0x20) m_nSongPreAmp = 0x20;\n\tif (m_nDefaultTempo < 32) m_nDefaultTempo = 125;\n\tif (!m_nDefaultSpeed) m_nDefaultSpeed = 6;\n\tm_nMusicSpeed = m_nDefaultSpeed;\n\tm_nMusicTempo = m_nDefaultTempo;\n\tm_nGlobalVolume = m_nDefaultGlobalVolume;\n\tm_nNextPattern = 0;\n\tm_nCurrentPattern = 0;\n\tm_nPattern = 0;\n\tm_nBufferCount = 0;\n\tm_nTickCount = m_nMusicSpeed;\n\tm_nNextRow = 0;\n\tm_nRow = 0;\n\tm_nNextStartRow = 0;\n\tif ((m_nRestartPos >= MAX_ORDERS) || (Order[m_nRestartPos] >= MAX_PATTERNS)) m_nRestartPos = 0;\n\t// Load plugins\n\tif (gpMixPluginCreateProc)\n\t{\n\t\tfor (UINT iPlug=0; iPlug<MAX_MIXPLUGINS; iPlug++)\n\t\t{\n\t\t\tif ((m_MixPlugins[iPlug].Info.dwPluginId1)\n\t\t\t || (m_MixPlugins[iPlug].Info.dwPluginId2))\n\t\t\t{\n\t\t\t\tgpMixPluginCreateProc(&m_MixPlugins[iPlug]);\n\t\t\t\tif (m_MixPlugins[iPlug].pMixPlugin)\n\t\t\t\t{\n\t\t\t\t\tm_MixPlugins[iPlug].pMixPlugin->RestoreAllParameters();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif (m_nType)\n\t{\n\t\tUINT maxpreamp = 0x10+(m_nChannels*8);\n\t\tif (maxpreamp > 100) maxpreamp = 100;\n\t\tif (m_nSongPreAmp > maxpreamp) m_nSongPreAmp = maxpreamp;\n\t\treturn TRUE;\n\t}\n\treturn FALSE;\n}\n\n\nBOOL CSoundFile::Destroy()\n\n//------------------------\n{\n\tint i;\n\tfor (i=0; i<MAX_PATTERNS; i++) if (Patterns[i])\n\t{\n\t\tFreePattern(Patterns[i]);\n\t\tPatterns[i] = NULL;\n\t}\n\tm_nPatternNames = 0;\n\tif (m_lpszPatternNames)\n\t{\n\t\tdelete [] m_lpszPatternNames;\n\t\tm_lpszPatternNames = NULL;\n\t}\n\tif (m_lpszSongComments)\n\t{\n\t\tdelete [] m_lpszSongComments;\n\t\tm_lpszSongComments = NULL;\n\t}\n\tfor (i=1; i<MAX_SAMPLES; i++)\n\t{\n\t\tMODINSTRUMENT *pins = &Ins[i];\n\t\tif (pins->pSample)\n\t\t{\n\t\t\tFreeSample(pins->pSample);\n\t\t\tpins->pSample = NULL;\n\t\t}\n\t}\n\tfor (i=0; i<MAX_INSTRUMENTS; i++)\n\t{\n\t\tif (Headers[i])\n\t\t{\n\t\t\tdelete Headers[i];\n\t\t\tHeaders[i] = NULL;\n\t\t}\n\t}\n\tfor (i=0; i<MAX_MIXPLUGINS; i++)\n\t{\n\t\tif ((m_MixPlugins[i].nPluginDataSize) && (m_MixPlugins[i].pPluginData))\n\t\t{\n\t\t\tm_MixPlugins[i].nPluginDataSize = 0;\n\t\t\tdelete [] (signed char*)m_MixPlugins[i].pPluginData;\n\t\t\tm_MixPlugins[i].pPluginData = NULL;\n\t\t}\n\t\tm_MixPlugins[i].pMixState = NULL;\n\t\tif (m_MixPlugins[i].pMixPlugin)\n\t\t{\n\t\t\tm_MixPlugins[i].pMixPlugin->Release();\n\t\t\tm_MixPlugins[i].pMixPlugin = NULL;\n\t\t}\n\t}\n\tm_nType = MOD_TYPE_NONE;\n\tm_nChannels = m_nSamples = m_nInstruments = 0;\n\treturn TRUE;\n}\n\n\n//////////////////////////////////////////////////////////////////////////\n// Memory Allocation\n\nMODCOMMAND *CSoundFile::AllocatePattern(UINT rows, UINT nchns)\n//------------------------------------------------------------\n{\n\tMODCOMMAND *p = new MODCOMMAND[rows*nchns];\n\tif (p) memset(p, 0, rows*nchns*sizeof(MODCOMMAND));\n\treturn p;\n}\n\n\nvoid CSoundFile::FreePattern(LPVOID pat)\n//--------------------------------------\n{\n\tif (pat) delete [] (signed char*)pat;\n}\n\n\nsigned char* CSoundFile::AllocateSample(UINT nbytes)\n//-------------------------------------------\n{\n\tsigned char * p = (signed char *)GlobalAllocPtr(GHND, (nbytes+39) & ~7);\n\tif (p) p += 16;\n\treturn p;\n}\n\n\nvoid CSoundFile::FreeSample(LPVOID p)\n//-----------------------------------\n{\n\tif (p)\n\t{\n\t\tGlobalFreePtr(((LPSTR)p)-16);\n\t}\n}\n\n\n//////////////////////////////////////////////////////////////////////////\n// Misc functions\n\nvoid CSoundFile::ResetMidiCfg()\n//-----------------------------\n{\n\tmemset(&m_MidiCfg, 0, sizeof(m_MidiCfg));\n\tlstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_START*32], \"FF\");\n\tlstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_STOP*32], \"FC\");\n\tlstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_NOTEON*32], \"9c n v\");\n\tlstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_NOTEOFF*32], \"9c n 0\");\n\tlstrcpy(&m_MidiCfg.szMidiGlb[MIDIOUT_PROGRAM*32], \"Cc p\");\n\tlstrcpy(&m_MidiCfg.szMidiSFXExt[0], \"F0F000z\");\n\tfor (int iz=0; iz<16; iz++) wsprintf(&m_MidiCfg.szMidiZXXExt[iz*32], \"F0F001%02X\", iz*8);\n}\n\n\nUINT CSoundFile::GetNumChannels() const\n//-------------------------------------\n{\n\tUINT n = 0;\n\tfor (UINT i=0; i<m_nChannels; i++) if (ChnSettings[i].nVolume) n++;\n\treturn n;\n}\n\n\nUINT CSoundFile::GetSongComments(LPSTR s, UINT len, UINT linesize)\n//----------------------------------------------------------------\n{\n\tLPCSTR p = m_lpszSongComments;\n\tif (!p) return 0;\n\tUINT i = 2, ln=0;\n\tif ((len) && (s)) s[0] = '\\x0D';\n\tif ((len > 1) && (s)) s[1] = '\\x0A';\n\twhile ((*p)\t&& (i+2 < len))\n\t{\n\t\tBYTE c = (BYTE)*p++;\n\t\tif ((c == 0x0D) || ((c == ' ') && (ln >= linesize)))\n\t\t\t{ if (s) { s[i++] = '\\x0D'; s[i++] = '\\x0A'; } else i+= 2; ln=0; }\n\t\telse\n\t\tif (c >= 0x20) { if (s) s[i++] = c; else i++; ln++; }\n\t}\n\tif (s) s[i] = 0;\n\treturn i;\n}\n\n\nUINT CSoundFile::GetRawSongComments(LPSTR s, UINT len, UINT linesize)\n//-------------------------------------------------------------------\n{\n\tLPCSTR p = m_lpszSongComments;\n\tif (!p) return 0;\n\tUINT i = 0, ln=0;\n\twhile ((*p)\t&& (i < len-1))\n\t{\n\t\tBYTE c = (BYTE)*p++;\n\t\tif ((c == 0x0D)\t|| (c == 0x0A))\n\t\t{\n\t\t\tif (ln)\n\t\t\t{\n\t\t\t\twhile (ln < linesize) { if (s) s[i] = ' '; i++; ln++; }\n\t\t\t\tln = 0;\n\t\t\t}\n\t\t} else\n\t\tif ((c == ' ') && (!ln))\n\t\t{\n\t\t\tUINT k=0;\n\t\t\twhile ((p[k]) && (p[k] >= ' '))\tk++;\n\t\t\tif (k <= linesize)\n\t\t\t{\n\t\t\t\tif (s) s[i] = ' ';\n\t\t\t\ti++;\n\t\t\t\tln++;\n\t\t\t}\n\t\t} else\n\t\t{\n\t\t\tif (s) s[i] = c;\n\t\t\ti++;\n\t\t\tln++;\n\t\t\tif (ln == linesize) ln = 0;\n\t\t}\n\t}\n\tif (ln)\n\t{\n\t\twhile ((ln < linesize) && (i < len))\n\t\t{\n\t\t\tif (s) s[i] = ' ';\n\t\t\ti++;\n\t\t\tln++;\n\t\t}\n\t}\n\tif (s) s[i] = 0;\n\treturn i;\n}\n\n\nBOOL CSoundFile::SetWaveConfig(UINT nRate,UINT nBits,UINT nChannels,BOOL bMMX)\n//----------------------------------------------------------------------------\n{\n\tBOOL bReset = FALSE;\n\tDWORD d = gdwSoundSetup & ~SNDMIX_ENABLEMMX;\n\tif (bMMX) d |= SNDMIX_ENABLEMMX;\n\tif ((gdwMixingFreq != nRate) || (gnBitsPerSample != nBits) || (gnChannels != nChannels) || (d != gdwSoundSetup)) bReset = TRUE;\n\tgnChannels = nChannels;\n\tgdwSoundSetup = d;\n\tgdwMixingFreq = nRate;\n\tgnBitsPerSample = nBits;\n\tInitPlayer(bReset);\n\treturn TRUE;\n}\n\nBOOL CSoundFile::SetMixConfig(UINT nStereoSeparation, UINT nMaxMixChannels)\n//-------------------------------------------------------------------------\n{\n\tif (nMaxMixChannels < 2) return FALSE;\n\n\tm_nMaxMixChannels = nMaxMixChannels;\n\tm_nStereoSeparation = nStereoSeparation;\n\treturn TRUE;\n}\n\n\nBOOL CSoundFile::SetResamplingMode(UINT nMode)\n//--------------------------------------------\n{\n\tDWORD d = gdwSoundSetup & ~(SNDMIX_NORESAMPLING|SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE);\n\tswitch(nMode)\n\t{\n\tcase SRCMODE_NEAREST:\td |= SNDMIX_NORESAMPLING; break;\n\tcase SRCMODE_LINEAR:\tbreak;\n\tcase SRCMODE_SPLINE:\td |= SNDMIX_HQRESAMPLER; break;\n\tcase SRCMODE_POLYPHASE:\td |= (SNDMIX_HQRESAMPLER|SNDMIX_ULTRAHQSRCMODE); break;\n\tdefault:\n\t\treturn FALSE;\n\t}\n\tgdwSoundSetup = d;\n\treturn TRUE;\n}\n\n\nBOOL CSoundFile::SetMasterVolume(UINT nVol, BOOL bAdjustAGC)\n//----------------------------------------------------------\n{\n\tif (nVol < 1) nVol = 1;\n\tif (nVol > 0x200) nVol = 0x200;\t// x4 maximum\n\tif ((nVol < m_nMasterVolume) && (nVol) && (gdwSoundSetup & SNDMIX_AGC) && (bAdjustAGC))\n\t{\n\t\tgnAGC = gnAGC * m_nMasterVolume / nVol;\n\t\tif (gnAGC > AGC_UNITY) gnAGC = AGC_UNITY;\n\t}\n\tm_nMasterVolume = nVol;\n\treturn TRUE;\n}\n\n\nvoid CSoundFile::SetAGC(BOOL b)\n//-----------------------------\n{\n\tif (b)\n\t{\n\t\tif (!(gdwSoundSetup & SNDMIX_AGC))\n\t\t{\n\t\t\tgdwSoundSetup |= SNDMIX_AGC;\n\t\t\tgnAGC = AGC_UNITY;\n\t\t}\n\t} else gdwSoundSetup &= ~SNDMIX_AGC;\n}\n\n\nUINT CSoundFile::GetNumPatterns() const\n//-------------------------------------\n{\n\tUINT i = 0;\n\twhile ((i < MAX_ORDERS) && (Order[i] < 0xFF)) i++;\n\treturn i;\n}\n\n\nUINT CSoundFile::GetNumInstruments() const\n//----------------------------------------\n{\n\tUINT n=0;\n\tfor (UINT i=0; i<MAX_INSTRUMENTS; i++) if (Ins[i].pSample) n++;\n\treturn n;\n}\n\n\nUINT CSoundFile::GetMaxPosition() const\n//-------------------------------------\n{\n\tUINT max = 0;\n\tUINT i = 0;\n\n\twhile ((i < MAX_ORDERS) && (Order[i] != 0xFF))\n\t{\n\t\tif (Order[i] < MAX_PATTERNS) max += PatternSize[Order[i]];\n\t\ti++;\n\t}\n\treturn max;\n}\n\n\nUINT CSoundFile::GetCurrentPos() const\n//------------------------------------\n{\n\tUINT pos = 0;\n\n\tfor (UINT i=0; i<m_nCurrentPattern; i++) if (Order[i] < MAX_PATTERNS)\n\t\tpos += PatternSize[Order[i]];\n\treturn pos + m_nRow;\n}\n\n\nvoid CSoundFile::SetCurrentPos(UINT nPos)\n//---------------------------------------\n{\n\tUINT i, nPattern;\n\n\tfor (i=0; i<MAX_CHANNELS; i++)\n\t{\n\t\tChn[i].nNote = Chn[i].nNewNote = Chn[i].nNewIns = 0;\n\t\tChn[i].pInstrument = NULL;\n\t\tChn[i].pHeader = NULL;\n\t\tChn[i].nPortamentoDest = 0;\n\t\tChn[i].nCommand = 0;\n\t\tChn[i].nPatternLoopCount = 0;\n\t\tChn[i].nPatternLoop = 0;\n\t\tChn[i].nFadeOutVol = 0;\n\t\tChn[i].dwFlags |= CHN_KEYOFF|CHN_NOTEFADE;\n\t\tChn[i].nTremorCount = 0;\n\t}\n\tif (!nPos)\n\t{\n\t\tfor (i=0; i<MAX_CHANNELS; i++)\n\t\t{\n\t\t\tChn[i].nPeriod = 0;\n\t\t\tChn[i].nPos = Chn[i].nLength = 0;\n\t\t\tChn[i].nLoopStart = 0;\n\t\t\tChn[i].nLoopEnd = 0;\n\t\t\tChn[i].nROfs = Chn[i].nLOfs = 0;\n\t\t\tChn[i].pSample = NULL;\n\t\t\tChn[i].pInstrument = NULL;\n\t\t\tChn[i].pHeader = NULL;\n\t\t\tChn[i].nCutOff = 0x7F;\n\t\t\tChn[i].nResonance = 0;\n\t\t\tChn[i].nLeftVol = Chn[i].nRightVol = 0;\n\t\t\tChn[i].nNewLeftVol = Chn[i].nNewRightVol = 0;\n\t\t\tChn[i].nLeftRamp = Chn[i].nRightRamp = 0;\n\t\t\tChn[i].nVolume = 256;\n\t\t\tif (i < MAX_BASECHANNELS)\n\t\t\t{\n\t\t\t\tChn[i].dwFlags = ChnSettings[i].dwFlags;\n\t\t\t\tChn[i].nPan = ChnSettings[i].nPan;\n\t\t\t\tChn[i].nGlobalVol = ChnSettings[i].nVolume;\n\t\t\t} else\n\t\t\t{\n\t\t\t\tChn[i].dwFlags = 0;\n\t\t\t\tChn[i].nPan = 128;\n\t\t\t\tChn[i].nGlobalVol = 64;\n\t\t\t}\n\t\t}\n\t\tm_nGlobalVolume = m_nDefaultGlobalVolume;\n\t\tm_nMusicSpeed = m_nDefaultSpeed;\n\t\tm_nMusicTempo = m_nDefaultTempo;\n\t}\n\tm_dwSongFlags &= ~(SONG_PATTERNLOOP|SONG_CPUVERYHIGH|SONG_FADINGSONG|SONG_ENDREACHED|SONG_GLOBALFADE);\n\tfor (nPattern = 0; nPattern < MAX_ORDERS; nPattern++)\n\t{\n\t\tUINT ord = Order[nPattern];\n\t\tif (ord == 0xFE) continue;\n\t\tif (ord == 0xFF) break;\n\t\tif (ord < MAX_PATTERNS)\n\t\t{\n\t\t\tif (nPos < (UINT)PatternSize[ord]) break;\n\t\t\tnPos -= PatternSize[ord];\n\t\t}\n\t}\n\t// Buggy position ?\n\tif ((nPattern >= MAX_ORDERS)\n\t || (Order[nPattern] >= MAX_PATTERNS)\n\t || (nPos >= PatternSize[Order[nPattern]]))\n\t{\n\t\tnPos = 0;\n\t\tnPattern = 0;\n\t}\n\tUINT nRow = nPos;\n\tif ((nRow) && (Order[nPattern] < MAX_PATTERNS))\n\t{\n\t\tMODCOMMAND *p = Patterns[Order[nPattern]];\n\t\tif ((p) && (nRow < PatternSize[Order[nPattern]]))\n\t\t{\n\t\t\tBOOL bOk = FALSE;\n\t\t\twhile ((!bOk) && (nRow > 0))\n\t\t\t{\n\t\t\t\tUINT n = nRow * m_nChannels;\n\t\t\t\tfor (UINT k=0; k<m_nChannels; k++, n++)\n\t\t\t\t{\n\t\t\t\t\tif (p[n].note)\n\t\t\t\t\t{\n\t\t\t\t\t\tbOk = TRUE;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!bOk) nRow--;\n\t\t\t}\n\t\t}\n\t}\n\tm_nNextPattern = nPattern;\n\tm_nNextRow = nRow;\n\tm_nNextStartRow = 0;\n\tm_nTickCount = m_nMusicSpeed;\n\tm_nBufferCount = 0;\n\tm_nPatternDelay = 0;\n\tm_nFrameDelay = 0;\n}\n\n\nvoid CSoundFile::SetCurrentOrder(UINT nPos)\n//-----------------------------------------\n{\n\twhile ((nPos < MAX_ORDERS) && (Order[nPos] == 0xFE)) nPos++;\n\tif ((nPos >= MAX_ORDERS) || (Order[nPos] >= MAX_PATTERNS)) return;\n\tfor (UINT j=0; j<MAX_CHANNELS; j++)\n\t{\n\t\tChn[j].nPeriod = 0;\n\t\tChn[j].nNote = 0;\n\t\tChn[j].nPortamentoDest = 0;\n\t\tChn[j].nCommand = 0;\n\t\tChn[j].nPatternLoopCount = 0;\n\t\tChn[j].nPatternLoop = 0;\n\t\tChn[j].nTremorCount = 0;\n\t}\n\tif (!nPos)\n\t{\n\t\tSetCurrentPos(0);\n\t} else\n\t{\n\t\tm_nNextPattern = nPos;\n\t\tm_nRow = m_nNextRow = m_nNextStartRow = 0;\n\t\tm_nPattern = 0;\n\t\tm_nTickCount = m_nMusicSpeed;\n\t\tm_nBufferCount = 0;\n\t\tm_nTotalCount = 0;\n\t\tm_nPatternDelay = 0;\n\t\tm_nFrameDelay = 0;\n\t}\n\tm_dwSongFlags &= ~(SONG_PATTERNLOOP|SONG_CPUVERYHIGH|SONG_FADINGSONG|SONG_ENDREACHED|SONG_GLOBALFADE);\n}\n\n\nvoid CSoundFile::ResetChannels()\n//------------------------------\n{\n\tm_dwSongFlags &= ~(SONG_CPUVERYHIGH|SONG_FADINGSONG|SONG_ENDREACHED|SONG_GLOBALFADE);\n\tm_nBufferCount = 0;\n\tfor (UINT i=0; i<MAX_CHANNELS; i++)\n\t{\n\t\tChn[i].nROfs = Chn[i].nLOfs = 0;\n\t}\n}\n\n\nvoid CSoundFile::LoopPattern(int nPat, int nRow)\n//----------------------------------------------\n{\n\tif ((nPat < 0) || (nPat >= MAX_PATTERNS) || (!Patterns[nPat]))\n\t{\n\t\tm_dwSongFlags &= ~SONG_PATTERNLOOP;\n\t} else\n\t{\n\t\tif ((nRow < 0) || (nRow >= PatternSize[nPat])) nRow = 0;\n\t\tm_nPattern = nPat;\n\t\tm_nRow = m_nNextRow = nRow;\n\t\tm_nTickCount = m_nMusicSpeed;\n\t\tm_nPatternDelay = 0;\n\t\tm_nFrameDelay = 0;\n\t\tm_nBufferCount = 0;\n\t\tm_dwSongFlags |= SONG_PATTERNLOOP;\n\t}\n}\n\n\nUINT CSoundFile::GetBestSaveFormat() const\n//----------------------------------------\n{\n\tif ((!m_nSamples) || (!m_nChannels)) return MOD_TYPE_NONE;\n\tif (!m_nType) return MOD_TYPE_NONE;\n\tif (m_nType & (MOD_TYPE_MOD|MOD_TYPE_OKT))\n\t\treturn MOD_TYPE_MOD;\n\tif (m_nType & (MOD_TYPE_S3M|MOD_TYPE_STM|MOD_TYPE_ULT|MOD_TYPE_FAR|MOD_TYPE_PTM))\n\t\treturn MOD_TYPE_S3M;\n\tif (m_nType & (MOD_TYPE_XM|MOD_TYPE_MED|MOD_TYPE_MTM|MOD_TYPE_MT2))\n\t\treturn MOD_TYPE_XM;\n\treturn MOD_TYPE_IT;\n}\n\n\nUINT CSoundFile::GetSaveFormats() const\n//-------------------------------------\n{\n\tUINT n = 0;\n\tif ((!m_nSamples) || (!m_nChannels) || (m_nType == MOD_TYPE_NONE)) return 0;\n\tif (m_nType & MOD_TYPE_MOD)\n\t\tn |= MOD_TYPE_MOD;\n\tif (m_nType & MOD_TYPE_S3M)\n\t\tn |= MOD_TYPE_S3M;\n\t// Can always save to XM & IT\n\tn |= MOD_TYPE_XM | MOD_TYPE_IT;\n\tif (!m_nInstruments)\n\t{\n\t\tif (m_nSamples < 32) n |= MOD_TYPE_MOD;\n\t\tn |= MOD_TYPE_S3M;\n\t}\n\treturn n;\n}\n\n\nUINT CSoundFile::GetSampleName(UINT nSample,LPSTR s) const\n//--------------------------------------------------------\n{\n        char sztmp[40] = \"\";      // changed from CHAR\n\tif (nSample < MAX_SAMPLES)\n\t\tmemcpy(sztmp, m_szNames[nSample], 32);\n\tsztmp[31] = 0;\n\tif (s) strcpy(s, sztmp);\n\treturn strlen(sztmp);\n}\n\n\nUINT CSoundFile::GetInstrumentName(UINT nInstr,LPSTR s) const\n//-----------------------------------------------------------\n{\n        char sztmp[40] = \"\";  // changed from CHAR\n\tif ((nInstr >= MAX_INSTRUMENTS) || (!Headers[nInstr]))\n\t{\n\t\tif (s) *s = 0;\n\t\treturn 0;\n\t}\n\tINSTRUMENTHEADER *penv = Headers[nInstr];\n\tmemcpy(sztmp, penv->name, 32);\n\tsztmp[31] = 0;\n\tif (s) strcpy(s, sztmp);\n\treturn strlen(sztmp);\n}\n\n\n#ifndef NO_PACKING\nUINT CSoundFile::PackSample(int &sample, int next)\n//------------------------------------------------\n{\n\tUINT i = 0;\n\tint delta = next - sample;\n\tif (delta >= 0)\n\t{\n\t\tfor (i=0; i<7; i++) if (delta <= (int)CompressionTable[i+1]) break;\n\t} else\n\t{\n\t\tfor (i=8; i<15; i++) if (delta >= (int)CompressionTable[i+1]) break;\n\t}\n\tsample += (int)CompressionTable[i];\n\treturn i;\n}\n\n\nBOOL CSoundFile::CanPackSample(LPSTR pSample, UINT nLen, UINT nPacking, BYTE *result)\n//-----------------------------------------------------------------------------------\n{\n\tint pos, old, oldpos, besttable = 0;\n\tDWORD dwErr, dwTotal, dwResult;\n\tint i,j;\n\n\tif (result) *result = 0;\n\tif ((!pSample) || (nLen < 1024)) return FALSE;\n\t// Try packing with different tables\n\tdwResult = 0;\n\tfor (j=1; j<MAX_PACK_TABLES; j++)\n\t{\n\t\tmemcpy(CompressionTable, UnpackTable[j], 16);\n\t\tdwErr = 0;\n\t\tdwTotal = 1;\n\t\told = pos = oldpos = 0;\n\t\tfor (i=0; i<(int)nLen; i++)\n\t\t{\n\t\t\tint s = (int)pSample[i];\n\t\t\tPackSample(pos, s);\n\t\t\tdwErr += abs(pos - oldpos);\n\t\t\tdwTotal += abs(s - old);\n\t\t\told = s;\n\t\t\toldpos = pos;\n\t\t}\n\t\tdwErr = _muldiv(dwErr, 100, dwTotal);\n\t\tif (dwErr >= dwResult)\n\t\t{\n\t\t\tdwResult = dwErr;\n\t\t\tbesttable = j;\n\t\t}\n\t}\n\tmemcpy(CompressionTable, UnpackTable[besttable], 16);\n\tif (result)\n\t{\n\t\tif (dwResult > 100) *result\t= 100; else *result = (BYTE)dwResult;\n\t}\n\treturn (dwResult >= nPacking) ? TRUE : FALSE;\n}\n#endif // NO_PACKING\n\n#ifndef MODPLUG_NO_FILESAVE\n\nUINT CSoundFile::WriteSample(FILE *f, MODINSTRUMENT *pins, UINT nFlags, UINT nMaxLen)\n//-----------------------------------------------------------------------------------\n{\n\tUINT len = 0, bufcount;\n\tsigned char buffer[4096];\n\tsigned char *pSample = (signed char *)pins->pSample;\n\tUINT nLen = pins->nLength;\n\n\tif ((nMaxLen) && (nLen > nMaxLen)) nLen = nMaxLen;\n\tif ((!pSample) || (f == NULL) || (!nLen)) return 0;\n\tswitch(nFlags)\n\t{\n#ifndef NO_PACKING\n\t// 3: 4-bit ADPCM data\n\tcase RS_ADPCM4:\n\t\t{\n\t\t\tint pos;\n\t\t\tlen = (nLen + 1) / 2;\n\t\t\tfwrite(CompressionTable, 16, 1, f);\n\t\t\tbufcount = 0;\n\t\t\tpos = 0;\n\t\t\tfor (UINT j=0; j<len; j++)\n\t\t\t{\n\t\t\t\tBYTE b;\n\t\t\t\t// Sample #1\n\t\t\t\tb = PackSample(pos, (int)pSample[j*2]);\n\t\t\t\t// Sample #2\n\t\t\t\tb |= PackSample(pos, (int)pSample[j*2+1]) << 4;\n\t\t\t\tbuffer[bufcount++] = (signed char)b;\n\t\t\t\tif (bufcount >= sizeof(buffer))\n\t\t\t\t{\n\t\t\t\t\tfwrite(buffer, 1, bufcount, f);\n\t\t\t\t\tbufcount = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (bufcount) fwrite(buffer, 1, bufcount, f);\n\t\t\tlen += 16;\n\t\t}\n\t\tbreak;\n#endif // NO_PACKING\n\n\t// 16-bit samples\n\tcase RS_PCM16U:\n\tcase RS_PCM16D:\n\tcase RS_PCM16S:\n\t\t{\n\t\t\tint16_t *p = (int16_t *)pSample;\n\t\t\tint s_old = 0, s_ofs;\n\t\t\tlen = nLen * 2;\n\t\t\tbufcount = 0;\n\t\t\ts_ofs = (nFlags == RS_PCM16U) ? 0x8000 : 0;\n\t\t\tfor (UINT j=0; j<nLen; j++)\n\t\t\t{\n\t\t\t\tint s_new = *p;\n\t\t\t\tp++;\n\t\t\t\tif (pins->uFlags & CHN_STEREO)\n\t\t\t\t{\n\t\t\t\t\ts_new = (s_new + (*p) + 1) >> 1;\n\t\t\t\t\tp++;\n\t\t\t\t}\n\t\t\t\tif (nFlags == RS_PCM16D)\n\t\t\t\t{\n\t\t\t\t\tint16_t temp = bswapLE16((int16_t)(s_new - s_old));\n\t\t\t\t\tmemcpy(&buffer[bufcount], &temp, sizeof(int16_t));\n\t\t\t\t\ts_old = s_new;\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tint16_t temp = bswapLE16((int16_t)(s_new + s_ofs));\n\t\t\t\t\tmemcpy(&buffer[bufcount], &temp, sizeof(int16_t));\n\t\t\t\t}\n\t\t\t\tbufcount += 2;\n\t\t\t\tif (bufcount >= sizeof(buffer) - 1)\n\t\t\t\t{\n\t\t\t\t\tfwrite(buffer, 1, bufcount, f);\n\t\t\t\t\tbufcount = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (bufcount) fwrite(buffer, 1, bufcount, f);\n\t\t}\n\t\tbreak;\n\n\n\t// 8-bit Stereo samples (not interleaved)\n\tcase RS_STPCM8S:\n\tcase RS_STPCM8U:\n\tcase RS_STPCM8D:\n\t\t{\n\t\t\tint s_ofs = (nFlags == RS_STPCM8U) ? 0x80 : 0;\n\t\t\tfor (UINT iCh=0; iCh<2; iCh++)\n\t\t\t{\n\t\t\t\tsigned char *p = pSample + iCh;\n\t\t\t\tint s_old = 0;\n\n\t\t\t\tbufcount = 0;\n\t\t\t\tfor (UINT j=0; j<nLen; j++)\n\t\t\t\t{\n\t\t\t\t\tint s_new = *p;\n\t\t\t\t\tp += 2;\n\t\t\t\t\tif (nFlags == RS_STPCM8D)\n\t\t\t\t\t{\n\t\t\t\t\t\tbuffer[bufcount++] = (signed char)(s_new - s_old);\n\t\t\t\t\t\ts_old = s_new;\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tbuffer[bufcount++] = (signed char)(s_new + s_ofs);\n\t\t\t\t\t}\n\t\t\t\t\tif (bufcount >= sizeof(buffer))\n\t\t\t\t\t{\n\t\t\t\t\t\tfwrite(buffer, 1, bufcount, f);\n\t\t\t\t\t\tbufcount = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (bufcount) fwrite(buffer, 1, bufcount, f);\n\t\t\t}\n\t\t}\n\t\tlen = nLen * 2;\n\t\tbreak;\n\n\t// 16-bit Stereo samples (not interleaved)\n\tcase RS_STPCM16S:\n\tcase RS_STPCM16U:\n\tcase RS_STPCM16D:\n\t\t{\n\t\t\tint s_ofs = (nFlags == RS_STPCM16U) ? 0x8000 : 0;\n\t\t\tfor (UINT iCh=0; iCh<2; iCh++)\n\t\t\t{\n\t\t\t\tint16_t *p = ((int16_t *)pSample) + iCh;\n\t\t\t\tint s_old = 0;\n\n\t\t\t\tbufcount = 0;\n\t\t\t\tfor (UINT j=0; j<nLen; j++)\n\t\t\t\t{\n\t\t\t\t\tint s_new = *p;\n\t\t\t\t\tp += 2;\n\t\t\t\t\tif (nFlags == RS_STPCM16D)\n\t\t\t\t\t{\n\t\t\t\t\t\tint16_t temp = bswapLE16((int16_t)(s_new - s_old));\n\t\t\t\t\t\tmemcpy(&buffer[bufcount], &temp, sizeof(int16_t));\n\t\t\t\t\t\ts_old = s_new;\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tint16_t temp = bswapLE16((int16_t)(s_new - s_ofs));\n\t\t\t\t\t\tmemcpy(&buffer[bufcount], &temp, sizeof(int16_t));\n\t\t\t\t\t}\n\t\t\t\t\tbufcount += 2;\n\t\t\t\t\tif (bufcount >= sizeof(buffer))\n\t\t\t\t\t{\n\t\t\t\t\t\tfwrite(buffer, 1, bufcount, f);\n\t\t\t\t\t\tbufcount = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (bufcount) fwrite(buffer, 1, bufcount, f);\n\t\t\t}\n\t\t}\n\t\tlen = nLen*4;\n\t\tbreak;\n\n\t//\tStereo signed interleaved\n\tcase RS_STIPCM8S:\n\tcase RS_STIPCM16S:\n\t\tlen = nLen * 2;\n\t\tif (nFlags == RS_STIPCM16S) len *= 2;\n\t\tfwrite(pSample, 1, len, f);\n\t\tbreak;\n\n\t// Default: assume 8-bit PCM data\n\tdefault:\n\t\tlen = nLen;\n\t\tbufcount = 0;\n\t\t{\n\t\t\tsigned char *p = pSample;\n\t\t\tint sinc = (pins->uFlags & CHN_16BIT) ? 2 : 1;\n\t\t\tint s_old = 0, s_ofs = (nFlags == RS_PCM8U) ? 0x80 : 0;\n\t\t\tif (pins->uFlags & CHN_16BIT) p++;\n\t\t\tfor (UINT j=0; j<len; j++)\n\t\t\t{\n\t\t\t\tint s_new = (signed char)(*p);\n\t\t\t\tp += sinc;\n\t\t\t\tif (pins->uFlags & CHN_STEREO)\n\t\t\t\t{\n\t\t\t\t\ts_new = (s_new + ((int)*p) + 1) >> 1;\n\t\t\t\t\tp += sinc;\n\t\t\t\t}\n\t\t\t\tif (nFlags == RS_PCM8D)\n\t\t\t\t{\n\t\t\t\t\tbuffer[bufcount++] = (signed char)(s_new - s_old);\n\t\t\t\t\ts_old = s_new;\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tbuffer[bufcount++] = (signed char)(s_new + s_ofs);\n\t\t\t\t}\n\t\t\t\tif (bufcount >= sizeof(buffer))\n\t\t\t\t{\n\t\t\t\t\tfwrite(buffer, 1, bufcount, f);\n\t\t\t\t\tbufcount = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (bufcount) fwrite(buffer, 1, bufcount, f);\n\t\t}\n\t}\n\treturn len;\n}\n\n#endif // MODPLUG_NO_FILESAVE\n\n\n// Flags:\n//\t0 = signed 8-bit PCM data (default)\n//\t1 = unsigned 8-bit PCM data\n//\t2 = 8-bit ADPCM data with linear table\n//\t3 = 4-bit ADPCM data\n//\t4 = 16-bit ADPCM data with linear table\n//\t5 = signed 16-bit PCM data\n//\t6 = unsigned 16-bit PCM data\n\n\nUINT CSoundFile::ReadSample(MODINSTRUMENT *pIns, UINT nFlags, LPCSTR lpMemFile, DWORD dwMemLength)\n//------------------------------------------------------------------------------\n{\n\tUINT len = 0, mem;\n\n\t// Disable >2Gb samples,(preventing buffer overflow in AllocateSample)\n\tif ((!pIns) || ((int)pIns->nLength < 4) || (!lpMemFile)) return 0;\n\tif (pIns->nLength > MAX_SAMPLE_LENGTH) pIns->nLength = MAX_SAMPLE_LENGTH;\n\tmem = pIns->nLength+6;\n\tpIns->uFlags &= ~(CHN_16BIT|CHN_STEREO);\n\tif (nFlags & RSF_16BIT)\n\t{\n\t\tmem *= 2;\n\t\tpIns->uFlags |= CHN_16BIT;\n\t}\n\tif (nFlags & RSF_STEREO)\n\t{\n\t\tmem *= 2;\n\t\tpIns->uFlags |= CHN_STEREO;\n\t}\n\tif ((pIns->pSample = AllocateSample(mem)) == NULL)\n\t{\n\t\tpIns->nLength = 0;\n\t\treturn 0;\n\t}\n\tswitch(nFlags)\n\t{\n\t// 1: 8-bit unsigned PCM data\n\tcase RS_PCM8U:\n\t\t{\n\t\t\tlen = pIns->nLength;\n\t\t\tif (len > dwMemLength) len = pIns->nLength = dwMemLength;\n\t\t\tsigned char *pSample = pIns->pSample;\n\t\t\tfor (UINT j=0; j<len; j++) pSample[j] = (signed char)(lpMemFile[j] - 0x80);\n\t\t}\n\t\tbreak;\n\n\t// 2: 8-bit ADPCM data with linear table\n\tcase RS_PCM8D:\n\t\t{\n\t\t\tlen = pIns->nLength;\n\t\t\tif (len > dwMemLength) break;\n\t\t\tsigned char *pSample = pIns->pSample;\n\t\t\tconst signed char *p = (const signed char *)lpMemFile;\n\t\t\tint delta = 0;\n\n\t\t\tfor (UINT j=0; j<len; j++)\n\t\t\t{\n\t\t\t\tdelta += p[j];\n\t\t\t\t*pSample++ = (signed char)delta;\n\t\t\t}\n\t\t}\n\t\tbreak;\n\n\t// 3: 4-bit ADPCM data\n\tcase RS_ADPCM4:\n\t\t{\n\t\t\tlen = (pIns->nLength + 1) / 2;\n\t\t\tif (len > dwMemLength - 16 || dwMemLength < 16) break;\n\t\t\tmemcpy(CompressionTable, lpMemFile, 16);\n\t\t\tlpMemFile += 16;\n\t\t\tsigned char *pSample = pIns->pSample;\n\t\t\tsigned char delta = 0;\n\t\t\tfor (UINT j=0; j<len; j++)\n\t\t\t{\n\t\t\t\tBYTE b0 = (BYTE)lpMemFile[j];\n\t\t\t\tBYTE b1 = (BYTE)(lpMemFile[j] >> 4);\n\t\t\t\tdelta = (signed char)GetDeltaValue((int)delta, b0);\n\t\t\t\tpSample[0] = delta;\n\t\t\t\tdelta = (signed char)GetDeltaValue((int)delta, b1);\n\t\t\t\tpSample[1] = delta;\n\t\t\t\tpSample += 2;\n\t\t\t}\n\t\t\tlen += 16;\n\t\t}\n\t\tbreak;\n\n\t// 4: 16-bit ADPCM data with linear table\n\tcase RS_PCM16D:\n\t\t{\n\t\t\tlen = pIns->nLength * 2;\n\t\t\tif (len > dwMemLength) break;\n\t\t\tint16_t *pSample = (int16_t *)pIns->pSample;\n\t\t\tint16_t *p = (int16_t *)lpMemFile;\n\t\t\tint delta16 = 0;\n\t\t\tfor (UINT j=0; j<len; j+=2)\n\t\t\t{\n\t\t\t\tdelta16 += bswapLE16(*p++);\n\t\t\t\t*pSample++ = (int16_t )delta16;\n\t\t\t}\n\t\t}\n\t\tbreak;\n\n\t// 5: 16-bit signed PCM data\n\tcase RS_PCM16S:\n\t        {\n\t\tlen = pIns->nLength * 2;\n\t\tif (len <= dwMemLength) memcpy(pIns->pSample, lpMemFile, len);\n\t\t\tint16_t *pSample = (int16_t *)pIns->pSample;\n\t\t\tfor (UINT j=0; j<len; j+=2)\n\t\t\t{\n\t\t\t\tint16_t rawSample = *pSample;\n\t\t\t        *pSample++ = bswapLE16(rawSample);\n\t\t\t}\n\t\t}\n\t\tbreak;\n\n\t// 16-bit signed mono PCM motorola byte order\n\tcase RS_PCM16M:\n\t\tlen = pIns->nLength * 2;\n\t\tif (len > dwMemLength) len = dwMemLength & ~1;\n\t\tif (len > 1)\n\t\t{\n\t\t\tsigned char *pSample = (signed char *)pIns->pSample;\n\t\t\tsigned char *pSrc = (signed char *)lpMemFile;\n\t\t\tfor (UINT j=0; j<len; j+=2)\n\t\t\t{\n\t\t\t  \t// pSample[j] = pSrc[j+1];\n\t\t\t\t// pSample[j+1] = pSrc[j];\n\t\t\t        *((uint16_t *)(pSample+j)) = bswapBE16(*((uint16_t *)(pSrc+j)));\n\t\t\t}\n\t\t}\n\t\tbreak;\n\n\t// 6: 16-bit unsigned PCM data\n\tcase RS_PCM16U:\n\t\t{\n\t\t\tlen = pIns->nLength * 2;\n\t\t\tif (len > dwMemLength) break;\n\t\t\tint16_t *pSample = (int16_t *)pIns->pSample;\n\t\t\tint16_t *pSrc = (int16_t *)lpMemFile;\n\t\t\tfor (UINT j=0; j<len; j+=2) *pSample++ = bswapLE16(*(pSrc++)) - 0x8000;\n\t\t}\n\t\tbreak;\n\n\t// 16-bit signed stereo big endian\n\tcase RS_STPCM16M:\n\t\tlen = pIns->nLength * 2;\n\t\tif (len*2 <= dwMemLength)\n\t\t{\n\t\t\tsigned char *pSample = (signed char *)pIns->pSample;\n\t\t\tsigned char *pSrc = (signed char *)lpMemFile;\n\t\t\tfor (UINT j=0; j<len; j+=2)\n\t\t\t{\n\t\t\t        // pSample[j*2] = pSrc[j+1];\n\t\t\t\t// pSample[j*2+1] = pSrc[j];\n\t\t\t\t// pSample[j*2+2] = pSrc[j+1+len];\n\t\t\t\t// pSample[j*2+3] = pSrc[j+len];\n\t\t\t        *((uint16_t *)(pSample+j*2)) = bswapBE16(*((uint16_t *)(pSrc+j)));\n\t\t\t\t*((uint16_t *)(pSample+j*2+2)) = bswapBE16(*((uint16_t *)(pSrc+j+len)));\n\t\t\t}\n\t\t\tlen *= 2;\n\t\t}\n\t\tbreak;\n\n\t// 8-bit stereo samples\n\tcase RS_STPCM8S:\n\tcase RS_STPCM8U:\n\tcase RS_STPCM8D:\n\t\t{\n\t\t\tint iadd_l = 0, iadd_r = 0;\n\t\t\tif (nFlags == RS_STPCM8U) { iadd_l = iadd_r = -128; }\n\t\t\tlen = pIns->nLength;\n\t\t\tsigned char *psrc = (signed char *)lpMemFile;\n\t\t\tsigned char *pSample = (signed char *)pIns->pSample;\n\t\t\tif (len*2 > dwMemLength) break;\n\t\t\tfor (UINT j=0; j<len; j++)\n\t\t\t{\n\t\t\t\tpSample[j*2] = (signed char)(psrc[0] + iadd_l);\n\t\t\t\tpSample[j*2+1] = (signed char)(psrc[len] + iadd_r);\n\t\t\t\tpsrc++;\n\t\t\t\tif (nFlags == RS_STPCM8D)\n\t\t\t\t{\n\t\t\t\t\tiadd_l = pSample[j*2];\n\t\t\t\t\tiadd_r = pSample[j*2+1];\n\t\t\t\t}\n\t\t\t}\n\t\t\tlen *= 2;\n\t\t}\n\t\tbreak;\n\n\t// 16-bit stereo samples\n\tcase RS_STPCM16S:\n\tcase RS_STPCM16U:\n\tcase RS_STPCM16D:\n\t\t{\n\t\t\tint iadd_l = 0, iadd_r = 0;\n\t\t\tif (nFlags == RS_STPCM16U) { iadd_l = iadd_r = -0x8000; }\n\t\t\tlen = pIns->nLength;\n\t\t\tint16_t *psrc = (int16_t *)lpMemFile;\n\t\t\tint16_t *pSample = (int16_t *)pIns->pSample;\n\t\t\tif (len*4 > dwMemLength) break;\n\t\t\tfor (UINT j=0; j<len; j++)\n\t\t\t{\n\t\t\t\tpSample[j*2] = (int16_t) (bswapLE16(psrc[0]) + iadd_l);\n\t\t\t\tpSample[j*2+1] = (int16_t) (bswapLE16(psrc[len]) + iadd_r);\n\t\t\t\tpsrc++;\n\t\t\t\tif (nFlags == RS_STPCM16D)\n\t\t\t\t{\n\t\t\t\t\tiadd_l = pSample[j*2];\n\t\t\t\t\tiadd_r = pSample[j*2+1];\n\t\t\t\t}\n\t\t\t}\n\t\t\tlen *= 4;\n\t\t}\n\t\tbreak;\n\n\t// IT 2.14 compressed samples\n\tcase RS_IT2148:\n\tcase RS_IT21416:\n\tcase RS_IT2158:\n\tcase RS_IT21516:\n\t\tlen = dwMemLength;\n\t\tif (len < 4) break;\n\t\tif ((nFlags == RS_IT2148) || (nFlags == RS_IT2158))\n\t\t\tITUnpack8Bit(pIns->pSample, pIns->nLength, (LPBYTE)lpMemFile, dwMemLength, (nFlags == RS_IT2158));\n\t\telse\n\t\t\tITUnpack16Bit(pIns->pSample, pIns->nLength, (LPBYTE)lpMemFile, dwMemLength, (nFlags == RS_IT21516));\n\t\tbreak;\n\n#ifndef MODPLUG_BASIC_SUPPORT\n#ifndef MODPLUG_FASTSOUNDLIB\n\t// 8-bit interleaved stereo samples\n\tcase RS_STIPCM8S:\n\tcase RS_STIPCM8U:\n\t\t{\n\t\t\tint iadd = 0;\n\t\t\tif (nFlags == RS_STIPCM8U) { iadd = -0x80; }\n\t\t\tlen = pIns->nLength;\n\t\t\tif (len*2 > dwMemLength) len = dwMemLength >> 1;\n\t\t\tLPBYTE psrc = (LPBYTE)lpMemFile;\n\t\t\tLPBYTE pSample = (LPBYTE)pIns->pSample;\n\t\t\tfor (UINT j=0; j<len; j++)\n\t\t\t{\n\t\t\t\tpSample[j*2] = (signed char)(psrc[0] + iadd);\n\t\t\t\tpSample[j*2+1] = (signed char)(psrc[1] + iadd);\n\t\t\t\tpsrc+=2;\n\t\t\t}\n\t\t\tlen *= 2;\n\t\t}\n\t\tbreak;\n\n\t// 16-bit interleaved stereo samples\n\tcase RS_STIPCM16S:\n\tcase RS_STIPCM16U:\n\t\t{\n\t\t\tint iadd = 0;\n\t\t\tif (nFlags == RS_STIPCM16U) iadd = -32768;\n\t\t\tlen = pIns->nLength;\n\t\t\tif (len*4 > dwMemLength) len = dwMemLength >> 2;\n\t\t\tint16_t *psrc = (int16_t *)lpMemFile;\n\t\t\tint16_t *pSample = (int16_t *)pIns->pSample;\n\t\t\tfor (UINT j=0; j<len; j++)\n\t\t\t{\n\t\t\t\tpSample[j*2] = (int16_t)(bswapLE16(psrc[0]) + iadd);\n\t\t\t\tpSample[j*2+1] = (int16_t)(bswapLE16(psrc[1]) + iadd);\n\t\t\t\tpsrc += 2;\n\t\t\t}\n\t\t\tlen *= 4;\n\t\t}\n\t\tbreak;\n\n#if 0\n\t// AMS compressed samples\n\tcase RS_AMS8:\n\tcase RS_AMS16:\n\t\tlen = 9;\n\t\tif (dwMemLength > 9)\n\t\t{\n\t\t\tconst char *psrc = lpMemFile;\n\t\t\tchar packcharacter = lpMemFile[8], *pdest = (char *)pIns->pSample;\n\t\t\tlen += bswapLE32(*((LPDWORD)(lpMemFile+4)));\n\t\t\tif (len > dwMemLength) len = dwMemLength;\n\t\t\tUINT dmax = pIns->nLength;\n\t\t\tif (pIns->uFlags & CHN_16BIT) dmax <<= 1;\n\t\t\tAMSUnpack(psrc+9, len-9, pdest, dmax, packcharacter);\n\t\t}\n\t\tbreak;\n\n\t// PTM 8bit delta to 16-bit sample\n\tcase RS_PTM8DTO16:\n\t\t{\n\t\t\tlen = pIns->nLength * 2;\n\t\t\tif (len > dwMemLength) break;\n\t\t\tint8_t *pSample = (int8_t *)pIns->pSample;\n\t\t\tint8_t delta8 = 0;\n\t\t\tfor (UINT j=0; j<len; j++)\n\t\t\t{\n\t\t\t\tdelta8 += lpMemFile[j];\n\t\t\t\t*pSample++ = delta8;\n\t\t\t}\n\t\t\tuint16_t *pSampleW = (uint16_t *)pIns->pSample;\n\t\t\tfor (UINT j=0; j<len; j+=2)   // swaparoni!\n\t\t\t{\n\t\t\t\tuint16_t rawSample = *pSampleW;\n\t\t\t        *pSampleW++ = bswapLE16(rawSample);\n\t\t\t}\n\t\t}\n\t\tbreak;\n\n\t// Huffman MDL compressed samples\n\tcase RS_MDL8:\n\tcase RS_MDL16:\n\t\tlen = dwMemLength;\n\t\tif (len >= 4)\n\t\t{\n\t\t\tLPBYTE pSample = (LPBYTE)pIns->pSample;\n\t\t\tLPBYTE ibuf = (LPBYTE)lpMemFile;\n\t\t\tDWORD bitbuf = bswapLE32(*((DWORD *)ibuf));\n\t\t\tUINT bitnum = 32;\n\t\t\tBYTE dlt = 0, lowbyte = 0;\n\t\t\tLPBYTE ibufend = (LPBYTE)lpMemFile + dwMemLength - 1;\n\t\t\tibuf += 4;\n\t\t\tfor (UINT j=0; j<pIns->nLength && ibuf < ibufend; j++)\n\t\t\t{\n\t\t\t\tBYTE hibyte;\n\t\t\t\tBYTE sign;\n\t\t\t\tif (nFlags == RS_MDL16) lowbyte = (BYTE)MDLReadBits(bitbuf, bitnum, ibuf, 8);\n\t\t\t\tsign = (BYTE)MDLReadBits(bitbuf, bitnum, ibuf, 1);\n\t\t\t\tif (MDLReadBits(bitbuf, bitnum, ibuf, 1))\n\t\t\t\t{\n\t\t\t\t\thibyte = (BYTE)MDLReadBits(bitbuf, bitnum, ibuf, 3);\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\thibyte = 8;\n\t\t\t\t\twhile (ibuf < ibufend && !MDLReadBits(bitbuf, bitnum, ibuf, 1))\n\t\t\t\t\t\thibyte += 0x10;\n\t\t\t\t\tif (ibuf < ibufend)\n\t\t\t\t\t\thibyte += MDLReadBits(bitbuf, bitnum, ibuf, 4);\n\t\t\t\t}\n\t\t\t\tif (sign) hibyte = ~hibyte;\n\t\t\t\tdlt += hibyte;\n\t\t\t\tif (nFlags != RS_MDL16)\n\t\t\t\t\tpSample[j] = dlt;\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tpSample[j<<1] = lowbyte;\n\t\t\t\t\tpSample[(j<<1)+1] = dlt;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tbreak;\n\n\tcase RS_DMF8:\n\tcase RS_DMF16:\n\t\tlen = dwMemLength;\n\t\tif (len >= 4)\n\t\t{\n\t\t\tUINT maxlen = pIns->nLength;\n\t\t\tif (pIns->uFlags & CHN_16BIT) maxlen <<= 1;\n\t\t\tLPBYTE ibuf = (LPBYTE)lpMemFile, ibufmax = (LPBYTE)(lpMemFile+dwMemLength);\n\t\t\tlen = DMFUnpack((LPBYTE)pIns->pSample, ibuf, ibufmax, maxlen);\n\t\t}\n\t\tbreak;\n#endif\n\n#ifdef MODPLUG_TRACKER\n\t// PCM 24-bit signed -> load sample, and normalize it to 16-bit\n\tcase RS_PCM24S:\n\tcase RS_PCM32S:\n\t\tlen = pIns->nLength * 3;\n\t\tif (nFlags == RS_PCM32S) len += pIns->nLength;\n\t\tif (len > dwMemLength) break;\n\t\tif (len > 4*8)\n\t\t{\n\t\t\tUINT slsize = (nFlags == RS_PCM32S) ? 4 : 3;\n\t\t\tLPBYTE pSrc = (LPBYTE)lpMemFile;\n\t\t\tLONG max = 255;\n\t\t\tif (nFlags == RS_PCM32S) pSrc++;\n\t\t\tfor (UINT j=0; j<len; j+=slsize)\n\t\t\t{\n\t\t\t\tLONG l = ((((pSrc[j+2] << 8) + pSrc[j+1]) << 8) + pSrc[j]) << 8;\n\t\t\t\tl /= 256;\n\t\t\t\tif (l > max) max = l;\n\t\t\t\tif (-l > max) max = -l;\n\t\t\t}\n\t\t\tmax = (max / 128) + 1;\n\t\t\tint16_t *pDest = (int16_t *)pIns->pSample;\n\t\t\tfor (UINT k=0; k<len; k+=slsize)\n\t\t\t{\n\t\t\t\tLONG l = ((((pSrc[k+2] << 8) + pSrc[k+1]) << 8) + pSrc[k]) << 8;\n\t\t\t\t*pDest++ = (uint16_t)(l / max);\n\t\t\t}\n\t\t}\n\t\tbreak;\n\n\t// Stereo PCM 24-bit signed -> load sample, and normalize it to 16-bit\n\tcase RS_STIPCM24S:\n\tcase RS_STIPCM32S:\n\t\tlen = pIns->nLength * 6;\n\t\tif (nFlags == RS_STIPCM32S) len += pIns->nLength * 2;\n\t\tif (len > dwMemLength) break;\n\t\tif (len > 8*8)\n\t\t{\n\t\t\tUINT slsize = (nFlags == RS_STIPCM32S) ? 4 : 3;\n\t\t\tLPBYTE pSrc = (LPBYTE)lpMemFile;\n\t\t\tLONG max = 255;\n\t\t\tif (nFlags == RS_STIPCM32S) pSrc++;\n\t\t\tfor (UINT j=0; j<len; j+=slsize)\n\t\t\t{\n\t\t\t\tLONG l = ((((pSrc[j+2] << 8) + pSrc[j+1]) << 8) + pSrc[j]) << 8;\n\t\t\t\tl /= 256;\n\t\t\t\tif (l > max) max = l;\n\t\t\t\tif (-l > max) max = -l;\n\t\t\t}\n\t\t\tmax = (max / 128) + 1;\n\t\t\tint16_t *pDest = (int16_t *)pIns->pSample;\n\t\t\tfor (UINT k=0; k<len; k+=slsize)\n\t\t\t{\n\t\t\t\tLONG lr = ((((pSrc[k+2] << 8) + pSrc[k+1]) << 8) + pSrc[k]) << 8;\n\t\t\t\tk += slsize;\n\t\t\t\tLONG ll = ((((pSrc[k+2] << 8) + pSrc[k+1]) << 8) + pSrc[k]) << 8;\n\t\t\t\tpDest[0] = (int16_t)ll;\n\t\t\t\tpDest[1] = (int16_t)lr;\n\t\t\t\tpDest += 2;\n\t\t\t}\n\t\t}\n\t\tbreak;\n\n\t// 16-bit signed big endian interleaved stereo\n\tcase RS_STIPCM16M:\n\t\t{\n\t\t\tlen = pIns->nLength;\n\t\t\tif (len*4 > dwMemLength) len = dwMemLength >> 2;\n\t\t\tLPCBYTE psrc = (LPCBYTE)lpMemFile;\n\t\t\tint16_t *pSample = (int16_t *)pIns->pSample;\n\t\t\tfor (UINT j=0; j<len; j++)\n\t\t\t{\n\t\t\t\tpSample[j*2] = (int16_t)(((UINT)psrc[0] << 8) | (psrc[1]));\n\t\t\t\tpSample[j*2+1] = (int16_t)(((UINT)psrc[2] << 8) | (psrc[3]));\n\t\t\t\tpsrc += 4;\n\t\t\t}\n\t\t\tlen *= 4;\n\t\t}\n\t\tbreak;\n\n#endif // MODPLUG_TRACKER\n#endif // !MODPLUG_FASTSOUNDLIB\n#endif // !MODPLUG_BASIC_SUPPORT\n\n\t// Default: 8-bit signed PCM data\n\tdefault:\n\t\tlen = pIns->nLength;\n\t\tif (len > dwMemLength) len = pIns->nLength = dwMemLength;\n\t\tmemcpy(pIns->pSample, lpMemFile, len);\n\t}\n\tif (len > dwMemLength)\n\t{\n\t\tif (pIns->pSample)\n\t\t{\n\t\t\tpIns->nLength = 0;\n\t\t\tFreeSample(pIns->pSample);\n\t\t\tpIns->pSample = NULL;\n\t\t}\n\t\treturn 0;\n\t}\n\tAdjustSampleLoop(pIns);\n\treturn len;\n}\n\n\nvoid CSoundFile::AdjustSampleLoop(MODINSTRUMENT *pIns)\n//----------------------------------------------------\n{\n\tif (!pIns->pSample) return;\n\tif (pIns->nLength > MAX_SAMPLE_LENGTH) pIns->nLength = MAX_SAMPLE_LENGTH;\n\tif (pIns->nLoopEnd > pIns->nLength) pIns->nLoopEnd = pIns->nLength;\n\tif (pIns->nLoopStart > pIns->nLength+2) pIns->nLoopStart = pIns->nLength+2;\n\tif (pIns->nLoopStart+2 >= pIns->nLoopEnd)\n\t{\n\t\tpIns->nLoopStart = pIns->nLoopEnd = 0;\n\t\tpIns->uFlags &= ~CHN_LOOP;\n\t}\n\tUINT len = pIns->nLength;\n\tif (pIns->uFlags & CHN_16BIT)\n\t{\n\t\tint16_t *pSample = (int16_t *)pIns->pSample;\n\t\t// Adjust end of sample\n\t\tif (pIns->uFlags & CHN_STEREO)\n\t\t{\n\t\t\tpSample[len*2+6] = pSample[len*2+4] = pSample[len*2+2] = pSample[len*2] = 0;\n\t\t\tpSample[len*2+7] = pSample[len*2+5] = pSample[len*2+3] = pSample[len*2+1] = 0;\n\t\t} else\n\t\t{\n\t\t\tpSample[len+4] = pSample[len+3] = pSample[len+2] = pSample[len+1] = pSample[len] = 0;\n\t\t}\n\t\tif ((pIns->uFlags & (CHN_LOOP|CHN_PINGPONGLOOP|CHN_STEREO)) == CHN_LOOP)\n\t\t{\n\t\t\t// Fix bad loops\n\t\t\tif ((pIns->nLoopEnd+3 >= pIns->nLength) || (m_nType & MOD_TYPE_S3M))\n\t\t\t{\n\t\t\t\tpSample[pIns->nLoopEnd] = pSample[pIns->nLoopStart];\n\t\t\t\tpSample[pIns->nLoopEnd+1] = pSample[pIns->nLoopStart+1];\n\t\t\t\tpSample[pIns->nLoopEnd+2] = pSample[pIns->nLoopStart+2];\n\t\t\t\tpSample[pIns->nLoopEnd+3] = pSample[pIns->nLoopStart+3];\n\t\t\t\tpSample[pIns->nLoopEnd+4] = pSample[pIns->nLoopStart+4];\n\t\t\t}\n\t\t}\n\t} else\n\t{\n\t\tsigned char *pSample = pIns->pSample;\n#ifndef MODPLUG_FASTSOUNDLIB\n\t\t// Crappy samples (except chiptunes) ?\n\t\tif ((pIns->nLength > 0x100) && (m_nType & (MOD_TYPE_MOD|MOD_TYPE_S3M))\n\t\t && (!(pIns->uFlags & CHN_STEREO)))\n\t\t{\n\t\t\tint smpend = pSample[pIns->nLength-1], smpfix = 0, kscan;\n\t\t\tfor (kscan=pIns->nLength-1; kscan>0; kscan--)\n\t\t\t{\n\t\t\t\tsmpfix = pSample[kscan-1];\n\t\t\t\tif (smpfix != smpend) break;\n\t\t\t}\n\t\t\tint delta = smpfix - smpend;\n\t\t\tif (((!(pIns->uFlags & CHN_LOOP)) || (kscan > (int)pIns->nLoopEnd))\n\t\t\t && ((delta < -8) || (delta > 8)))\n\t\t\t{\n\t\t\t\twhile (kscan<(int)pIns->nLength)\n\t\t\t\t{\n\t\t\t\t\tif (!(kscan & 7))\n\t\t\t\t\t{\n\t\t\t\t\t\tif (smpfix > 0) smpfix--;\n\t\t\t\t\t\tif (smpfix < 0) smpfix++;\n\t\t\t\t\t}\n\t\t\t\t\tpSample[kscan] = (signed char)smpfix;\n\t\t\t\t\tkscan++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n#endif\n\t\t// Adjust end of sample\n\t\tif (pIns->uFlags & CHN_STEREO)\n\t\t{\n\t\t\tpSample[len*2+6] = pSample[len*2+4] = pSample[len*2+2] = pSample[len*2] = 0;\n\t\t\tpSample[len*2+7] = pSample[len*2+5] = pSample[len*2+3] = pSample[len*2+1] = 0;\n\n\t\t} else\n\t\t{\n\t\t\tpSample[len+4] = pSample[len+3] = pSample[len+2] = pSample[len+1] = pSample[len] = 0;\n\t\t}\n\t\tif ((pIns->uFlags & (CHN_LOOP|CHN_PINGPONGLOOP|CHN_STEREO)) == CHN_LOOP)\n\t\t{\n\t\t\tif ((pIns->nLoopEnd+3 >= pIns->nLength) || (m_nType & (MOD_TYPE_MOD|MOD_TYPE_S3M)))\n\t\t\t{\n\t\t\t\tpSample[pIns->nLoopEnd] = pSample[pIns->nLoopStart];\n\t\t\t\tpSample[pIns->nLoopEnd+1] = pSample[pIns->nLoopStart+1];\n\t\t\t\tpSample[pIns->nLoopEnd+2] = pSample[pIns->nLoopStart+2];\n\t\t\t\tpSample[pIns->nLoopEnd+3] = pSample[pIns->nLoopStart+3];\n\t\t\t\tpSample[pIns->nLoopEnd+4] = pSample[pIns->nLoopStart+4];\n\t\t\t}\n\t\t}\n\t}\n}\n\n\n/////////////////////////////////////////////////////////////\n// Transpose <-> Frequency conversions\n\n// returns 8363*2^((transp*128+ftune)/(12*128))\nDWORD CSoundFile::TransposeToFrequency(int transp, int ftune)\n//-----------------------------------------------------------\n{\n\n#ifdef MSC_VER\n\tconst float _fbase = 8363;\n\tconst float _factor = 1.0f/(12.0f*128.0f);\n\tint result;\n\tDWORD freq;\n\n\ttransp = (transp << 7) + ftune;\n\t_asm {\n\tfild transp\n\tfld _factor\n\tfmulp st(1), st(0)\n\tfist result\n\tfisub result\n\tf2xm1\n\tfild result\n\tfld _fbase\n\tfscale\n\tfstp st(1)\n\tfmul st(1), st(0)\n\tfaddp st(1), st(0)\n\tfistp freq\n\t}\n\tUINT derr = freq % 11025;\n\tif (derr <= 8) freq -= derr;\n\tif (derr >= 11015) freq += 11025-derr;\n\tderr = freq % 1000;\n\tif (derr <= 5) freq -= derr;\n\tif (derr >= 995) freq += 1000-derr;\n\treturn freq;\n#else\n\t//---GCCFIX:  Removed assembly.\n\treturn (DWORD)(8363*pow(2, (double)(transp*128+ftune)/(1536)));\n#endif\n}\n\n\n// returns 12*128*log2(freq/8363)\nint CSoundFile::FrequencyToTranspose(DWORD freq)\n//----------------------------------------------\n{\n\n#ifdef MSC_VER\n\tconst float _f1_8363 = 1.0f / 8363.0f;\n\tconst float _factor = 128 * 12;\n\tLONG result;\n\n\tif (!freq) return 0;\n\t_asm {\n\tfld _factor\n\tfild freq\n\tfld _f1_8363\n\tfmulp st(1), st(0)\n\tfyl2x\n\tfistp result\n\t}\n\treturn result;\n#else\n\t//---GCCFIX: Removed assembly.\n\treturn int(1536*(log(freq/8363.0)/log(2.0)));\n#endif\n}\n\n\nvoid CSoundFile::FrequencyToTranspose(MODINSTRUMENT *psmp)\n//--------------------------------------------------------\n{\n\tint f2t = FrequencyToTranspose(psmp->nC4Speed);\n\tint transp = f2t >> 7;\n\tint ftune = f2t & 0x7F;\n\tif (ftune > 80)\n\t{\n\t\ttransp++;\n\t\tftune -= 128;\n\t}\n\tif (transp > 127) transp = 127;\n\tif (transp < -127) transp = -127;\n\tpsmp->RelativeTone = transp;\n\tpsmp->nFineTune = ftune;\n}\n\n\nvoid CSoundFile::CheckCPUUsage(UINT nCPU)\n//---------------------------------------\n{\n\tif (nCPU > 100) nCPU = 100;\n\tgnCPUUsage = nCPU;\n\tif (nCPU < 90)\n\t{\n\t\tm_dwSongFlags &= ~SONG_CPUVERYHIGH;\n\t} else\n\tif ((m_dwSongFlags & SONG_CPUVERYHIGH) && (nCPU >= 94))\n\t{\n\t\tUINT i=MAX_CHANNELS;\n\t\twhile (i >= 8)\n\t\t{\n\t\t\ti--;\n\t\t\tif (Chn[i].nLength)\n\t\t\t{\n\t\t\t\tChn[i].nLength = Chn[i].nPos = 0;\n\t\t\t\tnCPU -= 2;\n\t\t\t\tif (nCPU < 94) break;\n\t\t\t}\n\t\t}\n\t} else\n\tif (nCPU > 90)\n\t{\n\t\tm_dwSongFlags |= SONG_CPUVERYHIGH;\n\t}\n}\n\n\nBOOL CSoundFile::SetPatternName(UINT nPat, LPCSTR lpszName)\n//---------------------------------------------------------\n{\n        char szName[MAX_PATTERNNAME] = \"\";\n\t// check input arguments\n\tif (nPat >= MAX_PATTERNS) return FALSE;\n\tif (lpszName == NULL) return(FALSE);\n\n\tif (lpszName) lstrcpyn(szName, lpszName, MAX_PATTERNNAME);\n\tszName[MAX_PATTERNNAME-1] = 0;\n\tif (!m_lpszPatternNames) m_nPatternNames = 0;\n\tif (nPat >= m_nPatternNames)\n\t{\n\t\tif (!lpszName[0]) return TRUE;\n\t\tUINT len = (nPat+1)*MAX_PATTERNNAME;\n\t\tchar *p = new char[len];\n\t\tif (!p) return FALSE;\n\t\tmemset(p, 0, len);\n\t\tif (m_lpszPatternNames)\n\t\t{\n\t\t\tmemcpy(p, m_lpszPatternNames, m_nPatternNames * MAX_PATTERNNAME);\n\t\t\tdelete [] m_lpszPatternNames;\n\t\t\tm_lpszPatternNames = NULL;\n\t\t}\n\t\tm_lpszPatternNames = p;\n\t\tm_nPatternNames = nPat + 1;\n\t}\n\tmemcpy(m_lpszPatternNames + nPat * MAX_PATTERNNAME, szName, MAX_PATTERNNAME);\n\treturn TRUE;\n}\n\n\nBOOL CSoundFile::GetPatternName(UINT nPat, LPSTR lpszName, UINT cbSize) const\n//---------------------------------------------------------------------------\n{\n\tif ((!lpszName) || (!cbSize)) return FALSE;\n\tlpszName[0] = 0;\n\tif (cbSize > MAX_PATTERNNAME) cbSize = MAX_PATTERNNAME;\n\tif ((m_lpszPatternNames) && (nPat < m_nPatternNames))\n\t{\n\t\tmemcpy(lpszName, m_lpszPatternNames + nPat * MAX_PATTERNNAME, cbSize);\n\t\tlpszName[cbSize-1] = 0;\n\t\treturn TRUE;\n\t}\n\treturn FALSE;\n}\n\n\n#ifndef MODPLUG_FASTSOUNDLIB\n\nUINT CSoundFile::DetectUnusedSamples(BOOL *pbIns)\n//-----------------------------------------------\n{\n\tUINT nExt = 0;\n\n\tif (!pbIns) return 0;\n\tif (m_nInstruments)\n\t{\n\t\tmemset(pbIns, 0, MAX_SAMPLES * sizeof(BOOL));\n\t\tfor (UINT ipat=0; ipat<MAX_PATTERNS; ipat++)\n\t\t{\n\t\t\tMODCOMMAND *p = Patterns[ipat];\n\t\t\tif (p)\n\t\t\t{\n\t\t\t\tUINT jmax = PatternSize[ipat] * m_nChannels;\n\t\t\t\tfor (UINT j=0; j<jmax; j++, p++)\n\t\t\t\t{\n\t\t\t\t\tif ((p->note) && (p->note <= NOTE_MAX))\n\t\t\t\t\t{\n\t\t\t\t\t\tif ((p->instr) && (p->instr < MAX_INSTRUMENTS))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tINSTRUMENTHEADER *penv = Headers[p->instr];\n\t\t\t\t\t\t\tif (penv)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUINT n = penv->Keyboard[p->note-1];\n\t\t\t\t\t\t\t\tif (n < MAX_SAMPLES) pbIns[n] = TRUE;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfor (UINT k=1; k<=m_nInstruments; k++)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tINSTRUMENTHEADER *penv = Headers[k];\n\t\t\t\t\t\t\t\tif (penv)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tUINT n = penv->Keyboard[p->note-1];\n\t\t\t\t\t\t\t\t\tif (n < MAX_SAMPLES) pbIns[n] = TRUE;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (UINT ichk=1; ichk<=m_nSamples; ichk++)\n\t\t{\n\t\t\tif ((!pbIns[ichk]) && (Ins[ichk].pSample)) nExt++;\n\t\t}\n\t}\n\treturn nExt;\n}\n\n\nBOOL CSoundFile::RemoveSelectedSamples(BOOL *pbIns)\n//-------------------------------------------------\n{\n\tif (!pbIns) return FALSE;\n\tfor (UINT j=1; j<MAX_SAMPLES; j++)\n\t{\n\t\tif ((!pbIns[j]) && (Ins[j].pSample))\n\t\t{\n\t\t\tDestroySample(j);\n\t\t\tif ((j == m_nSamples) && (j > 1)) m_nSamples--;\n\t\t}\n\t}\n\treturn TRUE;\n}\n\n\nBOOL CSoundFile::DestroySample(UINT nSample)\n//------------------------------------------\n{\n\tif ((!nSample) || (nSample >= MAX_SAMPLES)) return FALSE;\n\tif (!Ins[nSample].pSample) return TRUE;\n\tMODINSTRUMENT *pins = &Ins[nSample];\n\tsigned char *pSample = pins->pSample;\n\tpins->pSample = NULL;\n\tpins->nLength = 0;\n\tpins->uFlags &= ~(CHN_16BIT);\n\tfor (UINT i=0; i<MAX_CHANNELS; i++)\n\t{\n\t\tif (Chn[i].pSample == pSample)\n\t\t{\n\t\t\tChn[i].nPos = Chn[i].nLength = 0;\n\t\t\tChn[i].pSample = Chn[i].pCurrentSample = NULL;\n\t\t}\n\t}\n\tFreeSample(pSample);\n\treturn TRUE;\n}\n\n#endif // MODPLUG_FASTSOUNDLIB\n"
  },
  {
    "path": "contrib/libmodplug/src/sndmix.cpp",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n*/\n\n#include \"libmodplug/stdafx.h\"\n#include \"libmodplug/sndfile.h\"\n#include \"tables.h\"\n\n#ifdef MODPLUG_TRACKER\n#define ENABLE_STEREOVU\n#endif\n\n// Volume ramp length, in 1/10 ms\n#define VOLUMERAMPLEN\t146\t// 1.46ms = 64 samples at 44.1kHz\n\n#if defined(MODPLUG_PLAYER) || defined(ENABLE_STEREOVU)\n// VU-Meter\n#define VUMETER_DECAY\t\t4\n#endif\n\n// SNDMIX: These are global flags for playback control (first two configurable via SetMixConfig)\nUINT CSoundFile::m_nStereoSeparation = 128;\nUINT CSoundFile::m_nMaxMixChannels = 32;\nLONG CSoundFile::m_nStreamVolume = 0x8000;\n// Mixing Configuration (SetWaveConfig)\nDWORD CSoundFile::gdwSysInfo = 0;\nDWORD CSoundFile::gnChannels = 1;\nDWORD CSoundFile::gdwSoundSetup = 0;\nDWORD CSoundFile::gdwMixingFreq = 44100;\nDWORD CSoundFile::gnBitsPerSample = 16;\n// Mixing data initialized in\nUINT CSoundFile::gnAGC = AGC_UNITY;\nUINT CSoundFile::gnVolumeRampSamples = 64;\nUINT CSoundFile::gnVUMeter = 0;\nUINT CSoundFile::gnCPUUsage = 0;\nLPSNDMIXHOOKPROC CSoundFile::gpSndMixHook = NULL;\nPMIXPLUGINCREATEPROC CSoundFile::gpMixPluginCreateProc = NULL;\nLONG gnDryROfsVol = 0;\nLONG gnDryLOfsVol = 0;\nLONG gnRvbROfsVol = 0;\nLONG gnRvbLOfsVol = 0;\nint gbInitPlugins = 0;\n\ntypedef DWORD (MPPASMCALL * LPCONVERTPROC)(LPVOID, int *, DWORD, LPLONG, LPLONG);\n\nextern DWORD MPPASMCALL X86_Convert32To8(LPVOID lpBuffer, int *, DWORD nSamples, LPLONG, LPLONG);\nextern DWORD MPPASMCALL X86_Convert32To16(LPVOID lpBuffer, int *, DWORD nSamples, LPLONG, LPLONG);\nextern DWORD MPPASMCALL X86_Convert32To24(LPVOID lpBuffer, int *, DWORD nSamples, LPLONG, LPLONG);\nextern DWORD MPPASMCALL X86_Convert32To32(LPVOID lpBuffer, int *, DWORD nSamples, LPLONG, LPLONG);\nextern UINT MPPASMCALL X86_AGC(int *pBuffer, UINT nSamples, UINT nAGC);\nextern VOID MPPASMCALL X86_Dither(int *pBuffer, UINT nSamples, UINT nBits);\nextern VOID MPPASMCALL X86_InterleaveFrontRear(int *pFrontBuf, int *pRearBuf, DWORD nSamples);\nextern VOID MPPASMCALL X86_StereoFill(int *pBuffer, UINT nSamples, LPLONG lpROfs, LPLONG lpLOfs);\nextern VOID MPPASMCALL X86_MonoFromStereo(int *pMixBuf, UINT nSamples);\n\nextern int MixSoundBuffer[MIXBUFFERSIZE*4];\nextern int MixRearBuffer[MIXBUFFERSIZE*2];\nUINT gnReverbSend;\n\n\n// Log tables for pre-amp\n// We don't want the tracker to get too loud\nconst UINT PreAmpTable[16] =\n{\n\t0x60, 0x60, 0x60, 0x70,\t// 0-7\n\t0x80, 0x88, 0x90, 0x98,\t// 8-15\n\t0xA0, 0xA4, 0xA8, 0xB0,\t// 16-23\n\t0xB4, 0xB8, 0xBC, 0xC0,\t// 24-31\n};\n\nconst UINT PreAmpAGCTable[16] =\n{\n\t0x60, 0x60, 0x60, 0x60,\n\t0x68, 0x70, 0x78, 0x80,\n\t0x84, 0x88, 0x8C, 0x90,\n\t0x94, 0x98, 0x9C, 0xA0,\n};\n\n\n// Return (a*b)/c - no divide error\nint _muldiv(long a, long b, long c)\n{\n#ifdef MSC_VER\n\tint sign, result;\n\t_asm {\n\tmov eax, a\n\tmov ebx, b\n\tor eax, eax\n\tmov edx, eax\n\tjge aneg\n\tneg eax\naneg:\n\txor edx, ebx\n\tor ebx, ebx\n\tmov ecx, c\n\tjge bneg\n\tneg ebx\nbneg:\n\txor edx, ecx\n\tor ecx, ecx\n\tmov sign, edx\n\tjge cneg\n\tneg ecx\ncneg:\n\tmul ebx\n\tcmp edx, ecx\n\tjae diverr\n\tdiv ecx\n\tjmp ok\ndiverr:\n\tmov eax, 0x7fffffff\nok:\n\tmov edx, sign\n\tor edx, edx\n\tjge rneg\n\tneg eax\nrneg:\n\tmov result, eax\n\t}\n\treturn result;\n#else\n\treturn ((uint64_t) a * (uint64_t) b ) / c;\n#endif\n}\n\n\n// Return (a*b+c/2)/c - no divide error\nint _muldivr(long a, long b, long c)\n{\n#ifdef MSC_VER\n\tint sign, result;\n\t_asm {\n\tmov eax, a\n\tmov ebx, b\n\tor eax, eax\n\tmov edx, eax\n\tjge aneg\n\tneg eax\naneg:\n\txor edx, ebx\n\tor ebx, ebx\n\tmov ecx, c\n\tjge bneg\n\tneg ebx\nbneg:\n\txor edx, ecx\n\tor ecx, ecx\n\tmov sign, edx\n\tjge cneg\n\tneg ecx\ncneg:\n\tmul ebx\n\tmov ebx, ecx\n\tshr ebx, 1\n\tadd eax, ebx\n\tadc edx, 0\n\tcmp edx, ecx\n\tjae diverr\n\tdiv ecx\n\tjmp ok\ndiverr:\n\tmov eax, 0x7fffffff\nok:\n\tmov edx, sign\n\tor edx, edx\n\tjge rneg\n\tneg eax\nrneg:\n\tmov result, eax\n\t}\n\treturn result;\n#else\n\treturn ((uint64_t) a * (uint64_t) b + (c >> 1)) / c;\n#endif\n}\n\n\nBOOL CSoundFile::InitPlayer(BOOL bReset)\n//--------------------------------------\n{\n\tif (m_nMaxMixChannels > MAX_CHANNELS) m_nMaxMixChannels = MAX_CHANNELS;\n\tif (gdwMixingFreq < 4000) gdwMixingFreq = 4000;\n\tif (gdwMixingFreq > MAX_SAMPLE_RATE) gdwMixingFreq = MAX_SAMPLE_RATE;\n\tgnVolumeRampSamples = (gdwMixingFreq * VOLUMERAMPLEN) / 100000;\n\tif (gnVolumeRampSamples < 8) gnVolumeRampSamples = 8;\n\tgnDryROfsVol = gnDryLOfsVol = 0;\n\tgnRvbROfsVol = gnRvbLOfsVol = 0;\n\tif (bReset)\n\t{\n\t\tgnVUMeter = 0;\n\t\tgnCPUUsage = 0;\n\t}\n\tgbInitPlugins = (bReset) ? 3 : 1;\n\tInitializeDSP(bReset);\n\treturn TRUE;\n}\n\n\nBOOL CSoundFile::FadeSong(UINT msec)\n//----------------------------------\n{\n\tLONG nsamples = _muldiv(msec, gdwMixingFreq, 1000);\n\tif (nsamples <= 0) return FALSE;\n\tif (nsamples > 0x100000) nsamples = 0x100000;\n\tm_nBufferCount = nsamples;\n\tLONG nRampLength = m_nBufferCount;\n\t// Ramp everything down\n\tfor (UINT noff=0; noff < m_nMixChannels; noff++)\n\t{\n\t\tMODCHANNEL *pramp = &Chn[ChnMix[noff]];\n\t\tif (!pramp) continue;\n\t\tpramp->nNewLeftVol = pramp->nNewRightVol = 0;\n\t\tpramp->nRightRamp = (-pramp->nRightVol << VOLUMERAMPPRECISION) / nRampLength;\n\t\tpramp->nLeftRamp = (-pramp->nLeftVol << VOLUMERAMPPRECISION) / nRampLength;\n\t\tpramp->nRampRightVol = pramp->nRightVol << VOLUMERAMPPRECISION;\n\t\tpramp->nRampLeftVol = pramp->nLeftVol << VOLUMERAMPPRECISION;\n\t\tpramp->nRampLength = nRampLength;\n\t\tpramp->dwFlags |= CHN_VOLUMERAMP;\n\t}\n\tm_dwSongFlags |= SONG_FADINGSONG;\n\treturn TRUE;\n}\n\n\nBOOL CSoundFile::GlobalFadeSong(UINT msec)\n//----------------------------------------\n{\n\tif (m_dwSongFlags & SONG_GLOBALFADE) return FALSE;\n\tm_nGlobalFadeMaxSamples = _muldiv(msec, gdwMixingFreq, 1000);\n\tm_nGlobalFadeSamples = m_nGlobalFadeMaxSamples;\n\tm_dwSongFlags |= SONG_GLOBALFADE;\n\treturn TRUE;\n}\n\n\nUINT CSoundFile::Read(LPVOID lpDestBuffer, UINT cbBuffer)\n//-------------------------------------------------------\n{\n\tLPBYTE lpBuffer = (LPBYTE)lpDestBuffer;\n\tLPCONVERTPROC pCvt = X86_Convert32To8;\n\tUINT lRead, lMax, lSampleSize, lCount, lSampleCount, nStat=0;\n\tLONG nVUMeterMin = 0x7FFFFFFF, nVUMeterMax = -0x7FFFFFFF;\n\tUINT nMaxPlugins;\n\n\t{\n\t\tnMaxPlugins = MAX_MIXPLUGINS;\n\t\twhile ((nMaxPlugins > 0) && (!m_MixPlugins[nMaxPlugins-1].pMixPlugin)) nMaxPlugins--;\n\t}\n\tm_nMixStat = 0;\n\tlSampleSize = gnChannels;\n\tif (gnBitsPerSample == 16) { lSampleSize *= 2; pCvt = X86_Convert32To16; }\n#ifndef MODPLUG_FASTSOUNDLIB\n\telse if (gnBitsPerSample == 24) { lSampleSize *= 3; pCvt = X86_Convert32To24; }\n\telse if (gnBitsPerSample == 32) { lSampleSize *= 4; pCvt = X86_Convert32To32; }\n#endif\n\tlMax = cbBuffer / lSampleSize;\n\tif ((!lMax) || (!lpBuffer) || (!m_nChannels)) return 0;\n\tlRead = lMax;\n\tif (m_dwSongFlags & SONG_ENDREACHED) goto MixDone;\n\twhile (lRead > 0)\n\t{\n\t\t// Update Channel Data\n\t\tif (!m_nBufferCount)\n\t\t{\n#ifndef MODPLUG_FASTSOUNDLIB\n\t\t\tif (m_dwSongFlags & SONG_FADINGSONG)\n\t\t\t{\n\t\t\t\tm_dwSongFlags |= SONG_ENDREACHED;\n\t\t\t\tm_nBufferCount = lRead;\n\t\t\t} else\n#endif\n\t\t\tif (!ReadNote())\n\t\t\t{\n#ifndef MODPLUG_FASTSOUNDLIB\n\t\t\t\tif (!FadeSong(FADESONGDELAY))\n#endif\n\t\t\t\t{\n\t\t\t\t\tm_dwSongFlags |= SONG_ENDREACHED;\n\t\t\t\t\tif (lRead == lMax) goto MixDone;\n\t\t\t\t\tm_nBufferCount = lRead;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlCount = m_nBufferCount;\n\t\tif (lCount > MIXBUFFERSIZE) lCount = MIXBUFFERSIZE;\n\t\tif (lCount > lRead) lCount = lRead;\n\t\tif (!lCount) break;\n\t\tlSampleCount = lCount;\n#ifndef MODPLUG_NO_REVERB\n\t\tgnReverbSend = 0;\n#endif\n\t\t// Resetting sound buffer\n\t\tX86_StereoFill(MixSoundBuffer, lSampleCount, &gnDryROfsVol, &gnDryLOfsVol);\n\t\tif (gnChannels >= 2)\n\t\t{\n\t\t\tlSampleCount *= 2;\n\t\t\tm_nMixStat += CreateStereoMix(lCount);\n\t\t\tProcessStereoDSP(lCount);\n\t\t} else\n\t\t{\n\t\t\tm_nMixStat += CreateStereoMix(lCount);\n\t\t\tif (nMaxPlugins) ProcessPlugins(lCount);\n\t\t\tProcessStereoDSP(lCount);\n\t\t\tX86_MonoFromStereo(MixSoundBuffer, lCount);\n\t\t}\n\t\tnStat++;\n#ifndef NO_AGC\n\t\t// Automatic Gain Control\n\t\tif (gdwSoundSetup & SNDMIX_AGC) ProcessAGC(lSampleCount);\n#endif\n\t\tUINT lTotalSampleCount = lSampleCount;\n#ifndef MODPLUG_FASTSOUNDLIB\n\t\t// Multichannel\n\t\tif (gnChannels > 2)\n\t\t{\n\t\t\tX86_InterleaveFrontRear(MixSoundBuffer, MixRearBuffer, lSampleCount);\n\t\t\tlTotalSampleCount *= 2;\n\t\t}\n\t\t// Hook Function\n\t\tif (gpSndMixHook)\n\t\t{\n\t\t\tgpSndMixHook(MixSoundBuffer, lTotalSampleCount, gnChannels);\n\t\t}\n#endif\n\t\t// Perform clipping + VU-Meter\n\t\tlpBuffer += pCvt(lpBuffer, MixSoundBuffer, lTotalSampleCount, &nVUMeterMin, &nVUMeterMax);\n\t\t// Buffer ready\n\t\tlRead -= lCount;\n\t\tm_nBufferCount -= lCount;\n\t}\nMixDone:\n\tif (lRead) memset(lpBuffer, (gnBitsPerSample == 8) ? 0x80 : 0, lRead * lSampleSize);\n\t// VU-Meter\n\tnVUMeterMin >>= (24-MIXING_ATTENUATION);\n\tnVUMeterMax >>= (24-MIXING_ATTENUATION);\n\tif (nVUMeterMax < nVUMeterMin) nVUMeterMax = nVUMeterMin;\n\tif ((gnVUMeter = (UINT)(nVUMeterMax - nVUMeterMin)) > 0xFF) gnVUMeter = 0xFF;\n\tif (nStat) { m_nMixStat += nStat-1; m_nMixStat /= nStat; }\n\treturn lMax - lRead;\n}\n\n\n\n/////////////////////////////////////////////////////////////////////////////\n// Handles navigation/effects\n\nBOOL CSoundFile::ProcessRow()\n//---------------------------\n{\n\tif (++m_nTickCount >= m_nMusicSpeed * (m_nPatternDelay+1) + m_nFrameDelay)\n\t{\n\t\tm_nPatternDelay = 0;\n\t\tm_nFrameDelay = 0;\n\t\tm_nTickCount = 0;\n\t\tm_nRow = m_nNextRow;\n\t\t// Reset Pattern Loop Effect\n\t\tif (m_nCurrentPattern != m_nNextPattern) m_nCurrentPattern = m_nNextPattern;\n\t\t// Check if pattern is valid\n\t\tif (!(m_dwSongFlags & SONG_PATTERNLOOP))\n\t\t{\n\t\t\tm_nPattern = (m_nCurrentPattern < MAX_ORDERS) ? Order[m_nCurrentPattern] : 0xFF;\n\t\t\tif ((m_nPattern < MAX_PATTERNS) && (!Patterns[m_nPattern])) m_nPattern = 0xFE;\n\t\t\twhile (m_nPattern >= MAX_PATTERNS)\n\t\t\t{\n\t\t\t\t// End of song ?\n\t\t\t\tif ((m_nPattern == 0xFF) || (m_nCurrentPattern >= MAX_ORDERS))\n\t\t\t\t{\n\t\t\t\t\tif (!m_nRepeatCount)\n\t\t\t\t\t\treturn FALSE;     //never repeat entire song\n\t\t\t\t\tif (!m_nRestartPos)\n\t\t\t\t\t{\n\t\t\t\t\t\tm_nMusicSpeed = m_nDefaultSpeed;\n\t\t\t\t\t\tm_nMusicTempo = m_nDefaultTempo;\n\t\t\t\t\t\tm_nGlobalVolume = m_nDefaultGlobalVolume;\n\t\t\t\t\t\tfor (UINT i=0; i<MAX_CHANNELS; i++)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tChn[i].dwFlags |= CHN_NOTEFADE | CHN_KEYOFF;\n\t\t\t\t\t\t\tChn[i].nFadeOutVol = 0;\n\t\t\t\t\t\t\tif (i < m_nChannels)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tChn[i].nGlobalVol = ChnSettings[i].nVolume;\n\t\t\t\t\t\t\t\tChn[i].nVolume = ChnSettings[i].nVolume;\n\t\t\t\t\t\t\t\tChn[i].nPan = ChnSettings[i].nPan;\n\t\t\t\t\t\t\t\tChn[i].nPanSwing = Chn[i].nVolSwing = 0;\n\t\t\t\t\t\t\t\tChn[i].nOldVolParam = 0;\n\t\t\t\t\t\t\t\tChn[i].nOldOffset = 0;\n\t\t\t\t\t\t\t\tChn[i].nOldHiOffset = 0;\n\t\t\t\t\t\t\t\tChn[i].nPortamentoDest = 0;\n\t\t\t\t\t\t\t\tif (!Chn[i].nLength)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tChn[i].dwFlags = ChnSettings[i].dwFlags;\n\t\t\t\t\t\t\t\t\tChn[i].nLoopStart = 0;\n\t\t\t\t\t\t\t\t\tChn[i].nLoopEnd = 0;\n\t\t\t\t\t\t\t\t\tChn[i].pHeader = NULL;\n\t\t\t\t\t\t\t\t\tChn[i].pSample = NULL;\n\t\t\t\t\t\t\t\t\tChn[i].pInstrument = NULL;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (m_nRepeatCount > 0) m_nRepeatCount--;\n\t\t\t\t\tm_nCurrentPattern = m_nRestartPos;\n\t\t\t\t\tm_nRow = 0;\n\t\t\t\t\tif ((Order[m_nCurrentPattern] >= MAX_PATTERNS) || (!Patterns[Order[m_nCurrentPattern]])) return FALSE;\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tm_nCurrentPattern++;\n\t\t\t\t}\n\t\t\t\tm_nPattern = (m_nCurrentPattern < MAX_ORDERS) ? Order[m_nCurrentPattern] : 0xFF;\n\t\t\t\tif ((m_nPattern < MAX_PATTERNS) && (!Patterns[m_nPattern])) m_nPattern = 0xFE;\n\t\t\t}\n\t\t\tm_nNextPattern = m_nCurrentPattern;\n\t\t}\n\t\t// Weird stuff?\n\t\tif ((m_nPattern >= MAX_PATTERNS) || (!Patterns[m_nPattern]) ||\n\t\t\tPatternSize[m_nPattern] == 0) return FALSE;\n\t\t// Should never happen\n\t\tif (m_nRow >= PatternSize[m_nPattern]) m_nRow = 0;\n\t\tm_nNextRow = m_nRow + 1;\n\t\tif (m_nNextRow >= PatternSize[m_nPattern])\n\t\t{\n\t\t\tif (!(m_dwSongFlags & SONG_PATTERNLOOP)) m_nNextPattern = m_nCurrentPattern + 1;\n\t\t\tm_nNextRow = m_nNextStartRow;\n\t\t\tm_nNextStartRow = 0;\n\t\t}\n\t\t// Reset channel values\n\t\tMODCHANNEL *pChn = Chn;\n\t\tMODCOMMAND *m = Patterns[m_nPattern] + m_nRow * m_nChannels;\n\t\tfor (UINT nChn=0; nChn<m_nChannels; pChn++, nChn++, m++)\n\t\t{\n\t\t\tpChn->nRowNote = m->note;\n\t\t\tpChn->nRowInstr = m->instr;\n\t\t\tpChn->nRowVolCmd = m->volcmd;\n\t\t\tpChn->nRowVolume = m->vol;\n\t\t\tpChn->nRowCommand = m->command;\n\t\t\tpChn->nRowParam = m->param;\n\n\t\t\tpChn->nLeftVol = pChn->nNewLeftVol;\n\t\t\tpChn->nRightVol = pChn->nNewRightVol;\n\t\t\tpChn->dwFlags &= ~(CHN_PORTAMENTO | CHN_VIBRATO | CHN_TREMOLO | CHN_PANBRELLO);\n\t\t\tpChn->nCommand = 0;\n\t\t}\n\t}\n\t// Should we process tick0 effects?\n\tif (!m_nMusicSpeed) m_nMusicSpeed = 1;\n\tm_dwSongFlags |= SONG_FIRSTTICK;\n\tif (m_nTickCount)\n\t{\n\t\tm_dwSongFlags &= ~SONG_FIRSTTICK;\n\t\tif ((!(m_nType & MOD_TYPE_XM)) && (m_nTickCount < m_nMusicSpeed * (1 + m_nPatternDelay)))\n\t\t{\n\t\t\tif (!(m_nTickCount % m_nMusicSpeed)) m_dwSongFlags |= SONG_FIRSTTICK;\n\t\t}\n\n\t}\n\t// Update Effects\n\treturn ProcessEffects();\n}\n\n\n////////////////////////////////////////////////////////////////////////////////////////////\n// Handles envelopes & mixer setup\n\nBOOL CSoundFile::ReadNote()\n//-------------------------\n{\n\tif (!ProcessRow()) return FALSE;\n\t////////////////////////////////////////////////////////////////////////////////////\n\tm_nTotalCount++;\n\tif (!m_nMusicTempo) return FALSE;\n\tm_nBufferCount = (gdwMixingFreq * 5 * m_nTempoFactor) / (m_nMusicTempo << 8);\n\t// Master Volume + Pre-Amplification / Attenuation setup\n\tDWORD nMasterVol;\n\t{\n\t\tint nchn32 = (m_nChannels < 32) ? m_nChannels : 31;\n\t\tif ((m_nType & MOD_TYPE_IT) && (m_nInstruments) && (nchn32 < 6)) nchn32 = 6;\n\t\tint realmastervol = m_nMasterVolume;\n\t\tif (realmastervol > 0x80)\n\t\t{\n\t\t\trealmastervol = 0x80 + ((realmastervol - 0x80) * (nchn32+4)) / 16;\n\t\t}\n\t\tUINT attenuation = (gdwSoundSetup & SNDMIX_AGC) ? PreAmpAGCTable[nchn32>>1] : PreAmpTable[nchn32>>1];\n\t\tDWORD mastervol = (realmastervol * (m_nSongPreAmp + 0x10)) >> 6;\n\t\tif (mastervol > 0x200) mastervol = 0x200;\n\t\tif ((m_dwSongFlags & SONG_GLOBALFADE) && (m_nGlobalFadeMaxSamples))\n\t\t{\n\t\t\tmastervol = _muldiv(mastervol, m_nGlobalFadeSamples, m_nGlobalFadeMaxSamples);\n\t\t}\n\t\tnMasterVol = (mastervol << 7) / attenuation;\n\t\tif (nMasterVol > 0x180) nMasterVol = 0x180;\n\t}\n\t////////////////////////////////////////////////////////////////////////////////////\n\t// Update channels data\n\tm_nMixChannels = 0;\n\tMODCHANNEL *pChn = Chn;\n\tfor (UINT nChn=0; nChn<MAX_CHANNELS; nChn++,pChn++)\n\t{\n\t\tif ((pChn->dwFlags & CHN_NOTEFADE) && (!(pChn->nFadeOutVol|pChn->nRightVol|pChn->nLeftVol)))\n\t\t{\n\t\t\tpChn->nLength = 0;\n\t\t\tpChn->nROfs = pChn->nLOfs = 0;\n\t\t}\n\t\t// Check for unused channel\n\t\tif ((pChn->dwFlags & CHN_MUTE) || ((nChn >= m_nChannels) && (!pChn->nLength)))\n\t\t{\n\t\t\tpChn->nVUMeter = 0;\n#ifdef ENABLE_STEREOVU\n\t\t\tpChn->nLeftVU = pChn->nRightVU = 0;\n#endif\n\t\t\tcontinue;\n\t\t}\n\t\t// Reset channel data\n\t\tpChn->nInc = 0;\n\t\tpChn->nRealVolume = 0;\n\t\tpChn->nRealPan = pChn->nPan + pChn->nPanSwing;\n\t\tif (pChn->nRealPan < 0) pChn->nRealPan = 0;\n\t\tif (pChn->nRealPan > 256) pChn->nRealPan = 256;\n\t\tpChn->nRampLength = 0;\n\t\t// Calc Frequency\n\t\tif ((pChn->nPeriod)\t&& (pChn->nLength))\n\t\t{\n\t\t\tint vol = pChn->nVolume + pChn->nVolSwing;\n\n\t\t\tif (vol < 0) vol = 0;\n\t\t\tif (vol > 256) vol = 256;\n\t\t\t// Tremolo\n\t\t\tif (pChn->dwFlags & CHN_TREMOLO)\n\t\t\t{\n\t\t\t\tUINT trempos = pChn->nTremoloPos & 0x3F;\n\t\t\t\tif (vol > 0)\n\t\t\t\t{\n\t\t\t\t\tint tremattn = (m_nType & MOD_TYPE_XM) ? 5 : 6;\n\t\t\t\t\tswitch (pChn->nTremoloType & 0x03)\n\t\t\t\t\t{\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tvol += (ModRampDownTable[trempos] * (int)pChn->nTremoloDepth) >> tremattn;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\tvol += (ModSquareTable[trempos] * (int)pChn->nTremoloDepth) >> tremattn;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\tvol += (ModRandomTable[trempos] * (int)pChn->nTremoloDepth) >> tremattn;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tvol += (ModSinusTable[trempos] * (int)pChn->nTremoloDepth) >> tremattn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif ((m_nTickCount) || ((m_nType & (MOD_TYPE_STM|MOD_TYPE_S3M|MOD_TYPE_IT)) && (!(m_dwSongFlags & SONG_ITOLDEFFECTS))))\n\t\t\t\t{\n\t\t\t\t\tpChn->nTremoloPos = (trempos + pChn->nTremoloSpeed) & 0x3F;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Tremor\n\t\t\tif (pChn->nCommand == CMD_TREMOR)\n\t\t\t{\n\t\t\t\tUINT n = (pChn->nTremorParam >> 4) + (pChn->nTremorParam & 0x0F);\n\t\t\t\tUINT ontime = pChn->nTremorParam >> 4;\n\t\t\t\tif ((!(m_nType & MOD_TYPE_IT)) || (m_dwSongFlags & SONG_ITOLDEFFECTS)) { n += 2; ontime++; }\n\t\t\t\tUINT tremcount = (UINT)pChn->nTremorCount;\n\t\t\t\tif (tremcount >= n) tremcount = 0;\n\t\t\t\tif ((m_nTickCount) || (m_nType & (MOD_TYPE_S3M|MOD_TYPE_IT)))\n\t\t\t\t{\n\t\t\t\t\tif (tremcount >= ontime) vol = 0;\n\t\t\t\t\tpChn->nTremorCount = (BYTE)(tremcount + 1);\n\t\t\t\t}\n\t\t\t\tpChn->dwFlags |= CHN_FASTVOLRAMP;\n\t\t\t}\n\t\t\t// Clip volume\n\t\t\tif (vol < 0) vol = 0;\n\t\t\tif (vol > 0x100) vol = 0x100;\n\t\t\tvol <<= 6;\n\t\t\t// Process Envelopes\n\t\t\tif (pChn->pHeader)\n\t\t\t{\n\t\t\t\tINSTRUMENTHEADER *penv = pChn->pHeader;\n\t\t\t\t// Volume Envelope\n\t\t\t\tif ((pChn->dwFlags & CHN_VOLENV) && (penv->nVolEnv))\n\t\t\t\t{\n\t\t\t\t\tint envpos = pChn->nVolEnvPosition;\n\t\t\t\t\tUINT pt = penv->nVolEnv - 1;\n\t\t\t\t\tfor (UINT i=0; i<(UINT)(penv->nVolEnv-1); i++)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (envpos <= penv->VolPoints[i])\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpt = i;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tint x2 = penv->VolPoints[pt];\n\t\t\t\t\tint x1, envvol;\n\t\t\t\t\tif (envpos >= x2)\n\t\t\t\t\t{\n\t\t\t\t\t\tenvvol = penv->VolEnv[pt] << 2;\n\t\t\t\t\t\tx1 = x2;\n\t\t\t\t\t} else\n\t\t\t\t\tif (pt)\n\t\t\t\t\t{\n\t\t\t\t\t\tenvvol = penv->VolEnv[pt-1] << 2;\n\t\t\t\t\t\tx1 = penv->VolPoints[pt-1];\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tenvvol = 0;\n\t\t\t\t\t\tx1 = 0;\n\t\t\t\t\t}\n\t\t\t\t\tif (envpos > x2) envpos = x2;\n\t\t\t\t\tif ((x2 > x1) && (envpos > x1))\n\t\t\t\t\t{\n\t\t\t\t\t\tenvvol += ((envpos - x1) * (((int)penv->VolEnv[pt]<<2) - envvol)) / (x2 - x1);\n\t\t\t\t\t}\n\t\t\t\t\tif (envvol < 0) envvol = 0;\n\t\t\t\t\tif (envvol > 256) envvol = 256;\n\t\t\t\t\tvol = (vol * envvol) >> 8;\n\t\t\t\t}\n\t\t\t\t// Panning Envelope\n\t\t\t\tif ((pChn->dwFlags & CHN_PANENV) && (penv->nPanEnv))\n\t\t\t\t{\n\t\t\t\t\tint envpos = pChn->nPanEnvPosition;\n\t\t\t\t\tUINT pt = penv->nPanEnv - 1;\n\t\t\t\t\tfor (UINT i=0; i<(UINT)(penv->nPanEnv-1); i++)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (envpos <= penv->PanPoints[i])\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpt = i;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tint x2 = penv->PanPoints[pt], y2 = penv->PanEnv[pt];\n\t\t\t\t\tint x1, envpan;\n\t\t\t\t\tif (envpos >= x2)\n\t\t\t\t\t{\n\t\t\t\t\t\tenvpan = y2;\n\t\t\t\t\t\tx1 = x2;\n\t\t\t\t\t} else\n\t\t\t\t\tif (pt)\n\t\t\t\t\t{\n\t\t\t\t\t\tenvpan = penv->PanEnv[pt-1];\n\t\t\t\t\t\tx1 = penv->PanPoints[pt-1];\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tenvpan = 128;\n\t\t\t\t\t\tx1 = 0;\n\t\t\t\t\t}\n\t\t\t\t\tif ((x2 > x1) && (envpos > x1))\n\t\t\t\t\t{\n\t\t\t\t\t\tenvpan += ((envpos - x1) * (y2 - envpan)) / (x2 - x1);\n\t\t\t\t\t}\n\t\t\t\t\tif (envpan < 0) envpan = 0;\n\t\t\t\t\tif (envpan > 64) envpan = 64;\n\t\t\t\t\tint pan = pChn->nPan;\n\t\t\t\t\tif (pan >= 128)\n\t\t\t\t\t{\n\t\t\t\t\t\tpan += ((envpan - 32) * (256 - pan)) / 32;\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tpan += ((envpan - 32) * (pan)) / 32;\n\t\t\t\t\t}\n\t\t\t\t\tif (pan < 0) pan = 0;\n\t\t\t\t\tif (pan > 256) pan = 256;\n\t\t\t\t\tpChn->nRealPan = pan;\n\t\t\t\t}\n\t\t\t\t// FadeOut volume\n\t\t\t\tif (pChn->dwFlags & CHN_NOTEFADE)\n\t\t\t\t{\n\t\t\t\t\tUINT fadeout = penv->nFadeOut;\n\t\t\t\t\tif (fadeout)\n\t\t\t\t\t{\n\t\t\t\t\t\tpChn->nFadeOutVol -= fadeout << 1;\n\t\t\t\t\t\tif (pChn->nFadeOutVol <= 0) pChn->nFadeOutVol = 0;\n\t\t\t\t\t\tvol = (vol * pChn->nFadeOutVol) >> 16;\n\t\t\t\t\t} else\n\t\t\t\t\tif (!pChn->nFadeOutVol)\n\t\t\t\t\t{\n\t\t\t\t\t\tvol = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Pitch/Pan separation\n\t\t\t\tif ((penv->nPPS) && (pChn->nRealPan) && (pChn->nNote))\n\t\t\t\t{\n\t\t\t\t\tint pandelta = (int)pChn->nRealPan + (int)((int)(pChn->nNote - penv->nPPC - 1) * (int)penv->nPPS) / (int)8;\n\t\t\t\t\tif (pandelta < 0) pandelta = 0;\n\t\t\t\t\tif (pandelta > 256) pandelta = 256;\n\t\t\t\t\tpChn->nRealPan = pandelta;\n\t\t\t\t}\n\t\t\t} else\n\t\t\t{\n\t\t\t\t// No Envelope: key off => note cut\n\t\t\t\tif (pChn->dwFlags & CHN_NOTEFADE) // 1.41-: CHN_KEYOFF|CHN_NOTEFADE\n\t\t\t\t{\n\t\t\t\t\tpChn->nFadeOutVol = 0;\n\t\t\t\t\tvol = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// vol is 14-bits\n\t\t\tif (vol)\n\t\t\t{\n\t\t\t\t// IMPORTANT: pChn->nRealVolume is 14 bits !!!\n\t\t\t\t// -> _muldiv( 14+8, 6+6, 18); => RealVolume: 14-bit result (22+12-20)\n\t\t\t\tpChn->nRealVolume = _muldiv(vol * m_nGlobalVolume, pChn->nGlobalVol * pChn->nInsVol, 1 << 20);\n\t\t\t}\n\t\t\tif (pChn->nPeriod < m_nMinPeriod) pChn->nPeriod = m_nMinPeriod;\n\t\t\tint period = pChn->nPeriod;\n\t\t\tif ((pChn->dwFlags & (CHN_GLISSANDO|CHN_PORTAMENTO)) ==\t(CHN_GLISSANDO|CHN_PORTAMENTO))\n\t\t\t{\n\t\t\t\tperiod = GetPeriodFromNote(GetNoteFromPeriod(period), pChn->nFineTune, pChn->nC4Speed);\n\t\t\t}\n\n\t\t\t// Arpeggio ?\n\t\t\tif (pChn->nCommand == CMD_ARPEGGIO)\n\t\t\t{\n\t\t\t\tswitch(m_nTickCount % 3)\n\t\t\t\t{\n\t\t\t\tcase 1:\tperiod = GetPeriodFromNote(pChn->nNote + (pChn->nArpeggio >> 4), pChn->nFineTune, pChn->nC4Speed); break;\n\t\t\t\tcase 2:\tperiod = GetPeriodFromNote(pChn->nNote + (pChn->nArpeggio & 0x0F), pChn->nFineTune, pChn->nC4Speed); break;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (m_dwSongFlags & SONG_AMIGALIMITS)\n\t\t\t{\n\t\t\t\tif (period < 113*4) period = 113*4;\n\t\t\t\tif (period > 856*4) period = 856*4;\n\t\t\t}\n\n\t\t\t// Pitch/Filter Envelope\n\t\t\tif ((pChn->pHeader) && (pChn->dwFlags & CHN_PITCHENV) && (pChn->pHeader->nPitchEnv))\n\t\t\t{\n\t\t\t\tINSTRUMENTHEADER *penv = pChn->pHeader;\n\t\t\t\tint envpos = pChn->nPitchEnvPosition;\n\t\t\t\tUINT pt = penv->nPitchEnv - 1;\n\t\t\t\tfor (UINT i=0; i<(UINT)(penv->nPitchEnv-1); i++)\n\t\t\t\t{\n\t\t\t\t\tif (envpos <= penv->PitchPoints[i])\n\t\t\t\t\t{\n\t\t\t\t\t\tpt = i;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tint x2 = penv->PitchPoints[pt];\n\t\t\t\tint x1, envpitch;\n\t\t\t\tif (envpos >= x2)\n\t\t\t\t{\n\t\t\t\t\tenvpitch = (((int)penv->PitchEnv[pt]) - 32) * 8;\n\t\t\t\t\tx1 = x2;\n\t\t\t\t} else\n\t\t\t\tif (pt)\n\t\t\t\t{\n\t\t\t\t\tenvpitch = (((int)penv->PitchEnv[pt-1]) - 32) * 8;\n\t\t\t\t\tx1 = penv->PitchPoints[pt-1];\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tenvpitch = 0;\n\t\t\t\t\tx1 = 0;\n\t\t\t\t}\n\t\t\t\tif (envpos > x2) envpos = x2;\n\t\t\t\tif ((x2 > x1) && (envpos > x1))\n\t\t\t\t{\n\t\t\t\t\tint envpitchdest = (((int)penv->PitchEnv[pt]) - 32) * 8;\n\t\t\t\t\tenvpitch += ((envpos - x1) * (envpitchdest - envpitch)) / (x2 - x1);\n\t\t\t\t}\n\t\t\t\tif (envpitch < -256) envpitch = -256;\n\t\t\t\tif (envpitch > 256) envpitch = 256;\n\t\t\t\t// Filter Envelope: controls cutoff frequency\n\t\t\t\tif (penv->dwFlags & ENV_FILTER)\n\t\t\t\t{\n#ifndef NO_FILTER\n\t\t\t\t\tSetupChannelFilter(pChn, (pChn->dwFlags & CHN_FILTER) ? FALSE : TRUE, envpitch);\n#endif // NO_FILTER\n\t\t\t\t} else\n\t\t\t\t// Pitch Envelope\n\t\t\t\t{\n\t\t\t\t\tint l = envpitch;\n\t\t\t\t\tif (l < 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tl = -l;\n\t\t\t\t\t\tif (l > 255) l = 255;\n\t\t\t\t\t\tperiod = _muldiv(period, LinearSlideUpTable[l], 0x10000);\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tif (l > 255) l = 255;\n\t\t\t\t\t\tperiod = _muldiv(period, LinearSlideDownTable[l], 0x10000);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Vibrato\n\t\t\tif (pChn->dwFlags & CHN_VIBRATO)\n\t\t\t{\n\t\t\t\tUINT vibpos = pChn->nVibratoPos;\n\t\t\t\tLONG vdelta;\n\t\t\t\tswitch (pChn->nVibratoType & 0x03)\n\t\t\t\t{\n\t\t\t\tcase 1:\n\t\t\t\t\tvdelta = ModRampDownTable[vibpos];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tvdelta = ModSquareTable[vibpos];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tvdelta = ModRandomTable[vibpos];\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tvdelta = ModSinusTable[vibpos];\n\t\t\t\t}\n\t\t\t\tUINT vdepth = ((m_nType != MOD_TYPE_IT) || (m_dwSongFlags & SONG_ITOLDEFFECTS)) ? 6 : 7;\n\t\t\t\tvdelta = (vdelta * (int)pChn->nVibratoDepth) >> vdepth;\n\t\t\t\tif ((m_dwSongFlags & SONG_LINEARSLIDES) && (m_nType & MOD_TYPE_IT))\n\t\t\t\t{\n\t\t\t\t\tLONG l = vdelta;\n\t\t\t\t\tif (l < 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tl = -l;\n\t\t\t\t\t\tvdelta = _muldiv(period, LinearSlideDownTable[l >> 2], 0x10000) - period;\n\t\t\t\t\t\tif (l & 0x03) vdelta += _muldiv(period, FineLinearSlideDownTable[l & 0x03], 0x10000) - period;\n\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tvdelta = _muldiv(period, LinearSlideUpTable[l >> 2], 0x10000) - period;\n\t\t\t\t\t\tif (l & 0x03) vdelta += _muldiv(period, FineLinearSlideUpTable[l & 0x03], 0x10000) - period;\n\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tperiod += vdelta;\n\t\t\t\tif ((m_nTickCount) || ((m_nType & MOD_TYPE_IT) && (!(m_dwSongFlags & SONG_ITOLDEFFECTS))))\n\t\t\t\t{\n\t\t\t\t\tpChn->nVibratoPos = (vibpos + pChn->nVibratoSpeed) & 0x3F;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Panbrello\n\t\t\tif (pChn->dwFlags & CHN_PANBRELLO)\n\t\t\t{\n\t\t\t\tUINT panpos = ((pChn->nPanbrelloPos+0x10) >> 2) & 0x3F;\n\t\t\t\tLONG pdelta;\n\t\t\t\tswitch (pChn->nPanbrelloType & 0x03)\n\t\t\t\t{\n\t\t\t\tcase 1:\n\t\t\t\t\tpdelta = ModRampDownTable[panpos];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\n\t\t\t\t\tpdelta = ModSquareTable[panpos];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\tpdelta = ModRandomTable[panpos];\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tpdelta = ModSinusTable[panpos];\n\t\t\t\t}\n\t\t\t\tpChn->nPanbrelloPos += pChn->nPanbrelloSpeed;\n\t\t\t\tpdelta = ((pdelta * (int)pChn->nPanbrelloDepth) + 2) >> 3;\n\t\t\t\tpdelta += pChn->nRealPan;\n\t\t\t\tif (pdelta < 0) pdelta = 0;\n\t\t\t\tif (pdelta > 256) pdelta = 256;\n\t\t\t\tpChn->nRealPan = pdelta;\n\t\t\t}\n\t\t\tint nPeriodFrac = 0;\n\t\t\t// Instrument Auto-Vibrato\n\t\t\tif ((pChn->pInstrument) && (pChn->pInstrument->nVibDepth))\n\t\t\t{\n\t\t\t\tMODINSTRUMENT *pins = pChn->pInstrument;\n\t\t\t\tif (pins->nVibSweep == 0)\n\t\t\t\t{\n\t\t\t\t\tpChn->nAutoVibDepth = pins->nVibDepth << 8;\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tif (m_nType & MOD_TYPE_IT)\n\t\t\t\t\t{\n\t\t\t\t\t\tpChn->nAutoVibDepth += pins->nVibSweep << 3;\n\t\t\t\t\t} else\n\t\t\t\t\tif (!(pChn->dwFlags & CHN_KEYOFF))\n\t\t\t\t\t{\n\t\t\t\t\t\tpChn->nAutoVibDepth += (pins->nVibDepth << 8) /\tpins->nVibSweep;\n\t\t\t\t\t}\n\t\t\t\t\tif ((pChn->nAutoVibDepth >> 8) > pins->nVibDepth)\n\t\t\t\t\t\tpChn->nAutoVibDepth = pins->nVibDepth << 8;\n\t\t\t\t}\n\t\t\t\tpChn->nAutoVibPos += pins->nVibRate;\n\t\t\t\tint val;\n\t\t\t\tswitch(pins->nVibType)\n\t\t\t\t{\n\t\t\t\tcase 4:\t// Random\n\t\t\t\t\tval = ModRandomTable[pChn->nAutoVibPos & 0x3F];\n\t\t\t\t\tpChn->nAutoVibPos++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\t// Ramp Down\n\t\t\t\t\tval = ((0x40 - (pChn->nAutoVibPos >> 1)) & 0x7F) - 0x40;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 2:\t// Ramp Up\n\t\t\t\t\tval = ((0x40 + (pChn->nAutoVibPos >> 1)) & 0x7f) - 0x40;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 1:\t// Square\n\t\t\t\t\tval = (pChn->nAutoVibPos & 128) ? +64 : -64;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\t// Sine\n\t\t\t\t\tval = ft2VibratoTable[pChn->nAutoVibPos & 255];\n\t\t\t\t}\n\t\t\t\tint n =\t((val * pChn->nAutoVibDepth) >> 8);\n\t\t\t\tif (m_nType & MOD_TYPE_IT)\n\t\t\t\t{\n\t\t\t\t\tint df1, df2;\n\t\t\t\t\tif (n < 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tn = -n;\n\t\t\t\t\t\tUINT n1 = n >> 8;\n\t\t\t\t\t\tdf1 = LinearSlideUpTable[n1];\n\t\t\t\t\t\tdf2 = LinearSlideUpTable[n1+1];\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tUINT n1 = n >> 8;\n\t\t\t\t\t\tdf1 = LinearSlideDownTable[n1];\n\t\t\t\t\t\tdf2 = LinearSlideDownTable[n1+1];\n\t\t\t\t\t}\n\t\t\t\t\tn >>= 2;\n\t\t\t\t\tperiod = _muldiv(period, df1 + ((df2-df1)*(n&0x3F)>>6), 256);\n\t\t\t\t\tnPeriodFrac = period & 0xFF;\n\t\t\t\t\tperiod >>= 8;\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tperiod += (n >> 6);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Final Period\n\t\t\tif (period <= m_nMinPeriod)\n\t\t\t{\n\t\t\t\tif (m_nType & MOD_TYPE_S3M) pChn->nLength = 0;\n\t\t\t\tperiod = m_nMinPeriod;\n\t\t\t}\n\t\t\tif (period > m_nMaxPeriod)\n\t\t\t{\n\t\t\t\tif ((m_nType & MOD_TYPE_IT) || (period >= 0x100000))\n\t\t\t\t{\n\t\t\t\t\tpChn->nFadeOutVol = 0;\n\t\t\t\t\tpChn->dwFlags |= CHN_NOTEFADE;\n\t\t\t\t\tpChn->nRealVolume = 0;\n\t\t\t\t}\n\t\t\t\tperiod = m_nMaxPeriod;\n\t\t\t\tnPeriodFrac = 0;\n\t\t\t}\n\t\t\tUINT freq = GetFreqFromPeriod(period, pChn->nC4Speed, nPeriodFrac);\n\t\t\tif ((m_nType & MOD_TYPE_IT) && (freq < 256))\n\t\t\t{\n\t\t\t\tpChn->nFadeOutVol = 0;\n\t\t\t\tpChn->dwFlags |= CHN_NOTEFADE;\n\t\t\t\tpChn->nRealVolume = 0;\n\t\t\t}\n\t\t\tUINT ninc = _muldiv(freq, 0x10000, gdwMixingFreq);\n\t\t\tif ((ninc >= 0xFFB0) && (ninc <= 0x10090)) ninc = 0x10000;\n\t\t\tif (m_nFreqFactor != 128) ninc = (ninc * m_nFreqFactor) >> 7;\n\t\t\tif (ninc > 0xFF0000) ninc = 0xFF0000;\n\t\t\tpChn->nInc = (ninc+1) & ~3;\n\t\t}\n\n\t\t// Increment envelope position\n\t\tif (pChn->pHeader)\n\t\t{\n\t\t\tINSTRUMENTHEADER *penv = pChn->pHeader;\n\t\t\t// Volume Envelope\n\t\t\tif (pChn->dwFlags & CHN_VOLENV)\n\t\t\t{\n\t\t\t\t// Increase position\n\t\t\t\tpChn->nVolEnvPosition++;\n\t\t\t\t// Volume Loop ?\n\t\t\t\tif (penv->dwFlags & ENV_VOLLOOP)\n\t\t\t\t{\n\t\t\t\t\tUINT volloopend = penv->VolPoints[penv->nVolLoopEnd];\n\t\t\t\t\tif (m_nType != MOD_TYPE_XM) volloopend++;\n\t\t\t\t\tif (pChn->nVolEnvPosition == volloopend)\n\t\t\t\t\t{\n\t\t\t\t\t\tpChn->nVolEnvPosition = penv->VolPoints[penv->nVolLoopStart];\n\t\t\t\t\t\tif ((penv->nVolLoopEnd == penv->nVolLoopStart) && (!penv->VolEnv[penv->nVolLoopStart])\n\t\t\t\t\t\t && ((!(m_nType & MOD_TYPE_XM)) || (penv->nVolLoopEnd+1 == penv->nVolEnv)))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpChn->dwFlags |= CHN_NOTEFADE;\n\t\t\t\t\t\t\tpChn->nFadeOutVol = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Volume Sustain ?\n\t\t\t\tif ((penv->dwFlags & ENV_VOLSUSTAIN) && (!(pChn->dwFlags & CHN_KEYOFF)))\n\t\t\t\t{\n\t\t\t\t\tif (pChn->nVolEnvPosition == (UINT)penv->VolPoints[penv->nVolSustainEnd]+1)\n\t\t\t\t\t\tpChn->nVolEnvPosition = penv->VolPoints[penv->nVolSustainBegin];\n\t\t\t\t} else\n\t\t\t\t// End of Envelope ?\n\t\t\t\tif (pChn->nVolEnvPosition > penv->VolPoints[penv->nVolEnv - 1])\n\t\t\t\t{\n\t\t\t\t\tif ((m_nType & MOD_TYPE_IT) || (pChn->dwFlags & CHN_KEYOFF)) pChn->dwFlags |= CHN_NOTEFADE;\n\t\t\t\t\tpChn->nVolEnvPosition = penv->VolPoints[penv->nVolEnv - 1];\n\t\t\t\t\tif ((!penv->VolEnv[penv->nVolEnv-1]) && ((nChn >= m_nChannels) || (m_nType & MOD_TYPE_IT)))\n\t\t\t\t\t{\n\t\t\t\t\t\tpChn->dwFlags |= CHN_NOTEFADE;\n\t\t\t\t\t\tpChn->nFadeOutVol = 0;\n\n\t\t\t\t\t\tpChn->nRealVolume = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Panning Envelope\n\t\t\tif (pChn->dwFlags & CHN_PANENV)\n\t\t\t{\n\t\t\t\tpChn->nPanEnvPosition++;\n\t\t\t\tif (penv->dwFlags & ENV_PANLOOP)\n\t\t\t\t{\n\t\t\t\t\tUINT panloopend = penv->PanPoints[penv->nPanLoopEnd];\n\t\t\t\t\tif (m_nType != MOD_TYPE_XM) panloopend++;\n\t\t\t\t\tif (pChn->nPanEnvPosition == panloopend)\n\t\t\t\t\t\tpChn->nPanEnvPosition = penv->PanPoints[penv->nPanLoopStart];\n\t\t\t\t}\n\t\t\t\t// Panning Sustain ?\n\t\t\t\tif ((penv->dwFlags & ENV_PANSUSTAIN) && (pChn->nPanEnvPosition == (UINT)penv->PanPoints[penv->nPanSustainEnd]+1)\n\t\t\t\t && (!(pChn->dwFlags & CHN_KEYOFF)))\n\t\t\t\t{\n\t\t\t\t\t// Panning sustained\n\t\t\t\t\tpChn->nPanEnvPosition = penv->PanPoints[penv->nPanSustainBegin];\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tif (pChn->nPanEnvPosition > penv->PanPoints[penv->nPanEnv - 1])\n\t\t\t\t\t\tpChn->nPanEnvPosition = penv->PanPoints[penv->nPanEnv - 1];\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Pitch Envelope\n\t\t\tif (pChn->dwFlags & CHN_PITCHENV)\n\t\t\t{\n\t\t\t\t// Increase position\n\t\t\t\tpChn->nPitchEnvPosition++;\n\t\t\t\t// Pitch Loop ?\n\t\t\t\tif (penv->dwFlags & ENV_PITCHLOOP)\n\t\t\t\t{\n\t\t\t\t\tif (pChn->nPitchEnvPosition >= penv->PitchPoints[penv->nPitchLoopEnd])\n\t\t\t\t\t\tpChn->nPitchEnvPosition = penv->PitchPoints[penv->nPitchLoopStart];\n\t\t\t\t}\n\t\t\t\t// Pitch Sustain ?\n\t\t\t\tif ((penv->dwFlags & ENV_PITCHSUSTAIN) && (!(pChn->dwFlags & CHN_KEYOFF)))\n\t\t\t\t{\n\t\t\t\t\tif (pChn->nPitchEnvPosition == (UINT)penv->PitchPoints[penv->nPitchSustainEnd]+1)\n\t\t\t\t\t\tpChn->nPitchEnvPosition = penv->PitchPoints[penv->nPitchSustainBegin];\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tif (pChn->nPitchEnvPosition > penv->PitchPoints[penv->nPitchEnv - 1])\n\t\t\t\t\t\tpChn->nPitchEnvPosition = penv->PitchPoints[penv->nPitchEnv - 1];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n#ifdef MODPLUG_PLAYER\n\t\t// Limit CPU -> > 80% -> don't ramp\n\t\tif ((gnCPUUsage >= 80) && (!pChn->nRealVolume))\n\t\t{\n\t\t\tpChn->nLeftVol = pChn->nRightVol = 0;\n\t\t}\n#endif // MODPLUG_PLAYER\n\t\t// Volume ramping\n\t\tpChn->dwFlags &= ~CHN_VOLUMERAMP;\n\t\tif ((pChn->nRealVolume) || (pChn->nLeftVol) || (pChn->nRightVol))\n\t\t\tpChn->dwFlags |= CHN_VOLUMERAMP;\n#ifdef MODPLUG_PLAYER\n\t\t// Decrease VU-Meter\n\t\tif (pChn->nVUMeter > VUMETER_DECAY)\tpChn->nVUMeter -= VUMETER_DECAY; else pChn->nVUMeter = 0;\n#endif // MODPLUG_PLAYER\n#ifdef ENABLE_STEREOVU\n\t\tif (pChn->nLeftVU > VUMETER_DECAY) pChn->nLeftVU -= VUMETER_DECAY; else pChn->nLeftVU = 0;\n\t\tif (pChn->nRightVU > VUMETER_DECAY) pChn->nRightVU -= VUMETER_DECAY; else pChn->nRightVU = 0;\n#endif\n\t\t// Check for too big nInc\n\t\tif (((pChn->nInc >> 16) + 1) >= (LONG)(pChn->nLoopEnd - pChn->nLoopStart)) pChn->dwFlags &= ~CHN_LOOP;\n\t\tpChn->nNewRightVol = pChn->nNewLeftVol = 0;\n\t\tpChn->pCurrentSample = ((pChn->pSample) && (pChn->nLength) && (pChn->nInc)) ? pChn->pSample : NULL;\n\t\tif (pChn->pCurrentSample)\n\t\t{\n\t\t\t// Update VU-Meter (nRealVolume is 14-bit)\n#ifdef MODPLUG_PLAYER\n\t\t\tUINT vutmp = pChn->nRealVolume >> (14 - 8);\n\t\t\tif (vutmp > 0xFF) vutmp = 0xFF;\n\t\t\tif (pChn->nVUMeter >= 0x100) pChn->nVUMeter = vutmp;\n\t\t\tvutmp >>= 1;\n\t\t\tif (pChn->nVUMeter < vutmp)\tpChn->nVUMeter = vutmp;\n#endif // MODPLUG_PLAYER\n#ifdef ENABLE_STEREOVU\n\t\t\tUINT vul = (pChn->nRealVolume * pChn->nRealPan) >> 14;\n\t\t\tif (vul > 127) vul = 127;\n\t\t\tif (pChn->nLeftVU > 127) pChn->nLeftVU = (BYTE)vul;\n\t\t\tvul >>= 1;\n\t\t\tif (pChn->nLeftVU < vul) pChn->nLeftVU = (BYTE)vul;\n\t\t\tUINT vur = (pChn->nRealVolume * (256-pChn->nRealPan)) >> 14;\n\t\t\tif (vur > 127) vur = 127;\n\t\t\tif (pChn->nRightVU > 127) pChn->nRightVU = (BYTE)vur;\n\t\t\tvur >>= 1;\n\t\t\tif (pChn->nRightVU < vur) pChn->nRightVU = (BYTE)vur;\n#endif\n#ifdef MODPLUG_TRACKER\n\t\t\tUINT kChnMasterVol = (pChn->dwFlags & CHN_EXTRALOUD) ? 0x100 : nMasterVol;\n#else\n#define\t\tkChnMasterVol\tnMasterVol\n#endif // MODPLUG_TRACKER\n\t\t\t// Adjusting volumes\n\t\t\tif (gnChannels >= 2)\n\t\t\t{\n\t\t\t\tint pan = ((int)pChn->nRealPan) - 128;\n\t\t\t\tpan *= (int)m_nStereoSeparation;\n\t\t\t\tpan /= 128;\n\t\t\t\tpan += 128;\n\n\t\t\t\tif (pan < 0) pan = 0;\n\t\t\t\tif (pan > 256) pan = 256;\n#ifndef MODPLUG_FASTSOUNDLIB\n\t\t\t\tif (gdwSoundSetup & SNDMIX_REVERSESTEREO) pan = 256 - pan;\n#endif\n\t\t\t\tLONG realvol = (pChn->nRealVolume * kChnMasterVol) >> (8-1);\n\t\t\t\tif (gdwSoundSetup & SNDMIX_SOFTPANNING)\n\t\t\t\t{\n\t\t\t\t\tif (pan < 128)\n\t\t\t\t\t{\n\t\t\t\t\t\tpChn->nNewLeftVol = (realvol * pan) >> 8;\n\t\t\t\t\t\tpChn->nNewRightVol = (realvol * 128) >> 8;\n\t\t\t\t\t} else\n\t\t\t\t\t{\n\t\t\t\t\t\tpChn->nNewLeftVol = (realvol * 128) >> 8;\n\t\t\t\t\t\tpChn->nNewRightVol = (realvol * (256 - pan)) >> 8;\n\t\t\t\t\t}\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tpChn->nNewLeftVol = (realvol * pan) >> 8;\n\t\t\t\t\tpChn->nNewRightVol = (realvol * (256 - pan)) >> 8;\n\t\t\t\t}\n\t\t\t} else\n\t\t\t{\n\t\t\t\tpChn->nNewRightVol = (pChn->nRealVolume * kChnMasterVol) >> 8;\n\t\t\t\tpChn->nNewLeftVol = pChn->nNewRightVol;\n\t\t\t}\n\t\t\t// Clipping volumes\n\t\t\tif (pChn->nNewRightVol > 0xFFFF) pChn->nNewRightVol = 0xFFFF;\n\t\t\tif (pChn->nNewLeftVol > 0xFFFF) pChn->nNewLeftVol = 0xFFFF;\n\t\t\t// Check IDO\n\t\t\tif (gdwSoundSetup & SNDMIX_NORESAMPLING)\n\t\t\t{\n\t\t\t\tpChn->dwFlags |= CHN_NOIDO;\n\t\t\t} else\n\t\t\t{\n\t\t\t\tpChn->dwFlags &= ~(CHN_NOIDO|CHN_HQSRC);\n\t\t\t\tif( pChn->nInc == 0x10000 )\n\t\t\t\t{\tpChn->dwFlags |= CHN_NOIDO;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\tif( ((gdwSoundSetup & SNDMIX_HQRESAMPLER) == 0) && ((gdwSoundSetup & SNDMIX_ULTRAHQSRCMODE) == 0) )\n\t\t\t\t\t{\tif (pChn->nInc >= 0xFF00) pChn->dwFlags |= CHN_NOIDO;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tpChn->nNewRightVol >>= MIXING_ATTENUATION;\n\t\t\tpChn->nNewLeftVol >>= MIXING_ATTENUATION;\n\t\t\tpChn->nRightRamp = pChn->nLeftRamp = 0;\n\t\t\t// Dolby Pro-Logic Surround\n\t\t\tif ((pChn->dwFlags & CHN_SURROUND) && (gnChannels <= 2)) pChn->nNewLeftVol = - pChn->nNewLeftVol;\n\t\t\t// Checking Ping-Pong Loops\n\t\t\tif (pChn->dwFlags & CHN_PINGPONGFLAG) pChn->nInc = -pChn->nInc;\n\t\t\t// Setting up volume ramp\n\t\t\tif ((pChn->dwFlags & CHN_VOLUMERAMP)\n\t\t\t && ((pChn->nRightVol != pChn->nNewRightVol)\n\t\t\t  || (pChn->nLeftVol != pChn->nNewLeftVol)))\n\t\t\t{\n\t\t\t\tLONG nRampLength = gnVolumeRampSamples;\n\t\t\t\tLONG nRightDelta = ((pChn->nNewRightVol - pChn->nRightVol) << VOLUMERAMPPRECISION);\n\t\t\t\tLONG nLeftDelta = ((pChn->nNewLeftVol - pChn->nLeftVol) << VOLUMERAMPPRECISION);\n#ifndef MODPLUG_FASTSOUNDLIB\n\t\t\t\tif ((gdwSoundSetup & SNDMIX_DIRECTTODISK)\n\t\t\t\t || ((gdwSysInfo & (SYSMIX_ENABLEMMX|SYSMIX_FASTCPU))\n\t\t\t\t  && (gdwSoundSetup & SNDMIX_HQRESAMPLER) && (gnCPUUsage <= 20)))\n\t\t\t\t{\n\t\t\t\t\tif ((pChn->nRightVol|pChn->nLeftVol) && (pChn->nNewRightVol|pChn->nNewLeftVol) && (!(pChn->dwFlags & CHN_FASTVOLRAMP)))\n\t\t\t\t\t{\n\t\t\t\t\t\tnRampLength = m_nBufferCount;\n\t\t\t\t\t\tif (nRampLength > (1 << (VOLUMERAMPPRECISION-1))) nRampLength = (1 << (VOLUMERAMPPRECISION-1));\n\t\t\t\t\t\tif (nRampLength < (LONG)gnVolumeRampSamples) nRampLength = gnVolumeRampSamples;\n\t\t\t\t\t}\n\t\t\t\t}\n#endif\n\t\t\t\tpChn->nRightRamp = nRightDelta / nRampLength;\n\t\t\t\tpChn->nLeftRamp = nLeftDelta / nRampLength;\n\t\t\t\tpChn->nRightVol = pChn->nNewRightVol - ((pChn->nRightRamp * nRampLength) >> VOLUMERAMPPRECISION);\n\t\t\t\tpChn->nLeftVol = pChn->nNewLeftVol - ((pChn->nLeftRamp * nRampLength) >> VOLUMERAMPPRECISION);\n\t\t\t\tif (pChn->nRightRamp|pChn->nLeftRamp)\n\t\t\t\t{\n\t\t\t\t\tpChn->nRampLength = nRampLength;\n\t\t\t\t} else\n\t\t\t\t{\n\t\t\t\t\tpChn->dwFlags &= ~CHN_VOLUMERAMP;\n\t\t\t\t\tpChn->nRightVol = pChn->nNewRightVol;\n\t\t\t\t\tpChn->nLeftVol = pChn->nNewLeftVol;\n\t\t\t\t}\n\t\t\t} else\n\t\t\t{\n\t\t\t\tpChn->dwFlags &= ~CHN_VOLUMERAMP;\n\t\t\t\tpChn->nRightVol = pChn->nNewRightVol;\n\t\t\t\tpChn->nLeftVol = pChn->nNewLeftVol;\n\t\t\t}\n\t\t\tpChn->nRampRightVol = pChn->nRightVol << VOLUMERAMPPRECISION;\n\t\t\tpChn->nRampLeftVol = pChn->nLeftVol << VOLUMERAMPPRECISION;\n\t\t\t// Adding the channel in the channel list\n\t\t\tChnMix[m_nMixChannels++] = nChn;\n\t\t\tif (m_nMixChannels >= MAX_CHANNELS) break;\n\t\t} else\n\t\t{\n#ifdef ENABLE_STEREOVU\n\t\t\t// Note change but no sample\n\t\t\tif (pChn->nLeftVU > 128) pChn->nLeftVU = 0;\n\t\t\tif (pChn->nRightVU > 128) pChn->nRightVU = 0;\n#endif\n\t\t\tif (pChn->nVUMeter > 0xFF) pChn->nVUMeter = 0;\n\t\t\tpChn->nLeftVol = pChn->nRightVol = 0;\n\t\t\tpChn->nLength = 0;\n\t\t}\n\t}\n\t// Checking Max Mix Channels reached: ordering by volume\n\tif ((m_nMixChannels >= m_nMaxMixChannels) && (!(gdwSoundSetup & SNDMIX_DIRECTTODISK)))\n\t{\n\t\tfor (UINT i=0; i<m_nMixChannels; i++)\n\t\t{\n\t\t\tUINT j=i;\n\t\t\twhile ((j+1<m_nMixChannels) && (Chn[ChnMix[j]].nRealVolume < Chn[ChnMix[j+1]].nRealVolume))\n\t\t\t{\n\t\t\t\tUINT n = ChnMix[j];\n\t\t\t\tChnMix[j] = ChnMix[j+1];\n\t\t\t\tChnMix[j+1] = n;\n\t\t\t\tj++;\n\t\t\t}\n\t\t}\n\t}\n\tif (m_dwSongFlags & SONG_GLOBALFADE)\n\t{\n\t\tif (!m_nGlobalFadeSamples)\n\t\t{\n\t\t\tm_dwSongFlags |= SONG_ENDREACHED;\n\t\t\treturn FALSE;\n\t\t}\n\t\tif (m_nGlobalFadeSamples > m_nBufferCount)\n\t\t\tm_nGlobalFadeSamples -= m_nBufferCount;\n\t\telse\n\t\t\tm_nGlobalFadeSamples = 0;\n\t}\n\treturn TRUE;\n}\n"
  },
  {
    "path": "contrib/libmodplug/src/tables.h",
    "content": "/*\n * This source code is public domain.\n *\n * Authors: Olivier Lapicque <olivierl@jps.net>\n */\n\n#include \"libmodplug/stdafx.h\"\n#include \"libmodplug/sndfile.h\"\n\n#ifndef MODPLUG_FASTSOUNDLIB\n//#pragma data_seg(\".tables\")\n#endif\n\nstatic const BYTE ImpulseTrackerPortaVolCmd[16] =\n{\n\t0x00, 0x01, 0x04, 0x08, 0x10, 0x20, 0x40, 0x60,\n\t0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF\n};\n\n// Period table for Protracker octaves 0-5:\nstatic const WORD ProTrackerPeriodTable[6*12] =\n{\n\t1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907,\n\t856,808,762,720,678,640,604,570,538,508,480,453,\n\t428,404,381,360,339,320,302,285,269,254,240,226,\n\t214,202,190,180,170,160,151,143,135,127,120,113,\n\t107,101,95,90,85,80,75,71,67,63,60,56,\n\t53,50,47,45,42,40,37,35,33,31,30,28\n};\n\n\nstatic const WORD ProTrackerTunedPeriods[16*12] = \n{\n\t1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,907,\n\t1700,1604,1514,1430,1348,1274,1202,1134,1070,1010,954,900,\n\t1688,1592,1504,1418,1340,1264,1194,1126,1064,1004,948,894,\n\t1676,1582,1492,1408,1330,1256,1184,1118,1056,996,940,888,\n\t1664,1570,1482,1398,1320,1246,1176,1110,1048,990,934,882,\n\t1652,1558,1472,1388,1310,1238,1168,1102,1040,982,926,874,\n\t1640,1548,1460,1378,1302,1228,1160,1094,1032,974,920,868,\n\t1628,1536,1450,1368,1292,1220,1150,1086,1026,968,914,862,\n\t1814,1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,\n\t1800,1700,1604,1514,1430,1350,1272,1202,1134,1070,1010,954,\n\t1788,1688,1592,1504,1418,1340,1264,1194,1126,1064,1004,948,\n\t1774,1676,1582,1492,1408,1330,1256,1184,1118,1056,996,940,\n\t1762,1664,1570,1482,1398,1320,1246,1176,1110,1048,988,934,\n\t1750,1652,1558,1472,1388,1310,1238,1168,1102,1040,982,926,\n\t1736,1640,1548,1460,1378,1302,1228,1160,1094,1032,974,920,\n\t1724,1628,1536,1450,1368,1292,1220,1150,1086,1026,968,914 \n};\n\n\n// S3M C-4 periods\nstatic const WORD FreqS3MTable[16] = \n{\n\t1712,1616,1524,1440,1356,1280,\n\t1208,1140,1076,1016,960,907,\n\t0,0,0,0\n};\n\n\n// S3M FineTune frequencies\nstatic const WORD S3MFineTuneTable[16] = \n{\n\t7895,7941,7985,8046,8107,8169,8232,8280,\n\t8363,8413,8463,8529,8581,8651,8723,8757,\t// 8363*2^((i-8)/(12*8))\n};\n\n\n// Sinus table\nstatic const int16_t ModSinusTable[64] =\n{\n\t0,12,25,37,49,60,71,81,90,98,106,112,117,122,125,126,\n\t127,126,125,122,117,112,106,98,90,81,71,60,49,37,25,12,\n\t0,-12,-25,-37,-49,-60,-71,-81,-90,-98,-106,-112,-117,-122,-125,-126,\n\t-127,-126,-125,-122,-117,-112,-106,-98,-90,-81,-71,-60,-49,-37,-25,-12\n};\n\n// Triangle wave table (ramp down)\nstatic const int16_t ModRampDownTable[64] =\n{\n\t0,-4,-8,-12,-16,-20,-24,-28,-32,-36,-40,-44,-48,-52,-56,-60,\n\t-64,-68,-72,-76,-80,-84,-88,-92,-96,-100,-104,-108,-112,-116,-120,-124,\n\t127,123,119,115,111,107,103,99,95,91,87,83,79,75,71,67,\n\t63,59,55,51,47,43,39,35,31,27,23,19,15,11,7,3\n};\n\n// Square wave table\nstatic const int16_t ModSquareTable[64] =\n{\n\t127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,\n\t127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,127,\n\t-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,\n\t-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127,-127\n};\n\n// Random wave table\nstatic const int16_t ModRandomTable[64] =\n{\n\t98,-127,-43,88,102,41,-65,-94,125,20,-71,-86,-70,-32,-16,-96,\n\t17,72,107,-5,116,-69,-62,-40,10,-61,65,109,-18,-38,-13,-76,\n\t-23,88,21,-94,8,106,21,-112,6,109,20,-88,-30,9,-127,118,\n\t42,-34,89,-4,-51,-72,21,-29,112,123,84,-101,-92,98,-54,-95\n};\n\n\n// volume fade tables for Retrig Note:\nstatic const int8_t retrigTable1[16] =\n{ 0, 0, 0, 0, 0, 0, 10, 8, 0, 0, 0, 0, 0, 0, 24, 32 };\n\nstatic const int8_t retrigTable2[16] =\n{ 0, -1, -2, -4, -8, -16, 0, 0, 0, 1, 2, 4, 8, 16, 0, 0 };\n\n\n\nstatic const WORD XMPeriodTable[104] = \n{\n\t907,900,894,887,881,875,868,862,856,850,844,838,832,826,820,814,\n\t808,802,796,791,785,779,774,768,762,757,752,746,741,736,730,725,\n\t720,715,709,704,699,694,689,684,678,675,670,665,660,655,651,646,\n\t640,636,632,628,623,619,614,610,604,601,597,592,588,584,580,575,\n\t570,567,563,559,555,551,547,543,538,535,532,528,524,520,516,513,\n\t508,505,502,498,494,491,487,484,480,477,474,470,467,463,460,457,\n\t453,450,447,443,440,437,434,431\n};\n\n\nstatic const uint32_t XMLinearTable[768] = \n{\n\t535232,534749,534266,533784,533303,532822,532341,531861,\n\t531381,530902,530423,529944,529466,528988,528511,528034,\n\t527558,527082,526607,526131,525657,525183,524709,524236,\n\t523763,523290,522818,522346,521875,521404,520934,520464,\n\t519994,519525,519057,518588,518121,517653,517186,516720,\n\t516253,515788,515322,514858,514393,513929,513465,513002,\n\t512539,512077,511615,511154,510692,510232,509771,509312,\n\t508852,508393,507934,507476,507018,506561,506104,505647,\n\t505191,504735,504280,503825,503371,502917,502463,502010,\n\t501557,501104,500652,500201,499749,499298,498848,498398,\n\t497948,497499,497050,496602,496154,495706,495259,494812,\n\t494366,493920,493474,493029,492585,492140,491696,491253,\n\t490809,490367,489924,489482,489041,488600,488159,487718,\n\t487278,486839,486400,485961,485522,485084,484647,484210,\n\t483773,483336,482900,482465,482029,481595,481160,480726,\n\t480292,479859,479426,478994,478562,478130,477699,477268,\n\t476837,476407,475977,475548,475119,474690,474262,473834,\n\t473407,472979,472553,472126,471701,471275,470850,470425,\n\t470001,469577,469153,468730,468307,467884,467462,467041,\n\t466619,466198,465778,465358,464938,464518,464099,463681,\n\t463262,462844,462427,462010,461593,461177,460760,460345,\n\t459930,459515,459100,458686,458272,457859,457446,457033,\n\t456621,456209,455797,455386,454975,454565,454155,453745,\n\t453336,452927,452518,452110,451702,451294,450887,450481,\n\t450074,449668,449262,448857,448452,448048,447644,447240,\n\t446836,446433,446030,445628,445226,444824,444423,444022,\n\t443622,443221,442821,442422,442023,441624,441226,440828,\n\t440430,440033,439636,439239,438843,438447,438051,437656,\n\t437261,436867,436473,436079,435686,435293,434900,434508,\n\t434116,433724,433333,432942,432551,432161,431771,431382,\n\t430992,430604,430215,429827,429439,429052,428665,428278,\n\t427892,427506,427120,426735,426350,425965,425581,425197,\n\t424813,424430,424047,423665,423283,422901,422519,422138,\n\t421757,421377,420997,420617,420237,419858,419479,419101,\n\t418723,418345,417968,417591,417214,416838,416462,416086,\n\t415711,415336,414961,414586,414212,413839,413465,413092,\n\t412720,412347,411975,411604,411232,410862,410491,410121,\n\t409751,409381,409012,408643,408274,407906,407538,407170,\n\t406803,406436,406069,405703,405337,404971,404606,404241,\n\t403876,403512,403148,402784,402421,402058,401695,401333,\n\t400970,400609,400247,399886,399525,399165,398805,398445,\n\t398086,397727,397368,397009,396651,396293,395936,395579,\n\t395222,394865,394509,394153,393798,393442,393087,392733,\n\t392378,392024,391671,391317,390964,390612,390259,389907,\n\t389556,389204,388853,388502,388152,387802,387452,387102,\n\t386753,386404,386056,385707,385359,385012,384664,384317,\n\t383971,383624,383278,382932,382587,382242,381897,381552,\n\t381208,380864,380521,380177,379834,379492,379149,378807,\n\n\t378466,378124,377783,377442,377102,376762,376422,376082,\n\t375743,375404,375065,374727,374389,374051,373714,373377,\n\t373040,372703,372367,372031,371695,371360,371025,370690,\n\t370356,370022,369688,369355,369021,368688,368356,368023,\n\t367691,367360,367028,366697,366366,366036,365706,365376,\n\t365046,364717,364388,364059,363731,363403,363075,362747,\n\t362420,362093,361766,361440,361114,360788,360463,360137,\n\t359813,359488,359164,358840,358516,358193,357869,357547,\n\t357224,356902,356580,356258,355937,355616,355295,354974,\n\t354654,354334,354014,353695,353376,353057,352739,352420,\n\t352103,351785,351468,351150,350834,350517,350201,349885,\n\t349569,349254,348939,348624,348310,347995,347682,347368,\n\t347055,346741,346429,346116,345804,345492,345180,344869,\n\t344558,344247,343936,343626,343316,343006,342697,342388,\n\t342079,341770,341462,341154,340846,340539,340231,339924,\n\t339618,339311,339005,338700,338394,338089,337784,337479,\n\t337175,336870,336566,336263,335959,335656,335354,335051,\n\t334749,334447,334145,333844,333542,333242,332941,332641,\n\t332341,332041,331741,331442,331143,330844,330546,330247,\n\t329950,329652,329355,329057,328761,328464,328168,327872,\n\t327576,327280,326985,326690,326395,326101,325807,325513,\n\t325219,324926,324633,324340,324047,323755,323463,323171,\n\t322879,322588,322297,322006,321716,321426,321136,320846,\n\t320557,320267,319978,319690,319401,319113,318825,318538,\n\t318250,317963,317676,317390,317103,316817,316532,316246,\n\t315961,315676,315391,315106,314822,314538,314254,313971,\n\t313688,313405,313122,312839,312557,312275,311994,311712,\n\t311431,311150,310869,310589,310309,310029,309749,309470,\n\t309190,308911,308633,308354,308076,307798,307521,307243,\n\t306966,306689,306412,306136,305860,305584,305308,305033,\n\t304758,304483,304208,303934,303659,303385,303112,302838,\n\t302565,302292,302019,301747,301475,301203,300931,300660,\n\t300388,300117,299847,299576,299306,299036,298766,298497,\n\t298227,297958,297689,297421,297153,296884,296617,296349,\n\t296082,295815,295548,295281,295015,294749,294483,294217,\n\t293952,293686,293421,293157,292892,292628,292364,292100,\n\t291837,291574,291311,291048,290785,290523,290261,289999,\n\t289737,289476,289215,288954,288693,288433,288173,287913,\n\t287653,287393,287134,286875,286616,286358,286099,285841,\n\t285583,285326,285068,284811,284554,284298,284041,283785,\n\t283529,283273,283017,282762,282507,282252,281998,281743,\n\t281489,281235,280981,280728,280475,280222,279969,279716,\n\t279464,279212,278960,278708,278457,278206,277955,277704,\n\t277453,277203,276953,276703,276453,276204,275955,275706,\n\t275457,275209,274960,274712,274465,274217,273970,273722,\n\t273476,273229,272982,272736,272490,272244,271999,271753,\n\t271508,271263,271018,270774,270530,270286,270042,269798,\n\t269555,269312,269069,268826,268583,268341,268099,267857 \n};\n\n\nstatic const int8_t ft2VibratoTable[256] = \n{\n\t0,-2,-3,-5,-6,-8,-9,-11,-12,-14,-16,-17,-19,-20,-22,-23,\n\t-24,-26,-27,-29,-30,-32,-33,-34,-36,-37,-38,-39,-41,-42,\n\t-43,-44,-45,-46,-47,-48,-49,-50,-51,-52,-53,-54,-55,-56,\n\t-56,-57,-58,-59,-59,-60,-60,-61,-61,-62,-62,-62,-63,-63,\n\t-63,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-64,-63,-63,\n\t-63,-62,-62,-62,-61,-61,-60,-60,-59,-59,-58,-57,-56,-56,\n\t-55,-54,-53,-52,-51,-50,-49,-48,-47,-46,-45,-44,-43,-42,\n\t-41,-39,-38,-37,-36,-34,-33,-32,-30,-29,-27,-26,-24,-23,\n\t-22,-20,-19,-17,-16,-14,-12,-11,-9,-8,-6,-5,-3,-2,0,\n\t2,3,5,6,8,9,11,12,14,16,17,19,20,22,23,24,26,27,29,30,\n\t32,33,34,36,37,38,39,41,42,43,44,45,46,47,48,49,50,51,\n\t52,53,54,55,56,56,57,58,59,59,60,60,61,61,62,62,62,63,\n\t63,63,64,64,64,64,64,64,64,64,64,64,64,63,63,63,62,62,\n\t62,61,61,60,60,59,59,58,57,56,56,55,54,53,52,51,50,49,\n\t48,47,46,45,44,43,42,41,39,38,37,36,34,33,32,30,29,27,\n\t26,24,23,22,20,19,17,16,14,12,11,9,8,6,5,3,2 \n};\n\n\n\nstatic const DWORD FineLinearSlideUpTable[16] =\n{\n\t65536, 65595, 65654, 65714,\t65773, 65832, 65892, 65951,\n\t66011, 66071, 66130, 66190, 66250, 66309, 66369, 66429\n};\n\n\nstatic const DWORD FineLinearSlideDownTable[16] =\n{\n\t65535, 65477, 65418, 65359, 65300, 65241, 65182, 65123,\n\t65065, 65006, 64947, 64888, 64830, 64772, 64713, 64645\n};\n\n\nstatic const DWORD LinearSlideUpTable[256] = \n{\n\t65536, 65773, 66010, 66249, 66489, 66729, 66971, 67213, \n\t67456, 67700, 67945, 68190, 68437, 68685, 68933, 69182, \n\t69432, 69684, 69936, 70189, 70442, 70697, 70953, 71209, \n\t71467, 71725, 71985, 72245, 72507, 72769, 73032, 73296, \n\t73561, 73827, 74094, 74362, 74631, 74901, 75172, 75444, \n\t75717, 75991, 76265, 76541, 76818, 77096, 77375, 77655, \n\t77935, 78217, 78500, 78784, 79069, 79355, 79642, 79930, \n\t80219, 80509, 80800, 81093, 81386, 81680, 81976, 82272, \n\t82570, 82868, 83168, 83469, 83771, 84074, 84378, 84683, \n\t84989, 85297, 85605, 85915, 86225, 86537, 86850, 87164, \n\t87480, 87796, 88113, 88432, 88752, 89073, 89395, 89718, \n\t90043, 90369, 90695, 91023, 91353, 91683, 92015, 92347, \n\t92681, 93017, 93353, 93691, 94029, 94370, 94711, 95053, \n\t95397, 95742, 96088, 96436, 96785, 97135, 97486, 97839, \n\t98193, 98548, 98904, 99262, 99621, 99981, 100343, 100706, \n\t101070, 101435, 101802, 102170, 102540, 102911, 103283, 103657, \n\t104031, 104408, 104785, 105164, 105545, 105926, 106309, 106694, \n\t107080, 107467, 107856, 108246, 108637, 109030, 109425, 109820, \n\t110217, 110616, 111016, 111418, 111821, 112225, 112631, 113038, \n\t113447, 113857, 114269, 114682, 115097, 115514, 115931, 116351, \n\t116771, 117194, 117618, 118043, 118470, 118898, 119328, 119760, \n\t120193, 120628, 121064, 121502, 121941, 122382, 122825, 123269, \n\t123715, 124162, 124611, 125062, 125514, 125968, 126424, 126881, \n\t127340, 127801, 128263, 128727, 129192, 129660, 130129, 130599, \n\t131072, 131546, 132021, 132499, 132978, 133459, 133942, 134426, \n\t134912, 135400, 135890, 136381, 136875, 137370, 137866, 138365, \n\t138865, 139368, 139872, 140378, 140885, 141395, 141906, 142419, \n\t142935, 143451, 143970, 144491, 145014, 145538, 146064, 146593, \n\t147123, 147655, 148189, 148725, 149263, 149803, 150344, 150888, \n\t151434, 151982, 152531, 153083, 153637, 154192, 154750, 155310, \n\t155871, 156435, 157001, 157569, 158138, 158710, 159284, 159860, \n\t160439, 161019, 161601, 162186, 162772, 163361, 163952, 164545, \n};\n\n\nstatic const DWORD LinearSlideDownTable[256] = \n{\n\t65536, 65299, 65064, 64830, 64596, 64363, 64131, 63900, \n\t63670, 63440, 63212, 62984, 62757, 62531, 62305, 62081, \n\t61857, 61634, 61412, 61191, 60970, 60751, 60532, 60314, \n\t60096, 59880, 59664, 59449, 59235, 59021, 58809, 58597, \n\t58385, 58175, 57965, 57757, 57548, 57341, 57134, 56928, \n\t56723, 56519, 56315, 56112, 55910, 55709, 55508, 55308, \n\t55108, 54910, 54712, 54515, 54318, 54123, 53928, 53733, \n\n\t53540, 53347, 53154, 52963, 52772, 52582, 52392, 52204, \n\t52015, 51828, 51641, 51455, 51270, 51085, 50901, 50717, \n\t50535, 50353, 50171, 49990, 49810, 49631, 49452, 49274, \n\t49096, 48919, 48743, 48567, 48392, 48218, 48044, 47871, \n\t47698, 47526, 47355, 47185, 47014, 46845, 46676, 46508, \n\t46340, 46173, 46007, 45841, 45676, 45511, 45347, 45184, \n\t45021, 44859, 44697, 44536, 44376, 44216, 44056, 43898, \n\t43740, 43582, 43425, 43268, 43112, 42957, 42802, 42648, \n\t42494, 42341, 42189, 42037, 41885, 41734, 41584, 41434, \n\t41285, 41136, 40988, 40840, 40693, 40546, 40400, 40254, \n\t40109, 39965, 39821, 39677, 39534, 39392, 39250, 39108, \n\t38967, 38827, 38687, 38548, 38409, 38270, 38132, 37995, \n\t37858, 37722, 37586, 37450, 37315, 37181, 37047, 36913, \n\t36780, 36648, 36516, 36384, 36253, 36122, 35992, 35862, \n\t35733, 35604, 35476, 35348, 35221, 35094, 34968, 34842, \n\t34716, 34591, 34466, 34342, 34218, 34095, 33972, 33850, \n\t33728, 33606, 33485, 33364, 33244, 33124, 33005, 32886, \n\t32768, 32649, 32532, 32415, 32298, 32181, 32065, 31950, \n\t31835, 31720, 31606, 31492, 31378, 31265, 31152, 31040, \n\t30928, 30817, 30706, 30595, 30485, 30375, 30266, 30157, \n\t30048, 29940, 29832, 29724, 29617, 29510, 29404, 29298, \n\t29192, 29087, 28982, 28878, 28774, 28670, 28567, 28464, \n\t28361, 28259, 28157, 28056, 27955, 27854, 27754, 27654, \n\t27554, 27455, 27356, 27257, 27159, 27061, 26964, 26866, \n\t26770, 26673, 26577, 26481, 26386, 26291, 26196, 26102, \n};\n\n\nstatic const int SpectrumSinusTable[256*2] = \n{\n\t0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 9, 10, 10, 11, \n\t12, 13, 14, 14, 15, 16, 17, 17, 18, 19, 20, 20, 21, 22, 22, 23, \n\t24, 25, 25, 26, 27, 28, 28, 29, 30, 30, 31, 32, 32, 33, 34, 34, \n\t35, 36, 36, 37, 38, 38, 39, 39, 40, 41, 41, 42, 42, 43, 44, 44, \n\t45, 45, 46, 46, 47, 47, 48, 48, 49, 49, 50, 50, 51, 51, 52, 52, \n\t53, 53, 53, 54, 54, 55, 55, 55, 56, 56, 57, 57, 57, 58, 58, 58, \n\t59, 59, 59, 59, 60, 60, 60, 60, 61, 61, 61, 61, 61, 62, 62, 62, \n\t62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, \n\t63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 62, 62, \n\t62, 62, 62, 62, 61, 61, 61, 61, 61, 60, 60, 60, 60, 59, 59, 59, \n\t59, 58, 58, 58, 57, 57, 57, 56, 56, 55, 55, 55, 54, 54, 53, 53, \n\t53, 52, 52, 51, 51, 50, 50, 49, 49, 48, 48, 47, 47, 46, 46, 45, \n\t45, 44, 44, 43, 42, 42, 41, 41, 40, 39, 39, 38, 38, 37, 36, 36, \n\t35, 34, 34, 33, 32, 32, 31, 30, 30, 29, 28, 28, 27, 26, 25, 25, \n\t24, 23, 22, 22, 21, 20, 20, 19, 18, 17, 17, 16, 15, 14, 14, 13, \n\t12, 11, 10, 10, 9, 8, 7, 7, 6, 5, 4, 3, 3, 2, 1, 0, \n\t0, -1, -1, -2, -3, -3, -4, -5, -6, -7, -7, -8, -9, -10, -10, -11, \n\t-12, -13, -14, -14, -15, -16, -17, -17, -18, -19, -20, -20, -21, -22, -22, -23, \n\t-24, -25, -25, -26, -27, -28, -28, -29, -30, -30, -31, -32, -32, -33, -34, -34, \n\t-35, -36, -36, -37, -38, -38, -39, -39, -40, -41, -41, -42, -42, -43, -44, -44, \n\t-45, -45, -46, -46, -47, -47, -48, -48, -49, -49, -50, -50, -51, -51, -52, -52, \n\t-53, -53, -53, -54, -54, -55, -55, -55, -56, -56, -57, -57, -57, -58, -58, -58, \n\t-59, -59, -59, -59, -60, -60, -60, -60, -61, -61, -61, -61, -61, -62, -62, -62, \n\t-62, -62, -62, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, \n\t-63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -63, -62, -62, \n\t-62, -62, -62, -62, -61, -61, -61, -61, -61, -60, -60, -60, -60, -59, -59, -59, \n\t-59, -58, -58, -58, -57, -57, -57, -56, -56, -55, -55, -55, -54, -54, -53, -53, \n\t-53, -52, -52, -51, -51, -50, -50, -49, -49, -48, -48, -47, -47, -46, -46, -45, \n\t-45, -44, -44, -43, -42, -42, -41, -41, -40, -39, -39, -38, -38, -37, -36, -36, \n\t-35, -34, -34, -33, -32, -32, -31, -30, -30, -29, -28, -28, -27, -26, -25, -25, \n\t-24, -23, -22, -22, -21, -20, -20, -19, -18, -17, -17, -16, -15, -14, -14, -13, \n\t-12, -11, -10, -10, -9, -8, -7, -7, -6, -5, -4, -3, -3, -2, -1, 0, \n};\n\n"
  },
  {
    "path": "contrib/libxmp/Makefile.megazeux",
    "content": "##\n# libxmp Makefile fragment\n##\n\n\n.PHONY: libxmp_clean\n\n\nlibxmp_base = contrib/libxmp\n\nlibxmp_include = -I${libxmp_base}/include\n\n\nlibxmp_cflags := \\\n -I${libxmp_base}/src \\\n -I${libxmp_base}/src/loaders \\\n ${libxmp_include} \\\n -DSKIP_SDL -include src/platform_endian.h \\\n -DLIBXMP_NO_PROWIZARD \\\n -DLIBXMP_NO_DEPACKERS\n\n#\n# Disable some warnings libxmp currently ignores for now.\n#\nlibxmp_cflags += \\\n -Wno-missing-prototypes -Wno-unused-macros -Wno-sign-compare \\\n\nifeq (${HAS_W_NO_UNUSED_BUT_SET_VARIABLE},1)\nlibxmp_cflags += -Wno-unused-but-set-variable\nendif\n\nSRC_OBJS = \\\n  control.o       \\\n  dataio.o        \\\n  effects.o       \\\n  extras.o        \\\n  far_extras.o    \\\n  filetype.o      \\\n  filter.o        \\\n  flow.o          \\\n  format.o        \\\n  hio.o           \\\n  hmn_extras.o    \\\n  lfo.o           \\\n  load.o          \\\n  load_helpers.o  \\\n  md5.o           \\\n  med_extras.o    \\\n  memio.o         \\\n  mixer.o         \\\n  mix_all.o       \\\n  period.o        \\\n  player.o        \\\n  read_event.o    \\\n  rng.o           \\\n  scan.o          \\\n  smix.o          \\\n  virtual.o       \\\n\nSRC_PATH\t= $(libxmp_base)/src\nSRC_OBJ = $(SRC_PATH)/.build\n\n\nlibxmp_objs += $(addprefix $(SRC_OBJ)/,$(SRC_OBJS))\n\n\nLOADERS = \\\n  669_load.o    \\\n  amf_load.o    \\\n  asylum_load.o \\\n  far_load.o    \\\n  flt_load.o    \\\n  gdm_load.o    \\\n  hmn_load.o    \\\n  ice_load.o    \\\n  it_load.o     \\\n  med2_load.o   \\\n  med3_load.o   \\\n  med4_load.o   \\\n  mmd_common.o  \\\n  mmd1_load.o   \\\n  mmd3_load.o   \\\n  mod_load.o    \\\n  mtm_load.o    \\\n  okt_load.o    \\\n  s3m_load.o    \\\n  st_load.o     \\\n  stm_load.o    \\\n  ult_load.o    \\\n  xm_load.o     \\\n\nLOADERS_OBJS = \\\n  common.o      \\\n  iff.o         \\\n  itsex.o       \\\n  sample.o      \\\n  $(LOADERS)\n\nLOADERS_PATH\t= $(libxmp_base)/src/loaders\nLOADERS_OBJ\t= $(LOADERS_PATH)/.build\n\n\nlibxmp_objs += $(addprefix $(LOADERS_OBJ)/,$(LOADERS_OBJS))\n\n\n${SRC_OBJ}/%.o: ${SRC_PATH}/%.c src/config.h\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${libxmp_cflags} -c $< -o $@\n\n${LOADERS_OBJ}/%.o: ${LOADERS_PATH}/%.c src/config.h\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${libxmp_cflags} -c $< -o $@\n\n\n-include $(libxmp_objs:.o=.d)\n\n\n$(libxmp_objs): $(filter-out $(wildcard ${SRC_OBJ}), ${SRC_OBJ})\n$(libxmp_objs): $(filter-out $(wildcard ${LOADERS_OBJ}), ${LOADERS_OBJ})\n\nlibxmp_clean:\n\t$(if ${V},,@echo \"  RM      \" ${SRC_OBJ})\n\t${RM} -r ${SRC_OBJ}\n\t$(if ${V},,@echo \"  RM      \" ${LOADERS_OBJ})\n\t${RM} -r ${LOADERS_OBJ}\n"
  },
  {
    "path": "contrib/libxmp/Makefile.megazeux-gen",
    "content": "#\n# Makefile to generate MegaZeux contrib/libxmp/ directory.\n#\n\nifeq (${MEGAZEUX_REPO_PATH},)\n$(error Must define MEGAZEUX_REPO_PATH!)\nendif\n\nbase\t\t:= .\ndocs\t\t:= ${base}/docs\ninclude\t\t:= ${base}/include\nsrc\t\t:= ${base}/src\nloaders\t\t:= ${src}/loaders\n\ndest_base\t:= ${MEGAZEUX_REPO_PATH}/contrib/libxmp\ndest_docs\t:= ${dest_base}/${docs}\ndest_include\t:= ${dest_base}/${include}\ndest_src\t:= ${dest_base}/${src}\ndest_loaders\t:= ${dest_base}/${loaders}\n\n#\n# Patch branches to merge into the generated libxmp branch.\n#\npatches := \\\n\tmzx-integration-hacks \\\n\tmzx-remove-extra-formats \\\n\treal-max-srate \\\n\n#\n# Files to install from the generated libxmp branch to the\n# MegaZeux libxmp contrib folder.\n#\ninstall := \\\n\t${dest_base}/README \\\n\t${dest_base}/README.megazeux.md \\\n\t${dest_base}/Makefile.megazeux \\\n\t${dest_base}/Makefile.megazeux-gen \\\n\t${dest_base}/mzx-gen.sh \\\n\t${dest_docs}/COPYING \\\n\t${dest_docs}/CREDITS \\\n\t${dest_docs}/Changelog \\\n\t${dest_include}/xmp.h \\\n\t${dest_src}/callbackio.h \\\n\t${dest_src}/common.h \\\n\t${dest_src}/control.c \\\n\t${dest_src}/dataio.c \\\n\t${dest_src}/effects.c \\\n\t${dest_src}/effects.h \\\n\t${dest_src}/extras.c \\\n\t${dest_src}/extras.h \\\n\t${dest_src}/far_extras.c \\\n\t${dest_src}/far_extras.h \\\n\t${dest_src}/filetype.c \\\n\t${dest_src}/filter.c \\\n\t${dest_src}/flow.c \\\n\t${dest_src}/format.c \\\n\t${dest_src}/format.h \\\n\t${dest_src}/hio.c \\\n\t${dest_src}/hio.h \\\n\t${dest_src}/hmn_extras.c \\\n\t${dest_src}/hmn_extras.h \\\n\t${dest_src}/lfo.c \\\n\t${dest_src}/lfo.h \\\n\t${dest_src}/list.h \\\n\t${dest_src}/load.c \\\n\t${dest_src}/load_helpers.c \\\n\t${dest_src}/md5.c \\\n\t${dest_src}/md5.h \\\n\t${dest_src}/mdataio.h \\\n\t${dest_src}/med_extras.c \\\n\t${dest_src}/med_extras.h \\\n\t${dest_src}/memio.c \\\n\t${dest_src}/memio.h \\\n\t${dest_src}/mix_all.c \\\n\t${dest_src}/mixer.c \\\n\t${dest_src}/mixer.h \\\n\t${dest_src}/period.c \\\n\t${dest_src}/period.h \\\n\t${dest_src}/player.c \\\n\t${dest_src}/player.h \\\n\t${dest_src}/precomp_lut.h \\\n\t${dest_src}/read_event.c \\\n\t${dest_src}/rng.c \\\n\t${dest_src}/rng.h \\\n\t${dest_src}/scan.c \\\n\t${dest_src}/smix.c \\\n\t${dest_src}/tempfile.h \\\n\t${dest_src}/virtual.c \\\n\t${dest_src}/virtual.h \\\n\t${dest_loaders}/common.c \\\n\t${dest_loaders}/iff.c \\\n\t${dest_loaders}/iff.h \\\n\t${dest_loaders}/loader.h \\\n\t${dest_loaders}/sample.c \\\n\t${dest_loaders}/669_load.c \\\n\t${dest_loaders}/amf_load.c \\\n\t${dest_loaders}/asylum_load.c \\\n\t${dest_loaders}/far_load.c \\\n\t${dest_loaders}/flt_load.c \\\n\t${dest_loaders}/gdm_load.c \\\n\t${dest_loaders}/hmn_load.c \\\n\t${dest_loaders}/ice_load.c \\\n\t${dest_loaders}/it.h \\\n\t${dest_loaders}/it_load.c \\\n\t${dest_loaders}/itsex.c \\\n\t${dest_loaders}/med.h \\\n\t${dest_loaders}/med2_load.c \\\n\t${dest_loaders}/med3_load.c \\\n\t${dest_loaders}/med4_load.c \\\n\t${dest_loaders}/mmd1_load.c \\\n\t${dest_loaders}/mmd3_load.c \\\n\t${dest_loaders}/mmd_common.c \\\n\t${dest_loaders}/mod.h \\\n\t${dest_loaders}/mod_load.c \\\n\t${dest_loaders}/mtm_load.c \\\n\t${dest_loaders}/okt_load.c \\\n\t${dest_loaders}/s3m.h \\\n\t${dest_loaders}/s3m_load.c \\\n\t${dest_loaders}/st_load.c \\\n\t${dest_loaders}/stm_load.c \\\n\t${dest_loaders}/ult_load.c \\\n\t${dest_loaders}/xm.h \\\n\t${dest_loaders}/xm_load.c\n\n\n.PHONY: all libxmp_dir libxmp_branch libxmp_patches\n\nall: libxmp_dir libxmp_branch libxmp_patches ${install}\n\nlibxmp_dir:\n\trm -rf ${dest_base}\n\tmkdir -p ${dest_base}\n\tmkdir -p ${dest_docs}\n\tmkdir -p ${dest_include}\n\tmkdir -p ${dest_src}\n\tmkdir -p ${dest_loaders}\n\nlibxmp_branch:\n\tgit checkout mzx-gen-libxmp || true\n\tgit branch -D mzx-libxmp-generated || true\n\tgit checkout -b mzx-libxmp-generated\n\nlibxmp_patches: libxmp_branch\n\t@for p in ${patches}; do \\\n\t  echo \"Attempting to merge:\" $$p; \\\n\t  git merge --no-edit $$p; \\\n\tdone\n\n${install}: libxmp_patches libxmp_dir\n\n${dest_base}/%: ${base}/%\n\tcp $< $@\n\n${dest_docs}/%: ${docs}/%\n\tcp $< $@\n\n${dest_include}/%: ${include}/%\n\tcp $< $@\n\n${dest_src}/%: ${src}/%\n\tcp $< $@\n\n${dest_loaders}/%: ${loaders}/%\n\tcp $< $@\n\n"
  },
  {
    "path": "contrib/libxmp/README",
    "content": "\n\n\n                 __   _____                           \n                |  | |__|  |______  ___ ____________  \n                |  | |  || __  \\  \\/  //      ____  \\ \n                |  |_|  || |_>  >    <|  Y Y  \\ |_>  >\n                |____/__||_____/__/\\  \\__|_|  /  ___/ \n                                    \\_/     \\/|_|    \n                    Extended Module Player Library\n\n                              Version 4.6\n\n\nOVERVIEW\n\nLibxmp is a library that renders module files to PCM data. It supports\nover 90 mainstream and obscure module formats including Protracker (MOD),\nScream Tracker 3 (S3M), Fast Tracker II (XM), and Impulse Tracker (IT).\n\nMany compressed module formats are supported, including popular Unix, DOS,\nand Amiga file packers including gzip, bzip2, SQSH, Powerpack, etc.\n\n\nLIBRARY API\n\nSee docs/libxmp.html for the library API reference. The documentation\nis also available in man page and PDF formats.\n\n\nSUPPORTED FORMATS\n\nSee docs/formats.txt for the list of supported formats. By default,\nProtracker modules are played with a reasonable mix of Protracker 2/3\nquirks (because certain Protracker 1/2 bugs are just too weird and\ngenerally not worth emulating). Other Amiga 4 channel MODs are played with\na generic Noisetracker/Protracker-style replayer, Scream Tracker III MODs\nare played with a Scream Tracker style replayer and multichannel MOD\nvariations are played with a Fasttracker II style replayer. Multifile\nformats (Startrekker AM, Magnetic Fields, etc). must have all files in\nthe same directory.\n\n\nSUPPORTED PACKERS\n\nThe following formats have built-in decompressors: bz2, gz, lha, oxm, xz,\nZ, zip, ArcFS, arc, MMCMP, PowerPack, !Spark, SQSH, MUSE, LZX, and S404.\nOther compressed formats need helpers to be installed on the system:\nmo3 (unmo3) and rar (unrar).\n\n\nAUTHORS AND CONTACT\n\nSee docs/CREDITS for the full list of authors. Send your comments\nand bug reports to xmp-devel@lists.sourceforge.net, or directly to\ncmatsuoka@gmail.com.\n\n\nLICENSE\n\nExtended Module Player\nCopyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the \"Software\"),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n"
  },
  {
    "path": "contrib/libxmp/README.megazeux.md",
    "content": "# MegaZeux libxmp fork.\n\nThis is a fork of libxmp generated specifically for MegaZeux\nfrom this branch using the file `mzx-gen.sh`: https://github.com/AliceLR/libxmp/tree/mzx-gen-libxmp\n\nThe script that generated this forked\n[libxmp 4.5.0](https://github.com/libxmp/libxmp/releases/tag/libxmp-4.5.0)\nand applied the following branches as patches:\n\n* [mzx-integration-hacks](https://github.com/AliceLR/libxmp/tree/mzx-integration-hacks):\n  adds some defines to the start of xmp.h to hack various things to act\n  the way MZX would prefer, since this copy of libxmp needs to build static\n  against MZX in the MZX build system.\n* [mzx-remove-extra-formats](https://github.com/AliceLR/libxmp/tree/mzx-remove-extra-formats):\n  removes some of the more obscure formats supported by libxmp that were not\n  likely to have been used. This means effectively everything that wasn't\n  supported by the MegaZeux fork of libmodplug except for things that could\n  be mistaken for other formats (misc. formats for MOD, MED derivatives for MED).\n  This keeps the MZX supported formats list roughly the same between libmodplug,\n  mikmod, and libxmp and helps reduce executable size for embedded platforms.\n\nThis patch automatically copies only the files required by MegaZeux, so manual file\nremoval should no longer be necessary.\n\nSee README and docs/COPYING.LIB for more libxmp information and the libxmp license.\nSee the upstream repository for the original libxmp, which you should probably use\ninstead of this hacked up copy for most purposes: https://github.com/libxmp/libxmp/\n\n## Pending MZX-specific hacks\n\n* asie contributed a patch for tracker detection removal in the S3M and IT loaders.\n  This is not currently used by MZX and could save about 2-3kb RAM. This might be\n  worth looking into someday. https://github.com/AliceLR/megazeux/pull/202\n"
  },
  {
    "path": "contrib/libxmp/docs/COPYING",
    "content": "Extended Module Player\nCopyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the \"Software\"),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "contrib/libxmp/docs/CREDITS",
    "content": "The Authors\n\n- Mirko Buffoni <mirbuf@gmail.com>\n  * Windows port fixes\n  * random hacks and improvements\n\n- Frederic Bujon <lvdl@bigfoot.com>\n  * XM pan fix\n\n- Hipolito Carraro Jr <hipolito.carraro@gmail.com>\n  * main player coding\n  * software mixer\n  * virtual channel system design and implementation\n\n- Cameron Cawley\n  * Coconizer loader improvements, cleanup\n\n- Rudolf Cejka <cejkar@dcse.fee.vutbr.cz>\n  * MED loader alignment and endianism fixes\n  * XM loader endianism fixes\n  * S3M loader fixes\n\n- Sylvain Chipaux\n  * Prowizard loaders\n\n- Chris Cox <cox.christopher@gmail.com>\n  * S3M loader fixes\n\n- Lionel Debroux\n  * numerous fuzzing test files\n\n- Michael Doering <mldoering@gmx.net>\n  * RAR unpacking support\n  * PP20 decrunching fixes\n\n- Rune Elvemo <relvemo@grm.hia.no>\n  * fixes for endianism problems in Linux/PPC\n\n- Mike Gorchak <mike@malva.ua>\n  * misc hacks\n\n- Tom Hargreaves <hex@freezone.co.uk>\n  * Digital Symphony and Archimedes Tracker loader fixes\n\n- Kevin Langman <langman@earthling.net>\n  * OS/2 port\n\n- Antti S. Lankila <alankila@bel.fi>\n  * Amiga sound model and processing algorithm\n\n- Stephen Leary <sleary@vavi.co.uk>\n  * AMOS Music Bank format loader\n\n- Claudio Matsuoka <cmatsuoka@gmail.com>\n  * main player coding\n  * module loaders\n\n- Dominik Mierzejewski <dmierzej@elka.pw.edu.pl>\n  * fixes for gcc 2.96/glibc 2.2\n\n- Vitaly Novichkov\n  * build system fixes, Emscripten port fixes\n\n- Alice Rowan <petrifiedrowan@gmail.com>\n  * numerous format loader fixes, improvements, and tests\n  * stability fixes\n\n- Thomas Neumann <thomas@polycode.dk>\n  * multiple format loader fixes.\n\n- Francis Russell <francis@unchartedbackwaters.co.uk>\n  * OctaMED tempo fixes\n\n- Adam Sampson <ats@offog.org>\n  * buildsystem and configuration file location fixes\n\n- Johan Samuelsson <spot@triad.se>\n  * Amiga port and fixes\n\n- Özkan Sezer <sezeroz@gmail.com>\n  * build system/portability improvements\n  * stability fixes and cleanup\n  * Unreal UMX loader\n\n- Attila Sipos <h430827@stud.u-szeged.hu>\n  * SQSH decruncher checksum\n\n- Miodrag Vallat <miod@mikmod.darkorb.net>\n  * 669 loader fixes\n  * XM loader endianism fixes\n\n- Chris Young <cdyoung@ntlworld.com>\n  * Amiga port and fixes\n\n\nOther contributors:\n\nAntti Huovilainen, Michael Janson, Matthias Leonhardt, Andy Eltsov,\nDavi Lima, Geoff Reedy, Sipos Attila, Bjoern Fisher, Matus Telgarsky,\nBernhard März, Cyke O'Path, Rudolf Cejka, Igor Krpanic, Chris Cox,\nRudá Moura, Paul Wise, Henrik Pauli, Zbigniew Luszpinski, Jon Rafkind,\nReynir Stefansson, Ralf Hoffmann, Douglas Carmichael, Adric Riedel,\nGürkan Sengün, Lorence Lombardo, Martin Willers, Laurent Clevy,\nMichael Doering, Bert Jahn, Adi Sapir, Jools Smyth, Martin Jeppesen,\nStuart Caie, Bernhard März, Matthias Arndt, Johannes Schultz, viiri,\nNoSuck, Gabriel Kind, LossyDragon, Carsten Teibes.\n\n\nThird-party code licenses:\n\nMD5 digest\n\n This code implements the MD5 message-digest algorithm.\n The algorithm is due to Ron Rivest.  This code was\n written by Colin Plumb in 1993, no copyright is claimed.\n This code is in the public domain; do with it what you wish.\n\n Equivalent code is available from RSA Data Security, Inc.\n This code has been tested against that, and is equivalent,\n except that you don't need to include two pages of legalese\n with every copy.\n\n\nPowerpack depacker\n\n Based on code by Stuart Caie <kyzer@4u.net>\n Modified by Michael Doering and Claudio Matsuoka\n This software is in the Public Domain\n\n\nXZ decompressor\n\n XZ Embedded is a relatively small, limited implementation of the .xz\n file format. Currently only decoding is implemented.\n\n Authors: Lasse Collin <lasse.collin@tukaani.org>\n          Igor Pavlov <http://7-zip.org/>\n This file has been put into the public domain.\n You can do whatever you want with this file.\n\n\nZip decompressor and DEFLATE decoder\n\n Copyright 2013-2014 RAD Game Tools and Valve Software\n Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC\n\n All Rights Reserved.\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in\n all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n THE SOFTWARE.\n\n\nStonecracker decompressor\n\n StoneCracker S404 algorithm data decompression routine\n (c) 2006 Jouni 'Mr.Spiv' Korhonen. The code is in public domain.\n\n\nBzip2 decompressor\n\n Copyright 2003, 2007 Rob Landley <rob@landley.net>\n\n Based on a close reading (but not the actual code) of the original bzip2\n decompression code by Julian R Seward (jseward@acm.org), which also\n acknowledges contributions by Mike Burrows, David Wheeler, Peter Fenwick,\n Alistair Moffat, Radford Neal, Ian H. Witten, Robert Sedgewick, and\n Jon L. Bentley.\n\n This is 0BSD-licensed code from https://github.com/landley/toybox:\n\n Permission to use, copy, modify, and/or distribute this software for any\n purpose with or without fee is hereby granted.\n\n THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n\nLHASA backend for LHA decompressor\n\n Copyright (c) 2011-2023, Simon Howard.\n\n Permission to use, copy, modify, and/or distribute this software\n for any purpose with or without fee is hereby granted, provided\n that the above copyright notice and this permission notice appear\n in all copies.\n\n THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL\n WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED\n WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE\n AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR\n CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,\n NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN\n CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n\nDMF sample decompressor\nIT resonant filters\nIT sample decompressor\n\n Modplug Plugin for XMMS v2.0 / libmodplug v0.8.5\n Based on the ModPlug sound engine by Olivier Lapicque <olivierl@jps.net>\n\n This source code is public domain.\n Authors: Olivier Lapicque <olivierl@jps.net>\n\n\nMED2 module loading code\n\n  V2.00 file loading/saving routines by T. Kinnunen 1990\n  MED2.00 by Teijo Kinnunen, 1990\n  MED is in Public Domain\n\n\nVorbis decoder\n\n Ogg Vorbis audio decoder - v1.22 - public domain\n http://nothings.org/stb_vorbis/\n\n Original version written by Sean Barrett in 2007.\n\n Originally sponsored by RAD Game Tools. Seeking implementation\n sponsored by Phillip Bennefall, Marc Andersen, Aaron Baker,\n Elias Software, Aras Pranckevicius, and Sean Barrett.\n\n Placed in the public domain April 2007 by the author: no copyright is\n claimed, and you may use it for any purpose you like.\n\n\nptpopen - A Windows replacement for popen()\n\n pt_popen/pt_pclose functions\n Written somewhere in the 90s by Kurt Keller\n\n This piece of code is in the public domain. I do not claim any rights\n on it. Do whatever you want to do with it and I hope it will be still\n useful.\n\n\nmkstemp implementation\n\n Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Högskolan\n (Royal Institute of Technology, Stockholm, Sweden).\n All rights reserved.\n\n Redistribution and use in source and binary forms, with or without\n modification, are permitted provided that the following conditions\n are met:\n\n 1. Redistributions of source code must retain the above copyright\n    notice, this list of conditions and the following disclaimer.\n 2. Redistributions in binary form must reproduce the above copyright\n    notice, this list of conditions and the following disclaimer in the\n    documentation and/or other materials provided with the distribution.\n 3. Neither the name of the Institute nor the names of its contributors\n    may be used to endorse or promote products derived from this software\n    without specific prior written permission.\n\n THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND\n ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE\n FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n SUCH DAMAGE.\n\n\nfnmatch implementation\n\n Copyright (c) 1989, 1993, 1994\n      The Regents of the University of California.  All rights reserved.\n\n This code is derived from software contributed to Berkeley by\n Guido van Rossum.\n\n Redistribution and use in source and binary forms, with or without\n modification, are permitted provided that the following conditions\n are met:\n\n 1. Redistributions of source code must retain the above copyright\n    notice, this list of conditions and the following disclaimer.\n 2. Redistributions in binary form must reproduce the above copyright\n    notice, this list of conditions and the following disclaimer in the\n    documentation and/or other materials provided with the distribution.\n 3. Neither the name of the University nor the names of its contributors\n    may be used to endorse or promote products derived from this software\n    without specific prior written permission.\n\n THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n SUCH DAMAGE.\n"
  },
  {
    "path": "contrib/libxmp/docs/Changelog",
    "content": "Stable versions\n---------------\n\n4.6.3 (20250511):\n\tChanges by Alice Rowan:\n\t- Fix crashes when xmp_set_position is used to set a negative position.\n\t  Negative positions now correctly return -XMP_ERROR_INVALID.\n\t- Fix crashes when xmp_set_row is used to set a negative row.\n\t  Negative positions now correctly return -XMP_ERROR_INVALID.\n\t- Fix hangs when xmp_prev_position is used on the first position of\n\t  a sequence which is preceded by an S3M/IT skip marker.\n\t- Fix out-of-bounds reads when xmp_next_position is used at the end of\n\t  a 256 position module.\n\t- Fix hangs when seeking to an end-of-module marker caused by these\n\t  positions getting assigned a non-existent sequence.\n\t- Document xmp_set_position/xmp_next_position/xmp_prev_position\n\t  interactions with xmp_stop_module/xmp_restart_module.\n\t- Fix stack underflow in Pha Packer loader (CVE-2025-47256).\n\t- Slight performance improvements for the Oktalyzer and SFX loaders.\n\t- Fix broken conversion of ProRunner 2.0 pattern data.\n\t- xmp_set_tempo_factor no longer alters frame time calculation for\n\t  xmp_get_frame_info. Frame time is now updated to account for the\n\t  new time factor after calling xmp_scan_module.\n\t- Fix loading XMs with some types of harmless pattern truncation.\n\t  See: Jazztiz/ta-da-da-da.xm, Sonic (UK)/phuture.xm, et al.\n\t- Fix Poly Tracker pattern break hex parameter.\n\t- Fix Digital Tracker broken instrument finetune.\n\t- Fix Digital Tracker 2.03 position jump effect for 4 channel DTMs.\n\t- Fix pattern loop jump interactions with same row pattern jump/break:\n\t  Scream Tracker 3.03b+; Impulse Tracker 1.00 to 1.06 IT;\n\t  Impulse Tracker 2.00+ IT/S3M; Modplug Tracker 1.16 IT/XM/S3M;\n\t  Imago Orpheus IMF/S3M; Liquid Tracker LIQ/S3M; Poly Tracker;\n\t  Digital Tracker <=2.02 DTM/MOD; Digital Tracker 2.03 (partial);\n\t  Digital Tracker 1.9 (partial); Octalyser.\n\tChanges by Thomas Neumann:\n\t- Fix the pattern loop effect in Astroidea XMF loader.\n\t- Fix loading of Extreme Pinball modules.\n\n4.6.2 (20250224):\n\tChanges by Alice Rowan:\n\t- Fix MED effect 1Fxy (delay and retrigger). The new implementation\n\t  supports both delay and retrigger at the same time and repeats.\n\t- Fix MED effect FF3 (revert change from 4.6.1). The buggy version\n\t  of this effect prior to OctaMED v5 is not currently supported.\n\t- Fix MED3 and MED4 time factor and tempos 1-10.\n\t- Fix MED4 effect 9xx (set speed).\n\t- Add support for MED3 and MED4 song files.\n\t- Better MED MMD tracker version fingerprinting.\n\t- Fix loading external instruments from the current directory.\n\tChanges by Thomas Neumann:\n\t- Fix oxm modules with empty samples (e.g.: The Four Ages.oxm)\n\t- Fix some modules would be misidentified as Scream Tracker 2.\n\t- Handle IT modules with edit history but no midi configuration\n\tChanges by Ozkan Sezer:\n\t- Revert a bad CVE fix to the miniz inflate code\n\t- Add missing HAVE_DIRENT definition to Android makefile.\n\n4.6.1 (20250101):\n\tChanges by Claudio Matsuoka:\n\t- The full library is now under MIT license\n\tChanges by Alice Rowan:\n\t- Add stereo sample loading support for IT, S3M, XM, MED, LIQ, and\n\t  Digital Tracker (partial).\n\t- Add sample preamplification to filter mixers for high sample rates.\n\t- Add support for Ultra Tracker tempo commands.\n\t- Load Ultra Tracker comments instead of skipping them.\n\t- Implement support for Protracker instrument swapping.\n\t- Implement retrigger effects for MED, OctaMED, and Liquid Tracker\n\t  where only one retrigger occurs. Liquid Tracker (new format) and\n\t  Digital Symphony now allow retrigger values larger than 15.\n\t- Fix XM envelope sustain points that exist on a zero-length loop.\n\t- Fix XM extra fine portamento effect memory.\n\t- Fix XM portamento up and portamento down memory (only for modules\n\t  where FT2 bug compatibility is enabled, for now).\n\t- Fix loading edge case STMs with an EOF byte of 2.\n\t- Fix loading Imago Orpheus modules with null instrument magic\n\t  strings, bidirectional samples, and disabled default panning.\n\t- Faster IT loading by buffering pattern, sample, and comment reads.\n\t- Fix loop detection edge cases broken by S3M/IT marker scan bugs.\n\t- Add fix for IT break to module scan (was missed in libxmp 4.5.0.)\n\t- Fix restart position for >64k sample and Digital Tracker MODs.\n\t- Reset Invert Loop position when a new instrument is encountered.\n\t- MOD: make presence of invert loop override tracker ID guesses.\n\t  M.K. modules within Amiga limits which use EFx invert loop are\n\t  now IDed as Protracker.\n\t- Multiple Digital Tracker bug fixes:\n\t  * Support for loading Digital Tracker 2.03 DTMs (MOD patterns).\n\t  * Support for loading Digital Tracker 1.9 DTMs (VERS/SV19).\n\t  * Better Digital Tracker version fingerprinting.\n\t  * Fix Digital Tracker 2.03 global sample rate and bit depth fields.\n\t  * Fix Digital Tracker 2.04 pattern note loading (was off-by-one).\n\t  * Fix Digital Tracker instrument loops (loop end was off-by-one).\n\t  * Allow patterns up to 396 rows in Digital Home Studio DTMs.\n\t  * Support for Digital Tracker 1.9 \"MIDI note\" transpose.\n\t  * Simulate Digital Tracker effects bugs where possible.\n\t  * Fix loading of Digital Tracker module names (not always 32 bytes).\n\t- Liquid Tracker bug fixes:\n\t  * [Old] Fix loading of module and instrument names (fixes the old\n\t    format version of WASTETIM.LIQ).\n\t  * [Old] Fix loading of instrument lengths/loops (32-bit, not 16-bit).\n\t  * [Old] Fix loading of pattern notes and volumes (off-by-one).\n\t  * [Old] Fix initial panning.\n\t  * [Old] Most effects are now supported. H70/H6A is not yet supported.\n\t    Fxx, if it was ever implemented, is not yet supported.\n\t  * [New] Fixed Pan Control, Retrigger, Global Volume effects, and\n\t    vibrato/tremolo waveform 3. P70/P6A is not yet supported.\n\t  * [New] Fix incorrect bounding of notes that caused several LIQs\n\t    to fail to load.\n\t  * [New] Fix loading of pattern volumes (off-by-one).\n\t  * [New] 16-bit samples with loops no longer cause a failed load.\n\t  * [New] Fix loading of instrument global volumes.\n\t  * [New] Add support for ping pong loops.\n\t  * [New] Fix initial channel volume/pan tables for 0.00 LIQs.\n\t  * [New] Support channel volume and instrument global volume gain\n\t    functionality by doubling the mix volume once for each (4x total).\n\t  * [Both] Stopped incorrectly applying QUIRK_S3MLOOP, QUIRK_VOLPDN,\n\t    QUIRK_S3MRTG, and QUIRK_MARKER.\n\t- Fix loading of Poly Tracker (PTM) empty sample names.\n\t- Fix Real Tracker loader on targets where char is unsigned by default.\n\t- Fix out-of-bounds reads in His Master's Noise Mupp instruments.\n\t- Fix slow ProWizard testing.\n\t- Fix Paula mixer state leak after changing XMP_PLAYER_MODE.\n\t- Replace rand() with a built-in reentrant alternative.\n\t- xmp_set_tempo_factor now returns -XMP_ERROR_STATE when called prior\n\t  to xmp_start_player (instead of causing crashes).\n\t- Fix mixer crashes caused by previously valid tempo factors after\n\t  sample rate or BPM changes.\n\t- Passing NULL to xmp_set_instrument_path() now unsets the instrument\n\t  path instead of crashing.\n\t- Merge song file instrument path detection routines.\n\t- Fix module scan pattern delay counting.\n\t- Add compatibility for non-standard Pattern Loop implementations:\n\t  Scream Tracker 3.01b; Scream Tracker 3.03b+; Impulse Tracker 1.00;\n\t  Impulse Tracker 1.04 to 2.09; Modplug Tracker 1.16; Digital Tracker\n\t  <=2.04; Digital Tracker 1.9; Octalyser; Imago Orpheus; Liquid Tracker;\n\t  Poly Tracker. (MOD, FT2, and IT 2.10+ were already supported.)\n\t- Fixed numerous defects found by fuzzing.\n\tChanges by Thomas Neumann:\n\t- Fix XM envelope handling\n\t- Bug fixes to DSMI loader\n\t- Fix 16-bit sample check in MultiTracker loader\n\t- Fix finetune in MultiTracker loader\n\t- Fix XM restart position, so that it is possible to play\n\t \"10 Days Of Abstinence.xm\"\n\tChanges by Saga Musix:\n\t- S3M: Detect PlayerPRO, Velvet Studio and old MPT versions\n\tChanges by ds-sloth:\n\t- Optimize scan code for common case of no effects\n\tChanges by Misty De Méo:\n\t- Filter off html files when reading archives.\n\tChanges by Ozkan Sezer:\n\t- Change lha depacker to use a stripped down version of lhasa.\n\t- Merge several CVE fixes to depackers stb_vorbis backend.\n\t- Make the lite version buildable from git or full distribution.\n\t- Build system fixes and clean-ups. Misc code clean-ups.\n\n4.6.0 (20230615):\n\tChanges by Alice Rowan:\n\t- Add Astroidea XMF format loader.\n\t- Implement S3M and IT mix volume.\n\t- Add IT MIDI macro filter effects support.\n\t- Fix for IT filter cutoff 127 on new note behavior.\n\t- Add missing IT filter clamp to mixer loops.\n\t- Fix IT duplicate note check to use the key prior to transpose.\n\t- Fix multiple IT playback bugs affecting, e.g. Atomic Playboy.\n\t- Fix IT tone portamento and offset.\n\t- Fix reverse sustain loop release bug, add IT effect S9F support.\n\t- Add Modplug ADPCM4 support for Impulse Tracker modules.\n\t- Improve anticlick performance and fix anticlick filter volume bug.\n\t- IT fade envelope reset should only affect volume envelope.\n\t- Fix Impulse Tracker envelope and fadeout order.\n\t- Replace bidirectional loop unrolling with reverse sample rendering.\n\t- Fix crash when xmp_set_row() is used on an IT end marker.\n\t- Fix NNA and tone portamento interaction with sample changes.\n\t- Add compatibility for Modplug Tracker preamp scaling.\n\t- Add tracker detection for ModPlug Tracker XMs, pre-alpha ITs.\n\t- New Protracker CIA and VBlank timing detection routine.\n\t- Fix detection for TakeTracker TDZx MODs.\n\t- Fix loading of Digital Tracker FA0x MODs.\n\t- Fix ASYLUM Music Format restart byte.\n\t- Fix >1MB S3M modules relying on the sample segment high byte.\n\t- New MIT-licensed Digital Symphony LZW decoder.\n\t- Add support for Digital Symphony sigma-delta samples.\n\t- Fix Digital Symphony effects and pattern loading issues.\n\t- Fix support for most Farandole Composer effects.\n\t- Implement Ultra Tracker tone portamento.\n\t- Multiple OctaMED fixes and support for MMDC packed modules.\n\t- Fix MED Soundstudio 2 default note events.\n\t- Fix MTM tempo effect and FX_SPEED scan bugs.\n\t- Add MTM module tempo mode detection and comments support.\n\t- Multiple fixes and updates for ST modules.\n\t- Move interpolation wraparound handling out of sample loader.\n\t- Don't increment voice position by step value at loop/tick end.\n\t- Several loading performance improvements.\n\t- Allow up to 255 sequences to be scanned.\n\t- Allow xmp_smix_play functions to play key off, cut and fade events.\n\t- Allow loading MED2 samples from the module directory.\n\t- Fix misc. bugs in the MASI 16 loader.\n\t- Fix heap corruption in Coconizer loader with invalid sequences.\n\t- Fix MMCMP literal block unpacking.\n\t- Fix bad seeking behavior in MMCMP compressed blocks.\n\t- New permissive licensed Amiga LZX and ARC/ArcFS depackers.\n\t- Update XZ depacker with an MIT reimplementation.\n\t- Fixed numerous defects found by fuzzing.\n\tChanges by Saga Musix:\n\t- Fix MMD0 pattern loader to honor play transpose setting.\n\tChanges by Vitaly Novichkov:\n\t- Cmake build system support.\n\tChanges by Anonymous Maarten:\n\t- Cmake and autotools build system updates.\n\tChanges by Ozkan Sezer:\n\t- Galaxy Music System loading no longer needs including depackers\n\t  to inflate MUSE container.\n\t- Cleanups and refactoring of platform-specific code.\n\t- Multiple code cleanups.\n\t- Build system fixes and clean-ups.\n\tChanges by Claudio Matsuoka:\n\t- Fix linkage with gcc when versioned symbols and LTO are enabled.\n\tChanges by Cameron Cawley:\n\t- Integrate the OXM depacker with the XM loader.\n\t- Change several depackers to work without using a temporary file.\n\t- Replace inflate code with permissive licensed miniz.\n\t- Update the bzip2 depacker code to a newer 0BSD-licensed version.\n\t- Update to latest version of stb_vorbis depacker.\n\t- Several code and build system clean-ups.\n\tChanges by Clownacy:\n\t- Fixes and cleanups for C++ compatibility.\n\tChanges by Chris Young:\n\t- Relicense XFD decruncher under MIT.\n\tChanges by Denis Barkar:\n\t- Fix compilation for UWP platform.\n\n4.5.0 (20210606):\n\tChanges by Alice Rowan:\n\t- xmp_load_module_from_callbacks and xmp_test_module_from_callbacks\n\t  added to api\n\t- fix xmp_set_position et al. when used during loops, pattern delay\n\t- make xmp_set_position() consistently clear pattern break/jump vars\n\t- avoid shell command injection when calling external unmo3 or unrar\n\t- fix volume event handling for FAR modules\n\t- fix GDM loader to correctly handle empty notes\n\t- fix GDM fine effects\n\t- fix incorrect handling of GDM speed effect\n\t- implement GDM surround effect\n\t- add support for DSMI 0.8 and 0.9 AMF modules\n\t- fix incorrect DSMI AMF volume and note 0x7f handling\n\t- fix DSMI AMF track 0 remapping bug\n\t- fix DSMI AMF speed effect and pan command conversions\n\t- fix IMAGO Orpheus channel panning and status values\n\t- fix S3M ADPCM4 samples\n\t- fix OctaMED 'tracker compatibility' tempos, more accurate OctaMED\n\t  8-channel mode BPM tables.\n\t- ignore MED volume/slide effects with param of 0, fix speed bound.\n\t- improve MOD loader checks for Mod's Grave WOW files\n\t- fix Schism Tracker version date calculation\n\t- fix MED2 BPM handling\n\t- fix MED modules with pattern lengths > 256\n\t- fix MED instrument corruption\n\t- allow up to 512 rows in X-Tracker patterns\n\t- add support for DigiBooster Pro pan envelopes\n\t- fix DigiBooster Pro volume envelope number of points\n\t- fix fine effects for DigiBooster Pro modules\n\t- fix loading DigiBooster Pro modules with large sample chunks\n\t- fix IT bug where Cxx on same row as SBx would not be ignored\n\t- fix IT bug where Qxy would ignore the volume parameter\n\t- fix IT sample global volume and sample vibrato\n\t- fix two IT bugs related to note off and volume handling\n\t- fix event out-of-bounds reads due to invalid key values\n\t- fix multiple out-of-bounds reads/writes, memory corruptions,\n\t  uninitialized reads and hangs in several loaders (thanks to\n\t  Lionel Debroux for providing fuzz files)\n\t- fix xmp_release_module double frees when invoked multiple times\n\t- check external sample file names before opening them\n\t- make it possible to disable module depacker functionality\n\t- make it possible to disable prowizard module loaders\n\tChanges by viiri:\n\t- fix samples corruption in STM modules\n\t- support more versions of STM modules\n\tChanges by Özkan Sezer:\n\t- add new xmp_syserrno call to the api\n\t- xmp_get_format_list() now returns const char* const*, not char**\n\t  (no ABI change)\n\t- xmp_test_module, xmp_load_module, xmp_set_instrument_path and\n\t  xmp_smix_load_sample() now accept const char* path parameters\n\t  (no ABI change)\n\t- xmp_load_module_from_memory() now accepts a const void* memory\n\t  param (no ABI change)\n\t- xmp_load_module_from_memory() no longer accepts sizes <= 0.\n\t- explicitly document that callers of xmp_load_module_from_file()\n\t  are responsible for closing their own file.\n\t- remove nonportable use of fdopen in xmp_load_module_from_file()\n\t- fix a seek issue with xmp_load_module_from_memory\n\t- fix memory-io functions' error handling\n\t- fix number of envelope points sanity check in IMF loader\n\t- rewrite the UMX loader\n\t- revise sanity checks to prevent oob reads in s404 depacker\n\t- fix vorbis depacker to function properly on big endian systems\n\t- fix windows static library builds\n\t- fix win64 compatibility in ptpopen\n\t- fix build with C89 compilers\n\t- fix issues related to visibility attributes\n\t- fix compatibility with old gcc, mingw, djgpp\n\t- fix warnings in configure script\n\t- fix Watcom C build on OS/2\n\t- fix Amiga build\n\t- several code clean-ups\n\tChanges by Carsten Teibes:\n\t- fix lite build mod loader symbols\n\tChanges by NoSuck:\n\t- add new xmp_set_row() call to skip replay to the given row\n\t- IT: T00 now repeats previous slide\n\t- prevent clobbering of muted channels' volumes in IT modules\n\t- clamp number of IT envelope nodes at load time\n\t- fix IT message (comment) length miscalculation\n\t- fix IT volume panning effect\n\t- fix mute status on player creation\n\tChanges by Cameron Cawley:\n\t- fix sanity check in Digital Symphony loader\n\t- fix and enable the Coconizer loader\n\t- support compiling for Windows with OpenWatcom\n\tChanges by Ghabry:\n\t- add xmp_test_module_from_memory and xmp_test_module_from_file\n\t  calls to api\n\tFix problems reported by Ralf Hoffmann:\n\t- fix MMD3 instrument type sanity check\n\t- fix strictness of MOD pattern data tester\n\t- fix loading of XMLiTE XM modules\n\t- fix loading of ST modules with invalid names\n\tFix problems reported by Lionel Debroux:\n\t- fix PTM loader issues\n\t- fix MED4 invalid sample load error\n\t- fix NNA and DCT/DCA issues\n\tFix problems reported by Dennis Mulleneers:\n\t- handle XM 16-bit samples with odd in-file data\n\tFix problems reported by Jay Garcia:\n\t- fix smix sample allocation\n\t- force reset of buffer state on player start\n\tFix problems reported by Vitaly Novichkov:\n\t- fix Emscripten builds\n\t- fix linkage errors with MSVC debug builds\n\tOther changes:\n\t- fix IT pattern delay volume reset bug (read row events only\n\t  once per row)\n\t- fix volume, pitch and pan slides lagging behind one frame\n\t- fix tempo assignment in module scan to fix seek issues/crashes\n\t- fix double free in case of ADPCM sample load error\n\t- code refactoring and cleanup\n\t- add new xmp_set_tempo_factor() call to set the replay tempo\n\t  multiplier\n\t- fix XM keyoff with instrument\n\t- fix loading xm instruments with more than 16 samples\n\n4.4.1 (20161012):\n\tFix issues reported by Saga Musix:\n\t- fix MDL c5spd to preserve base periods\n\t- fix MDL sample decoder loop with corrupted data\n\t- fix MASI loader OPLH and PPAN subchunks parsing\n\tOther changes:\n\t- fix MacOS Tiger build issues (reported by Misty De Meo)\n\t- fix sample loop corner case (reported by knight-ryu12)\n\t- fix set pan effect in multichannel MODs (reported by Leilei)\n\t- fix global volume on module loop (reported by Travis Evans)\n\t- fix IT pan right value (by NoSuck)\n\t- fix MASI effects based on OpenMPT PSM loader\n\t- fix memory leak in XMs with 256 patterns\n\t- fix anticlick when rendering only one sample\n\t- fix anticlick in His Master's Noise instruments\n\t- fix anticlick in MED synth instruments\n\n4.4.0 (20160719):\n\tFix bugs caught in the OpenMPT test cases:\n\t- fix XM arpeggio in FastTracker 2 compatible mode\n\t- fix IT bidirectional loop sample length\n\t- fix MOD vibrato and tremolo in Protracker compatible mode\n\tFix multichannel MOD issues reported by Leilei:\n\t- fix XM replayer note delay and retrig quirk\n\t- fix XM replayer channel pan\n\t- fix MOD loader period to note conversion\n\tFix issues reported by Lionel Debroux:\n\t- fix virtual channel deallocation error handling\n\t- fix S3M global volume effect\n\t- fix IT envelope reset on tone portamento\n\t- fix IT voice leak caused by disabled envelope\n\t- fix IT volume column tone portamento\n\t- fix XM envelope position setting\n\t- fix FT2 arpeggio+portamento quirk with finetunes\n\t- fix mixer anticlick routines\n\t- accept S3M modules with invalid effects\n\tFix issues reported by Saga Musix:\n\t- fix 669 effects when no instrument number is specified\n\t- fix 669 effects to be frequency-based\n\t- fix 669 initial tempo\n\tOther changes:\n\t- fix S3M channel reset on sample end (reported by Alexander Null)\n\t- fix Noisetracker MOD speed setting (reported by Tero Auvinen)\n\t- fix IT loader DCA sanity check (reported by Paul Gomez Givera)\n\t- fix IT envelope reset after offset with portamento\n\t- fix bidirectional sample interpolation\n\t- fix mixer resampling and tuning issues\n\t- add Antti Lankila's Amiga 500 modeling mixer\n\t- add support to filter effect E0 in Amiga mods\n\t- add flags to configure player mode\n\t- add option to set the maximum number of virtual channels\n\t- add frequency-based \"period\" mode\n\t- add support to IT sample sustain loop\n\t- limit Oktalyzer modules to MOD note range\n\t- remove broken synth chip and Adlib emulation support\n\t- code refactoring and cleanup\n\n4.3.13 (20160417):\n\tFix bugs caught in the OpenMPT test cases:\n\t- fix IT volume column fine volume slide with row delay\n\tOther changes:\n\t- fix MOD vs XM set finetune effect\n\t- fix IT old instrument volume\n\t- fix IT panbrello speed\n\t- fix IT random pan variation left bias\n\t- fix IT default pan in sample mode (reported by Hai Shalom)\n\t- fix S3M set pan effect (reported by Hai Shalom and Johannes Schultz)\n\t- code refactoring and cleanup\n\n4.3.12 (20160305):\n\tFix bugs caught in the OpenMPT test cases:\n\t- fix IT note off with instrument\n\t- fix IT note recover after cut\n\t- fix IT instrument without note after note cut event\n\t- fix IT pan reset on new note instead of new instrument\n\t- fix IT volume swing problems\n\t- fix XM glissando effect\n\t- fix Scream Tracker 3 period limits\n\t- fix Scream Tracker 3 tremolo memory\n\tOther changes:\n\t- fix IT pattern break in hexadecimal (reported by StarFox008)\n\t- fix S3M subsong detection (reported by knight-ryu12)\n\t- fix S3M/IT special marker handling (reported by knight-ryu12)\n\t- fix Galaxy Music System 4.0 song length (reported by AntonZab)\n\t- fix tone portamento memory without note (reported by NoSuck)\n\t- fix IT pan swing limits\n\t- Add TrackerPacker v1 format converter\n\t- Add TrackerPacker v2 format converter\n\t- Add ProPacker 1.0 format converter\n\n4.3.11 (20160212):\n\tFix bugs caught in the OpenMPT test cases:\n\t- fix FT2 XM arpeggio clamp\n\t- fix FT2 XM arpeggio + pitch slide\n\t- fix XM tremor effect handling\n\t- fix XM tremor recover after volume setting\n\t- fix IT instrument after keyoff\n\t- fix S3M first frame test in pattern delay\n\t- fix Protracker tone portamento target setting\n\t- fix Protracker arpeggio wraparound\n\t- fix Protracker finetune setting\n\tOther changes:\n\t- fix range of MMD effect 9 (reported by Lamar McLouth)\n\t- fix Visual C++ build (reported by Jochen Goernitz)\n\t- fix invalid sample offset handling in Skale Tracker XM (reported by\n\t  Vladislav Suschikh)\n\t- fix Protracker sample loop to use full repeat only if start is 0\n\t- fix Scream Tracker 4-channel MOD fingerprinting\n\t- fix lite build with IT support disabled\n\t- fix build with gcc 2.95 in Haiku\n\n4.3.10 (20151231):\n\tFix bugs reported by Coverity Scan:\n\t- fix out of bounds access in IT/XM/MDL/IMF envelopes\n\t- fix out of bounds read in STX effect decoding\n\t- fix RTM maximum sample name length\n\t- fix AC1D converter number of patterns underflow\n\t- fix PRU2 usage of uninitialized data\n\t- fix Vorbis depacker usage of uninitialized data\n\t- fix negative array index read when setting position\n\t- fix resource leak in MFP loader\n\t- fix resource leak in Chiptracker loader\n\t- fix resource leak in Startrekker loader\n\t- fix resource leak in module load error handling\n\t- fix event decoding in LIQ loader\n\t- fix JVS command parameter in MED synth\n\t- fix 669 effect decoding\n\t- fix memory violation in LZX decompressor\n\t- fix sanity check in PTM orders loading\n\t- add sanity check to smix sample loading\n\t- add sanity check to PP21 format converter\n\t- add sanity check to P40 and P61A format converters\n\t- add sanity check to Zen Packer format converter\n\t- add sanity check to TP3 format converter\n\t- add error handling to many decompressors\n\t- add error handling to many I/O operations\n\t- remove dead code from NO loader\n\t- remove dead code from Soundtracker loader\n\t- remove dead code from GMC format converter\n\t- remove dead code from LZX decompressor\n\t- remove dead code in virtual channel manager reset\n\t- remove unnecessary seeks in format loaders\n\t- prevent division by zero in memory I/O\n\t- change IFF info ID from string to binary buffer\n\t- better IFF error handling\n\tFix problems caused by fuzz files (reported by Jonathan Neuschäfer):\n\t- add sanity checks to LHA depacker\n\t- add sanity checks to MED3 loader\n\t- add sanity checks to ABK loader\n\t- add sanity checks to Fuchs converter\n\t- add sanity checks to GMC converter\n\tOther changes:\n\t- fix IT envelope release + fadeout (reported by NoSuck)\n\t- fix SFX effects 5, 6, 7, and 8 (reported by Lamar McLouth)\n\t- fix pattern loading in Galaxy 4 and 5 (reported by AntonZab)\n\t- fix memory leak in LZW decompressor (by Chris Spiegel)\n\t- fix tone portamento target setting (reported by Georgy Lomsadze)\n\t- fix IT autovibrato depth (reported by Travis Evans)\n\t- disable ST3 sample size limit (reported by Jochen Goernitz)\n\t- fix crash in Prowizard error handling\n\t- fix IMS sample loop start\n\t- fix LIQ pan setting and surround channel\n\t- add sanity check for IFF chunk size\n\t- refactor ProRunner2 event decoding\n\n4.3.9 (20150623):\n\tFix bugs caught in the OpenMPT test cases:\n\t- fix IT tone portamento on sample change and NNA\n\t- fix IT tone portamento with offset\n\tFix problems caused by fuzz files (reported by Lionel Debroux):\n\t- add sanity check to RTM/MMD/MDL/DBM/SFX/MASI/DT loaders\n\t- add sanity check to Starpack/Fuzzac converter\n\t- add sanity check to Oxm/vorbis depacker\n\t- add sanity check to lha/MMCMP/s404 depacker\n\t- fix memory leak in vorbis decoder\n\tFix problems caused by fuzz files (reported by Jonathan Neuschäfer):\n\t- add sanity check to IT instrument name loader\n\t- add sanity check to IT loader instrument mapping\n\t- add sanity check to AMF module parameters and event loading\n\t- initialize IT loader last event data\n\tOther changes:\n\t- detect Amiga frequency limits in MOD (reported by Mirko Buffoni)\n\t- fix problems in Amiga split channels (reported by Gabriele Orioli)\n\t- fix global volume on restart to invalid row (reported by Adam Purkrt)\n\t- fix Oktalyzer note slide effect (by Dennis Lindroos)\n\t- fix Oktalyzer volume setting in split channels (by Dennis Lindroos)\n\t- fix external sample mixer for IT files (reported by honguito98)\n\t- allow short sample reads (reported by Adam Purkrt)\n\t- address problems reported by clang sanitizer\n\n4.3.8 (20150404):\n\tFix bugs caught in the OpenMPT test cases:\n\t- fix pre-increment of envelope indexes\n\t- fix IT note release at end of envelope sustain loop\n\t- reset channel flags in case of delay effect\n\tOther changes:\n\t- fix MMD3 16-bit samples (reported by jbb666)\n\t- refactor XM envelopes\n\t- refactor IT envelopes\n\n4.3.7 (20150329):\n\tFix bugs caught in the OpenMPT test cases:\n\t- fix IT sample mode note cut on invalid sample\n\t- fix IT sample mode note end detection\n\t- fix IT envelope handling with carry and fadeout\n\t- fix IT tone portamento with sample changes\n\t- fix IT initial global volume setting\n\t- fix IT keyoff with instrument in old effects mode\n\t- fix IT filter maximum values with resonance\n\tOther changes:\n\t- fix IT random volume variation\n\t- fix pattern initialization sanity check\n\t- fix ++ pattern handling in IT loader (reported by honguito98)\n\t- fix Soundtracker short rip loading (reported by Shlomi Fish)\n\t- add IT high offset command (SAx)\n\t- add IT surround command (S9x)\n\t- add IT surround channel support\n\t- add IT sample pan setting support\n\n4.3.6 (20150322):\n\tFix bugs caught in the OpenMPT test cases:\n\t- fix IT volume column volume slide effect memory\n\t- fix IT default filter cutoff on new note\n\t- fix IT filter envelope memory\n\tFix crashes with fuzzed files (reported by Lionel Debroux):\n\t- add sanity check to MED2/3/4 loader\n\t- add sanity check to STIM/GDM/DBM/LIQ/ICE/PSM/PTM/MGT loader\n\t- add sanity check to MDL/RAD/MGT/IMF/RTM/DT/LIQ/DTM pattern loader\n\t- add sanity check to OKT/IMF/MMD/MDL sample loader\n\t- add sanity check to Archimedes Tracker format test\n\t- add sanity check to Digital Symphony track loader\n\t- add sanity checks to SQSH, bzip2, arc, lha, lzx and S404 depackers\n\t- add sanity check for AMD/STX number of patterns\n\t- add sanity check for DSYM/MMD1/MMD3 number of channels\n\t- add sanity check for MMD1/MMD3 instrument type\n\t- add sanity check for IT old instrument loading\n\t- add sanity checks and fix memory leaks in the Vorbis decoder\n\tOther changes:\n\t- fix instrument number in channel initialization\n\t- fix sample size limit (reported by Jochen Goernitz)\n\t- fix loading of OpenMPT 1.17 IT modules (reported by Dane Bush)\n\t- fix sample number limit (reported by Lionel Debroux)\n\t- fix Oktalyzer split channel replay (reported by Dennis Lindroos)\n\t- fix Oktalyzer sample loop (by Dennis Lindroos)\n\t- fix Oktalyzer note slide up/down effect\n\t- fix ThePlayer pattern decoding\n\t- fix XM loading for MED2XM modules (reported by Lorence Lombardo)\n\t- add support to Amiga split channel loop and volume setting\n\t- add IT random volume variation\n\t- add IT random pan variation\n\n4.3.5 (20150207):\n\tFix crashes with fuzzed files (reported by Lionel Debroux):\n\t- add sanity check for ST3 S3M maximum sample size\n\t- add sanity check for sample loop start\n\t- add sanity check for speed 0\n\t- add sanity check for invalid XM effects\n\t- add sanity check for maximum number of channels\n\t- add sanity check for number of points in IT envelope\n\t- add sanity check for S3M file format information\n\t- add sanity check for maximum sample size\n\t- add sanity check for invalid envelope points\n\t- add sanity check for basic module parameters\n\t- add sanity check for instrument release after load error\n\t- add sanity check for XM header size\n\t- add sanity check for XM/IT/S3M/MTM/RTM parameters and sample size\n\t- add sanity checks to inflate and lha decompressors\n\t- add more tests to 669 and NO file detection\n\t- fix mixer index overflow with large samples\n\t- fix prowizard data request response\n\t- fix EU/NP1/NP2/NP3 prowizard depackers\n\t- fix crash on attempt to play invalid sample\n\t- fix infinite loop in break+delay quirk\n\t- reset module data before loading module\n\tOther changes:\n\t- fix loop processing error in scan (reported by Lionel Debroux)\n\t- fix minimum BPM value for MED (reported by cspiegel)\n\t- fix sample loop adjustment (by Emmanuel Julien)\n\n4.3.4 (20150111):\n\tFix bugs caught in the OpenMPT test cases:\n\t- fix XM keyoff+delay combinations\n\t- fix XM fine pitch slide with pattern delay\n\t- fix XM vibrato rampdown waveform\n\t- fix XM volume column pan with keyoff and delay\n\t- fix XM pan envelope position setting\n\t- fix channel volume and instrument initialization\n\t- fix end of module detection inside a loop\n\tFix bugs reported by Francisco Pareja-Lecaros:\n\t- fix MASI (PSM) volume command\n\t- fix MASI (PSM) note number parsing\n\t- fix Noisetracker note limit detection\n\tOther changes:\n\t- fix overflow in linear interpolator (reported by Jochen Goernitz)\n\t- fix MTM invalid track load (reported by Douglas Carmichael)\n\t- add ProPacker 3.0 loader\n\n4.3.3 (20141231):\n\tFix bugs caught in the OpenMPT test cases:\n\t- fix XM note delay volume with no note or instrument set\n\t- fix XM out-of-range note delays with pattern delays\n\tOther changes:\n\t- fix XM envelope loop length (reported by Per Törner)\n\t- fix big-endian detection in configuration (by Andreas Schwab)\n\n4.3.2 (20141130):\n\tFix bugs caught in the OpenMPT test cases:\n\t- fix IT invalid instrument number recovery\n\t- fix IT note retrig on portamento with same sample\n\t- fix XM portamento target reset on new instrument\n\t- fix XM portamento with offset\n\t- fix XM pan slide memory\n\t- fix XM tremolo and vibrato waveforms\n\t- fix MOD pattern break with pattern delay\n\t- fix MOD Protracker offset bug emulation\n\t- fix tremolo rate\n\tOther changes:\n\t- fix IT portamento after keyoff and note end\n\t- fix IT fadeout reset on new note\n\t- fix IT pattern row delay scan\n\t- fix MOD/XM volume up+down priority (reported by Jason Gibson)\n\t- fix MOD fine volume slide memory (reported by Dennis Lindroos)\n\t- fix set sample offset effect (by Dennis Lindroos)\n\t- fix Windows temp file (reported by Andreas Argirakis & Eric Lévesque)\n\t- add emulation of the FT2 pattern loop bug (by Eugene Toder)\n\t- allow loading of packed formats from memory\n\t- allow loading of OpenMPT MOD files with large samples\n\t- enable offset bug emulation by default for Protracker MODs\n\t- code cleanup\n\n4.3.1 (20141111):\n\tFix bugs caught in the OpenMPT test cases:\n\t- fix IT filter envelope range\n\t- fix IT envelope carry after envelope end\n\t- fix XM note off with volume command\n\t- fix XM K00 effect handling\n\t- fix XM portamento with volume column portamento\n\t- fix XM keyoff with instrument\n\t- fix XM note limits\n\tFix bugs reported by Andreas Argirakis:\n\t- fix MOD false positive for UNIC Tracker modules\n\t- fix EMOD instrument finetune \n\t- fix UNIC Tracker instrument finetune test\n\t- fix NoisePacker1 loader\n\tOther changes:\n\t- fix IT tone portamento in first note (reported by Jan Engelhardt)\n\t- fix XM invalid memory access in event reader\n\t- fix STM empty note event read\n\t- fix ABK loader test in Win32\n\t- fix MOD period range enforcing (reported by Jason Gibson)\n\t- fix ST2.6 speed effect (reported by Saga Musix)\n\t- fix corner case memory leak in S3M loader\n\t- fix retrig of single-shot samples after the end of the sample\n\t- fix crash in envelope reset with invalid instrument\n\t- fix module titles and instrument names in Mac OS X\n\t- fix row delay initialization on new module\n\t- refactor depacking code\n\t- code cleanup\n\n4.3.0 (20140926):\n\tFix bugs reported by Sami Jumppanen:\n\t- fix MED4 instrument numbering\n\t- fix MED effect FFF (turn note off)\n\t- fix MED synth finetune effect\n\tFix bugs reported by Alexander Null:\n\t- fix fine volume slide memory\n\t- fix IT portamento after note end in sample mode\n\t- fix S3M portamento after note end\n\tFix bugs caught in the OpenMPT test cases:\n\t- add XM and IT envelope loop and sustain point quirk\n\t- fix Amiga limits for notes with finetune\n\t- fix XM invalid offset handling\n\t- fix XM note release reset on new volume\n\t- fix XM pattern loader to honor header size\n\t- fix XM fine volume slide effect memory\n\t- fix XM fine pitch slide effect memory\n\t- fix XM finetune effect\n\t- fix IT portamento if offset effect is used\n\t- fix IT NNA on invalid sample mapping\n\t- fix IT filter envelope index reset\n\t- fix IT envelope carry on note cut events\n\t- fix IT envelope reset on new instrument\n\t- fix IT instrument change on portamento in compatible GXX mode\n\t- fix IT unmapped sample parsing\n\t- fix IT filter cutoff reset\n\tOther changes:\n\t- add API call to load a module from a file handle\n\t- add API call to set default pan separation value\n\t- add OpenMPT test cases to regression test suite\n\t- add AMOS Music Bank loader (by Stephen Leary)\n\t- refactor memory I/O calls\n\t- read OctaMED annotation and song info text\n\t- fix segfault in mixer caused by sample position overflow\n\t- fix MED synth pitch slide reset on new note\n\t- fix MED synth volume change during wait command\n\t- fix MED synth envelope loop handling (reported by Stefan Martens) \n\t- fix OctaMED SS default pitch transpose (reported by Karl Churchill)\n\t- fix OctaMED instrument name loading\n\t- fix XM, S3M, IT and MED offset effect handling\n\t- fix IT fadeout and envelope reset on new virtual channel\n\t- fix S3M shared effect parameter memory\n\t- fix S3M default pan positions\n\t- fix S3M set BPM effect with values < 32 (reported by Kyu S.)\n\t- fix incorrect Noisetracker effect filtering (reported by Kyu S.)\n\t- fix period limits for (possibly non-Amiga) Protracker clones\n\t- fix loop counter reset on play buffer reset\n\t- fix finetune effect\n\t\n4.2.8 (20140714):\n\tFix bugs reported by Sami Jumppanen:\n\t- fix OctaMED decimal volume decoding\n\t- fix MED4 sampled instrument octave range\n\t- fix mishandling of MED4 effect FFD\n\t- fix MED synth waveform command CHD\n\tOther changes:\n\t- fix sequence number reset on player start\n\t- fix stray notes in XM (reported by Andreas Argirakis)\n\t- limit note number to avoid crash (reported by Bastian Pflieger)\n\t- disable recursive file decompression\n\n4.2.7 (20140412):\n\t- add support for XM with ADPCM samples (reported by mk.bikash)\n\t- add OctaMED effect 2E (reported by Andreas Argirakis)\n\t- fix MMD2/3 note event mapping (reported by Andreas Argirakis)\n\t- fix XM set pan effect\n\t- fix IT disabled instrument pan\n\n4.2.6 (20140407):\n\tFix bugs reported by Andreas Argirakis:\n\t- add OctaMED 2 to 7 octave IFFOCT sample loader\n\t- fix volume in MED synth instruments\n\t- fix OctaMED V5 MMD2 sample transpose\n\tOther changes:\n\t- fix double free in module loaded from memory (by Arnaud Troël)\n\t- fix old Soundtracker sample loops (reported by Dennis Lindroos)\n\t- fix Win64 portability issues (reported by Özkan Sezer)\n\t- fix OctaMED 3 octave limit for sampled instruments\n\t- fix OctaMED hold/decay event support\n\t- fix OctaMED vibrato effect depth\n\t- fix IT tempo slide effect\n\t- fix Visual C++ nmake build issues\n\t- refactor OctaMED event reader\n\t- generate Android NDK static libraries\n\n4.2.5 (20140302):\n\t- fix Oktalyzer sample numbering (reported by Andreas Argirakis)\n\t- fix XM delay effect with invalid instrument\n\t- disable incomplete Graoumf Tracker loader\n\t- disable incomplete TCB Tracker loader\n\t- code refactor for core mod player library subset\n\t\n4.2.4 (20140222):\n\tFix bugs reported by Justin Crawford:\n\t- fix XM note and envelope retrig on delay effect\n\t- fix XM keyoff reset on new note event\n\t- fix retrig effect frame counter\n\t- fix envelope update after manually set point\n\tOther changes:\n\t- fix Chiptracker pattern decoding (reported by Andreas Argirakis)\n\t- fix AMF sample loop end\n\t- fix false positives in Slamtilt format test\n\t- refactor S3M arpeggio effect memory\n\t- disable incomplete DMF loader\n\t- disable incomplete DTT loader\n\t- address clang-analyzer warnings\n\n4.2.3 (20140118):\n\t- remove limit of samples in RTM loader\n\t- fix S3M length bug introduced in 4.2.1 (reported by Misty De Meo)\n\t- fix MDL effect decoding\n\t- fix MDL envelope decoding\n\t- fix MDL fadeout setting when envelopes are disabled\n\t- fix MDL instrument vibrato depth\n\t- fix MDL sample loop size\n\t- fix MDL fine volume slide effect\n\t- fix MacOS X dylib versioning\n\n4.2.2 (20140111):\n\t- re-enable Falcon MegaTracker loader\n\t- fix DIGI Booster finetune (reported by Andreas Argirakis)\n\t- fix tempo in BPM mode MMD modules (reported by Andreas Argirakis)\n\t- fix crash in zip depacker\n\t- fix MED4 large (>64KB) sample loading\n\t- fix MED4 sample loop flag setting\n\t- fix MMD Protracker-compatible volume slide effect\n\t- fix number of channels in GDM loader\n\t- fix number of channels in MED4 loader\n\t- fix instrument name setting in MDL loader\n\t- replace LZX decompressor code with LGPL version from XAD\n\n4.2.1 (20131229):\n\tMany fixes by Vitamin/CAIG:\n\t- fixes in memory I/O layer\n\t- improve loading of many module formats including XM and S3M\n\t- fix resource leak in case of invalid module structure\n\t- portability fixes\n\tOther changes:\n\t- disable YM2149 emulator\n\t- disable poorly implemented and rarely used module formats\n\t- fix mod loop setting in very small loops (reported by Misty De Meo)\n\t- fix linear period mode vibrato handling\n\t- refactor vibrato effect processing\n\t- code cleanup\n\n4.2.0 (20131109):\n\t- ignore invalid Noisetracker effects\n\t- add API call to load a module from a buffer in memory\n\t- add API call to read the player state (loaded, playing, etc)\n\t- add API call to set the player master volume\n\t- add API calls to reserve channels and play instruments on them\n\t- add loader for His Master's Noise modules\n\t- fix loop parameter in xmp_play_buffer()\n\t- fix MED synth volume slide reset on new note\n\t- fix instrument mapping in IT old instrument format\n\t- fix number of tracks in IT loader\n\t- fix LHA depacker header parsing\n\t- fix thread-unsafe Archimedes Tracker loader\n\t- fix thread-unsafe Digital Tracker loader\n\t- fix handling of loader errors\n\t- fix S3M 16-bit sample replay\n\t- refactor handling of format-specific instrument and channel data\n\t- refactor MED synth command interpreter\n\t- rewrite SQSH depacker code\n\t- disable rarely used ZOO depacker\n\t- disable rarely used ALM loader\n\t- code cleanup\n\n4.1.5 (20130527):\n\tFix bugs reported by Andreas Falkenhahn:\n\t- fix OctaMED decay event and effect decoding\n\t- fix The Player 6.0A pattern depacking\n\t- fix Oktalyzer instrument to sample mapping\n\n4.1.4 (20130519):\n\t- fix array initialization in IT loader (reported by Jacques Philippe)\n\t- remove regression tests from the distribution package\n\t- address license issues in  md5 digest code\n\t- address Visual C++ portability issues\n\t- code cleanup\n\n4.1.3 (20130511):\n\t- fix envelope reset on new instrument (reported by ArtRemix)\n\t- fix JMP END sequences in MED synth wave table\n\t- fix IT portamento after note cut\n\t- fix IT and XM envelope resets\n\t- refactor virtual channel code\n\t- code cleanup\n\n4.1.2 (20130504):\n\t- fix Graoumf Tracker arpeggio, set linear volume and set number\n\t  of frames effects (reported by Misty De Meo)\n\t- fix MTM sample fine tuning\n\t- fix unsigned conversion sample range when downmixing\n\t- fix memory leaks when attempting to load corrupted modules\n\t- refactor note slide effect code\n\n4.1.1 (20130428):\n\t- add XM set envelope position effect\n\t- fix XM note with no instrument after keyoff\n\t- fix detection of compiler flags\n\t- fix library symbol versioning in OS X (by Douglas Carmichael)\n\t- fix loss of precision in portamento (reported by Misty De Meo)\n\t- fix OS X, Solaris and BeOS/Haiku build issues\n\n4.1.0 (20130420):\n\t- add API call to fill equally-sized data chunks with PCM data\n\t- add configurable player parameter to disable sample loading\n\t- add configurable player parameter to set/get current module flags\n\t- changed maximum sampling rate to 49170 Hz\n\t- fix floating point values in lowpass filter\n\t- fix buffer overflow in MASI loader (reported by Douglas Carmichael)\n\t- fix simultaneous volume slide up and down\n\t- fix IT vs XM vibrato rate using quirk\n\t- fix IT portamento after note cut (reported by Benjamin Shadwick)\n\t- fix segfault in AMD module loader (reported by Jacques Philippe)\n\t- fix memory leak in AMD module loader\n\t- fix sequence scanner to prevent listing empty sequences\n\t- fix build issues in Cygwin (reported by Benjamin Shadwick)\n\t- fix pkg-config library definition\n\t- fix loop count reset when restarting module\n\t- fix MMD0-3 pitch slides (reported by Simon Spiers)\n\t- fix MED4 pattern reading (reported by Simon Spiers)\n\t- fix MED2/3/4 portamento effect\n\t- fix Stonecracker depacker\n\t- fix IT envelopes with no envelope points\n\t- fix XM invalid instrument event (reported by Banjamin Shadwick)\n\n4.0.4 (20130406):\n\t- fix IT volume column slide to note\n\t- fix IT pan setting effect\n\t- fix IT vibrato effect depth\n\t- fix IT portamento after fadeout\n\t- fix IT panbrello waveform setting\n\t- fix tremolo effect depth\n\t- fix random waveform generator\n\n4.0.3 (20130331):\n\t- add module quirks for well-known cases\n\t- add built-in zoo depacker\n\t- add IT pan slide effect \n\t- add IT panbrello effect\n\t- fix IT pan setting effect (reported by Jan Engelhardt)\n\t- fix IT fine vibrato effect\n\t- fix MED BPM mode tempo setting\n\t- fix global volume slides\n\t- fix bidirectional sample loops\n\t- fix sequence entry points\n\t- rescan sequences if timing flag is changed\n\n4.0.2 (20130223):\n\t- add IT volume column vibrato\n\t- add IT pattern row delay effect\n\t- add fine global volume slide effect\n\t- fix IT instrument vibrato depth and sweep\n\t- fix IT past note effects\n\t- fix IT fadeout values\n\t- fix IT fadeout event loading\n\t- fix period range for values lower than 8\n\t- fix global volume slides\n\t- fix channel volume setting\n\t- fix multi-retrig effect counter\n\t- fix invalid sample number access\n\t- fix memory access violation in MMCMP depacker\n\t- fix global volume setting in module scan\n\t- reset virtual channel flags on creation\n\t- change maximum number of mixer voices to 128\n\n4.0.1 (20130216):\n\t- fix license issues reported by Jan Engelhardt\n\t- minor documentation updates\n\n4.0.0 (20130213):\n\t- split library and application in different packages\n\t- remove OSS sequencer support\n\t- remove platform-specific device drivers\n\t- remove all global data, make library code fully thread-safe\n\t- remove configuration files (moved to front-end)\n\t- remove support to uLaw-encoded output\n\t- remove bogus lzma file detection (by Bodo Thiesen)\n\t- extend note range to full 10-octave range\n\t- extensive code refactoring\n\t- rewrite MMCMP decompressor to be endian-safe\n\t- replaced IT sample decompressor with public domain version\n\t- add cubic spline sample interpolation\n\t- add built-in zip file decompressor\n\t- add built-in gzip file decompressor\n\t- add built-in compress file decompressor\n\t- add built-in bzip2 file decompressor\n\t- add built-in xz file decompressor\n\t- add built-in lha file decompressor\n\t- add built-in vorbis sample decoder\n\t- add support to IT envelope carry\n\t- add support to IT sample vibrato\n\t- add ASYLUM Music Format V1.0 loader\n\t- add regression tests\n\t- fix interpolation and sample loop processing\n\t- fix S404 depacker integration\n\t- fix note delay effect\n\t- fix FT2 old instrument volume quirk\n\t- fix XM tone portamento with finetune (reported by Rakesh Sewgolam)\n\t- fix instrument envelope loops (Storlek test #24)\n\t- fix IT tremor effect (Storlek tests #12 and #13)\n\t- fix IT global volume (Storlek test #16)\n\t- fix IT stray tone portamento handling (Storlek test #23)\n\t- fix IT unified pitch slide memory (Storlek test #25)\n\t- fix IT retrigger effect (Storlek test #15)\n\t- fix IT filters\n\t- fix IT fadeout event handling\n\t- fix persistent slide down effect\n\n3.5.0 (20120127):\n\t- fix AMF 1.0 module loading (reported by Andre Timmermans), probe\n\t  for sample loop size\n\t- fix AMF 1.1+ sample loops when loop start is zero\n\t- fix AMF track index including track 0 as empty track (reported by\n\t  Andre Timmermans)\n\t- fix AMF tremolo effect (reported by Andre Timmermans)\n\t- fix AMF pitchbend effects (reported by Andre Timmermans)\n\t- fix AMF volume slide effect\n\t- fix AMF track allocation\n\t- fix OpenBSD driver configuration\n\t- fix pattern delay + pattern break command (reported by The Welder)\n\t- fix memory leaks found by cppcheck (reported by Paul Wise)\n\t- fix XM note cut on invalid instrument (reported by Benjamin Shadwick)\n\t- fix invalid memory access in case of mismatched track/pattern lengths\n\t- fix uninitialized values when loading BoobieSqueezer XM modules\n\t- fix subinstrument mapping for certain parameters\n\t- fix invalid memory access in The Player loader\n\t- fix plugin for Audacious 2.5.4\n\t- add support to DSMS mod files\n\t- add YM2149 emulator and improved chip sound support\n\t- add support to ZX Spectrum AY-3-8192 chiptunes\n\t- add ZX Spectrum Soundtracker module loader\n\n3.4.1 (20110813):\n\t- test for unused but set variable warning in gcc (needed to\n\t  build on MacOS X, reported by Misty De Meo)\n\t- fix format specifiers in CoreAudio driver messages (reported\n\t  by Misty De Meo)\n\t- build audacious3 driver if system has Audacious 2.5\n\t- change dependency generation flags for clang (reported by Misty\n\t  De Meo)\n\t- fix OXM module loading\n\n3.4.0 (20110808):\n\t- fix reported elapsed time with looped modules\n\t- fix portamento of mapped instruments (reported by Null Vista)\n\t- add MED2 (MED 1.12) module support\n\t- add Noiserunner module support\n\t- add support for MED4 synth instruments (reported by Tim Newsham)\n\t- fix MED4 Soundtracker-compatible tempo setting (Song2.med)\n\t- fix Audacious plugin crash if module is invalid (reported by\n\t  Dominik Mierzejewski)\n\t- fix Audacious plugin seek widget position setting\n\t- remove nonexistent Modplug Tracker IT quirk (reported by Johannes\n\t  Schultz, voice samples shouldn't play in Deep in Her Eyes remake)\n\t- fix Startrekker Packer loader\n\t- fix IT215 compressed sample loader (reported by Ben \"GreaseMonkey\"\n\t  Russell)\n\t- use start/stoptimer also for pause in OSS driver (by Test Rat)\n\t- identify modules created with munch.py in IT loader\n\t- OctaMED MMD0/1/2/3 tempo fixes (by Francis Russell)\n\t- MMD0/1 note limit fix (by Francis Russell)\n\t- improve latency in ALSA driver output\n\t- Audacious 2.4 API 17 plugin fixes\n\t- add Audacious 3.0 plugin (by Michael Schwendt)\n\n3.3.0 (20101202):\n\t- change MED BPM mode tempo setting (reported by Lorence Lombardo)\n\t- fix OSS driver fragment setting\n\t- add interactive loop toggle (requested by Emanuel Haupt)\n\t- add filter to prevent loading NoiseRunner modules as Protracker\n\t- add NoiseRunner loader (requested by Johan Samuelsson)\n\t- add improved Impulse Tracker fingerprinting (from Schism Tracker)\n\t- add Archimedes Tracker StasisMod effects support (Tom Hargreaves)\n\t- add tarball decompressor (Tom Hargreaves)\n\t- limit uncompression recursion (Tom Hargreaves)\n\t- fix Tracker Packer 3 loader (Tom Hargreaves)\n\t- fix load issue with BoobieSqueezer XMs (reported by Null Vista)\n\t- fix modinfo tempo/bpm setting\n\t- fix Zip file detection (Tom Hargreaves)\n\t- fix Archimedes Tracker effects (Tom Hargreaves)\n\t- update Audacious plugin to API 16\n\t- code cleanup\n\n3.2.0 (20100530):\n\t- Digital Symphony fixes by Tom Hargreaves\n\t- Archimedes Tracker fixes by Tom Hargreaves\n\t- add shared logarithmic volume table for Archimedes formats\n\t- fix default Archimedes formats pan (RLLR instead of LRRL)\n\t- add Coconizer file loader\n\t- portability fixes for BeOS and Haiku\n\t- code cleanup and optimizations\n\t- Android port using NDK\n\t- fix time echoback event for MED\n\t- fix module time count not resetting at new module\n\t- make zipfile detection stricter (by Solomon Peachy)\n\t- fix DSMI loader volume event (by Solomon Peachy)\n\t- initialize formats only once\n\t- fix build with Audacious plugin API 13\n\t- fix seek in Audacious plugin\n\n3.1.0 (20100107):\n\t- implement MED4 instrument transposition\n\t- fix build with MSVC++ 2008\n\t- fix bogus information in winamp plugin file info display\n\t- fix Audacious plugin dialog stacking order (by Michael Schwendt)\n\t- add Titanics Player prowizard loader\n\t- add SKYT Packer prowizard loader\n\t- add Novotrade Packer prowizard loader\n\t- add Hornet Packer prowizard loader\n\t- fix empty instruments in Digital Illusions loader\n\t- fix silent Liquid Tracker module bug\n\t- add Magnetic Fields Packer loader\n\t- add The Player 6.1a prowizard loader\n\t- add StoneCracker S404 decompressor (from amigadepacker)\n\t- add extra Funktracker file tests to prevent false positives\n\t- add Polly Tracker module loader\n\t- code cleanup and optimizations\n\n3.0.1 (20091221):\n\t- better handling of corrupted modules\n\t- load Real Tracker RTMM 1.12 modules (tested with odyssey.rtm)\n\t- fix tuning of Real Tracker modules\n\t- fix Real Tracker pattern decoding\n\t- fix segfault in modules with 0 orders or 0 channels\n\t- fix loading of MED4 module patterns with less than 32 lines\n\t- fix memory leak when loading corrupt MED4 files\n\n3.0.0 (20091210): 13 years after the 0.09b release\n\t- allow parallel build (R.I.P. 1996 buildsystem)\n\t- implement the long postponed open player loop \n\t- generate win32 project files when packaging distfile\n\t- remove callback driver\n\t- split unified flags/quirks into separate variables\n\t- add elapsed time echoback event\n\t- add option to display elapsed and remaining time\n\t- implement IT volume column fine effects quirk (Storlek test #6)\n\t- fix bmp plugin build\n\t- fix FreeBSD build (by swell k)\n\t- fix terminal handling in Cygwin (by daniel åkerud)\n\t- add OpenMPT id to S3M loader\n\t- add Epic MegaGames MUSE data decompression\n\t- add Galaxy Music System (Jazz Jackrabbit 2 J2B) module loader\n\t- fix parsing of driver-specific parameters\n\t- fix GDM length, number of patterns and number of samples\n\t- fix memory access error in MDL sample depacker\n\t- fix ProRunner1 samples size\n\t- OSS driver resets the DSP device on exit (by Andrew Church)\n\t- fix handling of PT portamento+vslide effect (by Andrew Church)\n\t- move driver init from player core to main application or plugin\n\t- Epic MegaGames MASI loader fixes\n\t- add Amiga TuneNet plugin (by Chris Young)\n\t- fix Module Protector loader\n\t- fix lha depacking in Amiga (reported by Chris Young)\n\t- fix clang build (by swell k)\n\t- add support for xz decompressor (by swell k)\n\t- add built-in LZX decompressor\n\t- remove pause-related functions from player core\n\t- fix build in Solaris 10 and Sun Studio 12 Update 1 C++ compiler\n\t  (reported by Douglas Carmichael)\n\t- fix plugin to work with Audacious 2.2 (reported by Götz Waschk)\n\t- fix invalid and uninitialized data accesses reported by Valgrind\n\t- fix memory leaks reported by Valgrind\n\n2.7.1 (20090718):\n\t- fix -l option in manpage (debian bug #442147)\n\t- fix endianism in MDL sample depacking (reported by Gürkan Sengün)\n\t- fix loading of MOD2XM 1.0 modules (reported by Gürkan Sengün)\n\t- add some sanity checks in XM module loading\n\t- fix IT note cut and delay (Storlek test #22)\n\t- increase period resolution for better tuning (reported by Mirko\n\t  Buffoni and Gürkan Sengün)\n\t- allow lower BPM settings (fixes Lemmings 2 circus music)\n\n2.7.0 (20090711):\n\t- add StarTrekker packer loader (untested, need samples)\n\t- extended key range to IT octave 9 (fixes beek-my_eleventh_year.it,\n\t  reported by Mirko Buffoni)\n\t- ignore tempo/bpm settings to 0 in module scan (fixes albacore.it,\n\t  reported by Storlek)\n\t- implement IT T0x and T1x tempo slides\n\t- process effects in IT muted channels (Storlek test #10)\n\t- generalized delayed event support (Storlek test #8)\n\t- emulate \"always store instrument\" IT bug (Storlek test #8)\n\t- add extra click removal step in mixer routines\n\t- fix loop size in GMC loader (reported by Mirko Buffoni)\n\t- GMC loader code cleanup\n\t- store in-file comments\n\t- apply amplification in the final downmix\n\t- set sample format to unsigned on 8-bit wav file output\n\t- attempt to handle BPM-based MED tempos a bit better\n\t- add option to use the IT LPF as a click/noise filter\n\t- deprecate $HOME/.xmprc, use $HOME/.xmp/xmp.conf instead\n\t- reintroduce modules.conf, move SYSCONFDIR back to /etc/xmp\n\t- display checksum for platforms where cksum(1) not readily available\n\t- add filter quirk for rn-alone.it\n\t- reintroduce manual setting for vblank timing in Amiga modules\n\t- add vblank quirk for mod.siedler ii (by Daniel Åkerud)\n\t- don't crash if SoundSmith instruments not found\n\n2.6.2 (20090630):\n\t- Promizer 1.8a loader code cleanup\n\t- fix portamento to skip first frame of each row\n\t- fix periods in instruments with finetune\n\n2.6.1 (20090627):\n\t- fix XMMS plugin build (reported by Götz Waschk)\n\t- add Chibi Tracker fingerprint to IT loader (info by Storlek)\n\t- add Schism Tracker fingerprint to S3M loader (info by Storlek)\n\t- fix Modplug Tracker/OpenMPT identification in IT loader\n\t- IT instrument and sample modes use same quirks (Storlek test #9)\n\t- transposed period scale base down one semitone (Storlek test #1)\n\t- remove previous portamento in SpaceDebris.mod fix\n\t- add unified pitch slide/portamento memory (Storlek test #3)\n\t- no Amiga limits for multichannel mods (fixes Bending CD61)\n\n2.6.0 (20090625):\n\t- cleanup: remove rarely used Unix IPC code that difficults porting\n\t- cleanup: remove per-module configuration that nobody uses\n\t- cleanup: moved Prowizard depacking to loader section\n\t- don't abort loading if IT sample magic not found (fixes loading\n\t  of use-brdg.it and use-funk.it, reported by Mirko Buffoni)\n\t- multichannel mods written with Scream Tracker don't use Amiga note\n\t  limits (fixes Earth Mountains, reported by Samuli Sorvakko)\n\t- fix start option in DeusEx's .umx files (by erlk ozlr)\n\t- add OpenBSD sndio driver (by Thomas Pfaff)\n\t- fix memory leak: free extra pattern allocated by the XM loader\n\t- fix memory leak: free temporary pointer arrays in the IT loader\n\t- fix memory leak: free temporary pointer arrays in the S3M loader\n\t- fix memory leak: free header and filename when file is invalid\n\t- fix memory leak: free temporary buffer in MDL loader\n\t- fix memory leak: move UNIC check to test section of mod loader\n\t- fix memory leak: free Digital Symphony extra empty track\n\t- fix memory leak: free Music Module Compressor buffers\n\t- fix memory access violation freeing list nodes using list_for_each\n\t- fix memory access violation in MDL track allocation\n\t- fix memory access violation in MDL sample decompression\n\t- fix memory access violation in LIQ pattern loading\n\t- fix memory access violation in P18A format test\n\t- fix free of unallocated block in IT sample-only mode\n\t- fix buffer overflow in OXM/DTT loaders (reported by Luigi Auriemma)\n\t- rename oss_mix driver to oss and alsa_mix to alsa\n\t- restrict MMD0/MMD1 non-synth instrument note range to 3 octaves\n\t  (reported by Daniel Åkerud and Mirko Buffoni)\n\t- assume wav driver if output filename ends in .wav\n\t- fix volume slides with 00 parameter (by Mirko Buffoni)\n\t- fix crash when S3M C2spd is zero (by Mirko Buffoni)\n\t- merged Mirko Buffoni's Windows Visual C++ port\n\t- don't process tone portamento in first frame of each row, fixes\n\t  Space Debris.mod (by Mirko Buffoni)\n\t- add amplification factor option (by Mirko Buffoni)\n\t- improved Winamp plugin (by Mirko Buffoni)\n\t- don't unlink open files (for Windows port, by Mirko Buffoni)\n\t- add experimental DxF/DFx handling with volume slides in all frames\n\t- add better Archimedes .arc compressed file test\n\t- reverted to older YM3812 emulator for license compliance\n\t- fix byte swap error in HSC to SBI Adlib OPL2 instrument conversion\n\t- fix Reality Adlib tracker loader\n\t- implement Adlib OPL2 synth volume setting\n\t- improve tempo, tuning and envelope of HSC modules\n\t- fix scanning of patterns containing short tracks\n\t- don't play notes outside the valid 8 octave note range\n\t- enable The Player 5.0A loader (tested with Full Moon mods)\n\t- enable ProPacker 2.1 loader (tested with Cool World mods)\n\t- fix endianism issues in The Player 5.0 and 6.0 loaders\n\t- fix AMF track remapping error\n\t- enable instrument retriggering quirk in IT loader\n\t- configuration file moved back to /etc\n\t- fix estimated tempo for S3M/IT modules with BPM changes\n\n2.5.1 (20071207): 11 years after xmp 0.09a, the first public release!\n\t- fix Winamp plugin default sampling rate (reported by Mirko Buffoni)\n\t- Winamp plugin number of channels fixed by Mirko Buffoni\n\t- recognize TakeTracker TDZ4 modules (reported by Lorence Lombardo)\n\t- fix crash in anticlick when pan amplitude is set to 100% (reported\n\t  by Mirko Buffoni)\n\t- extend playable octave range (fixes replay of octave 9 notes in\n\t  beek-my_eleventh_year.it, reported by Mirko Buffoni)\n\t- Protracker-style sample loops only valid with loop start 0 (fixes\n\t  M.K. Amegas conversion and others, reported by Mirko Buffoni)\n\t- reset fadeout on new instrument fetch (fixes echo in \"pain of lace\"\n\t  pat 0 ch 2-3, reported by Mirko Buffoni)\n\t- add quirk for simultaneous volume slide up and down (M.K. allows it\n\t  but S3M doesn't, fixes Red Dream.mod reported by Ralf Hoffmann)\n\t- Impulse Tracker in sample mode has instrument priority quirk\n\t- fix IT far right (64) stereo channel panning\n\t- merge Amiga port improvements by Johan Samuelsson\n\t- merge Amiga xfdmaster.library support by Chris Young \n\t- Amiga port also buildable for AROS (AHI driver not tested)\n\t- fix global track parsing in DMF loader (fixes mok-trea.dmf, reported\n\t  by Lorence Lombardo)\n\t- fix Winamp plugin to use the equalizer (reported by Mirko Buffoni)\n\t- skip 0xfe and 0xff S3M/IT control patterns at load time\n\t- fix scan of pattern break in the last pattern of the module\n\t- add BPM quirk for XMs converted with MED2XM (fixes Fascinated.xm,\n\t  reported by Lorence Lombardo)\n\t- merge Windows patch for decompression by Mirko Buffoni\n\n2.5.0 (20071127):\n\t- remove DMP-specific effect from MOD loader\n\t- extend Protracker sample loops to Noisetracker and Startrekker\n\t- FLT loader recognizes Startrekker FLTM modules (only PCM channels)\n\t- implement support for Startrekker/ADSC AM synth instruments\n\t- fixed cast to signed type in finetune display\n\t- fixed Protracker 3 IFFMODL loader (process VERS chunk manually)\n\t- added support to Protracker sample loops in the Protracker 3 loader\n\t- added PulseAudio driver (using the simple API)\n\t- remove restrictive tests for Soundtracker modules (fixes\n\t  99redballoons.mod and atmosfer4.mod, reported by Adric Riedel)\n\t- fixed infinite loop control (allows full replay time of 11:04 for\n\t  Gryzor's extended Global Trash 3.mod, reported by Adric Riedel)\n\t- use floating point period generation for the software mixer\n\t- fix S3M tempo/bpm setting effect (fixes seaside_hotel.s3m)\n\t- MinGW32 build fixes and new Windows driver (based on MikMod)\n\t- merged Amiga AHI driver written by Lorence Lombardo\n\t- don't read commands from terminal in Windows and Amiga\n\t- reset parameter in case of MDL \"no effect\" (saa.mdl pos 13 ch 9\n\t  plays correctly, reported by Gürkan Sengün)\n\t- fixed wav and file drivers binary file creation for win32\n\t- add support for Octamed V6 16bit samples (fixes instruments in\n\t  LaEsperanza.mmd3, reported by Lorence Lombardo)\n\t- enforce minimum allowed BPM to prevent large frames (fix crash with\n\t  MED2XM modules such as Fascinated.xm, reported by Lorence Lombardo)\n\t- fixed conversion of big-endian 16-bit samples in big-endian machines\n\t- fixed decompression of 16-bit IT samples in big-endian machines\n\t- added experimental Winamp plugin\n\t- added handler for Ultra Tracker sample type 20 (fixes seasons.ult,\n\t  reported by Lorence Lombardo)\n\t- fixed instrument parameter handling in MED4 loader\n\t- added Generic Digital Music (GDM) loader\n\t- plugin code cleanup, remove mode button and hold buffer\n\t- merged AmigaOS4 patches by Chris Young\n\n2.4.1 (20071029):\n\t- fixed portamento after keyoff problem in metamorph_part_ii.xm\n\t  where new note is not recognized (reported by Adric Riedel)\n\t- implement Protracker-style sample loops: first play entire sample,\n\t  then play the loop (needed to play MeNoWantMiseria.mod correctly,\n\t  reported by Adric Riedel)\n\t- fixed finetune test in UNIC Tracker detection to prevent false\n\t  positive with all that she wants.mod (reported by Adric Riedel)\n\t- fixed test for ?CHN and ??CH TakeTracker/FastTracker2 modules\n\t- fixed data type in the XM loader to work in 64-bit systems\n\t- don't ignore effect on event with invalid instrument (fixes tempo\n\t  in 39.mod pos 11, reported by Adric Riedel)\n\t- removed restrictive tests for Ultimate Soundtracker (false negative\n\t  in Karsten Obarski's sleepwalk and others, reported by Adric Riedel)\n\t- minimum sample size changed from 5 to 4 bytes, childhood.it actually\n\t  has 4 byte samples (reported by Adric Riedel)\n\t- cut effect doesn't retrigger sample (fixes Comic Bakery Remix pos 1\n\t  ch 3, reported by Adric Riedel)\n\t- allow period 162 in ST mods (for blueberry.mod UST, reported by AR)\n\t- fixed period interpolation using real log function instead of table\n\t  \n2.4.0 (20071025):\n\t- added Oktalyzer note slide and fine note slide effects\n\t- added Oktalyzer arpeggio 3, arpeggio 4 and arpeggio 5 effects\n\t- added MED synth programmable arpeggio commands ARP and ARE\n\t- added MED synth vibrato commands VBS, VBD and VWF\n\t- added module probe method without loading (Audacious plugin can\n\t  test for files while a module is playing)\n\t- added persistent effects for 669, FNK and FAR\n\t- fixed MED synth volume slide commands CHD and CHU\n\t- fixed detuning in short samples with bidirectional loop by adjusting\n\t  the loop size to match forward loop size\n\t- fixed sound cut bug when changing samples in the MED synth (don't\n\t  reset channel on attempt to set invalid sample position)\n\t- fixed identification of IIgs MegaTracker modules\n\t- fixed 669 persistent vibrato and portamento effects\n\t- fixed FAR persistent vibrato/portamento and pattern break effects\n\t- fixed sample loading in FAR modules\n\t- fixed multi-retrig effect processing (see cyberculosis.xm ch 7)\n\t- fixed segfault when output file is specified but driver isn't\n\t- fixed XM sample loop size in XMs made with Digitrakker\n\t- revert CoreAudio driver pause patch (fix memory management problem)\n\t- reset MED synth program at each new note event\n\t- removed filesize-based module format detection\n\t- replaced XANN loader with Prowizard XANN depacker\n\t- reorganized internal data to remove lots of global variables\n\t- changed all loaders to load module from relative offset\n\t- changed UMX depacker to be a real loader (using relative offsets)\n\t- ported Audacious plugin to the Audacious 1.4.0 API\n\t- fixed sample offset on portamento after keyoff (Decibeter - Cosmic\n\t  'Wegian Mamas.xm plays correctly now)\n\t- fixed length of XM loops (jt_xmas.xm no longer out of tune)\n\t- fixed Audacious plugin to display duration when adding to playlist\n\t- fixed memory access violations reported by Valgrind\n\t- split XMMS/BMP/Audacious plugin source\n\t- invalid patterns in sequence ignored instead of aborting replay\n\t- fixed load of DBM 16-bit samples (reported by Ralf Hoffmann)\n\t- fixed DBM envelope offset error (reported by Ralf Hoffmann)\n\t- disabled AMF volslide effect (problems with CannonFodder2-Done.AMF)\n\t- fixed MMD1/MMD3 loaders to skip invalid synth instruments (reported\n\t  by Ralf Hoffmann, Misanthropy.MED loads correctly)\n\t- fixed number of patterns in Funktracker modules\n\t- added Funktracker persistent portamento and volume slide effects\n\t- fixed offset effect with parameter 00 (reported by Adric Riedel)\n\t- changed volume dynamic range to fix steps in volume ramps (tested\n\t  with departure soundtrack.xm, reported by Adric Riedel)\n\t- set priority to slide down when volume slide up and down is used,\n\t  fixes Skaven's 2nd Reality blast (reported by Douglas Carmichael)\n\n2.3.2 (20071009):\n\t- added ModPlug Tracker IT quirk: ignore sample global volume (fixes\n\t  speech in \"Deep In Her Eyes Remake\", reported by Douglas Carmichael)\n\t- added PTM/IMF note slide effects and PTM note slide + retrig effect\n\t- added partial support to MED synth sounds (ported from xmp 2.1.0)\n\t- added experimental BeOS driver based on the CoreAudio driver\n\t- fixed copy of overlapping memory areas in IT loader\n\t- fixed initialization of channel flags before loading module\n\t- fixed PTM sample loop size (tested with abnormality.ptm)\n\t- fixed PTM effects translation (PTM-specific effects were ignored)\n\t- fixed effects settings in AIX and OSX CoreAudio drivers (reported\n\t  by Douglas Carmichael and Chris Cox)\n\t- fixed pause in OSX CoreAudio driver\n\t- fixed Fuchs Tracker prowizard loader format detection\n\t- fixed --time option time counter for MED files\n\t- decoupled PT3 PTDT and MOD loader\n\n2.3.1 (20071005):\n\t- added PTM global volume effect\n\t- fixed output filename setting in wav output\n\t- fixed size field setting in wav driver\n\t- fixed configure option --sysconfdir (reported by Douglas Carmichael)\n\t- fixed major bug in anticlick routine generating clicks in the\n\t  right audio channel (reported by Douglas Carmichael)\n\t- changed rampdown time in Hipolito's anticlick algorithm (removes\n\t  clicks from PM's 2nd Reality, reported by Douglas Carmichael)\n\t- changed default file name when writing to WAV to <modname>.wav\n\n2.3.0 (20071002):\n\t- added runtime endianism detection\n\t- added extractor for Epic Games' Unreal UMX files\n\t- added workaround for S3M \"Return of Litmus\" 0x87 quirk (reported\n\t  by Ralf Hoffmann)\n\t- added DigiBooster Pro module loader\n\t- added Fmod OXM depacker (depends on oggdec)\n\t- enabled Tracker Packer 3 prowizard loader\n\t- enabled The Player 4.x prowizard loader\n\t- removed reverse-endian sample reading options and XMP_CTL_BIGEND\n\t- fixed semantics of big/little endian options, moved to file driver\n\t- fixed memory corruption in Quadra Composer module loader\n\t- fixed Quadra Composer vibrato, offset and jump effects\n\t- fixed endianism problem in KSM and Zen Packer loaders\n\t- fixed transposition of Digital Tracker module notes\n\t- fixed build for QNX Neutrino 6.3.2\n\t- fixed OSS sequencer driver timing (reported by Reynir Stefansson)\n\t- fixed BMP/Audacious plugin to build also as XMMS plugin\n\t- fixed Impulse Tracker identification in S3M loader\n\t- fixed Module Protector test to recognize mods from \"Made In Croatia\"\n\t- fixed crash when scanning modules with length zero (bug #1800766)\n\t- fixed driver detection in NetBSD (don't try to build OSS driver)\n\t- fixed crash when restart value is invalid (reported by Ralf Hoffmann)\n\t- fixed handling of S3M pattern 0xfe (reported by Ralf Hoffmann)\n\t- fixed data size in MMD3 pattern sequence loading\n\t- fixed MMD1/MMD3 invalid/unhandled effect translation\n\t- fixed MMD1/MMD3 mixing buffer size setting (for PrivInv.med)\n\t- fixed Soundtracker 15-instrument module tracker fingerprinting\n\t- format management code cleanup\n\t- prowizard code cleanup\n\n2.2.1 (20070917):\n\t- added IT tracker fingerprinting\n\t- enabled track volumes (fixes znm-believe.it, reported by Jon Rafkind)\n\t- fixed DESTDIR and config file location (by Adam Sampson)\n\t- fixed volume overdrive in the Megatracker loader\n\t- fixed probing order of PW-packed and Arc\n\t- raised sample number limit from 255 to 1024 (fixes megaman.xm\n\t  tempo and missing instruments reported by Jon Rafkind)\n\t- build plugin files as PIC\n\n2.2.0 (20070915):\n\t- added more module format specs\n\t- added CD61 Octalyser module support\n\t- added Flextrax FLX module detection\n\t- added TCB Tracker module loader\n\t- added Digital Tracker DTM module loader\n\t- added Digital Tracker FA04/6/8 module support\n\t- added Real Tracker module loader\n\t- added X-Tracker module loader\n\t- added portable, 64bit-safe MMD0/1/2/3 MED loader\n\t- added Graoumf Tracker GTK module loader\n\t- added old Liquid Tracker \"NO\" module loader\n\t- added OSX CoreAudio driver\n\t- added S3M/PTM/IMF/LIQ/IT fine vibrato effect\n\t- added Archimedes Tracker loader\n\t- added Arc/!Spark depacker\n\t- added ArcFS depacker\n\t- added Archimedes VIDC sample converter\n\t- added Digital Symphony module loader\n\t- added Megatracker module loader\n\t- added Desktop Tracker module loader\n\t- added Zoo depacker\n\t- added MED3 module loader\n\t- added MED4 module loader\n\t- added IIgs ASIF sample converter\n\t- added IIgs SoundSmith/MegaTracker loader\n\t- added Audacious plugin\n\t- enabled WAV writer\n\t- enabled IMF filter effects\n\t- enabled Game Music Creator prowizard converter\n\t- removed broken shared lib generation\n\t- removed packed structures\n\t- replaced non-free PowerPack depacker with Kyzer's PD version\n\t- replaced list management in IFF loader with kernel list helpers\n\t- replaced XMMS plugin with Beep Media Player plugin\n\t- fixed long-standing bug in S3M BPM handling, \"Panic\" plays correctly\n\t- fixed MDL effects translation\n\t- fixed MDL pattern order loading missing first pattern\n\t- fixed MDL memory corruption in envelope initialization\n\t- fixed MDL 16-bit sample depacking (reported by Paul Wise)\n\t- fixed MDL multisampled instrument mapping\n\t- fixed MDL note event keyoff (gothlord.mdl plays better)\n\t- fixed XM and MDL sample loop size\n\t- fixed XM BPM setting (speedup.xm plays correctly)\n\t- fixed LIQ effects and 16-bit sample loading\n\t- fixed S3M pan settings\n\t- fixed IT old instrument volume mode setting\n\t- fixed IT 16-bit sample loading (reported by Henrik Pauli)\n\t- fixed IT effect S00 and delta sample loading (fixes O4UFRDMX.IT)\n\t- fixed multi-retrig effect (reported by Henrik Pauli)\n\t- fixed infinite loop scan (reported by Zbigniew Luszpinski)\n\t- fixed Sinaria sample size and finetune\n\t- fixed issues with OpenBSD\n\t- fixed issues with 64-bit machines\n\t- fixed loading of big-endian 16-bit samples\n\t- using Asle's Prowizard to handle packed MODs\n\n2.1.1 (unreleased):\n\t- added more module format specs\n\t- added MO3 unpacking support\n\t- added file detection to the XMMS plugin\n\t- added Beep Media Player support to the XMMS plugin\n\t- added Epic Megagames PSM module support\n\t- added Epic Megagames old PSM (Silverball) module support\n\t- added DSMI/DMP Advanced Module Format support\n\t- added support to Ultimate Soundtracker modules\n\t- added ALSA 0.9/1.0 sound output support\n\t- fixed recursive decrunching of module files\n\t- fixed QNX6 portability issues (by Mike Gorchak)\n\t- fixed heavy memory leak in the XMMS plugin\n\t- fixed --time command-line parameter\n\t- fixed portamento-after-keyoff bug (Jeronen Tel's \"Nine One One\"\n\t  now plays correctly)\n\t- fixed IFF file loading to avoid data alignment errors\n\t- fixed endianism issues in MDL loader\n\t- updated OPL emulation (by Mike Gorchak)\n\t- default verbosity level changed to 1\n\t- default sound mode set to stereo\n\t- disabled MED loader (nonportable, didn't work well)\n\n2.1.0 (unreleased):\n\t- Added Takuya Ooura's FFT code\n\t- Added scope/spectrum analyser modes to xxmp\n\t- Fixed dynamic driver loading to honour the configuration prefix\n\t- Added --with-esd option to the configuration script for esd in\n\t  FreeBSD (reported by Nate Dannenberg <natedac@kscable.com>)\n\t- Added xxmp panel and module info to XMMS info box\n\t- Fixed YM3812 emulator output in mono and stereo modes\n\t- Reordered extra libraries in Makefile.rules to build correctly in\n\t  IRIX 6.5.10/gcc 2.95.2 (reported by Johan Hattne <hattne@ibg.uu.se>)\n\t- Added aRts driver\n\t- Added NAS driver (based on Martin Denn's mpg123 NAS driver)\n\t- Added experimental QNX4 driver based on Mike Gorchak's nspmod port\n\t- Added experimental win32 driver based on Tony Million's mpg123 driver\n\t- Added NEO Software/Electronic Rats HSC module loader\n\t- Added Liquid Tracker module 0.0 and 1.0 support\n\t- Added callback driver for plugins\n\t- XMMS plugin changed to use the callback driver\n\t- Added Images Music System support\n\n2.0.4 (20010119):\n\t- Added driver for synthesized sounds\n\t- Added Tatsuyuki Satoh's YM3812 emulator\n\t- Added support to The Player 6.0a modules (using Sylvain \"Asle\"\n\t  Chipaux's P60A loader)\n\t- Added seek capability to XMMS plugin\n\t- Added (very) experimental AIX driver\n\t- Added envelope point sanity checks (fixed \"Beautiful Ones\" IT\n\t  envelope bug reported by Chris Cox)\n\t- Added support to dynamic linked drivers (for better packaging)\n\t- Added option to package only DFSG-compliant code\n\t- Fixed audioio.h detection in OpenBSD 2.8 (by Chris Cox\n\t  <cox.family@sk.sympatico.ca>)\n\t- Max. filter cutoff value changed from 254 to 253 to avoid problems\n\t  in \"Beautiful Ones\")\n\t- Fixed external drivers problem with the XMMS plugin (reported by\n\t  greg <gjones@computelnet.com>)\n\t- Fixed xmp_ord_set() bug (was calling XMP_ORD_PREV)\n\t- Fixed period calculation algorithm (that was an OLD bug!)\n\t- Started adding support to MED 1.11, 1.12, 2.00 and 3.22\n\t- Replaced RPM spec with Dominik Mierzejewski's version\n\n2.0.3 (20001229):\n\t- Fixes for enabling/disabling features in configure.in\n\t- gcc 2.96/glibc 2.2 related fixes by Dominik Mierzejewski\n\t  <dmierzej@elka.pw.edu.pl>\n\t- Support for RAR packed files by Michael Doering <mldoering@gmx.net>\n\t- Improved powerpacker decrunching by Michael Doering\n\t- IT lowpass filters for the software mixer\n\t- Fixed \"yes/no\" switch in xmp-modules.conf\n\t- XMMS plugin in big-endian machines fixed by Griff Miller II\n\t  <griff.miller@positron.com>\n\t- Updated RPM specfile\n\n2.0.2 (20000506):\n\t- Fixes in the NetBSD driver (by Michael <skumle@grin.dk>)\n\t- Fixed sample size for MED synth instruments\n\t- Fixed the set offset effect for (offset > sample length) bug\n\t  reported by Igor Krpanic <krpa@renata.irb.hr>\n\t- Fixed configuration file loading in OS/2 (by Kevin Langman\n\t  <langman@earthling.net>)\n\t- Fixed S3M tone portamento bug introduced in 2.0.1\n\t- Fixed option --fix-sample-loops\n\t- Improved Noisetracker and Octalyser module detection\n\t- Fixed UNIC tracker and Mod's Grave module detection\n\t- Fixed Protracker song detection\n\t- Event loading in S3M fixed by Rudolf Cejka\n\t  <cejkar@dcse.fee.vutbr.cz>\n\t- ALSA 0.5 driver fixed by Rob Adamson <R.Adamson@fitz.cam.ac.uk>\n\t- Added experimental XMMS plugin\n\t- Removed calls to tempnam(3)\n\t- Big-endian sound output finally fixed?\n\n2.0.1 (20000223):\n\t- Endianism problems in Linux/PPC (Amiga) fixed by Rune Elvemo\n\t  <relvemo@grm.hia.no>\n\t- Added enhanced NetBSD/OpenBSD drivers written by Michael\n\t  <skumle@grin.dk>\n\t- Fixed sample loop detection bug in the MOD loader\n\t- ALSA 0.5 support fixes by Tijs van Bakel <smoke@casema.net>\n\t- Moved the YM3128 emulator sources to the 2.1 branch (shouldn't\n\t  be in the 2.0.0 package)\n\t- Added extra sanity tests for 15 instrument MODs (based on sample\n\t  size/loop info), relaxed file size test, added check for NT mods\n\t- Fixed pathname for Protracker song sample loading\n\t- Fixed XM loader for nonstandard mods sent by Cyke O'Path\n\t  <cyker@heatwave.co.uk>\n\t- Added workaround for IT fine global volume slides\n\t- Added support for EXO4/EXO8 Startrekker/Audio Sculpture modules\n\t- Added support for Soundtracker 2.6/Ice Tracker modules\n\t- MED synth instruments MUCH better now (but still far from perfection)\n\t- Fixed S3M instrument retriggering on portamento bug reported by\n\t  Igor Krpanic <krpa@renata.irb.hr>\n\n2.0.0 (20000202):\n\t- Allocations checked with Electric Fence\n\t- Fixed powerpack decruncher counter initialization\n\t- Number of tracks fixed in the XM loader\n\t- 0 byte allocation fixed in the XM loader\n\t- Vibrato depth fixed (>>1)\n\t- Independent effect memory for XM volume slide effect and volume\n\t  column effect\n\t- Disable sample loop when loop end < loop start\n\t- Continue S3M fine effects (e.g. x00 after xF5)\n\t- Loader for Startrekker FLT8 modules\n\t- Pattern loop fixed\n\t- Set offset effect bug fixed (reported by Martin Willers\n\t  <M.Willers@tu-bs.de>)\n\t- Sample length in the software mixer\n\t- 669 effects fixed by Miod Vallat <miod@mikmod.darkorb.net>\n\t- Fixed S3M/IT continue arpeggio effect\n\t- Fixed S3M/IT set tempo effect\n\t- Fixed set finetune effect (<<4)\n\t- Fixed S3M and XM global volume settings\n\t- Fixed STX memory leaks\n\t- Added support for XM 1.03 modules in the XM loader\n\t- Speed 0x20 correctly recognized\n\t- STM loader accepts BMOD2STM stms (reported by Bernhard März)\n\t- Fixed wrong number of patterns in FAR loader (reported by Bernhard\n\t  März <maerz@rklnw1.ngate.uni-regensburg.de>)\n\t- Fixed IFF chunk buffer allocation for MDL samples\n\t- Fixed sample buffer size for MDL 16 bit samples\n\t- SMIX_C4NOTE changed to from 6947 to 6864 in mixer.h (reported by\n\t  Christoph Groth -- fixes Cannon Fodder replaying)\n\t- Ignore garbage in the order list (reported by Spirilis\n\t  <hannibal@bitsmart.com> -- fixes dragnet.mod)\n\t- Event fetch now emulates ST3, FT2 and Protracker\n\t- Added virtual channel system (for IT NNAs etc)\n\t- Added loaders for Protracker 3.59 IFFMODL, STMIK 0.2, Promizer 0.1/\n\t  2.0/4.0, SoundFX 1.3/2.0, Slamtilt, MED/OctaMED, DIGIBooster, Quadra\n\t  Composer, Digital Illusions, Module Protector, Zen Packer, Kefrens\n\t  Sound Machine, Heatseeker, Imago Orpheus and Impulse Tracker modules\n\t- Added support for MED synth sounds (incomplete)\n\t- Added support for MED BPM tempos (incomplete)\n\t- S3M loader recognizes Imago Orpheus\n\t- xmprc renamed to xmp.conf\n\t- Configuration for specific mods using xmp-modules.conf\n\t- User configuration stored in $HOME/.xmp\n\t- Protracker effect 9 bug emulation\n\t- Support for Protracker song files\n\t- AWE support for IT filter envelopes\n\t- Filename in the xxmp window title (added by Geoff Reedy\n\t  <vader21@imsa.edu>)\n\t- Sample crunching for soundcards with limited memory\n\t  (requested by janne <sniff@utanet.fi>)\n\t- Bidirectional loop expansion and 16-bit conversion for AWE\n\t- Added anti-click routines in the mixer (requested by Teemu Kiviniemi\n\t  <teemuki@kolumbus.fi>)\n\t- Zirconia's MMCMP decrunching support\n\t- Old volume mode set for awedrv 0.4.3\n\t- Added option --loadonly\n\t- Changed finalvol formula\n\t- MOD loader split in M.K./xCHN, FLT and ST loaders\n\t- xmp_options changed to xmp_control\n\t- Removed redundant code from loaders\n\t- Dropped options -p (period mode), --disable-envelopes, --modrange\n\t  and --ntsc\n\t- UNIC and LAX collapsed in a single loader\n\t- Added test for AWE_MD_NEW_VOLUME_CALC definition in oss_seq.c\n\t- Fixed buffer write() after EINTR on SIGSTOP (reported by Ruda Moura\n\t  <ruda@helllabs.org>)\n\t- Title line in xxmp fixed by Geoff Reedy <vader21@imsa.edu>\n\t- Tweak configure.in to honour predefined CPPFLAGS in environment\n\t  since awe_voice.h moves around in FreeBSD. At the time it is in\n\t  /usr/src/sys/gnu/i386/isa/sound/ (by Bjoern Fisher\n\t  <bfischer@Techfak.Uni-Bielefeld.DE>)\n\t- Added missing #include \"config.h\" in main.c (by Bjoern Fisher\n\t  <bfischer@Techfak.Uni-Bielefeld.DE>)\n\t- Default mixing rate raised to 44.1 kHz\n\t- Fixed OSS sequencer timing in Linux/Alpha (by Nils Faerber\n\t  <nils@unix-ag.org>, reported by Andrew Hobgood <chaos@strange.net>\n\t  -- improved using Miodrag Vallat's HZ checking)\n\t- Added native ALSA PCM driver\n\t- Fixed xxmp title wrap\n\t- Fixed 4-bit ADPCM sample decompression\n\t- Solaris driver fixed by Keith Hargrove <Keith.Hargrove@Eng.Sun.COM>\n\t- IRIX driver fixed by Brian Downing <bdowning@wolfram.com>\n\t- Merged OS/2 DART port by Kevin Langman <langman@earthling.net>\n\t- Added BMOD2STM support in STX mods (reported by Miod Vallat)\n\n1.2.0 (Unreleased):\n\t- Added support for 16-bit samples in S3M (reported by Geoff\n\t  Reedy <vader21@imsa.edu> and Chris Jantzen <chris@maybe.net>)\n\t- Status display in main.c changed from curr_row/num_rows\n\t  to curr_row/max_rows.\n\t- esd driver fixed by Terry Glass <tglass@bigfoot.com>\n\t- (Yet another scanner bugfix) scanner ignores tempo 0\n\t- (Yet another scanner bugfix) estimated time limit extended\n\t  from 15 min. to approx. 4 hours (should be sufficient)\n\t- (Yet another scanner bugfix) scanner sets global volume\n\t- (Yet another scanner bugfix) S3M_END test fixed\n\t- Skip to previous module fixed\n\t- Loop start set in bytes in 15 instrument MOD files\n\t- Added return status for failure in decompression\n\t- Temporary file unlink after failed decompression\n\t- Fixed S_ISDIR using wrong argument\n\t- Fixed clear chunk ID buffer in the IFF loader\n\t- Fixed chunk ID test fixed in the IFF loader\n\t- Release the IFF loader linked list after loading\n\t- Init default options in load.c\n\t- Volume echo event normalized to 0x40\n\t- Fixed sample loop in UNIC/LAX modules\n\t- Fixed FAR number of patterns\n\t- Fixed FAR tempo effect\n\t- Fixed FAR effect parameter setting\n\t- STM loader now rejects STX files\n\t- Fixed XM note fadeout value\n\t- Option --fix-sample-loop sets sample loop start in bytes\n\t- Added support for NoisePacker 1/2/3, Digitrakker 0.0/1.0/1.1\n\t  and Promizer 1.0/1.8 module formats\n\t- SIGUSR1 and SIGUSR2 handlers for skipping to next/previous\n\t  module (requested by Geoff Reedy <vader21@imsa.edu>)\n\t- Recursive module unpacking\n\t- drv_solaris renamed to drv_bsd_sparc\n\t- Other cosmetic changes\n\n1.1.6 (19981019):\n\t- xxmp compilation in FreeBSD fixed by Adam Hodson\n\t  <A.Hodson-CSSE96@cs.bham.ac.uk>\n\t- Makefile fixed for bash 2 \n\t- S3M global volume setting removed (reported by John v/d Kamp\n\t  <blade_@dds.nl>)\n\t- S3M tempo/BPM effect fixed (reported by Joel Jordan\n\t  <scriber@usa.net>)\n\t- XM loader checks module version\n\t- XM loader fixed for DEC UNIX by Andrew Leahy\n\t  <alf@cit.nepean.uws.edu.au>\n\t- finalvol shifted right one bit to prevent volume overflow with\n\t  dh-pofot.xm (Party On Funk-o-tron)\n\t- File uncompression based on magic instead of file suffix\n\t- Loop detection and time estimation improved; --noback\n\t  option removed (reported by Scott Scriven <toykeeper@cheerful.com>)\n\t- Invalid values for module restart are ignored (reported by\n\t  John v/d Kamp <blade_@dds.nl>)\n\t- Don't play invalid samples and instruments\n\t- Fine effect processing changed to the Protracker standard\n\t  instead of FT2 (i.e. effects EB1-EE5 play fine vol slide five times)\n\t- OSS audio driver fragment setting fixed\n\t- Added test for file type before loading\n\t- MOD/XM tempo/BPM setting fixed (reported by Gabor Lenart\n\t  <lgb@hal2000.hal.vein.hu>)\n\t- XM loader limits number of samples (needed to play Jeronen Tel's\n\t  \"Pools of Poison\")\n\t- Invalid sample number in instrument map is set to 0xff and ignored\n\t  by the player (needed to play Jeronen Tel's \"Pools of Poison\")\n\t- Jump to previous order in order zero ignored.\n\t- Channel 1 to 10 mute/unmute keys changed\n\t- cfg.mode -1 bias removed\n\t- --ignoreff option removed\n\t- Reserved & unused fields removed from structures\n\t- S3M tremor effect implemented\n\t- XM keyoff effect implemented\n\t- Experimental (untested) SGI driver\n\t- Experimental (untested) OpenBSD driver\n\t- --nocmd option added by Mark R. Boyns <boyns@sdsu.edu>\n\t- Added support for XM 1.02, Ultra Tracker, ProRunner, Propacker,\n\t  Tracker, Unic Tracker, Laxity, FC-M, XANN and AC1D modules\n\t- Added built-in uncompressors for Powerpacker and XPK-SQSH\n\t- Option for realtime priority in FreeBSD added by Douglas\n\t  Carmichael <dcarmich@mcs.com>\n\t- Support for 15 bpp in xxmp added by John v/d Kamp <blade_@dds.nl>\n\n1.1.5 (19980321):\n\t- Bidirectional sample loop fixed (reported by Andy Eltsov)\n\t- Set pan effect bug fixed by Frederic Bujon\n\t- Solaris/Sparclinux driver for the AMD 7930 audio chip (tested in\n\t  Solaris 2.5.1 and Linux 2.0.33)\n\t- Support for the Enlightened Sound Daemon\n\t- Better SIGSTOP/SIGCONT handling\n\n1.1.4.1 (19980330):\n\t- New URL updated in docs\n\n1.1.4 (19980204):\n\t- Added missing error check in Solaris and HP-UX drivers\n\t- Fixed includes for FreeBSD\n\t- Fixed X setup in the configure script\n\t- Fixed X include path in Makefile.rules and src/main/Makefile\n\t- scan.c replaced by a new version from 1.2.0 development tree\n\t- HP-UX driver works (tested in a 9000/710 with HP-UX 9.05)\n\t- Misc doc updates\n\n1.1.3 (19980128):\n\t- xxmp color #000000 changed to #020202 (needed in Solaris)\n\t- `cmd' type changed to char\n\t- Interactive commands to unmute channels 6, 7 and 8\n\t- MTM loader works in big-endian machines\n\t- Experimental HP-UX support added (not tested)\n\t- Panel background colors changed\n\t- New INSTALL file\n\t- Misc doc updates\n\n1.1.2 (19980105):\n\t- Fixed xxmp palette corruption\n\t- Fixed xxmp error messages\n\t- Misc doc updates\n\n1.1.1 (19980103):\n\t- Fixed coredump in Oktalyzer loader (resetting pattern and\n\t  sample counters)\n\t- Fixed coredump with Adlib instruments\n\t- Fixed xxmp window update (added missing XSync, xxmp shows\n\t  current pattern and row)\n\t- Fixed color palette in 16 bpp True Color\n\t- Fixed command line arguments -S and -M\n\n1.1.0 (19971224): \"The Nightmare Before Christmas\" release\n\t- Package license changed to GPL\n\t- Configuration made by GNU autoconf\n\t- Software mixer and /dev/dsp support\n\t- Compiles on FreeBSD 2.2 and Solaris 2.4\n\t- Command line options changed, long options added\n\t- Random play mode added\n\t- AWE reverb and chorus options added\n\t- Support for OPL2 FM synthesizer\n\t- New formats supported: Elyssis Adlib Tracker (AMD), Reality Adlib\n\t  Tracker (RAD), Aley's Modules (ALM)\n\t- Support for multiple output devices\n\t- Support for Scream Tracker 3.00 modules (volslides in every frame)\n\t- Support for S3M Adlib instruments\n\t- Support for S3M (very old) signed samples\n\t- Support for S3M pan (\"The Crossing\" plays correctly)\n\t- Support for S3M global volume\n\t- Support for Oktalyzer 7 bit samples\n\t- Support for IFF modules and variations\n\t- S3M arpeggio kludge removed\n\t- S3M module length adjusted discarding 0xff patterns\n\t- S3M set tempo/BPM effect adjusted\n\t- XM envelope loop bug fixed (\"Shooting Star\" plays correctly)\n\t- XM 16 bit sample conversion bug fixed (\"Hyperdrive\" plays correctly)\n\t- Support for XM instruments with 29 byte headers (for \"Braintomb\") \n\t- AWE32 pan setting fixed\n\t- Glissando in linear period mod bug fixed\n\t- Volume overflow bug fixed (again)\n\t- Tone portamento update bug fixed\n\t- Period setting workaround for panic.s3m\n\t- Pattern jump effect bug fixed\n\t- Oktalyzer loader bugs fixed\n\t- period_to_bend precision loss bug fixed\n\t- Option -s fixed to play with correct tempo/BPM/volume\n\t- Added support for bzip, compress, zip and lha compressed modules\n\t- Added Protracker and Soundtracker wrappers to the MOD loader\n\t- Support for MDZ modules with ADPCM samples\n\t- IPC stuff removed, player engine built as a library\n\t- Fixed memory leak in MOD loader\n\t- Fixed memory leak in oss_seq\n\t- X11 version (xxmp)\n\t- Interactive commands\n\t- xmprc file\n\n1.0.1 (19970419):\n\t- IPC global volume setting bug fixed\n\t- FAR number of patterns bug fixed\n\t- S3M volume setting effect correctly handled (fixes Skaven's\n\t  2nd Reality)\n\t- Option to disable dynamic panning to prevent AWE-32 clicking\n\n1.0.0 (19970330): First non-experimental release\n\t- Added option -t (maximum playing time)\n\t- Added option -K to enable IPC\n\t- Test module removed from package\n\n\nExperimental versions\n---------------------\n\n0.99c (19970320): Fixed more bugs reported by Michael Janson\n\t- S3M loader changed to recognize fine and extra fine volume slides\n\t  only when the slide nibble is not zero (fixes PM's 2nd Reality)\n\t- XM patterns with 0 (==0xff) rows are being correctly handled\n\t  (Wave's Home Vist should play better)\n\t- Tone portamento effect does not reset envelopes (fixes Wave's\n\t  Home Visit pattern 0, channels 0 to 5)\n\t- Loop click removal fixed & improved - chipsamples sound smoother\n\t  using gmod's method to prevent clicking\n\t- Continue vibrato effect bug fixed\n\n0.99b (19970318): Fixed bugs reported by Antti Huovilainen and Michael Janson\n\t- Extra fine portamento bug fixed (ascent.s3m should play better)\n\t- Volume column tone portamento in XM shifted left 4 bits (fixes\n\t  guitar in Zodiak's Status Mooh order 7, channel 7)\n\t- Note delay bug fixed (fixes bass in Jogeir Liljedahl's Guitar\n\t  Slinger) - delay was working as note retrig\n\t- Sample offset effect bug fixed (fixes snare drum in Zodiak's\n\t  Status Mooh order 0D channel 5) - offset 00 uses previous offset\n\t- New instrument event with same instrument does not retrig the\n\t  sample (fixes pad in Romeo Knight's Wir Happy Hippos)\n\t- Global volume limited to 0x40 (fixes fadeout in Zodiak's Reflecter)\n\t- Sample loop adjusted for click removal\n\t- 669 loader changed to use secondary effects for tempo/break\n\t- S3M loader changed to use generic pattern loops (S3M-specific\n\t  pattern loop kluge removed from xm_play.c)\n\t- MOD loader fixed - the module may have unused patterns stored\n\t  and this situation was confusing the loader\n\t- Effect F changed to recognize 32 frames per row\n\n0.99a (19970313):\n\t- General code review\n\t- Internal module format changed to XXM\n\t- Added endianism correction\n\t- Volume overdrive bug fixed\n\t- Verbosity levels adjusted\n\t- Vibrato implementation bug fixed\n\t- Instrument vibrato sweep implemented\n\t- New module formats supported: STM, 669, WOW, MTM, PTM, OKT, FAR\n\t- Added mute/solo channel command line options\n\t- Tempo 0 ignored\n\t- Lots of cosmetic changes\n\t- Option to reduce sample resolution to 8 bits\n\t- Envelope sustain bug (\"Zodiak bug\") fixed (reported by Beta)\n\t- Infinite loop in pattern jump bug fixed\n\n0.09e (19970105): Improved S3M support and general bugfixes\n\t- Yet another pattern loop bug fixed\n\t- S3M J00 (arpeggio) effect workaround\n\t- S3M stereo enable/disable implemented\n\t- S3M sample pan bug fixed\n\t- Added warning for S3M Adlib channels\n\t- Improved S3M channel pan handling\n\t- Incremental verbosity option\n\t- Tone portamento behaviour fixed (for \"Elimination Part I\")\n\t- Added parameter -i to ignore S3M end of module markers\n\t- S3M FFx/F00 (continue fine period slide) effect bug fixed\n\t  (bug was audible in the Second Reality opening theme)\n\t- Global volume slide bug fixed\n\t- installbin target fixed in the Makefile\n\t- Volume reset with no instrument for new note bug fixed\n\t  (bug was audible in \"Knulla Kuk\" by Moby)\n\n0.09d (19970101):\n\t- Pattern jump bug fixed\n\t- Added support for ??CH mods - thanks to Toru Egashira\n\t  <toru@jms.jeton.or.jp>\n\t- Fine pitchbending effect bug fixed\n\t- Signal handling fixed (again)\n\t- USR1 and USR2 signals changed to ABRT and HUP\n\t- Command line parameter to force MOD octave range\n\t- NTSC timing for MOD files\n\t- Glissando effect implemented\n\t- Retrig and multi-retrig effects bug fixed\n\t- S3M fine volume slide effect translation bug fixed\n\t- S3M C2SPD translation to relnote/finetune bug fixed\n\t- S3M pattern loop fixed\n\t- S3M module loop bug fixed\n\t- Pattern loop (for restart order>=0x7f) bug fixed\n\t- version.o dependencies fixed in the Makefile\n\n0.09c (19970101): broken version (unreleased)\n\n0.09b (19961210):\n\t- Note release and fadeout bug fixed\n\t- Module restart (SIGUSR2) bug fixed\n\t- Octave shift bug fixed (\"Move to da beat\" plays OK)\n\t- \"Squeak\" bug fixed (the bug was caused by a tone portamento\n\t  with no destination note)\n\t- Pitchbending effect bug fixed (\"Crystal Dragon\" plays OK)\n\n0.09a (19961207): First public release.\n\t- Panel signal handling fixed\n\t- base_note set with C4 frequency of 130.812 Hz (actually C3)\n\t- GUS_VOICE_POS enabled for AWE_DEVICE (Iwai's patch)\n\t- Envelope fadeout (release) fixed\n\t- Note skip bug corrected after some shotgun debugging\n\t- GUS panning fixed (bypassing sequencer.h)\n\t- Added panning amplitude command line option\n\t- Added a channel pan parameter\n\t- Changed the XM loader to always unpack the patterns\n\t- S3M pan positions fixed\n\t- Timing variables changed to floating point - I really don't like\n\t  FP, maybe I've been hacking in assembly language too much\n\t- Added 15-instrument MOD loader\n\t- Added XM finetune interpolation\n\t- Arpeggio bug fixed: pitchbend increments between semitones is 100\n\t  and not 128 (why don't they use ROUND numbers?)\n\t- Changed period2bend to prevent lossage in higher octaves\n\t- Pattern loop effect implemented (running_lamer.mod plays OK)\n\t- Auto-detector (?) for 15-instrument MODs (option -f removed)\n\t- Added linear period support\n\t- All source files checked into RCS\n\n\nDevelopment (unreleased) versions\n---------------------------------\n\n0.08 (19961031):\n\t- Increased code mess\n\t- Included Iwai's AWE support\n\t- devices.c created to wrap output devices\n\t- sequencer.c, awe.c and gus.c included in devices.c\n\t- Portability macros set in the Makefile (but not used)\n\t- Manpage draft included in the package\n\t- Added command-line device selector\n\t- Finally got rid of those ridiculous fread()s in xm_load.c\n\t- xm_instrument_header split into xm_instrument_header and\n\t  xm_instrument\n\t- Removed OSS macros from xm_play.c\n\t- Volume overflow bug fixed (\"Thematic Hymn\" plays OK)\n\t- Scream Tracker S3M loader\n\t- Fixed the song length bug\n\t- XM relnotes are working again!\n\t- Added a garbage character filter to the MOD loader\n\t- Floating point stuff removed\n\t- Sequencer sync message support added\n\t- Multiple file entry point bug fixed\n\t- Song loop bug fixed, added a loop-enable option\n\t- Tremolo and extra fine portamento effects fixed\n\t- Player doesn't try to play invalid instruments (and dump core)\n\t- SIGUSR1 and SIGUSR2 handlers added (abort/restart module)\n\t- MOD effects with parameter 0 filtered in the loader (nasty bug)\n\t- Finetunes partially fixed (\"Ooo-uh-uh-uh\" does not work)\n\t- Started X11 panel (VERY experimental)\n\t- Volume column effect fxp bug fixed\n\t- Envelope retrig on tone portamento bug fixed\n\t- MOD sample loop length fixed\n\t- Finetune in tone portamento bug fixed\n\t\n0.07 (19961011): We've screwed up XM relnotes in this version. Yuck!\n\t- Sample loop bug fixed\n\t- Extra fine portamento effect implemented\n\t- Global volume set/slide effects implemented\n\t- Pan slide effect implemented\n\t- Delay pattern effect implemented\n\t- Retriggered tremolo/vibrato implemented\n\t- Added tremolo/vibrato waveforms 4, 5 and 6 (no retrigger)\n\t- Stereo reverse/mono command line options are now functional\n\t- Pan slide effect implemented (but does it work?)\n\t- Arpeggio effect implemented\n\t- \"Official\" Amiga (exponential) periods implemented\n\t- Multi-retrig and delay effects implemented\n\t- Retrig and cut implemented as special cases of multi retrig\n\t- Fixed vibrato/tremolo waveforms\n\t- Added some macros to reduce the code mess\n\t- Finetunes/relnotes processed by the player (and not by the loader)\n\t\n0.06 (19960924): This version can play most MODs\n\t- Changed a lot of variable names\n\t- Fixed envelope processing\n\t- Fixed pitchbending (SEQ_BENDER vs SEQ_PITCHBEND) bug\n\t- Fixed panning (SEQ_CONTROL vs SEQ_PANNING) bug\n\t- Fixed multisample struct definition bug\n\t- Fixed note number \"obi-wan\" bug (\"Neverending Story\" plays OK)\n\t- Fixed tone portamento behavior (\"Art of Chrome\" plays OK)\n\t- Added MOD finetune support (\"Elimination Part I\" plays OK)\n\t- Added offset, cut, delay and retrig effects\n\n0.05 and before:\n\t- Lots of changes.\n"
  },
  {
    "path": "contrib/libxmp/include/xmp.h",
    "content": "#ifndef XMP_H\n#define XMP_H\n\n/**** Start MZX-specific hacks. *****/\n\n/* Suppress unwanted debug messages */\n#ifdef DEBUG\n#undef DEBUG\n#define MEGAZEUX_DEBUG\n#endif\n\n/* Force libxmp to build static */\n#ifndef LIBXMP_STATIC\n#define LIBXMP_STATIC\n#endif\n\n/* Force libxmp to not use versioned symbols on Linux */\n#ifdef XMP_SYM_VISIBILITY\n#undef XMP_SYM_VISIBILITY\n#endif\n\n/***** End MZX-specific hacks. *****/\n\n#if defined(EMSCRIPTEN)\n# include <emscripten.h>\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define XMP_VERSION \"4.6.3\"\n#define XMP_VERCODE 0x040603\n#define XMP_VER_MAJOR 4\n#define XMP_VER_MINOR 6\n#define XMP_VER_RELEASE 3\n\n#if defined(_WIN32) && !defined(__CYGWIN__)\n# if defined(LIBXMP_STATIC)\n#  define LIBXMP_EXPORT\n# elif defined(BUILDING_DLL)\n#  define LIBXMP_EXPORT __declspec(dllexport)\n# else\n#  define LIBXMP_EXPORT __declspec(dllimport)\n# endif\n#elif defined(__OS2__) && defined(__WATCOMC__)\n# if defined(LIBXMP_STATIC)\n#  define LIBXMP_EXPORT\n# elif defined(BUILDING_DLL)\n#  define LIBXMP_EXPORT __declspec(dllexport)\n# else\n#  define LIBXMP_EXPORT\n# endif\n#elif (defined(__GNUC__) || defined(__clang__) || defined(__HP_cc)) && defined(XMP_SYM_VISIBILITY)\n# if defined(LIBXMP_STATIC)\n#  define LIBXMP_EXPORT\n# else\n#  define LIBXMP_EXPORT __attribute__((visibility(\"default\")))\n# endif\n#elif defined(__SUNPRO_C) && defined(XMP_LDSCOPE_GLOBAL)\n# if defined(LIBXMP_STATIC)\n#  define LIBXMP_EXPORT\n# else\n#  define LIBXMP_EXPORT __global\n# endif\n#elif defined(EMSCRIPTEN)\n# define LIBXMP_EXPORT EMSCRIPTEN_KEEPALIVE\n# define LIBXMP_EXPORT_VAR\n#else\n# define LIBXMP_EXPORT\n#endif\n\n#if !defined(LIBXMP_EXPORT_VAR)\n# define LIBXMP_EXPORT_VAR LIBXMP_EXPORT\n#endif\n\n#define XMP_NAME_SIZE\t\t64\t/* Size of module name and type */\n\n#define XMP_KEY_OFF\t\t0x81\t/* Note number for key off event */\n#define XMP_KEY_CUT\t\t0x82\t/* Note number for key cut event */\n#define XMP_KEY_FADE\t\t0x83\t/* Note number for fade event */\n\n/* mixer parameter macros */\n\n/* sample format flags */\n#define XMP_FORMAT_8BIT\t\t(1 << 0) /* Mix to 8-bit instead of 16 */\n#define XMP_FORMAT_UNSIGNED\t(1 << 1) /* Mix to unsigned samples */\n#define XMP_FORMAT_MONO\t\t(1 << 2) /* Mix to mono instead of stereo */\n\n/* player parameters */\n#define XMP_PLAYER_AMP\t\t0\t/* Amplification factor */\n#define XMP_PLAYER_MIX\t\t1\t/* Stereo mixing */\n#define XMP_PLAYER_INTERP\t2\t/* Interpolation type */\n#define XMP_PLAYER_DSP\t\t3\t/* DSP effect flags */\n#define XMP_PLAYER_FLAGS\t4\t/* Player flags */\n#define XMP_PLAYER_CFLAGS\t5\t/* Player flags for current module */\n#define XMP_PLAYER_SMPCTL\t6\t/* Sample control flags */\n#define XMP_PLAYER_VOLUME\t7\t/* Player module volume */\n#define XMP_PLAYER_STATE\t8\t/* Internal player state (read only) */\n#define XMP_PLAYER_SMIX_VOLUME\t9\t/* SMIX volume */\n#define XMP_PLAYER_DEFPAN\t10\t/* Default pan setting */\n#define XMP_PLAYER_MODE \t11\t/* Player personality */\n#define XMP_PLAYER_MIXER_TYPE\t12\t/* Current mixer (read only) */\n#define XMP_PLAYER_VOICES\t13\t/* Maximum number of mixer voices */\n\n/* interpolation types */\n#define XMP_INTERP_NEAREST\t0\t/* Nearest neighbor */\n#define XMP_INTERP_LINEAR\t1\t/* Linear (default) */\n#define XMP_INTERP_SPLINE\t2\t/* Cubic spline */\n\n/* dsp effect types */\n#define XMP_DSP_LOWPASS\t\t(1 << 0) /* Lowpass filter effect */\n#define XMP_DSP_ALL\t\t(XMP_DSP_LOWPASS)\n\n/* player state */\n#define XMP_STATE_UNLOADED\t0\t/* Context created */\n#define XMP_STATE_LOADED\t1\t/* Module loaded */\n#define XMP_STATE_PLAYING\t2\t/* Module playing */\n\n/* player flags */\n#define XMP_FLAGS_VBLANK\t(1 << 0) /* Use vblank timing */\n#define XMP_FLAGS_FX9BUG\t(1 << 1) /* Emulate FX9 bug */\n#define XMP_FLAGS_FIXLOOP\t(1 << 2) /* Emulate sample loop bug */\n#define XMP_FLAGS_A500\t\t(1 << 3) /* Use Paula mixer in Amiga modules */\n\n/* player modes */\n#define XMP_MODE_AUTO\t\t0\t/* Autodetect mode (default) */\n#define XMP_MODE_MOD\t\t1\t/* Play as a generic MOD player */\n#define XMP_MODE_NOISETRACKER\t2\t/* Play using Noisetracker quirks */\n#define XMP_MODE_PROTRACKER\t3\t/* Play using Protracker quirks */\n#define XMP_MODE_S3M\t\t4\t/* Play as a generic S3M player */\n#define XMP_MODE_ST3\t\t5\t/* Play using ST3 bug emulation */\n#define XMP_MODE_ST3GUS\t\t6\t/* Play using ST3+GUS quirks */\n#define XMP_MODE_XM\t\t7\t/* Play as a generic XM player */\n#define XMP_MODE_FT2\t\t8\t/* Play using FT2 bug emulation */\n#define XMP_MODE_IT\t\t9\t/* Play using IT quirks */\n#define XMP_MODE_ITSMP\t\t10\t/* Play using IT sample mode quirks */\n\n/* mixer types */\n#define XMP_MIXER_STANDARD\t0\t/* Standard mixer */\n#define XMP_MIXER_A500\t\t1\t/* Amiga 500 */\n#define XMP_MIXER_A500F\t\t2\t/* Amiga 500 with led filter */\n\n/* sample flags */\n#define XMP_SMPCTL_SKIP\t\t(1 << 0) /* Don't load samples */\n\n/* limits */\n#define XMP_MAX_KEYS\t\t121\t/* Number of valid keys */\n#define XMP_MAX_ENV_POINTS\t32\t/* Max number of envelope points */\n#define XMP_MAX_MOD_LENGTH\t256\t/* Max number of patterns in module */\n#define XMP_MAX_CHANNELS\t64\t/* Max number of channels in module */\n#define XMP_MAX_SRATE\t\t49170\t/* max sampling rate (Hz) */\n#define XMP_MIN_SRATE\t\t4000\t/* min sampling rate (Hz) */\n#define XMP_MIN_BPM\t\t20\t/* min BPM */\n/* frame rate = (50 * bpm / 125) Hz */\n/* frame size = (sampling rate * channels * size) / frame rate */\n#define XMP_MAX_FRAMESIZE\t(5 * XMP_MAX_SRATE * 2 / XMP_MIN_BPM)\n\n/* error codes */\n#define XMP_END\t\t\t1\n#define XMP_ERROR_INTERNAL\t2\t/* Internal error */\n#define XMP_ERROR_FORMAT\t3\t/* Unsupported module format */\n#define XMP_ERROR_LOAD\t\t4\t/* Error loading file */\n#define XMP_ERROR_DEPACK\t5\t/* Error depacking file */\n#define XMP_ERROR_SYSTEM\t6\t/* System error */\n#define XMP_ERROR_INVALID\t7\t/* Invalid parameter */\n#define XMP_ERROR_STATE\t\t8\t/* Invalid player state */\n\nstruct xmp_channel {\n\tint pan;\t\t\t/* Channel pan (0x80 is center) */\n\tint vol;\t\t\t/* Channel volume */\n#define XMP_CHANNEL_SYNTH\t(1 << 0)  /* Channel is synthesized */\n#define XMP_CHANNEL_MUTE  \t(1 << 1)  /* Channel is muted */\n#define XMP_CHANNEL_SPLIT\t(1 << 2)  /* Split Amiga channel in bits 5-4 */\n#define XMP_CHANNEL_SURROUND\t(1 << 4)  /* Surround channel */\n\tint flg;\t\t\t/* Channel flags */\n};\n\nstruct xmp_pattern {\n\tint rows;\t\t\t/* Number of rows */\n\tint index[1];\t\t\t/* Track index */\n};\n\nstruct xmp_event {\n\tunsigned char note;\t\t/* Note number (0 means no note) */\n\tunsigned char ins;\t\t/* Patch number */\n\tunsigned char vol;\t\t/* Volume (0 to basevol) */\n\tunsigned char fxt;\t\t/* Effect type */\n\tunsigned char fxp;\t\t/* Effect parameter */\n\tunsigned char f2t;\t\t/* Secondary effect type */\n\tunsigned char f2p;\t\t/* Secondary effect parameter */\n\tunsigned char _flag;\t\t/* Internal (reserved) flags */\n};\n\nstruct xmp_track {\n\tint rows;\t\t\t/* Number of rows */\n\tstruct xmp_event event[1];\t/* Event data */\n};\n\nstruct xmp_envelope {\n#define XMP_ENVELOPE_ON\t\t(1 << 0)  /* Envelope is enabled */\n#define XMP_ENVELOPE_SUS\t(1 << 1)  /* Envelope has sustain point */\n#define XMP_ENVELOPE_LOOP\t(1 << 2)  /* Envelope has loop */\n#define XMP_ENVELOPE_FLT\t(1 << 3)  /* Envelope is used for filter */\n#define XMP_ENVELOPE_SLOOP\t(1 << 4)  /* Envelope has sustain loop */\n#define XMP_ENVELOPE_CARRY\t(1 << 5)  /* Don't reset envelope position */\n\tint flg;\t\t\t/* Flags */\n\tint npt;\t\t\t/* Number of envelope points */\n\tint scl;\t\t\t/* Envelope scaling */\n\tint sus;\t\t\t/* Sustain start point */\n\tint sue;\t\t\t/* Sustain end point */\n\tint lps;\t\t\t/* Loop start point */\n\tint lpe;\t\t\t/* Loop end point */\n\tshort data[XMP_MAX_ENV_POINTS * 2];\n};\n\nstruct xmp_subinstrument {\n\tint vol;\t\t\t/* Default volume */\n\tint gvl;\t\t\t/* Global volume */\n\tint pan;\t\t\t/* Pan */\n\tint xpo;\t\t\t/* Transpose */\n\tint fin;\t\t\t/* Finetune */\n\tint vwf;\t\t\t/* Vibrato waveform */\n\tint vde;\t\t\t/* Vibrato depth */\n\tint vra;\t\t\t/* Vibrato rate */\n\tint vsw;\t\t\t/* Vibrato sweep */\n\tint rvv;\t\t\t/* Random volume/pan variation (IT) */\n\tint sid;\t\t\t/* Sample number */\n#define XMP_INST_NNA_CUT\t0x00\n#define XMP_INST_NNA_CONT\t0x01\n#define XMP_INST_NNA_OFF\t0x02\n#define XMP_INST_NNA_FADE\t0x03\n\tint nna;\t\t\t/* New note action */\n#define XMP_INST_DCT_OFF\t0x00\n#define XMP_INST_DCT_NOTE\t0x01\n#define XMP_INST_DCT_SMP\t0x02\n#define XMP_INST_DCT_INST\t0x03\n\tint dct;\t\t\t/* Duplicate check type */\n#define XMP_INST_DCA_CUT\tXMP_INST_NNA_CUT\n#define XMP_INST_DCA_OFF\tXMP_INST_NNA_OFF\n#define XMP_INST_DCA_FADE\tXMP_INST_NNA_FADE\n\tint dca;\t\t\t/* Duplicate check action */\n\tint ifc;\t\t\t/* Initial filter cutoff */\n\tint ifr;\t\t\t/* Initial filter resonance */\n};\n\nstruct xmp_instrument {\n\tchar name[32];\t\t\t/* Instrument name */\n\tint vol;\t\t\t/* Instrument volume */\n\tint nsm;\t\t\t/* Number of samples */\n\tint rls;\t\t\t/* Release (fadeout) */\n\tstruct xmp_envelope aei;\t/* Amplitude envelope info */\n\tstruct xmp_envelope pei;\t/* Pan envelope info */\n\tstruct xmp_envelope fei;\t/* Frequency envelope info */\n\n\tstruct {\n\t\tunsigned char ins;\t/* Instrument number for each key */\n\t\tsigned char xpo;\t/* Instrument transpose for each key */\n\t} map[XMP_MAX_KEYS];\n\n\tstruct xmp_subinstrument\t*sub;\n\n\tvoid *extra;\t\t\t/* Extra fields */\n};\n\nstruct xmp_sample {\n\tchar name[32];\t\t\t/* Sample name */\n\tint len;\t\t\t/* Sample length */\n\tint lps;\t\t\t/* Loop start */\n\tint lpe;\t\t\t/* Loop end */\n#define XMP_SAMPLE_16BIT\t(1 << 0)  /* 16bit sample */\n#define XMP_SAMPLE_LOOP\t\t(1 << 1)  /* Sample is looped */\n#define XMP_SAMPLE_LOOP_BIDIR\t(1 << 2)  /* Bidirectional sample loop */\n#define XMP_SAMPLE_LOOP_REVERSE\t(1 << 3)  /* Backwards sample loop */\n#define XMP_SAMPLE_LOOP_FULL\t(1 << 4)  /* Play full sample before looping */\n#define XMP_SAMPLE_SLOOP\t(1 << 5)  /* Sample has sustain loop */\n#define XMP_SAMPLE_SLOOP_BIDIR\t(1 << 6)  /* Bidirectional sustain loop */\n#define XMP_SAMPLE_STEREO\t(1 << 7)  /* Interlaced stereo sample */\n#define XMP_SAMPLE_SYNTH\t(1 << 15) /* Data contains synth patch */\n\tint flg;\t\t\t/* Flags */\n\tunsigned char *data;\t\t/* Sample data */\n};\n\nstruct xmp_sequence {\n\tint entry_point;\n\tint duration;\n};\n\nstruct xmp_module {\n\tchar name[XMP_NAME_SIZE];\t/* Module title */\n\tchar type[XMP_NAME_SIZE];\t/* Module format */\n\tint pat;\t\t\t/* Number of patterns */\n\tint trk;\t\t\t/* Number of tracks */\n\tint chn;\t\t\t/* Tracks per pattern */\n\tint ins;\t\t\t/* Number of instruments */\n\tint smp;\t\t\t/* Number of samples */\n\tint spd;\t\t\t/* Initial speed */\n\tint bpm;\t\t\t/* Initial BPM */\n\tint len;\t\t\t/* Module length in patterns */\n\tint rst;\t\t\t/* Restart position */\n\tint gvl;\t\t\t/* Global volume */\n\n\tstruct xmp_pattern **xxp;\t/* Patterns */\n\tstruct xmp_track **xxt;\t\t/* Tracks */\n\tstruct xmp_instrument *xxi;\t/* Instruments */\n\tstruct xmp_sample *xxs;\t\t/* Samples */\n\tstruct xmp_channel xxc[XMP_MAX_CHANNELS]; /* Channel info */\n\tunsigned char xxo[XMP_MAX_MOD_LENGTH];\t/* Orders */\n};\n\nstruct xmp_test_info {\n\tchar name[XMP_NAME_SIZE];\t/* Module title */\n\tchar type[XMP_NAME_SIZE];\t/* Module format */\n};\n\nstruct xmp_module_info {\n\tunsigned char md5[16];\t\t/* MD5 message digest */\n\tint vol_base;\t\t\t/* Volume scale */\n\tstruct xmp_module *mod;\t\t/* Pointer to module data */\n\tchar *comment;\t\t\t/* Comment text, if any */\n\tint num_sequences;\t\t/* Number of valid sequences */\n\tstruct xmp_sequence *seq_data;\t/* Pointer to sequence data */\n};\n\nstruct xmp_channel_info {\n\tunsigned int period;\t\t/* Sample period (* 4096) */\n\tunsigned int position;\t\t/* Sample position */\n\tshort pitchbend;\t\t/* Linear bend from base note*/\n\tunsigned char note;\t\t/* Current base note number */\n\tunsigned char instrument;\t/* Current instrument number */\n\tunsigned char sample;\t\t/* Current sample number */\n\tunsigned char volume;\t\t/* Current volume */\n\tunsigned char pan;\t\t/* Current stereo pan */\n\tunsigned char reserved;\t\t/* Reserved */\n\tstruct xmp_event event;\t\t/* Current track event */\n};\n\nstruct xmp_frame_info {\t\t\t/* Current frame information */\n\tint pos;\t\t\t/* Current position */\n\tint pattern;\t\t\t/* Current pattern */\n\tint row;\t\t\t/* Current row in pattern */\n\tint num_rows;\t\t\t/* Number of rows in current pattern */\n\tint frame;\t\t\t/* Current frame */\n\tint speed;\t\t\t/* Current replay speed */\n\tint bpm;\t\t\t/* Current bpm */\n\tint time;\t\t\t/* Current module time in ms */\n\tint total_time;\t\t\t/* Estimated replay time in ms*/\n\tint frame_time;\t\t\t/* Frame replay time in us */\n\tvoid *buffer;\t\t\t/* Pointer to sound buffer */\n\tint buffer_size;\t\t/* Used buffer size */\n\tint total_size;\t\t\t/* Total buffer size */\n\tint volume;\t\t\t/* Current master volume */\n\tint loop_count;\t\t\t/* Loop counter */\n\tint virt_channels;\t\t/* Number of virtual channels */\n\tint virt_used;\t\t\t/* Used virtual channels */\n\tint sequence;\t\t\t/* Current sequence */\n\n\tstruct xmp_channel_info channel_info[XMP_MAX_CHANNELS];\t\t/* Current channel information */\n};\n\nstruct xmp_callbacks {\n\tunsigned long\t(*read_func)(void *dest, unsigned long len,\n\t\t\t\t     unsigned long nmemb, void *priv);\n\tint\t\t(*seek_func)(void *priv, long offset, int whence);\n\tlong\t\t(*tell_func)(void *priv);\n\tint\t\t(*close_func)(void *priv);\n};\n\ntypedef char *xmp_context;\n\nLIBXMP_EXPORT_VAR extern const char *xmp_version;\nLIBXMP_EXPORT_VAR extern const unsigned int xmp_vercode;\n\nLIBXMP_EXPORT int         xmp_syserrno        (void);\n\nLIBXMP_EXPORT xmp_context xmp_create_context  (void);\nLIBXMP_EXPORT void        xmp_free_context    (xmp_context);\n\nLIBXMP_EXPORT int         xmp_load_module     (xmp_context, const char *);\nLIBXMP_EXPORT int         xmp_load_module_from_memory (xmp_context, const void *, long);\nLIBXMP_EXPORT int         xmp_load_module_from_file (xmp_context, void *, long);\nLIBXMP_EXPORT int         xmp_load_module_from_callbacks (xmp_context, void *, struct xmp_callbacks);\n\nLIBXMP_EXPORT int         xmp_test_module     (const char *, struct xmp_test_info *);\nLIBXMP_EXPORT int         xmp_test_module_from_memory (const void *, long, struct xmp_test_info *);\nLIBXMP_EXPORT int         xmp_test_module_from_file (void *, struct xmp_test_info *);\nLIBXMP_EXPORT int         xmp_test_module_from_callbacks (void *, struct xmp_callbacks, struct xmp_test_info *);\n\nLIBXMP_EXPORT void        xmp_scan_module     (xmp_context);\nLIBXMP_EXPORT void        xmp_release_module  (xmp_context);\n\nLIBXMP_EXPORT int         xmp_start_player    (xmp_context, int, int);\nLIBXMP_EXPORT int         xmp_play_frame      (xmp_context);\nLIBXMP_EXPORT int         xmp_play_buffer     (xmp_context, void *, int, int);\nLIBXMP_EXPORT void        xmp_get_frame_info  (xmp_context, struct xmp_frame_info *);\nLIBXMP_EXPORT void        xmp_end_player      (xmp_context);\nLIBXMP_EXPORT void        xmp_inject_event    (xmp_context, int, struct xmp_event *);\nLIBXMP_EXPORT void        xmp_get_module_info (xmp_context, struct xmp_module_info *);\nLIBXMP_EXPORT const char *const *xmp_get_format_list (void);\nLIBXMP_EXPORT int         xmp_next_position   (xmp_context);\nLIBXMP_EXPORT int         xmp_prev_position   (xmp_context);\nLIBXMP_EXPORT int         xmp_set_position    (xmp_context, int);\nLIBXMP_EXPORT int         xmp_set_row         (xmp_context, int);\nLIBXMP_EXPORT int         xmp_set_tempo_factor(xmp_context, double);\nLIBXMP_EXPORT void        xmp_stop_module     (xmp_context);\nLIBXMP_EXPORT void        xmp_restart_module  (xmp_context);\nLIBXMP_EXPORT int         xmp_seek_time       (xmp_context, int);\nLIBXMP_EXPORT int         xmp_channel_mute    (xmp_context, int, int);\nLIBXMP_EXPORT int         xmp_channel_vol     (xmp_context, int, int);\nLIBXMP_EXPORT int         xmp_set_player      (xmp_context, int, int);\nLIBXMP_EXPORT int         xmp_get_player      (xmp_context, int);\nLIBXMP_EXPORT int         xmp_set_instrument_path (xmp_context, const char *);\n\n/* External sample mixer API */\nLIBXMP_EXPORT int         xmp_start_smix       (xmp_context, int, int);\nLIBXMP_EXPORT void        xmp_end_smix         (xmp_context);\nLIBXMP_EXPORT int         xmp_smix_play_instrument(xmp_context, int, int, int, int);\nLIBXMP_EXPORT int         xmp_smix_play_sample (xmp_context, int, int, int, int);\nLIBXMP_EXPORT int         xmp_smix_channel_pan (xmp_context, int, int);\nLIBXMP_EXPORT int         xmp_smix_load_sample (xmp_context, int, const char *);\nLIBXMP_EXPORT int         xmp_smix_release_sample (xmp_context, int);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\t/* XMP_H */\n"
  },
  {
    "path": "contrib/libxmp/mzx-gen.sh",
    "content": "#!/bin/sh\n\nMEGAZEUX_REPO_PATH=\"~/megazeux\" make -f Makefile.megazeux-gen all\n"
  },
  {
    "path": "contrib/libxmp/src/callbackio.h",
    "content": "#ifndef LIBXMP_CALLBACKIO_H\n#define LIBXMP_CALLBACKIO_H\n\n#include <stddef.h>\n#include \"common.h\"\n\ntypedef struct {\n\tvoid *priv;\n\tstruct xmp_callbacks callbacks;\n\tint eof;\n} CBFILE;\n\nLIBXMP_BEGIN_DECLS\n\nstatic inline uint8 cbread8(CBFILE *f, int *err)\n{\n\tuint8 x = 0xff;\n\tsize_t r = f->callbacks.read_func(&x, 1, 1, f->priv);\n\tf->eof = (r == 1) ? 0 : EOF;\n\n\tif (err) *err = f->eof;\n\n\treturn x;\n}\n\nstatic inline int8 cbread8s(CBFILE *f, int *err)\n{\n\treturn (int8)cbread8(f, err);\n}\n\nstatic inline uint16 cbread16l(CBFILE *f, int *err)\n{\n\tuint8 buf[2];\n\tuint16 x = 0xffff;\n\tsize_t r = f->callbacks.read_func(buf, 2, 1, f->priv);\n\tf->eof = (r == 1) ? 0 : EOF;\n\n\tif (r)   x = readmem16l(buf);\n\tif (err) *err = f->eof;\n\n\treturn x;\n}\n\nstatic inline uint16 cbread16b(CBFILE *f, int *err)\n{\n\tuint8 buf[2];\n\tuint16 x = 0xffff;\n\tsize_t r = f->callbacks.read_func(buf, 2, 1, f->priv);\n\tf->eof = (r == 1) ? 0 : EOF;\n\n\tif (r)   x = readmem16b(buf);\n\tif (err) *err = f->eof;\n\n\treturn x;\n}\n\nstatic inline uint32 cbread24l(CBFILE *f, int *err)\n{\n\tuint8 buf[3];\n\tuint32 x = 0xffffffff;\n\tsize_t r = f->callbacks.read_func(buf, 3, 1, f->priv);\n\tf->eof = (r == 1) ? 0 : EOF;\n\n\tif (r)   x = readmem24l(buf);\n\tif (err) *err = f->eof;\n\n\treturn x;\n}\n\nstatic inline uint32 cbread24b(CBFILE *f, int *err)\n{\n\tuint8 buf[3];\n\tuint32 x = 0xffffffff;\n\tsize_t r = f->callbacks.read_func(buf, 3, 1, f->priv);\n\tf->eof = (r == 1) ? 0 : EOF;\n\n\tif (r)   x = readmem24b(buf);\n\tif (err) *err = f->eof;\n\n\treturn x;\n}\n\nstatic inline uint32 cbread32l(CBFILE *f, int *err)\n{\n\tuint8 buf[4];\n\tuint32 x = 0xffffffff;\n\tsize_t r = f->callbacks.read_func(buf, 4, 1, f->priv);\n\tf->eof = (r == 1) ? 0 : EOF;\n\n\tif (r)   x = readmem32l(buf);\n\tif (err) *err = f->eof;\n\n\treturn x;\n}\n\nstatic inline uint32 cbread32b(CBFILE *f, int *err)\n{\n\tuint8 buf[4];\n\tuint32 x = 0xffffffff;\n\tsize_t r = f->callbacks.read_func(buf, 4, 1, f->priv);\n\tf->eof = (r == 1) ? 0 : EOF;\n\n\tif (r)   x = readmem32b(buf);\n\tif (err) *err = f->eof;\n\n\treturn x;\n}\n\nstatic inline size_t cbread(void *dest, size_t len, size_t nmemb, CBFILE *f)\n{\n\tsize_t r = f->callbacks.read_func(dest, (unsigned long)len,\n\t\t\t\t\t(unsigned long)nmemb, f->priv);\n\tf->eof = (r < nmemb) ? EOF : 0;\n\treturn r;\n}\n\nstatic inline int cbseek(CBFILE *f, long offset, int whence)\n{\n\tf->eof = 0;\n\treturn f->callbacks.seek_func(f->priv, offset, whence);\n}\n\nstatic inline long cbtell(CBFILE *f)\n{\n\treturn f->callbacks.tell_func(f->priv);\n}\n\nstatic inline int cbeof(CBFILE *f)\n{\n\treturn f->eof;\n}\n\nstatic inline long cbfilelength(CBFILE *f)\n{\n\tlong pos = f->callbacks.tell_func(f->priv);\n\tlong length;\n\tint r;\n\n\tif (pos < 0)\n\t\treturn EOF;\n\n\tr = f->callbacks.seek_func(f->priv, 0, SEEK_END);\n\tif (r < 0)\n\t\treturn EOF;\n\n\tlength = f->callbacks.tell_func(f->priv);\n\tr = f->callbacks.seek_func(f->priv, pos, SEEK_SET);\n\n\treturn length;\n}\n\nstatic inline CBFILE *cbopen(void *priv, struct xmp_callbacks callbacks)\n{\n\tCBFILE *f;\n\tif (priv == NULL || callbacks.read_func == NULL ||\n\t    callbacks.seek_func == NULL || callbacks.tell_func == NULL)\n\t\tgoto err;\n\n\tf = (CBFILE *)calloc(1, sizeof(CBFILE));\n\tif (f == NULL)\n\t\tgoto err;\n\n\tf->priv = priv;\n\tf->callbacks = callbacks;\n\tf->eof = 0;\n\treturn f;\n\n    err:\n\tif (priv && callbacks.close_func)\n\t\tcallbacks.close_func(priv);\n\n\treturn NULL;\n}\n\nstatic inline int cbclose(CBFILE *f)\n{\n\tint r = 0;\n\tif (f->callbacks.close_func != NULL)\n\t\tr = f->callbacks.close_func(f->priv);\n\n\tfree(f);\n\treturn r;\n}\n\nLIBXMP_END_DECLS\n\n#endif /* LIBXMP_CALLBACKIO_H */\n"
  },
  {
    "path": "contrib/libxmp/src/common.h",
    "content": "#ifndef LIBXMP_COMMON_H\n#define LIBXMP_COMMON_H\n\n/* band-aid for autotools: we aren't using autoheader.\n * See: https://github.com/libxmp/libxmp/issues/373 . */\n#ifdef AC_APPLE_UNIVERSAL_BUILD\n# #undef WORDS_BIGENDIAN\n# if defined __BIG_ENDIAN__\n#  define WORDS_BIGENDIAN 1\n# endif\n#endif\n\n#ifdef LIBXMP_CORE_PLAYER\n#ifndef LIBXMP_NO_PROWIZARD\n#define LIBXMP_NO_PROWIZARD\n#endif\n#ifndef LIBXMP_NO_DEPACKERS\n#define LIBXMP_NO_DEPACKERS\n#endif\n#else\n#undef LIBXMP_CORE_DISABLE_IT\n#endif\n\n#include <stdarg.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include \"xmp.h\"\n\n#undef  LIBXMP_EXPORT_VAR\n#if defined(EMSCRIPTEN)\n#include <emscripten.h>\n#define LIBXMP_EXPORT_VAR EMSCRIPTEN_KEEPALIVE\n#else\n#define LIBXMP_EXPORT_VAR\n#endif\n\n#ifndef __cplusplus\n#define LIBXMP_BEGIN_DECLS\n#define LIBXMP_END_DECLS\n#else\n#define LIBXMP_BEGIN_DECLS\textern \"C\" {\n#define LIBXMP_END_DECLS\t}\n#endif\n\n#if defined(_MSC_VER) && !defined(__cplusplus)\n#define inline __inline\n#endif\n\n#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\\\n    (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \\\n    (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus))\n#define LIBXMP_RESTRICT __restrict\n#else\n#define LIBXMP_RESTRICT\n#endif\n\n#if defined(_MSC_VER) ||  defined(__WATCOMC__) || defined(__EMX__)\n#define XMP_MAXPATH _MAX_PATH\n#elif defined(PATH_MAX)\n#define XMP_MAXPATH  PATH_MAX\n#else\n#define XMP_MAXPATH  1024\n#endif\n\n#if defined(__MORPHOS__) || defined(__AROS__) || defined(__AMIGA__) \\\n || defined(__amigaos__) || defined(__amigaos4__) || defined(AMIGA)\n#define LIBXMP_AMIGA\t1\n#endif\n\n#if defined(_MSC_VER) && defined(__has_include)\n#if __has_include(<winapifamily.h>)\n#define HAVE_WINAPIFAMILY_H 1\n#else\n#define HAVE_WINAPIFAMILY_H 0\n#endif\n#endif\n\n#if defined(HAVE_WINAPIFAMILY_H) && HAVE_WINAPIFAMILY_H\n#include <winapifamily.h>\n#define LIBXMP_UWP (!WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP))\n#else\n#define LIBXMP_UWP 0\n#endif\n\n#ifdef HAVE_EXTERNAL_VISIBILITY\n#define LIBXMP_EXPORT_VERSIONED __attribute__((visibility(\"default\"),externally_visible))\n#else\n#define LIBXMP_EXPORT_VERSIONED __attribute__((visibility(\"default\")))\n#endif\n#ifdef HAVE_ATTRIBUTE_SYMVER\n#define LIBXMP_ATTRIB_SYMVER(_sym) __attribute__((__symver__(_sym)))\n#else\n#define LIBXMP_ATTRIB_SYMVER(_sym)\n#endif\n\n/* AmigaOS fixes by Chris Young <cdyoung@ntlworld.com>, Nov 25, 2007\n */\n#if defined B_BEOS_VERSION\n#  include <SupportDefs.h>\n#elif defined __amigaos4__\n#  include <exec/types.h>\n#elif defined _arch_dreamcast /* KallistiOS */\n#  include <arch/types.h>\n#else\ntypedef signed char int8;\ntypedef signed short int int16;\ntypedef signed int int32;\ntypedef unsigned char uint8;\ntypedef unsigned short int uint16;\ntypedef unsigned int uint32;\n#ifdef _MSC_VER /* MSVC6 has no long long */\ntypedef signed __int64 int64;\ntypedef unsigned __int64 uint64;\n#elif defined(_LP64) || defined(__LP64__)\ntypedef unsigned long uint64;\ntypedef signed long int64;\n#else\ntypedef unsigned long long uint64;\ntypedef signed long long int64;\n#endif /* [u]int64 */\n#endif\n/* just in case :  */\ntypedef int tst_uint32[2 * (4 == sizeof(uint32)) - 1];\ntypedef int tst_uint64[2 * (8 == sizeof(uint64)) - 1];\n\n#ifdef _MSC_VER\n#pragma warning(disable:4100) /* unreferenced formal parameter */\n#pragma warning(disable:4389) /* signed/unsigned mismatch ( <, <=, >, >= ) */\n#pragma warning(disable:4018) /* signed/unsigned mismatch ( ==, != ) */\n#pragma warning(disable:4127) /* conditional expression is constant. */\n#pragma warning(disable:4761) /* integral size mismatch in argument; conversion supplied (for MSVC6 and older.) */\n#pragma warning(disable:4244) /* conversion from 'type' to 'int', possible loss of data */\n#pragma warning(disable:4267) /* conversion from 'size_t' to 'type', possible loss of data */\n#endif\n\n/* Constants */\n#define REAL_MAX_SRATE\t384000\t\t/* actual maximum sample rate */\n#define PAL_RATE\t250.0\t\t/* 1 / (50Hz * 80us)\t\t  */\n#define NTSC_RATE\t208.0\t\t/* 1 / (60Hz * 80us)\t\t  */\n#define C4_PAL_RATE\t8287\t\t/* 7093789.2 / period (C4) * 2\t  */\n#define C4_NTSC_RATE\t8363\t\t/* 7159090.5 / period (C4) * 2\t  */\n\n/* [Amiga] PAL color carrier frequency (PCCF) = 4.43361825 MHz */\n/* [Amiga] CPU clock = 1.6 * PCCF = 7.0937892 MHz */\n\n#define DEFAULT_AMPLIFY\t1\n#define DEFAULT_MIX\t100\n\n#define MSN(x)\t\t(((x)&0xf0)>>4)\n#define LSN(x)\t\t((x)&0x0f)\n#define SET_FLAG(a,b)\t((a)|=(b))\n#define RESET_FLAG(a,b)\t((a)&=~(b))\n#define TEST_FLAG(a,b)\t!!((a)&(b))\n\n/* libxmp_get_filetype() return values */\n#define XMP_FILETYPE_NONE\t\t0\n#define XMP_FILETYPE_DIR\t(1 << 0)\n#define XMP_FILETYPE_FILE\t(1 << 1)\n\n#define CLAMP(x,a,b) do { \\\n    if ((x) < (a)) (x) = (a); \\\n    else if ((x) > (b)) (x) = (b); \\\n} while (0)\n#define MIN(x,y) ((x) < (y) ? (x) : (y))\n#define MAX(x,y) ((x) > (y) ? (x) : (y))\n#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))\n\n#define TRACK_NUM(a,c)\tm->mod.xxp[a]->index[c]\n#define EVENT(a,c,r)\tm->mod.xxt[TRACK_NUM((a),(c))]->event[r]\n\n#ifdef _MSC_VER\n\n#define D_CRIT \"  Error: \"\n#define D_WARN \"Warning: \"\n#define D_INFO \"   Info: \"\n#ifdef DEBUG\n#define D_ libxmp_msvc_dbgprint  /* in win32.c */\nvoid libxmp_msvc_dbgprint(const char *text, ...);\n#else\n/* VS prior to VC7.1 does not support variadic macros.\n * VC8.0 does not optimize unused parameters passing. */\n#if _MSC_VER < 1400\nstatic void __inline D_(const char *text, ...) {\n\tdo { } while (0);\n}\n#else\n#define D_(...) do {} while (0)\n#endif\n#endif\n\n#elif defined __ANDROID__\n\n#ifdef DEBUG\n#include <android/log.h>\n#define D_CRIT \"  Error: \"\n#define D_WARN \"Warning: \"\n#define D_INFO \"   Info: \"\n#define D_(...) do { \\\n\t__android_log_print(ANDROID_LOG_DEBUG, \"libxmp\", __VA_ARGS__); \\\n\t} while (0)\n#else\n#define D_(...) do {} while (0)\n#endif\n\n#else\n\n#ifdef DEBUG\n#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L\n#define LIBXMP_FUNC __func__\n#else\n#define LIBXMP_FUNC __FUNCTION__\n#endif\n#define D_INFO \"\\x1b[33m\"\n#define D_CRIT \"\\x1b[31m\"\n#define D_WARN \"\\x1b[36m\"\n#if defined(__GNUC__) && (__GNUC__ < 3)\n#define D_(fmt, args...) do { \\\n\tprintf(\"\\x1b[33m%s \\x1b[37m[%s:%d] \" D_INFO, LIBXMP_FUNC, \\\n\t\t__FILE__, __LINE__); printf (fmt, ##args); printf (\"\\x1b[0m\\n\"); \\\n\t} while (0)\n#else /* assume C99 compatibility: */\n#define D_(...) do { \\\n\tprintf(\"\\x1b[33m%s \\x1b[37m[%s:%d] \" D_INFO, LIBXMP_FUNC, \\\n\t\t__FILE__, __LINE__); printf (__VA_ARGS__); printf (\"\\x1b[0m\\n\"); \\\n\t} while (0)\n#endif\n#else\n#if defined(__GNUC__) && (__GNUC__ < 3)\n#define D_(fmt, args...) \\\n\t\tdo {} while (0)\n#else\n/* assume C99 compatibility: */\n#define D_(...) do {} while (0)\n#endif\n#endif\n\n#endif\t/* !_MSC_VER */\n\n#if defined(_WIN32) || defined(__WATCOMC__) /* in win32.c */\n#define USE_LIBXMP_SNPRINTF\n/* MSVC 2015+ has C99 compliant snprintf and vsnprintf implementations.\n * If __USE_MINGW_ANSI_STDIO is defined for MinGW (which it is by default),\n * compliant implementations will be used instead of the broken MSVCRT\n * functions. Additionally, GCC may optimize some calls to those functions. */\n#if defined(_MSC_VER) && _MSC_VER >= 1900\n#undef USE_LIBXMP_SNPRINTF\n#endif\n#if defined(__MINGW32__) && !defined(__MINGW_FEATURES__)\n#define __MINGW_FEATURES__ 0 /* to avoid -Wundef from old mingw.org headers */\n#endif\n#if defined(__MINGW32__) && defined(__USE_MINGW_ANSI_STDIO) && (__USE_MINGW_ANSI_STDIO != 0)\n#undef USE_LIBXMP_SNPRINTF\n#endif\n#ifdef USE_LIBXMP_SNPRINTF\n#if defined(__GNUC__) || defined(__clang__)\n#define LIBXMP_ATTRIB_PRINTF(x,y) __attribute__((__format__(__printf__,x,y)))\n#else\n#define LIBXMP_ATTRIB_PRINTF(x,y)\n#endif\nint libxmp_vsnprintf(char *, size_t, const char *, va_list) LIBXMP_ATTRIB_PRINTF(3,0);\nint libxmp_snprintf (char *, size_t, const char *, ...) LIBXMP_ATTRIB_PRINTF(3,4);\n#define snprintf  libxmp_snprintf\n#define vsnprintf libxmp_vsnprintf\n#endif\n#endif\n\n/* Output file size limit for files unpacked from unarchivers into RAM. Most\n * general archive compression formats can't nicely bound the output size\n * from their input filesize, and a cap is needed for a few reasons:\n *\n * - Linux is too dumb for its own good and its malloc/realloc will return\n *   pointers to RAM that doesn't exist instead of NULL. When these are used,\n *   it will kill the application instead of allowing it to fail gracefully.\n * - libFuzzer and the clang sanitizers have malloc/realloc interceptors that\n *   terminate with an error instead of returning NULL.\n *\n * Depackers that have better ways of bounding the output size can ignore this.\n * This value is fairly arbitrary and can be changed if needed.\n */\n#define LIBXMP_DEPACK_LIMIT (512 << 20)\n\n/* Quirks */\n#define QUIRK_S3MLOOP\t(1 << 0)\t/* S3M loop mode */\n#define QUIRK_ENVFADE\t(1 << 1)\t/* Fade at end of envelope */\n#define QUIRK_PROTRACK\t(1 << 2)\t/* Use Protracker-specific quirks */\n#define QUIRK_RTONCE\t(1 << 3)\t/* Retrigger one time only */\n#define QUIRK_ST3BUGS\t(1 << 4)\t/* Scream Tracker 3 bug compatibility */\n#define QUIRK_FINEFX\t(1 << 5)\t/* Enable 0xf/0xe for fine effects */\n#define QUIRK_VSALL\t(1 << 6)\t/* Volume slides in all frames */\n#define QUIRK_PBALL\t(1 << 7)\t/* Pitch bending in all frames */\n#define QUIRK_PERPAT\t(1 << 8)\t/* Cancel persistent fx at pat start */\n#define QUIRK_VOLPDN\t(1 << 9)\t/* Set priority to volume slide down */\n#define QUIRK_UNISLD\t(1 << 10)\t/* Unified pitch slide/portamento */\n#define QUIRK_ITVPOR\t(1 << 11)\t/* Disable fine bends in IT vol fx */\n#define QUIRK_FTMOD\t(1 << 12)\t/* Flag for multichannel mods */\n#define QUIRK_INVLOOP\t(1 << 13)\t/* Enable invert loop */\n/*#define QUIRK_MODRNG\t(1 << 13)*/\t/* Limit periods to MOD range */\n#define QUIRK_INSVOL\t(1 << 14)\t/* Use instrument volume */\n#define QUIRK_VIRTUAL\t(1 << 15)\t/* Enable virtual channels */\n#define QUIRK_FILTER\t(1 << 16)\t/* Enable filter */\n#define QUIRK_IGSTPOR\t(1 << 17)\t/* Ignore stray tone portamento */\n#define QUIRK_KEYOFF\t(1 << 18)\t/* Keyoff doesn't reset fadeout */\n#define QUIRK_VIBHALF\t(1 << 19)\t/* Vibrato is half as deep */\n#define QUIRK_VIBALL\t(1 << 20)\t/* Vibrato in all frames */\n#define QUIRK_VIBINV\t(1 << 21)\t/* Vibrato has inverse waveform */\n#define QUIRK_PRENV\t(1 << 22)\t/* Portamento resets envelope & fade */\n#define QUIRK_ITOLDFX\t(1 << 23)\t/* IT old effects mode */\n#define QUIRK_S3MRTG\t(1 << 24)\t/* S3M-style retrig when count == 0 */\n#define QUIRK_RTDELAY\t(1 << 25)\t/* Delay effect retrigs instrument */\n#define QUIRK_FT2BUGS\t(1 << 26)\t/* FT2 bug compatibility */\n#define QUIRK_MARKER\t(1 << 27)\t/* Patterns 0xfe and 0xff reserved */\n#define QUIRK_NOBPM\t(1 << 28)\t/* Adjust speed only, no BPM */\n#define QUIRK_ARPMEM\t(1 << 29)\t/* Arpeggio has memory (S3M_ARPEGGIO) */\n#define QUIRK_RSTCHN\t(1 << 30)\t/* Reset channel on sample end */\n#define QUIRK_FT2ENV\t(1 << 31)\t/* Use FT2-style envelope handling */\n\n#define HAS_QUIRK(x)\t(m->quirk & (x))\n\n\n/* Format quirks */\n#define QUIRKS_ST3\t\t(QUIRK_S3MLOOP | QUIRK_VOLPDN | QUIRK_FINEFX | \\\n\t\t\t\t QUIRK_S3MRTG  | QUIRK_MARKER | QUIRK_RSTCHN )\n#define QUIRKS_FT2\t\t(QUIRK_RTDELAY | QUIRK_FINEFX )\n#define QUIRKS_IT\t\t(QUIRK_S3MLOOP | QUIRK_FINEFX | QUIRK_VIBALL | \\\n\t\t\t\t QUIRK_ENVFADE | QUIRK_ITVPOR | QUIRK_KEYOFF | \\\n\t\t\t\t QUIRK_VIRTUAL | QUIRK_FILTER | QUIRK_RSTCHN | \\\n\t\t\t\t QUIRK_IGSTPOR | QUIRK_S3MRTG | QUIRK_MARKER )\n\n/* Quirks specific to flow effects, especially Pattern Loop. */\n#define FLOW_LOOP_GLOBAL_TARGET\t(1 <<  0) /* Global target for all tracks */\n#define FLOW_LOOP_GLOBAL_COUNT\t(1 <<  1) /* Global count for all tracks */\n#define FLOW_LOOP_END_ADVANCES\t(1 <<  2) /* Loop end advances target (S3M) */\n#define FLOW_LOOP_END_CANCELS\t(1 <<  3) /* Loop end cancels prev jumps on row (LIQ) */\n#define FLOW_LOOP_PATTERN_RESET\t(1 <<  4) /* Target/count reset on pattern change */\n#define FLOW_LOOP_INIT_SAMEROW\t(1 <<  5) /* SBx sets target if it isn't set (ST 3.01) */\n#define FLOW_LOOP_FIRST_EFFECT\t(1 <<  6) /* Only execute the first E60/E6x in a row */\n#define FLOW_LOOP_ONE_AT_A_TIME\t(1 <<  7) /* Init E6x if no other channel is looping (MPT) */\n#define FLOW_LOOP_IGNORE_TARGET\t(1 <<  8) /* Ignore E60 if count is >=1 (LIQ) */\n#define FLOW_LOOP_DELAY_BREAK\t(1 <<  9) /* E6x jump prevents later Dxx on same row (S3M, IT) */\n#define FLOW_LOOP_DELAY_JUMP\t(1 << 10) /* E6x jump prevents later Bxx on same row (S3M) */\n#define FLOW_LOOP_UNSET_BREAK\t(1 << 11) /* E6x jump cancels prior Dxx on same row (S3M, IMF) */\n#define FLOW_LOOP_UNSET_JUMP\t(1 << 12) /* E6x jump cancels prior Bxx on same row (S3M) */\n#define FLOW_LOOP_SHARED_BREAK\t(1 << 13) /* E6x overrides prior Dxx dest on same row (LIQ) */\n/*#define FLOW_LOOP_TICK_0_JUMP */\t  /* Loop jump shortens row to one tick (DTM) */\n\n#define HAS_FLOW_MODE(x)\t(m->flow_mode & (x))\n\n#define FLOW_MODE_GENERIC\t0\n#define FLOW_LOOP_GLOBAL\t(FLOW_LOOP_GLOBAL_TARGET | FLOW_LOOP_GLOBAL_COUNT)\n/* Simulate players where all breaks and jumps are ignored during a loop jump. */\n#define FLOW_LOOP_NO_BREAK_JUMP\t(FLOW_LOOP_DELAY_BREAK | FLOW_LOOP_DELAY_JUMP | \\\n\t\t\t\t FLOW_LOOP_UNSET_BREAK | FLOW_LOOP_UNSET_JUMP)\n\n/* Scream Tracker 3. No S3Ms seem to rely on the earlier behavior mode.\n * 3.01b has a bug where the end advancement sets the target to the same line\n * instead of the next line; there's no way to make use of this without getting\n * stuck, so it's not simulated.\n */\n#define FLOW_MODE_ST3_301\t(FLOW_LOOP_GLOBAL | FLOW_LOOP_PATTERN_RESET | \\\n\t\t\t\t FLOW_LOOP_END_ADVANCES | FLOW_LOOP_INIT_SAMEROW)\n#define FLOW_MODE_ST3_321\t(FLOW_LOOP_GLOBAL | FLOW_LOOP_PATTERN_RESET | \\\n\t\t\t\t FLOW_LOOP_END_ADVANCES | FLOW_LOOP_NO_BREAK_JUMP)\n\n/* Impulse Tracker. Not clear if anything relies on the old behavior types.\n * IT loops were global pre-1.04, and loop jumps override any prior break/jump.\n * IT 2.00+ loop jumps also delay any following break, but not pattern jumps.\n * IT 2.10+ reintroduced ST3's loop target advancement.\n */\n#define FLOW_MODE_IT_100\t(FLOW_LOOP_GLOBAL | \\\n\t\t\t\t FLOW_LOOP_UNSET_BREAK | FLOW_LOOP_UNSET_JUMP)\n#define FLOW_MODE_IT_104\t(FLOW_LOOP_UNSET_BREAK | FLOW_LOOP_UNSET_JUMP)\n#define FLOW_MODE_IT_200\t(FLOW_MODE_IT_104 | FLOW_LOOP_DELAY_BREAK)\n#define FLOW_MODE_IT_210\t(FLOW_MODE_IT_200 | FLOW_LOOP_END_ADVANCES)\n\n/* Modplug Tracker/early OpenMPT */\n#define FLOW_MODE_MPT_116\t(FLOW_LOOP_ONE_AT_A_TIME | FLOW_LOOP_NO_BREAK_JUMP)\n\n/* Imago Orpheus. Pattern Jump actually does not reset target/count, but all\n * other forms of pattern change do. Unclear if anything relies on it.\n * An XAx jump will set the destination row of a prior Txx jump.\n * An XAx jump will cancel a prior Uxx break on the same row.\n */\n#define FLOW_MODE_ORPHEUS\t(FLOW_LOOP_PATTERN_RESET | \\\n\t\t\t\t FLOW_LOOP_SHARED_BREAK | FLOW_LOOP_UNSET_BREAK)\n\n/* Liquid Tracker uses generic MOD loops with an added behavior where\n * the end of a loop will cancel any other jump in the row that preceded it.\n * M60 is also ignored in channels that have started a loop for some reason.\n * When M6x jumps, it overrides any prior break line set by Jxx/Cxx.\n * There is also a \"Scream Tracker\" compatibility mode (only detectable in the\n * newer format) that adds LOOP_MODE_PATTERN_RESET.\n */\n#define FLOW_MODE_LIQUID\t(FLOW_LOOP_END_CANCELS | FLOW_LOOP_IGNORE_TARGET | \\\n\t\t\t\t FLOW_LOOP_SHARED_BREAK)\n#define FLOW_MODE_LIQUID_COMPAT\t(FLOW_MODE_LIQUID | FLOW_LOOP_PATTERN_RESET)\n\n/* Octalyser (Atari). Looping jumps to the original position E60 was used in,\n * which libxmp doesn't simulate for now since it mostly gets the player stuck.\n * Octalyser ignores E60 if a loop is currently active; it's not clear if it's\n * possible for a module to actually rely on this behavior. Loop jumps\n * interrupt all breaks/jumps on the same row.\n *\n * LOOP_MODE_END_CANCELS is inaccurate but needed to fix \"Dammed Illusion\",\n * which has multiple E6x on one line that don't trigger because the module\n * expects to play in 4 channel mode. This quirk only works for this module\n * because it uses even loop counts, and doesn't break any other modules\n * because multiple E6x on a row otherwise traps the player.\n */\n#define FLOW_MODE_OCTALYSER\t(FLOW_LOOP_GLOBAL | FLOW_LOOP_IGNORE_TARGET | \\\n\t\t\t\t FLOW_LOOP_END_CANCELS | FLOW_LOOP_NO_BREAK_JUMP)\n\n/* Digital Tracker prior to shareware 1.02 doesn't use LOOP_MODE_FIRST_EFFECT,\n * but any MOD that would rely on it is impossible to fingerprint.\n * Early versions had fully working loop jump precedence over jump/break;\n * later versions gradually break this in ways libxmp only partially implements.\n * Commercial version 1.9(?) added per-track counters.\n * Digital Home Studio added a bizarre tick-0 jump bug.\n */\n#define FLOW_MODE_DTM_2015\t(FLOW_LOOP_GLOBAL | FLOW_LOOP_FIRST_EFFECT | \\\n\t\t\t\t FLOW_LOOP_NO_BREAK_JUMP)\n#define FLOW_MODE_DTM_2015_6CH\t(FLOW_LOOP_GLOBAL | FLOW_LOOP_FIRST_EFFECT | \\\n\t\t\t\t FLOW_LOOP_DELAY_BREAK | FLOW_LOOP_UNSET_BREAK | \\\n\t\t\t\t FLOW_LOOP_SHARED_BREAK)\n#define FLOW_MODE_DTM_19\t(FLOW_LOOP_GLOBAL_TARGET | FLOW_LOOP_UNSET_BREAK | \\\n\t\t\t\t FLOW_LOOP_SHARED_BREAK)\n#define FLOW_MODE_DTM_DHS\t(FLOW_LOOP_GLOBAL_TARGET | FLOW_LOOP_TICK_0_JUMP)\n\n\n/* DSP effects */\n#define DSP_EFFECT_CUTOFF\t0x02\n#define DSP_EFFECT_RESONANCE\t0x03\n#define DSP_EFFECT_FILTER_A0\t0xb0\n#define DSP_EFFECT_FILTER_B0\t0xb1\n#define DSP_EFFECT_FILTER_B1\t0xb2\n\n/* Time factor */\n#define DEFAULT_TIME_FACTOR\t10.0\n#define MED_TIME_FACTOR\t\t2.64\n#define FAR_TIME_FACTOR\t\t4.01373\t/* See far_extras.c */\n\n#define NO_SEQUENCE\t\tMAX_SEQUENCES\n#define MAX_SEQUENCES\t\t255\n#define MAX_SAMPLE_SIZE\t\t0x10000000\n#define MAX_SAMPLES\t\t1024\n#define MAX_INSTRUMENTS\t\t255\n#define MAX_PATTERNS\t\t256\n\n#define XMP_MARK_SKIP\t\t0xfe /* S3M/IT (QUIRK_MARKER) skip position */\n#define XMP_MARK_END\t\t0xff /* S3M/IT (QUIRK_MARKER) end position */\n\n#define IS_PLAYER_MODE_MOD()\t(m->read_event_type == READ_EVENT_MOD)\n#define IS_PLAYER_MODE_FT2()\t(m->read_event_type == READ_EVENT_FT2)\n#define IS_PLAYER_MODE_ST3()\t(m->read_event_type == READ_EVENT_ST3)\n#define IS_PLAYER_MODE_IT()\t(m->read_event_type == READ_EVENT_IT)\n#define IS_PLAYER_MODE_MED()\t(m->read_event_type == READ_EVENT_MED)\n#define IS_PERIOD_MODRNG()\t(m->period_type == PERIOD_MODRNG)\n#define IS_PERIOD_LINEAR()\t(m->period_type == PERIOD_LINEAR)\n#define IS_PERIOD_CSPD()\t(m->period_type == PERIOD_CSPD)\n\n#define IS_AMIGA_MOD()\t(IS_PLAYER_MODE_MOD() && IS_PERIOD_MODRNG())\n\nstruct ord_data {\n\tint speed;\n\tint bpm;\n\tint gvl;\n\tint time; /* TODO: double */\n\tint start_row;\n#ifndef LIBXMP_CORE_PLAYER\n\tint st26_speed;\n#endif\n};\n\n\n/* Context */\n\nstruct smix_data {\n\tint chn;\n\tint ins;\n\tint smp;\n\tstruct xmp_instrument *xxi;\n\tstruct xmp_sample *xxs;\n};\n\n/* This will be added to the sample structure in the next API revision */\nstruct extra_sample_data {\n\tdouble c5spd;\n\tint sus;\n\tint sue;\n};\n\nstruct midi_macro {\n\tchar data[32];\n};\n\nstruct midi_macro_data {\n\tstruct midi_macro param[16];\n\tstruct midi_macro fixed[128];\n};\n\nstruct module_data {\n\tstruct xmp_module mod;\n\n\tchar *dirname;\t\t\t/* file dirname */\n\tchar *basename;\t\t\t/* file basename */\n\tconst char *filename;\t\t/* Module file name */\n\tchar *comment;\t\t\t/* Comments, if any */\n\tuint8 md5[16];\t\t\t/* MD5 message digest */\n\tint size;\t\t\t/* File size */\n\tdouble rrate;\t\t\t/* Replay rate */\n\tdouble time_factor;\t\t/* Time conversion constant */\n\tint c4rate;\t\t\t/* C4 replay rate */\n\tint volbase;\t\t\t/* Volume base */\n\tint gvolbase;\t\t\t/* Global volume base */\n\tint gvol;\t\t\t/* Global volume */\n\tint mvolbase;\t\t\t/* Mix volume base (S3M/IT) */\n\tint mvol;\t\t\t/* Mix volume (S3M/IT) */\n\tconst int *vol_table;\t\t/* Volume translation table */\n\tint quirk;\t\t\t/* player quirks */\n\tint flow_mode;\t\t\t/* Flow quirks, esp. Pattern Loop */\n#define READ_EVENT_MOD\t0\n#define READ_EVENT_FT2\t1\n#define READ_EVENT_ST3\t2\n#define READ_EVENT_IT\t3\n#define READ_EVENT_MED\t4\n\tint read_event_type;\n#define PERIOD_AMIGA\t0\n#define PERIOD_MODRNG\t1\n#define PERIOD_LINEAR\t2\n#define PERIOD_CSPD\t3\n\tint period_type;\n\tint smpctl;\t\t\t/* sample control flags */\n\tint defpan;\t\t\t/* default pan setting */\n\tstruct ord_data xxo_info[XMP_MAX_MOD_LENGTH];\n\tint num_sequences;\n\tstruct xmp_sequence seq_data[MAX_SEQUENCES];\n\tchar *instrument_path;\n\tvoid *extra;\t\t\t/* format-specific extra fields */\n\tuint8 **scan_cnt;\t\t/* scan counters */\n\tstruct extra_sample_data *xtra;\n\tstruct midi_macro_data *midi;\n\tint compare_vblank;\n};\n\nstruct pattern_loop {\n\tint start;\n\tint count;\n};\n\nstruct flow_control {\n\tint pbreak;\n\tint jump;\n\tint delay;\n\tint jumpline;\n\tint loop_dest;\t\t/* Pattern loop destination, -1 for none */\n\tint loop_param;\t\t/* Last loop param for Digital Tracker */\n\tint loop_start;\t\t/* Global loop target for S3M et al. */\n\tint loop_count;\t\t/* Global loop count for S3M et al. */\n\tint loop_active_num;\t/* Number of active loops for scan */\n#ifndef LIBXMP_CORE_PLAYER\n\tint jump_in_pat;\n#endif\n\n\tstruct pattern_loop *loop;\n\n\tint num_rows;\n\tint end_point;\n#define ROWDELAY_ON\t\t(1 << 0)\n#define ROWDELAY_FIRST_FRAME\t(1 << 1)\n\tint rowdelay;\t\t/* For IT pattern row delay */\n\tint rowdelay_set;\n};\n\nstruct virt_channel {\n\tint count;\n\tint map;\n};\n\nstruct scan_data {\n\tint time;\t\t\t/* replay time in ms */ /* TODO: double */\n\tint row;\n\tint ord;\n\tint num;\n};\n\nstruct player_data {\n\tint ord;\n\tint pos;\n\tint row;\n\tint frame;\n\tint speed;\n\tint bpm;\n\tint mode;\n\tint player_flags;\n\tint flags;\n\n\tdouble scan_time_factor;\t/* m->time_factor for most recent scan */\n\tdouble current_time;\t\t/* current time based on scan time factor */\n\n\tint loop_count;\n\tint sequence;\n\tunsigned char sequence_control[XMP_MAX_MOD_LENGTH];\n\n\tint smix_vol;\t\t\t/* SFX volume */\n\tint master_vol;\t\t\t/* Music volume */\n\tint gvol;\n\n\tstruct flow_control flow;\n\n\tstruct scan_data *scan;\n\n\tstruct channel_data *xc_data;\n\n\tint channel_vol[XMP_MAX_CHANNELS];\n\tchar channel_mute[XMP_MAX_CHANNELS];\n\n\tstruct virt_control {\n\t\tint num_tracks;\t\t/* Number of tracks */\n\t\tint virt_channels;\t/* Number of virtual channels */\n\t\tint virt_used;\t\t/* Number of voices currently in use */\n\t\tint maxvoc;\t\t/* Number of sound card voices */\n\n\t\tstruct virt_channel *virt_channel;\n\n\t\tstruct mixer_voice *voice_array;\n\t} virt;\n\n\tstruct xmp_event inject_event[XMP_MAX_CHANNELS];\n\n\tstruct {\n\t\tint consumed;\n\t\tint in_size;\n\t\tchar *in_buffer;\n\t} buffer_data;\n\n#ifndef LIBXMP_CORE_PLAYER\n\tint st26_speed;\t\t\t/* For IceTracker speed effect */\n#endif\n\tint filter;\t\t\t/* Amiga led filter */\n};\n\nstruct mixer_data {\n\tint freq;\t\t/* sampling rate */\n\tint format;\t\t/* sample format */\n\tint amplify;\t\t/* amplification multiplier */\n\tint mix;\t\t/* percentage of channel separation */\n\tint interp;\t\t/* interpolation type */\n\tint dsp;\t\t/* dsp effect flags */\n\tchar *buffer;\t\t/* output buffer */\n\tint32 *buf32;\t\t/* temporary buffer for 32 bit samples */\n\tint total_size;\t\t/* allocated samples (not frames) in buffers */\n\tint numvoc;\t\t/* default softmixer voices number */\n\tint ticksize;\n\tint dtright;\t\t/* anticlick control, right channel */\n\tint dtleft;\t\t/* anticlick control, left channel */\n\tint bidir_adjust;\t/* adjustment for IT bidirectional loops */\n\tdouble pbase;\t\t/* period base */\n};\n\nstruct rng_state {\n\tunsigned state;\n};\n\nstruct context_data {\n\tstruct player_data p;\n\tstruct mixer_data s;\n\tstruct module_data m;\n\tstruct smix_data smix;\n\tstruct rng_state rng;\n\tint state;\n};\n\n\n/* Prototypes */\n\nchar\t*libxmp_adjust_string\t(char *);\nint\tlibxmp_prepare_scan\t(struct context_data *);\nvoid\tlibxmp_free_scan\t(struct context_data *);\nint\tlibxmp_scan_sequences\t(struct context_data *);\nint\tlibxmp_get_sequence\t(struct context_data *, int);\nint\tlibxmp_set_player_mode\t(struct context_data *);\nvoid\tlibxmp_reset_flow\t(struct context_data *);\n\nint8\tread8s\t\t\t(FILE *, int *err);\nuint8\tread8\t\t\t(FILE *, int *err);\nuint16\tread16l\t\t\t(FILE *, int *err);\nuint16\tread16b\t\t\t(FILE *, int *err);\nuint32\tread24l\t\t\t(FILE *, int *err);\nuint32\tread24b\t\t\t(FILE *, int *err);\nuint32\tread32l\t\t\t(FILE *, int *err);\nuint32\tread32b\t\t\t(FILE *, int *err);\nstatic inline void write8\t(FILE *f, uint8 b) {\n\tfputc(b, f);\n}\nvoid\twrite16l\t\t(FILE *, uint16);\nvoid\twrite16b\t\t(FILE *, uint16);\nvoid\twrite32l\t\t(FILE *, uint32);\nvoid\twrite32b\t\t(FILE *, uint32);\n\nuint16\treadmem16l\t\t(const uint8 *);\nuint16\treadmem16b\t\t(const uint8 *);\nuint32\treadmem24l\t\t(const uint8 *);\nuint32\treadmem24b\t\t(const uint8 *);\nuint32\treadmem32l\t\t(const uint8 *);\nuint32\treadmem32b\t\t(const uint8 *);\n\nstruct xmp_instrument *libxmp_get_instrument(struct context_data *, int);\nstruct xmp_sample *libxmp_get_sample(struct context_data *, int);\n\nchar *libxmp_strdup(const char *);\nint libxmp_get_filetype (const char *);\n\n#endif /* LIBXMP_COMMON_H */\n"
  },
  {
    "path": "contrib/libxmp/src/control.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"format.h\"\n#include \"virtual.h\"\n#include \"mixer.h\"\n#include \"rng.h\"\n\n/* TODO: Change this to const char *const in a future ABI change */\nconst char *xmp_version LIBXMP_EXPORT_VAR = XMP_VERSION;\nconst unsigned int xmp_vercode LIBXMP_EXPORT_VAR = XMP_VERCODE;\n\nxmp_context xmp_create_context(void)\n{\n\tstruct context_data *ctx;\n\n\tctx = (struct context_data *) calloc(1, sizeof(struct context_data));\n\tif (ctx == NULL) {\n\t\treturn NULL;\n\t}\n\n\tctx->state = XMP_STATE_UNLOADED;\n\tctx->m.defpan = 100;\n\tctx->s.numvoc = SMIX_NUMVOC;\n\tlibxmp_init_random(&ctx->rng);\n\n\treturn (xmp_context)ctx;\n}\n\nvoid xmp_free_context(xmp_context opaque)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct module_data *m = &ctx->m;\n\n\tif (ctx->state > XMP_STATE_UNLOADED)\n\t\txmp_release_module(opaque);\n\n\tfree(m->instrument_path);\n\tfree(opaque);\n}\n\nstatic void set_position(struct context_data *ctx, int pos, int dir)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct flow_control *f = &p->flow;\n\tint seq;\n\tint has_marker;\n\n\t/* If dir is 0, we can jump to a different sequence */\n\tif (dir == 0) {\n\t\tseq = libxmp_get_sequence(ctx, pos);\n\t} else {\n\t\tseq = p->sequence;\n\t}\n\n\tif (seq < 0 || seq >= m->num_sequences) {\n\t\treturn;\n\t}\n\n\thas_marker = HAS_QUIRK(QUIRK_MARKER);\n\n\tp->sequence = seq;\n\n\tif (pos >= 0) {\n\t\tint pat;\n\n\t\twhile (has_marker && pos > 0 && pos < mod->len - 1 &&\n\t\t       mod->xxo[pos] == XMP_MARK_SKIP) {\n\t\t\tif (dir < 0) {\n\t\t\t\tpos--;\n\t\t\t} else {\n\t\t\t\tpos++;\n\t\t\t}\n\t\t}\n\t\tif (pos >= mod->len) {\n\t\t\treturn;\n\t\t}\n\t\tpat = mod->xxo[pos];\n\n\t\tif (pat < mod->pat) {\n\t\t\tif (has_marker && pat == XMP_MARK_END) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (pos > p->scan[seq].ord) {\n\t\t\t\tf->end_point = 0;\n\t\t\t} else {\n\t\t\t\tf->num_rows = mod->xxp[pat]->rows;\n\t\t\t\tf->end_point = p->scan[seq].num;\n\t\t\t\tf->jumpline = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (pos < mod->len) {\n\t\tif (pos == 0) {\n\t\t\tp->pos = -1;\n\t\t} else {\n\t\t\tp->pos = pos;\n\t\t}\n\t\t/* Clear flow vars to prevent old pattern jumps and\n\t\t * other junk from executing in the new position. */\n\t\tlibxmp_reset_flow(ctx);\n\t}\n}\n\nint xmp_next_position(xmp_context opaque)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn -XMP_ERROR_STATE;\n\n\tif (p->pos < 0) {\n\t\t/* Restart a stopped or restarting module.\n\t\t * This was previously done implicitly. */\n\t\tset_position(ctx, -1, 1);\n\t} else if (p->pos < m->mod.len) {\n\t\tset_position(ctx, p->pos + 1, 1);\n\t}\n\n\treturn p->pos;\n}\n\nint xmp_prev_position(xmp_context opaque)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn -XMP_ERROR_STATE;\n\n\tif (p->pos == m->seq_data[p->sequence].entry_point) {\n\t\tset_position(ctx, -1, -1);\n\t} else if (p->pos > m->seq_data[p->sequence].entry_point) {\n\t\tset_position(ctx, p->pos - 1, -1);\n\t}\n\treturn p->pos < 0 ? 0 : p->pos;\n}\n\nint xmp_set_position(xmp_context opaque, int pos)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn -XMP_ERROR_STATE;\n\n\tif (pos < 0 || pos >= m->mod.len)\n\t\treturn -XMP_ERROR_INVALID;\n\n\tset_position(ctx, pos, 0);\n\n\treturn p->pos;\n}\n\nint xmp_set_row(xmp_context opaque, int row)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct flow_control *f = &p->flow;\n\tint pos = p->pos;\n\tint pattern;\n\n\tif (pos < 0 || pos >= mod->len) {\n\t\tpos = 0;\n\t}\n\tpattern = mod->xxo[pos];\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn -XMP_ERROR_STATE;\n\n\tif (pattern >= mod->pat || row < 0 || row >= mod->xxp[pattern]->rows)\n\t\treturn -XMP_ERROR_INVALID;\n\n\t/* See set_position. */\n\tif (p->pos < 0)\n\t\tp->pos = 0;\n\tp->ord = p->pos;\n\tp->row = row;\n\tp->frame = -1;\n\tf->num_rows = mod->xxp[mod->xxo[p->ord]]->rows;\n\n\treturn row;\n}\n\nvoid xmp_stop_module(xmp_context opaque)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn;\n\n\tp->pos = -2;\n}\n\nvoid xmp_restart_module(xmp_context opaque)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn;\n\n\tp->loop_count = 0;\n\tp->pos = -1;\n}\n\nint xmp_seek_time(xmp_context opaque, int time)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tint i, t;\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn -XMP_ERROR_STATE;\n\n\tfor (i = m->mod.len - 1; i >= 0; i--) {\n\t\tint pat = m->mod.xxo[i];\n\t\tif (pat >= m->mod.pat) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (libxmp_get_sequence(ctx, i) != p->sequence) {\n\t\t\tcontinue;\n\t\t}\n\t\tt = m->xxo_info[i].time;\n\t\tif (time >= t) {\n\t\t\tset_position(ctx, i, 1);\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (i < 0) {\n\t\txmp_set_position(opaque, 0);\n\t}\n\n\treturn p->pos < 0 ? 0 : p->pos;\n}\n\nint xmp_channel_mute(xmp_context opaque, int chn, int status)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tint ret;\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn -XMP_ERROR_STATE;\n\n\tif (chn < 0 || chn >= XMP_MAX_CHANNELS) {\n\t\treturn -XMP_ERROR_INVALID;\n\t}\n\n\tret = p->channel_mute[chn];\n\n\tif (status >= 2) {\n\t\tp->channel_mute[chn] = !p->channel_mute[chn];\n\t} else if (status >= 0) {\n\t\tp->channel_mute[chn] = status;\n\t}\n\n\treturn ret;\n}\n\nint xmp_channel_vol(xmp_context opaque, int chn, int vol)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tint ret;\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn -XMP_ERROR_STATE;\n\n\tif (chn < 0 || chn >= XMP_MAX_CHANNELS) {\n\t\treturn -XMP_ERROR_INVALID;\n\t}\n\n\tret = p->channel_vol[chn];\n\n\tif (vol >= 0 && vol <= 100) {\n\t\tp->channel_vol[chn] = vol;\n\t}\n\n\treturn ret;\n}\n\n#ifdef USE_VERSIONED_SYMBOLS\nLIBXMP_BEGIN_DECLS /* no name-mangling */\nLIBXMP_EXPORT_VERSIONED extern int xmp_set_player_v40__(xmp_context, int, int) LIBXMP_ATTRIB_SYMVER(\"xmp_set_player@XMP_4.0\");\nLIBXMP_EXPORT_VERSIONED extern int xmp_set_player_v41__(xmp_context, int, int)\n\t\t\t__attribute__((alias(\"xmp_set_player_v40__\"))) LIBXMP_ATTRIB_SYMVER(\"xmp_set_player@XMP_4.1\");\nLIBXMP_EXPORT_VERSIONED extern int xmp_set_player_v43__(xmp_context, int, int)\n\t\t\t__attribute__((alias(\"xmp_set_player_v40__\"))) LIBXMP_ATTRIB_SYMVER(\"xmp_set_player@XMP_4.3\");\nLIBXMP_EXPORT_VERSIONED extern int xmp_set_player_v44__(xmp_context, int, int)\n\t\t\t__attribute__((alias(\"xmp_set_player_v40__\"))) LIBXMP_ATTRIB_SYMVER(\"xmp_set_player@@XMP_4.4\");\n\n#ifndef HAVE_ATTRIBUTE_SYMVER\nasm(\".symver xmp_set_player_v40__, xmp_set_player@XMP_4.0\");\nasm(\".symver xmp_set_player_v41__, xmp_set_player@XMP_4.1\");\nasm(\".symver xmp_set_player_v43__, xmp_set_player@XMP_4.3\");\nasm(\".symver xmp_set_player_v44__, xmp_set_player@@XMP_4.4\");\n#endif\nLIBXMP_END_DECLS\n\n#define xmp_set_player__ xmp_set_player_v40__\n#else\n#define xmp_set_player__ xmp_set_player\n#endif\n\nint xmp_set_player__(xmp_context opaque, int parm, int val)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct mixer_data *s = &ctx->s;\n\tint ret = -XMP_ERROR_INVALID;\n\n\n\tif (parm == XMP_PLAYER_SMPCTL || parm == XMP_PLAYER_DEFPAN) {\n\t\t/* these should be set before loading the module */\n\t\tif (ctx->state >= XMP_STATE_LOADED) {\n\t\t\treturn -XMP_ERROR_STATE;\n\t\t}\n\t} else if (parm == XMP_PLAYER_VOICES) {\n\t\t/* these should be set before start playing */\n\t\tif (ctx->state >= XMP_STATE_PLAYING) {\n\t\t\treturn -XMP_ERROR_STATE;\n\t\t}\n\t} else if (ctx->state < XMP_STATE_PLAYING) {\n\t\treturn -XMP_ERROR_STATE;\n\t}\n\n\tswitch (parm) {\n\tcase XMP_PLAYER_AMP:\n\t\tif (val >= 0 && val <= 3) {\n\t\t\ts->amplify = val;\n\t\t\tret = 0;\n\t\t}\n\t\tbreak;\n\tcase XMP_PLAYER_MIX:\n\t\tif (val >= -100 && val <= 100) {\n\t\t\ts->mix = val;\n\t\t\tret = 0;\n\t\t}\n\t\tbreak;\n\tcase XMP_PLAYER_INTERP:\n\t\tif (val >= XMP_INTERP_NEAREST && val <= XMP_INTERP_SPLINE) {\n\t\t\ts->interp = val;\n\t\t\tret = 0;\n\t\t}\n\t\tbreak;\n\tcase XMP_PLAYER_DSP:\n\t\ts->dsp = val;\n\t\tret = 0;\n\t\tbreak;\n\tcase XMP_PLAYER_FLAGS: {\n\t\tp->player_flags = val;\n\t\tret = 0;\n\t\tbreak; }\n\n\t/* 4.1 */\n\tcase XMP_PLAYER_CFLAGS: {\n\t\tint vblank = p->flags & XMP_FLAGS_VBLANK;\n\t\tp->flags = val;\n\t\tif (vblank != (p->flags & XMP_FLAGS_VBLANK))\n\t\t\tlibxmp_scan_sequences(ctx);\n\t\tret = 0;\n\t\tbreak; }\n\tcase XMP_PLAYER_SMPCTL:\n\t\tm->smpctl = val;\n\t\tret = 0;\n\t\tbreak;\n\tcase XMP_PLAYER_VOLUME:\n\t\tif (val >= 0 && val <= 200) {\n\t\t\tp->master_vol = val;\n\t\t\tret = 0;\n\t\t}\n\t\tbreak;\n\tcase XMP_PLAYER_SMIX_VOLUME:\n\t\tif (val >= 0 && val <= 200) {\n\t\t\tp->smix_vol = val;\n\t\t\tret = 0;\n\t\t}\n\t\tbreak;\n\n\t/* 4.3 */\n\tcase XMP_PLAYER_DEFPAN:\n\t\tif (val >= 0 && val <= 100) {\n\t\t\tm->defpan = val;\n\t\t\tret = 0;\n\t\t}\n\t\tbreak;\n\n\t/* 4.4 */\n\tcase XMP_PLAYER_MODE:\n\t\tp->mode = val;\n\t\tlibxmp_set_player_mode(ctx);\n\t\tlibxmp_scan_sequences(ctx);\n\t\tret = 0;\n\t\tbreak;\n\tcase XMP_PLAYER_VOICES:\n\t\ts->numvoc = val;\n\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\n#ifdef USE_VERSIONED_SYMBOLS\nLIBXMP_BEGIN_DECLS /* no name-mangling */\nLIBXMP_EXPORT_VERSIONED extern int xmp_get_player_v40__(xmp_context, int) LIBXMP_ATTRIB_SYMVER(\"xmp_get_player@XMP_4.0\");\nLIBXMP_EXPORT_VERSIONED extern int xmp_get_player_v41__(xmp_context, int)\n\t\t__attribute__((alias(\"xmp_get_player_v40__\"))) LIBXMP_ATTRIB_SYMVER(\"xmp_get_player@XMP_4.1\");\nLIBXMP_EXPORT_VERSIONED extern int xmp_get_player_v42__(xmp_context, int)\n\t\t__attribute__((alias(\"xmp_get_player_v40__\"))) LIBXMP_ATTRIB_SYMVER(\"xmp_get_player@XMP_4.2\");\nLIBXMP_EXPORT_VERSIONED extern int xmp_get_player_v43__(xmp_context, int)\n\t\t__attribute__((alias(\"xmp_get_player_v40__\"))) LIBXMP_ATTRIB_SYMVER(\"xmp_get_player@XMP_4.3\");\nLIBXMP_EXPORT_VERSIONED extern int xmp_get_player_v44__(xmp_context, int)\n\t\t__attribute__((alias(\"xmp_get_player_v40__\"))) LIBXMP_ATTRIB_SYMVER(\"xmp_get_player@@XMP_4.4\");\n\n#ifndef HAVE_ATTRIBUTE_SYMVER\nasm(\".symver xmp_get_player_v40__, xmp_get_player@XMP_4.0\");\nasm(\".symver xmp_get_player_v41__, xmp_get_player@XMP_4.1\");\nasm(\".symver xmp_get_player_v42__, xmp_get_player@XMP_4.2\");\nasm(\".symver xmp_get_player_v43__, xmp_get_player@XMP_4.3\");\nasm(\".symver xmp_get_player_v44__, xmp_get_player@@XMP_4.4\");\n#endif\nLIBXMP_END_DECLS\n\n#define xmp_get_player__ xmp_get_player_v40__\n#else\n#define xmp_get_player__ xmp_get_player\n#endif\n\nint xmp_get_player__(xmp_context opaque, int parm)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct mixer_data *s = &ctx->s;\n\tint ret = -XMP_ERROR_INVALID;\n\n\tif (parm == XMP_PLAYER_SMPCTL || parm == XMP_PLAYER_DEFPAN) {\n\t\t// can read these at any time\n\t} else if (parm != XMP_PLAYER_STATE && ctx->state < XMP_STATE_PLAYING) {\n\t\treturn -XMP_ERROR_STATE;\n\t}\n\n\tswitch (parm) {\n\tcase XMP_PLAYER_AMP:\n\t\tret = s->amplify;\n\t\tbreak;\n\tcase XMP_PLAYER_MIX:\n\t\tret = s->mix;\n\t\tbreak;\n\tcase XMP_PLAYER_INTERP:\n\t\tret = s->interp;\n\t\tbreak;\n\tcase XMP_PLAYER_DSP:\n\t\tret = s->dsp;\n\t\tbreak;\n\tcase XMP_PLAYER_FLAGS:\n\t\tret = p->player_flags;\n\t\tbreak;\n\n\t/* 4.1 */\n\tcase XMP_PLAYER_CFLAGS:\n\t\tret = p->flags;\n\t\tbreak;\n\tcase XMP_PLAYER_SMPCTL:\n\t\tret = m->smpctl;\n\t\tbreak;\n\tcase XMP_PLAYER_VOLUME:\n\t\tret = p->master_vol;\n\t\tbreak;\n\tcase XMP_PLAYER_SMIX_VOLUME:\n\t\tret = p->smix_vol;\n\t\tbreak;\n\n\t/* 4.2 */\n\tcase XMP_PLAYER_STATE:\n\t\tret = ctx->state;\n\t\tbreak;\n\n\t/* 4.3 */\n\tcase XMP_PLAYER_DEFPAN:\n\t\tret = m->defpan;\n\t\tbreak;\n\n\t/* 4.4 */\n\tcase XMP_PLAYER_MODE:\n\t\tret = p->mode;\n\t\tbreak;\n\tcase XMP_PLAYER_MIXER_TYPE:\n\t\tret = XMP_MIXER_STANDARD;\n\t\tif (p->flags & XMP_FLAGS_A500) {\n\t\t\tif (IS_AMIGA_MOD()) {\n#ifdef LIBXMP_PAULA_SIMULATOR\n\t\t\t\tif (p->filter) {\n\t\t\t\t\tret = XMP_MIXER_A500F;\n\t\t\t\t} else {\n\t\t\t\t\tret = XMP_MIXER_A500;\n\t\t\t\t}\n#endif\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase XMP_PLAYER_VOICES:\n\t\tret = s->numvoc;\n\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\nconst char *const *xmp_get_format_list(void)\n{\n\treturn format_list();\n}\n\nvoid xmp_inject_event(xmp_context opaque, int channel, struct xmp_event *e)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn;\n\n\tmemcpy(&p->inject_event[channel], e, sizeof(struct xmp_event));\n\tp->inject_event[channel]._flag = 1;\n}\n\nint xmp_set_instrument_path(xmp_context opaque, const char *path)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct module_data *m = &ctx->m;\n\n\tif (m->instrument_path != NULL) {\n\t\tfree(m->instrument_path);\n\t\tm->instrument_path = NULL;\n\t}\n\tif (path == NULL) {\n\t\treturn 0;\n\t}\n\n\tm->instrument_path = libxmp_strdup(path);\n\tif (m->instrument_path == NULL) {\n\t\treturn -XMP_ERROR_SYSTEM;\n\t}\n\n\treturn 0;\n}\n\nint xmp_set_tempo_factor(xmp_context opaque, double val)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct mixer_data *s = &ctx->s;\n\tint ticksize;\n\n\t/* This function relies on values initialized by xmp_start_player\n\t * and will behave in an undefined manner if called prior. */\n\tif (ctx->state < XMP_STATE_PLAYING) {\n\t\treturn -XMP_ERROR_STATE;\n\t}\n\n\tif (val <= 0.0 || val != val /* NaN */) {\n\t\treturn -1;\n\t}\n\n\tval *= 10;\n\n\t/* s->freq can change between xmp_start_player calls and p->bpm can\n\t * change during playback, so repeat these checks in the mixer. */\n\tticksize = libxmp_mixer_get_ticksize(s->freq, val, m->rrate, p->bpm);\n\n\t/* ticksize is in frames, s->total_size is in frames * 2. */\n\tif (ticksize < 0 || ticksize > (s->total_size / 2)) {\n\t\treturn -1;\n\t}\n\tm->time_factor = val;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/dataio.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2022 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <errno.h>\n#include \"common.h\"\n\n\n#define read_byte(x) do {\t\t\\\n\t(x) = fgetc(f);\t\t\t\\\n\tif ((x) < 0)  goto error;\t\\\n} while (0)\n\n#define set_error(x) do {\t\t\\\n\tif (err != NULL) *err = (x);\t\\\n} while (0)\n\nuint8 read8(FILE *f, int *err)\n{\n\tint a;\n\n\tread_byte(a);\n\tset_error(0);\n\treturn a;\n\n   error:\n\tset_error(ferror(f) ? errno : EOF);\n\treturn 0xff;\n}\n\nint8 read8s(FILE *f, int *err)\n{\n\tint a;\n\n\tread_byte(a);\n\tset_error(0);\n\treturn (int8)a;\n\n   error:\n\tset_error(ferror(f) ? errno : EOF);\n\treturn 0;\n}\n\nuint16 read16l(FILE *f, int *err)\n{\n\tint a, b;\n\n\tread_byte(a);\n\tread_byte(b);\n\n\tset_error(0);\n\treturn ((uint16)b << 8) | a;\n\n    error:\n\tset_error(ferror(f) ? errno : EOF);\n\treturn 0xffff;\n}\n\nuint16 read16b(FILE *f, int *err)\n{\n\tint a, b;\n\n\tread_byte(a);\n\tread_byte(b);\n\n\tset_error(0);\n\treturn (a << 8) | b;\n\n    error:\n\tset_error(ferror(f) ? errno : EOF);\n\treturn 0xffff;\n}\n\nuint32 read24l(FILE *f, int *err)\n{\n\tint a, b, c;\n\n\tread_byte(a);\n\tread_byte(b);\n\tread_byte(c);\n\n\tset_error(0);\n\treturn (c << 16) | (b << 8) | a;\n\n    error:\n\tset_error(ferror(f) ? errno : EOF);\n\treturn 0xffffffff;\n}\n\nuint32 read24b(FILE *f, int *err)\n{\n\tint a, b, c;\n\n\tread_byte(a);\n\tread_byte(b);\n\tread_byte(c);\n\n\tset_error(0);\n\treturn (a << 16) | (b << 8) | c;\n\n    error:\n\tset_error(ferror(f) ? errno : EOF);\n\treturn 0xffffffff;\n}\n\nuint32 read32l(FILE *f, int *err)\n{\n\tint a, b, c, d;\n\n\tread_byte(a);\n\tread_byte(b);\n\tread_byte(c);\n\tread_byte(d);\n\n\tset_error(0);\n\treturn (d << 24) | (c << 16) | (b << 8) | a;\n\n    error:\n\tset_error(ferror(f) ? errno : EOF);\n\treturn 0xffffffff;\n}\n\nuint32 read32b(FILE *f, int *err)\n{\n\tint a, b, c, d;\n\n\tread_byte(a);\n\tread_byte(b);\n\tread_byte(c);\n\tread_byte(d);\n\n\tset_error(0);\n\treturn (a << 24) | (b << 16) | (c << 8) | d;\n\n    error:\n\tset_error(ferror(f) ? errno : EOF);\n\treturn 0xffffffff;\n}\n\nuint16 readmem16l(const uint8 *m)\n{\n\tuint32 a, b;\n\n\ta = m[0];\n\tb = m[1];\n\n\treturn (b << 8) | a;\n}\n\nuint16 readmem16b(const uint8 *m)\n{\n\tuint32 a, b;\n\n\ta = m[0];\n\tb = m[1];\n\n\treturn (a << 8) | b;\n}\n\nuint32 readmem24l(const uint8 *m)\n{\n\tuint32 a, b, c;\n\n\ta = m[0];\n\tb = m[1];\n\tc = m[2];\n\n\treturn (c << 16) | (b << 8) | a;\n}\n\nuint32 readmem24b(const uint8 *m)\n{\n\tuint32 a, b, c;\n\n\ta = m[0];\n\tb = m[1];\n\tc = m[2];\n\n\treturn (a << 16) | (b << 8) | c;\n}\n\nuint32 readmem32l(const uint8 *m)\n{\n\tuint32 a, b, c, d;\n\n\ta = m[0];\n\tb = m[1];\n\tc = m[2];\n\td = m[3];\n\n\treturn (d << 24) | (c << 16) | (b << 8) | a;\n}\n\nuint32 readmem32b(const uint8 *m)\n{\n\tuint32 a, b, c, d;\n\n\ta = m[0];\n\tb = m[1];\n\tc = m[2];\n\td = m[3];\n\n\treturn (a << 24) | (b << 16) | (c << 8) | d;\n}\n\n#ifndef LIBXMP_CORE_PLAYER\n\nvoid write16l(FILE *f, uint16 w)\n{\n\twrite8(f, w & 0x00ff);\n\twrite8(f, (w & 0xff00) >> 8);\n}\n\nvoid write16b(FILE *f, uint16 w)\n{\n\twrite8(f, (w & 0xff00) >> 8);\n\twrite8(f, w & 0x00ff);\n}\n\nvoid write32l(FILE *f, uint32 w)\n{\n\twrite8(f, w & 0x000000ff);\n\twrite8(f, (w & 0x0000ff00) >> 8);\n\twrite8(f, (w & 0x00ff0000) >> 16);\n\twrite8(f, (w & 0xff000000) >> 24);\n}\n\nvoid write32b(FILE *f, uint32 w)\n{\n\twrite8(f, (w & 0xff000000) >> 24);\n\twrite8(f, (w & 0x00ff0000) >> 16);\n\twrite8(f, (w & 0x0000ff00) >> 8);\n\twrite8(f, w & 0x000000ff);\n}\n\n#endif\n"
  },
  {
    "path": "contrib/libxmp/src/effects.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n#include \"player.h\"\n#include \"effects.h\"\n#include \"period.h\"\n#include \"virtual.h\"\n#include \"mixer.h\"\n#ifndef LIBXMP_CORE_PLAYER\n#include \"extras.h\"\n#endif\n\n#define NOT_IMPLEMENTED\n#define HAS_QUIRK(x) (m->quirk & (x))\n\n#define SET_LFO_NOTZERO(lfo, depth, rate) do { \\\n\tif ((depth) != 0) libxmp_lfo_set_depth(lfo, depth); \\\n\tif ((rate) != 0)  libxmp_lfo_set_rate(lfo, rate); \\\n} while (0)\n\n#define EFFECT_MEMORY__(p, m) do { \\\n\tif ((p) == 0) { (p) = (m); } else { (m) = (p); } \\\n} while (0)\n\n/* ST3 effect memory is not a bug, but it's a weird implementation and it's\n * unlikely to be supported in anything other than ST3 (or OpenMPT).\n */\n#define EFFECT_MEMORY(p, m) do { \\\n\tif (HAS_QUIRK(QUIRK_ST3BUGS)) { \\\n\t\tEFFECT_MEMORY__((p), xc->vol.memory); \\\n\t} else { \\\n\t\tEFFECT_MEMORY__((p), (m)); \\\n\t} \\\n} while (0)\n\n#define EFFECT_MEMORY_SETONLY(p, m) do { \\\n\tEFFECT_MEMORY__((p), (m)); \\\n\tif (HAS_QUIRK(QUIRK_ST3BUGS)) { \\\n\t\tif ((p) != 0) { xc->vol.memory = (p); } \\\n\t} \\\n} while (0)\n\n#define EFFECT_MEMORY_S3M(p) do { \\\n\tif (HAS_QUIRK(QUIRK_ST3BUGS)) { \\\n\t\tEFFECT_MEMORY__((p), xc->vol.memory); \\\n\t} \\\n} while (0)\n\n\nstatic void do_toneporta(struct context_data *ctx,\n                                struct channel_data *xc, int note)\n{\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_instrument *instrument = &m->mod.xxi[xc->ins];\n\tstruct xmp_subinstrument *sub;\n\tint mapped_xpo = 0;\n\tint mapped = 0;\n\n\tif (IS_VALID_NOTE(xc->key)) {\n\t\tmapped = instrument->map[xc->key].ins;\n\t}\n\n\tif (mapped >= instrument->nsm) {\n\t\tmapped = 0;\n\t}\n\n\tsub = &instrument->sub[mapped];\n\n\tif (IS_VALID_NOTE(note - 1) && (uint32)xc->ins < m->mod.ins) {\n\t\tnote--;\n\t\tif (IS_VALID_NOTE(xc->key_porta)) {\n\t\t\tmapped_xpo = instrument->map[xc->key_porta].xpo;\n\t\t}\n\t\txc->porta.target = libxmp_note_to_period(ctx, note + sub->xpo +\n\t\t\tmapped_xpo, xc->finetune, xc->per_adj);\n\t}\n\txc->porta.dir = xc->period < xc->porta.target ? 1 : -1;\n}\n\nvoid libxmp_process_fx(struct context_data *ctx, struct channel_data *xc, int chn,\n\t\tstruct xmp_event *e, int fnum)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct flow_control *f = &p->flow;\n\tuint8 note, fxp, fxt;\n\tint h, l;\n\n\t/* key_porta is IT only */\n\tif (m->read_event_type != READ_EVENT_IT) {\n\t\txc->key_porta = xc->key;\n\t}\n\n\tnote = e->note;\n\tif (fnum == 0) {\n\t\tfxt = e->fxt;\n\t\tfxp = e->fxp;\n\t} else {\n\t\tfxt = e->f2t;\n\t\tfxp = e->f2p;\n\t}\n\n\tswitch (fxt) {\n\tcase FX_ARPEGGIO:\n\t      fx_arpeggio:\n\t\tif (!HAS_QUIRK(QUIRK_ARPMEM) || fxp != 0) {\n\t\t\txc->arpeggio.val[0] = 0;\n\t\t\txc->arpeggio.val[1] = MSN(fxp);\n\t\t\txc->arpeggio.val[2] = LSN(fxp);\n\t\t\txc->arpeggio.size = 3;\n\t\t}\n\t\tbreak;\n\tcase FX_S3M_ARPEGGIO:\n\t\tEFFECT_MEMORY(fxp, xc->arpeggio.memory);\n\t\tgoto fx_arpeggio;\n\n#ifndef LIBXMP_CORE_PLAYER\n\tcase FX_OKT_ARP3:\n\t\tif (fxp != 0) {\n\t\t\txc->arpeggio.val[0] = -MSN(fxp);\n\t\t\txc->arpeggio.val[1] = 0;\n\t\t\txc->arpeggio.val[2] = LSN(fxp);\n\t\t\txc->arpeggio.size = 3;\n\t\t}\n\t\tbreak;\n\tcase FX_OKT_ARP4:\n\t\tif (fxp != 0) {\n\t\t\txc->arpeggio.val[0] = 0;\n\t\t\txc->arpeggio.val[1] = LSN(fxp);\n\t\t\txc->arpeggio.val[2] = 0;\n\t\t\txc->arpeggio.val[3] = -MSN(fxp);\n\t\t\txc->arpeggio.size = 4;\n\t\t}\n\t\tbreak;\n\tcase FX_OKT_ARP5:\n\t\tif (fxp != 0) {\n\t\t\txc->arpeggio.val[0] = LSN(fxp);\n\t\t\txc->arpeggio.val[1] = LSN(fxp);\n\t\t\txc->arpeggio.val[2] = 0;\n\t\t\txc->arpeggio.size = 3;\n\t\t}\n\t\tbreak;\n#endif\n\tcase FX_PORTA_UP:\t/* Portamento up */\n\t\tEFFECT_MEMORY(fxp, xc->freq.memory);\n\n\t\tif (HAS_QUIRK(QUIRK_FINEFX)\n\t\t    && (fnum == 0 || !HAS_QUIRK(QUIRK_ITVPOR))) {\n\t\t\tswitch (MSN(fxp)) {\n\t\t\tcase 0xf:\n\t\t\t\tfxp &= 0x0f;\n\t\t\t\tgoto fx_f_porta_up;\n\t\t\tcase 0xe:\n\t\t\t\tfxp &= 0x0f;\n\t\t\t\tgoto fx_xf_porta_up;\n\t\t\t}\n\t\t}\n\n\t\tif (fxp != 0) {\n\t\t\tSET(PITCHBEND);\n\t\t\txc->freq.slide = -fxp;\n\t\t\tif (HAS_QUIRK(QUIRK_UNISLD))\n\t\t\t\txc->porta.memory = fxp;\n\t\t}\n\t\tbreak;\n\tcase FX_PORTA_DN:\t/* Portamento down */\n\t\t/* FT2 has separate up and down memory. */\n\t\tif (HAS_QUIRK(QUIRK_FT2BUGS)) {\n\t\t\tEFFECT_MEMORY(fxp, xc->freq.down_memory);\n\t\t} else {\n\t\t\tEFFECT_MEMORY(fxp, xc->freq.memory);\n\t\t}\n\n\t\tif (HAS_QUIRK(QUIRK_FINEFX)\n\t\t    && (fnum == 0 || !HAS_QUIRK(QUIRK_ITVPOR))) {\n\t\t\tswitch (MSN(fxp)) {\n\t\t\tcase 0xf:\n\t\t\t\tfxp &= 0x0f;\n\t\t\t\tgoto fx_f_porta_dn;\n\t\t\tcase 0xe:\n\t\t\t\tfxp &= 0x0f;\n\t\t\t\tgoto fx_xf_porta_dn;\n\t\t\t}\n\t\t}\n\n\t\tif (fxp != 0) {\n\t\t\tSET(PITCHBEND);\n\t\t\txc->freq.slide = fxp;\n\t\t\tif (HAS_QUIRK(QUIRK_UNISLD))\n\t\t\t\txc->porta.memory = fxp;\n\t\t}\n\t\tbreak;\n\tcase FX_TONEPORTA:\t/* Tone portamento */\n\t\tEFFECT_MEMORY_SETONLY(fxp, xc->porta.memory);\n\n\t\tif (fxp != 0) {\n\t\t\tif (HAS_QUIRK(QUIRK_UNISLD)) /* IT compatible Gxx off */\n\t\t\t\txc->freq.memory = fxp;\n\t\t\txc->porta.slide = fxp;\n\t\t}\n\n\t\tif (HAS_QUIRK(QUIRK_IGSTPOR)) {\n\t\t\tif (note == 0 && xc->porta.dir == 0)\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (!IS_VALID_INSTRUMENT(xc->ins))\n\t\t\tbreak;\n\n\t\tdo_toneporta(ctx, xc, note);\n\n\t\tSET(TONEPORTA);\n\t\tbreak;\n\n\tcase FX_VIBRATO:\t/* Vibrato */\n\t\tEFFECT_MEMORY_SETONLY(fxp, xc->vibrato.memory);\n\n\t\tSET(VIBRATO);\n\t\tSET_LFO_NOTZERO(&xc->vibrato.lfo, LSN(fxp) << 2, MSN(fxp));\n\t\tbreak;\n\tcase FX_FINE_VIBRATO:\t/* Fine vibrato */\n\t\tEFFECT_MEMORY_SETONLY(fxp, xc->vibrato.memory);\n\n\t\tSET(VIBRATO);\n\t\tSET_LFO_NOTZERO(&xc->vibrato.lfo, LSN(fxp), MSN(fxp));\n\t\tbreak;\n\n\tcase FX_TONE_VSLIDE:\t/* Toneporta + vol slide */\n\t\tif (!IS_VALID_INSTRUMENT(xc->ins))\n\t\t\tbreak;\n\t\tdo_toneporta(ctx, xc, note);\n\t\tSET(TONEPORTA);\n\t\tgoto fx_volslide;\n\tcase FX_VIBRA_VSLIDE:\t/* Vibrato + vol slide */\n\t\tSET(VIBRATO);\n\t\tgoto fx_volslide;\n\n\tcase FX_TREMOLO:\t/* Tremolo */\n\t\tEFFECT_MEMORY(fxp, xc->tremolo.memory);\n\t\tSET(TREMOLO);\n\t\tSET_LFO_NOTZERO(&xc->tremolo.lfo, LSN(fxp), MSN(fxp));\n\t\tbreak;\n\n\tcase FX_SETPAN:\t\t/* Set pan */\n\t\tif (HAS_QUIRK(QUIRK_PROTRACK)) {\n\t\t\tbreak;\n\t\t}\n\t    fx_setpan:\n\t\t/* From OpenMPT PanOff.xm:\n\t\t * \"Another chapter of weird FT2 bugs: Note-Off + Note Delay\n\t\t *  + Volume Column Panning = Panning effect is ignored.\"\n\t\t */\n\t\tif (!HAS_QUIRK(QUIRK_FT2BUGS)\t\t/* If not FT2 */\n\t\t    || fnum == 0\t\t\t/* or not vol column */\n\t\t    || e->note != XMP_KEY_OFF\t\t/* or not keyoff */\n\t\t    || e->fxt != FX_EXTENDED\t\t/* or not delay */\n\t\t    || MSN(e->fxp) != EX_DELAY) {\n\t\t\txc->pan.val = fxp;\n\t\t\txc->pan.surround = 0;\n\t\t}\n\t\txc->rpv = 0;\t/* storlek_20: set pan overrides random pan */\n\t\txc->pan.surround = 0;\n\t\tbreak;\n\tcase FX_OFFSET:\t\t/* Set sample offset */\n\t\tEFFECT_MEMORY(fxp, xc->offset.memory);\n\t\tSET(OFFSET);\n\t\tif (note) {\n\t\t\txc->offset.val &= xc->offset.val & ~0xffff;\n\t\t\txc->offset.val |= fxp << 8;\n\t\t\txc->offset.val2 = fxp << 8;\n\t\t}\n\t\tif (e->ins) {\n\t\t\txc->offset.val2 = fxp << 8;\n\t\t}\n\t\tbreak;\n\tcase FX_VOLSLIDE:\t/* Volume slide */\n\t      fx_volslide:\n\t\t/* S3M file volume slide note:\n\t\t * DFy  Fine volume down by y (...) If y is 0, the command will\n\t\t *      be treated as a volume slide up with a value of f (15).\n\t\t *      If a DFF command is specified, the volume will be slid\n\t\t *      up.\n\t\t */\n\t\tif (HAS_QUIRK(QUIRK_FINEFX)) {\n\t\t\th = MSN(fxp);\n\t\t\tl = LSN(fxp);\n\t\t\tif (l == 0xf && h != 0) {\n\t\t\t\txc->vol.memory = fxp;\n\t\t\t\tfxp >>= 4;\n\t\t\t\tgoto fx_f_vslide_up;\n\t\t\t} else if (h == 0xf && l != 0) {\n\t\t\t\txc->vol.memory = fxp;\n\t\t\t\tfxp &= 0x0f;\n\t\t\t\tgoto fx_f_vslide_dn;\n\t\t\t}\n\t\t}\n\n\t\t/* recover memory */\n\t\tif (fxp == 0x00) {\n\t\t\tif ((fxp = xc->vol.memory) != 0)\n\t\t\t\tgoto fx_volslide;\n\t\t}\n\n\t\tSET(VOL_SLIDE);\n\n\t\t/* Skaven's 2nd reality (S3M) has volslide parameter D7 => pri\n\t\t * down. Other trackers only compute volumes if the other\n\t\t * parameter is 0, Fall from sky.xm has 2C => do nothing.\n\t\t * Also don't assign xc->vol.memory if fxp is 0, see Guild\n\t\t * of Sounds.xm\n\t\t */\n\t\tif (fxp) {\n\t\t\txc->vol.memory = fxp;\n\t\t\th = MSN(fxp);\n\t\t\tl = LSN(fxp);\n\t\t\tif (fxp) {\n\t\t\t\tif (HAS_QUIRK(QUIRK_VOLPDN)) {\n\t\t\t\t\txc->vol.slide = l ? -l : h;\n\t\t\t\t} else {\n\t\t\t\t\txc->vol.slide = h ? h : -l;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/* Mirko reports that a S3M with D0F effects created with ST321\n\t\t * should process volume slides in all frames like ST300. I\n\t\t * suspect ST3/IT could be handling D0F effects like this.\n\t\t */\n\t\tif (HAS_QUIRK(QUIRK_FINEFX)) {\n\t\t\tif (MSN(xc->vol.memory) == 0xf\n\t\t\t    || LSN(xc->vol.memory) == 0xf) {\n\t\t\t\tSET(FINE_VOLS);\n\t\t\t\txc->vol.fslide = xc->vol.slide;\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase FX_VOLSLIDE_2:\t/* Secondary volume slide */\n\t\tSET(VOL_SLIDE_2);\n\t\tif (fxp) {\n\t\t\th = MSN(fxp);\n\t\t\tl = LSN(fxp);\n\t\t\txc->vol.slide2 = h ? h : -l;\n\t\t}\n\t\tbreak;\n\tcase FX_JUMP:\t\t/* Order jump */\n\t\tlibxmp_process_pattern_jump(ctx, f, fxp);\n\t\tbreak;\n\tcase FX_VOLSET:\t\t/* Volume set */\n\t\tSET(NEW_VOL);\n\t\txc->volume = fxp;\n\t\tif (xc->split) {\n\t\t\tp->xc_data[xc->pair].volume = xc->volume;\n\t\t}\n\t\tbreak;\n\tcase FX_BREAK:\t\t/* Pattern break */\n\t\tlibxmp_process_pattern_break(ctx, f, 10 * MSN(fxp) + LSN(fxp));\n\t\tbreak;\n\tcase FX_EXTENDED:\t/* Extended effect */\n\t\tEFFECT_MEMORY_S3M(fxp);\n\t\tfxt = fxp >> 4;\n\t\tfxp &= 0x0f;\n\t\tswitch (fxt) {\n\t\tcase EX_FILTER:\t\t/* Amiga led filter */\n\t\t\tif (IS_AMIGA_MOD()) {\n\t\t\t\tp->filter = !(fxp & 1);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase EX_F_PORTA_UP:\t/* Fine portamento up */\n\t\t\tEFFECT_MEMORY(fxp, xc->fine_porta.up_memory);\n\t\t\tgoto fx_f_porta_up;\n\t\tcase EX_F_PORTA_DN:\t/* Fine portamento down */\n\t\t\tEFFECT_MEMORY(fxp, xc->fine_porta.down_memory);\n\t\t\tgoto fx_f_porta_dn;\n\t\tcase EX_GLISS:\t\t/* Glissando toggle */\n\t\t\tif (fxp) {\n\t\t\t\tSET_NOTE(NOTE_GLISSANDO);\n\t\t\t} else {\n\t\t\t\tRESET_NOTE(NOTE_GLISSANDO);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase EX_VIBRATO_WF:\t/* Set vibrato waveform */\n\t\t\tfxp &= 3;\n\t\t\tlibxmp_lfo_set_waveform(&xc->vibrato.lfo, fxp);\n\t\t\tbreak;\n\t\tcase EX_FINETUNE:\t/* Set finetune */\n\t\t\tif (!HAS_QUIRK(QUIRK_FT2BUGS) || note > 0) {\n\t\t\t\txc->finetune = (int8)(fxp << 4);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase EX_PATTERN_LOOP:\t/* Loop pattern */\n\t\t\tlibxmp_process_pattern_loop(ctx, f, chn, p->row, fxp);\n\t\t\tbreak;\n\t\tcase EX_TREMOLO_WF:\t/* Set tremolo waveform */\n\t\t\tlibxmp_lfo_set_waveform(&xc->tremolo.lfo, fxp & 3);\n\t\t\tbreak;\n\t\tcase EX_SETPAN:\n\t\t\tfxp <<= 4;\n\t\t\tgoto fx_setpan;\n\t\tcase EX_RETRIG:\t\t/* Retrig note */\n#ifndef LIBXMP_CORE_PLAYER\n\t\t    fx_retrig:\n#endif\n\t\t\tSET(RETRIG);\n\t\t\txc->retrig.val = fxp;\n\t\t\txc->retrig.count = fxp + 1;\n\t\t\txc->retrig.type = 0;\n\t\t\txc->retrig.limit = HAS_QUIRK(QUIRK_RTONCE) ? 1 : 0;\n\t\t\tbreak;\n\t\tcase EX_F_VSLIDE_UP:\t/* Fine volume slide up */\n\t\t\tEFFECT_MEMORY(fxp, xc->fine_vol.up_memory);\n\t\t\tgoto fx_f_vslide_up;\n\t\tcase EX_F_VSLIDE_DN:\t/* Fine volume slide down */\n\t\t\tEFFECT_MEMORY(fxp, xc->fine_vol.down_memory);\n\t\t\tgoto fx_f_vslide_dn;\n\t\tcase EX_CUT:\t\t/* Cut note */\n\t\t\tSET(RETRIG);\n\t\t\tSET_NOTE(NOTE_CUT);\t/* for IT cut-carry */\n\t\t\txc->retrig.val = fxp + 1;\n\t\t\txc->retrig.count = xc->retrig.val;\n\t\t\txc->retrig.type = 0x10;\n\t\t\tbreak;\n\t\tcase EX_DELAY:\t\t/* Note delay */\n\t\t\t/* computed at frame loop */\n\t\t\tbreak;\n\t\tcase EX_PATT_DELAY:\t/* Pattern delay */\n\t\t\tgoto fx_patt_delay;\n\t\tcase EX_INVLOOP:\t/* Invert loop / funk repeat */\n\t\t\txc->invloop.speed = fxp;\n\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\tcase FX_SPEED:\t\t/* Set speed */\n\t\tif (HAS_QUIRK(QUIRK_NOBPM) || p->flags & XMP_FLAGS_VBLANK) {\n\t\t\tgoto fx_s3m_speed;\n\t\t}\n\n\t\t/* speedup.xm needs BPM = 20 */\n\t\tif (fxp < 0x20) {\n\t\t\tgoto fx_s3m_speed;\n\t\t}\n\t\tgoto fx_s3m_bpm;\n\n\tcase FX_FINETUNE:\n\t\txc->finetune = (int16) (fxp - 0x80);\n\t\tbreak;\n\n\tcase FX_F_VSLIDE_UP:\t/* Fine volume slide up */\n\t\tEFFECT_MEMORY(fxp, xc->fine_vol.up_memory);\n\t    fx_f_vslide_up:\n\t\tSET(FINE_VOLS);\n\t\txc->vol.fslide = fxp;\n\t\tbreak;\n\tcase FX_F_VSLIDE_DN:\t/* Fine volume slide down */\n\t\tEFFECT_MEMORY(fxp, xc->fine_vol.up_memory);\n\t    fx_f_vslide_dn:\n\t\tSET(FINE_VOLS);\n\t\txc->vol.fslide = -fxp;\n\t\tbreak;\n\n\tcase FX_F_PORTA_UP:\t/* Fine portamento up */\n\t    fx_f_porta_up:\n\t\tif (fxp) {\n\t\t\tSET(FINE_BEND);\n\t\t\txc->freq.fslide = -fxp;\n\t\t}\n\t\tbreak;\n\tcase FX_F_PORTA_DN:\t/* Fine portamento down */\n\t    fx_f_porta_dn:\n\t\tif (fxp) {\n\t\t\tSET(FINE_BEND);\n\t\t\txc->freq.fslide = fxp;\n\t\t}\n\t\tbreak;\n\tcase FX_PATT_DELAY:\n\t    fx_patt_delay:\n\t\tif (m->read_event_type != READ_EVENT_ST3 || !p->flow.delay) {\n\t\t\tp->flow.delay = fxp;\n\t\t}\n\t\tbreak;\n\n\tcase FX_S3M_SPEED:\t/* Set S3M speed */\n\t\tEFFECT_MEMORY_S3M(fxp);\n\t    fx_s3m_speed:\n\t\tif (fxp) {\n\t\t\tp->speed = fxp;\n#ifndef LIBXMP_CORE_PLAYER\n\t\t\tp->st26_speed = 0;\n#endif\n\t\t}\n\t\tbreak;\n\tcase FX_S3M_BPM:\t/* Set S3M BPM */\n\t    fx_s3m_bpm: {\n\t\t/* Lower time factor in MED allows lower BPM values */\n\t\tint min_bpm = (int)(0.5 + m->time_factor * XMP_MIN_BPM / 10);\n\t\tif (fxp < min_bpm)\n\t\t\tfxp = min_bpm;\n\t\tp->bpm = fxp;\n\t\tbreak;\n\t}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tcase FX_IT_BPM:\t\t/* Set IT BPM */\n\t\tif (MSN(fxp) == 0) {\n\t\t\tSET(TEMPO_SLIDE);\n\t\t\tif (LSN(fxp))\t/* T0x - Tempo slide down by x */\n\t\t\t\txc->tempo.slide = -LSN(fxp);\n\t\t\t/* T00 - Repeat previous slide */\n\t\t} else if (MSN(fxp) == 1) {\t/* T1x - Tempo slide up by x */\n\t\t\tSET(TEMPO_SLIDE);\n\t\t\txc->tempo.slide = LSN(fxp);\n\t\t} else {\n\t\t\tif (fxp < XMP_MIN_BPM)\n\t\t\t\tfxp = XMP_MIN_BPM;\n\t\t\tp->bpm = fxp;\n\t\t}\n\t\tbreak;\n\tcase FX_IT_ROWDELAY:\n\t\tif (!f->rowdelay_set) {\n\t\t\tf->rowdelay = fxp;\n\t\t\tf->rowdelay_set = ROWDELAY_ON | ROWDELAY_FIRST_FRAME;\n\t\t}\n\t\tbreak;\n\n\t/* From the OpenMPT VolColMemory.it test case:\n\t * \"Volume column commands a, b, c and d (volume slide) share one\n\t *  effect memory, but it should not be shared with Dxy in the effect\n\t *  column.\n\t */\n\tcase FX_VSLIDE_UP_2:\t/* Fine volume slide up */\n\t\tEFFECT_MEMORY(fxp, xc->vol.memory2);\n\t\tSET(VOL_SLIDE_2);\n\t\txc->vol.slide2 = fxp;\n\t\tbreak;\n\tcase FX_VSLIDE_DN_2:\t/* Fine volume slide down */\n\t\tEFFECT_MEMORY(fxp, xc->vol.memory2);\n\t\tSET(VOL_SLIDE_2);\n\t\txc->vol.slide2 = -fxp;\n\t\tbreak;\n\tcase FX_F_VSLIDE_UP_2:\t/* Fine volume slide up */\n\t\tEFFECT_MEMORY(fxp, xc->vol.memory2);\n\t\tSET(FINE_VOLS_2);\n\t\txc->vol.fslide2 = fxp;\n\t\tbreak;\n\tcase FX_F_VSLIDE_DN_2:\t/* Fine volume slide down */\n\t\tEFFECT_MEMORY(fxp, xc->vol.memory2);\n\t\tSET(FINE_VOLS_2);\n\t\txc->vol.fslide2 = -fxp;\n\t\tbreak;\n\tcase FX_IT_BREAK:\t/* Pattern break with hex parameter */\n\t\tlibxmp_process_pattern_break(ctx, f, fxp);\n\t\tbreak;\n\n#endif\n\n\tcase FX_GLOBALVOL:\t/* Set global volume */\n\t\tif (fxp > m->gvolbase) {\n\t\t\tp->gvol = m->gvolbase;\n\t\t} else {\n\t\t\tp->gvol = fxp;\n\t\t}\n\t\tbreak;\n\tcase FX_GVOL_SLIDE:\t/* Global volume slide */\n            fx_gvolslide:\n\t\tif (fxp) {\n\t\t\tSET(GVOL_SLIDE);\n\t\t\txc->gvol.memory = fxp;\n\t\t\th = MSN(fxp);\n\t\t\tl = LSN(fxp);\n\n\t\t\tif (HAS_QUIRK(QUIRK_FINEFX)) {\n\t\t\t\tif (l == 0xf && h != 0) {\n\t\t\t\t\txc->gvol.slide = 0;\n\t\t\t\t\txc->gvol.fslide = h;\n\t\t\t\t} else if (h == 0xf && l != 0) {\n\t\t\t\t\txc->gvol.slide = 0;\n\t\t\t\t\txc->gvol.fslide = -l;\n\t\t\t\t} else {\n\t\t\t\t\txc->gvol.slide = h ? h : -l;\n\t\t\t\t\txc->gvol.fslide = 0;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\txc->gvol.slide = h ? h : -l;\n\t\t\t\txc->gvol.fslide = 0;\n\t\t\t}\n\t\t} else {\n\t\t\tif ((fxp = xc->gvol.memory) != 0) {\n\t\t\t\tgoto fx_gvolslide;\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase FX_KEYOFF:\t\t/* Key off */\n\t\txc->keyoff = fxp + 1;\n\t\tbreak;\n\tcase FX_ENVPOS:\t\t/* Set envelope position */\n\t\t/* From OpenMPT SetEnvPos.xm:\n\t\t * \"When using the Lxx effect, Fasttracker 2 only sets the\n\t\t *  panning envelope position if the volume envelope’s sustain\n\t\t *  flag is set.\n\t\t */\n\t\tif (HAS_QUIRK(QUIRK_FT2BUGS)) {\n\t\t\tstruct xmp_instrument *instrument;\n\t\t\tinstrument = libxmp_get_instrument(ctx, xc->ins);\n\t\t\tif (instrument != NULL) {\n\t\t\t\tif (instrument->aei.flg & XMP_ENVELOPE_SUS) {\n\t\t\t\t\txc->p_idx = fxp;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\txc->p_idx = fxp;\n\t\t}\n\t\txc->v_idx = fxp;\n\t\txc->f_idx = fxp;\n\t\tbreak;\n\tcase FX_PANSLIDE:\t/* Pan slide (XM) */\n\t\tEFFECT_MEMORY(fxp, xc->pan.memory);\n\t\tSET(PAN_SLIDE);\n\t\txc->pan.slide = LSN(fxp) - MSN(fxp);\n\t\tbreak;\n\tcase FX_PANSL_NOMEM:\t/* Pan slide (XM volume column) */\n\t\tSET(PAN_SLIDE);\n\t\txc->pan.slide = LSN(fxp) - MSN(fxp);\n\t\tbreak;\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tcase FX_IT_PANSLIDE:\t/* Pan slide w/ fine pan (IT) */\n\t\tSET(PAN_SLIDE);\n\t\tif (fxp) {\n\t\t\tif (MSN(fxp) == 0xf) {\n\t\t\t\txc->pan.slide = 0;\n\t\t\t\txc->pan.fslide = LSN(fxp);\n\t\t\t} else if (LSN(fxp) == 0xf) {\n\t\t\t\txc->pan.slide = 0;\n\t\t\t\txc->pan.fslide = -MSN(fxp);\n\t\t\t} else {\n\t\t\t\tSET(PAN_SLIDE);\n\t\t\t\txc->pan.slide = LSN(fxp) - MSN(fxp);\n\t\t\t\txc->pan.fslide = 0;\n\t\t\t}\n\t\t}\n\t\tbreak;\n#endif\n\n\tcase FX_MULTI_RETRIG:\t/* Multi retrig */\n\t\tEFFECT_MEMORY_S3M(fxp);\n\t\tif (fxp) {\n\t\t\txc->retrig.val = LSN(fxp);\n\t\t\txc->retrig.type = MSN(fxp);\n\t\t}\n\t\tif (note) {\n\t\t\txc->retrig.count = xc->retrig.val + 1;\n\t\t}\n\t\txc->retrig.limit = 0;\n\t\tSET(RETRIG);\n\t\tbreak;\n\tcase FX_TREMOR:\t\t\t/* Tremor */\n\t\tEFFECT_MEMORY(fxp, xc->tremor.memory);\n\t\txc->tremor.up = MSN(fxp);\n\t\txc->tremor.down = LSN(fxp);\n\t\tif (IS_PLAYER_MODE_FT2()) {\n\t\t\txc->tremor.count |= 0x80;\n\t\t} else {\n\t\t\tif (xc->tremor.up == 0) {\n\t\t\t\txc->tremor.up++;\n\t\t\t}\n\t\t\tif (xc->tremor.down == 0) {\n\t\t\t\txc->tremor.down++;\n\t\t\t}\n\t\t}\n\t\tSET(TREMOR);\n\t\tbreak;\n\tcase FX_XF_PORTA:\t/* Extra fine portamento */\n\t\th = MSN(fxp);\n\t\tfxp &= 0x0f;\n\t\tswitch (h) {\n\t\tcase XX_XF_PORTA_UP:\t/* Extra fine portamento up */\n\t\t\tEFFECT_MEMORY(fxp, xc->fine_porta.xf_up_memory);\n\t\t      fx_xf_porta_up:\n\t\t\tSET(FINE_BEND);\n\t\t\txc->freq.fslide = -0.25 * fxp;\n\t\t\tbreak;\n\t\tcase XX_XF_PORTA_DN:\t/* Extra fine portamento down */\n\t\t\tEFFECT_MEMORY(fxp, xc->fine_porta.xf_down_memory);\n\t\t      fx_xf_porta_dn:\n\t\t\tSET(FINE_BEND);\n\t\t\txc->freq.fslide = 0.25 * fxp;\n\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\tcase FX_SURROUND:\n\t\txc->pan.surround = fxp;\n\t\tbreak;\n\tcase FX_REVERSE:\t/* Play forward/backward */\n\t\tlibxmp_virt_reverse(ctx, chn, fxp);\n\t\tbreak;\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tcase FX_TRK_VOL:\t/* Track volume setting */\n\t\tif (fxp <= m->volbase) {\n\t\t\txc->mastervol = fxp;\n\t\t}\n\t\tbreak;\n\tcase FX_TRK_VSLIDE:\t/* Track volume slide */\n\t\tif (fxp == 0) {\n\t\t\tif ((fxp = xc->trackvol.memory) == 0)\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (HAS_QUIRK(QUIRK_FINEFX)) {\n\t\t\th = MSN(fxp);\n\t\t\tl = LSN(fxp);\n\t\t\tif (h == 0xf && l != 0) {\n\t\t\t\txc->trackvol.memory = fxp;\n\t\t\t\tfxp &= 0x0f;\n\t\t\t\tgoto fx_trk_fvslide;\n\t\t\t} else if (l == 0xf && h != 0) {\n\t\t\t\txc->trackvol.memory = fxp;\n\t\t\t\tfxp &= 0xf0;\n\t\t\t\tgoto fx_trk_fvslide;\n\t\t\t}\n\t\t}\n\n\t\tSET(TRK_VSLIDE);\n\t\tif (fxp) {\n\t\t\th = MSN(fxp);\n\t\t\tl = LSN(fxp);\n\n\t\t\txc->trackvol.memory = fxp;\n\t\t\tif (HAS_QUIRK(QUIRK_VOLPDN)) {\n\t\t\t\txc->trackvol.slide = l ? -l : h;\n\t\t\t} else {\n\t\t\t\txc->trackvol.slide = h ? h : -l;\n\t\t\t}\n\t\t}\n\n\t\tbreak;\n\tcase FX_TRK_FVSLIDE:\t/* Track fine volume slide */\n\t      fx_trk_fvslide:\n\t\tSET(TRK_FVSLIDE);\n\t\tif (fxp) {\n\t\t\txc->trackvol.fslide = MSN(fxp) - LSN(fxp);\n\t\t}\n\t\tbreak;\n\n\tcase FX_IT_INSTFUNC:\n\t\tswitch (fxp) {\n\t\tcase 0:\t/* Past note cut */\n\t\t\tlibxmp_virt_pastnote(ctx, chn, VIRT_ACTION_CUT);\n\t\t\tbreak;\n\t\tcase 1:\t/* Past note off */\n\t\t\tlibxmp_virt_pastnote(ctx, chn, VIRT_ACTION_OFF);\n\t\t\tbreak;\n\t\tcase 2:\t/* Past note fade */\n\t\t\tlibxmp_virt_pastnote(ctx, chn, VIRT_ACTION_FADE);\n\t\t\tbreak;\n\t\tcase 3:\t/* Set NNA to note cut */\n\t\t\tlibxmp_virt_setnna(ctx, chn, XMP_INST_NNA_CUT);\n\t\t\tbreak;\n\t\tcase 4:\t/* Set NNA to continue */\n\t\t\tlibxmp_virt_setnna(ctx, chn, XMP_INST_NNA_CONT);\n\t\t\tbreak;\n\t\tcase 5:\t/* Set NNA to note off */\n\t\t\tlibxmp_virt_setnna(ctx, chn, XMP_INST_NNA_OFF);\n\t\t\tbreak;\n\t\tcase 6:\t/* Set NNA to note fade */\n\t\t\tlibxmp_virt_setnna(ctx, chn, XMP_INST_NNA_FADE);\n\t\t\tbreak;\n\t\tcase 7:\t/* Turn off volume envelope */\n\t\t\tSET_PER(VENV_PAUSE);\n\t\t\tbreak;\n\t\tcase 8:\t/* Turn on volume envelope */\n\t\t\tRESET_PER(VENV_PAUSE);\n\t\t\tbreak;\n\t\tcase 9:\t/* Turn off pan envelope */\n\t\t\tSET_PER(PENV_PAUSE);\n\t\t\tbreak;\n\t\tcase 0xa:\t/* Turn on pan envelope */\n\t\t\tRESET_PER(PENV_PAUSE);\n\t\t\tbreak;\n\t\tcase 0xb:\t/* Turn off pitch envelope */\n\t\t\tSET_PER(FENV_PAUSE);\n\t\t\tbreak;\n\t\tcase 0xc:\t/* Turn on pitch envelope */\n\t\t\tRESET_PER(FENV_PAUSE);\n\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\tcase FX_FLT_CUTOFF:\n\t\txc->filter.cutoff = fxp;\n\t\tbreak;\n\tcase FX_FLT_RESN:\n\t\txc->filter.resonance = fxp;\n\t\tbreak;\n\tcase FX_MACRO_SET:\n\t\txc->macro.active = LSN(fxp);\n\t\tbreak;\n\tcase FX_MACRO:\n\t\tSET(MIDI_MACRO);\n\t\txc->macro.val = fxp;\n\t\txc->macro.slide = 0;\n\t\tbreak;\n\tcase FX_MACROSMOOTH:\n\t\tif (ctx->p.speed && xc->macro.val < 0x80) {\n\t\t\tSET(MIDI_MACRO);\n\t\t\txc->macro.target = fxp;\n\t\t\txc->macro.slide = ((float)fxp - xc->macro.val) / ctx->p.speed;\n\t\t}\n\t\tbreak;\n\tcase FX_PANBRELLO:\t/* Panbrello */\n\t\tSET(PANBRELLO);\n\t\tSET_LFO_NOTZERO(&xc->panbrello.lfo, LSN(fxp) << 4, MSN(fxp));\n\t\tbreak;\n\tcase FX_PANBRELLO_WF:\t/* Panbrello waveform */\n\t\tlibxmp_lfo_set_waveform(&xc->panbrello.lfo, fxp & 3);\n\t\tbreak;\n\tcase FX_HIOFFSET:\t/* High offset */\n\t\txc->offset.val &= 0xffff;\n\t\txc->offset.val |= fxp << 16;\n\t\tbreak;\n#endif\n\n#ifndef LIBXMP_CORE_PLAYER\n\n\t/* SFX effects */\n\tcase FX_VOL_ADD:\n\t\tif (!IS_VALID_INSTRUMENT(xc->ins)) {\n\t\t\tbreak;\n\t\t}\n\t\tSET(NEW_VOL);\n\t\txc->volume = m->mod.xxi[xc->ins].sub[0].vol + fxp;\n\t\tif (xc->volume > m->volbase) {\n\t\t\txc->volume = m->volbase;\n\t\t}\n\t\tbreak;\n\tcase FX_VOL_SUB:\n\t\tif (!IS_VALID_INSTRUMENT(xc->ins)) {\n\t\t\tbreak;\n\t\t}\n\t\tSET(NEW_VOL);\n\t\txc->volume = m->mod.xxi[xc->ins].sub[0].vol - fxp;\n\t\tif (xc->volume < 0) {\n\t\t\txc->volume =0;\n\t\t}\n\t\tbreak;\n\tcase FX_PITCH_ADD:\n\t\tSET_PER(TONEPORTA);\n\t\txc->porta.target = libxmp_note_to_period(ctx, note - 1, xc->finetune, 0)\n\t\t\t+ fxp;\n\t\txc->porta.slide = 2;\n\t\txc->porta.dir = 1;\n\t\tbreak;\n\tcase FX_PITCH_SUB:\n\t\tSET_PER(TONEPORTA);\n\t\txc->porta.target = libxmp_note_to_period(ctx, note - 1, xc->finetune, 0)\n\t\t\t- fxp;\n\t\txc->porta.slide = 2;\n\t\txc->porta.dir = -1;\n\t\tbreak;\n\n\t/* Saga Musix says:\n\t *\n\t * \"When both nibbles of an Fxx command are set, SoundTracker 2.6\n\t * applies the both values alternatingly, first the high nibble,\n\t * then the low nibble on the next row, then the high nibble again...\n\t * If only the high nibble is set, it should act like if only the low\n\t * nibble is set (i.e. F30 is the same as F03).\n\t */\n\tcase FX_ICE_SPEED:\n\t\tif (fxp) {\n\t\t\tif (LSN(fxp)) {\n\t\t\t\tp->st26_speed = (MSN(fxp) << 8) | LSN(fxp);\n\t\t\t} else {\n\t\t\t\tp->st26_speed = MSN(fxp);\n\t\t\t}\n\t\t}\n\t\tbreak;\n\n\tcase FX_VOLSLIDE_UP:\t/* Vol slide with uint8 arg */\n\t\tif (HAS_QUIRK(QUIRK_FINEFX)) {\n\t\t\th = MSN(fxp);\n\t\t\tl = LSN(fxp);\n\t\t\tif (h == 0xf && l != 0) {\n\t\t\t\tfxp &= 0x0f;\n\t\t\t\tgoto fx_f_vslide_up;\n\t\t\t}\n\t\t}\n\n\t\tif (fxp)\n\t\t\txc->vol.slide = fxp;\n\t\tSET(VOL_SLIDE);\n\t\tbreak;\n\tcase FX_VOLSLIDE_DN:\t/* Vol slide with uint8 arg */\n\t\tif (HAS_QUIRK(QUIRK_FINEFX)) {\n\t\t\th = MSN(fxp);\n\t\t\tl = LSN(fxp);\n\t\t\tif (h == 0xf && l != 0) {\n\t\t\t\tfxp &= 0x0f;\n\t\t\t\tgoto fx_f_vslide_dn;\n\t\t\t}\n\t\t}\n\n\t\tif (fxp)\n\t\t\txc->vol.slide = -fxp;\n\t\tSET(VOL_SLIDE);\n\t\tbreak;\n\tcase FX_F_VSLIDE:\t/* Fine volume slide */\n\t\tSET(FINE_VOLS);\n\t\tif (fxp) {\n\t\t\th = MSN(fxp);\n\t\t\tl = LSN(fxp);\n\t\t\txc->vol.fslide = h ? h : -l;\n\t\t}\n\t\tbreak;\n\tcase FX_NSLIDE_DN:\n\tcase FX_NSLIDE_UP:\n\tcase FX_NSLIDE_R_DN:\n\tcase FX_NSLIDE_R_UP:\n\t\tif (fxp != 0) {\n\t\t\tif (fxt == FX_NSLIDE_R_DN || fxt == FX_NSLIDE_R_UP) {\n\t\t\t\txc->retrig.val = MSN(fxp);\n\t\t\t\txc->retrig.count = MSN(fxp) + 1;\n\t\t\t\txc->retrig.type = 0;\n\t\t\t\txc->retrig.limit = 0;\n\t\t\t}\n\n\t\t\tif (fxt == FX_NSLIDE_UP || fxt == FX_NSLIDE_R_UP)\n\t\t\t\txc->noteslide.slide = LSN(fxp);\n\t\t\telse\n\t\t\t\txc->noteslide.slide = -LSN(fxp);\n\n\t\t\txc->noteslide.count = xc->noteslide.speed = MSN(fxp);\n\t\t}\n\t\tif (fxt == FX_NSLIDE_R_DN || fxt == FX_NSLIDE_R_UP)\n\t\t\tSET(RETRIG);\n\t\tSET(NOTE_SLIDE);\n\t\tbreak;\n\tcase FX_NSLIDE2_DN:\n\t\tSET(NOTE_SLIDE);\n\t\txc->noteslide.slide = -fxp;\n\t\txc->noteslide.count = xc->noteslide.speed = 1;\n\t\tbreak;\n\tcase FX_NSLIDE2_UP:\n\t\tSET(NOTE_SLIDE);\n\t\txc->noteslide.slide = fxp;\n\t\txc->noteslide.count = xc->noteslide.speed = 1;\n\t\tbreak;\n\tcase FX_F_NSLIDE_DN:\n\t\tSET(FINE_NSLIDE);\n\t\txc->noteslide.fslide = -fxp;\n\t\tbreak;\n\tcase FX_F_NSLIDE_UP:\n\t\tSET(FINE_NSLIDE);\n\t\txc->noteslide.fslide = fxp;\n\t\tbreak;\n\n\tcase FX_PER_VIBRATO:\t/* Persistent vibrato */\n\t\tif (LSN(fxp) != 0) {\n\t\t\tSET_PER(VIBRATO);\n\t\t} else {\n\t\t\tRESET_PER(VIBRATO);\n\t\t}\n\t\tSET_LFO_NOTZERO(&xc->vibrato.lfo, LSN(fxp) << 2, MSN(fxp));\n\t\tbreak;\n\tcase FX_PER_PORTA_UP:\t/* Persistent portamento up */\n\t\tSET_PER(PITCHBEND);\n\t\txc->freq.slide = -fxp;\n\t\tif ((xc->freq.memory = fxp) == 0)\n\t\t\tRESET_PER(PITCHBEND);\n\t\tbreak;\n\tcase FX_PER_PORTA_DN:\t/* Persistent portamento down */\n\t\tSET_PER(PITCHBEND);\n\t\txc->freq.slide = fxp;\n\t\tif ((xc->freq.memory = fxp) == 0)\n\t\t\tRESET_PER(PITCHBEND);\n\t\tbreak;\n\tcase FX_PER_TPORTA:\t/* Persistent tone portamento */\n\t\tif (!IS_VALID_INSTRUMENT(xc->ins))\n\t\t\tbreak;\n\t\tSET_PER(TONEPORTA);\n\t\tdo_toneporta(ctx, xc, note);\n\t\txc->porta.slide = fxp;\n\t\tif (fxp == 0)\n\t\t\tRESET_PER(TONEPORTA);\n\t\tbreak;\n\tcase FX_PER_VSLD_UP:\t/* Persistent volslide up */\n\t\tSET_PER(VOL_SLIDE);\n\t\txc->vol.slide = fxp;\n\t\tif (fxp == 0)\n\t\t\tRESET_PER(VOL_SLIDE);\n\t\tbreak;\n\tcase FX_PER_VSLD_DN:\t/* Persistent volslide down */\n\t\tSET_PER(VOL_SLIDE);\n\t\txc->vol.slide = -fxp;\n\t\tif (fxp == 0)\n\t\t\tRESET_PER(VOL_SLIDE);\n\t\tbreak;\n\tcase FX_VIBRATO2:\t/* Deep vibrato (2x) */\n\t\tSET(VIBRATO);\n\t\tSET_LFO_NOTZERO(&xc->vibrato.lfo, LSN(fxp) << 3, MSN(fxp));\n\t\tbreak;\n\tcase FX_MED_RETRIG:\t/* MED 1Fxy delay x, then retrig every y */\n\t\t/* initial delay is computed at frame loop */\n\t\tSET(RETRIG);\n\t\txc->retrig.val = LSN(fxp);\n\t\txc->retrig.count = LSN(fxp) + 1;\n\t\txc->retrig.type = 0;\n\t\txc->retrig.limit = 0;\n\t\tbreak;\n\tcase FX_SPEED_CP:\t/* Set speed and ... */\n\t\tif (fxp) {\n\t\t\tp->speed = fxp;\n\t\t\tp->st26_speed = 0;\n\t\t}\n\t\t/* fall through */\n\tcase FX_PER_CANCEL:\t/* Cancel persistent effects */\n\t\txc->per_flags = 0;\n\t\tbreak;\n\n\t/* 669 effects */\n\n\tcase FX_669_PORTA_UP:\t/* 669 portamento up */\n\t\tSET_PER(PITCHBEND);\n\t\txc->freq.slide = 80 * fxp;\n\t\tif ((xc->freq.memory = fxp) == 0)\n\t\t\tRESET_PER(PITCHBEND);\n\t\tbreak;\n\tcase FX_669_PORTA_DN:\t/* 669 portamento down */\n\t\tSET_PER(PITCHBEND);\n\t\txc->freq.slide = -80 * fxp;\n\t\tif ((xc->freq.memory = fxp) == 0)\n\t\t\tRESET_PER(PITCHBEND);\n\t\tbreak;\n\tcase FX_669_TPORTA:\t/* 669 tone portamento */\n\t\tif (!IS_VALID_INSTRUMENT(xc->ins))\n\t\t\tbreak;\n\t\tSET_PER(TONEPORTA);\n\t\tdo_toneporta(ctx, xc, note);\n\t\txc->porta.slide = 40 * fxp;\n\t\tif (fxp == 0)\n\t\t\tRESET_PER(TONEPORTA);\n\t\tbreak;\n\tcase FX_669_FINETUNE:\t/* 669 finetune */\n\t\txc->finetune = 80 * (int8)fxp;\n\t\tbreak;\n\tcase FX_669_VIBRATO:\t/* 669 vibrato */\n\t\tif (LSN(fxp) != 0) {\n\t\t\tlibxmp_lfo_set_waveform(&xc->vibrato.lfo, 669);\n\t\t\tSET_PER(VIBRATO);\n\t\t} else {\n\t\t\tRESET_PER(VIBRATO);\n\t\t}\n\t\tSET_LFO_NOTZERO(&xc->vibrato.lfo, 669, 1);\n\t\tbreak;\n\n\t/* ULT effects */\n\n\tcase FX_ULT_TEMPO:\t/* ULT tempo */\n\t\t/* Has unusual semantics and is hard to split into multiple\n\t\t * effects, due to ULT's two effects lanes per channel:\n\t\t *\n\t\t * 00:    reset both speed and BPM to the default 6/125.\n\t\t * 01-2f: set speed\n\t\t * 30-ff: set BPM (CIA compatible)\n\t\t */\n\t\tif (fxp == 0) {\n\t\t\tp->speed = 6;\n\t\t\tp->st26_speed = 0;\n\t\t\tfxp = 125;\n\t\t} else if (fxp < 0x30) {\n\t\t\tgoto fx_s3m_speed;\n\t\t}\n\t\tgoto fx_s3m_bpm;\n\tcase FX_ULT_TPORTA:\t/* ULT tone portamento */\n\t\t/* Like normal persistent tone portamento, except:\n\t\t *\n\t\t * 1) Despite the documentation claiming 300 cancels tone\n\t\t * portamento, it actually reuses the last parameter.\n\t\t *\n\t\t * 2) A 3xx without a note will reuse the last target note.\n\t\t */\n\t\tif (!IS_VALID_INSTRUMENT(xc->ins))\n\t\t\tbreak;\n\t\tSET_PER(TONEPORTA);\n\t\tEFFECT_MEMORY(fxp, xc->porta.memory);\n\t\tEFFECT_MEMORY(note, xc->porta.note_memory);\n\t\tdo_toneporta(ctx, xc, note);\n\t\txc->porta.slide = fxp;\n\t\tif (fxp == 0)\n\t\t\tRESET_PER(TONEPORTA);\n\t\tbreak;\n\n\t/* Archimedes (!Tracker, Digital Symphony, et al.) effects */\n\n\tcase FX_LINE_JUMP:\t/* !Tracker and Digital Symphony \"Line Jump\" */\n\t\tlibxmp_process_line_jump(ctx, f, p->ord, fxp);\n\t\tbreak;\n\tcase FX_RETRIG:\t\t/* Retrigger with extended range */\n\t\tgoto fx_retrig;\n#endif\n\n\tdefault:\n#ifndef LIBXMP_CORE_PLAYER\n\t\tlibxmp_extras_process_fx(ctx, xc, chn, note, fxt, fxp, fnum);\n#endif\n\t\tbreak;\n\t}\n}\n"
  },
  {
    "path": "contrib/libxmp/src/effects.h",
    "content": "#ifndef LIBXMP_EFFECTS_H\n#define LIBXMP_EFFECTS_H\n\n/* Protracker effects */\n#define FX_ARPEGGIO\t0x00\n#define FX_PORTA_UP\t0x01\n#define FX_PORTA_DN\t0x02\n#define FX_TONEPORTA\t0x03\n#define FX_VIBRATO\t0x04\n#define FX_TONE_VSLIDE  0x05\n#define FX_VIBRA_VSLIDE\t0x06\n#define FX_TREMOLO\t0x07\n#define FX_OFFSET\t0x09\n#define FX_VOLSLIDE\t0x0a\n#define FX_JUMP\t\t0x0b\n#define FX_VOLSET\t0x0c\n#define FX_BREAK\t0x0d\n#define FX_EXTENDED\t0x0e\n#define FX_SPEED\t0x0f\n\n/* Fast tracker effects */\n#define FX_SETPAN\t0x08\n\n/* Fast Tracker II effects */\n#define FX_GLOBALVOL\t0x10\n#define FX_GVOL_SLIDE\t0x11\n#define FX_KEYOFF\t0x14\n#define FX_ENVPOS\t0x15\n#define FX_PANSLIDE\t0x19\n#define FX_MULTI_RETRIG\t0x1b\n#define FX_TREMOR\t0x1d\n#define FX_XF_PORTA\t0x21\n\n/* Protracker extended effects */\n#define EX_FILTER\t0x00\n#define EX_F_PORTA_UP\t0x01\n#define EX_F_PORTA_DN\t0x02\n#define EX_GLISS\t0x03\n#define EX_VIBRATO_WF\t0x04\n#define EX_FINETUNE\t0x05\n#define EX_PATTERN_LOOP\t0x06\n#define EX_TREMOLO_WF\t0x07\n#define EX_SETPAN\t0x08\n#define EX_RETRIG\t0x09\n#define EX_F_VSLIDE_UP\t0x0a\n#define EX_F_VSLIDE_DN\t0x0b\n#define EX_CUT\t\t0x0c\n#define EX_DELAY\t0x0d\n#define EX_PATT_DELAY\t0x0e\n#define EX_INVLOOP\t0x0f\n\n/* XM extended effects 2 */\n#define XX_XF_PORTA_UP\t0x01\n#define XX_XF_PORTA_DN\t0x02\n\n#ifndef LIBXMP_CORE_PLAYER\n/* Oktalyzer effects */\n#define FX_OKT_ARP3\t0x70\n#define FX_OKT_ARP4\t0x71\n#define FX_OKT_ARP5\t0x72\n#define FX_NSLIDE2_DN\t0x73\n#define FX_NSLIDE2_UP\t0x74\n#define FX_F_NSLIDE_DN\t0x75\n#define FX_F_NSLIDE_UP\t0x76\n\n/* Persistent effects -- for FNK */\n#define FX_PER_PORTA_DN\t0x78\n#define FX_PER_PORTA_UP\t0x79\n#define FX_PER_TPORTA\t0x7a\n#define FX_PER_VIBRATO\t0x7b\n#define FX_PER_VSLD_UP\t0x7c\n#define FX_PER_VSLD_DN\t0x7d\n#define FX_SPEED_CP\t0x7e\n#define FX_PER_CANCEL\t0x7f\n\n/* 669 frequency based effects */\n#define FX_669_PORTA_UP\t0x60\n#define FX_669_PORTA_DN\t0x61\n#define FX_669_TPORTA\t0x62\n#define FX_669_FINETUNE\t0x63\n#define FX_669_VIBRATO\t0x64\n\n/* FAR effects */\n#define FX_FAR_PORTA_UP\t0x65\t/* FAR pitch offset up */\n#define FX_FAR_PORTA_DN\t0x66\t/* FAR pitch offset down */\n#define FX_FAR_TPORTA\t0x67\t/* FAR persistent tone portamento */\n#define FX_FAR_TEMPO\t0x68\t/* FAR coarse tempo and tempo mode */\n#define FX_FAR_F_TEMPO\t0x69\t/* FAR fine tempo slide up/down */\n#define FX_FAR_VIBDEPTH\t0x6a\t/* FAR set vibrato depth */\n#define FX_FAR_VIBRATO\t0x6b\t/* FAR persistent vibrato */\n#define FX_FAR_SLIDEVOL\t0x6c\t/* FAR persistent slide-to-volume */\n#define FX_FAR_RETRIG\t0x6d\t/* FAR retrigger */\n#define FX_FAR_DELAY\t0x6e\t/* FAR note offset */\n\n/* ULT effects */\n#define FX_ULT_TEMPO\t0x5f\n#define FX_ULT_TPORTA   0x6f\n#endif\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n/* IT effects */\n#define FX_TRK_VOL      0x80\n#define FX_TRK_VSLIDE   0x81\n#define FX_TRK_FVSLIDE  0x82\n#define FX_IT_INSTFUNC\t0x83\n#define FX_FLT_CUTOFF\t0x84\n#define FX_FLT_RESN\t0x85\n#define FX_IT_BPM\t0x87\n#define FX_IT_ROWDELAY\t0x88\n#define FX_IT_PANSLIDE\t0x89\n#define FX_PANBRELLO\t0x8a\n#define FX_PANBRELLO_WF\t0x8b\n#define FX_HIOFFSET\t0x8c\n#define FX_IT_BREAK\t0x8e\t/* like FX_BREAK with hex parameter */\n#define FX_MACRO_SET\t0xbd\t/* Set active IT parametered MIDI macro */\n#define FX_MACRO\t0xbe\t/* Execute IT MIDI macro */\n#define FX_MACROSMOOTH\t0xbf\t/* Execute IT MIDI macro slide */\n#endif\n\n#ifndef LIBXMP_CORE_PLAYER\n/* MED effects */\n#define FX_HOLD_DECAY\t0x90\n#define FX_SETPITCH\t0x91\n#define FX_VIBRATO2\t0x92\n#define FX_MED_RETRIG\t0x93\n\n/* PTM effects */\n#define FX_NSLIDE_DN\t0x9c\t/* IMF/PTM note slide down */\n#define FX_NSLIDE_UP\t0x9d\t/* IMF/PTM note slide up */\n#define FX_NSLIDE_R_UP\t0x9e\t/* PTM note slide down with retrigger */\n#define FX_NSLIDE_R_DN\t0x9f\t/* PTM note slide up with retrigger */\n\n/* Extra effects */\n#define FX_VOLSLIDE_UP\t0xa0\t/* SFX, MDL */\n#define FX_VOLSLIDE_DN\t0xa1\n#define FX_F_VSLIDE\t0xa5\t/* IMF/MDL */\n#define FX_CHORUS\t0xa9\t/* IMF */\n#define FX_ICE_SPEED\t0xa2\n#define FX_REVERB\t0xaa\t/* IMF */\n#define FX_MED_HOLD\t0xb1\t/* MMD hold/decay */\n#define FX_MEGAARP\t0xb2\t/* Smaksak effect 7: MegaArp */\n#define FX_VOL_ADD\t0xb6\t/* SFX change volume up */\n#define FX_VOL_SUB\t0xb7\t/* SFX change volume down */\n#define FX_PITCH_ADD\t0xb8\t/* SFX add steps to current note */\n#define FX_PITCH_SUB\t0xb9\t/* SFX add steps to current note */\n#define FX_LINE_JUMP\t0xba\t/* Archimedes jump to line in current order */\n#define FX_RETRIG\t0xbb\t/* Retrigger with extended range (LIQ, DSym) */\n#endif\n\n#define FX_SURROUND\t0x8d\t/* S3M/IT */\n#define FX_REVERSE\t0x8f\t/* XM/IT/others: play forward/reverse */\n#define FX_S3M_SPEED\t0xa3\t/* S3M */\n#define FX_VOLSLIDE_2\t0xa4\n#define FX_FINETUNE\t0xa6\n#define FX_S3M_BPM\t0xab\t/* S3M */\n#define FX_FINE_VIBRATO\t0xac\t/* S3M/PTM/IMF/LIQ */\n#define FX_F_VSLIDE_UP\t0xad\t/* MMD */\n#define FX_F_VSLIDE_DN\t0xae\t/* MMD */\n#define FX_F_PORTA_UP\t0xaf\t/* MMD */\n#define FX_F_PORTA_DN\t0xb0\t/* MMD */\n#define FX_PATT_DELAY\t0xb3\t/* MMD */\n#define FX_S3M_ARPEGGIO\t0xb4\n#define FX_PANSL_NOMEM\t0xb5\t/* XM volume column */\n\n#define FX_VSLIDE_UP_2\t0xc0\t/* IT volume column volume slide */\n#define FX_VSLIDE_DN_2\t0xc1\n#define FX_F_VSLIDE_UP_2 0xc2\n#define FX_F_VSLIDE_DN_2 0xc3\n\n#endif /* LIBXMP_EFFECTS_H */\n"
  },
  {
    "path": "contrib/libxmp/src/extras.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2021 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n#include \"player.h\"\n#include \"extras.h\"\n#include \"med_extras.h\"\n#include \"hmn_extras.h\"\n#include \"far_extras.h\"\n\n/*\n * Module extras\n */\n\nvoid libxmp_release_module_extras(struct context_data *ctx)\n{\n\tstruct module_data *m = &ctx->m;\n\n\tif (HAS_MED_MODULE_EXTRAS(*m))\n\t\tlibxmp_med_release_module_extras(m);\n\telse if (HAS_HMN_MODULE_EXTRAS(*m))\n\t\tlibxmp_hmn_release_module_extras(m);\n\telse if (HAS_FAR_MODULE_EXTRAS(*m))\n\t\tlibxmp_far_release_module_extras(m);\n}\n\n/*\n * Channel extras\n */\n\nint libxmp_new_channel_extras(struct context_data *ctx, struct channel_data *xc)\n{\n\tstruct module_data *m = &ctx->m;\n\n\tif (HAS_MED_MODULE_EXTRAS(*m)) {\n\t\tif (libxmp_med_new_channel_extras(xc) < 0)\n\t\t\treturn -1;\n\t} else if (HAS_HMN_MODULE_EXTRAS(*m)) {\n\t\tif (libxmp_hmn_new_channel_extras(xc) < 0)\n\t\t\treturn -1;\n\t} else if (HAS_FAR_MODULE_EXTRAS(*m)) {\n\t\tif (libxmp_far_new_channel_extras(xc) < 0)\n\t\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nvoid libxmp_release_channel_extras(struct context_data *ctx, struct channel_data *xc)\n{\n\tstruct module_data *m = &ctx->m;\n\n\tif (HAS_MED_CHANNEL_EXTRAS(*m))\n\t\tlibxmp_med_release_channel_extras(xc);\n\telse if (HAS_HMN_CHANNEL_EXTRAS(*m))\n\t\tlibxmp_hmn_release_channel_extras(xc);\n\telse if (HAS_FAR_CHANNEL_EXTRAS(*m))\n\t\tlibxmp_far_release_channel_extras(xc);\n}\n\nvoid libxmp_reset_channel_extras(struct context_data *ctx, struct channel_data *xc)\n{\n\tstruct module_data *m = &ctx->m;\n\n\tif (HAS_MED_CHANNEL_EXTRAS(*m))\n\t\tlibxmp_med_reset_channel_extras(xc);\n\telse if (HAS_HMN_CHANNEL_EXTRAS(*m))\n\t\tlibxmp_hmn_reset_channel_extras(xc);\n\telse if (HAS_FAR_CHANNEL_EXTRAS(*m))\n\t\tlibxmp_far_reset_channel_extras(xc);\n}\n\n/*\n * Player extras\n */\n\nvoid libxmp_play_extras(struct context_data *ctx, struct channel_data *xc, int chn)\n{\n\tstruct module_data *m = &ctx->m;\n\n\tif (HAS_FAR_CHANNEL_EXTRAS(*xc))\n\t\tlibxmp_far_play_extras(ctx, xc, chn);\n\n\tif (xc->ins >= m->mod.ins)\t/* SFX instruments have no extras */\n\t\treturn;\n\n\tif (HAS_MED_INSTRUMENT_EXTRAS(m->mod.xxi[xc->ins]))\n\t\tlibxmp_med_play_extras(ctx, xc, chn);\n\telse if (HAS_HMN_INSTRUMENT_EXTRAS(m->mod.xxi[xc->ins]))\n\t\tlibxmp_hmn_play_extras(ctx, xc, chn);\n}\n\nint libxmp_extras_get_volume(struct context_data *ctx, struct channel_data *xc)\n{\n\tstruct module_data *m = &ctx->m;\n\tint vol;\n\n\tif (xc->ins >= m->mod.ins)\n\t\tvol = xc->volume;\n\telse if (HAS_MED_INSTRUMENT_EXTRAS(m->mod.xxi[xc->ins]))\n\t\tvol = MED_CHANNEL_EXTRAS(*xc)->volume * xc->volume / 64;\n\telse if (HAS_HMN_INSTRUMENT_EXTRAS(m->mod.xxi[xc->ins]))\n\t\tvol = HMN_CHANNEL_EXTRAS(*xc)->volume * xc->volume / 64;\n\telse\n\t\tvol = xc->volume;\n\n\treturn vol;\n}\n\nint libxmp_extras_get_period(struct context_data *ctx, struct channel_data *xc)\n{\n\tint period;\n\n\tif (HAS_MED_CHANNEL_EXTRAS(*xc))\n\t\tperiod = libxmp_med_change_period(ctx, xc);\n\telse period = 0;\n\n\treturn period;\n}\n\nint libxmp_extras_get_linear_bend(struct context_data *ctx, struct channel_data *xc)\n{\n\tint linear_bend;\n\n\tif (HAS_MED_CHANNEL_EXTRAS(*xc))\n\t\tlinear_bend = libxmp_med_linear_bend(ctx, xc);\n\telse if (HAS_HMN_CHANNEL_EXTRAS(*xc))\n\t\tlinear_bend = libxmp_hmn_linear_bend(ctx, xc);\n\telse\n\t\tlinear_bend = 0;\n\n\treturn linear_bend;\n}\n\nvoid libxmp_extras_process_fx(struct context_data *ctx, struct channel_data *xc,\n\t\t\tint chn, uint8 note, uint8 fxt, uint8 fxp, int fnum)\n{\n\tif (HAS_MED_CHANNEL_EXTRAS(*xc))\n\t\tlibxmp_med_extras_process_fx(ctx, xc, chn, note, fxt, fxp, fnum);\n\telse if (HAS_HMN_CHANNEL_EXTRAS(*xc))\n\t\tlibxmp_hmn_extras_process_fx(ctx, xc, chn, note, fxt, fxp, fnum);\n\telse if (HAS_FAR_CHANNEL_EXTRAS(*xc))\n\t\tlibxmp_far_extras_process_fx(ctx, xc, chn, note, fxt, fxp, fnum);\n}\n"
  },
  {
    "path": "contrib/libxmp/src/extras.h",
    "content": "#ifndef LIBXMP_EXTRAS_H\n#define LIBXMP_EXTRAS_H\n\nvoid libxmp_release_module_extras(struct context_data *);\nint  libxmp_new_channel_extras(struct context_data *, struct channel_data *);\nvoid libxmp_release_channel_extras(struct context_data *, struct channel_data *);\nvoid libxmp_reset_channel_extras(struct context_data *, struct channel_data *);\nvoid libxmp_play_extras(struct context_data *, struct channel_data *, int);\nint  libxmp_extras_get_volume(struct context_data *, struct channel_data *);\nint  libxmp_extras_get_period(struct context_data *, struct channel_data *);\nint  libxmp_extras_get_linear_bend(struct context_data *, struct channel_data *);\nvoid libxmp_extras_process_fx(struct context_data *, struct channel_data *, int, uint8, uint8, uint8, int);\n\n\n/* FIXME */\nvoid libxmp_med_hold_hack(struct context_data *ctx, int, int, int);\n\n#endif\n"
  },
  {
    "path": "contrib/libxmp/src/far_extras.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n#include \"player.h\"\n#include \"lfo.h\"\n#include \"effects.h\"\n#include \"period.h\"\n#include \"far_extras.h\"\n\n#define FAR_GUS_CHANNELS\t17\n#define FAR_OLD_TEMPO_SHIFT\t2 /* Power of multiplier for old tempo mode. */\n\n/**\n * The time factor needed to directly use FAR tempos is a little unintuitive.\n *\n * Generally: FAR tries to run 32/[coarse tempo] rows per second, which\n * (usually, but not always) are subdivided into 4 \"ticks\". To achieve\n * this, it measures tempos in the number of ticks that should play per second\n * (see far_tempos below). Fine tempo is added or subtracted from this number.\n * To time these ticks, FAR uses the programmable interval timer (PIT) to run a\n * player interrupt.\n *\n * libxmp effectively uses a calculation of 10.0 * 0.25 / BPM to get the tick\n * duration in seconds. A base time factor of 4.0 makes this 1 / BPM, turning\n * BPM into the ticks/sec measure that FAR uses. This isn't completely\n * accurate to FAR, though.\n *\n * The x86 PIT runs at a rate of 1193182 Hz, but FAR does something strange\n * when calculating PIT divisors and uses a constant of 1197255 Hz instead.\n * This means FAR tempo is slightly slower by a factor of around:\n *\n * floor(1197255 / 32) / floor(1193182 / 32) ~= 1.003439\n *\n * This still isn't perfect, but it gets the playback rate fairly close.\n */\n\n/* tempo[0] = 256; tempo[i] = floor(128 / i). */\nstatic const int far_tempos[16] =\n{\n\t256, 128, 64, 42, 32, 25, 21, 18, 16, 14, 12, 11, 10, 9, 9, 8\n};\n\n/**\n * FAR tempo has some unusual requirements that don't really match any other\n * format:\n *\n * 1) The coarse tempo is roughly equivalent to speed, but a value of 0 is\n *    supported, and FAR doesn't actually have a concept of ticks: it translates\n *    this value to tempo.\n *\n * 2) There is some very bizarre clamping behavior involving fine tempo slides\n *    that needs to be emulated.\n *\n * 3) Tempos can range from 1 to 356(!). FAR uses a fixed row subdivision size\n *    of 16, so just shift the tempo by 4 and hope libxmp doesn't change it.\n *\n * 4) There are two tempo modes, and they can be switched between arbitrarily...\n */\nint libxmp_far_translate_tempo(int mode, int fine_change, int coarse,\n\t\t\t       int *fine, int *_speed, int *_bpm)\n{\n\tint speed, bpm;\n\n\tif (coarse < 0 || coarse > 15 || mode < 0 || mode > 1)\n\t\treturn -1;\n\n\t/* Compatibility for FAR's broken fine tempo \"clamping\". */\n\tif (fine_change < 0 && far_tempos[coarse] + *fine <= 0) {\n\t\t*fine = 0;\n\t} else if (fine_change > 0 && far_tempos[coarse] + *fine >= 100) {\n\t\t*fine = 100;\n\t}\n\n\tif (mode == 1) {\n\t\t/* \"New\" FAR tempo\n\t\t * Note that negative values are possible in Farandole Composer\n\t\t * via changing fine tempo and then slowing coarse tempo.\n\t\t * These result in very slow final tempos due to signed to\n\t\t * unsigned conversion. Zero should just be ignored entirely. */\n\t\tint tempo = far_tempos[coarse] + *fine;\n\t\tuint32 divisor;\n\t\tif (tempo == 0)\n\t\t\treturn -1;\n\n\t\tdivisor = 1197255 / tempo;\n\n\t\t/* Coincidentally(?), the \"new\" FAR tempo algorithm actually\n\t\t * prevents the BPM from dropping too far under XMP_MIN_BPM,\n\t\t * which is what libxmp needs anyway. */\n\t\tspeed = 0;\n\t\twhile (divisor > 0xffff) {\n\t\t\tdivisor >>= 1;\n\t\t\ttempo <<= 1;\n\t\t\tspeed++;\n\t\t}\n\t\tif (speed >= 2)\n\t\t\tspeed++;\n\t\tspeed += 3;\n\t\t/* Add an extra tick because the FAR replayer checks the tick\n\t\t * remaining count before decrementing it but after handling\n\t\t * each tick, i.e. a count of \"3\" executes 4 ticks. */\n\t\tspeed++;\n\t\tbpm = tempo;\n\t} else {\n\t\t/* \"Old\" FAR tempo\n\t\t * This runs into the XMP_MIN_BPM limit, but nothing uses it anyway.\n\t\t * Old tempo mode in the original FAR replayer has 32 ticks,\n\t\t * but ignores all except every 8th. */\n\t\tspeed = 4 << FAR_OLD_TEMPO_SHIFT;\n\t\tbpm = (far_tempos[coarse] + *fine * 2) << FAR_OLD_TEMPO_SHIFT;\n\t}\n\n\tif (bpm < XMP_MIN_BPM)\n\t\tbpm = XMP_MIN_BPM;\n\n\t*_speed = speed;\n\t*_bpm = bpm;\n\treturn 0;\n}\n\nstatic void libxmp_far_update_tempo(struct context_data *ctx, int fine_change)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct far_module_extras *me = (struct far_module_extras *)m->extra;\n\tint speed, bpm;\n\n\tif (libxmp_far_translate_tempo(me->tempo_mode, fine_change,\n\t    me->coarse_tempo, &me->fine_tempo, &speed, &bpm) == 0) {\n\t\tp->speed = speed;\n\t\tp->bpm = bpm;\n\t}\n}\n\nstatic void libxmp_far_update_vibrato(struct lfo *lfo, int rate, int depth)\n{\n\tlibxmp_lfo_set_depth(lfo, libxmp_gus_frequency_steps(depth << 1, FAR_GUS_CHANNELS));\n\tlibxmp_lfo_set_rate(lfo, rate * 3);\n}\n\n/* Convoluted algorithm for delay times for retrigger and note offset effects. */\nstatic int libxmp_far_retrigger_delay(struct far_module_extras *me, int param)\n{\n\tint delay;\n\tif (me->coarse_tempo < 0 || me->coarse_tempo > 15 || param < 1)\n\t\treturn -1;\n\n\tdelay = (far_tempos[me->coarse_tempo] + me->fine_tempo) / param;\n\n\tif (me->tempo_mode) {\n\t\t/* Effects divide by 4, timer increments by 2 (round up). */\n\t\treturn ((delay >> 2) + 1) >> 1;\n\t} else {\n\t\t/* Effects divide by 2, timer increments by 2 (round up).\n\t\t * Old tempo mode handles every 8th tick (<< FAR_OLD_TEMPO_SHIFT).\n\t\t * Delay values >4 result in no retrigger. */\n\t\tdelay = (((delay >> 1) + 1) >> 1) << FAR_OLD_TEMPO_SHIFT;\n\t\tif (delay >= 16)\n\t\t\treturn -1;\n\t\tif (delay < (1 << FAR_OLD_TEMPO_SHIFT))\n\t\t\treturn (1 << FAR_OLD_TEMPO_SHIFT);\n\t\treturn delay;\n\t}\n}\n\n\nvoid libxmp_far_play_extras(struct context_data *ctx, struct channel_data *xc, int chn)\n{\n\tstruct far_module_extras *me = FAR_MODULE_EXTRAS(ctx->m);\n\tstruct far_channel_extras *ce = FAR_CHANNEL_EXTRAS(*xc);\n\n\t/* FAR vibrato depth is global, even though rate isn't. This might have\n\t * been changed by a different channel, so make sure it's applied. */\n\tif (TEST(VIBRATO) || TEST_PER(VIBRATO))\n\t\tlibxmp_far_update_vibrato(&xc->vibrato.lfo, ce->vib_rate, me->vib_depth);\n}\n\nint libxmp_far_new_channel_extras(struct channel_data *xc)\n{\n\txc->extra = calloc(1, sizeof(struct far_channel_extras));\n\tif (xc->extra == NULL)\n\t\treturn -1;\n\tFAR_CHANNEL_EXTRAS(*xc)->magic = FAR_EXTRAS_MAGIC;\n\treturn 0;\n}\n\nvoid libxmp_far_reset_channel_extras(struct channel_data *xc)\n{\n\tmemset((char *)xc->extra + 4, 0, sizeof(struct far_channel_extras) - 4);\n}\n\nvoid libxmp_far_release_channel_extras(struct channel_data *xc)\n{\n\tfree(xc->extra);\n\txc->extra = NULL;\n}\n\nint libxmp_far_new_module_extras(struct module_data *m)\n{\n\tm->extra = calloc(1, sizeof(struct far_module_extras));\n\tif (m->extra == NULL)\n\t\treturn -1;\n\tFAR_MODULE_EXTRAS(*m)->magic = FAR_EXTRAS_MAGIC;\n\tFAR_MODULE_EXTRAS(*m)->vib_depth = 4;\n\treturn 0;\n}\n\nvoid libxmp_far_release_module_extras(struct module_data *m)\n{\n\tfree(m->extra);\n\tm->extra = NULL;\n}\n\nvoid libxmp_far_extras_process_fx(struct context_data *ctx, struct channel_data *xc,\n\t\t\t   int chn, uint8 note, uint8 fxt, uint8 fxp, int fnum)\n{\n\tstruct xmp_module *mod = &ctx->m.mod;\n\tstruct far_module_extras *me = FAR_MODULE_EXTRAS(ctx->m);\n\tstruct far_channel_extras *ce = FAR_CHANNEL_EXTRAS(*xc);\n\tint update_tempo = 0;\n\tint update_vibrato = 0;\n\tint fine_change = 0;\n\tint delay, target, tempo;\n\tint32 diff, step;\n\n\t/* Tempo effects and vibrato are multiplexed to reduce the effects count.\n\t *\n\t * Misc. notes: FAR pitch offset effects can overflow/underflow GUS\n\t * frequency, which isn't supported by libxmp (Haj/before.far).\n\t */\n\tswitch (fxt) {\n\tcase FX_FAR_PORTA_UP:\t\t/* FAR pitch offset up */\n\t\tSET(FINE_BEND);\n\t\tRESET_PER(TONEPORTA);\n\t\txc->freq.fslide = libxmp_gus_frequency_steps(fxp << 2, FAR_GUS_CHANNELS);\n\t\tbreak;\n\n\tcase FX_FAR_PORTA_DN:\t\t/* FAR pitch offset down */\n\t\tSET(FINE_BEND);\n\t\tRESET_PER(TONEPORTA);\n\t\txc->freq.fslide = -libxmp_gus_frequency_steps(fxp << 2, FAR_GUS_CHANNELS);\n\t\tbreak;\n\n\t/* Despite some claims, this effect scales with tempo and only\n\t * corresponds to (param) rows at tempo 4. See FORMATS.DOC.\n\t */\n\tcase FX_FAR_TPORTA:\t\t/* FAR persistent tone portamento */\n\t\tif (!IS_VALID_INSTRUMENT(xc->ins))\n\t\t\tbreak;\n\n\t\ttempo = far_tempos[me->coarse_tempo] + me->fine_tempo;\n\n\t\tSET_PER(TONEPORTA);\n\t\tif (IS_VALID_NOTE(note - 1)) {\n\t\t\txc->porta.target = libxmp_note_to_period(ctx, note - 1, xc->finetune, xc->per_adj);\n\t\t}\n\t\txc->porta.dir = xc->period < xc->porta.target ? 1 : -1;\n\n\t\t/* Parameter of 0 is equivalent to 1. */\n\t\tif (fxp < 1)\n\t\t\tfxp = 1;\n\t\t/* Tempos <=0 cause crashes and other weird behavior\n\t\t * here in Farandole Composer, don't emulate that. */\n\t\tif (tempo < 1)\n\t\t\ttempo = 1;\n\n\t\tdiff = xc->porta.target - xc->period;\n\t\tstep = (diff > 0 ? diff : -diff) * 8 / (tempo * fxp);\n\n\t\txc->porta.slide = (step > 0) ? step : 1;\n\t\tbreak;\n\n\n\t/* Despite some claims, this effect scales with tempo and only\n\t * corresponds to (param/2) rows at tempo 4. See FORMATS.DOC.\n\t */\n\tcase FX_FAR_SLIDEVOL:\t\t/* FAR persistent slide-to-volume */\n\t\ttempo = far_tempos[me->coarse_tempo] + me->fine_tempo;\n\t\ttarget = MSN(fxp) << 4;\n\t\tfxp = LSN(fxp);\n\n\t\t/* Parameter of 0 is equivalent to 1. */\n\t\tif (fxp < 1)\n\t\t\tfxp = 1;\n\t\t/* Tempos <=0 cause crashes and other weird behavior\n\t\t * here in Farandole Composer, don't emulate that. */\n\t\tif (tempo < 1)\n\t\t\ttempo = 1;\n\n\t\tdiff = target - xc->volume;\n\t\tstep = diff * 16 / (tempo * fxp);\n\t\tif (step == 0)\n\t\t\tstep = (diff > 0) ? 1 : -1;\n\n\t\tSET_PER(VOL_SLIDE);\n\t\txc->vol.slide = step;\n\t\txc->vol.target = target + 1;\n\t\tbreak;\n\n\tcase FX_FAR_VIBDEPTH:\t\t/* FAR set vibrato depth */\n\t\tme->vib_depth = LSN(fxp);\n\t\tupdate_vibrato = 1;\n\t\tbreak;\n\n\tcase FX_FAR_VIBRATO:\t\t/* FAR vibrato and sustained vibrato */\n\t\tif (ce->vib_sustain == 0) {\n\t\t\t/* With sustain, regular vibrato only sets the rate. */\n\t\t\tce->vib_sustain = MSN(fxp);\n\t\t\tif (ce->vib_sustain == 0)\n\t\t\t\tSET(VIBRATO);\n\t\t}\n\t\tce->vib_rate = LSN(fxp);\n\t\tupdate_vibrato = 1;\n\t\tbreak;\n\n\t/* Retrigger note param times at intervals that roughly evently\n\t * divide the row. A param of 0 crashes Farandole Composer.\n\t */\n\tcase FX_FAR_RETRIG:\t\t/* FAR retrigger */\n\t\tdelay = libxmp_far_retrigger_delay(me, fxp);\n\t\tif (note && fxp > 1 && delay >= 0 && delay <= ctx->p.speed) {\n\t\t\tSET(RETRIG);\n\t\t\txc->retrig.val = delay ? delay : 1;\n\t\t\txc->retrig.count = delay + 1;\n\t\t\txc->retrig.type = 0;\n\t\t\txc->retrig.limit = fxp - 1;\n\t\t}\n\t\tbreak;\n\n\t/* A better effect name would probably be \"retrigger once\".\n\t * The description/intent seems to be that this is a delay\n\t * effect, but an initial note always plays as well. The second\n\t * note always plays on the (param)th tick due to player quirks,\n\t * but it's supposed to be derived similar to retrigger.\n\t * A param of zero works like effect 4F (bug?).\n\t */\n\tcase FX_FAR_DELAY:\t\t/* FAR note offset */\n\t\tif (note) {\n\t\t\tdelay = me->tempo_mode ? fxp : fxp << FAR_OLD_TEMPO_SHIFT;\n\t\t\tSET(RETRIG);\n\t\t\txc->retrig.val = delay ? delay : 1;\n\t\t\txc->retrig.count = delay + 1;\n\t\t\txc->retrig.type = 0;\n\t\t\txc->retrig.limit = fxp ? 1 : 0;\n\t\t}\n\t\tbreak;\n\n\tcase FX_FAR_TEMPO:\t\t/* FAR coarse tempo and tempo mode */\n\t\tif (MSN(fxp)) {\n\t\t\tme->tempo_mode = MSN(fxp) - 1;\n\t\t} else {\n\t\t\tme->coarse_tempo = LSN(fxp);\n\t\t}\n\t\tupdate_tempo = 1;\n\t\tbreak;\n\n\tcase FX_FAR_F_TEMPO:\t\t/* FAR fine tempo slide up/down */\n\t\tif (MSN(fxp)) {\n\t\t\tme->fine_tempo += MSN(fxp);\n\t\t\tfine_change = MSN(fxp);\n\t\t} else if (LSN(fxp)) {\n\t\t\tme->fine_tempo -= LSN(fxp);\n\t\t\tfine_change = -LSN(fxp);\n\t\t} else {\n\t\t\tme->fine_tempo = 0;\n\t\t}\n\t\tupdate_tempo = 1;\n\t\tbreak;\n\t}\n\n\tif (update_vibrato) {\n\t\tif (ce->vib_rate != 0) {\n\t\t\tif (ce->vib_sustain)\n\t\t\t\tSET_PER(VIBRATO);\n\t\t} else {\n\t\t\tRESET_PER(VIBRATO);\n\t\t\tce->vib_sustain = 0;\n\t\t}\n\t\tlibxmp_far_update_vibrato(&xc->vibrato.lfo, ce->vib_rate, me->vib_depth);\n\t}\n\n\tif (update_tempo)\n\t\tlibxmp_far_update_tempo(ctx, fine_change);\n}\n"
  },
  {
    "path": "contrib/libxmp/src/far_extras.h",
    "content": "#ifndef XMP_FAR_EXTRAS_H\n#define XMP_FAR_EXTRAS_H\n\n#include \"common.h\"\n\n#define FAR_EXTRAS_MAGIC 0x7b12a83f\n\n/*\nstruct far_instrument_extras {\n\tuint32 magic;\n};\n*/\nstruct far_channel_extras {\n\tuint32 magic;\n\tint vib_sustain;\t/* Is vibrato persistent? */\n\tint vib_rate;\t\t/* Vibrato rate. */\n};\n\nstruct far_module_extras {\n\tuint32 magic;\n\tint coarse_tempo;\n\tint fine_tempo;\n\tint tempo_mode;\n\tint vib_depth;\t\t/* Vibrato depth for all channels. */\n};\n\n/*\n#define FAR_INSTRUMENT_EXTRAS(x) ((struct far_instrument_extras *)(x).extra)\n#define HAS_FAR_INSTRUMENT_EXTRAS(x) \\\n\t(FAR_INSTRUMENT_EXTRAS(x) != NULL && \\\n\t FAR_INSTRUMENT_EXTRAS(x)->magic == FAR_EXTRAS_MAGIC)\n*/\n#define FAR_CHANNEL_EXTRAS(x) ((struct far_channel_extras *)(x).extra)\n#define HAS_FAR_CHANNEL_EXTRAS(x) \\\n\t(FAR_CHANNEL_EXTRAS(x) != NULL && \\\n\t FAR_CHANNEL_EXTRAS(x)->magic == FAR_EXTRAS_MAGIC)\n\n#define FAR_MODULE_EXTRAS(x) ((struct far_module_extras *)(x).extra)\n#define HAS_FAR_MODULE_EXTRAS(x) \\\n\t(FAR_MODULE_EXTRAS(x) != NULL && \\\n\t FAR_MODULE_EXTRAS(x)->magic == FAR_EXTRAS_MAGIC)\n\nint libxmp_far_translate_tempo(int, int, int, int *, int *, int *);\n\nvoid libxmp_far_play_extras(struct context_data *, struct channel_data *, int);\nint  libxmp_far_linear_bend(struct context_data *, struct channel_data *);\nint  libxmp_far_new_channel_extras(struct channel_data *);\nvoid libxmp_far_reset_channel_extras(struct channel_data *);\nvoid libxmp_far_release_channel_extras(struct channel_data *);\nint  libxmp_far_new_module_extras(struct module_data *);\nvoid libxmp_far_release_module_extras(struct module_data *);\nvoid libxmp_far_extras_process_fx(struct context_data *, struct channel_data *, int, uint8, uint8, uint8, int);\n\n#endif\n\n"
  },
  {
    "path": "contrib/libxmp/src/filetype.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2022 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n#include <errno.h>\n\n#if defined(_WIN32)\n\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#include <windows.h>\n\nint libxmp_get_filetype (const char *path)\n{\n\tDWORD result = GetFileAttributesA(path);\n\tif (result == (DWORD)(-1)) {\n\t    errno = ENOENT;\n\t    return XMP_FILETYPE_NONE;\n\t}\n\treturn (result & FILE_ATTRIBUTE_DIRECTORY) ? XMP_FILETYPE_DIR : XMP_FILETYPE_FILE;\n}\n\n#elif defined(__OS2__) || defined(__EMX__)\n\n#define INCL_DOSFILEMGR\n#include <os2.h>\n\nint libxmp_get_filetype (const char *path)\n{\n\tFILESTATUS3 fs;\n\tif (DosQueryPathInfo(path, FIL_STANDARD, &fs, sizeof(fs)) != 0) {\n\t    errno = ENOENT;\n\t    return XMP_FILETYPE_NONE;\n\t}\n\treturn (fs.attrFile & FILE_DIRECTORY) ? XMP_FILETYPE_DIR : XMP_FILETYPE_FILE;\n}\n\n#elif defined(__DJGPP__)\n\n#include <dos.h>\n#include <io.h>\n\nint libxmp_get_filetype (const char *path)\n{\n\tint attr = _chmod(path, 0);\n\t/* Root directories on some non-local drives (e.g. CD-ROM), as well as\n\t * devices may fail _chmod, but we are not interested in such cases. */\n\tif (attr < 0) return XMP_FILETYPE_NONE;\n\t/* we shouldn't hit _A_VOLID ! */\n\treturn (attr & (_A_SUBDIR|_A_VOLID)) ? XMP_FILETYPE_DIR : XMP_FILETYPE_FILE;\n}\n\n#elif defined(__WATCOMC__) && defined(_DOS)\n\n#include <dos.h>\n#include <direct.h>\n\nint libxmp_get_filetype (const char *path)\n{\n\tunsigned int attr;\n\tif (_dos_getfileattr(path, &attr)) return XMP_FILETYPE_NONE;\n\treturn (attr & (_A_SUBDIR|_A_VOLID)) ? XMP_FILETYPE_DIR : XMP_FILETYPE_FILE;\n}\n\n#elif defined(__amigaos4__)\n\n#define __USE_INLINE__\n#include <proto/dos.h>\n\nint libxmp_get_filetype (const char *path)\n{\n\tint typ = XMP_FILETYPE_NONE;\n\tstruct ExamineData *data = ExamineObjectTags(EX_StringNameInput, path, TAG_END);\n\tif (data) {\n\t    if (EXD_IS_FILE(data)) {\n\t\ttyp = XMP_FILETYPE_FILE;\n\t    } else\n\t    if (EXD_IS_DIRECTORY(data)) {\n\t\ttyp = XMP_FILETYPE_DIR;\n\t    }\n\t    FreeDosObject(DOS_EXAMINEDATA, data);\n\t}\n\tif (typ == XMP_FILETYPE_NONE) errno = ENOENT;\n\treturn typ;\n}\n\n#elif defined(LIBXMP_AMIGA)\n\n#include <proto/dos.h>\n\nint libxmp_get_filetype (const char *path)\n{\n\tint typ = XMP_FILETYPE_NONE;\n\tBPTR lock = Lock((const STRPTR)path, ACCESS_READ);\n\tif (lock) {\n\t    struct FileInfoBlock *fib = (struct FileInfoBlock *) AllocDosObject(DOS_FIB,NULL);\n\t    if (fib) {\n\t\tif (Examine(lock, fib)) {\n\t\t    typ = (fib->fib_DirEntryType < 0) ? XMP_FILETYPE_FILE : XMP_FILETYPE_DIR;\n\t\t}\n\t\tFreeDosObject(DOS_FIB, fib);\n\t    }\n\t    UnLock(lock);\n\t}\n\tif (typ == XMP_FILETYPE_NONE) errno = ENOENT;\n\treturn typ;\n}\n\n#else /* unix (ish): */\n\n#include <sys/types.h>\n#include <sys/stat.h>\n\nint libxmp_get_filetype (const char *path)\n{\n\tstruct stat st;\n\tmemset(&st, 0, sizeof(st)); /* silence sanitizers.. */\n\tif (stat(path, &st) < 0) return XMP_FILETYPE_NONE;\n\tif (S_ISDIR(st.st_mode)) return XMP_FILETYPE_DIR;\n\tif (S_ISREG(st.st_mode)) return XMP_FILETYPE_FILE;\n\treturn XMP_FILETYPE_NONE;\n}\n\n#endif\n\n"
  },
  {
    "path": "contrib/libxmp/src/filter.c",
    "content": "/*\n * Based on the public domain version by Olivier Lapicque\n * Rewritten for libxmp by Claudio Matsuoka\n *\n * Copyright (C) 2012-2024 Claudio Matsuoka\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n#include <math.h>\n#include \"xmp.h\"\n#include \"mixer.h\"\n\n/* LUT for 2 * damping factor */\nstatic const float resonance_table[128] = {\n        1.0000000000000000f, 0.9786446094512940f, 0.9577452540397644f, 0.9372922182083130f,\n        0.9172759056091309f, 0.8976871371269226f, 0.8785166740417481f, 0.8597555756568909f,\n        0.8413951396942139f, 0.8234267830848694f, 0.8058421611785889f, 0.7886331081390381f,\n        0.7717915177345276f, 0.7553095817565918f, 0.7391796708106995f, 0.7233941555023193f,\n        0.7079457640647888f, 0.6928272843360901f, 0.6780316829681397f, 0.6635520458221436f,\n        0.6493816375732422f, 0.6355138421058655f, 0.6219421625137329f, 0.6086603403091431f,\n        0.5956621170043945f, 0.5829415321350098f, 0.5704925656318665f, 0.5583094954490662f,\n        0.5463865399360657f, 0.5347182154655457f, 0.5232990980148315f, 0.5121238231658936f,\n        0.5011872053146362f, 0.4904841780662537f, 0.4800096750259399f, 0.4697588682174683f,\n        0.4597269892692566f, 0.4499093294143677f, 0.4403013288974762f, 0.4308985173702240f,\n        0.4216965138912201f, 0.4126909971237183f, 0.4038778245449066f, 0.3952528536319733f,\n        0.3868120610713959f, 0.3785515129566193f, 0.3704673945903778f, 0.3625559210777283f,\n        0.3548133969306946f, 0.3472362160682678f, 0.3398208320140839f, 0.3325638175010681f,\n        0.3254617750644684f, 0.3185114264488220f, 0.3117094635963440f, 0.3050527870655060f,\n        0.2985382676124573f, 0.2921628654003143f, 0.2859236001968384f, 0.2798175811767578f,\n        0.2738419771194458f, 0.2679939568042755f, 0.2622708380222321f, 0.2566699385643005f,\n        0.2511886358261108f, 0.2458244115114212f, 0.2405747324228287f, 0.2354371547698975f,\n        0.2304092943668366f, 0.2254888117313385f, 0.2206734120845795f, 0.2159608304500580f,\n        0.2113489061594009f, 0.2068354636430740f, 0.2024184018373489f, 0.1980956792831421f,\n        0.1938652694225311f, 0.1897251904010773f, 0.1856735348701477f, 0.1817083954811096f,\n        0.1778279393911362f, 0.1740303486585617f, 0.1703138649463654f, 0.1666767448186874f,\n        0.1631172895431519f, 0.1596338599920273f, 0.1562248021364212f, 0.1528885662555695f,\n        0.1496235728263855f, 0.1464282870292664f, 0.1433012634515762f, 0.1402409970760346f,\n        0.1372461020946503f, 0.1343151479959488f, 0.1314467936754227f, 0.1286396980285645f,\n        0.1258925348520279f, 0.1232040524482727f, 0.1205729842185974f, 0.1179980933666229f,\n        0.1154781952500343f, 0.1130121126770973f, 0.1105986908078194f, 0.1082368120551109f,\n        0.1059253737330437f, 0.1036632955074310f, 0.1014495193958283f, 0.0992830246686935f,\n        0.0971627980470657f, 0.0950878411531448f, 0.0930572077631950f, 0.0910699293017387f,\n        0.0891250967979431f, 0.0872217938303947f, 0.0853591337800026f, 0.0835362523794174f,\n        0.0817523002624512f, 0.0800064504146576f, 0.0782978758215904f, 0.0766257941722870f,\n        0.0749894231557846f, 0.0733879879117012f, 0.0718207582831383f, 0.0702869966626167f,\n        0.0687859877943993f, 0.0673170387744904f, 0.0658794566988945f, 0.0644725710153580f,\n};\n\n\n#if !defined(HAVE_POWF) || defined(__DJGPP__) || defined(__WATCOMC__)\n/* Watcom doesn't have powf. DJGPP have a C-only implementation in libm. */\n#undef powf\n#define powf(f1_,f2_) (float)pow((f1_),(f2_))\n#endif\n\n/*\n * Simple 2-poles resonant filter\n */\n#define FREQ_PARAM_MULT (128.0f / (24.0f * 256.0f))\nvoid libxmp_filter_setup(int srate, int cutoff, int res, int *a0, int *b0, int *b1)\n{\n\tfloat fc, fs = (float)srate;\n\tfloat fg, fb0, fb1;\n\tfloat r, d, e;\n\n\t/* [0-255] => [100Hz-8000Hz] */\n\tCLAMP(cutoff, 0, 255);\n\tCLAMP(res, 0, 255);\n\n        fc = 110.0f * powf(2.0f, (float)cutoff * FREQ_PARAM_MULT + 0.25f);\n        if (fc > fs / 2.0f) {\n                fc = fs / 2.0f;\n\t}\n\n        r = fs / (2.0 * 3.14159265358979f * fc);\n        d = resonance_table[res >> 1] * (r + 1.0) - 1.0;\n        e = r * r;\n\n        fg = 1.0 / (1.0 + d + e);\n        fb0 = (d + e + e) / (1.0 + d + e);\n        fb1 = -e / (1.0 + d + e);\n\n\t*a0 = (int)(fg  * (1 << FILTER_SHIFT));\n\t*b0 = (int)(fb0 * (1 << FILTER_SHIFT));\n\t*b1 = (int)(fb1 * (1 << FILTER_SHIFT));\n}\n\n#endif\n"
  },
  {
    "path": "contrib/libxmp/src/flow.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n\n/* Process a pattern loop effect with the parameter fxp. A parameter of 0\n * will set the loop target, and a parameter of 1-15 (most formats) or\n * 1-255 (OctaMED) will perform a loop.\n *\n * The compatibility logic for Pattern Loop is complex, so a flow_control\n * argument is taken such that the scan can use this function directly.\n *\n * If the development tests ever start building against effects.c, this\n * can be moved back to effects.c.\n */\nvoid libxmp_process_pattern_loop(struct context_data *ctx,\n\t\tstruct flow_control *f, int chn, int row, int fxp)\n{\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tint *start = &f->loop[chn].start;\n\tint *count = &f->loop[chn].count;\n\tint looped = 0;\n\tint i;\n\n\t/* Digital Tracker: only the first E60 or E6x is handled per row. */\n\tif (HAS_FLOW_MODE(FLOW_LOOP_FIRST_EFFECT) && f->loop_param >= 0) {\n\t\treturn;\n\t}\n\tf->loop_param = fxp;\n\n\t/* Scream Tracker 3, Digital Tracker, Octalyser, and probably others\n\t * use global loop targets and counts. Later versions of Digital\n\t * Tracker use a global target but per-track counts. */\n\tif (HAS_FLOW_MODE(FLOW_LOOP_GLOBAL_TARGET)) {\n\t\tstart = &f->loop_start;\n\t}\n\tif (HAS_FLOW_MODE(FLOW_LOOP_GLOBAL_COUNT)) {\n\t\tcount = &f->loop_count;\n\t}\n\n\tif (fxp == 0) {\n\t\t/* mark start of loop */\n\t\t/* Liquid Tracker: M60 is ignored for channels with count >= 1 */\n\t\tif (HAS_FLOW_MODE(FLOW_LOOP_IGNORE_TARGET) && *count >= 1) {\n\t\t\treturn;\n\t\t}\n\t\t*start = row;\n\t\tif (HAS_QUIRK(QUIRK_FT2BUGS))\n\t\t\tf->jumpline = row;\n\t} else {\n\t\t/* end of loop */\n\t\tif (*start < 0) {\n\t\t\t/* Scream Tracker 3.01b: if SB0 wasn't used, the first\n\t\t\t * SBx used will set the loop target to its row. */\n\t\t\tif (HAS_FLOW_MODE(FLOW_LOOP_INIT_SAMEROW)) {\n\t\t\t\t*start = row;\n\t\t\t} else {\n\t\t\t\t*start = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (*count) {\n\t\t\tif (--(*count)) {\n\t\t\t\tf->loop_dest = *start;\n\t\t\t\tlooped = 1;\n\t\t\t} else {\n\t\t\t\t/* S3M and IT: loop termination advances the\n\t\t\t\t * loop target past SBx. */\n\t\t\t\tif (HAS_FLOW_MODE(FLOW_LOOP_END_ADVANCES)) {\n\t\t\t\t\t*start = row + 1;\n\t\t\t\t}\n\t\t\t\t/* Liquid Tracker cancels any other loop jumps\n\t\t\t\t * this row started on loop termination. */\n\t\t\t\tif (HAS_FLOW_MODE(FLOW_LOOP_END_CANCELS)) {\n\t\t\t\t\tf->loop_dest = -1;\n\t\t\t\t}\n\t\t\t\tf->loop_active_num--;\n\t\t\t}\n\t\t} else {\n\t\t\t/* Modplug Tracker: only begin a loop if no\n\t\t\t * other channel is currently looping. */\n\t\t\tif (HAS_FLOW_MODE(FLOW_LOOP_ONE_AT_A_TIME)) {\n\t\t\t\tfor (i = 0; i < mod->chn; i++) {\n\t\t\t\t\tif (i != chn && f->loop[i].count != 0)\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t*count = fxp;\n\t\t\tf->loop_dest = *start;\n\t\t\tf->loop_active_num++;\n\t\t\tlooped = 1;\n\t\t}\n\t}\n\n\t/* Hacks for loop jumps altering prior position jumps/breaks. */\n\tif (looped && f->pbreak != 0) {\n\t\t/* Many implementations use the same variable for both the\n\t\t * jump/break destination row and the loop destination row. */\n\t\tif (HAS_FLOW_MODE(FLOW_LOOP_SHARED_BREAK)) {\n\t\t\tf->jumpline = f->loop_dest;\n\t\t}\n\t\t/* Various players e.g. ST3, IT will block prior breaks. */\n\t\tif (HAS_FLOW_MODE(FLOW_LOOP_UNSET_BREAK) && f->jump < 0) {\n\t\t\tf->pbreak = 0;\n\t\t}\n\t\t/* Various players e.g. ST3, IT will block prior jumps. */\n\t\tif (HAS_FLOW_MODE(FLOW_LOOP_UNSET_JUMP) && f->jump >= 0) {\n\t\t\tf->pbreak = 0;\n\t\t\tf->jump = -1;\n\t\t}\n\t}\n}\n\n/* Process a pattern jump effect with the parameter fxp.\n * This function will not actually perform the jump, but it will\n * prepare the flow variables to perform a jump (unless prevented).\n */\nvoid libxmp_process_pattern_jump(struct context_data *ctx,\n\t\tstruct flow_control *f, int fxp)\n{\n\tstruct module_data *m = &ctx->m;\n\n\t/* Some formats and trackers e.g. S3M, Modplug Tracker 1.16 will\n\t * prevent jumps from being executed when a loop jump occurs. */\n\tif (HAS_FLOW_MODE(FLOW_LOOP_DELAY_JUMP) && f->loop_dest >= 0) {\n\t\treturn;\n\t}\n\n\tf->pbreak = 1;\n\tf->jump = fxp;\n\t/* effect B resets effect D in lower channels */\n\tf->jumpline = 0;\n}\n\n/* Process a pattern break effect with the parameter fxp.\n * This function will not actually perform the jump, but it will\n * prepare the flow variables to perform a jump (unless prevented).\n */\nvoid libxmp_process_pattern_break(struct context_data *ctx,\n\t\tstruct flow_control *f, int fxp)\n{\n\tstruct module_data *m = &ctx->m;\n\n\t/* Some formats and trackers e.g. S3M, IT 2.00+, Modplug Tracker 1.16\n\t * will prevent breaks from being executed when a loop jump occurs. */\n\tif (HAS_FLOW_MODE(FLOW_LOOP_DELAY_BREAK) && f->loop_dest >= 0) {\n\t\treturn;\n\t}\n\n\tf->pbreak = 1;\n\tf->jumpline = fxp;\n}\n\n/* Process a line jump within current position `ord` to row `fxp`.\n * This function will not actually perform the jump, but it will\n * prepare the flow variables to perform a jump (unless prevented).\n */\nvoid libxmp_process_line_jump(struct context_data *ctx,\n\t\tstruct flow_control *f, int ord, int fxp)\n{\n#ifndef LIBXMP_CORE_PLAYER\n\t/* In Digital Symphony, this can be combined with position jump\n\t * (like pattern break) and overrides the pattern break line in\n\t * lower channels. */\n\tif (f->pbreak == 0) {\n\t\tf->pbreak = 1;\n\t\tf->jump = ord;\n\t}\n\tf->jumpline = fxp;\n\tf->jump_in_pat = ord;\n#endif\n}\n"
  },
  {
    "path": "contrib/libxmp/src/format.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"format.h\"\n#ifndef LIBXMP_NO_PROWIZARD\n#include \"loaders/prowizard/prowiz.h\"\n#endif\n\nconst struct format_loader *const format_loaders[NUM_FORMATS + 2] = {\n\t&libxmp_loader_xm,\n\t&libxmp_loader_mod,\n\t&libxmp_loader_it,\n\t&libxmp_loader_s3m,\n\t&libxmp_loader_flt,\n\t&libxmp_loader_st,\n\t&libxmp_loader_stm,\n\t&libxmp_loader_mtm,\n\t&libxmp_loader_ice,\n\t&libxmp_loader_ult,\n\t&libxmp_loader_amf,\n\t&libxmp_loader_asylum,\n\t&libxmp_loader_gdm,\n\t&libxmp_loader_mmd1,\n\t&libxmp_loader_mmd3,\n\t&libxmp_loader_med2,\n\t&libxmp_loader_med3,\n\t&libxmp_loader_med4,\n\t&libxmp_loader_okt,\n\t&libxmp_loader_far,\n\t&libxmp_loader_hmn,\n\t&libxmp_loader_669,\n\tNULL /* list terminator */\n};\n\nstatic const char *_farray[NUM_FORMATS + NUM_PW_FORMATS + 1] = { NULL };\n\nconst char *const *format_list(void)\n{\n\tint count, i;\n\n\tif (_farray[0] == NULL) {\n\t\tfor (count = i = 0; format_loaders[i] != NULL; i++) {\n#ifndef LIBXMP_NO_PROWIZARD\n\t\t\tif (strcmp(format_loaders[i]->name, \"prowizard\") == 0) {\n\t\t\t\tint j;\n\n\t\t\t\tfor (j = 0; pw_formats[j] != NULL; j++) {\n\t\t\t\t\t_farray[count++] = pw_formats[j]->name;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n#endif\n\t\t\t_farray[count++] = format_loaders[i]->name;\n\t\t}\n\n\t\t_farray[count] = NULL;\n\t}\n\n\treturn _farray;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/format.h",
    "content": "#ifndef LIBXMP_FORMAT_H\n#define LIBXMP_FORMAT_H\n\n#include \"common.h\"\n#include \"hio.h\"\n\nstruct format_loader {\n\tconst char *name;\n\tint (*test)(HIO_HANDLE *, char *, const int);\n\tint (*loader)(struct module_data *, HIO_HANDLE *, const int);\n};\n\nextern const struct format_loader *const format_loaders[];\n\nconst char *const *format_list(void);\n\nextern const struct format_loader libxmp_loader_xm;\nextern const struct format_loader libxmp_loader_mod;\nextern const struct format_loader libxmp_loader_it;\nextern const struct format_loader libxmp_loader_s3m;\n\n#ifndef LIBXMP_CORE_PLAYER\nextern const struct format_loader libxmp_loader_flt;\nextern const struct format_loader libxmp_loader_st;\nextern const struct format_loader libxmp_loader_stm;\nextern const struct format_loader libxmp_loader_stx;\nextern const struct format_loader libxmp_loader_mtm;\nextern const struct format_loader libxmp_loader_ice;\nextern const struct format_loader libxmp_loader_imf;\nextern const struct format_loader libxmp_loader_ptm;\nextern const struct format_loader libxmp_loader_mdl;\nextern const struct format_loader libxmp_loader_ult;\nextern const struct format_loader libxmp_loader_liq;\nextern const struct format_loader libxmp_loader_no;\nextern const struct format_loader libxmp_loader_masi;\nextern const struct format_loader libxmp_loader_masi16;\nextern const struct format_loader libxmp_loader_muse;\nextern const struct format_loader libxmp_loader_gal5;\nextern const struct format_loader libxmp_loader_gal4;\nextern const struct format_loader libxmp_loader_amf;\nextern const struct format_loader libxmp_loader_asylum;\nextern const struct format_loader libxmp_loader_gdm;\nextern const struct format_loader libxmp_loader_mmd1;\nextern const struct format_loader libxmp_loader_mmd3;\nextern const struct format_loader libxmp_loader_med2;\nextern const struct format_loader libxmp_loader_med3;\nextern const struct format_loader libxmp_loader_med4;\nextern const struct format_loader libxmp_loader_rtm;\nextern const struct format_loader libxmp_loader_pt3;\nextern const struct format_loader libxmp_loader_dt;\nextern const struct format_loader libxmp_loader_mgt;\nextern const struct format_loader libxmp_loader_arch;\nextern const struct format_loader libxmp_loader_sym;\nextern const struct format_loader libxmp_loader_digi;\nextern const struct format_loader libxmp_loader_dbm;\nextern const struct format_loader libxmp_loader_emod;\nextern const struct format_loader libxmp_loader_okt;\nextern const struct format_loader libxmp_loader_sfx;\nextern const struct format_loader libxmp_loader_far;\nextern const struct format_loader libxmp_loader_umx;\nextern const struct format_loader libxmp_loader_stim;\nextern const struct format_loader libxmp_loader_coco;\nextern const struct format_loader libxmp_loader_ims;\nextern const struct format_loader libxmp_loader_669;\nextern const struct format_loader libxmp_loader_fnk;\nextern const struct format_loader libxmp_loader_mfp;\nextern const struct format_loader libxmp_loader_pw;\nextern const struct format_loader libxmp_loader_hmn;\nextern const struct format_loader libxmp_loader_chip;\nextern const struct format_loader libxmp_loader_abk;\nextern const struct format_loader libxmp_loader_xmf;\n#if 0 /* broken / unused, yet. */\nextern const struct format_loader libxmp_loader_dmf;\nextern const struct format_loader libxmp_loader_tcb;\nextern const struct format_loader libxmp_loader_gtk;\nextern const struct format_loader libxmp_loader_dtt;\nextern const struct format_loader libxmp_loader_mtp;\nextern const struct format_loader libxmp_loader_amd;\nextern const struct format_loader libxmp_loader_rad;\nextern const struct format_loader libxmp_loader_hsc;\nextern const struct format_loader libxmp_loader_alm;\nextern const struct format_loader libxmp_loader_polly;\nextern const struct format_loader libxmp_loader_stc;\n#endif\n#endif /* LIBXMP_CORE_PLAYER */\n\n#ifndef LIBXMP_CORE_PLAYER\n#define NUM_FORMATS 52\n#elif !defined(LIBXMP_CORE_DISABLE_IT)\n#define NUM_FORMATS 4\n#else\n#define NUM_FORMATS 3\n#endif\n\n#ifndef LIBXMP_NO_PROWIZARD\n#define NUM_PW_FORMATS 43\nextern const struct pw_format *const pw_formats[];\nint pw_test_format(HIO_HANDLE *, char *, const int, struct xmp_test_info *);\n#else\n#define NUM_PW_FORMATS 0\n#endif\n\n#endif /* LIBXMP_FORMAT_H */\n"
  },
  {
    "path": "contrib/libxmp/src/hio.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <errno.h>\n#include \"common.h\"\n#include \"hio.h\"\n#include \"callbackio.h\"\n#include \"mdataio.h\"\n\nstatic long get_size(FILE *f)\n{\n\tlong size, pos;\n\n\tpos = ftell(f);\n\tif (pos >= 0) {\n\t\tif (fseek(f, 0, SEEK_END) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\tsize = ftell(f);\n\t\tif (fseek(f, pos, SEEK_SET) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\treturn size;\n\t} else {\n\t\treturn pos;\n\t}\n}\n\nint8 hio_read8s(HIO_HANDLE *h)\n{\n\tint err;\n\tint8 ret;\n\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\tret = read8s(h->handle.file, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\tret = mread8s(h->handle.mem, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\tret = cbread8s(h->handle.cbfile, &err);\n\t\tbreak;\n\tdefault:\n\t\treturn 0;\n\t}\n\n\tif (err != 0) {\n\t\th->error = err;\n\t}\n\treturn ret;\n}\n\nuint8 hio_read8(HIO_HANDLE *h)\n{\n\tint err;\n\tuint8 ret;\n\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\tret = read8(h->handle.file, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\tret = mread8(h->handle.mem, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\tret = cbread8(h->handle.cbfile, &err);\n\t\tbreak;\n\tdefault:\n\t\treturn 0;\n\t}\n\n\tif (err != 0) {\n\t\th->error = err;\n\t}\n\treturn ret;\n}\n\nuint16 hio_read16l(HIO_HANDLE *h)\n{\n\tint err;\n\tuint16 ret;\n\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\tret = read16l(h->handle.file, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\tret = mread16l(h->handle.mem, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\tret = cbread16l(h->handle.cbfile, &err);\n\t\tbreak;\n\tdefault:\n\t\treturn 0;\n\t}\n\n\tif (err != 0) {\n\t\th->error = err;\n\t}\n\treturn ret;\n}\n\nuint16 hio_read16b(HIO_HANDLE *h)\n{\n\tint err;\n\tuint16 ret;\n\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\tret = read16b(h->handle.file, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\tret = mread16b(h->handle.mem, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\tret = cbread16b(h->handle.cbfile, &err);\n\t\tbreak;\n\tdefault:\n\t\treturn 0;\n\t}\n\n\tif (err != 0) {\n\t\th->error = err;\n\t}\n\treturn ret;\n}\n\nuint32 hio_read24l(HIO_HANDLE *h)\n{\n\tint err;\n\tuint32 ret;\n\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\tret = read24l(h->handle.file, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\tret = mread24l(h->handle.mem, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\tret = cbread24l(h->handle.cbfile, &err);\n\t\tbreak;\n\tdefault:\n\t\treturn 0;\n\t}\n\n\tif (err != 0) {\n\t\th->error = err;\n\t}\n\treturn ret;\n}\n\nuint32 hio_read24b(HIO_HANDLE *h)\n{\n\tint err;\n\tuint32 ret;\n\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\tret = read24b(h->handle.file, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\tret = mread24b(h->handle.mem, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\tret = cbread24b(h->handle.cbfile, &err);\n\t\tbreak;\n\tdefault:\n\t\treturn 0;\n\t}\n\n\tif (err != 0) {\n\t\th->error = err;\n\t}\n\treturn ret;\n}\n\nuint32 hio_read32l(HIO_HANDLE *h)\n{\n\tint err;\n\tuint32 ret;\n\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\tret = read32l(h->handle.file, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\tret = mread32l(h->handle.mem, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\tret = cbread32l(h->handle.cbfile, &err);\n\t\tbreak;\n\tdefault:\n\t\treturn 0;\n\t}\n\n\tif (err != 0) {\n\t\th->error = err;\n\t}\n\treturn ret;\n}\n\nuint32 hio_read32b(HIO_HANDLE *h)\n{\n\tint err;\n\tuint32 ret;\n\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\tret = read32b(h->handle.file, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\tret = mread32b(h->handle.mem, &err);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\tret = cbread32b(h->handle.cbfile, &err);\n\t\tbreak;\n\tdefault:\n\t\treturn 0;\n\t}\n\n\tif (err != 0) {\n\t\th->error = err;\n\t}\n\treturn ret;\n}\n\nsize_t hio_read(void *buf, size_t size, size_t num, HIO_HANDLE *h)\n{\n\tsize_t ret = 0;\n\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\tret = fread(buf, size, num, h->handle.file);\n\t\tif (ret != num) {\n\t\t\tif (ferror(h->handle.file)) {\n\t\t\t\th->error = errno;\n\t\t\t} else {\n\t\t\t\th->error = feof(h->handle.file) ? EOF : -2;\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\tret = mread(buf, size, num, h->handle.mem);\n\t\tif (ret != num) {\n\t\t\th->error = EOF;\n\t\t}\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\tret = cbread(buf, size, num, h->handle.cbfile);\n\t\tif (ret != num) {\n\t\t\th->error = EOF;\n\t\t}\n\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\nint hio_seek(HIO_HANDLE *h, long offset, int whence)\n{\n\tint ret = -1;\n\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\tret = fseek(h->handle.file, offset, whence);\n\t\tif (ret < 0) {\n\t\t\th->error = errno;\n\t\t}\n\t\telse if (h->error == EOF) {\n\t\t\th->error = 0;\n\t\t}\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\tret = mseek(h->handle.mem, offset, whence);\n\t\tif (ret < 0) {\n\t\t\th->error = EINVAL;\n\t\t}\n\t\telse if (h->error == EOF) {\n\t\t\th->error = 0;\n\t\t}\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\tret = cbseek(h->handle.cbfile, offset, whence);\n\t\tif (ret < 0) {\n\t\t\th->error = EINVAL;\n\t\t}\n\t\telse if (h->error == EOF) {\n\t\t\th->error = 0;\n\t\t}\n\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\nlong hio_tell(HIO_HANDLE *h)\n{\n\tlong ret = -1;\n\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\tret = ftell(h->handle.file);\n\t\tif (ret < 0) {\n\t\t\th->error = errno;\n\t\t}\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\tret = mtell(h->handle.mem);\n\t\tif (ret < 0) {\n\t\t/* should _not_ happen! */\n\t\t\th->error = EINVAL;\n\t\t}\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\tret = cbtell(h->handle.cbfile);\n\t\tif (ret < 0) {\n\t\t\th->error = EINVAL;\n\t\t}\n\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\nint hio_eof(HIO_HANDLE *h)\n{\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\treturn feof(h->handle.file);\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\treturn meof(h->handle.mem);\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\treturn cbeof(h->handle.cbfile);\n\t}\n\treturn EOF;\n}\n\nint hio_error(HIO_HANDLE *h)\n{\n\tint error = h->error;\n\th->error = 0;\n\treturn error;\n}\n\nHIO_HANDLE *hio_open(const char *path, const char *mode)\n{\n\tHIO_HANDLE *h;\n\n\th = (HIO_HANDLE *) calloc(1, sizeof(HIO_HANDLE));\n\tif (h == NULL)\n\t\tgoto err;\n\n\th->type = HIO_HANDLE_TYPE_FILE;\n\th->handle.file = fopen(path, mode);\n\tif (h->handle.file == NULL)\n\t\tgoto err2;\n\n\th->size = get_size(h->handle.file);\n\tif (h->size < 0)\n\t\tgoto err3;\n\n\treturn h;\n\n    err3:\n\tfclose(h->handle.file);\n    err2:\n\tfree(h);\n    err:\n\treturn NULL;\n}\n\nHIO_HANDLE *hio_open_const_mem(const void *ptr, long size)\n{\n\tHIO_HANDLE *h;\n\n\tif (size <= 0) return NULL;\n\th = (HIO_HANDLE *) calloc(1, sizeof(HIO_HANDLE));\n\tif (h == NULL)\n\t\treturn NULL;\n\n\th->type = HIO_HANDLE_TYPE_MEMORY;\n\th->handle.mem = mcopen(ptr, size);\n\th->size = size;\n\n\tif (!h->handle.mem) {\n\t\tfree(h);\n\t\th = NULL;\n\t}\n\n\treturn h;\n}\n\nHIO_HANDLE *hio_open_file(FILE *f)\n{\n\tHIO_HANDLE *h;\n\n\th = (HIO_HANDLE *) calloc(1, sizeof(HIO_HANDLE));\n\tif (h == NULL)\n\t\treturn NULL;\n\n\th->noclose = 1;\n\th->type = HIO_HANDLE_TYPE_FILE;\n\th->handle.file = f;\n\th->size = get_size(f);\n\tif (h->size < 0) {\n\t\tfree(h);\n\t\treturn NULL;\n\t}\n\n\treturn h;\n}\n\nHIO_HANDLE *hio_open_file2(FILE *f)\n{\n\tHIO_HANDLE *h = hio_open_file(f);\n\tif (h != NULL) {\n\t\th->noclose = 0;\n\t}\n\telse {\n\t\tfclose(f);\n\t}\n\treturn h;\n}\n\nHIO_HANDLE *hio_open_callbacks(void *priv, struct xmp_callbacks callbacks)\n{\n\tHIO_HANDLE *h;\n\tCBFILE *f = cbopen(priv, callbacks);\n\tif (!f)\n\t\treturn NULL;\n\n\th = (HIO_HANDLE *) calloc(1, sizeof(HIO_HANDLE));\n\tif (h == NULL) {\n\t\tcbclose(f);\n\t\treturn NULL;\n\t}\n\n\th->type = HIO_HANDLE_TYPE_CBFILE;\n\th->handle.cbfile = f;\n\th->size = cbfilelength(f);\n\tif (h->size < 0) {\n\t\tcbclose(f);\n\t\tfree(h);\n\t\treturn NULL;\n\t}\n\treturn h;\n}\n\nstatic int hio_close_internal(HIO_HANDLE *h)\n{\n\tint ret = -1;\n\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\t\tret = (h->noclose)? 0 : fclose(h->handle.file);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\tret = mclose(h->handle.mem);\n\t\tbreak;\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\tret = cbclose(h->handle.cbfile);\n\t\tbreak;\n\t}\n\treturn ret;\n}\n\n/* hio_close + hio_open_mem. Reuses the same HIO_HANDLE. */\nint hio_reopen_mem(void *ptr, long size, int free_after_use, HIO_HANDLE *h)\n{\n\tMFILE *m;\n\tint ret;\n\tif (size <= 0) return -1;\n\n\tm = mopen(ptr, size, free_after_use);\n\tif (m == NULL) {\n\t\treturn -1;\n\t}\n\n\tret = hio_close_internal(h);\n\tif (ret < 0) {\n\t\tm->ptr_free = NULL;\n\t\tmclose(m);\n\t\treturn ret;\n\t}\n\n\th->type = HIO_HANDLE_TYPE_MEMORY;\n\th->handle.mem = m;\n\th->size = size;\n\treturn 0;\n}\n\n/* hio_close + hio_open_file. Reuses the same HIO_HANDLE. */\nint hio_reopen_file(FILE *f, int close_after_use, HIO_HANDLE *h)\n{\n\tlong size = get_size(f);\n\tint ret;\n\tif (size < 0) {\n\t\treturn -1;\n\t}\n\n\tret = hio_close_internal(h);\n\tif (ret < 0) {\n\t\treturn -1;\n\t}\n\n\th->noclose = !close_after_use;\n\th->type = HIO_HANDLE_TYPE_FILE;\n\th->handle.file = f;\n\th->size = size;\n\treturn 0;\n}\n\nint hio_close(HIO_HANDLE *h)\n{\n\tint ret = hio_close_internal(h);\n\tfree(h);\n\treturn ret;\n}\n\nlong hio_size(HIO_HANDLE *h)\n{\n\treturn h->size;\n}\n\n/* Returns a pointer to the underlying continuous memory buffer the entire\n * contents of HIO_HANDLE `h` are stored at if applicable, otherwise NULL.\n * Do not reallocate this pointer or modify its underlying data!\n */\nconst unsigned char *hio_get_underlying_memory(HIO_HANDLE *h)\n{\n\tswitch (HIO_HANDLE_TYPE(h)) {\n\tcase HIO_HANDLE_TYPE_FILE:\n\tcase HIO_HANDLE_TYPE_CBFILE:\n\t\treturn NULL;\n\tcase HIO_HANDLE_TYPE_MEMORY:\n\t\treturn h->handle.mem->start;\n\t}\n\treturn NULL;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/hio.h",
    "content": "#ifndef XMP_HIO_H\n#define XMP_HIO_H\n\n#include \"callbackio.h\"\n#include \"memio.h\"\n\n#define HIO_HANDLE_TYPE(x) ((x)->type)\n\nenum hio_type {\n\tHIO_HANDLE_TYPE_FILE,\n\tHIO_HANDLE_TYPE_MEMORY,\n\tHIO_HANDLE_TYPE_CBFILE\n};\n\ntypedef struct {\n\tenum hio_type type;\n\tlong size;\n\tunion {\n\t\tFILE *file;\n\t\tMFILE *mem;\n\t\tCBFILE *cbfile;\n\t} handle;\n\tint error;\n\tint noclose;\n} HIO_HANDLE;\n\nint8\thio_read8s\t(HIO_HANDLE *);\nuint8\thio_read8\t(HIO_HANDLE *);\nuint16\thio_read16l\t(HIO_HANDLE *);\nuint16\thio_read16b\t(HIO_HANDLE *);\nuint32\thio_read24l\t(HIO_HANDLE *);\nuint32\thio_read24b\t(HIO_HANDLE *);\nuint32\thio_read32l\t(HIO_HANDLE *);\nuint32\thio_read32b\t(HIO_HANDLE *);\nsize_t\thio_read\t(void *, size_t, size_t, HIO_HANDLE *);\nint\thio_seek\t(HIO_HANDLE *, long, int);\nlong\thio_tell\t(HIO_HANDLE *);\nint\thio_eof\t\t(HIO_HANDLE *);\nint\thio_error\t(HIO_HANDLE *);\nHIO_HANDLE *hio_open\t(const char *, const char *);\nHIO_HANDLE *hio_open_const_mem  (const void *, long);\nHIO_HANDLE *hio_open_file (FILE *);\nHIO_HANDLE *hio_open_file2 (FILE *);/* allows fclose()ing the file by libxmp */\nHIO_HANDLE *hio_open_callbacks (void *, struct xmp_callbacks);\nint\thio_reopen_mem\t(void *, long, int, HIO_HANDLE *);\nint\thio_reopen_file\t(FILE *, int, HIO_HANDLE *);\nint\thio_close\t(HIO_HANDLE *);\nlong\thio_size\t(HIO_HANDLE *);\nconst unsigned char *hio_get_underlying_memory(HIO_HANDLE *);\n\n#endif\n"
  },
  {
    "path": "contrib/libxmp/src/hmn_extras.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n#include \"player.h\"\n#include \"virtual.h\"\n#include \"effects.h\"\n#include \"hmn_extras.h\"\n\nstatic const uint8 megaarp[16][16] = {\n\t{  0,  3,  7, 12, 15, 12,  7,  3,  0,  3,  7, 12, 15, 12,  7,  3 },\n\t{  0,  4,  7, 12, 16, 12,  7,  4,  0,  4,  7, 12, 16, 12,  7,  4 },\n\t{  0,  3,  8, 12, 15, 12,  8,  3,  0,  3,  8, 12, 15, 12,  8,  3 },\n\t{  0,  4,  8, 12, 16, 12,  8,  4,  0,  4,  8, 12, 16, 12,  8,  4 },\n\t{  0,  5,  8, 12, 17, 12,  8,  5,  0,  5,  8, 12, 17, 12,  8,  5 },\n\t{  0,  5,  9, 12, 17, 12,  9,  5,  0,  5,  9, 12, 17, 12,  9,  5 },\n\t{ 12,  0,  7,  0,  3,  0,  7,  0, 12,  0,  7,  0,  3,  0,  7,  0 },\n\t{ 12,  0,  7,  0,  4,  0,  7,  0, 12,  0,  7,  0,  4,  0,  7,  0 },\n\n\t{  0,  3,  7,  3,  7, 12,  7, 12, 15, 12,  7, 12,  7,  3,  7,  3 },\n\t{  0,  4,  7,  4,  7, 12,  7, 12, 16, 12,  7, 12,  7,  4,  7,  4 },\n\t{ 31, 27, 24, 19, 15, 12,  7,  3,  0,  3,  7, 12, 15, 19, 24, 27 },\n\t{ 31, 28, 24, 19, 16, 12,  7,  4,  0,  4,  7, 12, 16, 19, 24, 28 },\n\t{  0, 12,  0, 12,  0, 12,  0, 12,  0, 12,  0, 12,  0, 12,  0, 12 },\n\t{  0, 12, 24, 12,  0, 12, 24, 12,  0, 12, 24, 12,  0, 12, 24, 12 },\n\t{  0,  3,  0,  3,  0,  3,  0,  3,  0,  3,  0,  3,  0,  3,  0,  3 },\n\t{  0,  4,  0,  4,  0,  4,  0,  4,  0,  4,  0,  4,  0,  4,  0,  4 }\n};\n\n\nint libxmp_hmn_linear_bend(struct context_data *ctx, struct channel_data *xc)\n{\n\treturn 0;\n}\n\nvoid libxmp_hmn_play_extras(struct context_data *ctx, struct channel_data *xc, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct hmn_channel_extras *ce = (struct hmn_channel_extras *)xc->extra;\n\tstruct xmp_instrument *xxi;\n\tint pos, waveform, volume;\n\n\tif (p->frame == 0 && TEST(NEW_NOTE|NEW_INS)) {\n\t\tce->datapos = 0;\n\t}\n\n\txxi = &m->mod.xxi[xc->ins];\n\tpos = ce->datapos & 63; /* TODO: how are out of bounds values handled? */\n\twaveform = HMN_INSTRUMENT_EXTRAS(m->mod.xxi[xc->ins])->data[pos];\n\tvolume = HMN_INSTRUMENT_EXTRAS(m->mod.xxi[xc->ins])->progvolume[pos] & 0x7f;\n\n\tif (waveform < xxi->nsm && xxi->sub[waveform].sid != xc->smp) {\n\t\txc->smp = xxi->sub[waveform].sid;\n\t\tlibxmp_virt_setsmp(ctx, chn, xc->smp);\n\t}\n\n\tpos++;\n\tif (pos > HMN_INSTRUMENT_EXTRAS(m->mod.xxi[xc->ins])->dataloopend)\n\t\tpos = HMN_INSTRUMENT_EXTRAS(m->mod.xxi[xc->ins])->dataloopstart;\n\n\tce->datapos = pos;\n\tce->volume = volume;\n}\n\nint libxmp_hmn_new_instrument_extras(struct xmp_instrument *xxi)\n{\n\txxi->extra = calloc(1, sizeof(struct hmn_instrument_extras));\n\tif (xxi->extra == NULL)\n\t\treturn -1;\n\tHMN_INSTRUMENT_EXTRAS((*xxi))->magic = HMN_EXTRAS_MAGIC;\n\treturn 0;\n}\n\nint libxmp_hmn_new_channel_extras(struct channel_data *xc)\n{\n\txc->extra = calloc(1, sizeof(struct hmn_channel_extras));\n\tif (xc->extra == NULL)\n\t\treturn -1;\n\tHMN_CHANNEL_EXTRAS((*xc))->magic = HMN_EXTRAS_MAGIC;\n\treturn 0;\n}\n\nvoid libxmp_hmn_reset_channel_extras(struct channel_data *xc)\n{\n\tmemset((char *)xc->extra + 4, 0, sizeof(struct hmn_channel_extras) - 4);\n}\n\nvoid libxmp_hmn_release_channel_extras(struct channel_data *xc)\n{\n\tfree(xc->extra);\n\txc->extra = NULL;\n}\n\nint libxmp_hmn_new_module_extras(struct module_data *m)\n{\n\tm->extra = calloc(1, sizeof(struct hmn_module_extras));\n\tif (m->extra == NULL)\n\t\treturn -1;\n\tHMN_MODULE_EXTRAS((*m))->magic = HMN_EXTRAS_MAGIC;\n\treturn 0;\n}\n\nvoid libxmp_hmn_release_module_extras(struct module_data *m)\n{\n\tfree(m->extra);\n\tm->extra = NULL;\n}\n\nvoid libxmp_hmn_extras_process_fx(struct context_data *ctx, struct channel_data *xc,\n\t\t\t   int chn, uint8 note, uint8 fxt, uint8 fxp, int fnum)\n{\n\tswitch (fxt) {\n\tcase FX_MEGAARP:\n\t\t/* Not sure if this is correct... */\n\t\tfxp = LSN(fxp);\n\n\t\tmemcpy(xc->arpeggio.val, megaarp[fxp], 16);\n\t\txc->arpeggio.size = 16;\n\t\tbreak;\n\t}\n}\n"
  },
  {
    "path": "contrib/libxmp/src/hmn_extras.h",
    "content": "#ifndef XMP_HMN_EXTRAS_H\n#define XMP_HMN_EXTRAS_H\n\n#define HMN_EXTRAS_MAGIC 0x041bc81a\n\nstruct hmn_instrument_extras {\n\tuint32 magic;\n\tint dataloopstart;\n\tint dataloopend;\n\tuint8 data[64];\n\tuint8 progvolume[64];\n};\n\nstruct hmn_channel_extras {\n\tuint32 magic;\n\tint datapos;\t\t/* HMN waveform table pointer */\n\tint volume;\t\t/* HMN synth volume */\n};\n \nstruct hmn_module_extras {\n\tuint32 magic;\n};\n\n#define HMN_INSTRUMENT_EXTRAS(x) ((struct hmn_instrument_extras *)(x).extra)\n#define HAS_HMN_INSTRUMENT_EXTRAS(x) \\\n\t(HMN_INSTRUMENT_EXTRAS(x) != NULL && \\\n\t HMN_INSTRUMENT_EXTRAS(x)->magic == HMN_EXTRAS_MAGIC)\n\n#define HMN_CHANNEL_EXTRAS(x) ((struct hmn_channel_extras *)(x).extra)\n#define HAS_HMN_CHANNEL_EXTRAS(x) \\\n\t(HMN_CHANNEL_EXTRAS(x) != NULL && \\\n\t HMN_CHANNEL_EXTRAS(x)->magic == HMN_EXTRAS_MAGIC)\n\n#define HMN_MODULE_EXTRAS(x) ((struct hmn_module_extras *)(x).extra)\n#define HAS_HMN_MODULE_EXTRAS(x) \\\n\t(HMN_MODULE_EXTRAS(x) != NULL && \\\n\t HMN_MODULE_EXTRAS(x)->magic == HMN_EXTRAS_MAGIC)\n\nvoid libxmp_hmn_play_extras(struct context_data *, struct channel_data *, int);\nvoid libxmp_hmn_set_arpeggio(struct channel_data *, int);\nint  libxmp_hmn_linear_bend(struct context_data *, struct channel_data *);\nint  libxmp_hmn_new_instrument_extras(struct xmp_instrument *);\nint  libxmp_hmn_new_channel_extras(struct channel_data *);\nvoid libxmp_hmn_reset_channel_extras(struct channel_data *);\nvoid libxmp_hmn_release_channel_extras(struct channel_data *);\nint  libxmp_hmn_new_module_extras(struct module_data *);\nvoid libxmp_hmn_release_module_extras(struct module_data *);\nvoid libxmp_hmn_extras_process_fx(struct context_data *, struct channel_data *, int, uint8, uint8, uint8, int);\n\n#endif\n\n"
  },
  {
    "path": "contrib/libxmp/src/lfo.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"lfo.h\"\n#include \"rng.h\"\n\n#define WAVEFORM_SIZE 64\n\nstatic const int sine_wave[WAVEFORM_SIZE] = {\n\t   0,  24,  49,  74,  97, 120, 141, 161, 180, 197, 212, 224,\n\t 235, 244, 250, 253, 255, 253, 250, 244, 235, 224, 212, 197,\n\t 180, 161, 141, 120,  97,  74,  49,  24,   0, -24, -49, -74,\n\t -97,-120,-141,-161,-180,-197,-212,-224,-235,-244,-250,-253,\n\t-255,-253,-250,-244,-235,-224,-212,-197,-180,-161,-141,-120,\n\t -97, -74, -49, -24\n};\n\n/* LFO */\n\nstatic int get_lfo_mod(struct context_data *ctx, struct lfo *lfo)\n{\n\tint val;\n\n\tif (lfo->rate == 0)\n\t\treturn 0;\n\n\tswitch (lfo->type) {\n\tcase 0: /* sine */\n\t\tval = sine_wave[lfo->phase];\n\t\tbreak;\n\tcase 1:\t/* ramp down */\n\t\tval = 255 - (lfo->phase << 3);\n\t\tbreak;\n\tcase 2:\t/* square */\n\t\tval = lfo->phase < WAVEFORM_SIZE / 2 ? 255 : -255;\n\t\tbreak;\n\tcase 3: /* random */\n\t\tval = libxmp_get_random(&ctx->rng, 512) - 256;\n\t\tbreak;\n#ifndef LIBXMP_CORE_PLAYER\n\tcase 669: /* 669 vibrato */\n\t\tval = lfo->phase & 1;\n\t\tbreak;\n#endif\n\tdefault:\n\t\treturn 0;\n\t}\n\n\treturn val * lfo->depth;\n}\n\nstatic int get_lfo_st3(struct context_data *ctx, struct lfo *lfo)\n{\n\tif (lfo->rate == 0) {\n\t\treturn 0;\n\t}\n\n\t/* S3M square */\n\tif (lfo->type == 2) {\n\t\tint val = lfo->phase < WAVEFORM_SIZE / 2 ? 255 : 0;\n\t\treturn val * lfo->depth;\n\t}\n\n\treturn get_lfo_mod(ctx, lfo);\n}\n\n/* From OpenMPT VibratoWaveforms.xm:\n * \"Generally the vibrato and tremolo tables are identical to those that\n *  ProTracker uses, but the vibrato’s “ramp down” table is upside down.\"\n */\nstatic int get_lfo_ft2(struct context_data *ctx, struct lfo *lfo)\n{\n\tif (lfo->rate == 0)\n\t\treturn 0;\n\n\t/* FT2 ramp */\n\tif (lfo->type == 1) {\n\t\tint phase = (lfo->phase + (WAVEFORM_SIZE >> 1)) % WAVEFORM_SIZE;\n\t\tint val = (phase << 3) - 255;\n\t\treturn val * lfo->depth;\n\t}\n\n\treturn get_lfo_mod(ctx, lfo);\n}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\nstatic int get_lfo_it(struct context_data *ctx, struct lfo *lfo)\n{\n\tif (lfo->rate == 0)\n\t\treturn 0;\n\n\treturn get_lfo_st3(ctx, lfo);\n}\n\n#endif\n\nint libxmp_lfo_get(struct context_data *ctx, struct lfo *lfo, int is_vibrato)\n{\n\tstruct module_data *m = &ctx->m;\n\n\tswitch (m->read_event_type) {\n\tcase READ_EVENT_ST3:\n\t\treturn get_lfo_st3(ctx, lfo);\n\tcase READ_EVENT_FT2:\n\t\tif (is_vibrato) {\n\t\t\treturn get_lfo_ft2(ctx, lfo);\n\t\t} else {\n\t\t\treturn get_lfo_mod(ctx, lfo);\n\t\t}\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tcase READ_EVENT_IT:\n\t\treturn get_lfo_it(ctx, lfo);\n#endif\n\tdefault:\n\t\treturn get_lfo_mod(ctx, lfo);\n\t}\n}\n\n\nvoid libxmp_lfo_update(struct lfo *lfo)\n{\n\tlfo->phase += lfo->rate;\n\tlfo->phase &= WAVEFORM_SIZE - 1; /* Rate may be negative, don't %= */\n}\n\nvoid libxmp_lfo_set_phase(struct lfo *lfo, int phase)\n{\n\tlfo->phase = phase;\n}\n\nvoid libxmp_lfo_set_depth(struct lfo *lfo, int depth)\n{\n\tlfo->depth = depth;\n}\n\nvoid libxmp_lfo_set_rate(struct lfo *lfo, int rate)\n{\n\tlfo->rate = rate;\n}\n\nvoid libxmp_lfo_set_waveform(struct lfo *lfo, int type)\n{\n\tlfo->type = type;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/lfo.h",
    "content": "#ifndef LIBXMP_LFO_H\n#define LIBXMP_LFO_H\n\n#include \"common.h\"\n\nstruct lfo {\n\tint type;\n\tint rate;\n\tint depth;\n\tint phase;\n};\n\nint  libxmp_lfo_get(struct context_data *, struct lfo *, int);\nvoid libxmp_lfo_update(struct lfo *);\nvoid libxmp_lfo_set_phase(struct lfo *, int);\nvoid libxmp_lfo_set_depth(struct lfo *, int);\nvoid libxmp_lfo_set_rate(struct lfo *, int);\nvoid libxmp_lfo_set_waveform(struct lfo *, int);\n\n#endif\n"
  },
  {
    "path": "contrib/libxmp/src/list.h",
    "content": "#ifndef LIBXMP_LIST_H\n#define LIBXMP_LIST_H\n\n#include <stddef.h> /* offsetof */\n\n/*\n * Simple doubly linked list implementation.\n *\n * Some of the internal functions (\"__xxx\") are useful when\n * manipulating whole lists rather than single entries, as\n * sometimes we already know the next/prev entries and we can\n * generate better code by using them directly rather than\n * using the generic single-entry routines.\n */\n\nstruct list_head {\n\tstruct list_head *next, *prev;\n};\n\n#define LIST_HEAD_INIT(name) { &(name), &(name) }\n\n#define LIST_HEAD(name) \\\n\tstruct list_head name = LIST_HEAD_INIT(name)\n\n#define INIT_LIST_HEAD(ptr) do { \\\n\t(ptr)->next = (ptr); (ptr)->prev = (ptr); \\\n} while (0)\n\n/*\n * Insert a new entry between two known consecutive entries.\n *\n * This is only for internal list manipulation where we know\n * the prev/next entries already!\n */\nstatic inline void __list_add(struct list_head *_new,\n\tstruct list_head * prev,\n\tstruct list_head * next)\n{\n\tnext->prev = _new;\n\t_new->next = next;\n\t_new->prev = prev;\n\tprev->next = _new;\n}\n\n/**\n * list_add - add a new entry\n * @_new: new entry to be added\n * @head: list head to add it after\n *\n * Insert a new entry after the specified head.\n * This is good for implementing stacks.\n */\nstatic inline void list_add(struct list_head *_new, struct list_head *head)\n{\n\t__list_add(_new, head, head->next);\n}\n\n/**\n * list_add_tail - add a new entry\n * @_new: new entry to be added\n * @head: list head to add it before\n *\n * Insert a new entry before the specified head.\n * This is useful for implementing queues.\n */\nstatic inline void list_add_tail(struct list_head *_new, struct list_head *head)\n{\n\t__list_add(_new, head->prev, head);\n}\n\n/*\n * Delete a list entry by making the prev/next entries\n * point to each other.\n *\n * This is only for internal list manipulation where we know\n * the prev/next entries already!\n */\nstatic inline void __list_del(struct list_head * prev,\n\t\t\t\t  struct list_head * next)\n{\n\tnext->prev = prev;\n\tprev->next = next;\n}\n\n/**\n * list_del - deletes entry from list.\n * @entry: the element to delete from the list.\n */\nstatic inline void list_del(struct list_head *entry)\n{\n\t__list_del(entry->prev, entry->next);\n}\n\n/**\n * list_empty - tests whether a list is empty\n * @head: the list to test.\n */\nstatic inline int list_empty(struct list_head *head)\n{\n\treturn head->next == head;\n}\n\n/**\n * list_splice - join two lists\n * @list: the new list to add.\n * @head: the place to add it in the first list.\n */\nstatic inline void list_splice(struct list_head *list, struct list_head *head)\n{\n\tstruct list_head *first = list->next;\n\n\tif (first != list) {\n\t\tstruct list_head *last = list->prev;\n\t\tstruct list_head *at = head->next;\n\n\t\tfirst->prev = head;\n\t\thead->next = first;\n\n\t\tlast->next = at;\n\t\tat->prev = last;\n\t}\n}\n\n/**\n * list_entry - get the struct for this entry\n * @ptr:\tthe &struct list_head pointer.\n * @type:\tthe type of the struct this is embedded in.\n * @member:\tthe name of the list_struct within the struct.\n */\n#define list_entry(ptr, type, member) \\\n\t((type *)((char *)(ptr) - offsetof(type, member)))\n\n/**\n * list_for_each\t-\titerate over a list\n * @pos:\tthe &struct list_head to use as a loop counter.\n * @head:\tthe head for your list.\n */\n#define list_for_each(pos, head) \\\n\tfor (pos = (head)->next; pos != (head); pos = pos->next)\n\n#endif  /* LIBXMP_LIST_H */\n"
  },
  {
    "path": "contrib/libxmp/src/load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <errno.h>\n\n#include \"format.h\"\n#include \"list.h\"\n#include \"hio.h\"\n#include \"loaders/loader.h\"\n\n#ifndef LIBXMP_NO_DEPACKERS\n#include \"tempfile.h\"\n#include \"depackers/depacker.h\"\n#endif\n\n#ifndef LIBXMP_CORE_PLAYER\n#include \"md5.h\"\n#include \"extras.h\"\n#endif\n\n\nvoid libxmp_load_prologue(struct context_data *);\nvoid libxmp_load_epilogue(struct context_data *);\nint  libxmp_prepare_scan(struct context_data *);\n\n#ifndef LIBXMP_CORE_PLAYER\n#define BUFLEN 16384\n\nstatic void set_md5sum(HIO_HANDLE *f, unsigned char *digest)\n{\n\tunsigned char buf[BUFLEN];\n\tMD5_CTX ctx;\n\tint bytes_read;\n\n\thio_seek(f, 0, SEEK_SET);\n\n\tMD5Init(&ctx);\n\twhile ((bytes_read = hio_read(buf, 1, BUFLEN, f)) > 0) {\n\t\tMD5Update(&ctx, buf, bytes_read);\n\t}\n\tMD5Final(digest, &ctx);\n}\n\nstatic char *get_dirname(const char *name)\n{\n\tchar *dirname;\n\tconst char *p;\n\tptrdiff_t len;\n\n\tif ((p = strrchr(name, '/')) != NULL) {\n\t\tlen = p - name + 1;\n\t\tdirname = (char *) malloc(len + 1);\n\t\tif (dirname != NULL) {\n\t\t\tmemcpy(dirname, name, len);\n\t\t\tdirname[len] = 0;\n\t\t}\n\t} else {\n\t\tdirname = libxmp_strdup(\"\");\n\t}\n\n\treturn dirname;\n}\n\nstatic char *get_basename(const char *name)\n{\n\tconst char *p;\n\tchar *basename;\n\n\tif ((p = strrchr(name, '/')) != NULL) {\n\t\tbasename = libxmp_strdup(p + 1);\n\t} else {\n\t\tbasename = libxmp_strdup(name);\n\t}\n\n\treturn basename;\n}\n#endif /* LIBXMP_CORE_PLAYER */\n\nstatic int test_module(struct xmp_test_info *info, HIO_HANDLE *h)\n{\n\tchar buf[XMP_NAME_SIZE];\n\tint i;\n\n\tif (info != NULL) {\n\t\t*info->name = 0;\t/* reset name prior to testing */\n\t\t*info->type = 0;\t/* reset type prior to testing */\n\t}\n\n\tfor (i = 0; format_loaders[i] != NULL; i++) {\n\t\thio_seek(h, 0, SEEK_SET);\n\t\tif (format_loaders[i]->test(h, buf, 0) == 0) {\n\t\t\tint is_prowizard = 0;\n\n#ifndef LIBXMP_NO_PROWIZARD\n\t\t\tif (strcmp(format_loaders[i]->name, \"prowizard\") == 0) {\n\t\t\t\thio_seek(h, 0, SEEK_SET);\n\t\t\t\tpw_test_format(h, buf, 0, info);\n\t\t\t\tis_prowizard = 1;\n\t\t\t}\n#endif\n\n\t\t\tif (info != NULL && !is_prowizard) {\n\t\t\t\tstrncpy(info->name, buf, XMP_NAME_SIZE - 1);\n\t\t\t\tinfo->name[XMP_NAME_SIZE - 1] = '\\0';\n\n\t\t\t\tstrncpy(info->type, format_loaders[i]->name,\n\t\t\t\t\t\t\tXMP_NAME_SIZE - 1);\n\t\t\t\tinfo->type[XMP_NAME_SIZE - 1] = '\\0';\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t}\n\treturn -XMP_ERROR_FORMAT;\n}\n\nint xmp_test_module(const char *path, struct xmp_test_info *info)\n{\n\tHIO_HANDLE *h;\n#ifndef LIBXMP_NO_DEPACKERS\n\tchar *temp = NULL;\n#endif\n\tint ret;\n\n\tret = libxmp_get_filetype(path);\n\n\tif (ret == XMP_FILETYPE_NONE) {\n\t\treturn -XMP_ERROR_SYSTEM;\n\t}\n\tif (ret & XMP_FILETYPE_DIR) {\n\t\terrno = EISDIR;\n\t\treturn -XMP_ERROR_SYSTEM;\n\t}\n\n\tif ((h = hio_open(path, \"rb\")) == NULL)\n\t\treturn -XMP_ERROR_SYSTEM;\n\n#ifndef LIBXMP_NO_DEPACKERS\n\tif (libxmp_decrunch(h, path, &temp) < 0) {\n\t\tret = -XMP_ERROR_DEPACK;\n\t\tgoto err;\n\t}\n#endif\n\n\tret = test_module(info, h);\n\n#ifndef LIBXMP_NO_DEPACKERS\n    err:\n\thio_close(h);\n\tunlink_temp_file(temp);\n#else\n\thio_close(h);\n#endif\n\treturn ret;\n}\n\nint xmp_test_module_from_memory(const void *mem, long size, struct xmp_test_info *info)\n{\n\tHIO_HANDLE *h;\n\tint ret;\n\n\tif (size <= 0) {\n\t\treturn -XMP_ERROR_INVALID;\n\t}\n\n\tif ((h = hio_open_const_mem(mem, size)) == NULL)\n\t\treturn -XMP_ERROR_SYSTEM;\n\n\tret = test_module(info, h);\n\n\thio_close(h);\n\treturn ret;\n}\n\nint xmp_test_module_from_file(void *file, struct xmp_test_info *info)\n{\n\tHIO_HANDLE *h;\n\tint ret;\n#ifndef LIBXMP_NO_DEPACKERS\n\tchar *temp = NULL;\n#endif\n\n\tif ((h = hio_open_file((FILE *)file)) == NULL)\n\t\treturn -XMP_ERROR_SYSTEM;\n\n#ifndef LIBXMP_NO_DEPACKERS\n\tif (libxmp_decrunch(h, NULL, &temp) < 0) {\n\t\tret = -XMP_ERROR_DEPACK;\n\t\tgoto err;\n\t}\n#endif\n\n\tret = test_module(info, h);\n\n#ifndef LIBXMP_NO_DEPACKERS\n    err:\n\thio_close(h);\n\tunlink_temp_file(temp);\n#else\n\thio_close(h);\n#endif\n\treturn ret;\n}\n\nint xmp_test_module_from_callbacks(void *priv, struct xmp_callbacks callbacks,\n\t\t\t\tstruct xmp_test_info *info)\n{\n\tHIO_HANDLE *h;\n\tint ret;\n\n\tif ((h = hio_open_callbacks(priv, callbacks)) == NULL)\n\t\treturn -XMP_ERROR_SYSTEM;\n\n\tret = test_module(info, h);\n\n\thio_close(h);\n\treturn ret;\n}\n\nstatic int load_module(xmp_context opaque, HIO_HANDLE *h)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j, ret;\n\tint test_result, load_result;\n\n\tlibxmp_load_prologue(ctx);\n\n\tD_(D_WARN \"load\");\n\ttest_result = load_result = -1;\n\tfor (i = 0; format_loaders[i] != NULL; i++) {\n\t\thio_seek(h, 0, SEEK_SET);\n\n\t\tD_(D_WARN \"test %s\", format_loaders[i]->name);\n\t\ttest_result = format_loaders[i]->test(h, NULL, 0);\n\t\tif (test_result == 0) {\n\t\t\thio_seek(h, 0, SEEK_SET);\n\t\t\tD_(D_WARN \"load format: %s\", format_loaders[i]->name);\n\t\t\tload_result = format_loaders[i]->loader(m, h, 0);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (test_result < 0) {\n\t\txmp_release_module(opaque);\n\t\treturn -XMP_ERROR_FORMAT;\n\t}\n\n\tif (load_result < 0) {\n\t\tgoto err_load;\n\t}\n\n\t/* Sanity check: number of channels, module length */\n\tif (mod->chn > XMP_MAX_CHANNELS || mod->len > XMP_MAX_MOD_LENGTH) {\n\t\tgoto err_load;\n\t}\n\n\t/* Sanity check: channel pan */\n\tfor (i = 0; i < mod->chn; i++) {\n\t\tif (mod->xxc[i].vol < 0 || mod->xxc[i].vol > 0xff) {\n\t\t\tgoto err_load;\n\t\t}\n\t\tif (mod->xxc[i].pan < 0 || mod->xxc[i].pan > 0xff) {\n\t\t\tgoto err_load;\n\t\t}\n\t}\n\n\t/* Sanity check: patterns */\n\tif (mod->xxp == NULL) {\n\t\tgoto err_load;\n\t}\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tif (mod->xxp[i] == NULL) {\n\t\t\tgoto err_load;\n\t\t}\n\t\tfor (j = 0; j < mod->chn; j++) {\n\t\t\tint t = mod->xxp[i]->index[j];\n\t\t\tif (t < 0 || t >= mod->trk || mod->xxt[t] == NULL) {\n\t\t\t\tgoto err_load;\n\t\t\t}\n\t\t}\n\t}\n\n\tlibxmp_adjust_string(mod->name);\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tlibxmp_adjust_string(mod->xxi[i].name);\n\t}\n\tfor (i = 0; i < mod->smp; i++) {\n\t\tlibxmp_adjust_string(mod->xxs[i].name);\n\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\tif (test_result == 0 && load_result == 0)\n\t\tset_md5sum(h, m->md5);\n#endif\n\n\tlibxmp_load_epilogue(ctx);\n\n\tret = libxmp_prepare_scan(ctx);\n\tif (ret < 0) {\n\t\txmp_release_module(opaque);\n\t\treturn ret;\n\t}\n\n\tret = libxmp_scan_sequences(ctx);\n\tif (ret < 0) {\n\t\txmp_release_module(opaque);\n\t\treturn -XMP_ERROR_LOAD;\n\t}\n\n\tctx->state = XMP_STATE_LOADED;\n\n\treturn 0;\n\n    err_load:\n\txmp_release_module(opaque);\n\treturn -XMP_ERROR_LOAD;\n}\n\nint xmp_load_module(xmp_context opaque, const char *path)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n#ifndef LIBXMP_CORE_PLAYER\n\tstruct module_data *m = &ctx->m;\n#endif\n#ifndef LIBXMP_NO_DEPACKERS\n\tchar *temp_name;\n#endif\n\tHIO_HANDLE *h;\n\tint ret;\n\n\tD_(D_WARN \"path = %s\", path);\n\n\tret = libxmp_get_filetype(path);\n\n\tif (ret == XMP_FILETYPE_NONE) {\n\t\treturn -XMP_ERROR_SYSTEM;\n\t}\n\tif (ret & XMP_FILETYPE_DIR) {\n\t\terrno = EISDIR;\n\t\treturn -XMP_ERROR_SYSTEM;\n\t}\n\n\tif ((h = hio_open(path, \"rb\")) == NULL) {\n\t\treturn -XMP_ERROR_SYSTEM;\n\t}\n\n#ifndef LIBXMP_NO_DEPACKERS\n\tD_(D_INFO \"decrunch\");\n\tif (libxmp_decrunch(h, path, &temp_name) < 0) {\n\t\tret = -XMP_ERROR_DEPACK;\n\t\tgoto err;\n\t}\n#endif\n\n\tif (ctx->state > XMP_STATE_UNLOADED)\n\t\txmp_release_module(opaque);\n\n#ifndef LIBXMP_CORE_PLAYER\n\tm->dirname = get_dirname(path);\n\tif (m->dirname == NULL) {\n\t\tret = -XMP_ERROR_SYSTEM;\n\t\tgoto err;\n\t}\n\n\tm->basename = get_basename(path);\n\tif (m->basename == NULL) {\n\t\tret = -XMP_ERROR_SYSTEM;\n\t\tgoto err;\n\t}\n\n\tm->filename = path;\t/* For ALM, SSMT, etc */\n\tm->size = hio_size(h);\n#else\n\tctx->m.filename = NULL;\n\tctx->m.dirname = NULL;\n\tctx->m.basename = NULL;\n#endif\n\n\tret = load_module(opaque, h);\n\thio_close(h);\n\n#ifndef LIBXMP_NO_DEPACKERS\n\tunlink_temp_file(temp_name);\n#endif\n\n\treturn ret;\n\n#ifndef LIBXMP_CORE_PLAYER\n    err:\n\thio_close(h);\n#ifndef LIBXMP_NO_DEPACKERS\n\tunlink_temp_file(temp_name);\n#endif\n\treturn ret;\n#endif\n}\n\nint xmp_load_module_from_memory(xmp_context opaque, const void *mem, long size)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct module_data *m = &ctx->m;\n\tHIO_HANDLE *h;\n\tint ret;\n\n\tif (size <= 0) {\n\t\treturn -XMP_ERROR_INVALID;\n\t}\n\n\tif ((h = hio_open_const_mem(mem, size)) == NULL)\n\t\treturn -XMP_ERROR_SYSTEM;\n\n\tif (ctx->state > XMP_STATE_UNLOADED)\n\t\txmp_release_module(opaque);\n\n\tm->filename = NULL;\n\tm->basename = NULL;\n\tm->dirname = NULL;\n\tm->size = size;\n\n\tret = load_module(opaque, h);\n\n\thio_close(h);\n\n\treturn ret;\n}\n\nint xmp_load_module_from_file(xmp_context opaque, void *file, long size)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct module_data *m = &ctx->m;\n\tHIO_HANDLE *h;\n\tint ret;\n\n\tif ((h = hio_open_file((FILE *)file)) == NULL)\n\t\treturn -XMP_ERROR_SYSTEM;\n\n\tif (ctx->state > XMP_STATE_UNLOADED)\n\t\txmp_release_module(opaque);\n\n\tm->filename = NULL;\n\tm->basename = NULL;\n\tm->dirname = NULL;\n\tm->size = hio_size(h);\n\n\tret = load_module(opaque, h);\n\n\thio_close(h);\n\n\treturn ret;\n}\n\nint xmp_load_module_from_callbacks(xmp_context opaque, void *priv,\n\t\t\t\tstruct xmp_callbacks callbacks)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct module_data *m = &ctx->m;\n\tHIO_HANDLE *h;\n\tint ret;\n\n\tif ((h = hio_open_callbacks(priv, callbacks)) == NULL)\n\t\treturn -XMP_ERROR_SYSTEM;\n\n\tif (ctx->state > XMP_STATE_UNLOADED)\n\t\txmp_release_module(opaque);\n\n\tm->filename = NULL;\n\tm->basename = NULL;\n\tm->dirname = NULL;\n\tm->size = hio_size(h);\n\n\tret = load_module(opaque, h);\n\n\thio_close(h);\n\n\treturn ret;\n}\n\nvoid xmp_release_module(xmp_context opaque)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tint i;\n\n\t/* can't test this here, we must call release_module to clean up\n\t * load errors\n\tif (ctx->state < XMP_STATE_LOADED)\n\t\treturn;\n\t */\n\n\tif (ctx->state > XMP_STATE_LOADED)\n\t\txmp_end_player(opaque);\n\n\tctx->state = XMP_STATE_UNLOADED;\n\n\tD_(D_INFO \"Freeing memory\");\n\n#ifndef LIBXMP_CORE_PLAYER\n\tlibxmp_release_module_extras(ctx);\n#endif\n\n\tif (mod->xxt != NULL) {\n\t\tfor (i = 0; i < mod->trk; i++) {\n\t\t\tfree(mod->xxt[i]);\n\t\t}\n\t\tfree(mod->xxt);\n\t\tmod->xxt = NULL;\n\t}\n\n\tif (mod->xxp != NULL) {\n\t\tfor (i = 0; i < mod->pat; i++) {\n\t\t\tfree(mod->xxp[i]);\n\t\t}\n\t\tfree(mod->xxp);\n\t\tmod->xxp = NULL;\n\t}\n\n\tif (mod->xxi != NULL) {\n\t\tfor (i = 0; i < mod->ins; i++) {\n\t\t\tfree(mod->xxi[i].sub);\n\t\t\tfree(mod->xxi[i].extra);\n\t\t}\n\t\tfree(mod->xxi);\n\t\tmod->xxi = NULL;\n\t}\n\n\tif (mod->xxs != NULL) {\n\t\tfor (i = 0; i < mod->smp; i++) {\n\t\t\tlibxmp_free_sample(&mod->xxs[i]);\n\t\t}\n\t\tfree(mod->xxs);\n\t\tmod->xxs = NULL;\n\t}\n\n\tfree(m->xtra);\n\tfree(m->midi);\n\tm->xtra = NULL;\n\tm->midi = NULL;\n\n\tlibxmp_free_scan(ctx);\n\n\tfree(m->comment);\n\tm->comment = NULL;\n\n\tD_(\"free dirname/basename\");\n\tfree(m->dirname);\n\tfree(m->basename);\n\tm->basename = NULL;\n\tm->dirname = NULL;\n}\n\nvoid xmp_scan_module(xmp_context opaque)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\n\tif (ctx->state < XMP_STATE_LOADED)\n\t\treturn;\n\n\tlibxmp_scan_sequences(ctx);\n}\n"
  },
  {
    "path": "contrib/libxmp/src/load_helpers.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <ctype.h>\n#include \"common.h\"\n#include \"loaders/loader.h\"\n\n\n#ifndef LIBXMP_CORE_PLAYER\n\n/*\n * Handle special \"module quirks\" that can't be detected automatically\n * such as Protracker 2.x compatibility, vblank timing, etc.\n */\n\nstruct module_quirk {\n\tuint8 md5[16];\n\tint flags;\n\tint mode;\n};\n\nconst struct module_quirk mq[] = {\n\t/* \"No Mercy\" by Alf/VTL (added by Martin Willers) */\n\t{\n\t\t{ 0x36, 0x6e, 0xc0, 0xfa, 0x96, 0x2a, 0xeb, 0xee,\n\t  \t  0x03, 0x4a, 0xa2, 0xdb, 0xaa, 0x49, 0xaa, 0xea },\n\t\t0, XMP_MODE_PROTRACKER\n\t},\n\n\t/* mod.souvenir of china */\n\t{\n\t\t{ 0x93, 0xf1, 0x46, 0xae, 0xb7, 0x58, 0xc3, 0x9d,\n\t\t  0x8b, 0x5f, 0xbc, 0x98, 0xbf, 0x23, 0x7a, 0x43 },\n\t\tXMP_FLAGS_FIXLOOP, XMP_MODE_AUTO\n\t},\n\n#if 0\n\t/* \"siedler ii\" (added by Daniel Åkerud) */\n\t/* Timing fixed by vblank scan compare. CIA: 32m10s  VBlank: 12m32s */\n\t{\n\t\t{ 0x70, 0xaa, 0x03, 0x4d, 0xfb, 0x2f, 0x1f, 0x73,\n\t\t  0xd9, 0xfd, 0xba, 0xfe, 0x13, 0x1b, 0xb7, 0x01 },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n#endif\n\n\t/* \"Klisje paa klisje\" (added by Kjetil Torgrim Homme) */\n\t{\n\t\t{ 0xe9, 0x98, 0x01, 0x2c, 0x70, 0x0e, 0xb4, 0x3a,\n\t\t  0xf0, 0x32, 0x17, 0x11, 0x30, 0x58, 0x29, 0xb2 },\n\t\t0, XMP_MODE_NOISETRACKER\n\t},\n\n#if 0\n\t/* -- Already covered by Noisetracker fingerprinting -- */\n\n\t/* Another version of Klisje paa klisje sent by Steve Fernandez */\n\t{\n\t\t{ 0x12, 0x19, 0x1c, 0x90, 0x41, 0xe3, 0xfd, 0x70,\n\t\t  0xb7, 0xe6, 0xb3, 0x94, 0x8b, 0x21, 0x07, 0x63 },\n\t\tXMP_FLAGS_VBLANK\n\t},\n#endif\n\n\t/* \"((((( nebulos )))))\" sent by Tero Auvinen (AMP version) */\n\t{\n\t\t{ 0x51, 0x6e, 0x8d, 0xcc, 0x35, 0x7d, 0x50, 0xde,\n\t\t  0xa9, 0x85, 0xbe, 0xbf, 0x90, 0x2e, 0x42, 0xdc },\n\t\t0, XMP_MODE_NOISETRACKER\n\t},\n\n\t/* Purple Motion's Sundance.mod, Music Channel BBS edit */\n\t{\n\t\t{ 0x5d, 0x3e, 0x1e, 0x08, 0x28, 0x52, 0x12, 0xc7,\n\t\t  0x17, 0x64, 0x95, 0x75, 0x98, 0xe6, 0x95, 0xc1 },\n\t\t0, XMP_MODE_ST3\n\t},\n\n\t/* Asle's Ode to Protracker */\n\t{\n\t\t{ 0x97, 0xa3, 0x7d, 0x30, 0xd7, 0xae, 0x6d, 0x50,\n\t\t  0xc9, 0x62, 0xe9, 0xd8, 0x87, 0x1b, 0x7e, 0x8a },\n\t\t0, XMP_MODE_PROTRACKER\n\t},\n\n\t/* grooving3.mod */\n\t/* length 150778 crc32c 0xfdcf9aadU */\n\t{\n\t\t{ 0xdb, 0x61, 0x22, 0x44, 0x39, 0x85, 0x74, 0xe9,\n\t\t  0xfa, 0x11, 0xb8, 0xfb, 0x87, 0xe8, 0xde, 0xc5, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* mod.Rundgren */\n\t/* length 195078 crc32c 0x8fa827a4U */\n\t{\n\t\t{ 0x9a, 0xdb, 0xb2, 0x09, 0x07, 0x1c, 0x44, 0x82,\n\t\t  0xc5, 0xdf, 0x83, 0x52, 0xcc, 0x73, 0x9f, 0x20, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* dance feeling by Audiomonster */\n\t/* length 169734 crc32c 0x79fa2c9bU */\n\t{\n\t\t{ 0x31, 0x2c, 0x3d, 0xaa, 0x5f, 0x1a, 0x54, 0x44,\n\t\t  0x9d, 0xf7, 0xc4, 0x41, 0x8a, 0xc5, 0x01, 0x02, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* knights melody by Audiomonster */\n\t/* length 77798 crc32c 0x7bf19c5bU */\n\t{\n\t\t{ 0x31, 0xc3, 0x0e, 0x32, 0xfc, 0x99, 0x95, 0xd2,\n\t\t  0x97, 0x20, 0xb3, 0x77, 0x50, 0x05, 0xfe, 0xa5, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* hcomme by Bouffon */\n\t/* length 71346 crc32c 0x4ad49cb3U */\n\t{\n\t\t{ 0x6e, 0xf9, 0x78, 0xc1, 0x80, 0xae, 0x51, 0x06,\n\t\t  0x05, 0x7c, 0x6e, 0xd0, 0x26, 0x7e, 0xfe, 0x3d, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* ((((aquapool)))) by Dolphin */\n\t/* length 62932 crc32c 0x05b103fcU */\n\t{\n\t\t{ 0xff, 0x0b, 0xe0, 0x26, 0xc6, 0x31, 0xb5, 0x9b,\n\t\t  0x94, 0x83, 0x94, 0x99, 0x7e, 0x24, 0x7c, 0xdd, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* 100yarddash by Dr. Awesome */\n\t/* length 104666 crc32c 0xd2b0e4a6U */\n\t{\n\t\t{ 0x5b, 0xff, 0x2f, 0xb8, 0xef, 0x3c, 0xbe, 0x55,\n\t\t  0xa8, 0xe2, 0xa7, 0xcf, 0x5c, 0xbd, 0xdd, 0xb2, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* jazz-reggae-funk by Droid */\n\t/* length 115564 crc32c 0x41ff635fU */\n\t{\n\t\t{ 0xe5, 0x6e, 0x31, 0x2f, 0x62, 0x80, 0xc1, 0x9d,\n\t\t  0x2f, 0x24, 0x54, 0xf3, 0x89, 0x3f, 0x94, 0x6c, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* hard and heavy by Fish */\n\t/* length 69814 crc32c 0x1f09d3d5U */\n\t{\n\t\t{ 0x6b, 0xce, 0x39, 0x94, 0x75, 0x42, 0x06, 0x74,\n\t\t  0xd2, 0x83, 0xbc, 0x5e, 0x7b, 0x42, 0x1f, 0xa0, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* crazy valley by Julius and Droid */\n\t/* length 97496 crc32c 0xb8eec40eU */\n\t{\n\t\t{ 0x23, 0x77, 0x18, 0x1d, 0x21, 0x9b, 0x41, 0x8f,\n\t\t  0xc1, 0xb4, 0xf4, 0xf8, 0x22, 0xdd, 0xd8, 0xb6, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* THE ILLOGICAL ONE by Rhino */\n\t/* length 173432 crc32c 0xcb4e2987U */\n\t{\n\t\t{ 0xd8, 0xc2, 0xbb, 0xe6, 0x11, 0xd0, 0x5c, 0x02,\n\t\t  0x8e, 0x3b, 0xcb, 0x7c, 0x4a, 0x7d, 0x43, 0xa0, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* sounds of holiday by Spacebrain */\n\t/* length 309520 crc32c 0x28804a57U */\n\t{\n\t\t{ 0x36, 0x18, 0x19, 0xa4, 0x9d, 0xa2, 0xa2, 0x6f,\n\t\t  0x58, 0x60, 0xc4, 0xd9, 0x0d, 0xa2, 0x9f, 0x49, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* sunisinus by Speed-Head */\n\t/* length 175706 crc32c 0x2e56451bU */\n\t{\n\t\t{ 0x7e, 0x69, 0x44, 0xb6, 0x38, 0x0d, 0x27, 0x14,\n\t\t  0x70, 0x5d, 0x44, 0xce, 0xce, 0xdd, 0x37, 0x31, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* eat the fulcrum bop by The Assassin */\n\t/* length 160286 crc32c 0x583a4683U */\n\t{\n\t\t{ 0x11, 0xe9, 0x6f, 0x62, 0xe1, 0xc3, 0xc5, 0xcc,\n\t\t  0x3b, 0xaf, 0xea, 0x69, 0x4b, 0xce, 0x5f, 0xec, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* obvious disaster by Tip */\n\t/* length 221086 crc32c 0x51c6d489U */\n\t{\n\t\t{ 0x06, 0x8e, 0x69, 0x01, 0x49, 0x8f, 0xbd, 0x0f,\n\t\t  0xfc, 0xb7, 0x8f, 0x2a, 0x91, 0xe1, 0x8b, 0xe8, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* alien nation by Turtle */\n\t/* length 167548 crc32c 0xc9ec1674U */\n\t{\n\t\t{ 0x71, 0xdf, 0x11, 0xac, 0x5d, 0xec, 0x07, 0xf8,\n\t\t  0x10, 0x6f, 0x28, 0x8d, 0x47, 0x59, 0x54, 0x9b, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\t/* illusions!2 by Zuhl */\n\t/* length 289770 crc32c 0x6bf5fbcfU */\n\t{\n\t\t{ 0xca, 0x37, 0x8c, 0x0e, 0x87, 0x4f, 0x1e, 0xcd,\n\t\t  0xa3, 0xe9, 0x8b, 0xdd, 0x11, 0x46, 0x8d, 0x69, },\n\t\tXMP_FLAGS_VBLANK, XMP_MODE_AUTO\n\t},\n\n\t{\n\t\t{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },\n\t\t0, 0\n\t}\n};\n\nstatic void module_quirks(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tint i;\n\n\tfor (i = 0; mq[i].flags != 0 || mq[i].mode != 0; i++) {\n\t\tif (!memcmp(m->md5, mq[i].md5, 16)) {\n\t\t\tp->flags |= mq[i].flags;\n\t\t\tp->mode = mq[i].mode;\n\t\t}\n\t}\n}\n\n#endif /* LIBXMP_CORE_PLAYER */\n\nchar *libxmp_adjust_string(char *s)\n{\n\tint i;\n\n\tfor (i = 0; i < strlen(s); i++) {\n\t\tif (!isprint((unsigned char)s[i]) || ((uint8) s[i] > 127))\n\t\t\ts[i] = ' ';\n\t}\n\n\twhile (*s && (s[strlen(s) - 1] == ' ')) {\n\t\ts[strlen(s) - 1] = 0;\n\t}\n\n\treturn s;\n}\n\nstatic void check_envelope(struct xmp_envelope *env)\n{\n\t/* Disable envelope if invalid number of points */\n\tif (env->npt <= 0 || env->npt > XMP_MAX_ENV_POINTS) {\n\t\tenv->flg &= ~XMP_ENVELOPE_ON;\n\t}\n\n\t/* Disable envelope loop if invalid loop parameters */\n\tif (env->lps >= env->npt || env->lpe >= env->npt) {\n\t\tenv->flg &= ~XMP_ENVELOPE_LOOP;\n\t}\n\n\t/* Disable envelope sustain if invalid sustain */\n\tif (env->sus >= env->npt || env->sue >= env->npt) {\n\t\tenv->flg &= ~XMP_ENVELOPE_SUS;\n\t}\n}\n\nstatic void clamp_volume_envelope(struct module_data *m, struct xmp_envelope *env)\n{\n\t/* Clamp broken values in the volume envelope to the expected range. */\n\tif (env->flg & XMP_ENVELOPE_ON) {\n\t\tint i;\n\t\tfor (i = 0; i < env->npt; i++) {\n\t\t\tint16 *data = &env->data[i * 2 + 1];\n\t\t\tCLAMP(*data, 0, m->volbase);\n\t\t}\n\t}\n}\n\nvoid libxmp_load_prologue(struct context_data *ctx)\n{\n\tstruct module_data *m = &ctx->m;\n\tint i;\n\n\t/* Reset variables */\n\tmemset(&m->mod, 0, sizeof (struct xmp_module));\n\tm->rrate = PAL_RATE;\n\tm->c4rate = C4_PAL_RATE;\n\tm->volbase = 0x40;\n\tm->gvol = m->gvolbase = 0x40;\n\tm->vol_table = NULL;\n\tm->quirk = 0;\n\tm->flow_mode = FLOW_MODE_GENERIC;\n\tm->read_event_type = READ_EVENT_MOD;\n\tm->period_type = PERIOD_AMIGA;\n\tm->compare_vblank = 0;\n\tm->comment = NULL;\n\tm->scan_cnt = NULL;\n\tm->midi = NULL;\n\n\t/* Set defaults */\n\tm->mod.pat = 0;\n\tm->mod.trk = 0;\n\tm->mod.chn = 4;\n\tm->mod.ins = 0;\n\tm->mod.smp = 0;\n\tm->mod.spd = 6;\n\tm->mod.bpm = 125;\n\tm->mod.len = 0;\n\tm->mod.rst = 0;\n\n#ifndef LIBXMP_CORE_PLAYER\n\tm->extra = NULL;\n#endif\n\n\tm->time_factor = DEFAULT_TIME_FACTOR;\n\n\tfor (i = 0; i < 64; i++) {\n\t\tint pan = (((i + 1) / 2) % 2) * 0xff;\n\t\tm->mod.xxc[i].pan = 0x80 + (pan - 0x80) * m->defpan / 100;\n\t\tm->mod.xxc[i].vol = 0x40;\n\t\tm->mod.xxc[i].flg = 0;\n\t}\n}\n\nvoid libxmp_load_epilogue(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j;\n\n\tmod->gvl = m->gvol;\n\n\t/* Sanity check for module parameters */\n\tCLAMP(mod->len, 0, XMP_MAX_MOD_LENGTH);\n\tCLAMP(mod->pat, 0, 257);   /* some formats have an extra pattern */\n\tCLAMP(mod->ins, 0, 255);\n\tCLAMP(mod->smp, 0, MAX_SAMPLES);\n\tCLAMP(mod->chn, 0, XMP_MAX_CHANNELS);\n\n\t/* Fix cases where the restart value is invalid e.g. kc_fall8.xm\n\t * from http://aminet.net/mods/mvp/mvp_0002.lha (reported by\n\t * Ralf Hoffmann <ralf@boomerangsworld.de>)\n\t */\n\tif (mod->rst >= mod->len) {\n\t\tmod->rst = 0;\n\t}\n\n\t/* Sanity check for tempo and BPM */\n\tif (mod->spd <= 0 || mod->spd > 255) {\n\t\tmod->spd = 6;\n\t}\n\tCLAMP(mod->bpm, XMP_MIN_BPM, 1000);\n\n\t/* Set appropriate values for instrument volumes and subinstrument\n\t * global volumes when QUIRK_INSVOL is not set, to keep volume values\n\t * consistent if the user inspects struct xmp_module. We can later\n\t * set volumes in the loaders and eliminate the quirk.\n\t */\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tif (~m->quirk & QUIRK_INSVOL) {\n\t\t\tmod->xxi[i].vol = m->volbase;\n\t\t}\n\t\tfor (j = 0; j < mod->xxi[i].nsm; j++) {\n\t\t\tif (~m->quirk & QUIRK_INSVOL) {\n\t\t\t\tmod->xxi[i].sub[j].gvl = m->volbase;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Sanity check for envelopes\n\t */\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tcheck_envelope(&mod->xxi[i].aei);\n\t\tcheck_envelope(&mod->xxi[i].fei);\n\t\tcheck_envelope(&mod->xxi[i].pei);\n\t\tclamp_volume_envelope(m, &mod->xxi[i].aei);\n\t}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\t/* TODO: there's no unintrusive and clean way to get this struct into\n\t * libxmp_load_sample currently, so bound these fields here for now. */\n\tfor (i = 0; i < mod->smp; i++) {\n\t\tstruct xmp_sample *xxs = &mod->xxs[i];\n\t\tstruct extra_sample_data *xtra = &m->xtra[i];\n\t\tif (xtra->sus < 0) {\n\t\t\txtra->sus = 0;\n\t\t}\n\t\tif (xtra->sue > xxs->len) {\n\t\t\txtra->sue = xxs->len;\n\t\t}\n\t\tif (xtra->sus >= xxs->len || xtra->sus >= xtra->sue) {\n\t\t\txtra->sus = xtra->sue = 0;\n\t\t\txxs->flg &= ~(XMP_SAMPLE_SLOOP | XMP_SAMPLE_SLOOP_BIDIR);\n\t\t}\n\t}\n#endif\n\n\tp->filter = 0;\n\tp->mode = XMP_MODE_AUTO;\n\tp->flags = p->player_flags;\n\tp->scan_time_factor = m->time_factor;\n#ifndef LIBXMP_CORE_PLAYER\n\tmodule_quirks(ctx);\n#endif\n\tlibxmp_set_player_mode(ctx);\n}\n\nint libxmp_prepare_scan(struct context_data *ctx)\n{\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tint i, ord;\n\n\tif (mod->xxp == NULL || mod->xxt == NULL)\n\t\treturn -XMP_ERROR_LOAD;\n\tord = 0;\n\twhile (ord < mod->len && mod->xxo[ord] >= mod->pat) {\n\t\tord++;\n\t}\n\n\tif (ord >= mod->len) {\n\t\tmod->len = 0;\n\t\treturn 0;\n\t}\n\n\tm->scan_cnt = (uint8 **) calloc(mod->len, sizeof(uint8 *));\n\tif (m->scan_cnt == NULL)\n\t\treturn -XMP_ERROR_SYSTEM;\n\n\tfor (i = 0; i < mod->len; i++) {\n\t\tint pat_idx = mod->xxo[i];\n\t\tstruct xmp_pattern *pat;\n\n\t\t/* Add pattern if referenced in orders */\n\t\tif (pat_idx < mod->pat && !mod->xxp[pat_idx]) {\n\t\t\tif (libxmp_alloc_pattern(mod, pat_idx) < 0) {\n\t\t\t\treturn -XMP_ERROR_SYSTEM;\n\t\t\t}\n\t\t}\n\n\t\tpat = pat_idx >= mod->pat ? NULL : mod->xxp[pat_idx];\n\t\tm->scan_cnt[i] = (uint8 *) calloc(1, (pat && pat->rows)? pat->rows : 1);\n\t\tif (m->scan_cnt[i] == NULL)\n\t\t\treturn -XMP_ERROR_SYSTEM;\n\t}\n\n\treturn 0;\n}\n\nvoid libxmp_free_scan(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tint i;\n\n\tif (m->scan_cnt) {\n\t\tfor (i = 0; i < mod->len; i++)\n\t\t\tfree(m->scan_cnt[i]);\n\n\t\tfree(m->scan_cnt);\n\t\tm->scan_cnt = NULL;\n\t}\n\n\tfree(p->scan);\n\tp->scan = NULL;\n}\n\n/* Process player personality flags */\nint libxmp_set_player_mode(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tint q;\n\n\tswitch (p->mode) {\n\tcase XMP_MODE_AUTO:\n\t\tbreak;\n\tcase XMP_MODE_MOD:\n\t\tm->c4rate = C4_PAL_RATE;\n\t\tm->quirk = 0;\n\t\tm->flow_mode = FLOW_MODE_GENERIC;\n\t\tm->read_event_type = READ_EVENT_MOD;\n\t\tm->period_type = PERIOD_AMIGA;\n\t\tbreak;\n\tcase XMP_MODE_NOISETRACKER:\n\t\tm->c4rate = C4_PAL_RATE;\n\t\tm->quirk = QUIRK_NOBPM;\n\t\tm->flow_mode = FLOW_MODE_GENERIC;\n\t\tm->read_event_type = READ_EVENT_MOD;\n\t\tm->period_type = PERIOD_MODRNG;\n\t\tbreak;\n\tcase XMP_MODE_PROTRACKER:\n\t\tm->c4rate = C4_PAL_RATE;\n\t\tm->quirk = QUIRK_PROTRACK;\n\t\tm->flow_mode = FLOW_MODE_GENERIC;\n\t\tm->read_event_type = READ_EVENT_MOD;\n\t\tm->period_type = PERIOD_MODRNG;\n\t\tbreak;\n\tcase XMP_MODE_S3M:\n\t\tq = m->quirk & (QUIRK_VSALL | QUIRK_ARPMEM);\n\t\tm->c4rate = C4_NTSC_RATE;\n\t\tm->quirk = QUIRKS_ST3 | q;\n\t\tm->flow_mode = FLOW_MODE_ST3_321;\n\t\tm->read_event_type = READ_EVENT_ST3;\n\t\tbreak;\n\tcase XMP_MODE_ST3:\n\t\tq = m->quirk & (QUIRK_VSALL | QUIRK_ARPMEM);\n\t\tm->c4rate = C4_NTSC_RATE;\n\t\tm->quirk = QUIRKS_ST3 | QUIRK_ST3BUGS | q;\n\t\tm->flow_mode = FLOW_MODE_ST3_321;\n\t\tm->read_event_type = READ_EVENT_ST3;\n\t\tbreak;\n\tcase XMP_MODE_ST3GUS:\n\t\tq = m->quirk & (QUIRK_VSALL | QUIRK_ARPMEM);\n\t\tm->c4rate = C4_NTSC_RATE;\n\t\tm->quirk = QUIRKS_ST3 | QUIRK_ST3BUGS | q;\n\t\tm->quirk &= ~QUIRK_RSTCHN;\n\t\tm->flow_mode = FLOW_MODE_ST3_321;\n\t\tm->read_event_type = READ_EVENT_ST3;\n\t\tbreak;\n\tcase XMP_MODE_XM:\n\t\tm->c4rate = C4_NTSC_RATE;\n\t\tm->quirk = QUIRKS_FT2;\n\t\tm->flow_mode = FLOW_MODE_GENERIC;\n\t\tm->read_event_type = READ_EVENT_FT2;\n\t\tbreak;\n\tcase XMP_MODE_FT2:\n\t\tm->c4rate = C4_NTSC_RATE;\n\t\tm->quirk = QUIRKS_FT2 | QUIRK_FT2BUGS;\n\t\tm->flow_mode = FLOW_MODE_GENERIC;\n\t\tm->read_event_type = READ_EVENT_FT2;\n\t\tbreak;\n\tcase XMP_MODE_IT:\n\t\tm->c4rate = C4_NTSC_RATE;\n\t\tm->quirk = QUIRKS_IT | QUIRK_VIBHALF | QUIRK_VIBINV;\n\t\tm->flow_mode = FLOW_MODE_IT_210;\n\t\tm->read_event_type = READ_EVENT_IT;\n\t\tbreak;\n\tcase XMP_MODE_ITSMP:\n\t\tm->c4rate = C4_NTSC_RATE;\n\t\tm->quirk = QUIRKS_IT | QUIRK_VIBHALF | QUIRK_VIBINV;\n\t\tm->quirk &= ~(QUIRK_VIRTUAL | QUIRK_RSTCHN);\n\t\tm->flow_mode = FLOW_MODE_IT_210;\n\t\tm->read_event_type = READ_EVENT_IT;\n\t\tbreak;\n\tdefault:\n\t\treturn -1;\n\t}\n\n\tif (p->mode != XMP_MODE_AUTO)\n\t\tm->compare_vblank = 0;\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/669_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2021 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"loader.h\"\n\n\nstatic int c669_test (HIO_HANDLE *, char *, const int);\nstatic int c669_load (struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_669 = {\n    \"Composer 669\",\n    c669_test,\n    c669_load\n};\n\nstatic int c669_test(HIO_HANDLE *f, char *t, const int start)\n{\n    uint16 id;\n\n    id = hio_read16b(f);\n    if (id != 0x6966 && id != 0x4a4e)\n\treturn -1;\n\n    hio_seek(f, 110, SEEK_SET);\n    if (hio_read8(f) > 64)\n\treturn -1;\n    if (hio_read8(f) > 128)\n\treturn -1;\n\n    hio_seek(f, 240, SEEK_SET);\n    if (hio_read8(f) != 0xff)\n\treturn -1;\n\n    hio_seek(f, start + 2, SEEK_SET);\n    libxmp_read_title(f, t, 36);\n\n    return 0;\n}\n\n\nstruct c669_file_header {\n    uint8 marker[2];\t\t/* 'if'=standard, 'JN'=extended */\n    uint8 message[108];\t\t/* Song message */\n    uint8 nos;\t\t\t/* Number of samples (0-64) */\n    uint8 nop;\t\t\t/* Number of patterns (0-128) */\n    uint8 loop;\t\t\t/* Loop order number */\n    uint8 order[128];\t\t/* Order list */\n    uint8 speed[128];\t\t/* Tempo list for patterns */\n    uint8 pbrk[128];\t\t/* Break list for patterns */\n};\n\nstruct c669_instrument_header {\n    uint8 name[13];\t\t/* ASCIIZ instrument name */\n    uint32 length;\t\t/* Instrument length */\n    uint32 loop_start;\t\t/* Instrument loop start */\n    uint32 loopend;\t\t/* Instrument loop end */\n};\n\n\n#define NONE 0xff\n\n/* Effects bug fixed by Miod Vallat <miodrag@multimania.com> */\n\nstatic const uint8 fx[6] = {\n    FX_669_PORTA_UP,\n    FX_669_PORTA_DN,\n    FX_669_TPORTA,\n    FX_669_FINETUNE,\n    FX_669_VIBRATO,\n    FX_SPEED_CP\n};\n\n\nstatic int c669_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n    struct xmp_module *mod = &m->mod;\n    int i, j;\n    struct xmp_event *event;\n    struct c669_file_header sfh;\n    struct c669_instrument_header sih;\n    uint8 ev[3];\n\n    LOAD_INIT();\n\n    hio_read(sfh.marker, 2, 1, f);\t/* 'if'=standard, 'JN'=extended */\n    hio_read(sfh.message, 108, 1, f);\t/* Song message */\n    sfh.nos = hio_read8(f);\t\t/* Number of samples (0-64) */\n    sfh.nop = hio_read8(f);\t\t/* Number of patterns (0-128) */\n\n    /* Sanity check */\n    if (sfh.nos > 64 || sfh.nop > 128)\n\treturn -1;\n\n    sfh.loop = hio_read8(f);\t\t/* Loop order number */\n    if (hio_read(sfh.order, 1, 128, f) != 128)\t/* Order list */\n\treturn -1;\n    if (hio_read(sfh.speed, 1, 128, f) != 128)\t/* Tempo list for patterns */\n\treturn -1;\n    if (hio_read(sfh.pbrk, 1, 128, f) != 128) \t/* Break list for patterns */\n\treturn -1;\n\n    mod->chn = 8;\n    mod->ins = sfh.nos;\n    mod->pat = sfh.nop;\n    mod->trk = mod->chn * mod->pat;\n    for (i = 0; i < 128; i++) {\n\tif (sfh.order[i] > sfh.nop)\n\t    break;\n    }\n    mod->len = i;\n    memcpy (mod->xxo, sfh.order, mod->len);\n    mod->spd = 6;\n    mod->bpm = 78;\n    mod->smp = mod->ins;\n\n    m->period_type = PERIOD_CSPD;\n    m->c4rate = C4_NTSC_RATE;\n\n    libxmp_copy_adjust(mod->name, sfh.message, 36);\n    libxmp_set_type(m, strncmp((char *)sfh.marker, \"if\", 2) ?\n\t\t\t\t\"UNIS 669\" : \"Composer 669\");\n\n    MODULE_INFO();\n\n    m->comment = (char *) malloc(109);\n    memcpy(m->comment, sfh.message, 108);\n    m->comment[108] = 0;\n\n    /* Read and convert instruments and samples */\n\n    if (libxmp_init_instrument(m) < 0)\n\treturn -1;\n\n    D_(D_INFO \"Instruments: %d\", mod->pat);\n\n    for (i = 0; i < mod->ins; i++) {\n\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\tstruct xmp_sample *xxs = &mod->xxs[i];\n\tstruct xmp_subinstrument *sub;\n\n\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t    return -1;\n\n\tsub = &xxi->sub[0];\n\n\thio_read (sih.name, 13, 1, f);\t\t/* ASCIIZ instrument name */\n\tsih.length = hio_read32l(f);\t\t/* Instrument size */\n\tsih.loop_start = hio_read32l(f);\t/* Instrument loop start */\n\tsih.loopend = hio_read32l(f);\t\t/* Instrument loop end */\n\n\t/* Sanity check */\n\tif (sih.length > MAX_SAMPLE_SIZE)\n\t    return -1;\n\n\txxs->len = sih.length;\n\txxs->lps = sih.loop_start;\n\txxs->lpe = sih.loopend >= 0xfffff ? 0 : sih.loopend;\n\txxs->flg = xxs->lpe ? XMP_SAMPLE_LOOP : 0;\t/* 1 == Forward loop */\n\n\tsub->vol = 0x40;\n\tsub->pan = 0x80;\n\tsub->sid = i;\n\n\tif (xxs->len > 0)\n\t\txxi->nsm = 1;\n\n\tlibxmp_instrument_name(mod, i, sih.name, 13);\n\n\tD_(D_INFO \"[%2X] %-14.14s %04x %04x %04x %c\", i,\n\t\txxi->name, xxs->len, xxs->lps, xxs->lpe,\n\t\txxs->flg & XMP_SAMPLE_LOOP ? 'L' : ' ');\n    }\n\n    if (libxmp_init_pattern(mod) < 0)\n\treturn -1;\n\n    /* Read and convert patterns */\n    D_(D_INFO \"Stored patterns: %d\", mod->pat);\n    for (i = 0; i < mod->pat; i++) {\n\tint pbrk;\n\n\tif (libxmp_alloc_pattern_tracks(mod, i, 64) < 0)\n\t    return -1;\n\n\tevent = &EVENT(i, 0, 0);\n\tevent->f2t = FX_SPEED_CP;\n\tevent->f2p = sfh.speed[i];\n\n\tpbrk = sfh.pbrk[i];\n\tif (pbrk >= 64)\n\t    return -1;\n\n\tevent = &EVENT(i, 1, pbrk);\n\tevent->f2t = FX_BREAK;\n\tevent->f2p = 0;\n\n\tfor (j = 0; j < 64 * 8; j++) {\n\t    event = &EVENT(i, j % 8, j / 8);\n\t    if(hio_read(ev, 1, 3, f) < 3) {\n\t\tD_(D_CRIT \"read error at pat %d\", i);\n\t\treturn -1;\n\t    }\n\n\t    if ((ev[0] & 0xfe) != 0xfe) {\n\t\tevent->note = 1 + 36 + (ev[0] >> 2);\n\t\tevent->ins = 1 + MSN(ev[1]) + ((ev[0] & 0x03) << 4);\n\t    }\n\n\t    if (ev[0] != 0xff)\n\t\tevent->vol = (LSN(ev[1]) << 2) + 1;\n\n\t    if (ev[2] != 0xff) {\n\t\tif (MSN(ev[2]) >= ARRAY_SIZE(fx))\n\t\t    continue;\n\n\t\tevent->fxt = fx[MSN(ev[2])];\n\t\tevent->fxp = LSN(ev[2]);\n\n\t\tif (event->fxt == FX_SPEED_CP) {\n\t\t    event->f2t = FX_PER_CANCEL;\n\t\t}\n\t    }\n\t}\n    }\n\n    /* Read samples */\n    D_(D_INFO \"Stored samples: %d\", mod->smp);\n\n    for (i = 0; i < mod->ins; i++) {\n\tif (mod->xxs[i].len <= 2)\n\t    continue;\n\tif (libxmp_load_sample(m, f, SAMPLE_FLAG_UNS, &mod->xxs[i], NULL) < 0)\n\t    return -1;\n    }\n\n    for (i = 0; i < mod->chn; i++) {\n\tmod->xxc[i].pan = DEFPAN((i % 2) * 0xff);\n    }\n\n    m->quirk |= QUIRK_PBALL|QUIRK_PERPAT;\n\n    return 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/amf_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/* AMF loader written based on the format specs by Miodrag Vallat with\n * fixes by Andre Timmermans.\n *\n * The AMF format is the internal format used by DSMI, the DOS Sound and Music\n * Interface, which is the engine of DMP. As DMP was able to play more and more\n * module formats, the format evolved to support more features. There were 5\n * official formats, numbered from 10 (AMF 1.0) to 14 (AMF 1.4).\n */\n\n#include \"loader.h\"\n#include \"../period.h\"\n\n\nstatic int amf_test(HIO_HANDLE *, char *, const int);\nstatic int amf_load (struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_amf = {\n\t\"DSMI Advanced Module Format\",\n\tamf_test,\n\tamf_load\n};\n\nstatic int amf_test(HIO_HANDLE * f, char *t, const int start)\n{\n\tchar buf[4];\n\tint ver;\n\n\tif (hio_read(buf, 1, 3, f) < 3)\n\t\treturn -1;\n\n\tif (buf[0] != 'A' || buf[1] != 'M' || buf[2] != 'F')\n\t\treturn -1;\n\n\tver = hio_read8(f);\n\tif ((ver != 0x01 && ver < 0x08) || ver > 0x0e)\n\t\treturn -1;\n\n\tlibxmp_read_title(f, t, 32);\n\n\treturn 0;\n}\n\n\nstatic int amf_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j;\n\tstruct xmp_event *event;\n\tuint8 buf[1024];\n\tint *trkmap, newtrk;\n\tint no_loopend = 0;\n\tint ver;\n\n\tLOAD_INIT();\n\n\thio_read(buf, 1, 3, f);\n\tver = hio_read8(f);\n\n\tif (hio_read(buf, 1, 32, f) != 32)\n\t\treturn -1;\n\n\tmemcpy(mod->name, buf, 32);\n\tmod->name[32] = '\\0';\n\tlibxmp_set_type(m, \"DSMI %d.%d AMF\", ver / 10, ver % 10);\n\n\tmod->ins = hio_read8(f);\n\tmod->len = hio_read8(f);\n\tmod->trk = hio_read16l(f);\n\tmod->chn = 4;\n\n\tif (ver >= 0x09) {\n\t\tmod->chn = hio_read8(f);\n\t}\n\n\t/* Sanity check */\n\tif (mod->ins == 0 || mod->len == 0 || mod->trk == 0\n\t\t|| mod->chn == 0 || mod->chn > XMP_MAX_CHANNELS) {\n\t\treturn -1;\n\t}\n\n\tmod->smp = mod->ins;\n\tmod->pat = mod->len;\n\n\tif (ver == 0x09 || ver == 0x0a)\n\t\thio_read(buf, 1, 16, f);\t/* channel remap table */\n\n\tif (ver >= 0x0b) {\n\t\tint pan_len = ver >= 0x0c ? 32 : 16;\n\n\t\tif (hio_read(buf, 1, pan_len, f) != pan_len)\t/* panning table */\n\t\t\treturn -1;\n\n\t\tfor (i = 0; i < pan_len; i++) {\n\t\t\tmod->xxc[i].pan = 0x80 + 2 * (int8)buf[i];\n\t\t}\n\t}\n\n\tif (ver >= 0x0d) {\n\t\tmod->bpm = hio_read8(f);\n\t\tmod->spd = hio_read8(f);\n\t}\n\n\tm->c4rate = C4_NTSC_RATE;\n\n\tMODULE_INFO();\n\n\n\t/* Orders */\n\n\t/*\n\t * Andre Timmermans <andre.timmermans@atos.net> says:\n\t *\n\t * Order table: track numbers in this table are not explained,\n\t * but as you noticed you have to perform -1 to obtain the index\n\t * in the track table. For value 0, found in some files, I think\n\t * it means an empty track.\n\t *\n\t * 2021 note: this is misleading. Do not subtract 1 from the logical\n\t * track values found in the order table; load the mapping table to\n\t * index 1 instead.\n\t */\n\n\tfor (i = 0; i < mod->len; i++)\n\t\tmod->xxo[i] = i;\n\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\tmod->xxp = (struct xmp_pattern **) calloc(mod->pat, sizeof(struct xmp_pattern *));\n\tif (mod->xxp == NULL)\n\t\treturn -1;\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tif (libxmp_alloc_pattern(mod, i) < 0)\n\t\t\treturn -1;\n\n\t\tmod->xxp[i]->rows = ver >= 0x0e ? hio_read16l(f) : 64;\n\n\t\tif (mod->xxp[i]->rows > 256)\n\t\t\treturn -1;\n\n\t\tfor (j = 0; j < mod->chn; j++) {\n\t\t\tuint16 t = hio_read16l(f);\n\t\t\tmod->xxp[i]->index[j] = t;\n\t\t}\n\t}\n\n\t/* Instruments */\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\treturn -1;\n\n\t/* Probe for 2-byte loop start 1.0 format\n\t * in facing_n.amf and sweetdrm.amf have only the sample\n\t * loop start specified in 2 bytes\n\t *\n\t * These modules are an early variant of the AMF 1.0 format. Since\n\t * normal AMF 1.0 files have 32-bit lengths/loop start/loop end,\n\t * this is possibly caused by these fields having been expanded for\n\t * the 1.0 format, but M2AMF 1.3 writing instrument structs with\n\t * the old length (which would explain the missing 6 bytes).\n\t */\n\tif (ver == 0x0a) {\n\t\tuint8 b;\n\t\tuint32 len, val;\n\t\tlong pos = hio_tell(f);\n\t\tif (pos < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\tfor (i = 0; i < mod->ins; i++) {\n\t\t\tb = hio_read8(f);\n\t\t\tif (b != 0 && b != 1) {\n\t\t\t\tno_loopend = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\thio_seek(f, 32 + 13, SEEK_CUR);\n\t\t\tif (hio_read32l(f) > (uint32)mod->ins) { /* check index */\n\t\t\t\tno_loopend = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlen = hio_read32l(f);\n\t\t\tif (len > 0x100000) {\t\t/* check len */\n\t\t\t\tno_loopend = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (hio_read16l(f) == 0x0000) {\t/* check c2spd */\n\t\t\t\tno_loopend = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (hio_read8(f) > 0x40) {\t/* check volume */\n\t\t\t\tno_loopend = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tval = hio_read32l(f);\t\t/* check loop start */\n\t\t\tif (val > len) {\n\t\t\t\tno_loopend = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tval = hio_read32l(f);\t\t/* check loop end */\n\t\t\tif (val > len) {\n\t\t\t\tno_loopend = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\thio_seek(f, pos, SEEK_SET);\n\t}\n\n\tif (no_loopend) {\n\t\tD_(D_INFO \"Detected AMF 1.0 truncated instruments.\");\n\t}\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tint c2spd;\n\n\t\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t\t\treturn -1;\n\n\t\thio_read8(f);\n\n\t\thio_read(buf, 1, 32, f);\n\t\tlibxmp_instrument_name(mod, i, buf, 32);\n\n\t\thio_read(buf, 1, 13, f);\t/* sample name */\n\t\thio_read32l(f);\t\t\t/* sample index */\n\n\t\tmod->xxi[i].nsm = 1;\n\t\tmod->xxi[i].sub[0].sid = i;\n\t\tmod->xxi[i].sub[0].pan = 0x80;\n\n\t\tif (ver >= 0x0a) {\n\t\t\tmod->xxs[i].len = hio_read32l(f);\n\t\t} else {\n\t\t\tmod->xxs[i].len = hio_read16l(f);\n\t\t}\n\t\tc2spd = hio_read16l(f);\n\t\tlibxmp_c2spd_to_note(c2spd, &mod->xxi[i].sub[0].xpo, &mod->xxi[i].sub[0].fin);\n\t\tmod->xxi[i].sub[0].vol = hio_read8(f);\n\n\t\t/*\n\t\t * Andre Timmermans <andre.timmermans@atos.net> says:\n\t\t *\n\t\t * [Miodrag Vallat's] doc tells that in version 1.0 only\n\t\t * sample loop start is present (2 bytes) but the files I\n\t\t * have tells both start and end are present (2*4 bytes).\n\t\t * Maybe it should be read as version < 1.0.\n\t\t *\n\t\t * CM: confirmed with Maelcum's \"The tribal zone\"\n\t\t */\n\n\t\tif (no_loopend != 0) {\n\t\t\tmod->xxs[i].lps = hio_read16l(f);\n\t\t\tmod->xxs[i].lpe = mod->xxs[i].len;\n\t\t} else if (ver >= 0x0a) {\n\t\t\tmod->xxs[i].lps = hio_read32l(f);\n\t\t\tmod->xxs[i].lpe = hio_read32l(f);\n\t\t} else {\n\t\t\t/* Non-looping samples are stored with lpe=-1, not 0. */\n\t\t\tmod->xxs[i].lps = hio_read16l(f);\n\t\t\tmod->xxs[i].lpe = hio_read16l(f);\n\n\t\t\tif (mod->xxs[i].lpe == 0xffff)\n\t\t\t\tmod->xxs[i].lpe = 0;\n\t\t}\n\n\t\tif (no_loopend != 0) {\n\t\t\tmod->xxs[i].flg = mod->xxs[i].lps > 0 ? XMP_SAMPLE_LOOP : 0;\n\t\t} else {\n\t\t\tmod->xxs[i].flg = mod->xxs[i].lpe > mod->xxs[i].lps ?\n\t\t\t\t\t\t\tXMP_SAMPLE_LOOP : 0;\n\t\t}\n\n\t\tD_(D_INFO \"[%2X] %-32.32s %05x %05x %05x %c V%02x %5d\",\n\t\t\ti, mod->xxi[i].name, mod->xxs[i].len, mod->xxs[i].lps,\n\t\t\tmod->xxs[i].lpe, mod->xxs[i].flg & XMP_SAMPLE_LOOP ?\n\t\t\t'L' : ' ', mod->xxi[i].sub[0].vol, c2spd);\n\t}\n\n\tif (hio_error(f))\n\t\treturn -1;\n\n\n\t/* Tracks */\n\n\t/* Index 0 is a blank track that isn't stored in the file. To keep\n\t * things simple, load the mapping table to index 1 so the table\n\t * index is the same as the logical track value. Older versions\n\t * attempted to remap it to index 0 and subtract 1 from the index,\n\t * breaking modules that directly reference the empty track in the\n\t * order table (see \"cosmos st.amf\").\n\t */\n\ttrkmap = (int *) calloc(mod->trk + 1, sizeof(int));\n\tif (trkmap == NULL)\n\t\treturn -1;\n\tnewtrk = 0;\n\n\tfor (i = 1; i <= mod->trk; i++) {\t\t/* read track table */\n\t\tuint16 t;\n\t\tt = hio_read16l(f);\n\t\ttrkmap[i] = t;\n\t\tif (t > newtrk) newtrk = t;\n\t}\n\n\tfor (i = 0; i < mod->pat; i++) {\t\t/* read track table */\n\t\tfor (j = 0; j < mod->chn; j++) {\n\t\t\tuint16 k = mod->xxp[i]->index[j];\n\n\t\t\t/* Use empty track if an invalid track is requested\n\t\t\t * (such as in Lasse Makkonen \"faster and louder\")\n\t\t\t */\n\t\t\tif (k > mod->trk)\n\t\t\t\tk = 0;\n\t\t\tmod->xxp[i]->index[j] = trkmap[k];\n\t\t}\n\t}\n\n\tmod->trk = newtrk + 1;\t\t/* + empty track */\n\tfree(trkmap);\n\n\tif (hio_error(f))\n\t\treturn -1;\n\n\tD_(D_INFO \"Stored tracks: %d\", mod->trk - 1);\n\n\tmod->xxt = (struct xmp_track **) calloc(mod->trk, sizeof(struct xmp_track *));\n\tif (mod->xxt == NULL)\n\t\treturn -1;\n\n\t/* Alloc track 0 as empty track */\n\tif (libxmp_alloc_track(mod, 0, 64) < 0)\n\t\treturn -1;\n\n\t/* Alloc rest of the tracks */\n\tfor (i = 1; i < mod->trk; i++) {\n\t\tuint8 t1, t2, t3;\n\t\tint size;\n\n\t\tif (libxmp_alloc_track(mod, i, 64) < 0)\t/* FIXME! */\n\t\t\treturn -1;\n\n\t\t/* Previous versions loaded this as a 24-bit value, but it's\n\t\t * just a word. The purpose of the third byte is unknown, and\n\t\t * DSMI just ignores it.\n\t\t */\n\t\tsize = hio_read16l(f);\n\t\thio_read8(f);\n\n\t\tif (hio_error(f))\n\t\t\treturn -1;\n\n\t\t/* Version 0.1 AMFs do not count the end-of-track marker in\n\t\t * the event count, so add 1. This hasn't been verified yet. */\n\t\tif (ver == 0x01 && size != 0)\n\t\t\tsize++;\n\n\t\tfor (j = 0; j < size; j++) {\n\t\t\tt1 = hio_read8(f);\t\t\t/* row */\n\t\t\tt2 = hio_read8(f);\t\t\t/* type */\n\t\t\tt3 = hio_read8(f);\t\t\t/* parameter */\n\n\t\t\tif (t1 == 0xff && t2 == 0xff && t3 == 0xff)\n\t\t\t\tbreak;\n\n\t\t\t/* If an event is encountered past the end of the\n\t\t\t * track, treat it the same as the track end. This is\n\t\t\t * encountered in \"Avoid.amf\".\n\t\t\t */\n\t\t\tif (t1 >= mod->xxt[i]->rows) {\n\t\t\t\tif (hio_seek(f, (size - j - 1) * 3, SEEK_CUR))\n\t\t\t\t\treturn -1;\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tevent = &mod->xxt[i]->event[t1];\n\n\t\t\tif (t2 < 0x7f) {\t\t/* note */\n\t\t\t\tif (t2 > 0)\n\t\t\t\t\tevent->note = t2 + 1;\n\t\t\t\t/* A volume value of 0xff indicates that\n\t\t\t\t * the old volume should be reused. Prior\n\t\t\t\t * libxmp versions also forgot to add 1 here.\n\t\t\t\t */\n\t\t\t\tevent->vol = (t3 != 0xff) ? (t3 + 1) : 0;\n\t\t\t} else if (t2 == 0x7f) {\t/* note retrigger */\n\n\t\t\t\t/* AMF.TXT claims that this duplicates the\n\t\t\t\t * previous event, which is a lie. M2AMF emits\n\t\t\t\t * this for MODs when an instrument change\n\t\t\t\t * occurs with no note, indicating it should\n\t\t\t\t * retrigger (like in PT 2.3). Ignore this.\n\t\t\t\t *\n\t\t\t\t * See: \"aladdin - aladd.pc.amf\", \"eye.amf\".\n\t\t\t\t */\n\t\t\t} else if (t2 == 0x80) {\t/* instrument */\n\t\t\t\tevent->ins = t3 + 1;\n\t\t\t} else  {\t\t\t/* effects */\n\t\t\t\tuint8 fxp, fxt;\n\n\t\t\t\tfxp = fxt = 0;\n\n\t\t\t\tswitch (t2) {\n\t\t\t\tcase 0x81:\n\t\t\t\t\tfxt = FX_S3M_SPEED;\n\t\t\t\t\tfxp = t3;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x82:\n\t\t\t\t\tif ((int8)t3 > 0) {\n\t\t\t\t\t\tfxt = FX_VOLSLIDE;\n\t\t\t\t\t\tfxp = t3 << 4;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfxt = FX_VOLSLIDE;\n\t\t\t\t\t\tfxp = -(int8)t3 & 0x0f;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x83:\n\t\t\t\t\t/* See volume notes above. Previous\n\t\t\t\t\t * releases forgot to add 1 here. */\n\t\t\t\t\tevent->vol = (t3 + 1);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x84:\n\t\t\t\t\t/* AT: Not explained for 0x84, pitch\n\t\t\t\t\t * slide, value 0x00 corresponds to\n\t\t\t\t\t * S3M E00 and 0x80 stands for S3M F00\n\t\t\t\t\t * (I checked with M2AMF)\n\t\t\t\t\t */\n\t\t\t\t\tif ((int8)t3 >= 0) {\n\t\t\t\t\t\tfxt = FX_PORTA_DN;\n\t\t\t\t\t\tfxp = t3;\n\t\t\t\t\t} else if (t3 == 0x80) {\n\t\t\t\t\t\tfxt = FX_PORTA_UP;\n\t\t\t\t\t\tfxp = 0;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfxt = FX_PORTA_UP;\n\t\t\t\t\t\tfxp = -(int8)t3;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x85:\n\t\t\t\t\t/* porta abs -- unknown */\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x86:\n\t\t\t\t\tfxt = FX_TONEPORTA;\n\t\t\t\t\tfxp = t3;\n\t\t\t\t\tbreak;\n\n\t\t\t\t/* AT: M2AMF maps both tremolo and tremor to\n\t\t\t\t * 0x87. Since tremor is only found in certain\n\t\t\t\t * formats, maybe it would be better to\n\t\t\t\t * consider it is a tremolo.\n\t\t\t\t */\n\t\t\t\tcase 0x87:\n\t\t\t\t\tfxt = FX_TREMOLO;\n\t\t\t\t\tfxp = t3;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x88:\n\t\t\t\t\tfxt = FX_ARPEGGIO;\n\t\t\t\t\tfxp = t3;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x89:\n\t\t\t\t\tfxt = FX_VIBRATO;\n\t\t\t\t\tfxp = t3;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x8a:\n\t\t\t\t\tif ((int8)t3 > 0) {\n\t\t\t\t\t\tfxt = FX_TONE_VSLIDE;\n\t\t\t\t\t\tfxp = t3 << 4;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfxt = FX_TONE_VSLIDE;\n\t\t\t\t\t\tfxp = -(int8)t3 & 0x0f;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x8b:\n\t\t\t\t\tif ((int8)t3 > 0) {\n\t\t\t\t\t\tfxt = FX_VIBRA_VSLIDE;\n\t\t\t\t\t\tfxp = t3 << 4;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfxt = FX_VIBRA_VSLIDE;\n\t\t\t\t\t\tfxp = -(int8)t3 & 0x0f;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x8c:\n\t\t\t\t\tfxt = FX_BREAK;\n\t\t\t\t\tfxp = t3;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x8d:\n\t\t\t\t\tfxt = FX_JUMP;\n\t\t\t\t\tfxp = t3;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x8e:\n\t\t\t\t\t/* sync -- unknown */\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x8f:\n\t\t\t\t\tfxt = FX_EXTENDED;\n\t\t\t\t\tfxp = (EX_RETRIG << 4) | (t3 & 0x0f);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x90:\n\t\t\t\t\tfxt = FX_OFFSET;\n\t\t\t\t\tfxp = t3;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x91:\n\t\t\t\t\tif ((int8)t3 > 0) {\n\t\t\t\t\t\tfxt = FX_EXTENDED;\n\t\t\t\t\t\tfxp = (EX_F_VSLIDE_UP << 4) |\n\t\t\t\t\t\t\t(t3 & 0x0f);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfxt = FX_EXTENDED;\n\t\t\t\t\t\tfxp = (EX_F_VSLIDE_DN << 4) |\n\t\t\t\t\t\t\t(t3 & 0x0f);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x92:\n\t\t\t\t\tif ((int8)t3 > 0) {\n\t\t\t\t\t\tfxt = FX_PORTA_DN;\n\t\t\t\t\t\tfxp = 0xf0 | (fxp & 0x0f);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfxt = FX_PORTA_UP;\n\t\t\t\t\t\tfxp = 0xf0 | (fxp & 0x0f);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x93:\n\t\t\t\t\tfxt = FX_EXTENDED;\n\t\t\t\t\tfxp = (EX_DELAY << 4) | (t3 & 0x0f);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x94:\n\t\t\t\t\tfxt = FX_EXTENDED;\n\t\t\t\t\tfxp = (EX_CUT << 4) | (t3 & 0x0f);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x95:\n\t\t\t\t\tfxt = FX_SPEED;\n\t\t\t\t\tif (t3 < 0x21)\n\t\t\t\t\t\tt3 = 0x21;\n\t\t\t\t\tfxp = t3;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x96:\n\t\t\t\t\tif ((int8)t3 > 0) {\n\t\t\t\t\t\tfxt = FX_PORTA_DN;\n\t\t\t\t\t\tfxp = 0xe0 | (fxp & 0x0f);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfxt = FX_PORTA_UP;\n\t\t\t\t\t\tfxp = 0xe0 | (fxp & 0x0f);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x97:\n\t\t\t\t\t/* Same as S3M pan, but param is offset by -0x40. */\n\t\t\t\t\tif (t3 == 0x64) { /* 0xA4 - 0x40 */\n\t\t\t\t\t\tfxt = FX_SURROUND;\n\t\t\t\t\t\tfxp = 1;\n\t\t\t\t\t} else if (t3 >= 0xC0 || t3 <= 0x40) {\n\t\t\t\t\t\tint pan = ((int8)t3 << 1) + 0x80;\n\t\t\t\t\t\tfxt = FX_SETPAN;\n\t\t\t\t\t\tfxp = MIN(0xff, pan);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tevent->fxt = fxt;\n\t\t\t\tevent->fxp = fxp;\n\t\t\t}\n\t\t}\n\t}\n\n\n\t/* Samples */\n\n\tD_(D_INFO \"Stored samples: %d\", mod->smp);\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tif (libxmp_load_sample(m, f, SAMPLE_FLAG_UNS, &mod->xxs[i], NULL) < 0)\n\t\t\treturn -1;\n\t}\n\n\tm->quirk |= QUIRK_FINEFX;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/asylum_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2021 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/*\n * Based on AMF->MOD converter written by Mr. P / Powersource, 1995\n */\n\n#include \"loader.h\"\n#include \"../period.h\"\n\nstatic int asylum_test(HIO_HANDLE *, char *, const int);\nstatic int asylum_load(struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_asylum = {\n\t\"Asylum Music Format v1.0\",\n\tasylum_test,\n\tasylum_load\n};\n\nstatic int asylum_test(HIO_HANDLE *f, char *t, const int start)\n{\n\tchar buf[32];\n\n\tif (hio_read(buf, 1, 32, f) < 32)\n\t\treturn -1;\n\n\tif (memcmp(buf, \"ASYLUM Music Format V1.0\\0\\0\\0\\0\\0\\0\\0\\0\", 32))\n\t\treturn -1;\n\n\tlibxmp_read_title(f, t, 0);\n\n\treturn 0;\n}\n\nstatic int asylum_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_event *event;\n\tuint8 buf[2048];\n\tint i, j;\n\n\tLOAD_INIT();\n\n\thio_seek(f, 32, SEEK_CUR);\t\t\t/* skip magic */\n\tmod->spd = hio_read8(f);\t\t\t/* initial speed */\n\tmod->bpm = hio_read8(f);\t\t\t/* initial BPM */\n\tmod->ins = hio_read8(f);\t\t\t/* number of instruments */\n\tmod->pat = hio_read8(f);\t\t\t/* number of patterns */\n\tmod->len = hio_read8(f);\t\t\t/* module length */\n\tmod->rst = hio_read8(f);\t\t\t/* restart byte */\n\n\t/* Sanity check - this format only stores 64 sample structures. */\n\tif (mod->ins > 64) {\n\t\tD_(D_CRIT \"invalid sample count %d\", mod->ins);\n\t\treturn -1;\n\t}\n\n\thio_read(mod->xxo, 1, mod->len, f);\t/* read orders */\n\thio_seek(f, start + 294, SEEK_SET);\n\n\tmod->chn = 8;\n\tmod->smp = mod->ins;\n\tmod->trk = mod->pat * mod->chn;\n\n\tsnprintf(mod->type, XMP_NAME_SIZE, \"Asylum Music Format v1.0\");\n\n\tMODULE_INFO();\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\treturn -1;\n\n\t/* Read and convert instruments and samples */\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tuint8 insbuf[37];\n\n\t\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\t{\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (hio_read(insbuf, 1, 37, f) != 37) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tlibxmp_instrument_name(mod, i, insbuf, 22);\n\t\tmod->xxi[i].sub[0].fin = (int8)(insbuf[22] << 4);\n\t\tmod->xxi[i].sub[0].vol = insbuf[23];\n\t\tmod->xxi[i].sub[0].xpo = (int8)insbuf[24];\n\t\tmod->xxi[i].sub[0].pan = 0x80;\n\t\tmod->xxi[i].sub[0].sid = i;\n\n\t\tmod->xxs[i].len = readmem32l(insbuf + 25);\n\t\tmod->xxs[i].lps = readmem32l(insbuf + 29);\n\t\tmod->xxs[i].lpe = mod->xxs[i].lps + readmem32l(insbuf + 33);\n\n\t\t/* Sanity check - ASYLUM modules are converted from MODs. */\n\t\tif ((uint32)mod->xxs[i].len >= 0x20000) {\n\t\t\tD_(D_CRIT \"invalid sample %d length %d\", i, mod->xxs[i].len);\n\t\t\treturn -1;\n\t\t}\n\n\t\tmod->xxs[i].flg = mod->xxs[i].lpe > 2 ? XMP_SAMPLE_LOOP : 0;\n\n\t\tD_(D_INFO \"[%2X] %-22.22s %04x %04x %04x %c V%02x %d\", i,\n\t\t   mod->xxi[i].name, mod->xxs[i].len, mod->xxs[i].lps,\n\t\t   mod->xxs[i].lpe,\n\t\t   mod->xxs[i].flg & XMP_SAMPLE_LOOP ? 'L' : ' ',\n\t\t   mod->xxi[i].sub[0].vol, mod->xxi[i].sub[0].fin);\n\t}\n\n\thio_seek(f, 37 * (64 - mod->ins), SEEK_CUR);\n\n\tD_(D_INFO \"Module length: %d\", mod->len);\n\n\tif (libxmp_init_pattern(mod) < 0)\n\t\treturn -1;\n\n\t/* Read and convert patterns */\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tuint8 *pos;\n\t\tif (libxmp_alloc_pattern_tracks(mod, i, 64) < 0)\n\t\t\treturn -1;\n\n\t\tif (hio_read(buf, 1, 2048, f) < 2048) {\n\t\t\tD_(D_CRIT \"read error at pattern %d\", i);\n\t\t\treturn -1;\n\t\t}\n\n\t\tpos = buf;\n\t\tfor (j = 0; j < 64 * 8; j++) {\n\t\t\tuint8 note;\n\n\t\t\tevent = &EVENT(i, j % 8, j / 8);\n\t\t\tmemset(event, 0, sizeof(struct xmp_event));\n\t\t\tnote = *pos++;\n\n\t\t\tif (note != 0) {\n\t\t\t\tevent->note = note + 13;\n\t\t\t}\n\n\t\t\tevent->ins = *pos++;\n\t\t\tevent->fxt = *pos++;\n\t\t\tevent->fxp = *pos++;\n\n\t\t\t/* TODO: m07.amf and m12.amf from Crusader: No Remorse\n\t\t\t * use 0x1b for what looks *plausibly* like retrigger.\n\t\t\t * No other ASYLUM modules use effects over 16. */\n\t\t\tif (event->fxt >= 0x10 && event->fxt != FX_MULTI_RETRIG)\n\t\t\t\tevent->fxt = event->fxp = 0;\n\t\t}\n\t}\n\n\t/* Read samples */\n\tD_(D_INFO \"Stored samples: %d\", mod->smp);\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tif (mod->xxs[i].len > 1) {\n\t\t\tif (libxmp_load_sample(m, f, 0, &mod->xxs[i], NULL) < 0)\n\t\t\t\treturn -1;\n\t\t\tmod->xxi[i].nsm = 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/common.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <ctype.h>\n#if defined(HAVE_DIRENT)\n#include <sys/types.h>\n#include <dirent.h>\n#endif\n\n#include \"../common.h\"\n\n#include \"xmp.h\"\n#include \"../period.h\"\n#include \"loader.h\"\n\nint libxmp_init_instrument(struct module_data *m)\n{\n\tstruct xmp_module *mod = &m->mod;\n\n\tif (mod->ins > 0) {\n\t\tmod->xxi = (struct xmp_instrument *) calloc(mod->ins, sizeof(struct xmp_instrument));\n\t\tif (mod->xxi == NULL)\n\t\t\treturn -1;\n\t}\n\n\tif (mod->smp > 0) {\n\t\tint i;\n\t\t/* Sanity check */\n\t\tif (mod->smp > MAX_SAMPLES) {\n\t\t\tD_(D_CRIT \"sample count %d exceeds maximum (%d)\",\n\t\t\t   mod->smp, MAX_SAMPLES);\n\t\t\treturn -1;\n\t\t}\n\n\t\tmod->xxs = (struct xmp_sample *) calloc(mod->smp, sizeof(struct xmp_sample));\n\t\tif (mod->xxs == NULL)\n\t\t\treturn -1;\n\t\tm->xtra = (struct extra_sample_data *) calloc(mod->smp, sizeof(struct extra_sample_data));\n\t\tif (m->xtra == NULL)\n\t\t\treturn -1;\n\n\t\tfor (i = 0; i < mod->smp; i++) {\n\t\t\tm->xtra[i].c5spd = m->c4rate;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/* Sample number adjustment (originally by Vitamin/CAIG).\n * Only use this AFTER a previous usage of libxmp_init_instrument,\n * and don't use this to free samples that have already been loaded. */\nint libxmp_realloc_samples(struct module_data *m, int new_size)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_sample *xxs;\n\tstruct extra_sample_data *xtra;\n\n\t/* Sanity check */\n\tif (new_size < 0)\n\t\treturn -1;\n\n\tif (new_size == 0) {\n\t\t/* Don't rely on implementation-defined realloc(x,0) behavior. */\n\t\tmod->smp = 0;\n\t\tfree(mod->xxs);\n\t\tmod->xxs = NULL;\n\t\tfree(m->xtra);\n\t\tm->xtra = NULL;\n\t\treturn 0;\n\t}\n\n\txxs = (struct xmp_sample *) realloc(mod->xxs, sizeof(struct xmp_sample) * new_size);\n\tif (xxs == NULL)\n\t\treturn -1;\n\tmod->xxs = xxs;\n\n\txtra = (struct extra_sample_data *) realloc(m->xtra, sizeof(struct extra_sample_data) * new_size);\n\tif (xtra == NULL)\n\t\treturn -1;\n\tm->xtra = xtra;\n\n\tif (new_size > mod->smp) {\n\t\tint clear_size = new_size - mod->smp;\n\t\tint i;\n\n\t\tmemset(xxs + mod->smp, 0, sizeof(struct xmp_sample) * clear_size);\n\t\tmemset(xtra + mod->smp, 0, sizeof(struct extra_sample_data) * clear_size);\n\n\t\tfor (i = mod->smp; i < new_size; i++) {\n\t\t\tm->xtra[i].c5spd = m->c4rate;\n\t\t}\n\t}\n\tmod->smp = new_size;\n\treturn 0;\n}\n\nint libxmp_alloc_subinstrument(struct xmp_module *mod, int i, int num)\n{\n\tif (num == 0)\n\t\treturn 0;\n\n\tmod->xxi[i].sub = (struct xmp_subinstrument *) calloc(num, sizeof(struct xmp_subinstrument));\n\tif (mod->xxi[i].sub == NULL)\n\t\treturn -1;\n\n\treturn 0;\n}\n\nint libxmp_init_pattern(struct xmp_module *mod)\n{\n\tmod->xxt = (struct xmp_track **) calloc(mod->trk, sizeof(struct xmp_track *));\n\tif (mod->xxt == NULL)\n\t\treturn -1;\n\n\tmod->xxp = (struct xmp_pattern **) calloc(mod->pat, sizeof(struct xmp_pattern *));\n\tif (mod->xxp == NULL)\n\t\treturn -1;\n\n\treturn 0;\n}\n\nint libxmp_alloc_pattern(struct xmp_module *mod, int num)\n{\n\t/* Sanity check */\n\tif (num < 0 || num >= mod->pat || mod->xxp[num] != NULL)\n\t\treturn -1;\n\n\tmod->xxp[num] = (struct xmp_pattern *) calloc(1, sizeof(struct xmp_pattern) +\n\t\t\t\t\t\t\t sizeof(int) * (mod->chn - 1));\n\tif (mod->xxp[num] == NULL)\n\t\treturn -1;\n\n\treturn 0;\n}\n\nint libxmp_alloc_track(struct xmp_module *mod, int num, int rows)\n{\n\t/* Sanity check */\n\tif (num < 0 || num >= mod->trk || mod->xxt[num] != NULL || rows <= 0)\n\t\treturn -1;\n\n\tmod->xxt[num] = (struct xmp_track *) calloc(1,  sizeof(struct xmp_track) +\n\t\t\t\t\t\t\tsizeof(struct xmp_event) * (rows - 1));\n\tif (mod->xxt[num] == NULL)\n\t\treturn -1;\n\n\tmod->xxt[num]->rows = rows;\n\n\treturn 0;\n}\n\nint libxmp_alloc_tracks_in_pattern(struct xmp_module *mod, int num)\n{\n\tint i;\n\n\tD_(D_INFO \"Alloc %d tracks of %d rows\", mod->chn, mod->xxp[num]->rows);\n\tfor (i = 0; i < mod->chn; i++) {\n\t\tint t = num * mod->chn + i;\n\t\tint rows = mod->xxp[num]->rows;\n\n\t\tif (libxmp_alloc_track(mod, t, rows) < 0)\n\t\t\treturn -1;\n\n\t\tmod->xxp[num]->index[i] = t;\n\t}\n\n\treturn 0;\n}\n\nint libxmp_alloc_pattern_tracks(struct xmp_module *mod, int num, int rows)\n{\n\t/* Sanity check */\n\tif (rows <= 0 || rows > 256)\n\t\treturn -1;\n\n\tif (libxmp_alloc_pattern(mod, num) < 0)\n\t\treturn -1;\n\n\tmod->xxp[num]->rows = rows;\n\n\tif (libxmp_alloc_tracks_in_pattern(mod, num) < 0)\n\t\treturn -1;\n\n\treturn 0;\n}\n\n#ifndef LIBXMP_CORE_PLAYER\n/* Some formats explicitly allow more than 256 rows (e.g. OctaMED). This function\n * allows those formats to work without disrupting the sanity check for other formats.\n */\nint libxmp_alloc_pattern_tracks_long(struct xmp_module *mod, int num, int rows)\n{\n\t/* Sanity check */\n\tif (rows <= 0 || rows > 32768)\n\t\treturn -1;\n\n\tif (libxmp_alloc_pattern(mod, num) < 0)\n\t\treturn -1;\n\n\tmod->xxp[num]->rows = rows;\n\n\tif (libxmp_alloc_tracks_in_pattern(mod, num) < 0)\n\t\treturn -1;\n\n\treturn 0;\n}\n#endif\n\nchar *libxmp_instrument_name(struct xmp_module *mod, int i, uint8 *r, int n)\n{\n\tCLAMP(n, 0, 31);\n\n\treturn libxmp_copy_adjust(mod->xxi[i].name, r, n);\n}\n\nchar *libxmp_copy_adjust(char *s, uint8 *r, int n)\n{\n\tint i;\n\n\tmemset(s, 0, n + 1);\n\tstrncpy(s, (char *)r, n);\n\n\tfor (i = 0; s[i] && i < n; i++) {\n\t\tif (!isprint((unsigned char)s[i]) || ((uint8)s[i] > 127))\n\t\t\ts[i] = '.';\n\t}\n\n\twhile (*s && (s[strlen(s) - 1] == ' '))\n\t\ts[strlen(s) - 1] = 0;\n\n\treturn s;\n}\n\nvoid libxmp_read_title(HIO_HANDLE *f, char *t, int s)\n{\n\tuint8 buf[XMP_NAME_SIZE];\n\n\tif (t == NULL || s < 0)\n\t\treturn;\n\n\tif (s >= XMP_NAME_SIZE)\n\t\ts = XMP_NAME_SIZE -1;\n\n\tmemset(t, 0, s + 1);\n\n\ts = hio_read(buf, 1, s, f);\n\tbuf[s] = 0;\n\tlibxmp_copy_adjust(t, buf, s);\n}\n\n#ifndef LIBXMP_CORE_PLAYER\n\nint libxmp_test_name(const uint8 *s, int n, int flags)\n{\n\tint i;\n\n\tfor (i = 0; i < n; i++) {\n\t\tif (s[i] == '\\0' && (flags & TEST_NAME_IGNORE_AFTER_0))\n\t\t\tbreak;\n\t\tif (s[i] == '\\r' && (flags & TEST_NAME_IGNORE_AFTER_CR))\n\t\t\tbreak;\n\t\tif (s[i] > 0x7f)\n\t\t\treturn -1;\n\t\t/* ACS_Team2.mod has a backspace in instrument name */\n\t\t/* Numerous ST modules from Music Channel BBS have char 14. */\n\t\tif (s[i] > 0 && s[i] < 32 && s[i] != 0x08 && s[i] != 0x0e)\n\t\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint libxmp_copy_name_for_fopen(char *dest, const char *name, int n)\n{\n\tint converted_colon = 0;\n\tint i;\n\n\t/* libxmp_copy_adjust, but make sure the filename won't do anything\n\t * malicious when given to fopen. This should only be used on song files.\n\t */\n\tif (!strcmp(name, \".\") || strstr(name, \"..\") ||\n\t    name[0] == '\\\\' || name[0] == '/' || name[0] == ':' || name[0] == '\\0')\n\t\treturn -1;\n\n\tfor (i = 0; i < n - 1; i++) {\n\t\tuint8 t = name[i];\n\t\tif (!t)\n\t\t\tbreak;\n\n\t\t/* Reject non-ASCII symbols as they have poorly defined behavior.\n\t\t */\n\t\tif (t < 32 || t >= 0x7f)\n\t\t\treturn -1;\n\n\t\t/* Reject anything resembling a Windows-style root path. Allow\n\t\t * converting a single : to / so things like ST-01:samplename\n\t\t * work. (Leave the : as-is on Amiga.)\n\t\t */\n\t\tif (i > 0 && t == ':' && !converted_colon) {\n\t\t\tuint8 t2 = name[i + 1];\n\t\t\tif (!t2 || t2 == '/' || t2 == '\\\\')\n\t\t\t\treturn -1;\n\n\t\t\tconverted_colon = 1;\n#ifndef LIBXMP_AMIGA\n\t\t\tdest[i] = '/';\n\t\t\tcontinue;\n#endif\n\t\t}\n\n\t\tif (t == '\\\\') {\n\t\t\tdest[i] = '/';\n\t\t\tcontinue;\n\t\t}\n\n\t\tdest[i] = t;\n\t}\n\tdest[i] = '\\0';\n\treturn 0;\n}\n\n/*\n * Honor Noisetracker effects:\n *\n *  0 - arpeggio\n *  1 - portamento up\n *  2 - portamento down\n *  3 - Tone-portamento\n *  4 - Vibrato\n *  A - Slide volume\n *  B - Position jump\n *  C - Set volume\n *  D - Pattern break\n *  E - Set filter (keep the led off, please!)\n *  F - Set speed (now up to $1F)\n *\n * Pex Tufvesson's notes from http://www.livet.se/mahoney/:\n *\n * Note that some of the modules will have bugs in the playback with all\n * known PC module players. This is due to that in many demos where I synced\n * events in the demo with the music, I used commands that these newer PC\n * module players erroneously interpret as \"newer-version-trackers commands\".\n * Which they aren't.\n */\nvoid libxmp_decode_noisetracker_event(struct xmp_event *event, const uint8 *mod_event)\n{\n\tint fxt;\n\n\tmemset(event, 0, sizeof (struct xmp_event));\n\tevent->note = libxmp_period_to_note((LSN(mod_event[0]) << 8) + mod_event[1]);\n\tevent->ins = ((MSN(mod_event[0]) << 4) | MSN(mod_event[2]));\n\tfxt = LSN(mod_event[2]);\n\n\tif (fxt <= 0x06 || (fxt >= 0x0a && fxt != 0x0e)) {\n\t\tevent->fxt = fxt;\n\t\tevent->fxp = mod_event[3];\n\t}\n\n\tlibxmp_disable_continue_fx(event);\n}\n#endif\n\nvoid libxmp_decode_protracker_event(struct xmp_event *event, const uint8 *mod_event)\n{\n\tint fxt = LSN(mod_event[2]);\n\n\tmemset(event, 0, sizeof (struct xmp_event));\n\tevent->note = libxmp_period_to_note((LSN(mod_event[0]) << 8) + mod_event[1]);\n\tevent->ins = ((MSN(mod_event[0]) << 4) | MSN(mod_event[2]));\n\n\tif (fxt != 0x08) {\n\t\tevent->fxt = fxt;\n\t\tevent->fxp = mod_event[3];\n\t}\n\n\tlibxmp_disable_continue_fx(event);\n}\n\nvoid libxmp_disable_continue_fx(struct xmp_event *event)\n{\n\tif (event->fxp == 0) {\n\t\tswitch (event->fxt) {\n\t\tcase 0x05:\n\t\t\tevent->fxt = 0x03;\n\t\t\tbreak;\n\t\tcase 0x06:\n\t\t\tevent->fxt = 0x04;\n\t\t\tbreak;\n\t\tcase 0x01:\n\t\tcase 0x02:\n\t\tcase 0x0a:\n\t\t\tevent->fxt = 0x00;\n\t\t}\n\t} else if (event->fxt == 0x0e) {\n\t\tif (event->fxp == 0xa0 || event->fxp == 0xb0) {\n\t\t\tevent->fxt = event->fxp = 0;\n\t\t}\n\t}\n}\n\n#ifndef LIBXMP_CORE_PLAYER\n/* libxmp_check_filename_case(): */\n/* Given a directory, see if file exists there, ignoring case */\n\n#if defined(_WIN32) || defined(__DJGPP__)  || \\\n    defined(__OS2__) || defined(__EMX__)   || \\\n    defined(_DOS) || defined(LIBXMP_AMIGA) || \\\n    defined(__riscos__) || \\\n    /* case-insensitive file system: directly probe the file */\\\n    \\\n   !defined(HAVE_DIRENT) /* or, target does not have dirent. */\nint libxmp_check_filename_case(const char *dir, const char *name, char *new_name, int size)\n{\n\tchar path[XMP_MAXPATH];\n\tint ret;\n\n\tif (dir[0] == '\\0')\n\t\tdir = \".\";\n\n\tsnprintf(path, sizeof(path), \"%s/%s\", dir, name);\n\tif (! (libxmp_get_filetype(path) & XMP_FILETYPE_FILE))\n\t\treturn 0;\n\tret = snprintf(new_name, size, \"%s\", name);\n\treturn (ret < size);\n}\n#else /* target has dirent */\nint libxmp_check_filename_case(const char *dir, const char *name, char *new_name, int size)\n{\n\tint found = 0;\n\tint ret = size;\n\tDIR *dirp;\n\tstruct dirent *d;\n\n\tif (dir[0] == '\\0')\n\t\tdir = \".\";\n\n\tdirp = opendir(dir);\n\tif (dirp == NULL)\n\t\treturn 0;\n\n\twhile ((d = readdir(dirp)) != NULL) {\n\t\tif (!strcasecmp(d->d_name, name)) {\n\t\t\tfound = 1;\n\t\t\tret = snprintf(new_name, size, \"%s\", d->d_name);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tclosedir(dirp);\n\n\treturn found ? (ret < size) : 0;\n}\n#endif\n\nstatic const char *libxmp_get_instrument_path(struct module_data *m)\n{\n\tconst char *env;\n\tif (m->instrument_path) {\n\t\treturn m->instrument_path;\n\t}\n\tenv = getenv(\"XMP_INSTRUMENT_PATH\");\n\tif (env) {\n\t\treturn env;\n\t}\n\treturn NULL;\n}\n\nint libxmp_find_instrument_file(struct module_data *m, char *path_dest,\n\t\t\t\tint path_dest_len, const char *ins_name)\n{\n\tconst char *ins_path;\n\tchar name[256];\n\tint ret;\n\n\tins_path = libxmp_get_instrument_path(m);\n\tif (ins_path != NULL &&\n\t    libxmp_check_filename_case(ins_path, ins_name, name, sizeof(name))) {\n\t\tret = snprintf(path_dest, path_dest_len, \"%s/%s\", ins_path, name);\n\t\tpath_dest[path_dest_len - 1] = '\\0';\n\t\treturn (ret < path_dest_len);\n\t}\n\n\t/* Try the module dir if the instrument path didn't work. */\n\tif (m->dirname != NULL &&\n\t    libxmp_check_filename_case(m->dirname, ins_name, name, sizeof(name))) {\n\t\tret = snprintf(path_dest, path_dest_len, \"%s%s\", m->dirname, name);\n\t\tpath_dest[path_dest_len - 1] = '\\0';\n\t\treturn (ret < path_dest_len);\n\t}\n\n\tD_(D_WARN \"instrument '%s' not found (ins_path: '%s') (m->dirname: '%s')\",\n\t   ins_name, ins_path ? ins_path : \"NULL\", m->dirname ? m->dirname : \"NULL\");\n\treturn 0;\n}\n#endif /* LIBXMP_CORE_PLAYER */\n\nvoid libxmp_set_type(struct module_data *m, const char *fmt, ...)\n{\n\tva_list ap;\n\tva_start(ap, fmt);\n\n\tvsnprintf(m->mod.type, XMP_NAME_SIZE, fmt, ap);\n\tva_end(ap);\n}\n\n#ifndef LIBXMP_CORE_PLAYER\nstatic int schism_tracker_date(int year, int month, int day)\n{\n\tint mm = (month + 9) % 12;\n\tint yy = year - mm / 10;\n\n\tyy = yy * 365 + (yy / 4) - (yy / 100) + (yy / 400);\n\tmm = (mm * 306 + 5) / 10;\n\n\treturn yy + mm + (day - 1);\n}\n\n/* Generate a Schism Tracker version string.\n * Schism Tracker versions are stored as follows:\n *\n * s_ver <= 0x50:\t\t0.s_ver\n * s_ver > 0x50, < 0xfff:\tdays from epoch=(s_ver - 0x50)\n * s_ver = 0xfff:\t\tdays from epoch=l_ver\n */\nvoid libxmp_schism_tracker_string(char *buf, size_t size, int s_ver, int l_ver)\n{\n\tif (s_ver >= 0x50) {\n\t\t/* time_t epoch_sec = 1256947200; */\n\t\tint64 t = schism_tracker_date(2009, 10, 31);\n\t\tint year, month, day, dayofyear;\n\n\t\tif (s_ver == 0xfff) {\n\t\t\tt += l_ver;\n\t\t} else\n\t\t\tt += s_ver - 0x50;\n\n\t\t/* Date algorithm reimplemented from OpenMPT.\n\t\t */\n\t\tyear = (int)((t * 10000L + 14780) / 3652425);\n\t\tdayofyear = t - (365L * year + (year / 4) - (year / 100) + (year / 400));\n\t\tif (dayofyear < 0) {\n\t\t\tyear--;\n\t\t\tdayofyear = t - (365L * year + (year / 4) - (year / 100) + (year / 400));\n\t\t}\n\t\tmonth = (100 * dayofyear + 52) / 3060;\n\t\tday = dayofyear - (month * 306 + 5) / 10 + 1;\n\n\t\tyear += (month + 2) / 12;\n\t\tmonth = (month + 2) % 12 + 1;\n\n\t\tsnprintf(buf, size, \"Schism Tracker %04d-%02d-%02d\",\n\t\t\tyear, month, day);\n\t} else {\n\t\tsnprintf(buf, size, \"Schism Tracker 0.%x\", s_ver);\n\t}\n}\n\n/* Old MPT modules (from MPT <=1.16, older versions of OpenMPT) rely on a\n * pre-amp routine that scales mix volume down. This is based on the module's\n * channel count and a tracker pre-amp setting that isn't saved in the module.\n * This setting defaults to 128. When fixed to 128, it can be optimized out.\n *\n * In OpenMPT, this pre-amp routine is only available in the MPT and OpenMPT\n * 1.17 RC1 and RC2 mix modes. Changing a module to the compatible or 1.17 RC3\n * mix modes will permanently disable it for that module. OpenMPT applies the\n * old mix modes to MPT <=1.16 modules, \"IT 8.88\", and in old OpenMPT-made\n * modules that specify one of these mix modes in their extended properties.\n *\n * Set mod->chn and m->mvol first!\n */\nvoid libxmp_apply_mpt_preamp(struct module_data *m)\n{\n\t/* OpenMPT uses a slightly different table. */\n\tstatic const uint8 preamp_table[16] =\n\t{\n\t\t0x60, 0x60, 0x60, 0x70,\t/* 0-7 */\n\t\t0x80, 0x88, 0x90, 0x98,\t/* 8-15 */\n\t\t0xA0, 0xA4, 0xA8, 0xB0,\t/* 16-23 */\n\t\t0xB4, 0xB8, 0xBC, 0xC0,\t/* 24-31 */\n\t};\n\n\tint chn = m->mod.chn;\n\tCLAMP(chn, 1, 31);\n\n\tm->mvol = (m->mvol * 96) / preamp_table[chn >> 1];\n\n\t/* Pre-amp is applied like this in the mixers of libmodplug/libopenmpt\n\t * (still vastly simplified).\n\n\tint preamp = 128;\n\n\tif (preamp > 128) {\n\t\tpreamp = 128 + ((preamp - 128) * (chn + 4)) / 16;\n\t}\n\tpreamp = preamp * m->mvol / 64;\n\tpreamp = (preamp << 7) / preamp_table[chn >> 1];\n\n\t...\n\n\tchannel_volume_16bit = (channel_volume_16bit * preamp) >> 7;\n\t*/\n}\n#endif\n\nchar *libxmp_strdup(const char *src)\n{\n\tsize_t len;\n\tchar *buf;\n\tif (src == NULL) {\n\t\treturn NULL;\n\t}\n\tlen = strlen(src) + 1;\n\tbuf = (char *) malloc(len);\n\tif (buf) {\n\t\tmemcpy(buf, src, len);\n\t}\n\treturn buf;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/far_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/* Based on the Farandole Composer format specifications by Megan Potter.\n *\n * \"(...) this format is for EDITING purposes (storing EVERYTHING you're\n * working on) so it may include information not completely neccessary.\"\n */\n\n#include \"loader.h\"\n#include \"../far_extras.h\"\n\nstruct far_header {\n\tuint32 magic;\t\t/* File magic: 'FAR\\xfe' */\n\tuint8 name[40];\t\t/* Song name */\n\tuint8 crlf[3];\t\t/* 0x0d 0x0a 0x1A */\n\tuint16 headersize;\t/* Remaining header size in bytes */\n\tuint8 version;\t\t/* Version MSN=major, LSN=minor */\n\tuint8 ch_on[16];\t/* Channel on/off switches */\n\tuint8 rsvd1[9];\t\t/* Current editing values */\n\tuint8 tempo;\t\t/* Default tempo */\n\tuint8 pan[16];\t\t/* Channel pan definitions */\n\tuint8 rsvd2[4];\t\t/* Grid, mode (for editor) */\n\tuint16 textlen;\t\t/* Length of embedded text */\n};\n\nstruct far_header2 {\n\tuint8 order[256];\t/* Orders */\n\tuint8 patterns;\t\t/* Number of stored patterns (?) */\n\tuint8 songlen;\t\t/* Song length in patterns */\n\tuint8 restart;\t\t/* Restart pos */\n\tuint16 patsize[256];\t/* Size of each pattern in bytes */\n};\n\nstruct far_instrument {\n\tuint8 name[32];\t\t/* Instrument name */\n\tuint32 length;\t\t/* Length of sample (up to 64Kb) */\n\tuint8 finetune;\t\t/* Finetune (unsupported) */\n\tuint8 volume;\t\t/* Volume (unsupported?) */\n\tuint32 loop_start;\t/* Loop start */\n\tuint32 loopend;\t\t/* Loop end */\n\tuint8 sampletype;\t/* 1=16 bit sample */\n\tuint8 loopmode;\n};\n\nstruct far_event {\n\tuint8 note;\n\tuint8 instrument;\n\tuint8 volume;\t\t/* In reverse nibble order? */\n\tuint8 effect;\n};\n\n\n#define MAGIC_FAR\tMAGIC4('F','A','R',0xfe)\n\n\nstatic int far_test (HIO_HANDLE *, char *, const int);\nstatic int far_load (struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_far = {\n    \"Farandole Composer\",\n    far_test,\n    far_load\n};\n\nstatic int far_test(HIO_HANDLE *f, char *t, const int start)\n{\n    if (hio_read32b(f) != MAGIC_FAR)\n\treturn -1;\n\n    libxmp_read_title(f, t, 40);\n\n    return 0;\n}\n\n\nstatic void far_translate_effect(struct xmp_event *event, int fx, int param, int vol)\n{\n\tswitch (fx) {\n\tcase 0x0:\t\t/* 0x0?  Global funct */\n\t\tswitch (param) {\n\t\tcase 0x1:\t/* 0x01  Ramp delay on */\n\t\tcase 0x2:\t/* 0x02  Ramp delay off */\n\t\t\t/* These control volume ramping and can be ignored. */\n\t\t\tbreak;\n\t\tcase 0x3:\t/* 0x03  Fulfill loop */\n\t\t\t/* This is intended to be sustain release, but the\n\t\t\t * effect is buggy and just cuts most of the time. */\n\t\t\tevent->fxt = FX_KEYOFF;\n\t\t\tbreak;\n\t\tcase 0x4:\t/* 0x04  Old FAR tempo */\n\t\t\tevent->fxt = FX_FAR_TEMPO;\n\t\t\tevent->fxp = 0x10;\n\t\t\tbreak;\n\t\tcase 0x5:\t/* 0x05  New FAR tempo */\n\t\t\tevent->fxt = FX_FAR_TEMPO;\n\t\t\tevent->fxp = 0x20;\n\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\tcase 0x1:\t\t/* 0x1?  Pitch offset up */\n\t\tevent->fxt = FX_FAR_PORTA_UP;\n\t\tevent->fxp = param;\n\t\tbreak;\n\tcase 0x2:\t\t/* 0x2?  Pitch offset down */\n\t\tevent->fxt = FX_FAR_PORTA_DN;\n\t\tevent->fxp = param;\n\t\tbreak;\n\tcase 0x3:\t\t/* 0x3?  Note-port */\n\t\tevent->fxt = FX_FAR_TPORTA;\n\t\tevent->fxp = param;\n\t\tbreak;\n\tcase 0x4:\t\t\t/* 0x4?  Retrigger */\n\t\tevent->fxt = FX_FAR_RETRIG;\n\t\tevent->fxp = param;\n\t\tbreak;\n\tcase 0x5:\t\t\t/* 0x5?  Set Vibrato depth */\n\t\tevent->fxt = FX_FAR_VIBDEPTH;\n\t\tevent->fxp = param;\n\t\tbreak;\n\tcase 0x6:\t\t\t/* 0x6?  Vibrato note */\n\t\tevent->fxt = FX_FAR_VIBRATO;\n\t\tevent->fxp = param;\n\t\tbreak;\n\tcase 0x7:\t\t\t/* 0x7?  Vol Sld Up */\n\t\tevent->fxt = FX_F_VSLIDE_UP;\n\t\tevent->fxp = (param << 4);\n\t\tbreak;\n\tcase 0x8:\t\t\t/* 0x8?  Vol Sld Dn */\n\t\tevent->fxt = FX_F_VSLIDE_DN;\n\t\tevent->fxp = (param << 4);\n\t\tbreak;\n\tcase 0x9:\t\t\t/* 0x9?  Sustained vibrato */\n\t\tevent->fxt = FX_FAR_VIBRATO;\n\t\tevent->fxp = 0x10 /* Vibrato sustain flag */ | param;\n\t\tbreak;\n\tcase 0xa:\t\t\t/* 0xa?  Slide-to-vol */\n\t\tif (vol >= 0x01 && vol <= 0x10) {\n\t\t\tevent->fxt = FX_FAR_SLIDEVOL;\n\t\t\tevent->fxp = ((vol - 1) << 4) | param;\n\t\t\tevent->vol = 0;\n\t\t}\n\t\tbreak;\n\tcase 0xb:\t\t\t/* 0xb?  Balance */\n\t\tevent->fxt = FX_SETPAN;\n\t\tevent->fxp = (param << 4) | param;\n\t\tbreak;\n\tcase 0xc:\t\t\t/* 0xc?  Note Offset */\n\t\tevent->fxt = FX_FAR_DELAY;\n\t\tevent->fxp = param;\n\t\tbreak;\n\tcase 0xd:\t\t\t/* 0xd?  Fine tempo down */\n\t\tevent->fxt = FX_FAR_F_TEMPO;\n\t\tevent->fxp = param;\n\t\tbreak;\n\tcase 0xe:\t\t\t/* 0xe?  Fine tempo up */\n\t\tevent->fxt = FX_FAR_F_TEMPO;\n\t\tevent->fxp = param << 4;\n\t\tbreak;\n\tcase 0xf:\t\t\t/* 0xf?  Set tempo */\n\t\tevent->fxt = FX_FAR_TEMPO;\n\t\tevent->fxp = param;\n\t\tbreak;\n\t}\n}\n\n#define COMMENT_MAXLINES 44\n\nstatic void far_read_text(char *dest, size_t textlen, HIO_HANDLE *f)\n{\n\t/* FAR module text uses 132-char lines with no line breaks... */\n\tsize_t end, lastchar, i;\n\n\tif (textlen > COMMENT_MAXLINES * 132)\n\t\ttextlen = COMMENT_MAXLINES * 132;\n\n\twhile (textlen) {\n\t\tend = MIN(textlen, 132);\n\t\ttextlen -= end;\n\t\tend = hio_read(dest, 1, end, f);\n\n\t\tlastchar = 0;\n\t\tfor (i = 0; i < end; i++) {\n\t\t\t/* Nulls in the text area are equivalent to spaces. */\n\t\t\tif (dest[i] == '\\0')\n\t\t\t\tdest[i] = ' ';\n\t\t\telse if (dest[i] != ' ')\n\t\t\t\tlastchar = i;\n\t\t}\n\t\tdest += lastchar + 1;\n\t\t*dest++ = '\\n';\n\t}\n\t*dest = '\\0';\n}\n\nstatic int far_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n    struct xmp_module *mod = &m->mod;\n    struct far_module_extras *me;\n    int i, j, k;\n    struct xmp_event *event;\n    struct far_header ffh;\n    struct far_header2 ffh2;\n    struct far_instrument fih;\n    uint8 *patbuf = NULL;\n    uint8 sample_map[8];\n\n    LOAD_INIT();\n\n    hio_read32b(f);\t\t\t/* File magic: 'FAR\\xfe' */\n    hio_read(ffh.name, 40, 1, f);\t/* Song name */\n    hio_read(ffh.crlf, 3, 1, f);\t/* 0x0d 0x0a 0x1A */\n    ffh.headersize = hio_read16l(f);\t/* Remaining header size in bytes */\n    ffh.version = hio_read8(f);\t\t/* Version MSN=major, LSN=minor */\n    hio_read(ffh.ch_on, 16, 1, f);\t/* Channel on/off switches */\n    hio_seek(f, 9, SEEK_CUR);\t\t/* Current editing values */\n    ffh.tempo = hio_read8(f);\t\t/* Default tempo */\n    hio_read(ffh.pan, 16, 1, f);\t/* Channel pan definitions */\n    hio_read32l(f);\t\t\t/* Grid, mode (for editor) */\n    ffh.textlen = hio_read16l(f);\t/* Length of embedded text */\n\n    /* Sanity check */\n    if (ffh.tempo >= 16) {\n\treturn -1;\n    }\n\n    if ((m->comment = (char *)malloc(ffh.textlen + COMMENT_MAXLINES + 1)) != NULL) {\n\tfar_read_text(m->comment, ffh.textlen, f);\n    } else {\n\thio_seek(f, ffh.textlen, SEEK_CUR);\t/* Skip song text */\n    }\n\n    hio_read(ffh2.order, 256, 1, f);\t/* Orders */\n    ffh2.patterns = hio_read8(f);\t/* Number of stored patterns (?) */\n    ffh2.songlen = hio_read8(f);\t/* Song length in patterns */\n    ffh2.restart = hio_read8(f);\t/* Restart pos */\n    for (i = 0; i < 256; i++) {\n\tffh2.patsize[i] = hio_read16l(f); /* Size of each pattern in bytes */\n    }\n\n    if (hio_error(f)) {\n        return -1;\n    }\n\n    /* Skip unsupported header extension if it exists. The documentation claims\n     * this field is the \"remaining\" header size, but it's the total size. */\n    if (ffh.headersize > 869 + ffh.textlen) {\n\tif (hio_seek(f, ffh.headersize, SEEK_SET))\n\t    return -1;\n    }\n\n    mod->chn = 16;\n    /*mod->pat=ffh2.patterns; (Error in specs? --claudio) */\n    mod->len = ffh2.songlen;\n    mod->rst = ffh2.restart;\n    memcpy (mod->xxo, ffh2.order, mod->len);\n\n    for (mod->pat = i = 0; i < 256; i++) {\n\tif (ffh2.patsize[i])\n\t    mod->pat = i + 1;\n    }\n    /* Make sure referenced zero-sized patterns are also counted. */\n    for (i = 0; i < mod->len; i++) {\n\tif (mod->pat <= mod->xxo[i])\n\t    mod->pat = mod->xxo[i] + 1;\n    }\n\n    mod->trk = mod->chn * mod->pat;\n\n    if (libxmp_far_new_module_extras(m) != 0)\n\treturn -1;\n\n    me = FAR_MODULE_EXTRAS(*m);\n    me->coarse_tempo = ffh.tempo;\n    me->fine_tempo = 0;\n    me->tempo_mode = 1;\n    m->time_factor = FAR_TIME_FACTOR;\n    libxmp_far_translate_tempo(1, 0, me->coarse_tempo, &me->fine_tempo, &mod->spd, &mod->bpm);\n\n    m->period_type = PERIOD_CSPD;\n    m->c4rate = C4_NTSC_RATE;\n\n    m->quirk |= QUIRK_VSALL | QUIRK_PBALL | QUIRK_VIBALL;\n\n    strncpy(mod->name, (char *)ffh.name, 40);\n    libxmp_set_type(m, \"Farandole Composer %d.%d\", MSN(ffh.version), LSN(ffh.version));\n\n    MODULE_INFO();\n\n    if (libxmp_init_pattern(mod) < 0)\n\treturn -1;\n\n    /* Read and convert patterns */\n    D_(D_INFO \"Comment bytes  : %d\", ffh.textlen);\n    D_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n    if ((patbuf = (uint8 *)malloc(256 * 16 * 4)) == NULL)\n\treturn -1;\n\n    for (i = 0; i < mod->pat; i++) {\n\tuint8 brk, note, ins, vol, fxb;\n\tuint8 *pos;\n\tint rows;\n\n\tif (libxmp_alloc_pattern(mod, i) < 0)\n\t    goto err;\n\n\tif (!ffh2.patsize[i])\n\t    continue;\n\n\trows = (ffh2.patsize[i] - 2) / 64;\n\n\t/* Sanity check */\n\tif (rows <= 0 || rows > 256) {\n\t    goto err;\n\t}\n\n\tmod->xxp[i]->rows = rows;\n\n\tif (libxmp_alloc_tracks_in_pattern(mod, i) < 0)\n\t    goto err;\n\n\tbrk = hio_read8(f) + 1;\n\thio_read8(f);\n\n\tif (hio_read(patbuf, rows * 64, 1, f) < 1) {\n\t    D_(D_CRIT \"read error at pat %d\", i);\n\t    goto err;\n\t}\n\n\tpos = patbuf;\n\tfor (j = 0; j < mod->xxp[i]->rows; j++) {\n\t    for (k = 0; k < mod->chn; k++) {\n\t\tevent = &EVENT(i, k, j);\n\n\t\tif (k == 0 && j == brk)\n\t\t    event->f2t = FX_BREAK;\n\n\t\tnote = *pos++;\n\t\tins  = *pos++;\n\t\tvol  = *pos++;\n\t\tfxb  = *pos++;\n\n\t\tif (note)\n\t\t    event->note = note + 48;\n\t\tif (event->note || ins)\n\t\t    event->ins = ins + 1;\n\n\t\tif (vol >= 0x01 && vol <= 0x10)\n\t\t    event->vol = (vol - 1) * 16 + 1;\n\n\t\tfar_translate_effect(event, MSN(fxb), LSN(fxb), vol);\n\t    }\n\t}\n    }\n    free(patbuf);\n\n    /* Allocate tracks for any patterns referenced with a size of 0. These\n     * use the configured pattern break position, which is 64 by default. */\n    for (i = 0; i < mod->len; i++) {\n\tint pat = mod->xxo[i];\n\tif (mod->xxp[pat]->rows == 0) {\n\t    mod->xxp[pat]->rows = 64;\n\t    if (libxmp_alloc_tracks_in_pattern(mod, pat) < 0)\n\t\treturn -1;\n\t}\n    }\n\n    mod->ins = -1;\n    if (hio_read(sample_map, 1, 8, f) < 8) {\n\tD_(D_CRIT \"read error at sample map\");\n\treturn -1;\n    }\n    for (i = 0; i < 64; i++) {\n\tif (sample_map[i / 8] & (1 << (i % 8)))\n\t\tmod->ins = i;\n    }\n    mod->ins++;\n\n    mod->smp = mod->ins;\n\n    if (libxmp_init_instrument(m) < 0)\n\treturn -1;\n\n    /* Read and convert instruments and samples */\n\n    for (i = 0; i < mod->ins; i++) {\n\tif (!(sample_map[i / 8] & (1 << (i % 8))))\n\t\tcontinue;\n\n\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t    return -1;\n\n\thio_read(fih.name, 32, 1, f);\t/* Instrument name */\n\tfih.length = hio_read32l(f);\t/* Length of sample (up to 64Kb) */\n\tfih.finetune = hio_read8(f);\t/* Finetune (unsupported) */\n\tfih.volume = hio_read8(f);\t/* Volume (unsupported?) */\n\tfih.loop_start = hio_read32l(f);/* Loop start */\n\tfih.loopend = hio_read32l(f);\t/* Loop end */\n\tfih.sampletype = hio_read8(f);\t/* 1=16 bit sample */\n\tfih.loopmode = hio_read8(f);\n\n\t/* Sanity check */\n\tif (fih.length > 0x10000 || fih.loop_start > 0x10000 ||\n            fih.loopend > 0x10000) {\n\t\treturn -1;\n\t}\n\n\tmod->xxs[i].len = fih.length;\n\tmod->xxs[i].lps = fih.loop_start;\n\tmod->xxs[i].lpe = fih.loopend;\n\tmod->xxs[i].flg = 0;\n\n\tif (mod->xxs[i].len > 0)\n\t\tmod->xxi[i].nsm = 1;\n\n\tif (fih.sampletype != 0) {\n\t\tmod->xxs[i].flg |= XMP_SAMPLE_16BIT;\n\t\tmod->xxs[i].len >>= 1;\n\t\tmod->xxs[i].lps >>= 1;\n\t\tmod->xxs[i].lpe >>= 1;\n\t}\n\n\tmod->xxs[i].flg |= fih.loopmode ? XMP_SAMPLE_LOOP : 0;\n\tmod->xxi[i].sub[0].vol = 0xff; /* fih.volume; */\n\tmod->xxi[i].sub[0].sid = i;\n\n\tlibxmp_instrument_name(mod, i, fih.name, 32);\n\n\tD_(D_INFO \"[%2X] %-32.32s %04x %04x %04x %c V%02x\",\n\t\ti, mod->xxi[i].name, mod->xxs[i].len, mod->xxs[i].lps,\n\t\tmod->xxs[i].lpe, fih.loopmode ? 'L' : ' ', mod->xxi[i].sub[0].vol);\n\n\tif (libxmp_load_sample(m, f, 0, &mod->xxs[i], NULL) < 0)\n\t\treturn -1;\n    }\n\n    /* Panning map */\n    for (i = 0; i < 16; i++) {\n\tif (ffh.ch_on[i] == 0)\n\t    mod->xxc[i].flg |= XMP_CHANNEL_MUTE;\n\tif (ffh.pan[i] < 0x10)\n\t    mod->xxc[i].pan = (ffh.pan[i] << 4) | ffh.pan[i];\n    }\n\n    m->volbase = 0xf0;\n\n    return 0;\n\n  err:\n    free(patbuf);\n    return -1;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/flt_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"loader.h\"\n#include \"mod.h\"\n#include \"../period.h\"\n#include \"../rng.h\"\n\nstatic int flt_test(HIO_HANDLE *, char *, const int);\nstatic int flt_load(struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_flt = {\n\t\"Startrekker\",\n\tflt_test,\n\tflt_load\n};\n\nstatic int flt_test(HIO_HANDLE * f, char *t, const int start)\n{\n\tchar buf[4];\n\n\thio_seek(f, start + 1080, SEEK_SET);\n\tif (hio_read(buf, 1, 4, f) < 4)\n\t\treturn -1;\n\n\t/* Also RASP? */\n\tif (memcmp(buf, \"FLT\", 3) && memcmp(buf, \"EXO\", 3))\n\t\treturn -1;\n\n\tif (buf[3] != '4' && buf[3] != '8' && buf[3] != 'M')\n\t\treturn -1;\n\n\thio_seek(f, start + 0, SEEK_SET);\n\tlibxmp_read_title(f, t, 20);\n\n\treturn 0;\n}\n\n/* Waveforms from the Startrekker 1.2 AM synth replayer code */\n\nstatic const int8 am_waveform[3][32] = {\n\t{    0,   25,   49,   71,   90,  106,  117,  125,\t/* Sine */\n\t   127,  125,  117,  106,   90,   71,   49,   25,\n\t     0,  -25,  -49,  -71,  -90, -106, -117, -125,\n\t  -127, -125, -117, -106,  -90,  -71,  -49,  -25\n\t},\n\n\t{ -128, -120, -112, -104,  -96,  -88,  -80,  -72,\t/* Ramp */\n\t   -64,  -56,  -48,  -40,  -32,  -24,  -16,   -8,\n\t     0,    8,   16,   24,   32,   40,   48,   56,\n\t    64,   72,   80,   88,   96,  104,  112,  120\n\t},\n\n\t{ -128, -128, -128, -128, -128, -128, -128, -128,\t/* Square */\n\t  -128, -128, -128, -128, -128, -128, -128, -128,\n\t   127,  127,  127,  127,  127,  127,  127,  127,\n\t   127,  127,  127,  127,  127,  127,  127,  127\n\t}\n};\n\nstruct am_instrument {\n\tint16 l0;\t\t/* start amplitude */\n\tint16 a1l;\t\t/* attack level */\n\tint16 a1s;\t\t/* attack speed */\n\tint16 a2l;\t\t/* secondary attack level */\n\tint16 a2s;\t\t/* secondary attack speed */\n\tint16 sl;\t\t/* sustain level */\n\tint16 ds;\t\t/* decay speed */\n\tint16 st;\t\t/* sustain time */\n\tint16 rs;\t\t/* release speed */\n\tint16 wf;\t\t/* waveform */\n\tint16 p_fall;\t\t/* ? */\n\tint16 v_amp;\t\t/* vibrato amplitude */\n\tint16 v_spd;\t\t/* vibrato speed */\n\tint16 fq;\t\t/* base frequency */\n};\n\nstatic int is_am_instrument(HIO_HANDLE *nt, int i)\n{\n\tchar buf[2];\n\tint16 wf;\n\n\thio_seek(nt, 144 + i * 120, SEEK_SET);\n\thio_read(buf, 1, 2, nt);\n\tif (memcmp(buf, \"AM\", 2))\n\t\treturn 0;\n\thio_seek(nt, 24, SEEK_CUR);\n\twf = hio_read16b(nt);\n\tif (hio_error(nt) || wf < 0 || wf > 3)\n\t\treturn 0;\n\n\treturn 1;\n}\n\nstatic int read_am_instrument(struct module_data *m, HIO_HANDLE *nt, int i)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\tstruct xmp_sample *xxs = &mod->xxs[i];\n\tstruct xmp_envelope *vol_env = &xxi->aei;\n\tstruct xmp_envelope *freq_env = &xxi->fei;\n\tstruct am_instrument am;\n\tstruct rng_state rng;\n\tconst int8 *wave;\n\tint a, b;\n\tint8 am_noise[1024];\n\n\thio_seek(nt, 144 + i * 120 + 2 + 4, SEEK_SET);\n\tam.l0  = hio_read16b(nt);\n\tam.a1l = hio_read16b(nt);\n\tam.a1s = hio_read16b(nt);\n\tam.a2l = hio_read16b(nt);\n\tam.a2s = hio_read16b(nt);\n\tam.sl  = hio_read16b(nt);\n\tam.ds  = hio_read16b(nt);\n\tam.st  = hio_read16b(nt);\n\thio_read16b(nt);\n\tam.rs  = hio_read16b(nt);\n\tam.wf  = hio_read16b(nt);\n\tam.p_fall = -(int16) hio_read16b(nt);\n\tam.v_amp = hio_read16b(nt);\n\tam.v_spd = hio_read16b(nt);\n\tam.fq  = hio_read16b(nt);\n\n\tif (hio_error(nt)) {\n\t\treturn -1;\n\t}\n\n#if 0\n\tprintf\n\t    (\"L0=%d A1L=%d A1S=%d A2L=%d A2S=%d SL=%d DS=%d ST=%d RS=%d WF=%d\\n\",\n\t     am.l0, am.a1l, am.a1s, am.a2l, am.a2s, am.sl, am.ds, am.st, am.rs,\n\t     am.wf);\n#endif\n\n\tif (am.wf < 3) {\n\t\txxs->len = 32;\n\t\txxs->lps = 0;\n\t\txxs->lpe = 32;\n\t\twave = &am_waveform[am.wf][0];\n\t} else {\n\t\tint j;\n\n\t\txxs->len = 1024;\n\t\txxs->lps = 0;\n\t\txxs->lpe = 1024;\n\n\t\tlibxmp_init_random(&rng);\n\t\tfor (j = 0; j < 1024; j++)\n\t\t\tam_noise[j] = libxmp_get_random(&rng, 256);\n\n\t\twave = &am_noise[0];\n\t}\n\n\txxs->flg = XMP_SAMPLE_LOOP;\n\txxi->sub[0].vol = 0x40;\t/* prelude.mod has 0 in instrument */\n\txxi->nsm = 1;\n\txxi->sub[0].xpo = -12 * am.fq;\n\txxi->sub[0].vwf = 0;\n\txxi->sub[0].vde = am.v_amp << 2;\n\txxi->sub[0].vra = am.v_spd;\n\n\t/*\n\t * AM synth envelope parameters based on the Startrekker 1.2 docs\n\t *\n\t * L0    Start amplitude for the envelope\n\t * A1L   Attack level\n\t * A1S   The speed that the amplitude changes to the attack level, $1\n\t *       is slow and $40 is fast.\n\t * A2L   Secondary attack level, for those who likes envelopes...\n\t * A2S   Secondary attack speed.\n\t * DS    The speed that the amplitude decays down to the:\n\t * SL    Sustain level. There is remains for the time set by the\n\t * ST    Sustain time.\n\t * RS    Release speed. The speed that the amplitude falls from ST to 0.\n\t */\n\tif (am.a1s == 0)\n\t\tam.a1s = 1;\n\tif (am.a2s == 0)\n\t\tam.a2s = 1;\n\tif (am.ds == 0)\n\t\tam.ds = 1;\n\tif (am.rs == 0)\n\t\tam.rs = 1;\n\n\tvol_env->npt = 6;\n\tvol_env->flg = XMP_ENVELOPE_ON;\n\n\tvol_env->data[0] = 0;\n\tvol_env->data[1] = am.l0 / 4;\n\n\t/*\n\t * Startrekker increments/decrements the envelope by the stage speed\n\t * until it reaches the next stage level.\n\t *\n\t *         ^\n\t *         |\n\t *     100 +.........o\n\t *         |        /:\n\t *     A2L +.......o :        x = 256 * (A2L - A1L) / (256 - A1L)\n\t *         |      /: :\n\t *         |     / : :\n\t *     A1L +....o..:.:\n\t *         |    :  : :\n\t *         |    :x : :\n\t *         +----+--+-+----->\n\t *              |    |\n\t *              |256/|\n\t *               A2S\n\t */\n\n\tif (am.a1l > am.l0) {\n\t\ta = am.a1l - am.l0;\n\t\tb = 256 - am.l0;\n\t} else {\n\t\ta = am.l0 - am.a1l;\n\t\tb = am.l0;\n\t}\n\tif (b == 0)\n\t\tb = 1;\n\n\tvol_env->data[2] = vol_env->data[0] + (256 * a) / (am.a1s * b);\n\tvol_env->data[3] = am.a1l / 4;\n\n\tif (am.a2l > am.a1l) {\n\t\ta = am.a2l - am.a1l;\n\t\tb = 256 - am.a1l;\n\t} else {\n\t\ta = am.a1l - am.a2l;\n\t\tb = am.a1l;\n\t}\n\tif (b == 0)\n\t\tb = 1;\n\n\tvol_env->data[4] = vol_env->data[2] + (256 * a) / (am.a2s * b);\n\tvol_env->data[5] = am.a2l / 4;\n\n\tif (am.sl > am.a2l) {\n\t\ta = am.sl - am.a2l;\n\t\tb = 256 - am.a2l;\n\t} else {\n\t\ta = am.a2l - am.sl;\n\t\tb = am.a2l;\n\t}\n\tif (b == 0)\n\t\tb = 1;\n\n\tvol_env->data[6] = vol_env->data[4] + (256 * a) / (am.ds * b);\n\tvol_env->data[7] = am.sl / 4;\n\tvol_env->data[8] = vol_env->data[6] + am.st;\n\tvol_env->data[9] = am.sl / 4;\n\tvol_env->data[10] = vol_env->data[8] + (256 / am.rs);\n\tvol_env->data[11] = 0;\n\n\t/*\n\t * Implement P.FALL using pitch envelope\n\t */\n\n\tif (am.p_fall) {\n\t\tfreq_env->npt = 2;\n\t\tfreq_env->flg = XMP_ENVELOPE_ON;\n\t\tfreq_env->data[0] = 0;\n\t\tfreq_env->data[1] = 0;\n\t\tfreq_env->data[2] = 1024 / abs(am.p_fall);\n\t\tfreq_env->data[3] = 10 * (am.p_fall < 0 ? -256 : 256);\n\t}\n\n\tif (libxmp_load_sample(m, NULL, SAMPLE_FLAG_NOLOAD, xxs, wave))\n\t\treturn -1;\n\n\treturn 0;\n}\n\nstatic int flt_load(struct module_data *m, HIO_HANDLE * f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j;\n\tstruct xmp_event *event;\n\tstruct mod_header mh;\n\tuint8 mod_event[4];\n\tconst char *tracker;\n\tchar filename[1024];\n\tchar buf[16];\n\tHIO_HANDLE *nt;\n\tint am_synth;\n\n\tLOAD_INIT();\n\n\t/* See if we have the synth parameters file */\n\tam_synth = 0;\n\tsnprintf(filename, 1024, \"%s%s.NT\", m->dirname, m->basename);\n\tif ((nt = hio_open(filename, \"rb\")) == NULL) {\n\t\tsnprintf(filename, 1024, \"%s%s.nt\", m->dirname, m->basename);\n\t\tif ((nt = hio_open(filename, \"rb\")) == NULL) {\n\t\t\tsnprintf(filename, 1024, \"%s%s.AS\", m->dirname,\n\t\t\t\t m->basename);\n\t\t\tif ((nt = hio_open(filename, \"rb\")) == NULL) {\n\t\t\t\tsnprintf(filename, 1024, \"%s%s.as\", m->dirname,\n\t\t\t\t\t m->basename);\n\t\t\t\tnt = hio_open(filename, \"rb\");\n\t\t\t}\n\t\t}\n\t}\n\n\ttracker = \"Startrekker\";\n\n\tif (nt) {\n\t\tif (hio_read(buf, 1, 16, nt) != 16) {\n\t\t\tgoto err;\n\t\t}\n\t\tif (memcmp(buf, \"ST1.2 ModuleINFO\", 16) == 0) {\n\t\t\tam_synth = 1;\n\t\t\ttracker = \"Startrekker 1.2\";\n\t\t} else if (memcmp(buf, \"ST1.3 ModuleINFO\", 16) == 0) {\n\t\t\tam_synth = 1;\n\t\t\ttracker = \"Startrekker 1.3\";\n\t\t} else if (memcmp(buf, \"AudioSculpture10\", 16) == 0) {\n\t\t\tam_synth = 1;\n\t\t\ttracker = \"AudioSculpture 1.0\";\n\t\t}\n\t}\n\n\thio_read(mh.name, 20, 1, f);\n\tfor (i = 0; i < 31; i++) {\n\t\thio_read(mh.ins[i].name, 22, 1, f);\n\t\tmh.ins[i].size = hio_read16b(f);\n\t\tmh.ins[i].finetune = hio_read8(f);\n\t\tmh.ins[i].volume = hio_read8(f);\n\t\tmh.ins[i].loop_start = hio_read16b(f);\n\t\tmh.ins[i].loop_size = hio_read16b(f);\n\t}\n\tmh.len = hio_read8(f);\n\tmh.restart = hio_read8(f);\n\thio_read(mh.order, 128, 1, f);\n\thio_read(mh.magic, 4, 1, f);\n\n\tif (mh.magic[3] == '4') {\n\t\tmod->chn = 4;\n\t} else {\n\t\tmod->chn = 8;\n\t}\n\n\tmod->ins = 31;\n\tmod->smp = mod->ins;\n\tmod->len = mh.len;\n\tmod->rst = mh.restart;\n\tmemcpy(mod->xxo, mh.order, 128);\n\n\tfor (i = 0; i < 128; i++) {\n\t\tif (mod->chn > 4)\n\t\t\tmod->xxo[i] >>= 1;\n\t\tif (mod->xxo[i] > mod->pat)\n\t\t\tmod->pat = mod->xxo[i];\n\t}\n\n\tmod->pat++;\n\n\tmod->trk = mod->chn * mod->pat;\n\n\tstrncpy(mod->name, (char *)mh.name, 20);\n\tlibxmp_set_type(m, \"%s %4.4s\", tracker, mh.magic);\n\tMODULE_INFO();\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\tgoto err;\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\t\tstruct xmp_sample *xxs = &mod->xxs[i];\n\t\tstruct xmp_subinstrument *sub;\n\n\t\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t\t\tgoto err;\n\n\t\tsub = &xxi->sub[0];\n\n\t\txxs->len = 2 * mh.ins[i].size;\n\t\txxs->lps = 2 * mh.ins[i].loop_start;\n\t\txxs->lpe = xxs->lps + 2 * mh.ins[i].loop_size;\n\t\txxs->flg = mh.ins[i].loop_size > 1 ? XMP_SAMPLE_LOOP : 0;\n\t\tsub->fin = (int8) (mh.ins[i].finetune << 4);\n\t\tsub->vol = mh.ins[i].volume;\n\t\tsub->pan = 0x80;\n\t\tsub->sid = i;\n\t\txxi->rls = 0xfff;\n\n\t\tif (xxs->len > 0)\n\t\t\txxi->nsm = 1;\n\n\t\tlibxmp_instrument_name(mod, i, mh.ins[i].name, 22);\n\t}\n\n\tif (libxmp_init_pattern(mod) < 0)\n\t\tgoto err;\n\n\t/* Load and convert patterns */\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\t/* \"The format you are looking for is FLT8, and the ONLY two\n\t *  differences are: It says FLT8 instead of FLT4 or M.K., AND, the\n\t *  patterns are PAIRED. I thought this was the easiest 8 track\n\t *  format possible, since it can be loaded in a normal 4 channel\n\t *  tracker if you should want to rip sounds or patterns. So, in a\n\t *  8 track FLT8 module, patterns 00 and 01 is \"really\" pattern 00.\n\t *  Patterns 02 and 03 together is \"really\" pattern 01. That's it.\n\t *  Oh well, I didn't have the time to implement all effect commands\n\t *  either, so some FLT8 modules would play back badly (I think\n\t *  especially the portamento command uses a different \"scale\" than\n\t *  the normal portamento command, that would be hard to patch).\n\t */\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tif (libxmp_alloc_pattern_tracks(mod, i, 64) < 0)\n\t\t\tgoto err;\n\n\t\tfor (j = 0; j < (64 * 4); j++) {\n\t\t\tevent = &EVENT(i, j % 4, j / 4);\n\t\t\tif (hio_read(mod_event, 1, 4, f) < 4) {\n\t\t\t\tD_(D_CRIT \"read error at pat %d\", i);\n\t\t\t\tgoto err;\n\t\t\t}\n\t\t\tlibxmp_decode_noisetracker_event(event, mod_event);\n\t\t}\n\t\tif (mod->chn > 4) {\n\t\t\tfor (j = 0; j < (64 * 4); j++) {\n\t\t\t\tevent = &EVENT(i, (j % 4) + 4, j / 4);\n\t\t\t\tif (hio_read(mod_event, 1, 4, f) < 4) {\n\t\t\t\t\tD_(D_CRIT \"read error at pat %d\", i);\n\t\t\t\t\tgoto err;\n\t\t\t\t}\n\t\t\t\tlibxmp_decode_noisetracker_event(event, mod_event);\n\n\t\t\t\t/* no macros */\n\t\t\t\tif (event->fxt == 0x0e)\n\t\t\t\t\tevent->fxt = event->fxp = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* no such limit for synth instruments\n\t * mod->flg |= XXM_FLG_MODRNG;\n\t */\n\n\t/* Load samples */\n\n\tD_(D_INFO \"Stored samples: %d\", mod->smp);\n\n\tfor (i = 0; i < mod->smp; i++) {\n\t\tif (mod->xxs[i].len == 0) {\n\t\t\tif (am_synth && is_am_instrument(nt, i)) {\n\t\t\t\tif (read_am_instrument(m, nt, i) < 0) {\n\t\t\t\t\tD_(D_CRIT \"Missing nt file\");\n\t\t\t\t\tgoto err;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (libxmp_load_sample(m, f, SAMPLE_FLAG_FULLREP, &mod->xxs[i], NULL) <\n\t\t    0) {\n\t\t\tgoto err;\n\t\t}\n\t}\n\n\tif (nt) {\n\t\thio_close(nt);\n\t}\n\n\treturn 0;\n\n      err:\n\tif (nt) {\n\t\thio_close(nt);\n\t}\n\n\treturn -1;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/gdm_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/*\n * Based on the GDM (General Digital Music) version 1.0 File Format\n * Specification - Revision 2 by MenTaLguY\n */\n\n#include \"loader.h\"\n#include \"../period.h\"\n\n#define MAGIC_GDM\tMAGIC4('G','D','M',0xfe)\n#define MAGIC_GMFS\tMAGIC4('G','M','F','S')\n\n\nstatic int gdm_test(HIO_HANDLE *, char *, const int);\nstatic int gdm_load (struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_gdm = {\n\t\"General Digital Music\",\n\tgdm_test,\n\tgdm_load\n};\n\nstatic int gdm_test(HIO_HANDLE *f, char *t, const int start)\n{\n\tif (hio_read32b(f) != MAGIC_GDM)\n\t\treturn -1;\n\n\thio_seek(f, start + 0x47, SEEK_SET);\n\tif (hio_read32b(f) != MAGIC_GMFS)\n\t\treturn -1;\n\n\thio_seek(f, start + 4, SEEK_SET);\n\tlibxmp_read_title(f, t, 32);\n\n\treturn 0;\n}\n\n\n\nvoid fix_effect(uint8 *fxt, uint8 *fxp)\n{\n\tint h, l;\n\tswitch (*fxt) {\n\tcase 0x00:\t\t\t/* no effect */\n\t\t*fxp = 0;\n\t\tbreak;\n\tcase 0x01:\n\tcase 0x02:\n\tcase 0x03:\n\tcase 0x04:\n\tcase 0x05:\n\tcase 0x06:\n\tcase 0x07:\t\t\t/* same as protracker */\n\t\tbreak;\n\tcase 0x08:\n\t\t*fxt = FX_TREMOR;\n\t\tbreak;\n\tcase 0x09:\n\tcase 0x0a:\n\tcase 0x0b:\n\tcase 0x0c:\n\tcase 0x0d:\t\t\t/* same as protracker */\n\t\tbreak;\n\tcase 0x0e:\n\t\t/* Convert some extended effects to their S3M equivalents. This is\n\t\t * necessary because the continue effects were left as the original\n\t\t * effect (e.g. FX_VOLSLIDE for the fine volume slides) by 2GDM!\n\t\t * Otherwise, these should be the same as protracker.\n\t\t */\n\t\th = MSN(*fxp);\n\t\tl = LSN(*fxp);\n\t\tswitch(h) {\n\t\t\tcase EX_F_PORTA_UP:\n\t\t\t\t*fxt = FX_PORTA_UP;\n\t\t\t\t*fxp = l | 0xF0;\n\t\t\t\tbreak;\n\t\t\tcase EX_F_PORTA_DN:\n\t\t\t\t*fxt = FX_PORTA_DN;\n\t\t\t\t*fxp = l | 0xF0;\n\t\t\t\tbreak;\n\t\t\tcase 0x8:\t/* extra fine portamento up */\n\t\t\t\t*fxt = FX_PORTA_UP;\n\t\t\t\t*fxp = l | 0xE0;\n\t\t\t\tbreak;\n\t\t\tcase 0x9:\t/* extra fine portamento down */\n\t\t\t\t*fxt = FX_PORTA_DN;\n\t\t\t\t*fxp = l | 0xE0;\n\t\t\t\tbreak;\n\t\t\tcase EX_F_VSLIDE_UP:\n\t\t\t\t/* Don't convert 0 as it would turn into volume slide down... */\n\t\t\t\tif (l) {\n\t\t\t\t\t*fxt = FX_VOLSLIDE;\n\t\t\t\t\t*fxp = (l << 4) | 0xF;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase EX_F_VSLIDE_DN:\n\t\t\t\t/* Don't convert 0 as it would turn into volume slide up... */\n\t\t\t\tif (l) {\n\t\t\t\t\t*fxt = FX_VOLSLIDE;\n\t\t\t\t\t*fxp = l | 0xF0;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\tcase 0x0f:\t\t\t/* set speed */\n\t\t*fxt = FX_S3M_SPEED;\n\t\tbreak;\n\tcase 0x10:\t\t\t/* arpeggio */\n\t\t*fxt = FX_S3M_ARPEGGIO;\n\t\tbreak;\n\tcase 0x11:\t\t\t/* set internal flag */\n\t\t*fxt = *fxp = 0;\n\t\tbreak;\n\tcase 0x12:\n\t\t*fxt = FX_MULTI_RETRIG;\n\t\tbreak;\n\tcase 0x13:\n\t\t*fxt = FX_GLOBALVOL;\n\t\tbreak;\n\tcase 0x14:\n\t\t*fxt = FX_FINE_VIBRATO;\n\t\tbreak;\n\tcase 0x1e:\t\t\t/* special misc */\n\t\tswitch (MSN(*fxp)) {\n\t\tcase 0x0:\t\t/* sample control */\n\t\t\tif (LSN(*fxp) == 1) { /* enable surround */\n\t\t\t\t/* This is the only sample control effect\n\t\t\t\t * that 2GDM emits. BWSB ignores it,\n\t\t\t\t * but supporting it is harmless. */\n\t\t\t\t*fxt = FX_SURROUND;\n\t\t\t\t*fxp = 1;\n\t\t\t} else {\n\t\t\t\t*fxt = *fxp = 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 0x8:\t\t/* set pan position */\n\t\t\t*fxt = FX_EXTENDED;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t*fxt = *fxp = 0;\n\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\tcase 0x1f:\n\t\t*fxt = FX_S3M_BPM;\n\t\tbreak;\n\tdefault:\n\t\t*fxt = *fxp = 0;\n\t}\n}\n\n\nstatic int gdm_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_event *event;\n\tint vermaj, vermin, tvmaj, tvmin, tracker;\n\tint /*origfmt,*/ ord_ofs, pat_ofs, ins_ofs, smp_ofs;\n\tuint8 buffer[32], panmap[32];\n\tint i;\n\n\tLOAD_INIT();\n\n\thio_read32b(f);\t\t\t/* skip magic */\n\thio_read(mod->name, 1, 32, f);\n\thio_seek(f, 32, SEEK_CUR);\t/* skip author */\n\n\thio_seek(f, 7, SEEK_CUR);\n\n\tvermaj = hio_read8(f);\n\tvermin = hio_read8(f);\n\ttracker = hio_read16l(f);\n\ttvmaj = hio_read8(f);\n\ttvmin = hio_read8(f);\n\n\tif (tracker == 0) {\n\t\tlibxmp_set_type(m, \"GDM %d.%02d (2GDM %d.%02d)\",\n\t\t\t\t\tvermaj, vermin, tvmaj, tvmin);\n\t} else {\n\t\tlibxmp_set_type(m, \"GDM %d.%02d (unknown tracker %d.%02d)\",\n\t\t\t\t\tvermaj, vermin, tvmaj, tvmin);\n\t}\n\n\tif (hio_read(panmap, 32, 1, f) == 0) {\n\t\tD_(D_CRIT \"error reading header\");\n\t\treturn -1;\n\t}\n\tfor (i = 0; i < 32; i++) {\n\t\tif (panmap[i] == 255) {\n\t\t\tpanmap[i] = 8;\n\t\t\tmod->xxc[i].vol = 0;\n\t\t\tmod->xxc[i].flg |= XMP_CHANNEL_MUTE;\n\t\t} else if (panmap[i] == 16) {\n\t\t\tpanmap[i] = 8;\n\t\t}\n\t\tmod->xxc[i].pan = 0x80 + (panmap[i] - 8) * 16;\n\t}\n\n\tmod->gvl = hio_read8(f);\n\tmod->spd = hio_read8(f);\n\tmod->bpm = hio_read8(f);\n\t/*origfmt =*/ hio_read16l(f);\n\tord_ofs = hio_read32l(f);\n\tmod->len = hio_read8(f) + 1;\n\tpat_ofs = hio_read32l(f);\n\tmod->pat = hio_read8(f) + 1;\n\tins_ofs = hio_read32l(f);\n\tsmp_ofs = hio_read32l(f);\n\tmod->ins = mod->smp = hio_read8(f) + 1;\n\n\t/* Sanity check */\n\tif (mod->ins > MAX_INSTRUMENTS)\n\t\treturn -1;\n\n\tm->c4rate = C4_NTSC_RATE;\n\n\tMODULE_INFO();\n\n\thio_seek(f, start + ord_ofs, SEEK_SET);\n\n\tfor (i = 0; i < mod->len; i++)\n\t\tmod->xxo[i] = hio_read8(f);\n\n\t/* Read instrument data */\n\n\thio_seek(f, start + ins_ofs, SEEK_SET);\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\treturn -1;\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tint flg, c4spd, vol, pan;\n\n\t\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t\t\treturn -1;\n\n\t\tif (hio_read(buffer, 1, 32, f) != 32)\n\t\t\treturn -1;\n\n\t\tlibxmp_instrument_name(mod, i, buffer, 32);\n\t\thio_seek(f, 12, SEEK_CUR);\t\t/* skip filename */\n\t\thio_read8(f);\t\t\t/* skip EMS handle */\n\t\tmod->xxs[i].len = hio_read32l(f);\n\t\tmod->xxs[i].lps = hio_read32l(f);\n\t\tmod->xxs[i].lpe = hio_read32l(f);\n\t\tflg = hio_read8(f);\n\t\tc4spd = hio_read16l(f);\n\t\tvol = hio_read8(f);\n\t\tpan = hio_read8(f);\n\n\t\tmod->xxi[i].sub[0].vol = vol > 0x40 ? 0x40 : vol;\n\t\tmod->xxi[i].sub[0].pan = pan > 15 ? 0x80 : 0x80 + (pan - 8) * 16;\n\t\tlibxmp_c2spd_to_note(c4spd, &mod->xxi[i].sub[0].xpo, &mod->xxi[i].sub[0].fin);\n\n\t\tmod->xxi[i].sub[0].sid = i;\n\t\tmod->xxs[i].flg = 0;\n\n\n\t\tif (mod->xxs[i].len > 0)\n\t\t\tmod->xxi[i].nsm = 1;\n\n\t\tif (flg & 0x01) {\n\t\t\tmod->xxs[i].flg |= XMP_SAMPLE_LOOP;\n\t\t}\n\t\tif (flg & 0x02) {\n\t\t\tmod->xxs[i].flg |= XMP_SAMPLE_16BIT;\n\t\t\tmod->xxs[i].len >>= 1;\n\t\t\tmod->xxs[i].lps >>= 1;\n\t\t\tmod->xxs[i].lpe >>= 1;\n\t\t}\n\n\t\tD_(D_INFO \"[%2X] %-32.32s %05x%c%05x %05x %c V%02x P%02x %5d\",\n\t\t\t\ti, mod->xxi[i].name,\n\t\t\t\tmod->xxs[i].len,\n\t\t\t\tmod->xxs[i].flg & XMP_SAMPLE_16BIT ? '+' : ' ',\n\t\t\t\tmod->xxs[i].lps,\n\t\t\t\tmod->xxs[i].lpe,\n\t\t\t\tmod->xxs[i].flg & XMP_SAMPLE_LOOP ? 'L' : ' ',\n\t\t\t\tmod->xxi[i].sub[0].vol,\n\t\t\t\tmod->xxi[i].sub[0].pan,\n\t\t\t\tc4spd);\n\t}\n\n\t/* Read and convert patterns */\n\n\thio_seek(f, start + pat_ofs, SEEK_SET);\n\n\t/* Effects in muted channels are processed, so scan patterns first to\n\t * see the real number of channels\n\t */\n\tmod->chn = 0;\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tint len, c, r, k;\n\n\t\tlen = hio_read16l(f);\n\t\tlen -= 2;\n\n\t\tfor (r = 0; len > 0; ) {\n\t\t\tc = hio_read8(f);\n\t\t\tif (hio_error(f))\n\t\t\t\treturn -1;\n\t\t\tlen--;\n\n\t\t\tif (c == 0) {\n\t\t\t\tr++;\n\n\t\t\t\t/* Sanity check */\n\t\t\t\tif (len == 0) {\n\t\t\t\t\tif  (r > 64)\n\t\t\t\t\t\treturn -1;\n\t\t\t\t} else {\n\t\t\t\t\tif (r >= 64)\n\t\t\t\t\t\treturn -1;\n\t\t\t\t}\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (mod->chn <= (c & 0x1f))\n\t\t\t\tmod->chn = (c & 0x1f) + 1;\n\n\t\t\tif (c & 0x20) {\t\t/* note and sample follows */\n\t\t\t\thio_read8(f);\n\t\t\t\thio_read8(f);\n\t\t\t\tlen -= 2;\n\t\t\t}\n\n\t\t\tif (c & 0x40) {\t\t/* effect(s) follow */\n\t\t\t\tdo {\n\t\t\t\t\tk = hio_read8(f);\n\t\t\t\t\tif (hio_error(f))\n\t\t\t\t\t\treturn -1;\n\t\t\t\t\tlen--;\n\t\t\t\t\tif ((k & 0xc0) != 0xc0) {\n\t\t\t\t\t\thio_read8(f);\n\t\t\t\t\t\tlen--;\n\t\t\t\t\t}\n\t\t\t\t} while (k & 0x20);\n\t\t\t}\n\t\t}\n\t}\n\n\tmod->trk = mod->pat * mod->chn;\n\n\tif (libxmp_init_pattern(mod) < 0)\n\t\treturn -1;\n\n\thio_seek(f, start + pat_ofs, SEEK_SET);\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tint len, c, r, k;\n\n\t\tif (libxmp_alloc_pattern_tracks(mod, i, 64) < 0)\n\t\t\treturn -1;\n\n\t\tlen = hio_read16l(f);\n\t\tlen -= 2;\n\n\t\tfor (r = 0; len > 0; ) {\n\t\t\tc = hio_read8(f);\n\t\t\tif (hio_error(f))\n\t\t\t\treturn -1;\n\t\t\tlen--;\n\n\t\t\tif (c == 0) {\n\t\t\t\tr++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Sanity check */\n\t\t\tif ((c & 0x1f) >= mod->chn || r >= 64) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tevent = &EVENT(i, c & 0x1f, r);\n\n\t\t\tif (c & 0x20) {\t\t/* note and sample follows */\n\t\t\t\tk = hio_read8(f);\n\t\t\t\t/* 0 is empty note */\n\t\t\t\tevent->note = k ? 12 + 12 * MSN(k & 0x7f) + LSN(k) : 0;\n\t\t\t\tevent->ins = hio_read8(f);\n\t\t\t\tlen -= 2;\n\t\t\t}\n\n\t\t\tif (c & 0x40) {\t\t/* effect(s) follow */\n\t\t\t\tdo {\n\t\t\t\t\tk = hio_read8(f);\n\t\t\t\t\tif (hio_error(f))\n\t\t\t\t\t\treturn -1;\n\t\t\t\t\tlen--;\n\t\t\t\t\tswitch ((k & 0xc0) >> 6) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tevent->fxt = k & 0x1f;\n\t\t\t\t\t\tevent->fxp = hio_read8(f);\n\t\t\t\t\t\tlen--;\n\t\t\t\t\t\tfix_effect(&event->fxt, &event->fxp);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tevent->f2t = k & 0x1f;\n\t\t\t\t\t\tevent->f2p = hio_read8(f);\n\t\t\t\t\t\tlen--;\n\t\t\t\t\t\tfix_effect(&event->f2t, &event->f2p);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\thio_read8(f);\n\t\t\t\t\t\tlen--;\n\t\t\t\t\t}\n\t\t\t\t} while (k & 0x20);\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Read samples */\n\n\thio_seek(f, start + smp_ofs, SEEK_SET);\n\n\tD_(D_INFO \"Stored samples: %d\", mod->smp);\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tif (libxmp_load_sample(m, f, SAMPLE_FLAG_UNS, &mod->xxs[i], NULL) < 0)\n\t\t\treturn -1;\n\t}\n\n\tm->quirk |= QUIRK_ARPMEM | QUIRK_FINEFX;\n\n\t/* BWSB actually gets several aspects of this wrong, but this\n\t * seems to be the intent. No original GDMs exist so it's not\n\t * likely there's a reason to simulate its mistakes here. */\n\tm->flow_mode = FLOW_MODE_ST3_321;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/hmn_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2023 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"loader.h\"\n#include \"mod.h\"\n#include \"../period.h\"\n#include \"../hmn_extras.h\"\n\n/*\n * From http://www.livet.se/mahoney/:\n *\n * Most modules from His Master's Noise uses special chip-sounds or\n * fine-tuning of samples that never was a part of the standard NoiseTracker\n * v2.0 command set. So if you want to listen to them correctly use an Amiga\n * emulator and run the demo! DeliPlayer does a good job of playing them\n * (there are some occasional error mostly concerning vibrato and portamento\n * effects, but I can live with that!), and it can be downloaded from\n * http://www.deliplayer.com\n */\n\n/*\n * From http://www.cactus.jawnet.pl/attitude/index.php?action=readtext&issue=12&which=12\n *\n * [Bepp] For your final Amiga release, the music disk His Master's Noise,\n * you developed a special version of NoiseTracker. Could you tell us a\n * little about this project?\n *\n * [Mahoney] I wanted to make a music disk with loads of songs, without being\n * too repetitive or boring. So all of my \"experimental features\" that did not\n * belong to NoiseTracker v2.0 were put into a separate version that would\n * feature wavetable sounds, chord calculations, off-line filter calculations,\n * mixing, reversing, sample accurate delays, resampling, fades - calculations\n * that would be done on a standard setup of sounds instead of on individual\n * modules. This \"compression technique\" lead to some 100 songs fitting on two\n * standard 3.5\" disks, written by 22 different composers. I'd say that writing\n * a music program does give you loads of talented friends - you should try\n * that yourself someday!\n */\n\n/*\n * From: Pex Tufvesson\n * To: Claudio Matsuoka\n * Date: Sat, Jun 1, 2013 at 4:16 AM\n * Subject: Re: A question about (very) old stuff\n *\n * (...)\n * If I remember correctly, these chip sounds were done with several short\n * waveforms, and an index table that was loopable that would choose which\n * waveform to play each frame. And, you didn't have to \"draw\" every\n * waveform in the instrument - you would choose which waveforms to draw\n * and the replayer would (at startup) interpolate the waveforms that you\n * didn't draw.\n *\n * In the special noisetracker, you could draw all of these waveforms, draw\n * the index table, and the instrument would be stored in one of the\n * \"patterns\" of the song.\n */\n\nstatic int hmn_test(HIO_HANDLE *, char *, const int);\nstatic int hmn_load(struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_hmn = {\n\t\"His Master's Noise\",\n\thmn_test,\n\thmn_load\n};\n\n/* His Master's Noise M&K! will fail in regular Noisetracker loading\n * due to invalid finetune values.\n */\n#define MAGIC_FEST\tMAGIC4('F', 'E', 'S', 'T')\n#define MAGIC_MK\tMAGIC4('M', '&', 'K', '!')\n\nstatic int hmn_test(HIO_HANDLE * f, char *t, const int start)\n{\n\tint magic;\n\n\thio_seek(f, start + 1080, SEEK_SET);\n\tmagic = hio_read32b(f);\n\n\tif (magic != MAGIC_FEST && magic != MAGIC_MK)\n\t\treturn -1;\n\n\thio_seek(f, start + 0, SEEK_SET);\n\tlibxmp_read_title(f, t, 20);\n\n\treturn 0;\n}\n\nstruct mupp {\n\tuint8 prgon;\n\tuint8 pattno;\n\tuint8 dataloopstart;\n\tuint8 dataloopend;\n};\n\nstatic int hmn_load(struct module_data *m, HIO_HANDLE * f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j;\n\tstruct xmp_event *event;\n\tstruct mod_header mh;\n\tstruct mupp mupp[31];\n\tuint8 mod_event[4];\n\tint mupp_index, num_mupp;\n\n\tLOAD_INIT();\n\n\t/*\n\t *    clr.b   $1c(a6) ;prog on/off\n\t *    CMP.L   #'Mupp',-$16(a3,d4.l)\n\t *    bne.s   noprgo\n\t *    move.l  a0,-(a7)\n\t *    move.b  #1,$1c(a6)      ;prog on\n\t *    move.l  l697,a0\n\t *    lea     $43c(a0),a0\n\t *    moveq   #0,d2\n\t *    move.b  -$16+$4(a3,d4.l),d2     ;pattno\n\t *    mulu    #$400,d2\n\t *    lea     (a0,d2.l),a0\n\t *    move.l  a0,4(a6)        ;proginstr data-start\n\t *    moveq   #0,d2\n\t *    MOVE.B  $3C0(A0),$12(A6)\n\t *    AND.B   #$7F,$12(A6)\n\t *    move.b  $380(a0),d2\n\t *    mulu    #$20,d2\n\t *    lea     (a0,d2.w),a0\n\t *    move.l  a0,$a(a6)       ;loopstartmempoi = startmempoi\n\t *    move.B  $3(a3,d4.l),$13(a6)     ;volume\n\t *    move.b  -$16+$5(a3,d4.l),8(a6)  ;dataloopstart\n\t *    move.b  -$16+$6(a3,d4.l),9(a6)  ;dataloopend\n\t *    move.w  #$10,$e(a6)     ;looplen\n\t *    move.l  (a7)+,a0\n\t *    MOVE.W  $12(A6),(A2)\n\t *    AND.W   #$FF,(A2)\n\t *    BRA.S   L505_LQ\n\t */\n\n\t/*\n\t * Wavetable structure is 22 * 32 byte waveforms and 32 byte\n\t * wave control data with looping.\n\t */\n\tmemset(mupp, 0, 31 * sizeof (struct mupp));\n\n\thio_read(mh.name, 20, 1, f);\n\tnum_mupp = 0;\n\n\tfor (i = 0; i < 31; i++) {\n\t\thio_read(mh.ins[i].name, 22, 1, f);\t/* Instrument name */\n\t\tif (memcmp(mh.ins[i].name, \"Mupp\", 4) == 0) {\n\t\t\tmupp[i].prgon = 1;\n\t\t\tmupp[i].pattno = mh.ins[i].name[4];\n\t\t\tmupp[i].dataloopstart = mh.ins[i].name[5];\n\t\t\tmupp[i].dataloopend = mh.ins[i].name[6];\n\t\t\tnum_mupp++;\n\t\t}\n\n\t\tmh.ins[i].size = hio_read16b(f);\n\t\tmh.ins[i].finetune = hio_read8(f);\n\t\tmh.ins[i].volume = hio_read8(f);\n\t\tmh.ins[i].loop_start = hio_read16b(f);\n\t\tmh.ins[i].loop_size = hio_read16b(f);\n\t}\n\tmh.len = hio_read8(f);\n\tmh.restart = hio_read8(f);\n\thio_read(mh.order, 128, 1, f);\n\thio_read(mh.magic, 4, 1, f);\n\n\tmod->chn = 4;\n\tmod->ins = 31;\n\tmod->smp = mod->ins + 28 * num_mupp;\n\tmod->len = mh.len;\n\tmod->rst = mh.restart;\n\tmemcpy(mod->xxo, mh.order, 128);\n\n\tfor (i = 0; i < 128; i++) {\n\t\tif (mod->xxo[i] > mod->pat)\n\t\t\tmod->pat = mod->xxo[i];\n\t}\n\n\tmod->pat++;\n\tmod->trk = mod->chn * mod->pat;\n\n\tif (libxmp_hmn_new_module_extras(m) != 0)\n\t\treturn -1;\n\n\tstrncpy(mod->name, (char *)mh.name, 20);\n\tlibxmp_set_type(m, \"%s (%4.4s)\", \"His Master's Noise\", mh.magic);\n\tMODULE_INFO();\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\treturn -1;\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tif (mupp[i].prgon) {\n\t\t\tmod->xxi[i].nsm = 28;\n\t\t\tsnprintf(mod->xxi[i].name, 32,\n\t\t\t\t\"Mupp %02x %02x %02x\", mupp[i].pattno,\n\t\t\t\tmupp[i].dataloopstart, mupp[i].dataloopend);\n\t\t\tif (libxmp_hmn_new_instrument_extras(&mod->xxi[i]) != 0)\n\t\t\t\treturn -1;\n\t\t} else {\n\t\t\tmod->xxi[i].nsm = 1;\n\t\t\tlibxmp_instrument_name(mod, i, mh.ins[i].name, 22);\n\n\t\t\tmod->xxs[i].len = 2 * mh.ins[i].size;\n\t\t\tmod->xxs[i].lps = 2 * mh.ins[i].loop_start;\n\t\t\tmod->xxs[i].lpe = mod->xxs[i].lps +\n\t\t\t\t\t\t2 * mh.ins[i].loop_size;\n\t\t\tmod->xxs[i].flg = mh.ins[i].loop_size > 1 ?\n\t\t\t\t\t\tXMP_SAMPLE_LOOP : 0;\n\t\t}\n\n\t\tif (libxmp_alloc_subinstrument(mod, i, mod->xxi[i].nsm) < 0)\n\t\t\treturn -1;\n\n\t\tfor (j = 0; j < mod->xxi[i].nsm; j++) {\n\t\t\tmod->xxi[i].sub[j].fin =\n\t\t\t\t\t-(int8)(mh.ins[i].finetune << 3);\n\t\t\tmod->xxi[i].sub[j].vol = mh.ins[i].volume;\n\t\t\tmod->xxi[i].sub[j].pan = 0x80;\n\t\t\tmod->xxi[i].sub[j].sid = i;\n\t\t}\n\t}\n\n\tif (libxmp_init_pattern(mod) < 0)\n\t\treturn -1;\n\n\t/* Load and convert patterns */\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tif (libxmp_alloc_pattern_tracks(mod, i, 64) < 0)\n\t\t\treturn -1;\n\n\t\tfor (j = 0; j < (64 * 4); j++) {\n\t\t\tevent = &EVENT(i, j % 4, j / 4);\n\t\t\tif (hio_read(mod_event, 1, 4, f) < 4) {\n\t\t\t\tD_(D_CRIT \"read error at pat %d\", i);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tlibxmp_decode_protracker_event(event, mod_event);\n\n\t\t\tswitch (event->fxt) {\n\t\t\tcase 0x07:\n\t\t\t\tevent->fxt = FX_MEGAARP;\n\t\t\t\tbreak;\n\t\t\tcase 0x08:\n\t\t\tcase 0x09:\n\t\t\tcase 0x0e:\n\t\t\t\tevent->fxt = event->fxp = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Noisetracker does not support CIA timing (Glue Master/muppenkorva.mod) */\n\tm->quirk |= QUIRK_NOBPM;\n\tm->period_type = PERIOD_MODRNG;\n\n\t/* Load samples */\n\n\tD_(D_INFO \"Stored samples: %d\", mod->smp);\n\n\tfor (i = 0; i < 31; i++) {\n\t\tif (libxmp_load_sample(m, f, SAMPLE_FLAG_FULLREP,\n\t\t\t\t\t\t&mod->xxs[i], NULL) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\n\t/* Load Mupp samples */\n\n\tmupp_index = 0;\n\tfor (i = 0; i < 31; i ++) {\n\t\tstruct hmn_instrument_extras *extra =\n\t\t\t(struct hmn_instrument_extras *)mod->xxi[i].extra;\n\n\t\tif (!mupp[i].prgon)\n\t\t\tcontinue;\n\n\t\thio_seek(f, start + 1084 + 1024 * mupp[i].pattno, SEEK_SET);\n\t\tfor (j = 0; j < 28; j++) {\n\t\t\tint k = 31 + 28 * mupp_index + j;\n\t\t\tmod->xxi[i].sub[j].sid = k;\n\t\t\tmod->xxs[k].len = 32;\n\t\t\tmod->xxs[k].lps = 0;\n\t\t\tmod->xxs[k].lpe = 32;\n\t\t\tmod->xxs[k].flg = XMP_SAMPLE_LOOP;\n\t\t\tif (libxmp_load_sample(m, f, 0, &mod->xxs[k], NULL) < 0)\n\t\t\t\treturn -1;\n\t\t}\n\n\t\textra->dataloopstart = mupp[i].dataloopstart;\n\t\textra->dataloopend = mupp[i].dataloopend;\n\n\t\thio_read(extra->data, 1, 64, f);\n\t\thio_read(extra->progvolume, 1, 64, f);\n\n\t\tmupp_index++;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/ice_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2021 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/* Loader for Soundtracker 2.6/Ice Tracker modules */\n\n#include \"loader.h\"\n\n#define MAGIC_MTN_\tMAGIC4('M','T','N',0)\n#define MAGIC_IT10\tMAGIC4('I','T','1','0')\n\nstatic int ice_test(HIO_HANDLE *, char *, const int);\nstatic int ice_load(struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_ice = {\n\t\"Soundtracker 2.6/Ice Tracker\",\n\tice_test,\n\tice_load\n};\n\nstatic int ice_test(HIO_HANDLE * f, char *t, const int start)\n{\n\tuint32 magic;\n\n\thio_seek(f, start + 1464, SEEK_SET);\n\tmagic = hio_read32b(f);\n\tif (magic != MAGIC_MTN_ && magic != MAGIC_IT10)\n\t\treturn -1;\n\n\thio_seek(f, start + 0, SEEK_SET);\n\tlibxmp_read_title(f, t, 28);\n\n\treturn 0;\n}\n\nstruct ice_ins {\n\tchar name[22];\t\t/* Instrument name */\n\tuint16 len;\t\t/* Sample length / 2 */\n\tuint8 finetune;\t\t/* Finetune */\n\tuint8 volume;\t\t/* Volume (0-63) */\n\tuint16 loop_start;\t/* Sample loop start in file */\n\tuint16 loop_size;\t/* Loop size / 2 */\n};\n\nstruct ice_header {\n\tchar title[20];\n\tstruct ice_ins ins[31];\t/* Instruments */\n\tuint8 len;\t\t/* Size of the pattern list */\n\tuint8 trk;\t\t/* Number of tracks */\n\tuint8 ord[128][4];\n\tuint32 magic;\t\t/* 'MTN\\0', 'IT10' */\n};\n\nstatic int ice_load(struct module_data *m, HIO_HANDLE * f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j;\n\tstruct xmp_event *event;\n\tstruct ice_header ih;\n\tuint8 ev[4];\n\n\tLOAD_INIT();\n\n\thio_read(ih.title, 20, 1, f);\n\tfor (i = 0; i < 31; i++) {\n\t\thio_read(ih.ins[i].name, 22, 1, f);\n\t\tih.ins[i].len = hio_read16b(f);\n\t\tih.ins[i].finetune = hio_read8(f);\n\t\tih.ins[i].volume = hio_read8(f);\n\t\tih.ins[i].loop_start = hio_read16b(f);\n\t\tih.ins[i].loop_size = hio_read16b(f);\n\t}\n\tih.len = hio_read8(f);\n\tih.trk = hio_read8(f);\n\thio_read(ih.ord, 128 * 4, 1, f);\n\tih.magic = hio_read32b(f);\n\n\t/* Sanity check */\n\tif (ih.len > 128) {\n\t\treturn -1;\n\t}\n\tfor (i = 0; i < ih.len; i++) {\n\t\tfor (j = 0; j < 4; j++) {\n\t\t\tif (ih.ord[i][j] >= ih.trk)\n\t\t\t\treturn -1;\n\t\t}\n\t}\n\n\tif (ih.magic == MAGIC_IT10)\n\t\tlibxmp_set_type(m, \"Ice Tracker\");\n\telse if (ih.magic == MAGIC_MTN_)\n\t\tlibxmp_set_type(m, \"Soundtracker 2.6\");\n\telse\n\t\treturn -1;\n\n\tmod->ins = 31;\n\tmod->smp = mod->ins;\n\tmod->pat = ih.len;\n\tmod->len = ih.len;\n\tmod->trk = ih.trk;\n\n\tstrncpy(mod->name, (char *)ih.title, 20);\n\tMODULE_INFO();\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\treturn -1;\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tstruct xmp_instrument *xxi;\n\t\tstruct xmp_sample *xxs;\n\n\t\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t\t\treturn -1;\n\n\t\txxi = &mod->xxi[i];\n\t\txxs = &mod->xxs[i];\n\n\t\txxs->len = 2 * ih.ins[i].len;\n\t\txxs->lps = 2 * ih.ins[i].loop_start;\n\t\txxs->lpe = xxs->lps + 2 * ih.ins[i].loop_size;\n\t\txxs->flg = ih.ins[i].loop_size > 1 ? XMP_SAMPLE_LOOP : 0;\n\t\txxi->sub[0].vol = ih.ins[i].volume;\n\t\t/* xxi->sub[0].fin = (int8)(ih.ins[i].finetune << 4); */\n\t\txxi->sub[0].pan = 0x80;\n\t\txxi->sub[0].sid = i;\n\n\t\tif (xxs->len > 0)\n\t\t\txxi->nsm = 1;\n\n\t\tD_(D_INFO \"[%2X] %-22.22s %04x %04x %04x %c %02x %01x\",\n\t\t   i, ih.ins[i].name, xxs->len, xxs->lps,\n\t\t   xxs->lpe, xxs->flg & XMP_SAMPLE_LOOP ? 'L' : ' ',\n\t\t   xxi->sub[0].vol, xxi->sub[0].fin >> 4);\n\t}\n\n\tif (libxmp_init_pattern(mod) < 0)\n\t\treturn -1;\n\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tif (libxmp_alloc_pattern(mod, i) < 0)\n\t\t\treturn -1;\n\t\tmod->xxp[i]->rows = 64;\n\n\t\tfor (j = 0; j < mod->chn; j++) {\n\t\t\tmod->xxp[i]->index[j] = ih.ord[i][j];\n\t\t}\n\t\tmod->xxo[i] = i;\n\t}\n\n\tD_(D_INFO \"Stored tracks: %d\", mod->trk);\n\n\tfor (i = 0; i < mod->trk; i++) {\n\t\tif (libxmp_alloc_track(mod, i, 64) < 0)\n\t\t\treturn -1;\n\n\t\tfor (j = 0; j < mod->xxt[i]->rows; j++) {\n\t\t\tevent = &mod->xxt[i]->event[j];\n\t\t\tif (hio_read(ev, 1, 4, f) < 4) {\n\t\t\t\tD_(D_CRIT \"read error at track %d\", i);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tlibxmp_decode_protracker_event(event, ev);\n\n\t\t\tif (event->fxt == FX_SPEED) {\n\t\t\t\tif (MSN(event->fxp) && LSN(event->fxp)) {\n\t\t\t\t\tevent->fxt = FX_ICE_SPEED;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tm->period_type = PERIOD_MODRNG;\n\n\t/* Read samples */\n\n\tD_(D_INFO \"Stored samples: %d\", mod->smp);\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tif (mod->xxs[i].len <= 4)\n\t\t\tcontinue;\n\t\tif (libxmp_load_sample(m, f, 0, &mod->xxs[i], NULL) < 0)\n\t\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/iff.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common.h\"\n#include \"../list.h\"\n#include \"iff.h\"\n\n#include \"loader.h\"\n\nstruct iff_data {\n\tstruct list_head iff_list;\n\tunsigned id_size;\n\tunsigned flags;\n};\n\nstatic int iff_process(iff_handle opaque, struct module_data *m, char *id, long size,\n\t\tHIO_HANDLE *f, void *parm)\n{\n\tstruct iff_data *data = (struct iff_data *)opaque;\n\tstruct list_head *tmp;\n\tstruct iff_info *i;\n\tint pos;\n\n\tpos = hio_tell(f);\n\n\tlist_for_each(tmp, &data->iff_list) {\n\t\ti = list_entry(tmp, struct iff_info, list);\n\t\tif (id && !memcmp(id, i->id, data->id_size)) {\n\t\t\tD_(D_WARN \"Load IFF chunk %s (%ld) @%d\", id, size, pos);\n\t\t\tif (size > IFF_MAX_CHUNK_SIZE) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif (i->loader(m, size, f, parm) < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (hio_seek(f, pos + size, SEEK_SET) < 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int iff_chunk(iff_handle opaque, struct module_data *m, HIO_HANDLE *f, void *parm)\n{\n\tstruct iff_data *data = (struct iff_data *)opaque;\n\tunsigned size;\n\tchar id[17] = \"\";\n\n\tD_(D_INFO \"chunk id size: %d\", data->id_size);\n\tif (hio_read(id, 1, data->id_size, f) != data->id_size) {\n\t\t(void)hio_error(f);\t/* clear error flag */\n\t\treturn 1;\n\t}\n\tD_(D_INFO \"chunk id: [%s]\", id);\n\n\tif (data->flags & IFF_SKIP_EMBEDDED) {\n\t\t/* embedded RIFF hack */\n\t\tif (!strncmp(id, \"RIFF\", 4)) {\n\t\t\thio_read32b(f);\n\t\t\thio_read32b(f);\n\t\t\t/* read first chunk ID instead */\n\t\t\tif (hio_read(id, 1, data->id_size, f) != data->id_size){\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (data->flags & IFF_LITTLE_ENDIAN) {\n\t\tsize = hio_read32l(f);\n\t} else {\n\t\tsize = hio_read32b(f);\n\t}\n\tD_(D_INFO \"size: %d\", size);\n\n\tif (hio_error(f)) {\n\t\treturn -1;\n\t}\n\n\tif (data->flags & IFF_CHUNK_ALIGN2) {\n\t\t/* Sanity check */\n\t\tif (size > 0xfffffffe) {\n\t\t\treturn -1;\n\t\t}\n\t\tsize = (size + 1) & ~1;\n\t}\n\n\tif (data->flags & IFF_CHUNK_ALIGN4) {\n\t\t/* Sanity check */\n\t\tif (size > 0xfffffffc) {\n\t\t\treturn -1;\n\t\t}\n\t\tsize = (size + 3) & ~3;\n\t}\n\n\t/* PT 3.6 hack: this does not seem to ever apply to \"PTDT\".\n\t * This broke several modules (city lights.pt36, acid phase.pt36) */\n\tif ((data->flags & IFF_FULL_CHUNK_SIZE) && memcmp(id, \"PTDT\", 4)) {\n\t\tif (size < data->id_size + 4)\n\t\t\treturn -1;\n\t\tsize -= data->id_size + 4;\n\t}\n\n\treturn iff_process(opaque, m, id, size, f, parm);\n}\n\niff_handle libxmp_iff_new(void)\n{\n\tstruct iff_data *data;\n\n\tdata = (struct iff_data *) malloc(sizeof(struct iff_data));\n\tif (data == NULL) {\n\t\treturn NULL;\n\t}\n\n\tINIT_LIST_HEAD(&data->iff_list);\n\tdata->id_size = 4;\n\tdata->flags = 0;\n\n\treturn (iff_handle)data;\n}\n\nint libxmp_iff_load(iff_handle opaque, struct module_data *m, HIO_HANDLE *f, void *parm)\n{\n\tint ret;\n\n\twhile (!hio_eof(f)) {\n\t\tret = iff_chunk(opaque, m, f, parm);\n\t\tif (ret > 0)\n\t\t\tbreak;\n\t\tif (ret < 0)\n\t\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nint libxmp_iff_register(iff_handle opaque, const char *id,\n\tint (*loader)(struct module_data *, int, HIO_HANDLE *, void *))\n{\n\tstruct iff_data *data = (struct iff_data *)opaque;\n\tstruct iff_info *f;\n\tint i = 0;\n\n\tf = (struct iff_info *) malloc(sizeof(struct iff_info));\n\tif (f == NULL)\n\t\treturn -1;\n\n\t/* Note: previously was an strncpy */\n\tfor (; i < 4 && id && id[i]; i++)\n\t\tf->id[i] = id[i];\n\tfor (; i < 4; i++)\n\t\tf->id[i] = '\\0';\n\n\tf->loader = loader;\n\n\tlist_add_tail(&f->list, &data->iff_list);\n\n\treturn 0;\n}\n\nvoid libxmp_iff_release(iff_handle opaque)\n{\n\tstruct iff_data *data = (struct iff_data *)opaque;\n\tstruct list_head *tmp;\n\tstruct iff_info *i;\n\n\t/* can't use list_for_each, we free the node before incrementing */\n\tfor (tmp = (&data->iff_list)->next; tmp != (&data->iff_list);) {\n\t\ti = list_entry(tmp, struct iff_info, list);\n\t\tlist_del(&i->list);\n\t\ttmp = tmp->next;\n\t\tfree(i);\n\t}\n\n\tfree(data);\n}\n\n/* Functions to tune IFF mutations */\n\nvoid libxmp_iff_id_size(iff_handle opaque, int n)\n{\n\tstruct iff_data *data = (struct iff_data *)opaque;\n\n\tdata->id_size = n;\n}\n\nvoid libxmp_iff_set_quirk(iff_handle opaque, int i)\n{\n\tstruct iff_data *data = (struct iff_data *)opaque;\n\n\tdata->flags |= i;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/iff.h",
    "content": "#ifndef LIBXMP_IFF_H\n#define LIBXMP_IFF_H\n\n#include \"../hio.h\"\n#include \"../list.h\"\n\n#define IFF_NOBUFFER 0x0001\n\n#define IFF_LITTLE_ENDIAN\t0x01\n#define IFF_FULL_CHUNK_SIZE\t0x02\n#define IFF_CHUNK_ALIGN2\t0x04\n#define IFF_CHUNK_ALIGN4\t0x08\n#define IFF_SKIP_EMBEDDED\t0x10\n#define IFF_CHUNK_TRUNC4\t0x20\n\n#define IFF_MAX_CHUNK_SIZE\t0x800000\n\ntypedef void *iff_handle;\n\nstruct iff_header {\n\tchar form[4];\t\t/* FORM */\n\tint len;\t\t/* File length */\n\tchar id[4];\t\t/* IFF type identifier */\n};\n\nstruct iff_info {\n\tchar id[4];\n\tint (*loader)(struct module_data *, int, HIO_HANDLE *, void *);\n\tstruct list_head list;\n};\n\niff_handle libxmp_iff_new(void);\nint\tlibxmp_iff_load(iff_handle, struct module_data *, HIO_HANDLE *, void *);\n/* int libxmp_iff_chunk(iff_handle, struct module_data *, HIO_HANDLE *, void *); */\nint \tlibxmp_iff_register(iff_handle, const char *,\n\tint (*loader)(struct module_data *, int, HIO_HANDLE *, void *));\nvoid \tlibxmp_iff_id_size(iff_handle, int);\nvoid \tlibxmp_iff_set_quirk(iff_handle, int);\nvoid \tlibxmp_iff_release(iff_handle);\n/* int \tlibxmp_iff_process(iff_handle, struct module_data *, char *, long,\n\tHIO_HANDLE *, void *); */\n\n#endif /* LIBXMP_IFF_H */\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/it.h",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#ifndef LIBXMP_LOADERS_IT_H\n#define LIBXMP_LOADERS_IT_H\n\n#include \"loader.h\"\n\n/* IT flags */\n#define IT_STEREO\t0x01\n#define IT_VOL_OPT\t0x02\t/* Not recognized */\n#define IT_USE_INST\t0x04\n#define IT_LINEAR_FREQ\t0x08\n#define IT_OLD_FX\t0x10\n#define IT_LINK_GXX\t0x20\n#define IT_MIDI_WHEEL\t0x40\n#define IT_MIDI_CONFIG\t0x80\n\n/* IT special */\n#define IT_HAS_MSG\t0x01\n#define IT_EDIT_HISTORY\t0x02\n#define IT_HIGHLIGHTS\t0x04\n#define IT_SPEC_MIDICFG\t0x08\n\n/* IT instrument flags */\n#define IT_INST_SAMPLE\t0x01\n#define IT_INST_16BIT\t0x02\n#define IT_INST_STEREO\t0x04\n#define IT_INST_LOOP\t0x10\n#define IT_INST_SLOOP\t0x20\n#define IT_INST_BLOOP\t0x40\n#define IT_INST_BSLOOP\t0x80\n\n/* IT sample flags */\n#define IT_SMP_SAMPLE\t0x01\n#define IT_SMP_16BIT\t0x02\n#define IT_SMP_STEREO\t0x04\t/* unsupported */\n#define IT_SMP_COMP\t0x08\t/* unsupported */\n#define IT_SMP_LOOP\t0x10\n#define IT_SMP_SLOOP\t0x20\n#define IT_SMP_BLOOP\t0x40\n#define IT_SMP_BSLOOP\t0x80\n\n/* IT sample conversion flags */\n#define IT_CVT_SIGNED\t0x01\n#define IT_CVT_BIGEND\t0x02\t/* 'safe to ignore' according to ittech.txt */\n#define IT_CVT_DIFF\t0x04\t/* Compressed sample flag */\n#define IT_CVT_BYTEDIFF\t0x08\t/* 'safe to ignore' according to ittech.txt */\n#define IT_CVT_12BIT\t0x10\t/* 'safe to ignore' according to ittech.txt */\n#define IT_CVT_ADPCM\t0xff\t/* Special: always indicates Modplug ADPCM4 */\n\n/* IT envelope flags */\n#define IT_ENV_ON\t0x01\n#define IT_ENV_LOOP\t0x02\n#define IT_ENV_SLOOP\t0x04\n#define IT_ENV_CARRY\t0x08\n#define IT_ENV_FILTER\t0x80\n\n\nstruct it_file_header {\n\tuint32 magic;\t\t/* 'IMPM' */\n\tuint8 name[26];\t\t/* ASCIIZ Song name */\n\tuint8 hilite_min;\t/* Pattern editor highlight */\n\tuint8 hilite_maj;\t/* Pattern editor highlight */\n\tuint16 ordnum;\t\t/* Number of orders (must be even) */\n\tuint16 insnum;\t\t/* Number of instruments */\n\tuint16 smpnum;\t\t/* Number of samples */\n\tuint16 patnum;\t\t/* Number of patterns */\n\tuint16 cwt;\t\t/* Tracker ID and version */\n\tuint16 cmwt;\t\t/* Format version */\n\tuint16 flags;\t\t/* Flags */\n\tuint16 special;\t\t/* More flags */\n\tuint8 gv;\t\t/* Global volume */\n\tuint8 mv;\t\t/* Master volume */\n\tuint8 is;\t\t/* Initial speed */\n\tuint8 it;\t\t/* Initial tempo */\n\tuint8 sep;\t\t/* Panning separation */\n\tuint8 pwd;\t\t/* Pitch wheel depth */\n\tuint16 msglen;\t\t/* Message length */\n\tuint32 msgofs;\t\t/* Message offset */\n\tuint32 rsvd;\t\t/* Reserved */\n\tuint8 chpan[64];\t/* Channel pan settings */\n\tuint8 chvol[64];\t/* Channel volume settings */\n};\n\nstruct it_instrument1_header {\n\tuint32 magic;\t\t/* 'IMPI' */\n\tuint8 dosname[12];\t/* DOS filename */\n\tuint8 zero;\t\t/* Always zero */\n\tuint8 flags;\t\t/* Instrument flags */\n\tuint8 vls;\t\t/* Volume loop start */\n\tuint8 vle;\t\t/* Volume loop end */\n\tuint8 sls;\t\t/* Sustain loop start */\n\tuint8 sle;\t\t/* Sustain loop end */\n\tuint16 rsvd1;\t\t/* Reserved */\n\tuint16 fadeout;\t\t/* Fadeout (release) */\n\tuint8 nna;\t\t/* New note action */\n\tuint8 dnc;\t\t/* Duplicate note check */\n\tuint16 trkvers;\t\t/* Tracker version */\n\tuint8 nos;\t\t/* Number of samples */\n\tuint8 rsvd2;\t\t/* Reserved */\n\tuint8 name[26];\t\t/* ASCIIZ Instrument name */\n\tuint8 rsvd3[6];\t\t/* Reserved */\n\tuint8 keys[240];\n\tuint8 epoint[200];\n\tuint8 enode[50];\n};\n\nstruct it_instrument2_header {\n\tuint32 magic;\t\t/* 'IMPI' */\n\tuint8 dosname[12];\t/* DOS filename */\n\tuint8 zero;\t\t/* Always zero */\n\tuint8 nna;\t\t/* New Note Action */\n\tuint8 dct;\t\t/* Duplicate Check Type */\n\tuint8 dca;\t\t/* Duplicate Check Action */\n\tuint16 fadeout;\n\tuint8 pps;\t\t/* Pitch-Pan Separation */\n\tuint8 ppc;\t\t/* Pitch-Pan Center */\n\tuint8 gbv;\t\t/* Global Volume */\n\tuint8 dfp;\t\t/* Default pan */\n\tuint8 rv;\t\t/* Random volume variation */\n\tuint8 rp;\t\t/* Random pan variation */\n\tuint16 trkvers;\t\t/* Not used: tracked version */\n\tuint8 nos;\t\t/* Not used: number of samples */\n\tuint8 rsvd1;\t\t/* Reserved */\n\tuint8 name[26];\t\t/* ASCIIZ Instrument name */\n\tuint8 ifc;\t\t/* Initial filter cutoff */\n\tuint8 ifr;\t\t/* Initial filter resonance */\n\tuint8 mch;\t\t/* MIDI channel */\n\tuint8 mpr;\t\t/* MIDI program */\n\tuint16 mbnk;\t\t/* MIDI bank */\n\tuint8 keys[240];\n};\n\nstruct it_envelope_node {\n\tint8 y;\n\tuint16 x;\n};\n\nstruct it_envelope {\n\tuint8 flg;\t\t/* Flags */\n\tuint8 num;\t\t/* Number of node points */\n\tuint8 lpb;\t\t/* Loop beginning */\n\tuint8 lpe;\t\t/* Loop end */\n\tuint8 slb;\t\t/* Sustain loop beginning */\n\tuint8 sle;\t\t/* Sustain loop end */\n\tstruct it_envelope_node node[25];\n\tuint8 unused;\n};\n\nstruct it_sample_header {\n\tuint32 magic;\t\t/* 'IMPS' */\n\tuint8 dosname[12];\t/* DOS filename */\n\tuint8 zero;\t\t/* Always zero */\n\tuint8 gvl;\t\t/* Global volume for instrument */\n\tuint8 flags;\t\t/* Sample flags */\n\tuint8 vol;\t\t/* Volume */\n\tuint8 name[26];\t\t/* ASCIIZ sample name */\n\tuint8 convert;\t\t/* Sample flags */\n\tuint8 dfp;\t\t/* Default pan */\n\tuint32 length;\t\t/* Length */\n\tuint32 loopbeg;\t\t/* Loop begin */\n\tuint32 loopend;\t\t/* Loop end */\n\tuint32 c5spd;\t\t/* C 5 speed */\n\tuint32 sloopbeg;\t/* SusLoop begin */\n\tuint32 sloopend;\t/* SusLoop end */\n\tuint32 sample_ptr;\t/* Sample pointer */\n\tuint8 vis;\t\t/* Vibrato speed */\n\tuint8 vid;\t\t/* Vibrato depth */\n\tuint8 vir;\t\t/* Vibrato rate */\n\tuint8 vit;\t\t/* Vibrato waveform */\n};\n\nint itsex_decompress8(HIO_HANDLE *src, uint8 *dst, int len,\n\t\t      uint8 *tmp, int tmplen, int it215);\nint itsex_decompress16(HIO_HANDLE *src, int16 *dst, int len,\n\t\t       uint8 *tmp, int tmplen, int it215);\n\n#endif /* LIBXMP_LOADERS_IT_H */\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/it_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common.h\"\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\n#include \"loader.h\"\n#include \"it.h\"\n#include \"../period.h\"\n\n#define MAGIC_IMPM\tMAGIC4('I','M','P','M')\n#define MAGIC_IMPI\tMAGIC4('I','M','P','I')\n#define MAGIC_IMPS\tMAGIC4('I','M','P','S')\n\n#define TEMP_BUFFER_LEN\t65536\n\nstatic int it_test(HIO_HANDLE *, char *, const int);\nstatic int it_load(struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_it = {\n\t\"Impulse Tracker\",\n\tit_test,\n\tit_load\n};\n\nstatic int it_test(HIO_HANDLE *f, char *t, const int start)\n{\n\tif (hio_read32b(f) != MAGIC_IMPM)\n\t\treturn -1;\n\n\tlibxmp_read_title(f, t, 26);\n\n\treturn 0;\n}\n\n\n#define\tFX_NONE\t0xff\n#define FX_XTND 0xfe\n#define L_CHANNELS 64\n\nstatic const uint8 fx[32] = {\n\t/*   */ FX_NONE,\n\t/* A */ FX_S3M_SPEED,\n\t/* B */ FX_JUMP,\n\t/* C */ FX_IT_BREAK,\n\t/* D */ FX_VOLSLIDE,\n\t/* E */ FX_PORTA_DN,\n\t/* F */ FX_PORTA_UP,\n\t/* G */ FX_TONEPORTA,\n\t/* H */ FX_VIBRATO,\n\t/* I */ FX_TREMOR,\n\t/* J */ FX_S3M_ARPEGGIO,\n\t/* K */ FX_VIBRA_VSLIDE,\n\t/* L */ FX_TONE_VSLIDE,\n\t/* M */ FX_TRK_VOL,\n\t/* N */ FX_TRK_VSLIDE,\n\t/* O */ FX_OFFSET,\n\t/* P */ FX_IT_PANSLIDE,\n\t/* Q */ FX_MULTI_RETRIG,\n\t/* R */ FX_TREMOLO,\n\t/* S */ FX_XTND,\n\t/* T */ FX_IT_BPM,\n\t/* U */ FX_FINE_VIBRATO,\n\t/* V */ FX_GLOBALVOL,\n\t/* W */ FX_GVOL_SLIDE,\n\t/* X */ FX_SETPAN,\n\t/* Y */ FX_PANBRELLO,\n\t/* Z */ FX_MACRO,\n\t/* ? */ FX_NONE,\n\t/* / */ FX_MACROSMOOTH,\n\t/* ? */ FX_NONE,\n\t/* ? */ FX_NONE,\n\t/* ? */ FX_NONE\n};\n\nstatic void xlat_fx(int c, struct xmp_event *e, uint8 *last_fxp, int new_fx)\n{\n\tuint8 h = MSN(e->fxp), l = LSN(e->fxp);\n\n\tswitch (e->fxt = fx[e->fxt]) {\n\tcase FX_XTND:\t\t/* Extended effect */\n\t\te->fxt = FX_EXTENDED;\n\n\t\tif (h == 0 && e->fxp == 0) {\n\t\t\te->fxp = last_fxp[c];\n\t\t\th = MSN(e->fxp);\n\t\t\tl = LSN(e->fxp);\n\t\t} else {\n\t\t\tlast_fxp[c] = e->fxp;\n\t\t}\n\n\t\tswitch (h) {\n\t\tcase 0x1:\t/* Glissando */\n\t\t\te->fxp = 0x30 | l;\n\t\t\tbreak;\n\t\tcase 0x2:\t/* Finetune -- not supported */\n\t\t\te->fxt = e->fxp = 0;\n\t\t\tbreak;\n\t\tcase 0x3:\t/* Vibrato wave */\n\t\t\te->fxp = 0x40 | l;\n\t\t\tbreak;\n\t\tcase 0x4:\t/* Tremolo wave */\n\t\t\te->fxp = 0x70 | l;\n\t\t\tbreak;\n\t\tcase 0x5:\t/* Panbrello wave */\n\t\t\tif (l <= 3) {\n\t\t\t\te->fxt = FX_PANBRELLO_WF;\n\t\t\t\te->fxp = l;\n\t\t\t} else {\n\t\t\t\te->fxt = e->fxp = 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 0x6:\t/* Pattern delay */\n\t\t\te->fxp = 0xe0 | l;\n\t\t\tbreak;\n\t\tcase 0x7:\t/* Instrument functions */\n\t\t\te->fxt = FX_IT_INSTFUNC;\n\t\t\te->fxp &= 0x0f;\n\t\t\tbreak;\n\t\tcase 0x8:\t/* Set pan position */\n\t\t\te->fxt = FX_SETPAN;\n\t\t\te->fxp = l << 4;\n\t\t\tbreak;\n\t\tcase 0x9:\n\t\t\tif (l == 0 || l == 1) {\n\t\t\t\t/* 0x91 = set surround */\n\t\t\t\te->fxt = FX_SURROUND;\n\t\t\t\te->fxp = l;\n\t\t\t} else if (l == 0xe || l == 0xf) {\n\t\t\t\t/* 0x9f Play reverse (MPT) */\n\t\t\t\te->fxt = FX_REVERSE;\n\t\t\t\te->fxp = l - 0xe;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 0xa:\t/* High offset */\n\t\t\te->fxt = FX_HIOFFSET;\n\t\t\te->fxp = l;\n\t\t\tbreak;\n\t\tcase 0xb:\t/* Pattern loop */\n\t\t\te->fxp = 0x60 | l;\n\t\t\tbreak;\n\t\tcase 0xc:\t/* Note cut */\n\t\tcase 0xd:\t/* Note delay */\n\t\t\tif ((e->fxp = l) == 0)\n\t\t\t\te->fxp++;  /* SD0 and SC0 become SD1 and SC1 */\n\t\t\te->fxp |= h << 4;\n\t\t\tbreak;\n\t\tcase 0xe:\t/* Pattern row delay */\n\t\t\te->fxt = FX_IT_ROWDELAY;\n\t\t\te->fxp = l;\n\t\t\tbreak;\n\t\tcase 0xf:\t/* Set parametered macro */\n\t\t\te->fxt = FX_MACRO_SET;\n\t\t\te->fxp = l;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\te->fxt = e->fxp = 0;\n\t\t}\n\t\tbreak;\n\tcase FX_TREMOR:\n\t\tif (!new_fx && e->fxp != 0) {\n\t\t\te->fxp = ((MSN(e->fxp) + 1) << 4) | (LSN(e->fxp) + 1);\n\t\t}\n\t\tbreak;\n\tcase FX_GLOBALVOL:\n\t\tif (e->fxp > 0x80) {\t/* See storlek test 16 */\n\t\t\te->fxt = e->fxp = 0;\n\t\t}\n\t\tbreak;\n\tcase FX_NONE:\t\t/* No effect */\n\t\te->fxt = e->fxp = 0;\n\t\tbreak;\n\t}\n}\n\n\nstatic void xlat_volfx(struct xmp_event *event)\n{\n\tint b;\n\n\tb = event->vol;\n\tevent->vol = 0;\n\n\tif (b <= 0x40) {\n\t\tevent->vol = b + 1;\n\t} else if (b >= 65 && b <= 74) {\t/* A */\n\t\tevent->f2t = FX_F_VSLIDE_UP_2;\n\t\tevent->f2p = b - 65;\n\t} else if (b >= 75 && b <= 84) {\t/* B */\n\t\tevent->f2t = FX_F_VSLIDE_DN_2;\n\t\tevent->f2p = b - 75;\n\t} else if (b >= 85 && b <= 94) {\t/* C */\n\t\tevent->f2t = FX_VSLIDE_UP_2;\n\t\tevent->f2p = b - 85;\n\t} else if (b >= 95 && b <= 104) {\t/* D */\n\t\tevent->f2t = FX_VSLIDE_DN_2;\n\t\tevent->f2p = b - 95;\n\t} else if (b >= 105 && b <= 114) {\t/* E */\n\t\tevent->f2t = FX_PORTA_DN;\n\t\tevent->f2p = (b - 105) << 2;\n\t} else if (b >= 115 && b <= 124) {\t/* F */\n\t\tevent->f2t = FX_PORTA_UP;\n\t\tevent->f2p = (b - 115) << 2;\n\t} else if (b >= 128 && b <= 192) {\t/* pan */\n\t\tif (b == 192) {\n\t\t\tevent->f2p = 0xff;\n\t\t} else {\n\t\t\tevent->f2p = (b - 128) << 2;\n\t\t}\n\t\tevent->f2t = FX_SETPAN;\n\t} else if (b >= 193 && b <= 202) {\t/* G */\n\t\tuint8 val[10] = {\n\t\t\t0x00, 0x01, 0x04, 0x08, 0x10,\n\t\t\t0x20, 0x40, 0x60, 0x80, 0xff\n\t\t};\n\t\tevent->f2t = FX_TONEPORTA;\n\t\tevent->f2p = val[b - 193];\n\t} else if (b >= 203 && b <= 212) {\t/* H */\n\t\tevent->f2t = FX_VIBRATO;\n\t\tevent->f2p = b - 203;\n\t}\n}\n\n\nstatic void fix_name(uint8 *s, int l)\n{\n\tint i;\n\n\t/* IT names can have 0 at start of data, replace with space */\n\tfor (l--, i = 0; i < l; i++) {\n\t\tif (s[i] == 0)\n\t\t\ts[i] = ' ';\n\t}\n\tfor (i--; i >= 0 && s[i] == ' '; i--) {\n\t\tif (s[i] == ' ')\n\t\t\ts[i] = 0;\n\t}\n}\n\n\nstatic int load_it_midi_config(struct module_data *m, HIO_HANDLE *f)\n{\n\tint i;\n\n\tm->midi = (struct midi_macro_data *) calloc(1, sizeof(struct midi_macro_data));\n\tif (m->midi == NULL)\n\t\treturn -1;\n\n\t/* Skip global MIDI macros */\n\tif (hio_seek(f, 9 * 32, SEEK_CUR) < 0)\n\t\treturn -1;\n\n\t/* SFx macros */\n\tfor (i = 0; i < 16; i++) {\n\t\tif (hio_read(m->midi->param[i].data, 1, 32, f) < 32)\n\t\t\treturn -1;\n\t\tm->midi->param[i].data[31] = '\\0';\n\t}\n\t/* Zxx macros */\n\tfor (i = 0; i < 128; i++) {\n\t\tif (hio_read(m->midi->fixed[i].data, 1, 32, f) < 32)\n\t\t\treturn -1;\n\t\tm->midi->fixed[i].data[31] = '\\0';\n\t}\n\treturn 0;\n}\n\n\nstatic int read_envelope(struct xmp_envelope *ei, struct it_envelope *env,\n\t\t\t  HIO_HANDLE *f)\n{\n\tint i;\n\tuint8 buf[82];\n\n\tif (hio_read(buf, 1, 82, f) != 82) {\n\t\treturn -1;\n\t}\n\n\tenv->flg = buf[0];\n\tenv->num = MIN(buf[1], 25); /* Clamp to IT max */\n\n\tenv->lpb = buf[2];\n\tenv->lpe = buf[3];\n\tenv->slb = buf[4];\n\tenv->sle = buf[5];\n\n\tfor (i = 0; i < 25; i++) {\n\t\tenv->node[i].y = buf[6 + i * 3];\n\t\tenv->node[i].x = readmem16l(buf + 7 + i * 3);\n\t}\n\n\tei->flg = env->flg & IT_ENV_ON ? XMP_ENVELOPE_ON : 0;\n\n\tif (env->flg & IT_ENV_LOOP) {\n\t\tei->flg |= XMP_ENVELOPE_LOOP;\n\t}\n\n\tif (env->flg & IT_ENV_SLOOP) {\n\t\tei->flg |= XMP_ENVELOPE_SUS | XMP_ENVELOPE_SLOOP;\n\t}\n\n\tif (env->flg & IT_ENV_CARRY) {\n\t\tei->flg |= XMP_ENVELOPE_CARRY;\n\t}\n\n\tei->npt = env->num;\n\tei->sus = env->slb;\n\tei->sue = env->sle;\n\tei->lps = env->lpb;\n\tei->lpe = env->lpe;\n\n\tif (ei->npt > 0 && ei->npt <= 25 /* XMP_MAX_ENV_POINTS */) {\n\t\tfor (i = 0; i < ei->npt; i++) {\n\t\t\tei->data[i * 2] = env->node[i].x;\n\t\t\tei->data[i * 2 + 1] = env->node[i].y;\n\t\t}\n\t} else {\n\t\tei->flg &= ~XMP_ENVELOPE_ON;\n\t}\n\n\treturn 0;\n}\n\nstatic void identify_tracker(struct module_data *m, struct it_file_header *ifh,\n\t\t\t     int pat_before_smp, int *is_mpt_116)\n{\n#ifndef LIBXMP_CORE_PLAYER\n\tchar tracker_name[40];\n\tint sample_mode = ~ifh->flags & IT_USE_INST;\n\n\tm->flow_mode = FLOW_MODE_IT_210;\n\tswitch (ifh->cwt >> 8) {\n\tcase 0x00:\n\t\tstrcpy(tracker_name, \"unmo3\");\n\t\tbreak;\n\tcase 0x01:\n\tcase 0x02:\t\t/* test from Schism Tracker sources */\n\t\tif (ifh->cmwt == 0x0200 && ifh->cwt == 0x0214\n\t\t    && ifh->flags == 9 && ifh->special == 0\n\t\t    && ifh->hilite_maj == 0 && ifh->hilite_min == 0\n\t\t    && ifh->insnum == 0 && ifh->patnum + 1 == ifh->ordnum\n\t\t    && ifh->gv == 128 && ifh->mv == 100 && ifh->is == 1\n\t\t    && ifh->sep == 128 && ifh->pwd == 0\n\t\t    && ifh->msglen == 0 && ifh->msgofs == 0 && ifh->rsvd == 0) {\n\t\t\tstrcpy(tracker_name, \"OpenSPC conversion\");\n\t\t} else if (ifh->cmwt == 0x0200 && ifh->cwt == 0x0217) {\n\t\t\tstrcpy(tracker_name, \"ModPlug Tracker 1.16\");\n\t\t\t/* ModPlug Tracker files aren't really IMPM 2.00 */\n\t\t\tifh->cmwt = sample_mode ? 0x100 : 0x214;\n\t\t\tm->flow_mode = FLOW_MODE_MPT_116;\n\t\t\t*is_mpt_116 = 1;\n\t\t} else if (ifh->cmwt == 0x0200 && ifh->cwt == 0x0202 && pat_before_smp) {\n\t\t\t/* ModPlug Tracker ITs from pre-alpha 4 use tracker\n\t\t\t * 0x0202 and format 0x0200. Unfortunately, ITs from\n\t\t\t * Impulse Tracker may *also* use this. These MPT ITs\n\t\t\t * can be detected because they write patterns before\n\t\t\t * samples/instruments. */\n\t\t\tstrcpy(tracker_name, \"ModPlug Tracker 1.0 pre-alpha\");\n\t\t\tifh->cmwt = sample_mode ? 0x100 : 0x200;\n\t\t\t/* TODO: pre-alpha 4 has its own Pattern Loop behavior;\n\t\t\t * the <=1.16 behavior is present in pre-alpha 6. */\n\t\t\tm->flow_mode = FLOW_MODE_MPT_116;\n\t\t\t*is_mpt_116 = 1;\n\t\t} else if (ifh->cwt == 0x0216) {\n\t\t\tstrcpy(tracker_name, \"Impulse Tracker 2.14v3\");\n\t\t} else if (ifh->cwt == 0x0217) {\n\t\t\tstrcpy(tracker_name, \"Impulse Tracker 2.14v5\");\n\t\t} else if (ifh->cwt == 0x0214 && !memcmp(&ifh->rsvd, \"CHBI\", 4)) {\n\t\t\tstrcpy(tracker_name, \"Chibi Tracker\");\n\t\t} else {\n\t\t\tsnprintf(tracker_name, 40, \"Impulse Tracker %d.%02x\",\n\t\t\t\t (ifh->cwt & 0x0f00) >> 8, ifh->cwt & 0xff);\n\n\t\t\tif (ifh->cwt < 0x104) {\n\t\t\t\tm->flow_mode = FLOW_MODE_IT_100;\n\t\t\t} else if (ifh->cwt < 0x200) {\n\t\t\t\tm->flow_mode = FLOW_MODE_IT_104;\n\t\t\t} else if (ifh->cwt < 0x210) {\n\t\t\t\tm->flow_mode = FLOW_MODE_IT_200;\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase 0x08:\n\tcase 0x7f:\n\t\tif (ifh->cwt == 0x0888) {\n\t\t\tstrcpy(tracker_name, \"OpenMPT 1.17\");\n\t\t\t/* TODO: 1.17.02.49 onward implement IT 2.10+\n\t\t\t * Pattern Loop when the IT compatibility flag is set\n\t\t\t * (by default, it is not set). */\n\t\t\tm->flow_mode = FLOW_MODE_MPT_116;\n\t\t\t*is_mpt_116 = 1;\n\t\t} else if (ifh->cwt == 0x7fff) {\n\t\t\tstrcpy(tracker_name, \"munch.py\");\n\t\t} else {\n\t\t\tsnprintf(tracker_name, 40, \"unknown (%04x)\", ifh->cwt);\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tswitch (ifh->cwt >> 12) {\n\t\tcase 0x1:\n\t\t\tlibxmp_schism_tracker_string(tracker_name, 40,\n\t\t\t\t(ifh->cwt & 0x0fff), ifh->rsvd);\n\t\t\tbreak;\n\t\tcase 0x5:\n\t\t\tsnprintf(tracker_name, 40, \"OpenMPT %d.%02x\",\n\t\t\t\t (ifh->cwt & 0x0f00) >> 8, ifh->cwt & 0xff);\n\t\t\tif (memcmp(&ifh->rsvd, \"OMPT\", 4))\n\t\t\t\tstrncat(tracker_name, \" (compat.)\", 39);\n\t\t\tbreak;\n\t\tcase 0x06:\n\t\t\tsnprintf(tracker_name, 40, \"BeRoTracker %d.%02x\",\n\t\t\t\t (ifh->cwt & 0x0f00) >> 8, ifh->cwt & 0xff);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tsnprintf(tracker_name, 40, \"unknown (%04x)\", ifh->cwt);\n\t\t}\n\t}\n\n\tlibxmp_set_type(m, \"%s IT %d.%02x\", tracker_name, ifh->cmwt >> 8,\n\t\t\t\t\t\tifh->cmwt & 0xff);\n#else\n\tlibxmp_set_type(m, \"Impulse Tracker\");\n\tm->flow_mode = FLOW_MODE_IT_210;\n#endif\n}\n\nstatic int load_old_it_instrument(struct xmp_instrument *xxi, HIO_HANDLE *f)\n{\n\tint inst_map[120], inst_rmap[XMP_MAX_KEYS];\n\tstruct it_instrument1_header i1h;\n\tint c, k, j;\n\tuint8 buf[64];\n\n\tif (hio_read(buf, 1, 64, f) != 64) {\n\t\treturn -1;\n\t}\n\n\ti1h.magic = readmem32b(buf);\n\tif (i1h.magic != MAGIC_IMPI) {\n\t\tD_(D_CRIT \"bad instrument magic\");\n\t\treturn -1;\n\t}\n\tmemcpy(i1h.dosname, buf + 4, 12);\n\ti1h.zero = buf[16];\n\ti1h.flags = buf[17];\n\ti1h.vls = buf[18];\n\ti1h.vle = buf[19];\n\ti1h.sls = buf[20];\n\ti1h.sle = buf[21];\n\ti1h.fadeout = readmem16l(buf + 24);\n\ti1h.nna = buf[26];\n\ti1h.dnc = buf[27];\n\ti1h.trkvers = readmem16l(buf + 28);\n\ti1h.nos = buf[30];\n\n\tmemcpy(i1h.name, buf + 32, 26);\n\tfix_name(i1h.name, 26);\n\n\tif (hio_read(i1h.keys, 1, 240, f) != 240) {\n\t\treturn -1;\n\t}\n\tif (hio_read(i1h.epoint, 1, 200, f) != 200) {\n\t\treturn -1;\n\t}\n\tif (hio_read(i1h.enode, 1, 50, f) != 50) {\n\t\treturn -1;\n\t}\n\n\tlibxmp_copy_adjust(xxi->name, i1h.name, 25);\n\n\txxi->rls = i1h.fadeout << 7;\n\n\txxi->aei.flg = 0;\n\tif (i1h.flags & IT_ENV_ON) {\n\t\txxi->aei.flg |= XMP_ENVELOPE_ON;\n\t}\n\tif (i1h.flags & IT_ENV_LOOP) {\n\t\txxi->aei.flg |= XMP_ENVELOPE_LOOP;\n\t}\n\tif (i1h.flags & IT_ENV_SLOOP) {\n\t\txxi->aei.flg |= XMP_ENVELOPE_SUS | XMP_ENVELOPE_SLOOP;\n\t}\n\tif (i1h.flags & IT_ENV_CARRY) {\n\t\txxi->aei.flg |= XMP_ENVELOPE_SUS | XMP_ENVELOPE_CARRY;\n\t}\n\txxi->aei.lps = i1h.vls;\n\txxi->aei.lpe = i1h.vle;\n\txxi->aei.sus = i1h.sls;\n\txxi->aei.sue = i1h.sle;\n\n\tfor (k = 0; k < 25 && i1h.enode[k * 2] != 0xff; k++) ;\n\n\t/* Sanity check */\n\tif (k >= 25 || i1h.enode[k * 2] != 0xff) {\n\t\treturn -1;\n\t}\n\n\tfor (xxi->aei.npt = k; k--;) {\n\t\txxi->aei.data[k * 2] = i1h.enode[k * 2];\n\t\txxi->aei.data[k * 2 + 1] = i1h.enode[k * 2 + 1];\n\t}\n\n\t/* See how many different instruments we have */\n\tfor (j = 0; j < 120; j++)\n\t\tinst_map[j] = -1;\n\n\tfor (k = j = 0; j < XMP_MAX_KEYS; j++) {\n\t\tc = j < 120 ? i1h.keys[j * 2 + 1] - 1 : -1;\n\t\tif (c < 0 || c >= 120) {\n\t\t\txxi->map[j].ins = 0;\n\t\t\txxi->map[j].xpo = 0;\n\t\t\tcontinue;\n\t\t}\n\t\tif (inst_map[c] == -1) {\n\t\t\tinst_map[c] = k;\n\t\t\tinst_rmap[k] = c;\n\t\t\tk++;\n\t\t}\n\t\txxi->map[j].ins = inst_map[c];\n\t\txxi->map[j].xpo = i1h.keys[j * 2] - j;\n\t}\n\n\txxi->nsm = k;\n\txxi->vol = 0x40;\n\n\tif (k) {\n\t\txxi->sub = (struct xmp_subinstrument *) calloc(k, sizeof(struct xmp_subinstrument));\n\t\tif (xxi->sub == NULL) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tfor (j = 0; j < k; j++) {\n\t\t\tstruct xmp_subinstrument *sub = &xxi->sub[j];\n\n\t\t\tsub->sid = inst_rmap[j];\n\t\t\tsub->nna = i1h.nna;\n\t\t\tsub->dct =\n\t\t\t    i1h.dnc ? XMP_INST_DCT_NOTE : XMP_INST_DCT_OFF;\n\t\t\tsub->dca = XMP_INST_DCA_CUT;\n\t\t\tsub->pan = -1;\n\t\t}\n\t}\n\n\tD_(D_INFO \"[  ] %-26.26s %d %-4.4s %4d %2d %c%c%c %3d\",\n\t   /*i,*/ i1h.name,\n\t   i1h.nna,\n\t   i1h.dnc ? \"on\" : \"off\",\n\t   i1h.fadeout,\n\t   xxi->aei.npt,\n\t   xxi->aei.flg & XMP_ENVELOPE_ON ? 'V' : '-',\n\t   xxi->aei.flg & XMP_ENVELOPE_LOOP ? 'L' : '-',\n\t   xxi->aei.flg & XMP_ENVELOPE_SUS ? 'S' : '-', xxi->nsm);\n\n\treturn 0;\n}\n\nstatic int load_new_it_instrument(struct xmp_instrument *xxi, HIO_HANDLE *f)\n{\n\tint inst_map[120], inst_rmap[XMP_MAX_KEYS];\n\tstruct it_instrument2_header i2h;\n\tstruct it_envelope env;\n\tint dca2nna[] = { 0, 2, 3, 3 /* Northern Sky (cj-north.it) has this... */ };\n\tint c, k, j;\n\tuint8 buf[64];\n\n\tif (hio_read(buf, 1, 64, f) != 64) {\n\t\treturn -1;\n\t}\n\n\ti2h.magic = readmem32b(buf);\n\tif (i2h.magic != MAGIC_IMPI) {\n\t\tD_(D_CRIT \"bad instrument magic\");\n\t\treturn -1;\n\t}\n\tmemcpy(i2h.dosname, buf + 4, 12);\n\ti2h.zero = buf[16];\n\ti2h.nna = buf[17];\n\ti2h.dct = buf[18];\n\ti2h.dca = buf[19];\n\n\t/* Sanity check */\n\tif (i2h.dca > 3) {\n\t\t/* Northern Sky has an instrument with DCA 3 */\n\t\tD_(D_WARN \"bad instrument dca: %d\", i2h.dca);\n\t\ti2h.dca = 0;\n\t}\n\n\ti2h.fadeout = readmem16l(buf + 20);\n\ti2h.pps = buf[22];\n\ti2h.ppc = buf[23];\n\ti2h.gbv = buf[24];\n\ti2h.dfp = buf[25];\n\ti2h.rv = buf[26];\n\ti2h.rp = buf[27];\n\ti2h.trkvers = readmem16l(buf + 28);\n\ti2h.nos = buf[30];\n\n\tmemcpy(i2h.name, buf + 32, 26);\n\tfix_name(i2h.name, 26);\n\n\ti2h.ifc = buf[58];\n\ti2h.ifr = buf[59];\n\ti2h.mch = buf[60];\n\ti2h.mpr = buf[61];\n\ti2h.mbnk = readmem16l(buf + 62);\n\n\tif (hio_read(i2h.keys, 1, 240, f) != 240) {\n\t\tD_(D_CRIT \"key map read error\");\n\t\treturn -1;\n\t}\n\n\tlibxmp_copy_adjust(xxi->name, i2h.name, 25);\n\txxi->rls = i2h.fadeout << 6;\n\n\t/* Envelopes */\n\n\tif (read_envelope(&xxi->aei, &env, f) < 0) {\n\t\treturn -1;\n\t}\n\tif (read_envelope(&xxi->pei, &env, f) < 0) {\n\t\treturn -1;\n\t}\n\tif (read_envelope(&xxi->fei, &env, f) < 0) {\n\t\treturn -1;\n\t}\n\n\tif (xxi->pei.flg & XMP_ENVELOPE_ON) {\n\t\tfor (j = 0; j < xxi->pei.npt; j++)\n\t\t\txxi->pei.data[j * 2 + 1] += 32;\n\t}\n\n\tif (xxi->aei.flg & XMP_ENVELOPE_ON && xxi->aei.npt == 0) {\n\t\txxi->aei.npt = 1;\n\t}\n\tif (xxi->pei.flg & XMP_ENVELOPE_ON && xxi->pei.npt == 0) {\n\t\txxi->pei.npt = 1;\n\t}\n\tif (xxi->fei.flg & XMP_ENVELOPE_ON && xxi->fei.npt == 0) {\n\t\txxi->fei.npt = 1;\n\t}\n\n\tif (env.flg & IT_ENV_FILTER) {\n\t\txxi->fei.flg |= XMP_ENVELOPE_FLT;\n\t\tfor (j = 0; j < env.num; j++) {\n\t\t\txxi->fei.data[j * 2 + 1] += 32;\n\t\t\txxi->fei.data[j * 2 + 1] *= 4;\n\t\t}\n\t} else {\n\t\t/* Pitch envelope is *50 to get fine interpolation */\n\t\tfor (j = 0; j < env.num; j++)\n\t\t\txxi->fei.data[j * 2 + 1] *= 50;\n\t}\n\n\t/* See how many different instruments we have */\n\tfor (j = 0; j < 120; j++)\n\t\tinst_map[j] = -1;\n\n\tfor (k = j = 0; j < 120; j++) {\n\t\tc = i2h.keys[j * 2 + 1] - 1;\n\t\tif (c < 0 || c >= 120) {\n\t\t\txxi->map[j].ins = 0xff;\t/* No sample */\n\t\t\txxi->map[j].xpo = 0;\n\t\t\tcontinue;\n\t\t}\n\t\tif (inst_map[c] == -1) {\n\t\t\tinst_map[c] = k;\n\t\t\tinst_rmap[k] = c;\n\t\t\tk++;\n\t\t}\n\t\txxi->map[j].ins = inst_map[c];\n\t\txxi->map[j].xpo = i2h.keys[j * 2] - j;\n\t}\n\n\txxi->nsm = k;\n\txxi->vol = MIN(i2h.gbv, 128) >> 1;\n\n\tif (k) {\n\t\txxi->sub = (struct xmp_subinstrument *) calloc(k, sizeof(struct xmp_subinstrument));\n\t\tif (xxi->sub == NULL)\n\t\t\treturn -1;\n\n\t\tfor (j = 0; j < k; j++) {\n\t\t\tstruct xmp_subinstrument *sub = &xxi->sub[j];\n\n\t\t\tsub->sid = inst_rmap[j];\n\t\t\tsub->nna = i2h.nna;\n\t\t\tsub->dct = i2h.dct;\n\t\t\tsub->dca = dca2nna[i2h.dca];\n\t\t\tsub->pan = i2h.dfp & 0x80 ? -1 : i2h.dfp * 4;\n\t\t\tsub->ifc = i2h.ifc;\n\t\t\tsub->ifr = i2h.ifr;\n\t\t\tsub->rvv = ((int)i2h.rp << 8) | i2h.rv;\n\t\t}\n\t}\n\n\tD_(D_INFO \"[  ] %-26.26s %d %d %d %4d %4d  %2x \"\n\t   \"%02x %c%c%c %3d %02x %02x\",\n\t   /*i,*/ i2h.name,\n\t   i2h.nna, i2h.dct, i2h.dca,\n\t   i2h.fadeout,\n\t   i2h.gbv,\n\t   i2h.dfp & 0x80 ? 0x80 : i2h.dfp * 4,\n\t   i2h.rv,\n\t   xxi->aei.flg & XMP_ENVELOPE_ON ? 'V' : '-',\n\t   xxi->pei.flg & XMP_ENVELOPE_ON ? 'P' : '-',\n\t   env.flg & 0x01 ? env.flg & 0x80 ? 'F' : 'P' : '-',\n\t   xxi->nsm, i2h.ifc, i2h.ifr);\n\n\treturn 0;\n}\n\nstatic void force_sample_length(struct xmp_sample *xxs, struct extra_sample_data *xtra, int len)\n{\n\txxs->len = len;\n\n\tif (xxs->lpe > xxs->len)\n\t\txxs->lpe = xxs->len;\n\n\tif (xxs->lps >= xxs->len)\n\t\txxs->flg &= ~XMP_SAMPLE_LOOP;\n\n\tif (xtra) {\n\t\tif (xtra->sue > xxs->len)\n\t\t\txtra->sue = xxs->len;\n\n\t\tif(xtra->sus >= xxs->len)\n\t\t\txxs->flg &= ~(XMP_SAMPLE_SLOOP | XMP_SAMPLE_SLOOP_BIDIR);\n\t}\n}\n\nstatic void *unpack_it_sample(struct xmp_sample *xxs,\n\tconst struct it_sample_header *ish, uint8 *tmpbuf, HIO_HANDLE *f)\n{\n\tvoid *decbuf;\n\tint bytes = xxs->len;\n\tint channels = 1;\n\tint i;\n\n\tif (ish->flags & IT_SMP_16BIT)\n\t\tbytes <<= 1;\n\n\tif (ish->flags & IT_SMP_STEREO) {\n\t\tbytes <<= 1;\n\t\tchannels = 2;\n\t}\n\n\tdecbuf = calloc(1, bytes);\n\tif (decbuf == NULL)\n\t\treturn NULL;\n\n\tif (ish->flags & IT_SMP_16BIT) {\n\t\tint16 *pos = (int16 *)decbuf;\n\n\t\tfor (i = 0; i < channels; i++) {\n\t\t\titsex_decompress16(f, pos, xxs->len,\n\t\t\t\t\t   tmpbuf, TEMP_BUFFER_LEN,\n\t\t\t\t\t   ish->convert & IT_CVT_DIFF);\n\t\t\tpos += xxs->len;\n\t\t}\n\t} else {\n\t\tuint8 *pos = (uint8 *)decbuf;\n\n\t\tfor(i = 0; i < channels; i++) {\n\t\t\titsex_decompress8(f, pos, xxs->len,\n\t\t\t\t\t  tmpbuf, TEMP_BUFFER_LEN,\n\t\t\t\t\t  ish->convert & IT_CVT_DIFF);\n\t\t\tpos += xxs->len;\n\t\t}\n\t}\n\treturn decbuf;\n}\n\nstatic int load_it_sample(struct module_data *m, int i, int start,\n\t\t\t  int sample_mode, uint8 *tmpbuf, HIO_HANDLE *f)\n{\n\tstruct it_sample_header ish;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct extra_sample_data *xtra;\n\tstruct xmp_sample *xxs;\n\tint j, k;\n\tuint8 buf[80];\n\n\tif (sample_mode) {\n\t\tmod->xxi[i].sub = (struct xmp_subinstrument *) calloc(1, sizeof(struct xmp_subinstrument));\n\t\tif (mod->xxi[i].sub == NULL) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tif (hio_read(buf, 1, 80, f) != 80) {\n\t\treturn -1;\n\t}\n\n\tish.magic = readmem32b(buf);\n\t/* Changed to continue to allow use-brdg.it and use-funk.it to\n\t * load correctly (both IT 2.04)\n\t */\n\tif (ish.magic != MAGIC_IMPS) {\n\t\treturn 0;\n\t}\n\n\txxs = &mod->xxs[i];\n\txtra = &m->xtra[i];\n\n\tmemcpy(ish.dosname, buf + 4, 12);\n\tish.zero = buf[16];\n\tish.gvl = buf[17];\n\tish.flags = buf[18];\n\tish.vol = buf[19];\n\n\tmemcpy(ish.name, buf + 20, 26);\n\tfix_name(ish.name, 26);\n\n\tish.convert = buf[46];\n\tish.dfp = buf[47];\n\tish.length = readmem32l(buf + 48);\n\tish.loopbeg = readmem32l(buf + 52);\n\tish.loopend = readmem32l(buf + 56);\n\tish.c5spd = readmem32l(buf + 60);\n\tish.sloopbeg = readmem32l(buf + 64);\n\tish.sloopend = readmem32l(buf + 68);\n\tish.sample_ptr = readmem32l(buf + 72);\n\tish.vis = buf[76];\n\tish.vid = buf[77];\n\tish.vir = buf[78];\n\tish.vit = buf[79];\n\n\tif (ish.flags & IT_SMP_16BIT) {\n\t\txxs->flg = XMP_SAMPLE_16BIT;\n\t}\n\tif (ish.flags & IT_SMP_STEREO) {\n\t\txxs->flg |= XMP_SAMPLE_STEREO;\n\t}\n\txxs->len = ish.length;\n\n\txxs->lps = ish.loopbeg;\n\txxs->lpe = ish.loopend;\n\txxs->flg |= ish.flags & IT_SMP_LOOP ? XMP_SAMPLE_LOOP : 0;\n\txxs->flg |= ish.flags & IT_SMP_BLOOP ? XMP_SAMPLE_LOOP_BIDIR : 0;\n\txxs->flg |= ish.flags & IT_SMP_SLOOP ? XMP_SAMPLE_SLOOP : 0;\n\txxs->flg |= ish.flags & IT_SMP_BSLOOP ? XMP_SAMPLE_SLOOP_BIDIR : 0;\n\n\tif (ish.flags & IT_SMP_SLOOP) {\n\t\txtra->sus = ish.sloopbeg;\n\t\txtra->sue = ish.sloopend;\n\t}\n\n\tif (sample_mode) {\n\t\t/* Create an instrument for each sample */\n\t\tmod->xxi[i].vol = 64;\n\t\tmod->xxi[i].sub[0].vol = ish.vol;\n\t\tmod->xxi[i].sub[0].pan = 0x80;\n\t\tmod->xxi[i].sub[0].sid = i;\n\t\tmod->xxi[i].nsm = !!(xxs->len);\n\t\tlibxmp_instrument_name(mod, i, ish.name, 25);\n\t} else {\n\t\tlibxmp_copy_adjust(xxs->name, ish.name, 25);\n\t}\n\n\tD_(D_INFO \"\\n[%2X] %-26.26s %05x%c%c %05x %05x %05x %05x \"\n\t   \"%02x%02x %02x%02x %5d \",\n\t   i, sample_mode ? xxs->name : mod->xxs[i].name,\n\t   xxs->len,\n\t   ish.flags & IT_SMP_16BIT ? '+' : ' ',\n\t   ish.flags & IT_SMP_STEREO ? 's' : ' ',\n\t   MIN(xxs->lps, 0xfffff), MIN(xxs->lpe, 0xfffff),\n\t   MIN(ish.sloopbeg, 0xfffff), MIN(ish.sloopend, 0xfffff),\n\t   ish.flags, ish.convert, ish.vol, ish.gvl, ish.c5spd);\n\n\t/* Convert C5SPD to relnote/finetune\n\t *\n\t * In IT we can have a sample associated with two or more\n\t * instruments, but c5spd is a sample attribute -- so we must\n\t * scan all xmp instruments to set the correct transposition\n\t */\n\n\tfor (j = 0; j < mod->ins; j++) {\n\t\tfor (k = 0; k < mod->xxi[j].nsm; k++) {\n\t\t\tstruct xmp_subinstrument *sub = &mod->xxi[j].sub[k];\n\t\t\tif (sub->sid == i) {\n\t\t\t\tsub->vol = ish.vol;\n\t\t\t\tsub->gvl = MIN(ish.gvl, 64);\n\t\t\t\tsub->vra = ish.vis;\t/* sample to sub-instrument vibrato */\n\t\t\t\tsub->vde = ish.vid << 1;\n\t\t\t\tsub->vwf = ish.vit;\n\t\t\t\tsub->vsw = (0xff - ish.vir) >> 1;\n\n\t\t\t\tlibxmp_c2spd_to_note(ish.c5spd,\n\t\t\t\t\t      &mod->xxi[j].sub[k].xpo,\n\t\t\t\t\t      &mod->xxi[j].sub[k].fin);\n\n\t\t\t\t/* Set sample pan (overrides subinstrument) */\n\t\t\t\tif (ish.dfp & 0x80) {\n\t\t\t\t\tsub->pan = (ish.dfp & 0x7f) * 4;\n\t\t\t\t} else if (sample_mode) {\n\t\t\t\t\tsub->pan = -1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (ish.flags & IT_SMP_SAMPLE && xxs->len > 1) {\n\t\tint cvt = 0;\n\n\t\t/* Sanity check - some modules may have invalid sizes on\n\t\t * unused samples so only check this if the sample flag is set. */\n\t\tif (xxs->len > MAX_SAMPLE_SIZE) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (0 != hio_seek(f, start + ish.sample_ptr, SEEK_SET))\n\t\t\treturn -1;\n\n\t\tif (xxs->lpe > xxs->len || xxs->lps >= xxs->lpe)\n\t\t\txxs->flg &= ~XMP_SAMPLE_LOOP;\n\n\t\tif (ish.convert == IT_CVT_ADPCM)\n\t\t\tcvt |= SAMPLE_FLAG_ADPCM;\n\n\t\tif (~ish.convert & IT_CVT_SIGNED)\n\t\t\tcvt |= SAMPLE_FLAG_UNS;\n\n\t\t/* compressed samples */\n\t\tif (ish.flags & IT_SMP_COMP) {\n\t\t\tlong min_size, file_len, left;\n\t\t\tvoid *decbuf;\n\t\t\tint samples = xxs->len;\n\t\t\tint ret;\n\n\t\t\tif (ish.flags & IT_SMP_STEREO)\n\t\t\t\tsamples <<= 1;\n\n\t\t\t/* Sanity check - the lower bound on IT compressed\n\t\t\t * sample size (in bytes) is a little over 1/8th of the\n\t\t\t * number of SAMPLES in the sample.\n\t\t\t */\n\t\t\tfile_len = hio_size(f);\n\t\t\tmin_size = samples >> 3;\n\t\t\tleft = file_len - (long)ish.sample_ptr;\n\t\t\t/* No data to read at all? Just skip it... */\n\t\t\tif (left <= 0)\n\t\t\t\treturn 0;\n\n\t\t\tif ((file_len > 0) && (left < min_size)) {\n\t\t\t\tD_(D_WARN \"sample %X failed minimum size check \"\n\t\t\t\t   \"(len=%d, needs >=%ld bytes, %ld available): \"\n\t\t\t\t   \"resizing to %ld\",\n\t\t\t\t   i, xxs->len, min_size, left, left << 3);\n\n\t\t\t\tforce_sample_length(xxs, xtra, left << 3);\n\t\t\t}\n\n\t\t\tdecbuf = unpack_it_sample(xxs, &ish, tmpbuf, f);\n\t\t\tif (decbuf == NULL)\n\t\t\t\treturn -1;\n\n#ifdef WORDS_BIGENDIAN\n\t\t\tif (ish.flags & IT_SMP_16BIT) {\n\t\t\t\t/* decompression generates native-endian\n\t\t\t\t * samples, but we want little-endian.\n\t\t\t\t */\n\t\t\t\tcvt |= SAMPLE_FLAG_BIGEND;\n\t\t\t}\n#endif\n\n\t\t\tret = libxmp_load_sample(m, NULL, SAMPLE_FLAG_NOLOAD | cvt,\n\t\t\t\t\t  &mod->xxs[i], decbuf);\n\t\t\tif (ret < 0) {\n\t\t\t\tfree(decbuf);\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tfree(decbuf);\n\t\t} else {\n\t\t\tif (libxmp_load_sample(m, f, cvt, &mod->xxs[i], NULL) < 0)\n\t\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int load_it_pattern(struct module_data *m, int i, int new_fx,\n\t\t\t   uint8 *patbuf, HIO_HANDLE *f)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_event *event, dummy, lastevent[L_CHANNELS];\n\tuint8 mask[L_CHANNELS];\n\tuint8 last_fxp[64];\n\tuint8 *pos;\n\n\tint r, c, pat_len, num_rows;\n\tuint8 b;\n\n\tr = 0;\n\n\tmemset(last_fxp, 0, sizeof(last_fxp));\n\tmemset(lastevent, 0, L_CHANNELS * sizeof(struct xmp_event));\n\tmemset(&dummy, 0, sizeof(struct xmp_event));\n\n\tpat_len = hio_read16l(f) /* - 4 */ ;\n\tmod->xxp[i]->rows = num_rows = hio_read16l(f);\n\n\tif (libxmp_alloc_tracks_in_pattern(mod, i) < 0) {\n\t\treturn -1;\n\t}\n\n\tmemset(mask, 0, L_CHANNELS);\n\thio_read16l(f);\n\thio_read16l(f);\n\n\tif (hio_read(patbuf, 1, pat_len, f) < (size_t)pat_len) {\n\t\tD_(D_CRIT \"read error loading pattern %d\", i);\n\t\treturn -1;\n\t}\n\tpos = patbuf;\n\n\twhile (r < num_rows && --pat_len >= 0) {\n\t\tb = *(pos++);\n\t\tif (!b) {\n\t\t\tr++;\n\t\t\tcontinue;\n\t\t}\n\t\tc = (b - 1) & 63;\n\n\t\tif (b & 0x80) {\n\t\t\tif (pat_len < 1) break;\n\t\t\tmask[c] = *(pos++);\n\t\t\tpat_len--;\n\t\t}\n\t\t/*\n\t\t * WARNING: we IGNORE events in disabled channels. Disabled\n\t\t * channels should be muted only, but we don't know the\n\t\t * real number of channels before loading the patterns and\n\t\t * we don't want to set it to 64 channels.\n\t\t */\n\t\tif (c >= mod->chn) {\n\t\t\tevent = &dummy;\n\t\t} else {\n\t\t\tevent = &EVENT(i, c, r);\n\t\t}\n\n\t\tif (mask[c] & 0x01) {\n\t\t\tif (pat_len < 1) break;\n\t\t\tb = *(pos++);\n\n\t\t\t/* From ittech.txt:\n\t\t\t * Note ranges from 0->119 (C-0 -> B-9)\n\t\t\t * 255 = note off, 254 = notecut\n\t\t\t * Others = note fade (already programmed into IT's player\n\t\t\t *                     but not available in the editor)\n\t\t\t */\n\t\t\tswitch (b) {\n\t\t\tcase 0xff:\t/* key off */\n\t\t\t\tb = XMP_KEY_OFF;\n\t\t\t\tbreak;\n\t\t\tcase 0xfe:\t/* cut */\n\t\t\t\tb = XMP_KEY_CUT;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tif (b > 119) {\t/* fade */\n\t\t\t\t\tb = XMP_KEY_FADE;\n\t\t\t\t} else {\n\t\t\t\t\tb++;\t/* note */\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastevent[c].note = event->note = b;\n\t\t\tpat_len--;\n\t\t}\n\t\tif (mask[c] & 0x02) {\n\t\t\tif (pat_len < 1) break;\n\t\t\tb = *(pos++);\n\t\t\tlastevent[c].ins = event->ins = b;\n\t\t\tpat_len--;\n\t\t}\n\t\tif (mask[c] & 0x04) {\n\t\t\tif (pat_len < 1) break;\n\t\t\tb = *(pos++);\n\t\t\tlastevent[c].vol = event->vol = b;\n\t\t\txlat_volfx(event);\n\t\t\tpat_len--;\n\t\t}\n\t\tif (mask[c] & 0x08) {\n\t\t\tif (pat_len < 2) break;\n\t\t\tb = *(pos++);\n\t\t\tif (b >= ARRAY_SIZE(fx)) {\n\t\t\t\tD_(D_WARN \"invalid effect %#02x\", b);\n\t\t\t\tpos++;\n\n\t\t\t} else {\n\t\t\t\tevent->fxt = b;\n\t\t\t\tevent->fxp = *(pos++);\n\n\t\t\t\txlat_fx(c, event, last_fxp, new_fx);\n\t\t\t\tlastevent[c].fxt = event->fxt;\n\t\t\t\tlastevent[c].fxp = event->fxp;\n\t\t\t}\n\t\t\tpat_len -= 2;\n\t\t}\n\t\tif (mask[c] & 0x10) {\n\t\t\tevent->note = lastevent[c].note;\n\t\t}\n\t\tif (mask[c] & 0x20) {\n\t\t\tevent->ins = lastevent[c].ins;\n\t\t}\n\t\tif (mask[c] & 0x40) {\n\t\t\tevent->vol = lastevent[c].vol;\n\t\t\txlat_volfx(event);\n\t\t}\n\t\tif (mask[c] & 0x80) {\n\t\t\tevent->fxt = lastevent[c].fxt;\n\t\t\tevent->fxp = lastevent[c].fxp;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int it_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint c, i, j;\n\tstruct it_file_header ifh;\n\tint max_ch;\n\tuint32 *pp_ins;\t\t/* Pointers to instruments */\n\tuint32 *pp_smp;\t\t/* Pointers to samples */\n\tuint32 *pp_pat;\t\t/* Pointers to patterns */\n\tuint8 *patbuf = NULL;\n\tuint8 *pos;\n\tint new_fx, sample_mode;\n\tint pat_before_smp = 0;\n\tint is_mpt_116 = 0;\n\n\tLOAD_INIT();\n\n\t/* Load and convert header */\n\tifh.magic = hio_read32b(f);\n\tif (ifh.magic != MAGIC_IMPM) {\n\t\treturn -1;\n\t}\n\n\thio_read(ifh.name, 26, 1, f);\n\tifh.hilite_min = hio_read8(f);\n\tifh.hilite_maj = hio_read8(f);\n\n\tifh.ordnum = hio_read16l(f);\n\tifh.insnum = hio_read16l(f);\n\tifh.smpnum = hio_read16l(f);\n\tifh.patnum = hio_read16l(f);\n\n\tifh.cwt = hio_read16l(f);\n\tifh.cmwt = hio_read16l(f);\n\tifh.flags = hio_read16l(f);\n\tifh.special = hio_read16l(f);\n\n\tifh.gv = hio_read8(f);\n\tifh.mv = hio_read8(f);\n\tifh.is = hio_read8(f);\n\tifh.it = hio_read8(f);\n\tifh.sep = hio_read8(f);\n\tifh.pwd = hio_read8(f);\n\n\t/* Sanity check */\n\tif (ifh.gv > 0x80) {\n\t\tD_(D_CRIT \"invalid gv (%u)\", ifh.gv);\n\t\tgoto err;\n\t}\n\n\tifh.msglen = hio_read16l(f);\n\tifh.msgofs = hio_read32l(f);\n\tifh.rsvd = hio_read32l(f);\n\n\thio_read(ifh.chpan, 64, 1, f);\n\thio_read(ifh.chvol, 64, 1, f);\n\n\tif (hio_error(f)) {\n\t\tD_(D_CRIT \"error reading IT header\");\n\t\tgoto err;\n\t}\n\n\tmemcpy(mod->name, ifh.name, sizeof(ifh.name));\n\t/* sizeof(ifh.name) == 26, sizeof(mod->name) == 64. */\n\tmod->name[sizeof(ifh.name)] = '\\0';\n\tmod->len = ifh.ordnum;\n\tmod->ins = ifh.insnum;\n\tmod->smp = ifh.smpnum;\n\tmod->pat = ifh.patnum;\n\n\t/* Sanity check */\n\tif (mod->ins > 255 || mod->smp > 255 || mod->pat > 255) {\n\t\tD_(D_CRIT \"invalid ins (%u), smp (%u), or pat (%u)\",\n\t\t   mod->ins, mod->smp, mod->pat);\n\t\tgoto err;\n\t}\n\n\tif (mod->ins) {\n\t\tpp_ins = (uint32 *) calloc(4, mod->ins);\n\t\tif (pp_ins == NULL)\n\t\t\tgoto err;\n\t} else {\n\t\tpp_ins = NULL;\n\t}\n\n\tpp_smp = (uint32 *) calloc(4, mod->smp);\n\tif (pp_smp == NULL)\n\t\tgoto err2;\n\n\tpp_pat = (uint32 *) calloc(4, mod->pat);\n\tif (pp_pat == NULL)\n\t\tgoto err3;\n\n\tmod->spd = ifh.is;\n\tmod->bpm = ifh.it;\n\n\tsample_mode = ~ifh.flags & IT_USE_INST;\n\n\tif (ifh.flags & IT_LINEAR_FREQ) {\n\t\tm->period_type = PERIOD_LINEAR;\n\t}\n\n\tfor (i = 0; i < 64; i++) {\n\t\tstruct xmp_channel *xxc = &mod->xxc[i];\n\n\t\tif (ifh.chpan[i] == 100) {\t/* Surround -> center */\n\t\t\txxc->flg |= XMP_CHANNEL_SURROUND;\n\t\t}\n\n\t\tif (ifh.chpan[i] & 0x80) {\t/* Channel mute */\n\t\t\txxc->flg |= XMP_CHANNEL_MUTE;\n\t\t}\n\n\t\tif (ifh.flags & IT_STEREO) {\n\t\t\txxc->pan = (int)ifh.chpan[i] * 0x80 >> 5;\n\t\t\tif (xxc->pan > 0xff)\n\t\t\t\txxc->pan = 0xff;\n\t\t} else {\n\t\t\txxc->pan = 0x80;\n\t\t}\n\n\t\txxc->vol = ifh.chvol[i];\n\t}\n\n\tif (mod->len <= XMP_MAX_MOD_LENGTH) {\n\t\thio_read(mod->xxo, 1, mod->len, f);\n\t} else {\n\t\thio_read(mod->xxo, 1, XMP_MAX_MOD_LENGTH, f);\n\t\thio_seek(f, mod->len - XMP_MAX_MOD_LENGTH, SEEK_CUR);\n\t\tmod->len = XMP_MAX_MOD_LENGTH;\n\t}\n\n\tnew_fx = ifh.flags & IT_OLD_FX ? 0 : 1;\n\n\tfor (i = 0; i < mod->ins; i++)\n\t\tpp_ins[i] = hio_read32l(f);\n\tfor (i = 0; i < mod->smp; i++)\n\t\tpp_smp[i] = hio_read32l(f);\n\tfor (i = 0; i < mod->pat; i++)\n\t\tpp_pat[i] = hio_read32l(f);\n\n\t/* Skip edit history if it exists. */\n\tif (ifh.special & IT_EDIT_HISTORY) {\n\t\tint skip = hio_read16l(f) * 8;\n\t\tif (hio_error(f) || (skip && hio_seek(f, skip, SEEK_CUR) < 0))\n\t\t\tgoto err4;\n\t}\n\n\tif ((ifh.flags & IT_MIDI_CONFIG) || (ifh.special & IT_SPEC_MIDICFG)) {\n\t\tif (load_it_midi_config(m, f) < 0)\n\t\t\tgoto err4;\n\t}\n\tif (mod->smp && mod->pat && pp_pat[0] != 0 && pp_pat[0] < pp_smp[0])\n\t\tpat_before_smp = 1;\n\n\tm->c4rate = C4_NTSC_RATE;\n\n\tidentify_tracker(m, &ifh, pat_before_smp, &is_mpt_116);\n\n\tMODULE_INFO();\n\n\tD_(D_INFO \"Instrument/FX mode: %s/%s\",\n\t   sample_mode ? \"sample\" : ifh.cmwt >= 0x200 ?\n\t   \"new\" : \"old\", ifh.flags & IT_OLD_FX ? \"old\" : \"IT\");\n\n\tif (sample_mode)\n\t\tmod->ins = mod->smp;\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\tgoto err4;\n\n\tD_(D_INFO \"Instruments: %d\", mod->ins);\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\t/*\n\t\t * IT files can have three different instrument types: 'New'\n\t\t * instruments, 'old' instruments or just samples. We need a\n\t\t * different loader for each of them.\n\t\t */\n\n\t\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\n\t\tif (!sample_mode && ifh.cmwt >= 0x200) {\n\t\t\t/* New instrument format */\n\t\t\tif (hio_seek(f, start + pp_ins[i], SEEK_SET) < 0) {\n\t\t\t\tgoto err4;\n\t\t\t}\n\n\t\t\tif (load_new_it_instrument(xxi, f) < 0) {\n\t\t\t\tgoto err4;\n\t\t\t}\n\n\t\t} else if (!sample_mode) {\n\t\t\t/* Old instrument format */\n\t\t\tif (hio_seek(f, start + pp_ins[i], SEEK_SET) < 0) {\n\t\t\t\tgoto err4;\n\t\t\t}\n\n\t\t\tif (load_old_it_instrument(xxi, f) < 0) {\n\t\t\t\tgoto err4;\n\t\t\t}\n\t\t}\n\t}\n\n\tD_(D_INFO \"Stored Samples: %d\", mod->smp);\n\n\t/* This buffer should be able to hold any pattern or sample block.\n\t * Round up to a multiple of 4--the sample decompressor relies on\n\t * this to simplify its code.\n\t */\n\tif ((patbuf = (uint8 *)malloc(TEMP_BUFFER_LEN)) == NULL) {\n\t\tD_(D_CRIT \"failed to allocate temporary buffer\");\n\t\tgoto err4;\n\t}\n\n\tfor (i = 0; i < mod->smp; i++) {\n\n\t\tif (hio_seek(f, start + pp_smp[i], SEEK_SET) < 0) {\n\t\t\tgoto err4;\n\t\t}\n\n\t\tif (load_it_sample(m, i, start, sample_mode, patbuf, f) < 0) {\n\t\t\tgoto err4;\n\t\t}\n\t}\n\t/* Reset any error status set by truncated samples. */\n\thio_error(f);\n\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\t/* Effects in muted channels are processed, so scan patterns first to\n\t * see the real number of channels\n\t */\n\tmax_ch = 0;\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tuint8 mask[L_CHANNELS];\n\t\tint pat_len, num_rows, row;\n\n\t\t/* If the offset to a pattern is 0, the pattern is empty */\n\t\tif (pp_pat[i] == 0)\n\t\t\tcontinue;\n\n\t\thio_seek(f, start + pp_pat[i], SEEK_SET);\n\t\tpat_len = hio_read16l(f) /* - 4 */ ;\n\t\tnum_rows = hio_read16l(f);\n\t\tmemset(mask, 0, L_CHANNELS);\n\t\thio_read16l(f);\n\t\thio_read16l(f);\n\n\t\t/* Sanity check:\n\t\t * - Impulse Tracker and Schism Tracker allow up to 200 rows.\n\t\t * - ModPlug Tracker 1.16 allows 256 rows.\n\t\t * - OpenMPT allows 1024 rows.\n\t\t */\n\t\tif (num_rows > 1024) {\n\t\t\tD_(D_WARN \"skipping pattern %d (%d rows)\", i, num_rows);\n\t\t\tpp_pat[i] = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (hio_read(patbuf, 1, pat_len, f) < (size_t)pat_len) {\n\t\t\tD_(D_CRIT \"error scanning pattern %d\", i);\n\t\t\tgoto err4;\n\t\t}\n\t\tpos = patbuf;\n\n\t\trow = 0;\n\t\twhile (row < num_rows && --pat_len >= 0) {\n\t\t\tint b = *(pos++);\n\t\t\tif (b == 0) {\n\t\t\t\trow++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tc = (b - 1) & 63;\n\n\t\t\tif (c > max_ch)\n\t\t\t\tmax_ch = c;\n\n\t\t\tif (b & 0x80) {\n\t\t\t\tif (pat_len < 1) break;\n\t\t\t\tmask[c] = *(pos++);\n\t\t\t\tpat_len--;\n\t\t\t}\n\n\t\t\tif (mask[c] & 0x01) {\n\t\t\t\tpos++;\n\t\t\t\tpat_len--;\n\t\t\t}\n\t\t\tif (mask[c] & 0x02) {\n\t\t\t\tpos++;\n\t\t\t\tpat_len--;\n\t\t\t}\n\t\t\tif (mask[c] & 0x04) {\n\t\t\t\tpos++;\n\t\t\t\tpat_len--;\n\t\t\t}\n\t\t\tif (mask[c] & 0x08) {\n\t\t\t\tpos += 2;\n\t\t\t\tpat_len -= 2;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Set the number of channels actually used\n\t */\n\tmod->chn = max_ch + 1;\n\tmod->trk = mod->pat * mod->chn;\n\n\tif (libxmp_init_pattern(mod) < 0) {\n\t\tgoto err4;\n\t}\n\n\t/* Read patterns */\n\tfor (i = 0; i < mod->pat; i++) {\n\n\t\tif (libxmp_alloc_pattern(mod, i) < 0) {\n\t\t\tgoto err4;\n\t\t}\n\n\t\t/* If the offset to a pattern is 0, the pattern is empty */\n\t\tif (pp_pat[i] == 0) {\n\t\t\tmod->xxp[i]->rows = 64;\n\t\t\tfor (j = 0; j < mod->chn; j++) {\n\t\t\t\tint tnum = i * mod->chn + j;\n\t\t\t\tif (libxmp_alloc_track(mod, tnum, 64) < 0)\n\t\t\t\t\tgoto err4;\n\t\t\t\tmod->xxp[i]->index[j] = tnum;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (hio_seek(f, start + pp_pat[i], SEEK_SET) < 0) {\n\t\t\tD_(D_CRIT \"error seeking to %d\", start + pp_pat[i]);\n\t\t\tgoto err4;\n\t\t}\n\n\t\tif (load_it_pattern(m, i, new_fx, patbuf, f) < 0) {\n\t\t\tD_(D_CRIT \"error loading pattern %d\", i);\n\t\t\tgoto err4;\n\t\t}\n\t}\n\n\tfree(patbuf);\n\tfree(pp_pat);\n\tfree(pp_smp);\n\tfree(pp_ins);\n\n\t/* Song message */\n\tif ((ifh.special & IT_HAS_MSG) && ifh.msglen > 0) {\n\t\tif ((m->comment = (char *)malloc(ifh.msglen)) != NULL) {\n\t\t\thio_seek(f, start + ifh.msgofs, SEEK_SET);\n\n\t\t\tD_(D_INFO \"Message length : %d\", ifh.msglen);\n\n\t\t\tifh.msglen = hio_read(m->comment, 1, ifh.msglen, f);\n\t\t\thio_error(f); /* Clear error if any */\n\n\t\t\tfor (j = 0; j + 1 < ifh.msglen; j++) {\n\t\t\t\tint b = m->comment[j];\n\t\t\t\tif (b == '\\r') {\n\t\t\t\t\tm->comment[j] = '\\n';\n\t\t\t\t} else if ((b < 32 || b > 127) && b != '\\n'\n\t\t\t\t\t   && b != '\\t') {\n\t\t\t\t\tm->comment[j] = '.';\n\t\t\t\t}\n\t\t\t}\n\t\t\tm->comment[j] = 0;\n\t\t}\n\t}\n\n\t/* Format quirks */\n\n\tm->quirk |= QUIRKS_IT | QUIRK_ARPMEM | QUIRK_INSVOL;\n\n\tif (ifh.flags & IT_LINK_GXX) {\n\t\tm->quirk |= QUIRK_PRENV;\n\t} else {\n\t\tm->quirk |= QUIRK_UNISLD;\n\t}\n\n\tif (new_fx) {\n\t\tm->quirk |= QUIRK_VIBHALF | QUIRK_VIBINV;\n\t} else {\n\t\tm->quirk &= ~QUIRK_VIBALL;\n\t\tm->quirk |= QUIRK_ITOLDFX;\n\t}\n\n\tif (sample_mode) {\n\t\tm->quirk &= ~(QUIRK_VIRTUAL | QUIRK_RSTCHN);\n\t}\n\n\tm->gvolbase = 0x80;\n\tm->gvol = ifh.gv;\n\tm->mvolbase = 48;\n\tm->mvol = ifh.mv;\n\tm->read_event_type = READ_EVENT_IT;\n\n#ifndef LIBXMP_CORE_PLAYER\n\tif (is_mpt_116)\n\t\tlibxmp_apply_mpt_preamp(m);\n#endif\n\n\treturn 0;\n\nerr4:\n\tfree(patbuf);\n\tfree(pp_pat);\nerr3:\n\tfree(pp_smp);\nerr2:\n\tfree(pp_ins);\nerr:\n\treturn -1;\n}\n\n#endif /* LIBXMP_CORE_DISABLE_IT */\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/itsex.c",
    "content": "#include \"../common.h\"\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\n/* Public domain IT sample decompressor by Olivier Lapicque */\n\n/* Modified by Alice Rowan (2023)- more or less complete rewrite of the input\n * stream to add buffering.\n */\n\n#include \"loader.h\"\n#include \"it.h\"\n\n#define READ_BITS_MASK(n) ((1u << (unsigned)(n)) - 1u)\n\nstruct it_stream\n{\n\tuint8 *pos;\n\tsize_t left;\n\tuint32 bits;\n\tint num_bits;\n\tint err;\n};\n\nstatic inline uint32 read_bits(struct it_stream *in, int n)\n{\n\tuint32 retval = 0;\n\n\tif (n <= 0 || n >= 32) {\n\t\t/* Invalid shift value. */\n\t\tin->err = -2;\n\t\treturn 0;\n\t}\n\n\tretval = in->bits & READ_BITS_MASK(n);\n\n\tif (in->num_bits < n) {\n\t\tuint32 offset = in->num_bits;\n\t\tuint32 used;\n\n\t\tif (in->left == 0) {\n\t\t\tin->err = EOF;\n\t\t\treturn 0;\n\t\t}\n\t\t/* Buffer should be zero-padded to 4-byte alignment. */\n\t\tin->bits = in->pos[0] |\n\t\t\t   (in->pos[1] << 8) |\n\t\t\t   (in->pos[2] << 16) |\n\t\t\t   ((uint32)in->pos[3] << 24);\n\n\t\tused = MIN(in->left, 4);\n\n\t\tin->num_bits = used * 8;\n\t\tin->pos += 4;\n\t\tin->left -= used;\n\n\t\tn -= offset;\n\t\tretval |= (in->bits & READ_BITS_MASK(n)) << offset;\n\t}\n\n\tin->bits >>= n;\n\tin->num_bits -= n;\n\n\treturn retval;\n}\n\nstatic inline int init_block(struct it_stream *in, uint8 *tmp, int tmplen,\n\t\t\t     HIO_HANDLE *src)\n{\n\tsize_t i;\n\tin->pos = tmp;\n\tin->left = hio_read16l(src);\n\tin->bits = 0;\n\tin->num_bits = 0;\n\tin->err = 0;\n\n\t/* tmp should be INT16_MAX rounded up to a multiple of 4 bytes long. */\n\tif (tmplen < (int)((in->left + 4) & ~3))\n\t\treturn -1;\n\tif (hio_read(tmp, 1, in->left, src) < in->left)\n\t\treturn -1;\n\n\t/* Zero pad to a multiple of 4 bytes for read_bits. */\n\tfor (i = in->left; i & 3; i++)\n\t\ttmp[i] = 0;\n\n\treturn 0;\n}\n\nint itsex_decompress8(HIO_HANDLE *src, uint8 *dst, int len,\n\t\t      uint8 *tmp, int tmplen, int it215)\n{\n\tstruct it_stream in;\n\tuint32 block_count = 0;\n\tuint8 left = 0, temp = 0, temp2 = 0;\n\tuint32 d, pos;\n\n\tmemset(&in, 0, sizeof(in)); /* bogus GCC 12 -Wmaybe-uninitialized */\n\n\twhile (len) {\n\t\tif (!block_count) {\n\t\t\tblock_count = 0x8000;\n\t\t\tleft = 9;\n\t\t\ttemp = temp2 = 0;\n\n\t\t\tif (init_block(&in, tmp, tmplen, src) < 0)\n\t\t\t\treturn -1;\n\t\t}\n\n\t\td = block_count;\n\t\tif (d > len)\n\t\t\td = len;\n\n\t\t/* Unpacking */\n\t\tpos = 0;\n\t\tdo {\n\t\t\tuint16 bits = read_bits(&in, left);\n\t\t\tif (in.err)\n\t\t\t\treturn -1;\n\n\t\t\tif (left < 7) {\n\t\t\t\tuint32 i = 1 << (left - 1);\n\t\t\t\tuint32 j = bits & 0xffff;\n\t\t\t\tif (i != j)\n\t\t\t\t\tgoto unpack_byte;\n\t\t\t\tbits = (read_bits(&in, 3) + 1) & 0xff;\n\t\t\t\tif (in.err)\n\t\t\t\t\treturn -1;\n\n\t\t\t\tleft = ((uint8)bits < left) ?  (uint8)bits :\n\t\t\t\t\t\t(uint8)((bits + 1) & 0xff);\n\t\t\t\tgoto next;\n\t\t\t}\n\n\t\t\tif (left < 9) {\n\t\t\t\tuint16 i = (0xff >> (9 - left)) + 4;\n\t\t\t\tuint16 j = i - 8;\n\n\t\t\t\tif ((bits <= j) || (bits > i))\n\t\t\t\t\tgoto unpack_byte;\n\n\t\t\t\tbits -= j;\n\t\t\t\tleft = ((uint8)(bits & 0xff) < left) ?\n\t\t\t\t\t\t(uint8)(bits & 0xff) :\n\t\t\t\t\t\t(uint8)((bits + 1) & 0xff);\n\t\t\t\tgoto next;\n\t\t\t}\n\n\t\t\tif (left >= 10)\n\t\t\t\tgoto skip_byte;\n\n\t\t\tif (bits >= 256) {\n\t\t\t\tleft = (uint8) (bits + 1) & 0xff;\n\t\t\t\tgoto next;\n\t\t\t}\n\n\t\t    unpack_byte:\n\t\t\tif (left < 8) {\n\t\t\t\tuint8 shift = 8 - left;\n\t\t\t\tsigned char c = (signed char)(bits << shift);\n\t\t\t\tc >>= shift;\n\t\t\t\tbits = (uint16) c;\n\t\t\t}\n\t\t\tbits += temp;\n\t\t\ttemp = (uint8)bits;\n\t\t\ttemp2 += temp;\n\t\t\tdst[pos] = it215 ? temp2 : temp;\n\n\t\t    skip_byte:\n\t\t\tpos++;\n\n\t\t    next:\n\t\t\t/* if (slen <= 0)\n\t\t\t\treturn -1 */;\n\t\t} while (pos < d);\n\n\t\t/* Move On */\n\t\tblock_count -= d;\n\t\tlen -= d;\n\t\tdst += d;\n\t}\n\n\treturn 0;\n}\n\nint itsex_decompress16(HIO_HANDLE *src, int16 *dst, int len,\n\t\t       uint8 *tmp, int tmplen, int it215)\n{\n\tstruct it_stream in;\n\tuint32 block_count = 0;\n\tuint8 left = 0;\n\tint16 temp = 0, temp2 = 0;\n\tuint32 d, pos;\n\n\tmemset(&in, 0, sizeof(in)); /* bogus GCC 12 -Wmaybe-uninitialized */\n\n\twhile (len) {\n\t\tif (!block_count) {\n\t\t\tblock_count = 0x4000;\n\t\t\tleft = 17;\n\t\t\ttemp = temp2 = 0;\n\n\t\t\tif (init_block(&in, tmp, tmplen, src) < 0)\n\t\t\t\treturn -1;\n\t\t}\n\n\t\td = block_count;\n\t\tif (d > len)\n\t\t\td = len;\n\n\t\t/* Unpacking */\n\t\tpos = 0;\n\t\tdo {\n\t\t\tuint32 bits = read_bits(&in, left);\n\t\t\tif (in.err)\n\t\t\t\treturn -1;\n\n\t\t\tif (left < 7) {\n\t\t\t\tuint32 i = 1 << (left - 1);\n\t\t\t\tuint32 j = bits;\n\n\t\t\t\tif (i != j)\n\t\t\t\t\tgoto unpack_byte;\n\n\t\t\t\tbits = read_bits(&in, 4) + 1;\n\t\t\t\tif (in.err)\n\t\t\t\t\treturn -1;\n\n\t\t\t\tleft = ((uint8)(bits & 0xff) < left) ?\n\t\t\t\t\t\t(uint8)(bits & 0xff) :\n\t\t\t\t\t\t(uint8)((bits + 1) & 0xff);\n\t\t\t\tgoto next;\n\t\t\t}\n\n\t\t\tif (left < 17) {\n\t\t\t\tuint32 i = (0xffff >> (17 - left)) + 8;\n\t\t\t\tuint32 j = (i - 16) & 0xffff;\n\n\t\t\t\tif ((bits <= j) || (bits > (i & 0xffff)))\n\t\t\t\t\tgoto unpack_byte;\n\n\t\t\t\tbits -= j;\n\t\t\t\tleft = ((uint8)(bits & 0xff) < left) ?\n\t\t\t\t\t\t(uint8)(bits & 0xff) :\n\t\t\t\t\t\t(uint8)((bits + 1) & 0xff);\n\t\t\t\tgoto next;\n\t\t\t}\n\n\t\t\tif (left >= 18)\n\t\t\t\tgoto skip_byte;\n\n\t\t\tif (bits >= 0x10000) {\n\t\t\t\tleft = (uint8)(bits + 1) & 0xff;\n\t\t\t\tgoto next;\n\t\t\t}\n\n\t\t    unpack_byte:\n\t\t\tif (left < 16) {\n\t\t\t\tuint8 shift = 16 - left;\n\t\t\t\tint16 c = (int16)(bits << shift);\n\t\t\t\tc >>= shift;\n\t\t\t\tbits = (uint32) c;\n\t\t\t}\n\t\t\tbits += temp;\n\t\t\ttemp = (int16)bits;\n\t\t\ttemp2 += temp;\n\t\t\tdst[pos] = (it215) ? temp2 : temp;\n\n\t\t    skip_byte:\n\t\t\tpos++;\n\n\t\t    next:\n\t\t\t/* if (slen <= 0)\n\t\t\t\treturn -1 */;\n\t\t} while (pos < d);\n\n\t\t/* Move On */\n\t\tblock_count -= d;\n\t\tlen -= d;\n\t\tdst += d;\n\t\tif (len <= 0)\n\t\t\tbreak;\n\t}\n\n\treturn 0;\n}\n\n#endif /* LIBXMP_CORE_DISABLE_IT */\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/loader.h",
    "content": "#ifndef XMP_LOADER_H\n#define XMP_LOADER_H\n\n#include \"../common.h\"\n#include \"../effects.h\"\n#include \"../format.h\"\n#include \"../hio.h\"\n\n/* Sample flags */\n#define SAMPLE_FLAG_DIFF\t0x0001\t/* Differential */\n#define SAMPLE_FLAG_UNS\t\t0x0002\t/* Unsigned */\n#define SAMPLE_FLAG_8BDIFF\t0x0004\n#define SAMPLE_FLAG_7BIT\t0x0008\n#define SAMPLE_FLAG_NOLOAD\t0x0010\t/* Get from buffer, don't load */\n#define SAMPLE_FLAG_BIGEND\t0x0040\t/* Big-endian */\n#define SAMPLE_FLAG_VIDC\t0x0080\t/* Archimedes VIDC logarithmic */\n#define SAMPLE_FLAG_INTERLEAVED\t0x0100\t/* Interleaved stereo sample */\n#define SAMPLE_FLAG_FULLREP\t0x0200\t/* Play full sample before looping */\n#define SAMPLE_FLAG_ADLIB\t0x1000\t/* Adlib synth instrument */\n#define SAMPLE_FLAG_HSC\t\t0x2000\t/* HSC Adlib synth instrument */\n#define SAMPLE_FLAG_ADPCM\t0x4000\t/* ADPCM4 encoded samples */\n\n/* libxmp_test_name flags */\n#define TEST_NAME_IGNORE_AFTER_0\t0x0001\n#define TEST_NAME_IGNORE_AFTER_CR\t0x0002\n\n#define DEFPAN(x) (0x80 + ((x) - 0x80) * m->defpan / 100)\n\nint\tlibxmp_init_instrument\t\t(struct module_data *);\nint\tlibxmp_realloc_samples\t\t(struct module_data *, int);\nint\tlibxmp_alloc_subinstrument\t(struct xmp_module *, int, int);\nint\tlibxmp_init_pattern\t\t(struct xmp_module *);\nint\tlibxmp_alloc_pattern\t\t(struct xmp_module *, int);\nint\tlibxmp_alloc_track\t\t(struct xmp_module *, int, int);\nint\tlibxmp_alloc_tracks_in_pattern\t(struct xmp_module *, int);\nint\tlibxmp_alloc_pattern_tracks\t(struct xmp_module *, int, int);\n#ifndef LIBXMP_CORE_PLAYER\nint\tlibxmp_alloc_pattern_tracks_long(struct xmp_module *, int, int);\n#endif\nchar\t*libxmp_instrument_name\t\t(struct xmp_module *, int, uint8 *, int);\n\nchar\t*libxmp_copy_adjust\t\t(char *, uint8 *, int);\nint\tlibxmp_copy_name_for_fopen\t(char *, const char *, int);\nint\tlibxmp_test_name\t\t(const uint8 *, int, int);\nvoid\tlibxmp_read_title\t\t(HIO_HANDLE *, char *, int);\nvoid\tlibxmp_set_xxh_defaults\t\t(struct xmp_module *);\nvoid\tlibxmp_decode_protracker_event\t(struct xmp_event *, const uint8 *);\nvoid\tlibxmp_decode_noisetracker_event(struct xmp_event *, const uint8 *);\nvoid\tlibxmp_disable_continue_fx\t(struct xmp_event *);\nint\tlibxmp_check_filename_case\t(const char *, const char *, char *, int);\nint\tlibxmp_find_instrument_file\t(struct module_data *, char *, int, const char *);\nvoid\tlibxmp_set_type\t\t\t(struct module_data *, const char *, ...);\nint\tlibxmp_load_sample\t\t(struct module_data *, HIO_HANDLE *, int,\n\t\t\t\t\t struct xmp_sample *, const void *);\nvoid\tlibxmp_free_sample\t\t(struct xmp_sample *);\n#ifndef LIBXMP_CORE_PLAYER\nvoid\tlibxmp_schism_tracker_string\t(char *, size_t, int, int);\nvoid\tlibxmp_apply_mpt_preamp\t(struct module_data *m);\n#endif\n\nextern uint8\t\tlibxmp_ord_xlat[];\nextern const int\tlibxmp_arch_vol_table[];\n\n#define MAGIC4(a,b,c,d) \\\n    (((uint32)(a)<<24)|((uint32)(b)<<16)|((uint32)(c)<<8)|(d))\n\n#define LOAD_INIT()\n\n#define MODULE_INFO() do { \\\n    D_(D_WARN \"Module title: \\\"%s\\\"\", m->mod.name); \\\n    D_(D_WARN \"Module type: %s\", m->mod.type); \\\n} while (0)\n\n#endif\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/med.h",
    "content": "#ifndef LIBXMP_MED_H\n#define LIBXMP_MED_H\n\n#include \"../common.h\"\n#include \"../hio.h\"\n\n#define MMD_INST_TYPES 9\n\n#ifdef DEBUG\nextern const char *const mmd_inst_type[];\n#endif\n\n#define MED_VER_210\t\t0x0210\n#define MED_VER_300\t\t0x0300\n#define MED_VER_320\t\t0x0320\n#define MED_VER_OCTAMED_200\t0x2000\n#define MED_VER_OCTAMED_400\t0x4000\n#define MED_VER_OCTAMED_500\t0x5000\n#define MED_VER_OCTAMED_502\t0x5002\n#define MED_VER_OCTAMED_SS_1\t0x6000\n#define MED_VER_OCTAMED_SS_2\t0x7000\n\n/* Structures as defined in the MED/OctaMED MMD0 and MMD1 file formats,\n * revision 1, described by Teijo Kinnunen in Apr 25 1992\n */\n\nstruct PlaySeq {\n    char name[32];\t\t/* (0)  31 chars + \\0 */\n    uint32 reserved[2];\t\t/* (32) for possible extensions */\n    uint16 length;\t\t/* (40) # of entries */\n    uint16 seq[1];\t\t/* (42) block numbers.. */\n};\n\n\nstruct MMD0sample {\n    uint16 rep, replen;\t\t/* offs: 0(s), 2(s) */\n    uint8 midich;\t\t/* offs: 4(s) */\n    uint8 midipreset;\t\t/* offs: 5(s) */\n    uint8 svol;\t\t\t/* offs: 6(s) */\n    int8 strans;\t\t/* offs: 7(s) */\n};\n\n\nstruct MMD0song {\n    struct MMD0sample sample[63];\t/* 63 * 8 bytes = 504 bytes */\n    uint16 numblocks;\t\t/* offs: 504 */\n    uint16 songlen;\t\t/* offs: 506 */\n    uint8 playseq[256];\t\t/* offs: 508 */\n    uint16 deftempo;\t\t/* offs: 764 */\n    int8 playtransp;\t\t/* offs: 766 */\n#define FLAG_FILTERON   0x1\t/* the hardware audio filter is on */\n#define FLAG_JUMPINGON  0x2\t/* mouse pointer jumping on */\n#define FLAG_JUMP8TH    0x4\t/* ump every 8th line (not in OctaMED Pro) */\n#define FLAG_INSTRSATT  0x8\t/* sng+samples indicator (not useful in MMDs) */\n#define FLAG_VOLHEX\t0x10\t/* volumes are HEX */\n#define FLAG_STSLIDE    0x20\t/* use ST/NT/PT compatible sliding */\n#define FLAG_8CHANNEL   0x40\t/* this is OctaMED 5-8 channel song */\n#define FLAG_SLOWHQ     0X80\t/* HQ V2-4 compatibility mode */\n    uint8 flags;\t\t/* offs: 767 */\n#define FLAG2_BMASK\t0x1F\t/* (bits 0-4) BPM beat length (in lines) */\n#define FLAG2_BPM       0x20\t/* BPM mode on */\n#define FLAG2_MIX\t0x80\t/* Module uses mixing */\n    uint8 flags2;\t\t/* offs: 768 */\n    uint8 tempo2;\t\t/* offs: 769 */\n    uint8 trkvol[16];\t\t/* offs: 770 */\n    uint8 mastervol;\t\t/* offs: 786 */\n    uint8 numsamples;\t\t/* offs: 787 */\n};\t\t\t\t/* length = 788 bytes */\n\n\n/* This structure is exactly as long as the MMDsong structure. Common fields\n * are located at same offsets. You can also see, that there's a lot of room\n * for expansion in this structure.\n */\nstruct MMD2song {\n    struct MMD0sample sample[63];\n    uint16 numblocks;\n    uint16 songlen;\t\t/* NOTE: number of sections in MMD2 */\n    struct PlaySeq **playseqtable;\n    uint16 *sectiontable;\t/* UWORD section numbers */\n    uint8 *trackvols;\t\t/* UBYTE track volumes */\n    uint16 numtracks;\t\t/* max. number of tracks in the song\n\t\t\t\t * (also the number of entries in\n\t\t\t\t * 'trackvols' table) */\n    uint16 numpseqs;\t\t/* number of PlaySeqs in 'playseqtable' */\n    int8 *trackpans;\t\t/* NULL means 'all centered */\n#define FLAG3_STEREO\t0x1\t/* Mixing in stereo */\n#define FLAG3_FREEPAN\t0x2\t/* Mixing flag: free pan */\n    uint32 flags3;\t\t/* see defs below */\n    uint16 voladj;\t\t/* volume adjust (%), 0 means 100 */\n    uint16 channels;\t\t/* mixing channels, 0 means 4 */\n    uint8 mix_echotype;\t\t/* 0 = nothing, 1 = normal, 2 = cross */\n    uint8 mix_echodepth;\t/* 1 - 6, 0 = default */\n    uint16 mix_echolen;\t\t/* echo length in milliseconds */\n    int8 mix_stereosep;\t\t/* stereo separation */\n    uint8 pad0[223];\t\t/* reserved for future expansion */\n/* Fields below are MMD0/MMD1-compatible (except pad1[]) */\n    uint16 deftempo;\n    int8 playtransp;\n    uint8 flags;\n    uint8 flags2;\n    uint8 tempo2;\n    uint8 pad1[16];\t\t/* used to be trackvols, in MMD2 reserved */\n    uint8 mastervol;\n    uint8 numsamples;\n};\t\t\t\t/* length = 788 bytes */\n\n\nstruct MMD0 {\n    uint32 id;\n    uint32 modlen;\n    struct MMD0song *song;\n    uint16 psecnum;\t\t\t/* MMD2 only */\n    uint16 pseq;\t\t\t/* MMD2 only */\n    struct MMD0Block **blockarr;\n#define MMD_LOADTOFASTMEM 0x1\n    uint8 mmdflags;\t\t\t/* MMD2 only */\n    uint8 reserved[3];\n    struct InstrHdr **smplarr;\n    uint32 reserved2;\n    struct MMD0exp *expdata;\n    uint32 reserved3;\n    uint16 pstate;\t\t\t/* some data for the player routine */\n    uint16 pblock;\n    uint16 pline;\n    uint16 pseqnum;\n    int16 actplayline;\n    uint8 counter;\n    uint8 extra_songs;\t\t\t/* number of songs - 1 */\n};\t\t\t\t\t/* length = 52 bytes */\n\n\nstruct MMD0Block {\n    uint8 numtracks, lines;\n};\n\n\nstruct BlockCmdPageTable {\n    uint16 num_pages;\n    uint16 reserved;\n    uint16 *page[1];\n};\n\n\nstruct BlockInfo {\n    uint32 *hlmask;\n    uint8 *blockname;\n    uint32 blocknamelen;\n    struct BlockCmdPageTable *pagetable;\n    uint32 reserved[5];\n};\n\n\nstruct MMD1Block {\n    uint16 numtracks;\n    uint16 lines;\n    struct BlockInfo *info;\n};\n\n\nstruct InstrHdr {\n    uint32 length;\n#define S_16 0x10\t\t\t/* 16-bit sample */\n#define MD16 0x18\t\t\t/* 16-bit sample (Aura) */\n#define STEREO 0x20\t\t\t/* Stereo sample, not interleaved */\n    int16 type;\n    /* Followed by actual data */\n};\n\n\nstruct SynthWF {\n    uint16 length;\t\t\t/* length in words */\n    int8 wfdata[1];\t\t\t/* the waveform */\n};\n\n\nstruct SynthInstr {\n    uint32 length;\t\t\t/* length of this struct */\n    int16 type;\t\t\t\t/* -1 or -2 (offs: 4) */\n    uint8 defaultdecay;\n    uint8 reserved[3];\n    uint16 rep;\n    uint16 replen;\n    uint16 voltbllen;\t\t\t/* offs: 14 */\n    uint16 wftbllen;\t\t\t/* offs: 16 */\n    uint8 volspeed;\t\t\t/* offs: 18 */\n    uint8 wfspeed;\t\t\t/* offs: 19 */\n    uint16 wforms;\t\t\t/* offs: 20 */\n    uint8 voltbl[128];\t\t\t/* offs: 22 */\n    uint8 wftbl[128];\t\t\t/* offs: 150 */\n    uint32 wf[64];\t\t\t/* offs: 278 */\n};\n\n\n/* OctaMED SoundStudio 1 and prior use the InstrExt default_pitch field as a\n * default note value for the default note key 'F'. Pressing 'F' will insert a\n * note event with this note value.\n *\n * MED Soundstudio 2 in mix mode treats note 0x01 as a default note event,\n * which is emitted by the default note key instead of a regular note event.\n * It also makes this more complicated, despite not having changed the file\n * format: the user must enter a frequency in Hz instead of a note number,\n * where 8363 Hz corresponds to the event C-2. This frequency is converted to a\n * note number upon saving the module. Multi-octave instruments do not support\n * this feature as they are not supported by MED Soundstudio 2.\n *\n * This editor-only behavior would be irrelevant, except when default_pitch\n * is zero, the player uses the default frequency 22050 Hz instead. This\n * results in a note between E-3 and F-3. Since this feature is currently\n * implemented in the instrument map, use the mix mode note for F-3 instead.\n */\n#define MMD3_DEFAULT_NOTE\t53\n\nstruct InstrExt {\n    uint8 hold;\n    uint8 decay;\n    uint8 suppress_midi_off;\n    int8  finetune;\n    /* Below fields saved by >= V5 */\n    uint8 default_pitch;\n#define SSFLG_LOOP\t0x01\t\t/* Loop On/Off */\n#define SSFLG_EXTPSET\t0x02\t\t/* Ext. Preset */\n#define SSFLG_DISABLED\t0x04\t\t/* Disabled */\n#define SSFLG_PINGPONG\t0x08\t\t/* Ping-pong looping */\n    uint8 instr_flags;\n    uint16 long_midi_preset;\n    /* Below fields saved by >= V5.02 */\n    uint8 output_device;\n    uint8 reserved;\n    /* Below fields saved by >= V7 */\n    uint32 long_repeat;\n    uint32 long_replen;\n};\n\n\nstruct MMDInfo {\n    struct MMDInfo *next;\t\t/* next MMDInfo structure */\n    uint16 reserved;\n    uint16 type;\t\t\t\t/* data type (1 = ASCII) */\n    uint32 length;\t\t\t/* data length in bytes */\n    /* data follows... */\n};\n\n\nstruct MMDARexxTrigCmd {\n    struct MMDARexxTrigCmd *next;\t/* the next command, or NULL */\n    uint8 cmdnum;\t\t\t/* command number (01..FF) */\n    uint8 pad;\n    int16 cmdtype;\t\t\t/* command type (OMACTION_...) */\n    char *cmd;\t\t\t\t/* command, or NULL */\n    char *port;\t\t\t\t/* port, or NULL */\n    uint16 cmd_len;\t\t\t/* length of 'cmd' string (without\n\t\t\t\t\t * term. 0) */\n    uint16 port_len;\t\t\t/* length of 'port' string (without\n\t\t\t\t\t * term. 0) */\n};\t\t/* current (V7) structure size: 20 */\n\n\nstruct MMDARexx {\n    uint16 res;\t\t\t\t/* reserved, must be zero! */\n    uint16 trigcmdlen;\t\t\t/* size of trigcmd entries\n\t\t\t\t\t * (MUST be used!!) */\n    struct MMDARexxTrigCmd *trigcmd;\t/* chain of MMDARexxTrigCmds or NULL */\n};\n\n\nstruct MMDMIDICmd3x {\n    uint8 struct_vers;\t\t\t/* current version = 0 */\n    uint8 pad;\n    uint16 num_of_settings;\t\t/* number of Cmd3x settings\n\t\t\t\t\t * (currently set to 15) */\n    uint8 *ctrlr_types;\t\t\t/* controller types */\n    uint16 *ctrlr_numbers;\t\t/* controller numbers */\n};\n\n\nstruct MMDInstrInfo {\n    uint8 name[40];\n};\n\nstruct MMD0exp {\n    struct MMD0 *nextmod;\t\t/* pointer to the next module */\n    struct InstrExt *exp_smp;\t\t/* pointer to InstrExt */\n    uint16 s_ext_entries;\t\t/* size of InstrExt structure array */\n    uint16 s_ext_entrsz;\t\t/* size of each InstrExt structure */\n    uint8 *annotxt;\t\t\t/* pointer to the annotation text */\n    uint32 annolen;\t\t\t/* length of 'annotxt' */\n    struct MMDInstrInfo *iinfo;\t\t/* pointer to MMDInstrInfo */\n    uint16 i_ext_entries;\t\t/* size of MMDInstrInfo struct array */\n    uint16 i_ext_entrsz;\t\t/* size of each MMDInstrInfo struct */\n    uint32 jumpmask;\t\t\t/* mouse pointer jump control */\n    uint16 *rgbtable;\t\t\t/* screen colors */\n    uint8 channelsplit[4];\t\t/* channel splitting control */\n    struct NotationInfo *n_info;\t/* info for the notation editor */\n    uint8 *songname;\t\t\t/* song name of the current song */\n    uint32 songnamelen;\t\t\t/* song name length */\n    struct MMDDumpData *dumps;\t\t/* MIDI dump data */\n    struct MMDInfo *mmdinfo;\t\t/* more information about the song */\n    struct MMDARexx *mmdrexx;\t\t/* embedded ARexx commands */\n    struct MMDMIDICmd3x *mmdcmd3x;\t/* settings for command 3x */\n    uint32 reserved2[3];\t\t\t/* future expansion fields */\n    uint32 tag_end;\n};\n\n\nstruct NotationInfo {\n    uint8 n_of_sharps;\t\t\t/* number of sharps or flats */\n#define NFLG_FLAT   1\n#define NFLG_3_4    2\n    uint8 flags;\n    int16 trksel[5];\t\t\t/* number of the selected track */\n    uint8 trkshow[16];\t\t\t/* tracks shown */\n    uint8 trkghost[16];\t\t\t/* tracks ghosted */\n    int8 notetr[63];\t\t\t/* note transpose for each instrument */\n    uint8 pad;\n};\n\n\nstruct MMDDumpData {\n    uint16 numdumps;\n    uint16 reserved[3];\n};\n\n\nstruct MMDDump {\n    uint32 length;\t\t\t/* length of the MIDI message dump */\n    uint8 *data;\t\t\t/* pointer to MIDI dump data */\n    uint16 ext_len;\t\t\t/* MMDDump struct extension length */\n    /* if ext_len >= 20: */\n    uint8 name[20];\t\t\t/* name of the dump */\n};\n\nextern const int mmd_num_oct[6];\n\nvoid mmd_xlat_fx(struct xmp_event *, int, int, int, int);\nint mmd_alloc_tables(struct module_data *, int, struct SynthInstr *);\n\nint mmd_load_instrument(HIO_HANDLE *, struct module_data *, int, int,\n\t\tstruct MMD0exp *, struct InstrExt *, struct MMD0sample *, int);\nint med_load_external_instrument(HIO_HANDLE *, struct module_data *, int);\n\nint mmd_convert_tempo(int tempo, int bpm_on, int med_8ch);\nvoid mmd_set_bpm(struct module_data *, int, int, int, int);\nvoid mmd_info_text(HIO_HANDLE *, struct module_data *, int);\nint mmd_tracker_version(struct module_data *, int, int, struct MMD0exp *);\n\n#endif /* LIBXMP_MED_H */\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/med2_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/*\n * MED 1.12 is in Fish disk #255\n */\n\n#include \"med.h\"\n#include \"loader.h\"\n#include \"../period.h\"\n\n#define MAGIC_MED2\tMAGIC4('M','E','D',2)\n\nstatic int med2_test(HIO_HANDLE *, char *, const int);\nstatic int med2_load (struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_med2 = {\n\t\"MED 1.12 MED2\",\n\tmed2_test,\n\tmed2_load\n};\n\n\nstatic int med2_test(HIO_HANDLE *f, char *t, const int start)\n{\n\tif (hio_read32b(f) !=  MAGIC_MED2)\n\t\treturn -1;\n\n\tlibxmp_read_title(f, t, 0);\n\n\treturn 0;\n}\n\nstatic int med2_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j, k;\n\tint sliding;\n\tstruct xmp_event *event;\n\tuint8 buf[40];\n\n\tLOAD_INIT();\n\n\tif (hio_read32b(f) != MAGIC_MED2)\n\t\treturn -1;\n\n\tlibxmp_set_type(m, \"MED 1.12 MED2\");\n\n\tmod->ins = mod->smp = 32;\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\treturn -1;\n\n\t/* read instrument names */\n\thio_read(buf, 1, 40, f);\t/* skip 0 */\n\tfor (i = 0; i < 31; i++) {\n\t\tif (hio_read(buf, 1, 40, f) != 40)\n\t\t\treturn -1;\n\n\t\tlibxmp_instrument_name(mod, i, buf, 40);\n\t\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t\t\treturn -1;\n\t}\n\n\t/* read instrument volumes */\n\thio_read8(f);\t\t/* skip 0 */\n\tfor (i = 0; i < 31; i++) {\n\t\tmod->xxi[i].sub[0].vol = hio_read8(f);\n\t\tmod->xxi[i].sub[0].pan = 0x80;\n\t\tmod->xxi[i].sub[0].fin = 0;\n\t\tmod->xxi[i].sub[0].sid = i;\n\t}\n\n\t/* read instrument loops */\n\thio_read16b(f);\t\t/* skip 0 */\n\tfor (i = 0; i < 31; i++) {\n\t\tmod->xxs[i].lps = hio_read16b(f);\n\t}\n\n\t/* read instrument loop length */\n\thio_read16b(f);\t\t/* skip 0 */\n\tfor (i = 0; i < 31; i++) {\n\t\tuint32 lsiz = hio_read16b(f);\n\t\tmod->xxs[i].lpe = mod->xxs[i].lps + lsiz;\n\t\tmod->xxs[i].flg = lsiz > 1 ? XMP_SAMPLE_LOOP : 0;\n\t}\n\n\tmod->chn = 4;\n\tmod->pat = hio_read16b(f);\n\tmod->trk = mod->chn * mod->pat;\n\n\tif (hio_read(mod->xxo, 1, 100, f) != 100)\n\t\treturn -1;\n\n\tmod->len = hio_read16b(f);\n\n\t/* Sanity check */\n\tif (mod->pat > 256 || mod->len > 100)\n\t\treturn -1;\n\n\tk = hio_read16b(f);\n\tif (k < 1) {\n\t\treturn -1;\n\t}\n\n\tmod->spd = 6;\n\tmod->bpm = k;\n\tm->time_factor = MED_TIME_FACTOR;\n\n\thio_read16b(f);\t\t\t/* flags */\n\tsliding = hio_read16b(f);\t/* sliding */\n\thio_read32b(f);\t\t\t/* jumping mask */\n\thio_seek(f, 16, SEEK_CUR);\t/* rgb */\n\n\tMODULE_INFO();\n\n\tD_(D_INFO \"Sliding: %d\", sliding);\n\n\tif (sliding == 6)\n\t\tm->quirk |= QUIRK_VSALL | QUIRK_PBALL;\n\n\tif (libxmp_init_pattern(mod) < 0)\n\t\treturn -1;\n\n\t/* Load and convert patterns */\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tif (libxmp_alloc_pattern_tracks(mod, i, 64) < 0)\n\t\t\treturn -1;\n\n\t\thio_read32b(f);\n\n\t\tfor (j = 0; j < 64; j++) {\n\t\t\tfor (k = 0; k < 4; k++) {\n\t\t\t\tuint8 x;\n\t\t\t\tevent = &EVENT(i, k, j);\n\t\t\t\tevent->note = libxmp_period_to_note(hio_read16b(f));\n\t\t\t\tx = hio_read8(f);\n\t\t\t\tevent->ins = x >> 4;\n\t\t\t\tevent->fxt = x & 0x0f;\n\t\t\t\tevent->fxp = hio_read8(f);\n\n\t\t\t\tswitch (event->fxt) {\n\t\t\t\tcase 0x00:\t\t/* arpeggio */\n\t\t\t\tcase 0x01:\t\t/* slide up */\n\t\t\t\tcase 0x02:\t\t/* slide down */\n\t\t\t\tcase 0x03:\t\t/* portamento */\n\t\t\t\tcase 0x04:\t\t/* vibrato? */\n\t\t\t\tcase 0x0c:\t\t/* volume */\n\t\t\t\t\tbreak;\t\t/* ...like protracker */\n\t\t\t\tcase 0x0d:\t\t/* volslide */\n\t\t\t\tcase 0x0e:\t\t/* volslide */\n\t\t\t\t\tevent->fxt = FX_VOLSLIDE;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x0f:\n\t\t\t\t\tevent->fxt = FX_S3M_BPM;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Load samples */\n\n\tD_(D_INFO \"Instruments    : %d \", mod->ins);\n\n\tfor (i = 0; i < 31; i++) {\n\t\tif (med_load_external_instrument(f, m, i)) {\n\t\t\tD_(D_CRIT \"error loading instrument %d\", i);\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/med3_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/*\n * MED 2.00 is in Fish disk #349 and has a couple of demo modules, get it\n * from ftp://ftp.funet.fi/pub/amiga/fish/301-400/ff349\n */\n\n#include \"loader.h\"\n#include \"med.h\"\n\n#define MAGIC_MED3\tMAGIC4('M','E','D',3)\n\n\nstatic int med3_test(HIO_HANDLE *, char *, const int);\nstatic int med3_load (struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_med3 = {\n\t\"MED 2.00 MED3\",\n\tmed3_test,\n\tmed3_load\n};\n\nstatic int med3_test(HIO_HANDLE *f, char *t, const int start)\n{\n\tif (hio_read32b(f) !=  MAGIC_MED3)\n\t\treturn -1;\n\n\tlibxmp_read_title(f, t, 0);\n\n\treturn 0;\n}\n\n\n#define MASK\t\t0x80000000\n\n#define M0F_LINEMSK0F\t0x01\n#define M0F_LINEMSK1F\t0x02\n#define M0F_FXMSK0F\t0x04\n#define M0F_FXMSK1F\t0x08\n#define M0F_LINEMSK00\t0x10\n#define M0F_LINEMSK10\t0x20\n#define M0F_FXMSK00\t0x40\n#define M0F_FXMSK10\t0x80\n\n\n/*\n * From the MED 2.00 file loading/saving routines by Teijo Kinnunen, 1990\n */\n\nstatic uint8 get_nibble(uint8 *mem, uint16 *nbnum)\n{\n\tuint8 *mloc = mem + (*nbnum / 2), res;\n\n\tif(*nbnum & 0x1)\n\t\tres = *mloc & 0x0f;\n\telse\n\t\tres = *mloc >> 4;\n\t(*nbnum)++;\n\n\treturn res;\n}\n\nstatic uint16 get_nibbles(uint8 *mem, uint16 *nbnum, uint8 nbs)\n{\n\tuint16 res = 0;\n\n\twhile (nbs--) {\n\t\tres <<= 4;\n\t\tres |= get_nibble(mem, nbnum);\n\t}\n\n\treturn res;\n}\n\nstatic int unpack_block(struct module_data *m, uint16 bnum, uint8 *from, uint16 convsz)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_event *event;\n\tuint32 linemsk0 = *((uint32 *)from), linemsk1 = *((uint32 *)from + 1);\n\tuint32 fxmsk0 = *((uint32 *)from + 2), fxmsk1 = *((uint32 *)from + 3);\n\tuint32 *lmptr = &linemsk0, *fxptr = &fxmsk0;\n\tuint16 fromn = 0, lmsk;\n\tuint8 *fromst = from + 16, bcnt, *tmpto;\n\tuint8 *patbuf, *to;\n\tuint32 nibs_left = convsz * 2;\n\tint i, j, trkn = mod->chn;\n\n\t/*from += 16;*/\n\tpatbuf = to = (uint8 *) calloc(3, 4 * 64);\n\tif (to == NULL) {\n\t\tgoto err;\n\t}\n\n\tfor (i = 0; i < 64; i++) {\n\t\tif (i == 32) {\n\t\t\tlmptr = &linemsk1;\n\t\t\tfxptr = &fxmsk1;\n\t\t}\n\n\t\tif (*lmptr & MASK) {\n\t\t\tif (trkn / 4 > nibs_left) {\n\t\t\t\tgoto err2;\n\t\t\t}\n\t\t\tnibs_left -= trkn / 4;\n\n\t\t\tlmsk = get_nibbles(fromst, &fromn, (uint8)(trkn / 4));\n\t\t\tlmsk <<= (16 - trkn);\n\t\t\ttmpto = to;\n\n\t\t\tfor (bcnt = 0; bcnt < trkn; bcnt++) {\n\t\t\t\tif (lmsk & 0x8000) {\n\t\t\t\t\tif (nibs_left < 3) {\n\t\t\t\t\t\tgoto err2;\n\t\t\t\t\t}\n\t\t\t\t\tnibs_left -= 3;\n\t\t\t\t\t*tmpto = (uint8)get_nibbles(fromst,\n\t\t\t\t\t\t&fromn,2);\n\t\t\t\t\t*(tmpto + 1) = (get_nibble(fromst,\n\t\t\t\t\t\t\t&fromn) << 4);\n\t\t\t\t}\n\t\t\t\tlmsk <<= 1;\n\t\t\t\ttmpto += 3;\n\t\t\t}\n\t\t}\n\n\t\tif (*fxptr & MASK) {\n\t\t\tif (trkn / 4 > nibs_left) {\n\t\t\t\tgoto err2;\n\t\t\t}\n\t\t\tnibs_left -= trkn / 4;\n\n\t\t\tlmsk = get_nibbles(fromst,&fromn,(uint8)(trkn / 4));\n\t\t\tlmsk <<= (16 - trkn);\n\t\t\ttmpto = to;\n\n\t\t\tfor (bcnt = 0; bcnt < trkn; bcnt++) {\n\t\t\t\tif (lmsk & 0x8000) {\n\t\t\t\t\tif (nibs_left < 3) {\n\t\t\t\t\t\tgoto err2;\n\t\t\t\t\t}\n\t\t\t\t\tnibs_left -= 3;\n\t\t\t\t\t*(tmpto+1) |= get_nibble(fromst,\n\t\t\t\t\t\t\t&fromn);\n\t\t\t\t\t*(tmpto+2) = (uint8)get_nibbles(fromst,\n\t\t\t\t\t\t\t&fromn,2);\n\t\t\t\t}\n\t\t\t\tlmsk <<= 1;\n\t\t\t\ttmpto += 3;\n\t\t\t}\n\t\t}\n\t\tto += 3 * trkn;\n\t\t*lmptr <<= 1;\n\t\t*fxptr <<= 1;\n\t}\n\n\tfor (i = 0; i < 64; i++) {\n\t\tfor (j = 0; j < 4; j++) {\n\t\t\tevent = &EVENT(bnum, j, i);\n\n\t\t\tevent->note = patbuf[i * 12 + j * 3 + 0];\n\t\t\tif (event->note)\n\t\t\t\tevent->note += 48;\n\t\t\tevent->ins  = patbuf[i * 12 + j * 3 + 1] >> 4;\n\t\t\tif (event->ins)\n\t\t\t\tevent->ins++;\n\t\t\tevent->fxt  = patbuf[i * 12 + j * 3 + 1] & 0x0f;\n\t\t\tevent->fxp  = patbuf[i * 12 + j * 3 + 2];\n\n\t\t\tswitch (event->fxt) {\n\t\t\tcase 0x00:\t/* arpeggio */\n\t\t\tcase 0x01:\t/* slide up */\n\t\t\tcase 0x02:\t/* slide down */\n\t\t\tcase 0x03:\t/* portamento */\n\t\t\tcase 0x04:\t/* vibrato? */\n\t\t\t\tbreak;\n\t\t\tcase 0x0c:\t/* set volume (BCD) */\n\t\t\t\tevent->fxp = MSN(event->fxp) * 10 +\n\t\t\t\t\t\t\tLSN(event->fxp);\n\t\t\t\tbreak;\n\t\t\tcase 0x0d:\t/* volume slides */\n\t\t\t\tevent->fxt = FX_VOLSLIDE;\n\t\t\t\tbreak;\n\t\t\tcase 0x0f:\t/* tempo/break */\n\t\t\t\tif (event->fxp == 0) {\n\t\t\t\t\tevent->fxt = FX_BREAK;\n\t\t\t\t} else if (event->fxp == 0xff) {\n\t\t\t\t\tevent->fxp = event->fxt = 0;\n\t\t\t\t\tevent->vol = 1;\n\t\t\t\t} else if (event->fxp == 0xfe) {\n\t\t\t\t\tevent->fxp = event->fxt = 0;\n\t\t\t\t} else if (event->fxp == 0xf1) {\n\t\t\t\t\t/* Retrigger once on tick 3 */\n\t\t\t\t\tevent->fxt = FX_EXTENDED;\n\t\t\t\t\tevent->fxp = (EX_RETRIG << 4) | 3;\n\t\t\t\t} else if (event->fxp == 0xf2) {\n\t\t\t\t\t/* Delay until tick 3 */\n\t\t\t\t\tevent->fxt = FX_EXTENDED;\n\t\t\t\t\tevent->fxp = (EX_DELAY << 4) | 3;\n\t\t\t\t} else if (event->fxp == 0xf3) {\n\t\t\t\t\t/* Retrigger every 2 ticks (TODO: buggy) */\n\t\t\t\t\tevent->fxt = FX_MED_RETRIG;\n\t\t\t\t\tevent->fxp = 0x02;\n\t\t\t\t} else if (event->fxp <= 0xf0) {\n\t\t\t\t\tevent->fxt = FX_S3M_BPM;\n\t\t\t\t\tevent->fxp = mmd_convert_tempo(event->fxp, 0, 0);\n\t\t\t\t} else {\n\t\t\t\t\tevent->fxt = event->fxp = 0;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tevent->fxp = event->fxt = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tfree(patbuf);\n\n\treturn 0;\n\n     err2:\n\tfree(patbuf);\n     err:\n\treturn -1;\n}\n\n\nstatic int med3_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j;\n\tuint32 mask;\n\tint transp, sliding;\n\tint tempo;\n\tint flags;\n\n\tLOAD_INIT();\n\n\thio_read32b(f);\n\n\tlibxmp_set_type(m, \"MED 2.00 MED3\");\n\n\tmod->ins = mod->smp = 32;\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\treturn -1;\n\n\t/* read instrument names */\n\tfor (i = 0; i < 32; i++) {\n\t\tuint8 c, buf[40];\n\t\tfor (j = 0; j < 40; j++) {\n\t\t\tc = hio_read8(f);\n\t\t\tbuf[j] = c;\n\t\t\tif (c == 0)\n\t\t\t\tbreak;\n\t\t}\n\t\tlibxmp_instrument_name(mod, i, buf, 32);\n\t\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t\t\treturn -1;\n\t}\n\n\t/* read instrument volumes */\n\tmask = hio_read32b(f);\n\tfor (i = 0; i < 32; i++, mask <<= 1) {\n\t\tmod->xxi[i].sub[0].vol = mask & MASK ? hio_read8(f) : 0;\n\t\tmod->xxi[i].sub[0].pan = 0x80;\n\t\tmod->xxi[i].sub[0].fin = 0;\n\t\tmod->xxi[i].sub[0].sid = i;\n\t}\n\n\t/* read instrument loops */\n\tmask = hio_read32b(f);\n\tfor (i = 0; i < 32; i++, mask <<= 1) {\n\t\tmod->xxs[i].lps = mask & MASK ? hio_read16b(f) : 0;\n\t}\n\n\t/* read instrument loop length */\n\tmask = hio_read32b(f);\n\tfor (i = 0; i < 32; i++, mask <<= 1) {\n\t\tuint32 lsiz = mask & MASK ? hio_read16b(f) : 0;\n\t\tmod->xxs[i].len = mod->xxs[i].lps + lsiz;\n\t\tmod->xxs[i].lpe = mod->xxs[i].lps + lsiz;\n\t\tmod->xxs[i].flg = lsiz > 1 ? XMP_SAMPLE_LOOP : 0;\n\t}\n\n\tmod->chn = 4;\n\tmod->pat = hio_read16b(f);\n\tmod->trk = mod->chn * mod->pat;\n\n\tmod->len = hio_read16b(f);\n\n\t/* Sanity check */\n\tif (mod->len > 256 || mod->pat > 256)\n\t\treturn -1;\n\n\thio_read(mod->xxo, 1, mod->len, f);\n\ttempo = hio_read16b(f);\n\ttransp = hio_read8s(f);\n\tflags = hio_read8(f);\t\t/* flags */\n\tsliding = hio_read16b(f);\t/* sliding */\n\thio_read32b(f);\t\t\t/* jumping mask */\n\thio_seek(f, 16, SEEK_CUR);\t/* rgb */\n\n\tmod->spd = 6;\n\tmod->bpm = mmd_convert_tempo(tempo, 0, 0);\n\tm->time_factor = MED_TIME_FACTOR;\n\n\t/* read midi channels */\n\tmask = hio_read32b(f);\n\tfor (i = 0; i < 32; i++, mask <<= 1) {\n\t\tif (mask & MASK)\n\t\t\thio_read8(f);\n\t}\n\n\t/* read midi programs */\n\tmask = hio_read32b(f);\n\tfor (i = 0; i < 32; i++, mask <<= 1) {\n\t\tif (mask & MASK)\n\t\t\thio_read8(f);\n\t}\n\n\tMODULE_INFO();\n\n\tD_(D_INFO \"Sliding: %d\", sliding);\n\tD_(D_INFO \"Play transpose: %d\", transp);\n\n\tm->quirk |= QUIRK_RTONCE; /* FF1 */\n\tif (sliding == 6)\n\t\tm->quirk |= QUIRK_VSALL | QUIRK_PBALL;\n\n\tfor (i = 0; i < 32; i++)\n\t\tmod->xxi[i].sub[0].xpo = transp;\n\n\tif (libxmp_init_pattern(mod) < 0)\n\t\treturn -1;\n\n\t/* Load and convert patterns */\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tuint32 *conv;\n\t\tuint8 b;\n\t\t/*uint8 tracks;*/\n\t\tuint16 convsz;\n\n\t\tif (libxmp_alloc_pattern_tracks(mod, i, 64) < 0)\n\t\t\treturn -1;\n\n\t\t/* TODO: not clear if this should be respected. Later MED\n\t\t * formats are capable of having different track counts. */\n\t\t/*tracks =*/ hio_read8(f);\n\n\t\tb = hio_read8(f);\n\t\tconvsz = hio_read16b(f);\n\t\tconv = (uint32 *) calloc(1, convsz + 16);\n\t\tif (conv == NULL)\n\t\t\treturn -1;\n\n                if (b & M0F_LINEMSK00)\n\t\t\t*conv = 0L;\n                else if (b & M0F_LINEMSK0F)\n\t\t\t*conv = 0xffffffff;\n                else\n\t\t\t*conv = hio_read32b(f);\n\n                if (b & M0F_LINEMSK10)\n\t\t\t*(conv + 1) = 0L;\n                else if (b & M0F_LINEMSK1F)\n\t\t\t*(conv + 1) = 0xffffffff;\n                else\n\t\t\t*(conv + 1) = hio_read32b(f);\n\n                if (b & M0F_FXMSK00)\n\t\t\t*(conv + 2) = 0L;\n                else if (b & M0F_FXMSK0F)\n\t\t\t*(conv + 2) = 0xffffffff;\n                else\n\t\t\t*(conv + 2) = hio_read32b(f);\n\n                if (b & M0F_FXMSK10)\n\t\t\t*(conv + 3) = 0L;\n                else if (b & M0F_FXMSK1F)\n\t\t\t*(conv + 3) = 0xffffffff;\n                else\n\t\t\t*(conv + 3) = hio_read32b(f);\n\n\t\tif (hio_read(conv + 4, 1, convsz, f) != convsz) {\n\t\t\tfree(conv);\n\t\t\treturn -1;\n\t\t}\n\n                if (unpack_block(m, i, (uint8 *)conv, convsz) < 0) {\n\t\t\tfree(conv);\n\t\t\treturn -1;\n\t\t}\n\n\t\tfree(conv);\n\t}\n\n\t/* Load samples */\n\n\tD_(D_INFO \"Instruments: %d\", mod->ins);\n\n\tmask = hio_read32b(f);\n\tfor (i = 0; i < 32; i++, mask <<= 1) {\n\t\tif (~mask & MASK)\n\t\t\tcontinue;\n\n\t\tif (~flags & FLAG_INSTRSATT) {\n\t\t\t/* Song file */\n\t\t\tif (med_load_external_instrument(f, m, i)) {\n\t\t\t\tD_(D_CRIT \"error loading instrument %d\", i);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Module file */\n\t\tmod->xxi[i].nsm = 1;\n\t\tmod->xxs[i].len = hio_read32b(f);\n\n\t\tif (mod->xxs[i].len == 0)\n\t\t\tmod->xxi[i].nsm = 0;\n\n\t\tif (hio_read16b(f))\t\t/* type */\n\t\t\tcontinue;\n\n\t\tD_(D_INFO \"[%2X] %-32.32s %04x %04x %04x %c V%02x \",\n\t\t\ti, mod->xxi[i].name, mod->xxs[i].len, mod->xxs[i].lps,\n\t\t\tmod->xxs[i].lpe,\n\t\t\tmod->xxs[i].flg & XMP_SAMPLE_LOOP ? 'L' : ' ',\n\t\t\tmod->xxi[i].sub[0].vol);\n\n\t\tif (libxmp_load_sample(m, f, 0, &mod->xxs[i], NULL) < 0)\n\t\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/med4_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/*\n * MED 2.13 is in Fish disk #424 and has a couple of demo modules, get it\n * from ftp://ftp.funet.fi/pub/amiga/fish/401-500/ff424. Alex Van Starrex's\n * HappySong MED4 is in ff401. MED 3.00 is in ff476.\n */\n\n#include \"med.h\"\n#include \"loader.h\"\n#include \"../med_extras.h\"\n\n#define MAGIC_MED4\tMAGIC4('M','E','D',4)\n#undef MED4_DEBUG\n\nstatic int med4_test(HIO_HANDLE *, char *, const int);\nstatic int med4_load (struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_med4 = {\n\t\"MED 2.10 MED4\",\n\tmed4_test,\n\tmed4_load\n};\n\nstatic int med4_test(HIO_HANDLE *f, char *t, const int start)\n{\n\tif (hio_read32b(f) != MAGIC_MED4)\n\t\treturn -1;\n\n\tlibxmp_read_title(f, t, 0);\n\n\treturn 0;\n}\n\nstatic void fix_effect(struct xmp_event *event, int hexvol)\n{\n\tswitch (event->fxt) {\n\tcase 0x00:\t/* arpeggio */\n\tcase 0x01:\t/* slide up */\n\tcase 0x02:\t/* slide down */\n\tcase 0x03:\t/* portamento */\n\tcase 0x04:\t/* vibrato? */\n\t\tbreak;\n\tcase 0x09:\t/* set speed (3.00+) */\n\t\tif (event->fxp >= 0x01 && event->fxp <= 0x20) {\n\t\t\tevent->fxt = FX_SPEED;\n\t\t} else {\n\t\t\tevent->fxt = event->fxp = 0;\n\t\t}\n\t\tbreak;\n\tcase 0x0c:\t/* set volume (BCD) */\n\t\tif (!hexvol) {\n\t\t\tevent->fxp = MSN(event->fxp) * 10 + LSN(event->fxp);\n\t\t}\n\t\tbreak;\n\tcase 0x0d:\t/* volume slides */\n\t\tevent->fxt = FX_VOLSLIDE;\n\t\tbreak;\n\tcase 0x0f:\t/* tempo/break */\n\t\tif (event->fxp == 0) {\n\t\t\tevent->fxt = FX_BREAK;\n\t\t} else if (event->fxp == 0xff) {\n\t\t\tevent->fxp = event->fxt = 0;\n\t\t\tevent->vol = 1;\n\t\t} else if (event->fxp == 0xf1) {\n\t\t\t/* Retrigger once on tick 3 */\n\t\t\tevent->fxt = FX_EXTENDED;\n\t\t\tevent->fxp = (EX_RETRIG << 4) | 3;\n\t\t} else if (event->fxp == 0xf2) {\n\t\t\t/* Delay until tick 3 */\n\t\t\tevent->fxt = FX_EXTENDED;\n\t\t\tevent->fxp = (EX_DELAY << 4) | 3;\n\t\t} else if (event->fxp == 0xf3) {\n\t\t\t/* Retrigger every 2 ticks (TODO: buggy) */\n\t\t\tevent->fxt = FX_MED_RETRIG;\n\t\t\tevent->fxp = 0x02;\n\t\t} else if (event->fxp <= 0xf0) {\n\t\t\tevent->fxt = FX_S3M_BPM;\n\t\t\tevent->fxp = mmd_convert_tempo(event->fxp, 0, 0);\n\t\t} else {\n\t\t\tevent->fxp = event->fxt = 0;\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tevent->fxp = event->fxt = 0;\n\t}\n}\n\nstruct stream {\n\tHIO_HANDLE* f;\n\tint has_nibble;\n\tuint8 value;\n};\n\nstatic inline void stream_init(HIO_HANDLE* f, struct stream* s)\n{\n\ts->f = f;\n\ts->has_nibble = s->value = 0;\n}\n\nstatic inline unsigned stream_read4(struct stream* s)\n{\n\ts->has_nibble = !s->has_nibble;\n\tif (!s->has_nibble) {\n\t\treturn s->value & 0x0f;\n\t} else {\n\t\ts->value = hio_read8(s->f);\n\t\treturn s->value >> 4;\n\t}\n}\n\nstatic inline unsigned stream_read8(struct stream* s)\n{\n\tunsigned a = stream_read4(s);\n\tunsigned b = stream_read4(s);\n\treturn (a << 4) | b;\n}\n\nstatic inline unsigned stream_read12(struct stream* s)\n{\n\tunsigned a = stream_read4(s);\n\tunsigned b = stream_read4(s);\n\tunsigned c = stream_read4(s);\n\treturn (a << 8) | (b << 4) | c;\n}\n\nstatic inline uint16 stream_read16(struct stream* s)\n{\n\tunsigned a = stream_read4(s);\n\tunsigned b = stream_read4(s);\n\tunsigned c = stream_read4(s);\n\tunsigned d = stream_read4(s);\n\treturn (a << 12) | (b << 8) | (c << 4) | d;\n}\n\nstatic inline uint16 stream_read_aligned16(struct stream* s, int bits)\n{\n\tif (bits <= 4) {\n\t\treturn stream_read4(s) << 12;\n\t}\n\tif (bits <= 8) {\n\t\treturn stream_read8(s) << 8;\n\t}\n\tif (bits <= 12) {\n\t\treturn stream_read12(s) << 4;\n\t}\n\treturn stream_read16(s);\n}\n\nstruct temp_inst {\n\tchar name[32];\n\tint loop_start;\n\tint loop_end;\n\tint volume;\n\tint transpose;\n};\n\nstatic int med4_sample_check(struct module_data *m, int pos, int needed)\n{\n\tif (pos + needed > m->mod.smp) {\n\t\tif (pos + needed > MAX_SAMPLES)\n\t\t\treturn -1;\n\t\tif (libxmp_realloc_samples(m, pos + needed + 64) < 0)\n\t\t\treturn -1;\n\t}\n\treturn 0;\n}\n\nstatic int med4_load_sampled_instrument(HIO_HANDLE *f, struct module_data *m,\n\tint i, int length, int *smp_idx, struct temp_inst *temp_inst)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_instrument *xxi;\n\tstruct xmp_subinstrument *sub;\n\tstruct xmp_sample *xxs;\n\tint j, k;\n\n\txxi = &mod->xxi[i];\n\n\txxi->nsm = 1;\n\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t\treturn -1;\n\n\tsub = &xxi->sub[0];\n\n\tsub->vol = temp_inst[i].volume;\n\tsub->pan = 0x80;\n\tsub->xpo = temp_inst[i].transpose;\n\tsub->sid = *smp_idx;\n\n\t/* May need to add samples due to external synths. */\n\tif (med4_sample_check(m, *smp_idx, 1) < 0)\n\t\treturn -1;\n\n\txxs = &mod->xxs[*smp_idx];\n\n\txxs->len = length;\n\txxs->lps = temp_inst[i].loop_start;\n\txxs->lpe = temp_inst[i].loop_end;\n\txxs->flg = temp_inst[i].loop_end > 2 ? XMP_SAMPLE_LOOP : 0;\n\n\tD_(D_INFO \"  %04x %04x %04x %c V%02x %+03d\",\n\t\t\txxs->len, mod->xxs[*smp_idx].lps, xxs->lpe,\n\t\t\txxs->flg & XMP_SAMPLE_LOOP ? 'L' : ' ',\n\t\t\tsub->vol, sub->xpo);\n\n\tif (libxmp_load_sample(m, f, 0, xxs, NULL) < 0)\n\t\treturn -1;\n\n\t/* Limit range to 3 octave (see MED.El toro) */\n\tfor (j = 0; j < 9; j++) {\n\t\tfor (k = 0; k < 12; k++) {\n\t\t\tint xpo = 0;\n\n\t\t\tif (j < 4)\n\t\t\t\txpo = 12 * (4 - j);\n\t\t\telse if (j > 6)\n\t\t\t\txpo = -12 * (j - 6);\n\n\t\t\txxi->map[12 * j + k].xpo = xpo;\n\t\t}\n\t}\n\n\t(*smp_idx)++;\n\treturn 0;\n}\n\n/* This is very similar to MMD1 synth/hybrid instruments,\n * but just different enough to be reimplemented here.\n */\nstatic int med4_load_synth_instrument(HIO_HANDLE *f, struct module_data *m,\n\tint i, int type, int *smp_idx, struct temp_inst *temp_inst)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_instrument *xxi;\n\tstruct xmp_subinstrument *sub;\n\tstruct xmp_sample *xxs;\n\tstruct med_instrument_extras *ie;\n\tstruct SynthInstr synth;\n\tint j;\n\tint type2;\n\tlong pos;\n\n\tpos = hio_tell(f);\n\tif (pos < 0)\n\t\treturn -1;\n\n\tif (hio_read32b(f) != MAGIC4('M','S','H',0))\n\t\treturn -1;\n\n\ttype2 = (int16)hio_read16b(f);\n\tif (type2 != -1 && type2 != -2) {\n\t\tD_(D_WARN \"invalid type %04x on synth/hybrid %d\", type2, i);\n\t\treturn -1;\n\t}\n\tif (type != type2) {\n\t\tD_(D_WARN \"type mismatch on synth/hybrid %d: %04x != %04x\", i, type, type2);\n\t}\n\n\thio_read16b(f);\t/* ? - 0000 */\n\thio_read16b(f);\t/* ? - 0000 */\n\tsynth.rep = hio_read16b(f);\t\t/* ? */\n\tsynth.replen = hio_read16b(f);\t/* ? */\n\tsynth.voltbllen = hio_read16b(f);\n\tsynth.wftbllen = hio_read16b(f);\n\tsynth.volspeed = hio_read8(f);\n\tsynth.wfspeed = hio_read8(f);\n\tsynth.wforms = hio_read16b(f);\n\n\tif (synth.wforms == 0xffff)\n\t\treturn 0;\n\n\t/* Sanity check */\n\tif (synth.voltbllen > 128 ||\n\t    synth.wftbllen > 128 ||\n\t    synth.wforms > 64) {\n\t\treturn -1;\n\t}\n\n\thio_read(synth.voltbl, 1, synth.voltbllen, f);\n\thio_read(synth.wftbl, 1, synth.wftbllen, f);\n\n\tfor (j = 0; j < synth.wforms; j++)\n\t\tsynth.wf[j] = hio_read32b(f);\n\n\tif (hio_error(f))\n\t\treturn -1;\n\n\tD_(D_INFO \"  VS:%02x WS:%02x WF:%02x %02x %+03d %3.3s\",\n\t\t\tsynth.volspeed, synth.wfspeed,\n\t\t\tsynth.wforms & 0xff,\n\t\t\ttemp_inst[i].volume,\n\t\t\ttemp_inst[i].transpose /*,\n\t\t\texp_smp.finetune*/,\n\t\t\ttype == -1 ? \"Syn\" : \"Hyb\");\n\n\txxi = &mod->xxi[i];\n\n\tif (libxmp_med_new_instrument_extras(xxi) != 0)\n\t\treturn -1;\n\n\txxi->nsm = synth.wforms;\n\tif (libxmp_alloc_subinstrument(mod, i, synth.wforms) < 0)\n\t\treturn -1;\n\n\tie = MED_INSTRUMENT_EXTRAS(*xxi);\n\tie->vts = synth.volspeed;\n\tie->wts = synth.wfspeed;\n\tie->vtlen = synth.voltbllen;\n\tie->wtlen = synth.wftbllen;\n\n\t/* Synth may be external--can't always calculate ahead of time. */\n\tif (med4_sample_check(m, *smp_idx, synth.wforms) < 0)\n\t\treturn -1;\n\n\tj = 0;\n\tif (type == -2 && synth.wforms > 0) { /* Hybrid */\n\t\tuint32 length;\n\n\t\thio_seek(f, pos + synth.wf[0], SEEK_SET);\n\n\t\tlength = hio_read32b(f);\n\t\tif (hio_read16b(f) != 0) {\n\t\t\tD_(D_CRIT \"hybrid %d has non-sample at pos 0\", i);\n\t\t\treturn -1;\n\t\t}\n\n\t\tsub = &xxi->sub[0];\n\n\t\tsub->pan = 0x80;\n\t\tsub->vol = temp_inst[i].volume;\n\t\tsub->xpo = temp_inst[i].transpose;\n\t\tsub->sid = *smp_idx;\n\t\tsub->fin = 0 /*exp_smp.finetune*/;\n\n\t\txxs = &mod->xxs[*smp_idx];\n\n\t\txxs->len = length;\n\t\txxs->lps = temp_inst[i].loop_start;\n\t\txxs->lpe = temp_inst[i].loop_end;\n\t\txxs->flg = temp_inst[i].loop_end > 2 ?\n\t\t\t\t\tXMP_SAMPLE_LOOP : 0;\n\n\t\tD_(D_INFO \"  %05x %05x %05x %02x %+03d\",\n\t\t\t\txxs->len, xxs->lps, xxs->lpe,\n\t\t\t\tsub->vol, sub->xpo /*, sub->fin >> 4*/);\n\n\t\tif (libxmp_load_sample(m, f, 0, xxs, NULL) < 0) {\n\t\t\tD_(D_CRIT \"failed to load hybrid %d sample\", i);\n\t\t\treturn -1;\n\t\t}\n\n\t\t(*smp_idx)++;\n\t\tj++;\n\t}\n\n\tfor (; j < synth.wforms; j++) {\n\t\t/* Sanity check */\n\t\tif (*smp_idx >= mod->smp) {\n\t\t\tD_(D_CRIT \"internal error\");\n\t\t\treturn -1;\n\t\t}\n\n\t\tsub = &xxi->sub[j];\n\n\t\tsub->pan = 0x80;\n\t\tsub->vol = 64;\n\t\tsub->xpo = -24;\n\t\tsub->sid = *smp_idx;\n\t\tsub->fin = 0 /*exp_smp.finetune*/;\n\n\t\thio_seek(f, pos + synth.wf[j], SEEK_SET);\n\n\t\txxs = &mod->xxs[*smp_idx];\n\n\t\txxs->len = hio_read16b(f) * 2;\n\t\txxs->lps = 0;\n\t\txxs->lpe = xxs->len;\n\t\txxs->flg = XMP_SAMPLE_LOOP;\n\n\t\tif (libxmp_load_sample(m, f, 0, xxs, NULL) < 0) {\n\t\t\tD_(D_CRIT \"failed to load synth/hybrid %d waveform %d\", i, j);\n\t\t\treturn -1;\n\t\t}\n\n\t\t(*smp_idx)++;\n\t}\n\n\tif (mmd_alloc_tables(m, i, &synth) != 0) {\n\t\tD_(D_CRIT \"failed to initialize synth/hybrid %d tables\", i);\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int med4_load_instrument(HIO_HANDLE *f, struct module_data *m,\n\tint i, int length, int type, int *smp_idx, struct temp_inst *temp_inst)\n{\n\tif (type == 0) {\t\t\t/* Sampled */\n\t\treturn med4_load_sampled_instrument(f, m, i, length,\n\t\t\tsmp_idx, temp_inst);\n\t}\n\n\tif (type == -1 || type == -2) {\t\t/* Synthetic or Hybrid */\n\t\tlong pos = hio_tell(f);\n\t\tif (pos < 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (med4_load_synth_instrument(f, m, i, type,\n\t\t    smp_idx, temp_inst) < 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\thio_seek(f, pos + length, SEEK_SET);\n\t\treturn 0;\n\t}\n\n\t/* Skip unknown instrument type */\n\thio_seek(f, length, SEEK_CUR);\n\treturn 0;\n}\n\nstatic int med4_load_external_instrument(HIO_HANDLE *f, struct module_data *m,\n\tint i, int *smp_idx, struct temp_inst *temp_inst)\n{\n\tchar path[XMP_MAXPATH];\n\tchar ins_name[32];\n\tHIO_HANDLE *s = NULL;\n\tint length;\n\tint ret;\n\n\tif (libxmp_copy_name_for_fopen(ins_name, m->mod.xxi[i].name, 32) != 0)\n\t\treturn 0;\n\n\tif (!libxmp_find_instrument_file(m, path, sizeof(path), ins_name))\n\t\treturn 0;\n\n\tif ((s = hio_open(path, \"rb\")) == NULL) {\n\t\treturn 0;\n\t}\n\n\tlength = hio_size(s);\n\tif (length == 0) {\n\t\thio_close(s);\n\t\treturn 0;\n\t}\n\n\tif (length >= 6) {\n\t\t/* May be an external synth/hybrid */\n\t\tuint32 magic = hio_read32b(s);\n\t\tint type = (int16)hio_read16b(s);\n\t\thio_seek(s, 0, SEEK_SET);\n\n\t\tif (magic == MAGIC4('M','S','H',0) &&\n\t\t    (type == -1 || type == -2)) {\n\t\t\tret = med4_load_synth_instrument(s, m, i, type,\n\t\t\t\tsmp_idx, temp_inst);\n\n\t\t\thio_close(s);\n\t\t\treturn ret;\n\t\t}\n\t}\n\n\tret = med4_load_sampled_instrument(s, m, i, length, smp_idx, temp_inst);\n\thio_close(s);\n\treturn ret;\n}\n\nstatic int med4_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j, k, y;\n\tuint8 m0;\n\tuint64 mask;\n\tint transp, masksz;\n\tlong pos;\n\tint vermaj, vermin;\n\tuint8 trkvol[16], buf[1024];\n\tstruct xmp_event *event;\n\tint flags, hexvol = 0;\n\tint num_ins, num_smp;\n\tint smp_idx;\n\tint tempo;\n\tstruct temp_inst temp_inst[64];\n\n\tLOAD_INIT();\n\n\thio_read32b(f);\t\t/* Skip magic */\n\n\tvermaj = 2;\n\tvermin = 10;\n\n\t/*\n\t * Check if we have a MEDV chunk at the end of the file\n\t */\n\tif ((pos = hio_tell(f)) < 0) {\n\t\treturn -1;\n\t}\n\n\thio_seek(f, 0, SEEK_END);\n\tif (hio_tell(f) > 2000) {\n\t\thio_seek(f, -1024, SEEK_CUR);\n\t\thio_read(buf, 1, 1024, f);\n\t\tfor (i = 0; i < 1013; i++) {\n\t\t\tif (!memcmp(buf + i, \"MEDV\\000\\000\\000\\004\", 8)) {\n\t\t\t\tvermaj = *(buf + i + 10);\n\t\t\t\tvermin = *(buf + i + 11);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\thio_seek(f, start + pos, SEEK_SET);\n\n\tsnprintf(mod->type, XMP_NAME_SIZE, \"MED %d.%02d MED4\", vermaj, vermin);\n\n\tm0 = hio_read8(f);\n\n\tmask = masksz = 0;\n\tfor (i = 0; m0 != 0 && i < 8; i++, m0 <<= 1) {\n\t\tif (m0 & 0x80) {\n\t\t\tmask <<= 8;\n\t\t\tmask |= hio_read8(f);\n\t\t\tmasksz++;\n\t\t}\n\t}\n\n\t/* CID 128662 (#1 of 1): Bad bit shift operation (BAD_SHIFT)\n\t * large_shift: left shifting by more than 63 bits has undefined\n\t * behavior.\n\t */\n\tif (masksz > 0) {\n\t\tmask <<= 8 * (sizeof(mask) - masksz);\n\t}\n\t/*printf(\"m0=%x mask=%x\\n\", m0, mask);*/\n\n\t/* read instrument names in temporary space */\n\n\tnum_ins = 0;\n\tmemset(temp_inst, 0, sizeof(temp_inst));\n\tfor (i = 0; mask != 0 && i < 64; i++, mask <<= 1) {\n\t\tuint8 c, size;\n\t\tuint16 loop_len = 0;\n\n\t\tif ((int64)mask > 0)\n\t\t\tcontinue;\n\n\t\tnum_ins = i + 1;\n\n\t\t/* read flags */\n\t\tc = hio_read8(f);\n\n\t\t/* read instrument name */\n\t\tsize = hio_read8(f);\n\t\tfor (j = 0; j < size; j++)\n\t\t\tbuf[j] = hio_read8(f);\n\t\tbuf[j] = 0;\n#ifdef MED4_DEBUG\n\t\tprintf(\"%02x %02x %2d [%s]\\n\", i, c, size, buf);\n#endif\n\n\t\ttemp_inst[i].volume = 0x40;\n\n\t\tif ((c & 0x01) == 0)\n\t\t\ttemp_inst[i].loop_start = hio_read16b(f) << 1;\n\t\tif ((c & 0x02) == 0)\n\t\t\tloop_len = hio_read16b(f) << 1;\n\t\tif ((c & 0x04) == 0)\t/* ? Tanko2 (MED 3.00 demo) */\n\t\t\thio_read8(f);\n\t\tif ((c & 0x08) == 0)\t/* Tim Newsham's \"span\" */\n\t\t\thio_read8(f);\n\t\tif ((c & 0x30) == 0)\n\t\t\ttemp_inst[i].volume = hio_read8(f);\n\t\tif ((c & 0x40) == 0)\n\t\t\ttemp_inst[i].transpose = hio_read8s(f);\n\n\t\ttemp_inst[i].loop_end = temp_inst[i].loop_start + loop_len;\n\n\t\tlibxmp_copy_adjust(temp_inst[i].name, buf, 32);\n\t}\n\n\tmod->pat = hio_read16b(f);\n\tmod->len = hio_read16b(f);\n\n\tif (hio_error(f)) {\n\t\treturn -1;\n\t}\n\n#ifdef MED4_DEBUG\n\tprintf(\"pat=%x len=%x\\n\", mod->pat, mod->len);\n#endif\n\tif (mod->pat > 256 || mod->len > XMP_MAX_MOD_LENGTH)\n\t\treturn -1;\n\thio_read(mod->xxo, 1, mod->len, f);\n\n\t/* From MED V3.00 docs:\n\t *\n\t * The left proportional gadget controls the primary tempo. It canbe\n\t * 1 - 240. The bigger the number, the faster the speed. Note that\n\t * tempos 1 - 10 are Tracker-compatible (but obsolete, because\n\t * secondary tempo can be used now).\n\t */\n\ttempo = hio_read16b(f);\n\ttransp = hio_read8s(f);\n\tflags = hio_read8s(f);\n\tmod->spd = hio_read16b(f);\n\n\tmod->bpm = mmd_convert_tempo(tempo, 0, 0);\n\tm->time_factor = MED_TIME_FACTOR;\n\n\tm->quirk |= QUIRK_RTONCE; /* FF1 */\n\tif (~flags & 0x20)\t/* sliding */\n\t\tm->quirk |= QUIRK_VSALL | QUIRK_PBALL;\n\n\tif (flags & 0x10)\t/* dec/hex volumes */\n\t\thexvol = 1;\n\n\t/* This is just a guess... */\n\tif (vermaj == 2)\t/* Happy.med has tempo 5 but loads as 6 */\n\t\tmod->spd = flags & 0x20 ? 5 : 6;\n\n\thio_seek(f, 20, SEEK_CUR);\n\n\thio_read(trkvol, 1, 16, f);\n\thio_read8(f);\t\t/* master vol */\n\n\tMODULE_INFO();\n\n\tD_(D_INFO \"Play transpose: %d\", transp);\n\n\tfor (i = 0; i < 64; i++)\n\t\ttemp_inst[i].transpose += transp;\n\n\t/* Scan patterns to determine number of channels */\n\tmod->chn = 0;\n\tif ((pos = hio_tell(f)) < 0) {\n\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tint size, plen, chn;\n\n\t\tsize = hio_read8(f);\t/* pattern control block */\n\t\tchn = hio_read8(f);\n\t\tif (chn > mod->chn)\n\t\t\tmod->chn = chn;\n\t\thio_read8(f);\t\t/* skip number of rows */\n\t\tplen = hio_read16b(f);\n\n\t\thio_seek(f, size + plen - 4, SEEK_CUR);\n\t}\n\n\t/* Sanity check */\n\tif (mod->chn > 16) {\n\t\treturn -1;\n\t}\n\n\tmod->trk = mod->chn * mod->pat;\n\n\tif (libxmp_init_pattern(mod) < 0)\n\t\treturn -1;\n\n\thio_seek(f, pos, SEEK_SET);\n\n\t/* Load and convert patterns */\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tint size, plen, rows;\n\t\tuint8 ctl[4], chn;\n\t\tunsigned chmsk;\n\t\tuint32 linemask[8], fxmask[8], x;\n\t\tint num_masks;\n\t\tstruct stream stream;\n\n#ifdef MED4_DEBUG\n\t\tprintf(\"\\n===== PATTERN %d =====\\n\", i);\n\t\tprintf(\"offset = %lx\\n\", hio_tell(f));\n#endif\n\n\t\tsize = hio_read8(f);\t/* pattern control block */\n\t\tif ((pos = hio_tell(f)) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\tchn = hio_read8(f);\n\t\tif (chn > mod->chn) {\n\t\t\treturn -1;\n\t\t}\n\t\trows = (int)hio_read8(f) + 1;\n\t\tplen = hio_read16b(f);\n#ifdef MED4_DEBUG\n\t\tprintf(\"size = %02x\\n\", size);\n\t\tprintf(\"chn  = %01x\\n\", chn);\n\t\tprintf(\"rows = %01x\\n\", rows);\n\t\tprintf(\"plen = %04x\\n\", plen);\n#endif\n\t\t/* read control byte */\n\t\tfor (j = 0; j < 4; j++) {\n\t\t\tif (rows > j * 64)\n\t\t\t\tctl[j] = hio_read8(f);\n\t\t\telse\n\t\t\t\tbreak;\n#ifdef MED4_DEBUG\n\t\t\tprintf(\"ctl[%d] = %02x\\n\", j, ctl[j]);\n\n#endif\n\t\t}\n\n\t\tif (libxmp_alloc_pattern_tracks(mod, i, rows) < 0)\n\t\t\treturn -1;\n\n\t\t/* initialize masks */\n\t\tfor (y = 0; y < 8; y++) {\n\t\t\tlinemask[y] = 0;\n\t\t\tfxmask[y] = 0;\n\t\t}\n\n\t\t/* read masks */\n\t\tnum_masks = 0;\n\t\tfor (y = 0; y < 8; y++) {\n\t\t\tif (rows > y * 32) {\n\t\t\t\tint c = ctl[y / 2];\n\t\t\t\tint s = 4 * (y % 2);\n\t\t\t\tlinemask[y] = c & (0x80 >> s) ? ~0 :\n\t\t\t\t\t      c & (0x40 >> s) ? 0 : hio_read32b(f);\n\t\t\t\tfxmask[y]   = c & (0x20 >> s) ? ~0 :\n\t\t\t\t\t      c & (0x10 >> s) ? 0 : hio_read32b(f);\n\t\t\t\tnum_masks++;\n#ifdef MED4_DEBUG\n\t\t\t\tprintf(\"linemask[%d] = %08x\\n\", y, linemask[y]);\n\t\t\t\tprintf(\"fxmask[%d]   = %08x\\n\", y, fxmask[y]);\n#endif\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\thio_seek(f, pos + size, SEEK_SET);\n\t\tstream_init(f, &stream);\n\n\t\tfor (y = 0; y < num_masks; y++) {\n\n\t\tfor (j = 0; j < 32; j++) {\n\t\t\tint line = y * 32 + j;\n\n\t\t\tif (line >= rows)\n\t\t\t\tbreak;\n\n\t\t\tif (linemask[y] & 0x80000000) {\n\t\t\t\tchmsk = stream_read_aligned16(&stream, chn);\n\t\t\t\tfor (k = 0; k < chn; k++, chmsk <<= 1) {\n\t\t\t\t\tevent = &EVENT(i, k, line);\n\n\t\t\t\t\tif (chmsk & 0x8000) {\n\t\t\t\t\t\tx = stream_read12(&stream);\n\t\t\t\t\t\tevent->note = x >> 4;\n\t\t\t\t\t\tif (event->note)\n\t\t\t\t\t\t\tevent->note += 48;\n\t\t\t\t\t\tevent->ins  = x & 0x0f;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (fxmask[y] & 0x80000000) {\n\t\t\t\tchmsk = stream_read_aligned16(&stream, chn);\n\t\t\t\tfor (k = 0; k < chn; k++, chmsk <<= 1) {\n\t\t\t\t\tevent = &EVENT(i, k, line);\n\n\t\t\t\t\tif (chmsk & 0x8000) {\n\t\t\t\t\t\tx = stream_read12(&stream);\n\t\t\t\t\t\tevent->fxt = x >> 8;\n\t\t\t\t\t\tevent->fxp = x & 0xff;\n\t\t\t\t\t\tfix_effect(event, hexvol);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n#ifdef MED4_DEBUG\n\t\t\tprintf(\"%03d \", line);\n\t\t\tfor (k = 0; k < 4; k++) {\n\t\t\t\tevent = &EVENT(i, k, line);\n\t\t\t\tif (event->note)\n\t\t\t\t\tprintf(\"%03d\", event->note);\n\t\t\t\telse\n\t\t\t\t\tprintf(\"---\");\n\t\t\t\tprintf(\" %1x%1x%02x \",\n\t\t\t\t\tevent->ins, event->fxt, event->fxp);\n\t\t\t}\n\t\t\tprintf(\"\\n\");\n#endif\n\n\t\t\tlinemask[y] <<= 1;\n\t\t\tfxmask[y] <<= 1;\n\t\t}\n\n\t\t}\n\t\thio_seek(f, pos + size + plen, SEEK_SET);\n\t}\n\n\tmod->ins = num_ins;\n\n\tif (libxmp_med_new_module_extras(m) != 0)\n\t\treturn -1;\n\n\t/*\n\t * Load samples\n\t */\n\tif (~flags & FLAG_INSTRSATT) {\n\t\t/* Song file */\n\t\t/* Arbitrary initial count -- may grow during load. */\n\t\tmod->smp = mod->ins;\n\n\t\tif (libxmp_init_instrument(m) < 0)\n\t\t\treturn -1;\n\n\t\tsmp_idx = 0;\n\t\tfor (i = 0; i < num_ins; i++) {\n\t\t\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\n\t\t\tstrncpy((char *)xxi->name, temp_inst[i].name, 32);\n\t\t\txxi->name[31] = '\\0';\n\n\t\t\tD_(D_INFO \"[%2X] %-32.32s ???\", i, xxi->name);\n\n\t\t\tif (med4_load_external_instrument(f, m, i,\n\t\t\t    &smp_idx, temp_inst) < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\n\t\t/* Shrink samples to used */\n\t\tif (mod->smp > smp_idx) {\n\t\t\tif (libxmp_realloc_samples(m, smp_idx) < 0)\n\t\t\t\treturn -1;\n\t\t}\n\t\tgoto parse_iff;\n\t}\n\n\t/* Internal samples */\n\tmask = hio_read32b(f);\n\tif (mask == MAGIC4('M','E','D','V')) {\n\t\tD_(D_CRIT \"invalid module MED4 with no samples\");\n\t\treturn -1;\n\t}\n\tmask <<= 32;\n\tmask |= hio_read32b(f);\n\n\tmask <<= 1;\t/* no instrument #0 */\n\n\t/* obtain number of samples */\n\tif ((pos = hio_tell(f)) < 0) {\n\t\treturn -1;\n\t}\n\tnum_smp = 0;\n\t{\n\t\tint _len, _type;\n\t\tuint64 _mask = mask;\n\t\tfor (i = 0; _mask != 0 && i < 64; i++, _mask <<= 1) {\n\t\t\tif ((int64)_mask > 0)\n\t\t\t\tcontinue;\n\n\t\t\t_len = hio_read32b(f);\n\t\t\t_type = (int16)hio_read16b(f);\n\n\t\t\tif (_type == 0) {\n\t\t\t\tnum_smp++;\n\t\t\t} else if (_type == -1 || _type == -2) {\n\t\t\t\tif (_len < 22) {\n\t\t\t\t\tD_(D_CRIT \"invalid synth %d length\", i);\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\thio_seek(f, 20, SEEK_CUR);\n\t\t\t\tnum_smp += hio_read16b(f);\n\t\t\t\t_len -= 22;\n\t\t\t}\n\n\t\t\tif (_len < 0 || _len > hio_size(f)) {\n\t\t\t\tD_(D_CRIT \"invalid sample %d length\", i);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\thio_seek(f, _len, SEEK_CUR);\n\t\t}\n\t}\n\thio_seek(f, pos, SEEK_SET);\n\n\tmod->smp = num_smp;\n\n\tif (libxmp_init_instrument(m) < 0) {\n\t\treturn -1;\n\t}\n\n\tD_(D_INFO \"Instruments: %d\", mod->ins);\n\n\tsmp_idx = 0;\n\tfor (i = 0; mask != 0 && i < num_ins; i++, mask <<= 1) {\n\t\tint length, type;\n\t\tstruct xmp_instrument *xxi;\n\n\t\tif ((int64)mask > 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\txxi = &mod->xxi[i];\n\n\t\tlength = hio_read32b(f);\n\t\ttype = (int16)hio_read16b(f);\t/* instrument type */\n\n\t\tstrncpy((char *)xxi->name, temp_inst[i].name, 32);\n\t\txxi->name[31] = '\\0';\n\n\t\tD_(D_INFO \"[%2X] %-32.32s %d\", i, xxi->name, type);\n\n\t\tif (med4_load_instrument(f, m, i, length, type,\n\t\t    &smp_idx, temp_inst) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t/* Not sure what this was supposed to be, but it isn't present in\n\t * Synth-a-sysmic.med or any other MED4 module on ModLand. */\n\t/*hio_read16b(f);*/\t/* unknown */\n\n\t/* IFF-like section */\nparse_iff:\n\twhile (!hio_eof(f)) {\n\t\tint32 id, size, s2, ver;\n\n\t\tif ((id = hio_read32b(f)) <= 0)\n\t\t\tbreak;\n\n\t\tif ((size = hio_read32b(f)) <= 0)\n\t\t\tbreak;\n\n\t\tswitch (id) {\n\t\tcase MAGIC4('M','E','D','V'):\n\t\t\tver = hio_read32b(f);\n\t\t\tsize -= 4;\n\t\t\tvermaj = (ver & 0xff00) >> 8;\n\t\t\tvermin = (ver & 0xff);\n\t\t\tD_(D_INFO \"MED Version: %d.%0d\", vermaj, vermin);\n\t\t\tbreak;\n\t\tcase MAGIC4('A','N','N','O'):\n\t\t\t/* annotation */\n\t\t\ts2 = size < 1023 ? size : 1023;\n\t\t\tif ((m->comment = (char *) malloc(s2 + 1)) != NULL) {\n\t\t\t\tint read_len = hio_read(m->comment, 1, s2, f);\n\t\t\t\tm->comment[read_len] = '\\0';\n\n\t\t\t\tD_(D_INFO \"Annotation: %s\", m->comment);\n\t\t\t\tsize -= s2;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase MAGIC4('H','L','D','C'):\n\t\t\t/* hold & decay */\n\t\t\tbreak;\n\t\t}\n\n\t\thio_seek(f, size, SEEK_CUR);\n\t}\n\n\tm->read_event_type = READ_EVENT_MED;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/mmd1_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/*\n * OctaMED v1.00b: ftp://ftp.funet.fi/pub/amiga/fish/501-600/ff579\n */\n\n#include \"med.h\"\n#include \"loader.h\"\n#include \"../med_extras.h\"\n\nstatic int mmd1_test(HIO_HANDLE *, char *, const int);\nstatic int mmd1_load (struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_mmd1 = {\n\t\"MED 2.10/OctaMED\",\n\tmmd1_test,\n\tmmd1_load\n};\n\nstatic int mmd1_test(HIO_HANDLE *f, char *t, const int start)\n{\n\tchar id[4];\n\tuint32 offset, len;\n\n\tif (hio_read(id, 1, 4, f) < 4)\n\t\treturn -1;\n\n\tif (memcmp(id, \"MMD0\", 4) && memcmp(id, \"MMD1\", 4) && memcmp(id, \"MMDC\", 4))\n\t\treturn -1;\n\n\thio_seek(f, 28, SEEK_CUR);\n\toffset = hio_read32b(f);\t\t/* expdata_offset */\n\n\tif (offset) {\n\t\thio_seek(f, start + offset + 44, SEEK_SET);\n\t\toffset = hio_read32b(f);\n\t\tlen = hio_read32b(f);\n\t\thio_seek(f, start + offset, SEEK_SET);\n\t\tlibxmp_read_title(f, t, len);\n\t} else {\n\t\tlibxmp_read_title(f, t, 0);\n\t}\n\n\treturn 0;\n}\n\n\nstatic int mmd1_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j, k;\n\tstruct MMD0 header;\n\tstruct MMD0song song;\n\tstruct MMD1Block block;\n\tstruct InstrExt *exp_smp = NULL;\n\tstruct MMD0exp expdata;\n\tstruct xmp_event *event;\n\tuint32 *blockarr = NULL;\n\tuint32 *smplarr = NULL;\n\tuint8 *patbuf = NULL;\n\tint ver = 0;\n\tint mmdc = 0;\n\tint smp_idx = 0;\n\tint song_offset;\n\tint blockarr_offset;\n\tint smplarr_offset;\n\tint expdata_offset;\n\tint expsmp_offset;\n\tint songname_offset;\n\tint iinfo_offset;\n\tint annotxt_offset;\n\tint bpm_on, bpmlen, med_8ch, hexvol;\n\tint max_lines;\n\tint retval = -1;\n\n\tLOAD_INIT();\n\n\thio_read(&header.id, 4, 1, f);\n\n\tver = *((char *)&header.id + 3) - '1' + 1;\n\tif (ver > 1) {\n\t\tver = 0;\n\t\tmmdc = 1;\n\t}\n\n\tD_(D_WARN \"load header\");\n\theader.modlen = hio_read32b(f);\n\tsong_offset = hio_read32b(f);\n\tD_(D_INFO \"song_offset = 0x%08x\", song_offset);\n\thio_read16b(f);\n\thio_read16b(f);\n\tblockarr_offset = hio_read32b(f);\n\tD_(D_INFO \"blockarr_offset = 0x%08x\", blockarr_offset);\n\thio_read32b(f);\n\tsmplarr_offset = hio_read32b(f);\n\tD_(D_INFO \"smplarr_offset = 0x%08x\", smplarr_offset);\n\thio_read32b(f);\n\texpdata_offset = hio_read32b(f);\n\tD_(D_INFO \"expdata_offset = 0x%08x\", expdata_offset);\n\thio_read32b(f);\n\theader.pstate = hio_read16b(f);\n\theader.pblock = hio_read16b(f);\n\theader.pline = hio_read16b(f);\n\theader.pseqnum = hio_read16b(f);\n\theader.actplayline = hio_read16b(f);\n\theader.counter = hio_read8(f);\n\theader.extra_songs = hio_read8(f);\n\n\t/*\n\t * song structure\n\t */\n\tD_(D_WARN \"load song\");\n\tif (hio_seek(f, start + song_offset, SEEK_SET) != 0) {\n\t\tD_(D_CRIT \"seek error at song\");\n\t\treturn -1;\n\t}\n\tfor (i = 0; i < 63; i++) {\n\t\tsong.sample[i].rep = hio_read16b(f);\n\t\tsong.sample[i].replen = hio_read16b(f);\n\t\tsong.sample[i].midich = hio_read8(f);\n\t\tsong.sample[i].midipreset = hio_read8(f);\n\t\tsong.sample[i].svol = hio_read8(f);\n\t\tsong.sample[i].strans = hio_read8s(f);\n\t}\n\tsong.numblocks = hio_read16b(f);\n\tsong.songlen = hio_read16b(f);\n\n\t/* Sanity check */\n\tif (song.numblocks > 255 || song.songlen > 256) {\n\t\tD_(D_CRIT \"unsupported block count (%d) or song length (%d)\",\n\t\t   song.numblocks, song.songlen);\n\t\treturn -1;\n\t}\n\n\tD_(D_INFO \"song.songlen = %d\", song.songlen);\n\tfor (i = 0; i < 256; i++)\n\t\tsong.playseq[i] = hio_read8(f);\n\tsong.deftempo = hio_read16b(f);\n\tsong.playtransp = hio_read8(f);\n\tsong.flags = hio_read8(f);\n\tsong.flags2 = hio_read8(f);\n\tsong.tempo2 = hio_read8(f);\n\tfor (i = 0; i < 16; i++)\n\t\tsong.trkvol[i] = hio_read8(f);\n\tsong.mastervol = hio_read8(f);\n\tsong.numsamples = hio_read8(f);\n\n\t/* Sanity check */\n\tif (song.numsamples > 63) {\n\t\tD_(D_CRIT \"invalid instrument count %d\", song.numsamples);\n\t\treturn -1;\n\t}\n\n\t/*\n\t * convert header\n\t */\n\tm->c4rate = C4_NTSC_RATE;\n\tm->quirk |= QUIRK_RTONCE; /* FF1 */\n\tm->quirk |= song.flags & FLAG_STSLIDE ? 0 : QUIRK_VSALL | QUIRK_PBALL;\n\thexvol = song.flags & FLAG_VOLHEX;\n\tmed_8ch = song.flags & FLAG_8CHANNEL;\n\tbpm_on = song.flags2 & FLAG2_BPM;\n\tbpmlen = 1 + (song.flags2 & FLAG2_BMASK);\n\tm->time_factor = MED_TIME_FACTOR;\n\n\tmmd_set_bpm(m, med_8ch, song.deftempo, bpm_on, bpmlen);\n\n\tmod->spd = song.tempo2;\n\tmod->pat = song.numblocks;\n\tmod->ins = song.numsamples;\n\tmod->len = song.songlen;\n\tmod->rst = 0;\n\tmod->chn = 0;\n\tmemcpy(mod->xxo, song.playseq, mod->len);\n\tmod->name[0] = 0;\n\n\t/*\n\t * Read smplarr\n\t */\n\tD_(D_WARN \"read smplarr\");\n\tsmplarr = (uint32 *) malloc(mod->ins * sizeof(uint32));\n\tif (smplarr == NULL) {\n\t\treturn -1;\n\t}\n\tif (hio_seek(f, start + smplarr_offset, SEEK_SET) != 0) {\n\t\tD_(D_CRIT \"seek error at smplarr\");\n\t\tgoto err_cleanup;\n\t}\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tsmplarr[i] = hio_read32b(f);\n\t\tif (hio_eof(f)) {\n\t\t\tD_(D_CRIT \"read error at smplarr pos %d\", i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\t}\n\n\t/*\n\t * Obtain number of samples from each instrument\n\t */\n\tmod->smp = 0;\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tint16 type;\n\t\tif (smplarr[i] == 0)\n\t\t\tcontinue;\n\t\tif (hio_seek(f, start + smplarr[i], SEEK_SET) != 0) {\n\t\t\tD_(D_CRIT \"seek error at instrument %d\", i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\thio_read32b(f);\t\t\t\t/* length */\n\t\ttype = hio_read16b(f);\n\t\tif (type == -1 || type == -2) {\t\t/* type is synth? */\n\t\t\tint wforms;\n\t\t\thio_seek(f, 14, SEEK_CUR);\n\t\t\twforms = hio_read16b(f);\n\n\t\t\t/* Sanity check */\n\t\t\tif (wforms > 256) {\n\t\t\t\tD_(D_CRIT \"invalid wform count at instrument %d\", i);\n\t\t\t\tgoto err_cleanup;\n\t\t\t}\n\n\t\t\tmod->smp += wforms;\n\t\t} else if (type >= 1 && type <= 6) {\n\t\t\tmod->smp += mmd_num_oct[type - 1];\n\t\t} else {\n\t\t\tmod->smp++;\n\t\t}\n\t}\n\n\t/*\n\t * expdata\n\t */\n\tD_(D_WARN \"load expdata\");\n\texpdata.s_ext_entries = 0;\n\texpdata.s_ext_entrsz = 0;\n\texpdata.i_ext_entries = 0;\n\texpdata.i_ext_entrsz = 0;\n\texpsmp_offset = 0;\n\tiinfo_offset = 0;\n\tif (expdata_offset) {\n\t\tif (hio_seek(f, start + expdata_offset, SEEK_SET) != 0) {\n\t\t\tD_(D_CRIT \"seek error at expdata\");\n\t\t\tgoto err_cleanup;\n\t\t}\n\t\thio_read32b(f);\n\t\texpsmp_offset = hio_read32b(f);\n\t\tD_(D_INFO \"expsmp_offset = 0x%08x\", expsmp_offset);\n\t\texpdata.s_ext_entries = hio_read16b(f);\n\t\texpdata.s_ext_entrsz = hio_read16b(f);\n\t\tannotxt_offset = hio_read32b(f);\n\t\texpdata.annolen = hio_read32b(f);\n\t\tiinfo_offset = hio_read32b(f);\n\t\tD_(D_INFO \"iinfo_offset = 0x%08x\", iinfo_offset);\n\t\texpdata.i_ext_entries = hio_read16b(f);\n\t\texpdata.i_ext_entrsz = hio_read16b(f);\n\n\t\t/* Sanity check */\n\t\tif (expsmp_offset < 0 ||\n\t\t    annotxt_offset < 0 ||\n\t\t    expdata.annolen > 0x10000 ||\n\t\t    iinfo_offset < 0) {\n\t\t\tD_(D_CRIT \"invalid expdata (annotxt=0x%08x annolen=0x%08x)\",\n\t\t\t   annotxt_offset, expdata.annolen);\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\thio_read32b(f);\n\t\thio_read32b(f);\n\t\thio_read32b(f);\n\t\thio_read32b(f);\n\t\tsongname_offset = hio_read32b(f);\n\t\texpdata.songnamelen = hio_read32b(f);\n\t\tD_(D_INFO \"songname_offset = 0x%08x\", songname_offset);\n\t\tD_(D_INFO \"expdata.songnamelen = %d\", expdata.songnamelen);\n\n\t\thio_seek(f, start + songname_offset, SEEK_SET);\n\t\tfor (i = 0; i < expdata.songnamelen; i++) {\n\t\t\tif (i >= XMP_NAME_SIZE)\n\t\t\t\tbreak;\n\t\t\tmod->name[i] = hio_read8(f);\n\t\t}\n\n\t\t/* Read annotation */\n\t\tif (annotxt_offset != 0 && expdata.annolen != 0) {\n\t\t\tD_(D_INFO \"annotxt_offset = 0x%08x\", annotxt_offset);\n\t\t\tm->comment = (char *) malloc(expdata.annolen + 1);\n\t\t\tif (m->comment != NULL) {\n\t\t\t\thio_seek(f, start + annotxt_offset, SEEK_SET);\n\t\t\t\thio_read(m->comment, 1, expdata.annolen, f);\n\t\t\t\tm->comment[expdata.annolen] = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * Read blockarr.\n\t */\n\tD_(D_WARN \"read blockarr\");\n\tblockarr = (uint32 *) malloc(mod->pat * sizeof(uint32));\n\tif (blockarr == NULL) {\n\t\tgoto err_cleanup;\n\t}\n\tif (hio_seek(f, start + blockarr_offset, SEEK_SET) != 0) {\n\t\tD_(D_CRIT \"seek error at blockarr\");\n\t\tgoto err_cleanup;\n\t}\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tblockarr[i] = hio_read32b(f);\n\t\tif (hio_error(f)) {\n\t\t\tD_(D_CRIT \"read error at blockarr pos %d\", i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\t}\n\n\t/*\n\t * Quickly scan patterns to check the number of channels\n\t */\n\tD_(D_WARN \"find number of channels\");\n\n\tmax_lines = 1;\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tD_(D_INFO \"block %d block_offset = 0x%08x\", i, blockarr[i]);\n\t\tif (blockarr[i] == 0)\n\t\t\tcontinue;\n\n\t\tif (hio_seek(f, start + blockarr[i], SEEK_SET) != 0) {\n\t\t\tD_(D_CRIT \"seek error at block %d\", i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\tif (ver > 0) {\n\t\t\tblock.numtracks = hio_read16b(f);\n\t\t\tblock.lines = hio_read16b(f);\n\t\t} else {\n\t\t\tblock.numtracks = hio_read8(f);\n\t\t\tblock.lines = hio_read8(f);\n\t\t}\n\n\t\t/* Sanity check--Amiga OctaMED files have an upper bound of 3200 lines per block. */\n\t\tif (block.lines + 1 > 3200) {\n\t\t\tD_(D_CRIT \"invalid line count %d in block %d\", block.lines + 1, i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\tif (block.numtracks > mod->chn) {\n\t\t\tmod->chn = block.numtracks;\n\t\t}\n\t\tif (block.lines + 1 > max_lines) {\n\t\t\tmax_lines = block.lines + 1;\n\t\t}\n\t}\n\n\t/* Sanity check */\n\t/* MMD0/MMD1 can't have more than 16 channels... */\n\tif (mod->chn > MIN(16, XMP_MAX_CHANNELS)) {\n\t\tD_(D_CRIT \"invalid channel count %d\", mod->chn);\n\t\tgoto err_cleanup;\n\t}\n\n\tmod->trk = mod->pat * mod->chn;\n\n\tmmd_tracker_version(m, ver, mmdc, expdata_offset ? &expdata : NULL);\n\n\tMODULE_INFO();\n\n\tD_(D_INFO \"BPM mode: %s (length = %d)\", bpm_on ? \"on\" : \"off\", bpmlen);\n\tD_(D_INFO \"Song transpose: %d\", song.playtransp);\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\t/*\n\t * Read and convert patterns\n\t */\n\tD_(D_WARN \"read patterns\");\n\tif (libxmp_init_pattern(mod) < 0)\n\t\tgoto err_cleanup;\n\n\tif ((patbuf = (uint8 *)malloc(mod->chn * max_lines * 4)) == NULL) {\n\t\tgoto err_cleanup;\n\t}\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tuint8 *pos;\n\t\tsize_t size;\n\n\t\tif (blockarr[i] == 0)\n\t\t\tcontinue;\n\n\t\tif (hio_seek(f, start + blockarr[i], SEEK_SET) != 0) {\n\t\t\tD_(D_CRIT \"seek error at block %d\", i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\tif (ver > 0) {\n\t\t\tblock.numtracks = hio_read16b(f);\n\t\t\tblock.lines = hio_read16b(f);\n\t\t\thio_read32b(f);\n\t\t} else {\n\t\t\tblock.numtracks = hio_read8(f);\n\t\t\tblock.lines = hio_read8(f);\n\t\t}\n\n\t\tsize = block.numtracks * (block.lines + 1) * (ver ? 4 : 3);\n\n\t\tif (mmdc) {\n\t\t\t/* MMDC is just MMD0 with simple pattern packing. */\n\t\t\tmemset(patbuf, 0, size);\n\t\t\tfor (j = 0; j < size;) {\n\t\t\t\tunsigned pack = hio_read8(f);\n\t\t\t\tif (hio_error(f)) {\n\t\t\t\t\tD_(D_CRIT \"read error in block %d\", i);\n\t\t\t\t\tgoto err_cleanup;\n\t\t\t\t}\n\n\t\t\t\tif (pack & 0x80) {\n\t\t\t\t\t/* Run of 0 */\n\t\t\t\t\tj += 256 - pack;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t/* Uncompressed block */\n\t\t\t\tpack++;\n\t\t\t\tif (pack > size - j)\n\t\t\t\t\tpack = size - j;\n\n\t\t\t\tif (hio_read(patbuf + j, 1, pack, f) < pack) {\n\t\t\t\t\tD_(D_CRIT \"read error in block %d\", i);\n\t\t\t\t\tgoto err_cleanup;\n\t\t\t\t}\n\t\t\t\tj += pack;\n\t\t\t}\n\t\t} else {\n\t\t\tif (hio_read(patbuf, 1, size, f) < size) {\n\t\t\t\tD_(D_CRIT \"read error in block %d\", i);\n\t\t\t\tgoto err_cleanup;\n\t\t\t}\n\t\t}\n\n\t\tif (libxmp_alloc_pattern_tracks_long(mod, i, block.lines + 1) < 0)\n\t\t\tgoto err_cleanup;\n\n\t\tpos = patbuf;\n\t\tif (ver > 0) {\t\t/* MMD1 */\n\t\t\tfor (j = 0; j < mod->xxp[i]->rows; j++) {\n\t\t\t\tfor (k = 0; k < block.numtracks; k++) {\n\t\t\t\t\tevent = &EVENT(i, k, j);\n\t\t\t\t\tevent->note = pos[0] & 0x7f;\n\t\t\t\t\tif (event->note)\n\t\t\t\t\t\tevent->note +=\n\t\t\t\t\t\t    12 + song.playtransp;\n\n\t\t\t\t\tif (event->note >= XMP_MAX_KEYS)\n\t\t\t\t\t\tevent->note = 0;\n\n\t\t\t\t\tevent->ins = pos[1] & 0x3f;\n\n\t\t\t\t\t/* Decay */\n\t\t\t\t\tif (event->ins && !event->note) {\n\t\t\t\t\t\tevent->f2t = FX_MED_HOLD;\n\t\t\t\t\t}\n\n\t\t\t\t\tevent->fxt = pos[2];\n\t\t\t\t\tevent->fxp = pos[3];\n\t\t\t\t\tmmd_xlat_fx(event, bpm_on, bpmlen,\n\t\t\t\t\t\t\tmed_8ch, hexvol);\n\t\t\t\t\tpos += 4;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\t\t/* MMD0 */\n\t\t\tfor (j = 0; j < mod->xxp[i]->rows; j++) {\n\t\t\t\tfor (k = 0; k < block.numtracks; k++) {\n\t\t\t\t\tevent = &EVENT(i, k, j);\n\t\t\t\t\tevent->note = pos[0] & 0x3f;\n\t\t\t\t\tif (event->note)\n\t\t\t\t\t\tevent->note += 12 + song.playtransp;\n\n\t\t\t\t\tif (event->note >= XMP_MAX_KEYS)\n\t\t\t\t\t\tevent->note = 0;\n\n\t\t\t\t\tevent->ins =\n\t\t\t\t\t    (pos[1] >> 4) | ((pos[0] & 0x80) >> 3)\n\t\t\t\t\t    | ((pos[0] & 0x40) >> 1);\n\n\t\t\t\t\t/* Decay */\n\t\t\t\t\tif (event->ins && !event->note) {\n\t\t\t\t\t\tevent->f2t = FX_MED_HOLD;\n\t\t\t\t\t}\n\n\t\t\t\t\tevent->fxt = pos[1] & 0x0f;\n\t\t\t\t\tevent->fxp = pos[2];\n\t\t\t\t\tmmd_xlat_fx(event, bpm_on, bpmlen,\n\t\t\t\t\t\t\tmed_8ch, hexvol);\n\t\t\t\t\tpos += 3;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tfree(patbuf);\n\tpatbuf = NULL;\n\n\tif (libxmp_med_new_module_extras(m))\n\t\tgoto err_cleanup;\n\n\t/*\n\t * Read and convert instruments and samples\n\t */\n\tD_(D_WARN \"read instruments\");\n\tif (libxmp_init_instrument(m) < 0)\n\t\tgoto err_cleanup;\n\n\tD_(D_INFO \"Instruments: %d\", mod->ins);\n\n\t/* Instrument extras */\n\texp_smp = (struct InstrExt *) calloc(mod->ins, sizeof(struct InstrExt));\n\tif (exp_smp == NULL) {\n\t\tgoto err_cleanup;\n\t}\n\n\tif (expsmp_offset) {\n\t\tif (hio_seek(f, start + expsmp_offset, SEEK_SET) != 0) {\n\t\t\tD_(D_CRIT \"seek error at expsmp\");\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\tfor (i = 0; i < mod->ins && i < expdata.s_ext_entries; i++) {\n\t\t\tint skip = expdata.s_ext_entrsz - 4;\n\n\t\t\tD_(D_INFO \"sample %d expsmp_offset = 0x%08lx\", i, hio_tell(f));\n\n\t\t\texp_smp[i].hold = hio_read8(f);\n\t\t\texp_smp[i].decay = hio_read8(f);\n\t\t\texp_smp[i].suppress_midi_off = hio_read8(f);\n\t\t\texp_smp[i].finetune = hio_read8(f);\n\n\t\t\tif (hio_error(f)) {\n\t\t\t\tD_(D_CRIT \"read error at expsmp\");\n\t\t\t\tgoto err_cleanup;\n\t\t\t}\n\n\t\t\tif (skip && hio_seek(f, skip, SEEK_CUR) != 0) {\n\t\t\t\tD_(D_CRIT \"seek error at expsmp\");\n\t\t\t\tgoto err_cleanup;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Instrument names */\n\tif (iinfo_offset) {\n\t\tuint8 name[40];\n\n\t\tif (hio_seek(f, start + iinfo_offset, SEEK_SET) != 0) {\n\t\t\tD_(D_CRIT \"seek error at iinfo\");\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\tfor (i = 0; i < mod->ins && i < expdata.i_ext_entries; i++) {\n\t\t\tint skip = expdata.i_ext_entrsz - 40;\n\n\t\t\tD_(D_INFO \"sample %d iinfo_offset = 0x%08lx\", i, hio_tell(f));\n\n\t\t\tif (hio_read(name, 40, 1, f) < 1) {\n\t\t\t\tD_(D_CRIT \"read error at iinfo %d\", i);\n\t\t\t\tgoto err_cleanup;\n\t\t\t}\n\t\t\tlibxmp_instrument_name(mod, i, name, 40);\n\n\t\t\tif (skip && hio_seek(f, skip, SEEK_CUR) != 0) {\n\t\t\t\tD_(D_CRIT \"seek error at iinfo %d\", i);\n\t\t\t\tgoto err_cleanup;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Sample data */\n\tfor (smp_idx = i = 0; i < mod->ins; i++) {\n\t\tD_(D_INFO \"sample %d smpl_offset = 0x%08x\", i, smplarr[i]);\n\t\tif (smplarr[i] == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (hio_seek(f, start + smplarr[i], SEEK_SET) < 0) {\n\t\t\tD_(D_CRIT \"seek error at instrument %d\", i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\tsmp_idx = mmd_load_instrument(f, m, i, smp_idx, &expdata,\n\t\t\t&exp_smp[i], &song.sample[i], ver);\n\n\t\tif (smp_idx < 0) {\n\t\t\tgoto err_cleanup;\n\t\t}\n\t}\n\n\tfor (i = 0; i < mod->chn; i++) {\n\t\tmod->xxc[i].vol = song.trkvol[i];\n\t\tmod->xxc[i].pan = DEFPAN((((i + 1) / 2) % 2) * 0xff);\n\t}\n\n\tm->read_event_type = READ_EVENT_MED;\n\tretval = 0;\n\n    err_cleanup:\n\tfree(exp_smp);\n\tfree(blockarr);\n\tfree(smplarr);\n\tfree(patbuf);\n\n\treturn retval;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/mmd3_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"med.h\"\n#include \"loader.h\"\n#include \"../med_extras.h\"\n\nstatic int mmd3_test (HIO_HANDLE *, char *, const int);\nstatic int mmd3_load (struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_mmd3 = {\n\t\"OctaMED\",\n\tmmd3_test,\n\tmmd3_load\n};\n\nstatic int mmd3_test(HIO_HANDLE *f, char *t, const int start)\n{\n\tchar id[4];\n\tuint32 offset, len;\n\n\tif (hio_read(id, 1, 4, f) < 4)\n\t\treturn -1;\n\n\tif (memcmp(id, \"MMD2\", 4) && memcmp(id, \"MMD3\", 4))\n\t\treturn -1;\n\n\thio_seek(f, 28, SEEK_CUR);\n\toffset = hio_read32b(f);\t\t/* expdata_offset */\n\n\tif (offset) {\n\t\thio_seek(f, start + offset + 44, SEEK_SET);\n\t\toffset = hio_read32b(f);\n\t\tlen = hio_read32b(f);\n\t\thio_seek(f, start + offset, SEEK_SET);\n\t\tlibxmp_read_title(f, t, len);\n\t} else {\n\t\tlibxmp_read_title(f, t, 0);\n\t}\n\n\treturn 0;\n}\n\n\nstatic int mmd3_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j, k;\n\tstruct MMD0 header;\n\tstruct MMD2song song;\n\tstruct MMD1Block block;\n\tstruct InstrExt *exp_smp = NULL;\n\tstruct MMD0exp expdata;\n\tstruct xmp_event *event;\n\tuint32 *blockarr = NULL;\n\tuint32 *smplarr = NULL;\n\tuint8 *patbuf = NULL;\n\tint ver = 0;\n\tint smp_idx = 0;\n\tint song_offset;\n\tint seqtable_offset;\n\tint trackvols_offset;\n\tint trackpans_offset;\n\tint blockarr_offset;\n\tint smplarr_offset;\n\tint expdata_offset;\n\tint expsmp_offset;\n\tint songname_offset;\n\tint iinfo_offset;\n\tint mmdinfo_offset;\n\tint playseq_offset;\n\tint bpm_on, bpmlen, med_8ch, hexvol;\n\tint max_lines;\n\tint retval = -1;\n\n\tLOAD_INIT();\n\n\thio_read(&header.id, 4, 1, f);\n\n\tver = *((char *)&header.id + 3) - '1' + 1;\n\n\tD_(D_WARN \"load header\");\n\theader.modlen = hio_read32b(f);\n\tsong_offset = hio_read32b(f);\n\tD_(D_INFO \"song_offset = 0x%08x\", song_offset);\n\thio_read16b(f);\n\thio_read16b(f);\n\tblockarr_offset = hio_read32b(f);\n\tD_(D_INFO \"blockarr_offset = 0x%08x\", blockarr_offset);\n\thio_read32b(f);\n\tsmplarr_offset = hio_read32b(f);\n\tD_(D_INFO \"smplarr_offset = 0x%08x\", smplarr_offset);\n\thio_read32b(f);\n\texpdata_offset = hio_read32b(f);\n\tD_(D_INFO \"expdata_offset = 0x%08x\", expdata_offset);\n\thio_read32b(f);\n\theader.pstate = hio_read16b(f);\n\theader.pblock = hio_read16b(f);\n\theader.pline = hio_read16b(f);\n\theader.pseqnum = hio_read16b(f);\n\theader.actplayline = hio_read16b(f);\n\theader.counter = hio_read8(f);\n\theader.extra_songs = hio_read8(f);\n\n\t/*\n\t * song structure\n\t */\n\tD_(D_WARN \"load song\");\n\thio_seek(f, start + song_offset, SEEK_SET);\n\tfor (i = 0; i < 63; i++) {\n\t\tsong.sample[i].rep = hio_read16b(f);\n\t\tsong.sample[i].replen = hio_read16b(f);\n\t\tsong.sample[i].midich = hio_read8(f);\n\t\tsong.sample[i].midipreset = hio_read8(f);\n\t\tsong.sample[i].svol = hio_read8(f);\n\t\tsong.sample[i].strans = hio_read8s(f);\n\t}\n\tsong.numblocks = hio_read16b(f);\n\tsong.songlen = hio_read16b(f);\n\tD_(D_INFO \"song.songlen = %d\", song.songlen);\n\tseqtable_offset = hio_read32b(f);\n\thio_read32b(f);\n\ttrackvols_offset = hio_read32b(f);\n\tsong.numtracks = hio_read16b(f);\n\tsong.numpseqs = hio_read16b(f);\n\ttrackpans_offset = hio_read32b(f);\n\tsong.flags3 = hio_read32b(f);\n\tsong.voladj = hio_read16b(f);\n\tsong.channels = hio_read16b(f);\n\tsong.mix_echotype = hio_read8(f);\n\tsong.mix_echodepth = hio_read8(f);\n\tsong.mix_echolen = hio_read16b(f);\n\tsong.mix_stereosep = hio_read8(f);\n\n\thio_seek(f, 223, SEEK_CUR);\n\n\tsong.deftempo = hio_read16b(f);\n\tsong.playtransp = hio_read8(f);\n\tsong.flags = hio_read8(f);\n\tsong.flags2 = hio_read8(f);\n\tsong.tempo2 = hio_read8(f);\n\tfor (i = 0; i < 16; i++)\n\t\thio_read8(f);\t\t/* reserved */\n\tsong.mastervol = hio_read8(f);\n\tsong.numsamples = hio_read8(f);\n\n\t/* Sanity check */\n\tif (song.numsamples > 63) {\n\t\tD_(D_CRIT \"invalid instrument count %d\", song.numsamples);\n\t\treturn -1;\n\t}\n\n\t/*\n\t * read sequence\n\t */\n\thio_seek(f, start + seqtable_offset, SEEK_SET);\n\tplayseq_offset = hio_read32b(f);\n\thio_seek(f, start + playseq_offset, SEEK_SET);\n\thio_seek(f, 32, SEEK_CUR);\t/* skip name */\n\thio_read32b(f);\n\thio_read32b(f);\n\tmod->len = hio_read16b(f);\n\n\t/* Sanity check */\n\tif (mod->len > 255) {\n\t\tD_(D_CRIT \"unsupported song length %d\", mod->len);\n\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < mod->len; i++) {\n\t\tmod->xxo[i] = hio_read16b(f);\n\t}\n\n\t/*\n\t * convert header\n\t */\n\tm->c4rate = C4_NTSC_RATE;\n\tm->quirk |= QUIRK_RTONCE; /* FF1 */\n\tm->quirk |= song.flags & FLAG_STSLIDE ? 0 : QUIRK_VSALL | QUIRK_PBALL;\n\thexvol = song.flags & FLAG_VOLHEX;\n\tmed_8ch = song.flags & FLAG_8CHANNEL;\n\tbpm_on = song.flags2 & FLAG2_BPM;\n\tbpmlen = 1 + (song.flags2 & FLAG2_BMASK);\n\tm->time_factor = MED_TIME_FACTOR;\n\n\tmmd_set_bpm(m, med_8ch, song.deftempo, bpm_on, bpmlen);\n\n\tmod->spd = song.tempo2;\n\tmod->pat = song.numblocks;\n\tmod->ins = song.numsamples;\n\tmod->rst = 0;\n\tmod->chn = 0;\n\tmod->name[0] = 0;\n\n\t/*\n\t * Read smplarr\n\t */\n\tD_(D_WARN \"read smplarr\");\n\tsmplarr = (uint32 *) malloc(mod->ins * sizeof(uint32));\n\tif (smplarr == NULL) {\n\t\treturn -1;\n\t}\n\tif (hio_seek(f, start + smplarr_offset, SEEK_SET) != 0) {\n\t\tD_(D_CRIT \"seek error at smplarr\");\n\t\tgoto err_cleanup;\n\t}\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tsmplarr[i] = hio_read32b(f);\n\t\tif (hio_eof(f)) {\n\t\t\tD_(D_CRIT \"read error at smplarr pos %d\", i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\t}\n\n\t/*\n\t * Obtain number of samples from each instrument\n\t */\n\tmod->smp = 0;\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tint16 type;\n\t\tif (smplarr[i] == 0)\n\t\t\tcontinue;\n\t\thio_seek(f, start + smplarr[i], SEEK_SET);\n\t\thio_read32b(f);\t\t\t\t/* length */\n\t\ttype = hio_read16b(f);\n\t\tif (type == -1 || type == -2) {\t\t/* type is synth? */\n\t\t\thio_seek(f, 14, SEEK_CUR);\n\t\t\tmod->smp += hio_read16b(f);\t/* wforms */\n\t\t} else if (type >= 1 && type <= 6) {\t/* octave samples */\n\t\t\tmod->smp += mmd_num_oct[type - 1];\n\t\t} else {\n\t\t\tmod->smp++;\n\t\t}\n\t\tif (hio_error(f)) {\n\t\t\tD_(D_CRIT \"read error at sample %d\", i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\t}\n\n\t/*\n\t * expdata\n\t */\n\tD_(D_WARN \"load expdata\");\n\texpdata.s_ext_entries = 0;\n\texpdata.s_ext_entrsz = 0;\n\texpdata.i_ext_entries = 0;\n\texpdata.i_ext_entrsz = 0;\n\texpsmp_offset = 0;\n\tiinfo_offset = 0;\n\n\tif (expdata_offset) {\n\t\tif (hio_seek(f, start + expdata_offset, SEEK_SET) != 0) {\n\t\t\tD_(D_CRIT \"seek error at expdata\");\n\t\t\tgoto err_cleanup;\n\t\t}\n\t\thio_read32b(f);\n\t\texpsmp_offset = hio_read32b(f);\n\t\tD_(D_INFO \"expsmp_offset = 0x%08x\", expsmp_offset);\n\t\texpdata.s_ext_entries = hio_read16b(f);\n\t\texpdata.s_ext_entrsz = hio_read16b(f);\n\t\thio_read32b(f);\t\t/* annotxt */\n\t\thio_read32b(f);\t\t/* annolen */\n\t\tiinfo_offset = hio_read32b(f);\n\t\tD_(D_INFO \"iinfo_offset = 0x%08x\", iinfo_offset);\n\t\texpdata.i_ext_entries = hio_read16b(f);\n\t\texpdata.i_ext_entrsz = hio_read16b(f);\n\n\t\t/* Sanity check */\n\t\tif (expsmp_offset < 0 || iinfo_offset < 0) {\n\t\t\tD_(D_CRIT \"invalid expdata\");\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\thio_read32b(f);\n\t\thio_read32b(f);\n\t\thio_read32b(f);\n\t\thio_read32b(f);\n\t\tsongname_offset = hio_read32b(f);\n\t\tD_(D_INFO \"songname_offset = 0x%08x\", songname_offset);\n\t\texpdata.songnamelen = hio_read32b(f);\n\t\thio_read32b(f);\t\t/* dumps */\n\t\tmmdinfo_offset = hio_read32b(f);\n\n\t\tif (hio_error(f)) {\n\t\t\tD_(D_CRIT \"read error in expdata\");\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\thio_seek(f, start + songname_offset, SEEK_SET);\n\t\tD_(D_INFO \"expdata.songnamelen = %d\", expdata.songnamelen);\n\t\tfor (i = 0; i < expdata.songnamelen; i++) {\n\t\t\tif (i >= XMP_NAME_SIZE)\n\t\t\t\tbreak;\n\t\t\tmod->name[i] = hio_read8(f);\n\t\t}\n\n\t\tif (mmdinfo_offset != 0) {\n\t\t\tD_(D_INFO \"mmdinfo_offset = 0x%08x\", mmdinfo_offset);\n\t\t\thio_seek(f, start + mmdinfo_offset, SEEK_SET);\n\t\t\tmmd_info_text(f, m, mmdinfo_offset);\n\t\t}\n\t}\n\n\t/*\n\t * Read blockarr.\n\t */\n\tD_(D_WARN \"read blockarr\");\n\tblockarr = (uint32 *) malloc(mod->pat * sizeof(uint32));\n\tif (blockarr == NULL) {\n\t\tgoto err_cleanup;\n\t}\n\tif (hio_seek(f, start + blockarr_offset, SEEK_SET) != 0) {\n\t\tD_(D_CRIT \"seek error at blockarr\");\n\t\tgoto err_cleanup;\n\t}\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tblockarr[i] = hio_read32b(f);\n\t\tif (hio_error(f)) {\n\t\t\tD_(D_CRIT \"read error at blockarr pos %d\", i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\t}\n\n\t/*\n\t * Quickly scan patterns to check the number of channels\n\t */\n\tD_(D_WARN \"find number of channels\");\n\n\tmax_lines = 1;\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tD_(D_INFO \"block %d block_offset = 0x%08x\", i, blockarr[i]);\n\t\tif (blockarr[i] == 0)\n\t\t\tcontinue;\n\n\t\thio_seek(f, start + blockarr[i], SEEK_SET);\n\n\t\tblock.numtracks = hio_read16b(f);\n\t\tblock.lines = hio_read16b(f);\n\t\tif (hio_error(f)) {\n\t\t\tD_(D_CRIT \"read error at block %d\", i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\t/* Sanity check--Amiga OctaMED files have an upper bound of 3200 lines per block,\n\t\t * but MED Soundstudio for Windows allows up to 9999 lines.\n\t\t  */\n\t\tif (block.lines + 1 > 9999) {\n\t\t\tD_(D_CRIT \"invalid line count %d in block %d\", block.lines + 1, i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\tif (block.numtracks > mod->chn) {\n\t\t\tmod->chn = block.numtracks;\n\t\t}\n\t\tif (block.lines + 1 > max_lines) {\n\t\t\tmax_lines = block.lines + 1;\n\t\t}\n\t}\n\n\t/* Sanity check */\n\tif (mod->chn <= 0 || mod->chn > XMP_MAX_CHANNELS) {\n\t\tD_(D_CRIT \"invalid channel count %d\", mod->chn);\n\t\tgoto err_cleanup;\n\t}\n\n\tmod->trk = mod->pat * mod->chn;\n\n\tmmd_tracker_version(m, ver, 0, expdata_offset ? &expdata : NULL);\n\n\tMODULE_INFO();\n\n\tD_(D_INFO \"BPM mode: %s (length = %d)\", bpm_on ? \"on\" : \"off\", bpmlen);\n\tD_(D_INFO \"Song transpose : %d\", song.playtransp);\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\t/*\n\t * Read and convert patterns\n\t */\n\tD_(D_WARN \"read patterns\");\n\n\tif (libxmp_init_pattern(mod) < 0)\n\t\tgoto err_cleanup;\n\n\tif ((patbuf = (uint8 *)malloc(mod->chn * max_lines * 4)) == NULL) {\n\t\tgoto err_cleanup;\n\t}\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tuint8 *pos;\n\t\tsize_t size;\n\n\t\tif (blockarr[i] == 0)\n\t\t\tcontinue;\n\n\t\thio_seek(f, start + blockarr[i], SEEK_SET);\n\n\t\tblock.numtracks = hio_read16b(f);\n\t\tblock.lines = hio_read16b(f);\n\t\thio_read32b(f); /* FIXME: should try to load extra command pages when they exist. */\n\n\t\tsize = block.numtracks * (block.lines + 1) * 4;\n\t\tif (hio_read(patbuf, 1, size, f) < size) {\n\t\t\tD_(D_CRIT \"read error in block %d\", i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\tif (libxmp_alloc_pattern_tracks_long(mod, i, block.lines + 1) < 0)\n\t\t\tgoto err_cleanup;\n\n\t\tpos = patbuf;\n\t\tfor (j = 0; j < mod->xxp[i]->rows; j++) {\n\t\t\tfor (k = 0; k < block.numtracks; k++) {\n\t\t\t\tevent = &EVENT(i, k, j);\n\t\t\t\tevent->note = pos[0] & 0x7f;\n\t\t\t\tif (event->note) {\n\t\t\t\t\tevent->note += 12 + song.playtransp;\n\t\t\t\t}\n\n\t\t\t\tif (event->note >= XMP_MAX_KEYS)\n\t\t\t\t\tevent->note = 0;\n\n\t\t\t\tevent->ins = pos[1] & 0x3f;\n\n\t\t\t\t/* Decay */\n\t\t\t\tif (event->ins && !event->note) {\n\t\t\t\t\tevent->f2t = FX_MED_HOLD;\n\t\t\t\t}\n\n\t\t\t\tevent->fxt = pos[2];\n\t\t\t\tevent->fxp = pos[3];\n\t\t\t\tmmd_xlat_fx(event, bpm_on, bpmlen,\n\t\t\t\t\t\tmed_8ch, hexvol);\n\t\t\t\tpos += 4;\n\t\t\t}\n\t\t}\n\t}\n\tfree(patbuf);\n\tpatbuf = NULL;\n\n\tif (libxmp_med_new_module_extras(m) != 0)\n\t\tgoto err_cleanup;\n\n\t/*\n\t * Read and convert instruments and samples\n\t */\n\tD_(D_WARN \"read instruments\");\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\tgoto err_cleanup;\n\n\tD_(D_INFO \"Instruments: %d\", mod->ins);\n\n\t/* Instrument extras */\n\texp_smp = (struct InstrExt *) calloc(mod->ins, sizeof(struct InstrExt));\n\tif (exp_smp == NULL) {\n\t\tgoto err_cleanup;\n\t}\n\n\tif (expsmp_offset) {\n\t\tif (hio_seek(f, start + expsmp_offset, SEEK_SET) != 0) {\n\t\t\tD_(D_CRIT \"seek error at expsmp\");\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\tfor (i = 0; i < mod->ins && i < expdata.s_ext_entries; i++) {\n\t\t\tint skip = expdata.s_ext_entrsz - 4;\n\n\t\t\tD_(D_INFO \"sample %d expsmp_offset = 0x%08lx\", i, hio_tell(f));\n\n\t\t\texp_smp[i].hold = hio_read8(f);\n\t\t\texp_smp[i].decay = hio_read8(f);\n\t\t\texp_smp[i].suppress_midi_off = hio_read8(f);\n\t\t\texp_smp[i].finetune = hio_read8(f);\n\n\t\t\tif (expdata.s_ext_entrsz >= 8) {\t/* Octamed V5 */\n\t\t\t\texp_smp[i].default_pitch = hio_read8(f);\n\t\t\t\texp_smp[i].instr_flags = hio_read8(f);\n\t\t\t\thio_read16b(f);\n\t\t\t\tskip -= 4;\n\t\t\t}\n\t\t\tif (expdata.s_ext_entrsz >= 10) {\t/* OctaMED V5.02 */\n\t\t\t\thio_read16b(f);\n\t\t\t\tskip -= 2;\n\t\t\t}\n\t\t\tif (expdata.s_ext_entrsz >= 18) {\t/* OctaMED V7 */\n\t\t\t\texp_smp[i].long_repeat = hio_read32b(f);\n\t\t\t\texp_smp[i].long_replen = hio_read32b(f);\n\t\t\t\tskip -= 8;\n\t\t\t}\n\n\t\t\tif (hio_error(f)) {\n\t\t\t\tD_(D_CRIT \"read error at expsmp\");\n\t\t\t\tgoto err_cleanup;\n\t\t\t}\n\n\t\t\tif (skip && hio_seek(f, skip, SEEK_CUR) != 0) {\n\t\t\t\tD_(D_CRIT \"seek error at expsmp\");\n\t\t\t\tgoto err_cleanup;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Instrument names */\n\tif (iinfo_offset) {\n\t\tuint8 name[40];\n\n\t\tif (hio_seek(f, start + iinfo_offset, SEEK_SET) != 0) {\n\t\t\tD_(D_CRIT \"seek error at iinfo\");\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\tfor (i = 0; i < mod->ins && i < expdata.i_ext_entries; i++) {\n\t\t\tint skip = expdata.i_ext_entrsz - 40;\n\n\t\t\tD_(D_INFO \"sample %d iinfo_offset = 0x%08lx\", i, hio_tell(f));\n\n\t\t\tif (hio_read(name, 40, 1, f) < 1) {\n\t\t\t\tD_(D_CRIT \"read error at iinfo %d\", i);\n\t\t\t\tgoto err_cleanup;\n\t\t\t}\n\t\t\tlibxmp_instrument_name(mod, i, name, 40);\n\n\t\t\tif (skip && hio_seek(f, skip, SEEK_CUR) != 0) {\n\t\t\t\tD_(D_CRIT \"seek error at iinfo %d\", i);\n\t\t\t\tgoto err_cleanup;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Sample data */\n\tfor (smp_idx = i = 0; i < mod->ins; i++) {\n\t\tD_(D_INFO \"sample %d smpl_offset = 0x%08x\", i, smplarr[i]);\n\t\tif (smplarr[i] == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (hio_seek(f, start + smplarr[i], SEEK_SET) < 0) {\n\t\t\tD_(D_CRIT \"seek error at instrument %d\", i);\n\t\t\tgoto err_cleanup;\n\t\t}\n\n\t\tsmp_idx = mmd_load_instrument(f, m, i, smp_idx, &expdata,\n\t\t\t&exp_smp[i], &song.sample[i], ver);\n\n\t\tif (smp_idx < 0) {\n\t\t\tgoto err_cleanup;\n\t\t}\n\t}\n\n\thio_seek(f, start + trackvols_offset, SEEK_SET);\n\tfor (i = 0; i < mod->chn; i++)\n\t\tmod->xxc[i].vol = hio_read8(f);;\n\n\tif (trackpans_offset) {\n\t\thio_seek(f, start + trackpans_offset, SEEK_SET);\n\t\tfor (i = 0; i < mod->chn; i++) {\n\t\t\tint p = 8 * hio_read8s(f);\n\t\t\tmod->xxc[i].pan = 0x80 + (p > 127 ? 127 : p);\n\t\t}\n\t} else {\n\t\tfor (i = 0; i < mod->chn; i++)\n\t\t\tmod->xxc[i].pan = 0x80;\n\t}\n\n\tm->read_event_type = READ_EVENT_MED;\n\tretval = 0;\n\n    err_cleanup:\n\tfree(exp_smp);\n\tfree(blockarr);\n\tfree(smplarr);\n\tfree(patbuf);\n\n\treturn retval;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/mmd_common.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/*\n * Common functions for MMD0/1 and MMD2/3 loaders\n * Tempo fixed by Francis Russell\n */\n\n#include \"med.h\"\n#include \"loader.h\"\n#include \"../med_extras.h\"\n\n#ifdef DEBUG\nconst char *const mmd_inst_type[] = {\n\t\"HYB\",\t\t\t/* -2 */\n\t\"SYN\",\t\t\t/* -1 */\n\t\"SMP\",\t\t\t/*  0 */\n\t\"I5O\",\t\t\t/*  1 */\n\t\"I3O\",\t\t\t/*  2 */\n\t\"I2O\",\t\t\t/*  3 */\n\t\"I4O\",\t\t\t/*  4 */\n\t\"I6O\",\t\t\t/*  5 */\n\t\"I7O\",\t\t\t/*  6 */\n\t\"EXT\",\t\t\t/*  7 */\n};\n#endif\n\nint mmd_convert_tempo(int tempo, int bpm_on, int med_8ch)\n{\n\tconst int tempos_compat[10] = {\n\t\t195, 97, 65, 49, 39, 32, 28, 24, 22, 20\n\t};\n\tconst int tempos_8ch[10] = {\n\t\t179, 164, 152, 141, 131, 123, 116, 110, 104, 99\n\t};\n\n\tif (tempo > 0) {\n\t\t/* From the OctaMEDv4 documentation:\n\t\t *\n\t\t * In 8-channel mode, you can control the playing speed more\n\t\t * accurately (to techies: by changing the size of the mix buffer).\n\t\t * This can be done with the left tempo gadget (values 1-10; the\n\t\t * lower, the faster). Values 11-240 are equivalent to 10.\n\t\t *\n\t\t * NOTE: the tempos used for this are directly from OctaMED\n\t\t * Soundstudio 2, but in older versions the playback speeds\n\t\t * differed slightly between NTSC and PAL. This table seems to\n\t\t * have been intended to be a compromise between the two.\n\t\t */\n\t\tif (med_8ch) {\n\t\t\ttempo = tempo > 10 ? 10 : tempo;\n\t\t\treturn tempos_8ch[tempo-1];\n\t\t}\n\t\t/* Tempos 1-10 in tempo mode are compatibility tempos that\n\t\t * approximate Soundtracker speeds.\n\t\t */\n\t\tif (tempo <= 10 && !bpm_on) {\n\t\t\treturn tempos_compat[tempo-1];\n\t\t}\n\t}\n\treturn tempo;\n}\n\nvoid mmd_xlat_fx(struct xmp_event *event, int bpm_on, int bpmlen, int med_8ch,\n\t\t int hexvol)\n{\n\tswitch (event->fxt) {\n\tcase 0x00:\n\t\t/* ARPEGGIO 00\n\t\t * Changes the pitch six times between three different\n\t\t * pitches during the duration of the note. It can create a\n\t\t * chord sound or other special effect. Arpeggio works better\n\t\t * with some instruments than others.\n\t\t */\n\t\tbreak;\n\tcase 0x01:\n\t\t/* SLIDE UP 01\n\t\t * This slides the pitch of the current track up. It decreases\n\t\t * the period of the note by the amount of the argument on each\n\t\t * timing pulse. OctaMED-Pro can create slides automatically,\n\t\t * but you may want to use this function for special effects.\n\t\t * Note: a param of 0 does nothing and should be ignored.\n\t\t */\n\t\tif (!event->fxp)\n\t\t\tevent->fxt = 0;\n\t\tbreak;\n\tcase 0x02:\n\t\t/* SLIDE DOWN 02\n\t\t * The same as SLIDE UP, but it slides down.\n\t\t * Note: a param of 0 does nothing and should be ignored.\n\t\t */\n\t\tif (!event->fxp)\n\t\t\tevent->fxt = 0;\n\t\tbreak;\n\tcase 0x03:\n\t\t/* PORTAMENTO 03\n\t\t * Makes precise sliding easy.\n\t\t */\n\t\tbreak;\n\tcase 0x04:\n\t\t/* VIBRATO 04\n\t\t * The left half of the argument is the vibrato speed, the\n\t\t * right half is the depth. If the numbers are zeros, the\n\t\t * previous speed and depth are used.\n\t\t */\n\t\t/* Note: this is twice as deep as the Protracker vibrato */\n\t\tevent->fxt = FX_VIBRATO2;\n\t\tbreak;\n\tcase 0x05:\n\t\t/* SLIDE + FADE 05\n\t\t * ProTracker compatible. This command is the combination of\n\t\t * commands 0300 and 0Dxx. The argument is the fade speed.\n\t\t * The slide will continue during this command.\n\t\t */\n\t\t/* fall-through */\n\tcase 0x06:\n\t\t/* VIBRATO + FADE 06\n\t\t * ProTracker compatible. Combines commands 0400 and 0Dxx.\n\t\t * The argument is the fade speed. The vibrato will continue\n\t\t * during this command.\n\t\t */\n\t\t/* fall-through */\n\tcase 0x07:\n\t\t/* TREMOLO 07\n\t\t * ProTracker compatible.\n\t\t * This command is a kind of \"volume vibrato\". The left\n\t\t * number is the speed of the tremolo, and the right one is\n\t\t * the depth. The depth must be quite high before the effect\n\t\t * is audible.\n\t\t */\n\t\tbreak;\n\tcase 0x08:\n\t\t/* HOLD and DECAY 08\n\t\t * This command must be on the same line as the note. The\n\t\t * left half of the argument determines the decay and the\n\t\t * right half the hold.\n\t\t */\n\t\tevent->fxt = event->fxp = 0;\n\t\tbreak;\n\tcase 0x09:\n\t\t/* SECONDARY TEMPO 09\n\t\t * This sets the secondary tempo (the number of timing\n\t\t * pulses per note). The argument must be from 01 to 20 (hex).\n\t\t */\n\t\tif (event->fxp >= 0x01 && event->fxp <= 0x20) {\n\t\t\tevent->fxt = FX_SPEED;\n\t\t} else {\n\t\t\tevent->fxt = event->fxp = 0;\n\t\t}\n\t\tbreak;\n\tcase 0x0a:\n\t\t/* 0A not mentioned but it's Protracker-compatible */\n\t\t/* fall-through */\n\tcase 0x0b:\n\t\t/* POSITION JUMP 0B\n\t\t * The song plays up to this command and then jumps to\n\t\t * another position in the play sequence. The song then\n\t\t * loops from the point jumped to, to the end of the song\n\t\t * forever. The purpose is to allow for introductions that\n\t\t * play only once.\n\t\t */\n\t\t/* fall-through */\n\tcase 0x0c:\n\t\t/* SET VOLUME 0C\n\t\t * Overrides the default volume of an instrument.\n\t\t */\n\t\tif (!hexvol) {\n\t\t\tint p = event->fxp;\n\t\t\tevent->fxp = (p >> 8) * 10 + (p & 0xff);\n\t\t}\n\t\tbreak;\n\tcase 0x0d:\n\t\t/* VOLUME SLIDE 0D\n\t\t * Smoothly slides the volume up or down. The left half of\n\t\t * the argument increases the volume. The right decreases it.\n\t\t */\n\t\tevent->fxt = FX_VOLSLIDE;\n\t\tbreak;\n\tcase 0x0e:\n\t\t/* SYNTH JUMP 0E\n\t\t * When used with synthetic or hybrid instruments, it\n\t\t * triggers a jump in the Waveform Sequence List. The argument\n\t\t * is the jump destination (line no).\n\t\t */\n\t\tevent->fxt = event->fxp = 0;\n\t\tbreak;\n\tcase 0x0f:\n\t\t/* MISCELLANEOUS 0F\n\t\t * The effect depends upon the value of the argument.\n\t\t */\n\t\tif (event->fxp == 0x00) {\t/* Jump to next block */\n\t\t\tevent->fxt = FX_BREAK;\n\t\t\tbreak;\n\t\t} else if (event->fxp <= 0xf0) {\n\t\t\tevent->fxt = FX_S3M_BPM;\n\t\t\tevent->fxp = mmd_convert_tempo(event->fxp, bpm_on, med_8ch);\n\t\t\tbreak;\n\t\t} else switch (event->fxp) {\n\t\tcase 0xf1:\t/* Play note twice */\n\t\t\tevent->fxt = FX_EXTENDED;\n\t\t\tevent->fxp = (EX_RETRIG << 4) | 3;\n\t\t\tbreak;\n\t\tcase 0xf2:\t/* Delay note */\n\t\t\tevent->fxt = FX_EXTENDED;\n\t\t\tevent->fxp = (EX_DELAY << 4) | 3;\n\t\t\tbreak;\n\t\tcase 0xf3:\t/* Play note three times */\n\t\t\t/* Actually just retriggers every 2 ticks, except\n\t\t\t * for a bug in OctaMED <=4.00 where it will retrigger\n\t\t\t * on (tick & 7) >= 2 (TODO: verify). */\n\t\t\tevent->fxt = FX_MED_RETRIG;\n\t\t\tevent->fxp = 0x02;\n\t\t\tbreak;\n\t\tcase 0xf8:\t/* Turn filter off */\n\t\tcase 0xf9:\t/* Turn filter on */\n\t\tcase 0xfa:\t/* MIDI pedal on */\n\t\tcase 0xfb:\t/* MIDI pedal off */\n\t\tcase 0xfd:\t/* Set pitch */\n\t\tcase 0xfe:\t/* End of song */\n\t\t\tevent->fxt = event->fxp = 0;\n\t\t\tbreak;\n\t\tcase 0xff:\t/* Turn note off */\n\t\t\tevent->fxt = event->fxp = 0;\n\t\t\tevent->note = XMP_KEY_CUT;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tevent->fxt = event->fxp = 0;\n\t\t}\n\t\tbreak;\n\tcase 0x11:\n\t\t/* SLIDE PITCH UP (only once) 11\n\t\t * Equivalent to ProTracker command E1x.\n\t\t * Lets you control the pitch with great accuracy. This\n\t\t * command changes only this occurrence of the note.\n\t\t * Note: a param of 0 does nothing and should be ignored.\n\t\t */\n\t\tevent->fxt = FX_F_PORTA_UP;\n\t\tbreak;\n\tcase 0x12:\n\t\t/* SLIDE DOWN (only once) 12\n\t\t * Equivalent to ProTracker command E2x.\n\t\t * Note: a param of 0 does nothing and should be ignored.\n\t\t */\n\t\tevent->fxt = FX_F_PORTA_DN;\n\t\tbreak;\n\tcase 0x14:\n\t\t/* VIBRATO 14\n\t\t * ProTracker compatible. This is similar to command 04\n\t\t * except the depth is halved, to give greater accuracy.\n\t\t */\n\t\tevent->fxt = FX_VIBRATO;\n\t\tbreak;\n\tcase 0x15:\n\t\t/* SET FINETUNE 15\n\t\t * Set a finetune value for a note, overrides the default\n\t\t * fine tune value of the instrument. Negative numbers must\n\t\t * be entered as follows:\n\t\t *   -1 => FF    -3 => FD    -5 => FB    -7 => F9\n\t\t *   -2 => FE    -4 => FC    -6 => FA    -8 => F8\n\t\t */\n\t\tevent->fxt = FX_FINETUNE;\n\t\tevent->fxp = (event->fxp + 8) << 4;\n\t\tbreak;\n\tcase 0x16:\n\t\t/* LOOP 16\n\t\t * Creates a loop within a block. 1600 marks the beginning\n\t\t * of the loop.  The next occurrence of the 16 command\n\t\t * designates the number of loops. Same as ProTracker E6x.\n\t\t */\n\t\tevent->fxt = FX_EXTENDED;\n\t\tif (event->fxp > 0x0f)\n\t\t\tevent->fxp = 0x0f;\n\t\tevent->fxp |= 0x60;\n\t\tbreak;\n\tcase 0x18:\n\t\t/* STOP NOTE 18\n\t\t * Cuts the note by zeroing the volume at the pulse specified\n\t\t * in the argument value. This is the same as ProTracker\n\t\t * command ECx.\n\t\t */\n\t\tevent->fxt = FX_EXTENDED;\n\t\tif (event->fxp > 0x0f)\n\t\t\tevent->fxp = 0x0f;\n\t\tevent->fxp |= 0xc0;\n\t\tbreak;\n\tcase 0x19:\n\t\t/* SET SAMPLE START OFFSET\n\t\t * Same as ProTracker command 9.\n\t\t * When playing a sample, this command sets the starting\n\t\t * offset (at steps of $100 = 256 bytes). Useful for speech\n\t\t * samples.\n\t\t */\n\t\tevent->fxt = FX_OFFSET;\n\t\tbreak;\n\tcase 0x1a:\n\t\t/* SLIDE VOLUME UP ONCE\n\t\t * Only once ProTracker command EAx. Lets volume slide\n\t\t * slowly once per line.\n\t\t * Note: a param of 0 does nothing and should be ignored.\n\t\t */\n\t\tevent->fxt = event->fxp ? FX_F_VSLIDE_UP : 0;\n\t\tbreak;\n\tcase 0x1b:\n\t\t/* SLIDE VOLUME DOWN ONCE\n\t\t * Only once ProTracker command EBx.\n\t\t * Note: a param of 0 does nothing and should be ignored.\n\t\t */\n\t\tevent->fxt = event->fxp ? FX_F_VSLIDE_DN : 0;\n\t\tbreak;\n\tcase 0x1d:\n\t\t/* JUMP TO NEXT BLOCK 1D\n\t\t * Jumps to the next line in the PLAY SEQUENCE LIST at the\n\t\t * specified line. ProTracker command D. This command is\n\t\t * like F00, except that you can specify the line number of\n\t\t * the first line to be played. The line number must be\n\t\t * specified in HEX.\n\t\t */\n\t\tevent->fxt = FX_BREAK;\n\t\tbreak;\n\tcase 0x1e:\n\t\t/* PLAY LINE x TIMES 1E\n\t\t * Plays only commands, notes not replayed. ProTracker\n\t\t * pattern delay.\n\t\t */\n\t\tevent->fxt = FX_PATT_DELAY;\n\t\tbreak;\n\tcase 0x1f:\n\t\t/* Command 1F: NOTE DELAY AND RETRIGGER\n\t\t * (Protracker commands EC and ED)\n\t\t * Gives you accurate control over note playing. You can\n\t\t * delay the note any number of ticks, and initiate fast\n\t\t * retrigger. Level 1 = note delay value, level 2 = retrigger\n\t\t * value.\n\t\t * Unlike FF1/FF3, this does nothing on a line with no note.\n\t\t */\n\t\tif (event->fxp != 0 && event->note != 0) {\n\t\t\tevent->fxt = FX_MED_RETRIG;\n\t\t} else {\n\t\t\tevent->fxt = event->fxp = 0;\n\t\t}\n\t\tbreak;\n\tcase 0x20:\n\t\t/* Command 20: REVERSE SAMPLE / RELATIVE SAMPLE OFFSET\n\t\t * With command level $00, the sample is reversed. With other\n\t\t * levels, it changes the sample offset, just like command 19,\n\t\t * except the command level is the new offset relative to the\n\t\t * current sample position being played.\n\t\t * Note: 20 00 only works on the same line as a new note.\n\t\t */\n\t\tif (event->fxp == 0 && event->note != 0) {\n\t\t\tevent->fxt = FX_REVERSE;\n\t\t\tevent->fxp = 1;\n\t\t} else {\n\t\t\tevent->fxt = event->fxp = 0;\n\t\t}\n\t\tbreak;\n\tcase 0x2e:\n\t\t/* Command 2E: SET TRACK PANNING\n\t\t * Allows track panning to be changed during play. The track\n\t\t * on which the player command appears is the track affected.\n\t\t * The command level is in signed hex: $F0 to $10 = -16 to 16\n\t\t * decimal.\n\t\t */\n\t\tif (event->fxp >= 0xf0 || event->fxp <= 0x10) {\n\t\t\tint fxp = (signed char)event->fxp + 16;\n\t\t\tfxp <<= 3;\n\t\t\tif (fxp == 0x100)\n\t\t\t\tfxp--;\n\t\t\tevent->fxt = FX_SETPAN;\n\t\t\tevent->fxp = fxp;\n\t\t}\n\t\tbreak;\n\tdefault:\n\t\tevent->fxt = event->fxp = 0;\n\t\tbreak;\n\t}\n}\n\n\nstruct mmd_instrument_info\n{\n\tuint32 length;\n\tuint32 rep;\n\tuint32 replen;\n\tint sampletrans;\n\tint synthtrans;\n\tint flg;\n\tint enable;\n};\n\n/* Interpret loop/flag parameters for sampled instruments (sample, hybrid, IFF).\n * This is common code to avoid replicating this mess in each loader. */\nstatic void mmd_load_instrument_common(\n\t\t\tstruct mmd_instrument_info *info, struct InstrHdr *instr,\n\t\t\tstruct MMD0exp *expdata, struct InstrExt *exp_smp,\n\t\t\tstruct MMD0sample *sample, int ver)\n{\n\tinfo->enable = 1;\n\tinfo->flg = 0;\n\tif (ver >= 2 && expdata->s_ext_entrsz >= 8) {\t/* MMD2+ instrument flags */\n\t\tuint8 instr_flags = exp_smp->instr_flags;\n\n\t\tif (instr_flags & SSFLG_LOOP) {\n\t\t\tinfo->flg |= XMP_SAMPLE_LOOP;\n\t\t}\n\t\tif (instr_flags & SSFLG_PINGPONG) {\n\t\t\tinfo->flg |= XMP_SAMPLE_LOOP_BIDIR;\n\t\t}\n\t\tif (instr_flags & SSFLG_DISABLED) {\n\t\t\tinfo->enable = 0;\n\t\t}\n\t} else {\n\t\tif (sample->replen > 1) {\n\t\t\tinfo->flg |= XMP_SAMPLE_LOOP;\n\t\t}\n\t}\n\n\tinfo->sampletrans = 36 + sample->strans;\n\tinfo->synthtrans = 12 + sample->strans;\n\n\tif (instr) {\n\t\tint sample_type = instr->type & ~(S_16|MD16|STEREO);\n\n\t\tif ((ver >= 3 && sample_type == 0) || sample_type == 7) {\n\t\t\t/* Mix mode transposes samples down two octaves.\n\t\t\t * This does not apply to octave samples or synths.\n\t\t\t * ExtSamples (7) are transposed regardless. */\n\t\t\tinfo->sampletrans -= 24;\n\t\t}\n\n\t\tinfo->length = instr->length;\n\n\t\tif (ver >= 2 && expdata->s_ext_entrsz >= 18) {\t/* MMD2+ long repeat */\n\t\t\tinfo->rep = exp_smp->long_repeat;\n\t\t\tinfo->replen = exp_smp->long_replen;\n\t\t} else {\n\t\t\tinfo->rep = sample->rep << 1;\n\t\t\tinfo->replen = sample->replen << 1;\n\t\t}\n\n\t\tif (instr->type & S_16) {\n\t\t\tinfo->flg |= XMP_SAMPLE_16BIT;\n\t\t\t/* Length is (bytes / channels) but the\n\t\t\t * loop is measured in sample frames. */\n\t\t\tinfo->length >>= 1;\n\t\t}\n\n\t\t/* STEREO means that this is a stereo sample. The sample\n\t\t* is not interleaved. The left channel comes first,\n\t\t* followed by the right channel. Important: Length\n\t\t* specifies the size of one channel only! The actual memory\n\t\t* usage for both samples is length * 2 bytes.\n\t\t*/\n\t\tif (instr->type & STEREO) {\n\t\t\tinfo->flg |= XMP_SAMPLE_STEREO;\n\t\t}\n\t}\n}\n\n/* Compatibility for MED Soundstudio v2 default pitch events.\n * For single-octave samples and synthetics, MED mix mode note 0x01\n * plays the note number stored in the InstrExt default pitch field.\n * Mix mode events are currently transposed up an octave and are offset\n * down by 1 for the instrument map, hence index 12.\n *\n * See med.h for more info.\n */\nstatic void mmd_set_default_pitch_note(struct xmp_instrument *xxi,\n\t\t\t\t\tstruct InstrExt *exp_smp, int ver)\n{\n\tif (ver >= 3) {\n\t\tint note = MMD3_DEFAULT_NOTE;\n\n\t\tif (exp_smp->default_pitch)\n\t\t\tnote = exp_smp->default_pitch - 1;\n\n\t\tif (note >= 0 && note < XMP_MAX_KEYS)\n\t\t\txxi->map[12].xpo = note;\n\t}\n}\n\nint mmd_alloc_tables(struct module_data *m, int i, struct SynthInstr *synth)\n{\n\tstruct med_module_extras *me = (struct med_module_extras *)m->extra;\n\n\tme->vol_table[i] = (uint8 *) calloc(1, synth->voltbllen);\n\tif (me->vol_table[i] == NULL)\n\t\tgoto err;\n\tmemcpy(me->vol_table[i], synth->voltbl, synth->voltbllen);\n\n\tme->wav_table[i] = (uint8 *) calloc(1, synth->wftbllen);\n\tif (me->wav_table[i] == NULL)\n\t\tgoto err1;\n\tmemcpy(me->wav_table[i], synth->wftbl, synth->wftbllen);\n\n\treturn 0;\n\n    err1:\n\tfree(me->vol_table[i]);\n    err:\n\treturn -1;\n}\n\nstatic int mmd_load_hybrid_instrument(HIO_HANDLE *f, struct module_data *m, int i,\n\t\t\tint smp_idx, struct SynthInstr *synth,\n\t\t\tstruct MMD0exp *expdata, struct InstrExt *exp_smp,\n\t\t\tstruct MMD0sample *sample, int ver)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\tstruct xmp_subinstrument *sub;\n\tstruct xmp_sample *xxs;\n\tstruct med_instrument_extras *ie;\n\tstruct mmd_instrument_info info;\n\tstruct InstrHdr instr;\n\tint pos = hio_tell(f);\n\tint j;\n\n\t/* Sanity check */\n\tif (smp_idx >= mod->smp) {\n\t\treturn -1;\n\t}\n\n\tsynth->defaultdecay = hio_read8(f);\n\thio_seek(f, 3, SEEK_CUR);\n\tsynth->rep = hio_read16b(f);\n\tsynth->replen = hio_read16b(f);\n\tsynth->voltbllen = hio_read16b(f);\n\tsynth->wftbllen = hio_read16b(f);\n\tsynth->volspeed = hio_read8(f);\n\tsynth->wfspeed = hio_read8(f);\n\tsynth->wforms = hio_read16b(f);\n\thio_read(synth->voltbl, 1, 128, f);\n\thio_read(synth->wftbl, 1, 128, f);\n\n\t/* Sanity check */\n\tif (synth->voltbllen > 128 || synth->wftbllen > 128 ||\n\t    synth->wforms < 1 || synth->wforms > 64) {\n\t\treturn -1;\n\t}\n\n\tfor (j = 0; j < synth->wforms; j++)\n\t\tsynth->wf[j] = hio_read32b(f);\n\n\tif (hio_error(f))\n\t\treturn -1;\n\n\thio_seek(f, pos - 6 + synth->wf[0], SEEK_SET);\n\tinstr.length = hio_read32b(f);\n\tinstr.type = hio_read16b(f);\n\n\t/* Hybrids using IFFOCT/ext samples as their sample don't seem to\n\t * exist. If one is found, this should be fixed. OctaMED SS 1.03\n\t * converts 16-bit samples to 8-bit when changed to hybrid. */\n\tif (instr.type != 0) {\n\t\tD_(D_CRIT \"unsupported sample type %d for hybrid\", instr.type);\n\t\treturn -1;\n\t}\n\n\tif (libxmp_med_new_instrument_extras(xxi) != 0)\n\t\treturn -1;\n\n\txxi->nsm = synth->wforms;\n\tif (libxmp_alloc_subinstrument(mod, i, synth->wforms) < 0)\n\t\treturn -1;\n\n\tie = MED_INSTRUMENT_EXTRAS(*xxi);\n\tie->vts = synth->volspeed;\n\tie->wts = synth->wfspeed;\n\tie->vtlen = synth->voltbllen;\n\tie->wtlen = synth->wftbllen;\n\n\tmmd_load_instrument_common(&info, &instr, expdata, exp_smp, sample, ver);\n\tmmd_set_default_pitch_note(xxi, exp_smp, ver);\n\tsub = &xxi->sub[0];\n\n\tsub->pan = 0x80;\n\tsub->vol = info.enable ? sample->svol : 0;\n\tsub->xpo = info.sampletrans;\n\tsub->sid = smp_idx;\n\tsub->fin = exp_smp->finetune;\n\n\txxs = &mod->xxs[smp_idx];\n\n\txxs->len = info.length;\n\txxs->lps = info.rep;\n\txxs->lpe = info.rep + info.replen;\n\txxs->flg = info.flg;\n\n\tif (libxmp_load_sample(m, f, 0, xxs, NULL) < 0)\n\t\treturn -1;\n\n\tsmp_idx++;\n\n\tfor (j = 1; j < synth->wforms; j++) {\n\t\tsub = &xxi->sub[j];\n\t\txxs = &mod->xxs[smp_idx];\n\n\t\t/* Sanity check */\n\t\tif (j >= xxi->nsm || smp_idx >= mod->smp)\n\t\t\treturn -1;\n\n\t\tsub->pan = 0x80;\n\t\tsub->vol = info.enable ? 64 : 0;\n\t\tsub->xpo = info.synthtrans;\n\t\tsub->sid = smp_idx;\n\t\tsub->fin = exp_smp->finetune;\n\n\t\thio_seek(f, pos - 6 + synth->wf[j], SEEK_SET);\n\n\t\txxs->len = hio_read16b(f) * 2;\n\t\txxs->lps = 0;\n\t\txxs->lpe = xxs->len;\n\t\txxs->flg = XMP_SAMPLE_LOOP;\n\n\t\tif (libxmp_load_sample(m, f, 0, xxs, NULL) < 0)\n\t\t\treturn -1;\n\n\t\tsmp_idx++;\n\t}\n\treturn 0;\n}\n\nstatic int mmd_load_synth_instrument(HIO_HANDLE *f, struct module_data *m, int i,\n\t\t\tint smp_idx, struct SynthInstr *synth,\n\t\t\tstruct MMD0exp *expdata, struct InstrExt *exp_smp,\n\t\t\tstruct MMD0sample *sample, int ver)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\tstruct med_instrument_extras *ie;\n\tstruct mmd_instrument_info info;\n\tint pos = hio_tell(f);\n\tint j;\n\n\tmmd_load_instrument_common(&info, NULL, expdata, exp_smp, sample, ver);\n\tmmd_set_default_pitch_note(xxi, exp_smp, ver);\n\n\tsynth->defaultdecay = hio_read8(f);\n\thio_seek(f, 3, SEEK_CUR);\n\tsynth->rep = hio_read16b(f);\n\tsynth->replen = hio_read16b(f);\n\tsynth->voltbllen = hio_read16b(f);\n\tsynth->wftbllen = hio_read16b(f);\n\tsynth->volspeed = hio_read8(f);\n\tsynth->wfspeed = hio_read8(f);\n\tsynth->wforms = hio_read16b(f);\n\thio_read(synth->voltbl, 1, 128, f);\n\thio_read(synth->wftbl, 1, 128, f);\n\n\tif (synth->wforms == 0xffff) {\n\t\txxi->nsm = 0;\n\t\treturn 1;\n\t}\n\tif (synth->voltbllen > 128 ||\n\t    synth->wftbllen > 128 ||\n\t    synth->wforms > 64) {\n\t\treturn -1;\n\t}\n\n\tfor (j = 0; j < synth->wforms; j++)\n\t\tsynth->wf[j] = hio_read32b(f);\n\n\tif (hio_error(f))\n\t\treturn -1;\n\n\tD_(D_INFO \"  VS:%02x WS:%02x WF:%02x %02x %+3d %+1d\",\n\t\t\tsynth->volspeed, synth->wfspeed,\n\t\t\tsynth->wforms & 0xff,\n\t\t\tsample->svol,\n\t\t\tsample->strans,\n\t\t\texp_smp->finetune);\n\n\tif (libxmp_med_new_instrument_extras(&mod->xxi[i]) != 0)\n\t\treturn -1;\n\n\txxi->nsm = synth->wforms;\n\tif (libxmp_alloc_subinstrument(mod, i, synth->wforms) < 0)\n\t\treturn -1;\n\n\tie = MED_INSTRUMENT_EXTRAS(*xxi);\n\tie->vts = synth->volspeed;\n\tie->wts = synth->wfspeed;\n\tie->vtlen = synth->voltbllen;\n\tie->wtlen = synth->wftbllen;\n\n\tfor (j = 0; j < synth->wforms; j++) {\n\t\tstruct xmp_subinstrument *sub = &xxi->sub[j];\n\t\tstruct xmp_sample *xxs = &mod->xxs[smp_idx];\n\n\t\t/* Sanity check */\n\t\tif (j >= xxi->nsm || smp_idx >= mod->smp)\n\t\t\treturn -1;\n\n\t\tsub->pan = 0x80;\n\t\tsub->vol = info.enable ? 64 : 0;\n\t\tsub->xpo = info.synthtrans;\n\t\tsub->sid = smp_idx;\n\t\tsub->fin = exp_smp->finetune;\n\n\t\thio_seek(f, pos - 6 + synth->wf[j], SEEK_SET);\n\n\t\txxs->len = hio_read16b(f) * 2;\n\t\txxs->lps = 0;\n\t\txxs->lpe = xxs->len;\n\t\txxs->flg = XMP_SAMPLE_LOOP;\n\n\t\tif (libxmp_load_sample(m, f, 0, xxs, NULL) < 0)\n\t\t\treturn -1;\n\n\t\tsmp_idx++;\n\t}\n\n\treturn 0;\n}\n\nstatic int mmd_load_sampled_instrument(HIO_HANDLE *f, struct module_data *m, int i,\n\t\t\tint smp_idx, struct InstrHdr *instr,\n\t\t\tstruct MMD0exp *expdata, struct InstrExt *exp_smp,\n\t\t\tstruct MMD0sample *sample, int ver)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\tstruct xmp_subinstrument *sub;\n\tstruct xmp_sample *xxs;\n\tstruct mmd_instrument_info info;\n\tint j, k;\n\n\t/* Sanity check */\n\tif (smp_idx >= mod->smp)\n\t\treturn -1;\n\n\t/* hold & decay support */\n        if (libxmp_med_new_instrument_extras(xxi) != 0)\n                return -1;\n\tMED_INSTRUMENT_EXTRAS(*xxi)->hold = exp_smp->hold;\n\txxi->rls = 0xfff - (exp_smp->decay << 4);\n\n\txxi->nsm = 1;\n\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t\treturn -1;\n\n\tmmd_load_instrument_common(&info, instr, expdata, exp_smp, sample, ver);\n\tmmd_set_default_pitch_note(xxi, exp_smp, ver);\n\tsub = &xxi->sub[0];\n\n\tsub->vol = info.enable ? sample->svol : 0;\n\tsub->pan = 0x80;\n\tsub->xpo = info.sampletrans;\n\tsub->sid = smp_idx;\n\tsub->fin = exp_smp->finetune << 4;\n\n\txxs = &mod->xxs[smp_idx];\n\n\txxs->len = info.length;\n\txxs->lps = info.rep;\n\txxs->lpe = info.rep + info.replen;\n\txxs->flg = info.flg;\n\n        /* Restrict sampled instruments to 3 octave range except for MMD3.\n         * Checked in MMD0 with med.egypian/med.medieval from Lemmings 2\n         * and MED.ParasolStars, MMD1 with med.Lemmings2\n         */\n\n\tif (ver < 3) {\n\t\t/* ExtSamples have two extra octaves. */\n\t\tint octaves = (instr->type & 7) == 7 ? 5 : 3;\n\t\tfor (j = 0; j < 9; j++) {\n\t\t\tfor (k = 0; k < 12; k++) {\n\t\t\t\tint xpo = 0;\n\n\t\t\t\tif (j < 1)\n\t\t\t\t\txpo = 12 * (1 - j);\n\t\t\t\telse if (j > octaves)\n\t\t\t\t\txpo = -12 * (j - octaves);\n\n\t\t\t\txxi->map[12 * j + k].xpo = xpo;\n\t\t\t}\n\t\t}\n\t}\n\n\n\tif (libxmp_load_sample(m, f, SAMPLE_FLAG_BIGEND, xxs, NULL) < 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic const char iffoct_insmap[6][9] = {\n\t/* 2 */ { 1, 1, 1, 0, 0, 0, 0, 0, 0 },\n\t/* 3 */ { 2, 2, 2, 2, 2, 2, 1, 1, 0 },\n\t/* 4 */ { 3, 3, 3, 2, 2, 2, 1, 1, 0 },\n\t/* 5 */ { 4, 4, 4, 3, 2, 2, 1, 1, 0 },\n\t/* 6 */ { 5, 5, 5, 5, 4, 3, 2, 1, 0 },\n\t/* 7 */ { 6, 6, 6, 6, 5, 4, 3, 2, 1 }\n};\n\nstatic const char iffoct_xpomap[6][9] = {\n\t/* 2 */ { 12, 12, 12,  0,  0,  0,  0,  0,  0 },\n\t/* 3 */ { 12, 12, 12, 12, 12, 12,  0,  0,-12 },\n\t/* 4 */ { 12, 12, 12,  0,  0,  0,-12,-12,-24 },\n\t/* 5 */ { 24, 24, 24, 12,  0,  0,-12,-24,-36 },\n\t/* 6 */ { 12, 12, 12, 12,  0,-12,-24,-36,-48 },\n\t/* 7 */ { 12, 12, 12, 12,  0,-12,-24,-36,-48 },\n};\n\nstatic int mmd_load_iffoct_instrument(HIO_HANDLE *f, struct module_data *m, int i,\n\t\t\tint smp_idx, struct InstrHdr *instr, int num_oct,\n\t\t\tstruct MMD0exp *expdata, struct InstrExt *exp_smp,\n\t\t\tstruct MMD0sample *sample, int ver)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\tstruct xmp_subinstrument *sub;\n\tstruct xmp_sample *xxs;\n\tstruct mmd_instrument_info info;\n\tint size, j, k;\n\n\tif (num_oct < 2 || num_oct > 7)\n\t\treturn -1;\n\n\t/* Sanity check */\n\tif (smp_idx + num_oct > mod->smp)\n\t\treturn -1;\n\n\t/* Sanity check - ignore absurdly large IFFOCT instruments. */\n\tif ((int)instr->length < 0)\n\t\treturn -1;\n\n\t/* hold & decay support */\n\tif (libxmp_med_new_instrument_extras(xxi) != 0)\n\t\treturn -1;\n\n\tMED_INSTRUMENT_EXTRAS(*xxi)->hold = exp_smp->hold;\n\txxi->rls = 0xfff - (exp_smp->decay << 4);\n\n\txxi->nsm = num_oct;\n\tif (libxmp_alloc_subinstrument(mod, i, num_oct) < 0)\n\t\treturn -1;\n\n\t/* base octave size */\n\tsize = instr->length / ((1 << num_oct) - 1);\n\tmmd_load_instrument_common(&info, instr, expdata, exp_smp, sample, ver);\n\n\tfor (j = 0; j < num_oct; j++) {\n\t\tsub = &xxi->sub[j];\n\n\t\tsub->vol = info.enable ? sample->svol : 0;\n\t\tsub->pan = 0x80;\n\t\tsub->xpo = info.sampletrans - 12;\n\t\tsub->sid = smp_idx;\n\t\tsub->fin = exp_smp->finetune << 4;\n\n\t\txxs = &mod->xxs[smp_idx];\n\n\t\txxs->len = size;\n\t\txxs->lps = info.rep;\n\t\txxs->lpe = info.rep + info.replen;\n\t\txxs->flg = info.flg;\n\n\t\tif (libxmp_load_sample(m, f, SAMPLE_FLAG_BIGEND, xxs, NULL) < 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tsmp_idx++;\n\t\tsize <<= 1;\n\t\tinfo.rep <<= 1;\n\t\tinfo.replen <<= 1;\n\t}\n\n\t/* instrument mapping */\n\n\tfor (j = 0; j < 9; j++) {\n\t\tfor (k = 0; k < 12; k++) {\n\t\t\txxi->map[12 * j + k].ins = iffoct_insmap[num_oct - 2][j];\n\t\t\txxi->map[12 * j + k].xpo = iffoct_xpomap[num_oct - 2][j];\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/* Number of octaves in IFFOCT samples */\nconst int mmd_num_oct[6] = { 5, 3, 2, 4, 6, 7 };\n\nint mmd_load_instrument(HIO_HANDLE *f, struct module_data *m, int i, int smp_idx,\n\t\t\tstruct MMD0exp *expdata, struct InstrExt *exp_smp,\n\t\t\tstruct MMD0sample *sample, int ver)\n{\n\tstruct InstrHdr instr;\n\tstruct SynthInstr synth;\n\tint sample_type;\n\n\tinstr.length = hio_read32b(f);\n\tinstr.type = (int16)hio_read16b(f);\n\tsample_type = instr.type & ~(S_16|MD16|STEREO);\n\n\tD_(D_INFO \"[%2x] %-40.40s %d\", i, m->mod.xxi[i].name, instr.type);\n\n\tif (instr.type == -2) {\t\t\t\t\t/* Hybrid */\n\t\tint ret = mmd_load_hybrid_instrument(f, m, i, smp_idx,\n\t\t\t&synth, expdata, exp_smp, sample, ver);\n\n\t\tif (ret < 0) {\n\t\t\tD_(D_CRIT \"error loading hybrid instrument %d\", i);\n\t\t\treturn -1;\n\t\t}\n\n\t\tsmp_idx += synth.wforms;\n\n\t\tif (mmd_alloc_tables(m, i, &synth) != 0)\n\t\t\treturn -1;\n\n\t} else if (instr.type == -1) {\t\t\t\t/* Synthetic */\n\t\tint ret = mmd_load_synth_instrument(f, m, i, smp_idx,\n\t\t\t&synth, expdata, exp_smp, sample, ver);\n\n\t\tif (ret > 0)\n\t\t\treturn smp_idx;\n\n\t\tif (ret < 0) {\n\t\t\tD_(D_CRIT \"error loading synthetic instrument %d\", i);\n\t\t\treturn -1;\n\t\t}\n\n\t\tsmp_idx += synth.wforms;\n\n\t\tif (mmd_alloc_tables(m, i, &synth) != 0)\n\t\t\treturn -1;\n\n\t} else if (instr.type >= 1 && instr.type <= 6) {\t/* IFFOCT */\n\t\tint ret;\n\t\tconst int oct = mmd_num_oct[instr.type - 1];\n\n\t\tret = mmd_load_iffoct_instrument(f, m, i, smp_idx,\n\t\t\t&instr, oct, expdata, exp_smp, sample, ver);\n\n\t\tif (ret < 0) {\n\t\t\tD_(D_CRIT \"error loading IFFOCT instrument %d\", i);\n\t\t\treturn -1;\n\t\t}\n\n\t\tsmp_idx += oct;\n\n\t} else if (sample_type == 0 || sample_type == 7) {\t/* Sample */\n\t\tint ret;\n\n\t\tret = mmd_load_sampled_instrument(f, m, i, smp_idx,\n\t\t\t&instr, expdata, exp_smp, sample, ver);\n\n\t\tif (ret < 0) {\n\t\t\tD_(D_CRIT \"error loading sample %d\", i);\n\t\t\treturn -1;\n\t\t}\n\n\t\tsmp_idx++;\n\n\t} else {\n\t\t/* Invalid instrument type */\n\t\tD_(D_CRIT \"invalid instrument type: %d\", instr.type);\n\t\treturn -1;\n\t}\n\treturn smp_idx;\n}\n\n/* Load an external instrument (pre-MMD when the internal instruments flag is\n * not set). Returns 0 on successfully loading or if the instrument can't be\n * found. Returns -1 if an instrument is found but fails to load. */\nint med_load_external_instrument(HIO_HANDLE *f, struct module_data *m, int i)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tchar path[XMP_MAXPATH];\n\tchar ins_name[32];\n\tHIO_HANDLE *s = NULL;\n\n\tif (libxmp_copy_name_for_fopen(ins_name, mod->xxi[i].name, 32) != 0)\n\t\treturn 0;\n\n\tD_(D_INFO \"[%2X] %-32.32s ---- %04x %04x %c V%02x\",\n\t\ti, mod->xxi[i].name, mod->xxs[i].lps, mod->xxs[i].lpe,\n\t\tmod->xxs[i].flg & XMP_SAMPLE_LOOP ? 'L' : ' ',\n\t\tmod->xxi[i].sub[0].vol);\n\n\tif (!libxmp_find_instrument_file(m, path, sizeof(path), ins_name))\n\t\treturn 0;\n\n\tif ((s = hio_open(path, \"rb\")) == NULL) {\n\t\treturn 0;\n\t}\n\n\tmod->xxs[i].len = hio_size(s);\n\tif (mod->xxs[i].len == 0) {\n\t\thio_close(s);\n\t\treturn 0;\n\t}\n\tmod->xxi[i].nsm = 1;\n\n\tD_(D_INFO \"     %-32s %04x\", \"(OK)\", mod->xxs[i].len);\n\n\tif (libxmp_load_sample(m, s, 0, &mod->xxs[i], NULL) < 0) {\n\t\thio_close(s);\n\t\treturn -1;\n\t}\n\thio_close(s);\n\treturn 0;\n}\n\n\nvoid mmd_set_bpm(struct module_data *m, int med_8ch, int deftempo,\n\t\t\t\t\t\tint bpm_on, int bpmlen)\n{\n\tstruct xmp_module *mod = &m->mod;\n\n\tmod->bpm = mmd_convert_tempo(deftempo, bpm_on, med_8ch);\n\n\t/* 8-channel mode completely overrides regular timing.\n\t * See mmd_convert_tempo for more info.\n\t */\n\tif (med_8ch) {\n\t\tm->time_factor = DEFAULT_TIME_FACTOR;\n\t} else if (bpm_on) {\n\t\tm->time_factor = DEFAULT_TIME_FACTOR * 4 / bpmlen;\n\t}\n}\n\n\nvoid mmd_info_text(HIO_HANDLE *f, struct module_data *m, int offset)\n{\n\tint type, len;\n\n\t/* Copy song info text */\n\thio_read32b(f);\t\t/* skip next */\n\thio_read16b(f);\t\t/* skip reserved */\n\ttype = hio_read16b(f);\n\tD_(D_INFO \"mmdinfo type=%d\", type);\n\tif (type == 1) {\t/* 1 = ASCII */\n\t\tlen = hio_read32b(f);\n\t\tD_(D_INFO \"mmdinfo length=%d\", len);\n\t\tif (len > 0 && len < hio_size(f)) {\n\t\t\tm->comment = (char *) malloc(len + 1);\n\t\t\tif (m->comment == NULL)\n\t\t\t\treturn;\n\t\t\thio_read(m->comment, 1, len, f);\n\t\t\tm->comment[len] = 0;\n\t\t}\n\t}\n}\n\n/* Determine an approximate tracker version from an MMD module since, unlike\n * MED4, they don't store any useful tracker information. If expdata is not\n * present in the module, it should be passed as NULL.\n */\nint mmd_tracker_version(struct module_data *m, int mmdver, int mmdc,\n\tstruct MMD0exp *expdata)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint soundstudio = 0;\n\tint medver = 0;\n\tint s_ext_entrsz = 0;\n\tint mmdch = '0' + mmdver;\n\n\tif (expdata) {\n\t\tD_(D_INFO \"expdata.s_ext_entrsz = %d\", expdata->s_ext_entrsz);\n\t\tD_(D_INFO \"expdata.i_ext_entrsz = %d\", expdata->i_ext_entrsz);\n\t\ts_ext_entrsz = expdata->s_ext_entrsz;\n\t} else {\n\t\tD_(D_INFO \"expdata = NULL\");\n\t}\n\n\tif (s_ext_entrsz > 18) {\t\t/* s_ext_entrsz == 24 */\n\t\tmedver = MED_VER_OCTAMED_SS_2;\n\t\tsoundstudio = 2;\n\t} else if (mmdver >= 3) {\n\t\tmedver = MED_VER_OCTAMED_SS_1;\n\t\tsoundstudio = 1;\n\t} else if (s_ext_entrsz > 10) {\t\t/* s_ext_entrsz == 18 */\n\t\tmedver = MED_VER_OCTAMED_SS_1;\n\t\tsoundstudio = 1;\n\t} else if (s_ext_entrsz > 8) {\t\t/* s_ext_entrsz == 10 */\n\t\tmedver = MED_VER_OCTAMED_502;\n\t} else if (s_ext_entrsz > 4) {\t\t/* s_ext_entrsz == 8 */\n\t\tmedver = MED_VER_OCTAMED_500;\n\t} else if (mmdver >= 2) {\n\t\tmedver = MED_VER_OCTAMED_500;\n\t} else if (mmdver >= 1) {\n\t\tmedver = MED_VER_OCTAMED_400;\n\t} else if (mod->chn > 4) {\n\t\tmedver = MED_VER_OCTAMED_200;\n\t} else if (s_ext_entrsz > 2) {\t\t/* s_ext_entrsz == 4 */\n\t\tmedver = MED_VER_320;\n\t} else if (expdata != NULL) {\t\t/* s_ext_entrsz == 2 */\n\t\t/* MED 3.00 and 3.10 i_ext_entrsz always 0? */\n\t\tmedver = MED_VER_300;\n\t} else {\n\t\tmedver = MED_VER_210;\n\t}\n\n\tif (mmdc) {\n\t\tmmdch = 'C';\n\t}\n\n\tif (soundstudio == 2) {\n\t\tlibxmp_set_type(m, \"MED Soundstudio 2.00 MMD%c\", mmdch);\n\t} else if (soundstudio == 1) {\n\t\tlibxmp_set_type(m, \"OctaMED Soundstudio MMD%c\", mmdch);\n\t} else if (medver > MED_VER_320) {\n\t\tlibxmp_set_type(m, \"OctaMED %d.%02x MMD%c\",\n\t\t\t\tmedver >> 12, medver & 0xff, mmdch);\n\t} else {\n\t\tlibxmp_set_type(m, \"MED %d.%02x MMD%c\",\n\t\t\t\tmedver >> 8, medver & 0xff, mmdch);\n\t}\n\treturn medver;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/mod.h",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2021 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#ifndef LIBXMP_LOADERS_MOD_H\n#define LIBXMP_LOADERS_MOD_H\n\nstruct mod_instrument {\n\tuint8 name[22];\t\t/* Instrument name */\n\tuint16 size;\t\t/* Sample length in 16-bit words */\n\tint8 finetune;\t\t/* Finetune (signed nibble) */\n\tint8 volume;\t\t/* Linear playback volume */\n\tuint16 loop_start;\t/* Loop start in 16-bit words */\n\tuint16 loop_size;\t/* Loop length in 16-bit words */\n};\n\nstruct mod_header {\n\tuint8 name[20];\n\tstruct mod_instrument ins[31];\n\tuint8 len;\n\tuint8 restart;\t\t/* Number of patterns in Soundtracker,\n\t\t\t\t * Restart in Noisetracker/Startrekker,\n\t\t\t\t * 0x7F in Protracker\n\t\t\t\t */\n\tuint8 order[128];\n\tuint8 magic[4];\n};\n\n#ifndef LIBXMP_CORE_PLAYER\n/* Soundtracker 15-instrument module header */\n\nstruct st_header {\n\tuint8 name[20];\n\tstruct mod_instrument ins[15];\n\tuint8 len;\n\tuint8 restart;\n\tuint8 order[128];\n};\n#endif\n\n#endif  /* LIBXMP_LOADERS_MOD_H */\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/mod_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/* This loader recognizes the following variants of the Protracker\n * module format:\n *\n * - Protracker M.K. and M!K!\n * - Protracker songs\n * - Noisetracker N.T. and M&K! (not tested)\n * - Fast Tracker 6CHN and 8CHN\n * - Fasttracker II/Take Tracker ?CHN and ??CH\n * - Mod's Grave M.K. w/ 8 channels (WOW)\n * - Atari Octalyser CD61 and CD81\n * - Digital Tracker FA04, FA06 and FA08\n * - TakeTracker TDZ1, TDZ2, TDZ3, and TDZ4\n * - (unknown) NSMS, LARD\n *\n * The 'lite' version only recognizes Protracker M.K. and\n * Fasttracker ?CHN and ??CH formats.\n */\n\n#include <ctype.h>\n#include \"loader.h\"\n#include \"mod.h\"\n\n#ifndef LIBXMP_CORE_PLAYER\nstruct mod_magic {\n\tconst char *magic;\n\tint flag;\n\tint id;\n\tint ch;\n};\n\n#define TRACKER_PROTRACKER\t0\n#define TRACKER_NOISETRACKER\t1\n#define TRACKER_SOUNDTRACKER\t2\n#define TRACKER_FASTTRACKER\t3\n#define TRACKER_FASTTRACKER2\t4\n#define TRACKER_OCTALYSER\t5\n#define TRACKER_TAKETRACKER\t6\n#define TRACKER_DIGITALTRACKER\t7\n#define TRACKER_FLEXTRAX\t8\n#define TRACKER_MODSGRAVE\t9\n#define TRACKER_SCREAMTRACKER3\t10\n#define TRACKER_OPENMPT\t\t11\n#define TRACKER_UNKNOWN_CONV\t95\n#define TRACKER_CONVERTEDST\t96\n#define TRACKER_CONVERTED\t97\n#define TRACKER_CLONE\t\t98\n#define TRACKER_UNKNOWN\t\t99\n\n#define TRACKER_PROBABLY_NOISETRACKER 20\n\nconst struct mod_magic mod_magic[] = {\n\t{\"M.K.\", 0, TRACKER_PROTRACKER, 4},\n\t{\"M!K!\", 1, TRACKER_PROTRACKER, 4},\n\t{\"M&K!\", 1, TRACKER_NOISETRACKER, 4},\n\t{\"N.T.\", 1, TRACKER_NOISETRACKER, 4},\n\t{\"6CHN\", 0, TRACKER_FASTTRACKER, 6},\n\t{\"8CHN\", 0, TRACKER_FASTTRACKER, 8},\n\t{\"CD61\", 1, TRACKER_OCTALYSER, 6},\t/* Atari STe/Falcon */\n\t{\"CD81\", 1, TRACKER_OCTALYSER, 8},\t/* Atari STe/Falcon */\n\t{\"TDZ1\", 1, TRACKER_TAKETRACKER, 1},\t/* TakeTracker 1ch */\n\t{\"TDZ2\", 1, TRACKER_TAKETRACKER, 2},\t/* TakeTracker 2ch */\n\t{\"TDZ3\", 1, TRACKER_TAKETRACKER, 3},\t/* TakeTracker 3ch */\n\t{\"TDZ4\", 1, TRACKER_TAKETRACKER, 4},\t/* see XModule SaveTracker.c */\n\t{\"FA04\", 1, TRACKER_DIGITALTRACKER, 4},\t/* Atari Falcon */\n\t{\"FA06\", 1, TRACKER_DIGITALTRACKER, 6},\t/* Atari Falcon */\n\t{\"FA08\", 1, TRACKER_DIGITALTRACKER, 8},\t/* Atari Falcon */\n\t{\"LARD\", 1, TRACKER_UNKNOWN, 4},\t/* in judgement_day_gvine.mod */\n\t{\"NSMS\", 1, TRACKER_UNKNOWN, 4},\t/* in Kingdom.mod */\n};\n\n/* Returns non-zero if the given tracker ONLY supports VBlank timing. This\n * should be used only when the tracker is known for sure, e.g. magic match. */\nstatic int tracker_is_vblank(int id)\n{\n\tswitch (id) {\n\tcase TRACKER_NOISETRACKER:\n\tcase TRACKER_SOUNDTRACKER:\n\t\treturn 1;\n\tdefault:\n\t\treturn 0;\n\t}\n}\n#endif /* LIBXMP_CORE_PLAYER */\n\nstatic int mod_test(HIO_HANDLE *, char *, const int);\nstatic int mod_load(struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_mod = {\n\t#ifdef LIBXMP_CORE_PLAYER\n\t\"Protracker\",\n\t#else\n\t\"Amiga Protracker/Compatible\",\n\t#endif\n\tmod_test,\n\tmod_load\n};\n\n#ifndef LIBXMP_CORE_PLAYER\nstatic int validate_pattern(uint8 *buf)\n{\n\tint i, j;\n\n\tfor (i = 0; i < 64; i++) {\n\t\tfor (j = 0; j < 4; j++) {\n\t\t\tuint8 *d = buf + (i * 4 + j) * 4;\n\t\t\tif ((d[0] >> 4) > 1) {\n\t\t\t\tD_(D_CRIT \"invalid pattern data: row %d ch %d: %02x\", i, j, d[0]);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n#endif\n\nstatic int mod_test(HIO_HANDLE * f, char *t, const int start)\n{\n\tint i;\n\tchar buf[4];\n\t#ifndef LIBXMP_CORE_PLAYER\n\tuint8 pat_buf[1024];\n\tint smp_size, num_pat;\n\tlong size;\n\tint count;\n\tint detected;\n\t#endif\n\n\thio_seek(f, start + 1080, SEEK_SET);\n\tif (hio_read(buf, 1, 4, f) < 4) {\n\t\treturn -1;\n\t}\n\n\tif (!strncmp(buf + 2, \"CH\", 2) &&\n\t    isdigit((unsigned char)buf[0]) && isdigit((unsigned char)buf[1])) {\n\t\ti = (buf[0] - '0') * 10 + buf[1] - '0';\n\t\tif (i > 0 && i <= 32) {\n\t\t\tgoto found;\n\t\t}\n\t}\n\n\tif (!strncmp(buf + 1, \"CHN\", 3) && isdigit((unsigned char)*buf)) {\n\t\tif (*buf - '0') {\n\t\t\tgoto found;\n\t\t}\n\t}\n\n#ifdef LIBXMP_CORE_PLAYER\n\tif (memcmp(buf, \"M.K.\", 4))\n\t\treturn -1;\n#else\n\tfor (i = 0; i < ARRAY_SIZE(mod_magic); i++) {\n\t\tif (!memcmp(buf, mod_magic[i].magic, 4))\n\t\t\tbreak;\n\t}\n\tif (i >= ARRAY_SIZE(mod_magic)) {\n\t\treturn -1;\n\t}\n\n\tdetected = mod_magic[i].flag;\n\n\t/*\n\t * Sanity check to prevent loading NoiseRunner and other module\n\t * formats with valid magic at offset 1080 (e.g. His Master's Noise)\n\t */\n\n\thio_seek(f, start + 20, SEEK_SET);\n\tfor (i = 0; i < 31; i++) {\n\t\tuint8 x;\n\n\t\thio_seek(f, 22, SEEK_CUR);\t/* Instrument name */\n\n\t\t/* OpenMPT can create mods with large samples */\n\t\thio_read16b(f);\t/* sample size */\n\n\t\t/* Chris Spiegel tells me that sandman.mod has 0x20 in finetune */\n\t\tx = hio_read8(f);\n\t\tif (x & 0xf0 && x != 0x20)\t/* test finetune */\n\t\t\treturn -1;\n\t\tif (hio_read8(f) > 0x40)\t/* test volume */\n\t\t\treturn -1;\n\t\thio_read16b(f);\t/* loop start */\n\t\thio_read16b(f);\t/* loop size */\n\t}\n\n\t/* The following checks are only relevant for filtering out atypical\n\t * M.K. variants. If the magic is from a recognizable source, skip them. */\n\tif (detected)\n\t\tgoto found;\n\n\t/* Test for UNIC tracker modules\n\t *\n\t * From Gryzor's Pro-Wizard PW_FORMATS-Engl.guide:\n\t * ``The UNIC format is very similar to Protracker... At least in the\n\t * heading... same length : 1084 bytes. Even the \"M.K.\" is present,\n\t * sometimes !! Maybe to disturb the rippers.. hehe but Pro-Wizard\n\t * doesn't test this only!''\n\t */\n\n\t/* get file size */\n\tsize = hio_size(f);\n\tsmp_size = 0;\n\thio_seek(f, start + 20, SEEK_SET);\n\n\t/* get samples size */\n\tfor (i = 0; i < 31; i++) {\n\t\thio_seek(f, 22, SEEK_CUR);\n\t\tsmp_size += 2 * hio_read16b(f);\t/* Length in 16-bit words */\n\t\thio_seek(f, 6, SEEK_CUR);\n\t}\n\n\t/* get number of patterns */\n\tnum_pat = 0;\n\thio_seek(f, start + 952, SEEK_SET);\n\tfor (i = 0; i < 128; i++) {\n\t\tuint8 x = hio_read8(f);\n\t\tif (x > 0x7f)\n\t\t\tbreak;\n\t\tif (x > num_pat)\n\t\t\tnum_pat = x;\n\t}\n\tnum_pat++;\n\n\t/* see if module size matches UNIC */\n\tif (start + 1084 + num_pat * 0x300 + smp_size == size) {\n\t\tD_(D_CRIT \"module size matches UNIC\");\n\t\treturn -1;\n\t}\n\n\t/* validate pattern data in an attempt to catch UNICs with MOD size */\n\tfor (count = i = 0; i < num_pat; i++) {\n\t\thio_seek(f, start + 1084 + 1024 * i, SEEK_SET);\n\t\tif (!hio_read(pat_buf, 1024, 1, f)) {\n\t\t\tD_(D_WARN \"pattern %d: failed to read pattern data\", i);\n\t\t\treturn -1;\n\t\t}\n\t\tif (validate_pattern(pat_buf) < 0) {\n\t\t\tD_(D_WARN \"pattern %d: error in pattern data\", i);\n\t\t\t/* Allow a few errors, \"lexstacy\" has 0x52 */\n\t\t\tcount++;\n\t\t}\n\t}\n\tif (count > 2) {\n\t\treturn -1;\n\t}\n#endif /* LIBXMP_CORE_PLAYER */\n\nfound:\n\thio_seek(f, start + 0, SEEK_SET);\n\tlibxmp_read_title(f, t, 20);\n\n\treturn 0;\n}\n\n\n#ifndef LIBXMP_CORE_PLAYER\nstatic int is_st_ins(const char *s)\n{\n\tif (s[0] != 's' && s[0] != 'S')\n\t\treturn 0;\n\tif (s[1] != 't' && s[1] != 'T')\n\t\treturn 0;\n\tif (s[2] != '-' || s[5] != ':')\n\t\treturn 0;\n\tif (!isdigit((unsigned char)s[3]) || !isdigit((unsigned char)s[4]))\n\t\treturn 0;\n\n\treturn 1;\n}\n\nstatic int get_tracker_id(struct module_data *m, struct mod_header *mh, int id)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint has_loop_0 = 0;\n\tint has_vol_in_empty_ins = 0;\n\tint i;\n\n\tD_(D_INFO \"attempting initial tracker ID via header details\");\n\n\t/* Check if has instruments with loop size 0 */\n\tfor (i = 0; i < 31; i++) {\n\t\tif (mh->ins[i].loop_size == 0) {\n\t\t\thas_loop_0 = 1;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Check if has instruments with size 0 and volume > 0 */\n\tfor (i = 0; i < 31; i++) {\n\t\tif (mh->ins[i].size == 0 && mh->ins[i].volume > 0) {\n\t\t\thas_vol_in_empty_ins = 1;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/*\n\t * Test Protracker-like files\n\t */\n\tif (mh->restart == mod->pat) {\n\t\tif (mod->chn == 4) {\n\t\t\tD_(D_INFO \"restart=#pat and 4ch -> Soundtracker\");\n\t\t\tid = TRACKER_SOUNDTRACKER;\n\t\t} else {\n\t\t\tD_(D_INFO \"restart=#pat and !4ch -> unknown\");\n\t\t\tid = TRACKER_UNKNOWN;\n\t\t}\n\t} else if (mh->restart == 0x78) {\n\t\tif (mod->chn == 4) {\n\t\t\t/* Can't trust this for Noisetracker, MOD.Data City Remix\n\t\t\t * has Protracker effects and Noisetracker restart byte\n\t\t\t */\n\t\t\tD_(D_INFO \"restart=78 and 4ch -> maybe Noisetracker\");\n\t\t\tid = TRACKER_PROBABLY_NOISETRACKER;\n\t\t} else {\n\t\t\tD_(D_INFO \"restart=78 and !4ch -> unknown\");\n\t\t\tid = TRACKER_UNKNOWN;\n\t\t}\n\t\treturn id;\n\t} else if (mh->restart < 0x7f) {\n\t\tif (mod->chn == 4 && !has_vol_in_empty_ins) {\n\t\t\tD_(D_INFO \"restart<7f and 4ch and empty ins are \"\n\t\t\t   \"volume 0 -> Noisetracker\");\n\t\t\tid = TRACKER_NOISETRACKER;\n\t\t} else {\n\t\t\tD_(D_INFO \"restart<7f and not Noisetracker -> unknown\");\n\t\t\tid = TRACKER_UNKNOWN; /* ? */\n\t\t}\n\t\tmod->rst = mh->restart;\n\t} else if (mh->restart == 0x7f) {\n\t\tif (mod->chn == 4) {\n\t\t\tif (has_loop_0) {\n\t\t\t\tD_(D_INFO \"restart=7f and 4ch and 0-size loop -> clone\");\n\t\t\t\tid = TRACKER_CLONE;\n\t\t\t}\n\t\t} else {\n\t\t\tD_(D_INFO \"restart=7f and !4ch -> Scream Tracker\");\n\t\t\tid = TRACKER_SCREAMTRACKER3;\n\t\t}\n\t\treturn id;\n\t} else if (mh->restart > 0x7f) {\n\t\tD_(D_INFO \"restart>7f -> unknown\");\n\t\tid = TRACKER_UNKNOWN; /* ? */\n\t\treturn id;\n\t}\n\n\tif (!has_loop_0) { /* All loops are size 2 or greater */\n\t\tD_(D_INFO \"no instrument 0-length loops...\");\n\n\t\tfor (i = 0; i < 31; i++) {\n\t\t\tif (mh->ins[i].size == 1 && mh->ins[i].volume == 0) {\n\t\t\t\tD_(D_INFO \"...and length 2 with 0 volume -> converted\");\n\t\t\t\treturn TRACKER_CONVERTED;\n\t\t\t}\n\t\t}\n\n\t\tfor (i = 0; i < 31; i++) {\n\t\t\tif (is_st_ins((char *)mh->ins[i].name))\n\t\t\t\tbreak;\n\t\t}\n\t\tif (i == 31) {\t/* No st- instruments */\n\t\t\tD_(D_INFO \"...and no ST- instruments...\");\n\t\t\tfor (i = 0; i < 31; i++) {\n\t\t\t\tif (mh->ins[i].size != 0\n\t\t\t\t    || mh->ins[i].loop_size != 1) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tswitch (mod->chn) {\n\t\t\t\tcase 4:\n\t\t\t\t\tif (has_vol_in_empty_ins) {\n\t\t\t\t\t\tD_(D_INFO \"...and 4ch, vol in empty -> OpenMPT\");\n\t\t\t\t\t\tid = TRACKER_OPENMPT;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tD_(D_INFO \"...and 4ch -> Noisetracker\");\n\t\t\t\t\t\tid = TRACKER_NOISETRACKER;\n\t\t\t\t\t\t/* or Octalyser */\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 6:\n\t\t\t\tcase 8:\n\t\t\t\t\tD_(D_INFO \"..and 6ch or 8ch -> Octalyser\");\n\t\t\t\t\tid = TRACKER_OCTALYSER;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tid = TRACKER_UNKNOWN;\n\t\t\t\t}\n\t\t\t\treturn id;\n\t\t\t}\n\n\t\t\tD_(D_INFO \"...and no empty...\");\n\t\t\tif (mod->chn == 4) {\n\t\t\t\tD_(D_INFO \"...and 4ch -> Protracker\");\n\t\t\t\tid = TRACKER_PROTRACKER;\n\t\t\t} else if (mod->chn == 6 || mod->chn == 8) {\n\t\t\t\t/* FastTracker 1.01? */\n\t\t\t\tD_(D_INFO \"...and 6ch or 8ch -> Fast Tracker\");\n\t\t\t\tid = TRACKER_FASTTRACKER;\n\t\t\t} else {\n\t\t\t\tD_(D_INFO \"...and %dch -> unknown\", mod->chn);\n\t\t\t\tid = TRACKER_UNKNOWN;\n\t\t\t}\n\t\t}\n\t} else { /* Has loops with size 0 */\n\t\tD_(D_INFO \"instrument with 0-length loop...\");\n\t\tfor (i = 15; i < 31; i++) {\n\t\t\t/* Is the name or size set? */\n\t\t\tif (mh->ins[i].name[0] || mh->ins[i].size > 0)\n\t\t\t\tbreak;\n\t\t}\n\t\tif (i == 31 && is_st_ins((char *)mh->ins[14].name)) {\n\t\t\tD_(D_INFO \"...and <=15 instruments and ST-* -> converted ST\");\n\t\t\treturn TRACKER_CONVERTEDST;\n\t\t}\n\n\t\t/* Assume that Fast Tracker modules won't have ST- instruments */\n\t\tfor (i = 0; i < 31; i++) {\n\t\t\tif (is_st_ins((char *)mh->ins[i].name))\n\t\t\t\tbreak;\n\t\t}\n\t\tif (i < 31) {\n\t\t\tD_(D_INFO \"...and >15 instruments and ST-* -> converted\");\n\t\t\treturn TRACKER_UNKNOWN_CONV;\n\t\t}\n\n\t\tif (mod->chn == 4 || mod->chn == 6 || mod->chn == 8) {\n\t\t\tD_(D_INFO \"...and >15 instruments and 4ch/6ch/8ch -> Fast Tracker\");\n\t\t\treturn TRACKER_FASTTRACKER;\n\t\t}\n\n\t\tD_(D_INFO \"...and >15 instruments and %dch -> unknown\", mod->chn);\n\t\tid = TRACKER_UNKNOWN;\t/* ?! */\n\t}\n\n\treturn id;\n}\n#endif /* LIBXMP_CORE_PLAYER */\n\nstatic int mod_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n    struct xmp_module *mod = &m->mod;\n    int i, j, k;\n    struct xmp_event *event;\n    struct mod_header mh;\n    char magic[8];\n    uint8 *patbuf;\n#ifndef LIBXMP_CORE_PLAYER\n    uint8 pat_high_fxx[256];\n    const char *tracker = \"\";\n    int detected = 0;\n    int tracker_id = TRACKER_PROTRACKER;\n    int out_of_range = 0;\n    int maybe_wow = 1;\n    int smp_size, ptsong = 0;\n    int needs_timing_detection = 0;\n    int samerow_fxx = 0;\t\t/* speed + BPM set on same row */\n    int high_fxx = 0;\t\t\t/* high Fxx is used anywhere */\n    int invert_loop = 0;\t\t/* EFx found anywhere in module */\n#endif\n    int ptkloop = 0;\t\t\t/* Protracker loop */\n\n    LOAD_INIT();\n\n    mod->ins = 31;\n    mod->smp = mod->ins;\n    mod->chn = 0;\n    #ifndef LIBXMP_CORE_PLAYER\n    smp_size = 0;\n    #else\n    m->quirk |= QUIRK_PROTRACK;\n    #endif\n    m->period_type = PERIOD_MODRNG;\n\n    hio_read(mh.name, 20, 1, f);\n    for (i = 0; i < 31; i++) {\n\thio_read(mh.ins[i].name, 22, 1, f);\t/* Instrument name */\n\tmh.ins[i].size = hio_read16b(f);\t/* Length in 16-bit words */\n\tmh.ins[i].finetune = hio_read8(f);\t/* Finetune (signed nibble) */\n\tmh.ins[i].volume = hio_read8(f);\t/* Linear playback volume */\n\tmh.ins[i].loop_start = hio_read16b(f);\t/* Loop start in 16-bit words */\n\tmh.ins[i].loop_size = hio_read16b(f);\t/* Loop size in 16-bit words */\n\n\t#ifndef LIBXMP_CORE_PLAYER\n\t/* Mod's Grave WOW files are converted from 669s and have default\n\t * finetune and volume.\n\t */\n\tif (mh.ins[i].size && (mh.ins[i].finetune != 0 || mh.ins[i].volume != 64))\n\t    maybe_wow = 0;\n\n\tsmp_size += 2 * mh.ins[i].size;\n\t#endif\n    }\n    mh.len = hio_read8(f);\n    mh.restart = hio_read8(f);\n    hio_read(mh.order, 128, 1, f);\n    memset(magic, 0, sizeof(magic));\n    hio_read(magic, 1, 4, f);\n    if (hio_error(f)) {\n        return -1;\n    }\n\n#ifndef LIBXMP_CORE_PLAYER\n    /* Mod's Grave WOW files always have a 0 restart byte; 6692WOW implements\n     * 669 repeating by inserting a pattern jump and ignores this byte.\n     */\n    if (mh.restart != 0)\n\tmaybe_wow = 0;\n\n    for (i = 0; i < ARRAY_SIZE(mod_magic); i++) {\n\tif (!(strncmp (magic, mod_magic[i].magic, 4))) {\n\t    mod->chn = mod_magic[i].ch;\n\t    tracker_id = mod_magic[i].id;\n\t    detected = mod_magic[i].flag;\n\t    D_(D_INFO \"magic match %4.4s -> %d\", magic, tracker_id);\n\t    break;\n\t}\n    }\n\n    /* Enable timing detection for M.K. and M!K! modules. */\n    if (tracker_id == TRACKER_PROTRACKER)\n\tneeds_timing_detection = 1;\n\n    /* Digital Tracker MODs have an extra four bytes after the magic.\n     * These are always 00h 40h 00h 00h and can probably be ignored. */\n    if (tracker_id == TRACKER_DIGITALTRACKER) {\n\thio_read32b(f);\n    }\n#endif\n\n    if (mod->chn == 0) {\n\t#ifdef LIBXMP_CORE_PLAYER\n\tif (!memcmp(magic, \"M.K.\", 4)) {\n\t\tmod->chn = 4;\n\t} else\n\t#endif\n\tif (!strncmp(magic + 2, \"CH\", 2) &&\n\t    isdigit((unsigned char)magic[0]) && isdigit((unsigned char)magic[1])) {\n\t    mod->chn = (*magic - '0') * 10 + magic[1] - '0';\n\t} else if (!strncmp(magic + 1, \"CHN\", 3) && isdigit((unsigned char)*magic)) {\n\t    mod->chn = *magic - '0';\n\t} else {\n\t    return -1;\n\t}\n#ifndef LIBXMP_CORE_PLAYER\n\tD_(D_INFO \"#CHN or ##CH signature -> FastTracker or TakeTracker\");\n\ttracker_id = mod->chn & 1 ? TRACKER_TAKETRACKER : TRACKER_FASTTRACKER2;\n\tdetected = 1;\n#endif\n    }\n\n    strncpy(mod->name, (char *) mh.name, 20);\n\n    mod->len = mh.len;\n    memcpy(mod->xxo, mh.order, 128);\n\n    if (mh.restart < 0x7f && mh.restart != 0x78 && (int)mh.restart < mod->len) {\n\t/* TODO: an older version of this code was commented out 23+ years ago\n\t * and adding this may have rebroke something. */\n\tmod->rst = mh.restart;\n    }\n\n    for (i = 0; i < 128; i++) {\n\t/* This fixes dragnet.mod (garbage in the order list) */\n\tif (mod->xxo[i] > 0x7f)\n\t\tbreak;\n\tif (mod->xxo[i] > mod->pat)\n\t    mod->pat = mod->xxo[i];\n    }\n    mod->pat++;\n\n    if (libxmp_init_instrument(m) < 0)\n\treturn -1;\n\n    for (i = 0; i < mod->ins; i++) {\n\tstruct xmp_instrument *xxi;\n\tstruct xmp_subinstrument *sub;\n\tstruct xmp_sample *xxs;\n\n\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t    return -1;\n\n#ifndef LIBXMP_CORE_PLAYER\n\tif (mh.ins[i].size >= 0x8000) {\n\t    D_(D_INFO \"sample %d >64k length -> OpenMPT\", i);\n\t    tracker_id = TRACKER_OPENMPT;\n\t    needs_timing_detection = 0;\n\t    detected = 1;\n\t}\n#endif\n\n\txxi = &mod->xxi[i];\n\tsub = &xxi->sub[0];\n\txxs = &mod->xxs[i];\n\n\txxs->len = 2 * mh.ins[i].size;\n\txxs->lps = 2 * mh.ins[i].loop_start;\n\txxs->lpe = xxs->lps + 2 * mh.ins[i].loop_size;\n\tif (xxs->lpe > xxs->len) {\n\t\txxs->lpe = xxs->len;\n\t}\n\txxs->flg = (mh.ins[i].loop_size > 1 && xxs->lpe >= 4) ?\n\t\tXMP_SAMPLE_LOOP : 0;\n\tsub->fin = (int8)(mh.ins[i].finetune << 4);\n\tsub->vol = mh.ins[i].volume;\n\tsub->pan = 0x80;\n\tsub->sid = i;\n\tlibxmp_instrument_name(mod, i, mh.ins[i].name, 22);\n\n\tif (xxs->len > 0) {\n\t\txxi->nsm = 1;\n\t}\n    }\n\n#ifndef LIBXMP_CORE_PLAYER\n    /* Experimental tracker-detection routine */\n\n    if (detected)\n\tgoto skip_test;\n\n    /* Test for Flextrax modules\n     *\n     * FlexTrax is a soundtracker for Atari Falcon030 compatible computers.\n     * FlexTrax supports the standard MOD file format (up to eight channels)\n     * for compatibility reasons but also features a new enhanced module\n     * format FLX. The FLX format is an extended version of the standard\n     * MOD file format with support for real-time sound effects like reverb\n     * and delay.\n     */\n\n    if (0x43c + mod->pat * 4 * mod->chn * 0x40 + smp_size < m->size) {\n\tchar idbuffer[4];\n\tint pos = hio_tell(f);\n\tint num_read;\n        if (pos < 0) {\n           return -1;\n        }\n\thio_seek(f, start + 0x43c + mod->pat * 4 * mod->chn * 0x40 + smp_size, SEEK_SET);\n\tnum_read = hio_read(idbuffer, 1, 4, f);\n\thio_seek(f, start + pos, SEEK_SET);\n\n\tif (num_read == 4 && !memcmp(idbuffer, \"FLEX\", 4)) {\n\t    D_(D_INFO \"FlexTrax FLEX data detected -> FlexTrax\");\n\t    tracker_id = TRACKER_FLEXTRAX;\n\t    needs_timing_detection = 0;\n\t    detected = 1;\n\t    goto skip_test;\n\t}\n    }\n\n    /* Test for Mod's Grave WOW modules\n     *\n     * Stefan Danes <sdanes@marvels.hacktic.nl> said:\n     * This weird format is identical to '8CHN' but still uses the 'M.K.' ID.\n     * You can only test for WOW by calculating the size of the module for 8\n     * channels and comparing this to the actual module length. If it's equal,\n     * the module is an 8 channel WOW.\n     *\n     * Addendum: very rarely, WOWs will have an odd length due to an extra byte,\n     * so round the filesize down in this check. False positive WOWs can be ruled\n     * out by checking the restart byte and sample volume (see above).\n     *\n     * Worst case if there are still issues with this, OpenMPT validates later\n     * patterns in potential WOW files (where sample data would be located in a\n     * regular M.K. MOD) to rule out false positives.\n     */\n\n    if (!strncmp(magic, \"M.K.\", 4) && maybe_wow &&\n\t\t(0x43c + mod->pat * 32 * 0x40 + smp_size) == (m->size & ~1)) {\n\tD_(D_INFO \"file is M.K. with Mod's Grave characteristics -> Mod's Grave\");\n\tmod->chn = 8;\n\ttracker_id = TRACKER_MODSGRAVE;\n\tneeds_timing_detection = 0;\n\tdetected = 1;\n    } else {\n\t/* Test for Protracker song files */\n\tptsong = !strncmp((char *)magic, \"M.K.\", 4) &&\n\t\t (0x43c + mod->pat * 0x400 == m->size);\n\tif (ptsong) {\n\t\tD_(D_INFO \"file is Protracker song length M.K. -> Protracker\");\n\t\ttracker_id = TRACKER_PROTRACKER;\n\t\tdetected = 1;\n\t\tgoto skip_test;\n\t} else {\n\t/* something else */\n\t\ttracker_id = get_tracker_id(m, &mh, tracker_id);\n\t}\n    }\n\nskip_test:\n    D_(D_INFO \"initial tracker ID: %d%s\", tracker_id, detected ? \" (locked in)\" : \"\");\n#endif\n\n    if (mod->chn >= XMP_MAX_CHANNELS) {\n        return -1;\n    }\n\n    mod->trk = mod->chn * mod->pat;\n\n    for (i = 0; i < mod->ins; i++) {\n\tD_(D_INFO \"[%2X] %-22.22s %04x %04x %04x %c V%02x %+d %c\",\n\t\ti, mod->xxi[i].name,\n\t\tmod->xxs[i].len, mod->xxs[i].lps, mod->xxs[i].lpe,\n\t\t(mh.ins[i].loop_size > 1 && mod->xxs[i].lpe > 8) ?\n\t\t\t'L' : ' ', mod->xxi[i].sub[0].vol,\n\t\tmod->xxi[i].sub[0].fin >> 4,\n\t\tptkloop && mod->xxs[i].lps == 0 && mh.ins[i].loop_size > 1 &&\n\t\t\tmod->xxs[i].len > mod->xxs[i].lpe ? '!' : ' ');\n    }\n\n    if (libxmp_init_pattern(mod) < 0)\n\treturn -1;\n\n    /* Load and convert patterns */\n    D_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n    if ((patbuf = (uint8 *) malloc(64 * 4 * mod->chn)) == NULL) {\n\treturn -1;\n    }\n\n#ifndef LIBXMP_CORE_PLAYER\n    memset(pat_high_fxx, 0, sizeof(pat_high_fxx));\n#endif\n\n    for (i = 0; i < mod->pat; i++) {\n\tuint8 *mod_event;\n\n\tif (libxmp_alloc_pattern_tracks(mod, i, 64) < 0) {\n\t    free(patbuf);\n\t    return -1;\n\t}\n\n\tif (hio_read(patbuf, 64 * 4 * mod->chn, 1, f) < 1) {\n\t    free(patbuf);\n\t    return -1;\n\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\tmod_event = patbuf;\n\tfor (j = 0; j < 64; j++) {\n\t    int speed_row = 0;\n\t    int bpm_row = 0;\n\t    for (k = 0; k < mod->chn; k++) {\n\t\tint period;\n\n\t\tperiod = ((int)(LSN(mod_event[0])) << 8) | mod_event[1];\n\t\tif (period != 0 && (period < 108 || period > 907)) {\n\t\t    out_of_range = 1;\n\t\t}\n\n\t\t/* Filter noisetracker events */\n\t\tif (tracker_id == TRACKER_PROBABLY_NOISETRACKER) {\n\t\t    unsigned char fxt = LSN(mod_event[2]);\n\t\t    unsigned char fxp = LSN(mod_event[3]);\n\n\t\t    if ((fxt > 0x06 && fxt < 0x0a) || (fxt == 0x0e && fxp > 1)) {\n\t\t\tD_(D_INFO \"non-Noisetracker effect -> unknown\");\n\t\t\ttracker_id = TRACKER_UNKNOWN;\n\t\t    }\n\t\t}\n\t\t/* Needs CIA/VBlank detection? */\n\t\tif (LSN(mod_event[2]) == 0x0f) {\n\t\t    if (mod_event[3] >= 0x20) {\n\t\t\tpat_high_fxx[i] = mod_event[3];\n\t\t\tm->compare_vblank = 1;\n\t\t\thigh_fxx = 1;\n\t\t\tbpm_row = 1;\n\t\t    } else {\n\t\t\tspeed_row = 1;\n\t\t    }\n\t\t}\n\t\t/* Usage of effect EFx is typically Protracker invert loop. */\n\t\tif (LSN(mod_event[2]) == 0xe && MSN(mod_event[3]) == 0xf) {\n\t\t    invert_loop = 1;\n\t\t}\n\t\tmod_event += 4;\n\t    }\n\t    if (bpm_row && speed_row) {\n\t\tsamerow_fxx = 1;\n\t    }\n\t}\n\n\tif (out_of_range) {\n\t    if (tracker_id == TRACKER_UNKNOWN && mh.restart == 0x7f) {\n\t\tD_(D_INFO \"unknown tracker w/ restart 0x7f and out-of-range \"\n\t\t   \"notes are present -> Scream Tracker\");\n\t\ttracker_id = TRACKER_SCREAMTRACKER3;\n\t    }\n\n\t    /* Check out-of-range notes in Amiga trackers */\n\t    if (tracker_id == TRACKER_PROTRACKER ||\n\t\ttracker_id == TRACKER_NOISETRACKER ||\n\t\ttracker_id == TRACKER_PROBABLY_NOISETRACKER ||\n\t\ttracker_id == TRACKER_SOUNDTRACKER) {   /* note > B-3 */\n\n\t\tD_(D_INFO \"maybe-Amiga with out-of-range note -> unknown\");\n\t\ttracker_id = TRACKER_UNKNOWN;\n\t    }\n\t} else if (invert_loop && !detected &&\n\t    (tracker_id == TRACKER_NOISETRACKER ||\n\t     tracker_id == TRACKER_PROBABLY_NOISETRACKER)) {\n\t    /* Switch Noisetracker to Protracker to disable event filtering. */\n\t    D_(D_INFO \"effect EFx is present in suspected Noisetracker -> \"\n\t       \"more likely Protracker\");\n\t    tracker_id = TRACKER_PROTRACKER;\n\t}\n#endif\n\n\tmod_event = patbuf;\n\tfor (j = 0; j < 64; j++) {\n\t    for (k = 0; k < mod->chn; k++) {\n\t\tevent = &EVENT(i, k, j);\n#ifdef LIBXMP_CORE_PLAYER\n\t\tlibxmp_decode_protracker_event(event, mod_event);\n#else\n\t\tswitch (tracker_id) {\n\t\tcase TRACKER_PROBABLY_NOISETRACKER:\n\t\tcase TRACKER_NOISETRACKER:\n\t\t    libxmp_decode_noisetracker_event(event, mod_event);\n\t\t    break;\n\t\tdefault:\n\t\t    libxmp_decode_protracker_event(event, mod_event);\n\t\t}\n#endif\n\t\tmod_event += 4;\n\t    }\n\t}\n    }\n    free(patbuf);\n\n#ifndef LIBXMP_CORE_PLAYER\n    /* VBlank detection routine.\n     * Despite VBlank being dependent on the tracker used, VBlank detection\n     * is complex and uses heuristics mostly independent from tracker ID.\n     * See also: the scan.c comparison code enabled by m->compare_vblank\n     */\n    if (!needs_timing_detection) {\n\t/* Noisetracker and some other trackers do not support CIA timing. The\n\t * only known MOD in the wild that relies on this is muppenkorva.mod\n\t * by Glue Master (loaded by the His Master's Noise loader).\n\t */\n\tif (tracker_is_vblank(tracker_id)) {\n\t    m->quirk |= QUIRK_NOBPM;\n\t}\n\tm->compare_vblank = 0;\n\n    } else if (samerow_fxx) {\n\t/* If low Fxx and high Fxx are on the same row, there's a high chance\n\t * this is from a CIA-based tracker. There are some exceptions.\n\t */\n\tif (tracker_id == TRACKER_NOISETRACKER ||\n\t    tracker_id == TRACKER_PROBABLY_NOISETRACKER ||\n\t    tracker_id == TRACKER_SOUNDTRACKER) {\n\n\t    D_(D_INFO \"low Fxx and high Fxx on the same line, but tracker ID \"\n\t       \"selected a VBlank-only tracker -> unknown\");\n\t    tracker_id = TRACKER_UNKNOWN;\n\t}\n\tm->compare_vblank = 0;\n\n    } else if (high_fxx && mod->len >= 8) {\n\t/* Test for high Fxx at the end only--this is typically VBlank,\n\t * and is used to add silence to the end of modules.\n\t *\n\t * Exception: if the final high Fxx is F7D, this module is either CIA\n\t * or is VBlank that was modified to play as CIA, so do nothing.\n\t *\n\t * TODO: MPT resets modules on the end loop, so some of the very long\n\t * silent sections in modules affected by this probably expect CIA. It\n\t * should eventually be possible to detect those.\n\t */\n\tconst int threshold = mod->len - 2;\n\n\tfor (i = 0; i < threshold; i++) {\n\t    if (pat_high_fxx[mod->xxo[i]])\n\t\tbreak;\n\t}\n\tif (i == threshold) {\n\t    for (i = mod->len - 1; i >= threshold; i--) {\n\t\tuint8 fxx = pat_high_fxx[mod->xxo[i]];\n\t\tif (fxx == 0x00)\n\t\t    continue;\n\t\tif (fxx == 0x7d)\n\t\t    break;\n\n\t\tm->compare_vblank = 0;\n\t\tm->quirk |= QUIRK_NOBPM;\n\t\tbreak;\n\t    }\n\t}\n    }\n\n    if (invert_loop && !detected && !out_of_range) {\n\t/* If EFx was detected and NO notes were out-of-range,\n\t * that's a strong indicator of a Protracker origin. */\n\tD_(D_INFO \"effect EFx and no out-of-range notes -> Protracker or OpenMPT\");\n\tif (tracker_id != TRACKER_OPENMPT) {\n\t    tracker_id = TRACKER_PROTRACKER;\n\t}\n\tdetected = 1;\n    }\n\n    switch (tracker_id) {\n    case TRACKER_PROTRACKER:\n\ttracker = \"Protracker\";\n\tptkloop = 1;\n\tbreak;\n    case TRACKER_PROBABLY_NOISETRACKER:\n    case TRACKER_NOISETRACKER:\n\ttracker = \"Noisetracker\";\n\tbreak;\n    case TRACKER_SOUNDTRACKER:\n\ttracker = \"Soundtracker\";\n\tbreak;\n    case TRACKER_FASTTRACKER:\n    case TRACKER_FASTTRACKER2:\n\ttracker = \"Fast Tracker\";\n\tm->period_type = PERIOD_AMIGA;\n\tbreak;\n    case TRACKER_TAKETRACKER:\n\ttracker = \"Take Tracker\";\n\tm->period_type = PERIOD_AMIGA;\n\tbreak;\n    case TRACKER_OCTALYSER:\n\ttracker = \"Octalyser\";\n\tif (detected) {\n\t\tm->flow_mode = FLOW_MODE_OCTALYSER;\n\t}\n\tbreak;\n    case TRACKER_DIGITALTRACKER:\n\ttracker = \"Digital Tracker\";\n\tm->flow_mode = FLOW_MODE_DTM_2015;\n\tbreak;\n    case TRACKER_FLEXTRAX:\n\ttracker = \"Flextrax\";\n\tbreak;\n    case TRACKER_MODSGRAVE:\n\ttracker = \"Mod's Grave\";\n\tbreak;\n    case TRACKER_SCREAMTRACKER3:\n\ttracker = \"Scream Tracker\";\n\tm->period_type = PERIOD_AMIGA;\n\tbreak;\n    case TRACKER_CONVERTEDST:\n    case TRACKER_CONVERTED:\n\ttracker = \"Converted\";\n\tbreak;\n    case TRACKER_CLONE:\n\ttracker = \"Protracker clone\";\n\tm->period_type = PERIOD_AMIGA;\n\tbreak;\n    case TRACKER_OPENMPT:\n\ttracker = \"OpenMPT\";\n\tptkloop = 1;\n\tbreak;\n    default:\n    case TRACKER_UNKNOWN_CONV:\n    case TRACKER_UNKNOWN:\n\ttracker = \"Unknown tracker\";\n\tm->period_type = PERIOD_AMIGA;\n\tbreak;\n    }\n\n    if (out_of_range) {\n\tm->period_type = PERIOD_AMIGA;\n    }\n\n    if (tracker_id == TRACKER_MODSGRAVE) {\n\tsnprintf(mod->type, XMP_NAME_SIZE, \"%s\", tracker);\n    } else {\n\tsnprintf(mod->type, XMP_NAME_SIZE, \"%s %s\", tracker, magic);\n    }\n\n    D_(D_INFO \"final tracker ID: %d\", tracker_id);\n#else\n    libxmp_set_type(m, (mod->chn == 4) ? \"Protracker\" : \"Fasttracker\");\n#endif\n\n    MODULE_INFO();\n\n    /* Load samples */\n\n    D_(D_INFO \"Stored samples: %d\", mod->smp);\n\n    for (i = 0; i < mod->smp; i++) {\n\tint flags;\n\n\tif (!mod->xxs[i].len)\n\t    continue;\n\n\tflags = (ptkloop && mod->xxs[i].lps == 0) ? SAMPLE_FLAG_FULLREP : 0;\n\n#ifdef LIBXMP_CORE_PLAYER\n\tif (libxmp_load_sample(m, f, flags, &mod->xxs[i], NULL) < 0)\n\t\treturn -1;\n#else\n\tif (ptsong) {\n\t    HIO_HANDLE *s;\n\t    char sn[XMP_MAXPATH];\n\t    char tmpname[32];\n\t    const char *instname = mod->xxi[i].name;\n\n\t    if (libxmp_copy_name_for_fopen(tmpname, instname, 32) != 0)\n\t\tcontinue;\n\n\t    if (!libxmp_find_instrument_file(m, sn, sizeof(sn), tmpname))\n\t\tcontinue;\n\n\t    if ((s = hio_open(sn, \"rb\")) == NULL)\n\t\tcontinue;\n\n\t    if (libxmp_load_sample(m, s, flags, &mod->xxs[i], NULL) < 0) {\n\t\thio_close(s);\n\t\treturn -1;\n\t    }\n\t    hio_close(s);\n\t} else {\n\t    uint8 buf[5];\n\t    long pos;\n\t    int num;\n\n\t    if ((pos = hio_tell(f)) < 0) {\n\t\treturn -1;\n\t    }\n\t    num = hio_read(buf, 1, 5, f);\n\n\t    if (num == 5 && !memcmp(buf, \"ADPCM\", 5)) {\n\t\tflags |= SAMPLE_FLAG_ADPCM;\n\t    } else {\n\t\thio_seek(f, pos, SEEK_SET);\n\t    }\n\n\t    if (libxmp_load_sample(m, f, flags, &mod->xxs[i], NULL) < 0)\n\t\treturn -1;\n\t}\n#endif\n    }\n\n    #ifdef LIBXMP_CORE_PLAYER\n    if (mod->chn > 4) {\n\tm->quirk &= ~QUIRK_PROTRACK;\n\tm->quirk |= QUIRKS_FT2 | QUIRK_FTMOD;\n\tm->read_event_type = READ_EVENT_FT2;\n\tm->period_type = PERIOD_AMIGA;\n    }\n    #else\n    if (tracker_id == TRACKER_PROTRACKER || tracker_id == TRACKER_OPENMPT) {\n\tm->quirk |= QUIRK_PROTRACK;\n    } else if (tracker_id == TRACKER_SCREAMTRACKER3) {\n\tm->c4rate = C4_NTSC_RATE;\n\tm->quirk |= QUIRKS_ST3;\n\tm->read_event_type = READ_EVENT_ST3;\n    } else if (tracker_id == TRACKER_FASTTRACKER || tracker_id == TRACKER_FASTTRACKER2 || tracker_id == TRACKER_TAKETRACKER || tracker_id == TRACKER_MODSGRAVE || mod->chn > 4) {\n\tm->c4rate = C4_NTSC_RATE;\n\tm->quirk |= QUIRKS_FT2 | QUIRK_FTMOD;\n\tm->read_event_type = READ_EVENT_FT2;\n\tm->period_type = PERIOD_AMIGA;\n    }\n    #endif\n\n    return 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/mtm_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"loader.h\"\n\nstruct mtm_file_header {\n\tuint8 magic[3];\t\t/* \"MTM\" */\n\tuint8 version;\t\t/* MSN=major, LSN=minor */\n\tuint8 name[20];\t\t/* ASCIIZ Module name */\n\tuint16 tracks;\t\t/* Number of tracks saved */\n\tuint8 patterns;\t\t/* Number of patterns saved */\n\tuint8 modlen;\t\t/* Module length */\n\tuint16 extralen;\t/* Length of the comment field */\n\tuint8 samples;\t\t/* Number of samples */\n\tuint8 attr;\t\t/* Always zero */\n\tuint8 rows;\t\t/* Number rows per track */\n\tuint8 channels;\t\t/* Number of tracks per pattern */\n\tuint8 pan[32];\t\t/* Pan positions for each channel */\n};\n\nstruct mtm_instrument_header {\n\tuint8 name[22];\t\t/* Instrument name */\n\tuint32 length;\t\t/* Instrument length in bytes */\n\tuint32 loop_start;\t/* Sample loop start */\n\tuint32 loopend;\t\t/* Sample loop end */\n\tuint8 finetune;\t\t/* Finetune */\n\tuint8 volume;\t\t/* Playback volume */\n\tuint8 attr;\t\t/* &0x01: 16bit sample */\n};\n\nstatic int mtm_test(HIO_HANDLE *, char *, const int);\nstatic int mtm_load(struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_mtm = {\n\t\"Multitracker\",\n\tmtm_test,\n\tmtm_load\n};\n\nstatic int mtm_test(HIO_HANDLE *f, char *t, const int start)\n{\n\tuint8 buf[4];\n\n\tif (hio_read(buf, 1, 4, f) < 4)\n\t\treturn -1;\n\tif (memcmp(buf, \"MTM\", 3))\n\t\treturn -1;\n\tif (buf[3] != 0x10)\n\t\treturn -1;\n\n\tlibxmp_read_title(f, t, 20);\n\n\treturn 0;\n}\n\nstatic int mtm_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j, k;\n\tstruct mtm_file_header mfh;\n\tstruct mtm_instrument_header mih;\n\tuint8 mt[192];\n\tint fxx[2];\n\n\tLOAD_INIT();\n\n\thio_read(mfh.magic, 3, 1, f);\t/* \"MTM\" */\n\tmfh.version = hio_read8(f);\t/* MSN=major, LSN=minor */\n\thio_read(mfh.name, 20, 1, f);\t/* ASCIIZ Module name */\n\tmfh.tracks = hio_read16l(f);\t/* Number of tracks saved */\n\tmfh.patterns = hio_read8(f);\t/* Number of patterns saved */\n\tmfh.modlen = hio_read8(f);\t/* Module length */\n\tmfh.extralen = hio_read16l(f);\t/* Length of the comment field */\n\n\tmfh.samples = hio_read8(f);\t/* Number of samples */\n\tif (mfh.samples > 63) {\n\t\treturn -1;\n\t}\n\n\tmfh.attr = hio_read8(f);\t/* Always zero */\n\n\tmfh.rows = hio_read8(f);\t/* Number rows per track */\n\tif (mfh.rows != 64)\n\t\treturn -1;\n\n\tmfh.channels = hio_read8(f);\t/* Number of tracks per pattern */\n\tif (mfh.channels > MIN(32, XMP_MAX_CHANNELS)) {\n\t\treturn -1;\n\t}\n\n\thio_read(mfh.pan, 32, 1, f);\t/* Pan positions for each channel */\n\n\tif (hio_error(f)) {\n\t\treturn -1;\n\t}\n\n#if 0\n\tif (strncmp((char *)mfh.magic, \"MTM\", 3))\n\t\treturn -1;\n#endif\n\n\tmod->trk = mfh.tracks + 1;\n\tmod->pat = mfh.patterns + 1;\n\tmod->len = mfh.modlen + 1;\n\tmod->ins = mfh.samples;\n\tmod->smp = mod->ins;\n\tmod->chn = mfh.channels;\n\tmod->spd = 6;\n\tmod->bpm = 125;\n\n\tstrncpy(mod->name, (char *)mfh.name, 20);\n\tlibxmp_set_type(m, \"MultiTracker %d.%02d MTM\", MSN(mfh.version),\n\t\t LSN(mfh.version));\n\n\tMODULE_INFO();\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\treturn -1;\n\n\t/* Read and convert instruments */\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\t\tstruct xmp_sample *xxs = &mod->xxs[i];\n\t\tstruct xmp_subinstrument *sub;\n\n\t\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t\t\treturn -1;\n\n\t\tsub = &xxi->sub[0];\n\n\t\thio_read(mih.name, 22, 1, f);\t/* Instrument name */\n\t\tmih.length = hio_read32l(f);\t/* Instrument length in bytes */\n\n\t\tif (mih.length > MAX_SAMPLE_SIZE)\n\t\t\treturn -1;\n\n\t\tmih.loop_start = hio_read32l(f); /* Sample loop start */\n\t\tmih.loopend = hio_read32l(f);\t/* Sample loop end */\n\t\tmih.finetune = hio_read8(f);\t/* Finetune */\n\t\tmih.volume = hio_read8(f);\t/* Playback volume */\n\t\tmih.attr = hio_read8(f);\t/* &0x01: 16bit sample */\n\n\t\txxs->len = mih.length;\n\t\txxs->lps = mih.loop_start;\n\t\txxs->lpe = mih.loopend;\n\t\txxs->flg = (xxs->lpe > 2) ? XMP_SAMPLE_LOOP : 0;\t/* 1 == Forward loop */\n\t\tif (mih.attr & 1) {\n\t\t\txxs->flg |= XMP_SAMPLE_16BIT;\n\t\t\txxs->len >>= 1;\n\t\t\txxs->lps >>= 1;\n\t\t\txxs->lpe >>= 1;\n\t\t}\n\n\t\tsub->vol = mih.volume;\n\t\tsub->fin = (int8)(mih.finetune << 4);\n\t\tsub->pan = 0x80;\n\t\tsub->sid = i;\n\n\t\tlibxmp_instrument_name(mod, i, mih.name, 22);\n\n\t\tif (xxs->len > 0)\n\t\t\tmod->xxi[i].nsm = 1;\n\n\t\tD_(D_INFO \"[%2X] %-22.22s %04x%c%04x %04x %c V%02x F%+03d\\n\", i,\n\t\t   xxi->name, xxs->len, xxs->flg & XMP_SAMPLE_16BIT ? '+' : ' ',\n\t\t   xxs->lps, xxs->lpe, xxs->flg & XMP_SAMPLE_LOOP ? 'L' : ' ',\n\t\t   sub->vol, sub->fin - 0x80);\n\t}\n\n\thio_read(mod->xxo, 1, 128, f);\n\n\tif (libxmp_init_pattern(mod) < 0)\n\t\treturn -1;\n\n\tD_(D_INFO \"Stored tracks: %d\", mod->trk - 1);\n\n\tfxx[0] = fxx[1] = 0;\n\tfor (i = 0; i < mod->trk; i++) {\n\n\t\tif (libxmp_alloc_track(mod, i, mfh.rows) < 0)\n\t\t\treturn -1;\n\n\t\tif (i == 0)\n\t\t\tcontinue;\n\n\t\tif (hio_read(mt, 3, 64, f) != 64)\n\t\t\treturn -1;\n\n\t\tfor (j = 0; j < 64; j++) {\n\t\t\tstruct xmp_event *e = &mod->xxt[i]->event[j];\n\t\t\tuint8 *d = mt + j * 3;\n\n\t\t\te->note = d[0] >> 2;\n\t\t\tif (e->note) {\n\t\t\t\te->note += 37;\n\t\t\t}\n\t\t\te->ins = ((d[0] & 0x3) << 4) + MSN(d[1]);\n\t\t\te->fxt = LSN(d[1]);\n\t\t\te->fxp = d[2];\n\t\t\tif (e->fxt > FX_SPEED) {\n\t\t\t\te->fxt = e->fxp = 0;\n\t\t\t}\n\n\t\t\t/* See tempo mode detection below. */\n\t\t\tif (e->fxt == FX_SPEED) {\n\t\t\t\tfxx[e->fxp >= 0x20] = 1;\n\t\t\t}\n\n\t\t\t/* Set pan effect translation */\n\t\t\tif (e->fxt == FX_EXTENDED && MSN(e->fxp) == 0x8) {\n\t\t\t\te->fxt = FX_SETPAN;\n\t\t\t\te->fxp <<= 4;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Read patterns */\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat - 1);\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tif (libxmp_alloc_pattern(mod, i) < 0)\n\t\t\treturn -1;\n\n\t\tmod->xxp[i]->rows = 64;\n\t\tfor (j = 0; j < 32; j++) {\n\t\t\tint track = hio_read16l(f);\n\n\t\t\tif (track >= mod->trk) {\n\t\t\t\ttrack = 0;\n\t\t\t}\n\n\t\t\tif (j < mod->chn) {\n\t\t\t\tmod->xxp[i]->index[j] = track;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Tempo mode detection.\n\t *\n\t * The MTM tempo effect has an unusual property: when speed is set, the\n\t * tempo resets to 125, and when tempo is set, the speed resets to 6.\n\t * Modules that use both speed and tempo effects need to emulate this.\n\t * See: Absolve the Ambience by Sybaris, Soma by Ranger Rick.\n\t *\n\t * Dual Module Player and other DOS players did not know about this and\n\t * did not implement support for it, and instead used Protracker Fxx.\n\t * Many MTM authors created modules that rely on this which are various\n\t * degrees of broken in the tracker they were made with! Several MTMs\n\t * by Phoenix and Silent Mode expect this. The majority of them can be\n\t * detected by checking for high Fxx and low Fxx on the same row.\n\t */\n\tif (fxx[0] && fxx[1]) {\n\t\t/* Both used, check patterns. */\n\t\tD_(D_INFO \"checking patterns for MT or DMP Fxx effect usage\");\n\t\tfor (i = 0; i < mod->pat; i++) {\n\t\t\tfor (j = 0; j < mfh.rows; j++) {\n\t\t\t\tfxx[0] = fxx[1] = 0;\n\t\t\t\tfor (k = 0; k < mod->chn; k++) {\n\t\t\t\t\tstruct xmp_event *e = &EVENT(i, k, j);\n\t\t\t\t\tif (e->fxt == FX_SPEED) {\n\t\t\t\t\t\tfxx[e->fxp >= 0x20] = 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (fxx[0] && fxx[1]) {\n\t\t\t\t\t/* Same row, no change required */\n\t\t\t\t\tD_(D_INFO \"probably DMP (%d:%d)\", i, j);\n\t\t\t\t\tgoto probably_dmp;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tD_(D_INFO \"probably MT; injecting speed/BPM reset effects\");\n\t\tfor (i = 0; i < mod->pat; i++) {\n\t\t\tfor (j = 0; j < mfh.rows; j++) {\n\t\t\t\tfor (k = 0; k < mod->chn; k++) {\n\t\t\t\t\tstruct xmp_event *e = &EVENT(i, k, j);\n\t\t\t\t\tif (e->fxt == FX_SPEED) {\n\t\t\t\t\t\te->f2t = FX_SPEED;\n\t\t\t\t\t\te->f2p = (e->fxp < 0x20) ? 125 : 6;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n    probably_dmp:\n\n\t/* Comments */\n\tif (mfh.extralen) {\n\t\tm->comment = (char *)malloc(mfh.extralen + 1);\n\t\tif (m->comment) {\n\t\t\t/* Comments are stored in 40 byte ASCIIZ lines. */\n\t\t\tint len = hio_read(m->comment, 1, mfh.extralen, f);\n\t\t\tint line, last_line = 0;\n\n\t\t\tfor (i = 0; i + 40 <= len; i += 40) {\n\t\t\t\tif (m->comment[i] != '\\0')\n\t\t\t\t\tlast_line = i + 40;\n\t\t\t}\n\t\t\tfor (j = 0, line = 0; line < last_line; line += 40) {\n\t\t\t\tchar *pos = m->comment + line;\n\t\t\t\tfor (i = 0; i < 39; i++) {\n\t\t\t\t\tif (pos[i] == '\\0')\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tm->comment[j++] = pos[i];\n\t\t\t\t}\n\t\t\t\tm->comment[j++] = '\\n';\n\t\t\t}\n\t\t\tm->comment[j] = '\\0';\n\t\t} else {\n\t\t\thio_seek(f, mfh.extralen, SEEK_CUR);\n\t\t}\n\t}\n\n\t/* Read samples */\n\tD_(D_INFO \"Stored samples: %d\", mod->smp);\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tif (libxmp_load_sample(m, f, SAMPLE_FLAG_UNS, &mod->xxs[i], NULL) < 0)\n\t\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < mod->chn; i++)\n\t\tmod->xxc[i].pan = mfh.pan[i] << 4;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/okt_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/* Based on the format description written by Harald Zappe.\n * Additional information about Oktalyzer modules from Bernardo\n * Innocenti's XModule 3.4 sources.\n */\n\n#include \"loader.h\"\n#include \"iff.h\"\n\nstatic int okt_test(HIO_HANDLE *, char *, const int);\nstatic int okt_load(struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_okt = {\n\t\"Oktalyzer\",\n\tokt_test,\n\tokt_load\n};\n\nstatic int okt_test(HIO_HANDLE *f, char *t, const int start)\n{\n\tchar magic[8];\n\n\tif (hio_read(magic, 1, 8, f) < 8)\n\t\treturn -1;\n\n\tif (strncmp(magic, \"OKTASONG\", 8))\n\t\treturn -1;\n\n\tlibxmp_read_title(f, t, 0);\n\n\treturn 0;\n}\n\n#define OKT_MODE8 0x00\t\t/* 7 bit samples */\n#define OKT_MODE4 0x01\t\t/* 8 bit samples */\n#define OKT_MODEB 0x02\t\t/* Both */\n\n#define NONE 0xff\n\nstruct local_data {\n\tint mode[36];\n\tint idx[36];\n\tint pattern;\n\tint sample;\n\tint samples;\n\tint has_cmod;\n\tint has_samp;\n\tint has_slen;\n};\n\nstatic const int fx[32] = {\n\tNONE,\n\tFX_PORTA_UP,\t\t/*  1 */\n\tFX_PORTA_DN,\t\t/*  2 */\n\tNONE,\n\tNONE,\n\tNONE,\n\tNONE,\n\tNONE,\n\tNONE,\n\tNONE,\n\tFX_OKT_ARP3,\t\t/* 10 */\n\tFX_OKT_ARP4,\t\t/* 11 */\n\tFX_OKT_ARP5,\t\t/* 12 */\n\tFX_NSLIDE2_DN,\t\t/* 13 */\n\tNONE,\n\tNONE,\t\t\t/* 15 - filter */\n\tNONE,\n\tFX_NSLIDE2_UP,\t\t/* 17 */\n\tNONE,\n\tNONE,\n\tNONE,\n\tFX_NSLIDE_DN,\t\t/* 21 */\n\tNONE,\n\tNONE,\n\tNONE,\n\tFX_JUMP,\t\t/* 25 */\n\tNONE,\n\tNONE,\t\t\t/* 27 - release */\n\tFX_SPEED,\t\t/* 28 */\n\tNONE,\n\tFX_NSLIDE_UP,\t\t/* 30 */\n\tFX_VOLSET\t\t/* 31 */\n};\n\nstatic int okt_translate_effect(struct xmp_event *event, int fxt, int fxp)\n{\n\tif (fxt >= ARRAY_SIZE(fx)) {\n\t\treturn -1;\n\t}\n\tevent->fxt = fx[fxt];\n\tevent->fxp = fxp;\n\n\tif ((event->fxt == FX_VOLSET) && (event->fxp > 0x40)) {\n\t\tif (event->fxp <= 0x50) {\n\t\t\tevent->fxt = FX_VOLSLIDE;\n\t\t\tevent->fxp -= 0x40;\n\t\t} else if (event->fxp <= 0x60) {\n\t\t\tevent->fxt = FX_VOLSLIDE;\n\t\t\tevent->fxp = (event->fxp - 0x50) << 4;\n\t\t} else if (event->fxp <= 0x70) {\n\t\t\tevent->fxt = FX_F_VSLIDE_DN;\n\t\t\tevent->fxp = event->fxp - 0x60;\n\t\t} else if (event->fxp <= 0x80) {\n\t\t\tevent->fxt = FX_F_VSLIDE_UP;\n\t\t\tevent->fxp = event->fxp - 0x70;\n\t\t}\n\t}\n\tif (event->fxt == FX_ARPEGGIO)\t/* Arpeggio fixup */\n\t\tevent->fxp = (((24 - MSN(event->fxp)) % 12) << 4) | LSN(event->fxp);\n\tif (event->fxt == NONE)\n\t\tevent->fxt = event->fxp = 0;\n\n\treturn 0;\n}\n\nstatic int get_cmod(struct module_data *m, int size, HIO_HANDLE *f, void *parm)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct local_data *data = (struct local_data *)parm;\n\tint i;\n\n\t/* Sanity check */\n\tif (data->has_cmod || size < 8) {\n\t\treturn -1;\n\t}\n\tdata->has_cmod = 1;\n\n\tmod->chn = 0;\n\tfor (i = 0; i < 4; i++) {\n\t\tint pan = (((i + 1) / 2) % 2) * 0xff;\n\t\tint p = 0x80 + (pan - 0x80) * m->defpan / 100;\n\n\t\tif (hio_read16b(f) == 0) {\n\t\t\tmod->xxc[mod->chn++].pan = p;\n\t\t} else {\n\t\t\tmod->xxc[mod->chn].flg |= XMP_CHANNEL_SPLIT | (i << 4);\n\t\t\tmod->xxc[mod->chn++].pan = p;\n\t\t\tmod->xxc[mod->chn].flg |= XMP_CHANNEL_SPLIT | (i << 4);\n\t\t\tmod->xxc[mod->chn++].pan = p;\n\t\t}\n\n\t}\n\n\treturn 0;\n}\n\nstatic int get_samp(struct module_data *m, int size, HIO_HANDLE *f, void *parm)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct local_data *data = (struct local_data *)parm;\n\tint i, j;\n\tint looplen;\n\n\t/* Sanity check */\n\tif (data->has_samp || size != 36 * 32) {\n\t\treturn -1;\n\t}\n\tdata->has_samp = 1;\n\n\t/* Should be always 36 */\n\tmod->ins = size / 32;\t/* sizeof(struct okt_instrument_header); */\n\tmod->smp = mod->ins;\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\treturn -1;\n\n\tfor (j = i = 0; i < mod->ins; i++) {\n\t\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\t\tstruct xmp_sample *xxs = &mod->xxs[j];\n\t\tstruct xmp_subinstrument *sub;\n\n\t\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t\t\treturn -1;\n\n\t\tsub = &xxi->sub[0];\n\n\t\thio_read(xxi->name, 1, 20, f);\n\n\t\t/* Sample size is always rounded down */\n\t\txxs->len = hio_read32b(f) & ~1;\n\t\txxs->lps = hio_read16b(f) << 1;\n\t\tlooplen = hio_read16b(f) << 1;\n\t\txxs->lpe = xxs->lps + looplen;\n\t\txxs->flg = looplen > 2 ? XMP_SAMPLE_LOOP : 0;\n\n\t\tsub->vol = hio_read16b(f);\n\t\tdata->mode[i] = hio_read16b(f);\n\n\t\tsub->pan = 0x80;\n\t\tsub->sid = j;\n\n\t\tdata->idx[j] = i;\n\n\t\tif (xxs->len > 0) {\n\t\t\txxi->nsm = 1;\n\t\t\tj++;\n\t\t}\n\t}\n\tdata->samples = j;\n\n\treturn 0;\n}\n\nstatic int get_spee(struct module_data *m, int size, HIO_HANDLE *f, void *parm)\n{\n\tstruct xmp_module *mod = &m->mod;\n\n\tmod->spd = hio_read16b(f);\n\tmod->bpm = 125;\n\n\treturn 0;\n}\n\nstatic int get_slen(struct module_data *m, int size, HIO_HANDLE *f, void *parm)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct local_data *data = (struct local_data *)parm;\n\n\t/* Sanity check */\n\tif (data->has_slen || !data->has_cmod || size < 2) {\n\t\treturn -1;\n\t}\n\tdata->has_slen = 1;\n\n\tmod->pat = hio_read16b(f);\n\tmod->trk = mod->pat * mod->chn;\n\n\treturn 0;\n}\n\nstatic int get_plen(struct module_data *m, int size, HIO_HANDLE *f, void *parm)\n{\n\tstruct xmp_module *mod = &m->mod;\n\n\tmod->len = hio_read16b(f);\n\n\t/* Sanity check */\n\tif (mod->len > 256)\n\t\treturn -1;\n\n\tD_(D_INFO \"Module length: %d\", mod->len);\n\n\treturn 0;\n}\n\nstatic int get_patt(struct module_data *m, int size, HIO_HANDLE *f, void *parm)\n{\n\tstruct xmp_module *mod = &m->mod;\n\n\tif (hio_read(mod->xxo, 1, mod->len, f) != mod->len)\n\t\treturn -1;\n\n\treturn 0;\n}\n\nstatic int get_pbod(struct module_data *m, int size, HIO_HANDLE *f, void *parm)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct local_data *data = (struct local_data *)parm;\n\tstruct xmp_event *e;\n\tuint16 rows;\n\tint j, k;\n\n\t/* Sanity check */\n\tif (!data->has_slen || !data->has_cmod) {\n\t\treturn -1;\n\t}\n\n\tif (data->pattern >= mod->pat)\n\t\treturn 0;\n\n\tif (!data->pattern) {\n\t\tif (libxmp_init_pattern(mod) < 0)\n\t\t\treturn -1;\n\t\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\t}\n\n\trows = hio_read16b(f);\n\n\tif (libxmp_alloc_pattern_tracks(mod, data->pattern, rows) < 0)\n\t\treturn -1;\n\n\tfor (j = 0; j < rows; j++) {\n\t\tfor (k = 0; k < mod->chn; k++) {\n\t\t\tuint8 note, ins;\n\t\t\tuint8 c[4];\n\n\t\t\te = &EVENT(data->pattern, k, j);\n\t\t\tmemset(e, 0, sizeof(struct xmp_event));\n\n\t\t\tif (hio_read(c, 1, 4, f) < 4) {\n\t\t\t\tD_(D_CRIT \"read error in PBOD %d\", data->pattern);\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tnote = c[0];\n\t\t\tins = c[1];\n\n\t\t\tif (note) {\n\t\t\t\te->note = 48 + note;\n\t\t\t\te->ins = 1 + ins;\n\t\t\t}\n\n\t\t\tif (okt_translate_effect(e, c[2], c[3]) < 0) {\n\t\t\t\tD_(D_CRIT \"bad effect in PBOD %d: %02x %02x\",\n\t\t\t\t   data->pattern, c[2], c[3]);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\tdata->pattern++;\n\n\treturn 0;\n}\n\nstatic int get_sbod(struct module_data *m, int size, HIO_HANDLE *f, void *parm)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct local_data *data = (struct local_data *)parm;\n\tint flags = 0;\n\tint i, sid;\n\n\tif (data->sample >= data->samples)\n\t\treturn 0;\n\n\tD_(D_INFO \"Stored samples: %d\", mod->smp);\n\n\ti = data->idx[data->sample];\n\tif (data->mode[i] == OKT_MODE8 || data->mode[i] == OKT_MODEB)\n\t\tflags = SAMPLE_FLAG_7BIT;\n\n\tsid = mod->xxi[i].sub[0].sid;\n\tif (libxmp_load_sample(m, f, flags, &mod->xxs[sid], NULL) < 0)\n\t\treturn -1;\n\n\tdata->sample++;\n\n\treturn 0;\n}\n\nstatic int okt_load(struct module_data *m, HIO_HANDLE * f, const int start)\n{\n\tiff_handle handle;\n\tstruct local_data data;\n\tint ret;\n\n\tLOAD_INIT();\n\n\thio_seek(f, 8, SEEK_CUR);\t/* OKTASONG */\n\n\thandle = libxmp_iff_new();\n\tif (handle == NULL)\n\t\treturn -1;\n\n\tmemset(&data, 0, sizeof(struct local_data));\n\n\t/* IFF chunk IDs */\n\tret = libxmp_iff_register(handle, \"CMOD\", get_cmod);\n\tret |= libxmp_iff_register(handle, \"SAMP\", get_samp);\n\tret |= libxmp_iff_register(handle, \"SPEE\", get_spee);\n\tret |= libxmp_iff_register(handle, \"SLEN\", get_slen);\n\tret |= libxmp_iff_register(handle, \"PLEN\", get_plen);\n\tret |= libxmp_iff_register(handle, \"PATT\", get_patt);\n\tret |= libxmp_iff_register(handle, \"PBOD\", get_pbod);\n\tret |= libxmp_iff_register(handle, \"SBOD\", get_sbod);\n\n\tif (ret != 0)\n\t\treturn -1;\n\n\tlibxmp_set_type(m, \"Oktalyzer\");\n\n\tMODULE_INFO();\n\n\t/* Load IFF chunks */\n\tif (libxmp_iff_load(handle, m, f, &data) < 0) {\n\t\tlibxmp_iff_release(handle);\n\t\treturn -1;\n\t}\n\n\tlibxmp_iff_release(handle);\n\n\tm->period_type = PERIOD_MODRNG;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/s3m.h",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#ifndef LIBXMP_LOADERS_S3M_H\n#define LIBXMP_LOADERS_S3M_H\n\n/* S3M packed pattern macros */\n#define S3M_EOR\t\t0\t/* End of row */\n#define S3M_CH_MASK\t0x1f\t/* Channel */\n#define S3M_NI_FOLLOW\t0x20\t/* Note and instrument follow */\n#define S3M_VOL_FOLLOWS\t0x40\t/* Volume follows */\n#define S3M_FX_FOLLOWS\t0x80\t/* Effect and parameter follow */\n\n/* S3M mix volume macros */\n#define S3M_MV_VOLUME\t0x7f\t/* Module mix volume, typically 16 to 127 */\n#define S3M_MV_STEREO\t0x80\t/* Module is stereo if set, otherwise mono */\n\n/* S3M channel info macros */\n#define S3M_CH_ON\t0x80\t/* Psi says it's bit 8, I'll assume bit 7 */\n#define S3M_CH_OFF\t0xff\n#define S3M_CH_NUMBER\t0x1f\n#define S3M_CH_RIGHT\t0x08\n#define S3M_CH_ADLIB\t0x10\n\n/* S3M channel pan macros */\n#define S3M_PAN_SET\t0x20\n#define S3M_PAN_MASK\t0x0f\n\n/* S3M flags */\n#define S3M_ST2_VIB\t0x01\t/* Not recognized */\n#define S3M_ST2_TEMPO\t0x02\t/* Not recognized */\n#define S3M_AMIGA_SLIDE\t0x04\t/* Not recognized */\n#define S3M_VOL_OPT\t0x08\t/* Not recognized */\n#define S3M_AMIGA_RANGE\t0x10\n#define S3M_SB_FILTER\t0x20\t/* Not recognized */\n#define S3M_ST300_VOLS\t0x40\n#define S3M_CUSTOM_DATA\t0x80\t/* Not recognized */\n\n/* S3M Adlib instrument types */\n#define S3M_INST_SAMPLE\t0x01\n#define S3M_INST_AMEL\t0x02\n#define S3M_INST_ABD\t0x03\n#define S3M_INST_ASNARE\t0x04\n#define S3M_INST_ATOM\t0x05\n#define S3M_INST_ACYM\t0x06\n#define S3M_INST_AHIHAT\t0x07\n\n/* S3M sample flags */\n#define S3M_SAMP_LOOP\t0x01\n#define S3M_SAMP_STEREO\t0x02\n#define S3M_SAMP_16BIT\t0x04\n\nstruct s3m_file_header {\n\tuint8 name[28];\t\t/* Song name */\n\tuint8 doseof;\t\t/* 0x1a */\n\tuint8 type;\t\t/* File type */\n\tuint8 rsvd1[2];\t\t/* Reserved */\n\tuint16 ordnum;\t\t/* Number of orders (must be even) */\n\tuint16 insnum;\t\t/* Number of instruments */\n\tuint16 patnum;\t\t/* Number of patterns */\n\tuint16 flags;\t\t/* Flags */\n\tuint16 version;\t\t/* Tracker ID and version */\n\tuint16 ffi;\t\t/* File format information */\n\tuint32 magic;\t\t/* 'SCRM' */\n\tuint8 gv;\t\t/* Global volume */\n\tuint8 is;\t\t/* Initial speed */\n\tuint8 it;\t\t/* Initial tempo */\n\tuint8 mv;\t\t/* Master volume */\n\tuint8 uc;\t\t/* Ultra click removal */\n\tuint8 dp;\t\t/* Default pan positions if 0xfc */\n\tuint8 rsvd2[8];\t\t/* Reserved */\n\tuint16 special;\t\t/* Ptr to special custom data */\n\tuint8 chset[32];\t/* Channel settings */\n};\n\nstruct s3m_instrument_header {\n\tuint8 dosname[12];\t/* DOS file name */\n\tuint8 memseg_hi;\t/* High byte of sample pointer */\n\tuint16 memseg;\t\t/* Pointer to sample data */\n\tuint32 length;\t\t/* Length */\n\tuint32 loopbeg;\t\t/* Loop begin */\n\tuint32 loopend;\t\t/* Loop end */\n\tuint8 vol;\t\t/* Volume */\n\tuint8 rsvd1;\t\t/* Reserved */\n\tuint8 pack;\t\t/* Packing type (not used) */\n\tuint8 flags;\t\t/* Loop/stereo/16bit samples flags */\n\tuint16 c2spd;\t\t/* C 4 speed */\n\tuint16 rsvd2;\t\t/* Reserved */\n\tuint8 rsvd3[4];\t\t/* Reserved */\n\tuint16 int_gp;\t\t/* Internal - GUS pointer */\n\tuint16 int_512;\t\t/* Internal - SB pointer */\n\tuint32 int_last;\t/* Internal - SB index */\n\tuint8 name[28];\t\t/* Instrument name */\n\tuint32 magic;\t\t/* 'SCRS' */\n};\n\n#ifndef LIBXMP_CORE_PLAYER\nstruct s3m_adlib_header {\n\tuint8 dosname[12];\t/* DOS file name */\n\tuint8 rsvd1[3];\t\t/* 0x00 0x00 0x00 */\n\tuint8 reg[12];\t\t/* Adlib registers */\n\tuint8 vol;\n\tuint8 dsk;\n\tuint8 rsvd2[2];\n\tuint16 c2spd;\t\t/* C 4 speed */\n\tuint16 rsvd3;\t\t/* Reserved */\n\tuint8 rsvd4[12];\t/* Reserved */\n\tuint8 name[28];\t\t/* Instrument name */\n\tuint32 magic;\t\t/* 'SCRI' */\n};\n#endif\n#endif  /* LIBXMP_LOADERS_S3M_H */\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/s3m_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/*\n * Tue, 30 Jun 1998 20:23:11 +0200\n * Reported by John v/d Kamp <blade_@dds.nl>:\n * I have this song from Purple Motion called wcharts.s3m, the global\n * volume was set to 0, creating a divide by 0 error in xmp. There should\n * be an extra test if it's 0 or not.\n *\n * Claudio's fix: global volume ignored\n */\n\n/*\n * Sat, 29 Aug 1998 18:50:43 -0500 (CDT)\n * Reported by Joel Jordan <scriber@usa.net>:\n * S3M files support tempos outside the ranges defined by xmp (that is,\n * the MOD/XM tempo ranges).  S3M's can have tempos from 0 to 255 and speeds\n * from 0 to 255 as well, since these are handled with separate effects\n * unlike the MOD format.  This becomes an issue in such songs as Skaven's\n * \"Catch that Goblin\", which uses speeds above 0x1f.\n *\n * Claudio's fix: FX_S3M_SPEED added. S3M supports speeds from 0 to 255 and\n * tempos from 32 to 255 (S3M speed == xmp tempo, S3M tempo == xmp BPM).\n */\n\n/* Wed, 21 Oct 1998 15:03:44 -0500  Geoff Reedy <vader21@imsa.edu>\n * It appears that xmp has issues loading/playing a specific instrument\n * used in LUCCA.S3M.\n * (Fixed by Hipolito in xmp-2.0.0dev34)\n */\n\n/*\n * From http://code.pui.ch/2007/02/18/turn-demoscene-modules-into-mp3s/\n * The only flaw I noticed [in xmp] is a problem with portamento in Purple\n * Motion's second reality soundtrack (1:06-1:17)\n *\n * Claudio's note: that's a dissonant beating between channels 6 and 7\n * starting at pos12, caused by pitchbending effect F25.\n */\n\n#include \"loader.h\"\n#include \"s3m.h\"\n#include \"../period.h\"\n\n#define MAGIC_SCRM\tMAGIC4('S','C','R','M')\n#define MAGIC_SCRI\tMAGIC4('S','C','R','I')\n#define MAGIC_SCRS\tMAGIC4('S','C','R','S')\n\nstatic int s3m_test(HIO_HANDLE *, char *, const int);\nstatic int s3m_load(struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_s3m = {\n\t\"Scream Tracker 3\",\n\ts3m_test,\n\ts3m_load\n};\n\nstatic int s3m_test(HIO_HANDLE *f, char *t, const int start)\n{\n\thio_seek(f, start + 44, SEEK_SET);\n\tif (hio_read32b(f) != MAGIC_SCRM)\n\t\treturn -1;\n\n\thio_seek(f, start + 29, SEEK_SET);\n\tif (hio_read8(f) != 0x10)\n\t\treturn -1;\n\n\thio_seek(f, start + 0, SEEK_SET);\n\tlibxmp_read_title(f, t, 28);\n\n\treturn 0;\n}\n\n#define NONE\t\t0xff\n#define FX_S3M_EXTENDED\t0xfe\n\n/* Effect conversion table */\nstatic const uint8 fx[27] = {\n\tNONE,\n\tFX_S3M_SPEED,\t\t/* Axx  Set speed to xx (the default is 06) */\n\tFX_JUMP,\t\t/* Bxx  Jump to order xx (hexadecimal) */\n\tFX_BREAK,\t\t/* Cxx  Break pattern to row xx (decimal) */\n\tFX_VOLSLIDE,\t\t/* Dxy  Volume slide down by y/up by x */\n\tFX_PORTA_DN,\t\t/* Exx  Slide down by xx */\n\tFX_PORTA_UP,\t\t/* Fxx  Slide up by xx */\n\tFX_TONEPORTA,\t\t/* Gxx  Tone portamento with speed xx */\n\tFX_VIBRATO,\t\t/* Hxy  Vibrato with speed x and depth y */\n\tFX_TREMOR,\t\t/* Ixy  Tremor with ontime x and offtime y */\n\tFX_S3M_ARPEGGIO,\t/* Jxy  Arpeggio with halfnote additions */\n\tFX_VIBRA_VSLIDE,\t/* Kxy  Dual command: H00 and Dxy */\n\tFX_TONE_VSLIDE,\t\t/* Lxy  Dual command: G00 and Dxy */\n\tNONE,\n\tNONE,\n\tFX_OFFSET,\t\t/* Oxy  Set sample offset */\n\tNONE,\n\tFX_MULTI_RETRIG,\t/* Qxy  Retrig (+volumeslide) note */\n\tFX_TREMOLO,\t\t/* Rxy  Tremolo with speed x and depth y */\n\tFX_S3M_EXTENDED,\t/* Sxx  (misc effects) */\n\tFX_S3M_BPM,\t\t/* Txx  Tempo = xx (hex) */\n\tFX_FINE_VIBRATO,\t/* Uxx  Fine vibrato */\n\tFX_GLOBALVOL,\t\t/* Vxx  Set global volume */\n\tNONE,\n\tFX_SETPAN,\t\t/* Xxx  Set pan */\n\tNONE,\n\tNONE\n};\n\n/* Effect translation */\nstatic void xlat_fx(int c, struct xmp_event *e)\n{\n\tuint8 h = MSN(e->fxp), l = LSN(e->fxp);\n\n\tif (e->fxt >= ARRAY_SIZE(fx)) {\n\t\tD_(D_WARN \"invalid effect %02x\", e->fxt);\n\t\te->fxt = e->fxp = 0;\n\t\treturn;\n\t}\n\n\tswitch (e->fxt = fx[e->fxt]) {\n\tcase FX_S3M_BPM:\n\t\tif (e->fxp < 0x20) {\n\t\t\te->fxp = e->fxt = 0;\n\t\t}\n\t\tbreak;\n\tcase FX_S3M_EXTENDED:\t/* Extended effects */\n\t\te->fxt = FX_EXTENDED;\n\t\tswitch (h) {\n\t\tcase 0x1:\t/* Glissando */\n\t\t\te->fxp = LSN(e->fxp) | (EX_GLISS << 4);\n\t\t\tbreak;\n\t\tcase 0x2:\t/* Finetune */\n\t\t\te->fxp =\n\t\t\t    ((LSN(e->fxp) - 8) & 0x0f) | (EX_FINETUNE << 4);\n\t\t\tbreak;\n\t\tcase 0x3:\t/* Vibrato wave */\n\t\t\te->fxp = LSN(e->fxp) | (EX_VIBRATO_WF << 4);\n\t\t\tbreak;\n\t\tcase 0x4:\t/* Tremolo wave */\n\t\t\te->fxp = LSN(e->fxp) | (EX_TREMOLO_WF << 4);\n\t\t\tbreak;\n\t\tcase 0x5:\n\t\tcase 0x6:\n\t\tcase 0x7:\n\t\tcase 0x9:\n\t\tcase 0xa:\t/* Ignore */\n\t\t\te->fxt = e->fxp = 0;\n\t\t\tbreak;\n\t\tcase 0x8:\t/* Set pan */\n\t\t\te->fxt = FX_SETPAN;\n\t\t\te->fxp = l << 4;\n\t\t\tbreak;\n\t\tcase 0xb:\t/* Pattern loop */\n\t\t\te->fxp = LSN(e->fxp) | (EX_PATTERN_LOOP << 4);\n\t\t\tbreak;\n\t\tcase 0xc:\n\t\t\tif (!l)\n\t\t\t\te->fxt = e->fxp = 0;\n\t\t}\n\t\tbreak;\n\tcase FX_SETPAN:\n\t\t/* Saga Musix says: \"The X effect in S3M files is not\n\t\t * exclusive to IT and clones. You will find tons of S3Ms made\n\t\t * with ST3 itself using this effect (and relying on an\n\t\t * external player being used). X in S3M also behaves\n\t\t * differently than in IT, which your code does not seem to\n\t\t * handle: X00 - X80 is left... right, XA4 is surround (like\n\t\t * S91 in IT), other values are not supposed to do anything.\n\t\t */\n\t\tif (e->fxp == 0xa4) {\n\t\t\t// surround\n\t\t\te->fxt = FX_SURROUND;\n\t\t\te->fxp = 1;\n\t\t} else {\n\t\t\tint pan = ((int)e->fxp) << 1;\n\t\t\tif (pan > 0xff) {\n\t\t\t\tpan = 0xff;\n\t\t\t}\n\t\t\te->fxp = pan;\n\t\t}\n\t\tbreak;\n\tcase NONE:\t\t/* No effect */\n\t\te->fxt = e->fxp = 0;\n\t\tbreak;\n\t}\n}\n\nstatic int s3m_load(struct module_data *m, HIO_HANDLE * f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint c, r, i;\n\tstruct xmp_event *event = 0, dummy;\n\tstruct s3m_file_header sfh;\n\tstruct s3m_instrument_header sih;\n#ifndef LIBXMP_CORE_PLAYER\n\tstruct s3m_adlib_header sah;\n\tchar tracker_name[40];\n#endif\n\tint pat_len;\n\tuint8 n, b;\n\tuint16 *pp_ins;\t\t\t/* Parapointers to instruments */\n\tuint16 *pp_pat;\t\t\t/* Parapointers to patterns */\n\tint stereo;\n\tint ret;\n\tuint8 buf[96]\n\n\tLOAD_INIT();\n\n\tif (hio_read(buf, 1, 96, f) != 96) {\n\t\tgoto err;\n\t}\n\n\tmemcpy(sfh.name, buf, 28);\t\t/* Song name */\n\tsfh.type = buf[30];\t\t\t/* File type */\n\tsfh.ordnum = readmem16l(buf + 32);\t/* Number of orders (must be even) */\n\tsfh.insnum = readmem16l(buf + 34);\t/* Number of instruments */\n\tsfh.patnum = readmem16l(buf + 36);\t/* Number of patterns */\n\tsfh.flags = readmem16l(buf + 38);\t/* Flags */\n\tsfh.version = readmem16l(buf + 40);\t/* Tracker ID and version */\n\tsfh.ffi = readmem16l(buf + 42);\t\t/* File format information */\n\n\t/* Sanity check */\n\tif (sfh.ffi != 1 && sfh.ffi != 2) {\n\t\tgoto err;\n\t}\n\tif (sfh.ordnum > 255 || sfh.insnum > 255 || sfh.patnum > 255) {\n\t\tgoto err;\n\t}\n\n\tsfh.magic = readmem32b(buf + 44);\t/* 'SCRM' */\n\tsfh.gv = buf[48];\t\t\t/* Global volume */\n\tsfh.is = buf[49];\t\t\t/* Initial speed */\n\tsfh.it = buf[50];\t\t\t/* Initial tempo */\n\tsfh.mv = buf[51];\t\t\t/* Master volume */\n\tsfh.uc = buf[52];\t\t\t/* Ultra click removal */\n\tsfh.dp = buf[53];\t\t\t/* Default pan positions if 0xfc */\n\tmemcpy(sfh.rsvd2, buf + 54, 8);\t\t/* Reserved */\n\tsfh.special = readmem16l(buf + 62);\t/* Ptr to special custom data */\n\tmemcpy(sfh.chset, buf + 64, 32);\t/* Channel settings */\n\n\tif (sfh.magic != MAGIC_SCRM) {\n\t\tgoto err;\n\t}\n\n\tlibxmp_copy_adjust(mod->name, sfh.name, 28);\n\n\tpp_ins = (uint16 *) calloc(sfh.insnum, sizeof(uint16));\n\tif (pp_ins == NULL) {\n\t\tgoto err;\n\t}\n\n\tpp_pat = (uint16 *) calloc(sfh.patnum, sizeof(uint16));\n\tif (pp_pat == NULL) {\n\t\tgoto err2;\n\t}\n\n\tif (sfh.flags & S3M_AMIGA_RANGE) {\n\t\tm->period_type = PERIOD_MODRNG;\n\t}\n\tif (sfh.flags & S3M_ST300_VOLS) {\n\t\tm->quirk |= QUIRK_VSALL;\n\t}\n\t/* m->volbase = 4096 / sfh.gv; */\n\tmod->spd = sfh.is;\n\tmod->bpm = sfh.it;\n\tmod->chn = 0;\n\n\t/* Mix volume and stereo flag conversion (reported by Saga Musix).\n\t * 1) Old format uses mix volume 0-7, and the stereo flag is 0x10.\n\t * 2) Newer ST3s unconditionally convert MV 0x02 and 0x12 to 0x20.\n\t */\n\tm->mvolbase = 48;\n\n\tif (sfh.ffi == 1) {\n\t\tm->mvol = ((sfh.mv & 0xf) + 1) * 0x10;\n\t\tstereo = sfh.mv & 0x10;\n\t\tCLAMP(m->mvol, 0x10, 0x7f);\n\n\t} else if (sfh.mv == 0x02 || sfh.mv == 0x12) {\n\t\tm->mvol = 0x20;\n\t\tstereo = sfh.mv & 0x10;\n\n\t} else {\n\t\tm->mvol = sfh.mv & S3M_MV_VOLUME;\n\t\tstereo = sfh.mv & S3M_MV_STEREO;\n\n\t\tif (m->mvol == 0) {\n\t\t\tm->mvol = 48;\t\t/* Default is 48 */\n\t\t} else if (m->mvol < 16) {\n\t\t\tm->mvol = 16;\t\t/* Minimum is 16 */\n\t\t}\n\t}\n\n\t/* \"Note that in stereo, the mastermul is internally multiplied by\n\t * 11/8 inside the player since there is generally more room in the\n\t * output stream.\" Do the inverse to affect fewer modules. */\n\tif (!stereo) {\n\t\tm->mvol = m->mvol * 8 / 11;\n\t}\n\n\tfor (i = 0; i < 32; i++) {\n\t\tint x;\n\t\tif (sfh.chset[i] == S3M_CH_OFF)\n\t\t\tcontinue;\n\n\t\tmod->chn = i + 1;\n\n\t\tx = sfh.chset[i] & S3M_CH_NUMBER;\n\t\tif (stereo && x < S3M_CH_ADLIB) {\n\t\t\tmod->xxc[i].pan = x < S3M_CH_RIGHT ? 0x30 : 0xc0;\n\t\t} else {\n\t\t\tmod->xxc[i].pan = 0x80;\n\t\t}\n\t}\n\n\tif (sfh.ordnum <= XMP_MAX_MOD_LENGTH) {\n\t\tmod->len = sfh.ordnum;\n\t\tif (hio_read(mod->xxo, 1, mod->len, f) != mod->len) {\n\t\t\tgoto err3;\n\t\t}\n\t} else {\n\t\tmod->len = XMP_MAX_MOD_LENGTH;\n\t\tif (hio_read(mod->xxo, 1, mod->len, f) != mod->len) {\n\t\t\tgoto err3;\n\t\t}\n\t\tif (hio_seek(f, sfh.ordnum - XMP_MAX_MOD_LENGTH, SEEK_CUR) < 0) {\n\t\t\tgoto err3;\n\t\t}\n\t}\n\n\t/* Don't trust sfh.patnum */\n\tmod->pat = -1;\n\tfor (i = 0; i < mod->len; ++i) {\n\t\tif (mod->xxo[i] < 0xfe && mod->xxo[i] > mod->pat) {\n\t\t\tmod->pat = mod->xxo[i];\n\t\t}\n\t}\n\tmod->pat++;\n\tif (mod->pat > sfh.patnum) {\n\t\tmod->pat = sfh.patnum;\n\t}\n\tif (mod->pat == 0) {\n\t\tgoto err3;\n\t}\n\n\tmod->trk = mod->pat * mod->chn;\n\t/* Load and convert header */\n\tmod->ins = sfh.insnum;\n\tmod->smp = mod->ins;\n\n\tfor (i = 0; i < sfh.insnum; i++) {\n\t\tpp_ins[i] = hio_read16l(f);\n\t}\n\n\tfor (i = 0; i < sfh.patnum; i++) {\n\t\tpp_pat[i] = hio_read16l(f);\n\t}\n\n\t/* Default pan positions */\n\n\tif (sfh.dp == 0xfc) {\n\t\tfor (i = 0; i < 32; i++) {\n\t\t\tuint8 x = hio_read8(f);\n\t\t\tif (x & S3M_PAN_SET) {\n\t\t\t\tmod->xxc[i].pan = (x << 4) & 0xff;\n\t\t\t}\n\t\t}\n\t}\n\n\tm->c4rate = C4_NTSC_RATE;\n\tm->flow_mode = FLOW_MODE_ST3_321;\n\n\tif (sfh.version == 0x1300) {\n\t\tm->quirk |= QUIRK_VSALL;\n\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\tswitch (sfh.version >> 12) {\n\tcase 1:\n\t\tif (sfh.version == 0x1320 && sfh.special == 0 && (sfh.ordnum & 0x0f) == 0 && sfh.uc == 0 && (sfh.flags & ~0x50) == 0 && sfh.dp == 0xfc) {\n\t\t\tif ((sfh.mv & 0x80) != 0) {\n\t\t\t\tstrcpy(tracker_name, \"ModPlug Tracker / OpenMPT 1.17\");\n\t\t\t} else {\n\t\t\t\t/* MPT 1.0 alpha5 doesn't set the stereo flag, but MPT 1.0 alpha6 does. */\n\t\t\t\tstrcpy(tracker_name, \"ModPlug Tracker 1.0 alpha\");\n\t\t\t}\n\t\t\tm->flow_mode = FLOW_MODE_MPT_116;\n\t\t} else if(sfh.version == 0x1320 && sfh.special == 0 && sfh.uc == 0 && sfh.flags == 0 && sfh.dp == 0) {\n\t\t\tif (sfh.gv == 64 && sfh.mv == 48) {\n\t\t\t\tstrcpy(tracker_name, \"PlayerPRO\");\n\t\t\t} else { // Always stereo\n\t\t\t\tstrcpy(tracker_name, \"Velvet Studio\");\n\t\t\t}\n\t\t} else {\n\t\t\tsnprintf(tracker_name, 40, \"Scream Tracker %d.%02x\",\n\t\t\t\t (sfh.version & 0x0f00) >> 8, sfh.version & 0xff);\n\t\t\tm->quirk |= QUIRK_ST3BUGS;\n\t\t\tif (sfh.version < 0x1303) {\n\t\t\t\tm->flow_mode = FLOW_MODE_ST3_301;\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase 2:\n\t\tif (sfh.version == 0x2013) {\n\t\t\tstrcpy(tracker_name, \"PlayerPRO\"); /* PlayerPRO on Intel doesn't byte-swap the tracker ID bytes */\n\t\t} else {\n\t\t\tsnprintf(tracker_name, 40, \"Imago Orpheus %d.%02x\",\n\t\t\t\t (sfh.version & 0x0f00) >> 8, sfh.version & 0xff);\n\t\t\tm->flow_mode = FLOW_MODE_ORPHEUS;\n\t\t}\n\t\tbreak;\n\tcase 3:\n\t\tm->flow_mode = FLOW_MODE_IT_210;\n\t\tif (sfh.version == 0x3216) {\n\t\t\tstrcpy(tracker_name, \"Impulse Tracker 2.14v3\");\n\t\t} else if (sfh.version == 0x3217) {\n\t\t\tstrcpy(tracker_name, \"Impulse Tracker 2.14v5\");\n\t\t} else {\n\t\t\tsnprintf(tracker_name, 40, \"Impulse Tracker %d.%02x\",\n\t\t\t\t (sfh.version & 0x0f00) >> 8,\n\t\t\t\t sfh.version & 0xff);\n\t\t}\n\t\tbreak;\n\tcase 5:\n\t\tif (sfh.version == 0x5447) {\n\t\t\tstrcpy(tracker_name, \"Graoumf Tracker\");\n\t\t} else if (sfh.rsvd2[0] || sfh.rsvd2[1]) {\n\t\t\tsnprintf(tracker_name, 40, \"OpenMPT %d.%02x.%02x.%02x\",\n\t\t\t\t (sfh.version & 0x0f00) >> 8, sfh.version & 0xff, sfh.rsvd2[1], sfh.rsvd2[0]);\n\t\t} else {\n\t\t\tsnprintf(tracker_name, 40, \"OpenMPT %d.%02x\",\n\t\t\t\t (sfh.version & 0x0f00) >> 8, sfh.version & 0xff);\n\t\t}\n\t\tm->quirk |= QUIRK_ST3BUGS;\n\t\tbreak;\n\tcase 4:\n\t\tif (sfh.version != 0x4100) {\n\t\t\tlibxmp_schism_tracker_string(tracker_name, 40,\n\t\t\t\t(sfh.version & 0x0fff), sfh.rsvd2[0] | (sfh.rsvd2[1] << 8));\n\t\t\tbreak;\n\t\t}\n\t\t/* fall through */\n\tcase 6:\n\t\tsnprintf(tracker_name, 40, \"BeRoTracker %d.%02x\",\n\t\t\t (sfh.version & 0x0f00) >> 8, sfh.version & 0xff);\n\t\tbreak;\n\tdefault:\n\t\tsnprintf(tracker_name, 40, \"unknown (%04x)\", sfh.version);\n\t}\n\n\tlibxmp_set_type(m, \"%s S3M\", tracker_name);\n#else\n\tlibxmp_set_type(m, \"Scream Tracker 3\");\n\tm->quirk |= QUIRK_ST3BUGS;\n#endif\n\n\tMODULE_INFO();\n\n\tif (libxmp_init_pattern(mod) < 0)\n\t\tgoto err3;\n\n\t/* Read patterns */\n\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tif (libxmp_alloc_pattern_tracks(mod, i, 64) < 0)\n\t\t\tgoto err3;\n\n\t\tif (pp_pat[i] == 0)\n\t\t\tcontinue;\n\n\t\thio_seek(f, start + pp_pat[i] * 16, SEEK_SET);\n\t\tr = 0;\n\t\tpat_len = hio_read16l(f) - 2;\n\n\t\twhile (pat_len >= 0 && r < mod->xxp[i]->rows) {\n\t\t\tb = hio_read8(f);\n\n\t\t\tif (hio_error(f)) {\n\t\t\t\tgoto err3;\n\t\t\t}\n\n\t\t\tif (b == S3M_EOR) {\n\t\t\t\tr++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tc = b & S3M_CH_MASK;\n\t\t\tevent = c >= mod->chn ? &dummy : &EVENT(i, c, r);\n\n\t\t\tif (b & S3M_NI_FOLLOW) {\n\t\t\t\tswitch (n = hio_read8(f)) {\n\t\t\t\tcase 255:\n\t\t\t\t\tn = 0;\n\t\t\t\t\tbreak;\t/* Empty note */\n\t\t\t\tcase 254:\n\t\t\t\t\tn = XMP_KEY_OFF;\n\t\t\t\t\tbreak;\t/* Key off */\n\t\t\t\tdefault:\n\t\t\t\t\tn = 13 + 12 * MSN(n) + LSN(n);\n\t\t\t\t}\n\t\t\t\tevent->note = n;\n\t\t\t\tevent->ins = hio_read8(f);\n\t\t\t\tpat_len -= 2;\n\t\t\t}\n\n\t\t\tif (b & S3M_VOL_FOLLOWS) {\n\t\t\t\tevent->vol = hio_read8(f) + 1;\n\t\t\t\tpat_len--;\n\t\t\t}\n\n\t\t\tif (b & S3M_FX_FOLLOWS) {\n\t\t\t\tevent->fxt = hio_read8(f);\n\t\t\t\tevent->fxp = hio_read8(f);\n\t\t\t\txlat_fx(c, event);\n\n\t\t\t\tpat_len -= 2;\n\t\t\t}\n\t\t}\n\t}\n\n\tD_(D_INFO \"Stereo enabled: %s\", stereo ? \"yes\" : \"no\");\n\tD_(D_INFO \"Pan settings: %s\", (sfh.dp == 0xfc) ? \"yes\" : \"no\");\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\tgoto err3;\n\n\t/* Read and convert instruments and samples */\n\n\tD_(D_INFO \"Instruments: %d\", mod->ins);\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\t\tstruct xmp_sample *xxs = &mod->xxs[i];\n\t\tstruct xmp_subinstrument *sub;\n\t\tint load_sample_flags;\n\t\tuint32 sample_segment;\n\n\t\txxi->sub = (struct xmp_subinstrument *) calloc(1, sizeof(struct xmp_subinstrument));\n\t\tif (xxi->sub == NULL) {\n\t\t\tgoto err3;\n\t\t}\n\n\t\tsub = &xxi->sub[0];\n\n\t\thio_seek(f, start + pp_ins[i] * 16, SEEK_SET);\n\t\tsub->pan = 0x80;\n\t\tsub->sid = i;\n\n\t\tif (hio_read(buf, 1, 80, f) != 80) {\n\t\t\tgoto err3;\n\t\t}\n\n\t\tif (buf[0] >= 2) {\n#ifndef LIBXMP_CORE_PLAYER\n\t\t\t/* OPL2 FM instrument */\n\n\t\t\tmemcpy(sah.dosname, buf + 1, 12);\t/* DOS file name */\n\t\t\tmemcpy(sah.reg, buf + 16, 12);\t\t/* Adlib registers */\n\t\t\tsah.vol = buf[28];\n\t\t\tsah.dsk = buf[29];\n\t\t\tsah.c2spd = readmem16l(buf + 32);\t/* C4 speed */\n\t\t\tmemcpy(sah.name, buf + 48, 28);\t\t/* Instrument name */\n\t\t\tsah.magic = readmem32b(buf + 76);\t\t/* 'SCRI' */\n\n\t\t\tif (sah.magic != MAGIC_SCRI) {\n\t\t\t\tD_(D_CRIT \"error: FM instrument magic\");\n\t\t\t\tgoto err3;\n\t\t\t}\n\t\t\tsah.magic = 0;\n\n\t\t\tlibxmp_instrument_name(mod, i, sah.name, 28);\n\n\t\t\txxi->nsm = 1;\n\t\t\tsub->vol = sah.vol;\n\t\t\tlibxmp_c2spd_to_note(sah.c2spd, &sub->xpo, &sub->fin);\n\t\t\tsub->xpo += 12;\n\t\t\tret = libxmp_load_sample(m, f, SAMPLE_FLAG_ADLIB, xxs, (char *)sah.reg);\n\t\t\tif (ret < 0)\n\t\t\t\tgoto err3;\n\n\t\t\tD_(D_INFO \"[%2X] %-28.28s\", i, xxi->name);\n\n\t\t\tcontinue;\n#else\n\t\t\tgoto err3;\n#endif\n\t\t}\n\n\t\tmemcpy(sih.dosname, buf + 1, 12);\t/* DOS file name */\n\t\tsih.memseg_hi = buf[13];\t\t/* High byte of sample pointer */\n\t\tsih.memseg = readmem16l(buf + 14);\t/* Pointer to sample data */\n\t\tsih.length = readmem32l(buf + 16);\t/* Length */\n\n#if 0\n\t\t/* ST3 limit */\n\t\tif ((sfh.version >> 12) == 1 && sih.length > 64000)\n\t\t\tsih.length = 64000;\n#endif\n\n\t\tif (sih.length > MAX_SAMPLE_SIZE) {\n\t\t\tgoto err3;\n\t\t}\n\n\t\tsih.loopbeg = readmem32l(buf + 20);\t/* Loop begin */\n\t\tsih.loopend = readmem32l(buf + 24);\t/* Loop end */\n\t\tsih.vol = buf[28];\t\t\t/* Volume */\n\t\tsih.pack = buf[30];\t\t\t/* Packing type */\n\t\tsih.flags = buf[31];\t\t\t/* Loop/stereo/16bit flags */\n\t\tsih.c2spd = readmem16l(buf + 32);\t/* C4 speed */\n\t\tmemcpy(sih.name, buf + 48, 28);\t\t/* Instrument name */\n\t\tsih.magic = readmem32b(buf + 76);\t/* 'SCRS' */\n\n\t\tif (buf[0] == 1 && sih.magic != MAGIC_SCRS) {\n\t\t\tD_(D_CRIT \"error: instrument magic\");\n\t\t\tgoto err3;\n\t\t}\n\n\t\txxs->len = sih.length;\n\t\txxi->nsm = sih.length > 0 ? 1 : 0;\n\t\txxs->lps = sih.loopbeg;\n\t\txxs->lpe = sih.loopend;\n\n\t\txxs->flg = (sih.flags & S3M_SAMP_LOOP) ? XMP_SAMPLE_LOOP : 0;\n\n\t\tif (sih.flags & S3M_SAMP_STEREO) {\n\t\t\txxs->flg |= XMP_SAMPLE_STEREO;\n\t\t}\n\t\tif (sih.flags & S3M_SAMP_16BIT) {\n\t\t\txxs->flg |= XMP_SAMPLE_16BIT;\n\t\t}\n\n\t\tload_sample_flags = (sfh.ffi == 1) ? 0 : SAMPLE_FLAG_UNS;\n\t\tif (sih.pack == 4) {\n\t\t\tload_sample_flags = SAMPLE_FLAG_ADPCM;\n\t\t}\n\n\t\tsub->vol = sih.vol;\n\t\tsih.magic = 0;\n\n\t\tlibxmp_instrument_name(mod, i, sih.name, 28);\n\n\t\tD_(D_INFO \"[%2X] %-28.28s %04x%c%c %04x %04x %c V%02x %5d\",\n\t\t   i, mod->xxi[i].name, mod->xxs[i].len,\n\t\t   xxs->flg & XMP_SAMPLE_16BIT ? '+' : ' ',\n\t\t   xxs->flg & XMP_SAMPLE_STEREO ? 's' : ' ',\n\t\t   xxs->lps, mod->xxs[i].lpe,\n\t\t   xxs->flg & XMP_SAMPLE_LOOP ? 'L' : ' ', sub->vol, sih.c2spd);\n\n\t\tlibxmp_c2spd_to_note(sih.c2spd, &sub->xpo, &sub->fin);\n\n\t\tsample_segment = sih.memseg + ((uint32)sih.memseg_hi << 16);\n\n\t\tif (hio_seek(f, start + 16L * sample_segment, SEEK_SET) < 0) {\n\t\t\tgoto err3;\n\t\t}\n\n\t\tret = libxmp_load_sample(m, f, load_sample_flags, xxs, NULL);\n\t\tif (ret < 0) {\n\t\t\tgoto err3;\n\t\t}\n\t}\n\n\tfree(pp_pat);\n\tfree(pp_ins);\n\n\tm->quirk |= QUIRKS_ST3 | QUIRK_ARPMEM;\n\tm->read_event_type = READ_EVENT_ST3;\n\n\treturn 0;\n\nerr3:\n\tfree(pp_pat);\nerr2:\n\tfree(pp_ins);\nerr:\n\treturn -1;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/sample.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"../common.h\"\n#include \"loader.h\"\n\n#ifndef LIBXMP_CORE_PLAYER\n/*\n * From the Audio File Formats (version 2.5)\n * Submitted-by: Guido van Rossum <guido@cwi.nl>\n * Last-modified: 27-Aug-1992\n *\n * The Acorn Archimedes uses a variation on U-LAW with the bit order\n * reversed and the sign bit in bit 0.  Being a 'minority' architecture,\n * Arc owners are quite adept at converting sound/image formats from\n * other machines, and it is unlikely that you'll ever encounter sound in\n * one of the Arc's own formats (there are several).\n */\nstatic const int8 vdic_table[128] = {\n\t/*   0 */\t  0,   0,   0,   0,   0,   0,   0,   0,\n\t/*   8 */\t  0,   0,   0,   0,   0,   0,   0,   0,\n\t/*  16 */\t  0,   0,   0,   0,   0,   0,   0,   0,\n\t/*  24 */\t  1,   1,   1,   1,   1,   1,   1,   1,\n\t/*  32 */\t  1,   1,   1,   1,   2,   2,   2,   2,\n\t/*  40 */\t  2,   2,   2,   2,   3,   3,   3,   3,\n\t/*  48 */\t  3,   3,   4,   4,   4,   4,   5,   5,\n\t/*  56 */\t  5,   5,   6,   6,   6,   6,   7,   7,\n\t/*  64 */\t  7,   8,   8,   9,   9,  10,  10,  11,\n\t/*  72 */\t 11,  12,  12,  13,  13,  14,  14,  15,\n\t/*  80 */\t 15,  16,  17,  18,  19,  20,  21,  22,\n\t/*  88 */\t 23,  24,  25,  26,  27,  28,  29,  30,\n\t/*  96 */\t 31,  33,  34,  36,  38,  40,  42,  44,\n\t/* 104 */\t 46,  48,  50,  52,  54,  56,  58,  60,\n\t/* 112 */\t 62,  65,  68,  72,  77,  80,  84,  91,\n\t/* 120 */\t 95,  98, 103, 109, 114, 120, 126, 127\n};\n\n/* Convert 7 bit samples to 8 bit */\nstatic void convert_7bit_to_8bit(uint8 *p, int l)\n{\n\tfor (; l--; p++) {\n\t\t*p <<= 1;\n\t}\n}\n\n/* Convert Archimedes VIDC samples to linear */\nstatic void convert_vidc_to_linear(uint8 *p, int l)\n{\n\tint i;\n\tint8 amp;\n\tuint8 x;\n\n\tfor (i = 0; i < l; i++) {\n\t\tx = p[i];\n\t\tamp = vdic_table[x >> 1];\n\t\tp[i] = (uint8)((x & 0x01) ? -amp : amp);\n\t}\n}\n\nstatic void adpcm4_decoder(uint8 *inp, uint8 *outp, char *tab, int len)\n{\n\tchar delta = 0;\n\tuint8 b0, b1;\n\tint i;\n\n\tlen = (len + 1) / 2;\n\n\tfor (i = 0; i < len; i++) {\n\t\tb0 = *inp;\n\t\tb1 = *inp++ >> 4;\n\t\tdelta += tab[b0 & 0x0f];\n\t\t*outp++ = delta;\n\t\tdelta += tab[b1 & 0x0f];\n\t\t*outp++ = delta;\n\t}\n}\n#endif\n\n/* Convert differential to absolute sample data */\nstatic void convert_delta(uint8 *p, int frames, int is_16bit, int channels)\n{\n\tuint16 *w = (uint16 *)p;\n\tuint16 absval;\n\tint chn, i;\n\n\tif (is_16bit) {\n\t\tfor (chn = 0; chn < channels; chn++) {\n\t\t\tabsval = 0;\n\t\t\tfor (i = 0; i < frames; i++) {\n\t\t\t\tabsval = *w + absval;\n\t\t\t\t*w++ = absval;\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor (chn = 0; chn < channels; chn++) {\n\t\t\tabsval = 0;\n\t\t\tfor (i = 0; i < frames; i++) {\n\t\t\t\tabsval = *p + absval;\n\t\t\t\t*p++ = (uint8) absval;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/* Convert signed to unsigned sample data */\nstatic void convert_signal(uint8 *p, int l, int r)\n{\n\tuint16 *w = (uint16 *)p;\n\n\tif (r) {\n\t\tfor (; l--; w++)\n\t\t\t*w += 0x8000;\n\t} else {\n\t\tfor (; l--; p++)\n\t\t\t*p += (unsigned char)0x80;\n\t}\n}\n\n/* Convert little-endian 16 bit samples to big-endian */\nstatic void convert_endian(uint8 *p, int l)\n{\n\tuint8 b;\n\tint i;\n\n\tfor (i = 0; i < l; i++) {\n\t\tb = p[0];\n\t\tp[0] = p[1];\n\t\tp[1] = b;\n\t\tp += 2;\n\t}\n}\n\n/* Convert non-interleaved stereo to interleaved stereo.\n * Due to tracker quirks this should be done after delta decoding, etc. */\nstatic void convert_stereo_interleaved(void * LIBXMP_RESTRICT _out,\n const void *in, int frames, int is_16bit)\n{\n\tint i;\n\n\tif (is_16bit) {\n\t\tconst int16 *in_l = (const int16 *)in;\n\t\tconst int16 *in_r = in_l + frames;\n\t\tint16 *out = (int16 *)_out;\n\n\t\tfor (i = 0; i < frames; i++) {\n\t\t\t*(out++) = *(in_l++);\n\t\t\t*(out++) = *(in_r++);\n\t\t}\n\t} else {\n\t\tconst uint8 *in_l = (const uint8 *)in;\n\t\tconst uint8 *in_r = in_l + frames;\n\t\tuint8 *out = (uint8 *)_out;\n\n\t\tfor (i = 0; i < frames; i++) {\n\t\t\t*(out++) = *(in_l++);\n\t\t\t*(out++) = *(in_r++);\n\t\t}\n\t}\n}\n\n\nint libxmp_load_sample(struct module_data *m, HIO_HANDLE *f, int flags, struct xmp_sample *xxs, const void *buffer)\n{\n\tunsigned char *tmp = NULL;\n\tunsigned char *dest;\n\tint channels = 1;\n\tint framelen;\n\tint bytelen, extralen, i;\n\n#ifndef LIBXMP_CORE_PLAYER\n\t/* Adlib FM patches */\n\tif (flags & SAMPLE_FLAG_ADLIB) {\n\t\treturn 0;\n\t}\n#endif\n\n\t/* Empty or invalid samples\n\t */\n\tif (xxs->len <= 0) {\n\t\treturn 0;\n\t}\n\n\t/* Skip sample loading\n\t * FIXME: fails for ADPCM samples\n\t *\n\t * + Sanity check: skip huge samples (likely corrupt module)\n\t */\n\tif (xxs->len > MAX_SAMPLE_SIZE || (m && m->smpctl & XMP_SMPCTL_SKIP)) {\n\t\tif (~flags & SAMPLE_FLAG_NOLOAD) {\n\t\t\t/* coverity[check_return] */\n\t\t\thio_seek(f, xxs->len, SEEK_CUR);\n\t\t}\n\t\treturn 0;\n\t}\n\n\t/* Patches with samples\n\t * Allocate extra sample for interpolation.\n\t */\n\tbytelen = xxs->len;\n\tframelen = 1;\n\textralen = 4;\n\n\tif (xxs->flg & XMP_SAMPLE_16BIT) {\n\t\tbytelen *= 2;\n\t\textralen *= 2;\n\t\tframelen *= 2;\n\t}\n\tif (xxs->flg & XMP_SAMPLE_STEREO) {\n\t\tbytelen *= 2;\n\t\textralen *= 2;\n\t\tframelen *= 2;\n\t\tchannels = 2;\n\t}\n\n\t/* If this sample starts at or after EOF, skip it entirely.\n\t */\n\tif (~flags & SAMPLE_FLAG_NOLOAD) {\n\t\tlong file_pos, file_len;\n\t\tlong remaining = 0;\n\t\tlong over = 0;\n\t\tif (!f) {\n\t\t\treturn 0;\n\t\t}\n\t\tfile_pos = hio_tell(f);\n\t\tfile_len = hio_size(f);\n\t\tif (file_pos >= file_len) {\n\t\t\tD_(D_WARN \"ignoring sample at EOF\");\n\t\t\treturn 0;\n\t\t}\n\t\t/* If this sample goes past EOF, truncate it. */\n\t\tremaining = file_len - file_pos;\n#ifndef LIBXMP_CORE_PLAYER\n\t\tif (flags & SAMPLE_FLAG_ADPCM) {\n\t\t\tlong bound = 16 + ((bytelen + 1) >> 1);\n\t\t\tif (remaining < 16) {\n\t\t\t\tD_(D_WARN \"ignoring truncated ADPCM sample\");\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (bound > remaining) {\n\t\t\t\tover = bound - remaining;\n\t\t\t\tbytelen = (remaining - 16) << 1;\n\t\t\t}\n\t\t} else\n#endif\n\t\tif (bytelen > remaining) {\n\t\t\tover = bytelen - remaining;\n\t\t\tbytelen = remaining;\n\t\t}\n\n\t\tif (over) {\n\t\t\tD_(D_WARN \"sample would extend %ld bytes past EOF; truncating to %ld\",\n\t\t\t\tover, remaining);\n\n\t\t\t/* Trim extra bytes non-aligned to sample frame. */\n\t\t\tbytelen -= bytelen & (framelen - 1);\n\n\t\t\txxs->len = bytelen;\n\t\t\tif (xxs->flg & XMP_SAMPLE_16BIT)\n\t\t\t\txxs->len >>= 1;\n\t\t\tif (xxs->flg & XMP_SAMPLE_STEREO)\n\t\t\t\txxs->len >>= 1;\n\t\t}\n\t}\n\n\t/* Loop parameters sanity check\n\t */\n\tif (xxs->lps < 0) {\n\t\txxs->lps = 0;\n\t}\n\tif (xxs->lpe > xxs->len) {\n\t\txxs->lpe = xxs->len;\n\t}\n\tif (xxs->lps >= xxs->len || xxs->lps >= xxs->lpe) {\n\t\txxs->lps = xxs->lpe = 0;\n\t\txxs->flg &= ~(XMP_SAMPLE_LOOP | XMP_SAMPLE_LOOP_BIDIR);\n\t}\n\n\t/* Disable bidirectional loop flag if sample is not looped\n\t */\n\tif (xxs->flg & XMP_SAMPLE_LOOP_BIDIR) {\n\t\tif (~xxs->flg & XMP_SAMPLE_LOOP)\n\t\t\txxs->flg &= ~XMP_SAMPLE_LOOP_BIDIR;\n\t}\n\tif (xxs->flg & XMP_SAMPLE_SLOOP_BIDIR) {\n\t\tif (~xxs->flg & XMP_SAMPLE_SLOOP)\n\t\t\txxs->flg &= ~XMP_SAMPLE_SLOOP_BIDIR;\n\t}\n\n\t/* add guard bytes before the buffer for higher order interpolation */\n\txxs->data = (unsigned char *) malloc(bytelen + extralen + 4);\n\tif (xxs->data == NULL) {\n\t\tgoto err;\n\t}\n\n\t*(uint32 *)xxs->data = 0;\n\txxs->data += 4;\n\tdest = xxs->data;\n\n\t/* If this is a non-interleaved stereo sample, most conversions need\n\t * to occur in an intermediate buffer prior to interleaving. Most\n\t * formats supporting stereo samples use non-interleaved stereo.\n\t */\n\tif ((xxs->flg & XMP_SAMPLE_STEREO) && (~flags & SAMPLE_FLAG_INTERLEAVED)) {\n\t\ttmp = (unsigned char *) malloc(bytelen);\n\t\tif (!tmp)\n\t\t\tgoto err2;\n\n\t\tdest = tmp;\n\t}\n\n\tif (flags & SAMPLE_FLAG_NOLOAD) {\n\t\tmemcpy(dest, buffer, bytelen);\n\t} else\n#ifndef LIBXMP_CORE_PLAYER\n\tif (flags & SAMPLE_FLAG_ADPCM) {\n\t\tint x2 = (bytelen + 1) >> 1;\n\t\tchar table[16];\n\n\t\tif (hio_read(table, 1, 16, f) != 16) {\n\t\t\tgoto err2;\n\t\t}\n\t\tif (hio_read(dest + x2, 1, x2, f) != x2) {\n\t\t\tgoto err2;\n\t\t}\n\t\tadpcm4_decoder((uint8 *)dest + x2,\n\t\t\t       (uint8 *)dest, table, bytelen);\n\t} else\n#endif\n\t{\n\t\tint x = hio_read(dest, 1, bytelen, f);\n\t\tif (x != bytelen) {\n\t\t\tD_(D_WARN \"short read (%d) in sample load\", x - bytelen);\n\t\t\tmemset(dest + x, 0, bytelen - x);\n\t\t}\n\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\tif (flags & SAMPLE_FLAG_7BIT) {\n\t\tconvert_7bit_to_8bit(dest, xxs->len * channels);\n\t}\n#endif\n\n\t/* Fix endianism if needed */\n\tif (xxs->flg & XMP_SAMPLE_16BIT) {\n#ifdef WORDS_BIGENDIAN\n\t\tif (~flags & SAMPLE_FLAG_BIGEND)\n\t\t\tconvert_endian(dest, xxs->len * channels);\n#else\n\t\tif (flags & SAMPLE_FLAG_BIGEND)\n\t\t\tconvert_endian(dest, xxs->len * channels);\n#endif\n\t}\n\n\t/* Convert delta samples */\n\tif (flags & SAMPLE_FLAG_DIFF) {\n\t\tconvert_delta(dest, xxs->len, xxs->flg & XMP_SAMPLE_16BIT, channels);\n\t} else if (flags & SAMPLE_FLAG_8BDIFF) {\n\t\tint len = xxs->len;\n\t\tif (xxs->flg & XMP_SAMPLE_16BIT) {\n\t\t\tlen *= 2;\n\t\t}\n\t\tconvert_delta(dest, len, 0, channels);\n\t}\n\n\t/* Convert samples to signed */\n\tif (flags & SAMPLE_FLAG_UNS) {\n\t\tconvert_signal(dest, xxs->len * channels,\n\t\t\t\txxs->flg & XMP_SAMPLE_16BIT);\n\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\tif (flags & SAMPLE_FLAG_VIDC) {\n\t\tconvert_vidc_to_linear(dest, xxs->len * channels);\n\t}\n#endif\n\n\t/* Done converting individual samples; convert to interleaved. */\n\tif ((xxs->flg & XMP_SAMPLE_STEREO) && (~flags & SAMPLE_FLAG_INTERLEAVED)) {\n\t\tconvert_stereo_interleaved(xxs->data, dest, xxs->len,\n\t\t\t\t\t   xxs->flg & XMP_SAMPLE_16BIT);\n\t}\n\n\t/* Check for full loop samples */\n\tif (flags & SAMPLE_FLAG_FULLREP) {\n\t    if (xxs->lps == 0 && xxs->len > xxs->lpe)\n\t\txxs->flg |= XMP_SAMPLE_LOOP_FULL;\n\t}\n\n\t/* Add extra samples at end */\n\tfor (i = 0; i < extralen; i++) {\n\t\txxs->data[bytelen + i] = xxs->data[bytelen - framelen + i];\n\t}\n\n\t/* Add extra samples at start */\n\tfor (i = -1; i >= -4; i--) {\n\t\txxs->data[i] = xxs->data[framelen + i];\n\t}\n\n\tfree(tmp);\n\treturn 0;\n\n    err2:\n\tlibxmp_free_sample(xxs);\n\tfree(tmp);\n    err:\n\treturn -1;\n}\n\nvoid libxmp_free_sample(struct xmp_sample *s)\n{\n\tif (s->data) {\n\t\tfree(s->data - 4);\n\t\ts->data = NULL;\t\t/* prevent double free in PCM load error */\n\t}\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/st_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2022 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/* Ultimate Soundtracker support based on the module format description\n * written by Michael Schwendt <sidplay@geocities.com>\n */\n\n#include \"loader.h\"\n#include \"mod.h\"\n#include \"../period.h\"\n\nstatic int st_test(HIO_HANDLE *, char *, const int);\nstatic int st_load(struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_st = {\n\t\"Soundtracker\",\n\tst_test,\n\tst_load\n};\n\n/* musanx.mod contains 22 period and instrument errors */\n#define ST_MAX_PATTERN_ERRORS 22\n/* Allow some degree of sample truncation for ST modules.\n * The worst known module currently is u2.mod with 7% truncation. */\n#define ST_TRUNCATION_LIMIT   93\n\nstatic const int period[] = {\n\t856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453,\n\t428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226,\n\t214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113,\n\t/* Off-by-one period values found in blueberry.mod, snd.mod,\n\t * quite a lot.mod, sweet dreams.mod, and bar----fringdus.mod */\n\t763, 679, 641, 571, 539, 509, 429, 340, 321, 300, 286, 270,\n\t227, 191, 162,\n\t-1\n};\n\nstatic int st_expected_size(int smp_size, int pat)\n{\n\treturn 600 + smp_size + 1024 * pat;\n}\n\nstatic int st_test(HIO_HANDLE *f, char *t, const int start)\n{\n\tint i, j, k;\n\tint pat, pat_short, ins, smp_size;\n\tstruct st_header mh;\n\tuint8 pat_buf[1024];\n\tuint8 *mod_event;\n\tint pattern_errors;\n\tint test_flags = TEST_NAME_IGNORE_AFTER_CR;\n\tlong size;\n\n\tsize = hio_size(f);\n\n\tif (size < 600) {\n\t\treturn -1;\n\t}\n\n\tsmp_size = 0;\n\n\thio_seek(f, start, SEEK_SET);\n\thio_read(mh.name, 1, 20, f);\n\t/* The Super Ski 2 modules have unusual \"SONG\\x13\\x88\" names. */\n\tif (mh.name[5] == 0x88) {\n\t\tmh.name[5] = 'X';\n\t\tif (mh.name[4] == 0x13)\n\t\t\tmh.name[4] = 'X';\n\t}\n\tif (libxmp_test_name(mh.name, 20, 0) < 0) {\n\t\tD_(D_CRIT \"bad module name; not ST\");\n\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < 15; i++) {\n\t\thio_read(mh.ins[i].name, 1, 22, f);\n\t\tmh.ins[i].size = hio_read16b(f);\n\t\tmh.ins[i].finetune = hio_read8(f);\n\t\tmh.ins[i].volume = hio_read8(f);\n\t\tmh.ins[i].loop_start = hio_read16b(f);\n\t\tmh.ins[i].loop_size = hio_read16b(f);\n\t}\n\tmh.len = hio_read8(f);\n\tmh.restart = hio_read8(f);\n\thio_read(mh.order, 1, 128, f);\n\n\tfor (pat = pat_short = i = 0; i < 128; i++) {\n\t\tif (mh.order[i] > 0x7f)\n\t\t\treturn -1;\n\t\tif (mh.order[i] > pat) {\n\t\t\tpat = mh.order[i];\n\t\t\tif (i < mh.len)\n\t\t\t\tpat_short = pat;\n\t\t}\n\t}\n\tpat++;\n\tpat_short++;\n\n\tif (pat > 0x7f || mh.len == 0 || mh.len > 0x80)\n\t\treturn -1;\n\n\tfor (i = 0; i < 15; i++) {\n\t\tsmp_size += 2 * mh.ins[i].size;\n\n\t\t/* pennylane.mod and heymusic-sssexremix.mod have unusual\n\t\t * values after the \\0. */\n\t\tif (i == 0 &&\n\t\t    (!memcmp(mh.ins[i].name, \"funbass\\0\\r\", 9) ||\n\t\t     !memcmp(mh.ins[i].name, \"st-69:baseline\\0R\\0\\0\\xA5\", 17))) {\n\t\t\tD_(D_INFO \"ignoring junk name values after \\\\0\");\n\t\t\ttest_flags |= TEST_NAME_IGNORE_AFTER_0;\n\t\t}\n\n\t\t/* Crepequs.mod has random values in first byte */\n\t\tmh.ins[i].name[0] = 'X';\n\n\t\tif (libxmp_test_name(mh.ins[i].name, 22, test_flags) < 0) {\n\t\t\tD_(D_CRIT \"bad instrument name %d; not ST\", i);\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (mh.ins[i].volume > 0x40)\n\t\t\treturn -1;\n\n\t\tif (mh.ins[i].finetune > 0x0f)\n\t\t\treturn -1;\n\n\t\tif (mh.ins[i].size > 0x8000)\n\t\t\treturn -1;\n\n\t\t/* This test is always false, disable it\n\t\t *\n\t\t * if ((mh.ins[i].loop_start >> 1) > 0x8000)\n\t\t *    return -1;\n\t\t */\n\n\t\tif (mh.ins[i].loop_size > 0x8000)\n\t\t\treturn -1;\n\n\t\t/* This test fails in atmosfer.mod, disable it\n\t\t *\n\t\t * if (mh.ins[i].loop_size > 1 && mh.ins[i].loop_size > mh.ins[i].size)\n\t\t *    return -1;\n\t\t */\n\n\t\t/* Bad rip of fin-nv1.mod has this unused instrument. */\n\t\tif (mh.ins[i].size == 0 &&\n\t\t    mh.ins[i].loop_start == 4462 &&\n\t\t    mh.ins[i].loop_size == 2078) {\n\t\t\tD_(D_INFO \"ignoring bad instrument for fin-nv1.mod\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ((mh.ins[i].loop_start >> 1) > mh.ins[i].size)\n\t\t\treturn -1;\n\n\t\tif (mh.ins[i].size\n\t\t    && (mh.ins[i].loop_start >> 1) == mh.ins[i].size)\n\t\t\treturn -1;\n\n\t\tif (mh.ins[i].size == 0 && mh.ins[i].loop_start > 0)\n\t\t\treturn -1;\n\t}\n\n\tif (smp_size < 8) {\n\t\treturn -1;\n\t}\n\n\t/* If the file size is correct when counting only patterns prior to the\n\t * module length, use the shorter count. This quirk is found in some\n\t * ST modules, most of them authored by Jean Baudlot. See razor-1911.mod,\n\t * the Operation Wolf soundtrack, or the Bad Dudes soundtrack.\n\t */\n\tif (size < st_expected_size(smp_size, pat) &&\n\t    size == st_expected_size(smp_size, pat_short)) {\n\t\tD_(D_INFO \"ST pattern list probably quirked, ignoring patterns past len\");\n\t\tpat = pat_short;\n\t}\n\n\tpattern_errors = 0;\n\tfor (ins = i = 0; i < pat; i++) {\n\t\tif (hio_read(pat_buf, 1, 1024, f) < 1024) {\n\t\t\tD_(D_CRIT \"read error at pattern %d; not ST\", i);\n\t\t\treturn -1;\n\t\t}\n\t\tmod_event = pat_buf;\n\t\tfor (j = 0; j < (64 * 4); j++, mod_event += 4) {\n\t\t\tint p, s;\n\n\t\t\ts = (mod_event[0] & 0xf0) | MSN(mod_event[2]);\n\n\t\t\tif (s > 15) {\t/* sample number > 15 */\n\t\t\t\tD_(D_INFO \"%d/%d/%d: invalid sample number: %d\", i, j / 4, j % 4, s);\n\t\t\t\tif ((++pattern_errors) > ST_MAX_PATTERN_ERRORS)\n\t\t\t\t\tgoto bad_pattern_data;\n\t\t\t}\n\n\t\t\tif (s > ins) {\t/* find highest used sample */\n\t\t\t\tins = s;\n\t\t\t}\n\n\t\t\tp = 256 * LSN(mod_event[0]) + mod_event[1];\n\n\t\t\tif (p == 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor (k = 0; period[k] >= 0; k++) {\n\t\t\t\tif (p == period[k])\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (period[k] < 0) {\n\t\t\t\tD_(D_INFO \"%d/%d/%d: invalid period: %d\", i, j / 4, j % 4, p);\n\t\t\t\tif ((++pattern_errors) > ST_MAX_PATTERN_ERRORS)\n\t\t\t\t\tgoto bad_pattern_data;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Check if file was cut before any unused samples */\n\tif (size < st_expected_size(smp_size, pat)) {\n\t\tint ss, limit;\n\t\tfor (ss = i = 0; i < 15 && i < ins; i++) {\n\t\t\tss += 2 * mh.ins[i].size;\n\t\t}\n\n\t\tlimit = st_expected_size(ss, pat) * ST_TRUNCATION_LIMIT / 100;\n\t\tif (size < limit) {\n\t\t\tD_(D_CRIT \"expected size %d, minimum allowed size %d, real size %ld, diff %ld\",\n\t\t\t st_expected_size(smp_size, pat), limit, size, size - limit);\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\thio_seek(f, start, SEEK_SET);\n\tlibxmp_read_title(f, t, 20);\n\n\treturn 0;\n\nbad_pattern_data:\n\tD_(D_CRIT \"too many pattern errors; not ST: %d\", pattern_errors);\n\treturn -1;\n}\n\nstatic int st_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j;\n\tstruct xmp_event *event;\n\tstruct st_header mh;\n\tuint8 pat_buf[1024];\n\tuint8 *mod_event;\n\tint ust = 1;\n\t/* int lps_mult = m->fetch & XMP_CTL_FIXLOOP ? 1 : 2; */\n\tconst char *modtype;\n\tint fxused;\n\tint smp_size, pat_short;\n\tlong size;\n\n\tLOAD_INIT();\n\n\tmod->chn = 4;\n\tmod->ins = 15;\n\tmod->smp = mod->ins;\n\n\tsmp_size = 0;\n\n\thio_read(mh.name, 1, 20, f);\n\tfor (i = 0; i < 15; i++) {\n\t\thio_read(mh.ins[i].name, 1, 22, f);\n\t\tmh.ins[i].size = hio_read16b(f);\n\t\tmh.ins[i].finetune = hio_read8(f);\n\t\tmh.ins[i].volume = hio_read8(f);\n\t\tmh.ins[i].loop_start = hio_read16b(f);\n\t\tmh.ins[i].loop_size = hio_read16b(f);\n\t\tsmp_size += 2 * mh.ins[i].size;\n\t}\n\tmh.len = hio_read8(f);\n\tmh.restart = hio_read8(f);\n\thio_read(mh.order, 1, 128, f);\n\n\tmod->len = mh.len;\n\tmod->rst = mh.restart;\n\n\t/* UST: The byte at module offset 471 is BPM, not the song restart\n\t *      The default for UST modules is 0x78 = 120 BPM = 48 Hz.\n\t */\n\tif (mod->rst < 0x40)\t/* should be 0x20 */\n\t\tust = 0;\n\n\tmemcpy(mod->xxo, mh.order, 128);\n\n\tfor (pat_short = i = 0; i < 128; i++) {\n\t\tif (mod->xxo[i] > mod->pat) {\n\t\t\tmod->pat = mod->xxo[i];\n\t\t\tif (i < mh.len)\n\t\t\t\tpat_short = mod->pat;\n\t\t}\n\t}\n\tmod->pat++;\n\tpat_short++;\n\n\t/* If the file size is correct when counting only patterns prior to the\n\t * module length, use the shorter count. See test function for info.\n\t */\n\tsize = hio_size(f);\n\tif (size < st_expected_size(smp_size, mod->pat) &&\n\t    size == st_expected_size(smp_size, pat_short)) {\n\t\tmod->pat = pat_short;\n\t}\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\t/* UST: Volume word does not contain a \"Finetuning\" value in its\n\t\t * high-byte.\n\t\t */\n\t\tif (mh.ins[i].finetune)\n\t\t\tust = 0;\n\n\t\t/* if (mh.ins[i].size == 0 && mh.ins[i].loop_size == 1)\n\t\t   nt = 1; */\n\n\t\t/* UST: Maximum sample length is 9999 bytes decimal, but 1387\n\t\t * words hexadecimal. Longest samples on original sample disk\n\t\t * ST-01 were 9900 bytes.\n\t\t */\n\t\tif (mh.ins[i].size > 0x1387 || mh.ins[i].loop_start > 9999\n\t\t    || mh.ins[i].loop_size > 0x1387) {\n\t\t\tust = 0;\n\t\t}\n\t}\n\n\tif (libxmp_init_instrument(m) < 0) {\n\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\t\tstruct xmp_sample *xxs = &mod->xxs[i];\n\t\tstruct xmp_subinstrument *sub;\n\n\t\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t\t\treturn -1;\n\n\t\tsub = &xxi->sub[0];\n\n\t\txxs->len = 2 * mh.ins[i].size - mh.ins[i].loop_start;\n\t\txxs->lps = 0;\n\t\txxs->lpe = xxs->lps + 2 * mh.ins[i].loop_size;\n\t\txxs->flg = mh.ins[i].loop_size > 1 ? XMP_SAMPLE_LOOP : 0;\n\t\tsub->fin = (int8) (mh.ins[i].finetune << 4);\n\t\tsub->vol = mh.ins[i].volume;\n\t\tsub->pan = 0x80;\n\t\tsub->sid = i;\n\t\tstrncpy((char *)xxi->name, (char *)mh.ins[i].name, 22);\n\n\t\tif (xxs->len > 0) {\n\t\t\txxi->nsm = 1;\n\t\t}\n\t}\n\n\tmod->trk = mod->chn * mod->pat;\n\n\tstrncpy(mod->name, (char *)mh.name, 20);\n\n\tif (libxmp_init_pattern(mod) < 0) {\n\t\treturn -1;\n\t}\n\n\t/* Load and convert patterns */\n\t/* Also scan patterns for tracker detection */\n\tfxused = 0;\n\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n\tfor (i = 0; i < mod->pat; i++) {\n\t\tif (libxmp_alloc_pattern_tracks(mod, i, 64) < 0)\n\t\t\treturn -1;\n\n\t\tif (hio_read(pat_buf, 1, 1024, f) < 1024)\n\t\t\treturn -1;\n\n\t\tmod_event = pat_buf;\n\t\tfor (j = 0; j < (64 * 4); j++, mod_event += 4) {\n\t\t\tevent = &EVENT(i, j % 4, j / 4);\n\t\t\tlibxmp_decode_protracker_event(event, mod_event);\n\n\t\t\tif (event->fxt)\n\t\t\t\tfxused |= 1 << event->fxt;\n\t\t\telse if (event->fxp)\n\t\t\t\tfxused |= 1;\n\n\t\t\t/* UST: Only effects 1 (arpeggio) and 2 (pitchbend) are\n\t\t\t * available.\n\t\t\t */\n\t\t\tif (event->fxt && event->fxt != 1 && event->fxt != 2)\n\t\t\t\tust = 0;\n\n\t\t\t/* Karsten Obarski's sleepwalk uses arpeggio 30 and 40 */\n\t\t\tif (event->fxt == 1) {\t/* unlikely arpeggio */\n\t\t\t\tif (event->fxp == 0x00)\n\t\t\t\t\tust = 0;\n\t\t\t\t/*if ((ev.fxp & 0x0f) == 0 || (ev.fxp & 0xf0) == 0)\n\t\t\t\t   ust = 0; */\n\t\t\t}\n\n\t\t\tif (event->fxt == 2) {  /* bend up and down at same time? */\n\t\t\t\tif ((event->fxp & 0x0f) != 0 && (event->fxp & 0xf0) != 0)\n\t\t\t\t\tust = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (fxused & ~0x0006) {\n\t\tust = 0;\n\t}\n\n\tif (ust) {\n\t\tmodtype = \"Ultimate Soundtracker\";\n\t} else if ((fxused & ~0xd007) == 0) {\n\t\tmodtype = \"Soundtracker IX\";\t/* or MasterSoundtracker? */\n\t} else if ((fxused & ~0xf807) == 0) {\n\t\tmodtype = \"D.O.C Soundtracker 2.0\";\n\t} else {\n\t\tmodtype = \"unknown tracker 15 instrument\";\n\t}\n\n\tsnprintf(mod->type, XMP_NAME_SIZE, \"%s\", modtype);\n\n\tMODULE_INFO();\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tD_(D_INFO \"[%2X] %-22.22s %04x %04x %04x %c V%02x %+d\",\n\t\t   i, mod->xxi[i].name, mod->xxs[i].len, mod->xxs[i].lps,\n\t\t   mod->xxs[i].lpe, mh.ins[i].loop_size > 1 ? 'L' : ' ',\n\t\t   mod->xxi[i].sub[0].vol, mod->xxi[i].sub[0].fin >> 4);\n\t}\n\n\tm->quirk |= QUIRK_NOBPM;\n\tm->period_type = PERIOD_MODRNG;\n\n\t/* Perform the necessary conversions for Ultimate Soundtracker */\n\tif (ust) {\n\t\t/* Fix restart & bpm */\n\t\tmod->bpm = mod->rst;\n\t\tmod->rst = 0;\n\n\t\t/* Fix sample loops */\n\t\tfor (i = 0; i < mod->ins; i++) {\n\t\t\t/* FIXME */\n\t\t}\n\n\t\t/* Fix effects (arpeggio and pitchbending) */\n\t\tfor (i = 0; i < mod->pat; i++) {\n\t\t\tfor (j = 0; j < (64 * 4); j++) {\n\t\t\t\tevent = &EVENT(i, j % 4, j / 4);\n\t\t\t\tif (event->fxt == 1)\n\t\t\t\t\tevent->fxt = 0;\n\t\t\t\telse if (event->fxt == 2\n\t\t\t\t\t && (event->fxp & 0xf0) == 0)\n\t\t\t\t\tevent->fxt = 1;\n\t\t\t\telse if (event->fxt == 2\n\t\t\t\t\t && (event->fxp & 0x0f) == 0)\n\t\t\t\t\tevent->fxp >>= 4;\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif (mod->rst >= mod->len)\n\t\t\tmod->rst = 0;\n\t}\n\n\t/* Load samples */\n\n\tD_(D_INFO \"Stored samples: %d\", mod->smp);\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tif (!mod->xxs[i].len)\n\t\t\tcontinue;\n\n\t\t/* Skip transient part of sample.\n\t\t *\n\t\t * Dennis Lindroos <denafcm@gmail.com> reports: One main thing is\n\t\t * sample-looping which on all trackers up to Noisetracker 1 (i\n\t\t * think it was Mahoney who actually changed the loopstart to be\n\t\t * in WORDS) never play looped samples from the beginning, i.e.\n\t\t * only plays the looped part. This can be heard in old modules\n\t\t * especially with \"analogstring\", \"strings2\" or \"strings3\"\n\t\t * samples because these have \"slow attack\" that is not part of\n\t\t * the loop and thus they sound \"sharper\"..\n\t\t */\n\t\thio_seek(f, mh.ins[i].loop_start, SEEK_CUR);\n\n\t\tif (libxmp_load_sample(m, f, 0, &mod->xxs[i], NULL) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/stm_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"loader.h\"\n#include \"../period.h\"\n\n#define STM_TYPE_SONG\t0x01\n#define STM_TYPE_MODULE\t0x02\n\nstruct stm_instrument_header {\n\tuint8 name[12];\t\t/* ASCIIZ instrument name */\n\tuint8 id;\t\t/* Id=0 */\n\tuint8 idisk;\t\t/* Instrument disk */\n\tuint16 rsvd1;\t\t/* Reserved */\n\tuint16 length;\t\t/* Sample length */\n\tuint16 loopbeg;\t\t/* Loop begin */\n\tuint16 loopend;\t\t/* Loop end */\n\tuint8 volume;\t\t/* Playback volume */\n\tuint8 rsvd2;\t\t/* Reserved */\n\tuint16 c2spd;\t\t/* C4 speed */\n\tuint32 rsvd3;\t\t/* Reserved */\n\tuint16 paralen;\t\t/* Length in paragraphs */\n};\n\n/* v1 format header based on disassembled ST2 */\nstruct stm_file_subheader_v1 {\n\tuint16 insnum;\t\t/* Number of instruments */\n\tuint16 ordnum;\t\t/* Number of orders */\n\tuint16 patnum;\t\t/* Number of patterns */\n\tuint16 srate;\t\t/* Sample rate? */\n\tuint8 tempo;\t\t/* Playback tempo */\n\tuint8 channels;\t\t/* Number of channels */\n\tuint16 psize;\t\t/* Pattern size */\n\tuint16 rsvd2;\t\t/* Reserved */\n\tuint16 skip;\t\t/* Bytes to skip */\n};\n\nstruct stm_file_subheader_v2 {\n\tuint8 tempo;\t\t/* Playback tempo */\n\tuint8 patterns;\t\t/* Number of patterns */\n\tuint8 gvol;\t\t/* Global volume */\n\tuint8 rsvd2[13];\t/* Reserved */\n};\n\nstruct stm_file_header {\n\tuint8 name[20];\t\t/* ASCIIZ song name */\n\tuint8 magic[8];\t\t/* '!Scream!' */\n\tuint8 rsvd1;\t\t/* '\\x1a' */\n\tuint8 type;\t\t/* 1=song, 2=module */\n\tuint8 vermaj;\t\t/* Major version number */\n\tuint8 vermin;\t\t/* Minor version number */\n\tunion {\n\t\tstruct stm_file_subheader_v1 v1;\n\t\tstruct stm_file_subheader_v2 v2;\n\t} sub;\n\tstruct stm_instrument_header ins[32];\n};\n\nstatic int stm_test(HIO_HANDLE *, char *, const int);\nstatic int stm_load(struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_stm = {\n\t\"Scream Tracker 2\",\n\tstm_test,\n\tstm_load\n};\n\nstatic int stm_test(HIO_HANDLE * f, char *t, const int start)\n{\n\tuint8 buf[8];\n\tuint16 version;\n\n\thio_seek(f, start + 20, SEEK_SET);\n\tif (hio_read(buf, 1, 8, f) < 8)\n\t\treturn -1;\n\n\tif (libxmp_test_name(buf, 8, 0))\t/* Tracker name should be ASCII */\n\t\treturn -1;\n\n\t/* EOF should be 0x1a. putup10.stm and putup11.stm have 2 instead. */\n\tbuf[0] = hio_read8(f);\n\tif (buf[0] != 0x1a && buf[0] != 0x02)\n\t\treturn -1;\n\n\tif (hio_read8(f) > STM_TYPE_MODULE)\n\t\treturn -1;\n\n\tbuf[0] = hio_read8(f);\n\tbuf[1] = hio_read8(f);\n\tversion = (100 * buf[0]) + buf[1];\n\n\tif (version != 110 &&\n\t    version != 200 && version != 210 &&\n\t    version != 220 && version != 221) {\n\t\tD_(D_CRIT \"Unknown version: %d\", version);\n\t\treturn -1;\n\t}\n\n\thio_seek(f, start + 60, SEEK_SET);\n\tif (hio_read(buf, 1, 4, f) < 4)\n\t\treturn -1;\n\tif (!memcmp(buf, \"SCRM\", 4))\t/* We don't want STX files */\n\t\treturn -1;\n\n\thio_seek(f, start + 0, SEEK_SET);\n\tlibxmp_read_title(f, t, 20);\n\n\treturn 0;\n}\n\n#define FX_NONE\t\t0xff\n\n/*\n * Skaven's note from http://www.futurecrew.com/skaven/oldies_music.html\n *\n * FYI for the tech-heads: In the old Scream Tracker 2 the Arpeggio command\n * (Jxx), if used in a single row with a 0x value, caused the note to skip\n * the specified amount of halftones upwards halfway through the row. I used\n * this in some songs to give the lead some character. However, when played\n * in ModPlug Tracker, this effect doesn't work the way it did back then.\n */\nstatic const uint8 fx[16] = {\n\tFX_NONE,\n\tFX_SPEED,\t/* A - Set tempo to [INFO]. 60 normal. */\n\tFX_JUMP,\t/* B - Break pattern and jmp to order [INFO] */\n\tFX_BREAK,\t/* C - Break pattern */\n\tFX_VOLSLIDE,\t/* D - Slide volume; Hi-nibble=up, Lo-nibble=down */\n\tFX_PORTA_DN,\t/* E - Slide down at speed [INFO] */\n\tFX_PORTA_UP,\t/* F - Slide up at speed [INFO] */\n\tFX_TONEPORTA,\t/* G - Slide to the note specified at speed [INFO] */\n\tFX_VIBRATO,\t/* H - Vibrato; Hi-nibble, speed. Lo-nibble, size */\n\tFX_TREMOR,\t/* I - Tremor; Hi-nibble, ontime. Lo-nibble, offtime */\n\tFX_ARPEGGIO,\t/* J - Arpeggio */\n\tFX_NONE,\n\tFX_NONE,\n\tFX_NONE,\n\tFX_NONE,\n\tFX_NONE\n};\n\nstatic int stm_load(struct module_data *m, HIO_HANDLE * f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_event *event;\n\tstruct stm_file_header sfh;\n\tuint8 b;\n\tuint16 version;\n\tint blank_pattern = 0;\n\tint stored_patterns;\n\tint i, j, k;\n\n\tLOAD_INIT();\n\n\thio_read(sfh.name, 20, 1, f);\t/* ASCIIZ song name */\n\thio_read(sfh.magic, 8, 1, f);\t/* '!Scream!' */\n\tsfh.rsvd1 = hio_read8(f);\t/* '\\x1a' */\n\tsfh.type = hio_read8(f);\t/* 1=song, 2=module */\n\tsfh.vermaj = hio_read8(f);\t/* Major version number */\n\tsfh.vermin = hio_read8(f);\t/* Minor version number */\n\tversion = (100 * sfh.vermaj) + sfh.vermin;\n\n\tif (version != 110 &&\n\t    version != 200 && version != 210 &&\n\t    version != 220 && version != 221) {\n\t\tD_(D_CRIT \"Unknown version: %d\", version);\n\t\treturn -1;\n\t}\n\n\t// TODO: improve robustness of the loader against bad parameters\n\n\tif (version >= 200) {\n\t\tsfh.sub.v2.tempo = hio_read8(f);\t/* Playback tempo */\n\t\tsfh.sub.v2.patterns = hio_read8(f);\t/* Number of patterns */\n\t\tsfh.sub.v2.gvol = hio_read8(f);\t\t/* Global volume */\n\t\thio_read(sfh.sub.v2.rsvd2, 13, 1, f);\t/* Reserved */\n\t\tmod->chn = 4;\n\t\tmod->pat = sfh.sub.v2.patterns;\n\t\tmod->spd = (version < 221) ? LSN(sfh.sub.v2.tempo / 10) : MSN(sfh.sub.v2.tempo);\n\t\tmod->ins = 31;\n\t\tmod->len = (version == 200) ? 64 : 128;\n\t} else {\n\t\tif ((sfh.sub.v1.insnum = hio_read16l(f)) > 32) {\t/* Number of instruments */\n\t\t\tD_(D_CRIT \"Wrong number of instruments: %d (max 32)\", sfh.sub.v1.insnum);\n\t\t\treturn -1;\n\t\t}\n\t\tif ((sfh.sub.v1.ordnum = hio_read16l(f)) > XMP_MAX_MOD_LENGTH) {\t/* Number of orders */\n\t\t\tD_(D_CRIT \"Wrong number of orders: %d (max %d)\", sfh.sub.v1.ordnum, XMP_MAX_MOD_LENGTH);\n\t\t\treturn -1;\n\t\t}\n\t\tif ((sfh.sub.v1.patnum = hio_read16l(f)) > XMP_MAX_MOD_LENGTH) {\t/* Number of patterns */\n\t\t\tD_(D_CRIT \"Wrong number of patterns: %d (max %d)\", sfh.sub.v1.patnum, XMP_MAX_MOD_LENGTH);\n\t\t\treturn -1;\n\t\t}\n\t\tsfh.sub.v1.srate = hio_read16l(f);\t/* Sample rate? */\n\t\tsfh.sub.v1.tempo = hio_read8(f);\t/* Playback tempo */\n\t\tif ((sfh.sub.v1.channels = hio_read8(f)) != 4) {\t/* Number of channels */\n\t\t\tD_(D_CRIT \"Wrong number of sound channels: %d\", sfh.sub.v1.channels);\n\t\t\treturn -1;\n\t\t}\n\t\tif ((sfh.sub.v1.psize = hio_read16l(f)) != 64) {\t/* Pattern size */\n\t\t\tD_(D_CRIT \"Wrong number of rows per pattern: %d\", sfh.sub.v1.psize);\n\t\t\treturn -1;\n\t\t}\n\t\tsfh.sub.v1.rsvd2 = hio_read16l(f);\t/* Reserved */\n\t\tsfh.sub.v1.skip = hio_read16l(f);\t/* Bytes to skip */\n\t\thio_seek(f, sfh.sub.v1.skip, SEEK_CUR);\t/* Skip bytes */\n\t\tmod->chn = sfh.sub.v1.channels;\n\t\tmod->pat = sfh.sub.v1.patnum;\n\t\tmod->spd = (version != 100) ? LSN(sfh.sub.v1.tempo / 10) : LSN(sfh.sub.v1.tempo);\n\t\tmod->ins = sfh.sub.v1.insnum;\n\t\tmod->len = sfh.sub.v1.ordnum;\n\t}\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\thio_read(sfh.ins[i].name, 12, 1, f);\t/* Instrument name */\n\t\tsfh.ins[i].id = hio_read8(f);\t\t/* Id=0 */\n\t\tsfh.ins[i].idisk = hio_read8(f);\t/* Instrument disk */\n\t\tsfh.ins[i].rsvd1 = hio_read16l(f);\t/* Reserved */\n\t\tsfh.ins[i].length = hio_read16l(f);\t/* Sample length */\n\t\tsfh.ins[i].loopbeg = hio_read16l(f);\t/* Loop begin */\n\t\tsfh.ins[i].loopend = hio_read16l(f);\t/* Loop end */\n\t\tsfh.ins[i].volume = hio_read8(f);\t/* Playback volume */\n\t\tsfh.ins[i].rsvd2 = hio_read8(f);\t/* Reserved */\n\t\tsfh.ins[i].c2spd = hio_read16l(f);\t/* C4 speed */\n\t\tsfh.ins[i].rsvd3 = hio_read32l(f);\t/* Reserved */\n\t\tsfh.ins[i].paralen = hio_read16l(f);\t/* Length in paragraphs */\n\t}\n\n\tif (hio_error(f)) {\n\t\treturn -1;\n\t}\n\n\tmod->smp = mod->ins;\n\tm->c4rate = C4_NTSC_RATE;\n\n\tlibxmp_copy_adjust(mod->name, sfh.name, 20);\n\n\tif (!sfh.magic[0] || !strncmp((char *)sfh.magic, \"PCSTV\", 5) || !strncmp((char *)sfh.magic, \"!Scream!\", 8))\n\t\tlibxmp_set_type(m, \"Scream Tracker %d.%02d\", sfh.vermaj, sfh.vermin);\n\telse if (!strncmp((char *)sfh.magic, \"SWavePro\", 8))\n\t\tlibxmp_set_type(m, \"SoundWave Pro %d.%02d\", sfh.vermaj, sfh.vermin);\n\telse\n\t\tlibxmp_copy_adjust(mod->type, sfh.magic, 8);\n\n\tMODULE_INFO();\n\n\tif (libxmp_init_instrument(m) < 0)\n\t\treturn -1;\n\n\t/* Read and convert instruments and samples */\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t\t\treturn -1;\n\n\t\tmod->xxs[i].len = sfh.ins[i].length;\n\t\tmod->xxs[i].lps = sfh.ins[i].loopbeg;\n\t\tmod->xxs[i].lpe = sfh.ins[i].loopend;\n\t\tif (mod->xxs[i].lpe == 0xffff)\n\t\t\tmod->xxs[i].lpe = 0;\n\t\tmod->xxs[i].flg = mod->xxs[i].lpe > 0 ? XMP_SAMPLE_LOOP : 0;\n\t\tmod->xxi[i].sub[0].vol = sfh.ins[i].volume;\n\t\tmod->xxi[i].sub[0].pan = 0x80;\n\t\tmod->xxi[i].sub[0].sid = i;\n\n\t\tif (mod->xxs[i].len > 0)\n\t\t\tmod->xxi[i].nsm = 1;\n\n\t\tlibxmp_instrument_name(mod, i, sfh.ins[i].name, 12);\n\n\t\tD_(D_INFO \"[%2X] %-14.14s %04x %04x %04x %c V%02x %5d\", i,\n\t\t   mod->xxi[i].name, mod->xxs[i].len, mod->xxs[i].lps,\n\t\t   mod->xxs[i].lpe,\n\t\t   mod->xxs[i].flg & XMP_SAMPLE_LOOP ? 'L' : ' ',\n\t\t   mod->xxi[i].sub[0].vol, sfh.ins[i].c2spd);\n\n\t\tlibxmp_c2spd_to_note(sfh.ins[i].c2spd, &mod->xxi[i].sub[0].xpo,\n\t\t\t      &mod->xxi[i].sub[0].fin);\n\t}\n\n\tif (hio_read(mod->xxo, 1, mod->len, f) < mod->len)\n\t\treturn -1;\n\n\tfor (i = 0; i < mod->len; i++) {\n\t\tif (mod->xxo[i] >= 99) {\n\t\t\tbreak;\n\t\t}\n\t\t/* Patterns >= the pattern count are valid blank patterns.\n\t\t * Examples: jimmy.stm, Rauno/dogs.stm, Skaven/hevijanis istu maas.stm.\n\t\t * Patterns >= 64 have undefined behavior in Screamtracker 2.\n\t\t */\n\t\tif (mod->xxo[i] >= mod->pat) {\n\t\t\tmod->xxo[i] = mod->pat;\n\t\t\tblank_pattern = 1;\n\t\t}\n\t}\n\tstored_patterns = mod->pat;\n\tif(blank_pattern)\n\t\tmod->pat++;\n\n\tmod->trk = mod->pat * mod->chn;\n\tmod->len = i;\n\n\tD_(D_INFO \"Module length: %d\", mod->len);\n\n\tif (libxmp_init_pattern(mod) < 0)\n\t\treturn -1;\n\n\t/* Read and convert patterns */\n\tD_(D_INFO \"Stored patterns: %d\", stored_patterns);\n\n\tif(blank_pattern) {\n\t\tif (libxmp_alloc_pattern_tracks(mod, stored_patterns, 64) < 0)\n\t\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < stored_patterns; i++) {\n\t\tif (libxmp_alloc_pattern_tracks(mod, i, 64) < 0)\n\t\t\treturn -1;\n\n\t\tif (hio_error(f))\n\t\t\treturn -1;\n\n\t\tfor (j = 0; j < 64; j++) {\n\t\t\tfor (k = 0; k < mod->chn; k++) {\n\t\t\t\tevent = &EVENT(i, k, j);\n\t\t\t\tb = hio_read8(f);\n\t\t\t\tif (b == 251 || b == 252)\n\t\t\t\t\tcontinue; /* Empty note */\n\n\t\t\t\tif (b == 253) {\n\t\t\t\t\tevent->note = XMP_KEY_OFF;\n\t\t\t\t\tcontinue;  /* Key off */\n\t\t\t\t}\n\n\t\t\t\tif (b == 254)\n\t\t\t\t\tevent->note = XMP_KEY_OFF;\n\t\t\t\telse if (b == 255)\n\t\t\t\t\tevent->note = 0;\n\t\t\t\telse\n\t\t\t\t\tevent->note = 1 + LSN(b) + 12 * (3 + MSN(b));\n\n\t\t\t\tb = hio_read8(f);\n\t\t\t\tevent->vol = b & 0x07;\n\t\t\t\tevent->ins = (b & 0xf8) >> 3;\n\n\t\t\t\tb = hio_read8(f);\n\t\t\t\tevent->vol += (b & 0xf0) >> 1;\n\t\t\t\tif (version >= 200) {\n\t\t\t\t\tevent->vol = (event->vol > 0x40) ? 0 : event->vol + 1;\n\t\t\t\t} else {\n\t\t\t\t\tif (event->vol > 0) {\n\t\t\t\t\t\tevent->vol = (event->vol > 0x40) ? 1 : event->vol + 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tevent->fxt = fx[LSN(b)];\n\t\t\t\tevent->fxp = hio_read8(f);\n\t\t\t\tswitch (event->fxt) {\n\t\t\t\tcase FX_SPEED:\n\t\t\t\t\tevent->fxp = (version < 221) ? LSN(event->fxp / 10) : MSN(event->fxp);\n\t\t\t\t\tbreak;\n\t\t\t\tcase FX_NONE:\n\t\t\t\t\tevent->fxp = event->fxt = 0;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Read samples */\n\tD_(D_INFO \"Stored samples: %d\", mod->smp);\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tif (!sfh.ins[i].volume || !sfh.ins[i].length) {\n\t\t\tmod->xxi[i].nsm = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (sfh.type == STM_TYPE_SONG) {\n\t\t\tHIO_HANDLE *s;\n\t\t\tchar sn[XMP_MAXPATH];\n\t\t\tchar tmpname[32];\n\t\t\tconst char *instname = mod->xxi[i].name;\n\n\t\t\tif (libxmp_copy_name_for_fopen(tmpname, instname, 32) != 0)\n\t\t\t\tcontinue;\n\n\t\t\tif (!libxmp_find_instrument_file(m, sn, sizeof(sn), tmpname))\n\t\t\t\tcontinue;\n\n\t\t\tif ((s = hio_open(sn, \"rb\")) == NULL)\n\t\t\t\tcontinue;\n\n\t\t\tif (libxmp_load_sample(m, s, SAMPLE_FLAG_UNS, &mod->xxs[i], NULL) < 0) {\n\t\t\t\thio_close(s);\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\thio_close(s);\n\t\t} else {\n\t\t\thio_seek(f, start + (sfh.ins[i].rsvd1 << 4), SEEK_SET);\n\t\t\tif (libxmp_load_sample(m, f, 0, &mod->xxs[i], NULL) < 0)\n\t\t\t\treturn -1;\n\t\t}\n\t}\n\n\tm->quirk |= QUIRK_VSALL | QUIRKS_ST3;\n\tm->read_event_type = READ_EVENT_ST3;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/ult_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/* Based on the format description by FreeJack of The Elven Nation\n *\n * The loader recognizes four subformats:\n * - MAS_UTrack_V001: Ultra Tracker version < 1.4\n * - MAS_UTrack_V002: Ultra Tracker version 1.4\n * - MAS_UTrack_V003: Ultra Tracker version 1.5\n * - MAS_UTrack_V004: Ultra Tracker version 1.6\n */\n\n#include \"loader.h\"\n#include \"../period.h\"\n\n\nstatic int ult_test (HIO_HANDLE *, char *, const int);\nstatic int ult_load (struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_ult = {\n    \"Ultra Tracker\",\n    ult_test,\n    ult_load\n};\n\nstatic int ult_test(HIO_HANDLE *f, char *t, const int start)\n{\n    char buf[15];\n\n    if (hio_read(buf, 1, 15, f) < 15)\n\treturn -1;\n\n    if (memcmp(buf, \"MAS_UTrack_V00\", 14))\n\treturn -1;\n\n    if (buf[14] < '1' || buf[14] > '4')\n\treturn -1;\n\n    libxmp_read_title(f, t, 32);\n\n    return 0;\n}\n\n\nstruct ult_header {\n    uint8 magic[15];\t\t/* 'MAS_UTrack_V00x' */\n    uint8 name[32];\t\t/* Song name */\n    uint8 msgsize;\t\t/* ver < 1.4: zero */\n};\n\nstruct ult_header2 {\n    uint8 order[256];\t\t/* Orders */\n    uint8 channels;\t\t/* Number of channels - 1 */\n    uint8 patterns;\t\t/* Number of patterns - 1 */\n};\n\nstruct ult_instrument {\n    uint8 name[32];\t\t/* Instrument name */\n    uint8 dosname[12];\t\t/* DOS file name */\n    uint32 loop_start;\t\t/* Loop start */\n    uint32 loopend;\t\t/* Loop end */\n    uint32 sizestart;\t\t/* Sample size is sizeend - sizestart */\n    uint32 sizeend;\n    uint8 volume;\t\t/* Volume (log; ver >= 1.4 linear) */\n    uint8 bidiloop;\t\t/* Sample loop flags */\n    int16 finetune;\t\t/* Finetune */\n    uint16 c2spd;\t\t/* C2 frequency */\n};\n\nstruct ult_event {\n    /* uint8 note; */\n    uint8 ins;\n    uint8 fxt;\t\t\t/* MSN = fxt, LSN = f2t */\n    uint8 f2p;\t\t\t/* Secondary comes first -- little endian! */\n    uint8 fxp;\n};\n\n\nstatic void ult_translate_effect(uint8 *fxt, uint8 *fxp)\n{\n    switch (*fxt) {\n    case 0x03:\t\t\t/* Tone portamento */\n\t*fxt = FX_ULT_TPORTA;\n\tbreak;\n    case 0x05:\t\t\t/* 'Special' effect */\n    case 0x06:\t\t\t/* Reserved */\n\t*fxt = *fxp = 0;\n\tbreak;\n    case 0x0b:\t\t\t/* Pan */\n\t*fxt = FX_SETPAN;\n\t*fxp <<= 4;\n\tbreak;\n    case 0x09:\t\t\t/* Sample offset */\n/* TODO: fine sample offset (requires new effect or 2 more effect lanes) */\n\t*fxp <<= 2;\n\tbreak;\n    case 0x0f:\t\t\t/* Speed/BPM */\n\t/* 00:    default speed (6)/BPM (125)\n\t * 01-2f: set speed\n\t * 30-ff: set BPM\n\t */\n\t*fxt = FX_ULT_TEMPO;\n\tbreak;\n    }\n}\n\nstatic int ult_load(struct module_data *m, HIO_HANDLE *f, const int start)\n{\n    struct xmp_module *mod = &m->mod;\n    int i, j, k, ver, cnt;\n    struct xmp_event *event;\n    struct ult_header ufh;\n    struct ult_header2 ufh2;\n    struct ult_instrument uih;\n    struct ult_event ue;\n    const char *verstr[4] = { \"< 1.4\", \"1.4\", \"1.5\", \"1.6\" };\n\n    uint8 x8;\n\n    LOAD_INIT();\n\n    hio_read(ufh.magic, 15, 1, f);\n    hio_read(ufh.name, 32, 1, f);\n    ufh.msgsize = hio_read8(f);\n\n    ver = ufh.magic[14] - '0';\n\n    strncpy(mod->name, (char *)ufh.name, 32);\n    mod->name[32] = '\\0';\n    libxmp_set_type(m, \"Ultra Tracker %s ULT V%03d\", verstr[ver - 1], ver);\n\n    m->c4rate = C4_NTSC_RATE;\n\n    MODULE_INFO();\n\n    if (ufh.msgsize > 0) {\n\tif ((m->comment = (char *)malloc(ufh.msgsize * 33)) != NULL) {\n\t    char *pos = m->comment;\n\t    for (i = 0; i < (int)ufh.msgsize; i++) {\n\t\tif (hio_read(pos, 1, 32, f) < 32)\n\t\t    return -1;\n\t\tpos[32] = '\\n';\n\t\tpos += 33;\n\t    }\n\t    *(--pos) = '\\0';\n\t} else {\n\t    hio_seek(f, ufh.msgsize * 32, SEEK_CUR);\n\t}\n    }\n\n    mod->ins = mod->smp = hio_read8(f);\n    /* mod->flg |= XXM_FLG_LINEAR; */\n\n    /* Read and convert instruments */\n\n    if (libxmp_init_instrument(m) < 0)\n\treturn -1;\n\n    D_(D_INFO \"Instruments: %d\", mod->ins);\n\n    for (i = 0; i < mod->ins; i++) {\n\tif (libxmp_alloc_subinstrument(mod, i, 1) < 0)\n\t    return -1;\n\n\thio_read(uih.name, 32, 1, f);\n\thio_read(uih.dosname, 12, 1, f);\n\tuih.loop_start = hio_read32l(f);\n\tuih.loopend = hio_read32l(f);\n\tuih.sizestart = hio_read32l(f);\n\tuih.sizeend = hio_read32l(f);\n\tuih.volume = hio_read8(f);\n\tuih.bidiloop = hio_read8(f);\n\tuih.c2spd = (ver >= 4) ? hio_read16l(f) : 0; /* Incorrect in ult_form.txt */\n\tuih.finetune = hio_read16l(f);\n\tif (hio_error(f)) {\n\t    D_(D_CRIT \"read error at instrument %d\", i);\n\t    return -1;\n\t}\n\n\t/* Sanity check:\n\t * \"[SizeStart] seems to tell UT how to load the sample into the GUS's\n\t * onboard memory.\" The maximum supported GUS RAM is 16 MB (PnP).\n\t * Samples also can't cross 256k boundaries. In practice it seems like\n\t * nothing ever goes over 1 MB, the maximum on most GUS cards.\n\t */\n\tif (uih.sizestart > uih.sizeend || uih.sizeend > (16 << 20) ||\n\t    uih.sizeend - uih.sizestart > (256 << 10)) {\n\t    D_(D_CRIT \"invalid sample %d sizestart/sizeend\", i);\n\t    return -1;\n\t}\n\n\tmod->xxs[i].len = uih.sizeend - uih.sizestart;\n\tmod->xxs[i].lps = uih.loop_start;\n\tmod->xxs[i].lpe = uih.loopend;\n\n\tif (mod->xxs[i].len > 0)\n\t    mod->xxi[i].nsm = 1;\n\n\t/* BiDi Loop : (Bidirectional Loop)\n\t *\n\t * UT takes advantage of the Gus's ability to loop a sample in\n\t * several different ways. By setting the Bidi Loop, the sample can\n\t * be played forward or backwards, looped or not looped. The Bidi\n\t * variable also tracks the sample resolution (8 or 16 bit).\n\t *\n\t * The following table shows the possible values of the Bidi Loop.\n\t * Bidi = 0  : No looping, forward playback,  8bit sample\n\t * Bidi = 4  : No Looping, forward playback, 16bit sample\n\t * Bidi = 8  : Loop Sample, forward playback, 8bit sample\n\t * Bidi = 12 : Loop Sample, forward playback, 16bit sample\n\t * Bidi = 24 : Loop Sample, reverse playback 8bit sample\n\t * Bidi = 28 : Loop Sample, reverse playback, 16bit sample\n\t */\n\n\t/* Claudio's note: I'm ignoring reverse playback for samples */\n\n\tswitch (uih.bidiloop) {\n\tcase 20:\t\t/* Type 20 is in seasons.ult */\n\tcase 4:\n\t    mod->xxs[i].flg = XMP_SAMPLE_16BIT;\n\t    break;\n\tcase 8:\n\t    mod->xxs[i].flg = XMP_SAMPLE_LOOP;\n\t    break;\n\tcase 12:\n\t    mod->xxs[i].flg = XMP_SAMPLE_16BIT | XMP_SAMPLE_LOOP;\n\t    break;\n\tcase 24:\n\t    mod->xxs[i].flg = XMP_SAMPLE_LOOP | XMP_SAMPLE_LOOP_REVERSE;\n\t    break;\n\tcase 28:\n\t    mod->xxs[i].flg = XMP_SAMPLE_16BIT | XMP_SAMPLE_LOOP | XMP_SAMPLE_LOOP_REVERSE;\n\t    break;\n\t}\n\n/* TODO: Add logarithmic volume support */\n\tmod->xxi[i].sub[0].vol = uih.volume;\n\tmod->xxi[i].sub[0].pan = 0x80;\n\tmod->xxi[i].sub[0].sid = i;\n\n\tlibxmp_instrument_name(mod, i, uih.name, 24);\n\n\tD_(D_INFO \"[%2X] %-32.32s %05x%c%05x %05x %c V%02x F%04x %5d\",\n\t\ti, mod->xxi[i].name, mod->xxs[i].len,\n\t\tmod->xxs[i].flg & XMP_SAMPLE_16BIT ? '+' : ' ',\n\t\tmod->xxs[i].lps, mod->xxs[i].lpe,\n\t\tmod->xxs[i].flg & XMP_SAMPLE_LOOP ? 'L' : ' ',\n\t\tmod->xxi[i].sub[0].vol, uih.finetune, uih.c2spd);\n\n\tif (ver > 3)\n\t    libxmp_c2spd_to_note(uih.c2spd, &mod->xxi[i].sub[0].xpo, &mod->xxi[i].sub[0].fin);\n    }\n\n    hio_read(ufh2.order, 256, 1, f);\n    ufh2.channels = hio_read8(f);\n    ufh2.patterns = hio_read8(f);\n\n    if (hio_error(f)) {\n\treturn -1;\n    }\n\n    for (i = 0; i < 256; i++) {\n\tif (ufh2.order[i] == 0xff)\n\t    break;\n\tmod->xxo[i] = ufh2.order[i];\n    }\n    mod->len = i;\n    mod->chn = ufh2.channels + 1;\n    mod->pat = ufh2.patterns + 1;\n    mod->spd = 6;\n    mod->bpm = 125;\n    mod->trk = mod->chn * mod->pat;\n\n    /* Sanity check */\n    if (mod->chn > XMP_MAX_CHANNELS) {\n\treturn -1;\n    }\n\n    for (i = 0; i < mod->chn; i++) {\n\tif (ver >= 3) {\n\t    x8 = hio_read8(f);\n\t    mod->xxc[i].pan = 255 * x8 / 15;\n\t} else {\n\t    mod->xxc[i].pan = DEFPAN((((i + 1) / 2) % 2) * 0xff); /* ??? */\n\t}\n    }\n\n    if (libxmp_init_pattern(mod) < 0)\n\treturn -1;\n\n    /* Read and convert patterns */\n\n    D_(D_INFO \"Stored patterns: %d\", mod->pat);\n\n    /* Events are stored by channel */\n    for (i = 0; i < mod->pat; i++) {\n\tif (libxmp_alloc_pattern_tracks(mod, i, 64) < 0)\n\t    return -1;\n    }\n\n    for (i = 0; i < mod->chn; i++) {\n\tfor (j = 0; j < 64 * mod->pat; ) {\n\t    cnt = 1;\n\t    x8 = hio_read8(f);\t\t/* Read note or repeat code (0xfc) */\n\t    if (x8 == 0xfc) {\n\t\tcnt = hio_read8(f);\t\t/* Read repeat count */\n\t\tx8 = hio_read8(f);\t\t/* Read note */\n\t    }\n\t    if (hio_read(&ue, 1, 4, f) < 4) {\t/* Read rest of the event */\n\t\tD_(D_CRIT \"read error at channel %d pos %d\", i, j);\n\t\treturn -1;\n\t    }\n\n\t    if (cnt == 0)\n\t\tcnt++;\n\n\t    if (j + cnt > 64 * mod->pat) {\n\t\tD_(D_WARN \"invalid track data packing\");\n\t\treturn -1;\n\t    }\n\n\t    for (k = 0; k < cnt; k++, j++) {\n\t\tevent = &EVENT (j >> 6, i , j & 0x3f);\n\t\tmemset(event, 0, sizeof (struct xmp_event));\n\t\tif (x8)\n\t\t    event->note = x8 + 36;\n\t\tevent->ins = ue.ins;\n\t\tevent->fxt = MSN (ue.fxt);\n\t\tevent->f2t = LSN (ue.fxt);\n\t\tevent->fxp = ue.fxp;\n\t\tevent->f2p = ue.f2p;\n\n\t\tult_translate_effect(&event->fxt, &event->fxp);\n\t\tult_translate_effect(&event->f2t, &event->f2p);\n\t    }\n\t}\n    }\n\n    D_(D_INFO \"Stored samples: %d\", mod->smp);\n\n    for (i = 0; i < mod->ins; i++) {\n\tif (!mod->xxs[i].len)\n\t    continue;\n\tif (libxmp_load_sample(m, f, 0, &mod->xxs[i], NULL) < 0)\n\t    return -1;\n    }\n\n    m->volbase = 0x100;\n\n    return 0;\n}\n\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/xm.h",
    "content": "#ifndef LIBXMP_LOADERS_XM_H\n#define LIBXMP_LOADERS_XM_H\n\n#define XM_EVENT_PACKING 0x80\n#define XM_EVENT_PACK_MASK 0x7f\n#define XM_EVENT_NOTE_FOLLOWS 0x01\n#define XM_EVENT_INSTRUMENT_FOLLOWS 0x02\n#define XM_EVENT_VOLUME_FOLLOWS 0x04\n#define XM_EVENT_FXTYPE_FOLLOWS 0x08\n#define XM_EVENT_FXPARM_FOLLOWS 0x10\n#define XM_LINEAR_FREQ 0x01\n#define XM_LOOP_MASK 0x03\n#define XM_LOOP_NONE 0\n#define XM_LOOP_FORWARD 1\n#define XM_LOOP_PINGPONG 2\n#define XM_SAMPLE_16BIT 0x10\n#define XM_SAMPLE_STEREO 0x20\n#define XM_ENVELOPE_ON 0x01\n#define XM_ENVELOPE_SUSTAIN 0x02\n#define XM_ENVELOPE_LOOP 0x04\n#define XM_LINEAR_PERIOD_MODE 0x01\n\nstruct xm_file_header {\n\tuint8 id[17];\t\t/* ID text: \"Extended module: \" */\n\tuint8 name[20];\t\t/* Module name, padded with zeroes */\n\tuint8 doseof;\t\t/* 0x1a */\n\tuint8 tracker[20];\t/* Tracker name */\n\tuint16 version;\t\t/* Version number, minor-major */\n\tuint32 headersz;\t/* Header size */\n\tuint16 songlen;\t\t/* Song length (in pattern order table) */\n\tuint16 restart;\t\t/* Restart position */\n\tuint16 channels;\t/* Number of channels (2,4,6,8,10,...,32) */\n\tuint16 patterns;\t/* Number of patterns (max 256) */\n\tuint16 instruments;\t/* Number of instruments (max 128) */\n\tuint16 flags;\t\t/* bit 0: 0=Amiga freq table, 1=Linear */\n\tuint16 tempo;\t\t/* Default tempo */\n\tuint16 bpm;\t\t/* Default BPM */\n\tuint8 order[256];\t/* Pattern order table */\n};\n\nstruct xm_pattern_header {\n\tuint32 length;\t\t/* Pattern header length */\n\tuint8 packing;\t\t/* Packing type (always 0) */\n\tuint16 rows;\t\t/* Number of rows in pattern (1..256) */\n\tuint16 datasize;\t/* Packed patterndata size */\n};\n\nstruct xm_instrument_header {\n\tuint32 size;\t\t/* Instrument size */\n\tuint8 name[22];\t\t/* Instrument name */\n\tuint8 type;\t\t/* Instrument type (always 0) */\n\tuint16 samples;\t\t/* Number of samples in instrument */\n\tuint32 sh_size;\t\t/* Sample header size */\n};\n\nstruct xm_instrument {\n\tuint8 sample[96];\t/* Sample number for all notes */\n\tuint16 v_env[24];\t/* Points for volume envelope */\n\tuint16 p_env[24];\t/* Points for panning envelope */\n\tuint8 v_pts;\t\t/* Number of volume points */\n\tuint8 p_pts;\t\t/* Number of panning points */\n\tuint8 v_sus;\t\t/* Volume sustain point */\n\tuint8 v_start;\t\t/* Volume loop start point */\n\tuint8 v_end;\t\t/* Volume loop end point */\n\tuint8 p_sus;\t\t/* Panning sustain point */\n\tuint8 p_start;\t\t/* Panning loop start point */\n\tuint8 p_end;\t\t/* Panning loop end point */\n\tuint8 v_type;\t\t/* Bit 0: On; 1: Sustain; 2: Loop */\n\tuint8 p_type;\t\t/* Bit 0: On; 1: Sustain; 2: Loop */\n\tuint8 y_wave;\t\t/* Vibrato waveform */\n\tuint8 y_sweep;\t\t/* Vibrato sweep */\n\tuint8 y_depth;\t\t/* Vibrato depth */\n\tuint8 y_rate;\t\t/* Vibrato rate */\n\tuint16 v_fade;\t\t/* Volume fadeout */\n#if 0\n\tuint8 reserved[22];\t/* Reserved; 2 bytes in specs, 22 in 1.04 */\n#endif\n};\n\nstruct xm_sample_header {\n\tuint32 length;\t\t/* Sample length */\n\tuint32 loop_start;\t/* Sample loop start */\n\tuint32 loop_length;\t/* Sample loop length */\n\tuint8 volume;\t\t/* Volume */\n\tint8 finetune;\t\t/* Finetune (signed byte -128..+127) */\n\tuint8 type;\t\t/* 0=No loop,1=Fwd loop,2=Ping-pong,16-bit */\n\tuint8 pan;\t\t/* Panning (0-255) */\n\tint8 relnote;\t\t/* Relative note number (signed byte) */\n\tuint8 reserved;\t\t/* Reserved */\n\tuint8 name[22];\t\t/* Sample_name */\n};\n\nstruct xm_event {\n\tuint8 note;\t\t/* Note (0-71, 0 = C-0) */\n\tuint8 instrument;\t/* Instrument (0-128) */\n\tuint8 volume;\t\t/* Volume column byte */\n\tuint8 fx_type;\t\t/* Effect type */\n\tuint8 fx_parm;\t\t/* Effect parameter */\n};\n\n#endif  /* LIBXMP_LOADERS_XM_H */\n"
  },
  {
    "path": "contrib/libxmp/src/loaders/xm_load.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/*\n * Fri, 26 Jun 1998 17:45:59 +1000  Andrew Leahy <alf@cit.nepean.uws.edu.au>\n * Finally got it working on the DEC Alpha running DEC UNIX! In the pattern\n * reading loop I found I was getting \"0\" for (p-patbuf) and \"0\" for\n * xph.datasize, the next if statement (where it tries to read the patbuf)\n * would then cause a seg_fault.\n *\n * Sun Sep 27 12:07:12 EST 1998  Claudio Matsuoka <claudio@pos.inf.ufpr.br>\n * Extended Module 1.02 stores data in a different order, we must handle\n * this accordingly. MAX_SAMP used as a workaround to check the number of\n * samples recognized by the player.\n */\n\n#include \"loader.h\"\n#include \"xm.h\"\n#if 0\n#include \"vorbis.h\"\n#endif\n\nstatic int xm_test(HIO_HANDLE *, char *, const int);\nstatic int xm_load(struct module_data *, HIO_HANDLE *, const int);\n\nconst struct format_loader libxmp_loader_xm = {\n\t\"Fast Tracker II\",\n\txm_test,\n\txm_load\n};\n\nstatic int xm_test(HIO_HANDLE *f, char *t, const int start)\n{\n\tchar buf[20];\n\n\tif (hio_read(buf, 1, 17, f) < 17)\t/* ID text */\n\t\treturn -1;\n\n\tif (memcmp(buf, \"Extended Module: \", 17))\n\t\treturn -1;\n\n\tlibxmp_read_title(f, t, 20);\n\n\treturn 0;\n}\n\nstatic int load_xm_pattern(struct module_data *m, int num, int version,\n\t\t\t   uint8 *patbuf, HIO_HANDLE *f)\n{\n\tconst int headsize = version > 0x0102 ? 9 : 8;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xm_pattern_header xph;\n\tstruct xmp_event *event;\n\tuint8 *pat, b;\n\tint j, k, r;\n\tint size, size_read;\n\n\txph.length = hio_read32l(f);\n\txph.packing = hio_read8(f);\n\txph.rows = version > 0x0102 ? hio_read16l(f) : hio_read8(f) + 1;\n\n\t/* Sanity check */\n\tif (xph.rows > 256) {\n\t\tgoto err;\n\t}\n\n\txph.datasize = hio_read16l(f);\n\thio_seek(f, xph.length - headsize, SEEK_CUR);\n\tif (hio_error(f)) {\n\t\tgoto err;\n\t}\n\n\tr = xph.rows;\n\tif (r == 0) {\n\t\tr = 0x100;\n\t}\n\n\tif (libxmp_alloc_pattern_tracks(mod, num, r) < 0) {\n\t\tgoto err;\n\t}\n\n\tif (xph.datasize == 0) {\n\t\treturn 0;\n\t}\n\n\tsize = xph.datasize;\n\tpat = patbuf;\n\n\tsize_read = hio_read(patbuf, 1, size, f);\n\tif (size_read < size) {\n\t\tmemset(patbuf + size_read, 0, size - size_read);\n\t}\n\n\tfor (j = 0; j < r; j++) {\n\t\tfor (k = 0; k < mod->chn; k++) {\n\t\t\t/* Some XMs have cleanly truncated patterns. See:\n\t\t\t * Balrog/f0rtify.xm; Decayer-9/purification.xm;\n\t\t\t * Falcon (PL)/eaten vinyl.xm; Headcrasher/microcosm.xm;\n\t\t\t * Jazztiz/ta-da-da-da.xm; Jisemdu/smile.xm;\n\t\t\t * Markus Plomgren/cool jazzy jeff!!!.xm;\n\t\t\t * Orange/optical.xm; Skyraver/spirit of life.xm;\n\t\t\t * Sonic (UK)'s atomic_subculture.xm, luvdup.xm,\n\t\t\t * phuture.xm; Teemu/speed.xm; Warhawk/anaconda.xm.\n\t\t\t */\n\t\t\tif ((pat - patbuf) == (ptrdiff_t)xph.datasize) {\n\t\t\t\tD_(D_WARN \"early pattern %d end (row:%d/%d, ch:%d/%d)\",\n\t\t\t\t   num, j, r, k, mod->chn\n\t\t\t\t);\n\t\t\t\tgoto early_pattern_end;\n\t\t\t}\n\n\t\t\tevent = &EVENT(num, k, j);\n\n\t\t\tif (--size < 0) {\n\t\t\t\tgoto err;\n\t\t\t}\n\n\t\t\tif ((b = *pat++) & XM_EVENT_PACKING) {\n\t\t\t\tif (b & XM_EVENT_NOTE_FOLLOWS) {\n\t\t\t\t\tif (--size < 0)\n\t\t\t\t\t\tgoto err;\n\t\t\t\t\tevent->note = *pat++;\n\t\t\t\t}\n\t\t\t\tif (b & XM_EVENT_INSTRUMENT_FOLLOWS) {\n\t\t\t\t\tif (--size < 0)\n\t\t\t\t\t\tgoto err;\n\t\t\t\t\tevent->ins = *pat++;\n\t\t\t\t}\n\t\t\t\tif (b & XM_EVENT_VOLUME_FOLLOWS) {\n\t\t\t\t\tif (--size < 0)\n\t\t\t\t\t\tgoto err;\n\t\t\t\t\tevent->vol = *pat++;\n\t\t\t\t}\n\t\t\t\tif (b & XM_EVENT_FXTYPE_FOLLOWS) {\n\t\t\t\t\tif (--size < 0)\n\t\t\t\t\t\tgoto err;\n\t\t\t\t\tevent->fxt = *pat++;\n\t\t\t\t}\n\t\t\t\tif (b & XM_EVENT_FXPARM_FOLLOWS) {\n\t\t\t\t\tif (--size < 0)\n\t\t\t\t\t\tgoto err;\n\t\t\t\t\tevent->fxp = *pat++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tsize -= 4;\n\t\t\t\tif (size < 0)\n\t\t\t\t\tgoto err;\n\t\t\t\tevent->note = b;\n\t\t\t\tevent->ins = *pat++;\n\t\t\t\tevent->vol = *pat++;\n\t\t\t\tevent->fxt = *pat++;\n\t\t\t\tevent->fxp = *pat++;\n\t\t\t}\n\n\t\t\t/* Sanity check */\n\t\t\tswitch (event->fxt) {\n\t\t\tcase 18:\n\t\t\tcase 19:\n\t\t\tcase 22:\n\t\t\tcase 23:\n\t\t\tcase 24:\n\t\t\tcase 26:\n\t\t\tcase 28:\n\t\t\tcase 30:\n\t\t\tcase 31:\n\t\t\tcase 32:\n\t\t\t\tevent->fxt = 0;\n\t\t\t}\n\t\t\tif (event->fxt > 34) {\n\t\t\t\tevent->fxt = 0;\n\t\t\t}\n\n\t\t\tif (event->note == 0x61) {\n\t\t\t\t/* See OpenMPT keyoff+instr.xm test case */\n\t\t\t\tif (event->fxt == 0x0e && MSN(event->fxp) == 0x0d) {\n\t\t\t\t\tevent->note = XMP_KEY_OFF;\n\t\t\t\t} else {\n\t\t\t\t\tevent->note =\n\t\t\t\t\tevent->ins ? XMP_KEY_FADE : XMP_KEY_OFF;\n\t\t\t\t}\n\t\t\t} else if (event->note > 0) {\n\t\t\t\tevent->note += 12;\n\t\t\t}\n\n\t\t\tif (event->fxt == 0x0e) {\n\t\t\t\tif (MSN(event->fxp) == EX_FINETUNE) {\n\t\t\t\t\tunsigned char val = (LSN(event->fxp) - 8) & 0xf;\n\t\t\t\t\tevent->fxp = (EX_FINETUNE << 4) | val;\n\t\t\t\t}\n\t\t\t\tswitch (event->fxp) {\n\t\t\t\tcase 0x43:\n\t\t\t\tcase 0x73:\n\t\t\t\t\tevent->fxp--;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (event->fxt == FX_XF_PORTA && MSN(event->fxp) == 0x09) {\n\t\t\t\t/* Translate MPT hacks */\n\t\t\t\tswitch (LSN(event->fxp)) {\n\t\t\t\tcase 0x0:\t/* Surround off */\n\t\t\t\tcase 0x1:\t/* Surround on */\n\t\t\t\t\tevent->fxt = FX_SURROUND;\n\t\t\t\t\tevent->fxp = LSN(event->fxp);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0xe:\t/* Play forward */\n\t\t\t\tcase 0xf:\t/* Play reverse */\n\t\t\t\t\tevent->fxt = FX_REVERSE;\n\t\t\t\t\tevent->fxp = LSN(event->fxp) - 0xe;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!event->vol) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Volume set */\n\t\t\tif ((event->vol >= 0x10) && (event->vol <= 0x50)) {\n\t\t\t\tevent->vol -= 0x0f;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Volume column effects */\n\t\t\tswitch (event->vol >> 4) {\n\t\t\tcase 0x06:\t/* Volume slide down */\n\t\t\t\tevent->f2t = FX_VOLSLIDE_2;\n\t\t\t\tevent->f2p = event->vol - 0x60;\n\t\t\t\tbreak;\n\t\t\tcase 0x07:\t/* Volume slide up */\n\t\t\t\tevent->f2t = FX_VOLSLIDE_2;\n\t\t\t\tevent->f2p = (event->vol - 0x70) << 4;\n\t\t\t\tbreak;\n\t\t\tcase 0x08:\t/* Fine volume slide down */\n\t\t\t\tevent->f2t = FX_EXTENDED;\n\t\t\t\tevent->f2p =\n\t\t\t\t(EX_F_VSLIDE_DN << 4) | (event->vol - 0x80);\n\t\t\t\tbreak;\n\t\t\tcase 0x09:\t/* Fine volume slide up */\n\t\t\t\tevent->f2t = FX_EXTENDED;\n\t\t\t\tevent->f2p =\n\t\t\t\t(EX_F_VSLIDE_UP << 4) | (event->vol - 0x90);\n\t\t\t\tbreak;\n\t\t\tcase 0x0a:\t/* Set vibrato speed */\n\t\t\t\tevent->f2t = FX_VIBRATO;\n\t\t\t\tevent->f2p = (event->vol - 0xa0) << 4;\n\t\t\t\tbreak;\n\t\t\tcase 0x0b:\t/* Vibrato */\n\t\t\t\tevent->f2t = FX_VIBRATO;\n\t\t\t\tevent->f2p = event->vol - 0xb0;\n\t\t\t\tbreak;\n\t\t\tcase 0x0c:\t/* Set panning */\n\t\t\t\tevent->f2t = FX_SETPAN;\n\t\t\t\tevent->f2p = (event->vol - 0xc0) << 4;\n\t\t\t\tbreak;\n\t\t\tcase 0x0d:\t/* Pan slide left */\n\t\t\t\tevent->f2t = FX_PANSL_NOMEM;\n\t\t\t\tevent->f2p = (event->vol - 0xd0) << 4;\n\t\t\t\tbreak;\n\t\t\tcase 0x0e:\t/* Pan slide right */\n\t\t\t\tevent->f2t = FX_PANSL_NOMEM;\n\t\t\t\tevent->f2p = event->vol - 0xe0;\n\t\t\t\tbreak;\n\t\t\tcase 0x0f:\t/* Tone portamento */\n\t\t\t\tevent->f2t = FX_TONEPORTA;\n\t\t\t\tevent->f2p = (event->vol - 0xf0) << 4;\n\n\t\t\t\t/* From OpenMPT TonePortamentoMemory.xm:\n\t\t\t\t* \"Another nice bug (...) is the combination of both\n\t\t\t\t*  portamento commands (Mx and 3xx) in the same cell:\n\t\t\t\t*  The 3xx parameter is ignored completely, and the Mx\n\t\t\t\t*  parameter is doubled. (M2 3FF is the same as M4 000)\n\t\t\t\t*/\n\t\t\t\tif (event->fxt == FX_TONEPORTA\n\t\t\t\t|| event->fxt == FX_TONE_VSLIDE) {\n\t\t\t\t\tif (event->fxt == FX_TONEPORTA) {\n\t\t\t\t\t\tevent->fxt = 0;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tevent->fxt = FX_VOLSLIDE;\n\t\t\t\t\t}\n\t\t\t\t\tevent->fxp = 0;\n\n\t\t\t\t\tif (event->f2p < 0x80) {\n\t\t\t\t\t\tevent->f2p <<= 1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tevent->f2p = 0xff;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t/* From OpenMPT porta-offset.xm:\n\t\t\t\t* \"If there is a portamento command next to an offset\n\t\t\t\t*  command, the offset command is ignored completely. In\n\t\t\t\t*  particular, the offset parameter is not memorized.\"\n\t\t\t\t*/\n\t\t\t\tif (event->fxt == FX_OFFSET\n\t\t\t\t&& event->f2t == FX_TONEPORTA) {\n\t\t\t\t\tevent->fxt = event->fxp = 0;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tevent->vol = 0;\n\t\t}\n\t}\nearly_pattern_end:\n\treturn 0;\n\nerr:\n\treturn -1;\n}\n\nstatic int load_patterns(struct module_data *m, int version, HIO_HANDLE *f)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tuint8 *patbuf;\n\tint i, j;\n\n\tmod->pat++;\n\tif (libxmp_init_pattern(mod) < 0) {\n\t\treturn -1;\n\t}\n\n\tD_(D_INFO \"Stored patterns: %d\", mod->pat - 1);\n\n\tif ((patbuf = (uint8 *) calloc(1, 65536)) == NULL) {\n\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < mod->pat - 1; i++) {\n\t\tif (load_xm_pattern(m, i, version, patbuf, f) < 0) {\n\t\t\tgoto err;\n\t\t}\n\t}\n\n\t/* Alloc one extra pattern */\n\t{\n\t\tint t = i * mod->chn;\n\n\t\tif (libxmp_alloc_pattern(mod, i) < 0) {\n\t\t\tgoto err;\n\t\t}\n\n\t\tmod->xxp[i]->rows = 64;\n\n\t\tif (libxmp_alloc_track(mod, t, 64) < 0) {\n\t\t\tgoto err;\n\t\t}\n\n\t\tfor (j = 0; j < mod->chn; j++) {\n\t\t\tmod->xxp[i]->index[j] = t;\n\t\t}\n\t}\n\n\tfree(patbuf);\n\treturn 0;\n\nerr:\n\tfree(patbuf);\n\treturn -1;\n}\n\n/* Packed structures size */\n#define XM_INST_HEADER_SIZE 29\n#define XM_INST_SIZE 212\n\n/* grass.near.the.house.xm defines 23 samples in instrument 1. FT2 docs\n * specify at most 16. See https://github.com/libxmp/libxmp/issues/168\n * for more details. */\n#define XM_MAX_SAMPLES_PER_INST 32\n\n#if 0\n#define MAGIC_OGGS\t0x4f676753\n\nstatic int is_ogg_sample(HIO_HANDLE *f, struct xmp_sample *xxs)\n{\n\t/* uint32 size; */\n\tuint32 id;\n\n\t/* Sample must be at least 4 bytes long to be an OGG sample.\n\t * Bonnie's Bookstore music.oxm contains zero length samples\n\t * followed immediately by OGG samples. */\n\tif (xxs->len < 4)\n\t\treturn 0;\n\n\t/* size = */ hio_read32l(f);\n\tid = hio_read32b(f);\n\tif (hio_error(f) != 0 || hio_seek(f, -8, SEEK_CUR) < 0)\n\t\treturn 0;\n\n\tif (id != MAGIC_OGGS) {\t\t/* copy input data if not Ogg file */\n\t\treturn 0;\n\t}\n\n\treturn 1;\n}\n\nstatic int oggdec(struct module_data *m, HIO_HANDLE *f, struct xmp_sample *xxs, int len)\n{\n\tint i, n, ch, rate, ret, flags = 0;\n\tuint8 *data;\n\tint16 *pcm16 = NULL;\n\n\tif ((data = (uint8 *)calloc(1, len)) == NULL)\n\t\treturn -1;\n\n\thio_read32b(f);\n\tif (hio_error(f) != 0 || hio_read(data, 1, len - 4, f) != len - 4) {\n\t\tfree(data);\n\t\treturn -1;\n\t}\n\n\tn = stb_vorbis_decode_memory(data, len, &ch, &rate, &pcm16);\n\tfree(data);\n\n\tif (n < 0 || ch != 1) {\n\t\tfree(pcm16);\n\t\treturn -1;\n\t}\n\n\tif ((xxs->flg & XMP_SAMPLE_16BIT) == 0 && n > 0) {\n\t\tuint8 *pcm = (uint8 *)pcm16;\n\n\t\tfor (i = 0; i < n; i++) {\n\t\t\tpcm[i] = pcm16[i] >> 8;\n\t\t}\n\t\tpcm = (uint8 *)realloc(pcm16, n);\n\t\tif (pcm == NULL) {\n\t\t\tfree(pcm16);\n\t\t\treturn -1;\n\t\t}\n\t\tpcm16 = (int16 *)pcm;\n\t}\n\tif (xxs->flg & XMP_SAMPLE_STEREO) {\n\t\t/* OXM stereo is a single channel non-interleaved stream. */\n\t\tn >>= 1;\n\t}\n\txxs->len = n;\n\n\tflags |= SAMPLE_FLAG_NOLOAD;\n#ifdef WORDS_BIGENDIAN\n\tflags |= SAMPLE_FLAG_BIGEND;\n#endif\n\n\tret = libxmp_load_sample(m, NULL, flags, xxs, pcm16);\n\tfree(pcm16);\n\n\treturn ret;\n}\n#endif\n\nstatic int load_instruments(struct module_data *m, int version, HIO_HANDLE *f)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xm_instrument_header xih;\n\tstruct xm_instrument xi;\n\tstruct xm_sample_header xsh[XM_MAX_SAMPLES_PER_INST];\n\tint sample_num = 0;\n\tlong total_sample_size;\n\tint i, j;\n\tuint8 buf[208];\n\n\tD_(D_INFO \"Instruments: %d\", mod->ins);\n\n\t/* ESTIMATED value! We don't know the actual value at this point */\n\tmod->smp = MAX_SAMPLES;\n\n\tif (libxmp_init_instrument(m) < 0) {\n\t\treturn -1;\n\t}\n\n\tfor (i = 0; i < mod->ins; i++) {\n\t\tlong instr_pos = hio_tell(f);\n\t\tstruct xmp_instrument *xxi = &mod->xxi[i];\n\n\t\t/* Modules converted with MOD2XM 1.0 always say we have 31\n\t\t * instruments, but file may end abruptly before that. Also covers\n\t\t * XMLiTE stripped modules and truncated files. This test will not\n\t\t * work if file has trailing garbage.\n\t\t *\n\t\t * Note: loading 4 bytes past the instrument header to get the\n\t\t * sample header size (if it exists). This is NOT considered to\n\t\t * be part of the instrument header.\n\t\t */\n\t\tif (hio_read(buf, XM_INST_HEADER_SIZE + 4, 1, f) != 1) {\n\t\t\tD_(D_WARN \"short read in instrument header data\");\n\t\t\tbreak;\n\t\t}\n\n\t\txih.size = readmem32l(buf);\t\t/* Instrument size */\n\t\tmemcpy(xih.name, buf + 4, 22);\t\t/* Instrument name */\n\t\txih.type = buf[26];\t\t\t/* Instrument type (always 0) */\n\t\txih.samples = readmem16l(buf + 27);\t/* Number of samples */\n\t\txih.sh_size = readmem32l(buf + 29);\t/* Sample header size */\n\n\t\t/* Sanity check */\n\t\tif ((int)xih.size < XM_INST_HEADER_SIZE) {\n\t\t\tD_(D_CRIT \"instrument %d: instrument header size:%d\", i + 1, xih.size);\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (xih.samples > XM_MAX_SAMPLES_PER_INST || (xih.samples > 0 && xih.sh_size > 0x100)) {\n\t\t\tD_(D_CRIT \"instrument %d: samples:%d sample header size:%d\", i + 1, xih.samples, xih.sh_size);\n\t\t\treturn -1;\n\t\t}\n\n\t\tlibxmp_instrument_name(mod, i, xih.name, 22);\n\n\t\txxi->nsm = xih.samples;\n\n\t\tD_(D_INFO \"instrument:%2X (%s) samples:%2d\", i, xxi->name, xxi->nsm);\n\n\t\tif (xxi->nsm == 0) {\n\t\t\t/* Sample size should be in struct xm_instrument according to\n\t\t\t * the official format description, but FT2 actually puts it in\n\t\t\t * struct xm_instrument header. There's a tracker or converter\n\t\t\t * that follow the specs, so we must handle both cases (see\n\t\t\t * \"Braintomb\" by Jazztiz/ART).\n\t\t\t */\n\n\t\t\t/* Umm, Cyke O'Path <cyker@heatwave.co.uk> sent me a couple of\n\t\t\t * mods (\"Breath of the Wind\" and \"Broken Dimension\") that\n\t\t\t * reserve the instrument data space after the instrument header\n\t\t\t * even if the number of instruments is set to 0. In these modules\n\t\t\t * the instrument header size is marked as 263. The following\n\t\t\t * generalization should take care of both cases.\n\t\t\t */\n\n\t\t\tif (hio_seek(f, (int)xih.size - (XM_INST_HEADER_SIZE + 4), SEEK_CUR) < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (libxmp_alloc_subinstrument(mod, i, xxi->nsm) < 0) {\n\t\t\treturn -1;\n\t\t}\n\n\t\t/* for BoobieSqueezer (see http://boobie.rotfl.at/)\n\t\t * It works pretty much the same way as Impulse Tracker's sample\n\t\t * only mode, where it will strip off the instrument data.\n\t\t */\n\t\tif (xih.size < XM_INST_HEADER_SIZE + XM_INST_SIZE) {\n\t\t\tmemset(&xi, 0, sizeof(struct xm_instrument));\n\t\t\thio_seek(f, xih.size - (XM_INST_HEADER_SIZE + 4), SEEK_CUR);\n\t\t} else {\n\t\t\tuint8 *b = buf;\n\n\t\t\tif (hio_read(buf, 208, 1, f) != 1) {\n\t\t\t\tD_(D_CRIT \"short read in instrument data\");\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tmemcpy(xi.sample, b, 96);\t\t/* Sample map */\n\t\t\tb += 96;\n\n\t\t\tfor (j = 0; j < 24; j++) {\n\t\t\t\txi.v_env[j] = readmem16l(b);\t/* Points for volume envelope */\n\t\t\t\tb += 2;\n\t\t\t}\n\n\t\t\tfor (j = 0; j < 24; j++) {\n\t\t\t\txi.p_env[j] = readmem16l(b);\t/* Points for pan envelope */\n\t\t\t\tb += 2;\n\t\t\t}\n\n\t\t\txi.v_pts = *b++;\t\t/* Number of volume points */\n\t\t\txi.p_pts = *b++;\t\t/* Number of pan points */\n\t\t\txi.v_sus = *b++;\t\t/* Volume sustain point */\n\t\t\txi.v_start = *b++;\t\t/* Volume loop start point */\n\t\t\txi.v_end = *b++;\t\t/* Volume loop end point */\n\t\t\txi.p_sus = *b++;\t\t/* Pan sustain point */\n\t\t\txi.p_start = *b++;\t\t/* Pan loop start point */\n\t\t\txi.p_end = *b++;\t\t/* Pan loop end point */\n\t\t\txi.v_type = *b++;\t\t/* Bit 0:On 1:Sustain 2:Loop */\n\t\t\txi.p_type = *b++;\t\t/* Bit 0:On 1:Sustain 2:Loop */\n\t\t\txi.y_wave = *b++;\t\t/* Vibrato waveform */\n\t\t\txi.y_sweep = *b++;\t\t/* Vibrato sweep */\n\t\t\txi.y_depth = *b++;\t\t/* Vibrato depth */\n\t\t\txi.y_rate = *b++;\t\t/* Vibrato rate */\n\t\t\txi.v_fade = readmem16l(b);\t/* Volume fadeout */\n\n\t\t\t/* Skip reserved space */\n\t\t\tif (hio_seek(f, (int)xih.size - (XM_INST_HEADER_SIZE + XM_INST_SIZE), SEEK_CUR) < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\t/* Envelope */\n\t\t\txxi->rls = xi.v_fade << 1;\n\t\t\txxi->aei.npt = xi.v_pts;\n\t\t\txxi->aei.sus = xi.v_sus;\n\t\t\txxi->aei.lps = xi.v_start;\n\t\t\txxi->aei.lpe = xi.v_end;\n\t\t\txxi->aei.flg = xi.v_type;\n\t\t\txxi->pei.npt = xi.p_pts;\n\t\t\txxi->pei.sus = xi.p_sus;\n\t\t\txxi->pei.lps = xi.p_start;\n\t\t\txxi->pei.lpe = xi.p_end;\n\t\t\txxi->pei.flg = xi.p_type;\n\n\t\t\tif (xxi->aei.npt <= 0 || xxi->aei.npt > 12 /*XMP_MAX_ENV_POINTS */ ) {\n\t\t\t\txxi->aei.flg &= ~XMP_ENVELOPE_ON;\n\t\t\t} else {\n\t\t\t\tmemcpy(xxi->aei.data, xi.v_env, xxi->aei.npt * 4);\n\t\t\t}\n\n\t\t\tif (xxi->pei.npt <= 0 || xxi->pei.npt > 12 /*XMP_MAX_ENV_POINTS */ ) {\n\t\t\t\txxi->pei.flg &= ~XMP_ENVELOPE_ON;\n\t\t\t} else {\n\t\t\t\tmemcpy(xxi->pei.data, xi.p_env, xxi->pei.npt * 4);\n\t\t\t}\n\n\t\t\tfor (j = 12; j < 108; j++) {\n\t\t\t\txxi->map[j].ins = xi.sample[j - 12];\n\t\t\t\tif (xxi->map[j].ins >= xxi->nsm)\n\t\t\t\t\txxi->map[j].ins = 0xff;\n\t\t\t}\n\t\t}\n\n\t\t/* Read subinstrument and sample parameters */\n\n\t\tfor (j = 0; j < xxi->nsm; j++, sample_num++) {\n\t\t\tstruct xmp_subinstrument *sub = &xxi->sub[j];\n\t\t\tstruct xmp_sample *xxs;\n\t\t\tuint8 *b = buf;\n\n\t\t\tD_(D_INFO \"  sample index:%d sample id:%d\", j, sample_num);\n\n\t\t\tif (sample_num >= mod->smp) {\n\t\t\t\tif (libxmp_realloc_samples(m, mod->smp * 3 / 2) < 0)\n\t\t\t\t\treturn -1;\n\t\t\t}\n\t\t\txxs = &mod->xxs[sample_num];\n\n\t\t\tif (hio_read(buf, 40, 1, f) != 1) {\n\t\t\t\tD_(D_CRIT \"short read in sample data\");\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\txsh[j].length = readmem32l(b);\t/* Sample length */\n\t\t\tb += 4;\n\n\t\t\t/* Sanity check */\n\t\t\tif (xsh[j].length > MAX_SAMPLE_SIZE) {\n\t\t\t\tD_(D_CRIT \"sanity check: %d: bad sample size\", j);\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\txsh[j].loop_start = readmem32l(b);\t/* Sample loop start */\n\t\t\tb += 4;\n\t\t\txsh[j].loop_length = readmem32l(b);\t/* Sample loop length */\n\t\t\tb += 4;\n\t\t\txsh[j].volume = *b++;\t/* Volume */\n\t\t\txsh[j].finetune = *b++;\t/* Finetune (-128..+127) */\n\t\t\txsh[j].type = *b++;\t/* Flags */\n\t\t\txsh[j].pan = *b++;\t/* Panning (0-255) */\n\t\t\txsh[j].relnote = *(int8 *) b++;\t/* Relative note number */\n\t\t\txsh[j].reserved = *b++;\n\t\t\tmemcpy(xsh[j].name, b, 22);\n\n\t\t\tsub->vol = xsh[j].volume;\n\t\t\tsub->pan = xsh[j].pan;\n\t\t\tsub->xpo = xsh[j].relnote;\n\t\t\tsub->fin = xsh[j].finetune;\n\t\t\tsub->vwf = xi.y_wave;\n\t\t\tsub->vde = xi.y_depth << 2;\n\t\t\tsub->vra = xi.y_rate;\n\t\t\tsub->vsw = xi.y_sweep;\n\t\t\tsub->sid = sample_num;\n\n\t\t\tlibxmp_copy_adjust(xxs->name, xsh[j].name, 22);\n\n\t\t\txxs->len = xsh[j].length;\n\t\t\txxs->lps = xsh[j].loop_start;\n\t\t\txxs->lpe = xsh[j].loop_start + xsh[j].loop_length;\n\n\t\t\txxs->flg = 0;\n\t\t\tif (xsh[j].type & XM_SAMPLE_16BIT) {\n\t\t\t\txxs->flg |= XMP_SAMPLE_16BIT;\n\t\t\t\txxs->len >>= 1;\n\t\t\t\txxs->lps >>= 1;\n\t\t\t\txxs->lpe >>= 1;\n\t\t\t}\n\t\t\tif (xsh[j].type & XM_SAMPLE_STEREO) {\n\t\t\t\txxs->flg |= XMP_SAMPLE_STEREO;\n\t\t\t\txxs->len >>= 1;\n\t\t\t\txxs->lps >>= 1;\n\t\t\t\txxs->lpe >>= 1;\n\t\t\t}\n\n\t\t\txxs->flg |= xsh[j].type & XM_LOOP_FORWARD ? XMP_SAMPLE_LOOP : 0;\n\t\t\txxs->flg |= xsh[j].type & XM_LOOP_PINGPONG ? XMP_SAMPLE_LOOP | XMP_SAMPLE_LOOP_BIDIR : 0;\n\n\t\t\tD_(D_INFO \"  size:%06x loop start:%06x loop end:%06x %c V%02x F%+04d P%02x R%+03d %s%s\",\n\t\t\t   mod->xxs[sub->sid].len,\n\t\t\t   mod->xxs[sub->sid].lps,\n\t\t\t   mod->xxs[sub->sid].lpe,\n\t\t\t   mod->xxs[sub->sid].flg & XMP_SAMPLE_LOOP_BIDIR ? 'B' :\n\t\t\t   mod->xxs[sub->sid].flg & XMP_SAMPLE_LOOP ? 'L' : ' ',\n\t\t\t   sub->vol, sub->fin, sub->pan, sub->xpo,\n\t\t\t   mod->xxs[sub->sid].flg & XMP_SAMPLE_16BIT ? \" (16 bit)\" : \"\",\n\t\t\t   xxs->flg & XMP_SAMPLE_STEREO ? \" (stereo)\" : \"\");\n\t\t}\n\n\t\t/* Read actual sample data */\n\n\t\ttotal_sample_size = 0;\n\t\tfor (j = 0; j < xxi->nsm; j++) {\n\t\t\tstruct xmp_subinstrument *sub = &xxi->sub[j];\n\t\t\tstruct xmp_sample *xxs = &mod->xxs[sub->sid];\n\t\t\tint flags;\n\n\t\t\tflags = SAMPLE_FLAG_DIFF;\n#ifndef LIBXMP_CORE_PLAYER\n\t\t\tif (xsh[j].reserved == 0xad) {\n\t\t\t\tflags = SAMPLE_FLAG_ADPCM;\n\t\t\t}\n#endif\n\n\t\t\tif (version > 0x0103) {\n\t\t\t        D_(D_INFO \"  read sample: index:%d sample id:%d\", j, sub->sid);\n\n#if 0\n\t\t\t\tif (is_ogg_sample(f, xxs)) {\n\t\t\t\t\tif (oggdec(m, f, xxs, xsh[j].length) < 0) {\n\t\t\t\t\t\treturn -1;\n\t\t\t\t\t}\n\n\t\t\t\t\tD_(D_INFO \"  sample is vorbis\");\n\t\t\t\t\ttotal_sample_size += xsh[j].length;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n#endif\n\n\t\t\t\tif (libxmp_load_sample(m, f, flags, xxs, NULL) < 0) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\tif (flags & SAMPLE_FLAG_ADPCM) {\n\t\t\t                D_(D_INFO \"  sample is adpcm\");\n\t\t\t\t\ttotal_sample_size += 16 + ((xsh[j].length + 1) >> 1);\n\t\t\t\t} else {\n\t\t\t\t\ttotal_sample_size += xsh[j].length;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/* Reposition correctly in case of 16-bit sample having odd in-file length.\n\t\t * See \"Lead Lined for '99\", reported by Dennis Mulleneers.\n\t\t */\n\t\tif (hio_seek(f, instr_pos + xih.size + 40 * xih.samples + total_sample_size, SEEK_SET) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t/* Final sample number adjustment */\n\tif (libxmp_realloc_samples(m, sample_num) < 0) {\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int xm_load(struct module_data *m, HIO_HANDLE * f, const int start)\n{\n\tstruct xmp_module *mod = &m->mod;\n\tint i, j;\n\tstruct xm_file_header xfh;\n\tchar tracker_name[21];\n#ifndef LIBXMP_CORE_PLAYER\n\tint claims_ft2 = 0;\n\tint is_mpt_116 = 0;\n#endif\n\tint len;\n\tuint8 buf[80];\n\n\tLOAD_INIT();\n\n\tif (hio_read(buf, 80, 1, f) != 1) {\n\t\tD_(D_CRIT \"error reading header\");\n\t\treturn -1;\n\t}\n\n\tmemcpy(xfh.id, buf, 17);\t\t/* ID text */\n\tmemcpy(xfh.name, buf + 17, 20);\t\t/* Module name */\n\t/* */\t\t\t\t\t/* skip 0x1a */\n\tmemcpy(xfh.tracker, buf + 38, 20);\t/* Tracker name */\n\txfh.version = readmem16l(buf + 58);\t/* Version number, minor-major */\n\txfh.headersz = readmem32l(buf + 60);\t/* Header size */\n\txfh.songlen = readmem16l(buf + 64);\t/* Song length */\n\txfh.restart = readmem16l(buf + 66);\t/* Restart position */\n\txfh.channels = readmem16l(buf + 68);\t/* Number of channels */\n\txfh.patterns = readmem16l(buf + 70);\t/* Number of patterns */\n\txfh.instruments = readmem16l(buf + 72);\t/* Number of instruments */\n\txfh.flags = readmem16l(buf + 74);\t/* 0=Amiga freq table, 1=Linear */\n\txfh.tempo = readmem16l(buf + 76);\t/* Default tempo */\n\txfh.bpm = readmem16l(buf + 78);\t\t/* Default BPM */\n\n\t/* Sanity checks */\n\tif (xfh.songlen > 256) {\n\t\tD_(D_CRIT \"bad song length: %d\", xfh.songlen);\n\t\treturn -1;\n\t}\n\tif (xfh.patterns > 256) {\n\t\tD_(D_CRIT \"bad pattern count: %d\", xfh.patterns);\n\t\treturn -1;\n\t}\n\tif (xfh.instruments > 255) {\n\t\tD_(D_CRIT \"bad instrument count: %d\", xfh.instruments);\n\t\treturn -1;\n\t}\n\n\tif (xfh.channels > XMP_MAX_CHANNELS) {\n\t\tD_(D_CRIT \"bad channel count: %d\", xfh.channels);\n\t\treturn -1;\n\t}\n\n\t/* FT2 and MPT allow up to 255 BPM. OpenMPT allows up to 1000 BPM. */\n\tif (xfh.tempo >= 32 || xfh.bpm < 32 || xfh.bpm > 1000) {\n\t\tif (memcmp(\"MED2XM\", xfh.tracker, 6)) {\n\t\t\tD_(D_CRIT \"bad tempo or BPM: %d %d\", xfh.tempo, xfh.bpm);\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\t/* Honor header size -- needed by BoobieSqueezer XMs */\n\tlen = xfh.headersz - 0x14;\n\tif (len < 0 || len > 256) {\n\t\tD_(D_CRIT \"bad XM header length: %d\", len);\n\t\treturn -1;\n\t}\n\n\tmemset(xfh.order, 0, sizeof(xfh.order));\n\tif (hio_read(xfh.order, len, 1, f) != 1) {\t/* Pattern order table */\n\t\tD_(D_CRIT \"error reading orders\");\n\t\treturn -1;\n\t}\n\n\tstrncpy(mod->name, (char *)xfh.name, 20);\n\n\tmod->len = xfh.songlen;\n\tmod->chn = xfh.channels;\n\tmod->pat = xfh.patterns;\n\tmod->ins = xfh.instruments;\n\tmod->rst = xfh.restart >= xfh.songlen ? 0 : xfh.restart;\n\tmod->spd = xfh.tempo;\n\tmod->bpm = xfh.bpm;\n\tmod->trk = mod->chn * mod->pat + 1;\n\n\tm->c4rate = C4_NTSC_RATE;\n\tm->period_type = xfh.flags & XM_LINEAR_PERIOD_MODE ?  PERIOD_LINEAR : PERIOD_AMIGA;\n\n\tmemcpy(mod->xxo, xfh.order, mod->len);\n\t/*tracker_name[20] = 0;*/\n\tsnprintf(tracker_name, 21, \"%-20.20s\", xfh.tracker);\n\tfor (i = 20; i >= 0; i--) {\n\t\tif (tracker_name[i] == 0x20)\n\t\t\ttracker_name[i] = 0;\n\t\tif (tracker_name[i])\n\t\t\tbreak;\n\t}\n\n\t/* OpenMPT accurately emulates weird FT2 bugs */\n\tif (!strncmp(tracker_name, \"FastTracker v2.00\", 17)) {\n\t\tm->quirk |= QUIRK_FT2BUGS;\n#ifndef LIBXMP_CORE_PLAYER\n\t\tclaims_ft2 = 1;\n#endif\n\t} else if (!strncmp(tracker_name, \"OpenMPT \", 8)) {\n\t\tm->quirk |= QUIRK_FT2BUGS;\n\t}\n#ifndef LIBXMP_CORE_PLAYER\n\tif (xfh.headersz == 0x0113) {\n\t\tstrcpy(tracker_name, \"unknown tracker\");\n\t\tm->quirk &= ~QUIRK_FT2BUGS;\n\t} else if (*tracker_name == 0) {\n\t\tstrcpy(tracker_name, \"Digitrakker\");\t/* best guess */\n\t\tm->quirk &= ~QUIRK_FT2BUGS;\n\t}\n\n\t/* See MMD1 loader for explanation */\n\tif (!strncmp(tracker_name, \"MED2XM by J.Pynnone\", 19)) {\n\t\tif (mod->bpm <= 10) {\n\t\t\tmod->bpm = 125 * (0x35 - mod->bpm * 2) / 33;\n\t\t}\n\t\tm->quirk &= ~QUIRK_FT2BUGS;\n\t}\n\n\tif (!strncmp(tracker_name, \"FastTracker v 2.00\", 18)) {\n\t\tstrcpy(tracker_name, \"old ModPlug Tracker\");\n\t\tm->quirk &= ~QUIRK_FT2BUGS;\n\t\tis_mpt_116 = 1;\n\t}\n\n\tlibxmp_set_type(m, \"%s XM %d.%02d\", tracker_name, xfh.version >> 8, xfh.version & 0xff);\n#else\n\tlibxmp_set_type(m, tracker_name);\n#endif\n\n\tMODULE_INFO();\n\n\t/* Honor header size */\n\tif (hio_seek(f, start + xfh.headersz + 60, SEEK_SET) < 0) {\n\t\treturn -1;\n\t}\n\n\t/* XM 1.02/1.03 has a different patterns and instruments order */\n\tif (xfh.version <= 0x0103) {\n\t\tif (load_instruments(m, xfh.version, f) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\tif (load_patterns(m, xfh.version, f) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t} else {\n\t\tif (load_patterns(m, xfh.version, f) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t\tif (load_instruments(m, xfh.version, f) < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tD_(D_INFO \"Stored samples: %d\", mod->smp);\n\n\t/* XM 1.02 stores all samples after the patterns */\n\tif (xfh.version <= 0x0103) {\n\t\tfor (i = 0; i < mod->ins; i++) {\n\t\t\tfor (j = 0; j < mod->xxi[i].nsm; j++) {\n\t\t\t\tint sid = mod->xxi[i].sub[j].sid;\n\t\t\t\tif (libxmp_load_sample(m, f, SAMPLE_FLAG_DIFF, &mod->xxs[sid], NULL) < 0) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\t/* Load MPT properties from the end of the file. */\n\twhile (1) {\n\t\tuint32 ext = hio_read32b(f);\n\t\tuint32 sz = hio_read32l(f);\n\t\tint known = 0;\n\n\t\tif (hio_error(f) || sz > 0x7fffffff /* INT32_MAX */)\n\t\t\tbreak;\n\n\t\tswitch (ext) {\n\t\tcase MAGIC4('t','e','x','t'):\t\t/* Song comment */\n\t\t\tknown = 1;\n\t\t\tif (m->comment != NULL)\n\t\t\t\tbreak;\n\n\t\t\tif ((m->comment = (char *)malloc(sz + 1)) == NULL)\n\t\t\t\tbreak;\n\n\t\t\tsz = hio_read(m->comment, 1, sz, f);\n\t\t\tm->comment[sz] = '\\0';\n\n\t\t\tfor (i = 0; i < (int)sz; i++) {\n\t\t\t\tint b = m->comment[i];\n\t\t\t\tif (b == '\\r') {\n\t\t\t\t\tm->comment[i] = '\\n';\n\t\t\t\t} else if ((b < 32 || b > 127) && b != '\\n'\n\t\t\t\t\t   && b != '\\t') {\n\t\t\t\t\tm->comment[i] = '.';\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase MAGIC4('M','I','D','I'):\t\t/* MIDI config */\n\t\tcase MAGIC4('P','N','A','M'):\t\t/* Pattern names */\n\t\tcase MAGIC4('C','N','A','M'):\t\t/* Channel names */\n\t\tcase MAGIC4('C','H','F','X'):\t\t/* Channel plugins */\n\t\tcase MAGIC4('X','T','P','M'):\t\t/* Inst. extensions */\n\t\t\tknown = 1;\n\t\t\t/* fall-through */\n\n\t\tdefault:\n\t\t\t/* Plugin definition */\n\t\t\tif ((ext & MAGIC4('F','X', 0, 0)) == MAGIC4('F','X', 0, 0))\n\t\t\t\tknown = 1;\n\n\t\t\tif (sz) hio_seek(f, sz, SEEK_CUR);\n\t\t\tbreak;\n\t\t}\n\n\t\tif(known && claims_ft2)\n\t\t\tis_mpt_116 = 1;\n\n\t\tif (ext == MAGIC4('X','T','P','M'))\n\t\t\tbreak;\n\t}\n\n\tif (is_mpt_116) {\n\t\tlibxmp_set_type(m, \"ModPlug Tracker 1.16 XM %d.%02d\",\n\t\t\t\txfh.version >> 8, xfh.version & 0xff);\n\n\t\tm->quirk &= ~QUIRK_FT2BUGS;\n\t\tm->flow_mode = FLOW_MODE_MPT_116;\n\t\tm->mvolbase = 48;\n\t\tm->mvol = 48;\n\t\tlibxmp_apply_mpt_preamp(m);\n\t}\n#endif\n\n\tfor (i = 0; i < mod->chn; i++) {\n\t\tmod->xxc[i].pan = 0x80;\n\t}\n\n\tm->quirk |= QUIRKS_FT2 | QUIRK_FT2ENV;\n\tm->read_event_type = READ_EVENT_FT2;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/md5.c",
    "content": "/*\n * This code implements the MD5 message-digest algorithm.\n * The algorithm is due to Ron Rivest.\tThis code was\n * written by Colin Plumb in 1993, no copyright is claimed.\n * This code is in the public domain; do with it what you wish.\n *\n * Equivalent code is available from RSA Data Security, Inc.\n * This code has been tested against that, and is equivalent,\n * except that you don't need to include two pages of legalese\n * with every copy.\n *\n * To compute the message digest of a chunk of bytes, declare an\n * MD5Context structure, pass it to MD5Init, call MD5Update as\n * needed on buffers full of bytes, and then call MD5Final, which\n * will fill a supplied 16-byte array with the digest.\n */\n\n#include \"common.h\"\n#include \"md5.h\"\n\n#define PUT_64BIT_LE(cp, value) do {\t\t\t\t\t\\\n\t(cp)[7] = (value) >> 56;\t\t\t\t\t\\\n\t(cp)[6] = (value) >> 48;\t\t\t\t\t\\\n\t(cp)[5] = (value) >> 40;\t\t\t\t\t\\\n\t(cp)[4] = (value) >> 32;\t\t\t\t\t\\\n\t(cp)[3] = (value) >> 24;\t\t\t\t\t\\\n\t(cp)[2] = (value) >> 16;\t\t\t\t\t\\\n\t(cp)[1] = (value) >> 8;\t\t\t\t\t\t\\\n\t(cp)[0] = (value); } while (0)\n\n#define PUT_32BIT_LE(cp, value) do {\t\t\t\t\t\\\n\t(cp)[3] = (value) >> 24;\t\t\t\t\t\\\n\t(cp)[2] = (value) >> 16;\t\t\t\t\t\\\n\t(cp)[1] = (value) >> 8;\t\t\t\t\t\t\\\n\t(cp)[0] = (value); } while (0)\n\nstatic const uint8 PADDING[MD5_BLOCK_LENGTH] = {\n\t0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n};\n\n/* The four core functions - F1 is optimized somewhat */\n\n/* #define F1(x, y, z) (x & y | ~x & z) */\n#define F1(x, y, z) (z ^ (x & (y ^ z)))\n#define F2(x, y, z) F1(z, x, y)\n#define F3(x, y, z) (x ^ y ^ z)\n#define F4(x, y, z) (y ^ (x | ~z))\n\n/* This is the central step in the MD5 algorithm. */\n#define MD5STEP(f, w, x, y, z, data, s) \\\n\t( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )\n\n/*\n * The core of the MD5 algorithm, this alters an existing MD5 hash to\n * reflect the addition of 16 longwords of new data.  MD5Update blocks\n * the data and converts bytes into longwords for this routine.\n */\nstatic void MD5Transform(uint32 state[4], const uint8 block[MD5_BLOCK_LENGTH])\n{\n\tuint32 a, b, c, d, in[MD5_BLOCK_LENGTH / 4];\n\n#ifndef WORDS_BIGENDIAN\n\tmemcpy(in, block, sizeof(in));\n#else\n\tfor (a = 0; a < MD5_BLOCK_LENGTH / 4; a++) {\n\t\tin[a] = (uint32)(\n\t\t    (uint32)(block[a * 4 + 0]) |\n\t\t    (uint32)(block[a * 4 + 1]) <<  8 |\n\t\t    (uint32)(block[a * 4 + 2]) << 16 |\n\t\t    (uint32)(block[a * 4 + 3]) << 24);\n\t}\n#endif\n\n\ta = state[0];\n\tb = state[1];\n\tc = state[2];\n\td = state[3];\n\n\tMD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478,  7);\n\tMD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12);\n\tMD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17);\n\tMD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22);\n\tMD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf,  7);\n\tMD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12);\n\tMD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17);\n\tMD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22);\n\tMD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8,  7);\n\tMD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12);\n\tMD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);\n\tMD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);\n\tMD5STEP(F1, a, b, c, d, in[12] + 0x6b901122,  7);\n\tMD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);\n\tMD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);\n\tMD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);\n\n\tMD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562,  5);\n\tMD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340,  9);\n\tMD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);\n\tMD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20);\n\tMD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d,  5);\n\tMD5STEP(F2, d, a, b, c, in[10] + 0x02441453,  9);\n\tMD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);\n\tMD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20);\n\tMD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6,  5);\n\tMD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6,  9);\n\tMD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14);\n\tMD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20);\n\tMD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905,  5);\n\tMD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8,  9);\n\tMD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14);\n\tMD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);\n\n\tMD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942,  4);\n\tMD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11);\n\tMD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);\n\tMD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);\n\tMD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44,  4);\n\tMD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11);\n\tMD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16);\n\tMD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);\n\tMD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6,  4);\n\tMD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11);\n\tMD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16);\n\tMD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23);\n\tMD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039,  4);\n\tMD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);\n\tMD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);\n\tMD5STEP(F3, b, c, d, a, in[2 ] + 0xc4ac5665, 23);\n\n\tMD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244,  6);\n\tMD5STEP(F4, d, a, b, c, in[7 ] + 0x432aff97, 10);\n\tMD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);\n\tMD5STEP(F4, b, c, d, a, in[5 ] + 0xfc93a039, 21);\n\tMD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3,  6);\n\tMD5STEP(F4, d, a, b, c, in[3 ] + 0x8f0ccc92, 10);\n\tMD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);\n\tMD5STEP(F4, b, c, d, a, in[1 ] + 0x85845dd1, 21);\n\tMD5STEP(F4, a, b, c, d, in[8 ] + 0x6fa87e4f,  6);\n\tMD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);\n\tMD5STEP(F4, c, d, a, b, in[6 ] + 0xa3014314, 15);\n\tMD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);\n\tMD5STEP(F4, a, b, c, d, in[4 ] + 0xf7537e82,  6);\n\tMD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);\n\tMD5STEP(F4, c, d, a, b, in[2 ] + 0x2ad7d2bb, 15);\n\tMD5STEP(F4, b, c, d, a, in[9 ] + 0xeb86d391, 21);\n\n\tstate[0] += a;\n\tstate[1] += b;\n\tstate[2] += c;\n\tstate[3] += d;\n}\n\n/*\n * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious\n * initialization constants.\n */\nvoid MD5Init(MD5_CTX *ctx)\n{\n\tctx->count = 0;\n\tctx->state[0] = 0x67452301;\n\tctx->state[1] = 0xefcdab89;\n\tctx->state[2] = 0x98badcfe;\n\tctx->state[3] = 0x10325476;\n}\n\n/*\n * Update context to reflect the concatenation of another buffer full\n * of bytes.\n */\nvoid MD5Update(MD5_CTX *ctx, const unsigned char *input, size_t len)\n{\n\tsize_t have, need;\n\n\t/* Check how many bytes we already have and how many more we need. */\n\thave = (size_t)((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1));\n\tneed = MD5_BLOCK_LENGTH - have;\n\n\t/* Update bitcount */\n\tctx->count += (uint64)len << 3;\n\n\tif (len >= need) {\n\t\tif (have != 0) {\n\t\t\tmemcpy(ctx->buffer + have, input, need);\n\t\t\tMD5Transform(ctx->state, ctx->buffer);\n\t\t\tinput += need;\n\t\t\tlen -= need;\n\t\t\thave = 0;\n\t\t}\n\n\t\t/* Process data in MD5_BLOCK_LENGTH-byte chunks. */\n\t\twhile (len >= MD5_BLOCK_LENGTH) {\n\t\t\tMD5Transform(ctx->state, input);\n\t\t\tinput += MD5_BLOCK_LENGTH;\n\t\t\tlen -= MD5_BLOCK_LENGTH;\n\t\t}\n\t}\n\n\t/* Handle any remaining bytes of data. */\n\tif (len != 0)\n\t\tmemcpy(ctx->buffer + have, input, len);\n}\n\n/*\n * Pad pad to 64-byte boundary with the bit pattern\n * 1 0* (64-bit count of bits processed, MSB-first)\n */\nstatic void MD5Pad(MD5_CTX *ctx)\n{\n\tuint8 count[8];\n\tsize_t padlen;\n\n\t/* Convert count to 8 bytes in little endian order. */\n\tPUT_64BIT_LE(count, ctx->count);\n\n\t/* Pad out to 56 mod 64. */\n\tpadlen = MD5_BLOCK_LENGTH -\n\t    ((ctx->count >> 3) & (MD5_BLOCK_LENGTH - 1));\n\tif (padlen < 1 + 8)\n\t\tpadlen += MD5_BLOCK_LENGTH;\n\tMD5Update(ctx, PADDING, padlen - 8);\t\t/* padlen - 8 <= 64 */\n\tMD5Update(ctx, count, 8);\n}\n\n/*\n * Final wrapup--call MD5Pad, fill in digest and zero out ctx.\n */\nvoid MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], MD5_CTX *ctx)\n{\n\tint i;\n\n\tMD5Pad(ctx);\n\tif (digest != NULL) {\n\t\tfor (i = 0; i < 4; i++)\n\t\t\tPUT_32BIT_LE(digest + i * 4, ctx->state[i]);\n\t\tmemset(ctx, 0, sizeof(*ctx));\n\t}\n}\n\n"
  },
  {
    "path": "contrib/libxmp/src/md5.h",
    "content": "/*\n * This code implements the MD5 message-digest algorithm.\n * The algorithm is due to Ron Rivest.  This code was\n * written by Colin Plumb in 1993, no copyright is claimed.\n * This code is in the public domain; do with it what you wish.\n *\n * Equivalent code is available from RSA Data Security, Inc.\n * This code has been tested against that, and is equivalent,\n * except that you don't need to include two pages of legalese\n * with every copy.\n */\n\n#ifndef LIBXMP_MD5_H\n#define LIBXMP_MD5_H\n\n#include \"common.h\"\n\n#define\tMD5_BLOCK_LENGTH\t\t64\n#define\tMD5_DIGEST_LENGTH\t\t16\n#define\tMD5_DIGEST_STRING_LENGTH\t(MD5_DIGEST_LENGTH * 2 + 1)\n\ntypedef struct MD5Context {\n\tuint32 state[4];\t\t/* state */\n\tuint64 count;\t\t\t/* number of bits, mod 2^64 */\n\tuint8 buffer[MD5_BLOCK_LENGTH];\t/* input buffer */\n} MD5_CTX;\n\nLIBXMP_BEGIN_DECLS\n\nvoid\t MD5Init(MD5_CTX *);\nvoid\t MD5Update(MD5_CTX *, const unsigned char *, size_t);\nvoid\t MD5Final(uint8[MD5_DIGEST_LENGTH], MD5_CTX *);\n\nLIBXMP_END_DECLS\n\n#endif /* LIBXMP_MD5_H */\n\n"
  },
  {
    "path": "contrib/libxmp/src/mdataio.h",
    "content": "#ifndef LIBXMP_MDATAIO_H\n#define LIBXMP_MDATAIO_H\n\n#include <stddef.h>\n#include \"common.h\"\n\nstatic inline ptrdiff_t CAN_READ(MFILE *m)\n{\n\tif (m->size >= 0)\n\t\treturn m->pos >= 0 ? m->size - m->pos : 0;\n\n\treturn INT_MAX;\n}\n\nstatic inline uint8 mread8(MFILE *m, int *err)\n{\n\tuint8 x = 0xff;\n\tsize_t r = mread(&x, 1, 1, m);\n\tif (err) {\n\t    *err = (r == 1) ? 0 : EOF;\n\t}\n\treturn x;\n}\n\nstatic inline int8 mread8s(MFILE *m, int *err)\n{\n\tint r = mgetc(m);\n\tif (err) {\n\t    *err = (r < 0) ? EOF : 0;\n\t}\n\treturn (int8)r;\n}\n\nstatic inline uint16 mread16l(MFILE *m, int *err)\n{\n\tptrdiff_t can_read = CAN_READ(m);\n\tif (can_read >= 2) {\n\t\tuint16 n = readmem16l(m->start + m->pos);\n\t\tm->pos += 2;\n\t\tif(err) *err = 0;\n\t\treturn n;\n\t} else {\n\t\tm->pos += can_read;\n\t\tif(err) *err = EOF;\n\t\treturn 0xffff;\n\t}\n}\n\nstatic inline uint16 mread16b(MFILE *m, int *err)\n{\n\tptrdiff_t can_read = CAN_READ(m);\n\tif (can_read >= 2) {\n\t\tuint16 n = readmem16b(m->start + m->pos);\n\t\tm->pos += 2;\n\t\tif(err) *err = 0;\n\t\treturn n;\n\t} else {\n\t\tm->pos += can_read;\n\t\tif(err) *err = EOF;\n\t\treturn 0xffff;\n\t}\n}\n\nstatic inline uint32 mread24l(MFILE *m, int *err)\n{\n\tptrdiff_t can_read = CAN_READ(m);\n\tif (can_read >= 3) {\n\t\tuint32 n = readmem24l(m->start + m->pos);\n\t\tm->pos += 3;\n\t\tif(err) *err = 0;\n\t\treturn n;\n\t} else {\n\t\tm->pos += can_read;\n\t\tif(err) *err = EOF;\n\t\treturn 0xffffffff;\n\t}\n}\n\nstatic inline uint32 mread24b(MFILE *m, int *err)\n{\n\tptrdiff_t can_read = CAN_READ(m);\n\tif (can_read >= 3) {\n\t\tuint32 n = readmem24b(m->start + m->pos);\n\t\tm->pos += 3;\n\t\tif(err) *err = 0;\n\t\treturn n;\n\t} else {\n\t\tm->pos += can_read;\n\t\tif(err) *err = EOF;\n\t\treturn 0xffffffff;\n\t}\n}\n\nstatic inline uint32 mread32l(MFILE *m, int *err)\n{\n\tptrdiff_t can_read = CAN_READ(m);\n\tif (can_read >= 4) {\n\t\tuint32 n = readmem32l(m->start + m->pos);\n\t\tm->pos += 4;\n\t\tif(err) *err = 0;\n\t\treturn n;\n\t} else {\n\t\tm->pos += can_read;\n\t\tif(err) *err = EOF;\n\t\treturn 0xffffffff;\n\t}\n}\n\nstatic inline uint32 mread32b(MFILE *m, int *err)\n{\n\tptrdiff_t can_read = CAN_READ(m);\n\tif (can_read >= 4) {\n\t\tuint32 n = readmem32b(m->start + m->pos);\n\t\tm->pos += 4;\n\t\tif(err) *err = 0;\n\t\treturn n;\n\t} else {\n\t\tm->pos += can_read;\n\t\tif(err) *err = EOF;\n\t\treturn 0xffffffff;\n\t}\n}\n\n#endif\n"
  },
  {
    "path": "contrib/libxmp/src/med_extras.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n#include \"player.h\"\n#include \"virtual.h\"\n#include \"effects.h\"\n#include \"med_extras.h\"\n\n#ifdef __SUNPRO_C\n#pragma error_messages (off,E_STATEMENT_NOT_REACHED)\n#endif\n\n/* Commands in the volume and waveform sequence table:\n *\n *\tCmd\tVol\tWave\tAction\n *\n *\t0xff\t    END\t\tEnd sequence\n *\t0xfe\t    JMP\t\tJump\n *\t0xfd\t -\tARE\tEnd arpeggio definition\n *\t0xfc\t -\tARP\tBegin arpeggio definition\n *\t0xfb\t    HLT\t\tHalt\n *\t0xfa\tJWS\tJVS\tJump waveform/volume sequence\n *\t0xf9\t     -\t\t-\n *\t0xf8\t     -\t\t-\n *\t0xf7\t -\tVWF\tSet vibrato waveform\n *\t0xf6\tEST\tRES\t?/reset pitch\n *\t0xf5\tEN2\tVBS\tLooping envelope/set vibrato speed\n *\t0xf4\tEN1\tVBD\tOne shot envelope/set vibrato depth\n *\t0xf3\t    CHU\t\tChange volume/pitch up speed\n *\t0xf2\t    CHD\t\tChange volume/pitch down speed\n *\t0xf1\t    WAI\t\tWait\n *\t0xf0\t    SPD\t\tSet speed\n */\n\n#define VT ((ce->vp >= 0 && ce->vp < ie->vtlen) ? me->vol_table[xc->ins][ce->vp++] : 0xff)\n#define WT ((ce->wp >= 0 && ce->wp < ie->wtlen) ? me->wav_table[xc->ins][ce->wp++] : 0xff)\n#define VT_SKIP ce->vp++\n#define WT_SKIP ce->wp++\n\n#define ARP(idx) ((idx) < ie->wtlen ? me->wav_table[xc->ins][(idx)] : 0xfd)\n\n\nstatic const int sine[32] = {\n\t   0,  49,  97, 141, 180, 212, 235, 250,\n\t 255, 250, 235, 212, 180, 141,  97,  49,\n\t   0, -49, -97,-141,-180,-212,-235,-250,\n\t-255,-250,-235,-212,-180,-141, -97, -49\n};\n\nint libxmp_med_change_period(struct context_data *ctx, struct channel_data *xc)\n{\n\tstruct med_channel_extras *ce = (struct med_channel_extras *)xc->extra;\n\tint vib;\n\n\t/* Vibrato */\n\n#if 0\n\tif (ce->vib_wf >= xxi[xc->ins].nsm)\t/* invalid waveform */\n\t\treturn 0;\n\n\tif (xxs[xxi[xc->ins][ce->vib_wf].sid].len != 32)\n\t\treturn 0;\n#endif\n\n\t/* FIXME: always using sine waveform */\n\n\tvib = (sine[ce->vib_idx >> 5] * ce->vib_depth) >> 10;\n\tce->vib_idx += ce->vib_speed;\n\tce->vib_idx %= (32 << 5);\n\n\treturn vib;\n}\n\n\nint libxmp_med_linear_bend(struct context_data *ctx, struct channel_data *xc)\n{\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_instrument *xxi = &m->mod.xxi[xc->ins];\n\tstruct med_module_extras *me = (struct med_module_extras *)m->extra;\n\tstruct med_channel_extras *ce = (struct med_channel_extras *)xc->extra;\n\tstruct med_instrument_extras *ie = MED_INSTRUMENT_EXTRAS(*xxi);\n\tint arp;\n\n\t/* Arpeggio */\n\n\tif (ce->arp == 0)\n\t\treturn 0;\n\n\tif (ARP(ce->arp) == 0xfd) /* empty arpeggio */\n\t\treturn 0;\n\n\tarp = ARP(ce->aidx);\n\tif (arp == 0xfd) {\n\t\tce->aidx = ce->arp;\n\t\tarp = ARP(ce->aidx);\n\t}\n\tce->aidx++;\n\n\treturn (100 << 7) * arp;\n}\n\n\nvoid libxmp_med_play_extras(struct context_data *ctx, struct channel_data *xc, int chn)\n{\n\tstruct module_data *m = &ctx->m;\n\tstruct player_data *p = &ctx->p;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_instrument *xxi = &m->mod.xxi[xc->ins];\n\tstruct med_module_extras *me;\n\tstruct med_channel_extras *ce;\n\tstruct med_instrument_extras *ie;\n\tint b, jws = 0, jvs = 0, loop;\n\tint temp;\n\n\tif (!HAS_MED_MODULE_EXTRAS(*m))\n\t\treturn;\n\n\tme = (struct med_module_extras *)m->extra;\n\tce = (struct med_channel_extras *)xc->extra;\n\tie = MED_INSTRUMENT_EXTRAS(*xxi);\n\n\t/* Handle hold/decay */\n\n\t/* on the first row of a held note, continue note if ce->hold is 2\n\t * (this is set after pre-fetching the next row and see if we\n\t * continue to hold. On remaining rows with hold on, we have the\n\t * FX_MED_HOLD effect and ce->hold set to 1. On the last row, see\n\t * if ce->hold_count is set (meaning that a note was held) and\n\t * ce->hold is 0 (meaning that it's not held anymore). Then\n\t * proceed with normal frame counting until decay.\n\t */\n\n\tif (ce->hold_count) {\t\t/* was held in the past */\n\t\tif (!ce->hold && p->frame >= ie->hold) { /* but not now */\n\t\t\tSET_NOTE(NOTE_FADEOUT);\n\t\t\tce->hold_count = 0;\n\t\t}\n\t} else if (ie->hold) {\t\t/* has instrument hold */\n\t\tif (p->frame >= ie->hold && ce->hold == 0) {\n\t\t\tSET_NOTE(NOTE_FADEOUT);\n\t\t}\n\t}\n\n\tif (p->frame == (p->speed - 1) && ce->hold != 2) {\n\t\tce->hold = 0;\n\t}\n\n\t/* Handle synth */\n\n\tif (me->vol_table[xc->ins] == NULL || me->wav_table[xc->ins] == NULL) {\n\t\tce->volume = 64;  /* we need this in extras_get_volume() */\n\t\treturn;\n\t}\n\n\tif (p->frame == 0 && TEST(NEW_NOTE)) {\n\t\tce->period = xc->period;\n\t\tif (TEST(NEW_INS)) {\n\t\t\tce->arp = ce->aidx = 0;\n\t\t\tce->vp = ce->vc = ce->vw = 0;\n\t\t\tce->wp = ce->wc = ce->ww = 0;\n\t\t\tce->env_wav = -1;\n\t\t\tce->env_idx = 0;\n\t\t\tce->flags &= ~MED_SYNTH_ENV_LOOP;\n\t\t\tce->vv = 0;\n\t\t\tce->wv = 0;\n\t\t\tce->vs = ie->vts;\n\t\t\tce->ws = ie->wts;\n\t\t}\n\t}\n\n\tif (ce->vs > 0 && ce->vc-- == 0) {\n\t\tce->vc = ce->vs - 1;\n\n\t\tif (ce->vw > 0) {\n\t\t\tce->vw--;\n\t\t\tgoto skip_vol;\n\t\t}\n\n\t\tloop = jws = 0;\n\n\t\t/* Volume commands */\n\n\t    next_vt:\n\t\tswitch (b = VT) {\n\t\tcase 0xff:\t/* END */\n\t\tcase 0xfb:\t/* HLT */\n\t\t\tce->vp--;\n\t\t\tbreak;\n\t\tcase 0xfe:\t/* JMP */\n\t\t\tif (loop)\t/* avoid infinite loop */\n\t\t\t\tbreak;\n\t\t\ttemp = VT;\n\t\t\tce->vp = temp;\n\t\t\tloop = 1;\n\t\t\tgoto next_vt;\n\t\tcase 0xfa:\t/* JWS */\n\t\t\tjws = VT;\n\t\t\tbreak;\n\t\tcase 0xf5:\t/* EN2 */\n\t\t\tce->env_wav = VT;\n\t\t\tce->flags |= MED_SYNTH_ENV_LOOP;\n\t\t\tbreak;\n\t\tcase 0xf4:\t/* EN1 */\n\t\t\tce->env_wav = VT;\n\t\t\tbreak;\n\t\tcase 0xf3:\t/* CHU */\n\t\t\tce->vv = VT;\n\t\t\tbreak;\n\t\tcase 0xf2:\t/* CHD */\n\t\t\tce->vv = -VT;\n\t\t\tbreak;\n\t\tcase 0xf1:\t/* WAI */\n\t\t\tce->vw = VT;\n\t\t\tbreak;\n\t\tcase 0xf0:\t/* SPD */\n\t\t\tce->vs = VT;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tif (b >= 0x00 && b <= 0x40)\n\t\t\t\tce->volume = b;\n\t\t}\n\n\t    skip_vol:\n\t\t/* volume envelope */\n\t\tif (ce->env_wav >= 0 && ce->env_wav < xxi->nsm) {\n\t\t\tint sid = xxi->sub[ce->env_wav].sid;\n\t\t\tstruct xmp_sample *xxs = &mod->xxs[sid];\n\t\t\tif (xxs->len == 0x80) {\t\t/* sanity check */\n\t\t\t\tce->volume = ((int8)xxs->data[ce->env_idx] + 0x80) >> 2;\n\t\t\t\tce->env_idx++;\n\n\t\t\t\tif (ce->env_idx >= 0x80) {\n\t\t\t\t\tif (~ce->flags & MED_SYNTH_ENV_LOOP) {\n\t\t\t\t\t\tce->env_wav = -1;\n\t\t\t\t\t}\n\t\t\t\t\tce->env_idx = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tce->volume += ce->vv;\n\t\tCLAMP(ce->volume, 0, 64);\n\n\t\tif (ce->ww > 0) {\n\t\t\tce->ww--;\n\t\t\tgoto skip_wav;\n\t\t}\n\n\t\tloop = jvs = 0;\n\n\t\t/* Waveform commands */\n\n\t    next_wt:\n\t\tswitch (b = WT) {\n\t\tcase 0xff:\t/* END */\n\t\tcase 0xfb:\t/* HLT */\n\t\t\tce->wp--;\n\t\t\tbreak;\n\t\tcase 0xfe:\t/* JMP */\n\t\t\tif (loop)\t/* avoid infinite loop */\n\t\t\t\tbreak;\n\t\t\ttemp = WT;\n\t\t\tif (temp == 0xff) {\t/* handle JMP END case */\n\t\t\t\tce->wp--;\t/* see lepeltheme ins 0x02 */\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tce->wp = temp;\n\t\t\tloop = 1;\n\t\t\tgoto next_wt;\n\t\tcase 0xfd:\t/* ARE */\n\t\t\tbreak;\n\t\tcase 0xfc:\t/* ARP */\n\t\t\tce->arp = ce->aidx = ce->wp++;\n\t\t\twhile (b != 0xfd && b != 0xff) b = WT;\n\t\t\tbreak;\n\t\tcase 0xfa:\t/* JVS */\n\t\t\tjvs = WT;\n\t\t\tbreak;\n\t\tcase 0xf7:\t/* VWF */\n\t\t\tce->vwf = WT;\n\t\t\tbreak;\n\t\tcase 0xf6:\t/* RES */\n\t\t\txc->period = ce->period;\n\t\t\tbreak;\n\t\tcase 0xf5:\t/* VBS */\n\t\t\tce->vib_speed = WT;\n\t\t\tbreak;\n\t\tcase 0xf4:\t/* VBD */\n\t\t\tce->vib_depth = WT;\n\t\t\tbreak;\n\t\tcase 0xf3:\t/* CHU */\n\t\t\tce->wv = -WT;\n\t\t\tbreak;\n\t\tcase 0xf2:\t/* CHD */\n\t\t\tce->wv = WT;\n\t\t\tbreak;\n\t\tcase 0xf1:\t/* WAI */\n\t\t\tce->ww = WT;\n\t\t\tbreak;\n\t\tcase 0xf0:\t/* SPD */\n\t\t\tce->ws = WT;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tif (b < xxi->nsm && xxi->sub[b].sid != xc->smp) {\n\t\t\t\txc->smp = xxi->sub[b].sid;\n\t\t\t\tlibxmp_virt_setsmp(ctx, chn, xc->smp);\n\t\t\t}\n\t\t}\n\n\t    skip_wav:\n\t\txc->period += ce->wv;\n\t}\n\n\tif (jws) {\n\t\tce->wp = jws;\n\t\t/* jws = 0; */\n\t}\n\n\tif (jvs) {\n\t\tce->vp = jvs;\n\t\t/* jvs = 0; */\n\t}\n}\n\nint libxmp_med_new_instrument_extras(struct xmp_instrument *xxi)\n{\n\txxi->extra = calloc (1, sizeof(struct med_instrument_extras));\n\tif (xxi->extra == NULL)\n\t\treturn -1;\n\tMED_INSTRUMENT_EXTRAS((*xxi))->magic = MED_EXTRAS_MAGIC;\n\n\treturn 0;\n}\n\nint libxmp_med_new_channel_extras(struct channel_data *xc)\n{\n\txc->extra = calloc(1, sizeof(struct med_channel_extras));\n\tif (xc->extra == NULL)\n\t\treturn -1;\n\tMED_CHANNEL_EXTRAS((*xc))->magic = MED_EXTRAS_MAGIC;\n\n\treturn 0;\n}\n\nvoid libxmp_med_reset_channel_extras(struct channel_data *xc)\n{\n\tmemset((char *)xc->extra + 4, 0, sizeof(struct med_channel_extras) - 4);\n}\n\nvoid libxmp_med_release_channel_extras(struct channel_data *xc)\n{\n\tfree(xc->extra);\n\txc->extra = NULL;\n}\n\nint libxmp_med_new_module_extras(struct module_data *m)\n{\n\tstruct med_module_extras *me;\n\tstruct xmp_module *mod = &m->mod;\n\n\tm->extra = calloc(1, sizeof(struct med_module_extras));\n\tif (m->extra == NULL)\n\t\treturn -1;\n\tMED_MODULE_EXTRAS((*m))->magic = MED_EXTRAS_MAGIC;\n\n\tme = (struct med_module_extras *)m->extra;\n\n\tme->vol_table = (uint8 **) calloc(mod->ins, sizeof(uint8 *));\n\tif (me->vol_table == NULL)\n\t\treturn -1;\n\tme->wav_table = (uint8 **) calloc(mod->ins, sizeof(uint8 *));\n\tif (me->wav_table == NULL)\n\t\treturn -1;\n\n\treturn 0;\n}\n\nvoid libxmp_med_release_module_extras(struct module_data *m)\n{\n\tstruct med_module_extras *me;\n\tstruct xmp_module *mod = &m->mod;\n\tint i;\n\n\tme = (struct med_module_extras *)m->extra;\n\n\tif (me->vol_table) {\n\t\tfor (i = 0; i < mod->ins; i++)\n\t\t\tfree(me->vol_table[i]);\n\t\tfree(me->vol_table);\n\t}\n\n\tif (me->wav_table) {\n\t\tfor (i = 0; i < mod->ins; i++)\n\t\t\tfree(me->wav_table[i]);\n\t\tfree(me->wav_table);\n\t}\n\n\tfree(m->extra);\n\tm->extra = NULL;\n}\n\nvoid libxmp_med_extras_process_fx(struct context_data *ctx, struct channel_data *xc,\n\t\t\tint chn, uint8 note, uint8 fxt, uint8 fxp, int fnum)\n{\n\tswitch (fxt) {\n\tcase FX_MED_HOLD:\n\t\tMED_CHANNEL_EXTRAS((*xc))->hold_count++;\n\t\tMED_CHANNEL_EXTRAS((*xc))->hold = 1;\n\t\tbreak;\n\t}\n}\n\nvoid libxmp_med_hold_hack(struct context_data *ctx, int pat, int chn, int row)\n{\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tconst int num_rows = mod->xxt[TRACK_NUM(pat, chn)]->rows;\n\n\tif (row + 1 < num_rows) {\n\t\tstruct player_data *p = &ctx->p;\n\t\tstruct xmp_event *event = &EVENT(pat, chn, row + 1);\n\t\tstruct channel_data *xc = &p->xc_data[chn];\n\n\t\tif (event->f2t == FX_MED_HOLD) {\n\t\t\tMED_CHANNEL_EXTRAS(*xc)->hold = 2;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "contrib/libxmp/src/med_extras.h",
    "content": "#ifndef LIBXMP_MED_EXTRAS_H\n#define LIBXMP_MED_EXTRAS_H\n\n#define MED_EXTRAS_MAGIC 0x7f20ca5\n\nstruct med_instrument_extras {\n\tuint32 magic;\n\tint vts;\t\t/* Volume table speed */\n\tint wts;\t\t/* Waveform table speed */\n\tint vtlen;\t\t/* Volume table length */\n\tint wtlen;\t\t/* Waveform table length */\n\tint hold;\n};\n\nstruct med_channel_extras {\n\tuint32 magic;\n\tint vp;\t\t\t/* MED synth volume table pointer */\n\tint vv;\t\t\t/* MED synth volume slide value */\n\tint vs;\t\t\t/* MED synth volume speed */\n\tint vc;\t\t\t/* MED synth volume speed counter */\n\tint vw;\t\t\t/* MED synth volume wait counter */\n\tint wp;\t\t\t/* MED synth waveform table pointer */\n\tint wv;\t\t\t/* MED synth waveform slide value */\n\tint ws;\t\t\t/* MED synth waveform speed */\n\tint wc;\t\t\t/* MED synth waveform speed counter */\n\tint ww;\t\t\t/* MED synth waveform wait counter */\n\tint period;\t\t/* MED synth period for RES */\n\tint arp;\t\t/* MED synth arpeggio start */\n\tint aidx;\t\t/* MED synth arpeggio index */\n\tint vwf;\t\t/* MED synth vibrato waveform */\n\tint vib_depth;\t\t/* MED synth vibrato depth */\n\tint vib_speed;\t\t/* MED synth vibrato speed */\n\tint vib_idx;\t\t/* MED synth vibrato index */\n\tint vib_wf;\t\t/* MED synth vibrato waveform */\n\tint volume;\t\t/* MED synth note volume */\n\tint hold;\t\t/* MED note on hold flag */\n\tint hold_count;\t\t/* MED note on hold frame counter */\n\tint env_wav;\t\t/* MED synth volume envelope waveform */\n\tint env_idx;\t\t/* MED synth volume envelope index */\n#define MED_SYNTH_ENV_LOOP (1 << 0)\n\tint flags;\t\t/* flags */\n};\n\nstruct med_module_extras {\n\tuint32 magic;\n\tuint8 **vol_table;\t/* MED volume sequence table */\n\tuint8 **wav_table;\t/* MED waveform sequence table */\n};\n\n#define MED_INSTRUMENT_EXTRAS(x) ((struct med_instrument_extras *)(x).extra)\n#define HAS_MED_INSTRUMENT_EXTRAS(x) \\\n\t(MED_INSTRUMENT_EXTRAS(x) != NULL && \\\n\t MED_INSTRUMENT_EXTRAS(x)->magic == MED_EXTRAS_MAGIC)\n\n#define MED_CHANNEL_EXTRAS(x) ((struct med_channel_extras *)(x).extra)\n#define HAS_MED_CHANNEL_EXTRAS(x) \\\n\t(MED_CHANNEL_EXTRAS(x) != NULL && \\\n\t MED_CHANNEL_EXTRAS(x)->magic == MED_EXTRAS_MAGIC)\n\n#define MED_MODULE_EXTRAS(x) ((struct med_module_extras *)(x).extra)\n#define HAS_MED_MODULE_EXTRAS(x) \\\n\t(MED_MODULE_EXTRAS(x) != NULL && \\\n\t MED_MODULE_EXTRAS(x)->magic == MED_EXTRAS_MAGIC)\n\nint  libxmp_med_change_period(struct context_data *, struct channel_data *);\nint  libxmp_med_linear_bend(struct context_data *, struct channel_data *);\nint  libxmp_med_get_vibrato(struct channel_data *);\nvoid libxmp_med_play_extras(struct context_data *, struct channel_data *, int);\nint  libxmp_med_new_instrument_extras(struct xmp_instrument *);\nint  libxmp_med_new_channel_extras(struct channel_data *);\nvoid libxmp_med_reset_channel_extras(struct channel_data *);\nvoid libxmp_med_release_channel_extras(struct channel_data *);\nint  libxmp_med_new_module_extras(struct module_data *);\nvoid libxmp_med_release_module_extras(struct module_data *);\nvoid libxmp_med_extras_process_fx(struct context_data *, struct channel_data *, int, uint8, uint8, uint8, int);\n\n#endif\n"
  },
  {
    "path": "contrib/libxmp/src/memio.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n#include \"memio.h\"\n\nstatic inline ptrdiff_t CAN_READ(MFILE *m)\n{\n\treturn m->pos >= 0 ? m->size - m->pos : 0;\n}\n\n\nint mgetc(MFILE *m)\n{\n\tif (CAN_READ(m) >= 1)\n\t\treturn *(const uint8 *)(m->start + m->pos++);\n\treturn EOF;\n}\n\nsize_t mread(void *buf, size_t size, size_t num, MFILE *m)\n{\n\tsize_t should_read = size * num;\n\tptrdiff_t can_read = CAN_READ(m);\n\n\tif (!size || !num || can_read <= 0) {\n\t\treturn 0;\n\t}\n\n\tif (should_read > can_read) {\n\t\tmemcpy(buf, m->start + m->pos, can_read);\n\t\tm->pos += can_read;\n\n\t\treturn can_read / size;\n\t} else {\n\t\tmemcpy(buf, m->start + m->pos, should_read);\n\t\tm->pos += should_read;\n\n\t\treturn num;\n\t}\n}\n\n\nint mseek(MFILE *m, long offset, int whence)\n{\n\tptrdiff_t ofs = offset;\n\n\tswitch (whence) {\n\tcase SEEK_SET:\n\t\tbreak;\n\tcase SEEK_CUR:\n\t\tofs += m->pos;\n\t\tbreak;\n\tcase SEEK_END:\n\t\tofs += m->size;\n\t\tbreak;\n\tdefault:\n\t\treturn -1;\n\t}\n\tif (ofs < 0) return -1;\n\tif (ofs > m->size)\n\t\tofs = m->size;\n\tm->pos = ofs;\n\treturn 0;\n}\n\nlong mtell(MFILE *m)\n{\n\treturn (long)m->pos;\n}\n\nint meof(MFILE *m)\n{\n\treturn CAN_READ(m) <= 0;\n}\n\nMFILE *mopen(void *ptr, long size, int free_after_use)\n{\n\tMFILE *m;\n\n\tm = (MFILE *) malloc(sizeof(MFILE));\n\tif (m == NULL)\n\t\treturn NULL;\n\n\tm->start = (const unsigned char *)ptr;\n\tm->pos = 0;\n\tm->size = size;\n\tm->ptr_free = free_after_use ? ptr : NULL;\n\n\treturn m;\n}\n\nMFILE *mcopen(const void *ptr, long size)\n{\n\tMFILE *m;\n\n\tm = (MFILE *) malloc(sizeof(MFILE));\n\tif (m == NULL)\n\t\treturn NULL;\n\n\tm->start = (const unsigned char *)ptr;\n\tm->pos = 0;\n\tm->size = size;\n\tm->ptr_free = NULL;\n\n\treturn m;\n}\n\nint mclose(MFILE *m)\n{\n\tif (m->ptr_free)\n\t\tfree(m->ptr_free);\n\tfree(m);\n\treturn 0;\n}\n\n"
  },
  {
    "path": "contrib/libxmp/src/memio.h",
    "content": "#ifndef LIBXMP_MEMIO_H\n#define LIBXMP_MEMIO_H\n\n#include <stddef.h>\n#include \"common.h\"\n\ntypedef struct {\n\tconst unsigned char *start;\n\tptrdiff_t pos;\n\tptrdiff_t size;\n\tvoid *ptr_free;\n} MFILE;\n\nLIBXMP_BEGIN_DECLS\n\nMFILE  *mopen(void *, long, int);\nMFILE  *mcopen(const void *, long);\nint     mgetc(MFILE *stream);\nsize_t  mread(void *, size_t, size_t, MFILE *);\nint     mseek(MFILE *, long, int);\nlong    mtell(MFILE *);\nint     mclose(MFILE *);\nint\tmeof(MFILE *);\n\nLIBXMP_END_DECLS\n\n#endif\n"
  },
  {
    "path": "contrib/libxmp/src/mix_all.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n#include \"mixer.h\"\n#include \"precomp_lut.h\"\n\n#if defined(__cplusplus) && (__cplusplus >= 201402L)\n#define REGISTER\n#else\n#define REGISTER register\n#endif\n\n/* Mixers\n *\n * To increase performance eight mixers are defined, one for each\n * combination of the following parameters: interpolation, resolution\n * and number of channels.\n */\n#define NEAREST_8BIT(smp_in, off) do { \\\n    (smp_in) = ((int16)sptr[pos + (off)] << 8); \\\n} while (0)\n\n#define NEAREST_16BIT(smp_in, off) do { \\\n    (smp_in) = sptr[pos + (off)]; \\\n} while (0)\n\n#define LINEAR_8BIT(smp_in, off) do { \\\n    smp_l1 = ((int16)sptr[pos + (off)] << 8); \\\n    smp_dt = ((int16)sptr[pos + (off) + chn] << 8) - smp_l1; \\\n    (smp_in) = smp_l1 + (((frac >> 1) * smp_dt) >> (SMIX_SHIFT - 1)); \\\n} while (0)\n\n#define LINEAR_16BIT(smp_in, off) do { \\\n    smp_l1 = sptr[pos + (off)]; \\\n    smp_dt = sptr[pos + (off) + chn] - smp_l1; \\\n    (smp_in) = smp_l1 + (((frac >> 1) * smp_dt) >> (SMIX_SHIFT - 1)); \\\n} while (0)\n\n/* The following lut settings are PRECOMPUTED. If you plan on changing these\n * settings, you MUST also regenerate the arrays.\n */\n/* number of bits used to scale spline coefs */\n#define SPLINE_QUANTBITS  14\n#define SPLINE_SHIFT    (SPLINE_QUANTBITS)\n\n/* log2(number) of precalculated splines (range is [4..14]) */\n#define SPLINE_FRACBITS 10\n#define SPLINE_LUTLEN (1L<<SPLINE_FRACBITS)\n\n#define SPLINE_FRACSHIFT ((16 - SPLINE_FRACBITS) - 2)\n#define SPLINE_FRACMASK  (((1L << (16 - SPLINE_FRACSHIFT)) - 1) & ~3)\n\n#define SPLINE_8BIT(smp_in, off) do { \\\n    int f = frac >> 6; \\\n    (smp_in) = (cubic_spline_lut0[f] * sptr[pos + (off) - chn] + \\\n                cubic_spline_lut1[f] * sptr[pos + (off)   ] + \\\n                cubic_spline_lut3[f] * sptr[pos + (off) + (chn << 1)] + \\\n                cubic_spline_lut2[f] * sptr[pos + (off) + chn]) >> (SPLINE_SHIFT - 8); \\\n} while (0)\n\n#define SPLINE_16BIT(smp_in, off) do { \\\n    int f = frac >> 6; \\\n    (smp_in) = (cubic_spline_lut0[f] * sptr[pos + (off) - chn] + \\\n                cubic_spline_lut1[f] * sptr[pos + (off)   ] + \\\n                cubic_spline_lut3[f] * sptr[pos + (off) + (chn << 1)] + \\\n                cubic_spline_lut2[f] * sptr[pos + (off) + chn]) >> SPLINE_SHIFT; \\\n} while (0)\n\n#define LOOP_AC for (; count > ramp; count--)\n\n#define LOOP for (; count; count--)\n\n#define UPDATE_POS() do { \\\n    frac += step; \\\n    pos += (frac >> SMIX_SHIFT) * (chn); \\\n    frac &= SMIX_MASK; \\\n} while (0)\n\n/* Sample pre-amplification is required to fix filter rounding errors\n * at high sample rates. The non-filtered mixers do not need this. */\n#define PREAMP_BITS 15\n\n/* IT's WAV output driver uses a clamp that seems to roughly match this:\n * compare the WAV output of OpenMPT env-flt-max.it and filter-reset.it */\n#define FILTER_MIN (-65536 * (1 << PREAMP_BITS))\n#define FILTER_MAX (65535 * (1 << PREAMP_BITS))\n#define MIX_FILTER_CLAMP(a) \\\n ((a) < FILTER_MIN ? FILTER_MIN : (a) > FILTER_MAX ? FILTER_MAX : (a))\n\n#define FILTER_LEFT(smp_in_l) do { \\\n    sl64 = (a0 * ((smp_in_l) << PREAMP_BITS) + b0 * fl1 + b1 * fl2) >> FILTER_SHIFT; \\\n    sl = MIX_FILTER_CLAMP(sl64); \\\n    fl2 = fl1; fl1 = sl; \\\n    (smp_in_l) = sl >> PREAMP_BITS; \\\n} while (0)\n\n#define FILTER_RIGHT(smp_in_r) do { \\\n    sr64 = (a0 * ((smp_in_r) << PREAMP_BITS) + b0 * fr1 + b1 * fr2) >> FILTER_SHIFT; \\\n    sr = MIX_FILTER_CLAMP(sr64); \\\n    fr2 = fr1; fr1 = sr; \\\n    (smp_in_r) = sr >> PREAMP_BITS; \\\n} while (0)\n\n#define FILTER_MONO(smp_in_l) do { \\\n    FILTER_LEFT((smp_in_l)); \\\n} while(0)\n\n#define FILTER_STEREO(smp_in_l, smp_in_r) do { \\\n    FILTER_LEFT((smp_in_l)); \\\n    FILTER_RIGHT((smp_in_r)); \\\n} while (0)\n\n#define MIX_OUT(out_sample, out_level) do { \\\n    *(buffer++) += (out_sample) * (out_level); \\\n} while (0)\n\n#define MIX_MONO(smp_in) do { \\\n    MIX_OUT((smp_in), vl); \\\n} while (0)\n\n#define MIX_MONO_AC(smp_in) do { \\\n    MIX_OUT((smp_in), old_vl >> 8); \\\n    old_vl += delta_l; \\\n} while (0)\n\n#define MIX_STEREO(smp_in_l, smp_in_r) do { \\\n    MIX_OUT((smp_in_l), vl); \\\n    MIX_OUT((smp_in_r), vr); \\\n} while (0)\n\n#define MIX_STEREO_AC(smp_in_l, smp_in_r) do { \\\n    MIX_OUT((smp_in_l), old_vl >> 8); \\\n    MIX_OUT((smp_in_r), old_vr >> 8); \\\n    old_vl += delta_l; \\\n    old_vr += delta_r; \\\n} while (0)\n\n#define AVERAGE(smp_in_l, smp_in_r) (((smp_in_l) + (smp_in_r)) >> 1)\n\n#define MIX_MONO_AVG(smp_in_l, smp_in_r) do { \\\n    MIX_MONO(AVERAGE((smp_in_l), (smp_in_r))); \\\n} while (0)\n\n#define MIX_MONO_AVG_AC(smp_in_l, smp_in_r) do { \\\n    MIX_MONO_AC(AVERAGE((smp_in_l), (smp_in_r))); \\\n} while (0)\n\n/* For \"nearest\" to be nearest neighbor (instead of floor), the position needs\n * to be rounded. This only needs to be done once at the start of mixing, and\n * is required for reverse samples to round the same as forward samples.\n */\n#define NEAREST_ROUND() do { \\\n    frac += (1 << (SMIX_SHIFT - 1)); \\\n    pos += (frac >> SMIX_SHIFT) * (chn); \\\n    frac &= SMIX_MASK; \\\n} while (0)\n\n#define VAR_NORM(x) \\\n    REGISTER int smpl; \\\n    x *sptr = (x *)vi->sptr; \\\n    int pos = ((int)vi->pos) * chn; \\\n    int frac = (1 << SMIX_SHIFT) * (vi->pos - (int)vi->pos)\n\n#define VAR_MONO(x) \\\n    const int chn = 1; \\\n    VAR_NORM(x)\n\n#define VAR_STEREO(x) \\\n    const int chn = 2; \\\n    REGISTER int smpr; \\\n    VAR_NORM(x)\n\n#define VAR_LINEAR() \\\n    int smp_l1, smp_dt\n\n#define VAR_LINEAR_MONO(x) \\\n    VAR_MONO(x); \\\n    VAR_LINEAR()\n\n#define VAR_LINEAR_STEREO(x) \\\n    VAR_STEREO(x); \\\n    VAR_LINEAR()\n\n#define VAR_SPLINE_MONO(x) \\\n    VAR_MONO(x)\n\n#define VAR_SPLINE_STEREO(x) \\\n    VAR_STEREO(x)\n\n#define VAR_MONOOUT \\\n    int old_vl = vi->old_vl\n\n#define VAR_STEREOOUT \\\n    VAR_MONOOUT; \\\n    int old_vr = vi->old_vr\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\n#define VAR_FILTER_MONO \\\n    int fl1 = vi->filter.l1, fl2 = vi->filter.l2; \\\n    int64 a0 = vi->filter.a0, b0 = vi->filter.b0, b1 = vi->filter.b1; \\\n    int64 sl64; \\\n    int sl\n\n#define VAR_FILTER_STEREO \\\n    VAR_FILTER_MONO; \\\n    int fr1 = vi->filter.r1, fr2 = vi->filter.r2; \\\n    int64 sr64; \\\n    int sr\n\n/* Note: copying the left to the right here is for just in case these\n * values don't get cleared between playing stereo/mono samples. */\n#define SAVE_FILTER_MONO() do { \\\n    vi->filter.l1 = fl1; \\\n    vi->filter.l2 = fl2; \\\n    vi->filter.r1 = fl1; \\\n    vi->filter.r2 = fl2; \\\n} while (0)\n\n#define SAVE_FILTER_STEREO() do { \\\n    SAVE_FILTER_MONO(); \\\n    vi->filter.r1 = fr1; \\\n    vi->filter.r2 = fr2; \\\n} while (0)\n\n#endif\n\n\n/*\n * Nearest neighbor mixers\n */\n\n/* Handler for 8-bit mono samples, nearest neighbor mono output\n */\nMIXER(monoout_mono_8bit_nearest)\n{\n    VAR_MONO(int8);\n    NEAREST_ROUND();\n\n    LOOP { NEAREST_8BIT(smpl, 0); MIX_MONO(smpl); UPDATE_POS(); }\n}\n\n/* Handler for 16-bit mono samples, nearest neighbor mono output\n */\nMIXER(monoout_mono_16bit_nearest)\n{\n    VAR_MONO(int16);\n    NEAREST_ROUND();\n\n    LOOP { NEAREST_16BIT(smpl, 0); MIX_MONO(smpl); UPDATE_POS(); }\n}\n\n/* Handler for 8-bit stereo samples, nearest neighbor mono output\n */\nMIXER(monoout_stereo_8bit_nearest)\n{\n    VAR_STEREO(int8);\n    NEAREST_ROUND();\n\n    LOOP { NEAREST_8BIT(smpl, 0); NEAREST_8BIT(smpr, 1);\n           MIX_MONO_AVG(smpl, smpr); UPDATE_POS(); }\n}\n\n/* Handler for 16-bit stereo samples, nearest neighbor mono output\n */\nMIXER(monoout_stereo_16bit_nearest)\n{\n    VAR_STEREO(int16);\n    NEAREST_ROUND();\n\n    LOOP { NEAREST_16BIT(smpl, 0); NEAREST_16BIT(smpr, 1);\n           MIX_MONO_AVG(smpl, smpr); UPDATE_POS(); }\n}\n\n/* Handler for 8-bit mono samples, nearest neighbor stereo output\n */\nMIXER(stereoout_mono_8bit_nearest)\n{\n    VAR_MONO(int8);\n    NEAREST_ROUND();\n\n    LOOP { NEAREST_8BIT(smpl, 0); MIX_STEREO(smpl, smpl); UPDATE_POS(); }\n}\n\n/* Handler for 16-bit mono samples, nearest neighbor stereo output\n */\nMIXER(stereoout_mono_16bit_nearest)\n{\n    VAR_MONO(int16);\n    NEAREST_ROUND();\n\n    LOOP { NEAREST_16BIT(smpl, 0); MIX_STEREO(smpl, smpl); UPDATE_POS(); }\n}\n\n/* Handler for 8-bit stereo samples, nearest neighbor stereo output\n */\nMIXER(stereoout_stereo_8bit_nearest)\n{\n    VAR_STEREO(int8);\n    NEAREST_ROUND();\n\n    LOOP { NEAREST_8BIT(smpl, 0); NEAREST_8BIT(smpr, 1);\n           MIX_STEREO(smpl, smpr); UPDATE_POS(); }\n}\n\n/* Handler for 16-bit stereo samples, nearest neighbor stereo output\n */\nMIXER(stereoout_stereo_16bit_nearest)\n{\n    VAR_STEREO(int16);\n    NEAREST_ROUND();\n\n    LOOP { NEAREST_16BIT(smpl, 0); NEAREST_16BIT(smpr, 1);\n           MIX_STEREO(smpl, smpr); UPDATE_POS(); }\n}\n\n\n/*\n * Linear mixers\n */\n\n/* Handler for 8-bit mono samples, linear interpolated mono output\n */\nMIXER(monoout_mono_8bit_linear)\n{\n    VAR_LINEAR_MONO(int8);\n    VAR_MONOOUT;\n\n    LOOP_AC { LINEAR_8BIT(smpl, 0); MIX_MONO_AC(smpl); UPDATE_POS(); }\n    LOOP    { LINEAR_8BIT(smpl, 0); MIX_MONO(smpl); UPDATE_POS(); }\n}\n\n/* Handler for 16-bit mono samples, linear interpolated mono output\n */\nMIXER(monoout_mono_16bit_linear)\n{\n    VAR_LINEAR_MONO(int16);\n    VAR_MONOOUT;\n\n    LOOP_AC { LINEAR_16BIT(smpl, 0); MIX_MONO_AC(smpl); UPDATE_POS(); }\n    LOOP    { LINEAR_16BIT(smpl, 0); MIX_MONO(smpl); UPDATE_POS(); }\n}\n\n/* Handler for 8-bit stereo samples, linear interpolated mono output\n */\nMIXER(monoout_stereo_8bit_linear)\n{\n    VAR_LINEAR_STEREO(int8);\n    VAR_MONOOUT;\n\n    LOOP_AC {   LINEAR_8BIT(smpl, 0); LINEAR_8BIT(smpr, 1);\n                MIX_MONO_AVG_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   LINEAR_8BIT(smpl, 0); LINEAR_8BIT(smpr, 1);\n                MIX_MONO_AVG(smpl, smpr); UPDATE_POS(); }\n}\n\n/* Handler for 16-bit stereo samples, linear interpolated mono output\n */\nMIXER(monoout_stereo_16bit_linear)\n{\n    VAR_LINEAR_STEREO(int16);\n    VAR_MONOOUT;\n\n    LOOP_AC {   LINEAR_16BIT(smpl, 0); LINEAR_16BIT(smpr, 1);\n                MIX_MONO_AVG_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   LINEAR_16BIT(smpl, 0); LINEAR_16BIT(smpr, 1);\n                MIX_MONO_AVG(smpl, smpr); UPDATE_POS(); }\n}\n\n/* Handler for 8-bit mono samples, linear interpolated stereo output\n */\nMIXER(stereoout_mono_8bit_linear)\n{\n    VAR_LINEAR_MONO(int8);\n    VAR_STEREOOUT;\n\n    LOOP_AC { LINEAR_8BIT(smpl, 0); MIX_STEREO_AC(smpl, smpl); UPDATE_POS(); }\n    LOOP    { LINEAR_8BIT(smpl, 0); MIX_STEREO(smpl, smpl); UPDATE_POS(); }\n}\n\n/* Handler for 16-bit mono samples, linear interpolated stereo output\n */\nMIXER(stereoout_mono_16bit_linear)\n{\n    VAR_LINEAR_MONO(int16);\n    VAR_STEREOOUT;\n\n    LOOP_AC { LINEAR_16BIT(smpl, 0); MIX_STEREO_AC(smpl, smpl); UPDATE_POS(); }\n    LOOP    { LINEAR_16BIT(smpl, 0); MIX_STEREO(smpl, smpl); UPDATE_POS(); }\n}\n\n/* Handler for 8-bit stereo samples, linear interpolated stereo output\n */\nMIXER(stereoout_stereo_8bit_linear)\n{\n    VAR_LINEAR_STEREO(int8);\n    VAR_STEREOOUT;\n\n    LOOP_AC {   LINEAR_8BIT(smpl, 0); LINEAR_8BIT(smpr, 1);\n                MIX_STEREO_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   LINEAR_8BIT(smpl, 0); LINEAR_8BIT(smpr, 1);\n                MIX_STEREO(smpl, smpr); UPDATE_POS(); }\n}\n\n/* Handler for 16-bit stereo samples, linear interpolated stereo output\n */\nMIXER(stereoout_stereo_16bit_linear)\n{\n    VAR_LINEAR_STEREO(int16);\n    VAR_STEREOOUT;\n\n    LOOP_AC {   LINEAR_16BIT(smpl, 0); LINEAR_16BIT(smpr, 1);\n                MIX_STEREO_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   LINEAR_16BIT(smpl, 0); LINEAR_16BIT(smpr, 1);\n                MIX_STEREO(smpl, smpr); UPDATE_POS(); }\n}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\n/* Handler for 8-bit mono samples, filtered linear interpolated mono output\n */\nMIXER(monoout_mono_8bit_linear_filter)\n{\n    VAR_LINEAR_MONO(int8);\n    VAR_FILTER_MONO;\n    VAR_MONOOUT;\n\n    LOOP_AC {   LINEAR_8BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_MONO_AC(smpl); UPDATE_POS(); }\n    LOOP    {   LINEAR_8BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_MONO(smpl); UPDATE_POS(); }\n\n    SAVE_FILTER_MONO();\n}\n\n/* Handler for 16-bit mono samples, filtered linear interpolated mono output\n */\nMIXER(monoout_mono_16bit_linear_filter)\n{\n    VAR_LINEAR_MONO(int16);\n    VAR_FILTER_MONO;\n    VAR_MONOOUT;\n\n    LOOP_AC {   LINEAR_16BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_MONO_AC(smpl); UPDATE_POS(); }\n    LOOP    {   LINEAR_16BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_MONO(smpl); UPDATE_POS(); }\n\n    SAVE_FILTER_MONO();\n}\n\n/* Handler for 8-bit stereo samples, filtered linear interpolated mono output\n */\nMIXER(monoout_stereo_8bit_linear_filter)\n{\n    VAR_LINEAR_STEREO(int8);\n    VAR_FILTER_STEREO;\n    VAR_MONOOUT;\n\n    LOOP_AC {   LINEAR_8BIT(smpl, 0); LINEAR_8BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_MONO_AVG_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   LINEAR_8BIT(smpl, 0); LINEAR_8BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_MONO_AVG(smpl, smpr); UPDATE_POS(); }\n\n    SAVE_FILTER_STEREO();\n}\n\n/* Handler for 16-bit stereo samples, filtered linear interpolated mono output\n */\nMIXER(monoout_stereo_16bit_linear_filter)\n{\n    VAR_LINEAR_STEREO(int16);\n    VAR_FILTER_STEREO;\n    VAR_MONOOUT;\n\n    LOOP_AC {   LINEAR_16BIT(smpl, 0); LINEAR_16BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_MONO_AVG_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   LINEAR_16BIT(smpl, 0); LINEAR_16BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_MONO_AVG(smpl, smpr); UPDATE_POS(); }\n\n    SAVE_FILTER_STEREO();\n}\n\n/* Handler for 8-bit mono samples, filtered linear interpolated stereo output\n */\nMIXER(stereoout_mono_8bit_linear_filter)\n{\n    VAR_LINEAR_MONO(int8);\n    VAR_FILTER_MONO;\n    VAR_STEREOOUT;\n\n    LOOP_AC {   LINEAR_8BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_STEREO_AC(smpl, smpl); UPDATE_POS(); }\n    LOOP    {   LINEAR_8BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_STEREO(smpl, smpl); UPDATE_POS(); }\n\n    SAVE_FILTER_MONO();\n}\n\n/* Handler for 16-bit mono samples, filtered linear interpolated stereo output\n */\nMIXER(stereoout_mono_16bit_linear_filter)\n{\n    VAR_LINEAR_MONO(int16);\n    VAR_FILTER_MONO;\n    VAR_STEREOOUT;\n\n    LOOP_AC {   LINEAR_16BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_STEREO_AC(smpl, smpl); UPDATE_POS(); }\n    LOOP    {   LINEAR_16BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_STEREO(smpl, smpl); UPDATE_POS(); }\n\n    SAVE_FILTER_MONO();\n}\n\n/* Handler for 8-bit stereo samples, filtered linear interpolated stereo output\n */\nMIXER(stereoout_stereo_8bit_linear_filter)\n{\n    VAR_LINEAR_STEREO(int8);\n    VAR_FILTER_STEREO;\n    VAR_STEREOOUT;\n\n    LOOP_AC {   LINEAR_8BIT(smpl, 0); LINEAR_8BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_STEREO_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   LINEAR_8BIT(smpl, 0); LINEAR_8BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_STEREO(smpl, smpr); UPDATE_POS(); }\n\n    SAVE_FILTER_STEREO();\n}\n\n/* Handler for 16-bit stereo samples, filtered linear interpolated stereo output\n */\nMIXER(stereoout_stereo_16bit_linear_filter)\n{\n    VAR_LINEAR_STEREO(int16);\n    VAR_FILTER_STEREO;\n    VAR_STEREOOUT;\n\n    LOOP_AC {   LINEAR_16BIT(smpl, 0); LINEAR_16BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_STEREO_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   LINEAR_16BIT(smpl, 0); LINEAR_16BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_STEREO(smpl, smpr); UPDATE_POS(); }\n\n    SAVE_FILTER_STEREO();\n}\n\n#endif\n\n/*\n * Spline mixers\n */\n\n/* Handler for 8-bit mono samples, spline interpolated mono output\n */\nMIXER(monoout_mono_8bit_spline)\n{\n    VAR_SPLINE_MONO(int8);\n    VAR_MONOOUT;\n\n    LOOP_AC { SPLINE_8BIT(smpl, 0); MIX_MONO_AC(smpl); UPDATE_POS(); }\n    LOOP    { SPLINE_8BIT(smpl, 0); MIX_MONO(smpl); UPDATE_POS(); }\n}\n\n/* Handler for 16-bit mono samples, spline interpolated mono output\n */\nMIXER(monoout_mono_16bit_spline)\n{\n    VAR_SPLINE_MONO(int16);\n    VAR_MONOOUT;\n\n    LOOP_AC { SPLINE_16BIT(smpl, 0); MIX_MONO_AC(smpl); UPDATE_POS(); }\n    LOOP    { SPLINE_16BIT(smpl, 0); MIX_MONO(smpl); UPDATE_POS(); }\n}\n\n/* Handler for 8-bit stereo samples, spline interpolated mono output\n */\nMIXER(monoout_stereo_8bit_spline)\n{\n    VAR_SPLINE_STEREO(int8);\n    VAR_MONOOUT;\n\n    LOOP_AC {   SPLINE_8BIT(smpl, 0); SPLINE_8BIT(smpr, 1);\n                MIX_MONO_AVG_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   SPLINE_8BIT(smpl, 0); SPLINE_8BIT(smpr, 1);\n                MIX_MONO_AVG(smpl, smpr); UPDATE_POS(); }\n}\n\n/* Handler for 16-bit stereo samples, spline interpolated mono output\n */\nMIXER(monoout_stereo_16bit_spline)\n{\n    VAR_SPLINE_STEREO(int16);\n    VAR_MONOOUT;\n\n    LOOP_AC {   SPLINE_16BIT(smpl, 0); SPLINE_16BIT(smpr, 1);\n                MIX_MONO_AVG_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   SPLINE_16BIT(smpl, 0); SPLINE_16BIT(smpr, 1);\n                MIX_MONO_AVG(smpl, smpr); UPDATE_POS(); }\n}\n\n/* Handler for 8-bit mono samples, spline interpolated stereo output\n */\nMIXER(stereoout_mono_8bit_spline)\n{\n    VAR_SPLINE_MONO(int8);\n    VAR_STEREOOUT;\n\n    LOOP_AC { SPLINE_8BIT(smpl, 0); MIX_STEREO_AC(smpl, smpl); UPDATE_POS(); }\n    LOOP    { SPLINE_8BIT(smpl, 0); MIX_STEREO(smpl, smpl); UPDATE_POS(); }\n}\n\n/* Handler for 16-bit mono samples, spline interpolated stereo output\n */\nMIXER(stereoout_mono_16bit_spline)\n{\n    VAR_SPLINE_MONO(int16);\n    VAR_STEREOOUT;\n\n    LOOP_AC { SPLINE_16BIT(smpl, 0); MIX_STEREO_AC(smpl, smpl); UPDATE_POS(); }\n    LOOP    { SPLINE_16BIT(smpl, 0); MIX_STEREO(smpl, smpl); UPDATE_POS(); }\n}\n\n/* Handler for 8-bit stereo samples, spline interpolated stereo output\n */\nMIXER(stereoout_stereo_8bit_spline)\n{\n    VAR_SPLINE_STEREO(int8);\n    VAR_STEREOOUT;\n\n    LOOP_AC {   SPLINE_8BIT(smpl, 0); SPLINE_8BIT(smpr, 1);\n                MIX_STEREO_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   SPLINE_8BIT(smpl, 0); SPLINE_8BIT(smpr, 1);\n                MIX_STEREO(smpl, smpr); UPDATE_POS(); }\n}\n\n/* Handler for 16-bit stereo samples, spline interpolated stereo output\n */\nMIXER(stereoout_stereo_16bit_spline)\n{\n    VAR_SPLINE_STEREO(int16);\n    VAR_STEREOOUT;\n\n    LOOP_AC {   SPLINE_16BIT(smpl, 0); SPLINE_16BIT(smpr, 1);\n                MIX_STEREO_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   SPLINE_16BIT(smpl, 0); SPLINE_16BIT(smpr, 1);\n                MIX_STEREO(smpl, smpr); UPDATE_POS(); }\n}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\n/* Handler for 8-bit mono samples, filtered spline interpolated mono output\n */\nMIXER(monoout_mono_8bit_spline_filter)\n{\n    VAR_SPLINE_MONO(int8);\n    VAR_FILTER_MONO;\n    VAR_MONOOUT;\n\n    LOOP_AC {   SPLINE_8BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_MONO_AC(smpl); UPDATE_POS(); }\n    LOOP    {   SPLINE_8BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_MONO(smpl); UPDATE_POS(); }\n\n    SAVE_FILTER_MONO();\n}\n\n/* Handler for 16-bit mono samples, filtered spline interpolated mono output\n */\nMIXER(monoout_mono_16bit_spline_filter)\n{\n    VAR_SPLINE_MONO(int16);\n    VAR_FILTER_MONO;\n    VAR_MONOOUT;\n\n    LOOP_AC {   SPLINE_16BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_MONO_AC(smpl); UPDATE_POS(); }\n    LOOP    {   SPLINE_16BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_MONO(smpl); UPDATE_POS(); }\n\n    SAVE_FILTER_MONO();\n}\n\n/* Handler for 8-bit stereo samples, filtered spline interpolated mono output\n */\nMIXER(monoout_stereo_8bit_spline_filter)\n{\n    VAR_SPLINE_STEREO(int8);\n    VAR_FILTER_STEREO;\n    VAR_MONOOUT;\n\n    LOOP_AC {   SPLINE_8BIT(smpl, 0); SPLINE_8BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_MONO_AVG_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   SPLINE_8BIT(smpl, 0); SPLINE_8BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_MONO_AVG(smpl, smpr); UPDATE_POS(); }\n\n    SAVE_FILTER_STEREO();\n}\n\n/* Handler for 16-bit stereo samples, filtered spline interpolated mono output\n */\nMIXER(monoout_stereo_16bit_spline_filter)\n{\n    VAR_SPLINE_STEREO(int16);\n    VAR_FILTER_STEREO;\n    VAR_MONOOUT;\n\n    LOOP_AC {   SPLINE_16BIT(smpl, 0); SPLINE_16BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_MONO_AVG_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   SPLINE_16BIT(smpl, 0); SPLINE_16BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_MONO_AVG(smpl, smpr); UPDATE_POS(); }\n\n    SAVE_FILTER_STEREO();\n}\n\n/* Handler for 8-bit mono samples, filtered spline interpolated stereo output\n */\nMIXER(stereoout_mono_8bit_spline_filter)\n{\n    VAR_SPLINE_MONO(int8);\n    VAR_FILTER_MONO;\n    VAR_STEREOOUT;\n\n    LOOP_AC {   SPLINE_8BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_STEREO_AC(smpl, smpl); UPDATE_POS(); }\n    LOOP    {   SPLINE_8BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_STEREO(smpl, smpl); UPDATE_POS(); }\n\n    SAVE_FILTER_MONO();\n}\n\n/* Handler for 16-bit mono samples, filtered spline interpolated stereo output\n */\nMIXER(stereoout_mono_16bit_spline_filter)\n{\n    VAR_SPLINE_MONO(int16);\n    VAR_FILTER_MONO;\n    VAR_STEREOOUT;\n\n    LOOP_AC {   SPLINE_16BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_STEREO_AC(smpl, smpl); UPDATE_POS(); }\n    LOOP    {   SPLINE_16BIT(smpl, 0); FILTER_MONO(smpl);\n                MIX_STEREO(smpl, smpl); UPDATE_POS(); }\n\n    SAVE_FILTER_MONO();\n}\n\n/* Handler for 8-bit stereo samples, filtered spline interpolated stereo output\n */\nMIXER(stereoout_stereo_8bit_spline_filter)\n{\n    VAR_SPLINE_STEREO(int8);\n    VAR_FILTER_STEREO;\n    VAR_STEREOOUT;\n\n    LOOP_AC {   SPLINE_8BIT(smpl, 0); SPLINE_8BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_STEREO_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   SPLINE_8BIT(smpl, 0); SPLINE_8BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_STEREO(smpl, smpr); UPDATE_POS(); }\n\n    SAVE_FILTER_STEREO();\n}\n\n/* Handler for 16-bit stereo samples, filtered spline interpolated stereo output\n */\nMIXER(stereoout_stereo_16bit_spline_filter)\n{\n    VAR_SPLINE_STEREO(int16);\n    VAR_FILTER_STEREO;\n    VAR_STEREOOUT;\n\n    LOOP_AC {   SPLINE_16BIT(smpl, 0); SPLINE_16BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_STEREO_AC(smpl, smpr); UPDATE_POS(); }\n    LOOP    {   SPLINE_16BIT(smpl, 0); SPLINE_16BIT(smpr, 1); FILTER_STEREO(smpl, smpr);\n                MIX_STEREO(smpl, smpr); UPDATE_POS(); }\n\n    SAVE_FILTER_STEREO();\n}\n\n#endif\n"
  },
  {
    "path": "contrib/libxmp/src/mixer.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <math.h>\n#include \"common.h\"\n#include \"virtual.h\"\n#include \"mixer.h\"\n#include \"period.h\"\n#include \"player.h\"\t/* for set_sample_end() */\n\n#ifdef LIBXMP_PAULA_SIMULATOR\n#include \"paula.h\"\n#endif\n\n\n#define DOWNMIX_SHIFT\t 12\n#define LIM8_HI\t\t 127\n#define LIM8_LO\t\t-128\n#define LIM16_HI\t 32767\n#define LIM16_LO\t-32768\n\n#define ANTICLICK_FPSHIFT\t24\n\nstruct loop_data\n{\n#define LOOP_PROLOGUE 1\n#define LOOP_EPILOGUE 2\n\tvoid *sptr;\n\tint start;\n\tint end;\n\tint first_loop;\n\tint _16bit;\n\tint active;\n\tint prologue_num;\n\tint epilogue_num;\n\tuint8 prologue[LOOP_PROLOGUE * 2 /* 16-bit */ * 2 /* stereo */];\n\tuint8 epilogue[LOOP_EPILOGUE * 2 /* 16-bit */ * 2 /* stereo */];\n};\n\n/* Mixers array index:\n *\n * bit 0: 0=8 bit sample, 1=16 bit sample\n * bit 1: 0=mono sample, 1=stereo sample\n * bit 2: 0=mono output, 1=stereo output\n * bit 3: 0=unfiltered, 1=filtered\n */\n\n#define FLAG_16_BITS\t0x01\n#define FLAG_STEREO\t0x02\n#define FLAG_STEREOOUT\t0x04\n#define FLAG_FILTER\t0x08\n#define FLAG_ACTIVE\t0x10\n/* #define FLAG_SYNTH\t0x20 */\n#define FIDX_FLAGMASK\t(FLAG_16_BITS | FLAG_STEREO | FLAG_STEREOOUT | FLAG_FILTER)\n\n#define MIX_FN(x) void libxmp_mix_##x(struct mixer_voice * LIBXMP_RESTRICT, \\\n\tint32 * LIBXMP_RESTRICT, int, int, int, int, int, int, int)\n\n#define DECLARE_MIX_FUNCTIONS(type) \\\n\tMIX_FN(monoout_mono_8bit_ ## type); \\\n\tMIX_FN(monoout_mono_16bit_ ## type); \\\n\tMIX_FN(monoout_stereo_8bit_ ## type); \\\n\tMIX_FN(monoout_stereo_16bit_ ## type); \\\n\tMIX_FN(stereoout_mono_8bit_ ## type); \\\n\tMIX_FN(stereoout_mono_16bit_ ## type); \\\n\tMIX_FN(stereoout_stereo_8bit_ ## type); \\\n\tMIX_FN(stereoout_stereo_16bit_ ## type)\n\n#define LIST_MIX_FUNCTIONS(type) \\\n\tlibxmp_mix_monoout_mono_8bit_ ## type, \\\n\tlibxmp_mix_monoout_mono_16bit_ ## type, \\\n\tlibxmp_mix_monoout_stereo_8bit_ ## type, \\\n\tlibxmp_mix_monoout_stereo_16bit_ ## type, \\\n\tlibxmp_mix_stereoout_mono_8bit_ ## type, \\\n\tlibxmp_mix_stereoout_mono_16bit_ ## type, \\\n\tlibxmp_mix_stereoout_stereo_8bit_ ## type, \\\n\tlibxmp_mix_stereoout_stereo_16bit_ ## type\n\nDECLARE_MIX_FUNCTIONS(nearest);\nDECLARE_MIX_FUNCTIONS(linear);\nDECLARE_MIX_FUNCTIONS(spline);\n\n#ifndef LIBXMP_CORE_DISABLE_IT\nDECLARE_MIX_FUNCTIONS(linear_filter);\nDECLARE_MIX_FUNCTIONS(spline_filter);\n#endif\n\n#ifdef LIBXMP_PAULA_SIMULATOR\nMIX_FN(monoout_mono_a500);\nMIX_FN(monoout_mono_a500_filter);\nMIX_FN(stereoout_mono_a500);\nMIX_FN(stereoout_mono_a500_filter);\n#endif\n\ntypedef void (*MIX_FP) (struct mixer_voice* LIBXMP_RESTRICT, int32* LIBXMP_RESTRICT, int, int, int, int, int, int, int);\n\nstatic const MIX_FP nearest_mixers[] = {\n\tLIST_MIX_FUNCTIONS(nearest),\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tLIST_MIX_FUNCTIONS(nearest)\n#endif\n};\n\nstatic const MIX_FP linear_mixers[] = {\n\tLIST_MIX_FUNCTIONS(linear),\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tLIST_MIX_FUNCTIONS(linear_filter)\n#endif\n};\n\nstatic const MIX_FP spline_mixers[] = {\n\tLIST_MIX_FUNCTIONS(spline),\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tLIST_MIX_FUNCTIONS(spline_filter)\n#endif\n};\n\n#ifdef LIBXMP_PAULA_SIMULATOR\n#define LIST_MIX_FUNCTIONS_PAULA(type) \\\n\tlibxmp_mix_monoout_mono_ ## type, NULL, NULL, NULL, \\\n\tlibxmp_mix_stereoout_mono_ ## type, NULL, NULL, NULL, \\\n\tNULL, NULL, NULL, NULL, \\\n\tNULL, NULL, NULL, NULL\n\nstatic const MIX_FP a500_mixers[] = {\n\tLIST_MIX_FUNCTIONS_PAULA(a500)\n};\n\nstatic const MIX_FP a500led_mixers[] = {\n\tLIST_MIX_FUNCTIONS_PAULA(a500_filter)\n};\n#endif\n\n\n/* Downmix 32bit samples to 8bit, signed or unsigned, mono or stereo output */\nstatic void downmix_int_8bit(char *dest, int32 *src, int num, int amp, int offs)\n{\n\tint smp;\n\tint shift = DOWNMIX_SHIFT + 8 - amp;\n\n\tfor (; num--; src++, dest++) {\n\t\tsmp = *src >> shift;\n\t\tif (smp > LIM8_HI) {\n\t\t\t*dest = LIM8_HI;\n\t\t} else if (smp < LIM8_LO) {\n\t\t\t*dest = LIM8_LO;\n\t\t} else {\n\t\t\t*dest = smp;\n\t\t}\n\n\t\tif (offs) *dest += offs;\n\t}\n}\n\n\n/* Downmix 32bit samples to 16bit, signed or unsigned, mono or stereo output */\nstatic void downmix_int_16bit(int16 *dest, int32 *src, int num, int amp, int offs)\n{\n\tint smp;\n\tint shift = DOWNMIX_SHIFT - amp;\n\n\tfor (; num--; src++, dest++) {\n\t\tsmp = *src >> shift;\n\t\tif (smp > LIM16_HI) {\n\t\t\t*dest = LIM16_HI;\n\t\t} else if (smp < LIM16_LO) {\n\t\t\t*dest = LIM16_LO;\n\t\t} else {\n\t\t\t*dest = smp;\n\t\t}\n\n\t\tif (offs) *dest += offs;\n\t}\n}\n\nstatic void anticlick(struct mixer_voice *vi)\n{\n\tvi->flags |= ANTICLICK;\n\tvi->old_vl = 0;\n\tvi->old_vr = 0;\n}\n\n/* Ok, it's messy, but it works :-) Hipolito */\nstatic void do_anticlick(struct context_data *ctx, int voc, int32 *buf, int count)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_data *s = &ctx->s;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\tint smp_l, smp_r;\n\tint discharge = s->ticksize >> ANTICLICK_SHIFT;\n\tint stepmul, stepval;\n\tuint32 stepmul_sq;\n\n\tsmp_l = vi->sleft;\n\tsmp_r = vi->sright;\n\tvi->sright = vi->sleft = 0;\n\n\tif (smp_l == 0 && smp_r == 0) {\n\t\treturn;\n\t}\n\n\tif (buf == NULL) {\n\t\tbuf = s->buf32;\n\t\tcount = discharge;\n\t} else if (count > discharge) {\n\t\tcount = discharge;\n\t}\n\n\tif (count <= 0) {\n\t\treturn;\n\t}\n\n\tstepval = (1 << ANTICLICK_FPSHIFT) / count;\n\tstepmul = stepval * count;\n\n\tif (~s->format & XMP_FORMAT_MONO) {\n\t\twhile ((stepmul -= stepval) > 0) {\n\t\t\t/* Truncate to 16-bits of precision so the product is 32-bits. */\n\t\t\tstepmul_sq = stepmul >> (ANTICLICK_FPSHIFT - 16);\n\t\t\tstepmul_sq *= stepmul_sq;\n\t\t\t*buf++ += (stepmul_sq * (int64)smp_l) >> 32;\n\t\t\t*buf++ += (stepmul_sq * (int64)smp_r) >> 32;\n\t\t}\n\t} else {\n\t\twhile ((stepmul -= stepval) > 0) {\n\t\t\tstepmul_sq = stepmul >> (ANTICLICK_FPSHIFT - 16);\n\t\t\tstepmul_sq *= stepmul_sq;\n\t\t\t*buf++ += (stepmul_sq * (int64)smp_l) >> 32;\n\t\t}\n\t}\n}\n\nstatic void set_sample_end(struct context_data *ctx, int voc, int end)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\tstruct channel_data *xc;\n\n\tif ((uint32)voc >= p->virt.maxvoc)\n\t\treturn;\n\n\txc = &p->xc_data[vi->chn];\n\n\tif (end) {\n\t\tSET_NOTE(NOTE_SAMPLE_END);\n\t\tvi->fidx &= ~FLAG_ACTIVE;\n\t\tif (HAS_QUIRK(QUIRK_RSTCHN)) {\n\t\t\tlibxmp_virt_resetvoice(ctx, voc, 0);\n\t\t}\n\t} else {\n\t\tRESET_NOTE(NOTE_SAMPLE_END);\n\t}\n}\n\n/* Back up sample data before and after loop and replace it for interpolation.\n * TODO: if higher order interpolation than spline is added, the copy needs to\n *       properly wrap around the loop data (modulo) for correct small loops.\n * TODO: use an overlap buffer like OpenMPT? This is easier, but a little dirty. */\nstatic void init_sample_wraparound(struct mixer_data *s, struct loop_data *ld,\n\t\t\t\t   struct mixer_voice *vi, struct xmp_sample *xxs)\n{\n\tint prologue_num = LOOP_PROLOGUE;\n\tint epilogue_num = LOOP_EPILOGUE;\n\tint bidir;\n\tint i;\n\n\tif (!vi->sptr || s->interp == XMP_INTERP_NEAREST || (~xxs->flg & XMP_SAMPLE_LOOP)) {\n\t\tld->active = 0;\n\t\treturn;\n\t}\n\n\tld->sptr = vi->sptr;\n\tld->start = vi->start;\n\tld->end = vi->end;\n\tld->first_loop = !(vi->flags & SAMPLE_LOOP);\n\tld->_16bit = (xxs->flg & XMP_SAMPLE_16BIT);\n\tld->active = 1;\n\n\t/* Stereo */\n\tif (xxs->flg & XMP_SAMPLE_STEREO) {\n\t\tld->start <<= 1;\n\t\tld->end <<= 1;\n\t\tprologue_num <<= 1;\n\t\tepilogue_num <<= 1;\n\t}\n\tld->prologue_num = prologue_num;\n\tld->epilogue_num = epilogue_num;\n\n\tbidir = vi->flags & VOICE_BIDIR;\n\n\tif (ld->_16bit) {\n\t\tuint16 *start = (uint16 *)ld->sptr + ld->start;\n\t\tuint16 *end = (uint16 *)ld->sptr + ld->end;\n\n\t\tmemcpy(ld->prologue, start - prologue_num, prologue_num * 2);\n\t\tmemcpy(ld->epilogue, end, epilogue_num * 2);\n\n\t\tif (!ld->first_loop) {\n\t\t\tfor (i = 0; i < prologue_num; i++) {\n\t\t\t\tint j = i - prologue_num;\n\t\t\t\tstart[j] = bidir ? start[-1 - j] : end[j];\n\t\t\t}\n\t\t}\n\t\tfor (i = 0; i < epilogue_num; i++) {\n\t\t\tend[i] = bidir ? end[-1 - i] : start[i];\n\t\t}\n\t} else {\n\t\tuint8 *start = (uint8 *)ld->sptr + ld->start;\n\t\tuint8 *end = (uint8 *)ld->sptr + ld->end;\n\n\t\tmemcpy(ld->prologue, start - prologue_num, prologue_num);\n\t\tmemcpy(ld->epilogue, end, epilogue_num);\n\n\t\tif (!ld->first_loop) {\n\t\t\tfor (i = 0; i < prologue_num; i++) {\n\t\t\t\tint j = i - prologue_num;\n\t\t\t\tstart[j] = bidir ? start[-1 - j] : end[j];\n\t\t\t}\n\t\t}\n\t\tfor (i = 0; i < epilogue_num; i++) {\n\t\t\tend[i] = bidir ? end[-1 - i] : start[i];\n\t\t}\n\t}\n}\n\n/* Restore old sample data from before and after loop. */\nstatic void reset_sample_wraparound(struct loop_data *ld)\n{\n\tint prologue_num = ld->prologue_num;\n\tint epilogue_num = ld->epilogue_num;\n\n\tif (!ld->active)\n\t\treturn;\n\n\tif (ld->_16bit) {\n\t\tuint16 *start = (uint16 *)ld->sptr + ld->start;\n\t\tuint16 *end = (uint16 *)ld->sptr + ld->end;\n\n\t\tmemcpy(start - prologue_num, ld->prologue, prologue_num * 2);\n\t\tmemcpy(end, ld->epilogue, epilogue_num * 2);\n\t} else {\n\t\tuint8 *start = (uint8 *)ld->sptr + ld->start;\n\t\tuint8 *end = (uint8 *)ld->sptr + ld->end;\n\n\t\tmemcpy(start - prologue_num, ld->prologue, prologue_num);\n\t\tmemcpy(end, ld->epilogue, epilogue_num);\n\t}\n}\n\nstatic int has_active_sustain_loop(struct context_data *ctx, struct mixer_voice *vi,\n\t\t\t\t   struct xmp_sample *xxs)\n{\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tstruct module_data *m = &ctx->m;\n\treturn vi->smp < m->mod.smp && (xxs->flg & XMP_SAMPLE_SLOOP) && (~vi->flags & VOICE_RELEASE);\n#else\n\treturn 0;\n#endif\n}\n\nstatic int has_active_loop(struct context_data *ctx, struct mixer_voice *vi,\n\t\t\t   struct xmp_sample *xxs)\n{\n\treturn (xxs->flg & XMP_SAMPLE_LOOP) || has_active_sustain_loop(ctx, vi, xxs);\n}\n\n/* Update the voice endpoints based on current sample loop state. */\nstatic void adjust_voice_end(struct context_data *ctx, struct mixer_voice *vi,\n\t\t\t     struct xmp_sample *xxs, struct extra_sample_data *xtra)\n{\n\tvi->flags &= ~VOICE_BIDIR;\n\n\tif (xtra && has_active_sustain_loop(ctx, vi, xxs)) {\n\t\tvi->start = xtra->sus;\n\t\tvi->end = xtra->sue;\n\t\tif (xxs->flg & XMP_SAMPLE_SLOOP_BIDIR) vi->flags |= VOICE_BIDIR;\n\n\t} else if (xxs->flg & XMP_SAMPLE_LOOP) {\n\t\tvi->start = xxs->lps;\n\t\tif ((xxs->flg & XMP_SAMPLE_LOOP_FULL) && (~vi->flags & SAMPLE_LOOP)) {\n\t\t\tvi->end = xxs->len;\n\t\t} else {\n\t\t\tvi->end = xxs->lpe;\n\t\t\tif (xxs->flg & XMP_SAMPLE_LOOP_BIDIR) vi->flags |= VOICE_BIDIR;\n\t\t}\n\t} else {\n\t\tvi->start = 0;\n\t\tvi->end = xxs->len;\n\t}\n}\n\nstatic int loop_reposition(struct context_data *ctx, struct mixer_voice *vi,\n\t\t\t   struct xmp_sample *xxs, struct extra_sample_data *xtra)\n{\n\tint loop_changed = !(vi->flags & SAMPLE_LOOP);\n\n\tvi->flags |= SAMPLE_LOOP;\n\n\tif(loop_changed)\n\t\tadjust_voice_end(ctx, vi, xxs, xtra);\n\n\tif (~vi->flags & VOICE_BIDIR) {\n\t\t/* Reposition for next loop */\n\t\tif (~vi->flags & VOICE_REVERSE)\n\t\t\tvi->pos -= vi->end - vi->start;\n\t\telse\n\t\t\tvi->pos += vi->end - vi->start;\n\t} else {\n\t\t/* Bidirectional loop: switch directions */\n\t\tvi->flags ^= VOICE_REVERSE;\n\n\t\t/* Wrap voice position around endpoint */\n\t\tif (vi->flags & VOICE_REVERSE) {\n\t\t\t/* OpenMPT Bidi-Loops.it: \"In Impulse Tracker's software\n\t\t\t * mixer, ping-pong loops are shortened by one sample.\"\n\t\t\t */\n\t\t\tvi->pos = vi->end * 2 - ctx->s.bidir_adjust - vi->pos;\n\t\t} else {\n\t\t\tvi->pos = vi->start * 2 - vi->pos;\n\t\t}\n\t}\n\t/* Safety check: pos should not be excessively past the sample end.\n\t * This only seems to happen with very low sample rates. */\n\tif (vi->pos > xxs->len + 1) {\n\t\tvi->pos = xxs->len + 1;\n\t}\n\treturn loop_changed;\n}\n\nstatic void hotswap_sample(struct context_data *ctx, struct mixer_voice *vi,\n int voc, int smp)\n{\n\tint vol = vi->vol;\n\tint pan = vi->pan;\n\tlibxmp_mixer_setpatch(ctx, voc, smp, 0);\n\tvi->flags |= SAMPLE_LOOP;\n\tvi->vol = vol;\n\tvi->pan = pan;\n}\n\nstatic void get_current_sample(struct context_data *ctx, struct mixer_voice *vi,\n struct xmp_sample **xxs, struct extra_sample_data **xtra, int *c5spd)\n{\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\n\tif (vi->smp < mod->smp) {\n\t\t*xxs = &mod->xxs[vi->smp];\n\t\t*xtra = &m->xtra[vi->smp];\n\t\t*c5spd = m->xtra[vi->smp].c5spd;\n\t} else {\n\t\t*xxs = &ctx->smix.xxs[vi->smp - mod->smp];\n\t\t*xtra = NULL;\n\t\t*c5spd = m->c4rate;\n\t}\n\tadjust_voice_end(ctx, vi, *xxs, *xtra);\n}\n\n/* Calculate the required number of sample frames to render a tick.\n * Returns -1 if any of the parameters are invalid. */\nint libxmp_mixer_get_ticksize(int freq, double time_factor, double rrate, int bpm)\n{\n\tdouble calc;\n\tint ticksize;\n\n\tif (freq <= 0 || bpm <= 0 || time_factor <= 0.0 || rrate <= 0.0) {\n\t\treturn -1;\n\t}\n\n\tcalc = freq * time_factor * rrate / bpm / 1000;\n\tif (calc > INT_MAX || calc != calc /* NaN */) {\n\t\treturn -1;\n\t}\n\n\tticksize = (int)calc;\n\n\tif (ticksize < (1 << ANTICLICK_SHIFT))\n\t\tticksize = 1 << ANTICLICK_SHIFT;\n\n\treturn ticksize;\n}\n\n/* Prepare the mixer for the next tick */\nvoid libxmp_mixer_prepare(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct mixer_data *s = &ctx->s;\n\tint bytelen;\n\n\ts->ticksize = libxmp_mixer_get_ticksize(s->freq, m->time_factor, m->rrate, p->bpm);\n\n\t/* Protect the mixer from broken values caused by xmp_set_tempo_factor. */\n\tif (s->ticksize < 0 || s->ticksize > (XMP_MAX_FRAMESIZE / 2)) {\n\t\ts->ticksize = XMP_MAX_FRAMESIZE / 2;\n\t}\n\n\tbytelen = s->ticksize * sizeof(int32);\n\tif (~s->format & XMP_FORMAT_MONO) {\n\t\tbytelen *= 2;\n\t}\n\tmemset(s->buf32, 0, bytelen);\n}\n\n/* Fill the output buffer calling one of the handlers. The buffer contains\n * sound for one tick (a PAL frame or 1/50s for standard vblank-timed mods)\n */\nvoid libxmp_mixer_softmixer(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_data *s = &ctx->s;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct extra_sample_data *xtra;\n\tstruct xmp_sample *xxs;\n\tstruct mixer_voice *vi;\n\tstruct loop_data loop_data;\n\tdouble step, step_dir;\n\tint samples, size;\n\tint vol, vol_l, vol_r, voc, usmp;\n\tint prev_l, prev_r = 0;\n\tint32 *buf_pos;\n\tMIX_FP  mix_fn;\n\tconst MIX_FP *mixerset;\n\n\tswitch (s->interp) {\n\tcase XMP_INTERP_NEAREST:\n\t\tmixerset = nearest_mixers;\n\t\tbreak;\n\tcase XMP_INTERP_LINEAR:\n\t\tmixerset = linear_mixers;\n\t\tbreak;\n\tcase XMP_INTERP_SPLINE:\n\t\tmixerset = spline_mixers;\n\t\tbreak;\n\tdefault:\n\t\tmixerset = linear_mixers;\n\t}\n\n#ifdef LIBXMP_PAULA_SIMULATOR\n\tif (p->flags & XMP_FLAGS_A500) {\n\t\tif (IS_AMIGA_MOD()) {\n\t\t\tif (p->filter) {\n\t\t\t\tmixerset = a500led_mixers;\n\t\t\t} else {\n\t\t\t\tmixerset = a500_mixers;\n\t\t\t}\n\t\t}\n\t}\n#endif\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\t/* OpenMPT Bidi-Loops.it: \"In Impulse Tracker's software\n\t * mixer, ping-pong loops are shortened by one sample.\"\n\t */\n\ts->bidir_adjust = IS_PLAYER_MODE_IT() ? 1 : 0;\n#endif\n\n\tlibxmp_mixer_prepare(ctx);\n\n\tfor (voc = 0; voc < p->virt.maxvoc; voc++) {\n\t\tint c5spd, rampsize, delta_l, delta_r;\n\n\t\tvi = &p->virt.voice_array[voc];\n\n\t\tif (vi->flags & ANTICLICK) {\n\t\t\tif (s->interp > XMP_INTERP_NEAREST) {\n\t\t\t\tdo_anticlick(ctx, voc, NULL, 0);\n\t\t\t}\n\t\t\tvi->flags &= ~ANTICLICK;\n\t\t}\n\n\t\tif (vi->chn < 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (vi->period < 1) {\n\t\t\tlibxmp_virt_resetvoice(ctx, voc, 1);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Negative positions can be left over from some\n\t\t * loop edge cases. These can be safely clamped. */\n\t\tif (vi->pos < 0.0)\n\t\t\tvi->pos = 0.0;\n\n\t\tvi->pos0 = vi->pos;\n\n\t\tbuf_pos = s->buf32;\n\t\tvol = vi->vol;\n\n\t\t/* Mix volume (S3M and IT) */\n\t\tif (m->mvolbase > 0 && m->mvol != m->mvolbase) {\n\t\t\tvol = vol * m->mvol / m->mvolbase;\n\t\t}\n\n\t\tif (vi->pan == PAN_SURROUND) {\n\t\t\tvol_l = vol * 0x80;\n\t\t\tvol_r = -vol * 0x80;\n\t\t} else {\n\t\t\tvol_l = vol * (0x80 - vi->pan);\n\t\t\tvol_r = vol * (0x80 + vi->pan);\n\t\t}\n\n\t\t/* Sample is paused - skip channel unless a new sample is queued. */\n\t\tif (vi->flags & SAMPLE_PAUSED) {\n\t\t\tif ((~vi->flags & SAMPLE_QUEUED) || vi->queued.smp < 0) {\n\t\t\t\tvi->flags &= ~SAMPLE_QUEUED;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\thotswap_sample(ctx, vi, voc, vi->queued.smp);\n\t\t\tget_current_sample(ctx, vi, &xxs, &xtra, &c5spd);\n\t\t\tvi->pos = vi->start;\n\t\t} else {\n\t\t\tget_current_sample(ctx, vi, &xxs, &xtra, &c5spd);\n\t\t}\n\n\t\tstep = C4_PERIOD * c5spd / s->freq / vi->period;\n\n\t\t/* Don't allow <=0, otherwise m5v-nwlf.it crashes\n\t\t * Extremely high values that can cause undefined float/int\n\t\t * conversion are also possible for c5spd modules. */\n\t\tif (step < 0.001 || step > (double)SHRT_MAX) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tinit_sample_wraparound(s, &loop_data, vi, xxs);\n\n\t\trampsize = s->ticksize >> ANTICLICK_SHIFT;\n\t\tdelta_l = (vol_l - vi->old_vl) / rampsize;\n\t\tdelta_r = (vol_r - vi->old_vr) / rampsize;\n\n\t\tfor (size = usmp = s->ticksize; size > 0; ) {\n\t\t\tint split_noloop = 0;\n\n\t\t\tif (p->xc_data[vi->chn].split) {\n\t\t\t\tsplit_noloop = 1;\n\t\t\t}\n\n\t\t\t/* How many samples we can write before the loop break\n\t\t\t * or sample end... */\n\t\t\tif (~vi->flags & VOICE_REVERSE) {\n\t\t\t\tif (vi->pos >= vi->end) {\n\t\t\t\t\tsamples = 0;\n\t\t\t\t\tif (--usmp <= 0)\n\t\t\t\t\t\tbreak;\n\t\t\t\t} else {\n\t\t\t\t\tdouble c = ceil(((double)vi->end - vi->pos) / step);\n\t\t\t\t\t/* ...inside the tick boundaries */\n\t\t\t\t\tif (c > size) {\n\t\t\t\t\t\tc = size;\n\t\t\t\t\t}\n\t\t\t\t\tsamples = c;\n\t\t\t\t}\n\t\t\t\tstep_dir = step;\n\t\t\t} else {\n\t\t\t\t/* Reverse */\n\t\t\t\tif (vi->pos <= vi->start) {\n\t\t\t\t\tsamples = 0;\n\t\t\t\t\tif (--usmp <= 0)\n\t\t\t\t\t\tbreak;\n\t\t\t\t} else {\n\t\t\t\t\tdouble c = ceil((vi->pos - (double)vi->start) / step);\n\t\t\t\t\tif (c > size) {\n\t\t\t\t\t\tc = size;\n\t\t\t\t\t}\n\t\t\t\t\tsamples = c;\n\t\t\t\t}\n\t\t\t\tstep_dir = -step;\n\t\t\t}\n\n\t\t\tif (vi->vol) {\n\t\t\t\tint mix_size = samples;\n\t\t\t\tint mixer_id = vi->fidx & FIDX_FLAGMASK;\n\n\t\t\t\tif (~s->format & XMP_FORMAT_MONO) {\n\t\t\t\t\tmix_size *= 2;\n\t\t\t\t}\n\n\t\t\t\t/* For Hipolito's anticlick routine */\n\t\t\t\tif (samples > 0) {\n\t\t\t\t\tif (~s->format & XMP_FORMAT_MONO) {\n\t\t\t\t\t\tprev_l = buf_pos[mix_size - 2];\n\t\t\t\t\t\tprev_r = buf_pos[mix_size - 1];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tprev_l = buf_pos[mix_size - 1];\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tprev_r = prev_l = 0;\n\t\t\t\t}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\t\t\t\t/* See OpenMPT env-flt-max.it */\n\t\t\t\tif (vi->filter.cutoff >= 0xfe &&\n\t\t\t\t    vi->filter.resonance == 0) {\n\t\t\t\t\tmixer_id &= ~FLAG_FILTER;\n\t\t\t\t}\n#endif\n\n\t\t\t\tmix_fn = mixerset[mixer_id];\n\n\t\t\t\t/* Call the output handler */\n\t\t\t\tif (samples > 0 && vi->sptr != NULL) {\n\t\t\t\t\tint rsize = 0;\n\n\t\t\t\t\tif (rampsize > samples) {\n\t\t\t\t\t\trampsize -= samples;\n\t\t\t\t\t} else {\n\t\t\t\t\t\trsize = samples - rampsize;\n\t\t\t\t\t\trampsize = 0;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (delta_l == 0 && delta_r == 0) {\n\t\t\t\t\t\t/* no need to ramp */\n\t\t\t\t\t\trsize = samples;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (mix_fn != NULL) {\n\t\t\t\t\t\tmix_fn(vi, buf_pos, samples,\n\t\t\t\t\t\t\tvol_l >> 8, vol_r >> 8, step_dir * (1 << SMIX_SHIFT), rsize, delta_l, delta_r);\n\t\t\t\t\t}\n\n\t\t\t\t\tbuf_pos += mix_size;\n\t\t\t\t\tvi->old_vl += samples * delta_l;\n\t\t\t\t\tvi->old_vr += samples * delta_r;\n\n\t\t\t\t\t/* For Hipolito's anticlick routine */\n\t\t\t\t\tif (~s->format & XMP_FORMAT_MONO) {\n\t\t\t\t\t\tvi->sleft = buf_pos[-2] - prev_l;\n\t\t\t\t\t\tvi->sright = buf_pos[-1] - prev_r;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvi->sleft = buf_pos[-1] - prev_l;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvi->pos += step_dir * samples;\n\t\t\tsize -= samples;\n\n\t\t\t/* One-shot samples do not loop. */\n\t\t\tif ((!has_active_loop(ctx, vi, xxs) || split_noloop) &&\n\t\t\t    !(vi->flags & SAMPLE_QUEUED)) {\n\t\t\t\tif (size > 0) {\n\t\t\t\t\tdo_anticlick(ctx, voc, buf_pos, size);\n\t\t\t\t\tset_sample_end(ctx, voc, 1);\n\t\t\t\t\t/* Next sample should ramp. */\n\t\t\t\t\tvol_l = vol_r = 0;\n\t\t\t\t}\n\t\t\t\tsize = 0;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* Loop before continuing to the next channel if the\n\t\t\t * tick is complete. This is particularly important\n\t\t\t * for reverse loops to avoid position clamping. */\n\t\t\tif (size > 0 ||\n\t\t\t    ((~vi->flags & VOICE_REVERSE) && vi->pos >= vi->end) ||\n\t\t\t     ((vi->flags & VOICE_REVERSE) && vi->pos <= vi->start)) {\n\t\t\t\tif (vi->flags & SAMPLE_QUEUED) {\n\t\t\t\t\t/* Protracker sample swap */\n\t\t\t\t\tdo_anticlick(ctx, voc, buf_pos, size);\n\t\t\t\t\tif (vi->queued.smp < 0 ||\n\t\t\t\t\t    (!has_active_loop(ctx, vi, xxs) &&\n\t\t\t\t\t     !(mod->xxs[vi->queued.smp].flg & XMP_SAMPLE_LOOP))) {\n\t\t\t\t\t\t/* Invalid samples and one-shots that\n\t\t\t\t\t\t * are being replaced by one-shots\n\t\t\t\t\t\t * (OpenMPT PTStoppedSwap.mod) stop\n\t\t\t\t\t\t * the current sample. If the current\n\t\t\t\t\t\t * sample is looped, it needs to be paused.\n\t\t\t\t\t\t */\n\t\t\t\t\t\tvi->flags &= ~SAMPLE_QUEUED;\n\t\t\t\t\t\tvi->flags |= SAMPLE_PAUSED;\n\t\t\t\t\t\tset_sample_end(ctx, voc, 1);\n\t\t\t\t\t\t/* Next sample should ramp. */\n\t\t\t\t\t\tvol_l = vol_r = 0;\n\t\t\t\t\t\tsize = 0;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\treset_sample_wraparound(&loop_data);\n\t\t\t\t\thotswap_sample(ctx, vi, voc, vi->queued.smp);\n\t\t\t\t\tget_current_sample(ctx, vi, &xxs, &xtra, &c5spd);\n\t\t\t\t\tinit_sample_wraparound(s, &loop_data, vi, xxs);\n\t\t\t\t\tvi->pos = vi->start;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (loop_reposition(ctx, vi, xxs, xtra)) {\n\t\t\t\t\treset_sample_wraparound(&loop_data);\n\t\t\t\t\tinit_sample_wraparound(s, &loop_data, vi, xxs);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treset_sample_wraparound(&loop_data);\n\t\tvi->old_vl = vol_l;\n\t\tvi->old_vr = vol_r;\n\t}\n\n\t/* Render final frame */\n\n\tsize = s->ticksize;\n\tif (~s->format & XMP_FORMAT_MONO) {\n\t\tsize *= 2;\n\t}\n\n\tif (size > XMP_MAX_FRAMESIZE) {\n\t\tsize = XMP_MAX_FRAMESIZE;\n\t}\n\n\tif (s->format & XMP_FORMAT_8BIT) {\n\t\tdownmix_int_8bit(s->buffer, s->buf32, size, s->amplify,\n\t\t\t\ts->format & XMP_FORMAT_UNSIGNED ? 0x80 : 0);\n\t} else {\n\t\tdownmix_int_16bit((int16 *)s->buffer, s->buf32, size, s->amplify,\n\t\t\t\ts->format & XMP_FORMAT_UNSIGNED ? 0x8000 : 0);\n\t}\n\n\ts->dtright = s->dtleft = 0;\n}\n\nvoid libxmp_mixer_voicepos(struct context_data *ctx, int voc, double pos, int ac)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\tstruct xmp_sample *xxs;\n\tstruct extra_sample_data *xtra;\n\n\t/* Position changes e.g. retrigger make the new sample take effect\n\t * if queued (OpenMPT InstrSwapRetrigger.mod). */\n\tif (vi->flags & SAMPLE_QUEUED) {\n\t\tvi->flags &= ~SAMPLE_QUEUED;\n\t\tif (vi->queued.smp < 0) {\n\t\t\tvi->flags |= SAMPLE_PAUSED;\n\t\t} else if (vi->smp != vi->queued.smp) {\n\t\t\thotswap_sample(ctx, vi, voc, vi->queued.smp);\n\t\t}\n\t\tvi->flags |= SAMPLE_LOOP;\n\t}\n\n\tif (vi->smp < m->mod.smp) {\n\t\txxs = &m->mod.xxs[vi->smp];\n\t\txtra = &m->xtra[vi->smp];\n\t} else {\n\t\txxs = &ctx->smix.xxs[vi->smp - m->mod.smp];\n\t\txtra = NULL;\n\t}\n\n\tif (xxs->flg & XMP_SAMPLE_SYNTH) {\n\t\treturn;\n\t}\n\n\tvi->pos = pos;\n\n\tadjust_voice_end(ctx, vi, xxs, xtra);\n\n\tif (vi->pos >= vi->end) {\n\t\tvi->pos = vi->end;\n\t\t/* Restart forward sample loops. */\n\t\tif ((~vi->flags & VOICE_REVERSE) && has_active_loop(ctx, vi, xxs))\n\t\t\tloop_reposition(ctx, vi, xxs, xtra);\n\t} else if ((vi->flags & VOICE_REVERSE) && vi->pos <= 0.1) {\n\t\t/* Hack: 0 maps to the end for reversed samples. */\n\t\tvi->pos = vi->end;\n\t}\n\n\tif (ac) {\n\t\tanticlick(vi);\n\t}\n}\n\ndouble libxmp_mixer_getvoicepos(struct context_data *ctx, int voc)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\tstruct xmp_sample *xxs;\n\n\txxs = libxmp_get_sample(ctx, vi->smp);\n\n\tif (xxs->flg & XMP_SAMPLE_SYNTH) {\n\t\treturn 0;\n\t}\n\n\treturn vi->pos;\n}\n\nvoid libxmp_mixer_setpatch(struct context_data *ctx, int voc, int smp, int ac)\n{\n\tstruct player_data *p = &ctx->p;\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tstruct module_data *m = &ctx->m;\n#endif\n\tstruct mixer_data *s = &ctx->s;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\tstruct xmp_sample *xxs;\n\n\txxs = libxmp_get_sample(ctx, smp);\n\n\tvi->smp = smp;\n\tvi->vol = 0;\n\tvi->pan = 0;\n\tvi->flags &= ~(SAMPLE_LOOP | SAMPLE_QUEUED | SAMPLE_PAUSED | VOICE_REVERSE | VOICE_BIDIR);\n\n\tvi->fidx = 0;\n\n\tif (~s->format & XMP_FORMAT_MONO) {\n\t\tvi->fidx |= FLAG_STEREOOUT;\n\t}\n\n\tset_sample_end(ctx, voc, 0);\n\n\t/*mixer_setvol(ctx, voc, 0);*/\n\n\tvi->sptr = xxs->data;\n\tvi->fidx |= FLAG_ACTIVE;\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tif (HAS_QUIRK(QUIRK_FILTER) && s->dsp & XMP_DSP_LOWPASS) {\n\t\tvi->fidx |= FLAG_FILTER;\n\t}\n#endif\n\n\tif (xxs->flg & XMP_SAMPLE_16BIT) {\n\t\tvi->fidx |= FLAG_16_BITS;\n\t}\n\tif (xxs->flg & XMP_SAMPLE_STEREO) {\n\t\tvi->fidx |= FLAG_STEREO;\n\t}\n\n\tlibxmp_mixer_voicepos(ctx, voc, 0, ac);\n}\n\n/**\n * Replace the current playing sample when it reaches the end of its\n * sample loop, a la Protracker 1/2. The new sample will begin playing\n * at the start of its loop if it is looped, the start of the sample if\n * it is a one-shot, and it will not play and instead pause the channel\n * if both the original and the new sample are one-shots or if the new\n * sample is empty/invalid/-1.\n */\nvoid libxmp_mixer_queuepatch(struct context_data *ctx, int voc, int smp)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\n\tif (smp != vi->smp || (vi->flags & SAMPLE_PAUSED)) {\n\t\tvi->queued.smp = smp;\n\t\tvi->flags |= SAMPLE_QUEUED;\n\t}\n}\n\nvoid libxmp_mixer_setnote(struct context_data *ctx, int voc, int note)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\n\t/* FIXME: Workaround for crash on notes that are too high\n\t *        see 6nations.it (+114 transposition on instrument 16)\n\t */\n\tif (note > 149) {\n\t\tnote = 149;\n\t}\n\n\tvi->note = note;\n\tvi->period = libxmp_note_to_period_mix(note, 0);\n\n\tanticlick(vi);\n}\n\nvoid libxmp_mixer_setperiod(struct context_data *ctx, int voc, double period)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\n\tvi->period = period;\n}\n\nvoid libxmp_mixer_setvol(struct context_data *ctx, int voc, int vol)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\n\tif (vol == 0) {\n\t\tanticlick(vi);\n\t}\n\n\tvi->vol = vol;\n}\n\nvoid libxmp_mixer_release(struct context_data *ctx, int voc, int rel)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\n\tif (rel) {\n#ifndef LIBXMP_CORE_DISABLE_IT\n\t\t/* Cancel voice reverse when releasing an active sustain loop,\n\t\t * unless the main loop is bidirectional. This is done both for\n\t\t * bidirectional sustain loops and for forward sustain loops\n\t\t * that have been reversed with MPT S9F Play Backward. */\n\t\tif (~vi->flags & VOICE_RELEASE) {\n\t\t\tstruct xmp_sample *xxs = libxmp_get_sample(ctx, vi->smp);\n\n\t\t\tif (has_active_sustain_loop(ctx, vi, xxs) &&\n\t\t\t    (~xxs->flg & XMP_SAMPLE_LOOP_BIDIR))\n\t\t\t\tvi->flags &= ~VOICE_REVERSE;\n\t\t}\n#endif\n\t\tvi->flags |= VOICE_RELEASE;\n\t} else {\n\t\tvi->flags &= ~VOICE_RELEASE;\n\t}\n}\n\nvoid libxmp_mixer_reverse(struct context_data *ctx, int voc, int rev)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\n\t/* Don't reverse samples that have already ended */\n\tif (~vi->fidx & FLAG_ACTIVE) {\n\t\treturn;\n\t}\n\n\tif (rev) {\n\t\tvi->flags |= VOICE_REVERSE;\n\t} else {\n\t\tvi->flags &= ~VOICE_REVERSE;\n\t}\n}\n\nvoid libxmp_mixer_seteffect(struct context_data *ctx, int voc, int type, int val)\n{\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\n\tswitch (type) {\n\tcase DSP_EFFECT_CUTOFF:\n\t\tvi->filter.cutoff = val;\n\t\tbreak;\n\tcase DSP_EFFECT_RESONANCE:\n\t\tvi->filter.resonance = val;\n\t\tbreak;\n\tcase DSP_EFFECT_FILTER_A0:\n\t\tvi->filter.a0 = val;\n\t\tbreak;\n\tcase DSP_EFFECT_FILTER_B0:\n\t\tvi->filter.b0 = val;\n\t\tbreak;\n\tcase DSP_EFFECT_FILTER_B1:\n\t\tvi->filter.b1 = val;\n\t\tbreak;\n\t}\n#endif\n}\n\nvoid libxmp_mixer_setpan(struct context_data *ctx, int voc, int pan)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n\n\tvi->pan = pan;\n}\n\nint libxmp_mixer_numvoices(struct context_data *ctx, int num)\n{\n\tstruct mixer_data *s = &ctx->s;\n\n\tif (num > s->numvoc || num < 0) {\n\t\treturn s->numvoc;\n\t} else {\n\t\treturn num;\n\t}\n}\n\nint libxmp_mixer_on(struct context_data *ctx, int rate, int format, int c4rate)\n{\n\tstruct mixer_data *s = &ctx->s;\n\tint total_size = 5 * rate * 2 / XMP_MIN_BPM; /* See xmp.h */\n\n\tif(total_size < XMP_MAX_FRAMESIZE)\n\t\ttotal_size = XMP_MAX_FRAMESIZE;\n\n\ts->buffer = (char *) calloc(total_size, sizeof(int16));\n\tif (s->buffer == NULL)\n\t\tgoto err;\n\n\ts->buf32 = (int32 *) calloc(total_size, sizeof(int32));\n\tif (s->buf32 == NULL)\n\t\tgoto err1;\n\n\ts->total_size = total_size;\n\ts->freq = rate;\n\ts->format = format;\n\ts->amplify = DEFAULT_AMPLIFY;\n\ts->mix = DEFAULT_MIX;\n\t/* s->pbase = C4_PERIOD * c4rate / s->freq; */\n\ts->interp = XMP_INTERP_LINEAR;\t/* default interpolation type */\n\ts->dsp = XMP_DSP_LOWPASS;\t/* enable filters by default */\n\t/* s->numvoc = SMIX_NUMVOC; */\n\ts->dtright = s->dtleft = 0;\n\ts->bidir_adjust = 0;\n\n\treturn 0;\n\n    err1:\n\tfree(s->buffer);\n\ts->buffer = NULL;\n    err:\n\treturn -1;\n}\n\nvoid libxmp_mixer_off(struct context_data *ctx)\n{\n\tstruct mixer_data *s = &ctx->s;\n\n\tfree(s->buffer);\n\tfree(s->buf32);\n\ts->buf32 = NULL;\n\ts->buffer = NULL;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/mixer.h",
    "content": "#ifndef LIBXMP_MIXER_H\n#define LIBXMP_MIXER_H\n\n#define C4_PERIOD\t428.0\n\n#define SMIX_NUMVOC\t128\t/* default number of softmixer voices */\n#define SMIX_SHIFT\t16\n#define SMIX_MASK\t0xffff\n\n#define FILTER_SHIFT\t22\n#define ANTICLICK_SHIFT\t3\n\n#ifdef LIBXMP_PAULA_SIMULATOR\n#include \"paula.h\"\n#endif\n\n#define MIXER(f) void libxmp_mix_##f(struct mixer_voice * LIBXMP_RESTRICT vi, \\\n\tint * LIBXMP_RESTRICT buffer, int count, int vl, int vr, int step, int ramp, \\\n\tint delta_l, int delta_r)\n\nstruct mixer_voice {\n\tint chn;\t\t/* channel number */\n\tint root;\t\t/* */\n\tint note;\t\t/* */\n#define PAN_SURROUND 0x8000\n\tint pan;\t\t/* */\n\tint vol;\t\t/* */\n\tdouble period;\t\t/* current period */\n\tdouble pos;\t\t/* position in sample */\n\tint pos0;\t\t/* position in sample before mixing */\n\tint fidx;\t\t/* mixer function index */\n\tint ins;\t\t/* instrument number */\n\tint smp;\t\t/* sample number */\n\tint start;\t\t/* loop start */\n\tint end;\t\t/* loop end */\n\tint act;\t\t/* nna info & status of voice */\n\tint key;\t\t/* key for DCA note check */\n\tint old_vl;\t\t/* previous volume, left channel */\n\tint old_vr;\t\t/* previous volume, right channel */\n\tint sleft;\t\t/* last left sample output, in 32bit */\n\tint sright;\t\t/* last right sample output, in 32bit */\n#define VOICE_RELEASE\t(1 << 0)\n#define ANTICLICK\t(1 << 1)\n#define SAMPLE_LOOP\t(1 << 2)\n#define VOICE_REVERSE\t(1 << 3)\n#define VOICE_BIDIR\t(1 << 4)\n#define SAMPLE_QUEUED\t(1 << 5)\n#define SAMPLE_PAUSED\t(1 << 6)\n\tint flags;\t\t/* flags */\n\tvoid *sptr;\t\t/* sample pointer */\n#ifdef LIBXMP_PAULA_SIMULATOR\n\tstruct paula_state *paula; /* paula simulation state */\n#endif\n\n\tstruct {\t\t/* Protracker queued instrument change */\n\t\tint smp;\n\t} queued;\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tstruct {\n\t\tint r1;\t\t/* filter variables */\n\t\tint r2;\n\t\tint l1;\n\t\tint l2;\n\t\tint a0;\n\t\tint b0;\n\t\tint b1;\n\t\tint cutoff;\n\t\tint resonance;\n\t} filter;\n#endif\n};\n\nint\tlibxmp_mixer_on\t\t(struct context_data *, int, int, int);\nvoid\tlibxmp_mixer_off\t(struct context_data *);\nvoid    libxmp_mixer_setvol\t(struct context_data *, int, int);\nvoid    libxmp_mixer_seteffect\t(struct context_data *, int, int, int);\nvoid    libxmp_mixer_setpan\t(struct context_data *, int, int);\nint\tlibxmp_mixer_numvoices\t(struct context_data *, int);\nvoid\tlibxmp_mixer_softmixer\t(struct context_data *);\nvoid\tlibxmp_mixer_reset\t(struct context_data *);\nvoid\tlibxmp_mixer_setpatch\t(struct context_data *, int, int, int);\nvoid\tlibxmp_mixer_queuepatch\t(struct context_data *, int, int);\nvoid\tlibxmp_mixer_voicepos\t(struct context_data *, int, double, int);\ndouble\tlibxmp_mixer_getvoicepos(struct context_data *, int);\nvoid\tlibxmp_mixer_setnote\t(struct context_data *, int, int);\nvoid\tlibxmp_mixer_setperiod\t(struct context_data *, int, double);\nvoid\tlibxmp_mixer_release\t(struct context_data *, int, int);\nvoid\tlibxmp_mixer_reverse\t(struct context_data *, int, int);\nint\tlibxmp_mixer_get_ticksize(int freq, double time_factor, double rrate, int bpm);\n\n#endif /* LIBXMP_MIXER_H */\n"
  },
  {
    "path": "contrib/libxmp/src/period.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#ifndef _GNU_SOURCE\n#define _GNU_SOURCE\n#endif\n\n#include \"common.h\"\n#include \"period.h\"\n\n#include <math.h>\n\n#ifdef LIBXMP_PAULA_SIMULATOR\n/*\n * Period table from the Protracker V2.1A play routine\n */\nstatic const uint16 pt_period_table[16][36] = {\n\t/* Tuning 0, Normal */\n\t{\n\t\t856,808,762,720,678,640,604,570,538,508,480,453,\n\t\t428,404,381,360,339,320,302,285,269,254,240,226,\n\t\t214,202,190,180,170,160,151,143,135,127,120,113\n\t},\n\t/* Tuning 1 */\n\t{\n\t\t850,802,757,715,674,637,601,567,535,505,477,450,\n\t\t425,401,379,357,337,318,300,284,268,253,239,225,\n\t\t213,201,189,179,169,159,150,142,134,126,119,113\n\t},\n\t/* Tuning 2 */\n\t{\n\t\t844,796,752,709,670,632,597,563,532,502,474,447,\n\t\t422,398,376,355,335,316,298,282,266,251,237,224,\n\t\t211,199,188,177,167,158,149,141,133,125,118,112\n\t},\n\t/* Tuning 3 */\n\t{\n\t\t838,791,746,704,665,628,592,559,528,498,470,444,\n\t\t419,395,373,352,332,314,296,280,264,249,235,222,\n\t\t209,198,187,176,166,157,148,140,132,125,118,111\n\t},\n\t/* Tuning 4 */\n\t{\n\t\t832,785,741,699,660,623,588,555,524,495,467,441,\n\t\t416,392,370,350,330,312,294,278,262,247,233,220,\n\t\t208,196,185,175,165,156,147,139,131,124,117,110\n\t},\n\t/* Tuning 5 */\n\t{\n\t\t826,779,736,694,655,619,584,551,520,491,463,437,\n\t\t413,390,368,347,328,309,292,276,260,245,232,219,\n\t\t206,195,184,174,164,155,146,138,130,123,116,109\n\t},\n\t/* Tuning 6 */\n\t{\n\t\t820,774,730,689,651,614,580,547,516,487,460,434,\n\t\t410,387,365,345,325,307,290,274,258,244,230,217,\n\t\t205,193,183,172,163,154,145,137,129,122,115,109\n\t},\n\t/* Tuning 7 */\n\t{\n\t\t814,768,725,684,646,610,575,543,513,484,457,431,\n\t\t407,384,363,342,323,305,288,272,256,242,228,216,\n\t\t204,192,181,171,161,152,144,136,128,121,114,108\n\t},\n\t/* Tuning -8 */\n\t{\n\t\t907,856,808,762,720,678,640,604,570,538,508,480,\n\t\t453,428,404,381,360,339,320,302,285,269,254,240,\n\t\t226,214,202,190,180,170,160,151,143,135,127,120\n\t},\n\t/* Tuning -7 */\n\t{\n\t\t900,850,802,757,715,675,636,601,567,535,505,477,\n\t\t450,425,401,379,357,337,318,300,284,268,253,238,\n\t\t225,212,200,189,179,169,159,150,142,134,126,119\n\t},\n\t/* Tuning -6 */\n\t{\n\t\t894,844,796,752,709,670,632,597,563,532,502,474,\n\t\t447,422,398,376,355,335,316,298,282,266,251,237,\n\t\t223,211,199,188,177,167,158,149,141,133,125,118\n\t},\n\t/* Tuning -5 */\n\t{\n\t\t887,838,791,746,704,665,628,592,559,528,498,470,\n\t\t444,419,395,373,352,332,314,296,280,264,249,235,\n\t\t222,209,198,187,176,166,157,148,140,132,125,118\n\t},\n\t/* Tuning -4 */\n\t{\n\t\t881,832,785,741,699,660,623,588,555,524,494,467,\n\t\t441,416,392,370,350,330,312,294,278,262,247,233,\n\t\t220,208,196,185,175,165,156,147,139,131,123,117\n\t},\n\t/* Tuning -3 */\n\t{\n\t\t875,826,779,736,694,655,619,584,551,520,491,463,\n\t\t437,413,390,368,347,328,309,292,276,260,245,232,\n\t\t219,206,195,184,174,164,155,146,138,130,123,116\n\t},\n\t/* Tuning -2 */\n\t{\n\t\t868,820,774,730,689,651,614,580,547,516,487,460,\n\t\t434,410,387,365,345,325,307,290,274,258,244,230,\n\t\t217,205,193,183,172,163,154,145,137,129,122,115\n\t},\n\t/* Tuning -1 */\n\t{\n\t\t862,814,768,725,684,646,610,575,543,513,484,457,\n\t\t431,407,384,363,342,323,305,288,272,256,242,228,\n\t\t216,203,192,181,171,161,152,144,136,128,121,114\n\t}\n};\n#endif\n\n#ifndef M_LN2\n#define M_LN2\t0.69314718055994530942\n#endif\n\nstatic inline double libxmp_round(double val)\n{\n\treturn (val >= 0.0)? floor(val + 0.5) : ceil(val - 0.5);\n}\n\n#ifdef LIBXMP_PAULA_SIMULATOR\n/* Get period from note using Protracker tuning */\nstatic inline int libxmp_note_to_period_pt(int n, int f)\n{\n\tif (n < MIN_NOTE_MOD || n > MAX_NOTE_MOD) {\n\t\treturn -1;\n\t}\n\n\tn -= 48;\n\tf >>= 4;\n\tif (f < -8 || f > 7) {\n\t\treturn 0;\n\t}\n\n\tif (f < 0) {\n\t\tf += 16;\n\t}\n\n\treturn (int)pt_period_table[f][n];\n}\n#endif\n\n/* Get period from note */\ndouble libxmp_note_to_period(struct context_data *ctx, int n, int f, double adj)\n{\n\tdouble d, per;\n\tstruct module_data *m = &ctx->m;\n#ifdef LIBXMP_PAULA_SIMULATOR\n\tstruct player_data *p = &ctx->p;\n\n\t/* If mod replayer, modrng and Amiga mixing are active */\n\tif (p->flags & XMP_FLAGS_A500) {\n\t\tif (IS_AMIGA_MOD()) {\n\t\t\treturn libxmp_note_to_period_pt(n, f);\n\t\t}\n\t}\n#endif\n\n\td = (double)n + (double)f / 128;\n\n\tswitch (m->period_type) {\n\tcase PERIOD_LINEAR:\n\t\tper = (240.0 - d) * 16;\t\t\t\t/* Linear */\n\t\tbreak;\n\tcase PERIOD_CSPD:\n\t\tper = 8363.0 * pow(2, n / 12.0) / 32 + f;\t/* Hz */\n\t\tbreak;\n\tdefault:\n\t\tper = PERIOD_BASE / pow(2, d / 12);\t\t/* Amiga */\n\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\tif (adj > 0.1) {\n\t\tper *= adj;\n\t}\n#endif\n\n\treturn per;\n}\n\n/* For the software mixer */\ndouble libxmp_note_to_period_mix(int n, int b)\n{\n\tdouble d = (double)n + (double)b / 12800;\n\treturn PERIOD_BASE / pow(2, d / 12);\n}\n\n/* Get note from period */\n/* This function is used only by the MOD loader */\nint libxmp_period_to_note(int p)\n{\n\tif (p <= 0) {\n\t\treturn 0;\n\t}\n\n\treturn libxmp_round(12.0 * log(PERIOD_BASE / p) / M_LN2) + 1;\n}\n\n/* Get pitchbend from base note and amiga period */\nint libxmp_period_to_bend(struct context_data *ctx, double p, int n, double adj)\n{\n\tstruct module_data *m = &ctx->m;\n\tdouble d;\n\n\tif (n == 0 || p < 0.1) {\n\t\treturn 0;\n\t}\n\n\tswitch (m->period_type) {\n\tcase PERIOD_LINEAR:\n\t\treturn 100 * (8 * (((240 - n) << 4) - p));\n\tcase PERIOD_CSPD:\n\t\td = libxmp_note_to_period(ctx, n, 0, adj);\n\t\treturn libxmp_round(100.0 * (1536.0 / M_LN2) * log(p / d));\n\tdefault:\n\t\t/* Amiga */\n\t\td = libxmp_note_to_period(ctx, n, 0, adj);\n\t\treturn libxmp_round(100.0 * (1536.0 / M_LN2) * log(d / p));\n\t}\n}\n\n/* Convert finetune = 1200 * log2(C2SPD/8363))\n *\n *      c = (1200.0 * log(c2spd) - 1200.0 * log(c4_rate)) / M_LN2;\n *      xpo = c/100;\n *      fin = 128 * (c%100) / 100;\n */\nvoid libxmp_c2spd_to_note(int c2spd, int *n, int *f)\n{\n\tint c;\n\n\tif (c2spd <= 0) {\n\t\t*n = *f = 0;\n\t\treturn;\n\t}\n\n\tc = (int)(1536.0 * log((double)c2spd / 8363) / M_LN2);\n\t*n = c / 128;\n\t*f = c % 128;\n}\n\n#ifndef LIBXMP_CORE_PLAYER\n/* Gravis Ultrasound frequency increments in steps of Hz/1024, where Hz is the\n * current rate of the card and is dependent on the active channel count.\n * For <=14 channels, the rate is 44100. For 15 to 32 channels, the rate is\n * round(14 * 44100 / active_channels).\n */\nstatic const double GUS_rates[19] = {\n\t/* <= 14 */ 44100.0,\n\t/* 15-20 */ 41160.0,  38587.5,  36317.65, 34300.0, 32494.74, 30870.0,\n\t/* 21-26 */ 29400.0,  28063.64, 26843.48, 25725.0, 24696.0,  23746.15,\n\t/* 27-32 */ 22866.67, 22050.0,  21289.66, 20580.0, 19916.13, 19294.75\n};\n\n/* Get a Gravis Ultrasound frequency offset in Hz for a given number of steps.\n */\ndouble libxmp_gus_frequency_steps(int num_steps, int num_channels_active)\n{\n\tCLAMP(num_channels_active, 14, 32);\n\treturn (num_steps * GUS_rates[num_channels_active - 14]) / 1024.0;\n}\n#endif\n"
  },
  {
    "path": "contrib/libxmp/src/period.h",
    "content": "#ifndef LIBXMP_PERIOD_H\n#define LIBXMP_PERIOD_H\n\n#define PERIOD_BASE\t13696.0\t\t/* C0 period */\n\n/* Macros for period conversion */\n#define NOTE_B0\t\t11\n#define NOTE_Bb0\t(NOTE_B0 + 1)\n#define MAX_NOTE\t(NOTE_B0 * 8)\n#define MAX_PERIOD\t0x1c56\n#define MIN_PERIOD_A\t0x0071\n#define MAX_PERIOD_A\t0x0358\n#define MIN_PERIOD_L\t0x0000\n#define MAX_PERIOD_L\t0x1e00\n#define MIN_NOTE_MOD\t48\n#define MAX_NOTE_MOD\t83\n\ndouble\tlibxmp_note_to_period\t(struct context_data *, int, int, double);\ndouble\tlibxmp_note_to_period_mix (int, int);\nint\tlibxmp_period_to_note\t(int);\nint\tlibxmp_period_to_bend\t(struct context_data *, double, int, double);\nvoid\tlibxmp_c2spd_to_note\t(int, int *, int *);\n#ifndef LIBXMP_CORE_PLAYER\ndouble\tlibxmp_gus_frequency_steps (int, int);\n#endif\n\n#endif /* LIBXMP_PERIOD_H */\n"
  },
  {
    "path": "contrib/libxmp/src/player.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/*\n * Sat, 18 Apr 1998 20:23:07 +0200  Frederic Bujon <lvdl@bigfoot.com>\n * Pan effect bug fixed: In Fastracker II the track panning effect erases\n * the instrument panning effect, and the same should happen in xmp.\n */\n\n/*\n * Fri, 26 Jun 1998 13:29:25 -0400 (EDT)\n * Reported by Jared Spiegel <spieg@phair.csh.rit.edu>\n * when the volume envelope is not enabled (disabled) on a sample, and a\n * notoff is delivered to ft2 (via either a noteoff in the note column or\n * command Kxx [where xx is # of ticks into row to give a noteoff to the\n * sample]), ft2 will set the volume of playback of the sample to 00h.\n *\n * Claudio's fix: implementing effect K\n */\n\n#include \"common.h\"\n#include \"virtual.h\"\n#include \"period.h\"\n#include \"effects.h\"\n#include \"player.h\"\n#include \"mixer.h\"\n#ifndef LIBXMP_CORE_PLAYER\n#include \"extras.h\"\n#endif\n\n/* Values for multi-retrig */\nstatic const struct retrig_control rval[] = {\n\t{   0,  1,  1 }, {  -1,  1,  1 }, {  -2,  1,  1 }, {  -4,  1,  1 },\n\t{  -8,  1,  1 }, { -16,  1,  1 }, {   0,  2,  3 }, {   0,  1,  2 },\n\t{   0,  1,  1 }, {   1,  1,  1 }, {   2,  1,  1 }, {   4,  1,  1 },\n\t{   8,  1,  1 }, {  16,  1,  1 }, {   0,  3,  2 }, {   0,  2,  1 },\n\t{   0,  0,  1 }\t\t/* Note cut */\n};\n\n\n/*\n * \"Anyway I think this is the most brilliant piece of crap we\n *  have managed to put up!\"\n *\t\t\t  -- Ice of FC about \"Mental Surgery\"\n */\n\n\n/* Envelope */\n\nstatic int check_envelope_end(struct xmp_envelope *env, int x)\n{\n\tint16 *data = env->data;\n\tint idx;\n\n\tif (~env->flg & XMP_ENVELOPE_ON || env->npt <= 0)\n\t\treturn 0;\n\n\tidx = (env->npt - 1) * 2;\n\n\t/* last node */\n\tif (x >= data[idx] || idx == 0) {\n\t\tif (~env->flg & XMP_ENVELOPE_LOOP) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int get_envelope(struct xmp_envelope *env, int x, int def)\n{\n\tint x1, x2, y1, y2;\n\tint16 *data = env->data;\n\tint idx;\n\n\tif (x < 0 || ~env->flg & XMP_ENVELOPE_ON || env->npt <= 0)\n\t\treturn def;\n\n\tidx = (env->npt - 1) * 2;\n\n\tx1 = data[idx]; /* last node */\n\tif (x >= x1 || idx == 0) {\n\t\treturn data[idx + 1];\n\t}\n\n\tdo {\n\t\tidx -= 2;\n\t\tx1 = data[idx];\n\t} while (idx > 0 && x1 > x);\n\n\t/* interpolate */\n\ty1 = data[idx + 1];\n\tx2 = data[idx + 2];\n\ty2 = data[idx + 3];\n\n\t/* Interpolation requires x1 <= x <= x2 */\n\tif (x < x1 || x2 < x1) return y1;\n\n\treturn x2 == x1 ? y2 : ((y2 - y1) * (x - x1) / (x2 - x1)) + y1;\n}\n\n#ifndef LIBXMP_CORE_PLAYER\n\nstatic int update_envelope_generic(struct xmp_envelope *env, int x, int release)\n{\n\tint16 *data = env->data;\n\tint has_loop, has_sus;\n\tint lpe, lps, sus;\n\n\thas_loop = env->flg & XMP_ENVELOPE_LOOP;\n\thas_sus = env->flg & XMP_ENVELOPE_SUS;\n\n\tlps = env->lps << 1;\n\tlpe = env->lpe << 1;\n\tsus = env->sus << 1;\n\n\t/* FT2 and IT envelopes behave in a different way regarding loops,\n\t * sustain and release. When the sustain point is at the end of the\n\t * envelope loop end and the key is released, FT2 escapes the loop\n\t * while IT runs another iteration. (See EnvLoops.xm in the OpenMPT\n\t * test cases.)\n\t * TODO: this is a bit suspicious, has little relation to the above\n\t * description, and had to be removed from the XM handler because it\n\t * broke a module (fade_2_grey_visage.xm). Retesting is required.\n\t */\n\tif (has_loop && has_sus && sus == lpe) {\n\t\tif (!release)\n\t\t\thas_sus = 0;\n\t}\n\n\t/* If the envelope point is set to somewhere after the sustain point\n\t * or sustain loop, enable release to prevent the envelope point from\n\t * returning to the sustain point or loop start. (See Filip Skutela's\n\t * farewell_tear.xm.)\n\t */\n\tif (has_loop && x > data[lpe] + 1) {\n\t\trelease = 1;\n\t} else if (has_sus && x > data[sus] + 1) {\n\t\trelease = 1;\n\t}\n\n\t/* If enabled, stay at the sustain point */\n\tif (has_sus && !release) {\n\t\tif (x >= data[sus]) {\n\t\t\tx = data[sus];\n\t\t}\n\t}\n\n\t/* XM-like formats and players assume that an envelope position past the\n\t * end of the loop or sustain point should return to the loop/sustain point.\n\t * While there are some differences with sustain points, this general loop\n\t * behavior is used by DigiBooster Pro, Digitrakker, Imago Orpheus, and\n\t * Real Tracker 2.\n\t */\n\tif (has_loop && x >= data[lpe]) {\n\t\t/* FT2 and IT envelopes behave in a different way regarding\n\t\t * loops, sustain and release. When the sustain point is at the\n\t\t * end of the envelope loop end and the key is released, FT2\n\t\t * escapes the loop while IT runs another iteration.\n\t\t * (See OpenMPT EnvLoops.xm)\n\t\t */\n\t\tif (!(release && has_sus && sus == lpe))\n\t\t\tx = data[lps];\n\t}\n\n\treturn x;\n}\n\n#endif\n\nstatic int update_envelope_xm(struct xmp_envelope *env, int x, int release)\n{\n\tint16 *data = env->data;\n\tint has_loop, has_sus;\n\tint lpe, lps, sus;\n\n\thas_loop = env->flg & XMP_ENVELOPE_LOOP;\n\thas_sus = env->flg & XMP_ENVELOPE_SUS;\n\n\tlps = env->lps << 1;\n\tlpe = env->lpe << 1;\n\tsus = env->sus << 1;\n\n\t/* If the envelope point is set to somewhere after the sustain point\n\t * or sustain loop, enable release to prevent the envelope point from\n\t * returning to the sustain point or loop start. (See Filip Skutela's\n\t * farewell_tear.xm.)\n\t */\n\tif (has_sus && x > data[sus] + 1) {\n\t\trelease = 1;\n\t}\n\n\t/* If enabled, stay at the sustain point */\n\tif (has_sus && !release) {\n\t\tif (x >= data[sus]) {\n\t\t\tx = data[sus];\n\t\t}\n\t}\n\n\t/* Envelope loops\n\t *\n\t * If the envelope point is set to somewhere after the sustain point\n\t * or sustain loop, the loop point is ignored to prevent the envelope\n\t * point from returning to the sustain point or loop start.\n\t * (See Filip Skutela's farewell_tear.xm or Ebony Owl Netsuke.xm.)\n\t*/\n\tif (has_loop && x == data[lpe]) {\n\t\t/* FT2 and IT envelopes behave in a different way regarding\n\t\t * loops, sustain and release. When the sustain point is at the\n\t\t * end of the envelope loop end and the key is released, FT2\n\t\t * escapes the loop while IT runs another iteration.\n\t\t * (See OpenMPT EnvLoops.xm)\n\t\t */\n\t\tif (!(release && has_sus && sus == lpe))\n\t\t\tx = data[lps];\n\t}\n\n\treturn x;\n}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\nstatic int update_envelope_it(struct xmp_envelope *env, int x, int release, int key_off)\n{\n\tint16 *data = env->data;\n\tint has_loop, has_sus;\n\tint lpe, lps, sus, sue;\n\n\thas_loop = env->flg & XMP_ENVELOPE_LOOP;\n\thas_sus = env->flg & XMP_ENVELOPE_SUS;\n\n\tlps = env->lps << 1;\n\tlpe = env->lpe << 1;\n\tsus = env->sus << 1;\n\tsue = env->sue << 1;\n\n\t/* Release at the end of a sustain loop, run another loop */\n\tif (has_sus && key_off && x == data[sue] + 1) {\n\t\tx = data[sus];\n\t} else\n\t/* If enabled, stay in the sustain loop */\n\tif (has_sus && !release) {\n\t\tif (x == data[sue] + 1) {\n\t\t\tx = data[sus];\n\t\t}\n\t} else\n\t/* Finally, execute the envelope loop */\n\tif (has_loop) {\n\t\tif (x > data[lpe]) {\n\t\t\tx = data[lps];\n\t\t}\n\t}\n\n\treturn x;\n}\n\n#endif\n\nstatic int update_envelope(struct context_data *ctx, struct xmp_envelope *env, int x, int release, int key_off)\n{\n\tstruct module_data *m = &ctx->m;\n\n\tif (x < 0xffff)\t{\t/* increment tick */\n\t\tx++;\n\t}\n\n\tif (x < 0) {\n\t\treturn -1;\n\t}\n\n\tif (~env->flg & XMP_ENVELOPE_ON || env->npt <= 0) {\n\t\treturn x;\n\t}\n\n\t(void) m; /* unused in xmp-lite with IT disabled */\n\n\treturn\n#ifndef LIBXMP_CORE_DISABLE_IT\n\t\tIS_PLAYER_MODE_IT() ?\n\t\tupdate_envelope_it(env, x, release, key_off) :\n#endif\n#ifndef LIBXMP_CORE_PLAYER\n\t\t!HAS_QUIRK(QUIRK_FT2ENV) ?\n\t\tupdate_envelope_generic(env, x, release) :\n#endif\n\t\tupdate_envelope_xm(env, x, release);\n}\n\n\n/* Returns: 0 if do nothing, <0 to reset channel, >0 if has fade */\nstatic int check_envelope_fade(struct xmp_envelope *env, int x)\n{\n\tint16 *data = env->data;\n\tint idx;\n\n\tif (~env->flg & XMP_ENVELOPE_ON)\n\t\treturn 0;\n\n\tidx = (env->npt - 1) * 2;\t\t/* last node */\n\tif (x > data[idx]) {\n\t\tif (data[idx + 1] == 0)\n\t\t\treturn -1;\n\t\telse\n\t\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\n/* Impulse Tracker's filter effects are implemented using its MIDI macros.\n * Any module can customize these and they are parameterized using various\n * player and mixer values, which requires parsing them here instead of in\n * the loader. Since they're MIDI macros, they can contain actual MIDI junk\n * that needs to be skipped, and one macro may have multiple IT commands. */\n\nstruct midi_stream\n{\n\tconst char *pos;\n\tint buffer;\n\tint param;\n};\n\nstatic int midi_nibble(struct context_data *ctx, struct channel_data *xc,\n\t\t       int chn, struct midi_stream *in)\n{\n\tstruct xmp_instrument *xxi;\n\tstruct mixer_voice *vi;\n\tint voc, val, byte = -1;\n\tif (in->buffer >= 0) {\n\t\tval = in->buffer;\n\t\tin->buffer = -1;\n\t\treturn val;\n\t}\n\n\twhile (*in->pos) {\n\t\tval = *(in->pos)++;\n\t\tif (val >= '0' && val <= '9') return val - '0';\n\t\tif (val >= 'A' && val <= 'F') return val - 'A' + 10;\n\t\tswitch (val) {\n\t\tcase 'z':\t\t\t/* Macro parameter */\n\t\t\tbyte = in->param;\n\t\t\tbreak;\n\t\tcase 'n':\t\t\t/* Host key */\n\t\t\tbyte = xc->key & 0x7f;\n\t\t\tbreak;\n\t\tcase 'h':\t\t\t/* Host channel */\n\t\t\tbyte = chn;\n\t\t\tbreak;\n\t\tcase 'o':\t\t\t/* Offset effect memory */\n\t\t\t/* Intentionally not clamped, see ZxxSecrets.it */\n\t\t\tbyte = xc->offset.memory;\n\t\t\tbreak;\n\t\tcase 'm':\t\t\t/* Voice reverse flag */\n\t\t\tvoc = libxmp_virt_mapchannel(ctx, chn);\n\t\t\tvi = (voc >= 0) ? &ctx->p.virt.voice_array[voc] : NULL;\n\t\t\tbyte = vi ? !!(vi->flags & VOICE_REVERSE) : 0;\n\t\t\tbreak;\n\t\tcase 'v':\t\t\t/* Note velocity */\n\t\t\txxi = libxmp_get_instrument(ctx, xc->ins);\n\t\t\tbyte = ((uint32)ctx->p.gvol *\n\t\t\t\t(uint32)xc->volume *\n\t\t\t\t(uint32)xc->mastervol *\n\t\t\t\t(uint32)xc->gvl *\n\t\t\t\t(uint32)(xxi ? xxi->vol : 0x40)) >> 24UL;\n\t\t\tCLAMP(byte, 1, 127);\n\t\t\tbreak;\n\t\tcase 'u':\t\t\t/* Computed velocity */\n\t\t\tbyte = xc->macro.finalvol >> 3;\n\t\t\tCLAMP(byte, 1, 127);\n\t\t\tbreak;\n\t\tcase 'x':\t\t\t/* Note panning */\n\t\t\tbyte = xc->macro.notepan >> 1;\n\t\t\tCLAMP(byte, 0, 127);\n\t\t\tbreak;\n\t\tcase 'y':\t\t\t/* Computed panning */\n\t\t\tbyte = xc->info_finalpan >> 1;\n\t\t\tCLAMP(byte, 0, 127);\n\t\t\tbreak;\n\t\tcase 'a':\t\t\t/* Ins MIDI Bank hi */\n\t\tcase 'b':\t\t\t/* Ins MIDI Bank lo */\n\t\tcase 'p':\t\t\t/* Ins MIDI Program */\n\t\tcase 's':\t\t\t/* MPT: SysEx checksum */\n\t\t\tbyte = 0;\n\t\t\tbreak;\n\t\tcase 'c':\t\t\t/* Ins MIDI Channel */\n\t\t\treturn 0;\n\t\t}\n\n\t\t/* Byte output */\n\t\tif (byte >= 0) {\n\t\t\tin->buffer = byte & 0xf;\n\t\t\treturn (byte >> 4) & 0xf;\n\t\t}\n\t}\n\treturn -1;\n}\n\nstatic int midi_byte(struct context_data *ctx, struct channel_data *xc,\n\t\t     int chn, struct midi_stream *in)\n{\n\tint a = midi_nibble(ctx, xc, chn, in);\n\tint b = midi_nibble(ctx, xc, chn, in);\n\treturn (a >= 0 && b >= 0) ? (a << 4) | b : -1;\n}\n\nstatic void apply_midi_macro_effect(struct channel_data *xc, int type, int val)\n{\n\tswitch (type) {\n\tcase 0:\t\t\t/* Filter cutoff */\n\t\txc->filter.cutoff = val << 1;\n\t\tbreak;\n\tcase 1:\t\t\t/* Filter resonance */\n\t\txc->filter.resonance = val << 1;\n\t\tbreak;\n\t}\n}\n\nstatic void execute_midi_macro(struct context_data *ctx, struct channel_data *xc,\n\t\t\t       int chn, struct midi_macro *midi, int param)\n{\n\tstruct midi_stream in;\n\tint byte, cmd, val;\n\n\tin.pos = midi->data;\n\tin.buffer = -1;\n\tin.param = param;\n\n\twhile (*in.pos) {\n\t\t/* Very simple MIDI 1.0 parser--most bytes can just be ignored\n\t\t * (or passed through, if libxmp gets MIDI output). All bytes\n\t\t * with bit 7 are statuses which interrupt unfinished messages\n\t\t * (\"Data Types: Status Bytes\") or are real time messages.\n\t\t * This holds even for SysEx messages, which end at ANY non-\n\t\t * real time status (\"System Common Messages: EOX\").\n\t\t *\n\t\t * IT intercepts internal \"messages\" that begin with F0 F0,\n\t\t * which in MIDI is a useless zero-length SysEx followed by\n\t\t * a second SysEx. They are four bytes long including F0 F0,\n\t\t * and shouldn't be passed through. OpenMPT also uses F0 F1.\n\t\t */\n\t\tcmd = -1;\n\t\tbyte = midi_byte(ctx, xc, chn, &in);\n\t\tif (byte == 0xf0) {\n\t\t\tbyte = midi_byte(ctx, xc, chn, &in);\n\t\t\tif (byte == 0xf0 || byte == 0xf1)\n\t\t\t\tcmd = byte & 0xf;\n\t\t}\n\t\tif (cmd < 0) {\n\t\t\tif (byte == 0xfa || byte == 0xfc || byte == 0xff) {\n\t\t\t\t/* These real time statuses can appear anywhere\n\t\t\t\t * (even in SysEx) and reset the channel filter\n\t\t\t\t * params. See: OpenMPT ZxxSecrets.it */\n\t\t\t\tapply_midi_macro_effect(xc, 0, 127);\n\t\t\t\tapply_midi_macro_effect(xc, 1, 0);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tcmd = midi_byte(ctx, xc, chn, &in) | (cmd << 8);\n\t\tval = midi_byte(ctx, xc, chn, &in);\n\t\tif (cmd < 0 || cmd >= 0x80 || val < 0 || val >= 0x80) {\n\t\t\tcontinue;\n\t\t}\n\t\tapply_midi_macro_effect(xc, cmd, val);\n\t}\n}\n\n/* This needs to occur before all process_* functions:\n * - It modifies the filter parameters, used by process_frequency.\n * - process_volume and process_pan apply slide effects, which the\n *   filter parameters expect to occur after macro effect parsing. */\nstatic void update_midi_macro(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tstruct midi_macro_data *midicfg = m->midi;\n\tstruct midi_macro *macro;\n\tint val;\n\n\tif (TEST(MIDI_MACRO) && HAS_QUIRK(QUIRK_FILTER)) {\n\t\tif (xc->macro.slide > 0) {\n\t\t\txc->macro.val += xc->macro.slide;\n\t\t\tif (xc->macro.val > xc->macro.target) {\n\t\t\t\txc->macro.val = xc->macro.target;\n\t\t\t\txc->macro.slide = 0;\n\t\t\t}\n\t\t} else if (xc->macro.slide < 0) {\n\t\t\txc->macro.val += xc->macro.slide;\n\t\t\tif (xc->macro.val < xc->macro.target) {\n\t\t\t\txc->macro.val = xc->macro.target;\n\t\t\t\txc->macro.slide = 0;\n\t\t\t}\n\t\t} else if (p->frame) {\n\t\t\t/* Execute non-smooth macros on frame 0 only */\n\t\t\treturn;\n\t\t}\n\n\t\tval = (int)xc->macro.val;\n\t\tif (val >= 0x80) {\n\t\t\tif (midicfg) {\n\t\t\t\tmacro = &midicfg->fixed[val - 0x80];\n\t\t\t\texecute_midi_macro(ctx, xc, chn, macro, val);\n\t\t\t} else if (val < 0x90) {\n\t\t\t\t/* Default fixed macro: set resonance */\n\t\t\t\tapply_midi_macro_effect(xc, 1, (val - 0x80) << 3);\n\t\t\t}\n\t\t} else if (midicfg) {\n\t\t\tmacro = &midicfg->param[xc->macro.active];\n\t\t\texecute_midi_macro(ctx, xc, chn, macro, val);\n\t\t} else if (xc->macro.active == 0) {\n\t\t\t/* Default parameterized macro 0: set filter cutoff */\n\t\t\tapply_midi_macro_effect(xc, 0, val);\n\t\t}\n\t}\n}\n\n#endif /* LIBXMP_CORE_DISABLE_IT */\n\n\n#ifndef LIBXMP_CORE_PLAYER\n\n/* From http://www.un4seen.com/forum/?topic=7554.0\n *\n * \"Invert loop\" effect replaces (!) sample data bytes within loop with their\n * bitwise complement (NOT). The parameter sets speed of altering the samples.\n * This effectively trashes the sample data. Because of that this effect was\n * supposed to be removed in the very next ProTracker versions, but it was\n * never removed.\n *\n * Prior to [Protracker 1.1A] this effect is called \"Funk Repeat\" and it moves\n * loop of the instrument (just the loop information - sample data is not\n * altered). The parameter is the speed of moving the loop.\n */\n\nstatic const int invloop_table[] = {\n\t0, 5, 6, 7, 8, 10, 11, 13, 16, 19, 22, 26, 32, 43, 64, 128\n};\n\nstatic void update_invloop(struct context_data *ctx, struct channel_data *xc)\n{\n\tstruct xmp_sample *xxs = libxmp_get_sample(ctx, xc->smp);\n\tstruct module_data *m = &ctx->m;\n\tint lps = 0, len = -1;\n\n\t/* If an instrument number is present, reset the position. */\n\tif (ctx->p.frame == 0 && TEST(NEW_INS)) {\n\t\txc->invloop.pos = 0;\n\t}\n\n\txc->invloop.count += invloop_table[xc->invloop.speed];\n\n\tif (xxs != NULL) {\n\t\tif (xxs->flg & XMP_SAMPLE_LOOP) {\n\t\t\tlps = xxs->lps;\n\t\t\tlen = xxs->lpe - lps;\n\t\t} else if (xxs->flg & XMP_SAMPLE_SLOOP) {\n\t\t\t/* Some formats that support invert loop use sustain\n\t\t\t * loops instead (Digital Symphony). */\n\t\t\tlps = m->xtra[xc->smp].sus;\n\t\t\tlen = m->xtra[xc->smp].sue - lps;\n\t\t}\n\t}\n\n\tif (len >= 0 && xc->invloop.count >= 128) {\n\t\txc->invloop.count = 0;\n\n\t\tif (++xc->invloop.pos > len) {\n\t\t\txc->invloop.pos = 0;\n\t\t}\n\n\t\tif (xxs->data == NULL) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (~xxs->flg & XMP_SAMPLE_16BIT) {\n\t\t\txxs->data[lps + xc->invloop.pos] ^= 0xff;\n\t\t}\n\t}\n}\n\n#endif\n\n/*\n * From OpenMPT Arpeggio.xm test:\n *\n * \"[FT2] Arpeggio behavior is very weird with more than 16 ticks per row. This\n *  comes from the fact that Fasttracker 2 uses a LUT for computing the arpeggio\n *  note (instead of doing something like tick%3 or similar). The LUT only has\n *  16 entries, so when there are more than 16 ticks, it reads beyond array\n *  boundaries. The vibrato table happens to be stored right after arpeggio\n *  table. The tables look like this in memory:\n *\n *    ArpTab: 0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0\n *    VibTab: 0,24,49,74,97,120,141,161,180,197,...\n *\n *  All values except for the first in the vibrato table are greater than 1, so\n *  they trigger the third arpeggio note. Keep in mind that Fasttracker 2 counts\n *  downwards, so the table has to be read from back to front, i.e. at 16 ticks\n *  per row, the 16th entry in the LUT is the first to be read. This is also the\n *  reason why Arpeggio is played 'backwards' in Fasttracker 2.\"\n */\nstatic int ft2_arpeggio(struct context_data *ctx, struct channel_data *xc)\n{\n\tstruct player_data *p = &ctx->p;\n\tint i;\n\n\tif (xc->arpeggio.val[1] == 0 && xc->arpeggio.val[2] == 0) {\n\t\treturn 0;\n\t}\n\n\tif (p->frame == 0) {\n\t\treturn 0;\n\t}\n\n\ti = p->speed - (p->frame % p->speed);\n\n\tif (i == 16) {\n\t\treturn 0;\n\t} else if (i > 16) {\n\t\treturn xc->arpeggio.val[2];\n\t}\n\n\treturn xc->arpeggio.val[i % 3];\n}\n\nstatic int arpeggio(struct context_data *ctx, struct channel_data *xc)\n{\n\tstruct module_data *m = &ctx->m;\n\tint arp;\n\n\tif (HAS_QUIRK(QUIRK_FT2BUGS)) {\n\t\tarp = ft2_arpeggio(ctx, xc);\n\t} else {\n\t\tarp = xc->arpeggio.val[xc->arpeggio.count];\n\t}\n\n\txc->arpeggio.count++;\n\txc->arpeggio.count %= xc->arpeggio.size;\n\n\treturn arp;\n}\n\nstatic int is_first_frame(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\n\tswitch (m->read_event_type) {\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tcase READ_EVENT_IT:\n\t\t/* fall through */\n#endif\n\tcase READ_EVENT_ST3:\n\t\treturn p->frame % p->speed == 0;\n\tdefault:\n\t\treturn p->frame == 0;\n\t}\n}\n\nstatic void reset_channels(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct smix_data *smix = &ctx->smix;\n\tstruct channel_data *xc;\n\tint i;\n\n#ifndef LIBXMP_CORE_PLAYER\n\tfor (i = 0; i < p->virt.virt_channels; i++) {\n\t\tvoid *extra;\n\n\t\txc = &p->xc_data[i];\n\t\textra = xc->extra;\n\t\tmemset(xc, 0, sizeof (struct channel_data));\n\t\txc->extra = extra;\n\t\tlibxmp_reset_channel_extras(ctx, xc);\n\t\txc->ins = -1;\n\t\txc->old_ins = 1;\t/* raw value */\n\t\txc->key = -1;\n\t\txc->volume = m->volbase;\n\t}\n#else\n\tfor (i = 0; i < p->virt.virt_channels; i++) {\n\t\txc = &p->xc_data[i];\n\t\tmemset(xc, 0, sizeof (struct channel_data));\n\t\txc->ins = -1;\n\t\txc->old_ins = 1;\t/* raw value */\n\t\txc->key = -1;\n\t\txc->volume = m->volbase;\n\t}\n#endif\n\n\tfor (i = 0; i < p->virt.num_tracks; i++) {\n\t\txc = &p->xc_data[i];\n\n\t\tif (i >= mod->chn && i < mod->chn + smix->chn) {\n\t\t\txc->mastervol = 0x40;\n\t\t\txc->pan.val = 0x80;\n\t\t} else {\n\t\t\txc->mastervol = mod->xxc[i].vol;\n\t\t\txc->pan.val = mod->xxc[i].pan;\n\t\t}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\t\txc->filter.cutoff = 0xff;\n\n\t\t/* Amiga split channel */\n\t\tif (mod->xxc[i].flg & XMP_CHANNEL_SPLIT) {\n\t\t\tint j;\n\n\t\t\txc->split = ((mod->xxc[i].flg & 0x30) >> 4) + 1;\n\t\t\t/* Connect split channel pairs */\n\t\t\tfor (j = 0; j < i; j++) {\n\t\t\t\tif (mod->xxc[j].flg & XMP_CHANNEL_SPLIT) {\n\t\t\t\t\tif (p->xc_data[j].split == xc->split) {\n\t\t\t\t\t\tp->xc_data[j].pair = i;\n\t\t\t\t\t\txc->pair = j;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\txc->split = 0;\n\t\t}\n#endif\n\n\t\t/* Surround channel */\n\t\tif (mod->xxc[i].flg & XMP_CHANNEL_SURROUND) {\n\t\t\txc->pan.surround = 1;\n\t\t}\n\t}\n}\n\nstatic int check_delay(struct context_data *ctx, struct xmp_event *e, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tstruct module_data *m = &ctx->m;\n\n\t/* Tempo affects delay and must be computed first */\n\tif ((e->fxt == FX_SPEED && e->fxp < 0x20) || e->fxt == FX_S3M_SPEED) {\n\t\tif (e->fxp) {\n\t\t\tp->speed = e->fxp;\n\t\t}\n\t}\n\tif ((e->f2t == FX_SPEED && e->f2p < 0x20) || e->f2t == FX_S3M_SPEED) {\n\t\tif (e->f2p) {\n\t\t\tp->speed = e->f2p;\n\t\t}\n\t}\n\n\t/* Delay event read */\n\tif (e->fxt == FX_EXTENDED && MSN(e->fxp) == EX_DELAY && LSN(e->fxp)) {\n\t\txc->delay = LSN(e->fxp) + 1;\n\t\tgoto do_delay;\n\t}\n\tif (e->f2t == FX_EXTENDED && MSN(e->f2p) == EX_DELAY && LSN(e->f2p)) {\n\t\txc->delay = LSN(e->f2p) + 1;\n\t\tgoto do_delay;\n\t}\n#ifndef LIBXMP_CORE_PLAYER\n\t/* MED retrigger: reset retrigger so it doesn't continue during the delay. */\n\tif (e->fxt == FX_MED_RETRIG && MSN(e->fxp)) {\n\t\tRESET(RETRIG);\n\t\txc->delay = MSN(e->fxp) + 1;\n\t\tgoto do_delay;\n\t}\n\tif (e->f2t == FX_MED_RETRIG && MSN(e->f2p)) {\n\t\tRESET(RETRIG);\n\t\txc->delay = MSN(e->fxp) + 1;\n\t\tgoto do_delay;\n\t}\n#endif\n\n\treturn 0;\n\n    do_delay:\n\tmemcpy(&xc->delayed_event, e, sizeof (struct xmp_event));\n\n\tif (e->ins) {\n\t\txc->delayed_ins = e->ins;\n\t}\n\n\tif (HAS_QUIRK(QUIRK_RTDELAY)) {\n\t\tif (e->vol == 0 && e->f2t == 0 && e->ins == 0 && e->note != XMP_KEY_OFF)\n\t\t\txc->delayed_event.vol = xc->volume + 1;\n\t\tif (e->note == 0)\n\t\t\txc->delayed_event.note = xc->key + 1;\n\t\tif (e->ins == 0)\n\t\t\txc->delayed_event.ins = xc->old_ins;\n\t}\n\n\treturn 1;\n}\n\nstatic inline void read_row(struct context_data *ctx, int pat, int row)\n{\n\tint chn;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct player_data *p = &ctx->p;\n\tstruct flow_control *f = &p->flow;\n\tstruct xmp_event ev;\n\n\tfor (chn = 0; chn < mod->chn; chn++) {\n\t\tconst int num_rows = mod->xxt[TRACK_NUM(pat, chn)]->rows;\n\t\tif (row < num_rows) {\n\t\t\tmemcpy(&ev, &EVENT(pat, chn, row), sizeof(ev));\n\t\t} else {\n\t\t\tmemset(&ev, 0, sizeof(ev));\n\t\t}\n\n\t\tif (ev.note == XMP_KEY_OFF) {\n\t\t\tint env_on = 0;\n\t\t\tint ins = ev.ins - 1;\n\n\t\t\tif (IS_VALID_INSTRUMENT(ins) &&\n\t\t\t    (mod->xxi[ins].aei.flg & XMP_ENVELOPE_ON)) {\n\t\t\t\tenv_on = 1;\n\t\t\t}\n\n\t\t\tif (ev.fxt == FX_EXTENDED && MSN(ev.fxp) == EX_DELAY) {\n\t\t\t\tif (ev.ins && (LSN(ev.fxp) || env_on)) {\n\t\t\t\t\tif (LSN(ev.fxp)) {\n\t\t\t\t\t\tev.note = 0;\n\t\t\t\t\t}\n\t\t\t\t\tev.fxp = ev.fxt = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (check_delay(ctx, &ev, chn) == 0) {\n\t\t\t/* rowdelay_set bit 1 is set only in the first tick of the row\n\t\t\t * event if the delay causes the tick count resets to 0. We test\n\t\t\t * it to read row events only in the start of the row. (see the\n\t\t\t * OpenMPT test case FineVolColSlide.it)\n\t\t\t */\n\t\t\tif (!f->rowdelay_set || ((f->rowdelay_set & ROWDELAY_FIRST_FRAME) && f->rowdelay > 0)) {\n\t\t\t\tlibxmp_read_event(ctx, &ev, chn);\n#ifndef LIBXMP_CORE_PLAYER\n\t\t\t\tlibxmp_med_hold_hack(ctx, pat, chn, row);\n#endif\n\t\t\t}\n\t\t} else {\n\t\t\tif (IS_PLAYER_MODE_IT()) {\n\t\t\t\t/* Reset flags. See SlideDelay.it */\n\t\t\t\tp->xc_data[chn].flags = 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic inline int get_channel_vol(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tint root;\n\n\t/* channel is a root channel */\n\tif (chn < p->virt.num_tracks)\n\t\treturn p->channel_vol[chn];\n\n\t/* channel is invalid */\n\tif (chn >= p->virt.virt_channels)\n\t\treturn 0;\n\n\t/* root is invalid */\n\troot = libxmp_virt_getroot(ctx, chn);\n\tif (root < 0)\n\t\treturn 0;\n\n\treturn p->channel_vol[root];\n}\n\nstatic int tremor_ft2(struct context_data *ctx, int chn, int finalvol)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\n\tif (xc->tremor.count & 0x80) {\n\t\tif (TEST(TREMOR) && p->frame != 0) {\n\t\t\txc->tremor.count &= ~0x20;\n\t\t\tif (xc->tremor.count == 0x80) {\n\t\t\t\t/* end of down cycle, set up counter for up  */\n\t\t\t\txc->tremor.count = xc->tremor.up | 0xc0;\n\t\t\t} else if (xc->tremor.count == 0xc0) {\n\t\t\t\t/* end of up cycle, set up counter for down */\n\t\t\t\txc->tremor.count = xc->tremor.down | 0x80;\n\t\t\t} else {\n\t\t\t\txc->tremor.count--;\n\t\t\t}\n\t\t}\n\n\t\tif ((xc->tremor.count & 0xe0) == 0x80) {\n\t\t\tfinalvol = 0;\n\t\t}\n\t}\n\n\treturn finalvol;\n}\n\nstatic int tremor_s3m(struct context_data *ctx, int chn, int finalvol)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\n\tif (TEST(TREMOR)) {\n\t\tif (xc->tremor.count == 0) {\n\t\t\t/* end of down cycle, set up counter for up  */\n\t\t\txc->tremor.count = xc->tremor.up | 0x80;\n\t\t} else if (xc->tremor.count == 0x80) {\n\t\t\t/* end of up cycle, set up counter for down */\n\t\t\txc->tremor.count = xc->tremor.down;\n\t\t}\n\n\t\txc->tremor.count--;\n\n\t\tif (~xc->tremor.count & 0x80) {\n\t\t\tfinalvol = 0;\n\t\t}\n\t}\n\n\treturn finalvol;\n}\n\n/*\n * Update channel data\n */\n\n#define DOENV_RELEASE ((TEST_NOTE(NOTE_ENV_RELEASE) || act == VIRT_ACTION_OFF))\n\nstatic void process_volume(struct context_data *ctx, int chn, int act)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tstruct xmp_instrument *instrument;\n\tint finalvol;\n\tuint16 vol_envelope;\n\tint fade = 0;\n\n\tinstrument = libxmp_get_instrument(ctx, xc->ins);\n\n\t/* Keyoff and fadeout */\n\n\t/* Keyoff event in IT doesn't reset fadeout (see jeff93.it)\n\t * In XM it depends on envelope (see graff-strange_land.xm vs\n\t * Decibelter - Cosmic 'Wegian Mamas.xm)\n\t */\n\tif (HAS_QUIRK(QUIRK_KEYOFF)) {\n\t\t/* If IT, only apply fadeout on note release if we don't\n\t\t * have envelope, or if we have envelope loop\n\t\t */\n\t\tif (TEST_NOTE(NOTE_ENV_RELEASE) || act == VIRT_ACTION_OFF) {\n\t\t\tif ((~instrument->aei.flg & XMP_ENVELOPE_ON) ||\n\t\t\t    (instrument->aei.flg & XMP_ENVELOPE_LOOP)) {\n\t\t\t\tfade = 1;\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif (~instrument->aei.flg & XMP_ENVELOPE_ON) {\n\t\t\tif (TEST_NOTE(NOTE_ENV_RELEASE)) {\n\t\t\t\txc->fadeout = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (TEST_NOTE(NOTE_ENV_RELEASE) || act == VIRT_ACTION_OFF) {\n\t\t\tfade = 1;\n\t\t}\n\t}\n\n\tif (!TEST_PER(VENV_PAUSE)) {\n\t\txc->v_idx = update_envelope(ctx, &instrument->aei, xc->v_idx,\n\t\t\tDOENV_RELEASE, TEST(KEY_OFF));\n\t}\n\n\tvol_envelope = get_envelope(&instrument->aei, xc->v_idx, 64);\n\tif (check_envelope_end(&instrument->aei, xc->v_idx)) {\n\t\tif (vol_envelope == 0) {\n\t\t\tSET_NOTE(NOTE_END);\n\t\t}\n\t\tSET_NOTE(NOTE_ENV_END);\n\t}\n\n\t/* IT starts fadeout automatically at the end of the volume envelope. */\n\tswitch (check_envelope_fade(&instrument->aei, xc->v_idx)) {\n\tcase -1:\n\t\tSET_NOTE(NOTE_END);\n\t\t/* Don't reset channel, we may have a tone portamento later\n\t\t * virt_resetchannel(ctx, chn);\n\t\t */\n\t\tbreak;\n\tcase 0:\n\t\tbreak;\n\tdefault:\n\t\tif (HAS_QUIRK(QUIRK_ENVFADE)) {\n\t\t\tSET_NOTE(NOTE_FADEOUT);\n\t\t}\n\t}\n\n\t/* IT envelope fadeout starts immediately after the envelope tick,\n\t * so process fadeout after the volume envelope. */\n\tif (TEST_NOTE(NOTE_FADEOUT) || act == VIRT_ACTION_FADE) {\n\t\tfade = 1;\n\t}\n\n\tif (fade) {\n\t\tif (xc->fadeout > xc->ins_fade) {\n\t\t\txc->fadeout -= xc->ins_fade;\n\t\t} else {\n\t\t\txc->fadeout = 0;\n\t\t\tSET_NOTE(NOTE_END);\n\t\t}\n\t}\n\n\t/* If note ended in background channel, we can safely reset it */\n\tif (TEST_NOTE(NOTE_END) && chn >= p->virt.num_tracks) {\n\t\tlibxmp_virt_resetchannel(ctx, chn);\n\t\treturn;\n\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\tfinalvol = libxmp_extras_get_volume(ctx, xc);\n#else\n\tfinalvol = xc->volume;\n#endif\n\n\tif (IS_PLAYER_MODE_IT()) {\n\t\tfinalvol = xc->volume * (100 - xc->rvv) / 100;\n\t}\n\n\tif (TEST(TREMOLO)) {\n\t\t/* OpenMPT VibratoReset.mod */\n\t\tif (!is_first_frame(ctx) || !HAS_QUIRK(QUIRK_PROTRACK)) {\n\t\t\tfinalvol += libxmp_lfo_get(ctx, &xc->tremolo.lfo, 0) / (1 << 6);\n\t\t}\n\n\t\tif (!is_first_frame(ctx) || HAS_QUIRK(QUIRK_VIBALL)) {\n\t\t\tlibxmp_lfo_update(&xc->tremolo.lfo);\n\t\t}\n\t}\n\n\tCLAMP(finalvol, 0, m->volbase);\n\n\tfinalvol = (finalvol * xc->fadeout) >> 6;\t/* 16 bit output */\n\n\tfinalvol = (uint32)(vol_envelope * p->gvol * xc->mastervol /\n\t\tm->gvolbase * ((int)finalvol * 0x40 / m->volbase)) >> 18;\n\n\t/* Apply channel volume */\n\tfinalvol = finalvol * get_channel_vol(ctx, chn) / 100;\n\n#ifndef LIBXMP_CORE_PLAYER\n\t/* Volume translation table (for PTM, ARCH, COCO) */\n\tif (m->vol_table) {\n\t\tfinalvol = m->volbase == 0xff ?\n\t\t    m->vol_table[finalvol >> 2] << 2 :\n\t\t    m->vol_table[finalvol >> 4] << 4;\n\t}\n#endif\n\n\tif (HAS_QUIRK(QUIRK_INSVOL)) {\n\t\tfinalvol = (finalvol * instrument->vol * xc->gvl) >> 12;\n\t}\n\n\tif (IS_PLAYER_MODE_FT2()) {\n\t\tfinalvol = tremor_ft2(ctx, chn, finalvol);\n\t} else {\n\t\tfinalvol = tremor_s3m(ctx, chn, finalvol);\n\t}\n#ifndef LIBXMP_CORE_DISABLE_IT\n\txc->macro.finalvol = finalvol;\n#endif\n\n\tif (chn < m->mod.chn) {\n\t\tfinalvol = finalvol * p->master_vol / 100;\n\t} else {\n\t\tfinalvol = finalvol * p->smix_vol / 100;\n\t}\n\n\txc->info_finalvol = TEST_NOTE(NOTE_SAMPLE_END) ? 0 : finalvol;\n\n\tlibxmp_virt_setvol(ctx, chn, finalvol);\n\n\t/* Check Amiga split channel */\n\tif (xc->split) {\n\t\tlibxmp_virt_setvol(ctx, xc->pair, finalvol);\n\t}\n}\n\nstatic void process_frequency(struct context_data *ctx, int chn, int act)\n{\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tstruct mixer_data *s = &ctx->s;\n#endif\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tstruct xmp_instrument *instrument;\n\tdouble period, vibrato;\n\tdouble final_period;\n\tint linear_bend;\n\tint frq_envelope;\n\tint arp;\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tint cutoff, resonance;\n#endif\n\n\tinstrument = libxmp_get_instrument(ctx, xc->ins);\n\n\tif (!TEST_PER(FENV_PAUSE)) {\n\t\txc->f_idx = update_envelope(ctx, &instrument->fei, xc->f_idx,\n\t\t\tDOENV_RELEASE, TEST(KEY_OFF));\n\t}\n\tfrq_envelope = get_envelope(&instrument->fei, xc->f_idx, 0);\n\n#ifndef LIBXMP_CORE_PLAYER\n\t/* Do note slide */\n\n\tif (TEST(NOTE_SLIDE)) {\n\t\tif (xc->noteslide.count == 0) {\n\t\t\txc->note += xc->noteslide.slide;\n\t\t\txc->period = libxmp_note_to_period(ctx, xc->note,\n\t\t\t\t\txc->finetune, xc->per_adj);\n\t\t\txc->noteslide.count = xc->noteslide.speed;\n\t\t}\n\t\txc->noteslide.count--;\n\n\t\tlibxmp_virt_setnote(ctx, chn, xc->note);\n\t}\n#endif\n\n\t/* Instrument vibrato */\n\tvibrato = 1.0 * libxmp_lfo_get(ctx, &xc->insvib.lfo, 1) /\n\t\t\t\t(4096 * (1 + xc->insvib.sweep));\n\tlibxmp_lfo_update(&xc->insvib.lfo);\n\tif (xc->insvib.sweep > 1) {\n\t\txc->insvib.sweep -= 2;\n\t} else {\n\t\txc->insvib.sweep = 0;\n\t}\n\n\t/* Vibrato */\n\tif (TEST(VIBRATO) || TEST_PER(VIBRATO)) {\n\t\t/* OpenMPT VibratoReset.mod */\n\t\tif (!is_first_frame(ctx) || !HAS_QUIRK(QUIRK_PROTRACK)) {\n\t\t\tint shift = HAS_QUIRK(QUIRK_VIBHALF) ? 10 : 9;\n\t\t\tint vib = libxmp_lfo_get(ctx, &xc->vibrato.lfo, 1) / (1 << shift);\n\n\t\t\tif (HAS_QUIRK(QUIRK_VIBINV)) {\n\t\t\t\tvibrato -= vib;\n\t\t\t} else {\n\t\t\t\tvibrato += vib;\n\t\t\t}\n\t\t}\n\n\t\tif (!is_first_frame(ctx) || HAS_QUIRK(QUIRK_VIBALL)) {\n\t\t\tlibxmp_lfo_update(&xc->vibrato.lfo);\n\t\t}\n\t}\n\n\tperiod = xc->period;\n#ifndef LIBXMP_CORE_PLAYER\n\tperiod += libxmp_extras_get_period(ctx, xc);\n#endif\n\n\tif (HAS_QUIRK(QUIRK_ST3BUGS)) {\n\t\tif (period < 0.25) {\n\t\t\tlibxmp_virt_resetchannel(ctx, chn);\n\t\t}\n\t}\n\t/* Sanity check */\n\tif (period < 0.1) {\n\t\tperiod = 0.1;\n\t}\n\n\t/* Arpeggio */\n\tarp = arpeggio(ctx, xc);\n\n\t/* Pitch bend */\n\n\tlinear_bend = libxmp_period_to_bend(ctx, period + vibrato, xc->note, xc->per_adj);\n\n\tif (TEST_NOTE(NOTE_GLISSANDO) && TEST(TONEPORTA)) {\n\t\tif (linear_bend > 0) {\n\t\t\tlinear_bend = (linear_bend + 6400) / 12800 * 12800;\n\t\t} else if (linear_bend < 0) {\n\t\t\tlinear_bend = (linear_bend - 6400) / 12800 * 12800;\n\t\t}\n\t}\n\n\tif (HAS_QUIRK(QUIRK_FT2BUGS)) {\n\t\tif  (arp) {\n\t\t\t/* OpenMPT ArpSlide.xm */\n\t\t\tlinear_bend = linear_bend / 12800 * 12800 +\n\t\t\t\t\t\t\txc->finetune * 100;\n\n\t\t\t/* OpenMPT ArpeggioClamp.xm */\n\t\t\tif (xc->note + arp > 107) {\n\t\t\t\tif (p->speed - (p->frame % p->speed) > 0) {\n\t\t\t\t\tarp = 108 - xc->note;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Envelope */\n\n\tif (xc->f_idx >= 0 && (~instrument->fei.flg & XMP_ENVELOPE_FLT)) {\n\t\t/* IT pitch envelopes are always linear, even in Amiga period\n\t\t * mode. Each unit in the envelope scale is 1/25 semitone.\n\t\t */\n\t\tlinear_bend += frq_envelope << 7;\n\t}\n\n\t/* Arpeggio */\n\n\tif (arp != 0) {\n\t\tlinear_bend += (100 << 7) * arp;\n\n\t\t/* OpenMPT ArpWrapAround.mod */\n\t\tif (HAS_QUIRK(QUIRK_PROTRACK)) {\n\t\t\tif (xc->note + arp > MAX_NOTE_MOD + 1) {\n\t\t\t\tlinear_bend -= 12800 * (3 * 12);\n\t\t\t} else if (xc->note + arp > MAX_NOTE_MOD) {\n\t\t\t\tlibxmp_virt_setvol(ctx, chn, 0);\n\t\t\t}\n\t\t}\n\t}\n\n\n#ifndef LIBXMP_CORE_PLAYER\n\tlinear_bend += libxmp_extras_get_linear_bend(ctx, xc);\n#endif\n\n\tfinal_period = libxmp_note_to_period_mix(xc->note, linear_bend);\n\n\t/* From OpenMPT PeriodLimit.s3m:\n\t * \"ScreamTracker 3 limits the final output period to be at least 64,\n\t *  i.e. when playing a note that is too high or when sliding the\n\t *  period lower than 64, the output period will simply be clamped to\n\t *  64. However, when reaching a period of 0 through slides, the\n\t *  output on the channel should be stopped.\"\n\t */\n\t/* ST3 uses periods*4, so the limit is 16. Adjusted to the exact\n\t * A6 value because we compute periods in floating point.\n\t */\n\tif (HAS_QUIRK(QUIRK_ST3BUGS)) {\n\t\tif (final_period < 16.239270) {\t/* A6 */\n\t\t\tfinal_period = 16.239270;\n\t\t}\n\t}\n\n\tlibxmp_virt_setperiod(ctx, chn, final_period);\n\n\t/* For xmp_get_frame_info() */\n\txc->info_pitchbend = linear_bend >> 7;\n\txc->info_period = MIN(final_period * 4096, INT_MAX);\n\n\tif (IS_PERIOD_MODRNG()) {\n\t\tconst double min_period = libxmp_note_to_period(ctx, MAX_NOTE_MOD, xc->finetune, 0) * 4096;\n\t\tconst double max_period = libxmp_note_to_period(ctx, MIN_NOTE_MOD, xc->finetune, 0) * 4096;\n\t\tCLAMP(xc->info_period, min_period, max_period);\n\t} else if (xc->info_period < (1 << 12)) {\n\t\txc->info_period = (1 << 12);\n\t}\n\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\n\t/* Process filter */\n\n\tif (!HAS_QUIRK(QUIRK_FILTER)) {\n\t\treturn;\n\t}\n\n\tif (xc->f_idx >= 0 && (instrument->fei.flg & XMP_ENVELOPE_FLT)) {\n\t\tif (frq_envelope < 0xfe) {\n\t\t\txc->filter.envelope = frq_envelope;\n\t\t}\n\t\tcutoff = xc->filter.cutoff * xc->filter.envelope >> 8;\n\t} else {\n\t\tcutoff = xc->filter.cutoff;\n\t}\n\tresonance = xc->filter.resonance;\n\n\tif (cutoff > 0xff) {\n\t\tcutoff = 0xff;\n\t}\n\t/* IT: cutoff 127 + resonance 0 turns off the filter, but this\n\t * is only applied when playing a new note without toneporta.\n\t * All other combinations take effect immediately.\n\t * See OpenMPT filter-reset.it, filter-reset-carry.it */\n\tif (cutoff < 0xfe || resonance > 0 || xc->filter.can_disable) {\n\t\tint a0, b0, b1;\n\t\tlibxmp_filter_setup(s->freq, cutoff, resonance, &a0, &b0, &b1);\n\t\tlibxmp_virt_seteffect(ctx, chn, DSP_EFFECT_FILTER_A0, a0);\n\t\tlibxmp_virt_seteffect(ctx, chn, DSP_EFFECT_FILTER_B0, b0);\n\t\tlibxmp_virt_seteffect(ctx, chn, DSP_EFFECT_FILTER_B1, b1);\n\t\tlibxmp_virt_seteffect(ctx, chn, DSP_EFFECT_RESONANCE, resonance);\n\t\tlibxmp_virt_seteffect(ctx, chn, DSP_EFFECT_CUTOFF, cutoff);\n\t\txc->filter.can_disable = 0;\n\t}\n\n#endif\n}\n\nstatic void process_pan(struct context_data *ctx, int chn, int act)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct mixer_data *s = &ctx->s;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tstruct xmp_instrument *instrument;\n\tint finalpan, panbrello = 0;\n\tint pan_envelope;\n\tint channel_pan;\n\n\tinstrument = libxmp_get_instrument(ctx, xc->ins);\n\n\tif (!TEST_PER(PENV_PAUSE)) {\n\t\txc->p_idx = update_envelope(ctx, &instrument->pei, xc->p_idx,\n\t\t\tDOENV_RELEASE, TEST(KEY_OFF));\n\t}\n\tpan_envelope = get_envelope(&instrument->pei, xc->p_idx, 32);\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tif (TEST(PANBRELLO)) {\n\t\tpanbrello = libxmp_lfo_get(ctx, &xc->panbrello.lfo, 0) / 512;\n\t\tif (is_first_frame(ctx)) {\n\t\t\tlibxmp_lfo_update(&xc->panbrello.lfo);\n\t\t}\n\t}\n\txc->macro.notepan = xc->pan.val + panbrello + 0x80;\n#endif\n\n\tchannel_pan = xc->pan.val;\n\n#if 0\n#ifdef LIBXMP_PAULA_SIMULATOR\n\t/* Always use 100% pan separation in Amiga mode */\n\tif (p->flags & XMP_FLAGS_A500) {\n\t\tif (IS_AMIGA_MOD()) {\n\t\t\tchannel_pan = channel_pan < 0x80 ? 0 : 0xff;\n\t\t}\n\t}\n#endif\n#endif\n\n\tfinalpan = channel_pan + panbrello + (pan_envelope - 32) *\n\t\t\t\t(128 - abs(xc->pan.val - 128)) / 32;\n\n\tif (IS_PLAYER_MODE_IT()) {\n\t\tfinalpan = finalpan + xc->rpv * 4;\n\t}\n\n\tCLAMP(finalpan, 0, 255);\n\n\tif (s->format & XMP_FORMAT_MONO || xc->pan.surround) {\n\t\tfinalpan = 0;\n\t} else {\n\t\tfinalpan = (finalpan - 0x80) * s->mix / 100;\n\t}\n\n\txc->info_finalpan = finalpan + 0x80;\n\n\tif (xc->pan.surround) {\n\t\tlibxmp_virt_setpan(ctx, chn, PAN_SURROUND);\n\t} else {\n\t\tlibxmp_virt_setpan(ctx, chn, finalpan);\n\t}\n}\n\nstatic void update_volume(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tstruct flow_control *f = &p->flow;\n#endif\n\tstruct channel_data *xc = &p->xc_data[chn];\n\n\t/* Volume slides happen in all frames but the first, except when the\n\t * \"volume slide on all frames\" flag is set.\n\t */\n\tif (p->frame % p->speed != 0 || HAS_QUIRK(QUIRK_VSALL)) {\n\t\tif (TEST(GVOL_SLIDE)) {\n\t\t\tp->gvol += xc->gvol.slide;\n\t\t}\n\n\t\tif (TEST(VOL_SLIDE) || TEST_PER(VOL_SLIDE)) {\n\t\t\txc->volume += xc->vol.slide;\n\t\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\t\tif (TEST_PER(VOL_SLIDE)) {\n\t\t\tif (xc->vol.slide > 0) {\n\t\t\t\tint target = MAX(xc->vol.target - 1, m->volbase);\n\t\t\t\tif (xc->volume > target) {\n\t\t\t\t\txc->volume = target;\n\t\t\t\t\tRESET_PER(VOL_SLIDE);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (xc->vol.slide < 0) {\n\t\t\t\tint target = xc->vol.target > 0 ? MIN(0, xc->vol.target - 1) : 0;\n\t\t\t\tif (xc->volume < target) {\n\t\t\t\t\txc->volume = target;\n\t\t\t\t\tRESET_PER(VOL_SLIDE);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n#endif\n\n\t\tif (TEST(VOL_SLIDE_2)) {\n\t\t\txc->volume += xc->vol.slide2;\n\t\t}\n\t\tif (TEST(TRK_VSLIDE)) {\n\t\t\txc->mastervol += xc->trackvol.slide;\n\t\t}\n\t}\n\n\tif (p->frame % p->speed == 0) {\n\t\t/* Process \"fine\" effects */\n\t\tif (TEST(FINE_VOLS)) {\n\t\t\txc->volume += xc->vol.fslide;\n\t\t}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\t\tif (TEST(FINE_VOLS_2)) {\n\t\t\t/* OpenMPT FineVolColSlide.it:\n\t\t\t * Unlike fine volume slides in the effect column,\n\t\t\t * fine volume slides in the volume column are only\n\t\t\t * ever executed on the first tick -- not on multiples\n\t\t\t * of the first tick if there is a pattern delay.\n\t\t\t */\n\t\t\tif (!f->rowdelay_set || f->rowdelay_set & ROWDELAY_FIRST_FRAME) {\n\t\t\t\txc->volume += xc->vol.fslide2;\n\t\t\t}\n\t\t}\n#endif\n\n\t\tif (TEST(TRK_FVSLIDE)) {\n\t\t\txc->mastervol += xc->trackvol.fslide;\n\t\t}\n\n\t\tif (TEST(GVOL_SLIDE)) {\n\t\t\tp->gvol += xc->gvol.fslide;\n\t\t}\n\t}\n\n\t/* Clamp volumes */\n\tCLAMP(xc->volume, 0, m->volbase);\n\tCLAMP(p->gvol, 0, m->gvolbase);\n\tCLAMP(xc->mastervol, 0, m->volbase);\n\n\tif (xc->split) {\n\t\tp->xc_data[xc->pair].volume = xc->volume;\n\t}\n}\n\nstatic void update_frequency(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\n\tif (!is_first_frame(ctx) || HAS_QUIRK(QUIRK_PBALL)) {\n\t\tif (TEST(PITCHBEND) || TEST_PER(PITCHBEND)) {\n\t\t\txc->period += xc->freq.slide;\n\t\t\tif (HAS_QUIRK(QUIRK_PROTRACK)) {\n\t\t\t\txc->porta.target = xc->period;\n\t\t\t}\n\t\t}\n\n\t\t/* Do tone portamento */\n\t\tif (TEST(TONEPORTA) || TEST_PER(TONEPORTA)) {\n\t\t\tif (xc->porta.target > 0) {\n\t\t\t\tint end = 0;\n\t\t\t\tif (xc->porta.dir > 0) {\n\t\t\t\t\txc->period += xc->porta.slide;\n\t\t\t\t\tif (xc->period >= xc->porta.target)\n\t\t\t\t\t\tend = 1;\n\t\t\t\t} else {\n\t\t\t\t\txc->period -= xc->porta.slide;\n\t\t\t\t\tif (xc->period <= xc->porta.target)\n\t\t\t\t\t\tend = 1;\n\t\t\t\t}\n\n\t\t\t\tif (end) {\n\t\t\t\t\t/* reached end */\n\t\t\t\t\txc->period = xc->porta.target;\n\t\t\t\t\txc->porta.dir = 0;\n\t\t\t\t\tRESET(TONEPORTA);\n\t\t\t\t\tRESET_PER(TONEPORTA);\n\n\t\t\t\t\tif (HAS_QUIRK(QUIRK_PROTRACK)) {\n\t\t\t\t\t\txc->porta.target = -1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (is_first_frame(ctx)) {\n\t\tif (TEST(FINE_BEND)) {\n\t\t\txc->period += xc->freq.fslide;\n\t\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\t\tif (TEST(FINE_NSLIDE)) {\n\t\t\txc->note += xc->noteslide.fslide;\n\t\t\txc->period = libxmp_note_to_period(ctx, xc->note,\n\t\t\t\txc->finetune, xc->per_adj);\n\t\t}\n#endif\n\t}\n\n\tswitch (m->period_type) {\n\tcase PERIOD_LINEAR:\n\t\tCLAMP(xc->period, MIN_PERIOD_L, MAX_PERIOD_L);\n\t\tbreak;\n\tcase PERIOD_MODRNG: {\n\t\tconst double min_period = libxmp_note_to_period(ctx, MAX_NOTE_MOD, xc->finetune, 0);\n\t\tconst double max_period = libxmp_note_to_period(ctx, MIN_NOTE_MOD, xc->finetune, 0);\n\t\tCLAMP(xc->period, min_period, max_period);\n\t\t}\n\t\tbreak;\n\t}\n\n\t/* Check for invalid periods (from Toru Egashira's NSPmod)\n\t * panic.s3m has negative periods\n\t * ambio.it uses low (~8) period values\n\t */\n\tif (xc->period < 0.25) {\n\t\tlibxmp_virt_setvol(ctx, chn, 0);\n\t}\n}\n\nstatic void update_pan(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\n\tif (TEST(PAN_SLIDE)) {\n\t\tif (is_first_frame(ctx)) {\n\t\t\txc->pan.val += xc->pan.fslide;\n\t\t} else {\n\t\t\txc->pan.val += xc->pan.slide;\n\t\t}\n\n\t\tif (xc->pan.val < 0) {\n\t\t\txc->pan.val = 0;\n\t\t} else if (xc->pan.val > 0xff) {\n\t\t\txc->pan.val = 0xff;\n\t\t}\n\t}\n}\n\nstatic void play_channel(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct smix_data *smix = &ctx->smix;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tint act;\n\n\txc->info_finalvol = 0;\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\t/* IT tempo slide */\n\tif (!is_first_frame(ctx) && TEST(TEMPO_SLIDE)) {\n\t\tp->bpm += xc->tempo.slide;\n\t\tCLAMP(p->bpm, 0x20, 0xff);\n\t}\n#endif\n\n\t/* Do delay */\n\tif (xc->delay > 0) {\n\t\tif (--xc->delay == 0) {\n\t\t\tlibxmp_read_event(ctx, &xc->delayed_event, chn);\n\t\t}\n\t}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\t/* IT MIDI macros need to update regardless of the current voice state. */\n\tupdate_midi_macro(ctx, chn);\n#endif\n\n\tact = libxmp_virt_cstat(ctx, chn);\n\tif (act == VIRT_INVALID) {\n\t\t/* We need this to keep processing global volume slides */\n\t\tupdate_volume(ctx, chn);\n\t\treturn;\n\t}\n\n\tif (p->frame == 0 && act != VIRT_ACTIVE) {\n\t\tif (!IS_VALID_INSTRUMENT_OR_SFX(xc->ins) || act == VIRT_ACTION_CUT) {\n\t\t\tlibxmp_virt_resetchannel(ctx, chn);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (!IS_VALID_INSTRUMENT_OR_SFX(xc->ins))\n\t\treturn;\n\n#ifndef LIBXMP_CORE_PLAYER\n\tlibxmp_play_extras(ctx, xc, chn);\n#endif\n\n\t/* Do cut/retrig */\n\tif (TEST(RETRIG)) {\n\t\tint cond = HAS_QUIRK(QUIRK_S3MRTG) ?\n\t\t\t\t--xc->retrig.count <= 0 :\n\t\t\t\t--xc->retrig.count == 0;\n\n\t\tif (cond) {\n\t\t\tif (xc->retrig.type < 0x10) {\n\t\t\t\t/* don't retrig on cut */\n\t\t\t\tlibxmp_virt_voicepos(ctx, chn, 0);\n\t\t\t} else {\n\t\t\t\tSET_NOTE(NOTE_END);\n\t\t\t}\n\t\t\txc->volume += rval[xc->retrig.type].s;\n\t\t\txc->volume *= rval[xc->retrig.type].m;\n\t\t\txc->volume /= rval[xc->retrig.type].d;\n\t\t\txc->retrig.count = xc->retrig.val;\n\n\t\t\tif (xc->retrig.limit > 0) {\n\t\t\t\t/* Limit the number of retriggers. */\n\t\t\t\t--xc->retrig.limit;\n\t\t\t\tif (xc->retrig.limit == 0)\n\t\t\t\t\tRESET(RETRIG);\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Do keyoff */\n\tif (xc->keyoff) {\n\t\tif (--xc->keyoff == 0)\n\t\t\tSET_NOTE(NOTE_RELEASE);\n\t}\n\n\tlibxmp_virt_release(ctx, chn, TEST_NOTE(NOTE_SAMPLE_RELEASE));\n\n\tupdate_volume(ctx, chn);\n\tupdate_frequency(ctx, chn);\n\tupdate_pan(ctx, chn);\n\n\tprocess_volume(ctx, chn, act);\n\tprocess_frequency(ctx, chn, act);\n\tprocess_pan(ctx, chn, act);\n\n#ifndef LIBXMP_CORE_PLAYER\n\tif (HAS_QUIRK(QUIRK_PROTRACK | QUIRK_INVLOOP) && xc->ins < mod->ins) {\n\t\tupdate_invloop(ctx, xc);\n\t}\n#endif\n\n\tif (TEST_NOTE(NOTE_SUSEXIT)) {\n\t\tSET_NOTE(NOTE_ENV_RELEASE);\n\t}\n\n\txc->info_position = libxmp_virt_getvoicepos(ctx, chn);\n}\n\n/*\n * Event injection\n */\n\nstatic void inject_event(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct smix_data *smix = &ctx->smix;\n\tint chn;\n\n\tfor (chn = 0; chn < mod->chn + smix->chn; chn++) {\n\t\tstruct xmp_event *e = &p->inject_event[chn];\n\t\tif (e->_flag > 0) {\n\t\t\tlibxmp_read_event(ctx, e, chn);\n\t\t\te->_flag = 0;\n\t\t}\n\t}\n}\n\n/*\n * Sequencing\n */\n\nstatic void next_order(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct flow_control *f = &p->flow;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tint reset_gvol = 0;\n\tint mark;\n\tint i;\n\n\tdo {\n\t\tp->ord++;\n\n\t\t/* Restart module */\n\t\tmark = HAS_QUIRK(QUIRK_MARKER) && p->ord < mod->len &&\n\t\t       mod->xxo[p->ord] == XMP_MARK_END;\n\t\tif (p->ord >= mod->len || mark) {\n\t\t\tif (mod->rst > mod->len ||\n\t\t\t    mod->xxo[mod->rst] >= mod->pat ||\n\t\t\t    p->ord < m->seq_data[p->sequence].entry_point) {\n\t\t\t\tp->ord = m->seq_data[p->sequence].entry_point;\n\t\t\t} else {\n\t\t\t\tif (libxmp_get_sequence(ctx, mod->rst) == p->sequence) {\n\t\t\t\t\tp->ord = mod->rst;\n\t\t\t\t} else {\n\t\t\t\t\tp->ord = m->seq_data[p->sequence].entry_point;\n\t\t\t\t}\n\t\t\t}\n\t\t\t/* This might be a marker, so delay updating global\n\t\t\t * volume until an actual pattern is found */\n\t\t\treset_gvol = 1;\n\t\t}\n\t} while (mod->xxo[p->ord] >= mod->pat);\n\n\tif (reset_gvol)\n\t\tp->gvol = m->xxo_info[p->ord].gvl;\n\n#ifndef LIBXMP_CORE_PLAYER\n\t/* Archimedes line jump -- don't reset time tracking. */\n\tif (f->jump_in_pat != p->ord)\n#endif\n\tp->current_time = m->xxo_info[p->ord].time;\n\n\tf->num_rows = mod->xxp[mod->xxo[p->ord]]->rows;\n\tif (f->jumpline >= f->num_rows)\n\t\tf->jumpline = 0;\n\tp->row = f->jumpline;\n\tf->jumpline = 0;\n\n\tp->pos = p->ord;\n\tp->frame = 0;\n\n\t/* Scream Tracker 3, Imago Orpheus: position change resets loop vars.\n\t * For some reason the pattern jump effect does not do this in IMF. */\n\tif (HAS_FLOW_MODE(FLOW_LOOP_PATTERN_RESET)) {\n\t\tf->loop_start = -1;\n\t\tf->loop_count = 0;\n\t\tfor (i = 0; i < mod->chn; i++) {\n\t\t\tf->loop[i].start = 0;\n\t\t\tf->loop[i].count = 0;\n\t\t}\n\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\tf->jump_in_pat = -1;\n\n\t/* Reset persistent effects at new pattern */\n\tif (HAS_QUIRK(QUIRK_PERPAT)) {\n\t\tint chn;\n\t\tfor (chn = 0; chn < mod->chn; chn++) {\n\t\t\tp->xc_data[chn].per_flags = 0;\n\t\t}\n\t}\n#endif\n}\n\nstatic void next_row(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct flow_control *f = &p->flow;\n\n\tp->frame = 0;\n\tf->delay = 0;\n\tf->loop_param = -1;\n\n\tif (f->pbreak) {\n\t\tf->pbreak = 0;\n\t\tf->loop_dest = -1;\n\n\t\tif (f->jump != -1) {\n\t\t\tp->ord = f->jump - 1;\n\t\t\tf->jump = -1;\n\t\t}\n\n\t\tnext_order(ctx);\n\t} else {\n\t\tif (f->rowdelay == 0) {\n\t\t\tp->row++;\n\t\t\tf->rowdelay_set = 0;\n\t\t} else {\n\t\t\tf->rowdelay--;\n\t\t}\n\n\t\tif (f->loop_dest >= 0) {\n\t\t\tp->row = f->loop_dest;\n\t\t\tf->loop_dest = -1;\n\t\t}\n\n\t\t/* check end of pattern */\n\t\tif (p->row >= f->num_rows) {\n\t\t\tnext_order(ctx);\n\t\t}\n\t}\n}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\n/*\n * Set note action for libxmp_virt_pastnote\n */\nvoid libxmp_player_set_release(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\n\tSET_NOTE(NOTE_RELEASE);\n}\n\nvoid libxmp_player_set_fadeout(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\n\tSET_NOTE(NOTE_FADEOUT);\n}\n\n#endif\n\n/* Get frame time for calculation of the current playback time\n * based on the most recent scan. This value should be used for\n * playback time calculation ONLY. */\nstatic double libxmp_get_frame_time(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tif (p->bpm == 0)\n\t\treturn 0.0;\n\treturn p->scan_time_factor * m->rrate / p->bpm;\n}\n\nstatic void update_from_ord_info(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct ord_data *oinfo = &m->xxo_info[p->ord];\n\n\tif (oinfo->speed)\n\t\tp->speed = oinfo->speed;\n\tp->bpm = oinfo->bpm;\n\tp->gvol = oinfo->gvl;\n\tp->current_time = oinfo->time;\n\n#ifndef LIBXMP_CORE_PLAYER\n\tp->st26_speed = oinfo->st26_speed;\n#endif\n}\n\nvoid libxmp_reset_flow(struct context_data *ctx)\n{\n\tstruct flow_control *f = &ctx->p.flow;\n\tf->jumpline = 0;\n\tf->jump = -1;\n\tf->pbreak = 0;\n\tf->loop_dest = -1;\n\tf->loop_param = -1;\n\tf->loop_start = -1;\n\tf->loop_count = 0;\n\tf->loop_active_num = 0;\n\tf->delay = 0;\n\tf->rowdelay = 0;\n\tf->rowdelay_set = 0;\n#ifndef LIBXMP_CORE_PLAYER\n\tf->jump_in_pat = -1;\n#endif\n}\n\nint xmp_start_player(xmp_context opaque, int rate, int format)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct smix_data *smix = &ctx->smix;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct flow_control *f = &p->flow;\n\tint i;\n\tint ret = 0;\n\n\tif (rate < XMP_MIN_SRATE || rate > REAL_MAX_SRATE)\n\t\treturn -XMP_ERROR_INVALID;\n\n\tif (ctx->state < XMP_STATE_LOADED)\n\t\treturn -XMP_ERROR_STATE;\n\n\tif (ctx->state > XMP_STATE_LOADED)\n\t\txmp_end_player(opaque);\n\n\tif (libxmp_mixer_on(ctx, rate, format, m->c4rate) < 0)\n\t\treturn -XMP_ERROR_INTERNAL;\n\n\tp->master_vol = 100;\n\tp->smix_vol = 100;\n\tp->gvol = m->volbase;\n\tp->pos = p->ord = 0;\n\tp->frame = -1;\n\tp->row = 0;\n\tp->current_time = 0;\n\tp->loop_count = 0;\n\tp->sequence = 0;\n\n\t/* Set default volume and mute status */\n\tfor (i = 0; i < mod->chn; i++) {\n\t\tif (mod->xxc[i].flg & XMP_CHANNEL_MUTE)\n\t\t\tp->channel_mute[i] = 1;\n\t\tp->channel_vol[i] = 100;\n\t}\n\tfor (i = mod->chn; i < XMP_MAX_CHANNELS; i++) {\n\t\tp->channel_mute[i] = 0;\n\t\tp->channel_vol[i] = 100;\n\t}\n\n\t/* Skip invalid patterns at start (the seventh laboratory.it) */\n\twhile (p->ord < mod->len && mod->xxo[p->ord] >= mod->pat) {\n\t\tp->ord++;\n\t}\n\t/* Check if all positions skipped */\n\tif (p->ord >= mod->len) {\n\t\tmod->len = 0;\n\t}\n\n\tif (mod->len == 0) {\n\t\t/* set variables to sane state */\n\t\t/* Note: previously did this for mod->chn == 0, which caused\n\t\t * crashes on invalid order 0s. 0 channel modules are technically\n\t\t * valid (if useless) so just let them play normally. */\n\t\tp->ord = p->scan[0].ord = 0;\n\t\tp->row = p->scan[0].row = 0;\n\t\tf->end_point = 0;\n\t\tf->num_rows = 0;\n\t} else {\n\t\tf->num_rows = mod->xxp[mod->xxo[p->ord]]->rows;\n\t\tf->end_point = p->scan[0].num;\n\t}\n\n\tupdate_from_ord_info(ctx);\n\n\tif (libxmp_virt_on(ctx, mod->chn + smix->chn) != 0) {\n\t\tret = -XMP_ERROR_INTERNAL;\n\t\tgoto err;\n\t}\n\n\tlibxmp_reset_flow(ctx);\n\n\tf->loop = (struct pattern_loop *) calloc(p->virt.virt_channels, sizeof(struct pattern_loop));\n\tif (f->loop == NULL) {\n\t\tret = -XMP_ERROR_SYSTEM;\n\t\tgoto err;\n\t}\n\n\tp->xc_data = (struct channel_data *) calloc(p->virt.virt_channels, sizeof(struct channel_data));\n\tif (p->xc_data == NULL) {\n\t\tret = -XMP_ERROR_SYSTEM;\n\t\tgoto err1;\n\t}\n\n\t/* Reset our buffer pointers */\n\txmp_play_buffer(opaque, NULL, 0, 0);\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tfor (i = 0; i < p->virt.virt_channels; i++) {\n\t\tstruct channel_data *xc = &p->xc_data[i];\n\t\txc->filter.cutoff = 0xff;\n#ifndef LIBXMP_CORE_PLAYER\n\t\tif (libxmp_new_channel_extras(ctx, xc) < 0)\n\t\t\tgoto err2;\n#endif\n\t}\n#endif\n\treset_channels(ctx);\n\n\tctx->state = XMP_STATE_PLAYING;\n\n\treturn 0;\n\n#ifndef LIBXMP_CORE_PLAYER\n    err2:\n\tfree(p->xc_data);\n\tp->xc_data = NULL;\n#endif\n    err1:\n\tfree(f->loop);\n\tf->loop = NULL;\n    err:\n\treturn ret;\n}\n\nstatic void check_end_of_module(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct flow_control *f = &p->flow;\n\n\t/* check end of module */\n\tif (p->ord == p->scan[p->sequence].ord &&\n\t\t\tp->row == p->scan[p->sequence].row) {\n\t\tif (f->end_point == 0) {\n\t\t\tp->loop_count++;\n\t\t\tf->end_point = p->scan[p->sequence].num;\n\t\t\t/* return -1; */\n\t\t}\n\t\tf->end_point--;\n\t}\n}\n\nint xmp_play_frame(xmp_context opaque)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct flow_control *f = &p->flow;\n\tint i;\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn -XMP_ERROR_STATE;\n\n\tif (mod->len <= 0) {\n\t\treturn -XMP_END;\n\t}\n\n\tif (HAS_QUIRK(QUIRK_MARKER) && mod->xxo[p->ord] == XMP_MARK_END) {\n\t\treturn -XMP_END;\n\t}\n\n\t/* check reposition */\n\tif (p->ord != p->pos) {\n\t\tint start = m->seq_data[p->sequence].entry_point;\n\n\t\tif (p->pos == -2) {\t\t/* set by xmp_module_stop */\n\t\t\treturn -XMP_END;\t/* that's all folks */\n\t\t}\n\n\t\tif (p->pos == -1) {\n\t\t\t/* restart sequence */\n\t\t\tp->pos = start;\n\t\t}\n\n\t\tif (p->pos == start) {\n\t\t\tf->end_point = p->scan[p->sequence].num;\n\t\t}\n\n\t\t/* Check if lands after a loop point */\n\t\tif (p->pos > p->scan[p->sequence].ord) {\n\t\t\tf->end_point = 0;\n\t\t}\n\n\t\tf->jumpline = 0;\n\t\tf->jump = -1;\n\n\t\tp->ord = p->pos - 1;\n\n\t\t/* Stay inside our subsong */\n\t\tif (p->ord < start) {\n\t\t\tp->ord = start - 1;\n\t\t}\n\n\t\tnext_order(ctx);\n\n\t\tupdate_from_ord_info(ctx);\n\n\t\tlibxmp_virt_reset(ctx);\n\t\treset_channels(ctx);\n\t} else {\n\t\tp->frame++;\n\t\tif (p->frame >= (p->speed * (1 + f->delay))) {\n\t\t\t/* If break during pattern delay, next row is skipped.\n\t\t\t * See corruption.mod order 1D (pattern 0D) last line:\n\t\t\t * EE2 + D31 ignores D00 in order 1C line 31. Reported\n\t\t\t * by The Welder <welder@majesty.net>, Jan 14 2012\n\t\t\t */\n\t\t\tif (HAS_QUIRK(QUIRK_PROTRACK) && f->delay && f->pbreak)\n\t\t\t{\n\t\t\t\tnext_row(ctx);\n\t\t\t\tcheck_end_of_module(ctx);\n\t\t\t}\n\t\t\tnext_row(ctx);\n\t\t}\n\t}\n\n\tfor (i = 0; i < mod->chn; i++) {\n\t\tstruct channel_data *xc = &p->xc_data[i];\n\t\tRESET(KEY_OFF);\n\t}\n\n\t/* check new row */\n\n\tif (p->frame == 0) {\t\t\t/* first frame in row */\n\t\tcheck_end_of_module(ctx);\n\t\tread_row(ctx, mod->xxo[p->ord], p->row);\n\n#ifndef LIBXMP_CORE_PLAYER\n\t\tif (p->st26_speed) {\n\t\t\tif  (p->st26_speed & 0x10000) {\n\t\t\t\tp->speed = (p->st26_speed & 0xff00) >> 8;\n\t\t\t} else {\n\t\t\t\tp->speed = p->st26_speed & 0xff;\n\t\t\t}\n\t\t\tp->st26_speed ^= 0x10000;\n\t\t}\n#endif\n\t}\n\n\tinject_event(ctx);\n\n\t/* play_frame */\n\tfor (i = 0; i < p->virt.virt_channels; i++) {\n\t\tplay_channel(ctx, i);\n\t}\n\n\tf->rowdelay_set &= ~ROWDELAY_FIRST_FRAME;\n\n\tp->current_time += libxmp_get_frame_time(ctx);\n\n\tlibxmp_mixer_softmixer(ctx);\n\n\treturn 0;\n}\n\nint xmp_play_buffer(xmp_context opaque, void *out_buffer, int size, int loop)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tint ret = 0, filled = 0, copy_size;\n\tstruct xmp_frame_info fi;\n\n\t/* Reset internal state\n\t * Syncs buffer start with frame start */\n\tif (out_buffer == NULL) {\n\t\tp->loop_count = 0;\n\t\tp->buffer_data.consumed = 0;\n\t\tp->buffer_data.in_size = 0;\n\t\treturn 0;\n\t}\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn -XMP_ERROR_STATE;\n\n\t/* Fill buffer */\n\twhile (filled < size) {\n\t\t/* Check if buffer full */\n\t\tif (p->buffer_data.consumed == p->buffer_data.in_size) {\n\t\t\tret = xmp_play_frame(opaque);\n\t\t\txmp_get_frame_info(opaque, &fi);\n\n\t\t\t/* Check end of module */\n\t\t\tif (ret < 0 || (loop > 0 && fi.loop_count >= loop)) {\n\t\t\t\t/* Start of frame, return end of replay */\n\t\t\t\tif (filled == 0) {\n\t\t\t\t\tp->buffer_data.consumed = 0;\n\t\t\t\t\tp->buffer_data.in_size = 0;\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\n\t\t\t\t/* Fill remaining of this buffer */\n\t\t\t\tmemset((char *)out_buffer + filled, 0, size - filled);\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tp->buffer_data.consumed = 0;\n\t\t\tp->buffer_data.in_buffer = (char *)fi.buffer;\n\t\t\tp->buffer_data.in_size = fi.buffer_size;\n\t\t}\n\n\t\t/* Copy frame data to user buffer */\n\t\tcopy_size = MIN(size - filled, p->buffer_data.in_size -\n\t\t\t\t\tp->buffer_data.consumed);\n\t\tmemcpy((char *)out_buffer + filled, p->buffer_data.in_buffer +\n\t\t\t\t\tp->buffer_data.consumed, copy_size);\n\t\tp->buffer_data.consumed += copy_size;\n\t\tfilled += copy_size;\n\t}\n\n\treturn ret;\n}\n\nvoid xmp_end_player(xmp_context opaque)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct flow_control *f = &p->flow;\n#ifndef LIBXMP_CORE_PLAYER\n\tstruct channel_data *xc;\n\tint i;\n#endif\n\n\tif (ctx->state < XMP_STATE_PLAYING)\n\t\treturn;\n\n\tctx->state = XMP_STATE_LOADED;\n\n#ifndef LIBXMP_CORE_PLAYER\n\t/* Free channel extras */\n\tfor (i = 0; i < p->virt.virt_channels; i++) {\n\t\txc = &p->xc_data[i];\n\t\tlibxmp_release_channel_extras(ctx, xc);\n\t}\n#endif\n\n\tlibxmp_virt_off(ctx);\n\n\tfree(p->xc_data);\n\tfree(f->loop);\n\n\tp->xc_data = NULL;\n\tf->loop = NULL;\n\n\tlibxmp_mixer_off(ctx);\n}\n\nvoid xmp_get_module_info(xmp_context opaque, struct xmp_module_info *info)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\n\tif (ctx->state < XMP_STATE_LOADED)\n\t\treturn;\n\n\tmemcpy(info->md5, m->md5, 16);\n\tinfo->mod = mod;\n\tinfo->comment = m->comment;\n\tinfo->num_sequences = m->num_sequences;\n\tinfo->seq_data = m->seq_data;\n\tinfo->vol_base = m->volbase;\n}\n\nvoid xmp_get_frame_info(xmp_context opaque, struct xmp_frame_info *info)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_data *s = &ctx->s;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tint chn, i;\n\n\tif (ctx->state < XMP_STATE_LOADED)\n\t\treturn;\n\n\tchn = mod->chn;\n\n\tif (p->pos >= 0 && p->pos < mod->len) {\n\t\tinfo->pos = p->pos;\n\t} else {\n\t\tinfo->pos = 0;\n\t}\n\n\tinfo->pattern = mod->xxo[info->pos];\n\n\tif (info->pattern < mod->pat) {\n\t\tinfo->num_rows = mod->xxp[info->pattern]->rows;\n\t} else {\n\t\tinfo->num_rows = 0;\n\t}\n\n\tinfo->row = p->row;\n\tinfo->frame = p->frame;\n\tinfo->speed = p->speed;\n\tinfo->bpm = p->bpm;\n\tinfo->total_time = p->scan[p->sequence].time;\n\tinfo->frame_time = (int)(libxmp_get_frame_time(ctx) * 1000.0);\n\tinfo->time = p->current_time;\n\tinfo->buffer = s->buffer;\n\n\tinfo->total_size = s->total_size;\n\tinfo->buffer_size = s->ticksize;\n\tif (~s->format & XMP_FORMAT_MONO) {\n\t\tinfo->buffer_size *= 2;\n\t}\n\tif (~s->format & XMP_FORMAT_8BIT) {\n\t\tinfo->buffer_size *= 2;\n\t}\n\n\tinfo->volume = p->gvol;\n\tinfo->loop_count = p->loop_count;\n\tinfo->virt_channels = p->virt.virt_channels;\n\tinfo->virt_used = p->virt.virt_used;\n\n\tinfo->sequence = p->sequence;\n\n\tif (p->xc_data != NULL) {\n\t\tfor (i = 0; i < chn; i++) {\n\t\t\tstruct channel_data *c = &p->xc_data[i];\n\t\t\tstruct xmp_channel_info *ci = &info->channel_info[i];\n\t\t\tstruct xmp_track *track;\n\t\t\tstruct xmp_event *event;\n\t\t\tint trk;\n\n\t\t\tci->note = c->key;\n\t\t\tci->pitchbend = c->info_pitchbend;\n\t\t\tci->period = c->info_period;\n\t\t\tci->position = c->info_position;\n\t\t\tci->instrument = c->ins;\n\t\t\tci->sample = c->smp;\n\t\t\tci->volume = c->info_finalvol >> 4;\n\t\t\tci->pan = c->info_finalpan;\n\t\t\tci->reserved = 0;\n\t\t\tmemset(&ci->event, 0, sizeof(*event));\n\n\t\t\tif (info->pattern < mod->pat && info->row < info->num_rows) {\n\t\t\t\ttrk = mod->xxp[info->pattern]->index[i];\n\t\t\t\ttrack = mod->xxt[trk];\n\t\t\t\tif (info->row < track->rows) {\n\t\t\t\t\tevent = &track->event[info->row];\n\t\t\t\t\tmemcpy(&ci->event, event, sizeof(*event));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "contrib/libxmp/src/player.h",
    "content": "#ifndef LIBXMP_PLAYER_H\n#define LIBXMP_PLAYER_H\n\n#include \"lfo.h\"\n\n/* Quirk control */\n#define HAS_QUIRK(x)\t(m->quirk & (x))\n\n/* Channel flag control */\n#define SET(f)\t\tSET_FLAG(xc->flags,(f))\n#define RESET(f) \tRESET_FLAG(xc->flags,(f))\n#define TEST(f)\t\tTEST_FLAG(xc->flags,(f))\n\n/* Persistent effect flag control */\n#define SET_PER(f)\tSET_FLAG(xc->per_flags,(f))\n#define RESET_PER(f)\tRESET_FLAG(xc->per_flags,(f))\n#define TEST_PER(f)\tTEST_FLAG(xc->per_flags,(f))\n\n/* Note flag control */\n#define SET_NOTE(f)\tSET_FLAG(xc->note_flags,(f))\n#define RESET_NOTE(f)\tRESET_FLAG(xc->note_flags,(f))\n#define TEST_NOTE(f)\tTEST_FLAG(xc->note_flags,(f))\n\nstruct retrig_control {\n\tint s;\n\tint m;\n\tint d;\n};\n\n/* The following macros are used to set the flags for each channel */\n#define VOL_SLIDE\t(1 << 0)\n#define PAN_SLIDE\t(1 << 1)\n#define TONEPORTA\t(1 << 2)\n#define PITCHBEND\t(1 << 3)\n#define VIBRATO\t\t(1 << 4)\n#define TREMOLO\t\t(1 << 5)\n#define FINE_VOLS\t(1 << 6)\n#define FINE_BEND\t(1 << 7)\n#define OFFSET\t\t(1 << 8)\n#define TRK_VSLIDE\t(1 << 9)\n#define TRK_FVSLIDE\t(1 << 10)\n#define NEW_INS\t\t(1 << 11)\n#define NEW_VOL\t\t(1 << 12)\n#define VOL_SLIDE_2\t(1 << 13)\n#define NOTE_SLIDE\t(1 << 14)\n#define FINE_NSLIDE\t(1 << 15)\n#define NEW_NOTE\t(1 << 16)\n#define FINE_TPORTA\t(1 << 17)\n#define RETRIG\t\t(1 << 18)\n#define PANBRELLO\t(1 << 19)\n#define GVOL_SLIDE\t(1 << 20)\n#define TEMPO_SLIDE\t(1 << 21)\n#define VENV_PAUSE\t(1 << 22)\n#define PENV_PAUSE\t(1 << 23)\n#define FENV_PAUSE\t(1 << 24)\n#define FINE_VOLS_2\t(1 << 25)\n#define KEY_OFF\t\t(1 << 26)\t/* for IT release on envloop end */\n#define TREMOR\t\t(1 << 27)\t/* for XM tremor */\n#define MIDI_MACRO\t(1 << 28)\t/* IT midi macro */\n\n#define NOTE_FADEOUT\t(1 << 0)\n#define NOTE_ENV_RELEASE (1 << 1)\t/* envelope sustain loop release */\n#define NOTE_END\t(1 << 2)\n#define NOTE_CUT\t(1 << 3)\n#define NOTE_ENV_END\t(1 << 4)\n#define NOTE_SAMPLE_END\t(1 << 5)\n#define NOTE_SET\t(1 << 6)\t/* for IT portamento after keyoff */\n#define NOTE_SUSEXIT\t(1 << 7)\t/* for delayed envelope release */\n#define NOTE_KEY_CUT\t(1 << 8)\t/* note cut with XMP_KEY_CUT event */\n#define NOTE_GLISSANDO\t(1 << 9)\n#define NOTE_SAMPLE_RELEASE (1 << 10)\t/* sample sustain loop release */\n\n/* Most of the time, these should be set/reset together. */\n#define NOTE_RELEASE\t(NOTE_ENV_RELEASE | NOTE_SAMPLE_RELEASE)\n\n/* Note: checking the data pointer for samples should be good enough to filter\n * broken samples, since libxmp_load_sample will always allocate it for valid\n * samples of >0 length and bound the loop values for these samples. */\n#define IS_VALID_INSTRUMENT(x) ((uint32)(x) < mod->ins && mod->xxi[(x)].nsm > 0)\n#define IS_VALID_INSTRUMENT_OR_SFX(x) (((uint32)(x) < mod->ins && mod->xxi[(x)].nsm > 0) || (smix->ins > 0 && (uint32)(x) < mod->ins + smix->ins))\n#define IS_VALID_SAMPLE(x) ((uint32)(x) < mod->smp && mod->xxs[(x)].data != NULL)\n#define IS_VALID_NOTE(x) ((uint32)(x) < XMP_MAX_KEYS)\n\nstruct instrument_vibrato {\n\tint phase;\n\tint sweep;\n};\n\nstruct channel_data {\n\tint flags;\t\t/* Channel flags */\n\tint per_flags;\t\t/* Persistent effect channel flags */\n\tint note_flags;\t\t/* Note release, fadeout or end */\n\tint note;\t\t/* Note number */\n\tint key;\t\t/* Key number */\n\tdouble period;\t\t/* Amiga or linear period */\n\tdouble per_adj;\t\t/* MED period/pitch adjustment factor hack */\n\tint finetune;\t\t/* Guess what */\n\tint ins;\t\t/* Instrument number */\n\tint old_ins;\t\t/* Last instrument */\n\tint smp;\t\t/* Sample number */\n\tint mastervol;\t\t/* Master vol -- for IT track vol effect */\n\tint delay;\t\t/* Note delay in frames */\n\tint keyoff;\t\t/* Key off counter */\n\tint fadeout;\t\t/* Current fadeout (release) value */\n\tint ins_fade;\t\t/* Instrument fadeout value */\n\tint volume;\t\t/* Current volume */\n\tint gvl;\t\t/* Global volume for instrument for IT */\n\n\tint rvv;\t\t/* Random volume variation */\n\tint rpv;\t\t/* Random pan variation */\n\n\tuint8 split;\t\t/* Split channel */\n\tuint8 pair;\t\t/* Split channel pair */\n\n\tint v_idx;\t\t/* Volume envelope index */\n\tint p_idx;\t\t/* Pan envelope index */\n\tint f_idx;\t\t/* Freq envelope index */\n\n\tint key_porta;\t\t/* Key number for portamento target\n\t\t\t\t * -- needed to handle IT portamento xpo */\n\tstruct {\n\t\tstruct lfo lfo;\n\t\tint memory;\n\t} vibrato;\n\n\tstruct {\n\t\tstruct lfo lfo;\n\t\tint memory;\n\t} tremolo;\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tstruct {\n\t\tstruct lfo lfo;\n\t\tint memory;\n\t} panbrello;\n#endif\n\n\tstruct {\n        \tint8 val[16];\t/* 16 for Smaksak MegaArps */\n\t\tint size;\n\t\tint count;\n\t\tint memory;\n\t} arpeggio;\n\n\tstruct {\n\t\tstruct lfo lfo;\n\t\tint sweep;\n\t} insvib;\n\n\tstruct {\n\t\tint val;\n\t\tint val2;\t/* For fx9 bug emulation */\n\t\tint memory;\n\t} offset;\n\n\tstruct {\n\t\tint val;\t/* Retrig value */\n\t\tint count;\t/* Retrig counter */\n\t\tint type;\t/* Retrig type */\n\t\tint limit;\t/* Number of retrigs */\n\t} retrig;\n\n\tstruct {\n\t\tuint8 up,down;\t/* Tremor value */\n\t\tuint8 count;\t/* Tremor counter */\n\t\tuint8 memory;\t/* Tremor memory */\n\t} tremor;\n\n\tstruct {\n\t\tint slide;\t/* Volume slide value */\n\t\tint fslide;\t/* Fine volume slide value */\n\t\tint slide2;\t/* Volume slide value */\n\t\tint memory;\t/* Volume slide effect memory */\n#ifndef LIBXMP_CORE_DISABLE_IT\n\t\tint fslide2;\n\t\tint memory2;\t/* Volume slide effect memory */\n#endif\n#ifndef LIBXMP_CORE_PLAYER\n\t\tint target;\t/* Target for persistent volslide */\n#endif\n\t} vol;\n\n\tstruct {\n\t\tint up_memory;\t/* Fine volume slide up memory (XM) */\n\t\tint down_memory;/* Fine volume slide up memory (XM) */\n\t} fine_vol;\n\n\tstruct {\n\t\tint slide;\t/* Global volume slide value */\n\t\tint fslide;\t/* Fine global volume slide value */\n\t\tint memory;\t/* Global volume memory is saved per channel */\n\t} gvol;\n\n\tstruct {\n\t\tint slide;\t/* Track volume slide value */\n\t\tint fslide;\t/* Track fine volume slide value */\n\t\tint memory;\t/* Track volume slide effect memory */\n\t} trackvol;\n\n\tstruct {\n\t\tint slide;\t/* Frequency slide value */\n\t\tdouble fslide;\t/* Fine frequency slide value */\n\t\tint memory;\t/* Portamento effect memory */\n\t\tint down_memory;/* Portamento down effect memory (XM) */\n\t} freq;\n\n\tstruct {\n\t\tdouble target;\t/* Target period for tone portamento */\n\t\tint dir;\t/* Tone portamento up/down direction */\n\t\tint slide;\t/* Delta for tone portamento */\n\t\tint memory;\t/* Tone portamento effect memory */\n\t\tint note_memory;/* Tone portamento note memory (ULT) */\n\t} porta;\n\n\tstruct {\n\t\tint up_memory;\t/* FT2 has separate memories for these */\n\t\tint down_memory;/* cases (see Porta-LinkMem.xm) */\n\t\tint xf_up_memory;\n\t\tint xf_down_memory;\n\t} fine_porta;\n\n\tstruct {\n\t\tint val;\t/* Current pan value */\n\t\tint slide;\t/* Pan slide value */\n\t\tint fslide;\t/* Pan fine slide value */\n\t\tint memory;\t/* Pan slide effect memory */\n\t\tint surround;\t/* Surround channel flag */\n\t} pan;\n\n\tstruct {\n\t\tint speed;\n\t\tint count;\n\t\tint pos;\n\t} invloop;\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tstruct {\n\t\tint slide;\t/* IT tempo slide */\n\t} tempo;\n\n\tstruct {\n\t\tint cutoff;\t/* IT filter cutoff frequency */\n\t\tint resonance;\t/* IT filter resonance */\n\t\tint envelope;\t/* IT filter envelope */\n\t\tint can_disable;/* IT hack: allow disabling for cutoff 127 */\n\t} filter;\n\n\tstruct {\n\t\tfloat val;\t/* Current macro effect (use float for slides) */\n\t\tfloat target;\t/* Current macro target (smooth macro) */\n\t\tfloat slide;\t/* Current macro slide (smooth macro) */\n\t\tint active;\t/* Current active parameterized macro */\n\t\tint finalvol;\t/* Previous tick calculated volume (0-0x400) */\n\t\tint notepan;\t/* Previous tick note panning (0x80 center) */\n\t} macro;\n#endif\n\n#ifndef LIBXMP_CORE_PLAYER\n\tstruct {\n\t\tint slide;\t/* PTM note slide amount */\n\t\tint fslide;\t/* OKT fine note slide amount */\n\t\tint speed;\t/* PTM note slide speed */\n\t\tint count;\t/* PTM note slide counter */\n\t} noteslide;\n\n\tvoid *extra;\n#endif\n\n\tstruct xmp_event delayed_event;\n\tint delayed_ins;\t/* IT save instrument emulation */\n\n\tint info_period;\t/* Period */\n\tint info_pitchbend;\t/* Linear pitchbend */\n\tint info_position;\t/* Position before mixing */\n\tint info_finalvol;\t/* Final volume including envelopes */\n\tint info_finalpan;\t/* Final pan including envelopes */\n};\n\n\nvoid\tlibxmp_process_fx\t(struct context_data *, struct channel_data *,\n\t\t\t\t int, struct xmp_event *, int);\nvoid\tlibxmp_filter_setup\t(int, int, int, int*, int*, int *);\nint\tlibxmp_read_event\t(struct context_data *, struct xmp_event *, int);\n\nvoid\tlibxmp_process_pattern_loop\t(struct context_data *,\n\tstruct flow_control *f, int, int, int);\nvoid\tlibxmp_process_pattern_jump\t(struct context_data *,\n\tstruct flow_control *f, int);\nvoid\tlibxmp_process_pattern_break\t(struct context_data *,\n\tstruct flow_control *f, int);\nvoid\tlibxmp_process_line_jump\t(struct context_data *,\n\tstruct flow_control *f, int, int);\n\n#endif /* LIBXMP_PLAYER_H */\n"
  },
  {
    "path": "contrib/libxmp/src/precomp_lut.h",
    "content": "static const int16 cubic_spline_lut0[1024] = {\n\t 0, -8, -16, -24, -32, -40, -47, -55,\n\t -63, -71, -78, -86, -94, -101, -109, -117,\n\t -124, -132, -139, -146, -154, -161, -169, -176,\n\t -183, -190, -198, -205, -212, -219, -226, -233,\n\t -240, -247, -254, -261, -268, -275, -282, -289,\n\t -295, -302, -309, -316, -322, -329, -336, -342,\n\t -349, -355, -362, -368, -375, -381, -388, -394,\n\t -400, -407, -413, -419, -425, -432, -438, -444,\n\t -450, -456, -462, -468, -474, -480, -486, -492,\n\t -498, -504, -510, -515, -521, -527, -533, -538,\n\t -544, -550, -555, -561, -566, -572, -577, -583,\n\t -588, -594, -599, -604, -610, -615, -620, -626,\n\t -631, -636, -641, -646, -651, -656, -662, -667,\n\t -672, -677, -682, -686, -691, -696, -701, -706,\n\t -711, -715, -720, -725, -730, -734, -739, -744,\n\t -748, -753, -757, -762, -766, -771, -775, -780,\n\t -784, -788, -793, -797, -801, -806, -810, -814,\n\t -818, -822, -826, -831, -835, -839, -843, -847,\n\t -851, -855, -859, -863, -866, -870, -874, -878,\n\t -882, -886, -889, -893, -897, -900, -904, -908,\n\t -911, -915, -918, -922, -925, -929, -932, -936,\n\t -939, -943, -946, -949, -953, -956, -959, -962,\n\t -966, -969, -972, -975, -978, -981, -984, -987,\n\t -991, -994, -997, -999, -1002, -1005, -1008, -1011,\n\t -1014, -1017, -1020, -1022, -1025, -1028, -1031, -1033,\n\t -1036, -1039, -1041, -1044, -1047, -1049, -1052, -1054,\n\t -1057, -1059, -1062, -1064, -1066, -1069, -1071, -1074,\n\t -1076, -1078, -1080, -1083, -1085, -1087, -1089, -1092,\n\t -1094, -1096, -1098, -1100, -1102, -1104, -1106, -1108,\n\t -1110, -1112, -1114, -1116, -1118, -1120, -1122, -1124,\n\t -1125, -1127, -1129, -1131, -1133, -1134, -1136, -1138,\n\t -1139, -1141, -1143, -1144, -1146, -1147, -1149, -1150,\n\t -1152, -1153, -1155, -1156, -1158, -1159, -1161, -1162,\n\t -1163, -1165, -1166, -1167, -1169, -1170, -1171, -1172,\n\t -1174, -1175, -1176, -1177, -1178, -1179, -1180, -1181,\n\t -1182, -1184, -1185, -1186, -1187, -1187, -1188, -1189,\n\t -1190, -1191, -1192, -1193, -1194, -1195, -1195, -1196,\n\t -1197, -1198, -1198, -1199, -1200, -1200, -1201, -1202,\n\t -1202, -1203, -1204, -1204, -1205, -1205, -1206, -1206,\n\t -1207, -1207, -1208, -1208, -1208, -1209, -1209, -1210,\n\t -1210, -1210, -1211, -1211, -1211, -1212, -1212, -1212,\n\t -1212, -1212, -1213, -1213, -1213, -1213, -1213, -1213,\n\t -1213, -1213, -1214, -1214, -1214, -1214, -1214, -1214,\n\t -1214, -1214, -1213, -1213, -1213, -1213, -1213, -1213,\n\t -1213, -1213, -1212, -1212, -1212, -1212, -1211, -1211,\n\t -1211, -1211, -1210, -1210, -1210, -1209, -1209, -1209,\n\t -1208, -1208, -1207, -1207, -1207, -1206, -1206, -1205,\n\t -1205, -1204, -1204, -1203, -1202, -1202, -1201, -1201,\n\t -1200, -1199, -1199, -1198, -1197, -1197, -1196, -1195,\n\t -1195, -1194, -1193, -1192, -1192, -1191, -1190, -1189,\n\t -1188, -1187, -1187, -1186, -1185, -1184, -1183, -1182,\n\t -1181, -1180, -1179, -1178, -1177, -1176, -1175, -1174,\n\t -1173, -1172, -1171, -1170, -1169, -1168, -1167, -1166,\n\t -1165, -1163, -1162, -1161, -1160, -1159, -1158, -1156,\n\t -1155, -1154, -1153, -1151, -1150, -1149, -1148, -1146,\n\t -1145, -1144, -1142, -1141, -1140, -1138, -1137, -1135,\n\t -1134, -1133, -1131, -1130, -1128, -1127, -1125, -1124,\n\t -1122, -1121, -1119, -1118, -1116, -1115, -1113, -1112,\n\t -1110, -1109, -1107, -1105, -1104, -1102, -1101, -1099,\n\t -1097, -1096, -1094, -1092, -1091, -1089, -1087, -1085,\n\t -1084, -1082, -1080, -1079, -1077, -1075, -1073, -1071,\n\t -1070, -1068, -1066, -1064, -1062, -1061, -1059, -1057,\n\t -1055, -1053, -1051, -1049, -1047, -1046, -1044, -1042,\n\t -1040, -1038, -1036, -1034, -1032, -1030, -1028, -1026,\n\t -1024, -1022, -1020, -1018, -1016, -1014, -1012, -1010,\n\t -1008, -1006, -1004, -1002, -999, -997, -995, -993,\n\t -991, -989, -987, -985, -982, -980, -978, -976,\n\t -974, -972, -969, -967, -965, -963, -961, -958,\n\t -956, -954, -952, -950, -947, -945, -943, -941,\n\t -938, -936, -934, -931, -929, -927, -924, -922,\n\t -920, -918, -915, -913, -911, -908, -906, -903,\n\t -901, -899, -896, -894, -892, -889, -887, -884,\n\t -882, -880, -877, -875, -872, -870, -867, -865,\n\t -863, -860, -858, -855, -853, -850, -848, -845,\n\t -843, -840, -838, -835, -833, -830, -828, -825,\n\t -823, -820, -818, -815, -813, -810, -808, -805,\n\t -803, -800, -798, -795, -793, -790, -787, -785,\n\t -782, -780, -777, -775, -772, -769, -767, -764,\n\t -762, -759, -757, -754, -751, -749, -746, -744,\n\t -741, -738, -736, -733, -730, -728, -725, -723,\n\t -720, -717, -715, -712, -709, -707, -704, -702,\n\t -699, -696, -694, -691, -688, -686, -683, -680,\n\t -678, -675, -672, -670, -667, -665, -662, -659,\n\t -657, -654, -651, -649, -646, -643, -641, -638,\n\t -635, -633, -630, -627, -625, -622, -619, -617,\n\t -614, -611, -609, -606, -603, -601, -598, -595,\n\t -593, -590, -587, -585, -582, -579, -577, -574,\n\t -571, -569, -566, -563, -561, -558, -555, -553,\n\t -550, -547, -545, -542, -539, -537, -534, -531,\n\t -529, -526, -523, -521, -518, -516, -513, -510,\n\t -508, -505, -502, -500, -497, -495, -492, -489,\n\t -487, -484, -481, -479, -476, -474, -471, -468,\n\t -466, -463, -461, -458, -455, -453, -450, -448,\n\t -445, -442, -440, -437, -435, -432, -430, -427,\n\t -424, -422, -419, -417, -414, -412, -409, -407,\n\t -404, -402, -399, -397, -394, -392, -389, -387,\n\t -384, -382, -379, -377, -374, -372, -369, -367,\n\t -364, -362, -359, -357, -354, -352, -349, -347,\n\t -345, -342, -340, -337, -335, -332, -330, -328,\n\t -325, -323, -320, -318, -316, -313, -311, -309,\n\t -306, -304, -302, -299, -297, -295, -292, -290,\n\t -288, -285, -283, -281, -278, -276, -274, -272,\n\t -269, -267, -265, -263, -260, -258, -256, -254,\n\t -251, -249, -247, -245, -243, -240, -238, -236,\n\t -234, -232, -230, -228, -225, -223, -221, -219,\n\t -217, -215, -213, -211, -209, -207, -205, -202,\n\t -200, -198, -196, -194, -192, -190, -188, -186,\n\t -184, -182, -180, -178, -176, -175, -173, -171,\n\t -169, -167, -165, -163, -161, -159, -157, -156,\n\t -154, -152, -150, -148, -146, -145, -143, -141,\n\t -139, -137, -136, -134, -132, -130, -129, -127,\n\t -125, -124, -122, -120, -119, -117, -115, -114,\n\t -112, -110, -109, -107, -106, -104, -102, -101,\n\t -99, -98, -96, -95, -93, -92, -90, -89,\n\t -87, -86, -84, -83, -82, -80, -79, -77,\n\t -76, -75, -73, -72, -70, -69, -68, -67,\n\t -65, -64, -63, -61, -60, -59, -58, -57,\n\t -55, -54, -53, -52, -51, -49, -48, -47,\n\t -46, -45, -44, -43, -42, -41, -40, -39,\n\t -38, -37, -36, -35, -34, -33, -32, -31,\n\t -30, -29, -28, -27, -26, -26, -25, -24,\n\t -23, -22, -22, -21, -20, -19, -19, -18,\n\t -17, -16, -16, -15, -14, -14, -13, -13,\n\t -12, -11, -11, -10, -10, -9, -9, -8,\n\t -8, -7, -7, -6, -6, -6, -5, -5,\n\t -4, -4, -4, -3, -3, -3, -2, -2,\n\t -2, -2, -2, -1, -1, -1, -1, -1,\n\t 0, 0, 0, 0, 0, 0, 0, 0,\n};\n\nstatic const int16 cubic_spline_lut1[1024] = {\n\t 16384, 16384, 16384, 16384, 16384, 16383, 16382, 16381,\n\t 16381, 16381, 16380, 16379, 16379, 16377, 16377, 16376,\n\t 16374, 16373, 16371, 16370, 16369, 16366, 16366, 16364,\n\t 16361, 16360, 16358, 16357, 16354, 16351, 16349, 16347,\n\t 16345, 16342, 16340, 16337, 16335, 16331, 16329, 16326,\n\t 16322, 16320, 16317, 16314, 16309, 16307, 16304, 16299,\n\t 16297, 16293, 16290, 16285, 16282, 16278, 16274, 16269,\n\t 16265, 16262, 16257, 16253, 16247, 16244, 16239, 16235,\n\t 16230, 16225, 16220, 16216, 16211, 16206, 16201, 16196,\n\t 16191, 16185, 16180, 16174, 16169, 16163, 16158, 16151,\n\t 16146, 16140, 16133, 16128, 16122, 16116, 16109, 16104,\n\t 16097, 16092, 16085, 16077, 16071, 16064, 16058, 16052,\n\t 16044, 16038, 16030, 16023, 16015, 16009, 16002, 15995,\n\t 15988, 15980, 15973, 15964, 15957, 15949, 15941, 15934,\n\t 15926, 15918, 15910, 15903, 15894, 15886, 15877, 15870,\n\t 15861, 15853, 15843, 15836, 15827, 15818, 15810, 15801,\n\t 15792, 15783, 15774, 15765, 15756, 15747, 15738, 15729,\n\t 15719, 15709, 15700, 15691, 15681, 15672, 15662, 15652,\n\t 15642, 15633, 15623, 15613, 15602, 15592, 15582, 15572,\n\t 15562, 15552, 15540, 15530, 15520, 15509, 15499, 15489,\n\t 15478, 15467, 15456, 15446, 15433, 15423, 15412, 15401,\n\t 15390, 15379, 15367, 15356, 15345, 15333, 15321, 15310,\n\t 15299, 15287, 15276, 15264, 15252, 15240, 15228, 15216,\n\t 15205, 15192, 15180, 15167, 15155, 15143, 15131, 15118,\n\t 15106, 15094, 15081, 15067, 15056, 15043, 15031, 15017,\n\t 15004, 14992, 14979, 14966, 14953, 14940, 14927, 14913,\n\t 14900, 14887, 14874, 14860, 14846, 14833, 14819, 14806,\n\t 14793, 14778, 14764, 14752, 14737, 14723, 14709, 14696,\n\t 14681, 14668, 14653, 14638, 14625, 14610, 14595, 14582,\n\t 14567, 14553, 14538, 14523, 14509, 14494, 14480, 14465,\n\t 14450, 14435, 14420, 14406, 14391, 14376, 14361, 14346,\n\t 14330, 14316, 14301, 14285, 14270, 14254, 14239, 14223,\n\t 14208, 14192, 14177, 14161, 14146, 14130, 14115, 14099,\n\t 14082, 14067, 14051, 14035, 14019, 14003, 13986, 13971,\n\t 13955, 13939, 13923, 13906, 13890, 13873, 13857, 13840,\n\t 13823, 13808, 13791, 13775, 13758, 13741, 13724, 13707,\n\t 13691, 13673, 13657, 13641, 13623, 13607, 13589, 13572,\n\t 13556, 13538, 13521, 13504, 13486, 13469, 13451, 13435,\n\t 13417, 13399, 13383, 13365, 13347, 13330, 13312, 13294,\n\t 13277, 13258, 13241, 13224, 13205, 13188, 13170, 13152,\n\t 13134, 13116, 13098, 13080, 13062, 13044, 13026, 13008,\n\t 12989, 12971, 12953, 12934, 12916, 12898, 12879, 12860,\n\t 12842, 12823, 12806, 12787, 12768, 12750, 12731, 12712,\n\t 12694, 12675, 12655, 12637, 12618, 12599, 12580, 12562,\n\t 12542, 12524, 12504, 12485, 12466, 12448, 12427, 12408,\n\t 12390, 12370, 12351, 12332, 12312, 12293, 12273, 12254,\n\t 12235, 12215, 12195, 12176, 12157, 12137, 12118, 12097,\n\t 12079, 12059, 12039, 12019, 11998, 11980, 11960, 11940,\n\t 11920, 11900, 11880, 11860, 11839, 11821, 11801, 11780,\n\t 11761, 11741, 11720, 11700, 11680, 11660, 11640, 11619,\n\t 11599, 11578, 11559, 11538, 11518, 11498, 11477, 11457,\n\t 11436, 11415, 11394, 11374, 11354, 11333, 11313, 11292,\n\t 11272, 11251, 11231, 11209, 11189, 11168, 11148, 11127,\n\t 11107, 11084, 11064, 11043, 11023, 11002, 10982, 10959,\n\t 10939, 10918, 10898, 10876, 10856, 10834, 10814, 10792,\n\t 10772, 10750, 10728, 10708, 10687, 10666, 10644, 10623,\n\t 10602, 10581, 10560, 10538, 10517, 10496, 10474, 10453,\n\t 10431, 10410, 10389, 10368, 10346, 10325, 10303, 10283,\n\t 10260, 10239, 10217, 10196, 10175, 10152, 10132, 10110,\n\t 10088, 10068, 10045, 10023, 10002, 9981, 9959, 9936,\n\t 9915, 9893, 9872, 9851, 9829, 9806, 9784, 9763,\n\t 9742, 9720, 9698, 9676, 9653, 9633, 9611, 9589,\n\t 9567, 9545, 9523, 9501, 9479, 9458, 9436, 9414,\n\t 9392, 9370, 9348, 9326, 9304, 9282, 9260, 9238,\n\t 9216, 9194, 9172, 9150, 9128, 9106, 9084, 9062,\n\t 9040, 9018, 8996, 8974, 8951, 8929, 8907, 8885,\n\t 8863, 8841, 8819, 8797, 8775, 8752, 8730, 8708,\n\t 8686, 8664, 8642, 8620, 8597, 8575, 8553, 8531,\n\t 8509, 8487, 8464, 8442, 8420, 8398, 8376, 8353,\n\t 8331, 8309, 8287, 8265, 8242, 8220, 8198, 8176,\n\t 8154, 8131, 8109, 8087, 8065, 8042, 8020, 7998,\n\t 7976, 7954, 7931, 7909, 7887, 7865, 7842, 7820,\n\t 7798, 7776, 7754, 7731, 7709, 7687, 7665, 7643,\n\t 7620, 7598, 7576, 7554, 7531, 7509, 7487, 7465,\n\t 7443, 7421, 7398, 7376, 7354, 7332, 7310, 7288,\n\t 7265, 7243, 7221, 7199, 7177, 7155, 7132, 7110,\n\t 7088, 7066, 7044, 7022, 7000, 6978, 6956, 6934,\n\t 6911, 6889, 6867, 6845, 6823, 6801, 6779, 6757,\n\t 6735, 6713, 6691, 6669, 6647, 6625, 6603, 6581,\n\t 6559, 6537, 6515, 6493, 6472, 6450, 6428, 6406,\n\t 6384, 6362, 6340, 6318, 6297, 6275, 6253, 6231,\n\t 6209, 6188, 6166, 6144, 6122, 6101, 6079, 6057,\n\t 6035, 6014, 5992, 5970, 5949, 5927, 5905, 5884,\n\t 5862, 5841, 5819, 5797, 5776, 5754, 5733, 5711,\n\t 5690, 5668, 5647, 5625, 5604, 5582, 5561, 5540,\n\t 5518, 5497, 5476, 5454, 5433, 5412, 5390, 5369,\n\t 5348, 5327, 5305, 5284, 5263, 5242, 5221, 5199,\n\t 5178, 5157, 5136, 5115, 5094, 5073, 5052, 5031,\n\t 5010, 4989, 4968, 4947, 4926, 4905, 4885, 4864,\n\t 4843, 4822, 4801, 4780, 4760, 4739, 4718, 4698,\n\t 4677, 4656, 4636, 4615, 4595, 4574, 4553, 4533,\n\t 4512, 4492, 4471, 4451, 4431, 4410, 4390, 4370,\n\t 4349, 4329, 4309, 4288, 4268, 4248, 4228, 4208,\n\t 4188, 4167, 4147, 4127, 4107, 4087, 4067, 4047,\n\t 4027, 4007, 3988, 3968, 3948, 3928, 3908, 3889,\n\t 3869, 3849, 3829, 3810, 3790, 3771, 3751, 3732,\n\t 3712, 3693, 3673, 3654, 3634, 3615, 3595, 3576,\n\t 3557, 3538, 3518, 3499, 3480, 3461, 3442, 3423,\n\t 3404, 3385, 3366, 3347, 3328, 3309, 3290, 3271,\n\t 3252, 3233, 3215, 3196, 3177, 3159, 3140, 3121,\n\t 3103, 3084, 3066, 3047, 3029, 3010, 2992, 2974,\n\t 2955, 2937, 2919, 2901, 2882, 2864, 2846, 2828,\n\t 2810, 2792, 2774, 2756, 2738, 2720, 2702, 2685,\n\t 2667, 2649, 2631, 2614, 2596, 2579, 2561, 2543,\n\t 2526, 2509, 2491, 2474, 2456, 2439, 2422, 2405,\n\t 2387, 2370, 2353, 2336, 2319, 2302, 2285, 2268,\n\t 2251, 2234, 2218, 2201, 2184, 2167, 2151, 2134,\n\t 2117, 2101, 2084, 2068, 2052, 2035, 2019, 2003,\n\t 1986, 1970, 1954, 1938, 1922, 1906, 1890, 1874,\n\t 1858, 1842, 1826, 1810, 1794, 1779, 1763, 1747,\n\t 1732, 1716, 1701, 1685, 1670, 1654, 1639, 1624,\n\t 1608, 1593, 1578, 1563, 1548, 1533, 1518, 1503,\n\t 1488, 1473, 1458, 1444, 1429, 1414, 1400, 1385,\n\t 1370, 1356, 1342, 1327, 1313, 1298, 1284, 1270,\n\t 1256, 1242, 1228, 1214, 1200, 1186, 1172, 1158,\n\t 1144, 1131, 1117, 1103, 1090, 1076, 1063, 1049,\n\t 1036, 1022, 1009, 996, 983, 970, 956, 943,\n\t 930, 917, 905, 892, 879, 866, 854, 841,\n\t 828, 816, 803, 791, 778, 766, 754, 742,\n\t 729, 717, 705, 693, 681, 669, 658, 646,\n\t 634, 622, 611, 599, 588, 576, 565, 553,\n\t 542, 531, 520, 508, 497, 486, 475, 464,\n\t 453, 443, 432, 421, 411, 400, 389, 379,\n\t 369, 358, 348, 338, 327, 317, 307, 297,\n\t 287, 277, 268, 258, 248, 238, 229, 219,\n\t 210, 200, 191, 182, 172, 163, 154, 145,\n\t 136, 127, 118, 109, 100, 92, 83, 75,\n\t 66, 58, 49, 41, 32, 24, 16, 8,\n};\n\nstatic const int16 cubic_spline_lut2[1024] = {\n\t 0, 8, 16, 24, 32, 41, 49, 58,\n\t 66, 75, 83, 92, 100, 109, 118, 127,\n\t 136, 145, 154, 163, 172, 182, 191, 200,\n\t 210, 219, 229, 238, 248, 258, 268, 277,\n\t 287, 297, 307, 317, 327, 338, 348, 358,\n\t 369, 379, 389, 400, 411, 421, 432, 443,\n\t 453, 464, 475, 486, 497, 508, 520, 531,\n\t 542, 553, 565, 576, 588, 599, 611, 622,\n\t 634, 646, 658, 669, 681, 693, 705, 717,\n\t 729, 742, 754, 766, 778, 791, 803, 816,\n\t 828, 841, 854, 866, 879, 892, 905, 917,\n\t 930, 943, 956, 970, 983, 996, 1009, 1022,\n\t 1036, 1049, 1063, 1076, 1090, 1103, 1117, 1131,\n\t 1144, 1158, 1172, 1186, 1200, 1214, 1228, 1242,\n\t 1256, 1270, 1284, 1298, 1313, 1327, 1342, 1356,\n\t 1370, 1385, 1400, 1414, 1429, 1444, 1458, 1473,\n\t 1488, 1503, 1518, 1533, 1548, 1563, 1578, 1593,\n\t 1608, 1624, 1639, 1654, 1670, 1685, 1701, 1716,\n\t 1732, 1747, 1763, 1779, 1794, 1810, 1826, 1842,\n\t 1858, 1874, 1890, 1906, 1922, 1938, 1954, 1970,\n\t 1986, 2003, 2019, 2035, 2052, 2068, 2084, 2101,\n\t 2117, 2134, 2151, 2167, 2184, 2201, 2218, 2234,\n\t 2251, 2268, 2285, 2302, 2319, 2336, 2353, 2370,\n\t 2387, 2405, 2422, 2439, 2456, 2474, 2491, 2509,\n\t 2526, 2543, 2561, 2579, 2596, 2614, 2631, 2649,\n\t 2667, 2685, 2702, 2720, 2738, 2756, 2774, 2792,\n\t 2810, 2828, 2846, 2864, 2882, 2901, 2919, 2937,\n\t 2955, 2974, 2992, 3010, 3029, 3047, 3066, 3084,\n\t 3103, 3121, 3140, 3159, 3177, 3196, 3215, 3233,\n\t 3252, 3271, 3290, 3309, 3328, 3347, 3366, 3385,\n\t 3404, 3423, 3442, 3461, 3480, 3499, 3518, 3538,\n\t 3557, 3576, 3595, 3615, 3634, 3654, 3673, 3693,\n\t 3712, 3732, 3751, 3771, 3790, 3810, 3829, 3849,\n\t 3869, 3889, 3908, 3928, 3948, 3968, 3988, 4007,\n\t 4027, 4047, 4067, 4087, 4107, 4127, 4147, 4167,\n\t 4188, 4208, 4228, 4248, 4268, 4288, 4309, 4329,\n\t 4349, 4370, 4390, 4410, 4431, 4451, 4471, 4492,\n\t 4512, 4533, 4553, 4574, 4595, 4615, 4636, 4656,\n\t 4677, 4698, 4718, 4739, 4760, 4780, 4801, 4822,\n\t 4843, 4864, 4885, 4905, 4926, 4947, 4968, 4989,\n\t 5010, 5031, 5052, 5073, 5094, 5115, 5136, 5157,\n\t 5178, 5199, 5221, 5242, 5263, 5284, 5305, 5327,\n\t 5348, 5369, 5390, 5412, 5433, 5454, 5476, 5497,\n\t 5518, 5540, 5561, 5582, 5604, 5625, 5647, 5668,\n\t 5690, 5711, 5733, 5754, 5776, 5797, 5819, 5841,\n\t 5862, 5884, 5905, 5927, 5949, 5970, 5992, 6014,\n\t 6035, 6057, 6079, 6101, 6122, 6144, 6166, 6188,\n\t 6209, 6231, 6253, 6275, 6297, 6318, 6340, 6362,\n\t 6384, 6406, 6428, 6450, 6472, 6493, 6515, 6537,\n\t 6559, 6581, 6603, 6625, 6647, 6669, 6691, 6713,\n\t 6735, 6757, 6779, 6801, 6823, 6845, 6867, 6889,\n\t 6911, 6934, 6956, 6978, 7000, 7022, 7044, 7066,\n\t 7088, 7110, 7132, 7155, 7177, 7199, 7221, 7243,\n\t 7265, 7288, 7310, 7332, 7354, 7376, 7398, 7421,\n\t 7443, 7465, 7487, 7509, 7531, 7554, 7576, 7598,\n\t 7620, 7643, 7665, 7687, 7709, 7731, 7754, 7776,\n\t 7798, 7820, 7842, 7865, 7887, 7909, 7931, 7954,\n\t 7976, 7998, 8020, 8042, 8065, 8087, 8109, 8131,\n\t 8154, 8176, 8198, 8220, 8242, 8265, 8287, 8309,\n\t 8331, 8353, 8376, 8398, 8420, 8442, 8464, 8487,\n\t 8509, 8531, 8553, 8575, 8597, 8620, 8642, 8664,\n\t 8686, 8708, 8730, 8752, 8775, 8797, 8819, 8841,\n\t 8863, 8885, 8907, 8929, 8951, 8974, 8996, 9018,\n\t 9040, 9062, 9084, 9106, 9128, 9150, 9172, 9194,\n\t 9216, 9238, 9260, 9282, 9304, 9326, 9348, 9370,\n\t 9392, 9414, 9436, 9458, 9479, 9501, 9523, 9545,\n\t 9567, 9589, 9611, 9633, 9653, 9676, 9698, 9720,\n\t 9742, 9763, 9784, 9806, 9829, 9851, 9872, 9893,\n\t 9915, 9936, 9959, 9981, 10002, 10023, 10045, 10068,\n\t 10088, 10110, 10132, 10152, 10175, 10196, 10217, 10239,\n\t 10260, 10283, 10303, 10325, 10346, 10368, 10389, 10410,\n\t 10431, 10453, 10474, 10496, 10517, 10538, 10560, 10581,\n\t 10602, 10623, 10644, 10666, 10687, 10708, 10728, 10750,\n\t 10772, 10792, 10814, 10834, 10856, 10876, 10898, 10918,\n\t 10939, 10959, 10982, 11002, 11023, 11043, 11064, 11084,\n\t 11107, 11127, 11148, 11168, 11189, 11209, 11231, 11251,\n\t 11272, 11292, 11313, 11333, 11354, 11374, 11394, 11415,\n\t 11436, 11457, 11477, 11498, 11518, 11538, 11559, 11578,\n\t 11599, 11619, 11640, 11660, 11680, 11700, 11720, 11741,\n\t 11761, 11780, 11801, 11821, 11839, 11860, 11880, 11900,\n\t 11920, 11940, 11960, 11980, 11998, 12019, 12039, 12059,\n\t 12079, 12097, 12118, 12137, 12157, 12176, 12195, 12215,\n\t 12235, 12254, 12273, 12293, 12312, 12332, 12351, 12370,\n\t 12390, 12408, 12427, 12448, 12466, 12485, 12504, 12524,\n\t 12542, 12562, 12580, 12599, 12618, 12637, 12655, 12675,\n\t 12694, 12712, 12731, 12750, 12768, 12787, 12806, 12823,\n\t 12842, 12860, 12879, 12898, 12916, 12934, 12953, 12971,\n\t 12989, 13008, 13026, 13044, 13062, 13080, 13098, 13116,\n\t 13134, 13152, 13170, 13188, 13205, 13224, 13241, 13258,\n\t 13277, 13294, 13312, 13330, 13347, 13365, 13383, 13399,\n\t 13417, 13435, 13451, 13469, 13486, 13504, 13521, 13538,\n\t 13556, 13572, 13589, 13607, 13623, 13641, 13657, 13673,\n\t 13691, 13707, 13724, 13741, 13758, 13775, 13791, 13808,\n\t 13823, 13840, 13857, 13873, 13890, 13906, 13923, 13939,\n\t 13955, 13971, 13986, 14003, 14019, 14035, 14051, 14067,\n\t 14082, 14099, 14115, 14130, 14146, 14161, 14177, 14192,\n\t 14208, 14223, 14239, 14254, 14270, 14285, 14301, 14316,\n\t 14330, 14346, 14361, 14376, 14391, 14406, 14420, 14435,\n\t 14450, 14465, 14480, 14494, 14509, 14523, 14538, 14553,\n\t 14567, 14582, 14595, 14610, 14625, 14638, 14653, 14668,\n\t 14681, 14696, 14709, 14723, 14737, 14752, 14764, 14778,\n\t 14793, 14806, 14819, 14833, 14846, 14860, 14874, 14887,\n\t 14900, 14913, 14927, 14940, 14953, 14966, 14979, 14992,\n\t 15004, 15017, 15031, 15043, 15056, 15067, 15081, 15094,\n\t 15106, 15118, 15131, 15143, 15155, 15167, 15180, 15192,\n\t 15205, 15216, 15228, 15240, 15252, 15264, 15276, 15287,\n\t 15299, 15310, 15321, 15333, 15345, 15356, 15367, 15379,\n\t 15390, 15401, 15412, 15423, 15433, 15446, 15456, 15467,\n\t 15478, 15489, 15499, 15509, 15520, 15530, 15540, 15552,\n\t 15562, 15572, 15582, 15592, 15602, 15613, 15623, 15633,\n\t 15642, 15652, 15662, 15672, 15681, 15691, 15700, 15709,\n\t 15719, 15729, 15738, 15747, 15756, 15765, 15774, 15783,\n\t 15792, 15801, 15810, 15818, 15827, 15836, 15843, 15853,\n\t 15861, 15870, 15877, 15886, 15894, 15903, 15910, 15918,\n\t 15926, 15934, 15941, 15949, 15957, 15964, 15973, 15980,\n\t 15988, 15995, 16002, 16009, 16015, 16023, 16030, 16038,\n\t 16044, 16052, 16058, 16064, 16071, 16077, 16085, 16092,\n\t 16097, 16104, 16109, 16116, 16122, 16128, 16133, 16140,\n\t 16146, 16151, 16158, 16163, 16169, 16174, 16180, 16185,\n\t 16191, 16196, 16201, 16206, 16211, 16216, 16220, 16225,\n\t 16230, 16235, 16239, 16244, 16247, 16253, 16257, 16262,\n\t 16265, 16269, 16274, 16278, 16282, 16285, 16290, 16293,\n\t 16297, 16299, 16304, 16307, 16309, 16314, 16317, 16320,\n\t 16322, 16326, 16329, 16331, 16335, 16337, 16340, 16342,\n\t 16345, 16347, 16349, 16351, 16354, 16357, 16358, 16360,\n\t 16361, 16364, 16366, 16366, 16369, 16370, 16371, 16373,\n\t 16374, 16376, 16377, 16377, 16379, 16379, 16380, 16381,\n\t 16381, 16381, 16382, 16383, 16384, 16384, 16384, 16384,\n};\n\nstatic const int16 cubic_spline_lut3[1024] = {\n\t 0, 0, 0, 0, 0, 0, 0, 0,\n\t 0, -1, -1, -1, -1, -1, -2, -2,\n\t -2, -2, -2, -3, -3, -3, -4, -4,\n\t -4, -5, -5, -6, -6, -6, -7, -7,\n\t -8, -8, -9, -9, -10, -10, -11, -11,\n\t -12, -13, -13, -14, -14, -15, -16, -16,\n\t -17, -18, -19, -19, -20, -21, -22, -22,\n\t -23, -24, -25, -26, -26, -27, -28, -29,\n\t -30, -31, -32, -33, -34, -35, -36, -37,\n\t -38, -39, -40, -41, -42, -43, -44, -45,\n\t -46, -47, -48, -49, -51, -52, -53, -54,\n\t -55, -57, -58, -59, -60, -61, -63, -64,\n\t -65, -67, -68, -69, -70, -72, -73, -75,\n\t -76, -77, -79, -80, -82, -83, -84, -86,\n\t -87, -89, -90, -92, -93, -95, -96, -98,\n\t -99, -101, -102, -104, -106, -107, -109, -110,\n\t -112, -114, -115, -117, -119, -120, -122, -124,\n\t -125, -127, -129, -130, -132, -134, -136, -137,\n\t -139, -141, -143, -145, -146, -148, -150, -152,\n\t -154, -156, -157, -159, -161, -163, -165, -167,\n\t -169, -171, -173, -175, -176, -178, -180, -182,\n\t -184, -186, -188, -190, -192, -194, -196, -198,\n\t -200, -202, -205, -207, -209, -211, -213, -215,\n\t -217, -219, -221, -223, -225, -228, -230, -232,\n\t -234, -236, -238, -240, -243, -245, -247, -249,\n\t -251, -254, -256, -258, -260, -263, -265, -267,\n\t -269, -272, -274, -276, -278, -281, -283, -285,\n\t -288, -290, -292, -295, -297, -299, -302, -304,\n\t -306, -309, -311, -313, -316, -318, -320, -323,\n\t -325, -328, -330, -332, -335, -337, -340, -342,\n\t -345, -347, -349, -352, -354, -357, -359, -362,\n\t -364, -367, -369, -372, -374, -377, -379, -382,\n\t -384, -387, -389, -392, -394, -397, -399, -402,\n\t -404, -407, -409, -412, -414, -417, -419, -422,\n\t -424, -427, -430, -432, -435, -437, -440, -442,\n\t -445, -448, -450, -453, -455, -458, -461, -463,\n\t -466, -468, -471, -474, -476, -479, -481, -484,\n\t -487, -489, -492, -495, -497, -500, -502, -505,\n\t -508, -510, -513, -516, -518, -521, -523, -526,\n\t -529, -531, -534, -537, -539, -542, -545, -547,\n\t -550, -553, -555, -558, -561, -563, -566, -569,\n\t -571, -574, -577, -579, -582, -585, -587, -590,\n\t -593, -595, -598, -601, -603, -606, -609, -611,\n\t -614, -617, -619, -622, -625, -627, -630, -633,\n\t -635, -638, -641, -643, -646, -649, -651, -654,\n\t -657, -659, -662, -665, -667, -670, -672, -675,\n\t -678, -680, -683, -686, -688, -691, -694, -696,\n\t -699, -702, -704, -707, -709, -712, -715, -717,\n\t -720, -723, -725, -728, -730, -733, -736, -738,\n\t -741, -744, -746, -749, -751, -754, -757, -759,\n\t -762, -764, -767, -769, -772, -775, -777, -780,\n\t -782, -785, -787, -790, -793, -795, -798, -800,\n\t -803, -805, -808, -810, -813, -815, -818, -820,\n\t -823, -825, -828, -830, -833, -835, -838, -840,\n\t -843, -845, -848, -850, -853, -855, -858, -860,\n\t -863, -865, -867, -870, -872, -875, -877, -880,\n\t -882, -884, -887, -889, -892, -894, -896, -899,\n\t -901, -903, -906, -908, -911, -913, -915, -918,\n\t -920, -922, -924, -927, -929, -931, -934, -936,\n\t -938, -941, -943, -945, -947, -950, -952, -954,\n\t -956, -958, -961, -963, -965, -967, -969, -972,\n\t -974, -976, -978, -980, -982, -985, -987, -989,\n\t -991, -993, -995, -997, -999, -1002, -1004, -1006,\n\t -1008, -1010, -1012, -1014, -1016, -1018, -1020, -1022,\n\t -1024, -1026, -1028, -1030, -1032, -1034, -1036, -1038,\n\t -1040, -1042, -1044, -1046, -1047, -1049, -1051, -1053,\n\t -1055, -1057, -1059, -1061, -1062, -1064, -1066, -1068,\n\t -1070, -1071, -1073, -1075, -1077, -1079, -1080, -1082,\n\t -1084, -1085, -1087, -1089, -1091, -1092, -1094, -1096,\n\t -1097, -1099, -1101, -1102, -1104, -1105, -1107, -1109,\n\t -1110, -1112, -1113, -1115, -1116, -1118, -1119, -1121,\n\t -1122, -1124, -1125, -1127, -1128, -1130, -1131, -1133,\n\t -1134, -1135, -1137, -1138, -1140, -1141, -1142, -1144,\n\t -1145, -1146, -1148, -1149, -1150, -1151, -1153, -1154,\n\t -1155, -1156, -1158, -1159, -1160, -1161, -1162, -1163,\n\t -1165, -1166, -1167, -1168, -1169, -1170, -1171, -1172,\n\t -1173, -1174, -1175, -1176, -1177, -1178, -1179, -1180,\n\t -1181, -1182, -1183, -1184, -1185, -1186, -1187, -1187,\n\t -1188, -1189, -1190, -1191, -1192, -1192, -1193, -1194,\n\t -1195, -1195, -1196, -1197, -1197, -1198, -1199, -1199,\n\t -1200, -1201, -1201, -1202, -1202, -1203, -1204, -1204,\n\t -1205, -1205, -1206, -1206, -1207, -1207, -1207, -1208,\n\t -1208, -1209, -1209, -1209, -1210, -1210, -1210, -1211,\n\t -1211, -1211, -1211, -1212, -1212, -1212, -1212, -1213,\n\t -1213, -1213, -1213, -1213, -1213, -1213, -1213, -1214,\n\t -1214, -1214, -1214, -1214, -1214, -1214, -1214, -1213,\n\t -1213, -1213, -1213, -1213, -1213, -1213, -1213, -1212,\n\t -1212, -1212, -1212, -1212, -1211, -1211, -1211, -1210,\n\t -1210, -1210, -1209, -1209, -1208, -1208, -1208, -1207,\n\t -1207, -1206, -1206, -1205, -1205, -1204, -1204, -1203,\n\t -1202, -1202, -1201, -1200, -1200, -1199, -1198, -1198,\n\t -1197, -1196, -1195, -1195, -1194, -1193, -1192, -1191,\n\t -1190, -1189, -1188, -1187, -1187, -1186, -1185, -1184,\n\t -1182, -1181, -1180, -1179, -1178, -1177, -1176, -1175,\n\t -1174, -1172, -1171, -1170, -1169, -1167, -1166, -1165,\n\t -1163, -1162, -1161, -1159, -1158, -1156, -1155, -1153,\n\t -1152, -1150, -1149, -1147, -1146, -1144, -1143, -1141,\n\t -1139, -1138, -1136, -1134, -1133, -1131, -1129, -1127,\n\t -1125, -1124, -1122, -1120, -1118, -1116, -1114, -1112,\n\t -1110, -1108, -1106, -1104, -1102, -1100, -1098, -1096,\n\t -1094, -1092, -1089, -1087, -1085, -1083, -1080, -1078,\n\t -1076, -1074, -1071, -1069, -1066, -1064, -1062, -1059,\n\t -1057, -1054, -1052, -1049, -1047, -1044, -1041, -1039,\n\t -1036, -1033, -1031, -1028, -1025, -1022, -1020, -1017,\n\t -1014, -1011, -1008, -1005, -1002, -999, -997, -994,\n\t -991, -987, -984, -981, -978, -975, -972, -969,\n\t -966, -962, -959, -956, -953, -949, -946, -943,\n\t -939, -936, -932, -929, -925, -922, -918, -915,\n\t -911, -908, -904, -900, -897, -893, -889, -886,\n\t -882, -878, -874, -870, -866, -863, -859, -855,\n\t -851, -847, -843, -839, -835, -831, -826, -822,\n\t -818, -814, -810, -806, -801, -797, -793, -788,\n\t -784, -780, -775, -771, -766, -762, -757, -753,\n\t -748, -744, -739, -734, -730, -725, -720, -715,\n\t -711, -706, -701, -696, -691, -686, -682, -677,\n\t -672, -667, -662, -656, -651, -646, -641, -636,\n\t -631, -626, -620, -615, -610, -604, -599, -594,\n\t -588, -583, -577, -572, -566, -561, -555, -550,\n\t -544, -538, -533, -527, -521, -515, -510, -504,\n\t -498, -492, -486, -480, -474, -468, -462, -456,\n\t -450, -444, -438, -432, -425, -419, -413, -407,\n\t -400, -394, -388, -381, -375, -368, -362, -355,\n\t -349, -342, -336, -329, -322, -316, -309, -302,\n\t -295, -289, -282, -275, -268, -261, -254, -247,\n\t -240, -233, -226, -219, -212, -205, -198, -190,\n\t -183, -176, -169, -161, -154, -146, -139, -132,\n\t -124, -117, -109, -101, -94, -86, -78, -71,\n\t -63, -55, -47, -40, -32, -24, -16, -8,\n};\n\n"
  },
  {
    "path": "contrib/libxmp/src/read_event.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n#include \"player.h\"\n#include \"effects.h\"\n#include \"virtual.h\"\n#include \"period.h\"\n#include \"rng.h\"\n\n#ifndef LIBXMP_CORE_PLAYER\n#include \"med_extras.h\"\n#endif\n\n\nstatic struct xmp_subinstrument *get_subinstrument(struct context_data *ctx,\n\t\t\t\t\t\t   int ins, int key)\n{\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_instrument *instrument;\n\n\tif (IS_VALID_INSTRUMENT(ins)) {\n\t\tinstrument = &mod->xxi[ins];\n\t\tif (IS_VALID_NOTE(key)) {\n\t\t\tint mapped = instrument->map[key].ins;\n\t\t\tif (mapped != 0xff && mapped >= 0 && mapped < instrument->nsm)\n\t\t\t  \treturn &instrument->sub[mapped];\n\t\t} else {\n\t\t\tif (mod->xxi[ins].nsm > 0) {\n\t\t\t\treturn &instrument->sub[0];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nstatic void reset_envelopes(struct context_data *ctx, struct channel_data *xc)\n{\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\n\tif (!IS_VALID_INSTRUMENT(xc->ins))\n\t\treturn;\n\n\tRESET_NOTE(NOTE_ENV_END);\n\n\txc->v_idx = -1;\n\txc->p_idx = -1;\n\txc->f_idx = -1;\n}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\nstatic void reset_envelope_volume(struct context_data *ctx,\n\t\t\t\tstruct channel_data *xc)\n{\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\n\tif (!IS_VALID_INSTRUMENT(xc->ins))\n\t\treturn;\n\n\tRESET_NOTE(NOTE_ENV_END);\n\n\txc->v_idx = -1;\n}\n\nstatic void reset_envelopes_carry(struct context_data *ctx,\n\t\t\t\tstruct channel_data *xc)\n{\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_instrument *xxi;\n\n\tif (!IS_VALID_INSTRUMENT(xc->ins))\n\t\treturn;\n\n \tRESET_NOTE(NOTE_ENV_END);\n\n\txxi = libxmp_get_instrument(ctx, xc->ins);\n\n\t/* Reset envelope positions */\n\tif (~xxi->aei.flg & XMP_ENVELOPE_CARRY) {\n\t\txc->v_idx = -1;\n\t}\n\tif (~xxi->pei.flg & XMP_ENVELOPE_CARRY) {\n\t\txc->p_idx = -1;\n\t}\n\tif (~xxi->fei.flg & XMP_ENVELOPE_CARRY) {\n\t\txc->f_idx = -1;\n\t}\n}\n\n#endif\n\nstatic void set_effect_defaults(struct context_data *ctx, int note,\n\t\t\t\tstruct xmp_subinstrument *sub,\n\t\t\t\tstruct channel_data *xc, int is_toneporta)\n{\n\tstruct module_data *m = &ctx->m;\n\n\tif (sub != NULL && note >= 0) {\n\t\tif (!HAS_QUIRK(QUIRK_PROTRACK)) {\n\t\t\txc->finetune = sub->fin;\n\t\t}\n\t\txc->gvl = sub->gvl;\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\t\tif (sub->ifc & 0x80) {\n\t\t\txc->filter.cutoff = (sub->ifc - 0x80) * 2;\n\t\t}\n\t\txc->filter.envelope = 0x100;\n\n\t\tif (sub->ifr & 0x80) {\n\t\t\txc->filter.resonance = (sub->ifr - 0x80) * 2;\n\t\t}\n\n\t\t/* IT: on a new note without toneporta, allow a computed cutoff\n\t\t * of 127 with resonance 0 to disable the filter. */\n\t\txc->filter.can_disable = !is_toneporta;\n#endif\n\n\t\t/* TODO: should probably expand the LFO period size instead\n\t\t * of reducing the vibrato rate precision here.\n\t\t */\n\t\tlibxmp_lfo_set_depth(&xc->insvib.lfo, sub->vde);\n\t\tlibxmp_lfo_set_rate(&xc->insvib.lfo, (sub->vra + 2) >> 2);\n\t\tlibxmp_lfo_set_waveform(&xc->insvib.lfo, sub->vwf);\n\t\txc->insvib.sweep = sub->vsw;\n\n\t\tlibxmp_lfo_set_phase(&xc->vibrato.lfo, 0);\n\t\tlibxmp_lfo_set_phase(&xc->tremolo.lfo, 0);\n\t}\n\n\txc->delay = 0;\n\txc->tremor.up = xc->tremor.down = 0;\n\n\t/* Reset arpeggio */\n\txc->arpeggio.val[0] = 0;\n\txc->arpeggio.count = 0;\n\txc->arpeggio.size = 1;\n}\n\n/* From OpenMPT PortaTarget.mod:\n * \"A new note (with no portamento command next to it) does not reset the\n *  portamento target. That is, if a previous portamento has not finished yet,\n *  calling 3xx or 5xx after the new note will slide it towards the old target.\n *  Once the portamento target period is reached, the target is reset. This\n *  means that if the period is modified by another slide (e.g. 1xx or 2xx),\n *  a following 3xx will not slide back to the original target.\"\n */\nstatic void set_period(struct context_data *ctx, int note,\n\t\t\t\tstruct xmp_subinstrument *sub,\n\t\t\t\tstruct channel_data *xc, int is_toneporta)\n{\n\tstruct module_data *m = &ctx->m;\n\n\t/* TODO: blocking period updates on whether or not the event has a\n\t * valid instrument seems suspicious, but almost every format uses\n\t * this. Only allow Protracker to update without it for now. */\n\tif (sub == NULL && !HAS_QUIRK(QUIRK_PROTRACK))\n\t\treturn;\n\n\tif (note >= 0) {\n\t\tdouble per = libxmp_note_to_period(ctx, note, xc->finetune,\n\t\t\t\t\t\t\txc->per_adj);\n\n\t\tif (!HAS_QUIRK(QUIRK_PROTRACK) || (note > 0 && is_toneporta)) {\n\t\t\txc->porta.target = per;\n\t\t}\n\n\t\tif (xc->period < 1 || !is_toneporta) {\n\t\t\txc->period = per;\n\t\t}\n\t}\n}\n\n/* From OpenMPT Porta-Pickup.xm:\n * \"An instrument number should not reset the current portamento target. The\n *  portamento target is valid until a new target is specified by combining a\n *  note and a portamento effect.\"\n */\nstatic void set_period_ft2(struct context_data *ctx, int note,\n\t\t\t\tstruct xmp_subinstrument *sub,\n\t\t\t\tstruct channel_data *xc, int is_toneporta)\n{\n\tif (note > 0 && is_toneporta) {\n\t\txc->porta.target = libxmp_note_to_period(ctx, note, xc->finetune,\n\t\t\t\t\t\t\t\txc->per_adj);\n\t}\n\tif (sub != NULL && note >= 0) {\n\t\tif (xc->period < 1 || !is_toneporta) {\n\t\t\txc->period = libxmp_note_to_period(ctx, note, xc->finetune,\n\t\t\t\t\t\t\t\txc->per_adj);\n\t\t}\n\t}\n}\n\n\n#ifndef LIBXMP_CORE_PLAYER\n#define IS_SFX_PITCH(x) ((x) == FX_PITCH_ADD || (x) == FX_PITCH_SUB)\n#define IS_TONEPORTA(x) ((x) == FX_TONEPORTA || (x) == FX_TONE_VSLIDE \\\n\t\t|| (x) == FX_PER_TPORTA || (x) == FX_ULT_TPORTA \\\n\t\t|| (x) == FX_FAR_TPORTA)\n#else\n#define IS_TONEPORTA(x) ((x) == FX_TONEPORTA || (x) == FX_TONE_VSLIDE)\n#endif\n\n#define IS_MOD_RETRIG(x,p) ((x) == FX_EXTENDED && MSN(p) == EX_RETRIG && LSN(p) != 0)\n\n#define set_patch(ctx,chn,ins,smp,note) \\\n\tlibxmp_virt_setpatch(ctx, chn, ins, smp, note, 0, 0, 0, 0)\n\nstatic int read_event_mod(struct context_data *ctx, struct xmp_event *e, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tint note;\n\tstruct xmp_subinstrument *sub = NULL;\n\tint new_invalid_ins = 0;\n\tint new_swap_ins = 0;\n\tint is_toneporta;\n\tint is_retrig;\n\tint use_ins_vol;\n\n\txc->flags = 0;\n\tnote = -1;\n\tis_toneporta = 0;\n\tis_retrig = 0;\n\tuse_ins_vol = 0;\n\n\tif (IS_TONEPORTA(e->fxt) || IS_TONEPORTA(e->f2t)) {\n\t\tis_toneporta = 1;\n\t}\n\tif (IS_MOD_RETRIG(e->fxt, e->fxp) || IS_MOD_RETRIG(e->f2t, e->f2p)) {\n\t\tis_retrig = 1;\n\t}\n\n\t/* Check instrument */\n\n\tif (e->ins) {\n\t\tint ins = e->ins - 1;\n\t\tuse_ins_vol = 1;\n\t\tSET(NEW_INS);\n\t\txc->fadeout = 0x10000;\t/* for painlace.mod pat 0 ch 3 echo */\n\t\txc->per_flags = 0;\n\t\txc->offset.val = 0;\n\t\tRESET_NOTE(NOTE_RELEASE|NOTE_FADEOUT);\n\n\t\tif (IS_VALID_INSTRUMENT(ins)) {\n\t\t\tsub = get_subinstrument(ctx, ins, e->note - 1);\n\n\t\t\tif (sub != NULL) {\n\t\t\t\tnew_swap_ins = 1;\n\n\t\t\t\t/* Finetune is always loaded, but only applies\n\t\t\t\t * when the period is updated by a note/porta\n\t\t\t\t * (OpenMPT finetune.mod, PortaSwapPT.mod). */\n\t\t\t\tif (HAS_QUIRK(QUIRK_PROTRACK)) {\n\t\t\t\t\txc->finetune = sub->fin;\n\t\t\t\t\txc->ins = ins;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (is_toneporta) {\n\t\t\t\t/* Get new instrument volume */\n\t\t\t\tif (sub != NULL) {\n\t\t\t\t\t/* Dennis Lindroos: instrument volume\n\t\t\t\t\t * is not used on split channels\n\t\t\t\t\t */\n\t\t\t\t\tif (!xc->split) {\n\t\t\t\t\t\txc->volume = sub->vol;\n\t\t\t\t\t}\n\t\t\t\t\tuse_ins_vol = 0;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\txc->ins = ins;\n\t\t\t\txc->ins_fade = mod->xxi[ins].rls;\n\t\t\t}\n\t\t} else {\n\t\t\tnew_invalid_ins = 1;\n\n\t\t\t/* Invalid instruments do not reset the channel in\n\t\t\t * Protracker; instead, they set the current sample\n\t\t\t * to the invalid sample, which stops the current\n\t\t\t * sample at the end of its loop.\n\t\t\t *\n\t\t\t * OpenMPT PTInstrSwap.mod: uses a null sample to pause\n\t\t\t * a looping sample, plays several on a channel with no note.\n\t\t\t *\n\t\t\t * OpenMPT PTSwapEmpty.mod: repeatedly pauses and\n\t\t\t * restarts a sample using a null sample.\n\t\t\t */\n\t\t\tif (!HAS_QUIRK(QUIRK_PROTRACK) || is_retrig) {\n\t\t\t\tlibxmp_virt_resetchannel(ctx, chn);\n\t\t\t} else {\n\t\t\t\tlibxmp_virt_queuepatch(ctx, chn, -1, -1, 0);\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Check note */\n\n\tif (e->note) {\n\t\tSET(NEW_NOTE);\n\n\t\tif (e->note == XMP_KEY_OFF) {\n\t\t\tSET_NOTE(NOTE_RELEASE);\n\t\t\tuse_ins_vol = 0;\n\t\t} else if (!is_toneporta && IS_VALID_NOTE(e->note - 1)) {\n\t\t\txc->key = e->note - 1;\n\t\t\tRESET_NOTE(NOTE_END);\n\n\t\t\tsub = get_subinstrument(ctx, xc->ins, xc->key);\n\n\t\t\tif (sub != NULL) {\n\t\t\t\tint transp = mod->xxi[xc->ins].map[xc->key].xpo;\n\t\t\t\tint smp;\n\n\t\t\t\tnote = xc->key + sub->xpo + transp;\n\t\t\t\tsmp = sub->sid;\n\n\t\t\t\tif (new_invalid_ins || !IS_VALID_SAMPLE(smp)) {\n\t\t\t\t\tsmp = -1;\n\t\t\t\t}\n\n\t\t\t\tif (smp >= 0 && smp < mod->smp) {\n\t\t\t\t\tset_patch(ctx, chn, xc->ins, smp, note);\n\t\t\t\t\tnew_swap_ins = 0;\n\t\t\t\t\txc->smp = smp;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\txc->flags = 0;\n\t\t\t\tuse_ins_vol = 0;\n\t\t\t\tnote = xc->key;\n\t\t\t}\n\t\t}\n\n\t\tif (note >= 0) {\n\t\t\txc->note = note;\n\t\t\tSET_NOTE(NOTE_SET);\n\t\t}\n\t}\n\n\t/* Protracker 1/2 sample swap occurs when a sample number is\n\t * encountered without a note or with a note and toneporta. The new\n\t * instrument is switched to when the current sample reaches its loop\n\t * end. A valid note must have been played in this channel before.\n\t *\n\t * Empty samples can also be set, which stops the sample at the end\n\t * of its loop (see above).\n\t */\n\tif (new_swap_ins && sub && HAS_QUIRK(QUIRK_PROTRACK) && TEST_NOTE(NOTE_SET)) {\n\t\tlibxmp_virt_queuepatch(ctx, chn, e->ins - 1, sub->sid, xc->note);\n\t\txc->smp = sub->sid;\n\t}\n\n\tsub = get_subinstrument(ctx, xc->ins, xc->key);\n\n\tset_effect_defaults(ctx, note, sub, xc, is_toneporta);\n\tif (e->ins && sub != NULL) {\n\t\treset_envelopes(ctx, xc);\n\t}\n\n\t/* Process new volume */\n\tif (e->vol) {\n\t\txc->volume = e->vol - 1;\n\t\tSET(NEW_VOL);\n\t\tRESET_PER(VOL_SLIDE); /* FIXME: should this be for FAR only? */\n\t}\n\n\t/* Secondary effect handled first */\n\tlibxmp_process_fx(ctx, xc, chn, e, 1);\n\tlibxmp_process_fx(ctx, xc, chn, e, 0);\n\n#ifndef LIBXMP_CORE_PLAYER\n\tif (IS_SFX_PITCH(e->fxt)) {\n \t\txc->period = libxmp_note_to_period(ctx, note, xc->finetune,\n                                \t\t\txc->per_adj);\n\t} else\n#endif\n\t{\n\t\tset_period(ctx, note, sub, xc, is_toneporta);\n\t}\n\n\tif (sub == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (note >= 0 && !new_invalid_ins) {\n\t\tlibxmp_virt_voicepos(ctx, chn, xc->offset.val);\n\t} else if (new_swap_ins && is_retrig && HAS_QUIRK(QUIRK_PROTRACK)) {\n\t\t/* Protracker: an instrument number with no note and retrigger\n\t\t * triggers the new sample on tick 0. Other effects that set\n\t\t * RETRIG should not. (OpenMPT InstrSwapRetrigger.mod) */\n\t\tlibxmp_virt_voicepos(ctx, chn, 0);\n\t}\n\n\tif (TEST(OFFSET)) {\n\t\tif (HAS_QUIRK(QUIRK_PROTRACK) || p->flags & XMP_FLAGS_FX9BUG) {\n\t\t\txc->offset.val += xc->offset.val2;\n\t\t}\n\t\tRESET(OFFSET);\n\t}\n\n\tif (use_ins_vol && !TEST(NEW_VOL) && !xc->split) {\n\t\txc->volume = sub->vol;\n\t}\n\n\treturn 0;\n}\n\nstatic int sustain_check(struct xmp_envelope *env, int idx)\n{\n\treturn (env &&\n\t\t(env->flg & XMP_ENVELOPE_ON) &&\n\t\t(env->flg & XMP_ENVELOPE_SUS) &&\n\t\t(~env->flg & XMP_ENVELOPE_LOOP) &&\n\t\tidx == env->data[env->sus << 1]);\n}\n\nstatic int read_event_ft2(struct context_data *ctx, struct xmp_event *e, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tint note, key, ins;\n\tstruct xmp_subinstrument *sub;\n\tint new_invalid_ins;\n\tint is_toneporta;\n\tint use_ins_vol;\n\tint k00 = 0;\n\tstruct xmp_event ev;\n\n\t/* From the OpenMPT DelayCombination.xm test case:\n         * \"Naturally, Fasttracker 2 ignores notes next to an out-of-range\n\t *  note delay. However, to check whether the delay is out of range,\n\t *  it is simply compared against the current song speed, not taking\n\t *  any pattern delays into account.\"\n\t */\n\tif (p->frame >= p->speed) {\n\t\treturn 0;\n\t}\n\n\tmemcpy(&ev, e, sizeof (struct xmp_event));\n\n\t/* From OpenMPT TremorReset.xm test case:\n\t * \"Even if a tremor effect muted the sample on a previous row, volume\n\t *  commands should be able to override this effect.\"\n\t */\n\tif (ev.vol) {\n\t\txc->tremor.count &= ~0x80;\n\t}\n\n\txc->flags = 0;\n\tnote = -1;\n\tkey = ev.note;\n\tins = ev.ins;\n\tnew_invalid_ins = 0;\n\tis_toneporta = 0;\n\tuse_ins_vol = 0;\n\n\t/* From the OpenMPT key_off.xm test case:\n\t * \"Key off at tick 0 (K00) is very dodgy command. If there is a note\n\t *  next to it, the note is ignored. If there is a volume column\n\t *  command or instrument next to it and the current instrument has\n\t *  no volume envelope, the note is faded out instead of being cut.\"\n\t */\n\tif (ev.fxt == FX_KEYOFF && ev.fxp == 0) {\n\t\tk00 = 1;\n\t\tkey = 0;\n\n\t\tif (ins || ev.vol || ev.f2t) {\n\t\t\tif (IS_VALID_INSTRUMENT(xc->ins) &&\n\t\t\t    ~mod->xxi[xc->ins].aei.flg & XMP_ENVELOPE_ON) {\n\t\t\t\tSET_NOTE(NOTE_FADEOUT);\n\t\t\t\tev.fxt = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IS_TONEPORTA(ev.fxt) || IS_TONEPORTA(ev.f2t)) {\n\t\tis_toneporta = 1;\n\t}\n\n\t/* Check instrument */\n\n\t/* Ignore invalid instruments. The last instrument, invalid or\n\t * not, is preserved in channel data (see read_event() below).\n\t * Fixes stray delayed notes in forgotten_city.xm.\n\t */\n\tif (ins > 0 && !IS_VALID_INSTRUMENT(ins - 1)) {\n\t\tins = 0;\n\t}\n\n\t/* FT2: Retrieve old instrument volume */\n\tif (ins) {\n\t\tif (key == 0 || key >= XMP_KEY_OFF) {\n\t\t\t/* Previous instrument */\n\t\t\tsub = get_subinstrument(ctx, xc->ins, xc->key);\n\n\t\t\t/* No note */\n\t\t\tif (sub != NULL) {\n\t\t\t\tint pan = mod->xxc[chn].pan - 128;\n\t\t\t\txc->volume = sub->vol;\n\n\t\t\t\tif (!HAS_QUIRK(QUIRK_FTMOD)) {\n\t\t\t\t\txc->pan.val = pan + ((sub->pan - 128) *\n\t\t\t\t\t\t(128 - abs(pan))) / 128 + 128;\n\t\t\t\t}\n\n\t\t\t\txc->ins_fade = mod->xxi[xc->ins].rls;\n\t\t\t\tSET(NEW_VOL);\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Do this regardless if the instrument is invalid or not -- unless\n\t * XM keyoff is used. Fixes xyce-dans_la_rue.xm chn 0 patterns 0E/0F and\n\t * chn 10 patterns 0D/0E, see https://github.com/libxmp/libxmp/issues/152\n\t * for details.\n         */\n\tif (ev.ins && key != XMP_KEY_FADE) {\n\t\tSET(NEW_INS);\n\t\tuse_ins_vol = 1;\n\t\txc->per_flags = 0;\n\n\t\tRESET_NOTE(NOTE_RELEASE|NOTE_SUSEXIT);\n\t\tif (!k00) {\n\t\t\tRESET_NOTE(NOTE_FADEOUT);\n\t\t}\n\n\t\txc->fadeout = 0x10000;\n\n\t\tif (IS_VALID_INSTRUMENT(ins - 1)) {\n\t\t\tif (!is_toneporta)\n\t\t\t\txc->ins = ins - 1;\n\t\t} else {\n\t\t\tnew_invalid_ins = 1;\n\n\t\t\t/* If no note is set FT2 doesn't cut on invalid\n\t\t\t * instruments (it keeps playing the previous one).\n\t\t\t * If a note is set it cuts the current sample.\n\t\t\t */\n\t\t\txc->flags = 0;\n\n\t\t\tif (is_toneporta) {\n\t\t\t\tkey = 0;\n\t\t\t}\n\t\t}\n\n\t\txc->tremor.count = 0x20;\n\t}\n\n\t/* Check note */\n\tif (ins) {\n\t\tif (key > 0 && key < XMP_KEY_OFF) {\n\t\t\t/* Retrieve volume when we have note */\n\n\t\t\t/* and only if we have instrument, otherwise we're in\n\t\t\t * case 1: new note and no instrument\n\t\t\t */\n\n\t\t\t/* Current instrument */\n\t\t\tsub = get_subinstrument(ctx, xc->ins, key - 1);\n\t\t\tif (sub != NULL) {\n\t\t\t\tint pan = mod->xxc[chn].pan - 128;\n\t\t\t\txc->volume = sub->vol;\n\n\t\t\t\tif (!HAS_QUIRK(QUIRK_FTMOD)) {\n\t\t\t\t\txc->pan.val = pan + ((sub->pan - 128) *\n\t\t\t\t\t\t(128 - abs(pan))) / 128 + 128;\n\t\t\t\t}\n\n\t\t\t\txc->ins_fade = mod->xxi[xc->ins].rls;\n\t\t\t} else {\n\t\t\t\txc->volume = 0;\n\t\t\t}\n\t\t\tSET(NEW_VOL);\n\t\t}\n\t}\n\n\tif (key) {\n\t\tSET(NEW_NOTE);\n\n\t\tif (key == XMP_KEY_OFF) {\n\t\t\tint env_on = 0;\n\t\t\tint vol_set = ev.vol != 0 || ev.fxt == FX_VOLSET;\n\t\t\tint delay_fx = ev.fxt == FX_EXTENDED && ev.fxp == 0xd0;\n\t\t\tstruct xmp_envelope *env = NULL;\n\n\t\t\t/* OpenMPT NoteOffVolume.xm:\n\t\t\t * \"If an instrument has no volume envelope, a note-off\n\t\t\t *  command should cut the sample completely - unless\n\t\t\t *  there is a volume command next it. This applies to\n\t\t\t *  both volume commands (volume and effect column).\"\n\t\t\t *\n\t\t\t * ...and unless we have a keyoff+delay without setting\n\t\t\t * an instrument. See OffDelay.xm.\n\t\t\t */\n\t\t\tif (IS_VALID_INSTRUMENT(xc->ins)) {\n\t\t\t\tenv = &mod->xxi[xc->ins].aei;\n\t\t\t\tif (env->flg & XMP_ENVELOPE_ON) {\n\t\t\t\t\tenv_on = 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (env_on || (!vol_set && (!ev.ins || !delay_fx))) {\n\t\t\t\tif (sustain_check(env, xc->v_idx)) {\n\t\t\t\t\t/* See OpenMPT EnvOff.xm. In certain\n\t\t\t\t\t * cases a release event is effective\n\t\t\t\t\t * only in the next frame\n\t\t\t\t\t */\n\t\t\t\t\tSET_NOTE(NOTE_SUSEXIT);\n\t\t\t\t} else {\n\t\t\t\t\tSET_NOTE(NOTE_RELEASE);\n\t\t\t\t}\n\t\t\t\tuse_ins_vol = 0;\n\t\t\t} else {\n\t\t\t\tSET_NOTE(NOTE_FADEOUT);\n\t\t\t}\n\n\t\t\t/* See OpenMPT keyoff+instr.xm, pattern 2 row 0x40 */\n\t\t\tif (env_on && ev.fxt == FX_EXTENDED &&\n\t\t\t    (ev.fxp >> 4) == EX_DELAY) {\n\t\t\t\t/* See OpenMPT OffDelay.xm test case */\n\t\t\t\tif ((ev.fxp & 0xf) != 0) {\n\t\t\t\t\tRESET_NOTE(NOTE_RELEASE|NOTE_SUSEXIT);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (key == XMP_KEY_FADE) {\n\t\t\t/* Handle keyoff + instrument case (NoteOff2.xm) */\n\t\t\tSET_NOTE(NOTE_FADEOUT);\n\t\t} else if (is_toneporta) {\n\t\t\t/* set key to 0 so we can have the tone portamento from\n\t\t\t * the original note (see funky_stars.xm pos 5 ch 9)\n\t\t\t */\n\t\t\tkey = 0;\n\n\t\t\t/* And do the same if there's no keyoff (see comic\n\t\t\t * bakery remix.xm pos 1 ch 3)\n\t\t\t */\n\t\t}\n\n\t\tif (ev.ins == 0 && !IS_VALID_INSTRUMENT(xc->old_ins - 1)) {\n\t\t\tnew_invalid_ins = 1;\n\t\t}\n\n\t\tif (new_invalid_ins) {\n\t\t\tlibxmp_virt_resetchannel(ctx, chn);\n\t\t}\n\t}\n\n\n\t/* Check note range -- from the OpenMPT test NoteLimit.xm:\n\t * \"I think one of the first things Fasttracker 2 does when parsing a\n\t *  pattern cell is calculating the “real” note (i.e. pattern note +\n\t *  sample transpose), and if this “real” note falls out of its note\n\t *  range, it is ignored completely (wiped from its internal channel\n\t *  memory). The instrument number next it, however, is not affected\n\t *  and remains in the memory.\"\n\t */\n\tsub = NULL;\n\tif (IS_VALID_NOTE(key - 1)) {\n\t\tint k = key - 1;\n\t\tsub = get_subinstrument(ctx, xc->ins, k);\n\t\tif (!new_invalid_ins && sub != NULL) {\n\t\t\tint transp = mod->xxi[xc->ins].map[k].xpo;\n\t\t\tint k2 = k + sub->xpo + transp;\n\t\t\tif (k2 < 12 || k2 > 130) {\n\t\t\t\tkey = 0;\n\t\t\t\tRESET(NEW_NOTE);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IS_VALID_NOTE(key - 1)) {\n\t\txc->key = --key;\n\t\txc->fadeout = 0x10000;\n\t\tRESET_NOTE(NOTE_END);\n\n\t\tif (sub != NULL) {\n\t\t\tif (~mod->xxi[xc->ins].aei.flg & XMP_ENVELOPE_ON) {\n\t\t\t\tRESET_NOTE(NOTE_RELEASE|NOTE_FADEOUT);\n\t\t\t}\n\t\t}\n\n\t\tif (!new_invalid_ins && sub != NULL) {\n\t\t\tint transp = mod->xxi[xc->ins].map[key].xpo;\n\t\t\tint smp;\n\n\t\t\tnote = key + sub->xpo + transp;\n\t\t\tsmp = sub->sid;\n\n\t\t\tif (!IS_VALID_SAMPLE(smp)) {\n\t\t\t\tsmp = -1;\n\t\t\t}\n\n\t\t\tif (smp >= 0 && smp < mod->smp) {\n\t\t\t\tset_patch(ctx, chn, xc->ins, smp, note);\n\t\t\t\txc->smp = smp;\n\t\t\t}\n\t\t} else {\n\t\t\txc->flags = 0;\n\t\t\tuse_ins_vol = 0;\n\t\t}\n\t}\n\n\tsub = get_subinstrument(ctx, xc->ins, xc->key);\n\n\tset_effect_defaults(ctx, note, sub, xc, is_toneporta);\n\n\tif (ins && sub != NULL && !k00) {\n\t\t/* Reset envelopes on new instrument, see olympic.xm pos 10\n\t\t * But make sure we have an instrument set, see Letting go\n\t\t * pos 4 chn 20\n\t\t */\n\t\treset_envelopes(ctx, xc);\n\t}\n\n\t/* Process new volume */\n\tif (ev.vol) {\n\t\txc->volume = ev.vol - 1;\n\t\tSET(NEW_VOL);\n\t\tif (TEST_NOTE(NOTE_END)) {\t/* m5v-nine.xm */\n\t\t\txc->fadeout = 0x10000;\t/* OpenMPT NoteOff.xm */\n\t\t\tRESET_NOTE(NOTE_RELEASE|NOTE_FADEOUT);\n\t\t}\n\t}\n\n\t/* FT2: always reset sample offset */\n\txc->offset.val = 0;\n\n\t/* Secondary effect handled first */\n\tlibxmp_process_fx(ctx, xc, chn, &ev, 1);\n\tlibxmp_process_fx(ctx, xc, chn, &ev, 0);\n\tset_period_ft2(ctx, note, sub, xc, is_toneporta);\n\n\tif (sub == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (note >= 0) {\n\t\txc->note = note;\n\n\t\t/* From the OpenMPT test cases (3xx-no-old-samp.xm):\n\t\t * \"An offset effect that points beyond the sample end should\n\t\t *  stop playback on this channel.\"\n\t\t *\n\t\t * ... except in Skale Tracker (and possibly others), so make this a\n\t\t *  FastTracker2 quirk. See Armada Tanks game.it (actually an XM).\n\t\t *  Reported by Vladislav Suschikh.\n\t\t */\n\t\tif (HAS_QUIRK(QUIRK_FT2BUGS) && xc->offset.val >= mod->xxs[sub->sid].len) {\n\t\t\tlibxmp_virt_resetchannel(ctx, chn);\n\t\t} else {\n\n\t\t\t/* (From Decibelter - Cosmic 'Wegian Mamas.xm p04 ch7)\n\t\t\t * We retrigger the sample only if we have a new note\n\t\t\t * without tone portamento, otherwise we won't play\n\t\t\t * sweeps and loops correctly.\n\t\t\t */\n\t\t\tlibxmp_virt_voicepos(ctx, chn, xc->offset.val);\n\t\t}\n\t}\n\n\tif (use_ins_vol && !TEST(NEW_VOL)) {\n\t\txc->volume = sub->vol;\n\t}\n\n\treturn 0;\n}\n\nstatic int read_event_st3(struct context_data *ctx, struct xmp_event *e, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tint note;\n\tstruct xmp_subinstrument *sub;\n\tint not_same_ins;\n\tint is_toneporta;\n\tint use_ins_vol;\n\n\txc->flags = 0;\n\tnote = -1;\n\tnot_same_ins = 0;\n\tis_toneporta = 0;\n\tuse_ins_vol = 0;\n\n\tif (IS_TONEPORTA(e->fxt) || IS_TONEPORTA(e->f2t)) {\n\t\tis_toneporta = 1;\n\t}\n\n\tif (libxmp_virt_mapchannel(ctx, chn) < 0 && xc->ins != e->ins - 1) {\n\t\tis_toneporta = 0;\n\t}\n\n\t/* Check instrument */\n\n\tif (e->ins) {\n\t\tint ins = e->ins - 1;\n\t\tSET(NEW_INS);\n\t\tuse_ins_vol = 1;\n\t\txc->fadeout = 0x10000;\n\t\txc->per_flags = 0;\n\t\txc->offset.val = 0;\n\t\tRESET_NOTE(NOTE_RELEASE|NOTE_FADEOUT);\n\n\t\tif (IS_VALID_INSTRUMENT(ins)) {\n\t\t\t/* valid ins */\n\t\t\tif (xc->ins != ins) {\n\t\t\t\tnot_same_ins = 1;\n\t\t\t\tif (!is_toneporta) {\n\t\t\t\t\txc->ins = ins;\n\t\t\t\t\txc->ins_fade = mod->xxi[ins].rls;\n\t\t\t\t} else {\n\t\t\t\t\t/* Get new instrument volume */\n\t\t\t\t\tsub = get_subinstrument(ctx, ins, e->note - 1);\n\t\t\t\t\tif (sub != NULL) {\n\t\t\t\t\t\txc->volume = sub->vol;\n\t\t\t\t\t\tuse_ins_vol = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t/* invalid ins */\n\n\t\t\t/* Ignore invalid instruments */\n\t\t\txc->flags = 0;\n\t\t\tuse_ins_vol = 0;\n\t\t}\n\t}\n\n\t/* Check note */\n\n\tif (e->note) {\n\t\tSET(NEW_NOTE);\n\n\t\tif (e->note == XMP_KEY_OFF) {\n\t\t\tSET_NOTE(NOTE_RELEASE);\n\t\t\tuse_ins_vol = 0;\n\t\t} else if (is_toneporta) {\n\t\t\t/* Always retrig in tone portamento: Fix portamento in\n\t\t\t * 7spirits.s3m, mod.Biomechanoid\n\t\t\t */\n\t\t\tif (not_same_ins) {\n\t\t\t\txc->offset.val = 0;\n\t\t\t}\n\t\t} else if (IS_VALID_NOTE(e->note - 1)) {\n\t\t\txc->key = e->note - 1;\n\t\t\tRESET_NOTE(NOTE_END);\n\n\t\t\tsub = get_subinstrument(ctx, xc->ins, xc->key);\n\n\t\t\tif (sub != NULL) {\n\t\t\t\tint transp = mod->xxi[xc->ins].map[xc->key].xpo;\n\t\t\t\tint smp;\n\n\t\t\t\tnote = xc->key + sub->xpo + transp;\n\t\t\t\tsmp = sub->sid;\n\n\t\t\t\tif (!IS_VALID_SAMPLE(smp)) {\n\t\t\t\t\tsmp = -1;\n\t\t\t\t}\n\n\t\t\t\tif (smp >= 0 && smp < mod->smp) {\n\t\t\t\t\tset_patch(ctx, chn, xc->ins, smp, note);\n\t\t\t\t\txc->smp = smp;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\txc->flags = 0;\n\t\t\t\tuse_ins_vol = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tsub = get_subinstrument(ctx, xc->ins, xc->key);\n\n\tset_effect_defaults(ctx, note, sub, xc, is_toneporta);\n\tif (e->ins && sub != NULL) {\n\t\treset_envelopes(ctx, xc);\n\t}\n\n\t/* Process new volume */\n\tif (e->vol) {\n\t\txc->volume = e->vol - 1;\n\t\tSET(NEW_VOL);\n\t}\n\n\t/* Secondary effect handled first */\n\tlibxmp_process_fx(ctx, xc, chn, e, 1);\n\tlibxmp_process_fx(ctx, xc, chn, e, 0);\n\tset_period(ctx, note, sub, xc, is_toneporta);\n\n\tif (sub == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (note >= 0) {\n\t\txc->note = note;\n\t\tlibxmp_virt_voicepos(ctx, chn, xc->offset.val);\n\t}\n\n\tif (use_ins_vol && !TEST(NEW_VOL)) {\n\t\txc->volume = sub->vol;\n\t}\n\n\tif (HAS_QUIRK(QUIRK_ST3BUGS) && TEST(NEW_VOL)) {\n\t\txc->volume = xc->volume * p->gvol / m->volbase;\n\t}\n\n\treturn 0;\n}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\nstatic inline void copy_channel(struct player_data *p, int to, int from)\n{\n\tif (to > 0 && to != from) {\n\t\tmemcpy(&p->xc_data[to], &p->xc_data[from],\n\t\t\t\t\tsizeof (struct channel_data));\n\t}\n}\n\nstatic inline int has_note_event(struct xmp_event *e)\n{\n\treturn (e->note && e->note <= XMP_MAX_KEYS);\n}\n\nstatic int check_fadeout(struct context_data *ctx, struct channel_data *xc, int ins)\n{\n\tstruct xmp_instrument *xxi = libxmp_get_instrument(ctx, ins);\n\n\tif (xxi == NULL) {\n\t\treturn 1;\n\t}\n\n\treturn (~xxi->aei.flg & XMP_ENVELOPE_ON ||\n\t\t~xxi->aei.flg & XMP_ENVELOPE_CARRY ||\n\t\txc->ins_fade == 0 ||\n\t\txc->fadeout <= xc->ins_fade);\n}\n\nstatic int check_invalid_sample(struct context_data *ctx, int ins, int key)\n{\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\n\tif (ins < mod->ins) {\n\t\tint smp = mod->xxi[ins].map[key].ins;\n\t\tif (smp == 0xff || smp >= mod->smp) {\n\t\t\treturn 1;\n\t\t};\n\t}\n\n\treturn 0;\n}\n\nstatic void fix_period(struct context_data *ctx, int chn, struct xmp_subinstrument *sub)\n{\n\tif (sub->nna == XMP_INST_NNA_CONT) {\n\t\tstruct player_data *p = &ctx->p;\n\t\tstruct channel_data *xc = &p->xc_data[chn];\n\t\tstruct xmp_instrument *xxi = libxmp_get_instrument(ctx, xc->ins);\n\n\t\txc->period = libxmp_note_to_period(ctx, xc->key + sub->xpo +\n\t\t\txxi->map[xc->key_porta].xpo, xc->finetune, xc->per_adj);\n\t}\n}\n\nstatic int is_same_sid(struct context_data *ctx, int chn, int ins, int key)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tstruct xmp_subinstrument *s1, *s2;\n\n\ts1 = get_subinstrument(ctx, ins, key);\n\ts2 = get_subinstrument(ctx, xc->ins, xc->key);\n\n\treturn (s1 && s2 && s1->sid == s2->sid);\n}\n\nstatic int read_event_it(struct context_data *ctx, struct xmp_event *e, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tint note, key;\n\tstruct xmp_subinstrument *sub;\n\tint not_same_ins, not_same_smp;\n\tint new_invalid_ins;\n\tint is_toneporta, is_release;\n\tint candidate_ins;\n\tint reset_env;\n\tint reset_susloop;\n\tint use_ins_vol;\n\tint sample_mode;\n\tint toneporta_offset;\n\tint retrig_ins;\n\tstruct xmp_event ev;\n\n\tmemcpy(&ev, e, sizeof (struct xmp_event));\n\n\t/* Emulate Impulse Tracker \"always read instrument\" bug */\n\tif (ev.ins) {\n\t\txc->delayed_ins = 0;\n\t} else if (ev.note && xc->delayed_ins) {\n\t\tev.ins = xc->delayed_ins;\n\t\txc->delayed_ins = 0;\n\t}\n\n\txc->flags = 0;\n\tnote = -1;\n\tkey = ev.note;\n\tnot_same_ins = 0;\n\tnot_same_smp = 0;\n\tnew_invalid_ins = 0;\n\tis_toneporta = 0;\n\tis_release = 0;\n\treset_env = 0;\n\treset_susloop = 0;\n\tuse_ins_vol = 0;\n\tcandidate_ins = xc->ins;\n\tsample_mode = !HAS_QUIRK(QUIRK_VIRTUAL);\n\ttoneporta_offset = 0;\n\tretrig_ins = 0;\n\n\t/* Keyoff + instrument retrigs current instrument in old fx mode */\n\tif (HAS_QUIRK(QUIRK_ITOLDFX)) {\n\t\tif (ev.note == XMP_KEY_OFF && IS_VALID_INSTRUMENT(ev.ins -1)) {\n\t\t\tretrig_ins = 1;\n\t\t}\n\t}\n\n\t/* Notes with unmapped instruments are ignored */\n\tif (ev.ins) {\n\t\tif (ev.ins <= mod->ins && has_note_event(&ev)) {\n\t\t\tint ins = ev.ins - 1;\n\t\t\tif (check_invalid_sample(ctx, ins, ev.note - 1)) {\n\t\t\t\tcandidate_ins = ins;\n\t\t\t\tmemset(&ev, 0, sizeof (ev));\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif (has_note_event(&ev)) {\n\t\t\tint ins = xc->old_ins - 1;\n\t\t\tif (!IS_VALID_INSTRUMENT(ins)) {\n\t\t\t\tnew_invalid_ins = 1;\n\t\t\t} else if (check_invalid_sample(ctx, ins, ev.note - 1)) {\n\t\t\t\tmemset(&ev, 0, sizeof (ev));\n\t\t\t}\n\t\t}\n\t}\n\n\tif (IS_TONEPORTA(ev.fxt) || IS_TONEPORTA(ev.f2t)) {\n\t\tis_toneporta = 1;\n\t}\n\n\tif (TEST_NOTE(NOTE_ENV_RELEASE | NOTE_FADEOUT)) {\n\t\tis_release = 1;\n\t}\n\n\tif (xc->period <= 0 || TEST_NOTE(NOTE_END)) {\n\t\tis_toneporta = 0;\n\t}\n\n\t/* Off-Porta.it */\n\tif (is_toneporta && ev.fxt == FX_OFFSET) {\n\t\ttoneporta_offset = 1;\n \t\tif (!HAS_QUIRK(QUIRK_PRENV)) {\n\t\t\tRESET_NOTE(NOTE_ENV_END);\n\t\t}\n\t}\n\n\t/* Check instrument */\n\n\tif (ev.ins) {\n\t\tint ins = ev.ins - 1;\n\t\tint set_new_ins = 1;\n\n\t\t/* portamento_after_keyoff.it test case */\n\t\tif (is_release && !key) {\n\t\t\tif (is_toneporta) {\n\t\t\t\tif (HAS_QUIRK(QUIRK_PRENV) || TEST_NOTE(NOTE_SET)) {\n\t\t\t\t\tis_toneporta = 0;\n\t\t\t\t\treset_envelopes_carry(ctx, xc);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t/* fixes OpenMPT wnoteoff.it */\n\t\t\t\treset_envelopes_carry(ctx, xc);\n\t\t\t}\n\t\t}\n\n\t\tif (is_toneporta && xc->ins == ins) {\n\t\t\tif (!HAS_QUIRK(QUIRK_PRENV)) {\n\t\t\t\tif (is_same_sid(ctx, chn, ins, key - 1)) {\n\t\t\t\t\t/* same instrument and same sample */\n\t\t\t\t\tset_new_ins = !is_release;\n\t\t\t\t} else {\n\t\t\t\t\t/* same instrument, different sample */\n\t\t\t\t\tnot_same_ins = 1; /* need this too */\n\t\t\t\t\tnot_same_smp = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (set_new_ins) {\n\t\t\tSET(NEW_INS);\n\t\t\treset_env = 1;\n\t\t}\n\t\t/* Sample default volume is always enabled if a valid sample\n\t\t * is provided (Atomic Playboy, default_volume.it). */\n\t\tuse_ins_vol = 1;\n\t\txc->per_flags = 0;\n\n\t\tif (IS_VALID_INSTRUMENT(ins)) {\n\t\t\t/* valid ins */\n\n\t\t\t/* See OpenMPT StoppedInstrSwap.it for cut case */\n\t\t\tif (!key && !TEST_NOTE(NOTE_KEY_CUT)) {\n\t\t\t\t/* Retrig in new ins in sample mode */\n\t\t\t\tif (sample_mode && TEST_NOTE(NOTE_END)) {\n\t\t\t\t\tlibxmp_virt_voicepos(ctx, chn, 0);\n\t\t\t\t}\n\n\t\t\t\t/* IT: Reset note for every new != ins */\n\t\t\t\tif (xc->ins == ins) {\n\t\t\t\t\tSET(NEW_INS);\n\t\t\t\t\tuse_ins_vol = 1;\n\t\t\t\t} else {\n\t\t\t\t\tkey = xc->key + 1;\n\t\t\t\t}\n\n\t\t\t\tRESET_NOTE(NOTE_SET);\n\t\t\t}\n\n\t\t\tif (xc->ins != ins && (!is_toneporta || !HAS_QUIRK(QUIRK_PRENV))) {\n\t\t\t\tcandidate_ins = ins;\n\n\t\t\t\tif (!is_same_sid(ctx, chn, ins, key - 1)) {\n\t\t\t\t\tnot_same_ins = 1;\n\t\t\t\t\tif (is_toneporta) {\n\t\t\t\t\t\t/* Get new instrument volume */\n\t\t\t\t\t\tsub = get_subinstrument(ctx, ins, key);\n\t\t\t\t\t\tif (sub != NULL) {\n\t\t\t\t\t\t\txc->volume = sub->vol;\n\t\t\t\t\t\t\tuse_ins_vol = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t/* In sample mode invalid instruments cut the current\n\t\t\t * note (OpenMPT SampleNumberChange.it).\n\t\t\t * TODO: portamento_sustain.it order 3 row 19: when\n\t\t\t * sample release is set, this isn't always done? */\n\t\t\tif (sample_mode) {\n\t\t\t\txc->volume = 0;\n\t\t\t}\n\n\t\t\t/* Ignore invalid instruments */\n\t\t\tnew_invalid_ins = 1;\n\t\t\txc->flags = 0;\n\t\t\tuse_ins_vol = 0;\n\t\t}\n\t}\n\n\t/* Check note */\n\n\tif (key) {\n\t\tSET(NEW_NOTE);\n\t\tSET_NOTE(NOTE_SET);\n\n\t\tif (key == XMP_KEY_FADE) {\n\t\t\tSET_NOTE(NOTE_FADEOUT);\n\t\t\treset_env = 0;\n\t\t\treset_susloop = 0;\n\t\t\tuse_ins_vol = 0;\n\t\t} else if (key == XMP_KEY_CUT) {\n\t\t\tSET_NOTE(NOTE_END | NOTE_CUT | NOTE_KEY_CUT);\n\t\t\txc->period = 0;\n\t\t\tlibxmp_virt_resetchannel(ctx, chn);\n\t\t} else if (key == XMP_KEY_OFF) {\n\t\t\tstruct xmp_envelope *env = NULL;\n\t\t\tif (IS_VALID_INSTRUMENT(xc->ins)) {\n\t\t\t\tenv = &mod->xxi[xc->ins].aei;\n\t\t\t}\n\t\t\tif (sustain_check(env, xc->v_idx)) {\n\t\t\t\tSET_NOTE(NOTE_SUSEXIT);\n\t\t\t} else {\n\t\t\t\tSET_NOTE(NOTE_RELEASE);\n\t\t\t}\n\t\t\tSET(KEY_OFF);\n\t\t\t/* Use instrument volume if an instrument was explicitly\n\t\t\t * provided on this row (see OpenMPT NoteOffInstr.it row 4).\n\t\t\t * However, never reset the envelope (see OpenMPT wnoteoff.it).\n\t\t\t */\n\t\t\treset_env = 0;\n\t\t\treset_susloop = 0;\n\t\t\tif (!ev.ins) {\n\t\t\t\tuse_ins_vol = 0;\n\t\t\t}\n\t\t} else if (!new_invalid_ins) {\n\t\t\t/* Sample sustain release should always carry for tone\n\t\t\t * portamento, and is not reset unless a note is\n\t\t\t * present (Atomic Playboy, portamento_sustain.it). */\n\t\t\t/* portamento_after_keyoff.it test case */\n\t\t\t/* also see suburban_streets o13 c45 */\n\t\t\tif (!is_toneporta) {\n\t\t\t\treset_env = 1;\n\t\t\t\treset_susloop = 1;\n\t\t\t}\n\n\t\t\tif (is_toneporta) {\n\t\t\t\tif (not_same_ins || TEST_NOTE(NOTE_END)) {\n\t\t\t\t\tSET(NEW_INS);\n\t\t\t\t\tRESET_NOTE(NOTE_ENV_RELEASE|NOTE_SUSEXIT|NOTE_FADEOUT);\n\t\t\t\t} else {\n\t\t\t\t\tif (IS_VALID_NOTE(key - 1)) {\n\t\t\t\t\t\txc->key_porta = key - 1;\n\t\t\t\t\t}\n\t\t\t\t\tkey = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/* TODO: instrument change+porta(+release?) doesn't require a key.\n\t * Order 3/row 11 of portamento_sustain.it should change the sample. */\n\tif (IS_VALID_NOTE(key - 1) && !new_invalid_ins) {\n\t\tif (TEST_NOTE(NOTE_CUT)) {\n\t\t\tuse_ins_vol = 1;\t/* See OpenMPT NoteOffInstr.it */\n\t\t}\n\t\txc->key = --key;\n\t\tRESET_NOTE(NOTE_END);\n\n\t\tsub = get_subinstrument(ctx, candidate_ins, key);\n\n\t\tif (sub != NULL) {\n\t\t\tint transp = mod->xxi[candidate_ins].map[key].xpo;\n\t\t\tint smp, to;\n\t\t\tint dct;\n\t\t\tint rvv;\n\n\t\t\t/* Clear note delay before duplicating channels:\n\t\t\t * it_note_delay_nna.it */\n\t\t\txc->delay = 0;\n\n\t\t\tnote = key + sub->xpo + transp;\n\t\t\tsmp = sub->sid;\n\t\t\tif (!IS_VALID_SAMPLE(smp)) {\n\t\t\t\tsmp = -1;\n\t\t\t}\n\t\t\tdct = sub->dct;\n\n\t\t\tif (not_same_smp) {\n\t\t\t\tfix_period(ctx, chn, sub);\n\t\t\t\t/* Toneporta, even when not executed, disables\n\t\t\t\t * NNA and DCAs for the current note:\n\t\t\t\t * portamento_nna_sample.it, gxsmp2.it */\n\t\t\t\tlibxmp_virt_setnna(ctx, chn, XMP_INST_NNA_CUT);\n\t\t\t\tdct = XMP_INST_DCT_OFF;\n\t\t\t}\n\t\t\tto = libxmp_virt_setpatch(ctx, chn, candidate_ins, smp,\n\t\t\t\tnote, key, sub->nna, dct, sub->dca);\n\n\t\t\t/* Random value for volume swing */\n\t\t\trvv = sub->rvv & 0xff;\n\t\t\tif (rvv) {\n\t\t\t\tCLAMP(rvv, 0, 100);\n\t\t\t\txc->rvv = libxmp_get_random(&ctx->rng, rvv + 1);\n\t\t\t} else {\n\t\t\t\txc->rvv = 0;\n\t\t\t}\n\n\t\t\t/* Random value for pan swing */\n\t\t\trvv = (sub->rvv & 0xff00) >> 8;\n\t\t\tif (rvv) {\n\t\t\t\tCLAMP(rvv, 0, 64);\n\t\t\t\txc->rpv = libxmp_get_random(&ctx->rng, rvv + 1) - (rvv / 2);\n\t\t\t} else {\n\t\t\t\txc->rpv = 0;\n\t\t\t}\n\n\t\t\tif (to < 0)\n\t\t\t\treturn -1;\n\t\t\tif (to != chn) {\n\t\t\t\tcopy_channel(p, to, chn);\n\t\t\t\tp->xc_data[to].flags = 0;\n\t\t\t}\n\n\t\t\tif (smp >= 0) {\t\t/* Not sure if needed */\n\t\t\t\txc->smp = smp;\n\t\t\t}\n\t\t} else {\n\t\t\txc->flags = 0;\n\t\t\tuse_ins_vol = 0;\n\t\t}\n\t}\n\n\t/* Do after virtual channel copy */\n\tif (is_toneporta || retrig_ins) {\n\t\tif (HAS_QUIRK(QUIRK_PRENV) && ev.ins) {\n\t\t\treset_envelopes_carry(ctx, xc);\n\t\t}\n\t}\n\n\tif (IS_VALID_INSTRUMENT(candidate_ins)) {\n\t\tif (xc->ins != candidate_ins) {\n\t\t\t/* Reset envelopes if instrument changes */\n\t\t\treset_envelopes(ctx, xc);\n\t\t}\n\t\txc->ins = candidate_ins;\n\t\txc->ins_fade = mod->xxi[candidate_ins].rls;\n\t}\n\n\t/* Reset in case of new instrument and the previous envelope has\n\t * finished (OpenMPT test EnvReset.it). This must take place after\n\t * channel copies in case of NNA (see test/test.it)\n\t * Also if we have envelope in carry mode, check fadeout\n\t * Also, only reset the volume envelope. (it_fade_env_reset_carry.it)\n\t */\n\tif (ev.ins && TEST_NOTE(NOTE_ENV_END)) {\n\t\tif (check_fadeout(ctx, xc, candidate_ins)) {\n\t\t\treset_envelope_volume(ctx, xc);\n\t\t} else {\n\t\t\treset_env = 0;\n\t\t}\n\t}\n\n\tif (reset_env) {\n\t\tif (ev.note) {\n\t\t\tRESET_NOTE(NOTE_ENV_RELEASE|NOTE_SUSEXIT|NOTE_FADEOUT);\n\t\t}\n\t\t/* Set after copying to new virtual channel (see ambio.it) */\n\t\txc->fadeout = 0x10000;\n\t}\n\tif (reset_susloop && ev.note) {\n\t\tRESET_NOTE(NOTE_SAMPLE_RELEASE);\n\t}\n\n\t/* See OpenMPT wnoteoff.it vs noteoff3.it */\n\tif (retrig_ins && not_same_ins) {\n\t\tSET(NEW_INS);\n\t\tlibxmp_virt_voicepos(ctx, chn, 0);\n\t\txc->fadeout = 0x10000;\n\t\tRESET_NOTE(NOTE_RELEASE|NOTE_SUSEXIT|NOTE_FADEOUT);\n\t}\n\n\tsub = get_subinstrument(ctx, xc->ins, xc->key);\n\n\tset_effect_defaults(ctx, note, sub, xc, is_toneporta);\n\tif (sub != NULL) {\n\t\tif (note >= 0) {\n\t\t\t/* Reset pan, see OpenMPT PanReset.it */\n\t\t\tif (sub->pan >= 0) {\n\t\t\t\txc->pan.val = sub->pan;\n\t\t\t\txc->pan.surround = 0;\n\t\t\t}\n\n\t\t\tif (TEST_NOTE(NOTE_CUT)) {\n\t\t\t\treset_envelopes(ctx, xc);\n\t\t\t} else if (!toneporta_offset || HAS_QUIRK(QUIRK_PRENV)) {\n\t\t\t\treset_envelopes_carry(ctx, xc);\n\t\t\t}\n\t\t\tRESET_NOTE(NOTE_CUT);\n\t\t}\n\t}\n\n\t/* Process new volume */\n\tif (ev.vol && (!TEST_NOTE(NOTE_CUT) || ev.ins != 0)) {\n\t\t/* Do this even for XMP_KEY_OFF (see OpenMPT NoteOffInstr.it row 4). */\n\t\txc->volume = ev.vol - 1;\n\t\tSET(NEW_VOL);\n\t}\n\n\t/* IT: always reset sample offset */\n\txc->offset.val &= ~0xffff;\n\n\t/* According to Storlek test 25, Impulse Tracker handles the volume\n\t * column effects after the standard effects.\n\t */\n\tlibxmp_process_fx(ctx, xc, chn, &ev, 0);\n\tlibxmp_process_fx(ctx, xc, chn, &ev, 1);\n\tset_period(ctx, note, sub, xc, is_toneporta);\n\n\tif (sub == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (note >= 0) {\n\t\txc->note = note;\n\t}\n\tif (note >= 0 || toneporta_offset) {\n\t\tlibxmp_virt_voicepos(ctx, chn, xc->offset.val);\n\t}\n\n\tif (use_ins_vol && !TEST(NEW_VOL)) {\n\t\txc->volume = sub->vol;\n\t}\n\n\treturn 0;\n}\n\n#endif\n\n#ifndef LIBXMP_CORE_PLAYER\n\nstatic int read_event_med(struct context_data *ctx, struct xmp_event *e, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tint note;\n\tstruct xmp_subinstrument *sub;\n\tint new_invalid_ins = 0;\n\tint is_toneporta;\n\tint use_ins_vol;\n\tint finetune;\n\n\txc->flags = 0;\n\tnote = -1;\n\tis_toneporta = 0;\n\tuse_ins_vol = 0;\n\n\tif (e->fxt == FX_TONEPORTA || e->fxt == FX_TONE_VSLIDE) {\n\t\tis_toneporta = 1;\n\t}\n\n\t/* Check instrument */\n\n\tif (e->ins && e->note) {\n\t\tint ins = e->ins - 1;\n\t\tuse_ins_vol = 1;\n\t\tSET(NEW_INS);\n\t\txc->fadeout = 0x10000;\n\t\txc->offset.val = 0;\n\t\tRESET_NOTE(NOTE_RELEASE|NOTE_FADEOUT);\n\n\t\tif (IS_VALID_INSTRUMENT(ins)) {\n\t\t\tif (is_toneporta) {\n\t\t\t\t/* Get new instrument volume */\n\t\t\t\tsub = get_subinstrument(ctx, ins, e->note - 1);\n\t\t\t\tif (sub != NULL) {\n\t\t\t\t\txc->volume = sub->vol;\n\t\t\t\t\tuse_ins_vol = 0;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\txc->ins = ins;\n\t\t\t\txc->ins_fade = mod->xxi[ins].rls;\n\t\t\t}\n\t\t} else {\n\t\t\tnew_invalid_ins = 1;\n\t\t\tlibxmp_virt_resetchannel(ctx, chn);\n\t\t}\n\n\t\tMED_CHANNEL_EXTRAS(*xc)->arp = 0;\n\t\tMED_CHANNEL_EXTRAS(*xc)->aidx = 0;\n\t} else {\n\t\t/* Hold */\n\t\tif (e->ins && !e->note) {\n\t\t\tuse_ins_vol = 1;\n\t\t}\n\t}\n\n\t/* Check note */\n\n\tif (e->note) {\n\t\tSET(NEW_NOTE);\n\n\t\tif (e->note == XMP_KEY_OFF) {\n\t\t\tSET_NOTE(NOTE_RELEASE);\n\t\t\tuse_ins_vol = 0;\n\t\t} else if (e->note == XMP_KEY_CUT) {\n\t\t\tSET_NOTE(NOTE_END);\n\t\t\txc->period = 0;\n\t\t\tlibxmp_virt_resetchannel(ctx, chn);\n\t\t} else if (!is_toneporta && IS_VALID_INSTRUMENT(xc->ins) && IS_VALID_NOTE(e->note - 1)) {\n\t\t\tstruct xmp_instrument *xxi = &mod->xxi[xc->ins];\n\n\t\t\txc->key = e->note - 1;\n\t\t\tRESET_NOTE(NOTE_END);\n\n\t\t\txc->per_adj = 0.0;\n\t\t\tif (xxi->nsm > 1 && HAS_MED_INSTRUMENT_EXTRAS(*xxi)) {\n\t\t\t\t/* synth or iffoct */\n\t\t\t\tif (MED_INSTRUMENT_EXTRAS(*xxi)->vts == 0 &&\n\t\t\t\t    MED_INSTRUMENT_EXTRAS(*xxi)->wts == 0) {\n\t\t\t\t\t/* iffoct */\n\t\t\t\t\txc->per_adj = 2.0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsub = get_subinstrument(ctx, xc->ins, xc->key);\n\n\t\t\tif (!new_invalid_ins && sub != NULL) {\n\t\t\t\tint transp = xxi->map[xc->key].xpo;\n\t\t\t\tint smp;\n\n\t\t\t\tnote = xc->key + sub->xpo + transp;\n\t\t\t\tsmp = sub->sid;\n\n\t\t\t\tif (!IS_VALID_SAMPLE(smp)) {\n\t\t\t\t\tsmp = -1;\n\t\t\t\t}\n\n\t\t\t\tif (smp >= 0 && smp < mod->smp) {\n\t\t\t\t\tset_patch(ctx, chn, xc->ins, smp, note);\n\t\t\t\t\txc->smp = smp;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\txc->flags = 0;\n\t\t\t\tuse_ins_vol = 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tsub = get_subinstrument(ctx, xc->ins, xc->key);\n\n\t/* Keep effect-set finetune if no instrument set */\n\tfinetune = xc->finetune;\n\tset_effect_defaults(ctx, note, sub, xc, is_toneporta);\n\tif (!e->ins) {\n\t\txc->finetune = finetune;\n\t}\n\n\tif (e->ins && sub != NULL) {\n\t\treset_envelopes(ctx, xc);\n\t}\n\n\t/* Process new volume */\n\tif (e->vol) {\n\t\txc->volume = e->vol - 1;\n\t\tSET(NEW_VOL);\n\t}\n\n\t/* Secondary effect handled first */\n\tlibxmp_process_fx(ctx, xc, chn, e, 1);\n\tlibxmp_process_fx(ctx, xc, chn, e, 0);\n\tset_period(ctx, note, sub, xc, is_toneporta);\n\n\tif (sub == NULL) {\n\t\treturn 0;\n\t}\n\n\tif (note >= 0) {\n\t\txc->note = note;\n\t\tlibxmp_virt_voicepos(ctx, chn, xc->offset.val);\n\t}\n\n\tif (use_ins_vol && !TEST(NEW_VOL)) {\n\t\txc->volume = sub->vol;\n\t}\n\n\treturn 0;\n}\n\n#endif\n\nstatic int read_event_smix(struct context_data *ctx, struct xmp_event *e, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct smix_data *smix = &ctx->smix;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\tstruct xmp_subinstrument *sub;\n\tstruct xmp_instrument *xxi;\n\tint ins, note, transp, smp;\n\n\txc->flags = 0;\n\n\tif (!e->ins)\n\t\treturn 0;\n\n\tins = e->ins - 1;\n\tSET(NEW_INS);\n\txc->per_flags = 0;\n\txc->offset.val = 0;\n\tRESET_NOTE(NOTE_RELEASE|NOTE_FADEOUT);\n\n\txxi = libxmp_get_instrument(ctx, ins);\n\tif (xxi != NULL) {\n\t\txc->ins_fade = xxi->rls;\n\t}\n\txc->ins = ins;\n\n\tSET(NEW_NOTE);\n\n\tif (e->note == XMP_KEY_OFF) {\n\t\tSET_NOTE(NOTE_RELEASE);\n\t\treturn 0;\n\t} else if (e->note == XMP_KEY_FADE) {\n\t\tSET_NOTE(NOTE_FADEOUT);\n\t\treturn 0;\n\t} else if (e->note == XMP_KEY_CUT) {\n\t\tSET_NOTE(NOTE_END);\n\t\txc->period = 0;\n\t\tlibxmp_virt_resetchannel(ctx, chn);\n\t\treturn 0;\n\t}\n\n\txc->key = e->note - 1;\n\txc->fadeout = 0x10000;\n\tRESET_NOTE(NOTE_END);\n\n\tif (ins >= mod->ins && ins < mod->ins + smix->ins) {\n\t\tsub = &xxi->sub[0];\n\t\tif (sub == NULL) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tnote = xc->key + sub->xpo;\n\t\tsmp = sub->sid;\n\t\tif (smix->xxs[smp].len == 0)\n\t\t\tsmp = -1;\n\t\tif (smp >= 0 && smp < smix->smp) {\n\t\t\tsmp += mod->smp;\n\t\t\tset_patch(ctx, chn, xc->ins, smp, note);\n\t\t\txc->smp = smp;\n\t\t}\n\t} else {\n\t\tsub = IS_VALID_NOTE(xc->key) ?\n\t\t\tget_subinstrument(ctx, xc->ins, xc->key) : NULL;\n\t\tif (sub == NULL) {\n\t\t\treturn 0;\n\t\t}\n\t\ttransp = xxi->map[xc->key].xpo;\n\t\tnote = xc->key + sub->xpo + transp;\n\t\tsmp = sub->sid;\n\t\tif (!IS_VALID_SAMPLE(smp))\n\t\t\tsmp = -1;\n\t\tif (smp >= 0 && smp < mod->smp) {\n\t\t\tset_patch(ctx, chn, xc->ins, smp, note);\n\t\t\txc->smp = smp;\n\t\t}\n\t}\n\n\tset_effect_defaults(ctx, note, sub, xc, 0);\n\tset_period(ctx, note, sub, xc, 0);\n\n\tif (e->ins) {\n\t\treset_envelopes(ctx, xc);\n\t}\n\n\txc->volume = e->vol - 1;\n\n\txc->note = note;\n\tlibxmp_virt_voicepos(ctx, chn, xc->offset.val);\n\n\treturn 0;\n}\n\nint libxmp_read_event(struct context_data *ctx, struct xmp_event *e, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct channel_data *xc = &p->xc_data[chn];\n\n\tif (e->ins != 0)\n\t\txc->old_ins = e->ins;\n\n\tif (TEST_NOTE(NOTE_SAMPLE_END)) {\n\t\tSET_NOTE(NOTE_END);\n\t}\n\n\tif (chn >= m->mod.chn) {\n\t\treturn read_event_smix(ctx, e, chn);\n\t} else switch (m->read_event_type) {\n\tcase READ_EVENT_MOD:\n\t\treturn read_event_mod(ctx, e, chn);\n\tcase READ_EVENT_FT2:\n\t\treturn read_event_ft2(ctx, e, chn);\n\tcase READ_EVENT_ST3:\n\t\treturn read_event_st3(ctx, e, chn);\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tcase READ_EVENT_IT:\n\t\treturn read_event_it(ctx, e, chn);\n#endif\n#ifndef LIBXMP_CORE_PLAYER\n\tcase READ_EVENT_MED:\n\t\treturn read_event_med(ctx, e, chn);\n#endif\n\tdefault:\n\t\treturn read_event_mod(ctx, e, chn);\n\t}\n}\n"
  },
  {
    "path": "contrib/libxmp/src/rng.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n#include \"rng.h\"\n\n#include <time.h>\n\nstatic unsigned libxmp_random_step_xorshift32(unsigned state)\n{\n\tif (state == 0) state = 1;\n\tstate ^= state << 13u;\n\tstate ^= state >> 17u;\n\treturn   state <<  5u;\n}\n\nunsigned libxmp_get_random(struct rng_state *rng, unsigned range)\n{\n\tunsigned state = libxmp_random_step_xorshift32(rng->state);\n\trng->state = state;\n\n\treturn (uint64)range * state >> 32u;\n}\n\nvoid libxmp_set_random(struct rng_state *rng, unsigned state)\n{\n\trng->state = state;\n}\n\nvoid libxmp_init_random(struct rng_state *rng)\n{\n\trng->state = (unsigned) time(NULL);\n\tlibxmp_get_random(rng, 0);\n\tlibxmp_get_random(rng, 0);\n\tlibxmp_get_random(rng, 0);\n}\n"
  },
  {
    "path": "contrib/libxmp/src/rng.h",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#ifndef LIBXMP_RNG_H\n#define LIBXMP_RNG_H\n\n#include \"common.h\"\n\nLIBXMP_BEGIN_DECLS\n\n/* Returns a pseudo-random unsigned integer between 0 and (range - 1) and\n * steps the player's internal random state. If range = 0, returns 0. */\nunsigned libxmp_get_random\t(struct rng_state *, unsigned range);\nvoid\t libxmp_set_random\t(struct rng_state *, unsigned seed);\nvoid\t libxmp_init_random\t(struct rng_state *);\n\nLIBXMP_END_DECLS\n\n#endif /* LIBXMP_RNG_H */\n"
  },
  {
    "path": "contrib/libxmp/src/scan.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2025 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/*\n * Sun, 31 May 1998 17:50:02 -0600\n * Reported by ToyKeeper <scriven@CS.ColoState.EDU>:\n * For loop-prevention, I know a way to do it which lets most songs play\n * fine once through even if they have backward-jumps. Just keep a small\n * array (256 bytes, or even bits) of flags, each entry determining if a\n * pattern in the song order has been played. If you get to an entry which\n * is already non-zero, skip to the next song (assuming looping is off).\n */\n\n/*\n * Tue, 6 Oct 1998 21:23:17 +0200 (CEST)\n * Reported by John v/d Kamp <blade_@dds.nl>:\n * scan.c was hanging when it jumps to an invalid restart value.\n * (Fixed by hipolito)\n */\n\n\n#include \"common.h\"\n#include \"effects.h\"\n#include \"player.h\"\n#include \"mixer.h\"\n\n#ifndef LIBXMP_CORE_PLAYER\n#include \"far_extras.h\"\n#endif\n\n#define VBLANK_TIME_THRESHOLD\t480000 /* 8 minutes */\n\n\nstatic int scan_module(struct context_data *ctx, int ep, int chain)\n{\n    struct player_data *p = &ctx->p;\n    struct module_data *m = &ctx->m;\n    const struct xmp_module *mod = &m->mod;\n    const struct xmp_track *tracks[XMP_MAX_CHANNELS];\n    const struct xmp_event *event;\n    int parm, gvol_memory, f1, f2, p1, p2, ord, ord2;\n    int row, last_row, break_row, row_count, row_count_total;\n    int orders_since_last_valid, any_valid;\n    int gvl, bpm, speed, base_time, chn;\n    int frame_count;\n    double time, start_time, time_calc;\n    int inside_loop, line_jump;\n    int pdelay = 0;\n    struct flow_control f;\n    struct pattern_loop loop[XMP_MAX_CHANNELS];\n    int i, pat;\n    int has_marker;\n    struct ord_data *info;\n#ifndef LIBXMP_CORE_PLAYER\n    int st26_speed;\n    int far_tempo_coarse, far_tempo_fine, far_tempo_mode;\n#endif\n    /* was 255, but Global trash goes to 318.\n     * Higher limit for MEDs, defiance.crybaby.5 has blocks with 2048+ rows. */\n    const int row_limit = IS_PLAYER_MODE_MED() ? 3200 : 512;\n\n    if (mod->len == 0)\n\treturn 0;\n\n    for (i = 0; i < mod->len; i++) {\n\tpat = mod->xxo[i];\n\tmemset(m->scan_cnt[i], 0, pat >= mod->pat ? 1 :\n\t\t\tmod->xxp[pat]->rows ? mod->xxp[pat]->rows : 1);\n    }\n\n    /* Use a temporary flow_control so the scan can borrow the player's\n     * Pattern Loop handler. */\n    memset(&f, 0, sizeof(f));\n    f.loop = loop;\n    for (i = 0; i < mod->chn; i++) {\n\tloop[i].start = 0;\n\tloop[i].count = 0;\n    }\n    f.loop_dest = -1;\n    f.loop_param = -1;\n    f.loop_start = -1;\n    f.loop_count = 0;\n    f.loop_active_num = 0;\n    line_jump = 0;\n\n    gvl = mod->gvl;\n    bpm = mod->bpm;\n\n    speed = mod->spd;\n    base_time = m->rrate;\n#ifndef LIBXMP_CORE_PLAYER\n    st26_speed = 0;\n    far_tempo_coarse = 4;\n    far_tempo_fine = 0;\n    far_tempo_mode = 1;\n\n    if (HAS_FAR_MODULE_EXTRAS(ctx->m)) {\n\tfar_tempo_coarse = FAR_MODULE_EXTRAS(ctx->m)->coarse_tempo;\n\tlibxmp_far_translate_tempo(far_tempo_mode, 0, far_tempo_coarse,\n\t\t\t\t   &far_tempo_fine, &speed, &bpm);\n    }\n#endif\n\n    has_marker = HAS_QUIRK(QUIRK_MARKER);\n\n    /* By erlk ozlr <erlk.ozlr@gmail.com>\n     *\n     * xmp doesn't handle really properly the \"start\" option (-s for the\n     * command-line) for DeusEx's .umx files. These .umx files contain\n     * several loop \"tracks\" that never join together. That's how they put\n     * multiple musics on each level with a file per level. Each \"track\"\n     * starts at the same order in all files. The problem is that xmp starts\n     * decoding the module at order 0 and not at the order specified with\n     * the start option. If we have a module that does \"0 -> 2 -> 0 -> ...\",\n     * we cannot play order 1, even with the supposed right option.\n     *\n     * was: ord2 = ord = -1;\n     *\n     * CM: Fixed by using different \"sequences\" for each loop or subsong.\n     *     Each sequence has its entry point. Sequences don't overlap.\n     */\n    ord2 = -1;\n    ord = ep - 1;\n\n    gvol_memory = break_row = row_count = row_count_total = frame_count = 0;\n    orders_since_last_valid = any_valid = 0;\n    start_time = time = 0.0;\n    inside_loop = 0;\n\n    while (42) {\n\t/* Sanity check to prevent getting stuck due to broken patterns. */\n\tif (orders_since_last_valid > 512) {\n\t    D_(D_CRIT \"orders_since_last_valid = %d @ ord %d; ending scan\", orders_since_last_valid, ord);\n\t    break;\n\t}\n\torders_since_last_valid++;\n\n\tif ((uint32)++ord >= mod->len) {\n\t    if (mod->rst > mod->len || mod->xxo[mod->rst] >= mod->pat) {\n\t\tord = ep;\n\t    } else {\n\t\tif (libxmp_get_sequence(ctx, mod->rst) == chain) {\n\t            ord = mod->rst;\n\t\t} else {\n\t\t    ord = ep;\n\t        }\n\t    }\n\n\t    pat = mod->xxo[ord];\n\t    if (has_marker && pat == XMP_MARK_END) {\n\t\tbreak;\n\t    }\n\t}\n\n\tpat = mod->xxo[ord];\n\tinfo = &m->xxo_info[ord];\n\n\t/* Allow more complex order reuse only in main sequence */\n\tif (ep != 0 && p->sequence_control[ord] != NO_SEQUENCE) {\n\t    /* Currently to detect the end of the sequence, the player needs the\n\t     * end to be a real position and row, so skip invalid and S3M_SKIP.\n\t     * \"amazonas-dynomite mix.it\" by Skaven has a sequence (9) where an\n\t     * S3M_END repeats into an S3M_SKIP.\n\t     *\n\t     * Two sequences (7 and 8) in \"alien incident - leohou2.s3m\" by\n\t     * Purple Motion share the same S3M_END due to an off-by-one jump,\n\t     * so check S3M_END here too.\n\t     */\n\t    if (pat >= mod->pat) {\n\t\tif (has_marker && pat == XMP_MARK_END) {\n\t\t\tord = mod->len;\n\t\t}\n\t\tcontinue;\n\t    }\n\t    break;\n\t}\n\tp->sequence_control[ord] = chain;\n\n\t/* All invalid patterns skipped, only S3M_END aborts replay */\n\tif (pat >= mod->pat) {\n\t    if (has_marker && pat == XMP_MARK_END) {\n\t\tord = mod->len;\n\t        continue;\n\t    }\n\t    continue;\n\t}\n\n        if (break_row >= mod->xxp[pat]->rows) {\n            break_row = 0;\n        }\n\n\t/* Changing patterns may reset loop vars. */\n\tif (HAS_FLOW_MODE(FLOW_LOOP_PATTERN_RESET)) {\n\t    f.loop_start = -1;\n\t    f.loop_count = 0;\n\t    for (i = 0; i < mod->chn; i++) {\n\t\tf.loop[i].start = 0;\n\t\tf.loop[i].count = 0;\n\t    }\n\t}\n\n        /* Loops can cross pattern boundaries, so check if we're not looping */\n        if (m->scan_cnt[ord][break_row] && !inside_loop) {\n            break;\n        }\n\n        /* Only update pattern information if we weren't here before. This also\n         * means that we don't update pattern information if we're inside a loop,\n         * otherwise a loop containing e.g. a global volume fade can make the\n         * pattern start with the wrong volume. (fixes xyce-dans_la_rue.xm replay,\n         * see https://github.com/libxmp/libxmp/issues/153 for more details).\n         */\n        if (info->time < 0) {\n            info->gvl = gvl;\n            info->bpm = bpm;\n            info->speed = speed;\n\t    /* TODO: double ord_data::time */\n\t    time_calc = time + m->time_factor * frame_count * base_time / bpm;\n            info->time = time_calc > (double)INT_MAX ? INT_MAX : (int)time_calc;\n#ifndef LIBXMP_CORE_PLAYER\n            info->st26_speed = st26_speed;\n#endif\n        }\n\n\tif (info->start_row == 0 && ord != 0) {\n\t    if (ord == ep) {\n\t\tstart_time = time + m->time_factor * frame_count * base_time / bpm;\n\t    }\n\n\t    info->start_row = break_row;\n\t}\n\n\t/* Get tracks in advance to speed up the event parsing loop. */\n\tfor (chn = 0; chn < mod->chn; chn++) {\n\t\ttracks[chn] = mod->xxt[TRACK_NUM(pat, chn)];\n\t}\n\n\tlast_row = mod->xxp[pat]->rows;\n\tfor (row = break_row, break_row = 0; row < last_row; row++, row_count++, row_count_total++) {\n\t    /* Prevent crashes caused by large softmixer frames */\n\t    if (bpm < XMP_MIN_BPM) {\n\t        bpm = XMP_MIN_BPM;\n\t    }\n\n\t    /* Date: Sat, 8 Sep 2007 04:01:06 +0200\n\t     * Reported by Zbigniew Luszpinski <zbiggy@o2.pl>\n\t     * The scan routine falls into infinite looping and doesn't let\n\t     * xmp play jos-dr4k.xm.\n\t     * Claudio's workaround: we'll break infinite loops here.\n\t     *\n\t     * Date: Oct 27, 2007 8:05 PM\n\t     * From: Adric Riedel <adric.riedel@gmail.com>\n\t     * Jesper Kyd: Global Trash 3.mod (the 'Hardwired' theme) only\n\t     * plays the first 4:41 of what should be a 10 minute piece.\n\t     * (...) it dies at the end of position 2F\n\t     */\n\n\t    if (row_count_total > row_limit) {\n\t\tD_(D_CRIT \"row_count_total = %d @ ord %d, pat %d, row %d; ending scan\", row_count_total, ord, pat, row);\n\t\tgoto end_module;\n\t    }\n\n\t    if (!f.loop_active_num && !line_jump && m->scan_cnt[ord][row]) {\n\t\trow_count--;\n\t\tgoto end_module;\n\t    }\n\t    m->scan_cnt[ord][row]++;\n\t    orders_since_last_valid = 0;\n\t    any_valid = 1;\n\n\t    /* If the scan count for this row overflows, break.\n\t     * A scan count of 0 will help break this loop in playback (storlek_11.it).\n\t     */\n\t    if (!m->scan_cnt[ord][row]) {\n\t\tgoto end_module;\n\t    }\n\n\t    pdelay = 0;\n\t    line_jump = 0;\n\n\t    for (chn = 0; chn < mod->chn; chn++) {\n\t\tif (row >= tracks[chn]->rows)\n\t\t    continue;\n\n\t\t/* event = &EVENT(mod->xxo[ord], chn, row); */\n\t\tevent = &tracks[chn]->event[row];\n\n\t\tf1 = event->fxt;\n\t\tp1 = event->fxp;\n\t\tf2 = event->f2t;\n\t\tp2 = event->f2p;\n\n\t\tif (f1 == 0 && f2 == 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (f1 == FX_GLOBALVOL || f2 == FX_GLOBALVOL) {\n\t\t    gvl = (f1 == FX_GLOBALVOL) ? p1 : p2;\n\t\t    gvl = gvl > m->gvolbase ? m->gvolbase : gvl < 0 ? 0 : gvl;\n\t\t}\n\n\t\t/* Process fine global volume slide */\n\t\tif (f1 == FX_GVOL_SLIDE || f2 == FX_GVOL_SLIDE) {\n\t\t    int h, l;\n\t\t    parm = (f1 == FX_GVOL_SLIDE) ? p1 : p2;\n\n\t\tprocess_gvol:\n                    if (parm) {\n\t\t\tgvol_memory = parm;\n                        h = MSN(parm);\n                        l = LSN(parm);\n\n\t\t        if (HAS_QUIRK(QUIRK_FINEFX)) {\n                            if (l == 0xf && h != 0) {\n\t\t\t\tgvl += h;\n\t\t\t    } else if (h == 0xf && l != 0) {\n\t\t\t\tgvl -= l;\n\t\t\t    } else {\n\t\t                if (m->quirk & QUIRK_VSALL) {\n                                    gvl += (h - l) * speed;\n\t\t\t\t} else {\n                                    gvl += (h - l) * (speed - 1);\n\t\t\t\t}\n\t\t\t    }\n\t\t\t} else {\n\t\t            if (m->quirk & QUIRK_VSALL) {\n                                gvl += (h - l) * speed;\n\t\t\t    } else {\n                                gvl += (h - l) * (speed - 1);\n\t\t\t    }\n\t\t\t}\n\t\t    } else {\n                        if ((parm = gvol_memory) != 0)\n\t\t\t    goto process_gvol;\n\t\t    }\n\t\t}\n\n\t\t/* Some formats can have two FX_SPEED effects, and both need\n\t\t * to be checked. Slot 2 is currently handled first. */\n\t\tfor (i = 0; i < 2; i++) {\n\t\t    parm = i ? p1 : p2;\n\t\t    if ((i ? f1 : f2) != FX_SPEED || parm == 0)\n\t\t\tcontinue;\n\t\t    frame_count += row_count * speed;\n\t\t    row_count = 0;\n\t\t    if (HAS_QUIRK(QUIRK_NOBPM) || p->flags & XMP_FLAGS_VBLANK || parm < 0x20) {\n\t\t\tspeed = parm;\n#ifndef LIBXMP_CORE_PLAYER\n\t\t\tst26_speed = 0;\n#endif\n\t\t    } else {\n\t\t\ttime += m->time_factor * frame_count * base_time / bpm;\n\t\t\tframe_count = 0;\n\t\t\tbpm = parm;\n\t\t    }\n\t\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\t\tif (f1 == FX_SPEED_CP) {\n\t\t    f1 = FX_S3M_SPEED;\n\t\t}\n\t\tif (f2 == FX_SPEED_CP) {\n\t\t    f2 = FX_S3M_SPEED;\n\t\t}\n\n\t\t/* ST2.6 speed processing */\n\n\t\tif (f1 == FX_ICE_SPEED && p1) {\n\t\t    if (LSN(p1)) {\n\t\t        st26_speed = (MSN(p1) << 8) | LSN(p1);\n\t\t    } else {\n\t\t\tst26_speed = MSN(p1);\n\t\t    }\n\t\t}\n\n\t\t/* FAR tempo processing */\n\n\t\tif (f1 == FX_FAR_TEMPO || f1 == FX_FAR_F_TEMPO) {\n\t\t\tint far_speed, far_bpm, fine_change = 0;\n\t\t\tif (f1 == FX_FAR_TEMPO) {\n\t\t\t\tif (MSN(p1)) {\n\t\t\t\t\tfar_tempo_mode = MSN(p1) - 1;\n\t\t\t\t} else {\n\t\t\t\t\tfar_tempo_coarse = LSN(p1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (f1 == FX_FAR_F_TEMPO) {\n\t\t\t\tif (MSN(p1)) {\n\t\t\t\t\tfar_tempo_fine += MSN(p1);\n\t\t\t\t\tfine_change = MSN(p1);\n\t\t\t\t} else if (LSN(p1)) {\n\t\t\t\t\tfar_tempo_fine -= LSN(p1);\n\t\t\t\t\tfine_change = -LSN(p1);\n\t\t\t\t} else {\n\t\t\t\t\tfar_tempo_fine = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (libxmp_far_translate_tempo(far_tempo_mode, fine_change,\n\t\t\t    far_tempo_coarse, &far_tempo_fine, &far_speed, &far_bpm) == 0) {\n\t\t\t\tframe_count += row_count * speed;\n\t\t\t\trow_count = 0;\n\t\t\t\ttime += m->time_factor * frame_count * base_time / bpm;\n\t\t\t\tframe_count = 0;\n\t\t\t\tspeed = far_speed;\n\t\t\t\tbpm = far_bpm;\n\t\t\t}\n\t\t}\n\n\t\t/* ULT tempo processing */\n\n\t\tif (f1 == FX_ULT_TEMPO || f2 == FX_ULT_TEMPO) {\n\t\t    int parm2 = 0;\n\t\t    parm = 0;\n\t\t    if (f2 == FX_ULT_TEMPO) {\n\t\t\tif (p2 == 0) {\n\t\t\t\tparm = 6;\n\t\t\t\tparm2 = 125;\n\t\t\t} else if (p2 < 0x30) {\n\t\t\t\tparm = p2;\n\t\t\t} else {\n\t\t\t\tparm2 = p2;\n\t\t\t}\n\t\t    }\n\t\t    if (f1 == FX_ULT_TEMPO) {\n\t\t\tif (p1 == 0) {\n\t\t\t\tparm = 6;\n\t\t\t\tparm2 = 125;\n\t\t\t} else if (p1 < 0x30) {\n\t\t\t\tparm = p1;\n\t\t\t} else {\n\t\t\t\tparm2 = p1;\n\t\t\t}\n\t\t    }\n\t\t    frame_count += row_count * speed;\n\t\t    row_count = 0;\n\t\t    if (parm > 0) {\n\t\t\tspeed = parm;\n\t\t\tst26_speed = 0;\n\t\t    }\n\t\t    if (parm2 > 0) {\n\t\t\ttime += m->time_factor * frame_count * base_time / bpm;\n\t\t\tframe_count = 0;\n\t\t\tbpm = parm2;\n\t\t    }\n\t\t}\n#endif\n\n\t\tif ((f1 == FX_S3M_SPEED && p1) || (f2 == FX_S3M_SPEED && p2)) {\n\t\t    parm = (f1 == FX_S3M_SPEED) ? p1 : p2;\n\t\t    if (parm > 0) {\n\t\t        frame_count += row_count * speed;\n\t\t        row_count  = 0;\n\t\t        speed = parm;\n#ifndef LIBXMP_CORE_PLAYER\n\t\t        st26_speed = 0;\n#endif\n\t\t    }\n\t\t}\n\n\t\tif ((f1 == FX_S3M_BPM && p1) || (f2 == FX_S3M_BPM && p2)) {\n\t\t    parm = (f1 == FX_S3M_BPM) ? p1 : p2;\n\t\t    if (parm >= XMP_MIN_BPM) {\n\t\t\tframe_count += row_count * speed;\n\t\t\trow_count = 0;\n\t\t\ttime += m->time_factor * frame_count * base_time / bpm;\n\t\t\tframe_count = 0;\n\t\t\tbpm = parm;\n\t\t    }\n\t\t}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\t\tif ((f1 == FX_IT_BPM && p1) || (f2 == FX_IT_BPM && p2)) {\n\t\t    parm = (f1 == FX_IT_BPM) ? p1 : p2;\n\t\t    frame_count += row_count * speed;\n\t\t    row_count = 0;\n\t\t    time += m->time_factor * frame_count * base_time / bpm;\n\t\t    frame_count = 0;\n\n\t\t    if (MSN(parm) == 0) {\n\t\t        time += m->time_factor * base_time / bpm;\n\t\t\tfor (i = 1; i < speed; i++) {\n\t\t\t    bpm -= LSN(parm);\n\t\t\t    if (bpm < 0x20)\n\t\t\t\tbpm = 0x20;\n\t\t            time += m->time_factor * base_time / bpm;\n\t\t\t}\n\n\t\t\t/* remove one row at final bpm */\n\t\t\ttime -= m->time_factor * speed * base_time / bpm;\n\n\t\t    } else if (MSN(parm) == 1) {\n\t\t        time += m->time_factor * base_time / bpm;\n\t\t\tfor (i = 1; i < speed; i++) {\n\t\t\t    bpm += LSN(parm);\n\t\t\t    if (bpm > 0xff)\n\t\t\t\tbpm = 0xff;\n\t\t            time += m->time_factor * base_time / bpm;\n\t\t\t}\n\n\t\t\t/* remove one row at final bpm */\n\t\t\ttime -= m->time_factor * speed * base_time / bpm;\n\n\t\t    } else {\n\t\t\tbpm = parm;\n\t\t    }\n\t\t}\n\n\t\tif (f1 == FX_IT_ROWDELAY) {\n\t\t\t/* Don't allow the scan count for this row to overflow here. */\n\t\t\tint x = m->scan_cnt[ord][row] + (p1 & 0x0f);\n\t\t\tm->scan_cnt[ord][row] = MIN(x, 255);\n\t\t\tframe_count += (p1 & 0x0f) * speed;\n\t\t}\n\n\t\tif (f1 == FX_IT_BREAK || f2 == FX_IT_BREAK) {\n\t\t    parm = (f1 == FX_IT_BREAK) ? p1 : p2;\n\t\t    libxmp_process_pattern_break(ctx, &f, parm);\n\t\t    /* TODO: fully replace these variables with f */\n\t\t    if (f.pbreak) {\n\t\t\tbreak_row = f.jumpline;\n\t\t\tlast_row = 0;\n\t\t    }\n\t\t}\n#endif\n\n\t\tif (f1 == FX_JUMP || f2 == FX_JUMP) {\n\t\t    libxmp_process_pattern_jump(ctx, &f, (f1 == FX_JUMP ? p1 : p2));\n\t\t    /* TODO: fully replace these variables with f */\n\t\t    if (f.pbreak) {\n\t\t\tord2 = f.jump;\n\t\t\tbreak_row = f.jumpline;\n\t\t\tlast_row = 0;\n\n\t\t\t/* prevent infinite loop, see OpenMPT PatLoop-Various.xm */\n\t\t\tinside_loop = 0;\n\t\t    }\n\n\t\t}\n\n\t\tif (f1 == FX_BREAK || f2 == FX_BREAK) {\n\t\t    parm = (f1 == FX_BREAK) ? p1 : p2;\n\t\t    parm = 10 * MSN(parm) + LSN(parm);\n\t\t    libxmp_process_pattern_break(ctx, &f, parm);\n\t\t    /* TODO: fully replace these variables with f */\n\t\t    if (f.pbreak) {\n\t\t\tbreak_row = f.jumpline;\n\t\t\tlast_row = 0;\n\t\t    }\n\t\t}\n\n#ifndef LIBXMP_CORE_PLAYER\n\t\t/* Archimedes line jump */\n\t\tif (f1 == FX_LINE_JUMP || f2 == FX_LINE_JUMP) {\n\t\t    libxmp_process_line_jump(ctx, &f, ord,\n\t\t\t\t\t     (f1 == FX_LINE_JUMP ? p1 : p2));\n\t\t    /* Don't set order if preceded by jump or break. */\n\t\t    /* TODO: fully replace these variables with f */\n\t\t    if (last_row > 0)\n\t\t\tord2 = ord;\n\t\t    break_row = f.jumpline;\n\t\t    last_row = 0;\n\t\t    line_jump = 1;\n\t\t}\n#endif\n\n\t\tif (f1 == FX_EXTENDED || f2 == FX_EXTENDED) {\n\t\t    parm = (f1 == FX_EXTENDED) ? p1 : p2;\n\n\t\t    if ((parm >> 4) == EX_PATT_DELAY) {\n\t\t\tif (m->read_event_type != READ_EVENT_ST3 || !pdelay) {\n\t\t\t    pdelay = parm & 0x0f;\n                        }\n\t\t    }\n\n\t\t    if ((parm >> 4) == EX_PATTERN_LOOP) {\n\t\t\t/* QUIRK_FT2BUGS may set break_row */\n\t\t\tf.jumpline = break_row;\n\t\t\tlibxmp_process_pattern_loop(ctx, &f, chn, row, LSN(parm));\n\t\t\tbreak_row = f.jumpline;\n\n\t\t\t/* Attempt to detect the inside of a loop.\n\t\t\t * TODO: this won't detect all cases. */\n\t\t\tif (LSN(parm) > 0 && f.loop_dest < 0) {\n\t\t\t    inside_loop = 0;\n\t\t\t} else if (LSN(parm) == 0) {\n\t\t\t    inside_loop = 1;\n\t\t\t}\n\t\t    }\n\t\t}\n\t    }\n\n\t    if (pdelay > 0) {\n\t\tframe_count += pdelay * speed;\n\t    }\n\n\t    f.loop_param = -1;\n\t    if (f.loop_dest >= 0) {\n\t\t/* -1 as it will be incremented immediately by the loop. */\n\t\trow = f.loop_dest - 1;\n\t\tf.loop_dest = -1;\n\t    }\n\n#ifndef LIBXMP_CORE_PLAYER\n\t    if (st26_speed) {\n\t        frame_count += row_count * speed;\n\t        row_count  = 0;\n\t\tif (st26_speed & 0x10000) {\n\t\t\tspeed = (st26_speed & 0xff00) >> 8;\n\t\t} else {\n\t\t\tspeed = st26_speed & 0xff;\n\t\t}\n\t\tst26_speed ^= 0x10000;\n\t    }\n#endif\n\t}\n\n\tif (break_row && pdelay) {\n\t    break_row++;\n\t}\n\n\tif (ord2 >= 0) {\n\t    ord = ord2 - 1;\n\t    ord2 = -1;\n\t}\n\n\tframe_count += row_count * speed;\n\trow_count_total = 0;\n\trow_count = 0;\n    }\n    row = break_row;\n\nend_module:\n\n    /* Sanity check */\n    {\n        if (!any_valid) {\n\t    return -1;\n\t}\n        pat = mod->xxo[ord];\n        if (pat >= mod->pat || row >= mod->xxp[pat]->rows) {\n            row = 0;\n        }\n    }\n\n    p->scan[chain].num = m->scan_cnt[ord][row];\n    p->scan[chain].row = row;\n    p->scan[chain].ord = ord;\n\n    time -= start_time;\n    frame_count += row_count * speed;\n\n    /* TODO: double scan_data::time */\n    time_calc = time + m->time_factor * frame_count * base_time / bpm;\n    return time_calc > (double)INT_MAX ? INT_MAX : (int)time_calc;\n}\n\nstatic void reset_scan_data(struct context_data *ctx)\n{\n\tint i;\n\tfor (i = 0; i < XMP_MAX_MOD_LENGTH; i++) {\n\t\tctx->m.xxo_info[i].time = -1;\n\t}\n\tmemset(ctx->p.sequence_control, NO_SEQUENCE, XMP_MAX_MOD_LENGTH);\n}\n\n#ifndef LIBXMP_CORE_PLAYER\nstatic void compare_vblank_scan(struct context_data *ctx)\n{\n\t/* Calculate both CIA and VBlank time for certain long MODs\n\t * and pick the more likely (i.e. shorter) one. The same logic\n\t * works regardless of the initial mode selected--either way,\n\t * the wrong timing mode usually makes modules MUCH longer. */\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct ord_data *info_backup;\n\tstruct scan_data scan_backup;\n\tunsigned char ctrl_backup[256];\n\n\tif ((info_backup = (struct ord_data *)malloc(sizeof(m->xxo_info))) != NULL) {\n\t\t/* Back up the current info to avoid a third scan. */\n\t\tscan_backup = p->scan[0];\n\t\tmemcpy(info_backup, m->xxo_info, sizeof(m->xxo_info));\n\t\tmemcpy(ctrl_backup, p->sequence_control,\n\t\t\tsizeof(p->sequence_control));\n\n\t\treset_scan_data(ctx);\n\n\t\tm->quirk ^= QUIRK_NOBPM;\n\t\tp->scan[0].time = scan_module(ctx, 0, 0);\n\n\t\tD_(D_INFO \"%-6s %dms\", !HAS_QUIRK(QUIRK_NOBPM)?\"VBlank\":\"CIA\", scan_backup.time);\n\t\tD_(D_INFO \"%-6s %dms\",  HAS_QUIRK(QUIRK_NOBPM)?\"VBlank\":\"CIA\", p->scan[0].time);\n\n\t\tif (p->scan[0].time >= scan_backup.time) {\n\t\t\tm->quirk ^= QUIRK_NOBPM;\n\t\t\tp->scan[0] = scan_backup;\n\t\t\tmemcpy(m->xxo_info, info_backup, sizeof(m->xxo_info));\n\t\t\tmemcpy(p->sequence_control, ctrl_backup,\n\t\t\t\tsizeof(p->sequence_control));\n\t\t}\n\n\t\tfree(info_backup);\n\t}\n}\n#endif\n\nint libxmp_get_sequence(struct context_data *ctx, int ord)\n{\n\tstruct player_data *p = &ctx->p;\n\tif (ord < 0 || ord > ctx->m.mod.len)\n\t\treturn NO_SEQUENCE;\n\treturn p->sequence_control[ord];\n}\n\nint libxmp_scan_sequences(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct scan_data *s;\n\tint i, ep;\n\tint seq;\n\tunsigned char temp_ep[XMP_MAX_MOD_LENGTH];\n\n\ts = (struct scan_data *) realloc(p->scan, MAX(1, mod->len) * sizeof(struct scan_data));\n\tif (!s) {\n\t\tD_(D_CRIT \"failed to allocate scan data\");\n\t\treturn -1;\n\t}\n\tp->scan = s;\n\n\t/* Initialize order data to prevent overwrite when a position is used\n\t * multiple times at different starting points (see janosik.xm).\n\t */\n\treset_scan_data(ctx);\n\n\tep = 0;\n\ttemp_ep[0] = 0;\n\tp->scan[0].time = scan_module(ctx, ep, 0);\n\tseq = 1;\n\n#ifndef LIBXMP_CORE_PLAYER\n\tif (m->compare_vblank && !(p->flags & XMP_FLAGS_VBLANK) &&\n\t    p->scan[0].time >= VBLANK_TIME_THRESHOLD) {\n\t\tcompare_vblank_scan(ctx);\n\t}\n#endif\n\n\tif (p->scan[0].time < 0) {\n\t\tD_(D_CRIT \"scan was not able to find any valid orders\");\n\t\treturn -1;\n\t}\n\n\twhile (1) {\n\t\t/* Scan song starting at given entry point */\n\t\t/* Check if any patterns left */\n\t\tfor (i = 0; i < mod->len; i++) {\n\t\t\tif (p->sequence_control[i] == NO_SEQUENCE) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (i != mod->len && seq < MAX_SEQUENCES) {\n\t\t\t/* New entry point */\n\t\t\tep = i;\n\t\t\ttemp_ep[seq] = ep;\n\t\t\tp->scan[seq].time = scan_module(ctx, ep, seq);\n\t\t\tif (p->scan[seq].time > 0)\n\t\t\t\tseq++;\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (seq < mod->len) {\n\t\ts = (struct scan_data *) realloc(p->scan, seq * sizeof(struct scan_data));\n\t\tif (s != NULL) {\n\t\t\tp->scan = s;\n\t\t}\n\t}\n\tm->num_sequences = seq;\n\n\t/* Correct playback time calculation to match new base tempo factor. */\n\tp->current_time = p->current_time * (m->time_factor / p->scan_time_factor);\n\tp->scan_time_factor = m->time_factor;\n\n\t/* Now place entry points in the public accessible array */\n\tfor (i = 0; i < m->num_sequences; i++) {\n\t\tm->seq_data[i].entry_point = temp_ep[i];\n\t\tm->seq_data[i].duration = p->scan[i].time;\n\t}\n\t/* Wipe the remaining entries so the player doesn't think they're\n\t * valid e.g. when handling end-of-module markers. */\n\tfor (; i < MAX_SEQUENCES; i++) {\n\t\tm->seq_data[i].entry_point = 0;\n\t\tm->seq_data[i].duration = 0;\n\t}\n\t/* Correct any zero-length temporary sequences from the scan.\n\t * There's no \"correct\" sequence for these, so copy whichever\n\t * valid sequence precedes the affected position. */\n\tfor (i = 0; i < mod->len; i++) {\n\t\tif (p->sequence_control[i] >= m->num_sequences) {\n\t\t\tp->sequence_control[i] =\n\t\t\t\t(i > 0) ? p->sequence_control[i - 1] : 0;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/smix.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2022 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n#include \"period.h\"\n#include \"player.h\"\n#include \"hio.h\"\n#include \"loaders/loader.h\"\n\n\nstruct xmp_instrument *libxmp_get_instrument(struct context_data *ctx, int ins)\n{\n\tstruct smix_data *smix = &ctx->smix;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_instrument *xxi;\n\n\tif (ins < 0) {\n\t\txxi = NULL;\n\t} else if (ins < mod->ins) {\n\t\txxi = &mod->xxi[ins];\n\t} else if (ins < mod->ins + smix->ins) {\n\t\txxi = &smix->xxi[ins - mod->ins];\n\t} else {\n\t\txxi = NULL;\n\t}\n\n\treturn xxi;\n}\n\nstruct xmp_sample *libxmp_get_sample(struct context_data *ctx, int smp)\n{\n\tstruct smix_data *smix = &ctx->smix;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_sample *xxs;\n\n\tif (smp < 0) {\n\t\txxs = NULL;\n\t} else if (smp < mod->smp) {\n\t\txxs = &mod->xxs[smp];\n\t} else if (smp < mod->smp + smix->smp) {\n\t\txxs = &smix->xxs[smp - mod->smp];\n\t} else {\n\t\txxs = NULL;\n\t}\n\n\treturn xxs;\n}\n\nint xmp_start_smix(xmp_context opaque, int chn, int smp)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct smix_data *smix = &ctx->smix;\n\n\tif (ctx->state > XMP_STATE_LOADED) {\n\t\treturn -XMP_ERROR_STATE;\n\t}\n\n\tsmix->xxi = (struct xmp_instrument *) calloc(smp, sizeof(struct xmp_instrument));\n\tif (smix->xxi == NULL) {\n\t\tgoto err;\n\t}\n\tsmix->xxs = (struct xmp_sample *) calloc(smp, sizeof(struct xmp_sample));\n\tif (smix->xxs == NULL) {\n\t\tgoto err1;\n\t}\n\n\tsmix->chn = chn;\n\tsmix->ins = smix->smp = smp;\n\n\treturn 0;\n\n    err1:\n\tfree(smix->xxi);\n\tsmix->xxi = NULL;\n    err:\n\treturn -XMP_ERROR_INTERNAL;\n}\n\nint xmp_smix_play_instrument(xmp_context opaque, int ins, int note, int vol, int chn)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct smix_data *smix = &ctx->smix;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_event *event;\n\n\tif (ctx->state < XMP_STATE_PLAYING) {\n\t\treturn -XMP_ERROR_STATE;\n\t}\n\n\tif (chn >= smix->chn || chn < 0 || ins >= mod->ins || ins < 0) {\n\t\treturn -XMP_ERROR_INVALID;\n\t}\n\n\tif (note == 0) {\n\t\tnote = 60;\t\t/* middle C note number */\n\t}\n\n\tevent = &p->inject_event[mod->chn + chn];\n\tmemset(event, 0, sizeof (struct xmp_event));\n\tevent->note = (note < XMP_MAX_KEYS) ? note + 1 : note;\n\tevent->ins = ins + 1;\n\tevent->vol = vol + 1;\n\tevent->_flag = 1;\n\n\treturn 0;\n}\n\nint xmp_smix_play_sample(xmp_context opaque, int ins, int note, int vol, int chn)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct smix_data *smix = &ctx->smix;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_module *mod = &m->mod;\n\tstruct xmp_event *event;\n\n\tif (ctx->state < XMP_STATE_PLAYING) {\n\t\treturn -XMP_ERROR_STATE;\n\t}\n\n\tif (chn >= smix->chn || chn < 0 || ins >= smix->ins || ins < 0) {\n\t\treturn -XMP_ERROR_INVALID;\n\t}\n\n\tif (note == 0) {\n\t\tnote = 60;\t\t/* middle C note number */\n\t}\n\n\tevent = &p->inject_event[mod->chn + chn];\n\tmemset(event, 0, sizeof (struct xmp_event));\n\tevent->note = (note < XMP_MAX_KEYS) ? note + 1 : note;\n\tevent->ins = mod->ins + ins + 1;\n\tevent->vol = vol + 1;\n\tevent->_flag = 1;\n\n\treturn 0;\n}\n\nint xmp_smix_channel_pan(xmp_context opaque, int chn, int pan)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct player_data *p = &ctx->p;\n\tstruct smix_data *smix = &ctx->smix;\n\tstruct module_data *m = &ctx->m;\n\tstruct channel_data *xc;\n\n\tif (chn >= smix->chn || pan < 0 || pan > 255) {\n\t\treturn -XMP_ERROR_INVALID;\n\t}\n\n\txc = &p->xc_data[m->mod.chn + chn];\n\txc->pan.val = pan;\n\n\treturn 0;\n}\n\nint xmp_smix_load_sample(xmp_context opaque, int num, const char *path)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct smix_data *smix = &ctx->smix;\n\tstruct module_data *m = &ctx->m;\n\tstruct xmp_instrument *xxi;\n\tstruct xmp_sample *xxs;\n\tHIO_HANDLE *h;\n\tuint32 magic;\n\tint chn, rate, bits, size;\n\tint retval = -XMP_ERROR_INTERNAL;\n\n\tif (num >= smix->ins) {\n\t\tretval = -XMP_ERROR_INVALID;\n\t\tgoto err;\n\t}\n\n\txxi = &smix->xxi[num];\n\txxs = &smix->xxs[num];\n\n\th = hio_open(path, \"rb\");\n\tif (h == NULL) {\n\t\tretval = -XMP_ERROR_SYSTEM;\n\t\tgoto err;\n\t}\n\n\t/* Init instrument */\n\n\txxi->sub = (struct xmp_subinstrument *) calloc(1, sizeof(struct xmp_subinstrument));\n\tif (xxi->sub == NULL) {\n\t\tretval = -XMP_ERROR_SYSTEM;\n\t\tgoto err1;\n\t}\n\n\txxi->vol = m->volbase;\n\txxi->nsm = 1;\n\txxi->sub[0].sid = num;\n\txxi->sub[0].vol = xxi->vol;\n\txxi->sub[0].pan = 0x80;\n\n\t/* Load sample */\n\n\tmagic = hio_read32b(h);\n\tif (magic != 0x52494646) {\t/* RIFF */\n\t\tretval = -XMP_ERROR_FORMAT;\n\t\tgoto err2;\n\t}\n\n\tif (hio_seek(h, 22, SEEK_SET) < 0) {\n\t\tretval = -XMP_ERROR_SYSTEM;\n\t\tgoto err2;\n\t}\n\tchn = hio_read16l(h);\n\tif (chn != 1) {\n\t\tretval = -XMP_ERROR_FORMAT;\n\t\tgoto err2;\n\t}\n\n\trate = hio_read32l(h);\n\tif (rate == 0) {\n\t\tretval = -XMP_ERROR_FORMAT;\n\t\tgoto err2;\n\t}\n\n\tif (hio_seek(h, 34, SEEK_SET) < 0) {\n\t\tretval = -XMP_ERROR_SYSTEM;\n\t\tgoto err2;\n\t}\n\tbits = hio_read16l(h);\n\tif (bits == 0) {\n\t\tretval = -XMP_ERROR_FORMAT;\n\t\tgoto err2;\n\t}\n\n\tif (hio_seek(h, 40, SEEK_SET) < 0) {\n\t\tretval = -XMP_ERROR_SYSTEM;\n\t\tgoto err2;\n\t}\n\tsize = hio_read32l(h);\n\tif (size == 0) {\n\t\tretval = -XMP_ERROR_FORMAT;\n\t\tgoto err2;\n\t}\n\n\tlibxmp_c2spd_to_note(rate, &xxi->sub[0].xpo, &xxi->sub[0].fin);\n\n\txxs->len = 8 * size / bits;\n\txxs->lps = 0;\n\txxs->lpe = 0;\n\txxs->flg = bits == 16 ? XMP_SAMPLE_16BIT : 0;\n\n\txxs->data = (unsigned char *) malloc(size + 8);\n\tif (xxs->data == NULL) {\n\t\tretval = -XMP_ERROR_SYSTEM;\n\t\tgoto err2;\n\t}\n\n\t/* ugly hack to make the interpolator happy */\n\tmemset(xxs->data, 0, 4);\n\tmemset(xxs->data + 4 + size, 0, 4);\n\txxs->data += 4;\n\n\tif (hio_seek(h, 44, SEEK_SET) < 0) {\n\t\tretval = -XMP_ERROR_SYSTEM;\n\t\tgoto err2;\n\t}\n\tif (hio_read(xxs->data, 1, size, h) != size) {\n\t\tretval = -XMP_ERROR_SYSTEM;\n\t\tgoto err2;\n\t}\n\thio_close(h);\n\n\treturn 0;\n\n    err2:\n\tfree(xxi->sub);\n\txxi->sub = NULL;\n    err1:\n\thio_close(h);\n    err:\n\treturn retval;\n}\n\nint xmp_smix_release_sample(xmp_context opaque, int num)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct smix_data *smix = &ctx->smix;\n\n\tif (num >= smix->ins) {\n\t\treturn -XMP_ERROR_INVALID;\n\t}\n\n\tlibxmp_free_sample(&smix->xxs[num]);\n\tfree(smix->xxi[num].sub);\n\n\tsmix->xxs[num].data = NULL;\n\tsmix->xxi[num].sub = NULL;\n\n\treturn 0;\n}\n\nvoid xmp_end_smix(xmp_context opaque)\n{\n\tstruct context_data *ctx = (struct context_data *)opaque;\n\tstruct smix_data *smix = &ctx->smix;\n\tint i;\n\n\tfor (i = 0; i < smix->smp; i++) {\n\t\txmp_smix_release_sample(opaque, i);\n\t}\n\n\tfree(smix->xxs);\n\tfree(smix->xxi);\n\tsmix->xxs = NULL;\n\tsmix->xxi = NULL;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/tempfile.h",
    "content": "#ifndef XMP_PLATFORM_H\n#define XMP_PLATFORM_H\n\nFILE *make_temp_file(char **);\nvoid unlink_temp_file(char *);\n\n#endif\n"
  },
  {
    "path": "contrib/libxmp/src/virtual.c",
    "content": "/* Extended Module Player\n * Copyright (C) 1996-2024 Claudio Matsuoka and Hipolito Carraro Jr\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include \"common.h\"\n#include \"virtual.h\"\n#include \"mixer.h\"\n\n#ifdef LIBXMP_PAULA_SIMULATOR\n#include \"paula.h\"\n#endif\n\n#define\tFREE\t-1\n\n/* For virt_pastnote() */\nvoid libxmp_player_set_release(struct context_data *, int);\nvoid libxmp_player_set_fadeout(struct context_data *, int);\n\n\n/* Get parent channel */\nint libxmp_virt_getroot(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi;\n\tint voc;\n\n\tvoc = p->virt.virt_channel[chn].map;\n\tif (voc < 0) {\n\t\treturn -1;\n\t}\n\n\tvi = &p->virt.voice_array[voc];\n\n\treturn vi->root;\n}\n\nvoid libxmp_virt_resetvoice(struct context_data *ctx, int voc, int mute)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi = &p->virt.voice_array[voc];\n#ifdef LIBXMP_PAULA_SIMULATOR\n\tstruct paula_state *paula;\n#endif\n\n\tif ((uint32)voc >= p->virt.maxvoc) {\n\t\treturn;\n\t}\n\n\tif (mute) {\n\t\tlibxmp_mixer_setvol(ctx, voc, 0);\n\t}\n\n\tp->virt.virt_used--;\n\tp->virt.virt_channel[vi->root].count--;\n\tp->virt.virt_channel[vi->chn].map = FREE;\n#ifdef LIBXMP_PAULA_SIMULATOR\n\tpaula = vi->paula;\n#endif\n\tmemset(vi, 0, sizeof(struct mixer_voice));\n#ifdef LIBXMP_PAULA_SIMULATOR\n\tvi->paula = paula;\n#endif\n\tvi->chn = vi->root = FREE;\n}\n\n/* virt_on (number of tracks) */\nint libxmp_virt_on(struct context_data *ctx, int num)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tint i;\n\n\tp->virt.num_tracks = num;\n\tnum = libxmp_mixer_numvoices(ctx, -1);\n\n\tp->virt.virt_channels = p->virt.num_tracks;\n\n\tif (HAS_QUIRK(QUIRK_VIRTUAL)) {\n\t\tp->virt.virt_channels += num;\n\t} else if (num > p->virt.virt_channels) {\n\t\tnum = p->virt.virt_channels;\n\t}\n\n\tp->virt.maxvoc = libxmp_mixer_numvoices(ctx, num);\n\n\tp->virt.voice_array = (struct mixer_voice *) calloc(p->virt.maxvoc,\n\t\t\t\t\t\tsizeof(struct mixer_voice));\n\tif (p->virt.voice_array == NULL)\n\t\tgoto err;\n\n\tfor (i = 0; i < p->virt.maxvoc; i++) {\n\t\tp->virt.voice_array[i].chn = FREE;\n\t\tp->virt.voice_array[i].root = FREE;\n\t}\n\n#ifdef LIBXMP_PAULA_SIMULATOR\n\t/* Initialize Paula simulator */\n\tif (IS_AMIGA_MOD()) {\n\t\tfor (i = 0; i < p->virt.maxvoc; i++) {\n\t\t\tp->virt.voice_array[i].paula = (struct paula_state *) calloc(1, sizeof(struct paula_state));\n\t\t\tif (p->virt.voice_array[i].paula == NULL) {\n\t\t\t\tgoto err2;\n\t\t\t}\n\t\t\tlibxmp_paula_init(ctx, p->virt.voice_array[i].paula);\n\t\t}\n\t}\n#endif\n\n\tp->virt.virt_channel = (struct virt_channel *) malloc(p->virt.virt_channels *\n\t\t\t\t\t\t\tsizeof(struct virt_channel));\n\tif (p->virt.virt_channel == NULL)\n\t\tgoto err2;\n\n\tfor (i = 0; i < p->virt.virt_channels; i++) {\n\t\tp->virt.virt_channel[i].map = FREE;\n\t\tp->virt.virt_channel[i].count = 0;\n\t}\n\n\tp->virt.virt_used = 0;\n\n\treturn 0;\n\n      err2:\n#ifdef LIBXMP_PAULA_SIMULATOR\n\tif (IS_AMIGA_MOD()) {\n\t\tfor (i = 0; i < p->virt.maxvoc; i++) {\n\t\t\tfree(p->virt.voice_array[i].paula);\n\t\t}\n\t}\n#endif\n\tfree(p->virt.voice_array);\n\tp->virt.voice_array = NULL;\n      err:\n\treturn -1;\n}\n\nvoid libxmp_virt_off(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n#ifdef LIBXMP_PAULA_SIMULATOR\n\tint i;\n#endif\n\n#ifdef LIBXMP_PAULA_SIMULATOR\n\t/* Free Paula simulator state */\n\t/* Player type may have been changed; always free this. */\n\tfor (i = 0; i < p->virt.maxvoc; i++) {\n\t\tfree(p->virt.voice_array[i].paula);\n\t}\n#endif\n\n\tp->virt.virt_used = p->virt.maxvoc = 0;\n\tp->virt.virt_channels = 0;\n\tp->virt.num_tracks = 0;\n\n\tfree(p->virt.voice_array);\n\tfree(p->virt.virt_channel);\n\tp->virt.voice_array = NULL;\n\tp->virt.virt_channel = NULL;\n}\n\nvoid libxmp_virt_reset(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tint i;\n\n\tif (p->virt.virt_channels < 1) {\n\t\treturn;\n\t}\n\n\t/* CID 129203 (#1 of 1): Useless call (USELESS_CALL)\n\t * Call is only useful for its return value, which is ignored.\n\t *\n\t * libxmp_mixer_numvoices(ctx, p->virt.maxvoc);\n\t */\n\n\tfor (i = 0; i < p->virt.maxvoc; i++) {\n\t\tstruct mixer_voice *vi = &p->virt.voice_array[i];\n#ifdef LIBXMP_PAULA_SIMULATOR\n\t\tstruct paula_state *paula = vi->paula;\n#endif\n\t\tmemset(vi, 0, sizeof(struct mixer_voice));\n#ifdef LIBXMP_PAULA_SIMULATOR\n\t\tvi->paula = paula;\n#endif\n\t\tvi->chn = FREE;\n\t\tvi->root = FREE;\n\t}\n\n\tfor (i = 0; i < p->virt.virt_channels; i++) {\n\t\tp->virt.virt_channel[i].map = FREE;\n\t\tp->virt.virt_channel[i].count = 0;\n\t}\n\n\tp->virt.virt_used = 0;\n}\n\nstatic int free_voice(struct context_data *ctx)\n{\n\tstruct player_data *p = &ctx->p;\n\tint i, num, vol;\n\n\t/* Find background voice with lowest volume*/\n\tnum = FREE;\n\tvol = INT_MAX;\n\tfor (i = 0; i < p->virt.maxvoc; i++) {\n\t\tstruct mixer_voice *vi = &p->virt.voice_array[i];\n\n\t\tif (vi->chn >= p->virt.num_tracks && vi->vol < vol) {\n\t\t\tnum = i;\n\t\t\tvol = vi->vol;\n\t\t}\n\t}\n\n\t/* Free voice */\n\tif (num >= 0) {\n\t\tp->virt.virt_channel[p->virt.voice_array[num].chn].map = FREE;\n\t\tp->virt.virt_channel[p->virt.voice_array[num].root].count--;\n\t\tp->virt.virt_used--;\n\t}\n\n\treturn num;\n}\n\nstatic int alloc_voice(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tint i;\n\n\t/* Find free voice */\n\tfor (i = 0; i < p->virt.maxvoc; i++) {\n\t\tif (p->virt.voice_array[i].chn == FREE)\n\t\t\tbreak;\n\t}\n\n\t/* not found */\n\tif (i == p->virt.maxvoc) {\n\t\ti = free_voice(ctx);\n\t}\n\n\tif (i >= 0) {\n\t\tp->virt.virt_channel[chn].count++;\n\t\tp->virt.virt_used++;\n\n\t\tp->virt.voice_array[i].chn = chn;\n\t\tp->virt.voice_array[i].root = chn;\n\t\tp->virt.virt_channel[chn].map = i;\n\t}\n\n\treturn i;\n}\n\nstatic int map_virt_channel(struct player_data *p, int chn)\n{\n\tint voc;\n\n\tif ((uint32)chn >= p->virt.virt_channels)\n\t\treturn -1;\n\n\tvoc = p->virt.virt_channel[chn].map;\n\n\tif ((uint32)voc >= p->virt.maxvoc)\n\t\treturn -1;\n\n\treturn voc;\n}\n\nint libxmp_virt_mapchannel(struct context_data *ctx, int chn)\n{\n\treturn map_virt_channel(&ctx->p, chn);\n}\n\nvoid libxmp_virt_resetchannel(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi;\n#ifdef LIBXMP_PAULA_SIMULATOR\n\tstruct paula_state *paula;\n#endif\n\tint voc;\n\n\tif ((voc = map_virt_channel(p, chn)) < 0)\n\t\treturn;\n\n\tlibxmp_mixer_setvol(ctx, voc, 0);\n\n\tp->virt.virt_used--;\n\tp->virt.virt_channel[p->virt.voice_array[voc].root].count--;\n\tp->virt.virt_channel[chn].map = FREE;\n\n\tvi = &p->virt.voice_array[voc];\n#ifdef LIBXMP_PAULA_SIMULATOR\n\tpaula = vi->paula;\n#endif\n\tmemset(vi, 0, sizeof(struct mixer_voice));\n#ifdef LIBXMP_PAULA_SIMULATOR\n\tvi->paula = paula;\n#endif\n\tvi->chn = vi->root = FREE;\n}\n\nvoid libxmp_virt_setvol(struct context_data *ctx, int chn, int vol)\n{\n\tstruct player_data *p = &ctx->p;\n\tint voc, root;\n\n\tif ((voc = map_virt_channel(p, chn)) < 0) {\n\t\treturn;\n\t}\n\n\troot = p->virt.voice_array[voc].root;\n\tif (root < XMP_MAX_CHANNELS && p->channel_mute[root]) {\n\t\tvol = 0;\n\t}\n\n\tlibxmp_mixer_setvol(ctx, voc, vol);\n\n\tif (vol == 0 && chn >= p->virt.num_tracks) {\n\t\tlibxmp_virt_resetvoice(ctx, voc, 1);\n\t}\n}\n\nvoid libxmp_virt_release(struct context_data *ctx, int chn, int rel)\n{\n\tstruct player_data *p = &ctx->p;\n\tint voc;\n\n\tif ((voc = map_virt_channel(p, chn)) < 0) {\n\t\treturn;\n\t}\n\n\tlibxmp_mixer_release(ctx, voc, rel);\n}\n\nvoid libxmp_virt_reverse(struct context_data *ctx, int chn, int rev)\n{\n\tstruct player_data *p = &ctx->p;\n\tint voc;\n\n\tif ((voc = map_virt_channel(p, chn)) < 0) {\n\t\treturn;\n\t}\n\n\tlibxmp_mixer_reverse(ctx, voc, rev);\n}\n\nvoid libxmp_virt_setpan(struct context_data *ctx, int chn, int pan)\n{\n\tstruct player_data *p = &ctx->p;\n\tint voc;\n\n\tif ((voc = map_virt_channel(p, chn)) < 0) {\n\t\treturn;\n\t}\n\n\tlibxmp_mixer_setpan(ctx, voc, pan);\n}\n\nvoid libxmp_virt_seteffect(struct context_data *ctx, int chn, int type, int val)\n{\n\tstruct player_data *p = &ctx->p;\n\tint voc;\n\n\tif ((voc = map_virt_channel(p, chn)) < 0) {\n\t\treturn;\n\t}\n\n\tlibxmp_mixer_seteffect(ctx, voc, type, val);\n}\n\ndouble libxmp_virt_getvoicepos(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tint voc;\n\n\tif ((voc = map_virt_channel(p, chn)) < 0) {\n\t\treturn -1;\n\t}\n\n\treturn libxmp_mixer_getvoicepos(ctx, voc);\n}\n\n#ifndef LIBXMP_CORE_PLAYER\n\nvoid libxmp_virt_setsmp(struct context_data *ctx, int chn, int smp)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi;\n\tdouble pos;\n\tint voc;\n\n\tif ((voc = map_virt_channel(p, chn)) < 0) {\n\t\treturn;\n\t}\n\n\tvi = &p->virt.voice_array[voc];\n\tif (vi->smp == smp) {\n\t\treturn;\n\t}\n\n\tpos = libxmp_mixer_getvoicepos(ctx, voc);\n\tlibxmp_mixer_setpatch(ctx, voc, smp, 0);\n\tlibxmp_mixer_voicepos(ctx, voc, pos, 0);\t/* Restore old position */\n}\n\n#endif\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\nvoid libxmp_virt_setnna(struct context_data *ctx, int chn, int nna)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct module_data *m = &ctx->m;\n\tint voc;\n\n\tif (!HAS_QUIRK(QUIRK_VIRTUAL)) {\n\t\treturn;\n\t}\n\n\tif ((voc = map_virt_channel(p, chn)) < 0) {\n\t\treturn;\n\t}\n\n\tp->virt.voice_array[voc].act = nna;\n}\n\nstatic void check_dct(struct context_data *ctx, int i, int chn, int ins,\n\t\t\tint smp, int key, int nna, int dct, int dca)\n{\n\tstruct player_data *p = &ctx->p;\n\tstruct mixer_voice *vi = &p->virt.voice_array[i];\n\tint voc;\n\n\tvoc = p->virt.virt_channel[chn].map;\n\n\tif (vi->root == chn && vi->ins == ins) {\n\n\t\tif (nna == XMP_INST_NNA_CUT) {\n\t\t\tlibxmp_virt_resetvoice(ctx, i, 1);\n\t\t\treturn;\n\t\t}\n\n\t\tvi->act = nna;\n\n\t\tif ((dct == XMP_INST_DCT_INST) ||\n\t\t    (dct == XMP_INST_DCT_SMP && vi->smp == smp) ||\n\t\t    (dct == XMP_INST_DCT_NOTE && vi->key == key)) {\n\n\t\t\tif (nna == XMP_INST_NNA_OFF && dca == XMP_INST_DCA_FADE) {\n\t\t\t\tvi->act = VIRT_ACTION_OFF;\n\t\t\t} else if (dca) {\n\t\t\t\tif (i != voc || vi->act) {\n\t\t\t\t\tvi->act = dca;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlibxmp_virt_resetvoice(ctx, i, 1);\n\t\t\t}\n\t\t}\n\t}\n}\n\n#endif\n\n/* For note slides */\nvoid libxmp_virt_setnote(struct context_data *ctx, int chn, int note)\n{\n\tstruct player_data *p = &ctx->p;\n\tint voc;\n\n\tif ((voc = map_virt_channel(p, chn)) < 0) {\n\t\treturn;\n\t}\n\n\tlibxmp_mixer_setnote(ctx, voc, note);\n}\n\nint libxmp_virt_setpatch(struct context_data *ctx, int chn, int ins, int smp,\n\t\t\t int note, int key, int nna, int dct, int dca)\n{\n\tstruct player_data *p = &ctx->p;\n\tint voc, vfree;\n\n\tif ((uint32)chn >= p->virt.virt_channels) {\n\t\treturn -1;\n\t}\n\n\tif (ins < 0) {\n\t\tsmp = -1;\n\t}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\tif (dct) {\n\t\tint i;\n\n\t\tfor (i = 0; i < p->virt.maxvoc; i++) {\n\t\t\tcheck_dct(ctx, i, chn, ins, smp, key, nna, dct, dca);\n\t\t}\n\t}\n#endif\n\n\tvoc = p->virt.virt_channel[chn].map;\n\n\tif (voc > FREE) {\n\t\tif (p->virt.voice_array[voc].act) {\n\t\t\tvfree = alloc_voice(ctx, chn);\n\n\t\t\tif (vfree < 0) {\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tfor (chn = p->virt.num_tracks; chn < p->virt.virt_channels &&\n\t\t\t     p->virt.virt_channel[chn++].map > FREE;) ;\n\n\t\t\tp->virt.voice_array[voc].chn = --chn;\n\t\t\tp->virt.virt_channel[chn].map = voc;\n\t\t\tvoc = vfree;\n\t\t}\n\t} else {\n\t\tvoc = alloc_voice(ctx, chn);\n\t\tif (voc < 0) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tif (smp < 0) {\n\t\tlibxmp_virt_resetvoice(ctx, voc, 1);\n\t\treturn chn;\t/* was -1 */\n\t}\n\n\tlibxmp_mixer_setpatch(ctx, voc, smp, 1);\n\tlibxmp_mixer_setnote(ctx, voc, note);\n\tp->virt.voice_array[voc].ins = ins;\n\tp->virt.voice_array[voc].act = nna;\n\tp->virt.voice_array[voc].key = key;\n\n\treturn chn;\n}\n\nint libxmp_virt_queuepatch(struct context_data *ctx, int chn, int ins, int smp, int note)\n{\n\t/* Protracker 1/2 implements instrument swap in a strange way--the\n\t * volume/finetune take effect immediately but the sample change\n\t * does not apply until the current playing sample reaches the end of\n\t * its loop (or stops, if it's a one-off). */\n\tstruct player_data *p = &ctx->p;\n\tint voc;\n\n\tif ((uint32)chn >= p->virt.virt_channels) {\n\t\treturn -1;\n\t}\n\n\tif (ins < 0) {\n\t\tsmp = -1;\n\t}\n\n\tvoc = p->virt.virt_channel[chn].map;\n\tif (voc > FREE) {\n\t\tlibxmp_mixer_queuepatch(ctx, voc, smp);\n\t\tif (ins >= 0) {\n\t\t\tp->virt.voice_array[voc].ins = ins;\n\t\t}\n\t\treturn chn;\n\t}\n\t/* Original sample stopped--start a new note. */\n\tif (smp < 0) {\n\t\treturn -1;\n\t}\n\treturn libxmp_virt_setpatch(ctx, chn, ins, smp, note, 0, 0, 0, 0);\n}\n\nvoid libxmp_virt_setperiod(struct context_data *ctx, int chn, double period)\n{\n\tstruct player_data *p = &ctx->p;\n\tint voc;\n\n\tif ((voc = map_virt_channel(p, chn)) < 0) {\n\t\treturn;\n\t}\n\n\tlibxmp_mixer_setperiod(ctx, voc, period);\n}\n\nvoid libxmp_virt_voicepos(struct context_data *ctx, int chn, double pos)\n{\n\tstruct player_data *p = &ctx->p;\n\tint voc;\n\n\tif ((voc = map_virt_channel(p, chn)) < 0) {\n\t\treturn;\n\t}\n\n\tlibxmp_mixer_voicepos(ctx, voc, pos, 1);\n}\n\n#ifndef LIBXMP_CORE_DISABLE_IT\n\nvoid libxmp_virt_pastnote(struct context_data *ctx, int chn, int act)\n{\n\tstruct player_data *p = &ctx->p;\n\tint c, voc;\n\n\tfor (c = p->virt.num_tracks; c < p->virt.virt_channels; c++) {\n\t\tif ((voc = map_virt_channel(p, c)) < 0)\n\t\t\tcontinue;\n\n\t\tif (p->virt.voice_array[voc].root == chn) {\n\t\t\tswitch (act) {\n\t\t\tcase VIRT_ACTION_CUT:\n\t\t\t\tlibxmp_virt_resetvoice(ctx, voc, 1);\n\t\t\t\tbreak;\n\t\t\tcase VIRT_ACTION_OFF:\n\t\t\t\tlibxmp_player_set_release(ctx, c);\n\t\t\t\tbreak;\n\t\t\tcase VIRT_ACTION_FADE:\n\t\t\t\tlibxmp_player_set_fadeout(ctx, c);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\n#endif\n\nint libxmp_virt_cstat(struct context_data *ctx, int chn)\n{\n\tstruct player_data *p = &ctx->p;\n\tint voc;\n\n\tif ((voc = map_virt_channel(p, chn)) < 0) {\n\t\treturn VIRT_INVALID;\n\t}\n\n\tif (chn < p->virt.num_tracks) {\n\t\treturn VIRT_ACTIVE;\n\t}\n\n\treturn p->virt.voice_array[voc].act;\n}\n"
  },
  {
    "path": "contrib/libxmp/src/virtual.h",
    "content": "#ifndef LIBXMP_VIRTUAL_H\n#define LIBXMP_VIRTUAL_H\n\n#include \"common.h\"\n\n#define VIRT_ACTION_CUT\t\tXMP_INST_NNA_CUT\n#define VIRT_ACTION_CONT\tXMP_INST_NNA_CONT\n#define VIRT_ACTION_OFF\t\tXMP_INST_NNA_OFF\n#define VIRT_ACTION_FADE\tXMP_INST_NNA_FADE\n\n#define VIRT_ACTIVE\t\t0x100\n#define VIRT_INVALID\t\t-1\n\nint\tlibxmp_virt_on\t\t(struct context_data *, int);\nvoid\tlibxmp_virt_off\t\t(struct context_data *);\nint\tlibxmp_virt_mute\t(struct context_data *, int, int);\nint\tlibxmp_virt_setpatch\t(struct context_data *, int, int, int, int,\n\t\t\t\t int, int, int, int);\nint\tlibxmp_virt_queuepatch\t(struct context_data *, int, int, int, int);\nint\tlibxmp_virt_cvt8bit\t(void);\nvoid\tlibxmp_virt_setnote\t(struct context_data *, int, int);\nvoid\tlibxmp_virt_setsmp\t(struct context_data *, int, int);\nvoid\tlibxmp_virt_setnna\t(struct context_data *, int, int);\nvoid\tlibxmp_virt_pastnote\t(struct context_data *, int, int);\nvoid\tlibxmp_virt_setvol\t(struct context_data *, int, int);\nvoid\tlibxmp_virt_voicepos\t(struct context_data *, int, double);\ndouble\tlibxmp_virt_getvoicepos\t(struct context_data *, int);\nvoid\tlibxmp_virt_setperiod\t(struct context_data *, int, double);\nvoid\tlibxmp_virt_setpan\t(struct context_data *, int, int);\nvoid\tlibxmp_virt_seteffect\t(struct context_data *, int, int, int);\nint\tlibxmp_virt_cstat\t(struct context_data *, int);\nint\tlibxmp_virt_mapchannel\t(struct context_data *, int);\nvoid\tlibxmp_virt_resetchannel(struct context_data *, int);\nvoid\tlibxmp_virt_resetvoice\t(struct context_data *, int, int);\nvoid\tlibxmp_virt_reset\t(struct context_data *);\nvoid\tlibxmp_virt_release\t(struct context_data *, int, int);\nvoid\tlibxmp_virt_reverse\t(struct context_data *, int, int);\nint\tlibxmp_virt_getroot\t(struct context_data *, int);\n\n#endif /* LIBXMP_VIRTUAL_H */\n"
  },
  {
    "path": "contrib/mzvplay/mzvplay.txt",
    "content": ". \"MZV player copyright (C) 2023 Lachesis. This program is licensed\"\n. \"under the Creative Commons Attribution 4.0 (CC BY 4.0) license, and is\"\n. \"provided with NO WARRANTY. The original version of this program is\"\n. \"available in the source code distriution of MegaZeux 2.93. This notice\"\n. \"must remain in all derivatives of this program, and any changes to this\"\n. \"program must be documented following this notice.\"\n\nset \"$mzv_file\" to \"out.mzv\"\nmod \"out.ogg\"\n\nset \"mzx_speed\" to 2\nset \"commands\" to 10000\nplayercolor is c00\ncolor c00\n. \"This audio synchronization wait is configuration and video dependent :(\"\n. \"wait for 4\"\nset \"&$mzv_file&\" to \"fread_open\"\ngoto \"#play\"\nset \"\" to \"fread_open\"\nend mod\nend\n\n. \"Player will track up to time_m units of missed frames.\"\n: \"#time\"\nset \"mzv_time_n\" to \"('time_seconds'*1000+'time_millis')\"\nset \"mzv_time_d\" to 1000\nset \"mzv_time_m\" to \"(60000)\"\nif \"('mzv_time_leap' ? 'mzv_time_n'<'mzv_time_m' : 0)\" = 1 then \"leap\"\nif \"mzv_time_n\" < \"mzv_time_m\" then \"#return\"\nset \"mzv_time_leap\" to 1\ngoto \"#return\"\n: \"leap\"\ndec \"mzv_time_prev\" by \"mzv_time_d\"\nset \"mzv_time_leap\" to 0\ngoto \"#return\"\n\n: \"#vlayer\"\nset \"vlayer_size\" to \"('mzv_vw'*'mzv_vh')\"\nset \"vlayer_width\" to \"mzv_vw\"\n. \"Sprite reference offsets.\"\nset \"mzv_vx\" to 0\nset \"mzv_vy\" to 0\nset \"mzv_vo\" to 256\n. \"Current sprite index.\"\nset \"mzv_idx\" to 0\ngoto \"#return\"\n\n: \"#sprite\"\nset \"spr&mzv_idx&_overlaid\" to 1\ngoto \"#return\"\n\n\n. \"----------------------------------------------------------------------\"\n\n\n: \"#play\"\nset \"$mzv_tmp\" to \"fread4\"\nif \"$mzv_tmp\" === \"MZXV\" then \"play\"\n: \"err\"\ngoto \"#top\"\n: \"play\"\nset \"local\" to \"fread_counter\"\nset \"mzv_time_n\" to 0\nset \"mzv_time_d\" to 1000\ngoto \"#time\"\nset \"mzv_time_prev\" to \"mzv_time_n\"\nset \"mzv_w\" to 0\nset \"mzv_h\" to 0\nset \"mzv_vw\" to 0\nset \"mzv_vh\" to 0\nset \"mzv_sp_x\" to 0\nset \"mzv_sp_y\" to 0\nset \"mzv_sp_w\" to 1\nset \"mzv_sp_h\" to 1\nset \"mzv_sp_u\" to 0\nset \"mzv_sp_o\" to 0\nset \"mzv_sp_t\" to 0\nset \"mzv_mode\" to 0\nset \"mzv_fr_n\" to 125\nset \"mzv_fr_d\" to 6\nset \"mzv_fr_adv\" to 0\nset \"$mzv_chr\" to \"\"\nset \"$mzv_pal\" to \"\"\nset \"$mzv_idx\" to \"\"\nset \"$mzv_mzm\" to \"MZM2\"\nset \"$mzv_mzm.8#4\" to 0\nset \"$mzv_mzm.12#4\" to 0\nset \"$mzv_mzm.13\" to 1\n: \"headers\"\ngoto \"#chunk\"\ngoto \"head:&$mzv_tmp&\"\nif \"mzv_w\" = 0 then \"err\"\nif \"mzv_h\" = 0 then \"err\"\nset \"mzv_vw\" to \"('mzv_vw' ? 'mzv_vw' : 'mzv_w')\"\nset \"mzv_vh\" to \"('mzv_vh' ? 'mzv_vh' : 'mzv_h')\"\ngoto \"#vlayer\"\nset \"$mzv_mzm.4#2\" to \"mzv_w\"\nset \"$mzv_mzm.6#2\" to \"mzv_h\"\nset \"local\" to \"('mzv_sp_w'*'mzv_sp_h'-1)\"\nset \"local4\" to \"('mzv_sp_u'o'mzv_vo'o'mzv_sp_o' ? 1 : 0)\"\nset \"local2\" to \"(('local4' ? -'mzv_w'*8+640/2 : 80-'mzv_w'/2) + 'mzv_sp_x')\"\nset \"local3\" to \"(('local4' ? -'mzv_h'*14+350/2 : 25-'mzv_h'/2) + 'mzv_sp_y')\"\nloop start\nset \"spr&mzv_idx&_width\" to \"mzv_w\"\nset \"spr&mzv_idx&_height\" to \"mzv_h\"\nset \"spr&mzv_idx&_refx\" to \"('loopcount'%'mzv_sp_w'*'mzv_w'+'mzv_vx')\"\nset \"spr&mzv_idx&_refy\" to \"('loopcount'/'mzv_sp_w'*'mzv_h'+'mzv_vy')\"\nset \"spr&mzv_idx&_vlayer\" to 1\nset \"spr&mzv_idx&_unbound\" to \"local4\"\nset \"spr&mzv_idx&_offset\" to \"('loopcount'*'mzv_sp_o'+'mzv_vo')\"\nset \"spr&mzv_idx&_tcol\" to \"('loopcount' ? 'mzv_sp_t' : -1)\"\nput c?? Sprite \"mzv_idx\" at \"('loopcount'%'mzv_sp_w'+'local2')\" \"('loopcount'/'mzv_sp_w'+'local3')\"\ngoto \"#sprite\"\ninc \"mzv_idx\" by 1\nloop for \"local\"\ndec \"fread_pos\" by 8\n: \"frames\"\ngoto \"#chunk\"\ngoto \"frame:&$mzv_tmp&\"\n. \"fallthrough\"\n: \"frame_wait\"\ndec \"mzv_fr_adv\" by 1\nif \"mzv_fr_adv\" > 0 then \"frames\"\n: \"w\"\nwait for 1\ngoto \"#frameadv\"\nif \"mzv_fr_adv\" > 0 then \"frames\"\ngoto \"w\"\n\n: \"#frameadv\"\nset \"mzv_fr_adv\" to 0\ngoto \"#time\"\nset \"local\" to \"('mzv_time_n'-'mzv_time_prev')\"\ninc \"local\" by \"('local'<0 ? 'mzv_time_m' : 0)\"\nset \"local2\" to \"('local'/'mzv_time_d')\"\nset \"local3\" to \"('local'%'mzv_time_d')\"\nset \"local4\" to \"('local3'*'mzv_fr_n'/'mzv_time_d')\"\ninc \"local4\" by \"('local2'*'mzv_fr_n')\"\nset \"mzv_fr_adv\" to \"('local4'/'mzv_fr_d')\"\nif \"mzv_fr_adv\" <= 0 then \"#return\"\n. \"Only consume the time it takes to play exactly mzv_fr_adv frames.\"\nset \"local6\" to \"('mzv_fr_adv'*'mzv_time_d'*'mzv_fr_d'/'mzv_fr_n'-'local')\"\nset \"mzv_time_prev\" to \"('mzv_time_n'+'local6')\"\ngoto \"#return\"\n\n: \"#chunk\"\nset \"$mzv_tmp\" to \"fread4\"\nset \"mzv_sz\" to \"fread_counter\"\nif \"mzv_sz\" < 0 then \"err\"\ngoto \"#return\"\n\n: \"head:fwid\"\nif \"mzv_sz\" < 4 then \"err\"\nset \"mzv_w\" to \"fread_counter\"\nif \"mzv_w\" < 0 then \"err\"\nif \"mzv_w\" > 32767 then \"err\"\ninc \"fread_pos\" by \"('mzv_sz'-4)\"\ngoto \"headers\"\n\n: \"head:fhei\"\nif \"mzv_sz\" < 4 then \"err\"\nset \"mzv_h\" to \"fread_counter\"\nif \"mzv_h\" < 0 then \"err\"\nif \"mzv_h\" > 32767 then \"err\"\ninc \"fread_pos\" by \"('mzv_sz'-4)\"\ngoto \"headers\"\n\n: \"head:bwid\"\nif \"mzv_sz\" < 4 then \"err\"\nset \"mzv_vw\" to \"fread_counter\"\nif \"mzv_vw\" < 0 then \"err\"\nif \"mzv_vw\" > 32767 then \"err\"\ninc \"fread_pos\" by \"('mzv_sz'-4)\"\ngoto \"headers\"\n\n: \"head:bhei\"\nif \"mzv_sz\" < 4 then \"err\"\nset \"mzv_vh\" to \"fread_counter\"\nif \"mzv_vh\" < 0 then \"err\"\nif \"mzv_vh\" > 32767 then \"err\"\ninc \"fread_pos\" by \"('mzv_sz'-4)\"\ngoto \"headers\"\n\n: \"head:sprx\"\nif \"mzv_sz\" < 4 then \"err\"\nset \"mzv_sp_x\" to \"fread_counter\"\ninc \"fread_pos\" by \"('mzv_sz'-4)\"\ngoto \"headers\"\n\n: \"head:spry\"\nif \"mzv_sz\" < 4 then \"err\"\nset \"mzv_sp_y\" to \"fread_counter\"\ninc \"fread_pos\" by \"('mzv_sz'-4)\"\ngoto \"headers\"\n\n: \"head:sprw\"\nif \"mzv_sz\" < 4 then \"err\"\nset \"mzv_sp_w\" to \"fread_counter\"\nif \"mzv_sp_w\" < 0 then \"err\"\ninc \"fread_pos\" by \"('mzv_sz'-4)\"\ngoto \"headers\"\n\n: \"head:sprh\"\nif \"mzv_sz\" < 4 then \"err\"\nset \"mzv_sp_h\" to \"fread_counter\"\nif \"mzv_sp_h\" < 0 then \"err\"\ninc \"fread_pos\" by \"('mzv_sz'-4)\"\ngoto \"headers\"\n\n: \"head:spru\"\nif \"mzv_sz\" < 4 then \"err\"\nset \"mzv_sp_u\" to \"fread_counter\"\nif \"mzv_sp_u\" < 0 then \"err\"\nif \"mzv_sp_u\" > 1 then \"err\"\ninc \"fread_pos\" by \"('mzv_sz'-4)\"\ngoto \"headers\"\n\n: \"head:spro\"\nif \"mzv_sz\" < 4 then \"err\"\nset \"mzv_sp_o\" to \"fread_counter\"\ninc \"fread_pos\" by \"('mzv_sz'-4)\"\ngoto \"headers\"\n\n: \"head:sprt\"\nif \"mzv_sz\" < 4 then \"err\"\nset \"mzv_sp_t\" to \"fread_counter\"\nif \"mzv_sp_t\" < -1 then \"err\"\nif \"mzv_sp_t\" >= 256 then \"err\"\ninc \"fread_pos\" by \"('mzv_sz'-4)\"\ngoto \"headers\"\n\n: \"frame:smzx\"\nif \"mzv_sz\" < 4 then \"err\"\nset \"mzv_mode\" to \"fread_counter\"\nif \"mzv_mode\" < 0 then \"err\"\nif \"mzv_mode\" > 3 then \"err\"\ninc \"fread_pos\" by \"('mzv_sz'-4)\"\nset \"smzx_mode\" to \"mzv_mode\"\nload palette \"$mzv_pal\"\nif \"mzv_mode\" != 3 then \"frames\"\nset \"$mzv_idx\" to \"smzx_indices\"\ngoto \"frames\"\n\n: \"frame:rate\"\nif \"mzv_sz\" < 8 then \"err\"\nset \"mzv_fr_n\" to \"fread_counter\"\nset \"mzv_fr_d\" to \"fread_counter\"\nif \"mzv_fr_n\" < 0 then \"err\"\nif \"mzv_fr_d\" <= 0 then \"err\"\ninc \"fread_pos\" by \"('mzv_sz'-8)\"\ngoto \"frames\"\n\n: \"frame:fchr\"\nset \"$mzv_chr\" to \"fread&mzv_sz&\"\nload char set \"@&mzv_vo&$mzv_chr\"\ngoto \"frames\"\n\n: \"frame:fpal\"\nset \"$mzv_pal\" to \"fread&mzv_sz&\"\nload palette \"$mzv_pal\"\ngoto \"frames\"\n\n: \"frame:fidx\"\nset \"$mzv_idx\" to \"fread&mzv_sz&\"\nset \"$mzv_idx\" to \"smzx_indices\"\ngoto \"frames\"\n\n: \"frame:f1ch\"\nset \"$mzv_tmp\" to \"fread&mzv_sz&\"\nset \"local\" to \"('mzv_w'*'mzv_h'-1)\"\nloop start\nset \"$mzv_mzm.('loopcount'<<1+16)\" to \"$mzv_tmp.&loopcount&\"\nloop for \"local\"\ngoto \"mzm\"\n\n: \"frame:f1co\"\nset \"$mzv_tmp\" to \"fread&mzv_sz&\"\nset \"local\" to \"('mzv_w'*'mzv_h'-1)\"\nloop start\nset \"$mzv_mzm.('loopcount'<<1+17)\" to \"$mzv_tmp.&loopcount&\"\nloop for \"local\"\ngoto \"mzm\"\n\n: \"frame:f2in\"\nset \"$mzv_mzm+16\" to \"fread&mzv_sz&\"\n: \"mzm\"\nset \"$mzv_mzm.length\" to \"('mzv_w'*'mzv_h'*2+16)\"\nput \"@$mzv_mzm\" Image_file p02 at \"mzv_vx\" \"mzv_vy\"\ngoto \"frame_wait\"\n"
  },
  {
    "path": "contrib/patches/libmodplug/01-libmodplug-0.8.9.0-fix-looping.diff",
    "content": "diff -Nudr -U3 libmodplug.really-pristine/src/sndmix.cpp libmodplug.pristine/src/sndmix.cpp\n--- libmodplug.really-pristine/src/sndmix.cpp\t2009-04-20 06:00:54.000000000 +0100\n+++ libmodplug.pristine/src/sndmix.cpp\t2009-12-31 15:47:12.000000000 +0000\n@@ -365,7 +365,7 @@\n \t\t\t\t// End of song ?\n \t\t\t\tif ((m_nPattern == 0xFF) || (m_nCurrentPattern >= MAX_ORDERS))\n \t\t\t\t{\n-\t\t\t\t\t//if (!m_nRepeatCount)\n+\t\t\t\t\tif (!m_nRepeatCount)\n \t\t\t\t\t\treturn FALSE;     //never repeat entire song\n \t\t\t\t\tif (!m_nRestartPos)\n \t\t\t\t\t{\n@@ -398,7 +398,7 @@\n \t\t\t\t\t\t\t}\n \t\t\t\t\t\t}\n \t\t\t\t\t}\n-//\t\t\t\t\tif (m_nRepeatCount > 0) m_nRepeatCount--;\n+\t\t\t\t\tif (m_nRepeatCount > 0) m_nRepeatCount--;\n \t\t\t\t\tm_nCurrentPattern = m_nRestartPos;\n \t\t\t\t\tm_nRow = 0;\n \t\t\t\t\tif ((Order[m_nCurrentPattern] >= MAX_PATTERNS) || (!Patterns[Order[m_nCurrentPattern]])) return FALSE;\n"
  },
  {
    "path": "contrib/patches/libmodplug/02-libmodplug-0.8.9.0-fix-X86_Convert32To24.diff",
    "content": "--- libmodplug-0.8.7/src/fastmix.cpp\t2009-04-21 15:03:19.000000000 +0100\n+++ libmodplug-0.8.7-patched/src/fastmix.cpp\t2010-06-27 21:19:51.008963769 +0100\n@@ -1887,13 +1887,13 @@\n \t\t\tvumax = n;\n \t\tp = n >> (8-MIXING_ATTENUATION) ; // 24-bit signed\n #ifdef WORDS_BIGENDIAN\n-\t\tbuf[i*3+0] = p & 0xFF0000 >> 24;\n-\t\tbuf[i*3+1] = p & 0x00FF00 >> 16 ;\n-\t\tbuf[i*3+2] = p & 0x0000FF ;\n+\t\tbuf[i*3+0] = (p >> 16) & 0xFF;\n+\t\tbuf[i*3+1] = (p >> 8)  & 0xFF;\n+\t\tbuf[i*3+2] = (p >> 0)  & 0xFF;\n #else\n-\t\tbuf[i*3+0] = p & 0x0000FF ;\n-\t\tbuf[i*3+1] = p & 0x00FF00 >> 16;\n-\t\tbuf[i*3+2] = p & 0xFF0000 >> 24;\n+\t\tbuf[i*3+0] = (p >> 0)  & 0xFF;\n+\t\tbuf[i*3+1] = (p >> 8)  & 0xFF;\n+\t\tbuf[i*3+2] = (p >> 16) & 0xFF;\n #endif\n \t}\n \t*lpMin = vumin;\n"
  },
  {
    "path": "contrib/patches/libmodplug/03-libmodplug-0.8.9.0-if0-dead-code-make-globals-static.diff",
    "content": "diff -Nrup _src/fastmix.cpp src/fastmix.cpp\n--- _src/fastmix.cpp\t2017-05-24 19:12:03.728797917 +0200\n+++ src/fastmix.cpp\t2017-05-24 19:19:45.040811114 +0200\n@@ -568,7 +568,7 @@ CzWINDOWEDFIR sfir;\n typedef VOID (MPPASMCALL * LPMIXINTERFACE)(MODCHANNEL *, int *, int *);\n \n #define BEGIN_MIX_INTERFACE(func)\\\n-\tVOID MPPASMCALL func(MODCHANNEL *pChannel, int *pbuffer, int *pbufmax)\\\n+\tstatic VOID MPPASMCALL func(MODCHANNEL *pChannel, int *pbuffer, int *pbufmax)\\\n \t{\\\n \t\tLONG nPos;\n \n@@ -659,8 +659,8 @@ typedef VOID (MPPASMCALL * LPMIXINTERFAC\n /////////////////////////////////////////////////////\n //\n \n-void MPPASMCALL X86_InitMixBuffer(int *pBuffer, UINT nSamples);\n-void MPPASMCALL X86_EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples);\n+static void MPPASMCALL X86_InitMixBuffer(int *pBuffer, UINT nSamples);\n+static void MPPASMCALL X86_EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples);\n void MPPASMCALL X86_StereoFill(int *pBuffer, UINT nSamples, LPLONG lpROfs, LPLONG lpLOfs);\n void X86_StereoMixToFloat(const int *, float *, float *, UINT nCount);\n void X86_FloatToStereoMix(const float *pIn1, const float *pIn2, int *pOut, UINT nCount);\n@@ -794,6 +794,8 @@ BEGIN_MIX_INTERFACE(FastMono16BitLinearM\n \tSNDMIX_STOREFASTMONOVOL\n END_MIX_INTERFACE()\n \n+#ifdef MODPLUG_DEADCODE\n+\n BEGIN_MIX_INTERFACE(FastMono8BitSplineMix)\n \tSNDMIX_BEGINSAMPLELOOP8\n \tSNDMIX_GETMONOVOL8SPLINE\n@@ -818,6 +820,7 @@ BEGIN_MIX_INTERFACE(FastMono16BitFirFilt\n \tSNDMIX_STOREFASTMONOVOL\n END_MIX_INTERFACE()\n \n+#endif // MODPLUG_DEADCODE\n \n // Fast Ramps\n BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitRampMix)\n@@ -844,6 +847,8 @@ BEGIN_FASTRAMPMIX_INTERFACE(FastMono16Bi\n \tSNDMIX_RAMPFASTMONOVOL\n END_FASTRAMPMIX_INTERFACE()\n \n+#ifdef MODPLUG_DEADCODE\n+\n BEGIN_FASTRAMPMIX_INTERFACE(FastMono8BitSplineRampMix)\n \tSNDMIX_BEGINSAMPLELOOP8\n \tSNDMIX_GETMONOVOL8SPLINE\n@@ -868,6 +873,7 @@ BEGIN_FASTRAMPMIX_INTERFACE(FastMono16Bi\n \tSNDMIX_RAMPFASTMONOVOL\n END_FASTRAMPMIX_INTERFACE()\n \n+#endif // MODPLUG_DEADCODE\n \n //////////////////////////////////////////////////////\n // Stereo samples\n@@ -1986,7 +1992,7 @@ DWORD MPPASMCALL X86_Convert32To32(LPVOI\n \n \n #ifdef MSC_VER\n-void MPPASMCALL X86_InitMixBuffer(int *pBuffer, UINT nSamples)\n+static void MPPASMCALL X86_InitMixBuffer(int *pBuffer, UINT nSamples)\n //------------------------------------------------------------\n {\n \t_asm {\n@@ -2020,7 +2026,7 @@ done:;\n #else\n //---GCCFIX: Asm replaced with C function\n // Will fill in later.\n-void MPPASMCALL X86_InitMixBuffer(int *pBuffer, UINT nSamples)\n+static void MPPASMCALL X86_InitMixBuffer(int *pBuffer, UINT nSamples)\n {\n \tmemset(pBuffer, 0, nSamples * sizeof(int));\n }\n@@ -2208,7 +2214,7 @@ void MPPASMCALL X86_StereoFill(int *pBuf\n #endif\n \n #ifdef MSC_VER\n-void MPPASMCALL X86_EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples)\n+static void MPPASMCALL X86_EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples)\n //------------------------------------------------------------------------------\n {\n \t_asm {\n@@ -2251,7 +2257,7 @@ brkloop:\n #else\n //---GCCFIX: Asm replaced with C function\n // Will fill in later.\n-void MPPASMCALL X86_EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples)\n+static void MPPASMCALL X86_EndChannelOfs(MODCHANNEL *pChannel, int *pBuffer, UINT nSamples)\n {\n \tint rofs = pChannel->nROfs;\n \tint lofs = pChannel->nLOfs;\ndiff -Nrup _src/libmodplug/it_defs.h src/libmodplug/it_defs.h\n--- _src/libmodplug/it_defs.h\t2017-05-24 19:12:03.713797917 +0200\n+++ src/libmodplug/it_defs.h\t2017-05-24 19:14:01.554801288 +0200\n@@ -128,7 +128,4 @@ typedef struct ITSAMPLESTRUCT\n \n #pragma pack()\n \n-extern BYTE autovibit2xm[8];\n-extern BYTE autovibxm2it[8];\n-\n #endif\ndiff -Nrup _src/libmodplug/stdafx.h src/libmodplug/stdafx.h\n--- _src/libmodplug/stdafx.h\t2017-05-24 19:12:03.713797917 +0200\n+++ src/libmodplug/stdafx.h\t2017-05-24 19:14:23.382801912 +0200\n@@ -88,7 +88,7 @@ typedef const char* LPCSTR;\n typedef void* PVOID;\n typedef void VOID;\n \n-inline LONG MulDiv (long a, long b, long c)\n+static inline LONG MulDiv (long a, long b, long c)\n {\n /*if (!c) return 0;*/\n   return ((uint64_t) a * (uint64_t) b ) / c;\n@@ -104,7 +104,7 @@ inline LONG MulDiv (long a, long b, long\n \n #define  GHND   0\n #define GlobalFreePtr(p) free((void *)(p))\n-inline int8_t * GlobalAllocPtr(unsigned int, size_t size)\n+static inline int8_t * GlobalAllocPtr(unsigned int, size_t size)\n {\n   int8_t * p = (int8_t *) malloc(size);\n \n@@ -112,7 +112,7 @@ inline int8_t * GlobalAllocPtr(unsigned\n   return p;\n }\n \n-inline void ProcessPlugins(int n) {}\n+static inline void ProcessPlugins(int n) {}\n \n #ifndef FALSE\n #define FALSE\tfalse\ndiff -Nrup _src/load_amf.cpp src/load_amf.cpp\n--- _src/load_amf.cpp\t2017-05-24 19:12:03.726797917 +0200\n+++ src/load_amf.cpp\t2017-05-24 19:14:35.629802263 +0200\n@@ -52,7 +52,7 @@ typedef struct _AMFSAMPLE\n extern void Log(LPCSTR, ...);\n #endif\n \n-VOID AMF_Unpack(MODCOMMAND *pPat, const BYTE *pTrack, UINT nRows, UINT nChannels)\n+static VOID AMF_Unpack(MODCOMMAND *pPat, const BYTE *pTrack, UINT nRows, UINT nChannels)\n //-------------------------------------------------------------------------------\n {\n \tUINT lastinstr = 0;\ndiff -Nrup _src/load_it.cpp src/load_it.cpp\n--- _src/load_it.cpp\t2017-05-24 19:12:03.728797917 +0200\n+++ src/load_it.cpp\t2017-05-24 19:15:10.155803250 +0200\n@@ -15,11 +15,13 @@\n #pragma warning(disable:4244)\n #endif\n \n-BYTE autovibit2xm[8] =\n+static BYTE autovibit2xm[8] =\n { 0, 3, 1, 4, 2, 0, 0, 0 };\n \n-BYTE autovibxm2it[8] =\n+#ifndef MODPLUG_NO_FILESAVE\n+static BYTE autovibxm2it[8] =\n { 0, 2, 4, 1, 3, 0, 0, 0 };\n+#endif\n \n //////////////////////////////////////////////////////////\n // Impulse Tracker IT file support\n@@ -1181,7 +1183,7 @@ BOOL CSoundFile::SaveIT(LPCSTR lpszFileN\n //////////////////////////////////////////////////////////////////////////////\n // IT 2.14 compression\n \n-DWORD ITReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n)\n+static DWORD ITReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n)\n //-----------------------------------------------------------------\n {\n \tDWORD retval = 0;\ndiff -Nrup _src/load_mod.cpp src/load_mod.cpp\n--- _src/load_mod.cpp\t2017-05-24 19:12:03.725797917 +0200\n+++ src/load_mod.cpp\t2017-05-24 19:20:23.181812205 +0200\n@@ -39,7 +39,8 @@ void CSoundFile::ConvertModCommand(MODCO\n \tcase 0x0D:\tcommand = CMD_PATTERNBREAK; param = ((param >> 4) * 10) + (param & 0x0F); break;\n \tcase 0x0E:\tcommand = CMD_MODCMDEX; break;\n \tcase 0x0F:\tcommand = (param <= (UINT)((m_nType & (MOD_TYPE_XM|MOD_TYPE_MT2)) ? 0x1F : 0x20)) ? CMD_SPEED : CMD_TEMPO;\n-\t\t\t\tif ((param == 0xFF) && (m_nSamples == 15)) command = 0; break;\n+\t\t\t\tif ((param == 0xFF) && (m_nSamples == 15)) command = 0;\n+\t\t\t\tbreak;\n \t// Extension for XM extended effects\n \tcase 'G' - 55:\tcommand = CMD_GLOBALVOLUME; break;\n \tcase 'H' - 55:\tcommand = CMD_GLOBALVOLSLIDE; if (param & 0xF0) param &= 0xF0; break;\n@@ -184,7 +185,7 @@ static BOOL IsValidName(LPCSTR s, int le\n \treturn TRUE;\n }\n \n-BOOL IsMagic(LPCSTR s1, LPCSTR s2)\n+static BOOL IsMagic(LPCSTR s1, LPCSTR s2)\n {\n \treturn ((*(DWORD *)s1) == (*(DWORD *)s2)) ? TRUE : FALSE;\n }\ndiff -Nrup _src/mmcmp.cpp src/mmcmp.cpp\n--- _src/mmcmp.cpp\t2017-05-24 19:12:03.727797917 +0200\n+++ src/mmcmp.cpp\t2017-05-24 19:15:42.755804183 +0200\n@@ -8,7 +8,7 @@\n #include \"stdafx.h\"\n #include \"sndfile.h\"\n \n-BOOL PP20_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength);\n+static BOOL PP20_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength);\n \n #pragma pack(1)\n typedef struct MMCMPFILEHEADER\n@@ -482,7 +482,7 @@ static VOID PP20_DoUnpack(const BYTE *pS\n }\n \n \n-BOOL PP20_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength)\n+static BOOL PP20_Unpack(LPCBYTE *ppMemFile, LPDWORD pdwMemLength)\n {\n \tDWORD dwMemLength = *pdwMemLength;\n \tLPCBYTE lpMemFile = *ppMemFile;\ndiff -Nrup _src/modplug.cpp src/modplug.cpp\n--- _src/modplug.cpp\t2017-05-24 19:12:03.717797917 +0200\n+++ src/modplug.cpp\t2017-05-24 19:16:23.480805348 +0200\n@@ -15,7 +15,7 @@ struct _ModPlugFile\n \n namespace ModPlug\n {\n-\tModPlug_Settings gSettings =\n+\tstatic ModPlug_Settings gSettings =\n \t{\n \t\tMODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION,\n \n@@ -35,9 +35,9 @@ namespace ModPlug\n \t\t0\n \t};\n \n-\tint gSampleSize;\n+\tstatic int gSampleSize;\n \n-\tvoid UpdateSettings(bool updateBasicConfig)\n+\tstatic void UpdateSettings(bool updateBasicConfig)\n \t{\n \t\tif(gSettings.mFlags & MODPLUG_ENABLE_REVERB)\n \t\t{\n@@ -108,6 +108,8 @@ int ModPlug_Read(ModPlugFile* file, void\n \treturn file->mSoundFile.Read(buffer, size) * ModPlug::gSampleSize;\n }\n \n+#ifdef MODPLUG_DEADCODE\n+\n const char* ModPlug_GetName(ModPlugFile* file)\n {\n \treturn file->mSoundFile.GetTitle();\n@@ -118,6 +120,8 @@ int ModPlug_GetLength(ModPlugFile* file)\n \treturn file->mSoundFile.GetSongTime() * 1000;\n }\n \n+#endif // MODPLUG_DEADCODE\n+\n void ModPlug_InitMixerCallback(ModPlugFile* file,ModPlugMixerProc proc)\n {\n \tfile->mSoundFile.gpSndMixHook = (LPSNDMIXHOOKPROC)proc ;\ndiff -Nrup _src/modplug.h src/modplug.h\n--- _src/modplug.h\t2017-05-24 19:12:03.715797917 +0200\n+++ src/modplug.h\t2017-05-24 19:17:20.606806982 +0200\n@@ -51,6 +51,8 @@ MODPLUG_EXPORT void ModPlug_Unload(ModPl\n  * of the mod has been reached, zero is returned. */\n MODPLUG_EXPORT int  ModPlug_Read(ModPlugFile* file, void* buffer, int size);\n \n+#ifdef MODPLUG_DEADCODE\n+\n /* Get the name of the mod.  The returned buffer is stored within the ModPlugFile\n  * structure and will remain valid until you unload the file. */\n MODPLUG_EXPORT const char* ModPlug_GetName(ModPlugFile* file);\n@@ -67,6 +69,8 @@ MODPLUG_EXPORT int ModPlug_GetLength(Mod\n  * ModPlug_GetLength() does not report the full length. */\n MODPLUG_EXPORT void ModPlug_Seek(ModPlugFile* file, int millisecond);\n \n+#endif // MODPLUG_DEADCODE\n+\n enum _ModPlug_Flags\n {\n \tMODPLUG_ENABLE_OVERSAMPLING     = 1 << 0,  /* Enable oversampling (*highly* recommended) */\n"
  },
  {
    "path": "contrib/patches/libmodplug/04-libmodplug-0.8.9.0-remove-unused-loaders.diff",
    "content": "diff -Nudr -U3 libmodplug-0.8.8.1.pre/src/sndfile.cpp libmodplug-0.8.8.1/src/sndfile.cpp\n--- libmodplug-0.8.8.1.pre/src/sndfile.cpp\t2010-05-10 01:33:03.000000000 +0100\n+++ libmodplug-0.8.8.1/src/sndfile.cpp\t2010-06-27 21:38:14.882964688 +0100\n@@ -16,9 +16,11 @@\n #endif\n \n // External decompressors\n+#if 0\n extern void AMSUnpack(const char *psrc, UINT inputlen, char *pdest, UINT dmax, char packcharacter);\n extern WORD MDLReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n);\n extern int DMFUnpack(LPBYTE psample, LPBYTE ibuf, LPBYTE ibufmax, UINT maxlen);\n+#endif\n extern DWORD ITReadBits(DWORD &bitbuf, UINT &bitnum, LPBYTE &ibuf, CHAR n);\n extern void ITUnpack8Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215);\n extern void ITUnpack16Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwMemLength, BOOL b215);\n@@ -142,26 +144,26 @@\n \t\t && (!ReadWav(lpStream, dwMemLength))\n #ifndef MODPLUG_BASIC_SUPPORT\n /* Sequencer File Format Support */\n-\t\t && (!ReadABC(lpStream, dwMemLength))\n-\t\t && (!ReadMID(lpStream, dwMemLength))\n-\t\t && (!ReadPAT(lpStream, dwMemLength))\n+\t\t //&& (!ReadABC(lpStream, dwMemLength))\n+\t\t //&& (!ReadMID(lpStream, dwMemLength))\n+\t\t //&& (!ReadPAT(lpStream, dwMemLength))\n \t\t && (!ReadSTM(lpStream, dwMemLength))\n \t\t && (!ReadMed(lpStream, dwMemLength))\n \t\t && (!ReadMTM(lpStream, dwMemLength))\n-\t\t && (!ReadMDL(lpStream, dwMemLength))\n-\t\t && (!ReadDBM(lpStream, dwMemLength))\n+\t\t //&& (!ReadMDL(lpStream, dwMemLength))\n+\t\t //&& (!ReadDBM(lpStream, dwMemLength))\n \t\t && (!Read669(lpStream, dwMemLength))\n \t\t && (!ReadFAR(lpStream, dwMemLength))\n-\t\t && (!ReadAMS(lpStream, dwMemLength))\n+\t\t //&& (!ReadAMS(lpStream, dwMemLength))\n \t\t && (!ReadOKT(lpStream, dwMemLength))\n-\t\t && (!ReadPTM(lpStream, dwMemLength))\n+\t\t //&& (!ReadPTM(lpStream, dwMemLength))\n \t\t && (!ReadUlt(lpStream, dwMemLength))\n-\t\t && (!ReadDMF(lpStream, dwMemLength))\n+\t\t //&& (!ReadDMF(lpStream, dwMemLength))\n \t\t && (!ReadDSM(lpStream, dwMemLength))\n-\t\t && (!ReadUMX(lpStream, dwMemLength))\n+\t\t //&& (!ReadUMX(lpStream, dwMemLength))\n \t\t && (!ReadAMF(lpStream, dwMemLength))\n-\t\t && (!ReadPSM(lpStream, dwMemLength))\n-\t\t && (!ReadMT2(lpStream, dwMemLength))\n+\t\t //&& (!ReadPSM(lpStream, dwMemLength))\n+\t\t //&& (!ReadMT2(lpStream, dwMemLength))\n #endif // MODPLUG_BASIC_SUPPORT\n \t\t && (!ReadMod(lpStream, dwMemLength))) m_nType = MOD_TYPE_NONE;\n #ifdef MMCMP_SUPPORT\n@@ -1352,6 +1354,7 @@\n \t\t}\n \t\tbreak;\n \n+#if 0\n \t// AMS compressed samples\n \tcase RS_AMS8:\n \tcase RS_AMS16:\n@@ -1440,6 +1443,7 @@\n \t\t\tlen = DMFUnpack((LPBYTE)pIns->pSample, ibuf, ibufmax, maxlen);\n \t\t}\n \t\tbreak;\n+#endif\n \n #ifdef MODPLUG_TRACKER\n \t// PCM 24-bit signed -> load sample, and normalize it to 16-bit\n"
  },
  {
    "path": "contrib/patches/libmodplug/05-libmodplug-0.8.9.0-fix-type-punning-warnings.diff",
    "content": "diff -Nudr -U3 libmodplug-0.8.8.1.pre/src/sndfile.cpp libmodplug-0.8.8.1/src/sndfile.cpp\n--- libmodplug-0.8.8.1.pre/src/sndfile.cpp\t2010-06-27 21:38:14.882964688 +0100\n+++ libmodplug-0.8.8.1/src/sndfile.cpp\t2010-06-27 21:41:13.223963909 +0100\n@@ -940,12 +940,12 @@\n \t\t\t\tif (nFlags == RS_PCM16D)\n \t\t\t\t{\n \t\t\t\t\tint16_t temp = bswapLE16((int16_t)(s_new - s_old));\n-\t\t\t\t\t*((int16_t*)(&buffer[bufcount])) = temp;\n+\t\t\t\t\tmemcpy(&buffer[bufcount], &temp, sizeof(int16_t));\n \t\t\t\t\ts_old = s_new;\n \t\t\t\t} else\n \t\t\t\t{\n \t\t\t\t\tint16_t temp = bswapLE16((int16_t)(s_new + s_ofs));\n-\t\t\t\t\t*((int16_t *)(&buffer[bufcount])) = temp;\n+\t\t\t\t\tmemcpy(&buffer[bufcount], &temp, sizeof(int16_t));\n \t\t\t\t}\n \t\t\t\tbufcount += 2;\n \t\t\t\tif (bufcount >= sizeof(buffer) - 1)\n@@ -1014,12 +1014,12 @@\n \t\t\t\t\tif (nFlags == RS_STPCM16D)\n \t\t\t\t\t{\n \t\t\t\t\t\tint16_t temp = bswapLE16((int16_t)(s_new - s_old));\n-\t\t\t\t\t\t*((int16_t *)(&buffer[bufcount])) = temp;\n+\t\t\t\t\t\tmemcpy(&buffer[bufcount], &temp, sizeof(int16_t));\n \t\t\t\t\t\ts_old = s_new;\n \t\t\t\t\t} else\n \t\t\t\t\t{\n \t\t\t\t\t\tint16_t temp = bswapLE16((int16_t)(s_new - s_ofs));\n-\t\t\t\t\t\t*((int16_t*)(&buffer[bufcount])) = temp;\n+\t\t\t\t\t\tmemcpy(&buffer[bufcount], &temp, sizeof(int16_t));\n \t\t\t\t\t}\n \t\t\t\t\tbufcount += 2;\n \t\t\t\t\tif (bufcount >= sizeof(buffer))\n"
  },
  {
    "path": "contrib/patches/libmodplug/06-libmodplug-0.8.9.0-remove-unused-defines.diff",
    "content": "diff -Nudr -U3 libmodplug-0.8.8.1.pre//src/fastmix.cpp libmodplug-0.8.8.1/src/fastmix.cpp\n--- libmodplug-0.8.8.1.pre//src/fastmix.cpp\t2010-06-27 21:35:39.369214419 +0100\n+++ libmodplug-0.8.8.1/src/fastmix.cpp\t2010-06-27 21:44:03.545964479 +0100\n@@ -94,7 +94,6 @@\n #define SPLINE_CLAMPFORUNITY\n // log2(number) of precalculated splines (range is [4..14])\n #define SPLINE_FRACBITS 10\n-#define SPLINE_LUTLEN (1L<<SPLINE_FRACBITS)\n \n class CzCUBICSPLINE\n {\tpublic:\n@@ -168,7 +167,6 @@\n // number of samples in window\n #define WFIR_LOG2WIDTH\t\t3\n #define WFIR_WIDTH\t\t\t(1L<<WFIR_LOG2WIDTH)\n-#define WFIR_SMPSPERWING\t((WFIR_WIDTH-1)>>1)\n // cutoff (1.0 == pi/2)\n #define WFIR_CUTOFF\t\t0.90f\n // wfir type\n@@ -186,7 +184,6 @@\n #define M_zPI\t\t3.1415926535897932384626433832795\n #endif\n #define M_zEPS\t\t1e-8\n-#define M_zBESSELEPS\t1e-21\n \n class CzWINDOWEDFIR\n {\t\n@@ -309,9 +306,6 @@\n \tpChn->nPos += nPos >> 16;\\\n \tpChn->nPosLo = nPos & 0xFFFF;\n \n-#define SNDMIX_ENDSAMPLELOOP8\tSNDMIX_ENDSAMPLELOOP\n-#define SNDMIX_ENDSAMPLELOOP16\tSNDMIX_ENDSAMPLELOOP\n-\n //////////////////////////////////////////////////////////////////////////////\n // Mono\n \n@@ -2118,9 +2112,6 @@\n }\n #endif\n \n-#define OFSDECAYSHIFT\t8\n-#define OFSDECAYMASK\t0xFF\n-\n \n #ifdef MSC_VER\n void MPPASMCALL X86_StereoFill(int *pBuffer, UINT nSamples, LPLONG lpROfs, LPLONG lpLOfs)\ndiff -Nudr -U3 libmodplug-0.8.8.1.pre//src/load_med.cpp libmodplug-0.8.8.1/src/load_med.cpp\n--- libmodplug-0.8.8.1.pre//src/load_med.cpp\t2010-04-04 13:15:24.000000000 +0100\n+++ libmodplug-0.8.8.1/src/load_med.cpp\t2010-06-27 21:44:03.558214044 +0100\n@@ -18,44 +18,22 @@\n // OctaMed MED file support (import only)\n \n // flags\n-#define\tMMD_FLAG_FILTERON\t0x1\n-#define\tMMD_FLAG_JUMPINGON\t0x2\n-#define\tMMD_FLAG_JUMP8TH\t0x4\n-#define\tMMD_FLAG_INSTRSATT\t0x8 // instruments are attached (this is a module)\n #define\tMMD_FLAG_VOLHEX\t\t0x10\n-#define MMD_FLAG_STSLIDE\t0x20 // SoundTracker mode for slides\n #define MMD_FLAG_8CHANNEL\t0x40 // OctaMED 8 channel song\n-#define\tMMD_FLAG_SLOWHQ\t\t0x80 // HQ slows playing speed (V2-V4 compatibility)\n // flags2\n #define MMD_FLAG2_BMASK\t\t0x1F\n #define MMD_FLAG2_BPM\t\t0x20\n-#define\tMMD_FLAG2_MIX\t\t0x80 // uses Mixing (V7+)\n-// flags3:\n-#define\tMMD_FLAG3_STEREO\t0x1\t// mixing in Stereo mode\n-#define\tMMD_FLAG3_FREEPAN\t0x2\t// free panning\n-#define MMD_FLAG3_GM\t\t0x4 // module designed for GM/XG compatibility\n \n \n // generic MMD tags\n #define\tMMDTAG_END\t\t0\n #define\tMMDTAG_PTR\t\t0x80000000\t// data needs relocation\n-#define\tMMDTAG_MUSTKNOW\t0x40000000\t// loader must fail if this isn't recognized\n-#define\tMMDTAG_MUSTWARN\t0x20000000\t// loader must warn if this isn't recognized\n \n // ExpData tags\n // # of effect groups, including the global group (will\n // override settings in MMDSong struct), default = 1\n-#define\tMMDTAG_EXP_NUMFXGROUPS\t1\n #define\tMMDTAG_TRK_NAME\t\t(MMDTAG_PTR|1)\t// trackinfo tags\n #define\tMMDTAG_TRK_NAMELEN\t2\t\t\t\t// namelen includes zero term.\n-#define\tMMDTAG_TRK_FXGROUP\t3\n-// effectinfo tags\n-#define\tMMDTAG_FX_ECHOTYPE\t1\n-#define MMDTAG_FX_ECHOLEN\t2\n-#define\tMMDTAG_FX_ECHODEPTH\t3\n-#define\tMMDTAG_FX_STEREOSEP\t4\n-#define\tMMDTAG_FX_GROUPNAME\t(MMDTAG_PTR|5)\t// the Global Effects group shouldn't have name saved!\n-#define\tMMDTAG_FX_GRPNAMELEN 6\t// namelen includes zero term.\n \n #pragma pack(1)\n \ndiff -Nudr -U3 libmodplug-0.8.8.1.pre//src/mmcmp.cpp libmodplug-0.8.8.1/src/mmcmp.cpp\n--- libmodplug-0.8.8.1.pre//src/mmcmp.cpp\t2010-06-27 21:35:39.553964080 +0100\n+++ libmodplug-0.8.8.1/src/mmcmp.cpp\t2010-06-27 21:44:03.636028886 +0100\n@@ -46,9 +46,7 @@\n #define MMCMP_COMP\t\t0x0001\n #define MMCMP_DELTA\t\t0x0002\n #define MMCMP_16BIT\t\t0x0004\n-#define MMCMP_STEREO\t0x0100\n #define MMCMP_ABS16\t\t0x0200\n-#define MMCMP_ENDIAN\t0x0400\n \n typedef struct MMCMPBITBUFFER\n {\ndiff -Nudr -U3 libmodplug-0.8.8.1.pre//src/sndmix.cpp libmodplug-0.8.8.1/src/sndmix.cpp\n--- libmodplug-0.8.8.1.pre//src/sndmix.cpp\t2010-06-27 21:33:37.657964087 +0100\n+++ libmodplug-0.8.8.1/src/sndmix.cpp\t2010-06-27 21:47:32.420569554 +0100\n@@ -15,8 +15,10 @@\n // Volume ramp length, in 1/10 ms\n #define VOLUMERAMPLEN\t146\t// 1.46ms = 64 samples at 44.1kHz\n \n+#if defined(MODPLUG_PLAYER) || defined(ENABLE_STEREOVU)\n // VU-Meter\n #define VUMETER_DECAY\t\t4\n+#endif\n \n // SNDMIX: These are global flags for playback control (first two configurable via SetMixConfig)\n UINT CSoundFile::m_nStereoSeparation = 128;\n"
  },
  {
    "path": "contrib/patches/libmodplug/07-libmodplug-0.8.9.0-misc-warning-fixes.diff",
    "content": "diff -Nudr libmodplug-0.8.8.1.pre/src/fastmix.cpp libmodplug-0.8.8.1/src/fastmix.cpp\n--- libmodplug-0.8.8.1.pre/src/fastmix.cpp\t2010-06-27 21:44:03.545964479 +0100\n+++ libmodplug-0.8.8.1/src/fastmix.cpp\t2010-06-27 21:50:04.021106028 +0100\n@@ -1886,7 +1886,7 @@\n \t\telse if (n > vumax)\n \t\t\tvumax = n;\n \t\tp = n >> (8-MIXING_ATTENUATION) ; // 24-bit signed\n-#ifdef WORDS_BIGENDIAN\n+#if defined(WORDS_BIGENDIAN)\n \t\tbuf[i*3+0] = (p >> 16) & 0xFF;\n \t\tbuf[i*3+1] = (p >> 8)  & 0xFF;\n \t\tbuf[i*3+2] = (p >> 0)  & 0xFF;\ndiff -Nudr libmodplug-0.8.8.1.pre/src/libmodplug/sndfile.h libmodplug-0.8.8.1/src/libmodplug/sndfile.h\n--- libmodplug-0.8.8.1.pre/src/libmodplug/sndfile.h\t2010-04-05 08:40:38.000000000 +0100\n+++ libmodplug-0.8.8.1/src/libmodplug/sndfile.h\t2010-06-27 21:50:49.466475574 +0100\n@@ -517,7 +517,7 @@\n \tMIDIOUT_VOLUME,\n \tMIDIOUT_PAN,\n \tMIDIOUT_BANKSEL,\n-\tMIDIOUT_PROGRAM,\n+\tMIDIOUT_PROGRAM\n };\n \n \n@@ -1002,7 +1002,7 @@\n #define bswapBE32(X) bswap_32(ARM_get32(&X))\n \n // From libsdl\n-#elif defined(WORDS_BIGENDIAN) && WORDS_BIGENDIAN\n+#elif defined(WORDS_BIGENDIAN)\n #define bswapLE16(X) bswap_16(X)\n #define bswapLE32(X) bswap_32(X)\n #define bswapBE16(X) (X)\n"
  },
  {
    "path": "contrib/patches/libmodplug/08-libmodplug-0.8.9.0-remove-set-but-unused-variables.diff",
    "content": "diff --git a/src/fastmix.cpp b/src/fastmix.cpp\nindex eacaecc..86fdcd6 100644\n--- a/src/fastmix.cpp\n+++ b/src/fastmix.cpp\n@@ -1485,13 +1485,12 @@ UINT CSoundFile::CreateStereoMix(int count)\n \t{\n \t\tconst LPMIXINTERFACE *pMixFuncTable;\n \t\tMODCHANNEL * const pChannel = &Chn[ChnMix[nChn]];\n-\t\tUINT nFlags, nMasterCh;\n+\t\tUINT nFlags;\n \t\tLONG nSmpCount;\n \t\tint nsamples;\n \t\tint *pbuffer;\n \n \t\tif (!pChannel->pCurrentSample) continue;\n-\t\tnMasterCh = (ChnMix[nChn] < m_nChannels) ? ChnMix[nChn]+1 : pChannel->nMasterChn;\n \t\tpOfsR = &gnDryROfsVol;\n \t\tpOfsL = &gnDryLOfsVol;\n \t\tnFlags = 0;\ndiff --git a/src/load_669.cpp b/src/load_669.cpp\nindex f3aa0ee..4087fa2 100644\n--- a/src/load_669.cpp\n+++ b/src/load_669.cpp\n@@ -48,14 +48,12 @@ DWORD lengthArrayToDWORD(const BYTE length[4]) {\n BOOL CSoundFile::Read669(const BYTE *lpStream, DWORD dwMemLength)\n //---------------------------------------------------------------\n {\n-\tBOOL b669Ext;\n \tconst FILEHEADER669 *pfh = (const FILEHEADER669 *)lpStream;\n \tconst SAMPLE669 *psmp = (const SAMPLE669 *)(lpStream + 0x1F1);\n \tDWORD dwMemPos = 0;\n \n \tif ((!lpStream) || (dwMemLength < sizeof(FILEHEADER669))) return FALSE;\n \tif ((bswapLE16(pfh->sig) != 0x6669) && (bswapLE16(pfh->sig) != 0x4E4A)) return FALSE;\n-\tb669Ext = (bswapLE16(pfh->sig) == 0x4E4A) ? TRUE : FALSE;\n \tif ((!pfh->samples) || (pfh->samples > 64) || (pfh->restartpos >= 128)\n \t || (!pfh->patterns) || (pfh->patterns > 128)) return FALSE;\n \tDWORD dontfuckwithme = 0x1F1 + pfh->samples * sizeof(SAMPLE669) + pfh->patterns * 0x600;\ndiff --git a/src/load_it.cpp b/src/load_it.cpp\nindex 10b17af..bc57fb3 100644\n--- a/src/load_it.cpp\n+++ b/src/load_it.cpp\n@@ -166,7 +166,7 @@ BOOL CSoundFile::ReadIT(const BYTE *lpStream, DWORD dwMemLength)\n \tDWORD inspos[MAX_INSTRUMENTS];\n \tDWORD smppos[MAX_SAMPLES];\n \tDWORD patpos[MAX_PATTERNS];\n-\tBYTE chnmask[64], channels_used[64];\n+\tBYTE chnmask[64];\n \tMODCOMMAND lastvalue[64];\n \n \tpifh.id = bswapLE32(pifh.id);\n@@ -515,7 +515,6 @@ BOOL CSoundFile::ReadIT(const BYTE *lpStream, DWORD dwMemLength)\n \t\t\t\t\tif (note < 0x80) note++;\n \t\t\t\t\tm[ch].note = note;\n \t\t\t\t\tlastvalue[ch].note = note;\n-\t\t\t\t\tchannels_used[ch] = TRUE;\n \t\t\t\t}\n \t\t\t}\n \t\t\tif (chnmask[ch] & 2)\n@@ -1214,7 +1213,6 @@ void ITUnpack8Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwM\n {\n \tsigned char *pDst = pSample;\n \tLPBYTE pSrc = lpMemFile;\n-\tDWORD wHdr = 0;\n \tDWORD wCount = 0;\n \tDWORD bitbuf = 0;\n \tUINT bitnum = 0;\n@@ -1225,7 +1223,6 @@ void ITUnpack8Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dwM\n \t\tif (!wCount)\n \t\t{\n \t\t\twCount = 0x8000;\n-\t\t\twHdr = bswapLE16(*((LPWORD)pSrc));\n \t\t\tpSrc += 2;\n \t\t\tbLeft = 9;\n \t\t\tbTemp = bTemp2 = 0;\n@@ -1296,7 +1293,6 @@ void ITUnpack16Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dw\n {\n \tsigned short *pDst = (signed short *)pSample;\n \tLPBYTE pSrc = lpMemFile;\n-\tDWORD wHdr = 0;\n \tDWORD wCount = 0;\n \tDWORD bitbuf = 0;\n \tUINT bitnum = 0;\n@@ -1308,7 +1304,6 @@ void ITUnpack16Bit(signed char *pSample, DWORD dwLen, LPBYTE lpMemFile, DWORD dw\n \t\tif (!wCount)\n \t\t{\n \t\t\twCount = 0x4000;\n-\t\t\twHdr = bswapLE16(*((LPWORD)pSrc));\n \t\t\tpSrc += 2;\n \t\t\tbLeft = 17;\n \t\t\twTemp = wTemp2 = 0;\ndiff --git a/src/load_okt.cpp b/src/load_okt.cpp\nindex 7968f0b..50b905f 100644\n--- a/src/load_okt.cpp\n+++ b/src/load_okt.cpp\n@@ -43,7 +43,7 @@ BOOL CSoundFile::ReadOKT(const BYTE *lpStream, DWORD dwMemLength)\n {\n \tconst OKTFILEHEADER *pfh = (OKTFILEHEADER *)lpStream;\n \tDWORD dwMemPos = sizeof(OKTFILEHEADER);\n-\tUINT nsamples = 0, npatterns = 0, norders = 0;\n+\tUINT nsamples = 0, norders = 0;\n \n \tif ((!lpStream) || (dwMemLength < 1024)) return FALSE;\n \tif ((pfh->okta != 0x41544B4F) || (pfh->song != 0x474E4F53)\n@@ -88,7 +88,6 @@ BOOL CSoundFile::ReadOKT(const BYTE *lpStream, DWORD dwMemLength)\n \tif (dwMemPos >= dwMemLength) return TRUE;\n \tif (*((DWORD *)(lpStream + dwMemPos)) == 0x4E454C53)\n \t{\n-\t\tnpatterns = lpStream[dwMemPos+9];\n \t\tdwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8;\n \t}\n \t// PLEN\n"
  },
  {
    "path": "contrib/patches/libmodplug/09-libmodplug-0.8.9.0-remove-dll-defines.diff",
    "content": "diff -Nrup _src/libmodplug/stdafx.h src/libmodplug/stdafx.h\n--- _src/libmodplug/stdafx.h\t2017-05-25 07:26:43.565056316 +0200\n+++ src/libmodplug/stdafx.h\t2017-05-25 07:27:09.834057068 +0200\n@@ -124,18 +124,6 @@ static inline void ProcessPlugins(int n)\n \n #endif /* _WIN32 */\n \n-#if defined(_WIN32) || defined(__CYGWIN__)\n-# if defined(MODPLUG_BUILD) && defined(DLL_EXPORT)\t/* building libmodplug as a dll for windows */\n-#   define MODPLUG_EXPORT __declspec(dllexport)\n-# elif defined(MODPLUG_BUILD) || defined(MODPLUG_STATIC)\t/* building or using static libmodplug for windows */\n-#   define MODPLUG_EXPORT\n-# else\n-#   define MODPLUG_EXPORT __declspec(dllimport)\t\t\t/* using libmodplug dll for windows */\n-# endif\n-#elif defined(MODPLUG_BUILD) && defined(SYM_VISIBILITY)\n-#   define MODPLUG_EXPORT __attribute__((visibility(\"default\")))\n-#else\n #define MODPLUG_EXPORT\n-#endif\n \n #endif\ndiff -Nrup _src/modplug.h src/modplug.h\n--- _src/modplug.h\t2017-05-25 07:26:43.574056317 +0200\n+++ src/modplug.h\t2017-05-25 07:27:03.787056895 +0200\n@@ -11,19 +11,7 @@\n extern \"C\" {\n #endif\n \n-#if defined(_WIN32) || defined(__CYGWIN__)\n-# if defined(MODPLUG_BUILD) && defined(DLL_EXPORT)\t/* building libmodplug as a dll for windows */\n-#   define MODPLUG_EXPORT __declspec(dllexport)\n-# elif defined(MODPLUG_BUILD) || defined(MODPLUG_STATIC)\t/* building or using static libmodplug for windows */\n-#   define MODPLUG_EXPORT\n-# else\n-#   define MODPLUG_EXPORT __declspec(dllimport)\t\t\t/* using libmodplug dll for windows */\n-# endif\n-#elif defined(MODPLUG_BUILD) && defined(SYM_VISIBILITY)\n-#   define MODPLUG_EXPORT __attribute__((visibility(\"default\")))\n-#else\n #define MODPLUG_EXPORT\n-#endif\n \n struct _ModPlugFile;\n typedef struct _ModPlugFile ModPlugFile;\n"
  },
  {
    "path": "contrib/patches/libmodplug/10-libmodplug-0.8.9.0-fix-far-volume.diff",
    "content": "diff --git a/src/load_far.cpp b/src/load_far.cpp\nindex b070a24..a8245e5 100644\n--- a/src/load_far.cpp\n+++ b/src/load_far.cpp\n@@ -151,11 +151,10 @@ BOOL CSoundFile::ReadFAR(const BYTE *lpStream, DWORD dwMemLength)\n \t\t\t\tm->instr = ins + 1;\n \t\t\t\tm->note = note + 36;\n \t\t\t}\n-\t\t\tif (vol & 0x0F)\n+\t\t\tif (vol >= 0x01 && vol <= 0x10)\n \t\t\t{\n \t\t\t\tm->volcmd = VOLCMD_VOLUME;\n-\t\t\t\tm->vol = (vol & 0x0F) << 2;\n-\t\t\t\tif (m->vol <= 4) m->vol = 0;\n+\t\t\t\tm->vol = (vol - 1) << 2;\n \t\t\t}\n \t\t\tswitch(eff & 0xF0)\n \t\t\t{\n"
  },
  {
    "path": "contrib/patches/libmodplug/11-libmodplug-0.8.9.0-fix-okt-order-list.diff",
    "content": "diff --git a/src/load_okt.cpp b/src/load_okt.cpp\nindex 7968f0b..bfbeef8 100644\n--- a/src/load_okt.cpp\n+++ b/src/load_okt.cpp\n@@ -104,7 +104,7 @@ BOOL CSoundFile::ReadOKT(const BYTE *lpStream, DWORD dwMemLength)\n \t{\n \t\tUINT orderlen = norders;\n \t\tif (orderlen >= MAX_ORDERS) orderlen = MAX_ORDERS-1;\n-\t\tfor (UINT i=0; i<orderlen; i++) Order[i] = lpStream[dwMemPos+10+i];\n+\t\tfor (UINT i=0; i<orderlen; i++) Order[i] = lpStream[dwMemPos+8+i];\n \t\tfor (UINT j=orderlen; j>1; j--) { if (Order[j-1]) break; Order[j-1] = 0xFF; }\n \t\tdwMemPos += bswapBE32(*((DWORD *)(lpStream + dwMemPos + 4))) + 8;\n \t}\n"
  },
  {
    "path": "contrib/patches/libmodplug/12-libmodplug-0.8.9.0-add-mod-cd61-magic.diff",
    "content": "diff --git a/src/load_mod.cpp b/src/load_mod.cpp\nindex c8ba8d2..46bf4ae 100644\n--- a/src/load_mod.cpp\n+++ b/src/load_mod.cpp\n@@ -208,6 +208,7 @@ BOOL CSoundFile::ReadMod(const BYTE *lpStream, DWORD dwMemLength)\n \tif ((IsMagic(s, \"M.K.\")) || (IsMagic(s, \"M!K!\"))\n \t || (IsMagic(s, \"M&K!\")) || (IsMagic(s, \"N.T.\"))) m_nChannels = 4; else\n \tif ((IsMagic(s, \"CD81\")) || (IsMagic(s, \"OKTA\"))) m_nChannels = 8; else\n+\tif (IsMagic(s, \"CD61\")) m_nChannels = 6; else\n \tif ((s[0]=='F') && (s[1]=='L') && (s[2]=='T') && (s[3]>='4') && (s[3]<='9')) m_nChannels = s[3] - '0'; else\n \tif ((s[0]>='2') && (s[0]<='9') && (s[1]=='C') && (s[2]=='H') && (s[3]=='N')) m_nChannels = s[0] - '0'; else\n \tif ((s[0]=='1') && (s[1]>='0') && (s[1]<='9') && (s[2]=='C') && (s[3]=='H')) m_nChannels = s[1] - '0' + 10; else\n"
  },
  {
    "path": "contrib/patches/libmodplug/13-libmodplug-0.8.9.0-fix-669-tempo.diff",
    "content": "diff --git a/src/load_669.cpp b/src/load_669.cpp\nindex c275615..1920878 100644\n--- a/src/load_669.cpp\n+++ b/src/load_669.cpp\n@@ -71,7 +71,7 @@ BOOL CSoundFile::Read669(const BYTE *lpStream, DWORD dwMemLength)\n \tm_dwSongFlags |= SONG_LINEARSLIDES;\n \tm_nMinPeriod = 28 << 2;\n \tm_nMaxPeriod = 1712 << 3;\n-\tm_nDefaultTempo = 125;\n+\tm_nDefaultTempo = 78;\n \tm_nDefaultSpeed = 6;\n \tm_nChannels = 8;\n \tmemcpy(m_szNames[0], pfh->songmessage, 16);\n@@ -154,7 +154,7 @@ BOOL CSoundFile::Read669(const BYTE *lpStream, DWORD dwMemLength)\n \t\t\t\t\tcase 0x02:\tcommand = CMD_TONEPORTAMENTO; break;\n \t\t\t\t\tcase 0x03:\tcommand = CMD_MODCMDEX; param |= 0x50; break;\n \t\t\t\t\tcase 0x04:\tcommand = CMD_VIBRATO; param |= 0x40; break;\n-\t\t\t\t\tcase 0x05:\tif (param) command = CMD_SPEED; else command = 0; param += 2; break;\n+\t\t\t\t\tcase 0x05:\tif (param) command = CMD_SPEED; else command = 0; break;\n \t\t\t\t\tcase 0x06:\tif (param == 0) { command = CMD_PANNINGSLIDE; param = 0xFE; }\n \t\t\t\t\t\t\t\telse if (param == 1) { command = CMD_PANNINGSLIDE; param = 0xEF; }\n \t\t\t\t\t\t\t\telse command = 0;\n@@ -174,7 +174,7 @@ BOOL CSoundFile::Read669(const BYTE *lpStream, DWORD dwMemLength)\n \t\t\t\tfor (UINT i=0; i<8; i++) if (!mspeed[i].command)\n \t\t\t\t{\n \t\t\t\t\tmspeed[i].command = CMD_SPEED;\n-\t\t\t\t\tmspeed[i].param = pfh->tempolist[npat] + 2;\n+\t\t\t\t\tmspeed[i].param = pfh->tempolist[npat];\n \t\t\t\t\tbreak;\n \t\t\t\t}\n \t\t\t}\n"
  },
  {
    "path": "contrib/patches/libmodplug/14-libmodplug-0.8.9.0-add-gdm-loader.diff",
    "content": "diff --git a/src/libmodplug/sndfile.h b/src/libmodplug/sndfile.h\nindex 091e25fd..2a3c3147 100644\n--- a/src/libmodplug/sndfile.h\n+++ b/src/libmodplug/sndfile.h\n@@ -71,6 +71,7 @@ typedef const BYTE * LPCBYTE;\n #define MOD_TYPE_J2B\t\t0x800000\n #define MOD_TYPE_ABC\t\t0x1000000\n #define MOD_TYPE_PAT\t\t0x2000000\n+#define MOD_TYPE_GDM\t\t0x40000000 // Fake type\n #define MOD_TYPE_UMX\t\t0x80000000 // Fake type\n #define MAX_MODTYPE\t\t24\n \n@@ -652,6 +653,7 @@ public:\n \tBOOL ReadMT2(LPCBYTE lpStream, DWORD dwMemLength);\n \tBOOL ReadPSM(LPCBYTE lpStream, DWORD dwMemLength);\n \tBOOL ReadJ2B(LPCBYTE lpStream, DWORD dwMemLength);\n+\tBOOL ReadGDM(LPCBYTE lpStream, DWORD dwMemLength);\n \tBOOL ReadUMX(LPCBYTE lpStream, DWORD dwMemLength);\n \tBOOL ReadABC(LPCBYTE lpStream, DWORD dwMemLength);\n \tBOOL TestABC(LPCBYTE lpStream, DWORD dwMemLength);\ndiff --git a/src/sndfile.cpp b/src/sndfile.cpp\nindex cc84dee6..d1730910 100644\n--- a/contrib/libmodplug/src/sndfile.cpp\n+++ b/contrib/libmodplug/src/sndfile.cpp\n@@ -161,6 +161,7 @@ BOOL CSoundFile::Create(LPCBYTE lpStream, DWORD dwMemLength)\n \t\t && (!ReadUlt(lpStream, dwMemLength))\n \t\t //&& (!ReadDMF(lpStream, dwMemLength))\n \t\t && (!ReadDSM(lpStream, dwMemLength))\n+\t\t && (!ReadGDM(lpStream, dwMemLength))\n \t\t //&& (!ReadUMX(lpStream, dwMemLength))\n \t\t && (!ReadAMF(lpStream, dwMemLength))\n \t\t //&& (!ReadPSM(lpStream, dwMemLength))\n"
  },
  {
    "path": "contrib/patches/libmodplug/readme.txt",
    "content": "Apply the patches in this directory to bring a stock libmodplug-0.8.9.0\ndistribution in line with the local copy used by MegaZeux. Additionally,\nload_gdm.cpp needs to be copied from the local MegaZeux libmodplug.\n\nIn addition to the patches, the following files should be removed, as\nthey are no longer necessary:\n\n- src/load_abc.cpp\n- src/load_ams.cpp\n- src/load_dbm.cpp\n- src/load_dmf.cpp\n- src/load_j2b.cpp\n- src/load_mdl.cpp\n- src/load_mid.cpp\n- src/load_mt2.cpp\n- src/load_pat.cpp\n- src/load_pat.h\n- src/load_psm.cpp\n- src/load_ptm.cpp\n- src/load_umx.cpp\n\n--asie (20170524T19:22)\n--ajs (20120128T15:42)\n"
  },
  {
    "path": "contrib/patches/libxmp/01-libxmp-mzx-integration.patch",
    "content": "diff --git a/include/xmp.h b/include/xmp.h\nindex 22db2a3d..a9b06218 100644\n--- a/include/xmp.h\n+++ b/include/xmp.h\n@@ -1,6 +1,26 @@\n #ifndef XMP_H\n #define XMP_H\n \n+/**** Start MZX-specific hacks. *****/\n+\n+/* Suppress unwanted debug messages */\n+#ifdef DEBUG\n+#undef DEBUG\n+#define MEGAZEUX_DEBUG\n+#endif\n+\n+/* Force libxmp to build static */\n+#ifndef LIBXMP_STATIC\n+#define LIBXMP_STATIC\n+#endif\n+\n+/* Force libxmp to not use versioned symbols on Linux */\n+#ifdef XMP_SYM_VISIBILITY\n+#undef XMP_SYM_VISIBILITY\n+#endif\n+\n+/***** End MZX-specific hacks. *****/\n+\n #if defined(EMSCRIPTEN)\n # include <emscripten.h>\n #endif\n"
  },
  {
    "path": "contrib/patches/libxmp/02-libxmp-mzx-remove-extra-formats.patch",
    "content": "diff --git a/src/common.h b/src/common.h\nindex 04754cf6..01324288 100644\n--- a/src/common.h\n+++ b/src/common.h\n@@ -120,10 +120,6 @@ typedef int tst_uint64[2 * (8 == sizeof(uint64)) - 1];\n #pragma warning(disable:4100) /* unreferenced formal parameter */\n #endif\n \n-#ifndef LIBXMP_CORE_PLAYER\n-#define LIBXMP_PAULA_SIMULATOR\n-#endif\n-\n /* Constants */\n #define PAL_RATE\t250.0\t\t/* 1 / (50Hz * 80us)\t\t  */\n #define NTSC_RATE\t208.0\t\t/* 1 / (60Hz * 80us)\t\t  */\ndiff --git a/src/format.c b/src/format.c\nindex 593ace13..aecde7b5 100644\n--- a/src/format.c\n+++ b/src/format.c\n@@ -28,28 +28,14 @@\n const struct format_loader *const format_loaders[NUM_FORMATS + 2] = {\n \t&libxmp_loader_xm,\n \t&libxmp_loader_mod,\n-#ifndef LIBXMP_CORE_DISABLE_IT\n \t&libxmp_loader_it,\n-#endif\n \t&libxmp_loader_s3m,\n-#ifndef LIBXMP_CORE_PLAYER\n \t&libxmp_loader_flt,\n \t&libxmp_loader_st,\n \t&libxmp_loader_stm,\n-\t&libxmp_loader_stx,\n \t&libxmp_loader_mtm,\n \t&libxmp_loader_ice,\n-\t&libxmp_loader_imf,\n-\t&libxmp_loader_ptm,\n-\t&libxmp_loader_mdl,\n \t&libxmp_loader_ult,\n-\t&libxmp_loader_liq,\n-\t&libxmp_loader_no,\n-\t&libxmp_loader_masi,\n-\t&libxmp_loader_masi16,\n-\t&libxmp_loader_muse,\n-\t&libxmp_loader_gal5,\n-\t&libxmp_loader_gal4,\n \t&libxmp_loader_amf,\n \t&libxmp_loader_asylum,\n \t&libxmp_loader_gdm,\n@@ -58,44 +44,10 @@ const struct format_loader *const format_loaders[NUM_FORMATS + 2] = {\n \t&libxmp_loader_med2,\n \t&libxmp_loader_med3,\n \t&libxmp_loader_med4,\n-\t/* &libxmp_loader_dmf, */\n-\t&libxmp_loader_chip,\n-\t&libxmp_loader_rtm,\n-\t&libxmp_loader_pt3,\n-\t/* &libxmp_loader_tcb, */\n-\t&libxmp_loader_dt,\n-\t/* &libxmp_loader_gtk, */\n-\t/* &libxmp_loader_dtt, */\n-\t&libxmp_loader_mgt,\n-\t&libxmp_loader_arch,\n-\t&libxmp_loader_sym,\n-\t&libxmp_loader_digi,\n-\t&libxmp_loader_dbm,\n-\t&libxmp_loader_emod,\n \t&libxmp_loader_okt,\n-\t&libxmp_loader_sfx,\n \t&libxmp_loader_far,\n-\t&libxmp_loader_umx,\n \t&libxmp_loader_hmn,\n-\t&libxmp_loader_stim,\n-\t&libxmp_loader_coco,\n-\t/* &libxmp_loader_mtp, */\n-\t&libxmp_loader_ims,\n \t&libxmp_loader_669,\n-\t&libxmp_loader_fnk,\n-\t/* &libxmp_loader_amd, */\n-\t/* &libxmp_loader_rad, */\n-\t/* &libxmp_loader_hsc, */\n-\t&libxmp_loader_mfp,\n-\t&libxmp_loader_abk,\n-\t/* &libxmp_loader_alm, */\n-\t/* &libxmp_loader_polly, */\n-\t/* &libxmp_loader_stc, */\n-\t&libxmp_loader_xmf,\n-#ifndef LIBXMP_NO_PROWIZARD\n-\t&libxmp_loader_pw,\n-#endif\n-#endif /* LIBXMP_CORE_PLAYER */\n \tNULL /* list teminator */\n };\n \ndiff --git a/src/loaders/xm_load.c b/src/loaders/xm_load.c\nindex 72189600..bcc677ca 100644\n--- a/src/loaders/xm_load.c\n+++ b/src/loaders/xm_load.c\n@@ -35,7 +35,7 @@\n \n #include \"loader.h\"\n #include \"xm.h\"\n-#ifndef LIBXMP_CORE_PLAYER\n+#if 0\n #include \"vorbis.h\"\n #endif\n \n@@ -373,7 +373,7 @@ err:\n  * for more details. */\n #define XM_MAX_SAMPLES_PER_INST 32\n \n-#ifndef LIBXMP_CORE_PLAYER\n+#if 0\n #define MAGIC_OGGS\t0x4f676753\n \n static int is_ogg_sample(HIO_HANDLE *f, struct xmp_sample *xxs)\n@@ -721,7 +721,7 @@ static int load_instruments(struct module_data *m, int version, HIO_HANDLE *f)\n \t\t\tif (version > 0x0103) {\n \t\t\t        D_(D_INFO \"  read sample: index:%d sample id:%d\", j, sub->sid);\n \n-#ifndef LIBXMP_CORE_PLAYER\n+#if 0\n \t\t\t\tif (is_ogg_sample(f, xxs)) {\n \t\t\t\t\tif (oggdec(m, f, xxs, xsh[j].length) < 0) {\n \t\t\t\t\t\treturn -1;\n"
  },
  {
    "path": "contrib/patches/libxmp/03-libxmp-real-max-srate.patch",
    "content": "diff --git a/docs/libxmp.rst b/docs/libxmp.rst\nindex d20f00f3..4ee26341 100644\n--- a/docs/libxmp.rst\n+++ b/docs/libxmp.rst\n@@ -115,7 +115,7 @@ error. Error codes are::\n If a system error occurs, the specific error is set in ``errno``.\n \n Parameters to `xmp_start_player()`_ are the sampling\n-rate (up to 48kHz) and a bitmapped integer holding one or more of the\n+rate (up to 384kHz) and a bitmapped integer holding one or more of the\n following mixer flags::\n \n   XMP_MIX_8BIT          /* Mix to 8-bit instead of 16 */\n@@ -599,7 +599,7 @@ int xmp_start_player(xmp_context c, int rate, int format)\n     :c: the player context handle.\n \n     :rate: the sampling rate to use, in Hz (typically 44100). Valid values\n-       range from 8kHz to 48kHz.\n+       range from 8kHz to 384kHz.\n \n     :flags: bitmapped configurable player flags, one or more of the\n       following::\n@@ -709,7 +709,8 @@ void xmp_get_frame_info(xmp_context c, struct xmp_frame_info \\*info)\n       This function should be used to retrieve sound buffer data after\n       `xmp_play_frame()`_ is called. Fields ``buffer`` and ``buffer_size``\n       contain the pointer to the sound buffer PCM data and its size. The\n-      buffer size will be no larger than ``XMP_MAX_FRAMESIZE``.\n+      buffer size will be no larger than ``XMP_MAX_FRAMESIZE`` if the player\n+      is initialized with a rate equal to or less than ``XMP_MAX_SRATE``.\n \n .. _xmp_end_player():\n \ndiff --git a/src/common.h b/src/common.h\nindex 034d3ed8..73915148 100644\n--- a/src/common.h\n+++ b/src/common.h\n@@ -125,6 +125,7 @@ typedef int tst_uint64[2 * (8 == sizeof(uint64)) - 1];\n #endif\n \n /* Constants */\n+#define REAL_MAX_SRATE\t384000\t\t/* actual maximum sample rate */\n #define PAL_RATE\t250.0\t\t/* 1 / (50Hz * 80us)\t\t  */\n #define NTSC_RATE\t208.0\t\t/* 1 / (60Hz * 80us)\t\t  */\n #define C4_PAL_RATE\t8287\t\t/* 7093789.2 / period (C4) * 2\t  */\n@@ -515,6 +516,7 @@ struct mixer_data {\n \tint dsp;\t\t/* dsp effect flags */\n \tchar *buffer;\t\t/* output buffer */\n \tint32 *buf32;\t\t/* temporary buffer for 32 bit samples */\n+\tint total_size;\t\t/* allocated samples (not frames) in buffers */\n \tint numvoc;\t\t/* default softmixer voices number */\n \tint ticksize;\n \tint dtright;\t\t/* anticlick control, right channel */\ndiff --git a/src/control.c b/src/control.c\nindex c3666e33..7e923c2b 100644\n--- a/src/control.c\n+++ b/src/control.c\n@@ -594,8 +594,8 @@ int xmp_set_tempo_factor(xmp_context opaque, double val)\n \t * change during playback, so repeat these checks in the mixer. */\n \tticksize = libxmp_mixer_get_ticksize(s->freq, val, m->rrate, p->bpm);\n \n-\t/* ticksize is in frames, XMP_MAX_FRAMESIZE is in frames * 2. */\n-\tif (ticksize < 0 || ticksize > (XMP_MAX_FRAMESIZE / 2)) {\n+\t/* ticksize is in frames, s->total_size is in frames * 2. */\n+\tif (ticksize < 0 || ticksize > (s->total_size / 2)) {\n \t\treturn -1;\n \t}\n \tm->time_factor = val;\ndiff --git a/src/mixer.c b/src/mixer.c\nindex 3e6cc58d..27d048d9 100644\n--- a/src/mixer.c\n+++ b/src/mixer.c\n@@ -1007,15 +1007,20 @@ int libxmp_mixer_numvoices(struct context_data *ctx, int num)\n int libxmp_mixer_on(struct context_data *ctx, int rate, int format, int c4rate)\n {\n \tstruct mixer_data *s = &ctx->s;\n+\tint total_size = 5 * rate * 2 / XMP_MIN_BPM; /* See xmp.h */\n \n-\ts->buffer = (char *) calloc(XMP_MAX_FRAMESIZE, sizeof(int16));\n+\tif(total_size < XMP_MAX_FRAMESIZE)\n+\t\ttotal_size = XMP_MAX_FRAMESIZE;\n+\n+\ts->buffer = (char *) calloc(total_size, sizeof(int16));\n \tif (s->buffer == NULL)\n \t\tgoto err;\n \n-\ts->buf32 = (int32 *) calloc(XMP_MAX_FRAMESIZE, sizeof(int32));\n+\ts->buf32 = (int32 *) calloc(total_size, sizeof(int32));\n \tif (s->buf32 == NULL)\n \t\tgoto err1;\n \n+\ts->total_size = total_size;\n \ts->freq = rate;\n \ts->format = format;\n \ts->amplify = DEFAULT_AMPLIFY;\ndiff --git a/src/player.c b/src/player.c\nindex e1226d56..dcfadc04 100644\n--- a/src/player.c\n+++ b/src/player.c\n@@ -1859,7 +1859,7 @@ int xmp_start_player(xmp_context opaque, int rate, int format)\n \tint i;\n \tint ret = 0;\n \n-\tif (rate < XMP_MIN_SRATE || rate > XMP_MAX_SRATE)\n+\tif (rate < XMP_MIN_SRATE || rate > REAL_MAX_SRATE)\n \t\treturn -XMP_ERROR_INVALID;\n \n \tif (ctx->state < XMP_STATE_LOADED)\n@@ -2244,7 +2244,7 @@ void xmp_get_frame_info(xmp_context opaque, struct xmp_frame_info *info)\n \tinfo->time = p->current_time;\n \tinfo->buffer = s->buffer;\n \n-\tinfo->total_size = XMP_MAX_FRAMESIZE;\n+\tinfo->total_size = s->total_size;\n \tinfo->buffer_size = s->ticksize;\n \tif (~s->format & XMP_FORMAT_MONO) {\n \t\tinfo->buffer_size *= 2;\ndiff --git a/test-dev/test_api_start_player.c b/test-dev/test_api_start_player.c\nindex 079b8322..237c5d44 100644\n--- a/test-dev/test_api_start_player.c\n+++ b/test-dev/test_api_start_player.c\n@@ -33,7 +33,7 @@ TEST(test_api_start_player)\n \tstate = xmp_get_player(ctx, XMP_PLAYER_STATE);\n \tfail_unless(state == XMP_STATE_LOADED, \"state error\");\n \n-\tret = xmp_start_player(ctx, XMP_MAX_SRATE, 0);\n+\tret = xmp_start_player(ctx, REAL_MAX_SRATE, 0);\n \tfail_unless(ret == 0, \"max sample rate failed\");\n \n \tstate = xmp_get_player(ctx, XMP_PLAYER_STATE);\n@@ -53,7 +53,7 @@ TEST(test_api_start_player)\n \n \txmp_end_player(ctx);\n \n-\tret = xmp_start_player(ctx, XMP_MAX_SRATE + 1, 0);\n+\tret = xmp_start_player(ctx, REAL_MAX_SRATE + 1, 0);\n \tfail_unless(ret == -XMP_ERROR_INVALID, \"max sample rate limit failed\");\n \n \tstate = xmp_get_player(ctx, XMP_PLAYER_STATE);\n"
  },
  {
    "path": "contrib/patches/libxmp/README",
    "content": "These patches are designed to get a fresh copy of libxmp's master branch in line with\nMegaZeux's needs. Apply them in order (01, 02, ...) for best results. Alternatively,\nuse [this libxmp branch](https://github.com/AliceLR/libxmp/tree/mzx-gen-libxmp) to\nautomatically patch libxmp and generate contrib/libxmp/.\n\nPatch summary\n-------------\n\n01: Adds MZX integration hacks to include/xmp.h.\n02: Removes extra formats not required by MZX.\n\n--Lachesis\n"
  },
  {
    "path": "contrib/rad/README.md",
    "content": "## Reality Adlib Tracker Player\n\nThis is a modified version of the RAD 2.x player for MegaZeux. Included:\n\n* RAD 1.x playback and validation support.\n* New functions required for MegaZeux's set_order/set_position and get_length.\n* Clear RADPlayer::Instruments during Init() to prevent uninitialized reads and\n  crashes caused by instruments referenced but not saved in a RAD.\n* A bugfix for Dxx. The vanilla player fails to skip directly to the parameter\n  row and instead will play the next pattern extremely fast until the parameter\n  row is reached.\n* A bugfix for BPM validation. The vanilla validater incorrectly checks bit 6\n  when the BPM-present flag is bit 5 in both the player and the documentation.\n* A bugfix for MIDI instrument validation in the player. A MIDI instrument is 7\n  bytes long including the first algorithm byte; the final byte is a \"volume\"\n  byte that appears in the tracker but is not present in the documentation.\n  The vanilla validator only skips 6 bytes.\n* A bugfix for MIDI instrument processing in the player. The vanilla player\n  will ignore processing an entire note when it encounters a line that would\n  play a MIDI instrument, causing it to ignore effects and riffs.\n* A bugfix for nested riff references. Nested riffs in RAD will immediately\n  replace the parent riff, but the replayer would fail to do this in some cases.\n  See `test_nested_riffs.rad` and `test_nested_riffs_transpose.rad`.\n* Some -pedantic warning fixes.\n* Opal bugfixes from OpenMPT/libADLMIDI by JP Cimalando have been ported over:\n  * Fixed wrong KSL (key scale shift) values.\n  * Cases where attack/decay/release are not applied should not prevent envelope\n    state changes.\n  * Registers Ax/Bx should affect both channels in 4op mode. This issue does not\n    affect RAD since RAD implements its 4op algorithms that support detune in\n    2op mode.\n\nAs the RAD player code is public domain, so are these modifications. If you find this\nuseful, go ahead and use it.\n\nPlease visit the Reality website for the latest version of the RAD tracker, the\noriginal version of this playback code, and .RAD music releases by Reality:\n<https://www.3eality.com/productions/reality-adlib-tracker>.\n\nThanks to Reality for the original software and to zzo38 for finding and\nreporting several of these bugs.\n\n-Lachesis\n"
  },
  {
    "path": "contrib/rad/opal.cpp",
    "content": "/*\n\n    The Opal OPL3 emulator.\n\n    Note: this is not a complete emulator, just enough for Reality Adlib Tracker tunes.\n\n    Missing features compared to a real OPL3:\n\n        - Timers/interrupts\n        - OPL3 enable bit (it defaults to always on)\n        - CSW mode\n        - Test register\n        - Percussion mode\n\n*/\n\n\n\n#include <stdint.h>\n\n\n\n//==================================================================================================\n// Opal class.\n//==================================================================================================\nclass Opal {\n\n    class Channel;\n\n    // Various constants\n    enum {\n        OPL3SampleRate      = 49716,\n        NumChannels         = 18,\n        NumOperators        = 36,\n\n        EnvOff              = -1,\n        EnvAtt,\n        EnvDec,\n        EnvSus,\n        EnvRel\n    };\n\n    // A single FM operator\n    class Operator {\n\n        public:\n                            Operator();\n            void            SetMaster(Opal *opal) {  Master = opal;  }\n            void            SetChannel(Channel *chan) {  Chan = chan;  }\n\n            int16_t         Output(uint16_t keyscalenum, uint32_t phase_step, int16_t vibrato, int16_t mod = 0, int16_t fbshift = 0);\n\n            void            SetKeyOn(bool on);\n            void            SetTremoloEnable(bool on);\n            void            SetVibratoEnable(bool on);\n            void            SetSustainMode(bool on);\n            void            SetEnvelopeScaling(bool on);\n            void            SetFrequencyMultiplier(uint16_t scale);\n            void            SetKeyScale(uint16_t scale);\n            void            SetOutputLevel(uint16_t level);\n            void            SetAttackRate(uint16_t rate);\n            void            SetDecayRate(uint16_t rate);\n            void            SetSustainLevel(uint16_t level);\n            void            SetReleaseRate(uint16_t rate);\n            void            SetWaveform(uint16_t wave);\n\n            void            ComputeRates();\n            void            ComputeKeyScaleLevel();\n\n        protected:\n            Opal *          Master;             // Master object\n            Channel *       Chan;               // Owning channel\n            uint32_t        Phase;              // The current offset in the selected waveform\n            uint16_t        Waveform;           // The waveform id this operator is using\n            uint16_t        FreqMultTimes2;     // Frequency multiplier * 2\n            int             EnvelopeStage;      // Which stage the envelope is at (see Env* enums above)\n            int16_t         EnvelopeLevel;      // 0 - $1FF, 0 being the loudest\n            uint16_t        OutputLevel;        // 0 - $FF\n            uint16_t        AttackRate;\n            uint16_t        DecayRate;\n            uint16_t        SustainLevel;\n            uint16_t        ReleaseRate;\n            uint16_t        AttackShift;\n            uint16_t        AttackMask;\n            uint16_t        AttackAdd;\n            const uint16_t *AttackTab;\n            uint16_t        DecayShift;\n            uint16_t        DecayMask;\n            uint16_t        DecayAdd;\n            const uint16_t *DecayTab;\n            uint16_t        ReleaseShift;\n            uint16_t        ReleaseMask;\n            uint16_t        ReleaseAdd;\n            const uint16_t *ReleaseTab;\n            uint16_t        KeyScaleShift;\n            uint16_t        KeyScaleLevel;\n            int16_t         Out[2];\n            bool            KeyOn;\n            bool            KeyScaleRate;       // Affects envelope rate scaling\n            bool            SustainMode;        // Whether to sustain during the sustain phase, or release instead\n            bool            TremoloEnable;\n            bool            VibratoEnable;\n    };\n\n    // A single channel, which can contain two or more operators\n    class Channel {\n\n        public:\n                            Channel();\n            void            SetMaster(Opal *opal) {  Master = opal;  }\n            void            SetOperators(Operator *a, Operator *b, Operator *c, Operator *d) {\n                Op[0] = a;\n                Op[1] = b;\n                Op[2] = c;\n                Op[3] = d;\n                if (a)\n                    a->SetChannel(this);\n                if (b)\n                    b->SetChannel(this);\n                if (c)\n                    c->SetChannel(this);\n                if (d)\n                    d->SetChannel(this);\n            }\n\n            void            Output(int16_t &left, int16_t &right);\n            void            SetEnable(bool on) {  Enable = on;  }\n            void            SetChannelPair(Channel *pair) {  ChannelPair = pair;  }\n\n            void            SetFrequencyLow(uint16_t freq);\n            void            SetFrequencyHigh(uint16_t freq);\n            void            SetKeyOn(bool on);\n            void            SetOctave(uint16_t oct);\n            void            SetLeftEnable(bool on);\n            void            SetRightEnable(bool on);\n            void            SetFeedback(uint16_t val);\n            void            SetModulationType(uint16_t type);\n\n            uint16_t        GetFreq() const {  return Freq;  }\n            uint16_t        GetOctave() const {  return Octave;  }\n            uint16_t        GetKeyScaleNumber() const {  return KeyScaleNumber;  }\n            uint16_t        GetModulationType() const {  return ModulationType;  }\n            Channel *       GetChannelPair() const { return ChannelPair; }\n\n            void            ComputeKeyScaleNumber();\n\n        protected:\n            void            ComputePhaseStep();\n\n            Operator *      Op[4];\n\n            Opal *          Master;             // Master object\n            uint16_t        Freq;               // Frequency; actually it's a phase stepping value\n            uint16_t        Octave;             // Also known as \"block\" in Yamaha parlance\n            uint32_t        PhaseStep;\n            uint16_t        KeyScaleNumber;\n            uint16_t        FeedbackShift;\n            uint16_t        ModulationType;\n            Channel *       ChannelPair;\n            bool            Enable;\n            bool            LeftEnable, RightEnable;\n    };\n\n    public:\n                            Opal(int sample_rate);\n                            ~Opal();\n\n        void                SetSampleRate(int sample_rate);\n        void                Port(uint16_t reg_num, uint8_t val);\n        void                Sample(int16_t *left, int16_t *right);\n\n    protected:\n        void                Init(int sample_rate);\n        void                Output(int16_t &left, int16_t &right);\n\n        int32_t             SampleRate;\n        int32_t             SampleAccum;\n        int16_t             LastOutput[2], CurrOutput[2];\n        Channel             Chan[NumChannels];\n        Operator            Op[NumOperators];\n//      uint16_t            ExpTable[256];\n//      uint16_t            LogSinTable[256];\n        uint16_t            Clock;\n        uint16_t            TremoloClock;\n        uint16_t            TremoloLevel;\n        uint16_t            VibratoTick;\n        uint16_t            VibratoClock;\n        bool                NoteSel;\n        bool                TremoloDepth;\n        bool                VibratoDepth;\n\n        static const uint16_t   RateTables[4][8];\n        static const uint16_t   ExpTable[256];\n        static const uint16_t   LogSinTable[256];\n};\n//--------------------------------------------------------------------------------------------------\nconst uint16_t Opal::RateTables[4][8] = {\n    {   1, 0, 1, 0, 1, 0, 1, 0  },\n    {   1, 0, 1, 0, 0, 0, 1, 0  },\n    {   1, 0, 0, 0, 1, 0, 0, 0  },\n    {   1, 0, 0, 0, 0, 0, 0, 0  },\n};\n//--------------------------------------------------------------------------------------------------\nconst uint16_t Opal::ExpTable[0x100] = {\n    1018, 1013, 1007, 1002,  996,  991,  986,  980,  975,  969,  964,  959,  953,  948,  942,  937,\n     932,  927,  921,  916,  911,  906,  900,  895,  890,  885,  880,  874,  869,  864,  859,  854,\n     849,  844,  839,  834,  829,  824,  819,  814,  809,  804,  799,  794,  789,  784,  779,  774,\n     770,  765,  760,  755,  750,  745,  741,  736,  731,  726,  722,  717,  712,  708,  703,  698,\n     693,  689,  684,  680,  675,  670,  666,  661,  657,  652,  648,  643,  639,  634,  630,  625,\n     621,  616,  612,  607,  603,  599,  594,  590,  585,  581,  577,  572,  568,  564,  560,  555,\n     551,  547,  542,  538,  534,  530,  526,  521,  517,  513,  509,  505,  501,  496,  492,  488,\n     484,  480,  476,  472,  468,  464,  460,  456,  452,  448,  444,  440,  436,  432,  428,  424,\n     420,  416,  412,  409,  405,  401,  397,  393,  389,  385,  382,  378,  374,  370,  367,  363,\n     359,  355,  352,  348,  344,  340,  337,  333,  329,  326,  322,  318,  315,  311,  308,  304,\n     300,  297,  293,  290,  286,  283,  279,  276,  272,  268,  265,  262,  258,  255,  251,  248,\n     244,  241,  237,  234,  231,  227,  224,  220,  217,  214,  210,  207,  204,  200,  197,  194,\n     190,  187,  184,  181,  177,  174,  171,  168,  164,  161,  158,  155,  152,  148,  145,  142,\n     139,  136,  133,  130,  126,  123,  120,  117,  114,  111,  108,  105,  102,   99,   96,   93,\n      90,   87,   84,   81,   78,   75,   72,   69,   66,   63,   60,   57,   54,   51,   48,   45,\n      42,   40,   37,   34,   31,   28,   25,   22,   20,   17,   14,   11,    8,    6,    3,    0,\n};\n//--------------------------------------------------------------------------------------------------\nconst uint16_t Opal::LogSinTable[0x100] = {\n    2137, 1731, 1543, 1419, 1326, 1252, 1190, 1137, 1091, 1050, 1013,  979,  949,  920,  894,  869,\n     846,  825,  804,  785,  767,  749,  732,  717,  701,  687,  672,  659,  646,  633,  621,  609,\n     598,  587,  576,  566,  556,  546,  536,  527,  518,  509,  501,  492,  484,  476,  468,  461,\n     453,  446,  439,  432,  425,  418,  411,  405,  399,  392,  386,  380,  375,  369,  363,  358,\n     352,  347,  341,  336,  331,  326,  321,  316,  311,  307,  302,  297,  293,  289,  284,  280,\n     276,  271,  267,  263,  259,  255,  251,  248,  244,  240,  236,  233,  229,  226,  222,  219,\n     215,  212,  209,  205,  202,  199,  196,  193,  190,  187,  184,  181,  178,  175,  172,  169,\n     167,  164,  161,  159,  156,  153,  151,  148,  146,  143,  141,  138,  136,  134,  131,  129,\n     127,  125,  122,  120,  118,  116,  114,  112,  110,  108,  106,  104,  102,  100,   98,   96,\n      94,   92,   91,   89,   87,   85,   83,   82,   80,   78,   77,   75,   74,   72,   70,   69,\n      67,   66,   64,   63,   62,   60,   59,   57,   56,   55,   53,   52,   51,   49,   48,   47,\n      46,   45,   43,   42,   41,   40,   39,   38,   37,   36,   35,   34,   33,   32,   31,   30,\n      29,   28,   27,   26,   25,   24,   23,   23,   22,   21,   20,   20,   19,   18,   17,   17,\n      16,   15,   15,   14,   13,   13,   12,   12,   11,   10,   10,    9,    9,    8,    8,    7,\n       7,    7,    6,    6,    5,    5,    5,    4,    4,    4,    3,    3,    3,    2,    2,    2,\n       2,    1,    1,    1,    1,    1,    1,    1,    0,    0,    0,    0,    0,    0,    0,    0,\n};\n\n\n\n//==================================================================================================\n// This is the temporary code for generating the above tables.  Maths and data from this nice\n// reverse-engineering effort:\n//\n// https://docs.google.com/document/d/18IGx18NQY_Q1PJVZ-bHywao9bhsDoAqoIn1rIm42nwo/edit\n//==================================================================================================\n#if 0\n#include <math.h>\n\nvoid GenerateTables() {\n\n    // Build the exponentiation table (reversed from the official OPL3 ROM)\n    FILE *fd = fopen(\"exptab.txt\", \"wb\");\n    if (fd) {\n        for (int i = 0; i < 0x100; i++) {\n            int v = (pow(2, (0xFF - i) / 256.0) - 1) * 1024 + 0.5;\n            if (i & 15)\n                fprintf(fd, \" %4d,\", v);\n            else\n                fprintf(fd, \"\\n\\t%4d,\", v);\n        }\n        fclose(fd);\n    }\n\n    // Build the log-sin table\n    fd = fopen(\"sintab.txt\", \"wb\");\n    if (fd) {\n        for (int i = 0; i < 0x100; i++) {\n            int v = -log(sin((i + 0.5) * 3.1415926535897933 / 256 / 2)) / log(2) * 256 + 0.5;\n            if (i & 15)\n                fprintf(fd, \" %4d,\", v);\n            else\n                fprintf(fd, \"\\n\\t%4d,\", v);\n        }\n        fclose(fd);\n    }\n}\n#endif\n\n\n\n//==================================================================================================\n// Constructor/destructor.\n//==================================================================================================\nOpal::Opal(int sample_rate) {\n\n    Init(sample_rate);\n}\n//--------------------------------------------------------------------------------------------------\nOpal::~Opal() {\n}\n\n\n\n//==================================================================================================\n// Initialise the emulation.\n//==================================================================================================\nvoid Opal::Init(int sample_rate) {\n\n    Clock = 0;\n    TremoloClock = 0;\n    TremoloLevel = 0;\n    VibratoTick = 0;\n    VibratoClock = 0;\n    NoteSel = false;\n    TremoloDepth = false;\n    VibratoDepth = false;\n\n//  // Build the exponentiation table (reversed from the official OPL3 ROM)\n//  for (int i = 0; i < 0x100; i++)\n//      ExpTable[i] = (pow(2, (0xFF - i) / 256.0) - 1) * 1024 + 0.5;\n//\n//  // Build the log-sin table\n//  for (int i = 0; i < 0x100; i++)\n//      LogSinTable[i] = -log(sin((i + 0.5) * 3.1415926535897933 / 256 / 2)) / log(2) * 256 + 0.5;\n\n    // Let sub-objects know where to find us\n    for (int i = 0; i < NumOperators; i++)\n        Op[i].SetMaster(this);\n\n    for (int i = 0; i < NumChannels; i++)\n        Chan[i].SetMaster(this);\n\n    // Add the operators to the channels.  Note, some channels can't use all the operators\n    // FIXME: put this into a separate routine\n    const int chan_ops[] = {\n        0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32,\n    };\n\n    for (int i = 0; i < NumChannels; i++) {\n        Channel *chan = &Chan[i];\n        int op = chan_ops[i];\n        if (i < 3 || (i >= 9 && i < 12))\n            chan->SetOperators(&Op[op], &Op[op + 3], &Op[op + 6], &Op[op + 9]);\n        else\n            chan->SetOperators(&Op[op], &Op[op + 3], 0, 0);\n    }\n\n    // Initialise the operator rate data.  We can't do this in the Operator constructor as it\n    // relies on referencing the master and channel objects\n    for (int i = 0; i < NumOperators; i++)\n        Op[i].ComputeRates();\n\n    SetSampleRate(sample_rate);\n}\n\n\n\n//==================================================================================================\n// Change the sample rate.\n//==================================================================================================\nvoid Opal::SetSampleRate(int sample_rate) {\n\n    // Sanity\n    if (sample_rate == 0)\n        sample_rate = OPL3SampleRate;\n\n    SampleRate = sample_rate;\n    SampleAccum = 0;\n    LastOutput[0] = LastOutput[1] = 0;\n    CurrOutput[0] = CurrOutput[1] = 0;\n}\n\n\n\n//==================================================================================================\n// Write a value to an OPL3 register.\n//==================================================================================================\nvoid Opal::Port(uint16_t reg_num, uint8_t val) {\n\n    const int op_lookup[] = {\n    //  00  01  02  03  04  05  06  07  08  09  0A  0B  0C  0D  0E  0F\n        0,  1,  2,  3,  4,  5,  -1, -1, 6,  7,  8,  9,  10, 11, -1, -1,\n    //  10  11  12  13  14  15  16  17  18  19  1A  1B  1C  1D  1E  1F\n        12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n    };\n\n    uint16_t type = reg_num & 0xE0;\n\n    // Is it BD, the one-off register stuck in the middle of the register array?\n    if (reg_num == 0xBD) {\n        TremoloDepth = (val & 0x80);\n        VibratoDepth = (val & 0x40);\n        return;\n    }\n\n    // Global registers\n    if (type == 0x00) {\n\n        // 4-OP enables\n        if (reg_num == 0x104) {\n\n            // Enable/disable channels based on which 4-op enables\n            uint8_t mask = 1;\n            for (int i = 0; i < 6; i++, mask <<= 1) {\n\n                // The 4-op channels are 0, 1, 2, 9, 10, 11\n                uint16_t chan = i < 3 ? i : i + 6;\n                Channel *primary = &Chan[chan];\n                Channel *secondary = &Chan[chan + 3];\n\n                if (val & mask) {\n\n                    // Let primary channel know it's controlling the secondary channel\n                    primary->SetChannelPair(secondary);\n\n                    // Turn off the second channel in the pair\n                    secondary->SetEnable(false);\n\n                } else {\n\n                    // Let primary channel know it's no longer controlling the secondary channel\n                    primary->SetChannelPair(0);\n\n                    // Turn on the second channel in the pair\n                    secondary->SetEnable(true);\n                }\n            }\n\n        // CSW / Note-sel\n        } else if (reg_num == 0x08) {\n\n            NoteSel = (val & 0x40);\n\n            // Get the channels to recompute the Key Scale No. as this varies based on NoteSel\n            for (int i = 0; i < NumChannels; i++)\n                Chan[i].ComputeKeyScaleNumber();\n        }\n\n    // Channel registers\n    } else if (type >= 0xA0 && type <= 0xC0) {\n\n        // Convert to channel number\n        int chan_num = reg_num & 15;\n\n        // Valid channel?\n        if (chan_num >= 9)\n            return;\n\n        // Is it the other bank of channels?\n        if (reg_num & 0x100)\n            chan_num += 9;\n\n        Channel &chan = Chan[chan_num];\n\n        // Registers Ax and Bx affect both channels.\n        Channel *chans[2] = { &chan, chan.GetChannelPair() };\n        int numchans = chans[1] ? 2 : 1;\n\n        // Do specific registers\n        switch (reg_num & 0xF0) {\n\n            // Frequency low\n            case 0xA0: {\n                for (int i = 0; i < numchans; i++) {\n                    chan.SetFrequencyLow(val);\n                }\n                break;\n            }\n\n            // Key-on / Octave / Frequency High\n            case 0xB0: {\n                for (int i = 0; i < numchans; i++) {\n                    chan.SetKeyOn(val & 0x20);\n                    chan.SetOctave(val >> 2 & 7);\n                    chan.SetFrequencyHigh(val & 3);\n                }\n                break;\n            }\n\n            // Right Stereo Channel Enable / Left Stereo Channel Enable / Feedback Factor / Modulation Type\n            case 0xC0: {\n                chan.SetRightEnable(val & 0x20);\n                chan.SetLeftEnable(val & 0x10);\n                chan.SetFeedback(val >> 1 & 7);\n                chan.SetModulationType(val & 1);\n                break;\n            }\n        }\n\n    // Operator registers\n    } else if ((type >= 0x20 && type <= 0x80) || type == 0xE0) {\n\n        // Convert to operator number\n        int op_num = op_lookup[reg_num & 0x1F];\n\n        // Valid register?\n        if (op_num < 0)\n            return;\n\n        // Is it the other bank of operators?\n        if (reg_num & 0x100)\n            op_num += 18;\n\n        Operator &op = Op[op_num];\n\n        // Do specific registers\n        switch (type) {\n\n            // Tremolo Enable / Vibrato Enable / Sustain Mode / Envelope Scaling / Frequency Multiplier\n            case 0x20: {\n                op.SetTremoloEnable(val & 0x80);\n                op.SetVibratoEnable(val & 0x40);\n                op.SetSustainMode(val & 0x20);\n                op.SetEnvelopeScaling(val & 0x10);\n                op.SetFrequencyMultiplier(val & 15);\n                break;\n            }\n\n            // Key Scale / Output Level\n            case 0x40: {\n                op.SetKeyScale(val >> 6);\n                op.SetOutputLevel(val & 0x3F);\n                break;\n            }\n\n            // Attack Rate / Decay Rate\n            case 0x60: {\n                op.SetAttackRate(val >> 4);\n                op.SetDecayRate(val & 15);\n                break;\n            }\n\n            // Sustain Level / Release Rate\n            case 0x80: {\n                op.SetSustainLevel(val >> 4);\n                op.SetReleaseRate(val & 15);\n                break;\n            }\n\n            // Waveform\n            case 0xE0: {\n                op.SetWaveform(val & 7);\n                break;\n            }\n        }\n    }\n}\n\n\n\n//==================================================================================================\n// Generate sample.  Every time you call this you will get two signed 16-bit samples (one for each\n// stereo channel) which will sound correct when played back at the sample rate given when the\n// class was constructed.\n//==================================================================================================\nvoid Opal::Sample(int16_t *left, int16_t *right) {\n\n    // If the destination sample rate is higher than the OPL3 sample rate, we need to skip ahead\n    while (SampleAccum >= SampleRate) {\n\n        LastOutput[0] = CurrOutput[0];\n        LastOutput[1] = CurrOutput[1];\n\n        Output(CurrOutput[0], CurrOutput[1]);\n\n        SampleAccum -= SampleRate;\n    }\n\n    // Mix with the partial accumulation\n    int32_t omblend = SampleRate - SampleAccum;\n    *left = (LastOutput[0] * omblend + CurrOutput[0] * SampleAccum) / SampleRate;\n    *right = (LastOutput[1] * omblend + CurrOutput[1] * SampleAccum) / SampleRate;\n\n    SampleAccum += OPL3SampleRate;\n}\n\n\n\n//==================================================================================================\n// Produce final output from the chip.  This is at the OPL3 sample-rate.\n//==================================================================================================\nvoid Opal::Output(int16_t &left, int16_t &right) {\n\n    int32_t leftmix = 0, rightmix = 0;\n\n    // Sum the output of each channel\n    for (int i = 0; i < NumChannels; i++) {\n\n        int16_t chanleft, chanright;\n        Chan[i].Output(chanleft, chanright);\n\n        leftmix += chanleft;\n        rightmix += chanright;\n    }\n\n    // Clamp\n    if (leftmix < -0x8000)\n        left = -0x8000;\n    else if (leftmix > 0x7FFF)\n        left = 0x7FFF;\n    else\n        left = leftmix;\n\n    if (rightmix < -0x8000)\n        right = -0x8000;\n    else if (rightmix > 0x7FFF)\n        right = 0x7FFF;\n    else\n        right = rightmix;\n\n    Clock++;\n\n    // Tremolo.  According to this post, the OPL3 tremolo is a 13,440 sample length triangle wave\n    // with a peak at 26 and a trough at 0 and is simply added to the logarithmic level accumulator\n    //      http://forums.submarine.org.uk/phpBB/viewtopic.php?f=9&t=1171\n    TremoloClock = (TremoloClock + 1) % 13440;\n    TremoloLevel = ((TremoloClock < 13440 / 2) ? TremoloClock : 13440 - TremoloClock) / 256;\n    if (!TremoloDepth)\n        TremoloLevel >>= 2;\n\n    // Vibrato.  This appears to be a 8 sample long triangle wave with a magnitude of the three\n    // high bits of the channel frequency, positive and negative, divided by two if the vibrato\n    // depth is zero.  It is only cycled every 1,024 samples.\n    VibratoTick++;\n    if (VibratoTick >= 1024) {\n        VibratoTick = 0;\n        VibratoClock = (VibratoClock + 1) & 7;\n    }\n}\n\n\n\n//==================================================================================================\n// Channel constructor.\n//==================================================================================================\nOpal::Channel::Channel() {\n\n    Master = 0;\n    Freq = 0;\n    Octave = 0;\n    PhaseStep = 0;\n    KeyScaleNumber = 0;\n    FeedbackShift = 0;\n    ModulationType = 0;\n    ChannelPair = 0;\n    Enable = true;\n}\n\n\n\n//==================================================================================================\n// Produce output from channel.\n//==================================================================================================\nvoid Opal::Channel::Output(int16_t &left, int16_t &right) {\n\n    // Has the channel been disabled?  This is usually a result of the 4-op enables being used to\n    // disable the secondary channel in each 4-op pair\n    if (!Enable) {\n        left = right = 0;\n        return;\n    }\n\n    int16_t vibrato = (Freq >> 7) & 7;\n    if (!Master->VibratoDepth)\n        vibrato >>= 1;\n\n    // 0  3  7  3  0  -3  -7  -3\n    uint16_t clk = Master->VibratoClock;\n    if (!(clk & 3))\n        vibrato = 0;                // Position 0 and 4 is zero\n    else {\n        if (clk & 1)\n            vibrato >>= 1;          // Odd positions are half the magnitude\n        if (clk & 4)\n            vibrato = -vibrato;     // The second half positions are negative\n    }\n\n    vibrato <<= Octave;\n\n    // Combine individual operator outputs\n    int16_t out, acc;\n\n    // Running in 4-op mode?\n    if (ChannelPair) {\n\n        // Get the secondary channel's modulation type.  This is the only thing from the secondary\n        // channel that is used\n        if (ChannelPair->GetModulationType() == 0) {\n\n            if (ModulationType == 0) {\n\n                // feedback -> modulator -> modulator -> modulator -> carrier\n                out  = Op[0]->Output(KeyScaleNumber, PhaseStep, vibrato, 0, FeedbackShift);\n                out  = Op[1]->Output(KeyScaleNumber, PhaseStep, vibrato, out, 0);\n                out  = Op[2]->Output(KeyScaleNumber, PhaseStep, vibrato, out, 0);\n                out  = Op[3]->Output(KeyScaleNumber, PhaseStep, vibrato, out, 0);\n\n            } else {\n\n                // (feedback -> carrier) + (modulator -> modulator -> carrier)\n                out  = Op[0]->Output(KeyScaleNumber, PhaseStep, vibrato, 0, FeedbackShift);\n                acc  = Op[1]->Output(KeyScaleNumber, PhaseStep, vibrato, 0, 0);\n                acc  = Op[2]->Output(KeyScaleNumber, PhaseStep, vibrato, acc, 0);\n                out += Op[3]->Output(KeyScaleNumber, PhaseStep, vibrato, acc, 0);\n            }\n\n        } else {\n\n            if (ModulationType == 0) {\n\n                // (feedback -> modulator -> carrier) + (modulator -> carrier)\n                out  = Op[0]->Output(KeyScaleNumber, PhaseStep, vibrato, 0, FeedbackShift);\n                out  = Op[1]->Output(KeyScaleNumber, PhaseStep, vibrato, out, 0);\n                acc  = Op[2]->Output(KeyScaleNumber, PhaseStep, vibrato, 0, 0);\n                out += Op[3]->Output(KeyScaleNumber, PhaseStep, vibrato, acc, 0);\n\n            } else {\n\n                // (feedback -> carrier) + (modulator -> carrier) + carrier\n                out  = Op[0]->Output(KeyScaleNumber, PhaseStep, vibrato, 0, FeedbackShift);\n                acc  = Op[1]->Output(KeyScaleNumber, PhaseStep, vibrato, 0, 0);\n                out += Op[2]->Output(KeyScaleNumber, PhaseStep, vibrato, acc, 0);\n                out += Op[3]->Output(KeyScaleNumber, PhaseStep, vibrato, 0, 0);\n            }\n        }\n\n    } else {\n\n        // Standard 2-op mode\n        if (ModulationType == 0) {\n\n            // Frequency modulation (well, phase modulation technically)\n            out = Op[0]->Output(KeyScaleNumber, PhaseStep, vibrato, 0, FeedbackShift);\n            out = Op[1]->Output(KeyScaleNumber, PhaseStep, vibrato, out, 0);\n\n        } else {\n\n            // Additive\n            out = Op[0]->Output(KeyScaleNumber, PhaseStep, vibrato, 0, FeedbackShift);\n            out += Op[1]->Output(KeyScaleNumber, PhaseStep, vibrato);\n        }\n    }\n\n    left = LeftEnable ? out : 0;\n    right = RightEnable ? out : 0;\n}\n\n\n\n//==================================================================================================\n// Set phase step for operators using this channel.\n//==================================================================================================\nvoid Opal::Channel::SetFrequencyLow(uint16_t freq) {\n\n    Freq = (Freq & 0x300) | (freq & 0xFF);\n    ComputePhaseStep();\n}\n//--------------------------------------------------------------------------------------------------\nvoid Opal::Channel::SetFrequencyHigh(uint16_t freq) {\n\n    Freq = (Freq & 0xFF) | ((freq & 3) << 8);\n    ComputePhaseStep();\n\n    // Only the high bits of Freq affect the Key Scale No.\n    ComputeKeyScaleNumber();\n}\n\n\n\n//==================================================================================================\n// Set the octave of the channel (0 to 7).\n//==================================================================================================\nvoid Opal::Channel::SetOctave(uint16_t oct) {\n\n    Octave = oct & 7;\n    ComputePhaseStep();\n    ComputeKeyScaleNumber();\n}\n\n\n\n//==================================================================================================\n// Keys the channel on/off.\n//==================================================================================================\nvoid Opal::Channel::SetKeyOn(bool on) {\n\n    Op[0]->SetKeyOn(on);\n    Op[1]->SetKeyOn(on);\n}\n\n\n\n//==================================================================================================\n// Enable left stereo channel.\n//==================================================================================================\nvoid Opal::Channel::SetLeftEnable(bool on) {\n\n    LeftEnable = on;\n}\n\n\n\n//==================================================================================================\n// Enable right stereo channel.\n//==================================================================================================\nvoid Opal::Channel::SetRightEnable(bool on) {\n\n    RightEnable = on;\n}\n\n\n\n//==================================================================================================\n// Set the channel feedback amount.\n//==================================================================================================\nvoid Opal::Channel::SetFeedback(uint16_t val) {\n\n    FeedbackShift = val ? 9 - val : 0;\n}\n\n\n\n//==================================================================================================\n// Set frequency modulation/additive modulation\n//==================================================================================================\nvoid Opal::Channel::SetModulationType(uint16_t type) {\n\n    ModulationType = type;\n}\n\n\n\n//==================================================================================================\n// Compute the stepping factor for the operator waveform phase based on the frequency and octave\n// values of the channel.\n//==================================================================================================\nvoid Opal::Channel::ComputePhaseStep() {\n\n    PhaseStep = uint32_t(Freq) << Octave;\n}\n\n\n\n//==================================================================================================\n// Compute the key scale number and key scale levels.\n//\n// From the Yamaha data sheet this is the block/octave number as bits 3-1, with bit 0 coming from\n// the MSB of the frequency if NoteSel is 1, and the 2nd MSB if NoteSel is 0.\n//==================================================================================================\nvoid Opal::Channel::ComputeKeyScaleNumber() {\n\n    uint16_t lsb = Master->NoteSel ? Freq >> 9 : (Freq >> 8) & 1;\n    KeyScaleNumber = Octave << 1 | lsb;\n\n    // Get the channel operators to recompute their rates as they're dependent on this number.  They\n    // also need to recompute their key scale level\n    for (int i = 0; i < 4; i++) {\n\n        if (!Op[i])\n            continue;\n\n        Op[i]->ComputeRates();\n        Op[i]->ComputeKeyScaleLevel();\n    }\n}\n\n\n\n//==================================================================================================\n// Operator constructor.\n//==================================================================================================\nOpal::Operator::Operator() {\n\n    Master = 0;\n    Chan = 0;\n    Phase = 0;\n    Waveform = 0;\n    FreqMultTimes2 = 1;\n    EnvelopeStage = EnvOff;\n    EnvelopeLevel = 0x1FF;\n    AttackRate = 0;\n    DecayRate = 0;\n    SustainLevel = 0;\n    ReleaseRate = 0;\n    KeyScaleShift = 0;\n    KeyScaleLevel = 0;\n    Out[0] = Out[1] = 0;\n    KeyOn = false;\n    KeyScaleRate = false;\n    SustainMode = false;\n    TremoloEnable = false;\n    VibratoEnable = false;\n}\n\n\n\n//==================================================================================================\n// Produce output from operator.\n//==================================================================================================\nint16_t Opal::Operator::Output(uint16_t keyscalenum, uint32_t phase_step, int16_t vibrato, int16_t mod, int16_t fbshift) {\n\n    // Advance wave phase\n    if (VibratoEnable)\n        phase_step += vibrato;\n    Phase += (phase_step * FreqMultTimes2) / 2;\n\n    uint16_t level = (EnvelopeLevel + OutputLevel + KeyScaleLevel + (TremoloEnable ? Master->TremoloLevel : 0)) << 3;\n\n    switch (EnvelopeStage) {\n\n        // Attack stage\n        case EnvAtt: {\n            if (!(AttackRate == 0) && !(AttackMask && (Master->Clock & AttackMask))) {\n                uint16_t add = ((AttackAdd >> AttackTab[Master->Clock >> AttackShift & 7]) * ~EnvelopeLevel) >> 3;\n                EnvelopeLevel += add;\n            }\n            if (EnvelopeLevel <= 0) {\n                EnvelopeLevel = 0;\n                EnvelopeStage = EnvDec;\n            }\n            break;\n        }\n\n        // Decay stage\n        case EnvDec: {\n            if (!(DecayRate == 0) && !(DecayMask && (Master->Clock & DecayMask))) {\n                uint16_t add = DecayAdd >> DecayTab[Master->Clock >> DecayShift & 7];\n                EnvelopeLevel += add;\n            }\n            if (EnvelopeLevel >= SustainLevel) {\n                EnvelopeLevel = SustainLevel;\n                EnvelopeStage = EnvSus;\n            }\n            break;\n        }\n\n        // Sustain stage\n        case EnvSus: {\n\n            if (SustainMode)\n                break;\n        }\n\n        /* fallthrough */\n\n        // Release stage\n        case EnvRel: {\n            if (!(ReleaseRate == 0) && !(ReleaseMask && (Master->Clock & ReleaseMask))) {\n                uint16_t add = ReleaseAdd >> ReleaseTab[Master->Clock >> ReleaseShift & 7];\n                EnvelopeLevel += add;\n            }\n            if (EnvelopeLevel >= 0x1FF) {\n                EnvelopeLevel = 0x1FF;\n                EnvelopeStage = EnvOff;\n                Out[0] = Out[1] = 0;\n                return 0;\n            }\n            break;\n        }\n\n        // Envelope, and therefore the operator, is not running\n        default:\n            Out[0] = Out[1] = 0;\n            return 0;\n    }\n\n    // Feedback?  In that case we modulate by a blend of the last two samples\n    if (fbshift)\n        mod += (Out[0] + Out[1]) >> fbshift;\n\n    uint16_t phase = (Phase >> 10) + mod;\n    uint16_t offset = phase & 0xFF;\n    uint16_t logsin;\n    bool negate = false;\n\n    switch (Waveform) {\n\n        //------------------------------------\n        // Standard sine wave\n        //------------------------------------\n        case 0:\n            if (phase & 0x100)\n                offset ^= 0xFF;\n            logsin = Master->LogSinTable[offset];\n            negate = (phase & 0x200);\n            break;\n\n        //------------------------------------\n        // Half sine wave\n        //------------------------------------\n        case 1:\n            if (phase & 0x200)\n                offset = 0;\n            else if (phase & 0x100)\n                offset ^= 0xFF;\n            logsin = Master->LogSinTable[offset];\n            break;\n\n        //------------------------------------\n        // Positive sine wave\n        //------------------------------------\n        case 2:\n            if (phase & 0x100)\n                offset ^= 0xFF;\n            logsin =  Master->LogSinTable[offset];\n            break;\n\n        //------------------------------------\n        // Quarter positive sine wave\n        //------------------------------------\n        case 3:\n            if (phase & 0x100)\n                offset = 0;\n            logsin =  Master->LogSinTable[offset];\n            break;\n\n        //------------------------------------\n        // Double-speed sine wave\n        //------------------------------------\n        case 4:\n            if (phase & 0x200)\n                offset = 0;\n\n            else {\n\n                if (phase & 0x80)\n                    offset ^= 0xFF;\n\n                offset = (offset + offset) & 0xFF;\n                negate = (phase & 0x100);\n            }\n\n            logsin =  Master->LogSinTable[offset];\n            break;\n\n        //------------------------------------\n        // Double-speed positive sine wave\n        //------------------------------------\n        case 5:\n            if (phase & 0x200)\n                offset = 0;\n\n            else {\n\n                offset = (offset + offset) & 0xFF;\n                if (phase & 0x80)\n                    offset ^= 0xFF;\n            }\n\n            logsin =  Master->LogSinTable[offset];\n            break;\n\n        //------------------------------------\n        // Square wave\n        //------------------------------------\n        case 6:\n            logsin = 0;\n            negate = (phase & 0x200);\n            break;\n\n        //------------------------------------\n        // Exponentiation wave\n        //------------------------------------\n        default:\n            logsin = phase & 0x1FF;\n            if (phase & 0x200) {\n                logsin ^= 0x1FF;\n                negate = true;\n            }\n            logsin <<= 3;\n            break;\n    }\n\n    uint16_t mix = logsin + level;\n    if (mix > 0x1FFF)\n        mix = 0x1FFF;\n\n    // From the OPLx decapsulated docs:\n    // \"When such a table is used for calculation of the exponential, the table is read at the\n    // position given by the 8 LSB's of the input. The value + 1024 (the hidden bit) is then the\n    // significand of the floating point output and the yet unused MSB's of the input are the\n    // exponent of the floating point output.\"\n    int16_t v = ((Master->ExpTable[mix & 0xFF] + 1024) << 1) >> (mix >> 8);\n    if (negate)\n        v = ~v;\n\n    // Keep last two results for feedback calculation\n    Out[1] = Out[0];\n    Out[0] = v;\n\n    return v;\n}\n\n\n\n//==================================================================================================\n// Trigger operator.\n//==================================================================================================\nvoid Opal::Operator::SetKeyOn(bool on) {\n\n    // Already on/off?\n    if (KeyOn == on)\n        return;\n    KeyOn = on;\n\n    if (on) {\n\n        // The highest attack rate is instant; it bypasses the attack phase\n        if (AttackRate == 15) {\n            EnvelopeStage = EnvDec;\n            EnvelopeLevel = 0;\n        } else\n            EnvelopeStage = EnvAtt;\n\n        Phase = 0;\n\n    } else {\n\n        // Stopping current sound?\n        if (EnvelopeStage != EnvOff && EnvelopeStage != EnvRel)\n            EnvelopeStage = EnvRel;\n    }\n}\n\n\n\n//==================================================================================================\n// Enable amplitude vibrato.\n//==================================================================================================\nvoid Opal::Operator::SetTremoloEnable(bool on) {\n\n    TremoloEnable = on;\n}\n\n\n\n//==================================================================================================\n// Enable frequency vibrato.\n//==================================================================================================\nvoid Opal::Operator::SetVibratoEnable(bool on) {\n\n    VibratoEnable = on;\n}\n\n\n\n//==================================================================================================\n// Sets whether we release or sustain during the sustain phase of the envelope.  'true' is to\n// sustain, otherwise release.\n//==================================================================================================\nvoid Opal::Operator::SetSustainMode(bool on) {\n\n    SustainMode = on;\n}\n\n\n\n//==================================================================================================\n// Key scale rate.  Sets how much the Key Scaling Number affects the envelope rates.\n//==================================================================================================\nvoid Opal::Operator::SetEnvelopeScaling(bool on) {\n\n    KeyScaleRate = on;\n    ComputeRates();\n}\n\n\n\n//==================================================================================================\n// Multiplies the phase frequency.\n//==================================================================================================\nvoid Opal::Operator::SetFrequencyMultiplier(uint16_t scale) {\n\n    // Needs to be multiplied by two (and divided by two later when we use it) because the first\n    // entry is actually .5\n    const uint16_t mul_times_2[] = {\n        1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30,\n    };\n\n    FreqMultTimes2 = mul_times_2[scale & 15];\n}\n\n\n\n//==================================================================================================\n// Attenuates output level towards higher pitch.\n//==================================================================================================\nvoid Opal::Operator::SetKeyScale(uint16_t scale) {\n\n    static const uint8_t kslShift[4] = { 8, 1, 2, 0 };\n    KeyScaleShift = kslShift[scale & 3];\n    ComputeKeyScaleLevel();\n}\n\n\n\n//==================================================================================================\n// Sets the output level (volume) of the operator.\n//==================================================================================================\nvoid Opal::Operator::SetOutputLevel(uint16_t level) {\n\n    OutputLevel = level * 4;\n}\n\n\n\n//==================================================================================================\n// Operator attack rate.\n//==================================================================================================\nvoid Opal::Operator::SetAttackRate(uint16_t rate) {\n\n    AttackRate = rate;\n\n    ComputeRates();\n}\n\n\n\n//==================================================================================================\n// Operator decay rate.\n//==================================================================================================\nvoid Opal::Operator::SetDecayRate(uint16_t rate) {\n\n    DecayRate = rate;\n\n    ComputeRates();\n}\n\n\n\n//==================================================================================================\n// Operator sustain level.\n//==================================================================================================\nvoid Opal::Operator::SetSustainLevel(uint16_t level) {\n\n    SustainLevel = level < 15 ? level : 31;\n    SustainLevel *= 16;\n}\n\n\n\n//==================================================================================================\n// Operator release rate.\n//==================================================================================================\nvoid Opal::Operator::SetReleaseRate(uint16_t rate) {\n\n    ReleaseRate = rate;\n\n    ComputeRates();\n}\n\n\n\n//==================================================================================================\n// Assign the waveform this operator will use.\n//==================================================================================================\nvoid Opal::Operator::SetWaveform(uint16_t wave) {\n\n    Waveform = wave & 7;\n}\n\n\n\n//==================================================================================================\n// Compute actual rate from register rate.  From the Yamaha data sheet:\n//\n// Actual rate = Rate value * 4 + Rof, if Rate value = 0, actual rate = 0\n//\n// Rof is set as follows depending on the KSR setting:\n//\n//  Key scale   0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15\n//  KSR = 0     0   0   0   0   1   1   1   1   2   2   2   2   3   3   3   3\n//  KSR = 1     0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15\n//\n// Note: zero rates are infinite, and are treated separately elsewhere\n//==================================================================================================\nvoid Opal::Operator::ComputeRates() {\n\n    int combined_rate = AttackRate * 4 + (Chan->GetKeyScaleNumber() >> (KeyScaleRate ? 0 : 2));\n    int rate_high = combined_rate >> 2;\n    int rate_low = combined_rate & 3;\n\n    AttackShift = rate_high < 12 ? 12 - rate_high : 0;\n    AttackMask = (1 << AttackShift) - 1;\n    AttackAdd = (rate_high < 12) ? 1 : 1 << (rate_high - 12);\n    AttackTab = Master->RateTables[rate_low];\n\n    // Attack rate of 15 is always instant\n    if (AttackRate == 15)\n        AttackAdd = 0xFFF;\n\n    combined_rate = DecayRate * 4 + (Chan->GetKeyScaleNumber() >> (KeyScaleRate ? 0 : 2));\n    rate_high = combined_rate >> 2;\n    rate_low = combined_rate & 3;\n\n    DecayShift = rate_high < 12 ? 12 - rate_high : 0;\n    DecayMask = (1 << DecayShift) - 1;\n    DecayAdd = (rate_high < 12) ? 1 : 1 << (rate_high - 12);\n    DecayTab = Master->RateTables[rate_low];\n\n    combined_rate = ReleaseRate * 4 + (Chan->GetKeyScaleNumber() >> (KeyScaleRate ? 0 : 2));\n    rate_high = combined_rate >> 2;\n    rate_low = combined_rate & 3;\n\n    ReleaseShift = rate_high < 12 ? 12 - rate_high : 0;\n    ReleaseMask = (1 << ReleaseShift) - 1;\n    ReleaseAdd = (rate_high < 12) ? 1 : 1 << (rate_high - 12);\n    ReleaseTab = Master->RateTables[rate_low];\n}\n\n\n\n//==================================================================================================\n// Compute the operator's key scale level.  This changes based on the channel frequency/octave and\n// operator key scale value.\n//==================================================================================================\nvoid Opal::Operator::ComputeKeyScaleLevel() {\n\n    static const uint16_t levtab[] = {\n        0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,      0,\n        0,      0,      0,      0,      0,      0,      0,      0,      0,      8,      12,     16,     20,     24,     28,     32,\n        0,      0,      0,      0,      0,      12,     20,     28,     32,     40,     44,     48,     52,     56,     60,     64,\n        0,      0,      0,      20,     32,     44,     52,     60,     64,     72,     76,     80,     84,     88,     92,     96,\n        0,      0,      32,     52,     64,     76,     84,     92,     96,     104,    108,    112,    116,    120,    124,    128,\n        0,      32,     64,     84,     96,     108,    116,    124,    128,    136,    140,    144,    148,    152,    156,    160,\n        0,      64,     96,     116,    128,    140,    148,    156,    160,    168,    172,    176,    180,    184,    188,    192,\n        0,      96,     128,    148,    160,    172,    180,    188,    192,    200,    204,    208,    212,    216,    220,    224,\n    };\n\n    // This uses a combined value of the top four bits of frequency with the octave/block\n    uint16_t i = (Chan->GetOctave() << 4) | (Chan->GetFreq() >> 6);\n    KeyScaleLevel = levtab[i] >> KeyScaleShift;\n}\n"
  },
  {
    "path": "contrib/rad/player20.cpp",
    "content": "/*\n\n    C++ player code for Reality Adlib Tracker 2.0a (file version 2.1).\n\n    Please note, this is just the player code.  This does no checking of the tune data before\n    it tries to play it, as most use cases will be a known tune being used in a production.\n    So if you're writing an application that loads unknown tunes in at run time then you'll\n    want to do more validity checking.\n\n    To use:\n\n        - Instantiate the RADPlayer object\n\n        - Initialise player for your tune by calling the Init() method.  Supply a pointer to the\n          tune file and a function for writing to the OPL3 registers.\n\n        - Call the Update() method a number of times per second as returned by GetHertz().  If\n          your tune is using the default BPM setting you can safely just call it 50 times a\n          second, unless it's a legacy \"slow-timer\" tune then it'll need to be 18.2 times a\n          second.\n\n        - When you're done, stop calling Update() and call the Stop() method to turn off all\n          sound and reset the OPL3 hardware.\n\n*/\n\n\n\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n\n\n\n#ifndef RAD_DETECT_REPEATS\n#define RAD_DETECT_REPEATS  0\n#endif\n\n\n\n//==================================================================================================\n// RAD player class.\n//==================================================================================================\nclass RADPlayer {\n\n    // Various constants\n    enum {\n        kTracks             = 100,\n        kChannels           = 9,\n        kTrackLines         = 64,\n        kRiffTracks         = 10,\n        kInstruments        = 127,\n\n        cmPortamentoUp      = 0x1,\n        cmPortamentoDwn     = 0x2,\n        cmToneSlide         = 0x3,\n        cmToneVolSlide      = 0x5,\n        cmVolSlide          = 0xA,\n        cmSetVol            = 0xC,\n        cmJumpToLine        = 0xD,\n        cmSetSpeed          = 0xF,\n        cmIgnore            = ('I' - 55),\n        cmMultiplier        = ('M' - 55),\n        cmRiff              = ('R' - 55),\n        cmTranspose         = ('T' - 55),\n        cmFeedback          = ('U' - 55),\n        cmVolume            = ('V' - 55)\n    };\n\n    enum e_Source {\n        SNone, SRiff, SIRiff\n    };\n\n    enum {\n        fKeyOn              = 1 << 0,\n        fKeyOff             = 1 << 1,\n        fKeyedOn            = 1 << 2\n    };\n\n    struct CInstrument {\n        uint8_t             Feedback[2];\n        uint8_t             Panning[2];\n        uint8_t             Algorithm;\n        uint8_t             Detune;\n        uint8_t             Volume;\n        uint8_t             RiffSpeed;\n        uint8_t *           Riff;\n        uint8_t             Operators[4][5];\n    };\n\n    struct CEffects {\n        int8_t              PortSlide;\n        int8_t              VolSlide;\n        uint16_t            ToneSlideFreq;\n        uint8_t             ToneSlideOct;\n        uint8_t             ToneSlideSpeed;\n        int8_t              ToneSlideDir;\n    };\n\n    struct CChannel {\n        uint8_t             LastInstrument;\n        CInstrument *       Instrument;\n        uint8_t             Volume;\n        uint8_t             DetuneA;\n        uint8_t             DetuneB;\n        uint8_t             KeyFlags;\n        uint16_t            CurrFreq;\n        int8_t              CurrOctave;\n        CEffects            FX;\n        struct CRiff {\n            CEffects        FX;\n            uint8_t *       Track;\n            uint8_t *       TrackStart;\n            uint8_t         Line;\n            uint8_t         Speed;\n            uint8_t         SpeedCnt;\n            int8_t          TransposeOctave;\n            int8_t          TransposeNote;\n            uint8_t         LastInstrument;\n            bool            Updated;\n        } Riff, IRiff;\n    };\n\n    public:\n                            RADPlayer() : Initialised(false) {}\n        void                Init(const void *tune, void (*opl3)(void *, uint16_t, uint8_t), void *arg);\n        void                Stop();\n        bool                Update();\n        int                 GetHertz() const {  return Hertz;  }\n        int                 GetPlayTimeInSeconds() const {  return PlayTime / Hertz;  }\n        int                 GetTunePos() const {  return Order;  }\n        int                 GetTuneLength() const {  return OrderListSize;  }\n        int                 GetTuneLine() const {  return Line;  }\n        void                SetMasterVolume(int vol) {  MasterVol = vol;  }\n        int                 GetMasterVolume() const {  return MasterVol;  }\n        int                 GetSpeed() const {  return Speed;  }\n\n/* BEGIN MEGAZEUX ADDITIONS */\n\n        void                SetTunePos(uint32_t order, uint32_t line);\n        int                 GetTuneEffectiveLength();\n\n/* END MEGAZEUX ADDITIONS */\n\n#if RAD_DETECT_REPEATS\n        uint32_t            ComputeTotalTime();\n#endif\n\n    private:\n        bool                UnpackNote(uint8_t *&s, uint8_t &last_instrument);\n        uint8_t *           GetTrack();\n        uint8_t *           SkipToLine(uint8_t *trk, uint8_t linenum, bool chan_riff = false);\n        void                PlayLine();\n        void                PlayNote(int channum, int8_t notenum, int8_t octave, uint16_t instnum, uint8_t cmd = 0, uint8_t param = 0, e_Source src = SNone, int op = 0);\n        void                LoadInstrumentOPL3(int channum);\n        void                PlayNoteOPL3(int channum, int8_t octave, int8_t note);\n        void                ResetFX(CEffects *fx);\n        void                TickRiff(int channum, CChannel::CRiff &riff, bool chan_riff);\n        void                ContinueFX(int channum, CEffects *fx);\n        void                SetVolume(int channum, uint8_t vol);\n        void                GetSlideDir(int channum, CEffects *fx);\n        void                LoadInstMultiplierOPL3(int channum, int op, uint8_t mult);\n        void                LoadInstVolumeOPL3(int channum, int op, uint8_t vol);\n        void                LoadInstFeedbackOPL3(int channum, int which, uint8_t fb);\n        void                Portamento(uint16_t channum, CEffects *fx, int8_t amount, bool toneslide);\n        void                Transpose(int8_t note, int8_t octave);\n        void                SetOPL3(uint16_t reg, uint8_t val) {\n                                OPL3Regs[reg] = val;\n                                OPL3(OPL3Arg, reg, val);\n                            }\n        uint8_t             GetOPL3(uint16_t reg) const {\n                                return OPL3Regs[reg];\n                            }\n\n/* BEGIN MEGAZEUX ADDITIONS */\n\n        void                Init10(const void *tune);\n        bool                UnpackNote10(uint8_t *&s);\n        uint8_t *           SkipToLine10(uint8_t *trk, uint8_t linenum);\n        uint8_t             FixRadv21KSLVolume(uint8_t val);\n        int                 LastPatternOrder;\n        bool                Is10;\n\n/* END MEGAZEUX ADDITIONS */\n\n        void                (*OPL3)(void *, uint16_t, uint8_t);\n        void *              OPL3Arg;\n        CInstrument         Instruments[kInstruments];\n        CChannel            Channels[kChannels];\n        uint32_t            PlayTime;\n#if RAD_DETECT_REPEATS\n        uint32_t            OrderMap[4];\n        bool                Repeating;\n#endif\n        int16_t             Hertz;\n        uint8_t *           OrderList;\n        uint8_t *           Tracks[kTracks];\n        uint8_t *           Riffs[kRiffTracks][kChannels];\n        uint8_t *           Track;\n        bool                Initialised;\n        uint8_t             Speed;\n        uint8_t             OrderListSize;\n        uint8_t             SpeedCnt;\n        uint8_t             Order;\n        uint8_t             Line;\n        int8_t              Entrances;\n        uint8_t             MasterVol;\n        int8_t              LineJump;\n        uint8_t             OPL3Regs[512];\n\n        // Values exported by UnpackNote()\n        int8_t              NoteNum;\n        int8_t              OctaveNum;\n        uint8_t             InstNum;\n        uint8_t             EffectNum;\n        uint8_t             Param;\n        // Unused field, commented out to suppress warnings.\n        //bool                LastNote;\n\n        static const int8_t NoteSize[];\n        static const uint16_t ChanOffsets3[9], Chn2Offsets3[9];\n        static const uint16_t NoteFreq[];\n        static const uint16_t OpOffsets3[9][4];\n        static const bool   AlgCarriers[7][4];\n};\n//--------------------------------------------------------------------------------------------------\nconst int8_t RADPlayer::NoteSize[] = { 0, 2, 1, 3, 1, 3, 2, 4 };\nconst uint16_t RADPlayer::ChanOffsets3[9] = { 0, 1, 2, 0x100, 0x101, 0x102, 6, 7, 8 };              // OPL3 first channel\nconst uint16_t RADPlayer::Chn2Offsets3[9] = { 3, 4, 5, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108 };  // OPL3 second channel\nconst uint16_t RADPlayer::NoteFreq[] = { 0x16b,0x181,0x198,0x1b0,0x1ca,0x1e5,0x202,0x220,0x241,0x263,0x287,0x2ae };\nconst uint16_t RADPlayer::OpOffsets3[9][4] = {\n    {  0x00B, 0x008, 0x003, 0x000  },\n    {  0x00C, 0x009, 0x004, 0x001  },\n    {  0x00D, 0x00A, 0x005, 0x002  },\n    {  0x10B, 0x108, 0x103, 0x100  },\n    {  0x10C, 0x109, 0x104, 0x101  },\n    {  0x10D, 0x10A, 0x105, 0x102  },\n    {  0x113, 0x110, 0x013, 0x010  },\n    {  0x114, 0x111, 0x014, 0x011  },\n    {  0x115, 0x112, 0x015, 0x012  }\n};\nconst bool RADPlayer::AlgCarriers[7][4] = {\n    {  true, false, false, false  },  // 0 - 2op - op < op\n    {  true, true,  false, false  },  // 1 - 2op - op + op\n    {  true, false, false, false  },  // 2 - 4op - op < op < op < op\n    {  true, false, false, true   },  // 3 - 4op - op < op < op + op\n    {  true, false, true,  false  },  // 4 - 4op - op < op + op < op\n    {  true, false, true,  true   },  // 5 - 4op - op < op + op + op\n    {  true, true,  true,  true   },  // 6 - 4op - op + op + op + op\n};\n\n\n\n//==================================================================================================\n// Initialise a RAD tune for playback.  This assumes the tune data is valid and does minimal data\n// checking.\n//==================================================================================================\nvoid RADPlayer::Init(const void *tune, void (*opl3)(void *, uint16_t, uint8_t), void *arg) {\n\n    Initialised = false;\n    Is10 = false;\n    LastPatternOrder = -1;\n\n    // Version check; we only support version 2.1 tune files\n    uint8_t version = *((uint8_t *)tune + 0x10);\n    if ((version != 0x10) && (version != 0x21)) {\n        Hertz = -1;\n        return;\n    }\n\n    // The OPL3 call-back\n    OPL3 = opl3;\n    OPL3Arg = arg;\n\n    for (int i = 0; i < kTracks; i++)\n        Tracks[i] = 0;\n\n    for (int i = 0; i < kRiffTracks; i++)\n        for (int j = 0; j < kChannels; j++)\n            Riffs[i][j] = 0;\n\n    // These aren't guaranteed to all be stored in the file...\n    memset(&Instruments, 0, sizeof(Instruments));\n\n    if(version == 0x10)\n    {\n        Is10 = true;\n        Init10(tune);\n        return;\n    }\n\n    uint8_t *s = (uint8_t *)tune + 0x11;\n\n    uint8_t flags = *s++;\n    Speed = flags & 0x1F;\n\n    // Is BPM value present?\n    Hertz = 50;\n    if (flags & 0x20) {\n        Hertz = (s[0] | (int(s[1]) << 8)) * 2 / 5;\n        s += 2;\n    }\n\n    // Slow timer tune?  Return an approximate hz\n    if (flags & 0x40)\n        Hertz = 18;\n\n    // Skip any description\n    while (*s)\n        s++;\n    s++;\n\n    // Unpack the instruments\n    while (1) {\n\n        // Instrument number, 0 indicates end of list\n        uint8_t inst_num = *s++;\n        if (inst_num == 0)\n            break;\n\n        // Skip instrument name\n        uint8_t name_len = *(s++);\n        s += name_len;\n\n        CInstrument &inst = Instruments[inst_num - 1];\n\n        uint8_t alg = *s++;\n        inst.Algorithm = alg & 7;\n        inst.Panning[0] = (alg >> 3) & 3;\n        inst.Panning[1] = (alg >> 5) & 3;\n\n        if (inst.Algorithm < 7) {\n\n            uint8_t b = *s++;\n            inst.Feedback[0] = b & 15;\n            inst.Feedback[1] = b >> 4;\n\n            b = *s++;\n            inst.Detune = b >> 4;\n            inst.RiffSpeed = b & 15;\n\n            inst.Volume = *s++;\n\n            for (int i = 0; i < 4; i++) {\n                uint8_t *op = inst.Operators[i];\n                op[0] = *s++;\n                op[1] = FixRadv21KSLVolume(*s++);\n                op[2] = *s++;\n                op[3] = *s++;\n                op[4] = *s++;\n            }\n\n        } else {\n\n            // Ignore MIDI instrument data\n            s += 6;\n        }\n\n        // Instrument riff?\n        if (alg & 0x80) {\n            int size = s[0] | (int(s[1]) << 8);\n            s += 2;\n            inst.Riff = s;\n            s += size;\n        } else\n            inst.Riff = 0;\n    }\n\n    // Get order list\n    OrderListSize = *s++;\n    OrderList = s;\n    s += OrderListSize;\n\n    // Locate the tracks\n    while (1) {\n\n        // Track number\n        uint8_t track_num = *s++;\n        if (track_num >= kTracks)\n            break;\n\n        // Track size in bytes\n        int size = s[0] | (int(s[1]) << 8);\n        s += 2;\n\n        Tracks[track_num] = s;\n        s += size;\n    }\n\n    // Locate the riffs\n    while (1) {\n\n        // Riff id\n        uint8_t riffid = *s++;\n        uint8_t riffnum = riffid >> 4;\n        uint8_t channum = riffid & 15;\n        if (riffnum >= kRiffTracks || channum > kChannels)\n            break;\n\n        // Track size in bytes\n        int size = s[0] | (int(s[1]) << 8);\n        s += 2;\n\n        Riffs[riffnum][channum - 1] = s;\n        s += size;\n    }\n\n    // Done parsing tune, now set up for play\n    for (int i = 0; i < 512; i++)\n        OPL3Regs[i] = 255;\n    Stop();\n\n    Initialised = true;\n}\n\n\n\n//==================================================================================================\n// Stop all sounds and reset the tune.  Tune will play from the beginning again if you continue to\n// Update().\n//==================================================================================================\nvoid RADPlayer::Stop() {\n\n    // Clear all registers\n    for (uint16_t reg = 0x20; reg < 0xF6; reg++) {\n\n        // Ensure envelopes decay all the way\n        uint8_t val = (reg >= 0x60 && reg < 0xA0) ? 0xFF : 0;\n\n        SetOPL3(reg, val);\n        SetOPL3(reg + 0x100, val);\n    }\n\n    // Configure OPL3\n    SetOPL3(1, 0x20);  // Allow waveforms\n    SetOPL3(8, 0);     // No split point\n    SetOPL3(0xbd, 0);  // No drums, etc.\n    SetOPL3(0x104, 0); // Everything 2-op by default\n    SetOPL3(0x105, 1); // OPL3 mode on\n\n#if RAD_DETECT_REPEATS\n    // The order map keeps track of which patterns we've played so we can detect when the tune\n    // starts to repeat.  Jump markers can't be reliably used for this\n    PlayTime = 0;\n    Repeating = false;\n    for (int i = 0; i < 4; i++)\n        OrderMap[i] = 0;\n#endif\n\n    // Initialise play values\n    SpeedCnt = 1;\n    Order = 0;\n    Track = GetTrack();\n    Line = 0;\n    Entrances = 0;\n    MasterVol = 64;\n\n    // Initialise channels\n    for (int i = 0; i < kChannels; i++) {\n        CChannel &chan = Channels[i];\n        chan.LastInstrument = 0;\n        chan.Instrument = 0;\n        chan.Volume = 0;\n        chan.DetuneA = 0;\n        chan.DetuneB = 0;\n        chan.KeyFlags = 0;\n        chan.Riff.SpeedCnt = 0;\n        chan.IRiff.SpeedCnt = 0;\n    }\n}\n\n\n\n//==================================================================================================\n// Playback update.  Call BPM * 2 / 5 times a second.  Use GetHertz() for this number after the\n// tune has been initialised.  Returns true if tune is starting to repeat.\n//==================================================================================================\nbool RADPlayer::Update() {\n\n    if (!Initialised)\n        return false;\n\n    // Run riffs\n    for (int i = 0; i < kChannels; i++) {\n        CChannel &chan = Channels[i];\n        TickRiff(i, chan.IRiff, false);\n        TickRiff(i, chan.Riff, true);\n    }\n\n    // Run main track\n    PlayLine();\n\n    // Run effects\n    for (int i = 0; i < kChannels; i++) {\n        CChannel &chan = Channels[i];\n        ContinueFX(i, &chan.IRiff.FX);\n        ContinueFX(i, &chan.Riff.FX);\n        ContinueFX(i, &chan.FX);\n    }\n\n    // Update play time.  We convert to seconds when queried\n    PlayTime++;\n\n#if RAD_DETECT_REPEATS\n    return Repeating;\n#else\n    return false;\n#endif\n}\n\n\n\n//==================================================================================================\n// Unpacks a single RAD note.\n//==================================================================================================\nbool RADPlayer::UnpackNote(uint8_t *&s, uint8_t &last_instrument) {\n\n    if(Is10)\n        return UnpackNote10(s);\n\n    uint8_t chanid = *s++;\n\n    InstNum   = 0;\n    EffectNum = 0;\n    Param     = 0;\n\n    // Unpack note data\n    uint8_t note = 0;\n    if (chanid & 0x40) {\n        uint8_t n = *s++;\n        note = n & 0x7F;\n\n        // Retrigger last instrument?\n        if (n & 0x80)\n            InstNum = last_instrument;\n    }\n\n    // Do we have an instrument?\n    if (chanid & 0x20) {\n        InstNum = *s++;\n        last_instrument = InstNum;\n    }\n\n    // Do we have an effect?\n    if (chanid & 0x10) {\n        EffectNum = *s++;\n        Param = *s++;\n    }\n\n    NoteNum = note & 15;\n    OctaveNum = note >> 4;\n\n    return ((chanid & 0x80) != 0);\n}\n\n\n\n//==================================================================================================\n// Get current track as indicated by order list.\n//==================================================================================================\nuint8_t *RADPlayer::GetTrack() {\n\n    // If at end of tune start again from beginning\n    if (Order >= OrderListSize)\n        Order = 0;\n\n    uint8_t track_num = OrderList[Order];\n\n    // Jump marker?  Note, we don't recognise multiple jump markers as that could put us into an\n    // infinite loop\n    if (track_num & 0x80) {\n        Order = track_num & 0x7F;\n        track_num = OrderList[Order] & 0x7F;\n    }\n\n#if RAD_DETECT_REPEATS\n    // Check for tune repeat, and mark order in order map\n    if (Order < 128) {\n        int byte = Order >> 5;\n        uint32_t bit = uint32_t(1) << (Order & 31);\n        if (OrderMap[byte] & bit)\n            Repeating = true;\n        else\n            OrderMap[byte] |= bit;\n    }\n#endif\n\n    return Tracks[track_num];\n}\n\n\n\n//==================================================================================================\n// Skip through track till we reach the given line or the next higher one.  Returns null if none.\n//==================================================================================================\nuint8_t *RADPlayer::SkipToLine(uint8_t *trk, uint8_t linenum, bool chan_riff) {\n\n    if (Is10)\n        return SkipToLine10(trk, linenum);\n\n    while (1) {\n\n        uint8_t lineid = *trk;\n        if ((lineid & 0x7F) >= linenum)\n            return trk;\n        if (lineid & 0x80)\n            break;\n        trk++;\n\n        // Skip channel notes\n        uint8_t chanid;\n        do {\n            chanid = *trk++;\n            trk += NoteSize[(chanid >> 4) & 7];\n        } while (!(chanid & 0x80) && !chan_riff);\n    }\n\n    return 0;\n}\n\n\n\n//==================================================================================================\n// Plays one line of current track and advances pointers.\n//==================================================================================================\nvoid RADPlayer::PlayLine() {\n\n    SpeedCnt--;\n    if (SpeedCnt > 0)\n        return;\n    SpeedCnt = Speed;\n\n    // Reset channel effects\n    for (int i = 0; i < kChannels; i++)\n        ResetFX(&Channels[i].FX);\n\n    LineJump = -1;\n\n    // At the right line?\n    uint8_t *trk = Track;\n    if (trk && (*trk & 0x7F) <= Line) {\n        uint8_t lineid = *trk++;\n\n        // Run through channels\n        bool last;\n        do {\n            int channum = *trk & 15;\n            CChannel &chan = Channels[channum];\n            last = UnpackNote(trk, chan.LastInstrument);\n            PlayNote(channum, NoteNum, OctaveNum, InstNum, EffectNum, Param);\n        } while (!last);\n\n        // Was this the last line?\n        if (lineid & 0x80)\n            trk = 0;\n\n        Track = trk;\n    }\n\n    // Move to next line\n    Line++;\n    if (Line >= kTrackLines || LineJump >= 0) {\n\n        if (LineJump >= 0)\n            Line = LineJump;\n        else\n            Line = 0;\n\n        // Move to next track in order list\n        Order++;\n        Track = GetTrack();\n\n        // NOTE: This fixes a bug where the vanilla copy of this player\n        // fails to handle Dxx correctly. See: mindflux.rad order 13. -Lachesis\n        if(Line > 0)\n            Track = SkipToLine(Track, Line, false);\n    }\n}\n\n\n\n//==================================================================================================\n// Play a single note.  Returns the line number in the next pattern to jump to if a jump command was\n// found, or -1 if none.\n//==================================================================================================\nvoid RADPlayer::PlayNote(int channum, int8_t notenum, int8_t octave, uint16_t instnum, uint8_t cmd, uint8_t param, e_Source src, int op) {\n    CChannel &chan = Channels[channum];\n\n    // Recursion detector.  This is needed as riffs can trigger other riffs, and they could end up\n    // in a loop\n    if (Entrances >= 8)\n        return;\n    Entrances++;\n\n    // Select which effects source we're using\n    CEffects *fx = &chan.FX;\n    if (src == SRiff)\n        fx = &chan.Riff.FX;\n    else if (src == SIRiff)\n        fx = &chan.IRiff.FX;\n\n    bool transposing = false;\n\n    // For tone-slides the note is the target\n    if (cmd == cmToneSlide) {\n        if (notenum > 0 && notenum <= 12) {\n            fx->ToneSlideOct = octave;\n            fx->ToneSlideFreq = NoteFreq[notenum - 1];\n        }\n        goto toneslide;\n    }\n\n    // Playing a new instrument?\n    if (instnum > 0) {\n        CInstrument *oldinst = chan.Instrument;\n        CInstrument *inst = &Instruments[instnum - 1];\n        chan.Instrument = inst;\n\n        // Ignore MIDI instruments\n        // NOTE: the vanilla player does this, meaning no effects or new riffs\n        // will trigger on this line.\n        /*\n        if (inst->Algorithm == 7) {\n            Entrances--;\n            return;\n        }\n        */\n        if (inst->Algorithm < 7) {\n\n        LoadInstrumentOPL3(channum);\n\n        // Bounce the channel\n        chan.KeyFlags |= fKeyOff | fKeyOn;\n\n        ResetFX(&chan.IRiff.FX);\n\n        if (src != SIRiff || inst != oldinst) {\n\n            // Instrument riff?\n            if (inst->Riff && inst->RiffSpeed > 0) {\n\n                chan.IRiff.Track = chan.IRiff.TrackStart = inst->Riff;\n                chan.IRiff.Line = 0;\n                chan.IRiff.Speed = inst->RiffSpeed;\n                chan.IRiff.LastInstrument = 0;\n\n                // Note given with riff command is used to transpose the riff\n                if (notenum >= 1 && notenum <= 12) {\n                    chan.IRiff.TransposeOctave = octave;\n                    chan.IRiff.TransposeNote = notenum;\n                    transposing = true;\n                } else {\n                    chan.IRiff.TransposeOctave = 3;\n                    chan.IRiff.TransposeNote = 12;\n                }\n\n                // Do first tick of riff\n                chan.IRiff.SpeedCnt = 1;\n                TickRiff(channum, chan.IRiff, false);\n\n            } else\n                chan.IRiff.SpeedCnt = 0;\n        }\n        }\n        else\n            chan.Instrument = 0;\n    }\n\n    // Starting a channel riff?\n    if (cmd == cmRiff || cmd == cmTranspose) {\n\n        ResetFX(&chan.Riff.FX);\n\n        uint8_t p0 = param / 10;\n        uint8_t p1 = param % 10;\n        chan.Riff.Track = p1 > 0 ? Riffs[p0][p1 - 1] : 0;\n        if (chan.Riff.Track) {\n\n            chan.Riff.TrackStart = chan.Riff.Track;\n            chan.Riff.Line = 0;\n            chan.Riff.Speed = Speed;\n            chan.Riff.LastInstrument = 0;\n\n            // Note given with riff command is used to transpose the riff\n            if (cmd == cmTranspose && notenum >= 1 && notenum <= 12) {\n                chan.Riff.TransposeOctave = octave;\n                chan.Riff.TransposeNote = notenum;\n                transposing = true;\n            } else {\n                chan.Riff.TransposeOctave = 3;\n                chan.Riff.TransposeNote = 12;\n            }\n\n            // Do first tick of riff\n            chan.Riff.SpeedCnt = 1;\n            TickRiff(channum, chan.Riff, true);\n\n        } else\n            chan.Riff.SpeedCnt = 0;\n    }\n\n    // Play the note\n    if (!transposing && notenum > 0) {\n\n        // Key-off?\n        if (notenum == 15)\n            chan.KeyFlags |= fKeyOff;\n\n        if (!chan.Instrument || chan.Instrument->Algorithm < 7)\n            PlayNoteOPL3(channum, octave, notenum);\n    }\n\n    // Process effect\n    switch (cmd) {\n\n        case cmSetVol:\n            SetVolume(channum, param);\n            break;\n\n        case cmSetSpeed:\n            if (src == SNone) {\n                Speed = param;\n                SpeedCnt = param;\n            } else if (src == SRiff) {\n                chan.Riff.Speed = param;\n                chan.Riff.SpeedCnt = param;\n            } else if (src == SIRiff) {\n                chan.IRiff.Speed = param;\n                chan.IRiff.SpeedCnt = param;\n            }\n            break;\n\n        case cmPortamentoUp:\n            fx->PortSlide = param;\n            break;\n\n        case cmPortamentoDwn:\n            fx->PortSlide = -int8_t(param);\n            break;\n\n        case cmToneVolSlide:\n        case cmVolSlide: {\n            int8_t val = param;\n            if (val >= 50)\n                val = -(val - 50);\n            fx->VolSlide = val;\n            if (cmd != cmToneVolSlide)\n                break;\n        }\n        // Fall through!\n\n        case cmToneSlide: {\ntoneslide:\n            uint8_t speed = param;\n            if (speed)\n                fx->ToneSlideSpeed = speed;\n            GetSlideDir(channum, fx);\n            break;\n        }\n\n        case cmJumpToLine: {\n            if (param >= kTrackLines)\n                break;\n\n            // Note: jump commands in riffs are checked for within TickRiff()\n            if (src == SNone)\n                LineJump = param;\n\n            break;\n        }\n\n        case cmMultiplier: {\n            if (src == SIRiff)\n                LoadInstMultiplierOPL3(channum, op, param);\n            break;\n        }\n\n        case cmVolume: {\n            if (src == SIRiff)\n                LoadInstVolumeOPL3(channum, op, param);\n            break;\n        }\n\n        case cmFeedback: {\n            if (src == SIRiff) {\n                uint8_t which = param / 10;\n                uint8_t fb = param % 10;\n                LoadInstFeedbackOPL3(channum, which, fb);\n            }\n            break;\n        }\n    }\n\n    Entrances--;\n}\n\n\n\n//==================================================================================================\n// Sets the OPL3 registers for a given instrument.\n//==================================================================================================\nvoid RADPlayer::LoadInstrumentOPL3(int channum) {\n    CChannel &chan = Channels[channum];\n\n    const CInstrument *inst = chan.Instrument;\n    if (!inst)\n        return;\n\n    uint8_t alg = inst->Algorithm;\n    chan.Volume = inst->Volume;\n    chan.DetuneA = (inst->Detune + 1) >> 1;\n    chan.DetuneB = inst->Detune >> 1;\n\n    // Turn on 4-op mode for algorithms 2 and 3 (algorithms 4 to 6 are simulated with 2-op mode)\n    if (channum < 6) {\n        uint8_t mask = 1 << channum;\n        SetOPL3(0x104, (GetOPL3(0x104) & ~mask) | (alg == 2 || alg == 3 ? mask : 0));\n    }\n\n    // Left/right/feedback/algorithm\n    SetOPL3(0xC0 + ChanOffsets3[channum], ((inst->Panning[1] ^ 3) << 4) | inst->Feedback[1] << 1 | (alg == 3 || alg == 5 || alg == 6 ? 1 : 0));\n    SetOPL3(0xC0 + Chn2Offsets3[channum], ((inst->Panning[0] ^ 3) << 4) | inst->Feedback[0] << 1 | (alg == 1 || alg == 6 ? 1 : 0));\n\n    // Load the operators\n    for (int i = 0; i < 4; i++) {\n\n        static const uint8_t blank[] = { 0, 0x3F, 0, 0xF0, 0 };\n        const uint8_t *op = (alg < 2 && i >= 2) ? blank : inst->Operators[i];\n        uint16_t reg = OpOffsets3[channum][i];\n\n        uint16_t vol = ~op[1] & 0x3F;\n\n        // Do volume scaling for carriers\n        if (AlgCarriers[alg][i]) {\n            vol = vol * inst->Volume / 64;\n            vol = vol * MasterVol / 64;\n        }\n\n        SetOPL3(reg + 0x20, op[0]);\n        SetOPL3(reg + 0x40, (op[1] & 0xC0) | ((vol ^ 0x3F) & 0x3F));\n        SetOPL3(reg + 0x60, op[2]);\n        SetOPL3(reg + 0x80, op[3]);\n        SetOPL3(reg + 0xE0, op[4]);\n    }\n}\n\n\n\n//==================================================================================================\n// Play note on OPL3 hardware.\n//==================================================================================================\nvoid RADPlayer::PlayNoteOPL3(int channum, int8_t octave, int8_t note) {\n    CChannel &chan = Channels[channum];\n\n    uint16_t o1 = ChanOffsets3[channum];\n    uint16_t o2 = Chn2Offsets3[channum];\n\n    // Key off the channel\n    if (chan.KeyFlags & fKeyOff) {\n        chan.KeyFlags &= ~(fKeyOff | fKeyedOn);\n        SetOPL3(0xB0 + o1, GetOPL3(0xB0 + o1) & ~0x20);\n        SetOPL3(0xB0 + o2, GetOPL3(0xB0 + o2) & ~0x20);\n    }\n\n    if (note == 15)\n        return;\n\n    bool op4 = (chan.Instrument && chan.Instrument->Algorithm >= 2);\n\n    uint16_t freq = NoteFreq[note - 1];\n    uint16_t frq2 = freq;\n\n    chan.CurrFreq = freq;\n    chan.CurrOctave = octave;\n\n    // Detune.  We detune both channels in the opposite direction so the note retains its tuning\n    freq += chan.DetuneA;\n    frq2 -= chan.DetuneB;\n\n    // Frequency low byte\n    if (op4)\n        SetOPL3(0xA0 + o1, frq2 & 0xFF);\n    SetOPL3(0xA0 + o2, freq & 0xFF);\n\n    // Frequency high bits + octave + key on\n    if (chan.KeyFlags & fKeyOn)\n        chan.KeyFlags = (chan.KeyFlags & ~fKeyOn) | fKeyedOn;\n    if (op4)\n        SetOPL3(0xB0 + o1, (frq2 >> 8) | (octave << 2) | ((chan.KeyFlags & fKeyedOn) ? 0x20 : 0));\n    else\n        SetOPL3(0xB0 + o1, 0);\n    SetOPL3(0xB0 + o2, (freq >> 8) | (octave << 2) | ((chan.KeyFlags & fKeyedOn) ? 0x20 : 0));\n}\n\n\n\n//==================================================================================================\n// Prepare FX for new line.\n//==================================================================================================\nvoid RADPlayer::ResetFX(CEffects *fx) {\n    fx->PortSlide = 0;\n    fx->VolSlide = 0;\n    fx->ToneSlideDir = 0;\n}\n\n\n\n//==================================================================================================\n// Tick the channel riff.\n//==================================================================================================\nvoid RADPlayer::TickRiff(int channum, CChannel::CRiff &riff, bool chan_riff) {\n    uint8_t lineid;\n\n    riff.Updated = true;\n\n    if (riff.SpeedCnt == 0) {\n        ResetFX(&riff.FX);\n        return;\n    }\n\n    riff.SpeedCnt--;\n    if (riff.SpeedCnt > 0)\n        return;\n    riff.SpeedCnt = riff.Speed;\n\n    uint8_t line = riff.Line++;\n    if (riff.Line >= kTrackLines)\n        riff.SpeedCnt = 0;\n\n    ResetFX(&riff.FX);\n\n    // Is this the current line in track?\n    uint8_t *trk = riff.Track;\n    if (trk && (*trk & 0x7F) == line) {\n        lineid = *trk++;\n\n        // The current riff may be clobbered by recursive riffs.\n        // If this happens, this riff should exit early and not update the\n        // current playing riff pointer (or attempt a line jump).\n        riff.Updated = false;\n\n        if (chan_riff) {\n\n            // Channel riff: play current note\n            UnpackNote(trk, riff.LastInstrument);\n            Transpose(riff.TransposeNote, riff.TransposeOctave);\n            PlayNote(channum, NoteNum, OctaveNum, InstNum, EffectNum, Param, SRiff);\n\n        } else {\n\n            // Instrument riff: here each track channel is an extra effect that can run, but is not\n            // actually a different physical channel\n            bool last;\n            do {\n                int col = *trk & 15;\n                last = UnpackNote(trk, riff.LastInstrument);\n                if (EffectNum != cmIgnore)\n                    Transpose(riff.TransposeNote, riff.TransposeOctave);\n                PlayNote(channum, NoteNum, OctaveNum, InstNum, EffectNum, Param, SIRiff, col > 0 ? (col - 1) & 3 : 0);\n            } while (!last);\n        }\n\n        // Exit if another riff replaced or stopped this riff in the recursive call.\n        if (riff.Updated)\n            return;\n        riff.Updated = true;\n\n        // Last line?\n        if (lineid & 0x80)\n            trk = 0;\n\n        riff.Track = trk;\n    }\n\n    // Special case; if next line has a jump command, run it now\n    if (!trk || (*trk++ & 0x7F) != riff.Line)\n        return;\n\n    lineid = 0; // silence warning\n    UnpackNote(trk, lineid); // lineid is just a dummy here\n    if (EffectNum == cmJumpToLine && Param < kTrackLines) {\n        riff.Line = Param;\n        riff.Track = SkipToLine(riff.TrackStart, Param, chan_riff);\n    }\n}\n\n\n\n//==================================================================================================\n// This continues any effects that operate continuously (eg. slides).\n//==================================================================================================\nvoid RADPlayer::ContinueFX(int channum, CEffects *fx) {\n    CChannel &chan = Channels[channum];\n\n    if (fx->PortSlide)\n        Portamento(channum, fx, fx->PortSlide, false);\n\n    if (fx->VolSlide) {\n        int8_t vol = chan.Volume;\n        vol -= fx->VolSlide;\n        if (vol < 0)\n            vol = 0;\n        SetVolume(channum, vol);\n    }\n\n    if (fx->ToneSlideDir)\n        Portamento(channum, fx, fx->ToneSlideDir, true);\n}\n\n\n\n//==================================================================================================\n// Sets the volume of given channel.\n//==================================================================================================\nvoid RADPlayer::SetVolume(int channum, uint8_t vol) {\n    CChannel &chan = Channels[channum];\n\n    // Ensure volume is within range\n    if (vol > 64)\n        vol = 64;\n\n    chan.Volume = vol;\n\n    // Scale volume to master volume\n    vol = vol * MasterVol / 64;\n\n    CInstrument *inst = chan.Instrument;\n    if (!inst)\n        return;\n    uint8_t alg = inst->Algorithm;\n\n    // Set volume of all carriers\n    for (int i = 0; i < 4; i++) {\n        uint8_t *op = inst->Operators[i];\n\n        // Is this operator a carrier?\n        if (!AlgCarriers[alg][i])\n            continue;\n\n        uint8_t opvol = uint16_t((op[1] & 63) ^ 63) * vol / 64;\n        uint16_t reg = 0x40 + OpOffsets3[channum][i];\n        SetOPL3(reg, (GetOPL3(reg) & 0xC0) | (opvol ^ 0x3F));\n    }\n}\n\n\n\n//==================================================================================================\n// Starts a tone-slide.\n//==================================================================================================\nvoid RADPlayer::GetSlideDir(int channum, CEffects *fx) {\n    CChannel &chan = Channels[channum];\n\n    int8_t speed = fx->ToneSlideSpeed;\n    if (speed > 0) {\n        uint8_t oct = fx->ToneSlideOct;\n        uint16_t freq = fx->ToneSlideFreq;\n\n        uint16_t oldfreq = chan.CurrFreq;\n        uint8_t oldoct = chan.CurrOctave;\n\n        if (oldoct > oct)\n            speed = -speed;\n        else if (oldoct == oct) {\n            if (oldfreq > freq)\n                speed = -speed;\n            else if (oldfreq == freq)\n                speed = 0;\n        }\n    }\n\n    fx->ToneSlideDir = speed;\n}\n\n\n\n//==================================================================================================\n// Load multiplier value into operator.\n//==================================================================================================\nvoid RADPlayer::LoadInstMultiplierOPL3(int channum, int op, uint8_t mult) {\n    uint16_t reg = 0x20 + OpOffsets3[channum][op];\n    SetOPL3(reg, (GetOPL3(reg) & 0xF0) | (mult & 15));\n}\n\n\n\n//==================================================================================================\n// Load volume value into operator.\n//==================================================================================================\nvoid RADPlayer::LoadInstVolumeOPL3(int channum, int op, uint8_t vol) {\n    uint16_t reg = 0x40 + OpOffsets3[channum][op];\n    SetOPL3(reg, (GetOPL3(reg) & 0xC0) | ((vol & 0x3F) ^ 0x3F));\n}\n\n\n\n//==================================================================================================\n// Load feedback value into instrument.\n//==================================================================================================\nvoid RADPlayer::LoadInstFeedbackOPL3(int channum, int which, uint8_t fb) {\n\n    if (which == 0) {\n\n        uint16_t reg = 0xC0 + Chn2Offsets3[channum];\n        SetOPL3(reg, (GetOPL3(reg) & 0x31) | ((fb & 7) << 1));\n\n    } else if (which == 1) {\n\n        uint16_t reg = 0xC0 + ChanOffsets3[channum];\n        SetOPL3(reg, (GetOPL3(reg) & 0x31) | ((fb & 7) << 1));\n    }\n}\n\n\n\n//==================================================================================================\n// This adjusts the pitch of the given channel's note.  There may also be a limiting value on the\n// portamento (for tone slides).\n//==================================================================================================\nvoid RADPlayer::Portamento(uint16_t channum, CEffects *fx, int8_t amount, bool toneslide) {\n    CChannel &chan = Channels[channum];\n\n    uint16_t freq = chan.CurrFreq;\n    uint8_t oct = chan.CurrOctave;\n\n    freq += amount;\n\n    if (freq < 0x156) {\n\n        if (oct > 0) {\n            oct--;\n            freq += 0x2AE - 0x156;\n        } else\n            freq = 0x156;\n\n    } else if (freq > 0x2AE) {\n\n        if (oct < 7) {\n            oct++;\n            freq -= 0x2AE - 0x156;\n        } else\n            freq = 0x2AE;\n    }\n\n    if (toneslide) {\n\n        if (amount >= 0) {\n\n            if (oct > fx->ToneSlideOct || (oct == fx->ToneSlideOct && freq >= fx->ToneSlideFreq)) {\n                freq = fx->ToneSlideFreq;\n                oct = fx->ToneSlideOct;\n            }\n\n        } else {\n\n            if (oct < fx->ToneSlideOct || (oct == fx->ToneSlideOct && freq <= fx->ToneSlideFreq)) {\n                freq = fx->ToneSlideFreq;\n                oct = fx->ToneSlideOct;\n            }\n        }\n    }\n\n    chan.CurrFreq = freq;\n    chan.CurrOctave = oct;\n\n    // Apply detunes\n    uint16_t frq2 = freq - chan.DetuneB;\n    freq += chan.DetuneA;\n\n    // Write value back to OPL3\n    uint16_t chan_offset = Chn2Offsets3[channum];\n    SetOPL3(0xA0 + chan_offset, freq & 0xFF);\n    SetOPL3(0xB0 + chan_offset, (freq >> 8 & 3) | oct << 2 | (GetOPL3(0xB0 + chan_offset) & 0xE0));\n\n    chan_offset = ChanOffsets3[channum];\n    SetOPL3(0xA0 + chan_offset, frq2 & 0xFF);\n    SetOPL3(0xB0 + chan_offset, (frq2 >> 8 & 3) | oct << 2 | (GetOPL3(0xB0 + chan_offset) & 0xE0));\n}\n\n\n\n//==================================================================================================\n// Transpose the note returned by UnpackNote().\n// Note: due to RAD's wonky legacy middle C is octave 3 note number 12.\n//==================================================================================================\nvoid RADPlayer::Transpose(int8_t note, int8_t octave) {\n\n    if (NoteNum >= 1 && NoteNum <= 12) {\n\n        int8_t toct = octave - 3;\n        if (toct != 0) {\n            OctaveNum += toct;\n            if (OctaveNum < 0)\n                OctaveNum = 0;\n            else if (OctaveNum > 7)\n                OctaveNum = 7;\n        }\n\n        int8_t tnot = note - 12;\n        if (tnot != 0) {\n            NoteNum += tnot;\n            if (NoteNum < 1) {\n                NoteNum += 12;\n                if (OctaveNum > 0)\n                    OctaveNum--;\n                else\n                    NoteNum = 1;\n            }\n        }\n    }\n}\n\n\n\n//==================================================================================================\n// Compute total time of tune if it didn't repeat.  Note, this stops the tune so should only be done\n// prior to initial playback.\n//==================================================================================================\n#if RAD_DETECT_REPEATS\nstatic void RADPlayerDummyOPL3(void *arg, uint16_t reg, uint8_t data) {}\n//--------------------------------------------------------------------------------------------------\nuint32_t RADPlayer::ComputeTotalTime() {\n\n    Stop();\n    void (*old_opl3)(void *, uint16_t, uint8_t) = OPL3;\n    OPL3 = RADPlayerDummyOPL3;\n\n    while (!Update())\n        ;\n    uint32_t total = PlayTime;\n\n    Stop();\n    OPL3 = old_opl3;\n\n    return total / Hertz;\n}\n#endif\n\n\n\n// BEGIN MEGAZEUX ADDITIONS\n\n\n\n//==================================================================================================\n// Initialise a RAD v1 tune for playback.  This assumes the tune data is valid and does minimal data\n// checking. -Lachesis\n//==================================================================================================\nvoid RADPlayer::Init10(const void *tune)\n{\n    uint8_t *pos = (uint8_t *)tune + 0x11;\n\n    uint8_t flags = *(pos++);\n    Speed = flags & 0x1F;\n    Hertz = 50;\n\n    // Slow timer tune?  Return an approximate hz\n    if(flags & 0x40)\n        Hertz = 18;\n\n    // Skip any description (only present if flag is set)\n    if(flags & 0x80)\n        while(*(pos++)) {}\n\n    // Unpack the instruments\n    while(true)\n    {\n        // Instrument number, 0 indicates end of list\n        uint8_t inst_num = *(pos++);\n        if (inst_num == 0)\n            break;\n\n        CInstrument &inst = Instruments[inst_num - 1];\n\n        uint8_t c_flags = pos[0];\n        uint8_t m_flags = pos[1];\n        uint8_t c_ksl_volume = pos[2];\n        uint8_t m_ksl_volume = pos[3];\n        uint8_t c_attack_decay = pos[4];\n        uint8_t m_attack_decay = pos[5];\n        uint8_t c_sustain_release = pos[6];\n        uint8_t m_sustain_release = pos[7];\n        uint8_t feedback_algorithm = pos[8];\n        uint8_t c_waveform = pos[9];\n        uint8_t m_waveform = pos[10];\n        pos += 11;\n\n        inst.Algorithm = (feedback_algorithm & 1);\n        inst.Panning[0] = 0;\n        inst.Panning[1] = 0;\n\n        inst.Feedback[0] = (feedback_algorithm & 0x0E) >> 1;\n        inst.Feedback[1] = (feedback_algorithm & 0x0E) >> 1;\n\n        inst.Volume = (c_ksl_volume & 0x3F) ^ 0x3F;\n        inst.Detune = 0;\n        inst.RiffSpeed = 6;\n        inst.Riff = 0;\n\n        uint8_t *op0 = inst.Operators[0];\n        uint8_t *op1 = inst.Operators[1];\n        uint8_t *op2 = inst.Operators[2];\n        uint8_t *op3 = inst.Operators[3];\n\n        op0[0] = c_flags;\n        op0[1] = c_ksl_volume;\n        op0[2] = c_attack_decay;\n        op0[3] = c_sustain_release;\n        op0[4] = c_waveform;\n\n        op1[0] = m_flags;\n        op1[1] = m_ksl_volume;\n        op1[2] = m_attack_decay;\n        op1[3] = m_sustain_release;\n        op1[4] = m_waveform;\n\n        memset(op2, 0, 5);\n        memset(op3, 0, 5);\n    }\n\n    // Get order list\n    OrderListSize = *(pos++);\n    OrderList = pos;\n    pos += OrderListSize;\n\n    // Track offset table\n    for(int i = 0; i < 31; i++)\n    {\n        int offset = pos[0] | (int(pos[1]) << 8);\n        pos += 2;\n\n        if(offset)\n            Tracks[i] = (uint8_t *)tune + offset;\n    }\n\n    // Done parsing tune, now set up for play\n    for(int i = 0; i < 512; i++)\n        OPL3Regs[i] = 255;\n\n    Stop();\n\n    Initialised = true;\n}\n\n\n\n//==================================================================================================\n// Unpacks a single RAD note (v1). -Lachesis\n//==================================================================================================\nbool RADPlayer::UnpackNote10(uint8_t *&pos)\n{\n    uint8_t chanid = *(pos++);\n    uint8_t b1 = *(pos++);\n    uint8_t b2 = *(pos++);\n\n    // Unpack note data\n    NoteNum = b1 & 0x0F;\n    OctaveNum = (b1 & 0x70) >> 4;\n    InstNum = ((b1 & 0x80) >> 3) | ((b2 & 0xF0) >> 4);\n    EffectNum = (b2 & 0x0F);\n    Param = 0;\n\n    // Do we have an effect?\n    if(EffectNum)\n        Param = *(pos++);\n\n    return ((chanid & 0x80) != 0);\n}\n\n\n\n//==================================================================================================\n// Skip through track till we reach the given line or the next higher one (v1).  Returns null if none.\n// -Lachesis\n//==================================================================================================\nuint8_t *RADPlayer::SkipToLine10(uint8_t *trk, uint8_t linenum)\n{\n    while(true)\n    {\n        uint8_t lineid = *trk;\n\n        if((lineid & 0x7F) >= linenum)\n            return trk;\n\n        if(lineid & 0x80)\n            break;\n\n        trk++;\n\n        // Skip channel notes\n        while(true)\n        {\n            uint8_t chanid = *(trk++);\n            trk++;\n\n            // Do we have an effect?\n            if(*(trk++) & 0x0F)\n                trk++;\n\n            if(chanid & 0x80)\n                break;\n        }\n    }\n    return 0;\n}\n\n\n\n//==================================================================================================\n// KSL is handled different between RAD v1 / DOS RAD v2.1 and Windows/Mac RAD v2.1.\n// In DOS, since these are passed directly to the OPL3, KSL 1 is 3dB and KSL 2 is 1.5dB.\n// With Opal, these were originally reversed, so flip the KSL bits when loading RAD v2s.\n// -Lachesis\n//==================================================================================================\nuint8_t RADPlayer::FixRadv21KSLVolume(uint8_t val)\n{\n    return ((val & 0x80) >> 1) | ((val & 0x40) << 1) | (val & 0x3F);\n}\n\n\n\n//==================================================================================================\n// Set the current order and line. -Lachesis\n//==================================================================================================\nvoid RADPlayer::SetTunePos(uint32_t order, uint32_t line)\n{\n    if(line > kTrackLines)\n        line = 0;\n\n    if(order > kTracks || order > OrderListSize)\n        order = 0;\n\n    Order = order;\n    Line = line;\n\n    Track = GetTrack();\n\n    if(line > 0)\n        Track = SkipToLine(Track, Line, false);\n}\n\n\n\n//==================================================================================================\n// Get the effective length of the tune in orders, i.e., the length minus any jumps at the end.\n// -Lachesis\n//==================================================================================================\nint RADPlayer::GetTuneEffectiveLength()\n{\n    if(LastPatternOrder >= 0)\n        return LastPatternOrder + 1;\n\n    int i;\n    for(i = OrderListSize - 1; i >= 0; i--)\n    {\n        if(!(OrderList[i] & 0x80))\n            break;\n    }\n\n    if(i < 0)\n        return 0;\n\n    LastPatternOrder = i;\n    return i + 1;\n}\n"
  },
  {
    "path": "contrib/rad/validate20.cpp",
    "content": "/*\n\n    Code to check a RAD V2 tune file is valid.  That is, it will check the tune\n    can be played without crashing the player.  It doesn't exhaustively check\n    the tune except where needed to prevent a possible player crash.\n\n    Call RADValidate with a pointer to your tune and the size in bytes.  It\n    will return either NULL for all okay, or a pointer to a null-terminated\n    string describing what is wrong with the data.\n\n*/\n\n\n\n#include <stdint.h>\n\n\n\nconst char *RADValidate(const void *data, size_t data_size);\n\n\n\n//==================================================================================================\n// The error strings are all supplied here in case you want to translate them to another language\n// (or supply your own more descriptive error messages).\n//==================================================================================================\nconst char *g_RADNotATuneFile = \"Not a RAD tune file.\";\nconst char *g_RADNotAVersion21Tune = \"Not a version 2.1 file format RAD tune.\";\nconst char *g_RADTruncated = \"Tune file has been truncated and is incomplete.\";\nconst char *g_RADBadFlags = \"Tune file has invalid flags.\";\nconst char *g_RADBadBPMValue = \"Tune's BPM value is out of range.\";\nconst char *g_RADBadInstrument = \"Tune file contains a bad instrument definition.\";\nconst char *g_RADUnknownMIDIVersion = \"Tune file contains an unknown MIDI instrument version.\";\nconst char *g_RADOrderListTooLarge = \"Order list in tune file is an invalid size.\";\nconst char *g_RADBadJumpMarker = \"Order list jump marker is invalid.\";\nconst char *g_RADBadOrderEntry = \"Order list entry is invalid.\";\nconst char *g_RADBadPattNum = \"Tune file contains a bad pattern index.\";\nconst char *g_RADPattTruncated = \"Tune file contains a truncated pattern.\";\nconst char *g_RADPattExtraData = \"Tune file contains a pattern with extraneous data.\";\nconst char *g_RADPattBadLineNum = \"Tune file contains a pattern with a bad line definition.\";\nconst char *g_RADPattBadChanNum = \"Tune file contains a pattern with a bad channel definition.\";\nconst char *g_RADPattBadNoteNum = \"Pattern contains a bad note number.\";\nconst char *g_RADPattBadInstNum = \"Pattern contains a bad instrument number.\";\nconst char *g_RADPattBadEffect = \"Pattern contains a bad effect and/or parameter.\";\nconst char *g_RADBadRiffNum = \"Tune file contains a bad riff index.\";\nconst char *g_RADExtraBytes = \"Tune file contains extra bytes.\";\n\n\n\n//=============================================================================\n// Validate a RAD V1 file. -Lachesis\n//=============================================================================\nstatic const char *RADValidate10(const void *data, const uint8_t *end)\n{\n    const uint8_t *start = (const uint8_t *)data;\n    const uint8_t *pos = start;\n    uint16_t pattern_offsets[32];\n    uint32_t i;\n    bool pattern_used[32] = { false };\n    bool last_line;\n    bool last_note;\n\n    // NOTE: an extra byte was allocated and set to null so some extra tracks\n    // would pass this, so bump the end pointer.\n    end++;\n\n    // Description\n    pos += 17;\n    if(*(pos++) & 0x80)\n    {\n        do\n        {\n            if(pos >= end)\n                return g_RADTruncated;\n        }\n        while(*(pos++));\n    }\n\n    // Instruments\n    while(true)\n    {\n        uint8_t num = *(pos++);\n\n        if(num)\n        {\n            pos += 11;\n\n            if(pos >= end)\n                return g_RADTruncated;\n        }\n        else\n            break;\n    }\n\n    // Order list\n    uint8_t num_orders = *(pos++);\n    if(num_orders > 128)\n        return g_RADOrderListTooLarge;\n\n    if(pos + num_orders >= end)\n        return g_RADTruncated;\n\n    for(i = 0; i < num_orders; i++)\n    {\n        uint8_t order = *(pos++);\n\n        // Jump marker\n        if(order & 0x80)\n        {\n            order &= 0x7F;\n            if(order >= num_orders)\n                return g_RADBadJumpMarker;\n        }\n\n        // Pattern number\n        else\n        {\n            if(order >= 32)\n                return g_RADBadOrderEntry;\n\n            // Mark used patterns for validation.\n            // TODO: possibly walk through the order list instead so unreached\n            // orders don't have their patterns marked used.\n            pattern_used[order] = true;\n        }\n    }\n\n    // Pattern offset table\n    if(pos + 64 >= end)\n        return g_RADTruncated;\n\n    for(i = 0; i < 32; i++)\n    {\n        // Ignore offsets to unused patterns- usually they will already be\n        // zero, but \"blue_ad.rad\" has unused garbage patterns that will fail\n        // validation despite the track otherwise working fine.\n        pattern_offsets[i] = pattern_used[i] ? (pos[0] | (pos[1] << 8)) : 0;\n        pos += 2;\n\n        if(pattern_used[i] && (start + pattern_offsets[i] >= end))\n            return g_RADPattTruncated;\n    }\n\n    // Patterns\n    for(i = 0; i < 32; i++)\n    {\n        if(!pattern_offsets[i])\n            continue;\n\n        pos = start + pattern_offsets[i];\n\n        do\n        {\n            // Line number\n            uint8_t line_num = *(pos++);\n            if((line_num & 0x7F) >= 64)\n                return g_RADPattBadLineNum;\n\n            last_line = (line_num & 0x80) ? true : false;\n\n            do\n            {\n                if(pos + 2 >= end)\n                    return g_RADPattTruncated;\n\n                // Channel\n                uint8_t channel = *(pos++);\n                if((channel & 0x7F) >= 9)\n                    return g_RADPattBadChanNum;\n\n                last_note = (channel & 0x80) ? true : false;\n\n                uint8_t b1 = *(pos++);\n                uint8_t b2 = *(pos++);\n\n                // Note\n                uint8_t note = (b1 & 0x0F);\n                if(note == 13 || note == 14)\n                    return g_RADPattBadNoteNum;\n\n                // Instrument\n                //uint8_t inst = ((b1 & 0x80) >> 4) | ((b2 & 0xF0) >> 1);\n\n                // Effect\n                uint8_t fx = (b2 & 0x0F);\n                if(fx)\n                {\n                    // Parameter\n                    //uint8_t param = *pos;\n                    pos++;\n                }\n            }\n            while(!last_note);\n        }\n        while(!last_line);\n    }\n    return 0;\n}\n\n\n\n//==================================================================================================\n// Validate a RAD V2 (file format 2.1) tune file.  Note, this uses no C++ standard library code.\n//==================================================================================================\nstatic const char *RADCheckPattern(const uint8_t *&s, const uint8_t *e, bool riff) {\n\n    // Get pattern size\n    if (s + 2 > e)\n        return g_RADTruncated;\n    uint16_t pattsize = s[0] | (uint16_t(s[1]) << 8);\n    s += 2;\n\n    // Calculate end of pattern\n    const uint8_t *pe = s + pattsize;\n    if (pe > e)\n        return g_RADTruncated;\n\n    uint8_t linedef, chandef;\n    do {\n\n        // Check line of pattern\n        if (s >= pe)\n            return g_RADPattTruncated;\n        linedef = *s++;\n        uint8_t linenum = linedef & 0x7F;\n        if (linenum >= 64)\n            return g_RADPattBadLineNum;\n\n        do {\n\n            // Check channel of pattern\n            if (s >= pe)\n                return g_RADPattTruncated;\n            chandef = *s++;\n            uint8_t channum = chandef & 0x0F;\n            if (!riff && channum >= 9)\n                return g_RADPattBadChanNum;\n\n            // Check note\n            if (chandef & 0x40) {\n                if (s >= pe)\n                    return g_RADPattTruncated;\n                uint8_t note = *s++;\n                uint8_t notenum = note & 15;\n                //uint8_t octave = (note >> 4) & 7;\n                if (notenum == 0 || notenum == 13 || notenum == 14)\n                    return g_RADPattBadNoteNum;\n            }\n\n            // Check instrument.  This shouldn't be supplied if bit 7 of the note byte is set,\n            // but it doesn't break anything if it is so we don't check for it\n            if (chandef & 0x20) {\n                if (s >= pe)\n                    return g_RADPattTruncated;\n                uint8_t inst = *s++;\n                if (inst == 0 || inst >= 128)\n                    return g_RADPattBadInstNum;\n            }\n\n            // Check effect.  A non-existent effect could be supplied, but it'll just be\n            // ignored by the player so we don't care\n            if (chandef & 0x10) {\n                if (s + 2 > pe)\n                    return g_RADPattTruncated;\n                uint8_t effect = *s++;\n                uint8_t param = *s++;\n                if (effect > 31 || param > 99)\n                    return g_RADPattBadEffect;\n            }\n\n        } while (!(chandef & 0x80));\n\n    } while (!(linedef & 0x80));\n\n    if (s != pe)\n        return g_RADPattExtraData;\n\n    return 0;\n}\n//--------------------------------------------------------------------------------------------------\nconst char *RADValidate(const void *data, size_t data_size) {\n\n    const uint8_t *s = (const uint8_t *)data;\n    const uint8_t *e = s + data_size;\n    uint8_t version;\n\n    // Check header\n    if (data_size < 17)\n        return g_RADNotATuneFile;\n\n    const char *hdrtxt = \"RAD by REALiTY!!\";\n    for (int i = 0; i < 16; i++)\n        if (char(*s++) != *hdrtxt++)\n            return g_RADNotATuneFile;\n\n    // Check version\n    version = *(s++);\n    if (version == 0x10)\n        return RADValidate10(data, e);\n    if (version != 0x21)\n        return g_RADNotAVersion21Tune;\n\n    // Check flags\n    if (s >= e)\n        return g_RADTruncated;\n\n    uint8_t flags = *s++;\n    if (flags & 0x80)\n        return g_RADBadFlags; // Bit 7 is unused\n\n    // NOTE: the vanilla validator incorrectly checks 0x40 here.\n    if (flags & 0x20) {\n        if (s + 2 > e)\n            return g_RADTruncated;\n        uint16_t bpm = s[0] | (uint16_t(s[1]) << 8);\n        s += 2;\n        if (bpm < 46 || bpm > 300)\n            return g_RADBadBPMValue;\n    }\n\n    // Check description.  This is actually freeform text so there's not a lot to check, just that\n    // it's a null-terminated string\n    do {\n        if (s >= e)\n            return g_RADTruncated;\n    } while (*s++);\n\n    // Check instruments.  We don't actually validate the individual instrument fields as the tune\n    // file will still play with bad instrument data.  We're only concerned that the tune file\n    // doesn't crash the player\n    uint8_t last_inst = 0;\n    while (1) {\n\n        // Get instrument number, or 0 for end of instrument list\n        if (s >= e)\n            return g_RADTruncated;\n        uint8_t inst = *s++;\n        if (inst == 0)\n            break;\n\n        // RAD always saves the instruments out in order\n        if (inst > 127 || inst <= last_inst)\n            return g_RADBadInstrument;\n        last_inst = inst;\n\n        // Check the name\n        if (s >= e)\n            return g_RADTruncated;\n        uint8_t namelen = *s++;\n        s += namelen;\n\n        // Get algorithm\n        if (s > e)\n            return g_RADTruncated;\n        uint8_t alg = *s;\n\n        if ((alg & 7) == 7) {\n\n            // MIDI instrument.  We need to check the version as this can affect the following\n            // data size\n            if (s + 6 > e)\n                return g_RADTruncated;\n            if (s[2] >> 4)\n                return g_RADUnknownMIDIVersion;\n            // NOTE the vanilla validator skips 6 bytes here as if the algorithm\n            // byte was already skipped. It wasn't, so skip 7 instead.\n            s += 7;\n\n        } else {\n\n            s += 24;\n            if (s > e)\n                return g_RADTruncated;\n        }\n\n        // Riff track supplied?\n        if (alg & 0x80) {\n\n            const char *err = RADCheckPattern(s, e, false);\n            if (err)\n                return err;\n        }\n    }\n\n    // Get the order list\n    if (s >= e)\n        return g_RADTruncated;\n    uint8_t order_size = *s++;\n    const uint8_t *order_list = s;\n    if (order_size > 128)\n        return g_RADOrderListTooLarge;\n    s += order_size;\n\n    for (uint8_t i = 0; i < order_size; i++) {\n        uint8_t order = order_list[i];\n\n        if (order & 0x80) {\n\n            // Check jump marker\n            order &= 0x7F;\n            if (order >= order_size)\n                return g_RADBadJumpMarker;\n        } else {\n\n            // Check pattern number.  It doesn't matter if there is no pattern with this number\n            // defined later, as missing patterns are treated as empty\n            if (order >= 100)\n                return g_RADBadOrderEntry;\n        }\n    }\n\n    // Check the patterns\n    while (1) {\n\n        // Get pattern number\n        if (s >= e)\n            return g_RADTruncated;\n        uint8_t pattnum = *s++;\n\n        // Last pattern?\n        if (pattnum == 0xFF)\n            break;\n\n        if (pattnum >= 100)\n            return g_RADBadPattNum;\n\n        const char *err = RADCheckPattern(s, e, false);\n        if (err)\n            return err;\n    }\n\n    // Check the riffs\n    while (1) {\n\n        // Get riff number\n        if (s >= e)\n            return g_RADTruncated;\n        uint8_t riffnum = *s++;\n\n        // Last riff?\n        if (riffnum == 0xFF)\n            break;\n\n        uint8_t riffpatt = riffnum >> 4;\n        uint8_t riffchan = riffnum & 15;\n        if (riffpatt > 9 || riffchan == 0 || riffchan > 9)\n            return g_RADBadRiffNum;\n\n        const char *err = RADCheckPattern(s, e, true);\n        if (err)\n            return err;\n    }\n\n    // We should be at the end of the file now.  Note, you can safely remove this check if you\n    // like - extra bytes won't affect playback\n    if (s != e)\n        return g_RADExtraBytes;\n\n    // Tune file is all good\n    return 0;\n}\n"
  },
  {
    "path": "debian/README",
    "content": "BUILDING MEGAZEUX FOR DEBIAN\n\nMegaZeux was tested as building on Debian Unstable as of May, 2007. You must\nfirst rename the source directory \"megazeux-2.84\", for example, to match the\nrequired Debian standard. MegaZeux is usually shipped as \"mzx284\", for\nhistorical reasons.\n\nYou can then generate a deb file for your architecture by typing:\n\ndebuild -us -uc\n\nWhich will create a deb, changes, source, and dsc file in the directory above\nthis one. These may be added to an 'apt' repository, if you desire.\n\n--ajs.\n"
  },
  {
    "path": "debian/binary.lintian-overrides",
    "content": "megazeux: package-section-games-but-has-usr-bin\nmegazeux: binary-or-shlib-defines-rpath\n"
  },
  {
    "path": "debian/changelog",
    "content": "megazeux (2.93.3) unstable; urgency=low\n\n  * Updated for 2.93d.\n\n -- Dylan \"Dizzy\" J Morrison <dizzy@domad.science>  Fri, 28 Feb 2025 19:06:00 -0500\n\nmegazeux (2.93.2) unstable; urgency=low\n\n  * Updated for 2.93c.\n\n -- Dylan \"Dizzy\" J Morrison <dizzy@domad.science>  Fri, 28 Feb 2025 19:06:00 -0500\n\nmegazeux (2.93.1) unstable; urgency=low\n\n  * Updated for 2.93b.\n\n -- Dylan \"Dizzy\" J Morrison <dizzy@domad.science>  Tue, 10 Sep 2024 20:09:00 -0500\n\nmegazeux (2.93.0) unstable; urgency=low\n\n  * Updated for 2.93.\n\n -- Dylan \"Dizzy\" J Morrison <dizzy@domad.science>  Sun, 31 Dec 2023 20:35:00 -0500\n\nmegazeux (2.92f) unstable; urgency=low\n\n  * Updated for 2.92f.\n\n -- Dylan \"Dizzy\" J Morrison <dylanjmorrison611@gmail.com>  Sat, 21 Nov 2020 23:34:00 -0500\n\nmegazeux (2.92e) unstable; urgency=low\n\n  * Updated for 2.92e.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Mon, 20 Jul 2020 00:12:00 -0500\n\nmegazeux (2.92d) unstable; urgency=low\n\n  * Updated for 2.92d.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Fri, 08 May 2020 04:29:00 -0500\n\nmegazeux (2.92c) unstable; urgency=low\n\n  * Updated for 2.92c.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Sat, 07 Mar 2020 19:58:00 -0500\n\nmegazeux (2.92b) unstable; urgency=low\n\n  * Updated for 2.92b.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Mon, 23 Sep 2019 12:49:00 -0500\n\nmegazeux (2.92) unstable; urgency=low\n\n  * Updated for 2.92.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Mon, 22 Jul 2019 23:31:00 -0500\n\nmegazeux (2.91j) unstable; urgency=low\n\n  * Updated for 2.91j.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Wed, 20 Feb 2019 05:09:00 -0500\n\nmegazeux (2.91i) unstable; urgency=low\n\n  * Updated for 2.91i.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Sat, 09 Dec 2018 00:56:00 -0500\n\nmegazeux (2.91h) unstable; urgency=low\n\n  * Updated for 2.91h.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Wed, 14 Nov 2018 21:31:00 -0500\n\nmegazeux (2.91g) unstable; urgency=low\n\n  * Updated for 2.91g.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Sun, 07 Oct 2018 21:27:00 -0500\n\nmegazeux (2.91f) unstable; urgency=low\n\n  * Updated for 2.91f.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Mon, 17 Sep 2018 00:31:00 -0500\n\nmegazeux (2.91e) unstable; urgency=low\n\n  * Updated for 2.91e.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Mon, 03 Sep 2018 00:00:00 -0500\n\nmegazeux (2.91c) unstable; urgency=low\n\n  * Updated for 2.91c.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Sat, 13 May 2018 00:00:00 -0500\n\nmegazeux (2.91b) unstable; urgency=low\n\n  * Updated for 2.91b.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Sun, 07 Jan 2018 06:10:00 -0500\n\nmegazeux (2.91) unstable; urgency=low\n\n  * Updated for 2.91.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Wed, 22 Nov 2017 07:00:00 -0500\n\nmegazeux (2.90d) unstable; urgency=low\n\n  * Updated for 2.90d.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Mon, 04 Sep 2017 20:44:00 -0500\n\nmegazeux (2.90c) unstable; urgency=low\n\n  * Updated for 2.90c.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Tue, 25 Jul 2017 00:00:00 -0500\n\nmegazeux (2.90b) unstable; urgency=low\n\n  * Updated for 2.90b.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Sun, 16 Jul 2017 21:03:00 -0500\n\nmegazeux (2.90) unstable; urgency=low\n\n  * Updated for 2.90.\n\n -- Dylan J Morrison <dylanjmorrison611@gmail.com>  Tue, 27 Jun 2017 11:30:00 -0500\n\nmegazeux (2.84c) unstable; urgency=low\n\n  * Updated for 2.84c.\n\n -- Alistair John Strachan <alistair@devzero.co.uk>  Sat, 22 Dec 2012 15:13:00 -0800\n\nmegazeux (2.84) unstable; urgency=low\n\n  * Updated for 2.84.\n\n -- Alistair John Strachan <alistair@devzero.co.uk>  Tue, 02 Jun 2012 21:46:00 +0100\n\nmegazeux (2.83b) unstable; urgency=low\n\n  * Updated for 2.83b.\n\n -- Alistair John Strachan <alistair@devzero.co.uk>  Tue, 18 Jan 2011 07:56:00 +0000\n\nmegazeux (2.83) unstable; urgency=low\n\n  * Updated for 2.83.\n\n -- Alistair John Strachan <alistair@devzero.co.uk>  Sat, 26 Dec 2009 14:26:00 +0000\n\nmegazeux (2.82b) unstable; urgency=low\n\n  * Updated for 2.82b.\n\n -- Alistair John Strachan <alistair@devzero.co.uk>  Mon, 17 Nov 2008 19:03:00 +0000\n\nmegazeux (2.82) unstable; urgency=low\n\n  * Updated for 2.82.\n\n -- Alistair John Strachan <alistair@devzero.co.uk>  Sat, 9 Jun 2008 21:43:00 +0000\n\nmegazeux (2.81h) unstable; urgency=low\n\n  * Updated for 2.81h.\n\n -- Alistair John Strachan <alistair@devzero.co.uk>  Sat, 1 Sep 2007 17:34:00 +0000\n\nmegazeux (2.81g) unstable; urgency=low\n\n  * Updated for 2.81g.\n\n -- Alistair John Strachan <alistair@devzero.co.uk>  Sat, 5 May 2007 18:01:41 +0000\n\nmegazeux (2.81f) unstable; urgency=low\n\n  * Updated for 2.81f.\n\n -- Alistair John Strachan <alistair@devzero.co.uk>  Tue, 30 Jan 2007 20:26:03 +0000\n\nmegazeux (2.81e) unstable; urgency=low\n\n  * Initial Release.\n\n -- Alistair John Strachan <alistair@devzero.co.uk>  Fri, 19 Jan 2007 02:00:03 +0000\n"
  },
  {
    "path": "debian/compat",
    "content": "7\n"
  },
  {
    "path": "debian/control",
    "content": "Source: megazeux\nSection: games\nPriority: extra\nMaintainer: Dylan James Morrison <dylanjmorrison611@gmail.com>\nBuild-Depends: debhelper (>= 5), libsdl2-dev, libvorbis-dev, libpng-dev, zlib1g-dev\nStandards-Version: 3.8.0\n\nPackage: megazeux\nArchitecture: any\nDepends: ${shlibs:Depends}, ${misc:Depends}\nDescription: A simple game creation system (GCS)\n MegaZeux is a Game Creation System (GCS), inspired by Epic MegaGames' ZZT,\n first released by Alexis Janson in 1994. It was recently ported from 16bit\n MSDOS C/ASM to portable SDL C/C++, improving its platform support and\n hardware compatibility.\n .\n Many features have been added since the original DOS version, and with the\n help of an intuitive editor and a simple but powerful object-oriented\n programming language, MegaZeux allows you to create your own ANSI-esque games\n regardless of genre. It is actively maintained by a thriving community.\n .\n See https://www.digitalmzx.com/ for more information.\n\nPackage: megazeux-dbg\nSection: debug\nArchitecture: any\nDepends: megazeux, ${shlibs:Depends}, ${misc:Depends}\nDescription: MegaZeux debug symbols\n Debug symbols for MegaZeux.\n"
  },
  {
    "path": "debian/copyright",
    "content": "This package was debianized by Alistair John Strachan <alistair@devzero.co.uk>.\n\nIt was downloaded from: https://www.digitalmzx.com/\n\nUpstream Authors:\n\tAlexis Janson (original DOS version)\n\tCharles Goetzman (\"spider\" versions)\n\tGilead Kutnick <exophase@gmail.com> (SDL port and rewrite)\n\tAlistair John Strachan <alistair@devzero.co.uk>\n\tAlice Rowan <petrifiedrowan@gmail.com> (current maintainer)\n\nCopyright:\n\tCopyright (C) 1996 Alexis Janson\n\tCopyright (C) 1999 Charles Goetzman\n\tCopyright (C) 2004-2006 Gilead Kutnick <exophase@gmail.com>\n\tCopyright (C) 2006-2012 Alistair John Strachan <alistair@devzero.co.uk>\n\tCopyright (C) 2012-2020 Alice Rowan <petrifiedrowan@gmail.com>\n\nLicense:\n\n   This package is free software; you can redistribute it and/or modify\n   it under the terms of the GNU General Public License as published by\n   the Free Software Foundation; version 2 of the License.\n\n   This package is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n   GNU General Public License for more details.\n\n   You should have received a copy of the GNU General Public License\n   along with this package; if not, write to the Free Software\n   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA\n\nOn Debian systems, the complete text of the GNU General\nPublic License can be found in `/usr/share/common-licenses/GPL-2'.\n"
  },
  {
    "path": "debian/docs",
    "content": "docs/changelog.txt\ndocs/macro.txt\ndocs/keycodes.html\n"
  },
  {
    "path": "debian/megazeux.manpages",
    "content": "docs/megazeux.1\n"
  },
  {
    "path": "debian/rules",
    "content": "#!/usr/bin/make -f\n\n# uncomment this to turn on verbose mode.\n#export DH_VERBOSE=1\n\ninclude version.inc\n\nCFLAGS = -Wall -g\n\nifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))\n\tCFLAGS += -O0\nelse\n\tCFLAGS += -O2\nendif\n\nconfigure: configure-stamp\nconfigure-stamp:\n\tdh_testdir\n\t./config.sh --platform unix --as-needed-hack \\\n\t            --enable-release --enable-lto $(CONFIG_FLAGS)\n\ttouch configure-stamp\n\nbuild: build-stamp\n\nbuild-stamp: configure-stamp \n\tdh_testdir\n\t$(MAKE)\n\ttouch $@\n\ntest: test-stamp\ntest-stamp: build-stamp\n\tdh_testdir\n\t$(MAKE) test\n\ttouch test-stamp\n\nclean:\n\tdh_testdir\n\tdh_testroot\n\trm -f build-stamp configure-stamp\n\t$(MAKE) distclean\n\tdh_clean \n\ninstall: test\n\tdh_testdir\n\tdh_testroot\n\tdh_clean -k \n\tdh_installdirs\n\t$(MAKE) DESTDIR=$(CURDIR)/debian/megazeux install\n\tmkdir -p $(CURDIR)/debian/megazeux/usr/share/lintian/overrides\n\tcp debian/binary.lintian-overrides \\\n\t  $(CURDIR)/debian/megazeux/usr/share/lintian/overrides/megazeux\n\n# Build architecture-independent files here.\nbinary-indep: build install\n# We have nothing to do by default.\n\n# Build architecture-dependent files here.\nbinary-arch: build install\n\tdh_testdir\n\tdh_testroot\n\tdh_installchangelogs \n\tdh_installdocs\n\tdh_installman\n\tdh_strip --dbg-package=megazeux-dbg\n\tdh_compress\n\tdh_fixperms\n\tdh_installdeb\n\tdh_shlibdeps\n\tdh_gencontrol\n\tdh_md5sums\n\tdh_builddeb\n\nbinary: binary-indep binary-arch\n.PHONY: build clean binary-indep binary-arch binary install configure\n"
  },
  {
    "path": "docs/SDLkeys.txt",
    "content": "SDLK constant     SDL  SDL2XT  XT scan code  AT scan code\n-----------------------------------------------------------\nSDLK_BACKSPACE    8    0E      0E            66\nSDLK_TAB          9    0F      0F            0D\nSDLK_RETURN       13   1C      1C            5A\nSDLK_ESCAPE       27   01      01            76\nSDLK_SPACE        32   39      39            29\nSDLK_QUOTE        39   28      28            52\nSDLK_PLUS         43   00      XX            XX\nSDLK_COMMA        44   33      33            41\nSDLK_PERIOD       46   34      34            49\nSDLK_SLASH        47   35      35            4A\nSDLK_0            48   0B      0B            45\nSDLK_1            49   02      02            16\nSDLK_2            50   03      03            1E\nSDLK_3            51   04      04            26\nSDLK_4            52   05      05            25\nSDLK_5            53   06      06            2E\nSDLK_6            54   07      07            36\nSDLK_7            55   08      08            3D\nSDLK_8            56   09      09            3E\nSDLK_9            57   0A      0A            46\nSDLK_SEMICOLON    59   27      27            4C\nSDLK_MINUS        45   0C      0C            4E\nSDLK_EQUALS       61   0D      0D            55\nSDLK_LEFTBRACKET  91   1A      1A            54\nSDLK_BACKSLASH    92   2B      2B            5D\nSDLK_RIGHTBRACKET 93   1B      1B            5B\nSDLK_BACKQUOTE    96   29      29            0E\nSDLK_a            97   1E      1E            1C\nSDLK_b            98   30      30            32\nSDLK_c            99   2E      2E            21\nSDLK_d            100  20      20            23\nSDLK_e            101  12      12            24\nSDLK_f            102  21      21            2B\nSDLK_g            103  22      22            34\nSDLK_h            104  23      23            33\nSDLK_i            105  17      17            43\nSDLK_j            106  24      24            3B\nSDLK_k            107  25      25            42\nSDLK_l            108  26      26            4B\nSDLK_m            109  32      32            3A\nSDLK_n            110  31      31            31\nSDLK_o            111  18      18            44\nSDLK_p            112  19      19            4D\nSDLK_q            113  10      10            15\nSDLK_r            114  13      13            2D\nSDLK_s            115  1F      1F            1B\nSDLK_t            116  14      14            2C\nSDLK_u            117  16      16            3C\nSDLK_v            118  2F      2F            2A\nSDLK_w            119  11      11            1D\nSDLK_x            120  2D      2D            22\nSDLK_y            121  15      15            35\nSDLK_z            122  2C      2C            1A\nSDLK_DELETE       127  53      E053          E071\nSDLK_KP0          256  52      52            70\nSDLK_KP1          257  4F      4F            69\nSDLK_KP2          258  50      50            72\nSDLK_KP3          259  51      51            7A\nSDLK_KP4          260  4B      4B            6B\nSDLK_KP5          261  4C      4C            73\nSDLK_KP6          262  4D      4D            74\nSDLK_KP7          263  47      47            6C\nSDLK_KP8          264  48      48            75\nSDLK_KP9          265  49      49            7D\nSDLK_KP_PERIOD    266  53      53            71\nSDLK_KP_DIVIDE    267  35      E035          E04A\nSDLK_KP_MULTIPLY  268  37      37            7C\nSDLK_KP_MINUS     269  4A      4A            7B\nSDLK_KP_PLUS      270  4E      4E            79\nSDLK_KP_ENTER     271  1C      E01C          E05A\nSDLK_UP           273  48      E048          E075\nSDLK_DOWN         274  50      E050          E072\nSDLK_RIGHT        275  4D      E04D          E074\nSDLK_LEFT         276  4B      E04B          E06B\nSDLK_INSERT       277  52      E052          E070\nSDLK_HOME         278  47      E047          E06C\nSDLK_END          279  4F      E04F          E069\nSDLK_PAGEUP       280  49      E049          E07D\nSDLK_PAGEDOWN     281  51      E051          E07A\nSDLK_F1           282  3B      3B            05\nSDLK_F2           283  3C      3C            06\nSDLK_F3           284  3D      3D            04\nSDLK_F4           285  3E      3E            0C\nSDLK_F5           286  3F      3F            03\nSDLK_F6           287  40      40            0B\nSDLK_F7           288  41      41            83\nSDLK_F8           289  42      42            0A\nSDLK_F9           290  43      43            01\nSDLK_F10          291  44      44            09\nSDLK_F11          292  57      57            78\nSDLK_F12          293  58      58            07\nSDLK_NUMLOCK      300  45      45            77\nSDLK_CAPSLOCK     301  3A      3A            58\nSDLK_SCROLLOCK    302  46      46            7E\nSDLK_RSHIFT       303  36      36            59\nSDLK_LSHIFT       304  2A      2A            12\nSDLK_RCTRL        305  1D      1D            14\nSDLK_LCTRL        306  1D      E01D          E014\nSDLK_RALT         307  38      38            11\nSDLK_LALT         308  38      E038          E011\nSDLK_LSUPER       311  5B      E05B          E01F\nSDLK_RSUPER       312  5C      E05C          E027\nSDLK_SYSREQ       317  37      E02AE037      E012E07C\n w/ Shift/Ctrl                 E037          E07C\n w/ Alt                        54            84\nSDLK_BREAK        318  C5      E11D45        E11477\n w/ Ctrl                       E046          E07E\nSDLK_MENU         319  5D      E05D          E02F\n"
  },
  {
    "path": "docs/STYLE.md",
    "content": "# MegaZeux code formatting and style guide\n\nThis file is an attempt to summarize the coding style that should be used in\ncode contributed to MegaZeux.  It is a work-in-progress.  Contributions to the\nMZX source code will not be merged unless they comply with this guide.\n\n## License\n\nInclude the license at the start of every file in a block comment starting with\n`MegaZeux` and followed by copyright notices for all authors that have\ncontributed significantly to the file. If you contribute significantly to a\npre-existing file, you are recommended to add your copyright.\n\n## Margin\n\nWrap lines at 80 chars. Minor deviations from this are acceptible but generally\nspeaking lines significantly longer than this tend to have readability issues.\n\n## Control statements\n\nControl statements are not separated from their parentheses.\n\n```\nif(a == b)\n```\n\n```\nwhile(cond)\n```\n\n```\nfor(i = 0; i < x; i++)\n```\n\n```\nswitch(cond)\n```\n\n```\ndo\n{\n  ...\n}\nwhile(cond);\n```\n\n## Curly braces\n\nCurly braces belong on their own lines in function definitions, control\nstructures, structure/enum definitions, and in the outermost level of an array\nof structures or a 2D array.\n\n```\nvoid main(int argc, char *argv[])\n{\n}\n```\n\n### if/else\n\nThe following spacing/curly brace formats are generally okay:\n\n```\nif(cond)\n  do_thing();\n```\n\n```\nif(cond)\n  do_thing();\n\nelse\n  do_other_thing();\n```\n\n```\nif(cond)\n{\n  ...\n}\n```\n\n```\nif(cond)\n{\n  ...\n}\nelse\n{\n  ...\n}\n```\n\n```\nif(cond)\n{\n  ...\n}\nelse\n\nif(other_cond)\n{\n  ...\n}\n\nelse\n{\n  ...\n}\n```\n\nDo not mix curly braces and no curly braces in the same if/else chain unless\nyou are excluding curly braces from the final else. For example, this is OK:\n\n```\nif(cond)\n{\n  ...\n}\nelse\n  thing();\n```\n\nbut this is bad form (and generally looks tacky):\n\n```\nif(cond)\n  lol();\nelse\n{\n  ...\n}\n```\n\n### Structures/enums\n\nDefinition:\n```\nstruct a_struct\n{\n  ...\n};\n\nenum an_enum\n{\n  ...\n};\n```\n\nAssignment:\n```\nstatic struct a_struct thing =\n{\n  ...\n};\n```\n\n### Array definitions\n\nArrays of structs or 2D arrays only need to have curly braces on their own lines\nfor the outermost brace level. When defining a struct on a single line you\nshould generally tabulate its values so it is more readable.\n\n```\nstruct a things[] =\n{\n  { \"abcd\",       abcd_fn },\n  { \"hjsdfksd\",   hjsdfksd_fn },\n};\n```\n\nPutting the inner braces on their own lines is OK too:\n\n```\nstruct a things[] =\n{\n  {\n    \"abcd\",\n    abcd_fn\n  },\n  {\n    \"hjsdfksd\",\n    hjsdfksd_fn\n  },\n};\n```\n\nA trailing comma on the final element of an array or enum is OK.\n\n\n## Indentation\n\nUse spaces, not tabs. Exception: shell scripts and Makefiles should use tabs\ninstead.\n\n### Individual lines\n\nUse two *spaces* for each indentation level.\n\n```\n{\n  {\n    {\n      // ...\n    }\n  }\n}\n```\n\n### Wrapped lines\n\nTypically use one space after the current indentation level.\n\n```\n  if(a == b && ... &&\n   a == f)\n```\n\nFor certain expressions, it is OK and might be useful to indent further:\n\n```\n  if((a == b) &&\n   (b == c ||\n    c == d))\n```\n\n```\n  if(a == SOME_THING_1 ||\n     b == SOME_THING_2 ||\n     c == SOME_THING_3)\n```\n\nWhen splitting a function call with many arguments to multiple lines,\nthis indentation may look better (and helps avoid the margin!):\n\n```\n  func([params,]\n    params,\n    params,\n    ...\n  );\n```\n\n### Macros\n\n`#ifdef` et al. should not be indented. Aside from very short blocks, note the\noriginal condition in a block comment after `#endif`.\n\n```\n    // some code\n    ...\n\n#ifdef CONFIG_DEBYTECODE\n    ...\n#endif /* CONFIG_DEBYTECODE */\n\n    ...\n    // more code\n```\n\nIn complex cases where there is no other option, left-hash indentation is OK:\n\n```\n#ifdef CONFIG_GLES\n# ifdef CONFIG_RENDER_GL_FIXED\n#   if SDL_VERSION_ATLEAST(3,0,0)\n#     include <SDL3/SDL_opengles.h>\n#   else\n#     ...\n#   endif\n# endif\n# ifdef CONFIG_RENDER_GL_PROGRAM\n#   ...\n# endif\n#else\n# ...\n#endif\n```\n\n### Tabulated definitions\n\nIn some places where many values or repetitive structs or enums are defined,\ntabulation is used to keep these definitions readable. When editing a definition\nor set of definitions with tabulation, you should follow the existing tabulation.\n\nTabulation examples from MZX's source code:\n\nworld.h\n```\nenum mzx_version\n{\n  V100            = 0x0100, // Magic: MZX\n  V200            = 0x0205, // Magic: MZ2\n  V251            = 0x0205,\n  ...\n  VERSION_DECRYPT = 0x0211, // Special version used for decrypted worlds only.\n```\n\ncounter.c\n```\nstatic const struct function_counter builtin_counters[] =\n{\n  { \"$*\",               V262,   string_counter_read,  string_counter_write },\n  { \"abs!\",             V268,   abs_read,             NULL },\n  { \"acos!\",            V268,   acos_read,            NULL },\n  { \"arctan!,!\",        V284,   atan2_read,           NULL },\n```\n\nsai.frag\n```\n#define SHARPEN_EDGES     1\n#define MULTIPLIER        10.0\n\n#define TEX_SCREEN_WIDTH  1024.0\n#define TEX_SCREEN_HEIGHT 512.0\n#define PIXEL_X           1.0 / TEX_SCREEN_WIDTH\n#define PIXEL_Y           1.0 / TEX_SCREEN_HEIGHT\n#define HALF_PIXEL_X      0.5 / TEX_SCREEN_WIDTH\n#define HALF_PIXEL_Y      0.5 / TEX_SCREEN_HEIGHT\n```\n\n## Naming\n\nVariables, structs, and functions in C should generally be named with lowercase\nwords separated by underscores (examples: `thing`, `other_thing`).\n\nConstants should be `ALL_CAPS_WORDS_SEPARATED_BY_UNDERSCORES`.\n\nC++ code should follow the same guidelines as C with the exception that class\nnames (and only class names) may use `CamelCase` instead of C naming.\n\n## Types\n\n### Integers\n\nThe following ways of expressing integer types are generally OK.\nOther variants/orderings of specifiers may be redundant or undesirable,\nbut not necessarily bad. Don't mix cv specifiers between type words.\n\n| Typical | OK variants |\n|---------|-------------|\n| `char`                    | `unsigned char` (MZX uses -funsigned-char)\n| `signed char`             |\n| `short`                   |\n| `unsigned short`          |\n| `int`                     |\n| `unsigned int`            | `unsigned`\n| `long`                    | `long int`\n| `unsigned long`           | `unsigned long int`\n| `ssize_t` and `ptrdiff_t` |\n| `size_t`                  |\n\nC99 stdint.h fixed-width integer types should be used in cases where the\nwidth of a particular field needs to be guaranteed and/or emphasized. `int64_t`\nand `uint64_t` should be preferred over `long long` and `unsigned long long`.\nThe types `size_t`, `ssize_t`, and `ptrdiff_t` should not be assumed to be\n64-bits wide.\n\nDon't use SDL's fixed-width integer typedefs outside of SDL-specific code.\nPreviously these were used in quite a few places, and it caused `platform.h`\nincludes to creep into most files.\n\n### Pointers\n\nPointers are written in this style:\n```\nint *an_int_pointer;\n```\n```\nconst void *data;\n```\n```\nconst char * const a;\n```\n\n### Casting\n```\n  int an_int = (int)not_an_int;\n```\n```\n  int *an_int_ptr = (int *)not_an_int_ptr;\n```\n\n### Constants\n\nConstants should typically be preprocessor defines in C-compatible code/headers\nand `const` or `constexpr` in C++-only code (see C++98 vs C++11 below).\n\n```\n#define CHAR_W  8\n#define CHAR_H  14\n```\n```\nconst int CXX_CONSTANT        = 123;\nconstexpr int CXX_CONSTANT_2  = (CXX_CONSTANT * 456);\n```\n\nGenerally don't make constants in C code `const`. Also note that constants\ntypically exist for a reason (usually to make the code more readable and\nmaintainable), so code with consistent constant usage shouldn't be polluted\nwith magic numbers unless you have a good excuse.\n\n### `struct` and `class` in C++\n\nIn C++ code, `struct` and `class` may be omitted when using classes exclusive\nto the C++ code. When referencing types defined and used in C code, preface the\ntype name with `struct` for consistency between the two languages.\n\n## Language features\n\n### C vs. C++\n\nMZX is almost entirely written in C.  Add C++ only where absolutely necessary\ne.g. when interfacing with a library that only has a C++ interface available,\nor for things that need inheritance or access specifiers or templates to be\nmaintainable (example: the network code was mostly a mess prior to getting a C++\nmakeover).\n\n### C++98 vs. C++11\n\nWhen possible, MegaZeux's build system will attempt to select the C++11 standard\nwith GNU extensions for compilation. However, when this is not available, it\nwill fall back to C++98. For compatibility purposes, all platform-independent\nC++ code in MZX should be C++98 compatible. If such code uses C++11 features,\nthese should be enabled at compile time. Optional parts of MZX that rely on\nC++11 (such as the unit tests) should be disabled when C++11 support is not\navailable or is unreliable.\n\nTo avoid including libstdc++/libc++ for platforms where it would just bloat\n(Android NDK) or pull in ugly DLL dependency chains (MinGW via MSYS2),\nfeatures dependent on these libraries should be completely avoided. This means\nno exceptions and using MZX-specific implementations of new/delete/etc.\n(see `src/nostdc++.cpp`).\n\nFeatures introduced in C++11 that can be used without compatibility checks due\nto defines in `src/compat.h` are:\n\n| Feature | C++98 `compat.h` define |\n|---------|-------------------------|\n| `nullptr`             | `(NULL)`\n| `final` qualifier     | ` `\n| `noexcept` qualifier  | ` `\n| `override` qualifier  | ` `\n\n### C99 Features\n\nMost C99 features are allowed as MSVC now properly supports them. The primary\nexceptions are:\n\n* Never use VLAs.\n* Never use stdbool.h or any C type declared as \"bool\". There's a nasty\n  portability problem when using `bool` between C and C++ where the size of\n  this type will not necessarily be the same between the two languages.\n  Use `boolean` as defined in compat.h instead.\n* Only use designated array initializers in code that will never be compiled\n  as C++ (as C++ does not support them).\n* As a general rule, mixed declarations and code should be avoided as well.\n  The primary exception to this is when using declarations for RAII in C++.\n* The `restrict` keyword should be used only through the macro `RESTRICT`.\n  This is for intercompatibility with C++. See GNU extensions for more info\n  and restrictions on usage.\n\n### C standard library functions\n\nAvoid use of the following C standard library functions:\n\n* `strcpy`, `strcat`, and `sprintf` do not check the size of the provided buffer\n  and should only be used in circumstances where there is no chance of an overflow.\n  Generally speaking, `snprintf` (followed by manual null termination for MSVC\n  safety) or `memcpy`/`memmove` is preferable.\n* `strncpy` and `strncat` do not do what you think they do. `strncpy` was\n  intended to null-pad fixed size buffers and does not insert a null terminator\n  if there is an overflow. The length parameter of `strncat` does not specify\n  the size of the provided buffer but instead how many bytes from the source\n  should be copied. Do not use these functions, ever.\n* `strtok` is non-reentrant; it stores the initial pointer provided to it in a\n  static variable. Use `strsep` instead (see POSIX features).\n\n### GNU extensions\n\nGenerally speaking, MZX should not use GNU extensions as they will worsen\ncompatibility. The following GNU extensions are allowed:\n\n* Variadic macros.\n* Attributes (such as unused or format(printf) or format(gnu_printf)) to avoid\n  spurious warnings. Generally these should be wrapped in `#ifdef __GNUC__`.\n* Attributes to specify deprecated functions (wrapped in `#ifdef __GNUC__`).\n* `long long` (should usually use stdint.h instead).\n* Pre-C++11 code that relies on stdint.h.\n* Pragmas to locally disable warnings are acceptable in rare cases.\n* `__PRETTY_FUNCTION__` (C++ only; an MSVC compatibility define is included).\n* The `__restrict` keyword provided for C and C++ by GCC, Clang, ICC, and MSVC\n  may be used via the macro `RESTRICT` defined in compat.h. Since this is an\n  extension in C++, it's probably best to use it only on pointers. `RESTRICT`\n  should generally only be used in places where it's verified to produce a\n  significant performance increase (example: the software renderers).\n\n### POSIX features\n\nThe following POSIX features are allowed, though special restrictions may apply.\nThis list is probably not complete.\n\nFeatures with existing abstractions that shouldn't be used directly:\n* `dirent.h` (use `src/io/dir.{c,h}` instead)\n* `pthread.h` (use `src/platform.h` instead)\n* `getaddrinfo` (use `src/network/DNS.{cpp,hpp}` instead)\n* Sockets and other network features (use `src/network/Host.{cpp,hpp}` instead)\n\nFeatures with partial or complete fallback implementations for MSVC:\n* `ssize_t` (`msvc.h`)\n* `sys/stat.h` (`msvc.h`)\n* `unistd.h` (`msvc.h`) (includes of this header should be wrapped in `#ifndef _MSC_VER`).\n* `dirent.h` (`arch/msvc/dirent.h`)\n* `gettimeofday` (`arch/msvc/win32time.{c,h}`)\n\nFeatures with fallback implementations for MSVC and/or MinGW and/or AmigaOS 4:\n* `strsep` (`util.{c,h}`)\n* `strcasecmp` (`util.{c,h}` and/or `msvc.h`)\n* `strncasecmp` (`util.{c,h}` and/or `msvc.h`)\n* `mkdir(name,mode)` (`util.h` and/or `msvc.h` and/or `src/io/vfile.{c,h}`)\n* Various network features (`src/network/Socket.{cpp,hpp}`)\n\nOther notes:\n* Never use `fdopen` on a file descriptor obtained from using `fileno` on a\n  `FILE` pointer. This usage of `fdopen` will trick most operating systems into\n  thinking you leaked a file descriptor, and it is impossible to close either\n  resulting `FILE` pointer safely without corrupting the other.\n\n## Organization\n\n### Opaqueness and typedefs\n\nTypedefs are appropriate in MZX two cases:\n1) Hiding really ugly/long function pointer definitions.\n2) Ubiquitous basic types (e.g. boolean) or opaque types (e.g. FILE).\nOtherwise, don't use them.\n\nSome subsets of the MZX source (particularly audio, rendering, and networking)\nheavily practice hiding structs and implementation-specific functions.  Look at\nother files in these areas to make sure your audio handler/renderer/etc follows\nconventions.\n\n### Headers\n\nGeneral format is:\n\n```\n[GPL Copyright notice/license - see LICENSE]\n\n#ifndef __[YOUR FILE HERE]_H\n#define __[YOUR FILE HERE]_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n[all other contents]\n\n__M_END_DECLS\n\n#endif /* __[YOUR FILE HERE]_H */\n```\n\nNotes:\n1) The GPL notice must be in every source file. See LICENSE for more information.\n2) Each header needs a unique include guard to ensure that it isn't included multiple times.\n3) `src/compat.h` defines some required compatibility macros, including `__M_BEGIN_DECLS`\nand `__M_END_DECLS`. These macros are used to provide portability when the MZX headers\nare included in C++ files.\n4) Functions/structs/variables in these MUST be required elsewhere; otherwise, use static and\nput it in the C/CPP file!\n\n### Includes\n\nIncludes should generally be organized into several blocks separated by blank\nlines. The order of these blocks may vary but the order specified here is\nusually preferred. An effort should be made to keep each of these blocks in\nalphabetical order. Use angle brackets for system and external library includes\nand use quotes for MZX and contrib includes.\n\n* C standard library includes and other library includes (e.g. `zlib.h`).\n  In some cases the other library includes are in a separate block. External\n  library includes (particularly `SDL.h`) should be restricted to places that\n  explicitly need them to help reduce build times.\n* For Windows, `windows.h` may need to be included and certain headers may\n  need to be replaced with `_MSC_VER`-specific replacements. This should be its\n  own block, and in most cases `WIN32_LEAN_AND_MEAN` should be defined immediately\n  before the include. This header should be restricted to places that explicitly\n  use it, because it can make MinGW builds SLOW!\n* Includes from the current source dir or \"module\", followed by blocks for\n  includes from other dirs/\"modules\". These includes generally should be relative.\n  * Core includes (files in `src/` and `src/io/` should be in the same block,\n    with `src/` files first and `src/io/` files at the end).\n  * Audio includes (files in `src/audio/`).\n  * Editor includes (files in `src/editor/`).\n  * Network includes (files in `src/network/`).\n  * Contributed includes (files in `contrib/*/`).\n\n### Declarations\n\nUsually declarations occur in the following order, though there's no real\nconsistency between different files:\n\n1) `#define`s, enum declarations, and struct declarations.\n\nThen, in .c/.cpp files:\n\n2) static variable declarations.\n3) function definitions.\n\nOr in headers:\n\n2) extern variable declarations.\n3) `LIBSPEC` external function declarations.\n4) Other external function declarations.\n5) Conditionally external function declarations.\n6) static inline function declarations.\n\n### `LIBSPEC` macros\n\nThe `LIBSPEC` macros are used in function declarations to specify conditionally\nexternal functions/variables for modular builds. The `LIBSPEC` macro is defined\nas follows:\n\n| Build type                | `LIBSPEC` |\n|---------------------------|-----------|\n| Non-modular               | ` `\n| Windows, modular          | `__declspec(dllimport)`\n| Everything else, modular  | `__attribute__((visibility(\"default\")))`\n\nThen, the following `LIBSPEC` macros are defined:\n\n| Macro             | core      | editor    | megazeux/mzxrun | utils             | Used in |\n|-------------------|-----------|-----------|-----------------|-------------------|---------|\n| `CORE_LIBSPEC`    | Export    | `LIBSPEC` | `LIBSPEC`       | `LIBSPEC` or ` `  | `src/`, `src/io/`\n| `EDITOR_LIBSPEC`  | `LIBSPEC` | Export    | `LIBSPEC`       | n/a               | `src/editor/`\n| `AUDIO_LIBSPEC`   | Export    | `LIBSPEC` | `LIBSPEC`       | n/a               | `src/audio/`\n| `UTILS_LIBSPEC`   | Export    | `LIBSPEC` | `LIBSPEC`       | ` `               | `src/`, `src/io/`\n| `UPDATER_LIBSPEC` | Export    | `LIBSPEC` | `LIBSPEC`       | n/a               | `src/network/`\n\nExport = `__declspec(dllexport)` for Windows modular builds and `LIBSPEC` for\nall other builds.\n\nThese macros should only be used in declarations, e.g.:\n```\nCORE_LIBSPEC void *check_malloc(size_t size, const char *file, int line);\n```\n\n### Conditionally external function macros\n\nThe following conditionally external function/variable macros are defined:\n\n| Macro                     | Notes |\n|---------------------------|-------|\n| `__editor_maybe_static`   | Is `static` when the editor is disabled.\n| `__updater_maybe_static`  | Is `static` when the updater is disabled. Unused.\n| `__utils_maybe_static`    | Is `static` when the utils are disabled. Unused.\n\nThese macros are typically used for function/variable definitions and the header\ndeclarations of these functions/variables are typically enabled by an `#ifdef`\nfor the particular build option that expects them.\n\nDefinition (board.c):\n```\n__editor_maybe_static\nint load_board_direct(struct world *mzx_world, struct board *cur_board,\n struct zip_archive *zp, int savegame, int file_version, unsigned int board_id)\n{\n  ...\n}\n```\n\nDeclaration (board.h):\n```\n#ifdef CONFIG_EDITOR\nCORE_LIBSPEC int load_board_direct(struct world *mzx_world,\n struct board *cur_board, struct zip_archive *zp, int savegame,\n int file_version, unsigned int board_id);\n#endif /* CONFIG_EDITOR */\n```\n\n### Cross-platform threading\n\nFor general usage see `src/thread_sdl.h` or `src/thread_pthread.h`. To use\nthreading in a file, just include `src/platform.h`; this will include the\nappropriate platform-specific thread implementation.\n\nUse the macro `THREAD_RES` as the return type of the thread function and `THREAD_RETURN;` to\nexit the thread function, even if control would otherwise reach the end of the function.\nThis is necessary due to some platforms expecting a pointer, some expecting an integer,\nand some that use no return type at all.\n\nIn general, parts of MegaZeux likely to be used in a multithreaded context\nshould be thread-safe (particularly the `check_alloc` series of functions and\nthe `src/nostdc++.cpp` implementations of new/delete, which internally use\n`check_malloc`). Calling anything related to MZX gameplay from a thread should\ngenerally be avoided.\n"
  },
  {
    "path": "docs/WIPHelp.txt",
    "content": "$@1                   \n$@1  ~fM ~bE G ~3A Z ~bE U ~fX  \n$@1                   \n:072:\n$~9Table of Contents\n\n$~C1) Basics and Gameplay\n>#FAQ.HLP:1st:FREQUENTLY ASKED QUESTIONS.\n>#1ST_TIME.HLP:071:Overview of MegaZeux - First Time Users Read This!\n>#HELPONHE.HLP:000:Help on Help (Press F1 Within Help to Read This)\n>#CONFGINI.HLP:1st:The Config File\n>#MOUSESUP.HLP:1st:Mouse Support in MegaZeux\n>#DIABOX.HLP:098:Dialog Boxes\n>#CONTROLS.HLP:ctr:Controls\n>#NONDESCR.HLP:091:Nondescript Play Tips\n>#BUILTINS.HLP:1st:The Mirth of Built-ins\n\n$~C2) The Editor\n>#THEWORLD.HLP:1st:The World Editor\n>#GENERALE.HLP:1st:General Editing Tips\n>#EDITINGK.HLP:080:Editing Keys and Options Reference\n>#CHAREDIT.HLP:079:The Character Editor\n>#PALEEDIT.HLP:093:The Palette Editor\n>#SMZXMODE.HLP:095:Super MegaZeux Modes\n>#GLOBALIN.HLP:086:Global Info Options\n>#BOARDINF.HLP:085:Board Info Options\n>#SOUNDEFX.HLP:1st:MegaZeux's Sound System\n>#TOVERLAY.HLP:081:Editing and Using the Overlay\n>#SCROLLSS.HLP:1st:Signs and Scrolls in the Editor\n\n$~C3) Coding With Robotic\n>#ROBOTSWH.HLP:1st:Robots - What They Are and How to Use Them\n>#ROBOTICT.HLP:1st:Robotic Tutorial\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#DBGMODE.HLP:dbg:Debug Modes\n>#SENSORSW.HLP:094:Sensors - What They Are and How to Use Them\n>#ZZT.HLP:zzt:A ZZTer's Guide to MegaZeux\n\n$~C4) Miscellaneous\n>#UPDATER.HLP:099:The MegaZeux Updater\n>#ERRORMES.HLP:1st:Error Messages\n>#MEGAZEUX.HLP:1st:MegaZeux Limitations\n>#IFYOUFIN.HLP:1st:If You Find a Bug...\n>#NEWINVER.HLP:1st:NEW in MegaZeux!\n\n$** Credits and Acknowledgments **\n\n$Programming and Overall Design by Gilead Kutnick (Exophase),\n$Alistair Strachan (ajs), Alice Rowan (Lachesis) and Lancer-X\n$Based off of original program and source code by Alexis Janson\n$Help file by Terryn\n$Default SMZX palette by Joel Lamontagne (LogiCow)\n$ccv utility by Lancer-X\n$png2smzx utility by Alan Williams (Mr_Alert)\n$checkres utility by Josh Matthews (Revvy), ajs and Lachesis\n$Port contributors: Adrian Siekierka (asiekierka) [3DS],\n$Mr_Alert [Wii], Kevin Vance [NDS], Simon Parzer [GP2X]\n$Renderer code contributors: LogiCow, Mr_Alert\n$Shader code contributors: David Cravens (astral), GreaseMonkey\n$Icon by Quantum P.; Extra icons by LogiCow\n$GDM conversion by ajs and MadBrain\n$Other past contributors: Spider124, Koji, JZig, Akwende,\n$MenTaLguY.\n\n$** Special Thanks **\n\n$Dizzy (.deb Builds)\n$mzxgiant (MSVC Testing, Bug Fixes)\n$mzxrules (Testing)\n$Quantum P. (OS X Testing / Builds)\n$Spectere (OS X Builds)\n$Terryn (Testing)\n$Wervyn (Testing)\n\n$MegaZeux 2.80 Thanks & Beta Testers:\n$Exophase\n$ZoMbIeGuY\n$Wervyn\n$Quasar84\n$Lancer-X\n$KenOhki2112\n$Terryn\n$Inuchance (Macintosh testing)\n$Everyone who submitted a bug report\n\n>072:Table of Contents\n#HELPONHE.HLP\n:000:\n$~9Help on Help\n\nUsing MegaZeux's help system is very simple. Press F1 at almost\nany time to bring up context-relevant help. Within help, use the\narrow keys to scroll the current section. Press PageUp and\nPageDown to scroll faster. Many sections of help contain\nselections, like this:\n\n>sl:Selection\n:sl:\nScroll the section until the pointer is aligned with the arrows\non the edges of the help box, and press Enter. You will jump to\na help section indicated by the selection. Example: If a\nselection says \"Controls\", scroll until the word is aligned\nwith the arrows on the left and right, and press Enter. You\nshould now be reading help on Controls.\n\nPress F1 within help to jump to this section. Press Alt+F1\nwithin help to jump to the Table of Contents (Table of Major\nHelp Topics). Press ESC to exit the help system.\n\n>#MAIN.HLP:072:Table of Contents\n#1ST_TIME.HLP\n:071:\n$~9Overview of MegaZeux\n\n$Welcome to MegaZeux! Use the arrow keys to scroll\n$this text, and ESC when you are done reading.\n\n$Press END to learn about the NEW FEATURES in\n$MegaZeux!\n\nAs you may already know, MegaZeux is a game system which\nallows you to play almost limitless worlds with\ndated-yet-charming graphics and with excellent digitized music\nand sound. Not only are there several MZX worlds out there\nalready, but new worlds are being uploaded to large websites\nlike DigitalMZX. However, the best feature of MegaZeux is the\nWorld Editor.\n\nUsing the World Editor, ANYONE can create the world of their\ndreams. Make it as simple or complex (well, almost), as easy\nor difficult, as long or short as you please. We aren't just\ntalking about worlds made up of petty, pre-programmed enemies\nand objects; MegaZeux has its own, easy-to-use PROGRAMMING\nLANGUAGE called Robotic that allows you to create objects,\nengines and worlds that do almost anything you desire. Not only\ncan you make your own game with the editor, but every world\nMegaZeux can play can be opened in the editor. Take a look at\nhow other games tick!\n\nFor the newest user, it's recommended that you play Caverns,\nthe first ever MegaZeux game, to get the feel of simple yet\nwell-designed games in MegaZeux. You may wish to read the help\nsection entitled \"Controls\" to learn how to play MegaZeux.\n\nIf you're more adventurous, start with a more complex game like\nDemon Earth or Bernard the Bard, or a prettier game like & or\nFritz Blitz to see what kind of graphics functions, bells and\nwhistles MZX can offer.\n\nOnce you have the feel for the game, feel free to dive into the\nWorld Editor and get messy! You should probably read the help\nsection entitled \"The World Editor\" first.\n\nTo go to one of these sections now, hit Enter after aligning\nthe arrows with one of these choices. Press ESC now to exit to\nthe game.\n\n>#MAIN.HLP:072:Table of Contents\n>#CONTROLS.HLP:ctr:Controls\n>#THEWORLD.HLP:1st:The World Editor\n\n>#NEWINVER.HLP:1st:NEW in MegaZeux!\n#ZZT.HLP\n:zzt:\n$~9A ZZTer's Guide to MegaZeux\n\nZZT and MegaZeux are worlds apart, although one can convert\nfrom making ZZT games to making basic MegaZeux games with\n(mostly) minimal effort. This section is to help seasoned\nZZTers adjust to MegaZeux.\n\nFirstly, some mechanistic differences exist. Here are some of\nthe more prominent examples.\n\n-MegaZeux uses counters instead of flags; however, counters\ncan easily be used as flags if set only to 1 and 0.\n-MegaZeux has an incredibly high amount of available counters.\n-There is no default mechanism for torches.\n-No equivalent of centipedes exists as a default enemy.\n-Multiple MegaZeux keys of the same color can be picked up at\n once.\n-There is no board setting controlling the amount of player\nshots that can be on a given board, outside of preventing the\nplayer from shooting altogether.\n-Cloning the player has no use in MZX; uses of player clones\nneed simulated in Robotic.\n-Unlike ZZT Objects, MZX Robots can detect being shot when the\nplayer shoots them point-blank.\n-ZZT prevents moving between boards if the other board has an\nobject where the player would end up, but MZX simply overwrites\nthe object on the other board with the player.\n-Bomb explosions are not instantaneous but instead radiate out\nfrom the center. They are also diamond-shaped as opposed to\na flattened oval shape and do not ignore walls.\n-MegaZeux uses cycle 1 as default (as opposed to ZZT's cycle\n3).\n-No equivalent of duplicators exists.\n-Seekers (aka \"stars\") and bears do not destroy Breakaway\nterritories in MegaZeux.\n-MegaZeux water is not equivalent to ZZT water, as it can be\ntraversed. ZZT water is most similar to MZX's \"goop\".\n-MZX Sharks can be shot. They do not swim in water, but can in\nlava or goop. They can also shoot any type of projectile.\n-MZX Scrolls cannot execute code. Use a Robot ending with the\n\"die\" command to emulate ZZT scrolls with code instead.\n-Terrains that give out a distinctive message on first touch\n(fakes, forests) give out no such message in MZX.\n-There are two types of Gems; the one that best emulates ZZT\ngems is the Magic Gem.\n-Lasers will not push the player into other objects. This makes\nsome ZZT laser configurations a lot less deadly.\n-Scrolls with only one line launch a message box (instead of\nusing the message line for the line).\n-The Shift-? hotkey (often used for inventory engines) does not\nexist in MegaZeux but can be emulated with Robotic.\n-ZZT has one less bullet type than MZX; shift MZX types one\ntier of \"friendliness\" for each typical shooter to get\nZZT-equivalent bullets.\n-Putting in fake commands for comedic effect (e.g. putting in\n~A#GOSUCKANEGG~F to make ZZT beep and go \"ERR: Bad command\nGOSUCKANEGG\") does not work in MegaZeux and has to be emulated.\n-Special exploits (e.g. black holes, monitors, speed-up\nexploit, flag overloading) have no direct MZX equivalent.\n-Other, extremely minor, differences best left for total wonks\nor for ZZT port authors, while others are implicitly understood\nand do not require spelling out.\n\nSecondly, here is how the ZZT-OOP commands convert to Robotic.\n\n---------------------------------------------------------------\n\nKey:\n\n[*] = There's no direct (i.e. one-line) way to make Robots do\ncommands on IF statements. A label pointing to the command will\nhave to do.\n\n[+] = There is no good analogue for this command, due to\nbound objects sharing zap/restore settings in ZZT. Without\nthese concerns, COPYROBOT is the closest match. Also, ZZT\nobjects using BIND use only as much memory as the object had\nbefore in ZZT, while Robots using COPYROBOT will use as much\nmemory as the target Robot once copied.\n\n[#] = You need to manually add a label at the top of the Robot\nfor this in MZX.\n\n---------------------------------------------------------------\n\n~A@@object_name = . \"@@Robot_name\"\n\n~A/direction/direction = PERSISTENT GO ~F(works only for cardinal\ndirections and idle: n s e w i)\n\n~A?direction = / \"directions\"\n\n~A'comment = . \"comment\"\n\n~Aone line of text = * \"text\"\n\n~Amultiple lines of text = % \"text\"\n\n~A!label;text = ? \"label\" \"text\"\n\n~A$ text = % \"~~ftext\" ~F(by default palette)\n\n~A#BECOME thing = BECOME color thing param\n\n~A#BIND object_name = COPYROBOT \"Robot\" ~F[+]\n\n~A#CHANGE thing newthing = CHANGE color thing param newcolor\n~Anewthing newparam\n\n~A#CHAR # = CHAR #\n\n~A#CLEAR flag = SET \"counter\" 0\n\n~A#CYCLE # = CYCLE #\n\n~A#DIE = DIE\n\n~A#END = END\n\n~A#ENDGAME = ENDGAME\n\n~A#GIVE item # failure_label = GIVE # item ~F(MZX version has no\noptional failure label)\n\n~A#GO direction = GO direction #\n\n~A#IDLE = WAIT #\n\n~A#IF flag label;text = ? \"counter\" \"label\" \"text\"\n\n~A#IF flag THEN command = IF \"counter\" = 1 THEN \"label\" ~F[*]\n\n~A#IF ANY object THEN command = IF ANY color thing param \"label\"\n~F[*]\n\n~A#IF NOT flag THEN command = IF \"counter\" = 0 THEN \"label\" ~F[*]\n\n~A#IF condition THEN command = IF condition THEN \"label\" ~F[*]\n\n~A#IF NOT condition THEN command = IF NOT condition THEN \"label\"\n~F[*]\n\n~A#LOCK = LOCKSELF\n\n~A#PLAY notes = PLAY \"notes\"\n\n~A#PUT direction thing = PUT color thing param direction\n\n~A#RESTART ~F[#]\n\n~A#RESTORE label = RESTORE \"label\" #\n\n~A#SEND label = GOTO \"label\"\n\n~A#SEND Objectname:\"label\" = SEND \"Robotname\" \"label\"\n\n~A#SEND ALL:command = SEND \"All\" \"label\" ~F[*]\n\n~A#SET flag = SET \"counter\" 1\n\n~A#SHOOT direction = SHOOT direction\n\n~A#TAKE item # failure_label = TAKE # item \"failure_label\"\n(failure label optional)\n\n~A#THROWSTAR direction = SHOOTSEEKER direction\n\n~A#TRY direction label = TRY direction \"label\"\n\n~A#UNLOCK = UNLOCKSELF\n\n~A#WALK direction = WALK direction\n\n~A#ZAP label = ZAP \"label\" #\n\n---------------------------------------------------------------\n\nAs for conditions, a few are changed:\n\n~AALLIGNED~F is now ~AALIGNED~F\n\n~ACONTACT~F is now ~ATOUCHING ANYDIR~F\n\n~AENERGIZED~F is no longer a condition but the \"invinco\" label.\n\n---------------------------------------------------------------\n\nThis should be all one needs to make most ZZT-style games for\nMegaZeux, except for a little Robotic knowledge to replicate\nthings like torches and duplicators. Of course, much more\npowerful games can be made with MegaZeux; see corresponding\nhelp for details.\n\n>#MAIN.HLP:072:Table of Contents\n#DIABOX.HLP\n:098:\n$~9Dialog Boxes\n\nMuch of MegaZeux's interface is made of dialog boxes. A dialog\nbox is a form of inputting various information. An example of a\ndialog box is the Save Game box or the Settings box, both\nin-game. To use a box is simple - Use TAB and SHIFT+TAB to\nhighlight an element, and type or use the cursor keys to change\nthe value. Press ENTER on a button (Rectangle with a label) to\nexit the dialog or produce an effect.\n\nMZX also utilizes mouse control. You can click on a dialog box\nelement to select it, or click on a selected element to change\nit. The mouse wheel will cycle up/down the choices in the\ncurrent section. Click on a selected button to activate it.\n\nYou can usually use HOME to jump to the first section of the\ndialog box, and END to jump to the OK or NEXT button. TAB jumps\nforward a section, while SHIFT+TAB goes backwards a section.\n\nThe different dialog box elements and special related keys\nare explained in detail for the remainder of this section.\n\nINPUT - This is where you type in a series of characters,\nusually letters and numbers. You can move the cursor around\nwithin the line, type to insert characters, move to the start\nwith Home, and move to the end with End. Alt + Backspace\ndeletes the entire line, while Ctrl + Backspace deletes the\nlast typed word.\n\nNUMBER - This is where you have a number and you change it\nwith the arrow keys or mouse clicks on buttons. Up, Down, and\nthe mouse wheel will change it by 1, Alt+Up, Alt+Down and\nAlt+Wheel will change it by 10, and PageUp and PageDown will\nchange it by 100. You can also hold the mouse button down on one\nof the arrows to change the number or press 0-9 to set the last\ndigit. Pressing 0-9 on the keypad changes the current digit to\nthat value. Finally, Backspace deletes the last digit.\n\nNUMBER LINE - This is a line of numbers, with one highlighted.\nIt functions in a manner similar to the above, but in a limited\nrange.\n\nCHARACTER - This is where you can select characters. Press a\ncharacter to use that character; click on it or press Enter to\nget a large menu of characters (32x8) to select from.\n\nCOLOR - This is where you can select a single color. Press\nEnter or click on it to get a large menu of colors to choose\nfrom.\n\nCHECK BOXES - These are On/Off switches. Use Up/Down or the\nmouse wheel to move within the list of choices, and use Space,\nEnter, or mouse clicks to toggle choices on and off. Press Tab\nto jump out of a check box section. An [X] means the option is\ncurrently on.\n\nRADIO BUTTONS - These are similar to Check Boxes, but are\nmutually exclusive. The mouse or arrow keys will select the\ncurrent and ONLY option that is on. The ( ) shows the current\noption.\n\nBUTTON - These are rectangles with labels. When selected,\npress Enter or click to activate. OK or Done will verify the\ncontents of the dialog box. Cancel will cancel any changes\nyou've made. (ESC will usually do this as well.) Next and\nPrevious move between multiple dialog boxes. Other buttons\ndo what their label signifies and are explained in the\nappropriate help section.\n\nLISTS - These show a selection from a list of choices. The\ncurrent selection is shown. Press Enter or click on it to\nbring up a list of choices from which you can select a new\nchoice.\n\n>#MAIN.HLP:072:Table of Contents\n#CONTROLS.HLP\n:ctr:\n$~9Controls\n\nUniversal controls are as follows:\n\n-Tab goes forward one section; Shift+Tab goes backward one\nsection.\n\n-Esc cancels.\n\n-Enter selects.\n\n-Alt+Backspace deletes the entire line.\n\n-Ctrl+Backspace deletes the last typed word.\n\n-Ctrl+Left jumps to the previous word; Ctrl+Right jumps to the\nnext word.\n\n-0 thru 9 sets the last digit in a number box (such as setting\namounts of items in a chest); backspace deletes the last digit.\n\nOn Mac platforms, any command that uses the Alt key can take \n(the command key) instead.\n\nFile access controls are as follows:\n\n-(0-9 and A-Z) will jump to the first file/directory starting\nwith that character. More specific seeking can be done by\ntyping characters in quick succession (for instance, s+e+c\nwould jump to the first file starting with sec).\n\n-Del will delete the highlighted file/directory. You will be\nasked for confirmation.\n\n-Alt+R will rename the highlighted file/directory.\n\n-Alt+N will create a new directory.\n\nIn-game controls are as follows:\n\nF1 - Help\nUse this at any time to bring up context-relevant help. While in\na loaded world, this key will load the Controls section. Some\ngames may have this menu disabled or replaced with their own\nhelp menus.\n\nEnter - Menu/Status\nUse this to bring up a menu of options and a list of your\ncurrent stats, such as Score, Gems, any custom-defined status\ncounters, etc. Some games may have this key do nothing, or even\nsomething different entirely. Selecting the key shortcuts\n(everything but move/shoot/bomb) will perform the given action.\nEither click on actions to select them, or highlight an action\nwith the arrow keys and press Enter.\n\nCertain key shortcuts will not be listed if the current MZX\nworld prevents their default actions from happening. \"Help\",\n\"Save\", \"Load\", \"Quicksave\" and \"Quickload\" can be filtered out\nthis way, while \"Exit game\" and \"Settings\" are always available\nand still work when selected even if otherwise locked out.\n\nESC - Exit to Title\nThis will load a prompt to quit the current game and return to\nits title screen. In yes/no dialog boxes, this key triggers the\n\"no\" option. Some games may disable using ESC to trigger the\nexit prompt, but other methods (such as Alt+F4 or selecting the\n\"ESC - Exit to Title\" text in the Enter menu) will always work.\n\nF2 or Ctrl+F2 or Alt+F2 - Settings\n:092:This will bring up a dialog box where you can change the\ncurrent game settings: Game Speed (if allowed), Music/sample SFX\ntoggle, PC speaker SFX toggle, and Audio Volumes (Music,\nSamples, and PC Speaker).\n\nSpeed 1 is the fastest; speed 16 is the slowest. A speed of 1\nwill run MZX as fast as possible and as such acts differently\nbetween computers; it will also cause flicker in fullscreen\nmode, so it is not recommended unless it is your only option.\n(Please note that some games may have the speed change by\nitself and/or enforce a set speed.)\n\nNo changes to sound options will go into effect until the\nSettings menu is left.\n\nIn addition to these settings, one can change the current\nrenderer with the \"Select renderer...\" button, and can change\nthe scaling shader if running MegaZeux with the GLSL or\nauto_GLSL renderers.\n\nSettings for game speed will only apply for the current world;\nall other settings are global.\n\nNote that some games may block access to the F2 menu entirely.\nCtrl+F2 or Alt+F2 will ignore this setting and allow access to\nthe menu even if it is prohibited otherwise, as will selecting\nthe \"F2 - Settings\" text in the Enter menu. Ctrl+F2/Alt+F2 will\nnot override any F2 block while MZX is in standalone mode.\n\nF3 - Save Game\nThis will load a prompt for a filename, allowing the saving of\nthe exact state of the current game. Some games may not allow\nmanual saving on some (or any) of its screens, or will only\nallow manual saving in certain spots of a board. Saving lets you\nquit your game in the middle or take precautions against unknown\ndangers.\n\nF4 - Restore Game\nThis will let you select a saved game from a list of filenames.\nThe game will then be reloaded from the same point you left\noff. While most games will allow loading anywhere, some will\nrestrict loading to certain times or will use a custom\nmechanism for loading games instead. MegaZeux will refuse to\nload any save made in a version older than 2.84 or newer than\nthe current version number. (For instance, 2.92 can load saves\nmade in 2.92c and 2.90b, but not saves made in 2.93 or 2.83.)\n\nMegaZeux has the ability to save and load games in save slots\ninstead of choosing a file. Save slots are tied to the current\nMZX world, and each MZX world gets 10 save slots. This can be\nenabled in the config file.\n\nNOTE: A few games may save or load automatically, and\nhigh-caliber games may have completely different methods of\nsaving and loading games.\n\n>#CONFGINI.HLP:1st:The Config File\n\nF5 or Ins - Toggle Bomb Type\nThis will switch your current bomb type between High Strength\nand Low Strength.\n\nF6 - Debug Window\nThis will bring up a small box in the lower left corner of the\nscreen, detailing several statistics. The top-right of the box\ndisplays the world version of the current world; X/Y lists the\ncurrent x,y position of the player; Board lists the current\nboard number; Robot mem lists the amount of Robot memory\ncurrently consumed by the current board's Robots; the space\nbelow lists the currently playing music, with \"(no module)\" for\nnone and \"*\" for the module wildcard; finally, the bottom right\nnumbers list the key_code (green) and key_pressed (magenta)\nvalues of the last pressed key. Press F6 again to turn the box\noff. This shortcut will only work during playtesting in the\neditor.\n\nF7 - Items Cheat\n\nThis will set ammo, coins, gems, hibombs and lobombs to 32767,\nfill health (set to maxhealth), fill lives (set to maxlives),\nand set keys to one of each color. This is a cheating function,\nso do not use unless fully necessary; a few games punish the\nplayer for using this function. This may be limited to\nplaytesting sessions in the editor, or limited to MZXRun builds,\ndepending on config.txt settings.\n\nF8 - Zap Cheat\n\nThis will destroy everything in all eight directions directly\naround the player, replacing all eight squares with spaces.\nThis is a cheating function, so do not use unless fully\nnecessary; a few games punish the player for using this\nfunction. This may be limited to playtesting sessions in the\neditor, or limited to MZXRun builds, depending on config.txt\nsettings.\n\nF9 - Quicksave\nThis will save the game, like F3, but will automatically save to\nthe last filename you used. The old file will be overwritten. If\nno game has been saved since MegaZeux was started, the save will\nhave the default filename listed in the config.txt file. If\nsaving is blocked, quicksaving will be blocked as well.\n\n>#CONFGINI.HLP:1st:The Config File\n\nF10 - Quickload\nThis will load the last save that was saved with F3 or F9. If no\ngame has been saved since MegaZeux was started, the default save\nfilename will be used. The same caveats about save versions\nstill apply here. If loading is blocked, quickloading will be\nblocked as well.\n\nF11 - Counter Debug Mode\nThis will load a screen listing which counters and strings\nhave been set, as well as values for default counters and\nstrings. Counter debug mode allows considerable manipulation of\ncounters and strings, among other things. This shortcut only\nworks when playtesting in the editor. For more detail, go to its\nspecific section.\n\n>#DBGMODE.HLP:100:Debug Modes - Counter Debug Mode\n\nAlt+F11 - Robotic Debugger\nThis will launch the config menu for the built-in Robotic\ndebugger. The Robotic debugger can monitor running Robotic code\nand allow step-by-step reading and manipulation of running\nRobots as they execute. This shortcut only works when\nplaytesting in the editor. For more detail, go to its specific\nsection.\n\n>#DBGMODE.HLP:101:Debug Modes - The Robotic Debugger\n\nF12 - Take Screenshot\nPressing F12 will make a 640x350 screenshot (in PNG format by\ndefault) in the working directory, using the software renderer.\nThe file naming starts from \"screen0\". The config file may\ndisallow taking screenshots, depending on setting.\n\nArrows - Move\nThe arrow keys will move the player and allow it to interact\nwith most objects in most games.\n\nSpace - Shoot\nIf Space is held, pressing an arrow key fires a bullet from the\nplayer in the selected direction, with each shot taking one\nammo. The player will not move with the arrow keys while trying\nto fire a bullet, even if the player can't shoot or is out of\nammo. Some games may have Space shoot different shots or even\ngive Space different uses.\n\nDel - Bomb\nBy default, this will drop a bomb of the current type BENEATH\nthe player. Move off of it to activate it, then run before it\nexplodes! Uses of the del key may vary from game to game.\n\nTab / Left Arrow / Right Arrow - Select Option\nIn yes/no dialog boxes, these keys toggle between yes and no.\n\nOther games may have other controls, such as 'S' to cast a\nspell or 'I' to bring up an inventory screen, but they should be\ndetailed within the game.\n\nThe following keys are active at the title screen:\n\nF1 - Help (see above)\n\nEnter - Menu\nThis is similar to default Enter within the game. However,\nit brings up a list of key bindings instead.\n\nESC - Exit MegaZeux\nPressing ESC will load a prompt where the user can exit\nMegaZeux.\n\nF2 / Ctrl+F2 / Alt+F2 / S - Settings (see above)\n\nF3 or L - Load World\nThis will allow you to load up the title screen of any MegaZeux\nworld. A list of choices will be presented to you to select\nfrom. After the world is loaded, you may watch the title\nscreen, then press 'P' to play.\n\nF4 or R - Restore Game (see above)\n\nF5 or P - Play Game\nThis will stop the title screen and actually begin game play.\n\nF7 or U - Updater\nThis will load the updater, if it is enabled. The updater will\nconnect to an online repository and check if a new MegaZeux\nversion is available. If so, the user gets the option to update\nMegaZeux. MZX will then list the files added/changed/deleted in\nthe update, and if the user decides to update, MZX will restart\nwhen the update is finished.\nWARNING: This will most likely replace your config.txt file, so\nif you want to keep your settings, make a backup first so you\ncan apply your settings to the new file!\n\nF8 or N - New World\nThis will quit the gaming portion of MegaZeux and enter the\nintegrated World Editor, set to an empty MZX world. Alt+N will\nact the same, but skip the prompt to name and create a starting\nboard.\n\nF9 or E - Edit World\nThis will quit the gaming portion of MegaZeux and enter the\nintegrated World Editor, set to edit the currently-loaded\nworld. When no world is loaded, this acts the same as New World,\nand Alt+E with no world loaded will also skip the prompt to name\nand create a starting board. Note that \"MZXRun\" builds of\nMegaZeux do not contain the editor. For detailed info on using\nthe World Editor, view the appropriate help sections.\n\n>#THEWORLD.HLP:1st:The World Editor\n\nF10 - Quickload (see above)\n\n-= About MegaZeux =-\nSelecting this will display various information about the\ncurrently-running MegaZeux build, such as version, architecture,\nand library build versions.\n\n>#MAIN.HLP:072:Table of Contents\n#NONDESCR.HLP\n:091:\n$~9Nondescript Play Tips\n\nHi! Welcome to the default help entry for playing a MegaZeux\ngame!\n\nFirst, let me warn you that many worlds you get from archives\nor (especially) from personal webpages may not be of acceptable\nquality. They may be unfair, boring, have numerous bugs, and/or\nnot give proper instructions. On a related note, PLEASE try to\nget feedback and try acting on it before uploading your game,\neven in these days where games are fewer and farther between.\nReally old games of bad quality, on the other hand, are\ntolerated somewhat better. Don't let a few dull games ruin the\nfun.\n\nWith that out of the way, some basic play tips:\n\n~A*~F RTFM (Read the Flipping Manual). Whatever information\n the game has to give is probably important. Some authors\n include info files in their distributions or custom F1 help;\n make sure these are read. It saves everybody time and effort.\n To authors, it saves time and effort to include at least one of\n these in your submissions.\n\n~A*~F Make sure you visit every screen possible.\n\n~A*~F Save your game! It is rather simple - in most games,\n Press F3 to save your game to disk at the EXACT point you are\n (if allowed). Press F4 to reload a saved game. You can use F9\n and F10 to quick-save and quick-load, which work on the last\n game you saved. Remember to save often and keep multiple saves-\n If something ends your game, you'll want to lose as little\n progress as possible.\n\n~A*~F Touch everything! Even things of seemingly little value\n may prove worthy of your attention. If it kills you, then...\n well... hope you saved (see above).\n\n~A*~F Collect supplies! Make sure you grab every coin, gem,\n ammo dump, bomb, chest, pouch, and foobar you see! You will\n often be in want of supplies, so don't push things.\n\n~A*~F Remember how things work. Most things in MegaZeux have\n patterns, even Robots, and the same object will usually do the\n same thing all the time. (However, two objects may look alike\n and not really be the same thing.)\n\n~A*~F Don't take anything for granted. If it looks like a spike,\n it probably is. _Probably_. Although it is rare that these\n types of puzzles must be solved to complete a game, there\n are often bonuses, hidden rooms, etc. behind illusions.\n\n~A*~F Try things twice, even thrice. Sometimes objects respond\n differently at other times. If you get a new treasure, go\n talk to all the citizens - maybe one of them will say\n something new. If some place seems inaccessible, come back\n later!\n\n~A*~F If doing the same thing over and over keeps failing, try\n something else. It helps to look at new methods and to put\n distance between yourself and the problem.\n\n~A*~F Bullets can stop spitting fire, but only sometimes. If trying\n to gun down an enemy that spits fire, stagger your shots\n somewhat; holding down the spacebar often results in ALL of\n your shots being cancelled.\n\n~A*~F If all else fails, look in the game with the editor. This\n depends on the clarity of the author(s)'s code, but looking in\n the editor can help if all other solutions have been exhausted.\n Searching functions can make this easier: the world editor can\n (e.g.) show Robots with Shift+F2, and the Robotic editor can do\n searches with Ctrl+F.\n\n>#MAIN.HLP:072:Table of Contents\n#MOUSESUP.HLP\n:1st:\n$~9Mouse Support in MegaZeux\n\nUse of the mouse is very simple. Move the mouse to move the\nmouse cursor. Press the Left mouse button to select something,\nactivate something, etc. Press the Right mouse button to grab\nwhatever's under the cursor in the World Editor. Certain games\nmay make use of the mouse during gameplay. The scroll wheel will\nalways emulate up/down presses in dialog boxes and the list menu\non top of whatever functions it may have in the current game.\n\n>#MAIN.HLP:072:Table of Contents\n#BUILTINS.HLP\n:1st:\n$~9The Mirth of Built-ins\n\nThe following help section contains a list of the different\nobjects, enemies, items, and terrains you will find within\nthe different MegaZeux worlds.\nWant to make a (very) quick and (very) dirty game? Built-ins are\nthe way to do it! However, outside of a select few built-ins\n(such as certain terrains, Robots and items) heavy usage of\nthese built-ins for long-term projects is best-suited for\nbeginners and savants.\n\n$@0~8 ~FTerrains~8 @8\n\n~BSpace\n\nThis is the simplest terrain; it does absolutely nothing!\n\n~BNormal         ~A@0\n~BSolid          ~E\n~BTree           ~A\u0006\n~BLine           ~F\n~BCustom Block   ~7?\n\nThese are all basic obstacles; they just get in the way.\nSometimes trees can be burned down.\n\n~BBreakaway      ~C@0\n~BCustom Break   ~7?\n\nThese block movement as well, but they can be destroyed with\nmost weapons (such as bullets or bombs). Certain other things\ncan destroy them as well.\n\n~BFake           ~A\n~BCarpet         ~4@0\n~BFloor          ~9@0\n~BTiles          ~0@F\n~BCustom Floor   ~7?\n\nThese are all treated as flooring, or \"background\". Anything,\nincluding yourself, can move onto and over these. They are\nprimarily for decoration.\n\n~BWeb            ~7\n~BThick Web      ~7\n\nThese are another type of flooring. However, webs are often\nthe home for spiders, so watch your step!\n\n~BForest         ~2@0\n\nThis terrain will block the path of almost any built-in. You,\nhowever, can move through it with ease, clearing a pathway.\nEnemies can move along a cleared path.\n\n~BInvis. Wall\n\nThis LOOKS like just an empty space... until you bump into it.\nThen it becomes a normal wall, blocking your path.\n\n$@0~8 ~FItems~8 @8\n\n~BGem            \u0004\n~BMagic Gem      \u0004\n\nGems are a collectible. Your total number of gems is shown on\nthe status screen, and each gem gives you one point. Many older\ngames also use them as a type of currency, where you can trade\ngems for stuff like food, ammo, weapons, or hints. Gems are\nfragile, and will be destroyed when shot or bombed.\nMagic Gems count as Gems and act like Gems, but they also\nincrease health by one point apiece when taken.\n\n~BHealth         ~C\u0003\n\nThis will improve your outlook dramatically. Collecting one of\nthese increases health by a certain amount. The amount varies\nfor different hearts. Keep in mind that if you're currently at\nyour maximum health, running into a heart will still collect it.\n\n~BEnergizer      ~1\n\nAfter grabbing an energizer, the player will flash colors for a\nlimited time (specifically, for 113 cycles). During this period,\nyou are invincible against enemies, bullets, fire, lava, poison\ndamage, Robots attempting to TAKE HEALTHS, and most other forms\nof pain. Be careful not to get into a dangerous situation as its\nenergy runs out!\n\nAny energizer grabbed while already energized resets the timeout\nperiod to 113 cycles.\n\n~BAmmo           ~3 \n\nWhen you grab ammunition, it will add a certain amount of\nammo to your supplies. The small piles hold 0-9 shots, and the\nlarge piles 10-255. The amount may be different for each pile.\n\n~BBomb           ~0\u000b\n\nEach bomb you grab adds another to your supply. The sound made\nwhen you grab the bomb will be high-pitched for a high strength\nbomb and low-pitched for the rare low strength bomb. Hibombs\nwill explode for five tiles in all directions, while lobombs\nexplode for only three tiles in all directions.\n\nSome boards may not allow the player to collect bombs, and\ninstead will have bombs ignite on touch.\n\n~BKey            ~A\f\n\nCollect keys to open locks, doors, and gates later on. The key\nand the lock/etc. must match foreground colors, and a key will\nonly work once. (For example, a dark blue key will open any dark\nblue lock, regardless of the lock's background color.) You can\ncarry up to sixteen keys at once. The default status screen, if\nviewable, will show your current supply of keys.\n\nIf you've played ZZT games, you'll be interested to know that\nyou can carry multiple keys of the same color.\n\n~BLock           ~A\b\n\nA lock will only open if you have a key of the same foreground\ncolor to unlock it. The key can only be used once and will\ndisappear along with the lock.\n\n~BCoin           ~E\u0007\nCollecting coins is a good idea. Coins not only increase your\nscore by one, but can often be used to purchase valuable items\nor services from vendors. Unlike Gems, Coins can withstand being\nshot or bombed.\n\n~BLife           ~B\n\nA life orb will give you yet another chance for survival in\nMegaZeux. Keep in mind that if you're currently at your maximum\namount of lives, running into a life orb will still collect it.\n\n~BPouch          ~7\n\nA pouch is usually filled with coins, gems, or even both. The\namount varies, but often you will find yourself pleasantly\nrich...as long as you can get to it before a Bomb can destroy\nit.\n\n~BChest          ~6\n\nA chest can contain numerous things. The contents will be one\nof the following: Empty, a Key, Coins, Lives, Ammo, Health,\na Potion or Ring, Bombs, or Gems. Once you grab the contents,\nthe chest itself will remain, but be empty. Chests will not\nsurvive being bombed, empty or not.\n\n~BRing           ~Eo\n~BPotion         \n\nThese mystical items will bestow a magical effect on you when\nyou wear or drink them. The effect, however, is unknown to\nyou until you try it... and some effects aren't so nice. All\nrings and potions increase score by 5 when collected.\n:prx:\n$@0~8 ~FPotion and Ring Effects~8 @8\n\nEffects are limited to the current room only, except for\nhealing, hurting, and invinco.\n\n~BNo Effect\n\nThis effect does absolutely nothing.\n\n~BInvinco\n\nJust like an energizer, you will become invulnerable to most\nforms of pain until you stop flashing.\n\n~BBlast\n\nScatters plentiful, random explosions around the screen.\n\n~BHealth x10\n~BHealth x50\n\nGives the player 10 or 50 additional health points,\nrespectively.\n\n~BPoison\n\nReduces the player's health by 10.\n\n~BBlind\n\nTemporarily blinds the player. The entire viewport, except\nfor the player, will look like dark gray floors. You can still\nmove and interact, but you won't be able to see anything\ndistinct but the player.\n\n~BKill Enemies\n\nKills all enemies in the room, including \"Invincible\" enemies.\n\n~BLava Walker\n\nAllows the player to temporarily walk on lava and fire.\n\n~BDetonate\n\nExplodes all bombs and lit bombs in the room.\n\n~BBanish (Dragons)\n\nTurns all dragons on the board into ghosts. Each ghost made this\nway will have intelligence set to 4, movement speed set to 4,\nand the \"Invincible\" flag set to off.\n\n~BSummon (Dragons)\n\nTurns all creatures on the board - excepting guns - into\ndragons. (This includes other dragons.) Each dragon made this\nway will have a firing rate of 3, 4 hit points, and the \"Moves\"\nflag set to on.\n\n~BAvalanche\n\nScatters boulders randomly around the screen.\n\n~BFreeze Time\n\nFreezes ALL non-player on-screen objects (including all Robots\nexcept the Global) for a short time. This will also freeze any\nactive board timer.\n\n~BWind\n\nThe player retains control but will also move additional random\nsteps for a limited time. The wind will blow the player in all\ndirections.\n\n~BSlow Time\n\nSlows ALL non-player on-screen objects (including all Robots\nexcept the Global) for a short time. The resulting speed is\napproximately half normal speed. This will also slow any active\nboard timer.\n\n$@0~8 ~FCreatures~8 @8\n\n~BSnake          ~2\n\n~BRunner         ~C\u0002\n\nA snake moves in a straight line until it hits an obstruction,\nthen aims itself in another direction and continues. Hitting a\nsnake (like all enemies hereafter unless noted) will cause you\nto lose health, then kill the snake, while hitting the snake\nwith a player bullet will kill the snake and increase score by\n3. Runners act like snakes and will take up to four hits, but\nwill only go the opposite direction when obstructed.\n\n~BEye            ~F\n\nAn eye chases you down like any ordinary enemy, but when it\ncatches you or dies, it explodes! The size of the explosion\ncan vary with each eye. As eyes float, they can traverse both\ngoop and lava.\n\n~BGhost          ~7\n~BThief          ~C\u0005\n\nA ghost is the simplest enemy, simply chasing you. While some\nof them are invincible, most can be killed. Thieves chase but\ndon't do damage. They steal your gems on touch instead, and can\ndo so multiple times until killed. Ghosts can traverse lava and\ngoop; thieves cannot. Ghosts give no score when killed by a\nplayer bullet.\n\n                ~A*\n~BSlime Blob     ~A**\n                ~A*\n\nA slime blob usually doesn't hurt you, but it can quickly\nbecome incredibly annoying because of its habit of dividing\ninto more slime blobs, then hardening into \"solid slime\"\n(i.e. breakables), quickly filling rooms.... You can often\nkill slime by touching or shooting it, but some slimes can be\ninvincible, and others can be harmful to touch. Slime blobs give\nno score when shot by a player bullet.\n\n~BDragon         ~4\u0015 ~C\u000f ~E*~C\u000f\n\nSome dragons move around slowly, but their main advantage is\ntheir offense - they can shoot barrages of scorching flame.\nDragons also have a strong defense, taking up to eight hits to\nkill, and take only one hit of damage from explosions. Touching\nthem will take away health, but will NOT hurt the dragon.\nDragons can traverse lava but not goop.\n\n~BFish           ~E\n~BShark          ~7\n~BSpitting Tiger ~B  ~F   \n\nFish have to stay in the water and often can't hurt you. Those\nthat can, however, will hurt you if you're adjacent to them,\neven if you're on land. Some fish take two hits to kill. Sharks,\nhowever, swim in lava or goop, are always hostile, and can\nattempt to shoot you with fire, bullets or seekers. Spitting\nTigers act like sharks, but are restricted to land.\n\n                ~0ſ\n~BSpider         ~0~7~0\n                ~0\n\nSpiders, although eager to catch you, are usually restricted to\nmovement on webs. Certain spiders, however, can actually leave\nthe webs. Some spiders take two hits to kill.\n\n~BGoblin         ~2\u0005\n\nGoblins chase but periodically stop, making them an easy\nbuilt-in villain.\n\n~BBear           ~6\n\nBears only move if you get too close, then they lumber over for\nthe attack. Many require 2 hits to kill.\n\n~BBear Cub       ~6\n\nBear cubs are very lively, rushing all over the place. Since\nthey move so fast in a seemingly random manner, they are often\nhard to kill.\n\n~BBullet Gun     ~F  \u001b\n~BSpinning Gun   ~C\u000f~E* ~F\u001b\n\nBullet and spinning guns, both indestructible, fire any type of\nnormal projectile (bullets, fire or seekers). The bullet gun,\nhowever, is fixed while the spinning gun can fire from all\ncardinal directions.\n\n~BLazer Gun      ~0~1~9~3~B~F~7\n\nThe indestructible lazer guns fire off lazer walls at regular\nintervals, and sustain these walls for a specific length of\ntime. Unlike in ZZT, getting hit by a laser does not push the\nplayer into objects, though it will push the player if a space\nis open. The beam can be obstructed by nearly anything not a\nground type, even including a bullet.\n\n~BMissile Gun    ~0\u0010\n\nMissile guns fire missiles in one direction. Some fire just\nonce while others can fire missiles indefinitely. The gun\nitself is indestructible.\n\n$@0~8 ~FPuzzle Pieces~8 @8\n\nThe following objects are often used to create mind-twisting\npuzzles requiring you to push objects all over the place to\nreach a goal. Be warned.\n\n~BBoulder        ~7\n~BCrate          ~6\n~BCustom Push    ~7?\n\nBoulders and crates can be pushed in any direction, with any\nnumber of pushable things in a row. They can be blown up.\n\n~BBox            ~F\n~BCustom Box     ~7?\n\nBoxes can also be pushed in any direction, but cannot be blown\nup.\n\n~BPusher         ~0\u0010 ~7\n\nPushers cannot hurt you, but they constantly try to move in one\ngiven direction, pushing almost anything in their path - Boxes,\ncrates, and even you!\n\n~BSlider NS      ~E\u0012\n~BSlider EW      ~E\u001d\n\nSliders can be pushed, but ONLY in certain directions. A Slider\nNS can only be pushed north/south, while a Slider EW can only be\npushed east/west.\n\n$@0~8 ~FTransport~8 @8\n\n~BStairs         ~F\n~BCave (or door) ~0\n~BWhirlpool      ~9@1\n\nWhen you enter any one of these, you are transported to another\nlocation within the current world. These are not always\ntwo-way connections.\n\nThe specific destination is determined as follows, from highest\npriority to lowest, scanning right-to-left from the lower-right\ncorner to the upper-left:\n ~E*~F Same passage type, same exact color\n ~E*~F Same exact color\n ~E*~F Same passage type, same foreground color\n ~E*~F Same foreground color\n ~E*~F Same passage type\n ~E*~F Default player position on destination board\n\nIf the current board and destination board are the same, no\nteleport will happen.\n\n~BCW             ~A/\n~BCCW            ~A\\\n\nThese rotate in the given direction (ClockWise or\nCounterClockWise), rotating everything around them at the same\ntime. Walls and other solid objects will not be affected.\n\n                ~Fv\n~BTransport      ~F} {\n                ~F~~\n\nMost transporters face a single direction. Entering these on\nthe \"open\" side, or pushing other things into them, will\ncause a jump to another location. Some transporters rotate;\nthose can be entered from any side.\n\nThe destination of a transport is somewhat complex, but the\nsearch pattern is as follows:\n\n1. If the other side of the entered transport is empty, move\n  there. If there is something there that can be pushed out\n  of the way, move there and push it out of the way.\n2. If the other side is blocked, continue in that direction\n  looking for the first non-blocked transporter facing the\n  OPPOSITE direction, or an \"any-direction\" transport.\n3. If neither condition can be met, no transport takes place.\n\n$@0~8 ~FElements~8 @8\n\n~BStill Water    ~9@1\n~BN Water        ~9@1\u0018\n~BS Water        ~9@1\u0019\n~BE Water        ~9@1\u001a\n~BW Water        ~9@1\u001b\n\nWater is a type of floor that can be moved onto by the player,\nsome enemies, and certain other objects. Sometimes, water has a\ncurrent, moving you constantly in a certain direction.\n\n~BIce            @3\\\n\nIce is another type of floor. However, the player will slide on\nit, constantly moving in the last direction the player moved\nuntil running into a non-pushable obstacle. Attempting to move\nin another direction while still on ice will cause the player to\nslide in that direction instead.\n\n~BLava           @4~C\n\nLava is another floor. However, it is (almost always) very\ndeadly. Only a few things can traverse it, puzzle pieces cannot\nbe pushed onto it, and any player unfortunate enough to walk on\nit will typically suffer rapid and massive amounts of damage.\n\n~BFire           @0~C~E~C\n\nFire is yet another type of floor. It often will spread across\nthe board, turning things such as trees, fakes, brown objects,\nand sometimes even empty space into more fire. It can damage the\nplayer at random intervals when merely standing near it, and\ncause constant damage while standing ON it, but by default fire\ndoes far less damage than lava. Most fires will eventually burn\nout, and the objects it burns are determined by the board\nsettings.\n\n~BLit Bomb       ~0\n~BMine           ~4\n\nA lit bomb is an explosion on a fuse. You DON'T want to be\nnearby when the fuse runs out... mines, on the other hand, only\nblow up when touched, shot, or hit with an explosion.\n\n                 ~F@E\n                ~F@E~C~F\n~BExplosion      ~F@E~C@4@E~F\n                ~F@E~C~F\n                 ~F@E\n\nAn explosion starts at a certain point, quickly spreading\noutward, wreaking havoc in its wake and destroying most\ncreatures. Some explosions are smaller than others. Explosions\nwill also cause explosives such as bombs to explode, leading to\nsome very cool chain reactions.\n\n~BGoop           ~8@1\n\nGoop is pretty much non-traversable terrain to anything\nlandborne. Sharks may occasionally inhabit it, and select Robots\nmay be able to walk in it. Bullets, lazers, and other\nprojectiles will traverse it freely. Goop may also be treated\nas water or other terrains in certain worlds.\n\n$@0~8 ~FMiscellaneous~8 @8\n\n~BDoor           ~2~A~2۲\n~BGate           ~7\u0016۲\n\nA door is what you would expect - When you touch it, it will\nopen itself, pushing obstacles in its path out of the way, and\nthen close after a brief pause. Doors require two free spaces\nin the direction it moves to open fully. Certain doors are\nlocked and require you to use a matching key to open them. They\nthen remain unlocked. Gates act similarly, but do not move; they\ncan be moved over directly when open.\n\n~BRicochet Panel ~0/\n~BRicochet       ~A*\n\nWhen a bullet hits a ricochet panel, it is reflected to travel\nin a new direction, depending upon the way the panel is facing.\nWhen a bullet hits a ricochet, it is reflected to travel in the\nopposite direction. Any bullet that hits a ricochet panel or a\nricochet will have its type set to neutral when reflected.\n\n~BSpike          ~7\u0010\n~BCustom Hurt    ~7?\n\nSpikes and other painful devices will simply hurt you if you\ntouch them. These are often used in conjunction with ice or\nwith sidescroller engines.\n\n~BText           ~7?\n\nAnother type of wall. Cannot be walked on, but can provide\nhints or other enlightening messages.\n\n~BMoving Wall N  ~7?\n~BMoving Wall S  ~7?\n~BMoving Wall E  ~7?\n~BMoving Wall W  ~7?\n\nMoving walls act like Runners - they just move back and forth,\nto and fro... They can't hurt you by touch, but can get in the\nway very easily and can't be destroyed like Runners can.\n\n$@0~8 ~FObjects~8 @8\n\n~BPlayer         @1~B\u0002\n\nThis is you. Really. Well, sometimes. The player is often\nlocked, with a Robot or sprite representing the player\ncharacter, but exactly one player object is always present.\n\n~BScroll         ~F\n~BSign           ~6\n\nTouch this to display a given message. The message will be in a\nmessage box. A scroll will disappear after you finish reading\nit; signs will not.\n\nUnlike in ZZT, scrolls and signs are text-only: they cannot\nexecute any commands. Also unlike ZZT, messages only one line\nlong will still be shown in a message box.\n\n~BMissile        ~0\u0010\n\nMissiles fly around the room, turning at obstructions. They\nwill explode if they hit the player or when they cannot turn.\n\n~BBullet         ~F\n\nBullets will fly in a straight line until they hit something\n(and disappear) or do damage to someone or something. Ricochets\ncan change the direction a bullet is traveling in, as well as\nchange its type to neutral. There are three types of bullets:\nPlayer (the player shoots these by default), Neutral (Robots\nshoot these in a default shoot command), and Enemy (built-in\nenemies shoot these by default). Player bullets cannot harm the\nplayer; Enemy bullets cannot harm default enemies; Neutral\nbullets can harm both the player and default enemies.\n\n~BSeeker         ~A|\n\nA seeker, thrown by a Tiger, Shark, or other enemy, will chase\nthe player all over the screen until they collide and do damage.\nCollision with most projectiles will not destroy it, only pause\nit. They have a limited lifespan, however, and will expire after\na certain amount of time or when caught in an explosion.\nThey are still one of the deadliest weapons you will face;\nmore than only a few at once can easily ruin a game, especially\nin cramped spaces.\n\n~BShooting Fire  ~C\u000f ~E*~C\u000f\n\nShooting fire will continue in a straight line until it hits\nthe player (resulting in direct damage) or another object\n(resulting in a small fire). The fire may quickly spread out of\ncontrol in some areas, or just burn out in others. Bullets can\nsometimes destroy shooting fire, but other times the shooting\nfire simply absorbs the bullet.\n\n~BSensor         ~7?\n\nSensors are a form of floor that only the player can move onto.\nThey will usually then produce some form of effect, by\ninteracting with a Robot; other times they act as \"save points\".\nOther objects will push them around instead.\n\n>#SENSORSW.HLP:094:Sensors - What They Are and How to Use Them\n\n~BRobot          ~7?\n~BPushable Robot ~7?\n\nRobots are the workhorses, the artisans, the... nearly\neverythings of MegaZeux. They are highly flexible objects\nwhich can do almost anything. They utilize their own full-scale\nprogramming language, dubbed Robotic. If another object can\ndo something, a Robot can almost always do it better. They can\nshow messages, fight the player, play music or sound, set the\nentire gameplay structure, and do countless other things. See\nthe appropriate help sections for more information.\n\n>#ROBOTSWH.HLP:1st:Robots - What They Are and How to Use Them\n>#MAIN.HLP:072:Table of Contents\n#CONFGINI.HLP\n:1st:\n$~9The Config File\n\nOne of the first things a new MegaZeux user should do is edit\nthe options in the config file; even experienced MZXers may find\nsome of the customization options novel and useful. Go open\nconfig.txt in any text editor. There is ample commentary to let\nthe user know how to change the options. Make sure to remove\nthe # sign from any option you want set!\n\nEditing the options can result in better sound, better / more\ncustomizable graphics, more fitting defaults, a more\npersonalized Robotic editor, joystick support, extended macros,\nautomated backup and much more.\n\nNew versions - even minor version changes - can add new options\nto the config file or rename old ones, so it is helpful to keep\nthis file current.\n\n>#MAIN.HLP:072:Table of Contents\n#IFYOUFIN.HLP\n:1st:\n$~9If You Find a Bug...\n\n...we want to know! We're very interested in any problems or\nbugs you find in MegaZeux. We also welcome any comments,\ncriticism, or suggestions. We especially appreciate older\nMegaZeux worlds. Here's the contact information as of this\nwriting:\n\nCheck the reports on DigitalMZX's Bug Tracker\n(https://www.digitalmzx.com/forums/index.php?app=tracker) to see\nif your problem has already been addressed. Active problems and\nbugs that are only fixed in a development version of MegaZeux\nare listed, as of this writing, in the \"MegaZeux\" category,\nwhile the bugs that are fixed in a stable version are in the\n\"Closed MegaZeux Bugs\" category. Feature Requests are handled\nsimilarly.\n\nTo make things sane for all parties, do this before submitting:\n  1) Make sure you have the most current version of MegaZeux.\n  2) Make sure that the Bug Tracker or development version\n     doesn't already address your issue.\n  3) Try to be specific in explaining what happens. The more\n     precise, the better.\n  4) List what platform(s) you know show this problem.\n  5) Try to pinpoint the problem to a specific Robotic line or\n     MZX function; at the very least, try to narrow it down.\n     Pinpointing which version introduced the bug is very\n     helpful, if possible.\n  6) If it is a crash, try to run a debug build through GDB and\n     post a backtrace of the crash. Instructions for running a\n     debug build through GDB are on the MZXWiki at DigitalMZX,\n     in the New MegaZeux Release FAQ.\n  7) If you're unsure if the bug's been addressed, try to build\n     a version from GitHub's GIT repository and see if the bug\n     is still present in the test build. The MZXWiki has\n     instructions for doing so in the article Compiling\n     MegaZeux.\n  8) Optimally, if the problem is a regression caused by the\n     port, upload a world made in 2.70 that works in 2.70 but\n     does not in the current version to best isolate the\n     problem.\n\nAdditionally, contacting the developers in real-time can help\nilluminate the problem.\n\n>#MAIN.HLP:072:Table of Contents\n#FAQ.HLP\n:1st:\n$~9Frequently Asked Questions\n\nThe following is a list of questions that have been received\nabout MegaZeux innumerable times in some form or another.\n\nQ: I read that newer versions of MegaZeux break old games. Which\n  version should I get to play games?\n\nA: The most recent version. MegaZeux development places enormous\n  importance on backwards compatibility, and contrary to what\n  some others have stated, there are very few cases where an\n  older version is needed to play a specific game.\n\nQ: I hate that MegaZeux is always windowed! Could you change\n  this?\n\nA: There's already a way to switch between fullscreen and\n  windowed modes - press Ctrl+Alt+ENTER. Also, the config file\n  has options that start MZX in fullscreen mode (either true\n  fullscreen or as a borderless window at desktop resolution) at\n  launch.\n\nQ: How can I get option (foobar) in the config file to work?\n\nA: Make sure the pound / hash sign in front of the command is\n  deleted. Otherwise, the option is treated as a comment and\n  ignored.\n\nQ: Are there any MZX-specific tools that can make developing and\n  packaging an MZX game easier?\n\nA: Yes, plenty! MegaZeux comes with two helpful tools:\n  \"Checkres\" will detect what files are referenced by an MZX\n  world and which are present, and \"ccv\" can take an image file\n  and output a conversion of it in .chr/.mzm form for MZX use.\n  There are also several graphical conversion programs such as\n  CharCon. MZX has natural time-savers, of course. Automated\n  backups, macros, Robotic code import/export, expressions,\n  repeated block copying, sprites and other features can help\n  speed up development time.\n\nQ: This MZX game has some neat things in it! Can I use some of\n  these things in my own game?\n\nA: It's usually okay to do this, as long as you give definite\n  credit to the original creator(s). Unless it was made\n  specifically for the game, music and sound from other MZX\n  games generally doesn't need direct attribution, but re-used\n  artwork and code definitely should get attribution. If you're\n  unsure whether the original creator(s) would like you using\n  their work in your own game, ask! What we don't want to see,\n  though, is passing the work of others - especially graphics,\n  writing, and code - as your own. DON'T PLAGIARIZE. JUST\n  BECAUSE THEY DID NOT EXPLICITLY FORBID YOU FROM USING THEIR\n  WORK DOES NOT MEAN YOU CAN PASS IT OFF AS YOUR OWN.\n\nQ: Can I distribute a world I made with MegaZeux? Is it legal?\n  Must I/can I include MegaZeux with it? Can they be shareware\n  worlds?\n\nA: Of course you can distribute your worlds! That is what\n  MegaZeux is for! However, while there are those who would help\n  point out your game's flaws in a forgiving manner, not\n  everyone is capable of constructive criticism. Playtest your\n  game before uploading it, with at least one other person\n  evaluating the game for bugs, play balance, and\n  text/plot/grammatical errors (when relevant). Get creative and\n  make your game worth playing!\n\n  Please include any WAV, module, OGG, CHR, MZM, PAL and all\n  other files with the games, as well as a cohesive and helpful\n  description file or manual. (Using checkres to guarantee that\n  every relevant file is included can be helpful.)\n\n  While you can simply distribute your game with a copy of\n  MegaZeux included, or even just post the game's files in an\n  archive by itself, consider releasing your game as both a\n  standalone archive and as pure game files in separate\n  downloads. MegaZeux games can also be embedded in a webpage\n  for accessibility and player ease.\n\n  Typically, MegaZeux worlds are freeware, but if you've made an\n  assuredly epic and ground-breaking game, then you can attempt\n  to ask for registration money in a shareware demo version.\n  Just don't get your hopes up. Alternatively, consider\n  uploading your game to a platform that allows optional\n  donations, such as itch.io.\n\nQ: I WANT FUNCTION (FOOBAR) ADDED OR I LEAVE THE COMMUNITY\n  FOREVER :<\n\nA: Before you go bugging the maintainer(s) of MegaZeux to add\n  new functions, consider these things:\n  1) Some things require massive work to implement. These\n     things require changing the world, save or board format,\n     and format changes are explicitly reserved for version\n     number changes.\n  2) Some things have been purposely ignored because the\n     maintainer(s) deems these things not worth including. In\n     this case, it's probably best to pick up the source and\n     figure out how to add this function yourself.\n  3) You may not be the first to ask for any certain feature.\n     Current feature requests are listed in DMZX's Bug Tracker.\n     If you are not the first, then you'll often find out why a\n     feature is not implemented or, better yet, you'll end up\n     finding out that it is going to be implemented after all!\n     Either way, you learn more about the feature and what\n     would need to be done to include it.\n  4) It's technically possible to add new commands, but\n     currently the method of storing Robotic severely limits\n     the amount of new commands. Until this system is retooled,\n     there will be no new commands. Features that are most\n     cleanly implemented through a new command may possibly be\n     implemented in other ways, however.\n\nQ: Is adding network capabilities to MegaZeux planned?\n\nA: Short answer: Possibly. This has been discussed at-length;\n  the biggest problems with adding these to GAMES are thorny\n  implementation issues (how the MZX programmer would insert\n  network capabilities into a MegaZeux world, as well as the\n  structure in general). It's definitely possible, and possible\n  to do soundly, but developers don't have netplay as a high\n  priority. As for other networking capabilities and MZX, this\n  has already been done to some extent (starting with 2.82b,\n  MZX ships with an updater program). Implementing tools and\n  aids for MZX relying on networking is a lot less thorny of an\n  issue, and might be considered.\n\nQ: Will MegaZeux games ever be playable without MegaZeux\n  itself?\n\nA: In a sense, they can be now. MZXRun, the editor-less version\n  of MegaZeux, can now be set to run in \"standalone mode\", a\n  configuration designed for only playing a specific world. This\n  mode changes several MZX mannerisms to better accommodate a\n  standalone version of a game. The title screen can also be\n  completely skipped. Additions to Robotic (such as exiting the\n  game through code or blocking the default escape menu) have\n  also facilitated standalone releases. Read the comments for\n  standalone_mode in config.txt for more information, if\n  interested.\n\n  MegaZeux has also been ported to Emscripten, which means that\n  MZX games can now be embedded into web pages, allowing play\n  without any extra program download or web plugin necessary.\n\nQ: I need more than 256 characters! Could you help with this?\n\nA: Then you'll need to carefully utilize the Robot command\n  LOAD CHAR SET and ration your characters carefully. The\n  256-char limit is set within the world format and will\n  currently be left alone. Also, you might not REALLY\n  need 256 characters at once. If you're wanting more\n  characters because of heavy character use in animation,\n  partial character set loading will probably be the best\n  solution. Otherwise, find out which characters will never be\n  shown when other characters are and replace them with\n  characters you actually will use. (Alphanumeric characters,\n  for instance, may see limited use in certain games, or are\n  used heavily in some sections and sparsely in others.)\n\n  Alternatively, make use of unbound sprites. Using unbound\n  sprites (and ONLY unbound sprites) allows display of 15 full\n  character sets at once instead of just one, on top of their\n  other benefits.\n\n>#PARTIAL.HLP:par:Partial Character Sets\n>#SPRITES.HLP:ubs:Unbound Sprites\n\nQ: How can I load sounds at different volumes in my game?\n\nA: Unfortunately, MegaZeux currently has no way of doing this\n  via Robotic. The best you can do for now is load versions of\n  your sound that are louder/quieter. Volume settings for music,\n  on the other hand, can be manipulated freely.\n\nQ: Why is my MegaZeux world starting on the title board?\n\nA: The first board has to be set. Press 'G' in the editor, then\n  set the board using the \"First board\" field at the top left.\n  As of version 2.91, MZX will default to creating and naming a\n  starting board when making a new world, greatly lowering the\n  chance of mistakenly starting on the title.\n\nQ: Whenever I place a string on the overlay, the spaces aren't\n  acting like overlay at all! Is this a bug?\n\nA: No, it's not. Char 32 (the natural space) is never part of\n  the overlay. You have to reserve an extra character or use\n  the solid character for spaces, or otherwise convert spaces\n  to useable replacements during run-time.\n  If this issue is arising from using the overlay to display\n  messages, consider using multi-line * messages instead.\n\n>#COMMANDR.HLP:__3:* \"string\"\n\nQ: How can I input a number over 32767 or under -32768 in\n  Robotic?\n\nA: You must use an expression. Encase the number in\n  parentheses, as shown here:\n  ~Eset \"largesse\" to \"(2000000000)\"\n\nQ: My Robot can't change its/the player's characters! I\n  use CHAR \"A\" but it turns invisible! What am I doing wrong?\n\nA: Use CHAR 'A' instead. You MUST use single quotes\n  (apostrophes) or it will use the value of the COUNTER\n  A, which is probably 0.\n\nQ: Can I use the music from Caverns in my games? How about\n  the music from the registered games? How about the SAM\n  files?\n\nA: It'd be preferred if you did not use Caverns music in any\n  games you distribute, because Caverns music was ancient aeons\n  ago. The music from the registered games was illegal to\n  distribute; it's now under the GPL, but should be avoided for\n  the same reasons Caverns music should. The SAM files are\n  public domain and may be used as you please.\n\nQ: Where can I get module files?\n\nA: If you can't make them, search webpages for good ones.\n  https://www.modarchive.com has a very large tracked music\n  library, as do many old CD-ROM shareware collections. (It is\n  best practice to stick to modules that openly permit free\n  usage.) Another popular option is to convince a friend to do\n  it for you. If your game shows exceptional promise, then an\n  established musician in the community might make music for\n  you. In any case, remember to give credit where it is due\n  (yes, even to yourself).\n  Be careful when inserting OGG files into your game. OGG files\n  often present considerable bloat to a game if used as music.\n  Also, not only may you be chided for putting in a popular song\n  if you do so, but you could be violating copyright law.\n  Chances are that if you can make a coherent argument on \"fair\n  use\" of copyrighted songs, you will be far less likely to be\n  in this position to begin with. ~C\u0003\n\nQ: What is some good software to create module files?\n\nA: It's a matter of personal preference. Many people prefer\n  OpenMPT because it works well with Windows. Others prefer to\n  work with Schism Tracker. Finally, a few are still fond of\n  Fast Tracker II, and therefore use the similar MilkyTracker.\n  DOS-based trackers like IT and FT2 understandably need outside\n  tools such as DOSBox to run as expected.\n\n  If you don't mind pumping up your game's filesize, you could\n  use any typical composition software (like FL Studio) and\n  convert your wave to ogg format.\n\n  A third alternative would be Reality Tracker. This tracker\n  creates music based on the once-common OPL3 chip found in\n  typical shareware-era DOS sound cards. MegaZeux now directly\n  supports files made in this tracker.\n\nQ: How do I make WAV files?\n\nA: There are three easy ways to create WAV files. First, you\n  get recording software and a mic. Set it all up (read the\n  instructions if necessary) and go for it. Second, you can\n  download them or get them from outside sources (such as The\n  Freesound Project). Third, you can take existing files and\n  change them with effects like echo. Converting .wav files\n  intended for sound effects into OGG format is a worthwhile\n  idea; the drop in file size compared to the drop in quality\n  is huge.\n\nQ: Why do I get garbage when I try reading a file?\n\nA: You likely have it open for writing. To close a file, use\n  the command SET \"\" to \"FWRITE_OPEN\".\n\nQ: Why does MegaZeux slow down significantly when I set the\n  \"commands\" counter to a high value?\n\nA: You're likely using an idle loop without a CYCLE 1 or WAIT 1\n  command inserted. Find the offending loop and add one; if\n  you're wary about possible delay effects, rest assured that\n  adding either of these will not unnecessarily add delays. It\n  will simply end a cycle. By default, MZX will warn you if you\n  exceed 2000000 commands in a cycle (but only in test\n  sessions). For further information, look up the Cycles and\n  Board Scans section.\n\n>#PROCESS.HLP:prc:Cycles and Board Scans - How MZX Processes Robots\n\nQ: I don't like that anyone can open up my game and see how it\n  works, or that anyone can use the editor functions to cheat\n  at my game. Is there an easy way to prevent people from\n  opening my game in the editor?\n\nA: Nope. MegaZeux used to be able to protect worlds from being\n  edited or even played without a password, but this feature\n  has long since been removed.\n  Releasing games in standalone form can possibly help. While\n  anyone who has even cursory knowledge of MegaZeux can still\n  fully look through a standalone game, people who have no\n  knowledge of MegaZeux beforehand will lack the immediate means\n  to heavily manipulate or look through your game.\n\nQ: I've seen that \"certain renderers\" are unable to display\n  unbound sprites. What are renderers, what does this mean, and\n  what should I be worried about?\n\nA: MegaZeux has several different video settings to accommodate\n  a wide range of hardware. The engines used to provide video\n  are called renderers, and can be set in the config file or\n  changed on the fly in the F2 settings menu. Renderers range\n  from basic support (software, does not utilize video hardware\n  functions) to features-oriented (glsl, featuring customizable\n  filters).\n\n  Certain platforms and renderers cannot show extended graphics\n  (in other words, unbound sprites and SMZX mode 3 custom\n  indices). Right now, this is only a concern for very weak\n  platforms that cannot handle much to begin with, such as the\n  DS. Pretty much anyone running MegaZeux on an even remotely\n  modern computer should be able to view extended graphics.\n\n  (Besides the Nintendo DS, the other platforms and renderers\n  currently unable are the GP2X and any use of the weak and\n  obseleted \"overlay2\" renderer.)\n\nQ: I heard only certain graphics cards can properly show Super\n  MZX mode games. Is this true?\n\nA: Not anymore. Any computer that can display normal games in\n  MegaZeux can now display Super MZX mode games. Even the modern\n  DOS version supports them (via an SVGA mode).\n\nQ: How can I get \"mod *\" as the default board mod?\n\nA: To set \"mod *\", use either Shift+8 or the asterisk (*) on the\n  numpad.\n\nQ: What's with these \"~~roboclp.tmp\" files packed with so many\n  games?\n\nA: Those held clipboard information in DOS versions of MegaZeux.\n  They are useless and can be freely deleted.\n\nQ: Why doesn't [ work for taking screenshots?\n\nA: It has been changed to F12 and now works in any area in MZX,\n  including any part of the editor. Instead of PCX, it now\n  currently outputs PNG files by default, but will output MS\n  BMP format files on platforms unable to support PNG.\n\n>#MAIN.HLP:072:Table of Contents\n#MEGAZEUX.HLP\n:1st:\n$~9MegaZeux Limitations\n\nMegaZeux has to impose a number of limitations to ensure\nworldfile compatibility with MZX 2.x worldfiles.\n\n$Memory Limitations\n\nNo single Robot can exceed 2 megabytes in size.\nNo single sign or Scroll can exceed 64 kilobytes in size.\n\n$Quantity Limitations\n\nItem:               Largest number allowed:\n\nRobots              255 per board plus Global Robot\nScrolls/Signs       255 per board\nSensors             255 per board\nSprites             256 per world\nBoards              250 per world\nLocal Counters      32 per Robot plus specialized local counters\n                    (e.g. loopcount, lava_walk, bullettype)\n\n$Length Limitations\n\nRobot Name Length   14 characters\nBoard Name Length   24 characters\nString Length       4,194,304 characters (4 megabytes)\nEditor Line Length  241 characters (INCLUDING \"extra\" words)\n* Message Length    512 characters\nInput String Length 512 characters\nMacro Length        64 characters (for single-line macros)\nMod Filename Length 512 characters (including subdirectories,\n                    if applicable)\n\n$Theoretical Limitations\n\nBoard size maximum is roughly 16.7 million characters\n((2^24) - 1).\nVlayer size maximum is roughly 16.7 million characters\n((2^24) - 1).\nBoard width or height maximum is 32767.\nThe maximum number of counters and strings depends on the\nplatform (e.g. between 32 or 64-bit) but is sufficiently large.\nYou should be fine utilizing hundreds of thousands, or even\nmillions.\n\nThese limitations are mostly theoretical because of the utter\nimprobability of meeting these limits and the amount of RAM (up\nto hundreds of gigabytes if fully utilized) they consume.\n\n$Other Limitations\n\nNumbers less than -32768 or more than 32767 cannot be directly\nused in Robotic. This problem is a worldfile issue and can be\ncircumvented by using a constant expression (parentheses).\n\nNo board can have a width of a multiple of 256 chars because\nthis corrupts the board if it lacks an overlay. This problem is\na worldfile issue and widths set to a multiple of 256 will be\nincreased by one more horizontal character regardless of the\nstatus of the board's overlay.\n\n>#EXPRESS.HLP:exp:Expressions\n>#MAIN.HLP:072:Table of Contents\n#THEWORLD.HLP\n:1st:\n$~9The World Editor\n\nReady to start creating your own worlds? Then, let's get\nstarted! This section is a short editor tutorial. It will\nteach you the basics of creating your own worlds.\n\nTo get into the editor, press N or F8 from the title screen. You\nwill be asked to name a starting board: one can type an\nappropriate name and create a second starting board, or press\nEscape to cancel and remain on your new world's title board.\nWhen this prompt is dealt with, you will be presented with a\nblank board with a small status bar at the bottom. You can use\nAlt+H to pop up a listing of key shortcuts at the bottom. PageUp\nand PageDown change its currently shown key shortcuts. The mouse\nalso works to change pages (but not to select actions). Feel\nfree to play around with these various options. Press Alt+R to\nrestart and clear everything (you will be asked for\nconfirmation, to make sure you really want to go through with\nit).\n\n(On Mac platforms, any key shortcut containing Alt can work with\n replacing Alt. So, for above, +H would pop up the help\nlisting, and +R would restart the world.)\n\nFor your first world, you should start simple. The very first\nboard in the board listings is the title board, so name that\nboard after your game. (If you opted to create a starting board,\nyou have to switch to the first board with the B key.) Press I\nfor board info. This screen will display a bunch of options;\nthere are loads of important options, but the most relevant\noption for now is the \"Board name\" option. Type in the name of\nyour game and then click on \"OK\" or press Tab until OK is\nhighlighted and press Enter. Now that the title board is\nnamed, its design begins... or is put aside for later (or even\nthe end).\n\nIf you made a starting board upon creating your world, simply\npress B to move to it. If you skipped making a starting board,\nyou may need to create the first playable board, or location, of\nyour game. Press A to add a board, then type in a short\ndescription of the board, such as \"Starting Board\". Press Enter\nto go to this new board. This board also needs to be set as the\nstarting board. Press G to load the Global Settings screen. Once\nthere, select the First Board option and set it to your starting\nboard.\n\nNow you are free to doodle around. Use F3 through F9 to bring up\nmenus of items, terrains, and creatures. Selecting one with the\narrows and Enter will put that thing into the editor buffer,\nmaking it your current object, and place it on the board. This\nthing and its traits are shown in the upper-right section of the\neditor menu. With the thing in the buffer, you can now place it\nusing arrow keys and space. The thing's color in the buffer can\nbe changed by pressing C and selecting a color.\n\nFor example, press F3 for terrains, and select Line. (This is\na form of wall.) Now move around, placing walls. To ease\nthis, you can press Tab to toggle draw mode. When draw mode is\nactive, every move of the cursor will place a copy of the\ncurrent thing in the buffer onto the board.\n\nTry to create a pleasing-looking screen, regardless of its\nplanned function. Some items will require that you set settings\nto determine their behavior. To place the player's starting\nposition, move the cursor to the destination, press F10, and\nselect Player. Alternatively, press Enter while highlighting\nthe Player and press Space on the desired destination.\n\nWhen you are done, press G to go to the Global Info screen.\nTAB to the Next button and press Enter. You will now be\nhighlighting the option \"First Board\". Press Enter, and select\nthe starting board (this is where gameplay starts... NOT the\ntitle board in the vast majority of cases) from the list and\npress Enter. Then TAB to OK and press Enter.\n\nYou could now press Alt+N to select a module (music) file for\nthe board, if you wish. Then press S to save the world, and\ntype in a filename. (The extension of .MZX will automatically\nbe added.) Press Enter to save the world. Press ESC to exit the\neditor, and now you can play your game! You can use L to reload\nyour world in the editor to make changes, if necessary. See\nGeneral Editing Tips for more advanced editing info.\n\nNew in the 2.8+ line of MegaZeux is the protection of colors\nand characters the editor uses. The editor uses sets outside of\nthe currently used sets to keep the editor GUI consistent. The\ncharacter set for the editor is mzx_edit.chr; only edit if you\nwant to change the editor's appearance.\n\n>#GENERALE.HLP:1st:General Editing Tips\n>#MAIN.HLP:072:Table of Contents\n#GENERALE.HLP\n:1st:\n$~9General Editing Tips\n\nThe following is a list of important editing tips. They assume\nyou are familiar with MegaZeux's dialog box system, and that\nyou can navigate the editor's menus.\n\n$Linking Boards\n\nSimple, one-board games can get boring REALLY fast. There are\nfive ways to move between boards.\n\nA simple method of connecting boards is with the Board Exits\ndialog, accessed by pressing X. Here, you can select boards that\nyou will reach if the player moves off of the screen in a given\ndirection. The destination board shouldn't have anything in the\nway, and will not automatically lead back - you must set the\nexit on that board too. This menu will not allow linking boards\nto the title board.\nThe player will appear on the other board at the appropriate\nedge at the same position (if board sizes allow). For example,\nif the player triggers the north exit of a board while at its\nupper-left corner, it will appear in the lower-left corner of\nthe destination board.\n\nIf the player triggers a board exit and anything on the\ndestination board is in the way of the player moving to the next\nboard, the player replaces it upon entering the board\n(effectively destroying it).\n\nAnother way is to use a Robot to teleport a player with the\n~Ateleport player \"boardname\" X Y~F command. This assumes that you\nare familiar with using Robots, but is the only way to go to\nanother board without having the player moved past a board edge,\nmoving the player into a teleport, requiring the player to have\nbeen on the destination board earlier, or reloading the current\nworld. Robots can also dynamically set board exits using the\n~ABOARD [dir] \"boardname\"~F command, including disabling exits\nby inputting NONE instead of the board name.\n\n>#COMMAND2.HLP:_t5:TELEPORT PLAYER \"string\" # #\n>#COMMAND2.HLP:_b7:BOARD [dir] \"string\"\n>#COMMAND2.HLP:_b8:BOARD [dir] NONE\n\nThe third way is to add stairs, caves and whirlpools using the\nTransport (F7) menu. After one is picked, you select a\ndestination board. The destination board should contain a\ntransport of the same type and color leading back. The two\nentrances will now lead to each other.\n\nThe fourth way is by saving and restoring player positions using\nRobotic. The ~Asave player position #~F command will remember the\ncurrent board and player position and save this info in the\ngiven slot #, and the ~Arestore player position #~F command returns\nthe player to the saved position.\n\nThe last way - the most kludge-like - is to load the current\nworld with a 'swap world' command. This will send the player to\nthe starting board of the current world, but will restore the\nworld to its original state. Set counters and strings will be\npreserved. This method is used for its powerful restorative\nproperties.\n\nTo switch to other boards in the editor, use B. To add boards,\npress A, or press B and select (add board) from the board list.\n\n$Board Sizes\n:sizepos:\nYou can change the size and placement of a board's viewport with\nAlt+P. You can also choose to center the viewport; this will\nautomatically set the viewport placement to display the center\nof the screen.\n\nYou can also change the actual size of the board. The highest\npossible size is 16.7 million tiles, though a board that size\nwould require unreasonably high RAM requirements (128MB). Note\nthat reducing the size of a board will permanently destroy\nanything outside of the new limits.\n\n$Other Important Editing Keys\n\nYou can use Ins to \"grab\" the object beneath the cursor, or\nEnter to edit it and then grab it as well. Use P to modify the\nsettings of the object in the buffer. Use Alt+N to select music\nfor the current board, or turn the music off if it is already\nselected. Use Alt+Z to clear the current board entirely (you\nwill be asked for confirmation). You can edit important Board\nOptions with I, and important Global (world) Options with G.\nThis is only a small selection of key shortcuts in the editor;\nthe full reference is linked at the end of this article.\n\n$The Mouse in the Editor\nThe left mouse button acts like space: it places a copy of the\ncurrent item under the mouse cursor. The right mouse button acts\nlike insert: it grabs whatever is under the mouse cursor and\nplaces it into the buffer. Moving the scrollwheel or clicking\nmiddle mouse changes the editor cursor location to under the\nmouse cursor.\n\n>#EDITINGK.HLP:080:Editing Keys and Options Reference\n>#MAIN.HLP:072:Table of Contents\n#EDITINGK.HLP\n:080:\n$~9Editing Keys and Options Reference\n\nThe following is an alphabetical listing of keys within the\nWorld editor. This is followed by a detailed description of\nwhat each one does.\n\nOn Mac platforms, any command that uses the Alt key can take \n(the command key) instead.\n\n>_A:A                    - Add (board)\n>_B:B                    - Select Board\n>_C:C                    - Color\n>_D:D                    - Delete (board)\n>_F:F                    - Fill\n>_G:G                    - Global Info\n>_I:I                    - Info (board)\n>_L:L                    - Load\n>_M:M                    - Move Board\n>_P:P                    - Parameter\n>076:S                    - Save\n>_V:V                    - View\n>083:X                    - Exits\n>min:-                    - Goto Previous Board\n>plu:+                    - Goto Next Board\n>075:Alt+A                - Select Char Set\n>073:Alt+B                - Block\n>AltC:Alt+C                - Char Edit\n>AltD:Alt+D                - Default Colors\n>AltE:Alt+E                - Palette\n>097:Alt+F                - Sound Effects\n>AltG:Alt+G                - Edit Global Robot\n>AltH:Alt+H                - Hotkey Toggle\n>078:Alt+I                - Import\n>AltL:Alt+L                - Test WAV\n>AltM:Alt+M                - Modify\n>AltN:Alt+N                - Music\n>AltO:Alt+O                - Edit Overlay\n>084:Alt+P                - Size/Pos\n>AltR:Alt+R                - Restart\n>082:Alt+S                - Status Info\n>AltS2:Alt+S                - Show Level\n>AltT:Alt+T                - Test\n>077:Alt+X                - Export\n>AltY:Alt+Y                - Debug Window\n>AltZ:Alt+Z                - Clear (Board)\n>AltNu:Alt+Number           - Load Editor Position\n>CtrG:Ctrl+G               - Goto Position\n>CtrN:Ctrl+N               - Test Music\n>CtrY:Ctrl+Y               - Redo\n>CtrZ:Ctrl+Z               - Undo\n>CtrNu:Ctrl+Number          - Save Editor Position\n>Sft8:Shift+8 OR Numpad *  - Mod Wildcard\n>SftAr:Shift+Arrow          - Goto Linked Board\n\n>F1:F1  - Help\n>F2:F2  - Text\n>F3:F3  - Terrain\n>F4:F4  - Item\n>F5:F5  - Creature\n>F6:F6  - Puzzle\n>F7:F7  - Transport\n>F8:F8  - Element\n>F9:F9  - Misc. (thing)\n>F10:F10 - Objects\n>F11:F11 - Select Screen Mode\n>F12:F12 - Take Screenshot\n\n>AF11:Alt+F11 - Robotic Debugger\n>ShF1:Shift+F1 - Show InvisWalls\n>ShF2:Shift+F2 - Show Robots\n>ShF3:Shift+F3 - Show Fakes\n>ShF4:Shift+F4 - Show Spaces\n\n>Ar:Arrow     - Move\n>AltAr:Alt+Arrow - Move 10\n>BkSp:BackSpace - Delete (Main Layer Only)\n>Del:Delete    - Delete\n>End:End       - L/R Corner\n>En:Enter     - Modify+Grab [Board Mode]\n>Enter2:Enter     - Character [Overlay Mode]\n>ESC:Escape    - Exit/Cancel Mode\n>Home:Home      - U/L Corner\n>Ins:Insert    - Grab\n>Sp:Spacebar  - Place\n>Tab:Tab       - Draw\n>PgDn:PageDown  - Next Menu\n>PgUp:PageUp    - Previous Menu\n\n:_A:~EA - Add (Board)\n\nPress A to add another board to the current world. You will be\nasked for the name of the new board, and then a new board will\nbe created. The settings for the new board will be the defaults.\nAfter adding a board, you will be moved to the new board. There\nis a limit of 250 unique boards per world.\n\n:075:~EAlt+A - Select Char Set\n\nPress Alt+A and select one of the four different character\nsets. This will change the current character set to one of the\nfour defaults. ASCII is the default EGA ASCII character set\n(code page 437). MegaZeux default is the default MegaZeux\ncharacter set. Blank is the MegaZeux default, but with most\ngraphical characters blank instead. Text, lines, arrows, blocks,\nand certain other symbols are not affected. SMZX set is the\ndefault character set for SMZX modes.\n\n:_B:~EB - Select Board\n\nPress B to change the current board by selecting from a list.\nSelecting (add board) will prompt the user to name the new\nboard; once named, the board is created and becomes the current\nboard.\n\n:073:~EAlt+B - Block\n\nPress Alt+B to start block mode, then move the cursor to the\nopposite corner of a rectangular block and press Alt+B again.\nYou can then select an action to perform upon the block. In all\ncopy/move operations, the cursor marks the new UPPER LEFT\ncorner.\n\nCopy block will allow you to duplicate the block by moving the\ncursor to a destination and pressing Enter.\nCopy block (repeated) will allow same as Copy block, but will\nallow copying the same block to multiple places.\nMove block will allow you to move the block to a new location.\nClear block will erase the contents of the block.\nFlip block will flip the block upside down.\nMirror block will flip the block left to right.\nPaint block will change the entire block to the current color.\nCopy to overlay will copy the block to the given spot of the\noverlay.\nCopy to vlayer will copy the block to the given spot of the\nvlayer.\nSave as ANSi/TXT will save the block as an ANSi or TXT file.\nSave as MZM will save the block as a board-type MZM file.\n\nDestinations for Move and Copy can overlap the original block\nsafely.\n\nYou can block copy to other boards by going to the desired board\nwhen prompted to pick the destination of the block; just press B\nand pick the desired board.\n\nAlso, one can move the cursor across the width or height of the\nblock by using Ctrl+Dir. This is very helpful for tiling blocks\nusing the repeated copy function.\n\n:_C:~EC - Color\n\nPress C to select a new current color from a menu. The thing\nunder the cursor is not affected, just the thing in the buffer.\nOne can jump to a color by typing its hex code in the color\nmenu; for example, typing \"0D\" would jump to color 013\n(background color 0, foreground color D).\n\nA quirk to keep in mind: any entity with a background color of 0\nwill display the background color of anything beneath it.\n\n:AltC:~EAlt+C - Char Edit\n\nPress Alt+C to edit the character set. The character editor is\na separate section of the world editor and is discussed in\nanother section.\n\n>#CHAREDIT.HLP:079:The Character Editor\n\n:_D:~ED - Delete (board)\n\nPress D to select a board from a list to be deleted. You cannot\ndelete the first board (the title board). A deleted board will\nbe pruned out, with the next actual board taking its place. A\ndeleted board cannot be recovered or undone, so make sure this\nis the desired action.\n\n:AltD:~EAlt+D - Default Colors\n\nPress Alt+D to toggle forcing default colors for placing\nbuilt-ins (defaults to on). Turning this off causes built-ins to\nbe placed with the colors of the object held in the buffer. The\nbuffer object listed in the status bar will have a red dot at\nthe end when default colors are off. Only some types of\nbuilt-ins, such as creatures and items, have default colors.\n\n:AltE:~EAlt+E - Palette\n\nPress Alt+E to edit the palette (colors). The palette editor\nis a separate section of the world editor and is discussed in\nanother section.\n\n>#PALEEDIT.HLP:093:The Palette Editor\n\n:_F:~EF - Fill\n\nPress F to fill in an enclosed area with the thing in the\nbuffer. The area must be completely surrounded by things other\nthan the thing beneath the cursor. For example, you can fill\nover a solid square of Fakes with something else. The current\nfill command may not work accurately for very large and complex\nareas - in this case, you must move to the unfilled areas and\npress F to continue filling. This happens very rarely,\nhowever. Also, beware of trying to fill an area with an item\nwith limited placements, such as a Robot.\n\n:097:~EAlt+F - Sound Effects\n\nPress Alt+F to enter the sound effects editor. This editor\naffects all triggered sounds resulting from built-in actions,\nsuch as getting hit and shooting. First you must decide whether\nto use the default set of sound effects, or whether to edit\nyour own. You can't edit the default set. If you edit your own,\nyou will be in a series of editing screens. Use the Next and\nPrevious buttons to move between screens. Sound effects, and the\nformat used to represent them, are described in detail in\nanother section. The format is the same as used for the Robotic\ncommand PLAY. (Like with PLAY, digitized sounds are allowed\nhere.)\n\nPress F3 in the sound effects editor to rename the current sound\nslot. Slot names are limited to 9 characters in length.\n\nNOTE: Although this editor currently allows editing only the\nfirst 100 sound effects, a world can have as many as 256. Worlds\ncan also have effects up to 255 characters long. Use the CHANGE\nSFX # \"string\" command to edit higher-numbered/longer custom\nsound effects.\n\n>#SOUNDEFX.HLP:1st:MegaZeux's Sound System\n\n:AltG:~EAlt+G - Edit Global Robot\n\nAlt+G will start editing the Global Robot, starting at its name\nfield.\n\n:CtrG:~ECtrl+G - Goto Position\n\nCtrl+G will pop up a window, displaying target x,y coordinates.\nSet the coordinates by either typing in or selecting the desired\nX and Y values, and select OK to go to those coordinates on the\ncurrent board. Choosing Cancel or pressing Escape cancels.\n\n:_G:~EG - Global Info\n\nPress G to enter the global info dialog boxes. The global info\noptions are covered in another section.\n\n>#GLOBALIN.HLP:086:Global Info Options\n\n:AltH:~EAlt+H - Hotkey Toggle\n\nPress Alt+H to toggle display of the hotkeys and horizontal\nborder. The default state of the hotkeys can be changed in the\nconfig file.\n\n>#CONFGINI.HLP:1st:The Config File\n\n:_I:~EI - Info (board)\n\nPress I to enter the board info dialog box. The board info\noptions are covered in another section.\n\n>#BOARDINF.HLP:085:Board Info Options\n\n:078:~EAlt+I - Import\n\nAlt+I allows you to import a number of different file types\ninto the current board or world. You can import a board file\n(.MZB) unique to MegaZeux. You can import a character set file\n(.CHR). You can import another world (.MZX), which is appended\nto the end of the list of boards. The global of the imported\nworld will be ignored. (Unlike old versions of MegaZeux, all\nexits in an imported world will work.) You can import a palette\n(.PAL) file (including a palette indices (.PALIDX) file in SMZX\nmode 3) or a sound effects (.SFX) file, all unique to MegaZeux.\nFinally, you can import an MZM or ANSI/TXT file and place it at\na given position.\n\nImporting boards or worlds will clear the board and overlay undo\nhistories.\n\n:_L:~EL - Load\n\nL brings up a list of worlds in the current directory and allows\nyou to select one to load. You will be warned if the current\nworld as-is has not been saved yet.\n\n:AltL:~EAlt+L - Test WAV\n\nAlt+L brings up a list of WAV/SAM/OGG files in the current\ndirectory. Selecting one will play it once at its natural\nfrequency. This has no effect on the actual board or world.\n\n:_M:~EM - Move Board\n\nM moves the current board to the desired place on the board\nlist. The title board cannot be moved, and other boards cannot\nbe moved to the title board position.\n\n:AltM:~EAlt+M - Modify\n\nAlt+M allows you to modify the settings of the thing under the\ncursor without changing the item in the buffer.\n\n:AltN:~EAlt+N - Music\n\nAlt+N brings up a list of module files in the current directory.\nSelecting one will select it as the default music for the\ncurrent board. If music is already playing, Alt+N will turn it\noff.\n\n:CtrN:~ECtrl+N - Test Music\n\nCtrl+N does the same as Alt+N, except it only plays the\nselected module. It does not set the board module to the\nselected module. It also remembers the last-used directory to\nmaintain a current listening directory. A tested module will\ncontinue to play even if the current board is set to play music,\nand even if the current world file is changed.\n\n:AltO:~EAlt+O - Edit Overlay\n\nAlt+O enters overlay editing mode. The overlay is on normal\noverlay mode by default. The overlay editing mode is similar to\nthe normal editing mode, except that only certain keys are\nactive, and the Enter and Alt+S keys have new purposes. The\noverlay is explained in further detail in another section.\n\n>#TOVERLAY.HLP:081:Editing and Using the Overlay\n\n:_P:~EP - Parameter\n\nP allows you to change the settings of the current object in\nthe buffer. The object under the cursor is not affected.\n\n:084:~EAlt+P - Size/Pos\n\nAlt+P allows you to change the size of the current board, the\nviewport size, and location. See the linked section for details.\nThis action will clear the board and overlay undo histories.\n\n>#GENERALE.HLP:sizepos:Board Sizes\n\n:AltR:~EAlt+R - Restart\n\nAlt+R will clear the entire world. You will be asked for\nconfirmation. All undo histories will also be cleared.\n\n:076:~ES - Save\n\nS will prompt you for a filename, then save the current world\nas a MZX file. If there are unsaved changes, the title bar will\nbegin with an asterisk to show this.\n\n:082:~EAlt+S - Status Info\n\nAlt+S will allow you to type in six different counters that\ncan be shown on the default status screen within the game. This\nallows you to easily display your own items that the player can\ncollect, and see how many are currently held. These counters can\nbe up to 14 characters in length. Counters are discussed in\nfurther detail with Robots.\n\nAny status counter with a value of 0 will not be shown, and\nunseen counters will not change the position of the others.\n\n:AltS2:~EAlt+S - Show Level\n\nAlt+S while editing the overlay will toggle whether the level\nbeneath the overlay is shown in overlay editing mode (defaults\nto YES).\n\n>#TOVERLAY.HLP:081:Editing and Using the Overlay\n\n:AltT:~EAlt+T - Test\n\nAlt+T will allow you to test the current world, starting on the\ncurrent board. Games CAN be loaded and saved in this mode,\nmaking it especially helpful for debugging. Test mode also\nenables several debugging tools and cheats that can be used\nduring a test session. Quitting will return to the editor. Note\nthat entering a test session will clear all editor undo\nhistories.\n\n>#DBGMODE.HLP:dbg:Debug Modes\n\n:_V:~EV - View\n\nV will allow you to see the current board as it would appear in\nthe game. Use the arrows to Scroll the view, and ESC to return\nto the editor.\n\n:083:~EX - Exits\n\nX will bring up a menu where you can select exits for each\nboard direction. For example, selecting a destination board of\n\"City\" for north will cause the player to go to that board when\npressing against the north edge of the current board. Boards\nare not automatically back-linked - to make \"City\" lead back\nhere, you must go to that board and make a south exit back.\nMake sure the linked boards don't obstruct each other's exits,\nand that their linking boundaries are of the same size.\n\n:077:~EAlt+X - Export\n\nAlt+X will allow you to export a number of different file types.\nYou can export a board file (MZB) unique to MegaZeux, good for\ngiving single boards to others. (The character set, palette,\nvlayer and global info aren't included!) You can export the\ncharacter set (CHR) for later game use or to edit in Fontutil or\nanother external app, or export a partial portion of the current\nset instead. You can export palettes (PAL) [regular mode or\nSMZX] and sound effect settings (SFX) for transportation. Both\nare file formats unique to MegaZeux. You can export the current\nworld, but using the last version's world format, with the\n\"Downver. world (MZX)\" option. Finally, you can export a\nPNG-format image of the current board/overlay (when in\nboard/overlay edit modes) or the vlayer (when in vlayer edit\nmode); this option will launch a progress bar, and can be\naborted by pressing the Escape key while exporting.\n\nFiles like MZMs and SAVs can be exported in-game by Robotic\ncode. See their respective areas for details. Other file types\nsuch as character sets can be exported generically through file\nwriting facilities and charset counters. SMZX indices (PALIDX)\ncan be exported using the Export Palette (PAL) option in SMZX\nmode 3; its export dialogue will appear after the palette export\ndialogue.\n\n:AltY:~EAlt+Y - Debug Window\n\nAlt+Y will toggle a red box in the lower corner of the screen.\nThis box shows (on labeled lines) the current position of the\ncursor, the current Robot memory situation, and the presently\nplaying module, and lists the MZX version of the current world\nat the top. This is the same as pressing F6 in-game, but removes\nthe key_code and key_pressed info.\n\n:AltZ:~EAlt+Z - Clear (board)\n\nAlt+Z will clear the current board entirely. You will be asked\nfor confirmation. Clearing the board will clear the board undo\nhistory as well.\n\n:CtrZ:~ECtrl+Z - Undo\n:CtrY:~ECtrl+Y - Redo\n\nCtrl+Z will undo an action; Ctrl+Y will redo any previously\nundone action. Action depth is determined by config file\nsettings, with 100 as default.\n\n:CtrNu:~ECtrl+Number - Save Editor Position\n:AltNu:~EAlt+Number  - Load Editor Position\n\nThese save and load up to 10 cursor positions in the editor (0\nthrough 9). These positions save both current board coordinates\nand active board, so one can save position on one board and load\nposition from another board. These positions are saved for that\nworld in its .editor.cnf file.\n\n:Sft8:~EShift+8 OR Numpad * - Mod Wildcard\n\nThis sets the board's current mod to play whatever played in\nthe last-visited board. (i.e. The mod playing on that board\ndiffers if entering that board from a board using different\nmusic; it does not stick with the first such mod and play that\nfrom then on.)\n\n:SftAr:~EShift+Arrow - Goto Linked Board\n\nThis goes to the board linked in the given direction, if it\nexists. For example, pressing shift+up would go to the board\nlinked to the North exit, provided there is a board linked to\nthat exit.\n\n:min:~E- - Goto Previous Board\n\nThis moves the editor to the previous board in the board list,\nif it exists.\n\n:plu:~E+ - Goto Next Board\n\nThis moves the editor to the next board in the board list, if\nit exists.\n\n:F1:~EF1 - Help\n\nF1 will bring up context-relevant help. You can press F1 at\nalmost any time.\n\n:F2:~EF2 - Text\n\nF2 will toggle text mode on and off. When text mode is on,\nEnter will go to the next line, and Backspace will delete the\ncharacter under the cursor and move the cursor one position to\nthe left. All printable characters will type in as text,\nincluding pressing the Spacebar.\n\n:F3:~EF3  - Terrain\n:F4:~EF4  - Item\n:F5:~EF5  - Creature\n:F6:~EF6  - Puzzle\n:F7:~EF7  - Transport\n:F8:~EF8  - Element\n:F9:~EF9  - Misc. (thing)\n:F10:~EF10 - Objects\n\nThe above eight keys will bring up a menu of things to select\nfrom. Selecting one will ask you to enter settings (if\nappropriate) or choose a character for that thing. Then a copy\nof that thing will be placed at the cursor's location, and will\nalso become the current object in the buffer. Scrolls, Signs,\nSensors, Robots, and Pushable Robots (all in the Objects menu)\nwill be discussed in another section. The current thing, color,\nand settings parameter (p##) is shown in the upper-right corner\nof the editor menu. Knowing the settings parameter isn't\nimportant except for certain Robotic programming situations.\n\n>#SCROLLSS.HLP:1st:Signs and Scrolls in the Editor\n>#SENSORSW.HLP:094:Sensors - What They Are and How to Use Them\n>#ROBOTSWH.HLP:1st:Robots - What They Are and How to Use Them\n>#ROBOTICT.HLP:1st:Robotic Tutorial\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n\n:F11:~EF11 - Select Screen Mode\n\nPressing F11 will open a menu, allowing the user to select an\nMZX display mode (normal mode and Super MZX modes 1-3). This\nsetting will persist in editor tests until overridden in-game;\nif editor tests are left while in a different SMZX mode, it will\nbe reverted to the setting given here upon returning to the\neditor.\n\nSetting the screen mode is a prerequisite for editing Super MZX\npalettes and character sets.\n\n>#SMZXMODE.HLP:095:Super MZX Modes\n\n:F12:~EF12 - Take Screenshot\n\nPressing F12 will make a 640x350 screenshot (in PNG format by\ndefault) in the working directory, using the software renderer.\nThe file naming starts from \"screen0\". This may be disabled by\nchanging the allow_screenshots setting in the config file.\n\n:AF11:~EAlt+F11 - Robotic Debugger\n\nAlt plus F11 will launch the configuration screen for the\nRobotic Debugger. For more information on the Robotic Debugger,\ngo to its relevant section.\n\n>#DBGMODE.HLP:101:Debug Modes - The Robotic Debugger\n\n:ShF1:~EShift+F1 - Show InvisWalls\n:ShF2:~EShift+F2 - Show Robots\n:ShF3:~EShift+F3 - Show Fakes\n:ShF4:~EShift+F4 - Show Spaces\n\nThese four keys will cause the given things onscreen to flash.\nRobots flash as the exclamation point character, char #33; fakes\nas the pound character; spaces flash between the O and *\ncharacters; inviswalls between the carpet/floor characters. They\nare good for locating \"hidden\" or camouflaged Robots, passages,\nand the like.\n\n:Ar:~EArrow - Move\n\nThe arrow keys will move the cursor. The edit window will\nscroll when necessary.\n\n:AltAr:~EAlt+Arrow - Move 10\n\nAlt plus the arrow keys will move the cursor ten spaces at a\ntime, or up to the board's edge if under ten spaces away. These\nalso jump numbers by 10 in dialog boxes.\n\n:BkSp:~EBackSpace - Delete (Main Layer Only)\n:Del:~EDel - Delete\n\nThe Del key will delete everything under the cursor, while\nBackSpace will delete the object on the main layer and move any\nobject on the under layer to the main layer. The current thing\nin the buffer is not affected.\n\n:End:~EEnd - L/R Corner\n\nEnd will jump the cursor to the lower-right corner of the\nentire board.\n\n:En:~EEnter - Modify+Grab [Board Mode]\n\nEnter will modify the settings of the thing under the cursor,\nif applicable, then copy that thing into the buffer. It is just\nlike pressing Alt+M and then Ins.\n\n:Enter2:~EEnter - Character [Overlay Mode]\n\nEnter during Overlay editing mode will change the current\ncharacter. Select it from a menu and then press Enter to\nconfirm your choice.\n\n:ESC:~EESC - Exit/Cancel Mode\n\nESC will exit the editor, asking for confirmation if your world\nhas not been saved. If you are in block, overlay, text, or\ndraw mode, ESC will instead cancel the current mode and return\nto normal editing.\n\n:Home:~EHome - U/L Corner\n\nHome will jump the cursor to the upper-left corner of the\nentire board.\n\n:Ins:~EIns - Grab\n\nIns will copy the thing under the cursor into the buffer. The\nthing under the cursor is not affected.\n\n:Sp:~ESpacebar - Place\n\nSpacebar will copy the thing in the buffer to the location under\nthe cursor. Trying to place something over a similar thing will\ninstead replace the object under the cursor by default. (Robots\nand Pushable Robots are the exception; they will never be\nreplaced.) Placing something over a floor type will actually\nplace it OVER the floor, not replace the floor. Other things\nwill be deleted if they are in the way.\nSpacebar can also replace similar objects under the cursor with\na space; this can be set in the configuration file.\n\n>#CONFGINI.HLP:1st:The Config File\n\n:Tab:~ETab - Draw\n\nTab will toggle the current draw mode. When drawing is on, you\nwill place a copy of the current object in the buffer every\ntime you move the cursor.\n\n:PgDn:~EPageDown - Next Menu\n:PgUp:~EPageUp - Previous Menu\n\nThese two keys will cycle through the pages of the editor menu.\nThe menu bar can wrap around. You do not have to be viewing the\npage listing a specific option before you can use that option -\nthe menus are purely for reference.\nThese keys also jump numbers by 100 in dialog boxes.\n\n>#THEWORLD.HLP:1st:The World Editor\n>#GENERALE.HLP:1st:General Editing Tips\n>#MAIN.HLP:072:Table of Contents\n#CHAREDIT.HLP\n:079:\n$~9The Character Editor\n\nThe character editor is an important part of MegaZeux. With\nit, you can change the appearance of character sets. For\nexample, one can make bricks, rockets, stones, and demons;\nusing multiple characters, one can make much grander things.\nThe default pixel resolution is fair - 8x14 per character for a\ntotal of 640x350 for the entire screen. Each character has a\nforeground color (for \"on\" pixels) and a background color (for\n\"off\" pixels). Other modes, known as Super MZX modes, increase\ncolor amount to four at a cost of lowering per-character\nresolution to 4x14, but the intricacies of character editing in\nthose modes are best covered in that mode's section.\n\nTo use the character editor, press Alt+C in the editor. You\nwill be shown a zoomed version of the current character, a list\nof keyboard shortcuts, and a row of characters.\n\nThe current character(s) being edited are displayed on the left\nedge of the character bar, and also highlighted and shown in\nrelative position in the middle of the bar. To edit the\ncharacter(s), move the cursor with the keys and use Spacebar to\ntoggle pixels on and off (or left-click and right-click,\nrespectively). Use + and - to move one character (or character\nblock) up or down through the character set.\n\nUse Enter to select the current character (or block) from the\nentire set. When selecting from the entire set, one can move by\none character using the arrows, or by one block using the -/+\nkeys.\n\nUse Del to clear the character. Use N to make a \"negative\", or\ninverse, of the character, turning all on pixels off and all\noff pixels on. Use Alt with the arrow keys to shift the entire\ncharacter to one direction. Pixels shifted off of one edge\nwrap around to the other edge.\n\nUse M to mirror the character, flipping it left to right. Use\nF to flip the character top to bottom.\n\nHold Shift to select multiple pixels at a time. The selection\nbox will vanish once Shift is released. Alt+B will also create\na similar selection box, but will not vanish until the Escape\nkey is pressed.\n\nF2 will copy the current character to an internal buffer, while\nF3 will paste the buffer contents to the current character\nspace. The buffer can contain a selection of pixels instead of a\ncharacter by pressing F2 when selecting a group of pixels. When\ncopying a single character and pasting to another single\ncharacter, the new content simply replaces the old; in other\ncircumstances, the new content is placed starting with the\ncursor position as its upper-left corner with no wraparound. The\nbuffer is retained between uses of the character editor.\n\nCtrl+Z will undo actions done in the editor (up to the limit\ndefined in the config file). Keep in mind that the current\nframe is counted in the amount of undo actions.\n\nCtrl+Y will redo any undone actions.\n\nC will allow selection of colors used in the char editor. This\ncan be helpful in crafting characters for a specific palette.\nUse Alt+C to switch colors back to the default.\n\nAlt+F will flood fill any highlighted area.\n\nTab will toggle mode (set) on and off. (set) mode will turn all\npixels crossed to ON.\n\nShift+Tab will toggle (clear) mode. (clear) mode will turn all\npixels crossed OFF.\n\nF4 will revert the current character to its EGA ASCII (code page\n437) appearance; F5 will revert the current character to its\ndefault MZX appearance.\n\nMultiple characters can be edited at one time. To select a\ngroup of characters, press Enter in the character editor, hold\nShift on the upper-left corner of your desired block of\ncharacters to edit, and finally highlight your block of\ncharacters, release Shift, and press Enter. If more than one\nviable character space can be made from the selected characters,\nthe user can choose the desired space size (e.g. six characters\ncan be a 3x2, a 2x3 or a 6x1 space). The largest character space\none can use for editing is 18 characters large (6x3, the max\nallowed sizes for the respective dimensions).\n\nThe extra character sets accessible by unbound sprites can also\nbe edited in the character editor. Press PgUp and PgDown to\nswitch character sets, either on the main character edit screen\nor on the character selection menu.\n\nAlt+I will import character sets while in the character editor;\nAlt+X will export. This can be done with one full set, multiple\nsets, or groups of partials. For a full set, simply select the\ndesired set and select OK. For saving multiple sets or a series\nof partials, first insert a hash sign in the set name then\nchoose the Offset (starting character). Next, choose the First\nnumber of the series (e.g. char#.chr with a First value of 5\nwould start from char5.chr). Then, put the Size of each set (up\nto 256 chars, or a full set). Finally, choose the Count value to\nindicate how many sets will be saved.\nLoading is simpler: simply choose the starting character set,\nthe Offset value and the Count value. If multiple characters are\nbeing worked on at once, importing and exporting partials can be\nset to tile (which will base export and import positions on the\ncurrent character grouping) or linear (which will be based on\nsequential places in the char set).\n\nThe character editor is a key element in creating decent games\nwith MegaZeux. If you can't seem to draw well with it, that's\nokay, since it has some limitations. You can keep practicing\nat it, or you can get an MZXer friend to draw characters for\nyou.\n\nThe character editor changes somewhat in Super MZX modes - go\nto the Super MZX Modes section for more information on this.\n\n>#SMZXMODE.HLP:095:Super MZX Modes\n>#EDITINGK.HLP:080:Editing Keys and Options Reference\n>#MAIN.HLP:072:Table of Contents\n#PALEEDIT.HLP\n:093:\n$~9The Palette Editor\n\nMegaZeux is not limited to the colors it sets as default. The\nbuilt-in palette editor can modify colors, which is very helpful\nin designing a game. It allows you to change the appearance of\nthe currently-available colors, and is more user-friendly than\nhaving to change values through code. MegaZeux worlds can only\nhave one active palette at a time, but can load different\npalettes as frequently as desired, so MegaZeux designers can\ncreate different palettes that fit different parts of their\ngame.\n\nTo use the palette editor, press Alt+E while in the MegaZeux\neditor. The palette editing screen will appear. The current\npalette will be shown to the left, with the current color\nmarked. By default, the RGB values of the current color and the\ncurrent color creation mode are shown to the right of the\nvisible palette, and the menu is shown below. The color\ncurrently stored in the buffer is shown in the bottom-right\ncorner.\n\nSome color theory is in order. Computer monitors display colors\nby projecting various amounts of Red, Green, and Blue light.\nCombined, these three colors can produce almost any shade and\nhue of color. This is one way colors are represented in MegaZeux\nas well. In the RGB color model (the default), each color has a\nRed, Green, and Blue value, each ranging from 0 to 63. 0 is\n\"off\" while 63 is \"full intensity\".\n\nTo make colors other than red, green, and blue, you must mix\nthem. Purple is red plus blue, cyan is green plus blue, and\nyellow is red plus green. (Really.) Whites and grays are made\nfrom equal amounts of all three. For example, Red at 42, Blue at\n42 and Green at 0 would be a deep purple, and all as 20 would be\na dark gray. Orange is made with full red, green set to 31 and\nblue zeroed out. To brighten a color, raise all the numbers, or\nlower them to darken it.\n\nFor the RGB color model, use the arrow keys to select the\ncurrent color. R, G, B, and A will respectively increase Red,\nGreen, Blue, and All, while Alt+R, Alt+G,Alt+B and Alt+A will\nrespectively decrease the values.\n\nSeveral functions are consistent across all color models. Alt+D\nwill reset the palette to its default colors (you will NOT be\nasked for confirmation). 0 will blacken the current color. Alt+H\nwill toggle the shortcut display. The PgUp and PgDn keys select\nthe current color model. Alt+I will import a palette; Alt+X will\nexport the current palette. Tab will switch to and from a\nscratchpad palette that will only exist for the editor session.\nFinally, F2 will store the current color to an internal buffer,\nwhile F3 will paste the buffered color to the current color.\n\nIn addition to the keyboard shortcuts listed in the below menu,\nusers can click colors in the palette to select them. Users can\nalso use the mouse to change color values by clicking on the\nrelevant areas, and hold down the mouse button to act as a\nslider. Color values can also be clicked on and then typed in\ndirectly.\n\nIf one is more familiar with other color models, MegaZeux can\ncreate colors with two alternate models: HSL and CIELAB.\n\nHSL creates colors by setting hue (base color), saturation\n(color strength) and lightness (brightness relative to white).\nHue can take values of 0-360; saturation and lightness take\nvalues of 0-100. The keyboard shortcuts in this mode are C, S\nand V for increasing Hue, Saturation, and Luminance,\nrespectively, while alt key combinations of these keys reduce\nthese values.\n\nCIELAB creates colors through a combination of lightness (L*)\nand color values represented by the opponent pair colors of\nred-green (a*) and yellow-blue (b*). L* values range from 0-100,\nand opponent color values can range from -128 (fully one color\nof the pair) to +128 (fully the other color). The keyboard\nshortcuts in this mode are V, A and B for increasing L*, a* and\nb*, respectively, while alt key combinations of these keys\nreduce these values.\n\n$~9The Palette Editor in Super MZX Modes\n\nSuper MZX Modes allow 256 colors at once, and have their own\nquirks; therefore, the palette editor changes considerably to\naccommodate them. The palette editor for each SMZX mode is noted\nbelow.\n\n$Super MZX Mode 1 Palette Editor\n\nSuper MZX Mode 1 does not require any changes, since it merely\ninterpolates colors. Therefore, palette editing in this mode\nacts the exact same as it does in normal MZX mode.\n\n$Super MZX Mode 2 Palette Editor\n\nSuper MZX Mode 2's color selection is shown as a 16x16 set of\ncolors. Each color also represents the four-color palette (also\nknown as a subpalette) used when an item is assigned that color.\nThe palette number, as well as its hex number and the four\ncolors that comprise it, are shown in the upper-left corner. By\ndefault, the color selector shows the current color/palette\nhighlighted by a white box, with red boxes showing the other\ncolors of the palette. The buffer stores only subpalettes in\nthis mode, cycling through each color of the subpalette.\n\nThe base values of the current color and the current color\ncreation mode are shown in the upper-right, and the help menu\nis shown below.\n\nOn top of the functions allowed in the normal palette editor,\nSMZX Mode 2 adds these hotkeys:\n\n~EF5~F: Store Colors.\n\nStores the currently-highlighted subpalette into the buffer.\n\n~EF6~F: Place Colors.\n\nReplaces the highlighted subpalette with the subpalette colors\ncurrently in the buffer.\n\n~EInsert~F: Cursors.\nTurns the display of the red boxes highlighting the component\ncolors of the given subpalette on or off. Defaults to (on).\n\n$Super MZX Mode 3 Palette Editor\n\nSuper MZX Mode 3's color selection is shown as a 16x16 set of\ncolors. The palette number, as well as its hex number and the\nfour colors that comprise it, are shown in the upper-left\ncorner. Since Mode 3 allows user-defined indices, the buffer\ndisplay now also includes the currently-stored indices just to\nthe left of the stored subpalette.\n\nThe base values of the current color and the current color\ncreation mode are shown in the upper-right, and the help menu\nis shown below.\n\nIn this mode, the highlighted color only corresponds to a color,\nNOT to a subpalette; therefore, one can be editing a different\ncolor and subpalette at the same time, and changing highlighted\ncolor will NOT change the current subpalette.\n\nTo change the current subpalette, press Space or click the\nmiddle mouse button, and then choose the subpalette to edit.\nEach color can be assigned to a color slot in the current\nsubpalette; to place a color in a subpalette slot, press 1-4 to\nput the current color in the corresponding slot (for example,\npressing 2 while on color #120 would make the second color of\nthe current subpalette color #120).\n\nThe Import and Export commands can also handle indices files in\nthis mode.\n\nOn top of the functions allowed in the SMZX Mode 2 palette\neditor, SMZX Mode 3 adds these hotkeys:\n\n~EF7~F: Store Indices.\n\nPuts the current indices into the buffer.\n\n~EF8~F: Place Indices.\n\nAssigns the current subpalette the indices currently in the\nbuffer.\n\nWith the exception of hiding the help, all functions available\nto the palette editor in normal MZX mode are available in the\nSMZX mode 3 editor.\n\n>#EDITINGK.HLP:080:Editing Keys and Options Reference\n>#SMZXMODE.HLP:095:Super MZX Modes\n>#MAIN.HLP:072:Table of Contents\n#GLOBALIN.HLP\n:086:\n$~9Global Info Options\n\nTo edit the Global Info options, press G within the editor.\nHere you can edit options that affect the entire world. The\nfirst dialog you see has the following options:\n\n$First Board\n\nHere is where you select which board will be the starting board\nfor the game. The player will start on this board. This\ndefaults to the second board in the list, or to the title board\nif a second board was not made when creating a new world.\n\n$Edging Color\n\nThis is the color of the area outside of the viewport. It\ndefaults to dark gray on black (c08).\n\n$Starting/Maximum Lives\n\nThis is where you set the number of lives the player starts\nwith, and how many lives the player can have at any one time.\n\n$Starting/Maximum Health\n\nSame as above, but for health points.\n\n$Enemies' Bullets Hurt Other Enemies\n\nIf this is on, then bullets shot by enemies, such as tigers,\nwill damage other enemies that they hit. It basically turns\nall Enemy bullets fired into Neutral bullets (including ones\nshot by Robots).\n\n$Clear Messages and Projectiles on Exit\n\nIf this is on, the current message and all projectiles (seekers,\nbullets, missiles, shooting fire) are cleared whenever the\nplayer leaves the screen.\n\n$Can Only Play World from a 'SWAP WORLD'\n\nIf this is on, then the current world is unplayable in a normal\nfashion. The only ways to play it are by swapping to it from\nanother world with the Robotic command SWAP WORLD, or by testing\nit in the editor.\n\n$More\n\nThis button leads to another screen of settings.\n\n$Edit Chars\n\n:089:This button leads to a series of dialogs where you can edit the\ncharacters and colors used to represent various internal things.\nWhen editing chars, selecting char 255 for an object will make\nthat object type act like a Custom object, which means the char\ndisplayed is based on the object's current parameter.\n\n$Edit Dmg\n\n:090:This button leads to a dialog where you can edit the amount of\ndamage done by various internal things. Valid numbers range from\n0 to 255.\n\n$Edit Global Robot\n\nThis button allows you to edit the global Robot, which is a\nvery special Robot that is present at all times, although it is\nnot physically on every board. It is a very important part of\nMegaZeux. See the section on The Global for more information.\n\n>#THEGLOBL.HLP:gbl:The Global\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n\n:088:The More page of settings for global info has the following\noptions:\n\n$Death Board/Death X/Death Y/Mode\n\nThis tells MegaZeux what happens when the player dies. If the\nmode is Same Position, then the player will simply lose a life.\nIf it is Restart Board, then the player will also teleport to\nthe location on the board where the player entered. If it is\nTeleport, the player will instead teleport to the board and\nx,y coordinates stated in the above three settings.\n\n$Endgame Board/Endgame X/Endgame Y/Mode\n\nThis tells MegaZeux what happens when the player runs out of\nlives. If the mode is Game Over, then the game will simply end.\nIf it is Teleport, the player will be given one life and one\nhealth, and then will be teleported to the board and x,y\ncoordinates stated in the above three settings. This will\ncontinue to happen every time the player runs out of lives or\nwhen a Robot calls the ENDGAME command.\n\n$Play Game Over SFX\n\nIf this option is on, the game over sound effect will be looped\nwhen the game has ended and the Game Over message will flash\nalong the bottom of the screen. The Game Over SFX will not play\nwhen the endgame teleport option is chosen.\n\n$Previous\n\nThis will go to the previous global settings dialog.\n\nThe character editing dialogs (eight pages in total) are screens\nof characters and colors used to represent internal things.\nSelect one to change the color or character used. Use Next and\nPrevious to move between the screens, and Done when you are\nfinished. Lit Bomb Anim 1 is special; setting it will set the\nother 6 frames of Lit Bomb Animation to the succeeding\ncharacters.\n\nSetting the char of any thing to 255 (always represented by a\nmagenta question mark in the char select) will allow that object\ntype to act like a Custom object. This means that the character\nit displays will correspond to the object's parameter (so, for\ninstance, an object with this set and with a parameter of p0a\nwill display character 10). This is best-suited for things that\ndo not otherwise use the parameter field, but will work with\nobjects that have meaningful parameters as well.\n\nAny object type with its char set to 255 that did not previously\nuse the parameter field (such as Trees) will also prompt a\ncharacter selection screen when initially placed in the editor,\nmuch like what happens when selecting a Custom object. Other\nobjects are placed normally.\n\nThe damage editing dialog allows you to change the amount of\ndamage dealt by various things within the game. Select one and\nuse the usual dialog keys to change the values, which can range\nfrom 0 to 255. 0 will leave the player invincible versus that\nthing, but pain sounds and messages will still be triggered.\n\nEditing the global Robot is covered in the Robot tutorials, as\nit is (mostly) the same as editing any other Robot. The actual\nuse of a global Robot is covered in its own section.\n\n>#THEGLOBL.HLP:gbl:The Global\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#EDITINGK.HLP:080:Editing Keys and Options Reference\n>#MAIN.HLP:072:Table of Contents\n#BOARDINF.HLP\n:085:\n$~9Board Info Options\n\nTo edit Board options, press I within the editor. This will\nbring up a dialog where you can edit options pertaining to the\ncurrent board. The options are as follows:\n\n$Board Name\n\nThis is the name of the board. It is mainly for internal\nreference; however, the name of the first board (the title\nscreen) is also used to represent the entire world on a file\nlisting, and board names can be accessed through the BOARD_NAME\nstring counter in Robotic. The special color codes ~~ and @@\ncan apply here, allowing colored text in board names.\n\n$Can Shoot/Bomb\n\nThese options determine whether or not the player can shoot or\nbomb normally. If off, then the player cannot perform the\nnoted action. (Other ways of doing these actions in Robotic may\nreplace the default ways of shooting/bombing and are unimpeded\nby this setting.)\n\n$Fire Burns Space/Fakes/Trees/Brown\n\nThese options determine what fire will and will not burn\nthrough. Space is empty space, NOT floors; Fakes include fakes,\nfloors, carpets, and tiles. Trees is self-explanatory. Brown is\nanything other than the player or a Robot/sign/Scroll that is\ncolored brown (that is, anything with a color of cX6).\n\n$Forest to Floor\n\nIf on, when the player moves through forest, it will become\nFloor. If off, the forest will simply be replaced by a space.\n\n$Collect Bombs\n\nIf on, the player will collect bombs normally. If off, bombs\nwill be instantly lit when touched by the player instead.\n\n$Fire Burns Forever\n\nIf on, fire will never burn out. If off, fire will eventually\nturn into ash, which is actually a dark gray (c08) Floor. Of\ncourse, if fire is set to burn fakes, the ash will probably\nre-light immediately.\n\n$Dragons Move Randomly\n\nIf on, dragons that move have a 1/8 chance of moving randomly\neach time they move instead of always seeking the player.\n\n$Restart if Hurt\n\nWhen on, the player will be teleported to the place on the\ncurrent board that it originally entered every time health is\nlost.\n\n$Reset Board on Entry\n\nWhen on, every time the board is entered, the board will reset,\nacting as if the player is entering the board for the first\ntime.\n\n$Player Locked N/S\n\nWhen on, the player cannot directly move north or south until\nunlocked with the UNLOCKPLAYER command. (Other ways of moving\nthe player in those directions, such as Robotic or the player\nbeing pushed, are unimpeded by this setting.)\n\n$Player Locked E/W\n\nWhen on, the player cannot directly move east or west until\nunlocked with the UNLOCKPLAYER command. (Other ways of moving\nthe player in those directions, such as Robotic or the player\nbeing pushed, are unimpeded by this setting.)\n\n$Player Attack Locked\n\nWhen on, the player cannot shoot bullets or lay bombs until\nunlocked with the UNLOCKPLAYER command. (Other ways of doing\nthese actions, in Robotic, are unimpeded by this setting.)\n\n$Time Limit\n\nIf greater than zero, the player has the noted amount of time\nto complete the board. The current time is shown in the lower\nleft corner during gameplay. If time runs out, the player loses\nten health and the time limit is reset. This option is most\nuseful with the \"Restart if Hurt\" option. The countdown speed\nvaries based on the MZX speed, ticking every other cycle; test\nthe board to get a feel for the amount of time needed. The\ncurrent time is reset if the player exits and re-enters the\nboard.\n\n$Explosions to Space/Ash/Fire\n\nThis determines what explosions will leave in their wake: Empty\nspace, ash (dark gray floors - in other words, floors colored\nc08), or burning fire.\n\n$Can Save/Can't Save/Can Save on Sensors\n\nThis determines when the player can save the game. The first\ntwo options are self-explanatory. Can Save on Sensors allows\nthe player to only save if it is standing directly over a\nSensor. Sensors are explained in another section. This only\nprevents or limits saving using the built-in save functions;\nsaving done with Robotic is unaffected.\n\n>#SENSORSW.HLP:094:Sensors - What They Are and How to Use Them\n\n$No/Normal/Static/Transparent Overlay\n\nThis sets the type of overlay for the current board. No Overlay\nmeans just that - overlay mode is completely off. Normal\nOverlay is an overlay that scrolls with the board. Static\nOverlay is an overlay that remains in a fixed position even when\nthe board scrolls. A Transparent Overlay is similar to no\noverlay in that none is shown, but it allows you to edit it so\nthat it may appear later. Overlays are discussed in full in\nanother section.\n\n>#TOVERLAY.HLP:081:Editing and Using the Overlay\n\n$Load Charset on Entry\n\nThis can be set to load a specific charset every time the board\nis entered, or set to do nothing.\n\n$Load Palette on Entry\n\nThis can be set to load a specific palette every time the board\nis entered, or set to do nothing.\n\n$Reset/Load When Entering from the Same Board\n\nIf this option is set, all transitions from this board to itself\nwill trigger charset/palette reloads. If \"Reset board on entry\"\nis concurrently set, all transitions from this board to itself\nwill also reset the board.\n\nOne can set the current board's settings as the default\nsettings for all new boards made for this world file. Select\nthe \"Set as defaults\" button at the bottom and click or press\nSpace/Enter. This will save a .cnf file for the current world.\nThere will be no confirmation. Since the .cnf is named after the\ncurrent world, the current world must have been saved first.\n\n>#EDITINGK.HLP:080:Editing Keys and Options Reference\n>#MAIN.HLP:072:Table of Contents\n#SOUNDEFX.HLP\n:1st:\n$~9MegaZeux's Sound System\n\nMegaZeux has two types of sound: Digitized sound, played\nthrough a sound card, and sound effects, which are emulated PC\nSpeaker effects.\n\nMegaZeux allows OGG, WAV, SAM (a legacy sound format), and\nmodule files (that is, music files) for digitized sound.\n\nMegaZeux's many sound options can be accessed through the\nconfig.txt file. Please see this section for details.\n\n>#CONFGINI.HLP:1st:The Config File\n\n$Music Files\n\nMusic files, referred to as MODULES, can be any one of the\nfollowing formats:\n\n* OGG\n* MOD (or NST/WOW/OCT)\n* S3M\n* XM\n* IT\n* GDM\n* RAD\n* Other formats {STM, MTM, 669, ULT, FAR, MED, OKT, AMF}\n* Several other formats are in ModPlug or XMP, but are not\n  supported in MZX.\n\nThe default module engine is XMP. It is still possible to build\nusing ModPlug, and possible to build using MikMod to support\nplatforms that cannot handle anything else. OGGs, WAVs and RADs\nare handled outside of the module engines, while GDMs load\nnatively in XMP/MikMod, but not in ModPlug. When a GDM is loaded\nwith ModPlug as the music engine, it is converted to an S3M\nfile. The S3M file is then loaded in all future incidences.\n\nFormats common to all engines are MOD, S3M, XM, IT, STM, MTM,\n669, ULT, FAR, MED, OKT and AMF. All other formats (excluding\nWAV/OGG/SAM/GDM/RAD) are locked out of MZX by default.\n\nMED support is the most inconsistent between module engines,\ndue to its multiple subversions and the engines' varying\naccuracy on dealing with them, so use of this format is not\ngenerally advised.\n\nMZX supports loop markers for OGG files. OGG loops are supported\nwith the LOOPSTART, LOOPLENGTH and LOOPEND metadata tags. MZX\ncan also control OGG looping dynamically with the MOD_LOOPSTART\nand MOD_LOOPEND counters.\n\n$Using Sound and Music in Your Own Games\n\nThere are three ways to utilize sound and music in your own\ngames:\n\n 1- Select the default modules for each board (or mod *).\n 2- Edit the built-in speaker sfx tables.\n 3- Use Robots to play modules, SAMs/WAVs/OGGs, and speaker\n   sfx.\n\n$Selecting Default Modules for Each Board\n\nThis is perhaps the easiest method to get music in your games.\nSimply make sure that the proper module files are in the\ncurrent directory, or in a subdirectory of the current\ndirectory, and use Alt+N to select music for a board. Press\nAlt+N to turn it back off again. To set it to play the same\nmusic as the last board (mod *), press Shift+8 or the numpad's\nasterisk key.\n\n>#EDITINGK.HLP:AltN:Details on Alt+N\n\n$Editing the Built-in Speaker SFX\n\nTo edit the built-in speaker sound effect tables, use Alt+F.\nThe format for them is discussed later in this section.\n\n>#EDITINGK.HLP:097:Details on Alt+F\n\n$Using Robots\n\nRobotic has a large number of commands for playing modules,\nSAMs/WAVs/OGGs, and speaker sfx. A knowledge of Robotic basics\nis required, so see the following sections to learn about\nRobots.\n\n>#ROBOTSWH.HLP:1st:Robots - What They Are and How to Use Them\n>#ROBOTICT.HLP:1st:Robotic Tutorial\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n\nThe following sections discuss individual commands relating to\nsound and music.\n\n>#COMMAND2.HLP:_c6:~ACHANGE SFX # \"string\"\n>#COMMANDR.HLP:_e2:~AEND MOD\n>#COMMANDR.HLP:_e4:~AEND SAM\n>#COMMANDR.HLP:_e3:~AEND PLAY\n>#COMMAND2.HLP:_j1:~AJUMP MOD ORDER #\n>#COMMANDR.HLP:_m3:~AMOD \"file\"\n>#COMMAND2.HLP:_m5:~AMOD FADE IN \"file\"\n>#COMMAND2.HLP:_m6:~AMOD FADE OUT\n>#COMMAND2.HLP:_m4:~AMOD FADE # #\n>#COMMAND2.HLP:_m7:~AMOD SAM # #\n>#COMMANDR.HLP:_p2:~APLAY \"string\"\n>#COMMANDR.HLP:_p3:~APLAY SFX \"string\"\n>#COMMANDR.HLP:_s1:~ASAM # \"file\"\n>#COMMANDR.HLP:_sO:~ASFX #\n>#COMMANDR.HLP:_v3:~AVOLUME #\n>#COMMAND2.HLP:_w2:~AWAIT MOD FADE\n>#COMMAND2.HLP:_w3:~AWAIT PLAY\n>#COMMANDR.HLP:_w4:~AWAIT PLAY \"string\"\n\n$SAM File Support\n\nSAM files, formerly the only format for playing sounds in MZX,\nare directly supported. However, using OGGs or WAVs as the\noriginal source, if possible, is preferred as SAMs are low\nquality (~~8KHz mono) compared to typical WAV and OGG quality\nsettings. SAM support is mostly kept in for supporting older\ngames, not for current usage in designing games.\n\n$Format for Sound Effects\n\nThe sound effects editor and Robotic PLAY commands all use the\nsame format for playing sound effects. The format consists of\na single string of characters, each character representing one\nnote or command. Spaces are ignored, and capitalization is not\nimportant. Some knowledge of music is required to use the\nformat effectively. The commands are:\n\n~A A B C D E F G\n  Plays the note stated, at the current octave and duration.\n  The scale starts at C and continues DEFGAB before going up\n  an octave. Use # and $ for sharps and flats, placing them\n  after the note.\n~A # $\n  Sharps (#) or flats ($) the previous note. This does not\n  affect any note other than the note directly before the sharp\n  or flat. Constructs such as B# are allowed.\n~A 0 1 2 3 4 5 6\n  Sets the current octave. MZX octaves are shifted a number back\n  resulting in octave 3 starting at middle C (aka c'). The lower\n  the number, the lower the notes.\n~A + -\n  Raises or lowers the current octave by 1, but only if\n  possible. You cannot go below octave 0 or above octave 6.\n~A X\n  Plays a rest of the current duration. A rest is a period of\n  silence.\n~A Z T S I Q H W\n  These letters change the current duration of notes and rests,\n  I.E. the length of time each subsequent note is played. Each\n  one is twice as long as the previous one, with Z being the\n  fastest and W the slowest. Duration defaults to T. The\n  counterparts to the durations follow:\n   Z = sixty-fourth note\n   T = ~AT~Fhirty-second note\n   S = ~AS~Fixteenth note\n   I = e~AI~Fghth note\n   Q = ~AQ~Fuarter note\n   H = ~AH~Falf note\n   W = ~AW~Fhole note\n  Duration (and octave) does not carry over from one string to\n  the next.\n~A .\n  A dot will change the current duration to 150% that of usual.\n  This is a permanent duration change and will only affect\n  subsequent notes. Its use is similar to that of a dot in\n  regular music, except that this dot affects multiple notes.\n~A !\n  An exclamation point will change the current duration to 33%\n  that of usual. This is a permanent duration change and will\n  only affect subsequent notes. Its use is similar to that of\n  triplets in regular music, except it affects ALL subsequent\n  notes.\n~A &\n  & is a very special command. It is used to play SAM/WAV/OGG\n  files (digitized sounds) within an sfx string. It allows\n  digitized sounds within the sound effects editor. The general\n  format for & is as follows:\n\n$\"&FILENAME.WAV&4C\"\n\n  This will play FILENAME.WAV at a pitch of 4c, which\n  corresponds to a sample rate of 8363Hz. Any given durations\n  are irrelevant. If nothing follows the last &, the effect will\n  play at the file's natural frequency. Multiple filenames can\n  be used in the same PLAY command, but only the last one can\n  have nothing following its last &.\n~A _\n  An underscore is usually used in conjunction with &. It\n  turns off digitized sounds for the rest of the string, and\n  ignores the rest of the string ONLY IF digitized music/sound\n  is on. For example:\n\n$\"&SHOT.WAV&6F#_+C-C\"\n\n  The above will play SHOT.WAV at a sample rate of roughly 48KHz\n  (F-sharp, 6th octave) if digitized sound is on, OTHERWISE it\n  will play the normal shooting sound of \"+C-C\".\n\n$Sound Effect Numbers\n\nThe PLAY SFX command in Robotic plays one of the built-in sound\neffects. The numerical values corresponding to these sound\neffects are listed below.\n\n0  - Gem\n1  - Magic Gem\n2  - Health\n3  - Ammo\n4  - Coin\n5  - Life\n6  - Lo Bomb\n7  - Hi Bomb\n8  - Key\n9  - Full Keys\n10 - Unlock\n11 - Can't Unlock\n12 - Invis. Wall\n13 - Forest\n14 - Gate Locked\n15 - Open Gate\n16 - Invinco Start\n17 - Invinco Beat\n18 - Invinco End\n19 - Door Locked\n20 - Open Door\n21 - Hurt\n22 - Hurt (Lava)\n23 - Death\n24 - Game Over\n25 - Gate Closing\n26 - Push\n27 - Transport\n28 - Shoot\n29 - Break\n30 - Out of Ammo\n31 - Ricochet\n32 - Out of Bombs\n33 - Place Bomb (Lo)\n34 - Place Bomb (Hi)\n35 - Switch Bomb Type\n36 - Explosion\n37 - Entrance\n38 - Pouch\n39 - Ring/Potion\n40 - Empty Chest\n41 - Chest\n42 - Out of Time\n43 - Fire Ouch\n44 - Stolen Gem\n45 - Enemy HP Down\n46 - Dragon Fire\n47 - Scroll/Sign\n48 - Goop\n49 - Unused\n\n:frq:\n$SAM, OGG, MOD and WAV Frequencies\n\nSounds played using the SAM command in Robotic play at its\n\"natural\" frequency at value 0. If one wants to play the sound\nat a different pitch, a frequency value is needed. Frequencies\ncorresponding to MOD file notes follow:\n\n --------------------------------------------\n |        | C | C#| D | D#| E | F | F#| G | G#| A | A#| B |\n --------------------------------------------\n |Octave 1|856|808|762|720|678|640|604|570|538|508|480|453|\n --------------------------------------------\n |Octave 2|428|404|381|360|339|320|302|285|269|254|240|226|\n --------------------------------------------\n |Octave 3|214|202|190|180|170|160|151|143|135|127|120|113|\n --------------------------------------------\n |Octave 4|107|101| 95| 90| 85| 80| 75| 71| 67| 63| 60| 56|\n --------------------------------------------\n |Octave 5| 53| 50| 47| 45| 42| 40| 37| 35| 33| 31| 30| 28|\n --------------------------------------------\n\nThe following table lists frequencies corresponding to PLAY\nstatement notes:\n\n -------------------------------------------------\n |O|C   |C#  |D   |D#  |E   |F   |F#  |G   |G#  |A   |A#  |B   |\n -------------------------------------------------\n |0|3424|3232|3048|2880|2712|2560|2416|2280|2152|2032|1920|1812|\n -------------------------------------------------\n |1|1712|1616|1524|1440|1356|1280|1208|1140|1076|1016| 960| 906|\n -------------------------------------------------\n |2| 856| 808| 762| 720| 678| 640| 604| 570| 538| 508| 480| 453|\n -------------------------------------------------\n |3| 428| 404| 381| 360| 339| 320| 302| 285| 269| 254| 240| 226|\n -------------------------------------------------\n |4| 214| 202| 190| 180| 170| 160| 151| 143| 135| 127| 120| 113|\n -------------------------------------------------\n |5| 107| 101|  95|  90|  85|  80|  75|  71|  67|  63|  60|  56|\n -------------------------------------------------\n |6|  53|  50|  47|  45|  42|  40|  37|  35|  33|  31|  30|  28|\n -------------------------------------------------\n\nThe following tables list how to play a Wave or OGG at its\nnormal sound with PLAY statements:\n\n 48000Hz Wave/OGG --> 37\n 44100Hz Wave/OGG --> 41\n 32000Hz Wave/OGG --> 56\n 24000Hz Wave/OGG --> 75\n 22050Hz Wave/OGG --> 81\n 16000Hz Wave/OGG --> 112\n 12000Hz Wave/OGG --> 149\n 11025Hz Wave/OGG --> 162\n 08000Hz Wave/OGG --> 224\n\nTo convert frequencies from MZX to real, use these formulae:\n\n Actual frequency = 1789682 / MZX frequency\n MZX frequency = 1789682 / actual frequency\n\nSAMs and WAVs converted from SAMs by older versions of MZX play\nat a natural frequency of 8363Hz.\n\nFinally, tracked modules have a normal frequency of 44100 Hz;\nOGGs, on the other hand, have normal frequencies at their\nrecording rate (for example, a OGG encoded at 22050 Hz will play\nnormally at the 22050 Hz setting). The frequency of a module can\nbe controlled with the \"mod_frequency\" counter. Changing the\nfrequency mid-song can create a popping sound, especially while\nlowering frequency. The lowest allowed setting of mod_frequency\nis 16 Hz.\n\n$Where to Get Module and Sound Files\n\nThese questions are answered in the Frequently Asked Questions\nsection of help.\n\n>#FAQ.HLP:1st:Frequently Asked Questions\n>#CONFGINI.HLP:1st:The Config File\n>#MAIN.HLP:072:Table of Contents\n#TOVERLAY.HLP\n:081:\n$~9Editing and Using the Overlay\n\nThe overlay is a simple but useful part of a board. If one\nlooks at Floors and Fakes as a floor layer, and Walls as a\ncentral layer, the overlay is a ceiling layer. Basically, the\noverlay, when on, appears OVER anything on the board. The\nplayer, enemies, Robots, etc. all move underneath it; the only\nexceptions are sprites specifically set to go over it. The\noverlay is purely for show and graphical effects. For example,\nyou could create archways that the player moves under, or have\na score and lives display that is overlaid over the view at all\ntimes (by using Robotic). However, character 32 (empty space\n99.99% of the time) is never part of the overlay. To emulate a\nspace, use character 0 (if still blank) or make your own space\ncharacter. This is very important to remember when using the\noverlay to display text.\n\nThe overlay is set on \"Normal Overlay\" mode by default. The\neditor will prevent you from editing the overlay if it is Off,\nsince it will not be saved if it is. These are the available\noverlay modes:\n\nOff - No overlay is saved, none is shown.\nNormal - The overlay is shown normally, scrolling along with\n  the rest of the board. Default for boards.\nStatic - The overlay is shown, but it does not scroll with the\n  board. It always shows the top left position. Very useful for\n  creating HUDs and status displays.\nTransparent - The overlay is saved and can hold info, but is\n  currently not shown.\n\nIf the overlay is on, press Alt+O in the editor to edit it.\nThis brings you to the overlay editor. The menu at the bottom\nof the screen will change, and you will have access to the\nfollowing keys and options:\n\n:074:~EAlt+B - Block\n\nPress Alt+B to start block mode, then move the cursor to the\nopposite corner of a rectangular block and press Alt+B again.\nYou can then select an action to perform upon the block. In all\ncopy/move operations, the cursor marks the new UPPER LEFT\ncorner.\n\nCopy block will allow you to duplicate the block by moving the\ncursor to a destination and pressing Enter.\nCopy block (repeated) is the same as Copy block but can allow\ncopying of the same block to multiple places.\nMove block will allow you to move the block to a new location.\nClear block will erase the contents of the block.\nFlip block will flip the block upside down.\nMirror block will flip the block left to right.\nPaint block will change the entire block to the current color.\nCopy to board will copy the block to the given spot of the\nboard. You can choose to place it as either Custom Block, Custom\nFloor, or Text.\nCopy to vlayer will copy the block to the given spot of the\nvlayer.\nSave as ANSi/TXT will save the block as an ANSi or TXT file.\nSave as MZM will save the block as a layer-type MZM file.\n\nDestinations for Move and Copy can overlap the original block\nsafely. Ctrl+Dir is especially helpful when doing a repeated\ncopy block; it moves the cursor the width or height of the\nblock, ensuring no overlap when pasting.\n\nLike normal Block functions, one can copy between boards by\nselecting the board when the editor prompts the user for the\nblock's destination. Use the B key to select the destination\nboard.\n\n:_C:~EC - Color\n\nPress C to select a new current color from a menu. The thing\nunder the cursor is not affected. One can jump to a color by\ntyping its hex code in the color menu; for example, typing \"0D\"\nwould jump to color 013 (background color 0, foreground color\nD).\n\nA quirk to keep in mind: any overlay tile with a background\ncolor of 0 will display the background color of anything beneath\nit.\n\n:_F:~EF - Fill\n\nPress F to fill in an enclosed area with the current character\nand color. The area must be completely surrounded by characters\nother than the character beneath the cursor. For example, you\ncan fill over a solid square of As with something else. The\ncurrent fill command may not work right for very large and\ncomplex areas- In this case, you must move to the unfilled areas\nand press F to continue filling. However, this happens very\nrarely.\n\n:AltO:~EAlt+O - Edit Overlay\n\nAlt+O will exit overlay mode and return to editing the main\nboard.\n\n:AltS2:~EAlt+S - Show Level\n\nAlt+S will toggle whether the level beneath the overlay is\nshown (defaults to yes). Without the level, only the overlay is\nshown, and no board bits or underlying colors are seen through\nany holes or c0x characters in the overlay. However, it can be a\ngood idea to show the level to see how it looks beneath the\noverlay.\n\n:F1:~EF1 - Help\n\nF1 will bring up context-relevant help. You can press F1 at\nalmost any time.\n\n:F2:~EF2 - Text\n\nF2 will toggle text mode on and off. When text mode is on, Enter\nwill go to the next line, and Backspace will delete going\nbackwards. All printable characters will type in as text.\nPressing Space will place a char 32 on the overlay.\n\n:Ar:~EArrow - Move\n\nThe arrow keys will move the cursor. The edit window will scroll\nwhen necessary.\n\n:AltAr:~EAlt+Arrow - Move 10\n\nAlt with the arrow keys will move the cursor ten spaces at a\ntime (or to the edge if under 10 spaces away in that direction).\n\n:BkSp:~EBackspace - Delete\n:Del:~EDel - Delete\n\nThese two keys will delete any overlay under the cursor. The\ncurrent character is not affected.\n\n:End:~EEnd - L/R Corner\n\nEnd will jump the cursor to the lower-right corner of the entire\noverlay.\n\n:Enter2:~EEnter - Character\n\nEnter will alter the current character on the overlay. Select it\nfrom a menu and then press Enter to confirm your choice.\n\n:ESC:~EESC - Exit/Cancel Mode\n\nESC will exit overlay mode. If you are in block, text, or draw\nmode, ESC will instead cancel the current mode and return to\nnormal overlay editing.\n\n:Home:~EHome - U/L Corner\n\nHome will jump the cursor to the upper-left corner of the\nentire overlay.\n\n:Ins:~EIns - Grab\n\nIns will select the character and color under the cursor\nas the current. The actual character is not affected.\n\n:Sp:~ESpacebar - Place\n\nSpacebar will copy the current character and color to the\nlocation under the cursor. Other overlay will be deleted if it\nis under the cursor.\n\n:Tab:~ETab - Draw\n\nTab will toggle the current draw mode. When drawing is on, you\nwill place a copy of the current character every time you move\nthe cursor.\n\n~EP - Change Buffer Character\n\nP will act much like Enter, but only change the character in the\nbuffer; no character on the overlay is changed with this action.\n\n:CtrZ:~ECtrl+Z - Undo\n:CtrY:~ECtrl+Y - Redo\n\nCtrl+Z will undo an action; Ctrl+Y will redo any previously\nundone action. Action depth is determined by config file\nsettings, with 100 as default.\n\nThe basic method of editing the overlay is to use C and Enter to\nselect characters and colors, and use Space, arrows, and Tab to\ndraw with them. Do not switch overlay mode to Off once you have\ndrawn your overlay, or it may be permanently erased.\n\nThe overlay and overlay mode can also be changed using Robotic;\nwith Robotic, not only can one powerfully manipulate the\noverlay, but change the overlay's type, and also copy directly\nto the board or vlayer from the overlay (and vice versa). A\nknowledge of Robotic basics is required, so see the following\nsections to learn about Robots.\n\n>#ROBOTSWH.HLP:1st:Robots- What They Are and How to Use Them\n>#ROBOTICT.HLP:1st:Robotic Tutorial\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n\nThe following sections discuss individual commands relating to\noverlays.\n\n>#COMMAND2.HLP:_c4:CHANGE OVERLAY [color] [char] [color] [char]\n>#COMMAND2.HLP:_c5:CHANGE OVERLAY [color] [color]\n>#COMMAND2.HLP:_cJ:COPY BLOCK # # # # # #\n>#COMMAND2.HLP:_cL:COPY OVERLAY BLOCK # # # # # #\n>#COMMAND2.HLP:_cR:COPY OVERLAY BLOCK # # # # \"@@filename\" #\n>#COMMAND2.HLP:_cS:COPY OVERLAY BLOCK \"#x\" \"#y\" # # \"@@filename\" #\n>#COMMAND2.HLP:_cU:COPY OVERLAY BLOCK # # # # \"$string\" #\n>#COMMAND2.HLP:_cV:COPY OVERLAY BLOCK \"#x\" \"#y\" # # \"$string\" #\n>#COMMAND2.HLP:_cX:COPY OVERLAY BLOCK # # # # \"#x\" \"#y\"\n>#COMMAND2.HLP:_cZ:COPY OVERLAY BLOCK \"#x\" \"#y\" # # # #\n>#COMMAND2.HLP:_cAA:COPY OVERLAY BLOCK \"#x1\" \"#y1\" # # \"#x2\" \"#y2\"\n>#COMMAND2.HLP:_o2:OVERLAY ON\n>#COMMAND2.HLP:_o3:OVERLAY STATIC\n>#COMMAND2.HLP:_o4:OVERLAY TRANSPARENT\n>#COMMAND2.HLP:_pE:PUT [color] [char] OVERLAY # #\n>#COMMAND2.HLP:_w7:WRITE OVERLAY [color] \"string\" # #\n\n>#MAIN.HLP:072:Table of Contents\n#SCROLLSS.HLP\n:1st:\n$~9Signs and Scrolls in the Editor\n\nSigns and scrolls are a simple way to get messages across in\nthe game. Signs can be read multiple times; scrolls can be read\nonce, then disappear. To place them in the editor, press F10 and\nselect Sign or Scroll from the list. You must then edit the text\nof the sign or scroll.\n\nThe Scroll editor is very simple. You can move the cursor among\nthe different lines and characters. Type to insert text\nanywhere. Press Ins to toggle between insert mode (default)\nand overwrite mode, where typed characters will overwrite any\ncharacters already there. Backspace and delete work as normal,\nas do PageDown, PageUp, End, and Home. Use Alt+C to insert a\nselected character from the current character set into the text.\nPress Alt+V to toggle between viewing the scroll elements with\nthe current game charset/palette and the editor charset/palette.\nFinally, use Enter to start or insert new lines.\n\nSigns and scrolls allow the standard color codes in messages\n(described in the Strings section of the help). Default color\ncoding for messages in scrolls is background color 8 and\nforeground color F.\n\nPress ESC when you are done editing your sign or scroll. It will\nbe placed at the current cursor position and will become the\ncurrent object in the buffer. Note that although you can now\ncopy the scroll freely, there is a limit of 255 scrolls and\nsigns per board, but this limit shouldn't ever be a problem.\n\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n>#MAIN.HLP:072:Table of Contents\n#DBGMODE.HLP\n:dbg:\n$~9Debug Modes\n\nThe debug modes are powerful tools. MegaZeux contains two types\nof debug modes: the Counter Debug Mode and the Robotic\nDebugger.\n\n$Counter Debug Mode\n:100:\nThe counter debug mode - accessed by pressing F11 while running\na world during playtesting in the editor - not only can pause\nall current MZX world action and display all of the counters\nand strings the world has set, but also can manipulate these\nvalues and even add new ones.\n\nThe left side of the counter debug mode shows the current\nselection of counters/strings. Built-in counters ending with an\nasterisk (*) are read-only. Highlighting a counter/string and\neither clicking it or pressing Enter/Space while on it will\nbring up a box, allowing the value of the counter/string to be\nchanged. Type in the desired new value, and press Enter to set\nthe counter/string to the new value. Press Escape to cancel\nsetting a new value.\n\n(PLEASE NOTE: Strings can only show and set its first 68\ncharacters when attempting to set a new value. If a string\noriginally contained over 68 characters, none of the characters\npast the 68th will show in the value setting box, even when any\nof the first 68 characters are deleted. Simply choosing to set\na value, while not doing anything else, will truncate the\nstring down to its first 68 characters.)\n\nThese counters and strings are organized into various lists,\nshown in the upper-right. Selecting one of these lists will\ndisplay its contents in the left-side box. If a list has a plus\nsign next to it, it is a tree, and clicking on it or pressing\nSpace or Enter will expand it to show any of that tree's\nsub-lists (if they exist). Repeat this action to hide the\nsub-lists.\n\nThe lists are as follows:\n\n~ACounters~F: Lists all user-defined counters, as well as status\ncounters (things like AMMO, HEALTH, etc) and menu counters,\nfollowed by their values. Expanding this tree will allow\nselection of lists sorted by starting character - individual A\nthrough Z lists contain only counters starting with their\nrespective characters, and # does the same for everything else.\nOnly the first 45 characters of a counter name will be shown.\n\n~AStrings~F: Lists all user-defined strings, followed by their\nvalues. Expanding this tree will allow selection of lists\nsorted by starting character - individual A through Z lists for\nthe respective characters, and # for everything else. Only the\nfirst 16 characters of a string name (including the $) and the\nfirst 40 characters of a string value will be shown.\n\n~ASprites~F: Lists the global sprite counters and their values.\nExpanding this list will allow selection of lists sorted by\nrelevant sprite - \"spr0\", \"spr1\", \"spr2\", and so on - if any of\nthat sprite's counters have been set. Each spr# list shows all\nof each sprite's relevant counters. The number of\ncurrently-active sprites is given under the \"Active sprites\"\nentry.\n\n~AUniversal~F: Lists counters which persist throughout an entire\nsession of MegaZeux, as well as their values.\n\n~AWorld~F: Lists built-in world counters/strings and their values.\nAlso displays the amount of RAM used by each component of the\ncurrently-running world under the \"RAM\" sub-list.\n\n~ABoard~F: Lists the current board's built-in board counters/strings\nand their values. Also can expand to show lists of scrolls/signs\n(both under \"Scrolls\") and sensors, if any of these items exist\non that board. For scrolls/signs, each list shows a \"Scroll\ntext\" entry; if clicked or Enter is pressed while highlighted,\nit views that scroll's/sign's text as it would be if encountered\nin-game (i.e. in the currently-loaded charset, not the protected\ncharset). For sensors, each list shows the sensor name and the\nRobot it messages.\n\n~ARobots~F: If directly highlighted, this option lists nothing.\nHowever, expanding this tree will allow selection of lists\nsorted by the robot_id of the current board's Robots - starting\nwith \"0: <name>\" for the Global, and continuing on for each\nRobot on the board. Each list displays that Robot's local\ncounters and their values (as well as their robot_name\nstrings).\n\nIn addition, certain special values can be accessed in this\nmenu.\n-The number of commands that the given Robot has processed is\nlisted under its commands_total value.\n-The number of commands that the given Robot has executed in\nthe current cycle is listed under its commands_cycle value.\nThis value is read-only.\n-Whether the given Robot is locked or unlocked is denoted by\nthe value of lockself (0 for unlocked, 1 for locked).\n\nOutside of directly displaying and setting values of counters\nand strings, the counter debug mode has several other tools at\nits disposal. They are given buttons in the lower-right corner,\nand are as follows:\n\n~ASearch~F: Locates names and values in any of the counters/strings\nlisted in the counter debug mode. Type the desired search\nstring and select from the given options, then press Enter. The\nsearch begins from the last highlighted line. This menu can be\ndirectly accessed with Ctrl+F, and an instant repeat of the\nlast search can be done with Ctrl+R. Options:\n-Search names: Searches counter and string names.\n-Search values: Searches counter and string values.\n-Case sensitive: Returns results only if case matches.\n-Exact: Returns results only if the entire string or counter\nvalue matches (as opposed to if part of the string or counter\nvalue matches).\n-Reverse: Searches from bottom to top (as opposed to the\ndefault top to bottom).\n-Wrap search: If no results are found, starts from the opposite\nend and searches until a result is found or the original search\nlocation is reached.\n-Current list only: Only searches the current list. Child lists\nof the current list will not be searched.\n\n~ANew~F: Creates a new counter or string with the given name. Its\nvalue will be 0 for a new counter, and blank for a new string.\nIf any counter or string already exists with the given name,\nthis will be ignored and the original counter/string will keep\nits original value. You will not be notified of any such\nconflicts. This menu can be directly accessed with Alt+N.\n\n~AToggle Empties~F: Sets whether counters with values of 0 and\nblank strings are shown in the display lists. Defaults to Show\n(i.e. counters and strings with these values will be shown).\nSearching forces this setting to Show. This menu can be\ndirectly accessed with Alt+H.\n\n~AExport~F: Exports a text file list of SET commands that would set\nthe current user-defined counters and strings, and the built-in\ncounters in the Counters parent list, to their current values.\nIf overwriting a current file is chosen, MZX will ask for\nconfirmation.\n\nIn addition to its own considerable power, accessing the counter\ndebug mode can allow access to the character editor (via Alt+C)\nor the palette editor (via Alt+E) for further manipulation and\ntesting.\n\n$The Robotic Debugger\n:101:\nEver have problems with a Robot choking your game to a crawl?\nCan't figure out how someone else's code works from a simple\nlookthrough? Are your Robots just not interacting the way you'd\nplanned? The Robotic Debugger might help. The Robotic Debugger\ncan enable controlled line-by-line execution of Robots, or even\nhalt Robot execution altogether.\n:102:\nSince stepping through code line-by-line can be very, very\ntedious if unneeded, the debugger only starts allowing such\ncontrol after meeting given conditions. The conditions that\ntell the debugger when to begin stepping through lines are\ncalled breakpoints.\n\nMegaZeux does not provide default breakpoints (with one notable\nexception explained later), so suitable breakpoints must be\ncreated. Pressing Alt+F11 while in the editor or during an\neditor testplay will load the config screen for the Robotic\nDebugger.\n\nBreakpoints can be added by using Alt+N or Alt+A, or by clicking\nthe \"(new)\" text. Breakpoints will activate stepping when\ncharacters in a line of code match a given string, preventing\nthe line triggering the breakpoint from executing. Additionally,\nthe breakpoint can be set to trigger only for Robots whose names\ncontain the characters in another given string, or only when the\ncondition is met on a given line number.\n\nA blank string can be set as a breakpoint. In this case, all\nlines running in Robots matching the given Robot name string\nwill trigger the debugger, and all line numbers matching a given\nline number will likewise always trigger. However, leaving all\nstring fields blank and line number at 0 will have no notable\neffect.\n\nString matches for breakpoints, like MegaZeux string comparisons\nin general, do not have to match case. Also, as implied, the\nstring comparisons are not for the whole line or Robot name; the\nlines or names must merely contain the given strings.\n\nSimilar to breakpoints are watchpoints. Watchpoints activate\nstepping as well, but are based off of the values of counters\nand strings, and only activate stepping after a relevant value\nis changed, not right when one is seen. Unlike with breakpoints,\nthe watchpoint Variable string has to exactly match the counter\nor string name to trigger (although this check is case\ninsensitive). Local counters can be watched as well as global\ncounters; to watch local counters, check for rN.<counter>, where\nN is the robot_id, and <counter> is the relevant local counter\nname (without the angle brackets). For watching the global\nRobot's local counters, just check for the relevant local\ncounter name.\n\nWhen the Value field of a watchpoint is left blank, the\nwatchpoint triggers every time the relevant counter or string is\nchanged. Putting a value in the Value field has the watchpoint\ntrigger only when the relevant counter or string changes to that\nspecific value.\n\nWhen a counter watchpoint is triggered, the debugger will show\nthe original value and the changed value. String watchpoints\nwill only show that the watched string value has changed.\n\nEditing breakpoints/watchpoints is done by pressing Space.\nDeleting breakpoints/watchpoints is done by pressing Alt+D.\n\nThe Robotic debugger defaults to off. Selecting the Enable\nRobotic Debugger button, or adding a breakpoint or watchpoint,\nactivates the debugger from that point on. Once done editing\nbreakpoints and watchpoints, select the Done button by clicking\nit or by using Tab to highlight it and pressing Enter.\n\nIn addition to user-defined breakpoints and watchpoints,\nMegaZeux also activates stepping through the debugger when Robot\nprocessing reaches very high amounts; if the debugger was\ncurrently off, hitting this condition forces it on. By default,\nthis happens when a Robot executes over 2,000,000 commands in a\nsingle cycle, but this can be changed by changing the value of\nthe COMMANDS_STOP counter.\n\nOnce a breakpoint or watchpoint is hit, MegaZeux halts execution\nof all actions and opens up a menu. Actions, and the keys that\nactivate them, are as follows:\n\n~AContinue~F: Stops stepping through code until the next\nbreakpoint/watchpoint is hit. (C or Escape)\n\n~AStep~F: Steps through the current line, executing it, and\nprompts the user for further action at the next line. (S)\n\n~AGoto~F: Opens up a menu to send Robots to a given label. (G)\nAll Robots that match the given Name string (or all Robots for\nblank) will be sent to the given label. The Goto option will\nsend the relevant Robots to the given label regardless of locked\nstatus; the Send option will act as a normal label send; the\nSend All option will act as a normal label send to all Robots.\nSelect the \"Send/send all ignores locked status?\" option to have\nthese ignore locked states as well.\n\nThe strings given here do not have to be constants: expression\noutputs and interpolated strings will also evaluate correctly\nfor these values.\n\n~AHalt~F: Makes the Robot running the current line immediately\nstop processing commands. Note that this does not permanently\nstop the Robot from processing commands in the future (e.g. due\nto being sent to a label), but merely prevents it from running\nits currently planned commands. (H)\n\n~AHalt All~F: Immediately makes ALL Robots stop processing\ncommands. (Alt+H)\n\n~ACounters~F: Opens up the Counter Debug Mode. (F11)\n\n~ABreakpoints~F: Opens up the Robotic Debugger config. (Alt+F11)\n\nThe PgUp and PgDn keys can shift the debugger window to the top\nor bottom of the screen, respectively.\n\n>#MAIN.HLP:072:Table of Contents\n#ERRORMES.HLP\n:1st:\n$~9Error Messages\n\nThe following is a list of error messages in alphabetical\norder, which links to descriptions of the error and possible\nremedies. MegaZeux silently ignores many other possible errors.\n\nMegaZeux General Errors:\n\n>era:Cannot decrypt write-protected world; check permissions\n>erb:Cannot overwrite the player- move it first\n>erc:Current renderer lacks advanced graphical features; features\n  ~Fdisabled\n>erd:Directory rename failed.\n>ere:Error exporting ANSi\n>erf:Error exporting SFX file\n>erg:Error exporting text\n>erh:Error saving; file/directory may be write protected\n>eri:Error swapping to next world\n>erj:(Filename) already exists.\n>erk:File rename failed.\n>erl:Limited/missing extended charset support; some features may not\n  ~Fwork\n>erm:Overlay mode is not on (see Board Info)\n>ern:Save would overwrite older world. Aborted.\n>ero:Windowing code bug\n>erp:You can only play this game via a swap from another game\n\nMegaZeux Validation Errors:\n\n>eva:Any extra scrolls/signs/robots were replaced\n>evb:Board @@ (hex location) could not be found\n>evc:Board @@ (hex location): found (#) robots; expected (#)\n>evd:Board @@ (hex location): found (#) scrolls/signs; expected (#)\n>eve:Board @@ (hex location): found (#) sensors; expected (#)\n>evf:Board @@ (hex location) is irrecoverably truncated or corrupt\n>evg:Board @@ (hex location) is truncated, but could be partially\n  ~Frecovered\n>evh:Board file is from a future version (version)\n>evi:Bytecode file (filename) failed validation check\n>evj:Cannot load password protected world\n>evk:Error importing ANSi\n>evl:File doesn't exist\n>evm:File is invalid or is not an SFX file\n>evn:File is not a board file or is corrupt\n>evo:File is not an MZM or is corrupt\n>evp:File is not a valid .SAV file or is corrupt\n>evq:File is not a valid world file or is corrupt\n>evr:MZM contains runtime robots; dummying out\n>evs:MZM doesn't exist\n>evt:MZM from newer version (version); dummying out robots\n>evu:MZM is missing robots or contains corrupt robot\n>evv:Post validation IO error occurred\n>evw:Robot @@ (hex location) could not be found\n>evx:Robot @@ (hex location) is truncated or corrupt\n>evy:.SAV files from newer versions of MZX (version) are not\n  ~Fsupported\n>evz:.SAV files from older versions of MZX (version) are not\n  ~Fsupported\n>ev1:Scroll @@ (hex location) is truncated or corrupt\n>ev2:Sensor @@ (hex location) is truncated or corrupt\n>ev3:This world may be password protected. Decrypt it?\n>ev4:Unknown error reading from file\n>ev5:Unknown error writing to file\n>ev6:World is from a more recent version (version)\n\nMegaZeux Validation Errors (ZIP World Format):\n\n>eza:Board # (number) is corrupt\n>ezb:Board # (number) is missing data:\n>ezc:Robot # (number) contains duplicates on board # (number)\n>ezd:Robot # (number) does not exist on board # (number)\n>eze:Robot # (number) exists on board # (number), but was not found\n>ezf:Robot # (number) on board # (number) is corrupt\n>ezg:Scroll # (number) on board # (number) is corrupt\n>ezh:Sensor # (number) on board # (number) is corrupt\n\nMegaZeux Updater Errors:\n\n>eua:Attempt to invoke self failed!\n>eub:Failed to back up manifest. Check permissions.\n>euc:Failed to change back to user directory.\n>eud:Failed to change into install directory.\n>eue:Failed to compute update manifests\n>euf:Failed to create (filename). Check permissions.\n>eug:Failed to create directories (path too long)\n>euh:Failed to create TCP client socket.\n>eui:Failed to identify applicable update version.\n>euj:Failed to initialize network layer.\n>euk:Failed to remove (filename). Check permissions.\n>eul:Failed to roll back manifest. Check permissions.\n>eum:Failed to prune directories (path too long)\n>eun:Transferred more than expected uncompressed size.\n>euo:Unknown stat() error occurred\n\n~9MZX General Errors\n\n:era:~ACannot decrypt write-protected world; check permissions\n\nThis can be caused by file errors or a hard disk space\nshortage. It can also result from an attempt to overwrite a\nread-only file.\n\n:erb:~ACannot overwrite the player- move it first\n\nThe user attempted to place an object over the player. The\nplayer must be moved from its current location if something\nelse is desired at that location.\n\n:erc:~ACurrent renderer lacks advanced graphical features; features\n~Adisabled\n\nCertain graphics renderers (overlay2,gp2x) and platforms (NDS)\nare unable to utilize some newer graphical features (i.e. layer\nrendering). If this error message is displayed, these features\nwill not work.\n\n:erd:~ADirectory rename failed.\n:erk:~AFile rename failed.\n\nThe user attempted to rename a directory or file, respectively,\nbut failed. This could be due to trying to change a read-only\nfile, trying to change a locked file, or due to some other\nerror.\n\n:ere:~AError exporting ANSi\n:erf:~AError exporting SFX file\n:erg:~AError exporting text\n\nThe user attempted to export a file in the relevant format, but\nno file was created. This can be caused by file errors or a hard\ndisk space shortage. It can also result from an attempt to\noverwrite a read-only file or write to a directory without write\npermissions.\n\n:erh:~AError saving; file/directory may be write protected\n\nThis can be caused by file errors or a hard disk space\nshortage. It can also result from an attempt to overwrite a\nread-only file or write to a directory without write\npermissions.\n\n:eri:~AError swapping to next world\n\nA Robotic command, SWAP to WORLD, was issued, but the stated\nworld could not be found or otherwise could not be swapped to,\ndue to version conflicts, file errors, etc.\n\n:erj:~A(Filename) already exists.\n\nThe user attempted to create a new file, but a file with that\nname already exists.\n\n:erl:~ALimited/missing extended charset support; some features may not\n~Awork\n\nWhen this error message is displayed, the currently-running\ninstance of MegaZeux cannot fully support extended character\nsets. Worlds that rely on this feature will still run in this\ncase, but without this feature.\n\n:erm:~AOverlay mode is not on (see Board Info)\n\nYou cannot edit or copy to the Overlay if it is Off; go to\nBoard Info and turn it to Normal, Static, or Transparent.\n\n:ern:~ASave would overwrite older world. Aborted.\n\nA save was attempted in the debytecode version of MegaZeux that\nwould overwrite a non-debytecode world file. Due to the present\ninstability of debytecode, this is forcibly prevented.\n\n:ero:~AWindowing code bug\n\nThis is a critical internal error. Try to isolate the reason for\nthe problem and notify the maintainer(s), as this error\nsignifies a severe bug in MegaZeux.\n\n:erp:~AYou can only play this game via a swap from another game\n\nThis error occurs when the user tries to play a game that has\nthe option set to disallow normal gameplay. The world can only\nbe played as a swap from another game or in the editor. For\nexample, this may be the second half of another game, or a\nseparate world for showing the introduction.\n\n~9MZX Validation Errors\n\n:evc:~ABoard @@ (hex location): found (#) robots; expected (#)\n:evd:~ABoard @@ (hex location): found (#) scrolls/signs; expected (#)\n:eve:~ABoard @@ (hex location): found (#) sensors; expected (#)\n:eva:~AAny extra robots/scrolls/signs were replaced\n\nMegaZeux found more instances of the given object than the\nworld file indicated it had. All instances past the expected\namount are tossed out.\n\n:evb:~ABoard @@ (hex location) could not be found\n\nThe MegaZeux world is indicating that there should be a board\nat the given location, but it is not actually in the world\nfile.\n\n:evf:~ABoard @@ (hex location) is irrecoverably truncated or corrupt\n\nThe board at the given location is corrupt beyond repair and\nwill be replaced with a blank board.\n\n:evg:~ABoard @@ (hex location) is truncated, but could be partially\n~Arecovered\n\nOnly part of the board was found, but what board info remained\nis placed at that location.\n\n:evh:~ABoard file is from a future version (version)\n\nLoad of a board file from a more recent version of MegaZeux was\nattempted. You must use the version of MegaZeux listed or higher\nin order to import this board file.\n\n:evi:~ABytecode file failed validation check\n\nAn attempt to load a bytecode file has failed validation checks\nand will not be loaded.\n\n:evj:~ACannot load password protected world\n\nSince MegaZeux 2.80, worlds that are password protected have to\nbe stripped of the password to run. This error is seen when the\nuser refuses to decrypt the password-protected file.\n\n:evl:~AFile doesn't exist\n\nAn import of a file was attempted, but the file does not exist.\n\n:evk:~AError importing ANSi\n:evm:~AFile is invalid or is not an SFX file\n:evn:~AFile is not a board file or is corrupt\n:evo:~AFile is not an MZM or is corrupt\n:evp:~AFile is not a valid .SAV file or is corrupt\n:evq:~AFile is not a valid world file or is corrupt\n\nAn import of the given type was halted due to being unrecognized\nas a valid file, or due to corruption.\n\n:evr:~AMZM contains runtime robots; dummying out\n\nLoad of an MZM containing Robots and saved during a running\ngame was attempted in the editor. The Robots will be replaced\nwith CustomBlock facsimiles, as if the file were loaded as a\nlayer-type MZM.\n\n:evs:~AMZM doesn't exist\n\nLoading an MZM file was attempted, but the given file does not\nexist.\n\n:evt:~AMZM from more recent version (version); dummying out robots\n\nLoad of an MZM file with Robots from a more recent version of\nMZX was attempted. The Robots will be replaced with CustomBlock\nfacsimiles, as if the file were loaded as a layer-type MZM.\n\n:evu:~AMZM is missing robots or contains corrupt robot\n\nThe loaded MZM file indicates that a Robot should be at a\ncertain location of the MZM, but found either no code or\ncorrupt code.\n\n:evv:~APost validation IO error occurred\n\nThe given file passed validation, but some other problem is\npreventing it from loading.\n\n:evw:~ARobot @@ (hex location) could not be found\n\nThe board file indicates a Robot should be present at the given\nlocation, but no Robot exists there.\n\n:evx:~ARobot @@ (hex location) is truncated or corrupt\n\nThe Robot at the given location is unrecoverable, and will be\nreplaced with an empty Robot at that location.\n\n:evy:~A.SAV files from newer versions of MZX (version) are not\n~Asupported\n:evz:~A.SAV files from older versions of MZX (version) are not\n~Asupported\n\nThe .SAV file the user attempted to load is a different format\nthan a supported .SAV version and cannot be loaded. This\nconsists of all newer .SAV versions and all .SAV versions\npredating version 2.84.\n\n:ev1:~AScroll @@ (hex location) is truncated or corrupt\n\nThe Scroll at the given location is unrecoverable, and will be\nreplaced with an empty Scroll at that location.\n\n:ev2:~ASensor @@ (hex location) is truncated or corrupt\n\nThe Sensor at the given location is unrecoverable, and will be\nreplaced with a Sensor with default attributes at that location.\n\n:ev3:~AThis world may be password protected. Decrypt it?\n\nWorlds saved with password protection (some worlds made before\nMZX2.51s3.2) cannot natively run in MegaZeux. This prompt asks\nthe user if they want to convert the world to a readable format.\nIf Yes is chosen, by default MegaZeux will create and run a\ndecrypted copy of the world in memory or as a temp file. The\noriginal MZX file will remain unchanged. If No is chosen, the\nworld is left alone and MZX keeps any currently-running world as\nits active world. The decryption method can be changed via the\nconfig file.\n\n>#CONFGINI.HLP:1st:The Config File\n\n:ev4:~AUnknown error reading from file\n\nA file read was attempted, but MZX failed for some undetermined\nreason.\n\n:ev5:~AUnknown error writing to file\n\nMZX attempted to write to a file, but failed for some\nundertermined reason.\n\n:ev6:~AWorld is from a more recent version (version)\n\nThe .MZX file you tried to load or import is from a more\ncurrent version of MegaZeux. You must upgrade MegaZeux to the\ngiven version or higher to play this worldfile.\n\n~9MegaZeux Validation Errors (ZIP World Format):\n\n:eza:~ABoard # (number) is corrupt\n\nThe board with the given number is corrupt beyond repair.\n\n:ezb:~ABoard # (number) is missing data:\n\nThe given board lacks data that its structure indicates it\nshould have.\n\n:ezc:~ARobot # (number) contains duplicates on board # (number)\n\nA Robot is found in multiple places in the given board's data.\n\n:ezd:~ARobot # (number) does not exist on board # (number)\n\nA Robot should exist on the given board, according to the board\ndata, but does not.\n\n:eze:~ARobot # (number) exists on board # (number), but was not found\n\nA Robot exists on the given board, but the board data for that\nRobot is absent.\n\n:ezf:~ARobot # (number) on board # (number) is corrupt\n\nThe Robot on the board with the given number is corrupt beyond\nrepair.\n\n:ezg:~AScroll # (number) on board # (number) is corrupt\n\nThe Scroll on the board with the given number is corrupt beyond\nrepair.\n\n:ezh:~ASensor # (number) on board # (number) is corrupt\n\nThe Sensor on the board with the given number is corrupt beyond\nrepair.\n\n~9MZX Updater Errors\n\n:eua:~AAttempt to invoke self failed!\n\nThis error occurs when MZX fails to reload itself after an\nupdate. This signifies a major bug in MegaZeux; please contact\nthe maintainer(s).\n\n:eub:~AFailed to back up manifest. Check permissions.\n:euf:~AFailed to create (filename). Check permissions.\n:euk:~AFailed to remove (filename). Check permissions.\n:eul:~AFailed to roll back manifest. Check permissions.\n\nMZX tried to write/delete a file from the working directory,\nbut could not, likely due to the user's MZX working directory\ndisallowing write access.\n\n:euc:~AFailed to change back to user directory.\n:eud:~AFailed to change into install directory.\n\nThese errors occur when MZX fails to change active directories\nduring the updating process.\n\n:eue:~AFailed to compute update manifests\n\nThis error occurs when the hash check in a manifest file fails.\nThis error can also appear if the download of the manifest file\ntimes out.\n\n:eug:~AFailed to create directories (path too long)\n:eum:~AFailed to prune directories (path too long)\n\nThis error occurs when the pathnames of the subdirectories\nincluded in or deleted by the selected update are too long.\n\n:euh:~AFailed to create TCP client socket.\n\nMZX failed to create a socket. This may be because the last one\ncreated is still in use. This can signify a major bug in\nMegaZeux; please contact the maintainer(s).\n\n:eui:~AFailed to identify applicable update version.\n\nMZX could not find an update in the branch the user selected.\nPlease check the update_branch_pin and update_host options in\nconfig.txt and make sure they are correct.\n\n:euj:~AFailed to initialize network layer.\n\nMZX's networking code is unable to load. This signifies a major\nbug in MegaZeux; please contact the maintainer(s).\n\n:eun:~ATransferred more than expected uncompressed size.\n\nThe updater has sent the user more data than needed. The update\nmay be corrupt.\n\n:euo:~AUnknown stat() error occurred\n\nA fatal error outside of the ones mentioned occurred. Please\ncontact the developer(s) if you see this error.\n\n>#MAIN.HLP:072:Table of Contents\n#SENSORSW.HLP\n:094:\n$~9Sensors - What They Are and How to Use Them\n\nSensors have two real purposes. The first is to act as save\npoints. The second is to interact with Robots as a hybrid of\nfloor and object. The first use is simple - just set the board\nto \"Save only on Sensors\", and then the player can only save\nwhen standing on a sensor.\n\nThe second use is more complex, and requires knowledge of\nRobotic. Sensors are like controllable CustomFloors (albeit\nCustomFloors that can be pushed by non-player objects). When\ncreating one, give it a name and a character, then enter the\nname of a Robot with which it will interact. This use of sensors\nis easily replicable through Robotic now, so this use of sensors\nis deprecated. However, if one wants to use sensors, this\ninformation will easily help.\n\nSensors interact with Robots using labels (messages) and SEND\ncommands, just like Robots interact with each other. Any Robot\ncan SEND a message to a sensor, but the sensor will only SEND\nmessages to the Robot stated in its settings. If you entered a\nRobot of ALL, then it will SEND messages to all Robots. Unlike\nRobots, sensors cannot send to a dynamic name; for instance, it\ncannot be set to send to \"&fest&ive\" (whereas a Robot would\nproperly send to \"1ive\" when \"fest\" is 1 and to \"5ive\" when\n\"fest\" is 5).\n\n$Messages TO Sensors\n\nThe following messages can be sent to a sensor. A sensor can\nreceive messages even when it is beneath the player.\n\n~EDIE\n\nThis will cause the sensor to disappear forever.\n\n~ECHAR'X'\n\nThis will cause the sensor to change its character to X.\n\n~ECHAR###\n\nThis will cause the sensor to change its character to that\nrepresented by the number ###, from 0 to 255.\n\n~ECOLORxx\n\nThis will cause the sensor to change its color to the color\nrepresented by the code xx, a hexadecimal number from 00 to\nFF. The color coding is the same as used for Robotic commands,\nexcept without the \"c\" character. ?s are not allowed.\n\n~EN\n~ES\n~EE\n~EW\n\nThis will cause the sensor to move north, south, east, or west,\nrespectively. If the player is on top of the sensor, the player\nwill move along with it. If the sensor tries to move towards\nthe player, it will instead move beneath. If something pushable\nis in the direction of movement, the sensor will push it while\nmoving if there is room.\n\n$Messages FROM Sensors\n\nSensors will send Robots the following messages.\n\n~ESENSORON\n\nThis is sent when the player steps onto the sensor, when the\nsensor is told to move and it ends up beneath the player, or\nwhen something pushes the player onto the sensor.\n\n~ESENSORTHUD\n\nThis is sent when the sensor is told to move, but it is blocked.\n\n~ESENSORPUSHED\n\nThis is sent when something pushes the sensor.\n\n$Notes on Sensors\n\nOnly the player can step onto a sensor; other things will push\nit. The sensor, when moving, will move UNDER the player if in\nthe way, or will take the player with it if the player is on\nthe sensor. Laying a bomb while on a sensor will destroy the\nsensor.\n\n>#MAIN.HLP:072:Table of Contents\n#ROBOTICR.HLP\n:087:\n$~9Robotic Reference Manual\n\nUse the following help links to see your desired topic(s).\n\n$~C1) The Basics\n>#ROBOTSWH.HLP:1st:Robots - What They Are and How to Use Them\n>#ROBOTICT.HLP:1st:Robotic Tutorial\n\n$~C2) Command List and Core Concepts\n>#COMMANDR.HLP:1st:Command Reference\n>#COMMANDS.HLP:1st:Command Syntax\n>#USINGTHE.HLP:1st:Using the Editor\n>#THEGLOBL.HLP:gbl:The Global\n>#COMMANDS.HLP:dir:Directions\n>#COMMANDS.HLP:col:Colors\n>#BUILTINL.HLP:1st:Built-in Labels\n>#COUNTERS.HLP:1st:Counters, Built-in Counters and Local Counters\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n>#COMMANDS.HLP:con:Conditions\n>#SOUNDEFX.HLP:1st:Sound and Music\n>#BULLETTY.HLP:1st:Bullet Types\n>#BADPRACT.HLP:bad:Robotic Usages That Should be Avoided\n\n$~C3) Extended Concepts\n>#PROCESS.HLP:prc:Cycles and Board Scans - How MZX Processes Robots\n>#SPRITES.HLP:spr:Sprites\n>#EXPRESS.HLP:exp:Expressions\n>#SUBROUTE.HLP:sub:Subroutines\n>#VLAYER.HLP:vla:The Vlayer\n>#SMZXMODE.HLP:095:Super MZX Modes\n>#FILEACSS.HLP:fil:File Access\n>#MZM.HLP:mzm:Using MZMs\n>#TRIG.HLP:tri:Trigonometric Functions\n>#PARTIAL.HLP:par:Partial Character Sets\n>#CHANGECH.HLP:1st:CHANGE CHAR ID - The CHAR ID Table\n\n$~C4) Legacy Coding Tools\n>#COMMAND2.HLP:pre:Prefixes\n>#SENSORSW.HLP:094:Sensors\n\n>#MAIN.HLP:072:Table of Contents\n#COMMANDS.HLP\n:1st:\n$~9Command Syntax\n\nCommands in Robotic must conform to a certain syntax:\n\n  COMMAND [parameter] [parameter] ...\n\nCOMMAND is the words or symbol that specifies exactly which\ncommand you are using. Parameters are the values used to\nsupplement the command's function. This help section describes\nand details the various parameters that MegaZeux commands can\ntake.\n\n~E\"string\" or \"counter\" or \"label\" or \"Robot\" or \"file\"\n\nStrings are a series of characters surrounded by quotes.\nCounters are strings representing the name of a counter. Labels\nare strings representing the name of a label, a point within a\nRobot. Robots are strings representing the name of a Robot.\nFiles are strings representing a file or folder on disk, always\nincluding the extension in the case of files. (Some string\nexamples: \"Hi\", \"Gems\", \"Label5\", \"*1230 +725\", \"robott.txt\",\netc.)\n\nThe quotes can be left off if the string contains no spaces and\nif the string is not the same as any word found in any command\nor parameter, i.e. is a word that the editor could not mistake\nfor a part of a command. MegaZeux will add the quotes for you if\nthe string meets these criteria. For example, \"fifty\" and \"HONK\"\nwill be auto-completed by MZX, but \"Ammo\", \"N\", \"Goto\", and most\nnotably \"loop\" have to have the quotes typed in manually.\n\n~E#\n\nNumbers are allowed to be integers within the range of\n-2147483648 to 2147484647. Numbers can be replaced with a string\nrepresenting the name of a counter at any time. Examples of\nlegal numbers: 3200, \"Stuffs\", -19043, \"Ammo\". They can also be\nrepresented in hexadecimal, using $xxxx format. In this case,\nxxxx can be any number from 0 to FFFF.\n\nAny arithmatic that causes a number to go out of its range will\nnot cap the result at the limit, but will instead cause a number\nwraparound.\n\nNumbers can not directly be allowed to go past the 16-bit\nlimits (-32768 to 32767) in Robotic commands, but this can be\neasily circumvented by using constant expressions. Constant\nexpressions also allow larger hexadecimal numbers and octal\nnumbers; please view the expressions section for more detail.\n\nCertain commands will limit numbers to the range of 0 to 255.\n\n>#EXPRESS.HLP:exp:Expressions\n\n:col:~E[color]\n\nThe format for colors is cXX, where X is 0-9, A-F, or ?. The\nfirst X represents the background color; the second X represents\nthe foreground color. The numbers and symbols represent the\nfollowing colors, by default:\n\n0 Black       ~0(color #0)\n1 Blue        ~1(color #1)\n2 Green       ~2(color #2)\n3 Cyan        ~3(color #3)\n4 Red         ~4(color #4)\n5 Purple      ~5(color #5)\n6 Brown       ~6(color #6)\n7 Lt. Gray    ~7(color #7)\n8 Dk. Gray    ~8@F(color #8)\n9 Lt. Blue    ~9(color #9)\n10 Lt. Green  ~A(color #10)\n11 Lt. Cyan   ~B(color #11)\n12 Lt. Red    ~C(color #12)\n13 Lt. Purple ~D(color #13)\n14 Yellow     ~E(color #14)\n15 White      ~F(color #15)\n? Any color/No change in color\n\nThe UI uses its own set of protected colors to prevent general\ncolor edits from harming usability.\n\nThe use of ? is not always a logical option for some commands.\nWhen used, it signifies to replace it with the current color or\nignore that part of the color for that command (or if used in\nIF statements, signifies to accept any value in that field).\nColors may be selected from a menu within the Robot editor\nusing F2. Colors can also be replaced with the name of a\ncounter at any time. In this case, the value of the counter is\nBK*16+FG, where BK and FG are 0-15. To use ?, use the following\nvalues:\n\n   256+FG = c?X\n   272+BK = cX?\n   288    = c??\n\nA color of c?? will be inserted into commands when you do not\nput down anything for the color. Examples of legal colors: cF9,\nc02, c?5, c??, \"color_counter\".\n\nWhile detecting colors of ?? is generally an accepted practice,\nit's extremely discouraged to put colors of ?? or change to\nthem, with the notable exception of placing Sprites.\n\n>#BADPRACT.HLP:bad:Robotic Usages That Should be Avoided\n\n~E[char]\n\nCharacters (\"chars\" for short) are single characters surrounded\nby apostrophes, e.g. 'X'. X can be any character in the main\ncharacter set. A character can be selected from a menu in the\nRobot editor using F3. Numbers and counters can be used to\nrepresent characters as well. Number values range from 0 to 255\n(other values will wrap around to be 0-255). Examples of legal\ncharacters - 'a', '', 20, \"Count\". Characters can also\nrepresent numbers, so typing in commands like INC \"health\" '5'\nwill most likely yield _much_ different results than what was\nlikely intended. However, for some usages this use of chars is\nvastly superior to any other method (such as when reading from\nthe overlay onto strings).\n\nCertain characters must be inputted in specific ways to avoid\nproblems with Robotic:\n\n  \\0 for character 0\n  \\t for tab (character 9)\n  \\n for newline (character 10)\n  \\r for carriage return (character 13)\n  \\\" for quotation mark\n  \\\\ for backslash\n\nMZX handles these conversions for you when using F3 to select\na character from the current character set.\n\nThe commands that can manipulate extended char sets (CHAR EDIT,\nCOPY CHAR, SCROLL CHAR, FLIP CHAR) will directly accept the full\n16-bit integer range. (Numbers outside of the proper range of\n0-3839 will still display as inputted, but will internally\nwrap around that range.)\n\n:dir:~E[dir]\n\nDirections are used to denote a direction on the board. A\ndirection is one of the following:\n\n  NORTH (or N or UP)\n  SOUTH (or S or DOWN)\n  EAST (or E or RIGHT)\n  WEST (or W or LEFT)\n  IDLE\n  NODIR\n  ANYDIR\n  RANDNS\n  RANDEW\n  RANDNE\n  RANDNB\n  RANDB\n  SEEK\n  FLOW\n  RANDANY\n  UNDER (or BENEATH)\n\nThe four cardinal directions (N, S, E, W) are self-explanatory.\nBelow are descriptions of the other directions.\n\n~BIDLE\n\nNo direction, as in the absence of any direction. Used with:\n\n>#COMMANDR.HLP:_w5:WALK [dir]\n\n~BNODIR\n\nFor condition checks only. If no direction matches the\ncondition, the condition is met.\n\n>#COMMANDS.HLP:con:Conditions\n\n~BANYDIR\n\nFor condition checks only. If any direction matches the\ncondition, the condition is met.\n\n>#COMMANDS.HLP:con:Conditions\n\n~BRANDNS\n\nRandomly either NORTH or SOUTH.\n\n~BRANDEW\n\nRandomly either EAST or WEST.\n\n~BRANDNE\n\nRandomly either NORTH or EAST.\n\n~BRANDNB\n\nRandomly any direction where the Robot is not blocked by\nsomething.\n\n~BRANDB\n\nRandomly any direction where the Robot is blocked.\n\n~BSEEK\n\nThe direction closest to the player. If the player is on a\ndiagonal and is equally far away from the Robot vertically and\nhorizontally, the direction will randomly be one of the two\ndirections comprising the diagonal.\n\n~BFLOW\n\nThe direction that the Robot is currently walking.\n\n~BRANDANY\n\nRandomly one of NORTH, SOUTH, EAST, or WEST.\n\n~BUNDER\n\nThe direction signifying whatever is BENEATH something, such\nas floors. Often used with:\n\n>#COMMAND2.HLP:_l1:LAYBOMB [dir]\n>#COMMAND2.HLP:_l2:LAYBOMB HIGH [dir]\n\nMost directions can be used with the following modifying\nprefixes:\n\n~BOPP\n\nThe opposite direction. NORTH becomes SOUTH, etc.\n\n~BCW\n\nThe direction clockwise of the named direction. NORTH becomes\nEAST, etc.\n\n~BRANDP\n\nRandomly a direction perpendicular to the named direction.\nNORTH becomes EAST or WEST, etc.\n\n~BRANDNOT\n\nRandomly any direction OTHER than the given direction.\n\n~E[thing]\n\nThe name of any object from the editor, other than the player.\nUse the name from the object list, minus any punctuation or\nspaces. Examples of legal things: Gem, CustomFloor, LitBomb,\nRobot.\n\nThere are some special cases:\n * OpenGate will place an already-open gate. The parameter field\n  determines how long it remains open (ranging from p00 for 1\n  cycle, up to pff for 256 cycles).\n * Lazer will place a single piece of lazer wall. The parameter\n  field determines both initial lazer character and duration.\n * Sprite and Sprite_Colliding relate to sprites, while\n  Image_File relates to MZMs. These are covered in their own\n  sections.\n\n~E[param]\n\nA code representing the settings for a thing. (see above) The\nformat is p# or p??. # is a hexadecimal code from 0 to FF. ??\nrepresents settings that you don't care about or shouldn't\nchange. To enter parameter codes easily, use F4 in the Robot\neditor after you have typed in an object name. p?? will be\ninserted automatically any time you have a thing without a\nparameter. Counters can also be used as parameters, as can\ndecimal numbers; to insert decimal numbers, place the number\nin the proper location, but without the \"p\" before it. A value\nof 256 represents p??.\n\np?? in an IF command will allow that command to accept all\nparams.\n\n~E[item]\n\nOne of the following: TIME, SCORE, GEMS, AMMOS, LIVES, LOBOMBS,\nHIBOMBS, COINS, HEALTHS. Note the plural format; this\ndistinguishes items from things.\n\n~E!<>=\n\nA conditional for comparing two numbers, counters or strings:\n\n= or ==         Equal to\n.<               Less than\n.>               Greater than\n.<= or =<        Less than or equal to\n.>= or =>        Greater than or equal to\n!= or <> or ><  Not equal to\n\n:con:~E[condition]\n\nA word, sometimes followed by a direction, signifying a certain\ncondition. Use in IF [condition] and IF NOT [condition] commands\nto test whether a condition is currently present.\n\n~BWALKING [dir]\n\nTests whether the Robot is currently walking in a given\ndirection. IDLE, NODIR, and ANYDIR are allowed here.\n\n~BSWIMMING\n\nTests whether the Robot is currently in water.\n\n~BFIREWALKING\n\nTests whether the Robot is currently in either lava or fire.\n\n~BTOUCHING [dir]\n\nTests whether the player is next to the Robot in the given\ndirection. NODIR and ANYDIR are allowed here.\n\n~BBLOCKED [dir]\n\nTests whether the Robot is blocked by something in the given\ndirection. NODIR and ANYDIR are allowed here. This condition\nhas a special mode: if the IF command has a REL PLAYER prefix\nbefore it, it will instead check next to the PLAYER for being\nblocked. If the IF command has a REL COUNTERS prefix before\nit, it will instead check next to the position pointed to by\nthe counters XPOS and YPOS.\n\n>#COMMAND2.HLP:pre:REL COUNTERS\n>#COMMAND2.HLP:_r2:REL PLAYER\n\n~BALIGNED\n\nTests whether the player is aligned with the Robot either\nhorizontally or vertically.\n\n~BALIGNEDNS\n~BALIGNEDEW\n\nTests whether the player is aligned with the Robot, but only\ntests on the horizontal (EW) or vertical (NS) axis, not both\nat once.\n\n~BLASTSHOT [dir]\n\nTests whether the last direction the Robot was shot in was\nthe given direction. For example, if the Robot had just been\nshot on the north side, then LASTSHOT NORTH would be true.\nAll bullet types are valid for this condition. NODIR/ANYDIR\nare not valid for this condition.\n\n~BLASTTOUCH [dir]\n\nSimilar to the above; tests whether the last direction the\nRobot was touched by the player is the given direction.\nNODIR/ANYDIR are not valid for this condition.\n\n~BRIGHTPRESSED\n~BLEFTPRESSED\n~BUPPRESSED\n~BDOWNPRESSED\n~BSPACEPRESSED\n~BDELPRESSED\n\nTests whether the indicated key is currently being held down.\n\n~BMUSICON\n\nTests whether digitized music and sound effects are currently\non.\n\n~BPCSFXON\n\nTests whether PC speaker sound effects are currently on.\n\n~EMiscellaneous\n\nWhen typing in a command, the following symbols and words, as\nwell as spaces, can be used freely. They have no effect on the\nfinal command and are only used to clarify its meaning.\n\n, (comma)\n; (semicolon)\nA\nAN\nAND\nAS\nAT\nBY\nELSE\nFOR\nFROM\nINTO\nIS\nOF\nTHE\nTHEN\nTHERE\nTHROUGH\nTHRU\nTO\nWITH\n\nThese will disappear from the line if \"disassemble_extras\" is\nset to 0 in the config file. See the config file for further\ndetails.\n\n>#CONFGINI.HLP:1st:The Config File\n>#MAIN.HLP:072:Table of Contents\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n#STRINGS.HLP\n:1st:Strings, Special Formatting, and Their Place in Robotic\n\n$Simple Strings\n\nGenerally, a string is a series of symbols within quotes, such\nas: \"BUG\", \"Hi there!\", and even \"ŁEA9 _\u000f\" or \"\". There are\ntwo special aspects or features of strings that may be useful:\ncolor coding, and counter interpolation.\n\n$Color Codes\n\nUsed in Robotic commands *, %, ?, and &, color codes are used to\nplace colors in strings. There are two color symbols: ~~ for\nchanging foreground, and @@ for changing background. These should\nbe followed by one of these characters:\n\n    0 Black (color 0)        8 Dk. Gray (color 8)\n    1 Blue (color 1)         9 Lt. Blue (color 9)\n    2 Green (color 2)        A Lt. Green (color 10)\n    3 Cyan (color 3)         B Lt. Cyan (color 11)\n    4 Red (color 4)          C Lt. Red (color 12)\n    5 Purple (color 5)       D Lt. Purple (color 13)\n    6 Brown (color 6)        E Yellow (color 14)\n    7 Lt. Gray (color 7)     F White (color 15)\n\nTo show a ~~, use ~~~~. To show a @@, use @@@@.\n\n$Counter Interpolation\n\nStrings can show the values of counters by surrounding the\ncounter name with &s. This is called counter interpolation. For\nexample, if the player has 55 gems, \"You have &GEMS& gems.\" will\nbecome \"You have 55 gems.\" Use && to show a &. This feature is\navailable in nearly ALL Robotic commands that use strings,\nincluding in the names of OTHER counters. (The sole exceptions\nare in label names and in strings for the PLAY commands.) This\ncan be used to simulate array-like constructs.\n\n$Robotic Usage of Strings\n\nIn Robotic, strings can be incredibly powerful. They primarily\nhold text information, such as names. (They can hold a single\nnumber like counters can, but counters are by far better-suited\nfor number use). A Robotic string is any counter prefixed by a $\nsign (e.g. \"$string\").\n\nCertain characters must be inputted in specific ways to avoid\nproblems with Robotic:\n\n  \\0 for character 0 (this won't be parsed correctly in strings)\n  \\t for tab (character 9)\n  \\n for newline (character 10)\n  \\r for carriage return (character 13)\n  \\\" for quotation mark\n  \\\\ for backslash\n\n$Robotic Usage - Setting Strings\n\n~BSET \"$string\" to \"text\"\n\nSets the contents of the given string to the given line of text.\n\n~BSET \"$string\" to \"$string2\"\n\nSets the contents of the given string to the contents of another\nstring.\n\n~BSET \"$string\" to #\n\nSets the contents of the given string to an integer value.\n\n$Robotic Usage - Outputting Strings\n\nAnywhere you can output a counter, you can output a string.\nSimply encase the string name in ampersands. E.G.:\n\n~ESET \"$woohoo\" to \"My favorite song!\"\n~E* \"&$woohoo&\"\n\nWould output \"My favorite song!\" in the message line.\n\nThere are several special things you can set strings to for\ncertain functions. These are listed in the Counters section.\n\n>#COUNTERS.HLP:stc:String Counters\n\n$Robotic Usage - Comparing Strings\n\nString comparisons are case-insensitive unless stated otherwise.\nAllowed comparisons are to a line of text, to another string and\nto a number. However, the string is required to be the first\nitem, and string counters such as \"MOD_NAME\", \"INPUT\", etc. are\nnot valid comparisons.\n\nStrings can be compared through these commands:\n\n~BIF \"$string\" = \"value\" then \"label\" (equality)\n~BIF \"$string\" != \"value\" then \"label\" (inequality)\n~BIF \"$string\" > \"value\" \"label\" (greater than)\n~BIF \"$string\" < \"value\" \"label\" (less than)\n~BIF \"$string\" >= \"value\" \"label\" (greater than or equal to)\n~BIF \"$string\" <= \"value\" \"label\" (less than or equal to)\n~BIF \"$string\" === \"value\" then \"label\" (case-sensitive equality)\n\nStrings can also be compared using wildcards. There are two\nspecial wildcard characters: the ? character in the value being\ncompared against matches any one character, while the %\ncharacter matches any number of any characters (including zero).\nThe wildcard commands are:\n\n~BIF \"$string\" ?= \"value\" then \"label\"\n~BIF \"$string\" ?== \"value\" then \"label\" (case-sensitive)\n\nDue to their use as a token, actual ? and % characters must be\nescaped to be checked for in wildcard comparisons. Use \\\\? and\n\\\\% to have values that check for literal use of these\ncharacters.\n\n$Robotic Usage - Manipulating Strings\n\nFirstly, strings can be offset and limited (\"spliced\").\n\n~B$strname#X~F will cap the string to X characters in length.\n\n~B$strname+X~F will offset the start of the string by X\ncharacters. Using a negative number will offset the start of\nthe string backwards from the end of the string by X characters,\nwith -1 as the end character. (e.g. \"$str+-2\" would offset the\nstring \"str\" at two characters from its end.)\n\n~B$strname+X#Y~F will offset the string by X characters and limit\nthe string to Y characters in length.\n\nNormally string splicing is used to read a given selection of a\nstring; however, it can also be used for writing to parts of\nstrings. When you set an offset when setting a string, the\nlength of the string will not decrease, so you'll only\nmanipulate a portion of the string. For instance:\n\n~Bset \"$str\" \"hello\"\n~Bset \"$str+1\" \"LOL\"\n\nWill cause $str to have the value \"hLOLo\".\n\nWriting to parts of strings with an offset also changes how the\nstring limit works; any length cap will limit how much of the\nspliced-in material is inserted instead of limiting the string\nlength in total. For instance:\n\n~Bset \"$str\" \"Baby steps.\"\n~Bset \"$str+0#4\" \"Long journey.\"\n\nWill cause $str to have the value \"Long steps.\"\n\n~B$strname.X~F will manipulate the Xth character in a string\n(starting from 0). When used for output, it will give the\ncharacter value of the given character in a string. Any invalid\nnumbers (past the terminator) are given a value of 0. This\ncommand works well with char immediates:\nE.G. SET \"$string.0\" 'S' . When setting the character in a\nstring it will make the string length one larger than X, so be\ncareful when using very large values.\nUsing negative numbers will manipulate using the end of the\nstring as its basis: -X will manipulate the Xth character from\nthe end of the string, with -1 being the end character.\n\n~B$strname.X#Y~F will manipulate characters in the string from the\nXth character (starting from 0), up to character (X+Y-1). Y can\nbe a number from 1 to 4; all other values for Y are clamped to\nfit this range. The string given or output acts as a number 8*Y\nbits in length.\n\n~B$str.length~F will give the length of the given string.\n\nSecondly, strings can be clipped, appended or written to from\nthe board or overlay.\n\n~BINC \"$string\" \"$string2\"\n~BINC \"$string\" \"text\"\n\nThese two commands append another string's contents or text,\nrespectively, to the end of a given string.\n\n~BDEC \"$string\" #\n\nThis command clips the given number of characters from the end\nof a string. # is treated as unsigned so using negative numbers\nwill probably cause the length to go down to zero.\n\n~BCOPY BLOCK x y w h \"$string\" t\n~BCOPY OVERLAY BLOCK x y w h \"$string\" t\n\nThese commands copy information from the board or overlay,\nrespectively, to a given string. X and Y are the coordinates\nof the upper-left corner of the block; W and H are its width\nand height; T is the terminating character. For T, one can put\nin a counter with the param value of the desired terminator\ncharacter or a char immediate in the form 'c' (not just the\ncharacter itself). If \"t\" is 0 there will be no terminator.\nThe letters are added from left to right and up to down until\nthe terminator character is reached, or until the end of the\ngiven block is reached. If x and y begin with a pound sign (#),\nthe string will instead be read from the vlayer.\nPlease use this method of scanning strings instead of\nBOARD_SCAN.\n\n$Robotic Usage - File Access\n\nFinally, strings can be used for loading and saving several MZX\nfile formats. The following formats can be loaded from a string:\n\n-Palettes with ~BLOAD PALETTE \"$string\"\n-Charsets with ~BLOAD CHAR SET \"$string\"\n-Robots with ~BSET \"$string\" \"LOAD_ROBOT\"\n-MZMs with ~BPUT \"@@$string\" image_file pNN x y\n\nRobots can be saved into a string with this command:\n~BSET \"$string\" \"SAVE_ROBOT\"~F\n\nMZMs can be saved into a string via these methods:\n\n~BCOPY BLOCK x y w h \"@@$string\" p\n~BCOPY OVERLAY BLOCK x y w h \"@@$string\" p\n~BCOPY (OVERLAY) BLOCK \"#x\" \"#y\" w h \"@@$string\" m\n\nString splicing is valid for string file operations.\n\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#COMMANDR.HLP\n:1st:\n$~9Command Reference\n\nThe following tables are lists of all Robotic commands in\nMegaZeux. The commands are listed as help links and will jump\nto that specific command. After the tables are the actual\ncommand descriptions. Note that because of its size, the command\nreference is split into two parts.\n\nAny command that (typically) ends a cycle will be marked by a\n[~D!~F] symbol. For more precise information, view\ncycles_and_commands.txt in the documentation.\n\nThe command listing is arranged by alphabetical order and by\ncommand type. Select the respective links to jump to that\narrangement.\n\n>tcl:Type-Based Command Listing\n>acl:Alphabetical Command Listing\n:tcl:\n$~9Type-Based Command Listing\n\n>tc0:Flow and Branching Commands\n>tc1:General Color and Character Commands\n>tc2:Item Color and Character Commands\n>tc3:Counter and String Manipulation Commands\n>tc4:Music and Sound Commands\n>tc5:Textbox and Message Row Commands\n>tc6:Board Placement and Copy Commands\n>tc7:Overlay Commands\n>tc8:View Commands\n>tc9:Other Board Commands\n>tca:Player Control and Item Commands\n>tcb:Self Movement and Attack Commands\n>tcc:Other Self-Oriented Commands\n>tcd:Miscellaneous Commands\n:tc0:\n$~CFlow and Branching Commands\n\n>__6:: \"label\"\n>_w1:WAIT # [~D!~F]\n>_e1:END [~D!~F]\n>_g5:GOTO \"label\"\n>_g6:GOTO \"#return\"\n>_g7:GOTO \"#top\"\n>_sB:SEND # # \"label\"\n>_sC:SEND \"Robot\" \"label\"\n>_sD:SEND [dir] \"label\"\n>_sE:SEND [dir] PLAYER \"label\"\n>_i1:IF \"counter\" !<>= # \"label\"\n>#COMMAND2.HLP:_i2:IF [condition] \"label\"\n>#COMMAND2.HLP:_iB:IF NOT [condition] \"label\"\n>#COMMAND2.HLP:_i3:IF # # \"label\"\n>#COMMAND2.HLP:_iC:IF PLAYER # # \"label\"\n>#COMMAND2.HLP:_i7:IF ALIGNEDROBOT \"Robot\" \"label\"\n>#COMMAND2.HLP:_i5:IF [color] [thing] [param] # # \"label\"\n>#COMMAND2.HLP:_i6:IF [color] [thing] [param] [dir] \"label\"\n>#COMMAND2.HLP:_i4:IF [dir] PLAYER [color] [thing] [param] \"label\"\n>#COMMAND2.HLP:_iA:IF NOT [color] [thing] [param] [dir] \"label\"\n>#COMMAND2.HLP:_i8:IF ANY [color] [thing] [param] \"label\"\n>#COMMAND2.HLP:_i0:IF NO [color] [thing] [param] \"label\"\n>#COMMAND2.HLP:_iO:IF \"$string\" (equality) # \"label\"\n>#COMMAND2.HLP:_iK:IF \"$string\" (equality) \"text\" \"label\"\n>#COMMAND2.HLP:_iL:IF \"$string\" (equality) \"$string2\" \"label\"\n>#COMMAND2.HLP:_iG:IF cNN Sprite_Colliding pNN # # \"label\"\n>#COMMAND2.HLP:_iH:IF c?? Sprite pNN # # \"label\"\n>#COMMAND2.HLP:_iD:IF STRING \"string\" \"label\"\n>#COMMAND2.HLP:_iE:IF STRING MATCHES \"string\" \"label\"\n>#COMMAND2.HLP:_iF:IF STRING NOT \"string\" \"label\"\n>#COMMAND2.HLP:_i9:IF FIRST STRING \"string\" \"label\"\n>_lA:LOCKSELF\n>_u3:UNLOCKSELF\n>#COMMAND2.HLP:_z1:ZAP \"label\" #\n>#COMMAND2.HLP:_rA:RESTORE \"label\" #\n>#COMMAND2.HLP:__0:| \"label\"\n>_lB:LOOP #\n>_lC:LOOP START\n>_a1:ABORT LOOP\n>#COMMAND2.HLP:_a2:ASK \"string\"\n>_cP:CYCLE # [~D!~F]\n>#COMMAND2.HLP:_w2:WAIT MOD FADE [~D!~F]\n>#COMMAND2.HLP:_w3:WAIT PLAY [~D!~F]\n:tc1:\n$~CGeneral Color and Character Commands\n\n>#COMMAND2.HLP:_l5:LOAD PALETTE \"file\"\n>#COMMAND2.HLP:_l4:LOAD CHAR SET \"file\"\n>#COMMAND2.HLP:_sK:SET COLOR # # # #\n>#COMMAND2.HLP:_c0:CHAR EDIT [char] # # # # # # # # # # # # # #\n>#COMMAND2.HLP:_cK:COPY CHAR [char] [char]\n>#COMMAND2.HLP:_s4:SCROLL CHAR [char] [dir]\n>#COMMAND2.HLP:_f3:FLIP CHAR [char] [dir]\n>#COMMAND2.HLP:_cF:COLOR INTENSITY # PERCENT\n>#COMMAND2.HLP:_cG:COLOR INTENSITY # # PERCENT\n>_cD:COLOR FADE OUT\n>_cE:COLOR FADE IN\n:tc2:\n$~CItem Color and Character Commands\n\n>_p0:PLAYER CHAR [char]\n>_p9:PLAYER CHAR [dir] [char]\n>_pA:PLAYERCOLOR [color]\n>#COMMAND2.HLP:_c3:CHANGE CHAR ID # [char]\n>#COMMAND2.HLP:_b9:BULLETCOLOR [color]\n>#COMMAND2.HLP:_b0:BULLETE [char]\n>#COMMAND2.HLP:_bA:BULLETN [char]\n>#COMMAND2.HLP:_bB:BULLETS [char]\n>#COMMAND2.HLP:_bC:BULLETW [char]\n>#COMMAND2.HLP:_p4:PLAYER BULLETCOLOR [color]\n>#COMMAND2.HLP:_p5:PLAYER BULLETE [char]\n>#COMMAND2.HLP:_p6:PLAYER BULLETN [char]\n>#COMMAND2.HLP:_p7:PLAYER BULLETS [char]\n>#COMMAND2.HLP:_p8:PLAYER BULLETW [char]\n>#COMMAND2.HLP:_n1:NEUTRAL BULLETCOLOR [color]\n>#COMMAND2.HLP:_n2:NEUTRAL BULLETE [char]\n>#COMMAND2.HLP:_n3:NEUTRAL BULLETN [char]\n>#COMMAND2.HLP:_n4:NEUTRAL BULLETS [char]\n>#COMMAND2.HLP:_n5:NEUTRAL BULLETW [char]\n>#COMMAND2.HLP:_e0:ENEMY BULLETCOLOR [color]\n>#COMMAND2.HLP:_eA:ENEMY BULLETE [char]\n>#COMMAND2.HLP:_eB:ENEMY BULLETN [char]\n>#COMMAND2.HLP:_eC:ENEMY BULLETS [char]\n>#COMMAND2.HLP:_eD:ENEMY BULLETW [char]\n>#COMMAND2.HLP:_m2:MISSILECOLOR [color]\n>#COMMAND2.HLP:_c7:CHANGE THICK ARROW CHAR [dir] [char]\n>#COMMAND2.HLP:_c8:CHANGE THIN ARROW CHAR [dir] [char]\n:tc3:\n$~CCounter and String Manipulation Commands\n\n>_sF:SET \"counter\" #\n>_sG:SET \"counter\" RANDOM # #\n>_iI:INC \"counter\" #\n>_iJ:INC \"counter\" RANDOM # #\n>_d1:DEC \"counter\" #\n>_d2:DEC \"counter\" RANDOM # #\n>_mC:MULTIPLY \"counter\" #\n>_d7:DIVIDE \"counter\" #\n>_m9:MODULO \"counter\" #\n>_d8:DOUBLE \"counter\"\n>_h1:HALF \"counter\"\n>_sH:SET \"$string\" \"text\"\n>_sI:SET \"$string1\" \"$string2\"\n>_sJ:SET \"$string\" #\n>#COMMAND2.HLP:_iM:INC \"$string\" \"text\"\n>#COMMAND2.HLP:_iN:INC \"$string\" \"$string2\"\n>#COMMAND2.HLP:_dA:DEC \"$string\" #\n>#COMMAND2.HLP:_iP:INPUT STRING \"string\"\n>#COMMAND2.HLP:_cB:CLIP INPUT\n:tc4:\n$~CMusic and Sound Commands\n\n>_m3:MOD \"file\"\n>_m8:MOD \"*\"\n>_v3:VOLUME #\n>#COMMAND2.HLP:_m4:MOD FADE # #\n>#COMMAND2.HLP:_m5:MOD FADE IN \"file\"\n>#COMMAND2.HLP:_m6:MOD FADE OUT\n>#COMMAND2.HLP:_j1:JUMP MOD ORDER #\n>_s1:SAM # \"file\"\n>_p2:PLAY \"string\"\n>_p3:PLAY SFX \"string\"\n>_w4:WAIT PLAY \"string\" [~D!~F]\n>_sO:SFX #\n>#COMMAND2.HLP:_c6:CHANGE SFX # \"string\"\n>_e2:END MOD\n>_e3:END PLAY\n>_e4:END SAM\n>#COMMAND2.HLP:_m7:MOD SAM # #\n:tc5:\n$~CTextbox and Message Row Commands\n\n>__9:[ \"string\"\n>__1:% \"string\"\n>__2:& \"string\"\n>__8:? \"label\" \"string\"\n>__7:? \"counter\" \"label\" \"string\"\n>__3:* \"string\"\n>#COMMAND2.HLP:_m1:MESSAGE ROW #\n>#COMMAND2.HLP:_sN:SET MESG COLUMN #\n>#COMMAND2.HLP:_c1:CENTER MESG\n>#COMMAND2.HLP:_cA:CLEAR MESG\n>#COMMAND2.HLP:_e7:ENABLE MESG EDGE\n>#COMMAND2.HLP:_d5:DISABLE MESG EDGE\n>#COMMAND2.HLP:_s5:SCROLLARROW COLOR [color]\n>#COMMAND2.HLP:_s6:SCROLLBASE COLOR [color]\n>#COMMAND2.HLP:_s7:SCROLLCORNER COLOR [color]\n>#COMMAND2.HLP:_s8:SCROLLPOINTER COLOR [color]\n>#COMMAND2.HLP:_s9:SCROLLTITLE COLOR [color]\n>#COMMAND2.HLP:_a2:ASK \"string\"\n>#COMMAND2.HLP:_iP:INPUT STRING \"string\"\n:tc6:\n$~CBoard Placement and Copy Commands\n\n>_pC:PUT [color] [thing] [param] # #\n>_pD:PUT [color] [thing] [param] [dir]\n>_pF:PUT [color] [thing] [param] [dir] PLAYER\n>_pI:PUT \"@@FILENAME.XXX\" Image_File [param] # #\n>_pJ:PUT [color] Sprite [param] # #\n>#COMMAND2.HLP:_cH:COPY # # # #\n>#COMMAND2.HLP:_cI:COPY [dir] [dir]\n>#COMMAND2.HLP:_cJ:COPY BLOCK # # # # # #\n>#COMMAND2.HLP:_cQ:COPY BLOCK # # # # \"@@filename\" #\n>#COMMAND2.HLP:_cS:COPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"@@filename\" #\n>#COMMAND2.HLP:_cT:COPY BLOCK # # # # \"$string\" #\n>#COMMAND2.HLP:_cV:COPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"$string\" #\n>#COMMAND2.HLP:_cW:COPY BLOCK # # # # \"#x\" \"#y\"\n>#COMMAND2.HLP:_cY:COPY BLOCK \"#x\" \"#y\" # # # #\n>#COMMAND2.HLP:_cAA:COPY (OVERLAY) BLOCK \"#x1\" \"#y1\" # # \"#x2\" \"#y2\"\n:tc7:\n$~COverlay Commands\n\n>#COMMAND2.HLP:_pE:PUT [color] [char] OVERLAY # #\n>#COMMAND2.HLP:_w7:WRITE OVERLAY [color] \"string\" # #\n>#COMMAND2.HLP:_cL:COPY OVERLAY BLOCK # # # # # #\n>#COMMAND2.HLP:_cR:COPY OVERLAY BLOCK # # # # \"@@filename\" #\n>#COMMAND2.HLP:_cU:COPY OVERLAY BLOCK # # # # \"$string\" #\n>#COMMAND2.HLP:_cX:COPY OVERLAY BLOCK # # # # \"#x\" \"#y\"\n>#COMMAND2.HLP:_cZ:COPY OVERLAY BLOCK \"#x\" \"#y\" # # # #\n>#COMMAND2.HLP:_c5:CHANGE OVERLAY [color] [color]\n>#COMMAND2.HLP:_c4:CHANGE OVERLAY [color] [char] [color] [char]\n>#COMMAND2.HLP:_o2:OVERLAY ON\n>#COMMAND2.HLP:_o3:OVERLAY STATIC\n>#COMMAND2.HLP:_o4:OVERLAY TRANSPARENT\n:tc8:\n$~CView Commands\n\n>#COMMAND2.HLP:_v1:VIEWPORT # #\n>#COMMAND2.HLP:_v2:VIEWPORT SIZE # #\n>#COMMAND2.HLP:_s0:SCROLLVIEW [dir] #\n>#COMMAND2.HLP:_sA:SCROLLVIEW POSITION # #\n>#COMMAND2.HLP:_r0:RESETVIEW\n>#COMMAND2.HLP:_l0:LOCKSCROLL\n>#COMMAND2.HLP:_u2:UNLOCKSCROLL\n>#COMMAND2.HLP:_sL:SET EDGE COLOR [color]\n:tc9:\n$~COther Board Commands\n\n>_c2:CHANGE [color] [thing] [param] [color] [thing] [param]\n>#COMMAND2.HLP:_m0:MOVE ALL [color] [thing] [param] [dir] [~D!~F]\n>#COMMAND2.HLP:_b7:BOARD [dir] \"string\"\n>#COMMAND2.HLP:_b8:BOARD [dir] NONE\n>#COMMAND2.HLP:_a3:AVALANCHE\n>#COMMAND2.HLP:_d6:DISABLE SAVING\n>#COMMAND2.HLP:_e8:ENABLE SAVING\n>#COMMAND2.HLP:_e9:ENABLE SENSORONLY SAVING\n:tca:\n$~CPlayer Control and Item Commands\n\n>#COMMAND2.HLP:_g1:GIVE # [item]\n>#COMMAND2.HLP:_t1:TAKE # [item]\n>#COMMAND2.HLP:_t2:TAKE # [item] \"label\"\n>#COMMAND2.HLP:_g2:GIVEKEY [color]\n>#COMMAND2.HLP:_g3:GIVEKEY [color] \"label\"\n>#COMMAND2.HLP:_t3:TAKEKEY [color]\n>#COMMAND2.HLP:_t4:TAKEKEY [color] \"label\"\n>#COMMAND2.HLP:_t6:TRADE # [item] # [item] \"label\"\n>#COMMAND2.HLP:_l6:LOCKPLAYER\n>#COMMAND2.HLP:_l7:LOCKPLAYER ATTACK\n>#COMMAND2.HLP:_l8:LOCKPLAYER EW\n>#COMMAND2.HLP:_l9:LOCKPLAYER NS\n>#COMMAND2.HLP:_u1:UNLOCKPLAYER\n>#COMMAND2.HLP:_t5:TELEPORT PLAYER \"string\" # # [~D!~F]\n>_mA:MOVE PLAYER [dir] [~D!~F]\n>_mB:MOVE PLAYER [dir] \"label\" [~D!~F]\n>_pG:PUT PLAYER # # [~D!~F]\n>_pH:PUT PLAYER [dir] [~D!~F]\n>_e6:ENDLIFE\n>_e5:ENDGAME\n>#COMMAND2.HLP:_f2:FILLHEALTH\n>#COMMAND2.HLP:_sM:SET MAXHEALTH #\n>#COMMAND2.HLP:_s2:SAVE PLAYER POSITION\n>#COMMAND2.HLP:_s3:SAVE PLAYER POSITION #\n>#COMMAND2.HLP:_rB:RESTORE PLAYER POSITION [~D!~F]\n>#COMMAND2.HLP:_rC:RESTORE PLAYER POSITION # [~D!~F]\n>#COMMAND2.HLP:_rD:RESTORE PLAYER POSITION # DUPLICATE SELF [~D!~F]\n>#COMMAND2.HLP:_eE:EXCHANGE PLAYER POSITION [~D!~F]\n>#COMMAND2.HLP:_eF:EXCHANGE PLAYER POSITION # [~D!~F]\n>#COMMAND2.HLP:_eG:EXCHANGE PLAYER POSITION # DUPLICATE SELF [~D!~F]\n>#COMMAND2.HLP:_b6:BLIND #\n>#COMMAND2.HLP:_f1:FIREWALKER #\n>#COMMAND2.HLP:_w6:WIND #\n:tcb:\n$~CSelf Movement and Attack Commands\n\n>__5:/ \"string\" [~D!~F]\n>_w5:WALK [dir]\n>_g4:GO [dir] # [~D!~F]\n>_g8:GOTOXY # # [~D!~F]\n>_p1:PERSISTENT GO \"string\" [~D!~F]\n>_t7:TRY [dir] \"label\" [~D!~F]\n>_sP:SHOOT [dir]\n>_sT:SPITFIRE [dir]\n>_sQ:SHOOTMISSILE [dir]\n>_sR:SHOOTSEEKER [dir]\n>#COMMAND2.HLP:_l1:LAYBOMB [dir]\n>#COMMAND2.HLP:_l2:LAYBOMB HIGH [dir]\n>#COMMAND2.HLP:_l3:LAZERWALL [dir] #\n:tcc:\n$~COther Self-Oriented Commands\n\n>_c9:CHAR [char]\n>_cC:COLOR [color]\n>_d3:DIE\n>_d4:DIE ITEM\n>_eH:EXPLODE #\n>_d9:DUPLICATE SELF # #\n>_d0:DUPLICATE SELF [dir]\n>_cM:COPYROBOT \"Robot\" [~D!~F]\n>_cN:COPYROBOT # # [~D!~F]\n>_cO:COPYROBOT [dir] [~D!~F]\n>_4b:. \"@@string\"\n>#COMMAND2.HLP:_pB:PUSH [dir]\n>#COMMAND2.HLP:_rE:ROTATECW\n>#COMMAND2.HLP:_rF:ROTATECCW\n>#COMMAND2.HLP:_sW:SWITCH [dir] [dir]\n>#COMMAND2.HLP:_o1:OPEN [dir]\n>#COMMAND2.HLP:_b2:BECOME NONLAVAWALKER\n>#COMMAND2.HLP:_b3:BECOME NONPUSHABLE\n>#COMMAND2.HLP:_b4:BECOME LAVAWALKER\n>#COMMAND2.HLP:_b5:BECOME PUSHABLE\n>_b1:BECOME [color] [thing] [param]\n:tcd:\n$~CMiscellaneous Commands\n\n>__4:. \"string\"\n>#COMMAND2.HLP:_sV:SWAP WORLD \"file\"\n>#COMMAND2.HLP:_c3:CHANGE CHAR ID # [char]\n>#COMMAND2.HLP:_sU:STATUS COUNTER # \"counter\"\n>#COMMAND2.HLP:_f4:FREEZETIME #\n>#COMMAND2.HLP:_sS:SLOWTIME #\n>#COMMAND2.HLP:pre:REL COUNTERS\n>#COMMAND2.HLP:_r2:REL PLAYER\n>#COMMAND2.HLP:_r3:REL SELF\n>#COMMAND2.HLP:_r4:REL COUNTERS FIRST\n>#COMMAND2.HLP:_r5:REL PLAYER FIRST\n>#COMMAND2.HLP:_r6:REL SELF FIRST\n>#COMMAND2.HLP:_r7:REL COUNTERS LAST\n>#COMMAND2.HLP:_r8:REL PLAYER LAST\n>#COMMAND2.HLP:_r9:REL SELF LAST\n:acl:\n$~9Alphabetical Command Listing\n\n>__1:% \"string\"\n>__2:& \"string\"\n>__3:* \"string\"\n>__4:. \"string\"\n>_4b:. \"@@string\"\n>__5:/ \"string\" [~D!~F]\n>__6:: \"label\"\n>__7:? \"counter\" \"label\" \"string\"\n>__8:? \"label\" \"string\"\n>__9:[ \"string\"\n>#COMMAND2.HLP:__0:| \"label\"\n>_a1:ABORT LOOP\n>#COMMAND2.HLP:_a2:ASK \"string\"\n>#COMMAND2.HLP:_a3:AVALANCHE\n>_b1:BECOME [color] [thing] [param]\n>#COMMAND2.HLP:_b2:BECOME NONLAVAWALKER\n>#COMMAND2.HLP:_b3:BECOME NONPUSHABLE\n>#COMMAND2.HLP:_b4:BECOME LAVAWALKER\n>#COMMAND2.HLP:_b5:BECOME PUSHABLE\n>#COMMAND2.HLP:_b6:BLIND #\n>#COMMAND2.HLP:_b7:BOARD [dir] \"string\"\n>#COMMAND2.HLP:_b8:BOARD [dir] NONE\n>#COMMAND2.HLP:_b9:BULLETCOLOR [color]\n>#COMMAND2.HLP:_b0:BULLETE [char]\n>#COMMAND2.HLP:_bA:BULLETN [char]\n>#COMMAND2.HLP:_bB:BULLETS [char]\n>#COMMAND2.HLP:_bC:BULLETW [char]\n>#COMMAND2.HLP:_c1:CENTER MESG\n>_c2:CHANGE [color] [thing] [param] [color] [thing] [param]\n>#COMMAND2.HLP:_c3:CHANGE CHAR ID # [char]\n>#COMMAND2.HLP:_c4:CHANGE OVERLAY [color] [char] [color] [char]\n>#COMMAND2.HLP:_c5:CHANGE OVERLAY [color] [color]\n>#COMMAND2.HLP:_c6:CHANGE SFX # \"string\"\n>#COMMAND2.HLP:_c7:CHANGE THICK ARROW CHAR [dir] [char]\n>#COMMAND2.HLP:_c8:CHANGE THIN ARROW CHAR [dir] [char]\n>_c9:CHAR [char]\n>#COMMAND2.HLP:_c0:CHAR EDIT [char] # # # # # # # # # # # # # #\n>#COMMAND2.HLP:_cA:CLEAR MESG\n>#COMMAND2.HLP:_cB:CLIP INPUT\n>_cC:COLOR [color]\n>_cD:COLOR FADE OUT\n>_cE:COLOR FADE IN\n>#COMMAND2.HLP:_cF:COLOR INTENSITY # PERCENT\n>#COMMAND2.HLP:_cG:COLOR INTENSITY # # PERCENT\n>#COMMAND2.HLP:_cH:COPY # # # #\n>#COMMAND2.HLP:_cI:COPY [dir] [dir]\n>#COMMAND2.HLP:_cJ:COPY BLOCK # # # # # #\n>#COMMAND2.HLP:_cK:COPY CHAR [char] [char]\n>#COMMAND2.HLP:_cL:COPY OVERLAY BLOCK # # # # # #\n>#COMMAND2.HLP:_cQ:COPY BLOCK # # # # \"@@filename\" #\n>#COMMAND2.HLP:_cR:COPY OVERLAY BLOCK # # # # \"@@filename\" #\n>#COMMAND2.HLP:_cS:COPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"@@filename\" #\n>#COMMAND2.HLP:_cT:COPY BLOCK # # # # \"$string\" #\n>#COMMAND2.HLP:_cU:COPY OVERLAY BLOCK # # # # \"$string\" #\n>#COMMAND2.HLP:_cV:COPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"$string\" #\n>#COMMAND2.HLP:_cW:COPY BLOCK # # # # \"#x\" \"#y\"\n>#COMMAND2.HLP:_cX:COPY OVERLAY BLOCK # # # # \"#x\" \"#y\"\n>#COMMAND2.HLP:_cY:COPY BLOCK \"#x\" \"#y\" # # # #\n>#COMMAND2.HLP:_cZ:COPY OVERLAY BLOCK \"#x\" \"#y\" # # # #\n>#COMMAND2.HLP:_cAA:COPY (OVERLAY) BLOCK \"#x1\" \"#y1\" # # \"#x2\" \"#y2\"\n>_cM:COPYROBOT \"Robot\" [~D!~F]\n>_cN:COPYROBOT # # [~D!~F]\n>_cO:COPYROBOT [dir] [~D!~F]\n>_cP:CYCLE # [~D!~F]\n>_d1:DEC \"counter\" #\n>_d2:DEC \"counter\" RANDOM # #\n>#COMMAND2.HLP:_dA:DEC \"$string\" #\n>_d3:DIE\n>_d4:DIE ITEM\n>#COMMAND2.HLP:_d5:DISABLE MESG EDGE\n>#COMMAND2.HLP:_d6:DISABLE SAVING\n>_d7:DIVIDE \"counter\" #\n>_d8:DOUBLE \"counter\"\n>_d9:DUPLICATE SELF # #\n>_d0:DUPLICATE SELF [dir]\n>_e1:END [~D!~F]\n>_e2:END MOD\n>_e3:END PLAY\n>_e4:END SAM\n>_e5:ENDGAME\n>_e6:ENDLIFE\n>#COMMAND2.HLP:_e7:ENABLE MESG EDGE\n>#COMMAND2.HLP:_e8:ENABLE SAVING\n>#COMMAND2.HLP:_e9:ENABLE SENSORONLY SAVING\n>#COMMAND2.HLP:_e0:ENEMY BULLETCOLOR [color]\n>#COMMAND2.HLP:_eA:ENEMY BULLETE [char]\n>#COMMAND2.HLP:_eB:ENEMY BULLETN [char]\n>#COMMAND2.HLP:_eC:ENEMY BULLETS [char]\n>#COMMAND2.HLP:_eD:ENEMY BULLETW [char]\n>#COMMAND2.HLP:_eE:EXCHANGE PLAYER POSITION [~D!~F]\n>#COMMAND2.HLP:_eF:EXCHANGE PLAYER POSITION # [~D!~F]\n>#COMMAND2.HLP:_eG:EXCHANGE PLAYER POSITION # DUPLICATE SELF [~D!~F]\n>_eH:EXPLODE #\n>#COMMAND2.HLP:_f1:FIREWALKER #\n>#COMMAND2.HLP:_f2:FILLHEALTH\n>#COMMAND2.HLP:_f3:FLIP CHAR [char] [dir]\n>#COMMAND2.HLP:_f4:FREEZETIME #\n>#COMMAND2.HLP:_g1:GIVE # [item]\n>#COMMAND2.HLP:_g2:GIVEKEY [color]\n>#COMMAND2.HLP:_g3:GIVEKEY [color] \"label\"\n>_g4:GO [dir] # [~D!~F]\n>_g5:GOTO \"label\"\n>_g6:GOTO \"#return\"\n>_g7:GOTO \"#top\"\n>_g8:GOTOXY # # [~D!~F]\n>_h1:HALF \"counter\"\n>_i1:IF \"counter\" !<>= # \"label\"\n>#COMMAND2.HLP:_i2:IF [condition] \"label\"\n>#COMMAND2.HLP:_i3:IF # # \"label\"\n>#COMMAND2.HLP:_i4:IF [dir] PLAYER [color] [thing] [param] \"label\"\n>#COMMAND2.HLP:_i5:IF [color] [thing] [param] # # \"label\"\n>#COMMAND2.HLP:_i6:IF [color] [thing] [param] [dir] \"label\"\n>#COMMAND2.HLP:_i7:IF ALIGNEDROBOT \"Robot\" \"label\"\n>#COMMAND2.HLP:_i8:IF ANY [color] [thing] [param] \"label\"\n>#COMMAND2.HLP:_i9:IF FIRST STRING \"string\" \"label\"\n>#COMMAND2.HLP:_i0:IF NO [color] [thing] [param] \"label\"\n>#COMMAND2.HLP:_iA:IF NOT [color] [thing] [param] [dir] \"label\"\n>#COMMAND2.HLP:_iB:IF NOT [condition] \"label\"\n>#COMMAND2.HLP:_iC:IF PLAYER # # \"label\"\n>#COMMAND2.HLP:_iD:IF STRING \"string\" \"label\"\n>#COMMAND2.HLP:_iE:IF STRING MATCHES \"string\" \"label\"\n>#COMMAND2.HLP:_iF:IF STRING NOT \"string\" \"label\"\n>#COMMAND2.HLP:_iO:IF \"$string\" (equality) # \"label\"\n>#COMMAND2.HLP:_iK:IF \"$string\" (equality) \"text\" \"label\"\n>#COMMAND2.HLP:_iL:IF \"$string\" (equality) \"$string2\" \"label\"\n>#COMMAND2.HLP:_iG:IF cNN Sprite_Colliding pNN # # \"label\"\n>#COMMAND2.HLP:_iH:IF c?? Sprite pNN # # \"label\"\n>#COMMAND2.HLP:_iM:INC \"$string\" \"text\"\n>#COMMAND2.HLP:_iN:INC \"$string\" \"$string2\"\n>_iI:INC \"counter\" #\n>_iJ:INC \"counter\" RANDOM # #\n>#COMMAND2.HLP:_iP:INPUT STRING \"string\"\n>#COMMAND2.HLP:_j1:JUMP MOD ORDER #\n>#COMMAND2.HLP:_l1:LAYBOMB [dir]\n>#COMMAND2.HLP:_l2:LAYBOMB HIGH [dir]\n>#COMMAND2.HLP:_l3:LAZERWALL [dir] #\n>#COMMAND2.HLP:_l4:LOAD CHAR SET \"file\"\n>#COMMAND2.HLP:_l5:LOAD PALETTE \"file\"\n>#COMMAND2.HLP:_l6:LOCKPLAYER\n>#COMMAND2.HLP:_l7:LOCKPLAYER ATTACK\n>#COMMAND2.HLP:_l8:LOCKPLAYER EW\n>#COMMAND2.HLP:_l9:LOCKPLAYER NS\n>#COMMAND2.HLP:_l0:LOCKSCROLL\n>_lA:LOCKSELF\n>_lB:LOOP #\n>_lC:LOOP START\n>#COMMAND2.HLP:_m1:MESSAGE ROW #\n>#COMMAND2.HLP:_m2:MISSILECOLOR [color]\n>_m3:MOD \"file\"\n>#COMMAND2.HLP:_m4:MOD FADE # #\n>#COMMAND2.HLP:_m5:MOD FADE IN \"file\"\n>#COMMAND2.HLP:_m6:MOD FADE OUT\n>#COMMAND2.HLP:_m7:MOD SAM # #\n>_m8:MOD \"*\"\n>_m9:MODULO \"counter\" #\n>#COMMAND2.HLP:_m0:MOVE ALL [color] [thing] [param] [dir] [~D!~F]\n>_mA:MOVE PLAYER [dir] [~D!~F]\n>_mB:MOVE PLAYER [dir] \"label\" [~D!~F]\n>_mC:MULTIPLY \"counter\" #\n>#COMMAND2.HLP:_n1:NEUTRAL BULLETCOLOR [color]\n>#COMMAND2.HLP:_n2:NEUTRAL BULLETE [char]\n>#COMMAND2.HLP:_n3:NEUTRAL BULLETN [char]\n>#COMMAND2.HLP:_n4:NEUTRAL BULLETS [char]\n>#COMMAND2.HLP:_n5:NEUTRAL BULLETW [char]\n>#COMMAND2.HLP:_o1:OPEN [dir]\n>#COMMAND2.HLP:_o2:OVERLAY ON\n>#COMMAND2.HLP:_o3:OVERLAY STATIC\n>#COMMAND2.HLP:_o4:OVERLAY TRANSPARENT\n>_p1:PERSISTENT GO \"string\" [~D!~F]\n>_p2:PLAY \"string\"\n>_p3:PLAY SFX \"string\"\n>#COMMAND2.HLP:_p4:PLAYER BULLETCOLOR [color]\n>#COMMAND2.HLP:_p5:PLAYER BULLETE [char]\n>#COMMAND2.HLP:_p6:PLAYER BULLETN [char]\n>#COMMAND2.HLP:_p7:PLAYER BULLETS [char]\n>#COMMAND2.HLP:_p8:PLAYER BULLETW [char]\n>_p9:PLAYER CHAR [dir] [char]\n>_p0:PLAYER CHAR [char]\n>_pA:PLAYERCOLOR [color]\n>#COMMAND2.HLP:_pB:PUSH [dir]\n>_pC:PUT [color] [thing] [param] # #\n>_pD:PUT [color] [thing] [param] [dir]\n>#COMMAND2.HLP:_pE:PUT [color] [char] OVERLAY # #\n>_pF:PUT [color] [thing] [param] [dir] PLAYER\n>_pG:PUT PLAYER # # [~D!~F]\n>_pH:PUT PLAYER [dir] [~D!~F]\n>_pI:PUT \"@@FILENAME.XXX\" Image_File [param] # #\n>_pJ:PUT [color] Sprite [param] # #\n>#COMMAND2.HLP:pre:REL COUNTERS\n>#COMMAND2.HLP:_r2:REL PLAYER\n>#COMMAND2.HLP:_r3:REL SELF\n>#COMMAND2.HLP:_r4:REL COUNTERS FIRST\n>#COMMAND2.HLP:_r5:REL PLAYER FIRST\n>#COMMAND2.HLP:_r6:REL SELF FIRST\n>#COMMAND2.HLP:_r7:REL COUNTERS LAST\n>#COMMAND2.HLP:_r8:REL PLAYER LAST\n>#COMMAND2.HLP:_r9:REL SELF LAST\n>#COMMAND2.HLP:_r0:RESETVIEW\n>#COMMAND2.HLP:_rA:RESTORE \"label\" #\n>#COMMAND2.HLP:_rB:RESTORE PLAYER POSITION [~D!~F]\n>#COMMAND2.HLP:_rC:RESTORE PLAYER POSITION # [~D!~F]\n>#COMMAND2.HLP:_rD:RESTORE PLAYER POSITION # DUPLICATE SELF [~D!~F]\n>#COMMAND2.HLP:_rE:ROTATECW\n>#COMMAND2.HLP:_rF:ROTATECCW\n>_s1:SAM # \"file\"\n>#COMMAND2.HLP:_s2:SAVE PLAYER POSITION\n>#COMMAND2.HLP:_s3:SAVE PLAYER POSITION #\n>#COMMAND2.HLP:_s4:SCROLL CHAR [char] [dir]\n>#COMMAND2.HLP:_s5:SCROLLARROW COLOR [color]\n>#COMMAND2.HLP:_s6:SCROLLBASE COLOR [color]\n>#COMMAND2.HLP:_s7:SCROLLCORNER COLOR [color]\n>#COMMAND2.HLP:_s8:SCROLLPOINTER COLOR [color]\n>#COMMAND2.HLP:_s9:SCROLLTITLE COLOR [color]\n>#COMMAND2.HLP:_s0:SCROLLVIEW [dir] #\n>#COMMAND2.HLP:_sA:SCROLLVIEW POSITION # #\n>_sB:SEND # # \"label\"\n>_sC:SEND \"Robot\" \"label\"\n>_sD:SEND [dir] \"label\"\n>_sE:SEND [dir] PLAYER \"label\"\n>_sF:SET \"counter\" #\n>_sG:SET \"counter\" RANDOM # #\n>_sH:SET \"$string\" \"text\"\n>_sI:SET \"$string1\" \"$string2\"\n>_sJ:SET \"$string\" #\n>#COMMAND2.HLP:_sK:SET COLOR # # # #\n>#COMMAND2.HLP:_sL:SET EDGE COLOR [color]\n>#COMMAND2.HLP:_sM:SET MAXHEALTH #\n>#COMMAND2.HLP:_sN:SET MESG COLUMN #\n>_sO:SFX #\n>_sP:SHOOT [dir]\n>_sQ:SHOOTMISSILE [dir]\n>_sR:SHOOTSEEKER [dir]\n>#COMMAND2.HLP:_sS:SLOWTIME #\n>_sT:SPITFIRE [dir]\n>#COMMAND2.HLP:_sU:STATUS COUNTER # \"counter\"\n>#COMMAND2.HLP:_sV:SWAP WORLD \"file\"\n>#COMMAND2.HLP:_sW:SWITCH [dir] [dir]\n>#COMMAND2.HLP:_t1:TAKE # [item]\n>#COMMAND2.HLP:_t2:TAKE # [item] \"label\"\n>#COMMAND2.HLP:_t3:TAKEKEY [color]\n>#COMMAND2.HLP:_t4:TAKEKEY [color] \"label\"\n>#COMMAND2.HLP:_t5:TELEPORT PLAYER \"string\" # # [~D!~F]\n>#COMMAND2.HLP:_t6:TRADE # [item] # [item] \"label\"\n>_t7:TRY [dir] \"label\" [~D!~F]\n>#COMMAND2.HLP:_u1:UNLOCKPLAYER\n>#COMMAND2.HLP:_u2:UNLOCKSCROLL\n>_u3:UNLOCKSELF\n>#COMMAND2.HLP:_v1:VIEWPORT # #\n>#COMMAND2.HLP:_v2:VIEWPORT SIZE # #\n>_v3:VOLUME #\n>_w1:WAIT # [~D!~F]\n>#COMMAND2.HLP:_w2:WAIT MOD FADE [~D!~F]\n>#COMMAND2.HLP:_w3:WAIT PLAY [~D!~F]\n>_w4:WAIT PLAY \"string\" [~D!~F]\n>_w5:WALK [dir]\n>#COMMAND2.HLP:_w6:WIND #\n>#COMMAND2.HLP:_w7:WRITE OVERLAY [color] \"string\" # #\n>#COMMAND2.HLP:_z1:ZAP \"label\" #\n\n$Command Descriptions\n\n:__3:~A* \"string\"\n\nThis command displays the given string as a message on the\nmessage line, which by default is at the bottom of the screen\nand centered. This command will cycle the message in different\ncolors by default, but will also accept ~~ and @@ color codes.\nUsing the newline char (\\n) in this string will cause the\nmessage line to span another line underneath, allowing\nmulti-line messages.\n\nA * message will stay on screen on that board for either 160\ncycles, or until replaced by another * message, or until a CLEAR\nMESG command is ran, whichever comes first.\n\n>#COMMAND2.HLP:_cA:CLEAR MESG\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n\n:__5:~A/ \"string\" ~F[~D!~F]\n:_p1:~APERSISTENT GO \"string\" ~F[~D!~F]\n:_g4:~AGO [dir] # ~F[~D!~F]\n\nUse / followed by a string to move the Robot around the board.\nThe string must consist of a series of N, S, E, W, and I. NSEW\nwill move the Robot in that direction, and I will wait for one\ncycle. One action is performed each cycle.\n\nUse PERSISTENT GO in the same way as /. However, if the Robot\nattempts to move in a given direction and it cannot, the Robot\nwill wait until it can before moving on to the next symbol.\n\nUse GO to move in a single direction for a given number of\nspaces (limited to 255; higher numbers and negatives wrap).\n\nThese commands can be used concurrently with ~AWALK [dir]~F,\nallowing Robots to move in double-speed steps. However, this\ncan only happen when the commands are in the same cycle and if\nthe given string/number for the moving command is small.\n\n>_w5:WALK [dir]\n>#COMMANDS.HLP:dir:Directions\n\n:__6:~A:~A \"label\"\n\nA : is used to denote actual LABELS within a Robotic program.\nThese labels are used as points within the program to branch\nto when a Robot receives a message from another Robot, goes to\na label of its own, or otherwise receives an external message.\nLabel names can be any length and consist of any characters.\nThey are not case-sensitive, and they can not take interpreted\ncounters (e.g. : \"&label&\").\nA Robot can have multiple active labels with the same name. In\nthese cases, the topmost of these labels in the Robot's code is\nthe one that is triggered.\nThere are several label names that are automatically jumped to\nunder certain conditions. These are detailed in the Built-in\nLabels section.\n\nLabels starting with a pound sign/hash (#) are subroutines.\nSee the Subroutine section for more information.\n\n>#SUBROUTE.HLP:sub:Subroutines\n>#BUILTINL.HLP:1st:Built-in Labels\n\n:_g5:~AGOTO \"label\"\n\nThis will attempt to send the current Robot to the label\ngiven; if the label does not exist, this command does nothing.\nIf the label begins with a # it will go to it as a subroutine.\nSeveral (but not all!) built-in labels also have subroutine\nversions.\n\n:_g6:~AGOTO \"#return\"\n:_g7:~AGOTO \"#top\"\n\nThis will send the current Robot to the line after the\nnext-highest subroutine call and to the line after the first\nsubroutine call, respectively.\n\n>#SUBROUTE.HLP:sub:Subroutines\n\n:_sC:~ASEND \"Robot\" \"label\"\n:_sD:~ASEND [dir] \"label\"\n:_sE:~ASEND [dir] PLAYER \"label\"\n:_sB:~ASEND # # \"label\"\n\nUse SEND to send another Robot to the label given. This is\ncalled \"messaging\" a Robot, and the label given is the\n\"message\". The target Robot can be given one of four ways,\nas listed above respectively:\n\n1. State the target Robot's name. Use \"ALL\" in place of the\nRobot name to message all Robots (including the Robot doing the\nsend). Set ~ALOCKSELF~F beforehand to prevent sending to yourself\nwhen sending to \"ALL\".\n2. State the direction of the target Robot, relative to the\nsource Robot.\n3. State the direction of the target Robot, relative to the\nplayer.\n4. State the x,y coordinates of the target Robot.\n\n>#COMMANDS.HLP:dir:Directions\n>#COMMANDR.HLP:_lA:LOCKSELF\n\n:__9:~A[ \"string\"\n:__2:~A& \"string\"\n:__1:~A% \"string\"\n:__8:~A? \"label\" \"string\"\n:__7:~A? \"counter\" \"label\" \"string\"\n\nUse these commands to bring up a message box during the game\ncontaining the given text. The message box will contain all\nconsecutive lines using the commands [, &, %, or ?. Blank lines\nand labels will be skipped over. The box message commands are\nas follows:\n\n  [ will simply display the given message.\n  & will display the given message, centered within the box.\n  % will display the given message, but will parse color codes.\n  ? will display an option that will send the current Robot to\n   the given label when selected. The counter is optional. If a\n   counter is included, the option will only be shown if the\n   counter is NOT zero.\n\nAll box-message commands can use ~~ and @@ color codes, EXCEPT\nfor [. The message box can show up to 64 characters per line;\nany characters past that will not be shown.\n\nNOTE: Take care when using any of these commands in a loop (and\nbe especially vigilant of possible unintended loops). Looping\nthese commands without any WAITs or similar cycle-breaking\ncommands (or repeatedly jumping to a label with said command)\ncan cause the dialogue box to re-appear as soon as it is\nclosed, making it very hard to resume normal action.\n\n>#COUNTERS.HLP:1st:Counters, Built-in Counters and Local Counters\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n\n:__4:~A. \"string\"\n\nThis command does nothing. It is used for comments and other\nnotes you may wish to make within your Robots. There is one\nexception - If \"string\" begins with a @@. (see next) You may\ncomment or de-comment lines of code automatically with the\nCtrl+C command (comment mark). Comments can nonetheless have an\nactual effect on code speed, as they must be interpreted when\nencountered.\n\n:_4b:~A. \"@@string\"\n\nIf a comment's string begins with a @@, then the rest of the\nstring becomes the new name for the Robot. For example,\n\"@@Hiya\" would name the Robot \"Hiya\". This clips at the first\n14 characters of the string; for example, . \"@@Robothasanewname\"\nwould cut off and rename the Robot \"Robothasanewna\".\n\n:_e1:~AEND ~F[~D!~F]\n\nThis command ends the Robot's program. The Robot will not run\nany further commands until an external event affects it.\n\n:_d3:~ADIE\n:_d4:~ADIE ITEM\n\nThese commands destroy the Robot forever. DIE ITEM will also\nput the player at the location the Robot previously occupied,\nwhich is useful for creating Robots that simulate collectible\nitems.\n\n:_w1:~AWAIT # ~F[~D!~F]\n\nThis command will cause the Robot to do absolutely nothing for\na given number of cycles (limited to 255; values over this will\nwrap around). Robots will still respond to label sends when\nwaiting, provided that the Robot is not locked.\n\n>#PROCESS.HLP:prc:Cycles and Board Scans - How MZX Processes Robots\n\n:_c9:~ACHAR [char]\n\nThis command will change the Robot's character (appearance) to\nthe character given.\n\n:_cC:~ACOLOR [color]\n\nThis command will change the Robot's color to the color given.\n\n>#COMMANDS.HLP:col:Colors\n\n:_sF:~ASET \"counter\" #\n:_sG:~ASET \"counter\" RANDOM # #\n\nThese commands set a counter to a certain value. Counters are\ninternal variables that can be changed through Robots. They are\nusually used for custom purposes, although there are many\ncounters with pre-defined uses as well, such as \"Gems\". The\nRANDOM version will set the counter to a random number within\nthe given range, inclusive (first number minimum, second\nmaximum).\n\n:_sH:~ASET \"$string\" \"text\"\n:_sI:~ASET \"$string\" \"$string2\"\n:_sJ:~ASET \"$string\" number\n\nThese commands set strings to a certain value: a line of text,\nanother string or a number, respectively.\n\n>#COUNTERS.HLP:1st:Counters, Built-in Counters and Local Counters\n\n:_iI:~AINC \"counter\" #\n:_d1:~ADEC \"counter\" #\n:_iJ:~AINC \"counter\" RANDOM # #\n:_d2:~ADEC \"counter\" RANDOM # #\n\nThese commands will increase or decrease a given counter or\nstring by a given amount. The RANDOM versions will increase or\ndecrease by a random number within the given range, inclusive\n(first number minimum, second maximum).\n\n>#COUNTERS.HLP:1st:Counters, Built-in Counters and Local Counters\n\n:_i1:~AIF \"counter\" !<>= # \"label\"\n\nThis command tests to see if a given counter is equal to, less\nthan, etc. another counter or value. If the conditional is met,\nthe current Robot is sent to the given label. The following\nconditionals are allowed: = Equal, < Less than, > Greater than,\n.<= Less than/equal, >= Greater than/equal, != (also <>) Not\nequal.\n\n>#COUNTERS.HLP:1st:Counters, Built-in Counters and Local Counters\n\n:_cP:~ACYCLE # ~F[~D!~F]\n\nThis command changes the Robot's speed to one Robot cycle for\nevery given number of update cycles. An update cycle is how\noften everything onscreen is updated, such as most enemies.\nEvery time a given Robot gets to run commands is called a cycle\nin terms of that Robot; most commands can run several times in\na single Robot cycle. (CYCLE # values are limited to 255;\nvalues over this will wrap around.)\n\n>#PROCESS.HLP:prc:Cycles and Board Scans - How MZX Processes Robots\n\n:_e6:~AENDLIFE\n:_e5:~AENDGAME\n\nThese commands will end the player's current life or the entire\ngame, just as if the player died or lost all of its lives.\n\n:_w5:~AWALK [dir]\n\nThis will cause the Robot to attempt to move one space in the\ngiven direction every cycle. Use a direction of IDLE to turn\nwalking off.\n\nThis can be used concurrently with ~AGO [dir] #~F and its\nrelatives to make Robots move in double-speed steps.\n\n>_g4:GO [dir] #\n>__5:/ \"string\"\n>_p1:PERSISTENT GO \"string\"\n>#COMMANDS.HLP:dir:Directions\n\n:_sP:~ASHOOT [dir]\n:_sQ:~ASHOOTMISSILE [dir]\n:_sR:~ASHOOTSEEKER [dir]\n:_sT:~ASPITFIRE [dir]\n\nThese four commands all shoot a weapon in the given direction:\nA bullet, a missile, a seeker, or shooting fire, respectively.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_t7:~ATRY [dir] \"label\" ~F[~D!~F]\n\nThis will have the Robot attempt to move in the given\ndirection. If it can, it will. If it can't, it will jump to the\ngiven label.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_b1:~ABECOME [color] [thing] [param]\n\nThe Robot will end its program permanently, and instead become\na specified object with a given color and parameter.\nNOTE: Becoming another Robot type will not end or destroy the\nprogram, but there are far better commands for doing this:\n~ABECOME~F ~APUSHABLE~F and ~ABECOME NONPUSHABLE~F.\n\n>#COMMANDS.HLP:col:Colors\n>#COMMAND2.HLP:_b3:BECOME NONPUSHABLE\n>#COMMAND2.HLP:_b5:BECOME PUSHABLE\n\n:_c2:~ACHANGE [color] [thing] [param] [color] [thing] [param]\n\nAll on-board objects of the first given type will become\nobjects of the second given type.\n\n>#COMMANDS.HLP:col:Colors\n\n:_pC:~APUT [color] [thing] [param] # #\n:_pD:~APUT [color] [thing] [param] [dir]\n:_pF:~APUT [color] [thing] [param] [dir] PLAYER\n\nThis will put a given object somewhere on the board. If anything\nelse is in that spot and layer, it will be placed under the new\nobject, if possible; otherwise, it will be replaced by this\nobject. (The exception is if the object were to replace the\nplayer; in this case, nothing happens.) The target location can\nbe given in one of three ways:\n\n1. State the target x,y coordinates.\n2. State the direction, relative to the Robot.\n3. State the direction, relative to the player.\n\nNote that this command is not suitable for placing Robots. For\nmoving a Robot, use ~AGOTOXY # #~F; for placing a copy of a Robot,\nuse ~ADUPLICATE SELF # #~F or ~ADUPLICATE SELF [dir]~F.\n\n>#COMMANDS.HLP:col:Colors\n>#COMMANDS.HLP:dir:Directions\n\n:_m3:~AMOD \"file\"\n\nThis command loads the given module/OGG/SAM file as background\nmusic. These files also can be loaded from a subdirectory with\nthe syntax of \"\"dir\"\\\\\"file\"\" or \"\"dir\"/\"file\"\" (without the\ninner quotes). MOD, unlike SAM, will loop audio, and is affected\nby related commands like VOLUME and MOD ORDER.\nNOTE: placing * at the end of the filename will set the current\nmod to the given filename, but institute MOD \"*\" (see below) for\nsubsequent board visits.\n\n:_m8:~AMOD \"*\"\n\nSets the board's currently running music to \"wildcard\". This\nmeans that it will continue to play whatever was last playing.\nThis setting does not \"lock\" the board's music to any one song;\nfor instance, if 1.mod was playing on the last board, mod \"*\"\nwill continue playing 1.mod, but if the player then goes to a\nboard playing 2.mod and jumps back to the mod \"*\" board from\nthere, 2.mod will continue playing, and 1.mod will not start.\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n\n:_s1:~ASAM # \"file\"\n\nPlays the given SAM/WAV/OGG/module file at a given frequency.\nThese files also can be loaded from a subdirectory with the\nsyntax of \"\"dir\"\\\\\"file\"\" or \"\"dir\"/\"file\"\" (without the inner\nquotes). 0 plays the file at natural frequency. Music loaded\nwith the SAM command will not loop.\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n\n:_e2:~AEND MOD\n:_e4:~AEND SAM\n:_e3:~AEND PLAY\n\nEnds the playing of the current audio set by MOD \"file\",\nthe current audio set by SAM # \"file\", or the currently playing\nPLAY statement (respectively).\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n\n:_d8:~ADOUBLE \"counter\"\n:_h1:~AHALF \"counter\"\n\nDoubles or halves (rounding down) the value of the given\ncounter, respectively.\n\n>#COUNTERS.HLP:1st:Counters, Built-in Counters and Local Counters\n\n:_mC:~AMULTIPLY \"counter\" #\n:_d7:~ADIVIDE \"counter\" #\n:_m9:~AMODULO \"counter\" #\n\nMultiplies, divides, or performs a modulo on the value of the\ngiven counter by the given number (rounding down), and then sets\nthe counter to the result.\nTo modulo is to set to the remainder when dividing. Example: 7\nmodulo 3 is equal to 1, since 7 divided by 3 gives a remainder\nof 1.\n\nNOTE: The modulo command may output different numbers compared\nto the modulo expression function; they may act differently\nwhen negative numbers are involved. For the modulo command, the\noutput will give results similar to when both numbers are\npositive, but will simply have the sign of the dividend (i.e. a\ntruncated modulo).\n\n>#COUNTERS.HLP:1st:Counters, Built-in Counters and Local Counters\n\n:_p2:~APLAY \"string\"\n:_p3:~APLAY SFX \"string\"\n:_w4:~AWAIT PLAY \"string\" ~F[~D!~F]\n\nThese all play sound effects, PC speaker notes, or a\ncombination thereof. PLAY SFX will only play if there are no\nother notes currently playing, while PLAY will add its notes to\nthe end of the current sound effects queue. WAIT PLAY will wait\nuntil the queue is almost empty before adding its notes.\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n\n:_sO:~ASFX #\n\nPlays the given built-in sound effect.\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n\n:_eH:~AEXPLODE #\n\nThe Robot will explode, destroying it and its program forever.\nThe radius of the explosion in characters is stated in the\ncommand, with a maximum of 16 characters.\n\n:_cD:~ACOLOR FADE OUT\n:_cE:~ACOLOR FADE IN\n\nFades the palette out to black or in to full color. Fading in\nwill not work if you haven't faded out, and fades affect\nneither the current palette nor current intensities.\n\n:_g8:~AGOTOXY # # ~F[~D!~F]\n\nThe Robot will go to the given x,y coordinates on the current\nboard, destroying anything there that cannot go on the under\nlayer. If the player is at those coordinates, the Robot ignores\nthe command.\n\n:_lC:~ALOOP START\n:_lB:~ALOOP #\n:_a1:~AABORT LOOP\n\nThese three commands define a loop, which is a sequence of\ncommands to be repeated. LOOP START marks the start of a loop.\nLOOP # will loop back to LOOP START the given number of times.\nABORT LOOP will jump out of the loop, to the command after LOOP\n.#. The loop count is kept in the local counter \"loopcount\".\nMake sure your number in LOOP # is one less than the amount for\nwhich you actually want to loop, as the initial iteration\nbefore first hitting LOOP # is not counted in the number.\n\nNOTE: Using ABORT LOOP outside of the loop (that is, outside of\nany block of code starting with LOOP START and ending in\nLOOP #) is very highly unadvised and will most likely cause\nunintended effects.\n\n>#COUNTERS.HLP:1st:Counters, Built-in Counters and Local Counters\n\n:_mA:~AMOVE PLAYER [dir] ~F[~D!~F]\n:_mB:~AMOVE PLAYER [dir] \"label\" ~F[~D!~F]\n\nThis attempts to move the player in the given direction. If the\nplayer is blocked, and the command contains a label, the Robot\nwill jump to that label.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_p0:~APLAYER CHAR [char]\n:_p9:~APLAYER CHAR [dir] [char]\n\nChanges the character (appearance) of the player. The second\nform allows you to change the appearance of one of the four\nfacing directions at a time, while the first form changes all\nfour directions at once.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_pA:~APLAYERCOLOR [color]\n\nChanges the color of the player.\n\n>#COMMANDS.HLP:col:Colors\n\n:_pG:~APUT PLAYER # # ~F[~D!~F]\n:_pH:~APUT PLAYER [dir] ~F[~D!~F]\n\nThe player will go to the given x,y coordinates or in the given\ndirection immediately relative to the Robot, destroying anything\nthere that cannot go on the under layer. If the x,y coordinates\nof the put command are out of bounds, the player is moved to the\nclosest-matching coordinates, and if a directional put is out of\nbounds it is ignored.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_pI:~APUT \"@@FILENAME.XXX\" Image_File [param] # #\n\nPlaces an MZM of the given filename. These files also can be\nloaded from a subdirectory with the syntax of \"\"@@dir\"\\\\\"file\"\"\nor \"\"@@dir\"/\"file\"\" (without the inner quotes). The @@ is\nREQUIRED. The numbers mark the destination x,y coordinates of\nthe MZM's upper-left corner; the parameter determines the\nplacement of the MZM (00 = board, 01 = overlay, 02 = vlayer).\nSee the MZM section for further details.\n\n>#MZM.HLP:mzm:Using MZMs\n\n:_pJ:~APUT [color] Sprite [param] # #\n\nPuts a sprite on the board. The param number is the number of\nthe sprite in hex (auto-converted from decimal). If the color\nis c?? then the sprite is drawn with its reference's colors;\notherwise, it's painted with the given colors. # # marks the x,y\ncoordinates of the sprite's upper-left corner. See the Spites\nsection for further details.\n\n>#SPRITES.HLP:spr:Sprites\n\n:_lA:~ALOCKSELF\n:_u3:~AUNLOCKSELF\n\nLocks or unlocks, respectively, the current Robot. When a Robot\nis locked, no external messages will be acknowledged - Only\ninternal GOTOs, etc. will be processed. (Built-in label calls\nto \"thud\" and \"edge\" are exceptions.)\n\n:_v3:~AVOLUME #\n\nChanges the volume of the MOD playing, from 0 (silent) to 255.\nThis change is specific to the current board. The default when\nmusic is loaded is 255.\n\n:_cM:~ACOPYROBOT \"Robot\" ~F[~D!~F]\n:_cN:~ACOPYROBOT # # ~F[~D!~F]\n:_cO:~ACOPYROBOT [dir] ~F[~D!~F]\n\nThe current Robot becomes an exact duplicate of the noted\nRobot, starting from the beginning of the noted Robot's code.\nNote that if the source Robot was previously ended with the END\ncommand, or was at the end of its program, the destination\nRobot will be in an ended state as well. The Robot to copy can\nbe stated in one of three ways:\n\n1. State the target Robot's name. If multiple Robots on the same\n   board share the same name, the Robot chosen may vary, so\n   using this command in that situation is not advised.\n2. State the x,y coordinates of target Robot.\n3. State the direction of target Robot relative to the source\n   Robot.\n\nNOTE: Exact means just that, EXACT, down to its states and the\nvalues of local counters. The only thing that will differ will\nbe the robot_id. If one requires a group of objects differing\nonly in name, COPYROBOT is generally unsuitable for this purpose\nbecause even the Robot name is copied.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_d9:~ADUPLICATE SELF # #\n:_d0:~ADUPLICATE SELF [dir]\n\nThe current Robot creates an exact duplicate of itself\n(including local counter values!) and places it at the noted\nx,y coordinates or relative direction. That Robot then begins\nits program at the first command.\n\n>#COMMANDS.HLP:dir:Directions\n\n~EThe remaining Robotic commands are covered in another section.\n\n>#COMMAND2.HLP:1st:Command Reference Part II\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#COMMAND2.HLP\n:1st:\n$~9Command Reference Part II\n\n>#COMMANDR.HLP:1st:Command Reference (Part I)\n\n:_b9:~ABULLETCOLOR [color]\n\nChanges the color of all bullets.\n\n>#COMMANDS.HLP:col:Colors\n\n:_p4:~APLAYER BULLETCOLOR [color]\n:_e0:~AENEMY BULLETCOLOR [color]\n:_n1:~ANEUTRAL BULLETCOLOR [color]\n\nChanges the color of a specific type of bullet: Player, Enemy,\nor Neutral (ricocheted/Robot) bullets.\n\n>#COMMANDS.HLP:col:Colors\n>#BULLETTY.HLP:1st:Bullet Types\n\n:_bA:~ABULLETN [char]\n:_bB:~ABULLETS [char]\n:_b0:~ABULLETE [char]\n:_bC:~ABULLETW [char]\n\nChanges the character (appearance) of one of the four\ndirections of bullet, across all three types.\n\n:_p6:~APLAYER BULLETN [char]\n:_p7:~APLAYER BULLETS [char]\n:_p5:~APLAYER BULLETE [char]\n:_p8:~APLAYER BULLETW [char]\n:_eA:~AENEMY BULLETE [char]\n:_eB:~AENEMY BULLETN [char]\n:_eC:~AENEMY BULLETS [char]\n:_eD:~AENEMY BULLETW [char]\n:_n3:~ANEUTRAL BULLETN [char]\n:_n4:~ANEUTRAL BULLETS [char]\n:_n2:~ANEUTRAL BULLETE [char]\n:_n5:~ANEUTRAL BULLETW [char]\n\nChanges the character of one of the four directions of bullet,\nfor one of the three types of bullet: Player, Enemy, or Neutral\n(ricocheted/Robot) bullets, respectively.\n\n>#BULLETTY.HLP:1st:Bullet Types\n\n:_b5:~ABECOME PUSHABLE\n:_b3:~ABECOME NONPUSHABLE\n\nThe Robot becomes a pushable or nonpushable Robot,\nrespectively.\n\n:_i2:~AIF [condition] \"label\"\n:_iB:~AIF NOT [condition] \"label\"\n\nThese test whether a given condition is present or not, and if\nit is, the first will jump to the given label. If it isn't, the\nsecond will jump to the given label. Conditions are covered in\nanother section.\n\n>#COMMANDS.HLP:con:Conditions\n\n:_i3:~AIF # # \"label\"\n\nJumps to the given label if the current Robot is at the x,y\ncoordinates given.\n\n:_i4:~AIF [dir] PLAYER [color] [thing] [param] \"label\"\n\nJumps to the given label if the given thing is the given\ndirection relative to the player.\n\n>#COMMANDS.HLP:col:Colors\n>#COMMANDS.HLP:dir:Directions\n\n:_i5:~AIF [color] [thing] [param] # # \"label\"\n:_i6:~AIF [color] [thing] [param] [dir] \"label\"\n:_iA:~AIF NOT [color] [thing] [param] [dir] \"label\"\n\nJumps to a label if a given thing is at the given x,y\ncoordinates or to the immediate given direction of the current\nRobot. The third form jumps if the thing is NOT in the\nimmediate given direction.\n\n>#COMMANDS.HLP:col:Colors\n>#COMMANDS.HLP:dir:Directions\n\n:_i7:~AIF ALIGNEDROBOT \"Robot\" \"label\"\n\nJumps to the given label if the current Robot is aligned either\nvertically or horizontally with the named Robot.\n\n:_i8:~AIF ANY [color] [thing] [param] \"label\"\n:_i0:~AIF NO [color] [thing] [param] \"label\"\n\nIf there are ANY of a given thing with the given color and\nparameter on the current board, jumps to the given label. The\nsecond form triggers on the absence of the given thing.\n\n>#COMMANDS.HLP:col:Colors\n\n:_iC:~AIF PLAYER # # \"label\"\n\nIf the player is at the given x,y coordinates, jump to the given\nlabel.\n\n:_g2:~AGIVEKEY [color]\n:_g3:~AGIVEKEY [color] \"label\"\n:_t3:~ATAKEKEY [color]\n:_t4:~ATAKEKEY [color] \"label\"\n\nAttempts to give keys to or take keys from the player. The\nforms without labels will give or take the key if possible,\notherwise they will do nothing. The forms with labels will jump\nto the labels if the Robot tries to give a key when the player\nhas no room for keys, or if the Robot tries to take a key the\nplayer doesn't have.\n\n>#COMMANDS.HLP:col:Colors\n\n:_g1:~AGIVE # [item]\n:_t1:~ATAKE # [item]\n:_t2:~ATAKE # [item] \"label\"\n\nGives or takes a number of a certain item. If the player does\nnot have enough of this type of item to take, nothing is taken.\nThe third form jumps to a given label if the player doesn't have\nenough of the stated item.\n\n:_t6:~ATRADE # [item] # [item] \"label\"\n\nGives the player a number of the first item and takes a number\nof the second item in exchange. If the player doesn't have\nenough of the second item, this command jumps to the given label\nand has no other effect. This command eases the writing of\nsimple shops and vendors.\n\nKeep in mind that trades involving healths and lives can end up\nwith either at 0, leading to potential deaths and endgames if\nthe player has the exact amount of health/lives being traded\naway.\n\n:_l6:~ALOCKPLAYER\n:_l9:~ALOCKPLAYER NS\n:_l8:~ALOCKPLAYER EW\n:_l7:~ALOCKPLAYER ATTACK\n:_u1:~AUNLOCKPLAYER\n\nThe first four commands lock the player, preventing the player\nfrom doing any moving and attacking (i.e. default shooting and\nbombing), from moving north or south, from moving east or west,\nor from attacking, respectively. The fifth command will clear\nall locks on the player, allowing free movement and attacking.\nThese have no relation to the Board Info options \"Can Bomb\" and\n\"Can Shoot\". Player locking is local, only affecting the\ncurrent board.\n\nLocking the player will also prevent messages related to locked\nactions from showing (such as bomb switch messages or a \"Can't\n{shoot/bomb} here!\" message when the player is attack locked).\n\nAlso, locking the player will not cause the relevant input to\nbe ignored completely. MegaZeux will still detect that you are\npressing keys, even if the player is forbidden from responding\nto them, and will act accordingly. In short, even if a player\nis locked, Robots that take directional and space/delete inputs\nwill still act the same.\n\n:_b7:~ABOARD [dir] \"string\"\n:_b8:~ABOARD [dir] NONE\n\nChanges the board exit in the given direction to lead to the\ngiven board or to nowhere. Unlike with the Board Exits menu,\nlinking exits to the title board with this command is\nallowed.\n\n>#COMMANDS.HLP:dir:Directions\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n\n:_z1:~AZAP \"label\" #\n:_rA:~ARESTORE \"label\" #\n:__0:~A| \"label\"\n\nZAP will scan the Robotic program from the top down, changing\na given number of instances of the stated label to ZAPPED\nlabels. A ZAPPED label is inactive - it cannot be jumped to or\nsent to under any circumstance. This has two uses: It will\nprevent interference from a certain outside event, and it\nallows multiple instances of the same label. For example, you\ncould have four SHOT labels in a row, ZAP one each time the\nRobot was shot, and jump back to normal code, but have a fifth\nSHOT label lead to an explosion.\n\nRESTORE will scan the Robotic program from the bottom up,\nchanging a given number of the stated ZAPPED labels back to a\nnormal label. Essentially, this means that the most recently\nZAPPED labels are restored first. Both ZAP and RESTORE can have\na number larger than the number of instances of the stated\nlabel. The final command, |, is a pre-ZAPPED label, allowing you\nto have labels in your Robotic programs that are already ZAPPED\nand inactive.\n\n:pre:~AREL COUNTERS\n:_r2:~AREL PLAYER\n:_r3:~AREL SELF\n\nPLEASE NOTE: Judicious use of expressions and certain counters\n(THISX/THISY/PLAYERX/PLAYERY) is a less cumbersome alternative\nto using the REL set of commands, and is generally preferred.\n\nThese are a special class of commands, called PREFIXES. Prefixes\ndon't do a thing by themselves, but when placed before another\ncommand that uses x,y coordinates, they change the origin of the\nx,y coordinate system for that ONE following command. REL\nCOUNTERS moves the origin to the x,y location stated in the\ncounters XPOS and YPOS. REL PLAYER moves the origin to the\nx,y of the player. REL SELF moves the origin to the x,y of the\ncurrent Robot.\n\nThe movement of the origin changes the (0,0) coordinates from\nthe upper left of the board to somewhere else, effectively\nadding that x,y position to any coordinates entered. For\nexample, if the Robot is at the (5,5) position and uses REL\nSELF on the coordinates (2,2), it will effectively act on\ncoordinates (7,7). If the player is at the (7,2) position and a\nRobot uses REL PLAYER on coordinates (-3,6), the Robot will\neffectively act on coordinates (4,8).\n\nLet's look at some examples.\n\n~EPUT c0E GEM p?? 5 5\n\nThis places a gem at the x,y coordinates (5,5).\n\n~EREL PLAYER\n~EPUT c0E GEM p?? 5 5\n\nThis places a gem 5 spaces to the right and 5 spaces below\nwhere the player currently is, or at the board edge if it is\nunder 5 spaces to the right/below the player.\n\n~EREL SELF\n~EPUT c0E GEM p?? -2 -3\n\nThis places a gem 2 spaces to the left and 3 spaces above\nwhere this Robot currently is, or at the board edge if it is\nunder 2 spaces to the left or 3 spaces above the Robot.\n\n~EREL COUNTERS\n~EPUT c0E GEM p?? 1 -4\n\nThis places a gem at the coordinates (XPOS+1,YPOS-4), or on a\nboard edge if either of these coordinates would extend past the\nboard. XPOS and YPOS are counters with amounts defined by the\nuser before using this command.\n\nPrefixes only have an effect on the very next command. This\nincludes comments.\n\nPrefixes also affect two (and only two) other things. The built\nin counters THISX and THISY will be offset by a prefix, and the\ncommand IF [dir] (NOT) BLOCKED will check next to the player or\nnext to the position noted by the counters if a prefix is\npresent. See their relevant sections for details.\n\n:_r4:~AREL COUNTERS FIRST\n:_r5:~AREL PLAYER FIRST\n:_r6:~AREL SELF FIRST\n:_r7:~AREL COUNTERS LAST\n:_r8:~AREL PLAYER LAST\n:_r9:~AREL SELF LAST\n\nThese prefixes are similar to the above prefixes, but they only\naffect the first or last x,y pair of coordinates in the next\ncommand. For example, COPY accepts two pairs of coordinates. If\nyou precede it with the following:\n\nREL PLAYER FIRST\nREL SELF LAST\n\nThen the coordinates of the object being copied will be relative\nto the player, and the destination coordinates will be relative\nto the Robot. There is one other difference with these commands\n- REL COUNTERS FIRST works off of the counters FIRSTXPOS and\nFIRSTYPOS, and REL COUNTERS LAST works off of the counters\nLASTXPOS and LASTYPOS.\n\n:_j1:~AJUMP MOD ORDER #\n\nThe current module jumps to the stated module order, if\napplicable. For example, JUMP MOD ORDER 3 will begin playing\nthe third order in the module, which is the fourth section of\nthe module. Sometimes your music editor will refer to this as\nthe 'Pattern' or 'Track'.\nWith OGG music, this command makes a jump to the stated frame.\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n\n:_l1:~ALAYBOMB [dir]\n:_l2:~ALAYBOMB HIGH [dir]\n\nPlaces a lit bomb (high strength for the second form) next to\nthe Robot in the given direction. Use a direction of UNDER or\nBENEATH to place the bomb underneath the Robot.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_l3:~ALAZERWALL [dir] #\n\nThe Robot shoots a lazer in the given direction, and the lazer\nhas the color of the Robot and the duration given. The number of\ncycles the lazer is active is duration x 2.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_cH:~ACOPY # # # #\n:_cI:~ACOPY [dir] [dir]\n:_sW:~ASWITCH [dir] [dir]\n\nCopies the thing at one location to another, with the location\nstated through x,y coordinates or directions relative to the\ncurrent Robot. The third command will swap the things in the\ntwo directions.\n\nCOPY can copy between different layers by using the appropriate\nprefixes. Prefix coordinate numbers with \"+\" to indicate\noverlay coordinates, and with \"#\" to indicate vlayer\ncoordinates.\n\nNOTE: DO NOT normally use COPY # # # # to copy Robots; use\nDUPLICATE SELF or COPYROBOT. It is useful for copying by Robot\nid, however; use COPY rN.thisx and rN.thisy as the first pair\nof coordinates.\n\nUsing COPY # # # # on a Robot will no longer cause the Robot\ncopy to start where the parent left off, and will have new\nRobots start at the first line.\n\n>#COUNTERS.HLP:rnc:Rn.<counter>\n>#BADPRACT.HLP:bad:Robotic Usages That Should be Avoided\n>#COMMANDS.HLP:dir:Directions\n\n:_cJ:~ACOPY BLOCK # # # # # #\n:_cL:~ACOPY OVERLAY BLOCK # # # # # #\n\nCopies a block of things from one location on the board or\noverlay to another section. The first two numbers state the\nupper-left corner of the block to be copied, the second two\nnumbers state the size of the block, and the third two numbers\nstate the destination block's upper-left corner.\n\nCOPY BLOCK and COPY OVERLAY BLOCK can copy from board to\noverlay and from overlay to board (respectively). The last two\nnumbers must be prefixed by plus signs, like so:\n\nCOPY BLOCK # # # # \"+#\" \"+#\"\nCOPY OVERLAY BLOCK # # # # \"+#\" \"+#\"\n\nValid Robotic block copies from the overlay to the board are\nalways CustomBlocks.\n\n>#TOVERLAY.HLP:081:Editing and Using the Overlay\n\n:_cW:~ACOPY BLOCK # # # # \"#x\" \"#y\"\n:_cX:~ACOPY OVERLAY BLOCK # # # # \"#x\" \"#y\"\n\nThese commands copy from the board or overlay (respectively) to\nthe vlayer. The first two numbers are the x,y coordinates of the\nupper-left corner of the block to be copied; the next two\nnumbers are the respective width and height of the block; the\n\"#x\" and \"#y\" are the x,y coordinates of the destination block's\nupper-left corner. The pound sign in \"#x\" and \"#y\" MUST be\nincluded.\n\n:_cY:~ACOPY BLOCK \"#x\" \"#y\" # # # #\n:_cZ:~ACOPY OVERLAY BLOCK \"#x\" \"#y\" # # # #\n\nThese commands similarly copy from the vlayer to the board or\noverlay (respectively).\n\n>#TOVERLAY.HLP:081:Editing and Using the Overlay\n>#VLAYER.HLP:vla:The Vlayer and Its Uses\n\n:_cAA:~ACOPY (OVERLAY) BLOCK \"#x1\" \"#y1\" # # \"#x2\" \"#y2\"\n\nThis command copies a block from the vlayer and copies it onto\na different spot on the vlayer. \"#x1\" and \"#y1\" are the x,y\ncoordinates of the upper-left corner of the copied block; the\nnext two numbers are the respective width and height of the\ncopied area; \"#x2\" and \"#y2\" are the x,y coordinates of the\ndestination block's upper-left corner. The pound sign in #x1\",\n\"#y1\", \"#x2\" and \"#y2\" MUST be included.\n\n>#VLAYER.HLP:vla:The Vlayer and Its Uses\n\n:_cQ:~ACOPY BLOCK # # # # \"@@filename\" #\n:_cR:~ACOPY OVERLAY BLOCK # # # # \"@@filename\" #\n\nThese commands copy from the board or overlay (respectively)\nto an MZM. The first two numbers are the x,y coordinates of the\nblock's upper-left corner; # # is the respective width and\nheight of the block; filename is the name of the file - the @@\nis required. Finally, the last number determines the MZM type.\n1 saves as layer; 0 saves as board. 0 is integral for copying\nnon-graphical data (primarily Robots); 1 is proper for copying\ngraphical data, as the block's appearance will stay as exactly\nas it was when saved.\n\n>#MZM.HLP:mzm:Using MZMs\n>#TOVERLAY.HLP:081:Editing and Using the Overlay\n\n:_cS:~ACOPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"@@filename\" #\n\nThese commands act much like the above two commands, but they\ncopy from the vlayer to an MZM instead.\n\n>#MZM.HLP:mzm:Using MZMs\n>#VLAYER.HLP:vla:The Vlayer and Its Uses\n\n:_cT:~ACOPY BLOCK # # # # \"$string\" #\n:_cU:~ACOPY OVERLAY BLOCK # # # # \"$string\" #\n:_cV:~ACOPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"$string\" #\n\nThese commands copy information from the board, overlay, or\nvlayer (respectively) to a given string. The first two numbers\nare the x,y coordinates of the upper-left corner of the block;\nthe third and fourth are its respective width and height; the\nlast is the terminating character's char value (i.e. a number\nfrom 0-255). The operation will end before all characters in the\ngiven range are read if either the parameter matching the\nterminating character, or a parameter of p00, is found. For the\nvlayer command, the pound sign is REQUIRED.\n\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n>#TOVERLAY.HLP:081:Editing and Using the Overlay\n\n:_s2:~ASAVE PLAYER POSITION\n:_s3:~ASAVE PLAYER POSITION #\n\nSaves the player's coordinates and board in one of eight\ninternal slots (1-8). If no number is given, slot one is used.\n\n:_rB:~ARESTORE PLAYER POSITION ~F[~D!~F]\n:_rC:~ARESTORE PLAYER POSITION # ~F[~D!~F]\n:_rD:~ARESTORE PLAYER POSITION # DUPLICATE SELF ~F[~D!~F]\n\nRelocates the player to the coordinates and board stored in\nthe given slot, or slot number one if none is given. The third\nform also duplicates the current Robot where the player used to\nbe; this Robot then starts its program at the first command.\nThis command will do transition fades on activation, even if the\ndestination is the same board.\n\n:_eE:~AEXCHANGE PLAYER POSITION ~F[~D!~F]\n:_eF:~AEXCHANGE PLAYER POSITION # ~F[~D!~F]\n:_eG:~AEXCHANGE PLAYER POSITION # DUPLICATE SELF ~F[~D!~F]\n\nExchanges the player's current coordinates and board with the\ncoordinates and board stored in the given slot, or slot number\none if none is given. The third form also duplicates the\ncurrent Robot where the player used to be; this Robot then\nstarts its program at the first command. This command will do\ntransition fades on activation, even if the destination is the\nsame board.\n\n:_f2:~AFILLHEALTH\n\nThe player's health is filled to its maximum.\n\n:_cA:~ACLEAR MESG\n\nThe message line, if currently displayed, stops displaying its\ncurrent message.\n\n:_m1:~AMESSAGE ROW #\n\nThe top row of the message line is moved to the stated row. The\nrange is 0 to 24.\n\n:_sN:~ASET MESG COLUMN #\n\nChanges the message line to display its messages starting from\nthe given column. The range is 0 to 79 and the changes affect\neach row. (The actual message will start at this location, not\nthe message edge.)\n\n:_c1:~ACENTER MESG\n\nChanges the message line to center its messages on its rows.\n\n:_l4:~ALOAD CHAR SET \"file\"\n:_l5:~ALOAD PALETTE \"file\"\n\nLoads in the specified character set or palette, exported using\nAlt+X from the world editor. The new character set or palette\nis permanent and affects the entire world until a new one is\nloaded. These files also can be loaded from a subdirectory with\nthe syntax of \"\"dir\"\\\\\"file\"\" or \"\"dir\"/\"file\"\" (without the\ninner quotes).\n\nCharacter sets can be loaded to the middle of the current\ncharset by prefixing the \"file\" name with +offset, where offset\nis a hexadecimal number, or @@offset where offset is a decimal\nnumber. Up to 14 additional charsets can be loaded for use with\nunbound sprites. To set and access these, set the decimal\noffset number to (256*extra charset number); hexadecimal\noffsetting cannot access additional charsets. Naturally,\noffsets within these charsets can be set as well by adding the\ndesired offset value to these numbers.\n\nThese commands can also take string inputs in place of\nhard-coded filenames.\n\n>#PARTIAL.HLP:par:Partial Character Sets\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n\n:_t5:~ATELEPORT PLAYER \"string\" # # ~F[~D!~F]\n\nTeleports the player to the given board at the stated x,y\ncoordinates. This happens instantaneously, even if this causes a\nmove between boards. Same-board TELEPORT PLAYER will trigger any\n\"justloaded\" labels and reset board time limits and entry\npositions, but will not otherwise act as a new board entry.\n\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n\n:_c4:~ACHANGE OVERLAY [color] [char] [color] [char]\n:_c5:~ACHANGE OVERLAY [color] [color]\n\nAll of the stated character and color on the overlay are\nchanged to the new character and color. The second form doesn't\ncare about the character, only the color.\n\n>#COMMANDS.HLP:col:Colors\n>#TOVERLAY.HLP:081:Editing and Using the Overlay\n\n:_m0:~AMOVE ALL [color] [thing] [param] [dir] ~F[~D!~F]\n\nAll instances of the stated thing with the color and parameter\ngiven are moved in the given direction. This can force objects\nonto lava that normally don't go onto lava, but cannot force any\nobjects onto goop.\n\n>#COMMANDS.HLP:col:Colors\n>#COMMANDS.HLP:dir:Directions\n\n:_o1:~AOPEN [dir]\n\nIf there is a gate or door in the given direction, it will\nbe opened. If the door is going to open towards the Robot, the\nRobot will be pushed out of the way if necessary and possible.\nIt is suggested to wait a couple cycles before then continuing\non through the door. (Gates aren't a problem.)\nIf the gate or door is locked, it will be treated as if the\nplayer attempted to open it. This means that whether the door is\nopened or not depends on the player's held keys, and if the\nRobot unlocks the door, the player will lose the relevant key.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_m7:~AMOD SAM # #\n\nPlays a sample (instrument) from the current module file. The\nfirst number states the frequency at which to play it; the\nsecond is the number of the sample, from 1 on.\n\nThis command will not work in current versions. Support for\nolder worlds using this command is implemented only for the XMP\nmodule engine (the engine used for most release builds).\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n\n:_l0:~ALOCKSCROLL\n:_u2:~AUNLOCKSCROLL\n\nLocks or unlocks scrolling on the current board. When scrolling\nis locked, the view will remain showing the same thing, even if\nthe player moves. The screen will not scroll.\n\n:_c0:~ACHAR EDIT [char] # # # # # # # # # # # # # #\n\nChanges the appearance of the given character, using a 14-number\ncode of 8-bit numbers derived from which pixels are set per\nhorizontal line. To insert these codes easily, use F5 in the\nRobot editor, select the desired character, and press ESC. This\ncommand can access extended character sets.\n\n:_m5:~AMOD FADE IN \"file\"\n\nFades in the stated module/OGG music file, from 0 volume to 255\nvolume. These files also can be loaded from a subdirectory with\nthe syntax of \"\"dir\"\\\\\"file\"\" or \"\"dir\"/\"file\"\" (without the\ninner quotes).\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n\n:_m6:~AMOD FADE OUT\n\nFades out the current module/OGG music file, from the current\nvolume to 0 volume. Although the mod will be inaudible, it will\nstill be the current music file after the fade out.\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n\n:_m4:~AMOD FADE # #\n\nFades the current module/OGG music file to a given volume at the\ngiven rate. The first number is the target volume, the second\nnumber is the speed at which to fade. All negative numbers and\nnumbers over 255 will wrap around.\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n\n:_m2:~AMISSILECOLOR [color]\n\nChanges the color of all missiles fired from that point on.\n\n>#COMMANDS.HLP:col:Colors\n\n:_pB:~APUSH [dir]\n\nPushes anything next to the Robot in the given direction one\nspace forward, if the object in that direction is pushable. The\nRobot itself does not move.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_sL:~ASET EDGE COLOR [color]\n\nChanges the color of the edging shown around the viewport.\n\n>#COMMANDS.HLP:col:Colors\n\n:_sM:~ASET MAXHEALTH #\n\nChanges the player's health limit, or maximum health rating.\n\n:_w2:~AWAIT MOD FADE ~F[~D!~F]\n\nWaits for the module/OGG music to finish its fading. Does\nnothing if music is not fading. (see MOD FADE IN, MOD FADE OUT,\nand MOD FADE)\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n>#COMMAND2.HLP:_m5:MOD FADE IN \"file\"\n>#COMMAND2.HLP:_m6:MOD FADE OUT\n>#COMMAND2.HLP:_m4:MOD FADE # #\n\n:_w3:~AWAIT PLAY ~F[~D!~F]\n\nWaits for all queued sound effects, if any, to finish playing.\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n\n:_a2:~AASK \"string\"\n\nBrings up a dialog box asking the player a question. The player\ncan select YES or NO. The Robot is then sent to either the YES\nor NO label, depending on the choice. If the player presses\nESC, the NO label is used.\n\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n\n:_a3:~AAVALANCHE\n\nEnacts the Avalanche potion/ring effect, placing boulders\nrandomly about the board. Often precedes the CHANGE c?? Boulder\np?? to c?? Explosion p?? command.\n\n>#BUILTINS.HLP:prx:Potion and Ring Effects\n\n:_b4:~ABECOME LAVAWALKER\n:_b2:~ABECOME NONLAVAWALKER\n\nSets whether the current Robot can or cannot move on lava.\nRobots can always move over fire.\n\n:_b6:~ABLIND #\n:_f1:~AFIREWALKER #\n:_f4:~AFREEZETIME #\n:_sS:~ASLOWTIME #\n:_w6:~AWIND #\n\nThe stated potion/ring effect activates for the given number of\ncycles. Potion/ring effects are global; that is, they affect the\nentire world. The only exception is with the global Robot, as\nFREEZETIME and SLOWTIME commands do not affect it.\n\n>#THEGLOBL.HLP:gbl:The Global\n>#BUILTINS.HLP:prx:Potion and Ring Effects\n\n:_c3:~ACHANGE CHAR ID # [char]\n\nChanges the character/color/number at the stated (numeric)\nlocation within the CHAR ID table. The CHAR ID table is\ninternal and is covered in another section.\n\n>#CHANGECH.HLP:1st:CHANGE CHAR ID - The CHAR ID Table\n\n:_c6:~ACHANGE SFX # \"string\"\n\nThe sound effect referenced by the given number is changed to\nthe new string. The numbers refer to the sound events editable\nthrough ALT+F.\n\n>#SOUNDEFX.HLP:1st:Sound and Music\n\n:_c8:~ACHANGE THIN ARROW CHAR [dir] [char]\n:_c7:~ACHANGE THICK ARROW CHAR [dir] [char]\n\nChanges the character used to represent thin arrows (for guns)\nor thick arrows (for pushers and spikes) for the specified\ndirection.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_iP:~AINPUT STRING \"string\"\n\nAsks the player to input a string, presenting the given string\nas a prompt. The inputted string is stored in memory. This\nstring can be displayed within box messages or on the message\nrow by using &INPUT& within a message. Inputted strings are\nlocal to the current board.\n\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n\n:_iD:~AIF STRING \"string\" \"label\"\n:_iF:~AIF STRING NOT \"string\" \"label\"\n\nJumps to the given label if the inputted string is or is not\nequal to the given string. Case is ignored.\n\n:_iE:~AIF STRING MATCHES \"string\" \"label\"\n\nIf the inputted string matches a given pattern, jump to the\ngiven label. (Case is ignored.) The pattern is a series of\ncharacters and symbols to match. Characters must match exactly.\nThe following symbols apply:\n\n.# matches any number from 0-9\n_ matches any letter from A-Z or a-z\n? matches any character\n* matches the remainder of the input string\n\n:_i9:~AIF FIRST STRING \"string\" \"label\"\n\nIf the first word in the inputted string (i.e. everything up\nto the first space (char 32) or end of input) matches the given\nstring, jump to the given label.\n\n:_cB:~ACLIP INPUT\n\nThis command chops off the first word of the inputted string\n(everything before and including the first instance of a\nspace, aka char 32). For example, \"Hello there\" will now read\nin memory as \"there\".\n\n:_iM:~AINC \"$string\" \"text\"\n:_iN:~AINC \"$string\" \"$string2\"\n\nThis command appends the given text or string to the end of a\ngiven string.\n\n:_dA:~ADEC \"$string\" #\n\nThis command clips the given string by the amount of characters\ngiven. Decreasing by negative numbers will not add characters to\nthe string.\n\n:_iO:~AIF \"$string\" (equality) # \"label\"\n:_iK:~AIF \"$string\" (equality) \"text\" \"label\"\n:_iL:~AIF \"$string\" (equality) \"$string2\" \"label\"\n\nThese commands jump to a given label if the string matches the\ngiven equality conditions (ONLY equal or not equal; others such\nas less than are automatically parsed as FALSE). These commands\ncompare the given string to a number, a line of text, or\nanother string respectively.\n\n:_iG:~AIF cNN Sprite_Colliding pNN x y \"label\"\n\nThis command, when the color is c??, determines whether the\nsprite numbered NN would collide if the sprite is moved by x\nhorizontally and y vertically. If it would, the program goes to\nthe given label and spr_clistN counters are set to the detected\ncollisions. c values other than c?? check if the sprite would\ncollide with anything currently at absolute x,y coordinates\ninstead. If the sprite numbered NN is unbound, movement amounts\nand absolute coordinates will be in pixels.\n\n:_iH:~AIF c?? Sprite pNN x y \"label\"\n\nThis command checks if the sprite numbered NN is currently at\nthe location x,y; if it is, the program goes to the given\nlabel. For multi-character sprites, the label is jumped to if\nany part of the sprite is at the given coordinates. Use p?? to\ncheck for any sprite. If p?? is used, SPR_NUM is also set to\nthe lowest sprite number at that location (if any).\n\nFor static sprites, this check is valid against the coordinates\nwhere they visually appear, not their actual locations.\n\n:_cF:~ACOLOR INTENSITY # PERCENT\n:_cG:~ACOLOR INTENSITY # # PERCENT\n\nThe first command changes the intensity of the entire palette\nfrom 0 to 255 percent (100 percent is a palette's default). The\nsecond command only changes one color, with the first number\nbeing the chosen color and the second number the intensity.\n\n:_cK:~ACOPY CHAR [char] [char]\n\nThe character picture of the first character is copied to the\nsecond character. This command can access extended character\nsets.\n\n:_d5:~ADISABLE MESG EDGE\n:_e7:~AENABLE MESG EDGE\n\nDisables or enables the edging on the message line. When\nenabled, all messages have a space (char 32) tacked onto each\nend for display clarity. This is enabled by default.\n\n:_d6:~ADISABLE SAVING\n:_e8:~AENABLE SAVING\n:_e9:~AENABLE SENSORONLY SAVING\n\nDisables or enables manual saving on this board, or enables\nmanual saving on sensors only.\n\n>#SENSORSW.HLP:094:Sensors\n\n:_f3:~AFLIP CHAR [char] [dir]\n\nThe character picture of the stated character is flipped\nvertically if the direction is north/south, or horizontally if\nthe direction is east/west. This command can access extended\nchar sets.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_o2:~AOVERLAY ON\n:_o3:~AOVERLAY STATIC\n:_o4:~AOVERLAY TRANSPARENT\n\nTurns the overlay on (to normal mode), to static mode, or to\ntransparent mode.\n\n>#TOVERLAY.HLP:081:Editing and Using the Overlay\n\n:_rE:~AROTATECW\n:_rF:~AROTATECCW\n\nRotates all pushable items around the Robot in a clockwise or\ncounter-clockwise direction, respectively. This is done in the\nsame manner as a CW Rotate or CCW Rotate object, except that\nthese commands rotate objects only one space.\n\n:_s4:~ASCROLL CHAR [char] [dir]\n\nThe character picture of the stated character is scrolled one\npixel in the given direction, with wraparound in effect. This\ncommand can access extended char sets, but it will not work as\nexpected in SMZX modes.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_s5:~ASCROLLARROW COLOR [color]\n:_s6:~ASCROLLBASE COLOR [color]\n:_s7:~ASCROLLCORNER COLOR [color]\n:_s8:~ASCROLLPOINTER COLOR [color]\n:_s9:~ASCROLLTITLE COLOR [color]\n\nChanges the color used to represent a specific element of the\nScroll and box-message system:\n\nARROW   = The arrows used to denote options\nBASE    = The overall box and text color\nCORNER  = The color of the corners of the box, and of the\n          options in the option box at the bottom\nPOINTER = The color of the pointers on the left and right\nTITLE   = The color of the scroll/box title\n\n>#COMMANDS.HLP:col:Colors\n\n:_s0:~ASCROLLVIEW [dir] #\n:_sA:~ASCROLLVIEW POSITION # #\n:_r0:~ARESETVIEW\n\nThe first two commands jump the current view a number of spaces\nin one direction or until the given coordinates are in the\nupper-left corner. RESETVIEW resets the view to normal, showing\nthe player in the center of the screen when possible. All of\nthese change to the target view instantaneously.\n\n>#COMMANDS.HLP:dir:Directions\n\n:_sK:~ASET COLOR # # # #\n\nThe given color (stated by the first number) is changed to the\nRGB values of the next three numbers. The RGB values must range\nfrom 0 to 63. These are the same numbers shown in the palette\neditor.\n\n:_sU:~ASTATUS COUNTER # \"counter\"\n\nThe status screen counter with the stated position # is changed\nto the given counter. Valid positions for status counters range\nfrom 1 (top slot) to 6 (bottom slot).\nStatus counters have a name limit of 14 characters. If the given\ncounter has a name over 14 characters long, this command will be\nignored.\n\n>#COUNTERS.HLP:1st:Counters, Built-in Counters and Local Counters\n\n:_sV:~ASWAP WORLD \"file\"\n\nTransfers play to the starting board of the stated world file,\nwith the target world starting as freshly loaded. In contrast to\na launch from the title screen, a SWAP WORLD will retain all\ncounters, items, keys, strings, etc. from the current world.\nThe target file can be the same world that is currently running;\nthis is a simple way to \"reset\" the world while keeping progress\nin the form of items and counters.\n\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n\n:_pE:~APUT [color] [char] OVERLAY # #\n:_w7:~AWRITE OVERLAY [color] \"string\" # #\n\nPuts the given color/character at the given coordinates on the\noverlay, or writes a message in the given color starting at the\ngiven coordinates on the overlay.\n\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n>#TOVERLAY.HLP:081:Editing and Using the Overlay\n>#COMMANDS.HLP:col:Colors\n\n:_v1:~AVIEWPORT # #\n\nMoves the viewport's upper-left corner to the given screen\ncoordinates. X ranges from 0 to 79, and Y ranges from 0 to 24.\n\n:_v2:~AVIEWPORT SIZE # #\n\nChanges the size of the viewport. X size must be from 1 to 80,\nand Y size must be from 1 to 25.\n\n>#COMMANDR.HLP:1st:Command Reference (Part I)\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#BULLETTY.HLP\n:1st:\n$~9Bullet Types\n\nThere are three different types of bullets in MegaZeux: Player,\nEnemy, and Neutral. The player shoots Player bullets, enemies\nshoot Enemy bullets, and ricocheted bullets become Neutral\nbullets. A Robot can shoot any type of bullet, according to its\nBULLETTYPE counter, but it defaults to Neutral bullets.\n\nAll fired Enemy bullets, even those shot from Robots, become\nNeutral bullets when the option \"Enemies' bullets can hurt\nenemies\" is set. Only pre-placed Enemy bullets will remain Enemy\nbullets in this case.\n\nPlayer bullets can only hurt enemies. Enemy bullets can only\nhurt the player. Neutral bullets can hurt either. Robots go to\ndifferent labels when shot with each one, or to a generic SHOT\nlabel if there are none of the specific PLAYERSHOT, ENEMYSHOT,\nor NEUTRALSHOT labels.\n\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#CHANGECH.HLP\n:1st:\n$~9CHANGE CHAR ID - The CHAR ID Table\n\nThe CHAR ID table has many uses. The first area of usage - the\nCHANGE CHAR ID # [char] command - basically changes the value\n(a character, color, or number) of a given type of item. These\nvalues correspond to the Global Chars or Damage tables from\nGlobal Info. The first number in the command states what is to\nchange. (Note - if noted, the number corresponds to a color or\ndamage. Otherwise, it is a character.) These numbers are also\nlisted next to things in the Global Edit Chars menus.\n\nThe second area - using the BOARD_ID, BIDx,y or UIDx,y counters\n- will change an actual item instead of that entry's value;\ni.e. these counters transform the actual item (and ONLY that\nitem) at a specific pair of coordinates from one type to\nanother.\n\nWARNING: Changing certain CHAR ID values will have decidedly\nspecial effects:\n\n~A*~FChanging the value of any Custom type will turn ALL of that\ntype into the given character; same with text.\n~A*~FThere is a special exception to this, though. If the value is\n(char) 255, then all items of that type act like Custom objects\nand assume a character based on their parameter (e.g. p01\nobjects will use character 1). This can allow built-ins of the\nsame type that display different characters in-game at the same\ntime. Some built-ins may show interesting effects if set to\nthis, such as gaining animation frames.\n~A*~FChanging the CHAR IDs of objects with animations or four\ndirectional characters will fix ALL animations/chars of that\nobject type to the given character.\n~A*~FIt is inadvisable to use any UNUSED values.\n~A*~FUsing CHAR ID to change between actual items only works\nwith values <122. For example, changing something into Ice by\nsetting BOARD_ID to 160 will not work, but setting BOARD_ID to\n25 will. However, there is no such restriction with changing\nCHAR ID table values using CHANGE CHAR ID.\n~A*~FDamage values start at 327. Not all values are listed, because\ntechnically you can assign damage values for things that cannot\ngive damage (i.e. pretty much everything not listed in the\ntable). If you really want to manipulate damage values for such\nitems, to find the CHAR ID damage number for an item, just add\n327 to it.\n\nNumber  Changes\n------  -------\n0       Space\n1       Normal\n2       Solid\n3       Tree\n4       Line\n5       Custom Block\n6       Breakaway\n7       Custom Break\n8       Boulder\n9       Crate\n10      Custom Push\n11      Box\n12      Custom Box\n13      Fake\n14      Carpet\n15      Floor\n16      Tiles\n17      Custom Floor\n18      Web\n19      Thick Web\n20      Still Water\n21      N Water\n22      S Water\n23      E Water\n24      W Water\n25      Ice\n26      Lava\n27      Chest\n28      Gem\n29      Magic Gem\n30      Health\n31      Ring\n32      Potion\n33      Energizer\n34      Goop\n35      Ammo\n36      Bomb\n37      Lit Bomb\n38      Explosion\n39      Key\n40      Lock\n41      Door\n42      Open Door\n43      Stairs\n44      Cave\n45      CW Rotate\n46      CCW Rotate\n47      Gate\n48      Open Gate\n49      Transport\n50      Coin\n51      N Moving Wall\n52      S Moving Wall\n53      E Moving Wall\n54      W Moving Wall\n55      Pouch\n56      Pusher\n57      Slider NS\n58      Slider EW\n59      Lazer\n60      Lazer Gun\n61      Bullet\n62      Missile\n63      Fire\n64      UNUSED\n65      Forest\n66      Life\n67-70   Whirlpool Anim 1-4\n71      Invis Wall\n72      Ricochet Panel\n73      Ricochet\n74      Mine\n75      Spike\n76      Custom Hurt\n77      Text\n78      Shooting Fire\n79      Seeker\n80      Snake\n81      Eye\n82      Thief\n83      Slimeblob\n84      Runner\n85      Ghost\n86      Dragon\n87      Fish\n88      Shark\n89      Spider\n90      Goblin\n91      Spitting Tiger\n92      Bullet Gun\n93      Spinning Gun\n94      Bear\n95      Bear Cub\n96      UNUSED\n97      Missile Gun\n98      Sprite\n99      Sprite Collision\n100     Image File\n101-121 UNUSED\n122     Sensor\n123     Pushable Robot\n124     Robot\n125     Sign\n126     Scroll\n127     Player\n128-143 Thin Lines (Web) (see below)\n144-159 Thick Lines (Thick Web, Lines) (see below)\n160     Ice\n161-163 Ice Anim 1-3\n164-166 Lava Anim 1-3\n167     Small Ammo (below 10)\n168     Large Ammo (above or equal to 10)\n169-175 Lit Bomb Anim 1-7\n176-183 Energizer Color Anim 1-8\n184-187 Explosion Colors 1-4\n188     Horizontal Door\n189     Vertical Door\n190-193 CW Rotate Anim 1-4\n194-197 CCW Rotate Anim 1-4\n198-229 UNUSED\n230-233 Transport N Anim 1-4\n234-237 Transport S Anim 1-4\n238-241 Transport E Anim 1-4\n242-245 Transport W Anim 1-4\n246-249 Transport All-Dir Anim 1-4\n250     Thick Arrow N (Pusher/Spike)\n251     Thick Arrow S (Pusher/Spike)\n252     Thick Arrow E (Pusher/Spike)\n253     Thick Arrow W (Pusher/Spike)\n254     Thin Arrow N (Guns)\n255     Thin Arrow S (Guns)\n256     Thin Arrow E (Guns)\n257     Thin Arrow W (Guns)\n258-261 Horizontal Lazer Anim 1-4\n262-265 Vertical Lazer Anim 1-4\n266-271 Fire Anim 1-6\n272-277 Fire Color Anim 1-6\n278-281 Life Anim 1-4\n282-285 Life Color Anim 1-4\n286     Ricochet Panel \\\n287     Ricochet Panel /\n288-289 Mine Anim 1-2\n290-291 Shooting Fire Anim 1-2\n292-293 Shooting Fire Color Anim 1-2\n294-297 Seeker Anim 1-4\n298-301 Seeker Color Anim 1-4\n302-305 Whirlpool Color Anim 1-4\n306-309 Player Bullets, NSEW\n310-313 Neutral Bullets, NSEW\n314-317 Enemy Bullets, NSEW\n318-321 Player, NSEW\n322     Player Color\n323     Missile Color\n324     Player Bullet Color\n325     Neutral Bullet Color\n326     Enemy Bullet Color\n353     Lava Damage\n365     Explosion Damage\n386     Lazer Damage\n388     Bullet Damage\n389     Missile Damage\n390     Fire Damage\n402     Spike Damage\n403     Custom Hurt Damage\n405     Shooting Fire Damage\n406     Seeker Damage\n407     Snake Damage\n410     Slimeblob Damage\n411     Runner Damage\n412     Ghost Damage\n413     Dragon Damage\n414     Fish Damage\n415     Shark Damage\n416     Spider Damage\n417     Goblin Damage\n418     Spitting Tiger Damage\n421     Bear Damage\n422     Bear Cub Damage\n\nTo change a character, use the following form:\n\n~ACHANGE CHAR ID # [char]~F\n\n.# is the number corresponding to the character that you want to\nchange, or a counter representing that number. [char] is the\ncharacter (or counter representing the character) that you wish\nto change it to.\n\nTo change a color, use the following form:\n\n~ACHANGE CHAR ID # [color]~F\n\n.# is the same as above, but corresponding to a color. [color]\nis the color or a counter representing the color that you wish\nto change it to.\n\nTo change damage values, use a number or counter in place of\nthe [char] or [color].\n\nAfter typing in a [color] or damage value, it may change to a\ncharacter representation, but the value will still remain the\nsame.\n\nTo change line characters:\n\nTake the base number for the type of line you wish to change\n(128 for thin, 144 for thick) and add one of the following\nvalues to represent which sides are connected via other web or\nlines:\n\n0  -\n1  N\n2  S\n3  N S\n4  E\n5  E N\n6  E S\n7  E N S\n8  W\n9  W N\n10 W S\n11 W N S\n12 W E\n13 W E N\n14 W E S\n15 W E N S\n\n>#COMMAND2.HLP:_c3:CHANGE CHAR ID # [char]\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#BUILTINL.HLP\n:1st:\n$~9Built-in Labels\n\nRobotic has many built-in labels (messages) that are activated\non certain external events. Each label, and what triggers it,\nis listed below.\n\nSubroutine versions of most labels exist. If a built-in label\nhas a subroutine version, [#] will be listed to the right of\nits name. If both forms exist, they will both be triggered,\nwith the subroutine version being triggered first, and the\nnormal version triggered as the subroutine is left.\n\nSpecial care is required when dealing with built-in subroutine\nlabels. To prevent abuse of the stack, using LOCKPLAYER and/or\nZAP to keep subroutine calls to an absolute minimum is highly\nadvised. Subroutines themselves are explained in another\nsection.\n\n>#SUBROUTE.HLP:sub:Subroutines\n\n~ETOUCH [#]\n\nThis is sent when the player touches the Robot, that is, when\nthe player is next to it and tries moving into it. If the player\nis locked in that direction, this label is not sent.\n\n~ETHUD [#]\n\nThis is sent when the Robot is walking and it runs into a\nnon-pushable obstacle. This label is not sent when the obstacle\nis hit during a ~EGO [dir] #~F or the ~E/ \"string\"~F command. THUD\nignores LOCKSELF. This label will be constantly triggered if the\ncondition persists.\n\n>#COMMANDR.HLP:_w5:WALK [dir]\n\n~EEDGE [#]\n\nThis is sent when the Robot is walking and it runs into the\nedge of the board. If EDGE is not found, the Robot is sent to\nTHUD. This label is not sent when the edge of the board is hit\nduring the ~EGO [dir] #~F or the ~E/ \"string\"~F commands. EDGE\nignores LOCKSELF. This label will be constantly triggered if the\ncondition persists.\n\n>#COMMANDR.HLP:_w5:WALK [dir]\n\n~EBOMBED [#]\n\nThis is sent when the Robot is hit with an explosion.\n\n~EKEY? [#]\n\nThis is sent when the player presses the given key. ? should be\nreplaced with the key to be scanned for. Characters a to z and 0\nto 9 work as expected. Shifted alphabetic characters will NOT\nwork, and will actually scan as their non-shifted counterparts.\nAny other character that can be outputted directly, such as \";\"\nor \"*\" or \"=\", will work, and \"key \" will detect the space bar.\nOther characters can be entered by pressing F3 and selecting\nthem from the grid, but they are usually not guaranteed to work.\nSpecial-case characters that can work with KEY? are character 8\n(BackSpace), character 9 (Tab), character 27 (Esc), character\n127 (Delete) and character 13 (Enter, though there's a better\nlabel just for this mentioned in the very next entry).\n\nThere is another way to scan for key presses: using counters.\nKey-scanning counters such as KEY_PRESSED, KEY, and KEY_CODE\nare detailed in the Miscellaneous Counters section of the help\nfile.\n\n>#COUNTERS.HLP:msc:Miscellaneous Counters\n\n~EKEYENTER [#]\n\nThis is sent when the player presses the Enter key.\n\n~EINVINCO [#]\n\nThis is sent when the player grabs an energizer.\n\n~EPUSHED [#]\n\nThis is sent when a pushable Robot is pushed by something. If\nsomething tries to push the Robot and can't, this label is not\nsent.\n\n~EENEMYSHOT [#]\n~EPLAYERSHOT [#]\n~ENEUTRALSHOT [#]\n\nOne of these is sent when the Robot is shot, correlating to the\ntype of bullet used. If the appropriate label is not found, the\nRobot is sent to SHOT instead.\n\n~ESHOT [#]\n\nThis is sent when the Robot is shot by a bullet and the more\nspecific label cannot be found.\n\n~EPLAYERHIT [#]\n\nThis is sent when the player is shot with a bullet. If not\nfound, the Robot is sent to PLAYERHURT instead.\n\n~EPLAYERHURT [#]\n\nThis is sent when the player is hurt by anything other than a\nbullet. (This includes when Robots successfully TAKE HEALTHS.)\nBullets send the Robot to PLAYERHIT, and only send it to\nPLAYERHURT if PLAYERHIT cannot be found.\n\n~EPLAYERDIED [#]\n\nThis is sent when the player dies, whether through health being\nlowered to/below 0 or through the ENDLIFE command. This will not\ntrigger when the LIVES counter is decremented.\n\nNOTE: Unlike other built-in labels, this label is jumped to on\nthe cycle after it is sent, not the same cycle.\n\n~ESPITFIRE [#]\n\nThis is sent when the Robot is hit with shooting fire.\n\n~EJUSTLOADED\n\nThis is sent when the world was just loaded or restored from a\nsaved game. This is also sent upon swapping worlds; in that\ncase, it will only trigger if no JUSTENTERED label is set.\n\n~EJUSTENTERED\n\nThis is sent when the board was just entered or the world was\njust started on this board. This is also sent upon swapping\nworlds; in that case, if no JUSTENTERED label is set,\nJUSTLOADED is triggered instead.\n\n~ESENSORON\n\nThis is sent by a Sensor when it is stepped upon by the player,\nor when a Sensor moves beneath the player.\n\n>#SENSORSW.HLP:094:Sensors\n\n~ESENSORTHUD\n\nThis is sent by a Sensor when it tries to move and is blocked.\n\n>#SENSORSW.HLP:094:Sensors\n\n~ESENSORPUSHED\n\nThis is sent by a Sensor when it is pushed by something.\n\n>#SENSORSW.HLP:094:Sensors\n\n~ELAZER [#]\n\nThis is sent when the Robot is hit with a lazer.\n\n~EGOOPTOUCHED [#]\n\nThis is sent when the player touches Goop.\n\n~EYES\n\nThis is sent when the YES button is selected in an ASK box.\n\n~ENO\n\nThis is sent when either the NO button is selected in an ASK\nbox, or when ESC exits an ASK box.\n\n>#COMMAND2.HLP:_a2:ASK \"string\"\n\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#SENSORSW.HLP:094:Sensors\n>#MAIN.HLP:072:Table of Contents\n#COUNTERS.HLP\n:1st:\n$~9Counters, Built-in Counters and Local Counters\n\nCounters are built-in variables, or stored values with names.\nEach counter has a name, such as \"Yellow\" or \"Apple Pies\" or\neven \"100\". A counter can hold a value from -2147483648 to\n2147483647, in most cases. A counter is \"active\" when it has a\nvalue other than 0. There can be a practically unlimited amount\nof active counters at any one time, in addition to the built-in\ncounters. Counters retain values between different boards.\n\nLocal counters, on the other hand, only hold their values for\none Robot. These are useful because multiple Robots can set\nlocal counters with the exact same names without causing\nconflicts. There are several local counters explicitly reserved\nfor use; these are the \"local\" & \"local2\" through \"local32\"\ncounters.\n\nTo display a counter's number, enclose the counter in\nampersands. (e.g. &counter&). This displays the counter value\nin decimal. Sometimes hexadecimal is needed, like in certain\nMegaZeux commands. To output a counter number in hex, add a\n+ to the front of the enclosed counter (e.g. &+hexcount&).\nLastly, other MegaZeux commands might require a two-digit\nhex number; for these, add a # to the front of the enclosed\ncounter (e.g. &#twodigithex&). Note that in this case, only the\ntwo most significant digits will be used (e.x. the number\n'abcdef' becomes 'AB') and single-digit numbers will gain a\nleading zero (e.g. the number '8' will become '08').\n\nAlso, characters can be used as counters in commands. For\nexample: SET \"boom\" to '0' would come out as SET \"boom\" to 48.\nIt's quite a difference!\n\nCommands that check or modify counters will accept numbers\nranging from -32768 to 32767. To use numbers outside of this\nrange with these commands, one must put the number in\nparentheses. For example: SET \"bonus\" to \"(1000000)\".\n\nThe following is a list of built-in counters, which all have\nspecial meanings. There are four types of built-in counters:\nGlobal, which affect everything; Local, which are different for\neach Robot and require special handling for other Robots to\nmanipulate; Board, which are specific to the current board; and\nUniversal, which persist for the entire time MegaZeux is\nrunning. Some built-in counters are read-only; a few are\nwrite-only.\n\nMegaZeux utilizes a large number of built-in counters to\nmaximize flexibility. For this reference, they have been\ndivided into distinct sections.\n\n>#COUNTERS.HLP:ply:Player Counters\n>#COUNTERS.HLP:rob:Robot Counters\n>#COUNTERS.HLP:brd:Board Counters\n>#COUNTERS.HLP:auc:Audio Counters\n>#COUNTERS.HLP:mth:Mathematical Counters\n>#COUNTERS.HLP:fac:File Access Counters\n>#COUNTERS.HLP:spc:Sprite Counters\n>#COUNTERS.HLP:stc:String Counters\n>#COUNTERS.HLP:szc:Super MZX Counters\n>#COUNTERS.HLP:cha:Character Edit Counters\n>#COUNTERS.HLP:nkp:Mouse/Joystick Counters\n>#COUNTERS.HLP:def:Defaults Counters\n>#COUNTERS.HLP:vlc:Vlayer Counters\n>#COUNTERS.HLP:msc:Miscellaneous Counters\n\n$Player Counters\n:ply:\n~BGEMS, AMMO, LOBOMBS, HIBOMBS, COINS, LIVES, HEALTH\n\nThese contain the current number of the stated item that the\nplayer currently has.\n\n~BINVINCO\n\nThe number of cycles of invincibility the player currently has.\n\n~BSCORE\n\nThe player's current score.\n\n~BPLAYERLASTDIR\n\nThe last direction the player moved: 0 for none, or 1 2 3 4 for\nN S E W. Used to determine ice slippage and certain other\neffects.\n\n~BPLAYERFACEDIR\n\nThe direction the player is facing: 0 1 2 3 for N S E W. Used\nto determine which player character to display.\n\n~BPLAYERX (read-only)\n~BPLAYERY (read-only)\n\nThe x or y coordinate of the player, respectively.\n\n$Robot Counters\n:rob:\n~BPLAYERDIST (read-only, local)\n\nThe distance from the Robot to the player, in spaces. The sum\nof HORIZPLD and VERTPLD.\n\n~BHORIZPLD (read-only, local)\n~BVERTPLD (read-only, local)\n\nThe horizontal or vertical distance from the Robot to the\nplayer, in spaces.\n\n~BTHISX (read-only, local)\n~BTHISY (read-only, local)\n\nThe current X or Y coordinate of the Robot. Affected by\nprefixes - If a command that uses THISX and THISY is prefaced\nwith a REL PLAYER command, THISX and THISY will be generated\nbased on the player's current position. For example, if the\nplayer is at (2,2) and the Robot is at (5,5), THISX and THISY\nwill be 3. If the player is at (8,8), THISX and THISY will be\n-3. If a command that uses THISX and THISY is prefaced with\na REL COUNTERS command, THISX and THISY will be generated\nbased on XPOS and YPOS. Basically, THISX and THISY will be\nset to THISX-XPOS and THISY-YPOS.\n\n>#COMMAND2.HLP:pre:REL COUNTERS\n>#COMMAND2.HLP:_r2:REL PLAYER\n\n~BBULLETTYPE (local)\n\nThe current type of bullet shot by the Robot: 0 for Player, 1\nfor Neutral, and 2 for Enemy. Technically, this counter can\nhold any number from 0 to 255, but only 0, 1 and 2 have\ndefined behavior. The others essentially set the Robot to shoot\nEnemy bullets, if actually used to affect shot bullets.\n\n>#BULLETTY.HLP:1st:Bullet Types\n\n~BCOMMANDS\n\nThe number of commands executed per Robot cycle (40 is default).\nThis number is global. It can severely slow MegaZeux if set too\nhigh and coupled with poor coding (especially busy loops lacking\ncycle-ending commands).\n\n~BLOOPCOUNT (local)\n\nThe counter used for looping commands in Robotic - LOOP START,\nLOOP #, and ABORT LOOP.\n\n>#COMMANDR.HLP:_lC:LOOP START\n\n~BROBOT_ID (read-only, local)\n\nThe internal ID of the Robot. These numbers match the parameters\nof Robots on the board, but are in decimal. So, for example, if\na Robot on the board has a parameter of p0a, its ROBOT_ID would\nbe 10.\n\n~BRID<Robot name> (read-only)\n~BROBOT_ID_<Robot name> (read-only)\n\nBoth of these give the robot_id number of the given named\nRobot. In the case of multiple Robots with the same name, the\nID number corresponds to only one of them. If no Robots have the\nname, or if the name in question only belongs to the global\nRobot, -1 is given.\n\nBEWARE: The existence of this counter makes it impossible to\nuse words which start with \"rid\" as counters. The port will NOT\nfall through and give the values of any counter that starts\nwith \"rid\"; instead, it ends at -1 (since the Robot the counter\nis trying to find, like \"dles\" (for \"riddles\"), will most\nlikely not exist). Older games will allow counters with names\nlike this, but only for compatibility reasons.\n:rnc:\n~BRn.<counter>\n\nContains the value of the given counter (without the angle\nbrackets) in the Robot with the robot_id number n. If no Robot\nexists with this robot_id, the counter will return -1.\n\n~BTHIS_COLOR (read-only, local)\n~BTHIS_CHAR (read-only, local)\n\nThe current color and character of the Robot, respectively.\n\n~BLAVA_WALK (local)\n\nControls whether the Robot can walk on lava or not. Setting\nthis to 1 is the equivalent of using the BECOME LAVAWALKER\ncommand. Setting it to 0 is the same as BECOME NONLAVAWALKER.\nLike BULLETTYPE, this counter can hold any value from 0 to 255,\nbut only 0 and 1 have defined behavior. This was a popular\nlocal counter before the local counters were plentiful, as most\nRobots didn't deal with lava.\n\n>#COMMANDR.HLP:_b4:BECOME LAVAWALKER\n\n~BLOCAL (local)\n~BLOCAL2 (local)\n...\n~BLOCAL32 (local)\n\nThe non-special local counters each Robot can use. In effect,\nany number can be used, not just 2-32; the counter accessed by\nit would be N%32 (so, for example, any change to \"local0\" would\nhave the same effect as changing \"local32\"). LOCAL is the same\nas LOCAL1.\n\n~BGOOP_WALK (local)\n\nControls whether the Robot can walk on goop or not. A value of 0\nprevents walking on goop; a non-zero value allows it. This\ncounter has an effective range of 0 to 255.\n\n$Board Counters\n:brd:\n\n~BBOARD_X\n~BBOARD_Y\n\nBOARD_X and BOARD_Y represent the x,y coordinates of a target\nlocation on the current board. Several other counters use the\nvalues of these counters to read or alter parts of the board.\nThese counters default to 0.\n\n~BBOARD_COLOR (read-only, board)\n~BBOARD_CHAR (read-only, board)\n\nReads the color and character, respectively, of the object\nlocated at \"board_x\" \"board_y\". If board_x or board_y is an\ninvalid coordinate, BOARD_COLOR and BOARD_CHAR access the\nclosest valid coordinate. For example, if board_y is 60 and the\nboard is 50 characters tall, board_y is treated as if it were\n49.\n\nOne thing to note is that BOARD_COLOR will not take background\ncolor 0's transparency into account. For example, assuming\ndefault colors, if a white-on-black Robot walks onto a\nyellow-on-blue floor tile, the Robot appears as white-on-blue on\nthe screen, but this counter will still report it as\nwhite-on-black instead of the displayed colors.\n\n~BBOARD_ID\n\nReads or alters the CHAR ID value of the object located at\n\"board_x\" \"board_y\". If board_x or board_y is an invalid\ncoordinate, BOARD_ID accesses the closest valid coordinate.\nFor example, if board_x is 60 and the board is 50 characters\nwide, board_x is treated as if it were 49.\n\n>#CHANGECH.HLP:1st:CHANGE CHAR ID - The CHAR ID Table\n\n~BBOARD_PARAM\n\nReads or alters the parameter of the object located at \"board_x\"\n\"board_y\". If board_x or board_y is an invalid coordinate,\nBOARD_PARAM accesses the closest valid coordinate. For example,\nif board_y is a negative number, board_y is treated as if it\nwere 0.\n\n~BBOARD_W (read-only, board)\n~BBOARD_H (read-only, board)\n\nThe current board's width and height, respectively.\n\n~BTIME (board)\n\nThe current timer value, between 0 and the time limit for the\ncurrent board. If there is no time limit set, setting this\ncounter will cause the timer to tick down but turn off after it\nruns out.\n\n~BTIMERESET (board)\n\nThe time limit for the current board. 0 for no time limit.\nNOTE: This timer was erroneously called \"TIMERSET\" in some\nversions. MZX will NOT use TIMERSET as a synonym for this\ncounter in current versions.\n\n~BSCROLLEDX (read-only, board)\n~BSCROLLEDY (read-only, board)\n\nThe top-left corner of the screen's x or y coordinates,\nrespectively.\n\n~BBCHx,y (read-only, board)\n\nAllows the reading of single characters to the board with\nregards to coordinates x,y. The comma must be included. Returns\n-1 for invalid coordinates.\n\n~BBCOx,y (read-only, board)\n\nAllows the reading of single colors to the board with regards\nto coordinates x,y. The comma must be included.  Returns -1 for\ninvalid coordinates. Much like BOARD_COLOR, BCOx,y will not take\ntransparency into account.\n\n~BBIDx,y (board)\n\nAllows reading and writing of the CHAR ID of an object on the\nboard at the coordinates x,y. The comma must be included.\nReturns -1 for invalid coordinates.\n\n~BBPRx,y (board)\n\nAllows reading and writing parameter values of an object on the\nboard at the coordinates x,y. The comma must be included.\nReturns -1 for invalid coordinates.\n\n~BUCHx,y (read-only, board)\n\nAllows the reading of single characters on the under layer at\nthe coordinates x,y. The comma must be included.\n\n~BUCOx,y (read-only, board)\n\nAllows the reading of single colors on the under layer at the\ncoordinates x,y. The comma must be included.\n\n~BUIDx,y (board)\n\nAllows reading and writing of the CHAR ID of an object on the\nunder layer at the coordinates x,y. The comma must be included.\n\n~BUPRx,y (board)\n\nAllows reading and writing parameter values of an object on the\nunder layer at the coordinates x,y. The comma must be included.\n\nAll of the above four under layer counters will return -1 if\nnothing is on the under layer at that location, as well as for\ninvalid coordinates. Floor-type objects by themselves (such as\nfake, floor, carpet, ice, lava, etc) count as being on the board\nlayer if nothing is on them.\n\n~BOVERLAY_X (board)\n~BOVERLAY_Y (board)\n~BOVERLAY_CHAR (read-only, board)\n~BOVERLAY_COLOR (read-only, board)\n\nThese work in the same way as the BOARD_xxx counters above, but\ndeal with the overlay instead. OVERLAY_CHAR and OVERLAY_COLOR\nreturn -1 if the overlay is off or if the coordinates are\ninvalid.\n\n~BOCHx,y (read-only, board)\n\nAllows the reading of single characters on the overlay at the\ncoordinates x,y. The comma must be included. Returns -1 if the\noverlay is off or if the coordinates are invalid. \n\n~BOCOx,y (read-only, board)\n\nAllows the reading of single colors on the overlay at the\ncoordinates x,y. The comma must be included. Returns -1 if the\noverlay is off or if the coordinates are invalid.\n\n~BOVERLAY_MODE (read-only, board)\n\nShows the state of the overlay. 0 is off, 1 is normal, 2 is\nstatic and 3 is transparent.\n\n~BVIEWPORT_X (read-only, board)\n~BVIEWPORT_Y (read-only, board)\n\nContains the x,y coordinates of the upper-left corner of the\ncurrent board's viewport.\n\n~BVIEWPORT_WIDTH (read-only, board)\n~BVIEWPORT_HEIGHT (read-only, board)\n\nContains the respective width and height of the current board's\nviewport.\n\n$Audio Counters\n:auc:\n\n~BMOD_FREQUENCY (board)\n\nControls the frequency (in Hz) of the currently playing module/\nOGG music. Note that this value is NOT saved by save or counter\nfiles, so if necessary, it needs set again after a save gets\nloaded.\n\n>#SOUNDEFX.HLP:frq:SAM, OGG, MOD and WAV Frequencies\n\n~BMOD_ORDER (board)\n\nControls the current mod order of the currently playing mod.\n\n~BMOD_POSITION (board)\n\nControls the current row in tracked modules, or the current PCM\nsample in OGGs.\n\n~BMOD_LENGTH (read-only, board)\n\nContains the length of the currently playing mod. This counter's\nvalue shows number of rows for module files and number of\nsamples for OGG files.\n\nNOTE: This counter will not work if MegaZeux was built with the\nMikmod engine. This is a minor concern due to Mikmod never being\nthe default module engine for MegaZeux.\n\n~BMOD_LOOPSTART\n~BMOD_LOOPEND\n\nThese counters control looping in the currently-playing music.\nMOD_LOOPSTART marks the beginning position of a loop;\nMOD_LOOPEND marks the end. If MOD_LOOPEND is 0, no looping will\noccur. Despite the counter names, they do not work on tracked\nmodules, only OGG files.\n\n$Mathematical Counters\n:mth:\n\n~BABSn\n\nGives the absolute value of the number n.\n\n~BSQRTn\n\nGives the integer approximation of the square root of n. Results\nare truncated, not rounded.\n\n~BMINx,y\n\nTakes the numbers x and y and outputs the smaller number. The\ncomma is required.\n\n~BMAXx,y\n\nTakes the numbers x and y and outputs the larger number. The\ncomma is required.\n\n~BMULTIPLIER\n\nMultiplies values output by the normal trigonometric functions\n(sin, cos, tan) by the given number. Defaults to 10000.\n\n~BDIVIDER\n\nDivides the number given to the inverse trigonometric functions\n(asin, acos, atan) by the given number. Defaults to 10000.\n\n~BC_DIVISIONS\n\nDetermines the number of divisions of the base circle in\nMegaZeux's trigonometric functions. Defaults to 360.\n\n~BSINn\n~BCOSn\n~BTANn\n~BASINn\n~BACOSn\n~BATANn\n~BARCTANdy,dx\n\nAccesses MegaZeux's sine, cosine, tangent, inverse sine,\ninverse cosine, inverse tangent, and atan2 functions\n(respectively).\n\n>#TRIG.HLP:tri:Trigonometric Functions\n\n$File Access Counters\n:fac:\n\n~BFREAD_OPEN (function)\n~BFWRITE_OPEN (function)\n\nOpens a file for reading from or writing to, respectively. The\nsyntax is as such:\n\n~Eset \"file.ext\" to \"FREAD_OPEN\"\n\nFREAD_OPEN will also read subdirectories with the given syntax:\n\n~Eset \"dir\" to \"FREAD_OPEN\"\n\n~BFWRITE_MODIFY (function)\n\nThis opens an existing file for reading without overwriting it,\nstarting at the beginning of the file.\n\n~BFWRITE_APPEND (function)\n\nThis opens a file for writing at the END of the file. Note that\nall FWRITE_POS functionality is ignored in this mode.\n\n~BFREAD_COUNTER (function)\n\nReads four bytes from the open file.\n\n~BFWRITE_COUNTER (function)\n\nWrites four bytes to the open file.\n\nNOTE: The above two counters will have different functionality\nin worlds made before MZX 2.82 (in the old implementation, they\nuse two bytes and are unsigned). This is purely for\ncompatibility reasons.\n\n~BFREAD_POS\n~BFWRITE_POS\n\nThe current reading or writing position of the accessed file.\n0 is the beginning position; when no file is loaded, the\nrelevant counter is -1. If set to -1, the position is set to the\nend of the file.\n\n~BFREAD_DELIMITER\n~BFWRITE_DELIMITER\n\nThe character considered the terminator for fread and fwrite\noperations, respectively.\n\n~BFWRITE (function)\n~BFREAD (function)\n\nRespectively writes to and reads from the open file. With\ncounters, these counters only write/read a single byte at a\ntime; with strings, FWRITE will write a full string and tack the\nterminator character to the end, and FREAD will read a full\nstring up to the terminator.\n\n~BFWRITEn (function)\n~BFREADn (function)\n\nRespectively writes to and reads from the open file, but will\nlimit action to n number of characters. These counters only work\nwith strings, not with counters, and will neither place nor\nrespect terminator characters.\n\n~BFREAD_LENGTH\n\nReturns the length in characters of the open file in read mode.\nIf this file does not exist, returns -1. If a directory is open,\nreturns the number of items (files and subdirectories) in that\ndirectory.\n\n~BFWRITE_LENGTH\n\nReturns the length in characters of the open file in write mode.\n\n~BSAVE_ROBOT (function)\n~BLOAD_ROBOT (function)\n~BSAVE_ROBOTn (function)\n~BLOAD_ROBOTn (function)\n\nSaves or loads Robotic code to or from plaintext files,\nrespectively. The first forms save/load the current Robot,\nwhile the second forms save/load the Robot with the Robot ID of\nthe number n.\n\nOne can also load a string as a Robot instead of a text file, or\nsave a Robot into a given string.\n\n~BSAVE_BC (function)\n~BLOAD_BC (function)\n~BSAVE_BCn (function)\n~BLOAD_BCn (function)\n\nSaves or loads Robotic code to or from bytecode files,\nrespectively. The first forms save/load the current Robot,\nwhile the second forms save/load the Robot with the Robot ID of\nthe number n.\n\nKeep in mind that due to slated changes in MegaZeux, saving out\nRobots (in either text or bytecode form) is considered a\ndeprecated function and may be removed entirely in the future.\n\n~BSAVE_GAME (function)\n~BLOAD_GAME (function)\n\nSaves or loads MZX savegames, respectively.\n\n~BSAVE_COUNTERS (function)\n~BLOAD_COUNTERS (function)\n\nSaves or loads MZX counter files, respectively.\n\n>#FILEACSS.HLP:fil:File Access\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n\n$Sprite Counters\n:spc:\n\n~BSPRn_WIDTH\n~BSPRn_HEIGHT\n\nThe dimensions of sprite n. (x and y dimensions, respectively.)\n\n~BSPRn_X\n~BSPRn_Y\n\nThe current position of sprite n. These can be used to set the\nsprite outside the edges of the board, whereas PUT c?? Sprite\np?? X Y disallows this.\n\n~BSPRn_REFX\n~BSPRn_REFY\n\nWhere sprite n's appearance is stored - either on the board or\nthe vlayer (see SPRn_VLAYER). REFX is the x-coordinate, REFY is\nthe y-coordinate. Whatever this looks like at this position is\nwhat the sprite will look like when drawn, including the colors\nif c?? is used when placing the sprite.\n\n~BSPRn_CWIDTH\n~BSPRn_CHEIGHT\n\nThe dimensions of the collision box around sprite n. (X and Y\ndimensions, respectively)\n\n~BSPRn_CX\n~BSPRn_CY\n\nThe location, relative to sprite n's upper left corner, of the\nsprite's collision box (X and Y coordinates, respectively).\nThese have default values of 0.\n\n~BSPR_CLISTn (read-only)\n\nA list of flagged collisions after a collision test was\nperformed. Values of n from 0 to (SPR_COLLISIONS - 1) are\nvalid. Each valid SPR_CLISTn value lists the number of a sprite\ninvolved in a collision with the checked sprite, arranged in\nascending sprite order. If the foreground collided, SPR_CLIST0\nis always set to -1 to indicate this.\n\n~BSPR_COLLISIONS (read-only)\n\nThe number of sprite/foreground collisions reported by\nSPR_CLISTn counters.\n\n~BSPRn_CCHECK (write-only)\n\nIf set to 1 for sprite n, overlapping char 32s of a sprite,\nregardless of whether char 32 is blank, will not cause a\ncollision between that sprite and other sprites or the\nforeground. If set to 2, fully blank chars of a sprite will not\ncollide, and blank characters of a sprite will not be drawn. If\nset to 3 - a setting only valid for unbound sprites - the given\nunbound sprite will undergo pixel-precise collision checks, and\ntransparent colors will not cause a collision. Setting to 0 will\ncancel any previously set ccheck modes for that given sprite.\n\nStandard sprites will apply only the checked sprite's ccheck\nvalue to all involved sprites in collision checks, while checks\nagainst unbound sprites will also apply the values of all\nsprites involved with it. Foreground char 32s and blank chars\nwill not be ignored in collision checks, regardless of a\nstandard sprite's ccheck value.\n\n~BSPRn_CLIST (write-only)\n\nSetting this counter to any value will perform a collision test\non sprite n against its current location.\n\n~BSPRn_VLAYER (write-only)\n\nIf set to 1, sprite n is referenced from the vlayer instead of\nthe board.\n\n~BSPRn_OFF\n\nShows whether sprite n is inactive: a value of 1 means yes\n(the sprite is off), 0 means no (the sprite is on). Also, if set\nto any value, turns sprite n off if it is on.\n\n~BSPRn_OFFONEXIT\n\nIf set to any non-zero value, turns the sprite off upon leaving\nthe current board. The sprite will be turned off even if the\ndestination board is also the current board.\n\n~BSPRn_OVERLAID (write-only)\n~BSPRn_OVERLAY (write-only)\n\nIf either is set to 1, these draw sprite n over the overlay as\nopposed to underneath. Note that this has different behavior\ndepending on whether unbound sprites are present. If there are\nany unbound sprites active, all overlaid sprites will be drawn\nabove all non-overlaid sprites (as well as over the overlay).\nIf unbound sprites are not in use, overlaid sprites will be\ndrawn above the overlay, but will otherwise respect the standard\nsprite draw order.\n\n~BSPRn_SETVIEW (write-only)\n\nSetting this to 1 will cause the viewport to be centered around\nsprite n. This is not a permanent effect, but a one-time jump\nakin to the RESETVIEW command. This is useful for scrolling the\nscreen for (e.g.) a sprite-based player object.\n\n~BSPRn_STATIC (write-only)\n\nIf set to 1, makes sprite n act static (like a static overlay, a\nstatic sprite always stays in a given position on the screen);\nif this is 0, the sprite scrolls with the board.\n\nOf note: any collision with a static sprite happens based on its\nvisually-apparent location, not its actual location. This means\nthat when the board scrolls, the valid coordinates for absolute\ncollision checks against a static sprite change even when the\nstatic sprite itself is not visually moved, and that relative\ncollision checks like SPRn_CLIST against a static sprite work\nbased on the static sprite's screen position.\n\n~BSPRn_Z\n\nThese counters allow a user-defined sprite draw order. This\nmethod of determining draw order takes precedence over\nSPR_YORDER and sprite number. Higher SPRn_Z values are higher\npriority and will be drawn over sprites with lower SPRn_Z\nnumbers. Matching SPRn_Z values will fall back to SPR_YORDER\nprecedence if active, and sprite number precedence otherwise.\n\n~BSPR_NUM\n\nWhen using p?? for placing a sprite, this counter's value\nbecomes the sprite's parameter. When IF c?? Sprite p?? X Y is\nused, SPR_NUM will be set to the number of the first sprite that\nmakes the IF statement true (if there are any). The sprites are\nchecked by sprite number in ascending order.\n\n~BSPR_YORDER (write-only)\n\nIf set to 1, the sprites are drawn in order of lowest sum of\nSPRn_Y and SPRn_CY values to highest instead of in order of\ntheir sprite numbers. Any incidence of matching sums will have\ndraw order determined by sprite number. If any SPRn_Z values are\nset, those take precedence.\n\n~BSPRn_SWAP (write-only)\n\nSwaps sprite n with the sprite number to which this function is\nset.\n\n~BSPRn_UNBOUND\n\nIf set to 1, unbinds sprite n from the grid, making its\ncoordinates refer to pixel positions instead of tile positions.\nIts dimensions (SPRn_HEIGHT, SPRn_WIDTH) and reference\ncoordinates (SPRn_REFX, SPRn_REFY) will still be relative to\ntiles, but its current position (SPRn_X, SPRn_Y) and collision\nbox counters (SPRn_CX, SPRn_CY, SPRn_CWIDTH, SPRn_CHEIGHT) will\nadhere to this change. See the Unbound Sprites section in the\nSprites help for more information.\n\n~BSPRn_TCOL\n\nSets which color provides transparency for unbound sprite n.\nSetting to -1 disables transparency for that sprite. Defaults to\n0.\n\n~BSPRn_OFFSET\n\nInternally increments any number that refers to a character for\nsprite n by the counter's value. This only applies to unbound\nsprites. This number will often be a multiple of 256 in order to\naccess an extra charset, but offset values within char sets may\nalso be used.\n\n>#SPRITES.HLP:spr:Sprites\n\n$String Counters\n:stc:\n\n~BSET \"$string\" to \"INPUT\"\n\nSets the given string to any input placed for the \"input string\"\ncommand.\n\n~BSET \"$string\" to \"BOARD_NAME\"\n\nSets the given string to the name of the current board, or to a\nblank string when the board has no name.\n\n~BSET \"$string\" to \"MOD_NAME\"\n\nSets the given string to the filename of the currently playing\nmodule, or to a blank string when no music is playing. If the\ncurrent board mod is mod *, this will contain the actual\nfilename of the currently-playing mod, not \"*\".\n\n~BSET \"$string\" to \"ROBOT_NAME\"\n\nSets the given string to the name of the Robot this command\nis in, or to a blank string when the Robot has no name.\n\nNote: BOARD_NAME, MOD_NAME and ROBOT_NAME cannot be directly\ndisplayed, unlike &INPUT&.\n\n~BSET \"$string\" to \"FREAD\"\n~BSET \"$string\" to \"FREADn\"\n\nSets the given string to the contents of the currently open\nfile. FREAD stops at the terminator (*, or char 42, by default)\nor at the end of the file. FREADn reads the first n characters\nfrom the file. You can use FREAD with the #N string splicing\nformat to read up to N characters while still terminating on\nthe terminator.\n\n~BSET \"$string\" to \"FWRITE\"\n~BSET \"$string\" to \"FWRITEn\"\n\nPlaces the contents of the given string into the currently open\nfile. FWRITE places the entire string, ending it with the\nterminator character (*, or char 42, by default), while FWRITEn\nonly places the first n characters. If n goes over the string's\nlength, the entire string (and nothing else like whitespace or\ngarbage characters) will be placed.\n\n~BSET \"$string\" to \"BOARD_SCAN\"\n\nSets the given string to a line of characters read directly\nfrom the board starting at \"board_x\" \"board_y\" and terminating\nat an asterisk (char 42), any instance of char 0, or after 63\ncharacters (whichever comes first). This command is\ndeprecated; please use copy block to strings instead.\n\n$Super MZX Counters\n:szc:\n\n~BSMZX_MODE\n\nDetermines whether Super MZX mode is off (0), 1, 2, or 3. All\nother values wrap around these four (so, for instance, trying to\nset SMZX_MODE to 4 would set it to 0, and trying to set\nSMZX_MODE to -1 would set it to 3).\n\n~BSMZX_Rn\n~BSMZX_Bn\n~BSMZX_Gn\n\nChanges or reads the value of color n's red/blue/green value,\nrespectively. These counters range from 0-63; numbers outside\nthese bounds wrap around. These counters also work outside of\nSMZX modes; in normal mode, using numbers 0-15 for n alters the\ncurrent palette.\n\n~BSMZX_IDXx,y\n\nReads or sets the color of a tile with a given color x's yth\ncolor. For example, ~ESET \"SMZX_IDX191,0\" to 50~F would set the\ncolor number of the base color (color 0 out of 0-3) of a c1a\n(color 191) tile to color 50. This applies to SMZX mode 3 only,\nand only to renderers that support unbound sprites.\n\nKeep in mind that all re-ordered indices will be set to normal\nvalues when SMZX_MODE is changed.\n\n~BSMZX_INDICES (function)\n\nLoads SMZX indices files. This only applies for SMZX Mode 3.\n\n~BSMZX_MESSAGE\n\nControls whether messages in the message line are displayed with\nSMZX characters and colors in SMZX modes. 1 is yes, 0 is no\n(displays in normal mode); the default value is 1.\n\n~BSMZX_PALETTE (read-only)\n\nLoads a Super MZX palette. This counter is deprecated; please\nuse load palette instead.\n\n$Character Edit Counters\n:cha:\n\n~BCHAR_X\n~BCHAR_Y\n~BPIXEL\n\nSet CHAR_X and CHAR_Y to a pixel location in the char set. You\ncan then set the PIXEL counter to one of the following to edit\nthe pixel:\n\n  0 Turns the pixel OFF\n  1 Turns the pixel ON\n  2 Toggles the pixel\n\nYou can also read the counter to see the current state of the\nchosen pixel. A value of 1 denotes that the pixel is set, and 0\nif not.\n\nThe pixel location system works on a 32 x 8 grid of characters,\nexactly like the 'Select Character' grid. Each character\nconsists of fourteen rows of eight pixels, which gives us 256\npixels across by 112 pixels down. Since we start counting at 0,\nthe top-left pixel of the top-left character (char 0) would be\n0,0. The bottom-right pixel of the bottom-right character\n(char 255) would be 255,111.\n\nThese counters become useless in SMZX modes.\n\n>#CHAREDIT.HLP:079:The Character Editor\n\n~BCHAR_BYTE\n~BCHAR\n~BBYTE\n\nSet CHAR to a character (0-255) and BYTE to one of the fourteen\nbytes within the char (0-13). You can then read or write that\nbyte using the CHAR_BYTE counter. Char_byte values range from\n0-255, and the meaning of each char_byte setting changes between\nnormal mode and SMZX modes. Normal MZX graphics mode sets bytes\nin binary (base 2); SMZX modes use quaternary (base 4).\n\nThese counters can also edit the extra character sets unbound\nsprites use by setting CHAR to the character numbers of the\nextra sets (e.g. 256 for the first char of the first extra set).\n\nIt helps to write the number in binary/quaternary first, then\nconvert to decimal. Writing the numbers this way helps\ndemonstrate that whatever is desired to be set is set. The\nvalues per pixel decrease going left to right.\n\nExamples:\n\n(Normal MZX mode, setting the third, fifth and seventh pixel)\n\nBinary: 00101010\n\n-------------\n|128|64|32|16|8|4|2|1|\n-------------\n|  0| 0| 1| 0|1|0|1|0|\n-------------\n\nDecimal: ((128*0)+(64*0)+(32*1)+(16*0)+(8*1)+(4*0)+(2*1)+(1*0))\n         = 42\n\nNotice that the binary number shows which pixels are on, in\norder.\n\n(Super MZX Mode, setting first and second pixel to color 2,\nthird to color 4 and fourth to color 1)\n\nQuaternary: 1130\n\n------\n|64|16|4|1|\n------\n| 1| 1|3|0|\n------\n\nDecimal: ((64*1)+(16*1)+(4*3)+(1*0)) = 92\n\nMuch like the binary numbers for normal MZX mode, the\nquaternary number shows what color each pixel is in order.\nHere, color values range from 0 to 3.\n\n>#CHAREDIT.HLP:079:The Character Editor\n\n$Mouse/Joystick Counters\n:nkp:\n\n~BMOUSEX\n~BMOUSEY\n\nReads/sets the mouse cursor's x or y-coordinates (respectively)\nrelative to the screen in characters.\n\n~BMOUSEPX\n~BMOUSEPY\n\nReads/sets the mouse cursor's x or y-coordinates (respectively)\nrelative to the screen in pixels. Always scales to terms of\n640x350, regardless of the actual resolution or current SMZX\nmode.\n\n~BMBOARDX (read-only, board)\n~BMBOARDY (read-only, board)\n\nReads the mouse cursor's x or y-coordinates (respectively)\nrelative to the board.\n\n~BBUTTONS (read-only)\n\nReads which mouse buttons are currently being pressed. The\ncounter value is based as follows:\n0 for no input pressed\n+1 for left button\n+2 for right button\n+4 for middle button\n+8 for wheel up\n+16 for wheel down\n+32 for wheel left\n+64 for wheel right\n+128 for extra button 1\n+256 for extra button 2\nE.G., left by itself would output 1, and left+middle would\noutput 5 (1 + 4).\n\n~BCURSORSTATE\n\nDetermines whether the default mouse cursor is on or off (0 is\noff, 1 (or any non-zero value) is on - defaults to off).\n\n~BJOY#ACTIVE (read-only)\n\nDetermines the status of the given joystick numbered #. Returns\n1 for active, 0 for inactive, and -1 for an invalid index.\n\n~BJOY#.[ACTION] (read-only)\n\nDetermines the status of the given joystick action for the given\njoystick numbered #. The square brackets are not included.\nDigital actions return 1 for pressed/held and 0 for\nunpressed/unheld. Analog actions will return their current\nvalue. Invalid actions will return -1.\n\nPlease read joystick.html for a full list of actions.\n\n~BJOY_SIMULATE_KEYS\n\nAllows MegaZeux to simulate key presses with joystick actions\nwhen enabled. 0 is off, 1 (or any non-zero value) is on.\nDefaults to on.\n\n$Defaults Counters\n:def:\n~BENTER_MENU\n\nDetermines whether the built-in status window is loaded when\nthe enter key is pressed. 1 (or any non-zero value) is yes, 0\nis no. The default is 1 (yes). If on the title screen, this\ncounter is ignored unless in standalone mode.\n\n~BESCAPE_MENU\n\nDetermines whether the built-in game exit dialog is loaded when\nthe escape key is pressed. 1 (or any non-zero value) is yes, 0\nis no. The default is 1 (yes). If on the title screen, this\ncounter is ignored unless in standalone mode.\n\n~BHELP_MENU\n\nDetermines whether the built-in help window is loaded when the\nF1 key is pressed. 1 (or any non-zero value) is yes, 0 is no.\nThe default is 1 (yes). The help menu is still accessible from\nother default dialogs (such as the file menus) unless in\nstandalone mode. If on the title screen, this counter is ignored\nunless in standalone mode.\n\n~BF2_MENU\n\nDetermines whether the built-in MegaZeux options window is\nloaded when the F2 key is pressed. 1 (or any non-zero value) is\nyes, 0 is no. The default is 1 (yes). If on the title screen,\nthis counter is ignored unless in standalone mode.\n\n~BLOAD_MENU\n\nDetermines whether the built-in MegaZeux loadgame window is\nloaded when the F4 key is pressed. 1 (or any non-zero value) is\nyes, 0 is no. The default is 1 (yes). If on the title screen,\nthis counter is ignored unless in standalone mode.\n\n~BBIMESG (write-only)\n\nControls built-in game messages (e.g. the message given when\ntouching goop); 1 (or any non-zero value) is on while 0 is off.\nThe default is 1 (on).\n\n~BSPACELOCK (write-only)\n\nControls whether holding space locks player movement. If 1 (or\nany non-zero value), the player will be locked into place when\nspace is held; if 0, the player can move freely when space is\nheld, but will be unable to shoot bullets. The default is 1\n(on).\n\n$Vlayer Counters\n:vlc:\n\n~BVLAYER_WIDTH\n~BVLAYER_HEIGHT\n\nSets the width and height of the vlayer, respectively. The\ndefaults are 256 for VLAYER_WIDTH and 128 for VLAYER_HEIGHT.\nSetting one will automatically set the other to the maximum\npossible for the current vlayer_size.\n\n~BVLAYER_SIZE\n\nSets the maximum size of the vlayer. The default is 32768.\nMaking this larger won't change the values of VLAYER_WIDTH or\nVLAYER_HEIGHT but shrinking it may shrink both - height first,\nthen width.\n\n~BVCHx,y\n\nAllows reading and writing of single characters to the vlayer\nat the coordinates x,y. The comma must be included. Returns -1\nfor invalid coordinates.\n\n~BVCOx,y\n\nAllows reading and writing of single colors to the vlayer with\nat the coordinates x,y. The comma must be included. Returns -1\nfor invalid coordinates.\n\n>#VLAYER.HLP:vla:The Vlayer and Its Uses\n\n$Miscellaneous Counters\n:msc:\n~BMZX_SPEED\n\nThe current game speed (1-16) of MegaZeux. MZX_SPEED 1 is\nprocessor-bound and executes as many cycles per second as\npossible. Other speeds execute cycles according to this\nformula:\n\n~E62.5 / (MZX_SPEED - 1) = cycles per second\n\nOnce the speed is set with this counter, the player cannot\nchange the speed in the F2 menu until MZX_SPEED is set to 0.\nSetting MZX_SPEED to 0 will not affect the current speed.\nMZX_SPEED will revert back to the default value listed in the\nconfiguration file upon loading a different game.\n\n>#CONFGINI.HLP:1st:The Config File\n\n~BCOMMANDS_STOP\n\nThis is a special counter active only while playtesting in the\neditor. COMMANDS_STOP will activate stepping in the Robotic\ndebugger, even if the debugger was currently disabled, once the\nnumber of commands any specific Robot executes in a single cycle\nexceeds COMMANDS_STOP's value. By default, COMMANDS_STOP holds a\nvalue of 2000000.\n\n~BEXIT_GAME\n\nIf this counter is set to a non-zero value, the current\nMegaZeux world will exit to the title screen, or back to the\neditor if playing from the editor. This counter does nothing on\nthe title screen unless in standalone mode, and if standalone\nmode is ran without a title screen, this counter will cause\nMegaZeux to exit entirely.\n\n~BPLAY_GAME\n\nIf this counter is set to a non-zero value when MZXRun is in\nstandalone mode and on the title screen, the game will be\nstarted. Instances outside of the title screen and outside of\nMZXRun will be ignored.\n\n~BCURRENT_COLOR\n~BRED_VALUE\n~BGREEN_VALUE\n~BBLUE_VALUE\n\nWhen CURRENT_COLOR is set between 0 and 15 (between 0 and 255\nin Super MZX modes), the other three counters contain the red,\ngreen and blue values of that color.\n\n>#SMZXMODE.HLP:095:Super MegaZeux Modes\n>#PALEEDIT.HLP:093:The Palette Editor\n\n~BINPUT (board)\n\nThe numerical value of the last string inputted in an INPUT\nSTRING box. Strings that are solely numbers and strings leading\nwith numbers (e.g. \"86ed\") set this counter to the relevant\nnumber. This counter returns 0 if the last string inputted did\nnot lead with a number. This function is separate from its use\nin messages with &INPUT&.\n\nNOTE: When this counter is interpolated, it will instead\ninterpolate the counter NAMED after the inputted string, so\nexercise caution.\n\n>#COMMAND2.HLP:_iP:INPUT STRING \"string\"\n\n~BINPUTSIZE (board)\n\nThe size, in characters, of the last string inputted in an INPUT\nSTRING box.\n\n>#COMMAND2.HLP:_iP:INPUT STRING \"string\"\n\n~BDATE_DAY (read-only, universal)\n~BDATE_MONTH (read-only, universal)\n~BDATE_YEAR (read-only, universal)\n\nContain the current day, month and year respectively, based on\nthe system clock.\n\n~BTIME_HOURS (read-only, universal)\n~BTIME_MINUTES (read-only, universal)\n~BTIME_SECONDS (read-only, universal)\n~BTIME_MILLIS (read-only, universal)\n\nContain the current hour, minute, second and millisecond\nrespectively, based on the system clock, in 24-hour format.\n\n~BDATE_WEEKDAY (read-only, universal)\n\nContains the current day of the week as a number 0 through 6,\nstarting with Sunday (0) and ending with Saturday (6).\n\n~BINT\n~BBIT_PLACE\n~BINT2BIN\n\nUsed to manipulate bits within a counter. Set INT to the number\nor counter you wish to edit. Set BIT_PLACE to a number between\n0 and 15 (which correspond to the bits in a 16-bit signed\ncounter), and then set INT2BIN to one of the following to edit\nthe bit:\n\n  1 Sets the bit ON\n  2 Sets the bit OFF\n  3 Toggles the bit\n\nThe edited value is placed into the INT counter. You can also\nread the INT2BIN counter to see the current state of the bit.\n\nThis method of bit manipulation is heavily deprecated in favor\nof expressions (especially considering it only works on 16 bits\ninstead of the current 32). Follow the link below to read more.\n\n>#EXPRESS.HLP:exp:Expressions\n\n~BKEY_PRESSED (read-only)\n~BKEY\n~BKEY_CODE (read-only)\n\nKEY_PRESSED lists whether any key (each referenced by a number)\nis currently being held down; for example, on a typical\nkeyboard, if the spacebar is being pressed the value of\nkey_pressed will be 32. If no key is currently held, its value\nis 0. Results are unsigned, so checking for negative numbers\nwill not work. \"Autorepeat stall\" affects KEY_PRESSED, so if you\nhold down a key you will get the value, then get zeros for a\nperiod, then get the values again. This is useful for many\napplications.\nKEY acts like KEY_PRESSED, but lacks autorepeat stall, returns\nuppercase letter values, and retains its value until another key\nis pressed.\nKEY_CODE acts similar to KEY_PRESSED (has an autorepeat stall,\nis 0 when nothing is held) but uses different keycodes. Its\nkeycode set is more limited, making KEY_PRESSED generally\npreferable.\nIn the case of multiple keys being held down at once, these\ncounters only report the value of the most recently-pressed key.\n\n~BKEYn (read-only)\n~BKEY_RELEASE (read-only)\n~BKEY_PRESSEDn (read-only)\n\nKEYn counters check whether the key with the keycode n is\npressed; a counter value of 1 means yes, 0 means no. KEY_RELEASE\ncontains the keycode of the last key released, or is 0 if a key\nis currently being held. These display/use the same keycodes as\nKEY_CODE, but do not cause an autorepeat stall. Results are\nunsigned, so checking for negative numbers will not work.\nKEY_PRESSEDn counters function like KEYn counters, but use\nKEY_PRESSED's keycodes.\nAs multiple KEYn and KEY_PRESSEDn values can be 1 at the same\ntime, these counters can be very useful for detecting key\ncombinations.\n\n~BRANDOM_SEED0 (universal)\n~BRANDOM_SEED1 (universal)\n\nThese counters allow reading and controlling MegaZeux's random\nseed. RANDOM_SEED0 controls the low 32 bits of the seed, while\nRANDOM_SEED1 controls the high 32 bits. The random seed is not\nretained by save files.\n\n~BMAX_SAMPLES\n\nSets number of samples that can play at once. -1 sets no limit,\n0 mutes all samples; -1 is the default setting. The actual\nsample limit will be the smaller of the limit set by this\ncounter and the limit set by the max_simultaneous_samples config\nfile setting. PC Speaker emulated sounds are unaffected.\n\n~BXPOS\n~BYPOS\n\nThe X or Y-coordinates (respectively) used by the REL COUNTERS\ncommand.\n\n>#COMMAND2.HLP:pre:REL COUNTERS\n\n~BFIRSTXPOS\n~BFIRSTYPOS\n\nThe X or Y-coordinates (respectively) used by the REL COUNTERS\nFIRST command.\n\n>#COMMAND2.HLP:_r4:REL COUNTERS FIRST\n\n~BLASTXPOS\n~BLASTYPOS\n\nThe X or Y-coordinates (respectively) used by the REL COUNTERS\nLAST command.\n\n>#COMMAND2.HLP:_r7:REL COUNTERS LAST\n\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#USINGTHE.HLP\n:1st:\n$~9Using the Editor\n\nThe Robotic editor is quite simple to use. After selecting your\nRobot's character and name, you will enter the editor screen.\nThere are three major sections to the editor screen: The top\nbox, showing current statistics; the middle box, showing the\nRobot's program with the current line highlighted in the\ncenter; and the bottom box, showing helpful key shortcuts.\n\nTo edit the Robot, you enter lines just as in editing scrolls.\nUse up and down or PageUp and PageDown to scroll between lines,\nand use Enter at the end of a line to insert new blank lines.\nPressing Enter in the middle of a line will remove everything\nto the right of the cursor and place it onto a new, following\nline. Press Backspace at the beginning of a line to place its\ncontents onto the preceding line (this be ignored if there is\nnot enough space).\n\nAfter you enter a line, it will be checked for proper syntax.\nIf it passes, it will be reformatted and redisplayed, and you\ncan then enter another line; if it does not, it will be marked\nas ignored. The reformatting is unpreventable - Since MegaZeux\ntokenizes Robotic lines to conserve space, all irrelevant\ninformation such as extra spaces is lost. The editor attempts\nto print the lines in a visually pleasing manner. It is possible\nfor MZX to reformat the line in a way you aren't intending, so\ndouble-check your line if MZX reformats it.\n\nAll lines other than the current line will have the various\nelements such as strings and numbers highlighted in different\ncolors. cXX-type color codes will be shown as their actual\ncolor using the current palette. The current line is shown in a\nsolid color and color codes are shown in the usual cXX format.\n\nThe following keys are active within the Robotic editor:\n\n>_ar:F1 - Help\n>_br:F2 - Color\n>_cr:F3 - Character\n>_dr:F4 - Parameter\n>_er:F5 - Char Edit\n>_fr:F6-F10 - Macros\n>_gr:F12 - Take Screenshot\n>_hr:Alt+B OR Alt+Enter - Block Action\n>_ir:Alt+E OR Alt+End - Mark Block End\n>_jr:Alt+H - Hide Help / Borders\n>_kr:Alt+I - Import\n>_lr:Alt+M - Configure Macro\n>_mr:Alt+O - Edit Single Line Macros\n>_nr:Alt+S OR Alt+Home - Mark Block Top\n>_or:Alt+U - Unmark Block\n>_pr:Alt+V - Verify\n>_qr:Alt+X - Export\n>_rr:Alt+BackSpace - Delete Entire Line\n>_sr:Alt+Ins OR Alt+P - Paste\n>_tr:Ctrl+F - Search\n>_ur:Ctrl+H - Replace\n>_vr:Ctrl+R - Repeat Search/Replace\n>_wr:Ctrl+G - Goto Position\n>_xr:Ctrl+Y - Redo\n>_yr:Ctrl+Z - Undo\n>_zr:Ctrl+I/D/C - Mark Line\n>_1r:Ctrl+BackSpace - Delete Previous Word\n>_2r:Ctrl+LeftArrow - Jump to Previous Word\n>_3r:Ctrl+RightArrow - Jump to Next Word\n>_4r:Ctrl+Home - Jump to Program Start\n>_5r:Ctrl+End - Jump to Program End\n>_6r:Esc - Exit Robotic Editor\n\nOn Mac platforms, any command that uses the Alt key can take \n(the command key) instead.\n\n:_ar:~EF1 - Help\n\nUse F1 to bring up context-sensitive help at any time.\n\n:_br:~EF2 - Color\n\nF2 will bring up a menu of colors representing the current\npalette. Select a color and press Enter to insert a cXX color\ncode into the current line corresponding to that color.\n\n:_cr:~EF3 - Character\n\nF3 will bring up a menu of characters representing the current\ncharacter set. Select one and press Enter to insert that\ncharacter into the current line. No quotes or apostrophes are\nadded.\n\n:_dr:~EF4 - Parameter\n\nIf you have just typed in the name of a thing, such as Ammo,\nF4 will allow you to select settings for it, placing a pXX\nparameter code for the chosen settings. This includes things\nthat are turned into Custom things by setting their CHAR ID\nvalues to 255. If the thing is a valid MZX thing without\nmeaningful parameter values, pressing F4 automatically outputs\n\" p00\".\n\n:_er:~EF5 - Char Edit\n\nUse F5 to edit and select a character for use with the Robotic\nCHAR EDIT command. This outputs the numbers needed for that\ncommand to visually match the character selected.\n\n:_fr:~EF6-F10 - Macro\n\nF6 through F10 will insert short macros, customizable using\nAlt+O. If you have an extended macro set for these an input\nwindow for it will come up (see Configure Macro for more\ndetails).\n\n:_gr:~EF12 - Take Screenshot\n\nPressing F12 will make a 640x350 screenshot (in PNG format by\ndefault) in the working directory, using the software renderer.\nThe file naming starts from \"screen0\".\n\n:_hr:~EAlt+B OR Alt+Enter - Block Action\n\nAllows you to perform an action on the current block of lines.\nPossible actions are: Copy to the OS's clipboard, Cut to the\nOS's clipboard (copy it and then delete it), Clear from memory,\nor Export to a text file on disk. If no block is currently\nhighlighted, the actions will take effect on the current line.\nAlt+Enter is the old and deprecated hotkey, left in to ease use\nfor long-time users.\n\n:_nr:~EAlt+S OR Alt+Home - Mark Block Top\n:_ir:~EAlt+E OR Alt+End - Mark Block End\n\nAlt+S/E will mark the top or bottom of a block of lines. You\ncan then perform various block actions on these lines. The\nmarked lines are highlighted. Alt+Home and Alt+End are the old\nand deprecated hotkeys, left in to ease use for long-time\nusers.\n\n:_jr:~EAlt+H - Hide Help / Borders\n\nAlt+H toggles the horizontal borders and help key reference.\n\n:_kr:~EAlt+I - Import\n\nUse Alt+I to import a text file or bytecode file into the\ncurrent Robot. The new lines will be inserted into the current\nprogram, starting from the current line.\n\n:lr_:~EAlt+M - Configure Macro\n\nAlt+M will load a box where you list the name of an extended\nmacro to be configured (for example, type \"1\" to edit macro_1).\nAfter choosing a macro to edit, you will be able to set the\nvarious variables for the macro. Select OK to place the macro,\nCancel to go back to the editor without placing the macro, and\nDefault to change all values to their defaults.\n\n:_mr:~EAlt+O - Edit Single Line Macros\n\nYou can change the values of the five single-line macros here.\nUse ^ for Enter in the macros. If there is an extended macro in\nplace of macros 1-5, it will override any single-line macro also\nset for that macro.\n\n>#CONFGINI.HLP:1st:The Config File\n\n:_or:~EAlt+U - Unmark Block\n\nThe current block is unmarked.\n\n:_pr:~EAlt+V - Verify\n\nUse Alt+V to determine if there are any invalid lines in the\nRobot, and if there are what their errors are. Each invalid line\ncan be set to I (ignore), D (delete) or C (comment), which will\nbe applied if the user selects OK. Only the first 256 errors\nwill be listed.\n\n:_qr:~EAlt+X - Export\n\nUse Alt+X to export either the current block or the entire\nRobot program to a text file or bytecode file on disk. If saved\nas text, one can then edit the program outside of MegaZeux or\nuse the text file in other ways.\n\n:_rr:~EAlt+BackSpace - Delete Entire Line\n\nThe currently selected line is blanked. The empty line itself\nwill remain.\n\n:_sr:~EAlt+Ins OR Alt+P - Paste\n\nAlt+Ins or Alt+P will paste in the block last copied to the\ninternal clipboard. The clipboard used is the OS clipboard, and\nas such is preserved between Robots and even between worlds.\n\n:_tr:~ECtrl+F - Search\n\nUse Ctrl+F to activate the Search menu. After inputting a string\nto find in the Search box and choosing options, the Robotic\neditor will search for the term starting from the current line\nand jump to the first string that matches.\nWrap will ensure every line of the code is checked (otherwise\nall code above the current line will be ignored) and Case\nSensitive will require the search string and the results match\ncase exactly before giving results.\n\n:+ur:~ECtrl+H - Replace\n\nUse Ctrl+H to activate the Replace menu. The Search box takes\nthe text to be changed, and the Replace box takes the text that\nreplaces it. Replace only changes the first seen instance of the\nsearched text, with the search starting from the current line.\nReplace All changes all of them at once. Once completed, the\nRobotic editor will jump to the last line changed by a replace\naction (if any).\nWrap will ensure every line of the code is checked (otherwise\nall code above the current line will be ignored) and Case\nSensitive will require any string matching the search string to\nalso match case before being replaced.\n\n:_vr:~ECtrl+R - Repeat Search/Replace\n\nUse Ctrl+R after a Search or Replace action to repeat the\naction.\n\n:_wr:~ECtrl+G - Goto Position\n\nUse Ctrl+G to jump to the given Robotic line/column (if too\nhigh of a number is given, the editor will jump to the last\npossible line/column).\n\n:_yr:~ECtrl+Z - Undo\n:_xr:~ECtrl+Y - Redo\n\nUse Ctrl+Z to undo the most recent editor action(s). Ctrl+Y\nreverts an undo.\n\n:_zr:~ECtrl+I/D/C - Mark Line\n\nThese commands mark the current invalid line as to be ignored,\ndeleted, or commented out upon editor exit, respectively. If\nthe line is valid Ctrl + C will turn it into a comment\n(truncating the end of the line if commenting would make the\nline exceed 241 characters). Note that comment-marking a\ncomment will immediately strip the line.\n\n:_1r:~ECtrl+BackSpace - Delete Previous Word\n\nUse Ctrl+BackSpace to delete the word to the left of the cursor.\n\n:_2r:~ECtrl+LeftArrow - Jump to Previous Word\n\nUse Ctrl+LeftArrow to jump to the beginning of the previous\nword in the current line.\n\n:_3r:~ECtrl+RightArrow - Jump to Next Word\n\nUse Ctrl+RightArrow to jump to the beginning of the next word\nin the current line. If on the last word of the line, this\ncommand jumps to the end of the line.\n\n:_4r:~ECtrl+Home - Jump to Program Start\n\nUse Ctrl+Home to jump to the first line of the current program.\n\n:_5r:~ECtrl+End - Jump to Program End\n\nUse Ctrl+End to jump to the last line of the current program.\n\n:_6r:~EEsc - Exit Robotic Editor\n\nThis key will exit the Robotic Editor, unless any lines are\nmarked as invalid.\n\nThe statistics line across the top of the Robot editor shows\nthe current line number, the current column within the line, the\nsize of the Robot program versus the maximum size this Robot can\nreach, and the Robot's X/Y position.\n\nUse ESC to exit the Robot editor when you are done editing your\nRobot program.\n\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#ROBOTSWH.HLP\n:1st:\n$~9Robots - What They Are and How to Use Them\n\nRobots are the heart and soul of MegaZeux. Nearly anything you\ncould want to do in MegaZeux - anything you see done in a\nMegaZeux game, many things you didn't know were possible - can\nbe done with MegaZeux's Robots.\n\nRobots are programmed in their own programming language, called\nRobotic. Robotic is a fairly simple language to learn, but\nsomewhat moderate to completely master. It is somewhere along\nthe lines of BASIC, although more complex. If you've ever used\nEpic Megagame's ZZT, Robotic is vastly more complex than\nZZT-OOP, although there are vague similiarities. There's a\nkludge or dozen to get really powerful things working, but\nyou'll get used to them.\n\nTo place a Robot in the editor, press F10 and select Robot. (You\ncan also select Pushable Robot if you want things to be able to\npush the Robot.) Then name the Robot and select a character to\nrepresent it. You are then brought to the Robot editor, where\nyou can program the Robot in Robotic.\n\nRobots are limited to the board, and there can be 255 of them on\neach board. They can be pretty large - 2MB in size, enough code\nspace to create extraordinarily complex (or convoluted)\nprograms. There is an exception: the Global Robot, which is\noutside of the board and runs at all times.\n\n>#ROBOTICT.HLP:1st:Robotic Tutorial\n>#MAIN.HLP:072:Table of Contents\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n#ROBOTICT.HLP\n:1st:\n$~9Robotic Tutorial\n\nRobotic has a few concepts that you must understand before you\nbegin to program in it. You can skip this if you want, but it\nwill help you learn Robotic much faster. A large portion of\nthis will be obvious to anybody who has ever programmed in any\nlanguage or used computers extensively.\n\n$Commands\n\nYou can have one command per line. A command is an instruction\nto MegaZeux or the Robot telling it to do something. A program\nis a series of commands that are ran right after another,\nfrom top to bottom (until told otherwise).\n\n$Parameters\n\nA parameter is the part of the command that can change, such\nas numbers or colors. They specify how the command should be\nrun or what it should affect. For example, in the command:\n\nWAIT 6\n\nWAIT is the command, and 6 is the parameter. Many commands have\nmultiple parameters. Parameters are often specified using #,\n\"string\", or [color] [char], etc.\n\n$Labels\n\nA label is a point in a Robotic program that has been given a\nname. It is used as a reference point so the Robot can return\nto that point at any time, to allow actions like repeating\nsections of commands (looping) or reactions to certain events.\nLabels are written as follows - : \"label\"\n\n$Messages\n\nA message is something one Robot sends to another, or an\nexternal event sends to a Robot, to tell it to do something.\nThis involves jumping to the label corresponding to that\nmessage. For example, a Robot can tell another Robot to\n\"Bounce\"; That Robot will now be executing commands starting\nat the label : \"Bounce\" .\n\n$Coordinates\n\nx,y coordinates are a pair of numbers representing a point on\nthe current board. (0,0) is the upper-left corner of the board,\nand the first coordinate increases while going right, while the\nsecond increases while going down. You can find coordinates\neasily at the bottom of the screen when help is hidden, by\npressing Alt+Y, or by pressing F6 in the game (only in editor\nplaythroughs). x,y coordinates can be negative, but this is\nuncommon and used for special purposes only (e.g. using REL\ncommands).\n\n$Strings\n\nA string is a series of letters or characters, inside quotes.\nFor example, \"STRING\", \"Booga!\", \"-21_Trees-\", or \"[[]]\".\n\n$Characters\n\nA character is a single symbol, letter, number, punctuation\nmark, space, graphic, etc. All things are viewed as single\ncharacters. When using single characters in Robotic, they must\nbe enclosed in apostrophes- e.g. 'X'. Certain characters must\nbe inputted in specific ways to avoid problems with Robotic:\n\n  \\0 for character 0 (this won't work in strings, but will work\n  for single chars)\n  \\t for tab (character 9)\n  \\n for newline (character 10)\n  \\r for carriage return (character 13)\n  \\\" for quotation mark\n  \\\\ for backslash\n\n$Colors\n\nA color is a code representing a background/foreground color\npair. They follow the format cXX, where X is 0-9, A-F or ?\n(which stands for any color). To enter color codes easily, use\nF2 in the Robot editor. The exact format of color codes is\ncovered in another section.\n\nA quirk to keep in mind: any entity with a background color of 0\nwill display the background color of anything beneath it.\n\n$Directions\n\nDirections represent one of the four cardinal directions of\nNORTH, SOUTH, EAST, or WEST. They can be abbreviated to N, S,\nE, and W. There are other directions, but these are the most\ncommonly used.\n\nHopefully you understood the above material, as knowledge of it\nis essential to program in Robotic. If you don't quite grasp\nit, you can return to it later, but you will need to understand\nit eventually.\n\n$Your First Robotic Program: HELLO WORLD!\n\nFirst, open the Editor and create a Robot. Then open the\nRobot's programming box by pressing Enter twice while the Robot\nis highlighted.\n\nNow, type the following into the editor:\n\n~E* \"HELLO WORLD!\"\n~Eend\n\nHere's what each line does.\n\n~E* \"HELLO WORLD!\"\n\nThe * command puts whatever text is in the quotation marks on\nthe bottom of the screen. (Actually, it's placed on the given\nmessage line row, which is the bottom by default.) All Robotic\ncode starts immediately, so this particular statement happens\nright as the board with this Robot is loaded.\n\n~Eend\n\nThis ends the code. It is good practice to include the END\ncommand even when you don't think you need it; leaving the END\ncommand out can cause some problems such as text glitches or,\nworse, the running of any and all code after where you want\nyour Robot to stop!\n\nThat was really simple, wasn't it? Now let's step up to\nsomething a bit bigger.\n\n$Your Second Robotic Program\n\nCreate a Robot in the editor, and give it the following\nprogram:\n\n ~E: \"start\"\n ~EGO NORTH 2\n ~EGO SOUTH 2\n ~EGOTO \"start\"\n\nNow test the world. As you can see, this program makes the\nRobot move up and down. Let's analyze it line by line.\n\n~E: \"start\"\n\nAs we know, this is a label. It marks the beginning of the\nprogram.\n\n~EGO NORTH 2\n\nThis quite plainly tells the Robot to move north two spaces.\n\n~EGO SOUTH 2\n\nThis tells the Robot to move south two spaces.\n\n~EGOTO \"start\"\n\nGOTO tells the Robot to jump to a label. In this case, the\nRobot returns to the label \"start\", effectively restarting its\ncode. This particular Robot will be executing these lines of\ncode for the entirety of the game, or until destroyed.\n\nPretty easy so far, huh? You will probably find that Robotic's\nbasics are easy to grasp. Let's try something a little more\ncomplex.\n\n$Your Third Robotic Program\n\nTry the following Robot program:\n\n ~E: \"loop\"\n ~E/ \"NNEESSWW\"\n ~EGOTO \"loop\"\n ~E: \"touch\"\n ~E* \"Hello!\"\n ~ECHAR 'X'\n ~EEND\n\nTesting will show you that this Robot runs around in a small\nsquare. Now try touching it. If you can catch it, the Robot\nwill stop moving, change to an X and greet you! Let's look at\nthis program.\n\n~E: \"loop\"\n\nA label.\n\n~E/ \"NNEESSWW\"\n\nThe / command tells the Robot to move along a given path. The\npath is represented with a string of the letters N, S, E, W,\nand I. NSEW represent the four cardinal directions, while I\ntells the Robot to wait for a bit before continuing. Our path\ntells the Robot to go north twice, east twice, south twice, and\nwest twice, forming a square path.\n\n~EGOTO \"loop\"\n\nThe Robot will now return to the \"loop\" label, continuing in a\nsquare path forever. Without an outside stimulus, the Robot\nwill never do anything else.\n\n~E: \"touch\"\n\nAnother label, but this one is special. When the player touches\na Robot, by standing next to it and pressing against it, the\nRobot is sent to the label \"touch\". MegaZeux has several labels\nlike this, called \"built-in labels\", that are triggered in\nspecific ways. The following commands will be run when the\nplayer touches the Robot.\n\n~ECHAR 'X'\n\nThis tells the Robot to assume the appearance of character X.\nMake sure that the X is in apostrophes ' and NOT quotes \".\n\n$Robot Interaction\n\nLet's get some Robots to interact with each other. Put two\nRobots on a board, right next to each other. Name the left one\nLefty, and give it this program:\n\n ~E: \"dance\"\n ~E/ \"WWWEEE\"\n ~ESEND \"Righty\" to \"dance\"\n ~EEND\n\nName the right one Righty, and give it this program:\n\n ~EEND\n ~E: \"dance\"\n ~E/ \"EEEWWW\"\n ~ESEND \"Lefty\" to \"dance\"\n ~EEND\n\nNow test this board out. You will see a silly pair of dancing\nRobots, as they bump each other left and right. Let's examine\ntheir programs more closely.\n\nSince Righty has an END command as the first line, he isn't\ngoing to be doing anything until told; so, we look at Lefty.\nIt's nothing we haven't seen, except for the SEND command.\n\n~ESEND \"Robot\" to \"message\"\n\nThe SEND command allows one Robot to send messages to another,\ntelling it what to do. In this case, Lefty will tell Righty to\n\"dance\", sending Righty to the label \"dance\".\n\nKnowing this, the behavior of the two Robots should be clear.\nEach one, in turn, moves away and back again, then it tells\nthe other one to do this. This repeats ad infinitum.\n\nFor a little practice, see if you can get them each to display\na message at the start of their dance step. Make the messages\ndifferent for each Robot.\n\n$Some New Commands\n\nThe following Robot will demonstrate some new commands and\nfeatures.\n\n ~EGIVE 6 AMMOS\n ~EGIVE 5 HIBOMBS\n ~EEND\n ~E: \"shot\"\n ~ECOLOR c2C\n ~E[ \"OKAY, I GET THE POINT. I NEED MORE IRON\"\n ~E[ \"...and now a liver transplant :<\"\n ~Ezap \"shot\" 1\n ~EEND\n ~E: \"shot\"\n ~EIF \"AMMO\" = 0 \"lecture\"\n ~E[ \"...cut it out. =(\"\n ~EEND\n ~E: \"bombed\"\n ~E. \"Check this out...\"\n ~E. \"(editor) I'm not impressed.\"\n ~E* \"ARRGHFRRAGGH I'M ALLERGIC TO BEING BOMBED\"\n ~EDIE\n ~EEND\n ~E: \"touch\"\n ~E* \"Please leave me alone :/\"\n ~EWAIT 10\n ~ESHOOT SEEK\n ~EEND\n ~E: \"lecture\"\n ~E[ \"Did you REALLY have to use all of your shots.\"\n ~EEND\n\nTest this Robot out, bombing, shooting, and touching it. The\nnew commands and labels are explained below.\n\n~EGIVE 6 AMMOS\n\nSensibly enough, this gives the player 6 units of ammo.\n\n~EGIVE 5 HIBOMBS\n\nThis gives the player 5 high-strength bombs.\n\n~E: \"shot\"\n\nWhen the Robot is shot by a bullet, it jumps to this label.\n\n~ECOLOR c2C\n\nThis changes the Robot's color. The c2C represents Lt. Red on\nRed. To enter colors, press F2 in the Robot editor and select\nthe color you want. The appropriate cXX code will be typed in\nfor you.\n\n~E[ \"OKAY, I GET THE POINT. I NEED MORE IRON\"\n~E[ \"...and now a liver transplant :<\"\n\nThe [ command is slick for a default. When encountered, all\nconsecutive [ commands are put together to form one long\nmessage, which is then displayed in a Scroll-like window.\nIt can be scrolled and viewed by the player.\n\n~EZAP \"shot\" 1\n\nThe ZAP command tells the Robot to ignore a certain number of\nlabels with the given name - in this case, the first \"shot\"\nlabel. The ZAP command always starts from the top. Using the\nZAP command lets programmers give out different reactions for\ngoing to the same label. In this case, we get to go to the\nsecond \"shot\" label after we went to the first.\n\n~EIF \"AMMO\" = 0 \"lecture\"\n\nThe IF command will jump to a label (in this case, one called\n\"lecture\") if the given condition is met, and will continue to\nthe next line if not met. In this case, it will check to see if\nthe AMMO counter is 0. The AMMO counter is a built-in counter\nMZX uses to represent how many units of ammo the player holds.\n\n~E: \"bombed\"\n\nWhen the Robot is bombed, or otherwise hit by an explosion,\nit is sent this message.\n\n~E. \"Check this out...\"\n~E. \"(editor) I'm not impressed.\"\n\nNotice how this message was not shown in any way. The . command\nis used for comments, i.e. messages that are not to be shown.\nThey are good for your own reference, so you know what you were\ntrying to do with this Robot, or so you can keep notes of\nimportant things.\n\n~EDIE\n\nThis command destroys the Robot forever.\n\n~EWAIT 10\n\nWAIT causes the Robot to stop and sit for an amount of time.\nThe time is in number of cycles (in this case, 10 cycles). A\ncycle is the amount of time it takes to update the board once,\nmoving all the enemies and bullets, etc. Robots can run at\ndifferent speeds; the default is cycle 1 (fastest, acts every\ncycle) unless a cycle command is placed inside the Robot.\n\n~ESHOOT SEEK\n\nThe Robot will shoot a bullet in the indicated direction (in\nthis case, the nearest direction the player is in).\n\n$Conclusion\n\nYou now should know enough Robotic for simple programs. Even\nmore importantly, you should be able to scan the Robotic\nreference manual, including the command reference, and be able\nto learn many new commands. There are loads of important\ncommands and concepts in the command reference that we have not\nyet mentioned, such as TELEPORT PLAYER, strings, file access,\nexpressions, sprites, and the COPY class of statements. It is\nrecommended that you at least skim each of the Robotic reference\nmanual sections.\n\nMake sure you (eventually) read over every part of the\nreference manual, as there are many sections not covered in the\ntutorials. Especially of interest will be advanced topics like\n\"The Vlayer and Its Uses\", \"Using MZMs\", \"Subroutines\",\n\"Debug Modes\", and most newer help file additions.\n\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#THEGLOBL.HLP\n:gbl:\n$~9The Global\n\nThe global Robot is a special-case Robot that is always present\non every board of the game. It is immobile (technically off the\nboard, but listed as at (-1,-1)), has a ROBOT_ID of 0, and\ncannot be destroyed or changed into another object, even\nthrough deliberate attempts to do so such as the EXPLODE\ncommand. All other traits of Robots, such as 2MB of Robot\nmemory, reserved local counters and the ability to be copied,\napply to the global.\n\nThe global is accessed either through pressing G to access the\nGlobal Settings menu and then selecting Edit Global Robot, or by\nsimply pressing Alt+G on the editor screen.\n\nThe global is important because it not only exists on all\nboards, but also continues its state across boards. This allows\nfor easy management of continuous global engines such as an\ninventory, status bars, and sidescrolling control, among\nother possibilities. If the global did not exist, a copy of the\nengine would be needed on every board, and the states of each\nsuch Robot would be separate (possibly leading to management\nproblems).\n\nCertain commands (as hinted above) are worthless in the global.\nObvious commands such as BECOME, EXPLODE, COLOR/CHAR and all\ncommand forms that take a relative direction (like LAYBOMB and\nDUPLICATE SELF [dir]) will be ignored. DIE, DIE ITEM and GOTOXY\n.# # are completely ignored as well. Any checks at (-1,-1) will\nfail to detect the global.\n\nFinally, the global is the only Robot that ignores FREEZETIME\nand SLOWTIME states. This is extremely handy in creating the\nability to pause and unpause a MegaZeux game, among other\nthings.\n\n>#COMMAND2.HLP:_f4:FREEZETIME #\n>#COMMAND2.HLP:_sS:SLOWTIME #\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#BADPRACT.HLP\n:bad:\n$~9Robotic Usages That Should be Avoided\n\nThere are several coding pitfalls in Robotic. While some are\ncomplex in nature, others can be easily avoided. In general,\nclean and efficient code is the least likely to cause\nunexpected errors as well as the easiest to repair. Listed here\nare several practices to avoid, as well as proper replacements.\n\n~cBAD: Creating anything with c?? or p??\n  Anything that has an undefined value will use a default\n  that may be undesirable. The correct action would therefore\n  be to define the object's color and parameter values. c?? and\n  p?? generally exist for the sake of Robotic comparisons and\n  conditional branching, not for being applied to actual\n  objects.\n  Sprites are an exception regarding color, since they refer to\n  a set of placed characters with their own colors.\n~9GOOD: Putting in set values for colors and parameters when\n  ~9placing objects.\n\n~cBAD: Using global counters for temporary or localized work.\n  There are several counters that are Robot-specific and don't\n  eat up global counter space. local through local32 per Robot\n  should be enough for almost everyone, and the local counters\n  can be written to and read by other Robots as well (using the\n  rN.local# counter form). If global temp counters are used in\n  a Robot with several copies on the board, you will most likely\n  run into problems. Unfortunately, using local counters can\n  come at the cost of minor readability, since local counters\n  cannot be given descriptive names.\n~9GOOD: Using local counters for temporary or localized work.\n\n~CBAD: Breaking a subroutine without returning or entering the\n  ~Cmiddle of a subroutine.\n~9GOOD: Fully finishing and starting subroutines.\n  This is especially important for built-in labels which can\n  trigger very rapidly, such as #keyN.\n\n~cBAD: Using a high amount of if statements regarding one\n  ~Cvariable.\n~9GOOD: Using \"label&number&\" as the destination label in IF\n  ~9statements regarding one variable.\n\n~CBAD: Looping without using cycle-ending commands of any kind.\n  Looping without CYCLE 1 or WAIT 1 in idle loops is more\n  severe than one might think. Coupled with the \"commands\"\n  counter set to a high number, loops without CYCLE 1 or WAIT 1\n  can absolutely choke a game's speed, especially idle loops,\n  and potentially FREEZE YOUR GAME.\n~9GOOD: Including CYCLE 1 or WAIT 1 in every loop except loops\n  ~9that are designed to get a finite length task done as\n  ~9quickly as possible.\n  You should add a CYCLE 1 or WAIT 1 for any loop that\n  constantly checks for events (such as keypresses or board\n  status).\n\n~CBAD: Setting the COMMANDS value to maximum.\n  It's generally unneeded, and a loop without a cycle-ending\n  command coupled with this change is a guaranteed way to cause\n  freezes in MZX. MZX has safeguards for when this happens, but\n  it is still very undesirable.\n~9GOOD: Setting the COMMANDS counter to a reasonably high value.\n  Your code can utilize the extra processing speed higher\n  commands-per-cycle allows, and if a loop without a\n  cycle-ending command is accidentally created, it becomes\n  apparent without requiring severe intervention.\n\n~cBAD: Ending Robots with a label.\n~9GOOD: Ending Robots with the END command.\n  It's debatable whether putting END after commands that\n  destroy the Robot (e.g. DIE, EXPLODE #) is necessary, but it\n  is still recommended in order to prevent a habit of leaving\n  it out in less redundant places.\n\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#SUBROUTE.HLP\n:sub:\n$~9Subroutines\n\nSubroutines are returnable labels, in essence. After the\nsubroutine actions finish, the Robotic goes to the line right\nafter the subroutine call. Anywhere a label can be used, a\nsubroutine can be called.\n\nAnother way to think about it is a label with a place marker.\nGoing to this label marks your previous place; when done, the\nprogram goes straight to the place marker and returns where it\nleft off.\n\nSubroutines are accessed by using the # sign as the first\nletter. For example: ~egoto \"#example\"~f would call the\nsubroutine at the label \"#example\". To return to the next\ncommand after the call use ~egoto \"#return\"~f.\n\nIn addition, one can go to the first subroutine's return\nposition using ~egoto \"#top\"~f.\n\nA quick example of subroutines:\n\n~E* \"YE SHALL NOT PASS UNLESS YOU GIVE ME SPACE.\"\n~Egoto \"#spacewait\"\n~E* \"THAT'S BETTER BUT YOU'RE STILL HERE. _CORRECT THIS_\"\n~Egoto \"#spacewait\"\n~E* \"THANKS FOR YOUR COMPLIANCE!\"\n~Ewait 25\n~E* \"YOUR PASS WILL ARRIVE IN 6 TO 8 WEEKS! or now! byebyebye\"\n~Eset \"PASS\" to 1\n~Edie\n~Eend\n~E: \"#spacewait\"\n~E: \"spaceheld\"\n~Ecycle 1\n~E. \"If space is held down, progress no further.\"\n~E. \"We want to force space to be pressed each time.\"\n~Eif spacepressed \"spaceheld\"\n~E: \"notspaceloop\"\n~Ecycle 1\n~Eif not spacepressed \"notspaceloop\"\n~Egoto \"#return\"\n\nWhen sending other Robots to subroutines the behavior is\nslightly different. Because the Robot is pre-empted\n(interrupted) it will return to the same command it was at\nbefore going to the subroutine, not the instruction after.\n\nWhen using subroutines - especially with easily triggerable\nconditions - it is important to try to make sure that every\nsubroutine send ends in a #return, or that every set of\nsubroutine sends ends in a #top. The built-in labels can be\nespecially problematic, as some can trigger rapidly in a short\nperiod of time. State management with LOCKSELF and/or ZAP is\ncrucial.\n\nNOTE: Unlike older versions of MegaZeux, it is no longer needed\nto initialize the stack, so don't get confused looking at older\nexamples of code and seeing odd statements like ~e. \"#*-1-2-3\"~f.\nThese statements are now treated as comments, and you should\nmentally treat them the same way.\n\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#EXPRESS.HLP\n:exp:\n$~9Expressions\n\nExpressions in MegaZeux allow users to set counters or a\ndisplayed number to a mathematical expression. The possible\ninputs are: counters, constants, or a combination. Any\nexpression in MegaZeux is bound in quotation marks and\nparentheses. For example, an expression adding 5 and 3 would\nbe done in MegaZeux by inputting \"(5 + 3)\".\n\nCounters can be used inside expressions using single quotes or\nampersands like so: \"('five' + &three&)\". Using single quotes is\npreferred to prevent confusion with the normal usage of\nampersands to interpolate counters.\n\nUnlike in many other programming languages, MegaZeux does not\nfollow a strict order of operations. Items in parentheses take\nprecedence, then unary ops; ternary is evaluated last, with all\nother operator precedence solely determined by their positions.\nUnary ops are handled right-to-left; everything else is handled\nleft-to-right.\n\nFor example, \"(5 - 4 * 20 / (21 - 16))\" would evaulate to 4.\n5 - 4 evaluates to 1; 1 * 20 evaluates to 20; 20 / (21 - 16)\nfirst evaluates to 20 / 5, which becomes 4.\n\nWhitespace between operators is not necessary. (5+3) is as valid\nas (5 + 3).\n\nMegaZeux cannot decipher directly nested ampersands or single\nquotes; such will cause MegaZeux to misinterpret your\nexpression in different ways, depending on which you used.\nTherefore, using expressions for nesting is another benefit.\n\nExample: We set \"r\" to 1, \"in\" to 8 and \"out18\" to 11. We want\nto output the value of \"out18\".\n\"(&out&r&&in&&)\" would output (0r&in&).\n\"('out'r''in'')\" would output ('out'r''in'')\n\"('out('r')('in')')\" would output the desired result - 11.\n\nNesting is limited only to the amount one can put in a single\neditor line - 241 characters.\n\nIn expressions, the following operators may be used:\n\nBinary operators (value-argument-value):\n\n +   Addition\n -   Subtraction\n *   Multiplication\n /   Division\n %   Modulo\n ^   Exponent (A^B is A to the Bth order/power)\n >>  Bitshift Right (logical, not arithmetic)\n <<  Bitshift Left (logical, not arithmetic)\n >>> Signed Shift Right (arithmetic)\n >   Greater Than\n <   Less Than\n >=  Greater Than or Equal To\n <=  Less Than or Equal To\n =   Equal To\n !=  Not Equal To\n a   Bitwise AND (not logical AND)\n o   Bitwise OR (not logical OR)\n x   Bitwise XOR (not logical XOR)\n\n~E+ Addition~F: Outputs the sum of the two given values. (3+2)\nwould return 5.\n~E- Subtraction~F: Outputs the difference of the two given values.\n(3-2) would return 1.\n~E* Multiplication~F: Outputs the product of the two given values.\n(3*2) would return 6.\n~E/ Division~F: Outputs the quotient of the two given values.\n(3/2) would return 1. Note that any fractional values will be\ntruncated, i.e. have the decimal part of the number thrown out.\n~E% Modulo~F: Outputs the remainder of the two given values, when\nboth values are positive. (5%3) would return 2. The modulo\nexpression function and the modulo command may output different\nresults if any number involved is negative; unlike the command,\nthe modulo expression function uses a floored modulo.\nTo explain, if the dividend is negative, than the result will\nbe the divisor minus the remainder (e.g. (-5%3) would return\n1); if the divisor is negative, than the result will be the\nadditive inverse of the difference of the divisor and the\nremainder (e.g. (5%-3) would return -1); if BOTH are negative,\nthe result will be the additive inverse of the remainder (e.g.\n(-5%-3) would return -2).\n~E^ Exponent~F: Outputs the result of taking the first number to\nthe power of the second number. (3^2) would return 9.\n~E>> Bitshift Right~F: Shifts the first number rightward by [second\nnumber] of bits. Info shifted off of the boundaries will NOT\nwrap around. Using an 8-bit counter for clarity's sake, (5>>2)\nwould turn 5 [00000101] into 1 [00000001] by shifting two bits\nrightward.\n~E<< Bitshift Left~F: Shifts the first number leftward by [second\nnumber] of bits. Info shifted off of the boundaries will NOT\nwrap around. Using an 8-bit counter for clarity's sake, (5<<2)\nwould turn 5 [00000101] into 20 [00010100] by shifting two bits\nleftward.\n~E>>> Signed Shift Right~F: Also known as Arithmetic Right Shift.\nShifts the first number rightward by [second number] of bits,\nbut unlike normal bitshift right, shifts in whatever number was\nin the leftmost bit instead of always shifting in 0s. Info\nshifted off of the boundaries still will not wrap around. Using\nan 8-bit counter for clarity's sake, (5>>>2) would turn 5\n[00000101] into 1 [00000001], while (-5>>>2) would turn -5\n[11111011] into -2 [11111110].\n~E> Greater Than~F: If the first number is greater than the second\nnumber, outputs 1 (TRUE); otherwise, outputs 0 (FALSE). (3>2)\nwould return 1, (2>2) would return 0.\n~E< Less Than~F: If the first number is less than the second\nnumber, outputs 1 (TRUE); otherwise, outputs 0 (FALSE). (3<2)\nwould return 0, as would (2<2).\n~E>= Greater Than or Equal To~F: If the first number is either\ngreater than or equal to the second number, outputs 1 (TRUE);\notherwise, outputs 0 (FALSE). (3>=2) would return 1, as would\n(2>=2).\n~E<= Less Than or Equal To~F: If the first number is either less\nthan or equal to the second number, outputs 1 (TRUE);\notherwise, outputs 0 (FALSE). (3<=2) would return 0, (2<=2)\nwould return 1.\n~E= Equal To~F: If the first number is the same value as the second\nnumber, outputs 1 (TRUE); otherwise, outputs 0 (FALSE). (3=2)\nwould return 0, (2=2) would return 1.\n~E!= Not Equal To~F: If the first number is a different value\ncompared to the second number, outputs 1 (TRUE); otherwise,\noutputs 0 (FALSE). (3!=2) would return 1, (2!=2) would return\n0.\n~Ea = Bitwise AND~F: Performs a bitwise AND operation on the two\ngiven values; i.e. compares the bits of each value and sets to\n1 each bit that is 1 in both numbers. Using an 8-bit counter\nfor clarity's sake, (5a3) would return 1, as 5 [00000101] and 3\n[00000011] has only the bit in the ones place set in both\nnumbers, leaving 1 [00000001].\n~Eo = Bitwise OR~F: Performs a bitwise OR operation on the two\ngiven values; i.e. compares the bits of each value and sets to\n1 each bit that is 1 in either number. Using an 8-bit counter\nfor clarity's sake, (5o3) would return 7, as 5 [00000101] and\n3 [00000011] have bits set in the fours, twos, and ones places\namong them, leaving 7 [00000111].\n~Ex = Bitwise XOR~F: Performs a bitwise XOR operation on the two\ngiven values; i.e. compares the bits of each value and sets to\n1 each bit that is different between numbers. Using an 8-bit\ncounter for clarity's sake, (5x3) would return 6, as 5\n[00000101] and 3 [00000011] have different bits set in the\nfours and the twos places, leaving 6 [00000110].\n\nUnary operators (operator followed by value):\n\n~E- Unary Negation~F: returns the negative value of the operand\n(two's complement); i.e. reverses the sign of a number. (-10)\nwould return -10.\n~E~~ Bitwise Negation~F: returns the bitwise NOT value of the operand\n(one's complement); i.e. changes each bit in a number. Using an\n8-bit counter for clarity's sake, (~~10) [00001010] would return\n-11. [11110101]\n\nTernary operators (value - operator token - value - operator\ntoken - value):\n\n~E?: Conditional Operator~F: Evaluates expressions based on the\nvalue of the first given expression. This operator is often\nsimply called the ternary operator. If the expression to the\nleft of the question mark is non-zero, the expression between\nthe question mark and colon is evaluated; otherwise, the\nexpression to the right of the colon is evaluated. For example,\nIf \"local\" is 35, SET \"local\" ('local'<60?'local'+2:'local'+1)\nwould set the value of \"local\" to 37, as the first expression\nevaluates to 1 and the second expression ('local'+2) is then\nevaluated.\n\nSpecial usages:\n\n-Using a constant expression \"(n)\" is the only way to directly\nuse numbers over 32767 or under -32768. This can not be changed\ndue to the way the world file format is coded. This is also the\nonly way to use octal (base 8) and full-sized hexadecimal\nnumbers; octal numbers always begin with a 0 (ex: 01745) and hex\nnumbers begin with 0x (ex: 0xDEAF).\n\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#FILEACSS.HLP\n:fil:\n$~9File Access\n\nMegaZeux allows somewhat unwieldy but powerful file access\nabilities. File access can do things that no other tools of\nMegaZeux can do, such as create high score tables or preserve\nand unlock game options without needing a save. An MZX world\ncan access any file in its own directory or deeper, including\nfor charset, palette, sound and MZM loading commands. To do\nthis, give MZX the subdirectory name as well as the file name.\nBoth slashes and backslashes are accepted, regardless of OS.\nExamples: \"subdir/subsubdir/file.txt\", \"subdir\\\\file.txt\".\nAccess of folders outside of this range is forbidden.\n\nMegaZeux has some ways of accessing standard formats in-game:\n\n~ASET \"file.txt\" to \"SAVE_ROBOT\"\n~ASET \"file.txt\" to \"LOAD_ROBOT\"\n~ASET \"file.txt\" to \"SAVE_ROBOTn\"\n~ASET \"file.txt\" to \"LOAD_ROBOTn\"\n\nThese commands can save Robotic code to text files and import\ntext files into a Robot. The first forms use the current Robot,\nwhile the second forms use the Robot with the given Robot ID n.\nString inputs are also accepted in place of hard-coded file\nnames.\n\n~ASET \"file.bc\" to \"SAVE_BC\"\n~ASET \"file.bc\" to \"LOAD_BC\"\n~ASET \"file.bc\" to \"SAVE_BCn\"\n~ASET \"file.bc\" to \"LOAD_BCn\"\n\nThese commands can save Robotic code to files in bytecode format\nand import bytecode files into a Robot. The first forms use the\ncurrent Robot, while the second forms use the Robot with the\ngiven Robot ID n. Bytecode is much more compact than plain text,\nbut cannot be easily read by the human eye.\n\nNote that for loading Robotic code via LOAD_ROBOT(n) or\nLOAD_BC(n), any and all local counters previously set in the\nRobot will retain their value.\n\n~ASET \"file.sav\" to \"SAVE_GAME\"\n~ASET \"file.sav\" to \"LOAD_GAME\"\n\nThese commands save or load MZX savegames, respectively.\n\n~ASET \"file.sav\" to \"SAVE_COUNTERS\"\n~ASET \"file.sav\" to \"LOAD_COUNTERS\"\n\nThese commands respectively save or load a counters file.\nCounter files only contain counter and string data (to be\nspecific, only global and current board counters/strings).\nUnlike save files, they do not require version checks. Saving\nwill dump the current contents of the current world's counters\nand strings; loading will set the current world's counter and\nstring values to the values given in the file.\n\n~ASET \"file.palidx\" to \"SMZX_INDICES\"\n\nThis command loads an SMZX indices file. SMZX indices are\nessentially miniature palettes and will only load in SMZX Mode\n3.\n\nMegaZeux can manipulate any type of file with certain Robotic\ncommands. Here are some basic things to remember when using file\naccess.\n\n-A file can only be read or written to at one time, never both.\nThis means you can't select the same file to be the current\n\"FREAD_OPEN\" and \"FWRITE_OPEN\" targets.\n-Only one file can be read to and one can be written to at the\nsame time.\n-Writing and reading operations change the current fwrite and\nfread positions.\n-The writing and reading position is set to 0 (the beginning\nposition of a file) when a file is loaded, unless the\nFWRITE_APPEND command is chosen.\n-There is a special character called the terminator (char #42,\n'*', by default) placed at the end of a write when a string is\nwritten to a file with FWRITE. This character ends an FREAD to\na string when encountered (the terminator character itself will\nnot be in the string output). However, using FWRITEn will not\nplace a terminator, and FREADn will ignore any terminator\ncharacters encountered.\n-To find the length of a file, use the FREAD_LENGTH and\nFWRITE_LENGTH counters. They will report the length of the file\n(in characters) currently open in the respective mode. If a\ndirectory is open for reading, the counter will be the number of\nfiles and subdirectories in that directory. If there is no file\nor directory open, these counters will return -1.\n-Lastly, files need to be closed when the current task is\nfinished. Otherwise, the file remains open and causes problems.\nTo close a file, use SET \"\" to \"FREAD_OPEN\" to close a file for\nreading and SET \"\" to \"FWRITE_OPEN\" to close a file for\nwriting.\n\nHere are the file writing commands at your disposal.\n\n~ASET \"file.xxx\" to \"FREAD_OPEN\"\n\nThis command loads a file for reading.\n\n~ASET \"file.xxx\" to \"FWRITE_OPEN\"\n\nThis command creates a new file for writing. If a file by this\nname already exists, it is overwritten.\n\n~ASET \"file.xxx\" to \"FWRITE_MODIFY\"\n\nThis command opens an existing file for reading without\noverwriting it, starting at the beginning of the file.\n\n~ASET \"file.xxx\" to \"FWRITE_APPEND\"\n\nThis command opens a file for writing at the END of the file.\nPlease note that FWRITE_POS settings will not work in this\nmode.\n\n~ASET \"FREAD_POS\" to #\n~ASET \"FWRITE_POS\" to #\n\nThese commands change the current reading or writing position\nof the accessed files. The position is in DECIMAL, not\nhexadecimal, and the first position is 0; when no file is\nloaded, the relevant counter is -1.\n\nSetting \"FREAD_POS\" or \"FWRITE_POS\" to -1 is called \"file\nend seeking\" and will set the relevant counter to the final\nposition in the file (or -1 if the file does not exist). This is\nvery helpful for placing stuff at the end of a file.\n\n~ASET \"dir\" to \"FREAD_OPEN\"\n\nThis command opens subdirectories for reading (by using the name\nof the subdirectory by itself, e.g. ~ASET \"directory\" to~F\n~A\"FREAD_OPEN\"~F). FREAD will then list the names of files in the\ndirectory, with each read position being a different filename.\nTo read the directory the current world is in,\n~ASET \".\" to \"FREAD_OPEN\"~F. Note that the files are not guaranteed\nto be read in any specific order.\n\n~ASET \"FREAD_DELIMITER\" to #\n~ASET \"FWRITE_DELIMITER\" to #\n\nThese commands change the character considered the terminator.\nThe FREAD command changes which character is detected as the\nterminator, and the FWRITE command changes which character is\nwritten as the terminator.\n\n~ASET \"FWRITE\" to \"counter\"\n~ASET \"$string\" to \"FWRITE\"\n~ASET \"$string\" to \"FWRITEn\"\n~ASET \"counter\" to \"FREAD\"\n~ASET \"$string\" to \"FREAD\"\n~ASET \"$string\" to \"FREADn\"\n\nThese commands write values to and read values from,\nrespectively, the open file relative to the FWRITE_POS or\nFREAD_POS counters. FWRITE and FREAD, when used with counters,\nonly write one byte at a time (from 0-255).\nThe SET \"$string\" to \"FWRITEn\" and \"FREADn\" commands will write\nthe first n characters (relative to FREAD_POS or FWRITE_POS)\nfrom the file to the given string or read the first n\ncharacters from the string into the open file, respectively.\nUsing ~ASET \"$string.N\" to \"FWRITE(n)\"~F will ensure that the\nstring will be at least N characters long when written.\nFWRITE and FREAD, when used with counters, can be substituted\nby FWRITE_COUNTER and FREAD_COUNTER to read in longer values\nat a time (four bytes as opposed to one).\n\n~ASET \"counter\" to \"FREAD_COUNTER\"\n\nReads from the open file to a counter. This will grab four\nbytes instead of one (treated as signed, so the range is from\n-2147483648 to 2147483647).\n\n~ASET \"FWRITE_COUNTER\" to \"counter\"\n\nWrites from a counter to the open file. This will write all four\nbytes of the counter instead of just the first byte (treated as\nsigned, so the range is -2147483648 to 2147483647).\n\nHere's a quick example of file access.\n\n~ESET \"example.txt\" to \"FWRITE_OPEN\"\n~ESET \"$zoosound\" to \"ROAAAAR\"\n~ESET \"$zoosound\" to \"FWRITE\"\n~EDEC \"FWRITE_POS\" by 1\n~E. \"This line backs us up one space so that the terminator\n~E. \"character will be overwritten, allowing us to display the\"\n~E. \"whole contents of the file.\"\n~ESET \"$ender\" to \" (click)\"\n~ESET \"$ender\" to \"FWRITE\"\n~ESET \"\" to \"FWRITE_OPEN\"\n~E. \"We're closing the file because we're going to read from it.\"\n~ESET \"example.txt\" to \"FREAD_OPEN\"\n~EASK \"Want to play your animal sounds tape?\"\n~EEND\n~E: \"yes\"\n~ESET \"$tape\" to \"FREAD\"\n~E* \"&$tape&\"\n~EWAIT for 30\n~EZAP \"yes\" 1\n~EASK \"Want to play it again?\"\n~EEND\n~E: \"yes\"\n~ESET \"\" to \"FREAD_OPEN\"\n~E. \"Switching tasks for the same file again.\"\n~ESET \"example.txt\" to \"FWRITE_OPEN\"\n~E. \"We're wiping the file clean.\"\n~ESET \"$winder\" to \"bzz-bz-zipfwip--(clik) \"\n~ESET \"$winder\" to \"FWRITE\"\n~EDEC \"FWRITE_POS\" by 1\n~ESET \"$tape\" to \"FWRITE\"\n~ESET \"\" to \"FWRITE_OPEN\"\n~ESET \"example.txt\" to \"FREAD_OPEN\"\n~EZAP \"yes\" 1\n~E: \"yes\"\n~ESET \"FREAD_POS\" to 0\n~E. \"We're forcing the beginning position because we want to\"\n~E. \"read all of the file every possible time, not just the\"\n~E. \"first time.\"\n~ESET \"$tape\" to \"FREAD\"\n~E* \"&$tape&\"\n~EWAIT for 30\n~EASK \"Want to play it again?\"\n~EEND\n~E: \"no\"\n~ESET \"\" to \"FREAD_OPEN\"\n~EEND\n\nThis Robot will display first a roar and the sound of a tape\nplayer clicking off before it asks if you want to play the tape\nagain. If no, the file is closed and the program ended. If yes,\nrewinding tape sounds are stuck on the front of the text. The\nplayer can then choose to re-view this text until \"No\" is\nchosen.\n\nHere is another example, for a basic and very common use of file\naccess: using and updating a high score list.\n\n~ESET \"game.hi\" \"FREAD_OPEN\"\n~ESET \"NAME_LENGTH\" 3\n~ESET \"SCORE_LENGTH\" 4\n~ESET \"ENTRY_LENGTH\" 7\n~ESET \"ENTRY_NUMBER\" 10\n~E. \"We will allow 3 characters maximum for listed names here.\"\n~E. \"File structure: initials (3 chars) and score (4 chars).\"\n~E. \"Ten 7-char entries total, no chars between them, in order.\"\n~E. \"This assumes a fully-populated default highscore list.\"\n~E. \"However, it should be relatively simple to create your own.\"\n~ESET \"FREAD_POS\" \"('FREAD_LENGTH'-'SCORE_LENGTH')\"\n~E. \"First, we read the lowest high score value to compare.\"\n~E. \"If our score is below it, no new high score.\"\n~ESET \"local\" \"FREAD_COUNTER\"\n~EIF \"SCORE\" >= \"local\" \"newscore\"\n~ESET \"\" \"FREAD_OPEN\"\n~EEND\n~E: \"newscore\"\n~E. \"Let's find out where it needs to go first.\"\n~ESET \"FREAD_POS\" 0\n~E: \"placecheck\"\n~EINC \"FREAD_POS\" \"NAME_LENGTH\"\n~ESET \"local\" \"FREAD_COUNTER\"\n~EINC \"local2\" 1\n~EIF \"SCORE\" >= \"local\" \"placement\"\n~EIF \"local2\" < \"ENTRY_NUMBER\" \"placecheck\"\n~E[ \"WTF\"\n~E. \"We should never, ever, get here, but it's nice to check :)\"\n~ESET \"\" \"FREAD_OPEN\"\n~EEND\n~E: \"placement\"\n~EDEC \"FREAD_POS\" \"ENTRY_LENGTH\"\n~ESET \"$temp\" \"FREAD('FREAD_LENGTH'-('local2'*'ENTRY_LENGTH'))\"\n~E. \"This places the lower, still-ranked entries into a string.\"\n~E. \"We will write these scores to their new places later.\"\n~E. \"We skip reading entry #10, as it no longer qualifies.\"\n~ESET \"\" \"FREAD_OPEN\"\n~ESET \"game.hi\" \"FWRITE_MODIFY\"\n~E. \"This write mode keeps the previous contents intact.\"\n~ESET \"FWRITE_POS\" \"('local2'-1*'ENTRY_LENGTH')\"\n~E. \"Setting to write in the correct place.\"\n~EINPUT STRING \"Hooray! You're #&local2&! Enter your initials.\"\n~ESET \"$temp2\" \"INPUT\"\n~E. \"The next line checks input length, as that needs to be 3+\"\n~EIF \"$temp2.length\" < \"NAME_LENGTH\" \"#correct\"\n~ESET \"$temp2\" \"FWRITE&NAME_LENGTH&\"\n~E. \"We only write the first 3 chars; ignore any extra.\"\n~ESET \"FWRITE_COUNTER\" to \"SCORE\"\n~ESET \"$temp\" \"FWRITE('$temp.length')\"\n~E. \"The whole dang string goes into the rest of the file.\"\n~E. \"Last example used FWRITE instead of FWRITEn for this.\"\n~E. \"That leaves a terminator, though, which we don't want.\"\n~E. \"This is another way to write it all with no terminator.\"\n~ESET \"\" \"FWRITE_OPEN\"\n~E. \"Closing the file doesn't require the same write type.\"\n~EEND\n~E: \"#correct\"\n~EIF \"$temp2.length\" = 0 \"#default\"\n~E: \"add\"\n~E. \"Input was under three chars long, and we need three.\"\n~EINC \"$temp2\" \" \"\n~EIF \"$temp2.length\" < \"NAME_LENGTH\" \"add\"\n~EGOTO \"#return\"\n~E: \"#default\"\n~ESET \"$temp2\" \"???\"\n~E. \"If the user doesn't put in anything, this makes initials ???\"\n~EGOTO \"#top\"\n\nCode to properly display the scores will be an exercise left to\nthe reader.\n\n>#COUNTERS.HLP:fac:File Access Counters\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#SPRITES.HLP\n:spr:\n$~9Sprites\n\nSprites allow the designer to have interactive entities on the\nboard composed of several characters. A sprite is a block of\ncharacters displayed on the board, either beneath or above the\noverlay.\nThe biggest benefit of sprites is that they are cohesive:\nunlike use of several Robots, there is absolutely no chance of\nthe sprite falling apart.\n\nSprites also have the benefit of a well-defined draw order. This\nmeans that if sprites overlap, it is easy to determine which\nsprite gets shown in the overlap. By default, sprites determine\nthis by sprite number. The sprites are drawn in order of sprite\nnumber, which means that parts of higher-numbered sprites will\nbe displayed over lower-numbered ones whenever they overlap.\n\nThere are up to 256 available sprites (globally, not per board),\nand sprites are controlled in the following ways:\n\n-Through sprite-specific counters named SPRn_value (\"n\" being\n the sprite number, e.g. SPR10_value modifies sprite number 10);\n-Through control counters for all of the sprites;\n-Through commands for placing sprites and testing for collision.\n\nA sprite's appearance is determined by a set of reference\ncharacters. These are characters on the board or the vlayer that\nmake up what the sprite will look like. By default, sprites look\nfor these characters on the board.\nAs with the overlay, any instance of char 32 in the sprite will\nnot be shown, instead displaying the next underlying layer.\nAlso, if the reference characters change, the sprite's\ncharacters will change to match, as will colors of a c?? sprite.\n\nTo set up a sprite you must set its SPRn_REFX / REFY / WIDTH /\nHEIGHT counters to define where the sprite's reference\ncharacters are and the sprite's size. (SPRn_REFX and SPRn_REFY\nare the x,y coordinates for the upper-left corner of the\nreference characters.) Then you use:\n\n~APUT cXX sprite pNN x y\n\nwhere NN is the number of the sprite in hexadecimal (if you\nwrite NN without the p in decimal it will be automatically\nconverted). If cXX is c?? then the sprite is drawn with the\ncolors of the reference characters, otherwise it's painted with\nthe given colors. Once a sprite is placed, it can be easily\nremoved by setting its SPRn_OFF counter to any value.\n\nSo, for example, to make a 2x2 sprite, you could base its\nreference characters on the board from the position (10, 10),\nassign it to sprite number 15, then have it drawn at position\n(40, 5). The Robotic for this is as follows:\n\n~ASET \"spr15_refx\" 10\n~ASET \"spr15_refy\" 10\n~ASET \"spr15_width\" 2\n~ASET \"spr15_height\" 2\n~APUT c?? sprite p0f 40 5\n\nYou can then move the sprite around by changing spr15_x and\nspr15_y. For example, using:\n\n~AINC \"spr15_x\" 1\n~ADEC \"spr15_y\" 1\n\nwill move the sprite to the northeast by 1.\n\nOutside of simple sprite motion such as this, you can do\ncollision tests. These check if moving sprites will cause\ncollision (i.e. overlapping) with either the foreground\n(customblocks) or with other sprites. This way, you can do\nthings such as prevent sprites from moving into walls or other\ncharacters, or allow a sprite to damage another one when\nstriking.\n\nFor these, sprite collision boxes must be set, as sprites will\nnot have one set by default. Use SPRn_CWIDTH and SPRn_CHEIGHT to\nset the respective width and height of the collision box (in\ntiles). By default, sprite collision boxes originate from the\nupper-left corner of the sprite, but the origin point can be\noffset horizonally and vertically by setting the SPRn_CX and\nSPRn_CY counters.\n\nOnce collision boxes are established, collision tests have to\nbe triggered. This is done by using the following command:\n\n~AIF c?? sprite_colliding pNN x y \"collision\"\n\nThis will go to the label \"collision\" if moving sprite NN x by\ny from its current position would cause it to collide with\nsomething. If anything besides c?? is used then it tests whether\nit would collide with whatever is at absolute board coordinates\nx,y instead.\n\nIf collisions are detected, then the spr_clistN counters will be\nset to what sprite numbers it collided with - one counter set\nfor each other sprite in the collision, starting with spr_clist0\n- and spr_collisions will be set to the number of things that\nthe sprite collided with. If the sprite collided with the\nforeground, spr_clist0 will be set to -1.\n\nSo for instance, if the sprite collided with the foreground and\nsprites 2 and 3, spr_collisions will be set to 3, spr_clist0\nwill be -1, spr_clist1 will be 2, and spr_clist2 will be 3. If\nthe sprite collided only with sprites 2 and 3, spr_collisions\nwill be set to 2, spr_clist0 will be 2, and spr_clist1 will be\n3. The values of spr_clistN with values of N greater than\n(spr_collisions - 1) are undefined.\n\nAlternatively, a collision check for a sprite can be done by\nsetting that sprite n's \"SPRn_CLIST\" counter to anything. This\nchecks that sprite against its current location and does not\nforce a label jump on collision.\n\nSprites can be set to ignore certain types of sprite chars in\ncollision checks by setting the sprN_ccheck value of a given\nsprite N. In addition, setting a sprite's ccheck value to 2 will\nprevent blank characters in a sprite from being shown, much like\nchar 32.\n\nSince sprites are global, they will persist upon changing\nboards. (They can, however, be turned off on leaving a board by\nsetting the SPRn_OFFONEXIT counter to 1). Since the vlayer is\nglobal as well, using the vlayer to reference characters (by\nsetting the SPRn_VLAYER counter to 1) will keep sprite\ncharacters consistent between boards.\n\n$~9Unbound Sprites\n:ubs:\nUnbound sprites are sprites with special implementations,\nallowing significantly greater precision and access to extra\ncharacter sets. They have their own setup, their own quirks, and\ntheir own unique restrictions.\n\nA sprite can be set as unbound by setting the SPRn_UNBOUND\ncounter to 1 for a given sprite number n. Once this is done, the\nsprite becomes \"unbound\" from the tile grid and immediately\nstarts using pixels as reference for the collision box (SPRn_CX,\nSPRn_CY, SPRn_CWIDTH, SPRn_CHEIGHT), for sprite location\n(SPRn_X, SPRn_Y), and for values in Sprite_Colliding checks.\nOther attributes, such as reference location and sprite\ndimensions, are left in terms of tiles. Essentially, this means\nthat sprite control and collision checks can now be easily\npixel-based.\n\n(If, for whatever reason, a sprite is given tile-based x/y\nparameters and placed before setting as an unbound sprite, the\nsprite will not adjust its x/y location in pixels to match and\nwill keep the literal x/y values, adjusting them to be in terms\nof pixels instead of tiles once the unbound status is set.)\n\nOn top of pixel-based manipulation, unbound sprites have\nextended display options at their disposal. Unlike other\nsprites, unbound sprites are allowed access to up to 14 \"extra\"\ncharacter sets.\n\nFirst, one must place characters in the extra locations.\nThis can be done in two ways: with the LOAD CHAR SET command or\nthe CHAR EDIT command. With LOAD CHAR SET, extra character sets\ncan be loaded into their proper places by using the @@ prefix to\nload it into proper positions. The first extra character set\nbegins at position 256, the second at 512, the third at 768, and\nso on. (One could also load a character set file that is much\nlarger than normal to fill these positions.) CHAR EDIT can\nmanipulate individual characters of an extra charset by being\ngiven similar numbers.\n\nSecond, the sprite must be set to access these charsets. This is\ndone by setting an offset for that sprite using the SPRn_OFFSET\ncounter. For example, setting SPR0_OFFSET to 256 will change all\ncharacter references done by sprite 0 to the first extra\ncharacter set.\n\nAs this process implies, each unbound sprite is still limited to\none character set's worth of characters - 256 - simultaneously.\nHowever, different unbound sprites can accept different offsets\nand display their sets of characters at the same time, resulting\nin up to 3840 different characters loaded at once (not counting\nthe system set).\n\nIn addition to access to larger numbers of characters, unbound\nsprites can provide transparency effects on a per-sprite basis,\ninstead of merely treating certain characters as transparent.\nEach unbound sprite has its own transparent color, which is set\nwith the SPRn_TCOL counter. All pixels of that color in that\nunbound sprite will not display, and instead show the next\nunderlying layer.\n\nWhen assigned a ccheck value with SPRn_CCHECK, an unbound sprite\nwill apply all ccheck values of colliding sprites in collision\ntests instead of applying the value of the sprite that initiated\nthe check to all colliding sprites. In addition, unbound sprites\ncan use a special ccheck value (3) to make collision checks\npixel-based. A SPRn_CCHECK value of 3 will also ignore\ntransparent colors in collision checks.\n\nFinally, SMZX modes do not affect unbound sprite movement and\nmanipulation. This fact can be exploited to overlap SMZX sprites\nand potentially raise detail to normal MZX mode levels while\nretaining much of SMZX's palette benefits.\n\nSee the Sprite Counters and Robotic sections for other features\nof sprites not noted here.\n\n>#COUNTERS.HLP:spc:Sprite Counters\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#SMZXMODE.HLP\n:095:\n$~9Super MegaZeux Modes\n\nSuper MZX modes (a.k.a. SMZX) allow richer color options at the\nexpense of halved horizontal resolution per character. The\nnumber of colors per character doubles to four, while the number\nof total max colors on-screen at one time squares to 256.\nHowever, each mode works differently and has different\nlimitations and uses.\n\nIn the editor, F11 switches display modes. To set Super MZX\nmodes in a game, use the ~Aset \"smzx_mode\" # ~FRobotic command,\nwhere # is the SMZX mode (0 to revert to normal mode).\n\nSuper MZX mode 1 is the simplest mode and the easiest mode to\nuse. SMZX mode 1 simply blends the foreground and background\ncolors to get the third and fourth character colors. It is\nobviously limited, as this mode only allows editing of the 16\nbase colors, but is useful for anti-aliasing and shading\neffects.\n\nSuper MZX mode 2 is the most difficult mode to use, although it\nhas its benefits. It allows considerable control over the\npalette and allows for some overlay and sprite translucency\neffects. All 256 colors can be individually manipulated, and\neach color also represents a subpalette of four colors that is\nused by a character when an object is assigned that color.\nThe four colors (values in hex) are determined as such:\n\n-Color I is the background color number as the first and second\ndigits. (For example, if background color is 5, the hex number\nof color I is 55.)\n-Color II is the foreground color number as the first digit\nand the background color number as the second. (For example, if\nforeground color is 10 and background color is 5, the hex\nnumber of color II is A5.)\n-Color III is the background color number as the first digit and\nthe foreground color number as the second. (For example, if\nbackground color is 5 and foreground color is 10, the hex\nnumber of color III is 5A.)\n-Color IV is the foreground color number as the first and\nsecond digit. (For example, if foreground color is 10, the hex\nnumber of color IV is AA.)\n\nSuper MZX mode 3 is the best for dynamic usage of the palette,\nand the easiest to edit in general. By default, its four colors\nare determined around a base value in hex (background color\nnumber as first digit, foreground color number as second). Color\nI's number is the base, II's is base+1, III's is base+2, and\nIV's is base+3. The numbers wrap around (so if your base is, for\nexample, FF, color II would be 00).\n\nHowever, one can also directly change each value in this mode,\ndisregarding these limitations. This essentially allows 256\nuser-defined subpalettes of four colors each. These overrides,\ncalled SMZX indices, are loaded and saved out separately from\nthe normal palette. These changes can only apply for renderers\nthat can show unbound sprites (which is the vast majority of\nrenderers).\n\nCharacter editing in Super MZX modes is noticeably different.\nWhile the character set can be edited in MegaZeux's internal\ncharacter editor, certain changes occur. 1-4 selects the\ndesired color; space no longer toggles between colors (it\nalways sets here); right click no longer clears; clear mode is\ngone.\n\nPalette editing for SMZX modes is also accommodated by MZX's\npalette editor. However, the user needs to be in the relevant\nSMZX mode before loading the palette editor.\n\nOn top of direct editing through the editor, SMZX palettes can\nbe changed on the fly with Robotic. The counters for SMZX\npalettes are:\n\n~BSMZX_Rn\n~BSMZX_Bn\n~BSMZX_Gn\n\nManipulation of these counters can change or read the value of\ncolor n's red/blue/green value, respectively. n ranges from 0\nto 255, and counter values range from 0 to 63.\n\nThe SMZX indices can also be directly read or changed with the\n~BSMZX_IDXx,y~F counter. x is the palette color ranging from 0\nto 255; y is one of the given color's indices ranging from 0 to\n3.\n\nLastly, SMZX palettes and character sets can be loaded and\nsaved like any other palette or character set. SMZX indices must\nbe loaded in a special manner in Robotic:\n~ESET \"filename\" \"SMZX_INDICES\"~F.\nThere is a deprecated way to load an SMZX palette in Robotic\n(SET \"filename\" \"smzx_palette\") due to constraints DOS imposed,\nbut it is no longer necessary.\n\n>#COUNTERS.HLP:szc:Super MZX Counters\n>#PALEEDIT.HLP:093:The Palette Editor\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#CHAREDIT.HLP:079:The Character Editor\n>#MAIN.HLP:072:Table of Contents\n#VLAYER.HLP\n:vla:\n$~9The Vlayer and Its Uses\n\nThe vlayer is an extra global graphical layer, but it is never\ndirectly seen in a game. It can only hold character and color\ninfo. The vlayer acts like a workspace allowing copying from/to\noverlay, copying from/to MZMs, copying between places in the\nvlayer, reading/writing individual graphical data to/from the\nvlayer, and reading strings from the vlayer.\n\nAnother important aspect of the vlayer is the ability to\nreference a sprite to the vlayer. This allows indirect display\nof the vlayer through display of sprites. This can be\nexploited to create the semblance of extra overlay layers.\n\nThe vlayer defaults to 32768 characters large (256x128); its\nsize can be as large as roughly 16.7 million characters (2^24).\nIt cannot be removed altogether.\n\nWhile in the editor, press Alt+V to enter the Vlayer Editor. The\nvlayer editor accepts the following commands:\n\n:074:~EAlt+B - Block\n\nPress Alt+B to start block mode, then move the cursor to the\nopposite corner of a rectangular block and press Alt+B again.\nYou can then select an action to perform upon the block. In all\ncopy/move operations, the cursor marks the new UPPER LEFT\ncorner.\n\nCopy block will allow you to duplicate the block by moving the\ncursor to a destination and pressing Enter.\nCopy block (repeated) is the same as Copy block but can allow\ncopying of the same block to multiple places.\nMove block will allow you to move the block to a new location.\nClear block will erase the contents of the block.\nFlip block will flip the block upside down.\nMirror block will flip the block left to right.\nPaint block will change the entire block to the current color.\nCopy to board will copy the block to the given spot of the\nboard. You can choose to place it as either Custom Block, Custom\nFloor, or Text.\nCopy to overlay will copy the block to the given spot of the\noverlay.\nSave as ANSi/TXT will save the block as an ANSi or TXT file.\nSave as MZM will save the block as a layer-type MZM file.\n\nDestinations for Move and Copy can overlap the original block\nsafely. Ctrl+Dir is especially helpful when doing a repeated\ncopy block; it moves the cursor the width or height of the\nblock, ensuring no overlap when pasting.\n\nLike normal Block functions, one can copy between boards by\nselecting the board when the editor prompts the user for the\nblock's destination. Use the B key to select the destination\nboard.\n\n:_C:~EC - Color\n\nPress C to select a new current color from a menu. The thing\nunder the cursor is not affected. One can jump to a color by\ntyping its hex code in the color menu; for example, typing \"0D\"\nwould jump to color 013 (background color 0, foreground color\nD).\n\n:_F:~EF - Fill\n\nPress F to fill in an enclosed area with the current character\nand color. The area must be completely surrounded by characters\nother than the character beneath the cursor. For example, you\ncan fill over a solid square of As with something else. The\ncurrent fill command may not work correctly for very large and\ncomplex areas - in this case, you must move to the unfilled\nareas and press F to continue filling. However, this happens\nvery rarely.\n\n:AltV:~EAlt+V - Exit Vlayer Editor\n\nAlt+V will exit vlayer mode and return to editing the main\nboard.\n\n:103:~EAlt+P - Size\n\nAlt+P will open the vlayer size menu. From here, one can change\nthe dimensions of the vlayer; if the vlayer is shrank in one or\nboth dimensions, the user will be asked for confirmation. Any\ncharacters removed by shrinking vlayer dimensions are lost, and\nany dimension settings that would cause the vlayer to go over\nits maximum size will be limited to the largest size possible by\nreducing the smallest given dimension until the vlayer is within\nsize limits (if both given dimensions are the same size, width\nis reduced). Changing the size of the vlayer will clear the undo\nhistory of the vlayer.\n\n:F1:~EF1 - Help\n\nF1 will bring up context-relevant help. You can press F1 at\nalmost any time.\n\n:F2:~EF2 - Text\n\nF2 will toggle text mode on and off. When text mode is on, Enter\nwill go to the next line, and Backspace will delete going\nbackwards. All printable characters will type in as text.\n\n:Ar:~EArrow - Move\n\nThe arrow keys will move the cursor. The edit window will scroll\nwhen necessary.\n\n:AltAr:~EAlt+Arrow - Move 10\n\nAlt with the arrow keys will move the cursor ten spaces at a\ntime (or to a vlayer edge if it is under 10 spaces away in that\ndirection).\n\n:BkSp:~EBackspace - Delete\n:Del:~EDel - Delete\n\nThese two keys will delete everything under the cursor. The\ncurrent character is not affected.\n\n:End:~EEnd - L/R Corner\n\nEnd will jump the cursor to the lower-right corner of the entire\nvlayer.\n\n:Enter2:~EEnter - Character\n\nEnter will alter the current character on the vlayer. Select it\nfrom a menu and then press Enter to confirm your choice.\n\n:ESC:~EESC - Exit/Cancel Mode\n\nESC will exit vlayer mode. If you are in block, text, or draw\nmode, ESC will instead cancel the current mode and return to\nnormal vlayer editing.\n\n:Home:~EHome - U/L Corner\n\nHome will jump the cursor to the upper-left corner of the\nentire vlayer.\n\n:Ins:~EIns - Grab\n\nIns will select the character and color under the cursor\nas the current. The actual character is not affected.\n\n:Sp:~ESpacebar - Place\n\nSpacebar will copy the current character and color to the\nlocation under the cursor. Other characters will be deleted if\nthey are under the cursor.\n\n:Tab:~ETab - Draw\n\nTab will toggle the current draw mode. When drawing is on, you\nwill place a copy of the current character every time you move\nthe cursor.\n\n:CtrG:~ECtrl+G - Goto Position\n\nCtrl+G will pop up a window, displaying target x,y coordinates.\nSet the coordinates by either typing in or selecting the\ndesired X and Y values, and select OK to go to those coordinates\non the vlayer. Choosing Cancel or pressing Escape cancels.\n\n~EP - Change Buffer Character\n\nP will act much like Enter, but only change the character in the\nbuffer. No character on the vlayer is changed with this action.\n\n~EAlt+Z - Clear (Vlayer)\n\nAlt+Z will clear the vlayer entirely. You will be asked for\nconfirmation. Clearing the vlayer will clear the vlayer undo\nhistory as well.\n\n:CtrZ:~ECtrl+Z - Undo\n:CtrY:~ECtrl+Y - Redo\n\nCtrl+Z will undo an action; Ctrl+Y will redo any previously\nundone action. Action depth is determined by config file\nsettings, with 100 as default.\n\n~ECtrl+Number - Save Editor Position\n~EAlt+Number  - Load Editor Position\n\nThese save and load up to 10 cursor positions in the vlayer (0\nthrough 9). These positions save the current coordinates in\nthe vlayer. These positions are saved for that world in its\n..editor.cnf file.\n\n---\n\nOn top of being modifiable in the editor, the vlayer can be\nchanged in a running MZX world with Robotic. To manipulate the\nvlayer in Robotic, \"#n\" is used instead of pure numbers in copy\ncommands. For example, if copying a 2x2 block from (6,6) of the\nboard to (0,0) of the vlayer is wanted, the command would be:\n\n~ECOPY BLOCK 6 6 2 2 \"#0\" \"#0\"\n\nHere are the vlayer copy functions:\n\nBoard to vlayer: ~ACOPY BLOCK x1 y1 w h \"#x2\" \"#y2\"\nOverlay to vlayer: ~ACOPY OVERLAY BLOCK x1 y1 w h \"#x2\" \"#y2\"\nVlayer to board: ~ACOPY BLOCK \"#x1\" \"#y1\" w h x2 y2\nVlayer to overlay: ~ACOPY OVERLAY BLOCK \"#x1\" \"#y1\" w h x2 y2\n\nValid Robotic copies from vlayer to board are always\nCustomBlocks.\n\nIn the commands below, ~eCOPY BLOCK~f and ~eCOPY OVERLAY BLOCK~f\nare interchangeable.\n\nVlayer to MZM:\n~ACOPY (OVERLAY) BLOCK \"#x1\" \"#y1\" w h \"@@name.mzm\" m\nVlayer to string:\n~ACOPY (OVERLAY) BLOCK \"#x1\" \"#y1\" w h \"$stringN\" c\nVlayer to itself:\n~ACOPY (OVERLAY) BLOCK \"#x1\" \"#y1\" w h \"#x2\" \"#y2\"\n\n\"m\" and \"c\" can be any value but must exist.\n\nThere are several ways to give the vlayer working coordinates:\n\nPure numbers - ~E\"#xxx\"~F\nCounter value - ~E\"#&counter&\"~F\nExpressions - ~E\"#('counter' + x)\"~F\n\nMZMs can be placed on the vlayer by using p02. Example:\n\n~EPUT \"@@visuals.mzm\" image_file p02 x y\n\nReading and writing single characters or colors from the vlayer\nrequires the vch and vco counters.\n\nFor reading and writing characters use ~E\"vchX,Y\"~F.\nFor reading and writing colors use ~E\"vcoX,Y\"~F.\n\nFor example:\n\n~ESET \"vch10,10\" '+'~f sets the vlayer char at (10,10) to a plus\nsign.\n~ESET \"vco10,10\" 15~f sets the vlayer color at (10,10) to white on\nblack.\n~ESET \"temp\" \"vch10,10\"~f sets temp to whatever the character at\n(10,10) of the vlayer is.\n\nVlayer size can be changed by setting the counter \"vlayer_size\"\nto the desired length of the vlayer. Doing this only sets the\nmaximum size; the actual vlayer will not be this large unless\nthe vlayer's dimensions are changed. All attempts to set the\nvlayer's size above the maximum will be ignored.\n\nTo change the vlayer's dimensions in Robotic, use the counters\n\"vlayer_width\" and \"vlayer_height\". When one dimension is\ngiven, the other becomes as large as possible to fill the given\nvlayer. For example, if one has a vlayer 50000 characters large\nand sets vlayer_height to 499, the vlayer's width will become\n100 characters wide (for a total vlayer size of 49900\ncharacters).\n\nAll bad values of vlayer_size, vlayer_height and vlayer_width\n(0 and below) will be changed to 1.\n\nRemember that the vlayer is global. If many vlayers are wanted,\nliberal usage of MZM saving and loading can help get around this\nproblem.\n\n>#COUNTERS.HLP:vlc:Vlayer Counters\n>#STRINGS.HLP:1st:Strings, Special Formatting, and Their Place in Robotic\n>#MZM.HLP:mzm:Using MZMs\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#MZM.HLP\n:mzm:\n$~9Using MZMs\n\nMZM files are rough equivalents of the ANSI files used in older\nMZX versions. MZMs, however, are much more powerful and can\nstore much more information. MZMs can be up to 65535 x 65535\ntiles in size. MZMs are useful for easy access of reused\ngraphics and reused, multi-Robot entities.\n\nMZMs come in two modes: \"board\" and \"layer\".\n  * Board mode saves color, param, ID, and under information.\n    However, it cannot save the player, scrolls or sensors.\n    Board mode can save most built-ins. As for Robots, MZMs\n    saved in the editor will save fresh Robots, while MZMs saved\n    in-game keep the Robots' current states.\n  * Layer mode saves only color and char information.\n\nAny information not saveable will be replaced by a customblock\nfacsimile.\n\nWhile in the editor, MZMs can be placed by using the import\nfunction (Alt+I) and choosing MZM. Layer MZMs can be placed on\nthe board layer as CustomBlock, CustomFloor, or Text.\n\nWhen loading a board MZM file to the overlay or vlayer, the\ncharacter used is the parameter stored. Outside of customblocks\nand related, different characters could appear due to different\nparam settings (especially built-ins and Robots).\n\nOne can save MZMs in the editor; use the block action function\n(Alt+B). MZMs saved in the editor from the board are saved as\nboard; from the overlay and vlayer, MZMs are always saved as\nlayer.\n\nRobotic methods of saving MZMs are:\n\n~ACOPY BLOCK x y w h \"@@filename\" p\n~ACOPY OVERLAY BLOCK x y w h \"@@filename\" p\n~ACOPY (OVERLAY) BLOCK \"#x\" \"#y\" w h \"@@filename\" m\n\nX/Y are the coordinates of the block's upper-left corner; w/h\nare the width and height of the block; filename is the name of\nthe file (the @@ is required); finally, p determines the MZM\ntype. 1 saves as layer; 0 saves as board. 0 is integral for\ncopying non-graphical data (especially Robots); 1 is proper for\ncopying graphical data, as the block's appearance will stay as\nexactly as it was when saved. The last case is for saving from\nthe vlayer to MZM. The x,y coordinates MUST be in \"#x\" \"#y\"\nformat, and the m can be any value but MUST exist. Any valid MZX\nstring can take the place of a filename, but the @@ prefix is\nstill required.\n\nUnlike with board MZMs containing Robots saved in the editor,\nloading board MZMs containing runtime Robots in the editor is\nunsupported. Any such Robot will be replaced with a customblock\nfascimilie. If you want any such MZMs to be fully loaded, they\nmust also be loaded at runtime.\n\nThe Robotic method of loading MZMs is:\n\n~APUT \"@@filename\" image_file pNN x y\n\nfilename is the name of the MZM file (after the required @@),\nx and y are the upper-left corner's coordinates, and NN is 00\nfor board, 01 for overlay, 02 for vlayer. Again, any valid\nstring can take the place of a filename if preceded by the\nrequired @@ prefix.\n\nFinally, MZMs will neither be saved nor loaded if at the edge\nof a board.\n\n>#USINGTHE.HLP:_g:Alt+B OR Alt+Enter - Block action\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#TRIG.HLP\n:tri:\n$~9Trigonometric Functions\n\nTrigonometry can simplify some complex engines, and MegaZeux\ncan access several trigonometric functions with fair precision.\nHowever, accessing trigonometric functions in MegaZeux requires\nsome setup.\n\nBecause MegaZeux works solely with integers, you need to set\nthe counter \"multiplier\" to determine how the non-integer\nworking value is converted. For example, if your function\nhas a value of .86602 and \"multiplier\" is set to 10000, the\nresulting value would be 8660. Results are always truncated\n(that is, have everything to the right of the decimal removed).\nThe default value for the \"multiplier\" counter is 10000.\n\nThe counter \"c_divisions\" determines the precision of the trig\nfunctions by determining how many times the circle is divided.\nSensible values for this include 360 (degree precision), 21600\n(arc-minute precision) and 1296000 (arc-second precision). The\ndefault value for the \"c_divisions\" counter is 360.\n\nThe \"divider\" counter works solely on inverse trigonometric\nfunctions. Its effect is to divide the number passed to an\ninverse function before it is calculated. For example, if you\nhave \"divider\" set to 100 and try to calculate \"acos50\", MZX\nwill calculate the arccosine of .50 (50 divided by 100),\noutputting 60 degrees. The default value for the \"divider\"\ncounter is 10000.\n\nThese are the trig functions MegaZeux can directly utilize.\n\n~ASIN - sine\n~ACOS - cosine\n~ATAN - tangent\n~AASIN - arcsine (inverse sine)\n~AACOS - arccosine (inverse cosine)\n~AATAN - arctangent (inverse tangent, takes signs into account)\n~AARCTAN - atan2 (special form of arctangent, takes two values\n~Ain the form of ARCTANdy,dx (\"dy\" is the first input, \"dx\" the\n~Asecond))\n\nNOTE: The inverse functions output degrees, not radians.\n\nTo use the counters you put the unit number after the counter\nname. Here's an example. To display the cosine of 30 degrees\nwith degree-based precision, the needed line would be:\n\n~E* \"&cos30&\"\n\nTrig functions can also take expressions and counters. However,\ncounters can only be inserted through expressions; a construct\nlike \"&sinlocal&\" will not work, but \"&sin('local')&\" will.\n\n>#COUNTERS.HLP:mth:Mathematical Counters\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#PROCESS.HLP\n:prc:\n$~9Cycles and Board Scans - How MZX Processes Robots\n\nKnowing MZX's commands is important for coding, obviously, but\nknowing how and when they get executed can be just as\nparamount.\n\nAfter executing code in the global Robot, MegaZeux executes\nRobotic code by scanning the board from the top-leftmost Robot\nfirst, then proceeding from left-to-right, jumping down to the\nbeginning of the next line when at the end of its current line,\nand ending at the Robot in the bottom-right corner. This action\nis called a BOARD SCAN. MegaZeux then does a clean-up scan in\nreverse to catch certain actions. This complete act of scanning\na board and executing any Robotic commands is called a CYCLE,\nand when a Robot completes all of its commands for that cycle,\nit's called ENDING A CYCLE for that Robot. After a cycle is\nexecuted, the display is updated, so MegaZeux's internal frame\nrate is essentially the same rate as the number of cycles\nexecuted per second.\n\nBy default, MegaZeux will run up to 40 commands per Robot in a\ncycle. Setting the COMMANDS counter can set the number of\ncommands processed per Robot per cycle to be lower or\n(significantly) higher. Some commands, however, have the ability\nto end processing prematurely; these are called cycle-ending\ncommands. When the COMMANDS value is very high, MegaZeux can end\nup consuming large amounts of processing power in a single\ncycle. Cycle-ending commands can limit the number of commands\nprocessed in a cycle to only what is necessary. Otherwise, very\nhigh COMMANDS values can potentially slow down MegaZeux or even\ncause MegaZeux to trigger safeguards that prevent it from\nfreezing.\n\nThe two commands that are best suited for ending the cycle are\nWAIT 1 and CYCLE 1, because of their very low amounts of side\neffects. While WAIT 1 is preferred in most cases, mostly due to\nbeing far more intuitive, there is a subtle difference between\nprocessing the two due to its quirk with reverse scan detailed\nlater in this section.\n\nAs a simple rule of thumb, commands that move anything on the\nboard generally end a cycle, especially moving the player,\nthough this is not always the case.\n\nThe amount of cycles MegaZeux processes per second is set by the\nMZX_SPEED value. Speed 1 is completely processor-bound and will\nexecute as many cycles per second as possible. Other speeds\nprocess cycles according to this formula:\n\n~E62.5 / (MZX_SPEED - 1) = cycles per second\n\nSimplified, this means that MegaZeux can be set to consistent\nrates ranging from 62 & 1/2 cycles per second (speed 2) all the\nway down to 4 & 1/6 cycles per second (speed 16).\n\nThe CYCLE command dictates how often a Robot is active. By\ndefault, Robots will execute commands on every cycle, but the\nCYCLE command can cause a Robot to spend a certain number of\ncycles idling before executing commands. Robots can go from\nexecuting 1 out of every 1 cycles (i.e. every cycle) to 1 out\nof every 255.\n\nSpecial care should be given to the scan that happens in reverse\nto clean up certain actions. This scan ensures that if a Robot\nsends a Robot that has already executed its commands to a label,\nthen the target Robot can actually be sent to the proper label.\nCertain commands in the target Robot - specifically, the\ncycle-ending commands that can act across multiple cycles - can\ncause that Robot to execute the new label commands in the very\nsame cycle. The global Robot is exempt from this process.\n\nCommands that can prompt this behavior are:\n\n>#COMMANDR.HLP:__5:/ \"string\"\n>#COMMANDR.HLP:_e1:END\n>#COMMANDR.HLP:_g4:GO [dir] #\n>#COMMANDR.HLP:_t7:TRY [dir] \"label\"\n>#COMMANDR.HLP:_w1:WAIT #\n\nAnything besides solely one of these commands ran in the target\nRobot's cycle (even blank lines!) will prevent the target Robot\nfrom executing in the same cycle.\nThere is an exception for Robots using CYCLE # values over 1:\nbeing in such a Robot's idle cycles will trigger a same-cycle\nexecution.\n\nFor more detailed information about what commands end cycles and\nwhen, read cycles_and_commands.txt in the additional\ndocumentation.\n\n>#MAIN.HLP:072:Table of Contents\n#PARTIAL.HLP\n:par:\n$~9Partial Character Sets\n\nPartial character sets are smaller than full sets, as the name\ndefinitely implies, but they only overwrite the characters they\nare told to when loaded. This leaves the rest of the characters\nintact. Heavy animations and changes that keep part of the\ncharacter set intact (such as the alphanumeric characters) are\ntwo issues best tackled with partial character sets.\n\nOne can save a partial character set in the editor by selecting\na starting character value (aka \"offset\") and a length value.\nThese fields show up when you export a character set, under the\nfilename input.\n\nTo load partial character sets into your current set, you have\ntwo options. You can firstly load it as you would a normal\ncharacter set; this replaces all characters from the start of\nthe target character set until the end of the partial set.\nSecondly, you can load the partial set into a non-0 position of\nthe target character set. In the editor, this can be done by\nsetting the offset field in the import character dialog, under\nthe filename input. The @@xxxx prefix allows this in Robotic;\nxxxx is the number (in decimal) of the first character to\nreplace in the target character set.\n\n>#ROBOTICR.HLP:087:Robotic Reference Manual\n>#MAIN.HLP:072:Table of Contents\n#UPDATER.HLP\n:099:\n$~9The MegaZeux Updater\n\nModern MegaZeux versions on supported platforms, such as\nWindows, have the ability to update MegaZeux from MegaZeux\nitself. The updater can be set to run by pressing either F7 or U\non any title screen. It can also be configured to check for\nupdates every launch in config.txt settings, or disabled\naltogether.\n\nMegaZeux checks the sites listed under the config.txt settings,\nin order. By default, the updater checks the Stable branch of\nMegaZeux, but can be set in config.txt to other branches (such\nas Unstable or Debytecode).\n\nIf a host reports back that your version of MegaZeux is current,\nthe next host can be checked to see if they differ.\n\nIf a new version of MegaZeux is detected, MegaZeux offers the\nuser these choices:\n-Upgrading to the new version using the \"Upgrade\" button.\n-Updating the current version to whatever minor \"refreshed\"\n version may exist (where the version number remains the same,\n but a small issue or two not caught before release is fixed)\n using the \"Update Old\" button.\n-Cancelling out of the update process with the \"Cancel\" button.\n No changes are made.\n\nOnce the option is chosen, MegaZeux lists what files will be\nadded, changed, or deleted by the pending update. At this point,\nthe user can still cancel out of updating with the Escape key.\nPressing Enter at this point begins the updating process.\n\nThe updater will then attempt to download the necessary files\nfor the update. The download indicator lists the current file\nbeing downloaded, its size in bytes, and current file number\nout of total files to be downloaded. If all files are properly\ndownloaded, MegaZeux will apply the update and notify the user\nof a restart. Select \"OK\" and MegaZeux will restart, fully\nupdated.\n\n>#MAIN.HLP:072:Table of Contents\n#NEWINVER.HLP\n:1st:\n$~9NEW in MegaZeux!\n\nJune 9th, 2025 - MZX 2.93d\n\nThis release includes several features and bugfixes to help with\nportability. Screen keyboard support from the NDS/3DS ports has\nbeen generalized and enabled for SDL ports; Android and Vita can\nnow open their respective screen keyboard interfaces by pressing\nright shoulder on the gamepad (configurable). The Android port\nno longer overwrites config.txt if it has changed. Other notes:\nbetter AltGr tolerance, faster software renderer performance,\nrendering fixes for softscale, glsl/glslscale, and opengl1/2.\n\nCompatibility changes include Spitting Tiger logic bugfixes and\ncorrected version locking for the KEY? and KEYENTER labels.\nSeveral out-of-bounds accesses in ID char resolution and in the\nMissile logic have been fixed, which may introduce (unfixable)\ndifferences in behavior for out-of-range parameters. There are\nalso various module playback improvements (libxmp 4.6.3).\n\nSDL3 is now selected by default for Windows, Emscripten,\nand Vita (thanks to Aeon17 for Vita testing). SDL3 adoption for\nmacOS has been delayed to 2.94 (requires increasing the minimum\nOS version to 10.14).\n\nUSERS\n\n+ MegaZeux on Android no longer overwrites config.txt if it has\n  more recent changes than the copy in assets.zip.\n+ Added support for system screen keyboards for SDL. This allows\n  opening the system screen keyboards with the Android and Vita\n  ports, as well as some Linux configurations e.g. Steam Deck.\n  The screen keyboard can be toggled with rshoulder (see below)\n  or, in Android, by performing a 3-point touch on the screen.\n+ Added config option \"joy#.show_screen_keyboard\" to configure\n  which button (if any) is used to open the screen keyboard for\n  platforms that support it (set to act_rshoulder by default,\n  except for Linux, where it's disabled by default).\n+ Added config options \"key_left_alt_is_altgr\" and\n  \"key_right_alt_is_altgr\". When set to 1, these prevent their\n  respective Alt keys from activating built-in UI and editor\n  keyboard shortcuts e.g. Ctrl+Alt+Enter. They do not alter the\n  keycodes emitted by these keys. This may be useful for Windows\n  international keyboard layouts and for macOS. These options\n  can be disregarded for Wayland/X11 (AltGr has its own keysym)\n  or other ports that don't have AltGr.\n+ More strict modifier checking for keyboard shortcuts that use\n  alphanumeric keys in places with text entry: dialog boxes,\n  editor, scroll editor, Robot editor, Robot debugger config,\n  and variable debugger. This should make it easier to use\n  Windows Ctrl+Alt style AltGr in these interfaces.\n+ Wayland/X11 AltGr now correctly emits internal keycode 313 and\n  PC XT keycode 56.\n+ The KEY? label is no longer sent for pre-2.62 worlds when\n  the char pressed is outside of the A-Z 1-9 range, and is no\n  longer sent for 1.x worlds when the char is outside of the A-Z\n  range. (Previously checked version for the KEY counter only.)\n+ The KEYENTER label is no longer sent for pre-2.62 worlds.\n+ Fixed bug where, after Alt+R or failure to reload __TEST.MZX,\n  the editor would not mark the new blank world as active. This\n  would cause the screen to not be drawn during Alt+T testing.\n+ Spitting Tigers from pre-2.80 worlds no longer move slower\n  than they're supposed to with low intelligence, and no longer\n  shoot themselves on these buggy idle cycles when \"Enemies'\n  bullets hurt other enemies\" is enabled.\n+ Spitting Tigers that shoot fire now play the fire SFX.\n+ Fixed out-of-bounds array reads when a Missile with an invalid\n  parameter attempted to turn after colliding with itself (no\n  compatibility checks for old behavior).\n+ Fixed out-of-bounds array reads when reading the char of or\n  displaying (no compat checks): Ice, Lava, CWRotate, CCWRotate,\n  Pusher, Missile, Spike, Bullet, Fire, Life, Ricochet Panel.\n+ Fixed out-of-bounds array reads when reading the color of or\n  displaying (no compat checks): Energizer, Fire, Life.\n+ ANSi export now replaces char 26 with a dash to avoid import\n  bugs and conflicts with other software.\n+ Added ANSi export support for Doorway mode, a non-standard\n  ANSI extension that allows displaying control characters that\n  would otherwise get stripped out. MegaZeux now supports\n  loading ANSi files that use Doorway mode, as well.\n+ Software layer renderer performance enhancements for repeat\n  colors, clipping, and unaligned renderering.\n+ Unaligned software layer rendering using 64-bit writes is\n  now enabled for x86-64, POWER8, Emscripten, and probably most\n  AArch64 builds. This optimizes out up to 48 renderers for\n  these architectures, and improves 8-bit, 16-bit, and 32-bit\n  rendering performance.\n+ Unaligned software layer rendering using 32-bit writes is\n  now enabled for x86, PowerPC, and M68k. This optimizes out up\n  to 24 renderers for these architectures, and improves 8-bit\n  and 16-bit rendering performance.\n+ Fixed slow softscale rendering for some drivers. Texture\n  streaming is now used for direct3d/consoles/software only.\n+ Fixed OpenGL rendering issues in Wayland caused by OpenGL's\n  transparent default clear color.\n+ Fixed opengl1 renderer colors missing alpha component.\n+ Fixed glsl, glslscale, opengl2 renderers sometimes showing a\n  thin white line on the edges of the screen.\n+ Fixed date and time counters on NDS (missing IRQ_NETWORK).\n+ Fixed VFS cache failing to init caused by getcwd returning\n  a root with no trailing slash.\n+ Unsupported mouse button presses are now ignored.\n+ Fixed the window icon for Flatpak builds.\n+ Fixed the directory mentioned in the PS Vita README.md\n  (claimed \"ux0:/data/MegaZeux\", actually \"ux0:/data/megazeux\").\n\nDEVELOPERS\n\n+ Added renderer set_window_caption, set_window_icon functions;\n  moved SDL-specific icon functionality out of graphics.c.\n+ Failure to query X11 no longer disables the icon in Linux/BSD.\n+ manifest.sh no longer requires bash. All scripts and Makefiles\n  should now be fully compatible with POSIX sh (with only a few\n  GNU extended options remaining where required).\n+ Fix compilation with glibc versions prior to 2.17 caused by\n  clock_gettime (disabling for now until -lrt can be tested).\n+ Fix compilation with libpng versions missing png_const_bytep\n  and png_set_add_alpha.\n+ Remove -Wno-missing-field-initializers to fix GCC 3.4 support.\n+ Remove redundant order-only prerequisites in src/Makefile.in\n  to fix building with older versions of GNU Make.\n+ Define _LARGEFILE_SOURCE in addition to _FILE_OFFSET_BITS=64\n  in the unix Makefile to fix builds with old glibcs that\n  predate _FILE_OFFSET_BITS.\n+ Numerous warnings fixes/workarounds affecting macOS, NetBSD,\n  OpenBSD, 3DS, GCC 4.x, SDL 1.2, and old versions of libpng.\n+ Fixed OS detection, unit tests, and testworlds for Haiku.\n+ Replaced return values of all VFS functions to fix issues with\n  using errno values here on Haiku.\n+ Add Emscripten Makefile support for SDL3.\n+ Add PS Vita Makefile support for SDL3 and SDL 1.2.\n+ make install now installs an AppStream .metainfo.xml file.\n+ Added --metainfodir [dir] option to config.sh. This setting\n  defaults to [sharedir]/metainfo for relevant platforms.\n+ --licensedir now defaults to [sharedir]/licenses instead of\n  [prefix]/share/doc.\n+ Fixed ansi.c compilation failure with --disable-datestamp.\n+ More accurate FPS counter for --enable-fps.\n+ Updated libxmp to 4.6.3+sample rate patch.\n+ Updated dependency builder scripts: SDL3 3.2.16, SDL2 2.32.8,\n  libpng 1.6.48.\n\n>#MAIN.HLP:072:Table of Contents\n\nFebruary 28th, 2025 - MZX 2.93c\n\nYes, the text input bug and show thing keys are fixed now. :)\n\nInitial support for SDL3 has been added, and will be enabled for\nthe Fedora and AUR builds for this release. Other platforms will\ngradually transition to SDL3 over the next few releases.\n\nAs part of the preparation for SDL3, this release includes many\nlong-belated changes to the way MegaZeux resizes windows and\ncalculates the scaled display area within the window (including\nmouse coordinate conversion). This overhaul has fixed several\nbugs, but please be on the lookout for regressions.\n\nThe Xcode port has been updated to build with the latest version\nof SDL and other dependencies, and now requires OS X El Capitan\n(x86_64) or macOS Big Sur (arm64) to run. This port no longer\nsupports x86 (32-bit). The Darwin multiarchitecture port has\nalso been updated to fix arm64 and arm64e support (only arm64 is\nincluded in builds).\n\nAlso included are Android fixes for Android 11+ (thanks asie)\nand also a fix for a bug preventing MZX from starting on Jelly\nBean and KitKat.\n\nUSERS\n\n+ The DOS Sound Blaster driver now supports Sound Blaster with\n  DSP 2.0, Sound Blaster 2, and Sound Blaster Pro/16 mono mode.\n  Original Sound Blaster with the old DSP is still unsupported.\n+ The DOS port and SDL ports now support mono audio. Mono can be\n  selected with the new config option \"audio_output_channels\".\n+ Setting the config option \"system_mouse\" to \"1\" or \"on\" will\n  no longer disable the software cursor. The old behavior can be\n  restored by setting it to \"only\". The new behavior of \"1\" is\n  more useful, especially in Wayland (due to forced mouse grab\n  when the system cursor is hidden).\n+ macOS now enables fullscreen_windowed by default to work\n  around macOS bugs involving real fullscreen and maximizing.\n+ Fixed window resize bugs in Windows, macOS, Linux/BSD (both\n  X11 and Wayland), and probably other operating systems caused\n  by MZX recreating the window after every window resize event.\n+ Fixed junk display in the letterbox area after resizing using\n  the software, gp2x, or SDL 1.2 overlay renderers.\n+ The software and gp2x renderers now enable blitting when the\n  created window is smaller than expected instead of crashing.\n+ Fixed sai.frag not being installed by \"make install\" or\n  distributed with Unix-style packages for Linux/BSD/Darwin.\n+ Fixed the board editor show thing hotkeys (broken by 2.93b).\n+ Fixed nested riff behavior in the RAD replayer. The replayer\n  would fail to replace the outer riff with the inner riff in\n  some cases.\n+ Fixed a crash that would occur in builds using extra memory\n  hacks (DOS) when copying blocks between boards in the editor.\n+ Fixed text input bugs caused by a 2.93b change accidentally\n  reenabling old deadcode for exiting intake().\n+ Fixed crash when saving editor config files/\"Set as Default\"\n  after the file fails to open for write. If the \".editor.cnf\"\n  file fails to save or load, MZX will attempt to save or load a\n  \".cne\" file instead. This allows this feature to work in DOS\n  builds when there is no long filename (LFN) support.\n+ Fixed filesystem permissions on Android 11.0 and up. (asie)\n+ Fixed linkage bug preventing MegaZeux from starting in Android\n  Jelly Bean and KitKat due to a missing logging symbol.\n+ Added support for pre-2.70 \"put thing BENEATH\" being ignored.\n+ Added support for pre-2.80 \"put thing\" treating p?? as 0.\n+ Robo-P p0 now converts to either p00 or p?? when appropriate.\n+ Robo-P PUT commands now ignore the high thing bit instead of\n  combining it into the color.\n+ Fixed disassembly of out-of-bounds color and param values to\n  match their most common interpretation during gameplay.\n+ Fixed assembly of immediates in second char of CHANGE OVERLAY.\n+ Out-of-bounds char values are now disassembled to immediates\n  for CHAR EDIT, COPY CHAR, SCROLL CHAR, and FLIP CHAR. This\n  makes commands such as \"CHAR EDIT 256 [...]\" more usable since\n  they will persist in the robot editor.\n+ Temporarily revert the broken Vita SHAREDIR/startup dir split.\n  MegaZeux now expects assets to exist in ux0:/data/megazeux\n  like the README claims.\n\nDEVELOPERS\n\n+ Added initial support for SDL3. No architectures select SDL3\n  by default, but it is now possible to build and test MegaZeux\n  for platforms that already support it.\n+ Split the renderer function \"set_video_mode\" into two renderer\n  functions \"create_window\" and \"resize_window\" to more closely\n  reflect the reality of SDL2/3. The former is used during\n  renderer init and is mandatory; the latter is used to resize\n  and update other window settings like mouse grab/visibility\n  e.g. when switching to/from fullscreen, and is optional. If\n  it is not provided, switching to/from fullscreen will do\n  nothing. \"resize_window\" is also invoked in SDL 1.2 when a\n  window resize event is received. Some renderers (software,\n  gp2x) still use the same implementation for both functions.\n+ Renamed the renderer function \"resize_screen\" to\n  \"resize_callback\" to more accurately reflect its purpose. It\n  is called in response to SDL2/3 window resize events and after\n  \"resize_window\" (by the video functions that call it, see\n  above). This function is now responsible for post-resize\n  maintenance such as glViewport and selecting the filter mode,\n  and is no longer required to call update_screen.\n+ Replaced renderer functions \"get_screen_coords\" and\n  \"set_screen_coords\" with a single function \"set_viewport\" to\n  calculate the current window's display area. This area is used\n  in places where \"fix_viewport_ratio\" previously was and also\n  to convert mouse coordinates. Most renderers should use one of\n  the two prefab implementations of this function.\n+ SDL text event enablement now happens after the window is\n  created. This shouldn't really affect anything.\n+ Added detection for LoongArch64. 64-bit architectures that\n  platform_endian.h fails to identify as such should no longer\n  abort in the software layer renderer.\n+ Updated the Xcode project. Supported architectures are now\n  x86_64 and arm64. The x86_64 architecture requires OS X El\n  Capitan (minimum version supported by SDL) and arm64 requires\n  macOS Big Sur. The Xcode project format is still 8.x, but\n  Xcode 12.2 or newer is required to compile release builds.\n+ Tweaked the Darwin .app buildsystem to properly support arm64\n  and arm64e. The documentation has also been updated to note\n  linker issues in versions prior to 10.10 when combining\n  x86_64 and x86_64h into the same binary, and issues involving\n  arm64e builds being terminated by the kernel.\n+ The dependency builder scripts now support building Xcode\n  frameworks for both x86_64 and arm64. Updated SDL2 to 2.32.0,\n  libpng to 1.6.47, libogg to 1.3.5, and libvorbis to 1.3.7.\n  Added building SDL3 3.2.4 for all relevant platforms.\n+ Enabled 16k pages on Android to hopefully future-proof\n  MegaZeux against Android 15.0. (asie)\n+ Updated libxmp to 4.6.2+sample rate patch.\n\n>#MAIN.HLP:072:Table of Contents\n\nSeptember 10th, 2024 - MZX 2.93b\n\nThis is mostly a big bugfix release, but there are a few new\nfeatures of note. First and probably of most interest, there is\na new feature to export a full screenshot of an entire board or\nthe entire vlayer from the editor. Renderers based on the\nsoftware layer renderer should see some performance improvement.\nThe Darwin multiarchitecture .app port has also been massively\noverhauled, and now targets up to 5 architectures (ARM still\npending). libxmp now plays stereo samples correctly.\n\nDOS port users should be happy to hear that Sound Blaster Pro\nsupport has been restored, and support for even more sound cards\nis coming. Additionally, some Ogg Vorbis-related crashes have\nbeen fixed, and the SVGA renderer should work better than prior.\nScreenshots have been enabled for this port, and the TIME/DATE\ncounters should now work properly.\n\nFinally, as usual, there are a large number of bugfixes, which\nincludes several crash fixes, scroll fixes, and FREAD fixes.\nNo compatibility checks have been added for the string splice\nFREAD/FREADn bugfixes, as the old behavior was unusably buggy.\n\nUSERS\n\n+ Added board and vlayer image export to the editor (Alt+X).\n  This feature will render the entire board (with overlay, if\n  applicable) or vlayer as it appears in the editor. This may\n  take a long time with large boards/vlayers.\n+ Builds without libpng (Android, DOS) now use a simple fallback\n  PNG exporter for screenshots instead of falling back to BMP.\n+ The DOS port now supports Sound Blaster Pro in stereo mode.\n  The driver should also support all other Sound Blasters with\n  DSP >=2.00 and the mono/8-bit modes of the Sound Blaster 16,\n  but these are disabled for now (no software mixer support for\n  mono output). IRQs higher than 7 are also supported now.\n+ The DOS port now uses stb_vorbis for Ogg Vorbis playback,\n  which should fix most audio-related crash bugs. Related crash\n  bugs caused by stream management have also been fixed.\n+ All \"gamecontroller[...]\" config.txt options have been renamed\n  to \"gamepad[...]\". The old names are still supported.\n+ Windows UNC paths are now supported in the file manager.\n+ Paths with colons (:) are now explicitly supported in Linux\n  and macOS and always rejected in path strings used by worlds.\n+ Fixed blank screen bugs that occur with the glslscale renderer\n  in conjunction with Intel HD Graphics 2500 graphics drivers\n  and possibly some other old drivers.\n+ The softscale renderer now respects disable_screensaver.\n+ HTML5: fixed the poor performance of FREADn and other features\n  that rely on calculating the length of an open file.\n+ Clear world (Alt+R) now resets the world version.\n+ Fixed a crash bug and a false positive match bug in the\n  command IF ALIGNEDROBOT. The false positive bug has been\n  version locked to 2.80 through 2.92X (just in case).\n+ Software layer rendering performance for transparent sprites\n  has been improved, significantly in some cases.\n+ Fixed a software layer renderer bug where a buffer with a\n  pixel alignment that varies between lines would cause the\n  frame to render incorrectly.\n+ Fixed a software layer renderer bug in 32-bit builds where\n  SMZX sprites would sometimes render incorrectly when clipping\n  off the left side of the screen.\n+ Setting a string offset, limit, or splice to FREADn now\n  respects the provided limit. If n is larger than the limit,\n  n total bytes will still be read (up to the maximum string\n  size); any bytes read past the limit are discarded.\n+ Setting a string offset, limit, or splice to FREAD now\n  respects the provided limit and no longer pads the string with\n  spaces in some cases. Setting any string to FREAD now reads\n  the file until a terminator or the end of the file is found\n  instead of stopping when the maximum string size is reached;\n  any bytes read past the limit or maximum size are discarded.\n+ Setting a string splice to FREAD or FREADn with invalid offset\n  or size values no longer causes a crash.\n+ Setting a string splice to FREAD with a valid offset that\n  would write past the current length no longer causes a crash.\n+ Fixed a rare networking bug where fetching a compressed file\n  over HTTP could fail for particular compressed file sizes.\n+ The About MegaZeux dialog now shows both the compiled and\n  linked versions of libpng if they are different.\n+ Fixed flash thing layer draw order bug that would cause it to\n  display over editor elements using the game UI layer.\n+ Licenses are now properly loaded in the About MegaZeux dialog\n  for Darwin builds.\n+ Fixed potential heap overflows in mfread and mfwrite when the\n  current position is past the end. This should never actually\n  happen, but it's safer and shuts up _FORTIFY_SOURCE and ASan\n  compilation warnings.\n+ Fixed platform-dependent out-of-memory errors when loading\n  robots from <=2.84 save files with corrupt stack sizes.\n+ Fixed hangs in the robot editor caused by trying to use\n  Replace All to replace quotes or extra words with nothing.\n+ Fixed the handling of lines over 64 characters long during\n  scroll editing. Scroll editing now previews colors and allows\n  lines up to 255 bytes in length. Like robot message boxes, any\n  characters past the first 64 will clip.\n+ Clicking a line in the scroll display and editor now jumps to\n  that line (same as the help file).\n+ You can now toggle between editing scrolls with the protected\n  palette/charset and the game palette/charset with Alt+V.\n+ Games for versions 2.80X through 2.90X now display scrolls\n  using the protected palette instead of the game palette (fixes\n  a scroll in Welkin).\n+ Fixed possible robot stack and scroll message memory leaks.\n+ Fixed a 2.93 regression where color strings with escapes like\n  \"~~x\", where x is not a hex digit, would also print the ~~ or @@.\n+ Fixed clipping of the board message when it contains color\n  codes and when the message column is set to something besides\n  centered or 0. The next row will also use the correct color\n  code from the previous row (as if there had been no clipping)\n  instead of whatever the last color code displayed was. The old\n  behavior is version locked to <=2.93, so this fix won't take\n  effect until 2.94.\n+ Added compatibility for MegaZeux 1.x's reverse send behavior\n  which allows robots to execute twice per cycle in some cases.\n+ Fixed COPY when used with REL COUNTERS for 1.x worlds.\n+ Added support for MegaZeux 1.x entrance precedence (color\n  match, then thing match only). This fixes an entrance bug in\n  MegaZeux Tutorial.\n+ Added support for MegaZeux 1.x entrances linking to other\n  entrances on the same board. This fixes the whirlpools in the\n  Aqua Cavern board in Caverns of Zeux.\n+ Added support for MegaZeux 1.x entrances not triggering when\n  the player is pushed onto them.\n+ Added support for MegaZeux 1.x TELEPORT interrupting the\n  current board scan and partial support for the same behavior\n  in RESTORE PLAYER POSITION/EXCHANGE PLAYER POSITION.\n+ The DJGPP port now locks all of its memory. This prevents\n  paging crashes related to audio, but increases memory usage.\n+ Fixed the TIME and DATE counters in the DOS port when the\n  environment variable TZ is not set.\n+ The SDL audio driver will now initialize the software mixer\n  frequency with the value returned by SDL_OpenAudioDevice.\n+ Fixed audible pops when setting MOD_FREQUENCY.\n+ ccv and png2smzx now return slightly more useful error\n  messages when an image fails to load.\n+ ccv and png2smzx now support BMPs that use BI_BITFIELDS and\n  BI_ALPHABITFIELDS.\n+ ccv and png2smzx now support Truevision TGA images.\n+ Fixed YUV subsample averaging for UYVY (little endian) and\n  YUY2/YVYU (big endian).\n+ ANSi file import/export options are now identified as both\n  ANSi and TXT, and the extension of the selected format is now\n  forced during export.\n+ Fixed taking screenshots when in the board editor text mode.\n+ The SVGA renderer now uses vsync palette/display page flips,\n  which improves performance and fixes display bugs. (asie)\n+ Fixed libxmp stereo samples not playing correctly.\n+ Fixed libxmp music not playing at high audio sample rates.\n\nDEVELOPERS\n\n+ Renamed \"compat_sdl.h\" to \"SDLmzx.h\". This header is now used\n  to abstract ALL trivial includes of SDL headers. This makes\n  supporting the future release of SDL 3 cleaner.\n+ The version of SDL to build with is now determined by the\n  config.sh options:\n    --enable-sdl (enables the default SDL version, usually 2);\n    --enable-sdl2 (enables SDL 2.x);\n    --enable-sdl1 (enables SDL 1.2.x);\n    --disable-sdl (disables SDL).\n  For compatibility, \"--disable-libsdl2\" is still recognized.\n+ Added SDL 2 support to the Wii port.\n+ Added SDL 2 and SDL 1.2 support to the 3DS port. (asie)\n+ Updated the \"sdlaccel\" renderer, which can now be enabled with\n  --enable-sdlaccel in config.sh. This renderer uses the SDL\n  renderer API for hardware rendering and has an experimental\n  threaded charset texture update routine. SMZX isn't currently\n  implemented and it's slow, so it's disabled by default.\n+ Moved SDL renderer window creation to render_sdl.c so both of\n  the SDL renderer-based renderers can benefit from it.\n+ Removed floating point division usage in render_gl2.c.\n+ Added --enable-lto to enable link-time optimizations.\n+ The sanitizer config.sh options can now be used with release\n  builds e.g. --enable-release --enable-asan.\n+ Simplified the configuration of system directories for ports\n  in config.sh, which now prints more information about them.\n+ Fixed config.sh output of \"/usr/bin/git\" and the Git HEAD.\n+ Fixed testworlds process running check edge cases.\n+ Fixed config.sh and platform_endian.h detection for IBM Z and\n  System/390 architecture.\n+ Software mixer configuration (rate, buffer, channels) is now\n  done using the function audio_mixer_init instead of each\n  driver handling it manually.\n+ The software mixer render function now takes the number of\n  frames and channels to render and the output format instead\n  of the number of bytes. Supported output formats are 16-bit\n  signed, 8-bit signed, and 8-bit unsigned.\n+ Audio streams with a null mix_data function are now supported.\n  These streams will be ignored by the software mixer.\n+ sampled_mix_data now properly handles output requests smaller\n  than the size of the audio buffer.\n+ Added software renderer regression tests.\n+ Removed PPAL compile time render_layer variants. These did not\n  help performance and increased the render_layer size by 50%.\n+ Added dependency builder makefile system in scripts/deps to\n  help with regenerating MinGW, DJGPP, darwin-dist, and Linux w/\n  MemorySanitizer dependencies.\n+ Overhauled the darwin-dist build system to allow for more\n  modern XcodeLegacy-based compilation, including code signing\n  and shared library support (via dylibbundler). The following\n  architectures are now supported: i386, x86_64, x86_64h, ppc,\n  ppc64, and arm64/arm64e (untested).\n+ Updated libxmp to 4.6.0+6cb48a4a+sample rate patch.\n+ Added more PowerPC64 GCC defines to platform_endian.h.\n\n>#MAIN.HLP:072:Table of Contents\n\nDecember 31st, 2023 - MZX 2.93\n\nThis is the first MegaZeux release in about 3 years, so there\nare a lot of changes, including a large number of crash fixes.\nBrief summaries of each section:\n\nGeneral: worlds are decrypted in memory now, MegaZeux 1.x worlds\nare now supported, you can have more custom sound effects, the\n1/8th random movement chance of dragons has been readded, and\nstatic sprites now collide at their apparent position.\n\nRobotic: new label PLAYERDIED, new counter KEY_PRESSEDn, new\ncounter DATE_WEEKDAY, new counter SPRn_OFFONEXIT, new viewport\ncounters, and setting MOD_LOOPSTART after MOD_LOOPEND now works.\n\nEditor: the robot editor now has undo/redo, layer MZM block type\ncan now be selected, reimplemented ANSi/TXT import/export,\nwatchpoint improvements, a new variable debugger RAM menu, and\nvlayer saved positions.\n\nVideo/audio: most GLSL performance and graphical issues have\nbeen addressed and libxmp has received a massive bugfix update.\n\nPortability: new PS Vita, DOS, Wii U, and Dreamcast ports.\nNumerous NDS and 3DS improvements. The extra memory system from\nthe NDS port has been generalized to several ports and now\nsupports memory compression. Virtual filesystem support.\n\nUtilities: vastly expanded image file support for ccv/png2smzx\nand a new video conversion utility called y4m2smzx.\n\nGENERAL\n\n+ Added an \"About MegaZeux\" dialog, accessible from the main\n  menu. This menu contains extended version information, and\n  displays license information for MegaZeux and the 3rd party\n  software it uses.\n+ Protected worlds are now decrypted to RAM or a temporary file\n  when 'auto_decrypt_worlds' is enabled and the original file is\n  left unmodified. This setting is now enabled by default.\n+ Added experimental support for loading MZX 1.x worlds. Support\n  is not fully implemented yet, but all currently known 1.x\n  worlds are playable.\n+ Up to 256 custom sound effects are now supported instead of\n  50, and custom sound effects can be up to 255 chars in length.\n  Currently, the sound effects editor is restricted to 100 SFX\n  of length 68; the CHANGE SFX # to \"\" command may be used as a\n  temporary workaround.\n+ Custom sound effects can now be given custom names in the\n  sound effects editor. Currently names are limited to 9 chars\n  and are only useful when editing.\n+ Fixed dragon 1/8th chance of random movement, which has been\n  broken since 2.80. The original DOS behavior (random movement)\n  or the 2.80 behavior (no random movement) can be selected via\n  the new board setting \"Dragons randomly move\".\n+ Reset Board on Entry and load charset/palette on entry now\n  optionally will be performed even if re-entering a board from\n  itself (enabled by default).\n+ Fixed a Reset Board on Entry bug where a temporary board would\n  not be generated when loading the title screen or if the\n  starting board is the title board.\n+ Fixed bug where the second cycle of a board when entering from\n  a board edge or entrance would be shortened due to incorrect\n  gameplay framerate timing introduced in 2.91g.\n+ Fixed a bug where, if a world's title screen music is set but\n  fails to load when the world is loaded, the music from another\n  world could continue to play.\n+ Fixed a bug where boards wouldn't be pulled from extra memory\n  before saving, potentially causing save corruption on the NDS.\n+ Fixed a standalone mode crash on exit that occurred when using\n  Alt+F4 or the window close button.\n+ Fixed a crash that could occur when loading a save file with\n  the temporary board flag set but no temporary board data.\n+ Fixed a bug in the random number generation function that\n  could cause it to rarely return a number outside of the\n  expected range. This unfortunately breaks compatibility for\n  the RANDOM_SEED# counter.\n+ Static sprites now collide as if they are at the position on\n  the board where they are currently being drawn i.e. relative\n  to the viewport instead of at their actual X/Y position. This\n  behavior has also been added to the IF SPRITE AT # # command.\n+ Optimized ccheck 3 sprite collision performance by generating\n  visibility masks for characters directly instead of using an\n  intermediate render and skipping pixel checks for blank chars.\n+ Fixed a bug where worlds with SMZX mode selected would display\n  the previous world's palette after switching back to MZX mode.\n+ World and save files now save the MZX mode palette when in\n  SMZX modes 2 or 3. Save files now save the MZX mode palette\n  intensities when in SMZX modes 2 or 3. The palette and palette\n  intensities for SMZX modes 2 and 3 are now saved as \"palsmzx\"\n  and \"palints\" when one of these modes is active.\n+ Save files now save palette intensities for all modes as a raw\n  array of little endian DWORDs instead of bytes. The 2.92 and\n  2.93 intensities file formats are mutually incompatible.\n+ Scrolls can now display standard message ~~@@ color codes.\n+ Fixed warning messages caused by SDL controller CRCs.\n+ Fixed hangs that could be caused by malformed counters files.\n+ Fixed world/save/counters file corruption in rare edge cases\n  where the aforementioned files could break the zip format's\n  internal field bounds.\n+ Fixed loading of world/save/counters files over 4 GB.\n+ Fixed crashes when loading world or save files that contain\n  invalid current board IDs (both loaders), saved position\n  board IDs (both), start/death/game over board IDs (zip), board\n  exit board IDs (zip), robot program positions (both), robot\n  stack pointers (both), or robot stack sizes (legacy).\n+ The output file mode (FWRITE_OPEN, FWRITE_MODIFY, or\n  FWRITE_APPEND) is now stored in save files. This fixes a bug\n  where the output file would be reopened in the wrong mode (ab)\n  when reloading a saved game.\n+ Added a second Robotic command listing to the help file that\n  lists commands categorized by command type. (Terryn)\n+ Links in the help file table of contents and Robotic Reference\n  Manual are now grouped into categories. (Terryn)\n+ Other misc. help file corrections and maintenance. (Terryn)\n\nROBOTIC\n\n+ New built-in label: PLAYERDIED (and the subroutine equivalent\n  #PLAYERDIED). When the built-in player dies, this label is\n  sent on the board the player died on. Because player death\n  occurs at the end of the cycle, this is received during the\n  following cycle.\n+ New counter: KEY_PRESSEDn, to read internal keycode statuses.\n  This is like KEYn, but KEY_PRESSEDn accepts internal keycodes\n  instead of PC XT keycodes. KEY_PRESSEDn will return 1 if the\n  key is currently pressed, or 0 if it is not pressed.\n+ New counter: DATE_WEEKDAY, which returns the number of the\n  current day of the week. Values are 0=Sunday, 1=Monday,\n  2=Tuesday, 3=Wednesday, 4=Thursday, 5=Friday, 6=Saturday.\n+ New counters: VIEWPORT_X, VIEWPORT_Y, VIEWPORT_WIDTH, and\n  VIEWPORT_HEIGHT to read the current board's viewport settings.\n  (Sparkette)\n+ New counter: SPRn_OFFONEXIT. When enabled for a sprite, that\n  sprite will be automatically turned off when exiting the board\n  (same as setting SPRn_OFF). This occurs even if the new board\n  and the previous board are the same. (Sparkette)\n+ Fixed crash bugs caused by using CHANGE SFX # to \"\" with an\n  invalid sound effect number.\n+ Fixed loop detection for Ogg Vorbis files when MOD_LOOPSTART\n  is set to a value greater than MOD_LOOPEND.\n+ Fixed a crash bug when attempting to save MZMs over 4MB to an\n  existing string.\n+ Fixed a buffer overflow crash that could occur when using LOAD\n  CHAR SET with both a large input file and a large offset.\n+ Reading from and writing to extended character sets should no\n  longer display an \"advanced graphical features\" error message\n  when a renderer without layer rendering support is active.\n  This will, however, display a (new) error message specific to\n  the Nintendo DS port.\n+ Fixed CLIP INPUT and IF FIRST STRING \"\" \"\" bug where INPUTSIZE\n  would be used as the bound on the input string without also\n  checking for null chars, potentially allowing heap corruption.\n+ Fixed a hack where the TELEPORT command would temporarily\n  store the current board to extra memory, causing the game\n  update loop to potentially cause memory corruption with stale\n  board pointers on the NDS.\n+ Fixed string wildcard matching bug that could cause patterns\n  ending with ? to sometimes fail.\n+ Dividing the value -2147483648 by -1, either by the commands\n  DIVIDE and MODULO or through expression operators / and %, no\n  longer crashes MegaZeux.\n+ When the right operand to a bitshift operator in an expression\n  is less than 0 or greater than 31, << and >> will return 0,\n  and >>> will return 0 if the left operand is positive or -1 if\n  the left operand is negative. The old behavior of the bitshift\n  operators for these shift values was architecture dependent.\n+ The date and time for the Robotic date and time counters is\n  now sampled once per command maximum. When multiple date/time\n  counters are read in the same command, they are guaranteed to\n  represent the same time.\n+ The Robotic message box commands now properly display char 10.\n+ The Robotic command ? now correctly accounts for color codes\n  in its length bounding.\n+ Fixed crashes in the & Robotic command caused by bad color\n  string length calculations.\n+ Fixed PrintScreen/SysRq and Menu/\"Right Click\" keycodes.\n+ Unsupported keys no longer return a KEY_PRESSED value of 1\n  for SDL 2 builds.\n+ Fixed crashes when executing the GIVE, TAKE, TAKE ELSE, and\n  TRADE commands with an invalid item type.\n+ Fixed crashes when disassembling a Robotic command with an\n  invalid condition.\n\nEDITOR\n\n+ Added robot editor undo (Ctrl+Z) and redo (Ctrl+Y). Like with\n  board/vlayer/charset editing, the size of the undo stack is\n  defined by the config setting \"undo_history_size\". Note that\n  extended macro expansion can sometimes clobber undo or redo\n  frames (this is not easily fixable, and prevents worse bugs).\n+ When loading a layer MZM to the board in the editor, instead\n  of always loading the MZM as CustomBlocks the conversion type\n  can now be selected from CustomBlock, CustomFloor, and Text.\n  Canceling the object type dialog no longer cancels the current\n  block or MZM placement.\n+ Reimplemented ANSi import/export support for the editor. ANSi\n  and TXT files can be imported in the editor with Alt+I and can\n  be exported by selecting a block (Alt+B).\n+ Creating a new world using N from the title screen or Alt+R\n  in the editor now clears the extended character sets.\n+ Improved the performance of Robotic debugger watchpoints,\n  especially for non-built-in counters and non-spliced strings.\n+ Robotic debugger watchpoints can now watch for particular\n  counter or string values. Leaving the value field blank will\n  still check for any value.\n+ Added a list of variables related to RAM usage to the counter\n  debug window. This list can be found in the \"World\" list.\n+ Added vlayer saved positions to the editor. Like regular saved\n  positions, these can be configured with the config file option\n  \"vlayer_position#\", and they work identically to board saved\n  positions.\n+ Fixed bugs caused by missing validation in the Import SFX\n  feature in the editor.\n+ Fixed a bug where multichar char tile movement could still\n  sometimes jump to the -/+/= chars in the main charset.\n+ Fixed another bug that would cause string searching in the\n  robot editor to sometimes skip matches.\n+ Replace all (Ctrl+H) in the robot editor should no longer skip\n  adjacent instances of the search string.\n+ Opening the block action menu in the robot editor (either with\n  Alt+B or Alt+Enter) now updates the current line, fixing bugs\n  where it could copy old line contents to the buffer.\n+ Fixed a robot editor crash that could occur when combining two\n  lines with backspace or delete if one of the lines contained\n  the start or end block mark.\n+ Fixed a bug in the robot editor where Ctrl+Home and Ctrl+End\n  would only set the current line (and not the current column).\n+ Fixed a robot editor extended macro crash that would occur:\n    when pasting a line containing a macro from the clipboard;\n    when importing a Robotic source file that invokes macros;\n    when invoking nested extended macros.\n+ Invoking an extended macro with enter/return no longer inserts\n  an extra blank line.\n+ Pressing enter at the start of a line with an extended macro\n  now invokes the macro instead of just inserting a line.\n+ Fixed editor extra memory crashes when importing and deleting\n  boards.\n+ editor_show_thing_toggles is now enabled by default.\n- board_editor_hide_help and robot_editor_hide_help are no\n  longer enabled by default for accessibility.\n\nVIDEO/AUDIO\n\n+ Added the \"glslscale\" renderer. This is the same as the GLSL\n  renderer, but uses software rendering with scaling shaders\n  instead of hardware rendering. This is faster on low end PCs\n  and embedded devices and is the new default renderer selected\n  by auto_glsl. (Hardware rendered GLSL is still available for\n  users with graphics cards by selecting the \"glsl\" renderer.)\n+ The software fallback renderer functions now support SMZX mode\n  3 indices, fixing graphical bugs in situations where the\n  fallback would be used and display the wrong colors in games\n  using custom indices.\n+ Added an \"auto\" mode for the force_bpp config option. Modern\n  SDL automatically selects a native window pixel format and a\n  fixed value can make the software renderer slower. Detecting\n  the BPP from the created window is more in line with reality.\n+ Fixed color inaccuracies in the softscale renderer on Mac OS\n  caused by using full-swing YUV values instead of the expected\n  studio-swing values for GL_YCBCR_422_APPLE textures.\n+ Simplified GLSL renderer texture data packing to improve\n  support for OpenGL ES and older OpenGL implementations.\n+ The GLSL tilemap fragment shaders will now use highp floats if\n  provided by the OpenGL ES driver. If highp is unavailable and\n  mediump has inadequate precision, MZX will print a warning.\n+ Fixed software renderer display issues caused by relying on\n  SDL_PixelFormat::BitsPerPixel instead of BytesPerPixel on\n  platforms with native 15 BPP display modes.\n+ Added disable_screensaver configuration option. MegaZeux now\n  leaves the screensaver enabled by default (fixes regression\n  caused by SDL 2.0.2+).\n+ Added an \"sdl_render_driver\" config setting. This setting can\n  be used to specify the underlying SDL renderer driver used by\n  the softscale renderer. This setting does not affect any other\n  renderers.\n+ libxmp playback improvements for GDM, AMF, and OctaMED modules\n  as well as general stability improvements.\n+ Updated libxmp to 4.6.0+ceb2d025.\n+ Fixed crashes in the RAD player that could be caused by\n  references to uninitialized instruments.\n+ Applied various Opal fixes from OpenMPT.\n- Removed GL4ES from the GLSL blacklist.\n\nPORTABILITY\n\n+ Added an experimental PlayStation Vita port. (Spectere)\n+ Added an experimental Wii U port. (asie)\n+ Added an experimental Dreamcast port. (asie, Lachesis)\n+ Added an experimental MS-DOS 32-bit port based on DJGPP.\n  (Mr_Alert, asie, Lachesis)\n+ Added support for BlocksDS to the NDS port. (asie)\n+ Added support for 800x240 mode to the 3DS port. This mode can\n  be toggled with the 3DS keyboard. (asie)\n+ Added support for using 352kB of previously unused NDS VRAM\n  as board storage. This area can be used for board storage even\n  when a slot 2 expansion cartridge is not present. (asie)\n+ Reduced RAM usage by 50k by removing variants of *printf and\n  *scanf with floating point support on the NDS. (asie)\n+ The MOD_ORDER counter is now readable on the NDS. (asie)\n+ save_slots is now enabled by default for consoles. (Spectere)\n+ Inactive boards and some robots are now compressed on the NDS,\n  which (combined with asie's VRAM patch) should vastly increase\n  the number of games that can be played on the original DS and\n  DS Lite without a slot 2 expansion cartridge. This is also\n  enabled for the PSP and DOS ports.\n+ The NDS, PSP, and DOS ports now write zip archives with the\n  fastest zlib compression level. This makes saved files that\n  use compression (worlds, saves, etc.) slightly larger.\n+ Added virtual filesystem support to MegaZeux. Currently, this\n  is only used for caching files, and is only enabled by default\n  on platforms with slow or buggy file IO (3DS, Vita). This\n  feature may be optionally configured with the config options\n  vfs_enable, vfs_enable_auto_cache, vfs_max_cache_size, and\n  vfs_max_cache_file_size.\n+ Fixed a bug where the 3DS and Switch ports would display .. in\n  the file manager when selecting a board module, board charset,\n  or board palette.\n+ Fixed a bug where, when a faulty dirent implementation returns\n  .. when listing the contents of a root directory, .. would be\n  displayed despite being meaningless.\n+ Fixed a bug where some checks in the file manager could fail\n  when getcwd returns different slashes than expected.\n+ Fixed a bug where stdio redirect could generate corrupted log\n  files on the Nintendo DS due to poor freopen support.\n+ Enabled the GLSL renderer for the Android port. The Android\n  port now uses the GLSL scaled software renderer by default.\n- Removed 3DS CIA support. (asie)\n\nUTILITIES\n\n+ ccv and png2smzx now both support the following image formats:\n  PNG (libpng builds only), GIF, BMP (uncompressed, RLE8, RLE4),\n  NetPBM (PBM, PGM, PPM, PNM, PAM), farbfeld. png2smzx will now\n  build when libpng support is disabled, though it won't support\n  loading PNGs with this configuration.\n+ ccv and png2smzx now both support streaming image data from\n  stdin using the input filename \"-\".\n+ Added y4m2smzx, a video converter frontend for the same image\n  converter that png2smzx uses. This is a rewrite of Mr_Alert's\n  original frontend from 2010. This uses a custom y4m loader\n  rather than MJPEG Tools for now, so it may be buggy. Usage:\n  ffmpeg -i input -f yuv4mpegpipe pipe:1 | y4m2smzx - out.mzv\n+ The downver utility now supports save files.\n+ Fixed a bug where checkres would not check the current dir\n  for files when provided with a MZX filename with no path\n  component.\n+ checkres now attempts to scan every board in <=2.84 worlds\n  where a corrupt board/robot is found. Due to old world format\n  limitations, robots after a corrupt robot can not be scanned.\n+ checkres now supports scanning protected worlds.\n+ Added experimental checkres support for MZX 1.x worlds.\n\nDEVELOPERS\n\n+ Added --enable-extram config.sh flag to enable extra memory\n  hacks on arbitrary platforms. This is enabled by default in\n  CONFIG.NDS, CONFIG.PSP, and CONFIG.DJGPP. When enabled, non-\n  active boards are compressed in RAM. Certain platforms can use\n  platform-specific storage, like the NDS.\n+ Board input strings, charset paths, and palette paths are now\n  heap allocated on-demand to save RAM for low-memory systems.\n+ Status counters are now saved as a nested properties file.\n+ Refactored the 3DS renderer to use templates. (asie)\n+ intake2() now supports custom handling of intake events, i.e.\n  it can now be used without providing a fixed size buffer.\n+ The draw and click handlers have been removed from intake2().\n  This should provide more flexibility to the parent context for\n  displaying the string that is being edited.\n+ Added \"mzxout\" and \"mzxerr\" streams for printing console\n  messages. In most places these should be used instead of\n  \"stdout\" and \"stderr\" to allow debug and warning messages\n  print to the correct log files when stdio redirect is enabled.\n+ Fixed broken MIPS big endian detection inherited from SDL 1.2.\n+ Added architecture width detection for RISC-V RV32 and MIPS64.\n+ Replaced the macro-based mixers in sampled_stream.c with more\n  maintainable template-based mixers.\n+ Fixed the DSO -Wstrict-aliasing warnings in Socket.cpp and the\n  OpenGL renderers for GCC versions using -Wstrict-aliasing=2.\n+ Added --host config.sh option to manually specify a cross\n  compiler prefix. This is ignored or overriden by most ports\n  and is mainly just for Linux.\n+ Relicensed ccv from GPL 3 to GPL 2+ to match the rest of MZX.\n  (Lancer-X)\n+ If available, GetSystemTimePreciseAsFileTime and clock_gettime\n  are now used to calculate the system clock time.\n+ If available, SDL_GetTicks64 is now used by get_ticks().\n+ Minor performance improvements for run_robot, is_string, and\n  find_function_counter.\n+ _FILE_OFFSET_BITS=64 is now used for 64-bit fseeko, ftello,\n  readdir, and stat/fstat support for 32-bit Linux builds.\n+ Updated Android SDL to 2.28.2. (asie)\n+ Updated Android NDK to r23c. (asie)\n+ Fixed Android build system handling of missing libraries.\n+ Improved fileform.html, joystick.html, keycodes.html, and\n  platform_matrix.html readability on small/mobile displays.\n- String values are now allocated separately from the string\n  struct and name. This may make strings very slightly slower,\n  but means string pointers are now stable through an entire\n  gameplay session.\n\n>#292CLOG.HLP:292:New in Versions 2.92 to 2.92f\n>#2901CLOG.HLP:291:New in Versions 2.90 to 2.91j\n>#284CLOG.HLP:284:New in Versions 2.84 to 2.84c\n>#2823CLOG.HLP:283:New in Versions 2.82 to 2.83\n>#281CLOG.HLP:281:New in Versions 2.81 to 2.81h\n>#280CLOG.HLP:280:New in Versions 2.80 to 2.80h\n>#OLDERVER.HLP:260:New in Versions 2.60 to 2.70\n>#OLDESVER.HLP:201:New in Versions 2.01 to 2.60\n>#NEWIN200.HLP:1st:New in Version 2.00\n>#ANCENVER.HLP:1xx:New in Versions Through 1.03\n>#MAIN.HLP:072:Table of Contents\n#292CLOG.HLP\n:292:\nNew in Versions 2.92 to 2.92f\n\nNovember 22nd, 2020 - MZX 2.92f\n\nHere's another bugfix release. Highlights of this release are\ncrash fixes (including the 3DS OGG crash bugs), numerous SAM/WAV\nplayback bugfixes, some edge case compatibility fixes, world\ndecryption improvements, networking improvements (including\nexperimental Wii, 3DS, and Switch support), bugfixes for the\nfile manager in Nintendo Switch builds, and too many libxmp\nfixes/improvements to list. Since libxmp development is active\nagain, the number of MegaZeux hack patches is down to just two!\nThe editor is now enabled for 3DS builds, and some rudimentary\njoystick support has been added to the editor so it will (maybe)\nbe (almost) usable.\n\nOf special note are asie's improvements to the NDS port. New\nfeatures include protected character set and palette support,\nimproved touchscreen focus, reduced memory consumption, and\n*audio support*. Currently NDS audio requires preconversion of\nmusic files to .MAS and sample files to .SAM, but that it works\nat all is amazing by itself. Pre-converted .MAS files are\nincluded with the packed-in copy of Caverns of Zeux.\n\nUSERS\n\n+ Added audio support to the NDS port. PC speaker effects and\n  .SAM files will work out of the box, but .MOD/.S3M/.XM/.IT\n  files require preconversion to .MAS using mmutil. All other\n  audio formats are unsupported. (asie)\n  Example:\n\n    mmutil -d -m CV_TECH.MOD\n\n+ Added protected character set and palette support to the NDS\n  port and reduced overall RAM usage for the NDS port. (asie)\n+ Fixed a bug in the NDS port where MZX would immediately focus\n  the player after a touchscreen press event, preventing the\n  touchscreen from being used to scroll the upper screen. (asie)\n+ Fixed a crash that could occur sometimes when duplicating\n  robots on a board with Reset Board on Entry enabled.\n+ Added SET RANDOM, INC RANDOM, DEC RANDOM, and TAKE \"label\"\n  commands to the list of commands that should allow \"infinite\"\n  loops in pre-2.80 versions.\n+ Fixed SET RANDOM # TO # for large ranges on certain platforms\n  (example: Linux) confused by the 32-bit math used to calculate\n  the random range.\n+ Fixed crashes that could occur when attempting to run MegaZeux\n  without help.fil.\n+ Fixed graphics corruption when using the glsl and opengl2\n  renderers on big endian platforms.\n+ Fixed a bug that would cause string searches to sometimes skip\n  certain matches.\n+ Improved performance of saving ZIP worlds/save files/etc. for\n  some platforms.\n+ Platforms without a protected palette and the meter enabled\n  should now fade out properly before loading a world.\n+ Rewrote decrypt() to work better on low-memory platforms.\n+ Improved GLSL layer renderer performance slightly by sending\n  the palette and indices to the GPU fewer times per frame.\n+ Fixed a bug where the GLSL renderer could attempt to load the\n  framebuffer symbols from a driver that doesn't support them\n  when resizing the window.\n+ Fixed a bug where checkres wouldn't correctly detect\n  \"LOAD_ROBOT#\" when used to load to a specific robot.\n+ Fixed a memory leak when printing network error messages in\n  Windows builds.\n+ Fixed a potential crash bug in the netcode where socket IDs\n  could be reused after close() when attempting to connect.\n+ Fixed a crash bug affecting the compatibility implementation\n  of getaddrinfo() that would occur if NULL was returned by\n  gethostbyname() during DNS resolution.\n+ Fixed a potential crash in the compatibility implementation of\n  getaddrinfo() caused by gethostbyname() being thread-unsafe.\n+ Fixed a bug where the updater would hang for up to 10 seconds\n  due to connections timing out during the confirmation UI.\n+ Fixed a bug where the local manifest.txt would be overwritten\n  by the remote manifest, potentially causing bugs. The local\n  manifest will now be replaced only after a successful update.\n+ Fixed bugs where the HTTP layer would filter header names,\n  content/transfer coding values, and content type values too\n  strictly.\n+ Fixed a bug where INPUT STRING would display newlines from\n  an interpolated string.\n+ MZX now validates the world version of encrypted worlds before\n  offering to decrypt them. This should reduce the chance of\n  accidentally \"decrypting\" a corrupted file and prevents MZX\n  from attempting to decrypt MZX 1.x files (which store the\n  password differently).\n+ Added config settings \"editor_show_thing_blink_speed\" and\n  \"editor_show_thing_toggles\". The former controls the blinking\n  speed of the show thing (Shift+F2 et al.) editor keys and the\n  latter allows these keys to be treated as a toggle instead of\n  blocking input.\n+ Added SOCKS5 IPv6 and username/password support.\n+ Added \"network_address_family\" config setting to allow users\n  to force either IPv4 or IPv6 connections/name resolution. By\n  default, MZX will now request either or both depending on the\n  system IP address configuration (previously, it was hardcoded\n  to only allow IPv4 despite IPv6 support being implemented).\n+ The config setting \"updater_enabled\" has been added to turn\n  off the updater system entirely without disabling networking.\n+ The updater now allows update checks to be performed even when\n  a full update wouldn't be possible, and should be more helpful\n  about printing warnings to the console when this situation\n  occurs.\n+ Enabled rewinddir hack and .. file manager hacks for Nintendo\n  Switch builds to fix the file manager and directory seeking.\n+ Fixed a bug where importing a board over the title board would\n  not update the world title.\n+ Fixed a crash that could be caused by selecting a block on the\n  overlay/vlayer, changing boards, then pressing enter.\n+ Fixed a bug where the currently playing PC speaker note would\n  play for the rest of its duration after turning off PC speaker\n  sound effects, after \"end play\", after exiting gameplay, etc.\n+ Fixed a regression where turning off music in the settings\n  menu and then turning music back on would not start playing\n  the music file for the current board.\n+ Fixed multiple libvorbis and tremor crashes related to the\n  3DS platform_mutex_lock implementation not blocking.\n+ Fixed audio frequency bugs in .SAM file playback.\n+ Certain .WAV subtypes handled by SDL_LoadWav should now load\n  properly on big endian machines.\n+ Signed 16-bit samples should now work correctly with MOD SAM\n  on big endian machines.\n+ Fixed the white border present when using the GLSL renderer in\n  HTML5 builds.\n+ Added joystick input handlers for most editor interfaces.\n  Only very basic things are supported like navigating boards\n  and robots. This is intended for things like handheld consoles\n  without a keyboard.\n+ Fixed a bug where turning off the listening mod would load the\n  mod that was playing when the listening mod was loaded instead\n  of the current board's mod.\n+ Fixed a bug where setting a board mod to a partiular mod, then\n  setting the board mod to *, then changing to a different board\n  with the same mod would not restart the board mod.\n+ Fixed a bug where libxmp would not ignore zero volume samples\n  in STM files.\n+ Restored libxmp support for Soundtracker 2.6/IceTracker .MODs.\n  Currently only libxmp supports this .MOD variant.\n+ Fixed a bug where MZX would fail to load Noisetracker,\n  Octalyser, and Mod's Grave .MOD files if they were named using\n  the extensions .NST, .OCT, and .WOW (respectively). These can\n  now be selected with Alt+N/Ctrl+N in the editor.\n+ Enabled loading .XM and .AMF files with MikMod. Whatever the\n  issue was with these in 2007 seems to have since been fixed.\n+ Fixed loading Soundtracker 15-instrument .MODs with MikMod.\n+ Fixed MOD_POSITION and MOD_LENGTH counters for MikMod.\n+ MikMod now uses interpolation if module_resample_mode is set.\n- Disabled playing modules using the SAM command with MikMod to\n  avoid crashes due to MikMod using a global player state.\n\nDEVELOPERS\n\n+ The test worlds now run correctly when AddressSanitizer is\n  enabled.\n+ The SUPPRESS_BUILD Makefile flag has been split into several\n  flags, allowing more granularity for disabling default rules.\n  The Android and Darwin \"dist\" meta targets use these flags to\n  to disable the default \"all\" target, so \"make\" can now be used\n  in place of \"make dist\" for these platforms.\n+ Using \"make build\" will now remove the build/${SUBPLATFORM}\n  directory if it exists before preparing a build. Before, using\n  \"make build\" when the platform build directory already exists\n  would not copy any updated binaries/assets/etc., potentially\n  causing confusion.\n+ The check_alloc series of functions should now be thread safe.\n  The main thread will display a fatal error as usual when\n  allocation fails; others threads will print a warning and\n  return NULL.\n+ Added implementations of new/delete/__cxa_pure_virtual that\n  can be linked to avoid linking libstdc++/libc++ on platforms\n  and configurations that otherwise don't need those libraries.\n+ Refactored the netcode to use C++ instead of/in addition to C,\n  allowing some cleanup that otherwise wouldn't have worked.\n  Host::bind, Host::listen, Host::accept, and Host::poll are no\n  longer disabled. Other network deadcode functions have been\n  updated but are still disabled.\n+ Fixed FD_ISSET in the Windows netcode.\n+ Added a EAI_AGAIN define for the getaddrinfo() fallback.\n+ Fixed leak of updates.txt file pointers in the updater that\n  could occur when updates.txt fails to download. The updater\n  now downloads updates.txt to a buffer instead of to disk.\n+ Added graphics.renderer.hardware_cursor for platforms with\n  hardware cursors that may need to be updated during any frame\n  regardless of the current cursor state/blinking (i.e. DJGPP).\n+ Fixed \"make build\" error after using \"make debuglink\" with png\n  support disabled.\n+ Added const and restrict to the software render_graph and\n  render_layer implementations for a small performance boost.\n+ Added --disable-getaddrinfo and --disable-ipv6 config.sh flags\n  to allow disabling these for old platforms and consoles SDKs.\n  Amiga, Wii, and PSP network builds force-disable these. Switch\n  and 3DS builds currently force-disable IPv6.\n+ Added experimental network support for Wii, Switch, and 3DS.\n+ Updated libxmp to git-ab70ec9f, refactored libxmp patches.\n+ Removed unused libxmp Paula simulator code to save RAM. (asie)\n+ SDL is no longer required to build MZX with MikMod enabled.\n+ Fixed MikMod static linking on Windows.\n+ Added --disable-stack-protector config.sh option. Platforms\n  that previously disabled the stack protector in the Makefile\n  now force disable this config option. These force disable\n  hacks will probably be removed in the near future.\n\n>#MAIN.HLP:072:Table of Contents\n\nJuly 20th, 2020 - MZX 2.92e\n\nThis bugfix release includes miscellaneous audio fixes and fixes\nfor bugs in the configuration system. Other things of note are a\nfix for a graphical bug in the robot editor, a freeze bugfix in\nthe updater when updating from older MZX releases, and the title\nof the Alt+A (\"Select Char Set\") dialog has been reverted back\nto roughly what it was in DOS versions. A second default SMZX\ncharacter set with a different font has been added as well.\n\nUSERS\n\n+ Fixed Windows file handle leaks in audio_xmp.c caused by\n  MinGW's non-compliant fdopen.\n+ Fixed a bug where the title screen's intro message wouldn't\n  cycle the scroll color while no world was loaded.\n+ Added the missing \"ccode_chars\" config setting.\n+ Fixed a bug where the \"ccode_extras\" config setting would\n  change the strings color code instead.\n+ Fixed a bug where \"macro_#\" where # is 0 or between 6-9 would\n  corrupt various parts of the editor configuration. Notably,\n  \"macro_6\" would corrupt the extended macros and cause crashes.\n+ Fixed division-by-zero crashes and GL errors when using\n  invalid fullscreen_resolution or window_resolution values.\n+ Fixed a bug where files included by the config file include\n  directive would ignore editor settings. Also added a recursion\n  limit for includes and fixed a bug where the \"include=file\"\n  format would not work on some platforms.\n+ Fixed a libxmp bug where pattern jump/break could take effect\n  after using JUMP MOD ORDER or setting MOD_ORDER/MOD_POSITION.\n  This fixes a bug at the end of the Inmaportal scene in Cans 3.\n+ Zip files with the language encoding flag set no longer print\n  unsupported flag errors.\n+ Fixed a bug where recursive directory deletion in the updater\n  could get stuck in an infinite loop due to not checking the\n  return value of rmdir and path_get_directory not behaving the\n  same as get_path used to.\n+ Fixed a bug where the cursor could display over the color\n  selector and char editor when opened from the robot editor.\n+ Fixed a bug where the default character set selection menu was\n  inconsistently labeled \"Object type\".\n+ Added a second SMZX default character set with a more legible\n  font. The original is still available for those who prefer it.\n\nDEVELOPERS\n\n+ Fixed memcasecmp test alignment checks for the Motorola 68000.\n+ Improved validation of config setting inputs and added a unit\n  test for config file and command line parsing.\n+ Added buffered zip compression/decompression support.\n+ Added unit tests for the zip functions and (de)compressors.\n\n>#MAIN.HLP:072:Table of Contents\n\nMay 8th, 2020 - MZX 2.92d\n\nThis release includes minor new features, an overhaul to the way\nMZX handles text input from SDL 2.0 which should fix various\ntext bugs (particularly with non-US keyboards), fixes required\nto get the Android port working, and a new file format document.\nOther things of note are two libxmp patches fixing issues with\nS3Ms saved by the original Modplug Tracker and with GDMs relying\non fine effect continue, checkres updates, and misc. fixes for\nthe Windows and HTML5 ports.\n\nFixed compatibility issues in this release are a string compare\nbug that prevented Mines of Madness from working and MODs with\nextended filenames working when referenced by their truncated\nDOS SFN in the MZX file. The latter fix only works when there is\na single unambiguous match, which turned out to be essentially\nevery affected game.\n\nIt is worth noting that the Android port is EXPERIMENTAL and is\nlikely to have compatibility issues on many devices. A USB OTG\nor Bluetooth keyboard or game controller is required at minimum.\nMany devices have key press issues that are likely caused by\ndrivers or SDL. SDL text input events are disabled as they cause\neven more keyboard bugs, and as a result non-US keyboard layouts\nprobably won't work with this port right now. Other problems\nwith this port that have been reported include graphical bugs\nand crashes when focusing/unfocusing MegaZeux.\n\nUSERS\n\n+ Added robot editor shortcuts CTRL+Home and CTRL+End to jump to\n  the start and the end of the current program.\n+ Char selector tile movement is now disabled outside of the\n  char editor and for 1x1 char selections. This allows typing\n  -/_/=/+ in other char selectors to jump to those characters.\n+ Added docs/fileform.html, a document describing all currently\n  supported MZX file formats in excessive detail.\n+ When startup_file is provided with both a directory and a\n  filename and no startup_path has been set, the directory will\n  now be used as startup_path. If startup_path is set, the\n  directory portion of startup_file will still be ignored.\n+ The flash thing editor functions (Show Robots, etc.) now use\n  the protected charset and will display with the protected\n  palette in SMZX mode and in regular mode situations where the\n  normal flashing would be difficult/impossible to see.\n+ Fixed a bug where input text with no corresponding internal\n  key would be ignored by the robot editor.\n+ Fixed a bug where some AltGr key combinations would not work\n  in the robot editor (note: SDL doesn't distinguish Alt from\n  AltGr, so any Alt key combos with a special meaning in the\n  robot editor will still be intercepted by MZX).\n+ Windows Alt+numpad sequences corresponding to ASCII characters\n  32-126 should now be recognized.\n+ Fixed an out-of-memory error in the save slot UI and improved\n  its mouse functionality.\n+ Added optional CRC-32 output to checkres. Currently this flag\n  only works for files in ZIP archives and no CRC-32 validation\n  is performed.\n+ Fixed checkres parsing for LOAD CHAR SET \"+&+var&file.chr\".\n+ Fixed checkres crash that could occur on some platforms when\n  attempting to checkres a directory.\n+ Fixed wrong robot board positions reported by checkres for\n  some files when run with -vv.\n+ Repurposed the old checkres -A flag to display everything but\n  unused files, which should be much more useful than the older\n  behavior (particularly with loose files in directory dumps).\n+ Fixed memory leaks in legacy_assemble_line.\n+ Fixed potential null termination issues in both world loaders\n  that could be triggered with invalid board name, robot name,\n  scroll, sensor, sfx, and status counter strings. Also improved\n  loaded robot stack bounds checks.\n+ Decreased the sizes of the counter and string structs and\n  fixed a crash that would occur when attempting to expand the\n  counters or strings lists beyond 1 billion. Checks have been\n  added to prevent them from expanding beyond 2^31 (please don't\n  actually use this many counters/strings).\n+ Fixed string comparison for pre-2.81 games that relied on\n  null-terminated string compares. This fixes Mines of Madness.\n+ Fixed hlp2html bug where certain chars wouldn't be escaped and\n  decreased the size of the output HTML files somewhat.\n+ Fixed libxmp playback for S3Ms containing ADPCM4 samples. This\n  fixes mm2flash.s3m in ZeuxDrop.\n+ Fixed libxmp playback for GDMs where 100/200/A00 should\n  continue the fine portamento and fine volume slide commands.\n  This fixes LB2_7.GDM from Kikan.\n+ fsafetranslate now attempts to detect matches for truncated\n  DOS SFNs on non-Windows platforms. If an unambiguous match for\n  a truncated SFN is found, the SFN will be expanded to the\n  match. This fixes numerous games relying on truncated SFNs\n  with unambiguous matches.\n+ Reimplemented COPY # # # # for layer-to-layer copies.\n+ Fixed a collision bug affecting unbound sprites with ccheck 3\n  set in SMZX mode where wrong colors could be treated as the\n  transparent color for the purposes of collision.\n+ Fixed softscale bug on some platforms where the screen border\n  wouldn't be cleared.\n+ The GLSL renderer will now attempt to use a framebuffer when\n  available. This fixes GL_INVALID_OPERATION bugs with certain\n  OpenGL ES 2.0 implementations.\n+ Directory navigation in the file manager now checks directory\n  access permissions before allowing navigation and displays an\n  error when navigation fails.\n+ Fixed windowing code bug in extended macro editor that would\n  occur after selecting \"Default\" multiple times.\n+ Fixed a bug where long extended macros could sometimes break.\n+ Fixed extended macros not working when the params line is\n  indented by more than one space or tab.\n+ Windows builds now attempt to load and call SetProcessDPIAware\n  on startup. This should fix undesirable DPI-related scaling\n  and fullscreen bugs.\n+ Fixed Emscripten crashes on startup when built with Emscripten\n  1.39.5 and up caused by usage of deprecated canvas behavior.\n+ Fixed a COPY BLOCK $string bug where the buffer could be\n  allocated at the empty destination string's value on some\n  platforms and a faulty bounds check would result in the string\n  being expanded but otherwise blank.\n\nDEVELOPERS\n\n+ Added a set of C++-based unit tests for portions of MZX that\n  can't be reached/tested easily by MZX worlds. These tests will\n  be built and run when \"make test\" is used prior to running the\n  test worlds.\n+ Decoupled SDL 2 text input handling from regular key handling\n  and added a text input buffer. Text keys can now be requested\n  multiple times and either as ASCII or unicode; the next key in\n  the buffer is returned on subsequent calls. intake and editor\n  text entry can now accept multiple text chars per frame.\n+ Added SDL 1.2 implementation for __peek_exit_input.\n+ Contributed sound engines (libxmp, libmodplug) should now\n  properly rebuild after config.sh is run.\n+ Fixed a bug where khash tables couldn't expand beyond 2^31,\n  added hash caching, made the MZX copy of khash use MZX style,\n  and better documented the changes made to it.\n+ Added fast sprintf replacements for Robotic counter to string\n  conversion (counter interpolation, string/counter compares).\n+ Fixed alignment issues in memcasecmp.\n+ Fixed alignment issues in get_cursor_color.\n+ Added a config.sh flag for UndefinedBehaviorSanitizer.\n+ Categorized config.sh flags list.\n+ Added a trace logging level that is disabled by default and\n  changed most dns.c, fsafeopen.c, and checkres.c debug logging\n  to use it. Use the --enable-trace config.sh flag to enable\n  trace logging for debug builds.\n+ Added a config.sh flag to build using GCC's -fanalyzer flag\n  (disabled by default).\n+ Refactored path functions out of util.c.\n+ Replaced the Android build scripts with an improved Makefile\n  system. The Android Makefile now uses the NDK toolchains\n  directly. Also added support for APK signing and fixed several\n  Android port bugs.\n+ MZX will now exit on startup if no world could be loaded and\n  the SDL dummy video driver is active.\n- Removed -fpermissive from CXXFLAGS (pointless).\n- Removed -fbounds-check from debug builds (pointless; MegaZeux\n  doesn't use GCC to build any Fortran or Java code).\n\n>#MAIN.HLP:072:Table of Contents\n\nMarch 8th, 2020 - MZX 2.92c\n\nHere's another bugfix release, this time mostly oriented towards\nfixing bugs in the Emscripten/HTML5 port and checkres. Other\nfixes of note are the module_resample_mode (formerly 'modplug')\nconfig setting has been fixed, numerous optimizations have been\nmade to the software layer renderer, and yet another ternary\noperator bug has been fixed.\n\nFinally, Spectere has added an experimental save slots UI that\ncan be enabled with the config setting \"save_slots\". This\nfeature is intended primarily for consoles and will likely be\nenabled by default for consoles in the future.\n\nUSERS\n\n+ Added an optional save slots interface to complement the file\n  manager. This can be enabled by setting the \"save_slots\"\n  config setting to 1, and is disabled by default. (Spectere)\n+ Board list typing now allows non-alphanumeric keys like spaces\n  and periods. Typing a space first will match zero or more\n  prefixed spaces while seeking.\n+ Added the config setting \"auto_decrypt_worlds\". When set to 1,\n  this allows MegaZeux to automatically attempt to decrypt\n  protected worlds. For most platforms this option is disabled\n  by default.\n+ MegaZeux now silently ignores robots in older worlds with\n  corrupt programs that are marked unused. This fixes an error\n  message that would display when loading Wes.\n+ Renamed \"modplug_resample_mode\" to \"module_resample_mode\".\n  Fixed a bug where the xmp interpolation mode wouldn't be set\n  correctly from this setting and instead would always reset to\n  \"linear\". The default for this setting in config.txt has been\n  corrected (the default has been \"cubic\" since 2.82b).\n+ Fixed a ternary operator bug where the expression could break\n  with certain counter names in the second and third terms.\n+ Added a comment to the Emscripten frontend for how to disable\n  the \"Click to Play\" splash.\n+ Fixed Emscripten frontend issues when MegaZeux is embedded in\n  an iframe making it difficult to regain focus after losing it.\n+ Fixed an Emscripten frontend bug where certain filenames could\n  cause false positives when checking for a directory name,\n  preventing fsafetranslate from detecting the correct path.\n  This fixes various things in Eternal Eclipse Taoyarin.\n+ The Emscripten frontend should now fall back to memory storage\n  if IndexedDB or local storage fails to initialize. This fixes\n  an error when using MZX in a private browser window.\n+ Fixed a robot editor regression where certain numpad symbols\n  could not be typed.\n+ Added \"GL4ES wrapper\" to the auto_glsl blacklist.\n+ Added \"Software Rasterizer\" to the auto_glsl blacklist.\n+ checkres no longer requires zip archive filenames to end with\n  the .ZIP extension.\n+ Added optional CSV output to checkres.\n+ Added error to checkres for 1.xx worlds.\n+ Fix checkres errors when encountering VER1TO2 empty robots.\n+ ZIP support now ignores __MACOSX/, .DS_Store, and Thumbs.db.\n+ Added decompression support for various legacy ZIP compression\n  methods and deflate64.\n+ The Emscripten frontend now falls back to a wrapper for MZX's\n  internal ZIP support when UZIP fails to extract an archive.\n+ Fixed IndexedDB support for legacy Edge versions. (asie)\n+ Fixed an 8bpp software layer renderer bug where transparent UI\n  colors would not be transparent in SMZX mode.\n+ General software layer renderer (software, softscale, opengl1)\n  performance improvements.\n+ Fixed out-of-bounds CHAR_BYTE access with BYTE values over 14,\n  added compatibility check for games relying on this. (asie)\n+ Fixed faulty bounding on CHAR_X/CHAR_Y/PIXEL.\n- Removed the options \"overlay1\" and \"overlay2\" from the list of\n  selectable renderer options except when SDL 1.2 is enabled.\n\nDEVELOPERS\n\n+ Refactored render_layer_code to use C++ templates.\n+ render_layer now disables the (unused) 64-aligned renderers\n  for 32-bit platforms and Emscripten builds.\n+ Fixed buggy render_layer clipping that required clipping\n  renderers to unconditionally use a suboptimal alignment.\n+ Switched from xmp_load_module to xmp_load_module_from_file.\n+ Fixed various clang unused argument compiler warnings.\n+ Updated MSVC dirent.h implementation to latest version.\n+ Moved dir_* functions from util.c to dir.c.\n+ Added Windows UTF-8 compatibility layer to vfs.c and dir.c.\n  UTF-8 support for Windows is still limited.\n+ Moved Makefile compiler version checks to arch/compat.inc.\n+ Added cycles_and_commands.txt to build and install targets.\n\n>#MAIN.HLP:072:Table of Contents\n\nSeptember 23rd, 2019 - MZX 2.92b\n\nThis release fixes several bugs, notably 3DS and Wii rendering\nissues and a bug where the wrong palette would be saved while\nCOLOR FADE OUT was active. Also in this release are performance\nand usability improvements for the NDS and 3DS ports and a new\nfrontend for HTML5/Emscripten builds (special thanks to asie).\n\nUSERS\n\n+ Fixed a bug where mouse warping both mouse coordinates would\n  not actually warp both coordinates.\n+ Fixed a port regression where save files would be saved with\n  the wrong color intensities while COLOR FADE OUT was active\n  (or during the first cycle of a board for <2.90 worlds). This\n  fixes fading bugs in Ruin Diver 3.\n+ Added compatibility for a bug where the robot message box\n  would disable COLOR FADE OUT in versions 2.83 through 2.90X.\n  This fixes several BKZX games.\n+ Fixed checkres bugs separately preventing detection of global\n  robots in both world formats.\n+ Fixed 3DS renderer bug where the UI wouldn't properly display\n  char foregrounds with extended graphics enabled.\n+ Fixed 3DS renderer bug where sprites containing chars with\n  transparent foregrounds wouldn't draw any char backgrounds.\n+ Fixed 3DS renderer crashes that would occur when large numbers\n  of sprites were active.\n+ Improved file manager directory read times for 3DS/NDS/Wii.\n+ File IO for world saving/loading is now buffered, which should\n  improve save/load times on the 3DS and Switch.\n+ Enabled writing zip data descriptors for the Switch.\n+ Added classic/stretch video ratio support for the 3DS and NDS\n  lower screens. (asie)\n+ \"classic\" is now the default ratio for the 3DS, NDS, and Wii.\n+ Fixed GX renderer bug where chars with both colors transparent\n  could cause various graphical bugs in normal MZX mode.\n+ Fixed bug where board exits could be cleared when deleting a\n  board or after loading a world containing null boards.\n+ Fixed bugs when playing STARDSTM.GDM with the libxmp engine.\n+ Fixed a crash that could occur when importing a world in the\n  editor containing a null board.\n+ Fixed a bug where null boards wouldn't be properly refactored\n  on the NDS while using a memory cart, causing entrances to\n  point to the wrong boards and possibly other problems.\n+ Fixed a bug that could corrupt the ends of robot programs when\n  using an NDS memory cart.\n+ Improved world loading times when using an NDS memory cart.\n+ Improved NDS memory cart transfer speed. (asie)\n+ Disabled NDS logging hacks when stdio redirect is enabled.\n+ Exiting MZX on the NDS should now power off the NDS.\n+ Added Emscripten frontend. (asie)\n+ Fixed bug where errors in the title screen Load World window\n  could clear the rest of the screen.\n+ Fixed crash that could occur when attempting to load an\n  invalid zip world.\n+ Non-recursive directory deletion in the file manager now tries\n  to delete the directory itself instead of its contents. Added\n  error messages for failed file and directory deletes.\n\nDEVELOPERS\n\n+ Added initial support for the dirent d_type field to help\n  avoid stat calls when reading directories.\n+ Replaced most fseek calls in the legacy world loader.\n+ Refactored move_current_board to be more readable.\n+ Corrected some of the editor's bad extram handling and added\n  warnings to debug builds to help debug it (no platforms at the\n  present support both the editor and extram).\n+ Replaced error wait_event() with update_event_status_delay().\n+ Added preliminary VFS code to replace function pointer casting\n  in zip.c.\n+ Fixed Wayland/X11 detection for setups that don't allow X or\n  Xorg to be run normally.\n+ Unix builds should now attempt to load the icon from the\n  installed path instead of from a hardcoded path. Builds with\n  no sharedir set will use \"contrib/icons/quantump.png\" instead.\n\n>#MAIN.HLP:072:Table of Contents\n\nJuly 22nd, 2019 - MZX 2.92\n\nHere's another inexcusably big minor version release. First off,\nthere are two new ports (Switch and Emscripten) and the Android\nport has been updated to be actually usable. Second, joystick\nsupport has been significantly overhauled (and depending on the\ncontroller/joystick you plug in, it may work with no config\nsettings required!). Game joystick settings should no longer\noverride the new UI joystick behavior. Also, controllers can now\nbe used directly via Robotic. Read docs/joystick.html for more\ninformation on the new features.\n\nOther changes of note include RAD music support (v1 and v2),\npalette editor improvements, and some new counters. There's a\nnew SDL 2 exclusive renderer to replace overlay1/overlay2 called\n\"softscale\" that performs much better than either did and with\nfewer crashes. Also of note are the massive number of fixed bugs\nand compatibility improvements, fixing regressions and error\nmessages that affected around 20 different games (or more). A\nmajor bug was fixed that caused the updater to not initialize\nin Windows has been fixed too, so basically, update already.\n\nFEATURES\n\n+ Added numerous improvements to MZX's joystick handling:\n  + Joystick inputs can now be bound to generic actions. These\n    actions correspond to XInput buttons and can interact with\n    MegaZeux's various windows in a context-specific manner.\n    They can be used directly in Robotic with the new counter\n    \"joy#.[action]\" or can be bound to keycodes in the config\n    file to allow them to be used with older games. The NDS,\n    3DS, Wii, PSP, and GP2X pad.config files have been updated\n    to use actions. See the help file or config file for more\n    info on the config options and available actions.\n  + Added automatic joystick mapping for Windows/Mac/Linux/etc.\n    Joysticks recognized as SDL game controllers are assigned\n    joystick actions when detected. This behavior can be altered\n    or disabled via config options.\n  + New counter: \"joy#active\" will return 1 if a given joystick\n    is availabled, 0 if not, and -1 if the index is invalid.\n  + New counter: \"joy_simulate_keys\". When set to 1 (default),\n    joysticks can produce simulated key presses, as in older MZX\n    versions. When set to 0, this behavior is disabled.\n  + Joystick settings in game.cnf files are now loaded as game-\n    specific joystick bindings. These bindings will no longer\n    override global/UI bindings defined by the config file or\n    pad.config. The default bindings are restored when any world\n    is loaded from the title screen; they are not restored when\n    a save is loaded, between world swaps, or in the editor.\n  + Joystick config options can be assigned to key names in the\n    format of \"key_X\" (without quotes), where X is the key name.\n  + Joystick config options for a particular controller now work\n    with ranges of controllers e.g. \"joy[1,16].lshoulder = 13\".\n  + Added the config setting \"joy_axis_threshold\". This setting\n    allows the threshold for joystick axis mapped presses to be\n    configured.\n  + The NDS directional pad is now considered a joystick hat.\n  + The 3DS directional pad is now considered a joystick hat.\n    The circle pad is now mappable as axes 1 and 2.\n  + The Wii Classic Controller triggers are now recognized as\n    axes 7 and 8. The Guitar Hero axes have been shifted to 9,\n    10, and 11, and a likely bug with the whammy bar axis has\n    been fixed. The Gamecube controller triggers are recognized\n    as axes 5 and 6, and all 8 controllers now have default\n    bindings in pad.config.\n+ Added/updated Android port. (asie)\n+ Added Emscripten port. (asie)\n+ Added Nintendo Switch port. (Lachesis, asie)\n+ The main menu and game menu are now usable with keyboard,\n  mouse, or joystick. Pressing a navigation key/joystick button\n  or clicking an option in these menus will enable a cursor to\n  select one of the options. Most listed options are selectable\n  unless disabled by the current game, in which case they will\n  be hidden from the menu entirely. However, the settings and\n  exit options appear regardless of their respective counters.\n+ MegaZeux now supports Reality Adlib Tracker (RAD) music files.\n  Both 1.x and 2.x variants are fully supported.\n+ New counter: TIME_MILLIS. Returns the milliseconds of the\n  current system time.\n+ New counters: MOD_LOOPSTART and MOD_LOOPEND. These counters\n  return the start and end of the loop for the current playing\n  music and can be set to change the loop while the music is\n  playing. Setting MOD_LOOPEND to 0 disables the loop. These\n  counters only work for Ogg Vorbis and WAV audio (not modules).\n+ Added support for the LOOPEND tag for Ogg Vorbis files. This\n  tag can be used to define the end of the loop directly, which\n  may be more convenient for some users. This tag takes lower\n  precedence than LOOPLENGTH if both are present, and will be\n  ignored if its value is less than LOOPSTART.\n+ The SPR#_OFF counter is now readable through Robotic.\n+ New sprite counter: SPR#_Z. Sprites are displayed in order\n  of their z value. Sprites with a higher z value will appear in\n  front of sprites with a lower z value. This ordering takes\n  precedence over both Y ordering (if SPR_YORDER is enabled) and\n  sprite number. Removes the need to use SPR#_SWAP for managing\n  sprite draw order. (Lancer-X)\n+ New counters: FREAD_LENGTH and FWRITE_LENGTH. These counters\n  return the length of the FREAD file/directory (if open) or the\n  current length of the FWRITE file (if open).\n+ New counter: SET \"$string\" \"SAVE_ROBOT\". This does the same\n  thing as SET \"filename\" \"SAVE_ROBOT\", except it saves the\n  robot to the specified string.\n+ The default boot.dol for Wii builds with the editor enabled\n  is now MegaZeux instead of MZXRun.\n+ New GLSL scaling shader: \"sai\". This shader implements the\n  \"Scale 2xSaI\" algorithm by Kreed with a modified interpolation\n  function to produce sharper edges. This shader can scale up to\n  any size larger than 640x350 and is comparable to hqscale\n  (though it should look and perform a little better).\n+ Fullscreen mode will now attempt to match a supported screen\n  resolution if no \"fullscreen_resolution\" setting is specified.\n  For scaling renderers, this favors the current desktop\n  resolution. For \"software\", this favors smaller resolutions.\n+ Added the config setting \"fullscreen_windowed\". When set to 1,\n  MZX will create a borderless window with the current desktop\n  resolution instead of regular fullscreen. Disabled by default.\n+ Added the config setting \"grab_mouse\". When set to 1, MZX will\n  trap the mouse cursor within the window while the window is\n  active. Disabled by default.\n+ The current renderer can now be set from the settings menu.\n+ Added the 'softscale' renderer. This renderer is a generalized\n  rewrite of the former SDL 2 overlay renderer implementation.\n  It should perform significantly better and be more portable.\n+ Added several palette editor usability improvements:\n  + The current color stored with F2 is now displayed in the\n    bottom-right corner of the palette editor help menu.\n  + In SMZX mode 2 or 3 editing, the four colors of the current\n    subpalette can be stored with F5. Stored subpalette colors\n    can be placed at the current subpalette with F6. These are\n    stored independently of the stored current color. The help\n    menu color display will cycle through the stored colors.\n  + In SMZX mode 3 editing, the current subpalette's indices can\n    be stored with F7 and placed at the current subpalette with\n    F8. The stored indices are displayed in the help menu next\n    to the stored color display.\n  + The shortcut for toggling the subpalette cursors has been\n    changed to Insert. The subpalette cursors are now displayed\n    in SMZX mode 3 editing and are enabled by default.\n  + Palettes (and/or indices) can be imported into and exported\n    from the palette editor with Alt+I and Alt+X, respectively.\n  + Added a secondary editor-only palette which can be switched\n    to with Tab. This palette is not saved with any world, but\n    can be edited and imported to/exported from and can be used\n    to copy colors/subpalettes between different sources.\n+ checkres now displays the board, sfx, and robot (with line and\n  position on the board) each file was referenced in.\n+ checkres can now be used with a directory as the base file.\n  Like a ZIP archive, checkres will recursively search the\n  directory for .MZX/.MZB files and display information for all\n  of them.\n+ checkres has more display options: only display missing (-M)\n  (default), only display created (-C), only display found (-F),\n  also display missing (-m), also display created (-c), and also\n  display found (-f).\n+ checkres can now wildcard match resources from commands that\n  use expressions or interpolation in filenames. This can be\n  enabled with -w or enabled exclusively with -W.\n+ checkres can now display unused resources in a path/zip after\n  the other resources are listed (-u), or display only unused\n  resources (-U).\n+ checkres can now display every reference to a particular\n  resource in a world instead of just the first with varying\n  levels of information (-v, -vv). Added the option -s, which\n  is the default and equivalent to older versions of checkres,\n  and -1 as an alternate flag for -q.\n+ checkres can now sort its output either by the name of the\n  expected resource file (-N) (default) or by the location in\n  the world the file was referenced (-L).\n+ checkres option flags can now be combined (e.g. -aL). This\n  does not apply to the \"-extra\" and \"-in\" flags.\n+ New config options: \"test_mode\" and \"test_mode_start_board\".\n  If \"test_mode\" is set to 1, MegaZeux will start the given file\n  in testing mode, exactly as if the user used Alt+T on the\n  first board from the editor. Setting \"test_mode_start_board\"\n  to a valid board number in the world will start instead from\n  that board. Exiting testing will exit MegaZeux entirely.\n- SDL 2 support for the overlay renderers has been removed. When\n  specified for video_output, softscale will be enabled instead.\n- Removed WAV from the list of board mod extensions.\n\nFIXES\n\n+ SET \"$string\" TO \"fwrite\" will now always write the delimiter\n  regardless of the length of $string. Previously, this command\n  would only write a delimiter if $string was a length greater\n  than zero, requiring extra checks by the user to account for\n  this special case. Compatibility checks have been added for\n  for worlds made in older versions.\n+ Fixed bug where robots attempting to copy the player over\n  themselves with COPY x y x y or COPY dir dir would get stuck\n  in an infinite loop instead of executing the next command.\n+ SPR_YORDER and the sprite collision list are properly saved\n  and loaded from saves again.\n+ The SPR_NUM counter now has the full range of a regular\n  counter and is saved. Out-of-range values are ignored for the\n  purposes of its legacy IF and PUT uses. This only affects\n  2.92+ worlds. Additionally, 2.65 through 2.69c worlds treat\n  its range as -128 through 127, fixing a bug in Sprite Catcher.\n+ The active sprites count is properly updated when SPR#_OFF is\n  set and is properly loaded from saves again. This value can be\n  viewed from the variable debug screen now, though it may be\n  inaccurate for older saves.\n+ Fixed various bugs related to the robot position not being\n  internally tracked correctly. This could be caused by WALK,\n  SWITCH dir dir, OPEN, MOVE ALL, moving through a transport,\n  pushing, and rotation. This fixes issues with IF ALIGNEDROBOT,\n  THISX, THISY, THIS_COLOR, PLAYERDIST, HORIZPLD, VERTPLD, RID#,\n  ROBOT_ID_#, the robot's coordinates displayed in the counter\n  and robot debuggers, and MZM saving. Due to the number of\n  features affected by these bugs, the fix only affects worlds\n  saved in 2.92 or later.\n+ Fixed a regression where rotating could sometimes fail to\n  rotate the player, sensors, pushable robots, or transports.\n+ When a robot uses the OPEN command and is pushed through a\n  transport, MZX no longer crashes or creates invalid robots.\n+ Fixed crash that could sometimes occur when pressing Alt+Up\n  while drawing near the top of a board in the editor.\n+ Fixed bug where canceling 'Alt+C' char selection in the\n  robot editor would comment the current line.\n+ Fixed a bug where F5 in the robot editor would not print data\n  for extended chars.\n+ Clicking through board name windows and other interfaces with\n  the mouse should no longer place the current buffer.\n+ Fixed a bug where the updater could fail to initialize due to\n  a relative argv[0] in Windows. MegaZeux attempts to get the\n  executable path from Win32 first now.\n+ Improved warning message when no HTTP response is received.\n+ The software renderer no longer tries to use an SDL_Renderer\n  to draw the surface returned by SDL_GetWindowSurface. This\n  fixes a crash when switching to fullscreen with the software\n  renderer on some platforms.\n+ Fixed a software renderer mouse crash bug caused by moving the\n  cursor off the screen with a window bigger than the screen.\n+ Fixed a bug where setting SMZX_MESSAGE to 0 wouldn't always\n  draw the message correctly.\n+ If a referenced SAM or GDM file isn't present, MegaZeux will\n  attempt to load a corresponding WAV or S3M (respectively) with\n  the same filename minus the extension. This should fix some\n  music/sounds in older games that referenced SAM/GDM files but\n  were packed with the post-conversion WAV/S3M files only.\n+ The behavior for reseting the player movement repeat timers\n  has been reverted to requiring all four movement keys released\n  (as in 2.80 to 2.91i). The related player locking fix has not\n  been modified.\n+ IF \"&$string&\" and COPY BLOCK # # # # \"&$string&\" now properly\n  resolve &$string& before checking if the first operand or the\n  copy destination are a string (respectively), bringing their\n  behavior more in line with SET/INC/DEC and other commands.\n+ Fixed a regression where the COLOR INTENSITY commands would\n  update the screen if used the same cycle as TELEPORT PLAYER.\n  This fixes a graphical issue in Thanatos Insignia.\n+ Improved string wildcard comparison performance.\n+ Changed the debug window to display \"2.65/2.68\" for worlds\n  from MZX 2.65 and MZX 2.68. Features from 2.68 still check for\n  the intended MZX 2.68 magic; the very few existing 2.68 worlds\n  that use these features need to be resaved in 2.69.\n+ MegaZeux now attempts to fix Robotic that fails bytecode\n  validation. This resolves error messages and bugs in Eternal\n  Eclipse Taoyarin v1.0, Loco, Manuel the Manx, SaintZZT 7th,\n  Slave Pit, and Ep+Arp World MZX.\n+ The MOD SAM command has been restored for compatibility and\n  is version locked to <2.80 worlds. This fixes missing sound\n  effects in Crisis in Stuffyou City (original), Sprite Catcher,\n  and Talon's Tale. This only works for libxmp builds.\n+ Fixed a sprite collision bounding bug where the collision\n  height bounding for the reference checked using the collision\n  width instead. This fixes Manuel the Manx.\n+ Fixed several memory leaks in the editor related to editing,\n  placing, grab, and undo/redo operations on robots and scrolls.\n+ Fixed vlayer memory leak when loading 2.84 saves.\n+ Fixed a regression where the 'if c?? Sprite' legacy fix wasn't\n  locked to 2.69c and up. This fixes Depravity (2003 DoZ #666).\n+ PUT Sprite now works unconditionally for <2.80 games, fixing\n  bugs where games relied on placing 0-height sprites or placing\n  sprites before initializing their dimensions: Hackers Can Turn\n  Your Computer Into a Bomb!, Lethal Recurse (2003 DoZ #215),\n  Serum (2003 DoZ #404), Trashman Dan (Summer 2004 DoZ #18349).\n+ The LOCAL and LOCAL# counters are now versioned separately,\n  fixing bugs in Freedom Bound (Summer 2000 DoZ #4096) and\n  Impact (2001 DoZ #027).\n+ SHOOT now properly updates the blocked array. This fixes a bug\n  in Captain Proton and the Reality Rippers (1999 WEoZ #173).\n+ Fixed a bug where the custom SFX flag and max samples wouldn't\n  be reset when creating a new world in the editor.\n\nDEVELOPERS\n\n+ Fixed -Wunused-result warnings for Linux builds.\n+ Added \"Intel EMGD\" to auto_glsl blacklist (questionable\n  OpenGL 2.0 support).\n+ Added platform-independent joystick event functions and\n  updated the SDL, NDS, 3DS, and Wii event handlers for them.\n+ MinGW builds now use -Wl,-Bstatic and -Wl,-Bdynamic to enforce\n  the preferred linking when both static and dynamic builds of\n  libraries are present.\n+ For MSYS2 shells, config.sh now chooses the prefix /mingw32\n  or /mingw64 by default if $MSYSTEM is \"MINGW32\" or \"MINGW64\"\n  and either the \"win32\" or \"win64\" arch is selected.\n+ MegaZeux now uses the renderer free_video function on exit.\n+ GL_LoadLibrary no longer fails if the library is already\n  loaded in SDL 1.2. (Lancer-X)\n+ GL renderers free textures and revert to fixed function\n  pipeline where appropriate. (Lancer-X)\n+ OpenGL ES support can now be enabled for SDL builds with the\n  --enable-gles option. This option is force-enabled for\n  platforms that only support OpenGL ES.\n+ opengl1 renderer now always uses power of 2 textures, same as\n  opengl2 and glsl. This provides a tiny performance improvement\n  on most cards and a massive performance improvement on some\n  other cards. (Lancer-X)\n+ Added debug build sanitizer options for AddressSanitizer,\n  MemorySanitizer, and ThreadSanitizer. MemorySanitizer requires\n  clang and all currently require Linux/BSD/Mac OS X/similar.\n  Support for all three is very experimental; they may require\n  disabling options like modular builds or may not work at all.\n+ Moved several param functions from robot.c to run_robot.c.\n  Aside from a few exceptions, these functions are largely used\n  in run_robot.c only. This should improve Robotic performance\n  in release builds.\n+ Updated MSVC project for Visual Studio 2019. (elig, Spectere)\n+ Added experimental pledge(2) and unveil(2) support for OpenBSD\n  builds. By default, this is enabled for the utils but not for\n  MZX. Additionally, config.sh now detects OpenBSD properly and\n  several warnings have been fixed.\n+ Fixed faulty '!' rule logic in match_function_counter and\n  changed 'key?' to 'key!' to prevent potential false matches.\n- Removed uthash as a build option.\n\n>#NEWINVER.HLP:1st:NEW in MegaZeux!\n>#2901CLOG.HLP:291:New in Versions 2.90 to 2.91j\n>#284CLOG.HLP:284:New in Versions 2.84 to 2.84c\n>#2823CLOG.HLP:283:New in Versions 2.82 to 2.83\n>#281CLOG.HLP:281:New in Versions 2.81 to 2.81h\n>#280CLOG.HLP:280:New in Versions 2.80 to 2.80h\n>#OLDERVER.HLP:260:New in Versions 2.60 to 2.70\n>#OLDESVER.HLP:201:New in Versions 2.01 to 2.60\n>#NEWIN200.HLP:1st:New in Version 2.00\n>#ANCENVER.HLP:1xx:New in Versions Through 1.03\n>#MAIN.HLP:072:Table of Contents\n#2901CLOG.HLP\n:291:\n$~9New in Versions 2.90 to 2.91j\n\nFebruary 20th, 2019 - MZX 2.91j\n\nHere's a huge collection of bugfixes and a couple of features\nthat needed to be split off from 2.92.\n\nFeatures: every release has an HTML copy of the help file now,\nthe editor keeps separate board/overlay/vlayer buffers now, and\nanyone who still uses scrolls/sensors can view their info in the\ncounter debugger. The global volume settings use an exponential\ncurve now, range from 0 to 10, and have updated UI elements.\n\nFixes in this release include: the updater won't complain about\nmzx_help.fil anymore, the NDS and 3DS ports have keyboards that\nwork again, several bugs that would lead to worlds/saves with\n\"Robot # does not exist on board #\" errors have been fixed, and\nseveral crashes have been fixed.\n\nUSERS\n\n+ A complete HTML copy of the MegaZeux help file is now packaged\n  with releases for all platforms. It can be found in the docs/\n  folder.\n+ Nvidia and AMD switchable graphics drivers should now detect\n  MegaZeux and MZXRun properly on Windows.\n+ The editor now remembers the contents of the buffer when\n  switching between board, overlay, and vlayer editing. For\n  example, if a robot is selected on the board before switching\n  to overlay mode, the robot will still be in the buffer when\n  the user switches back to board mode.\n+ The editor now remembers the last filename for importing and\n  exporting MZM, MZB, CHR, PAL, and PALIDX files.\n+ Scrolls and sensors can now be viewed in the counter debugger.\n  They can be found by expanding the Board list (when present).\n+ Readded \"#version 110\" directives to all GLSL shaders, fixing\n  warnings on some Intel HD drivers. OpenGL ES builds should now\n  comment out these directives.\n+ Fixed bug where using Modify+Grab on built-ins could clear the\n  floor under the built-in.\n+ Fixed bug where key repeat wouldn't work for any text field in\n  the NDS/3DS/Wii ports. This also affected the robot editor in\n  Wii builds.\n+ Fixed NDS touch screen support. Touching the screen no longer\n  acts like escape and the keyboard properly works again.\n+ Fixed a bug where the NDS keyboard would drop keypresses on\n  games not running at speed 2.\n+ The counter debugger now correctly displays the value of\n  local0.\n+ Fixed a bug where the counter debugger would not reopen to\n  the correct robot local# value.\n+ MZX now attempts to retry removal of an old file when updating\n  if deletion fails initially. Error messages related to the\n  failed deletion of \"mzx_help.fil\" or \"assets/help.fil\" are now\n  suppressed as they are caused by a bug in older MZX versions.\n+ The help file is now closed before the updater restarts MZX.\n+ Fixed crash bug that could occur when removing old files after\n  an update.\n+ Improved manifest validity checks.\n+ Fixed board editor crash caused by invalid thing IDs.\n+ Player movement key repeat no longer resets when the player is\n  locked. It will reset for a direction only when the arrow key\n  corresponding to that direction is released. This fixes a bug\n  in Brotherhood where the player would move at a fraction of\n  the speed they were supposed to while swimming.\n+ Added version checking for legacy IF c?? Sprite behavior. In\n  versions 2.82b and prior, c?? would make this command ignore\n  the provided param and use SPR_NUM instead. This fixes Project\n  MoveZig.\n+ Fixed spelling error in missile param dialog.\n+ Switching boards in the editor now properly translates the new\n  board's mod name, fixing cases where board mods would restart\n  sometimes.\n+ Fixed a bug where the current board mod wouldn't play after\n  starting a listening mod, testing, returning to the editor,\n  and then disabling the listening mod.\n+ Replaced the linear volume setting curve with an exponential\n  curve to make lower volumes less loud. The volume settings can\n  now be set from 0 to 10 instead of from 1 to 8.\n+ The config file setting pc_speaker_on=0 no longer prevents\n  turning on PC speaker SFX at runtime.\n+ Added temporary workaround for numpad 5 key detection when\n  numlock is disabled.\n+ \"Forest to floor\" and \"Collect bombs\" are properly enabled\n  again for new boards by default. This fix doesn't alter config\n  settings or saved .editor.cnf defaults.\n+ The \"Downver. world (MZX)\" option in the \"Export as\" dialog\n  is now completely highlighted when selected. (Lancer-X)\n+ Starting MegaZeux with \"startup_editor=1\" no longer loads the\n  default palette over the startup world's palette.\n+ The move block action and undo/redo operations should now\n  preserve the element beneath the player.\n+ Fixed crashes caused by using block actions or COPY BLOCK on\n  the player while the player is standing on a sensor.\n+ Fixed several bugs where editor block actions and the commands\n  COPY/COPY BLOCK would not check for the player or clean up\n  robots/scrolls/sensors. Worlds/saves saved after these bugs\n  occured could display \"Robot # does not exist on board\"\n  errors when loaded.\n+ The DUPLICATE SELF x y and DUPLICATE SELF dir commands now\n  properly check for the player before attempting to duplicate\n  a robot. This fixes a bug where MZX could create saves with\n  \"Robot # does not exist on board\" errors.\n+ Fixed a crash that could occur when importing a new board.\n+ Fixed a bug where the 3DS onscreen keyboard would send key\n  releases instead of key presses.\n+ The 3DS onscreen keyboard now returns shifted chars when shift\n  is active.\n+ Fixed a bug where the libxmp implementation of MOD_ORDER and\n  JUMP MOD ORDER could not restart the current order.\n+ Fixed a crash with the libxmp implementation of MOD_POSITION\n  when providing an out-of-bounds value.\n+ Fixed a bug where Ctrl+[key] shortcuts would not work while a\n  dialog list element was active on the Wii.\n+ Fixed a bug where setting the board mod/charset/palette could\n  truncate the first letter of the path on the Wii.\n+ \"mask_midchars\" no longer masks the non-text char 127.\n+ The message box title and the command [ now properly display\n  the graphic for char 9 instead of a tab.\n+ Added newline escaping to robot, counter, and string names in\n  the counter debugger to work around formatting bugs.\n\nDEVELOPERS\n\n+ Added the hlp2html utility, which can be used to convert\n  WIPHelp.txt into web and printable HTML files.\n+ Updated to latest NDS ARM7 template.\n+ Updated SDL2 for Windows release builds to SDL 2.0.9.\n\n>#MAIN.HLP:072:Table of Contents\n\nDecember 9th, 2018 - MZX 2.91i\n\nA small release with a bunch of fixes for bugs introduced mostly\nwithin the past few versions. Other highlights: joysticks should\nbe hot pluggable now and Alt shortcuts now also work with the\ncommand key in Mac builds.\n\nUSERS\n\n+ MZX now properly detects when joysticks are connected and\n  disconnected at runtime for platforms using SDL 2.\n+ Fixed bug where the robot editor would open with the cursor at\n  the end of the line instead of the start of the line.\n+ Fixed a bug where the robot box could display using MZX mode\n  but SMZX colors if opened on the first frame SMZX was enabled.\n+ The built-in text box title should display correctly again in\n  SMZX modes.\n+ Fixed regression where keys could trigger wrong \"keyN\" labels.\n+ Alt+F2 can now be used to open the settings dialog.\n+ Mac users can now optionally use command in place of alt.\n+ Improved editor cursor color selection for SMZX modes 2 and 3.\n\nDEVELOPERS\n\n+ Fixed make test for SDL 1.2 builds.\n\n>#MAIN.HLP:072:Table of Contents\n\nNovember 14th, 2018 - MZX 2.91h\n\nA minor release primarily focused on improving counter/string\nlookups and fixing bugs in the robot editor and counter debug\nmenu.\n\nUSERS\n\n+ auto_glsl correctly switches to glsl on startup now.\n+ Fix bug where \"(editor)\" wouldn't always display in the\n  caption when editing.\n+ The single line macro dialog now allows the full size of the\n  single line macros to be edited. This fixes a crash caused by\n  having a macro configured to be longer than the edit box could\n  display.\n+ Setting a counter to a string no longer duplicates the entire\n  string in memory.\n+ Fixed faulty BOARD_X and BOARD_Y bounding for values greater\n  than the board width or height. Fixes potential crashes with\n  BOARD_CHAR, BOARD_COLOR, BOARD_ID, and BOARD_PARAM.\n+ The value of SPR_YORDER is now correctly displayed in the\n  counter debug menu.\n+ Improved the performance of the counter debug search.\n+ Fixed cosmetic counter debug menu issue where the the \"String\"\n  tree appeared to be expandable/collapsable when empty.\n+ Fixed faulty substring searching algorithm used in the counter\n  debug menu search and for breakpoints.\n+ Added \"Search\" and \"Cancel\" buttons to the counter debug menu\n  search so it can be used with the mouse.\n+ When testing from the editor, if \"__test.mzx\" exists MegaZeux\n  will now choose a numbered name for this world backup (e.g.\n  \"__test2.mzx\") instead of potentially overwriting work.\n+ Fixed bug where six-digit line numbers would display in the\n  robot editor incorrectly.\n+ Inserting text in the robot editor using the F2/F3/F4/F5\n  shortcuts can no longer exceed the line length limit, fixing\n  related crashes.\n+ Using F5 in the robot editor generates decimal numbers instead\n  of hex numbers now.\n+ Fixed robot editor bug where pressing delete at the end of a\n  line wouldn't join lines unless the current line was empty.\n+ Restored the ability to use the F7/F8 cheats outside of the\n  editor. This feature was broken in 2.91g. To enable cheats\n  outside of the editor, the config option \"allow_cheats\" must\n  now be set to \"mzxrun\" or 1. The former will enable them for\n  MZXRun only (same as from 2.82b to 2.91f) and the latter will\n  enable them for both executables (same as the -t flag in DOS\n  versions).\n+ Fixed bounding bug on the \"board_default_width\" and\n  \"board_default_height\" config file options.\n+ Separated robot editor \"Search and Replace\" dialog into a\n  \"Search\" dialog and a \"Replace\" dialog. Both dialogs are\n  smaller in size than the original. The order of the elements\n  in these dialogs has also been altered to be more user-\n  friendly. The hotkey for the new replace dialog is Ctrl+H.\n+ Fixed Ctrl+R repeating for the \"Replace All\" robot editor\n  search operation. Previously, it would act like \"Replace\".\n+ Canceling the robot editor search/replace menus no longer\n  disables the repeat feature.\n+ Searching in the robot editor now properly preserves the case\n  of the search string if case sensitive search is disabled.\n+ Fixed bug where toggling the robot debugger position wouldn't\n  take effect immediately.\n+ Exiting the editor with the \"startup_editor\" config setting\n  enabled should no longer inappropriately close MZX.\n+ Selecting \"tile\" for the char editor import mode works again.\n+ The bounds for the main editor charset import/export offsets\n  and size have been increased to allow for extended charset\n  values.\n+ Fixed the \"KEY?\" labels for keys that no longer have a unicode\n  representation in SDL2.\n+ Fixed SET \"$string\" \"FWRITE#\" bug where, if # exceeded the\n  length of $string, the output would be clipped to the wrong\n  length.\n+ The missile color ID char is now correctly set back to its\n  default value when a new world is created from the editor.\n\nDEVELOPERS\n\n+ Cleaned up the counter debug menu code.\n+ Switched from uthash to a modified version of khash for\n  counter and string lookups. Counters/strings now consume less\n  memory and counter/string lookups should perform slightly\n  better on most platforms.\n+ Replaced toupper/tolower in memcasecmp and substring searching\n  with a lookup table-based implementation of tolower.\n+ Refactored the following contexts to use the main loop: robot\n  editor, intake, thing menu. For compatibility purposes the old\n  intake still exists, but all robot editor hacks have been\nremoved from it.\n  \n>#MAIN.HLP:072:Table of Contents\n\nOctober 7th, 2018 - MZX 2.91g\n\nThis bugfix release fixes various minor-to-moderate bugs in\nvarious areas of MegaZeux. The title/game update code has gone\nthrough a fairly major overhaul and several parts of MegaZeux\nhave been combined to use the same loop. The conversion of\nMegaZeux's interfaces to be compatible with this loop is an\nongoing process expected to occur over several versions.\n\nUSERS\n\n+ Added \"allow_screenshots\" config file option. Setting this\n  option to 0 will disable the built-in screenshot feature.\n+ The F12 key can now be detected by Robotic.\n+ Loading saves from the title screen no longer resets TIME.\n+ Loading saves from the title screen no longer erroneously\n  changes the player restart position.\n+ Swapping worlds now sets the correct TIME and player restart\n  position values.\n+ Fixed a bug where the fade out effect from COLOR FADE OUT\n  would be reset after any Robotic dialog.\n+ Undo for overlay and vlayer mouse drawing now works properly.\n+ Mouse drawing in the editor now draws smooth lines instead of\n  lines with gaps.\n+ Fixed move block bug when the source and destination both\n  overlapped the player.\n+ Palette editor component entry now accepts most of the same\n  inputs as dialog number boxes.\n+ The board name in the caption now updates to reflect the\n  current board while testing.\n+ Fixed bug where TIME would either decrement normally or not\n  decrement at all while the slow time effect was active.\n+ Added compatibility for pre-port endgame teleport behavior.\n  In DOS versions of MZX, the endgame teleport would disable\n  itself after being triggered for the first time and any\n  following endgame would instead trigger a game over.\n+ Reduced stutter in NDS and 3DS main screen scrolling.\n+ Fixed a bug where creating a string MZM would not correctly\n  set the string's length for preexisting strings.\n+ Fixed a bug where loading robot source code from a string\n  wouldn't work correctly with the robot debugger.\n+ The help system is now accessible from the main menu and game\n  menu.\n+ The settings screen is now accessible from the main menu, game\n  menu, editor, and palette editor.\n+ The shortcut Ctrl+F2 can be used to open the settings screen\n  from anywhere the settings screen is accessible. This shortcut\n  works even if F2 is used for a different feature and will also\n  ignore the value of the F2_MENU counter.\n+ Fix a crash that would occur decrypting a world with a\n  password exactly 15 chars long.\n+ The label for the INPUT STRING command should no longer be\n  able to overflow outside of the window.\n+ Fix a crash that would occur attempting to open the help file\n  from the world decryption confirm dialog.\n+ Fix bug where saved layer MZMs and board MZMs without robots\n  would have useless extra data at the end of the file.\n+ Scroll contents properly display game colors during gameplay.\n+ An error message is now displayed for failed board exports.\n+ Enabled writing zip data descriptors on the 3DS to decrease\n  saving time.\n- The \"password protected\" error message has been removed as it\n  was redundant with the confirmation dialog following it.\n- The \"disassemble_extras\" and \"disassemble_base\" options no\n  longer affect Robotic output during gameplay. SAVE_ROBOT will\n  always output extra words and base 10 numbers regardless of\n  user configuration.\n\nDEVELOPERS\n\n+ Implemented a main event loop to replace the various separate\n  event loops scattered around MegaZeux. See core.h and core.c\n  for more info.\n+ Refactored the following contexts to use the main loop:\n  titlescreen, gameplay, main menu, game menu, editor, palette\n  editor.\n+ When enabled, the debug FPS display will now update from any\n  interface using the main loop. If fullscreen mode is active,\n  the FPS display will now appear in the top-left corner of the\n  screen for any main loop interface aside from the editor.\n+ The --disable-screenshots config.sh option can be used to\n  disable screenshot support. This allows platforms that don't\n  use render_layer.c to stop building and linking it.\n+ Cleaned up m_show()/m_hide() overuse.\n+ Replaced all uses of \"bool\" with \"boolean\" to avoid potential\n  C/C++ compatibility issues.\n- Removed most old/unused source code in contrib/unzip, src/old,\nand src/vfs.\n\n>#MAIN.HLP:072:Table of Contents\n\nSeptember 17th, 2018 - MZX 2.91f\n\nA bugfix release focusing primarily on renderers, the 3DS and\nWii ports, and the Makefile build system. Highlights: the 3DS\nport should perform better now, the Wii port's GX renderer has\nbeen restored, the 'darwin' platform has more Unix-like options\nnow, and several crashes have been fixed.\n\nUSERS\n\n+ The config setting 'audio_buffer' can also be specified with\n  'audio_buffer_samples' now. The default value for this setting\n  is now 1024 (instead of 4096).\n+ Fixed a crash that could occur in the counter debugger.\n+ Fixed a crash that occurs when attempting to add a watchpoint\n  for robot local counters, loopcount, etc. Using these counters\n  as watchpoints will watch the global robot's instances of\n  these variables (for other robots, use e.g. r#.local1).\n+ Using SMZX_INDICES or LOAD_ROBOT on an unset string no longer\n  causes a crash.\n+ png2smzx is now properly bundled with Linux builds.\n+ Improved the performance of loading partial charsets for the\n  glsl, opengl2, and 3DS renderers. Other renderers were not\n  affected by this issue.\n+ Fix opengl2 unbound sprite regression introduced in 2.91e.\n+ The opengl2 renderer now correctly draws unbound sprites\n  containing chars with a transparent foreground color.\n+ 3DS renderer optimizations. (asie)\n+ 3DS screen focusing now mimics the NDS port's behavior. (asie)\n+ Fixed bug where games could fail to open files on some\n  platforms if the paths contained duplicate slashes. (asie)\n+ Added a Wii software layer renderer. This renderer can be\n  selected with \"video_output = xfb\" in the config file.\n+ Wii GX renderer optimizations.\n+ Added layer rendering support to the GX renderer.\n+ The Wii GX renderer now uses the gl_vsync config option to\n  toggle vsync. For the Wii, this feature is on by default.\n  Disabling it may increase the framerate of some games but may\n  also cause other problems.\n+ The loading bar on console platforms now redraws the screen\n  less often.\n\nDEVELOPERS\n\n+ GAMESDIR/BINDIR/SHAREDIR now properly apply PREFIX when they\n  are not explicitly set by config.sh.\n+ LIBDIR is now user-definable like BINDIR et al. Variations\n  such as \"lib64\" currently must be explicitly provided for\n  platforms that still use them.\n+ Remove src folder from CFLAGS to fix a bug where system\n  includes could be mistaken for MegaZeux headers. All includes\n  of MegaZeux headers must be done using relative paths now.\n+ Fixed include bugs for X11 and Carbon clipboard handlers.\n+ Makefile now attempts to respect the prefix when running\n  sdl-config, sdl2-config, and libpng-config.\n+ Renamed the \"darwin\" config.sh platform to \"darwin-dist\".\n+ Added \"darwin\" and \"darwin-devel\" config.sh platforms. These\n  act like Mac OS X versions of the \"unix\" and \"unix-devel\"\n  options.\n+ The darwin-dist build system and instructions are now somewhat\n  more clear and robust.\n+ Fixed a bug where zlib wouldn't necessarily be linked to\n  png2smzx.\n+ Renderers will now use render_layer to draw the text_video\n  fallback if no render_graph function is present.\n\n>#MAIN.HLP:072:Table of Contents\n\nSeptember 3rd, 2018 - MZX 2.91e\n\nBugfix release fixing some crashes, port bugs, and some other\nsignificant issues affecting a variety of things.\n\nUSERS\n\n+ Fixed bug where loading a board charset would also clear all\n  of the extended charsets.\n+ Fixed bug where loading a default charset in the editor would\n  also clear all of the extended charsets.\n+ Loading a 2.91 world when a renderer with no layer support is\n  active no longer triggers an error message.\n+ Fixed a memory corruption bug in the non-SDL Wii port caused\n  by faulty threading code.\n+ Fixed crash that could occur when updating a board undo frame.\n+ Fixed crash when changing to a board with overlay disabled in\n  overlay editing mode.\n+ Fixed a bug where the protected charset could get cleared for\n  renderers without layer rendering support.\n+ Enabled C99-compliant stdio functions for mingw, fixing at\n  least one crash bug and possibly improving performance.\n+ The NDS port now attempts to detect argv[0].\n+ The 3DS port now attempts to detect argv[0] and otherwise will\n  start in /3ds/megazeux instead of /.\n+ The 3DS port .3dsx file is now located in /3ds/megazeux.\n+ The MOVE PLAYER [dir] and MOVE PLAYER [dir] [label] commands\n  now update the commands cycle and commands total values.\n+ Fixed a bug where MZX and checkres wouldn't accept some\n  DEFLATE-related ZIP flags.\n+ checkres no longer crashes on failing to open a resource zip.\n+ The window caption now updates correctly when using either of\n  the overlay renderers.\n+ Update checking will now display an error instead of silently\n  hiding the updater when the updater fails to initialize.\n+ Fixed an issue where UIs would execute redundant frames.\n+ Fixed a bug where string comparison would not order strings of\n  different lengths correctly. (GreaseMonkey)\n\nDEVELOPERS\n\n+ Code style has been cleaned up in numerous files.\n+ Moved GLSL shaders from assets/shaders/ to assets/glsl/.\n+ Added missing GPL headers to GLSL shaders and added slightly\n  better documentation to them.\n\n>#MAIN.HLP:072:Table of Contents\n\nJuly 12th, 2018 - MZX 2.91d\n\nThis is a minor bugfix release to get a handful of fixes out\nprimarily concerning the updater. The NDS/3DS/PSP/Wii ports now\ninclude a copy of Caverns of Zeux.\n\nUSERS\n\n+ Added timeouts to updater network operations.\n+ Fixed bug where the updater would retry downloads without\n  regard to the status code.\n+ The updater is now force-disabled for all non-Windows\n  platforms in config.sh.\n+ Fixed bug where breakpoints could trigger on the wrong line.\n+ Amended VOLUME/MOD FADE # # bounding to clamp values in port\n  versions. DOS versions will still wrap between 0 and 255.\n+ Added DOS compatibility fix for sprN_off.\n+ Window caption is now properly updated when loading a world\n  from an unedited world in the editor.\n+ Fixed bug where \"..\" would change to the current directory if\n  the current path ended in a path separator.\n- Removed undocumented support for \"title.cnf\", \"game.cnf\", and\n  \"editor.cnf\" config files, which would have been loaded from\n  the same directory as the main config (not to be confused with\n  world-specific config files, which are still supported).\n\nDEVELOPERS\n\n+ The MSYS2 buildscripts have been overhauled to work with the\n  new devkitPro pacman repositories.\n+ Added config options to select vorbis, tremor, tremor-lowmem,\n  or to disable ogg vorbis file support. The 3DS and Wii ports\n  now use tremor instead of tremor-lowmem.\n\n>#MAIN.HLP:072:Table of Contents\n\nMarch 4th, 2018 - MZX 2.91c\n\nThis release contains an assortment of bugfixes ranging from\nplayer clone fixes to obscure compatibility patches. A handful\nof updater issues have been fixed (or at least addressed).\n\nMegaZeux can now automatically check for updates on startup on\nWindows platforms. This behavior can be configured, and by\ndefault will simply leave a message in the window caption.\n\nUSERS\n\n+ MegaZeux can now automatically run the updater on startup\n  on applicable platforms. This can be configured with the\n  'update_auto_check' config file option.\n+ Fixed a bug where several .MOD variants stopped working with\n  libxmp enabled.\n+ Sound effects in subdirectories can now be used in the SFX\n  editor.\n+ Fixed a bug where checkres would fail on legacy worlds with\n  very long custom SFX.\n+ Checkres no longer ignores the SFX tables of 2.90+ worlds.\n+ Fixed a bug where interpolated expressions containing ternary\n  operators could terminate counter names early.\n+ Fixed faulty IF ANY compatibility behavior.\n+ Fixed a bug where FWRITE functions could change the case of\n  user-defined filenames when creating new files on non-Windows\n  platforms.\n+ SMZX mode 1 palettes are now exported correctly as 16 colors\n  instead of as 256 colors.\n+ Fixed subtly inconsistent timing in certain built-ins.\n+ Fixed bug where wind and transporters could clone the player.\n+ MegaZeux now restarts correctly in paths containing spaces\n  when the updater is run.\n+ Subroutines in pre-port MZX worlds now always return to the\n  command after the subroutine was sent.\n+ Typed color components now wrap from the maximum value to zero\n  like dialog number inputs, and pressing minus will now negate\n  the input number.\n+ Copying chars in the char editor between regular and SMZX\n  screen modes now works properly.\n+ Fixed a crash that could occur when placing things on a vlayer\n  larger than the current board.\n+ Sharks and SpittingTigers with firing rates greater than 4 now\n  retain their firing rate when edited.\n+ Fixed a bug where world decryption could crash. (Revvy)\n+ If the glsl renderer is selected by default, the detection of\n  a software opengl driver will result in falling back to\n  software. (Lancer-X)\n+ Fixed a bug in the GLSL renderer where an additional window is\n  created if the OpenGL version is low enough to cause a\n  fallback to software. (Lancer-X)\n\nDEVELOPERS\n\n+ Added .MZX-based unit testing system. Run with \"make test\".\n  See testworlds/README.md for more information.\n+ The default renderer is now auto_glsl, which turns into glsl\n  if not using a blacklisted opengl driver. (Lancer-X)\n\n>#MAIN.HLP:072:Table of Contents\n\nJanuary 6, 2018 - MZX 2.91b\n\nHere's a fairly significant update for MegaZeux 2.91, fixing\nseveral major Robotic, audio, and editor related bugs, as well\nas issues that mostly affected Linux platforms. Also of note is\nthe updated MSVC and Xcode support by Spectere.\n\nUSERS\n\n+ Added the config file option \"editor_thing_menu_places\". When\n  set to 0, selecting an object from the thing menus (F3-F10)\n  will place the object in the buffer but not on the board.\n  Defaults to 1.\n+ Fixed a bug where MZMs could sometimes fail to load in the\n  editor.\n+ MZMs loaded in the editor now behave correctly when undone.\n+ Fixed bug where GOOP_WALK wouldn't always be 0 when loading\n  old worlds.\n+ Values of GOOP_WALK greater than 1 no longer cause issues.\n+ Board time limits greater than 255 are no longer truncated\n  when saving worlds/saves.\n+ Fixed bug where \"robot not found\" errors could result from\n  using backspace on robots in the editor.\n+ Fixed backwards string inequality evaluation.\n+ String inequality compares are now endian-safe.\n+ Fixed a bug where certain mods (e.g. FR_TOWER.MOD, Neve.s3m)\n  could repeat to the wrong order after reaching the end.\n+ Fixed a bug where certain .it channels could partially ignore\n  the volume command.\n+ Setting mod_position in libxmp now works for positions in the\n  middle of orders (NoSuck).\n\nDEVELOPERS\n\n+ Updated MSVC for Visual Studio 2017. (Spectere)\n+ Added xcode support. (Spectere)\n+ \"make clean\" now deletes ccv. (pgimeno)\n+ Fixed creeping CRLF usage. (Lachesis, pgimeno, Spectere)\n+ Fixed a bug where the MSYS2 build scripts could inadvertently\ninclude non-portable SDL2 binaries in Windows builds.\n\n>#MAIN.HLP:072:Table of Contents\n\nNovember 22, 2017 - MZX 2.91\n\nThis release introduces mostly new editor features, but also a\na few Robotic features to note. It also fixes over 30 bugs.\n\nMegaZeux now features a palette editor for SMZX modes 2 and 3,\nand worlds can be saved and loaded in SMZX mode. The char editor\nhas been changed to allow preview palettes (mostly to aid with\nSMZX editing), and can now access the extended character sets.\nThe new vlayer editor allows the vlayer to be edited directly.\n\nOn the Robotic side of things, LOAD_COUNTERS actually works now,\nSMZX mode 3 indices can be loaded from a file, the message line\ncan be configured to use normal MZX mode in SMZX modes, and new\nstring features such as simple wildcard matching and negative\noffsets have been added. String support has been improved to\ndisallow invalid or nonsense string splices that previously had\nundefined behavior.\n\nAdditionally, asie's new Nintendo 3DS port has been merged into\nMZX, and optional SDL support has been added for the Wii.\n\nFEATURES\n\n+ Added SMZX_INDICES special counter to load SMZX indices. This\n  counter works with either a filename or a string, e.g.\n  set \"file.palidx\" \"SMZX_INDICES\", and will do nothing outside\n  of mode 3.\n+ Added MOD_LENGTH counter. The value of this counter is length\n  of the current playing music in rows for modules, or in\n  samples for PCM audio.\n+ Added MAX_SAMPLES counter. When set, MegaZeux will limit the\n  maximum number of samples that will play simultaneously. Set\n  to -1 to disable the sample limit.\n+ Added 'max_simultaneous_samples' config file option. This\n  behaves the same as the MAX_SAMPLES counter, but applies\n  globally.\n+ Added SMZX_MESSAGE counter. When set to 0 with SMZX active,\n  messages will display in normal MZX mode instead of SMZX\n  using the first 16 colors of the SMZX palette.\n+ Added 'random_seed#' counters to read and write the random\n  seed, 32 bits at a time (random_seed0 controls the low 32 bits\n  and random_seed1 controls the high 32 bits. (Lancer-X)\n+ Added a case-sensitive string equality operator:\n  IF \"$string\" === \"ABC\" then \"label\".\n+ Added wildcard-matching string equality operators:\n  IF \"$string\" ?= \"a?%\" then \"label\"\n  IF \"$string\" ?== \"a?%\" then \"label\" (case-sensitive).\n  The character '?' will match exactly one of any character in\n  $string, and '%' will match any number of any character in\n  $string (including no characters).\n+ Added negative indexing for strings, e.g. SET \"$string.-X\" 32.\n  This manipulates the string at the Xth character starting from\n  the end of the string (with -1 for the final character).\n+ Added negative offsets for strings, e.g. SET \"$string+-X\" \"a\".\n+ Added multiple character indexing for strings. When a length\n  is provided with a string index, e.g. SET \"$string.X#Y\" 12345,\n  the characters at positions X, X+1, ..., X+Y-1 will be treated\n  as a single 8*Y bit number. This works for values of Y between\n  1 and 4, with 4 characters providing the same functionality as\n  a counter.\n+ The palette editor and character editor are now accessible\n  while testing. Press Alt+E or Alt+C respectively to access\n  them from the counter debugger.\n+ Extended the palette editor to SMZX modes 2 and 3. The updated\n  palette editor allows the editing of all 256 SMZX mode 2/3\n  colors and editing color indices for SMZX 3.\n+ Component numbers can now be typed in the palette editor by\n  clicking the component name or its number, e.g. clicking \"Red\"\n  allows you to type a red value.\n+ SMZX indices can now be imported/exported while editing in\n  mode 3 using import/export palette.\n+ Added a new vlayer editor. In the world editor, press Alt+V to\n  switch to the vlayer editor. The vlayer is something like an\n  invisible global overlay that can be used to store and\n  retrieve graphical data through Robotic. See the editor help\n  and the Robotic reference manual for more details.\n+ Added color selection to the character editor. Press C to\n  choose a color to preview and edit chars with, and press Alt+C\n  to revert back to the default grey.\n+ Added extended charset support to the char editor. Extended\n  charsets can be selected during char selection. Note that in\n  gameplay these chars can be accessed only by unbound sprites.\n+ Selected blocks of chars with a height greater than one\n  (before selecting a subdivision) are now properly supported.\n  Using -/+ with these selections will move in a tiled manner,\n  with no overlap between chars in \"tiles\". Charsets can also be\n  exported/imported using this tiling behavior.\n+ The -/+ keys now behave the same in the char selection screen\n  as they do in the char editor.\n+ The character editor help now covers several shortcuts that\n  were previously missing (Alt+B, Shift+Arrows, etc...).\n+ The undo/redo shortcuts in the character editor are now Ctrl+Z\n  for undo and Ctrl+Y for redo.\n+ Added undo/redo functionality to the world editor. Use Ctrl+Z\n  to undo changes to the board/overlay/vlayer and Ctrl+Y to\n  redo.\n+ The default editor undo history stack size has been extended\n  to 100 levels.\n+ The editor will now prompt the user to create a new starting\n  board when opened. If a new board is created, the global first\n  board will be set to the new board and the title board will be\n  renamed.\n+ Saved positions now have confirmation dialogs. Saved positions\n  are saved to/loaded from the editor.cnf file for each world.\n+ Pressing Enter/Return on the overlay (and vlayer) now acts the\n  same as on the board, changing both the buffer and the current\n  layer being edited.\n+ Pressing P on the overlay (and vlayer) now acts the same as on\n  the board, changing the character in the buffer but NOT on the\n  current layer. This is equivalent to the original overlay\n  behavior for Enter.\n+ Block tiling movement (Ctrl+Arrows) now works with most block\n  actions, and does not require an initial block placement to\n  activate.\n+ Restoring its DOS functionality, Alt+D now toggles the default\n  colors of built-in types in the editor. When disabled,\n  built-ins placed from thing menus will use the buffer color\n  instead of their default colors, and a red dot will appear on\n  the right side of the status bar.\n+ Color selection now supports typing in the hex value of a\n  color/subpalette to select the given color/subpalette. For\n  example, typing \"4c\" will select background color 4 and\n  foreground color 12.\n+ Readded the downver utility.\n- Removed the undocumented Shift+F7 shortcut in the editor. This\n  shortcut was redundant with F11.\n\nFIXES\n\n+ Fixed a bug that would cause Linux binaries to fail to find\n  most resources.\n+ Fixed a bug where loading a save from the titlescreen, exiting\n  gameplay to a world that doesn't exist, and then loading a\n  second save would corrupt the counter and string hash tables.\n+ Fixed crash that would occur when loading a saved position on\n  the same board referring to out-of-bounds coordinates.\n+ Fixed a freeze that could occur opening the counter debugger\n  to an empty list or searching for a counter in an empty list.\n+ LOAD_COUNTERS now works as intended.\n+ Fixed a bug where key repeat would be prematurely terminated\n  when releasing a key while holding another key.\n+ Attempting to open the robot validator with no errors will no\n  longer crash MegaZeux.\n+ Backspace now mirrors delete in overlay mode instead of\n  affecting the board.\n+ Moving an overlay block partially out of bounds in the editor\n  will now correctly clear the block's original position.\n+ The color selector will now correctly display SMZX palettes in\n  SMZX mode instead of the default char 254.\n+ Fixed bug where the mouse cursor would vanish after using the\n  robot debugger configuration dialog in the editor.\n+ Fixed a bug where the counter debugger could sometimes display\n  behind the robot debugger after exiting the counter debugger.\n+ Undo in the char editor now works correctly with the mouse.\n+ The char editor no longer forces the screen to SMZX mode 1\n  when editing in SMZX modes 2 or 3.\n+ Fixed screen corruption bug when resizing the char editor in\n  SMZX modes.\n+ Fixed a bug where multichar editing wouldn't correctly wrap to\n  the start of the charset.\n+ The editing area outside of the current board is now correctly\n  drawn when SMZX is enabled.\n+ The viewport is now correctly drawn when SMZX is enabled and\n  the UI or unbound sprites are active.\n+ The editor bar no longer appears while SMZX is enabled in view\n  mode.\n+ Messages are now drawn correctly in SMZX mode.\n+ Fixed bug where centering the viewport could change the width\n  and height of the viewport.\n+ Unbound sprites are now clipped correctly against the viewport\n  when the viewport origin is not 0,0. (Lancer-X)\n+ The VLAYER_SIZE counter now clears new vlayer area added when\n  it is increased. This applies only to 2.91+ worlds.\n+ The VLAYER_WIDTH and VLAYER_HEIGHT counters now preserve data\n  on the vlayer when set. This applies only to 2.91+ worlds.\n+ Fixed several bugs with string length and offset parsing that\n  would allow invalid string accesses to work in certain cases.\n+ COPY BLOCK $string now works correctly with string splices.\n+ Fixed a bug where saving and loading 2.90X files wouldn't work\n  on the Nintendo DS.\n+ Fixed a bug where mouse clicks would carry through from the UI\n  into games.\n+ Invalid gl_scaling_shader values should no longer appear as\n  the current loaded scaler.\n+ MegaZeux now falls back to the default scaling shader when a\n  selected scaling shader fails to compile.\n+ Blank sprite chars are now drawn when ccheck is 3. (Lancer-X)\n+ The sprite color is used with ccheck 3 collisions. (Lancer-X)\n+ When spr_yorder is enabled, sprites with the same (sprN_y +\n  sprN_cy) value are now consistently ordered by their sprite\n  numbers.\n+ The collision rectangles of ccheck 3 sprites now constrained\n  to the sprite dimensions (Lancer-X).\n+ MZX no longer crashes when a line of length 512 characters or\n  longer is pasted into the robot editor.\n+ Fixed a crash bug when trying to swap a sprite with an\n  invalid sprite index.\n\nDEVELOPERS\n\n+ Added Nintendo 3DS port. (asiekierka)\n+ Added \"make uninstall\" option to the Linux makefile.\n+ Added devkitPro portlibs paths to the NDS and Wii Makefiles.\n- Removed the depackers and non-MegaZeux formats from libxmp.\n\n>#MAIN.HLP:072:Table of Contents\n\nSeptember 4, 2017 - MZX 2.90d\n\nHere's another bugfix release. This is mostly assorted small\nfixes, but there are also a couple of major fixes here: first,\nthe ternary operator now works correctly when nested both with\nand without expressions, and second, sprN_setview does not break\nin conjunction with certain unbound sprites. Additionally, SAMs\nwill not be converted to WAV files anymore (and are now natively\nsupported) and the robot debugger config is accessible from the\neditor.\n\nFEATURES\n\n+ The current MegaZeux version is now visible from the enter\n  menu on the title screen. This is to assist identifying the\n  version on platforms that don't respect window title changes\n  or have no window border.\n+ Updated checkres.bat and checkres documentation.\n+ The robot debugger configuration screen is now accessible from\n  the editor via Alt+F11.\n+ Removed SAM to WAV converter. MegaZeux now has native SAM\n  support. (asiekierka)\n+ Added crt-wave.frag, updated crt.frag. (astral)\n\nFIXES\n\n+ Fixed a bug where MegaZeux would crash when editing a robot\n  with an invalid IF command operator.\n+ Fixed a bug where the ternary operator would fail to find the\n  correct colon when the middle term contained a second ternary\n  operator in a nested expression.\n+ Nested ternary operators should now behave as expected.\n+ Fixed a bug where the built-in cursor would disappear after\n  exiting testing with EXIT_GAME.\n+ Fixed a bug where the listening mod would not restart after\n  exiting testing.\n+ Fixed a bug where the robot debugger would not wrap long lines\n  of Robotic code correctly.\n+ Attempting to save a world in a write-protected location\n  correctly displays an error message again.\n+ Fixed a bug where key repeat would not work when scrolling\n  through the counter debugger tree list.\n+ checkres will no longer report empty filenames as dependencies\n  e.g. SET \"\" TO \"FWRITE_OPEN\".\n+ The -q flag in checkres will now correctly output filenames\n  instead of nothing.\n+ Fixed a bug where sprN_setview would scroll the viewport to\n  the wrong side of the screen if the sprite is unbound and is\n  close to the top or left edge of the board. (Lancer-X)\n\nDEVELOPERS\n\n+ Fixed PSP and NDS ports. (asiekierka)\n\n>#MAIN.HLP:072:Table of Contents\n\nJuly 25, 2017 - MZX 2.90c\n\nThis is a small fix release mostly cleaning up issues from the\nprevious release.\n\nNotable fixes include a crash on certain instances of string\ninterpolation into counters/labels/text. Playing mods as sound\neffects works again, and the audio_sample_rate config file\noption now works correctly.\n\nThis release also includes support for loading save files from\nMegaZeux 2.84X and experimental editor behavior for handling\nboard charsets and palettes.\n\nFEATURES\n\n+ Save files from 2.84X worlds can now be loaded.\n+ The editor can be configured to automatically load board\n  charsets and palettes now. Use the 'editor_load_board_assets'\n  config file option to enable this behavior. This behavior is\n  disabled by default. Note that this will OVERWRITE THE CURRENT\n  WORLD CHARSET AND PALETTE and your changes will NOT be saved\n  automatically.\n+ GLSL is now the default renderer. (Lancer-X)\n\nFIXES\n\n+ Fixed bug where --disable-modular builds would fail to link.\n+ Fixed bug with libxmp integration where mods could not be\n  played as sound effects.\n+ Fixed bug with libxmp integration where MZX would assume\n  audio_sample_rate was 44100, causing mods to play at incorrect\n  pitches with other rates.\n+ Fixed bug where editor config file options would be ignored at\n  the command line.\n+ Fixed bug where string interpolation into a label/counter name\ncould cause MZX to crash with very long strings.\n\n>#MAIN.HLP:072:Table of Contents\n\nJuly 16, 2017 - MZX 2.90b\n\nIt's been two weeks, so here's a bugfix release for MZX 2.90.\nThere isn't a whole lot that's new to talk about.\n\nFirstly, the palette editor has gone through an overhaul. This\nis mostly internal preparation for an eventual SMZX palette\neditor, but the new features include the partial restoration of\nmouse input (which was removed in 2.80X), the addition of color\nsliders, the ability to hide the palette editor help, and two\nnew colorspaces (HSL and CIELAB) to aid in the selection of\npalette colors.\n\nNext, the robot debugger introduced in 2.90 has new features.\nYou can now set \"watchpoints\" to watch the status of particular\nvariables, and you can send labels/goto from the robot debugger.\nThe robot debugger config screen has been visually improved, and\nline number breakpoints can be defined. Another notable feature\nis that KEY_PRESSED and KEY_CODE values are now displayed in the\ndebug window during gameplay.\n\nlibxmp is now the default sound engine for modules, fixing a\nlongstanding bug where certain S3Ms would have muted channels.\nGDM modules are now supported by MZX again. If you notice any\ninaccuracies with mod files, please report them to the tracker.\n\nFixed bugs include a major overhaul of the glsl shaders for\ncompatibility with almost any system, various sprite, text box,\nand input bugs, a crash fix when MZMs were saved from out of\nbounds board locations, and a bug where opening worlds or\ntesting from the editor could cause MegaZeux to exit.\n\nFEATURES\n\n+ The \"shaders/extra\" folder is now \"shaders/scalers\". The\n  default vertex shader is still located in the shaders folder.\n+ The default scaling shader can now be set in the config file\n  using the gl_scaling_shader option. If not defined, MegaZeux\n  will load assets/shaders/scalers/semisoft.frag.\n+ The robot debugger now supports monitoring the values of\n  counters and strings. Use the config menu/breakpoint editor to\n  add watchpoints; when the debugger detects a change in a\n  watchpoint counter/string AFTER a command has been executed,\n  the robot debugger will open.\n+ The robot debugger can now send robots labels. Use 'G' or\n  select 'Goto' in the robot debugger to use this feature. The\n  name and label inputs support expression parsing and string/\n  counter interpolation.\n+ The robot debugger can now use line numbers in breakpoints.\n+ The palette editor help can now be hidden with Alt+H. The\n  default behavior for this can be set in the config file. The\n  palette editor help is visible by default.\n+ The palette editor's mouse functionality has been improved.\n  Behavior when clicking the palette has been restored, and\n  component sliders have been added.\n+ The palette editor now supports alternate color spaces.\n+ The debug window now displays the last key_code (green) and\n  key_pressed (magenta) values detected during gameplay. These\n  values are located in the bottom right corner of the window.\n+ The default fullscreen resolution for scalable renderers is\n  now 1280,720. The default fullscreen resolution for 'software'\n  is still 640,480. The default aspect ratio is now 'modern'.\n+ The checkres utility has been updated to support 2.90X worlds,\n  worlds in subdirectories of zip archives, multiple input files\n  to test, and the ability to specify secondary directories and\n  zips to search (e.g. for games with separate music zips).\n\nBUGFIXES\n\n+ Fixed a bug where certain \"keyN\" labels, such as \"key$\", would\n  not work correctly.\n+ Integer comparisons between numbers greater than 2^31-1 apart\n  now work correctly. (Lancer-X)\n+ The palette editor now displays the SMZX mode 1 palette\n  correctly.\n+ Sprite collisions now correctly ignore the sprite's visual\n  width and height.\n+ Blank lines are no longer skipped when copy-pasting text into\n  the robot editor. (Lancer-X)\n+ Fixed a bug where key presses in dialogs could be detected in\n  games, and keypresses could still carry between other dialogs.\n+ [ message boxes now use the same screen mode as everything\n  else. Same with scrolls. (Lancer-X)\n+ spr#_setview now works with unbound sprites. It's pretty nasty\n  as the viewport can't scroll with pixel precision, but the new\n  behavior should still be better than the old. (Lancer-X)\n+ Fixed a bug affecting certain Intel HD Graphics cards and the\n  GLSL renderer that appears to have been around since that\n  renderer was first introduced. (Lancer-X)\n+ Fixed a bug where the numeric numpad keys were inappropriately\n  translated to regular keys based on numlock when they were\n  read from KEY_PRESSED. This behavior is now locked to worlds\n  from 2.82X through 2.84X.\n+ Fixed a bug where warping the mouse on one axis would snap the\n  mouse to the nearest pixel on the other axis, causing problems\n  for upscaled windows.\n+ Fixed a bug where mouse control would not respect video_ratio\n  settings aside from \"stretch\".\n* Fixed a bug where MZX would attempt to load certain invalid\n  worlds that should have been caught by validation.\n+ Copy block to MZM no longer crashes when the MZM is partially\n  overlapping the board edge. (Lancer-X)\n+ Fixed bug where MegaZeux would sometimes exit immediately on\n  loading a game or would immediately exit testing.\n\nDEVELOPERS\n\n+ libxmp is now the default module sound engine. Use \"make xmp\"\n  to make and install libxmp if it is not available on your\n  platform.\n+ Mac OS X portability improved. (Spectere and Why-Fi)\n\n>#MAIN.HLP:072:Table of Contents\n\nJune 29, 2017 - MZX 2.90\n\nHey, it's been a while! Didn't mean to keep everyone waiting.\nTo make up for it as much as possible, we've prepared an extra\nspecial release of MegaZeux here for you. Yeah, you!\n\nI don't even know where to start with this release, as there's\nsimply a lot to talk about. MZX's world formats and rendering\narchitecture have gone through major overhauls, and a multitude\nof bugs have been fixed (60+). Particularly the MZX UI graphical\nglitch that occurred when an SMZX mode was enabled.\n\nSome notable new features are unbound sprites (MZX's new native\npixel precision support), loading character sets/palettes/source\ncode/MZMs from strings, saving MZMs to strings, a new Robotic\ndebugger, the SAVE_COUNTERS and LOAD_COUNTERS function counters,\noptions to help create standalone game releases, the ability to\nassign character sets/palettes to a board to load on entry, and\nresetting boards on entry. The size limit of Robotic bytecode\nhas been increased to 2MB.\n\nOther important mentions: MegaZeux has been updated to use\nSDL 2.0 and libmodplug 0.8.9.0. The SAVE_WORLD function counter\nhas been permanently removed. The downver utility has been\ntemporarily discontinued, as adapting it to this release would\nhave been a considerable amount of work. Instead, there is a new\noption in the Export menu in the editor to export a 2.84 world.\n\nEnjoy, and try not to get overwhelmed by all of the new stuff!\n\nFEATURES\n\n+ Added a built-in robot debugger. See the Debug Mode section of\n  the help file for details.\n+ Added the COMMANDS_STOP special counter to MegaZeux. When a\n  robot executes a number of commands exceeding this value while\n  testing, the robot debugger will be enabled automatically and\n  will open. During regular gameplay, it will be ignored. The\n  default value of COMMANDS_STOP is 2000000.\n+ The counter debugger now remembers its previous position after\n  being reopened.\n+ Added commands_cycle and commands_total variables to the Robot\n  section of the counter debugger. See the Debug Mode section of\n  the help file for details.\n+ Certain types specified as 255 on the char ID table are now\n  treated as Custom* types when placed and selected in the\n  editor. If you switched a type to char ID 255 in a game that\n  already had that type, the parameters will not be changed\n  (and the types will still be char 0 if they were already\n  placed in the world). When making a type a Custom* type from\n  the global chars dialog, it will appear visually different in\n  the list and in the character selection window, and also ask\n  for confirmation.\n+ The enter menu can be closed with escape now.\n+ In the editor, the Backspace key now removes the top layer\n  and brings the under layer to the top, as a compliment to Del.\n  This does not affect the text entry behavior.\n+ Seeking in the String section of the counter debugger will now\n  ignore the $ prefix.\n+ Tentative joypad POV hat support added: joyNhat = U, D, L, R\n+ The BUTTONS counter has been extended to support the mouse\n  wheel and X1/X2 buttons.\n+ Strings can now be used in the LOAD PALETTE \"file\" command in\n  place of a file name, i.e. LOAD PALETTE \"$string+10\".\n+ Strings can now be used in the LOAD CHAR SET \"file\" command in\n  place of a file name, i.e. LOAD CHAR SET \"@@240$chars\".\n+ Strings can now be used for the special counter LOAD_ROBOT in\n  place of a file name, i.e. set \"$str\" \"LOAD_ROBOT\".\n+ Strings can now be used for saving and loading MZMs in place\n  of a file name, i.e. put \"@@$str\" image_file... (Lancer-X)\n+ Added ESCAPE_MENU counter. When set to 0, this counter will\n  prevent pressing escape from opening the exit gameplay menu.\n  This will not affect any other escape menus in MZX, and the\n  exit gameplay menu can still be opened by other means, such as\n  ALT+F4, CTRL+C, the window close button, etc.\n+ Added EXIT_GAME function counter. When set to anything but 0,\n  this will cause MegaZeux to exit to the title screen. This has\n  no effect on the title screen.\n+ Added the ternary operator (?:) to expressions. If the value\n  to the left of ? is not equal to zero, the expression between\n  the ? and the : will be evaluated. If the value to the left of\n  ? is equal to zero, the expression to the right of : will be\n  evaluated.\n+ Added SAVE_COUNTERS and LOAD_COUNTERS special counters. These\n  will save and load files containing every counter and string\n  from the world they're used in, respectively. These files are\n  not version checked, and may be used like save files.\n+ Robots now keep their robot IDs after reloading saves.\n+ Added standalone mode. The config parameters standalone_mode\n  and no_titlescreen can now be used (from MZXRun only) to\n  create standalone versions of MZX with the ability to fully\n  customise the player's experience. See config.txt for details.\n  (Lancer-X)\n+ The minimal help bar now displays robot memory and the current\n  board mod when contextually appropriate.\n+ The minimal help options for the board and robot editors are\n  now enabled by default.\n+ Shaders can now be changed from the F2 menu when using the\n  GLSL renderer. The user can select a fragment shader from the\n  assets/shaders/extra/ directory. If a matching vertex shader\n  exists, it will be loaded alongside the fragment shader.\n  Otherwise, MZX will fall back to the default vertex shader.\n+ On Linux and Mac OS X the configuration file will be copied\n  into the user's home directory and given the name\n  .megazeux-config if it is not already present. This config\n  file will then be used instead of the global one. (Lancer-X)\n+ Added unbound sprites. Sprites can be unbound from the grid by\n  setting spr#_unbound to 1. Their coordinates will now refer to\n  the sprite's location in pixels, not tiles. spr#_width,\n  height, refx and refy still refer to chars; however, spr#_x,\n  y, cx, cy, cwidth and cheight are all in pixels. Unbound\n  sprites do not work with all renderers; currently, out of the\n  renderers available on the PC platforms, the overlay2 renderer\n  lacks this functionality. (Lancer-X)\n+ Unbound sprites do not let char 32 or blank characters (when\n  in ccheck 2) through like regular sprites do. Instead, set\n  spr#_tcol to a color that will be transparent when the unbound\n  sprite is drawn. (Lancer-X)\n+ Unbound sprites can make use of additional hidden charsets.\n  There are an additional 14 charsets beyond the default that\n  can be modified through using load char set or char edit.\n  (e.g. load char set \"@@256charset.chr\")\n  A sprite can be set to refer to these later chars with the\n  spr#_offset counter. The offset value is then added to each\n  char in the sprite. You can use this to refer to higher char\n  sets.\n  (e.g. set \"spr0_offset\" 256)\n  Offset values can also be set to locations within a char set.\n  (Lancer-X)\n+ To allow you to access the extended charsets, the @@ option to\n  load char set now takes up to 4 digits, rather than merely 3.\n  The + option still only allows 2 hex digits. (Lancer-X)\n+ The colors used by tiles of a given color # in SMZX mode 3\n  can now be rebound using the \"smzx_idx#,#\" counters.\n  (e.g. set \"smzx_idx7,0\" 32)\n  Now if you put down a c07 tile, all 00 pixels will refer to\n  color 32 (as opposed to color 0 in SMZX mode 2 or color 7 in\n  SMZX mode 1). These values are reset whenever the SMZX mode is\n  changed. Once again, this is only available to renderers that\n  support unbound sprites. (Lancer-X)\n+ Edited worlds now retain their world version until resaved.\n  Before, they would lose their world version after testing.\n+ Added world version display to the debug window.\n+ Added GOOP_WALK counter for robots.\n+ Added \"Reset board on entry\" parameter for boards. When set,\n  the board will reset to its original state when the board is\n  entered during gameplay. This setting can be saved as a board\n  default.\n+ Added \"Load charset on entry\" parameter for boards. When set,\n  MegaZeux will load the selected charset file when the board\n  is entered during gameplay. This setting can be saved as a\n  board default.\n+ Added \"Load palette on entry\" parameter for boards. When set,\n  MegaZeux will load the selected palette file when the board\n  is entered during gameplay. This setting can be saved as a\n  board default.\n+ Maximum robot size has been increased from 64kb to 2mb.\n  (Lancer-X)\n\n+ Debytecode: the LOAD_SOURCE_FILE special counter can now be\n  used to load and compile a robot program from source code.\n  This can be used with either a file or a string as input.\n+ Debytecode: the robot editor's import robot menu now supports\n  loading legacy source code.\n\nBUGFIXES\n\n+ Fixed crash bug when using ALT+Z (Clear Board).\n+ Fixed crash bug when attempting to view the help file in the\n  updater.\n+ Fixed crash bug when attempting to use SMZX_R/G/B with out-of-\n  range indexes.\n+ Fixed crash on exit when the config file did not explicitly\n  specify update hosts.\n+ Fixed memory leak involving bounds-breaking FREADn calls.\n+ Fixed counter debugger issue where counters and strings\n  starting with chars over 'Z' would cause everything in their\n  section to be placed into the '#' list.\n+ Dialog window labels can not be put into focus by clicking\n  them with the mouse anymore.\n+ Dialog windows closed by pressing escape will now close only\n  on a new press instead of whenever the key is held.\n+ Load (F4)/quickload (F10) on invalid save files in-game leaves\n  the current game running instead of exiting to the titlescreen\n  or editor.\n+ Fixed issue where counter debugger would display wrong values\n  for the robot LOCAL counters.\n+ Fixed robot editor issue where global robot coords would be\n  reloaded as 65535.\n+ Fixed issue where board defaults would be reset after leaving\n  testing until the world was manually reloaded.\n+ Fixed issue where the Board Info dialog would not close when\n  'Cancel' was selected.\n+ Fixed bug where chars 254 and 255 would not be reverted back\n  to their defaults via F4/F5.\n+ Fixed bug where player would move onto goop when walking into\n  enemys on top of goop.\n+ Fixed bug where 'enter' would be detected in loaded save files\n  when loading from a title screen.\n+ Fixed issue where MZX was using the world file version instead\n  of the internal MZX version when saving MZMs and determining\n  whether it could load MZMs both within the editor and in game.\n+ Changed the caption behavior in the world editor as to refresh\n  only when board values are synchronized.\n+ Fixed bug where, when exiting gameplay, the world would keep\n  running on the title screen if MegaZeux lost read access to\n  the world file.\n+ Fixed minor bug where trying to load a nonexistant mod would\n  change MOD_NAME's output while the previous mod still played.\n+ Fixed a Windows-only bug, where attempting to load a Robotic\n  file in the Robot Editor or with LOAD_ROBOT would not load the\n  entire file if the file contained 0x1A (char 26).\n+ Fixed a bug where the lazer animation would fail to complete.\n+ Fixed bug where the VOLUME # and MOD FADE # # inputs could go\n  out of range, resulting in ear-piercing white noise. Inputs\n  now wrap between 0 and 255 for compatibility reasons.\n+ Fixed bug where certain unicode values could crash MZX in the\n  character selection dialog.\n+ Fixed bug where : \"keyN\" would not work on the Pandora.\n+ Fixed a bug where FREAD could create a string larger than the\n  maximum string size.\n+ In the sprite section of the counter debugger, the spr_clistN\n  values were incorrectly labelled as \"spr_collisionN\". Fixed.\n+ Fixed counter debugger crash when selecting certain strings\n  containing escaped values.\n+ Added compatibility for the cycle-ending SHOOT, SHOOTMISSILE,\n  SHOOTSEEKER, and SPITFIRE commands from MegaZeux 2.83.\n+ Fixed crash bug when attempting to use file counters with\n  paths equal to or longer than 512 chars in length. MegaZeux\n  will now ignore such paths.\n+ Fixed bug where MZX would crash with out-of-bounds joystick\n  key mappings.\n+ Fixed a bug where robots could not push the SliderNS type to\n  the south.\n+ Fixed a bug where a robot using #return or #top multiple times\n  within a single command could cause a crash.\n+ Fixed a port regression where IF ANY would continue to iterate\n  over the board after finding a match, potentially triggering\n  multiple subroutines, #return, or #top labels.\n+ Fixed a bug where MZM error messages could repeat indefinitely\n  and lock up MZX.\n+ Fixed a bug where the (#)PUSHED label would not be sent to the\n  first object in a row of pushed objects if that object was a\n  robot.\n+ Fixed support for PPC Linux builds. (Insidious)\n+ Fixed a bug where a robot sent a subroutine would restart the\n  command it was executing upon #return if the command was a\n  multi-cycle command. This fix applies to worlds saved in 2.90\n  and later only.\n+ Fixed a bug where ALT+F4 would cause menus to open instead of/\n  in addition to triggering an exit event.\n+ Fixed a bug where subdirectory mods with different types of\n  slashes in their names would fail to be recognized as the same\n  mod.\n+ Fixed a bug where SMZX palette intensities were not saved.\n+ Made the opengl2 and glsl renderers endian-correct. (Lancer-X)\n+ Fixed a bug where keystrokes were getting lost during event\n  processing. (Lancer-X)\n+ The UI now uses the regular MZX mode and the protected palette\n  when playing SMZX games. This is only available to renderers\n  that support unbound sprites. (Lancer-X)\n+ Changing from SMZX mode 1 to 0 correctly restores the palette.\n  (Lancer-X)\n+ Fixed a bug where the tree list in the counter debugger would\n  rapidly scroll through elements when clicked.\n+ Fixed a bug where the updater, on failing to receive data from\n  the server, would consume large amounts of CPU before timing\n  out. (Lancer-X)\n+ Fixed various bugs related to the pushing of sensors.\n  (Lancer-X)\n+ Fixed a bug where MZX would crash in some cases when testing\n  after adding/importing new boards into a world.\n+ Made KEY a board counter <=2.70. Also added masking <=2.62 to\n  make Oath Demo work again. (Lancer-X)\n+ Bullet types shot by robots clamped to 0-2. (Lancer-X)\n+ SAVE_GAME and SAVE_WORLD now happen at the end of a cycle, not\n  immediately. (Lancer-X)\n+ SAVE_WORLD no longer exists. (Lancer-X)\n+ Labels at the end of a program no longer get randomly sorted\n  first and stop the actual first instance of that label being\n  called. This fixes a bug in the Dark Corner 'Zane' demo.\n  (Lancer-X)\n+ Copy and copy block in pre-port MZX games now preserves the\n  state of any robots copied. (Lancer-X)\n+ Copying and pasting robot code with LF line endings on Windows\n  now works. (Lancer-X)\n+ MegaZeux can now be exited during an infinite loop. (Lancer-X)\n+ Fixed a bug where SAVE_GAME on the first cycle of a board\n  would cause the game to be faded out on load. This still\n  affects legacy worlds. (Lancer-X)\n+ Fixed a bug where trying to create new counters called \"fread\"\n  or \"fread_counter\" in the counter debugger would cause the\n  file being read to advance.\n+ Fixed a bug where the numpad was unable to be used to control\n  the player, despite being identical to the cursor keys for\n  every other situation. (Lancer-X)\n+ Fixed a bug where pre-port worlds could play any number of\n  concurrent samples. This makes portions of Bernard the Bard\n  quite cacophonous. (Lancer-X)\n+ Fixed a bug where per-game config files could set options they\n  weren't supposed to be able to.\n\nDEVELOPERS\n\n+ Fixed linking bug when attempting to make a modular build of\n  MegaZeux in Debian/armhf. -fPIC is now enabled for modular\n  builds on all platforms. (Insidious, ajs)\n+ Added SDL 2.0 support. At the moment, the overlay renderers\n  are not accelerated by SDL 2.0 and may be slower than their\n  SDL 1.2 counterparts. Other minor inconsistencies may exist\n  between the two versions. (ajs)\n+ Keycode help diagram replaced with an HTML file (from the\n  older PNG file). This will make future updates much easier.\n+ Updated NDS, Wii and PSP ports to latest devkitpro toolchains.\n+ Updated libmodplug to version 0.8.9.0. (asiekierka)\n+ Added experimental libxmp support. (asiekierka)\n+ Added experimental libopenmpt support. (asiekierka)\n+ New rendering architecture that mostly sits alongside the old\n  rendering architecture. There is a new function implemented by\n  supporting renderers: render_layer(). This provides a layer\n  in a similar form to the text_video array in graphics and\n  expects that to be drawn on the screen. This is called once\n  for each layer that is drawn- this means the board, overlay,\n  UI and sprites. Each layer can have a different SMZX mode,\n  although at the moment this is only used to allow the UI to\n  remain in regular MZX mode while the game or editor is in SMZX\n  mode. The render_layer() function is only called if it is\n  implemented by the renderer and if there is something on the\n  screen that can only be drawn by the renderer (e.g. unbound\n  sprites, or UI elements in an SMZX game) (Lancer-X)\n+ LibSDL2 support is now the default. It can still be disabled\n  by passing --disable-libsdl2 to config.sh. (Lancer-X)\n\n>#NEWINVER.HLP:1st:NEW in MegaZeux!\n>#292CLOG.HLP:292:New in Versions 2.92 to 2.92f\n>#284CLOG.HLP:284:New in Versions 2.84 to 2.84c\n>#2823CLOG.HLP:283:New in Versions 2.82 to 2.83\n>#281CLOG.HLP:281:New in Versions 2.81 to 2.81h\n>#280CLOG.HLP:280:New in Versions 2.80 to 2.80h\n>#OLDERVER.HLP:260:New in Versions 2.60 to 2.70\n>#OLDESVER.HLP:201:New in Versions 2.01 to 2.60\n>#NEWIN200.HLP:1st:New in Version 2.00\n>#ANCENVER.HLP:1xx:New in Versions Through 1.03\n>#MAIN.HLP:072:Table of Contents\n#284CLOG.HLP\n:284:\n$~9New in Versions 2.84 to 2.84c\n\nDecember 24, 2012 - MZX 2.84c\n\nHey all, new version of MegaZeux here! And hopefully without any\ncrashes this time around!  Haha yeah right.\n\nThe most notable thing this release (besides fixed crash bugs,\nas usual) is the new counter debugger! Give it a try and tell us\nwhat you think!\n\nCounter binary search has been replaced with a hash table in\nversions of MegaZeux for most platforms. This has resulted in\nslightly faster counter lookups, and MUCH faster creation of\ncounters, since MegaZeux doesn't have to keep the list ordered.\nThe hash table is turned off for NDS builds for now due to tight\nmemory constraints.\n\nOtherwise, most of the new features are editor enhancements and\nmodifications. Notable mentions are reordering the board list\nwith 'M' and a custom undo history size.\n\nFEATURES\n\n+ The functionality of the F11 counter/string debugger has been\n  expanded to include sprites, robots, and miscellaneous world\n  and board variables. The ability to modify many of these vars\n  is limited and a large portion are read-only. In addition, you\n  may add new counters and strings, hide empty counters/strings\n  (does not affect built-in variables), and search by name and\n  contents. Export is still limited to counters and strings.\n+ Max string length has been increased from 1MiB (1048576 bytes)\n  to 4MiB (4194304 bytes).\n+ Board mods may now be selected from subdirectories of the game\n  folder in the ALT+N dialog.\n+ The title bar now displays the world and editing board/robot\n  names based on context.\n+ Fire Burns Space and Fire Burns Forever are off by default.\n+ You may now specify the size of the undo history in the char\n  editor, and the history affects the entire char set. Undo is\n  still ALT+U, redo is now ALT+R.\n+ You may now use the -/+ keys in the world editor to move to\n  the previous/next boards in the board list, and Shift+Arrows\n  to change to the boards linked by board edge.\n+ You may now move the current board anywhere in the board list\n  with M. You may not move the title screen board.\n+ View mode in the board editor (V) will start from the current\n  location of the screen now rather than at the top-left corner.\n+ Config file options for default board settings added, as well\n  as the ability to save these per-world from the editor.\n+ Ctrl+G - Goto board position at X,Y.\n+ png2smzx now requires less arguments and has an option to skip\n  a char (generally char 32 will be useful to skip).\n\n+ Debytecode: The robotic editor now asks for a confirmation to\n  save the program on exit.\n\nBUGFIXES\n\n+ Fixed a bug in the 'glsl' renderer that caused the cursor\n  color to be ignored (always white). This caused the cursor to\n  not be visible if shown on a white background. (ajs)\n+ Setting $string.length will not cause memory corruption now.\n  In addition, its pre-2.84 behavior of intentionally allocating\n  the string past the set length has been restored, but the\n  length itself will still be set to the correct value.\n+ Added bounds check to INC \"$string\" \"value\". Attempts to\n  increase a string past its maximum length (4 MiB, or 4194304\n  bytes) will now fail.\n+ Fixed MegaZeux crash that could sometimes occur when a string\n  was increased by itself.\n+ Fixed MegaZeux crash that could occur when exiting the editor\n  to a world file that failed validation.\n+ Rolled string->storage_space into string->name to prevent\n  buffer overflow errors and crashes. (Mr_Alert)\n+ Fixed crash that would occur when attempting to type in a dir\n  in the ALT+N dialog.\n+ Fixed a bug where copy block $string would apply REL twice.\n+ Thanatos Insignia (DoZ Q1 2011, 98485) will now play without\n  freezing both normally AND after loading a save. Robots will\n  not be incorrectly versioned with the save format magic now.\n+ Fixed bug where ALT+M wouldn't always edit non-stored types.\n+ Fixed bug where ! could not be used as a substitute for ~~.\n+ Fixed a bug where tab-draw and block actions would carry\n  between board and overlay editing. ALT+O now sets the drawing\n  mode back to normal on a switch.\n+ ALT+M now edits the overlay instead of the board while editing\n  the overlay.\n+ Quick-load (F10 during gameplay) will not work if the load\n  menu (F4) has been disabled with the LOAD_MENU counter.\n+ Fixed a bug where the listening mod directory would stay the\n  same as the directory the editor was opened in.\n+ The listening mod now continues to play after world and board\n  changes, and also after the board mod has been changed by\n  ALT+N or Shift+8/*.\n+ LOAD_GAME will not incorrectly trigger JUSTENTERED anymore.\n+ Palette/intensity changes made on the same cycle as a teleport\n  player command are now properly taken into account. This fixes\n  a game-stopping regression in Sponkgo's Legendary Journey\n  where leaving the second stage's bonus area would leave the\n  color intensity at 0.\n+ SMZX mode is now disabled when leaving the editor if it was\n  enabled in the editor via F11.\n+ Fixed potential crash bug where robot bytecode files were not\n  being validated before being loaded with LOAD_BC.\n+ Fixed minor message box bug dating back to DOS where a message\n  box starting with an unavailable option would begin with the\n  cursor on a blank line.\n+ Fixed crash that happened when typing over bounds while\n  renaming a file in a file manager dialog. File and directory\n  renaming now use a popup dialog akin to ALT+N.\n+ Files with names longer than 55 chars may now be selected in\n  file managers. The limit for typing in a file name is still 55\n  chars.  You may not enter a blank line as a file name anymore.\n+ Fixed segfault when attempting to read THIS_COLOR for the\n  global robot. It will now always return -1.\n\nDEVELOPERS\n\n+ Overhauled txt2hlp. Error messages now differentiate between\n  hyperlinks and labels and will take the display chars ~~ and &\n  into account. Eventually, the goal should be to get rid of\n  txt2hlp altogether and have MZX load straight from the .txt\n  file.\n+ Added compile option to use uthash for counter/string lookups\n  as opposed to binary search. Ideally, this will vastly improve\n  lookup speeds for large numbers of counters/strings, but will\n  consume more memory. (thanks to Lancer-X for the modified\n  uthash header file)\n  \n>#MAIN.HLP:072:Table of Contents\n\nJune 20, 2012 - MZX 2.84b\n\nLess than three weeks after the last one, MegaZeux 2.84b is\nhere a few weeks early to fix a key regression regarding the\nuse of REL commands with the COPY x y dx dy command.  While\nseveral new features have been added, this release was mostly\nabout rooting out as many crash bugs from MegaZeux as possible.\nAn official public beta release of debytecode has been pushed\noff once again due to time constraints.\n\nMost notable as far as new features go, pressing 'E' while a\ngame's title screen is running will now take you directly to\nthat world in the editor. A blank new world may still be\ncreated with 'F8', and now with the 'N' key. The function key\ncorresponding to the new 'E' functionality is 'F9'.\n\nA major new aspect of this version, which could be seen as a\nbugfix or as a new feature, is MegaZeux's ability to validate\nthe MZX, MZB, MZM, and SAV file formats. Validation has been\nrigorously tested and refined, and a comprehensive list of most\nworld files that fail any check is available on the MZX Wiki.\nFile load crashes and force-quit \"Out of memory\" errors in\nthese instances are nearly a thing of the past.\n\nOne final major change regards the file manager dialogs (Load /\nSave Game, etc). These dialogs have been internally overhauled\nto avoid permanent directory changes unless a valid world or\nsave file has been loaded.  If any bugs are experienced using\nthese, they should be reported to the MZX Bug Tracker.\n\nFEATURES\n\n+ Pressing 'E' or 'F9' on a title screen will now open the\n  current world for editing.  Press 'N' or 'F8' to create a new\n  world.\n+ Multiple hosts may now be defined in config.txt. Update\n  attempts will be carried out in the order they are defined.\n+ A startup path may be defined in config.txt (\"startup_path\").\n+ Specifying the backup filename with a directory, ex.\n  \"backup/file\", will now silently attempt to create the\n  directory if it does not exist.\n+ MZM3 is now forward compatible (robots will be dummied out).\n\nBUGFIXES\n\n+ World validation has been strengthened, preventing\n  disasterous loads of most non-world files.  Mostly\n  intact/valid worlds, such as HUNTDRAK.MZX, can be loaded --\n  corrupt/missing boards will be replaced with blank boards,\n  corrupt robots will be replaced by robots with empty\n  programs, and so on.  If more robots/scrolls/signs are found\n  on a board than their data suggests there should be, the\n  extras will be replaced with customblocks. Back up the\n  version of the world with errors in this instance, as the\n  robots' code or scroll text may still be salvageable from the\n  world data.\n+ Fixed a long-standing memory corruption bug in the shoot\n  command introduced by the port.\n+ Mac OS X: MegaZeux now uses /Users/[username] as the default\n  starting directory. Apps previously would always start at the\n  filesystem root. (ajs)\n+ Fixed dangerous crash-causing bug where the editor would not\n  chdir back to the correct directory after testing.\n+ MegaZeux, after saving a world to a new directory, now chdirs\n  to the new current world file's directory.\n+ Changed behavior where a failed save file load would fade the\n  still-running world out of focus. MegaZeux now leaves the\n  world in focus.\n+ All failed world/save loads leave the current world running\n  and in the same working directory.\n+ When startup_file is defined as a directory at the command\n  line, MZX chdirs to the directory instead of attempting to\n  open it as a world file.  In the config file, the directory\n  is pruned off since there's a new config option for the\n  startup path.\n+ Fixed regression where the rel commands would be ignored for\n  board to board copy x y dx dy.\n+ Updated MZX_SPEED in the counter debugger, where it was still\n  getting clamped from 1 to 9.\n+ COLOR FADE IN and COLOR FADE OUT (and all built-in uses of\n  them) now correctly respect any COLOR INTENSITY changes made\n  before they are used.\n+ In file selection dialogs, when attempting to make a\n  directory that already exists with ALT+N, MZX now shows the\n  correct file name and does not force the user to quit\n  MegaZeux.\n+ In file selection dialogs, when pressing ALT+D with a\n  directory selected, MZX now correctly prompts to delete the\n  selected directory instead of a file.\n+ The option to import world files in the board editor now\n  correctly takes the world version into account.\n+ Savegame MZMs loaded into the editor now have their robots\n  dummied out for safety purposes.\n+ Fixed bug where INPUT STRING would not terminate lines longer\n  than 71 chars after clipping them.\n+ INPUT STRING and ASK do not allow either tabs (INPUT only) or\n  line breaks (both) anymore.\n+ Fixed bug where putting a scroll/sign in the editor buffer\n  and then selecting something else could cause a crash on\n  leaving the editor.\n\n+ Temporary fix for MSVC bug where all window dialogs would\n  freeze. (MZXGiant)\n+ Temporary fix: board block actions will not corrupt robot\n  source code in DBC anymore. (MZXGiant)\n\nDEVELOPERS\n\n+ Switched debian prereq. and darwin libpng12 to libpng,\n  switched darwin and default ldflags to use '--ldflags'\n  instead of '--libs'.\n+ Darwin CC/CXX compiler can now be specified on the command\n  line. (ajs)\n+ Cleaned up broken ifeq structure in darwin Makefile.in so\n  ARCHes other than i686 can be built.\n+ Updated MSVC dirent.h to the latest version (MZXGiant)\n+ Added updated MSVC dependencies. (MZXGiant)\n\n>#MAIN.HLP:072:Table of Contents\n\nJune 01, 2012 - MZX 2.84\n\nThe first version of MegaZeux to be released in two and a half\nyears, this time with a vast number of bugfixes, several new\nfeatures, and hopefully no new bugs.\n\nThere's a new port to the Pandora platform from Exophase. There\nare no binaries for this platform yet, as ajs has not had time\nto set up the cross-compiler. Same goes for Android.\n\nAnother major (internal) change this time around is that\nExophase's experimental \"debytecode\" language modification has\nbeen merged. This still has some major bugs open against it,\nand missing features, so I won't be doing official releases\nyet. You can add support for this feature by passing\n\"--enable-debytecode\" to config.sh on all platforms.\n\nThanks to Terryn, Exophase and MZXGiant for their contributions\nand to Lancer-X, Old-Sckool and Lachesis who reported and\ntracked the majority of bugs this time round.\n\nFEATURES\n+ Added experimental port to Pandora. See arch/pandora/README\n  for more information. (Exophase)\n+ Directories may now be opened with FREAD_OPEN.  This\n  functionality can be used in conjunction with FREAD_POS and\n  SET \"$str\" FREAD. FREAD will set the string to \"\" when it has\n  reached the end of the file listing.\n+ MZX_SPEED can now be set up to 16 by a robot or from the F2\n  dialog menu. (Lachesis)\n+ Numbers can now be temporarily backspaced past their minimum\n  value to make typing a new number more intuitive in dialogs.\n  (Lachesis)\n+ Chars 0 and 255 can now be selected from the Edit Chars\n  submenu of the Global Info menu. Using char 255 on a kind\n  that would have previously denied it now gives a warning\n  dialog. (Lachesis)\n+ New Counter: SPACELOCK represents the default space\n  functionality for the built-in player. Setting this counter\n  to 0 disables it, allowing the player to move as normal when\n  space is pressed. Setting it back to 1 enables it again.\n  Defaults to 1. (Lachesis)\n+ New Counter: FREAD_DELIMITER allows you to change the\n  terminating char for the string FREAD function.  The\n  terminating char still defaults to '*'. A complementary\n  FWRITE_DELIMITER function has been added as well. (Lachesis)\n+ New Counter: ARCTANdy,dx takes two values and returns the\n  angle with corrected quadrants as an alternative to using\n  ATANdy with DIVIDER as dx, which was less intuitive and never\n  documented properly. (Lachesis)\n+ New Counters: MINv1,v2 and MAXv1,v2 return the minimum or\n  maximum value between two inputs, respectively. Chain several\n  of these in an expression or a loop for more arguments.\n  (Lachesis)\n+ New Counters: bchX,Y; bcoX,Y; bidX,Y; and bprX,Y are new\n  shorthand access counters for BOARD_CHAR, BOARD_COLOR,\n  BOARD_ID, and BOARD_PARAM.  The same limitations to those\n  counters apply to the new ones. (Lachesis)\n+ New Counters: uchX,Y; ucoX,Y; uidX,Y; and uprX,Y are new\n  shorthand access counters for the board's under layer.  The\n  same limitations apply to these as to their board\n  counterparts.  Additionally, these counters will fail if the\n  same spot on the normal board is occupied by a floor-type\n  (space, [dir]water, lava, fake, etc...). (Lachesis)\n+ New Counters: ochX,Y and ocoX,Y are new shorthand access\n  counters for the overlay.  Like OVERLAY_CHAR and\n  OVERLAY_COLOR, these are read-only to discourage the user\n  from writing to these instead of using the much faster PUT\n  [color] [char] OVERLAY [x] [y]. (Lachesis)\n+ Pressing ALT+G from the world editor now goes directly to the\n  Global Robot without having to skip through the Global Info\n  menu. (Lachesis)\n+ MZM3 has been enabled for 2.84 and all following versions.\n  The difference between MZM3 and MZM2 is that MZM3 stores a\n  copy of the world version, allowing the robot format to\n  change. (Lachesis)\n+ COPY can now take + and # prefixes to its arguments. COPY\n  BLOCK and COPY OVERLAY BLOCK can now take + prefixes to their\n  first set of coordinates. (Lachesis)\n+ Subroutine versions of TOUCH, BOMBED, INVINCO, PUSHED,\n  PLAYERSHOT, NEUTRALSHOT, ENEMYSHOT, SHOT, PLAYERHIT, LAZER,\n  SPITFIRE, GOOPTOUCHED, PLAYERHURT, KEY[char], KEYENTER, THUD,\n  and EDGE have been added. These should ALWAYS be used in\n  conjunction with LOCKSELF/ZAP and #RETURN or #TOP to keep the\n  robot stack under control.  Please remember that THUD and\n  EDGE ignore LOCKSELF and their subroutine versions must be\n  ZAPped. JUSTENTERED, JUSTLOADED, and the sensor labels have\n  been excluded from this due to various reasons. (Lachesis)\n\nBUGFIXES\n\n+ Fixed a bug where LOAD_ROBOT or LOAD_BC would not reset the\n  stack pointer for newly loaded programs. This could cause\n  crashes if a robot popped the stack in the new program.\n+ Fixed a bug where range checking of BOARD_X and BOARD_Y would\n  sometimes not be done correctly, leading to crashes.\n+ Strings in the debug menu list no longer interpret any color\n  codes they may contain.\n+ Fixed a bug where a string would not be interpreted correctly\n  if it used a '.' character in a splice parameter expression.\n  Expressions such as IF \"$str#('$str2.length'-4)\" = \"blah\"\n  THEN \"label\" will now work correctly.\n+ Fixed negative sprN_cheight et al from crashing. (Exophase)\n+ Placement of objects on the player will be blocked with an\n  error dialog like DOS versions, instead of silently failing\n  after setup. (MZXGiant)\n+ Fixed a bug where LOAD_ROBOT would not properly parse lines\n  of imported code that had leading whitespace. (MZXGiant)\n+ Fixed an integer wrapping bug in debytecode, disallowed\n  numeric literals outside of the bounds of a signed short.\n  (MZXGiant)\n+ Fixed a bug that would corrupt the UI palette if \"set color\"\n  was run against a color index over 15. (MZXGiant)\n+ If either board dimension is less than the editor viewport,\n  the character and colors used to indicate space outside of\n  the board are taken from protected sets. In game, they are\n  taken from the game's sets.\n+ Increase limit on difference for RANDOM \"A\" TO \"B\" to\n  UINT_MAX rather than INT_MAX - 1 as it was previously. Since\n  the entire range represented by a counter is now usable,\n  there are no cases where RANDOM will \"break\".\n+ Fixed avalanche rings and potions to limit boulder placement\n  to 1/18, matching the AVALANCHE command.\n+ Fixed corruption and possible crashes when using VIEWPORT\n  SIZE to set the viewport to a size less than 80x25 but\n  greater than the current board dimensions. The viewport will\n  now always be clamped to board size.\n+ Fixed incorrectly changing horizontal mouse position on\n  setting MOUSEY. (Mr_Alert)\n+ Fixed overflow into protected character set when \"Revert\n  to...\" is selected in the character editing dialog.\n  (MZXGiant)\n+ Music and SFX now mute when the updater launches and are\n  restored when it is complete. (MZXGiant)\n+ Fixed odd string behaviour when copying between strings that\n  happen to be stored close to each other in memory. This fixes\n  a regression introduced by the \"crash when pasting to and\n  from the same string\" fix in 2.82b.\n+ The size/offset parameters for strings can now be specified\n  in either order (#+ vs +#) and will behave correctly.\n+ Reverted bogus cycle-ending behaviour for SHOOT,\n  SHOOTMISSILE, SHOOTSEEKER and SPITFIRE.\n+ Improved cycle-ending compatibility with MZX versions prior\n  to 2.80. Fixes games such as Kya's Sword and Stones & Roks\n  II.\n+ Restored shark's ability to move in goop.\n+ MZX now clears SPR_YORDER upon loading a new world.\n+ When transitioning between boards, MZX now compares the\n  module filenames of the source and destination boards\n  case-insensitively. A difference in case will no longer cause\n  the board module to be incorrectly restarted.\n+ Fixed rare rendering corruption in the load game dialog.\n+ Fixed a bug where SEND \"robot\" TO \"#return\" could corrupt the\n  program counter of the target robot if it had a stack pointer\n  of zero.\n+ Fixed some security issues with SMZX_PALETTE and LOAD_BC\n  counters.\n+ MZX now only lists/opens regular files or symlinks to regular\n  files in file dialogs. Special files are now ignored.\n+ Opening directories with FWRITE_OPEN is now rejected properly\n  on all platforms.\n+ Fixed a bug where a file would be re-opened for read/write,\n  even if the file was missing or an I/O error occurred before\n  saving.\n+ Fixed a bug where MZX could occasionally crash due to label\n  list corruption when copying robots from heap locations\n  greater than 2^32 bytes apart (only affected 64bit builds).\n+ Stopped SET \"var\" <command> from assembling. Some invalid\n  uses of command tokens were already being ignored, but this\n  was just luck.\n+ Sprites with color c?? (inherited \"natural\" colors) will\n  correctly inherit the colors of special characters such as\n  the player and other self-colored built-ins.\n+ Debytecode's legacy expression converter should use\n  is_string() instead of its own (buggy) hand-rolled version.\n  Fixes crash when converting CoAZ.\n+ The editor no longer incorrectly clamps the intelligence of\n  sharks, spitting tigers and spiders to <=4, and no longer\n  clamps the HP of dragons to <=4. (Lancer-X)\n+ MZX now accepts SET EDGE COLOR \"string\" in addition to SET\n  EDGE COLOR c??.\n+ Fixed NDS port initialization on DSi devices. (asiekierka)\n+ Fixed a bug in the joystick code where centering an axis\n  clears the previous axis button. (iamgreaser)\n+ Fixed a bug that allowed vlayer->board COPY BLOCK to\n  overwrite the player. Blocks that would overwrite the player\n  are now ignored.\n+ Fixed a 2.81e regression that allowed SENDs to self to ignore\n  LOCKSELF.\n+ Setting $str.length now makes $str the length specified.\n  (Lachesis)\n+ Caps Lock no longer interferes with dialog box text input.\n  (Lachesis)\n+ Increasing the size of a string with $str.length, $str.N, or\n  with a splice now wipes old string data with char 32s.\n+ Dialogs (especially \"Exit gameplay - Are you sure?\") now\n  require the user to have actually hit ESC to close, making\n  escaping busyloops and message loops much easier. (Lachesis)\n+ Added a compatibility fix for different label caching in 2.80\n  through 2.83 that allowed constructs such as : \"LABEL\" / SEND\n  \"ALL\" \"LABEL\" on an unlocked robot to continue instead of\n  getting caught in a loop. The altered label caching caused\n  #98485 Thanatos Insignia to lock up in an unescapable\n  busyloop in GIT versions. (Lachesis)\n+ MOD \"[lead-in file]*\" now works properly. In previous\n  versions of the port, this construct would result in the\n  lead-in file failing to play and the wildcard mod restarting\n  when the player re-entered and re-exited the board.\n  (Lachesis)\n+ Fixed a bug where mod \"*\" would cause the mod to restart when\n  entering another board with the same playing mod. (Lachesis)\n+ ALT+D (Default palette) now requires a confirmation.\n  (Lachesis)\n+ The COMMANDS counter is now saved as a 32-bit variable.\n  (Lachesis)\n+ The LOOPCOUNT counter has been moved to save-only data and is\n  now saved as a 32-bit variable. (Lachesis)\n+ Fixed a bug where not all whirlpools were being considered as\n  such, notably during the transport board scan. (Lachesis)\n+ The abilities of PLAY \"&file&\" to play at multiple\n  frequencies and to parse multiple files have been restored.\n  (Lachesis)\n+ As of MZX 2.84, BOARD_COLOR will now ignore the under color\n  of any object with a BG color of 0 that is on top of\n  something.  Worlds that relied on this between 2.80 and 2.83\n  are unaffected. (Lachesis)\n+ Setting BIMESG to 0 will now disable Game Over's\n  auto-centering of the message row. (Lachesis)\n+ The DIVIDER counter's documentation has been updated to\n  explain its true purpose.\n+ Fixed an editor bug where canceling a world load could cause\n  MZX to forget the filename of the current world. (Lachesis)\n+ Pressing ALT+M in the world editor now edits anything with\n  parameters, not just Robots, Signs/Scrolls, and Sensors.\n\nDEVELOPERS\n\n+ Source tarballs are now generated in XZ (LZMA2) format.\n+ Make hlp2txt utility work correctly on Windows platforms.\n+ Updated and repaired MSVC project for Visual Studio 2010.\n  (MZXGiant)\n+ Win32 binaries are built ASLR-capable (via pefix).\n+ Version control was changed from SVN to Git; the repo is at:\n  http://github.com/ajs1984/megazeux\n+ The EGL backend now supports Mesa's EGL implementation on\n  X11.\n+ Imported libmodplug 0.8.8.4 and rebased all patches.\n+ Introduced SOCKS4/4a/5 support transparently into the network\n  layer. (MZXGiant)\n\n>#NEWINVER.HLP:1st:NEW in MegaZeux!\n>#292CLOG.HLP:292:New in Versions 2.92 to 2.92f\n>#2901CLOG.HLP:291:New in Versions 2.90 to 2.91j\n>#2823CLOG.HLP:283:New in Versions 2.82 to 2.83\n>#281CLOG.HLP:281:New in Versions 2.81 to 2.81h\n>#280CLOG.HLP:280:New in Versions 2.80 to 2.80h\n>#OLDERVER.HLP:260:New in Versions 2.60 to 2.70\n>#OLDESVER.HLP:201:New in Versions 2.01 to 2.60\n>#NEWIN200.HLP:1st:New in Version 2.00\n>#ANCENVER.HLP:1xx:New in Versions Through 1.03\n>#MAIN.HLP:072:Table of Contents\n\n#2823CLOG.HLP\n:283:\n$~9New in Versions 2.82 to 2.83\n\nDecember 29, 2009 - MZX 2.83\n\nIt's been a year since the last release, due in part to my\nreduced free time, and less contribution from other developers\nin 2009. I'd also like to believe that 2.82b was such a good\nrelease, there was no need to rush.\n\nThere's over 30 bugs fixed this time. A few features I had to\nwithhold for 2.82b have been added; sample loop markers and\nsome changes to the board file format necessitated the bump to\n2.83. Logicow's GLSL renderer has finally made it in (various\nbits had to be re-written to extend portability to other\nplatforms).\n\nThere's a new (semi-complete) Android port this time; I hope to\ncomplete it, and provide binaries for Android 2.0 phones, in\n2.83b.\n\nThanks go out to the usual suspects -- Terryn, Mr_Alert,\nLogicow, kvance, Lancer-X, revvy and Exophase -- for supporting\ndevelopment this year.\n\nUSERS\n\n+ Added support for loop markers in WAV and OGG files. The WAV\n  loop support uses the \"smpl\" chunk used by ModPlug Tracker\n  and Wavosaur among others. Only the first loop is used, and\n  only forward looping is supported. The OGG loop support uses\n  the \"LOOPSTART\" and \"LOOPLENGTH\" tags as used by RPG Maker\n  VX. (Mr_Alert)\n+ Added OpenGL Shader Language (glsl) renderer which uses\n  shaders to render and scale the video. This renderer is\n  compatible with Open GL >=2.0 and Open GL-ES 2.0 video cards\n  only. A variety of shader programs have been provided and\n  these can be customized. Performance of all MZX modes\n  (including SMZX) is excellent. (Logicow, ajs)\n+ Files will no longer be silently overwritten by save dialogs\n  if the user enters an existing filename without the default\n  extension. (revvy)\n+ The string editor in the counter debug menu (F11) now escapes\n  newlines and backslashes to prevent UI corruption.\n+ Fixed a bug where the LOAD_GAME counter handler could\n  continue to use the old board state after load, causing\n  crashes. (Lancer-X)\n+ Fixed a bug where status counters containing numbers >6\n  characters would cause MegaZeux to crash or behave strangely.\n+ Fixed a bug in the robot editor's find/replace function that\n  caused crashes when replacing a string with another longer\n  string, with a replacement at the end of a line.\n+ Programmatically writing to a read-only \"built-in\" counter\n  will no longer allocate it general heap space. This prevents\n  writes from showing up in the F11 counter debugger that are\n  inaccessible from robotic.\n+ Fixed a bug that caused the SMZX mode 3 palette to become\n  corrupted upon entering the char editor (the editor would\n  re-write colours 2-4 and not restore them from backup\n  correctly).\n+ Fixed a bug where a robot program would never progress if the\n  subroutine stack was popped more times than it was pushed\n  (via return or top).\n+ On UNIX platforms a desktop/menu entry is now installed by\n  default, using the existing icon. (Sci-freak)\n+ Clamp score to >= 0 if world <= 2.70. Fixes \"Gates: The\n  Puzzles\" and possibly other old titles depending on this\n  behavior. (Exophase)\n+ Fullscreen modes will now automatically use your current\n  desktop resolution if using any hardware renderer (i.e. not\n  the default software renderer). To get the old behaviour back\n  you must set fullscreen_resolution explicitly.\n+ Fixed a bug where web and thick web would be treated the\n  same.\n+ Fixed a bug in the updater where modified/replaced files\n  would be considered for deletion.\n+ Fixed a bug on case-sensitive filesystems where saving a\n  game, world or MZM could fail to overwrite any existing file\n  by the same name (if matched case insensitively).\n+ MZX no longer applies masking to chars 32-127 in signs or\n  scrolls when playing a world. Previously, even the \n  mask_midchars option would have no effect on the display of\n  signs or scrolls. This has been broken since 2.80g.\n+ Re-worked board editor's Alt+H option to provide minimal\n  editor (one row) status info, rather than completely hiding\n  the help.\n+ The checkres utility now checks the global robot for missing\n  resources too.\n+ Chests can be added with Hi Bombs (omission noted by zzo38).\n+ Fix IF c?? Sprite p?? # # \"label\" so that a non-wildcard\n  parameter is respected (previously it would always just check\n  sprite 0).\n+ NDS port updated from dsmzx2 release. (kvance, ajs)\n+ Updated SDL to 1.2.14 in Windows x86, Windows x64 and Mac OS\n  X builds.\n+ Security checks are no longer applied to filenames in module\n  or sample playback in \"listening only\" modes in the editor.\n+ Module volume is applied immediately before playback upon\n  switching boards. This prevents one cycle of audio \"leaking\"\n  at the wrong volume.\n+ Prevent crash with negative string clip where clip + offset =\n  0. Clip is now correctly limited to total string length.\n+ Help file is now optional for MZXRun, even with\n  CONFIG_HELPSYS=1 builds.\n+ Fixed crash when robot editor macros expanded other macros.\n+ The lock icon is no longer missing from the Items THING menu\n  (F4).\n+ A world to start up with can now be passed to MegaZeux\n  without the startup_file= prefix. This makes megazeux\n  consistent with other applications.\n+ SHOOT, SHOOTMISSILE, SHOOTSEEKER and SPITFIRE now end the\n  cycle, to restore compatibility with pre-port MZX and fix\n  games such as Kya's Sword and Stones & Roks II.\n+ Have IF [dir] PLAYER [color] [thing] [param] \"label\"\n  interpret SEEK direction wrt robot coordinates, rather than\n  player coordinates. Other directions are not affected.\n+ Zapping a label at the end of a robot program will no longer\n  corrupt the robot list (which usually caused crashes).\n+ Entering lines in the robot editor with leading or trailing\n  spaces will be trimmed before the line is compiled.\n+ The single quote characters encasing S_CHARACTER parameters\n  in the robot editor will now use the protected (GUI) charset\n  rather than the game one.\n+ Added a \"system_mouse\" config.txt option that allows the\n  mouse cursor to be replaced with the system mouse cursor,\n  rather than being drawn by MegaZeux.\n+ Disallowed placing player clones with SET \"board_id\" 127.\n+ Relaxed file name limit on board MOD file. The board MOD can\n  now be as long as the limit imposed by file dialog's input\n  box (previously limited to 12 characters).\n+ Truncation of currently open input/output file names will now\n  only occur at MAX_PATH bytes (typically 512 characters). The\n  previous limit was 13 characters.\n+ Relaxed limit of INPUT string and bottom (\"*\") messages from\n  80 characters to ROBOT_MAX_TR (512) characters. In the case\n  of bottom messages this can be usefully exploited to ~~200\n  characters.\n+ Progress meter will be shown for world decrypt on console\n  platforms.\n+ Fixed a bug where a malformatted BMP header would be written\n  (length too short, didn't include dummy channel in BMP\n  palette). (Mr_Alert)\n+ Optimized audio locking; do file I/O outside of critical\n  sections to decrease stalling, particularly on platforms with\n  slow I/O. (Mr_Alert)\n+ Added support for MacOS 10.6 (Snow Leopard) and removed\n  support for 10.3.\n+ Loading a save game from robotic will now correctly restore\n  intensities to their saved values.\n+ Copy/pasting a block either with COPY BLOCK or the editor,\n  where the copy would exceed the limit on robots/signs/scrolls\n  /sensors, will no longer place junk at the target\n  co-ordinates. Instead, the object's background will be copied\n  in isolation.\n+ Pasting from the clipboard, expanding a macro,  or importing\n  .txt or .bc files that would cause a robot to exceed the 64k\n  limit now has the operation ignored at the point it exceeds\n  the limit, rather than adding an unlimited number of\n  unrecognized lines.\n\nDEVELOPERS\n\n+ MZXRun compilation can now be disabled. Compilation of\n  pre-2.82b style non-modular builds requires\n  `--disable-modular --disable-mzxrun'.\n+ Disabling SDL can now be done with --disable-sdl and the\n  resulting configuration will automatically disable any\n  SDL-dependent components. This is useless to anybody except\n  developers doing new ports.\n+ Game directory, utility directory and resource directories\n  can now be specified and will be respected on \"make install\".\n  (Sci-freak, ajs)\n+ Added experimental port to Android. See arch/android/README\n  for more information.\n+ Ported opengl1 and opengl2 renderers to OpenGL ES 1.x and\n  glsl renderer to OpenGL ES 2.0, used increasingly by mobile\n  devices.\n+ Removed SDL dependency from NDS port. It only used it for\n  timing and stuffed events.\n+ On Windows platforms, binaries are processed with the `pefix'\n  in-tree tool to eliminate data section differences in\n  programs with identical texts. This minimizes the amount of\n  content required to be sent for updates.\n+ Updated Wii port: improved audio and video support, added USB\n  mouse support, numerous optimizations and improved file\n  selector. (Mr_Alert)\n  \n>#MAIN.HLP:072:Table of Contents\n\nDecember 29, 2008 - MZX 2.82b\n\nThis release contains plenty of important bug fixes, ranging\nfrom regressions such as the broken command-line editor macro\nexpansion to third party bugs like the Windows \"directx\" SDL\nvideo driver breakage.\n\nThere are also some new ports and features. MegaZeux now runs\non the Wii (port by Mr_Alert) and AmigaOS 4.x (port by myself\nand Spot from os4depot). The Windows x64 port has matured\nimmensely and can now be considered stable. MacOS X builds now\nhave clipboard support. The hardware stretching renderers now\nhave a couple of fixed aspect ratio modes.\n\nThe biggest feature of this release is the introduction of a\nportable network layer, which is currently being tested by the\nnew built-in updater (F7/U).\n\nInternally, MegaZeux is now modularized and builds as several\nDLL and EXE files, which should make redesigning parts like the\nhelp system and editor a little easier, as well as allowing us\nto ship a \"mzxrun\" executable for the first time since 2.69c.\nThis \"mzxrun\" executable is now used by a majority of the\nconsole ports.\n\nContributions from Revvy, Mr_Alert, Terryn and Exophase have\nhelped make this another solid release.\n\nUSERS\n\n+ Writing to $str.length (which previously did undefined\n  things) will now truncate or enlarge the string to the size\n  specified.\n+ Removed filename size limit for FWRITE_MODIFY and\n  FWRITE_APPEND. (Revvy)\n+ Added support to the checkres tool to check worlds in\n  non-local directories. (Revvy)\n+ Fixed an old bug with saving games and worlds from Robotic\n  where a board could be prematurely \"optimized\", renumbering\n  robot IDs within the same cycle. For commands like DIE this\n  could cause unpredictable behaviour or simply crashes (if\n  invoked in the same cycle). As a special case, end the cycle\n  if either of these SET specials are used.\n+ Mistaken or malicious file I/O such as set \"$test\" to\n  \"fread(-1>>1)\" will no longer crash MegaZeux. The read size\n  will be truncated to a contextual maximum for the current\n  file.\n+ Fixed a crash using \"fwrite0\" in conjunction with an empty\n  string.\n+ Fixed a bug where checking sprite_collisions on a disabled\n  target sprite would unconditionally trigger (regardless of\n  whether a collision was present or not).\n+ Un-grouped the handling of the KEY and KEYn counters so that\n  different compatibility checks can be applied to either\n  counter. Fixes \"Bocco Chronicles 1\" and probably several\n  other titles.\n+ Fixed a crash when using RIDn or ROBOT_ID_n in the same cycle\n  as DIE for another robot positioned earlier in the board\n  scan.\n+ Fixed poor sanity checks on BOARD_ID counter writes. Illegal\n  character IDs such as -1 can no longer be used to bypass the\n  check (causing subsequent crashes).\n+ Windows builds now use a patched version of SDL 1.2.13\n  containing a fix for the directx+F10 issue.\n+ Fixed a bug where one robot could send another robot to\n  \"#return\", with an address outside its program. In such\n  cases, the robot will now terminate.\n+ An \"mzxrun\" binary is now shipped alongside the\n  editor-capable MZX binary.\n+ Fixed TIME/TIMERESET overflows with very large values. Board\n  timeout is now programmatically limited to 32767, which is\n  consistent with the Board Info control.\n+ Clamped CHAR_X/CHAR_Y properly so that negative numbers can\n  no longer be used to corrupt the editor charset and\n  potentially other process memory.\n+ Fixed recent breakage of SHIFT+F{1,2,3,4} so that the\n  percentage of time spent displaying the original character\n  and the '!' are equal.\n+ Removed some bogus handling of lines containing \"only\" ';',\n  ',' or ' '.\n+ Honor user's robot character selection if they are holding\n  shift when pressing return or space (would previously always\n  return char 247).\n+ Backspacing a line and then expanding a macro no longer\n  restores the original line contents immediately after the\n  expansion.\n+ MacOS 10.x clipboard support (via Cocoa Pasteboard). Alt+Ins\n  can be entered with Fn+Alt+Numpad0 on a Macbook or Powerbook\n  keyboard.\n+ Robot editor S_CHARACTER fields no longer bogusly escape\n  characters such as \". In addition, rendering glitches are no\n  longer encountered when using the ' S_CHARACTER (which is\n  completely reasonable).\n+ Fixed robot editor glitches where the game charset SPACE\n  would be used in places where the protected UI charset should\n  be used instead.\n+ The introductory help message is displayed if the load dialog\n  is cancelled prior to loading a game. Hopefully the screen is\n  now never totally blank.\n+ The F7/F8 cheats can now be used freely in MZXRun (in\n  MegaZeux proper they remain usable only in editor tests).\n+ Saving to a directory above the MegaZeux startup directory,\n  then attempting to save to this location again, will no\n  longer crash MegaZeux. Instead, the parent directory will be\n  changed into before the dialog is displayed.\n+ Fixed numerous crash bugs with the scroll editor; it should\n  be relatively usable now.\n+ Writing to $str+0 is no longer interpreted in the same way as\n  a plain write to $str. Instead, it behaves like writes to\n  non-zero offsets (as more of a paste than a replace).\n+ Display of current X,Y position of robot in the robot editor\n  status bar.\n+ Fixed directory rename so that it no longer displays garbage\n  and/or crashes MegaZeux (Alt+R to rename a directory in any\n  file picker).\n+ In the robot editor, lines can now be split at a midpoint\n  with enter and two consecutive lines merged together with\n  backspace. (Exophase)\n+ Fixed use of status counter 6 and display of status counters\n  in general, which has been broken since 2.80.\n+ Fixed swapping to encrypted worlds if initially the user\n  decides to not decrypt the world. Previously, this would\n  either crash, or loop forever.\n+ When using the OpenGL or overlay renderers, in either\n  windowed or fullscreen mode, the aspect ratio can now be\n  preserved as either 4:3 (most similar to DOS) or 64:35 (most\n  similar to the port). The display will be letterboxed or\n  margins applied as appropriate. See the \"video_ratio\"\n  configuration option for more information.\n+ Fixed a bug on some systems where numlock could not be used\n  as a key, only as a flag. The numlock \"key\" is now masked out\n  of \"key_code\" and similar; hopefully this won't break any\n  games.\n+ Restored the meter widget from the old DOS MZX for use with\n  the world loader and saver routines. This reassures users,\n  especially on consoles, where loading a world can take a long\n  time. (Mr_Alert)\n+ On Windows, directx.bat now passes %cd% through to `start' so\n  that features such as the updater continue to work.\n  (MZXGiant)\n+ Pasting into a string with set \"$str+N\" with an N > \n  str.length will no longer crash MZX.\n+ \"Exit to DOS\" is now \"Exit MegaZeux\" to reflect the\n  multi-platform nature of the program.\n+ Setting a substring size to zero with $string#0 will no\n  longer return the whole string; it will instead return the\n  empty string.\n+ Accessing a substring with an offset >= $string.length will\n  no longer return the last character from the string; it will\n  instead return the empty string.\n+ Writing beyond MAX_STRING_LEN (1MB) or using negative offsets\n  (which has the same effect) no longer crashes. Instead, the\n  write is ignored.\n+ Fixed crash when pasting to and from the same string,\n  specifically in conjunction with $str+offset.\n- Removed the legacy \"force_resolution\" option which was\n  replaced long ago by the more accurate\n  \"fullscreen_resolution\" option.\n\nDEVELOPERS\n\n+ Ported to OpenSolaris. You need to install `SUNWxorg-headers'\n  if you want X11 clipboard support.\n+ Removed dependency on SDL_image on non-win32 platforms when\n  enabling the icon branding feature (see pngops.c).\n+ Ported to AmigaOS. You need to install the clib2 version of\n  libSDL and miniGL, and the build system assumes you are using\n  a cross compiler.\n+ Added experimental port to the Wii. See arch/wii/README for\n  more information. (Mr_Alert)\n+ get_path() in util.c now returns <0 for failure, or the\n  length of the path for the given file. (Revvy)\n+ Added the make time WHOLE_PROGRAM=1 flag which enables\n  compilation of the core binary in GCC's \"-fwhole-program\n  --combine\" mode. This makes all symbols static and improves\n  optimization, somewhat like MSVC's LTO does.\n+ Added a valgrind.supp file to suppress bugs in third party\n  libraries when valgrinding MegaZeux.\n+ Cleaned up all the ports and documented making new ports. The\n  platforms \"linux\", \"solaris\" and \"obsd\" are now called\n  \"unix\", and the \"linux-static\" platform is now \"unix-devel\"\n  and available on all UNIX derivatives/clones.\n+ Added a special hack to enable linking with --as-needed for\n  DT_NEEDED link optimization for GNU ld platforms.\n+ Updated MSVC projects. Fixed all warnings emitted by MSVC\n  2008, and implemented icon support with existing mingw\n  resource files.\n+ Now uses the GNU ld \"debuglink\" feature on all platforms to\n  enable shipping of a side-by-side symbol package. Optimized\n  release builds can now be debugged with minimal user effort.\n+ MegaZeux now provides the option for \"modular\" linkage,\n  factoring out the \"core\", \"editor\" and \"network\" features to\n  shared objects that other binaries can link against. This\n  feature works on the unix, mingw, amiga and darwin ports.\n+ Added RPM .spec file. Capable of building (at least) Fedora\n  10 RPMs.\n- Removed HOST_CC feature for cross compilation; since the\n  utilities now intimately depend on the MZX runtime, they must\n  be built with the same compiler.\n  \n>#MAIN.HLP:072:Table of Contents\n\nJune 10, 2008 - MZX 2.82\n\nDespite the increase in minor version, this release mostly\ntargets bug and regression fixes. However, there ARE some\nadditional new features, such as the introduction of the\nLOAD_MENU and mouse pixel counters, and refinement of the\n{FREAD,FWRITE}_COUNTER counters. (There are several other\nsmaller features that are documented in the changelog.)\n\nSAVs from older worlds (requiring compatibility hacks) no\nlonger fail to play (Darkness, etc. are affected). We've also\ndone a good bit to fix compatibility with 2.70 and older.\n\nA new tool, \"checkres\", is now routinely packaged, allowing you\nto check your games for missing resource files (PALs, CHRs,\netc.) before passing them on to other people. This should be\nespecially handy for DoZ game submissions.\n\nThe Nintendo DS port (Kevin Vance's \"DsMZX\") has been merged\ninto this release. I'll provide binaries for GP2X, PSP and NDS\nthis time, but I can't guarantee they'll work.\n\nBoth of the snags from the last DoZ have been addressed -- the\nhelp system should no longer crash and the Block Action crashes\nshould be reduced in frequency. However, there are still issues\nwith pasting in the robot editor that remain unfixed (they're\njust really hard to reproduce). With your bug reports, I look\nforward to fixing this.\n\nAs usual, thanks go out to Revvy and Mr_Alert for their\ncontributions to the bug-fixing effort, and to Terryn for his\nunwavering dedication to creating and organising bug reports,\nand for testing our bug fixes.\n\nUSERS\n\n+ Fixed and improved quality of the half-width renderer for the\n  GP2X port (Mr_Alert).\n+ The numpad now works correctly when numlock is disabled. Keys\n  are no longer ignored by the MZX editor, and games should\n  recognize them as before.\n+ Added a tool, \"checkres\", which extracts all resources from a\n  MegaZeux world or board file and lists them (or lists only\n  those which are not found in the game directory). ZIP files\n  are also supported (to a more limited extent). (ajs & Revvy,\n  ideas from Exophase & Terryn).\n+ Removed the bogus \"F1 for Help\" option from error dialogs,\n  and finally got rid of the \"** BETA **\" banner on title\n  boards in play mode.\n+ Obsoleted support for the AMS, DBM, DMF, MDL, MT2, PSM, PTM\n  and UMX module formats. As noted for several versions in the\n  help file, these are not loadable by MikMod. It is extremely\n  unlikely any game uses these obscure formats, but denying\n  their use is now enforced (at a robotic level).\n+ Fixed crash when writing to an MZX string at an illegal\n  offset (< 0).\n+ Fixed returning from a subroutine invoked by a jump from a\n  MZX text box class command so that it no longer skips the\n  next impending line (after the text box).\n+ Assembled single non-alphanum/punctuation characters as\n  bytecode CHARACTER instead of bytecode STRING. Fixes bogus\n  auto-quoting for commands like SCROLL CHAR (Revvy).\n+ Switched the Win32 package back to using the \"windib\" SDL\n  video driver, instead of the \"directx\" SDL video driver. The\n  windib.bat file has been replaced with directx.bat, which has\n  opposing semantics.\n+ SAM/GDMs with converted WAV/S3M counterparts of zero length\n  will be automatically re-converted. This hack can be used to\n  procedurally regenerate WAV files from SAMs, or transparently\n  work around on-disk corruption.\n+ Strings are now limited to a maximum length of 1M. I'm open\n  to suggestions over a better limit, but there must be a limit\n  (set \"$string.X\" notation grows a string arbitrarily, so\n  robotic can crash MZX when a string is resized beyond a\n  reasonable limit).\n+ Strings, when grown, will fill gaps with ' ' instead of\n  garbage. This can be useful when the string grows after using\n  the set \"$string.X\" notation; the rest of the string is no\n  longer garbage, allowing the debugger to be used.\n+ A robot that does a \"put c?? Thing p?? [dir] player\" and\n  overwrites itself will no longer leak commands. Instead, if\n  the robot overwrites itself, its program will end.\n+ Fixed message edges always showing up black, instead of \n  whatever color 0 is. (Revvy)\n+ Changed starting/max health and lives minimum to 1 instead of\n  0. (Revvy)\n+ Some help system (F1) bugs have been fixed, hopefully\n  mitigating some of the crashes people have been seeing.\n+ Fixed a bug on Linux where fclose() on a robot-opened file\n  could, on world reload, occassionally crash (due to a stale\n  handle). Fixes loading Taoyarin saves multiple times in a\n  row.\n+ The new option \"gl_vsync\" has been added to allow the SDL\n  \"flip on vsync\" in the OpenGL renderers to be forcibly\n  enabled or disabled. This fixes a problem where speed 1 would\n  only be as fast as the video refresh rate.\n+ Setting the music volume to 0 (when using the ModPlug engine)\n  now ensures that no music is audible. Previously, setting the\n  volume to 0 would be equivalent to setting the volume to 1,\n  which was still audible.\n+ Upon exiting the initial load screen, and not entering the\n  editor, the screen is now updated. This fixes rendering\n  glitches in the MZX game window when overlapping the window\n  with another, at the slight expense of CPU time.\n+ If loading a save game from the title screen (or when no\n  world has been loaded) MZX no longer sends JUSTENTERED to all\n  robots. This restores compatibility with MZX 2.70 and is\n  consistent with loading a save from another board.\n+ Counters with 10 digits and a negative sign are no longer\n  truncated in the debug menu.\n+ Correctly clamped (rather than truncating) the value passed\n  through to a SET COLOR. Restores compatibility with 2.70, and\n  fixes Xenogenesis.\n+ Improved clipboard copy behaviour on Linux. Some actions are\n  still mysteriously broken.\n+ Fixed replacing with a blank string in conjunction with the\n  replace all Ctrl+F action in the robot editor. The cursor can\n  now no longer become negative, fixing numerous possible\n  crashes on search/replace.\n+ Fixed loading the intrinsic SMZX palette when switching to\n  SMZX modes from a game not in the same directory as the\n  \"smzx.pal\" file.\n+ Reloading a world that requires switching between SMZX and\n  non-SMZX modes will now respect the world's intrinsic palette\n  on the title screen. Fixes problems loading non-SMZX games\n  after having an SMZX game loaded.\n+ Clamped array offsets on boards. Some older MZX worlds are\n  corrupted and have the endgame_{x,y} coordinates outside of\n  the limits of the endgame board. Fixes \"Fourth Power\".\n+ Where possible, versioned all counters that the port\n  understands. This ensures that in the unlikely case that a\n  game made with an older version of MZX (actually, with an\n  older world magic) uses a counter that did not exist in that\n  game's era, the port will no longer try to interpret it.\n  Previously, only rid? and key? were versioned.\n+ SAV files will now be stamped with the world magic of the\n  world they were loaded from. This allows compatibility hacks\n  to apply to SAV files as they would to worlds (ajs, Terryn,\n  Mr_Alert).\n+ Added LOAD_MENU counter like ENTER_MENU, F2_MENU et al. to\n  allow control (from Robotic) over whether the LOAD_MENU can\n  be brought up.\n+ Made FREAD_COUNTER and FWRITE_COUNTER read in a DOS dword\n  (32bit) instead of a DOS word (16bit). This allows modern\n  post-port MZX counters to be fully represented in files.\n  Compatibility with older worlds is preserved.\n+ Added a new config option \"board_editor_hide_help\" which\n  changes the default hide setting of the help text on the\n  primary board editor.\n+ Numerous fixes for bugs found by valgrind. (Nightwatch)\n+ Icon support is now fixed and works on all platforms. On\n  Windows, the icon cannot currently be changed (it is loaded\n  from the EXE's resource section). Use ResHacker if you really\n  want to change it.\n+ Fixed a bug where either LOAD_ROBOTn or LOAD_BCn (where n was\n  equal to ROBOT_ID) would alter the robot's line number rather\n  than completely restart it. Due to complexities in robot\n  context, this lead to the first line being skipped.\n+ Added a new tool \"downver\" which supports drag-and-drop\n  downgrading of a world or board from the version of MZX it is\n  packaged with to the previous version of MZX. This tool may\n  be unsafe to use -- be careful.\n+ Fixed a bug in the robotic assembler which would\n  occassionally emit corrupt programs with SAVE_ROBOT. These\n  programs, if loaded by LOAD_ROBOT, could cause a crash.\n+ Added a config.txt (or command line) option \"startup_editor\"\n  which, if set to a non-zero value, will start MegaZeux in the\n  editor with a blank world.\n+ Fixed a bug where a robot's WALK processing, on entering a\n  transporter, could allow subsequent commands (such as GO) to\n  corrupt the board. WALK now ends the cycle in the special\n  case that a robot goes through a transporter.\n+ You can now directly import bytecode into the robot editor\n  via the Alt+I menu. The extension for the bytecode file must\n  be .bc for it to be loaded.\n+ A game loading SAVs via the LOAD_GAME counter will no longer\n  crash MZX if the SAV attempted is from an incompatible\n  version of MZX, or in any way corrupted.\n+ Fixed a crash when auto-completing lines that were greater\n  than 241 characters in length after completion.\n+ Added mouse pixel counters MOUSEPX and MOUSEPY. (Mr_Alert)\n+ Commenting a line of maximum length (241 characters) can no\n  longer grow the length of the line beyond this limit.\n+ Fixed a bug causing the software renderer to fail to center\n  when using a boxed fullscreen resolution. Also fixes a bug\n  where the PSP platform would ignore an override of the\n  force_bpp option. (Mr_Alert)\n+ Fixed a bug causing macros loaded from config.txt to be\n  expanded incorrectly. Relatedly, fixed a bug where #<string>\n  in the robot exitor would \"disappear\" on entry, if there was\n  no correspondingly named macro.\n\nDEVELOPERS\n\n+ Builds no longer initialize the SDL audio subsystem if audio\n  is permanently disabled with --disable-audio.\n+ Added fixes for OpenBSD to allow PNG screenshots and X11\n  clipboard support to work. Tested with OpenBSD 4.2 and \n  GCC 3.3.5.\n+ Updated Win32 builds with SDL 1.2.13.\n+ Build and package two utilities, txt2hlp (for helpfile\n  generation) and checkres, on Win32.\n+ Dependencies are now correctly tracked in the build system.\n  Modifying a header will automatically regenerate the minimal\n  set of object files that depend on this header.\n+ Out of the box MSVC support. The file \"msvc.zip\" in the root\n  of the source package now provides a Visual Studio 2005\n  project and pre-compiled dependencies. There may be stability\n  issues with the resulting binary. See also the documentation\n  in arch/msvc/README.txt.\n+ The Nintendo DS port (a.k.a. 'dsmzx') has been merged. This\n  is the most exotic port thus far, and adds features such as\n  player focus (on the second display). Sound isn't working\n  yet, and large games still won't play (due to lack of\n  memory). See docs/nds.txt for more information. (kvance)\n+ Many stack-heavy functions have been de-bloated and allocate\n  large storage on the heap (if performance is not critical).\n  This helps out platforms with a small, fixed stack size (such\n  as NDS).\n+ The built-in help system can now be disabled for embedded\n  platforms. The startup check for the help file will not be\n  performed if the help system is disabled, and so this file\n  can be omitted from distributions.\n+ The package.sh script now supports OS X, PSP, GP2X and NDS\n  packaging.\n+ The OS X port no longer requires Xcode. The new build system\n  and package.sh can create a universal Application and\n  corresponding DMG file. The new infrastructure deprecates the\n  old macosx.zip method.\n+ Most of the internal dependency on SDL has been removed.\n  Therefore, MZX can be built (but not yet work) without SDL\n  present. The only remaining component to convert is MikMod,\n  but this can be disabled, so port authors can start using the\n  feature right away (see config.sh). (Mr_Alert)\n\n>#NEWINVER.HLP:1st:NEW in MegaZeux!\n>#292CLOG.HLP:292:New in Versions 2.92 to 2.92f\n>#2901CLOG.HLP:291:New in Versions 2.90 to 2.91j\n>#284CLOG.HLP:284:New in Versions 2.84 to 2.84c\n>#281CLOG.HLP:281:New in Versions 2.81 to 2.81h\n>#280CLOG.HLP:280:New in Versions 2.80 to 2.80h\n>#OLDERVER.HLP:260:New in Versions 2.60 to 2.70\n>#OLDESVER.HLP:201:New in Versions 2.01 to 2.60\n>#NEWIN200.HLP:1st:New in Version 2.00\n>#ANCENVER.HLP:1xx:New in Versions Through 1.03\n>#MAIN.HLP:072:Table of Contents\n#281CLOG.HLP\n:281:\n$~9New in Versions 2.81 to 2.81h\n\nDecember 8, 2007 - MZX 2.81h\n\nAnother bugfix release with a couple of new features, in time\nfor the Winter 2007 Dualstream Day of Zeux. The major new\nfeatures of this release are automatic module renaming in the\neditor, PNG screenshots and many improvements to MZX on\nembedded platforms (like PSP and DS).\n\nMegaZeux can now be compiled in MZXRun mode (like the old DOS\nimplementation) and by disabling features such as unnecessary\nrenderers and audio support, can be made approximately 70%\nsmaller.\n\nThanks again to Terryn for relentlessly tracking down many\nserious bugs; we've tried to fix all the issues that have crept\nup in the last 5 months.\n\nThanks too to Exophase, Mr_Alert and Wervyn for contributing to\nthis release; your time and help is invaluable.\n\nHappy Holidays!\n\nUSERS\n\n+ Added a more lenient WAV file loader so that ModPlug isn't\n  relied on as much to play malformed WAV files (mostly old SAM\n  conversions) (Mr_Alert).\n+ Added SCORE and mzx_speed to the counter debugger\n  (Mr_Alert, ajs).\n+ Added a 16-bit software renderer and a half-width renderer\n  for the GP2X port (Mr_Alert).\n+ Made the mouse cursor in the \"opengl2\" renderer look more\n  like the mouse cursor in the other renderers (Mr_Alert).\n+ Setting vlayer_size, vlayer_width or vlayer_height to values\n  less than or equal to zero would crash MegaZeux. Limited the\n  smallest vlayer size to 1x1.\n+ Setting vlayer_width or vlayer_height to a value larger than\n  vlayer_size would crash MegaZeux. Limited the largest size of\n  either dimension to a maximum of vlayer_size.\n+ If selecting a module with a non 8.3 filename, MZX will now\n  ask you if you want to rename it to 8.3, and do so in an\n  intelligent way. This means that music can be selected in the\n  editor and correctly saved (Wervyn).\n+ The OpenBSD compiler detected some serious string bugs in\n  MegaZeux. These have now been fixed and should eliminate some\n  more potential crashes.\n+ Fixed a bug where an ENERGIZER item or use of the INVINCO\n  counter would cause the original player color to be corrupted\n  at the end of the colour blitz.\n+ Fixed a long-standing bug where set \"$string\" to \"FWRITEn\"\n  would be cheerfully ignored.\n+ Fixed a bug where a corrupt robot list could crash MegaZeux\n  (e.g. the list from Star Quest from DoZ'02).\n+ FEATURE: Screenshots are now saved in a palettized PNG file\n  format. For platforms without libpng, PNG support can be\n  compiled out, and BMP will be used instead.\n+ Fixed a bug where changing boards in the editor could\n  sometimes corrupt memory, later causing a crash (either\n  testing or coming out of testing a board).\n+ Fixed a sensor bug that happens when a sensor can't go\n  anywhere it is told to, and the player is on it (Exophase).\n+ Fixed using ABORT LOOP in some situations. Using it outside\n  of a loop still has undefined semantics and this has been\n  documented in the help file (Exophase).\n+ Setting a board option below its numeric limit is no longer\n  possible (Exophase).\n+ Fixed problems with going over Robot name character limits\n  using the .@@ command (Exophase).\n+ Fixed problems with LOAD_ROBOT freezing on a robot with no\n  newline at the end of the file.\n+ Fixed a problem with \"Replace All\" in the robotic editor that\n  could sometimes cause a line to exceed 240 characters and\n  crash the editor.\n+ Fixed a problem with \"Replace\" in the robotic editor which\n  could cause a line to temporarily become 241 characters and\n  then truncate silently to 240 characters.\n+ Fixed a bug that caused the original game palette to be lost\n  when testing a game in the editor that switched between\n  Regular/SMZX1 and SMZX2/3 modes. MegaZeux should now try much\n  harder to preserve the user palette, regardless of game\n  edits.\n+ Fixed a bug causing board switching to not correctly alter\n  the x,y viewport scroll leading to the display of raw memory\n  and potential crashes, with differently sized boards.\n+ Fixed stack corruption caused by SCROLL CHAR SOUTH, detected\n  by Ubuntu's SSP (Stack Smashing Protection) enabled binary.\n\nDEVELOPERS\n\n+ Made the build system less verbose by default (like Linux).\n  This should help make warnings (due to coding errors) easier\n  to identify. If you don't like the new syntax, or need the\n  command debug, you can build with \"make V=1\".\n+ Updated Win32 builds with SDL 1.2.12.\n+ Rewrote the build system to not use recursive Makefiles.\n  Variable propagation was starting to be a problem, and\n  recursive designs are generally discouraged.\n+ Refactored the graphics rendering code to modularize the\n  renderers and reduce code duplication (Mr_Alert).\n+ GDM2S3M switched over to use inttypes.h instead of home-brew\n  types.\n+ MegaZeux now compiles on OpenBSD (and probably other BSDs).\n+ Made all unnecessary global symbols static. This should\n  improve compiler optimisations and correctness (Mr_Alert,\n  ajs).\n+ Fixed compilation of MegaZeux against SDL 1.3 SVN. However,\n  this SDL version is still in development, and MegaZeux does\n  not work correctly when compiled against it.\n+ MegaZeux now builds with the experimental MINGW-x64 branch,\n  enabling x64 binaries for Windows.\n+ MegaZeux now builds with MSVC if you apply the patch from\n  contrib/,megazeux-r326-replace-c99-variable-arrays-with-\n  malloc-free.diff . This patch is required for MSVC because it\n  makes non-compiler-specific changes (which involve converting\n  from C99 variable length arrays to malloc/free) which are\n  slower and should not be used with competent C99 compilers\n  like GCC. Microsoft Visual C++ Express Edition 2005 was used\n  to build libogg, libvorbis, libsdl and MegaZeux itself. Only\n  32bit builds were tested.\n+ MegaZeux now has size optimisations which can reduce binary\n  size when features are disabled. For example, all renderers\n  can now be disabled, and when module engines are disabled,\n  audio will not export any symbols.\n+ The entire audio subsystem can now be disabled. This further\n  reduces binary size on embedded platforms. However, SFX\n  editing still remains enabled (though useless) until editor\n  modularity is implemented.\n+ The PSP port is now officially supported, and compiles out of\n  the box. See docs/psp.txt.\n+ Renamed macos platform \"darwin\", to reflect its true nature\n  (use Xcode to build as a real Application, instead of just a\n  UNIX binary). Also fixed some bogosities with robo_ed's X11\n  includes on OS 10.5.\n+ The editor can now be disabled, a la MZXRUN from the old DOS\n  versions. Configure with --disable-editor to shrink MZX by\n  about 150k.\n+ MegaZeux can now be compiled with size optimisations\n  (--optimize-size to config.sh) for a 20% space saving.\n+ MegaZeux's core now builds with -W (basically all GCC\n  warnings) plus some additional warnings that aren't switched\n  on by this flag. All warnings have been fixed.\n  \n>#MAIN.HLP:072:Table of Contents\n\nJuly 4, 2007 - MZX 2.81g\n\nAgain, no significant new features have been introduced in this\nrelease. However, there have been many essential bugfixes,\nincluding improved compatibility with games made in older\nversions of MegaZeux.\n\nAdditionally, improvements have been made to the opengl2 and\noverlay2 renderers, improving performance for most users. A\nport of MegaZeux to the GP2X console has been added. MegaZeux\nhas been backported to C (rather than C++) and can operate\ncorrectly on a CPU without a floating-point unit.\n\nParticular thanks go out to Mr_Alert (for his valuable bug\nfixes), Lancer-X (for fixing what I was too lazy to) and Terryn\n(for finding many annoying bugs that nobody else could).\n\nUSERS\n\n+ Fixed a bug in the audio code. The linear resampler was not\n  taking volume into account, which broke changing the volume\n  of samples (WAV and Vorbis) which cannot natively alter their\n  volumes.\n+ Fixed a regression in the overlay editor caused by the new\n  editor space semantics.\n+ Screenshots are now rendered to a separate texture using the\n  8bit software renderer. This means that the hardware scalers\n  will not affect the quality of the screenshot. It also fixes\n  a bug when using opengl2, which would dump only a white\n  screen.\n+ Temporarily reverted a bugfix that broke Zeux IV - Forest of\n  Ruin. I'm not dropping the bugfix, I just can't immediately\n  see what's wrong.\n+ Fixed a bug where setting the viewport to negative\n  coordinates would crash MegaZeux. There was code to handle\n  this, but it was wrong.\n+ Fixed a bug that permitted the mouse y coordinate to be\n  warped to row 25, which does not exist. This bug caused some\n  of the renderers to crash, and the software renderer to draw\n  in memory it did not possess.\n+ Fixed a bug where games made before 2.68 could have available\n  the \"key?\" counters, unsupported in that version. This caused\n  collisions with counters with the key? name used with\n  inc/dec/mul/div/mod. Fixes \"Doom Keep\".\n+ Imported libmodplug 0.8.4, which adds MIDI/PAT and ABC format\n  support, fixes some bugs in the mixer, and should build on\n  more platforms.\n  NOTE: MID files currently cannot be selected in the editor,\n  because they do not play correctly.\n+ Improved the performance of the \"opengl2\" renderer, by\n  removing the convoluted 3D drawing commands and replacing\n  them with 2D ones. Reduced the quad count by using an\n  intermediary 80x25 texture. MegaZeux now depends on fewer GL\n  features. [LogiCow]\n+ Introduced an \"fsafegets\" to work around problems where\n  robots exported by a Windows version of MegaZeux would not\n  load on other platforms. This was due to differing EOL style\n  and broke at least one game (Termination).\n+ Renamed \"force_resolution\" to \"fullscreen_resolution\" to\n  better match its semantics with the scaling renderers. The\n  new name is less accurate for software render modes, but most\n  people using software will not want to change it from the\n  default anyway.\n+ Fixed a bug where the variable-length string allocator would\n  prematurely bail out when reading a string (of indeterminate\n  length) from a file with the set \"$var\" to \"FREAD\" syntax.\n+ Fixed a bug where more than 256 errors would crash the\n  robotic checker.\n+ Improved performance of the overlay2 (faster) renderer\n  (Mr_Alert).\n+ Made the transparent overlay \"really\" transparent when used\n  in conjunction with sprites (Mr_Alert).\n+ Fixed a bug reported by Mr_Alert where MZX would not handle\n  short, non-looping mods in the editor. The editor would try\n  to destroy the mod again, even after the callback had\n  destroyed it (premature termination).\n+ Fixed a bug with SWAP WORLD where file translation would\n  occur but the result would mistakenly not be used. This broke\n  some uses of SWAP WORLD on non-Windows platforms (Mr_Alert).\n+ Fixed a bug where using JUMP to MOD ORDER right after\n  switching boards would fail due to the board music not having\n  been loaded yet (Mr_Alert).\n+ Fixed a bug where games made before 2.80 would inadvertently\n  trigger \"PLAYERHURT\" due to using the SET command to reduce\n  the amount of health (Mr_Alert).\n+ Fixed a bug where player clones were generated when entering\n  transports during FREEZETIME (Lancer-X).\n+ Debug menu is now eradicated on leaving the editor\n  (Lancer-X).\n+ Debug menu is now properly painted over when the board size\n  is < the editor viewport. Fixes various graphical glitches\n  (Lancer-X).\n+ Fixed a crash bug when playing older MZX games from read-only\n  media (such as a CD) or where file-system permissions\n  prohibited creating SAM conversions (Lancer-X).\n+ Fixed bug where certain file formats would not be\n  automatically converted if their extensions were mixed or\n  upper case (e.g. OGG/SAM/GDM).\n+ Restored functionality of \"if lasttouch DIR\" which has been\n  broken since MZX 2.02.\n+ Fixed a bug where attempting to decrypt a read-only world\n  file would result in a crash (Mr_Alert).\n+ Fixed several bugs where an error loading a world file would\n  result in crashes in several different situations (Mr_Alert).\n+ Fixed a bug where a robot using the BECOME command to change\n  into a PushableRobot or vice versa would freeze (Mr_Alert).\n+ Fixed memory leaks in the file selection dialog, the counter\n  debugger, the collision list and the global robot (Mr_Alert).\n+ Updated counter list (see docs/counter_list.txt in the\n  source) (Terryn).\n+ Fixed a bug where pressing escape when editing the effect of\n  a ring or potion would result in an invalid parameter which\n  would later cause a crash if edited again (Mr_Alert).\n+ Fixed a bug in which robot-driven text boxes using option\n  commands (the ? command) could overflow by two characters and\n  spill over the side (Lancer-X).\n+ Fixed the list box searching mechanism (used in the file\n  manager and F11 counter list) and made the existing function\n  more understandable. (Lancer-X).\n+ Fixed a bug in which the message string given to the 'ask'\n  command could spill over. Now, the 'ask' dialog resizes if\n  possible, and clips when no further resizing can be performed\n  (Lancer-X).\n+ Clipped the 'input string' message properly, to prevent\n  similar overflow.\n+ Fixed a bug with the EXPLODE, DIE, DIE ITEM and BECOME\n  commands when used with the global robot (would clear the\n  global robot, eventually corrupting memory when in the\n  editor). Presumably, these commands are bogus for the global\n  robot, and have been disabled.\n\nDEVELOPERS\n\n+ Rewrote config.sh to use POSIX sh compatible functions, so\n  that there is no dependency on the BASH interpreter.\n  Surprisingly, some distributions still don't enable BASH by\n  default (using csh, ash or zsh instead).\n+ Ported most of MegaZeux back to C. Many more changes were\n  required than I anticipated; MZX was using more C++ features\n  than I expected. The only exception is audio.cpp, which\n  cannot be ported back to C because it uses ModPlug's C++\n  classes directly (but I plan to split this file up shortly).\n  NOTE: The changes required were enormous, so I might have\n  introduced some weird bugs! Please test!\n+ Enabled GCC's -W flag for even more warnings, switching off\n  unused parameter warnings (useful for delegates). Mostly\n  typing fixes, but it found a bug in string handling.\n+ No longer suppresses char-subscript warnings, and fixed up\n  any remaining abuses in the tree.\n+ Added manpages for 'megazeux' and other binaries for the\n  Debian packages. Complied with the Debian packaging\n  guidelines by providing a copyright note, listing significant\n  contributors to MegaZeux.\n+ Added support to the build system for supporting icons\n  modularly. See contrib/icons/README for more information.\n+ The debug build (make DEBUG=1) now enables GCC 4.x's stack\n  protector. This breaks compatibility with GCC 3.x, but you\n  can just remove the flag if you don't want to use it (the\n  stack protector will improve stack corruption detection and\n  provide more usable debug traces).\n+ Custom Random() implementation to provide a more uniform\n  number distribution. Factored out for future (better)\n  implementations.\n+ The audio backend (audio.cpp) has been modularised to support\n  the use of mikmod instead of modplug. This should enable\n  ports of MZX to platforms without an FPU, and improve\n  performance on platforms with weak FPUs.\n+ Added GP2X port to config.sh, based on work done by Simon\n  Parzer.\n  \n>#MAIN.HLP:072:Table of Contents\n\nJanuary 30, 2007 - MZX 2.81f\n\nThis release is mostly about the new renderers, the first of\nwhich was introduced in the previous version. There's also a\nfew important bugfixes, and a lot of internal tidy-up work. I'd\nlike to thank Mr_Alert, Quantum P. and LogiCow for contributing\nto this release. Thanks guys.\n\nUSERS\n\n+ Renamed the force_32bpp config option to force_bpp, in\n  preparation for 16bit OpenGL render modes. This option now\n  takes 8, 16 or 32. 16 is reportedly broken on Windows, so\n  stick to 32 for now.\n+ Added infrastructure for \"pluggable\" renderers. This code\n  isn't perfect, but it's far better than the mess in 'e'.\n  Defaults to the 'software' render mode.\n+ Added Logicow's alternative OpenGL renderer. For more\n  information about this renderer, see config.txt. NOTE: This\n  code may be buggy! Please test!\n+ Added Mr Alert's YUV overlay renderers. One does full YUV\n  macropixel approximation, the other (faster) render does\n  chroma supersampling. See config.txt for more information.\n  NOTE: This code may be buggy! Please test!\n+ Simplified Exophase's OpenGL renderer present in 'e', and\n  fixed a few bugs that caused it to not work for some people.\n+ Really made MegaZeux use 'directx' by default on Windows. The\n  code in 'e' was non-functional. Use 'windib.bat' to run\n  MegaZeux with the SDL windib driver.\n+ The OpenGL renderers now have a 'filter' option that allows\n  you to choose linear (where pixels are interpolated, looks\n  \"blurred\") or nearest (where nearest-neighbour approximation\n  occurs, looks \"sharp\").\n+ Mouse warping was broken when using any of the hardware\n  renderers. There should be code in there now to take account\n  of this (thanks Mr_Alert).\n+ Added an option 'editor_spaces_replace' which allows you to\n  revert MZX's space overwrite behavior to the semantics of 'd'\n  (the feature was removed in 'e'). By default, the behavior is\n  unchanged (the same as 'e').\n+ F6 (the debug menu) can now no longer be enabled anywhere but\n  in the editor Alt-T test mode. In 'e', it was possible to\n  enable on the title screen, but could not be enabled in a\n  game. Like the cheats, this option is now visible only in\n  test mode.\n+ Fixed a bug where the global robot could be exited via some\n  legal commands, in an abnormal fashion. The bug resulted in\n  all the code up to the offending command being executed over\n  and over.\n+ Fixed a bug in the audio code. The linear resampler was not\n  taking volume into account, which broke changing the volume\n  of samples (WAV and Vorbis) which cannot natively alter their\n  volumes.\n+ Fixed a regression in the overlay editor caused by the new\n  editor space semantics.\n+ Temporarily reverted a bugfix that broke Zeux IV - Forest of\n  Ruin. I'm not dropping the bugfix, I just can't immediately\n  see what's wrong.\n+ Screenshots are now rendered to a separate texture using the\n  8bit software renderer. This means that the hardware scalers\n  will not affect the quality of the screenshot. It also fixes\n  a bug when using opengl2, which would dump only a white\n  screen.\n- The force_height_multiplier option has been removed. A lot of\n  code wasn't properly designed to handle it, there have been\n  mouse warp bugs with it for years, and nobody seems to use\n  it. If people want stretching, they can choose one of the\n  four hardware renderers to achieve this.\n- Removed the 'lame/1337' menu feature.\n\nDEVELOPERS\n\n+ OpenGL can now be disabled via config.sh. This allows\n  MegaZeux ebuilds to be constructed on systems that do not\n  have any form of OpenGL support. (Although MZX runtime loads\n  the OpenGL library, 'e' required the headers to build\n  correctly. This is now no longer the case.)\n+ On Windows, due to an ATi driver bug, I have provided a means\n  of linking directly to opengl32.dll, instead of relying on\n  the dynamic loader. This reduces binary portability, but\n  fixes many bug reports of being unable to fullscreen on ATi\n  video cards. See OPENGL_LINKING for more information.\n+ Improved support for cross compiling with mingw32 on Linux,\n  combined the win32 Makefile with this new support.\n+ Rewrote the config.sh script. All of the options have\n  changed, and the broken platform auto-detection has been\n  removed. See ./config.sh for more information.\n+ Rejigged MegaZeux's headers so that they can be used in both\n  C and C++ mode. Renamed fsafeopen.cpp to fsafeopen.c.\n  Hopefully by 'g' most of MegaZeux should be ported back to C,\n  instead of the \"C++\" it is now.\n+ Fixed up the 'txt2hlp' utility which Terryn has been using a\n  version of to build the internal MZX help system. This binary\n  is built in the source distribution, but it is not\n  distributed with the MegaZeux binaries.\n+ Moved some antiquated Alexis code out into 'old'. No attempt\n  has been made to make it compile, it is provided purely for\n  reference.\n+ For the windows binaries, \"windib.bat\" is now generated by\n  package.sh and auto-generated for the name of the MegaZeux\n  executable.\n+ Updated Xcode package from Quantum P. (see macosx.zip).\n+ Fixed an invalid assumption in config.sh where /bin/sh was\n  chosen as the shell script interpreter. This should have been\n  /bin/bash, as 'sh' is not required to support functions,\n  which config.sh uses.\n  \n>#MAIN.HLP:072:Table of Contents\n\nJanuary 19, 2007 - MZX 2.81e\n \n+ Made grabbing in the editor not combine background color\n  - only uses \"special\" in game colors for player. tell me if\n  anything ends up being weird because of this.\n+ Possibly fixed an obscure bug where moving something happened\n  immediately if it was sent to a label by a robot further\n  east/south than it and it moved north or west (has to do with\n  the way robots are reverse scanned). Tell me if this changed\n  any behavior for the worse and I'll change it back or try to\n  work out something new.\n+ Added GDMs to ctrl + n; this will, of course, auto convert\n  and play the s3m.\n+ Added ability to preset player locked status from board\n  settings.\n+ Instead added ability to debug variables (counters and\n  strings) ingame with F11. There's also an option to export\n  the current variables to Robotic program that sets them.\n+ Fixed bug where moving a block with the player into an\n  overlapping region leaves a space where the player was.\n+ Fixed bug with a robot indirectly sending itself to a\n  subroutine via send all or send name causing it to loop the\n  send.\n+ Added compatability hacks for key# prior to MZX 2.69 worlds\n  and ridNAME falling through in MZX 2.70 and earlier worlds.\n+ F6, F7, F8, and F11 debug/cheat keys only work in editing\n  mode now (as things were prior to the port) You can still\n  save/load in the editor so if you want all of these things\n  you can play the game from the start there.\n+ Space in the editor no longer deletes something of similar\n  type that is beneath; not sure what the point of this was\n  anyway.\n+ Fixed bug causing cursor to clipped be out of bounds in SMZX\n  char editor if changing to smaller multichar edit region.\n+ Accidentally messed up screen centering in fullscreen for\n  32bpp mode, fixed.\n+ Added hardware scaling option. You can now supply a window\n  resolution besides 640x350 and allow for window resizing if\n  hardware scaling is on; this will also scale fullscreen\n  output to fill the entire screen. This can slow down\n  rendering somewhat.\n+ Fixed bug causing flip block to crash in the editor.\n+ Made blocked directions relative to the player for put dir\n  player.\n+ Fixed bug where putting something to a direction relative the\n  player overwriting the robot could crash MZX.\n+ Fixed ability to input in input boxes by clicking on their\n  question string.\n+ Removed the bogus patch to Modplug and correctly fixed it in\n  the build system.\n+ Added 'debian' subdirectory for building Debian and Ubuntu\n  upstream packages. Hopefully MegaZeux will be in the primary\n  pool in a few months.\n+ Added OS X xcode project files (see 'macosx.zip'). Fixed many\n  bugs relating to endian that caused MegaZeux to be buggy on\n  big-endian architectures (like PPC). Credit goes to Quantum P\n  for finding these bugs and engineering high quality\n  solutions.\n+ Made 'directx' the default video render again on Windows.\n  NOTE: This overrides the default SDL behaviour, but will not\n  be applied if you set SDL_VIDEODRIVER yourself.\n+ Repaired the 'linux-static' target so that it no longer\n  includes a system C++ library, which caused unpredictable\n  results on distros without a static version.\n+ Fixed a locking bug with the audio code that caused hangs at\n  startup on OS X. Also provided a mutex implementation using\n  GNU pthreads as a temporary workaround for an SDL bug on the\n  Linux platform.\n+ Added PlayStation Portable (PSP) port. This code was written\n  by Exophase and is highly experimental. It may not work at\n  all for you. Please see docs/build.txt for more information\n  regarding this port.\n+ Fixed mouse movement from being affected by height_multiplier\n  when not in fullscreen mode.\n+ Fixed height_multiplier config.txt option allowing you to\n  enter really stupid values (like negatives, 0, and values too\n  large for the resolution).\n+ Added in an extra video mode check to stop MZX from crashing\n  on video modes that the video card can not reproduce.\n+ Fixed Avalanche to a constant placement rate of 1/18 (this\n  caused MZX to deliver an uneven number of boulders, and to\n  crash with certain board sizes).\n+ Fixed sprite collision box to stop MZX from crashing when\n  stupid values are entered.\n+ Fixed setting the viewport size to weird values like some old\n  MZX games do.\n+ Default fullscreen resolution is now 640x480; this can be\n  changed in config.txt .\n+ The config.txt option force_32bpp is now enabled by default.\n+ Seeking with mod_position when using a .WAV file as\n  background music fixed (thanks Mr_Alert).\n- Removed ability to change SMZX mode ingame (F11).\n\n>#MAIN.HLP:072:Table of Contents\n\nDecember 10, 2006 - MZX 2.81d\n\nNOTE: This release was made by Alistair Strachan (ajs) and not\n      by Exophase. As such, any problems present in this\n      release that were not present in 2.81c should be reported\n      directly to ajs.\n\n+ Fixed a compilation failure on Linux, due to SDL no longer\n  depending on libX11. Now we manually link X11 into MZX if\n  necessary.\n+ Various build system improvements, fixing bugs in the\n  prefixing of dependencies.\n+ New libmodplug 0.8 imported, fixing many endian problems on\n  big-endian machines, integrating all of our local patches to\n  0.7.\n+ Fixed bug causing MZX to freeze when starting up on Win9x\n  machines.\n+ Fixed a warning generated by GCC 4.1.\n+ Updated the GPL boilerplates project-wide to the newest FSF\n  address.\n+ Fixed a string range check causing an obscure crash in\n  certain games.\n+ Updated the build.txt documentation.\n\n>#MAIN.HLP:072:Table of Contents\n\nDecember 14, 2005 - MZX 2.81c\n\n+ Oops, accidentally broke shift + F2. Fixed that.\n+ Also accidentally broke &+counter& for full hex\n  representation. Fixed.\n+ Fixed memory leak problem with playing certain WAVs in a\n  loop.\n+ Fixed inconsistency of bad viewport sizes behaving\n  differently on current versions from old DOS versions.\n+ Accidentally broke joystick stuff in config.txt (has to do\n  with way configure options were being read), fixed.\n+ Fixed bug causing crash when loading MZBs larger than the\n  current board size.\n+ Made cursor hidden in alt + V in editor.\n+ String comparison failed with nulls in the strings, fixed.\n  Also should be slightly more optimal.\n+ Fixed bug when using negative numbers for\n  if sprite_colliding \"counter\".\n+ Fixed math operations (inc, dec, etc) not working on string\n  indeces. \n+ Added ability to force screen to 32bpp. Fixes some slight\n  rendering issues, and if you have problems with fullscreen\n  let me know if this helps (try it without first though). See\n  force_32bpp in config.txt. \n+ Fixed sprite clipping bug with respect to overlay. \n+ Fixed bug where pressing enter on things besides robots,\n  scrolls/signs, or sensors in the editor would clear whatever\n  was underneath it. \n+ Accidentally broke SFX with optional PC speaker chains\n  (played both, should only play PC speaker when digital music\n  is off, fixes Bernard the Bard).\n+ Made last character in char selection for F3 and alt + C\n  remembered (note that they're remembered in two different\n  places for both).\n+ Accidentally broke life animations, fixed.\n\n>#MAIN.HLP:072:Table of Contents\n\nNovember 26, 2005 - MZX 2.81b\n\n+ Fixed inability to make proper .savs of worlds with strings.\n  (they'd crash when loaded..)\n+ Fixed PC speaker audio bug causing a constant high pitched\n  noise to be played instead of PC speaker audio sometimes.\n+ Fixed some issues with long pathnames.\n+ Fixed a bug causing Caverns to crash in recent versions (long\n  story, it was most likely due to an error in ver1to2).\n+ Now when you set mzx_speed in a game you can no longer change\n  the speed from the F2 menu. Setting mzx_speed to 0 reallows\n  this (and doesn't set the speed).\n+ When loading a game its speed is now set to the speed MZX\n  started with (whatever's in config.txt, or the default of 4).\n+ Added backup_ext config.txt option to specify the extension\n  of backup files (default is .mzx).\n+ Fixed backup_interval for config.txt possibly being broken.\n+ Fixed a bug messing up the death board on some old MZX games\n  (like Nick Brick 2)\n+ Escaped more things and made displays always in escaped form\n  for certain character sequences. It should be impossible to\n  type non-escaped forms. The following should be used:\n  \\0 for 0 (this probably won't work in strings, but in chars\n  should)\n  \\t for tab (character 9)\n  \\n for newline (character 10)\n  \\r for carriage return (character 13)\n  \\\" for double quote\n  \\\\ for slash\n+ Copy + paste on escaped character won't unescape them\n  anymore.\n+ Fixed error message for invalid lines in Robotic.\n+ Fixed inability to import text files from other directories.\n+ Huge overhaul of the source (proper types for things,\n  directions, equalities, conditions, chest items, and\n  potions), if anything is suddenly broken now let me know.\n+ Made scrolls/signs only display text (letters, numbers, etc).\n  in the default char set. That should be enough for now.\n+ Added mousewheel support for robot editor and robot box\n  display.\n+ Fixed inability to load MZMs from other directories in the\n  editor.\n+ Wrapped audio stuff in proper mutex, hopefully this fixes\n  some issues (like crashing when changing mod_frequency a\n  lot).\n+ Long current directory paths no longer write out too much in\n  the file loader (instead the last bit is shown with a ...\n  prefixing the beginning)\n+ Decided to be nice and make board_scan not crash. Don't use\n  it. It's only there to make one legacy game work. If you use\n  it I will personally scold you. And don't tell other people\n  to use it (that means you CJA). Use\n  copy block x y w h \"$str\" t\n  instead. If you don't know what that means read the help\n  file, it explains everything.\n+ Removed ability to copy + paste after changing board\n  dimensions of the source under any circumstances (alt + R,\n  alt + Z, import world, import board).\n+ Fixed appearance of ghosts in F10 menu.\n+ Prevented char editor from counting moving the cursor as an\n  undo step if nothing was actually drawn.\n+ Made pressing escape on initial char selection/board\n  selection/param selection for things cause it to cancel\n  placing anything.\n+ Made it impossible to set board width/height to 0 again\n  (oops).\n+ Made starting lives and starting health take effect\n  immediately for the first alt + t.\n+ Added ability to play OGG from alt + l (but not the other\n  mods, don't want to clutter that up).\n+ Made it so if no note follows an embedded SAM in a play\n  string it's played at native frequency.\n+ Accidentally made loading worlds in the editor not change the\n  current directory, fixed that.\n  \n>#MAIN.HLP:072:Table of Contents\n\nNovember 20, 2005 - MZX 2.81\n\n+ Fixed a bug where MZX world/save names > 12 chars could cause\n  weird things to happen (like doors breaking).\n+ Fixed problems with help file/charsets loading when loading\n  MZX outside of the directory MZX is in. This should fix file\n  associations on Windows as well.\n+ Changed board selector so when board 0 is \"(no board)\" it\n  doesn't actually refer to the title but to no board.\n+ Made import world not overwrite the title string.\n+ Fixed bug that causes crash when trying to flood fill an area\n  with the color it already is in the SMZX char editor.\n+ Redid audio engine. Everything is unified now, meaning that\n  anything you can use as a mod you can use as a sam and\n  vice-versa.\n+ The new audio engine uses its own master resampler that has\n  three interpolation modes - see config.txt for more\n  information.\n+ sam 0 filename will play a file at its native frequency (note\n  that SAMs that have been converted from WAVs are set to be\n  played at 8363Hz).\n+ Added support for OGG vorbis audio files.\n+ Fixed bug causing SFX volume control in F2 menu to not work.\n+ Removed limitation on number of SAMs that can be played\n  simultaneously.\n+ Fixed bug where the mouse got \"stuck\" in the black border\n  edges of non 640x350 fullscreen resolutions.\n+ Fixed issues with message boxes being part default palette\n  part current palette, they now always use the current\n  palette.\n+ Added mod_position counter. What these actually set/return is\n  dependant on the type of file loaded. Modules use the current\n  row, OGGs use the current PCM sample.\n+ Added mod_frequency counter. There are a few things to note\n  here: Modules have a \"nominal\" frequency of 44.1KHz. Other\n  data types have their own nominal frequency to prevent output\n  from sounding differently depending on the audio_sample_rate\n  in config.txt. For OGGs and WAVs the nominal frequency is the\n  one the file is encoded at. Changing the frequency can cause\n  a noticeable one time popping sound, so it might not be\n  desirable to slide it. This is much more prominent with\n  lowering the frequency than raising it. This value is capped\n  so it can't reach below 16.\n+ Changed alt + L to play back at natural frequency instead of\n  8363Hz.\n+ Fixed bug causing sensor deletion while the player is on top\n  to destroy the player.\n+ Fixed bug causing imported boards to possibly crash after\n  being tested.\n+ Fixed bug causing save_game and save_world to not work if a\n  file with the given name isn't already present.\n+ Changed function counter matching removing restriction on\n  number of digits for parameters. 10+ digit inputs should no\n  longer fail (for instance, abs-123456789)\n+ Made counter/string names internally variable length instead\n  of a fixed 14 chars. There is now no longer a name length\n  limitation.\n+ Changed alt+8 for mod * to just * in the hotkey listing.\n+ Fixed crash when referencing (by param) sprites > 256.\n+ The string system has been redone. Strings are now\n  dynamically sized and don't have an artificial maximum\n  length. Writing to string.N will guarantee that the size of\n  the string becomes at least N, while reading in this way will\n  return 0 if out of bounds to maintain the illusion of null\n  termination. Be careful when using this.\n+ $str.length returns the length of string $str (this is faster\n  than iterating through it to find when chars hit 0).\n+ Vlayer is dynamically sized. The vlayer_width/vlayer_height\n  counters still work as per usual, but the vlayer_size counter\n  has been added to adjust the maximum size. The default is\n  32768.\n+ Fixed bug not allowing things to move over goop.\n+ Fixed bugs causing current directory to be changed when\n  importing things from other directories.\n+ Properly implemented support for volume \"string\".\n+ Fixed a few commands not working when they should from the\n  global robot (such as put to dir of player).\n+ Fixed a bug where going to a label at the end of a robot\n  would treat it as if it's the first of its name in a sequence\n  of labels.\n+ Fixed a bug involving moving the a block with the player not\n  moving what was underneath the player.\n+ New help file, thanks MUCHLY to Terryn for pulling off this\n  enormous effort! (This is what you're reading from.)\n- Save files from 2.80 are not compatible due to several\n  changes in the save format.\n\n>#NEWINVER.HLP:1st:NEW in MegaZeux!\n>#292CLOG.HLP:292:New in Versions 2.92 to 2.92f\n>#2901CLOG.HLP:291:New in Versions 2.90 to 2.91j\n>#284CLOG.HLP:284:New in Versions 2.84 to 2.84c\n>#2823CLOG.HLP:283:New in Versions 2.82 to 2.83\n>#280CLOG.HLP:280:New in Versions 2.80 to 2.80h\n>#OLDERVER.HLP:260:New in Versions 2.60 to 2.70\n>#OLDESVER.HLP:201:New in Versions 2.01 to 2.60\n>#NEWIN200.HLP:1st:New in Version 2.00\n>#ANCENVER.HLP:1xx:New in Versions Through 1.03\n>#MAIN.HLP:072:Table of Contents\n#280CLOG.HLP\n:280:\n$~9New in Versions 2.80 to 2.80h\n\nJune 6, 2005 - MZX 2.80h\n\n+ Fixed a bug which could cause crashes when quitting the game.\n+ Fixed some bugs when changing boards and other things that\n  can cause duplicate players.. I think.\n+ Fixed a bug that could cause crashes when adding boards.\n+ Improved response time in editor for slower computers/high\n  load situations.\n+ Fixed some endian issues with the GUI.\n+ Fixed some crash when moving the mouse cursor around in the\n  editor.\n+ Fixed bug where you if you had a robot whose name is the same\n  as the global robot it wouldn't get messages (fixes yoyo in\n  Weirdness).\n+ Fixed debug box not moving with text input.\n+ Fixed bug with duplicate player appearing when killed and a\n  new one can't be put at 0, 0.\n+ Added copy/paste for outside of MZX to/from the robot editor.\n  It only works in Windows and X11, and functionality may be\n  limited in X11 right now (currently seems to work in native\n  X11 apps and GTK 2.6 apps but not earlier GTK or QT, also try\n  shift + insert to paste).\n+ Fixed bug in resizing involving overlay blanking.\n+ Fixed clear messages/projectiles not working (and damaging\n  the game instead).\n+ Fixed behavior of P key in editor for wildweasel.\n+ Fixed random in Robotic not correctly swapping the range if\n  they're given in the wrong order.\n+ Fixed clip length in [ messages.\n+ Fixed crash when changing volume without a game loaded.\n+ Redid internal GUI system, fixes some minor things.\n+ New file loading/saving window - press del to delete a\n  file/dir, alt + n to create a new directory, alt + r to\n  rename a file/dir.\n+ Added PC speaker volume control to F2 settings and\n  config.txt.\n+ Fixed yet another crash bug with resizing boards.\n+ Fixed inability to type * in text placement in the editor\n  (although this adds inability to turn on mod * while F2 is\n  on...).\n+ Added ctrl + n in the editor to load a module for listening\n  only (won't set the current board's module, and will let you\n  choose ones from different directories).\n+ Fixed crash on macros with more variables than can be\n  displayed in their configuration.\n+ Fixed bug that causes char selection cursor to reset to 0 on\n  unhandled keys (and continuously do so for lock keys).\n+ Tweaked ctrl + dir in text entry boxes.\n+ Added gdm2s3m in-tree to the contrib/ directory. gdm2s3m no\n  longer needs to be installed on the system before compiling\n  mzx.\n+ Improved the build system to automatically build .c and .cpp\n  files with compound system CFLAGS/CXXFLAGS, respectively.\n+ Made package.sh automatically ship the source package with a\n  Makefile.dist to warn the user that they need to run\n  config.sh before 'make'.\n+ Rectified inconsistency in source copyrights.\n+ Added multi-character editor. Select multiple keys in the\n  character selection with shift. The char editor also now has\n  the ability to perform operations (delete, copy, scroll, etc)\n  on subblocks. Hold down shift or press alt + b to highlight a\n  region (press escape to remove the latter). Blocks copied\n  like this will be pasted to where the cursor is at. Other\n  small things in chareditor tweaked/changed... No longer press\n  tab to toggle through set/clear/toggle draw modes, instead\n  tab for set mode and shift + tab for clear (no more toggle).\n  Mouse behavior is modified as well. In non-SMZX left click\n  sets, right click clears. Shift + F2 will cut a block (clear\n  + copy).\n\n  alt + x/alt + i can now be used to import/export partial\n  charsets while in the char editor. You can do so for several\n  in series: put a # in the name of the charset then set the\n  First for the first number # will be replaced with and the\n  Count value to indicate how many in series to work with. For\n  instance, saving s#.chr with first = 0, count = 3, starting\n  at offset 100, with a 2x2 char selection will save charsets\n  s0.chr from 100, s1.chr from 104, s2.chr from 108, and\n  s3.chr from 112.\n\n  *** NOTE *** Series import/export will only work correctly\n  with char selections that are one in height (they can still\n  be split up another way in the editor itself). If you want to\n  use partial charsets on your edits, it's important that you\n  select all the chars in a row.\n\n+ Made characters for the editor/GUI use another charset that's\n  protected. Please notify me if any characters are incorrect.\n  Modify mzx_edit.chr to change this charset. The same thing\n  goes for colors. It doesn't work for SMZX, which also might\n  look a bit different in the editor...\n+ Added option (defaults on) to protect chars 32-127 in input\n  boxes and strings in the robot editor.\n+ Mouse warping goes to middle instead of top corner now, so\n  there isn't a bias towards moving up.\n+ Hopefully fixed another bug with the cursor and changing\n  boards...\n+ Fixed module looping problem in modplug...\n+ Added libmodplug 0.7 with both patches (see contrib/)\n  in-tree. Removes system dependency on libmodplug.\n+ Made auto-backup on by default. (3 count)\n+ Made if touching idle, beneath always false instead of like\n  nodir.\n+ Fixed bug that caused bad things to happen if you pressed too\n  many different keys too rapidly.\n+ Made mouse wheel emulate up/down in dialog boxes and list\n  menu.\n+ Added ctrl + backspace to intake (delete previous word).\n+ Made modulo operator use floored instead of truncated mod\n  (uses positive remainder instead of negative).\n+ Fixed crash when testing after using ctrl + z to clear a\n  board.\n+ Fixed bug where sending other robots to subroutines caused\n  the return address to be to the next instruction like local\n  subroutine calling works.\n+ Fixed bug where going to a label on the last line of the\n  robot could screw the game up.\n+ Fixed a bug where the editor froze if you tried to fill the\n  board with players. (eheh...)\n+ Fixed a bug that could cause crashes when sending all sensors\n  something.\n+ Fixed some crashes when exporting/saving fails.\n+ Fixed import world's ability to go over the board limit and\n  cause crashes.\n+ Allowed input of decimal numbers for params.\n\n>#MAIN.HLP:072:Table of Contents\n\nApril 1, 2005 - MZX 2.80g\n\n+ Fixed crash on alt + x in robot editor.\n+ Fixed missing line on alt + h in robot editor.\n+ Introduced incorrect enter action in robot editor (didn't\n  reset to beginning of the line), fixed.\n+ Fixed garbage appearing when moving from a larger to smaller\n  board and being outside of that board's scroll region.\n+ Fixed bug that could cause glitches/crashing when resizing\n  the board.\n+ Fixed error with global next option not retaining the three\n  checkmark options correctly.\n+ Added work around so that move block moves the player (won't\n  move it on inter-board moves).\n+ Fixed bad palette loading for Linux introduced in 2.80e or f\n  or something\n+ Made it so block highlighting doesn't highlight the debug\n  window.\n+ Made the debug window move if necessary when home/end is\n  pressed.\n+ Added autorepeat buffering so previous keys can be resumed.\n+ Fixed bug with swap world possibly not working (crashing??)\n  off Windows.\n+ Fixed more problems with garbage/crashes when resizing with\n  the cursor in a position causing the scroll to go off the\n  edge.\n+ Fixed incorrect text cursor offset with\n  force_height_multiplier on.\n+ Redid way directories are loaded internally so you can load\n  dirs with over 4096 entries now. Might be faster (unsure)\n+ Chest contents list menu looked funny, fixed.\n+ Changed default.spl to smzx.pal so you can load it more\n  sanely.\n+ Export block wasn't getting the last selected line. Fixed.\n+ Hacked scroll editor so it wouldn't crash when removing\n  lines. Scroll code either needs to be 100% overhauled or\n  replaced by robots somehow...\n+ Fixed config files not being closed.\n+ Added include file option for config files. Use it like this:\n  include \"config file\"; e.g., \"include subconfig.cnf\" will\n  load subconfig.cnf's options.\n+ Fixed freadN not terminating strings.\n+ Fixed graphical glitch when using the mouse in the char\n  selector.\n+ Fixed save games crashing when they can't load fopened files.\n+ Fixed some other problems with save games and fopened files.\n+ Accidentally had title screen running a bit slow...\n+ Value strings starting with ( not parsed as an expression if\n  they don't end with the ).\n+ Fixed problem with key_code being triggered for keys that\n  aren't in-game.\n+ Added extended macros. This allows for parameter based macros\n  to be entered in the robot editor via a window or by command.\n  See macro.txt for more information.\n+ Fixed player cloning after flip/mirror and player placing.\n+ Added random seeding that was mysteriously missing...\n+ Finally added drive changing for Windows builds.\n+ Fixed mousex/mousey for resolutions other than 640x350 (only\n  applies to fullscreen).\n+ Fixed crash on weird invalid death/endgame boards...\n+ % and & messages clip correctly now.\n+ Fixed potential crash on double closing the files.\n+ Fixed crash bugs with placing sensors and maybe scrolls.\n+ Fixed sending sensors when you have robots of the same name\n  (fixes Weirdness chapter 1).\n- Removed the unimplemented if player dir and if not player dir\n  commands from RASM.\n  \n>#MAIN.HLP:072:Table of Contents\n\nDecember 26, 2004 - MZX 2.80f\n\n+ Fixed a bug that could cause crashes when auto-quoting params\n  in the robot editor (eg, set x 1 -> set \"x\" to 1).\n+ Fixed a bug that could crash the robot editor if you added a\n  new line prior to the first line of a marked block, then did\n  an action on it.\n+ Fixed a bug where clearing the first and only line could\n  cause it to appear as if it hadn't been cleared at all.\n+ Unified global and global next parameter setting so that\n  nothing is lost between first/next but information can be\n  cancelled without application.\n+ Fixed E/S block markers appearing in the robot editor when\n  they should be off the screen.\n+ Left click position in robot editor mysteriously disappeared\n  after having been added somewhere after 2.80d. Re-added.\n+ Added option to hide the hotkeys help and horizontal border\n  in the robot editor with alt + h. Also added a config.txt\n  option to have it default this way.\n+ Search/replace in the robot editor. ctrl + f to find or\n  replace/replace all, ctrl + r to repeat either search or\n  replace (depending the last one you did, if you cancelled\n  this does nothing).\n+ The load_game counter sequence was broken; fixed.\n+ Hopefully fixed all means of overrunning the current line max\n  length in the robot editor...\n+ Fixed robot editor validation not showing every 13th line.\n+ Fixed aesthetic problem with validation report...\n+ Fixed crash with setting message column less than 0.\n+ A couple things added for 2.80e mysteriously disappeared in\n  source handling. Re-added.\n+ Changed max board size prevention to auto resize the lower\n  dimension to the max that can be handled with the higher\n  (ex, 30000x25000 becomes 30000x559).\n+ Added flood fill to char editors (alt + f).\n+ Added single depth undo to char editors (alt + u).\n\n>#MAIN.HLP:072:Table of Contents\n\nDecember 19, 2004 - MZX 2.80e\n\n+ Fixed a bug causing problems with static overlay if a\n  non-overlaid sprite is displayed so it's clipped off the edge\n  of the screen.\n+ Fixed a bug in the display of c?x color boxes in the F2 menu\n  in the robot editor.\n+ Fixed a bug that caused incorrect thisx/thisy for one cycle\n  after copyblock.\n+ Fixed a bug preventing calls to nonexistent subroutines from\n  passing that point in the robot.\n+ Fixed crash on sam 0 \"file\".\n+ Fixed a bug where loading new SFX may not correctly overwrite\n  previous ones.\n+ Fixed a bug where you could only load/unload so many mods\n  before MZX couldn't load anymore.. same bug as the SAMs but\n  went unnoticed!\n+ Fixed a bug that caused you to be infinitely stuck in the\n  global settings dialog box when you press previous on the\n  next page.\n+ Fixed a bug where going to next then exiting would not save\n  the changes from the previous page.\n+ Fixed a bug that could cause crashes while ending modules.\n+ Fixed a bug that could do the same kind of thing with sams.\n+ Fixed an allocation bug when loading MZX worlds that could\n  lead to crashes.\n+ Fixed a bug that caused MZX to crash if you interpolated an\n  expression with a value equal to or greater than 1 billion.\n+ Fixed a bug where mixing ccheck1/2 with sprites from board\n  and vlayer could cause problems (that's the short version of\n  the explanation, I'll spare you the long one).\n+ Fixed a bug that could cause certain old MZX games to crash\n  after the title screen\n+ Somewhere broke missiles between 2.80c and 2.80d. Fixed.\n+ Fixed error in lit bomb anim sequence setup in char ID\n  editor.\n+ Reworked a lot of robot editor code; adding/deleting lines\n  while marked areas are active should work more naturally now\n  and it's hopefully no longer possible to crash it in the same\n  ways it was previously.\n+ Fixed crash when setting mesg row to less than 0.\n+ Fixed mouse presses not working in the robot editor.\n+ Made MZX ignore alt + tab so you can safely switch in your WM\n  without it triggering...\n+ Added numerical key entry for number boxes. Use 0-9 to add to\n  the most significant digit and backspace to take it away.\n+ Added config.txt option to make MZX pause when key focus is\n  lost (when clicking on another window, perhaps) or when it's\n  minimized. Music will still continue.\n+ Added save/load position to the editor. Works for loads\n  in between boards as well. Press ctrl + num to save to slots\n  0 through 9 and alt + num to load from that slot. Please\n  press shift + 8 or the numpad * key instead of alt + 8 to set\n  mod wildcard.\n+ Fixed a further bug that could cause playing samples to\n  crash.\n+ Added config file option to revert the robot editor to the\n  default palette when loaded.\n+ Fixed bug in shoot command.\n+ Fixed error when making save name in editor but cancelling.\n+ Auto-backup - see config.txt for details.\n+ Joystick key mapping - see config.txt for details.\n+ You can now load game-specific config files by creating\n  game.cnf for the corresponding game.cnf (for instance,\n  caverns.cnf). This is mainly useful for joystick key mapping.\n  Note that these settings will NOT go away if another game is\n  loaded that doesn't have a .cnf.\n+ Alt+enter finally works as block action in the robot editor.\n+ Loading a .mzx/.sav from another directory indirectly (via\n  command-line or robotic) will now actually change the current\n  working directory.\n+ Fixed bug that crashed MZX with ctrl + i in the robot editor.\n- Fixed maximum board size to about 16.7 million tiles (128MB)\n  for now.\n- MZX now ignores the mouse scroll wheel instead of\n  interpreting it as a click.\n  \n>#MAIN.HLP:072:Table of Contents\n\nOctober 9, 2004 - MZX 2.80d\n\n+ Fixed cursor going invisible when escaping from import in the\n  editor.\n+ Fixed robot editor entry when pressing OK on global info.\n+ Fixed lack of name for MZB import/export (any MZB's exported\n  in prior beta versions still won't have a name).\n+ Fixed some problems with setting the mouse position.\n+ Fixed problem with exits not bringing you all the way to the\n  edge if width over 400 or height over 200.\n+ Fixed bug that cleared too much when increasing both width\n  and height while resizing the board.\n+ Fixed problem with 1 char shortcut commands with spaces\n  immediately after them.\n+ Fixed problems with load_robot and load_bc (caused crashes\n  and infinite loops).\n+ Optimized RASM heavily (this should be most noticeable when\n  doing a lot of external robot loading from text files).\n+ Fixed inability to use absolute paths in loading a game from\n  command line.\n+ Fixed lastshot/lasttouch conditions with directions not\n  working.\n+ Fixed char editor in robots not going into SMZX mode when\n  proper.\n+ Cleaned up source code so it passes -Wall without complaint\n  and in the process corrected some glaring code errors that\n  may have corrected random problems.\n+ File opening broken in 2.80c, fixed.\n+ Implemented MZM2 saving and loading and rewrote mzm.cpp (if\n  anything is changed or fixed regarding MZMs, attribute it to\n  this). MZM2s can be of larger dimensions, smaller filesize\n  for same amount of data, and can store robots.\n+ Fixed bug that could cause MZX to crash when making new\n  strings.\n+ Block operations to overlay when overlay was off caused\n  crashes - fixed.\n+ Fixed a problem with sprite ccheck2 against other sprites.\n+ Optimized function counter lookups a bit; speed gain for all\n  counter accesses (especially ones that begin with certain\n  characters such as _).\n+ Fixed disassembly error with ' ' character.\n+ Fixed assembly error where condition extended dir (such as\n  blocked opp seek) was not getting compiled with the dir\n  extension.\n+ Fixed editor bug where the param was not being cleared when\n  overwriting things by double placement.\n+ Fixed inability to use counters with\n  playercolor/bulletcolor/missilecolor.\n+ Added ability to use counters in place of p?? in the robot\n  editor. Note - even though this expands functionality of the\n  editor this does not require a version number change because\n  the worlds will still be playable in older MZX versions (and\n  will display correctly in the robot editor - you simply won't\n  be able to correctly edit the commands).\n+ Mouse correctly limited to screen edges now.\n+ Fixed inability to overwrite robots with pushable robots and\n  vice-versa, as well as scrolls with signs and vice-versa.\n+ Possibly fixed problem with windowing error when editing\n  global robot (?).\n+ Fixed disappearing cursor after color selection box with\n  mouse (and other places?).\n+ Fixed bug in sprite clipping that caused some to be clipped\n  off inappropriately.\n+ Made board_id/board_param counters readable.\n+ Added bound checks for all counters using\n  board_x/board_y/overlay_x/overlay_y.\n+ Fixed potential direction corruption bug causing directions\n  not to work sometimes even if they display correctly in the\n  robot editor.\n+ Fixed copy overlay to MZM copying to overlay too.\n+ Fixed a bug where debug window could display the wrong amount\n  of robot mem and potentially even crash MZX.\n+ Fixed help_menu counter not doing anything (durr).\n+ Changed sprite draw order so they're drawn underneath the\n  message bar, debug box, and time remaining display.\n+ Changed put p?? in Robotic so it will put default params if\n  available.\n+ Fixed a bug that could cause copies from overlay to vlayer to\n  not end up at the correct destination.\n+ Fixed a bug where c?x and cx? would not display correctly in\n  the robot editor.\n+ Optimized copy blocks a bit using variable length arrays\n  instead of malloc.\n- Removed ability to put robots, scrolls/signs, and sensors\n  (with the put command in Robotic).\n  \n>#MAIN.HLP:072:Table of Contents\n\nAugust 16, 2004 - MZX 2.80c\n+ Fixed issues with the commands counter not being reset.\n+ Color intensity now gets reset when you enter the editor\n+ SAMs got cut off sometimes now.. fixed.\n+ Fixed bug where loading a world with empty boards could\n  change the starting, endgame, and death boards.\n+ Fixed bug where you could text enter off the bottom of the\n  board, causing problems.\n+ Fixed bug involving cutting/clearing the entire robot in the\n  robot editor while not at the first line.\n+ Fixed robot name entry for global robot not disappearing on\n  small boards.\n+ Fixed bug where you could duplicate the player by holding\n  down a direction as a saved game loads.\n+ Fixed bug where you could go to line 0 in the robot editor.\n+ Saving an MZM now auto-adds the .mzm extension...\n+ Fixed black screen on quicksave.\n+ Fixed bug where opening a file didn't close the old one if\n  one was open (so it'd eventually crash MZX).\n+ Changed Alt+backspace behavior in intake so it doesn't exit.\n+ Added clipping for refx/refy/width/height for sprites (less\n  than 0 at initialization, greater than board width/height at\n  draw).\n+ Fixed direction parsing for move all.\n+ Fixed bug where creating things on top of the player would\n  use a slot for robots/scrolls/signs/sensors instead of just\n  copy to the buffer.\n+ Added ability to use chars as immediates in Robotic commands\n  (ie, set \"$str.0\" 'a').\n+ Added options to enable oversampling and specify resampling\n  mode in the config file (higher quality audio).\n+ Building with patched modplug that fixes loading 2-channel\n  mods outputted by FT2. If you're building yourself, see\n  build.txt.\n+ Fixed inability to mouse click in alt + h mode.\n+ Fixed ability to mouse click outside of board range.\n+ Should work better for Linux users; case insensitivity for\n  file opens has been added.\n+ Fixed close bug that was affecting Linux builds (may affect\n  more).\n+ Keypad enter works where normal enter works now.\n+ Fixed disappearing cursor when cancelling out of abandon\n  changes box when loading a new world in the editor.\n+ Fixed problems when loading/saving robots outside of ID range\n  (do not hardcode ID's people).\n+ Fixed problem with NO BOARD exits being set to something else\n  when empty boards were being stripped or when worlds were\n  being imported.\n+ Fixed bug where auto-decrypting worlds didn't work if the XOR\n  value was negative.\n+ Fixed problem with rid not working the first cycle.\n+ Fixed inability to interpolate (with &&'s) counter names\n  larger than 14.\n+ Added new robot mem counter in debug box (only kb precise,\n  rounds up).\n+ Fixed ability to clone the player on non-title board after\n  testing.\n+ Lengthened size of mod name buffers.\n+ Fixed bug where send x y doesn't work from the global robot.\n+ Fixed a few bugs that could cause MZX to crash.\n\n>#MAIN.HLP:072:Table of Contents\n\nAugust 11, 2004 - MZX 2.80b\n\n+ Made it possible for robots to move through teleporters.\n+ Fixed bug with pressing shift in text entry boxes.\n+ Made it so alt + tab does not switch draw modes in editor.\n+ Fixed a disassembly error for color intensity N percent\n  command.\n+ Fixed problem with looping on mods that do not loop\n  explicitly.\n+ Fixed alt + dir scrolling in the char editor.\n+ Fixed not being able to click the rightmost char in the char\n  editor.\n+ Re-added unmark (alt + u) to robot editor (mysteriously\n  disappeared??).\n+ Fixed key label so it returns proper unicode values.\n+ The player and pushable robots can now be pushed by the push\n  command.\n+ Fixed bug where you could clone the player by switching\n  boards.\n+ Fixed bug where you could either turn off overlay or switch\n  to boards that don't have it while in overlay edit mode...\n+ Fixed bug where remains of debug window would not be cleared\n  in editor if the board width is too small.\n+ Fixed bug where turning off the menu with a board too small\n  would mess things up.\n+ Fixed bug where run lengths were saved one too large... this\n  could fix stability problems in at least occasional cases\n  (with saved worlds or save games, at least).\n+ Fixed placing solid things beneath robots (like bombs).\n+ Added support for a keyboard plus in the char editors.\n+ Fixed previous button in SFX editor.\n+ Made robot name box disappear when robot char box comes up...\n+ Fixed bug where mods restart after pressing P if they're the\n  same mod as what was playing before.\n+ Fixed problem with changing params (with P) in the editor.\n+ Fixed bug where null boards were not being pruned from old\n  worlds upon load.\n+ Made file name saving box larger (for saving games and\n  worlds).\n+ Fixed bug where default (100%) palette intensity values would\n  not be applied to the palette a game loads with.\n+ Fixed bug where exporting char sets that are full size caused\n  a 0 byte charset to be exported (8bit wraparound).\n+ Added support for forms such as :line.\n+ Fixed sporadic incompletion of strings without trailing\n  quotes at the end of the line.\n+ Fixed bug where clearing/cutting the last line of a robot\n  crashed MZX.\n+ F4 in robot editor now works more generally.\n+ Made line numbers in robot editor error report start at 1.\n+ Added ctrl + G to go to a line in the robot editor (ala\n  nano).\n+ Made it possible to change a robot's color in the editor\n+ Fixed bug where spitfire, seekers, and missiles didn't hurt\n  something immediate adjacent to whatever shot it.\n+ Fixed editing of spitfires.\n+ Made default speed 4 again (5, bleh).\n+ Re-added quicksave/quickload.\n+ Re-added F8 clear (always works - be careful).\n+ Fixed autorepeat problems for spacepressed/delpressed.\n+ Wrapped around sprite values so LogiCow's bad code would work\n  (HTMCIAB).\n+ Cleared block commands in between board changing and other\n  things like that.\n+ Fixed bug where MZX would crash if put dir player overwrote\n  the robot doing it.\n+ Fixed bug where playing SAMs would eventually crash MZX\n+ Fixed some mod * problems (hopefully?).\n+ Fixed bug where pasting blocks over the edge of the board in\n  the editor would cause MZX to crash.\n+ Uses new GDM2S3M source that fixes some bugs. If your\n  converted GDM's have problems, delete the S3Ms it generated.\n- Removed export text in the board editor. Don't think anyone\n  wanted it...\n  \n>#MAIN.HLP:072:Table of Contents\n\nAugust 9, 2004 - First release, MZX 2.80 BETA\n\n+ All ASM files have been rewritten to ANSI C compliant C++;\n  this mainly involves the mammoth game2.asm.\n+ Everything graphics related has been replaced with an SDL\n  driven engine, including text mode rendering (also capable of\n  rendering SMZX modes).\n+ All keyboard and mouse events are now also handled by an SDL\n  driven engine. All internal keyboard/mouse routines use SDL\n  keycodes; no attempt was made to convert to MZX's old format\n  at this level.\n+ All world/board/robot loading and saving code has been\n  entirely rewritten and is more modular now.\n+ All robot handling code has been rewritten from scratch, and\n  is more sane/efficient now (allocation, message sending,\n  sensors, etc.)\n+ All counter handling code has also been rewritten from\n  scratch. This includes the functionality of builtin and\n  \"function\" counters. It also includes a new string framework.\n+ The robot interpreter has been almost entirely rewritten.\n+ The main game playing code (game.cpp) has been almost\n  entirely rewritten.\n+ All module/sample playing code has been rewritten to use\n  modplug and SDL. PC speaker effects are emulated.\n+ The main part of the editor (edit.cpp) has been almost\n  entirely rewritten.\n+ The robot editor has been entirely rewritten, utilizing a\n  modified (fixed) version of RASM (released by me over a year\n  ago)\n+ All other source files (for instance, window handling code,\n  scroll displaying, char editor, etc.) have been largely\n  overhauled, some to more of a degree than others. At the\n  very least, everything has been reformatted and made to work\n  with the new systems.\n+ key_pressed and key_code now return unsigned chars instead of\n  signed, so if the game checks for negative values, it won't\n  work. The same goes for reading back characters for strings.\n+ Boards may now be as large as 32767x32767. Please do not make\n  boards this large. :)\n+ You may now have up to 250 boards.\n+ Boards may have up to 255 robots, 255 scrolls, and 255\n  sensors.\n+ There is no robot memory per board limit. However, individual\n  robots may only be up to 64k large. If you include the global\n  robot, this means that you effectively can have up to 16MB of\n  robot memory per board.\n+ The same applies to scrolls, which have separate memory\n  spaces and may only be up to 64k in length each (so you could\n  have almost 16MB of scroll text per board, but who would want\n  that??).\n+ The robot stack need not be initialized, and will be\n  dynamically resized as necessary (useful for recursion). This\n  refers to lines such as\n  . \"#*-1-2-3-4-5-6-7-8\"\n  at the beginning of robots. They're no longer necessary or\n  useful (they will be ignored, like any other comment).\n+ You may have a virtually unlimited number of counters active.\n  The theoretical limit is some 4 billion. The effective limit\n  depends on memory available.\n+ The same goes for strings, which may now have any name, so\n  long as it begins with a dollar sign ($). The same\n  conventions regarding strings in 2.68+ still apply (see\n  268_info.txt for more information).\n+ Local2 and local3 no longer have dangerous side-effects.\n  Local4 through local32 are also available per-robot now.\n+ The following are now natively supported: XM, S3M, MOD, MED,\n  MTM, STM, IT, 669, ULT, WAV, DSM, FAR, AMS, MDL, OKT, DMF,\n  PTM, DBM, AMF, MT2, PSM, J2B, and UMX.\n+ You can now perform repeated copies by selecting \"Copy\n  (repeated)\" from the block command window. This will cause\n  you to repeat the copy indefinitely until you press escape.\n  Also, after the first paste is made, you may press ctrl +\n  direction to move in steps that are the size of the copy (you\n  really have to try it to get a good idea of what I mean).\n+ Press alt + H to hide/unhide the menu/information occupying\n  the bottom 6 lines of the screen.\n+ Right click is similar to pressing insert (grabs whatever's\n  under the cursor).\n+ Exporting char sets now gives direct options to set the\n  size/offset for partial charsets. The old format (prefixing\n  the name) will not work.\n+ For saving files, you may make filenames larger and use mixed\n  case (as opposed to all caps).\n+ Now when you import worlds their exits will actually work.\n+ Loading worlds results in all empty boards and empty\n  robots/scrolls/sensors within the boards being removed. When\n  you delete a board, you won't be presented with a gap in the\n  board list anymore.\n+ You may once again select ASCII as a default charset and\n  revert to it in the char editor. You may also select a\n  default SMZX charset.\n+ Anywhere you can enter a line of text you can now press ctrl\n  + left and ctrl + right to go to the previous/next word.\n+ This isn't totally limited to the editor, but now everywhere\n  a selection box shows up (such as for files, boards, items)\n  you can press multiple keys to seek to a specific entry.\n  Furthermore, in file selection boxes you can press backspace\n  to go up a directory.\n+ The SMZX char editor has been changed quite a bit. First, to\n  use, you must be in SMZX modes 1-3 (toggle using F11). Within\n  the editor, press keys 1 through 4 to change the \"current\n  color.\" You can place this color with space, and you can\n  toggle repeated placement on/off using tab (sorry, no\n  clear/toggle in this mode, they got in the way far more than\n  they helped). You now move in increments of 1 char naturally\n  instead of \"half chars.\" Right click grabs the current\n  \"color\" underneath the cursor.\n+ You are now able to type invalid code within a robotic line.\n  You will see that it's invalid due to the way the line is\n  color coded. If you try to escape while any lines are invalid\n  or if you press alt + V, a window will be brought up\n  describing the nature of all erroneous lines. It will also\n  let you mark each line to be deleted or commented out on\n  exit. If all invalid lines are marked with anything besides\n  ignore, you can exit safely. You may also mark invalid lines\n  using ctrl + I, D, and C (to mark as ignore, delete, and\n  comment respectively). ctrl + C may also be used to comment\n  out a normal line, and then uncomment.\n+ alt + O is still available, but you may only change macros\n  here (see the config file for the other rarely used options).\n  This does give you more room to modify macros, though. They\n  may be up to 64 characters in length, and you may set up to\n  around 45 or so in this window. (To get larger, you'll have\n  to use the config file.).\n+ Command line params have changed.\n+ MegaZeux configuration information is now stored in a file\n  called config.txt.\n- There are some slight discrepancies in robot size; if you\n  highlight the last line then leave it, it will register as a\n  compilation of that line. Yet, when you exit, the line will\n  be discarded, so this addition is not permanent. This\n  shouldn't be problematic.\n- alt + enter and alt + escape no longer work; use the\n  alternatives (alt + b and alt + u).\n- You cannot click on help options to make it happen (for\n  instance, you can't click on L:Load and expect it to bring up\n  the load window).\n- You cannot import or export ANS files. Use MZM instead.\n- Some things that worked in the DOS version only worked due to\n  chance, such as there being default (but valid) values for\n  sprite widths or heights and thus sprites could be displayed\n  before new values were set. They will not work here.\n\n>#NEWINVER.HLP:1st:NEW in MegaZeux!\n>#292CLOG.HLP:292:New in Versions 2.92 to 2.92f\n>#2901CLOG.HLP:291:New in Versions 2.90 to 2.91j\n>#284CLOG.HLP:284:New in Versions 2.84 to 2.84c\n>#2823CLOG.HLP:283:New in Versions 2.82 to 2.83\n>#281CLOG.HLP:281:New in Versions 2.81 to 2.81h\n>#OLDERVER.HLP:260:New in Versions 2.60 to 2.70\n>#OLDESVER.HLP:201:New in Versions 2.01 to 2.60\n>#NEWIN200.HLP:1st:New in Version 2.00\n>#ANCENVER.HLP:1xx:New in Versions Through 1.03\n>#MAIN.HLP:072:Table of Contents\n#OLDERVER.HLP\n:260:\n$~9New in Versions 2.60 to 2.70\n\n**NEW in 2.70***\n\n+ Robotic code now loadable and savable to files, either to\n  text files or compact but human-unreadable bytecode files.\n  Other robots' code can be written to or read as well.\n+ Defaults changed to more accepted values.\n+ The swap world bug should be fixed. You should now be able to\n  swap worlds back and forth with no trouble.\n+ \"atan\" now works with any input/output value.\n+ The \"load_game\" command now should work without any problems.\n+ It should now be easier to compile MZX from the source.\n+ It's not really a bug fix, but the palette is now returned to\n  its original colors.\n+ Robots can now 'put' things underneath themselves: eg. put\n  c04 carpet p?? under.\n+ The title screen menu now works: e.g. the menu you get when\n  pressing 'Enter' on the title screen.\n+ \"key_pressed\" now returns the twos-complement of the key\n  value, i.e. the one used for :key?\n+ The mouse can scroll across the whole screen after a screen\n  refresh / transition to/from SMZX mode.\n\n>#MAIN.HLP:072:Table of Contents\n\n**NEW in 2.69c***\n\n+ Sprite limit expanded from 64 to 256.\n+ New sprite flag added: sprN_ccheck mode 2.\n+ Vlayer added.  This is a virtual, unseen layer 32767 in size\n  used to store graphical data.\n+ World loading and saving through Robotic now possible.\n+ Special class fwrite_modify; this allows editing of a file as\n  opposed to overwriting.\n+ File end seeking added.\n+ Char file offsetting added.\n+ File seeking added to editor.\n\n>#MAIN.HLP:072:Table of Contents\n\n***NEW in 2.69b***\n\n+ Fixed the fwrite_append problem RoSS mentioned.\n+ Fixed the string problem Nanobot mentioned.\n+ Added ridNAME as an alternative to robot_id_NAME.\n+ Added mouse_mx and mouse_my to determine mouse motion in\n  \"mickey\" units.\n+ Added two local counters \"local2\" and \"local3\", local2 uses\n  walk_dir and is_locked (so walking and locking will affect\n  it), local3 uses last_shot_dir and last_touch_dir (so\n  shooting and being touched will affect it).\n+ Added the builtin string \"robot_name\" to determine the name\n  of the robot invoking it.\n+ Reverted back to BWSB 1.20a again, in the hopes that it will\n  alleviate swap world crashing a bit.\n\n>#MAIN.HLP:072:Table of Contents\n\n***NEW in 2.69***\n\n+ if \"$stringN\" = \"literal\" should now work.\n+ Embedding && or () in names for save/load files for MZM\n  saving and the like should work now.\n+ Expressions won't be activated in pre-2.68 games, meaning if\n  they used the constructs which would be valid expressions\n  they should be okay.\n+ More minutiae.\n+ key_code, a more useful key_pressed alternative, added.\n+ SMZX mode 1 returns.\n+ SMZX mode 2 added.\n\n>#MAIN.HLP:072:Table of Contents\n\n***NEW in 2.68***\n\n+ Expressions added.\n+ Trigonometric capabilities added.\n+ MZM file capabilities added.\n+ Board and overlay can now copy block to each other.\n+ Saving and loading of SAV files through Robotic added.\n+ String commands totally redone.\n+ Now, instead of using value/sqrt_value/abs_value you may use\n  a value with the counter directly as such:\n   * sqrtN\n   * absN\n+ fread_pos/fwrite_pos and page should work correctly now; when\n  you increase the pos it should bump up the page too.\n+ pixel_x, pixel_y, char were broken in 2.65; fixed in 2.68\n+ strings had some bugs in 2.62b+, should be fixed now because\n  of a separate implementation.\n+ MZX Robot files are now saved to save files, so if you have a\n  read and/or write file open and the game is saved, if the\n  game is loaded the file will be restored at the position it\n  was at.\n\n>#MAIN.HLP:072:Table of Contents\n\n***NEW in 2.65***\n\n+ Shows current mod playing in debug window\n+ Misc new counters: (ro = read only, wo = write only)\n * fread_counter (ro), fwrite_counter (wo): reads/writes a full\n   counter from file\n * board_w (ro), board_h (ro): returns current board\n   width/height\n * robot_id_(name) (ro): returns the ID number of robot \"name\"\n * r(number).(counter) (ro): returns the value of the local\n   counter for the robot with the given ID. Note that you\n   cannot write to another robot's counter.\n+ You can now save partial charsets in the editor.\n+ Sprites added; limit set at 64 global.\n+ Counters now representable in hex.\n+ Subroutines added.\n\n>#MAIN.HLP:072:Table of Contents\n\n***NEW in 2.62***\n\n+ Strings added; limited to ten strings.\n+ \"mod_order\" counter: reports the current order (that is,\n   pattern) the playing module is at. Used in conjunction with\n   the jump mod order command it can be used to save/restore a\n   mod position between boards...\n- MZX 2.62 files are no longer forward compatible. That means\n  that a file made in MZX 2.62 will NOT work in any previous\n  versions (but it WILL work in future versions, so long as\n  they set the version string correctly).\n- The string reading from files may not work correctly on the\n  first read (that is, it might truncate the first character of\n  the string). For this reason put a junk string in the first\n  part of the file and read it first.\n\n>#MAIN.HLP:072:Table of Contents\n\n***NEW in 2.61***\n\n+ Game speeds have been normalized.\n+ Original startup color scheme implemented!\n+ Redocumentation of a few features left undocumented.\n- Password protection has been fully removed.\n\n$***\n\n>#NEWINVER.HLP:1st:NEW in MegaZeux!\n>#292CLOG.HLP:292:New in Versions 2.92 to 2.92f\n>#2901CLOG.HLP:291:New in Versions 2.90 to 2.91j\n>#284CLOG.HLP:284:New in Versions 2.84 to 2.84c\n>#2823CLOG.HLP:283:New in Versions 2.82 to 2.83\n>#281CLOG.HLP:281:New in Versions 2.81 to 2.81h\n>#280CLOG.HLP:280:New in Versions 2.80 to 2.80h\n>#OLDESVER.HLP:201:New in Versions 2.01 to 2.60\n>#NEWIN200.HLP:1st:New in Version 2.00\n>#ANCENVER.HLP:1xx:New in Versions Through 1.03\n>#MAIN.HLP:072:Table of Contents\n#OLDESVER.HLP\n:201:\n$~9New in Versions 2.01 to 2.60\n\n~E2.60 release:\n\n+ File access\n+ Menu activation/deactivation\n+ Current key pressed detection\n+ Easy access to all 16 bits of a counter\n+ Pixel editing of a char set\n+ Single Byte editing of a char.\n+ Real player distance\n+ :key# added: gives twos-compliment of key value.\n- SMZX removed.\n- inter robot targeting removed.\n- Random color startup removed.\n\n>#MAIN.HLP:072:Table of Contents\n\n~E2.51ak1.0 release:\n + Added Numerous new Counters\n + Added new inter robot targeting system\n + Added new SMZX mode, dynamic resolution setting\n + Fixed fatal crash when taking a picture, also increased\n  the number of picture you can take to 99\n + Removed Built-in ASCII charset\n + Added Random color start up\n\n>#MAIN.HLP:072:Table of Contents\n\n~E2.51s3.2 release:\n\n + Removed password protection\n + Added new counter BIMesg to turn off built-in messages\n\n>#MAIN.HLP:072:Table of Contents\n\n~E2.51s3.1 release:\n\n + Fixed damage table, now works correctly\n + Fixed unworking Alt+s\n + Fixed screenshot name\n + Fixed strange missile color when firing\n\n>#MAIN.HLP:072:Table of Contents\n\n~E2.51s3 release:\n\n + Fixed bug involving savegame name being overwritten by mod\n  name\n + Fixed overlay transparency weirdness. Now, background of\n  lower layer will ALWAYS show through if background of\n  overlay is 0\n + Added in kev's refresh screen support. Press Alt+w on the\n  title screen or editor screen, and = in the game(don't ask,\n  has to do with mzx's keyboard handler). Use this if your\n  charset gets corrupted in windows.\n + Screen shot saving rotates file name(starts with screen0.pcx)\n + Fixed bug involving incorrect loading of mzx health etc.\n\n>#MAIN.HLP:072:Table of Contents\n\n~E2.51s3final release:\n\n + Fixed bug involving mod * settings being lost when loading\n + Should have fixed ems problems some were reporting, if not,\n just try to get more conventional memory. Enclosed document\n (convmem.txt) should help you.\n + Fixed a bug involving direction checking.\n + Pressing ']' at most any time will now save a screenshot to\n  screen.pcx\n + Pressing Alt+8 in the editor will set the current mod to *.\n\n>#MAIN.HLP:072:Table of Contents\n\n~E2.5.1s2beta release:\n\n + Added MOD \"*\", which allows a board to use whatever module\n  the previous board was using; this necessitated a change in\n  the .SAV file format (still need changes in the editor UI\n  to access this feature outside of Robotic)\n + inmate's semantics for MOD \"SOMETHIN.MOD*\" aren't\n  immediately possible, although a rough hack is in already\n + char edits are now not displayed until the beginning of the\n  next cycle; the deferred display should eliminate a lot of\n  flickering\n + an (at least partial) fix for the infamous UNDER bug\n + MOUSEX, MOUSEY and BUTTONS are now buffered.\n + more sane magic handling\n + can load MZX 2.51 and 2.51S1 worlds, but will only save\n  2.5.1spider2 format\n\n>#MAIN.HLP:072:Table of Contents\n\n~E2.51s1beta release:\n\n + Increased counters to 1000.\n + Added following counters: (ro) is read only, (r&w) is read +\n   write\n   * (r&w) MOUSEX, MOUSEY - Location of mouse cursor over the\n     screen.\n   * (ro)  BUTTONS - Status of the buttons (none=0,left=1,\n     right=2,both=3)\n   * (ro)  MBOARDX, MBOARDy - Location of mouse cursor over the\n     board.\n   * (ro)  SCROLLEDX, SCROLLEDY - Length the screen has been\n     scrolled in each direction.\n   * (ro)  PLAYERX, PLAYERY - Location of the player\n   * (r&w) CURSORSTATE - Turns on & off the hardware cursor\n     (0= off, 1= on, 0 default)\n + Added new independent counter like LOOPCOUNT, named LOCAL,\n   it's now possible for a robot to search the screen or\n   something without wasting a counter.\n + New .MZX and .SAV formats for MZX2.51S1.\n + .SAV files from 2.51S1 are incompatible with 2.51, and\n   vice-versa\n + 2.51S1 is capable of loading 2.51 .MZX world files.\n - 2.51 is not capable of loading .MZX files created or opened &\n   saved in 2.51S1\n\n>#MAIN.HLP:072:Table of Contents\n\n$~ENEW in version 2.51: ALL NEW music/sound code! 32-channels;\n$~Ebug-free; support for stereo, 16-bit, GUS, and PAS-16;\n$~Esupport for many formats and effects; up to four\n$~Esimultaneous sound effects!\n\nNote that the new music support requires that you CONVERT all\nnon-MOD files using the included program, 2GDM. See 'MegaZeux's\nSound System' for details on the NEW music system.\n\n>#SOUNDEFX.HLP:1st:MegaZeux's Sound System\n\nNote- This is a direct translation from the file WHATSNEW.251,\nincluded with MegaZeux. If you haven't used version 1.03 or\nbefore, you probably won't understand much of this list.\n\n~ENew in version 2.51: (quick fix before I do 3.00!)\n\n * Bug where moving north or west towards a board that is\n  larger than 100 tiles in a dimension may not work properly\n  fixed.\n * Bugs in 2GDM.EXE preventing proper conversion of some S3Ms\n  fixed.\n * A rare bug preventing internal MOD conversion fixed.\n * \"Error opening MOD\" will no longer appear during normal\n  gameplay.\n * Exporting ANSis now adds a color to the end returning text\n  to normal grey.\n\n>#MAIN.HLP:072:Table of Contents\n\n~ENew in version 2.50:\n\n * FOR ALL YOU 2.07 USERS- THE MAJOR UPGRADE is the new sound\n  code! Support for stereo, 16-bit, more cards, 4 sound\n  effects channels, 32 music channels, higher quality, and\n  faster sound code! Less bugs! More formats supported! All\n  non-MOD files MUST be converted to GDM using the included\n  2GDM.EXE program. Formats supported- MOD (up to 32 channels),\n  WOW/OCT/NST, S3M, 669, and MTM.\n\n * Volume controls (overall and SAM) added to F2-Settings (saved\n  in .CFG file)\n * SAMs play at 2x volume.\n * MTM support added to 2GDM.EXE. Other formats aren't added\n  because they aren't needed (not used often enough) or in the\n  case of XM, because they support too many advanced features\n  that MZX does not support. Partial XM support MAY be added\n  later.\n * Many minor bug fixes in 2GDM.EXE's conversion routines.\n * Internal MOD conversion (in MegaZeux) sped up considerably.\n * For all those that upgraded from 2.07, see 2.49g and 2.48b\n  WHATS-NEW for more stuff, including a couple interesting\n  new Robotic features\n\n>#MAIN.HLP:072:Table of Contents\n\n~ENew in version 2.49g:\n\n * New Import function added- You can import an ANSi to any\n  position on the board.\n * Full SAM sound effect support as in the original 2.07 version\n  (but using the new MOD code for up to 4 simultaneous\n  channels)\n * The OPEN Robotic command will now push the Robot out of the\n  way if the door would be blocked by the robot.\n * REL PLAYER and REL COUNTERS now affects the THISX/THISY\n  counters (giving the distance FROM the player/counters TO\n  the Robot) and the prefixes also affect the IF [dir] BLOCKED\n  command, allowing you to check for blocked status next to\n  the player or an arbitrary position.\n * [ box message statements now clip their message to 64\n  characters (the maximum) on display. Note that all other\n  box-message statements are NOT clipped!\n * Color codes are allowed in ? and & box message statements\n * Invalid sound card settings no longer cause a lock-up\n * You can hold down the mouse button to cycle through RGB\n  values in the palette editor\n * Changing something to an explosion no longer can cause weird\n  colors\n * What's New section of help sorted by version\n * Placing a robot over the player is prevented with a warning\n * The \"Alt+N- Music\" lights up properly in the editor now\n * When changing from a larger board to a smaller board, you\n  can no longer accidentally have the cursor outside of the\n  board size.\n * GUS owners don't need to enter # of SFX channels, since\n  MegaZeux cannot support GUS sound effects anyways.\n * File boxes (loading MODs, worlds, etc.) can now hold as\n   many filenames as memory allows.\n * GUS setup shown properly on configuration screen\n * 2GDM.EXE rewritten- In THIS release, it can only convert\n  MOD, NST, WOW, OCT, 669, and S3M files! The final (2.50)\n  release will have support for the remaining file formats.\n  It is being rewritten to fix many small bugs and make it\n  smaller and faster, as well as possibly add more file\n  formats.\n * 2GDM.EXE- F00 effect in MODs deleted during conversion\n * 2GDM.EXE- Bad effects S8x, S0x, and Xxx deleted during S3M\n  conversion\n * 2GDM.EXE now works properly from other directories\n * INTERNAL MOD loading support. You can now load MODs, NSTs,\n  WOWs, and OCTs from within the game without having to\n  convert them to GDM first. The only exception is 15-\n  instrument MODs- they must first be converted. Note that\n  all other formats, such as S3M, 669, etc. must still be\n  converted. NST/WOW/OCT support was only added because they\n  are extremely similar to MODs, and MODs were required for\n  backward compatibility.\n * Insert doesn't display those \"Lo bomb selected\" messages,\n  etc. if the player is Attack Locked or settings say he\n  cannot bomb.\n * Minor bugs in ANSi import corrected. (problem fixed- caused\n  errors on import of ANSis with more or the same number of\n  lines as the current board size maximum)\n * \"Ammo <10\" and \"Ammo >9\" entries in Global Chars changed to\n  \"Small Ammo\" and \"Large Ammo\" to prevent confusion.\n\n>#MAIN.HLP:072:Table of Contents\n\n~ENew in version 2.48b:\n\n * ALL NEW music code! Features include faster, NO BUGs, 32\n  channels, more formats supported (sorry, no MID or XM) etc.\n * Bug where selecting \"(no board)\" to add a board caused an\n  error if that wasn't the first \"(no board)\" on the list.\n * Bug where if a robot line entered consisted of only\n  semicolons, spaces, and commas, it would screw up that\n  robot's program, fixed.\n * Command line options (-port,-dma,-irq) added for setting\n  sound card parameters. Only needed if auto-detection fails.\n * Minor help corrections.\n * Save/load during testing in editor disabled.\n * Pressing \\ no longer causes problems during gameplay.\n * Bug where \"Restart board\" for death option in global\n  options didn't function properly on first board, nor did\n  restart-if-zapped. (fixed)\n * Filling in the overlay no longer locks up if you fill over\n  something of the same color and picture.\n * Overlay no longer messed up when resizing board.\n * Lock up on Import World removed.\n * Setting the counter \"INVINCO\" to 0 works properly.\n * Hopefully fixed rare lockups after robot box messages.\n * Added confirmation for board deletion in editor.\n * All included worlds are NO LONGER password protected. (The\n  old password was \"megahertz\".)\n * Minor bugs in passage search algorithms corrected.\n * Obscure bug involving REL prefixes if used in \"infinite-\n  loop\" type constructs (fixed)\n * In editor- Alt+Dir to move 10 spaces now works as expected\n  with draw mode on. (IE it will draw in ALL 10 spaces)\n * Bug where passages on screens wider than 256 spaces didn't\n  work properly. (fixed)\n * Bug where fire-to-player's-right would lock up and kill him\n  FIXED.\n * Counters can be used in ALL strings in &COUNTER& notation.\n  For example, SEND \"ROBOT\" \"LABEL&COUNTER&\" will replace\n  &COUNTER& with a number. This works for ALL strings -\n  counter names, labels, robots, etc. See Robotic help for\n  details.\n\n>#MAIN.HLP:072:Table of Contents\n\n~ENew in version 2.07:\n\n * Labels and NON-valid counter-regulated options in robot box\n  messages are not blank lines; Instead they are just removed\n  from the message.\n * Passage (stairs/caves/whirlpools) search algorithm\n  corrected.\n * Digitized sample SFX works properly with the note B now.\n * Bug where getting multiple energizers in a row screwed up\n  player's color is fixed.\n * Bug where chests with invinco potions will die when taken\n  is fixed.\n * Filling in editor, with a robot/scroll/sensor over itself,\n  is now handled properly.\n * Whee!! More minor help typos fixed.\n\n>#MAIN.HLP:072:Table of Contents\n\n~ENew in version 2.06:\n\n * Yet more cursor safeguards inserted. (Where do you GET\n  these problems!? :)\n * Saving a game no longer asks for overwrite confirmation if\n  the file doesn't really exist.\n * Help and tutorial- Minor textual errors fixed\n * Save dialog boxes only allow entry of 12 characters now,\n  instead of 13.\n * Help file can be accessed from any directory.\n * -l cmd line option will no longer cause a loading error with\n  an oversized filename. (it will instead be ignored)\n\n>#MAIN.HLP:072:Table of Contents\n\n~ENew in version 2.05:\n\n * Cursor bug fixed. (If it isn't, then get a new BIOS! :p)\n * MegaZeux now utilizes overlaid code, for almost 50k core\n  memory savings.\n * LOCKSCROLL cmd fixed\n * Moving into the lower-right corner of full-size boards no\n  longer warps you to the upper-left corner.\n * SET COLOR and COLOR INTENSITY don't blow up if the color is\n  not from 0 to 16.\n * Code for activating 16 background colors is now shorter and\n  uses BIOS calls for compatibility.\n\n>#MAIN.HLP:072:Table of Contents\n\n~ENew in version 2.04:\n\n * Slight modifications to Robotic Tutorial (help file)\n * Sending a robot a message, when it hadn't done anything yet\n  that turn, activates it immediately. This helps with\n  synchronized activities, such as large, multi-robot\n  creatures.\n * Alt+Numerics feature of BIOS keyboard routine DISABLED (IE\n  Alt+3 will no longer break to DOS)\n * Counters' code optimized.\n * The temp file ~EDITRSZ.TMP created when resizing a board is\n  now deleted when finished.\n * a KEYBOARD CODE kink was worked out (hopefully got 'em\n  all...)\n * Made printer code more general (should work on any printer\n  in text mode)\n * PALETTE BUG on some SVGA cards FIXED. (black now looks like\n  black) If it still isn't, please notify me.\n * HOME and END will jump to the top/bottom of a box\n  message/help/scroll.\n * PgUp/PgDn/Mouse navigation work properly in box\n  messages/help/scrolls\n * HOME and END in dialogs works properly now- Unless you are\n  editing a number, they will jump to the FIRST item and the\n  NEXT or OK button, respectively. In a string, they will\n  still jump to the start/end unless you are already there,\n  then they will jump to the proper dialog location.\n * Robots (especially when there are lots of them) sped up.\n * DUPLICATE SELF and COPY ROBOT cmds sped up some, except when\n  used by global robot.\n * Palette intensities reset when going into editor after the\n  title screen changes them.\n * MODULO \"str\" # will no longer crash if # is 0.\n * Extreme palette activity no longer causes snow or (on EGA)\n  screen breakup. Palette activity also sped up.\n * Bug fixed- If a SAM/MOD command had to free up board memory\n  (showing the little \"freeing up board memory...\" meter) then\n  that robot stopped running.\n * Bug fixed- If the robot changed it's surroundings (IE\n  Putting a SPACE to it's NORTH) and then did a RANDNB or\n  RANDB, the new surroundings weren't always taken into\n  consideration.\n * DIVIDE ERROR crash/bug fixed. (It was related to usage of\n  RANDNB and RANDB)\n * CHAR \"A\" vs. CHAR 'A' question added to F.A.Q.\n * README.TXT changed- Boot disk/support info added.\n * Help on cmd .\"@@string\" corrected.\n * SPEED defaults to 4 (settings) and is saved in MEGAZEUX.CFG.\n\n>#MAIN.HLP:072:Table of Contents\n\n~ENew in version 2.03:\n\n * Intensity of palette (and other stuff?) now resets after a\n  test game.\n * Messed-up black color on certain graphics cards- Attempted\n  to fix.\n * Minor bug in VER1TO2.EXE fixed\n * Robot section of Tutorial corrected (the descriptor scrolls)\n * Speeds based on real time (IE speed 3 will be the same on\n  ANY computer, unless the computer itself is so slow that it\n  forces a slower speed.)\n * CAVERNS has game over screen fixed\n * MOVE PLAYER [dir] \"label\" command now works properly.\n  (Before, the label was almost always ignored)\n\n>#MAIN.HLP:072:Table of Contents\n\n~ENew in version 2.02:\n\n * Bugs with Enter in text mode on small boards fixed\n * Bugs with slime fixed\n * If you have screen faded out (COLOR FADE OUT) and go to\n  another screen, it no longer fades it in for you\n  automatically.\n * HELP.DOC- CHANGE CHAR ID section fixed\n * Fill really does now fill properly on boards larger than\n  127x127. :)\n * Sped up sensor interactions somewhat\n * Fixed problems with boards not scrolling with the player,\n  in the game on over-sized boards. Run FIX.EXE in all\n  directories containing version 2.01 or 2.00 .MZX/.MZB files.\n * Nothing can give you negative coins, gems, etc. (such as\n  thieves)\n * Minor robot speed improvements\n * Turning Music Off (Settings in Game) really keeps it\n  off... :)\n\n>#MAIN.HLP:072:Table of Contents\n\n~ENew in version 2.01:\n\n * Sensor command CHAR'X' fixed\n * TAKEing health now counts as hurting the player for the\n  :playerhurt label\n * Can load help from any drive/directory\n * Copy block in editor now properly clips the block's\n  destination\n * Move block in editor now properly clips the block's\n  destination\n * .\"@@new_robot_name\" command added\n * If the player runs up against a player bullet, it won't\n  hurt him\n * JUSTENTERED, JUSTLOADED, and GOOPTOUCHED labels fixed\n * UNLOCKSCROLL and locked scrolling in general fixed\n * Global info now properly sets endgame/death boards\n * Robot ASK command works properly now\n * Help- internet address/address validity date corrected\n * Help- CHANGE CHAR ID help section- added note about how the\n  numbers are also listed in the Global Edit Chars menus\n * ENTER can now exit robot box messages/scrolls\n * Message line (bottom) shows color correctly\n * EXCHANGE/RESTORE w/DUPLICATE SELF fixed\n * Attempted to fix cursor problems in editor. If it doesn't\n  work now, then I have no solution, as I use documented BIOS\n  routines with many safegaurds.\n * Minor speed improvements\n * Minor size reduction\n * Fill in editor now works with board sizes over 127x127\n\n>#NEWINVER.HLP:1st:NEW in MegaZeux!\n>#292CLOG.HLP:292:New in Versions 2.92 to 2.92f\n>#2901CLOG.HLP:291:New in Versions 2.90 to 2.91j\n>#284CLOG.HLP:284:New in Versions 2.84 to 2.84c\n>#2823CLOG.HLP:283:New in Versions 2.82 to 2.83\n>#281CLOG.HLP:281:New in Versions 2.81 to 2.81h\n>#280CLOG.HLP:280:New in Versions 2.80 to 2.80h\n>#OLDERVER.HLP:260:New in Versions 2.60 to 2.70\n>#NEWIN200.HLP:1st:New in Version 2.00\n>#ANCENVER.HLP:1xx:New in Versions Through 1.03\n>#MAIN.HLP:072:Table of Contents\n#NEWIN200.HLP\n:1st:\n$~9New in Version 2.00:\n\n * All code has been rewritten or at least stepped through line\n  by line, except for the music code.\n * New user interface style and startup screen. Lots of neat\n  little items like shadows, nicer colors, etc. Mostly\n  aesthetic but nice. Mouse support also improved.\n * Keyboard code rewritten (again) All Alt+lock, shift-lock,\n  and ctrl+lock problems, as well as most other keyboard\n  problems, should not be a problem anymore. You may have to\n  use the -keyb2 command line option. If a key locks, tap it\n  a couple times.\n * No flicker- The game engine now page flips.\n * Better string input- ANYWHERE you can enter a string, you\n  can now move with the cursors and insert in the middle...\n * Better character selection box- Shows all the characters.\n * New default character set- Much nicer, more general use\n  characters.\n * Now detects processor to avoid lock ups on an old 8086/8088.\n  If an invalid graphics card is found, DOS services are used\n  for printing.\n * Different command line options. (use -? to get info on them)\n * Context sensitive help.\n * Bombs and Sensors under the player no longer mess up floors,\n  etc.\n * CHANGEing something to lava, fire, etc. now works correctly.\n * Transporting onto a Sensor works properly.\n * New conditions- MUSICON and PCSFXON\n * Changing things to spaces CLEARS them, to avoid screwing up\n  floors.\n * Restart position not changed during a save/load\n * Label- :playerhurt for when player is hurt, not sent on\n  invincible hurts\n * Maximum Robot commands per cycle is 40, not 25\n * Global Robot runs normally in freeze/slow time\n * Choice of 100x100, 200x50, 400x25, 80x125, or 60x166 for\n  each board.\n * Character editor remembers character you were editing.\n * Robo-P renamed to Robotic\n * Scrolls now allow proper use of mouse, pgup, and pgdn.\n * Scrollborder became Scrollcorner.\n * :gooptouched auto label added, for when the player touches\n  Goop.\n * Messages like \"You got a red key\" are now \"You got a key\" so\n  palette fiddling won't make strange words. :)\n * Within Robotic messages (box/line) a &INPUT& will be\n  replaced by the exact text of the currently inputted string\n * Notes in play of same freq. won't run together\n * Explosions won't destroy entrances, lava, water, ice, or\n  goop\n * Robots are stopped from firing if there is already a bullet\n  of the same dir/type in that dir.\n * Other things will push the player ONTO a Sensor.\n * The above will trigger the SENSORON label.\n * If player is on an entrance without having been there before\n  the update, use it. (I.E. can now be pushed onto entrances)\n * Teleporting or walking onto a screen and starting on a\n  Sensor will trigger the SENSORON label\n * Non-players/puzzle pieces now transport properly.\n * PLAYERLASTDIR (0-4) and PLAYERFACEDIR (0-3) as counters.\n * Points for killing enemies (3) and points for rings/potions\n  (5)\n * Way to disable edging spaces on message row- ENABLE MESG\n  EDGE, DISABLE MESG EDGE.\n * Cmds- LOOP START, LOOP # TIMES, ABORT LOOP, uses Robot\n  counter LOOPCOUNT\n * Counter limit increased to 50 plus built-in.\n * New passage search order- 1) Same type, same color 2) Same\n  color 3) Same type, same foreground 4) Same foreground 5)\n  Same type 6) Default player position\n * Palette import/export\n * If the starting board is deleted, change starting board to\n  title.\n * \"Sets of five\" numerical input actually increase by fives\n * Holding the mouse button on a numeric arrow button cycles\n  the num.\n * Removed the \".\" Directory from file menus\n * Six (not four) status counters.\n * CHANGE blah p?? blah p?? (notice the p?? added for the first\n  thing)\n * Doors, if can't move, don't advance in anim. I.E., doors\n  won't get \"stuck\" if they are blocked.\n * PERSISTENT GO command, like /\"nsew\" but WAITS to move if\n  blocked.\n * No chest message on empty.\n * Startup \"help\" screen on first use.\n * Explosions Leave Empty doesn't mess up ANY floors, etc.\n * Much better fill routine.\n * Entering a non-cmd with the first character as a [, ., :,\n  etc. automatically formats it. Leading and trailing spaces\n  and quotes are cleared first.\n * Save/Load game/etc. are accessed through F-keys in the game.\n * Removed special menu for Alt+M (modify) in editor\n * In editor, keeps track of whether world has been modified.\n * Ceiling layer - non-interactive, but overlays things and\n  looks neat. Included in editor- Edit, display toggle.\n  Robots - changing it by character, copying areas of it, and\n  filling an area of it by cmd or string (I.E. set area to \"A\n  string\") Char of 32 is see-thru. Layer during game can be on,\n  off, or static (I.E. not Scrolling, just showing upper left\n  portion).\n * Robot commands to change the mode of saving.\n * Limit any one Robot/Scroll to 31k.\n * Allow placing shooting fire in editor.\n * Make all enemies without speed arguments move 1/2 their\n  speed.\n * Player CANNOT be overwritten, you must place him anew to\n  move him.\n * First line of Robot now shows if it is a box mesg.\n * Speed 1 in game does NOT page flip, for speed.\n * Inputted strings allow input of spaces.\n * Time limits- Out of time zaps to entrance automatically,\n  THEN RESETS TIMER. Counter TIMERESET holds reset value for\n  timer, and the TIME counter holds the current time.\n * MOD code has only an error on no memory. Errors for loading\n  MODs and SAMs (I.E. not enough memory) are active if the\n  debug menu (F6) is shown. SAMs have errors for file errors/\n  out of memory. These errors are always in the editor/testing\n  games.\n * First test of a game doesn't give GAME OVER.\n * Sound effects aren't cut off across screens.\n * Placing a bomb while upon a passage no longed warps to title.\n * Sensors are pushable by things other than the player.\n * New Sensor-activated label- SENSORPUSHED, when the Sensor is\n  pushed, with or without the player on it.\n * Label- JUSTENTERED when the player just entered the screen\n  or the game is started. (NOT restored)\n * Command- Can lock/unlock board Scrolling temporarily-\n  LOCKSCROLL, UNLOCKSCROLL.\n * IF ALIGNEDROBOT \"Robot\" \"label\"\n * Damage editing- Changing the amount of damage things\n  inflict.\n * Load title screen directly when loading game for title.\n  Prevents music on first board from playing, and saves time.\n * New Robot-specific auto-counters- (read only) THISX, THISY.\n * Anywhere a number can go in a Robotic command, a counter can\n  too. Wherever there is a number, character, or color, you\n  can use a counter name in quotes instead.\n * Cmd line- load MZX file. (-lxxxxxxxx.MZX)\n * Config file for options, ask \"OK?\" on startup. Removed all\n  config cmd line options.\n * Cmd- SCROLLVIEW X Y. (upper left hand corner is specified)\n  Based off of current player position.\n * Cmd- SWAP TO WORLD \"world.mzx\" as if starting the world up.\n  Skips any title screen. (you could put a message there) The\n  other world can have a \"Only play from swap\" option set.\n * Quicksave key in game- Saves without asking for filename and\n  confirmation.\n * Quickload key in game- Same idea.\n * Option (default off) on world to clear messages, bullets,\n  and spitfires from a screen when exited.\n * Label JUSTLOADED sent to as soon as the game is started or\n  restored. (including the title screen and actual playing)\n * Shows character number on char edit and char selection.\n * Shows color number on color selection.\n * Multiple spots to SAVE/RESTORE/EXCHANGE PLAYER POSITION. (8)\n * RESTORE/EXCHANGE PLAYER POSITION with the option to\n  duplicate the Robot to take his place. When the player moves\n  back, the Robot is, of course, deleted.\n * Remove RANDOM POS/SIZE options.\n * Allow labels to interrupt in a Robot's box-message code and\n  have the message still show uninterrupted.\n * Show a pic of the item next to it in the THINGS menus.\n * Different bullet pics/colors for player, neutral, and enemy\n  bullets.\n * Status shown counters won't show if the value is 0.\n * Prefixes that affect only the first or last x y pair of a\n  command. (REL COUNTERS LAST, REL SELF FIRST, etc.)\n * Mod fading commands (background)\n * Allow lives/health to max out at 65535.\n * Score as a counter.\n * Maximum of 150 boards. (not 127)\n * Cmd- COPY BLOCK x y x y x y.\n * Label for SpitFire hitting a Robot- \"spitfire\"\n * Label for Lazer hitting a Robot- \"lazer\"\n * Cmd- CLIP INPUT (chops first word + spaces off of input)\n * Cmd- IF FIRST INPUT \"str\" \"label\"\n * KEY1 through KEY9 labels. (like KEYA thru KEYZ)\n * Allow viewport sizes down to 1x1.\n * PUSH Robot command- push things to dir without moving there.\n * ONE global Robot. (No ID) Stored separate from a board, and\n  is active on ALL boards.\n * Robots- Allow importing of a character into a CHAR EDIT\n  command.\n * Robots- Have SCROLL CHAR, FLIP CHAR, and COPY CHAR commands.\n * Full backward compatibility w/old MegaZeux via a conversion\n  program. (VER1TO2.EXE)\n * Editable built-in sfx. (including to digitized)\n * Elements- Goop, which is like Water from ZZT.\n * EGA/VGA palette editor, with easy fading. Commands- COLOR\n  INTENSITY # PERCENT, COLOR INTENSITY \"counter\" PERCENT,\n  COLOR FADE IN, COLOR FADE OUT, SET COLOR # TO r g b, SET\n  COLOR # TO INTENSITY # PERCENT, SET COLOR \"counter\" TO\n  INTENSITY \"counter\" PERCENT. (note- the FADE OUT/IN cmds\n  are \"quick\" fades, IE not in the bk, but they actually\n  stall the game a bit. They also end the current cycle.)\n * Display counters within strings, IE. * \"You have &GEMS&\n  gems.\" (use && for &)\n * Robot command- LOAD CHAR SET \"file.chr\"\n * New default characters for global chars and char set.\n * When adding another board, copy most of the options (can\n  attack, etc.) from the current board, including MOD file.\n * Edit ANY character/color from Global Chars.\n * Char editor- Revert to ASCII, Revert to MegaZeux, changed\n  REVERSE to NEGATIVE.\n * Alt+Y Debug info LABELs each line.\n * Block command- Paint (w/color).\n * Board editor- Scroll when cursor is five spaces from the\n  edge.\n * Fade in/out between screens and program areas.\n * Ice does NOT keep pushing you against something. If you are\n  blocked, cease movement.\n * Make sound test available in editor as an option, from the\n  sound effects editing screen.\n * Can't test a save-locked game w/o pw, and it no longer locks\n  up from this.\n * REMOVED Scroll coloring on line by line. (using ! codes)\n * Export .ANS file.\n * Energizer will return player color to old color.\n * Capture and throw away ctrl+C, PrintScreen, SysRq, and\n  Pause.\n * Return in editor at end of program can now add a blank line.\n * Allow marking of a section of Robot, in lines. You can now\n  Copy, Cut, Clear, or Paste blocks.\n * Remove useless \"pro\" mode.\n * Robot commands- DIVIDE, MULTIPLY, MODULO.\n * Different player pics per direction.\n\n>#NEWINVER.HLP:1st:NEW in MegaZeux!\n>#292CLOG.HLP:292:New in Versions 2.92 to 2.92f\n>#2901CLOG.HLP:291:New in Versions 2.90 to 2.91j\n>#284CLOG.HLP:284:New in Versions 2.84 to 2.84c\n>#2823CLOG.HLP:283:New in Versions 2.82 to 2.83\n>#281CLOG.HLP:281:New in Versions 2.81 to 2.81h\n>#280CLOG.HLP:280:New in Versions 2.80 to 2.80h\n>#OLDERVER.HLP:260:New in Versions 2.60 to 2.70\n>#OLDESVER.HLP:201:New in Versions 2.01 to 2.60\n>#ANCENVER.HLP:1xx:New in Versions Through 1.03\n>#MAIN.HLP:072:Table of Contents\n#ANCENVER.HLP\n:1xx:\n$~9New in Versions Through 1.03\n\n1.03 release: QUICK FIXES, LIMITED DISTRIBUTION\n\n * TELEPORT command fixed\n * Gives error on attempt to test pw-protected world\n * \"Explosions leave Empty\" won't screw up floors, etc. (except\n   web)\n * Placing a bomb over an entrance no longer warps to the title\n   screen\n * INPUT STRING allows input of spaces\n\n>#MAIN.HLP:072:Table of Contents\n\n1.02 release: NEW:\n\n * Miscellaneous help errors fixed\n * Miscellaneous errors in Caverns fixed\n * Miscellaneous speed/efficiency code updated\n * AVALANCHE fully fixed, now can be entered into a robot\n * Timing code fixed (may fix some problems)\n * Slight modification to keyboard code (may fix some problems)\n * Added -keyboard2 option\n\n>#MAIN.HLP:072:Table of Contents\n\n1.01 release: FIRST \"NON-BETA\" RELEASE:\n\n * JUMP MOD ORDER 'num' has now been documented.\n * AVALANCE corrected to AVALANCHE.\n * Robot command GO DIR \"LABEL\" fixed.\n * Some minor help errors fixed.\n * Fixed bug where a BECOME THING command, if becoming a space,\n   fake, or other \"under\"-type object, would not erase anything\n   already under the robot.\n * REL TO PLAYER and REL TO SELF now work with the TELEPORT\n   command.\n * If a robot (from WALKing) is sent to the EDGE label and none\n   exists, it then attempts to find a THUD label as well.\n\n>#MAIN.HLP:072:Table of Contents\n\n1.00g release: FIRST PUBLIC RELEASE:\n\n * Removed ALL joystick support- Didn't work, couldn't get it\n   to.\n * PW protect- lot simpler, faster, and easier to hack. :)\n * GLOBAL INFO- Toggleable game over music (you should correct\n   this in all your pre-1.00g games)\n * Fixed misc. bugs in games. SOMEONE NEEDS TO TEST THEM ALL\n   AGAIN FROM START TO FINISH.\n * Strings w/o quotes that begin with C or P are not\n   capitilized funny.\n * Settings box no longer turns off music.\n * Alt-A in editor (param) gives error if there is not a legal\n   \"thing\".\n * ALLIGNED fixed to ALIGNED (spelling error inherited from\n   ZZT)\n * Fixed bug where if slow time or freeze time was active when\n   player touched a door, player was copied.\n * Help- fixed slight bugs\n * Fixed minor problems in Caverns, Forest, and Chronos\n * MOD not required for SAM\n * Level not speeded up if MOD not playing\n * MOD not require a speed command (speed properly done)\n * Help uses Alt-P to print\n * Speed defaults to 3 not 4\n\n>#MAIN.HLP:072:Table of Contents\n\n6th beta release: NEW:\n\n * Fixed bug where slow/stop effects didn't work on title.\n * Fixed bug in command MOVE PLAYER where it would occasionally\n   create junk.\n * Fixed bug in robot editor where if \"Show Colors\" option\n   (Alt-O) was off, any color with a ? for the second digit\n   showed an incorrect first digit- cV? for cF?, etc.\n * The direction UNDER now works with the PUT thing dir PLAYER\n   command.\n * Dir/drive support added to loading MZX fileboxes.\n * HELP IS IN ONE FILE- MZX_HELP.PKG (note- memory minimum is\n   still maintained.)\n * Fixed bug where fire could hurt you indirectly even if\n   lavawalker was in effect.\n * Fixed bug where nothing (except player) could push a\n   pushable robot head on. (only if it was in a string of\n   things to push)\n * Fixed bug where shooting fire would destroy entrances.\n * Fixed bug in Put commands that would erase before placing,\n   so putting things over a fake or floor erased the\n   fake/floor. (etc)\n * Change command- Didn't change params properly if you were\n   changing between like objects (IE text to text) Also made\n   more robust against attempts to change things to\n   robots/scrolls/signs/sensors/players.\n * Put commands- Made more robust against attempts to put\n   robots/scrolls/signs/sensors/players.\n * Fixed bug where no player on a board would really mean no\n   player!\n * With Shift-F1 through Shift-F4 in editor, the key used to\n   stop the flashing is not interpreted but ignored.\n * Sensor/Robot interactions! (See help)\n * PageUp will change menus in the editor now. (along with\n   PageDown)\n * Removed all refrences (except in file format) to double\n   speed robots and projectiles. Code increase is not worth it.\n * RANDB, RANDNB directions added!\n * Misc. help corrections and bug fixes, finished help. (except\n   for minor changes)\n * Alt-O (options) no longer clears the command line at the\n   bottom\n * Character insert in robot editor now inserts the characters\n   corresponding to 10 and 13.\n * If you select base 16 in Alt-O options (robot editor) then\n   ALL numbers are shown in hex, including when editing the\n   line. (previously, the current line was shown in base 10 at\n   all times.)\n * Chronos-Stasis finished. (not tested outside of myself)\n * Forest of Ruin finished except for ending. (see above)\n * Repeat (F4) and Delete line (Alt-D) added to robot editor\n * Copy, Cut, and Paste line (Alt-C/T/P) added to robot editor\n   (note- paste REPLACES current line)\n * Export robot (Alt-X) added to editor. Exporting from current\n   line or top of program both supported.\n * Import robot (Alt-I) added to editor. Importing only looks\n   for *.TXT in the filebox. Import always REPLACES the current\n   robot program.\n\n>#MAIN.HLP:072:Table of Contents\n\n5th beta release: NEW:\n\n * Fixed bug in teleport command where screen was not updated\n   if player ended up in the same position.\n * Fixed bug where mouse didn't select right item on game menu\n * Fixed bug where adding a new robot/scroll/sensor when the\n   current item was a robot/scroll/sensor would reset the color\n   to lt.grey\n * Caverns all finished (hope I got all the bugs)\n * The DEF.COLORS option doesn't reset when changing/adding\n   boards now\n * SAM now works right...\n * In fact, added new code for MOD and VOC- a LOT less \"out of\n   mem\" or \"error loading\" errors.\n * Fixed bug with locked doors (not locks, but real doors)\n * Saving games and files is MUCH faster in almost all\n   circumstances. Save meter more accurate as well.\n * Board import/export fixed- robots/scrolls/sensors were\n   messed up\n * Pgdn, Pgup, Ctrl/Alt Home, Ctrl/Alt End added to robot\n   editor.\n * Ctrl/Alt Left + Right used to jump 10 chars in character\n   selection boxes.\n * Save area in character editor (F-2 and F-3) stays the same\n   between calls to the character editor, including when\n   loading games.\n * Character editor shows not only current char pic, but those\n   of the three chars before and three chars after as well.\n * Fixed bug where a robot with no program would majorly mess\n   up things.\n * Misc. bug fixes, help typos, and optimizations.\n !ADDED 14 COMMANDS (all commands finished)\n !ADDED  2 DIRECTIONS (randp, randnot)\n * Help brings up Robot Table of Contents within robot editor\n * Quotes not required around strings of one word not matching\n   any reserved word, IE a word used for ANYTHING else,\n   anywhere. They are added automaticaly, though.\n * EDITOR OPTIONS in robot editor (Alt-O) Choose number base,\n   upper case or lower case, and whether to display colors.\n * Bug fixed in editor- Now colors like c7? (with ? at end)\n   work right.\n * Calibrate Joystick button added to Game options (F2) Tony-\n   See if this helps.\n * Speeded up certain file operations. This MAY fix the\n   \"lockup\" bug when accessing Help during a MOD- I think that\n   the lockup may of been just a really slow file access.\n * Added X position indicator to bottom left of robot window,\n   to facilate creating box messages that aren't too long.\n * Fixed bug that allowed entering ANY character at the mixing\n   rate prompt.\n * INPUT STRING, IF STRING \"str\" \"label\", IF STRING NOT \"str\"\n   \"label\" robot commands added.\n * UNDER/BENEATH direction added to IF dir PLAYER thing \"label\"\n   command.\n * Clarified options at startup- Music device became Device for\n   Digitized Music and Sound.\n * Added four more mixing rates. Choose from 7500 up to 25000\n   in jumps of 2500. (eight rates total)\n\n>#MAIN.HLP:072:Table of Contents\n\n4th beta release: NEW:\n\n * Note- SAVE GAME FORMAT CHANGED. Delete all your .SAV files.\n   .MZX file format NOT changed- compatibility still\n   maintained.\n * Scroll border/etc. colors changable using robot commands-\n   SCROLLBASE COLOR, SCROLLBORDER COLOR, etc. (see help)\n * Removed Alt-Minus and Alt-Plus keys in editor- Useless and\n   dangerous. (they were only for testing purposes.)\n * Various bug fixes\n * Joystick support- UNTESTED! Tony- I need feedback!\n * Mixing rate options at startup.\n * A warning is given and verification is necessary if you try\n   to save a .MZX file over an already existing file.\n * You can now choose from some additional music output\n   devices.\n * Help is in seperate files, MZX*.HLP, to save memory. (lots!)\n * I FINALLY THINK THE MEMORY AND FILE CODE WORKS PERFECT!\n   Except for speed. Something to work on...\n * Char editor shows values of bytes along left side, for use\n   with robot command CHAR EDIT.\n * Yet more help\n * Robot editor! See the top box for keys to use (Import/eXport\n   not implemented yet)\n * Robots as well! See Help, Robo-P Reference, Command\n   Reference, and look at commands for help using them. Some\n   are not done yet.\n * Video mode returns to default VGA mode (text 16x9) on exit\n * File lists sorted\n * Directory support and DRIVE support in file boxes WAS added,\n   but was taken out due to problems since changing the current\n   directory makes it almost impossible to find the current\n   .MZX file to read boards from.\n * Custom Critical Error Handler (no dos grey messages if the\n   drive is invalid or something like that)\n * Password displayed as *'s when inputting, except on\n   protection menu. (Do YOU think it should be *'s on\n   protection menu? 'T'? CP?)\n * Enemys-hurt-enemys option added (GLOBAL info, SPEC. button)\n * \"Show Robots\" option added (Shift F2)\n * More help added\n * Fixed sensor bug (The player couldn't move onto it in most\n   cases)\n * Fixed a bug where if you tried to Grab something via the\n   Modify menu and the current thing was a scroll, sign, or\n   robot that had no copy anywhere else on the current board,\n   then the memory allocated to that object was not freed.\n   (whew!)\n * A few more optimizations\n * Mouse disapears after a certain period of time of non-use\n * Boulders from the AVALANCHE potion, and explosions from the\n   BLAST potion can't appear over an entrance anymore\n * Fixed some bugs in Text Export\n * Removed .BIN import- Waste of program space\n * Made ALL options (protection, ANSi import) available in\n   unregistered- now registration is only for the games\n * Removed \"suicide key\" from game. Think about how it could\n   screw up carefully planned cinemas, story sequences, etc.\n * Mouse support in character editor\n * Test function added\n * Speeded up saves and board switches\n * Slowed down all file access (okay, so it's not a feature,\n   but it's more reliable, as is memory management as well)\n * Made save meter more accurate\n * BLOCK COMMANDS! (in editor)\n * ZZT board import (not perfectly accurate, but...)\n * Bug fixed where games with the title screen title at maximum\n   length would mess up the file list box\n * MOD (SB & speaker) support! (& SAM)\n * Explosion-meets-player bug fixed\n\n>#MAIN.HLP:072:Table of Contents\n\n3rd beta release: NEW:\n\n * Full mouse support (except in character editing)\n * No \"pointer\" mouse...\n * Scrolls and sensors\n * Robot code (memory management) mostly transparent to user\n * GLOBAL Info- Death & endgame options\n * GLOBAL Info- Removed \"Health Only/Lives Only\" options\n * CW/CCW support\n * Sound always the same speed\n * Bug fixes and optimizations\n * Status Info added\n\n>#MAIN.HLP:072:Table of Contents\n\n2nd beta release: NEW:\n\n * Cmd line options\n * Mouse support (not finished)\n * -Bios option\n * Hopefully fixed Tony's graphics bugs (try it with and\n   without -bios)\n * Bug fixes\n * Tutorial.mzx has creatures/guns sections\n * Fixed/better/changed keyboard routines\n * Optimized code\n\n>#NEWINVER.HLP:1st:NEW in MegaZeux!\n>#292CLOG.HLP:292:New in Versions 2.92 to 2.92f\n>#2901CLOG.HLP:291:New in Versions 2.90 to 2.91j\n>#284CLOG.HLP:284:New in Versions 2.84 to 2.84c\n>#2823CLOG.HLP:283:New in Versions 2.82 to 2.83\n>#281CLOG.HLP:281:New in Versions 2.81 to 2.81h\n>#280CLOG.HLP:280:New in Versions 2.80 to 2.80h\n>#OLDERVER.HLP:260:New in versions 2.60 to 2.70\n>#OLDESVER.HLP:201:New in versions 2.01 to 2.60\n>#NEWIN200.HLP:1st:New in version 2.00\n>#MAIN.HLP:072:Table of Contents\n@\n"
  },
  {
    "path": "docs/changelog.txt",
    "content": "GIT - MZX 2.94\n\nFIXES\n\n+ TODO: 2.94 enables board message clipping bugfixes from 2.93b.\n+ TODO: 2.94 fixes tiger self destruct behavior by default (2.93d).\n+ TODO: 2.94 should allow configuration of tiger behavior per-board.\n\n\nGIT - MZX 2.93e\n\nUSERS\n\n+ fsafetranslate now rejects paths containing invalid characters\n  for NTFS/VFAT/exFAT filenames and paths containing reserved\n  DOS/Windows devices. There is no compatibility check for this,\n  as this has always been very broken in DOS/Windows, and no\n  known games made in Linux/macOS rely on this.\n+ The file manager rejects invalid NTFS/VFAT/exFAT characters\n  and reserved DOS and Windows devices when selecting asset file\n  paths (or when selecting any file on DOS/Windows).\n+ Invalid NTFS/VFAT/exFAT characters and DOS/Windows reserved\n  devices are now rejected from manifest files on all platforms.\n+ DOS/Windows reserved devices are now rejected from startup\n  files, default save names, config includes, and char editor\n  partial charset wildcards on DOS/Windows.\n+ Fixed out-of-memory error on some platforms when attempting to\n  flood fill the player in the editor.\n+ Fixed checkres testing of 2.90X through 2.92X worlds with\n  custom sound effects tables.\n+ Added file manager support for handling Amiga paths.\n+ Added file manager support for listing volumes on Amiga.\n+ The file manager now correctly ignores rename/delete when used\n  on volumes in the directory list for Wii, PS Vita, and Amiga.\n+ Fixed file manager crashes with world filenames over 56 chars.\n+ Fixed file manager deleting files with names over 56 chars.\n\nDEVELOPERS\n\n+ Renamed enum thing __TEXT to TEXT_ID to fix conflicts with\n  libnix BSD sockets headers.\n+ Fix vfwrite return value for size 0 on DragonFly BSD.\n+ Replace various instances of audio and editor single-precision\n  floating point with doubles or 64-bit integers.\n+ Updated dependency builder scripts: SDL3 3.4.4, SDL2 2.32.10,\n  libpng 1.6.57, libogg 1.3.6.\n\n\nJune 9th, 2025 - MZX 2.93d\n\nThis release includes several features and bugfixes to help with\nportability. Screen keyboard support from the NDS/3DS ports has\nbeen generalized and enabled for SDL ports; Android and Vita can\nnow open their respective screen keyboard interfaces by pressing\nright shoulder on the gamepad (configurable). The Android port\nno longer overwrites config.txt if it has changed. Other notes:\nbetter AltGr tolerance, faster software renderer performance,\nrendering fixes for softscale, glsl/glslscale, and opengl1/2.\n\nCompatibility changes include Spitting Tiger logic bugfixes and\ncorrected version locking for the KEY? and KEYENTER labels.\nSeveral out-of-bounds accesses in ID char resolution and in the\nMissile logic have been fixed, which may introduce (unfixable)\ndifferences in behavior for out-of-range parameters. There are\nalso various module playback improvements (libxmp 4.6.3).\n\nSDL3 is now selected by default for Windows, Emscripten,\nand Vita (thanks to Aeon17 for Vita testing). SDL3 adoption for\nmacOS has been delayed to 2.94 (requires increasing the minimum\nOS version to 10.14).\n\nUSERS\n\n+ MegaZeux on Android no longer overwrites config.txt if it has\n  more recent changes than the copy in assets.zip.\n+ Added support for system screen keyboards for SDL. This allows\n  opening the system screen keyboards with the Android and Vita\n  ports, as well as some Linux configurations e.g. Steam Deck.\n  The screen keyboard can be toggled with rshoulder (see below)\n  or, in Android, by performing a 3-point touch on the screen.\n+ Added config option \"joy#.show_screen_keyboard\" to configure\n  which button (if any) is used to open the screen keyboard for\n  platforms that support it (set to act_rshoulder by default,\n  except for Linux, where it's disabled by default).\n+ Added config options \"key_left_alt_is_altgr\" and\n  \"key_right_alt_is_altgr\". When set to 1, these prevent their\n  respective Alt keys from activating built-in UI and editor\n  keyboard shortcuts e.g. Ctrl+Alt+Enter. They do not alter the\n  keycodes emitted by these keys. This may be useful for Windows\n  international keyboard layouts and for macOS. These options\n  can be disregarded for Wayland/X11 (AltGr has its own keysym)\n  or other ports that don't have AltGr.\n+ More strict modifier checking for keyboard shortcuts that use\n  alphanumeric keys in places with text entry: dialog boxes,\n  editor, scroll editor, Robot editor, Robot debugger config,\n  and variable debugger. This should make it easier to use\n  Windows Ctrl+Alt style AltGr in these interfaces.\n+ Wayland/X11 AltGr now correctly emits internal keycode 313 and\n  PC XT keycode 56.\n+ The KEY? label is no longer sent for pre-2.62 worlds when\n  the char pressed is outside of the A-Z 1-9 range, and is no\n  longer sent for 1.x worlds when the char is outside of the A-Z\n  range. (Previously checked version for the KEY counter only.)\n+ The KEYENTER label is no longer sent for pre-2.62 worlds.\n+ Fixed bug where, after Alt+R or failure to reload __TEST.MZX,\n  the editor would not mark the new blank world as active. This\n  would cause the screen to not be drawn during Alt+T testing.\n+ Spitting Tigers from pre-2.80 worlds no longer move slower\n  than they're supposed to with low intelligence, and no longer\n  shoot themselves on these buggy idle cycles when \"Enemies'\n  bullets hurt other enemies\" is enabled.\n+ Spitting Tigers that shoot fire now play the fire SFX.\n+ Fixed out-of-bounds array reads when a Missile with an invalid\n  parameter attempted to turn after colliding with itself (no\n  compatibility checks for old behavior).\n+ Fixed out-of-bounds array reads when reading the char of or\n  displaying (no compat checks): Ice, Lava, CWRotate, CCWRotate,\n  Pusher, Missile, Spike, Bullet, Fire, Life, Ricochet Panel.\n+ Fixed out-of-bounds array reads when reading the color of or\n  displaying (no compat checks): Energizer, Fire, Life.\n+ ANSi export now replaces char 26 with a dash to avoid import\n  bugs and conflicts with other software.\n+ Added ANSi export support for Doorway mode, a non-standard\n  ANSI extension that allows displaying control characters that\n  would otherwise get stripped out. MegaZeux now supports\n  loading ANSi files that use Doorway mode, as well.\n+ Software layer renderer performance enhancements for repeat\n  colors, clipping, and unaligned renderering.\n+ Unaligned software layer rendering using 64-bit writes is\n  now enabled for x86-64, POWER8, Emscripten, and probably most\n  AArch64 builds. This optimizes out up to 48 renderers for\n  these architectures, and improves 8-bit, 16-bit, and 32-bit\n  rendering performance.\n+ Unaligned software layer rendering using 32-bit writes is\n  now enabled for x86, PowerPC, and M68k. This optimizes out up\n  to 24 renderers for these architectures, and improves 8-bit\n  and 16-bit rendering performance.\n+ Fixed slow softscale rendering for some drivers. Texture\n  streaming is now used for direct3d/consoles/software only.\n+ Fixed OpenGL rendering issues in Wayland caused by OpenGL's\n  transparent default clear color.\n+ Fixed opengl1 renderer colors missing alpha component.\n+ Fixed glsl, glslscale, opengl2 renderers sometimes showing a\n  thin white line on the edges of the screen.\n+ Fixed date and time counters on NDS (missing IRQ_NETWORK).\n+ Fixed VFS cache failing to init caused by getcwd returning\n  a root with no trailing slash.\n+ Unsupported mouse button presses are now ignored.\n+ Fixed the window icon for Flatpak builds.\n+ Fixed the directory mentioned in the PS Vita README.md\n  (claimed \"ux0:/data/MegaZeux\", actually \"ux0:/data/megazeux\").\n\nDEVELOPERS\n\n+ Added renderer set_window_caption, set_window_icon functions;\n  moved SDL-specific icon functionality out of graphics.c.\n+ Failure to query X11 no longer disables the icon in Linux/BSD.\n+ manifest.sh no longer requires bash. All scripts and Makefiles\n  should now be fully compatible with POSIX sh (with only a few\n  GNU extended options remaining where required).\n+ Fix compilation with glibc versions prior to 2.17 caused by\n  clock_gettime (disabling for now until -lrt can be tested).\n+ Fix compilation with libpng versions missing png_const_bytep\n  and png_set_add_alpha.\n+ Remove -Wno-missing-field-initializers to fix GCC 3.4 support.\n+ Remove redundant order-only prerequisites in src/Makefile.in\n  to fix building with older versions of GNU Make.\n+ Define _LARGEFILE_SOURCE in addition to _FILE_OFFSET_BITS=64\n  in the unix Makefile to fix builds with old glibcs that\n  predate _FILE_OFFSET_BITS.\n+ Numerous warnings fixes/workarounds affecting macOS, NetBSD,\n  OpenBSD, 3DS, GCC 4.x, SDL 1.2, and old versions of libpng.\n+ Fixed OS detection, unit tests, and testworlds for Haiku.\n+ Replaced return values of all VFS functions to fix issues with\n  using errno values here on Haiku.\n+ Add Emscripten Makefile support for SDL3.\n+ Add PS Vita Makefile support for SDL3 and SDL 1.2.\n+ make install now installs an AppStream .metainfo.xml file.\n+ Added --metainfodir [dir] option to config.sh. This setting\n  defaults to [sharedir]/metainfo for relevant platforms.\n+ --licensedir now defaults to [sharedir]/licenses instead of\n  [prefix]/share/doc.\n+ Fixed ansi.c compilation failure with --disable-datestamp.\n+ More accurate FPS counter for --enable-fps.\n+ Updated libxmp to 4.6.3+sample rate patch.\n+ Updated dependency builder scripts: SDL3 3.2.16, SDL2 2.32.8,\n  libpng 1.6.48.\n\n\nFebruary 28th, 2025 - MZX 2.93c\n\nYes, the text input bug and show thing keys are fixed now. :)\n\nInitial support for SDL3 has been added, and will be enabled for\nthe Fedora and AUR builds for this release. Other platforms will\ngradually transition to SDL3 over the next few releases.\n\nAs part of the preparation for SDL3, this release includes many\nlong-belated changes to the way MegaZeux resizes windows and\ncalculates the scaled display area within the window (including\nmouse coordinate conversion). This overhaul has fixed several\nbugs, but please be on the lookout for regressions.\n\nThe Xcode port has been updated to build with the latest version\nof SDL and other dependencies, and now requires OS X El Capitan\n(x86_64) or macOS Big Sur (arm64) to run. This port no longer\nsupports x86 (32-bit). The Darwin multiarchitecture port has\nalso been updated to fix arm64 and arm64e support (only arm64 is\nincluded in builds).\n\nAlso included are Android fixes for Android 11+ (thanks asie)\nand also a fix for a bug preventing MZX from starting on Jelly\nBean and KitKat.\n\nUSERS\n\n+ The DOS Sound Blaster driver now supports Sound Blaster with\n  DSP 2.0, Sound Blaster 2, and Sound Blaster Pro/16 mono mode.\n  Original Sound Blaster with the old DSP is still unsupported.\n+ The DOS port and SDL ports now support mono audio. Mono can be\n  selected with the new config option \"audio_output_channels\".\n+ Setting the config option \"system_mouse\" to \"1\" or \"on\" will\n  no longer disable the software cursor. The old behavior can be\n  restored by setting it to \"only\". The new behavior of \"1\" is\n  more useful, especially in Wayland (due to forced mouse grab\n  when the system cursor is hidden).\n+ macOS now enables fullscreen_windowed by default to work\n  around macOS bugs involving real fullscreen and maximizing.\n+ Fixed window resize bugs in Windows, macOS, Linux/BSD (both\n  X11 and Wayland), and probably other operating systems caused\n  by MZX recreating the window after every window resize event.\n+ Fixed junk display in the letterbox area after resizing using\n  the software, gp2x, or SDL 1.2 overlay renderers.\n+ The software and gp2x renderers now enable blitting when the\n  created window is smaller than expected instead of crashing.\n+ Fixed sai.frag not being installed by \"make install\" or\n  distributed with Unix-style packages for Linux/BSD/Darwin.\n+ Fixed the board editor show thing hotkeys (broken by 2.93b).\n+ Fixed nested riff behavior in the RAD replayer. The replayer\n  would fail to replace the outer riff with the inner riff in\n  some cases.\n+ Fixed a crash that would occur in builds using extra memory\n  hacks (DOS) when copying blocks between boards in the editor.\n+ Fixed text input bugs caused by a 2.93b change accidentally\n  reenabling old deadcode for exiting intake().\n+ Fixed crash when saving editor config files/\"Set as Default\"\n  after the file fails to open for write. If the \".editor.cnf\"\n  file fails to save or load, MZX will attempt to save or load a\n  \".cne\" file instead. This allows this feature to work in DOS\n  builds when there is no long filename (LFN) support.\n+ Fixed filesystem permissions on Android 11.0 and up. (asie)\n+ Fixed linkage bug preventing MegaZeux from starting in Android\n  Jelly Bean and KitKat due to a missing logging symbol.\n+ Added support for pre-2.70 \"put thing BENEATH\" being ignored.\n+ Added support for pre-2.80 \"put thing\" treating p?? as 0.\n+ Robo-P p0 now converts to either p00 or p?? when appropriate.\n+ Robo-P PUT commands now ignore the high thing bit instead of\n  combining it into the color.\n+ Fixed disassembly of out-of-bounds color and param values to\n  match their most common interpretation during gameplay.\n+ Fixed assembly of immediates in second char of CHANGE OVERLAY.\n+ Out-of-bounds char values are now disassembled to immediates\n  for CHAR EDIT, COPY CHAR, SCROLL CHAR, and FLIP CHAR. This\n  makes commands such as \"CHAR EDIT 256 [...]\" more usable since\n  they will persist in the robot editor.\n+ Temporarily revert the broken Vita SHAREDIR/startup dir split.\n  MegaZeux now expects assets to exist in ux0:/data/megazeux\n  like the README claims.\n\nDEVELOPERS\n\n+ Added initial support for SDL3. No architectures select SDL3\n  by default, but it is now possible to build and test MegaZeux\n  for platforms that already support it.\n+ Split the renderer function \"set_video_mode\" into two renderer\n  functions \"create_window\" and \"resize_window\" to more closely\n  reflect the reality of SDL2/3. The former is used during\n  renderer init and is mandatory; the latter is used to resize\n  and update other window settings like mouse grab/visibility\n  e.g. when switching to/from fullscreen, and is optional. If\n  it is not provided, switching to/from fullscreen will do\n  nothing. \"resize_window\" is also invoked in SDL 1.2 when a\n  window resize event is received. Some renderers (software,\n  gp2x) still use the same implementation for both functions.\n+ Renamed the renderer function \"resize_screen\" to\n  \"resize_callback\" to more accurately reflect its purpose. It\n  is called in response to SDL2/3 window resize events and after\n  \"resize_window\" (by the video functions that call it, see\n  above). This function is now responsible for post-resize\n  maintenance such as glViewport and selecting the filter mode,\n  and is no longer required to call update_screen.\n+ Replaced renderer functions \"get_screen_coords\" and\n  \"set_screen_coords\" with a single function \"set_viewport\" to\n  calculate the current window's display area. This area is used\n  in places where \"fix_viewport_ratio\" previously was and also\n  to convert mouse coordinates. Most renderers should use one of\n  the two prefab implementations of this function.\n+ SDL text event enablement now happens after the window is\n  created. This shouldn't really affect anything.\n+ Added detection for LoongArch64. 64-bit architectures that\n  platform_endian.h fails to identify as such should no longer\n  abort in the software layer renderer.\n+ Updated the Xcode project. Supported architectures are now\n  x86_64 and arm64. The x86_64 architecture requires OS X El\n  Capitan (minimum version supported by SDL) and arm64 requires\n  macOS Big Sur. The Xcode project format is still 8.x, but\n  Xcode 12.2 or newer is required to compile release builds.\n+ Tweaked the Darwin .app buildsystem to properly support arm64\n  and arm64e. The documentation has also been updated to note\n  linker issues in versions prior to 10.10 when combining\n  x86_64 and x86_64h into the same binary, and issues involving\n  arm64e builds being terminated by the kernel.\n+ The dependency builder scripts now support building Xcode\n  frameworks for both x86_64 and arm64. Updated SDL2 to 2.32.0,\n  libpng to 1.6.47, libogg to 1.3.5, and libvorbis to 1.3.7.\n  Added building SDL3 3.2.4 for all relevant platforms.\n+ Enabled 16k pages on Android to hopefully future-proof\n  MegaZeux against Android 15.0. (asie)\n+ Updated libxmp to 4.6.2+sample rate patch.\n\n\nSeptember 10th, 2024 - MZX 2.93b\n\nThis is mostly a big bugfix release, but there are a few new\nfeatures of note. First and probably of most interest, there is\na new feature to export a full screenshot of an entire board or\nthe entire vlayer from the editor. Renderers based on the\nsoftware layer renderer should see some performance improvement.\nThe Darwin multiarchitecture .app port has also been massively\noverhauled, and now targets up to 5 architectures (ARM still\npending). libxmp now plays stereo samples correctly.\n\nDOS port users should be happy to hear that Sound Blaster Pro\nsupport has been restored, and support for even more sound cards\nis coming. Additionally, some Ogg Vorbis-related crashes have\nbeen fixed, and the SVGA renderer should work better than prior.\nScreenshots have been enabled for this port, and the TIME/DATE\ncounters should now work properly.\n\nFinally, as usual, there are a large number of bugfixes, which\nincludes several crash fixes, scroll fixes, and FREAD fixes.\nNo compatibility checks have been added for the string splice\nFREAD/FREADn bugfixes, as the old behavior was unusably buggy.\n\nUSERS\n\n+ Added board and vlayer image export to the editor (Alt+X).\n  This feature will render the entire board (with overlay, if\n  applicable) or vlayer as it appears in the editor. This may\n  take a long time with large boards/vlayers.\n+ Builds without libpng (Android, DOS) now use a simple fallback\n  PNG exporter for screenshots instead of falling back to BMP.\n+ The DOS port now supports Sound Blaster Pro in stereo mode.\n  The driver should also support all other Sound Blasters with\n  DSP >=2.00 and the mono/8-bit modes of the Sound Blaster 16,\n  but these are disabled for now (no software mixer support for\n  mono output). IRQs higher than 7 are also supported now.\n+ The DOS port now uses stb_vorbis for Ogg Vorbis playback,\n  which should fix most audio-related crash bugs. Related crash\n  bugs caused by stream management have also been fixed.\n+ All \"gamecontroller[...]\" config.txt options have been renamed\n  to \"gamepad[...]\". The old names are still supported.\n+ Windows UNC paths are now supported in the file manager.\n+ Paths with colons (:) are now explicitly supported in Linux\n  and macOS and always rejected in path strings used by worlds.\n+ Fixed blank screen bugs that occur with the glslscale renderer\n  in conjunction with Intel HD Graphics 2500 graphics drivers\n  and possibly some other old drivers.\n+ The softscale renderer now respects disable_screensaver.\n+ HTML5: fixed the poor performance of FREADn and other features\n  that rely on calculating the length of an open file.\n+ Clear world (Alt+R) now resets the world version.\n+ Fixed a crash bug and a false positive match bug in the\n  command IF ALIGNEDROBOT. The false positive bug has been\n  version locked to 2.80 through 2.92X (just in case).\n+ Software layer rendering performance for transparent sprites\n  has been improved, significantly in some cases.\n+ Fixed a software layer renderer bug where a buffer with a\n  pixel alignment that varies between lines would cause the\n  frame to render incorrectly.\n+ Fixed a software layer renderer bug in 32-bit builds where\n  SMZX sprites would sometimes render incorrectly when clipping\n  off the left side of the screen.\n+ Setting a string offset, limit, or splice to FREADn now\n  respects the provided limit. If n is larger than the limit,\n  n total bytes will still be read (up to the maximum string\n  size); any bytes read past the limit are discarded.\n+ Setting a string offset, limit, or splice to FREAD now\n  respects the provided limit and no longer pads the string with\n  spaces in some cases. Setting any string to FREAD now reads\n  the file until a terminator or the end of the file is found\n  instead of stopping when the maximum string size is reached;\n  any bytes read past the limit or maximum size are discarded.\n+ Setting a string splice to FREAD or FREADn with invalid offset\n  or size values no longer causes a crash.\n+ Setting a string splice to FREAD with a valid offset that\n  would write past the current length no longer causes a crash.\n+ Fixed a rare networking bug where fetching a compressed file\n  over HTTP could fail for particular compressed file sizes.\n+ The About MegaZeux dialog now shows both the compiled and\n  linked versions of libpng if they are different.\n+ Fixed flash thing layer draw order bug that would cause it to\n  display over editor elements using the game UI layer.\n+ Licenses are now properly loaded in the About MegaZeux dialog\n  for Darwin builds.\n+ Fixed potential heap overflows in mfread and mfwrite when the\n  current position is past the end. This should never actually\n  happen, but it's safer and shuts up _FORTIFY_SOURCE and ASan\n  compilation warnings.\n+ Fixed platform-dependent out-of-memory errors when loading\n  robots from <=2.84 save files with corrupt stack sizes.\n+ Fixed hangs in the robot editor caused by trying to use\n  Replace All to replace quotes or extra words with nothing.\n+ Fixed the handling of lines over 64 characters long during\n  scroll editing. Scroll editing now previews colors and allows\n  lines up to 255 bytes in length. Like robot message boxes, any\n  characters past the first 64 will clip.\n+ Clicking a line in the scroll display and editor now jumps to\n  that line (same as the help file).\n+ You can now toggle between editing scrolls with the protected\n  palette/charset and the game palette/charset with Alt+V.\n+ Games for versions 2.80X through 2.90X now display scrolls\n  using the protected palette instead of the game palette (fixes\n  a scroll in Welkin).\n+ Fixed possible robot stack and scroll message memory leaks.\n+ Fixed a 2.93 regression where color strings with escapes like\n  \"~x\", where x is not a hex digit, would also print the ~ or @.\n+ Fixed clipping of the board message when it contains color\n  codes and when the message column is set to something besides\n  centered or 0. The next row will also use the correct color\n  code from the previous row (as if there had been no clipping)\n  instead of whatever the last color code displayed was. The old\n  behavior is version locked to <=2.93, so this fix won't take\n  effect until 2.94.\n+ Added compatibility for MegaZeux 1.x's reverse send behavior\n  which allows robots to execute twice per cycle in some cases.\n+ Fixed COPY when used with REL COUNTERS for 1.x worlds.\n+ Added support for MegaZeux 1.x entrance precedence (color\n  match, then thing match only). This fixes an entrance bug in\n  MegaZeux Tutorial.\n+ Added support for MegaZeux 1.x entrances linking to other\n  entrances on the same board. This fixes the whirlpools in the\n  Aqua Cavern board in Caverns of Zeux.\n+ Added support for MegaZeux 1.x entrances not triggering when\n  the player is pushed onto them.\n+ Added support for MegaZeux 1.x TELEPORT interrupting the\n  current board scan and partial support for the same behavior\n  in RESTORE PLAYER POSITION/EXCHANGE PLAYER POSITION.\n+ The DJGPP port now locks all of its memory. This prevents\n  paging crashes related to audio, but increases memory usage.\n+ Fixed the TIME and DATE counters in the DOS port when the\n  environment variable TZ is not set.\n+ The SDL audio driver will now initialize the software mixer\n  frequency with the value returned by SDL_OpenAudioDevice.\n+ Fixed audible pops when setting MOD_FREQUENCY.\n+ ccv and png2smzx now return slightly more useful error\n  messages when an image fails to load.\n+ ccv and png2smzx now support BMPs that use BI_BITFIELDS and\n  BI_ALPHABITFIELDS.\n+ ccv and png2smzx now support Truevision TGA images.\n+ Fixed YUV subsample averaging for UYVY (little endian) and\n  YUY2/YVYU (big endian).\n+ ANSi file import/export options are now identified as both\n  ANSi and TXT, and the extension of the selected format is now\n  forced during export.\n+ Fixed taking screenshots when in the board editor text mode.\n+ The SVGA renderer now uses vsync palette/display page flips,\n  which improves performance and fixes display bugs. (asie)\n+ Fixed libxmp stereo samples not playing correctly.\n+ Fixed libxmp music not playing at high audio sample rates.\n\nDEVELOPERS\n\n+ Renamed \"compat_sdl.h\" to \"SDLmzx.h\". This header is now used\n  to abstract ALL trivial includes of SDL headers. This makes\n  supporting the future release of SDL 3 cleaner.\n+ The version of SDL to build with is now determined by the\n  config.sh options:\n    --enable-sdl (enables the default SDL version, usually 2);\n    --enable-sdl2 (enables SDL 2.x);\n    --enable-sdl1 (enables SDL 1.2.x);\n    --disable-sdl (disables SDL).\n  For compatibility, \"--disable-libsdl2\" is still recognized.\n+ Added SDL 2 support to the Wii port.\n+ Added SDL 2 and SDL 1.2 support to the 3DS port. (asie)\n+ Updated the \"sdlaccel\" renderer, which can now be enabled with\n  --enable-sdlaccel in config.sh. This renderer uses the SDL\n  renderer API for hardware rendering and has an experimental\n  threaded charset texture update routine. SMZX isn't currently\n  implemented and it's slow, so it's disabled by default.\n+ Moved SDL renderer window creation to render_sdl.c so both of\n  the SDL renderer-based renderers can benefit from it.\n+ Removed floating point division usage in render_gl2.c.\n+ Added --enable-lto to enable link-time optimizations. This is\n  now enabled by default in most platforms' config scripts.\n+ The sanitizer config.sh options can now be used with release\n  builds e.g. --enable-release --enable-asan.\n+ Simplified the configuration of system directories for ports\n  in config.sh, which now prints more information about them.\n+ Fixed config.sh output of \"/usr/bin/git\" and the Git HEAD.\n+ Fixed testworlds process running check edge cases.\n+ Fixed config.sh and platform_endian.h detection for IBM Z and\n  System/390 architecture.\n+ Software mixer configuration (rate, buffer, channels) is now\n  done using the function audio_mixer_init instead of each\n  driver handling it manually.\n+ The software mixer render function now takes the number of\n  frames and channels to render and the output format instead\n  of the number of bytes. Supported output formats are 16-bit\n  signed, 8-bit signed, and 8-bit unsigned.\n+ Audio streams with a null mix_data function are now supported.\n  These streams will be ignored by the software mixer.\n+ sampled_mix_data now properly handles output requests smaller\n  than the size of the audio buffer.\n+ Added software renderer regression tests.\n+ Removed PPAL compile time render_layer variants. These did not\n  help performance and increased the render_layer size by 50%.\n+ Added dependency builder makefile system in scripts/deps to\n  help with regenerating MinGW, DJGPP, darwin-dist, and Linux w/\n  MemorySanitizer dependencies.\n+ Overhauled the darwin-dist build system to allow for more\n  modern XcodeLegacy-based compilation, including code signing\n  and shared library support (via dylibbundler). The following\n  architectures are now supported: i386, x86_64, x86_64h, ppc,\n  ppc64, and arm64/arm64e (untested).\n+ Updated libxmp to 4.6.0+6cb48a4a+sample rate patch.\n+ Added more PowerPC64 GCC defines to platform_endian.h.\n\n\nDecember 31st, 2023 - MZX 2.93\n\nThis is the first MegaZeux release in about 3 years, so there\nare a lot of changes, including a large number of crash fixes.\nBrief summaries of each section:\n\nGeneral: worlds are decrypted in memory now, MegaZeux 1.x worlds\nare now supported, you can have more custom sound effects, the\n1/8th random movement chance of dragons has been readded, and\nstatic sprites now collide at their apparent position.\n\nRobotic: new label PLAYERDIED, new counter KEY_PRESSEDn, new\ncounter DATE_WEEKDAY, new counter SPRn_OFFONEXIT, new viewport\ncounters, and setting MOD_LOOPSTART after MOD_LOOPEND now works.\n\nEditor: the robot editor now has undo/redo, layer MZM block type\ncan now be selected, reimplemented ANSi/TXT import/export,\nwatchpoint improvements, a new variable debugger RAM menu, and\nvlayer saved positions.\n\nVideo/audio: most GLSL performance and graphical issues have\nbeen addressed and libxmp has received a massive bugfix update.\n\nPortability: new PS Vita, DOS, Wii U, and Dreamcast ports.\nNumerous NDS and 3DS improvements. The extra memory system from\nthe NDS port has been generalized to several ports and now\nsupports memory compression. Virtual filesystem support.\n\nUtilities: vastly expanded image file support for ccv/png2smzx\nand a new video conversion utility called y4m2smzx.\n\nGENERAL\n\n+ Added an \"About MegaZeux\" dialog, accessible from the main\n  menu. This menu contains extended version information, and\n  displays license information for MegaZeux and the 3rd party\n  software it uses.\n+ Protected worlds are now decrypted to RAM or a temporary file\n  when 'auto_decrypt_worlds' is enabled and the original file is\n  left unmodified. This setting is now enabled by default.\n+ Added experimental support for loading MZX 1.x worlds. Support\n  is not fully implemented yet, but all currently known 1.x\n  worlds are playable.\n+ Up to 256 custom sound effects are now supported instead of\n  50, and custom sound effects can be up to 255 chars in length.\n  Currently, the sound effects editor is restricted to 100 SFX\n  of length 68; the CHANGE SFX # to \"\" command may be used as a\n  temporary workaround.\n+ Custom sound effects can now be given custom names in the\n  sound effects editor. Currently names are limited to 9 chars\n  and are only useful when editing.\n+ Fixed dragon 1/8th chance of random movement, which has been\n  broken since 2.80. The original DOS behavior (random movement)\n  or the 2.80 behavior (no random movement) can be selected via\n  the new board setting \"Dragons randomly move\".\n+ Reset Board on Entry and load charset/palette on entry now\n  optionally will be performed even if re-entering a board from\n  itself (enabled by default).\n+ Fixed a Reset Board on Entry bug where a temporary board would\n  not be generated when loading the title screen or if the\n  starting board is the title board.\n+ Fixed bug where the second cycle of a board when entering from\n  a board edge or entrance would be shortened due to incorrect\n  gameplay framerate timing introduced in 2.91g.\n+ Fixed a bug where, if a world's title screen music is set but\n  fails to load when the world is loaded, the music from another\n  world could continue to play.\n+ Fixed a bug where boards wouldn't be pulled from extra memory\n  before saving, potentially causing save corruption on the NDS.\n+ Fixed a standalone mode crash on exit that occurred when using\n  Alt+F4 or the window close button.\n+ Fixed a crash that could occur when loading a save file with\n  the temporary board flag set but no temporary board data.\n+ Fixed a bug in the random number generation function that\n  could cause it to rarely return a number outside of the\n  expected range. This unfortunately breaks compatibility for\n  the RANDOM_SEED# counter.\n+ Static sprites now collide as if they are at the position on\n  the board where they are currently being drawn i.e. relative\n  to the viewport instead of at their actual X/Y position. This\n  behavior has also been added to the IF SPRITE AT # # command.\n+ Optimized ccheck 3 sprite collision performance by generating\n  visibility masks for characters directly instead of using an\n  intermediate render and skipping pixel checks for blank chars.\n+ Fixed a bug where worlds with SMZX mode selected would display\n  the previous world's palette after switching back to MZX mode.\n+ World and save files now save the MZX mode palette when in\n  SMZX modes 2 or 3. Save files now save the MZX mode palette\n  intensities when in SMZX modes 2 or 3. The palette and palette\n  intensities for SMZX modes 2 and 3 are now saved as \"palsmzx\"\n  and \"palints\" when one of these modes is active.\n+ Save files now save palette intensities for all modes as a raw\n  array of little endian DWORDs instead of bytes. The 2.92 and\n  2.93 intensities file formats are mutually incompatible.\n+ Scrolls can now display standard message ~@ color codes.\n+ Fixed warning messages caused by SDL controller CRCs.\n+ Fixed hangs that could be caused by malformed counters files.\n+ Fixed world/save/counters file corruption in rare edge cases\n  where the aforementioned files could break the zip format's\n  internal field bounds.\n+ Fixed loading of world/save/counters files over 4 GB.\n+ Fixed crashes when loading world or save files that contain\n  invalid current board IDs (both loaders), saved position\n  board IDs (both), start/death/game over board IDs (zip), board\n  exit board IDs (zip), robot program positions (both), robot\n  stack pointers (both), or robot stack sizes (legacy).\n+ The output file mode (FWRITE_OPEN, FWRITE_MODIFY, or\n  FWRITE_APPEND) is now stored in save files. This fixes a bug\n  where the output file would be reopened in the wrong mode (ab)\n  when reloading a saved game.\n+ Added a second Robotic command listing to the help file that\n  lists commands categorized by command type. (Terryn)\n+ Links in the help file table of contents and Robotic Reference\n  Manual are now grouped into categories. (Terryn)\n+ Other misc. help file corrections and maintenance. (Terryn)\n\nROBOTIC\n\n+ New built-in label: PLAYERDIED (and the subroutine equivalent\n  #PLAYERDIED). When the built-in player dies, this label is\n  sent on the board the player died on. Because player death\n  occurs at the end of the cycle, this is received during the\n  following cycle.\n+ New counter: KEY_PRESSEDn, to read internal keycode statuses.\n  This is like KEYn, but KEY_PRESSEDn accepts internal keycodes\n  instead of PC XT keycodes. KEY_PRESSEDn will return 1 if the\n  key is currently pressed, or 0 if it is not pressed.\n+ New counter: DATE_WEEKDAY, which returns the number of the\n  current day of the week. Values are 0=Sunday, 1=Monday,\n  2=Tuesday, 3=Wednesday, 4=Thursday, 5=Friday, 6=Saturday.\n+ New counters: VIEWPORT_X, VIEWPORT_Y, VIEWPORT_WIDTH, and\n  VIEWPORT_HEIGHT to read the current board's viewport settings.\n  (Sparkette)\n+ New counter: SPRn_OFFONEXIT. When enabled for a sprite, that\n  sprite will be automatically turned off when exiting the board\n  (same as setting SPRn_OFF). This occurs even if the new board\n  and the previous board are the same. (Sparkette)\n+ Fixed crash bugs caused by using CHANGE SFX # to \"\" with an\n  invalid sound effect number.\n+ Fixed loop detection for Ogg Vorbis files when MOD_LOOPSTART\n  is set to a value greater than MOD_LOOPEND.\n+ Fixed a crash bug when attempting to save MZMs over 4MB to an\n  existing string.\n+ Fixed a buffer overflow crash that could occur when using LOAD\n  CHAR SET with both a large input file and a large offset.\n+ Reading from and writing to extended character sets should no\n  longer display an \"advanced graphical features\" error message\n  when a renderer without layer rendering support is active.\n  This will, however, display a (new) error message specific to\n  the Nintendo DS port.\n+ Fixed CLIP INPUT and IF FIRST STRING \"\" \"\" bug where INPUTSIZE\n  would be used as the bound on the input string without also\n  checking for null chars, potentially allowing heap corruption.\n+ Fixed a hack where the TELEPORT command would temporarily\n  store the current board to extra memory, causing the game\n  update loop to potentially cause memory corruption with stale\n  board pointers on the NDS.\n+ Fixed string wildcard matching bug that could cause patterns\n  ending with ? to sometimes fail.\n+ Dividing the value -2147483648 by -1, either by the commands\n  DIVIDE and MODULO or through expression operators / and %, no\n  longer crashes MegaZeux.\n+ When the right operand to a bitshift operator in an expression\n  is less than 0 or greater than 31, << and >> will return 0,\n  and >>> will return 0 if the left operand is positive or -1 if\n  the left operand is negative. The old behavior of the bitshift\n  operators for these shift values was architecture dependent.\n+ The date and time for the Robotic date and time counters is\n  now sampled once per command maximum. When multiple date/time\n  counters are read in the same command, they are guaranteed to\n  represent the same time.\n+ The Robotic message box commands now properly display char 10.\n+ The Robotic command ? now correctly accounts for color codes\n  in its length bounding.\n+ Fixed crashes in the & Robotic command caused by bad color\n  string length calculations.\n+ Fixed PrintScreen/SysRq and Menu/\"Right Click\" keycodes.\n+ Unsupported keys no longer return a KEY_PRESSED value of 1\n  for SDL 2 builds.\n+ Fixed crashes when executing the GIVE, TAKE, TAKE ELSE, and\n  TRADE commands with an invalid item type.\n+ Fixed crashes when disassembling a Robotic command with an\n  invalid condition.\n\nEDITOR\n\n+ Added robot editor undo (Ctrl+Z) and redo (Ctrl+Y). Like with\n  board/vlayer/charset editing, the size of the undo stack is\n  defined by the config setting \"undo_history_size\". Note that\n  extended macro expansion can sometimes clobber undo or redo\n  frames (this is not easily fixable, and prevents worse bugs).\n+ When loading a layer MZM to the board in the editor, instead\n  of always loading the MZM as CustomBlocks the conversion type\n  can now be selected from CustomBlock, CustomFloor, and Text.\n  Canceling the object type dialog no longer cancels the current\n  block or MZM placement.\n+ Reimplemented ANSi import/export support for the editor. ANSi\n  and TXT files can be imported in the editor with Alt+I and can\n  be exported by selecting a block (Alt+B).\n+ Creating a new world using N from the title screen or Alt+R\n  in the editor now clears the extended character sets.\n+ Improved the performance of Robotic debugger watchpoints,\n  especially for non-built-in counters and non-spliced strings.\n+ Robotic debugger watchpoints can now watch for particular\n  counter or string values. Leaving the value field blank will\n  still check for any value.\n+ Added a list of variables related to RAM usage to the counter\n  debug window. This list can be found in the \"World\" list.\n+ Added vlayer saved positions to the editor. Like regular saved\n  positions, these can be configured with the config file option\n  \"vlayer_position#\", and they work identically to board saved\n  positions.\n+ Fixed bugs caused by missing validation in the Import SFX\n  feature in the editor.\n+ Fixed a bug where multichar char tile movement could still\n  sometimes jump to the -/+/= chars in the main charset.\n+ Fixed another bug that would cause string searching in the\n  robot editor to sometimes skip matches.\n+ Replace all (Ctrl+H) in the robot editor should no longer skip\n  adjacent instances of the search string.\n+ Opening the block action menu in the robot editor (either with\n  Alt+B or Alt+Enter) now updates the current line, fixing bugs\n  where it could copy old line contents to the buffer.\n+ Fixed a robot editor crash that could occur when combining two\n  lines with backspace or delete if one of the lines contained\n  the start or end block mark.\n+ Fixed a bug in the robot editor where Ctrl+Home and Ctrl+End\n  would only set the current line (and not the current column).\n+ Fixed a robot editor extended macro crash that would occur:\n    when pasting a line containing a macro from the clipboard;\n    when importing a Robotic source file that invokes macros;\n    when invoking nested extended macros.\n+ Invoking an extended macro with enter/return no longer inserts\n  an extra blank line.\n+ Pressing enter at the start of a line with an extended macro\n  now invokes the macro instead of just inserting a line.\n+ Fixed editor extra memory crashes when importing and deleting\n  boards.\n+ editor_show_thing_toggles is now enabled by default.\n- board_editor_hide_help and robot_editor_hide_help are no\n  longer enabled by default for accessibility.\n\nVIDEO/AUDIO\n\n+ Added the \"glslscale\" renderer. This is the same as the GLSL\n  renderer, but uses software rendering with scaling shaders\n  instead of hardware rendering. This is faster on low end PCs\n  and embedded devices and is the new default renderer selected\n  by auto_glsl. (Hardware rendered GLSL is still available for\n  users with graphics cards by selecting the \"glsl\" renderer.)\n+ The software fallback renderer functions now support SMZX mode\n  3 indices, fixing graphical bugs in situations where the\n  fallback would be used and display the wrong colors in games\n  using custom indices.\n+ Added an \"auto\" mode for the force_bpp config option. Modern\n  SDL automatically selects a native window pixel format and a\n  fixed value can make the software renderer slower. Detecting\n  the BPP from the created window is more in line with reality.\n+ Fixed color inaccuracies in the softscale renderer on Mac OS\n  caused by using full-swing YUV values instead of the expected\n  studio-swing values for GL_YCBCR_422_APPLE textures.\n+ Simplified GLSL renderer texture data packing to improve\n  support for OpenGL ES and older OpenGL implementations.\n+ The GLSL tilemap fragment shaders will now use highp floats if\n  provided by the OpenGL ES driver. If highp is unavailable and\n  mediump has inadequate precision, MZX will print a warning.\n+ Fixed software renderer display issues caused by relying on\n  SDL_PixelFormat::BitsPerPixel instead of BytesPerPixel on\n  platforms with native 15 BPP display modes.\n+ Added disable_screensaver configuration option. MegaZeux now\n  leaves the screensaver enabled by default (fixes regression\n  caused by SDL 2.0.2+).\n+ Added an \"sdl_render_driver\" config setting. This setting can\n  be used to specify the underlying SDL renderer driver used by\n  the softscale renderer. This setting does not affect any other\n  renderers.\n+ libxmp playback improvements for GDM, AMF, and OctaMED modules\n  as well as general stability improvements.\n+ Updated libxmp to 4.6.0+ceb2d025.\n+ Fixed crashes in the RAD player that could be caused by\n  references to uninitialized instruments.\n+ Applied various Opal fixes from OpenMPT.\n- Removed GL4ES from the GLSL blacklist.\n\nPORTABILITY\n\n+ Added an experimental PlayStation Vita port. (Spectere)\n+ Added an experimental Wii U port. (asie)\n+ Added an experimental Dreamcast port. (asie, Lachesis)\n+ Added an experimental MS-DOS 32-bit port based on DJGPP.\n  (Mr_Alert, asie, Lachesis)\n+ Added support for BlocksDS to the NDS port. (asie)\n+ Added support for 800x240 mode to the 3DS port. This mode can\n  be toggled with the 3DS keyboard. (asie)\n+ Added support for using 352kB of previously unused NDS VRAM\n  as board storage. This area can be used for board storage even\n  when a slot 2 expansion cartridge is not present. (asie)\n+ Reduced RAM usage by 50k by removing variants of *printf and\n  *scanf with floating point support on the NDS. (asie)\n+ The MOD_ORDER counter is now readable on the NDS. (asie)\n+ save_slots is now enabled by default for consoles. (Spectere)\n+ Inactive boards and some robots are now compressed on the NDS,\n  which (combined with asie's VRAM patch) should vastly increase\n  the number of games that can be played on the original DS and\n  DS Lite without a slot 2 expansion cartridge. This is also\n  enabled for the PSP and DOS ports.\n+ The NDS, PSP, and DOS ports now write zip archives with the\n  fastest zlib compression level. This makes saved files that\n  use compression (worlds, saves, etc.) slightly larger.\n+ Added virtual filesystem support to MegaZeux. Currently, this\n  is only used for caching files, and is only enabled by default\n  on platforms with slow or buggy file IO (3DS, Vita). This\n  feature may be optionally configured with the config options\n  vfs_enable, vfs_enable_auto_cache, vfs_max_cache_size, and\n  vfs_max_cache_file_size.\n+ Fixed a bug where the 3DS and Switch ports would display .. in\n  the file manager when selecting a board module, board charset,\n  or board palette.\n+ Fixed a bug where, when a faulty dirent implementation returns\n  .. when listing the contents of a root directory, .. would be\n  displayed despite being meaningless.\n+ Fixed a bug where some checks in the file manager could fail\n  when getcwd returns different slashes than expected.\n+ Fixed a bug where stdio redirect could generate corrupted log\n  files on the Nintendo DS due to poor freopen support.\n+ Enabled the GLSL renderer for the Android port. The Android\n  port now uses the GLSL scaled software renderer by default.\n- Removed 3DS CIA support. (asie)\n\nUTILITIES\n\n+ ccv and png2smzx now both support the following image formats:\n  PNG (libpng builds only), GIF, BMP (uncompressed, RLE8, RLE4),\n  NetPBM (PBM, PGM, PPM, PNM, PAM), farbfeld. png2smzx will now\n  build when libpng support is disabled, though it won't support\n  loading PNGs with this configuration.\n+ ccv and png2smzx now both support streaming image data from\n  stdin using the input filename \"-\".\n+ Added y4m2smzx, a video converter frontend for the same image\n  converter that png2smzx uses. This is a rewrite of Mr_Alert's\n  original frontend from 2010. This uses a custom y4m loader\n  rather than MJPEG Tools for now, so it may be buggy. Usage:\n  ffmpeg -i input -f yuv4mpegpipe pipe:1 | y4m2smzx - out.mzv\n+ The downver utility now supports save files.\n+ Fixed a bug where checkres would not check the current dir\n  for files when provided with a MZX filename with no path\n  component.\n+ checkres now attempts to scan every board in <=2.84 worlds\n  where a corrupt board/robot is found. Due to old world format\n  limitations, robots after a corrupt robot can not be scanned.\n+ checkres now supports scanning protected worlds.\n+ Added experimental checkres support for MZX 1.x worlds.\n\nDEVELOPERS\n\n+ Added --enable-extram config.sh flag to enable extra memory\n  hacks on arbitrary platforms. This is enabled by default in\n  CONFIG.NDS, CONFIG.PSP, and CONFIG.DJGPP. When enabled, non-\n  active boards are compressed in RAM. Certain platforms can use\n  platform-specific storage, like the NDS.\n+ Board input strings, charset paths, and palette paths are now\n  heap allocated on-demand to save RAM for low-memory systems.\n+ Status counters are now saved as a nested properties file.\n+ Refactored the 3DS renderer to use templates. (asie)\n+ intake2() now supports custom handling of intake events, i.e.\n  it can now be used without providing a fixed size buffer.\n+ The draw and click handlers have been removed from intake2().\n  This should provide more flexibility to the parent context for\n  displaying the string that is being edited.\n+ Added \"mzxout\" and \"mzxerr\" streams for printing console\n  messages. In most places these should be used instead of\n  \"stdout\" and \"stderr\" to allow debug and warning messages\n  print to the correct log files when stdio redirect is enabled.\n+ Fixed broken MIPS big endian detection inherited from SDL 1.2.\n+ Added architecture width detection for RISC-V RV32 and MIPS64.\n+ Replaced the macro-based mixers in sampled_stream.c with more\n  maintainable template-based mixers.\n+ Fixed the DSO -Wstrict-aliasing warnings in Socket.cpp and the\n  OpenGL renderers for GCC versions using -Wstrict-aliasing=2.\n+ Added --host config.sh option to manually specify a cross\n  compiler prefix. This is ignored or overriden by most ports\n  and is mainly just for Linux.\n+ Relicensed ccv from GPL 3 to GPL 2+ to match the rest of MZX.\n  (Lancer-X)\n+ If available, GetSystemTimePreciseAsFileTime and clock_gettime\n  are now used to calculate the system clock time.\n+ If available, SDL_GetTicks64 is now used by get_ticks().\n+ Minor performance improvements for run_robot, is_string, and\n  find_function_counter.\n+ _FILE_OFFSET_BITS=64 is now used for 64-bit fseeko, ftello,\n  readdir, and stat/fstat support for 32-bit Linux builds.\n+ Updated Android SDL to 2.28.2. (asie)\n+ Updated Android NDK to r23c. (asie)\n+ Fixed Android build system handling of missing libraries.\n+ Improved fileform.html, joystick.html, keycodes.html, and\n  platform_matrix.html readability on small/mobile displays.\n- String values are now allocated separately from the string\n  struct and name. This may make strings very slightly slower,\n  but means string pointers are now stable through an entire\n  gameplay session.\n\n\nNovember 22nd, 2020 - MZX 2.92f\n\nHere's another bugfix release. Highlights of this release are\ncrash fixes (including the 3DS OGG crash bugs), numerous SAM/WAV\nplayback bugfixes, some edge case compatibility fixes, world\ndecryption improvements, networking improvements (including\nexperimental Wii, 3DS, and Switch support), bugfixes for the\nfile manager in Nintendo Switch builds, and too many libxmp\nfixes/improvements to list. Since libxmp development is active\nagain, the number of MegaZeux hack patches is down to just two!\nThe editor is now enabled for 3DS builds, and some rudimentary\njoystick support has been added to the editor so it will (maybe)\nbe (almost) usable.\n\nOf special note are asie's improvements to the NDS port. New\nfeatures include protected character set and palette support,\nimproved touchscreen focus, reduced memory consumption, and\n*audio support*. Currently NDS audio requires preconversion of\nmusic files to .MAS and sample files to .SAM, but that it works\nat all is amazing by itself. Pre-converted .MAS files are\nincluded with the packed-in copy of Caverns of Zeux.\n\nUSERS\n\n+ Added audio support to the NDS port. PC speaker effects and\n  .SAM files will work out of the box, but .MOD/.S3M/.XM/.IT\n  files require preconversion to .MAS using mmutil. All other\n  audio formats are unsupported. (asie)\n  Example:\n\n    mmutil -d -m CV_TECH.MOD\n\n+ Added protected character set and palette support to the NDS\n  port and reduced overall RAM usage for the NDS port. (asie)\n+ Fixed a bug in the NDS port where MZX would immediately focus\n  the player after a touchscreen press event, preventing the\n  touchscreen from being used to scroll the upper screen. (asie)\n+ Fixed a crash that could occur sometimes when duplicating\n  robots on a board with Reset Board on Entry enabled.\n+ Added SET RANDOM, INC RANDOM, DEC RANDOM, and TAKE \"label\"\n  commands to the list of commands that should allow \"infinite\"\n  loops in pre-2.80 versions.\n+ Fixed SET RANDOM # TO # for large ranges on certain platforms\n  (example: Linux) confused by the 32-bit math used to calculate\n  the random range.\n+ Fixed crashes that could occur when attempting to run MegaZeux\n  without help.fil.\n+ Fixed graphics corruption when using the glsl and opengl2\n  renderers on big endian platforms.\n+ Fixed a bug that would cause string searches to sometimes skip\n  certain matches.\n+ Improved performance of saving ZIP worlds/save files/etc. for\n  some platforms.\n+ Platforms without a protected palette and the meter enabled\n  should now fade out properly before loading a world.\n+ Rewrote decrypt() to work better on low-memory platforms.\n+ Improved GLSL layer renderer performance slightly by sending\n  the palette and indices to the GPU fewer times per frame.\n+ Fixed a bug where the GLSL renderer could attempt to load the\n  framebuffer symbols from a driver that doesn't support them\n  when resizing the window.\n+ Fixed a bug where checkres wouldn't correctly detect\n  \"LOAD_ROBOT#\" when used to load to a specific robot.\n+ Fixed a memory leak when printing network error messages in\n  Windows builds.\n+ Fixed a potential crash bug in the netcode where socket IDs\n  could be reused after close() when attempting to connect.\n+ Fixed a crash bug affecting the compatibility implementation\n  of getaddrinfo() that would occur if NULL was returned by\n  gethostbyname() during DNS resolution.\n+ Fixed a potential crash in the compatibility implementation of\n  getaddrinfo() caused by gethostbyname() being thread-unsafe.\n+ Fixed a bug where the updater would hang for up to 10 seconds\n  due to connections timing out during the confirmation UI.\n+ Fixed a bug where the local manifest.txt would be overwritten\n  by the remote manifest, potentially causing bugs. The local\n  manifest will now be replaced only after a successful update.\n+ Fixed bugs where the HTTP layer would filter header names,\n  content/transfer coding values, and content type values too\n  strictly.\n+ Fixed a bug where INPUT STRING would display newlines from\n  an interpolated string.\n+ MZX now validates the world version of encrypted worlds before\n  offering to decrypt them. This should reduce the chance of\n  accidentally \"decrypting\" a corrupted file and prevents MZX\n  from attempting to decrypt MZX 1.x files (which store the\n  password differently).\n+ Added config settings \"editor_show_thing_blink_speed\" and\n  \"editor_show_thing_toggles\". The former controls the blinking\n  speed of the show thing (Shift+F2 et al.) editor keys and the\n  latter allows these keys to be treated as a toggle instead of\n  blocking input.\n+ Added SOCKS5 IPv6 and username/password support.\n+ Added \"network_address_family\" config setting to allow users\n  to force either IPv4 or IPv6 connections/name resolution. By\n  default, MZX will now request either or both depending on the\n  system IP address configuration (previously, it was hardcoded\n  to only allow IPv4 despite IPv6 support being implemented).\n+ The config setting \"updater_enabled\" has been added to turn\n  off the updater system entirely without disabling networking.\n+ The updater now allows update checks to be performed even when\n  a full update wouldn't be possible, and should be more helpful\n  about printing warnings to the console when this situation\n  occurs.\n+ Enabled rewinddir hack and .. file manager hacks for Nintendo\n  Switch builds to fix the file manager and directory seeking.\n+ Fixed a bug where importing a board over the title board would\n  not update the world title.\n+ Fixed a crash that could be caused by selecting a block on the\n  overlay/vlayer, changing boards, then pressing enter.\n+ Fixed a bug where the currently playing PC speaker note would\n  play for the rest of its duration after turning off PC speaker\n  sound effects, after \"end play\", after exiting gameplay, etc.\n+ Fixed a regression where turning off music in the settings\n  menu and then turning music back on would not start playing\n  the music file for the current board.\n+ Fixed multiple libvorbis and tremor crashes related to the\n  3DS platform_mutex_lock implementation not blocking.\n+ Fixed audio frequency bugs in .SAM file playback.\n+ Certain .WAV subtypes handled by SDL_LoadWav should now load\n  properly on big endian machines.\n+ Signed 16-bit samples should now work correctly with MOD SAM\n  on big endian machines.\n+ Fixed the white border present when using the GLSL renderer in\n  HTML5 builds.\n+ Added joystick input handlers for most editor interfaces.\n  Only very basic things are supported like navigating boards\n  and robots. This is intended for things like handheld consoles\n  without a keyboard.\n+ Fixed a bug where turning off the listening mod would load the\n  mod that was playing when the listening mod was loaded instead\n  of the current board's mod.\n+ Fixed a bug where setting a board mod to a partiular mod, then\n  setting the board mod to *, then changing to a different board\n  with the same mod would not restart the board mod.\n+ Fixed a bug where libxmp would not ignore zero volume samples\n  in STM files.\n+ Restored libxmp support for Soundtracker 2.6/IceTracker .MODs.\n  Currently only libxmp supports this .MOD variant.\n+ Fixed a bug where MZX would fail to load Noisetracker,\n  Octalyser, and Mod's Grave .MOD files if they were named using\n  the extensions .NST, .OCT, and .WOW (respectively). These can\n  now be selected with Alt+N/Ctrl+N in the editor.\n+ Enabled loading .XM and .AMF files with MikMod. Whatever the\n  issue was with these in 2007 seems to have since been fixed.\n+ Fixed loading Soundtracker 15-instrument .MODs with MikMod.\n+ Fixed MOD_POSITION and MOD_LENGTH counters for MikMod.\n+ MikMod now uses interpolation if module_resample_mode is set.\n- Disabled playing modules using the SAM command with MikMod to\n  avoid crashes due to MikMod using a global player state.\n\nDEVELOPERS\n\n+ The test worlds now run correctly when AddressSanitizer is\n  enabled.\n+ The SUPPRESS_BUILD Makefile flag has been split into several\n  flags, allowing more granularity for disabling default rules.\n  The Android and Darwin \"dist\" meta targets use these flags to\n  to disable the default \"all\" target, so \"make\" can now be used\n  in place of \"make dist\" for these platforms.\n+ Using \"make build\" will now remove the build/${SUBPLATFORM}\n  directory if it exists before preparing a build. Before, using\n  \"make build\" when the platform build directory already exists\n  would not copy any updated binaries/assets/etc., potentially\n  causing confusion.\n+ The check_alloc series of functions should now be thread safe.\n  The main thread will display a fatal error as usual when\n  allocation fails; others threads will print a warning and\n  return NULL.\n+ Added implementations of new/delete/__cxa_pure_virtual that\n  can be linked to avoid linking libstdc++/libc++ on platforms\n  and configurations that otherwise don't need those libraries.\n+ Refactored the netcode to use C++ instead of/in addition to C,\n  allowing some cleanup that otherwise wouldn't have worked.\n  Host::bind, Host::listen, Host::accept, and Host::poll are no\n  longer disabled. Other network deadcode functions have been\n  updated but are still disabled.\n+ Fixed FD_ISSET in the Windows netcode.\n+ Added a EAI_AGAIN define for the getaddrinfo() fallback.\n+ Fixed leak of updates.txt file pointers in the updater that\n  could occur when updates.txt fails to download. The updater\n  now downloads updates.txt to a buffer instead of to disk.\n+ Added graphics.renderer.hardware_cursor for platforms with\n  hardware cursors that may need to be updated during any frame\n  regardless of the current cursor state/blinking (i.e. DJGPP).\n+ Fixed \"make build\" error after using \"make debuglink\" with png\n  support disabled.\n+ Added const and restrict to the software render_graph and\n  render_layer implementations for a small performance boost.\n+ Added --disable-getaddrinfo and --disable-ipv6 config.sh flags\n  to allow disabling these for old platforms and consoles SDKs.\n  Amiga, Wii, and PSP network builds force-disable these. Switch\n  and 3DS builds currently force-disable IPv6.\n+ Added experimental network support for Wii, Switch, and 3DS.\n+ Updated libxmp to git-ab70ec9f, refactored libxmp patches.\n+ Removed unused libxmp Paula simulator code to save RAM. (asie)\n+ SDL is no longer required to build MZX with MikMod enabled.\n+ Fixed MikMod static linking on Windows.\n+ Added --disable-stack-protector config.sh option. Platforms\n  that previously disabled the stack protector in the Makefile\n  now force disable this config option. These force disable\n  hacks will probably be removed in the near future.\n\n\nJuly 20th, 2020 - MZX 2.92e\n\nThis bugfix release includes miscellaneous audio fixes and fixes\nfor bugs in the configuration system. Other things of note are a\nfix for a graphical bug in the robot editor, a freeze bugfix in\nthe updater when updating from older MZX releases, and the title\nof the Alt+A (\"Select Char Set\") dialog has been reverted back\nto roughly what it was in DOS versions. A second default SMZX\ncharacter set with a different font has been added as well.\n\nUSERS\n\n+ Fixed Windows file handle leaks in audio_xmp.c caused by\n  MinGW's non-compliant fdopen.\n+ Fixed a bug where the title screen's intro message wouldn't\n  cycle the scroll color while no world was loaded.\n+ Added the missing \"ccode_chars\" config setting.\n+ Fixed a bug where the \"ccode_extras\" config setting would\n  change the strings color code instead.\n+ Fixed a bug where \"macro_#\" where # is 0 or between 6-9 would\n  corrupt various parts of the editor configuration. Notably,\n  \"macro_6\" would corrupt the extended macros and cause crashes.\n+ Fixed division-by-zero crashes and GL errors when using\n  invalid fullscreen_resolution or window_resolution values.\n+ Fixed a bug where files included by the config file include\n  directive would ignore editor settings. Also added a recursion\n  limit for includes and fixed a bug where the \"include=file\"\n  format would not work on some platforms.\n+ Fixed a libxmp bug where pattern jump/break could take effect\n  after using JUMP MOD ORDER or setting MOD_ORDER/MOD_POSITION.\n  This fixes a bug at the end of the Inmaportal scene in Cans 3.\n+ Zip files with the language encoding flag set no longer print\n  unsupported flag errors.\n+ Fixed a bug where recursive directory deletion in the updater\n  could get stuck in an infinite loop due to not checking the\n  return value of rmdir and path_get_directory not behaving the\n  same as get_path used to.\n+ Fixed a bug where the cursor could display over the color\n  selector and char editor when opened from the robot editor.\n+ Fixed a bug where the default character set selection menu was\n  inconsistently labeled \"Object type\".\n+ Added a second SMZX default character set with a more legible\n  font. The original is still available for those who prefer it.\n\nDEVELOPERS\n\n+ Fixed memcasecmp test alignment checks for the Motorola 68000.\n+ Improved validation of config setting inputs and added a unit\n  test for config file and command line parsing.\n+ Added buffered zip compression/decompression support.\n+ Added unit tests for the zip functions and (de)compressors.\n\n\nMay 8th, 2020 - MZX 2.92d\n\nThis release includes minor new features, an overhaul to the way\nMZX handles text input from SDL 2.0 which should fix various\ntext bugs (particularly with non-US keyboards), fixes required\nto get the Android port working, and a new file format document.\nOther things of note are two libxmp patches fixing issues with\nS3Ms saved by the original Modplug Tracker and with GDMs relying\non fine effect continue, checkres updates, and misc. fixes for\nthe Windows and HTML5 ports.\n\nFixed compatibility issues in this release are a string compare\nbug that prevented Mines of Madness from working and MODs with\nextended filenames working when referenced by their truncated\nDOS SFN in the MZX file. The latter fix only works when there is\na single unambiguous match, which turned out to be essentially\nevery affected game.\n\nIt is worth noting that the Android port is EXPERIMENTAL and is\nlikely to have compatibility issues on many devices. A USB OTG\nor Bluetooth keyboard or game controller is required at minimum.\nMany devices have key press issues that are likely caused by\ndrivers or SDL. SDL text input events are disabled as they cause\neven more keyboard bugs, and as a result non-US keyboard layouts\nprobably won't work with this port right now. Other problems\nwith this port that have been reported include graphical bugs\nand crashes when focusing/unfocusing MegaZeux.\n\nUSERS\n\n+ Added robot editor shortcuts CTRL+Home and CTRL+End to jump to\n  the start and the end of the current program.\n+ Char selector tile movement is now disabled outside of the\n  char editor and for 1x1 char selections. This allows typing\n  -/_/=/+ in other char selectors to jump to those characters.\n+ Added docs/fileform.html, a document describing all currently\n  supported MZX file formats in excessive detail.\n+ When startup_file is provided with both a directory and a\n  filename and no startup_path has been set, the directory will\n  now be used as startup_path. If startup_path is set, the\n  directory portion of startup_file will still be ignored.\n+ The flash thing editor functions (Show Robots, etc.) now use\n  the protected charset and will display with the protected\n  palette in SMZX mode and in regular mode situations where the\n  normal flashing would be difficult/impossible to see.\n+ Fixed a bug where input text with no corresponding internal\n  key would be ignored by the robot editor.\n+ Fixed a bug where some AltGr key combinations would not work\n  in the robot editor (note: SDL doesn't distinguish Alt from\n  AltGr, so any Alt key combos with a special meaning in the\n  robot editor will still be intercepted by MZX).\n+ Windows Alt+numpad sequences corresponding to ASCII characters\n  32-126 should now be recognized.\n+ Fixed an out-of-memory error in the save slot UI and improved\n  its mouse functionality.\n+ Added optional CRC-32 output to checkres. Currently this flag\n  only works for files in ZIP archives and no CRC-32 validation\n  is performed.\n+ Fixed checkres parsing for LOAD CHAR SET \"+&+var&file.chr\".\n+ Fixed checkres crash that could occur on some platforms when\n  attempting to checkres a directory.\n+ Fixed wrong robot board positions reported by checkres for\n  some files when run with -vv.\n+ Repurposed the old checkres -A flag to display everything but\n  unused files, which should be much more useful than the older\n  behavior (particularly with loose files in directory dumps).\n+ Fixed memory leaks in legacy_assemble_line.\n+ Fixed potential null termination issues in both world loaders\n  that could be triggered with invalid board name, robot name,\n  scroll, sensor, sfx, and status counter strings. Also improved\n  loaded robot stack bounds checks.\n+ Decreased the sizes of the counter and string structs and\n  fixed a crash that would occur when attempting to expand the\n  counters or strings lists beyond 1 billion. Checks have been\n  added to prevent them from expanding beyond 2^31 (please don't\n  actually use this many counters/strings).\n+ Fixed string comparison for pre-2.81 games that relied on\n  null-terminated string compares. This fixes Mines of Madness.\n+ Fixed hlp2html bug where certain chars wouldn't be escaped and\n  decreased the size of the output HTML files somewhat.\n+ Fixed libxmp playback for S3Ms containing ADPCM4 samples. This\n  fixes mm2flash.s3m in ZeuxDrop.\n+ Fixed libxmp playback for GDMs where 100/200/A00 should\n  continue the fine portamento and fine volume slide commands.\n  This fixes LB2_7.GDM from Kikan.\n+ fsafetranslate now attempts to detect matches for truncated\n  DOS SFNs on non-Windows platforms. If an unambiguous match for\n  a truncated SFN is found, the SFN will be expanded to the\n  match. This fixes numerous games relying on truncated SFNs\n  with unambiguous matches.\n+ Reimplemented COPY # # # # for layer-to-layer copies.\n+ Fixed a collision bug affecting unbound sprites with ccheck 3\n  set in SMZX mode where wrong colors could be treated as the\n  transparent color for the purposes of collision.\n+ Fixed softscale bug on some platforms where the screen border\n  wouldn't be cleared.\n+ The GLSL renderer will now attempt to use a framebuffer when\n  available. This fixes GL_INVALID_OPERATION bugs with certain\n  OpenGL ES 2.0 implementations.\n+ Directory navigation in the file manager now checks directory\n  access permissions before allowing navigation and displays an\n  error when navigation fails.\n+ Fixed windowing code bug in extended macro editor that would\n  occur after selecting \"Default\" multiple times.\n+ Fixed a bug where long extended macros could sometimes break.\n+ Fixed extended macros not working when the params line is\n  indented by more than one space or tab.\n+ Windows builds now attempt to load and call SetProcessDPIAware\n  on startup. This should fix undesirable DPI-related scaling\n  and fullscreen bugs.\n+ Fixed Emscripten crashes on startup when built with Emscripten\n  1.39.5 and up caused by usage of deprecated canvas behavior.\n+ Fixed a COPY BLOCK $string bug where the buffer could be\n  allocated at the empty destination string's value on some\n  platforms and a faulty bounds check would result in the string\n  being expanded but otherwise blank.\n\nDEVELOPERS\n\n+ Added a set of C++-based unit tests for portions of MZX that\n  can't be reached/tested easily by MZX worlds. These tests will\n  be built and run when \"make test\" is used prior to running the\n  test worlds.\n+ Decoupled SDL 2 text input handling from regular key handling\n  and added a text input buffer. Text keys can now be requested\n  multiple times and either as ASCII or unicode; the next key in\n  the buffer is returned on subsequent calls. intake and editor\n  text entry can now accept multiple text chars per frame.\n+ Added SDL 1.2 implementation for __peek_exit_input.\n+ Contributed sound engines (libxmp, libmodplug) should now\n  properly rebuild after config.sh is run.\n+ Fixed a bug where khash tables couldn't expand beyond 2^31,\n  added hash caching, made the MZX copy of khash use MZX style,\n  and better documented the changes made to it.\n+ Added fast sprintf replacements for Robotic counter to string\n  conversion (counter interpolation, string/counter compares).\n+ Fixed alignment issues in memcasecmp.\n+ Fixed alignment issues in get_cursor_color.\n+ Added a config.sh flag for UndefinedBehaviorSanitizer.\n+ Categorized config.sh flags list.\n+ Added a trace logging level that is disabled by default and\n  changed most dns.c, fsafeopen.c, and checkres.c debug logging\n  to use it. Use the --enable-trace config.sh flag to enable\n  trace logging for debug builds.\n+ Added a config.sh flag to build using GCC's -fanalyzer flag\n  (disabled by default).\n+ Refactored path functions out of util.c.\n+ Replaced the Android build scripts with an improved Makefile\n  system. The Android Makefile now uses the NDK toolchains\n  directly. Also added support for APK signing and fixed several\n  Android port bugs.\n+ MZX will now exit on startup if no world could be loaded and\n  the SDL dummy video driver is active.\n- Removed -fpermissive from CXXFLAGS (pointless).\n- Removed -fbounds-check from debug builds (pointless; MegaZeux\n  doesn't use GCC to build any Fortran or Java code).\n\n\nMarch 8th, 2020 - MZX 2.92c\n\nHere's another bugfix release, this time mostly oriented towards\nfixing bugs in the Emscripten/HTML5 port and checkres. Other\nfixes of note are the module_resample_mode (formerly 'modplug')\nconfig setting has been fixed, numerous optimizations have been\nmade to the software layer renderer, and yet another ternary\noperator bug has been fixed.\n\nFinally, Spectere has added an experimental save slots UI that\ncan be enabled with the config setting \"save_slots\". This\nfeature is intended primarily for consoles and will likely be\nenabled by default for consoles in the future.\n\nUSERS\n\n+ Added an optional save slots interface to complement the file\n  manager. This can be enabled by setting the \"save_slots\"\n  config setting to 1, and is disabled by default. (Spectere)\n+ Board list typing now allows non-alphanumeric keys like spaces\n  and periods. Typing a space first will match zero or more\n  prefixed spaces while seeking.\n+ Added the config setting \"auto_decrypt_worlds\". When set to 1,\n  this allows MegaZeux to automatically attempt to decrypt\n  protected worlds. For most platforms this option is disabled\n  by default.\n+ MegaZeux now silently ignores robots in older worlds with\n  corrupt programs that are marked unused. This fixes an error\n  message that would display when loading Wes.\n+ Renamed \"modplug_resample_mode\" to \"module_resample_mode\".\n  Fixed a bug where the xmp interpolation mode wouldn't be set\n  correctly from this setting and instead would always reset to\n  \"linear\". The default for this setting in config.txt has been\n  corrected (the default has been \"cubic\" since 2.82b).\n+ Fixed a ternary operator bug where the expression could break\n  with certain counter names in the second and third terms.\n+ Added a comment to the Emscripten frontend for how to disable\n  the \"Click to Play\" splash.\n+ Fixed Emscripten frontend issues when MegaZeux is embedded in\n  an iframe making it difficult to regain focus after losing it.\n+ Fixed an Emscripten frontend bug where certain filenames could\n  cause false positives when checking for a directory name,\n  preventing fsafetranslate from detecting the correct path.\n  This fixes various things in Eternal Eclipse Taoyarin.\n+ The Emscripten frontend should now fall back to memory storage\n  if IndexedDB or local storage fails to initialize. This fixes\n  an error when using MZX in a private browser window.\n+ Fixed a robot editor regression where certain numpad symbols\n  could not be typed.\n+ Added \"GL4ES wrapper\" to the auto_glsl blacklist.\n+ Added \"Software Rasterizer\" to the auto_glsl blacklist.\n+ checkres no longer requires zip archive filenames to end with\n  the .ZIP extension.\n+ Added optional CSV output to checkres.\n+ Added error to checkres for 1.xx worlds.\n+ Fix checkres errors when encountering VER1TO2 empty robots.\n+ ZIP support now ignores __MACOSX/, .DS_Store, and Thumbs.db.\n+ Added decompression support for various legacy ZIP compression\n  methods and deflate64.\n+ The Emscripten frontend now falls back to a wrapper for MZX's\n  internal ZIP support when UZIP fails to extract an archive.\n+ Fixed IndexedDB support for legacy Edge versions. (asie)\n+ Fixed an 8bpp software layer renderer bug where transparent UI\n  colors would not be transparent in SMZX mode.\n+ General software layer renderer (software, softscale, opengl1)\n  performance improvements.\n+ Fixed out-of-bounds CHAR_BYTE access with BYTE values over 14,\n  added compatibility check for games relying on this. (asie)\n+ Fixed faulty bounding on CHAR_X/CHAR_Y/PIXEL.\n- Removed the options \"overlay1\" and \"overlay2\" from the list of\n  selectable renderer options except when SDL 1.2 is enabled.\n\nDEVELOPERS\n\n+ Refactored render_layer_code to use C++ templates.\n+ render_layer now disables the (unused) 64-aligned renderers\n  for 32-bit platforms and Emscripten builds.\n+ Fixed buggy render_layer clipping that required clipping\n  renderers to unconditionally use a suboptimal alignment.\n+ Switched from xmp_load_module to xmp_load_module_from_file.\n+ Fixed various clang unused argument compiler warnings.\n+ Updated MSVC dirent.h implementation to latest version.\n+ Moved dir_* functions from util.c to dir.c.\n+ Added Windows UTF-8 compatibility layer to vfs.c and dir.c.\n  UTF-8 support for Windows is still limited.\n+ Moved Makefile compiler version checks to arch/compat.inc.\n+ Added cycles_and_commands.txt to build and install targets.\n\n\nSeptember 23rd, 2019 - MZX 2.92b\n\nThis release fixes several bugs, notably 3DS and Wii rendering\nissues and a bug where the wrong palette would be saved while\nCOLOR FADE OUT was active. Also in this release are performance\nand usability improvements for the NDS and 3DS ports and a new\nfrontend for HTML5/Emscripten builds (special thanks to asie).\n\nUSERS\n\n+ Fixed a bug where mouse warping both mouse coordinates would\n  not actually warp both coordinates.\n+ Fixed a port regression where save files would be saved with\n  the wrong color intensities while COLOR FADE OUT was active\n  (or during the first cycle of a board for <2.90 worlds). This\n  fixes fading bugs in Ruin Diver 3.\n+ Added compatibility for a bug where the robot message box\n  would disable COLOR FADE OUT in versions 2.83 through 2.90X.\n  This fixes several BKZX games.\n+ Fixed checkres bugs separately preventing detection of global\n  robots in both world formats.\n+ Fixed 3DS renderer bug where the UI wouldn't properly display\n  char foregrounds with extended graphics enabled.\n+ Fixed 3DS renderer bug where sprites containing chars with\n  transparent foregrounds wouldn't draw any char backgrounds.\n+ Fixed 3DS renderer crashes that would occur when large numbers\n  of sprites were active.\n+ Improved file manager directory read times for 3DS/NDS/Wii.\n+ File IO for world saving/loading is now buffered, which should\n  improve save/load times on the 3DS and Switch.\n+ Enabled writing zip data descriptors for the Switch.\n+ Added classic/stretch video ratio support for the 3DS and NDS\n  lower screens. (asie)\n+ \"classic\" is now the default ratio for the 3DS, NDS, and Wii.\n+ Fixed GX renderer bug where chars with both colors transparent\n  could cause various graphical bugs in normal MZX mode.\n+ Fixed bug where board exits could be cleared when deleting a\n  board or after loading a world containing null boards.\n+ Fixed bugs when playing STARDSTM.GDM with the libxmp engine.\n+ Fixed a crash that could occur when importing a world in the\n  editor containing a null board.\n+ Fixed a bug where null boards wouldn't be properly refactored\n  on the NDS while using a memory cart, causing entrances to\n  point to the wrong boards and possibly other problems.\n+ Fixed a bug that could corrupt the ends of robot programs when\n  using an NDS memory cart.\n+ Improved world loading times when using an NDS memory cart.\n+ Improved NDS memory cart transfer speed. (asie)\n+ Disabled NDS logging hacks when stdio redirect is enabled.\n+ Exiting MZX on the NDS should now power off the NDS.\n+ Added Emscripten frontend. (asie)\n+ Fixed bug where errors in the title screen Load World window\n  could clear the rest of the screen.\n+ Fixed crash that could occur when attempting to load an\n  invalid zip world.\n+ Non-recursive directory deletion in the file manager now tries\n  to delete the directory itself instead of its contents. Added\n  error messages for failed file and directory deletes.\n\nDEVELOPERS\n\n+ Added initial support for the dirent d_type field to help\n  avoid stat calls when reading directories.\n+ Replaced most fseek calls in the legacy world loader.\n+ Refactored move_current_board to be more readable.\n+ Corrected some of the editor's bad extram handling and added\n  warnings to debug builds to help debug it (no platforms at the\n  present support both the editor and extram).\n+ Replaced error wait_event() with update_event_status_delay().\n+ Added preliminary VFS code to replace function pointer casting\n  in zip.c.\n+ Fixed Wayland/X11 detection for setups that don't allow X or\n  Xorg to be run normally.\n+ Unix builds should now attempt to load the icon from the\n  installed path instead of from a hardcoded path. Builds with\n  no sharedir set will use \"contrib/icons/quantump.png\" instead.\n\n\nJuly 22nd, 2019 - MZX 2.92\n\nHere's another inexcusably big minor version release. First off,\nthere are two new ports (Switch and Emscripten) and the Android\nport has been updated to be actually usable. Second, joystick\nsupport has been significantly overhauled (and depending on the\ncontroller/joystick you plug in, it may work with no config\nsettings required!). Game joystick settings should no longer\noverride the new UI joystick behavior. Also, controllers can now\nbe used directly via Robotic. Read docs/joystick.html for more\ninformation on the new features.\n\nOther changes of note include RAD music support (v1 and v2),\npalette editor improvements, and some new counters. There's a\nnew SDL 2 exclusive renderer to replace overlay1/overlay2 called\n\"softscale\" that performs much better than either did and with\nfewer crashes. Also of note are the massive number of fixed bugs\nand compatibility improvements, fixing regressions and error\nmessages that affected around 20 different games (or more). A\nmajor bug was fixed that caused the updater to not initialize\nin Windows has been fixed too, so basically, update already.\n\nFEATURES\n\n+ Added numerous improvements to MZX's joystick handling:\n  + Joystick inputs can now be bound to generic actions. These\n    actions correspond to XInput buttons and can interact with\n    MegaZeux's various windows in a context-specific manner.\n    They can be used directly in Robotic with the new counter\n    \"joy#.[action]\" or can be bound to keycodes in the config\n    file to allow them to be used with older games. The NDS,\n    3DS, Wii, PSP, and GP2X pad.config files have been updated\n    to use actions. See the help file or config file for more\n    info on the config options and available actions.\n  + Added automatic joystick mapping for Windows/Mac/Linux/etc.\n    Joysticks recognized as SDL game controllers are assigned\n    joystick actions when detected. This behavior can be altered\n    or disabled via config options.\n  + New counter: \"joy#active\" will return 1 if a given joystick\n    is availabled, 0 if not, and -1 if the index is invalid.\n  + New counter: \"joy_simulate_keys\". When set to 1 (default),\n    joysticks can produce simulated key presses, as in older MZX\n    versions. When set to 0, this behavior is disabled.\n  + Joystick settings in game.cnf files are now loaded as game-\n    specific joystick bindings. These bindings will no longer\n    override global/UI bindings defined by the config file or\n    pad.config. The default bindings are restored when any world\n    is loaded from the title screen; they are not restored when\n    a save is loaded, between world swaps, or in the editor.\n  + Joystick config options can be assigned to key names in the\n    format of \"key_X\" (without quotes), where X is the key name.\n  + Joystick config options for a particular controller now work\n    with ranges of controllers e.g. \"joy[1,16].lshoulder = 13\".\n  + Added the config setting \"joy_axis_threshold\". This setting\n    allows the threshold for joystick axis mapped presses to be\n    configured.\n  + The NDS directional pad is now considered a joystick hat.\n  + The 3DS directional pad is now considered a joystick hat.\n    The circle pad is now mappable as axes 1 and 2.\n  + The Wii Classic Controller triggers are now recognized as\n    axes 7 and 8. The Guitar Hero axes have been shifted to 9,\n    10, and 11, and a likely bug with the whammy bar axis has\n    been fixed. The Gamecube controller triggers are recognized\n    as axes 5 and 6, and all 8 controllers now have default\n    bindings in pad.config.\n+ Added/updated Android port. (asie)\n+ Added Emscripten port. (asie)\n+ Added Nintendo Switch port. (Lachesis, asie)\n+ The main menu and game menu are now usable with keyboard,\n  mouse, or joystick. Pressing a navigation key/joystick button\n  or clicking an option in these menus will enable a cursor to\n  select one of the options. Most listed options are selectable\n  unless disabled by the current game, in which case they will\n  be hidden from the menu entirely. However, the settings and\n  exit options appear regardless of their respective counters.\n+ MegaZeux now supports Reality Adlib Tracker (RAD) music files.\n  Both 1.x and 2.x variants are fully supported.\n+ New counter: TIME_MILLIS. Returns the milliseconds of the\n  current system time.\n+ New counters: MOD_LOOPSTART and MOD_LOOPEND. These counters\n  return the start and end of the loop for the current playing\n  music and can be set to change the loop while the music is\n  playing. Setting MOD_LOOPEND to 0 disables the loop. These\n  counters only work for Ogg Vorbis and WAV audio (not modules).\n+ Added support for the LOOPEND tag for Ogg Vorbis files. This\n  tag can be used to define the end of the loop directly, which\n  may be more convenient for some users. This tag takes lower\n  precedence than LOOPLENGTH if both are present, and will be\n  ignored if its value is less than LOOPSTART.\n+ The SPR#_OFF counter is now readable through Robotic.\n+ New sprite counter: SPR#_Z. Sprites are displayed in order\n  of their z value. Sprites with a higher z value will appear in\n  front of sprites with a lower z value. This ordering takes\n  precedence over both Y ordering (if SPR_YORDER is enabled) and\n  sprite number. Removes the need to use SPR#_SWAP for managing\n  sprite draw order. (Lancer-X)\n+ New counters: FREAD_LENGTH and FWRITE_LENGTH. These counters\n  return the length of the FREAD file/directory (if open) or the\n  current length of the FWRITE file (if open).\n+ New counter: SET \"$string\" \"SAVE_ROBOT\". This does the same\n  thing as SET \"filename\" \"SAVE_ROBOT\", except it saves the\n  robot to the specified string.\n+ The default boot.dol for Wii builds with the editor enabled\n  is now MegaZeux instead of MZXRun.\n+ New GLSL scaling shader: \"sai\". This shader implements the\n  \"Scale 2xSaI\" algorithm by Kreed with a modified interpolation\n  function to produce sharper edges. This shader can scale up to\n  any size larger than 640x350 and is comparable to hqscale\n  (though it should look and perform a little better).\n+ Fullscreen mode will now attempt to match a supported screen\n  resolution if no \"fullscreen_resolution\" setting is specified.\n  For scaling renderers, this favors the current desktop\n  resolution. For \"software\", this favors smaller resolutions.\n+ Added the config setting \"fullscreen_windowed\". When set to 1,\n  MZX will create a borderless window with the current desktop\n  resolution instead of regular fullscreen. Disabled by default.\n+ Added the config setting \"grab_mouse\". When set to 1, MZX will\n  trap the mouse cursor within the window while the window is\n  active. Disabled by default.\n+ The current renderer can now be set from the settings menu.\n+ Added the 'softscale' renderer. This renderer is a generalized\n  rewrite of the former SDL 2 overlay renderer implementation.\n  It should perform significantly better and be more portable.\n+ Added several palette editor usability improvements:\n  + The current color stored with F2 is now displayed in the\n    bottom-right corner of the palette editor help menu.\n  + In SMZX mode 2 or 3 editing, the four colors of the current\n    subpalette can be stored with F5. Stored subpalette colors\n    can be placed at the current subpalette with F6. These are\n    stored independently of the stored current color. The help\n    menu color display will cycle through the stored colors.\n  + In SMZX mode 3 editing, the current subpalette's indices can\n    be stored with F7 and placed at the current subpalette with\n    F8. The stored indices are displayed in the help menu next\n    to the stored color display.\n  + The shortcut for toggling the subpalette cursors has been\n    changed to Insert. The subpalette cursors are now displayed\n    in SMZX mode 3 editing and are enabled by default.\n  + Palettes (and/or indices) can be imported into and exported\n    from the palette editor with Alt+I and Alt+X, respectively.\n  + Added a secondary editor-only palette which can be switched\n    to with Tab. This palette is not saved with any world, but\n    can be edited and imported to/exported from and can be used\n    to copy colors/subpalettes between different sources.\n+ checkres now displays the board, sfx, and robot (with line and\n  position on the board) each file was referenced in.\n+ checkres can now be used with a directory as the base file.\n  Like a ZIP archive, checkres will recursively search the\n  directory for .MZX/.MZB files and display information for all\n  of them.\n+ checkres has more display options: only display missing (-M)\n  (default), only display created (-C), only display found (-F),\n  also display missing (-m), also display created (-c), and also\n  display found (-f).\n+ checkres can now wildcard match resources from commands that\n  use expressions or interpolation in filenames. This can be\n  enabled with -w or enabled exclusively with -W.\n+ checkres can now display unused resources in a path/zip after\n  the other resources are listed (-u), or display only unused\n  resources (-U).\n+ checkres can now display every reference to a particular\n  resource in a world instead of just the first with varying\n  levels of information (-v, -vv). Added the option -s, which\n  is the default and equivalent to older versions of checkres,\n  and -1 as an alternate flag for -q.\n+ checkres can now sort its output either by the name of the\n  expected resource file (-N) (default) or by the location in\n  the world the file was referenced (-L).\n+ checkres option flags can now be combined (e.g. -aL). This\n  does not apply to the \"-extra\" and \"-in\" flags.\n+ New config options: \"test_mode\" and \"test_mode_start_board\".\n  If \"test_mode\" is set to 1, MegaZeux will start the given file\n  in testing mode, exactly as if the user used Alt+T on the\n  first board from the editor. Setting \"test_mode_start_board\"\n  to a valid board number in the world will start instead from\n  that board. Exiting testing will exit MegaZeux entirely.\n- SDL 2 support for the overlay renderers has been removed. When\n  specified for video_output, softscale will be enabled instead.\n- Removed WAV from the list of board mod extensions.\n\nFIXES\n\n+ SET \"$string\" TO \"fwrite\" will now always write the delimiter\n  regardless of the length of $string. Previously, this command\n  would only write a delimiter if $string was a length greater\n  than zero, requiring extra checks by the user to account for\n  this special case. Compatibility checks have been added for\n  for worlds made in older versions.\n+ Fixed bug where robots attempting to copy the player over\n  themselves with COPY x y x y or COPY dir dir would get stuck\n  in an infinite loop instead of executing the next command.\n+ SPR_YORDER and the sprite collision list are properly saved\n  and loaded from saves again.\n+ The SPR_NUM counter now has the full range of a regular\n  counter and is saved. Out-of-range values are ignored for the\n  purposes of its legacy IF and PUT uses. This only affects\n  2.92+ worlds. Additionally, 2.65 through 2.69c worlds treat\n  its range as -128 through 127, fixing a bug in Sprite Catcher.\n+ The active sprites count is properly updated when SPR#_OFF is\n  set and is properly loaded from saves again. This value can be\n  viewed from the variable debug screen now, though it may be\n  inaccurate for older saves.\n+ Fixed various bugs related to the robot position not being\n  internally tracked correctly. This could be caused by WALK,\n  SWITCH dir dir, OPEN, MOVE ALL, moving through a transport,\n  pushing, and rotation. This fixes issues with IF ALIGNEDROBOT,\n  THISX, THISY, THIS_COLOR, PLAYERDIST, HORIZPLD, VERTPLD, RID#,\n  ROBOT_ID_#, the robot's coordinates displayed in the counter\n  and robot debuggers, and MZM saving. Due to the number of\n  features affected by these bugs, the fix only affects worlds\n  saved in 2.92 or later.\n+ Fixed a regression where rotating could sometimes fail to\n  rotate the player, sensors, pushable robots, or transports.\n+ When a robot uses the OPEN command and is pushed through a\n  transport, MZX no longer crashes or creates invalid robots.\n+ Fixed crash that could sometimes occur when pressing Alt+Up\n  while drawing near the top of a board in the editor.\n+ Fixed bug where canceling 'Alt+C' char selection in the\n  robot editor would comment the current line.\n+ Fixed a bug where F5 in the robot editor would not print data\n  for extended chars.\n+ Clicking through board name windows and other interfaces with\n  the mouse should no longer place the current buffer.\n+ Fixed a bug where the updater could fail to initialize due to\n  a relative argv[0] in Windows. MegaZeux attempts to get the\n  executable path from Win32 first now.\n+ Improved warning message when no HTTP response is received.\n+ The software renderer no longer tries to use an SDL_Renderer\n  to draw the surface returned by SDL_GetWindowSurface. This\n  fixes a crash when switching to fullscreen with the software\n  renderer on some platforms.\n+ Fixed a software renderer mouse crash bug caused by moving the\n  cursor off the screen with a window bigger than the screen.\n+ Fixed a bug where setting SMZX_MESSAGE to 0 wouldn't always\n  draw the message correctly.\n+ If a referenced SAM or GDM file isn't present, MegaZeux will\n  attempt to load a corresponding WAV or S3M (respectively) with\n  the same filename minus the extension. This should fix some\n  music/sounds in older games that referenced SAM/GDM files but\n  were packed with the post-conversion WAV/S3M files only.\n+ The behavior for reseting the player movement repeat timers\n  has been reverted to requiring all four movement keys released\n  (as in 2.80 to 2.91i). The related player locking fix has not\n  been modified.\n+ IF \"&$string&\" and COPY BLOCK # # # # \"&$string&\" now properly\n  resolve &$string& before checking if the first operand or the\n  copy destination are a string (respectively), bringing their\n  behavior more in line with SET/INC/DEC and other commands.\n+ Fixed a regression where the COLOR INTENSITY commands would\n  update the screen if used the same cycle as TELEPORT PLAYER.\n  This fixes a graphical issue in Thanatos Insignia.\n+ Improved string wildcard comparison performance.\n+ Changed the debug window to display \"2.65/2.68\" for worlds\n  from MZX 2.65 and MZX 2.68. Features from 2.68 still check for\n  the intended MZX 2.68 magic; the very few existing 2.68 worlds\n  that use these features need to be resaved in 2.69.\n+ MegaZeux now attempts to fix Robotic that fails bytecode\n  validation. This resolves error messages and bugs in Eternal\n  Eclipse Taoyarin v1.0, Loco, Manuel the Manx, SaintZZT 7th,\n  Slave Pit, and Ep+Arp World MZX.\n+ The MOD SAM command has been restored for compatibility and\n  is version locked to <2.80 worlds. This fixes missing sound\n  effects in Crisis in Stuffyou City (original), Sprite Catcher,\n  and Talon's Tale. This only works for libxmp builds.\n+ Fixed a sprite collision bounding bug where the collision\n  height bounding for the reference checked using the collision\n  width instead. This fixes Manuel the Manx.\n+ Fixed several memory leaks in the editor related to editing,\n  placing, grab, and undo/redo operations on robots and scrolls.\n+ Fixed vlayer memory leak when loading 2.84 saves.\n+ Fixed a regression where the 'if c?? Sprite' legacy fix wasn't\n  locked to 2.69c and up. This fixes Depravity (2003 DoZ #666).\n+ PUT Sprite now works unconditionally for <2.80 games, fixing\n  bugs where games relied on placing 0-height sprites or placing\n  sprites before initializing their dimensions: Hackers Can Turn\n  Your Computer Into a Bomb!, Lethal Recurse (2003 DoZ #215),\n  Serum (2003 DoZ #404), Trashman Dan (Summer 2004 DoZ #18349).\n+ The LOCAL and LOCAL# counters are now versioned separately,\n  fixing bugs in Freedom Bound (Summer 2000 DoZ #4096) and\n  Impact (2001 DoZ #027).\n+ SHOOT now properly updates the blocked array. This fixes a bug\n  in Captain Proton and the Reality Rippers (1999 WEoZ #173).\n+ Fixed a bug where the custom SFX flag and max samples wouldn't\n  be reset when creating a new world in the editor.\n\nDEVELOPERS\n\n+ Fixed -Wunused-result warnings for Linux builds.\n+ Added \"Intel EMGD\" to auto_glsl blacklist (questionable\n  OpenGL 2.0 support).\n+ Added platform-independent joystick event functions and\n  updated the SDL, NDS, 3DS, and Wii event handlers for them.\n+ MinGW builds now use -Wl,-Bstatic and -Wl,-Bdynamic to enforce\n  the preferred linking when both static and dynamic builds of\n  libraries are present.\n+ For MSYS2 shells, config.sh now chooses the prefix /mingw32\n  or /mingw64 by default if $MSYSTEM is \"MINGW32\" or \"MINGW64\"\n  and either the \"win32\" or \"win64\" arch is selected.\n+ MegaZeux now uses the renderer free_video function on exit.\n+ GL_LoadLibrary no longer fails if the library is already\n  loaded in SDL 1.2. (Lancer-X)\n+ GL renderers free textures and revert to fixed function\n  pipeline where appropriate. (Lancer-X)\n+ OpenGL ES support can now be enabled for SDL builds with the\n  --enable-gles option. This option is force-enabled for\n  platforms that only support OpenGL ES.\n+ opengl1 renderer now always uses power of 2 textures, same as\n  opengl2 and glsl. This provides a tiny performance improvement\n  on most cards and a massive performance improvement on some\n  other cards. (Lancer-X)\n+ Added debug build sanitizer options for AddressSanitizer,\n  MemorySanitizer, and ThreadSanitizer. MemorySanitizer requires\n  clang and all currently require Linux/BSD/Mac OS X/similar.\n  Support for all three is very experimental; they may require\n  disabling options like modular builds or may not work at all.\n+ Moved several param functions from robot.c to run_robot.c.\n  Aside from a few exceptions, these functions are largely used\n  in run_robot.c only. This should improve Robotic performance\n  in release builds.\n+ Updated MSVC project for Visual Studio 2019. (elig, Spectere)\n+ Added experimental pledge(2) and unveil(2) support for OpenBSD\n  builds. By default, this is enabled for the utils but not for\n  MZX. Additionally, config.sh now detects OpenBSD properly and\n  several warnings have been fixed.\n+ Fixed faulty '!' rule logic in match_function_counter and\n  changed 'key?' to 'key!' to prevent potential false matches.\n- Removed uthash as a build option.\n\n\nFebruary 20th, 2019 - MZX 2.91j\n\nHere's a huge collection of bugfixes and a couple of features\nthat needed to be split off from 2.92.\n\nFeatures: every release has an HTML copy of the help file now,\nthe editor keeps separate board/overlay/vlayer buffers now, and\nanyone who still uses scrolls/sensors can view their info in the\ncounter debugger. The global volume settings use an exponential\ncurve now, range from 0 to 10, and have updated UI elements.\n\nFixes in this release include: the updater won't complain about\nmzx_help.fil anymore, the NDS and 3DS ports have keyboards that\nwork again, several bugs that would lead to worlds/saves with\n\"Robot # does not exist on board #\" errors have been fixed, and\nseveral crashes have been fixed.\n\nUSERS\n\n+ A complete HTML copy of the MegaZeux help file is now packaged\n  with releases for all platforms. It can be found in the docs/\n  folder.\n+ Nvidia and AMD switchable graphics drivers should now detect\n  MegaZeux and MZXRun properly on Windows.\n+ The editor now remembers the contents of the buffer when\n  switching between board, overlay, and vlayer editing. For\n  example, if a robot is selected on the board before switching\n  to overlay mode, the robot will still be in the buffer when\n  the user switches back to board mode.\n+ The editor now remembers the last filename for importing and\n  exporting MZM, MZB, CHR, PAL, and PALIDX files.\n+ Scrolls and sensors can now be viewed in the counter debugger.\n  They can be found by expanding the Board list (when present).\n+ Readded \"#version 110\" directives to all GLSL shaders, fixing\n  warnings on some Intel HD drivers. OpenGL ES builds should now\n  comment out these directives.\n+ Fixed bug where using Modify+Grab on built-ins could clear the\n  floor under the built-in.\n+ Fixed bug where key repeat wouldn't work for any text field in\n  the NDS/3DS/Wii ports. This also affected the robot editor in\n  Wii builds.\n+ Fixed NDS touch screen support. Touching the screen no longer\n  acts like escape and the keyboard properly works again.\n+ Fixed a bug where the NDS keyboard would drop keypresses on\n  games not running at speed 2.\n+ The counter debugger now correctly displays the value of\n  local0.\n+ Fixed a bug where the counter debugger would not reopen to\n  the correct robot local# value.\n+ MZX now attempts to retry removal of an old file when updating\n  if deletion fails initially. Error messages related to the\n  failed deletion of \"mzx_help.fil\" or \"assets/help.fil\" are now\n  suppressed as they are caused by a bug in older MZX versions.\n+ The help file is now closed before the updater restarts MZX.\n+ Fixed crash bug that could occur when removing old files after\n  an update.\n+ Improved manifest validity checks.\n+ Fixed board editor crash caused by invalid thing IDs.\n+ Player movement key repeat no longer resets when the player is\n  locked. It will reset for a direction only when the arrow key\n  corresponding to that direction is released. This fixes a bug\n  in Brotherhood where the player would move at a fraction of\n  the speed they were supposed to while swimming.\n+ Added version checking for legacy IF c?? Sprite behavior. In\n  versions 2.82b and prior, c?? would make this command ignore\n  the provided param and use SPR_NUM instead. This fixes Project\n  MoveZig.\n+ Fixed spelling error in missile param dialog.\n+ Switching boards in the editor now properly translates the new\n  board's mod name, fixing cases where board mods would restart\n  sometimes.\n+ Fixed a bug where the current board mod wouldn't play after\n  starting a listening mod, testing, returning to the editor,\n  and then disabling the listening mod.\n+ Replaced the linear volume setting curve with an exponential\n  curve to make lower volumes less loud. The volume settings can\n  now be set from 0 to 10 instead of from 1 to 8.\n+ The config file setting pc_speaker_on=0 no longer prevents\n  turning on PC speaker SFX at runtime.\n+ Added temporary workaround for numpad 5 key detection when\n  numlock is disabled.\n+ \"Forest to floor\" and \"Collect bombs\" are properly enabled\n  again for new boards by default. This fix doesn't alter config\n  settings or saved .editor.cnf defaults.\n+ The \"Downver. world (MZX)\" option in the \"Export as\" dialog\n  is now completely highlighted when selected. (Lancer-X)\n+ Starting MegaZeux with \"startup_editor=1\" no longer loads the\n  default palette over the startup world's palette.\n+ The move block action and undo/redo operations should now\n  preserve the element beneath the player.\n+ Fixed crashes caused by using block actions or COPY BLOCK on\n  the player while the player is standing on a sensor.\n+ Fixed several bugs where editor block actions and the commands\n  COPY/COPY BLOCK would not check for the player or clean up\n  robots/scrolls/sensors. Worlds/saves saved after these bugs\n  occured could display \"Robot # does not exist on board\"\n  errors when loaded.\n+ The DUPLICATE SELF x y and DUPLICATE SELF dir commands now\n  properly check for the player before attempting to duplicate\n  a robot. This fixes a bug where MZX could create saves with\n  \"Robot # does not exist on board\" errors.\n+ Fixed a crash that could occur when importing a new board.\n+ Fixed a bug where the 3DS onscreen keyboard would send key\n  releases instead of key presses.\n+ The 3DS onscreen keyboard now returns shifted chars when shift\n  is active.\n+ Fixed a bug where the libxmp implementation of MOD_ORDER and\n  JUMP MOD ORDER could not restart the current order.\n+ Fixed a crash with the libxmp implementation of MOD_POSITION\n  when providing an out-of-bounds value.\n+ Fixed a bug where Ctrl+[key] shortcuts would not work while a\n  dialog list element was active on the Wii.\n+ Fixed a bug where setting the board mod/charset/palette could\n  truncate the first letter of the path on the Wii.\n+ \"mask_midchars\" no longer masks the non-text char 127.\n+ The message box title and the command [ now properly display\n  the graphic for char 9 instead of a tab.\n+ Added newline escaping to robot, counter, and string names in\n  the counter debugger to work around formatting bugs.\n\nDEVELOPERS\n\n+ Added the hlp2html utility, which can be used to convert\n  WIPHelp.txt into web and printable HTML files.\n+ Updated to latest NDS ARM7 template.\n+ Updated SDL2 for Windows release builds to SDL 2.0.9.\n\n\nDecember 9th, 2018 - MZX 2.91i\n\nA small release with a bunch of fixes for bugs introduced mostly\nwithin the past few versions. Other highlights: joysticks should\nbe hot pluggable now and Alt shortcuts now also work with the\ncommand key in Mac builds.\n\nUSERS\n\n+ MZX now properly detects when joysticks are connected and\n  disconnected at runtime for platforms using SDL 2.\n+ Fixed bug where the robot editor would open with the cursor at\n  the end of the line instead of the start of the line.\n+ Fixed a bug where the robot box could display using MZX mode\n  but SMZX colors if opened on the first frame SMZX was enabled.\n+ The built-in text box title should display correctly again in\n  SMZX modes.\n+ Fixed regression where keys could trigger wrong \"keyN\" labels.\n+ Alt+F2 can now be used to open the settings dialog.\n+ Mac users can now optionally use command in place of alt.\n+ Improved editor cursor color selection for SMZX modes 2 and 3.\n\nDEVELOPERS\n\n+ Fixed make test for SDL 1.2 builds.\n\n\nNovember 14th, 2018 - MZX 2.91h\n\nA minor release primarily focused on improving counter/string\nlookups and fixing bugs in the robot editor and counter debug\nmenu.\n\nUSERS\n\n+ auto_glsl correctly switches to glsl on startup now.\n+ Fix bug where \"(editor)\" wouldn't always display in the\n  caption when editing.\n+ The single line macro dialog now allows the full size of the\n  single line macros to be edited. This fixes a crash caused by\n  having a macro configured to be longer than the edit box could\n  display.\n+ Setting a counter to a string no longer duplicates the entire\n  string in memory.\n+ Fixed faulty BOARD_X and BOARD_Y bounding for values greater\n  than the board width or height. Fixes potential crashes with\n  BOARD_CHAR, BOARD_COLOR, BOARD_ID, and BOARD_PARAM.\n+ The value of SPR_YORDER is now correctly displayed in the\n  counter debug menu.\n+ Improved the performance of the counter debug search.\n+ Fixed cosmetic counter debug menu issue where the the \"String\"\n  tree appeared to be expandable/collapsable when empty.\n+ Fixed faulty substring searching algorithm used in the counter\n  debug menu search and for breakpoints.\n+ Added \"Search\" and \"Cancel\" buttons to the counter debug menu\n  search so it can be used with the mouse.\n+ When testing from the editor, if \"__test.mzx\" exists MegaZeux\n  will now choose a numbered name for this world backup (e.g.\n  \"__test2.mzx\") instead of potentially overwriting work.\n+ Fixed bug where six-digit line numbers would display in the\n  robot editor incorrectly.\n+ Inserting text in the robot editor using the F2/F3/F4/F5\n  shortcuts can no longer exceed the line length limit, fixing\n  related crashes.\n+ Using F5 in the robot editor generates decimal numbers instead\n  of hex numbers now.\n+ Fixed robot editor bug where pressing delete at the end of a\n  line wouldn't join lines unless the current line was empty.\n+ Restored the ability to use the F7/F8 cheats outside of the\n  editor. This feature was broken in 2.91g. To enable cheats\n  outside of the editor, the config option \"allow_cheats\" must\n  now be set to \"mzxrun\" or 1. The former will enable them for\n  MZXRun only (same as from 2.82b to 2.91f) and the latter will\n  enable them for both executables (same as the -þ flag in DOS\n  versions).\n+ Fixed bounding bug on the \"board_default_width\" and\n  \"board_default_height\" config file options.\n+ Separated robot editor \"Search and Replace\" dialog into a\n  \"Search\" dialog and a \"Replace\" dialog. Both dialogs are\n  smaller in size than the original. The order of the elements\n  in these dialogs has also been altered to be more user-\n  friendly. The hotkey for the new replace dialog is Ctrl+H.\n+ Fixed Ctrl+R repeating for the \"Replace All\" robot editor\n  search operation. Previously, it would act like \"Replace\".\n+ Canceling the robot editor search/replace menus no longer\n  disables the repeat feature.\n+ Searching in the robot editor now properly preserves the case\n  of the search string if case sensitive search is disabled.\n+ Fixed bug where toggling the robot debugger position wouldn't\n  take effect immediately.\n+ Exiting the editor with the \"startup_editor\" config setting\n  enabled should no longer inappropriately close MZX.\n+ Selecting \"tile\" for the char editor import mode works again.\n+ The bounds for the main editor charset import/export offsets\n  and size have been increased to allow for extended charset\n  values.\n+ Fixed the \"KEY?\" labels for keys that no longer have a unicode\n  representation in SDL2.\n+ Fixed SET \"$string\" \"FWRITE#\" bug where, if # exceeded the\n  length of $string, the output would be clipped to the wrong\n  length.\n+ The missile color ID char is now correctly set back to its\n  default value when a new world is created from the editor.\n\nDEVELOPERS\n\n+ Cleaned up the counter debug menu code.\n+ Switched from uthash to a modified version of khash for\n  counter and string lookups. Counters/strings now consume less\n  memory and counter/string lookups should perform slightly\n  better on most platforms.\n+ Replaced toupper/tolower in memcasecmp and substring searching\n  with a lookup table-based implementation of tolower.\n+ Refactored the following contexts to use the main loop: robot\n  editor, intake, thing menu. For compatibility purposes the old\n  intake still exists, but all robot editor hacks have been\n  removed from it.\n\n\nOctober 7th, 2018 - MZX 2.91g\n\nThis bugfix release fixes various minor-to-moderate bugs in\nvarious areas of MegaZeux. The title/game update code has gone\nthrough a fairly major overhaul and several parts of MegaZeux\nhave been combined to use the same loop. The conversion of\nMegaZeux's interfaces to be compatible with this loop is an\nongoing process expected to occur over several versions.\n\nUSERS\n\n+ Added \"allow_screenshots\" config file option. Setting this\n  option to 0 will disable the built-in screenshot feature.\n+ The F12 key can now be detected by Robotic.\n+ Loading saves from the title screen no longer resets TIME.\n+ Loading saves from the title screen no longer erroneously\n  changes the player restart position.\n+ Swapping worlds now sets the correct TIME and player restart\n  position values.\n+ Fixed a bug where the fade out effect from COLOR FADE OUT\n  would be reset after any Robotic dialog.\n+ Undo for overlay and vlayer mouse drawing now works properly.\n+ Mouse drawing in the editor now draws smooth lines instead of\n  lines with gaps.\n+ Fixed move block bug when the source and destination both\n  overlapped the player.\n+ Palette editor component entry now accepts most of the same\n  inputs as dialog number boxes.\n+ The board name in the caption now updates to reflect the\n  current board while testing.\n+ Fixed bug where TIME would either decrement normally or not\n  decrement at all while the slow time effect was active.\n+ Added compatibility for pre-port endgame teleport behavior.\n  In DOS versions of MZX, the endgame teleport would disable\n  itself after being triggered for the first time and any\n  following endgame would instead trigger a game over.\n+ Reduced stutter in NDS and 3DS main screen scrolling.\n+ Fixed a bug where creating a string MZM would not correctly\n  set the string's length for preexisting strings.\n+ Fixed a bug where loading robot source code from a string\n  wouldn't work correctly with the robot debugger.\n+ The help system is now accessible from the main menu and game\n  menu.\n+ The settings screen is now accessible from the main menu, game\n  menu, editor, and palette editor.\n+ The shortcut Ctrl+F2 can be used to open the settings screen\n  from anywhere the settings screen is accessible. This shortcut\n  works even if F2 is used for a different feature and will also\n  ignore the value of the F2_MENU counter.\n+ Fix a crash that would occur decrypting a world with a\n  password exactly 15 chars long.\n+ The label for the INPUT STRING command should no longer be\n  able to overflow outside of the window.\n+ Fix a crash that would occur attempting to open the help file\n  from the world decryption confirm dialog.\n+ Fix bug where saved layer MZMs and board MZMs without robots\n  would have useless extra data at the end of the file.\n+ Scroll contents properly display game colors during gameplay.\n+ An error message is now displayed for failed board exports.\n+ Enabled writing zip data descriptors on the 3DS to decrease\n  saving time.\n- The \"password protected\" error message has been removed as it\n  was redundant with the confirmation dialog following it.\n- The \"disassemble_extras\" and \"disassemble_base\" options no\n  longer affect Robotic output during gameplay. SAVE_ROBOT will\n  always output extra words and base 10 numbers regardless of\n  user configuration.\n\nDEVELOPERS\n\n+ Implemented a main event loop to replace the various separate\n  event loops scattered around MegaZeux. See core.h and core.c\n  for more info.\n+ Refactored the following contexts to use the main loop:\n  titlescreen, gameplay, main menu, game menu, editor, palette\n  editor.\n+ When enabled, the debug FPS display will now update from any\n  interface using the main loop. If fullscreen mode is active,\n  the FPS display will now appear in the top-left corner of the\n  screen for any main loop interface aside from the editor.\n+ The --disable-screenshots config.sh option can be used to\n  disable screenshot support. This allows platforms that don't\n  use render_layer.c to stop building and linking it.\n+ Cleaned up m_show()/m_hide() overuse.\n+ Replaced all uses of \"bool\" with \"boolean\" to avoid potential\n  C/C++ compatibility issues.\n- Removed most old/unused source code in contrib/unzip, src/old,\n  and src/vfs.\n\n\nSeptember 17th, 2018 - MZX 2.91f\n\nA bugfix release focusing primarily on renderers, the 3DS and\nWii ports, and the Makefile build system. Highlights: the 3DS\nport should perform better now, the Wii port's GX renderer has\nbeen restored, the 'darwin' platform has more Unix-like options\nnow, and several crashes have been fixed.\n\nUSERS\n\n+ The config setting 'audio_buffer' can also be specified with\n  'audio_buffer_samples' now. The default value for this setting\n  is now 1024 (instead of 4096).\n+ Fixed a crash that could occur in the counter debugger.\n+ Fixed a crash that occurs when attempting to add a watchpoint\n  for robot local counters, loopcount, etc. Using these counters\n  as watchpoints will watch the global robot's instances of\n  these variables (for other robots, use e.g. r#.local1).\n+ Using SMZX_INDICES or LOAD_ROBOT on an unset string no longer\n  causes a crash.\n+ png2smzx is now properly bundled with Linux builds.\n+ Improved the performance of loading partial charsets for the\n  glsl, opengl2, and 3DS renderers. Other renderers were not\n  affected by this issue.\n+ Fix opengl2 unbound sprite regression introduced in 2.91e.\n+ The opengl2 renderer now correctly draws unbound sprites\n  containing chars with a transparent foreground color.\n+ 3DS renderer optimizations. (asie)\n+ 3DS screen focusing now mimics the NDS port's behavior. (asie)\n+ Fixed bug where games could fail to open files on some\n  platforms if the paths contained duplicate slashes. (asie)\n+ Added a Wii software layer renderer. This renderer can be\n  selected with \"video_output = xfb\" in the config file.\n+ Wii GX renderer optimizations.\n+ Added layer rendering support to the GX renderer.\n+ The Wii GX renderer now uses the gl_vsync config option to\n  toggle vsync. For the Wii, this feature is on by default.\n  Disabling it may increase the framerate of some games but may\n  also cause other problems.\n+ The loading bar on console platforms now redraws the screen\n  less often.\n\nDEVELOPERS\n\n+ GAMESDIR/BINDIR/SHAREDIR now properly apply PREFIX when they\n  are not explicitly set by config.sh.\n+ LIBDIR is now user-definable like BINDIR et al. Variations\n  such as \"lib64\" currently must be explicitly provided for\n  platforms that still use them.\n+ Remove src folder from CFLAGS to fix a bug where system\n  includes could be mistaken for MegaZeux headers. All includes\n  of MegaZeux headers must be done using relative paths now.\n+ Fixed include bugs for X11 and Carbon clipboard handlers.\n+ Makefile now attempts to respect the prefix when running\n  sdl-config, sdl2-config, and libpng-config.\n+ Renamed the \"darwin\" config.sh platform to \"darwin-dist\".\n+ Added \"darwin\" and \"darwin-devel\" config.sh platforms. These\n  act like Mac OS X versions of the \"unix\" and \"unix-devel\"\n  options.\n+ The darwin-dist build system and instructions are now somewhat\n  more clear and robust.\n+ Fixed a bug where zlib wouldn't necessarily be linked to\n  png2smzx.\n+ Renderers will now use render_layer to draw the text_video\n  fallback if no render_graph function is present.\n\n\nSeptember 3rd, 2018 - MZX 2.91e\n\nBugfix release fixing some crashes, port bugs, and some other\nsignificant issues affecting a variety of things.\n\nUSERS\n\n+ Fixed bug where loading a board charset would also clear all\n  of the extended charsets.\n+ Fixed bug where loading a default charset in the editor would\n  also clear all of the extended charsets.\n+ Loading a 2.91 world when a renderer with no layer support is\n  active no longer triggers an error message.\n+ Fixed a memory corruption bug in the non-SDL Wii port caused\n  by faulty threading code.\n+ Fixed crash that could occur when updating a board undo frame.\n+ Fixed crash when changing to a board with overlay disabled in\n  overlay editing mode.\n+ Fixed a bug where the protected charset could get cleared for\n  renderers without layer rendering support.\n+ Enabled C99-compliant stdio functions for mingw, fixing at\n  least one crash bug and possibly improving performance.\n+ The NDS port now attempts to detect argv[0].\n+ The 3DS port now attempts to detect argv[0] and otherwise will\n  start in /3ds/megazeux instead of /.\n+ The 3DS port .3dsx file is now located in /3ds/megazeux.\n+ The MOVE PLAYER [dir] and MOVE PLAYER [dir] [label] commands\n  now update the commands cycle and commands total values.\n+ Fixed a bug where MZX and checkres wouldn't accept some\n  DEFLATE-related ZIP flags.\n+ checkres no longer crashes on failing to open a resource zip.\n+ The window caption now updates correctly when using either of\n  the overlay renderers.\n+ Update checking will now display an error instead of silently\n  hiding the updater when the updater fails to initialize.\n+ Fixed an issue where UIs would execute redundant frames.\n+ Fixed a bug where string comparison would not order strings of\n  different lengths correctly. (GreaseMonkey)\n\nDEVELOPERS\n\n+ Code style has been cleaned up in numerous files.\n+ Moved GLSL shaders from assets/shaders/ to assets/glsl/.\n+ Added missing GPL headers to GLSL shaders and added slightly\n  better documentation to them.\n\n\nJuly 12th, 2018 - MZX 2.91d\n\nThis is a minor bugfix release to get a handful of fixes out\nprimarily concerning the updater. The NDS/3DS/PSP/Wii ports now\ninclude a copy of Caverns of Zeux.\n\nUSERS\n\n+ Added timeouts to updater network operations.\n+ Fixed bug where the updater would retry downloads without\n  regard to the status code.\n+ The updater is now force-disabled for all non-Windows\n  platforms in config.sh.\n+ Fixed bug where breakpoints could trigger on the wrong line.\n+ Amended VOLUME/MOD FADE # # bounding to clamp values in port\n  versions. DOS versions will still wrap between 0 and 255.\n+ Added DOS compatibility fix for sprN_off.\n+ Window caption is now properly updated when loading a world\n  from an unedited world in the editor.\n+ Fixed bug where \"..\" would change to the current directory if\n  the current path ended in a path separator.\n- Removed undocumented support for \"title.cnf\", \"game.cnf\", and\n  \"editor.cnf\" config files, which would have been loaded from\n  the same directory as the main config (not to be confused with\n  world-specific config files, which are still supported).\n\nDEVELOPERS\n\n+ The MSYS2 buildscripts have been overhauled to work with the\n  new devkitPro pacman repositories.\n+ Added config options to select vorbis, tremor, tremor-lowmem,\n  or to disable ogg vorbis file support. The 3DS and Wii ports\n  now use tremor instead of tremor-lowmem.\n\n\nMarch 4th, 2018 - MZX 2.91c\n\nThis release contains an assortment of bugfixes ranging from\nplayer clone fixes to obscure compatibility patches. A handful\nof updater issues have been fixed (or at least addressed).\n\nMegaZeux can now automatically check for updates on startup on\nWindows platforms. This behavior can be configured, and by\ndefault will simply leave a message in the window caption.\n\nUSERS\n\n+ MegaZeux can now automatically run the updater on startup\n  on applicable platforms. This can be configured with the\n  'update_auto_check' config file option.\n+ Fixed a bug where several .MOD variants stopped working with\n  libxmp enabled.\n+ Sound effects in subdirectories can now be used in the SFX\n  editor.\n+ Fixed a bug where checkres would fail on legacy worlds with\n  very long custom SFX.\n+ Checkres no longer ignores the SFX tables of 2.90+ worlds.\n+ Fixed a bug where interpolated expressions containing ternary\n  operators could terminate counter names early.\n+ Fixed faulty IF ANY compatibility behavior.\n+ Fixed a bug where FWRITE functions could change the case of\n  user-defined filenames when creating new files on non-Windows\n  platforms.\n+ SMZX mode 1 palettes are now exported correctly as 16 colors\n  instead of as 256 colors.\n+ Fixed subtly inconsistent timing in certain built-ins.\n+ Fixed bug where wind and transporters could clone the player.\n+ MegaZeux now restarts correctly in paths containing spaces\n  when the updater is run.\n+ Subroutines in pre-port MZX worlds now always return to the\n  command after the subroutine was sent.\n+ Typed color components now wrap from the maximum value to zero\n  like dialog number inputs, and pressing minus will now negate\n  the input number.\n+ Copying chars in the char editor between regular and SMZX\n  screen modes now works properly.\n+ Fixed a crash that could occur when placing things on a vlayer\n  larger than the current board.\n+ Sharks and SpittingTigers with firing rates greater than 4 now\n  retain their firing rate when edited.\n+ Fixed a bug where world decryption could crash. (Revvy)\n+ If the glsl renderer is selected by default, the detection of\n  a software opengl driver will result in falling back to\n  software. (Lancer-X)\n+ Fixed a bug in the GLSL renderer where an additional window is\n  created if the OpenGL version is low enough to cause a\n  fallback to software. (Lancer-X)\n\nDEVELOPERS\n\n+ Added .MZX-based unit testing system. Run with \"make test\".\n  See testworlds/README.md for more information.\n+ The default renderer is now auto_glsl, which turns into glsl\n  if not using a blacklisted opengl driver. (Lancer-X)\n\n\nJanuary 6, 2018 - MZX 2.91b\n\nHere's a fairly significant update for MegaZeux 2.91, fixing\nseveral major Robotic, audio, and editor related bugs, as well\nas issues that mostly affected Linux platforms. Also of note is\nthe updated MSVC and Xcode support by Spectere.\n\nUSERS\n\n+ Added the config file option \"editor_thing_menu_places\". When\n  set to 0, selecting an object from the thing menus (F3-F10)\n  will place the object in the buffer but not on the board.\n  Defaults to 1.\n+ Fixed a bug where MZMs could sometimes fail to load in the\n  editor.\n+ MZMs loaded in the editor now behave correctly when undone.\n+ Fixed bug where GOOP_WALK wouldn't always be 0 when loading\n  old worlds.\n+ Values of GOOP_WALK greater than 1 no longer cause issues.\n+ Board time limits greater than 255 are no longer truncated\n  when saving worlds/saves.\n+ Fixed bug where \"robot not found\" errors could result from\n  using backspace on robots in the editor.\n+ Fixed backwards string inequality evaluation.\n+ String inequality compares are now endian-safe.\n+ Fixed a bug where certain mods (e.g. FR_TOWER.MOD, Neve.s3m)\n  could repeat to the wrong order after reaching the end.\n+ Fixed a bug where certain .it channels could partially ignore\n  the volume command.\n+ Setting mod_position in libxmp now works for positions in the\n  middle of orders (NoSuck).\n\nDEVELOPERS\n\n+ Updated MSVC for Visual Studio 2017. (Spectere)\n+ Added xcode support. (Spectere)\n+ \"make clean\" now deletes ccv. (pgimeno)\n+ Fixed creeping CRLF usage. (Lachesis, pgimeno, Spectere)\n+ Fixed a bug where the MSYS2 build scripts could inadvertently\n  include non-portable SDL2 binaries in Windows builds.\n\n\nNovember 22, 2017 - MZX 2.91\n\nThis release introduces mostly new editor features, but also a\na few Robotic features to note. It also fixes over 30 bugs.\n\nMegaZeux now features a palette editor for SMZX modes 2 and 3,\nand worlds can be saved and loaded in SMZX mode. The char editor\nhas been changed to allow preview palettes (mostly to aid with\nSMZX editing), and can now access the extended character sets.\nThe new vlayer editor allows the vlayer to be edited directly.\n\nOn the Robotic side of things, LOAD_COUNTERS actually works now,\nSMZX mode 3 indices can be loaded from a file, the message line\ncan be configured to use normal MZX mode in SMZX modes, and new\nstring features such as simple wildcard matching and negative\noffsets have been added. String support has been improved to\ndisallow invalid or nonsense string splices that previously had\nundefined behavior.\n\nAdditionally, asie's new Nintendo 3DS port has been merged into\nMZX, and optional SDL support has been added for the Wii.\n\nFEATURES\n\n+ Added SMZX_INDICES special counter to load SMZX indices. This\n  counter works with either a filename or a string, e.g.\n  set \"file.palidx\" \"SMZX_INDICES\", and will do nothing outside\n  of mode 3.\n+ Added MOD_LENGTH counter. The value of this counter is length\n  of the current playing music in rows for modules, or in\n  samples for PCM audio.\n+ Added MAX_SAMPLES counter. When set, MegaZeux will limit the\n  maximum number of samples that will play simultaneously. Set\n  to -1 to disable the sample limit.\n+ Added 'max_simultaneous_samples' config file option. This\n  behaves the same as the MAX_SAMPLES counter, but applies\n  globally.\n+ Added SMZX_MESSAGE counter. When set to 0 with SMZX active,\n  messages will display in normal MZX mode instead of SMZX\n  using the first 16 colors of the SMZX palette.\n+ Added 'random_seed#' counters to read and write the random\n  seed, 32 bits at a time (random_seed0 controls the low 32 bits\n  and random_seed1 controls the high 32 bits. (Lancer-X)\n+ Added a case-sensitive string equality operator:\n  IF \"$string\" === \"ABC\" then \"label\".\n+ Added wildcard-matching string equality operators:\n  IF \"$string\" ?= \"a?%\" then \"label\"\n  IF \"$string\" ?== \"a?%\" then \"label\" (case-sensitive).\n  The character '?' will match exactly one of any character in\n  $string, and '%' will match any number of any character in\n  $string (including no characters).\n+ Added negative indexing for strings, e.g. SET \"$string.-X\" 32.\n  This manipulates the string at the Xth character starting from\n  the end of the string (with -1 for the final character).\n+ Added negative offsets for strings, e.g. SET \"$string+-X\" \"a\".\n+ Added multiple character indexing for strings. When a length\n  is provided with a string index, e.g. SET \"$string.X#Y\" 12345,\n  the characters at positions X, X+1, ..., X+Y-1 will be treated\n  as a single 8*Y bit number. This works for values of Y between\n  1 and 4, with 4 characters providing the same functionality as\n  a counter.\n+ The palette editor and character editor are now accessible\n  while testing. Press Alt+E or Alt+C respectively to access\n  them from the counter debugger.\n+ Extended the palette editor to SMZX modes 2 and 3. The updated\n  palette editor allows the editing of all 256 SMZX mode 2/3\n  colors and editing color indices for SMZX 3.\n+ Component numbers can now be typed in the palette editor by\n  clicking the component name or its number, e.g. clicking \"Red\"\n  allows you to type a red value.\n+ SMZX indices can now be imported/exported while editing in\n  mode 3 using import/export palette.\n+ Added a new vlayer editor. In the world editor, press Alt+V to\n  switch to the vlayer editor. The vlayer is something like an\n  invisible global overlay that can be used to store and\n  retrieve graphical data through Robotic. See the editor help\n  and the Robotic reference manual for more details.\n+ Added color selection to the character editor. Press C to\n  choose a color to preview and edit chars with, and press Alt+C\n  to revert back to the default grey.\n+ Added extended charset support to the char editor. Extended\n  charsets can be selected during char selection. Note that in\n  gameplay these chars can be accessed only by unbound sprites.\n+ Selected blocks of chars with a height greater than one\n  (before selecting a subdivision) are now properly supported.\n  Using -/+ with these selections will move in a tiled manner,\n  with no overlap between chars in \"tiles\". Charsets can also be\n  exported/imported using this tiling behavior.\n+ The -/+ keys now behave the same in the char selection screen\n  as they do in the char editor.\n+ The character editor help now covers several shortcuts that\n  were previously missing (Alt+B, Shift+Arrows, etc...).\n+ The undo/redo shortcuts in the character editor are now Ctrl+Z\n  for undo and Ctrl+Y for redo.\n+ Added undo/redo functionality to the world editor. Use Ctrl+Z\n  to undo changes to the board/overlay/vlayer and Ctrl+Y to\n  redo.\n+ The default editor undo history stack size has been extended\n  to 100 levels.\n+ The editor will now prompt the user to create a new starting\n  board when opened. If a new board is created, the global first\n  board will be set to the new board and the title board will be\n  renamed.\n+ Saved positions now have confirmation dialogs. Saved positions\n  are saved to/loaded from the editor.cnf file for each world.\n+ Pressing Enter/Return on the overlay (and vlayer) now acts the\n  same as on the board, changing both the buffer and the current\n  layer being edited.\n+ Pressing P on the overlay (and vlayer) now acts the same as on\n  the board, changing the character in the buffer but NOT on the\n  current layer. This is equivalent to the original overlay\n  behavior for Enter.\n+ Block tiling movement (Ctrl+Arrows) now works with most block\n  actions, and does not require an initial block placement to\n  activate.\n+ Restoring its DOS functionality, Alt+D now toggles the default\n  colors of built-in types in the editor. When disabled,\n  built-ins placed from thing menus will use the buffer color\n  instead of their default colors, and a red dot will appear on\n  the right side of the status bar.\n+ Color selection now supports typing in the hex value of a\n  color/subpalette to select the given color/subpalette. For\n  example, typing \"4c\" will select background color 4 and\n  foreground color 12.\n+ Readded the downver utility.\n- Removed the undocumented Shift+F7 shortcut in the editor. This\n  shortcut was redundant with F11.\n\nFIXES\n\n+ Fixed a bug that would cause Linux binaries to fail to find\n  most resources.\n+ Fixed a bug where loading a save from the titlescreen, exiting\n  gameplay to a world that doesn't exist, and then loading a\n  second save would corrupt the counter and string hash tables.\n+ Fixed crash that would occur when loading a saved position on\n  the same board referring to out-of-bounds coordinates.\n+ Fixed a freeze that could occur opening the counter debugger\n  to an empty list or searching for a counter in an empty list.\n+ LOAD_COUNTERS now works as intended.\n+ Fixed a bug where key repeat would be prematurely terminated\n  when releasing a key while holding another key.\n+ Attempting to open the robot validator with no errors will no\n  longer crash MegaZeux.\n+ Backspace now mirrors delete in overlay mode instead of\n  affecting the board.\n+ Moving an overlay block partially out of bounds in the editor\n  will now correctly clear the block's original position.\n+ The color selector will now correctly display SMZX palettes in\n  SMZX mode instead of the default char 254.\n+ Fixed bug where the mouse cursor would vanish after using the\n  robot debugger configuration dialog in the editor.\n+ Fixed a bug where the counter debugger could sometimes display\n  behind the robot debugger after exiting the counter debugger.\n+ Undo in the char editor now works correctly with the mouse.\n+ The char editor no longer forces the screen to SMZX mode 1\n  when editing in SMZX modes 2 or 3.\n+ Fixed screen corruption bug when resizing the char editor in\n  SMZX modes.\n+ Fixed a bug where multichar editing wouldn't correctly wrap to\n  the start of the charset.\n+ The editing area outside of the current board is now correctly\n  drawn when SMZX is enabled.\n+ The viewport is now correctly drawn when SMZX is enabled and\n  the UI or unbound sprites are active.\n+ The editor bar no longer appears while SMZX is enabled in view\n  mode.\n+ Messages are now drawn correctly in SMZX mode.\n+ Fixed bug where centering the viewport could change the width\n  and height of the viewport.\n+ Unbound sprites are now clipped correctly against the viewport\n  when the viewport origin is not 0,0. (Lancer-X)\n+ The VLAYER_SIZE counter now clears new vlayer area added when\n  it is increased. This applies only to 2.91+ worlds.\n+ The VLAYER_WIDTH and VLAYER_HEIGHT counters now preserve data\n  on the vlayer when set. This applies only to 2.91+ worlds.\n+ Fixed several bugs with string length and offset parsing that\n  would allow invalid string accesses to work in certain cases.\n+ COPY BLOCK $string now works correctly with string splices.\n+ Fixed a bug where saving and loading 2.90X files wouldn't work\n  on the Nintendo DS.\n+ Fixed a bug where mouse clicks would carry through from the UI\n  into games.\n+ Invalid gl_scaling_shader values should no longer appear as\n  the current loaded scaler.\n+ MegaZeux now falls back to the default scaling shader when a\n  selected scaling shader fails to compile.\n+ Blank sprite chars are now drawn when ccheck is 3. (Lancer-X)\n+ The sprite color is used with ccheck 3 collisions. (Lancer-X)\n+ When spr_yorder is enabled, sprites with the same (sprN_y +\n  sprN_cy) value are now consistently ordered by their sprite\n  numbers.\n+ The collision rectangles of ccheck 3 sprites now constrained\n  to the sprite dimensions (Lancer-X).\n+ MZX no longer crashes when a line of length 512 characters or\n  longer is pasted into the robot editor.\n+ Fixed a crash bug when trying to swap a sprite with an\n  invalid sprite index.\n\nDEVELOPERS\n\n+ Added Nintendo 3DS port. (asiekierka)\n+ Added \"make uninstall\" option to the Linux makefile.\n+ Added devkitPro portlibs paths to the NDS and Wii Makefiles.\n- Removed the depackers and non-MegaZeux formats from libxmp.\n\n\nSeptember 4, 2017 - MZX 2.90d\n\nHere's another bugfix release. This is mostly assorted small\nfixes, but there are also a couple of major fixes here: first,\nthe ternary operator now works correctly when nested both with\nand without expressions, and second, sprN_setview does not break\nin conjunction with certain unbound sprites. Additionally, SAMs\nwill not be converted to WAV files anymore (and are now natively\nsupported) and the robot debugger config is accessible from the\neditor.\n\nFEATURES\n\n+ The current MegaZeux version is now visible from the enter\n  menu on the title screen. This is to assist identifying the\n  version on platforms that don't respect window title changes\n  or have no window border.\n+ Updated checkres.bat and checkres documentation.\n+ The robot debugger configuration screen is now accessible from\n  the editor via Alt+F11.\n+ Removed SAM to WAV converter. MegaZeux now has native SAM\n  support. (asiekierka)\n+ Added crt-wave.frag, updated crt.frag. (astral)\n\nFIXES\n\n+ Fixed a bug where MegaZeux would crash when editing a robot\n  with an invalid IF command operator.\n+ Fixed a bug where the ternary operator would fail to find the\n  correct colon when the middle term contained a second ternary\n  operator in a nested expression.\n+ Nested ternary operators should now behave as expected.\n+ Fixed a bug where the built-in cursor would disappear after\n  exiting testing with EXIT_GAME.\n+ Fixed a bug where the listening mod would not restart after\n  exiting testing.\n+ Fixed a bug where the robot debugger would not wrap long lines\n  of Robotic code correctly.\n+ Attempting to save a world in a write-protected location\n  correctly displays an error message again.\n+ Fixed a bug where key repeat would not work when scrolling\n  through the counter debugger tree list.\n+ checkres will no longer report empty filenames as dependencies\n  e.g. SET \"\" TO \"FWRITE_OPEN\".\n+ The -q flag in checkres will now correctly output filenames\n  instead of nothing.\n+ Fixed a bug where sprN_setview would scroll the viewport to\n  the wrong side of the screen if the sprite is unbound and is\n  close to the top or left edge of the board. (Lancer-X)\n\nDEVELOPERS\n\n+ Fixed PSP and NDS ports. (asiekierka)\n\n\nJuly 25, 2017 - MZX 2.90c\n\nThis is a small fix release mostly cleaning up issues from the\nprevious release.\n\nNotable fixes include a crash on certain instances of string\ninterpolation into counters/labels/text. Playing mods as sound\neffects works again, and the audio_sample_rate config file\noption now works correctly.\n\nThis release also includes support for loading save files from\nMegaZeux 2.84X and experimental editor behavior for handling\nboard charsets and palettes.\n\nFEATURES\n\n+ Save files from 2.84X worlds can now be loaded.\n+ The editor can be configured to automatically load board\n  charsets and palettes now. Use the 'editor_load_board_assets'\n  config file option to enable this behavior. This behavior is\n  disabled by default. Note that this will OVERWRITE THE CURRENT\n  WORLD CHARSET AND PALETTE and your changes will NOT be saved\n  automatically.\n+ GLSL is now the default renderer. (Lancer-X)\n\nFIXES\n\n+ Fixed bug where --disable-modular builds would fail to link.\n+ Fixed bug with libxmp integration where mods could not be\n  played as sound effects.\n+ Fixed bug with libxmp integration where MZX would assume\n  audio_sample_rate was 44100, causing mods to play at incorrect\n  pitches with other rates.\n+ Fixed bug where editor config file options would be ignored at\n  the command line.\n+ Fixed bug where string interpolation into a label/counter name\n  could cause MZX to crash with very long strings.\n\n\nJuly 16, 2017 - MZX 2.90b\n\nIt's been two weeks, so here's a bugfix release for MZX 2.90.\nThere isn't a whole lot that's new to talk about.\n\nFirstly, the palette editor has gone through an overhaul. This\nis mostly internal preparation for an eventual SMZX palette\neditor, but the new features include the partial restoration of\nmouse input (which was removed in 2.80X), the addition of color\nsliders, the ability to hide the palette editor help, and two\nnew colorspaces (HSL and CIELAB) to aid in the selection of\npalette colors.\n\nNext, the robot debugger introduced in 2.90 has new features.\nYou can now set \"watchpoints\" to watch the status of particular\nvariables, and you can send labels/goto from the robot debugger.\nThe robot debugger config screen has been visually improved, and\nline number breakpoints can be defined. Another notable feature\nis that KEY_PRESSED and KEY_CODE values are now displayed in the\ndebug window during gameplay.\n\nlibxmp is now the default sound engine for modules, fixing a\nlongstanding bug where certain S3Ms would have muted channels.\nGDM modules are now supported by MZX again. If you notice any\ninaccuracies with mod files, please report them to the tracker.\n\nFixed bugs include a major overhaul of the glsl shaders for\ncompatibility with almost any system, various sprite, text box,\nand input bugs, a crash fix when MZMs were saved from out of\nbounds board locations, and a bug where opening worlds or\ntesting from the editor could cause MegaZeux to exit.\n\nFEATURES\n\n+ The \"shaders/extra\" folder is now \"shaders/scalers\". The\n  default vertex shader is still located in the shaders folder.\n+ The default scaling shader can now be set in the config file\n  using the gl_scaling_shader option. If not defined, MegaZeux\n  will load assets/shaders/scalers/semisoft.frag.\n+ The robot debugger now supports monitoring the values of\n  counters and strings. Use the config menu/breakpoint editor to\n  add watchpoints; when the debugger detects a change in a\n  watchpoint counter/string AFTER a command has been executed,\n  the robot debugger will open.\n+ The robot debugger can now send robots labels. Use 'G' or\n  select 'Goto' in the robot debugger to use this feature. The\n  name and label inputs support expression parsing and string/\n  counter interpolation.\n+ The robot debugger can now use line numbers in breakpoints.\n+ The palette editor help can now be hidden with Alt+H. The\n  default behavior for this can be set in the config file. The\n  palette editor help is visible by default.\n+ The palette editor's mouse functionality has been improved.\n  Behavior when clicking the palette has been restored, and\n  component sliders have been added.\n+ The palette editor now supports alternate color spaces.\n+ The debug window now displays the last key_code (green) and\n  key_pressed (magenta) values detected during gameplay. These\n  values are located in the bottom right corner of the window.\n+ The default fullscreen resolution for scalable renderers is\n  now 1280,720. The default fullscreen resolution for 'software'\n  is still 640,480. The default aspect ratio is now 'modern'.\n+ The checkres utility has been updated to support 2.90X worlds,\n  worlds in subdirectories of zip archives, multiple input files\n  to test, and the ability to specify secondary directories and\n  zips to search (e.g. for games with separate music zips).\n\nBUGFIXES\n\n+ Fixed a bug where certain \"keyN\" labels, such as \"key$\", would\n  not work correctly.\n+ Integer comparisons between numbers greater than 2^31-1 apart\n  now work correctly. (Lancer-X)\n+ The palette editor now displays the SMZX mode 1 palette\n  correctly.\n+ Sprite collisions now correctly ignore the sprite's visual\n  width and height.\n+ Blank lines are no longer skipped when copy-pasting text into\n  the robot editor. (Lancer-X)\n+ Fixed a bug where key presses in dialogs could be detected in\n  games, and keypresses could still carry between other dialogs.\n+ [ message boxes now use the same screen mode as everything\n  else. Same with scrolls. (Lancer-X)\n+ spr#_setview now works with unbound sprites. It's pretty nasty\n  as the viewport can't scroll with pixel precision, but the new\n  behavior should still be better than the old. (Lancer-X)\n+ Fixed a bug affecting certain Intel HD Graphics cards and the\n  GLSL renderer that appears to have been around since that\n  renderer was first introduced. (Lancer-X)\n+ Fixed a bug where the numeric numpad keys were inappropriately\n  translated to regular keys based on numlock when they were\n  read from KEY_PRESSED. This behavior is now locked to worlds\n  from 2.82X through 2.84X.\n+ Fixed a bug where warping the mouse on one axis would snap the\n  mouse to the nearest pixel on the other axis, causing problems\n  for upscaled windows.\n+ Fixed a bug where mouse control would not respect video_ratio\n  settings aside from \"stretch\".\n* Fixed a bug where MZX would attempt to load certain invalid\n  worlds that should have been caught by validation.\n+ Copy block to MZM no longer crashes when the MZM is partially\n  overlapping the board edge. (Lancer-X)\n+ Fixed bug where MegaZeux would sometimes exit immediately on\n  loading a game or would immediately exit testing.\n\nDEVELOPERS\n\n+ libxmp is now the default module sound engine. Use \"make xmp\"\n  to make and install libxmp if it is not available on your\n  platform.\n+ Mac OS X portability improved. (Spectere and Why-Fi)\n\n\nJune 29, 2017 - MZX 2.90\n\nHey, it's been a while! Didn't mean to keep everyone waiting.\nTo make up for it as much as possible, we've prepared an extra\nspecial release of MegaZeux here for you. Yeah, you!\n\nI don't even know where to start with this release, as there's\nsimply a lot to talk about. MZX's world formats and rendering\narchitecture have gone through major overhauls, and a multitude\nof bugs have been fixed (60+). Particularly the MZX UI graphical\nglitch that occurred when an SMZX mode was enabled.\n\nSome notable new features are unbound sprites (MZX's new native\npixel precision support), loading character sets/palettes/source\ncode/MZMs from strings, saving MZMs to strings, a new Robotic\ndebugger, the SAVE_COUNTERS and LOAD_COUNTERS function counters,\noptions to help create standalone game releases, the ability to\nassign character sets/palettes to a board to load on entry, and\nresetting boards on entry. The size limit of Robotic bytecode\nhas been increased to 2MB.\n\nOther important mentions: MegaZeux has been updated to use\nSDL 2.0 and libmodplug 0.8.9.0. The SAVE_WORLD function counter\nhas been permanently removed. The downver utility has been\ntemporarily discontinued, as adapting it to this release would\nhave been a considerable amount of work. Instead, there is a new\noption in the Export menu in the editor to export a 2.84 world.\n\nEnjoy, and try not to get overwhelmed by all of the new stuff!\n\nFEATURES\n\n+ Added a built-in robot debugger. See the Debug Mode section of\n  the help file for details.\n+ Added the COMMANDS_STOP special counter to MegaZeux. When a\n  robot executes a number of commands exceeding this value while\n  testing, the robot debugger will be enabled automatically and\n  will open. During regular gameplay, it will be ignored. The\n  default value of COMMANDS_STOP is 2000000.\n+ The counter debugger now remembers its previous position after\n  being reopened.\n+ Added commands_cycle and commands_total variables to the Robot\n  section of the counter debugger. See the Debug Mode section of\n  the help file for details.\n+ Certain types specified as 255 on the char ID table are now\n  treated as Custom* types when placed and selected in the\n  editor. If you switched a type to char ID 255 in a game that\n  already had that type, the parameters will not be changed\n  (and the types will still be char 0 if they were already\n  placed in the world). When making a type a Custom* type from\n  the global chars dialog, it will appear visually different in\n  the list and in the character selection window, and also ask\n  for confirmation.\n+ The enter menu can be closed with escape now.\n+ In the editor, the Backspace key now removes the top layer\n  and brings the under layer to the top, as a compliment to Del.\n  This does not affect the text entry behavior.\n+ Seeking in the String section of the counter debugger will now\n  ignore the $ prefix.\n+ Tentative joypad POV hat support added: joyNhat = U, D, L, R\n+ The BUTTONS counter has been extended to support the mouse\n  wheel and X1/X2 buttons.\n+ Strings can now be used in the LOAD PALETTE \"file\" command in\n  place of a file name, i.e. LOAD PALETTE \"$string+10\".\n+ Strings can now be used in the LOAD CHAR SET \"file\" command in\n  place of a file name, i.e. LOAD CHAR SET \"@240$chars\".\n+ Strings can now be used for the special counter LOAD_ROBOT in\n  place of a file name, i.e. set \"$str\" \"LOAD_ROBOT\".\n+ Strings can now be used for saving and loading MZMs in place\n  of a file name, i.e. put \"@$str\" image_file... (Lancer-X)\n+ Added ESCAPE_MENU counter. When set to 0, this counter will\n  prevent pressing escape from opening the exit gameplay menu.\n  This will not affect any other escape menus in MZX, and the\n  exit gameplay menu can still be opened by other means, such as\n  ALT+F4, CTRL+C, the window close button, etc.\n+ Added EXIT_GAME function counter. When set to anything but 0,\n  this will cause MegaZeux to exit to the title screen. This has\n  no effect on the title screen.\n+ Added the ternary operator (?:) to expressions. If the value\n  to the left of ? is not equal to zero, the expression between\n  the ? and the : will be evaluated. If the value to the left of\n  ? is equal to zero, the expression to the right of : will be\n  evaluated.\n+ Added SAVE_COUNTERS and LOAD_COUNTERS special counters. These\n  will save and load files containing every counter and string\n  from the world they're used in, respectively. These files are\n  not version checked, and may be used like save files.\n+ Robots now keep their robot IDs after reloading saves.\n+ Added standalone mode. The config parameters standalone_mode\n  and no_titlescreen can now be used (from MZXRun only) to\n  create standalone versions of MZX with the ability to fully\n  customise the player's experience. See config.txt for details.\n  (Lancer-X)\n+ The minimal help bar now displays robot memory and the current\n  board mod when contextually appropriate.\n+ The minimal help options for the board and robot editors are\n  now enabled by default.\n+ Shaders can now be changed from the F2 menu when using the\n  GLSL renderer. The user can select a fragment shader from the\n  assets/shaders/extra/ directory. If a matching vertex shader\n  exists, it will be loaded alongside the fragment shader.\n  Otherwise, MZX will fall back to the default vertex shader.\n+ On Linux and Mac OS X the configuration file will be copied\n  into the user's home directory and given the name\n  .megazeux-config if it is not already present. This config\n  file will then be used instead of the global one. (Lancer-X)\n+ Added unbound sprites. Sprites can be unbound from the grid by\n  setting spr#_unbound to 1. Their coordinates will now refer to\n  the sprite's location in pixels, not tiles. spr#_width,\n  height, refx and refy still refer to chars; however, spr#_x,\n  y, cx, cy, cwidth and cheight are all in pixels. Unbound\n  sprites do not work with all renderers; currently, out of the\n  renderers available on the PC platforms, the overlay2 renderer\n  lacks this functionality. (Lancer-X)\n+ Unbound sprites do not let char 32 or blank characters (when\n  in ccheck 2) through like regular sprites do. Instead, set\n  spr#_tcol to a color that will be transparent when the unbound\n  sprite is drawn. (Lancer-X)\n+ Unbound sprites can make use of additional hidden charsets.\n  There are an additional 14 charsets beyond the default that\n  can be modified through using load char set or char edit.\n  (e.g. load char set \"@256charset.chr\")\n  A sprite can be set to refer to these later chars with the\n  spr#_offset counter. The offset value is then added to each\n  char in the sprite. You can use this to refer to higher char\n  sets.\n  (e.g. set \"spr0_offset\" 256)\n  Offset values can also be set to locations within a char set.\n  (Lancer-X)\n+ To allow you to access the extended charsets, the @ option to\n  load char set now takes up to 4 digits, rather than merely 3.\n  The + option still only allows 2 hex digits. (Lancer-X)\n+ The colors used by tiles of a given color # in SMZX mode 3\n  can now be rebound using the \"smzx_idx#,#\" counters.\n  (e.g. set \"smzx_idx7,0\" 32)\n  Now if you put down a c07 tile, all 00 pixels will refer to\n  color 32 (as opposed to color 0 in SMZX mode 2 or color 7 in\n  SMZX mode 1). These values are reset whenever the SMZX mode is\n  changed. Once again, this is only available to renderers that\n  support unbound sprites. (Lancer-X)\n+ Edited worlds now retain their world version until resaved.\n  Before, they would lose their world version after testing.\n+ Added world version display to the debug window.\n+ Added GOOP_WALK counter for robots.\n+ Added \"Reset board on entry\" parameter for boards. When set,\n  the board will reset to its original state when the board is\n  entered during gameplay. This setting can be saved as a board\n  default.\n+ Added \"Load charset on entry\" parameter for boards. When set,\n  MegaZeux will load the selected charset file when the board\n  is entered during gameplay. This setting can be saved as a\n  board default.\n+ Added \"Load palette on entry\" parameter for boards. When set,\n  MegaZeux will load the selected palette file when the board\n  is entered during gameplay. This setting can be saved as a\n  board default.\n+ Maximum robot size has been increased from 64kb to 2mb.\n  (Lancer-X)\n\n+ Debytecode: the LOAD_SOURCE_FILE special counter can now be\n  used to load and compile a robot program from source code.\n  This can be used with either a file or a string as input.\n+ Debytecode: the robot editor's import robot menu now supports\n  loading legacy source code.\n\nBUGFIXES\n\n+ Fixed crash bug when using ALT+Z (Clear Board).\n+ Fixed crash bug when attempting to view the help file in the\n  updater.\n+ Fixed crash bug when attempting to use SMZX_R/G/B with out-of-\n  range indexes.\n+ Fixed crash on exit when the config file did not explicitly\n  specify update hosts.\n+ Fixed memory leak involving bounds-breaking FREADn calls.\n+ Fixed counter debugger issue where counters and strings\n  starting with chars over 'Z' would cause everything in their\n  section to be placed into the '#' list.\n+ Dialog window labels can not be put into focus by clicking\n  them with the mouse anymore.\n+ Dialog windows closed by pressing escape will now close only\n  on a new press instead of whenever the key is held.\n+ Load (F4)/quickload (F10) on invalid save files in-game leaves\n  the current game running instead of exiting to the titlescreen\n  or editor.\n+ Fixed issue where counter debugger would display wrong values\n  for the robot LOCAL counters.\n+ Fixed robot editor issue where global robot coords would be\n  reloaded as 65535.\n+ Fixed issue where board defaults would be reset after leaving\n  testing until the world was manually reloaded.\n+ Fixed issue where the Board Info dialog would not close when\n  'Cancel' was selected.\n+ Fixed bug where chars 254 and 255 would not be reverted back\n  to their defaults via F4/F5.\n+ Fixed bug where player would move onto goop when walking into\n  enemys on top of goop.\n+ Fixed bug where 'enter' would be detected in loaded save files\n  when loading from a title screen.\n+ Fixed issue where MZX was using the world file version instead\n  of the internal MZX version when saving MZMs and determining\n  whether it could load MZMs both within the editor and in game.\n+ Changed the caption behavior in the world editor as to refresh\n  only when board values are synchronized.\n+ Fixed bug where, when exiting gameplay, the world would keep\n  running on the title screen if MegaZeux lost read access to\n  the world file.\n+ Fixed minor bug where trying to load a nonexistant mod would\n  change MOD_NAME's output while the previous mod still played.\n+ Fixed a Windows-only bug, where attempting to load a Robotic\n  file in the Robot Editor or with LOAD_ROBOT would not load the\n  entire file if the file contained 0x1A (char 26).\n+ Fixed a bug where the lazer animation would fail to complete.\n+ Fixed bug where the VOLUME # and MOD FADE # # inputs could go\n  out of range, resulting in ear-piercing white noise. Inputs\n  now wrap between 0 and 255 for compatibility reasons.\n+ Fixed bug where certain unicode values could crash MZX in the\n  character selection dialog.\n+ Fixed bug where : \"keyN\" would not work on the Pandora.\n+ Fixed a bug where FREAD could create a string larger than the\n  maximum string size.\n+ In the sprite section of the counter debugger, the spr_clistN\n  values were incorrectly labelled as \"spr_collisionN\". Fixed.\n+ Fixed counter debugger crash when selecting certain strings\n  containing escaped values.\n+ Added compatibility for the cycle-ending SHOOT, SHOOTMISSILE,\n  SHOOTSEEKER, and SPITFIRE commands from MegaZeux 2.83.\n+ Fixed crash bug when attempting to use file counters with\n  paths equal to or longer than 512 chars in length. MegaZeux\n  will now ignore such paths.\n+ Fixed bug where MZX would crash with out-of-bounds joystick\n  key mappings.\n+ Fixed a bug where robots could not push the SliderNS type to\n  the south.\n+ Fixed a bug where a robot using #return or #top multiple times\n  within a single command could cause a crash.\n+ Fixed a port regression where IF ANY would continue to iterate\n  over the board after finding a match, potentially triggering\n  multiple subroutines, #return, or #top labels.\n+ Fixed a bug where MZM error messages could repeat indefinitely\n  and lock up MZX.\n+ Fixed a bug where the (#)PUSHED label would not be sent to the\n  first object in a row of pushed objects if that object was a\n  robot.\n+ Fixed support for PPC Linux builds. (Insidious)\n+ Fixed a bug where a robot sent a subroutine would restart the\n  command it was executing upon #return if the command was a\n  multi-cycle command. This fix applies to worlds saved in 2.90\n  and later only.\n+ Fixed a bug where ALT+F4 would cause menus to open instead of/\n  in addition to triggering an exit event.\n+ Fixed a bug where subdirectory mods with different types of\n  slashes in their names would fail to be recognized as the same\n  mod.\n+ Fixed a bug where SMZX palette intensities were not saved.\n+ Made the opengl2 and glsl renderers endian-correct. (Lancer-X)\n+ Fixed a bug where keystrokes were getting lost during event\n  processing. (Lancer-X)\n+ The UI now uses the regular MZX mode and the protected palette\n  when playing SMZX games. This is only available to renderers\n  that support unbound sprites. (Lancer-X)\n+ Changing from SMZX mode 1 to 0 correctly restores the palette.\n  (Lancer-X)\n+ Fixed a bug where the tree list in the counter debugger would\n  rapidly scroll through elements when clicked.\n+ Fixed a bug where the updater, on failing to receive data from\n  the server, would consume large amounts of CPU before timing\n  out. (Lancer-X)\n+ Fixed various bugs related to the pushing of sensors.\n  (Lancer-X)\n+ Fixed a bug where MZX would crash in some cases when testing\n  after adding/importing new boards into a world.\n+ Made KEY a board counter <=2.70. Also added masking <=2.62 to\n  make Oath Demo work again. (Lancer-X)\n+ Bullet types shot by robots clamped to 0-2. (Lancer-X)\n+ SAVE_GAME and SAVE_WORLD now happen at the end of a cycle, not\n  immediately. (Lancer-X)\n+ SAVE_WORLD no longer exists. (Lancer-X)\n+ Labels at the end of a program no longer get randomly sorted\n  first and stop the actual first instance of that label being\n  called. This fixes a bug in the Dark Corner 'Zane' demo.\n  (Lancer-X)\n+ Copy and copy block in pre-port MZX games now preserves the\n  state of any robots copied. (Lancer-X)\n+ Copying and pasting robot code with LF line endings on Windows\n  now works. (Lancer-X)\n+ MegaZeux can now be exited during an infinite loop. (Lancer-X)\n+ Fixed a bug where SAVE_GAME on the first cycle of a board\n  would cause the game to be faded out on load. This still\n  affects legacy worlds. (Lancer-X)\n+ Fixed a bug where trying to create new counters called \"fread\"\n  or \"fread_counter\" in the counter debugger would cause the\n  file being read to advance.\n+ Fixed a bug where the numpad was unable to be used to control\n  the player, despite being identical to the cursor keys for\n  every other situation. (Lancer-X)\n+ Fixed a bug where pre-port worlds could play any number of\n  concurrent samples. This makes portions of Bernard the Bard\n  quite cacophonous. (Lancer-X)\n+ Fixed a bug where per-game config files could set options they\n  weren't supposed to be able to.\n\nDEVELOPERS\n\n+ Fixed linking bug when attempting to make a modular build of\n  MegaZeux in Debian/armhf. -fPIC is now enabled for modular\n  builds on all platforms. (Insidious, ajs)\n+ Added SDL 2.0 support. At the moment, the overlay renderers\n  are not accelerated by SDL 2.0 and may be slower than their\n  SDL 1.2 counterparts. Other minor inconsistencies may exist\n  between the two versions. (ajs)\n+ Keycode help diagram replaced with an HTML file (from the\n  older PNG file). This will make future updates much easier.\n+ Updated NDS, Wii and PSP ports to latest devkitpro toolchains.\n+ Updated libmodplug to version 0.8.9.0. (asiekierka)\n+ Added experimental libxmp support. (asiekierka)\n+ Added experimental libopenmpt support. (asiekierka)\n+ New rendering architecture that mostly sits alongside the old\n  rendering architecture. There is a new function implemented by\n  supporting renderers: render_layer(). This provides a layer\n  in a similar form to the text_video array in graphics and\n  expects that to be drawn on the screen. This is called once\n  for each layer that is drawn- this means the board, overlay,\n  UI and sprites. Each layer can have a different SMZX mode,\n  although at the moment this is only used to allow the UI to\n  remain in regular MZX mode while the game or editor is in SMZX\n  mode. The render_layer() function is only called if it is\n  implemented by the renderer and if there is something on the\n  screen that can only be drawn by the renderer (e.g. unbound\n  sprites, or UI elements in an SMZX game) (Lancer-X)\n+ LibSDL2 support is now the default. It can still be disabled\n  by passing --disable-libsdl2 to config.sh. (Lancer-X)\n\n\nDecember 24, 2012 - MZX 2.84c\n\nHey all, new version of MegaZeux here! And hopefully without any\ncrashes this time around!  Haha yeah right.\n\nThe most notable thing this release (besides fixed crash bugs,\nas usual) is the new counter debugger! Give it a try and tell us\nwhat you think!\n\nCounter binary search has been replaced with a hash table in\nversions of MegaZeux for most platforms. This has resulted in\nslightly faster counter lookups, and MUCH faster creation of\ncounters, since MegaZeux doesn't have to keep the list ordered.\nThe hash table is turned off for NDS builds for now due to tight\nmemory constraints.\n\nOtherwise, most of the new features are editor enhancements and\nmodifications. Notable mentions are reordering the board list\nwith 'M' and a custom undo history size.\n\nFEATURES\n\n+ The functionality of the F11 counter/string debugger has been\n  expanded to include sprites, robots, and miscellaneous world\n  and board variables. The ability to modify many of these vars\n  is limited and a large portion are read-only. In addition, you\n  may add new counters and strings, hide empty counters/strings\n  (does not affect built-in variables), and search by name and\n  contents. Export is still limited to counters and strings.\n+ Max string length has been increased from 1MiB (1048576 bytes)\n  to 4MiB (4194304 bytes).\n+ Board mods may now be selected from subdirectories of the game\n  folder in the ALT+N dialog.\n+ The title bar now displays the world and editing board/robot\n  names based on context.\n+ Fire Burns Space and Fire Burns Forever are off by default.\n+ You may now specify the size of the undo history in the char\n  editor, and the history affects the entire char set. Undo is\n  still ALT+U, redo is now ALT+R.\n+ You may now use the -/+ keys in the world editor to move to\n  the previous/next boards in the board list, and Shift+Arrows\n  to change to the boards linked by board edge.\n+ You may now move the current board anywhere in the board list\n  with M. You may not move the title screen board.\n+ View mode in the board editor (V) will start from the current\n  location of the screen now rather than at the top-left corner.\n+ Config file options for default board settings added, as well\n  as the ability to save these per-world from the editor.\n+ Ctrl+G - Goto board position at X,Y.\n+ png2smzx now requires less arguments and has an option to skip\n  a char (generally char 32 will be useful to skip).\n\n+ Debytecode: The robotic editor now asks for a confirmation to\n  save the program on exit.\n\nBUGFIXES\n\n+ Fixed a bug in the 'glsl' renderer that caused the cursor\n  color to be ignored (always white). This caused the cursor to\n  not be visible if shown on a white background. (ajs)\n+ Setting $string.length will not cause memory corruption now.\n  In addition, its pre-2.84 behavior of intentionally allocating\n  the string past the set length has been restored, but the\n  length itself will still be set to the correct value.\n+ Added bounds check to INC \"$string\" \"value\". Attempts to\n  increase a string past its maximum length (4 MiB, or 4194304\n  bytes) will now fail.\n+ Fixed MegaZeux crash that could sometimes occur when a string\n  was increased by itself.\n+ Fixed MegaZeux crash that could occur when exiting the editor\n  to a world file that failed validation.\n+ Rolled string->storage_space into string->name to prevent\n  buffer overflow errors and crashes. (Mr_Alert)\n+ Fixed crash that would occur when attempting to type in a dir\n  in the ALT+N dialog.\n+ Fixed a bug where copy block $string would apply REL twice.\n+ Thanatos Insignia (DoZ Q1 2011, 98485) will now play without\n  freezing both normally AND after loading a save. Robots will\n  not be incorrectly versioned with the save format magic now.\n+ Fixed bug where ALT+M wouldn't always edit non-stored types.\n+ Fixed bug where ! could not be used as a substitute for ~.\n+ Fixed a bug where tab-draw and block actions would carry\n  between board and overlay editing. ALT+O now sets the drawing\n  mode back to normal on a switch.\n+ ALT+M now edits the overlay instead of the board while editing\n  the overlay.\n+ Quick-load (F10 during gameplay) will not work if the load\n  menu (F4) has been disabled with the LOAD_MENU counter.\n+ Fixed a bug where the listening mod directory would stay the\n  same as the directory the editor was opened in.\n+ The listening mod now continues to play after world and board\n  changes, and also after the board mod has been changed by\n  ALT+N or Shift+8/*.\n+ LOAD_GAME will not incorrectly trigger JUSTENTERED anymore.\n+ Palette/intensity changes made on the same cycle as a teleport\n  player command are now properly taken into account. This fixes\n  a game-stopping regression in Sponkgo's Legendary Journey\n  where leaving the second stage's bonus area would leave the\n  color intensity at 0.\n+ SMZX mode is now disabled when leaving the editor if it was\n  enabled in the editor via F11.\n+ Fixed potential crash bug where robot bytecode files were not\n  being validated before being loaded with LOAD_BC.\n+ Fixed minor message box bug dating back to DOS where a message\n  box starting with an unavailable option would begin with the\n  cursor on a blank line.\n+ Fixed crash that happened when typing over bounds while\n  renaming a file in a file manager dialog. File and directory\n  renaming now use a popup dialog akin to ALT+N.\n+ Files with names longer than 55 chars may now be selected in\n  file managers. The limit for typing in a file name is still 55\n  chars.  You may not enter a blank line as a file name anymore.\n+ Fixed segfault when attempting to read THIS_COLOR for the\n  global robot. It will now always return -1.\n\nDEVELOPERS\n\n+ Overhauled txt2hlp. Error messages now differentiate between\n  hyperlinks and labels and will take the display chars ~ and &\n  into account. Eventually, the goal should be to get rid of\n  txt2hlp altogether and have MZX load straight from the .txt\n  file.\n+ Added compile option to use uthash for counter/string lookups\n  as opposed to binary search. Ideally, this will vastly improve\n  lookup speeds for large numbers of counters/strings, but will\n  consume more memory. (thanks to Lancer-X for the modified\n  uthash header file)\n\n\nJune 20, 2012 - MZX 2.84b\n\nLess than three weeks after the last one, MegaZeux 2.84b is here a few weeks\nearly to fix a key regression regarding the use of REL commands with the\nCOPY x y dx dy command.  While several new features have been added, this\nrelease was mostly about rooting out as many crash bugs from MegaZeux as\npossible.  An official public beta release of debytecode has been pushed off\nonce again due to time constraints.\n\nMost notable as far as new features go, pressing 'E' while a game's  title\nscreen is running will now take you directly to that world in the editor. A\nblank new world may still be created with 'F8', and now with the 'N' key. The\nfunction key corresponding to the new 'E' functionality is 'F9'.\n\nA major new aspect of this version, which could be seen as a bugfix or as a\nnew feature, is MegaZeux's ability to validate the MZX, MZB, MZM, and SAV file\nformats. Validation has been rigorously tested and refined, and a comprehensive\nlist of most world files that fail any check is available on the MZX Wiki. File\nload crashes and force-quit \"Out of memory\" errors in these instances are nearly\na thing of the past.\n\nOne final major change regards the file manager dialogs (Load Save Game, etc).\nThese dialogs have been internally overhauled to avoid permanent directory\nchanges unless a valid world or save file has been loaded.  If any bugs are\nexperienced using these, they should be reported to the MZX Bug Tracker.\n\nFEATURES\n\n+ Pressing 'E' or 'F9' on a title screen will now open the current world for\n  editing.  Press 'N' or 'F8' to create a new world.\n+ Multiple hosts may now be defined in config.txt. Update attempts will\n  be carried out in the order they are defined.\n+ A startup path may be defined in config.txt (\"startup_path\").\n+ Specifying the backup filename with a directory, ex. \"backup/file\", will now\n  silently attempt to create the directory if it does not exist.\n+ MZM3 is now forward compatible (robots will be dummied out).\n\nBUGFIXES\n\n+ World validation has been strengthened, preventing disasterous loads of most\n  non-world files.  Mostly intact/valid worlds, such as HUNTDRAK.MZX, can be\n  loaded -- corrupt/missing boards will be replaced with blank boards, corrupt\n  robots will be replaced by robots with empty programs, and so on.  If more\n  robots/scrolls/signs are found on a board than their data suggests there\n  should be, the extras will be replaced with customblocks. Back up the version\n  of the world with errors in this instance, as the robots' code or scroll text\n  may still be salvageable from the world data.\n+ Fixed a long-standing memory corruption bug in the shoot command introduced\n  by the port.\n+ Mac OS X: MegaZeux now uses /Users/[username] as the default starting\n  directory. Apps previously would always start at the filesystem root. (ajs)\n+ Fixed dangerous crash-causing bug where the editor would not chdir back\n  to the correct directory after testing.\n+ MegaZeux, after saving a world to a new directory, now chdirs to the new\n  current world file's directory.\n+ Changed behavior where a failed save file load would fade the still-running\n  world out of focus. MegaZeux now leaves the world in focus.\n+ All failed world/save loads leave the current world running and in the same\n  working directory.\n+ When startup_file is defined as a directory at the command line, MZX chdirs\n  to the directory instead of attempting to open it as a world file.  In the\n  config file, the directory is pruned off since there's a new config option\n  for the startup path.\n+ Fixed regression where the rel commands would be ignored for board to board\n  copy x y dx dy.\n+ Updated MZX_SPEED in the counter debugger, where it was still getting clamped\n  from 1 to 9.\n+ COLOR FADE IN and COLOR FADE OUT (and all built-in uses of them) now correctly\n  respect any COLOR INTENSITY changes made before they are used.\n+ In file selection dialogs, when attempting to make a directory that already\n  exists with ALT+N, MZX now shows the correct file name and does not force the\n  user to quit MegaZeux.\n+ In file selection dialogs, when pressing ALT+D with a directory selected, MZX\n  now correctly prompts to delete the selected directory instead of a file.\n+ The option to import world files in the board editor now correctly takes the\n  world version into account.\n+ Savegame MZMs loaded into the editor now have their robots dummied out for\n  safety purposes.\n+ Fixed bug where INPUT STRING would not terminate lines longer than 71 chars\n  after clipping them.\n+ INPUT STRING and ASK do not allow either tabs (INPUT only) or line breaks\n  (both) anymore.\n+ Fixed bug where putting a scroll/sign in the editor buffer and then selecting\n  something else could cause a crash on leaving the editor.\n\n+ Temporary fix for MSVC bug where all window dialogs would freeze. (MZXGiant)\n+ Temporary fix: board block actions will not corrupt robot source code in DBC\n  anymore. (MZXGiant)\n\nDEVELOPERS\n\n+ Switched debian prereq. and darwin libpng12 to libpng, switched darwin and\n  default ldflags to use '--ldflags' instead of '--libs'.\n+ Darwin CC/CXX compiler can now be specified on the command line. (ajs)\n+ Cleaned up broken ifeq structure in darwin Makefile.in so ARCHes other than\n  i686 can be built.\n+ Updated MSVC dirent.h to the latest version (MZXGiant)\n+ Added updated MSVC dependencies. (MZXGiant)\n\n\n\nJune 1, 2012 - MZX 2.84\n\nThe first version of MegaZeux to be released in two and a half years, this\ntime with a vast number of bugfixes, several new features, and hopefully\nno new bugs.\n\nThere's a new port to the Pandora platform from Exophase. There are no\nbinaries for this platform yet, as ajs has not had time to set up the\ncross-compiler. Same goes for Android.\n\nAnother major (internal) change this time around is that Exophase's\nexperimental \"debytecode\" language modification has been merged. This still\nhas some major bugs open against it, and missing features, so we won't be\ndoing official releases yet. You can add support for this feature by\npassing \"--enable-debytecode\" to config.sh on all platforms.\n\nThanks to Terryn, Exophase and MZXGiant for their contributions and to\nLancer-X, Old-Sckool and Lachesis who reported and tracked the majority\nof bugs this time round.\n\nFEATURES\n\n+ Added experimental port to Pandora. See arch/pandora/README for more\n  information. (Exophase)\n+ Directories may now be opened with FREAD_OPEN.  This functionality\n  can be used in conjunction with FREAD_POS and set \"$str\" FREAD.\n  FREAD will set the string to \"\" when it has reached the end of the\n  file listing.\n+ MZX_SPEED can now be set up to 16 by a robot or from the F2 dialog\n  menu. (Lachesis)\n+ Numbers can now be temporarily backspaced past their minimum value\n  to make typing a new number more intuitive in dialogs. (Lachesis)\n+ Chars 0 and 255 can now be selected from the Edit Chars submenu of\n  the Global Info menu. Using char 255 on a kind that would have\n  previously denied it now gives a warning dialog. (Lachesis)\n+ New Counter: SPACELOCK represents the default space functionality for\n  the built-in player. Setting this counter to 0 disables it, allowing\n  the player to move as normal when space is pressed. Setting it back\n  to 1 enables it again. Defaults to 1. (Lachesis)\n+ New Counter: FREAD_DELIMITER allows you to change the terminating\n  char for the string FREAD function.  The terminating char still\n  defaults to '*'. A complementary FWRITE_DELIMITER function has been\n  added as well. (Lachesis)\n+ New Counter: ARCTANdy,dx takes two values and returns the angle\n  with corrected quadrants as an alternative to using ATANdy with\n  DIVIDER as dx, which was less intuitive and never documented\n  properly. (Lachesis)\n+ New Counters: MINv1,v2 and MAXv1,v2 return the minimum or maximum\n  value between two inputs, respectively. Chain several of these in\n  an expression or a loop for more arguments. (Lachesis)\n+ New Counters: bchX,Y; bcoX,Y; bidX,Y; and bprX,Y are new shorthand\n  access counters for BOARD_CHAR, BOARD_COLOR, BOARD_ID, and\n  BOARD_PARAM.  The same limitations to those counters apply to the\n  new ones. (Lachesis)\n+ New Counters: uchX,Y; ucoX,Y; uidX,Y; and uprX,Y are new shorthand\n  access counters for the board's under layer.  The same limitations\n  apply to these as to their board counterparts.  Additionally, these\n  counters will fail if the same spot on the normal board is occupied\n  by a floor-type (space, [dir]water, lava, fake, etc...). (Lachesis)\n+ New Counters: ochX,Y and ocoX,Y are new shorthand access counters\n  for the overlay.  Like OVERLAY_CHAR and OVERLAY_COLOR, these are\n  read-only to discourage the user from writing to these instead of\n  using the much faster put [color] [char] overlay [x] [y]. (Lachesis)\n+ Pressing ALT+G from the world editor now goes directly to the Global\n  Robot without having to skip through the Global Info menu. (Lachesis)\n+ MZM3 has been enabled for 2.84 and all following versions.  The\n  difference between MZM3 and MZM2 is that MZM3 stores a copy of the\n  world version, allowing the robot format to change. (Lachesis)\n+ COPY can now take + and # prefixes to its arguments. COPY BLOCK and\n  COPY OVERLAY BLOCK can now take + prefixes to their first set of\n  coordinates. (Lachesis)\n+ Subroutine versions of TOUCH, BOMBED, INVINCO, PUSHED, PLAYERSHOT,\n  NEUTRALSHOT, ENEMYSHOT, SHOT, PLAYERHIT, LAZER, SPITFIRE, GOOPTOUCHED,\n  PLAYERHURT, KEY[char], KEYENTER, THUD, and EDGE have been added. These\n  should ALWAYS be used in conjunction with LOCKSELF/ZAP and #RETURN or\n  #TOP to keep the robot stack under control.  Please remember that THUD\n  and EDGE ignore LOCKSELF and their subroutine versions must be ZAPped.\n  JUSTENTERED, JUSTLOADED, and the sensor labels have been excluded from\n  this due to various reasons. (Lachesis)\n\nBUGFIXES\n\n+ Fixed a bug where LOAD_ROBOT or LOAD_BC would not reset the stack\n  pointer for newly loaded programs. This could cause crashes if a robot\n  popped the stack in the new program.\n+ Fixed a bug where range checking of BOARD_X and BOARD_Y would sometimes\n  not be done correctly, leading to crashes.\n+ Strings in the debug menu list no longer interpret any color codes they\n  may contain.\n+ Fixed a bug where a string would not be interpreted correctly if it\n  used a '.' character in a splice parameter expression. Expressions such\n  as IF \"$str#('$str2.length'-4)\" = \"blah\" THEN \"label\" will now work\n  correctly.\n+ Fixed negative sprN_cheight et al from crashing. (Exophase)\n+ Placement of objects on the player will be blocked with an error dialog\n  like DOS versions, instead of silently failing after setup. (MZXGiant)\n+ Fixed a bug where LOAD_ROBOT would not properly parse lines of imported\n  code that had leading whitespace. (MZXGiant)\n+ Fixed an integer wrapping bug in debytecode, disallowed numeric literals\n  outside of the bounds of a signed short. (MZXGiant)\n+ Fixed a bug that would corrupt the UI palette if \"set color\" was run\n  against a color index over 15. (MZXGiant)\n+ If either board dimension is less than the editor viewport, the character\n  and colors used to indicate space outside of the board are taken from\n  protected sets. In game, they are taken from the game's sets.\n+ Increase limit on difference for RANDOM \"A\" TO \"B\" to UINT_MAX rather\n  than INT_MAX - 1 as it was previously. Since the entire range represented\n  by a counter is now usable, there are no cases where RANDOM will \"break\".\n+ Fix avalanche rings and potions to limit boulder placement to 1/18,\n  matching the AVALANCHE command.\n+ Fix corruption and possible crashes when using VIEWPORT SIZE to set the\n  viewport to a size less than 80x25 but greater than the current board\n  dimensions. The viewport will now always be clamped to board size.\n+ Fix incorrectly changing horizontal mouse position on setting MOUSEY.\n  (Mr_Alert)\n+ Fixed overflow into protected character set when \"Revert to...\" is\n  selected in the character editing dialog. (MZXGiant)\n+ Music and SFX now mute when the updater launches and are restored when\n  it is complete. (MZXGiant)\n+ Fix odd string behaviour when copying between strings that happen to\n  be stored close to each other in memory. This fixes a regression\n  introduced by the \"crash when pasting to and from the same string\"\n  fix in 2.82b.\n+ The size/offset parameters for strings can now be specified in either\n  order (#+ vs +#) and will behave correctly.\n+ Revert bogus cycle-ending behaviour for SHOOT, SHOOTMISSILE,\n  SHOOTSEEKER and SPITFIRE.\n+ Improve cycle-ending compatibility with MZX versions prior to 2.80.\n  Fixes games such as Kya's Sword and Stones & Roks II.\n+ Restored shark's ability to move in goop.\n+ Clear SPR_YORDER upon loading a new world.\n+ When transitioning between boards, compare the module filenames of the\n  source and destination boards case-insensitively. A difference in case\n  will no longer cause the board module to be incorrectly restarted.\n+ Fixed rare rendering corruption in the load game dialog.\n+ Fixed a bug where SEND \"robot\" TO \"#return\" could corrupt the program\n  counter of the target robot if it had a stack pointer of zero.\n+ Fixed some security issues with SMZX_PALETTE and LOAD_BC counters.\n+ Only list/open regular files or symlinks to regular files in file\n  dialogs. Special files are now ignored.\n+ Opening directories with FWRITE_OPEN is now rejected properly on all\n  platforms.\n+ Fixed a bug where a file would be re-opened for read/write, even if\n  the file was missing or an I/O error occurred before saving.\n+ Fixed a bug where MZX could occasionally crash due to label list\n  corruption when copying robots from heap locations greater than\n  2^32 bytes apart (only affected 64bit builds).\n+ Stopped SET \"var\" <command> from assembling. Some invalid uses of\n  command tokens were already being ignored, but this was just luck.\n+ Sprites with color c?? (inherited \"natural\" colors) will correctly\n  inherit the colors of special characters such as the player and other\n  self-colored built-ins.\n+ Debytecode's legacy expression converter should use is_string()\n  instead of its own (buggy) hand-rolled version. Fixes crash when\n  converting CoAZ.\n+ The editor no longer incorrectly clamps the intelligence of sharks,\n  spitting tigers and spiders to <=4, and no longer clamps the HP of\n  dragons to <=4. (Lancer-X)\n+ Accept SET EDGE COLOR \"string\" in addition to SET EDGE COLOR c??.\n+ Fixed NDS port initialization on DSi devices. (asiekierka)\n+ Fixed a bug in the joystick code where centering an axis clears\n  the previous axis button. (iamgreaser)\n+ Fixed a bug that allowed vlayer->board COPY BLOCK to overwrite\n  player. Blocks that would overwrite the player are now ignored.\n+ Fixed a 2.81e regression that allowed SENDs to self to ignore\n  LOCKSELF.\n+ Setting $str.length now makes $str the length specified. (Lachesis)\n+ Caps Lock no longer interferes with dialog box text input. (Lachesis)\n+ Increasing the size of a string with $str.length, $str.N, or with\n  a splice now wipes old string data with char 32s.\n+ Dialogs (especially \"Exit gameplay - Are you sure?\") now require the\n  user to have actually hit ESC to close, making escaping busyloops\n  and message loops much easier. (Lachesis)\n+ Added a compatibility fix for different label caching in 2.80 through\n  2.83 that allowed constructs such as : \"LABEL\" / SEND \"ALL\" \"LABEL\"\n  on an unlocked robot to continue instead of getting caught in a loop.\n  The altered label caching caused #98485 Thanatos Insignia to lock up\n  in an unescapable busyloop in GIT versions. (Lachesis)\n+ MOD \"[lead-in file]*\" now works properly. In previous versions of the\n  port, this construct would result in the lead-in file failing to play\n  and the wildcard mod restarting when the player re-entered and re-\n  exited the board. (Lachesis)\n+ Fixed a bug where mod \"*\" would cause the mod to restart when entering\n  another board with the same playing mod. (Lachesis)\n+ ALT+D (Default palette) now requires a confirmation. (Lachesis)\n+ The COMMANDS counter is now saved as a 32-bit variable. (Lachesis)\n+ The LOOPCOUNT counter has been moved to save-only data and is now\n  saved as a 32-bit variable. (Lachesis)\n+ Fixed a bug where not all whirlpools were being considered as such,\n  notably during the transport board scan. (Lachesis)\n+ The abilities of PLAY \"&file&\" to play at multiple frequencies and\n  to parse multiple files have been restored. (Lachesis)\n+ As of MZX 2.84, BOARD_COLOR will now ignore the under color of any\n  object with a BG color of 0 that is on top of something.  Worlds that\n  relied on this between 2.80 and 2.83 are unaffected. (Lachesis)\n+ Setting BIMESG to 0 will now disable Game Over's auto-centering of the\n  message row. (Lachesis)\n+ The DIVIDER counter's documentation has been updated to explain its\n  true purpose.\n+ Fixed an editor bug where canceling a world load could cause MZX to\n  forget the filename of the current world. (Lachesis)\n+ Pressing ALT+M in the world editor now edits anything with parameters,\n  not just Robots, Signs/Scrolls, and Sensors.\n\nDEVELOPERS\n\n+ Source tarballs are now generated in XZ (LZMA2) format.\n+ Make hlp2txt utility work correctly on Windows platforms.\n+ Updated and repaired MSVC project for Visual Studio 2010. (MZXGiant)\n+ Win32 binaries are built ASLR-capable (via pefix).\n+ Version control was changed from SVN to Git; the repo is at:\n  http://github.com/ajs1984/megazeux\n+ The EGL backend now supports Mesa's EGL implementation on X11.\n+ Imported libmodplug 0.8.8.4 and rebased all patches.\n+ Introduced SOCKS4/4a/5 support transparently into the network layer.\n  (MZXGiant)\n\nDecember 29, 2009 - MZX 2.83\n\nIt's been a year since the last release, due in part to my reduced free\ntime, and less contribution from other developers in 2009. I'd also like\nto believe that 2.82b was such a good release, there was no need to rush.\n\nThere's over 30 bugs fixed this time. A few features I had to withhold for\n2.82b have been added; sample loop markers and some changes to the board\nfile format necessitated the bump to 2.83. Logicow's GLSL renderer has\nfinally made it in (various bits had to be re-written to extend\nportability to other platforms).\n\nThere's a new (semi-complete) Android port this time; I hope to complete\nit, and provide binaries for Android 2.0 phones, in 2.83b.\n\nThanks go out to the usual suspects -- Terryn, Mr_Alert, Logicow, kvance,\nLancer-X, revvy and Exophase -- for supporting development this year.\n\nUSERS\n\n+ Added support for loop markers in WAV and OGG files. The WAV loop support uses\n  the \"smpl\" chunk used by ModPlug Tracker and Wavosaur among others. Only the\n  first loop is used, and only forward looping is supported. The OGG loop\n  support uses the \"LOOPSTART\" and \"LOOPLENGTH\" tags as used by RPG Maker VX.\n  (Mr_Alert)\n+ Added OpenGL Shader Language (glsl) renderer which uses shaders to render\n  and scale the video. This renderer is compatible with Open GL >=2.0 and\n  Open GL-ES 2.0 video cards only. A variety of shader programs have been\n  provided and these can be customized. Performance of all MZX modes\n  (including SMZX) is excellent. (Logicow, ajs)\n+ Files will no longer be silently overwritten by save dialogs if the user\n  enters an existing filename without the default extension. (revvy)\n+ The string editor in the counter debug menu (F11) now escapes newlines\n  and backslashes to prevent UI corruption.\n+ Fix a bug where the LOAD_GAME counter handler could continue to use the\n  old board state after load, causing crashes. (Lancer-X)\n+ Fixed a bug where status counters containing numbers >6 characters would\n  cause MegaZeux to crash or behave strangely.\n+ Fix a bug in the robot editor's find/replace function that caused crashes\n  when replacing a string with another longer string, with a replacement at\n  the end of a line.\n+ Programmatically writing to a read-only \"built-in\" counter will no longer\n  allocate it general heap space. This prevents writes from showing up in\n  the F11 counter debugger that are inaccessible from robotic.\n+ Fix a bug that caused the SMZX mode 3 palette to become corrupted upon\n  entering the char editor (the editor would re-write colours 2-4 and not\n  restore them from backup correctly).\n+ Fix a bug where a robot program would never progress if the subroutine\n  stack was popped more times than it was pushed (via return or top).\n+ On UNIX platforms a desktop/menu entry is now installed by default, using\n  the existing icon. (Sci-freak)\n+ Clamp score to >= 0 if world <= 2.70. Fixes \"Gates: The Puzzles\" and\n  possibly other old titles depending on this behavior. (Exophase)\n+ Fullscreen modes will now automatically use your current desktop resolution\n  if using any hardware renderer (i.e. not the default software renderer). To\n  get the old behaviour back you must set fullscreen_resolution explicitly.\n+ Fix a bug where web and thick web would be treated the same.\n+ Fixed a bug in the updater where modified/replaced files would be considered\n  for deletion.\n+ Fixed a bug on case-sensitive filesystems where saving a game, world or MZM\n  could fail to overwrite any existing file by the same name (if matched\n  case insensitively).\n+ Do not apply masking to chars 32-127 in signs or scrolls when playing a\n  world. Previously, even the mask_midchars option would have no effect on\n  the display of signs or scrolls. This has been broken since 2.80g.\n+ Re-work board editor's Alt+H option to provide minimal editor (one row)\n  status info, rather than completely hiding the help.\n+ The checkres utility now checks the global robot and custom sfx tables\n  for missing resources.\n+ Chests can be added with Hi Bombs (omission noted by zzo38).\n+ Fix IF c?? Sprite p?? # # \"label\" so that a non-wildcard parameter is\n  respected (previously it would always just check sprite 0).\n+ NDS port updated from dsmzx2 release. (kvance, ajs)\n+ Updated SDL to 1.2.14 in Windows x86, Windows x64 and Mac OS X builds.\n+ Security checks are no longer applied to filenames in module or sample\n  playback in \"listening only\" modes in the editor.\n+ Module volume is applied immediately before playback upon switching boards.\n  This prevents one cycle of audio \"leaking\" at the wrong volume.\n+ Prevent crash with negative string clip where clip + offset = 0. Clip is\n  now correctly limited to total string length.\n+ Help file is now optional for MZXRun, even with CONFIG_HELPSYS=1 builds.\n+ Fix crash when robot editor macros expanded other macros.\n+ The lock icon is no longer missing from the Items THING menu (F4).\n+ A world to start up with can now be passed to megazeux without the\n  startup_file= prefix. This makes megazeux consistent with other\n  applications.\n+ SHOOT, SHOOTMISSILE, SHOOTSEEKER and SPITFIRE now end the cycle, to restore\n  compatibility with pre-port MZX and fix games such as Kya's Sword and\n  Stones & Roks II.\n+ Have IF [dir] PLAYER [color] [thing] [param] \"label\" interpret SEEK\n  direction wrt robot coordinates, rather than player coordinates.\n  Other directions are not affected.\n+ Zapping a label at the end of a robot program will no longer corrupt the\n  robot list, usually causing crashes.\n+ Entering lines in the robot editor with leading or trailing spaces will be\n  trimmed before the line is compiled.\n+ The single quote characters encasing S_CHARACTER parameters in the robot\n  editor will now use the protected (GUI) charset rather than the game one.\n+ Add a \"system_mouse\" config.txt option that allows the mouse cursor to be\n  replaced with the system mouse cursor, rather than being drawn by MegaZeux.\n+ Disallow placing player clones with SET \"board_id\" 127.\n+ Relaxed file name limit on board MOD file. The board MOD can now be as long\n  as the limit imposed by file dialog's input box (previously limited to 12\n  characters).\n+ Truncation of currently open input/output file names will now only occur\n  at MAX_PATH bytes (typically 512 characters). The previous limit was 13\n  characters.\n+ Relaxed limit of INPUT string and bottom (\"*\") messages from 80 characters\n  to ROBOT_MAX_TR (512) characters. In the case of bottom messages this can\n  be usefully exploited to ~200 characters.\n+ Progress meter will be shown for world decrypt on console platforms.\n+ Fix a bug where a malformatted BMP header would be written (length too\n  short, didn't include dummy channel in BMP palette). (Mr_Alert)\n+ Optimize audio locking; do file I/O outside of critical sections to\n  decrease stalling, particularly on platforms with slow I/O. (Mr_Alert)\n+ Added support for MacOS 10.6 (Snow Leopard) and removed support for 10.3.\n+ Loading a save game from robotic will now correctly restore intensities\n  to their saved values.\n+ Copy/pasting a block either with COPY BLOCK or the editor, where the\n  copy would exceed the limit on robots/signs/scrolls/sensors, will no\n  longer place junk at the target co-ordinates. Instead, the object's\n  background will be copied in isolation.\n+ Pasting from the clipboard, expanding a macro,  or importing .txt\n  or .bc files that would cause a robot to exceed the 64k limit now has\n  the operation ignored at the point it exceeds the limit, rather than\n  adding an unlimited number of unrecognized lines.\n+ Fix a bug with the pc_speaker_on option which could cause the audio\n  thread to block indefinitely when PC speaker audio was disabled.\n\nDEVELOPERS\n\n+ MZXRun compilation can now be disabled. Compilation of pre-2.82b style\n  non-modular builds requires `--disable-modular --disable-mzxrun'.\n+ Disabling SDL can now be done with --disable-sdl and the resulting\n  configuration will automatically disable any SDL-dependent components.\n  This is useless to anybody except developers doing new ports.\n+ Game directory, utility directory and resource directories can now\n  be specified and will be respected on \"make install\". (Sci-freak, ajs)\n+ Added experimental port to Android. See arch/android/README for more\n  information.\n+ Ported opengl1 and opengl2 renderers to OpenGL ES 1.x and glsl renderer\n  to OpenGL ES 2.0, used increasingly by mobile devices.\n+ Removed SDL dependency from NDS port. It only used it for timing and\n  stuffed events.\n+ On Windows platforms, binaries are processed with the `pefix' in-tree\n  tool to eliminate data section differences in programs with identical\n  texts. This minimizes the amount of content required to be sent for\n  updates.\n+ Updated Wii port: improved audio and video support, added USB mouse\n  support, numerous optimizations and improved file selector. (Mr_Alert)\n\nDecember 29, 2008 - MZX 2.82b\n\nThis release contains plenty of important bug fixes, ranging from regressions\nsuch as the broken command-line editor macro expansion to third party bugs\nlike the Windows \"directx\" SDL video driver breakage.\n\nThere are also some new ports and features. MegaZeux now runs on the Wii (port\nby Mr_Alert) and AmigaOS 4.x (port by myself and Spot from os4depot). The\nWindows x64 port has matured immensely and can now be considered stable.\nMacOS X builds now have clipboard support. The hardware stretching renderers\nnow have a couple of fixed aspect ratio modes.\n\nThe biggest feature of this release is the introduction of a portable network\nlayer, which is currently being tested by the new built-in updater (F7/U).\n\nInternally, MegaZeux is now modularized and builds as several DLL and EXE\nfiles, which should make redesigning parts like the help system and editor\na little easier, as well as allowing us to ship a \"mzxrun\" executable for\nthe first time since 2.69c. This \"mzxrun\" executable is now used by a\nmajority of the console ports.\n\nContributions from Revvy, Mr_Alert, Terryn and Exophase have helped make\nthis another solid release.\n\nUSERS\n\n+ Writing to $str.length (which previously did undefined things) will now\n  truncate or enlarge the string to the size specified.\n+ Removed filename size limit for FWRITE_MODIFY and FWRITE_APPEND. (Revvy)\n+ Added support to the checkres tool to check worlds in non-local\n  directories. (Revvy)\n+ Fix an old bug with saving games and worlds from Robotic where a board could\n  be prematurely \"optimized\", renumbering robot IDs within the same cycle. For\n  commands like DIE this could cause unpredictable behaviour or simply crashes\n  (if invoked in the same cycle). As a special case, end the cycle if either\n  of these SET specials are used.\n+ Mistaken or malicious file I/O such as set \"$test\" to \"fread(-1>>1)\" will\n  no longer crash MegaZeux. The read size will be truncated to a contextual\n  maximum for the current file.\n+ Fixed a crash using \"fwrite0\" in conjunction with an empty string.\n+ Fixed a bug where checking sprite_collisions on a disabled target sprite\n  would unconditionally trigger (regardless of whether a collision was\n  present or not).\n+ Un-group the handling of the KEY and KEYn counters so that different\n  compatibility checks can be applied to either counter. Fixes\n  \"Bocco Chronicles 1\" and probably several other titles.\n+ Fix a crash when using RIDn or ROBOT_ID_n in the same cycle as DIE for\n  another robot positioned earlier in the board scan.\n+ Fix poor sanity checks on BOARD_ID counter writes. Illegal character IDs such\n  as -1 can no longer be used to bypass the check (causing subsequent crashes).\n+ Windows builds now use a patched version of SDL 1.2.13 containing a fix\n  for the directx+F10 issue.\n+ Fix a bug where one robot could send another robot to \"#return\", with an\n  address outside its program. In such cases, the robot will now terminate.\n+ A \"mzxrun\" binary is now shipped alongside the editor-capable MZX binary.\n+ Fix TIME/TIMERESET overflows with very large values. Board timeout is now\n  programmatically limited to 32767, which is consistent with the Board Info\n  control.\n+ Clamp CHAR_X/CHAR_Y properly so that negative numbers can no longer be used\n  to corrupt the editor charset and potentially other process memory.\n+ Fix recent breakage of SHIFT+F{1,2,3,4} so that the percentage time spent\n  displaying the original character and the '!' are equal.\n+ Remove some bogus handling of lines containing \"only\" ';', ',' or ' '.\n+ Honor user's robot character selection if they are holding shift when\n  pressing return or space (would previously always return char 247).\n+ Backspacing a line and then expanding a macro no longer restores the\n  original line contents immediately after the expansion.\n+ MacOS 10.x clipboard support (via Cocoa Pasteboard). Alt+Ins can be entered\n  with Fn+Alt+Numpad0 on a Macbook or Powerbook keyboard.\n+ Rendering glitches are no longer encountered when using the ' S_CHARACTER.\n+ Fix robot editor glitches where the game charset SPACE would be used in\n  places where the protected UI charset should be used instead.\n+ The introductory help message is displayed if the load dialog is cancelled\n  prior to loading a game. Hopefully the screen is now never totally blank.\n+ The F7/F8 cheats can now be used freely in MZXRun (in MegaZeux proper they\n  remain usable only in editor tests).\n+ Saving to a directory above the MegaZeux startup directory, then attempting\n  to save to this location again, will no longer crash MegaZeux. Instead, the\n  parent directory will be changed into before the dialog is displayed.\n+ Fixed numerous crash bugs with the scroll editor; it should be relatively\n  usable now.\n+ Writing to $str+0 is no longer interpreted in the same way as a plain\n  write to $str. Instead, it behaves like writes to non-zero offsets (as more\n  of a paste than a replace).\n+ Display current X,Y position of robot in the robot editor status bar.\n+ Fix directory rename so that it no longer displays garbage and/or crashes\n  MegaZeux (Alt+R to rename a directory in any file picker).\n+ In the robot editor, lines can now be split at a midpoint with enter and\n  two consecutive lines merged together with backspace. (Exophase)\n+ Fix use of status counter 6 and display of status counters in general,\n  which has been broken since 2.80.\n+ Fix swapping to encrypted worlds if initially the user decides to not\n  decrypt the world. Previously, this would either crash, or loop forever.\n- Removed the legacy \"force_resolution\" option which was replaced long ago\n  by the more accurate \"fullscreen_resolution\" option.\n+ When using the OpenGL or overlay renderers, in either windowed or\n  fullscreen mode, the aspect ratio can now be preserved as either 4:3\n  (most similar to DOS) or 64:35 (most similar to the port). The display\n  will be letterboxed or margins applied as appropriate. See the\n  \"video_ratio\" configuration option for more information.\n+ Fixed a bug on some systems where numlock could not be used as a key,\n  only as a flag. The numlock \"key\" is now masked out of \"key_code\" and\n  similar; hopefully this won't break any games.\n+ Restored the meter widget from the old DOS MZX for use with the world\n  loader and saver routines. This reassures users, especially on consoles,\n  where loading a world can take a long time. (Mr_Alert)\n+ On Windows, directx.bat now passes %cd% through to `start' so that features\n  such as the updater continue to work. (MZXGiant)\n+ Pasting into a string with set \"$str+N\" with an N > str.length will no\n  longer crash MZX.\n+ \"Exit to DOS\" is now \"Exit MegaZeux\" to reflect the multi-platform nature\n  of the program.\n+ Setting a substring size to zero with $string#0 will no longer return the\n  whole string; it will instead return the empty string.\n+ Accessing a substring with an offset >= $string.length will no longer return\n  the last character from the string; it will instead return the empty string.\n+ Writing beyond MAX_STRING_LEN (1MB) or using negative offsets (which has the\n  same effect) no longer crashes. Instead, the write is ignored.\n+ Fix crash when pasting to and from the same string, specifically in\n  conjunction with $str+offset.\n+ Fix bug where altering \"num\" in a GO DIR [num] or WAIT [num] while in\n  process could cause the robot to stall forever. The robot will now only\n  wait for either the cycles it has waited already, or the current \"num\"\n  at that cycle.\n\nDEVELOPERS\n\n+ Ported to OpenSolaris. You need to install `SUNWxorg-headers' if you want\n  X11 clipboard support.\n+ Remove dependency on SDL_image on non-win32 platforms when enabling\n  the icon branding feature (see pngops.c).\n+ Ported to AmigaOS. You need to install the clib2 version of libSDL and\n  miniGL, and the build system assumes you are using a cross compiler.\n+ Added experimental port to the Wii. See arch/wii/README for more\n  information. (Mr_Alert)\n+ get_path() in util.c now returns <0 for failure, or the length of the path\n  for the given file. (Revvy)\n+ Add a valgrind.supp file to suppress bugs in third party libraries when\n  valgrinding MegaZeux.\n+ Cleaned up all the ports and documented making new ports. The platforms\n  \"linux\", \"solaris\" and \"obsd\" are now called \"unix\" and the \"linux-static\"\n  platform is now \"unix-devel\" and available on all UNIX derivatives/clones.\n+ Add a special hack to enable linking with --as-needed for DT_NEEDED link\n  optimization for GNU ld platforms.\n+ Updated MSVC projects. Fixed all warnings emitted by MSVC 2008, and\n  implemented icon support with existing mingw resource files.\n+ Use the GNU ld \"debuglink\" feature on all platforms to enable shipping of\n  a side-by-side symbol package. Optimized release builds can now be\n  debugged with minimal user effort.\n- Removed HOST_CC feature for cross compilation; since the utilities now\n  intimately depend on the MZX runtime, they must be built with the same\n  compiler.\n+ MegaZeux now provides the option for \"modular\" linkage, factoring out the\n  \"core\", \"editor\" and \"network\" features to shared objects that other\n  binaries can link against. This feature works on the unix, mingw,\n  amiga and darwin ports.\n+ Added RPM .spec file. Capable of building (at least) Fedora 10 RPMs.\n\nJune 10, 2008 - MZX 2.82\n\nDespite the increase in minor version, this release mostly targets bug and\nregression fixes. However, there ARE some additional new features, such as\nthe introduction of the LOAD_MENU and mouse pixel counters, and refinement\nof the {FREAD,FWRITE}_COUNTER counter. (There are several other smaller\nfeatures that are documented in the changelog.)\n\nSAVs from older worlds (requiring compatibility hacks) no longer fail to\nplay (Darkness, etc. are affected). We've also done a good bit to fix\ncompatibility with 2.70 and older.\n\nA new tool, \"checkres\", is now routinely packaged, allowing you to check\nyour games for missing resource files (PALs, CHRs, etc.) before passing\nthem on to other people. This should be especially handy for DoZ game\nsubmissions.\n\nThe Nintendo DS port (Kevin Vance's \"DsMZX\") has been merged into this\nrelease. I'll provide binaries for GP2X, PSP and NDS this time, but I\ncan't guarantee they'll work.\n\nBoth of the snags from the last DoZ have been addressed -- the help system\nshould no longer crash and the Block Action crashes should be reduced in\nfrequency. However, there are still issues with pasting in the robot editor\nthat remain unfixed (they're just really hard to reproduce). With your bug\nreports, I look forward to fixing this.\n\nAs usual, thanks go out to Revvy and Mr_Alert for their contributions to\nthe bug-fixing effort, and to Terryn for his unwavering dedication to\ncreating and organising bug reports, and for testing our bug fixes.\n\nUSERS\n\n+ Fixed and improved quality of the half-width renderer for the GP2X port\n  (Mr_Alert).\n+ Have the numpad work correctly when numlock is disabled. Keys are no longer\n  ignored by the MZX editor, and games should recognize them as before.\n+ Added a tool, \"checkres\", which extracts all resources from a MegaZeux\n  world or board file and lists them (or lists only those which are not\n  found in the game directory. ZIP files are also supported (to a more\n  limited extent) (ajs & Revvy, ideas from Exophase & Terryn).\n+ Removed the bogus \"F1 for Help\" option from error dialogs, and finally\n  get rid of the \"** BETA **\" banner on title boards in play mode.\n+ Obsolete support for the AMS, DBM, DMF, MDL, MT2, PSM, PTM and UMX module\n  formats. As noted for several versions in the help file, these are not\n  loadable by MikMod. It is extremely unlikely any game uses these obscure\n  formats, but denying their use is now enforced (at a robotic level).\n+ Fix crash when writing to a MZX string at an illegal offset (< 0).\n+ Fix returning from a subroutine invoked by a jump from a MZX text box\n  class command so that it no longer skips the next impending line (after\n  the text box).\n+ Assemble single non-alphanum/punctuation characters as bytecode CHARACTER\n  instead of bytecode STRING. Fixes bogus auto-quoting for commands like\n  SCROLL CHAR (Revvy).\n+ Switch the Win32 package back to using the \"windib\" SDL video driver,\n  instead of the \"directx\" SDL video driver. The windib.bat file has been\n  replaced with directx.bat, which has opposing semantics.\n+ SAM/GDMs with converted WAV/S3M counterparts of zero length will be\n  automatically re-converted. This hack can be used to procedurally\n  regenerate WAV files from SAMs, or transparently work around on-disk\n  corruption.\n+ Strings are now limited to a maximum length of 1M. I'm open to suggestions\n  over a better limit, but there must be a limit (set \"$string.X\" notation\n  grows a string arbitrarily, so robotic can crash MZX when a string is resized\n  beyond a reasonable limit).\n+ Strings, when grown, will fill gaps with ' ' instead of garbage. This can be\n  useful when the string grows after using the set \"$string.X\" notation; the\n  rest of the string is no longer garbage, allowing the debugger to be used.\n+ A robot that does a \"put c?? Thing p?? [dir] player\" and overwrites itself\n  will no longer leak commands. Instead, if the robot overwrites itself, its\n  program will end.\n+ Fix message edges always showing up black, instead of whatever color 0\n  is. (Revvy)\n+ Change starting/max health and lives minimum to 1 instead of 0. (Revvy)\n+ Some help system (F1) bugs have been fixed, hopefully mitigating some of\n  the crashes people have been seeing.\n+ Fix a bug on Linux where fclose() on a robot-opened file could, on world\n  reload, occassionally crash (due to a stale handle). Fixes loading Toayarin\n  saves multiple times in a row.\n+ The new option \"gl_vsync\" has been added to allow the SDL \"flip on vsync\"\n  in the OpenGL renderers to be forcibly enabled or disabled. This fixes a\n  problem where speed 1 would only be as fast as the video refresh rate.\n+ Setting the music volume to 0 (when using the ModPlug engine) now ensures\n  that no music is audible. Previously, setting the volume to 0 would be\n  equivalent to setting the volume to 1, which was still audible.\n+ Upon exiting the initial load screen, and not entering the editor, the\n  screen is now updated. This fixes rendering glitches in the MZX game\n  window when overlapping the window with another, at the slight expense\n  of CPU time.\n+ If loading a save game from the title screen (or when no world has been\n  loaded) do not send JUSTENTERED to all robots. This restores compatibility\n  with MZX 2.70 and is consistent with loading a save from another board.\n+ Counters with 10 digits and a negative sign are no longer truncated in\n  the debug menu.\n+ Correctly clamp (rather than truncate) the value passed through to a\n  SET COLOR. Restores compatibility with 2.70, and fixes Xenogenesis.\n+ Improve clipboard copy behaviour on Linux. Some actions are still\n  mysteriously broken.\n+ Fix replacing with a blank string in conjunction with the replace all\n  Ctrl+F action in the robot editor. The cursor can now no longer become\n  negative, fixing numerous possible crashes on search/replace.\n+ Fix loading the intrinsic SMZX palette when switching to SMZX modes\n  from a game not in the same directory as the \"smzx.pal\" file.\n+ Reloading a world that requires switching between SMZX and non-SMZX modes\n  will now respect the world's intrinsic palette on the title screen. Fixes\n  problems loading non-SMZX games after having an SMZX game loaded.\n+ Clamp array offsets on boards. Some older MZX worlds are corrupted and\n  have the endgame_{x,y} coordinates outside of the limits of the endgame\n  board. Fixes \"Fourth Power\".\n+ Where possible, version all counters that the port understands. This\n  ensures that in the unlikely case that a game made with an older version\n  of MZX (actually, with an older world magic) uses a counter that did not\n  exist in that game's era, the port will no longer try to interpret it.\n  Previously, only rid? and key? were versioned.\n+ SAV files will now be stamped with the world magic of the world they\n  were loaded from. This allows compatibility hacks to apply to SAV files\n  as they would to worlds. (ajs, Terryn, Mr_Alert)\n+ Add LOAD_MENU counter like ENTER_MENU, F2_MENU et al. to allow control\n  (from robotic) over whether the LOAD_MENU can be brought up.\n+ Have FREAD_COUNTER and FWRITE_COUNTER read in a DOS dword (32bit) instead\n  of a DOS word (16bit). This allows modern post-port MZX counters to be\n  fully represented in files. Compatibility with older worlds is preserved.\n+ Add a new config option \"board_editor_hide_help\" which changes the default\n  hide setting of the help text on the primary board editor.\n+ Numerous fixes for bugs found by valgrind. (Nightwatch)\n+ Icon support is now fixed and works on all platforms. On Windows, the\n  icon cannot currently be changed (it is loaded from the EXE's resource\n  section). Use ResHacker if you really want to change it.\n+ Fixed a bug where either LOAD_ROBOTn or LOAD_BCn (where n was equal to\n  ROBOT_ID) would alter the robot's line number rather than completely restart\n  it. Due to complexities in robot context, this lead to the first line being\n  skipped.\n+ Added a new tool \"downver\" which supports drag-and-drop downgrading of\n  a world or board from the version of MZX it is packaged with to the\n  previous version of MZX. This tool may be unsafe to use -- be careful.\n+ Fixed a bug in the robotic assembler which would occassionally emit corrupt\n  programs with SAVE_ROBOT. These programs, if loaded by LOAD_ROBOT could\n  cause a crash.\n+ Added a config.txt (or command line) option \"startup_editor\" which, if set\n  to a non-zero value, will start MegaZeux in the editor with a blank world.\n+ Fix a bug where a robot's WALK processing, on entering a transporter, could\n  allow subsequent commands (such as GO) to corrupt the board. WALK now ends\n  the cycle in the special case that a robot goes through a transporter.\n+ You can now directly import bytecode into the robot editor via the Alt+I\n  menu. The extension for the bytecode file must .bc for it to be loaded.\n+ A game loading SAVs via the LOAD_GAME counter will no longer crash MZX\n  if the SAV attempted is from an incompatible version of MZX, or in any\n  way corrupted.\n+ Fix a crash when auto-completing lines that were greater than 241 characters\n  in length after completion.\n+ Added mouse pixel counters MOUSEPX and MOUSEPY. (Mr_Alert)\n+ Commenting a line of maximum length (241 characters) can no longer grow the\n  length of the line beyond this limit.\n+ Fixed a bug causing the software renderer to fail to center when using a\n  boxed fullscreen resolution. Also fixes a bug where the PSP platform would\n  ignore an override of the force_bpp option. (Mr_Alert)\n+ Fixed a bug causing macros loaded from config.txt to be expanded\n  incorrectly. Relatedly, fixed a bug where #<string> in the robot exitor\n  would \"disappear\" on entry, if there was no correspondingly named macro.\n\nDEVELOPERS\n\n+ Don't initialize the SDL audio subsystem if audio is permanently disabled\n  with --disable-audio.\n+ Add fixes for OpenBSD to allow PNG screenshots and X11 clipboard support to\n  work. Tested with OpenBSD 4.2 and GCC 3.3.5.\n+ Updated Win32 builds with SDL 1.2.13.\n+ Dependencies are now correctly tracked in the build system. Modifying a\n  header will automatically regenerate the minimal set of object files that\n  depend on this header.\n+ Out of the box MSVC support. The file \"msvc.zip\" in the root of the source\n  package now provides a Visual Studio 2005 project and pre-compiled\n  dependencies. There may be stability issues with the resulting binary.\n  See also the documentation in arch/msvc/README.txt.\n+ The Nintendo DS port (a.k.a. 'dsmzx') has been merged. This is the most\n  exotic port thus far, and adds features such as player focus (on the second\n  display). Sound isn't working yet, and large games still won't play (due to\n  lack of memory). See docs/nds.txt for more information. (kvance)\n+ Many stack-heavy functions have been de-bloated and allocate large storage\n  on the heap (if performance is not critical). This helps out platforms\n  with a small, fixed stack size (such as NDS).\n+ The built-in help system can now be disabled for embedded platforms.\n  The startup check for the help file will not be performed if the help\n  system is disabled, and so this file can be omitted from distributions.\n+ The package.sh script now supports OS X, PSP, GP2X and NDS packaging.\n+ The OS X port no longer requires Xcode. The new build system and package.sh\n  can create a universal Application and corresponding DMG file. The new\n  infrastructure deprecates the old macosx.zip method.\n+ Most of the internal dependency on SDL has been removed. Therefore, MZX\n  can be built (but not yet work) without SDL present. The only remaining\n  component to convert is MikMod, but this can be disabled, so port authors\n  can start using the feature right away (see config.sh). (Mr_Alert)\n\nDecember 8, 2007 - MZX 2.81h\n\nAnother bugfix release with a couple of new features, in time for the\nWinter 2007 Dualstream Day of Zeux. The major new features of this release\nare automatic module renaming in the editor, PNG screenshots and many\nimprovements to MZX on embedded platforms (like PSP and DS).\n\nMegaZeux can now be compiled in MZXRun mode (like the old DOS\nimplementation) and by disabling features such as unnecessary renderers\nand audio support, can be made approximately 70% smaller.\n\nThanks again to Terryn for relentlessly tracking down many serious bugs;\nwe've tried to fix all the issues that have crept up in the last 5 months.\n\nThanks too to Exophase, Mr_Alert and Wervyn for contributing to this\nrelease; your time and help is invaluable.\n\nHappy Holidays!\n\nUSERS\n\n+ Added a more lenient WAV file loader so that ModPlug isn't relied on as much\n  to play malformed WAV files (mostly old SAM conversions) (Mr_Alert).\n+ Added SCORE and mzx_speed to the counter debugger (Mr_Alert, ajs).\n+ Added a 16-bit software renderer and a half-width renderer for the GP2X port\n  (Mr_Alert).\n+ Made the mouse cursor in the \"opengl2\" renderer look more like the mouse\n  cursor in the other renderers (Mr_Alert).\n+ Setting vlayer_size, vlayer_width or vlayer_height to values less than or\n  equal to zero would crash MegaZeux. Limit the smallest vlayer size to 1x1.\n+ Setting vlayer_width or vlayer_height to a value larger than vlayer_size\n  would crash MegaZeux. Limit the largest size of either dimension to a\n  maximum of vlayer_size.\n+ If selecting a module with a non 8.3 filename, MZX will now ask you if you\n  want to rename it to 8.3, and do so in an intelligent way. This means that\n  music can be selected in the editor and correctly saved (Wervyn).\n+ The OpenBSD compiler detected some serious string bugs in MegaZeux. These\n  have now been fixed and should eliminate some more potential crashes.\n+ Fixed a bug where an ENERGIZER item or use of the INVINCO counter would\n  cause the original player color to be corrupted at the end of the colour\n  blitz.\n+ Fixed a long-standing bug where set \"$string\" to \"FWRITEn\" would be\n  cheerfully ignored.\n+ Fixed a bug where a corrupt robot list could crash MegaZeux (e.g. the\n  list from Star Quest from DoZ'02).\n+ FEATURE: Screenshots are now saved in a palettized PNG file format. For\n  platforms without libpng, PNG support can be compiled out, and BMP will\n  be used instead.\n+ Fixed a bug where changing boards in the editor could sometimes corrupt\n  memory, later causing a crash (either testing or coming out of testing a\n  board).\n+ Fixed a sensor bug that happens when a sensor can't go anywhere it is told\n  to, and the player is on it (Exophase).\n+ Fixed using ABORT LOOP in some situations. Using it outside of a loop\n  still has undefined semantics and this has been documented in the help\n  file (Exophase).\n+ Setting a board option below its numeric limit is no longer\n  possible (Exophase).\n+ Fixed problems with going over Robot name character limits using the .@\n  command (Exophase).\n+ Fixed problems with LOAD_ROBOT freezing on a robot with no newline at the\n  end of the file.\n+ Fixed a problem with \"Replace All\" in the robotic editor, that could\n  sometimes cause a line to exceed 240 characters and crash the editor.\n+ Fixed a problem with \"Replace\" in the robotic editor, which could cause a\n  line to temporarily become 241 characters and then truncate silently to\n  240 characters.\n+ Fix a bug that caused the original game palette to be lost when testing\n  a game in the editor that switched between Regular/SMZX1 and SMZX2/3 modes.\n  MegaZeux should now try much harder to preserve the user palette,\n  regardless of game edits.\n+ Fix a bug causing board switching to not correctly alter the x,y viewport\n  scroll leading to the display of raw memory and potentially crashes, with\n  differently sized boards.\n+ Fixed stack corruption caused by SCROLL CHAR SOUTH, detected by Ubuntu's\n  SSP (Stack Smashing Protection) enabled binary.\n\nDEVELOPERS\n\n+ Made the build system less verbose by default (like Linux). This should help\n  make warnings (due to coding errors) easier to identify. If you don't like\n  the new syntax, or need the command debug, you can build with \"make V=1\".\n+ Updated Win32 builds with SDL 1.2.12.\n+ Rewrote the build system to not use recursive Makefiles. Variable propagation\n  was starting to be a problem, and recursive designs are generally discouraged.\n+ Refactored the graphics rendering code to modularize the renderers and reduce\n  code duplication (Mr_Alert).\n+ GDM2S3M switched over to use inttypes.h instead of home-brew types.\n+ MegaZeux now compiles on OpenBSD (and probably other BSDs).\n+ Made all unnecessary global symbols static. This should improve compiler\n  optimisations and correctness (Mr_Alert, ajs).\n+ Fix compilation of MegaZeux against SDL 1.3 SVN. However, this SDL version\n  is still in development, and MegaZeux does not work correctly when compiled\n  against it.\n+ MegaZeux now builds with the experimental MINGW-x64 branch, enabling x64\n  binaries for Windows.\n+ MegaZeux now builds with MSVC if you apply the patch from contrib/,\n    megazeux-r326-replace-c99-variable-arrays-with-malloc-free.diff\n  This patch is required for MSVC because it makes non-compiler-specific\n  changes (which involve converting from C99 variable length arrays to\n  malloc/free) which are slower and should not be used with competent\n  C99 compilers like GCC.\n  Microsoft Visual C++ Express Edition 2005 was used to build libogg,\n  libvorbis, libsdl and MegaZeux itself. Only 32bit builds were tested.\n+ MegaZeux now has size optimisations which can reduce binary size when\n  features are disabled. For example, all renderers can now be disabled,\n  and when module engines are disabled, audio will not export any symbols.\n+ The entire audio subsystem can now be disabled. This further reduces\n  binary size on embedded platforms. However, SFX editing still remains\n  enabled (though useless) until editor modularity is implemented.\n+ The PSP port is now officially supported, and compiles out of the box.\n  See docs/psp.txt.\n+ Renamed macos platform \"darwin\", to reflect its true nature (use Xcode to\n  build as a real Application, instead of just a UNIX binary). Also fixed\n  some bogosities with robo_ed's X11 includes on OS 10.5.\n+ The editor can now be disabled, a la MZXRUN from the old DOS versions.\n  Configure with --disable-editor to shrink MZX by about 150k.\n+ MegaZeux can now be compiled with size optimisations (--optimize-size to\n  config.sh) for a 20% space saving.\n+ MegaZeux's core now builds with -W (basically all GCC warnings) plus\n  some additional warnings that aren't switched on by this flag. All\n  warnings have been fixed.\n\nJuly 4, 2007 - MZX 2.81g\n\nAgain, no significant new features have been introduced in this release.\nHowever, there have been many essential bugfixes, including improved\ncompatibility with games made in older versions of MegaZeux.\n\nAdditionally, improvements have been made to the opengl2 and overlay2\nrenderers, improving performance for most users. A port of MegaZeux to the\nGP2X console has been added. MegaZeux has been backported to C (rather than\nC++) and can operate correctly on a CPU without a floating-point unit.\n\nParticular thanks go out to Mr_Alert (for his valuable bug fixes),\nLancer-X (for fixing what I was too lazy to) and Terryn (for finding many\nannoying bugs that nobody else could).\n\nUSERS\n\n+ Fixed a bug in the audio code. The linear resampler was not taking volume\n  into account, which broke changing the volume of samples (WAV and Vorbis)\n  which cannot natively alter their volumes.\n+ Fixed a regression in the overlay editor caused by the new editor space\n  semantics.\n+ Screenshots are now rendered to a separate texture using the 8bit software\n  renderer. This means that the hardware scalers will not affect the quality\n  of the screenshot. It also fixes a bug when using opengl2, which would dump\n  only a white screen.\n+ Temporarily reverted a bugfix that broke Zeux IV - Forest of Ruin. I'm not\n  dropping the bugfix, I just can't immediately see what's wrong.\n+ Fixed a bug where setting the viewport to negative coordinates would crash\n  MegaZeux. There was code to handle this, but it was wrong.\n+ Fixed a bug that permitted the mouse y coordinate to be warped to row 25,\n  which does not exist. This bug caused some of the renderers to crash, and\n  the software renderer to draw in memory it did not possess.\n+ Fixed a bug where games made before 2.68 could have available the \"key?\"\n  counters, unsupported in that version. This caused collisions with counters\n  with the key? name used with inc/dec/mul/div/mod. Fixes \"Doom Keep\".\n+ Imported libmodplug 0.8.4, which adds MIDI/PAT and ABC format support,\n  fixes some bugs in the mixer, and should build on more platforms.\n  NOTE: MID files currently cannot be selected in the editor, because they\n        do not play correctly.\n+ Improved the performance of the \"opengl2\" renderer, by removing the\n  convoluted 3D drawing commands and replacing them with 2D ones. Reduced the\n  quad count by using an intermediary 80x25 texture. MegaZeux now depends on\n  fewer GL features (LogiCow).\n+ Introduced an \"fsafegets\" to work around problems where robots exported by\n  a Windows version of MegaZeux would not load on other platforms. This was\n  due to differing EOL style and broke at least one game (Termination).\n+ Renamed \"force_resolution\" to \"fullscreen_resolution\" to better match its\n  semantics with the scaling renderers. The new name is less accurate for\n  software render modes, but most people using software will not want to\n  change it from the default anyway.\n+ Fix a bug where the variable-length string allocator would prematurely\n  bail out when reading a string (of indeterminate length) from a file with\n  the set \"$var\" to \"FREAD\" syntax.\n+ Fix a bug where more than 256 errors would crash the robotic checker.\n+ Improved performance of the overlay2 (faster) renderer (Mr_Alert).\n+ Make the transparent overlay \"really\" transparent when used in conjunction\n  with sprites (Mr_Alert).\n+ Fixed a bug reported by Mr_Alert where MZX would not handle short,\n  non-looping mods in the editor. The editor would try to destroy the mod\n  again, even after the callback had destroyed it (premature termination).\n+ Fixed a bug with SWAP WORLD where file translation would occur but the\n  result would mistakenly not be used. This broke some uses of SWAP WORLD\n  on non-Windows platforms (Mr_Alert).\n+ Fixed a bug where using JUMP to MOD ORDER right after switching boards\n  would fail due to the board music not having been loaded yet (Mr_Alert).\n+ Fixed a bug where games made before 2.80 would inadvertently trigger\n  \"PLAYERHURT\" due to using the SET command to reduce the amount of\n  health (Mr_Alert).\n+ Fixed a bug where player clones where generated when entering transports\n  during FREEZETIME (Lancer-X).\n+ Debug menu is now eradicated on leaving the editor (Lancer-X).\n+ Debug menu is now properly painted over when the board size is < the editor\n  viewport. Fixes various graphical glitches (Lancer-X).\n+ Fix a crash bug when playing older MZX games from read-only media (such as a\n  CD) or where file-system permissions prohibited creating SAM conversions\n  (Lancer-X).\n+ Fixed bug where certain file formats would not be automatically converted\n  if their extensions were mixed or upper case (e.g. OGG/SAM/GDM).\n+ Restored functionality of \"if lasttouched DIR\" which has been broken since\n  MZX 2.02.\n+ Fixed a bug where attempting to decrypt a read-only world file would result\n  in a crash (Mr_Alert).\n+ Fixed several bugs where an error loading a world file would result in\n  crashes in several different situations (Mr_Alert).\n+ Fixed a bug where a robot using the BECOME command to change into a\n  PushableRobot or vice versa would freeze (Mr_Alert).\n+ Fixed memory leaks in the file selection dialog, the counter debugger, the\n  collision list and the global robot (Mr_Alert).\n+ Updated counter list (see docs/counter_list.txt in the source) (Terryn).\n+ Fixed a bug where pressing escape when editing the effect of a ring or potion\n  would result in an invalid parameter which would later cause a crash if\n  edited again (Mr_Alert).\n+ Fixed a bug in which robot-driven text boxes using option commands (the ?\n  command) could overflow by two characters and spill over the side (Lancer-X).\n+ Fixed the list box searching mechanism (used in the file manager and F11\n  counter list) and made the existing function more understandable. (Lancer-X).\n+ Fixed a bug in which the message string given to the 'ask' command could\n  spill over. Now, the 'ask' dialog resizes if possible, and clips when no\n  further resizing can be performed (Lancer-X).\n+ Clipped the 'input string' message properly, to prevent similar overflow.\n+ Fixed a bug with the EXPLODE, DIE, DIE ITEM and BECOME commands when used\n  with the global robot (would clear the global robot, eventually corrupting\n  memory when in the editor). Presumably, these commands are bogus for the\n  global robot, and have been disabled.\n\nDEVELOPERS\n\n+ Rewrote config.sh to use POSIX sh compatible functions, so that there is\n  no dependency on the BASH interpreter. Surprisingly, some distributions\n  still don't enable BASH by default (using csh, ash or zsh instead).\n+ Ported most of MegaZeux back to C. This should further reduce the platform\n  requirements for running MZX, and enable marginally faster compilation.\n+ Enabled GCC's -W flag for even more warnings, switching off unused\n  parameter warnings (useful for delegates). Mostly typing fixes, but it\n  found a bug in string handling.\n+ No longer suppress char-subscript warnings, and fix up any remaining\n  abuses in the tree.\n+ Add manpages for 'megazeux' and other binaries for the Debian packages.\n  Comply with the Debian packaging guidelines by providing a copyright\n  note, listing significant contributors to MegaZeux.\n+ Added support to the build system for supporting icons modularly.\n  See contrib/icons/README for more information.\n+ The debug build (make DEBUG=1) now enables GCC 4.x's stack protector. This\n  breaks compatibility with GCC 3.x, but you can just remove the flag if you\n  don't want to use it (the stack protector will improve stack corruption\n  detection and provide more usable debug traces).\n+ Custom Random() implementation to provide a more uniform number\n  distribution. Factored out for future (better) implementations.\n+ The audio backend (audio.cpp) has been modularised to support the use of\n  mikmod instead of modplug. This should enable ports of MZX to platforms\n  without an FPU, and improve performance on platforms with weak FPUs.\n+ Added GP2X port to config.sh, based on work done by Simon Parzer.\n\nJanuary 30, 2007 - MZX 2.81f\n\nThis release is mostly about the new renderers, the first of which was\nintroduced in the previous version. There's also a few important bugfixes,\nand a lot of internal tidy-up work. I'd like to thank Mr_Alert, Quantum P.\nand LogiCow for contributing to this release. Thanks guys.\n\nUSERS\n\n+ Renamed the force_32bpp config option to force_bpp, in preparation for\n  16bit OpenGL render modes. This option now takes 8, 16 or 32. 16 is\n  reportedly broken on Windows, so stick to 32 for now.\n- The force_height_multiplier option has been removed. A lot of code\n  wasn't properly designed to handle it, there have been mouse warp bugs\n  with it for years, and nobody seems to use it. If people want stretching,\n  they can choose one of the four hardware renderers to achieve this.\n+ Added infrastructure for \"pluggable\" renderers. This code isn't perfect,\n  but it's far better than the mess in 'e'. Defaults to the 'software'\n  render mode.\n+ Added Logicow's alternative OpenGL renderer. For more information about\n  this renderer, see config.txt. NOTE: This code may be buggy! Please test!\n+ Added Mr Alert's YUV overlay renderers. One does full YUV macropixel\n  approximation, the other (faster) render does chroma supersampling. See\n  config.txt for more information. NOTE: This code may be buggy! Please test!\n+ Simplified Exophase's OpenGL renderer present in 'e', and fixed a few bugs\n  that caused it to not work for some people.\n+ Really made MegaZeux use 'directx' by default on Windows. The code in 'e'\n  was non-functional. Use 'windib.bat' to run MegaZeux with the SDL windib\n  driver.\n+ The OpenGL renderers now have a 'filter' option that allows you to choose\n  linear (where pixels are interpolated, looks \"blurred\") or nearest (where\n  nearest-neighbour approximation occurs, looks \"sharp\").\n+ Mouse warping was broken when using any of the hardware renderers. There\n  should be code in there now to take account of this (thanks Mr_Alert).\n+ Added an option 'editor_spaces_toggle' which allows you to revert MZX's\n  space overwrite behavior to the semantics of 'd' (the feature was\n  removed in 'e'). By default, the behavior is unchanged.\n+ F6 (the debug menu) can now no longer be enabled anywhere but in the\n  editor Alt-T test mode. In 'e', it was possible to enable on the title\n  screen, but could not be enabled in a game. Like the cheats, this option\n  is now visible only in test mode.\n- Removed the 'lame/1337' menu feature.\n+ Fixed a bug where the global robot could be exited via some legal commands,\n  in an abnormal fashion. The bug resulted in all the code up to the offending\n  command being executed over and over.\n\nDEVELOPERS\n\n+ OpenGL can now be disabled via config.sh. This allows MegaZeux ebuilds\n  to be constructed on systems that do not have any form of OpenGL support.\n  (Although MZX runtime loads the OpenGL library, 'e' required the headers\n   to build correctly. This is now no longer the case.)\n+ On Windows, due to an ATi driver bug, I have provided a means of linking\n  directly to opengl32.dll, instead of relying on the dynamic loader. This\n  reduces binary portability, but fixes many bug reports of being unable\n  to fullscreen on ATi video cards. See OPENGL_LINKING for more information.\n+ Improved support for cross compiling with mingw32 on Linux, combined the\n  win32 Makefile with this new support.\n+ Rewrote the config.sh script. All of the options have changed, and the\n  broken platform auto-detection has been removed. See ./config.sh for more\n  information.\n+ Rejigged MegaZeux's headers so that they can be used in both C and C++\n  mode. Renamed fsafeopen.cpp to fsafeopen.c. Hopefully by 'g' most of\n  MegaZeux should be ported back to C, instead of the \"C++\" it is now.\n+ Fixed up the 'txt2hlp' utility which Terryn has been using a version of\n  to build the internal MZX help system. This binary is built in the source\n  distribution, but it is not distributed with the MegaZeux binaries.\n+ Moved some antiquated Alexis code out into 'old'. No attempt has been made\n  to make it compile, it is provided purely for reference.\n+ For the windows binaries, \"windib.bat\" is now generated by package.sh\n  and auto-generated for the name of the MegaZeux executable.\n+ Updated Xcode package from Quantum P. (see macosx.zip).\n\nJanuary 18, 2007 - MZX 2.81e\n\n+ Made grabbing in the editor not combine background color, only uses\n  \"special\" in game colors for player, tell me if anything ends up being\n  weird because of this.\n+ Possibly fixed an obscure bug where moving something happened\n  immediately if it was sent to a label by a robot further east/south than\n  it and it moved north or west (has to do with the way robots are\n  reverse scanned). Tell me if this changed any behavior for the worse and\n  I'll change it back or try to work out something new.\n+ Added GDMs to ctrl + n, this will of course auto convert and play the\n  s3m.\n+ Added ability to preset player locked status from board settings.\n- Removed ability to change SMZX mode ingame (F11)\n+ Instead added ability to debug variables (counters and strings)\n  ingame with F11. There's also an option to export the current variables\n  to Robotic program that sets them.\n+ Fixed bug where moving a block with the player into an overlapping\n  region leaves a space where the player was.\n+ Fixed bug with a robot indirectly sending itself to a subroutine via\n  send all or send name causing it to loop the send.\n+ Added compatability hacks for key# prior to MZX 2.69 worlds and\n  ridNAME falling through in MZX 2.70 and earlier worlds.\n+ F6, F7, F8, and F11 debug/cheat keys only work in editing mode now\n  (as things were prior to the port) You can still save/load in the editor\n  so if you want all of these things you can play the game from the start\n  there.\n+ Space in the editor no longer deletes something of similar type that is\n  beneath not sure what the point of this was anyway.\n+ Fixed bug causing cursor to clipped be out of bounds in SMZX char editor\n  if changing to smaller multichar edit region.\n+ Accidentally messed up screen centering in fullscreen for 32bpp mode,\n  fixed.\n+ Added hardware scaling option. You can now supply a window resolution\n  besides 640x350 and allow for window resizing if hardware scaling is on;\n  this will also scale fullscreen output to fill the entire screen. This\n  can slow down rendering somewhat.\n+ Fixed bug causing flip block to crash in the editor.\n+ Made blocked directions relative to the player for put dir player.\n+ Fixed bug where putting something to a direction relative the player\n  overwriting the robot could crash MZX.\n+ Fixed ability to input in input boxes by clicking on their question\n  string.\n+ Removed the bogus patch to Modplug and correctly fixed it in the build\n  system.\n+ Added 'debian' subdirectory for building Debian and Ubuntu upstream\n  packages. Hopefully MegaZeux will be in the primary pool in a few\n  months.\n+ Added OS X xcode project files (see 'macosx.zip'). Fixed many bugs\n  relating to endian that caused MegaZeux to be buggy on big-endian\n  architectures (like PPC). Credit goes to Quantum P for finding these\n  bugs and engineering high quality solutions.\n+ Made 'directx' the default video render again on Windows. NOTE: This\n  overrides the default SDL behaviour, but will not be applied if you\n  set SDL_VIDEODRIVER yourself.\n+ Repaired the 'linux-static' target so that it no longer includes a\n  system C++ library, which caused unpredictable results on distros\n  without a static version.\n+ Fixed a locking bug with the audio code that caused hangs at startup\n  on OS X. Also provided a mutex implementation using GNU pthreads as\n  a temporary workaround for an SDL bug on the Linux platform.\n+ Added PlayStation Portable (PSP) port. This code was written by Exophase\n  and is highly experimental. It may not work at all for you. Please\n  see docs/build.txt for more information regarding this port.\n+ Fixed mouse movement from being affected by height_multiplier when\n  not in fullscreen mode\n+ Fixed height_multiplier config.txt option allowing you to enter really\n  stupid values (like negatives, 0, and values too large for the\n  resolution)\n+ Added in an extra video mode check to stop MZX crashing on video modes\n  that the video card can not reproduce\n+ Fixed Avalanche to a constant placement rate of 1/18 (this caused MZX\n  to deliver an uneven number of boulders, and crash with certain board\n  sizes\n+ Fixed sprite collision box to stop MZX from crashing when stupid values\n  are entered\n+ Fixed setting the viewport size to weird values like some old MZX games\n  do\n+ Default fullscreen resolution is now 640x480, this can be changed in\n  config.txt\n+ The config.txt option force_32bpp is now enabled by default\n+ Seeking with mod_position when using a .WAV file as background music\n  now works (thanks Mr_Alert)\n\nDecember 10, 2006 - MZX 2.81d\n\n+ Fixed a compilation failure on Linux, due to SDL no longer depending\n  on libX11. Now we manually link X11 into MZX if necessary.\n+ Various build system improvements, fixing bugs in the prefixing\n  of dependencies.\n+ New libmodplug 0.8 imported, fixing many endian problems on big-endian\n  machines, integrating all of our local patches to 0.7.\n+ Fixed bug causing MZX to freeze when starting up on Win9x machines.\n+ Fix a warning generated by GCC 4.1.\n+ Update the GPL boilerplates project-wide to the newest FSF address.\n+ Fix a string range check causing an obscure crash in certain games.\n+ Update the build.txt documentation.\n+ Address documentation bugs found in the internal MZX help system (Terryn).\n+ Fixed a recently introduced bug breaking the open command.\n+ Fixed some bugs with changing params and pressing escape.\n\nDecember 14, 2005 - MZX 2.81c\n\n+ Oops, accidentally broke shift + F2. Fixed that..\n+ Also accidentally broke &+counter& for full hex representation. Fixed.\n+ Fixed memory leak problem with playing certain WAVs in a loop.\n+ Fixed inconsistency of bad viewport sizes behaving differently on\n  current versions from old DOS versions.\n+ Accidentally broke joystick stuff in config.txt (has to do with way\n  configure options were being read), fixed.\n+ Fixed bug causing crash when loading MZBs larger than the current board\n  size.\n+ Made cursor hidden in alt + V in editor.\n+ String comparison failed with nulls in the strings, fixed. Also\n  should be slightly more optimal.\n+ Fixed bug when using negative numbers for if sprite_colliding \"counter\"\n+ Fixed math operations (inc, dec, etc) not working on string indeces.\n+ Added ability to force screen to 32bpp. Fixes some slight rendering\n  issues, and if you have problems with fullscreen let me know if this\n  helps (try it without first though). See force_32bpp in config.txt.\n+ Fixed sprite clipping bug with respect to overlay.\n+ Fixed bug where pressing enter on things besides robots, scrolls/signs,\n  or sensors in the editor would clear whatever was underneath it.\n+ Accidentally broke SFX with optional PC speaker chains (played both,\n  should only play PC speaker when digital music is off, fixes Bernard\n  the Bard)\n+ Made last character in char selection for F3 and alt + C remembered\n  (note that they're remembered in two different places for both)\n+ Accidentally broke life animations, fixed\n\nNovember 26, 2005 - MZX 2.81b\n\n+ Fixed inability to make proper .savs of worlds with strings (they'd\n  crash when loaded..)\n+ Fixed PC speaker audio bug causing a constant high pitched noise to\n  be played instead of PC speaker audio sometimes.\n+ Fixed some issues with long pathnames.\n+ Fixed a bug causing Caverns to crash in recent versions (long story,\n  it was most likely due to an error in ver1to2)\n+ Now when you set mzx_speed in a game you can no longer change the\n  speed from the F2 menu. Setting mzx_speed to 0 reallows this (and\n  doesn't set the speed)\n+ When loading a game its speed is now set to the speed MZX started\n  with (whatever's in config.txt, or the default of 4)\n+ Added backup_ext config.txt option to specify the extension of backup\n  files (default is .mzx).\n+ Fixed backup_interval for config.txt possibly being broken.\n+ Fixed a bug messing up the death board on some old MZX games\n  (like Nick Brick 2)\n+ Escaped more things and made displays always in escaped form for\n  certain character sequences. It should be impossible to type\n  non-escaped forms. The following should be used:\n  \\0 for 0 (this probably won't work in strings, but in chars should)\n  \\t for tab (character 9)\n  \\n for newline (character 10)\n  \\r for carriage return (character 13)\n  \\\" for double quote\n  \\\\ for slash\n+ Copy + paste on escaped character won't unescape them anymore.\n+ Fixed error message for invalid lines in Robotic\n+ Fixed inability to import text files from other directories.\n+ Huge overhaul of the source (proper types for things, directions,\n  equalities, conditions, chest items, and potions), if anything is\n  suddenly broken now let me know.\n+ Made scrolls/signs only display text (letters, numbers, etc) in the\n  default char set. That should be enough for now.\n+ Added mousewheel support for robot editor and robot box display.\n+ Fixed inability to load MZMs from other directories in the editor.\n+ Wrapped audio stuff in proper mutex, hopefully this fixes some issues\n  (like crashing when changing mod_frequency a lot).\n+ Long current directory paths no longer write out too much in the file\n  loader (instead the last bit is shown with a ... prefixing the\n  beginning)\n+ Decided to be nice and make board_scan not crash. Don't use it. It's\n  only there to make one legacy game work. If you use it I will\n  personally scold you. And don't tell other people to use it (that\n  means you CJA). Use copy block x y w h \"$str\" t instead. If you don't\n  know what that means read the help file, it explains everything.\n+ Removed ability to copy + paste after changing board dimensions of the\n  source under any circumstances (alt + R, alt + Z, import world, import\n  board)\n+ Fixed appearance of ghosts in F10 menu.\n+ Prevented char editor from counting moving the cursor as an undo step\n  if nothing was actually drawn.\n+ Made pressing escape on initial char selection/board selection/param\n  selection for things cause it to cancel placing anything.\n+ Made it impossible to set board width/height to 0 again (oops)\n+ Made starting lives and starting health take effect immediately for\n  the first alt + t\n+ Added ability to play OGG from alt + l (but not the other mods, don't\n  want to clutter that up)\n+ Made it so if no note follows an embedded SAM in a play string it's\n  played at native frequency.\n+ Accidentally made loading worlds in the editor not change the current\n  directory, fixed that.\n\nNovember 20, 2005 - MZX 2.81\n\n+ Fixed a bug where MZX world/save names > 12 chars could cause weird\n  things to happen (like doors breaking)\n+ Fixed problems with helpfile/charsets loading when loading MZX\n  outside of the directory MZX is in. This should fix file associations\n  on Windows as well.\n+ Changed board selector so when board 0 is \"(no board)\" it doesn't\n  actually refer to the title but to no board.\n+ Made import world not overwrite the title string.\n+ Fixed bug that causes crash when trying to flood fill an area with\n  the color it already is in the SMZX char editor.\n+ Redid audio engine. Everything is unified now, meaning that anything\n  you can use as a mod you can use as a sam and vice-versa.\n+ The new audio engine uses its own master resampler that has three\n  interpolation modes - see config.txt for more information.\n+ sam 0 filename will play a file at its native frequency (note that\n  SAMs that have been converted from WAVs are set to be played at\n  8363Hz).\n+ Added support for OGG vorbis audio files.\n+ Fixed bug causing SFX volume control in F2 menu to not work.\n+ Removed limitation on number of SAMs that can be played\n  simultaneously.\n+ Fixed bug where the mouse got \"stuck\" in the black border edges of\n  non 640x350 fullscreen resolutions.\n+ Fixed issues with message boxes being part default palette part\n  current palette, they now always use the current palette.\n+ Added mod_position counter. What these actually set/return is\n  dependant on the type of file loaded. Modules use the current row,\n  OGGs use the current PCM sample.\n+ Added mod_frequency counter. There are a few things to note here:\n  Modules have a \"nominal\" frequency of 44.1KHz. Other data types\n  have their own nominal frequency to prevent output from\n  sounding differently depending on the audio_sample_rate in config.txt.\n  For OGGs and WAVs the nominal frequency is the one the file is\n  encoded at.\n  Changing the frequency can cause a noticeable one time popping sound,\n  so it might not be desirable to slide it. This is much more prominent\n  with lowering the frequency than raising it.\n  This value is capped so it can't reach below 16.\n+ Changed alt + L to play back at natural frequency instead of 8363Hz.\n+ Fixed bug causing sensor deletion while the player is on top to\n  destroy the player.\n+ Fixed bug causing imported boards to possibly crash after being\n  tested.\n+ Fixed bug causing save_game and save_world to not work if a file\n  with the given name isn't already present.\n+ Changed function counter matching removing restriction on number of\n  digits for parameters. 10+ digit inputs should no longer fail (for\n  instance, abs-123456789)\n- Save files from 2.80 are not compatible due to several changes in\n  the save format.\n+ Made counter/string names internally variable length instead of a\n  fixed 14 chars. There is now no longer a name length limitation.\n+ Changed alt+8 for mod * to just * in the hotkey listing.\n+ Fixed crash when referencing (by param) sprites > 256.\n+ The string system has been redone. Strings are now dynamically\n  sized and don't have an artificial maximum length. Writing to\n  string.N will guarantee that the size of the string becomes at\n  least N, while reading in this way will return 0 if out of bounds\n  to maintain the illusion of null termination. Be careful when using\n  this.\n+ $str.length returns the length of string $str (this is faster than\n  iterating through it to find when chars hit 0)\n+ Vlayer is dynamically sized. The vlayer_width/vlayer_height counters\n  still work as per usual, but the vlayer_size counter has been added\n  to adjust the maximum size. The default is 32768.\n+ Fixed bug not allowing things to move over goop.\n+ Fixed bugs causing current directory to be changed when importing\n  things from other directories.\n+ Properly implemented support for volume \"string\"\n+ Fixed a few commands not working when they should from the global\n  robot (such as put to dir of player)\n+ Fixed a bug where going to a label at the end of a robot would treat\n  it as if it's the first of its name in a sequence of labels.\n+ Fixed a bug involving moving the a block with the player not moving\n  what was underneath the player.\n+ New help file, thanks MUCHLY to Terryn for pulling off this enormous\n  effort!\n\nJune 6, 2005 - MZX 2.80h\n\n+ Fixed a bug which could cause crashes when quiting the game.\n+ Fixed some bugs when changing boards and other things that can\n  cause duplicate players.. I think.\n+ Fixed a bug that could cause crashes when adding boards\n+ Improved response time in editor for slower computers/high load\n  situations\n+ Fixed some endian issues with the GUI\n+ Fixed some crash when moving the mouse cursor around in the editor\n+ Fixed bug where you if you had a robot whose name is the same as\n  the global robot it wouldn't get messages (fixes yoyo in Weirdness)\n+ Fixed debug box not moving with text input\n+ Fixed bug with duplicate player appearing when killed and a new one\n  can't be put at 0, 0\n+ Added copy/paste for outside of MZX to/from the robot editor. It\n  only works in Windows and X11, and functionality may be limited in\n  X11 right now (currently seems to work in native X11 apps and\n  GTK 2.6 apps but not earlier GTK or QT, also try shift + insert to\n  paste)\n+ Fixed bug in resizing involving overlay blanking.\n+ Fixed clear messages/projectiles not working (and damaging the game\n  instead)\n+ Fixed behavior of P key in editor for wildweasel.\n+ Fixed random in Robotic not correctly swapping the range if they're\n  given in the wrong order\n+ Fixed clip length in [ messages\n+ Fixed crash when changing volume without a game loaded\n+ Redid internal GUI system, fixes some minor things\n+ New file loading/saving window - press del to delete a file/dir,\n  alt + n to create a new directory, alt + r to rename a file/dir.\n+ Added PC speaker volume control to F2 settings and config.txt\n+ Fixed yet another crash bug with resizing boards\n+ Fixed inability to type * in text placement in the editor\n  (although this adds inability to turn on mod * while F2 is on...)\n+ Added ctrl + n in the editor to load a module for listening only\n  (won't set the current board's module, and will let you choose\n  ones from different directories)\n+ Fixed crash on macros with more variables than can be displayed\n  in their configuration.\n+ Fixed bug that causes char selection cursor to reset to 0 on\n  unhandled keys (and continuously do so for lock keys)\n+ Tweaked ctrl + dir in text entry boxes.\n+ Added gdm2s3m in-tree to the contrib/ directory. gdm2s3m no longer\n  needs to be installed on the system before compiling mzx.\n+ Improved the build system to automatically build .c and .cpp files\n  with compound system CFLAGS/CXXFLAGS, respectively.\n+ Made package.sh automatically ship the source package with a\n  Makefile.dist to warn the user that they need to run config.sh before\n  'make'.\n+ Rectified inconsistency in source copyrights.\n+ Added multicharacter editor. Select multiple keys in the character\n  selection with shift. The char editor also now has the ability to\n  perform operations (delete, copy, scroll, etc) on subblocks. Hold\n  down shift or press alt + b to highlight a region (press escape to\n  remove the latter). Blocks copied like this will be pasted to where\n  the cursor is at. Other small things in chareditor tweaked/changed...\n  No longer press tab to toggle through set/clear/toggle draw modes,\n  instead tab for set mode and shift + tab for clear (no more toggle).\n  Mouse behavior is modified as well. In non-SMZX left click sets,\n  right click clears. Shift + F2 will cut a block (clear + copy)\n\n  alt + x/alt + i can now be used to import/export partial charsets\n  while in the char editor. You can do so for several in series:\n  put a # in the name of the charset then set the First for the\n  first number # will be replaced with and the Count value to\n  indicate how many in series to work with.\n  For instance, saving s#.chr with first = 0, count = 3, starting\n  at offset 100, with a 2x2 char selection will save charsets\n  s0.chr from 100, s1.chr from 104, s2.chr from 108, and\n  s3.chr from 112.\n\n  *** NOTE *** Series import/export will only work correctly with\n  char selections that are one in height (can still be split up\n  another way in the editor itself). So if you want to use\n  partial charsets on your edits it's important that you select\n  all the chars in a row.\n\n+ Made characters for the editor/GUI use another charset that's\n  protected. Please notify me if any characters are incorrect.\n  Modify mzx_edit.chr to change this charset.\n+ The same thing goes for colors. It doesn't work for SMZX, which\n  also might look a bit different in the editor...\n+ Added option (defaults on) to protect chars 32-127 in input\n  boxes and strings in the robot editor.\n+ Mouse warping goes to middle instead of top corner now, so there\n  isn't a biased towards moving up.\n+ Hopefully fixed another bug with the cursor and changing boards...\n+ Fixed module looping problem in modplug...\n+ Added libmodplug 0.7 with both patches (see contrib/) in-tree.\n  Removes system dependency on libmodplug.\n+ Made auto-backup on by default (3 count)\n+ Made if touching idle, beneath always false instead of like nodir\n+ Fixed bug that caused bad things to happen if you pressed too many\n  different keys too rapidly.\n+ Made mouse wheel emulate up/down in dialog boxes and list menu.\n+ Added ctrl + backspace to intake (delete previous word)\n+ Made modulo operator use floored instead of truncated mod (uses\n  positive remainder instead of negative)\n+ Fixed crash when testing after using ctrl + z to clear a board.\n+ Fixed bug where sending other robots to subroutines caused the\n  return address to be to the next instruction like local subroutine\n  calling works.\n+ Fixed bug where going to a label on the last line of the robot could\n  screw the game up.\n+ Fixed a bug where the editor froze if you tried to fill the board with\n  players (eheh...)\n+ Fixed a bug that could cause crashes when sending all sensors something.\n+ Fixed some crashes when exporting/saving fails.\n+ Fixed import world's ability to go over the board limit and cause\n  crashes.\n+ Allowed input of decimal numbers for params.\n\nApril 1, 2005 - MZX 2.80g\n\n+ Fixed crash on alt + x in robot editor.\n+ Fixed missing line on alt + h in robot editor.\n+ Introduced incorrect enter action in robot editor (didn't reset to beginning\n  of the line), fixed.\n+ Fixed garbage appearing when moving from a larger to smaller board and being\n  outside of that board's scroll region.\n+ Fixed bug that could cause glitches/crashing when resizing the board\n+ Fixed error with global next option not retaining the three checkmark\n  options correctly.\n+ Added work around so that move block moves the player (won't move it on\n  inter-board moves)\n+ Fixed bad palette loading for Linux introduced in 2.80e or f or something\n+ Made it so block highlighting doesn't highlight the debug window.\n+ Made the debug window move if necessary when home/end is pressed.\n+ Added autorepeat buffering so previous keys can be resumed.\n+ Fixed bug with swap world possibly not working (crashing??) off Windows\n+ Fixed more problems with garbage/crashes when resizing with the cursor\n  in a position causing the scroll to go off the edge\n+ Fixed incorrect text cursor offset with force_height_multiplier on.\n+ Redid way directories are loaded internally so you can load dirs with over\n  4096 entries now. Might be faster (unsure)\n+ Chest contents list menu looked funny, fixed.\n+ Changed default.spl to smzx.pal so you can load it more sanely.\n+ Export block wasn't getting the last selected line. Fixed.\n+ Hacked scroll editor so it wouldn't crash when removing lines. Scroll code\n  either needs to be 100% overhauled or replaced by robots somehow...\n+ Fixed config files not being closed.\n+ Added include file option for config files. Use it like this:\n  include configfile  e.g. include subconfig.cnf will load subconfig.cnf's\n  options\n+ Fixed freadN not terminating strings.\n+ Fixed graphical glitch when using the mouse in the char selector.\n+ Fixed save games crashing when they can't load fopened files.\n+ Fixed some other problems with save games and fopened files...\n+ Accidentally had title screen running a bit slow...\n+ Value strings starting with ( not parsed as an expression if they\n  don't end with the )\n+ Fixed problem with key_code being triggered for keys that aren't\n  in-game\n+ Removed the unimplemented if player dir and if not player dir\n  commands from RASM\n+ Added extended macros. This allows for parameter based macros to\n  be entered in the robot editor via a window or by command. See\n  macro.txt for more information.\n+ Fixed player cloning after flip/mirror and player placing.\n+ Added random seeding that was mysteriously missing...\n+ Finally added drive changing for Windows builds.\n+ Fixed mousex/mousey for resolutions other than 640x350 (only applies to\n  fullscreen)\n+ Fixed crash on weird invalid death/endgame boards...\n+ % and & messages clip correctly now.\n+ Fixed potential crash on double closing the files.\n+ Fixed crash bugs with placing sensors and maybe scrolls.\n+ Fixed sending sensors when you have robots of the same name (fixes\n  Weirdness chapter 1)\n+ Fixed bug that can screw up your world when placing scrolls or sensors..\n  sometimes.\n\nDecember 26, 2004 - MZX 2.80f\n\n+ Fixed a bug that could cause crashes when auto-quoting params in the robot\n  editor (eg, set x 1 -> set \"x\" to 1)\n+ Fixed a bug that could crash the robot editor if you added a new line prior\n  to the first line of a marked block, then did an action on it.\n+ Fixed a bug where clearing the first and only line could cause it to appear\n  as if it hadn't been cleared at all.\n+ Unified global and global next parameter setting so that nothing is lost\n  between first/next but information can be cancelled without application.\n+ Fixed E/S block markers appearing in the robot editor when they should be\n  off the screen.\n+ Left click position in robot editor mysteriously disappeared after having\n  been added somewhere after 2.80d. Readded.\n+ Added option to hide the hotkeys help and horizontal border in the robot\n  editor with alt + h. Also added a config.txt option to have it default\n  this way.\n+ Search/replace in the robot editor. ctrl + f to find or replace/replace all,\n  ctrl + r to repeat either search or replace (depending the last one you did,\n  if you cancelled this does nothing)\n+ The load_game counter sequence was broken; fixed.\n+ Hopefully fixed all means of overrunning the current line max length in the\n  robot editor...\n+ Fixed robot editor validation not showing every 13th line\n+ Fixed aesthetic problem with validation report....\n+ Fixed crash with setting message column less than 0.\n+ A couple things added for 2.80e mysteriously disappeared in source handling.\n  Readded.\n+ Changed max board size prevention to auto resize the lower dimension to the\n  max that can be handled with the higher (ex, 30000x25000 becomes 30000x559)\n+ Added floodfill to char editors (alt + f)\n+ Added single depth undo to char editors (alt + u)\n\nDecember 19, 2004 - MZX 2.80e\n\n+ Fixed a bug causing problems with static overlay if a non-overlaid sprite\n  is displayed so it's clipped off the edge of the screen.\n+ Fixed a bug in the display of c?x color boxes in the F2 menu in the robot\n  editor\n+ Fixed a bug that caused incorrect thisx/thisy for one cycle after copyblock\n+ Fixed a bug preventing calls to nonexistant subroutines from passing that\n  point in the robot\n+ Fixed crash on sam 0 \"file\"\n+ Fixed a bug where loading new SFX may not correctly overwrite previous ones\n+ Fixed a bug where you could only load/unload so many mods before MZX couldn't\n  load anymore.. same bug as the SAMs but went unnoticed!\n+ Fixed a bug that caused you to be infinitely stuck in the global settings\n  dialog box when you press previous on the next page.\n+ Fixed a bug where going to next then exiting would not save the changes\n  from the previous page.\n+ Fixed a bug that could cause crashes while ending modules.\n+ Fixed a bug that could do the same kind of thing with sams.\n+ Fixed an allocation bug when loading MZX worlds that could lead to crashes.\n+ Fixed a bug that caused MZX to crash if you interpolated an expression with\n  a value equal to or greater than 1 billion.\n+ Fixed a bug where mixing ccheck1/2 with sprites from board and vlayer could\n  cause problems (that's the short version of the explanation, I'll spare you\n  the long one)\n+ Fixed a bug that could cause certain old MZX games to crash after the title\n  screen\n+ Somewhere broke missiles between 2.80c and 2.80d. Fixed.\n+ Fixed error in lit bomb anim sequence setup in char ID editor.\n+ Reworked a lot of robot editor code; adding/deleting lines while marked areas\n  are active should work more naturally now and it's hopefully no longer possible\n  to crash it in the same ways it was previously.\n+ Fixed crash when setting mesg row to less than 0.\n+ Fixed mouse presses not working in the robot editor.\n+ Made MZX ignore alt + tab so you can safely switch in your WM without it\n  triggering...\n+ Added numerical key entry for number boxes. Use 0-9 to add to the most\n  significant digit and backspace to take it away.\n- Fixed maximum board size to about 16.7 million tiles (128MB), for now.\n+ Added config.txt option to make MZX pause when key focus is lost\n  (when clicking on another window, perhaps) or when it's minimized. Music will\n  still continue.\n+ Added save/load position to the editor. Works for loads inbetween boards as\n  well. Press ctrl + num to save to slots 0 through 9 and alt + num to load from\n  that slot. Please press shift + 8 or the numpad * key instead of alt + 8 to\n  set mod wildcard.\n+ MZX now ignores the mouse scroll wheel instead of interpreting it as a click.\n+ Fixed a further bug that could cause playing samples to crash.\n+ Added config file option to revert the robot editor to the default palette\n  when loaded.\n+ Fixed bug in shoot command.\n+ Fixed error when making save name in editor but cancelling.\n+ Auto-backup - see config.txt for details.\n+ Joystick key mapping - see config.txt for details.\n+ You can now load game-specific config files by creating game.cnf for the\n  corresponding game.cnf (for instance, caverns.cnf). This is mainly useful\n  for joystick key mapping. Note that these settings will NOT go away if\n  another game is loaded that doesn't have a .cnf.\n+ Alt-enter finally works as block action in the robot editor.\n+ Loading a .mzx/.sav from another directory indirectly (via command-line or\n  robotic) will now actually change the current working directory.\n+ Fixed bug that crashed MZX with ctrl + i in the robot editor.\n\nOctober 9, 2004 - MZX 2.80d\n\n+ Fixed cursor going invisible when escaping from import in the editor\n+ Fixed robot editor entry when pressing OK on global info\n+ Fixed lack of name for MZB import/export (any MZB's exported in prior\n  beta versions still won't have a name)\n+ Fixed some problems with setting the mouse position\n+ Fixed problem with exits not bringing you all the way to the edge if width\n  over 400 or height over 200\n+ Fixed bug that cleared too much when increasing both width and height while\n  resizing the board\n+ Fixed problem with 1 char shortcut commands with spaces immediately after them\n+ Fixed problems with load_robot and load_bc (caused crashes and infinite loops)\n+ Optmized RASM heavily (this should be most noticable when doing a lot of\n  external robot loading from text files)\n+ Fixed inability to use absolute paths in loading a game from command line\n+ Fixed lastshot/lasttouch conditions with directions not working\n+ Fixed char editor in robots not going into SMZX mode when proper\n+ Cleaned up source code so it passes -Wall without complaint and in the process\n  corrected some glaring code errors that may have corrected random problems\n+ File opening broken in 2.80c, fixed\n+ Implemented MZM2 saving and loading and rewrote mzm.cpp (if anything is changed\n  or fixed regarding MZMs, attribute it to this). MZM2s can be of larger\n  dimensions, smaller filesize for same amount of data, and can store robots.\n+ Fixed bug that could cause MZX to crash when making new strings\n+ Block operations to overlay when overlay was off caused crashes - fixed\n+ Fixed a problem with sprite ccheck2 against other sprites\n+ Optimized function counter lookups a bit; speed gain for all counter accesses\n  (especially ones that begin with certain characters such as _)\n+ Fixed disassembly error with ' ' character\n+ Fixed assembly error where condition extended dir (such as blocked opp seek)\n  was not getting compiled with the dir extension\n+ Fixed editor bug where the param was not being cleared when overwriting things\n  by double placement\n+ Fixed inability to use counters with playercolor/bulletcolor/missilecolor\n+ Added ability to use counters in place of p?? in the robot editor\n  Note - even though this expands functionality of the editor this does not\n  require a version number change because the worlds will still be playable in\n  older MZX versions (and will display correctly in the robot editor - you simply\n  won't be able to correctly edit the commands)\n+ Mouse correctly limited to screen edges now\n+ Fixed inability to overwrite robots with pushable robots and vice-versa,\n  as well as scrolls with signs and vice-versa\n+ Possibly fixed problem with windowing error when editing global robot (?)\n+ Fixed disappearing cursor after color selection box with mouse (and other places?)\n+ Fixed bug in sprite clipping that caused some to be clipped off inappropriately\n+ Made board_id/board_param counters readable\n+ Added bound checks for all counters using board_x/board_y/overlay_x/overlay_y\n+ Removed ability to put robots, scrolls/signs, and sensors (with the put command in\n  Robotic)\n+ Fixed potential direction corruption bug causing directions not to work\n  sometimes even if they display correctly in the robot editor\n+ Fixed copy overlay to MZM copying to overlay too\n+ Fixed a bug where debug window could display the wrong amount of robot mem\n  and potentially even crash MZX\n+ Fixed help_menu counter not doing anything (durr)\n+ Changed sprite draw order so they're drawn underneath the message bar,\n  debug box, and time remaining display\n+ Changed put p?? in Robotic so it will put default params if available\n+ Fixed a bug that could cause copies from overlay to vlayer to not end up at\n  the correct destination\n+ Fixed a bug where c?x and cx? would not display correctly in the robot editor\n+ Optimized copy blocks a bit using variable length arrays instead of malloc\n\nAugust 16, 2004 - MZX 2.80c\n\n+ Fixed issues with the commands counter not being reset\n+ Color intensity now gets reset when you enter the editor\n+ SAMs got cutoff sometimes now.. fixed\n+ Fixed bug where loading a world with empty boards could change the starting,\n  endgame, and death boards\n+ Fixed bug where you could text enter off the bottom of the board, causing\n  problems\n+ Fixed bug involving cutting/clearing the entire robot in the robot editor\n  while not at the first line\n+ Fixed robot name entry for global robot not disappearing on small boards\n+ Fixed bug where you could duplicate the player by holding down a direction\n  as a saved game loads\n+ Fixed bug where you could go to line 0 in the robot editor\n+ Saving an MZM now auto-adds the .mzm extension...\n+ Fixed black screen on quicksave\n+ Fixed bug where opening a file didn't close the old one if one was open\n  (so it'd eventually crash MZX)\n+ Changed alt+backspace behavior in intake so it doesn't exit\n+ Added clipping for refx/refy/width/height for sprites (less than 0 at\n  initialization, greater than board width/height at draw)\n+ Fixed direction parsing for move all\n+ Fixed bug where creating things on top of the player would use a slot\n  for  robots/scrolls/signs/sensors instead of just copy to the buffer\n+ Added ability to use chars as immediates in Robotic commands\n  (ie, set \"$str.0\" 'a')\n+ Added options to enable oversampling and specify resampling mode in\n  the config file (higher quality audio)\n+ Building with patched modplug that fixes loading 2-channel mods\n  outputted by FT2. If you're building yourself, see build.txt.\n+ Fixed inability to mouse click in alt + h mode\n+ Fixed ability to mouse click outside of board range\n+ Should work better for Linux users; case insensitivity for file opens\n  has been added.\n+ Fixed close bug that was affecting Linux builds (may affect more)\n+ Keypad enter works where normal enter works now\n+ Fixed disappearing cursor when cancelling out of abandon changes box\n  when loading a new world in the editor\n+ Fixed problems when loading/saving robots outside of ID range (do not\n  hardcode ID's people)\n+ Fixed problem with NO BOARD exits being set to something else when\n  empty boards were being stripped or when worlds were being imported\n+ Fixed bug where auto-decrypting worlds didn't work if the XOR value\n  was negative\n+ Fixed problem with rid not working the first cycle\n+ Fixed inability to interpolate (with &&'s) counter names larger than\n  14\n+ Added new robot mem counter in debug box (only kb precise, rounds up)\n+ Fixed ability to clone the player on non-title board after testing\n+ Lengthened size of mod name buffers\n+ Fixed bug where send x y doesn't work from the global robot\n+ Fixed a few bugs that could cause MZX to crash\n+ Fixed a bug that prevented copyrobot \"string\" from working in some\n  situations\n+ Fixed a bug allowing player duplication in board importing\n\nAugust 11, 2004 - MZX 2.80b\n\n+ Made it possible for robots to move through teleporters\n+ Fixed bug with pressing shift in text entry boxes\n+ Made it so alt + tab does not switch draw modes in editor\n+ Fixed a disassembly error for color intensity N percent command\n+ Fixed problem with looping on mods that do not loop explicitely\n+ Fixed alt + dir scrolling in the char editor\n+ Fixed not being able to click the rightmost char in the char editor\n+ Readded unmark (alt + u) to robot editor (mysteriously disappeared??)\n+ Fixed key label so it returns proper unicode values\n+ The player and pushable robots can now be pushed by the push command\n+ Fixed bug where you could clone the player by switching boards\n+ Fixed bug where you could either turn off overlay or switch to boards\n  that don't have it while in overlay edit mode...\n+ Fixed bug where remains of debug window would not be cleared in editor\n  if the board width is too small\n+ Fixed bug where turning off the menu with a board too small would mess\n  things up\n+ Fixed bug where run lengths were saved one too large... this could fix\n  stability problems in at least occasional cases (with saved worlds or\n  save games, at least)\n+ Fixed placing solid things beneath robots (like bombs)\n+ Added support for a keyboard plus in the char editors\n+ Fixed previous button in SFX editor\n+ Made robot name box disappear when robot char box comes up...\n+ Fixed bug where mods restart after pressing P if they're the same mod\n  as what was playing before\n+ Fixed problem with changing params (with P) in the editor.\n+ Fixed bug where null boards were not being pruned from old worlds upon\n  load\n+ Made file name saving box larger (for saving games and worlds)\n+ Fixed bug where default (100%) palette intensity values would not be\n  applied to the palette a game loads with\n+ Fixed bug where exporting char sets that are full size caused a 0\n  byte charset to be exported (8bit wraparound)\n- Removed export text in the board editor. Don't think anyone wanted it...\n+ Added support for forms such as :line\n+ Fixed sporadic incompletion of strings without trailing quotes at the\n  end of the line\n+ Fixed bug where clearing/cutting the last line of a robot crashed MZX\n+ F4 in robot editor now works more generally\n+ Made line numbers in robot editor error report start at 1\n+ Added ctrl + G to go to a line in the robot editor (ala nano)\n+ Made it possible to change a robot's color in the editor\n+ Fixed bug where spitfire, seekers, and missiles didn't hurt something\n  immediate adjacent to whatever shot it\n+ Fixed editing of spitfires\n+ Made default speed 4 again (5, bleh)\n+ Readded quicksave/quickload\n+ Readded F8 clear (always works - be careful)\n+ Fixed autorepeat problems for spacepressed/delpressed.\n+ Wrapped around sprite values so LogiCow's bad code would work (HTMCIAB)\n+ Cleared block commands inbetween board changing and other things like\n  that\n+ Fixed bug where MZX would crash if put dir player overwrote the robot\n  doing it\n+ Fixed bug where playing SAMs would eventually crash MZX\n+ Fixed some mod * problems (hopefully?)\n+ Fixed bug where pasting blocks over the edge of the board in the editor\n  would cause MZX to crash\n+ Uses new GDM2S3M source that fixes some bugs. If your converted GDM's\n  have problems, delete the S3M's it generated.\n\nAugust 9, 2004 - First release, MZX 2.80 BETA\n\n\n"
  },
  {
    "path": "docs/counter_list.txt",
    "content": "Counters:\nname                      r/w      g/n/f/b    i\n\"ABSn\"                    r           f       *\n\"ACOS\"                    r           f       *\n\"AMMO\"                    r w         n       *\n\"ARCTANdy,dx\"             r w         f       *\n\"ASIN\"                    r           f       *\n\"ATAN\"                    r           f       *\n\"BCHn,n\"                  r           b       *\n\"BCOn,n\"                  r           b       *\n\"BIDn,n\"                  r w         b       *\n\"BPRn,n\"                  r w         b       *\n\"BIMESG\"                    w         f       *\n\"BIT_PLACE\"               r w         n       *\n\"BLUE_VALUE\"              r w         f       *\n\"BOARD_CHAR\"              r           b       *\n\"BOARD_COLOR\"             r           b       *\n\"BOARD_H\"                 r           b       *\n\"BOARD_ID\"                  w         b       *\n\"BOARD_PARAM\"               w         b       *\n\"BOARD_W\"                 r           b       *\n\"BOARD_X\"                 r           n       *\n\"BOARD_Y\"                 r           n       *\n\"BULLETTYPE\"              r w         b       *\n\"BUTTONS\"                 r           b       *\n\"BYTE\"                    r w         n       *\n\"CHAR\"                    r w         n       *\n\"CHAR_BYTE\"                           f       *\n\"CHAR_X\"                  r w         b       *\n\"CHAR_Y\"                  r w         b       *\n\"COINS\"                   r w         n       *\n\"COMMANDS\"                  w         b       *\n\"COS\"                     r           f       *\n\"CURRENT_COLOR\"           r w         n       *\n\"CURSORSTATE\"             r w         n       *\n\"C_DIVISIONS\"             r w         b       *\n\"DATE_DAY\"                r           f       *\n\"DATE_MONTH\"              r           f       *\n\"DATE_WEEKDAY\"            r           f       *\n\"DATE_YEAR\"               r           f       *\n\"DIVIDER\"                 r w         b       *\n\"ENTER_MENU\"              r w         n       *\n\"F2_MENU\"                 r w         n       *\n\"FIRSTXPOS\"               r w         n       *\n\"FIRSTYPOS\"               r w         n       *\n\"FREAD\"                   r           f       *\n\"FREAD_COUNTER\"           r           f       *\n\"FREAD_DELIMITER\"         r w         b       *\n\"FREAD_OPEN\"              r           f       *\n\"FREAD_POS\"               r w         b       *\n\"FWRITE\"                    w         f       *\n\"FWRITE_APPEND\"           r           f       *\n\"FWRITE_COUNTER\"          r w         f       *\n\"FWRITE_DELIMITER\"        r w         b       *\n\"FWRITE_MODIFY\"           r           f       *\n\"FWRITE_OPEN\"             r           f       *\n\"FWRITE_POS\"              r w         b       *\n\"GEMS\"                    r w         n       *\n\"GREEN_VALUE\"             r w         f       *\n\"HEALTH\"                    w         g       *\n\"HELP_MENU\"               r w         n       *\n\"HIBOMBS\"                 r w         n       *\n\"HORIZPLD\"                r           b       *\n\"INPUT\"                   r w         b       *\n\"INPUTSIZE\"               r w         b       *\n\"INT\"                     r w         n       *\n\"INT2BIN\"                 r w         f       *\n\"INVINCO\"                   w         g       *\n\"KEY\"                     r           b       *\n\"KEY_CODE\"                r           b       *\n\"KEY_PRESSED\"             r           b       *\n\"KEY_PRESSEDn\"            r           b       *\n\"KEY_RELEASE\"             r           b       *\n\"KEYn\"                    r           b       *\n\"LASTXPOS\"                r w         n       *\n\"LASTYPOS\"                r w         n       *\n\"LAVA_WALK\"               r w         g       *\n\"LIVES\"                   r w         g       *\n\"LOAD_BC\"                 r           f       *\n\"LOAD_GAME\"               r           f       *\n\"LOAD_MENU\"               r w         b       *\n\"LOAD_ROBOT\"              r           f       *\n\"LOBOMBS\"                 r w         n       *\n\"LOCALn\"                  r w         b       *\n\"LOOPCOUNT\"               r w         b       *\n\"MAXn,n\"                  r w         f       *\n\"MBOARDX\"                 r           b       *\n\"MBOARDY\"                 r           b       *\n\"MINn,n\"                  r w         f       *\n\"MOD_FREQUENCY\"           r w         b       *\n\"MOD_ORDER\"               r w         b       *\n\"MOD_POSITION\"            r w         b       *\n\"MOUSEPX\"                 r w         b       *\n\"MOUSEPY\"                 r w         b       *\n\"MOUSEX\"                  r w         b       *\n\"MOUSEY\"                  r w         b       *\n\"MULTIPLIER\"              r w         b       *\n\"MZX_SPEED\"               r w         b       *\n\"OCHn,n\"                  r           b       *\n\"OCOn,n\"                  r           b       *\n\"OVERLAY_CHAR\"            r           b       *\n\"OVERLAY_COLOR\"           r           b       *\n\"OVERLAY_MODE\"            r           b       *\n\"OVERLAY_X\"               r           n       *\n\"OVERLAY_Y\"               r           n       *\n\"PIXEL\"                   r w         f       *\n\"PLAYERDIST\"              r           b       *\n\"PLAYERFACEDIR\"           r w         b       *\n\"PLAYERLASTDIR\"           r w         b       *\n\"PLAYERX\"                 r           b       *\n\"PLAYERY\"                 r           b       *\n\"RED_VALUE\"               r w         b       *\n\"RID<robot name>\"         r           b       *\n\"ROBOT_ID\"                r           b       *\n\"ROBOT_ID_<robot name>\"   r           b       *\n\"Rn.<counter>             r           b       *\n\"SAVE_BC\"                 r           f       *\n\"SAVE_GAME\"               r           f       *\n\"SAVE_ROBOT\"              r           f       *\n\"SAVE_WORLD\"              r           f       *\n\"SCORE\"                   r w         b       *\n\"SCROLLEDX\"               r           b       *\n\"SCROLLEDY\"               r           b       *\n\"SIN\"                     r           f       *\n\"SMZX_Bn\"                 r w         b       *\n\"SMZX_Gn\"                 r w         b       *\n\"SMZX_MODE\"               r w         f       *\n\"SMZX_PALETTE\"            r           f       *\n\"SMZX_Rn\"                 r w         b       *\n\"SPACELOCK\"                 w         f       *\n\"SPR_CLISTn\"              r           b       *\n\"SPR_COLLISIONS\"          r           b       *\n\"SPR_NUM\"                 r w         b       *\n\"SPR_YORDER\"                w         f       *\n\"SPRn_CCHECK\"               w         f       *\n\"SPRn_CHEIGHT\"            r w         b       *\n\"SPRn_CLIST\"                w         f       *\n\"SPRn_CWIDTH\"             r w         b       *\n\"SPRn_CX\"                 r w         b       *\n\"SPRn_CY\"                 r w         b       *\n\"SPRn_HEIGHT\"             r w         b       *\n\"SPRn_OFF\"                r w         f       *\n\"SPRn_OFFONEXIT\"          r w         f       *\n\"SPRn_OFFSET\"             r w         b       *\n\"SPRn_OVERLAID\"             w         f       *\n\"SPRn_OVERLAY\"              w         f       *\n\"SPRn_REFX\"               r w         b       *\n\"SPRn_REFY\"               r w         b       *\n\"SPRn_SETVIEW\"              w         f       *\n\"SPRn_STATIC\"               w         b       *\n\"SPRn_SWAP\"                 w         f       *\n\"SPRn_TCOL\"               r w         b       *\n\"SPRn_UNBOUND\"            r w         f       *\n\"SPRn_VLAYER\"               w         f       *\n\"SPRn_WIDTH\"              r w         b       *\n\"SPRn_X\"                  r w         b       *\n\"SPRn_Y\"                  r w         b       *\n\"SPRn_Z\"                  r w         b       *\n\"SQRTn\"                   r           f       *\n\"TAN\"                     r           f       *\n\"THISX\"                   r           b       *\n\"THISY\"                   r           b       *\n\"THIS_CHAR\"               r           b       *\n\"THIS_COLOR\"              r           b       *\n\"TIME\"                    r w         n       *\n\"TIMERESET\"               r w         b       *\n\"TIME_HOURS\"              r           f       *\n\"TIME_MILLIS\"             r           f       *\n\"TIME_MINUTES\"            r           f       *\n\"TIME_SECONDS\"            r           f       *\n\"UCHn,n\"                  r           b       *\n\"UCOn,n\"                  r           b       *\n\"UIDn,n\"                  r w         b       *\n\"UPRn,n\"                  r w         b       *\n\"VCHn,n\"                  r w         b       *\n\"VCOn,n\"                  r w         b       *\n\"VERTPLD\"                 r           b       *\n\"VIEWPORT_HEIGHT\"         r           b       *\n\"VIEWPORT_WIDTH\"          r           b       *\n\"VIEWPORT_X\"              r           b       *\n\"VIEWPORT_Y\"              r           b       *\n\"VLAYER_HEIGHT\"           r w         b       *\n\"VLAYER_SIZE\"             r w         b       *\n\"VLAYER_WIDTH\"            r w         b       *\n\"XPOS\"                    r w         n       *\n\"YPOS\"                    r w         n       *\n\nStrings:\nname                      r/w      g/n/f/b    i\n\"BOARD_NAME\"              r           b       *\n\"BOARD_SCAN\"              r           b       *\n\"INPUT\"                   r           b       *\n\"MOD_NAME\"                r           b       *\n\"ROBOT_NAME\"              r           b       *\n\"FREAD\"                   r           f       *\n\"FWRITE\"                    w         f       *\n\nLegend:\n\ng - Normal counter with side-effects\nn - Normal counter\nf - Function counter\nb - Points to a C variable\n"
  },
  {
    "path": "docs/cycles_and_commands.txt",
    "content": "                     COMMAND NAME                       | CYCLE ENDED |\n                                                        |             |\n% \"string\"                                              |    NO(1)    |\n& \"string\"                                              |    NO(1)    |\n* \"string\"                                              |     NO      |\n. \"string\"                                              |     NO      |\n. \"@string\"                                             |     NO      |\n/ \"string\"                                              |    YES(2)   |\n: \"label\"                                               |    NO(3)    |\n? \"counter\" \"label\" \"string\"                            |    NO(1)    |\n? \"label\" \"string\"                                      |    NO(1)    |\n[ \"string\"                                              |    NO(1)    |\n| \"label\"                                               |     NO      |\nABORT LOOP                                              |     NO      |\nASK \"string\"                                            |    NO(1)    |\nAVALANCHE                                               |     NO      |\nBECOME [color] [thing] [param]                          |     NO      |\nBECOME NONLAVAWALKER                                    |     NO      |\nBECOME NONPUSHABLE                                      |     NO      |\nBECOME LAVAWALKER                                       |     NO      |\nBECOME PUSHABLE                                         |     NO      |\nBLIND #                                                 |     NO      |\nBOARD [dir] \"string\"                                    |     NO      |\nBOARD [dir] NONE                                        |     NO      |\nBULLETCOLOR [color]                                     |     NO      |\nBULLETE [char]                                          |     NO      |\nBULLETN [char]                                          |     NO      |\nBULLETS [char]                                          |     NO      |\nBULLETW [char]                                          |     NO      |\nCENTER MESG                                             |     NO      |\nCHANGE [color] [thing] [param] [color] [thing] [param]  |     NO      |\nCHANGE CHAR ID # [char]                                 |     NO      |\nCHANGE OVERLAY [color] [char] [color] [char]            |     NO      |\nCHANGE OVERLAY [color] [color]                          |     NO      |\nCHANGE SFX # \"string\"                                   |     NO      |\nCHANGE THICK ARROW CHAR [dir] [char]                    |     NO      |\nCHANGE THIN ARROW CHAR [dir] [char]                     |     NO      |\nCHAR [char]                                             |     NO      |\nCHAR EDIT [char] # # # # # # # # # # # # # #            |     NO      |\nCLEAR MESG                                              |     NO      |\nCLIP INPUT                                              |     NO      |\nCOLOR [color]                                           |     NO      |\nCOLOR FADE OUT                                          |     NO      |\nCOLOR FADE IN                                           |     NO      |\nCOLOR INTENSITY # PERCENT                               |     NO      |\nCOLOR INTENSITY # # PERCENT                             |     NO      |\nCOPY # # # #                                            |     NO      |\nCOPY [dir] [dir]                                        |     NO      |\nCOPY BLOCK # # # # # #                                  |     NO      |\nCOPY CHAR [char] [char]                                 |     NO      |\nCOPY OVERLAY BLOCK # # # # # #                          |     NO      |\nCOPY BLOCK # # # # \"@filename\" #                        |     NO      |\nCOPY OVERLAY BLOCK # # # # \"@filename\" #                |     NO      |\nCOPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"@filename\" #        |     NO      |\nCOPY BLOCK # # # # \"$string\" #                          |     NO      |\nCOPY OVERLAY BLOCK # # # # \"$string\" #                  |     NO      |\nCOPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"$string\" #          |     NO      |\nCOPY BLOCK # # # # \"#x\" \"#y\"                            |     NO      |\nCOPY OVERLAY BLOCK # # # # \"#x\" \"#y\"                    |     NO      |\nCOPY BLOCK \"#x\" \"#y\" # # # #                            |     NO      |\nCOPY OVERLAY BLOCK \"#x\" \"#y\" # # # #                    |     NO      |\nCOPY (OVERLAY) BLOCK \"#x1\" \"#y1\" # # \"#x2\" \"#y2\"        |     NO      |\nCOPYROBOT \"Robot\"                                       |     YES     |\nCOPYROBOT # #                                           |     YES     |\nCOPYROBOT [dir]                                         |     YES     |\nCYCLE #                                                 |     YES     |\nDEC \"counter\" #                                         |     NO      |\nDEC \"counter\" RANDOM # #                                |     NO      |\nDEC \"$string\" #                                         |     NO      |\nDIE                                                     |     NO      |\nDIE ITEM                                                |     NO      |\nDISABLE MESG EDGE                                       |     NO      |\nDISABLE SAVING                                          |     NO      |\nDIVIDE \"counter\" #                                      |     NO      |\nDOUBLE \"counter\"                                        |     NO      |\nDUPLICATE SELF # #                                      |     NO      |\nDUPLICATE SELF [dir]                                    |     NO      |\nEND                                                     |     YES     |\nEND MOD                                                 |     NO      |\nEND PLAY                                                |     NO      |\nEND SAM                                                 |     NO      |\nENDGAME                                                 |     NO      |\nENDLIFE                                                 |     NO      |\nENABLE MESG EDGE                                        |     NO      |\nENABLE SAVING                                           |     NO      |\nENABLE SENSORONLY SAVING                                |     NO      |\nENEMY BULLETCOLOR [color]                               |     NO      |\nENEMY BULLETE [char]                                    |     NO      |\nENEMY BULLETN [char]                                    |     NO      |\nENEMY BULLETS [char]                                    |     NO      |\nENEMY BULLETW [char]                                    |     NO      |\nEXCHANGE PLAYER POSITION                                |     YES     |\nEXCHANGE PLAYER POSITION #                              |     YES     |\nEXCHANGE PLAYER POSITION # DUPLICATE SELF               |     YES     |\nEXPLODE #                                               |     NO      |\nFIREWALKER #                                            |     NO      |\nFILLHEALTH                                              |     NO      |\nFLIP CHAR [char] [dir]                                  |     NO      |\nFREEZETIME #                                            |     NO      |\nGIVE # [item]                                           |     NO      |\nGIVEKEY [color]                                         |     NO      |\nGIVEKEY [color] \"label\"                                 |    NO(4)    |\nGO [dir] #                                              |    YES(5)   |\nGOTO \"label\"                                            |    NO(4)    |\nGOTO \"#return\"                                          |     NO      |\nGOTO \"#top\"                                             |     NO      |\nGOTOXY # #                                              |     YES     |\nHALF \"counter\"                                          |     NO      |\nIF \"counter\" !<>_= # \"label\"                            |    NO(4)    |\nIF [condition] \"label\"                                  |    NO(4)    |\nIF # # \"label\"                                          |    NO(4)    |\nIF [dir] PLAYER [color] [thing] [param] \"label\"         |    NO(4)    |\nIF [color] [thing] [param] # # \"label\"                  |    NO(4)    |\nIF [color] [thing] [param] [dir] \"label\"                |    NO(4)    |\nIF ALIGNEDROBOT \"Robot\" \"label\"                         |    NO(4)    |\nIF ANY [color] [thing] [param] \"label\"                  |    NO(4)    |\nIF FIRST STRING \"string\" \"label\"                        |    NO(4)    |\nIF NO [color] [thing] [param] \"label\"                   |    NO(4)    |\nIF NOT [color] [thing] [param] [dir] \"label\"            |    NO(4)    |\nIF NOT [condition] \"label\"                              |    NO(4)    |\nIF PLAYER # # \"label\"                                   |    NO(4)    |\nIF STRING \"string\" \"label\"                              |    NO(4)    |\nIF STRING MATCHES \"string\" \"label\"                      |    NO(4)    |\nIF STRING NOT \"string\" \"label\"                          |    NO(4)    |\nIF \"$string\" (equality) # \"label\"                       |    NO(4)    |\nIF \"$string\" (equality) \"text\" \"label\"                  |    NO(4)    |\nIF \"$string\" (equality) \"$string2\" \"label\"              |    NO(4)    |\nIF c?? Sprite_Colliding pNN # # \"label\"                 |    NO(4)    |\nIF c?? Sprite p?? # # \"label\"                           |    NO(4)    |\nINC \"$string\" \"text\"                                    |     NO      |\nINC \"$string\" \"$string2\"                                |     NO      |\nINC \"counter\" #                                         |     NO      |\nINC \"counter\" RANDOM # #                                |     NO      |\nINPUT STRING \"string\"                                   |    NO(1)    |\nJUMP MOD ORDER #                                        |     NO      |\nLAYBOMB [dir]                                           |     NO      |\nLAYBOMB HIGH [dir]                                      |     NO      |\nLAZERWALL [dir] #                                       |     NO      |\nLOAD CHAR SET \"file\"                                    |     NO      |\nLOAD PALETTE \"file\"                                     |     NO      |\nLOCKPLAYER                                              |     NO      |\nLOCKPLAYER ATTACK                                       |     NO      |\nLOCKPLAYER EW                                           |     NO      |\nLOCKPLAYER NS                                           |     NO      |\nLOCKSCROLL                                              |     NO      |\nLOCKSELF                                                |     NO      |\nLOOP #                                                  |     NO      |\nLOOP START                                              |     NO      |\nMESSAGE ROW #                                           |     NO      |\nMISSILECOLOR [color]                                    |     NO      |\nMOD \"file\"                                              |     NO      |\nMOD FADE # #                                            |     NO      |\nMOD FADE IN \"file\"                                      |     NO      |\nMOD FADE OUT                                            |     NO      |\nMOD SAM # #                                             |     NO      |\nMODULO \"counter\" #                                      |     NO      |\nMOVE ALL [color] [thing] [param] [dir]                  |     YES     |\nMOVE PLAYER [dir]                                       |     YES     |\nMOVE PLAYER [dir] \"label\"                               |     YES     |\nMULTIPLY \"counter\" #                                    |     NO      |\nNEUTRAL BULLETCOLOR [color]                             |     NO      |\nNEUTRAL BULLETE [char]                                  |     NO      |\nNEUTRAL BULLETN [char]                                  |     NO      |\nNEUTRAL BULLETS [char]                                  |     NO      |\nNEUTRAL BULLETW [char]                                  |     NO      |\nOPEN [dir]                                              |     NO      |\nOVERLAY ON                                              |     NO      |\nOVERLAY STATIC                                          |     NO      |\nOVERLAY TRANSPARENT                                     |     NO      |\nPERSISTENT GO \"string\"                                  |    YES(2)   |\nPLAY \"string\"                                           |     NO      |\nPLAY SFX \"string\"                                       |     NO      |\nPLAYER BULLETCOLOR [color]                              |     NO      |\nPLAYER BULLETE [char]                                   |     NO      |\nPLAYER BULLETN [char]                                   |     NO      |\nPLAYER BULLETS [char]                                   |     NO      |\nPLAYER BULLETW [char]                                   |     NO      |\nPLAYER CHAR [dir] [char]                                |     NO      |\nPLAYER CHAR [char]                                      |     NO      |\nPLAYERCOLOR [color]                                     |     NO      |\nPUSH [dir]                                              |     NO      |\nPUT [color] [thing] [param] # #                         |     NO      |\nPUT [color] [thing] [param] [dir]                       |     NO      |\nPUT [color] [char] OVERLAY # #                          |     NO      |\nPUT [color] [thing] [param] [dir] PLAYER                |     NO      |\nPUT PLAYER # #                                          |    YES(8)   |\nPUT PLAYER [dir]                                        |    YES(8)   |\nPUT \"@FILENAME.XXX\" Image_File [param] # #              |     NO      |\nPUT [color] Sprite [param] # #                          |     NO      |\nREL COUNTERS                                            |     NO      |\nREL PLAYER                                              |     NO      |\nREL SELF                                                |     NO      |\nREL COUNTERS FIRST                                      |     NO      |\nREL PLAYER FIRST                                        |     NO      |\nREL SELF FIRST                                          |     NO      |\nREL COUNTERS LAST                                       |     NO      |\nREL PLAYER LAST                                         |     NO      |\nREL SELF LAST                                           |     NO      |\nRESETVIEW                                               |     NO      |\nRESTORE \"label\" #                                       |     NO      |\nRESTORE PLAYER POSITION                                 |     YES     |\nRESTORE PLAYER POSITION #                               |     YES     |\nRESTORE PLAYER POSITION # DUPLICATE SELF                |     YES     |\nROTATECW                                                |     NO      |\nROTATECCW                                               |     NO      |\nSAM # \"file\"                                            |     NO      |\nSAVE PLAYER POSITION                                    |     NO      |\nSAVE PLAYER POSITION #                                  |     NO      |\nSCROLL CHAR [char] [dir]                                |     NO      |\nSCROLLARROW COLOR [color]                               |     NO      |\nSCROLLBASE COLOR [color]                                |     NO      |\nSCROLLCORNER COLOR [color]                              |     NO      |\nSCROLLPOINTER COLOR [color]                             |     NO      |\nSCROLLTITLE COLOR [color]                               |     NO      |\nSCROLLVIEW [dir] #                                      |     NO      |\nSCROLLVIEW POSITION # #                                 |     NO      |\nSEND # # \"label\"                                        |     NO      |\nSEND \"Robot\" \"label\"                                    |     NO      |\nSEND [dir] \"label\"                                      |     NO      |\nSEND [dir] PLAYER \"label\"                               |     NO      |\nSET \"counter\" #                                         |  NO(7)(10)  |\nSET \"counter\" RANDOM # #                                |     NO      |\nSET \"$string\" \"text\"                                    |     NO      |\nSET \"$string1\" \"$string2\"                               |     NO      |\nSET \"$string\" #                                         |     NO      |\nSET COLOR # # # #                                       |     NO      |\nSET EDGE COLOR [color]                                  |     NO      |\nSET MAXHEALTH #                                         |     NO      |\nSET MESG COLUMN #                                       |     NO      |\nSFX #                                                   |     NO      |\nSHOOT [dir]                                             |    NO(9)    |\nSHOOTMISSILE [dir]                                      |    NO(9)    |\nSHOOTSEEKER [dir]                                       |    NO(9)    |\nSLOWTIME #                                              |     NO      |\nSPITFIRE [dir]                                          |    NO(9)    |\nSTATUS COUNTER # \"counter\"                              |     NO      |\nSWAP WORLD \"file\"                                       |     NO      |\nSWITCH [dir] [dir]                                      |     NO      |\nTAKE # [item]                                           |     NO      |\nTAKE # [item] \"label\"                                   |    NO(4)    |\nTAKEKEY [color]                                         |     NO      |\nTAKEKEY [color] \"label\"                                 |    NO(4)    |\nTELEPORT PLAYER \"string\" # #                            |    YES(2)   |\nTRADE # [item] # [item] \"label\"                         |    NO(4)    |\nTRY [dir] \"label\"                                       |    YES(4)   |\nUNLOCKPLAYER                                            |     NO      |\nUNLOCKSCROLL                                            |     NO      |\nUNLOCKSELF                                              |     NO      |\nVIEWPORT # #                                            |     NO      |\nVIEWPORT SIZE # #                                       |     NO      |\nVOLUME #                                                |     NO      |\nWAIT #                                                  |    YES(5)   |\nWAIT MOD FADE                                           |     NO      |\nWAIT PLAY                                               |     NO      |\nWAIT PLAY \"string\"                                      |    YES(2)   |\nWALK [dir]                                              |    NO(6)    |\nWIND #                                                  |     NO      |\nWRITE OVERLAY [color] \"string\" # #                      |     NO      |\nZAP \"label\" #                                           |     NO      |\n\n(1) = While the cycle does not end, the act of opening default dialog boxes\nsuspends (almost) all other game action, including Robot processing, until the\nbox is closed. The only exceptions are the play of music/sound and the\nread/write actions of the various fread/fwrite counters during the loading of\ndefault \"[\"/\"%\"/\"&\" boxes.\n(2) = Does not end a cycle if the string is blank or a completely\ninvalid/inapplicable string.\n(3) = While it never ends the cycle in current MZX worlds, it can end the cycle\nin special cases in pre-port MZX worlds. If a loop consists of a label, a send\nto the same label, and none of the following commands: DEC, DEC RANDOM, DIVIDE,\nDOUBLE, GIVE, HALF, INC, INC RANDOM, LOOP FOR #, MODULO, MULTIPLY, SET,\nSET RANDOM, TAKE, TAKE \"label\", TRADE, then the label itself will end the cycle.\n(4) = Attempting to jump to labels that do not exist does not consume a cycle.\n(5) = Does not end a cycle if all # is/are 0.\n(6) = A special case exists where it currently executes a cycle end going\nthrough a transporter.\n(7) = A special case exists for the SAVE_GAME \"special\" counter which does end\nthe cycle if used.\n(8) = Does not end a cycle if any or all of the affected objects do not move.\n(9) = Ends a cycle in MZX v2.83 and worlds made in MZX v2.83.\n(10) = If the SAVE_GAME \"special\" counter is used with this command, the action\nhappens at the very end of the cycle.\n"
  },
  {
    "path": "docs/exotic.txt",
    "content": "Robotic revision ideas\n\nAfter gauging the response to a modestly altered Robotic language, it is is now\nmy intention to include in a later version of MZX a language which is 100%\ncompatible with Robotic as we know it now. It will, however, have several\nimportant extensions. Simplicity is a high priority. Syntax sensibility is\nalso important, but it is more necessary to retain familiar style than to\nintroduce new forms which may be deemed more writable for whatever reason. There\nwill be no major attempt to make Robotic resemble any one specific known language;\nrather features will be added as a result of determined necessity.\n\nIt should be noted that I have every intention of changing the Robotic bytecode\ndramatically. This has a few implications:\n\n- Source code will no longer have a one to one relationship with bytecode. This\n  means that bytecode cannot be disassembled into source code without losing\n  quite a bit of information; no attempt will be made to provide such a\n  disassembler as part of MZX itself. MZX currently stores bytecode alone in\n  its world files and to edit the robots this bytecode is disassembled. To edit\n  robots created with the new format (or indeed, converted from existing worlds)\n  the source code will have to be present. Since it is not necessary to have the\n  source code present to execute the game, there will likely exist world versions\n  that have had the source code stripped. These versions will be playable but\n  will not be editable. This is not necessarily an intentional form of protection,\n  but a way to reduce the size of the games tremendously.\n\n- The bytecode will be relatively simple to interpret. Most commands will be\n  linked as function calls by the interpreter. The bytecode itself will most\n  represent a relatively forth-like stack based machine. It will be lower level\n  than the current bytecode, and thus lower level than the Robotic language itself\n  (the current bytecode is merely a tokenization of Robotic code, and ensures\n  correct syntax so that need not be checked at runtime).\n\n- The bytecode will be done with efficiency in mind. I intend to provide as many\n  compile time optimizations to make code faster. Mainly, references that would\n  otherwise have to be deduced at runtime (such as the location of a counter with\n  a given name) will be hard linked if they can be deduced at compile time. These\n  will be considered \"static\" references and ones that cannot be deduced will be\n  considered \"dynamic\" or \"run-time\" references.\n\nWith the last point in mind, I would like to point out that I intend to specifically\ndocument optimization ideas at some point, but that this will not be considered\npart of the language specification itself. However, since some language forms will\ninevitably be far more conducive to efficient optimization, it will often be in the\nprogrammer's best interest to code in a certain style.\n\nI'd also like to note that I intend for the language to be very simple to parse and\ncompile (as it is now). This may add some extra overhead to writing the code, but I\nbelieve it is highly important that code can be quickly compiled on the fly. This is\nespecially true for games that will import robots externally at run-time. Besides,\nI'm hardly interested in writing a full blown LALR table traversing or\nrecursive-descent parser for MegaZeux at this time.\n\nNow, onto actual language specifications...\n\nA Robotic program will consist of several statements. A statement will be one of the\nfollowing:\n\nA single command - this \"does\" something; as seen in Robotic currently.\nA compiler directive - this describes the code in some way, limits names, etc. It\n  may allow for macros and comments will certainly fall under this category.\nA multi-command command - some commands may take, as a parameter, a command block.\n  These will be clearly delimited in some way.\n\nAll commands take a fixed number of parameters. Each parameter can be one of multiple\ntypes. For now it is best to consider a constant and a variable to be two different\ntypes; there are many instances where one will not be usable as another. For Robotic\nas we know it now, all types are basically analogous to either integers or strings.\nThus I'll skip the existing types (such as character, direction, parameter) and give\ngeneral types for variables. The type of a variable is denoted by its first character.\n\ninteger variable - currently known as a \"counter.\" Has no specific first character\n  (thus anything which doesn't fall under the other categories falls here). Integers\n  will have a size that is at LEAST 32bits, but you shouldn't rely on a certain size.\nstring variable - contains a series of characters. Currently MZX has a form of\n  slicing the string by using + and # operators in the name of the string (at the end).\n  An individual character can be returned in integer form with the . operator.\n  Denoted with a $ at the beginning of the name.\nreal variable - values with decimal portions. Guaranteed to have at leat double\n  precision (64bits), may have more. Denoted with a % at the beginning of the name.\n\nNow, using the name interpolation operators of MZX, it is possible to come to other\nmore complicated types. These can be considered aggregate types. Consider the following:\n\nint&int2& - this is an integer indexed array of ints. Putting the subscript at the end\n  will be preferable by convention and possibly for optimization issues.\nint&$string& - this is a string indexed or associative array (may be considered a\n  dictionary or set as well).\nint&int2&,&int3& - multi-dimensional array of ints.\n$str&int& - array of stringgs\n\netc.\n\nThese are just some examples. Actually, I would like to make functions appear to be\narrays! This is confusing, but a function is afterall a map from type a to type b,\nalthough there may side-effects to calling one. Nonetheless, it is quite possible to\nrepresent a function in exactly the same way a counter is represented. If you don't\nbelieve me, look at the current builtin functions such as cos and vco. They are used\nas counters but are actually functions! This is how I would like functions to be\ndefined:\n\n: \"<character denoting return type>name(parameter list)\"\n\nAs you can see they're like labels. Let me give some examples.\n\n: \"#printme\"\n  * \"~fhello!\"\n  goto #return\n\nOR\n\n: \"#printme\"\n  * \"~fhello!\"\n  return\n\nNotice the new return command - this is used to return a value. The # indicates that\nthere is actually no return value at all. The consequences of such a thing have yet\nto be determined, but this does cover existing subroutines nicely.\n\n: \"square(value)\"\n  return (value * value)\n\nReturns the square of int value. Lack of special first char indicates a return type of\nint.\n\n: \"$concat($str1, $str2)\"\n  return ($str1 + $str2)\n\nReturns the concatenation of two strings. Pointless since this is a primitive\noperation, but nicely demonstrates the use of functions.\n\nThere is a problem however - how to differentiate between functions and normal labels?\nWell, parentheses inside the label name will indicate that it has a parameter list. If\nthere is no special type character (returns int) then it must have an empty parem list,\notherwise the compiler will think it's a label. The reason why the compiler needs to be\nable to discern if it's a label or not is because a function is loaded into the counter\nlist. Note that () in label names do NOT act as expressions, so you can't interpolate\ndynamic values into label names. This should come as no surprise because MZX 2.80 won't\nallow such a thing to work as it is. They're simply not efficient, and I don't believe\nanyone has ever required them, even though they were indeed possible in DOS MZX.\n\n\nMulti command commands:\n\nAs noted earlier there will be some commands that take command blocks as parameters.\nThis will be useful for flow control constructs, such as:\n\nif value\n  ...\n  ...\n\nWhere the following indented lines are the code block. Value is anything that can be\nturned into an integer value (and as it stands now, anything can be). Most of the time\nit will be a variable or an expression, and if it evaluates to non-zero the block is\ndone, otherwise if it evaluates to 0 it isn't. This should be clear to anyone who has\nused just about any imperatively based programming language.\n\nSome other constructs are loops:\n\nloop for x\n  ...\n  ...\n\nAnd for legacy's sake,\n\nloopstart\n...\nloop for x\n\nNote that indentation is not necessary if something else ends the block such as this.\n\nwhile value\n  ...\n  ...\n\nDoes the block while the value is true.\n\ndowhile value\n  ...\n  ...\n\nDoes the block once, then repeated times while the value is true.\n\nfor initial value increment\n  ...\n\nA for loop. It should be noted here that while initial and increment are values, what\nthey actually evaluate to isn't important! Actually, expressions will most likely be\nable to have side-effects. Mainly, an assignment operator and combinations of assignment\nand other operators. Since = is used for comparison, the assignment operator will\nprobably be :=. Look at this example:\n\nfor ((x := 0) a ($str := \"\")) (x < 10) (x++)\n  set $str ($str + x + \" \")\n* ~f&$str&\n\nThis will print out..\n0 1 2 3 4 5 6 7 8 9 10\n\nThis was done in a Robotic familiar form, but more concise forms will probably be\npossible. Note that this allows string operations in expressions. This will probably be\nallowed, but I'm not yet completely certain. Expressions MAY be limited to integers.\n\nforeach pattern variable\n  ...\n  ...\n\nThis one is rather interesting. I'm not totally sure how to approach it, really, but I\nthink that pattern will be something of the form you would give in MZX (such as\na&b& where the type of b is known) and the variable will be a temporary variable that\nthe matching variables are stored in for each iteration. For now consider variable to\nbe a reference, although I haven't formally touched on references. That means that it\nisn't a copy of the matching value but IS the value, just with a different name. So if\nyou change it, you change the value. This can only really be made clear with an example..\n\nforeach str($strindex) $s\n  set $s ($s + \" blah\")\n\nThis will put \" blah\" at the end of every string str followed by a string. So let's say\nyou have the string stra, strb, and str0.. those would all match. Note that this is a\ngood way of doing something with respect to every element of an array, but it can be\nmore powerful than that. The details are still up in the air, but I'd really like to\nhave something like this.\n\n\nReferences\n\nI should at least mention the possability of these. References are important because\notherwise it's tough to pass arrays and whatnot to functions. You can actually do\nreferences already using string interpolation in variable names:\n\nset $ref \"name\"\nset ($ref) 10\n\nWill set the variable named \"name\" to 10. $ref thus refers to name.\nHowever I would like a way to do this that the compiler does more for you. I believe\nthat references will only be possible when calling functions. In case it wasn't clear,\nbe mindful of the fact that the above functions described were \"pass by value.\"\nConsider the following:\n\n: 'set to 10(&value)'\n  set value 10\n  return\n\nThis will set something passed to it to 10:\n\nset counter 1\ndo 'set to 10&counter&'\n* \"~f&counter&\"\n\nI should point out the \"do\" command which merely evaluates a supplied value, which\ncan thus call any functions. This is useful if you want to call a function merely for\nits side-effect and not a return type. Also, if it wasn't clear before, note the\nmethod of calling functions - the parameter is merely interpolated into the function\nname itself. This may be changed, though. And then notice the parameter value with the\n& before it. This indicates pass by reference (stolen from C++)\n\nAnyway, this will print out 10. When you call the function \"set to 10\", it passes the\nvariable named counter by reference, and \"value\" is thus bound to what's passed to it.\nChanging value changes whatever's passed to it. One note - you will not be able to\nrefer to references with the traditional && way. That is to say, they don't exist as\nactual counters with names. Something that may be alarming - if you were to do, within\nthat function, the following:\n\nset \"value&index&\" 10\n\n(let's say index has the value 100)\n\nThis would actually set the counter named counter100 to 10! Thus the reference\n\"replaces\" the name of the counter passed. This is useful in many cases, but care must\nbe taken not to accidentally use the name unintentionally within another name.\n\n\n\n\n\n"
  },
  {
    "path": "docs/exotic_translations.txt",
    "content": "File loading:\n\nopen file file_name (for) read (to) (number)\nopen file file_name (for) write (to) (number)\nopen file file_name (for) append (to) (number)\nopen file file_name (for) modify (to) (number)\n\n\nFile operations:\n\n\nPartial charset loading:\n\nload char set \"filename.chr\" offset\n\n\nCopy block:\n\ncopy (board) block x1 y1 w h [overlay|vlayer] x2 y2\ncopy overlay block x1 y1 w h [board|vlayer] x2 y2\ncopy vlayer block x1 y1 w h [board|overlay] x2 y2\n\ncopy [(board)|overlay|vlayer] block x1 y1 w h mzm file_name\ncopy [(board)|overlay|vlayer] block x1 y1 w h string str\n\n\nMZM loading:\n\nload mzm [BOARD|OVERLAY|VLAYER] x y w h file_name\n\n\nSprite collision:\n\nspr(number)_collides(x, y)\nspr(number)_collides_absolute(x, y)\nspr(number)_at(x, y)\n\n Examples:\n\n if (~spr0_collides1,0)\n   inc spr0_x 1\n\n if spr(0)_collides(a, b)\n   * \"~fouch\"\n\n\nTrig:\n\nsin(%value)\ncos(%value)\ntan(%value)\nasin(%value)\nacos(%value)\natan(%value)\natan2(%value)\n\n sin(val) * multiplier\n asin(val / divider)\n\n\n"
  },
  {
    "path": "docs/fileform.html",
    "content": "<!DOCTYPE html>\n<!--\n  MegaZeux File Format Reference\n\n  Copyright (C) 2020-2025 Lachesis - https://github.com/AliceLR/megazeux/\n\n  Permission to use, copy, modify, and distribute this document for any\n  purpose with or without fee is hereby granted, provided that the above\n  copyright notice and this permission notice appear in all copies.\n\n  THE DOCUMENT IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n  WITH REGARD TO THIS DOCUMENT INCLUDING ALL IMPLIED WARRANTIES OF\n  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS DOCUMENT.\n-->\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<title>MegaZeux File Formats</title>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"title\" content=\"MegaZeux File Format Reference\">\n<meta name=\"description\" content=\"Information about various file formats defined by MegaZeux.\">\n<meta name=\"twitter:card\" content=\"summary\">\n<meta name=\"twitter:title\" content=\"MegaZeux File Format Reference\">\n<meta name=\"twitter:description\" content=\"Information about various file formats defined by MegaZeux.\">\n<style>\n  body\n  {\n    /* Explicitly define colors for ELinks */\n    background-color: #fff;\n    color: #000;\n    margin: 6px;\n  }\n\n  #container\n  {\n    max-width: 780px;\n    margin: auto;\n    padding: 0px;\n  }\n\n  header, section\n  {\n    margin: 4px;\n    margin-bottom: 16px;\n  }\n\n  header, section.contents\n  {\n    padding: 8px 4px;\n  }\n\n  section.main\n  {\n    padding: 8px 4px;\n    padding-bottom: 72px;\n  }\n\n  section.inner\n  {\n    background-color: #F4F4F4;\n    border-radius: 5px;\n    padding: 2px 16px 8px;\n  }\n\n  code\n  {\n    background-color: #CCC;\n    border-radius: 3px;\n    padding: 1px 4px;\n    font-weight: bold;\n  }\n\n  code.block\n  {\n    display: inline-block;\n  }\n\n  code.example\n  {\n    display: block;\n    background-color: #555;\n    font-size: .95em;\n    color: #fff;\n    margin: 20px 8px;\n    padding: 0px 20px 16px;\n    overflow: auto;\n    white-space: pre;\n  }\n\n  div.image-wrapper\n  {\n    max-width: 640px;\n    margin: 0 auto;\n    overflow: auto;\n  }\n\n  div.scroll-wrapper\n  {\n    overflow: auto;\n  }\n\n  table\n  {\n    width: 99%;\n    margin: 8px 2px 24px;\n    border: 1px solid #aaa;\n    font-size: small;\n  }\n\n  tr:hover\n  {\n    background-color: rgba(127, 127, 255, .15);\n  }\n\n  td\n  {\n    padding: 1px 6px;\n    border: 4px solid transparent;\n  }\n\n  td.type\n  {\n    background-color: #ddd;\n    font-weight: bold;\n    white-space: pre-line;\n  }\n\n  thead td, thead th\n  {\n    border-bottom: 2px solid #aaa;\n    font-weight: bold;\n  }\n\n  th\n  {\n    border-right: 2px solid #aaa;\n    border-radius: 3px;\n    font-weight: bold;\n  }\n\n  .markdown\n  {\n    font-family: monospace;\n    white-space: pre;\n    overflow: auto;\n  }\n\n  li\n  {\n    padding-bottom: .9em;\n  }\n\n  .contents li\n  {\n    padding-bottom: .05em;\n  }\n</style>\n<script type=\"text/javascript\">\n  function thead(contents)\n  {\n    return \"<thead>\" + contents + \"</thead>\";\n  }\n\n  function tr(contents)\n  {\n    return \"<tr>\" + contents + \"</tr>\";\n  }\n\n  function th(contents)\n  {\n    return \"<th>\" + contents + \"</th>\";\n  }\n\n  function td(contents, cls=undefined)\n  {\n    if(typeof(cls) === \"string\")\n      return '<td class=\"' + cls + '\">' + contents + \"</td>\";\n    return \"<td>\" + contents + \"</td>\";\n  }\n\n  /**\n   * Also support this specific case of Markdown code quotes because it's\n   * nicer than having to put <code></code> everywhere.\n   */\n  function mdtocode(text)\n  {\n    if(text[0] == '`' && text[text.length - 1] == '`')\n      return \"<code>\" + text.substr(1, text.length - 2) + \"</code>\";\n    return text;\n  }\n\n  /**\n   * Convert an inline Markdown table into an HTML table.\n   * This is only guaranteed to support the Markdown tables in this file...\n   */\n  function mdtotable(element)\n  {\n    var hasvertheader = element.classList.contains(\"vert\");\n    var rows = element.innerHTML.split(/\\r?\\n/);\n    var table = \"\";\n    var hasfirstrow = false;\n    var hasseparator = false;\n    var numcolumns = 0;\n    var sizecolumn = -1;\n\n    rows.forEach(function (row)\n    {\n      var columns = row.split('|');\n      var hasfirstcolumn = false;\n      var contents = \"\";\n\n      if(!hasfirstrow)\n      {\n        numcolumns = columns.length - 2;\n        if(numcolumns <= 0)\n          return;\n\n        var hashorizheader = false;\n        for(var i = 1; i <= numcolumns; i++)\n        {\n          var t = columns[i].trim();\n          if(t.length)\n          {\n            hashorizheader = true;\n            if(t == \"Size\" || t == \"Data Type\")\n              sizecolumn = i;\n          }\n\n          if(hasvertheader && !hasfirstcolumn)\n          {\n            contents += th(t);\n            hasfirstcolumn = true;\n          }\n          else\n            contents += td(t);\n        }\n        if(hashorizheader)\n          table += thead(contents);\n        hasfirstrow = true;\n      }\n      else\n\n      if(!hasseparator)\n      {\n        if(columns.length - 2 != numcolumns)\n          return;\n\n        /**\n         * Assuming on good faith that this line is formatted properly\n         * otherwise because I really don't care...\n         */\n        hasseparator = true;\n      }\n      else\n\n      if(columns.length > 1)\n      {\n        var columns_present = columns.length - 1;\n        var columns_iter = columns_present < numcolumns ? columns_present : numcolumns;\n\n        for(var i = 1; i <= columns_iter; i++)\n        {\n          var t = columns[i].trim();\n          t = mdtocode(t);\n\n          if(hasvertheader && !hasfirstcolumn)\n          {\n            contents += th(t);\n            hasfirstcolumn = true;\n          }\n          else\n            contents += td(t, (i == sizecolumn) ? \"type\" : undefined);\n        }\n        for(var i = 0; i < numcolumns - columns_present; i++)\n          contents += td(\"\");\n\n        table += tr(contents);\n      }\n    });\n\n    element.innerHTML = \"<table>\" + table + \"</table>\";\n  }\n\n  window.onload = function()\n  {\n    if(document.getElementsByClassName !== \"undefined\")\n    {\n      var elements = document.getElementsByClassName(\"markdown\");\n      var length = elements.length;\n      for(var i = 0; i < length; i++)\n      {\n        if(elements[i].classList !== \"undefined\")\n          mdtotable(elements[i]);\n      }\n\n      /**\n       * This probably changed the size of the file, so if there's an anchor in\n       * the URL, try to jump to it...\n       */\n      if(window.location.href.indexOf('#') >= 0)\n        window.location.href = \"\" + window.location.href;\n    }\n  };\n</script>\n</head>\n<body>\n<div id=\"container\">\n  <header>\n    <h1>MegaZeux File Format Reference</h1>\n    <h3>MegaZeux 2.93d &mdash; June 9th, 2025</h3>\n    <p>\n      This guide contains specifications for most MegaZeux file formats.\n      This is a table-dense document and should be displayed in a window/viewport\n      at least 640px wide to reduce table overflow.\n    </p>\n  </header>\n\n  <hr>\n\n  <section class=\"contents\">\n    <h2>Contents</h2>\n    <ol>\n      <li><a href=\"#key\">Key for data types</a></li>\n      <li><a href=\"#chr\">Character Set (.CHR)</a></li>\n      <li><a href=\"#pals\">Palette Formats</a></li>\n      <ol type=\"i\">\n        <li><a href=\"#pal\">Palette (.PAL)</a></li>\n        <li><a href=\"#palidx\">Palette Indices (.PALIDX)</a></li>\n      </ol>\n      <li><a href=\"#world\">World and Save Format (all versions)</a></li>\n      <ol type=\"i\">\n        <li><a href=\"#worldheader\">World Format Header and Magic</a></li>\n        <li><a href=\"#saveheader\">Save Format Header and Magic</a></li>\n      </ol>\n      <li><a href=\"#world100\">World and Save Format (1.00)</a></li>\n      <ol type=\"i\">\n        <li><a href=\"#global100\">Global Data</a></li>\n        <li><a href=\"#boardlist100\">Board Names, Sizes, and Offsets</a></li>\n        <li><a href=\"#board100\">Boards and Board RLE Planes</a></li>\n        <li><a href=\"#boardparams100\">Board Parameters</a></li>\n        <li><a href=\"#boardobjs100\">Board Objects</a></li>\n        <li><a href=\"#robot100\">Robots</a></li>\n        <li><a href=\"#scroll100\">Scrolls</a></li>\n        <li><a href=\"#sensor100\">Sensors</a></li>\n      </ol>\n      <li><a href=\"#world200\">World and Save Format (2.00 through 2.84X)</a></li>\n      <ol type=\"i\">\n        <li><a href=\"#global200\">Global Data</a></li>\n        <ol type=\"a\">\n          <li><a href=\"#global200world1\">World Block 1</a></li>\n          <li><a href=\"#global200save1\">Save Block 1 (.SAV only)</a></li>\n          <li><a href=\"#global200world2\">World Block 2</a></li>\n          <li><a href=\"#global200save2\">Save Block 2 (.SAV only)</a></li>\n          <li><a href=\"#global200counters\">Counters List (.SAV only)</a></li>\n          <li><a href=\"#global200strings\">Strings List (2.80X through 2.84X) (.SAV only)</a></li>\n          <li><a href=\"#global200sprites\">Sprites List (2.65 through 2.84X) (.SAV only)</a></li>\n          <li><a href=\"#global200stringsDOS\">Strings List (2.68 through 2.70) (.SAV only)</a></li>\n          <li><a href=\"#global200mathfile\">Math and File IO (2.68 through 2.84X) (.SAV only)</a></li>\n          <li><a href=\"#global200smzx\">SMZX Data and Commands (2.69 through 2.84X) (.SAV only)</a></li>\n          <li><a href=\"#global200vlayer\">Vlayer (2.69c through 2.84X) (.SAV only)</a></li>\n        </ol>\n        <li><a href=\"#sfx200\">Custom SFX Table</a></li>\n        <li><a href=\"#boardlist200\">Board Names, Sizes, and Offsets</a></li>\n        <li><a href=\"#board200\">Boards</a></li>\n        <li><a href=\"#boardplanes200\">Board RLE Planes</a></li>\n        <li><a href=\"#boardparams200\">Board Parameters (2.00 through 2.82X)</a></li>\n        <li><a href=\"#boardparams283\">Board Parameters (2.83 and 2.84X)</a></li>\n        <li><a href=\"#boardobjs200\">Board Objects</a></li>\n        <li><a href=\"#robot200\">Robots</a></li>\n        <li><a href=\"#scroll200\">Scrolls</a></li>\n        <li><a href=\"#sensor200\">Sensors</a></li>\n      </ol>\n      <li><a href=\"#world290\">World and Save Format (2.90 onward)</a></li>\n      <ol type=\"i\">\n        <li><a href=\"#files290\">Files List</a></li>\n        <li><a href=\"#properties\">Properties Format</a></li>\n        <li><a href=\"#worldprop290\">World Properties</a></li>\n        <ol type=\"a\">\n          <li><a href=\"#statctrprop290\">Status Counter Properties</a></li>\n        </ol>\n        <li><a href=\"#sfx290\">Custom SFX Table</a></li>\n        <li><a href=\"#chars290\">Character Sets</a></li>\n        <li><a href=\"#pal290\">Palettes, Palette Indices, and Palette Intensities</a></li>\n        <ol type=\"a\">\n          <li><a href=\"#pal290old\">Prior to 2.93</a></li>\n        </ol>\n        <li><a href=\"#vlayer290\">Vlayer Planes</a></li>\n        <li><a href=\"#sprites290\">Sprite Properties (.SAV only)</a></li>\n        <li><a href=\"#counters290\">Counter and String Lists (.SAV only)</a></li>\n        <li><a href=\"#board290\">Board Properties</a></li>\n        <li><a href=\"#boardplanes290\">Board Planes</a></li>\n        <li><a href=\"#robot290\">Robot Properties</a></li>\n        <li><a href=\"#scroll290\">Scroll Properties</a></li>\n        <li><a href=\"#sensor290\">Sensor Properties</a></li>\n      </ol>\n      <li><a href=\"#mzb\">Board File (.MZB)</a></li>\n      <li><a href=\"#mzm\">Image File (.MZM)</a></li>\n      <ol type=\"i\">\n        <li><a href=\"#mzm3\">MZM3 (current)</a></li>\n        <li><a href=\"#mzm2\">MZM2</a></li>\n        <li><a href=\"#mzmx\">MZMX</a></li>\n      </ol>\n      <li><a href=\"#counters\">Counters File</a></li>\n      <li><a href=\"#sfx\">Custom SFX Table File (.SFX)</a></li>\n      <li><a href=\"#bytecode\">Robotic Bytecode (.BC)</a></li>\n      <li><a href=\"#license\">License</a></li>\n    </ol>\n  </section>\n\n  <hr>\n\n  <section id=\"key\" class=\"main\">\n    <h2>Key for data types</h2>\n    <p>\n      The data types used to describe the MZX file formats are as follows:\n    </p>\n    <div class=\"markdown\">\n| Data Type | Description                             | Min.        | Max. |\n|-----------|-----------------------------------------|-------------|------|\n| b         | byte (8-bit)                            | 0           | 255\n| bs        | byte (8-bit, signed)                    | -128        | 127\n| w         | word (16-bit, little endian)            | 0           | 65535\n| ws        | word (16-bit, little endian, signed)    | -32768      | 32767\n| d         | dword (32-bit, little endian)           | 0           | 4294967295\n| ds        | dword (32-bit, little endian, signed)   | -2147483648 | 2147483647\n| s#        | string of fixed length # (i.e. # bytes) |\n| s...      | string of variable length               |\n    </div>\n  </section>\n\n  <hr>\n\n  <section id=\"chr\" class=\"main\">\n    <h2>Character Set (.CHR)</h2>\n    <p>\n      This file format describes one or more MZX graphical characters. A CHR\n      file contains raw graphical data only. Each character in the CHR file\n      consists of 14 bytes representing each graphical row of the character.\n    </p>\n\n    <p>\n      Within the byte, each pixel of the row is encoded either as a\n      single bit (MZX) or as two bits (SMZX). The first byte of the next\n      character immediately follows the 14th byte of the previous character.\n    </p>\n\n    <p>\n      A typical character set file contains 256 chars (for a total size of 3584).\n      However, partial character sets are possible, as are character sets with\n      more than 256 chars. Complete and partial character sets can be loaded\n      from files or from MZX strings with the <code>load char set</code> command.\n      The bytes representing a character in a character set are also the same\n      as the values provided to the <code>char edit</code> command or to the\n      <code>CHAR_BYTE</code> counter.\n    </p>\n\n    <section id=\"chr-mzx\" class=\"inner\">\n      <h3>MZX</h3>\n      <p>\n        The most significant bit of the byte represents the leftmost pixel in\n        the row and the least significant bit of the byte represents the\n        rightmost pixel.\n      </p>\n\n      <p>\n        Example: setting the third, fifth, and seventh pixels.\n      </p>\n\n      <div class=\"markdown\">\n| 128 |  64 |  32 |  16 |   8 |   4 |   2 |   1 |\n|-----|-----|-----|-----|-----|-----|-----|-----|\n|   0 |   0 |   1 |   0 |   1 |   0 |   1 |   0 |\n      </div>\n\n      <div class=\"markdown vert\">\n|         |     |\n|---------|-----|\n| Binary  | `00101010`\n| Hex     | `2A`\n| Decimal | <code class=\"block\">  (128 * 0) <br> + (64 * 0) <br> + (32 * 1) <br> + (16 * 0) <br> +  (8 * 1) <br> +  (4 * 0) <br> +  (2 * 1) <br> +  (1 * 0) = 42 </code>\n      </div>\n    </section>\n\n    <section id=\"chr-smzx\" class=\"inner\">\n      <h3>SMZX</h3>\n      <p>\n        The most significant two bits of the byte represent the leftmost pixel\n        in the row and the least significant two bits represent the rightmost\n        pixel. The four colors are encoded as the following bit pairs and\n        quaternary digits:\n      </p>\n\n      <div class=\"markdown\">\n| Color | Binary | Quaternary |\n|-------|--------|------------|\n| 1     | `00`   | `0`\n| 2     | `01`   | `1`\n| 3     | `10`   | `2`\n| 4     | `11`   | `3`\n      </div>\n\n      <p>\n        Example: setting the first and second pixels to color 2, third to\n        color 4, and fourth to color 1.\n      </p>\n\n      <div class=\"markdown\">\n|  64 |  16 |   4 |   1 |\n|-----|-----|-----|-----|\n| `1` | `1` | `3` | `0` |\n      </div>\n\n      <div class=\"markdown vert\">\n|            |      |\n|------------|------|\n| Quaternary | `1130`\n| Binary     | `01011100`\n| Hex        | `5C`\n| Decimal    | <code class=\"block\">   (64 * 1) <br> + (16 * 1) <br> +  (4 * 3) <br> +  (1 * 0) = 92 </code>\n      </div>\n    </section>\n  </section>\n\n  <hr>\n\n  <section id=\"pals\" class=\"main\">\n    <h2>Palette Formats</h2>\n    <p>\n    </p>\n    <section id=\"pal\" class=\"inner\">\n      <h3>Palette (.PAL)</h3>\n      <p>\n        This file format describes an MZX palette. A PAL file contains raw\n        palette data only. Each color in the PAL file consists of 3 bytes\n        representing the red, green, and blue components of the color. Each RGB\n        component of a color is a value from 0 to 63 (inclusive).\n      </p>\n      <p>\n        A typical MZX or SMZX mode 1 PAL file contains 16 colors (48 bytes) and\n        a typical SMZX mode 2 or mode 3 pal file contains 256 colors (768 bytes).\n      </p>\n      <p>\n        The PAL file does not encode any SMZX 3 color index information (see PALIDX).\n      </p>\n\n      <h4>Examples</h4>\n      <div class=\"markdown\">\n| Color         | R   | G   | B   | Hex in-file: |\n|---------------|-----|-----|-----|--------------|\n| Black         | 0   | 0   | 0   | `00 00 00`\n| Dark Blue     | 0   | 0   | 42  | `00 00 2A`\n| Dark Green    | 0   | 42  | 0   | `00 2A 00`\n| Dark Cyan     | 0   | 42  | 42  | `00 2A 2A`\n| Dark Red      | 42  | 0   | 0   | `2A 00 00`\n| Dark Magenta  | 42  | 0   | 42  | `2A 00 2A`\n| Brown         | 42  | 21  | 0   | `2A 15 00`\n| Light Grey    | 42  | 42  | 42  | `2A 2A 2A`\n| Dark Grey     | 21  | 21  | 21  | `15 15 15`\n| Light Blue    | 21  | 21  | 63  | `15 15 3F`\n| Light Green   | 21  | 63  | 21  | `15 3F 15`\n| Light Cyan    | 21  | 63  | 63  | `15 3F 3F`\n| Light Red     | 63  | 21  | 21  | `3F 15 15`\n| Light Magenta | 63  | 21  | 63  | `3F 15 3F`\n| Yellow        | 63  | 63  | 21  | `3F 3F 15`\n| White         | 63  | 63  | 63  | `3F 3F 3F`\n      </div>\n    </section>\n\n    <section id=\"palidx\" class=\"inner\">\n      <h3>Palette Indices (.PALIDX)</h3>\n      <p>\n        This file format describes SMZX mode 3 subpalette indices. A PALIDX file\n        contains 256 sets of indices (one for each subpalette). Each set of\n        indices is a sequence of four bytes (for a total of 1024 bytes per file);\n        the first indicates color 1 of the subpalette, the second color 2, and so on.\n      </p>\n    </section>\n  </section>\n\n  <hr>\n\n  <section id=\"world\" class=\"main\">\n    <h2>World and Save Format (all versions)</h2>\n    <p>\n      This section documents the portions of the world and save formats that\n      are consistent between all MZX versions.\n    </p>\n\n    <section id=\"worldheader\" class=\"inner\">\n      <h3>World Format Header and Magic</h3>\n      <p>\n        Most world files begin with a 29-byte header. The only exceptions are\n        locked worlds from MegaZeux 1.xx, encrypted worlds from MegaZeux 2.00\n        through 2.6, and rearchived MegaZeux 2.90 worlds\n        (see <a href=\"#world290\">the 2.90+ world format</a>).\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s25  | World title <a href=\"#worldheadernote1\">(1)</a> <a href=\"#worldheadernote2\">(2)</a>\n| 25   | b    | Protection method (always zero)\n| 26   | s3   | Magic\n      </div>\n\n      <h4>MegaZeux 1.xx locked world header:</h4>\n      <div class=\"markdown\">\n        | Pos. | Size | Description |\n        |------|------|-------------|\n        | 0    | s25  | World title\n        | 25   | b    | Protection method (always between 1 and 3)\n        | 26   | b    | Length of password (0 to 15)\n        | 27   | s15  | Password (encrypted) <a href=\"#worldheadernote4\">(4)</a>\n        | 42   | s3   | Magic (not encrypted)\n      </div>\n\n      <h4>MegaZeux 2.00 through 2.6 encrypted world header:</h4>\n      <div class=\"markdown\">\n        | Pos. | Size | Description |\n        |------|------|-------------|\n        | 0    | s25  | World title\n        | 25   | b    | Protection method (always between 1 and 3)\n        | 26   | s15  | Password (encrypted) <a href=\"#worldheadernote4\">(4)</a>\n        | 41   | s3   | Magic (not encrypted)\n      </div>\n\n      <p>\n        The following magic values are valid:\n      </p>\n\n      <div class=\"markdown\">\n| Magic       | World/File format version |\n|-------------|---------------------------|\n| `MZX`       | 1.00\n| `MZ2`       | 2.00 through 2.51\n| `MZA`       | 2.51s1\n| `M\\x02\\x09` | 2.51s2 through 2.61 (including MZXAk, SMZX 1.00a)\n| `M\\x02\\x11` | Used for decrypted worlds by some port MZX versions.<br>Likely derived from misreading 2.51s2 magic <code>M\\002\\011</code> (octal) as hex.\n| `M\\x02\\x32` | 2.62 and 2.62b (from octal <code>M\\002\\062</code>)\n| `M\\x02\\x41` | 2.65 (from decimal <code>65</code>)\n| `M\\x02\\x44` | 2.68 (from decimal <code>68</code>) <a href=\"#worldheadernote3\">(3)</a>\n| `M\\x02\\x45` | 2.69 (from decimal <code>69</code>)\n| `M\\x02\\x46` | 2.69b\n| `M\\x02\\x48` | 2.69c\n| `M\\x02\\x49` | 2.70\n| `M\\x02\\x50` | 2.80X (from decimal <code>80</code>) (first port releases)\n| `M\\x02\\x51` | 2.81X (and so on...)\n| `M\\x02\\x52` | 2.82X\n| `M\\x02\\x53` | 2.83\n| `M\\x02\\x54` | 2.84X\n| `M\\x02\\x5A` | 2.90X\n| `M\\x02\\x5B` | 2.91X\n| `M\\x02\\x5C` | 2.92X\n| `M\\x02\\x5D` | 2.93X\n      </div>\n\n      <p>\n        The internal MZX version value is derived from the world magic by 1)\n        treating the last two bytes as a big-endian word if the magic is from\n        2.51s2 or later or 2) using <code>0x0100</code>, <code>0x0205</code>,\n        and <code>0x0208</code> for 1.00, 2.00, and 2.51s1 worlds (respectively).\n      </p>\n\n      <p>\n        In all world files with a password, the password can be decrypted with\n        the following algorithm:\n      </p>\n\n      <code class=\"example\">\n/**\n * This code is copied from the MZX source code, which is GPL 2+ licensed.\n */\nchar magic_code[16] = \"\\xE6\\x52\\xEB\\xF2\\x6D\\x4D\\x4A\\xB7\\x87\\xB2\\x92\\x88\\xDE\\x91\\x24\";\n\nfor(i = 0; i &lt; 15; i++)\n{\n  password[i] ^= magic_code[i];\n  password[i] -= 0x12 - protection_method;\n  password[i] ^= 0x8D;\n}\n      </code>\n\n      <p>\n        In MegaZeux 2.00 through 2.6 encrypted worlds,\n        the rest of the MZX file is XORed with a byte calculated from the\n        password. The bundled MegaZeux utility \"checkres\" can output the\n        unencrypted password and XOR value when given the -v option, if needed.\n      </p>\n\n      <h4>Notes:</h4>\n      <ol>\n        <li id=\"worldheadernote1\">\n          The world title is a duplicate of the name of the title board.\n        </li>\n        <li id=\"worldheadernote2\">\n          The world title is null terminated within its fixed buffer.\n        </li>\n        <li id=\"worldheadernote3\">\n          The MZX 2.68 magic was erroneously never saved with world files,\n          meaning worlds from MZX 2.68 relying on MZX 2.68 features won't work\n          (as they are versioned for MZX 2.65). Resaving these worlds in a\n          newer version (such as MZX 2.69) usually fixes this issue.\n        </li>\n        <li id=\"worldheadernote4\">\n          The password field is null terminated if it is 14 or fewer characters\n          long. If the password is 15 characters long, it is NOT null terminated,\n          and the null terminator must be added by the decryption algorithm.\n        </li>\n      </ol>\n      </ol>\n    </section>\n\n    <section id=\"saveheader\" class=\"inner\">\n      <h3>Save Format Header and Magic</h3>\n      <p>\n        Save files use a different header from world files, but the idea is\n        generally the same.\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s5   | Magic\n| 5    | w    | World version (separate from file format version)\n| 7    | b    | Current board number\n      </div>\n\n      <h4>Format prior to MegaZeux 2.82</h4>\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s5   | Magic\n| 5    | b    | Current board number\n      </div>\n\n      <div class=\"markdown\">\n| Magic         | File Format Version |\n|---------------|---------------------|\n| `MZSAV`       | 1.00\n| `MZSV2`       | 2.00 through 2.51\n| `MZXSA`       | 2.51s1\n| `MZS\\x02\\x09` | 2.51s2 through 2.61 (including MZXAk, SMZX 1.00a)\n| `MZS\\x02\\x32` | 2.62 and 2.62b\n| `MZS\\x02\\x41` | 2.65\n| `MZS\\x02\\x44` | 2.68\n| `MZS\\x02\\x45` | 2.69\n| `MZS\\x02\\x46` | 2.69b\n| `MZS\\x02\\x48` | 2.69c\n| `MZS\\x02\\x49` | 2.70\n| `MZS\\x02\\x50` | 2.80X (first port releases)\n| `MZS\\x02\\x51` | 2.81X\n| `MZS\\x02\\x52` | 2.82X\n| `MZS\\x02\\x53` | 2.83\n| `MZS\\x02\\x54` | 2.84X\n| `MZS\\x02\\x5A` | 2.90X\n| `MZS\\x02\\x5B` | 2.91X\n| `MZS\\x02\\x5C` | 2.92X\n| `MZS\\x02\\x5D` | 2.93X\n      </div>\n\n      <p>\n        The internal MZX file format version is derived from the save magic the\n        same way it is derived from the world magic, and is always the same as\n        the MZX version that produced the save file.\n      </p>\n      <p>\n        The world version field (saved separately here) is always the same as\n        the world version derived from the world magic when the world was\n        initially loaded. Notably, this value is saved as a <i>little endian</i>\n        word (whereas it is represented as a <i>big endian</i> word in the world\n        magic string). In versions where this field is not present, the world\n        version should be treated as the same as the file format version.\n      </p>\n    </section>\n  </section>\n\n  <hr>\n\n  <section id=\"world100\" class=\"main\">\n    <h2>World and Save Format (1.00)</h2>\n    <p>\n      MegaZeux 1.00 world (.MZX) and save (.SAV) files share very similar formats,\n      so they are documented together here. This format is sufficiently different\n      enough from the 2.00 format that it has been given its own section.\n    </p>\n\n    <section id=\"global100\" class=\"inner\">\n      <h3>Global Data</h3>\n      <p>\n        World data immediately follows the header.\n      </p>\n\n      <h4 id=\"global100world1\">World Block 1</h4>\n      <p>\n        Total block length is 3890 bytes.\n      </p>\n      <div class=\"markdown\">\n| Pos.  | Size     | Description |\n|-------|----------|-------------|\n| 0     | b * 3584 | Character set (see <a href=\"#chr\">Character Sets</a>)\n| 3584  | b * 128  | ID Chars block 1 (thing chars) <a href=\"#global100note1\">(1)</a>\n| 3712  | b * 178  | ID Chars block 2 (animations and colors) <a href=\"#global100note2\">(2)</a>\n      </div>\n      <h5>Notes</h5>\n      <ol>\n        <li id=\"global100note1\">\n          Worlds in this format do not contain ID chars block 3 (damages),\n          so the 1.x default damages should be substituted for these instead.\n          Likewise, goop (ID 34) didn't exist in 1.x, so char 176 should should\n          be patched in for its character.\n        </li>\n        <li id=\"global100note2\">\n          This is the first 178 bytes of the 2.00 ID chars block 2. The missing\n          values are bullet chars (306-317), player chars (318-321), player\n          color (322), missile color (323), and bullet colors (324-326).\n          Equivalents to these values are stored separately later in the file.\n        </li>\n      </ol>\n\n      <h4 id=\"global100save1\">Save Block 1 (.SAV only)</h4>\n      <p>\n        This block is present in save files only. Total block length is 16.\n      </p>\n      <div class=\"markdown\">\n| Pos.  | Size  | Description |\n|-------|-------|-------------|\n| 0     | b * 16| Keys\n      </div>\n\n      <h4 id=\"global100world2\">World Block 2</h4>\n      <p>\n        Total block length is 72.\n      </p>\n      <div class=\"markdown\">\n| Pos.  | Size     | Description |\n|-------|----------|-------------|\n| 0     | s15 * 4  | Status counter names (null terminated)\n| 60    | b * 4    | Bullet chars\n| 64    | b        | Player char\n| 65    | b        | Saved player X position\n| 66    | b        | Saved player Y position\n| 67    | b        | Saved player board number\n| 68    | b        | Viewport edge color\n| 69    | b        | Player color\n| 70    | b        | Bullet color\n| 71    | b        | Missile color\n      </div>\n\n      <h4 id=\"global100save2\">Save Block 2 (.SAV only)</h4>\n      <p>\n        This block is present in save files only. Total block length is 9.\n      </p>\n      <div class=\"markdown\">\n| Pos.  | Size  | Description |\n|-------|-------|-------------|\n| 0     | b     | Scroll base color\n| 1     | b     | Scroll corner color\n| 2     | b     | Scroll pointer color\n| 3     | b     | Scroll title color\n| 4     | b     | Scroll arrow color\n| 5     | d     | Score\n      </div>\n\n      <h4 id=\"global100world3\">World Block 3</h4>\n      <p>\n        Total block length is 18.\n      </p>\n      <div class=\"markdown\">\n| Pos.  | Size     | Description |\n|-------|----------|-------------|\n| 0     | b        | First board number\n| 1     | b        | Endgame board number (>=128: no endgame board)\n| 2     | b        | Death board number (>=129: same position, 128: restart board)\n| 3     | b        | Endgame board teleport X\n| 4     | b        | Endgame board teleport Y\n| 5     | b        | Death board teleport X\n| 6     | b        | Death board teleport Y\n| 7     | ws       | Starting lives\n| 9     | ws       | Lives limit\n| 11    | ws       | Starting health\n| 13    | ws       | Health limit\n| 15    | b        | Enemies' bullets hurt other enemies\n| 16    | b        | Game over SFX enabled (1) or disabled (0)\n| 17    | b        | Unused\n      </div>\n\n      <h4 id=\"global100counters\">Counters List (.SAV only)</h4>\n      <p>\n        This block is only present in save files and has an variable length.\n        The counters list begins with the number of counters.\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size    | Description |\n|------|---------|-------------|\n| 0    | b       | Number of counters = N\n      </div>\n      <p>\n        A list of <code>N</code> counters follows, each in the following format:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s15  | Counter name (null terminated)\n| 15   | w    | Counter value (unsigned)\n      </div>\n    </section>\n\n    <section id=\"boardlist100\" class=\"inner\">\n      <h3>Board Names, Sizes, and Offsets</h3>\n\n      <p>\n        Following the board count byte is the board names list and the board sizes and\n        offsets table.\n      </p>\n\n      <p>\n        For each board:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s25  | Board names (null terminated)\n      </div>\n\n      <p>\n        For each board:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | d    | Size of board data within the file\n| 4    | d    | Offset of board data within the file\n      </div>\n\n      <p>\n        Typically, board data immediately follows these tables.\n      </p>\n    </section>\n\n    <section id=\"board100\" class=\"inner\">\n      <h3>Boards and Board RLE Planes</h3>\n      <p>\n        MegaZeux 1.xx does not support overlays or board modes; these should be\n        treated as disabled and 100x100, respectively. Boards begin immediately\n        with the board RLE plane data. Board contents are compressed with run\n        length encoding, which is different from its MegaZeux 2.xx counterpart.\n        Each board always has exactly 6 planes in the following order:\n      </p>\n\n      <div class=\"markdown\">\n| name              | description |\n|-------------------|-------------|\n| level_id          | Board thing IDs\n| level_color       | Board thing colors\n| level_param       | Board thing parameters\n| level_under_id    | Board thing IDs for floors beneath non-floor things\n| level_under_color | Board thing colors for floors beneath non-floor things\n| level_under_param | Board thing parameters for floors beneath non-floor things\n      </div>\n\n      <p>\n        Each compressed plane starts with the following fields:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | b    | Width <a href=\"#board100note1\">(1)</a>\n| 1    | b    | Height\n      </div>\n\n      <p>\n        The RLE data follows after, and consists entirely of pairs of run\n        lengths and byte values. The RLE plane can be decompressed with the\n        following algorithm:\n      </p>\n\n      <code class=\"example\">\n/**\n * This code fragment is a simplified version of the RLE unpacking code from\n * MegaZeux's source code, and is therefore GPL 2+ licensed.\n */\n\nint size = width * height;\nint i = 0;\nwhile(i &lt; size)\n{\n  runsize = *(stream++);\n  current_char = *(stream++);\n\n  if(runsize &gt; size - i)\n    runsize = size - i; // MegaZeux emits an error if this occurs.\n\n  for(int j = 0; j &lt; runsize; j++)\n    plane[i++] = current_char;\n}\n      </code>\n\n      <p>\n        The RLE stream ends when <code>(board width * board height)</code>\n        bytes have been expanded from the stream, and the next compressed plane\n        or the board parameters block immediately follows.\n      </p>\n\n      <h5>Notes</h5>\n      <ol>\n        <li id=\"board100note1\">\n          There MAY be an extra run of length 0 padding between any two planes;\n          if the width byte read is 0, skip the following byte and read the\n          width and height from the next two bytes. (It's not clear why old\n          versions of MegaZeux emit these runs, but this is exactly how VER1TO2\n          handles these. I have never seen this in practice. -Lachesis)\n        </li>\n      </ol>\n    </section>\n\n    <section id=\"boardparams100\" class=\"inner\">\n      <h3>Board Parameters</h3>\n      <p>\n        The board parameters block follows the board RLE planes.\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s13  | Mod playing (null terminated)\n| 13   | b    | Viewport X\n| 14   | b    | Viewport Y\n| 15   | b    | Viewport width\n| 16   | b    | Viewport height\n| 17   | b    | Can shoot?\n| 18   | b    | Can bomb?\n| 19   | b    | Fire burns brown?\n| 20   | b    | Fire burns space?\n| 21   | b    | Fire burns fakes?\n| 22   | b    | Fire burns trees?\n| 23   | b    | Explosions leave (0: space, 1: ash, 2: fire)\n| 24   | b    | Save mode (0: enabled, 1: disabled, 2: sensor only)\n| 25   | b    | Forest to floor?\n| 26   | b    | Collect bombs?\n| 27   | b    | Fire burns forever?\n| 28   | b    | Board # to north\n| 29   | b    | Board # to south\n| 30   | b    | Board # to east\n| 31   | b    | Board # to west\n| 32   | b    | Restart if zapped?\n| 33   | w    | Time limit\n| 35   | b    | Last key pressed\n| 36   | ws   | Input (numeric value)\n| 38   | b    | Inputsize\n| 39   | b    | Volume\n| 40   | b    | Player locked NS?\n| 41   | b    | Player locked EW?\n| 42   | b    | Player attack locked?\n| 43   | s81  | Input (string value) (null terminated)\n| 124  | b    | Blind duration <a href=\"#boardparams100note1\">(1)</a>\n| 125  | b    | Firewalker duration\n| 126  | b    | Unused\n| 127  | b    | Freeze time duration\n| 128  | b    | Slow time duration\n| 129  | b    | Unused\n| 130  | b    | Unused\n| 131  | b    | Unused\n| 132  | b    | Wind duration\n| 133  | b    | Last player direction\n| 134  | s81  | Bottom message (null terminated)\n| 215  | b    | Bottom message timer\n| 216  | b    | Lazerwall timer\n| 217  | b    | Bottom message row\n| 218  | b    | Bottom message column (-1: center) <a href=\"#boardparams100note2\">(2)</a>\n| 219  | bs   | Scroll relative X offset\n| 220  | bs   | Scroll relative Y offset\n      </div>\n\n      <h5>Notes</h5>\n      <ol>\n        <li id=\"boardparams100note1\">\n          All 5 status durations are board variables in MegaZeux 1.x. This\n          can be observed most easily in the \"Flame Cavern\" board of Caverns.\n        </li>\n        <li id=\"boardparams100note2\">\n          The default behavior is centered, but 1.x world files contain 0 here by\n          default. A value of 0 needs to be corrected to -1 when loading worlds.\n        </li>\n      </ol>\n    </section>\n\n    <section id=\"boardobjs100\" class=\"inner\">\n      <h3>Board Objects</h3>\n      <p>\n        Following the board parameters are the robot, scroll, and sensor lists.\n        Each list is stored as a single byte <code>N</code> indicating the\n        number of robots, scrolls, or sensors on the board (0 to 255), followed\n        by <code>N</code> stored objects of that type. Each object in its\n        respective list immediately follows the prior, and the next list starts\n        immediately following the last object in the previous list. The board\n        data ends after all three lists and their objects have been read.\n      </p>\n      <p>\n        The board robots/scrolls/sensors in the list count from ID 1 upward, as\n        ID 0 is the global robot and is invalid for scrolls and sensors. Since\n        objects in the file are sequential, robot/scroll/sensor IDs may have to\n        be reassigned when saving worlds or saves in these versions to optimize\n        out gaps in the list.\n      </p>\n    </section>\n\n    <section id=\"robot100\" class=\"inner\">\n      <h3>Robots</h3>\n      <p>\n        Each robot is 37 bytes plus the length of the robot program long.\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Default | Description |\n|------|------|---------|-------------|\n| 0    | d    |         | Program length in bytes. FILEFORM.TXT claims signed...\n| 4    | d    |         | unused <a href=\"#robot100note1\">(1)</a>\n| 8    | s15  |         | Robot name (null-terminated)\n| 23   | b    | 2       | Robot char\n| 24   | d    | 1       | Current position in program\n| 28   | b    | 0       | Position within current line (for <code>WAIT</code>/<code>GO</code>/etc)\n| 29   | b    | 1       | Robot cycle\n| 30   | b    | 0       | Cycle counter\n| 31   | b    | 1       | Bullet type (0: player, 1: neutral, 2: enemy)\n| 32   | b    | 0       | Locked status (0: unlocked, 1: locked)\n| 33   | b    | 0       | Can lavawalk (0: no, 1: yes)\n| 34   | b    | 0       | Walk direction (0: idle)\n| 35   | b    | 0       | Last touch direction\n| 36   | b    | 0       | Last shot direction\n| 37   | s... |         | Robot program\n      </div>\n\n      <h4>Notes</h4>\n      <ol>\n        <li id=\"robot100note1\">\n          DOS MZX versions wrote robot, scroll, and sensor structs directly to\n          world or save files. The unused 4-byte field at offset 4 probably\n          corresponds to the program memory pointer in these versions, if\n          MegaZeux 2.x is any indication. Scrolls likewise have an unused 4\n          byte field.\n        </li>\n      </ol>\n    </section>\n\n    <section id=\"scroll100\" class=\"inner\">\n      <h3>Scrolls</h3>\n      <p>\n        Each scroll block is 10 bytes plus the length of the scroll text long.\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | w    | Number of lines\n| 2    | d    | unused\n| 6    | d    | Scroll text length in bytes\n| 10   | s... | Scroll text <a href=\"#scroll100note1\">(1)</a>\n      </div>\n\n      <h4>Notes</h4>\n      <ol>\n        <li id=\"scroll100note1\">\n          Scrolls in MegaZeux 1.x have a unique form of color coding that is\n          not available in versions after. If the first character of a line is\n          '!', the byte immediately following it will be used to apply the\n          palette corresponding to that byte to the entire line. For example,\n          if a line begins with <code>33 8e</code> hex, the line will use\n          color 8 as the background color and color 14 as the foreground color.\n          Unlike 2.x scrolls, the scroll text does not begin with <code>01</code>.\n        </li>\n      </ol>\n    </section>\n\n    <section id=\"sensor100\" class=\"inner\">\n      <h3>Sensors</h3>\n      <p>\n        Each sensor block is 31 bytes long.\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s15  | Sensor name (null-terminated)\n| 15   | b    | Sensor char\n| 16   | s15  | Robot to message (null-terminated)\n      </div>\n    </section>\n  </section>\n\n  <hr>\n\n  <section id=\"world200\" class=\"main\">\n    <h2>World and Save Format (2.00 through 2.84X)</h2>\n    <p>\n      MegaZeux 2.00 world (.MZX) and save (.SAV) files share very similar formats,\n      so they are documented together here. This section also tries to note changes\n      in these formats and when they occurred, but save format modifications between\n      versions are somewhat of a mess, so please report any inaccuracies in this\n      document.\n    </p>\n\n    <section id=\"global200\" class=\"inner\">\n      <h3>Global Data</h3>\n      <p>\n        World data immediately follows the header. For ease of readability, it\n        has been broken down into several blocks. Each block starts immediately\n        after the previous block ends.\n      </p>\n\n      <h4 id=\"global200world1\">World Block 1</h4>\n      <p>\n        Total block length is 4129 bytes.\n      </p>\n      <div class=\"markdown\">\n| Pos.  | Size     | Description |\n|-------|----------|-------------|\n| 0     | b * 3584 | Character set (see <a href=\"#chr\">Character Sets</a>)\n| 3584  | b * 128  | ID Chars block 1 (thing chars)\n| 3712  | b * 195  | ID Chars block 2 (animations and colors)\n| 3907  | b        | ID Chars missile color (block 2 no. 323)\n| 3908  | b * 3    | ID Chars bullet colors (block 2 no. 324 through 326)\n| 3911  | b * 128  | ID Chars block 3 (damage)\n| 4039  | s15 * 6  | Status counter names (null terminated)\n      </div>\n\n      <h4 id=\"global200save1\">Save Block 1 (.SAV only)</h4>\n      <p>\n        This block is only present in save files. Total block length is 73 bytes\n        plus the length of the current playing mod name.\n      </p>\n      <div class=\"markdown\">\n| Pos.  | Size  | Description |\n|-------|-------|-------------|\n| 0     | b * 16| Keys\n| 16    | b     | Blind duration\n| 17    | b     | Firewalker duration\n| 18    | b     | Freeze time duration\n| 19    | b     | Slow time duration\n| 20    | b     | Wind duration\n| 21    | w * 8 | Saved player X positions\n| 37    | w * 8 | Saved player Y positions\n| 53    | b * 8 | Saved player board numbers <a href=\"#global200save1note3\">(3)</a>\n| 61    | b     | Player color\n| 62    | b * 3 | Under player ID/color/param <a href=\"#global200save1note1\">(1)</a>\n| 65    | b     | Message edges enabled (1) or disabled (0)\n| 66    | b     | Scroll base color\n| 67    | b     | Scroll corner color\n| 68    | b     | Scroll pointer color\n| 69    | b     | Scroll title color\n| 70    | b     | Scroll arrow color\n| 71    | w     | Mod playing length <a href=\"#global200save1note2\">(2)</a>\n| 73    | s...  | Mod playing (NOT null terminated) <a href=\"#global200save1note2\">(2)</a>\n      </div>\n\n      <h5>Notes</h5>\n      <ol>\n        <li id=\"global200save1note1\">\n          The under player fields are used as an extra \"layer\" beneath where\n          the player is standing. This is so e.g. the player can be placed on\n          top of something that is on top of a floor and the floor won't be\n          erased when the thing immediately beneath the player is pushed to the\n          under layer. These fields are effectively unused though, because there\n          is a duplicate in <a href=\"#global200save2\">the second save block</a>\n          that is always loaded over these.\n        </li>\n        <li id=\"global200save1note2\">\n          Prior to MZX 2.83, the mod playing length and mod playing fields were\n          a single 13 byte null terminated buffer (total block length 84 bytes).\n          Prior to MZX 2.51s2, the current playing mod was not saved at all\n          (as it was always the same as the current board mod) (total block\n          length 71 bytes).\n        </li>\n        <li id=\"global200save1note3\">\n          Due to a bug, MegaZeux 2.80 through 2.84c instead save the board\n          numbers of the first two saved positions as endian-dependent dwords\n          (4 bytes) and ignore the last six.\n        </li>\n      </ol>\n\n      <h4 id=\"global200world2\">World Block 2</h4>\n      <p>\n        Total block length is 72.\n      </p>\n      <div class=\"markdown\">\n| Pos.  | Size       | Description |\n|-------|------------|-------------|\n| 0     | b          | Viewport edge color\n| 1     | b          | First board number\n| 2     | b          | Endgame board number (255: no endgame board)\n| 3     | b          | Death board number (254: same position, 255: restart board)\n| 4     | w          | Endgame board teleport X\n| 6     | w          | Endgame board teleport Y\n| 8     | b          | Game over SFX enabled (1) or disabled (0)\n| 9     | w          | Death board teleport X\n| 11    | w          | Death board teleport Y\n| 13    | ws         | Starting lives\n| 15    | ws         | Lives limit\n| 17    | ws         | Starting health\n| 19    | ws         | Health limit\n| 21    | b          | Enemies' bullets hurt other enemies\n| 22    | b          | Clear messages and projectiles on exit\n| 23    | b          | Can only play world from a <code>SWAP WORLD</code>\n| 24    | b * 3 * 16 | Palette (see <a href=\"#pal\">Palettes</a>)\n      </div>\n\n      <h4 id=\"global200save2\">Save Block 2 (.SAV only)</h4>\n      <p>\n        This block is only present in save files has a length of 24 bytes.\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size    | Description |\n|------|---------|-------------|\n| 0    | b * 16  | Palette intensities <a href=\"#global200save2note1\">(1)</a>\n| 16   | b       | Faded state (1: faded out, 0: faded in)\n| 17   | w       | Player restart X\n| 19   | w       | Player restart Y\n| 21   | b * 3   | Under player ID/color/param <a href=\"#global200save2note2\">(2)</a>\n      </div>\n\n      <h5>Notes</h5>\n      <ol>\n        <li id=\"global200save2note1\">\n          Only the first 16 palette intensities were ever saved for SMZX modes\n          2 and 3.\n        </li>\n        <li id=\"global200save2note2\">\n          Yes, this is duplicate info from the first save block, and no, I\n          don't know why MZX saved this info twice. This copy is always loaded\n          over the copy in save block 1.\n        </li>\n      </ol>\n\n      <h4 id=\"global200counters\">Counters List (.SAV only)</h4>\n      <p>\n        WARNING: The format of the save blocks following this frequently changed\n        from version to version. If there are any inaccuracies in the legacy\n        world format documentation, they're probably below. MZX versions prior\n        to 2.90 regularly dropped support for saves from older MZX versions due\n        to the format changes in this part of the format being too messy to support.\n        Difficulty in supporting changes to the world/save format are one of the main\n        reasons <a href=\"#world290\">the world format was replaced in MZX 2.90.</a>\n      </p>\n\n      <p>\n        This block is only present in save files and has an variable length.\n        The counters list begins with the number of counters.\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size    | Description |\n|------|---------|-------------|\n| 0    | <a href=\"#global200countersnote1\">(1)</a>     | Number of counters = N\n      </div>\n      <p>\n        A list of <code>N</code> counters follows, each in the following format\n        in MZX versions 2.00 through 2.80X:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s15  | Counter name (null terminated)\n| 15   | ws   | Counter value (2.80X saves this as a <code>ds</code> instead)\n      </div>\n      <p>\n        In MZX versions 2.81X and up, the counters are instead stored in the\n        following format:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | ds   | Counter value\n| 4    | d    | Name length\n| 8    | s... | Counter name (NOT null terminated)\n      </div>\n\n      <h5>Notes</h5>\n      <ol>\n        <li id=\"global200countersnote1\">\n          In MZX 2.51 and below, the maximum number of counters saved was 59,\n          and the number of counters was saved as a byte.\n          In DOS versions afterward, the maximum was 1020, and the number of\n          counters was saved as a word (2 bytes).\n          In MZX 2.80 to 2.84X, the maximum number of counters was lifted, and\n          the number of counters was saved as a dword (4 bytes).\n        </li>\n        <li>\n          Save files created by MZX 2.84c will save two additional \"counters\"\n          for <code>MZX_SPEED</code> and the <code>MZX_SPEED</code> locked\n          status. These are named \"mzx_speed\" and \"_____lock_speed\". This vile\n          hack was made unnecessary in MZX 2.90 by the new world format.\n        </li>\n      </ol>\n\n      <h4 id=\"global200strings\">Strings List (2.80X through 2.84X) (.SAV only)</h4>\n      <p>\n        The strings block in versions 2.80X through 2.84X follows the counters\n        block and is stored in a similar way.\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size    | Description |\n|------|---------|-------------|\n| 0    | d       | Number of strings = N\n      </div>\n\n      <p>\n        A list of <code>N</code> string definitions follows. In 2.80X the strings\n        were saved in the following format:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size    | Description |\n|------|---------|-------------|\n| 0    | s15     | String name (null terminated)\n| 15   | s64     | String value (null terminated)\n      </div>\n\n      <p>\n        In versions 2.81b and onward <a href=\"#global200stringsnote1\">(1)</a>,\n        the following structure is used instead:\n      </p>\n      <div class=\"markdown\">\n| Pos.  | Size    | Description |\n|-------|---------|-------------|\n| 0     | d       | String name length = X\n| 4     | d       | String value length = Y\n| 8     | b * X   | String name (NOT null terminated)\n| 8 + X | b * Y   | String value (NOT null terminated)\n      </div>\n\n      <h5>Notes</h5>\n      <ol>\n        <li id=\"global200stringsnote1\">\n          MegaZeux 2.81 tries to load strings in the format provided above but\n          saves them in a different format unique to this release. Combined with\n          no format validation at the time, the result was that 2.81 saves with\n          strings always crashed on load.\n        </li>\n      </ol>\n\n      <h4 id=\"global200sprites\">Sprites List (2.65 through 2.84X) (.SAV only)</h4>\n      <p>\n        In versions that support sprites, the sprites list begins with\n        <code>N</code> sprite definitions, where <code>N</code> = 64 for MZX\n        2.65 through 2.69b and <code>N</code> = 256 for MZX 2.69c and all releases\n        afterward.\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | w    | Sprite X\n| 2    | w    | Sprite Y\n| 4    | w    | Sprite reference X\n| 6    | w    | Sprite reference Y\n| 8    | b    | Sprite color\n| 9    | b    | Sprite flags <a href=\"#global200spritesnote1\">(1)</a>\n| 10   | b    | Sprite width\n| 11   | b    | Sprite height\n| 12   | bs   | Sprite collision X\n| 13   | bs   | Sprite collision Y\n| 14   | b    | Sprite collision width\n| 15   | b    | Sprite collision height\n      </div>\n\n      <p>\n        The sprite data is followed by the following global sprite variables:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size     | Description |\n|------|----------|-------------|\n| 0    | b        | Total number of active sprites\n| 1    | b        | Sprite Y order enabled (1) or disabled (0)\n| 2    | w        | Current number of sprite collisions\n      </div>\n\n      <p>\n        The sprite collision list follows. In MZX 2.65 through 2.69b it is\n        stored as follows:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size     | Description |\n|------|----------|-------------|\n| 4    | bs * N   | Sprite collision list (2.65: N=32, 2.68 through 2.69b: N=64)\n      </div>\n\n      <p>\n        From MZX 2.69c onward, the sprite collision list is saved as words and\n        is always 256 in length:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size     | Description |\n|------|----------|-------------|\n| 4    | ws * 256 | Sprite collision list\n      </div>\n\n      <h5>Notes</h5>\n      <ol>\n        <li id=\"global200spritesnote1\">\n          Sprite flags:\n          <div class=\"markdown\">\n| Flag   | Description |\n|--------|-------------|\n| `0x01` | Sprite is initialized\n| `0x02` | Sprite ccheck 1 is enabled\n| `0x04` | Sprite is drawn over the overlay\n| `0x08` | Sprite uses reference colors\n| `0x10` | Sprite is static relative to the viewport\n| `0x20` | Sprite ccheck 2 is enabled\n| `0x40` | Sprite references the vlayer\n          </div>\n        </li>\n      </ol>\n\n      <h4 id=\"global200stringsDOS\">Strings List (2.68 through 2.70) (.SAV only)</h4>\n      <p>\n        Strings in DOS MZX versions do not have names and are instead numbered\n        from 1 to 16. They are saved as fixed length buffers after the sprites\n        list (rather than before it as in port versions). The length of this\n        block is always 16*64=1024 (these weren't saved in versions with 16 char\n        strings).\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size        | Description |\n|------|-------------|-------------|\n| 0    | b * 16 * 64 | Strings data (16 strings, fixed length null terminated values)\n      </div>\n\n      <h4 id=\"global200mathfile\">Math and File IO (2.68 through 2.84X) (.SAV only)</h4>\n      <p>\n        MZX versions 2.68 through 2.70 save the math and file IO vars as follows:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | ws   | Multiplier\n| 2    | ws   | Divider\n| 4    | ws   | Circle divisions\n| 6    | s12  | Input filename (null terminated)\n| 18   | d    | Input file position\n| 22   | s12  | Output filename (null terminated)\n| 24   | d    | Output file position\n      </div>\n\n      <p>\n        MZX versions 2.80X through 2.82X save the following:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | w<a href=\"#global200mathnote1\">(1)</a> | Multiplier\n| 2    | w<a href=\"#global200mathnote1\">(1)</a> | Divider\n| 4    | w<a href=\"#global200mathnote1\">(1)</a> | Circle divisions\n| 6    | b    | Built-in message status (0: disabled, 1: enabled)\n| 7    | s12  | Input filename (null terminated)\n| 19   | d    | Input file position\n| 21   | s12  | Output filename (null terminated)\n| 25   | d    | Output file position\n      </div>\n\n      <p>\n        MZX 2.83 saves the following:\n      </p>\n      <div class=\"markdown\">\n| Pos.       | Size | Description |\n|------------|------|-------------|\n| 0          | w<a href=\"#global200mathnote1\">(1)</a> | Multiplier\n| 2          | w<a href=\"#global200mathnote1\">(1)</a> | Divider\n| 4          | w<a href=\"#global200mathnote1\">(1)</a> | Circle divisions\n| 6          | b    | Built-in message status (0: disabled, 1: enabled)\n| 7          | w    | Input filename length = X\n| 9          | s... | Input filename (NOT null terminated)\n| 9 + X      | d    | Input file position\n| 13 + X     | w    | Output filename length = Y\n| 15 + X     | s... | Output filename (NOT null terminated)\n| 15 + X + Y | d    | Output file position\n      </div>\n\n      <p>\n        MZX 2.84X saves the following:\n      </p>\n      <div class=\"markdown\">\n| Pos.       | Size | Description |\n|------------|------|-------------|\n| 0          | w<a href=\"#global200mathnote1\">(1)</a> | Multiplier\n| 2          | w<a href=\"#global200mathnote1\">(1)</a> | Divider\n| 4          | w<a href=\"#global200mathnote1\">(1)</a> | Circle divisions\n| 6          | ws   | Input file delimiter\n| 8          | ws   | Output file delimiter\n| 10         | b    | Built-in shooting status (<code>SPACELOCK</code>) (0: disabled, 1: enabled)\n| 11         | b    | Built-in message status (0: disabled, 1: enabled)\n| 12         | w    | Input filename length = X\n| 14         | s... | Input filename (NOT null terminated)\n| 14 + X     | d    | Input file position\n| 18 + X     | w    | Output filename length = Y\n| 20 + X     | s... | Output filename (NOT null terminated)\n| 20 + X + Y | d    | Output file position\n      </div>\n\n      <h5>Notes</h5>\n      <ol>\n        <li id=\"global200mathnote1\">\n          These variables were still saved as a word from 2.80X through 2.84X\n          despite the internal variable being expanded to a signed dword, meaning\n          these were truncated and then loaded as unsigned words. This wasn't\n          corrected until MZX 2.90. OOPS!\n        </li>\n      </ol>\n\n      <h4 id=\"global200smzx\">SMZX Data and Commands (2.69 through 2.84X) (.SAV only)</h4>\n      <p>\n        MZX versions 2.69 and up save the SMZX mode here.\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | w    | SMZX mode (0: disabled)\n      </div>\n\n      <p>\n        If SMZX mode is 2 or 3 (2.81+), the SMZX palette (but NOT intensities,\n        which were unsaved prior to 2.90) follows:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size        | Description |\n|------|-------------|-------------|\n| 0    | b * 3 * 768 | SMZX palette (see <a href=\"#pal\">Palettes</a>)\n      </div>\n\n      <p>\n        The commands value follows. MZX 2.69 through MZX 2.83 saved it as a word\n        (despite the internal value being expanded to a dword in 2.80):\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | w    | Commands\n      </div>\n\n      <p>\n        MZX 2.84X finally corrected this to a dword:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | d    | Commands\n      </div>\n\n      <h4 id=\"global200vlayer\">Vlayer (2.69c through 2.84X) (.SAV only)</h4>\n      <p>\n        The vlayer is saved as follows in 2.69c and 2.70:\n      </p>\n      <div class=\"markdown\">\n| Pos.  | Size      | Description |\n|-------|-----------|-------------|\n| 0     | w         | Vlayer width\n| 2     | w         | Vlayer height\n| 4     | b * 32768 | Vlayer chars\n| 32772 | b * 32768 | Vlayer colors\n      </div>\n\n      <p>\n        MegaZeux 2.80X saves this instead:\n      </p>\n      <div class=\"markdown\">\n| Pos.        | Size      | Description |\n|-------------|-----------|-------------|\n| 0           | w         | Vlayer width = X\n| 2           | w         | Vlayer height = Y\n| 4           | b * X * Y | Vlayer chars\n| 4 + (X * Y) | b * X * Y | Vlayer colors\n      </div>\n\n      <p>\n        This was changed to the following in 2.81X:\n      </p>\n      <div class=\"markdown\">\n| Pos.  | Size  | Description |\n|-------|-------|-------------|\n| 0     | d     | Vlayer size = X\n| 4     | w     | Vlayer width\n| 6     | w     | Vlayer height\n| 8     | b * X | Vlayer chars\n| 8 + X | b * X | Vlayer colors\n      </div>\n\n      <h4>Global Robot Offset and Board Count</h4>\n      <p>\n        In an unencrypted world file this will always be at offset 4230.\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | d    | Offset of global robot within the file\n| 4    | b    | Number of boards (1-250) or 0 for SFX table\n      </div>\n    </section>\n\n    <section id=\"sfx200\" class=\"inner\">\n      <h3>Custom SFX Table</h3>\n\n      <p>\n        If the board count byte is 0, a custom SFX table is present. If there\n        is not a custom SFX table in the world file, the world will use the MZX\n        default SFX instead. The SFX table begins with:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | w    | Length of the SFX table in bytes\n      </div>\n\n      <p>\n        Then, for each SFX (50 total):\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | b    | Length of SFX string, including null terminator (69 max)\n| 1    | s... | SFX string\n      </div>\n\n      <p>\n        Finally:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | b    | Number of boards in the world (1-250)\n      </div>\n    </section>\n\n    <section id=\"boardlist200\" class=\"inner\">\n      <h3>Board Names, Sizes, and Offsets</h3>\n\n      <p>\n        Following the board count byte is the board names list and the board sizes and\n        offsets table.\n      </p>\n\n      <p>\n        For each board:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s25  | Board names (null terminated)\n      </div>\n\n      <p>\n        For each board:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | d    | Size of board data within the file\n| 4    | d    | Offset of board data within the file\n      </div>\n\n      <p>\n        Typically, board data immediately follows these tables, and the global robot\n        is placed at the very end of the world file.\n      </p>\n    </section>\n\n    <section id=\"board200\" class=\"inner\">\n      <h3>Boards</h3>\n      <p>\n        Each board begins with the following fields:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | b    | Board mode <a href=\"#board200note1\">(1)</a>\n| 1    | b    | Varies <a href=\"#board200note2\">(2)</a>\n      </div>\n\n      <h4>Notes</h4>\n      <ol>\n        <li id=\"board200note1\">\n          Boards in DOS MZX had a fixed buffer size of <code>10000</code> for\n          each board plane. DOS MZX used the board mode field to determine what\n          the maximum dimensions of the board were within the available space.\n          MegaZeux 2.80+ ignores this field and uses the plane dimensions to\n          to determine the board size.\n          <div class=\"markdown\">\n| Board Mode | Width | Height |\n|------------|-------|--------|\n| `0`        | 60    | 166    |\n| `1`        | 80    | 125    |\n| `2`        | 100   | 100    |\n| `3`        | 200   | 50     |\n| `4`        | 400   | 25     |\n          </div>\n        </li>\n        <li id=\"board200note2\">\n          If the second byte of the board non-zero, it is the lower byte of the\n          level_id plane's width field. If this byte is <code>00</code>, the\n          board has an overlay and has the following extra field. As a\n          consequence of this, saving a world with overlay disabled and a board\n          width of 256 (which results in a lower byte of <code>00</code>) in\n          DOS MZX versions will generate a corrupt world and crash MZX. Port\n          versions avoid this by incrementing the board width to a non-multiple\n          of 256 when setting the board size in the editor.\n        </li>\n      </ol>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 2    | b    | Overlay mode (1: enabled, 2: static, 3: transparent)\n      </div>\n\n    </section>\n\n    <section id=\"boardplanes200\" class=\"inner\">\n      <h3>Board RLE Planes</h3>\n      <p>\n        Board and overlay contents are compressed with run length encoding. Each board\n        has either 6 or 8 planes (depending on whether the overlay is enabled), which\n        immediately follow the mode fields and are stored in the following order:\n      </p>\n\n      <div class=\"markdown\">\n| name              | description |\n|-------------------|-------------|\n| overlay_char      | Overlay char data (only if overlay is enabled)\n| overlay_color     | Overlay color data (only if overlay is enabled)\n| level_id          | Board thing IDs\n| level_color       | Board thing colors\n| level_param       | Board thing parameters\n| level_under_id    | Board thing IDs for floors beneath non-floor things\n| level_under_color | Board thing colors for floors beneath non-floor things\n| level_under_param | Board thing parameters for floors beneath non-floor things\n      </div>\n\n      <p>\n        Each compressed plane starts with the following fields:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | w    | Width\n| 2    | w    | Height\n      </div>\n\n      <p>\n        The RLE data follows after. Values 0 through 127 represent a single\n        literal value in the uncompressed data. If bit 7 is set, instead a run\n        of length <code>(byte & 127)</code> should be filled with the next byte\n        in the stream. The RLE plane can be decompressed with the following\n        algorithm:\n      </p>\n\n      <code class=\"example\">\n/**\n * This code fragment is a simplified version of the RLE2 unpacking code from\n * MegaZeux's source code, and is therefore GPL 2+ licensed.\n */\n\nint size = width * height;\nint i = 0;\nwhile(i &lt; size)\n{\n  current_char = *(stream++);\n  if(!(current_char & 0x80))\n  {\n    plane[i++] = current_char;\n  }\n  else\n  {\n    int runsize = current_char & 0x7F;\n    current_char = *(stream++);\n\n    if(runsize &gt; size - i)\n      runsize = size - i; // MegaZeux emits an error if this occurs.\n\n    for(int j = 0; j &lt; runsize; j++)\n      plane[i++] = current_char;\n  }\n}\n      </code>\n\n      <p>\n        The RLE stream ends when <code>(board width * board height)</code>\n        bytes have been expanded from the stream, and the next compressed plane\n        or the board parameters block immediately follows.\n      </p>\n    </section>\n\n    <section id=\"boardparams200\" class=\"inner\">\n      <h3>Board Parameters (2.00 through 2.82X)</h3>\n      <p>\n        The board parameters block follows the board RLE planes.\n        The following is the board parameters format for MZX versions prior to\n        2.83.\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s13  | Mod playing (null terminated)\n| 13   | b    | Viewport X\n| 14   | b    | Viewport Y\n| 15   | b    | Viewport width\n| 16   | b    | Viewport height\n| 17   | b    | Can shoot?\n| 18   | b    | Can bomb?\n| 19   | b    | Fire burns brown?\n| 20   | b    | Fire burns space?\n| 21   | b    | Fire burns fakes?\n| 22   | b    | Fire burns trees?\n| 23   | b    | Explosions leave (0: space, 1: ash, 2: fire)\n| 24   | b    | Save mode (0: enabled, 1: disabled, 2: sensor only)\n| 25   | b    | Forest to floor?\n| 26   | b    | Collect bombs?\n| 27   | b    | Fire burns forever?\n| 28   | b    | Board # to north\n| 29   | b    | Board # to south\n| 30   | b    | Board # to east\n| 31   | b    | Board # to west\n| 32   | b    | Restart if zapped?\n| 33   | w    | Time limit\n| 35   | b    | Last key pressed\n| 36   | ws   | Input (numeric value)\n| 38   | b    | Inputsize\n| 39   | s81  | Input (string value) (null terminated)\n| 120  | b    | Last player direction\n| 121  | s81  | Bottom message (null terminated)\n| 202  | b    | Bottom message timer\n| 203  | b    | Lazerwall timer\n| 204  | b    | Bottom message row\n| 205  | bs   | Bottom message column (-1: center)\n| 206  | ws   | Scroll relative X offset\n| 208  | ws   | Scroll relative Y offset\n| 210  | ws   | Scroll locked X (-1: no lock)\n| 212  | ws   | Scroll locked Y (-1: no lock)\n| 214  | b    | Player locked NS?\n| 215  | b    | Player locked EW?\n| 216  | b    | Player attack locked?\n| 217  | b    | Volume\n| 218  | b    | Volume increment\n| 219  | b    | Volume target\n      </div>\n    </section>\n\n    <section id=\"boardparams283\" class=\"inner\">\n      <h3>Board Parameters (2.83 and 2.84X)</h3>\n      <p>\n        The board parameters block follows the board RLE planes.\n        The following is the board format for MZX versions 2.83 and 2.84X.\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | w    | Mod playing length\n| 2    | s... | Mod playing (NOT null terminated)\n      </div>\n\n      <p>\n        This block immediately follows the playing mod:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | b    | Viewport X\n| 1    | b    | Viewport Y\n| 2    | b    | Viewport width\n| 3    | b    | Viewport height\n| 4    | b    | Can shoot?\n| 5    | b    | Can bomb?\n| 6    | b    | Fire burns brown?\n| 7    | b    | Fire burns space?\n| 8    | b    | Fire burns fakes?\n| 9    | b    | Fire burns trees?\n| 10   | b    | Explosions leave (0: space, 1: ash, 2: fire)\n| 11   | b    | Save mode (0: enabled, 1: disabled, 2: sensor only)\n| 12   | b    | Forest to floor?\n| 13   | b    | Collect bombs?\n| 14   | b    | Fire burns forever?\n| 15   | b    | Board # to north\n| 16   | b    | Board # to south\n| 17   | b    | Board # to east\n| 18   | b    | Board # to west\n| 19   | b    | Restart if zapped?\n| 20   | w    | Time limit\n      </div>\n\n      <p>\n        The following block is only present in save files.\n      </p>\n\n      <div class=\"markdown\">\n| Pos.    | Size | Description |\n|---------|------|-------------|\n| 0       | b    | Last key pressed\n| 1       | w    | Input (numeric value)\n| 3       | w    | Inputsize\n| 5       | w    | Input string value length = x\n| 7       | s... | Input (string value) (NOT null terminated)\n| 7+x     | b    | Last player direction\n| 8+x     | w    | Bottom message length = y\n| 10+x    | s... | Bottom message (NOT null terminated)\n| 10+x+y  | b    | Bottom message timer\n| 11+x+y  | b    | Lazerwall timer\n| 12+x+y  | b    | Bottom message row\n| 13+x+y  | bs   | Bottom message column (-1: center)\n| 14+x+y  | ws   | Scroll relative X offset\n| 16+x+y  | ws   | Scroll relative Y offset\n| 18+x+y  | ws   | Scroll locked X (-1: no lock)\n| 20+x+y  | ws   | Scroll locked Y (-1: no lock)\n      </div>\n\n      <p>\n        The following block is in both world files and save files.\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | b    | Player locked NS?\n| 1    | b    | Player locked EW?\n| 2    | b    | Player attack locked?\n      </div>\n\n      <p>\n        The final block is only present in save files.\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | b    | Volume\n| 1    | b    | Volume increment\n| 2    | b    | Volume target\n      </div>\n\n      <h4>Notes</h4>\n      <ol>\n        <li>\n          Some MZX worlds created with an early alpha version of MZX 2.83 have\n          boards parameters in the older format. Since these were saved with the\n          MZX 2.83 <a href=\"#worldheader\">world magic</a>, they can't be loaded\n          in newer versions of MZX, but they can be fixed by hex editing the\n          magic from <code>M\\x02\\x53</code> to <code>M\\x02\\x52</code>.\n        </li>\n      </ol>\n    </section>\n\n    <section id=\"boardobjs200\" class=\"inner\">\n      <h3>Board Objects</h3>\n      <p>\n        Following the board parameters are the robot, scroll, and sensor lists.\n        Each list is stored as a single byte <code>N</code> indicating the\n        number of robots, scrolls, or sensors on the board (0 to 255), followed\n        by <code>N</code> stored objects of that type. Each object in its\n        respective list immediately follows the prior, and the next list starts\n        immediately following the last object in the previous list. The board\n        data ends after all three lists and their objects have been read.\n      </p>\n      <p>\n        The board robots/scrolls/sensors in the list count from ID 1 upward, as\n        ID 0 is the global robot and is invalid for scrolls and sensors. Since\n        objects in the file are sequential, robot/scroll/sensor IDs may have to\n        be reassigned when saving worlds or saves in these versions to optimize\n        out gaps in the list.\n      </p>\n    </section>\n\n    <section id=\"robot200\" class=\"inner\">\n      <h3>Robots</h3>\n      <p>\n        Robots are stored in world/save files in the following format. Several\n        of these fields are only used during runtime and thus initial values\n        used when saving worlds are also listed.\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Default | Description |\n|------|------|---------|-------------|\n| 0    | w    |         | Program length in bytes\n| 2    | w    |         | unused <a href=\"#robot200note1\">(1)</a>\n| 4    | s15  |         | Robot name (null-terminated)\n| 19   | b    | 2       | Robot char\n| 20   | w    | 1       | Current position in program\n| 22   | b    | 0       | Position within current line (for <code>WAIT</code>/<code>GO</code>/etc)\n| 23   | b    | 1       | Robot cycle\n| 24   | b    | 0       | Cycle counter\n| 25   | b    | 1       | Bullet type\n| 26   | b    | 0       | Locked status (0: unlocked, 1: locked) <a href=\"#robot200note2\">(2)</a>\n| 27   | b    | 0       | Can lavawalk (0: no, 1: yes)\n| 28   | b    | 0       | Walk direction (0: idle) <a href=\"#robot200note2\">(2)</a>\n| 29   | b    | 0       | Last touch direction <a href=\"#robot200note3\">(3)</a>\n| 30   | b    | 0       | Last shot direction <a href=\"#robot200note3\">(3)</a>\n| 31   | ws   | <a href=\"#robot200note4\">note 4</a>  | X position\n| 33   | ws   | <a href=\"#robot200note4\">note 4</a>  | Y position\n| 35   | b    | 0       | Status for the current board cycle <a href=\"#robot200note5\">(5)</a>\n| 36   | ws   | 0       | Local <a href=\"#robot200note6\">(6)</a>\n| 38   | b    | 1       | Used (1) or unused (0)\n| 39   | ws   | 0       | Loopcount <a href=\"#robot200note7\">(7)</a>\n      </div>\n\n      <h4>Notes</h4>\n      <ol>\n        <li id=\"robot200note1\">\n          DOS MZX versions wrote robot, scroll, and sensor structs directly to\n          world or save files. The unused 2-byte field at offset 2 corresponds\n          to the program memory pointer in these versions.\n        </li>\n        <li id=\"robot200note2\">\n          In DOS MZX versions from 2.69b to 2.70, the <code>LOCAL2</code> counter\n          was stored by using the robot walk direction as the upper byte and the\n          robot locked status as the lower byte. Setting <code>LOCAL2</code> had\n          the side effects you would expect from modifying those variables.\n        </li>\n        <li id=\"robot200note3\">\n          In DOS MZX versions from 2.69b to 2.70, the <code>LOCAL3</code> counter\n          was stored by using the robot last touch direction as the upper byte and\n          the robot last shot direction as the lower byte. Setting <code>LOCAL3</code>\n          had the side effects you would expect from modifying those variables.\n        </li>\n        <li id=\"robot200note4\">\n          The X/Y position variables are generally initialized to 0 in world files\n          from DOS versions. Versions 2.80+ typically save the actual X and Y position\n          in these variables in world files. The global robot is saved with the\n          coordinates (-1,-1).\n        </li>\n        <li id=\"robot200note5\">\n          Robot statuses:\n          <div class=\"markdown\">\n| Status | Description |\n|--------|-------------|\n| 0      | Normal\n| 1      | Did not execute this cycle or only executed <code>end</code>, <code>wait</code>\n| 2      | Same as 1 but has also been sent a label\n          </div>\n          When a robot's status is equal to 2, it will execute the label it was\n          sent on reverse board scan. Every robot's status is reset to 0 at the\n          start of each board cycle.\n        </li>\n        <li id=\"robot200note6\">\n          The local field in this block is completely ignored in versions 2.80+ and\n          is saved in the robot save block instead. In versions prior to 2.51s1,\n          this field existed but was not used.\n        </li>\n        <li id=\"robot200note7\">\n          The loopcount field in this block is completely ignored in 2.84X save files\n          and is saved in the robot save block instead.\n        </li>\n      </ol>\n\n      <p>\n        In save files from MegaZeux 2.80 and onward, an extra data block follows:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size    | Description |\n|------|---------|-------------|\n| 41   | ds      | Loopcount <a href=\"#robot200note8\">(8)</a>\n| 45   | ds * 32 | Locals\n| 173  | d       | Stack length = X\n| 177  | d       | Current position in stack\n| 181  | d * X   | Robot stack\n      </div>\n\n      <h4>Notes</h4>\n      <ol>\n        <li id=\"robot200note8\" value=\"8\">\n          The loopcount field in this block was not present prior to 2.84 (i.e. the\n          block started with the local counters instead and was 4 bytes shorter).\n        </li>\n      </ol>\n\n      <p>\n        In both world and save files, the <a href=\"#bytecode\">robot program</a>\n        immediately follows the above block(s). The size of the program in file\n        is the same as the program length field in the robot data.\n      </p>\n\n      <p>\n        In older worlds, robots marked unused may have uninitialized memory\n        saved where their program would usually go (the global robot from\n        <i>Slave Pit</i> is an example). In this situation, the Robotic program\n        should not be validated and should instead be ignored or replaced with\n        <code>FF 00</code>.\n      </p>\n    </section>\n\n    <section id=\"scroll200\" class=\"inner\">\n      <h3>Scrolls</h3>\n      <p>\n        Each scroll block is 7 bytes plus the length of the scroll text long.\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | w    | Number of lines\n| 2    | w    | unused <a href=\"#scroll200note1\">(1)</a>\n| 4    | w    | Scroll text length in bytes\n| 6    | b    | Used (1) or unused (0)\n| 7    | s... | Scroll text <a href=\"#scroll200note2\">(2)</a>\n      </div>\n\n      <h4>Notes</h4>\n      <ol>\n        <li id=\"scroll200note1\">\n          The unused 2-byte field corresponds to the scroll text memory pointer\n          in DOS versions of MZX.\n        </li>\n        <li id=\"scroll200note2\">\n          The scroll text must begin with a <code>01</code> hex byte, must\n          contain at least one line break (<code>0A</code> hex), and must be\n          null terminated (i.e. the smallest valid scroll is\n          <code>01 0A 00</code> with number of lines=1 and length=3).\n        </li>\n      </ol>\n    </section>\n\n    <section id=\"sensor200\" class=\"inner\">\n      <h3>Sensors</h3>\n      <p>\n        Each sensor block is 32 bytes long.\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s15  | Sensor name (null-terminated)\n| 15   | b    | Sensor char\n| 16   | s15  | Robot to message (null-terminated)\n| 31   | b    | Used (1) or unused (0)\n      </div>\n    </section>\n  </section>\n\n  <hr>\n\n  <section id=\"world290\" class=\"main\">\n    <h2>World and Save Format (2.90 onward)</h2>\n    <p>\n      The legacy world format was replaced starting from MegaZeux 2.90 with a\n      ZIP-based format. The old world format was replaced for the following\n      reasons:\n    </p>\n    <ul>\n      <li>\n        A large number of problems with the save format were already documented,\n        such as <a href=\"#global200smzx\">a lack of SMZX intensities in the save\n        file</a>, <a href=\"#global200mathfile\">the math variables being saved\n        with the wrong data type</a>, an awkward counter list hack being used to\n        save <code>MZX_SPEED</code>, and others, so the format needed to be\n        changed anyway. There were also numerous MZX features queued for\n        addition that required world format changes, including improved SMZX\n        support and vlayer editing.\n      </li>\n      <li>\n        The legacy format was hard to maintain and modify. The logic required\n        to support even simple changes to the binary world format (such as the\n        changes to the board parameters format in MZX 2.83) tended to get ugly\n        fast.<br><br>The ZIP-based format addresses this problem in two ways: first,\n        it allows for cleanly partitioning the world format into\n        palatable chunks, and second, it replaces the proprietary binary blobs\n        of data with slightly-less proprietary extensible fields lists in the\n        <a href=\"#properties\">properties format</a>. These changes make it much\n        easier to, for a few examples, add new data structures or fields or\n        change the size of a field or to move formerly save-only data to world\n        files without breaking the world format. ZIP-based save files from older\n        versions can also be supported with <a href=\"#global200mathfile\">fewer headaches</a>.\n      </li>\n      <li>\n        The legacy format mostly lacked anything resembling internal consistency\n        checks, infamously leading to broken games like <i>Brotherhood: The Legacy\n        of Drake</i> outwardly appearing to be fine but causing MZX instability\n        and crashes at certain points in the game. The ZIP format provides a\n        much more strict file structure, internal redundancy in its headers, and\n        CRC-32 checksums out of the box. While these aren't foolproof, they're\n        better than nothing, and combined with the increased internal\n        modularization of world files, means corruption at the very least may\n        corrupt less world data.\n      </li>\n      <li>\n        Creating a new format provided an opportunity to clean up some\n        <a href=\"#robot200\">artifacts of DOS MZX's saving code</a> and other\n        <a href=\"#global200save2\">confusing relics</a> that had managed to\n        survive from the days of MZX 2.51.\n      </li>\n      <li>\n        The old format notably was lacking compression for certain things that\n        could <a href=\"#global200vlayer\">generate very large .SAV files</a>, and\n        adding compression to these things would have added additional loader\n        compatibility checks. Furthermore, the board plane RLE compression was\n        very bad at handling values >=128 and could easily be manipulated to\n        produce planes larger in file than they were in memory. With the ZIP\n        format, however, any compression is an aspect of the container and not\n        the data itself, meaning a data structure can be compressed with\n        little-to-no consequence for the parsing of the data structure.\n        DEFLATE also tends to compress board planes much better than the RLE did.\n      </li>\n    </ul>\n\n    <h4>World and Save Headers</h4>\n    <p>\n      ZIP-based worlds begin with the same <a href=\"#worldheader\">29-byte header</a>\n      that unencrypted legacy worlds do, and ZIP-based saves begin with the same\n      <a href=\"#saveheader\">8-byte header</a> that legacy save files do. This is\n      possible as ZIP archives parse starting from the end of the file, so ZIP\n      archives with prefixed data are still valid ZIP archives. The header data\n      is stored redundantly in the <a href=\"#worldprop290\">world properties</a>\n      file so ZIP worlds can be extracted and rearchived by an external program\n      and still work even without the header.\n    </p>\n\n    <section id=\"files290\" class=\"inner\">\n      <h3>Files List</h3>\n      <p>\n        World and save files split their data into multiple internal files for\n        modularization and to allow for compression of specific parts of the\n        world data. The files supported by the world format and the order they\n        are loaded in are as follows:\n      </p>\n      <div class=\"markdown\">\n| Filename  | Description                                                 | Notes                       | Compressed |\n|-----------|-------------------------------------------------------------|-----------------------------|------------|\n| `world`   | <a href=\"#worldprop290\"  >World Properties</a>              |                             | Never\n| `gr`      | <a href=\"#robot290\"      >Global Robot</a>                  |\n| `sfx`     | <a href=\"#sfx290\"        >Custom SFX Table</a>              | Only if custom SFX enabled  | As of 2.91c\n| `chars`   | <a href=\"#chars290\"      >Character Sets</a>                |                             | Always\n| `pal`     | <a href=\"#pal290\"        >Palette</a>                       |\n| `palsmzx` | <a href=\"#pal290\"        >Palette (SMZX)</a>                | SMZX 2 and 3 only\n| `palidx`  | <a href=\"#pal290\"        >Palette Indices</a>               | Save-only before 2.91, SMZX 3 only\n| `vco`     | <a href=\"#vlayer290\"     >Vlayer Colors Plane</a>           | Save-only before 2.91       | Always\n| `vch`     | <a href=\"#vlayer290\"     >Vlayer Chars Plane</a>            | Save-only before 2.91       | Always\n| `palint`  | <a href=\"#pal290\"        >Palette Intensities</a>           | Save-only\n| `palints` | <a href=\"#pal290\"        >Palette Intensities (SMZX)</a>    | Save-only, SMZX 2 and 3 only\n| `spr`     | <a href=\"#spriteprop290\" >Sprite Properties</a>             | Save-only                   | Always\n| `counter` | <a href=\"#counters290\"   >Counters List</a>                 | Save-only                   | As of 2.93\n| `string`  | <a href=\"#counters290\"   >Strings List</a>                  | Save-only                   | As of 2.93\n| `b##`     | <a href=\"#boardprop290\"  >Board Properties</a>              | See below.\n| `b##bid`  | <a href=\"#boardplanes290\">Board level_id Plane</a>          |                             | Always\n| `b##bpr`  | <a href=\"#boardplanes290\">Board level_param Plane</a>       |                             | Always\n| `b##bco`  | <a href=\"#boardplanes290\">Board level_color Plane</a>       |                             | Always\n| `b##uid`  | <a href=\"#boardplanes290\">Board level_under_id Plane</a>    |                             | Always\n| `b##upr`  | <a href=\"#boardplanes290\">Board level_under_param Plane</a> |                             | Always\n| `b##uco`  | <a href=\"#boardplanes290\">Board level_under_color Plane</a> |                             | Always\n| `b##och`  | <a href=\"#boardplanes290\">Board overlay_char Plane</a>      | Only if overlay enabled     | Always\n| `b##oco`  | <a href=\"#boardplanes290\">Board overlay_color Plane</a>     | Only if overlay enabled     | Always\n| `b##r##`  | <a href=\"#robot290\"      >Board Robot Properties</a>        | Only if robot exists\n| `b##sc##` | <a href=\"#scroll290\"     >Board Scroll Properties</a>       | Only if scroll exists\n| `b##se##` | <a href=\"#sensor290\"     >Board Sensor Properties</a>       | Only if sensor exists\n      </div>\n\n      <p>\n        The board files (prefixed with <code>b##</code>) corresponding to a\n        single board are all loaded before loading any files corresponding to\n        the next board. The first <code>##</code> in the names of these files\n        is the board number in hex. Examples: <code>b00</code> for the title\n        board, <code>b0A</code> for board 10, <code>bF9</code> for\n        board 249 (the highest possible regular board number), and\n        <code>bFF</code> for the special temporary board that may sometimes\n        exist in save files. This number must be exactly two hex digits long.\n      </p>\n      <p>\n        Extra storage data for robots, scrolls, and sensors is stored in separate\n        files. The file <code>bXXrYY</code> corresponds to the robot on board\n        <code>XX</code> (hex) with the robot ID <code>YY</code> (also hex). Values\n        from <code>01</code> (for robot 1) to <code>FF</code> (for robot 255) are\n        valid. The same applies for scrolls and sensors. The robot/scroll/sensor\n        must exist on the board for its corresponding properties file to be valid.\n        These numbers must be exactly two hex digits long.\n      </p>\n      <p>\n        Filenames are case-insensitive. Prior to 2.93, they were case-sensitive\n        and had to be all lowercase (aside from board/object numbers).\n      </p>\n    </section>\n\n    <section id=\"properties\" class=\"inner\">\n      <h3>Properties Format</h3>\n      <p>\n        World, board, sprite, and object data are all stored in a simple\n        extensible format created for MZX files similar to RIFF or a (very)\n        simplified EBML. All internal files labeled \"properties\" use this format.\n      </p>\n      <p>\n        These files consist of an unspecified number of blocks of \"properties\"\n        in the format:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size  | Description |\n|------|-------|-------------|\n| 0    | w     | Property ID\n| 2    | d     | Property length = X\n| 6    | b * X | Property data\n      </div>\n\n      <p>\n        Property data contents vary between different properties, so in the\n        property list specifications the expected contents are explicitly\n        defined. Most properties expect an <code>int</code> value and accept\n        either 1 byte (equivalent to <code>b</code>), 2 bytes (<code>w</code>),\n        or 4 bytes (<code>ds</code>) (expected size and signedness are explicitly\n        noted). The next most common stored type is a variable length\n        <code>string</code> with no null terminator (equivalent to <code>s...</code>).\n        <code>string</code>s that are stored with null terminators are marked\n        <code>(with \\0)</code>.\n      </p>\n\n      <p>\n        The file ends immediately when a property ID of <code>0x0000</code> is\n        encountered indicating the end of the file. This value is the same for\n        all different properties files. Unrecognized properties IDs are usually\n        skipped unless noted otherwise. The EOF ID should always be present at\n        the end of the file and there should be no data after the EOF ID.\n      </p>\n      <p>\n        It is possible to nest properties files to create a file with a more\n        complex structure (though MZX currently does not do this).\n      </p>\n    </section>\n\n    <section id=\"worldprop290\" class=\"inner\">\n      <h3>World Properties</h3>\n      <p>\n        Global data is stored in a properties file for both world and save files.\n        This is a common pattern in the new world format; save data is typically\n        implemented as either extra properties only present in save files or\n        (occasionally) as save-exclusive raw data files.\n      </p>\n      <p>\n        Unlike every other properties file in the format, the world properties\n        file is very strict: every property expected for a particular file\n        version <b>must</b> exist and <b>must</b> be in the order listed below.\n        Also unlike other properties files in the world format, unrecognized\n        properties in this file will usually generate an error.\n        An integrity check is performed to enforce this early in the load\n        process to determine whether or not the provided file is actually a\n        world/save.\n      </p>\n      <p>\n        Additionally, compressing this file should be avoided, as it may be\n        useful in the future for MZX to be able to peek at the contents of this\n        file prior to initializing ZIP archive data structures.\n      </p>\n      <div class=\"markdown\">\n| ID       | Property                             | Data Type               | Notes |\n|----------|--------------------------------------|-------------------------|-------|\n| `0x0001` | World name                           | string                  | <a href=\"#world290note1\">(1)</a>\n| `0x0002` | World version                        | int(w)                  |\n| `0x0003` | File version                         | int(w)                  |\n| `0x0004` | Save start board #                   | int(b)                  | 255: temporary board\n| `0x0005` | Save has temporary board?            | int(b)                  |\n| `0x0008` | Number of boards in world            | int(b)                  |\n\n| `0x0010` | ID Chars blocks 1 and 2              | array(b * 323)          |\n| `0x0011` | ID Chars missile color               | int(b)                  |\n| `0x0012` | ID Chars bullet colors               | array(b * 3)            |\n| `0x0013` | ID Chars block 3 (damage)            | array(b * 128)          |\n\n| `0x0018` | Status counters                      | Properties (2.93+)<br>array(s15&nbsp;with&nbsp;\\0&nbsp;*&nbsp;6) |\n\n| `0x0020` | Edge color                           | int(b)\n| `0x0021` | First board #                        | int(b)\n| `0x0022` | Endgame board #                      | int(b)  | 255: no endgame board\n| `0x0023` | Death board #                        | int(b)  | 254: same position<br>255: restart board\n| `0x0024` | Endgame board teleport X             | int(w)\n| `0x0025` | Endgame board teleport Y             | int(w)\n| `0x0026` | Game over SFX enabled?               | int(b)\n| `0x0027` | Death board teleport X               | int(w)\n| `0x0028` | Death board teleport Y               | int(w)\n| `0x0029` | Starting lives                       | int(w)\n| `0x002A` | Lives limit                          | int(w)\n| `0x002B` | Starting health                      | int(w)\n| `0x002C` | Health limit                         | int(w)\n| `0x002D` | Enemies' bullets hurt other enemies  | int(b)\n| `0x002E` | Clear messages/projectiles on exit   | int(b)\n| `0x002F` | Can only play world from <code>SWAP WORLD</code>| int(b)\n\n| `0x8030` | SMZX mode (0: disabled)              | int(b)                  | Save-only before 2.91\n| `0x8031` | Vlayer width                         | int(w)                  | Save-only before 2.91\n| `0x8032` | Vlayer height                        | int(w)                  | Save-only before 2.91\n| `0x8033` | Vlayer size                          | int(d)                  | Save-only before 2.91\n\n| `0x8040` | Mod playing                          | string                  | Save-only\n| `0x8041` | MZX speed                            | int(b)                  | Save-only\n| `0x8042` | MZX speed is locked?                 | int(b)                  | Save-only\n| `0x8043` | Commands per cycle                   | int(ds)                 | Save-only\n| `0x8044` | Commands per cycle breakpoint limit  | int(ds)                 | Save-only\n| `0x8048` | Saved positions                      | array((w&nbsp;+&nbsp;w&nbsp;+&nbsp;b)&nbsp;*&nbsp;8)  | Save-only\n| `0x8049` | Under player ID/param/color          | array(b * 3)            | Save-only\n| `0x804A` | Player restart X                     | int(w)                  | Save-only\n| `0x804B` | Player restart Y                     | int(w)                  | Save-only\n| `0x804C` | Player color                         | int(b)                  | Save-only\n| `0x804D` | Keys                                 | array(b * 16)           | Save-only\n| `0x8050` | Blind duration                       | int(d)                  | Save-only\n| `0x8051` | Firewalker duration                  | int(d)                  | Save-only\n| `0x8052` | Freeze time duration                 | int(d)                  | Save-only\n| `0x8053` | Slow time duration                   | int(d)                  | Save-only\n| `0x8054` | Wind duration                        | int(d)                  | Save-only\n| `0x8058` | Scroll base color                    | int(b)                  | Save-only\n| `0x8059` | Scroll corner color                  | int(b)                  | Save-only\n| `0x805A` | Scroll pointer color                 | int(b)                  | Save-only\n| `0x805B` | Scroll title color                   | int(b)                  | Save-only\n| `0x805C` | Scroll arrow color                   | int(b)                  | Save-only\n| `0x8060` | Message edges enabled?               | int(b)                  | Save-only\n| `0x8061` | Built-in shooting enabled?           | int(b)                  | Save-only\n| `0x8062` | Built-in messages enabled?           | int(b)                  | Save-only\n| `0x8063` | Faded state (1: faded out)           | int(b)                  | Save-only\n| `0x8070` | Input filename                       | string                  | Save-only\n| `0x8074` | Input file position                  | int(d)                  | Save-only\n| `0x8075` | Input file delimiter                 | int(ds)                 | Save-only\n| `0x8078` | Output filename                      | string                  | Save-only\n| `0x807C` | Output file position                 | int(d)                  | Save-only\n| `0x807D` | Output file delimiter                | int(ds)                 | Save-only\n| `0x807E` | Output file mode                     | int(b)                  | Save-only, 2.93+ <a href=\"#world290note1\">(2)</a>\n| `0x8080` | Multiplier                           | int(ds)                 | Save-only\n| `0x8081` | Divider                              | int(ds)                 | Save-only\n| `0x8082` | Circle divisions                     | int(ds)                 | Save-only\n| `0x8090` | Maximum simultaneous sound effects   | int(ds)                 | Save-only, 2.91+\n| `0x8091` | SMZX message enabled in SMZX mode?   | int(b)                  | Save-only, 2.91+\n| `0x8092` | Joystick presses simulate keypresses?| int(b)                  | Save-only, 2.92+\n      </div>\n      <ol>\n        <li id=\"world290note1\">\n          World name maximum length is currently 24 characters; extra data\n          will be ignored. Versions prior to 2.92d expect this field to be null\n          terminated, and may leave parts of previous world names in the loaded\n          name if it is not.\n        </li>\n        <li id=\"world290note2\">\n          This field may be omitted. Valid output file mode values:\n<div class=\"markdown\">\n| Value | Mode         |\n|-------|--------------|\n| 0     | none/unknown\n| 1     | `w+b`\n| 2     | `r+b`\n| 3     | `a+b`\n</div>\n          Files opened in mode <code>w+b</code> should be reopened in mode\n          <code>r+b</code>. The output file is currently write-only, so\n          <code>wb</code> and <code>ab</code> are used in Robotic instead\n          of <code>w+b</code> and <code>a+b</code> for now.\n        </li>\n      </ol>\n      <h4 id=\"statctrprop290\">Status Counter Properties</h4>\n      As of 2.93, the status counters are a nested properties file. Status\n      counters not present in the properties file are unused/blank.\n      <div class=\"markdown\">\n| ID       | Property                             | Data Type               | Notes |\n|----------|--------------------------------------|-------------------------|-------|\n| `0x0001` | Set current status counter ID        | int(b)                  | Counters >=6 are ignored\n| `0x0002` | Status counter name                  | string                  | Max. length 14\n      </div>\n      Prior to 2.93, the status counters were a fixed size array of 6 15-byte\n      ASCIIZ strings (total size 90 bytes). In 2.93+ worlds, if the status\n      counters are 90 bytes long <i>and</i> are not a valid properties file,\n      they will be loaded as the old format.\n    </section>\n\n    <section id=\"sfx290\" class=\"inner\">\n      <h3>Custom SFX Table</h3>\n      <p>\n        If this file is present, custom SFX will be enabled for the world. If\n        this file is absent, custom SFX will be disabled.\n      </p>\n      <p>\n        In versions 2.93 and up, the sound effects are stored as a properties file\n        with a 8-byte header:\n        <div class=\"markdown\">\n| Pos. | Size  | Description |\n|------|-------|-------------|\n| 0    | s6    | `MZFX\\x1a\\0`\n| 6    | w(BE) | MegaZeux version in big endian, same as last two bytes of <a href=\"#worldheader\">world magic</a>.\n        </div>\n        Versions less than 2.93 (<code>025Dh</code>) are invalid. The properties\n        follow immediately:\n        <div class=\"markdown\">\n| ID       | Property                             | Data Type               | Notes |\n|----------|--------------------------------------|-------------------------|-------|\n| `0x0001` | Set current sound effect ID          | int(b)                  | SFX >= 256 are ignored\n| `0x0002` | Sound effect string                  | string                  | Max. length 255\n| `0x0003` | Sound effect name                    | string                  | Max. length 9\n        </div>\n        Prior to 2.93, the custom SFX table is saved as a raw array of\n        <code>(NUM_BUILTIN_SFX * LEGACY_SFX_SIZE</code> bytes, where\n        <code>NUM_BUILTIN_SFX = 50</code> and <code>LEGACY_SFX_SIZE = 69</code>\n        (for a total of <code>3450</code> bytes prior to compression). Each\n        individual sound effect must be null terminated. This format is still\n        allowed in 2.93+ version world files. If loaded into a 2.93+ world, all\n        custom sound effects after the first 50 and all names will be cleared.\n      <p>\n        This file is identical in format to the\n        <a href=\"#sfx\">exported custom SFX file format</a>, and files in 2.90+\n        world and save files can be used interchangeably with files created by\n        SFX export.\n      </p>\n    </section>\n\n    <section id=\"chars290\" class=\"inner\">\n      <h3>Character Sets</h3>\n      <p>\n        The world character sets are saved as a single raw\n        <a href=\"#chr\">charset file</a>. In 2.90+ saves and 2.91+ worlds, all\n        15 user charsets will be saved for a total size of <code>14*15*256 = 53760</code>\n        bytes prior to compression. Worlds from MZX 2.90 will only save the\n        main charset (for a total size of <code>3840</code> bytes).\n      </p>\n    </section>\n\n    <section id=\"pal290\" class=\"inner\">\n      <h3>Palettes, Palette Indices, and Palette Intensities</h3>\n      <p>\n        The world palettes are saved as raw <a href=\"#pal\">palette files</a>.\n        In 2.93 and up, the file <code>pal</code> always contains the 16\n        color palette corresponding to MZX mode and SMZX mode 1. If SMZX\n        modes 2 or 3 are active, an additional 256 color palette will be saved\n        to <code>palsmzx</code>. If SMZX mode 3 is enabled, the palette indices\n        will also be saved as a raw <a href=\"#palidx\">palette indices file</a>\n        in 2.90+ save files and 2.91+ world files.\n      </p>\n      <p>\n        In save files, the palette intensities are also saved. The file\n        <code>palint</code> contains a raw array of 16 unsigned little endian\n        dwords representing the MZX and SMZX mode 1 palette intensities. If\n        SMZX modes 2 or 3 are active, an additional file <code>palints</code>\n        containing the SMZX modes 2 and 3 will be saved. This file contains 256\n        unsigned little endian dwords.\n      </p>\n      <h4 id=\"pal290old\">Prior to 2.93</h4>\n      <p>\n        Only one <code>pal</code> and <code>palint</code> file were saved.\n        These files always contained 256 entries, and always represented the\n        current active screen mode. The palette intensities were stored as\n        <i>bytes</i>, which corrupted larger intensity values.\n      </p>\n      <p>\n        In SMZX modes 2 and 3, the MZX palette and palette intensities were NOT\n        saved. For worlds/saves from these versions with SMZX modes 2 or 3 active,\n        the MZX palette should be derived from the first 16 entries of the SMZX\n        palette (same as 2.84X and prior) and the MZX intensities should default\n        to 100.\n      </p>\n      <p>\n        In 2.90X save files, the palette intensities file was stored using the\n        internal indices order, and was incompatible with palette indices\n        files. For each color, the two middle indices are in reverse order.\n      </p>\n    </section>\n\n    <section id=\"vlayer290\" class=\"inner\">\n      <h3>Vlayer Planes</h3>\n      <p>\n        The vlayer chars and colors planes are saved as raw data in two separate\n        files in saves (2.90 and up) and world files (2.91 and up). These files\n        are expected to be <code>(vlayer_size)</code> bytes long, where\n        <code>vlayer_size</code> is specified in the world properties. If one\n        of these files is missing, an error will be displayed and the plane will\n        be zero-initialized.\n      </p>\n    </section>\n\n    <section id=\"sprites290\" class=\"inner\">\n      <h3>Sprite Properties (.SAV only)</h3>\n      <p>\n        Sprite data is stored in a single properties file for all sprites with\n        the following properties:\n      </p>\n      <div class=\"markdown\">\n| ID       | Property                   | Data Type     | Notes |\n|----------|----------------------------|---------------|-------|\n| `0x0001` | Set current sprite ID      | int(b)        | Each sprite<a href=\"#sprites290note1\">(1)</a>\n| `0x0002` | Sprite X                   | int(ds)       | Each sprite\n| `0x0003` | Sprite Y                   | int(ds)       | Each sprite\n| `0x0004` | Sprite reference X         | int(ds)       | Each sprite\n| `0x0005` | Sprite reference Y         | int(ds)       | Each sprite\n| `0x0006` | Sprite color               | int(b)        | Each sprite\n| `0x0007` | Sprite flags               | int(d)        | Each sprite<a href=\"#sprites290note2\">(2)</a>\n| `0x0008` | Sprite width               | int(d)        | Each sprite\n| `0x0009` | Sprite height              | int(d)        | Each sprite\n| `0x000A` | Sprite collision X         | int(ds)       | Each sprite\n| `0x000B` | Sprite collision Y         | int(ds)       | Each sprite\n| `0x000C` | Sprite collision width     | int(d)        | Each sprite\n| `0x000D` | Sprite collision height    | int(d)        | Each sprite\n| `0x000E` | Sprite transparent color   | int(ds)       | Each sprite\n| `0x000F` | Sprite character offset    | int(ds)       | Each sprite\n| `0x0010` | Sprite Z                   | int(ds)       | Each sprite, 2.92+\n| `0x8000` | Number of active sprites   | int(d)        | Only once\n| `0x8001` | Sprite Y order enabled     | int(ds)       | Only once\n| `0x8002` | Sprite collision count = N | int(d)        | Only once\n| `0x8003` | Sprite collision list      | array(ds * N) | Only once\n| `0x8004` | `SPR_NUM`                  | int(ds)       | Only once\n      </div>\n      <ol>\n        <li id=\"sprites290note1\">\n          This property sets the sprite ID that all following individual sprite\n          properties will be loaded to. This must be present before the properties\n          of a given sprite for the sprite to be correctly loaded. All 256 sprites\n          and their properties will typically be saved regardless of whether they\n          have been used or not, which is why this file is compressed.\n        </li>\n        <li id=\"sprites290note2\">\n          Sprite flags:\n          <div class=\"markdown\">\n| Flag   | Description |\n|--------|-------------|\n| `0x01` | Sprite is initialized\n| `0x02` | Sprite ccheck 1 is enabled\n| `0x04` | Sprite is drawn over the overlay\n| `0x08` | Sprite uses reference colors\n| `0x10` | Sprite is static relative to the viewport\n| `0x20` | Sprite ccheck 2 is enabled\n| `0x40` | Sprite references the vlayer\n| `0x80` | Sprite is unbound\n| `0x100`| Sprite off-on-exit is enabled\n          </div>\n          For unbound sprites, if both the ccheck 1 and ccheck 2 flags are set,\n          sprite ccheck 3 is enabled instead.\n        </li>\n      </ol>\n    </section>\n\n    <section id=\"counters290\" class=\"inner\">\n      <h3>Counter and String Lists (.SAV only)</h3>\n      <p>\n        The counter and string lists are packed in the same binary format they\n        were in prior versions, each in their own file.\n      </p>\n\n      <h4>Counters List</h4>\n      <p>\n        The counter list file starts with the number of counters:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size    | Description |\n|------|---------|-------------|\n| 0    | d       | Number of counters = N\n      </div>\n      <p>\n        A list of <code>N</code> counter definitions follows:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size  | Description |\n|------|-------|-------------|\n| 0    | ds    | Counter value\n| 4    | d     | Name length = X\n| 8    | b * X | Counter name (NOT null terminated)\n      </div>\n\n      <h4>Strings List</h4>\n      <p>\n        The string list file starts with the number of strings:\n      </p>\n      <div class=\"markdown\">\n| Pos. | Size    | Description |\n|------|---------|-------------|\n| 0    | d       | Number of strings = N\n      </div>\n      <p>\n        A list of <code>N</code> string definitions follows:\n      </p>\n      <div class=\"markdown\">\n| Pos.  | Size    | Description |\n|-------|---------|-------------|\n| 0     | d       | String name length = X\n| 4     | d       | String value length = Y\n| 8     | b * X   | String name (NOT null terminated)\n| 8 + X | b * Y   | String value (NOT null terminated)\n      </div>\n\n    </section>\n\n    <section id=\"board290\" class=\"inner\">\n      <h3>Board Properties</h3>\n      <p>\n        Board data is stored in a properties file with the following properties:\n      </p>\n      <div class=\"markdown\">\n| ID       | Property                           | Data Type | Notes |\n|----------|------------------------------------|-----------|-------|\n| `0x0001` | Board name                         | string    | Strict<a href=\"#board290note1\">(1)</a><a href=\"#board290note2\">(2)</a>\n| `0x0002` | Board width                        | int(w)    | Strict\n| `0x0003` | Board height                       | int(w)    | Strict\n| `0x0004` | Overlay mode                       | int(b)    | Strict\n| `0x0005` | Robot count                        | int(b)    | Strict\n| `0x0006` | Scroll count                       | int(b)    | Strict\n| `0x0007` | Sensor count                       | int(b)    | Strict\n| `0x0008` | File version                       | int(w)    | Strict<a href=\"#board290note3\">(3)</a>\n| `0x0010` | Mod playing                        | string\n| `0x0011` | Viewport X                         | int(b)\n| `0x0012` | Viewport Y                         | int(b)\n| `0x0013` | Viewport width                     | int(b)\n| `0x0014` | Viewport height                    | int(b)\n| `0x0015` | Can shoot?                         | int(b)\n| `0x0016` | Can bomb?                          | int(b)\n| `0x0017` | Fire burns brown?                  | int(b)\n| `0x0018` | Fire burns space?                  | int(b)\n| `0x0019` | Fire burns fakes?                  | int(b)\n| `0x001A` | Fire burns trees?                  | int(b)\n| `0x001B` | Explosions leave                   | int(b)\n| `0x001C` | Save mode                          | int(b)\n| `0x001D` | Forest to floor?                   | int(b)\n| `0x001E` | Collect bombs?                     | int(b)\n| `0x001F` | Fire burns forever?                | int(b)\n| `0x0020` | Board # to north                   | int(b)\n| `0x0021` | Board # to south                   | int(b)\n| `0x0022` | Board # to east                    | int(b)\n| `0x0023` | Board # to west                    | int(b)\n| `0x0024` | Restart if zapped?                 | int(b)\n| `0x0025` | Time limit                         | int(w)\n| `0x0026` | Player locked NS?                  | int(b)\n| `0x0027` | Player locked EW?                  | int(b)\n| `0x0028` | Player attack locked?              | int(b)\n| `0x0029` | Reset board on entry?              | int(b)\n| `0x002A` | Board charset path                 | string\n| `0x002B` | Board palette path                 | string\n| `0x002C` | Reset/load on entry if same board? | int(b)    | Since 2.93\n| `0x002D` | Dragons move randomly              | int(b)    | Since 2.93\n| `0x0100` | Scroll relative X offset           | int(ws)   | Save-only\n| `0x0101` | Scroll relative Y offset           | int(ws)   | Save-only\n| `0x0102` | Scroll locked X                    | int(ws)   | Save-only (-1: no lock)\n| `0x0103` | Scroll locked Y                    | int(ws)   | Save-only (-1: no lock)\n| `0x0104` | Last player direction              | int(b)    | Save-only\n| `0x010A` | Lazerwall timer                    | int(b)    | Save-only\n| `0x010B` | Last key pressed                   | int(b)    | Save-only\n| `0x010C` | Input (numeric value)              | int(ds)   | Save-only\n| `0x010D` | Inputsize                          | int(ds)   | Save-only\n| `0x010E` | Input (string value)               | string    | Save-only\n| `0x0110` | Bottom message                     | string    | Save-only\n| `0x0111` | Bottom message timer               | int(b)    | Save-only\n| `0x0112` | Bottom message row                 | int(b)    | Save-only\n| `0x0113` | Bottom message column              | int(bs)   | Save-only (-1: center)\n| `0x0114` | Volume                             | int(b)    | Save-only\n| `0x0115` | Volume increment                   | int(b)    | Save-only\n| `0x0116` | Volume target                      | int(b)    | Save-only\n| `0x0117` | Blind duration                     | int(b)    | Save-only, 1.x only\n| `0x0118` | Firewalker duration                | int(b)    | Save-only, 1.x only\n| `0x0119` | Freeze time duration               | int(b)    | Save-only, 1.x only\n| `0x011a` | Slow time duration                 | int(b)    | Save-only, 1.x only\n| `0x011b` | Wind duration                      | int(b)    | Save-only, 1.x only\n      </div>\n      <ol>\n        <li id=\"board290note1\">\n          Properties marked \"strict\" must be present and in the order specified\n          in the table for the board data to be considered valid. If the board\n          properties file is invalid, the entire board contents will be dummied\n          out. Properties marked \"save-only\" are saved to save files only, but will\n          be respected if loaded from a world file. Properties marked \"1.x only\"\n          are saved/loaded only if the world compatibility version is 1.x.\n        </li>\n        <li id=\"board290note2\">\n          Board name maximum length is currently 24 characters; extra data\n          will be ignored. Versions prior to 2.93 expect this field to be null\n          terminated and may append garbage to the name if it is not.\n        </li>\n        <li id=\"board290note3\">\n          This field exists mainly to provide redundancy for the\n          <a href=\"#mzb\">.MZB</a> file header. In world and save files, this\n          field should be redundant with the file version field derived from\n          the world or save magic.\n        </li>\n      </ol>\n    </section>\n\n    <section id=\"boardplanes290\" class=\"inner\">\n      <h3>Board Planes</h3>\n      <p>\n        Each board has 6 or 8 planes (depending on if overlay is enabled) saved\n        in separate files as raw data of size <code>(board width * board height)</code>\n        bytes each. If any of the expected planes are missing when loading a\n        board, an error will display and the plane will be zero-initialized.\n      </p>\n    </section>\n\n    <section id=\"robot290\" class=\"inner\">\n      <h3>Robot Properties</h3>\n      <p>\n        Robot data is stored in a properties file with the following properties:\n      </p>\n      <div class=\"markdown\">\n| ID       | Property                     | Data Type             | Notes |\n|----------|------------------------------|-----------------------|-------|\n| `0x0001` | Robot name                   | string                | Strict<a href=\"#robot290note1\">(1)</a><a href=\"#robot290note1\">(2)</a>\n| `0x0002` | Robot char                   | int(b)                | Strict\n| `0x0003` | X position                   | int(ws)               | Strict\n| `0x0004` | Y position                   | int(ws)               | Strict\n| `0x00FF` | Program bytecode             | array(b * N)          | Strict\n| `0x0100` | Current position in program  | int(d)                | Save-only\n| `0x0101` | Position within current line | int(d)                | Save-only\n| `0x0102` | Robot cycle                  | int(b)                | Save-only\n| `0x0103` | Cycle counter                | int(b)                | Save-only\n| `0x0104` | Bullet type                  | int(b)                | Save-only\n| `0x0105` | Locked status                | int(b)                | Save-only\n| `0x0106` | Can lavawalk                 | int(b)                | Save-only\n| `0x0107` | Walk direction               | int(b)                | Save-only\n| `0x0108` | Last touch direction         | int(b)                | Save-only\n| `0x0109` | Last shot direction          | int(b)                | Save-only\n| `0x010A` | Status                       | int(b)                | Save-only\n| `0x010B` | Loopcount                    | int(ds)               | Save-only\n| `0x010C` | Locals                       | array(ds * 32)        | Save-only\n| `0x0110` | Current position in stack    | int(d)                | Save-only\n| `0x0111` | Robot stack                  | array(d * N)          | Save-only\n| `0x0120` | Can goopwalk                 | int(b)                | Save-only\n      </div>\n      <ol>\n        <li id=\"robot290note1\">\n          Properties marked \"strict\" must be present and in the order specified\n          in the table for the robot data to be considered valid. Properties\n          marked \"save-only\" are saved to save files only, but will be respected\n          if loaded from a world file.\n        </li>\n        <li id=\"robot290note2\">\n          Robot name maximum length is currently 14 characters; extra data\n          will be ignored. Versions prior to 2.92d expect this field to be\n          exactly 15 bytes long and to contain a null terminator, and may behave\n          unexpectedly if it does not.\n        </li>\n      </ol>\n    </section>\n\n    <section id=\"scroll290\" class=\"inner\">\n      <h3>Scroll Properties</h3>\n      <p>\n        Scroll data is stored in a properties file with the following properties:\n      </p>\n      <div class=\"markdown\">\n| ID       | Property        | Data Type |\n|----------|-----------------|-----------|\n| `0x0001` | Number of lines | int(w)\n| `0x0002` | Scroll text     | string (with \\0)\n      </div>\n    </section>\n\n    <section id=\"sensor290\" class=\"inner\">\n      <h3>Sensor Properties</h3>\n      <p>\n        Sensor data is stored in a properties file with the following properties:\n      </p>\n      <div class=\"markdown\">\n| ID       | Property         | Data Type | Notes |\n|----------|------------------|-----------|-------|\n| `0x0001` | Sensor name      | string    | <a href=\"#sensor290note1\">(1)</a>\n| `0x0002` | Sensor char      | int(b)\n| `0x0003` | Robot to message | string    | <a href=\"#sensor290note1\">(1)</a>\n      </div>\n      <ol>\n        <li id=\"sensor290note1\">\n          Robot/sensor name maximum length is currently 14 characters; extra data\n          will be ignored. Versions prior to 2.92d expect these fields to be\n          exactly 15 bytes long and to contain a null terminator, and may behave\n          unexpectedly if they do not.\n        </li>\n      </ol>\n    </section>\n  </section>\n\n  <hr>\n\n  <section id=\"mzb\" class=\"main\">\n    <h2>Board File (.MZB)</h2>\n    <p>\n      A MegaZeux board file begins with a 4-byte header:\n    </p>\n\n    <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | b    | Always hex <code>FF</code>\n| 1    | s3   | Magic (<code>MB2</code> for 2.00 through 2.51s1, same as <a href=\"#worldheader\">world magic</a> after)\n    </div>\n\n    <section id=\"mzb100\" class=\"inner\">\n      <h3>Board Files for MegaZeux 1.x</h3>\n      <p>\n        A MegaZeux 1.x board file contains the raw data for a single board in\n        the world format (including robots, scrolls, and sensors as-needed).\n        These board files actually do NOT contain the above header; they begin\n        exactly at the first RLE plane. See the <a href=\"#board100\">world format\n        documentation for boards</a> for more info. A 25-byte null terminated\n        board name follows the board data.\n      </p>\n    </section>\n\n    <section id=\"mzb200\" class=\"inner\">\n      <h3>Board Files for 2.00 through 2.84X</h3>\n      <p>\n        The header is immediately followed by the raw data for a single board\n        in the world format (including robots, scrolls, and sensors as-needed).\n        See the <a href=\"#board200\">world format documentation for boards</a>\n        for more info. A 25-byte null terminated board name follows the board\n        data.\n      </p>\n    </section>\n\n    <section id=\"mzb290\" class=\"inner\">\n      <h3>Board Files for 2.90 onward</h3>\n      <p>\n        The header is immediately followed by a ZIP archive containing the\n        board properties, board planes, and object properties files for a single\n        board. The following filenames are used:\n      </p>\n\n      <div class=\"markdown\">\n| Filename  | Description |\n|-----------|-------------|\n| `b00`     | <a href=\"#board290\">Board properties</a>\n| `b00bid`  | <a href=\"#boardplanes290\">Board level_id plane</a>\n| `b00bpr`  | <a href=\"#boardplanes290\">Board level_param plane</a>\n| `b00bco`  | <a href=\"#boardplanes290\">Board level_color plane</a>\n| `b00uid`  | <a href=\"#boardplanes290\">Board level_under_id plane</a>\n| `b00upr`  | <a href=\"#boardplanes290\">Board level_under_param plane</a>\n| `b00uco`  | <a href=\"#boardplanes290\">Board level_under_color plane</a>\n| `b00och`  | <a href=\"#boardplanes290\">Board overlay_char plane</a> (if overlay is enabled)\n| `b00oco`  | <a href=\"#boardplanes290\">Board overlay_color plane</a> (if overlay is enabled)\n| `b00r##`  | <a href=\"#robot290\">Robot properties</a> (up to 255)\n| `b00sc##` | <a href=\"#scroll290\">Scroll properties</a> (up to 255)\n| `b00se##` | <a href=\"#sensor290\">Sensor properties</a> (up to 255)\n      </div>\n    </section>\n  </section>\n\n  <hr>\n\n  <section id=\"mzm\" class=\"main\">\n    <h2>Image File (.MZM)</h2>\n    <p>\n      Raw blocks of board and overlay/vlayer data can be saved to and loaded\n      from MZMs (or MZX image files). Three variants of the MZM format exist.\n      The current MZM format is MZM3, which was introduced in MegaZeux 2.84.\n    </p>\n    <section id=\"mzm3\" class=\"inner\">\n      <h3>MZM3</h3>\n      <p>\n        An MZM3 file begins with the following 20-byte header:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description                                   | Values |\n|------|------|-----------------------------------------------|--------|\n| 0    | s4   | Magic                                         | <code>MZM3</code>\n| 4    | w    | Width\n| 6    | w    | Height\n| 8    | d    | Location in file of robot data                | 0 if not present\n| 12   | b    | Number of robots in data                      | 0 if not present\n| 13   | b    | Storage mode                                  | 0: board, 1: layer\n| 14   | b    | \"Savegame\" MZM? (includes runtime robot data) | 0 if false, 1 if true\n| 15   | w    | World version                                 | See below.\n| 17   | b    | reserved\n| 18   | b    | reserved\n| 19   | b    | reserved\n      </div>\n\n      <p>\n        The world version is stored as a 2-byte little endian value where the\n        upper byte is the major version number and the lower byte is the minor\n        version number (like the world version field in the\n        <a href=\"#saveheader\">save format header</a>). MZM3 files are forward\n        compatible unless they contain robot information, in which case the\n        robots in the MZM are replaced with customblocks.\n      </p>\n\n      <p>\n        The header is immediately followed by a board data block or a layer\n        data block depending on the storage mode value indicated in the header.\n      </p>\n\n      <h4 id=\"mzmstorage0\">Storage Mode 0 (Board)</h4>\n      <p>\n        The MZM data is stored in the following format if the \"board\" storage\n        mode is selected. The data is composed of <code>(width * height)</code>\n        blocks, where each block is 6 bytes and contains the following:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | b    | ID\n| 1    | b    | Param\n| 2    | b    | Color\n| 3    | b    | Under ID\n| 4    | b    | Under param\n| 5    | b    | Under color\n      </div>\n\n      <p>\n        Signs, scrolls, sensors, players, and IDs >=128 will be replaced with\n        spaces when an MZM is loaded. Robots in a board MZM require extra\n        storage information.\n      </p>\n\n      <p>\n        When a board MZM is created from the overlay or vlayer, the ID is\n        typically set to 5, the param to the char, the color to the color, and\n        all other values are set to 0.\n      </p>\n\n      <h4>Storage Mode 1 (Layer)</h4>\n      <p>\n        The MZM data is stored in the following format if the \"layer\" storage\n        mode is selected. The data is composed of <code>(width * height)</code>\n        blocks, where each block is 2 bytes and contains the following:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | b    | Char\n| 1    | b    | Color\n      </div>\n\n      <h4>Robot Data</h4>\n      <p>\n        MZMs using the \"board\" storage mode may additionally include a robot\n        data block if the MZM board data contains robots. This block will always\n        be located after the board data, and the format of this block is based\n        on the MZX world format corresponding to the world version indicated in\n        the header.\n      </p>\n\n      <p>\n        MZMs created in MZX 2.84X will contain <code>N</code> robot blocks as\n        described in the <a href=\"#robot200\">2.00 through 2.84X world format\n        documentation</a>. If this is a savegame MZM, the robots will be in the\n        save format instead of the world format.\n      </p>\n\n      <p>\n        MZMs created in MZX 2.90 and onward will contain a ZIP archive after\n        the board data containing <code>N</code> <a href=\"#robot290\">robot\n        properties files</a> named in the format <code>r##</code>, where\n        <code>##</code> is a hexadecimal number between 1 and 255 corresponding\n        to a robot ID in the board data. If this is a savegame MZM, the robots\n        will contain savegame properties.\n      </p>\n    </section>\n\n    <section id=\"mzm2\" class=\"inner\">\n      <h3>MZM2</h3>\n      <p>\n        MZM2 was introduced in MegaZeux 2.80 and is essentially the same as MZM3\n        without the version field in the header. The MZM2 header is 16 bytes\n        long:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s4   | `MZM2`\n| 4    | w    | Width\n| 6    | w    | Height\n| 8    | d    | Location in file of robot table (0 for no robots)\n| 12   | b    | Number of robots (0 for no robots)\n| 13   | b    | Storage mode (0: board, 1: layer)\n| 14   | b    | \"Savegame\" MZM? (includes runtime robot data) (0 if false, 1 if true)\n| 15   | b    | unused\n      </div>\n\n      <p>\n        The board data and robot data follows exactly as MZM3 with the\n        exception that robots are always use the MZX 2.80 through 2.83 robot\n        format rather than newer formats.\n      </p>\n    </section>\n\n    <section id=\"mzmx\" class=\"inner\">\n      <h3>MZMX</h3>\n      <p>\n        MZMX is the original MZM format that was introduced in MZX 2.68. It is\n        much more limited than newer MZM formats and is generally only supported\n        for compatibility. MZMX only supports blocks with dimensions of 255 or\n        smaller, does not save robots, and only supports storage mode 0. The\n        MZMX header is 16 bytes long:\n      </p>\n\n      <div class=\"markdown\">\n| Pos. | Size   | Description |\n|------|--------|-------------|\n| 0    | s4     | `MZMX`\n| 4    | b      | Width\n| 5    | b      | Height\n| 6    | b * 10 | unused\n      </div>\n\n      <p>\n        The board data follows as described by\n        <a href=\"#mzmstorage0\">MZM3's Storage Mode 0</a>.\n      </p>\n    </section>\n  </section>\n\n  <hr>\n\n  <section id=\"counters\" class=\"main\">\n    <h2>Counters File</h2>\n    <p>\n      The counters file format is a light wrapper around the MZX 2.90+ save\n      format <a href=\"#counters290\">counter list and string list files</a>.\n      Counters files are created and loaded by the <code>SAVE_COUNTERS</code>\n      and <code>LOAD_COUNTERS</code> pseudo-commands and currently have no\n      standard filename extension. A counters file beings with this header:\n    </p>\n\n    <div class=\"markdown\">\n| Pos. | Size | Description |\n|------|------|-------------|\n| 0    | s8   | Magic (<code>COUNTERS</code>)\n    </div>\n\n    <p>\n      This header is followed by a ZIP archive containing only the\n      <code>counter</code> and <code>string</code> files.\n    </p>\n  </section>\n\n  <hr>\n\n  <section id=\"sfx\" class=\"main\">\n    <h2>Custom SFX Table File (.SFX)</h2>\n    <p>\n      The custom SFX table file format is exactly the same as the\n      <a href=\"#sfx290\">custom SFX table described in the 2.90+ world format</a>.\n      MegaZeux 2.93 and up can save sound effects in either the new format or in\n      the old raw array format. All prior versions of MegaZeux that support sound\n      effects export (2.00 through 2.92f) use the raw array format.\n    </p>\n  </section>\n\n  <hr>\n\n  <section id=\"bytecode\" class=\"main\">\n    <h2>Robotic Bytecode (.BC)</h2>\n    <p>\n      This section describes the general structure of Robotic bytecode. The\n      specifics are out of the scope of this document. More info can be found\n      in the file <code>info_mzx.txt</code> distributed with MegaZeux source\n      packages.\n    </p>\n\n    <ul>\n      <li>\n        Valid Robotic programs begin with the byte <code>FF</code>.\n      </li>\n      <li>\n        A sequence of commands in the following format follows:\n        <code>LL XX ... LL</code>, where <code>LL</code> is the length of the\n        bytecode command (0-255) and <code>XX</code> is the command number. The\n        length does not include the length bytes themselves, only the command\n        and parameters between them.\n      </li>\n      <li>\n        Between the command number and terminating length byte, a number of\n        parameters follow in one of two formats: <code>00 XX XX</code>, where\n        <code>XX XX</code> is a little-endian word, or\n        <code>LL [string] 00</code>, where <code>LL</code> is the length of the\n        null-terminated param string that follows (including the null\n        terminator). The number of required params varies between commands.\n      </li>\n      <li>\n        The next command's prefixed length byte immediately follows the previous\n        command's trailing length byte.\n      </li>\n      <li>\n        A final byte of <code>00</code> (indicating a command of length zero)\n        terminates the program.\n      </li>\n      <li>\n        The shortest valid Robotic program is <code>FF 00</code> with a length of 2.\n      </li>\n    </ul>\n  </section>\n\n  <hr>\n\n  <section id=\"license\" class=\"main\">\n    <h2>License</h2>\n    <section class=\"inner\">\n      <p>\n        Copyright &copy; 2020-2025 Lachesis &mdash;\n        <a href=\"https://github.com/AliceLR/megazeux/\">\n          https://github.com/AliceLR/megazeux/\n        </a>\n      </p>\n\n      <p>\n        Permission to use, copy, modify, and distribute this document for any\n        purpose with or without fee is hereby granted, provided that the above\n        copyright notice and this permission notice appear in all copies.\n      </p>\n\n      <p>\n        THE DOCUMENT IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n        WITH REGARD TO THIS DOCUMENT INCLUDING ALL IMPLIED WARRANTIES OF\n        MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n        ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n        WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n        ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n        OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS DOCUMENT.\n      </p>\n    </section>\n  </section>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "docs/idle_direction_oddities.txt",
    "content": "Direction idiosyncracities:\n\nIDLE counts as walking RANDB if the Robot is not blocked in any cardinal direction.\nIDLE counts as walking RANDNB if the Robot is blocked in all cardinal directions.\nIDLE Robots are moving CW IDLE, OPP IDLE, RANDP IDLE, and RANDNOT IDLE.\nWalking FLOW works for IDLE Robots.\nIDLE Robots are not moving CW BENEATH, OPP BENEATH, RANDP BENEATH, or RANDNOT BENEATH."
  },
  {
    "path": "docs/info_mzx.txt",
    "content": "\n** Codes for objects-\n\nID  Is              Char Param           Color Special\n--- --------------- ---- --------------- ----- ----------------------------\n0   Space           32                   Curr  UNDER\n1   Normal          178                  Curr\n2   Solid           219                  Curr\n3   Tree            6                    10\n4   Line            Var                  Curr\n5   Custom Block    Par  Char            Curr\n6   Breakaway       177                  Curr\n7   Custom Break    Par  Char            Curr\n8   Boulder         233                  7\n9   Crate           254                  6\n10  Custom Push     Par  Char            Curr\n11  Box             254                  Curr\n12  Custom Box      Par  Char            Curr\n13  Fake            178                  Curr  UNDER\n14  Carpet          177                  Curr  UNDER\n15  Floor           176                  Curr  UNDER\n16  Tiles           254                  Curr  UNDER\n17  Custom Floor    Par  Char            Curr  UNDER\n18  Web             Var                  7     UNDER\n19  Thick Web       Var                  7     UNDER\n20  Still Water     176                  25    UNDER\n21  N Water         24                   25    UNDER\n22  S Water         25                   25    UNDER\n23  E Water         26                   25    UNDER\n24  W Water         27                   25    UNDER\n25  Ice             Var  Anim            59    UNDER\n26  Lava            Var  Anim            76    UNDER\n27  Chest           239  Contents        6     CHAR 160 IN v2.0+\n28  Gem             4                    Curr\n29  Magic Gem       4                    Curr\n30  Health          3    Amount          12\n31  Ring            9    Effect          14\n32  Potion          173  Effect          11    CHAR 150 IN v2.0+\n33  Energizer       7    Glow            15\n34  Goop            176                  24    v2.0+ LIKE ZZT'S WATER\n35  Ammo            Var  Amount          3\n36  Bomb            11   High or Low     8\n37  Lit Bomb        Var  Fuse            8\n38  Explosion       177  Stage           239\n39  Key             12                   Curr\n40  Lock            10                   Curr\n41  Door            Var  Dir/Lock        Curr\n42  Open Door       Var  Dir/Stage       Curr  TREAT AS ABOVE FOR CHANGE CMD\n43  Stairs          240  Destination     Curr  UNDER, CHAR 162 IN v2.0+\n44  Cave (\"Door\")   239  Destination     Curr  UNDER, CHAR 161 IN v2.0+\n45  CW Rotate       Var  Anim            Curr\n46  CCW Rotate      Var  Anim            Curr\n47  Gate            22   Lock            8\n48  Open Gate       95   Delay           8     UNDER, CHAR 196 IN v2.0+\n49  Transport       Var  Anim/Dir        Curr\n50  Coin            7                    14\n51  N Moving Wall   Par  Char            Curr\n52  S Moving Wall   Par  Char            Curr\n53  E Moving Wall   Par  Char            Curr\n54  W Moving Wall   Par  Char            Curr\n55  Pouch           229  Contents        7     CHAR 159 IN v2.0+\n56  Pusher          Var  Direction       Curr\n57  Slider NS       18                   Curr\n58  Slider EW       29                   Curr\n59  Lazer           Var  Anim/Dir/Stage  Curr\n60  Lazer Gun       206  Dir/Timing      4\n61  Bullet          Var  Dir/Type        15\n62  Missile         Var  Dir             8\n63  Fire            Var  Anim            12\n64\n65  Forest          178                  2\n66  Life            Var  Anim            13\n67  Whirlpool 1     54   Destination     31    UNDER, CHAR 151-154 IN v2.0+\n68  Whirlpool 2     64   Destination     31    UNDER, TREAT AS 1 FOR CHANGE\n69  Whirlpool 3     57   Destination     31    UNDER, TREAT AS 1 FOR CHANGE\n70  Whirlpool 4     149  Destination     31    UNDER, TREAT AS 1 FOR CHANGE\n71  Invis Wall      32                   Curr\n72  Ricochet Panel  Var  Orientation     9\n73  Ricochet        42                   10\n74  Mine            Var  Anim/Radius     12\n75  Spike           Var  Dir             8\n76  Custom Hurt     Par  Char            Curr\n77  Text            Par  Char            Curr\n78  Shooting Fire   Var  Anim/Dir        14\n79  Seeker          Var  Anim/Life       10\n80  Snake           235  Int/Spd/Dir     2\n81  Eye             236  Int/Spd/Radius  15\n82  Thief           1    Int/Spd/Steal   12    CHAR 5 IN v2.0+\n83  Slimeblob       42   Spd/Invnc/Hurts 10\n84  Runner          2    Spd/Dir/HP      4\n85  Ghost           234  Int/Spd/Invnc   7\n86  Dragon          21   Fire/Move/HP    4\n87  Fish            224  Int/Spd/Hurts   14    PARAM CONT- HP/CurrentAffects\n88  Shark           94   Int/Fire/Rate   7     CHAR 127 IN v2.0+\n89  Spider          15   Int/Spd/HP/Web  7     CHAR 149 IN v2.0+\n90  Goblin          5    Int/Rest        2\n91  Spitting Tiger  227  Int/Fire/Rate   11\n92  Bullet Gun      Var  Dir/Rate/Int    15\n93  Spinning Gun    Var  Dir/Rate/Int    15\n94  Bear            153  Sens/Spd/HP     6     CHAR 172 IN v2.0+\n95  Bear Cub        148  Int/Mode/Switch 6     CHAR 173 IN v2.0+\n96\n97  Missile Gun     Var  Int/Dir/Rate    8\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n108\n109\n110\n111\n112\n113\n114\n115\n116\n117\n118\n119\n120\n121\n122 Sensor          Var  ID #            Curr\n123 Robot Pushable  Var  ID #            Curr\n124 Robot           Var  ID #            Curr\n125 Sign            226  ID #            8\n126 Scroll          232  ID #            15\n127 Player          Var                  27\n\n** Robot program storage format-\n\nBYTE 1- Number of total bytes in this command, not including this one or\n\t\t  the final byte which is a repeat of this count.\n\t\t  A zero here signifies the end of the program.\n\t\t  A -1 (~0) (0FFh) here signifies the start of the program, and is\n\t\t  immediatly followed by BYTE 1 of the first command.\nBYTE 2- Command identification code.\nTHEN  - Command paramters, if any, according to command. Parameters take\n\t\t  the minimum bytes required for ver 1.0?. For ver 2.00, they are\n\t\t  all min. 3 bytes- The first byte is a code of n for a string/counter\n\t\t  of n length or 0 for a numeral, the second/third bytes is the numeral\n\t\t  or the null-terminated counter/string. Conditions/etc are stored as\n\t\t  numerals. Even things like directions that fit in one byte take up\n\t\t  two bytes. Nothing except counters/strings requires more than these\n\t\t  two bytes, however. The length of a string INCLUDES THE NULL BYTE.\nFINAL - Repeat of BYTE 1 for backwards searching.\n\nA blank program is 2 bytes long- A -1 then a 0.\n\n** Scroll storage format-\n\nBYTE 1- A 01h. This marks the start of the text.\nMIDDLE- Lines, all terminated with a '\\n'. (0Ah) There must be at least\n\t\t  one line, even if it is just a '\\n'.\nLAST  - A 00h. This marks the end of the text.\n\nA blank text is 3 bytes long- A 01h, a '\\n', and a 00h.\n\n** Command line options-\n\nVersion 1.0? and below-\n\n-debug  : Begin with x/y pos, etc showing (Like Alt-Y within program)\n-ms     : Music on SB\n-mp     : Music on PC\n-mo     : Music off\n-sp     : PC speaker sound effects on\n-so     : PC speaker sound effects off\n-bios   : Use bios for certain functions\n-nomouse: Don't activate mouse\n-mouse  : Use mouse even if found\n-prism  : Cheat keys\n-ii!    : Pro mode\n-keyboard2:Use another method of ending the keyboard handler\n\nVersion 2.0 and above-\n\n-?      : Bring up command line help screen, also done on invalid option\n\t\t\t along with a beep\n-lblah  : Loads up blah.mzx at start up (.mzx is optional)\n-nomouse: Don't activate mouse\n-noems  : Don't use EMS\n-ega    : Use EGA mode even on higher cards\n-keyb2  : Use another method of ending the keyboard handler\n\n** Transport destination search algorithim\n\n1) Check space on other side of transport- if it is empty or can be pushed\n\tout of the way, transport to there.\n2) Check starting at space on other side for a transport of opposite\n\torientation or of anydir orientation. The first found that has empty\n\ton the other side or stuff that can be pushed out of the way on the\n\tother side is the destination.\n3) No destination.\n\n** Robot command codes-\n\nVersion 1.0?-\n\nEOC=End-of-cycle (x=yes, Pre=prefix)\nParams-\n\t[#] Number 0 to 255\n\t[##] Number 0 to 65535\n\t[-#] Number from -128 to 127\n\t[dir] NSEW plus modifiers\n\t[ch] Character\n\t[col] Color\n\t[color/thing/param] Color + Thing + Param\n\t[color/thing] Color + Thing (no Param)\n\t[thing] Thing (no Color or Param)\n\t[str] String\n\t[!<>=] Equality\n\t[cond] Condition (often with a direction)\n\t[item] Item (AMMOS, etc)\n\n\tColor can only have ?'s when coupled with a thing. In that case, if\n\ta ? is present, the high bit of the thing is set. (+128) See later\n\tsection for color coding.\n\nID  EOC Command\n--- --- -------\n0    x  End\n1    x  Die\n2    x  Wait [#]\n3       Cycle [#]\n4    x  Go [dir] [#]\n5       Walk [dir]\n6    x  Become [color/thing/param]\n7       Char [ch]\n8       Color [col]\n9    x  Gotoxy [-#] [-#]\n10      Set [str] [##]\n11      Inc [str] [##]\n12      Dec [str] [##]\n13      Set [str] [str]\n14      Inc [str] [str]\n15      Dec [str] [str]\n16      If [str] [!<>=] [##] [str]\n17      If [str] [!<>=] [str] [str]\n18      If [cond] [str]\n19      If not [cond] [str]\n20      If any [color/thing] [str]\n21      If not any [color/thing] [str]\n22      If [color/thing] [dir] [str]\n23      If not [color/thing] [dir] [str]\n24      If [color/thing] [-#] -#] [str]\n25      If [-#] [-#] [str]\n26      If [dir] player [color/thing] [str]\n27      Double [str]\n28      Half [str]\n29      Goto [str]\n30      Send [str] [str]\n31   x  Explode [#]\n32      Put [color/thing/param] [dir]\n33      Give [##] [item]\n34      Take [##] [item]\n35      Take [##] [item] [str]\n36      Endgame\n37      Endlife\n38      Mod [str]\n39      Sam [##] [str]\n40      Volume [#]\n41      End mod\n42      End sam\n43      Play [str]\n44      End play\n45      Wait play [str]\n46      Wait play\n47      (blank line)\n48      Sfx [#]\n49      Play sfx [str]\n50      Open [dir]\n51      Lockself\n52      Unlockself\n53      Send [dir] [label]\n54      Zap [str] [#]\n55      Restore [str] [#]\n56      Lockplayer\n57      Unlockplayer\n58      Lockplayer ns\n59      Lockplayer ew\n60      Lockplayer attack\n61   x  Move player [dir]\n62   x  Move player [dir] [str]\n63      Put player [-#] [-#]\n64      If player [dir] [str]\n65      If player not [dir] [str]\n66      If player [-#] [-#] [str]\n67      Put player [dir]\n68   x  Go [dir] [str]\n69      Rotatecw\n70      Rotateccw\n71      Switch [dir] [dir]\n72      Shoot [dir]\n73      Laybomb [dir]\n74      Laybomb high [dir]\n75      Shootmissile [dir]\n76      Shootseeker [dir]\n77      Spitfire [dir]\n78      Lazerwall [dir] [#]\n79      Put [color/thing/param] [-#] [-#]\n80   x  Die item\n81      Send [-#] [-#] [str]\n82   x  Copyrobot [str]\n83   x  Copyrobot [-#] [-#]\n84   x  Copyrobot [dir]\n85      Duplicate self [dir]\n86      Duplicate self [-#] [-#]\n87      Bulletn [ch] (In MZX 2.0, changes all bullet n pics)\n88      Bullets [ch] (In MZX 2.0, changes all bullet s pics)\n89      Bullete [ch] (In MZX 2.0, changes all bullet e pics)\n90      Bulletw [ch] (In MZX 2.0, changes all bullet w pics)\n91      Givekey [col]\n92      Givekey [col] [str]\n93      Takekey [col]\n94      Takekey [col] [str]\n95      Inc [str] random [##] [##]\n96      Dec [str] random [##] [##]\n97      Set [str] random [##] [##]\n98      Trade [##] [item] [##] [item] [str]\n99      Send [dir] player [str]\n100     Put [color/thing/param] [dir] player\n101  x  /[str]\n102     *[str]\n103     [[str]\n104     ?[str];[str]\n105     ?[str];[str];[str]\n106     :[str]\n107     .[str]\n108     |[str]\n109  x  Teleport player [str] [-#] [-#]\n110     Scrollview [dir] [#]\n111     Input string [str]\n112     If string [str] [str]\n113     If string not [str] [str]\n114     If string matches [str] [str]\n115     Player char [ch] (In MZX 2.0, sets the pic for all four directions)\n116     %[str]\n117     &[str]\n118  x  Move all [color/thing] [dir]\n119  x  Copy [-#] [-#] [-#] [-#]\n120     Set edge color [col]\n121     Board [dir] [str]\n122     Board [dir] none\n123     Char edit [ch] [#] [#] [#] [#] [#] [#] [#] [#] [#] [#] [#] [#] [#] [#]\n124     Become pushable\n125     Become nonpushable\n126     Blind [#]\n127     Firewalker [#]\n128     Freezetime [#]\n129     Slowtime [#]\n130     Wind [#]\n131     Avalance\n132  x  Copy [dir] [dir]\n133     Become lavawalker\n134     Become nonlavawalker\n135     Change [color/thing] [color/thing/param]\n136     Playercolor [col]\n137     Bulletcolor [col] (In MZX 2.0, changes all bullet colors)\n138     Missilecolor [col]\n139     Message row [#]\n140 Pre Rel self\n141 Pre Rel player\n142 Pre Rel counters\n143     Change char id [##] [ch] (Not param checked or doc'd until MZX 2.0)\n144     Jump mod order [#]\n145     Ask [str]\n146     Fillhealth\n147     Change thick arrow char [dir] [ch]\n148     Change thin arrow char [dir] [ch]\n149     Set maxhealth [##]\n150     Save player position (In MZX 2.0, saves to position 1)\n151     Restore player position (In MZX 2.0, uses position 1)\n152     Exchange player position (In MZX 2.0, saves to position 1)\n153     Set mesg column [#]\n154     Center mesg\n155     Clear mesg\n156     Resetview\n157     Sam [##] [#]\n158     Volume [str]\n159     Scrollbase color [col]\n160     Scrollborder color [col]\n161     Scrolltitle color [col]\n162     Scrollpointer color [col]\n163     Scrollarrow color [col]\n164     Viewport [#] [#] (Not param checked until MZX 2.0)\n165     Viewport size [#] [#] (Not param checked until MZX 2.0)\n166     Set mesg column [str]\n167     Message row [str]\n\nVersion 2.00-\n\nEOC=End-of-cycle (x=yes, Pre=prefix, ODT=Outdated and NOT SUPPORTED since it\n\tcan be translated into another command, *=would be nice to seperate into\n\tmultiple words, g=works different/unpredictably for a global robot) The\n\tODT's may be replaced with new commands at a later\tdate.\nParams-\n\t[##] Number 0 to 65535 OR a counter (can be converted from a color, char,\n\t\tor param)\n\t[-#] Number from -32768 to 32768 OR a counter (can be converted)\n\t[ch] Character OR a counter (can be converted from a color, param, or num)\n\t[col] Color OR a counter (can be converted from a char, param, or num)\n\t[dir] NSEW plus modifiers\n\t[color/thing/param] Color + Thing + Param <- OR a counter/char/color/num\n\t[str] String (often a counter)\n\t[!<>=] Equality\n\t[cond] Condition (often with a direction)\n\t[item] Item (AMMOS/AMMO, GEMS/GEM, etc)\n\n\tColor can only have ?'s when coupled with a thing OR used in the overlay\n\tput command. In that case, if a ? is present, the +256 bit of the color\n\tis set. See later section for color coding.\n\n\tA param of 256 means \"any param\" or p??.\n\nID  EOC Command\n--- --- -------\n0    x  End\n1    x  Die\n2    x  Wait [##]\n3       Cycle [##]\n4   x g Go [dir] [##]\n5    g  Walk [dir]\n6   x g Become [color/thing/param]\n7    g  Char [ch]\n8    g  Color [col]\n9   x g Gotoxy [-#] [-#]\n10      Set [str] [##]\n11      Inc [str] [##]\n12      Dec [str] [##]\n13  ODT Set [str] [str]\n14  ODT Inc [str] [str]\n15  ODT Dec [str] [str]\n16      If [str] [!<>=] [##] [str]\n17  ODT If [str] [!<>=] [str] [str]\n18   g  If [cond] [str]\n19   g  If not [cond] [str]\n20      If any [color/thing/param] [str] (different)\n21      If no [color/thing/param] [str] (different)\n22      If [color/thing/param] [dir] [str] (different)\n23      If not [color/thing/param] [dir] [str] (different)\n24      If [color/thing/param] [-#] -#] [str] (different)\n25   g  If [-#] [-#] [str]\n26      If [dir] player [color/thing/param] [str] (different)\n27      Double [str]\n28      Half [str]\n29      Goto [str]\n30      Send [str] [str]\n31  x g Explode [##]\n32   g  Put [color/thing/param] [dir]\n33      Give [##] [item]\n34      Take [##] [item]\n35      Take [##] [item] [str]\n36   *  Endgame\n37   *  Endlife\n38      Mod [str]\n39      Sam [##] [str]\n40      Volume [##]\n41      End mod\n42      End sam\n43      Play [str]\n44      End play\n45      Wait play [str]\n46      Wait play\n47      (blank line)\n48      Sfx [##]\n49      Play sfx [str]\n50   g  Open [dir]\n51   *  Lockself\n52   *  Unlockself\n53   g  Send [dir] [label]\n54      Zap [str] [##]\n55      Restore [str] [##]\n56   *  Lockplayer\n57   *  Unlockplayer\n58   *  Lockplayer ns\n59   *  Lockplayer ew\n60   *  Lockplayer attack\n61   x  Move player [dir]\n62   x  Move player [dir] [str]\n63      Put player [-#] [-#]\n64  ODT If player [dir] [str] (Becomes If [cond=touching] [dir] [str])\n65  ODT If player not [dir] [str] (Becomes If not [cond=touching] [dir] [str])\n66      If player [-#] [-#] [str]\n67   g  Put player [dir]\n68  x g Try [dir] [str]\n69  * g Rotatecw\n70  * g Rotateccw\n71   g  Switch [dir] [dir]\n72   g  Shoot [dir]\n73  * g Laybomb [dir]\n74  * g Laybomb high [dir]\n75  * g Shootmissile [dir]\n76  * g Shootseeker [dir]\n77  * g Spitfire [dir]\n78  * g Lazerwall [dir] [##]\n79      Put [color/thing/param] [-#] [-#]\n80  x g Die item\n81      Send [-#] [-#] [str]\n82  x * Copyrobot [str]\n83  x * Copyrobot [-#] [-#]\n84  x*g Copyrobot [dir]\n85   g  Duplicate self [dir]\n86      Duplicate self [-#] [-#]\n87      Bulletn [ch] (In MZX 2.0, changes ALL bullet n pics)\n88      Bullets [ch] (In MZX 2.0, changes ALL bullet s pics)\n89      Bullete [ch] (In MZX 2.0, changes ALL bullet e pics)\n90      Bulletw [ch] (In MZX 2.0, changes ALL bullet w pics)\n91   *  Givekey [col]\n92   *  Givekey [col] [str]\n93   *  Takekey [col]\n94   *  Takekey [col] [str]\n95      Inc [str] random [##] [##]\n96      Dec [str] random [##] [##]\n97      Set [str] random [##] [##]\n98      Trade [##] [item] [##] [item] [str]\n99      Send [dir] player [str]\n100     Put [color/thing/param] [dir] player\n101  x  /[str]\n102     *[str]\n103     [[str]\n104     ?[str];[str]\n105     ?[str];[str];[str]\n106     :[str]\n107     .[str]\n108     |[str]\n109  x  Teleport player [str] [-#] [-#]\n110  *  Scrollview [dir] [##]\n111     Input string [str]\n112     If string [str] [str]\n113     If string not [str] [str]\n114     If string matches [str] [str]\n115     Player char [ch] (In MZX 2.0, sets the pic for all four directions)\n116     %[str]\n117     &[str]\n118  x  Move all [color/thing/param] [dir] (different)\n119  x  Copy [-#] [-#] [-#] [-#]\n120     Set edge color [col]\n121     Board [dir] [str]\n122     Board [dir] none\n123     Char edit [ch] [##] [##] [##] [##] [##] [##] [##] [##] [##] [##] [##] [##] [##] [##]\n124  g  Become pushable\n125  g  Become nonpushable\n126     Blind [##]\n127     Firewalker [##]\n128  *  Freezetime [##]\n129  *  Slowtime [##]\n130     Wind [##]\n131     Avalance\n132 x g Copy [dir] [dir]\n133  g  Become lavawalker\n134  g  Become nonlavawalker\n135     Change [color/thing/param] [color/thing/param] (Diff. from ver 1.0?)\n136  *  Playercolor [col]\n137  *  Bulletcolor [col] (In MZX 2.0, changes all bullet colors)\n138  *  Missilecolor [col]\n139     Message row [##]\n140 Pre Rel self (In MZX 2.0, unpredictable/useless for use w/global robot)\n141 Pre Rel player\n142 Pre Rel counters\n143     Change char id [##] [ch] (Not param checked or doc'd until MZX 2.0)\n144     Jump mod order [##]\n145     Ask [str]\n146  *  Fillhealth\n147     Change thick arrow char [dir] [ch]\n148     Change thin arrow char [dir] [ch]\n149  *  Set maxhealth [##]\n150     Save player position (In MZX 2.0, saves to position 1)\n151     Restore player position (In MZX 2.0, uses position 1)\n152     Exchange player position (In MZX 2.0, uses position 1)\n153     Set mesg column [##]\n154     Center mesg\n155     Clear mesg\n156  *  Resetview\n157     Sam [##] [##]\n158 ODT Volume [str]\n159  *  Scrollbase color [col]\n160  *  Scrollcorner color [col] (Diff. from MZX 1.03)\n161  *  Scrolltitle color [col]\n162  *  Scrollpointer color [col]\n163  *  Scrollarrow color [col]\n164     Viewport [##] [##] (Not param checked until MZX 2.0)\n165     Viewport size [##] [##] (Not param checked until MZX 2.0)\n166 ODT Set mesg column [str]\n167 ODT Message row [str]\n168     Save player position [##]\n169     Restore player position [##]\n170     Exchange player position [##]\n171     Restore player position [##] duplicate self\n172     Exchange player position [##] duplicate self\n173     Player bulletn [ch]\n174     Player bullets [ch]\n175     Player bullete [ch]\n176     Player bulletw [ch]\n177     Neutral bulletn [ch]\n178     Neutral bullets [ch]\n179     Neutral bullete [ch]\n180     Neutral bulletw [ch]\n181     Enemy bulletn [ch]\n182     Enemy bullets [ch]\n183     Enemy bullete [ch]\n184     Enemy bulletw [ch]\n185  *  Player bulletcolor [col]\n186  *  Neutral bulletcolor [col]\n187  *  Enemy bulletcolor [col]\n188\n189\n190\n191\n192\n193 Pre Rel self first (Unpredictable/useless for use w/global robot)\n194 Pre Rel self last (Unpredictable/useless for use w/global robot)\n195 Pre Rel player first\n196 Pre Rel player last\n197 Pre Rel counters first\n198 Pre Rel counters last\n199     Mod fade out\n200     Mod fade in [str]\n201  x  Copy block [-#] [-#] [##] [##] [-#] [-#]\n202     Clip input\n203  g  Push [dir]\n204     Scroll char [ch] [dir]\n205     Flip char [ch] [dir]\n206     Copy char [ch] [ch]\n207\n208\n209\n210     Change sfx [##] [str]\n211     Color intensity [##] percent\n212     Color intensity [##] [##] percent\n213  x  Color fade out\n214  x  Color fade in\n215     Set color [##] [##] [##] [##]\n216     Load char set [str]\n217     Multiply [str] [##]\n218     Divide [str] [##]\n219     Modulo [str] [##]\n220     Player char [dir] [ch]\n221\n222\t  Load palette [str]\n223\n224     Mod fade [##] [##] (target/speed)\n225  *  Scrollview [-#] [-#]\n226  x  Swap world [str]\n227  *  If alignedrobot [str] [str]\n228\n229  *  Lockscroll\n230  *  Unlockscroll\n231     If first string [str] [str]\n232     Persistent go [str] (waits until it can move then moves)\n233     Wait mod fade\n234\n235\t  Enable saving\n236\t  Disable saving\n237  *  Enable sensoronly saving\n238     Status counter [##] [str]\n239     Overlay on\n240     Overlay static\n241     Overlay transparent\n242     Put [col] [ch] overlay [-#] [-#]\n243  x  Copy overlay block [-#] [-#] [##] [##] [-#] [-#]\n244\n245     Change overlay [col] [ch] [col] [ch]\n246     Change overlay [col] [col]\n247     Write overlay [col] [str] [-#] [-#]\n248\n249     INTERNAL USE (temporary use for box messages)\n250\n251\t  Loop start\n252     Loop [##]\n253     Abort loop\n254     Disable mesg edge\n255     Enable mesg edge\n\n** Color words-\n\n00 Black\n01 Dark Blue\n02 Dark Green\n03 Dark Cyan\n04 Dark Red\n05 Dark Purple\n06 Brown\n07 Light Gray\n08 Dark Gray\n09 Blue\n10 Green\n11 Cyan\n12 Red\n13 Purple\n14 Yellow\n15 White\n\n** Conditions-\n\nWalking [dir]\nSwimming\nFirewalking\nTouching [dir]\nBlocked [dir]\nAligned\nAlignedns\nAlignedew\nLastshot [dir]\nLasttouch [dir]\nRightpressed\nLeftpressed\nUppressed\nDownpressed\nSpacepressed\nDelpressed\nMusicon\nPcsfxon\n\n** Auto labels-\n\nTHUD, TOUCH, BOMBED, KEYA-Z, INVINCO, PUSHED, EDGE, ENEMYSHOT, PLAYERSHOT,\nNEUTRALSHOT, SHOT, PLAYERHIT, SENSORON, SENSORTHUD.\n\nMZX 2.0- KEY1-9, SPITFIRE, JUSTLOADED, JUSTENTERED, SENSORPUSHED, LAZER,\n\tGOOPTOUCHED, PLAYERHURT.\n\n** Auto counters- (* = Robot specific)\n\nGEMS, AMMO, LOBOMBS, HIBOMBS, COINS, LIVES, HEALTH, TIME, INVINCO, INPUT,\nINPUTSIZE, KEY, BULLETTYPE*, PLAYERDIST*, HORIZPLD*, VERTPLD*.\n\nMZX 2.0- SCORE, THISX*, THISY*, TIMERESET, LOOPCOUNT*, PLAYERLASTDIR,\n\tPLAYERFACEDIR.\n\nSpecial- XPOS, YPOS (for REL COUNTERS) These are still global.\n\n** Directions-\n\nIDLE (0)\nNORTH, N, UP (1)\nSOUTH, S, DOWN (2)\nEAST, E, RIGHT (3)\nWEST, W, LEFT (4)\nRANDNS (5)\nRANDEW (6)\nRANDNE (7)\nRANDNB (8)\nSEEK (9)\nRANDANY (10)\nBENEATH, UNDER (11)\nANYDIR (12)\nFLOW (13)\nNODIR (14)\nRANDB (15)\nRANDP (+16)\nCW (+32)\nOPP (+64)\nRANDNOT (+128)\n\n** Equalitys-\n\n= or ==        (equal to)\n!= or <> or >< (not equal to)\n>              (greater than)\n<              (less than)\n>= or =>       (greater than or equal to)\n<= or =<       (less than or equal to)\n\n** Color coding-\n\nW/o ?-\n\tLow nybble foreground (0-15)\n\tHigh nybble background (0-15)\n\nWith ?-\n\tCheck to see if bit for ? is set (In 1.0? robots, this is the high bit of\n\tthe following thing code. Otherwise, it is +256.) If not, color is same as\n\tabove. Otherwise- 0 through 15 stand for a specific foreground color with\n\tunknown background color. 16 through 31 stand for a specific background\n\tcolor (the number minus 16) with unknown foreground color. 32 stands for\n\tunknown background and unknown foreground both.\n\n** Sound effects numbers and codings- (version 1.0?) (See v2.00 editor for\n\tchanges to game over and death sfx)\n\nNum String                                               Is\n--- ---------------------------------------------------- ------------------\n0   t5c-gec-gec\t\t\t\t\t\t\t\t\t\t\t\t\t\tGem\n1   t5c-gec-gec\t\t\t\t\t\t\t\t\t\t\t\t\t\tMagic Gem\n2   tcge-zcge\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tHealth\n3\t t-c+c+c-g-g-g\t\t\t\t\t\t\t\t\t\t\t\t\t\tAmmo\n4   t6a#a#zx\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCoin\n5   t-cegeg+c-g+cecegeg+c\t\t\t\t\t\t\t\t\t\t\tLife\n6   t-cc#dd#e\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tLo Bomb\n7   tcc#dd#e\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tHi Bomb\n8   t4gec-g+ec-ge+c-gec\t\t\t\t\t\t\t\t\t\t\t\tKey\n9   t-gc#-a#a\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tFull Keys\n10  tceg+c-eg+ce-g+ceg\t\t\t\t\t\t\t\t\t\t\t\tUnlock\n11  s1a#z+a#-a#x\t\t\t\t\t\t\t\t\t\t\t\t\t\tCan't Unlock\n12  t-a-a+e-e+c-c\t\t\t\t\t\t\t\t\t\t\t\t\t\tInvis. Wall\n13  zax\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tForest\n14  t0a#+a#-a#b+b-b\t\t\t\t\t\t\t\t\t\t\t\t\tGate Locked\n15  t0a+a-a+a-a\t\t\t\t\t\t\t\t\t\t\t\t\t\tOpening Gate\n16  t-g+g-g+g+g-g+g-g-g+g-g-tg+g\t\t\t\t\t\t\t\t\tInvinco Start\n17  sc-cqxsg-g+a#-a#xx+g-g+a#-a#\t\t\t\t\t\t\t\t\tInvinco Beat\n18  sc-cqxsg-g+f-f+d#-d#+c-ca#-a#2c-c\t\t\t\t\t\t\tInvinco End\n19  t0g+g-gd#+d#-d#\t\t\t\t\t\t\t\t\t\t\t\t\tDoor locked\n20  t0g+gd#+d#\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tDoor opening\n21  tc-zgd#c-gd#c\t\t\t\t\t\t\t\t\t\t\t\t\t\tHurt\n22  ta-a-a+a-a-a\t\t\t\t\t\t\t\t\t\t\t\t\t\tAUGH!\n23  t+c-gd#cgd#c-g+d#c-g+d#c-gd#+c-gd#cgd#c-g+d#c-gd#+c  Death\n\t  -gd#cgd#c-g+d#c-gc-gd#g+c\n24  sc-cqxsa#-a#++c-c+q.xsd#-d#qxsa#-a#++c-cq.xs+c-cqxs  Game over\n\t  a#-a#++c-c+q.xsd#-d#qxs+f-f+c-cq.xs\n25  t0c+c-c+c-c\t\t\t\t\t\t\t\t\t\t\t\t\t\tGate closing\n26  t1d#x\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tPush\n27  t2c+c+c2d#+d#+d#2g+g+g3cd#g\t\t\t\t\t\t\t\t\tTransport\n28  z+c-c\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tShoot\n29  t1a+a+a\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tBreak\n30  t-af#-f#a\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tOut of ammo\n31  t+f#\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tRicochet\n32  s-d-d-d\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tOut of bombs\n33  tc-aec-a\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tPlace bomb (lo)\n34  t+c-aec-a\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tPlace bomb (hi)\n35  t+g-ege-ege\t\t\t\t\t\t\t\t\t\t\t\t\t\tSwitch bomb type\n36  t1c+c0d#+d#-g+g-c#\t\t\t\t\t\t\t\t\t\t\t\tExplosion\n37  t2cg+e+c2c#g#+f+c#2da+f#+d2d#a#+g+d#\t\t\t\t\t\tEntrance\n38  tcge+c-g+ecge+c-g+ec\t\t\t\t\t\t\t\t\t\t\tPouch\n39  zcd#gd#cd#gd#cd#gd#cd#gd#cfg#fcfg#fcfg#fcfg#fcga#gc  Ring/potion\n\t  ga#gcga#gcga#gs+c\n40  z-a-a-aa-aa\t\t\t\t\t\t\t\t\t\t\t\t\t\tEmpty chest\n41  t1c+c-c#+c#-d+d-d#+d#-e+e-ec\t\t\t\t\t\t\t\t\tChest\n42  tc-gd#c-zd#gd#c\t\t\t\t\t\t\t\t\t\t\t\t\tOut of time\n43  zc-d#g-cd#\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tFire ouch\n44  tcd#g+cd#g\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tStolen gem\n45  z1d#+d#+d#\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tEnemy HP down\n46  z0ca#f+d#cf#\t\t\t\t\t\t\t\t\t\t\t\t\t\tDragon fire\n47  tcg+c-eda+d-f#eb+e-g#\t\t\t\t\t\t\t\t\t\t\tScroll/sign\n48  See editor in v2.00                                  Goop touch (v2.0+)\n\n** Codes for variables, etc-\n\nOverlay off                   0\nOverlay on                    1\nOverlay static                2\nOverlay transparent           3\n\nExplosion leave space\t\t\t0\nExplosion leave ash\t\t\t\t1\nExplosion leave fire\t\t\t\t2\n\nCan save\t\t\t\t\t\t\t\t0\nCan't save\t\t\t\t\t\t\t1\nCan save only on a sensor\t\t2\n\nForest becomes empty\t\t\t\t0\nForest becomes floor\t\t\t\t1\n\nFire burns limited\t\t\t\t0\nFire burns forever\t\t\t\t1\n\nCode for no board\t\t\t\t\t255\nCode for no endgame board\t\t128 (255 in MZX v2.0+)\nCode for death to restart\t\t128 (255 in MZX v2.0+)\nCode for death to be same pos\t129 (254 in MZX v2.0+)\n\nPlayer bullet\t\t\t\t\t\t0\nNeutral bullet\t\t\t\t\t\t1\nEnemy bullet\t\t\t\t\t\t2\n\nDirections\t\t\t\t\t\t\tSEE ABOVE\nColors\t\t\t\t\t\t\t\tSEE ABOVE\n\nSimple direction, N\t\t\t\t0\nSimple direction, S\t\t\t\t1\nSimple direction, E\t\t\t\t2\nSimple direction, W\t\t\t\t3\n\nSimple direction -> Complex   Add 1\nComplex direction -> Simple\tSubtract 1\n\nHorizontal\t\t\t\t\t\t\t0\nVertical\t\t\t\t\t\t\t\t1\n\nNo key (key array)\t\t\t\t127\n\nNo time limit\t\t\t\t\t\t0\n\nNo protection\t\t\t\t\t\t0\nProtection from saving\t\t\t1\nProtection from editing\t\t\t2\nProtection from everything\t\t3\n\nSize of full pathname holders\t129\n\nOther variable codes are not important or are only internal and will not\ncarry over to MZX 2.0."
  },
  {
    "path": "docs/joystick.html",
    "content": "<!DOCTYPE html>\n<!--\n  MegaZeux Joystick Support Guide\n\n  Copyright (C) 2020-2025 Lachesis - https://github.com/AliceLR/megazeux/\n\n  Permission to use, copy, modify, and distribute this document for any\n  purpose with or without fee is hereby granted, provided that the above\n  copyright notice and this permission notice appear in all copies.\n\n  THE DOCUMENT IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n  WITH REGARD TO THIS DOCUMENT INCLUDING ALL IMPLIED WARRANTIES OF\n  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS DOCUMENT.\n-->\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <title>MegaZeux Joystick Support</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <meta name=\"title\" content=\"MegaZeux Joystick Support Guide\">\n  <meta name=\"description\" content=\"Guide for configuring gamepad support for games and standard console gamepad layouts.\">\n  <meta name=\"twitter:card\" content=\"summary\">\n  <meta name=\"twitter:title\" content=\"MegaZeux Joystick Support Guide\">\n  <meta name=\"twitter:description\" content=\"Guide for configuring gamepad support for games and standard console gamepad layouts.\">\n\n  <style>\n    body\n    {\n      /* Explicitly define colors for ELinks */\n      background-color: #fff;\n      color: #000;\n      margin: 6px;\n    }\n\n    #container\n    {\n      max-width: 780px;\n      margin: auto;\n      padding: 0px;\n    }\n\n    header, section\n    {\n      margin: 4px;\n      margin-bottom: 16px;\n    }\n\n    header, section.contents\n    {\n      padding: 8px 4px;\n    }\n\n    section.main\n    {\n      padding: 8px 4px;\n      padding-bottom: 72px;\n    }\n\n    section.inner\n    {\n      background-color: #F4F4F4;\n      border-radius: 5px;\n      padding: 2px 16px 8px;\n    }\n\n    code\n    {\n      background-color: #CCC;\n      border-radius: 3px;\n      padding: 1px 4px;\n      font-weight: bold;\n    }\n\n    code.example\n    {\n      display: block;\n      background-color: #555;\n      font-size: .95em;\n      color: #fff;\n      margin: 20px 8px;\n      padding: 0px 20px 16px;\n      overflow: auto;\n      white-space: pre-line;\n    }\n\n    div.image-wrapper\n    {\n      max-width: 640px;\n      margin: 0 auto;\n      overflow: auto;\n    }\n\n    div.scroll-wrapper\n    {\n      overflow: auto;\n    }\n\n    table\n    {\n      width: 99%;\n      margin: 8px 2px 24px;\n      border: 1px solid #aaa;\n    }\n\n    td\n    {\n      padding: 1px 6px;\n      border: 4px solid transparent;\n    }\n\n    thead td\n    {\n      border-bottom: 2px solid #aaa;\n      font-weight: bold;\n    }\n  </style>\n</head>\n\n<body>\n<div id=\"container\">\n  <header>\n    <h1>MegaZeux Joystick Support Guide</h1>\n    <h3>MegaZeux 2.93d &mdash; June 9th, 2025</h3>\n    <p>\n      This guide contains all current information for joystick mapping and\n      Robotic support for using joysticks.\n    </p>\n  </header>\n\n  <hr>\n\n  <section class=\"contents\">\n    <h2>Contents</h2>\n    <ol>\n      <li><a href=\"#info\">General Info</a></li>\n      <ol type=\"i\">\n        <li><a href=\"#info-layout\">A typical controller layout</a></li>\n        <li><a href=\"#info-actions\">List of available actions</a></li>\n      </ol>\n      <li><a href=\"#config\">Config Usage and Examples</a></li>\n      <ol type=\"i\">\n        <li><a href=\"#config-examples\">Examples for specific games</a></li>\n        <li><a href=\"#config-sdl\">Adding/modifying controller support</a></li>\n        <li><a href=\"#config-legacy\">Config options in versions prior to 2.92</a></li>\n      </ol>\n      <li><a href=\"#robotic\">Robotic Usage and Examples</a></li>\n      <li><a href=\"#layouts\">Controller Layouts for Console Ports</a></li>\n      <ol type=\"i\">\n        <li><a href=\"#layout-nds\">NDS and 3DS</a></li>\n        <li><a href=\"#layout-psp\">PSP</a></li>\n        <li><a href=\"#layout-switch\">Switch</a></li>\n        <li><a href=\"#layout-wii\">Wii</a></li>\n        <li><a href=\"#layout-gp2x\">GP2X</a></li>\n      </ol>\n      <li><a href=\"#license\">License</a></li>\n    </ol>\n  </section>\n\n  <hr>\n\n  <section id=\"info\" class=\"main\">\n    <h2>General Info</h2>\n    <p>\n      Joystick support in MegaZeux is primarily handled through abstracted\n      buttons and axes called <i>actions</i>. These actions correspond primarily\n      to XInput controls in both name and general physical location on a\n      controller. Actions can interact directly with the UI elements of MegaZeux\n      and may be used by MegaZeux games.\n    </p>\n\n    <p>\n      Actions can be used by games in two ways.\n\n      The simplest method is to bind a key to a given action via a game config\n      file (see <a href=\"#examples-config\">Config Usage and Examples</a>).\n      When the joystick action is pressed/released in game, MegaZeux will\n      simulate the press/release of the bound key. Each action has a default\n      game key bound to it initially (see <a href=\"#info-actions\">the list of\n      available actions</a> below).\n\n      Alternatively, the simulated key behavior can be disabled through Robotic\n      and the pressed/released status of an action may be read directly with\n      a counter (see <a href=\"#examples-robotic\">Robotic Usage and Examples</a>).\n    </p>\n\n    <section id=\"info-layout\" class=\"inner\">\n      <h3>A Typical Controller Layout</h3>\n      <p id=\"info-layout\">\n        This figure shows most available actions and a typical placement of them\n        on controllers for most platforms:\n      </p>\n\n      <div class=\"image-wrapper\">\n      <svg width=\"640\" height=\"400\" role=\"img\">\n        <title>Diagram of a typical XInput-like controller with buttons and axes labeled.</title>\n        <style>\n          .label { font: bold 16px monospace; }\n        </style>\n        <svg x=\"1%\" y=\"5%\" width=\"98%\" height=\"95%\" viewbox=\"0 0 200 125\">\n          <path fill=\"none\" stroke=\"darkgrey\" stroke-width=\"1.5\"\n            d=\"M 25 13  H50  Q 50 15 54 15  H146   Q 150 15 150 13  H175\n               C 235 125 175 160 150 80\n               C 142  89 128  89 120 80\n               H80\n               C 72  89  58  89 50 80\n               C 25 160 -35 125 25 13 Z\"/>\n\n          <svg x=\"6%\" y=\"22%\" width=\"24%\" height=\"24%\" viewbox=\"0 0 100 100\">\n            <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"4\"\n              d=\"M37  5 H63 V28 L50 42 L37 28 Z\n                  M 5 37 V63 H28 L42 50 L28 37 Z\n                  M37 95 H63 V72 L50 58 L37 72 Z\n                  M95 37 V63 H72 L58 50 L72 37 Z\" />\n          </svg>\n\n          <circle cx=\"100\" cy=\"67.5\" r=\"5\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"1\"/>\n\n          <circle cx=\"65\" cy=\"68\" r=\"8%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"1\"/>\n          <circle cx=\"135\" cy=\"68\" r=\"8%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"1\"/>\n          <circle cx=\"65\" cy=\"68\" r=\"6.5%\"\n            fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1.25\"/>\n          <circle cx=\"135\" cy=\"68\" r=\"6.5%\"\n            fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1.25\"/>\n\n          <circle cx=\"163\" cy=\"30\" r=\"5\"\n            fill=\"#ced\" stroke=\"#222\" stroke-width=\"1\"/>\n          <circle cx=\"150\" cy=\"43\" r=\"5\"\n            fill=\"#ece\" stroke=\"#222\" stroke-width=\"1\"/>\n          <circle cx=\"176\" cy=\"43\" r=\"5\"\n            fill=\"#ecc\" stroke=\"#222\" stroke-width=\"1\"/>\n          <circle cx=\"163\" cy=\"56\" r=\"5\"\n            fill=\"#cce\" stroke=\"#222\" stroke-width=\"1\"/>\n\n          <rect x=\"75\" y=\"35\" width=\"8\" height=\"5\" rx=\"1\"\n            fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1\"/>\n          <rect x=\"117\" y=\"35\" width=\"8\" height=\"5\" rx=\"1\"\n            fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1\"/>\n\n          <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1\"\n            d=\"M 26 12  H48  V10  Q 37 8 26 10  Z\"/>\n          <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1\"\n            d=\"M 152 12  H173  V10  Q 163 8 152 10  Z\"/>\n        </svg>\n\n        <text x=\"14%\" y=\"8%\" class=\"label\">lshoulder</text>\n        <text x=\"74%\" y=\"8%\" class=\"label\">rshoulder</text>\n        <text x=\"30.5%\" y=\"5%\" class=\"label\">ltrigger</text>\n        <text x=\"59.5%\" y=\"5%\" class=\"label\">rtrigger</text>\n        <text x=\"30.5%\" y=\"10%\" class=\"label\">axis_ltrigger</text>\n        <text x=\"52.5%\" y=\"10%\" class=\"label\">axis_rtrigger</text>\n        <line x1=\"29.5%\" x2=\"28.5%\" y1=\"11.5%\" y2=\"14%\"\n          stroke=\"black\" stroke-width=\"2.5\"/>\n        <line x1=\"70.5%\" x2=\"71.5%\" y1=\"11.5%\" y2=\"14%\"\n          stroke=\"black\" stroke-width=\"2.5\"/>\n\n        <text x=\"18.25%\" y=\"24%\" class=\"label\">up</text>\n        <text x=\"5.75%\" y=\"38.5%\" class=\"label\">left</text>\n        <text x=\"27.5%\" y=\"38.5%\" class=\"label\">right</text>\n        <text x=\"16.75%\" y=\"53%\" class=\"label\">down</text>\n\n        <text x=\"36%\" y=\"29%\" class=\"label\">select</text>\n        <text x=\"56.5%\" y=\"29%\" class=\"label\">start</text>\n\n        <text x=\"79%\" y=\"22%\" class=\"label\">y</text>\n        <text x=\"73%\" y=\"32%\" class=\"label\">x</text>\n        <text x=\"85.5%\" y=\"32%\" class=\"label\">b</text>\n        <text x=\"79%\" y=\"42%\" class=\"label\">a</text>\n\n        <text x=\"34.5%\" y=\"80%\" class=\"label\">axis_lx</text>\n        <text x=\"34.5%\" y=\"85%\" class=\"label\">axis_ly</text>\n        <text x=\"35%\" y=\"90%\" class=\"label\">lstick</text>\n\n        <text x=\"55.5%\" y=\"80%\" class=\"label\">axis_rx</text>\n        <text x=\"55.5%\" y=\"85%\" class=\"label\">axis_ry</text>\n        <text x=\"56%\" y=\"90%\" class=\"label\">rstick</text>\n\n        <line x1=\"37%\" x2=\"35%\" y1=\"76%\" y2=\"68%\"\n          stroke=\"black\" stroke-width=\"2.5\"/>\n        <line x1=\"62%\" x2=\"64%\" y1=\"76%\" y2=\"68%\"\n          stroke=\"black\" stroke-width=\"2.5\"/>\n      </svg>\n      </div>\n\n      <p>\n        NOTE: Not all controllers have the same layout or support all of the\n        same controls; the default layout for a given controller depends on the\n        mapping provided by SDL or gamecontrollerdb.txt, if any. See\n        <a href=\"#config-sdl\">Adding/modifying controller support</a> for more\n        info on getting new controllers to work in SDL and tweaking existing\n        controllers.\n      </p>\n    </section>\n\n    <section id=\"info-actions\" class=\"inner\">\n      <h3>List of Available Actions</h3>\n      <p>\n        The available joystick actions, their UI behavior, and their default\n        gameplay keybindings are:\n      </p>\n      <div class=\"scroll-wrapper\">\n      <table>\n        <thead>\n          <td>Name</td>\n          <td>Title behavior</td>\n          <td>Window behavior</td>\n          <td>Default gameplay key</td>\n        </thead>\n\n        <tr>\n          <td><code>up</code></td>\n          <td></td>\n          <td>Element/&#x200b;cursor up</td>\n          <td><code>key_up</code> (movement)</td>\n        </tr>\n\n        <tr>\n          <td><code>down</code></td>\n          <td></td>\n          <td>Element/&#x200b;cursor down</td>\n          <td><code>key_down</code> (movement)</td>\n        </tr>\n\n        <tr>\n          <td><code>left</code></td>\n          <td></td>\n          <td>Element/&#x200b;cursor left</td>\n          <td><code>key_left</code> (movement)</td>\n        </tr>\n\n        <tr>\n          <td><code>right</code></td>\n          <td></td>\n          <td>Element/&#x200b;cursor right</td>\n          <td><code>key_right</code> (movement)</td>\n        </tr>\n\n        <tr>\n          <td><code>a</code></td>\n          <td>Play game</td>\n          <td>Select</td>\n          <td><code>key_space</code> (shoot)</td>\n        </tr>\n\n        <tr>\n          <td><code>b</code></td>\n          <td>Main menu</td>\n          <td>Cancel</td>\n          <td><code>key_delete</code> (bomb)</td>\n        </tr>\n\n        <tr>\n          <td><code>x</code></td>\n          <td>Load world</td>\n          <td>Select char (text)</td>\n          <td><code>key_return</code> (game menu)</td>\n        </tr>\n\n        <tr>\n          <td><code>y</code></td>\n          <td>Reload save</td>\n          <td>Backspace (text)</td>\n          <td><code>key_s</code> (Caverns: spells)</td>\n        </tr>\n\n        <tr>\n          <td><code>start</code></td>\n          <td>Play game</td>\n          <td>Select</td>\n          <td>Game menu<sup>1</sup></td>\n        </tr>\n\n        <tr>\n          <td><code>select</code></td>\n          <td>Main menu</td>\n          <td>Cancel</td>\n          <td>Game menu<sup>1</sup></td>\n        </tr>\n\n        <tr>\n          <td><code>lshoulder</code></td>\n          <td>Settings</td>\n          <td>Next element</td>\n          <td><code>key_insert</code> (bomb type)</td>\n        </tr>\n\n        <tr>\n          <td><code>rshoulder</code><sup>2</sup></td>\n          <td>Settings</td>\n          <td>Settings<sup>3</sup></td>\n          <td><code>key_p</code> (Caverns: altar)</td>\n        </tr>\n\n        <tr>\n          <td><code>ltrigger</code><sup>2</sup></td>\n          <td>Load world</td>\n          <td>Page up</td>\n          <td><code>key_f3</code> (save game)</td>\n        </tr>\n\n        <tr>\n          <td><code>rtrigger</code><sup>2</sup></td>\n          <td>Reload save</td>\n          <td>Page down</td>\n          <td><code>key_f4</code> (load save)</td>\n        </tr>\n\n        <tr>\n          <td><code>lstick</code><sup>2</sup></td>\n          <td></td>\n          <td>Home</td>\n          <td></td>\n        </tr>\n\n        <tr>\n          <td><code>rstick</code><sup>2</sup></td>\n          <td></td>\n          <td>End</td>\n          <td></td>\n        </tr>\n      </table>\n      </div>\n\n      <p>\n        The following actions are available only through Robotic or are mostly\n        similar to other actions listed above:\n      </p>\n\n      <table>\n        <thead>\n          <td style=\"min-width:40%\">Name</td>\n          <td>Description</td>\n        </thead>\n\n        <tr>\n          <td>\n            <code>l_up</code>, <code>l_down</code>, <code>l_left</code>, and\n            <code>l_right</code><sup>2</sup>\n          </td>\n          <td>\n            Directional actions for the left analog stick. Same behavior as\n            the dpad actions by default.\n          </td>\n        </tr>\n        <tr>\n          <td>\n            <code>r_up</code>, <code>r_down</code>, <code>r_left</code>, and\n            <code>r_right</code><sup>2</sup>\n          </td>\n          <td>\n            Directional actions for the right analog stick. Same behavior as\n            the dpad actions by default.\n          </td>\n        </tr>\n        <tr>\n          <td><code>axis_lx</code> and <code>axis_ly</code><sup>2</sup></td>\n          <td>X and Y axes of the left stick (counter only).</td>\n        </tr>\n        <tr>\n          <td><code>axis_rx</code> and <code>axis_ry</code><sup>2</sup></td>\n          <td>X and Y axes of the right stick (counter only).</td>\n        </tr>\n        <tr>\n          <td><code>axis_ltrigger</code> and <code>axis_rtrigger</code><sup>2</sup></td>\n          <td>Axes for the left and right triggers (counter only).</td>\n        </tr>\n      </table>\n\n      <p>\n        <sup>1</sup>Gives access to most UI menus such as saving, loading,\n        exiting the game, and settings. This behavior overrides any keys\n        assigned to this action unless both <code>ENTER_MENU</code> and\n        <code>ESCAPE_MENU</code> are disabled. This occupies both\n        <code>start</code> and <code>select</code> right now because some\n        controllers may lack one of these buttons.\n      </p>\n\n      <p>\n        <sup>2</sup>The <code>rshoulder</code> action is overriden by some\n        devices to toggle the state of the onscreen keyboard. To support\n        these devices, it's generally helpful to double controls mapped to\n        this button onto a second button or onto the keyboard. The\n        availability of some other actions may vary between consoles and\n        controllers (for example, some controllers may have a left analog\n        stick but no dpad, and many controllers lack the stick press\n        <code>lstick</code> and <code>rstick</code> actions).\n      </p>\n\n      <p>\n        <sup>3</sup>This behavior currently only works on some windows, such\n        as the main menu and game menu.\n      </p>\n    </section>\n  </section>\n\n  <hr>\n\n  <section id=\"config\" class=\"main\">\n    <h2>Config Usage and Examples</h2>\n    <p>\n      Creating a game config file to give a game compatibility with joystick\n      actions is very easy and, best of all, requires no modification to older\n      games. A game config file is a text file that shares the same filename as\n      a world but with the \".cnf\" extension instead of \".mzx\" (for example, the\n      the config file for <code>CAVERNS.MZX</code> would be\n      <code>CAVERNS.CNF</code>). Joystick config file options can be used in\n      this file just as they would be in the regular config file, but they'll\n      only affect their given world.\n    </p>\n\n    <p>\n      The main config option worth noting for the purpose of configuring a\n      game.cnf file is\n    </p>\n\n    <code class=\"example\">\n      joy#.ACTION = KEY\n    </code>\n\n    <p>\n      where # is the joystick number (1-16), <code>ACTION</code> is the name\n      of a joystick action, and <code>KEY</code> is either a key number or a\n      string in the format of <code>key_NAME</code>\n      (see <a href=\"keycodes.html\">keycodes.html</a> for <i>key_pressed</i>\n      numbers and/or key names to use here). For example:\n    </p>\n\n    <code class=\"example\">\n      joy1.lshoulder = key_space\n    </code>\n\n    <p>\n      would assign the space key to the <code>lshoulder</code> action of\n      joystick 1. Since up to 16 joysticks can be used with MegaZeux, it may be\n      useful to assign the same action keybinding to multiple joysticks. This\n      can be done by substituting the joystick number with a range. For example:\n    </p>\n\n    <code class=\"example\">\n      joy[1,16].lshoulder = key_space\n    </code>\n\n    <p>\n      will assign the <code>lshoulder</code> action on every controller to\n      the space key.\n    </p>\n\n    <section id=\"config-examples\" class=\"inner\">\n      <h3>Examples for Specific Games</h3>\n\n      <section>\n        <h4>Forest of Ruin</h4>\n        <p>\n          The default config mostly works for this game, but it would be\n          nice to be able to switch weapons (and <code>start</code> is good\n          enough for the menu in this game, which frees up <code>x</code>):\n        </p>\n        <code class=\"example\">\n          joy[1,16].x = key_w\n        </code>\n      </section>\n\n      <section>\n        <h4>red</h4>\n        <p>\n          red also mostly works with the default config thanks to <code>x</code>\n          being bound to <code>key_return</code>. For convenience, we'll go\n          ahead and bind <code>y</code> to <code>key_return</code> too:\n        </p>\n        <code class=\"example\">\n          joy[1,16].y = key_return\n        </code>\n      </section>\n\n      <section>\n        <h4>Demon Earth</h4>\n        <p>\n          Demon Earth uses different keys for attacking and uses space to hold\n          the player's current facing direction. Because of this, it's useful\n          to put the attack keys on the face buttons and space on the shoulders.\n\n          Note: because Demon Earth uses swap worlds, you may need to create\n          a game.cnf file for each world.\n        </p>\n        <code class=\"example\">\n          joy[1,16].a = key_a\n          joy[1,16].b = key_s\n          joy[1,16].x = key_return\n          joy[1,16].y = key_return\n          joy[1,16].lshoulder = key_space\n          joy[1,16].rshoulder = key_space\n        </code>\n      </section>\n\n      <section>\n        <h4>Achtung!!</h4>\n        <p>\n          Comments can be added to explain the in-game function of each key:\n        </p>\n        <code class=\"example\">\n          # Shoot down; change to B for some consoles\n          # Shoot right; change to A for some consoles\n          # Shoot left; change to Y for some consoles\n          # Shoot up; change to X for some consoles\n          joy[1,16].a = key_s\n          joy[1,16].b = key_d\n          joy[1,16].x = key_a\n          joy[1,16].y = key_w\n\n          # Shoot up\n          # Shoot down\n          # Shoot left\n          # Shoot right\n          joy[1,16].r_up = key_w\n          joy[1,16].r_down = key_s\n          joy[1,16].r_left = key_a\n          joy[1,16].r_right = key_d\n\n          # Select menu, bomb\n          joy[1,16].lshoulder = key_space\n          joy[1,16].rshoulder = key_space\n          joy[1,16].ltrigger = key_space\n          joy[1,16].rtrigger = key_space\n        </code>\n      </section>\n    </section>\n\n    <section id=\"config-sdl\" class=\"inner\">\n      <h3>Adding/Modifying Controller Support</h3>\n      <p>\n        Most platforms supported by MegaZeux (excluding consoles) rely on SDL 2.\n        Controller support in MegaZeux for these platforms is based on SDL's\n        game controller API, and any controller supported by the game controller\n        API is automatically detected by MegaZeux. However, many controllers are\n        still missing from SDL and/or gamecontrollerdb.txt.\n      </p>\n\n      <p>\n        It should be possible to map most missing USB or Bluetooth controllers,\n        and several utilities for this purpose are available at the\n        <a href=\"https://github.com/gabomdq/SDL_GameControllerDB\">SDL_GameControllerDB\n        GitHub repository</a>. When used, these utilities will produce a mapping\n        string that can be added to MZX using the following config setting:\n      </p>\n\n      <code class=\"example\" style=\"word-wrap: break-word\">\n        gamepad_add = 030000005e0400008e02000000007801,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,\n      </code>\n\n      <p>\n        (note: it must be a single line).\n        This will override any existing config for your controller (in either\n        SDL or gamecontrollerdb.txt) with your new mapping. It might also be a\n        good idea to thoroughly test your new mapping and send it to an MZX\n        developer or submit it to SDL or SDL_GameControllerDB yourself if it's\n        a controller that is missing or if the mapping in the database is\n        unusable/broken.\n      </p>\n\n      <p>\n        The way MZX uses the SDL game controller API to generate controller\n        mappings can be further configured globally with the following settings:\n      </p>\n\n      <code class=\"example\">\n        gamepad.SDLBUTTON = act_NAME\n        gamepad.+SDLAXIS = act_NAME\n        gamepad.-SDLAXIS = act_NAME\n      </code>\n\n      <p>\n        The first will assign an SDL game controller button to the MZX action\n        <code>NAME</code>. The second will assign the positive direction of an\n        SDL axis to <code>NAME</code>; the third does the same, for the negative\n        direction. A complete list of SDL buttons that can be customized is in\n        the config file.\n      </p>\n\n      <p>\n        Finally, if you'd like to go back to the old and bad joystick support\n        for some reason, you can turn off automatic mappings altogether via the\n        config file:\n      </p>\n\n      <code class=\"example\">\n        gamepad_enable = 0\n      </code>\n    </section>\n\n    <section id=\"config-legacy\" class=\"inner\">\n      <h3>Config Options in Versions Prior to 2.92</h3>\n      <p>\n        MZX versions prior to 2.92 do not have access to any joystick counters,\n        nor to actions in general. Only the following config options are\n        available. The use of these options in versions 2.92 and up is not\n        recommended.\n      </p>\n      <p>\n        Versions prior to 2.92 are also not capable of using joystick index\n        ranges or binding key names and must use key_pressed values directly.\n        They also do not distinguish global config from game config, meaning\n        a game.cnf file that uses these settings will affect all UIs in MZX\n        permanently until another game.cnf file is loaded.\n      </p>\n      <p>\n        Most port pad.config files still use these config settings as\n        the physical location of each button/axis/hat is already known and will\n        not change.\n      </p>\n\n      <code class=\"example\">\n        joyXbuttonY = A\n      </code>\n      <p>\n        This will assign button Y (1-256) on joystick X (1-16) to key A.\n      </p>\n      <p>\n        In versions 2.92 and up, A can also be a key name in the format of\n        <code>key_NAME</code> or an action name in the format of\n        <code>act_NAME</code>. Additionally, X can be a range, e.g.\n        <code>joy[Z,W]buttonY</code>.\n      </p>\n\n      <code class=\"example\">\n        joyXaxisY = A, B\n      </code>\n      <p>\n        This will assign axis Y (1-16) on joystick X (1-16) to keys A and B.\n        The negative direction of the axis will be assigned to A and the\n        positive direction will be assigned to B.\n      </p>\n      <p>\n        In versions 2.92 and up, A and B can also be key names in the format of\n        <code>key_NAME</code> or action names in the format of\n        <code>act_NAME</code>. Additionally, X can be a range, e.g.\n        <code>joy[Z,W]axisY</code>.\n      </p>\n\n      <code class=\"example\">\n        joyXhat = A, B, C, D\n      </code>\n      <p>\n        This will assign the hat of joystick X (1-16) to keys A, B, C, and D.\n        Up will be assigned to A, down will be assigned to B, left will be\n        assigned to C, and right will be assigned to D.\n      </p>\n      <p>\n        In versions 2.92 and up, A/B/C/D can also be key names in the format of\n        <code>key_NAME</code> or action names in the format of\n        <code>act_NAME</code>. Additionally, X can be a range, e.g.\n        <code>joy[Z,W]hat</code>.\n      </p>\n    </section>\n  </section>\n\n  <hr>\n\n  <section id=\"robotic\" class=\"main\">\n    <h2>Robotic Usage and Examples</h2>\n    <p>\n      Joystick presses can be read directly from Robotic via counters. First,\n      however, it might be useful to turn off the simulated key press behavior\n      for joysticks that is enabled by default:\n    </p>\n\n    <code class=\"example\">\n      set \"joy_simulate_keys\" to 0\n    </code>\n\n    <p>\n      The <code>joy#active</code> counter returns <b>1</b> if a joystick is\n      active, <b>0</b> if it is not active, and <b>-1</b> if the provided\n      joystick index is invalid. You can use it if you need to check to see how\n      many joysticks are active or if any are active:\n    </p>\n\n    <code class=\"example\">\n      set \"num_active\" 0\n      loop start\n      if \"joy&loopcount&active\" = 1 then \"#is_active\"\n      loop for 15\n      * \"&num_active& controllers are active.\"\n      end\n\n      : \"#is_active\"\n      inc \"num_active\" by 1\n      goto \"#return\"\n    </code>\n\n    <p>\n      The <code>joy#.NAME</code> counter is probably more useful, however.\n      For action <code>NAME</code>, this counter will return the value of that\n      action for a given joystick number, or <b>-1</b> if either the joystick\n      number or action name are invalid. For button actions, the value is\n      <b>1</b> if pressed and <b>0</b> if not pressed. For axis actions, the\n      value can be anywhere between <b>-32768</b> and <b>32767</b>, though\n      trigger axes are more likely to be between <b>0</b> and <b>32767</b>.\n    </p>\n\n    <p>\n      The following example moves a player sprite around with an analog stick:\n    </p>\n    <code class=\"example\">\n      lockplayer\n      set \"spr0_refx\" to \"playerx\"\n      set \"spr0_refy\" to \"playery\"\n      set \"spr0_width\" to 1\n      set \"spr0_height\" to 1\n      set \"spr0_unbound\" to 1\n      put c?? Sprite p00 at 316 168\n\n      : \"l\"\n      inc \"spr0_x\" by \"('joy1.axis_lx'/2500)\"\n      inc \"spr0_y\" by \"('joy1.axis_ly'/2500)\"\n      wait for 1\n      goto \"l\"\n    </code>\n\n    <p>\n      This longer example implements a simple player robot that can move around\n      or shoot by holding the <code>a</code> button and pressing a direction:\n    </p>\n\n    <code class=\"example\">\n      lockplayer\n      : \"l\"\n      if \"joy1.a\" = 1 then \"shoot\"\n      if \"joy1.up\" = 1 then \"go_up\"\n      if \"joy1.down\" = 1 then \"go_down\"\n      if \"joy1.right\" = 1 then \"go_right\"\n      if \"joy1.left\" = 1 then \"go_left\"\n      wait for 1\n      goto \"l\"\n\n      : \"go_up\"\n      go NORTH for 1\n      goto \"l\"\n      : \"go_down\"\n      go SOUTH for 1\n      goto \"l\"\n      : \"go_right\"\n      go EAST for 1\n      goto \"l\"\n      : \"go_left\"\n      go WEST for 1\n      goto \"l\"\n\n      : \"shoot\"\n      if \"joy1.up\" = 1 \"shoot_up\"\n      if \"joy1.down\" = 1 \"shoot_down\"\n      if \"joy1.right\" = 1 \"shoot_right\"\n      if \"joy1.left\" = 1 \"shoot_left\"\n      wait for 1\n      goto \"l\"\n      : \"shoot_up\"\n      sfx 28\n      shoot NORTH\n      wait for 2\n      goto \"l\"\n      : \"shoot_down\"\n      sfx 28\n      shoot SOUTH\n      wait for 2\n      goto \"l\"\n      : \"shoot_right\"\n      sfx 28\n      shoot EAST\n      wait for 2\n      goto \"l\"\n      : \"shoot_left\"\n      sfx 28\n      shoot WEST\n      wait for 2\n      goto \"l\"\n    </code>\n\n    <p>\n      The above example can be modified to check the axes, too:\n    </p>\n\n    <code class=\"example\">\n      ...\n\n      if \"joy1.up\" = 1 then \"go_up\"\n      if \"joy1.l_up\" = 1 then \"go_up\"\n      if \"joy1.r_up\" = 1 then \"go_up\"\n\n      if \"joy1.down\" = 1 then \"go_down\"\n      if \"joy1.l_down\" = 1 then \"go_down\"\n      if \"joy1.r_down\" = 1 then \"go_down\"\n\n      if \"joy1.right\" = 1 then \"go_right\"\n      if \"joy1.l_right\" = 1 then \"go_right\"\n      if \"joy1.r_right\" = 1 then \"go_right\"\n\n      if \"joy1.left\" = 1 then \"go_left\"\n      if \"joy1.l_left\" = 1 then \"go_left\"\n      if \"joy1.r_left\" = 1 then \"go_left\"\n\n      ...\n    </code>\n\n    <p>\n      Ideally, a game should work with both keyboard and rather than joystick\n      inputs alone. However, if you do make a game that is playable only with a\n      joystick, it should probably indicate clearly that a joystick is required.\n    </p>\n  </section>\n\n  <hr>\n\n  <section id=\"layouts\" class=\"main\">\n    <h2>Controller Layouts for Console Ports</h2>\n    <p>\n      Diagrams of the default layouts for the console platforms supported by\n      MegaZeux are provided here. These layouts can be further configured for\n      each platform by modifying the \"pad.config\" file that comes with them.\n    </p>\n\n    <section id=\"layout-nds\" class=\"inner\">\n      <h3>NDS and 3DS</h3>\n      <p>\n        The NDS and 3DS ports share the same layout; the only difference is that\n        the 3DS port has extra controls, particularly the New 3DS (note the\n        New 3DS circle stick is reserved for future use and can't be mapped).\n      </p>\n      <p>\n        Neither console maps <code>axis_rx</code>, <code>axis_ry</code>,\n        <code>axis_ltrigger</code>, <code>axis_rtrigger</code>,\n        <code>lstick</code>, or <code>rstick</code>. The right shoulder button\n        is hardcoded to open the onscreen keyboard, so <code>rshoulder</code>\n        is unmapped as well.\n      </p>\n      <p>\n        Furthermore, the NDS doesn't have the circle pad (<code>axis_lx</code>,\n        <code>axis_ly</code>) and both the NDS and original 3DS are missing\n        ZL (<code>ltrigger</code>) and ZR (<code>rtrigger</code>).\n      </p>\n\n      <div class=\"image-wrapper\">\n      <svg width=\"640\" height=\"560\" role=\"img\">\n        <title>Diagram resembling an NDS or 3DS, with buttons and axes labeled.</title>\n        <svg x=\"5%\" y=\"1%\" width=\"90%\" height=\"98%\">\n          <style>\n            .label { font: bold 16px monospace; }\n          </style>\n\n          <!-- Outline -->\n          <rect x=\"1%\" y=\"1%\" width=\"98%\" height=\"98%\" rx=\"5%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"5\" />\n          <line x1=\"1%\" y1=\"50%\" x2=\"99%\" y2=\"50%\"\n            stroke=\"darkgrey\" stroke-width=\"3\" />\n          <rect x=\"25%\" y=\"5%\" width=\"50%\" height=\"40%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\" />\n          <rect x=\"25%\" y=\"55%\" width=\"50%\" height=\"40%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\" />\n          <circle cx=\"80%\" cy=\"58.5%\" r=\"2%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"2.5\" />\n\n          <!-- circle pad -->\n          <svg x=\"5%\" y=\"58%\" width=\"15%\" height=\"14%\" viewbox=\"0 0 100 100\">\n            <circle cx=\"50\" cy=\"50\" r=\"44\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"5\" />\n            <circle cx=\"50\" cy=\"50\" r=\"30\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"5\" />\n          </svg>\n\n          <line x1=\"20%\" y1=\"65%\" x2=\"30%\" y2=\"65%\"\n            stroke=\"black\" stroke-width=\"2\" />\n          <text x=\"32%\" y=\"64%\" class=\"label\">axis_lx</text>\n          <text x=\"32%\" y=\"68%\" class=\"label\">axis_ly</text>\n\n          <!-- dpad -->\n          <svg x=\"5%\" y=\"75%\" width=\"15%\" height=\"13%\" viewbox=\"0 0 100 100\">\n            <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"5\"\n              d=\"M33 5 H66 V33 H95 V66 H66 V95 H33 V66 H5 V33 H33 Z\" />\n          </svg>\n\n          <line x1=\"20%\" y1=\"81%\" x2=\"28%\" y2=\"81%\"\n            stroke=\"black\" stroke-width=\"2\" />\n          <text x=\"30%\" y=\"80%\" class=\"label\">up, down,</text>\n          <text x=\"30%\" y=\"84%\" class=\"label\">left, right</text>\n\n          <!-- abxy -->\n          <svg x=\"77%\" y=\"60%\" width=\"20%\" height=\"20%\" viewbox=\"0 0 100 100\">\n            <circle cx=\"50%\" cy=\"25%\" r=\"12%\"\n             fill=\"blue\" fill-opacity=\".4\" stroke=\"#222\" stroke-width=\"3\" />\n            <circle cx=\"50%\" cy=\"75%\" r=\"12%\"\n             fill=\"yellow\" fill-opacity=\".4\" stroke=\"#222\" stroke-width=\"3\" />\n            <circle cx=\"25%\" cy=\"50%\" r=\"12%\"\n             fill=\"green\" fill-opacity=\".4\" stroke=\"#222\" stroke-width=\"3\" />\n            <circle cx=\"75%\" cy=\"50%\" r=\"12%\"\n             fill=\"red\" fill-opacity=\".4\" stroke=\"#222\" stroke-width=\"3\" />\n          </svg>\n\n          <text x=\"95%\" y=\"72%\" class=\"label\">a</text>\n          <text x=\"90%\" y=\"78%\" class=\"label\">b</text>\n          <text x=\"82%\" y=\"63%\" class=\"label\">x</text>\n          <text x=\"77%\" y=\"69%\" class=\"label\">y</text>\n\n          <!-- start select -->\n          <circle cx=\"82%\" cy=\"85%\" r=\"1.60%\"\n            fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"2\" />\n          <circle cx=\"82%\" cy=\"90%\" r=\"1.60%\"\n            fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"2\" />\n\n          <text x=\"85%\" y=\"85%\" class=\"label\">start</text>\n          <text x=\"85%\" y=\"90%\" class=\"label\">select</text>\n\n          <!-- shoulders -->\n          <rect x=\"1%\" y=\"49%\" width=\"11%\" height=\"5.5%\" rx=\".75%\"\n            fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\" />\n          <rect x=\"88%\" y=\"49%\" width=\"11%\" height=\"5.5%\" rx=\".75%\"\n            fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\" />\n          <rect x=\"14%\" y=\"49.25%\" width=\"7%\" height=\"4.5%\" rx=\".75%\"\n            fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\" />\n          <rect x=\"79%\" y=\"49.25%\" width=\"7%\" height=\"4.5%\" rx=\".75%\"\n            fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\" />\n\n          <line x1=\"5%\" y1=\"40%\" x2=\"5%\" y2=\"47%\"\n            stroke=\"black\" stroke-width=\"2\" />\n          <line x1=\"95%\" y1=\"40%\" x2=\"95%\" y2=\"47%\"\n            stroke=\"black\" stroke-width=\"2\" />\n          <text x=\"3%\" y=\"38%\" class=\"label\">lshoulder</text>\n          <text x=\"81%\" y=\"34%\" class=\"label\">rshoulder</text>\n          <text x=\"80%\" y=\"38%\" class=\"label\">(keyboard)</text>\n          <text x=\"8.5%\" y=\"46%\" class=\"label\">ltrigger</text>\n          <text x=\"75.5%\" y=\"46%\" class=\"label\">rtrigger</text>\n        </svg>\n      </svg>\n      </dev>\n      <!-- -->\n    </section>\n\n\n    <section id=\"layout-psp\" class=\"inner\">\n      <h3>PSP</h3>\n      <p>The PSP port does not map\n        <code>axis_rx</code>, <code>axis_ry</code>,\n        <code>axis_ltrigger</code>, <code>axis_rtrigger</code>,\n        <code>ltrigger</code>, <code>rtrigger</code>,\n        <code>lstick</code>, or <code>rstick</code>. No button is reserved for\n        a keyboard as the PSP port supports an infrared keyboard.\n      </p>\n\n      <div class=\"image-wrapper\">\n      <svg width=\"640\" height=\"280\" role=\"img\">\n        <title>Diagram resembling a PSP, with buttons and axes labeled.</title>\n        <style>\n          .label { font: bold 16px monospace; }\n        </style>\n\n        <!-- Outline -->\n        <svg x=\"2%\" y=\"10%\" width=\"95%\" height=\"90%\" viewbox=\"0 0 1000 400\">\n          <path fill=\"none\" stroke=\"darkgrey\" stroke-width=\"8\"\n            d=\"\n            M 200 10\n            H 800    Q 820  50 850  50\n            H 900    Q 975 200 900 350\n            H 850    Q 820 350 800 390\n            H 200    Q 180 350 150 350\n            H 100    Q  25 200 100  50\n            H 150    Q 180  50 200  10\n            Z\" />\n          <rect x=\"26%\" y=\"12%\" width=\"48%\" height=\"76%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"5\" />\n\n          <!-- Shoulder paths here because they follow the outline. -->\n          <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"6\"\n            d=\"M110 45   H 145   Q 170 45 190 15   H140  Q120 15 110 45 Z\" />\n          <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"6\"\n            d=\"M890 45   H 855   Q 830 45 810 15   H860  Q880 15 890 45 Z\" />\n        </svg>\n\n        <!-- shoulders -->\n        <text x=\"10%\" y=\"10%\" class=\"label\">lshoulder</text>\n        <text x=\"76%\" y=\"10%\" class=\"label\">rshoulder</text>\n\n        <!-- analog stick -->\n        <svg x=\"6%\" y=\"65%\" width=\"22%\" height=\"22%\" viewbox=\"0 0 100 100\">\n          <circle cx=\"50\" cy=\"50\" r=\"30\"\n          fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"6\" />\n        </svg>\n\n        <line x1=\"22%\" y1=\"75%\" x2=\"28%\" y2=\"75%\"\n          stroke=\"black\" stroke-width=\"2\" />\n        <text x=\"30%\" y=\"73%\" class=\"label\">axis_lx</text>\n        <text x=\"30%\" y=\"80%\" class=\"label\">axis_ly</text>\n\n        <!-- dpad -->\n        <svg x=\"4.5%\" y=\"35%\" width=\"25%\" height=\"25%\" viewbox=\"0 0 100 100\">\n          <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"5\"\n            d=\"M33  5 H66 V25 L50 42 L33 25 Z\n                M 5 33 V66 H25 L42 50 L25 33 Z\n                M33 95 H66 V75 L50 58 L33 75 Z\n                M95 33 V66 H75 L58 50 L75 33 Z\" />\n        </svg>\n\n        <line x1=\"23%\" y1=\"47%\" x2=\"30%\" y2=\"47%\"\n          stroke=\"black\" stroke-width=\"2\" />\n        <text x=\"32%\" y=\"45%\" class=\"label\">up, down,</text>\n        <text x=\"32%\" y=\"52%\" class=\"label\">left, right</text>\n\n        <!-- abxy -->\n        <svg x=\"67%\" y=\"31%\" width=\"30%\" height=\"36%\" viewbox=\"0 0 100 100\">\n          <circle cx=\"50%\" cy=\"50%\" r=\"25%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"2\" />\n          <circle cx=\"50%\" cy=\"25%\" r=\"12%\"\n            fill=\"#ced\" stroke=\"#222\" stroke-width=\"3\" />\n          <circle cx=\"50%\" cy=\"75%\" r=\"12%\"\n            fill=\"#cce\" stroke=\"#222\" stroke-width=\"3\" />\n          <circle cx=\"25%\" cy=\"50%\" r=\"12%\"\n            fill=\"#ece\" stroke=\"#222\" stroke-width=\"3\" />\n          <circle cx=\"75%\" cy=\"50%\" r=\"12%\"\n            fill=\"#ecc\" stroke=\"#222\" stroke-width=\"3\" />\n        </svg>\n\n        <text x=\"88.5%\" y=\"53%\" class=\"label\">a</text>\n        <text x=\"84.5%\" y=\"62%\" class=\"label\">b</text>\n        <text x=\"78%\" y=\"37%\" class=\"label\">x</text>\n        <text x=\"74%\" y=\"46%\" class=\"label\">y</text>\n\n        <!-- start select -->\n        <svg x=\"61.5%\" y=\"88.5%\" width=\"15%\" height=\"7%\" viewbox=\"0 0 100 25\">\n          <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\"\n            d=\"M 5 5   H 45   V 10   Q 25 30  5 10   Z\" />\n          <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\"\n            d=\"M 55 5  H 95   V 10   Q 75 30 55 10   Z\" />\n        </svg>\n\n        <text x=\"60%\" y=\"86%\" class=\"label\">select</text>\n        <text x=\"70%\" y=\"86%\" class=\"label\">start</text>\n      </svg>\n    </section>\n\n\n    <section id=\"layout-switch\" class=\"inner\">\n      <h3>Switch</h3>\n      <p>\n        The Nintendo Switch port uses SDL 2 and thus has mappings built into\n        SDL. However, SDL default mappings for <code>a</code>, <code>b</code>,\n        <code>x</code>, and <code>y</code> correspond to the XInput positions\n        for these buttons, which Nintendo console users generally don't want.\n        Because of this, the Switch port also ships with a default pad.config\n        that reconfigures the mapping of <code>a</code>, <code>b</code>,\n        <code>x</code>, and <code>y</code> globally for all SDL controllers:\n      </p>\n\n      <code class=\"example\">\n        gamepad.a = act_b\n        gamepad.b = act_a\n        gamepad.x = act_y\n        gamepad.y = act_x\n      </code>\n\n      <p>\n        Furthermore, the Switch port may not map <code>axis_ltrigger</code>\n        or <code>axis_rtrigger</code>, and if it does, they probably only\n        return a value of either 0 or 32767 (this needs to be verified).\n      </p>\n\n      <div class=\"image-wrapper\">\n      <svg width=\"640\" height=\"320\" role=\"img\">\n        <title>Diagram resembling a Switch with buttons and axes labeled.</title>\n        <style>\n          .label { font: bold 16px monospace; }\n        </style>\n\n        <!-- shoulders -->\n        <line x1=\"5%\" y1=\"22%\" x2=\"5%\" y2=\"4%\"\n          stroke=\"black\" stroke-width=\"2\"/>\n        <line x1=\"8%\" y1=\"18%\" x2=\"8%\" y2=\"12%\"\n          stroke=\"black\" stroke-width=\"2\"/>\n        <line x1=\"5%\" y1=\"4%\" x2=\"16%\" y2=\"4%\"\n          stroke=\"black\" stroke-width=\"2\"/>\n        <line x1=\"8%\" y1=\"12%\" x2=\"12%\" y2=\"12%\"\n          stroke=\"black\" stroke-width=\"2\"/>\n        <text x=\"17%\" y=\"5%\" class=\"label\">ltrigger</text>\n        <text x=\"13%\" y=\"13%\" class=\"label\">lshoulder</text>\n\n        <line x1=\"95%\" y1=\"22%\" x2=\"95%\" y2=\"4%\"\n          stroke=\"black\" stroke-width=\"2\"/>\n        <line x1=\"92%\" y1=\"18%\" x2=\"92%\" y2=\"12%\"\n          stroke=\"black\" stroke-width=\"2\"/>\n        <line x1=\"95%\" y1=\"4%\" x2=\"84%\" y2=\"4%\"\n          stroke=\"black\" stroke-width=\"2\"/>\n        <line x1=\"92%\" y1=\"12%\" x2=\"88%\" y2=\"12%\"\n          stroke=\"black\" stroke-width=\"2\"/>\n        <text x=\"71%\" y=\"5%\" class=\"label\">rtrigger</text>\n        <text x=\"74%\" y=\"13%\" class=\"label\">rshoulder</text>\n\n        <svg x=\"2%\" y=\"20%\" width=\"96%\" height=\"80%\">\n\n          <!-- Outline -->\n          <rect x=\"1%\" y=\"1%\" width=\"98%\" height=\"98%\" rx=\"10%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"5\" />\n          <rect x=\"15%\" y=\"1%\" width=\"70%\" height=\"98%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\" />\n          <rect x=\"17%\" y=\"4%\" width=\"66%\" height=\"92%\" rx=\"2%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\" />\n          <rect x=\"22%\" y=\"10%\" width=\"56%\" height=\"80%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\" />\n          <rect x=\"10%\" y=\"75%\" width=\"2%\" height=\"5%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\" />\n          <circle cx=\"88.5%\" cy=\"77.5%\" r=\"1.5%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\" />\n\n          <!-- minus, plus -->\n          <rect x=\"11%\" y=\"8%\" width=\"2.5%\" height=\"2.5%\"\n            fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\" />\n          <svg x=\"86%\" y=\"5.5%\" width=\"3%\" height=\"7%\" viewbox=\"0 0 100 100\">\n            <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"15\"\n              d=\"M 33 10  H67 V33 H90 V67 H67 V90 H33 V67 H5 V33 H33 Z\" />\n          </svg>\n\n          <text x=\"77%\" y=\"9%\" class=\"label\">start</text>\n          <text x=\"16%\" y=\"9%\" class=\"label\">select</text>\n\n          <!-- Sticks -->\n          <svg x=\"4%\" y=\"8%\" width=\"8%\" height=\"40%\" viewbox=\"0 0 100 100\">\n            <circle cx=\"50%\" cy=\"50%\" r=\"48%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"4\" />\n            <circle cx=\"50%\" cy=\"50%\" r=\"40%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"6\" />\n          </svg>\n          <svg x=\"88%\" y=\"38%\" width=\"8%\" height=\"40%\" viewbox=\"0 0 100 100\">\n            <circle cx=\"50%\" cy=\"50%\" r=\"48%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"4\" />\n            <circle cx=\"50%\" cy=\"50%\" r=\"40%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"6\" />\n          </svg>\n\n          <line x1=\"13%\" y1=\"27%\" x2=\"27%\" y2=\"27%\"\n            stroke=\"black\" stroke-width=\"2\" />\n          <line x1=\"73%\" y1=\"58%\" x2=\"87%\" y2=\"58%\"\n            stroke=\"black\" stroke-width=\"2\" />\n          <text x=\"29%\" y=\"21%\" class=\"label\">axis_lx</text>\n          <text x=\"29%\" y=\"28%\" class=\"label\">axis_ly</text>\n          <text x=\"29%\" y=\"35%\" class=\"label\">lstick</text>\n          <text x=\"61%\" y=\"52%\" class=\"label\">axis_rx</text>\n          <text x=\"61%\" y=\"59%\" class=\"label\">axis_ry</text>\n          <text x=\"61%\" y=\"66%\" class=\"label\">rstick</text>\n\n          <!-- dpad -->\n          <svg x=\"2%\" y=\"38%\" width=\"12%\" height=\"40%\" viewbox=\"0 0 100 100\">\n            <circle cx=\"25%\" cy=\"50%\" r=\"12%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"4\"/>\n            <circle cx=\"75%\" cy=\"50%\" r=\"12%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"4\"/>\n            <circle cx=\"50%\" cy=\"25%\" r=\"12%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"4\"/>\n            <circle cx=\"50%\" cy=\"75%\" r=\"12%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"4\"/>\n          </svg>\n\n          <text x=\"6%\" y=\"45%\" class=\"label\">up</text>\n          <text x=\"6%\" y=\"74%\" class=\"label\">down</text>\n          <text x=\"-0.25%\" y=\"53%\" class=\"label\">left</text>\n          <text x=\"11%\" y=\"66%\" class=\"label\">right</text>\n\n          <!-- ABXY -->\n          <svg x=\"86%\" y=\"8%\" width=\"12%\" height=\"40%\" viewbox=\"0 0 100 100\">\n            <circle cx=\"25%\" cy=\"50%\" r=\"12%\"\n              fill=\"#aea\" stroke=\"#222\" stroke-width=\"4\"/>\n            <circle cx=\"75%\" cy=\"50%\" r=\"12%\"\n              fill=\"#eaa\" stroke=\"#222\" stroke-width=\"4\"/>\n            <circle cx=\"50%\" cy=\"25%\" r=\"12%\"\n              fill=\"#aae\" stroke=\"#222\" stroke-width=\"4\"/>\n            <circle cx=\"50%\" cy=\"75%\" r=\"12%\"\n              fill=\"#eea\" stroke=\"#222\" stroke-width=\"4\"/>\n          </svg>\n\n          <text x=\"97%\" y=\"28%\" class=\"label\">a</text>\n          <text x=\"90%\" y=\"44%\" class=\"label\">b</text>\n          <text x=\"94%\" y=\"19%\" class=\"label\">x</text>\n          <text x=\"86%\" y=\"35%\" class=\"label\">y</text>\n\n        </svg>\n      </svg>\n      </div>\n    </section>\n\n\n    <section id=\"layout-wii\" class=\"inner\">\n      <h3>Wii</h3>\n      <p>\n        Button availability for the Wii port varies given which controller\n        or extension controller is being used. Regardless of the available\n        controllers, however, no Wii layout maps <code>lstick</code> or\n        <code>rstick</code>. No buttons are reserved for a keyboard in any\n        layout as the Wii has multiple built-in USB ports. The home button is\n        hardcoded to <code>select</code> when present.\n      </p>\n      <p>\n        Extensions not otherwise listed below are the\n        Guitar Hero 3 extension controller (supported by MegaZeux) and the\n        Wii Balance Board and Wii Motion Plus (not supported by MegaZeux).\n      </p>\n\n      <section>\n        <h4>Wii Remote (no extension)</h4>\n        <p>\n          The mapping for this layout is fairly arbitrary and may change in\n          future versions (particularly if <code>start</code>/<code>select</code>\n          become more usable by games). This layout has no axes or mappings for\n          <code>ltrigger</code>, <code>rtrigger</code>, or <code>start</code>.\n          This layout assumes the Wii Remote is held horizontally like an NES\n          controller.\n        </p>\n\n        <div class=\"image-wrapper\">\n        <svg width=\"640\" height=\"200\" role=\"img\">\n          <title>Diagram resembling a Wii Remote with buttons labeled.</title>\n          <style>\n            .label { font: bold 16px monospace; }\n          </style>\n          <!-- Graphic is nested so it can be transformed below -->\n          <g>\n          <svg x=\"40\" y=\"50\" width=\"560\" height=\"140\">\n            <rect x=\"1%\" y=\"2%\" width=\"98%\" height=\"96%\" rx=\"5%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"5\"/>\n            <rect x=\"90%\" y=\"17.5%\" width=\"1.25%\" height=\"5%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\"/>\n            <rect x=\"90%\" y=\"37.5%\" width=\"1.25%\" height=\"5%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\"/>\n            <rect x=\"90%\" y=\"57.5%\" width=\"1.25%\" height=\"5%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\"/>\n            <rect x=\"90%\" y=\"77.5%\" width=\"1.25%\" height=\"5%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\"/>\n            <circle cx=\"5%\" cy=\"82.5%\" r=\"2%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\"/>\n\n            <circle cx=\"47.5%\" cy=\"25%\" r=\"2.25%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\"/>\n            <circle cx=\"47.5%\" cy=\"50%\" r=\"2%\"\n              fill=\"#ace\" stroke=\"#222\" stroke-width=\"3\"/>\n            <circle cx=\"47.5%\" cy=\"75%\" r=\"2.25%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\"/>\n            <circle cx=\"30%\" cy=\"50%\" r=\"5%\"\n              fill=\"#aac\" stroke=\"#222\" stroke-width=\"3\"/>\n            <circle cx=\"72.5%\" cy=\"50%\" r=\"3%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\"/>\n            <circle cx=\"82.5%\" cy=\"50%\" r=\"3%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\"/>\n\n            <svg x=\"4%\" y=\"25%\" width=\"25%\" height=\"50%\" viewbox=\"0 0 100 100\">\n              <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"4\"\n                d=\"M 37 5 H63 V37 H95 V63 H63 V95 H37 V63 H5 V37 H37 Z\"/>\n            </svg>\n          </svg>\n          </g>\n\n          <!-- AB labels -->\n          <line x1=\"28%\" x2=\"28%\" y1=\"10%\" y2=\"20%\"\n            stroke=\"black\" stroke-width=\"3\"/>\n          <text x=\"22%\" y=\"6%\" class=\"label\">lshoulder</text>\n          <text x=\"28%\" y=\"45%\" class=\"label\">rshoulder</text>\n\n          <!-- dpad labels -->\n          <text x=\"19.5%\" y=\"38%\" class=\"label\">up</text>\n          <text x=\"17.75%\" y=\"86%\" class=\"label\">down</text>\n          <text x=\"9%\" y=\"62%\" class=\"label\">left</text>\n          <text x=\"23%\" y=\"74%\" class=\"label\">right</text>\n\n          <!-- plus/minus labels -->\n          <text x=\"50.5%\" y=\"44%\" class=\"label\">x</text>\n          <text x=\"50%\" y=\"62%\" class=\"label\">select</text>\n          <text x=\"50.5%\" y=\"80%\" class=\"label\">y</text>\n\n          <!-- 12 labels -->\n          <text x=\"69%\" y=\"50%\" class=\"label\">b</text>\n          <text x=\"77.75%\" y=\"50%\" class=\"label\">a</text>\n        </svg>\n        </div>\n      </section>\n\n      <section>\n        <h4>Wii Remote (with nunchuck)</h4>\n        <p>\n          The mapping for this layout is fairly arbitrary and may change in\n          future versions (particularly if <code>start</code>/<code>select</code>\n          become more usable by games). This layout has no mappings for\n          <code>start</code>, <code>axis_rx</code>, <code>axis_ry</code>,\n          <code>axis_ltrigger</code>, or <code>axis_rtrigger</code>. This layout\n          assumes the Wii Remote is held like a remote in one hand and the\n          nunchuck is held by the other hand.\n        </p>\n\n        <div class=\"image-wrapper\">\n        <svg width=\"640\" height=\"500\" role=\"img\">\n          <title>Diagram resembling a Wii Remote and nunchuck, with buttons and axes labeled.</title>\n          <style>\n            .label { font: bold 16px monospace; }\n          </style>\n          <!-- Same as above but rotated. -->\n          <g transform=\"rotate(70,320,240)\">\n          <svg x=\"120\" y=\"80\" width=\"500\" height=\"120\">\n            <rect x=\"1%\" y=\"2%\" width=\"98%\" height=\"96%\" rx=\"5%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"5\"/>\n            <rect x=\"90%\" y=\"17.5%\" width=\"1.25%\" height=\"5%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\"/>\n            <rect x=\"90%\" y=\"37.5%\" width=\"1.25%\" height=\"5%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\"/>\n            <rect x=\"90%\" y=\"57.5%\" width=\"1.25%\" height=\"5%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\"/>\n            <rect x=\"90%\" y=\"77.5%\" width=\"1.25%\" height=\"5%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\"/>\n            <circle cx=\"5%\" cy=\"82.5%\" r=\"2%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\"/>\n\n            <circle cx=\"47.5%\" cy=\"25%\" r=\"2.25%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\"/>\n            <circle cx=\"47.5%\" cy=\"50%\" r=\"2%\"\n              fill=\"#ace\" stroke=\"#222\" stroke-width=\"3\"/>\n            <circle cx=\"47.5%\" cy=\"75%\" r=\"2.25%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\"/>\n            <circle cx=\"30%\" cy=\"50%\" r=\"5%\"\n              fill=\"#aac\" stroke=\"#222\" stroke-width=\"3\"/>\n            <circle cx=\"72.5%\" cy=\"50%\" r=\"3%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\"/>\n            <circle cx=\"82.5%\" cy=\"50%\" r=\"3%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\"/>\n\n            <svg x=\"4%\" y=\"25%\" width=\"25%\" height=\"50%\" viewbox=\"0 0 100 100\">\n              <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"4\"\n                d=\"M 37 5 H63 V37 H95 V63 H63 V95 H37 V63 H5 V37 H37 Z\"/>\n            </svg>\n          </svg>\n          </g>\n\n          <svg x=\"5%\" y=\"17.5%\" width=\"30%\" height=\"65%\" viewbox=\"0 0 100 200\">\n            <path fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\"\n              d=\"M 12 65   C 10 -15 90 -15 88 65   C 75 240 25 240 12 65    Z\"/>\n            <svg x=\"25\" y=\"35\" width=\"50\" height=\"50\" viewbox=\"0 0 100 100\">\n              <path fill=\"none\" stroke=\"darkgrey\" stroke-width=\"4\"\n                d=\"M 50  5  L 82 18  L 95 50  L 82 82\n                   L 50 95  L 18 82  L  5 50  L 18 18  Z\"/>\n              <circle cx=\"50\" cy=\"50\" r=\"30\"\n                fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"4\"/>\n            </svg>\n          </svg>\n\n          <!-- nunchuck labels -->\n          <line x1=\"24%\" x2=\"28%\" y1=\"19%\" y2=\"13%\"\n            stroke=\"black\" stroke-width=\"3\"/>\n          <line x1=\"28%\" x2=\"32%\" y1=\"24%\" y2=\"18%\"\n            stroke=\"black\" stroke-width=\"3\"/>\n          <line x1=\"27%\" x2=\"32%\" y1=\"36.5%\" y2=\"36.5%\"\n            stroke=\"black\" stroke-width=\"3\"/>\n          <text x=\"28%\" y=\"12%\" class=\"label\">lshoulder</text>\n          <text x=\"32%\" y=\"17%\" class=\"label\">ltrigger</text>\n          <text x=\"34%\" y=\"35%\" class=\"label\">axis_lx</text>\n          <text x=\"34%\" y=\"40%\" class=\"label\">axis_ly</text>\n\n          <!-- dpad labels -->\n          <text x=\"55%\" y=\"12%\" class=\"label\">up</text>\n          <text x=\"60%\" y=\"26.5%\" class=\"label\">down</text>\n          <text x=\"48%\" y=\"23%\" class=\"label\">left</text>\n          <text x=\"63.5%\" y=\"17%\" class=\"label\">right</text>\n\n          <!-- AB labels -->\n          <line x1=\"74%\" x2=\"70%\" y1=\"20%\" y2=\"22%\"\n            stroke=\"black\" stroke-width=\"3\"/>\n          <text x=\"75%\" y=\"20%\" class=\"label\">rtrigger</text>\n          <text x=\"66%\" y=\"32%\" class=\"label\">rshoulder</text>\n\n          <!-- plus/minus labels -->\n          <text x=\"61%\" y=\"55%\" class=\"label\">y</text>\n          <text x=\"67%\" y=\"53%\" class=\"label\">select</text>\n          <text x=\"71%\" y=\"43%\" class=\"label\">x</text>\n\n          <!-- 12 labels -->\n          <text x=\"76%\" y=\"70%\" class=\"label\">b</text>\n          <text x=\"79%\" y=\"80%\" class=\"label\">a</text>\n        </svg>\n        </div>\n      </section>\n\n      <section>\n        <h4>Wii Classic Controller</h4>\n        <p>\n          This layout maps all buttons and axes aside from the aforementioned\n          <code>lstick</code> and <code>rstick</code>. Input from the Wii Remote\n          is ignored in this configuration (aside from pointing/clicking).\n          Note that L/R are the triggers as they are analog and ZL/ZR are the\n          shoulders (opposite from the New 3DS layout).\n\n          This is the recommended controller for use with the Wii port.\n        </p>\n        <p>\n          The NES and SNES classic controllers currently select this layout as\n          well, but (obviously) several buttons will be missing. It is unknown\n          currently whether libogc can/will distinguish between these.\n        </p>\n\n        <div class=\"image-wrapper\">\n        <svg width=\"640\" height=\"320\" role=\"img\">\n          <title>Diagram resembling a Wii Classic Controller with buttons and axes labeled.</title>\n          <style>\n            .label { font: bold 16px monospace; }\n          </style>\n          <svg x=\"2%\" y=\"15%\" width=\"96%\" height=\"85%\" viewbox=\"0 0 200 100\">\n            <!-- Outline -->\n            <path fill=\"none\" stroke=\"darkgrey\" stroke-width=\"2\"\n              d=\"M 60 5  Q 100 10 140  5  C 210   0 210 100 140 95\n                         Q 100 90  60 95  C -10 100 -10   0  60  5  Z\" />\n\n            <!-- Shoulder buttons -->\n            <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1\"\n              d=\"M  73 5  Q  78 2  83 5  V 6.75  L  73 6  Z\"/>\n            <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1\"\n              d=\"M 127 5  Q 122 2 117 5  V 6.75  L 127 6  Z\"/>\n\n            <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1\"\n              d=\"M  29 11  C  40 5  50 4  59 4.5  V 1  C  50 1  40 1  29 8  Z\"/>\n            <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1\"\n              d=\"M 171 11  C 160 5 150 4 141 4.5  V 1  C 150 1 160 1 171 8  Z\"/>\n\n            <!-- Left stick -->\n            <svg x=\"61\" y=\"55\" width=\"30\" height=\"30\" viewbox=\"0 0 100 100\">\n              <path fill=\"none\" stroke=\"darkgrey\" stroke-width=\"4\"\n                d=\"M 50  5  L 82 18  L 95 50  L 82 82\n                   L 50 95  L 18 82  L  5 50  L 18 18  Z\"/>\n              <circle cx=\"50\" cy=\"50\" r=\"30\"\n                fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"4\"/>\n            </svg>\n\n            <!-- Right stick -->\n            <svg x=\"109\" y=\"55\" width=\"30\" height=\"30\" viewbox=\"0 0 100 100\">\n              <path fill=\"none\" stroke=\"darkgrey\" stroke-width=\"4\"\n                d=\"M 50  5  L 82 18  L 95 50  L 82 82\n                   L 50 95  L 18 82  L  5 50  L 18 18  Z\"/>\n              <circle cx=\"50\" cy=\"50\" r=\"30\"\n                fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"4\"/>\n            </svg>\n\n            <!-- dpad -->\n            <svg x=\"28\" y=\"23\" width=\"32\" height=\"32\" viewbox=\"0 0 100 100\">\n              <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"4\"\n                d=\"M 37 5 H63 V37 H95 V63 H63 V95 H37 V63 H5 V37 H37 Z\"/>\n            </svg>\n\n            <!-- center buttons -->\n            <circle cx=\"86\" cy=\"40\" r=\"3.25\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1\"/>\n            <circle cx=\"100\" cy=\"40\" r=\"3\"\n              fill=\"#ace\" stroke=\"#222\" stroke-width=\"1\"/>\n            <circle cx=\"114\" cy=\"40\" r=\"3.25\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1\"/>\n\n            <!-- abxy -->\n            <circle cx=\"138\" cy=\"40\" r=\"6\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1\"/>\n            <circle cx=\"156\" cy=\"26.5\" r=\"6\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"1\"/>\n            <circle cx=\"174\" cy=\"40\" r=\"6\"\n              fill=\"#aac\" stroke=\"#222\" stroke-width=\"1\"/>\n            <circle cx=\"156\" cy=\"53.5\" r=\"6\"\n              fill=\"#aac\" stroke=\"#222\" stroke-width=\"1\"/>\n          </svg>\n\n          <!-- Labels -->\n          <text x=\"12%\" y=\"7%\" class=\"label\">ltrigger</text>\n          <text x=\"12%\" y=\"13%\" class=\"label\">axis_ltrigger</text>\n          <text x=\"70%\" y=\"7%\" class=\"label\">rtrigger</text>\n          <text x=\"70%\" y=\"13%\" class=\"label\">axis_rtrigger</text>\n          <text x=\"36%\" y=\"15%\" class=\"label\">lshoulder</text>\n          <text x=\"52%\" y=\"15%\" class=\"label\">rshoulder</text>\n\n          <text x=\"8%\" y=\"35%\" class=\"label\">up, down,</text>\n          <text x=\"8%\" y=\"42%\" class=\"label\">left, right</text>\n\n          <text x=\"38%\" y=\"44%\" class=\"label\">select</text>\n          <text x=\"46%\" y=\"57%\" class=\"label\">select</text>\n          <text x=\"54%\" y=\"44%\" class=\"label\">start</text>\n\n          <text x=\"73%\" y=\"30%\" class=\"label\">x</text>\n          <text x=\"65.5%\" y=\"42%\" class=\"label\">y</text>\n          <text x=\"80.5%\" y=\"42%\" class=\"label\">a</text>\n          <text x=\"73%\" y=\"53%\" class=\"label\">b</text>\n\n          <text x=\"24%\" y=\"82%\" class=\"label\">axis_lx</text>\n          <text x=\"24%\" y=\"87%\" class=\"label\">axis_ly</text>\n          <text x=\"66%\" y=\"82%\" class=\"label\">axis_rx</text>\n          <text x=\"66%\" y=\"87%\" class=\"label\">axis_ry</text>\n        </svg>\n        </div>\n      </section>\n\n      <section>\n        <h4>Gamecube Controller</h4>\n        <p>\n          This layout maps most buttons but is notably lacking a button for\n          <code>select</code> and one shoulder button. Currently\n          <code>lshoulder</code> is mapped to Z and <code>rshoulder</code> is\n          left unmapped, as <code>lshoulder</code> is arguably more useful by\n          default.\n        </p>\n      </section>\n    </section>\n\n\n    <section id=\"layout-gp2x\" class=\"inner\">\n      <h3>GP2X</h3>\n      <p>\n        Some buttons are unusually mapped on the GP2X due to its non-standard\n        layout. The <code>a</code>, <code>b</code>, <code>x</code>, and\n        <code>y</code> actions are assigned using their Nintendo positions\n        rather than the GP2X button names. While the GP2X appears to have an\n        analog stick, it is actually only 8-directional. However, some models\n        have a stick press button, which is mapped to <code>lstick</code>. The\n        volume buttons are also mappable and assigned to <code>ltrigger</code>\n        and <code>rtrigger</code> (for lack of anything better to put there).\n      </p>\n      <p>\n        The GP2X does not map <code>axis_lx</code>, <code>axis_ly</code>,\n        <code>axis_rx</code>, <code>axis_ry</code>, <code>axis_ltrigger</code>,\n        <code>axis_rtrigger</code>, or <code>rstick</code>. No button is\n        reserved for a keyboard as the GP2X has a built-in USB port.\n      </p>\n\n      <div class=\"image-wrapper\">\n      <svg width=\"640\" height=\"360\" role=\"img\">\n        <title>Diagram resembling a GP2X with buttons and axes labeled.</title>\n        <style>\n          .label { font: bold 16px monospace; }\n        </style>\n\n        <!-- Shoulder text needs to be outside of outline... -->\n        <text x=\"7.5%\" y=\"7.5%\" class=\"label\">lshoulder</text>\n        <text x=\"80%\" y=\"7.5%\" class=\"label\">rshoulder</text>\n\n        <svg x=\"5%\" y=\"10%\" width=\"90%\" height=\"90%\">\n          <!-- Outline -->\n          <rect x=\"1%\" y=\"1%\" width=\"98%\" height=\"98%\" rx=\"10%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"5\" />\n          <rect x=\"3%\" y=\"4%\" width=\"94%\" height=\"85%\" rx=\"7.5%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\" />\n          <rect x=\"22%\" y=\"6%\" width=\"56%\" height=\"78%\" rx=\"1%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\" />\n          <rect x=\"26%\" y=\"10%\" width=\"48%\" height=\"70%\"\n            fill=\"none\" stroke=\"darkgrey\" stroke-width=\"3\" />\n\n          <!-- Shoulders -->\n          <svg width=\"100%\" height=\"20%\" viewbox=\"0 0 100 10\">\n            <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\".75\"\n              d=\"M 1 10   Q  1.5 0.5 10 0  H18 V1.5 H12   Q  3 1.75  3 10 Z\"/>\n            <path fill=\"lightgrey\" stroke=\"#222\" stroke-width=\".75\"\n              d=\"M99 10   Q 98.5 0.5 90 0  H82 V1.5 H88   Q 97 1.75 97 10 Z\"/>\n          </svg>\n\n          <!-- Directional stick -->\n          <svg x=\"5%\" y=\"22.5%\" width=\"15%\" height=\"25%\" viewbox=\"0 0 100 100\">\n            <circle cx=\"50\" cy=\"50\" r=\"44\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"5\" />\n            <circle cx=\"50\" cy=\"50\" r=\"30\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"5\" />\n          </svg>\n\n          <line x1=\"20%\" y1=\"35%\" x2=\"30%\" y2=\"35%\"\n            stroke=\"black\" stroke-width=\"2\" />\n          <text x=\"32%\" y=\"30%\" class=\"label\">up, down,</text>\n          <text x=\"32%\" y=\"36%\" class=\"label\">left, right,</text>\n          <text x=\"32%\" y=\"42%\" class=\"label\">lstick</text>\n\n          <!-- Volume buttons -->\n          <rect x=\"6%\" y=\"65%\" width=\"13%\" height=\"5%\" rx=\"1.5%\"\n            fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\" />\n          <line x1=\"12.5%\" y1=\"66%\" x2=\"12.5%\" y2=\"69%\"\n            stroke=\"#222\" stroke-width=\"3\" />\n\n          <line x1=\"8%\" y1=\"59%\" x2=\"8%\" y2=\"63%\"\n            stroke=\"black\" stroke-width=\"2\" />\n          <line x1=\"17%\" y1=\"72%\" x2=\"17%\" y2=\"76%\"\n            stroke=\"black\" stroke-width=\"2\" />\n          <text x=\"5%\" y=\"57%\" class=\"label\">ltrigger</text>\n          <text x=\"8%\" y=\"80%\" class=\"label\">rtrigger</text>\n\n          <!-- ABXY -->\n          <svg x=\"72.5%\" y=\"19%\" width=\"30%\" height=\"35%\" viewbox=\"0 0 100 100\">\n            <circle cx=\"50%\" cy=\"50%\" r=\"25%\"\n              fill=\"none\" stroke=\"darkgrey\" stroke-width=\"2\" />\n            <circle cx=\"50%\" cy=\"25%\" r=\"12%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\" />\n            <circle cx=\"50%\" cy=\"75%\" r=\"12%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\" />\n            <circle cx=\"25%\" cy=\"50%\" r=\"12%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\" />\n            <circle cx=\"75%\" cy=\"50%\" r=\"12%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"3\" />\n          </svg>\n\n          <text x=\"95%\" y=\"40%\" class=\"label\">a</text>\n          <text x=\"90.5%\" y=\"50%\" class=\"label\">b</text>\n          <text x=\"83%\" y=\"26%\" class=\"label\">x</text>\n          <text x=\"78.5%\" y=\"35%\" class=\"label\">y</text>\n\n          <!-- Start/Select -->\n          <svg x=\"77%\" y=\"55%\" width=\"20%\" height=\"20%\" viewbox=\"0 0 100 100\">\n            <rect x=\"0%\" y=\"50%\" width=\"50%\" height=\"20%\" rx=\"10%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"5\"\n              transform=\"rotate(-45 25 50)\" />\n            <rect x=\"50%\" y=\"50%\" width=\"50%\" height=\"20%\" rx=\"10%\"\n              fill=\"lightgrey\" stroke=\"#222\" stroke-width=\"5\"\n              transform=\"rotate(-45 75 50)\" />\n          </svg>\n\n          <text x=\"79%\" y=\"60%\" class=\"label\">select</text>\n          <text x=\"88%\" y=\"76%\" class=\"label\">start</text>\n        </svg>\n      </svg>\n      </div>\n    </section>\n  </section>\n\n  <hr>\n\n  <section id=\"license\" class=\"main\">\n    <h2>License</h2>\n    <section class=\"inner\">\n      <p>\n        Copyright &copy; 2019-2025 Lachesis &mdash;\n        <a href=\"https://github.com/AliceLR/megazeux/\">\n          https://github.com/AliceLR/megazeux/\n        </a>\n      </p>\n\n      <p>\n        Permission to use, copy, modify, and distribute this document for any\n        purpose with or without fee is hereby granted, provided that the above\n        copyright notice and this permission notice appear in all copies.\n      </p>\n\n      <p>\n        THE DOCUMENT IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n        WITH REGARD TO THIS DOCUMENT INCLUDING ALL IMPLIED WARRANTIES OF\n        MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n        ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n        WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n        ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n        OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS DOCUMENT.\n      </p>\n    </section>\n  </section>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "docs/keycodes.html",
    "content": "<!DOCTYPE html>\n<!--\n  MegaZeux US Key-Mapping Guide\n\n  Copyright (C) 2010-2025 Lachesis - https://github.com/AliceLR/megazeux/\n\n  Permission to use, copy, modify, and distribute this document for any\n  purpose with or without fee is hereby granted, provided that the above\n  copyright notice and this permission notice appear in all copies.\n\n  THE DOCUMENT IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n  WITH REGARD TO THIS DOCUMENT INCLUDING ALL IMPLIED WARRANTIES OF\n  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS DOCUMENT.\n-->\n<html>\n <head>\n  <title>MegaZeux Keycodes Guide</title>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <meta name=\"title\" content=\"MegaZeux Keycodes Guide\">\n  <meta name=\"description\" content=\"Diagram of MegaZeux keycodes for the US keyboard layout.\">\n  <meta name=\"twitter:card\" content=\"summary\">\n  <meta name=\"twitter:title\" content=\"MegaZeux Keycodes Guide\">\n  <meta name=\"twitter:description\" content=\"Diagram of MegaZeux keycodes for the US keyboard layout.\">\n  <style>\n   body\n   {\n    max-width: 1260px;\n    font-family: Arial, sans;\n   }\n\n   /* Browsers that don't support flexboxes should ignore these settings and\n    * and use the floating divs. */\n   #keyboardcontainer\n   {\n    display: flex;\n    flex-flow: row-reverse;\n    /* The keyboard starts to visually break apart around ~824px. */\n    /* It breaks closer to 860px in Firefox/Linux. */\n    min-width: 860px;\n    overflow: auto;\n   }\n\n   #keyboardmaincontainer\n   {\n    /* Shrink within parent ^ */\n    flex-shrink: 3;\n    /* This div is also a separate flexbox */\n    display: flex;\n    flex-flow: column;\n   }\n\n   #keyboardkey\n   {\n    /* See keyboardcontainer */\n    min-width: 860px;\n    overflow: auto;\n   }\n\n   table, tbody, tr, td, div, p, b\n   {\n    /* Need this for the flex shrinking to work properly. */\n    min-width: 0px;\n    /* Need this for annotation superscript positioning to work properly. */\n    position: relative;\n   }\n\n   .navigation, .numpad, .capslock, .modifier, .enter, .backspace\n   {\n    /* Keys with lengthy text get smaller text. */\n    font-size: 95%;\n   }\n\n   span#capslocktext:before { content: \"Caps\\00a0Lock\"; }\n\n   table.keyboard\n   {\n    margin: 0px;\n    border: 0px solid black;\n    border-spacing: 0px 0px;\n    padding: 0px;\n   }\n   table.keyboard.navigation\n   {\n    flex-shrink: 1;\n    float: right;\n   }\n   table.keyboard.numpad\n   {\n    flex-shrink: 1;\n    float: right;\n   }\n   table.keyboard.key\n   {\n    margin-left: 8px;\n   }\n\n   table.keyboard td\n   {\n    width: 48px;\n    height: 64px;\n    border-width: 0px 7px 1px 0px;\n    border-style: solid;\n    border-color: white;\n   }\n   table.keyboard .wide\n   {\n    width: 72px;\n   }\n   table.keyboard .extrawide\n   {\n    width: 88px;\n   }\n   table.keyboard .double\n   {\n    width: 116px;\n   }\n   table.keyboard .spacebar\n   {\n    width: 244px;\n   }\n   table.keyboard .space\n   {\n    display: block;\n    width: 20px;\n    height: 16px;\n    margin: 0px;\n    border: 0px solid white;\n   }\n\n   table.keyboard div\n   {\n    display: block;\n    width: 100%;\n    height: 100%;\n    margin-right: -4px;\n    margin-left: -1px;\n    border-width: 1px 3px 6px 1px;\n    border-style: solid;\n    border-color: black;\n    padding: 1px;\n    padding-top: 0px;\n    padding-bottom: 0px;\n\n    background-color: #EEF;\n    color: #007;\n   }\n   table.keyboard .game\n   {\n    background-color: #CFC;\n    color: #070;\n   }\n   table.keyboard .test\n   {\n    background-color: #FFC;\n    color: #770;\n   }\n   table.keyboard .warn\n   {\n    background-color: #FCE;\n    color: #705;\n   }\n   table.keyboard .bad\n   {\n    background-color: #FCC;\n    color: #700;\n   }\n   table.keyboard .none\n   {\n    background-color: #FFF;\n    color: #AAA;\n    border-color: #EEE;\n   }\n\n   /* PC XT (KEY_CODE) */\n   table.keyboard td b\n   {\n    display: block;\n    text-align: center;\n    color: #585;\n   }\n\n   /* Internal (KEY_PRESSED) */\n   table.keyboard td p\n   {\n    margin: 0px;\n    text-align: center;\n    font-weight: bold;\n    color: #A5A;\n   }\n\n   /* Config file key name */\n   table.keyboard td span.keyname\n   {\n    display: none;\n    position: absolute;\n    left: -4px;\n    bottom: -20px;\n    background-color: #444;\n    color: white;\n    font-family: monospace;\n    text-align: center;\n    padding: 3px 5px;\n    border-radius: 5px;\n   }\n   table.keyboard td span.keyname.rhs\n   {\n    left: auto;\n    right: -6px;\n   }\n   table.keyboard td:hover span.keyname\n   {\n    display: inline-block;\n    z-index: 1;\n   }\n\n   table.keyboard.key td div\n   {\n    height: 80px;\n   }\n\n   div:before, b:before, p:before\n   {\n    font-size: 60%;\n    position: absolute;\n    right: 1px;\n    top: -1px;\n   }\n   .f12:before {\n    content: \"A\";\n   }\n   .badkey:before {\n    content: \"B\";\n   }\n   .capslock:before {\n    content: \"C\";\n   }\n   .modifier:before {\n    content: \"D\";\n   }\n   .altgr:before {\n    content: \"E\";\n   }\n   .annotation {\n    font-size: .95em;\n   }\n\n   table.bypass\n   {\n    border: 1px solid #000;\n    margin: 12px;\n   }\n   table.bypass th\n   {\n    border: 1px solid #555;\n    font-weight: bold;\n    text-align: left;\n    padding-right: 16px;\n    padding-top: 12px;\n    padding-bottom: 12px;\n   }\n   table.bypass td\n   {\n    border-top: 1px solid #aaa;\n    border-bottom: 1px solid #aaa;\n    padding: 5px 0px 5px 10px;\n    font-weight: normal;\n   }\n   var\n   {\n    padding-left: 4px;\n    padding-right: 4px;\n    font-family: \"Lucida Console\", Monaco, monospace;\n    font-style: normal;\n    font-weight: bold;\n    font-size: 85%;\n   }\n\n   /* Scaling styles. */\n   @media (max-width: 1024px)\n   {\n    /* Need the normal width for the Fn row to line up usually,\n     * but for smaller screens these are just wasting space... */\n    table.keyboard .space { width: 4px; }\n\n    /* Also make the Caps Lock label waste less space. */\n    span#capslocktext:before { content: \"CapsL.\"; }\n   }\n\n   /* IE10+ hacks. */\n   @media all and (-ms-high-contrast: none), (-ms-high-contrast: active)\n   {\n    /* Scaling is so broken it's easier to just force full width. */\n    body { width: 1280px; }\n   }\n\n   /* Edge 12+ hacks. */\n   @supports (-ms-ime-align:auto)\n   {\n    /* Edge won't scale the navigation/numpad tables properly, breaking\n     * scaling earlier than usual. */\n    body { min-width: 864px; }\n\n    /* It also displays the unicode chars as emoji for no reason. */\n    table.keyboard div { font-family: \"Segoe UI Symbol\"; font-size: 90%; }\n   }\n\n   /* Firefox hacks. */\n   @-moz-document url-prefix()\n   {\n    table.keyboard.key td\n    {\n     /* Firefox won't properly expand tds vertically to fit their contents. */\n     height: 72px;\n    }\n\n    table.keyboard .rowspan2hack\n    {\n     /* Firefox won't properly expand height:100% divs to fit rowspan=2. */\n     height: 138px;\n    }\n   }\n  </style>\n </head>\n <body>\n  <div>\n   <h1>MegaZeux US Key-Mapping Guide</h1>\n   <h3>MegaZeux 2.93d &mdash; June 9th, 2025</h3>\n   <p>\n    This guide contains information and warnings for reading keycodes in\n    MegaZeux games.\n   </p>\n\n   <hr/>\n  </div>\n\n  <div id=\"keyboardcontainer\">\n\n\n<!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->\n\n\n  <table class=\"keyboard numpad\">\n   <tr>\n    <td><div class=\"none\">&nbsp;\n    </div></td>\n    <td><div class=\"none\">&nbsp;\n    </div></td>\n    <td><div class=\"none\">&nbsp;\n    </div></td>\n    <td><div class=\"none\">&nbsp;\n    </div></td>\n   </tr>\n   <tr class=\"space\">\n   </tr>\n   <tr>\n\n    <td>\n     <div class=\"bad numlock\">\n      NumL.\n      <b class=\"badkey\">n/a</b>\n      <p class=\"badkey\">n/a</p>\n      <span class=\"keyname\">key_numlock</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      /\n      <b>53</b>\n      <p>267</p>\n      <span class=\"keyname\">key_kp_divide</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      *\n      <b>55</b>\n      <p>268</p>\n      <span class=\"keyname rhs\">key_kp_multiply</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      -\n      <b>74</b>\n      <p>269</p>\n      <span class=\"keyname rhs\">key_kp_minus</span>\n     </div>\n    </td>\n\n   </tr>\n   <tr>\n\n    <td>\n     <div class=\"warn\">\n      7<sup>&nbsp;Home</sup>\n      <b>71</b>\n      <p>263</p>\n      <span class=\"keyname\">key_kp7</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n       8<sup>&nbsp;&#x25b2;</sup>\n      <b>72</b>\n      <p>264</p>\n      <span class=\"keyname\">key_kp8</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      9<sup>&nbsp;PgUp</sup>\n      <b>73</b>\n      <p>265</p>\n      <span class=\"keyname\">key_kp9</span>\n     </div>\n    </td>\n\n    <td rowspan=\"2\" class=\"rowspan2hack\">\n     <div class=\"warn\">\n      +\n      <b>78</b>\n      <p>270</p>\n      <span class=\"keyname rhs\">key_kp_plus</span>\n     </div>\n    </td>\n\n   </tr>\n   <tr>\n\n    <td>\n     <div class=\"warn\">\n       4<sup>&nbsp;&#x25c0;</sup>\n      <b>75</b>\n      <p>260</p>\n      <span class=\"keyname\">key_kp4</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      5\n      <b>76</b>\n      <p>261</p>\n      <span class=\"keyname\">key_kp5</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n       6<sup>&nbsp;&#x25b6;</sup>\n      <b>77</b>\n      <p>262</p>\n      <span class=\"keyname\">key_kp6</span>\n     </div>\n    </td>\n\n   </tr>\n   <tr>\n\n    <td>\n     <div class=\"warn\">\n      1<sup>&nbsp;End</sup>\n      <b>79</b>\n      <p>257</p>\n      <span class=\"keyname\">key_kp1</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n       2<sup>&nbsp;&#x25bc;</sup>\n      <b>80</b>\n      <p>258</p>\n      <span class=\"keyname\">key_kp2</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      3<sup>&nbsp;PgDn</sup>\n      <b>81</b>\n      <p>259</p>\n      <span class=\"keyname\">key_kp3</span>\n     </div>\n    </td>\n\n    <td rowspan=\"2\" class=\"rowspan2hack\">\n     <div class=\"warn\">\n      Enter\n      <b>28</b>\n      <p>271</p>\n      <span class=\"keyname rhs\">key_kp_enter</span>\n     </div>\n    </td>\n\n   </tr>\n   <tr>\n\n    <td colspan=\"2\">\n     <div class=\"warn\">\n      0<sup>&nbsp;Insert</sup>\n      <b>82</b>\n      <p>256</p>\n      <span class=\"keyname\">key_kp0</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      .<sup>Delete</sup>\n      <b>83</b>\n      <p>266</p>\n      <span class=\"keyname\">key_kp_period</span>\n     </div>\n    </td>\n\n   </tr>\n   <tr class=\"space\">\n   </tr>\n  </table>\n\n\n<!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->\n\n\n  <table class=\"keyboard navigation\">\n   <tr>\n\n    <td>\n     <div class=\"warn\">\n      SysRq\n      <b>55</b>\n      <p>317</p>\n      <span class=\"keyname\">key_sysreq</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      Scr.L.\n      <b>70</b>\n      <p>302</p>\n      <span class=\"keyname\">key_scrolllock</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      Break\n      <b>197</b>\n      <p>318</p>\n      <span class=\"keyname\">key_break</span>\n     </div>\n    </td>\n\n    <td class=\"space\">\n    </td>\n   </tr>\n   <tr class=\"space\">\n   </tr>\n   <tr>\n\n    <td>\n     <div class=\"game\">\n      Insert\n      <b>82</b>\n      <p>277</p>\n      <span class=\"keyname\">key_insert</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      Home\n      <b>71</b>\n      <p>278</p>\n      <span class=\"keyname\">key_home</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      PgUp\n      <b>73</b>\n      <p>280</p>\n      <span class=\"keyname\">key_pageup</span>\n     </div>\n    </td>\n\n    <td class=\"space\">\n    </td>\n   </tr>\n   <tr>\n\n    <td>\n     <div class=\"game\">\n      Delete\n      <b>83</b>\n      <p>127</p>\n      <span class=\"keyname\">key_delete</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      End\n      <b>79</b>\n      <p>279</p>\n      <span class=\"keyname\">key_end</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      PgDn\n      <b>81</b>\n      <p>281</p>\n      <span class=\"keyname\">key_pagedown</span>\n     </div>\n    </td>\n\n    <td class=\"space\">\n    </td>\n   </tr>\n   <tr>\n\n    <td><div class=\"none\">&nbsp;\n    </div></td>\n    <td><div class=\"none\">&nbsp;\n    </div></td>\n    <td><div class=\"none\">&nbsp;\n    </div></td>\n\n    <td class=\"space\">\n    </td>\n   </tr>\n   <tr>\n\n    <td><div class=\"none\">&nbsp;\n    </div></td>\n\n    <td>\n     <div class=\"game\">\n      <!-- &#x2191; -->\n      &#x25b2;\n      <b>72</b>\n      <p>273</p>\n      <span class=\"keyname\">key_up</span>\n     </div>\n    </td>\n\n    <td><div class=\"none\">&nbsp;\n    </div></td>\n\n    <td class=\"space\">\n    </td>\n   </tr>\n   <tr>\n\n    <td>\n     <div class=\"game\">\n      <!-- &#x2190; -->\n      &#x25c0;\n      <b>75</b>\n      <p>276</p>\n      <span class=\"keyname\">key_left</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"game\">\n      <!-- &#x2193; -->\n      &#x25bc;\n      <b>80</b>\n      <p>274</p>\n      <span class=\"keyname\">key_down</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"game\">\n      <!-- &#x2192; -->\n      &#x25b6;\n      <b>77</b>\n      <p>275</p>\n      <span class=\"keyname\">key_right</span>\n     </div>\n    </td>\n\n    <td class=\"space\">\n    </td>\n   </tr>\n   <tr class=\"space\">\n   </tr>\n  </table>\n\n\n<!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->\n\n\n  <div id=\"keyboardmaincontainer\">\n\n  <table class=\"keyboard main\">\n   <tr>\n\n    <td>\n     <div class=\"game\">\n      Esc\n      <b>1</b>\n      <p>27</p>\n      <span class=\"keyname\">key_escape</span>\n     </div>\n    </td>\n\n    <td class=\"space\">\n    </td>\n\n    <td>\n     <div class=\"game\">\n      F1\n      <b>59</b>\n      <p>282</p>\n      <span class=\"keyname\">key_f1</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"game\">\n      F2\n      <b>60</b>\n      <p>283</p>\n      <span class=\"keyname\">key_f2</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"game\">\n      F3\n      <b>61</b>\n      <p>284</p>\n      <span class=\"keyname\">key_f3</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"game\">\n      F4\n      <b>62</b>\n      <p>285</p>\n      <span class=\"keyname\">key_f4</span>\n     </div>\n    </td>\n\n    <td class=\"space\">\n    </td>\n\n    <td>\n     <div class=\"game\">\n      F5\n      <b>63</b>\n      <p>286</p>\n      <span class=\"keyname\">key_f5</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"test\">\n      F6\n      <b>64</b>\n      <p>287</p>\n      <span class=\"keyname\">key_f6</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"test\">\n      F7\n      <b>65</b>\n      <p>288</p>\n      <span class=\"keyname\">key_f7</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"test\">\n      F8\n      <b>66</b>\n      <p>289</p>\n      <span class=\"keyname\">key_f8</span>\n     </div>\n    </td>\n\n    <td class=\"space\">\n    </td>\n\n    <td>\n     <div class=\"game\">\n      F9\n      <b>67</b>\n      <p>290</p>\n      <span class=\"keyname\">key_f9</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"game\">\n      F10\n      <b>68</b>\n      <p>291</p>\n      <span class=\"keyname\">key_f10</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"test\">\n      F11\n      <b>87</b>\n      <p>292</p>\n      <span class=\"keyname\">key_f11</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"game f12\">\n      F12\n      <b>88</b>\n      <p>293</p>\n      <span class=\"keyname\">key_f12</span>\n     </div>\n    </td>\n\n    <td class=\"space\">\n    </td>\n   </tr>\n   <tr class=\"space\">\n   </tr>\n  </table>\n  <table class=\"keyboard main\">\n   <tr>\n\n    <td>\n     <div class=\"warn\">\n      `\n      <b>41</b>\n      <p>96</p>\n      <span class=\"keyname\">key_backquote</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      1\n      <b>2</b>\n      <p>49</p>\n      <span class=\"keyname\">key_1</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      2\n      <b>3</b>\n      <p>50</p>\n      <span class=\"keyname\">key_2</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      3\n      <b>4</b>\n      <p>51</p>\n      <span class=\"keyname\">key_3</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      4\n      <b>5</b>\n      <p>52</p>\n      <span class=\"keyname\">key_4</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      5\n      <b>6</b>\n      <p>53</p>\n      <span class=\"keyname\">key_5</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      6\n      <b>7</b>\n      <p>54</p>\n      <span class=\"keyname\">key_6</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      7\n      <b>8</b>\n      <p>55</p>\n      <span class=\"keyname\">key_7</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      8\n      <b>9</b>\n      <p>56</p>\n      <span class=\"keyname\">key_8</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      9\n      <b>10</b>\n      <p>57</p>\n      <span class=\"keyname\">key_9</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      0\n      <b>11</b>\n      <p>48</p>\n      <span class=\"keyname\">key_0</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      -\n      <b>12</b>\n      <p>45</p>\n      <span class=\"keyname\">key_minus</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      =\n      <b>13</b>\n      <p>61</p>\n      <span class=\"keyname\">key_equals</span>\n     </div>\n    </td>\n\n    <td class=\"wide\">\n     <div class=\"backspace\">\n      BackSp.\n      <b>14</b>\n      <p>8</p>\n      <span class=\"keyname\">key_backspace</span>\n     </div>\n    </td>\n\n    <td class=\"space\">\n    </td>\n   </tr>\n  </table>\n  <table class=\"keyboard main\">\n   <tr>\n\n    <td class=\"wide\">\n     <div>\n      Tab\n      <b>15</b>\n      <p>9</p>\n      <span class=\"keyname\">key_tab</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      Q\n      <b>16</b>\n      <p>113</p>\n      <span class=\"keyname\">key_q</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      W\n      <b>17</b>\n      <p>119</p>\n      <span class=\"keyname\">key_w</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      E\n      <b>18</b>\n      <p>101</p>\n      <span class=\"keyname\">key_e</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      R\n      <b>19</b>\n      <p>114</p>\n      <span class=\"keyname\">key_r</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      T\n      <b>20</b>\n      <p>116</p>\n      <span class=\"keyname\">key_t</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      Y\n      <b>21</b>\n      <p>121</p>\n      <span class=\"keyname\">key_y</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      U\n      <b>22</b>\n      <p>117</p>\n      <span class=\"keyname\">key_u</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      I\n      <b>23</b>\n      <p>105</p>\n      <span class=\"keyname\">key_i</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      O\n      <b>24</b>\n      <p>111</p>\n      <span class=\"keyname\">key_o</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      P\n      <b>25</b>\n      <p>112</p>\n      <span class=\"keyname\">key_p</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      [\n      <b>26</b>\n      <p>91</p>\n      <span class=\"keyname\">key_leftbracket</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      ]\n      <b>27</b>\n      <p>93</p>\n      <span class=\"keyname\">key_rightbracket</span>\n     </div>\n    </td>\n\n    <td>\n     <div class=\"warn\">\n      \\\n      <b>43</b>\n      <p>92</p>\n      <span class=\"keyname\">key_backslash</span>\n     </div>\n    </td>\n\n    <td class=\"space\">\n    </td>\n   </tr>\n  </table>\n  <table class=\"keyboard main\">\n   <tr>\n\n    <td class=\"extrawide\">\n     <div class=\"capslock\">\n      <span id=\"capslocktext\"></span>\n      <b>58</b>\n      <p>301</p>\n      <span class=\"keyname\">key_capslock</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      A\n      <b>30</b>\n      <p>97</p>\n      <span class=\"keyname\">key_a</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      S\n      <b>31</b>\n      <p>115</p>\n      <span class=\"keyname\">key_s</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      D\n      <b>32</b>\n      <p>100</p>\n      <span class=\"keyname\">key_d</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      F\n      <b>33</b>\n      <p>102</p>\n      <span class=\"keyname\">key_f</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      G\n      <b>34</b>\n      <p>103</p>\n      <span class=\"keyname\">key_g</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      H\n      <b>35</b>\n      <p>104</p>\n      <span class=\"keyname\">key_h</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      J\n      <b>36</b>\n      <p>106</p>\n      <span class=\"keyname\">key_j</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      K\n      <b>37</b>\n      <p>107</p>\n      <span class=\"keyname\">key_k</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      L\n      <b>38</b>\n      <p>108</p>\n      <span class=\"keyname\">key_l</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      ;\n      <b>39</b>\n      <p>59</p>\n      <span class=\"keyname\">key_semicolon</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      '\n      <b>40</b>\n      <p>39</p>\n      <span class=\"keyname\">key_quote</span>\n     </div>\n    </td>\n\n    <td class=\"extrawide\">\n     <div class=\"game enter\">\n      Enter\n      <b>28</b>\n      <p>13</p>\n      <span class=\"keyname\">key_return</span>\n     </div>\n    </td>\n\n    <td class=\"space\">\n    </td>\n   </tr>\n  </table>\n  <table class=\"keyboard main\">\n   <tr>\n\n    <td class=\"double\">\n     <div class=\"modifier\">\n      L.&nbsp;Shift\n      <b>42</b>\n      <p>304</p>\n      <span class=\"keyname\">key_lshift</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      Z\n      <b>44</b>\n      <p>122</p>\n      <span class=\"keyname\">key_z</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      X\n      <b>45</b>\n      <p>120</p>\n      <span class=\"keyname\">key_x</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      C\n      <b>46</b>\n      <p>99</p>\n      <span class=\"keyname\">key_c</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      V\n      <b>47</b>\n      <p>118</p>\n      <span class=\"keyname\">key_v</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      B\n      <b>48</b>\n      <p>98</p>\n      <span class=\"keyname\">key_b</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      N\n      <b>49</b>\n      <p>110</p>\n      <span class=\"keyname\">key_n</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      M\n      <b>50</b>\n      <p>109</p>\n      <span class=\"keyname\">key_m</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      ,\n      <b>51</b>\n      <p>44</p>\n      <span class=\"keyname\">key_comma</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      .\n      <b>52</b>\n      <p>46</p>\n      <span class=\"keyname\">key_period</span>\n     </div>\n    </td>\n\n    <td>\n     <div>\n      /\n      <b>53</b>\n      <p>47</p>\n      <span class=\"keyname\">key_slash</span>\n     </div>\n    </td>\n\n    <td class=\"double\">\n     <div class=\"modifier\">\n      R.&nbsp;Shift\n      <b>54</b>\n      <p>303</p>\n      <span class=\"keyname\">key_rshift</span>\n     </div>\n    </td>\n\n    <td class=\"space\">&nbsp;\n    </td>\n   </tr>\n  </table>\n  <table class=\"keyboard main\">\n   <tr>\n\n    <td class=\"wide\">\n     <div class=\"modifier\">\n      L.&nbsp;Ctrl\n      <b>29</b>\n      <p>306</p>\n      <span class=\"keyname\">key_lctrl</span>\n     </div>\n    </td>\n\n    <td class=\"wide\">\n     <div class=\"warn modifier\">\n      L.&nbsp;Win\n      <b>91</b>\n      <p>311</p>\n      <span class=\"keyname\">key_lsuper</span>\n     </div>\n    </td>\n\n    <td class=\"wide\">\n     <div class=\"modifier\">\n      L.&nbsp;Alt\n      <b>56</b>\n      <p>308</p>\n      <span class=\"keyname\">key_lalt</span>\n     </div>\n    </td>\n\n    <td class=\"spacebar\"><div>\n      Space\n      <b>57</b>\n      <p>32</p>\n      <span class=\"keyname\">key_space</span>\n     </div>\n    </td>\n\n    <td class=\"wide\">\n     <div class=\"warn modifier altgr\">\n      R.&nbsp;Alt\n      <b>56</b>\n      <p>307</p>\n      <span class=\"keyname\">key_ralt</span>\n     </div>\n    </td>\n\n    <td class=\"wide\">\n     <div class=\"warn modifier\">\n      R.&nbsp;Win\n      <b>92</b>\n      <p>312</p>\n      <span class=\"keyname\">key_rsuper</span>\n     </div>\n    </td>\n\n    <td class=\"wide\">\n     <div class=\"warn\">\n      Menu\n      <b>93</b>\n      <p>319</p>\n      <span class=\"keyname\">key_menu</span>\n     </div>\n    </td>\n\n    <td class=\"wide\">\n     <div class=\"modifier\">\n      R.&nbsp;Ctrl\n      <b>29</b>\n      <p>305</p>\n      <span class=\"keyname\">key_rctrl</span>\n     </div>\n    </td>\n\n    <td class=\"space\">&nbsp;\n    </td>\n   </tr>\n   <tr class=\"space\">\n   </tr>\n  </table>\n\n  </div> <!-- #keyboardmaincontainer -->\n\n\n<!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->\n\n\n  </div> <!-- #keyboardcontainer -->\n\n  <table id=\"keyboardkey\" class=\"keyboard key\">\n   <tr>\n    <td class=\"spacebar\">\n     <div class=\"none\">\n      Key:\n      <b>key_code / key(n) <span style=\"font-size: 75%\">[PC XT]</span></b>\n      <p>key_pressed <span style=\"font-size: 75%\">[internal]</span></p>\n      <i style=\"display: block; text-align: center\">(hover: config file key name)</i>\n      <span class=\"keyname\">\n       Key names can be used instead of key_pressed numbers in the config file.\n       To get the config file name for a key, hover over it.\n      </span>\n     </div>\n    </td>\n    <td class=\"space\">\n    </td>\n    <td class=\"double\">\n     <div>\n      Normal key&mdash;always available for use.\n     </div>\n    </td>\n    <td class=\"space\">\n    </td>\n    <td class=\"double\">\n     <div class=\"game\">\n      Game key&mdash;can disable via Robotic or other means.\n     </div>\n    </td>\n    <td class=\"space\">\n    </td>\n    <td class=\"double\">\n     <div class=\"test\">\n      Debug key&mdash;has a unique function only while testing.\n     </div>\n    </td>\n    <td class=\"space\">\n    </td>\n    <td class=\"double\">\n     <div class=\"warn\">\n      This key may be missing between keyboards.\n     </div>\n    </td>\n    <td class=\"space\">\n    </td>\n    <td class=\"double\">\n     <div class=\"bad\">\n      Unusable key&mdash;can not or should not be used ever.\n     </div>\n    </td>\n   </tr>\n  </table>\n\n\n<!-- +++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->\n\n\n  <hr/>\n  <table class=\"annotations\">\n   <tr>\n    <td class=\"f12\">&nbsp;</td>\n    <td class=\"annotation\">\n     F12 is currently used as the screenshot key on platforms that support\n     screenshots. Screenshot support can be disabled in the config file. Other\n     function keys (e.g. F13) are not supported by MegaZeux.\n    </td>\n   </tr>\n\n   <tr>\n    <td class=\"badkey\">&nbsp;</td>\n    <td class=\"annotation\">\n     The results for this value can vary between systems or\n     may not be detected at all by MegaZeux. Personal use only.\n    </td>\n   </tr>\n\n   <tr>\n    <td class=\"capslock\">&nbsp;</td>\n    <td class=\"annotation\">\n     In previous versions of MegaZeux, Caps Lock would be held while enabled.\n     In 2.90, it acts like a regular key.\n    </td>\n   </tr>\n\n   <tr>\n    <td class=\"modifier\">&nbsp;</td>\n    <td class=\"annotation\">\n     Modifier keys have slightly different repeating behavior than regular keys\n     and may trigger special MZX or operating system functionality in\n     conjunction with other keys. The Windows key (aka \"Meta\", \"Super\") is\n     equivalent to the Command key on Mac keyboards, however, the positions of\n     these keys are swapped with the Alt keys (which may be called \"Option\").\n    </td>\n   </tr>\n\n   <tr>\n    <td class=\"altgr\">&nbsp;</td>\n    <td class=\"annotation\">\n     In Wayland and X11, when using keyboard layouts with AltGr (e.g.\n     non-English layouts or US International), right alt will instead have the\n     internal keycode 313 and the PC XT keycode 56.\n    </td>\n   </tr>\n\n   <tr><td>&nbsp;</td></tr>\n\n   <tr>\n    <td></td>\n    <td class=\"annotation\">\n     Game keys can be bypassed using game settings, Robotic, or the config file.\n\n     <table class=\"bypass\">\n      <tr>\n       <th>Enter</th>\n       <td>\n        The game menu can be disabled by setting the <var>ENTER_MENU</var>\n        counter to 0.\n       </td>\n      </tr>\n\n      <tr>\n       <th>Escape</th>\n       <td>\n        The escape menu can be disabled by setting the <var>ESCAPE_MENU</var>\n        counter to 0. The user can still access this menu by pressing Alt+F4.\n       </td>\n      </tr>\n\n      <tr>\n       <th>F1</th>\n       <td>\n        The help menu can be disabled by setting the <var>HELP_MENU</var>\n        counter to 0.\n       </td>\n      </tr>\n\n      <tr>\n       <th>F2</th>\n       <td>\n        The settings menu can be disabled by setting the <var>F2_MENU</var>\n        counter to 0. The user can still access this menu by pressing Ctrl+F2\n        or Alt+F2 unless standalone mode is enabled.\n       </td>\n      </tr>\n\n      <tr>\n       <th>F3/F9</th>\n       <td>\n        Saving can be restricted on a per-board basis with the Board Settings\n        dialog or using the Robotic commands <var>enable saving</var>,\n        <var>disable saving</var>, and <var>enable sensoronly saving</var>.\n       </td>\n      </tr>\n\n      <tr>\n       <th>F4/F10</th>\n       <td>\n        Loading can be disabled by setting the <var>LOAD_MENU</var>\n        counter to 0.\n       </td>\n      </tr>\n\n      <tr>\n       <th>F12</th>\n       <td>\n        Screenshots can be configured by the user with the config file option\n        <var>allow_screenshots</var>.\n       </td>\n      </tr>\n\n      <tr>\n       <th>Space</th>\n       <td>\n        Shooting can be disabled with the board flags <var>Can shoot</var> and\n        <var>Player attack locked</var> or by the Robotic commands\n        <var>lockplayer</var> and <var>lockplayer attack</var>.\n\n        The \"You can't shoot here!\" message can be disabled by setting the\n        <var>BIMESG</var> counter to 0.\n\n        Both shooting and the behavior that prevents the player from moving\n        while space is held can be disabled by setting the <var>SPACELOCK</var>\n        counter to 0.\n       </td>\n      </tr>\n\n      <tr>\n       <th>F5/Insert/Delete</th>\n       <td>\n        Bomb switching and bombing can be disabled with the board flags\n        <var>Can bomb</var> and <var>Player attack locked</var> or by the\n        Robotic commands <var>lockplayer</var> and <var>lockplayer attack</var>.\n\n        The \"You can't bomb here!\" message can be disabled by setting the\n        <var>BIMESG</var> counter to 0.\n       </td>\n      </tr>\n\n      <tr>\n       <th>Arrows</th>\n       <td>\n        Player movement can be disabled with the board flags\n        <var>Player locked N/S</var> and <var>Player locked E/W</var> or by the\n        Robotic commands <var>lockplayer</var>, <var>lockplayer ns</var>, and\n        <var>lockplayer ew</var>.\n\n        The behavior that causes the viewport to snap to the player when the\n        player moves can be disabled with the Robotic command\n        <var>lockscroll</var>.\n       </td>\n      </tr>\n     </table>\n\n    </td>\n   </tr>\n  </table>\n\n  <br><hr><br>\n\n  <h3>License</h3>\n  <p>\n    Copyright &copy; 2010-2025 Lachesis &mdash;\n    <a href=\"https://github.com/AliceLR/megazeux/\">\n      https://github.com/AliceLR/megazeux/\n    </a>\n  </p>\n\n  <p>\n    Permission to use, copy, modify, and distribute this document for any\n    purpose with or without fee is hereby granted, provided that the above\n    copyright notice and this permission notice appear in all copies.\n  </p>\n\n  <p>\n    THE DOCUMENT IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n    WITH REGARD TO THIS DOCUMENT INCLUDING ALL IMPLIED WARRANTIES OF\n    MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n    ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n    ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS DOCUMENT.\n  </p>\n </body>\n</html>\n"
  },
  {
    "path": "docs/macro.txt",
    "content": "Extended macros\nExtended macros allows which takes parameters over several\nlines in this file. Like single line macros, macro_1 through\nmacro_5 correspond to the F6 through F10 keys, but you can\nname these anything, so long as you start it with macro_\n(for instance, macro_sprite is valid). The first line is\nthe definition which states the macro's name and label,\nand all proceeding lines which are indented by a single space\nor tab character are part of the macro body.\n\nLines enclosed in parentheses denote parameters. The format\nis as follows:\n(type param1, param2, param3, ...)\nWhere type describes the kind of parameter and param1, 2, 3,\netc are parameters of that type. For instance, you may type\n\n(string32 first_name, last_name, address)\n\nWhere the user is expected to supply three strings of up to\n32 characters. These strings are referred to as first_name,\nlast_name, and address.\n\nThe following types are valid:\n\nstringN - strings of N characters\nExample: string100\nThe N is not optional.\n\nnumberA-B - an integer value in the range of A to B.\nnumberA - an integer value in the range of 0 to A.\nExamples:\nnumber20-30\nnumber256\n\nchar - a single byte character.\n\ncolor - an MZX style color (includes wildcard variants)\n\nAfter defining all of your parameters, you can write the\ncontents of the actual macro. To insert the value of the\nparameter, put inside the macro its name, enclosed in\nexclamation points. For instance:\n\n* \"~fHello, !firstname! !lastname!.\"\n\nParameters may also have default values. For instance,\n\n(string10 name=Bob, address=Nowhere)\n\nNote that whitespace may not be placed before or after\nthe equals sign, otherwise it'd be considered part of the\nvariable name and the default value respectively.\n\nThe following are valid default values:\n\nFor string: Any sequence of characters.\nFor number: An integer number such as 1234\nFor char: An integer value or a character inside\n single quotes, for instance 200 or 'x'\nFor color: An MZX style color, such as c0F or c?3\n\nIf you don't supply defaults, default defaults are used:\n\nFor string: The empty string\nFor number: The lower boundary of the type\nFor char: 0\nFor color: c??\n\nOnce you write up an extended macro you can use it in the\nrobot editor. F6 through F10 will execute the macros named\nmacro_1 through macro_5 if they exist (even if single line\nversions are defined). This will bring up a GUI window\nwhere you can visually enter all paramter values.\n\nFor other macros, you can enter them via text. All lines\nstarting with  are considered macro invocations. The\nformat is\nmacro_name(param1, param2, param3, ...)\nNote that the parameters can be supplied in one of two\nways, by order or by name. Supplying by order means\nthat the nth by order parameter you enter will\ncorrespond to the nth parameter defined in the macro.\nBy name means that you give the name of the macro along\nwith its value in the following way:\nname=value\nNote that values here follow the same formatting that\ndefault values do.\n\nHere's an example of writing a simple macro then calling\nit textually from the robot editor:\n\nmacro_test = A Test\n (number20 a, b)\n (string32 c)\n set \"!c!\" \"(!a! + !b!)\"\n\nIf the user then types..\n\n#test(1, 2, counter)\n\nThe following code would be produced:\n\nset \"counter\" \"(1 + 2)\"\n\nYou could also type something like:\n\n#test(c=counter)\n\nWhich would output...\n\nset \"counter\" \"(0 + 0)\"\n\nNotice that you don't have to supply every paramter. If you\ndon't supply something for a value, its default will be used.\n\n\nBy pressing alt + m, you can bring up a text box that will\nlet you enter the name of a macro to be edited via GUI.\n"
  },
  {
    "path": "docs/megazeux.1",
    "content": ".TH MEGAZEUX 1 \"2017-11-22\"\n.SH NAME\nMegaZeux - Simple Game Creation System (GCS)\n\n.SH SYNOPSIS\n.B megazeux\n.SH DESCRIPTION\nMegaZeux is a Game Creation System (GCS), inspired by Epic MegaGames' ZZT,\nfirst released by Alexis Janson in 1994.  Originally written for DOS,\nMegaZeux is now available on a wide variety of platforms.\n\n.SH FILES\n.TP\n.B /etc/megazeux-config\nThis file contains setting for \\fBmegazeux\\fP. All of the variables can\nalso be passed as options to the program which will override the settings\nin this file, e.g. \\fBmegazeux fullscreen=1\\fP\n\n.SH AUTHORS\n.B MegaZeux\nwas written by Alexis Janson, Charles Goetzman,\nGilead Kutnick <exophase@gmail.com>,\nAlistair John Strachan <alistair@devzero.co.uk>,\nAlice Rowan <petrifiedrowan@gmail.com>,\nand various other contributors during 1996-2017.\n"
  },
  {
    "path": "docs/mzxhelp.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n<title>MegaZeux 2.93d (20250609) - Help File</title>\n<meta charset=\"UTF-8\">\n<meta name=\"title\" content=\"MegaZeux 2.93d (20250609) - Help File\">\n<meta name=\"twitter:card\" content=\"summary\">\n<meta name=\"twitter:title\" content=\"MegaZeux 2.93d (20250609) - Help File\">\n<style>\n/* MegaZeux\n *\n * Copyright (C) 2012 Alice Rowan <petrifiedrowan@gmail.com>\n * Copyright (C) 2012 Dr. Lancer-X\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * fonts.css: Embeddable MZX fonts.\n */\n\n@font-face\n{\n  font-family: 'mzxfont';\n\n  src: /* mzxfont.eot */\n       url(data:application/vnd.ms-fontobject;charset=utf-8;base64,uoEAAAiBAAABAAIAAAAAAAIABgkAAAAAAAABAPQBAAAAAExQAQAAAEAAABAAAAAAAAAAAAEAAAAAAAAA6VMSAgAAAAAAAAAAAAAAAAAAAAAAABAAbQBlAGcAYQB6AGUAdQB4AAAADABNAGUAZABpAHUAbQAAACAAVgBlAHIAcwBpAG8AbgAgADAAMAAxAC4AMAAwADAAIAAAABIAbQBlAGcAYQB6AGUAdQB4ACAAAAAAAAABAAAADgCAAAMAYEZGVE1hme1gAACA7AAAABxHREVGAToAJAAAgMQAAAAoT1MvMlcsUroAAAFoAAAAYGNtYXDByceQAAAD6AAAAYpjdnQgACECeQAABXQAAAAEZ2FzcP//AAMAAIC8AAAACGdseWbOkpDHAAAHkAAAbVxoZWFk+IPf3AAAAOwAAAA2aGhlYQQ2AUQAAAEkAAAAJGhtdHgPAA19AAAByAAAAh5sb2Nh7tAIzAAABXgAAAIYbWF4cAFpAfEAAAFIAAAAIG5hbWW5N82IAAB07AAAAltwb3N0JecELAAAd0gAAAl0AAEAAAABAAACElPpXw889QAfAjAAAAAAzAhPCwAAAADMCE8LAAD//wFAApoAAAAIAAIAAAAAAAAAAQAAApr//wBaAUAAAAAAAUAAAQAAAAAAAAAAAAAAAAAAAAQAAQAAAQsBwAAcAAAAAAACAAAAAQABAAAAQAAuAAAAAAAEAUAB9AAFAAACigK7AAAAjAKKArsAAAHfADEBAgAAAgAGCQAAAAAAAAAAAAEQAABAAAAAAAAAAABQZkVkAMAAIOD/AjAAAABaApoAAQAAAAEAAAAAAWcB3wAAACAAAQFAACEAAAAAAUAAAAFAAAAAUAAoABQAFAAUABQAZABQAFAAAAAoAGQAFAB4ABQAFAAoABQAFAAUABQAFAAUABQAFAB4AGQAKAAoACgAFAAUABQAFAAUABQAFAAUABQAFABQABQAFAAUABQAFAAUABQAFAAUABQAKAAUABQAFAAUACgAFABQABQAUAAUAAAAZAAUABQAFAAUABQAKAAUABQAUAAoABQAUAAUABQAFAAUABQAFAAUABQAFAAoABQAFAAUABQAKAB4ACgAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAAABQAAAAKAAAAAAAKAAAAAAAAAAUABQAKAAAAAAAFAAAAAAAKAAoABQAFAAAABQAFAAUABQAKAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8ADwAKAAAAHgAeAB4AHgAAAAoAAAAAAAAAAAAAAAAAAAAKAAUACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAFAAUABQAFAAAAAAAeAAAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAB4AAAAAAB4AAAAAAAAAAAAUABQAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAACgAAAAAAAUAAAAFAAAAAAAAAAAAAAAFAAoACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AIwAAABQACgAPABQAAAAAAADAAAAAwAAABwAAQAAAAAAhAADAAEAAAAcAAQAaAAAABYAEAADAAYAfiIaIiAiKyI1IlIiYSKl4B/g////AAAAICIaIiAiKSI1IlIiYSKl4AHgf////+PeSN5D3jveMt4W3gjdxSBqIAsAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGAAABAAAAAAAAAAECAAAAAgAAAAAAAAAAAAAAAAAAAAEAAAMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmAAAAAAAAAABiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQJ5AAAAKgAqACoAKgBMAGwAqgEAAVIBtgHOAgQCOgJyAoYCngKsArgDBANMA2wDwAP6BD4EaASgBM4FDgVEBVYFcgXSBeQGRAZ4BqQG5gceB24HrgfyCC4IfgiUCLgI4AkSCT4JbgmcCfAKIApWCogK1gsACx4LWgt8C8wL+gxSDGQMsAzCDQQNEA0oDVwNjA26DfoOKg5sDqoO2g78DyQPaA+GD6gPzA/0ECwQZBCaENwRDBEwEVYRfhHaEgYSSBJwEoISqhLWEtYS1hLWEtYS1hLWEtYS1hLWEx4TYBOmFBQUdhTMFO4VGBVWFY4V/hZOFoIWqBboFxgXSBeMF7AXzhhkGIYYzBj0GRwZRhlwGbYZ/BocGjwaYhqoGu4bLBtqG2obahtqG2objhuwG9Qb+BwyHH4c1h0oHeId7h36HgYeEh6yHtwfJB90H7YgFiBkILIhACFOIagh2iIUIjAiUCKKIv4jaiPcJGIk3iVuJfomXiaiJwwndiiwKuwsHiwsLD4sPiw+LD4sPixaLG4shiyeLJ4sniyuLL4s0CziLPQtAC0WLRYtFi0wLUotaC2ILawtvi3sLewt7C3sLewt7C3sLewt7C3sLewt/C4MLhouKC40LkIuUC6SLugvFi9sL5ovyC/0MCAwxjE0MXwx8jKuMtwzCDM0M2IzojPWNBY0dDSyNTI1djW4NcQ11jYWNkI2djaCNq4AAgAhAAABKgKaAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDtMgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIREnMxEjIQEJ6MfHApr9ZiECWAAAAgBQAHcA8AHfAAMAFQAANzUzFQM1MxUUFjsBFSMVIzUjNTMyNnhQUFAFDxQoUCgUDwV3UFABVBQUDwV4UFB4BQAAAAACACgBZwEYAgcACQATAAATNTMVIyIGFAYjJzUzFSMiJjQmI8hQFA8FBQ+0UBQPBQUPAWegeAUeBSh4oAUeBQAAAgAUAHcBLAHfACsALwAAEzUzFTM1MxUzMhYUBisBFTMyFhQGKwEVIzUjFSM1IyImNDY7ATUjIiY0NjMXNSMVPFAoUBQPBQUPFBQPBQUPFFAoUBQPBQUPFBQPBQUPjCgBj1BQUFAFHgV4BR4FUFBQUAUeBXgFHgWgeHgAAAEAFAAnASwCLwBFAAATNTMVMzIWFBY7ARUjIiY0JiImPQEjFTMVFBY7ARUjIgYUBisBFSM1IzU0JisBNTMyFhQWMhYdATM1IzU0JisBNTMyNj0BjFAUDwUFDxQUDwUFHgV4oAUPFBQPBQUPFFBQBQ8UFA8FBR4FeKAFDxQUDwUB31BQBR4FUAUeBQUPFHgUDwV4BR4FUFAUDwVQBR4FBQ8UeBQPBXgFDxQAAAADABQAdwEsAY8AAwA3ADsAADc1MxUmNDY7ARUjIgYUBiIGFAYiBhQGIgYUBiIGHQEjNTQ2MjY0NjI2NDYyNjQ2MjY0NjI2NDYyBzUzFdxQKAUPFBQPBQUeBQUeBQUeBQUeBVAFHgUFHgUFHgUFHgUFHgUFHutQd1BQ9R4FUAUeBQUeBQUeBQUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeBShQUAAAAAQAFAB3ASwB3wA3ADsAQwBNAAATNTMVFBY7ARUjIgYdATMVFAYrARUzMhYdASM1NCYiBh0BIzU0JisBNTMyNjQ2MjY0JisBNTMyNhc1IxUWNCYiBhQWMgY0JisBFTM1IyJkeAUPFBQPBVAFDxQUDwVQBR4FeAUPFBQPBQUeBQUPFBQPBVAoUAUeBQUeSwUPFFAUDwHLFBQPBVAFDxQUDwV4BQ8UFA8FBQ8UFA8FeAUeBQUeBVAFVVBQSx4FBR4FIx4FeFAAAAAAAQBkAWcA3AIHAA0AABM1MxUjIgYdASM1NDYzjFAUDwVQBQ8Bj3h4BQ8UFA8FAAAAAQBQAHcA8AHfACcAABM1MxUUBiIGFAYrARUzMhYUFjIWHQEjNTQmIiY0JisBNTMyNjQ2MjagUAUeBQUPFBQPBQUeBVAFHgUFDxQUDwUFHgUByxQUDwUFHgXIBR4FBQ8UFA8FBR4FyAUeBQUAAQBQAHcA8AHfACcAABM1MxUUFjIWFBY7ARUjIgYUBiIGHQEjNTQ2MjY0NjsBNSMiJjQmIiZQUAUeBQUPFBQPBQUeBVAFHgUFDxQUDwUFHgUByxQUDwUFHgXIBR4FBQ8UFA8FBR4FyAUeBQUAAQAAAMcBQAGPACsAABM1MxUzNTMVFAYiBh0BMxUjFRQWMhYdASM1IxUjNTQ2MjY9ASM1MzU0JiImKFBQUAUeBVBQBR4FUFBQBR4FUFAFHgUBexQoKBQPBQUPFCgUDwUFDxQoKBQPBQUPFCgUDwUFAAEAKADHARgBjwALAAATNTMVMxUjFSM1IzV4UFBQUFABP1BQKFBQKAABAGQATwDcAO8ADQAANzUzFSMiBh0BIzU0NjOMUBQPBVAFD3d4eAUPFBQPBQAAAAABABQBFwEsAT8AAwAAEzUhFRQBGAEXKCgAAAAAAQB4AHcAyADHAAMAADc1MxV4UHdQUAAAAQAUAJ8BLAHfADcAAAA0NjsBFSMiBhQGIgYUBiIGFAYiBhQGIgYUBiIGFAYrATUzMjY0NjI2NDYyNjQ2MjY0NjI2NDYyAQQFDxQUDwUFHgUFHgUFHgUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgG8HgVQBR4FBR4FBR4FBR4FBR4FBR4FUAUeBQUeBQUeBQUeBQUeBQADABQAdwEsAd8AFwAmADQAABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhc1IxUzMjY0NjI2NDYyNhU1IyIGFAYiBhQGKwEVPMgFDxQUDwXIBQ8UFA8FoHgUDwUFHgUFHgUUDwUFHgUFDxQByxQUDwX+6AUPFBQPBQEYBRkUeAUeBQUeBQX1oAUeBQUeBVAAAAABACgAdwEYAd8AEwAAEzUzETMVIzUzNSM1NDYyNjQ2MjZ4UFDwUFAFHgUFHgUByxT+wCgoyBQPBQUeBQUAAAAAAQAUAHcBLAHfAD8AABM1MxUUFjsBFSMiBhQGIgYUBiIGFAYiBhQGIgYdATM1MxUhNTMyNjQ2MjY0NjI2NDYyNjQ2OwE1IxUjNTQ2MjY8yAUPFBQPBQUeBQUeBQUeBQUeBXhQ/ugUDwUFHgUFHgUFHgUFDxR4UAUeBQHLFBQPBVAFHgUFHgUFHgUFHgUFDxQoUFAFHgUFHgUFHgUFHgVQKBQPBQUAAAAAAQAUAHcBLAHfAC0AABM1MxUUFjsBFSMiBhQWOwEVIyIGHQEjNTQmIiY9ATMVMzUjNTM1IxUjNTQ2MjY8yAUPFBQPBQUPFBQPBcgFHgVQeHh4eFAFHgUByxQUDwV4BR4FeAUPFBQPBQUPFCh4KHgoFA8FBQACABQAdwEsAd8AKAAzAAATNTMVMzIWFAYrARUzMhYdASM1NDY7ATUjNTMyNjQ2MjY0NjI2NDYyNhU1IyIGFAYiBh0BtFAUDwUFDxQUDwWgBQ8UoBQPBQUeBQUeBQUeBRQPBQUeBQHLFMgFHgVQBQ8UFA8FUFAFHgUFHgUFHgUFpVAFHgUFDxQAAQAUAHcBLAHfAB0AABM1IRUjFTMVFBY7ARUjIgYdASM1NCYiJj0BMxUzNRQBGMigBQ8UFA8FyAUeBVB4ARfIKHgUDwV4BQ8UFA8FBQ8UKHgAAAAAAgAUAHcBLAHfACQAKAAAEzUzFSMVFAYrARUzFRQWOwEVIyIGHQEjNTQmKwE1MzI2NDYyNhM1IxVkeFAFDxSgBQ8UFA8FyAUPFBQPBQUeBXh4AcsUKBQPBVAUDwV4BQ8UFA8F8AUeBQX+43h4AAAAAAEAFAB3ASwB3wAfAAATNSEVIyIGFAYiBhQGKwEVIzUzMjY0NjI2NDY7ATUjFRQBGBQPBQUeBQUPFFAUDwUFHgUFDxR4AY9QeAUeBQUeBaCgBR4FBR4FUCgAAAAAAwAUAHcBLAHfACcAKwAvAAATNTMVFBY7ARUjIgYUFjsBFSMiBh0BIzU0JisBNTMyNjQmKwE1MzI2FzUjFRc1IxU8yAUPFBQPBQUPFBQPBcgFDxQUDwUFDxQUDwWgeHh4AcsUFA8FeAUeBXgFDxQUDwV4BR4FeAV9eHigeHgAAAACABQAdwEsAd8AJAAoAAATNTMVFBY7ARUjIgYUBiIGHQEjNTM1NDY7ATUjNTQmKwE1MzI2FzUjFTzIBQ8UFA8FBR4FoHgFDxSgBQ8UFA8FoHgByxQUDwXwBR4FBQ8UKBQPBVAUDwV4BX14eAACAHgAnwDIAbcAAwAHAAA3NTMVJzUzFXhQUFCfUFDIUFAAAAACAGQAdwDcAbcADQARAAA3NTMVIyIGHQEjNTQ2Mzc1MxWMUBQPBVAFDxRQn1BQBQ8UFA8FyFBQAAEAKAB3ARgB3wBHAAATNTMVFAYiBhQGIgYUBiIGFAYiBhQWMhYUFjIWFBYyFhQWMhYdASM1NCYiJjQmIiY0JiImNCYiJjQ2MjY0NjI2NDYyNjQ2MjbIUAUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBVAFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUByxQUDwUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFAAIAKADHARgBZwADAAcAADc1MxUnNTMVKPDw8McoKHgoKAAAAAEAKAB3ARgB3wBHAAATNTMVFBYyFhQWMhYUFjIWFBYyFhQGIgYUBiIGFAYiBhQGIgYdASM1NDYyNjQ2MjY0NjI2NDYyNjQmIiY0JiImNCYiJjQmIiYoUAUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBVAFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUByxQUDwUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFAAIAFAB3ASwB3wADACUAADc1MxUDNTMVFBY7ARUjIgYUBisBFSM1MzI2NDY7ATUjFSM1MzI2jFCgyAUPFBQPBQUPFFAUDwUFDxR4UBQPBXdQUAFUFBQPBVAFHgVQUAUeBVBQUAUAAAAAAQAUAHcBLAHfAB8AABM1MxUUFjsBFSMiBh0BIzUzNSMRMxUjNTQmKwERMzI2PMgFDxQUDwV4UHigyAUPFBQPBQHLFBQPBcgFDxSgUP7oKBQPBQEYBQAAAAIAFAB3ASwB3wAhAC8AABI0NjIWFBYyFhQWMhYUFjsBFSM1IxUjNTMyNjQ2MjY0NjIWNCYiBhQGKwEVMzUjIowFHgUFHgUFHgUFDxRQeFAUDwUFHgUFHi0FHgUFDxR4FA8BvB4FBR4FBR4FBR4F8Hh48AUeBQUeBUseBQUeBVBQAAAAAAMAFAB3ASwB3wAfACMAJwAAEzUzFRQWOwEVIyIGFBY7ARUjIgYdASM1NDY7AREjIiYXNSMVFzUjFRTwBQ8UFA8FBQ8UFA8F8AUPFBQPBchQUFAByxQUDwV4BR4FeAUPFBQPBQEYBX14eKB4eAAAAAABABQAdwEsAd8APwAAEzUzFRQWOwEVIyImNCYiJj0BIxUUBisBFTMyFh0BMzU0NjI2NDY7ARUjIgYdASM1NCYiJjQmKwE1MzI2NDYyNmSgBQ8UFA8FBR4FUAUPFBQPBVAFHgUFDxQUDwWgBR4FBQ8UFA8FBR4FAcsUFA8FUAUeBQUPFBQPBcgFDxQUDwUFHgVQBQ8UFA8FBR4FyAUeBQUAAgAUAHcBLAHfAB8ALwAAEzUzFRQWMhYUFjsBFSMiBhQGIgYdASM1NDY7AREjIiYWNCYrAREzMjY0NjsBNSMiFMgFHgUFDxQUDwUFHgXIBQ8UFA8FoAUPFBQPBQUPFBQPAcsUFA8FBR4FyAUeBQUPFBQPBQEYBSgeBf7oBR4FyAABABQAdwEsAd8AMwAAEzUhFSMiJjQmIiY9ASMVMzI2NDY7ARUjIiY0JisBFTM1NDYyNjQ2OwEVITU0NjsBESMiJhQBGBQPBQUeBVAUDwUFDxQUDwUFDxRQBR4FBQ8U/ugFDxQUDwUByxR4BR4FBQ8UeAUeBXgFHgV4FA8FBR4FeBQPBQEYBQAAAQAUAHcBLAHfAC0AABM1IRUjIiY0JiImPQEjFTMyNjQ2OwEVIyImNCYrARUzMhYdASM1NDY7AREjIiYUARgUDwUFHgVQFA8FBQ8UFA8FBQ8UFA8FoAUPFBQPBQHLFHgFHgUFDxR4BR4FeAUeBXgFDxQUDwUBGAUAAQAUAHcBLAHfAD4AABM1MxUUFjsBFSMiJjQmIiY9ASMVFAYrARUzMhYdATM1IzUzFSMiJjQmIgYdASM1NCYiJjQmKwE1MzI2NDYyNmSgBQ8UFA8FBR4FUAUPFBQPBVBQoBQPBQUeBXgFHgUFDxQUDwUFHgUByxQUDwVQBR4FBQ8UFA8FyAUPFFAooAUeBQUPFBQPBQUeBcgFHgUFAAAAAAEAFAB3ASwB3wALAAA3ETMVMzUzESM1IxUUUHhQUHh3AWigoP6YoKAAAAAAAQBQAHcA8AHfABcAABM1MxUUBisBETMyFh0BIzU0NjsBESMiJlCgBQ8UFA8FoAUPFBQPBQHLFBQPBf7oBQ8UFA8FARgFAAAAAQAUAHcBLAHfABsAABM1MxUUBisBESMiBh0BIzU0JisBNTMVMxEjIiaMoAUPFBQPBaAFDxRQUBQPBQHLFBQPBf7oBQ8UFA8FUFABGAUAAAABABQAdwEsAd8AJQAAEzUzFTM1MzUzFSMVIyIGFBY7ARUzFSM1IzUjFSM1NDY7AREjIiYUeCgoUCgUDwUFDxQoUCgoeAUPFBQPBQHLFKBQUFBQBR4FUFBQUKAUDwUBGAUAAAAAAQAUAHcBLAHfAB0AABM1MxUUBisBETM1NDYyNjQ2OwEVITU0NjsBESMiJhSgBQ8UUAUeBQUPFP7oBQ8UFA8FAcsUFA8F/ugUDwUFHgV4FA8FARgFAAAAAAEAFAB3ASwB3wAhAAA3ETMVFBYyFhQWMjY0NjI2PQEzESM1IyIGFAYiJjQmKwEVFFAFHgUFHgUFHgVQUBQPBQUeBQUPFHcBaBQPBQUeBQUeBQUPFP6YyAUeBQUeBcgAAAEAFAB3ASwB3wAgAAA3ETMVFBYyFhQWMhYUFjsBNTMRIzUjIiY0JiImNCYrARUUUAUeBQUeBQUPFFBQFA8FBR4FBQ8UdwFoFA8FBR4FBR4FeP6YeAUeBQUeBcgAAgAUAHcBLAHfACcAPwAAEzUzFRQWMhYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNhY0JiIGFAYrARUzMhYUFjI2NDY7ATUjImR4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBVAFHgUFDxQUDwUFHgUFDxQUDwHLFBQPBQUeBcgFHgUFDxQUDwUFHgXIBR4FBSgeBQUeBcgFHgUFHgXIAAAAAgAUAHcBLAHfAB4AIgAAEzUzFRQWOwEVIyIGHQEjFTMyFh0BIzU0NjsBESMiJhc1IxUU8AUPFBQPBXgUDwWgBQ8UFA8FyFAByxQUDwV4BQ8UeAUPFBQPBQEYBX14eAAAAgAUAE8BLAHfABsAJwAAEzUzFRQWOwEVIxUzMhYdASM1IzU0JisBNTMyNhc1IxUzNTMyFhQWMzzIBQ8UKBQPBXh4BQ8UFA8FoHgoFA8FBQ8ByxQUDwXwUAUPFFAUDwXwBc3I8FAFHgUAAAAAAgAUAHcBLAHfACAAJAAAEzUzFRQWOwEVIxUzFSM1IyImNCYrARUjNTQ2OwERIyImFzUjFRTwBQ8UKChQFA8FBQ8UeAUPFBQPBchQAcsUFA8FeFB4eAUeBaAUDwUBGAV9eHgAAAEAFAB3ASwB3wA/AAATNTMVFBY7ARUjNSMVMzIWHQEzFRQWMhYUFjsBFSMiBh0BIzU0JisBNTMVMzUjIiY9ASM1NCYiJjQmKwE1MzI2PMgFDxRQeBQPBVAFHgUFDxQUDwXIBQ8UUHgUDwVQBR4FBQ8UFA8FAcsUFA8FUFBQBQ8UFA8FBR4FUAUPFBQPBVBQUAUPFBQPBQUeBVAFAAEAKAB3ARgB3wAdAAATNTMVIyImNCYrARUzMhYdASM1NDY7ATUjIgYUBiMo8BQPBQUPFBQPBaAFDxQUDwUFDwFneHgFHgXwBQ8UFA8F8AUeBQAAAAEAFAB3ASwB3wARAAA3ETMRMxEzESMiBh0BIzU0JiMUUHhQFA8FyAUPnwFA/sABQP7ABQ8UFA8FAAAAAAEAFAB3ASwB3wArAAA3NTMVMzIWFBYyNjQ2OwE1MxUjIgYUBiIGFAYiBhQGIiY0JiImNCYiJjQmIxRQFA8FBR4FBQ8UUBQPBQUeBQUeBQUeBQUeBQUeBQUP7/DwBR4FBR4F8PAFHgUFHgUFHgUFHgUFHgUFHgUAAAEAFAB3ASwB3wAXAAA3ETMVMzUzFTM1MxEjFSM1NCYiBh0BIzUUUCgoKFAoUAUeBVDHARjwUFDw/uhQFA8FBQ8UUAAAAQAUAHcBLAHfAD8AABM1MxUzMhYUFjI2NDY7ATUzFSMiBhQGKwEVMzIWFBY7ARUjNSMiJjQmIgYUBisBFSM1MzI2NDY7ATUjIiY0JiMUUBQPBQUeBQUPFFAUDwUFDxQUDwUFDxRQFA8FBR4FBQ8UUBQPBQUPFBQPBQUPAY9QUAUeBQUeBVBQBR4FeAUeBVBQBR4FBR4FUFAFHgV4BR4FAAEAKAB3ARgB3wAhAAATNTMVMzUzFSMiBhQGKwEVMzIWHQEjNTQ2OwE1IyImNCYjKFBQUBQPBQUPFBQPBaAFDxQUDwUFDwE/oKCgoAUeBXgFDxQUDwV4BR4FAAAAAQAUAHcBLAHfAEIAABM1IRUjIgYUBiIGFAYiBhQGIgYUBisBFTM1NDYyNjQ2OwEVITUzMjY0NjI2NDYyNjQ2MjY0NjI2PQEjFRQGIgYUBiMUARgUDwUFHgUFHgUFHgUFDxR4BR4FBQ8U/ugUDwUFHgUFHgUFHgUFHgV4BR4FBQ8BZ3hQBR4FBR4FBR4FBR4FUBQPBQUeBXh4BR4FBR4FBR4FBR4FBQ8UFA8FBR4FAAABAFAAdwDwAd8ABwAANxEzFSMRMxVQoFBQdwFoKP7oKAAAAAABABQAdwEsAd8ANwAAEzUzMhYUFjIWFBYyFhQWMhYUFjIWFBYyFhQWOwEVIyImNCYiJjQmIiY0JiImNCYiJjQmIiY0JiMUFA8FBR4FBR4FBR4FBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FBQ8BZ3gFHgUFHgUFHgUFHgUFHgUFHgV4BR4FBR4FBR4FBR4FBR4FBR4FAAEAUAB3APAB3wAHAAATNTMRIzUzEVCgoFABtyj+mCgBGAAAAAEAFAGPASwCLwAvAAASNDYyFhQWMhYUFjIWFBYyFh0BIzU0JiImNCYiBhQGIgYdASM1NDYyNjQ2MjY0NjKMBR4FBR4FBR4FBR4FUAUeBQUeBQUeBVAFHgUFHgUFHgIMHgUFHgUFHgUFHgUFDxQUDwUFHgUFHgUFDxQUDwUFHgUFHgUAAAEAAAB3AUAAnwADAAA9ASEVAUB3KCgAAAEAZAG3ANwCLwANAAATNTMVMzIWHQEjNTQmI2RQFA8FUAUPAd9QUAUPFBQPBQAAAAIAFAB3ASwBZwAjACcAABM1MxUUFjsBFTMyFh0BIzU0JiIGHQEjNTQmKwE1MzI2PQEzNRU1IxU8oAUPFBQPBVAFHgV4BQ8UFA8FeFABPygUDwWgBQ8UFA8FBQ8UFA8FUAUPFCigUFAAAgAUAHcBLAHfABgAIgAAEzUzFTMVFBYyFhQWOwEVIyIGHQEjESMiJhY0JisBFTM1IyIUeFAFHgUFDxQUDwXIFA8FoAUPFFAUDwHLFHgUDwUFHgV4BQ8UAUAFoB4FoHgAAQAUAHcBLAFnACEAABM1MxUUFjIWHQEjNSMVMzUzFRQGIgYdASM1NCYrATUzMjY8yAUeBVB4eFAFHgXIBQ8UFA8FAVMUFA8FBQ8UKKAoFA8FBQ8UFA8FoAUAAAACABQAdwEsAd8AJQAvAAATNTMRMzIWHQEjNTQmIgYdASM1NCYrATUzMjY0NjI2PQEzNSMiJhM1IyIGFAYrARWMeBQPBVAFHgV4BQ8UFA8FBR4FUBQPBSgUDwUFDxQByxT+wAUPFBQPBQUPFBQPBXgFHgUFDxRQBf7joAUeBXgAAAIAFAB3ASwBZwAeACIAABM1MxUUFjsBFSMVMzUzFRQGIgYdASM1NCYrATUzMjYXNSMVPMgFDxTIeFAFHgXIBQ8UFA8FoHgBUxQUDwVQUCgUDwUFDxQUDwWgBS0oKAAAAAEAKAB3ARgB3wAzAAATNTMVFBY7ARUjIiY0JiImNCYrARUzMhYUBisBFTMyFh0BIzU0NjsBNSMiJjQ2OwE1MzI2eHgFDxQUDwUFHgUFDxQUDwUFDxQUDwWgBQ8UFA8FBQ8UFA8FAcsUFA8FUAUeBQUeBXgFHgV4BQ8UFA8FeAUeBXgFAAIAFAAnASwBZwArAC8AABM1MxUUFjI2PQEzFRQGKwEVIyIGHQEjNTQmIiY9ATMVMzUjNTQmKwE1MzI2FzUjFTx4BR4FUAUPFBQPBaAFHgVQUHgFDxQUDwV4UAFTFBQPBQUPFBQPBfAFDxQUDwUFDxQoUBQPBXgFfXh4AAABABQAdwEsAd8AIwAAEzUzFTMyNj0BMxUUFjsBFSM1IyIGFAYrARUjNTQ2OwERIyImFHgUDwVQBQ8UUBQPBQUPFHgFDxQUDwUByxSgBQ8UFA8FyMgFHgWgFA8FARgFAAACAFAAdwDwAd8AEgAWAAATNTMVMzIWHQEjNTQ2OwE1IyImNzUzFVB4FA8FoAUPFBQPBShQAVMUyAUPFBQPBaAFS1BQAAIAKAAnARgB3wAWABoAABM1MxEjIgYdASM1NCYrATUzFTM1IyImNzUzFaB4FA8FoAUPFFBQFA8FKFABUxT+6AUPFBQPBVBQ8AVLUFAAAAAAAQAUAHcBLAHfADMAABM1MxUzMjY0NjI2PQEzFRQGIgYUBiIGFBYyFhQWOwEVIzUjIiY0JisBFSM1NDY7AREjIiYUeBQPBQUeBVAFHgUFHgUFHgUFDxRQFA8FBQ8UeAUPFBQPBQHLFMgFHgUFDxQUDwUFHgUFHgUFHgVQUAUeBXgUDwUBGAUAAAEAUAB3APAB3wASAAATNTMRMzIWHQEjNTQ2OwERIyImUHgUDwWgBQ8UFA8FAcsU/sAFDxQUDwUBGAUAAAEAFAB3ASwBZwAYAAA3NTMVFBYyNj0BMxUUFjsBFSM1IxUjNSMVFHgFHgVQBQ8UUCgoKHfwFA8FBQ8UFA8FyKB4eKAAAQAUAHcBLAFnABkAABM1MxUUFjI2PQEzFRQWOwEVIzUjFSM1IyImFFAFHgV4BQ8UUFBQFA8FAVMUFA8FBQ8UFA8FyMjIyAUAAgAUAHcBLAFnABcAGwAAEzUzFRQWOwEVIyIGHQEjNTQmKwE1MzI2FzUjFTzIBQ8UFA8FyAUPFBQPBaB4AVMUFA8FoAUPFBQPBaAFpaCgAAACABQAJwEsAWcAJgAqAAATNTMVFBYyNj0BMxUUFjsBFSMiBh0BIxUzMhYdASM1NDY7ATUjIiYXNSMVFFAFHgV4BQ8UFA8FeBQPBaAFDxQUDwXIUAFTFBQPBQUPFBQPBXgFDxRQBQ8UFA8F8AV9eHgAAgAUACcBLAFnACYAKgAAEzUzFRQWMjY9ATMVFAYrARUzMhYdASM1NDY7ATUjNTQmKwE1MzI2FzUjFTx4BR4FUAUPFBQPBaAFDxR4BQ8UFA8FeFABUxQUDwUFDxQUDwXwBQ8UFA8FUBQPBXgFfXh4AAEAFAB3ASwBZwApAAATNTMVFBYyNj0BMxUUFjsBFSM1IyIGFAYrARUzMhYdASM1NDY7ATUjIiYUUAUeBXgFDxRQFA8FBQ8UFA8FoAUPFBQPBQFTFBQPBQUPFBQPBVBQBR4FeAUPFBQPBaAFAAEAFAB3ASwBZwAzAAATNTMVFBYyFh0BIzUjFTMVMxUUFjIWFAYiBh0BIzU0JiImPQEzFTM1IzUjNTQmIiY0NjI2PMgFHgVQeFBQBR4FBR4FyAUeBVB4UFAFHgUFHgUBUxQUDwUFDxQoKCgUDwUFHgUFDxQUDwUFDxQoKCgUDwUFHgUFAAEAFAB3ASwB3wAjAAASNDY7ARUzFSMVMzI2PQEzFRQGIgYdASM1NCYrATUjNTM1MzKMBQ8UUFAUDwVQBR4FeAUPFFBQFA8BvB4FeCigBQ8UFA8FBQ8UFA8FoChQAAAAAAEAFAB3ASwBZwAZAAA3NTMVMzUzFTMyFh0BIzU0JiIGHQEjNTQmIxRQUFAUDwVQBR4FeAUPn8jIyMgFDxQUDwUFDxQUDwUAAAEAKAB3ARgBZwAZAAA3NTMVMzUzFSMiBhQGIgYdASM1NCYiJjQmIyhQUFAUDwUFHgVQBR4FBQ/HoKCgoAUeBQUPFBQPBQUeBQAAAAABABQAdwEsAWcAHQAANzUzFTM1MxUzNTMVIyIGHQEjNTQmIgYdASM1NCYjFFAoKChQFA8FUAUeBVAFD5/IoFBQoMgFDxQUDwUFDxQUDwUAAAEAFAB3ASwBZwBHAAATNTMVFBYyFhQWMjY0NjI2PQEzFRQGIgYUBisBFTMyFhQWMhYdASM1NCYiJjQmIgYUBiIGHQEjNTQ2MjY0NjsBNSMiJjQmIiYUUAUeBQUeBQUeBVAFHgUFDxQUDwUFHgVQBR4FBR4FBR4FUAUeBQUPFBQPBQUeBQFTFBQPBQUeBQUeBQUPFBQPBQUeBVAFHgUFDxQUDwUFHgUFHgUFDxQUDwUFHgVQBR4FBQABABQAJwEsAWcAHwAANzUzFTM1MxUjIgYUBiIGHQEjNTM1NDYyNj0BIzU0JiMUUHhQFA8FBR4FyKAFHgWgBQ/HoKCg8AUeBQUPFCgUDwUFDxQUDwUAAAAAAQAUAHcBLAFnAC8AABM1IRUUBiIGFAYiBhQGIgYUBiIGHQEzNTMVITU0NjI2NDYyNjQ2MjY0NjI2PQEjFRQBGAUeBQUeBQUeBQUeBVBQ/ugFHgUFHgUFHgUFHgVQARdQFA8FBR4FBR4FBR4FBQ8UKFAUDwUFHgUFHgUFHgUFDxQoAAAAAQAoAHcBGAHfAB0AABM1MxUjFSMiBhQWOwEVMxUjNTQmKwE1IzUzNTMyNqB4UBQPBQUPFFB4BQ8UUFAUDwUByxQoeAUeBXgoFA8FeCh4BQACAHgAdwDIAd8AAwAHAAA3NTMVJzUzFXhQUFB3oKDIoKAAAAABACgAdwEYAd8AHQAAEzUzFRQWOwEVMxUjFSMiBh0BIzUzNTMyNjQmKwE1KHgFDxRQUBQPBXhQFA8FBQ8UAbcoFA8FeCh4BQ8UKHgFHgV4AAEAFAGPASwB3wAfAAATNTMVFBYyNj0BMxUUBiIGHQEjNTQmIgYdASM1NDYyNjx4BR4FUAUeBXgFHgVQBR4FAcsUFA8FBQ8UFA8FBQ8UFA8FBQ8UFA8FBQAFAAAATwFAAd8ADwATABcALwAzAAA3NTMVFAYiBh0BIzU0JiImNzUzFSM1MxUnNTMVFBY7AREjIgYdASM1NCYrAREzMjYTESMRUKAFHgVQBR4FeCigKFDwBQ8UFA8F8AUPFBQPBfDw2xQUDwUFDxQUDwUFc1BQUFCMFBQPBf7ABQ8UFA8FAUAF/rsBQP7AAAAEAAAATwFAAd8AFwAbAB8ALwAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzUjFTM1IxUXNSMVFBYyFh0BMzU0NjI2KPAFDxQUDwXwBQ8UFA8FUCigKCigBR4FUAUeBQHLFBQPBf7ABQ8UFA8FAUAFfVBQUFBkFBQPBQUPFBQPBQUAAAACABQAdwEsAbcALwAzAAATNTMVFBYyNj0BMxUUFjsBFSMiBhQGIgYUBiIGFAYiJjQmIiY0JiImNCYrATUzMjYXNSMVPFAFHgVQBQ8UFA8FBR4FBR4FBR4FBR4FBR4FBQ8UFA8FUCgBoxQUDwUFDxQUDwWgBR4FBR4FBR4FBR4FBR4FBR4FoAVVUFAAAAMAFACfASwBtwA3AEcATwAAEjQ2MhYUFjIWFBYyFhQWMhYUBiIGFAYiBhQGIgYUBiImNCYiJjQmIiY0JiImNDYyNjQ2MjY0NjIWNCYiBhQGIgYUFjI2NDYyFjQmIgYUFjKMBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4tBR4FBR4FBR4FBR4tBR4FBR4BlB4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FIx4FBR4FBR4FBR4Fcx4FBR4FAAADABQATwEsAgcAOwBDAEsAABM1MxUUFjsBFTMVIxUUFjsBFTMyFh0BIzUjFSM1MzI2NDYyNjQmIiY0JiImNDYyNjQ2MjY0JisBNTMyNhY0JiIGFBYyFjQmIgYUFjJkUAUPFFBQBQ8UFA8FeFBQFA8FBR4FBR4FBR4FBR4FBR4FBQ8UFA8FKAUeBQUeLQUeBQUeAfMUFA8FeHgUDwVQBQ8UUFBQBR4FBR4FBR4FBR4FBR4FBR4FUAUoHgUFHgWbHgUFHgUAAgAAAHcBQAIHADkAQQAAEzUzMhYUFjIWFBYyFhQWMhYUFjsBFSMiBh0BIxUzMhYdASM1NDY7ATUjNTQmKwE1MzI2NDYyNjQ2MxY0JiIGFBYyeBQPBQUeBQUeBQUeBQUPFBQPBVAUDwWgBQ8UUAUPFBQPBQUeBQUPPAUeBQUeAbdQBR4FBR4FBR4FBR4FUAUPFFAFDxQUDwVQFA8FUAUeBQUeBSMeBQUeBQAAAAEAUADHAPABZwAXAAATNTMVFBY7ARUjIgYdASM1NCYrATUzMjZ4UAUPFBQPBVAFDxQUDwUBUxQUDwVQBQ8UFA8FUAUAAgAA//8BQAIvAAMAGwAAFREhEQM1IxUUBisBFTMyFh0BMzU0NjsBNSMiJgFAeFAFDxQUDwVQBQ8UFA8FAQIw/dABVBQUDwVQBQ8UFA8FUAUAAAAAAgAoAJ8BGAGPABcALwAAEzUzFRQWOwEVIyIGHQEjNTQmKwE1MzI2FzUjFRQGKwEVMzIWHQEzNTQ2OwE1IyImUKAFDxQUDwWgBQ8UFA8FeFAFDxQUDwVQBQ8UFA8FAXsUFA8FoAUPFBQPBaAFGRQUDwVQBQ8UFA8FUAUAAAIAAP//AUACLwADACgAABURIREDNSMVFAYrARUzFTMyFhQGIgYdATM1IyImNDYyNjQ2OwE1IyImAUB4UAUPFCgUDwUFHgVQFA8FBR4FBQ8UFA8FAQIw/dABpBQUDwVQeAUeBQUPFHgFHgUFHgVQBQACAAAATwFAAgcARgBWAAATNTMVFBY7ARUjIgYUBiIGFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjY9ATMVFBYyNjQ2OwE1IxUUBiImNDYyNgY0JiIGFAYiBhQWMjY0NjKgeAUPFBQPBQUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UeAUeBQUeBSgFHgUFHgUFHgUFHgHzFBQPBXgFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgUDwUFHgUFyB4FBR4FBR4FBR4FAAAAAAIAKAAnARgCBwA3ADsAABM1MxUUFjsBFSMiBhQGIgYdATMVFAYiBhQGIgYUFjIWFAYiBhQGIgYUBisBESMiJjQmKwE1MzI2FzUjFVCgBQ8UFA8FBR4FUAUeBQUeBQUeBQUeBQUeBQUPFBQPBQUPFBQPBXhQAfMUFA8FeAUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeBQEYBR4FeAV9eHgAAAACAAAAdwFAAd8AHAAkAAA3NTMVFAYiBhQGIgYdASMVIyIGHQEjNTMyNjQ2MzY0JiIGFBYyUPAFHgUFHgVQFA8FeBQPBQUPjAUeBQUe7/AUDwUFHgUFDxTIBQ8UUAUeBaUeBQUeBQAAAAIAAABPAUAB3wAVABkAADcRIREjIgYdASM1MzUjFSMiBh0BIzU3NSMVKAEYFA8FUCh4FA8FUPB4xwEY/sAFDxR4ePAFDxR4yCgoAAACAAAAnwFAAbcALwAzAAATNTMVMzI2PQEzFSMVMxUjFTMVIzU0JisBFSM1IyIGHQEjNTM1IzUzNSM1MxUUFjMXNSMVeFAUDwVQUFBQUFAFDxRQFA8FUFBQUFBQBQ9kUAFnUFAFDxQoKCgoKBQPBVBQBQ8UKCgoKCgUDwVQKCgAAAEAFAB3ASwB3wAhAAA3ETMyFhQWMhYUFjIWHQEzFTMVIxUjFRQGIgYUBiIGFAYjFBQPBQUeBQUeBVBQUFAFHgUFHgUFD3cBaAUeBQUeBQUPFCgoKBQPBQUeBQUeBQAAAAEAFAB3ASwB3wAhAAAANDY7AREjIiY0JiImNCYiJj0BIzUjNTM1MzU0NjI2NDYyAQQFDxQUDwUFHgUFHgVQUFBQBR4FBR4BvB4F/pgFHgUFHgUFDxQoKCgUDwUFHgUAAAEAKAB3ARgB3wAzAAATNTMVFBYyFhQWMhYdASMVMxUUBiIGFAYiBh0BIzU0JiImNCYiJj0BMzUjNTQ2MjY0NjI2eFAFHgUFHgVQUAUeBQUeBVAFHgUFHgVQUAUeBQUeBQHLFBQPBQUeBQUPFHgUDwUFHgUFDxQUDwUFHgUFDxR4FA8FBR4FBQABAAD//wFAAi8AGAAAETUhFRQGKwEVMzIWHQEjFSM1IyImPQEzNQFABQ8UFA8FyFAUDwXIAgcoFA8F8AUPFPDwBQ8U8AAAAAAEAAD//wFAAgcAAwAHAAsADwAAFzUzFSE1MxUTNTMVITUzFXjI/sAo8Cj+wMgB8PDw8AEY8PDw8AAAAAADABQAJwEsAgcAWwBrAHMAABM1MxUUFjsBFSMiJj0BIxUUFjIWFBY7ARUjIgYUFjIWFBYyFhQGIgYdASM1NCYiJjQ2MhYUFjI2NDYyNjQmIiY0JiImNCYrATUzMjY0NjI2NCYiJjQmIiY0NjI2FjQmIgYUFjIWFBYyNjQmIjY0JiIGFBYyPMgFDxQUDwVQBR4FBQ8UFA8FBR4FBR4FBR4FyAUeBQUeBQUeBQUeBQUeBQUeBQUPFBQPBQUeBQUeBQUeBQUeBVAFHgUFHgUFHgUFHnMFHgUFHgHzFBQPBXgFDxQUDwUFHgV4BR4FBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FBR4FUAUeBQUeBQUeBQUeBQUoHgUFHgUFHgUFHgUFHgUFHgUAAAADAAAA7wFAAWcAAwALABMAAD0BIRUmNCYiBhQWMjY0JiIGFBYyAUDIBR4FBR59BR4FBR7veHgtHgUFHgUFHgUFHgUAAAEAAP//AUACLwA3AAAANDY7ARUjFSMiBhQGKwEVIxUjIgYUBisBFSMiBhQGKwE1MzUzMjY0NjsBNTM1MzI2NDY7ATUzMgEYBQ8UKBQPBQUPFCgUDwUFDxQUDwUFDxQoFA8FBQ8UKBQPBQUPFBQPAgweBXhQBR4FUFAFHgVQBR4FeFAFHgVQUAUeBVAAAQAoAHcBGAHfABsAABM1MxUUFjIWFBYyFh0BIxUjNSM1NDYyNjQ2MjZ4UAUeBQUeBVBQUAUeBQUeBQHLFBQPBQUeBQUPFPDwFA8FBR4FBQABACgAdwEYAd8AGwAANzUzFTMVFAYiBhQGIgYdASM1NCYiJjQmIiY9AXhQUAUeBQUeBVAFHgUFHgXv8PAUDwUFHgUFDxQUDwUFHgUFDxQAAAEAFADHASwBjwAbAAATNTMyFhQWMhYUFjIWFAYiBhQGIgYUBisBNSM1tBQPBQUeBQUeBQUeBQUeBQUPFKABP1AFHgUFHgUFHgUFHgUFHgVQKAAAAAEAFADHASwBjwAbAAASNDY7ARUzFSMVIyImNCYiJjQmIiY0NjI2NDYyZAUPFKCgFA8FBR4FBR4FBR4FBR4BbB4FUChQBR4FBR4FBR4FBR4FAAAAAAEAAP//AUACLwA3AAARNTMyFhQWOwEVMzIWFBY7ARUzFTMyFhQWOwEVMxUjIiY0JisBNSMiJjQmKwE1IzUjIiY0JisBNRQPBQUPFBQPBQUPFCgUDwUFDxQoFA8FBQ8UFA8FBQ8UKBQPBQUPFAG3eAUeBVAFHgVQUAUeBVB4BR4FUAUeBVBQBR4FUAAAAQAUAMcBLAGPADMAABI0NjsBFTM1MzIWFBYyFhQWMhYUBiIGFAYiBhQGKwE1IxUjIiY0JiImNCYiJjQ2MjY0NjJkBQ8UKBQPBQUeBQUeBQUeBQUeBQUPFCgUDwUFHgUFHgUFHgUFHgFsHgVQUAUeBQUeBQUeBQUeBQUeBVBQBR4FBR4FBR4FBR4FAAABABQAnwEsAbcAFQAAEzUzFTMVMxUzMhYdASE1NDY7ATUzNYwoKCgUDwX+6AUPFCgBZ1BQUFAFDxQUDwVQUAAAAQAUAJ8BLAG3ABUAABM1IRUUBisBFSMVIxUjNSM1IzUjIiYUARgFDxQoKCgoKBQPBQGjFBQPBVBQUFBQUAUAAAEAFADHASwBZwAZAAASNDY7ARUzMhYdATMVITU0NjI2NDYyNjQ2MowFDxQUDwVQ/ugFHgUFHgUFHgFEHgVQBQ8UKBQPBQUeBQUeBQABACj//wEYAi8ANwAAEzUzFRQGKwEVIxUzMhYUFjIWFBY7ARUzFSMiBh0BIzU0NjsBNSM1IyImNCYiJjQmKwE1MzUzMjZ4UAUPFCgUDwUFHgUFDxQoFA8FUAUPFCgUDwUFHgUFDxQoFA8FAhsUFA8FUHgFHgUFHgVQeAUPFBQPBXhQBR4FBR4FeFAFAAEAKP//ARgCLwA3AAATNTMVFBY7ARUzFSMiBhQGIgYUBisBFSMVMzIWHQEjNTQmKwE1MzUzMjY0NjI2NDY7ATUjNSMiJnhQBQ8UKBQPBQUeBQUPFCgUDwVQBQ8UKBQPBQUeBQUPFCgUDwUCGxQUDwVQeAUeBQUeBVB4BQ8UFA8FeFAFHgUFHgV4UAUAAQAAAMcBQAGPAC8AABM1MxUUFjsBFSMiJj0BIxUUBiIGFAYiBh0BIzU0JisBNTMyFh0BMzU0NjI2NDYyNshQBQ8UFA8FUAUeBQUeBVAFDxQUDwVQBR4FBR4FAXsUFA8FUAUPFBQPBQUeBQUPFBQPBVAFDxQUDwUFHgUFAAEAAADHAUABjwAvAAATNTMVFBYyFhQWMhYdATM1NDY7ARUjIgYdASM1NCYiJjQmIiY9ASMVFAYrATUzMjYoUAUeBQUeBVAFDxQUDwVQBR4FBR4FUAUPFBQPBQF7FBQPBQUeBQUPFBQPBVAFDxQUDwUFHgUFDxQUDwVQBQABAAD//wFAAi8AGAAAATUzESE1NDY7ATUzNTM1MzI2NDY7ATUzNQEYKP7ABQ8UKCgUDwUFDxQoAd9Q/dAUDwVQUFAFHgVQUAABAAD//wFAAi8AGAAAFREzFTMVMxUzMhYUFjsBFTMVMxUzMhYdASgoKBQPBQUPFCgoFA8FAQIwUFBQBR4FUFBQBQ8UAAEAAP//AUACLwAYAAARNSERIzUjNSM1IyImNCYrATUjNSM1IyImAUAoKCgUDwUFDxQoKBQPBQIbFP3QUFBQBR4FUFBQBQAAAAEAAP//AUACLwAYAAAVESEVFAYrARUjFSMVIyIGFAYrARUjFSMVAUAFDxQoKBQPBQUPFCgoAQIwFA8FUFBQBR4FUFBQAAAAAAEAAAFnAUACBwArAAARNTMVFBYyFhQWMjY0NjI2NDY7ARUjNSMiBhQGIiY0JisBFSM1IxUjNSMiJqAFHgUFHgUFHgUFDxQoFA8FBR4FBQ8UKCgoFA8FAfMUFA8FBR4FBR4FBR4FoFAFHgUFHgVQeHh4BQAAAgA8AHcBBAHfABcAOwAAEzUzFRQGKwEVMzIWHQEjNTQmKwE1MzI2JzUzFSMVFAYrARUzMhYdATMVIzU0JiImNCYrATUzMjY0NjI2tFAFDxQUDwVQBQ8UFA8FKHh4BQ8UFA8FeHgFHgUFDxQUDwUFHgUBexQUDwV4BQ8UFA8FeAVfFCgUDwXIBQ8UKBQPBQUeBcgFHgUFAAADADwAdwEEAd8ADwAfAEMAABM1MxUUBiIGHQEjNTQ2MjYnNTMVFBYyFh0BIzU0JiImPQEzFRQWMhYUFjsBFSMiBhQGIgYdASM1MzU0NjsBNSMiJj0BZFAFHgVQBR4FKFAFHgVQBR4FeAUeBQUPFBQPBQUeBXh4BQ8UFA8FAQMUFA8FBQ8UFA8FBYcUFA8FBQ8UFA8FBUsoFA8FBR4FyAUeBQUPFCgUDwXIBQ8UAAAAAAIAKACfARgBjwAnAD0AABM1MxUUFjIWFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjYXNSMiBhQGIgYdATMVMzI2NDYyNj0BeFAFHgUFDxQUDwUFHgVQBR4FBQ8UFA8FBR4FKBQPBQUeBVAUDwUFHgUBexQUDwUFHgVQBR4FBQ8UFA8FBR4FUAUeBQVVUAUeBQUPFFAFHgUFDxQAAAAABAAAAE8BQAHfAAcADwA/AJUAADY0NjIWFAYiNjQ2MhYUBiInNTMVFBYyFhQWOwEVIxUzFSMiBhQGIgYdASM1NCYiJjQmKwE1MzUjNTMyNjQ2MjYXNSMVFAYiBhQGIgYUFjI2NDYyNj0BMxUUBisBFSMVFAYiBhQWMhYUFjIWHQEzNTQ2MjY0NjI2NCYiBhQGIgYdASM1NDY7ATUzNTQ2MjY0JiImNCYiJlAFHgUFHnMFHgUFHn2gBR4FBQ8UKCgUDwUFHgWgBR4FBQ8UKCgUDwUFHgV4UAUeBQUeBQUeBQUeBVAFDxRQBR4FBR4FBR4FUAUeBQUeBQUeBQUeBVAFDxRQBR4FBR4FBR4FzB4FBR4FfR4FBR4FjBQUDwUFHgVQUFAFHgUFDxQUDwUFHgVQUFAFHgUFGRQUDwUFHgUFHgUFHgUFDxQUDwVQFA8FBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBQ8UFA8FUBQPBQUeBQUeBQUAAQB4ARcAyAFnAAMAABM1MxV4UAEXUFAAAQB4AMcAyAEXAAMAADc1MxV4UMdQUAAAAQB4AO8AyAE/AAMAADc1MxV4UO9QUAAAAQB4AO8AyAE/AAMAADc1MxV4UO9QUAAABQAAAHcBQAG3AEkAUQBZAGkAeQAAEjQ2MhYUFjI2PQEzFRQWMjY0NjIWFBYyFhQGIgYUFjIWFAYiBhQWOwEVIzUjFSM1IxUjNSMVIzUzMjY0JiImNDYyNjQmIiY0NjIWNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjIWFBYyNjQmIjY0JiIGFAYiBhQWMjY0NjIoBR4FBR4FUAUeBQUeBQUeBQUeBQUeBQUeBQUPFCgoKFAoKCgUDwUFHgUFHgUFHgUFHi0FHgUFHs0FHgUFHsMFHgUFHgUFHgUFHsMFHgUFHgUFHgUFHgGUHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgUFHgVQUHh4eHhQUAUeBQUeBQUeBQUeBSMeBQUeBQUeBQUeBUseBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQAAAAABACgATwEYAd8AHwAAEzUzFRQGKwEVMxUzFSMiBh0BIzU0JisBNTM1MzUjIiZQoAUPFCgoFA8FoAUPFCgoFA8FAcsUFA8FeFB4BQ8UFA8FeFB4BQAEAAAAJwFAAgcAEQAZACEAMwAANzUzFSMVIzU0JiImNDYyFh0BPAE2MhYUBiI2NDYyFhQGIic1MxUUFjIWFAYiJj0BIxUjNfBQUKAFHgUFHgUFHgUFHnMFHgUFHn2gBR4FBR4FoFBPUFAoFA8FBR4FBQ8UfR4FBR4FfR4FBR4FoCgUDwUFHgUFDxRQUAAAAAAEAAAAJwFAAgcABwAdACUAOwAANjQ2MhYUBiI3NTMVIyIGFAYiBh0BIzUzNTQ2OwE1JjQ2MhYUBiInNTMVIxUUBisBFTMVIzUzMjY0NjI2oAUeBQUeS1AUDwUFHgV4eAUPFKAFHgUFHi14eAUPFChQFA8FBR4FpB4FBR4FKFCgBR4FBQ8UKBQPBVClHgUFHgWMFCgUDwVQUKAFHgUFAAAAAgAAAHcBQAG3ABcALwAAEjQ2MhYUFjIWFAYiJjQmKwEVMxUjNTMyMzUzFSMiBhQGIiY0JiImNDYyFhQWOwE1KAUeBQUeBQUeBQUPFChQFA/NUBQPBQUeBQUeBQUeBQUPFAFsHgUFHgUFHgUFHgWgUPBQ8AUeBQUeBQUeBQUeBaAAAAAABAAAAE8BQAHfAAcADwAsAEkAADY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGKwEVMzIWHQEzFSM1IyImNCYrATUzMjc1MxUzMhYUFjsBFSMiBhQGIiY0NjsBNSMiJj0BUAUeBQUecwUeBQUepQUeBQUPFBQPBVBQFA8FBQ8UFA99UBQPBQUPFBQPBQUeBQUPFBQPBfQeBQUeBS0eBQUeBS0eBQUeBXgFDxRQUAUeBXhQUFAFHgV4BR4FBR4FeAUPFAAABQAAAHcBQAG3AAcADwAnAC8ANwAANjQ2MhYUBiI2NDYyFhQGIic1MxUUFjsBFSMiBh0BIzU0JisBNTMyNiI0NjIWFAYiNjQ2MhYUBiJQBR4FBR7DBR4FBR6lUAUPFBQPBVAFDxQUDwV4BR4FBR7DBR4FBR58HgUFHgVVHgUFHgWMFBQPBVAFDxQUDwVQBR4FBR4FVR4FBR4FAAAABQAAAHcBQAG3AAcADwAXAC8ANwAANjQ2MhYUBiImNDYyFhQGIiQ0NjIWFAYiJzUzFRQWOwEVIyIGHQEjNTQmKwE1MzI2PAE2MhYUBiKgBR4FBR6lBR4FBR4BEwUeBQUepVAFDxQUDwVQBQ8UFA8FBR4FBR58HgUFHgV9HgUFHgUtHgUFHgU8FBQPBVAFDxQUDwVQBVAeBQUeBQAABQAAAHcBQAG3AAcADwAXAC8ANwAANjQ2MhYUBiImNDYyFhQGIiQ0NjIWFAYiJzUzFRQWOwEVIyIGHQEjNTQmKwE1MzI2JjQ2MhYUBiLIBR4FBR7NBR4FBR4BEwUeBQUepVAFDxQUDwVQBQ8UFA8FKAUeBQUefB4FBR4FVR4FBR4FfR4FBR4FFBQUDwVQBQ8UFA8FUAVQHgUFHgUABQAoAJ8BGAGPAAcADwAnAC8ANwAANjQ2MhYUBiImNDYyFhQGIjc1MxUUFjsBFSMiBh0BIzU0JisBNTMyPgE0NjIWFAYiJjQ2MhYUBiLwBR4FBR7NBR4FBR5LUAUPFBQPBVAFDxQUDwV4BR4FBR7NBR4FBR6kHgUFHgUFHgUFHgW0FBQPBVAFDxQUDwVQBSgeBQUeBQUeBQUeBQAAAgAUAE8BLAG3ADUARQAAEzUzFSMVMxUUFjIWFAYiJj0BIxUUFjIWFBY7ARUjIgYdASM1NCYrATUzMjY9ATM1IzU0JiImFjQmIgYUBiIGFBYyNjQ2MhTwUFAFHgUFHgVQBR4FBQ8UFA8FoAUPFBQPBXh4BR4FyAUeBQUeBQUeBQUeAaMUKCgUDwUFHgUFDxQUDwUFHgV4BQ8UFA8FoAUPFCgUDwUF8B4FBR4FBR4FBR4FAAAAAgAoAHcBGAGPAB0AIQAAEzUzFRQWMhYUBiIGFBY7ARUjNTMyNjQmIiY0NjI2FzUjFVCgBR4FBR4FBQ8U8BQPBQUeBQUeBXhQAXsUFA8FBR4FBR4FoKAFHgUFHgUFVSgoAAAAAAIAAAAnAUACBwAlACkAABM1MxUUFjIWFBY7AREhNSMiJjQ2OwE1IyImNDY7ATUzMjY0NjI2FzUjFXh4BR4FBQ8U/ugUDwUFDxQUDwUFDxQUDwUFHgWgUAHzFBQPBQUeBf5wUAUeBaAFHgVQBR4FBfUoKAAABAAAACcBQAHfAAMABwALAA8AAD0BIRUnNTMVJzUzFSc1MxUBQPDwoKBQUCdQUHhQUHhQUHhQUAACAAAA7wFAAWcAAwATAAA3NTMVJDQ2OwEVIyImNCYiJjQ2MnjI/ugFDxQUDwUFHgUFHu94eFUeBXgFHgUFHgUABAAAAJ8BQAG3AAMAEwAXACcAADc1MxUkNDY7ARUjIiY0JiImNDYyNzUzFSQ0NjsBFSMiJjQmIiY0NjJ4yP7oBQ8UFA8FBR4FBR5VyP7oBQ8UFA8FBR4FBR6feHhVHgV4BR4FBR4FUHh4VR4FeAUeBQUeBQADAAAATwFAAY8ANwBHAFcAABM0NjIWFBYyNjQ2MhYUBiIGFBYyFhQGKwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEzFjQmIgYUFjIWFBYyNjQmIgY0JiIGFAYiBhQWMjY0NjLIBR4FBR4FBR4FBR4FBR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgoBR4FBR4FBR4FBR59BR4FBR4FBR4FBR4Bew8FBR4FBR4FBR4FBR4FBR4FeAUeBQUPFBQPBQUeBXgFHgUFDxQjHgUFHgUFHgUFHgUjHgUFHgUFHgUFHgUAAgAAAE8BQAHfAEMAUwAAEjQ2MhYUFjIWFAYiBhQWMhYdASMVFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjY9ATMVFBY7ATUzMjY0JiIGNCYiBhQGIgYUFjI2NDYy8AUeBQUeBQUeBQUeBVAFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUPFBQPBQUefQUeBQUeBQUeBQUeAbweBQUeBQUeBQUeBQUPFBQPBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FUAUeBZseBQUeBQUeBQUeBQAAAgAAAE8BQAIHAEcAVwAAEjQ2MhYUFjsBFSMiBhQGIgYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNj0BMxUUFjI2NDYyNjQmIiY0NjI2NCYiBjQmIgYUBiIGFBYyNjQ2MvAFHgUFDxQUDwUFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUeBQUeBQUeBQUefQUeBQUeBQUeBQUeAeQeBQUeBXgFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBQUeBQUeBQUeBcMeBQUeBQUeBQUeBQAAAgAAAE8BQAIvAFcAZwAAEjQ2MhYUFjI2NDYyFhQGIgYUFjsBFSMiBhQGIgYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNj0BMxUUFjI2NDY7ATUjIgYUBiImNDYyNjQmIgY0JiIGFAYiBhQWMjY0NjLIBR4FBR4FBR4FBR4FBQ8UFA8FBR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgUFHgUFHlUFHgUFHgUFHgUFHgIMHgUFHgUFHgUFHgUFHgV4BR4FBR4FeAUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BR4FBR4FBR4F6x4FBR4FBR4FBR4FAAAAAAIAAABPAUACLwBPAF8AABI0NjIWFBYyFhQWOwEVIyIGFAYiBhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEzFRQWMjY0NjsBNSMiBhQGIiY0JiImNDYyBjQmIgYUBiIGFBYyNjQ2MsgFHgUFHgUFDxQUDwUFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBQUeBQUeSwUeBQUeBQUeBQUeAgweBQUeBQUeBXgFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgFHgUFHgUFHgXrHgUFHgUFHgUFHgUAAAAAAgAAAE8BQAIvAF8AbwAAEjQ2MhYUFjI2NDYyFhQWMhYUFjsBFSMiBhQGIgYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNj0BMxUUFjI2NDY7ATUjIgYUBiImNCYiBhQGIiY0NjI2NCYiBjQmIgYUBiIGFBYyNjQ2MngFHgUFHgUFHgUFHgUFDxQUDwUFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeAgweBQUeBQUeBQUeBQUeBXgFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgFHgUFHgUFHgUFHgUFHgXrHgUFHgUFHgUFHgUAAAAAAgAAAE8BQAIHAF4AbgAAEjQ2MhYUFjI2PQEzFRQWOwEVIyIGFAYiBhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEzFRQWMjY0NjsBNSMVFBYyFhQGIiY0JiIGFAYiJjQ2MjY0JiIWNCYiBhQGIgYUFjI2NDYyUAUeBQUeBXgFDxQUDwUFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFHgFHgUFHgUFHgUFHgUFHgUFHiMFHgUFHgUFHgUFHgHkHgUFHgUFDxQUDwV4BR4FBR4FeAUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4FA8FBR4FBR4FBR4FBR4FBR4Fwx4FBR4FBR4FBR4FAAMAFAB3ASwCBwAvAD0ATQAAEzUzFTM1MxUjFRQWMhYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNj0BFjQmKwEVMzUjIgYUBiIWNCYiBhQGIgYdATM1NCYiFFB4UFAFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FKAUPFHgUDwUFHiMFHgUFHgV4BR4Bt1BQUFAUDwUFHgWgBR4FBQ8UFA8FBR4FoAUeBQUPFEseBVBQBR4Fcx4FBR4FBQ8UFA8FAAAAAgAUAHcBLAG3ACEAMwAAEzUzFTM1MxUjIgYUFjsBFSMiBh0BIzU0JisBNTMyNjQmIxc1IxUzMhYUFjI2NDY7ATUjFRRQeFAUDwUFDxQUDwXIBQ8UFA8FBQ9kKBQPBQUeBQUPFCgBZ1BQUFAFHgWgBQ8UFA8FoAUeBXhQUAUeBQUeBVBQAAAAAAIAFADHASwBjwA3AE8AABM1MxUUFjI2PQEzFRQGIgYUBiIGFBYyFhQWMhYdASM1NCYiBh0BIzU0JiImNCYiJjQ2MjY0NjI2FjQmIgYUBiIGFBYyFhQWMjY0JiImNDYyZFAFHgVQBR4FBR4FBR4FBR4FUAUeBVAFHgUFHgUFHgUFHgVQBR4FBR4FBR4FBR4FBR4FBR4BexQUDwUFDxQUDwUFHgUFHgUFHgUFDxQUDwUFDxQUDwUFHgUFHgUFHgUFKB4FBR4FBR4FBR4FBR4FBR4FAAAAAgAUAMcBLAGPADcATwAAEzUzFRQWMjY9ATMVFBYyFhQWMhYUBiIGFAYiBh0BIzU0JiIGHQEjNTQ2MjY0NjI2NCYiJjQmIiYWNCYiBhQWMhYUBiIGFBYyNjQ2MjY0JiIUUAUeBVAFHgUFHgUFHgUFHgVQBR4FUAUeBQUeBQUeBQUeBaAFHgUFHgUFHgUFHgUFHgUFHgF7FBQPBQUPFBQPBQUeBQUeBQUeBQUPFBQPBQUPFBQPBQUeBQUeBQUeBQUoHgUFHgUFHgUFHgUFHgUFHgUAAAAcABT//wEsAi8ABwAPABcAHwAnAC8ANwA/AEcATwBXAF8AZwBvAHcAfwCHAI8AlwCfAKcArwC3AL8AxwDPANcA3wAANjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiK0BR4FBR6lBR4FBR7rBR4FBR6lBR4FBR5LBR4FBR6lBR4FBR7rBR4FBR6lBR4FBR5LBR4FBR6lBR4FBR7rBR4FBR6lBR4FBR5LBR4FBR6lBR4FBR7rBR4FBR6lBR4FBR5LBR4FBR6lBR4FBR7rBR4FBR6lBR4FBR5LBR4FBR6lBR4FBR7rBR4FBR6lBR4FBR5LBR4FBR6lBR4FBR7rBR4FBR6lBR4FBR4EHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUtHgUFHgUFHgUFHgUABAAA//8BQAIvAF8AnwE/Ab8AABI0NjIWFBYyNjQ2MhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUBiImNCYiBhQGIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MhY0JiIGFBYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQWMjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyFhQWMjY0NjIWFBYyNjQ2MhYUFjI2NDYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUBiImNCYiBhQGIiY0JiIGFAYiJjQmIgYUBiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyFjQmIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQWMjY0NjIWFBYyNjQ2MhYUFjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0JiIGFAYiJjQmIgYUBiJ4BR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4tBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR59BR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4tBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4BvB4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FIx4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FfR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FIx4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FAAAAABMAAP//AUACLwBSAFoAYgBqAHIAegCCAIoAkgCaAKIAqgCyALoAwgDKANIA2gDiAAARNTMVFBYyNj0BMxUUFjI2NDY7AREjNTQmIgYdASM1NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJhY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMgY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMgY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMgY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMgY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMgY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMlAFHgV4BR4FBQ8UeAUeBXgFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgXIBR4FBR5LBR4FBR6lBR4FBR5LBR4FBR5LBR4FBR6lBR4FBR5LBR4FBR5LBR4FBR6lBR4FBR5LBR4FBR5LBR4FBR6lBR4FBR5LBR4FBR5LBR4FBR6lBR4FBR5LBR4FBR5LBR4FBR6lBR4FBR4CGxQUDwUFDxQUDwUFHgX90BQPBQUPFBQPBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUoHgUFHgUjHgUFHgUFHgUFHgUjHgUFHgUjHgUFHgUFHgUFHgUjHgUFHgUjHgUFHgUFHgUFHgUjHgUFHgUjHgUFHgUFHgUFHgUjHgUFHgUjHgUFHgUFHgUFHgUjHgUFHgUjHgUFHgUFHgUFHgUAAAEAeP//AMgCLwADAAAXETMReFABAjD90AAAAAABAAD//wDIAi8ABwAAEzUzESMRIzV4UFB4AT/w/dABGCgAAAADAAD//wEYAi8ABQAJAA8AAD0BMxEjNRcRMxEDNTMVIzWgUHhQyFCg7yj+6PDwAjD90AFoyPAoAAIAUP//ARgCLwADAAcAABcRMxEjETMRyFDIUAECMP3QAjD90AAAAAACAAD//wEYAWcABQALAAA9ATMRIzUnNSERIxGgUFABGFDvKP7o8FAo/pgBQAAAAAACAAAA7wEYAi8ABQALAAATETMRITU3NTMVIzXIUP7oUFCgARcBGP7AKFDI8CgAAAABAAD//wDIAT8ABQAAETUzESMRyFABFyj+wAEYAAAAAAEAeAEXAUACLwAFAAATETMVMxV4UHgBFwEY8CgAAAAAAQAAARcBQAIvAAcAABM1MxUzFSE1eFB4/sABP/DwKCgAAAAAAQAA//8BQAE/AAcAABE1IRUjESMRAUB4UAEXKCj+6AEYAAAAAQB4//8BQAIvAAcAABcRMxUzFSMReFB4eAECMPAo/ugAAAAAAQAAARcBQAE/AAMAABE1IRUBQAEXKCgAAQAA//8BQAIvAAsAABM1MxUzFSMRIxEjNXhQeHhQeAE/8PAo/ugBGCgAAAACAFAA7wFAAi8ACAAOAAATNTMVMzIWHQEHETMRMxXIUBQPBfBQoAE/8MgFDxRQAUD+6CgAAgBQ//8BQAFnAAgADgAAFxEzFRQGKwEVIxEzFSMRyHgFDxTI8KABARgUDwXwAWgo/sAAAAMAAADvAUACLwADAAwAEgAAPQEhFSc1MxUzMhYdASc1MxUjNQFAeFAUDwXwUKDvKChQ8MgFDxQoyPAoAAMAAP//AUABZwAIAA4AEgAAFxEzFRQGKwEVJTUzESM1JzUhFch4BQ8U/uigUFABQAEBGBQPBfDwKP7o8FAoKAADAFD//wFAAi8ACAARABUAABcRMxUUBisBFQM1MxUzMhYdAQMRMxHIeAUPFFBQFA8F8FABARgUDwXwAUDwyAUPFP7AAjD90AACAAAA7wFAAWcAAwAHAAA9ASEVJTUhFQFA/sABQO8oKFAoKAAEAAD//wFAAi8ACAAOABcAHQAAFxEzFRQGKwEVJTUzESM1NzUzFTMyFh0BJzUzFSM1yHgFDxT+6KBQeFAUDwXwUKABARgUDwXw8Cj+6PBQ8MgFDxQoyPAoAAAAAAEAAAEXAMgCLwAFAAATNTMRIzV4UMgBP/D+6CgAAAAAAQB4//8BQAE/AAUAABcRMxUjEXjIeAEBQCj+6AAAAAABAAD//wFAAi8AAwAAFREhEQFAAQIw/dAAAAAAAQAA//8BQAEXAAMAABURIREBQAEBGP7oAAAAAAEAAP//AKACLwADAAAVETMRoAECMP3QAAEAoP//AUACLwADAAAXETMRoKABAjD90AAAAAABAAABFwFAAi8AAwAAGQEhEQFAARcBGP7oAAAAAgAAAJ8BQAGPACcALwAAADQ2OwEVIyImNCYiJjQmIgYdASM1NCYrATUzMjY9ATMVFBYyNjQ2MgY0JiIGFBYyARgFDxQUDwUFHgUFHgWgBQ8UFA8FoAUeBQUewwUeBQUeAWweBfAFHgUFHgUFDxQUDwVQBQ8UFA8FBR4FSx4FBR4FAAAAAwAUAJ8BLAG3ACcANwA/AAATNTMVFBYyFhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2FjQmIgYUBiIGFBYyNjQ2MhY0JiIGFBYyZHgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FKAUeBQUeBQUeBQUeVQUeBQUeAaMUFA8FBR4FeAUeBQUPFBQPBQUeBXgFHgUFKB4FBR4FBR4FBR4Fmx4FBR4FAAMAAAB3AUAB3wANABUAHQAANREhFSMVMxUjNTM1IxE2NCYiBhQWMhY0JiIGFBYyAUBQUMhQeHgFHgUFHlUFHgUFHncBaCgooKAo/sDNHgUFHgUjHgUFHgUAAAAABAAUAHcBLAHfACUALQA1AD0AABI0NjIWFBYyNjQ2MhYUFjsBESM1IxUjNSMVIzUzMjY0NjI2NDYyFjQmIgYUFjI2NCYiBhQWMgY0JiIGFBYyjAUeBQUeBQUeBQUPFFAoUCgoFA8FBR4FBR4tBR4FBR5VBR4FBR4jBR4FBR4BvB4FBR4FBR4FBR4F/sB4eKBQoAUeBQUeBUseBQUeBQUeBQUeBUseBQUeBQAAAAEAAP//AUABFwAeAAAkNDY7AREhNTQ2MjY0NjI2NDYyNj0BMzU0NjI2NDYyARgFDxT+wAUeBQUeBQUeBVAFHgUFHvQeBf7oFA8FBR4FBR4FBQ8UFA8FBR4FAAAAAQAA//8BQAIvAB0AAAA0NjsBESERMzI2NDYyNjQ2MjY9ATM1NDYyNjQ2MgEYBQ8U/sAUDwUFHgUFHgVQBR4FBR4CDB4F/dABQAUeBQUeBQUPFBQPBQUeBQAAAAABAAD//wFAAi8AHQAAFREzMhYUFjIWFBYyFh0BMxUUFjIWFBYyFhQWOwERFA8FBR4FBR4FUAUeBQUeBQUPFAECMAUeBQUeBQUPFBQPBQUeBQUeBf7AAAAAAQAA//8BQAEXAB4AABURMzIWFBYyFhQWMhYdATMVFBYyFhQWMhYUFjIWHQEUDwUFHgUFHgVQBR4FBR4FBR4FAQEYBR4FBR4FBQ8UFA8FBR4FBR4FBQ8UAAsAAABPAUACBwA3AD8AQwBLAFMAVwBfAGcAbwBzAHsAABE1MxUUFjIWFBY7ARUjIgYUBisBFTMyFhQWMhYdASM1NCYiJjQmKwE1MzI2NDY7ATUjIiY0JiImFjQmIgYUFjIzNSMVFjQmIgYUFjI2NCYiBhQWMgc1IxU2NCYiBhQWMgY0JiIGFBYyNjQmIgYUFjIHNSMVNjQmIgYUFjLwBR4FBQ8UFA8FBQ8UFA8FBR4F8AUeBQUPFBQPBQUPFBQPBQUeBXgFHgUFHn1QKAUeBQUeVQUeBQUec1CgBR4FBR6bBR4FBR59BR4FBR4jUKAFHgUFHgHzFBQPBQUeBXgFHgV4BR4FBQ8UFA8FBR4FeAUeBXgFHgUFKB4FBR4FKChLHgUFHgUFHgUFHgVQKCgFHgUFHgVLHgUFHgUFHgUFHgVQKCgFHgUFHgUAAAAABgAUAHcBLAHfACcALwA3AD8ARwBPAAATNTMVFBYyFhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2FjQmIgYUFjIWNCYiBhQWMgY0JiIGFBYyFjQmIgYUFjIGNCYiBhQWMmR4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBSgFHgUFHlUFHgUFHnMFHgUFHqUFHgUFHksFHgUFHgHLFBQPBQUeBcgFHgUFDxQUDwUFHgXIBR4FBSgeBQUeBUseBQUeBSMeBQUeBSMeBQUeBUseBQUeBQAAAAQAKAB3ARgB3wAjACcAKwAzAAATNTMVFBY7AREjIiY0JiIGFAYiJjQmIgYUBiImNCYrAREzMjYXNSMVMzUjFRQ0JiIGFBYyUKAFDxQUDwUFHgUFHgUFHgUFHgUFDxQUDwUoKHgoBR4FBR4ByxQUDwX+wAUeBQUeBQUeBQUeBQUeBQEYBX1QUFBQSx4FBR4FAAEAKAB3ARgCBwBcAAATNTMVMxUUBiIGFBYyFhQGIiY0JiImNCYrARUzMhYUFjIWFBY7ARUjIgYdASM1NCYrATUzMjY0JiImNDYyFhQWMhYUBiIGFBYyFh0BMzUjIiY0JiImNCYrATUzMjZ4UFAFHgUFHgUFHgUFHgUFDxQUDwUFHgUFDxQUDwWgBQ8UFA8FBR4FBR4FBR4FBR4FBR4FUBQPBQUeBQUPFBQPBQHzFCgUDwUFHgUFHgUFHgUFHgVQBR4FBR4FeAUPFBQPBVAFHgUFHgUFHgUFHgUFHgUFDxR4BR4FBR4FeAUAAAAABQAAAE8BQAIvAAMAUABwAIoAkgAANzUzFQI0NjIWFBYyFhQWOwEVMzIWFBYyFhQGIgYUFjIWFAYiBhQGIgYdASM1NCYiJjQmIiY0NjI2NCYiJjQ2OwE1MxUzNTQmKwE1IyImNDYyFjQmIgYUFjsBFTMyFhQWMhYUFjI2NCYiJjQmKwE1IyIHNCYiBhQWMhYUBiIGFBYyFh0BMzUzNSM1IxI0NjIWFAYiUFAoBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FoAUeBQUeBQUeBQUeBQUPFChQBQ8UFA8FBR4tBR4FBQ8UFA8FBR4FBR4FBR4FBQ8UFA9VBR4FBR4FBR4FBR4FUFBQUCgFHgUFHp8oKAEdHgUFHgUFHgVQBR4FBR4FBR4FBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBR4FUFAUDwVQBR4FIx4FBR4FUAUeBQUeBQUeBQUeBVCMDwUFHgUFHgUFHgUFDxQoKCgBHR4FBR4FAAAAAAEAAAEXAUACLwAeAAARNSERIyImNCYiJjQmIiY9ASM1NCYiJjQmIiY0JiImAUAUDwUFHgUFHgVQBR4FBR4FBR4FAhsU/ugFHgUFHgUFDxQUDwUFHgUFHgUFAAAAAQAA//8BQAIvAB0AADURIREjIiY0JiImNCYiJj0BIzU0JiImNCYiJjQmIwFAFA8FBR4FBR4FUAUeBQUeBQUP7wFA/dAFHgUFHgUFDxQUDwUFHgUFHgUAAAEAAP//AUACLwAdAAAVESERIyIGFAYiBhQGIgYdASMVFAYiBhQGIgYUBiMBQBQPBQUeBQUeBVAFHgUFHgUFDwECMP7ABR4FBR4FBQ8UFA8FBR4FBR4FAAABAAABFwFAAi8AHgAAGQEhFRQGIgYUBiIGFAYiBh0BIxUUBiIGFAYiBhQGIwFABR4FBR4FBR4FUAUeBQUeBQUPARcBGBQPBQUeBQUeBQUPFBQPBQUeBQUeBQAAAAQAAABPAUAB3wAXABsAHwAtAAATNTMVFBY7AREjIgYdASM1NCYrAREzMjYXNSMVMzUjFQc0JiIGFBYyFh0BMzUjKPAFDxQUDwXwBQ8UFA8FUCigKFAFHgUFHgVQUAHLFBQPBf7ABQ8UFA8FAUAFfVBQUFBkDwUFHgUFDxQoAAAABAAAAE8BQAHfABcAGwAfACMAABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhc1IxUzNSMdATUjFSjwBQ8UFA8F8AUPFBQPBVAooChQAcsUFA8F/sAFDxQUDwUBQAV9UFBQUKBQUAAAAAAEAAAATwFAAd8AFwAbAB8ALQAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzUjFTM1Ix0BNSMVFAYiBhQWMjY9ASjwBQ8UFA8F8AUPFBQPBVAooChQBR4FBR4FAcsUFA8F/sAFDxQUDwUBQAV9UFBQUKAoFA8FBR4FBQ8UAAAAAAMAAABPAUAB3wAXADkARwAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzQmIgYUFjIWFAYrARUzNTMVMzUjIiY0NjI2NCYiBh0BIxc1IxUUBiIGFBYyNj0BKPAFDxQUDwXwBQ8UFA8FUAUeBQUeBQUPFChQKBQPBQUeBQUeBVBQUAUeBQUeBQHLFBQPBf7ABQ8UFA8FAUAFGQ8FBR4FBR4FUFBQUAUeBQUeBQUPFPAoFA8FBR4FBQ8UAAAABAAAAE8BQAHfABcAHwAjACsAABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhY0JiIGFBYyMzUjFRQ0JiIGFBYyKPAFDxQUDwXwBQ8UFA8FUAUeBQUefVAFHgUFHgHLFBQPBf7ABQ8UFA8FAUAFeB4FBR4FKCibHgUFHgUAAAQAAABPAUAB3wAXADcAVwBfAAATNTMVFBY7AREjIgYdASM1NCYrAREzMjYWNCYiBhQWMhYUBiIGFBYyNjQ2MhYUFjI2NCYiJjQmIjY0JiIGFBYyFhQWMhYUFjI2NCYiJjQ2MjY0JiIGFAYiBjQmIgYUFjIo8AUPFBQPBfAFDxQUDwUoBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR5zBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR5VBR4FBR4ByxQUDwX+wAUPFBQPBQFABVAeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBeseBQUeBQADAAAATwFAAd8AFwApADIAABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhc1IxUzNTQ2MhYdATM1NDYyNgc0JisBFTM1IyjwBQ8UFA8F8AUPFBQPBfDwUAUeBVAFHgWgBQ8UeFAByxQUDwX+wAUPFBQPBQFABUEUUBQPBQUPFBQPBQWRDwVQKAAABAAAAE8BQAHfABcAGwAfAC8AABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhc1IxUzNSMdATUjFRQGIgYdATM1NCYiJijwBQ8UFA8F8AUPFBQPBVAooChQBR4FoAUeBQHLFBQPBf7ABQ8UFA8FAUAFfVBQUFBkFBQPBQUPFBQPBQUAAAAAAQB4AO8AyAE/AAMAADc1MxV4UO9QUAAAAQCMAO8AtAEXAAcAADY0NjIWFAYijAUeBQUe9B4FBR4FAAAAAQAAAE8BQAIHADEAAAA0NjIWFAYrARUjFSMVIxUjIgYUBiImNCYrATUjNTMyFhQWOwEVMzUzNTM1MzI2NDYyARgFHgUFDxQoKCgUDwUFHgUFDxQoFA8FBQ8UKCgoFA8FBR4B5B4FBR4FUHhQUAUeBQUeBVB4BR4FUFBQUAUeBQABAFABPwDwAi8AHQAAEjQ2MhYUBisBFSMVIyImNCYrATUzMjY0NjI2NDYyyAUeBQUPFCgUDwUFDxQUDwUFHgUFHgIMHgUFHgVQeAUeBVAFHgUFHgUAAAAAAQAoAHcBGAG3ACcAABI0NjsBFTMyFh0BMxUUFjsBFSMiBhQGKwE1IyImPQEjNTQmKwE1MzJQBQ8UFA8FUAUPFBQPBQUPFBQPBVAFDxQUDwGUHgV4BQ8UFA8FUAUeBXgFDxQUDwVQAAABADwAnwEEAY8AAwAANzUzFTzIn/DwAAABAFD//wDwAO8AHQAANzUzMhYUFjsBFSMiBhQGIgYUBiIGFAYiJjQ2OwE1oBQPBQUPFBQPBQUeBQUeBQUeBQUPFHd4BR4FUAUeBQUeBQUeBQUeBVAAAAAAAAAOAK4AAQAAAAAAAAA0AGoAAQAAAAAAAQAIALEAAQAAAAAAAgAGAMgAAQAAAAAAAwAlARsAAQAAAAAABAAJAVUAAQAAAAAABQAQAYEAAQAAAAAABgAIAaQAAwABBAkAAABoAAAAAwABBAkAAQAQAJ8AAwABBAkAAgAMALoAAwABBAkAAwBKAM8AAwABBAkABAASAUEAAwABBAkABQAgAV8AAwABBAkABgAQAZIAQwByAGUAYQB0AGUAZAAgAHcAaQB0AGgAIABGAG8AbgB0AEYAbwByAGcAZQAgADIALgAwACAAKABoAHQAdABwADoALwAvAGYAbwBuAHQAZgBvAHIAZwBlAC4AcwBmAC4AbgBlAHQAKQAAQ3JlYXRlZCB3aXRoIEZvbnRGb3JnZSAyLjAgKGh0dHA6Ly9mb250Zm9yZ2Uuc2YubmV0KQAAbQBlAGcAYQB6AGUAdQB4AABtZWdhemV1eAAATQBlAGQAaQB1AG0AAE1lZGl1bQAARgBvAG4AdABGAG8AcgBnAGUAIAAyAC4AMAAgADoAIABtAGUAZwBhAHoAZQB1AHgAIAAgADoAIAAyADEALQA2AC0AMgAwADEAMgAARm9udEZvcmdlIDIuMCA6IG1lZ2F6ZXV4ICA6IDIxLTYtMjAxMgAAbQBlAGcAYQB6AGUAdQB4ACAAAG1lZ2F6ZXV4IAAAVgBlAHIAcwBpAG8AbgAgADAAMAAxAC4AMAAwADAAIAAAVmVyc2lvbiAwMDEuMDAwIAAAbQBlAGcAYQB6AGUAdQB4AABtZWdhemV1eAAAAAIAAAAAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAAAAABCwAAAAEAAgECAQMBBAEFAQYBBwEIAQkBCgELAQwBDQEOAQ8BEAERARIBEwEUARUBFgEXARgBGQEaARsBHAEdAR4BHwEgASEBIgEjASQBJQEmAScBKAEpASoBKwEsAS0BLgEvATABMQEyATMBNAE1ATYBNwE4ATkBOgE7ATwBPQE+AT8BQAFBAUIBQwFEAUUBRgFHAUgBSQFKAUsBTAFNAU4BTwFQAVEBUgFTAVQBVQFWAVcBWAFZAVoBWwFcAV0BXgFfAWAApQFhAWIBYwCcAWQBZQFmAWcBaAFpAWoBawFsAW0BbgFvAXABcQFyAXMBdAF1AXYBdwF4AXkBegF7AXwBfQF+AX8BgAGBAYIBgwGEAYUBhgGHAYgBiQGKAYsBjAGNAY4BjwGQAZEBkgGTAZQBlQGWAZcBmAGZAZoBmwGcAZ0BngGfAaABoQGiAaMBpAGlAaYBpwGoAakBqgGrAawBrQGuAa8BsAGxAbIBswG0AbUBtgG3AbgBuQG6AbsBvAG9Ab4BvwHAAcEBwgHDAcQBxQHGAccByAHJAcoBywHMAc0BzgHPAdAB0QHSAdMB1AHVAdYB1wHYAdkB2gHbAdwB3QHeAd8B4AHhAeIB4wHkAeUB5gHnAegB6QHqAesB7AHtAe4B7wHwAfEB8gHzAfQB9QH2AfcB+AH5AfoB+wH8Af0B/gH/AgACAQICAgMCBAIFAgYCBwZVKzAwMjAGVSswMDIxBlUrMDAyMgZVKzAwMjMGVSswMDI0BlUrMDAyNQZVKzAwMjYGVSswMDI3BlUrMDAyOAZVKzAwMjkGVSswMDJhBlUrMDAyYgZVKzAwMmMGVSswMDJkBlUrMDAyZQZVKzAwMmYGVSswMDMwBlUrMDAzMQZVKzAwMzIGVSswMDMzBlUrMDAzNAZVKzAwMzUGVSswMDM2BlUrMDAzNwZVKzAwMzgGVSswMDM5BlUrMDAzYQZVKzAwM2IGVSswMDNjBlUrMDAzZAZVKzAwM2UGVSswMDNmBlUrMDA0MAZVKzAwNDEGVSswMDQyBlUrMDA0MwZVKzAwNDQGVSswMDQ1BlUrMDA0NgZVKzAwNDcGVSswMDQ4BlUrMDA0OQZVKzAwNGEGVSswMDRiBlUrMDA0YwZVKzAwNGQGVSswMDRlBlUrMDA0ZgZVKzAwNTAGVSswMDUxBlUrMDA1MgZVKzAwNTMGVSswMDU0BlUrMDA1NQZVKzAwNTYGVSswMDU3BlUrMDA1OAZVKzAwNTkGVSswMDVhBlUrMDA1YgZVKzAwNWMGVSswMDVkBlUrMDA1ZQZVKzAwNWYGVSswMDYwBlUrMDA2MQZVKzAwNjIGVSswMDYzBlUrMDA2NAZVKzAwNjUGVSswMDY2BlUrMDA2NwZVKzAwNjgGVSswMDY5BlUrMDA2YQZVKzAwNmIGVSswMDZjBlUrMDA2ZAZVKzAwNmUGVSswMDZmBlUrMDA3MAZVKzAwNzEGVSswMDcyBlUrMDA3MwZVKzAwNzQGVSswMDc1BlUrMDA3NgZVKzAwNzcGVSswMDc4BlUrMDA3OQZVKzAwN2EGVSswMDdiBlUrMDA3YwZVKzAwN2QGVSswMDdlBWFuZ2xlDGludGVyc2VjdGlvbgV1bmlvbgd1bmkyMjM1B3VuaTIyNTILZXF1aXZhbGVuY2UNcGVycGVuZGljdWxhcgZVK2UwMDEGVStlMDAyBlUrZTAwMwZVK2UwMDQGVStlMDA1BlUrZTAwNgZVK2UwMDcGVStlMDA4BlUrZTAwOQZVK2UwMGEGVStlMDBiBlUrZTAwYwZVK2UwMGQGVStlMDBlBlUrZTAwZgZVK2UwMTAGVStlMDExBlUrZTAxMgZVK2UwMTMGVStlMDE0BlUrZTAxNQZVK2UwMTYGVStlMDE3BlUrZTAxOAZVK2UwMTkGVStlMDFhBlUrZTAxYgZVK2UwMWMGVStlMDFkBlUrZTAxZQZVK2UwMWYGVStlMDdmBlUrZTA4MAZVK2UwODEGVStlMDgyBlUrZTA4MwZVK2UwODQGVStlMDg1BlUrZTA4NgZVK2UwODcGVStlMDg4BlUrZTA4OQZVK2UwOGEGVStlMDhiBlUrZTA4YwZVK2UwOGQGVStlMDhlBlUrZTA4ZgZVK2UwOTAGVStlMDkxBlUrZTA5MgZVK2UwOTMGVStlMDk0BlUrZTA5NQZVK2UwOTYGVStlMDk3BlUrZTA5OAZVK2UwOTkGVStlMDlhBlUrZTA5YgZVK2UwOWMGVStlMDlkBlUrZTA5ZQZVK2UwOWYGVStlMGEwBlUrZTBhMQZVK2UwYTIGVStlMGEzBlUrZTBhNAZVK2UwYTUGVStlMGE2BlUrZTBhNwZVK2UwYTgGVStlMGE5BlUrZTBhYQZVK2UwYWIGVStlMGFjBlUrZTBhZAZVK2UwYWUGVStlMGFmBlUrZTBiMAZVK2UwYjEGVStlMGIyBlUrZTBiMwZVK2UwYjQGVStlMGI1BlUrZTBiNgZVK2UwYjcGVStlMGI4BlUrZTBiOQZVK2UwYmEGVStlMGJiBlUrZTBiYwZVK2UwYmQGVStlMGJlBlUrZTBiZgZVK2UwYzAGVStlMGMxBlUrZTBjMgZVK2UwYzMGVStlMGM0BlUrZTBjNQZVK2UwYzYGVStlMGM3BlUrZTBjOAZVK2UwYzkGVStlMGNhBlUrZTBjYgZVK2UwY2MGVStlMGNkBlUrZTBjZQZVK2UwY2YGVStlMGQwBlUrZTBkMQZVK2UwZDIGVStlMGQzBlUrZTBkNAZVK2UwZDUGVStlMGQ2BlUrZTBkNwZVK2UwZDgGVStlMGQ5BlUrZTBkYQZVK2UwZGIGVStlMGRjBlUrZTBkZAZVK2UwZGUGVStlMGRmBlUrZTBlMAZVK2UwZTEGVStlMGUyBlUrZTBlMwZVK2UwZTQGVStlMGU1BlUrZTBlNgZVK2UwZTcGVStlMGU4BlUrZTBlOQZVK2UwZWEGVStlMGViBlUrZTBlYwZVK2UwZWQGVStlMGVlBlUrZTBlZgZVK2UwZjAGVStlMGYxBlUrZTBmMgZVK2UwZjMGVStlMGY0BlUrZTBmNQZVK2UwZjYGVStlMGY3BlUrZTBmOAZVK2UwZjkGVStlMGZhBlUrZTBmYgZVK2UwZmMGVStlMGZkBlUrZTBmZQZVK2UwZmYAAAAB//8AAgABAAAADgAAABgAIAAAAAIAAQABAQoAAQAEAAAAAgAAAAEAAAABAAAAAAABAAAAAMmJbzEAAAAAzAgvTwAAAADMCE7f);\n\n  src: /* mzxfont.woff */\n       url(data:application/x-font-woff;charset=utf-8;base64,d09GRk9UVE8AAC+UAAsAAAAB7AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAAD4AAAKtcAAeIMsz6ifUZGVE0AAC7cAAAAHAAAABxe5KzIR0RFRgAALrgAAAAkAAAAKAE6ACRPUy8yAAABYAAAAE4AAABgVyxS2mNtYXAAAAL4AAAAzgAAAYqPj5ZRaGVhZAAAAQgAAAAwAAAANvhv3/hoaGVhAAABOAAAAB4AAAAkBDYCS2htdHgAAC74AAAAnAAABCxMgBi9bWF4cAAAAVgAAAAGAAAABgELUABuYW1lAAABsAAAAUUAAAJbuTfNiHBvc3QAAAPIAAAAFgAAACD/hgAzeNpjYGRgYADieLM/S+P5bb4ycDMZAEUYznD4SyLo//8ZHZhmAbkcDEwgUQAS1glyeNpjYGRgYJr1/z9DFKMDAxAASUYGFMDIDQBivAOFAAAAAFAAAQsAAHjaY2BhdGD8wsDKwMDUxbSbgYGhB0Iz3mcwZGQCijKwcTLAAKMAA4MDjBOQ5prCcIBB4cF/JgMgN4ppFgMjSA1YYTpQvwIQMgIAdQ0MpQAAeNp9kM1Kw0AUhc+0aWlBpLh1MxuhLjqZDNJFdlLoQujSbqXQyY/QpKRTKt35Cr6AG9eCW5/Ana/kSRwLijTDzP3uyb03JwPgFK8Q+H6ucO9ZoIc3zy108eG5jQtx7jlAX9x67mAgHj130RMvrBRBn1nWdNUsMMCz5xZO8O65jRt8eg5wJq49dyDFnecu5z9hggoWCzieS0jskJMz0hQlCnIdK6R8L2GgoBmHrHBca8QIuRJfmxxqFTbMFFVL/RKYVHbh7FLucpfJaVm4aVmlVhql5TBzbh2HYUI1qVW1SVRhHbtWbE9pb8+4xQMFmy72dkuaNY5zyismdplvGY+Zjrn/zpNeN4gwwpjbsDbiid8eY/nzZUk20Wg8Mjoy+GfiwSNxTrXiVeSNL8nZ9XTVxNoT5rba5GUhtY6U1loe++Uv3NRk+AAAAHjaY2BgYGaAYBkGRgYQaAHyGMF8FoYMIC3GIAAUYWOoU5JSUlDSVjJVClJKVFr6QP7B////gfIKYHFNuDjjg3qgxMN7bvcc71neM7gnco/t7mGFDAVOqPlYACMbA25JmBomZhZWNnYOTi5uHl4+fgFBIWERUTFxCUkpaRlZOXkFRSVlFVU1dQ1NLW0dXT19A0MjYxNTM3MLSytrG1s7ewdHJ2cXVzd3D08vbx9fP/+AwKDgkNCw8IjIqOiY2Lh4BvJBCoyRQIEhDABO1S3lAAB42mNgZgCD/80MRkCKkQENAAAoVQG5AAB42u19C3wcVb3/ObOzm+1mCFiYIF6F8Cgtj5ammbwY3igUK9RHUvGyqMlkA4VSSilEmgBCF4oNClSBXq8QBK+PZqVcB/ByQa+Pan3rlcAtjH/13j+Kj17hetUJTm3vOb99ZHezm53f7JzNbEk/n063m8l3Zs6c8zu/5/dHiSwTSum8qxKX9W1MXPchQiVCyYrJg8lrC+hrx0uvLQxNHi7fqdAjY+Tw267ZPzqa+6A0jG6xJ15bEn4LIfOOOZgdCTmEHRs738A/H/EWQr//9vn0IA7YQA4ih5IjyFFkATmJtJJOcjo5l1xAVpJecgnpI5eRq8i15AZyM7mNjJJ7yP3kAfII+QLZQZ4gT5Ovk13kh+RZ8gL5BfkV+T35H2KTv9EQnUcPpir9O3o0XUgX0zbaTc+kb6Mr6Lvoe+ml1KCr6dX0OjpMb6Gb6Ufpx+k/0DH6T3Sc/jP9Mv0K/Sb9Lv0xfY5a9D/py/S/6f/S1+h+KSw1Sm+QDpfeIh0rnSCdIrVLunSOdL70Huli6YPSZdJa6QbpdulOaau0TXpQ+oy0XXpMelJ6RvqG9B3pR9KE9KL0S+nX0h7pj9KktC8kh2KhQ0LNoTeHjgktCi0JaaFTQ2eFzgu9I/Tu0MWh94cGQleE1oWuD42Ebg3dEfpY6BOhT4YeCn02lAp9KfQvoa+Gdoa+F/pJ6PnQz0L/FfpN6A+hP4X+KhM5IivyfPmN8pHycfKJ8lK5Qz5NPkdeLl8k98h/L39QHpTXyOvlD8k3yUl5i3y3fJ/8Kflh+fPyo/Lj8r/KX5O/Lf9A/qm8W/65/JL8O/lV+S/y3rAUjoabwoeF3xRuCR8fPjm8LNwVPiP81vDbw+8MrwrHw/3hy8NrwxvCG8MfDt8evjO8Nbwt/GD4M+Ht4cfCT4afCX8j/J3wj8IT4RfDvwz/Orwn/MfwZHhfRI7EIodEmiNvjhwTWRRZEtEip0bOipwXWRG5JHJXZFvkod6Tli5dthSOrXBcBsc2OGpwbIdjBxw74dgFx2449sGxH44GHAfgmIDjID+2AX4b4LcBfhvgtwF+G+C3AX4b4LcBfhvgtwF+G+C3AX4b4LcBfhvga4CvAb4G+Brga4CvAb4G+Brga4CvAb4G+Brga4CvAb4G+BrgtwN+O+C3A3474LcDfjvgtwN+O+C3A3474LcDfjvgtwN+O+C3A3474HcAfgfgdwB+B+B3AH4H4HcAfgfgdwB+B+B3AH4H4HcAfgfgdwB+B+B3An4n4HcCfifgdwJ+J+B3An4n4HcCfifgdwJ+J+B3An4n4Hcm1vcNrDb61vStvWxNYvXaDYn11yaMDauvXnvdWnbgX1y2vm8N+8+yZW3t8E/7ssQ1162+vm9NYq2RWJdYvy6xliFct6Zvfe9JiaVLW+G4DI5tcNTg2A7HDjh2wrELjt1w7INjPxwNOA7AMQHHQX5sXQpHwG8F/FbAbwX8VsBvBfxWwG8F/FbAbwX8VsBvBfxWwG8F/FbA74RjF1ylC67SBVfpgqt0wVW64CpdcJUuuEoXXKULrtIFV+mCq3TBVbrgKl1wlS7A7wb8bsDvBvxuwO8G/G7A7wb8bsDvBvxuwO8G/G7A7wb8bsDvBvxuwO8D/D7A7wP8PsDvA/w+wO8D/D7A7wP8PsDvA/w+wO8D/D7A7wP8PsDvB/x+wO8H/H7A7wf8fsDvB/x+wO8H/H7A7wf8fsDvB/x+wO8H/H7ANwDfAHwD8A3ANwDfAHwD8A3ANwDfAHwD8A3ANwDfAHwD8A3AHwD8AcAfAPwBwB8A/AHAHwD8AcAfAPwBwB8A/AHAHwD8AcAfAPwBwE8AfgLwE4CfAPwE4CcAPwH4CcBPAH4C8BOAnwD8BOAnAD8B+AnAHwT8QcAfBPxBwB8E/EHAHwT8QcAfBPxBwB8E/EHAHwT8QcAfBPzBwSVrr1uzZu3Va6/qW3/l6rWXrU9suG792nPXJ/o2JAZahlZvuLzlvKvXbjjv6vWXJVqWLVnasujyDRvWnXrKKYPs20H+7ZJrGUZiwwlZLaglpw4RwnSHO+hH6BY6Su9kWsTH6F30bnoP3cr0iU/Qe+l99H66jWkWn6T/SD9FH6APMh3jIfpp+jB9hH6GaRufpZ+jn6dfoNuZ3pGiX6SP0h30MaaBfIma9HH6BH2S6SL/Qp+i/0qfps8wreSr9N/o1+jX6TeYfrKTfot+m+6i32Gayvfo9+kP6A/pj5jO8hP67/Sn9Fk6wbSX5+l/0N30Bfoi02N+Rv8f/Tn9Bf0l02j+i/5/+hL9Ff01021+Q39Lf0d/T/cwLecP9BX6Kv0f+kem7/yJ/pn+hdp0kmk+f6UO3Uv/RvcxHYhIVJKkkCQzbSgiNUhRaZ4UY3qRIh0kNUkHS4cwDWm+dKh0mKRKzUxXeqN0hPQm6e+kNzOt6UjpKKlFOlo6hulPx0kLpOOlhdIipkmdKJ0knSwtlpYwnWqp1Cotk9okjWlXHVKn1CV1S6cyPes06XTpDOlM6SzpbKZznSu9VXqbdB7TvZZLF0hvl1ZI75AulC6SVkrvlN4lvZtpZD1Sr7RKei/TzN4n/b10iRSXLpXeL32A6Wl9Ur9kSANSQhpkOtvl0mrpCulKaY10FdPfrpbWSddI66VrpQ3SddL10pD0IabTbZSGpRHpRukm6Wbpw9It0q3SJikp3cZ0vc3SHdJHpC3SKNP6Pip9TLpLups2htjEIKSZkMMJ+SihFxPptyR8D5l3M1GWEeVU0pQgh9xF5h9K5i8g8xeT+V1k/jmk+Qbyxt+SI3aQt5xPjtxNjr6CHHM2OfbTZMFR5HiTLDqMLFpAFt1HlpxClpxD2l4k7Y2k/SXS9T1yKiGnvUrOXEzOvpKcexE57zBy3snkvEly/nfJ8kfIBd8jKz5FLnwruegZsvKb5J17ybvDpOdC0nsd6X2ZXPx18r4OEl9ALn036VtL+m4iicdJ4ltk9UVkdQ9Z/RVyxZ/JmveQq24mV19G1l1N1j9KNpxFrltHrj+UXP80uWEFueFlsnGUDL+PjLSSGz9BbrLJh28mtz5FNv2GJJ8ktz1Pbv8c2fJ7cmcz+dj/krt2kbteIHd/kWxdSLYuIltPIFtPJFtPIltPJlsXk61LyNZTyNal5OOHkI+/RO77LnlAJQ81kof3kUci5JH55JGXyD99nHzhcpIaJ48S8uitZMca8s86MQ8iT7xKnryPPPll8tVF5KtnkK/dSL7+J/LNK8m3OskuiXz3dPKDb5MfdpMfbic/Por8+wfJs58mzz1PdsfI7kayWyG7DyK7m8juHeSFd5AXfkde3Ex+9hT5hUz+cx/51Zlkz0/InhfJnpfInj+QPZPkT9eQP59P/vIimdxAnIvJvh1k34/Jvj+Q/QdRto7CW2nDajrvAjrvShq7nSpP0Tc8SdXH6JvitKWZLjiKnvQR2no9bb+Rdq6mpyv0nMfoBTfSdVfQjU/Rjd+kG5+jG5+nG/+DbtxNN75AN75Kh2U63EyHT6bDi+nwEjp8Gh1eQYcvpcNr6fDNdPg2OvwgHR6jww/R4Sfp8Pfp8M/p8Ct0RKYjTXRkER05gY6cSEdOoiMn05HFdGQJHTmFjiylI610ZBkdOZuOrKQjPXTk/XTkcjqygY7cTG96gt56Ib31Gpr8Ir39c/SOIbrlPHqnTu/+Cr3nLvqJG+knG+jD36afeYZ+9g76+ffQ7RfS8aPp+Fl0/FL6xU76qETNT9PHH6ZPDNEnbqFPfIR+eSd96h769OH06SPpM7+jz+yhz/x3qtl+4LUlzgOR1N8OUif32Jfs3dPQ1GQrdqNqWXrE3iaP3rJ///4Tt+/f/1d+2Ne7atoBfsBOuWU0OuQopm5Zpq0MRW4Z5T8a30/Ic3Bm735Ct2/P/7Sf/zT83KrRe6NjekOTHbcddSxy7yj/sjf9e+zE8fTZeZ8I/yn83ui9U/8rfW4W6d7R6Jit6BFHc5TcRValHy17X6vyP8Fds0cbvYWNQO5/pc/NIsFFND3StGvyWHVqBNLjx587O2rZT3wE8i/Sm7vIeO4i47mL9OYu4igF6OM5zIJDBn28AH08h9mb/ymDPs7R+Rtkf6dGKT3GRSMLn+Adsd+FV5EeTEAqunf4BE/GkPg8sZUC9FU5zIJDBn1VAfqqacMPnzLoqzi6pesGn72Kk1QNWzEjTXbL5LWqPhQpP5t7c7N5PG82T03H7EwZn2GmsOdi04v9VgNmNNIvMjOvC87NvqHMQplxlHtzuOO5UdYLbr/ozWeXYub2x/ntW/y1N5RfgSVfTG4SlJ4i2QmUXoHZebs9Jxemv8rsi+ZywdGYMFKGGjDrA/PKcIvbwt1++Vnbm5u143mzls3TdRnJa9rLVcxkRcqa3P9Kn5u3CEQBY3CjecBlZF1WEk4tp6xcXDUNPTOm49kb3p7DnX5uFik3EqWASwrcMsDlbtidIGezBISL6zWXt5Z7c6cVHDL7Z2/Bblrm3CwSWwuigDG4Ucz+71GqldmashtXkVTIzubpWlJ2IbN36CRXwhpvemXyAypOv9LRqotr8PKPUHZBetxHXWxEtpKd5rhZMx19PIfeW7SGBKorTIcquH8XyqmF1ZgdpcFiuqxhmRHTaVFbYrqhIvU83Z5RwRnPzafe7HvnitSx6tGxJlvLzV2MvHe3QpA2R7xgUYxPG7Wi2ZW35Zc5N4vkeV6N5+ZV7wwKWt77LnNuseLnDpfN17g4OwqDC3adlZ4prtUscbaZEy+Q1L6qBxjcvF3c/00RtYuP2XGc3Mmbh2XOnVJp0R4Ac/JgtbyiX91MYXunYYEd4cMrLamYmZZhebH83AkAtkOYyE0FuWMZusm9MIr9rMrNZwu8ANwmaWE2CRP5Y/a4aqdMJxWZchA12dtm1GFex4bK7O2IGE0OY6gUOBSmi6KSPqEyoqicoeLO1xStgQVUWWULxAZbnxZQ0y6k1GBaXgptQ8RxpjLbDFNo91S8wdZWqqLkSSAEIDd/bCXipGRHsWYSANWtJ2HTPjrGNyykqR0I4cyUCieuG3bS1O0JtjfvErjXcgXGVvShhtfLPjp7Dj+2j9opSze4W6SefXkH+E7megeowbrkUSUj/Y+pF0RDKvp5hG+aCjoogngfHiJG7IXYyz15p4Rtyv7vIwK9tbqt6bjwMo97Yl2q3sPLLgLAdlyPOHFZL5CvFWebuMyDSNp4jmfWb6Se128mJhs3nVQDOoLh0TR1Yx8ka2AfeBiqGePppTymlukoDUzUT6jHxJrGJo9V03pZpO70MvZONIsJhlnwRJfwb2G3He7m8mClIjNGhMfRDoTt3NYcjS0GZoCyFSH+nfDdrNAOcrEBcvVM88upX86T5vodJr2PMvfttqWT/xTZyvh/21Rc4AQd4GBiL++C8dev9lbCWSZwf3UdaYl6DMq5DLC6C9xGRUVuUWFzoakGwkLCooBRISx88FDAHnQAue2rNEuQ8cGGJtO+RLWTppOMWHL63yYzF9z3P2SLCu6LAkalx/iTpToduBZxAF8TK6OiUieEOXDnMiurzKwUkMgfiMQUUcAY3ChOm+dpHC2ZDA3d3oa0l3TLEupwgCvM5BWoCx++t4i06Wh2io2uzn1oaNM87skX3hKUTE2Rzj+Lx6gdxcKavpgSjXoVGy59e6au4qrQ9OJ8ACH6tLu85LEZ9dCSNXQpdJAjWffOtGSDHXc09VjuTDs2FiT3fRwnTMub7FX6GIam3Hhu37LlV1ZxtbVBOi7/HJ8nzp+1PkvxeCimoTZSIh4YN3A8IHthXpocZujj6CJn17Yse00pbOlLTSZPihe7BkSacA0V6aVAVB/ZikgTw1asgjD7LEqqdCyxJrNHQ28AyAKwuVc8wyue06ZEFnLOWMjnipjD0QxLT+cEzelI5XUk7jiyNTalNcveq3PCFOA0wZbx5Qswt1FjDwIshX07/BmzPhGUNNZh8iA9Pho2Dj71QG4DMzVTbNk8MHXODMML6ZA+C0wltMn5W3SDc6lEXodaVwqbMTa1auOed3PXUkpAeYYT54ImS84TkCoaMTEHhAe/Zm8UG9l0ofhxRyzbOpSA1BYLfU9c6zsuZgXJuvWalBEQZoEAmeMeDCpbwWWdV+fH9RK3wmdKK7pdSGrmhoTN9JqGmFQL5AYmX6lySGSI19PZtVIsFD6DxBnYXM4qRj7fYk1mkI5OchZE6WDhSqHSISsPWny8ipDAdFq/VbkZvL1idUvVrBoFAszF8uCxf8tTBN3Mo+90nQyCEJNOsgbmZFKcvgVuoiQvBWpTa5DHP6E7E8x0n9D5FTepAXEI1SCFt/KL8KjACMuwmtVSAX1qXdWXJpqEmX2JaupWVuMSQhbHttcUGyTD4j6JJLfMJo9Vxe2A+ey+QmrPLe8kDv4OLZqV2POm6oIrNz8SIiAlTa9BBZ/vtnCak9a0nxXKmD3zdPdpX1eEEotruqPp4CUfq8BhXm4ilbpUuQplt47hDG/ygctME5QyKoT0ZRpZylaCkjDEE/gaDiD2D18KYoTIfx4ftuxGlYdQ7JRlOlqxrJhVS1pURYywyjp/GBSqVEoEBquVgDgQApXGPKsxGYz3szxwuRt22ySAx+FXqEyc8PiovZeLEbtlckd+gn8qJ+178j9ldoVUwe6Wyu0nPfmfMrtNKiAJ/ki+pAJXyeyxvgQibldUgFzFzK2yvQVqDe8n8/bvK7GOU/mfMmg9BYKnp/S5GaSeQonW4xY4u1Kmr6rsmgNu9EZ5Ac+pv1ZFppYWEYe7tMt2MbvMVgxuPnlmca1PNn4NzZWvcUaduKpblqfAzwQPewov/RAVAuCFUTyuthz72IqeT9/jLiy7y34ZG4/URHvDUdzCY5yYkee/GQJFZ2FpHqbmasYmVjXNMa37ph4TjhIxnQlVF+sdihwga8Ky49ZUlhwioK+rjmLykDuahZfPspmopasktUF72mvkekQoAlUk8AtLEHMhxrx0u/GyjDzIKF0vYlj1NYF7WkqwG/dyEt19yJvj1/Wr4MnhPHWk9jk0/otnnn2KLqxHDnC6MST3AL6MzaHXahAcifON0Mw09NBy9+j6nSbzU+Pd5u54WAepvPus0dQrLIHzl0lMbEgTG3qfffLtmsg4AXVocVjawSyPmYAainRJjwlqmMnrekQLPPzmPoYXO1xwW57Et+MpMopICg+ixs/j1N4ScuNq2iKOeHlLSVziw7RZ7i5nUbR7RvycyaXj1kbLxTX6mMbw7oaEPbhqK34Wa7WtxfZfzfWS1Bs0KYncw/EJoe4sOMuwjKD14fDYS6GymwyjnrKXaupmLT2nGNI53bBm3mRLeRWxLG1It1rBAnPDWqJZpo6lVYh6SzkPmLc2rjtxnTf2ABpa0S1/3fIbBKeRa34OIv5tW+JfNoq5SOdkDVAEruO1TnGp2bq4zGycehE0stpZzdnyxrIuIotcIK9pfXpHAlROfwl+C9TEjSK3+I0irrhZ7HvH64CyTC+8V5VlWjUhZKis89S+YXsJeStOkEMnSD0gWkTtU8Gr7NSEZMpJMctBQ1sOjsJNBwVvOoCqLpApx1Z4s7BsO3a+dk0Z/mkyIUkUHxtGjsqU3821mqoUGTCI8K/YLpveskbd5fcVWZHB2Ovdj3zuj2VfrdalLZVJAbRU0zIibObCv8Yu9fiYnXT2qk7SnrAz3UyOj1mOphqWGbHZefxfZ7nspeYVydnrv+rKjUK+bDoFhyLmisVfL8XiWqDChnoD2+JghXJ/YOeB3eQA26ZTtLOk4kqPinXQCSznOgA6+pRwNovSPzx09JlrzYFtzWEEqx+m370shJcKu1LTmS2lLuQFPJuRSco6mh5h9vu8ByKWKlJzFOIIQlOtWHrGRsYw5+Ho76JjFhD04TwNXH0yV6qLYiazdlp429/JFepMufaiOt5WOY/RbHS8eA5H9uslmcOysCyQll/1YuXi6wdsxTeyWjQ6FDEXsonP3WYvqyfEmrbIJ8bsuLNOPSnWZE0ejK1Q0XCxaE+k4N4qHlfyJ8o8XQu20Ew8XxHWlHFFiKnjMgGRqjkqncuwsAlwFvinN4u0ZLEddd2SkPMiU1xOoUifZdbtUG9FkuKoUovSeUSR7LgKAaBpwYW0oo0KZ2lWGgy2q5zMd5XN6LpHoZRrqYoUgHXfB929qEZyTQfFw+s6ybAmC99VuNIbbwPkbFv2JlUoLaaHGoJ4IfGo342yRVVOce5vphsyo49nUhrQxRWbLcK3+2m/6YqW2ElF7HheSbjtqOioAqQGwl9sZmEB+6nL5DJTNzN/sVWflh7hwUT2u+kiLM/MkGXcux7zjkUpyNC7h7cIwhXwiDJ7oyJpuOxtAtMRhLFr8vYHfOVYftUuVFkCK5BBby/OmRXFs1MEIobIhIyjzFiKUp0rFROZi9Zn7jxSnbIVT+Uts+wlgyTYyX+U7biT5L2HkH6VpMX2/DjWk11UAtiTG9NU/qfM6PaAbjLRwBbiFnVxzFGcpGraSSNip8z0ByfFvlrMO5ZsVus51uE7d2sAPB36gZ8zhI+TioqiuFFool7i57NcWVlU9Vh3eQrCSDnEuNXEdNub6n/rke3YXQ0osuOesETooJATi0ldtpXmJbEme5NsTzCTfyLCHQenxNi3PGScyhEPb88RD08nE85SDcMuIbIhODNt/CLWn54lwOEdZSa3abnsyrIGfHEA2HPzLDcNyUxLJLtAYQs6N8TS9WxvcWW4bm0AW7E3qfpMBsCcweizwcibRtnL7WdVXvKn6eLbL80lhArsiRRpegWZJCLM6hKltqO09kAIkjHgN9MsrNNvcoVs6iJDrbqp4wIOyDSdivdexZjyFBbPrXjdJMkZApPe8vIot+c6A6yaoTNAHniZcwtyyOakQBClgDm3u9b37qobaBlut9iOauRZf+5r77EsRJz6yzI4+2wnNp+Sx0VNHh9FZwVC2XpLjiutPgvcfKdNMPGtrbG0WcxCRTs4DJGZW157urmQnLrpVKYVK0dCVFZ+TssAqkKh8NdLiaXZL/Kf9OQeMZX/KTMUPTlbOz2Bsu8hlbtIT/6nzHtIuX4PNe+YiXSPuVNGi/selZoMld5DfXSsLDHzKzeqggkUzymZASn9czVzilgNXCTU6J4J64QSoFXTM7RUljw6NIImbWWTxpzRMKkQvHVF0YN9jkC0DPXAQ4hVRYc8Zb4J8woj5gz/Y6cmr52uR/sfpHFxgWJtb2Jyp9HQtEW2JwpSUEpp7MVOAMMyLHGR0SmTYHKHnDd1U7mb6sn/lJmrqZxF4Ll7j5v2EmByTO50JiJs6AptDwGScGy6ceMy4Wdys4ztTAeNDAqUAVHdjwQoA7ppa7hoGd7P5L68TkgRlsAYlqPxaApSL6pUioVmFcUzuJek7mowdgXDrB9y4qI5lRHEeY6Cb2KEi29zldzk5Gav2PerwngjxXKasdnziiquZNASp8wA+4BsKwVhaFc9X7A+PkcxvDKZCyDpjgclR4vPfFvxLYJRrfDR5/jci6aKFZSZwhaqo6iFbMC+ciDiOiBYOq5YTVjN3VgE6DCXxniXOLU15vB/ApKLbXJK4aAUnmte6k+F5MAZwGodCJaVLEGHwBRWbJK2KPIq15VpaFqeoOWDudy6kKqmS02nLilqfWpFhKnTr7Znj2XhutHmkaAGJM3VZTlsGYldJVu1OBpsC3JbxzNtbBX7ktynTcWfTCQnclAKyOqOLRZDwGBa6QalVtq7p5uRwnbzboSJmKklqpeZMI4HYc3XvOhDs9pTxrP0FUZG4KrIwDTUZTE73twWc+K6KipFBBXg97itVzH/SmdtoLclV2YKG2tRmq2HbjdoLqLZZmVyOcpeumQrBj6nylN3ecWwDGwP1kw0IvmiymkgvUx7l9a/+x512HEymUbU4Gi7CjwZLaquWxaPUuGVA2F5rKgAkWafr2oxa0w1dWwD6uku49ljduJ9HSOO8oraHjMC419yNMvCk1tm4i347BQnyS5na+LTKmdbGRSkTNjxIdV0NHGVHFg/RVB4h1y3S8YQnCV17hi+31OfJGT35mrmSYnsJbZAufTXTLVmJKeVu4hwxja9YtJPcbNgBV1LLWTjZkO6KxM6QWW34IKc+BUlgCcOz+KALOaCZXWU2sHMIovpKo7GtsMTmK4ypHbCV60xvukfpXalf9Qd47RBp8J0PiG2S25nP46nf8xO1tlP2Un8x80nxIxd7BPT4pjKIGosPTg6XcpTBywXdO2VsEbxrvIimGrJ3gpbpmqg9EtX+mHhgLuOlgkr7nIliNAVS0jiHk4GOpOjxUWfOTeX8WDVBaTexFvK9CzXBPJURcvkfLFxZ5tagwQA3+l2gtOOz1XuG7r/RCDcCNiGGWyn1YLDAS8ywO8oafJfW0EywAtMtUJn92NUV82z6uqmhtZOTR4XmAQOpcHWnFT6/X5JticsZyJimaqdtJwkfNAsR+MfpoKPp8X0IfWA7tJVaAa4U03Y0LRNGxpf64UFcxILGxprV/1PmcpbjZcpw+sih+b6vAS7z4t4J4wgcsSaWmt+UMIK7O2GaFCKNuR9Vy4DkTItrEGpoxi8m5CdzEVRZ3m485XJudZcr7/WXJy0tGEuO0p0dhRb+I6WWfmbgyVoxXCOCWO+n/VstDn5N9eacNZbE051zC2SVan8T1nexIK1k8pNp578T5lJmErLqjjIqniO+8gdI0LeZCxzbhZJYIOkgDAEiOeAmhNYcwKr3gTW9CsU0IpkSUcKbr0yBUl53tdykhDN2VGZOBDDDJZnuPZUanaOEt26k8yX3Lkflf7FLCzuIj65HAPL7TInueck95zkrlrVdMWQKFwQYuk7Bauwrr3HaGI+Yc26BdWqi+hhMye55yT3nOSub8mN0VoF69wu22A5SZntNFbOryu0yrMadnc/NhphHAGzv9EIyhrlKWTZKvi5bWluW3rdb0v50xBHmSli04uiZL2Z9oyPTa6Y4lOb/VrOgDRuHvKPprHqGj7NryTTqosGLF13FEvnqcQtqigXF5K9mN0QjnY5yhkERdV9wm4QrF4Arsakacx2VFwBjNiVPa39tpiVgtgbMpM/YvOQommZOjq5HF2v4rYxN6bLnTnXK642veI8MwL6wAfuGw9e5WYpYrU8l9Tl4njcdC/qnXs223TfeFENdYTFtoRLMl8b0nvI3fNfVUFux2kRLYoJb/Yr71AVFuI693ls3ITs8CPUhKj7dNSqOcHFWeBcRNuaUGJkYZljwmS0KPeSmBYt4mS0vfxBT3akS0vPfSNCVPsqR2v256YxDB3V3rSdNCtFJDC8IrUpnJoaasxNl50f1S4XlzdtpWmXmjtjfNA7YpUfwnMMH2W5osjY2UNo/M51eIilRQ/hukOQDyu1uoc4nd95NzwEsAO1xwpfT4Uvhbnoco7pohlYrkE9Lmdba/bnxkus9Rk96tXeeJGUcpmg4y4xFJMXiosDsOE+A7KLPiCSE8I9yWrtLY7S3YbrNvztbxMif7qRTRdvqIbgHntazP6c8J2Cte6SkAUrBGhyat+5l9DEpH6zaQcjOeR1YRi6XR1CeA1EMpOh/a6uJKYnIobZ95wIueFoDfz8/rYoF9bDSSDpsvAF4lLKuwu0RQUmCuCNUjFdbYQJzdmP/Qi5YWH5ptHinpBFpmEgKukOrEZT9ZMBIZrQzXeiOzHxQH92JoxNWqdzotwQV3Tle5oT7iOiZXssYpoxTXOAiGqdMNPejzL7Pfee8JNoGBNKC0SzjHJpQRVDRnXdg8/XVDEPGqxAtv+5voxzFJdemOXnGD1qwujhhcXD7xuOihrivPlW5jVnJ4Fvc6LEEOc8bgLCFMhFEkXyDwgKVeTuusjTm8r/lIHrQZMm1Flb+zm2xhqwNeJ1WWRY02UcVpjN638cti7mRMkhLhPM8xxfifoYEcJkeVZLze0Llziqx3OVnTUE9nj22yCbYSQwoe7qDLKoPyYkJtQdCPtmduZENfYNJtQdCJu3xA3PqLWV1TVX5dS26Wl32aQ8nrk9uUP2hwt2Ovic978W7VzqNiJUMVwRFdtr1WXxdQ1aHx0Yc6LcEJe6YTzDhGePOkef3Jml6hHl7HBJr1VM1eOvK4VXaMW9VdG6Sv1A+g9QXo+obqinxGytuS3GPrXEZvrvQn7wkoyLdIpX5llApQ/bkMyPtlz9TurE3TQb6TP5cJ8VK7x9X/sfCb39lqLZ4tKedW1s4SMSrizwysNdjRGOzah1edNNtrJFtSZ3pvsqPqs6imErTir9Df9jb5LPjtlxJ5U+C3oSnMOZrbdkv9i7MwvATzVldvKEZe91UhHeJ3xcdeKGnbInpn6XgY7LcBV7wonDhcdVO246ip3Mv4+J3DfZ01O2YjoTkfRNp/+XO2NBrPAJ8n/cxO/kWfXcGLuFCdVOmo5mp/ivsad4K/+SPZ09YWumk4THWMCe2Eyfnntadl7cUPkDLsh+Ycr8lPznX5D/4/T5hUhTf3I3G0/fytQjsSHZIp8YYwd2Tyn+vOyTNrnT0dhp2hY193k8d4Kd8kSLMvtNXDRsq2sdTcWjiWMyEekwdV9gMmYnZ+r6WKq4M66ry2JNr6Q7mdc3DeGBy52mBIU7zVEaTC9aqoCmy0HxHInJcrUcjSlvTXajzLcjxdDZPhW3NWgdPxFhG2sLU44sQ21j57Tkah4D8kpEMCryB9cs2MjZiIhfD27NajHmmqhQS9TU+bxpXhIz+AemLdj3q6JicpjKiyiO8Eb3izU3ODPecy4q26mY7lfgJam873M9cYfqxVmCpAz3t6zpgHzzxbJuwp6Y3Fmgx1Uuf0+bCL7JxfK9NYX1RHbJkiSwN6pXnuLpDr5yyV2lUqVKJHcNgZ03MRapEKws96i+xpmD0f+56MWLIor0PYjtnoGSvfTJzXIg2PLy2rYc4EzzSaGtlt3PKm5sRoSxLfhoQFa5MYw5yQZbWckDAsqQyqyZiLEQFNK2mKMshC+sIfaFo+k+RQmqSy1BRgmU5jNjRu4xBFoHWJe7S67HpleYKXlcjJn4i2IWNxAcxVCXxWxtpphNdVR8PpGBl1jdus4taAtJCMTEU2pqKSI9dMJatvhK6VmL3mD+0kSj7nhsSg1273xuMB1NNSwzwqY9/8dItwZAMgf4X7uD5L+3lXTTI8x0Vwqmu5skaTSNnltmCcRLdhS9on5cjUaIDyQj68FqxUtbHtilZiecZbni7p4X63HtvEfGejz1+hFGQeTe+TeEbBsikhcGnc4uKJt0lns1jemm3tBk2W2qZeoR094WDELCMR3ZYGZItwwd3SetbjP5fOd2EGWzal66o6GtSmH7Xt0VbvtcpIsBHrLqcE8Sya2GlmK1qLLyOx1QGFEgRq0Q14MKtQnpzCwbsgy2Exm+eYB9cgnVTu6hGhigeWIK57Vpp9QzYoJrS4SZFELYjvlqaBDIACms+hqhHGaSBzfJAcm+EkYEPYvvsmwSYEX2JlwS4OTOdKbo1KsUxTzllmVIUFcz3zoTzdij0n1auw9kTsWZfxOwLMcL32XlBgHByXfz9i5F9Xx2nzMvgksERbgz5kw0NJmTH1CPj1kZ37StpJ3TjuIs51qR2Ji4oFKaMs9kOElVt6zIDD9+Wyz7w5XqkMf5UjekzMzSMSyd/UXHhYPDhuVKubKcbXlv1mnJiw3rbJrXIAjjdoK7DaKmH8VcqApsv4FtROL/zJ+F8MQBkjEuhmvAC/+Eu2o2pTkQTdLnqKdq0xVhlnM8xXWH15kOcVZ2p7GTC4WVyfF4ZYMwl+mQbtnJBkdL64COwjQhfxSw5aqolga8gMNLi1qXnD5jVgMvotykWhavn2yx71dP57UNyFwIYZWQQzp3Xxoz+lTLrVy32aOOYuqiEzkEGIns3ZmWaSsmVs9FJmvjkunteK44AsfzKag4QoAyNCRwtvBkCtMqcFn0lGacyAh6kXStTRaythU9NMU+xcqR5LH8FGi/M+vYkiqIGlbuhjyG3K54ujKyzrhpl92mLmZCWbMbVUOv+Nuem+xhyeCQzOqu+9oK63vmHhg7D1DtdoYiTaNNTfbEYaOq3ThqJ0dtZTTypdGGUSX2ZnLEoeR4iVDyOH0TPYYuCr0z9F75KPmn4dsiF0Te1aBGb4jeMk+b93Lsk40XKkQxDxpsOqLpBwfffMiyQ371hm3zV8x/z6GRQ586bI16lHqCukxdrT7U/L4aJFP7rG42IpcI1qXFpaweaeT9cGwl0qjbihVpDEgRf3yuiH+aKz8eoCL+Rk6pMjHTdCldmThT8sUMBTaYUhVkDSOUlDR6afJWw+qEUtJBlK9NXHZ5YzValJvILDZl18Lm+et84kN4tBHpi5+6kssCdVxoJi+P13XArKGxBoXQlXOzRZHkCo9LulLtG+24nXTiOEk5C/pHqVv3Mj2EtXJxN9oCu1ThS3zd9P+ONgr0jQviAPFpYtQyBtFYA/pCn/n0GkU1MPPUXtzVrtXopbZL0Fx2aTQ1CmxnH2yJgYn9+yExRAs5MXO52uWHqQzwaZSFzmX3xQyY5Sd0lMtolZ4nBtPolIxGJ1o+C9i4c4nUpfsDZLsHFNyyr853VOuxaC0sc59JEGqhN/ucU9doOUnLTjlapFE3bI0z3TZyRhFcsZEdZ79m2ckCS9RF9JX7k5iBHKhhcxs3bvw/7Zog+AB42mNgZGBg4ANiCQYFIMnEwMjAyMgFJFnAPAYGRggGAApeAGAAAAABAAAAAMbULpkAAAAAzAgvTwAAAADMCE7feNqdVMENhDAMMzt0AX4ZggcjZIgbIkMwxI1KETkpstIe4WEVJbbrNBLLjnXZgQ7tkI5G+HhPnSdeu3oWeJmWYa6VgPYQWuDKpKZ0Iswz89HE420OCz0UEH1AO0HyDbo31iJfyDfLLMWsm+OnMwIeeEqSvfpOjea1iU4HdfujV9JqMe/I+5vsrE1mfrOnePdB/4JrfwrcnBMROVxI)\n       format('woff'),\n\n       /* mzxfont.ttf */\n       url(data:application/font-sfnt;charset=utf-8;base64,AAEAAAAOAIAAAwBgRkZUTWGZ7WAAAIDsAAAAHEdERUYBOgAkAACAxAAAAChPUy8yVyxSugAAAWgAAABgY21hcMHJx5AAAAPoAAABimN2dCAAIQJ5AAAFdAAAAARnYXNw//8AAwAAgLwAAAAIZ2x5Zs6SkMcAAAeQAABtXGhlYWT4g9/cAAAA7AAAADZoaGVhBDYBRAAAASQAAAAkaG10eA8ADX0AAAHIAAACHmxvY2Hu0AjMAAAFeAAAAhhtYXhwAWkB8QAAAUgAAAAgbmFtZbk3zYgAAHTsAAACW3Bvc3Ql5wQsAAB3SAAACXQAAQAAAAEAAAISU+lfDzz1AB8CMAAAAADMCE8LAAAAAMwITwsAAP//AUACmgAAAAgAAgAAAAAAAAABAAACmv//AFoBQAAAAAABQAABAAAAAAAAAAAAAAAAAAAABAABAAABCwHAABwAAAAAAAIAAAABAAEAAABAAC4AAAAAAAQBQAH0AAUAAAKKArsAAACMAooCuwAAAd8AMQECAAACAAYJAAAAAAAAAAAAARAAAEAAAAAAAAAAAFBmRWQAwAAg4P8CMAAAAFoCmgABAAAAAQAAAAABZwHfAAAAIAABAUAAIQAAAAABQAAAAUAAAABQACgAFAAUABQAFABkAFAAUAAAACgAZAAUAHgAFAAUACgAFAAUABQAFAAUABQAFAAUAHgAZAAoACgAKAAUABQAFAAUABQAFAAUABQAFAAUAFAAFAAUABQAFAAUABQAFAAUABQAFAAoABQAFAAUABQAKAAUAFAAFABQABQAAABkABQAFAAUABQAFAAoABQAFABQACgAFABQABQAFAAUABQAFAAUABQAFAAUACgAFAAUABQAFAAoAHgAKAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUABQAFAAAAFAAAAAoAAAAAAAoAAAAAAAAABQAFAAoAAAAAAAUAAAAAAAoACgAFAAUAAAAFAAUABQAFAAoACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAPAAoAAAAeAB4AHgAeAAAACgAAAAAAAAAAAAAAAAAAAAoABQAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAUABQAFAAUAAAAAAB4AAAAAAAAAAAAAAAAAFAAAAAAAAAAAAAAAHgAAAAAAHgAAAAAAAAAAABQAFAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAAAAKAAAAAAABQAAAAUAAAAAAAAAAAAAAAUACgAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAjAAAAFAAKAA8AFAAAAAAAAMAAAADAAAAHAABAAAAAACEAAMAAQAAABwABABoAAAAFgAQAAMABgB+IhoiICIrIjUiUiJhIqXgH+D///8AAAAgIhoiICIpIjUiUiJhIqXgAeB/////495I3kPeO94y3hbeCN3FIGogCwABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGYAAAAAAAAAAGIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAnkAAAAqACoAKgAqAEwAbACqAQABUgG2Ac4CBAI6AnIChgKeAqwCuAMEA0wDbAPAA/oEPgRoBKAEzgUOBUQFVgVyBdIF5AZEBngGpAbmBx4HbgeuB/IILgh+CJQIuAjgCRIJPgluCZwJ8AogClYKiArWCwALHgtaC3wLzAv6DFIMZAywDMINBA0QDSgNXA2MDboN+g4qDmwOqg7aDvwPJA9oD4YPqA/MD/QQLBBkEJoQ3BEMETARVhF+EdoSBhJIEnASghKqEtYS1hLWEtYS1hLWEtYS1hLWEtYTHhNgE6YUFBR2FMwU7hUYFVYVjhX+Fk4WghaoFugXGBdIF4wXsBfOGGQYhhjMGPQZHBlGGXAZthn8GhwaPBpiGqga7hssG2obahtqG2obahuOG7Ab1Bv4HDIcfhzWHSgd4h3uHfoeBh4SHrIe3B8kH3QftiAWIGQgsiEAIU4hqCHaIhQiMCJQIooi/iNqI9wkYiTeJW4l+iZeJqInDCd2KLAq7CweLCwsPiw+LD4sPiw+LFosbiyGLJ4sniyeLK4svizQLOIs9C0ALRYtFi0WLTAtSi1oLYgtrC2+Lewt7C3sLewt7C3sLewt7C3sLewt7C38LgwuGi4oLjQuQi5QLpIu6C8WL2wvmi/IL/QwIDDGMTQxfDHyMq4y3DMIMzQzYjOiM9Y0FjR0NLI1MjV2Nbg1xDXWNhY2QjZ2NoI2rgACACEAAAEqApoAAwAHAC6xAQAvPLIHBADtMrEGBdw8sgMCAO0yALEDAC88sgUEAO0ysgcGAfw8sgECAO0yMxEhESczESMhAQnox8cCmv1mIQJYAAACAFAAdwDwAd8AAwAVAAA3NTMVAzUzFRQWOwEVIxUjNSM1MzI2eFBQUAUPFChQKBQPBXdQUAFUFBQPBXhQUHgFAAAAAAIAKAFnARgCBwAJABMAABM1MxUjIgYUBiMnNTMVIyImNCYjyFAUDwUFD7RQFA8FBQ8BZ6B4BR4FKHigBR4FAAACABQAdwEsAd8AKwAvAAATNTMVMzUzFTMyFhQGKwEVMzIWFAYrARUjNSMVIzUjIiY0NjsBNSMiJjQ2Mxc1IxU8UChQFA8FBQ8UFA8FBQ8UUChQFA8FBQ8UFA8FBQ+MKAGPUFBQUAUeBXgFHgVQUFBQBR4FeAUeBaB4eAAAAQAUACcBLAIvAEUAABM1MxUzMhYUFjsBFSMiJjQmIiY9ASMVMxUUFjsBFSMiBhQGKwEVIzUjNTQmKwE1MzIWFBYyFh0BMzUjNTQmKwE1MzI2PQGMUBQPBQUPFBQPBQUeBXigBQ8UFA8FBQ8UUFAFDxQUDwUFHgV4oAUPFBQPBQHfUFAFHgVQBR4FBQ8UeBQPBXgFHgVQUBQPBVAFHgUFDxR4FA8FeAUPFAAAAAMAFAB3ASwBjwADADcAOwAANzUzFSY0NjsBFSMiBhQGIgYUBiIGFAYiBhQGIgYdASM1NDYyNjQ2MjY0NjI2NDYyNjQ2MjY0NjIHNTMV3FAoBQ8UFA8FBR4FBR4FBR4FBR4FUAUeBQUeBQUeBQUeBQUeBQUe61B3UFD1HgVQBR4FBR4FBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FKFBQAAAABAAUAHcBLAHfADcAOwBDAE0AABM1MxUUFjsBFSMiBh0BMxUUBisBFTMyFh0BIzU0JiIGHQEjNTQmKwE1MzI2NDYyNjQmKwE1MzI2FzUjFRY0JiIGFBYyBjQmKwEVMzUjImR4BQ8UFA8FUAUPFBQPBVAFHgV4BQ8UFA8FBR4FBQ8UFA8FUChQBR4FBR5LBQ8UUBQPAcsUFA8FUAUPFBQPBXgFDxQUDwUFDxQUDwV4BR4FBR4FUAVVUFBLHgUFHgUjHgV4UAAAAAABAGQBZwDcAgcADQAAEzUzFSMiBh0BIzU0NjOMUBQPBVAFDwGPeHgFDxQUDwUAAAABAFAAdwDwAd8AJwAAEzUzFRQGIgYUBisBFTMyFhQWMhYdASM1NCYiJjQmKwE1MzI2NDYyNqBQBR4FBQ8UFA8FBR4FUAUeBQUPFBQPBQUeBQHLFBQPBQUeBcgFHgUFDxQUDwUFHgXIBR4FBQABAFAAdwDwAd8AJwAAEzUzFRQWMhYUFjsBFSMiBhQGIgYdASM1NDYyNjQ2OwE1IyImNCYiJlBQBR4FBQ8UFA8FBR4FUAUeBQUPFBQPBQUeBQHLFBQPBQUeBcgFHgUFDxQUDwUFHgXIBR4FBQABAAAAxwFAAY8AKwAAEzUzFTM1MxUUBiIGHQEzFSMVFBYyFh0BIzUjFSM1NDYyNj0BIzUzNTQmIiYoUFBQBR4FUFAFHgVQUFAFHgVQUAUeBQF7FCgoFA8FBQ8UKBQPBQUPFCgoFA8FBQ8UKBQPBQUAAQAoAMcBGAGPAAsAABM1MxUzFSMVIzUjNXhQUFBQUAE/UFAoUFAoAAEAZABPANwA7wANAAA3NTMVIyIGHQEjNTQ2M4xQFA8FUAUPd3h4BQ8UFA8FAAAAAAEAFAEXASwBPwADAAATNSEVFAEYARcoKAAAAAABAHgAdwDIAMcAAwAANzUzFXhQd1BQAAABABQAnwEsAd8ANwAAADQ2OwEVIyIGFAYiBhQGIgYUBiIGFAYiBhQGIgYUBisBNTMyNjQ2MjY0NjI2NDYyNjQ2MjY0NjIBBAUPFBQPBQUeBQUeBQUeBQUeBQUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeAbweBVAFHgUFHgUFHgUFHgUFHgUFHgVQBR4FBR4FBR4FBR4FBR4FAAMAFAB3ASwB3wAXACYANAAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzUjFTMyNjQ2MjY0NjI2FTUjIgYUBiIGFAYrARU8yAUPFBQPBcgFDxQUDwWgeBQPBQUeBQUeBRQPBQUeBQUPFAHLFBQPBf7oBQ8UFA8FARgFGRR4BR4FBR4FBfWgBR4FBR4FUAAAAAEAKAB3ARgB3wATAAATNTMRMxUjNTM1IzU0NjI2NDYyNnhQUPBQUAUeBQUeBQHLFP7AKCjIFA8FBR4FBQAAAAABABQAdwEsAd8APwAAEzUzFRQWOwEVIyIGFAYiBhQGIgYUBiIGFAYiBh0BMzUzFSE1MzI2NDYyNjQ2MjY0NjI2NDY7ATUjFSM1NDYyNjzIBQ8UFA8FBR4FBR4FBR4FBR4FeFD+6BQPBQUeBQUeBQUeBQUPFHhQBR4FAcsUFA8FUAUeBQUeBQUeBQUeBQUPFChQUAUeBQUeBQUeBQUeBVAoFA8FBQAAAAABABQAdwEsAd8ALQAAEzUzFRQWOwEVIyIGFBY7ARUjIgYdASM1NCYiJj0BMxUzNSM1MzUjFSM1NDYyNjzIBQ8UFA8FBQ8UFA8FyAUeBVB4eHh4UAUeBQHLFBQPBXgFHgV4BQ8UFA8FBQ8UKHgoeCgUDwUFAAIAFAB3ASwB3wAoADMAABM1MxUzMhYUBisBFTMyFh0BIzU0NjsBNSM1MzI2NDYyNjQ2MjY0NjI2FTUjIgYUBiIGHQG0UBQPBQUPFBQPBaAFDxSgFA8FBR4FBR4FBR4FFA8FBR4FAcsUyAUeBVAFDxQUDwVQUAUeBQUeBQUeBQWlUAUeBQUPFAABABQAdwEsAd8AHQAAEzUhFSMVMxUUFjsBFSMiBh0BIzU0JiImPQEzFTM1FAEYyKAFDxQUDwXIBR4FUHgBF8goeBQPBXgFDxQUDwUFDxQoeAAAAAACABQAdwEsAd8AJAAoAAATNTMVIxUUBisBFTMVFBY7ARUjIgYdASM1NCYrATUzMjY0NjI2EzUjFWR4UAUPFKAFDxQUDwXIBQ8UFA8FBR4FeHgByxQoFA8FUBQPBXgFDxQUDwXwBR4FBf7jeHgAAAAAAQAUAHcBLAHfAB8AABM1IRUjIgYUBiIGFAYrARUjNTMyNjQ2MjY0NjsBNSMVFAEYFA8FBR4FBQ8UUBQPBQUeBQUPFHgBj1B4BR4FBR4FoKAFHgUFHgVQKAAAAAADABQAdwEsAd8AJwArAC8AABM1MxUUFjsBFSMiBhQWOwEVIyIGHQEjNTQmKwE1MzI2NCYrATUzMjYXNSMVFzUjFTzIBQ8UFA8FBQ8UFA8FyAUPFBQPBQUPFBQPBaB4eHgByxQUDwV4BR4FeAUPFBQPBXgFHgV4BX14eKB4eAAAAAIAFAB3ASwB3wAkACgAABM1MxUUFjsBFSMiBhQGIgYdASM1MzU0NjsBNSM1NCYrATUzMjYXNSMVPMgFDxQUDwUFHgWgeAUPFKAFDxQUDwWgeAHLFBQPBfAFHgUFDxQoFA8FUBQPBXgFfXh4AAIAeACfAMgBtwADAAcAADc1MxUnNTMVeFBQUJ9QUMhQUAAAAAIAZAB3ANwBtwANABEAADc1MxUjIgYdASM1NDYzNzUzFYxQFA8FUAUPFFCfUFAFDxQUDwXIUFAAAQAoAHcBGAHfAEcAABM1MxUUBiIGFAYiBhQGIgYUBiIGFBYyFhQWMhYUFjIWFBYyFh0BIzU0JiImNCYiJjQmIiY0JiImNDYyNjQ2MjY0NjI2NDYyNshQBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FUAUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQHLFBQPBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUAAgAoAMcBGAFnAAMABwAANzUzFSc1MxUo8PDwxygoeCgoAAAAAQAoAHcBGAHfAEcAABM1MxUUFjIWFBYyFhQWMhYUFjIWFAYiBhQGIgYUBiIGFAYiBh0BIzU0NjI2NDYyNjQ2MjY0NjI2NCYiJjQmIiY0JiImNCYiJihQBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FUAUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQHLFBQPBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUAAgAUAHcBLAHfAAMAJQAANzUzFQM1MxUUFjsBFSMiBhQGKwEVIzUzMjY0NjsBNSMVIzUzMjaMUKDIBQ8UFA8FBQ8UUBQPBQUPFHhQFA8Fd1BQAVQUFA8FUAUeBVBQBR4FUFBQBQAAAAABABQAdwEsAd8AHwAAEzUzFRQWOwEVIyIGHQEjNTM1IxEzFSM1NCYrAREzMjY8yAUPFBQPBXhQeKDIBQ8UFA8FAcsUFA8FyAUPFKBQ/ugoFA8FARgFAAAAAgAUAHcBLAHfACEALwAAEjQ2MhYUFjIWFBYyFhQWOwEVIzUjFSM1MzI2NDYyNjQ2MhY0JiIGFAYrARUzNSMijAUeBQUeBQUeBQUPFFB4UBQPBQUeBQUeLQUeBQUPFHgUDwG8HgUFHgUFHgUFHgXweHjwBR4FBR4FSx4FBR4FUFAAAAAAAwAUAHcBLAHfAB8AIwAnAAATNTMVFBY7ARUjIgYUFjsBFSMiBh0BIzU0NjsBESMiJhc1IxUXNSMVFPAFDxQUDwUFDxQUDwXwBQ8UFA8FyFBQUAHLFBQPBXgFHgV4BQ8UFA8FARgFfXh4oHh4AAAAAAEAFAB3ASwB3wA/AAATNTMVFBY7ARUjIiY0JiImPQEjFRQGKwEVMzIWHQEzNTQ2MjY0NjsBFSMiBh0BIzU0JiImNCYrATUzMjY0NjI2ZKAFDxQUDwUFHgVQBQ8UFA8FUAUeBQUPFBQPBaAFHgUFDxQUDwUFHgUByxQUDwVQBR4FBQ8UFA8FyAUPFBQPBQUeBVAFDxQUDwUFHgXIBR4FBQACABQAdwEsAd8AHwAvAAATNTMVFBYyFhQWOwEVIyIGFAYiBh0BIzU0NjsBESMiJhY0JisBETMyNjQ2OwE1IyIUyAUeBQUPFBQPBQUeBcgFDxQUDwWgBQ8UFA8FBQ8UFA8ByxQUDwUFHgXIBR4FBQ8UFA8FARgFKB4F/ugFHgXIAAEAFAB3ASwB3wAzAAATNSEVIyImNCYiJj0BIxUzMjY0NjsBFSMiJjQmKwEVMzU0NjI2NDY7ARUhNTQ2OwERIyImFAEYFA8FBR4FUBQPBQUPFBQPBQUPFFAFHgUFDxT+6AUPFBQPBQHLFHgFHgUFDxR4BR4FeAUeBXgUDwUFHgV4FA8FARgFAAABABQAdwEsAd8ALQAAEzUhFSMiJjQmIiY9ASMVMzI2NDY7ARUjIiY0JisBFTMyFh0BIzU0NjsBESMiJhQBGBQPBQUeBVAUDwUFDxQUDwUFDxQUDwWgBQ8UFA8FAcsUeAUeBQUPFHgFHgV4BR4FeAUPFBQPBQEYBQABABQAdwEsAd8APgAAEzUzFRQWOwEVIyImNCYiJj0BIxUUBisBFTMyFh0BMzUjNTMVIyImNCYiBh0BIzU0JiImNCYrATUzMjY0NjI2ZKAFDxQUDwUFHgVQBQ8UFA8FUFCgFA8FBR4FeAUeBQUPFBQPBQUeBQHLFBQPBVAFHgUFDxQUDwXIBQ8UUCigBR4FBQ8UFA8FBR4FyAUeBQUAAAAAAQAUAHcBLAHfAAsAADcRMxUzNTMRIzUjFRRQeFBQeHcBaKCg/pigoAAAAAABAFAAdwDwAd8AFwAAEzUzFRQGKwERMzIWHQEjNTQ2OwERIyImUKAFDxQUDwWgBQ8UFA8FAcsUFA8F/ugFDxQUDwUBGAUAAAABABQAdwEsAd8AGwAAEzUzFRQGKwERIyIGHQEjNTQmKwE1MxUzESMiJoygBQ8UFA8FoAUPFFBQFA8FAcsUFA8F/ugFDxQUDwVQUAEYBQAAAAEAFAB3ASwB3wAlAAATNTMVMzUzNTMVIxUjIgYUFjsBFTMVIzUjNSMVIzU0NjsBESMiJhR4KChQKBQPBQUPFChQKCh4BQ8UFA8FAcsUoFBQUFAFHgVQUFBQoBQPBQEYBQAAAAABABQAdwEsAd8AHQAAEzUzFRQGKwERMzU0NjI2NDY7ARUhNTQ2OwERIyImFKAFDxRQBR4FBQ8U/ugFDxQUDwUByxQUDwX+6BQPBQUeBXgUDwUBGAUAAAAAAQAUAHcBLAHfACEAADcRMxUUFjIWFBYyNjQ2MjY9ATMRIzUjIgYUBiImNCYrARUUUAUeBQUeBQUeBVBQFA8FBR4FBQ8UdwFoFA8FBR4FBR4FBQ8U/pjIBR4FBR4FyAAAAQAUAHcBLAHfACAAADcRMxUUFjIWFBYyFhQWOwE1MxEjNSMiJjQmIiY0JisBFRRQBR4FBR4FBQ8UUFAUDwUFHgUFDxR3AWgUDwUFHgUFHgV4/ph4BR4FBR4FyAACABQAdwEsAd8AJwA/AAATNTMVFBYyFhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2FjQmIgYUBisBFTMyFhQWMjY0NjsBNSMiZHgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FUAUeBQUPFBQPBQUeBQUPFBQPAcsUFA8FBR4FyAUeBQUPFBQPBQUeBcgFHgUFKB4FBR4FyAUeBQUeBcgAAAACABQAdwEsAd8AHgAiAAATNTMVFBY7ARUjIgYdASMVMzIWHQEjNTQ2OwERIyImFzUjFRTwBQ8UFA8FeBQPBaAFDxQUDwXIUAHLFBQPBXgFDxR4BQ8UFA8FARgFfXh4AAACABQATwEsAd8AGwAnAAATNTMVFBY7ARUjFTMyFh0BIzUjNTQmKwE1MzI2FzUjFTM1MzIWFBYzPMgFDxQoFA8FeHgFDxQUDwWgeCgUDwUFDwHLFBQPBfBQBQ8UUBQPBfAFzcjwUAUeBQAAAAACABQAdwEsAd8AIAAkAAATNTMVFBY7ARUjFTMVIzUjIiY0JisBFSM1NDY7AREjIiYXNSMVFPAFDxQoKFAUDwUFDxR4BQ8UFA8FyFAByxQUDwV4UHh4BR4FoBQPBQEYBX14eAAAAQAUAHcBLAHfAD8AABM1MxUUFjsBFSM1IxUzMhYdATMVFBYyFhQWOwEVIyIGHQEjNTQmKwE1MxUzNSMiJj0BIzU0JiImNCYrATUzMjY8yAUPFFB4FA8FUAUeBQUPFBQPBcgFDxRQeBQPBVAFHgUFDxQUDwUByxQUDwVQUFAFDxQUDwUFHgVQBQ8UFA8FUFBQBQ8UFA8FBR4FUAUAAQAoAHcBGAHfAB0AABM1MxUjIiY0JisBFTMyFh0BIzU0NjsBNSMiBhQGIyjwFA8FBQ8UFA8FoAUPFBQPBQUPAWd4eAUeBfAFDxQUDwXwBR4FAAAAAQAUAHcBLAHfABEAADcRMxEzETMRIyIGHQEjNTQmIxRQeFAUDwXIBQ+fAUD+wAFA/sAFDxQUDwUAAAAAAQAUAHcBLAHfACsAADc1MxUzMhYUFjI2NDY7ATUzFSMiBhQGIgYUBiIGFAYiJjQmIiY0JiImNCYjFFAUDwUFHgUFDxRQFA8FBR4FBR4FBR4FBR4FBR4FBQ/v8PAFHgUFHgXw8AUeBQUeBQUeBQUeBQUeBQUeBQAAAQAUAHcBLAHfABcAADcRMxUzNTMVMzUzESMVIzU0JiIGHQEjNRRQKCgoUChQBR4FUMcBGPBQUPD+6FAUDwUFDxRQAAABABQAdwEsAd8APwAAEzUzFTMyFhQWMjY0NjsBNTMVIyIGFAYrARUzMhYUFjsBFSM1IyImNCYiBhQGKwEVIzUzMjY0NjsBNSMiJjQmIxRQFA8FBR4FBQ8UUBQPBQUPFBQPBQUPFFAUDwUFHgUFDxRQFA8FBQ8UFA8FBQ8Bj1BQBR4FBR4FUFAFHgV4BR4FUFAFHgUFHgVQUAUeBXgFHgUAAQAoAHcBGAHfACEAABM1MxUzNTMVIyIGFAYrARUzMhYdASM1NDY7ATUjIiY0JiMoUFBQFA8FBQ8UFA8FoAUPFBQPBQUPAT+goKCgBR4FeAUPFBQPBXgFHgUAAAABABQAdwEsAd8AQgAAEzUhFSMiBhQGIgYUBiIGFAYiBhQGKwEVMzU0NjI2NDY7ARUhNTMyNjQ2MjY0NjI2NDYyNjQ2MjY9ASMVFAYiBhQGIxQBGBQPBQUeBQUeBQUeBQUPFHgFHgUFDxT+6BQPBQUeBQUeBQUeBQUeBXgFHgUFDwFneFAFHgUFHgUFHgUFHgVQFA8FBR4FeHgFHgUFHgUFHgUFHgUFDxQUDwUFHgUAAAEAUAB3APAB3wAHAAA3ETMVIxEzFVCgUFB3AWgo/ugoAAAAAAEAFAB3ASwB3wA3AAATNTMyFhQWMhYUFjIWFBYyFhQWMhYUFjIWFBY7ARUjIiY0JiImNCYiJjQmIiY0JiImNCYiJjQmIxQUDwUFHgUFHgUFHgUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgUFDwFneAUeBQUeBQUeBQUeBQUeBQUeBXgFHgUFHgUFHgUFHgUFHgUFHgUAAQBQAHcA8AHfAAcAABM1MxEjNTMRUKCgUAG3KP6YKAEYAAAAAQAUAY8BLAIvAC8AABI0NjIWFBYyFhQWMhYUFjIWHQEjNTQmIiY0JiIGFAYiBh0BIzU0NjI2NDYyNjQ2MowFHgUFHgUFHgUFHgVQBR4FBR4FBR4FUAUeBQUeBQUeAgweBQUeBQUeBQUeBQUPFBQPBQUeBQUeBQUPFBQPBQUeBQUeBQAAAQAAAHcBQACfAAMAAD0BIRUBQHcoKAAAAQBkAbcA3AIvAA0AABM1MxUzMhYdASM1NCYjZFAUDwVQBQ8B31BQBQ8UFA8FAAAAAgAUAHcBLAFnACMAJwAAEzUzFRQWOwEVMzIWHQEjNTQmIgYdASM1NCYrATUzMjY9ATM1FTUjFTygBQ8UFA8FUAUeBXgFDxQUDwV4UAE/KBQPBaAFDxQUDwUFDxQUDwVQBQ8UKKBQUAACABQAdwEsAd8AGAAiAAATNTMVMxUUFjIWFBY7ARUjIgYdASMRIyImFjQmKwEVMzUjIhR4UAUeBQUPFBQPBcgUDwWgBQ8UUBQPAcsUeBQPBQUeBXgFDxQBQAWgHgWgeAABABQAdwEsAWcAIQAAEzUzFRQWMhYdASM1IxUzNTMVFAYiBh0BIzU0JisBNTMyNjzIBR4FUHh4UAUeBcgFDxQUDwUBUxQUDwUFDxQooCgUDwUFDxQUDwWgBQAAAAIAFAB3ASwB3wAlAC8AABM1MxEzMhYdASM1NCYiBh0BIzU0JisBNTMyNjQ2MjY9ATM1IyImEzUjIgYUBisBFYx4FA8FUAUeBXgFDxQUDwUFHgVQFA8FKBQPBQUPFAHLFP7ABQ8UFA8FBQ8UFA8FeAUeBQUPFFAF/uOgBR4FeAAAAgAUAHcBLAFnAB4AIgAAEzUzFRQWOwEVIxUzNTMVFAYiBh0BIzU0JisBNTMyNhc1IxU8yAUPFMh4UAUeBcgFDxQUDwWgeAFTFBQPBVBQKBQPBQUPFBQPBaAFLSgoAAAAAQAoAHcBGAHfADMAABM1MxUUFjsBFSMiJjQmIiY0JisBFTMyFhQGKwEVMzIWHQEjNTQ2OwE1IyImNDY7ATUzMjZ4eAUPFBQPBQUeBQUPFBQPBQUPFBQPBaAFDxQUDwUFDxQUDwUByxQUDwVQBR4FBR4FeAUeBXgFDxQUDwV4BR4FeAUAAgAUACcBLAFnACsALwAAEzUzFRQWMjY9ATMVFAYrARUjIgYdASM1NCYiJj0BMxUzNSM1NCYrATUzMjYXNSMVPHgFHgVQBQ8UFA8FoAUeBVBQeAUPFBQPBXhQAVMUFA8FBQ8UFA8F8AUPFBQPBQUPFChQFA8FeAV9eHgAAAEAFAB3ASwB3wAjAAATNTMVMzI2PQEzFRQWOwEVIzUjIgYUBisBFSM1NDY7AREjIiYUeBQPBVAFDxRQFA8FBQ8UeAUPFBQPBQHLFKAFDxQUDwXIyAUeBaAUDwUBGAUAAAIAUAB3APAB3wASABYAABM1MxUzMhYdASM1NDY7ATUjIiY3NTMVUHgUDwWgBQ8UFA8FKFABUxTIBQ8UFA8FoAVLUFAAAgAoACcBGAHfABYAGgAAEzUzESMiBh0BIzU0JisBNTMVMzUjIiY3NTMVoHgUDwWgBQ8UUFAUDwUoUAFTFP7oBQ8UFA8FUFDwBUtQUAAAAAABABQAdwEsAd8AMwAAEzUzFTMyNjQ2MjY9ATMVFAYiBhQGIgYUFjIWFBY7ARUjNSMiJjQmKwEVIzU0NjsBESMiJhR4FA8FBR4FUAUeBQUeBQUeBQUPFFAUDwUFDxR4BQ8UFA8FAcsUyAUeBQUPFBQPBQUeBQUeBQUeBVBQBR4FeBQPBQEYBQAAAQBQAHcA8AHfABIAABM1MxEzMhYdASM1NDY7AREjIiZQeBQPBaAFDxQUDwUByxT+wAUPFBQPBQEYBQAAAQAUAHcBLAFnABgAADc1MxUUFjI2PQEzFRQWOwEVIzUjFSM1IxUUeAUeBVAFDxRQKCgod/AUDwUFDxQUDwXIoHh4oAABABQAdwEsAWcAGQAAEzUzFRQWMjY9ATMVFBY7ARUjNSMVIzUjIiYUUAUeBXgFDxRQUFAUDwUBUxQUDwUFDxQUDwXIyMjIBQACABQAdwEsAWcAFwAbAAATNTMVFBY7ARUjIgYdASM1NCYrATUzMjYXNSMVPMgFDxQUDwXIBQ8UFA8FoHgBUxQUDwWgBQ8UFA8FoAWloKAAAAIAFAAnASwBZwAmACoAABM1MxUUFjI2PQEzFRQWOwEVIyIGHQEjFTMyFh0BIzU0NjsBNSMiJhc1IxUUUAUeBXgFDxQUDwV4FA8FoAUPFBQPBchQAVMUFA8FBQ8UFA8FeAUPFFAFDxQUDwXwBX14eAACABQAJwEsAWcAJgAqAAATNTMVFBYyNj0BMxUUBisBFTMyFh0BIzU0NjsBNSM1NCYrATUzMjYXNSMVPHgFHgVQBQ8UFA8FoAUPFHgFDxQUDwV4UAFTFBQPBQUPFBQPBfAFDxQUDwVQFA8FeAV9eHgAAQAUAHcBLAFnACkAABM1MxUUFjI2PQEzFRQWOwEVIzUjIgYUBisBFTMyFh0BIzU0NjsBNSMiJhRQBR4FeAUPFFAUDwUFDxQUDwWgBQ8UFA8FAVMUFA8FBQ8UFA8FUFAFHgV4BQ8UFA8FoAUAAQAUAHcBLAFnADMAABM1MxUUFjIWHQEjNSMVMxUzFRQWMhYUBiIGHQEjNTQmIiY9ATMVMzUjNSM1NCYiJjQ2MjY8yAUeBVB4UFAFHgUFHgXIBR4FUHhQUAUeBQUeBQFTFBQPBQUPFCgoKBQPBQUeBQUPFBQPBQUPFCgoKBQPBQUeBQUAAQAUAHcBLAHfACMAABI0NjsBFTMVIxUzMjY9ATMVFAYiBh0BIzU0JisBNSM1MzUzMowFDxRQUBQPBVAFHgV4BQ8UUFAUDwG8HgV4KKAFDxQUDwUFDxQUDwWgKFAAAAAAAQAUAHcBLAFnABkAADc1MxUzNTMVMzIWHQEjNTQmIgYdASM1NCYjFFBQUBQPBVAFHgV4BQ+fyMjIyAUPFBQPBQUPFBQPBQAAAQAoAHcBGAFnABkAADc1MxUzNTMVIyIGFAYiBh0BIzU0JiImNCYjKFBQUBQPBQUeBVAFHgUFD8egoKCgBR4FBQ8UFA8FBR4FAAAAAAEAFAB3ASwBZwAdAAA3NTMVMzUzFTM1MxUjIgYdASM1NCYiBh0BIzU0JiMUUCgoKFAUDwVQBR4FUAUPn8igUFCgyAUPFBQPBQUPFBQPBQAAAQAUAHcBLAFnAEcAABM1MxUUFjIWFBYyNjQ2MjY9ATMVFAYiBhQGKwEVMzIWFBYyFh0BIzU0JiImNCYiBhQGIgYdASM1NDYyNjQ2OwE1IyImNCYiJhRQBR4FBR4FBR4FUAUeBQUPFBQPBQUeBVAFHgUFHgUFHgVQBR4FBQ8UFA8FBR4FAVMUFA8FBR4FBR4FBQ8UFA8FBR4FUAUeBQUPFBQPBQUeBQUeBQUPFBQPBQUeBVAFHgUFAAEAFAAnASwBZwAfAAA3NTMVMzUzFSMiBhQGIgYdASM1MzU0NjI2PQEjNTQmIxRQeFAUDwUFHgXIoAUeBaAFD8egoKDwBR4FBQ8UKBQPBQUPFBQPBQAAAAABABQAdwEsAWcALwAAEzUhFRQGIgYUBiIGFAYiBhQGIgYdATM1MxUhNTQ2MjY0NjI2NDYyNjQ2MjY9ASMVFAEYBR4FBR4FBR4FBR4FUFD+6AUeBQUeBQUeBQUeBVABF1AUDwUFHgUFHgUFHgUFDxQoUBQPBQUeBQUeBQUeBQUPFCgAAAABACgAdwEYAd8AHQAAEzUzFSMVIyIGFBY7ARUzFSM1NCYrATUjNTM1MzI2oHhQFA8FBQ8UUHgFDxRQUBQPBQHLFCh4BR4FeCgUDwV4KHgFAAIAeAB3AMgB3wADAAcAADc1MxUnNTMVeFBQUHegoMigoAAAAAEAKAB3ARgB3wAdAAATNTMVFBY7ARUzFSMVIyIGHQEjNTM1MzI2NCYrATUoeAUPFFBQFA8FeFAUDwUFDxQBtygUDwV4KHgFDxQoeAUeBXgAAQAUAY8BLAHfAB8AABM1MxUUFjI2PQEzFRQGIgYdASM1NCYiBh0BIzU0NjI2PHgFHgVQBR4FeAUeBVAFHgUByxQUDwUFDxQUDwUFDxQUDwUFDxQUDwUFAAUAAABPAUAB3wAPABMAFwAvADMAADc1MxUUBiIGHQEjNTQmIiY3NTMVIzUzFSc1MxUUFjsBESMiBh0BIzU0JisBETMyNhMRIxFQoAUeBVAFHgV4KKAoUPAFDxQUDwXwBQ8UFA8F8PDbFBQPBQUPFBQPBQVzUFBQUIwUFA8F/sAFDxQUDwUBQAX+uwFA/sAAAAQAAABPAUAB3wAXABsAHwAvAAATNTMVFBY7AREjIgYdASM1NCYrAREzMjYXNSMVMzUjFRc1IxUUFjIWHQEzNTQ2MjYo8AUPFBQPBfAFDxQUDwVQKKAoKKAFHgVQBR4FAcsUFA8F/sAFDxQUDwUBQAV9UFBQUGQUFA8FBQ8UFA8FBQAAAAIAFAB3ASwBtwAvADMAABM1MxUUFjI2PQEzFRQWOwEVIyIGFAYiBhQGIgYUBiImNCYiJjQmIiY0JisBNTMyNhc1IxU8UAUeBVAFDxQUDwUFHgUFHgUFHgUFHgUFHgUFDxQUDwVQKAGjFBQPBQUPFBQPBaAFHgUFHgUFHgUFHgUFHgUFHgWgBVVQUAAAAwAUAJ8BLAG3ADcARwBPAAASNDYyFhQWMhYUFjIWFBYyFhQGIgYUBiIGFAYiBhQGIiY0JiImNCYiJjQmIiY0NjI2NDYyNjQ2MhY0JiIGFAYiBhQWMjY0NjIWNCYiBhQWMowFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHi0FHgUFHgUFHgUFHi0FHgUFHgGUHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUjHgUFHgUFHgUFHgVzHgUFHgUAAAMAFABPASwCBwA7AEMASwAAEzUzFRQWOwEVMxUjFRQWOwEVMzIWHQEjNSMVIzUzMjY0NjI2NCYiJjQmIiY0NjI2NDYyNjQmKwE1MzI2FjQmIgYUFjIWNCYiBhQWMmRQBQ8UUFAFDxQUDwV4UFAUDwUFHgUFHgUFHgUFHgUFHgUFDxQUDwUoBR4FBR4tBR4FBR4B8xQUDwV4eBQPBVAFDxRQUFAFHgUFHgUFHgUFHgUFHgUFHgVQBSgeBQUeBZseBQUeBQACAAAAdwFAAgcAOQBBAAATNTMyFhQWMhYUFjIWFBYyFhQWOwEVIyIGHQEjFTMyFh0BIzU0NjsBNSM1NCYrATUzMjY0NjI2NDYzFjQmIgYUFjJ4FA8FBR4FBR4FBR4FBQ8UFA8FUBQPBaAFDxRQBQ8UFA8FBR4FBQ88BR4FBR4Bt1AFHgUFHgUFHgUFHgVQBQ8UUAUPFBQPBVAUDwVQBR4FBR4FIx4FBR4FAAAAAQBQAMcA8AFnABcAABM1MxUUFjsBFSMiBh0BIzU0JisBNTMyNnhQBQ8UFA8FUAUPFBQPBQFTFBQPBVAFDxQUDwVQBQACAAD//wFAAi8AAwAbAAAVESERAzUjFRQGKwEVMzIWHQEzNTQ2OwE1IyImAUB4UAUPFBQPBVAFDxQUDwUBAjD90AFUFBQPBVAFDxQUDwVQBQAAAAACACgAnwEYAY8AFwAvAAATNTMVFBY7ARUjIgYdASM1NCYrATUzMjYXNSMVFAYrARUzMhYdATM1NDY7ATUjIiZQoAUPFBQPBaAFDxQUDwV4UAUPFBQPBVAFDxQUDwUBexQUDwWgBQ8UFA8FoAUZFBQPBVAFDxQUDwVQBQAAAgAA//8BQAIvAAMAKAAAFREhEQM1IxUUBisBFTMVMzIWFAYiBh0BMzUjIiY0NjI2NDY7ATUjIiYBQHhQBQ8UKBQPBQUeBVAUDwUFHgUFDxQUDwUBAjD90AGkFBQPBVB4BR4FBQ8UeAUeBQUeBVAFAAIAAABPAUACBwBGAFYAABM1MxUUFjsBFSMiBhQGIgYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNj0BMxUUFjI2NDY7ATUjFRQGIiY0NjI2BjQmIgYUBiIGFBYyNjQ2MqB4BQ8UFA8FBR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgFHgUFDxR4BR4FBR4FKAUeBQUeBQUeBQUeAfMUFA8FeAUeBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeBQPBQUeBQXIHgUFHgUFHgUFHgUAAAAAAgAoACcBGAIHADcAOwAAEzUzFRQWOwEVIyIGFAYiBh0BMxUUBiIGFAYiBhQWMhYUBiIGFAYiBhQGKwERIyImNCYrATUzMjYXNSMVUKAFDxQUDwUFHgVQBR4FBR4FBR4FBR4FBR4FBQ8UFA8FBQ8UFA8FeFAB8xQUDwV4BR4FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FARgFHgV4BX14eAAAAAIAAAB3AUAB3wAcACQAADc1MxUUBiIGFAYiBh0BIxUjIgYdASM1MzI2NDYzNjQmIgYUFjJQ8AUeBQUeBVAUDwV4FA8FBQ+MBR4FBR7v8BQPBQUeBQUPFMgFDxRQBR4FpR4FBR4FAAAAAgAAAE8BQAHfABUAGQAANxEhESMiBh0BIzUzNSMVIyIGHQEjNTc1IxUoARgUDwVQKHgUDwVQ8HjHARj+wAUPFHh48AUPFHjIKCgAAAIAAACfAUABtwAvADMAABM1MxUzMjY9ATMVIxUzFSMVMxUjNTQmKwEVIzUjIgYdASM1MzUjNTM1IzUzFRQWMxc1IxV4UBQPBVBQUFBQUAUPFFAUDwVQUFBQUFAFD2RQAWdQUAUPFCgoKCgoFA8FUFAFDxQoKCgoKBQPBVAoKAAAAQAUAHcBLAHfACEAADcRMzIWFBYyFhQWMhYdATMVMxUjFSMVFAYiBhQGIgYUBiMUFA8FBR4FBR4FUFBQUAUeBQUeBQUPdwFoBR4FBR4FBQ8UKCgoFA8FBR4FBR4FAAAAAQAUAHcBLAHfACEAAAA0NjsBESMiJjQmIiY0JiImPQEjNSM1MzUzNTQ2MjY0NjIBBAUPFBQPBQUeBQUeBVBQUFAFHgUFHgG8HgX+mAUeBQUeBQUPFCgoKBQPBQUeBQAAAQAoAHcBGAHfADMAABM1MxUUFjIWFBYyFh0BIxUzFRQGIgYUBiIGHQEjNTQmIiY0JiImPQEzNSM1NDYyNjQ2MjZ4UAUeBQUeBVBQBR4FBR4FUAUeBQUeBVBQBR4FBR4FAcsUFA8FBR4FBQ8UeBQPBQUeBQUPFBQPBQUeBQUPFHgUDwUFHgUFAAEAAP//AUACLwAYAAARNSEVFAYrARUzMhYdASMVIzUjIiY9ATM1AUAFDxQUDwXIUBQPBcgCBygUDwXwBQ8U8PAFDxTwAAAAAAQAAP//AUACBwADAAcACwAPAAAXNTMVITUzFRM1MxUhNTMVeMj+wCjwKP7AyAHw8PDwARjw8PDwAAAAAAMAFAAnASwCBwBbAGsAcwAAEzUzFRQWOwEVIyImPQEjFRQWMhYUFjsBFSMiBhQWMhYUFjIWFAYiBh0BIzU0JiImNDYyFhQWMjY0NjI2NCYiJjQmIiY0JisBNTMyNjQ2MjY0JiImNCYiJjQ2MjYWNCYiBhQWMhYUFjI2NCYiNjQmIgYUFjI8yAUPFBQPBVAFHgUFDxQUDwUFHgUFHgUFHgXIBR4FBR4FBR4FBR4FBR4FBR4FBQ8UFA8FBR4FBR4FBR4FBR4FUAUeBQUeBQUeBQUecwUeBQUeAfMUFA8FeAUPFBQPBQUeBXgFHgUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgUFHgVQBR4FBR4FBR4FBR4FBSgeBQUeBQUeBQUeBQUeBQUeBQAAAAMAAADvAUABZwADAAsAEwAAPQEhFSY0JiIGFBYyNjQmIgYUFjIBQMgFHgUFHn0FHgUFHu94eC0eBQUeBQUeBQUeBQAAAQAA//8BQAIvADcAAAA0NjsBFSMVIyIGFAYrARUjFSMiBhQGKwEVIyIGFAYrATUzNTMyNjQ2OwE1MzUzMjY0NjsBNTMyARgFDxQoFA8FBQ8UKBQPBQUPFBQPBQUPFCgUDwUFDxQoFA8FBQ8UFA8CDB4FeFAFHgVQUAUeBVAFHgV4UAUeBVBQBR4FUAABACgAdwEYAd8AGwAAEzUzFRQWMhYUFjIWHQEjFSM1IzU0NjI2NDYyNnhQBR4FBR4FUFBQBR4FBR4FAcsUFA8FBR4FBQ8U8PAUDwUFHgUFAAEAKAB3ARgB3wAbAAA3NTMVMxUUBiIGFAYiBh0BIzU0JiImNCYiJj0BeFBQBR4FBR4FUAUeBQUeBe/w8BQPBQUeBQUPFBQPBQUeBQUPFAAAAQAUAMcBLAGPABsAABM1MzIWFBYyFhQWMhYUBiIGFAYiBhQGKwE1IzW0FA8FBR4FBR4FBR4FBR4FBQ8UoAE/UAUeBQUeBQUeBQUeBQUeBVAoAAAAAQAUAMcBLAGPABsAABI0NjsBFTMVIxUjIiY0JiImNCYiJjQ2MjY0NjJkBQ8UoKAUDwUFHgUFHgUFHgUFHgFsHgVQKFAFHgUFHgUFHgUFHgUAAAAAAQAA//8BQAIvADcAABE1MzIWFBY7ARUzMhYUFjsBFTMVMzIWFBY7ARUzFSMiJjQmKwE1IyImNCYrATUjNSMiJjQmKwE1FA8FBQ8UFA8FBQ8UKBQPBQUPFCgUDwUFDxQUDwUFDxQoFA8FBQ8UAbd4BR4FUAUeBVBQBR4FUHgFHgVQBR4FUFAFHgVQAAABABQAxwEsAY8AMwAAEjQ2OwEVMzUzMhYUFjIWFBYyFhQGIgYUBiIGFAYrATUjFSMiJjQmIiY0JiImNDYyNjQ2MmQFDxQoFA8FBR4FBR4FBR4FBR4FBQ8UKBQPBQUeBQUeBQUeBQUeAWweBVBQBR4FBR4FBR4FBR4FBR4FUFAFHgUFHgUFHgUFHgUAAAEAFACfASwBtwAVAAATNTMVMxUzFTMyFh0BITU0NjsBNTM1jCgoKBQPBf7oBQ8UKAFnUFBQUAUPFBQPBVBQAAABABQAnwEsAbcAFQAAEzUhFRQGKwEVIxUjFSM1IzUjNSMiJhQBGAUPFCgoKCgoFA8FAaMUFA8FUFBQUFBQBQAAAQAUAMcBLAFnABkAABI0NjsBFTMyFh0BMxUhNTQ2MjY0NjI2NDYyjAUPFBQPBVD+6AUeBQUeBQUeAUQeBVAFDxQoFA8FBR4FBR4FAAEAKP//ARgCLwA3AAATNTMVFAYrARUjFTMyFhQWMhYUFjsBFTMVIyIGHQEjNTQ2OwE1IzUjIiY0JiImNCYrATUzNTMyNnhQBQ8UKBQPBQUeBQUPFCgUDwVQBQ8UKBQPBQUeBQUPFCgUDwUCGxQUDwVQeAUeBQUeBVB4BQ8UFA8FeFAFHgUFHgV4UAUAAQAo//8BGAIvADcAABM1MxUUFjsBFTMVIyIGFAYiBhQGKwEVIxUzMhYdASM1NCYrATUzNTMyNjQ2MjY0NjsBNSM1IyImeFAFDxQoFA8FBR4FBQ8UKBQPBVAFDxQoFA8FBR4FBQ8UKBQPBQIbFBQPBVB4BR4FBR4FUHgFDxQUDwV4UAUeBQUeBXhQBQABAAAAxwFAAY8ALwAAEzUzFRQWOwEVIyImPQEjFRQGIgYUBiIGHQEjNTQmKwE1MzIWHQEzNTQ2MjY0NjI2yFAFDxQUDwVQBR4FBR4FUAUPFBQPBVAFHgUFHgUBexQUDwVQBQ8UFA8FBR4FBQ8UFA8FUAUPFBQPBQUeBQUAAQAAAMcBQAGPAC8AABM1MxUUFjIWFBYyFh0BMzU0NjsBFSMiBh0BIzU0JiImNCYiJj0BIxUUBisBNTMyNihQBR4FBR4FUAUPFBQPBVAFHgUFHgVQBQ8UFA8FAXsUFA8FBR4FBQ8UFA8FUAUPFBQPBQUeBQUPFBQPBVAFAAEAAP//AUACLwAYAAABNTMRITU0NjsBNTM1MzUzMjY0NjsBNTM1ARgo/sAFDxQoKBQPBQUPFCgB31D90BQPBVBQUAUeBVBQAAEAAP//AUACLwAYAAAVETMVMxUzFTMyFhQWOwEVMxUzFTMyFh0BKCgoFA8FBQ8UKCgUDwUBAjBQUFAFHgVQUFAFDxQAAQAA//8BQAIvABgAABE1IREjNSM1IzUjIiY0JisBNSM1IzUjIiYBQCgoKBQPBQUPFCgoFA8FAhsU/dBQUFAFHgVQUFAFAAAAAQAA//8BQAIvABgAABURIRUUBisBFSMVIxUjIgYUBisBFSMVIxUBQAUPFCgoFA8FBQ8UKCgBAjAUDwVQUFAFHgVQUFAAAAAAAQAAAWcBQAIHACsAABE1MxUUFjIWFBYyNjQ2MjY0NjsBFSM1IyIGFAYiJjQmKwEVIzUjFSM1IyImoAUeBQUeBQUeBQUPFCgUDwUFHgUFDxQoKCgUDwUB8xQUDwUFHgUFHgUFHgWgUAUeBQUeBVB4eHgFAAACADwAdwEEAd8AFwA7AAATNTMVFAYrARUzMhYdASM1NCYrATUzMjYnNTMVIxUUBisBFTMyFh0BMxUjNTQmIiY0JisBNTMyNjQ2Mja0UAUPFBQPBVAFDxQUDwUoeHgFDxQUDwV4eAUeBQUPFBQPBQUeBQF7FBQPBXgFDxQUDwV4BV8UKBQPBcgFDxQoFA8FBR4FyAUeBQUAAAMAPAB3AQQB3wAPAB8AQwAAEzUzFRQGIgYdASM1NDYyNic1MxUUFjIWHQEjNTQmIiY9ATMVFBYyFhQWOwEVIyIGFAYiBh0BIzUzNTQ2OwE1IyImPQFkUAUeBVAFHgUoUAUeBVAFHgV4BR4FBQ8UFA8FBR4FeHgFDxQUDwUBAxQUDwUFDxQUDwUFhxQUDwUFDxQUDwUFSygUDwUFHgXIBR4FBQ8UKBQPBcgFDxQAAAAAAgAoAJ8BGAGPACcAPQAAEzUzFRQWMhYUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNhc1IyIGFAYiBh0BMxUzMjY0NjI2PQF4UAUeBQUPFBQPBQUeBVAFHgUFDxQUDwUFHgUoFA8FBR4FUBQPBQUeBQF7FBQPBQUeBVAFHgUFDxQUDwUFHgVQBR4FBVVQBR4FBQ8UUAUeBQUPFAAAAAAEAAAATwFAAd8ABwAPAD8AlQAANjQ2MhYUBiI2NDYyFhQGIic1MxUUFjIWFBY7ARUjFTMVIyIGFAYiBh0BIzU0JiImNCYrATUzNSM1MzI2NDYyNhc1IxUUBiIGFAYiBhQWMjY0NjI2PQEzFRQGKwEVIxUUBiIGFBYyFhQWMhYdATM1NDYyNjQ2MjY0JiIGFAYiBh0BIzU0NjsBNTM1NDYyNjQmIiY0JiImUAUeBQUecwUeBQUefaAFHgUFDxQoKBQPBQUeBaAFHgUFDxQoKBQPBQUeBXhQBR4FBR4FBR4FBR4FUAUPFFAFHgUFHgUFHgVQBR4FBR4FBR4FBR4FUAUPFFAFHgUFHgUFHgXMHgUFHgV9HgUFHgWMFBQPBQUeBVBQUAUeBQUPFBQPBQUeBVBQUAUeBQUZFBQPBQUeBQUeBQUeBQUPFBQPBVAUDwUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFDxQUDwVQFA8FBR4FBR4FBQABAHgBFwDIAWcAAwAAEzUzFXhQARdQUAABAHgAxwDIARcAAwAANzUzFXhQx1BQAAABAHgA7wDIAT8AAwAANzUzFXhQ71BQAAABAHgA7wDIAT8AAwAANzUzFXhQ71BQAAAFAAAAdwFAAbcASQBRAFkAaQB5AAASNDYyFhQWMjY9ATMVFBYyNjQ2MhYUFjIWFAYiBhQWMhYUBiIGFBY7ARUjNSMVIzUjFSM1IxUjNTMyNjQmIiY0NjI2NCYiJjQ2MhY0JiIGFBYyNjQmIgYUFjIGNCYiBhQWMhYUFjI2NCYiNjQmIgYUBiIGFBYyNjQ2MigFHgUFHgVQBR4FBR4FBR4FBR4FBR4FBR4FBQ8UKCgoUCgoKBQPBQUeBQUeBQUeBQUeLQUeBQUezQUeBQUewwUeBQUeBQUeBQUewwUeBQUeBQUeBQUeAZQeBQUeBQUPFBQPBQUeBQUeBQUeBQUeBQUeBQUeBVBQeHh4eFBQBR4FBR4FBR4FBR4FIx4FBR4FBR4FBR4FSx4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FAAAAAAEAKABPARgB3wAfAAATNTMVFAYrARUzFTMVIyIGHQEjNTQmKwE1MzUzNSMiJlCgBQ8UKCgUDwWgBQ8UKCgUDwUByxQUDwV4UHgFDxQUDwV4UHgFAAQAAAAnAUACBwARABkAIQAzAAA3NTMVIxUjNTQmIiY0NjIWHQE8ATYyFhQGIjY0NjIWFAYiJzUzFRQWMhYUBiImPQEjFSM18FBQoAUeBQUeBQUeBQUecwUeBQUefaAFHgUFHgWgUE9QUCgUDwUFHgUFDxR9HgUFHgV9HgUFHgWgKBQPBQUeBQUPFFBQAAAAAAQAAAAnAUACBwAHAB0AJQA7AAA2NDYyFhQGIjc1MxUjIgYUBiIGHQEjNTM1NDY7ATUmNDYyFhQGIic1MxUjFRQGKwEVMxUjNTMyNjQ2MjagBR4FBR5LUBQPBQUeBXh4BQ8UoAUeBQUeLXh4BQ8UKFAUDwUFHgWkHgUFHgUoUKAFHgUFDxQoFA8FUKUeBQUeBYwUKBQPBVBQoAUeBQUAAAACAAAAdwFAAbcAFwAvAAASNDYyFhQWMhYUBiImNCYrARUzFSM1MzIzNTMVIyIGFAYiJjQmIiY0NjIWFBY7ATUoBR4FBR4FBR4FBQ8UKFAUD81QFA8FBR4FBR4FBR4FBQ8UAWweBQUeBQUeBQUeBaBQ8FDwBR4FBR4FBR4FBR4FoAAAAAAEAAAATwFAAd8ABwAPACwASQAANjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYrARUzMhYdATMVIzUjIiY0JisBNTMyNzUzFTMyFhQWOwEVIyIGFAYiJjQ2OwE1IyImPQFQBR4FBR5zBR4FBR6lBR4FBQ8UFA8FUFAUDwUFDxQUD31QFA8FBQ8UFA8FBR4FBQ8UFA8F9B4FBR4FLR4FBR4FLR4FBR4FeAUPFFBQBR4FeFBQUAUeBXgFHgUFHgV4BQ8UAAAFAAAAdwFAAbcABwAPACcALwA3AAA2NDYyFhQGIjY0NjIWFAYiJzUzFRQWOwEVIyIGHQEjNTQmKwE1MzI2IjQ2MhYUBiI2NDYyFhQGIlAFHgUFHsMFHgUFHqVQBQ8UFA8FUAUPFBQPBXgFHgUFHsMFHgUFHnweBQUeBVUeBQUeBYwUFA8FUAUPFBQPBVAFHgUFHgVVHgUFHgUAAAAFAAAAdwFAAbcABwAPABcALwA3AAA2NDYyFhQGIiY0NjIWFAYiJDQ2MhYUBiInNTMVFBY7ARUjIgYdASM1NCYrATUzMjY8ATYyFhQGIqAFHgUFHqUFHgUFHgETBR4FBR6lUAUPFBQPBVAFDxQUDwUFHgUFHnweBQUeBX0eBQUeBS0eBQUeBTwUFA8FUAUPFBQPBVAFUB4FBR4FAAAFAAAAdwFAAbcABwAPABcALwA3AAA2NDYyFhQGIiY0NjIWFAYiJDQ2MhYUBiInNTMVFBY7ARUjIgYdASM1NCYrATUzMjYmNDYyFhQGIsgFHgUFHs0FHgUFHgETBR4FBR6lUAUPFBQPBVAFDxQUDwUoBR4FBR58HgUFHgVVHgUFHgV9HgUFHgUUFBQPBVAFDxQUDwVQBVAeBQUeBQAFACgAnwEYAY8ABwAPACcALwA3AAA2NDYyFhQGIiY0NjIWFAYiNzUzFRQWOwEVIyIGHQEjNTQmKwE1MzI+ATQ2MhYUBiImNDYyFhQGIvAFHgUFHs0FHgUFHktQBQ8UFA8FUAUPFBQPBXgFHgUFHs0FHgUFHqQeBQUeBQUeBQUeBbQUFA8FUAUPFBQPBVAFKB4FBR4FBR4FBR4FAAACABQATwEsAbcANQBFAAATNTMVIxUzFRQWMhYUBiImPQEjFRQWMhYUFjsBFSMiBh0BIzU0JisBNTMyNj0BMzUjNTQmIiYWNCYiBhQGIgYUFjI2NDYyFPBQUAUeBQUeBVAFHgUFDxQUDwWgBQ8UFA8FeHgFHgXIBR4FBR4FBR4FBR4BoxQoKBQPBQUeBQUPFBQPBQUeBXgFDxQUDwWgBQ8UKBQPBQXwHgUFHgUFHgUFHgUAAAACACgAdwEYAY8AHQAhAAATNTMVFBYyFhQGIgYUFjsBFSM1MzI2NCYiJjQ2MjYXNSMVUKAFHgUFHgUFDxTwFA8FBR4FBR4FeFABexQUDwUFHgUFHgWgoAUeBQUeBQVVKCgAAAAAAgAAACcBQAIHACUAKQAAEzUzFRQWMhYUFjsBESE1IyImNDY7ATUjIiY0NjsBNTMyNjQ2MjYXNSMVeHgFHgUFDxT+6BQPBQUPFBQPBQUPFBQPBQUeBaBQAfMUFA8FBR4F/nBQBR4FoAUeBVAFHgUF9SgoAAAEAAAAJwFAAd8AAwAHAAsADwAAPQEhFSc1MxUnNTMVJzUzFQFA8PCgoFBQJ1BQeFBQeFBQeFBQAAIAAADvAUABZwADABMAADc1MxUkNDY7ARUjIiY0JiImNDYyeMj+6AUPFBQPBQUeBQUe73h4VR4FeAUeBQUeBQAEAAAAnwFAAbcAAwATABcAJwAANzUzFSQ0NjsBFSMiJjQmIiY0NjI3NTMVJDQ2OwEVIyImNCYiJjQ2MnjI/ugFDxQUDwUFHgUFHlXI/ugFDxQUDwUFHgUFHp94eFUeBXgFHgUFHgVQeHhVHgV4BR4FBR4FAAMAAABPAUABjwA3AEcAVwAAEzQ2MhYUFjI2NDYyFhQGIgYUFjIWFAYrARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjY9ATMWNCYiBhQWMhYUFjI2NCYiBjQmIgYUBiIGFBYyNjQ2MsgFHgUFHgUFHgUFHgUFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeCgFHgUFHgUFHgUFHn0FHgUFHgUFHgUFHgF7DwUFHgUFHgUFHgUFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFCMeBQUeBQUeBQUeBSMeBQUeBQUeBQUeBQACAAAATwFAAd8AQwBTAAASNDYyFhQWMhYUBiIGFBYyFh0BIxUUFjsBFSMiBhQGIgYdASM1NCYiJjQmKwE1MzI2NDYyNj0BMxUUFjsBNTMyNjQmIgY0JiIGFAYiBhQWMjY0NjLwBR4FBR4FBR4FBR4FUAUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BQ8UFA8FBR59BR4FBR4FBR4FBR4BvB4FBR4FBR4FBR4FBQ8UFA8FeAUeBQUPFBQPBQUeBXgFHgUFDxQUDwVQBR4Fmx4FBR4FBR4FBR4FAAACAAAATwFAAgcARwBXAAASNDYyFhQWOwEVIyIGFAYiBhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEzFRQWMjY0NjI2NCYiJjQ2MjY0JiIGNCYiBhQGIgYUFjI2NDYy8AUeBQUPFBQPBQUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BR4FBR4FBR4FBR4FBR59BR4FBR4FBR4FBR4B5B4FBR4FeAUeBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FBR4FBR4FBR4Fwx4FBR4FBR4FBR4FAAACAAAATwFAAi8AVwBnAAASNDYyFhQWMjY0NjIWFAYiBhQWOwEVIyIGFAYiBhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEzFRQWMjY0NjsBNSMiBhQGIiY0NjI2NCYiBjQmIgYUBiIGFBYyNjQ2MsgFHgUFHgUFHgUFHgUFDxQUDwUFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBQUeBQUeVQUeBQUeBQUeBQUeAgweBQUeBQUeBQUeBQUeBXgFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgFHgUFHgUFHgXrHgUFHgUFHgUFHgUAAAAAAgAAAE8BQAIvAE8AXwAAEjQ2MhYUFjIWFBY7ARUjIgYUBiIGFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjY9ATMVFBYyNjQ2OwE1IyIGFAYiJjQmIiY0NjIGNCYiBhQGIgYUFjI2NDYyyAUeBQUeBQUPFBQPBQUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FBR4FBR5LBR4FBR4FBR4FBR4CDB4FBR4FBR4FeAUeBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUeBQUeBeseBQUeBQUeBQUeBQAAAAACAAAATwFAAi8AXwBvAAASNDYyFhQWMjY0NjIWFBYyFhQWOwEVIyIGFAYiBhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEzFRQWMjY0NjsBNSMiBhQGIiY0JiIGFAYiJjQ2MjY0JiIGNCYiBhQGIgYUFjI2NDYyeAUeBQUeBQUeBQUeBQUPFBQPBQUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4CDB4FBR4FBR4FBR4FBR4FeAUeBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUeBQUeBQUeBQUeBeseBQUeBQUeBQUeBQAAAAACAAAATwFAAgcAXgBuAAASNDYyFhQWMjY9ATMVFBY7ARUjIgYUBiIGFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjY9ATMVFBYyNjQ2OwE1IxUUFjIWFAYiJjQmIgYUBiImNDYyNjQmIhY0JiIGFAYiBhQWMjY0NjJQBR4FBR4FeAUPFBQPBQUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgV4BR4FBQ8UeAUeBQUeBQUeBQUeBQUeBQUeIwUeBQUeBQUeBQUeAeQeBQUeBQUPFBQPBXgFHgUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUPFBQPBQUeBXgUDwUFHgUFHgUFHgUFHgUFHgXDHgUFHgUFHgUFHgUAAwAUAHcBLAIHAC8APQBNAAATNTMVMzUzFSMVFBYyFhQWOwEVIyIGFAYiBh0BIzU0JiImNCYrATUzMjY0NjI2PQEWNCYrARUzNSMiBhQGIhY0JiIGFAYiBh0BMzU0JiIUUHhQUAUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgUoBQ8UeBQPBQUeIwUeBQUeBXgFHgG3UFBQUBQPBQUeBaAFHgUFDxQUDwUFHgWgBR4FBQ8USx4FUFAFHgVzHgUFHgUFDxQUDwUAAAACABQAdwEsAbcAIQAzAAATNTMVMzUzFSMiBhQWOwEVIyIGHQEjNTQmKwE1MzI2NCYjFzUjFTMyFhQWMjY0NjsBNSMVFFB4UBQPBQUPFBQPBcgFDxQUDwUFD2QoFA8FBR4FBQ8UKAFnUFBQUAUeBaAFDxQUDwWgBR4FeFBQBR4FBR4FUFAAAAAAAgAUAMcBLAGPADcATwAAEzUzFRQWMjY9ATMVFAYiBhQGIgYUFjIWFBYyFh0BIzU0JiIGHQEjNTQmIiY0JiImNDYyNjQ2MjYWNCYiBhQGIgYUFjIWFBYyNjQmIiY0NjJkUAUeBVAFHgUFHgUFHgUFHgVQBR4FUAUeBQUeBQUeBQUeBVAFHgUFHgUFHgUFHgUFHgUFHgF7FBQPBQUPFBQPBQUeBQUeBQUeBQUPFBQPBQUPFBQPBQUeBQUeBQUeBQUoHgUFHgUFHgUFHgUFHgUFHgUAAAACABQAxwEsAY8ANwBPAAATNTMVFBYyNj0BMxUUFjIWFBYyFhQGIgYUBiIGHQEjNTQmIgYdASM1NDYyNjQ2MjY0JiImNCYiJhY0JiIGFBYyFhQGIgYUFjI2NDYyNjQmIhRQBR4FUAUeBQUeBQUeBQUeBVAFHgVQBR4FBR4FBR4FBR4FoAUeBQUeBQUeBQUeBQUeBQUeAXsUFA8FBQ8UFA8FBR4FBR4FBR4FBQ8UFA8FBQ8UFA8FBR4FBR4FBR4FBSgeBQUeBQUeBQUeBQUeBQUeBQAAABwAFP//ASwCLwAHAA8AFwAfACcALwA3AD8ARwBPAFcAXwBnAG8AdwB/AIcAjwCXAJ8ApwCvALcAvwDHAM8A1wDfAAA2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIjY0NjIWFAYiJjQ2MhYUBiI2NDYyFhQGIiY0NjIWFAYiNjQ2MhYUBiImNDYyFhQGIrQFHgUFHqUFHgUFHusFHgUFHqUFHgUFHksFHgUFHqUFHgUFHusFHgUFHqUFHgUFHksFHgUFHqUFHgUFHusFHgUFHqUFHgUFHksFHgUFHqUFHgUFHusFHgUFHqUFHgUFHksFHgUFHqUFHgUFHusFHgUFHqUFHgUFHksFHgUFHqUFHgUFHusFHgUFHqUFHgUFHksFHgUFHqUFHgUFHusFHgUFHqUFHgUFHgQeBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBS0eBQUeBQUeBQUeBQAEAAD//wFAAi8AXwCfAT8BvwAAEjQ2MhYUFjI2NDYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQGIiY0JiIGFAYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyFjQmIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFBYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjIWFBYyNjQ2MhYUFjI2NDYyFhQWMjY0NjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQGIiY0JiIGFAYiJjQmIgYUBiImNCYiBhQGIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjIWNCYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFAYiBhQWMhYUBiIGFBYyFhQGIgYUFjIWFBYyNjQ2MhYUFjI2NDYyFhQWMjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQmIgYUBiImNCYiBhQGIngFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHi0FHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHn0FHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHi0FHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgG8HgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUjHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgV9HgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUjHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHgUAAAAAEwAA//8BQAIvAFIAWgBiAGoAcgB6AIIAigCSAJoAogCqALIAugDCAMoA0gDaAOIAABE1MxUUFjI2PQEzFRQWMjY0NjsBESM1NCYiBh0BIzU0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImNDYyNjQmIiY0NjI2NCYiJjQ2MjY0JiImFjQmIgYUFjIGNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjIGNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjIGNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjIGNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjIGNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjIGNCYiBhQWMjY0JiIGFBYyUAUeBXgFHgUFDxR4BR4FeAUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBQUeBcgFHgUFHksFHgUFHqUFHgUFHksFHgUFHksFHgUFHqUFHgUFHksFHgUFHksFHgUFHqUFHgUFHksFHgUFHksFHgUFHqUFHgUFHksFHgUFHksFHgUFHqUFHgUFHksFHgUFHksFHgUFHqUFHgUFHgIbFBQPBQUPFBQPBQUeBf3QFA8FBQ8UFA8FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBSgeBQUeBSMeBQUeBQUeBQUeBSMeBQUeBSMeBQUeBQUeBQUeBSMeBQUeBSMeBQUeBQUeBQUeBSMeBQUeBSMeBQUeBQUeBQUeBSMeBQUeBSMeBQUeBQUeBQUeBSMeBQUeBSMeBQUeBQUeBQUeBQAAAQB4//8AyAIvAAMAABcRMxF4UAECMP3QAAAAAAEAAP//AMgCLwAHAAATNTMRIxEjNXhQUHgBP/D90AEYKAAAAAMAAP//ARgCLwAFAAkADwAAPQEzESM1FxEzEQM1MxUjNaBQeFDIUKDvKP7o8PACMP3QAWjI8CgAAgBQ//8BGAIvAAMABwAAFxEzESMRMxHIUMhQAQIw/dACMP3QAAAAAAIAAP//ARgBZwAFAAsAAD0BMxEjNSc1IREjEaBQUAEYUO8o/ujwUCj+mAFAAAAAAAIAAADvARgCLwAFAAsAABMRMxEhNTc1MxUjNchQ/uhQUKABFwEY/sAoUMjwKAAAAAEAAP//AMgBPwAFAAARNTMRIxHIUAEXKP7AARgAAAAAAQB4ARcBQAIvAAUAABMRMxUzFXhQeAEXARjwKAAAAAABAAABFwFAAi8ABwAAEzUzFTMVITV4UHj+wAE/8PAoKAAAAAABAAD//wFAAT8ABwAAETUhFSMRIxEBQHhQARcoKP7oARgAAAABAHj//wFAAi8ABwAAFxEzFTMVIxF4UHh4AQIw8Cj+6AAAAAABAAABFwFAAT8AAwAAETUhFQFAARcoKAABAAD//wFAAi8ACwAAEzUzFTMVIxEjESM1eFB4eFB4AT/w8Cj+6AEYKAAAAAIAUADvAUACLwAIAA4AABM1MxUzMhYdAQcRMxEzFchQFA8F8FCgAT/wyAUPFFABQP7oKAACAFD//wFAAWcACAAOAAAXETMVFAYrARUjETMVIxHIeAUPFMjwoAEBGBQPBfABaCj+wAAAAwAAAO8BQAIvAAMADAASAAA9ASEVJzUzFTMyFh0BJzUzFSM1AUB4UBQPBfBQoO8oKFDwyAUPFCjI8CgAAwAA//8BQAFnAAgADgASAAAXETMVFAYrARUlNTMRIzUnNSEVyHgFDxT+6KBQUAFAAQEYFA8F8PAo/ujwUCgoAAMAUP//AUACLwAIABEAFQAAFxEzFRQGKwEVAzUzFTMyFh0BAxEzEch4BQ8UUFAUDwXwUAEBGBQPBfABQPDIBQ8U/sACMP3QAAIAAADvAUABZwADAAcAAD0BIRUlNSEVAUD+wAFA7ygoUCgoAAQAAP//AUACLwAIAA4AFwAdAAAXETMVFAYrARUlNTMRIzU3NTMVMzIWHQEnNTMVIzXIeAUPFP7ooFB4UBQPBfBQoAEBGBQPBfDwKP7o8FDwyAUPFCjI8CgAAAAAAQAAARcAyAIvAAUAABM1MxEjNXhQyAE/8P7oKAAAAAABAHj//wFAAT8ABQAAFxEzFSMReMh4AQFAKP7oAAAAAAEAAP//AUACLwADAAAVESERAUABAjD90AAAAAABAAD//wFAARcAAwAAFREhEQFAAQEY/ugAAAAAAQAA//8AoAIvAAMAABURMxGgAQIw/dAAAQCg//8BQAIvAAMAABcRMxGgoAECMP3QAAAAAAEAAAEXAUACLwADAAAZASERAUABFwEY/ugAAAACAAAAnwFAAY8AJwAvAAAANDY7ARUjIiY0JiImNCYiBh0BIzU0JisBNTMyNj0BMxUUFjI2NDYyBjQmIgYUFjIBGAUPFBQPBQUeBQUeBaAFDxQUDwWgBR4FBR7DBR4FBR4BbB4F8AUeBQUeBQUPFBQPBVAFDxQUDwUFHgVLHgUFHgUAAAADABQAnwEsAbcAJwA3AD8AABM1MxUUFjIWFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjYWNCYiBhQGIgYUFjI2NDYyFjQmIgYUFjJkeAUeBQUPFBQPBQUeBXgFHgUFDxQUDwUFHgUoBR4FBR4FBR4FBR5VBR4FBR4BoxQUDwUFHgV4BR4FBQ8UFA8FBR4FeAUeBQUoHgUFHgUFHgUFHgWbHgUFHgUAAwAAAHcBQAHfAA0AFQAdAAA1ESEVIxUzFSM1MzUjETY0JiIGFBYyFjQmIgYUFjIBQFBQyFB4eAUeBQUeVQUeBQUedwFoKCigoCj+wM0eBQUeBSMeBQUeBQAAAAAEABQAdwEsAd8AJQAtADUAPQAAEjQ2MhYUFjI2NDYyFhQWOwERIzUjFSM1IxUjNTMyNjQ2MjY0NjIWNCYiBhQWMjY0JiIGFBYyBjQmIgYUFjKMBR4FBR4FBR4FBQ8UUChQKCgUDwUFHgUFHi0FHgUFHlUFHgUFHiMFHgUFHgG8HgUFHgUFHgUFHgX+wHh4oFCgBR4FBR4FSx4FBR4FBR4FBR4FSx4FBR4FAAAAAQAA//8BQAEXAB4AACQ0NjsBESE1NDYyNjQ2MjY0NjI2PQEzNTQ2MjY0NjIBGAUPFP7ABR4FBR4FBR4FUAUeBQUe9B4F/ugUDwUFHgUFHgUFDxQUDwUFHgUAAAABAAD//wFAAi8AHQAAADQ2OwERIREzMjY0NjI2NDYyNj0BMzU0NjI2NDYyARgFDxT+wBQPBQUeBQUeBVAFHgUFHgIMHgX90AFABR4FBR4FBQ8UFA8FBR4FAAAAAAEAAP//AUACLwAdAAAVETMyFhQWMhYUFjIWHQEzFRQWMhYUFjIWFBY7AREUDwUFHgUFHgVQBR4FBR4FBQ8UAQIwBR4FBR4FBQ8UFA8FBR4FBR4F/sAAAAABAAD//wFAARcAHgAAFREzMhYUFjIWFBYyFh0BMxUUFjIWFBYyFhQWMhYdARQPBQUeBQUeBVAFHgUFHgUFHgUBARgFHgUFHgUFDxQUDwUFHgUFHgUFDxQACwAAAE8BQAIHADcAPwBDAEsAUwBXAF8AZwBvAHMAewAAETUzFRQWMhYUFjsBFSMiBhQGKwEVMzIWFBYyFh0BIzU0JiImNCYrATUzMjY0NjsBNSMiJjQmIiYWNCYiBhQWMjM1IxUWNCYiBhQWMjY0JiIGFBYyBzUjFTY0JiIGFBYyBjQmIgYUFjI2NCYiBhQWMgc1IxU2NCYiBhQWMvAFHgUFDxQUDwUFDxQUDwUFHgXwBR4FBQ8UFA8FBQ8UFA8FBR4FeAUeBQUefVAoBR4FBR5VBR4FBR5zUKAFHgUFHpsFHgUFHn0FHgUFHiNQoAUeBQUeAfMUFA8FBR4FeAUeBXgFHgUFDxQUDwUFHgV4BR4FeAUeBQUoHgUFHgUoKEseBQUeBQUeBQUeBVAoKAUeBQUeBUseBQUeBQUeBQUeBVAoKAUeBQUeBQAAAAAGABQAdwEsAd8AJwAvADcAPwBHAE8AABM1MxUUFjIWFBY7ARUjIgYUBiIGHQEjNTQmIiY0JisBNTMyNjQ2MjYWNCYiBhQWMhY0JiIGFBYyBjQmIgYUFjIWNCYiBhQWMgY0JiIGFBYyZHgFHgUFDxQUDwUFHgV4BR4FBQ8UFA8FBR4FKAUeBQUeVQUeBQUecwUeBQUepQUeBQUeSwUeBQUeAcsUFA8FBR4FyAUeBQUPFBQPBQUeBcgFHgUFKB4FBR4FSx4FBR4FIx4FBR4FIx4FBR4FSx4FBR4FAAAABAAoAHcBGAHfACMAJwArADMAABM1MxUUFjsBESMiJjQmIgYUBiImNCYiBhQGIiY0JisBETMyNhc1IxUzNSMVFDQmIgYUFjJQoAUPFBQPBQUeBQUeBQUeBQUeBQUPFBQPBSgoeCgFHgUFHgHLFBQPBf7ABR4FBR4FBR4FBR4FBR4FARgFfVBQUFBLHgUFHgUAAQAoAHcBGAIHAFwAABM1MxUzFRQGIgYUFjIWFAYiJjQmIiY0JisBFTMyFhQWMhYUFjsBFSMiBh0BIzU0JisBNTMyNjQmIiY0NjIWFBYyFhQGIgYUFjIWHQEzNSMiJjQmIiY0JisBNTMyNnhQUAUeBQUeBQUeBQUeBQUPFBQPBQUeBQUPFBQPBaAFDxQUDwUFHgUFHgUFHgUFHgUFHgVQFA8FBR4FBQ8UFA8FAfMUKBQPBQUeBQUeBQUeBQUeBVAFHgUFHgV4BQ8UFA8FUAUeBQUeBQUeBQUeBQUeBQUPFHgFHgUFHgV4BQAAAAAFAAAATwFAAi8AAwBQAHAAigCSAAA3NTMVAjQ2MhYUFjIWFBY7ARUzMhYUFjIWFAYiBhQWMhYUBiIGFAYiBh0BIzU0JiImNCYiJjQ2MjY0JiImNDY7ATUzFTM1NCYrATUjIiY0NjIWNCYiBhQWOwEVMzIWFBYyFhQWMjY0JiImNCYrATUjIgc0JiIGFBYyFhQGIgYUFjIWHQEzNTM1IzUjEjQ2MhYUBiJQUCgFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgUFHgWgBR4FBR4FBR4FBR4FBQ8UKFAFDxQUDwUFHi0FHgUFDxQUDwUFHgUFHgUFHgUFDxQUD1UFHgUFHgUFHgUFHgVQUFBQKAUeBQUenygoAR0eBQUeBQUeBVAFHgUFHgUFHgUFHgUFHgUFDxQUDwUFHgUFHgUFHgUFHgVQUBQPBVAFHgUjHgUFHgVQBR4FBR4FBR4FBR4FUIwPBQUeBQUeBQUeBQUPFCgoKAEdHgUFHgUAAAAAAQAAARcBQAIvAB4AABE1IREjIiY0JiImNCYiJj0BIzU0JiImNCYiJjQmIiYBQBQPBQUeBQUeBVAFHgUFHgUFHgUCGxT+6AUeBQUeBQUPFBQPBQUeBQUeBQUAAAABAAD//wFAAi8AHQAANREhESMiJjQmIiY0JiImPQEjNTQmIiY0JiImNCYjAUAUDwUFHgUFHgVQBR4FBR4FBQ/vAUD90AUeBQUeBQUPFBQPBQUeBQUeBQAAAQAA//8BQAIvAB0AABURIREjIgYUBiIGFAYiBh0BIxUUBiIGFAYiBhQGIwFAFA8FBR4FBR4FUAUeBQUeBQUPAQIw/sAFHgUFHgUFDxQUDwUFHgUFHgUAAAEAAAEXAUACLwAeAAAZASEVFAYiBhQGIgYUBiIGHQEjFRQGIgYUBiIGFAYjAUAFHgUFHgUFHgVQBR4FBR4FBQ8BFwEYFA8FBR4FBR4FBQ8UFA8FBR4FBR4FAAAABAAAAE8BQAHfABcAGwAfAC0AABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhc1IxUzNSMVBzQmIgYUFjIWHQEzNSMo8AUPFBQPBfAFDxQUDwVQKKAoUAUeBQUeBVBQAcsUFA8F/sAFDxQUDwUBQAV9UFBQUGQPBQUeBQUPFCgAAAAEAAAATwFAAd8AFwAbAB8AIwAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzUjFTM1Ix0BNSMVKPAFDxQUDwXwBQ8UFA8FUCigKFAByxQUDwX+wAUPFBQPBQFABX1QUFBQoFBQAAAAAAQAAABPAUAB3wAXABsAHwAtAAATNTMVFBY7AREjIgYdASM1NCYrAREzMjYXNSMVMzUjHQE1IxUUBiIGFBYyNj0BKPAFDxQUDwXwBQ8UFA8FUCigKFAFHgUFHgUByxQUDwX+wAUPFBQPBQFABX1QUFBQoCgUDwUFHgUFDxQAAAAAAwAAAE8BQAHfABcAOQBHAAATNTMVFBY7AREjIgYdASM1NCYrAREzMjYXNCYiBhQWMhYUBisBFTM1MxUzNSMiJjQ2MjY0JiIGHQEjFzUjFRQGIgYUFjI2PQEo8AUPFBQPBfAFDxQUDwVQBR4FBR4FBQ8UKFAoFA8FBR4FBR4FUFBQBR4FBR4FAcsUFA8F/sAFDxQUDwUBQAUZDwUFHgUFHgVQUFBQBR4FBR4FBQ8U8CgUDwUFHgUFDxQAAAAEAAAATwFAAd8AFwAfACMAKwAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FjQmIgYUFjIzNSMVFDQmIgYUFjIo8AUPFBQPBfAFDxQUDwVQBR4FBR59UAUeBQUeAcsUFA8F/sAFDxQUDwUBQAV4HgUFHgUoKJseBQUeBQAABAAAAE8BQAHfABcANwBXAF8AABM1MxUUFjsBESMiBh0BIzU0JisBETMyNhY0JiIGFBYyFhQGIgYUFjI2NDYyFhQWMjY0JiImNCYiNjQmIgYUFjIWFBYyFhQWMjY0JiImNDYyNjQmIgYUBiIGNCYiBhQWMijwBQ8UFA8F8AUPFBQPBSgFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHnMFHgUFHgUFHgUFHgUFHgUFHgUFHgUFHlUFHgUFHgHLFBQPBf7ABQ8UFA8FAUAFUB4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4FBR4F6x4FBR4FAAMAAABPAUAB3wAXACkAMgAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzUjFTM1NDYyFh0BMzU0NjI2BzQmKwEVMzUjKPAFDxQUDwXwBQ8UFA8F8PBQBR4FUAUeBaAFDxR4UAHLFBQPBf7ABQ8UFA8FAUAFQRRQFA8FBQ8UFA8FBZEPBVAoAAAEAAAATwFAAd8AFwAbAB8ALwAAEzUzFRQWOwERIyIGHQEjNTQmKwERMzI2FzUjFTM1Ix0BNSMVFAYiBh0BMzU0JiImKPAFDxQUDwXwBQ8UFA8FUCigKFAFHgWgBR4FAcsUFA8F/sAFDxQUDwUBQAV9UFBQUGQUFA8FBQ8UFA8FBQAAAAABAHgA7wDIAT8AAwAANzUzFXhQ71BQAAABAIwA7wC0ARcABwAANjQ2MhYUBiKMBR4FBR70HgUFHgUAAAABAAAATwFAAgcAMQAAADQ2MhYUBisBFSMVIxUjFSMiBhQGIiY0JisBNSM1MzIWFBY7ARUzNTM1MzUzMjY0NjIBGAUeBQUPFCgoKBQPBQUeBQUPFCgUDwUFDxQoKCgUDwUFHgHkHgUFHgVQeFBQBR4FBR4FUHgFHgVQUFBQBR4FAAEAUAE/APACLwAdAAASNDYyFhQGKwEVIxUjIiY0JisBNTMyNjQ2MjY0NjLIBR4FBQ8UKBQPBQUPFBQPBQUeBQUeAgweBQUeBVB4BR4FUAUeBQUeBQAAAAABACgAdwEYAbcAJwAAEjQ2OwEVMzIWHQEzFRQWOwEVIyIGFAYrATUjIiY9ASM1NCYrATUzMlAFDxQUDwVQBQ8UFA8FBQ8UFA8FUAUPFBQPAZQeBXgFDxQUDwVQBR4FeAUPFBQPBVAAAAEAPACfAQQBjwADAAA3NTMVPMif8PAAAAEAUP//APAA7wAdAAA3NTMyFhQWOwEVIyIGFAYiBhQGIgYUBiImNDY7ATWgFA8FBQ8UFA8FBR4FBR4FBR4FBQ8Ud3gFHgVQBR4FBR4FBR4FBR4FUAAAAAAAAA4ArgABAAAAAAAAADQAagABAAAAAAABAAgAsQABAAAAAAACAAYAyAABAAAAAAADACUBGwABAAAAAAAEAAkBVQABAAAAAAAFABABgQABAAAAAAAGAAgBpAADAAEECQAAAGgAAAADAAEECQABABAAnwADAAEECQACAAwAugADAAEECQADAEoAzwADAAEECQAEABIBQQADAAEECQAFACABXwADAAEECQAGABABkgBDAHIAZQBhAHQAZQBkACAAdwBpAHQAaAAgAEYAbwBuAHQARgBvAHIAZwBlACAAMgAuADAAIAAoAGgAdAB0AHAAOgAvAC8AZgBvAG4AdABmAG8AcgBnAGUALgBzAGYALgBuAGUAdAApAABDcmVhdGVkIHdpdGggRm9udEZvcmdlIDIuMCAoaHR0cDovL2ZvbnRmb3JnZS5zZi5uZXQpAABtAGUAZwBhAHoAZQB1AHgAAG1lZ2F6ZXV4AABNAGUAZABpAHUAbQAATWVkaXVtAABGAG8AbgB0AEYAbwByAGcAZQAgADIALgAwACAAOgAgAG0AZQBnAGEAegBlAHUAeAAgACAAOgAgADIAMQAtADYALQAyADAAMQAyAABGb250Rm9yZ2UgMi4wIDogbWVnYXpldXggIDogMjEtNi0yMDEyAABtAGUAZwBhAHoAZQB1AHgAIAAAbWVnYXpldXggAABWAGUAcgBzAGkAbwBuACAAMAAwADEALgAwADAAMAAgAABWZXJzaW9uIDAwMS4wMDAgAABtAGUAZwBhAHoAZQB1AHgAAG1lZ2F6ZXV4AAAAAgAAAAAAAP+DADIAAAABAAAAAAAAAAAAAAAAAAAAAAELAAAAAQACAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4BDwEQAREBEgETARQBFQEWARcBGAEZARoBGwEcAR0BHgEfASABIQEiASMBJAElASYBJwEoASkBKgErASwBLQEuAS8BMAExATIBMwE0ATUBNgE3ATgBOQE6ATsBPAE9AT4BPwFAAUEBQgFDAUQBRQFGAUcBSAFJAUoBSwFMAU0BTgFPAVABUQFSAVMBVAFVAVYBVwFYAVkBWgFbAVwBXQFeAV8BYAClAWEBYgFjAJwBZAFlAWYBZwFoAWkBagFrAWwBbQFuAW8BcAFxAXIBcwF0AXUBdgF3AXgBeQF6AXsBfAF9AX4BfwGAAYEBggGDAYQBhQGGAYcBiAGJAYoBiwGMAY0BjgGPAZABkQGSAZMBlAGVAZYBlwGYAZkBmgGbAZwBnQGeAZ8BoAGhAaIBowGkAaUBpgGnAagBqQGqAasBrAGtAa4BrwGwAbEBsgGzAbQBtQG2AbcBuAG5AboBuwG8Ab0BvgG/AcABwQHCAcMBxAHFAcYBxwHIAckBygHLAcwBzQHOAc8B0AHRAdIB0wHUAdUB1gHXAdgB2QHaAdsB3AHdAd4B3wHgAeEB4gHjAeQB5QHmAecB6AHpAeoB6wHsAe0B7gHvAfAB8QHyAfMB9AH1AfYB9wH4AfkB+gH7AfwB/QH+Af8CAAIBAgICAwIEAgUCBgIHBlUrMDAyMAZVKzAwMjEGVSswMDIyBlUrMDAyMwZVKzAwMjQGVSswMDI1BlUrMDAyNgZVKzAwMjcGVSswMDI4BlUrMDAyOQZVKzAwMmEGVSswMDJiBlUrMDAyYwZVKzAwMmQGVSswMDJlBlUrMDAyZgZVKzAwMzAGVSswMDMxBlUrMDAzMgZVKzAwMzMGVSswMDM0BlUrMDAzNQZVKzAwMzYGVSswMDM3BlUrMDAzOAZVKzAwMzkGVSswMDNhBlUrMDAzYgZVKzAwM2MGVSswMDNkBlUrMDAzZQZVKzAwM2YGVSswMDQwBlUrMDA0MQZVKzAwNDIGVSswMDQzBlUrMDA0NAZVKzAwNDUGVSswMDQ2BlUrMDA0NwZVKzAwNDgGVSswMDQ5BlUrMDA0YQZVKzAwNGIGVSswMDRjBlUrMDA0ZAZVKzAwNGUGVSswMDRmBlUrMDA1MAZVKzAwNTEGVSswMDUyBlUrMDA1MwZVKzAwNTQGVSswMDU1BlUrMDA1NgZVKzAwNTcGVSswMDU4BlUrMDA1OQZVKzAwNWEGVSswMDViBlUrMDA1YwZVKzAwNWQGVSswMDVlBlUrMDA1ZgZVKzAwNjAGVSswMDYxBlUrMDA2MgZVKzAwNjMGVSswMDY0BlUrMDA2NQZVKzAwNjYGVSswMDY3BlUrMDA2OAZVKzAwNjkGVSswMDZhBlUrMDA2YgZVKzAwNmMGVSswMDZkBlUrMDA2ZQZVKzAwNmYGVSswMDcwBlUrMDA3MQZVKzAwNzIGVSswMDczBlUrMDA3NAZVKzAwNzUGVSswMDc2BlUrMDA3NwZVKzAwNzgGVSswMDc5BlUrMDA3YQZVKzAwN2IGVSswMDdjBlUrMDA3ZAZVKzAwN2UFYW5nbGUMaW50ZXJzZWN0aW9uBXVuaW9uB3VuaTIyMzUHdW5pMjI1MgtlcXVpdmFsZW5jZQ1wZXJwZW5kaWN1bGFyBlUrZTAwMQZVK2UwMDIGVStlMDAzBlUrZTAwNAZVK2UwMDUGVStlMDA2BlUrZTAwNwZVK2UwMDgGVStlMDA5BlUrZTAwYQZVK2UwMGIGVStlMDBjBlUrZTAwZAZVK2UwMGUGVStlMDBmBlUrZTAxMAZVK2UwMTEGVStlMDEyBlUrZTAxMwZVK2UwMTQGVStlMDE1BlUrZTAxNgZVK2UwMTcGVStlMDE4BlUrZTAxOQZVK2UwMWEGVStlMDFiBlUrZTAxYwZVK2UwMWQGVStlMDFlBlUrZTAxZgZVK2UwN2YGVStlMDgwBlUrZTA4MQZVK2UwODIGVStlMDgzBlUrZTA4NAZVK2UwODUGVStlMDg2BlUrZTA4NwZVK2UwODgGVStlMDg5BlUrZTA4YQZVK2UwOGIGVStlMDhjBlUrZTA4ZAZVK2UwOGUGVStlMDhmBlUrZTA5MAZVK2UwOTEGVStlMDkyBlUrZTA5MwZVK2UwOTQGVStlMDk1BlUrZTA5NgZVK2UwOTcGVStlMDk4BlUrZTA5OQZVK2UwOWEGVStlMDliBlUrZTA5YwZVK2UwOWQGVStlMDllBlUrZTA5ZgZVK2UwYTAGVStlMGExBlUrZTBhMgZVK2UwYTMGVStlMGE0BlUrZTBhNQZVK2UwYTYGVStlMGE3BlUrZTBhOAZVK2UwYTkGVStlMGFhBlUrZTBhYgZVK2UwYWMGVStlMGFkBlUrZTBhZQZVK2UwYWYGVStlMGIwBlUrZTBiMQZVK2UwYjIGVStlMGIzBlUrZTBiNAZVK2UwYjUGVStlMGI2BlUrZTBiNwZVK2UwYjgGVStlMGI5BlUrZTBiYQZVK2UwYmIGVStlMGJjBlUrZTBiZAZVK2UwYmUGVStlMGJmBlUrZTBjMAZVK2UwYzEGVStlMGMyBlUrZTBjMwZVK2UwYzQGVStlMGM1BlUrZTBjNgZVK2UwYzcGVStlMGM4BlUrZTBjOQZVK2UwY2EGVStlMGNiBlUrZTBjYwZVK2UwY2QGVStlMGNlBlUrZTBjZgZVK2UwZDAGVStlMGQxBlUrZTBkMgZVK2UwZDMGVStlMGQ0BlUrZTBkNQZVK2UwZDYGVStlMGQ3BlUrZTBkOAZVK2UwZDkGVStlMGRhBlUrZTBkYgZVK2UwZGMGVStlMGRkBlUrZTBkZQZVK2UwZGYGVStlMGUwBlUrZTBlMQZVK2UwZTIGVStlMGUzBlUrZTBlNAZVK2UwZTUGVStlMGU2BlUrZTBlNwZVK2UwZTgGVStlMGU5BlUrZTBlYQZVK2UwZWIGVStlMGVjBlUrZTBlZAZVK2UwZWUGVStlMGVmBlUrZTBmMAZVK2UwZjEGVStlMGYyBlUrZTBmMwZVK2UwZjQGVStlMGY1BlUrZTBmNgZVK2UwZjcGVStlMGY4BlUrZTBmOQZVK2UwZmEGVStlMGZiBlUrZTBmYwZVK2UwZmQGVStlMGZlBlUrZTBmZgAAAAH//wACAAEAAAAOAAAAGAAgAAAAAgABAAEBCgABAAQAAAACAAAAAQAAAAEAAAAAAAEAAAAAyYlvMQAAAADMCC9PAAAAAMwITt8=)\n       format('truetype');\n\n  /* The original thing that was here:\n  src: url('mzxfont.eot');\n  src: url('mzxfont.eot?#iefix') format('embedded-opentype'),\n       url('mzxfont.woff') format('woff'),\n       url('mzxfont.tff') format('truetype');\n      */\n  font-weight: normal;\n  font-style: normal;\n\n  /* Try to fix the ugly blur. */\n  /*\n  font-smooth: never;\n  font-smoothing: unset;\n  -webkit-font-smoothing: none;\n  -moz-osx-font-smoothing: unset;\n  */\n}\n\n</style>\n<style>\n/* MegaZeux\n *\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * style.css: Embeddable formatting CSS for HTML help file generation.\n */\n\nbody\n{\n  margin: 0px;\n}\n\n#helpnav\n{\n  font-family: Arial, Helvetica, sans-serif;\n  width: 100%;\n  height: 5em;\n  padding: .5em;\n  padding-left: 0px;\n  padding-right: 0px;\n  border-bottom: 5px solid;\n  text-align: center;\n\n  /* Floating navigation bar. */\n  position: fixed;\n  top: 0;\n}\n\n#helpnav h1\n{\n  display: block;\n  margin: .5em;\n  padding: 0px;\n  font-size: 1.5em;\n}\n\n.helpnavcentered\n{\n  display: inline;\n  text-align: center;\n}\n\n.helpnavcentered ul\n{\n  padding: 0px;\n  margin: .25em;\n}\n\n.helpnavcentered li\n{\n  display: inline-block;\n  margin: 1px;\n  list-style-type: none;\n  text-align: center;\n  font-size: .95em;\n}\n\n.helpnavcentered li a\n{\n  background-color: #222;\n  border-color: #222;\n  border-radius: 4px 4px;\n  -moz-border-radius: 4px 4px;\n  padding: 3px 5px;\n  color: #FFF;\n  text-decoration: none;\n}\n\n.helpnavcentered li a:hover\n{\n  background-color: #666;\n  border-color: #666;\n}\n\n#helpcontainer\n{\n  font-family: 'mzxfont';\n  width: 512px;\n  height: auto;\n  padding: 3px 76px;\n  margin: auto;\n  overflow: auto;\n  white-space: pre;\n  line-height: 14px;\n  font-size: 14px;\n\n  /* Extra top padding to help fix anchors. */\n  padding-top: 6em;\n}\n\n/* Help file div. */\n.hF\n{\n  page-break-after: always;\n}\n\n.hF hr\n{\n  background-color: #000;\n  height: 2px;\n  margin: 14px 8px;\n  border: none;\n}\n\n/* Help file centered text. */\n.hC\n{\n  display: block;\n  text-align: center;\n  margin: 0px;\n}\n\n/* Help file anchor (link destination). */\n.hA\n{\n  /* This forces the page to jump above the anchor so it doesn't display\n   * under the navigation bar. */\n  padding-top: 8em;\n  margin-top: -8em;\n\n  /* This is a fix for the fix above so the now gigantic anchor doesn't\n   * cover links up. */\n  pointer-events: none;\n}\n\n/* Help file link. */\n.hL\n{\n  text-decoration: none;\n}\n\n.hL:before\n{\n  padding-right: 8px;\n  font-family: 'mzxfont';\n  content: \"\\E010\";\n}\n\n@media print\n{\n  #helpnav\n  {\n    /* Disable the floating navigation bar in print mode. */\n    position: absolute;\n    border-bottom: 3px solid;\n  }\n\n  .helpnavcentered\n  {\n    /* Hide navigation buttons when in print mode. */\n    display: none;\n  }\n}\n\n@media only screen and (max-width: 680px),\n       only screen and (max-device-width: 1080px)\n{\n  /* Disable big margins on small windows, small displays, or phones. */\n\n  #helpcontainer\n  {\n    padding: 3px 8px;\n    padding-top: 6em;\n  }\n}\n\n@media only screen and (max-device-width: 800px)\n{\n  /* Probably a phone. Scale up navigation and text. */\n\n  #helpnav\n  {\n    height: 7em;\n  }\n\n  #helpnav h1\n  {\n    font-size: 2em;\n  }\n\n  .helpnavcentered li\n  {\n    font-size: 1.5em;\n  }\n\n  #helpcontainer\n  {\n    width: 768px;\n    line-height: 21px;\n    font-size: 21px;\n  }\n}\n\n@media only screen and (max-device-width: 1080px)\n{\n  /* Probably a phone. Scale up navigation and text. */\n\n  #helpnav\n  {\n    height: 8em;\n  }\n\n  #helpnav h1\n  {\n    font-size: 2.5em;\n  }\n\n  .helpnavcentered li\n  {\n    font-size: 1.75em;\n  }\n\n  #helpcontainer\n  {\n    width: 960px;\n    line-height: 26px;\n    font-size: 26px;\n  }\n}\n\n</style>\n<style>\n/* MegaZeux\n *\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * style_color.css: Embeddable color CSS for HTML help files.\n * Should only style outside of printing mode.\n *\n * TODO: possibly invert the query here.\n */\n\n@media not print\n{\n  body\n  {\n    background-color: #333;\n  }\n\n  #helpnav\n  {\n    background-color: #444;\n    color: #FFF;\n    border-bottom-color: #333;\n  }\n\n  #helpcontainer\n  {\n    background-color: #555;\n    color: #FFF;\n  }\n\n  .hL { color: #FFFFFF; }\n  .hL:before { color: #FFFF55; }\n  .hL:hover { color: #FF55FF; }\n\n  .f0 { color: #000000; }\n  .f1 { color: #0000AA; }\n  .f2 { color: #00AA00; }\n  .f3 { color: #00AAAA; }\n  .f4 { color: #AA0000; }\n  .f5 { color: #AA00AA; }\n  .f6 { color: #AA5500; }\n  .f7 { color: #AAAAAA; }\n  .f8 { color: #555555; }\n  .f9 { color: #7777FF; } /* Should be #5555FF, but that's ugly on dk grey. */\n  .fa, .fA { color: #55FF55; }\n  .fb, .fB { color: #55FFFF; }\n  .fc, .fC { color: #FF5555; }\n  .fd, .fD { color: #FF55FF; }\n  .fe, .fE { color: #FFFF55; }\n  .ff, .fF { color: #FFFFFF; }\n\n  .b0 { background-color: #000000; }\n  .b1 { background-color: #0000AA; }\n  .b2 { background-color: #00AA00; }\n  .b3 { background-color: #00AAAA; }\n  .b4 { background-color: #AA0000; }\n  .b5 { background-color: #AA00AA; }\n  .b6 { background-color: #AA5500; }\n  .b7 { background-color: #AAAAAA; }\n  .b8 { background-color: #555555; }\n  .b9 { background-color: #5555FF; }\n  .ba, .bA { background-color: #55FF55; }\n  .bb, .bB { background-color: #55FFFF; }\n  .bc, .bC { background-color: #FF5555; }\n  .bd, .bD { background-color: #FF55FF; }\n  .be, .bE { background-color: #FFFF55; }\n  .bf, .bF { background-color: #FFFFFF; }\n}\n\n</style>\n</head>\n\n<body>\n<div id=\"helpnav\">\n<h1>MegaZeux 2.93d (20250609) - Help File</h1>\n<div class=\"helpnavcentered\"><ul>\n<li><a href=\"#MAIN.HLP__072\">Contents</a></li><li><a href=\"#EDITINGK.HLP__080\">Editor Keys</a></li><li><a href=\"#ROBOTICR.HLP__087\">Robotic Manual</a></li><li><a href=\"#COMMANDR.HLP__1st\">Commands</a></li><li><a href=\"#COUNTERS.HLP__1st\">Counters</a></li><li><a href=\"#NEWINVER.HLP__1st\">Changelog</a></li></ul></div>\n</div>\n\n<div id=\"helpcontainer\">\n<div class=\"hF\" id=\"MAIN.HLP\">\n<p class=\"hC\"><span class=\"b1\">                   </span></p><p class=\"hC\"><span class=\"b1\">  </span><span class=\"ff b1\">M </span><span class=\"fb b1\">E G </span><span class=\"f3 b1\">A Z </span><span class=\"fb b1\">E U </span><span class=\"ff b1\">X  </span></p><p class=\"hC\"><span class=\"b1\">                   </span></p><a class=\"hA\" name=\"MAIN.HLP__072\"> </a>\n<p class=\"hC\"><span class=\"f9\">Table of Contents</span></p>\n<p class=\"hC\"><span class=\"fC\">1) Basics and Gameplay</span></p><a class=\"hL\" href=\"#FAQ.HLP__1st\">FREQUENTLY ASKED QUESTIONS.</a>\n<a class=\"hL\" href=\"#1ST_TIME.HLP__071\">Overview of MegaZeux - First Time Users Read This!</a>\n<a class=\"hL\" href=\"#HELPONHE.HLP__000\">Help on Help (Press F1 Within Help to Read This)</a>\n<a class=\"hL\" href=\"#CONFGINI.HLP__1st\">The Config File</a>\n<a class=\"hL\" href=\"#MOUSESUP.HLP__1st\">Mouse Support in MegaZeux</a>\n<a class=\"hL\" href=\"#DIABOX.HLP__098\">Dialog Boxes</a>\n<a class=\"hL\" href=\"#CONTROLS.HLP__ctr\">Controls</a>\n<a class=\"hL\" href=\"#NONDESCR.HLP__091\">Nondescript Play Tips</a>\n<a class=\"hL\" href=\"#BUILTINS.HLP__1st\">The Mirth of Built-ins</a>\n\n<p class=\"hC\"><span class=\"fC\">2) The Editor</span></p><a class=\"hL\" href=\"#THEWORLD.HLP__1st\">The World Editor</a>\n<a class=\"hL\" href=\"#GENERALE.HLP__1st\">General Editing Tips</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__080\">Editing Keys and Options Reference</a>\n<a class=\"hL\" href=\"#CHAREDIT.HLP__079\">The Character Editor</a>\n<a class=\"hL\" href=\"#PALEEDIT.HLP__093\">The Palette Editor</a>\n<a class=\"hL\" href=\"#SMZXMODE.HLP__095\">Super MegaZeux Modes</a>\n<a class=\"hL\" href=\"#GLOBALIN.HLP__086\">Global Info Options</a>\n<a class=\"hL\" href=\"#BOARDINF.HLP__085\">Board Info Options</a>\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">MegaZeux's Sound System</a>\n<a class=\"hL\" href=\"#TOVERLAY.HLP__081\">Editing and Using the Overlay</a>\n<a class=\"hL\" href=\"#SCROLLSS.HLP__1st\">Signs and Scrolls in the Editor</a>\n\n<p class=\"hC\"><span class=\"fC\">3) Coding With Robotic</span></p><a class=\"hL\" href=\"#ROBOTSWH.HLP__1st\">Robots - What They Are and How to Use Them</a>\n<a class=\"hL\" href=\"#ROBOTICT.HLP__1st\">Robotic Tutorial</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#DBGMODE.HLP__dbg\">Debug Modes</a>\n<a class=\"hL\" href=\"#SENSORSW.HLP__094\">Sensors - What They Are and How to Use Them</a>\n<a class=\"hL\" href=\"#ZZT.HLP__zzt\">A ZZTer's Guide to MegaZeux</a>\n\n<p class=\"hC\"><span class=\"fC\">4) Miscellaneous</span></p><a class=\"hL\" href=\"#UPDATER.HLP__099\">The MegaZeux Updater</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__1st\">Error Messages</a>\n<a class=\"hL\" href=\"#MEGAZEUX.HLP__1st\">MegaZeux Limitations</a>\n<a class=\"hL\" href=\"#IFYOUFIN.HLP__1st\">If You Find a Bug...</a>\n<a class=\"hL\" href=\"#NEWINVER.HLP__1st\">NEW in MegaZeux!</a>\n\n<p class=\"hC\">** Credits and Acknowledgments **</p>\n<p class=\"hC\">Programming and Overall Design by Gilead Kutnick (Exophase),</p><p class=\"hC\">Alistair Strachan (ajs), Alice Rowan (Lachesis) and Lancer-X</p><p class=\"hC\">Based off of original program and source code by Alexis Janson</p><p class=\"hC\">Help file by Terryn</p><p class=\"hC\">Default SMZX palette by Joel Lamontagne (LogiCow)</p><p class=\"hC\">ccv utility by Lancer-X</p><p class=\"hC\">png2smzx utility by Alan Williams (Mr_Alert)</p><p class=\"hC\">checkres utility by Josh Matthews (Revvy), ajs and Lachesis</p><p class=\"hC\">Port contributors: Adrian Siekierka (asiekierka) [3DS],</p><p class=\"hC\">Mr_Alert [Wii], Kevin Vance [NDS], Simon Parzer [GP2X]</p><p class=\"hC\">Renderer code contributors: LogiCow, Mr_Alert</p><p class=\"hC\">Shader code contributors: David Cravens (astral), GreaseMonkey</p><p class=\"hC\">Icon by Quantum P.; Extra icons by LogiCow</p><p class=\"hC\">GDM conversion by ajs and MadBrain</p><p class=\"hC\">Other past contributors: Spider124, Koji, JZig, Akwende,</p><p class=\"hC\">MenTaLguY.</p>\n<p class=\"hC\">** Special Thanks **</p>\n<p class=\"hC\">Dizzy (.deb Builds)</p><p class=\"hC\">mzxgiant (MSVC Testing, Bug Fixes)</p><p class=\"hC\">mzxrules (Testing)</p><p class=\"hC\">Quantum P. (OS X Testing / Builds)</p><p class=\"hC\">Spectere (OS X Builds)</p><p class=\"hC\">Terryn (Testing)</p><p class=\"hC\">Wervyn (Testing)</p>\n<p class=\"hC\">MegaZeux 2.80 Thanks &amp; Beta Testers:</p><p class=\"hC\">Exophase</p><p class=\"hC\">ZoMbIeGuY</p><p class=\"hC\">Wervyn</p><p class=\"hC\">Quasar84</p><p class=\"hC\">Lancer-X</p><p class=\"hC\">KenOhki2112</p><p class=\"hC\">Terryn</p><p class=\"hC\">Inuchance (Macintosh testing)</p><p class=\"hC\">Everyone who submitted a bug report</p>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"HELPONHE.HLP\">\n<a class=\"hA\" name=\"HELPONHE.HLP__000\"> </a>\n<p class=\"hC\"><span class=\"f9\">Help on Help</span></p>\nUsing MegaZeux's help system is very simple. Press F1 at almost\nany time to bring up context-relevant help. Within help, use the\narrow keys to scroll the current section. Press PageUp and\nPageDown to scroll faster. Many sections of help contain\nselections, like this:\n\n<a class=\"hL\" href=\"#HELPONHE.HLP__sl\">Selection</a>\n<a class=\"hA\" name=\"HELPONHE.HLP__sl\"> </a>\nScroll the section until the pointer is aligned with the arrows\non the edges of the help box, and press Enter. You will jump to\na help section indicated by the selection. Example: If a\nselection says \"Controls\", scroll until the word is aligned\nwith the arrows on the left and right, and press Enter. You\nshould now be reading help on Controls.\n\nPress F1 within help to jump to this section. Press Alt+F1\nwithin help to jump to the Table of Contents (Table of Major\nHelp Topics). Press ESC to exit the help system.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"1ST_TIME.HLP\">\n<a class=\"hA\" name=\"1ST_TIME.HLP__071\"> </a>\n<p class=\"hC\"><span class=\"f9\">Overview of MegaZeux</span></p>\n<p class=\"hC\">Welcome to MegaZeux! Use the arrow keys to scroll</p><p class=\"hC\">this text, and ESC when you are done reading.</p>\n<p class=\"hC\">Press END to learn about the NEW FEATURES in</p><p class=\"hC\">MegaZeux!</p>\nAs you may already know, MegaZeux is a game system which\nallows you to play almost limitless worlds with\ndated-yet-charming graphics and with excellent digitized music\nand sound. Not only are there several MZX worlds out there\nalready, but new worlds are being uploaded to large websites\nlike DigitalMZX. However, the best feature of MegaZeux is the\nWorld Editor.\n\nUsing the World Editor, ANYONE can create the world of their\ndreams. Make it as simple or complex (well, almost), as easy\nor difficult, as long or short as you please. We aren't just\ntalking about worlds made up of petty, pre-programmed enemies\nand objects; MegaZeux has its own, easy-to-use PROGRAMMING\nLANGUAGE called Robotic that allows you to create objects,\nengines and worlds that do almost anything you desire. Not only\ncan you make your own game with the editor, but every world\nMegaZeux can play can be opened in the editor. Take a look at\nhow other games tick!\n\nFor the newest user, it's recommended that you play Caverns,\nthe first ever MegaZeux game, to get the feel of simple yet\nwell-designed games in MegaZeux. You may wish to read the help\nsection entitled \"Controls\" to learn how to play MegaZeux.\n\nIf you're more adventurous, start with a more complex game like\nDemon Earth or Bernard the Bard, or a prettier game like &amp; or\nFritz Blitz to see what kind of graphics functions, bells and\nwhistles MZX can offer.\n\nOnce you have the feel for the game, feel free to dive into the\nWorld Editor and get messy! You should probably read the help\nsection entitled \"The World Editor\" first.\n\nTo go to one of these sections now, hit Enter after aligning\nthe arrows with one of these choices. Press ESC now to exit to\nthe game.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n<a class=\"hL\" href=\"#CONTROLS.HLP__ctr\">Controls</a>\n<a class=\"hL\" href=\"#THEWORLD.HLP__1st\">The World Editor</a>\n\n<a class=\"hL\" href=\"#NEWINVER.HLP__1st\">NEW in MegaZeux!</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"ZZT.HLP\">\n<a class=\"hA\" name=\"ZZT.HLP__zzt\"> </a>\n<p class=\"hC\"><span class=\"f9\">A ZZTer's Guide to MegaZeux</span></p>\nZZT and MegaZeux are worlds apart, although one can convert\nfrom making ZZT games to making basic MegaZeux games with\n(mostly) minimal effort. This section is to help seasoned\nZZTers adjust to MegaZeux.\n\nFirstly, some mechanistic differences exist. Here are some of\nthe more prominent examples.\n\n-MegaZeux uses counters instead of flags; however, counters\ncan easily be used as flags if set only to 1 and 0.\n-MegaZeux has an incredibly high amount of available counters.\n-There is no default mechanism for torches.\n-No equivalent of centipedes exists as a default enemy.\n-Multiple MegaZeux keys of the same color can be picked up at\n once.\n-There is no board setting controlling the amount of player\nshots that can be on a given board, outside of preventing the\nplayer from shooting altogether.\n-Cloning the player has no use in MZX; uses of player clones\nneed simulated in Robotic.\n-Unlike ZZT Objects, MZX Robots can detect being shot when the\nplayer shoots them point-blank.\n-ZZT prevents moving between boards if the other board has an\nobject where the player would end up, but MZX simply overwrites\nthe object on the other board with the player.\n-Bomb explosions are not instantaneous but instead radiate out\nfrom the center. They are also diamond-shaped as opposed to\na flattened oval shape and do not ignore walls.\n-MegaZeux uses cycle 1 as default (as opposed to ZZT's cycle\n3).\n-No equivalent of duplicators exists.\n-Seekers (aka \"stars\") and bears do not destroy Breakaway\nterritories in MegaZeux.\n-MegaZeux water is not equivalent to ZZT water, as it can be\ntraversed. ZZT water is most similar to MZX's \"goop\".\n-MZX Sharks can be shot. They do not swim in water, but can in\nlava or goop. They can also shoot any type of projectile.\n-MZX Scrolls cannot execute code. Use a Robot ending with the\n\"die\" command to emulate ZZT scrolls with code instead.\n-Terrains that give out a distinctive message on first touch\n(fakes, forests) give out no such message in MZX.\n-There are two types of Gems; the one that best emulates ZZT\ngems is the Magic Gem.\n-Lasers will not push the player into other objects. This makes\nsome ZZT laser configurations a lot less deadly.\n-Scrolls with only one line launch a message box (instead of\nusing the message line for the line).\n-The Shift-? hotkey (often used for inventory engines) does not\nexist in MegaZeux but can be emulated with Robotic.\n-ZZT has one less bullet type than MZX; shift MZX types one\ntier of \"friendliness\" for each typical shooter to get\nZZT-equivalent bullets.\n-Putting in fake commands for comedic effect (e.g. putting in\n<span class=\"fA\">#GOSUCKANEGG</span><span class=\"fF\"> to make ZZT beep and go \"ERR: Bad command</span>\nGOSUCKANEGG\") does not work in MegaZeux and has to be emulated.\n-Special exploits (e.g. black holes, monitors, speed-up\nexploit, flag overloading) have no direct MZX equivalent.\n-Other, extremely minor, differences best left for total wonks\nor for ZZT port authors, while others are implicitly understood\nand do not require spelling out.\n\nSecondly, here is how the ZZT-OOP commands convert to Robotic.\n\n---------------------------------------------------------------\n\nKey:\n\n[*] = There's no direct (i.e. one-line) way to make Robots do\ncommands on IF statements. A label pointing to the command will\nhave to do.\n\n[+] = There is no good analogue for this command, due to\nbound objects sharing zap/restore settings in ZZT. Without\nthese concerns, COPYROBOT is the closest match. Also, ZZT\nobjects using BIND use only as much memory as the object had\nbefore in ZZT, while Robots using COPYROBOT will use as much\nmemory as the target Robot once copied.\n\n[#] = You need to manually add a label at the top of the Robot\nfor this in MZX.\n\n---------------------------------------------------------------\n\n<span class=\"fA\">@object_name = . \"@Robot_name\"</span>\n\n<span class=\"fA\">/direction/direction = PERSISTENT GO </span><span class=\"fF\">(works only for cardinal</span>\ndirections and idle: n s e w i)\n\n<span class=\"fA\">?direction = / \"directions\"</span>\n\n<span class=\"fA\">'comment = . \"comment\"</span>\n\n<span class=\"fA\">one line of text = * \"text\"</span>\n\n<span class=\"fA\">multiple lines of text = % \"text\"</span>\n\n<span class=\"fA\">!label;text = ? \"label\" \"text\"</span>\n\n<span class=\"fA\">$ text = % \"~ftext\" </span><span class=\"fF\">(by default palette)</span>\n\n<span class=\"fA\">#BECOME thing = BECOME color thing param</span>\n\n<span class=\"fA\">#BIND object_name = COPYROBOT \"Robot\" </span><span class=\"fF\">[+]</span>\n\n<span class=\"fA\">#CHANGE thing newthing = CHANGE color thing param newcolor</span>\n<span class=\"fA\">newthing newparam</span>\n\n<span class=\"fA\">#CHAR # = CHAR #</span>\n\n<span class=\"fA\">#CLEAR flag = SET \"counter\" 0</span>\n\n<span class=\"fA\">#CYCLE # = CYCLE #</span>\n\n<span class=\"fA\">#DIE = DIE</span>\n\n<span class=\"fA\">#END = END</span>\n\n<span class=\"fA\">#ENDGAME = ENDGAME</span>\n\n<span class=\"fA\">#GIVE item # failure_label = GIVE # item </span><span class=\"fF\">(MZX version has no</span>\noptional failure label)\n\n<span class=\"fA\">#GO direction = GO direction #</span>\n\n<span class=\"fA\">#IDLE = WAIT #</span>\n\n<span class=\"fA\">#IF flag label;text = ? \"counter\" \"label\" \"text\"</span>\n\n<span class=\"fA\">#IF flag THEN command = IF \"counter\" = 1 THEN \"label\" </span><span class=\"fF\">[*]</span>\n\n<span class=\"fA\">#IF ANY object THEN command = IF ANY color thing param \"label\"</span>\n<span class=\"fF\">[*]</span>\n\n<span class=\"fA\">#IF NOT flag THEN command = IF \"counter\" = 0 THEN \"label\" </span><span class=\"fF\">[*]</span>\n\n<span class=\"fA\">#IF condition THEN command = IF condition THEN \"label\" </span><span class=\"fF\">[*]</span>\n\n<span class=\"fA\">#IF NOT condition THEN command = IF NOT condition THEN \"label\"</span>\n<span class=\"fF\">[*]</span>\n\n<span class=\"fA\">#LOCK = LOCKSELF</span>\n\n<span class=\"fA\">#PLAY notes = PLAY \"notes\"</span>\n\n<span class=\"fA\">#PUT direction thing = PUT color thing param direction</span>\n\n<span class=\"fA\">#RESTART </span><span class=\"fF\">[#]</span>\n\n<span class=\"fA\">#RESTORE label = RESTORE \"label\" #</span>\n\n<span class=\"fA\">#SEND label = GOTO \"label\"</span>\n\n<span class=\"fA\">#SEND Objectname:\"label\" = SEND \"Robotname\" \"label\"</span>\n\n<span class=\"fA\">#SEND ALL:command = SEND \"All\" \"label\" </span><span class=\"fF\">[*]</span>\n\n<span class=\"fA\">#SET flag = SET \"counter\" 1</span>\n\n<span class=\"fA\">#SHOOT direction = SHOOT direction</span>\n\n<span class=\"fA\">#TAKE item # failure_label = TAKE # item \"failure_label\"</span>\n(failure label optional)\n\n<span class=\"fA\">#THROWSTAR direction = SHOOTSEEKER direction</span>\n\n<span class=\"fA\">#TRY direction label = TRY direction \"label\"</span>\n\n<span class=\"fA\">#UNLOCK = UNLOCKSELF</span>\n\n<span class=\"fA\">#WALK direction = WALK direction</span>\n\n<span class=\"fA\">#ZAP label = ZAP \"label\" #</span>\n\n---------------------------------------------------------------\n\nAs for conditions, a few are changed:\n\n<span class=\"fA\">ALLIGNED</span><span class=\"fF\"> is now </span><span class=\"fA\">ALIGNED</span>\n\n<span class=\"fA\">CONTACT</span><span class=\"fF\"> is now </span><span class=\"fA\">TOUCHING ANYDIR</span>\n\n<span class=\"fA\">ENERGIZED</span><span class=\"fF\"> is no longer a condition but the \"invinco\" label.</span>\n\n---------------------------------------------------------------\n\nThis should be all one needs to make most ZZT-style games for\nMegaZeux, except for a little Robotic knowledge to replicate\nthings like torches and duplicators. Of course, much more\npowerful games can be made with MegaZeux; see corresponding\nhelp for details.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"DIABOX.HLP\">\n<a class=\"hA\" name=\"DIABOX.HLP__098\"> </a>\n<p class=\"hC\"><span class=\"f9\">Dialog Boxes</span></p>\nMuch of MegaZeux's interface is made of dialog boxes. A dialog\nbox is a form of inputting various information. An example of a\ndialog box is the Save Game box or the Settings box, both\nin-game. To use a box is simple - Use TAB and SHIFT+TAB to\nhighlight an element, and type or use the cursor keys to change\nthe value. Press ENTER on a button (Rectangle with a label) to\nexit the dialog or produce an effect.\n\nMZX also utilizes mouse control. You can click on a dialog box\nelement to select it, or click on a selected element to change\nit. The mouse wheel will cycle up/down the choices in the\ncurrent section. Click on a selected button to activate it.\n\nYou can usually use HOME to jump to the first section of the\ndialog box, and END to jump to the OK or NEXT button. TAB jumps\nforward a section, while SHIFT+TAB goes backwards a section.\n\nThe different dialog box elements and special related keys\nare explained in detail for the remainder of this section.\n\nINPUT - This is where you type in a series of characters,\nusually letters and numbers. You can move the cursor around\nwithin the line, type to insert characters, move to the start\nwith Home, and move to the end with End. Alt + Backspace\ndeletes the entire line, while Ctrl + Backspace deletes the\nlast typed word.\n\nNUMBER - This is where you have a number and you change it\nwith the arrow keys or mouse clicks on buttons. Up, Down, and\nthe mouse wheel will change it by 1, Alt+Up, Alt+Down and\nAlt+Wheel will change it by 10, and PageUp and PageDown will\nchange it by 100. You can also hold the mouse button down on one\nof the arrows to change the number or press 0-9 to set the last\ndigit. Pressing 0-9 on the keypad changes the current digit to\nthat value. Finally, Backspace deletes the last digit.\n\nNUMBER LINE - This is a line of numbers, with one highlighted.\nIt functions in a manner similar to the above, but in a limited\nrange.\n\nCHARACTER - This is where you can select characters. Press a\ncharacter to use that character; click on it or press Enter to\nget a large menu of characters (32x8) to select from.\n\nCOLOR - This is where you can select a single color. Press\nEnter or click on it to get a large menu of colors to choose\nfrom.\n\nCHECK BOXES - These are On/Off switches. Use Up/Down or the\nmouse wheel to move within the list of choices, and use Space,\nEnter, or mouse clicks to toggle choices on and off. Press Tab\nto jump out of a check box section. An [X] means the option is\ncurrently on.\n\nRADIO BUTTONS - These are similar to Check Boxes, but are\nmutually exclusive. The mouse or arrow keys will select the\ncurrent and ONLY option that is on. The ( ) shows the current\noption.\n\nBUTTON - These are rectangles with labels. When selected,\npress Enter or click to activate. OK or Done will verify the\ncontents of the dialog box. Cancel will cancel any changes\nyou've made. (ESC will usually do this as well.) Next and\nPrevious move between multiple dialog boxes. Other buttons\ndo what their label signifies and are explained in the\nappropriate help section.\n\nLISTS - These show a selection from a list of choices. The\ncurrent selection is shown. Press Enter or click on it to\nbring up a list of choices from which you can select a new\nchoice.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"CONTROLS.HLP\">\n<a class=\"hA\" name=\"CONTROLS.HLP__ctr\"> </a>\n<p class=\"hC\"><span class=\"f9\">Controls</span></p>\nUniversal controls are as follows:\n\n-Tab goes forward one section; Shift+Tab goes backward one\nsection.\n\n-Esc cancels.\n\n-Enter selects.\n\n-Alt+Backspace deletes the entire line.\n\n-Ctrl+Backspace deletes the last typed word.\n\n-Ctrl+Left jumps to the previous word; Ctrl+Right jumps to the\nnext word.\n\n-0 thru 9 sets the last digit in a number box (such as setting\namounts of items in a chest); backspace deletes the last digit.\n\nOn Mac platforms, any command that uses the Alt key can take &#xE0BD;&#xE0BE;\n(the command key) instead.\n\nFile access controls are as follows:\n\n-(0-9 and A-Z) will jump to the first file/directory starting\nwith that character. More specific seeking can be done by\ntyping characters in quick succession (for instance, s+e+c\nwould jump to the first file starting with sec).\n\n-Del will delete the highlighted file/directory. You will be\nasked for confirmation.\n\n-Alt+R will rename the highlighted file/directory.\n\n-Alt+N will create a new directory.\n\nIn-game controls are as follows:\n\nF1 - Help\nUse this at any time to bring up context-relevant help. While in\na loaded world, this key will load the Controls section. Some\ngames may have this menu disabled or replaced with their own\nhelp menus.\n\nEnter - Menu/Status\nUse this to bring up a menu of options and a list of your\ncurrent stats, such as Score, Gems, any custom-defined status\ncounters, etc. Some games may have this key do nothing, or even\nsomething different entirely. Selecting the key shortcuts\n(everything but move/shoot/bomb) will perform the given action.\nEither click on actions to select them, or highlight an action\nwith the arrow keys and press Enter.\n\nCertain key shortcuts will not be listed if the current MZX\nworld prevents their default actions from happening. \"Help\",\n\"Save\", \"Load\", \"Quicksave\" and \"Quickload\" can be filtered out\nthis way, while \"Exit game\" and \"Settings\" are always available\nand still work when selected even if otherwise locked out.\n\nESC - Exit to Title\nThis will load a prompt to quit the current game and return to\nits title screen. In yes/no dialog boxes, this key triggers the\n\"no\" option. Some games may disable using ESC to trigger the\nexit prompt, but other methods (such as Alt+F4 or selecting the\n\"ESC - Exit to Title\" text in the Enter menu) will always work.\n\nF2 or Ctrl+F2 or Alt+F2 - Settings\n<a class=\"hA\" name=\"CONTROLS.HLP__092\">This will bring up a dialog box where you can change the</a>\ncurrent game settings: Game Speed (if allowed), Music/sample SFX\ntoggle, PC speaker SFX toggle, and Audio Volumes (Music,\nSamples, and PC Speaker).\n\nSpeed 1 is the fastest; speed 16 is the slowest. A speed of 1\nwill run MZX as fast as possible and as such acts differently\nbetween computers; it will also cause flicker in fullscreen\nmode, so it is not recommended unless it is your only option.\n(Please note that some games may have the speed change by\nitself and/or enforce a set speed.)\n\nNo changes to sound options will go into effect until the\nSettings menu is left.\n\nIn addition to these settings, one can change the current\nrenderer with the \"Select renderer...\" button, and can change\nthe scaling shader if running MegaZeux with the GLSL or\nauto_GLSL renderers.\n\nSettings for game speed will only apply for the current world;\nall other settings are global.\n\nNote that some games may block access to the F2 menu entirely.\nCtrl+F2 or Alt+F2 will ignore this setting and allow access to\nthe menu even if it is prohibited otherwise, as will selecting\nthe \"F2 - Settings\" text in the Enter menu. Ctrl+F2/Alt+F2 will\nnot override any F2 block while MZX is in standalone mode.\n\nF3 - Save Game\nThis will load a prompt for a filename, allowing the saving of\nthe exact state of the current game. Some games may not allow\nmanual saving on some (or any) of its screens, or will only\nallow manual saving in certain spots of a board. Saving lets you\nquit your game in the middle or take precautions against unknown\ndangers.\n\nF4 - Restore Game\nThis will let you select a saved game from a list of filenames.\nThe game will then be reloaded from the same point you left\noff. While most games will allow loading anywhere, some will\nrestrict loading to certain times or will use a custom\nmechanism for loading games instead. MegaZeux will refuse to\nload any save made in a version older than 2.84 or newer than\nthe current version number. (For instance, 2.92 can load saves\nmade in 2.92c and 2.90b, but not saves made in 2.93 or 2.83.)\n\nMegaZeux has the ability to save and load games in save slots\ninstead of choosing a file. Save slots are tied to the current\nMZX world, and each MZX world gets 10 save slots. This can be\nenabled in the config file.\n\nNOTE: A few games may save or load automatically, and\nhigh-caliber games may have completely different methods of\nsaving and loading games.\n\n<a class=\"hL\" href=\"#CONFGINI.HLP__1st\">The Config File</a>\n\nF5 or Ins - Toggle Bomb Type\nThis will switch your current bomb type between High Strength\nand Low Strength.\n\nF6 - Debug Window\nThis will bring up a small box in the lower left corner of the\nscreen, detailing several statistics. The top-right of the box\ndisplays the world version of the current world; X/Y lists the\ncurrent x,y position of the player; Board lists the current\nboard number; Robot mem lists the amount of Robot memory\ncurrently consumed by the current board's Robots; the space\nbelow lists the currently playing music, with \"(no module)\" for\nnone and \"*\" for the module wildcard; finally, the bottom right\nnumbers list the key_code (green) and key_pressed (magenta)\nvalues of the last pressed key. Press F6 again to turn the box\noff. This shortcut will only work during playtesting in the\neditor.\n\nF7 - Items Cheat\n\nThis will set ammo, coins, gems, hibombs and lobombs to 32767,\nfill health (set to maxhealth), fill lives (set to maxlives),\nand set keys to one of each color. This is a cheating function,\nso do not use unless fully necessary; a few games punish the\nplayer for using this function. This may be limited to\nplaytesting sessions in the editor, or limited to MZXRun builds,\ndepending on config.txt settings.\n\nF8 - Zap Cheat\n\nThis will destroy everything in all eight directions directly\naround the player, replacing all eight squares with spaces.\nThis is a cheating function, so do not use unless fully\nnecessary; a few games punish the player for using this\nfunction. This may be limited to playtesting sessions in the\neditor, or limited to MZXRun builds, depending on config.txt\nsettings.\n\nF9 - Quicksave\nThis will save the game, like F3, but will automatically save to\nthe last filename you used. The old file will be overwritten. If\nno game has been saved since MegaZeux was started, the save will\nhave the default filename listed in the config.txt file. If\nsaving is blocked, quicksaving will be blocked as well.\n\n<a class=\"hL\" href=\"#CONFGINI.HLP__1st\">The Config File</a>\n\nF10 - Quickload\nThis will load the last save that was saved with F3 or F9. If no\ngame has been saved since MegaZeux was started, the default save\nfilename will be used. The same caveats about save versions\nstill apply here. If loading is blocked, quickloading will be\nblocked as well.\n\nF11 - Counter Debug Mode\nThis will load a screen listing which counters and strings\nhave been set, as well as values for default counters and\nstrings. Counter debug mode allows considerable manipulation of\ncounters and strings, among other things. This shortcut only\nworks when playtesting in the editor. For more detail, go to its\nspecific section.\n\n<a class=\"hL\" href=\"#DBGMODE.HLP__100\">Debug Modes - Counter Debug Mode</a>\n\nAlt+F11 - Robotic Debugger\nThis will launch the config menu for the built-in Robotic\ndebugger. The Robotic debugger can monitor running Robotic code\nand allow step-by-step reading and manipulation of running\nRobots as they execute. This shortcut only works when\nplaytesting in the editor. For more detail, go to its specific\nsection.\n\n<a class=\"hL\" href=\"#DBGMODE.HLP__101\">Debug Modes - The Robotic Debugger</a>\n\nF12 - Take Screenshot\nPressing F12 will make a 640x350 screenshot (in PNG format by\ndefault) in the working directory, using the software renderer.\nThe file naming starts from \"screen0\". The config file may\ndisallow taking screenshots, depending on setting.\n\nArrows - Move\nThe arrow keys will move the player and allow it to interact\nwith most objects in most games.\n\nSpace - Shoot\nIf Space is held, pressing an arrow key fires a bullet from the\nplayer in the selected direction, with each shot taking one\nammo. The player will not move with the arrow keys while trying\nto fire a bullet, even if the player can't shoot or is out of\nammo. Some games may have Space shoot different shots or even\ngive Space different uses.\n\nDel - Bomb\nBy default, this will drop a bomb of the current type BENEATH\nthe player. Move off of it to activate it, then run before it\nexplodes! Uses of the del key may vary from game to game.\n\nTab / Left Arrow / Right Arrow - Select Option\nIn yes/no dialog boxes, these keys toggle between yes and no.\n\nOther games may have other controls, such as 'S' to cast a\nspell or 'I' to bring up an inventory screen, but they should be\ndetailed within the game.\n\nThe following keys are active at the title screen:\n\nF1 - Help (see above)\n\nEnter - Menu\nThis is similar to default Enter within the game. However,\nit brings up a list of key bindings instead.\n\nESC - Exit MegaZeux\nPressing ESC will load a prompt where the user can exit\nMegaZeux.\n\nF2 / Ctrl+F2 / Alt+F2 / S - Settings (see above)\n\nF3 or L - Load World\nThis will allow you to load up the title screen of any MegaZeux\nworld. A list of choices will be presented to you to select\nfrom. After the world is loaded, you may watch the title\nscreen, then press 'P' to play.\n\nF4 or R - Restore Game (see above)\n\nF5 or P - Play Game\nThis will stop the title screen and actually begin game play.\n\nF7 or U - Updater\nThis will load the updater, if it is enabled. The updater will\nconnect to an online repository and check if a new MegaZeux\nversion is available. If so, the user gets the option to update\nMegaZeux. MZX will then list the files added/changed/deleted in\nthe update, and if the user decides to update, MZX will restart\nwhen the update is finished.\nWARNING: This will most likely replace your config.txt file, so\nif you want to keep your settings, make a backup first so you\ncan apply your settings to the new file!\n\nF8 or N - New World\nThis will quit the gaming portion of MegaZeux and enter the\nintegrated World Editor, set to an empty MZX world. Alt+N will\nact the same, but skip the prompt to name and create a starting\nboard.\n\nF9 or E - Edit World\nThis will quit the gaming portion of MegaZeux and enter the\nintegrated World Editor, set to edit the currently-loaded\nworld. When no world is loaded, this acts the same as New World,\nand Alt+E with no world loaded will also skip the prompt to name\nand create a starting board. Note that \"MZXRun\" builds of\nMegaZeux do not contain the editor. For detailed info on using\nthe World Editor, view the appropriate help sections.\n\n<a class=\"hL\" href=\"#THEWORLD.HLP__1st\">The World Editor</a>\n\nF10 - Quickload (see above)\n\n-= About MegaZeux =-\nSelecting this will display various information about the\ncurrently-running MegaZeux build, such as version, architecture,\nand library build versions.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"NONDESCR.HLP\">\n<a class=\"hA\" name=\"NONDESCR.HLP__091\"> </a>\n<p class=\"hC\"><span class=\"f9\">Nondescript Play Tips</span></p>\nHi! Welcome to the default help entry for playing a MegaZeux\ngame!\n\nFirst, let me warn you that many worlds you get from archives\nor (especially) from personal webpages may not be of acceptable\nquality. They may be unfair, boring, have numerous bugs, and/or\nnot give proper instructions. On a related note, PLEASE try to\nget feedback and try acting on it before uploading your game,\neven in these days where games are fewer and farther between.\nReally old games of bad quality, on the other hand, are\ntolerated somewhat better. Don't let a few dull games ruin the\nfun.\n\nWith that out of the way, some basic play tips:\n\n<span class=\"fA\">*</span><span class=\"fF\"> RTFM (Read the Flipping Manual). Whatever information</span>\n the game has to give is probably important. Some authors\n include info files in their distributions or custom F1 help;\n make sure these are read. It saves everybody time and effort.\n To authors, it saves time and effort to include at least one of\n these in your submissions.\n\n<span class=\"fA\">*</span><span class=\"fF\"> Make sure you visit every screen possible.</span>\n\n<span class=\"fA\">*</span><span class=\"fF\"> Save your game! It is rather simple - in most games,</span>\n Press F3 to save your game to disk at the EXACT point you are\n (if allowed). Press F4 to reload a saved game. You can use F9\n and F10 to quick-save and quick-load, which work on the last\n game you saved. Remember to save often and keep multiple saves-\n If something ends your game, you'll want to lose as little\n progress as possible.\n\n<span class=\"fA\">*</span><span class=\"fF\"> Touch everything! Even things of seemingly little value</span>\n may prove worthy of your attention. If it kills you, then...\n well... hope you saved (see above).\n\n<span class=\"fA\">*</span><span class=\"fF\"> Collect supplies! Make sure you grab every coin, gem,</span>\n ammo dump, bomb, chest, pouch, and foobar you see! You will\n often be in want of supplies, so don't push things.\n\n<span class=\"fA\">*</span><span class=\"fF\"> Remember how things work. Most things in MegaZeux have</span>\n patterns, even Robots, and the same object will usually do the\n same thing all the time. (However, two objects may look alike\n and not really be the same thing.)\n\n<span class=\"fA\">*</span><span class=\"fF\"> Don't take anything for granted. If it looks like a spike,</span>\n it probably is. _Probably_. Although it is rare that these\n types of puzzles must be solved to complete a game, there\n are often bonuses, hidden rooms, etc. behind illusions.\n\n<span class=\"fA\">*</span><span class=\"fF\"> Try things twice, even thrice. Sometimes objects respond</span>\n differently at other times. If you get a new treasure, go\n talk to all the citizens - maybe one of them will say\n something new. If some place seems inaccessible, come back\n later!\n\n<span class=\"fA\">*</span><span class=\"fF\"> If doing the same thing over and over keeps failing, try</span>\n something else. It helps to look at new methods and to put\n distance between yourself and the problem.\n\n<span class=\"fA\">*</span><span class=\"fF\"> Bullets can stop spitting fire, but only sometimes. If trying</span>\n to gun down an enemy that spits fire, stagger your shots\n somewhat; holding down the spacebar often results in ALL of\n your shots being cancelled.\n\n<span class=\"fA\">*</span><span class=\"fF\"> If all else fails, look in the game with the editor. This</span>\n depends on the clarity of the author(s)'s code, but looking in\n the editor can help if all other solutions have been exhausted.\n Searching functions can make this easier: the world editor can\n (e.g.) show Robots with Shift+F2, and the Robotic editor can do\n searches with Ctrl+F.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"MOUSESUP.HLP\">\n<a class=\"hA\" name=\"MOUSESUP.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Mouse Support in MegaZeux</span></p>\nUse of the mouse is very simple. Move the mouse to move the\nmouse cursor. Press the Left mouse button to select something,\nactivate something, etc. Press the Right mouse button to grab\nwhatever's under the cursor in the World Editor. Certain games\nmay make use of the mouse during gameplay. The scroll wheel will\nalways emulate up/down presses in dialog boxes and the list menu\non top of whatever functions it may have in the current game.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"BUILTINS.HLP\">\n<a class=\"hA\" name=\"BUILTINS.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">The Mirth of Built-ins</span></p>\nThe following help section contains a list of the different\nobjects, enemies, items, and terrains you will find within\nthe different MegaZeux worlds.\nWant to make a (very) quick and (very) dirty game? Built-ins are\nthe way to do it! However, outside of a select few built-ins\n(such as certain terrains, Robots and items) heavy usage of\nthese built-ins for long-term projects is best-suited for\nbeginners and savants.\n\n<p class=\"hC\"><span class=\"f8 b0\">&#xE0B1; </span><span class=\"fF b0\">Terrains</span><span class=\"f8 b0\"> &#xE0B1;</span></p>\n<span class=\"fB\">Space</span>\n\nThis is the simplest terrain; it does absolutely nothing!\n\n<span class=\"fB\">Normal         </span><span class=\"fA b0\">&#xE0B2;</span>\n<span class=\"fB\">Solid          </span><span class=\"fE\">&#xE0DB;</span>\n<span class=\"fB\">Tree           </span><span class=\"fA\">&#xE006;</span>\n<span class=\"fB\">Line           </span><span class=\"fF\">&#xE0CD;&#xE0CD;&#xE0CD;&#xE0CD;&#xE0CD;</span>\n<span class=\"fB\">Custom Block   </span><span class=\"f7\">?</span>\n\nThese are all basic obstacles; they just get in the way.\nSometimes trees can be burned down.\n\n<span class=\"fB\">Breakaway      </span><span class=\"fC b0\">&#xE0B1;</span>\n<span class=\"fB\">Custom Break   </span><span class=\"f7\">?</span>\n\nThese block movement as well, but they can be destroyed with\nmost weapons (such as bullets or bombs). Certain other things\ncan destroy them as well.\n\n<span class=\"fB\">Fake           </span><span class=\"fA\">&#xE0B2;</span>\n<span class=\"fB\">Carpet         </span><span class=\"f4 b0\">&#xE0B1;</span>\n<span class=\"fB\">Floor          </span><span class=\"f9 b0\">&#xE0B0;</span>\n<span class=\"fB\">Tiles          </span><span class=\"f0 bF\">&#xE0FE;</span>\n<span class=\"fB\">Custom Floor   </span><span class=\"f7\">?</span>\n\nThese are all treated as flooring, or \"background\". Anything,\nincluding yourself, can move onto and over these. They are\nprimarily for decoration.\n\n<span class=\"fB\">Web            </span><span class=\"f7\">&#xE0C5;</span>\n<span class=\"fB\">Thick Web      </span><span class=\"f7\">&#xE0CE;</span>\n\nThese are another type of flooring. However, webs are often\nthe home for spiders, so watch your step!\n\n<span class=\"fB\">Forest         </span><span class=\"f2 b0\">&#xE0B2;</span>\n\nThis terrain will block the path of almost any built-in. You,\nhowever, can move through it with ease, clearing a pathway.\nEnemies can move along a cleared path.\n\n<span class=\"fB\">Invis. Wall</span>\n\nThis LOOKS like just an empty space... until you bump into it.\nThen it becomes a normal wall, blocking your path.\n\n<p class=\"hC\"><span class=\"f8 b0\">&#xE0B1; </span><span class=\"fF b0\">Items</span><span class=\"f8 b0\"> &#xE0B1;</span></p>\n<span class=\"fB\">Gem            &#xE004;</span>\n<span class=\"fB\">Magic Gem      &#xE004;</span>\n\nGems are a collectible. Your total number of gems is shown on\nthe status screen, and each gem gives you one point. Many older\ngames also use them as a type of currency, where you can trade\ngems for stuff like food, ammo, weapons, or hints. Gems are\nfragile, and will be destroyed when shot or bombed.\nMagic Gems count as Gems and act like Gems, but they also\nincrease health by one point apiece when taken.\n\n<span class=\"fB\">Health         </span><span class=\"fC\">&#xE003;</span>\n\nThis will improve your outlook dramatically. Collecting one of\nthese increases health by a certain amount. The amount varies\nfor different hearts. Keep in mind that if you're currently at\nyour maximum health, running into a heart will still collect it.\n\n<span class=\"fB\">Energizer      </span><span class=\"f1\">&#xE09B;</span>\n\nAfter grabbing an energizer, the player will flash colors for a\nlimited time (specifically, for 113 cycles). During this period,\nyou are invincible against enemies, bullets, fire, lava, poison\ndamage, Robots attempting to TAKE HEALTHS, and most other forms\nof pain. Be careful not to get into a dangerous situation as its\nenergy runs out!\n\nAny energizer grabbed while already energized resets the timeout\nperiod to 113 cycles.\n\n<span class=\"fB\">Ammo           </span><span class=\"f3\">&#xE0A3; &#xE0A4;</span>\n\nWhen you grab ammunition, it will add a certain amount of\nammo to your supplies. The small piles hold 0-9 shots, and the\nlarge piles 10-255. The amount may be different for each pile.\n\n<span class=\"fB\">Bomb           </span><span class=\"f0\">&#xE00B;</span>\n\nEach bomb you grab adds another to your supply. The sound made\nwhen you grab the bomb will be high-pitched for a high strength\nbomb and low-pitched for the rare low strength bomb. Hibombs\nwill explode for five tiles in all directions, while lobombs\nexplode for only three tiles in all directions.\n\nSome boards may not allow the player to collect bombs, and\ninstead will have bombs ignite on touch.\n\n<span class=\"fB\">Key            </span><span class=\"fA\">&#xE00C;</span>\n\nCollect keys to open locks, doors, and gates later on. The key\nand the lock/etc. must match foreground colors, and a key will\nonly work once. (For example, a dark blue key will open any dark\nblue lock, regardless of the lock's background color.) You can\ncarry up to sixteen keys at once. The default status screen, if\nviewable, will show your current supply of keys.\n\nIf you've played ZZT games, you'll be interested to know that\nyou can carry multiple keys of the same color.\n\n<span class=\"fB\">Lock           </span><span class=\"fA\">&#xE008;</span>\n\nA lock will only open if you have a key of the same foreground\ncolor to unlock it. The key can only be used once and will\ndisappear along with the lock.\n\n<span class=\"fB\">Coin           </span><span class=\"fE\">&#xE007;</span>\nCollecting coins is a good idea. Coins not only increase your\nscore by one, but can often be used to purchase valuable items\nor services from vendors. Unlike Gems, Coins can withstand being\nshot or bombed.\n\n<span class=\"fB\">Life           </span><span class=\"fB\">&#xE09B;</span>\n\nA life orb will give you yet another chance for survival in\nMegaZeux. Keep in mind that if you're currently at your maximum\namount of lives, running into a life orb will still collect it.\n\n<span class=\"fB\">Pouch          </span><span class=\"f7\">&#xE09F;</span>\n\nA pouch is usually filled with coins, gems, or even both. The\namount varies, but often you will find yourself pleasantly\nrich...as long as you can get to it before a Bomb can destroy\nit.\n\n<span class=\"fB\">Chest          </span><span class=\"f6\">&#xE0A0;</span>\n\nA chest can contain numerous things. The contents will be one\nof the following: Empty, a Key, Coins, Lives, Ammo, Health,\na Potion or Ring, Bombs, or Gems. Once you grab the contents,\nthe chest itself will remain, but be empty. Chests will not\nsurvive being bombed, empty or not.\n\n<span class=\"fB\">Ring           </span><span class=\"fE\">o</span>\n<span class=\"fB\">Potion         &#xE096;</span>\n\nThese mystical items will bestow a magical effect on you when\nyou wear or drink them. The effect, however, is unknown to\nyou until you try it... and some effects aren't so nice. All\nrings and potions increase score by 5 when collected.\n<a class=\"hA\" name=\"BUILTINS.HLP__prx\"> </a>\n<p class=\"hC\"><span class=\"f8 b0\">&#xE0B1; </span><span class=\"fF b0\">Potion and Ring Effects</span><span class=\"f8 b0\"> &#xE0B1;</span></p>\nEffects are limited to the current room only, except for\nhealing, hurting, and invinco.\n\n<span class=\"fB\">No Effect</span>\n\nThis effect does absolutely nothing.\n\n<span class=\"fB\">Invinco</span>\n\nJust like an energizer, you will become invulnerable to most\nforms of pain until you stop flashing.\n\n<span class=\"fB\">Blast</span>\n\nScatters plentiful, random explosions around the screen.\n\n<span class=\"fB\">Health x10</span>\n<span class=\"fB\">Health x50</span>\n\nGives the player 10 or 50 additional health points,\nrespectively.\n\n<span class=\"fB\">Poison</span>\n\nReduces the player's health by 10.\n\n<span class=\"fB\">Blind</span>\n\nTemporarily blinds the player. The entire viewport, except\nfor the player, will look like dark gray floors. You can still\nmove and interact, but you won't be able to see anything\ndistinct but the player.\n\n<span class=\"fB\">Kill Enemies</span>\n\nKills all enemies in the room, including \"Invincible\" enemies.\n\n<span class=\"fB\">Lava Walker</span>\n\nAllows the player to temporarily walk on lava and fire.\n\n<span class=\"fB\">Detonate</span>\n\nExplodes all bombs and lit bombs in the room.\n\n<span class=\"fB\">Banish (Dragons)</span>\n\nTurns all dragons on the board into ghosts. Each ghost made this\nway will have intelligence set to 4, movement speed set to 4,\nand the \"Invincible\" flag set to off.\n\n<span class=\"fB\">Summon (Dragons)</span>\n\nTurns all creatures on the board - excepting guns - into\ndragons. (This includes other dragons.) Each dragon made this\nway will have a firing rate of 3, 4 hit points, and the \"Moves\"\nflag set to on.\n\n<span class=\"fB\">Avalanche</span>\n\nScatters boulders randomly around the screen.\n\n<span class=\"fB\">Freeze Time</span>\n\nFreezes ALL non-player on-screen objects (including all Robots\nexcept the Global) for a short time. This will also freeze any\nactive board timer.\n\n<span class=\"fB\">Wind</span>\n\nThe player retains control but will also move additional random\nsteps for a limited time. The wind will blow the player in all\ndirections.\n\n<span class=\"fB\">Slow Time</span>\n\nSlows ALL non-player on-screen objects (including all Robots\nexcept the Global) for a short time. The resulting speed is\napproximately half normal speed. This will also slow any active\nboard timer.\n\n<p class=\"hC\"><span class=\"f8 b0\">&#xE0B1; </span><span class=\"fF b0\">Creatures</span><span class=\"f8 b0\"> &#xE0B1;</span></p>\n<span class=\"fB\">Snake          </span><span class=\"f2\">&#xE0EB;</span>\n\n<span class=\"fB\">Runner         </span><span class=\"fC\">&#xE002;</span>\n\nA snake moves in a straight line until it hits an obstruction,\nthen aims itself in another direction and continues. Hitting a\nsnake (like all enemies hereafter unless noted) will cause you\nto lose health, then kill the snake, while hitting the snake\nwith a player bullet will kill the snake and increase score by\n3. Runners act like snakes and will take up to four hits, but\nwill only go the opposite direction when obstructed.\n\n<span class=\"fB\">Eye            </span><span class=\"fF\">&#xE0EC;</span>\n\nAn eye chases you down like any ordinary enemy, but when it\ncatches you or dies, it explodes! The size of the explosion\ncan vary with each eye. As eyes float, they can traverse both\ngoop and lava.\n\n<span class=\"fB\">Ghost          </span><span class=\"f7\">&#xE0EA;</span>\n<span class=\"fB\">Thief          </span><span class=\"fC\">&#xE005;</span>\n\nA ghost is the simplest enemy, simply chasing you. While some\nof them are invincible, most can be killed. Thieves chase but\ndon't do damage. They steal your gems on touch instead, and can\ndo so multiple times until killed. Ghosts can traverse lava and\ngoop; thieves cannot. Ghosts give no score when killed by a\nplayer bullet.\n\n                <span class=\"fA\">*</span>\n<span class=\"fB\">Slime Blob     </span><span class=\"fA\">*&#xE0B1;*</span>\n                <span class=\"fA\">*</span>\n\nA slime blob usually doesn't hurt you, but it can quickly\nbecome incredibly annoying because of its habit of dividing\ninto more slime blobs, then hardening into \"solid slime\"\n(i.e. breakables), quickly filling rooms.... You can often\nkill slime by touching or shooting it, but some slimes can be\ninvincible, and others can be harmful to touch. Slime blobs give\nno score when shot by a player bullet.\n\n<span class=\"fB\">Dragon         </span><span class=\"f4\">&#xE015; </span><span class=\"fC\">&#xE00F; </span><span class=\"fE\">*</span><span class=\"fC\">&#xE00F;</span>\n\nSome dragons move around slowly, but their main advantage is\ntheir offense - they can shoot barrages of scorching flame.\nDragons also have a strong defense, taking up to eight hits to\nkill, and take only one hit of damage from explosions. Touching\nthem will take away health, but will NOT hurt the dragon.\nDragons can traverse lava but not goop.\n\n<span class=\"fB\">Fish           </span><span class=\"fE\">&#xE0E0;</span>\n<span class=\"fB\">Shark          </span><span class=\"f7\">&#xE07F;</span>\n<span class=\"fB\">Spitting Tiger </span><span class=\"fB\">&#xE0E3;  </span><span class=\"fF\">&#xE0F9;  &#xE0F9; &#xE0F9;</span>\n\nFish have to stay in the water and often can't hurt you. Those\nthat can, however, will hurt you if you're adjacent to them,\neven if you're on land. Some fish take two hits to kill. Sharks,\nhowever, swim in lava or goop, are always hostile, and can\nattempt to shoot you with fire, bullets or seekers. Spitting\nTigers act like sharks, but are restricted to land.\n\n                <span class=\"f0\">&#xE0DA;&#xE0C5;&#xE0BF;</span>\n<span class=\"fB\">Spider         </span><span class=\"f0\">&#xE0C4;&#xE0C5;</span><span class=\"f7\">&#xE095;</span><span class=\"f0\">&#xE0C5;&#xE0C4;</span>\n                <span class=\"f0\">&#xE0C0;&#xE0C5;&#xE0D9;</span>\n\nSpiders, although eager to catch you, are usually restricted to\nmovement on webs. Certain spiders, however, can actually leave\nthe webs. Some spiders take two hits to kill.\n\n<span class=\"fB\">Goblin         </span><span class=\"f2\">&#xE005;</span>\n\nGoblins chase but periodically stop, making them an easy\nbuilt-in villain.\n\n<span class=\"fB\">Bear           </span><span class=\"f6\">&#xE0AC;</span>\n\nBears only move if you get too close, then they lumber over for\nthe attack. Many require 2 hits to kill.\n\n<span class=\"fB\">Bear Cub       </span><span class=\"f6\">&#xE0AD;</span>\n\nBear cubs are very lively, rushing all over the place. Since\nthey move so fast in a seemingly random manner, they are often\nhard to kill.\n\n<span class=\"fB\">Bullet Gun     </span><span class=\"fF\">&#xE094; &#xE094; &#xE01B;</span>\n<span class=\"fB\">Spinning Gun   </span><span class=\"fC\">&#xE00F;</span><span class=\"fE\">* </span><span class=\"fF\">&#xE01B;</span>\n\nBullet and spinning guns, both indestructible, fire any type of\nnormal projectile (bullets, fire or seekers). The bullet gun,\nhowever, is fixed while the spinning gun can fire from all\ncardinal directions.\n\n<span class=\"fB\">Lazer Gun      </span><span class=\"f0\">&#xE0CE;</span><span class=\"f1\">&#xE082;</span><span class=\"f9\">&#xE082;</span><span class=\"f3\">&#xE082;</span><span class=\"fB\">&#xE082;</span><span class=\"fF\">&#xE082;&#xE082;&#xE082;&#xE082;</span><span class=\"f7\">&#xE082;</span>\n\nThe indestructible lazer guns fire off lazer walls at regular\nintervals, and sustain these walls for a specific length of\ntime. Unlike in ZZT, getting hit by a laser does not push the\nplayer into objects, though it will push the player if a space\nis open. The beam can be obstructed by nearly anything not a\nground type, even including a bullet.\n\n<span class=\"fB\">Missile Gun    </span><span class=\"f0\">&#xE010;</span>\n\nMissile guns fire missiles in one direction. Some fire just\nonce while others can fire missiles indefinitely. The gun\nitself is indestructible.\n\n<p class=\"hC\"><span class=\"f8 b0\">&#xE0B1; </span><span class=\"fF b0\">Puzzle Pieces</span><span class=\"f8 b0\"> &#xE0B1;</span></p>\nThe following objects are often used to create mind-twisting\npuzzles requiring you to push objects all over the place to\nreach a goal. Be warned.\n\n<span class=\"fB\">Boulder        </span><span class=\"f7\">&#xE0E9;</span>\n<span class=\"fB\">Crate          </span><span class=\"f6\">&#xE0FE;</span>\n<span class=\"fB\">Custom Push    </span><span class=\"f7\">?</span>\n\nBoulders and crates can be pushed in any direction, with any\nnumber of pushable things in a row. They can be blown up.\n\n<span class=\"fB\">Box            </span><span class=\"fF\">&#xE0FE;</span>\n<span class=\"fB\">Custom Box     </span><span class=\"f7\">?</span>\n\nBoxes can also be pushed in any direction, but cannot be blown\nup.\n\n<span class=\"fB\">Pusher         </span><span class=\"f0\">&#xE010; </span><span class=\"f7\">&#xE0FE;&#xE0FE;&#xE0FE;</span>\n\nPushers cannot hurt you, but they constantly try to move in one\ngiven direction, pushing almost anything in their path - Boxes,\ncrates, and even you!\n\n<span class=\"fB\">Slider NS      </span><span class=\"fE\">&#xE012;</span>\n<span class=\"fB\">Slider EW      </span><span class=\"fE\">&#xE01D;</span>\n\nSliders can be pushed, but ONLY in certain directions. A Slider\nNS can only be pushed north/south, while a Slider EW can only be\npushed east/west.\n\n<p class=\"hC\"><span class=\"f8 b0\">&#xE0B1; </span><span class=\"fF b0\">Transport</span><span class=\"f8 b0\"> &#xE0B1;</span></p>\n<span class=\"fB\">Stairs         </span><span class=\"fF\">&#xE0A2;</span>\n<span class=\"fB\">Cave (or door) </span><span class=\"f0\">&#xE0A1;</span>\n<span class=\"fB\">Whirlpool      </span><span class=\"f9 b1\">&#xE097;</span>\n\nWhen you enter any one of these, you are transported to another\nlocation within the current world. These are not always\ntwo-way connections.\n\nThe specific destination is determined as follows, from highest\npriority to lowest, scanning right-to-left from the lower-right\ncorner to the upper-left:\n <span class=\"fE\">*</span><span class=\"fF\"> Same passage type, same exact color</span>\n <span class=\"fE\">*</span><span class=\"fF\"> Same exact color</span>\n <span class=\"fE\">*</span><span class=\"fF\"> Same passage type, same foreground color</span>\n <span class=\"fE\">*</span><span class=\"fF\"> Same foreground color</span>\n <span class=\"fE\">*</span><span class=\"fF\"> Same passage type</span>\n <span class=\"fE\">*</span><span class=\"fF\"> Default player position on destination board</span>\n\nIf the current board and destination board are the same, no\nteleport will happen.\n\n<span class=\"fB\">CW             </span><span class=\"fA\">/</span>\n<span class=\"fB\">CCW            </span><span class=\"fA\">\\</span>\n\nThese rotate in the given direction (ClockWise or\nCounterClockWise), rotating everything around them at the same\ntime. Walls and other solid objects will not be affected.\n\n                <span class=\"fF\">v</span>\n<span class=\"fB\">Transport      </span><span class=\"fF\">} {</span>\n                <span class=\"fF\">~</span>\n\nMost transporters face a single direction. Entering these on\nthe \"open\" side, or pushing other things into them, will\ncause a jump to another location. Some transporters rotate;\nthose can be entered from any side.\n\nThe destination of a transport is somewhat complex, but the\nsearch pattern is as follows:\n\n1. If the other side of the entered transport is empty, move\n  there. If there is something there that can be pushed out\n  of the way, move there and push it out of the way.\n2. If the other side is blocked, continue in that direction\n  looking for the first non-blocked transporter facing the\n  OPPOSITE direction, or an \"any-direction\" transport.\n3. If neither condition can be met, no transport takes place.\n\n<p class=\"hC\"><span class=\"f8 b0\">&#xE0B1; </span><span class=\"fF b0\">Elements</span><span class=\"f8 b0\"> &#xE0B1;</span></p>\n<span class=\"fB\">Still Water    </span><span class=\"f9 b1\">&#xE0B0;</span>\n<span class=\"fB\">N Water        </span><span class=\"f9 b1\">&#xE018;</span>\n<span class=\"fB\">S Water        </span><span class=\"f9 b1\">&#xE019;</span>\n<span class=\"fB\">E Water        </span><span class=\"f9 b1\">&#xE01A;</span>\n<span class=\"fB\">W Water        </span><span class=\"f9 b1\">&#xE01B;</span>\n\nWater is a type of floor that can be moved onto by the player,\nsome enemies, and certain other objects. Sometimes, water has a\ncurrent, moving you constantly in a certain direction.\n\n<span class=\"fB\">Ice            </span><span class=\"fB b3\">\\</span>\n\nIce is another type of floor. However, the player will slide on\nit, constantly moving in the last direction the player moved\nuntil running into a non-pushable obstacle. Attempting to move\nin another direction while still on ice will cause the player to\nslide in that direction instead.\n\n<span class=\"fB\">Lava           </span><span class=\"fC b4\">&#xE0B1;&#xE0B2;&#xE0B0;&#xE0B1;&#xE0B2;</span>\n\nLava is another floor. However, it is (almost always) very\ndeadly. Only a few things can traverse it, puzzle pieces cannot\nbe pushed onto it, and any player unfortunate enough to walk on\nit will typically suffer rapid and massive amounts of damage.\n\n<span class=\"fB\">Fire           </span><span class=\"fC b0\">&#xE0B1;&#xE0B0;</span><span class=\"fE b0\">&#xE0B2;&#xE0B1;</span><span class=\"fC b0\">&#xE0B2;</span>\n\nFire is yet another type of floor. It often will spread across\nthe board, turning things such as trees, fakes, brown objects,\nand sometimes even empty space into more fire. It can damage the\nplayer at random intervals when merely standing near it, and\ncause constant damage while standing ON it, but by default fire\ndoes far less damage than lava. Most fires will eventually burn\nout, and the objects it burns are determined by the board\nsettings.\n\n<span class=\"fB\">Lit Bomb       </span><span class=\"f0\">&#xE0AB;</span>\n<span class=\"fB\">Mine           </span><span class=\"f4\">&#xE08F;</span>\n\nA lit bomb is an explosion on a fuse. You DON'T want to be\nnearby when the fuse runs out... mines, on the other hand, only\nblow up when touched, shot, or hit with an explosion.\n\n                 <span class=\"fF bE\">&#xE0B1;</span>\n                <span class=\"fF bE\">&#xE0B1;</span><span class=\"fC bE\">&#xE0B1;</span><span class=\"fF bE\">&#xE0B1;</span>\n<span class=\"fB\">Explosion      </span><span class=\"fF bE\">&#xE0B1;</span><span class=\"fC bE\">&#xE0B1;</span><span class=\"fC b4\">&#xE0B1;</span><span class=\"fC bE\">&#xE0B1;</span><span class=\"fF bE\">&#xE0B1;</span>\n                <span class=\"fF bE\">&#xE0B1;</span><span class=\"fC bE\">&#xE0B1;</span><span class=\"fF bE\">&#xE0B1;</span>\n                 <span class=\"fF bE\">&#xE0B1;</span>\n\nAn explosion starts at a certain point, quickly spreading\noutward, wreaking havoc in its wake and destroying most\ncreatures. Some explosions are smaller than others. Explosions\nwill also cause explosives such as bombs to explode, leading to\nsome very cool chain reactions.\n\n<span class=\"fB\">Goop           </span><span class=\"f8 b1\">&#xE0B0;&#xE0B0;&#xE0B0;</span>\n\nGoop is pretty much non-traversable terrain to anything\nlandborne. Sharks may occasionally inhabit it, and select Robots\nmay be able to walk in it. Bullets, lazers, and other\nprojectiles will traverse it freely. Goop may also be treated\nas water or other terrains in certain worlds.\n\n<p class=\"hC\"><span class=\"f8 b0\">&#xE0B1; </span><span class=\"fF b0\">Miscellaneous</span><span class=\"f8 b0\"> &#xE0B1;</span></p>\n<span class=\"fB\">Door           </span><span class=\"f2\">&#xE0B1;&#xE0B2;&#xE0DB;</span><span class=\"fA\">&#xE0C4;</span><span class=\"f2\">&#xE0DB;&#xE0B2;&#xE0B1;</span>\n<span class=\"fB\">Gate           </span><span class=\"f7\">&#xE0B1;&#xE0B2;&#xE0DB;&#xE016;&#xE0DB;&#xE0B2;&#xE0B1;</span>\n\nA door is what you would expect - When you touch it, it will\nopen itself, pushing obstacles in its path out of the way, and\nthen close after a brief pause. Doors require two free spaces\nin the direction it moves to open fully. Certain doors are\nlocked and require you to use a matching key to open them. They\nthen remain unlocked. Gates act similarly, but do not move; they\ncan be moved over directly when open.\n\n<span class=\"fB\">Ricochet Panel </span><span class=\"f0\">/</span>\n<span class=\"fB\">Ricochet       </span><span class=\"fA\">*</span>\n\nWhen a bullet hits a ricochet panel, it is reflected to travel\nin a new direction, depending upon the way the panel is facing.\nWhen a bullet hits a ricochet, it is reflected to travel in the\nopposite direction. Any bullet that hits a ricochet panel or a\nricochet will have its type set to neutral when reflected.\n\n<span class=\"fB\">Spike          </span><span class=\"f7\">&#xE010;</span>\n<span class=\"fB\">Custom Hurt    </span><span class=\"f7\">?</span>\n\nSpikes and other painful devices will simply hurt you if you\ntouch them. These are often used in conjunction with ice or\nwith sidescroller engines.\n\n<span class=\"fB\">Text           </span><span class=\"f7\">?</span>\n\nAnother type of wall. Cannot be walked on, but can provide\nhints or other enlightening messages.\n\n<span class=\"fB\">Moving Wall N  </span><span class=\"f7\">?</span>\n<span class=\"fB\">Moving Wall S  </span><span class=\"f7\">?</span>\n<span class=\"fB\">Moving Wall E  </span><span class=\"f7\">?</span>\n<span class=\"fB\">Moving Wall W  </span><span class=\"f7\">?</span>\n\nMoving walls act like Runners - they just move back and forth,\nto and fro... They can't hurt you by touch, but can get in the\nway very easily and can't be destroyed like Runners can.\n\n<p class=\"hC\"><span class=\"f8 b0\">&#xE0B1; </span><span class=\"fF b0\">Objects</span><span class=\"f8 b0\"> &#xE0B1;</span></p>\n<span class=\"fB\">Player         </span><span class=\"fB b1\">&#xE002;</span>\n\nThis is you. Really. Well, sometimes. The player is often\nlocked, with a Robot or sprite representing the player\ncharacter, but exactly one player object is always present.\n\n<span class=\"fB\">Scroll         </span><span class=\"fF\">&#xE0E8;</span>\n<span class=\"fB\">Sign           </span><span class=\"f6\">&#xE0E2;</span>\n\nTouch this to display a given message. The message will be in a\nmessage box. A scroll will disappear after you finish reading\nit; signs will not.\n\nUnlike in ZZT, scrolls and signs are text-only: they cannot\nexecute any commands. Also unlike ZZT, messages only one line\nlong will still be shown in a message box.\n\n<span class=\"fB\">Missile        </span><span class=\"f0\">&#xE010;</span>\n\nMissiles fly around the room, turning at obstructions. They\nwill explode if they hit the player or when they cannot turn.\n\n<span class=\"fB\">Bullet         </span><span class=\"fF\">&#xE093;</span>\n\nBullets will fly in a straight line until they hit something\n(and disappear) or do damage to someone or something. Ricochets\ncan change the direction a bullet is traveling in, as well as\nchange its type to neutral. There are three types of bullets:\nPlayer (the player shoots these by default), Neutral (Robots\nshoot these in a default shoot command), and Enemy (built-in\nenemies shoot these by default). Player bullets cannot harm the\nplayer; Enemy bullets cannot harm default enemies; Neutral\nbullets can harm both the player and default enemies.\n\n<span class=\"fB\">Seeker         </span><span class=\"fA\">|</span>\n\nA seeker, thrown by a Tiger, Shark, or other enemy, will chase\nthe player all over the screen until they collide and do damage.\nCollision with most projectiles will not destroy it, only pause\nit. They have a limited lifespan, however, and will expire after\na certain amount of time or when caught in an explosion.\nThey are still one of the deadliest weapons you will face;\nmore than only a few at once can easily ruin a game, especially\nin cramped spaces.\n\n<span class=\"fB\">Shooting Fire  </span><span class=\"fC\">&#xE00F; </span><span class=\"fE\">*</span><span class=\"fC\">&#xE00F;</span>\n\nShooting fire will continue in a straight line until it hits\nthe player (resulting in direct damage) or another object\n(resulting in a small fire). The fire may quickly spread out of\ncontrol in some areas, or just burn out in others. Bullets can\nsometimes destroy shooting fire, but other times the shooting\nfire simply absorbs the bullet.\n\n<span class=\"fB\">Sensor         </span><span class=\"f7\">?</span>\n\nSensors are a form of floor that only the player can move onto.\nThey will usually then produce some form of effect, by\ninteracting with a Robot; other times they act as \"save points\".\nOther objects will push them around instead.\n\n<a class=\"hL\" href=\"#SENSORSW.HLP__094\">Sensors - What They Are and How to Use Them</a>\n\n<span class=\"fB\">Robot          </span><span class=\"f7\">?</span>\n<span class=\"fB\">Pushable Robot </span><span class=\"f7\">?</span>\n\nRobots are the workhorses, the artisans, the... nearly\neverythings of MegaZeux. They are highly flexible objects\nwhich can do almost anything. They utilize their own full-scale\nprogramming language, dubbed Robotic. If another object can\ndo something, a Robot can almost always do it better. They can\nshow messages, fight the player, play music or sound, set the\nentire gameplay structure, and do countless other things. See\nthe appropriate help sections for more information.\n\n<a class=\"hL\" href=\"#ROBOTSWH.HLP__1st\">Robots - What They Are and How to Use Them</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"CONFGINI.HLP\">\n<a class=\"hA\" name=\"CONFGINI.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">The Config File</span></p>\nOne of the first things a new MegaZeux user should do is edit\nthe options in the config file; even experienced MZXers may find\nsome of the customization options novel and useful. Go open\nconfig.txt in any text editor. There is ample commentary to let\nthe user know how to change the options. Make sure to remove\nthe # sign from any option you want set!\n\nEditing the options can result in better sound, better / more\ncustomizable graphics, more fitting defaults, a more\npersonalized Robotic editor, joystick support, extended macros,\nautomated backup and much more.\n\nNew versions - even minor version changes - can add new options\nto the config file or rename old ones, so it is helpful to keep\nthis file current.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"IFYOUFIN.HLP\">\n<a class=\"hA\" name=\"IFYOUFIN.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">If You Find a Bug...</span></p>\n..we want to know! We're very interested in any problems or\nbugs you find in MegaZeux. We also welcome any comments,\ncriticism, or suggestions. We especially appreciate older\nMegaZeux worlds. Here's the contact information as of this\nwriting:\n\nCheck the reports on DigitalMZX's Bug Tracker\n(https://www.digitalmzx.com/forums/index.php?app=tracker) to see\nif your problem has already been addressed. Active problems and\nbugs that are only fixed in a development version of MegaZeux\nare listed, as of this writing, in the \"MegaZeux\" category,\nwhile the bugs that are fixed in a stable version are in the\n\"Closed MegaZeux Bugs\" category. Feature Requests are handled\nsimilarly.\n\nTo make things sane for all parties, do this before submitting:\n  1) Make sure you have the most current version of MegaZeux.\n  2) Make sure that the Bug Tracker or development version\n     doesn't already address your issue.\n  3) Try to be specific in explaining what happens. The more\n     precise, the better.\n  4) List what platform(s) you know show this problem.\n  5) Try to pinpoint the problem to a specific Robotic line or\n     MZX function; at the very least, try to narrow it down.\n     Pinpointing which version introduced the bug is very\n     helpful, if possible.\n  6) If it is a crash, try to run a debug build through GDB and\n     post a backtrace of the crash. Instructions for running a\n     debug build through GDB are on the MZXWiki at DigitalMZX,\n     in the New MegaZeux Release FAQ.\n  7) If you're unsure if the bug's been addressed, try to build\n     a version from GitHub's GIT repository and see if the bug\n     is still present in the test build. The MZXWiki has\n     instructions for doing so in the article Compiling\n     MegaZeux.\n  8) Optimally, if the problem is a regression caused by the\n     port, upload a world made in 2.70 that works in 2.70 but\n     does not in the current version to best isolate the\n     problem.\n\nAdditionally, contacting the developers in real-time can help\nilluminate the problem.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"FAQ.HLP\">\n<a class=\"hA\" name=\"FAQ.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Frequently Asked Questions</span></p>\nThe following is a list of questions that have been received\nabout MegaZeux innumerable times in some form or another.\n\nQ: I read that newer versions of MegaZeux break old games. Which\n  version should I get to play games?\n\nA: The most recent version. MegaZeux development places enormous\n  importance on backwards compatibility, and contrary to what\n  some others have stated, there are very few cases where an\n  older version is needed to play a specific game.\n\nQ: I hate that MegaZeux is always windowed! Could you change\n  this?\n\nA: There's already a way to switch between fullscreen and\n  windowed modes - press Ctrl+Alt+ENTER. Also, the config file\n  has options that start MZX in fullscreen mode (either true\n  fullscreen or as a borderless window at desktop resolution) at\n  launch.\n\nQ: How can I get option (foobar) in the config file to work?\n\nA: Make sure the pound / hash sign in front of the command is\n  deleted. Otherwise, the option is treated as a comment and\n  ignored.\n\nQ: Are there any MZX-specific tools that can make developing and\n  packaging an MZX game easier?\n\nA: Yes, plenty! MegaZeux comes with two helpful tools:\n  \"Checkres\" will detect what files are referenced by an MZX\n  world and which are present, and \"ccv\" can take an image file\n  and output a conversion of it in .chr/.mzm form for MZX use.\n  There are also several graphical conversion programs such as\n  CharCon. MZX has natural time-savers, of course. Automated\n  backups, macros, Robotic code import/export, expressions,\n  repeated block copying, sprites and other features can help\n  speed up development time.\n\nQ: This MZX game has some neat things in it! Can I use some of\n  these things in my own game?\n\nA: It's usually okay to do this, as long as you give definite\n  credit to the original creator(s). Unless it was made\n  specifically for the game, music and sound from other MZX\n  games generally doesn't need direct attribution, but re-used\n  artwork and code definitely should get attribution. If you're\n  unsure whether the original creator(s) would like you using\n  their work in your own game, ask! What we don't want to see,\n  though, is passing the work of others - especially graphics,\n  writing, and code - as your own. DON'T PLAGIARIZE. JUST\n  BECAUSE THEY DID NOT EXPLICITLY FORBID YOU FROM USING THEIR\n  WORK DOES NOT MEAN YOU CAN PASS IT OFF AS YOUR OWN.\n\nQ: Can I distribute a world I made with MegaZeux? Is it legal?\n  Must I/can I include MegaZeux with it? Can they be shareware\n  worlds?\n\nA: Of course you can distribute your worlds! That is what\n  MegaZeux is for! However, while there are those who would help\n  point out your game's flaws in a forgiving manner, not\n  everyone is capable of constructive criticism. Playtest your\n  game before uploading it, with at least one other person\n  evaluating the game for bugs, play balance, and\n  text/plot/grammatical errors (when relevant). Get creative and\n  make your game worth playing!\n\n  Please include any WAV, module, OGG, CHR, MZM, PAL and all\n  other files with the games, as well as a cohesive and helpful\n  description file or manual. (Using checkres to guarantee that\n  every relevant file is included can be helpful.)\n\n  While you can simply distribute your game with a copy of\n  MegaZeux included, or even just post the game's files in an\n  archive by itself, consider releasing your game as both a\n  standalone archive and as pure game files in separate\n  downloads. MegaZeux games can also be embedded in a webpage\n  for accessibility and player ease.\n\n  Typically, MegaZeux worlds are freeware, but if you've made an\n  assuredly epic and ground-breaking game, then you can attempt\n  to ask for registration money in a shareware demo version.\n  Just don't get your hopes up. Alternatively, consider\n  uploading your game to a platform that allows optional\n  donations, such as itch.io.\n\nQ: I WANT FUNCTION (FOOBAR) ADDED OR I LEAVE THE COMMUNITY\n  FOREVER :&lt;\n\nA: Before you go bugging the maintainer(s) of MegaZeux to add\n  new functions, consider these things:\n  1) Some things require massive work to implement. These\n     things require changing the world, save or board format,\n     and format changes are explicitly reserved for version\n     number changes.\n  2) Some things have been purposely ignored because the\n     maintainer(s) deems these things not worth including. In\n     this case, it's probably best to pick up the source and\n     figure out how to add this function yourself.\n  3) You may not be the first to ask for any certain feature.\n     Current feature requests are listed in DMZX's Bug Tracker.\n     If you are not the first, then you'll often find out why a\n     feature is not implemented or, better yet, you'll end up\n     finding out that it is going to be implemented after all!\n     Either way, you learn more about the feature and what\n     would need to be done to include it.\n  4) It's technically possible to add new commands, but\n     currently the method of storing Robotic severely limits\n     the amount of new commands. Until this system is retooled,\n     there will be no new commands. Features that are most\n     cleanly implemented through a new command may possibly be\n     implemented in other ways, however.\n\nQ: Is adding network capabilities to MegaZeux planned?\n\nA: Short answer: Possibly. This has been discussed at-length;\n  the biggest problems with adding these to GAMES are thorny\n  implementation issues (how the MZX programmer would insert\n  network capabilities into a MegaZeux world, as well as the\n  structure in general). It's definitely possible, and possible\n  to do soundly, but developers don't have netplay as a high\n  priority. As for other networking capabilities and MZX, this\n  has already been done to some extent (starting with 2.82b,\n  MZX ships with an updater program). Implementing tools and\n  aids for MZX relying on networking is a lot less thorny of an\n  issue, and might be considered.\n\nQ: Will MegaZeux games ever be playable without MegaZeux\n  itself?\n\nA: In a sense, they can be now. MZXRun, the editor-less version\n  of MegaZeux, can now be set to run in \"standalone mode\", a\n  configuration designed for only playing a specific world. This\n  mode changes several MZX mannerisms to better accommodate a\n  standalone version of a game. The title screen can also be\n  completely skipped. Additions to Robotic (such as exiting the\n  game through code or blocking the default escape menu) have\n  also facilitated standalone releases. Read the comments for\n  standalone_mode in config.txt for more information, if\n  interested.\n\n  MegaZeux has also been ported to Emscripten, which means that\n  MZX games can now be embedded into web pages, allowing play\n  without any extra program download or web plugin necessary.\n\nQ: I need more than 256 characters! Could you help with this?\n\nA: Then you'll need to carefully utilize the Robot command\n  LOAD CHAR SET and ration your characters carefully. The\n  256-char limit is set within the world format and will\n  currently be left alone. Also, you might not REALLY\n  need 256 characters at once. If you're wanting more\n  characters because of heavy character use in animation,\n  partial character set loading will probably be the best\n  solution. Otherwise, find out which characters will never be\n  shown when other characters are and replace them with\n  characters you actually will use. (Alphanumeric characters,\n  for instance, may see limited use in certain games, or are\n  used heavily in some sections and sparsely in others.)\n\n  Alternatively, make use of unbound sprites. Using unbound\n  sprites (and ONLY unbound sprites) allows display of 15 full\n  character sets at once instead of just one, on top of their\n  other benefits.\n\n<a class=\"hL\" href=\"#PARTIAL.HLP__par\">Partial Character Sets</a>\n<a class=\"hL\" href=\"#SPRITES.HLP__ubs\">Unbound Sprites</a>\n\nQ: How can I load sounds at different volumes in my game?\n\nA: Unfortunately, MegaZeux currently has no way of doing this\n  via Robotic. The best you can do for now is load versions of\n  your sound that are louder/quieter. Volume settings for music,\n  on the other hand, can be manipulated freely.\n\nQ: Why is my MegaZeux world starting on the title board?\n\nA: The first board has to be set. Press 'G' in the editor, then\n  set the board using the \"First board\" field at the top left.\n  As of version 2.91, MZX will default to creating and naming a\n  starting board when making a new world, greatly lowering the\n  chance of mistakenly starting on the title.\n\nQ: Whenever I place a string on the overlay, the spaces aren't\n  acting like overlay at all! Is this a bug?\n\nA: No, it's not. Char 32 (the natural space) is never part of\n  the overlay. You have to reserve an extra character or use\n  the solid character for spaces, or otherwise convert spaces\n  to useable replacements during run-time.\n  If this issue is arising from using the overlay to display\n  messages, consider using multi-line * messages instead.\n\n<a class=\"hL\" href=\"#COMMANDR.HLP____3\">* \"string\"</a>\n\nQ: How can I input a number over 32767 or under -32768 in\n  Robotic?\n\nA: You must use an expression. Encase the number in\n  parentheses, as shown here:\n  <span class=\"fE\">set \"largesse\" to \"(2000000000)\"</span>\n\nQ: My Robot can't change its/the player's characters! I\n  use CHAR \"A\" but it turns invisible! What am I doing wrong?\n\nA: Use CHAR 'A' instead. You MUST use single quotes\n  (apostrophes) or it will use the value of the COUNTER\n  A, which is probably 0.\n\nQ: Can I use the music from Caverns in my games? How about\n  the music from the registered games? How about the SAM\n  files?\n\nA: It'd be preferred if you did not use Caverns music in any\n  games you distribute, because Caverns music was ancient aeons\n  ago. The music from the registered games was illegal to\n  distribute; it's now under the GPL, but should be avoided for\n  the same reasons Caverns music should. The SAM files are\n  public domain and may be used as you please.\n\nQ: Where can I get module files?\n\nA: If you can't make them, search webpages for good ones.\n  https://www.modarchive.com has a very large tracked music\n  library, as do many old CD-ROM shareware collections. (It is\n  best practice to stick to modules that openly permit free\n  usage.) Another popular option is to convince a friend to do\n  it for you. If your game shows exceptional promise, then an\n  established musician in the community might make music for\n  you. In any case, remember to give credit where it is due\n  (yes, even to yourself).\n  Be careful when inserting OGG files into your game. OGG files\n  often present considerable bloat to a game if used as music.\n  Also, not only may you be chided for putting in a popular song\n  if you do so, but you could be violating copyright law.\n  Chances are that if you can make a coherent argument on \"fair\n  use\" of copyrighted songs, you will be far less likely to be\n  in this position to begin with. <span class=\"fC\">&#xE003;</span>\n\nQ: What is some good software to create module files?\n\nA: It's a matter of personal preference. Many people prefer\n  OpenMPT because it works well with Windows. Others prefer to\n  work with Schism Tracker. Finally, a few are still fond of\n  Fast Tracker II, and therefore use the similar MilkyTracker.\n  DOS-based trackers like IT and FT2 understandably need outside\n  tools such as DOSBox to run as expected.\n\n  If you don't mind pumping up your game's filesize, you could\n  use any typical composition software (like FL Studio) and\n  convert your wave to ogg format.\n\n  A third alternative would be Reality Tracker. This tracker\n  creates music based on the once-common OPL3 chip found in\n  typical shareware-era DOS sound cards. MegaZeux now directly\n  supports files made in this tracker.\n\nQ: How do I make WAV files?\n\nA: There are three easy ways to create WAV files. First, you\n  get recording software and a mic. Set it all up (read the\n  instructions if necessary) and go for it. Second, you can\n  download them or get them from outside sources (such as The\n  Freesound Project). Third, you can take existing files and\n  change them with effects like echo. Converting .wav files\n  intended for sound effects into OGG format is a worthwhile\n  idea; the drop in file size compared to the drop in quality\n  is huge.\n\nQ: Why do I get garbage when I try reading a file?\n\nA: You likely have it open for writing. To close a file, use\n  the command SET \"\" to \"FWRITE_OPEN\".\n\nQ: Why does MegaZeux slow down significantly when I set the\n  \"commands\" counter to a high value?\n\nA: You're likely using an idle loop without a CYCLE 1 or WAIT 1\n  command inserted. Find the offending loop and add one; if\n  you're wary about possible delay effects, rest assured that\n  adding either of these will not unnecessarily add delays. It\n  will simply end a cycle. By default, MZX will warn you if you\n  exceed 2000000 commands in a cycle (but only in test\n  sessions). For further information, look up the Cycles and\n  Board Scans section.\n\n<a class=\"hL\" href=\"#PROCESS.HLP__prc\">Cycles and Board Scans - How MZX Processes Robots</a>\n\nQ: I don't like that anyone can open up my game and see how it\n  works, or that anyone can use the editor functions to cheat\n  at my game. Is there an easy way to prevent people from\n  opening my game in the editor?\n\nA: Nope. MegaZeux used to be able to protect worlds from being\n  edited or even played without a password, but this feature\n  has long since been removed.\n  Releasing games in standalone form can possibly help. While\n  anyone who has even cursory knowledge of MegaZeux can still\n  fully look through a standalone game, people who have no\n  knowledge of MegaZeux beforehand will lack the immediate means\n  to heavily manipulate or look through your game.\n\nQ: I've seen that \"certain renderers\" are unable to display\n  unbound sprites. What are renderers, what does this mean, and\n  what should I be worried about?\n\nA: MegaZeux has several different video settings to accommodate\n  a wide range of hardware. The engines used to provide video\n  are called renderers, and can be set in the config file or\n  changed on the fly in the F2 settings menu. Renderers range\n  from basic support (software, does not utilize video hardware\n  functions) to features-oriented (glsl, featuring customizable\n  filters).\n\n  Certain platforms and renderers cannot show extended graphics\n  (in other words, unbound sprites and SMZX mode 3 custom\n  indices). Right now, this is only a concern for very weak\n  platforms that cannot handle much to begin with, such as the\n  DS. Pretty much anyone running MegaZeux on an even remotely\n  modern computer should be able to view extended graphics.\n\n  (Besides the Nintendo DS, the other platforms and renderers\n  currently unable are the GP2X and any use of the weak and\n  obseleted \"overlay2\" renderer.)\n\nQ: I heard only certain graphics cards can properly show Super\n  MZX mode games. Is this true?\n\nA: Not anymore. Any computer that can display normal games in\n  MegaZeux can now display Super MZX mode games. Even the modern\n  DOS version supports them (via an SVGA mode).\n\nQ: How can I get \"mod *\" as the default board mod?\n\nA: To set \"mod *\", use either Shift+8 or the asterisk (*) on the\n  numpad.\n\nQ: What's with these \"~roboclp.tmp\" files packed with so many\n  games?\n\nA: Those held clipboard information in DOS versions of MegaZeux.\n  They are useless and can be freely deleted.\n\nQ: Why doesn't [ work for taking screenshots?\n\nA: It has been changed to F12 and now works in any area in MZX,\n  including any part of the editor. Instead of PCX, it now\n  currently outputs PNG files by default, but will output MS\n  BMP format files on platforms unable to support PNG.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"MEGAZEUX.HLP\">\n<a class=\"hA\" name=\"MEGAZEUX.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">MegaZeux Limitations</span></p>\nMegaZeux has to impose a number of limitations to ensure\nworldfile compatibility with MZX 2.x worldfiles.\n\n<p class=\"hC\">Memory Limitations</p>\nNo single Robot can exceed 2 megabytes in size.\nNo single sign or Scroll can exceed 64 kilobytes in size.\n\n<p class=\"hC\">Quantity Limitations</p>\nItem:               Largest number allowed:\n\nRobots              255 per board plus Global Robot\nScrolls/Signs       255 per board\nSensors             255 per board\nSprites             256 per world\nBoards              250 per world\nLocal Counters      32 per Robot plus specialized local counters\n                    (e.g. loopcount, lava_walk, bullettype)\n\n<p class=\"hC\">Length Limitations</p>\nRobot Name Length   14 characters\nBoard Name Length   24 characters\nString Length       4,194,304 characters (4 megabytes)\nEditor Line Length  241 characters (INCLUDING \"extra\" words)\n* Message Length    512 characters\nInput String Length 512 characters\nMacro Length        64 characters (for single-line macros)\nMod Filename Length 512 characters (including subdirectories,\n                    if applicable)\n\n<p class=\"hC\">Theoretical Limitations</p>\nBoard size maximum is roughly 16.7 million characters\n((2^24) - 1).\nVlayer size maximum is roughly 16.7 million characters\n((2^24) - 1).\nBoard width or height maximum is 32767.\nThe maximum number of counters and strings depends on the\nplatform (e.g. between 32 or 64-bit) but is sufficiently large.\nYou should be fine utilizing hundreds of thousands, or even\nmillions.\n\nThese limitations are mostly theoretical because of the utter\nimprobability of meeting these limits and the amount of RAM (up\nto hundreds of gigabytes if fully utilized) they consume.\n\n<p class=\"hC\">Other Limitations</p>\nNumbers less than -32768 or more than 32767 cannot be directly\nused in Robotic. This problem is a worldfile issue and can be\ncircumvented by using a constant expression (parentheses).\n\nNo board can have a width of a multiple of 256 chars because\nthis corrupts the board if it lacks an overlay. This problem is\na worldfile issue and widths set to a multiple of 256 will be\nincreased by one more horizontal character regardless of the\nstatus of the board's overlay.\n\n<a class=\"hL\" href=\"#EXPRESS.HLP__exp\">Expressions</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"THEWORLD.HLP\">\n<a class=\"hA\" name=\"THEWORLD.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">The World Editor</span></p>\nReady to start creating your own worlds? Then, let's get\nstarted! This section is a short editor tutorial. It will\nteach you the basics of creating your own worlds.\n\nTo get into the editor, press N or F8 from the title screen. You\nwill be asked to name a starting board: one can type an\nappropriate name and create a second starting board, or press\nEscape to cancel and remain on your new world's title board.\nWhen this prompt is dealt with, you will be presented with a\nblank board with a small status bar at the bottom. You can use\nAlt+H to pop up a listing of key shortcuts at the bottom. PageUp\nand PageDown change its currently shown key shortcuts. The mouse\nalso works to change pages (but not to select actions). Feel\nfree to play around with these various options. Press Alt+R to\nrestart and clear everything (you will be asked for\nconfirmation, to make sure you really want to go through with\nit).\n\n(On Mac platforms, any key shortcut containing Alt can work with\n&#xE0BD;&#xE0BE; replacing Alt. So, for above, &#xE0BD;&#xE0BE;+H would pop up the help\nlisting, and &#xE0BD;&#xE0BE;+R would restart the world.)\n\nFor your first world, you should start simple. The very first\nboard in the board listings is the title board, so name that\nboard after your game. (If you opted to create a starting board,\nyou have to switch to the first board with the B key.) Press I\nfor board info. This screen will display a bunch of options;\nthere are loads of important options, but the most relevant\noption for now is the \"Board name\" option. Type in the name of\nyour game and then click on \"OK\" or press Tab until OK is\nhighlighted and press Enter. Now that the title board is\nnamed, its design begins... or is put aside for later (or even\nthe end).\n\nIf you made a starting board upon creating your world, simply\npress B to move to it. If you skipped making a starting board,\nyou may need to create the first playable board, or location, of\nyour game. Press A to add a board, then type in a short\ndescription of the board, such as \"Starting Board\". Press Enter\nto go to this new board. This board also needs to be set as the\nstarting board. Press G to load the Global Settings screen. Once\nthere, select the First Board option and set it to your starting\nboard.\n\nNow you are free to doodle around. Use F3 through F9 to bring up\nmenus of items, terrains, and creatures. Selecting one with the\narrows and Enter will put that thing into the editor buffer,\nmaking it your current object, and place it on the board. This\nthing and its traits are shown in the upper-right section of the\neditor menu. With the thing in the buffer, you can now place it\nusing arrow keys and space. The thing's color in the buffer can\nbe changed by pressing C and selecting a color.\n\nFor example, press F3 for terrains, and select Line. (This is\na form of wall.) Now move around, placing walls. To ease\nthis, you can press Tab to toggle draw mode. When draw mode is\nactive, every move of the cursor will place a copy of the\ncurrent thing in the buffer onto the board.\n\nTry to create a pleasing-looking screen, regardless of its\nplanned function. Some items will require that you set settings\nto determine their behavior. To place the player's starting\nposition, move the cursor to the destination, press F10, and\nselect Player. Alternatively, press Enter while highlighting\nthe Player and press Space on the desired destination.\n\nWhen you are done, press G to go to the Global Info screen.\nTAB to the Next button and press Enter. You will now be\nhighlighting the option \"First Board\". Press Enter, and select\nthe starting board (this is where gameplay starts... NOT the\ntitle board in the vast majority of cases) from the list and\npress Enter. Then TAB to OK and press Enter.\n\nYou could now press Alt+N to select a module (music) file for\nthe board, if you wish. Then press S to save the world, and\ntype in a filename. (The extension of .MZX will automatically\nbe added.) Press Enter to save the world. Press ESC to exit the\neditor, and now you can play your game! You can use L to reload\nyour world in the editor to make changes, if necessary. See\nGeneral Editing Tips for more advanced editing info.\n\nNew in the 2.8+ line of MegaZeux is the protection of colors\nand characters the editor uses. The editor uses sets outside of\nthe currently used sets to keep the editor GUI consistent. The\ncharacter set for the editor is mzx_edit.chr; only edit if you\nwant to change the editor's appearance.\n\n<a class=\"hL\" href=\"#GENERALE.HLP__1st\">General Editing Tips</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"GENERALE.HLP\">\n<a class=\"hA\" name=\"GENERALE.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">General Editing Tips</span></p>\nThe following is a list of important editing tips. They assume\nyou are familiar with MegaZeux's dialog box system, and that\nyou can navigate the editor's menus.\n\n<p class=\"hC\">Linking Boards</p>\nSimple, one-board games can get boring REALLY fast. There are\nfive ways to move between boards.\n\nA simple method of connecting boards is with the Board Exits\ndialog, accessed by pressing X. Here, you can select boards that\nyou will reach if the player moves off of the screen in a given\ndirection. The destination board shouldn't have anything in the\nway, and will not automatically lead back - you must set the\nexit on that board too. This menu will not allow linking boards\nto the title board.\nThe player will appear on the other board at the appropriate\nedge at the same position (if board sizes allow). For example,\nif the player triggers the north exit of a board while at its\nupper-left corner, it will appear in the lower-left corner of\nthe destination board.\n\nIf the player triggers a board exit and anything on the\ndestination board is in the way of the player moving to the next\nboard, the player replaces it upon entering the board\n(effectively destroying it).\n\nAnother way is to use a Robot to teleport a player with the\n<span class=\"fA\">teleport player \"boardname\" X Y</span><span class=\"fF\"> command. This assumes that you</span>\nare familiar with using Robots, but is the only way to go to\nanother board without having the player moved past a board edge,\nmoving the player into a teleport, requiring the player to have\nbeen on the destination board earlier, or reloading the current\nworld. Robots can also dynamically set board exits using the\n<span class=\"fA\">BOARD [dir] \"boardname\"</span><span class=\"fF\"> command, including disabling exits</span>\nby inputting NONE instead of the board name.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP___t5\">TELEPORT PLAYER \"string\" # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b7\">BOARD [dir] \"string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b8\">BOARD [dir] NONE</a>\n\nThe third way is to add stairs, caves and whirlpools using the\nTransport (F7) menu. After one is picked, you select a\ndestination board. The destination board should contain a\ntransport of the same type and color leading back. The two\nentrances will now lead to each other.\n\nThe fourth way is by saving and restoring player positions using\nRobotic. The <span class=\"fA\">save player position #</span><span class=\"fF\"> command will remember the</span>\ncurrent board and player position and save this info in the\ngiven slot #, and the <span class=\"fA\">restore player position #</span><span class=\"fF\"> command returns</span>\nthe player to the saved position.\n\nThe last way - the most kludge-like - is to load the current\nworld with a 'swap world' command. This will send the player to\nthe starting board of the current world, but will restore the\nworld to its original state. Set counters and strings will be\npreserved. This method is used for its powerful restorative\nproperties.\n\nTo switch to other boards in the editor, use B. To add boards,\npress A, or press B and select (add board) from the board list.\n\n<p class=\"hC\">Board Sizes</p><a class=\"hA\" name=\"GENERALE.HLP__sizepos\"> </a>\nYou can change the size and placement of a board's viewport with\nAlt+P. You can also choose to center the viewport; this will\nautomatically set the viewport placement to display the center\nof the screen.\n\nYou can also change the actual size of the board. The highest\npossible size is 16.7 million tiles, though a board that size\nwould require unreasonably high RAM requirements (128MB). Note\nthat reducing the size of a board will permanently destroy\nanything outside of the new limits.\n\n<p class=\"hC\">Other Important Editing Keys</p>\nYou can use Ins to \"grab\" the object beneath the cursor, or\nEnter to edit it and then grab it as well. Use P to modify the\nsettings of the object in the buffer. Use Alt+N to select music\nfor the current board, or turn the music off if it is already\nselected. Use Alt+Z to clear the current board entirely (you\nwill be asked for confirmation). You can edit important Board\nOptions with I, and important Global (world) Options with G.\nThis is only a small selection of key shortcuts in the editor;\nthe full reference is linked at the end of this article.\n\n<p class=\"hC\">The Mouse in the Editor</p>The left mouse button acts like space: it places a copy of the\ncurrent item under the mouse cursor. The right mouse button acts\nlike insert: it grabs whatever is under the mouse cursor and\nplaces it into the buffer. Moving the scrollwheel or clicking\nmiddle mouse changes the editor cursor location to under the\nmouse cursor.\n\n<a class=\"hL\" href=\"#EDITINGK.HLP__080\">Editing Keys and Options Reference</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"EDITINGK.HLP\">\n<a class=\"hA\" name=\"EDITINGK.HLP__080\"> </a>\n<p class=\"hC\"><span class=\"f9\">Editing Keys and Options Reference</span></p>\nThe following is an alphabetical listing of keys within the\nWorld editor. This is followed by a detailed description of\nwhat each one does.\n\nOn Mac platforms, any command that uses the Alt key can take &#xE0BD;&#xE0BE;\n(the command key) instead.\n\n<a class=\"hL\" href=\"#EDITINGK.HLP___A\">A                    - Add (board)</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP___B\">B                    - Select Board</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP___C\">C                    - Color</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP___D\">D                    - Delete (board)</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP___F\">F                    - Fill</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP___G\">G                    - Global Info</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP___I\">I                    - Info (board)</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP___L\">L                    - Load</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP___M\">M                    - Move Board</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP___P\">P                    - Parameter</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__076\">S                    - Save</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP___V\">V                    - View</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__083\">X                    - Exits</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__min\">-                    - Goto Previous Board</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__plu\">+                    - Goto Next Board</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__075\">Alt+A                - Select Char Set</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__073\">Alt+B                - Block</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltC\">Alt+C                - Char Edit</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltD\">Alt+D                - Default Colors</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltE\">Alt+E                - Palette</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__097\">Alt+F                - Sound Effects</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltG\">Alt+G                - Edit Global Robot</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltH\">Alt+H                - Hotkey Toggle</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__078\">Alt+I                - Import</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltL\">Alt+L                - Test WAV</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltM\">Alt+M                - Modify</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltN\">Alt+N                - Music</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltO\">Alt+O                - Edit Overlay</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__084\">Alt+P                - Size/Pos</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltR\">Alt+R                - Restart</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__082\">Alt+S                - Status Info</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltS2\">Alt+S                - Show Level</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltT\">Alt+T                - Test</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__077\">Alt+X                - Export</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltY\">Alt+Y                - Debug Window</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltZ\">Alt+Z                - Clear (Board)</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltNu\">Alt+Number           - Load Editor Position</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__CtrG\">Ctrl+G               - Goto Position</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__CtrN\">Ctrl+N               - Test Music</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__CtrY\">Ctrl+Y               - Redo</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__CtrZ\">Ctrl+Z               - Undo</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__CtrNu\">Ctrl+Number          - Save Editor Position</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__Sft8\">Shift+8 OR Numpad *  - Mod Wildcard</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__SftAr\">Shift+Arrow          - Goto Linked Board</a>\n\n<a class=\"hL\" href=\"#EDITINGK.HLP__F1\">F1  - Help</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__F2\">F2  - Text</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__F3\">F3  - Terrain</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__F4\">F4  - Item</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__F5\">F5  - Creature</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__F6\">F6  - Puzzle</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__F7\">F7  - Transport</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__F8\">F8  - Element</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__F9\">F9  - Misc. (thing)</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__F10\">F10 - Objects</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__F11\">F11 - Select Screen Mode</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__F12\">F12 - Take Screenshot</a>\n\n<a class=\"hL\" href=\"#EDITINGK.HLP__AF11\">Alt+F11 - Robotic Debugger</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__ShF1\">Shift+F1 - Show InvisWalls</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__ShF2\">Shift+F2 - Show Robots</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__ShF3\">Shift+F3 - Show Fakes</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__ShF4\">Shift+F4 - Show Spaces</a>\n\n<a class=\"hL\" href=\"#EDITINGK.HLP__Ar\">Arrow     - Move</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltAr\">Alt+Arrow - Move 10</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__BkSp\">BackSpace - Delete (Main Layer Only)</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__Del\">Delete    - Delete</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__End\">End       - L/R Corner</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__En\">Enter     - Modify+Grab [Board Mode]</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__Enter2\">Enter     - Character [Overlay Mode]</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__ESC\">Escape    - Exit/Cancel Mode</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__Home\">Home      - U/L Corner</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__Ins\">Insert    - Grab</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__Sp\">Spacebar  - Place</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__Tab\">Tab       - Draw</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__PgDn\">PageDown  - Next Menu</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__PgUp\">PageUp    - Previous Menu</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP___A\"><span class=\"fE\">A - Add (Board)</span></a>\n\nPress A to add another board to the current world. You will be\nasked for the name of the new board, and then a new board will\nbe created. The settings for the new board will be the defaults.\nAfter adding a board, you will be moved to the new board. There\nis a limit of 250 unique boards per world.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__075\"><span class=\"fE\">Alt+A - Select Char Set</span></a>\n\nPress Alt+A and select one of the four different character\nsets. This will change the current character set to one of the\nfour defaults. ASCII is the default EGA ASCII character set\n(code page 437). MegaZeux default is the default MegaZeux\ncharacter set. Blank is the MegaZeux default, but with most\ngraphical characters blank instead. Text, lines, arrows, blocks,\nand certain other symbols are not affected. SMZX set is the\ndefault character set for SMZX modes.\n\n<a class=\"hA\" name=\"EDITINGK.HLP___B\"><span class=\"fE\">B - Select Board</span></a>\n\nPress B to change the current board by selecting from a list.\nSelecting (add board) will prompt the user to name the new\nboard; once named, the board is created and becomes the current\nboard.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__073\"><span class=\"fE\">Alt+B - Block</span></a>\n\nPress Alt+B to start block mode, then move the cursor to the\nopposite corner of a rectangular block and press Alt+B again.\nYou can then select an action to perform upon the block. In all\ncopy/move operations, the cursor marks the new UPPER LEFT\ncorner.\n\nCopy block will allow you to duplicate the block by moving the\ncursor to a destination and pressing Enter.\nCopy block (repeated) will allow same as Copy block, but will\nallow copying the same block to multiple places.\nMove block will allow you to move the block to a new location.\nClear block will erase the contents of the block.\nFlip block will flip the block upside down.\nMirror block will flip the block left to right.\nPaint block will change the entire block to the current color.\nCopy to overlay will copy the block to the given spot of the\noverlay.\nCopy to vlayer will copy the block to the given spot of the\nvlayer.\nSave as ANSi/TXT will save the block as an ANSi or TXT file.\nSave as MZM will save the block as a board-type MZM file.\n\nDestinations for Move and Copy can overlap the original block\nsafely.\n\nYou can block copy to other boards by going to the desired board\nwhen prompted to pick the destination of the block; just press B\nand pick the desired board.\n\nAlso, one can move the cursor across the width or height of the\nblock by using Ctrl+Dir. This is very helpful for tiling blocks\nusing the repeated copy function.\n\n<a class=\"hA\" name=\"EDITINGK.HLP___C\"><span class=\"fE\">C - Color</span></a>\n\nPress C to select a new current color from a menu. The thing\nunder the cursor is not affected, just the thing in the buffer.\nOne can jump to a color by typing its hex code in the color\nmenu; for example, typing \"0D\" would jump to color 013\n(background color 0, foreground color D).\n\nA quirk to keep in mind: any entity with a background color of 0\nwill display the background color of anything beneath it.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltC\"><span class=\"fE\">Alt+C - Char Edit</span></a>\n\nPress Alt+C to edit the character set. The character editor is\na separate section of the world editor and is discussed in\nanother section.\n\n<a class=\"hL\" href=\"#CHAREDIT.HLP__079\">The Character Editor</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP___D\"><span class=\"fE\">D - Delete (board)</span></a>\n\nPress D to select a board from a list to be deleted. You cannot\ndelete the first board (the title board). A deleted board will\nbe pruned out, with the next actual board taking its place. A\ndeleted board cannot be recovered or undone, so make sure this\nis the desired action.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltD\"><span class=\"fE\">Alt+D - Default Colors</span></a>\n\nPress Alt+D to toggle forcing default colors for placing\nbuilt-ins (defaults to on). Turning this off causes built-ins to\nbe placed with the colors of the object held in the buffer. The\nbuffer object listed in the status bar will have a red dot at\nthe end when default colors are off. Only some types of\nbuilt-ins, such as creatures and items, have default colors.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltE\"><span class=\"fE\">Alt+E - Palette</span></a>\n\nPress Alt+E to edit the palette (colors). The palette editor\nis a separate section of the world editor and is discussed in\nanother section.\n\n<a class=\"hL\" href=\"#PALEEDIT.HLP__093\">The Palette Editor</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP___F\"><span class=\"fE\">F - Fill</span></a>\n\nPress F to fill in an enclosed area with the thing in the\nbuffer. The area must be completely surrounded by things other\nthan the thing beneath the cursor. For example, you can fill\nover a solid square of Fakes with something else. The current\nfill command may not work accurately for very large and complex\nareas - in this case, you must move to the unfilled areas and\npress F to continue filling. This happens very rarely,\nhowever. Also, beware of trying to fill an area with an item\nwith limited placements, such as a Robot.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__097\"><span class=\"fE\">Alt+F - Sound Effects</span></a>\n\nPress Alt+F to enter the sound effects editor. This editor\naffects all triggered sounds resulting from built-in actions,\nsuch as getting hit and shooting. First you must decide whether\nto use the default set of sound effects, or whether to edit\nyour own. You can't edit the default set. If you edit your own,\nyou will be in a series of editing screens. Use the Next and\nPrevious buttons to move between screens. Sound effects, and the\nformat used to represent them, are described in detail in\nanother section. The format is the same as used for the Robotic\ncommand PLAY. (Like with PLAY, digitized sounds are allowed\nhere.)\n\nPress F3 in the sound effects editor to rename the current sound\nslot. Slot names are limited to 9 characters in length.\n\nNOTE: Although this editor currently allows editing only the\nfirst 100 sound effects, a world can have as many as 256. Worlds\ncan also have effects up to 255 characters long. Use the CHANGE\nSFX # \"string\" command to edit higher-numbered/longer custom\nsound effects.\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">MegaZeux's Sound System</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltG\"><span class=\"fE\">Alt+G - Edit Global Robot</span></a>\n\nAlt+G will start editing the Global Robot, starting at its name\nfield.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__CtrG\"><span class=\"fE\">Ctrl+G - Goto Position</span></a>\n\nCtrl+G will pop up a window, displaying target x,y coordinates.\nSet the coordinates by either typing in or selecting the desired\nX and Y values, and select OK to go to those coordinates on the\ncurrent board. Choosing Cancel or pressing Escape cancels.\n\n<a class=\"hA\" name=\"EDITINGK.HLP___G\"><span class=\"fE\">G - Global Info</span></a>\n\nPress G to enter the global info dialog boxes. The global info\noptions are covered in another section.\n\n<a class=\"hL\" href=\"#GLOBALIN.HLP__086\">Global Info Options</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltH\"><span class=\"fE\">Alt+H - Hotkey Toggle</span></a>\n\nPress Alt+H to toggle display of the hotkeys and horizontal\nborder. The default state of the hotkeys can be changed in the\nconfig file.\n\n<a class=\"hL\" href=\"#CONFGINI.HLP__1st\">The Config File</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP___I\"><span class=\"fE\">I - Info (board)</span></a>\n\nPress I to enter the board info dialog box. The board info\noptions are covered in another section.\n\n<a class=\"hL\" href=\"#BOARDINF.HLP__085\">Board Info Options</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP__078\"><span class=\"fE\">Alt+I - Import</span></a>\n\nAlt+I allows you to import a number of different file types\ninto the current board or world. You can import a board file\n(.MZB) unique to MegaZeux. You can import a character set file\n(.CHR). You can import another world (.MZX), which is appended\nto the end of the list of boards. The global of the imported\nworld will be ignored. (Unlike old versions of MegaZeux, all\nexits in an imported world will work.) You can import a palette\n(.PAL) file (including a palette indices (.PALIDX) file in SMZX\nmode 3) or a sound effects (.SFX) file, all unique to MegaZeux.\nFinally, you can import an MZM or ANSI/TXT file and place it at\na given position.\n\nImporting boards or worlds will clear the board and overlay undo\nhistories.\n\n<a class=\"hA\" name=\"EDITINGK.HLP___L\"><span class=\"fE\">L - Load</span></a>\n\nL brings up a list of worlds in the current directory and allows\nyou to select one to load. You will be warned if the current\nworld as-is has not been saved yet.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltL\"><span class=\"fE\">Alt+L - Test WAV</span></a>\n\nAlt+L brings up a list of WAV/SAM/OGG files in the current\ndirectory. Selecting one will play it once at its natural\nfrequency. This has no effect on the actual board or world.\n\n<a class=\"hA\" name=\"EDITINGK.HLP___M\"><span class=\"fE\">M - Move Board</span></a>\n\nM moves the current board to the desired place on the board\nlist. The title board cannot be moved, and other boards cannot\nbe moved to the title board position.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltM\"><span class=\"fE\">Alt+M - Modify</span></a>\n\nAlt+M allows you to modify the settings of the thing under the\ncursor without changing the item in the buffer.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltN\"><span class=\"fE\">Alt+N - Music</span></a>\n\nAlt+N brings up a list of module files in the current directory.\nSelecting one will select it as the default music for the\ncurrent board. If music is already playing, Alt+N will turn it\noff.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__CtrN\"><span class=\"fE\">Ctrl+N - Test Music</span></a>\n\nCtrl+N does the same as Alt+N, except it only plays the\nselected module. It does not set the board module to the\nselected module. It also remembers the last-used directory to\nmaintain a current listening directory. A tested module will\ncontinue to play even if the current board is set to play music,\nand even if the current world file is changed.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltO\"><span class=\"fE\">Alt+O - Edit Overlay</span></a>\n\nAlt+O enters overlay editing mode. The overlay is on normal\noverlay mode by default. The overlay editing mode is similar to\nthe normal editing mode, except that only certain keys are\nactive, and the Enter and Alt+S keys have new purposes. The\noverlay is explained in further detail in another section.\n\n<a class=\"hL\" href=\"#TOVERLAY.HLP__081\">Editing and Using the Overlay</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP___P\"><span class=\"fE\">P - Parameter</span></a>\n\nP allows you to change the settings of the current object in\nthe buffer. The object under the cursor is not affected.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__084\"><span class=\"fE\">Alt+P - Size/Pos</span></a>\n\nAlt+P allows you to change the size of the current board, the\nviewport size, and location. See the linked section for details.\nThis action will clear the board and overlay undo histories.\n\n<a class=\"hL\" href=\"#GENERALE.HLP__sizepos\">Board Sizes</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltR\"><span class=\"fE\">Alt+R - Restart</span></a>\n\nAlt+R will clear the entire world. You will be asked for\nconfirmation. All undo histories will also be cleared.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__076\"><span class=\"fE\">S - Save</span></a>\n\nS will prompt you for a filename, then save the current world\nas a MZX file. If there are unsaved changes, the title bar will\nbegin with an asterisk to show this.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__082\"><span class=\"fE\">Alt+S - Status Info</span></a>\n\nAlt+S will allow you to type in six different counters that\ncan be shown on the default status screen within the game. This\nallows you to easily display your own items that the player can\ncollect, and see how many are currently held. These counters can\nbe up to 14 characters in length. Counters are discussed in\nfurther detail with Robots.\n\nAny status counter with a value of 0 will not be shown, and\nunseen counters will not change the position of the others.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltS2\"><span class=\"fE\">Alt+S - Show Level</span></a>\n\nAlt+S while editing the overlay will toggle whether the level\nbeneath the overlay is shown in overlay editing mode (defaults\nto YES).\n\n<a class=\"hL\" href=\"#TOVERLAY.HLP__081\">Editing and Using the Overlay</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltT\"><span class=\"fE\">Alt+T - Test</span></a>\n\nAlt+T will allow you to test the current world, starting on the\ncurrent board. Games CAN be loaded and saved in this mode,\nmaking it especially helpful for debugging. Test mode also\nenables several debugging tools and cheats that can be used\nduring a test session. Quitting will return to the editor. Note\nthat entering a test session will clear all editor undo\nhistories.\n\n<a class=\"hL\" href=\"#DBGMODE.HLP__dbg\">Debug Modes</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP___V\"><span class=\"fE\">V - View</span></a>\n\nV will allow you to see the current board as it would appear in\nthe game. Use the arrows to Scroll the view, and ESC to return\nto the editor.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__083\"><span class=\"fE\">X - Exits</span></a>\n\nX will bring up a menu where you can select exits for each\nboard direction. For example, selecting a destination board of\n\"City\" for north will cause the player to go to that board when\npressing against the north edge of the current board. Boards\nare not automatically back-linked - to make \"City\" lead back\nhere, you must go to that board and make a south exit back.\nMake sure the linked boards don't obstruct each other's exits,\nand that their linking boundaries are of the same size.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__077\"><span class=\"fE\">Alt+X - Export</span></a>\n\nAlt+X will allow you to export a number of different file types.\nYou can export a board file (MZB) unique to MegaZeux, good for\ngiving single boards to others. (The character set, palette,\nvlayer and global info aren't included!) You can export the\ncharacter set (CHR) for later game use or to edit in Fontutil or\nanother external app, or export a partial portion of the current\nset instead. You can export palettes (PAL) [regular mode or\nSMZX] and sound effect settings (SFX) for transportation. Both\nare file formats unique to MegaZeux. You can export the current\nworld, but using the last version's world format, with the\n\"Downver. world (MZX)\" option. Finally, you can export a\nPNG-format image of the current board/overlay (when in\nboard/overlay edit modes) or the vlayer (when in vlayer edit\nmode); this option will launch a progress bar, and can be\naborted by pressing the Escape key while exporting.\n\nFiles like MZMs and SAVs can be exported in-game by Robotic\ncode. See their respective areas for details. Other file types\nsuch as character sets can be exported generically through file\nwriting facilities and charset counters. SMZX indices (PALIDX)\ncan be exported using the Export Palette (PAL) option in SMZX\nmode 3; its export dialogue will appear after the palette export\ndialogue.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltY\"><span class=\"fE\">Alt+Y - Debug Window</span></a>\n\nAlt+Y will toggle a red box in the lower corner of the screen.\nThis box shows (on labeled lines) the current position of the\ncursor, the current Robot memory situation, and the presently\nplaying module, and lists the MZX version of the current world\nat the top. This is the same as pressing F6 in-game, but removes\nthe key_code and key_pressed info.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltZ\"><span class=\"fE\">Alt+Z - Clear (board)</span></a>\n\nAlt+Z will clear the current board entirely. You will be asked\nfor confirmation. Clearing the board will clear the board undo\nhistory as well.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__CtrZ\"><span class=\"fE\">Ctrl+Z - Undo</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__CtrY\"><span class=\"fE\">Ctrl+Y - Redo</span></a>\n\nCtrl+Z will undo an action; Ctrl+Y will redo any previously\nundone action. Action depth is determined by config file\nsettings, with 100 as default.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__CtrNu\"><span class=\"fE\">Ctrl+Number - Save Editor Position</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__AltNu\"><span class=\"fE\">Alt+Number  - Load Editor Position</span></a>\n\nThese save and load up to 10 cursor positions in the editor (0\nthrough 9). These positions save both current board coordinates\nand active board, so one can save position on one board and load\nposition from another board. These positions are saved for that\nworld in its .editor.cnf file.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__Sft8\"><span class=\"fE\">Shift+8 OR Numpad * - Mod Wildcard</span></a>\n\nThis sets the board's current mod to play whatever played in\nthe last-visited board. (i.e. The mod playing on that board\ndiffers if entering that board from a board using different\nmusic; it does not stick with the first such mod and play that\nfrom then on.)\n\n<a class=\"hA\" name=\"EDITINGK.HLP__SftAr\"><span class=\"fE\">Shift+Arrow - Goto Linked Board</span></a>\n\nThis goes to the board linked in the given direction, if it\nexists. For example, pressing shift+up would go to the board\nlinked to the North exit, provided there is a board linked to\nthat exit.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__min\"><span class=\"fE\">- - Goto Previous Board</span></a>\n\nThis moves the editor to the previous board in the board list,\nif it exists.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__plu\"><span class=\"fE\">+ - Goto Next Board</span></a>\n\nThis moves the editor to the next board in the board list, if\nit exists.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__F1\"><span class=\"fE\">F1 - Help</span></a>\n\nF1 will bring up context-relevant help. You can press F1 at\nalmost any time.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__F2\"><span class=\"fE\">F2 - Text</span></a>\n\nF2 will toggle text mode on and off. When text mode is on,\nEnter will go to the next line, and Backspace will delete the\ncharacter under the cursor and move the cursor one position to\nthe left. All printable characters will type in as text,\nincluding pressing the Spacebar.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__F3\"><span class=\"fE\">F3  - Terrain</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__F4\"><span class=\"fE\">F4  - Item</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__F5\"><span class=\"fE\">F5  - Creature</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__F6\"><span class=\"fE\">F6  - Puzzle</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__F7\"><span class=\"fE\">F7  - Transport</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__F8\"><span class=\"fE\">F8  - Element</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__F9\"><span class=\"fE\">F9  - Misc. (thing)</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__F10\"><span class=\"fE\">F10 - Objects</span></a>\n\nThe above eight keys will bring up a menu of things to select\nfrom. Selecting one will ask you to enter settings (if\nappropriate) or choose a character for that thing. Then a copy\nof that thing will be placed at the cursor's location, and will\nalso become the current object in the buffer. Scrolls, Signs,\nSensors, Robots, and Pushable Robots (all in the Objects menu)\nwill be discussed in another section. The current thing, color,\nand settings parameter (p##) is shown in the upper-right corner\nof the editor menu. Knowing the settings parameter isn't\nimportant except for certain Robotic programming situations.\n\n<a class=\"hL\" href=\"#SCROLLSS.HLP__1st\">Signs and Scrolls in the Editor</a>\n<a class=\"hL\" href=\"#SENSORSW.HLP__094\">Sensors - What They Are and How to Use Them</a>\n<a class=\"hL\" href=\"#ROBOTSWH.HLP__1st\">Robots - What They Are and How to Use Them</a>\n<a class=\"hL\" href=\"#ROBOTICT.HLP__1st\">Robotic Tutorial</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP__F11\"><span class=\"fE\">F11 - Select Screen Mode</span></a>\n\nPressing F11 will open a menu, allowing the user to select an\nMZX display mode (normal mode and Super MZX modes 1-3). This\nsetting will persist in editor tests until overridden in-game;\nif editor tests are left while in a different SMZX mode, it will\nbe reverted to the setting given here upon returning to the\neditor.\n\nSetting the screen mode is a prerequisite for editing Super MZX\npalettes and character sets.\n\n<a class=\"hL\" href=\"#SMZXMODE.HLP__095\">Super MZX Modes</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP__F12\"><span class=\"fE\">F12 - Take Screenshot</span></a>\n\nPressing F12 will make a 640x350 screenshot (in PNG format by\ndefault) in the working directory, using the software renderer.\nThe file naming starts from \"screen0\". This may be disabled by\nchanging the allow_screenshots setting in the config file.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AF11\"><span class=\"fE\">Alt+F11 - Robotic Debugger</span></a>\n\nAlt plus F11 will launch the configuration screen for the\nRobotic Debugger. For more information on the Robotic Debugger,\ngo to its relevant section.\n\n<a class=\"hL\" href=\"#DBGMODE.HLP__101\">Debug Modes - The Robotic Debugger</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP__ShF1\"><span class=\"fE\">Shift+F1 - Show InvisWalls</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__ShF2\"><span class=\"fE\">Shift+F2 - Show Robots</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__ShF3\"><span class=\"fE\">Shift+F3 - Show Fakes</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__ShF4\"><span class=\"fE\">Shift+F4 - Show Spaces</span></a>\n\nThese four keys will cause the given things onscreen to flash.\nRobots flash as the exclamation point character, char #33; fakes\nas the pound character; spaces flash between the O and *\ncharacters; inviswalls between the carpet/floor characters. They\nare good for locating \"hidden\" or camouflaged Robots, passages,\nand the like.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__Ar\"><span class=\"fE\">Arrow - Move</span></a>\n\nThe arrow keys will move the cursor. The edit window will\nscroll when necessary.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__AltAr\"><span class=\"fE\">Alt+Arrow - Move 10</span></a>\n\nAlt plus the arrow keys will move the cursor ten spaces at a\ntime, or up to the board's edge if under ten spaces away. These\nalso jump numbers by 10 in dialog boxes.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__BkSp\"><span class=\"fE\">BackSpace - Delete (Main Layer Only)</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__Del\"><span class=\"fE\">Del - Delete</span></a>\n\nThe Del key will delete everything under the cursor, while\nBackSpace will delete the object on the main layer and move any\nobject on the under layer to the main layer. The current thing\nin the buffer is not affected.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__End\"><span class=\"fE\">End - L/R Corner</span></a>\n\nEnd will jump the cursor to the lower-right corner of the\nentire board.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__En\"><span class=\"fE\">Enter - Modify+Grab [Board Mode]</span></a>\n\nEnter will modify the settings of the thing under the cursor,\nif applicable, then copy that thing into the buffer. It is just\nlike pressing Alt+M and then Ins.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__Enter2\"><span class=\"fE\">Enter - Character [Overlay Mode]</span></a>\n\nEnter during Overlay editing mode will change the current\ncharacter. Select it from a menu and then press Enter to\nconfirm your choice.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__ESC\"><span class=\"fE\">ESC - Exit/Cancel Mode</span></a>\n\nESC will exit the editor, asking for confirmation if your world\nhas not been saved. If you are in block, overlay, text, or\ndraw mode, ESC will instead cancel the current mode and return\nto normal editing.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__Home\"><span class=\"fE\">Home - U/L Corner</span></a>\n\nHome will jump the cursor to the upper-left corner of the\nentire board.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__Ins\"><span class=\"fE\">Ins - Grab</span></a>\n\nIns will copy the thing under the cursor into the buffer. The\nthing under the cursor is not affected.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__Sp\"><span class=\"fE\">Spacebar - Place</span></a>\n\nSpacebar will copy the thing in the buffer to the location under\nthe cursor. Trying to place something over a similar thing will\ninstead replace the object under the cursor by default. (Robots\nand Pushable Robots are the exception; they will never be\nreplaced.) Placing something over a floor type will actually\nplace it OVER the floor, not replace the floor. Other things\nwill be deleted if they are in the way.\nSpacebar can also replace similar objects under the cursor with\na space; this can be set in the configuration file.\n\n<a class=\"hL\" href=\"#CONFGINI.HLP__1st\">The Config File</a>\n\n<a class=\"hA\" name=\"EDITINGK.HLP__Tab\"><span class=\"fE\">Tab - Draw</span></a>\n\nTab will toggle the current draw mode. When drawing is on, you\nwill place a copy of the current object in the buffer every\ntime you move the cursor.\n\n<a class=\"hA\" name=\"EDITINGK.HLP__PgDn\"><span class=\"fE\">PageDown - Next Menu</span></a>\n<a class=\"hA\" name=\"EDITINGK.HLP__PgUp\"><span class=\"fE\">PageUp - Previous Menu</span></a>\n\nThese two keys will cycle through the pages of the editor menu.\nThe menu bar can wrap around. You do not have to be viewing the\npage listing a specific option before you can use that option -\nthe menus are purely for reference.\nThese keys also jump numbers by 100 in dialog boxes.\n\n<a class=\"hL\" href=\"#THEWORLD.HLP__1st\">The World Editor</a>\n<a class=\"hL\" href=\"#GENERALE.HLP__1st\">General Editing Tips</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"CHAREDIT.HLP\">\n<a class=\"hA\" name=\"CHAREDIT.HLP__079\"> </a>\n<p class=\"hC\"><span class=\"f9\">The Character Editor</span></p>\nThe character editor is an important part of MegaZeux. With\nit, you can change the appearance of character sets. For\nexample, one can make bricks, rockets, stones, and demons;\nusing multiple characters, one can make much grander things.\nThe default pixel resolution is fair - 8x14 per character for a\ntotal of 640x350 for the entire screen. Each character has a\nforeground color (for \"on\" pixels) and a background color (for\n\"off\" pixels). Other modes, known as Super MZX modes, increase\ncolor amount to four at a cost of lowering per-character\nresolution to 4x14, but the intricacies of character editing in\nthose modes are best covered in that mode's section.\n\nTo use the character editor, press Alt+C in the editor. You\nwill be shown a zoomed version of the current character, a list\nof keyboard shortcuts, and a row of characters.\n\nThe current character(s) being edited are displayed on the left\nedge of the character bar, and also highlighted and shown in\nrelative position in the middle of the bar. To edit the\ncharacter(s), move the cursor with the keys and use Spacebar to\ntoggle pixels on and off (or left-click and right-click,\nrespectively). Use + and - to move one character (or character\nblock) up or down through the character set.\n\nUse Enter to select the current character (or block) from the\nentire set. When selecting from the entire set, one can move by\none character using the arrows, or by one block using the -/+\nkeys.\n\nUse Del to clear the character. Use N to make a \"negative\", or\ninverse, of the character, turning all on pixels off and all\noff pixels on. Use Alt with the arrow keys to shift the entire\ncharacter to one direction. Pixels shifted off of one edge\nwrap around to the other edge.\n\nUse M to mirror the character, flipping it left to right. Use\nF to flip the character top to bottom.\n\nHold Shift to select multiple pixels at a time. The selection\nbox will vanish once Shift is released. Alt+B will also create\na similar selection box, but will not vanish until the Escape\nkey is pressed.\n\nF2 will copy the current character to an internal buffer, while\nF3 will paste the buffer contents to the current character\nspace. The buffer can contain a selection of pixels instead of a\ncharacter by pressing F2 when selecting a group of pixels. When\ncopying a single character and pasting to another single\ncharacter, the new content simply replaces the old; in other\ncircumstances, the new content is placed starting with the\ncursor position as its upper-left corner with no wraparound. The\nbuffer is retained between uses of the character editor.\n\nCtrl+Z will undo actions done in the editor (up to the limit\ndefined in the config file). Keep in mind that the current\nframe is counted in the amount of undo actions.\n\nCtrl+Y will redo any undone actions.\n\nC will allow selection of colors used in the char editor. This\ncan be helpful in crafting characters for a specific palette.\nUse Alt+C to switch colors back to the default.\n\nAlt+F will flood fill any highlighted area.\n\nTab will toggle mode (set) on and off. (set) mode will turn all\npixels crossed to ON.\n\nShift+Tab will toggle (clear) mode. (clear) mode will turn all\npixels crossed OFF.\n\nF4 will revert the current character to its EGA ASCII (code page\n437) appearance; F5 will revert the current character to its\ndefault MZX appearance.\n\nMultiple characters can be edited at one time. To select a\ngroup of characters, press Enter in the character editor, hold\nShift on the upper-left corner of your desired block of\ncharacters to edit, and finally highlight your block of\ncharacters, release Shift, and press Enter. If more than one\nviable character space can be made from the selected characters,\nthe user can choose the desired space size (e.g. six characters\ncan be a 3x2, a 2x3 or a 6x1 space). The largest character space\none can use for editing is 18 characters large (6x3, the max\nallowed sizes for the respective dimensions).\n\nThe extra character sets accessible by unbound sprites can also\nbe edited in the character editor. Press PgUp and PgDown to\nswitch character sets, either on the main character edit screen\nor on the character selection menu.\n\nAlt+I will import character sets while in the character editor;\nAlt+X will export. This can be done with one full set, multiple\nsets, or groups of partials. For a full set, simply select the\ndesired set and select OK. For saving multiple sets or a series\nof partials, first insert a hash sign in the set name then\nchoose the Offset (starting character). Next, choose the First\nnumber of the series (e.g. char#.chr with a First value of 5\nwould start from char5.chr). Then, put the Size of each set (up\nto 256 chars, or a full set). Finally, choose the Count value to\nindicate how many sets will be saved.\nLoading is simpler: simply choose the starting character set,\nthe Offset value and the Count value. If multiple characters are\nbeing worked on at once, importing and exporting partials can be\nset to tile (which will base export and import positions on the\ncurrent character grouping) or linear (which will be based on\nsequential places in the char set).\n\nThe character editor is a key element in creating decent games\nwith MegaZeux. If you can't seem to draw well with it, that's\nokay, since it has some limitations. You can keep practicing\nat it, or you can get an MZXer friend to draw characters for\nyou.\n\nThe character editor changes somewhat in Super MZX modes - go\nto the Super MZX Modes section for more information on this.\n\n<a class=\"hL\" href=\"#SMZXMODE.HLP__095\">Super MZX Modes</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__080\">Editing Keys and Options Reference</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"PALEEDIT.HLP\">\n<a class=\"hA\" name=\"PALEEDIT.HLP__093\"> </a>\n<p class=\"hC\"><span class=\"f9\">The Palette Editor</span></p>\nMegaZeux is not limited to the colors it sets as default. The\nbuilt-in palette editor can modify colors, which is very helpful\nin designing a game. It allows you to change the appearance of\nthe currently-available colors, and is more user-friendly than\nhaving to change values through code. MegaZeux worlds can only\nhave one active palette at a time, but can load different\npalettes as frequently as desired, so MegaZeux designers can\ncreate different palettes that fit different parts of their\ngame.\n\nTo use the palette editor, press Alt+E while in the MegaZeux\neditor. The palette editing screen will appear. The current\npalette will be shown to the left, with the current color\nmarked. By default, the RGB values of the current color and the\ncurrent color creation mode are shown to the right of the\nvisible palette, and the menu is shown below. The color\ncurrently stored in the buffer is shown in the bottom-right\ncorner.\n\nSome color theory is in order. Computer monitors display colors\nby projecting various amounts of Red, Green, and Blue light.\nCombined, these three colors can produce almost any shade and\nhue of color. This is one way colors are represented in MegaZeux\nas well. In the RGB color model (the default), each color has a\nRed, Green, and Blue value, each ranging from 0 to 63. 0 is\n\"off\" while 63 is \"full intensity\".\n\nTo make colors other than red, green, and blue, you must mix\nthem. Purple is red plus blue, cyan is green plus blue, and\nyellow is red plus green. (Really.) Whites and grays are made\nfrom equal amounts of all three. For example, Red at 42, Blue at\n42 and Green at 0 would be a deep purple, and all as 20 would be\na dark gray. Orange is made with full red, green set to 31 and\nblue zeroed out. To brighten a color, raise all the numbers, or\nlower them to darken it.\n\nFor the RGB color model, use the arrow keys to select the\ncurrent color. R, G, B, and A will respectively increase Red,\nGreen, Blue, and All, while Alt+R, Alt+G,Alt+B and Alt+A will\nrespectively decrease the values.\n\nSeveral functions are consistent across all color models. Alt+D\nwill reset the palette to its default colors (you will NOT be\nasked for confirmation). 0 will blacken the current color. Alt+H\nwill toggle the shortcut display. The PgUp and PgDn keys select\nthe current color model. Alt+I will import a palette; Alt+X will\nexport the current palette. Tab will switch to and from a\nscratchpad palette that will only exist for the editor session.\nFinally, F2 will store the current color to an internal buffer,\nwhile F3 will paste the buffered color to the current color.\n\nIn addition to the keyboard shortcuts listed in the below menu,\nusers can click colors in the palette to select them. Users can\nalso use the mouse to change color values by clicking on the\nrelevant areas, and hold down the mouse button to act as a\nslider. Color values can also be clicked on and then typed in\ndirectly.\n\nIf one is more familiar with other color models, MegaZeux can\ncreate colors with two alternate models: HSL and CIELAB.\n\nHSL creates colors by setting hue (base color), saturation\n(color strength) and lightness (brightness relative to white).\nHue can take values of 0-360; saturation and lightness take\nvalues of 0-100. The keyboard shortcuts in this mode are C, S\nand V for increasing Hue, Saturation, and Luminance,\nrespectively, while alt key combinations of these keys reduce\nthese values.\n\nCIELAB creates colors through a combination of lightness (L*)\nand color values represented by the opponent pair colors of\nred-green (a*) and yellow-blue (b*). L* values range from 0-100,\nand opponent color values can range from -128 (fully one color\nof the pair) to +128 (fully the other color). The keyboard\nshortcuts in this mode are V, A and B for increasing L*, a* and\nb*, respectively, while alt key combinations of these keys\nreduce these values.\n\n<p class=\"hC\"><span class=\"f9\">The Palette Editor in Super MZX Modes</span></p>\nSuper MZX Modes allow 256 colors at once, and have their own\nquirks; therefore, the palette editor changes considerably to\naccommodate them. The palette editor for each SMZX mode is noted\nbelow.\n\n<p class=\"hC\">Super MZX Mode 1 Palette Editor</p>\nSuper MZX Mode 1 does not require any changes, since it merely\ninterpolates colors. Therefore, palette editing in this mode\nacts the exact same as it does in normal MZX mode.\n\n<p class=\"hC\">Super MZX Mode 2 Palette Editor</p>\nSuper MZX Mode 2's color selection is shown as a 16x16 set of\ncolors. Each color also represents the four-color palette (also\nknown as a subpalette) used when an item is assigned that color.\nThe palette number, as well as its hex number and the four\ncolors that comprise it, are shown in the upper-left corner. By\ndefault, the color selector shows the current color/palette\nhighlighted by a white box, with red boxes showing the other\ncolors of the palette. The buffer stores only subpalettes in\nthis mode, cycling through each color of the subpalette.\n\nThe base values of the current color and the current color\ncreation mode are shown in the upper-right, and the help menu\nis shown below.\n\nOn top of the functions allowed in the normal palette editor,\nSMZX Mode 2 adds these hotkeys:\n\n<span class=\"fE\">F5</span><span class=\"fF\">: Store Colors.</span>\n\nStores the currently-highlighted subpalette into the buffer.\n\n<span class=\"fE\">F6</span><span class=\"fF\">: Place Colors.</span>\n\nReplaces the highlighted subpalette with the subpalette colors\ncurrently in the buffer.\n\n<span class=\"fE\">Insert</span><span class=\"fF\">: Cursors.</span>\nTurns the display of the red boxes highlighting the component\ncolors of the given subpalette on or off. Defaults to (on).\n\n<p class=\"hC\">Super MZX Mode 3 Palette Editor</p>\nSuper MZX Mode 3's color selection is shown as a 16x16 set of\ncolors. The palette number, as well as its hex number and the\nfour colors that comprise it, are shown in the upper-left\ncorner. Since Mode 3 allows user-defined indices, the buffer\ndisplay now also includes the currently-stored indices just to\nthe left of the stored subpalette.\n\nThe base values of the current color and the current color\ncreation mode are shown in the upper-right, and the help menu\nis shown below.\n\nIn this mode, the highlighted color only corresponds to a color,\nNOT to a subpalette; therefore, one can be editing a different\ncolor and subpalette at the same time, and changing highlighted\ncolor will NOT change the current subpalette.\n\nTo change the current subpalette, press Space or click the\nmiddle mouse button, and then choose the subpalette to edit.\nEach color can be assigned to a color slot in the current\nsubpalette; to place a color in a subpalette slot, press 1-4 to\nput the current color in the corresponding slot (for example,\npressing 2 while on color #120 would make the second color of\nthe current subpalette color #120).\n\nThe Import and Export commands can also handle indices files in\nthis mode.\n\nOn top of the functions allowed in the SMZX Mode 2 palette\neditor, SMZX Mode 3 adds these hotkeys:\n\n<span class=\"fE\">F7</span><span class=\"fF\">: Store Indices.</span>\n\nPuts the current indices into the buffer.\n\n<span class=\"fE\">F8</span><span class=\"fF\">: Place Indices.</span>\n\nAssigns the current subpalette the indices currently in the\nbuffer.\n\nWith the exception of hiding the help, all functions available\nto the palette editor in normal MZX mode are available in the\nSMZX mode 3 editor.\n\n<a class=\"hL\" href=\"#EDITINGK.HLP__080\">Editing Keys and Options Reference</a>\n<a class=\"hL\" href=\"#SMZXMODE.HLP__095\">Super MZX Modes</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"GLOBALIN.HLP\">\n<a class=\"hA\" name=\"GLOBALIN.HLP__086\"> </a>\n<p class=\"hC\"><span class=\"f9\">Global Info Options</span></p>\nTo edit the Global Info options, press G within the editor.\nHere you can edit options that affect the entire world. The\nfirst dialog you see has the following options:\n\n<p class=\"hC\">First Board</p>\nHere is where you select which board will be the starting board\nfor the game. The player will start on this board. This\ndefaults to the second board in the list, or to the title board\nif a second board was not made when creating a new world.\n\n<p class=\"hC\">Edging Color</p>\nThis is the color of the area outside of the viewport. It\ndefaults to dark gray on black (c08).\n\n<p class=\"hC\">Starting/Maximum Lives</p>\nThis is where you set the number of lives the player starts\nwith, and how many lives the player can have at any one time.\n\n<p class=\"hC\">Starting/Maximum Health</p>\nSame as above, but for health points.\n\n<p class=\"hC\">Enemies' Bullets Hurt Other Enemies</p>\nIf this is on, then bullets shot by enemies, such as tigers,\nwill damage other enemies that they hit. It basically turns\nall Enemy bullets fired into Neutral bullets (including ones\nshot by Robots).\n\n<p class=\"hC\">Clear Messages and Projectiles on Exit</p>\nIf this is on, the current message and all projectiles (seekers,\nbullets, missiles, shooting fire) are cleared whenever the\nplayer leaves the screen.\n\n<p class=\"hC\">Can Only Play World from a 'SWAP WORLD'</p>\nIf this is on, then the current world is unplayable in a normal\nfashion. The only ways to play it are by swapping to it from\nanother world with the Robotic command SWAP WORLD, or by testing\nit in the editor.\n\n<p class=\"hC\">More</p>\nThis button leads to another screen of settings.\n\n<p class=\"hC\">Edit Chars</p>\n<a class=\"hA\" name=\"GLOBALIN.HLP__089\">This button leads to a series of dialogs where you can edit the</a>\ncharacters and colors used to represent various internal things.\nWhen editing chars, selecting char 255 for an object will make\nthat object type act like a Custom object, which means the char\ndisplayed is based on the object's current parameter.\n\n<p class=\"hC\">Edit Dmg</p>\n<a class=\"hA\" name=\"GLOBALIN.HLP__090\">This button leads to a dialog where you can edit the amount of</a>\ndamage done by various internal things. Valid numbers range from\n0 to 255.\n\n<p class=\"hC\">Edit Global Robot</p>\nThis button allows you to edit the global Robot, which is a\nvery special Robot that is present at all times, although it is\nnot physically on every board. It is a very important part of\nMegaZeux. See the section on The Global for more information.\n\n<a class=\"hL\" href=\"#THEGLOBL.HLP__gbl\">The Global</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n\n<a class=\"hA\" name=\"GLOBALIN.HLP__088\">The More page of settings for global info has the following</a>\noptions:\n\n<p class=\"hC\">Death Board/Death X/Death Y/Mode</p>\nThis tells MegaZeux what happens when the player dies. If the\nmode is Same Position, then the player will simply lose a life.\nIf it is Restart Board, then the player will also teleport to\nthe location on the board where the player entered. If it is\nTeleport, the player will instead teleport to the board and\nx,y coordinates stated in the above three settings.\n\n<p class=\"hC\">Endgame Board/Endgame X/Endgame Y/Mode</p>\nThis tells MegaZeux what happens when the player runs out of\nlives. If the mode is Game Over, then the game will simply end.\nIf it is Teleport, the player will be given one life and one\nhealth, and then will be teleported to the board and x,y\ncoordinates stated in the above three settings. This will\ncontinue to happen every time the player runs out of lives or\nwhen a Robot calls the ENDGAME command.\n\n<p class=\"hC\">Play Game Over SFX</p>\nIf this option is on, the game over sound effect will be looped\nwhen the game has ended and the Game Over message will flash\nalong the bottom of the screen. The Game Over SFX will not play\nwhen the endgame teleport option is chosen.\n\n<p class=\"hC\">Previous</p>\nThis will go to the previous global settings dialog.\n\nThe character editing dialogs (eight pages in total) are screens\nof characters and colors used to represent internal things.\nSelect one to change the color or character used. Use Next and\nPrevious to move between the screens, and Done when you are\nfinished. Lit Bomb Anim 1 is special; setting it will set the\nother 6 frames of Lit Bomb Animation to the succeeding\ncharacters.\n\nSetting the char of any thing to 255 (always represented by a\nmagenta question mark in the char select) will allow that object\ntype to act like a Custom object. This means that the character\nit displays will correspond to the object's parameter (so, for\ninstance, an object with this set and with a parameter of p0a\nwill display character 10). This is best-suited for things that\ndo not otherwise use the parameter field, but will work with\nobjects that have meaningful parameters as well.\n\nAny object type with its char set to 255 that did not previously\nuse the parameter field (such as Trees) will also prompt a\ncharacter selection screen when initially placed in the editor,\nmuch like what happens when selecting a Custom object. Other\nobjects are placed normally.\n\nThe damage editing dialog allows you to change the amount of\ndamage dealt by various things within the game. Select one and\nuse the usual dialog keys to change the values, which can range\nfrom 0 to 255. 0 will leave the player invincible versus that\nthing, but pain sounds and messages will still be triggered.\n\nEditing the global Robot is covered in the Robot tutorials, as\nit is (mostly) the same as editing any other Robot. The actual\nuse of a global Robot is covered in its own section.\n\n<a class=\"hL\" href=\"#THEGLOBL.HLP__gbl\">The Global</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#EDITINGK.HLP__080\">Editing Keys and Options Reference</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"BOARDINF.HLP\">\n<a class=\"hA\" name=\"BOARDINF.HLP__085\"> </a>\n<p class=\"hC\"><span class=\"f9\">Board Info Options</span></p>\nTo edit Board options, press I within the editor. This will\nbring up a dialog where you can edit options pertaining to the\ncurrent board. The options are as follows:\n\n<p class=\"hC\">Board Name</p>\nThis is the name of the board. It is mainly for internal\nreference; however, the name of the first board (the title\nscreen) is also used to represent the entire world on a file\nlisting, and board names can be accessed through the BOARD_NAME\nstring counter in Robotic. The special color codes ~ and @\ncan apply here, allowing colored text in board names.\n\n<p class=\"hC\">Can Shoot/Bomb</p>\nThese options determine whether or not the player can shoot or\nbomb normally. If off, then the player cannot perform the\nnoted action. (Other ways of doing these actions in Robotic may\nreplace the default ways of shooting/bombing and are unimpeded\nby this setting.)\n\n<p class=\"hC\">Fire Burns Space/Fakes/Trees/Brown</p>\nThese options determine what fire will and will not burn\nthrough. Space is empty space, NOT floors; Fakes include fakes,\nfloors, carpets, and tiles. Trees is self-explanatory. Brown is\nanything other than the player or a Robot/sign/Scroll that is\ncolored brown (that is, anything with a color of cX6).\n\n<p class=\"hC\">Forest to Floor</p>\nIf on, when the player moves through forest, it will become\nFloor. If off, the forest will simply be replaced by a space.\n\n<p class=\"hC\">Collect Bombs</p>\nIf on, the player will collect bombs normally. If off, bombs\nwill be instantly lit when touched by the player instead.\n\n<p class=\"hC\">Fire Burns Forever</p>\nIf on, fire will never burn out. If off, fire will eventually\nturn into ash, which is actually a dark gray (c08) Floor. Of\ncourse, if fire is set to burn fakes, the ash will probably\nre-light immediately.\n\n<p class=\"hC\">Dragons Move Randomly</p>\nIf on, dragons that move have a 1/8 chance of moving randomly\neach time they move instead of always seeking the player.\n\n<p class=\"hC\">Restart if Hurt</p>\nWhen on, the player will be teleported to the place on the\ncurrent board that it originally entered every time health is\nlost.\n\n<p class=\"hC\">Reset Board on Entry</p>\nWhen on, every time the board is entered, the board will reset,\nacting as if the player is entering the board for the first\ntime.\n\n<p class=\"hC\">Player Locked N/S</p>\nWhen on, the player cannot directly move north or south until\nunlocked with the UNLOCKPLAYER command. (Other ways of moving\nthe player in those directions, such as Robotic or the player\nbeing pushed, are unimpeded by this setting.)\n\n<p class=\"hC\">Player Locked E/W</p>\nWhen on, the player cannot directly move east or west until\nunlocked with the UNLOCKPLAYER command. (Other ways of moving\nthe player in those directions, such as Robotic or the player\nbeing pushed, are unimpeded by this setting.)\n\n<p class=\"hC\">Player Attack Locked</p>\nWhen on, the player cannot shoot bullets or lay bombs until\nunlocked with the UNLOCKPLAYER command. (Other ways of doing\nthese actions, in Robotic, are unimpeded by this setting.)\n\n<p class=\"hC\">Time Limit</p>\nIf greater than zero, the player has the noted amount of time\nto complete the board. The current time is shown in the lower\nleft corner during gameplay. If time runs out, the player loses\nten health and the time limit is reset. This option is most\nuseful with the \"Restart if Hurt\" option. The countdown speed\nvaries based on the MZX speed, ticking every other cycle; test\nthe board to get a feel for the amount of time needed. The\ncurrent time is reset if the player exits and re-enters the\nboard.\n\n<p class=\"hC\">Explosions to Space/Ash/Fire</p>\nThis determines what explosions will leave in their wake: Empty\nspace, ash (dark gray floors - in other words, floors colored\nc08), or burning fire.\n\n<p class=\"hC\">Can Save/Can't Save/Can Save on Sensors</p>\nThis determines when the player can save the game. The first\ntwo options are self-explanatory. Can Save on Sensors allows\nthe player to only save if it is standing directly over a\nSensor. Sensors are explained in another section. This only\nprevents or limits saving using the built-in save functions;\nsaving done with Robotic is unaffected.\n\n<a class=\"hL\" href=\"#SENSORSW.HLP__094\">Sensors - What They Are and How to Use Them</a>\n\n<p class=\"hC\">No/Normal/Static/Transparent Overlay</p>\nThis sets the type of overlay for the current board. No Overlay\nmeans just that - overlay mode is completely off. Normal\nOverlay is an overlay that scrolls with the board. Static\nOverlay is an overlay that remains in a fixed position even when\nthe board scrolls. A Transparent Overlay is similar to no\noverlay in that none is shown, but it allows you to edit it so\nthat it may appear later. Overlays are discussed in full in\nanother section.\n\n<a class=\"hL\" href=\"#TOVERLAY.HLP__081\">Editing and Using the Overlay</a>\n\n<p class=\"hC\">Load Charset on Entry</p>\nThis can be set to load a specific charset every time the board\nis entered, or set to do nothing.\n\n<p class=\"hC\">Load Palette on Entry</p>\nThis can be set to load a specific palette every time the board\nis entered, or set to do nothing.\n\n<p class=\"hC\">Reset/Load When Entering from the Same Board</p>\nIf this option is set, all transitions from this board to itself\nwill trigger charset/palette reloads. If \"Reset board on entry\"\nis concurrently set, all transitions from this board to itself\nwill also reset the board.\n\nOne can set the current board's settings as the default\nsettings for all new boards made for this world file. Select\nthe \"Set as defaults\" button at the bottom and click or press\nSpace/Enter. This will save a .cnf file for the current world.\nThere will be no confirmation. Since the .cnf is named after the\ncurrent world, the current world must have been saved first.\n\n<a class=\"hL\" href=\"#EDITINGK.HLP__080\">Editing Keys and Options Reference</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"SOUNDEFX.HLP\">\n<a class=\"hA\" name=\"SOUNDEFX.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">MegaZeux's Sound System</span></p>\nMegaZeux has two types of sound: Digitized sound, played\nthrough a sound card, and sound effects, which are emulated PC\nSpeaker effects.\n\nMegaZeux allows OGG, WAV, SAM (a legacy sound format), and\nmodule files (that is, music files) for digitized sound.\n\nMegaZeux's many sound options can be accessed through the\nconfig.txt file. Please see this section for details.\n\n<a class=\"hL\" href=\"#CONFGINI.HLP__1st\">The Config File</a>\n\n<p class=\"hC\">Music Files</p>\nMusic files, referred to as MODULES, can be any one of the\nfollowing formats:\n\n* OGG\n* MOD (or NST/WOW/OCT)\n* S3M\n* XM\n* IT\n* GDM\n* RAD\n* Other formats {STM, MTM, 669, ULT, FAR, MED, OKT, AMF}\n* Several other formats are in ModPlug or XMP, but are not\n  supported in MZX.\n\nThe default module engine is XMP. It is still possible to build\nusing ModPlug, and possible to build using MikMod to support\nplatforms that cannot handle anything else. OGGs, WAVs and RADs\nare handled outside of the module engines, while GDMs load\nnatively in XMP/MikMod, but not in ModPlug. When a GDM is loaded\nwith ModPlug as the music engine, it is converted to an S3M\nfile. The S3M file is then loaded in all future incidences.\n\nFormats common to all engines are MOD, S3M, XM, IT, STM, MTM,\n669, ULT, FAR, MED, OKT and AMF. All other formats (excluding\nWAV/OGG/SAM/GDM/RAD) are locked out of MZX by default.\n\nMED support is the most inconsistent between module engines,\ndue to its multiple subversions and the engines' varying\naccuracy on dealing with them, so use of this format is not\ngenerally advised.\n\nMZX supports loop markers for OGG files. OGG loops are supported\nwith the LOOPSTART, LOOPLENGTH and LOOPEND metadata tags. MZX\ncan also control OGG looping dynamically with the MOD_LOOPSTART\nand MOD_LOOPEND counters.\n\n<p class=\"hC\">Using Sound and Music in Your Own Games</p>\nThere are three ways to utilize sound and music in your own\ngames:\n\n 1- Select the default modules for each board (or mod *).\n 2- Edit the built-in speaker sfx tables.\n 3- Use Robots to play modules, SAMs/WAVs/OGGs, and speaker\n   sfx.\n\n<p class=\"hC\">Selecting Default Modules for Each Board</p>\nThis is perhaps the easiest method to get music in your games.\nSimply make sure that the proper module files are in the\ncurrent directory, or in a subdirectory of the current\ndirectory, and use Alt+N to select music for a board. Press\nAlt+N to turn it back off again. To set it to play the same\nmusic as the last board (mod *), press Shift+8 or the numpad's\nasterisk key.\n\n<a class=\"hL\" href=\"#EDITINGK.HLP__AltN\">Details on Alt+N</a>\n\n<p class=\"hC\">Editing the Built-in Speaker SFX</p>\nTo edit the built-in speaker sound effect tables, use Alt+F.\nThe format for them is discussed later in this section.\n\n<a class=\"hL\" href=\"#EDITINGK.HLP__097\">Details on Alt+F</a>\n\n<p class=\"hC\">Using Robots</p>\nRobotic has a large number of commands for playing modules,\nSAMs/WAVs/OGGs, and speaker sfx. A knowledge of Robotic basics\nis required, so see the following sections to learn about\nRobots.\n\n<a class=\"hL\" href=\"#ROBOTSWH.HLP__1st\">Robots - What They Are and How to Use Them</a>\n<a class=\"hL\" href=\"#ROBOTICT.HLP__1st\">Robotic Tutorial</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n\nThe following sections discuss individual commands relating to\nsound and music.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP___c6\"><span class=\"fA\">CHANGE SFX # \"string\"</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e2\"><span class=\"fA\">END MOD</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e4\"><span class=\"fA\">END SAM</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e3\"><span class=\"fA\">END PLAY</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___j1\"><span class=\"fA\">JUMP MOD ORDER #</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___m3\"><span class=\"fA\">MOD \"file\"</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m5\"><span class=\"fA\">MOD FADE IN \"file\"</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m6\"><span class=\"fA\">MOD FADE OUT</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m4\"><span class=\"fA\">MOD FADE # #</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m7\"><span class=\"fA\">MOD SAM # #</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p2\"><span class=\"fA\">PLAY \"string\"</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p3\"><span class=\"fA\">PLAY SFX \"string\"</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___s1\"><span class=\"fA\">SAM # \"file\"</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sO\"><span class=\"fA\">SFX #</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___v3\"><span class=\"fA\">VOLUME #</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___w2\"><span class=\"fA\">WAIT MOD FADE</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___w3\"><span class=\"fA\">WAIT PLAY</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___w4\"><span class=\"fA\">WAIT PLAY \"string\"</span></a>\n\n<p class=\"hC\">SAM File Support</p>\nSAM files, formerly the only format for playing sounds in MZX,\nare directly supported. However, using OGGs or WAVs as the\noriginal source, if possible, is preferred as SAMs are low\nquality (~8KHz mono) compared to typical WAV and OGG quality\nsettings. SAM support is mostly kept in for supporting older\ngames, not for current usage in designing games.\n\n<p class=\"hC\">Format for Sound Effects</p>\nThe sound effects editor and Robotic PLAY commands all use the\nsame format for playing sound effects. The format consists of\na single string of characters, each character representing one\nnote or command. Spaces are ignored, and capitalization is not\nimportant. Some knowledge of music is required to use the\nformat effectively. The commands are:\n\n<span class=\"fA\"> A B C D E F G</span>\n  Plays the note stated, at the current octave and duration.\n  The scale starts at C and continues DEFGAB before going up\n  an octave. Use # and $ for sharps and flats, placing them\n  after the note.\n<span class=\"fA\"> # $</span>\n  Sharps (#) or flats ($) the previous note. This does not\n  affect any note other than the note directly before the sharp\n  or flat. Constructs such as B# are allowed.\n<span class=\"fA\"> 0 1 2 3 4 5 6</span>\n  Sets the current octave. MZX octaves are shifted a number back\n  resulting in octave 3 starting at middle C (aka c'). The lower\n  the number, the lower the notes.\n<span class=\"fA\"> + -</span>\n  Raises or lowers the current octave by 1, but only if\n  possible. You cannot go below octave 0 or above octave 6.\n<span class=\"fA\"> X</span>\n  Plays a rest of the current duration. A rest is a period of\n  silence.\n<span class=\"fA\"> Z T S I Q H W</span>\n  These letters change the current duration of notes and rests,\n  I.E. the length of time each subsequent note is played. Each\n  one is twice as long as the previous one, with Z being the\n  fastest and W the slowest. Duration defaults to T. The\n  counterparts to the durations follow:\n   Z = sixty-fourth note\n   T = <span class=\"fA\">T</span><span class=\"fF\">hirty-second note</span>\n   S = <span class=\"fA\">S</span><span class=\"fF\">ixteenth note</span>\n   I = e<span class=\"fA\">I</span><span class=\"fF\">ghth note</span>\n   Q = <span class=\"fA\">Q</span><span class=\"fF\">uarter note</span>\n   H = <span class=\"fA\">H</span><span class=\"fF\">alf note</span>\n   W = <span class=\"fA\">W</span><span class=\"fF\">hole note</span>\n  Duration (and octave) does not carry over from one string to\n  the next.\n<span class=\"fA\"> .</span>\n  A dot will change the current duration to 150% that of usual.\n  This is a permanent duration change and will only affect\n  subsequent notes. Its use is similar to that of a dot in\n  regular music, except that this dot affects multiple notes.\n<span class=\"fA\"> !</span>\n  An exclamation point will change the current duration to 33%\n  that of usual. This is a permanent duration change and will\n  only affect subsequent notes. Its use is similar to that of\n  triplets in regular music, except it affects ALL subsequent\n  notes.\n<span class=\"fA\"> &amp;</span>\n  &amp; is a very special command. It is used to play SAM/WAV/OGG\n  files (digitized sounds) within an sfx string. It allows\n  digitized sounds within the sound effects editor. The general\n  format for &amp; is as follows:\n\n<p class=\"hC\">\"&amp;FILENAME.WAV&amp;4C\"</p>\n  This will play FILENAME.WAV at a pitch of 4c, which\n  corresponds to a sample rate of 8363Hz. Any given durations\n  are irrelevant. If nothing follows the last &amp;, the effect will\n  play at the file's natural frequency. Multiple filenames can\n  be used in the same PLAY command, but only the last one can\n  have nothing following its last &amp;.\n<span class=\"fA\"> _</span>\n  An underscore is usually used in conjunction with &amp;. It\n  turns off digitized sounds for the rest of the string, and\n  ignores the rest of the string ONLY IF digitized music/sound\n  is on. For example:\n\n<p class=\"hC\">\"&amp;SHOT.WAV&amp;6F#_+C-C\"</p>\n  The above will play SHOT.WAV at a sample rate of roughly 48KHz\n  (F-sharp, 6th octave) if digitized sound is on, OTHERWISE it\n  will play the normal shooting sound of \"+C-C\".\n\n<p class=\"hC\">Sound Effect Numbers</p>\nThe PLAY SFX command in Robotic plays one of the built-in sound\neffects. The numerical values corresponding to these sound\neffects are listed below.\n\n0  - Gem\n1  - Magic Gem\n2  - Health\n3  - Ammo\n4  - Coin\n5  - Life\n6  - Lo Bomb\n7  - Hi Bomb\n8  - Key\n9  - Full Keys\n10 - Unlock\n11 - Can't Unlock\n12 - Invis. Wall\n13 - Forest\n14 - Gate Locked\n15 - Open Gate\n16 - Invinco Start\n17 - Invinco Beat\n18 - Invinco End\n19 - Door Locked\n20 - Open Door\n21 - Hurt\n22 - Hurt (Lava)\n23 - Death\n24 - Game Over\n25 - Gate Closing\n26 - Push\n27 - Transport\n28 - Shoot\n29 - Break\n30 - Out of Ammo\n31 - Ricochet\n32 - Out of Bombs\n33 - Place Bomb (Lo)\n34 - Place Bomb (Hi)\n35 - Switch Bomb Type\n36 - Explosion\n37 - Entrance\n38 - Pouch\n39 - Ring/Potion\n40 - Empty Chest\n41 - Chest\n42 - Out of Time\n43 - Fire Ouch\n44 - Stolen Gem\n45 - Enemy HP Down\n46 - Dragon Fire\n47 - Scroll/Sign\n48 - Goop\n49 - Unused\n\n<a class=\"hA\" name=\"SOUNDEFX.HLP__frq\"> </a>\n<p class=\"hC\">SAM, OGG, MOD and WAV Frequencies</p>\nSounds played using the SAM command in Robotic play at its\n\"natural\" frequency at value 0. If one wants to play the sound\nat a different pitch, a frequency value is needed. Frequencies\ncorresponding to MOD file notes follow:\n\n &#xE0DA;--------&#xE0C2;---&#xE0C2;---&#xE0C2;---&#xE0C2;---&#xE0C2;---&#xE0C2;---&#xE0C2;---&#xE0C2;---&#xE0C2;---&#xE0C2;---&#xE0C2;---&#xE0C2;---&#xE0BF;\n |        | C | C#| D | D#| E | F | F#| G | G#| A | A#| B |\n &#xE0C3;--------&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0B4;\n |Octave 1|856|808|762|720|678|640|604|570|538|508|480|453|\n &#xE0C3;--------&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C3;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0B4;\n |Octave 2|428|404|381|360|339|320|302|285|269|254|240|226|\n &#xE0C3;--------&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C3;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0B4;\n |Octave 3|214|202|190|180|170|160|151|143|135|127|120|113|\n &#xE0C3;--------&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C3;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0B4;\n |Octave 4|107|101| 95| 90| 85| 80| 75| 71| 67| 63| 60| 56|\n &#xE0C3;--------&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0C3;---&#xE0C5;---&#xE0C5;---&#xE0C5;---&#xE0B4;\n |Octave 5| 53| 50| 47| 45| 42| 40| 37| 35| 33| 31| 30| 28|\n &#xE0C0;--------&#xE0C1;---&#xE0C1;---&#xE0C1;---&#xE0C1;---&#xE0C1;---&#xE0C1;---&#xE0C1;---&#xE0C1;---&#xE0C1;---&#xE0C1;---&#xE0C1;---&#xE0C1;---&#xE0D9;\n\nThe following table lists frequencies corresponding to PLAY\nstatement notes:\n\n &#xE0DA;-&#xE0C2;----&#xE0C2;----&#xE0C2;----&#xE0C2;----&#xE0C2;----&#xE0C2;----&#xE0C2;----&#xE0C2;----&#xE0C5;----&#xE0C2;----&#xE0C2;----&#xE0C2;----&#xE0BF;\n |O|C   |C#  |D   |D#  |E   |F   |F#  |G   |G#  |A   |A#  |B   |\n &#xE0C3;-&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0B4;\n |0|3424|3232|3048|2880|2712|2560|2416|2280|2152|2032|1920|1812|\n &#xE0C3;-&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0B4;\n |1|1712|1616|1524|1440|1356|1280|1208|1140|1076|1016| 960| 906|\n &#xE0C3;-&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0B4;\n |2| 856| 808| 762| 720| 678| 640| 604| 570| 538| 508| 480| 453|\n &#xE0C3;-&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0B4;\n |3| 428| 404| 381| 360| 339| 320| 302| 285| 269| 254| 240| 226|\n &#xE0C3;-&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0B4;\n |4| 214| 202| 190| 180| 170| 160| 151| 143| 135| 127| 120| 113|\n &#xE0C3;-&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0B4;\n |5| 107| 101|  95|  90|  85|  80|  75|  71|  67|  63|  60|  56|\n &#xE0C3;-&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0C5;----&#xE0B4;\n |6|  53|  50|  47|  45|  42|  40|  37|  35|  33|  31|  30|  28|\n &#xE0C0;-&#xE0C1;----&#xE0C1;----&#xE0C1;----&#xE0C1;----&#xE0C1;----&#xE0C1;----&#xE0C1;----&#xE0C1;----&#xE0C1;----&#xE0C1;----&#xE0C1;----&#xE0C1;----&#xE0D9;\n\nThe following tables list how to play a Wave or OGG at its\nnormal sound with PLAY statements:\n\n 48000Hz Wave/OGG --&gt; 37\n 44100Hz Wave/OGG --&gt; 41\n 32000Hz Wave/OGG --&gt; 56\n 24000Hz Wave/OGG --&gt; 75\n 22050Hz Wave/OGG --&gt; 81\n 16000Hz Wave/OGG --&gt; 112\n 12000Hz Wave/OGG --&gt; 149\n 11025Hz Wave/OGG --&gt; 162\n 08000Hz Wave/OGG --&gt; 224\n\nTo convert frequencies from MZX to real, use these formulae:\n\n Actual frequency = 1789682 / MZX frequency\n MZX frequency = 1789682 / actual frequency\n\nSAMs and WAVs converted from SAMs by older versions of MZX play\nat a natural frequency of 8363Hz.\n\nFinally, tracked modules have a normal frequency of 44100 Hz;\nOGGs, on the other hand, have normal frequencies at their\nrecording rate (for example, a OGG encoded at 22050 Hz will play\nnormally at the 22050 Hz setting). The frequency of a module can\nbe controlled with the \"mod_frequency\" counter. Changing the\nfrequency mid-song can create a popping sound, especially while\nlowering frequency. The lowest allowed setting of mod_frequency\nis 16 Hz.\n\n<p class=\"hC\">Where to Get Module and Sound Files</p>\nThese questions are answered in the Frequently Asked Questions\nsection of help.\n\n<a class=\"hL\" href=\"#FAQ.HLP__1st\">Frequently Asked Questions</a>\n<a class=\"hL\" href=\"#CONFGINI.HLP__1st\">The Config File</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"TOVERLAY.HLP\">\n<a class=\"hA\" name=\"TOVERLAY.HLP__081\"> </a>\n<p class=\"hC\"><span class=\"f9\">Editing and Using the Overlay</span></p>\nThe overlay is a simple but useful part of a board. If one\nlooks at Floors and Fakes as a floor layer, and Walls as a\ncentral layer, the overlay is a ceiling layer. Basically, the\noverlay, when on, appears OVER anything on the board. The\nplayer, enemies, Robots, etc. all move underneath it; the only\nexceptions are sprites specifically set to go over it. The\noverlay is purely for show and graphical effects. For example,\nyou could create archways that the player moves under, or have\na score and lives display that is overlaid over the view at all\ntimes (by using Robotic). However, character 32 (empty space\n99.99% of the time) is never part of the overlay. To emulate a\nspace, use character 0 (if still blank) or make your own space\ncharacter. This is very important to remember when using the\noverlay to display text.\n\nThe overlay is set on \"Normal Overlay\" mode by default. The\neditor will prevent you from editing the overlay if it is Off,\nsince it will not be saved if it is. These are the available\noverlay modes:\n\nOff - No overlay is saved, none is shown.\nNormal - The overlay is shown normally, scrolling along with\n  the rest of the board. Default for boards.\nStatic - The overlay is shown, but it does not scroll with the\n  board. It always shows the top left position. Very useful for\n  creating HUDs and status displays.\nTransparent - The overlay is saved and can hold info, but is\n  currently not shown.\n\nIf the overlay is on, press Alt+O in the editor to edit it.\nThis brings you to the overlay editor. The menu at the bottom\nof the screen will change, and you will have access to the\nfollowing keys and options:\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__074\"><span class=\"fE\">Alt+B - Block</span></a>\n\nPress Alt+B to start block mode, then move the cursor to the\nopposite corner of a rectangular block and press Alt+B again.\nYou can then select an action to perform upon the block. In all\ncopy/move operations, the cursor marks the new UPPER LEFT\ncorner.\n\nCopy block will allow you to duplicate the block by moving the\ncursor to a destination and pressing Enter.\nCopy block (repeated) is the same as Copy block but can allow\ncopying of the same block to multiple places.\nMove block will allow you to move the block to a new location.\nClear block will erase the contents of the block.\nFlip block will flip the block upside down.\nMirror block will flip the block left to right.\nPaint block will change the entire block to the current color.\nCopy to board will copy the block to the given spot of the\nboard. You can choose to place it as either Custom Block, Custom\nFloor, or Text.\nCopy to vlayer will copy the block to the given spot of the\nvlayer.\nSave as ANSi/TXT will save the block as an ANSi or TXT file.\nSave as MZM will save the block as a layer-type MZM file.\n\nDestinations for Move and Copy can overlap the original block\nsafely. Ctrl+Dir is especially helpful when doing a repeated\ncopy block; it moves the cursor the width or height of the\nblock, ensuring no overlap when pasting.\n\nLike normal Block functions, one can copy between boards by\nselecting the board when the editor prompts the user for the\nblock's destination. Use the B key to select the destination\nboard.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP___C\"><span class=\"fE\">C - Color</span></a>\n\nPress C to select a new current color from a menu. The thing\nunder the cursor is not affected. One can jump to a color by\ntyping its hex code in the color menu; for example, typing \"0D\"\nwould jump to color 013 (background color 0, foreground color\nD).\n\nA quirk to keep in mind: any overlay tile with a background\ncolor of 0 will display the background color of anything beneath\nit.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP___F\"><span class=\"fE\">F - Fill</span></a>\n\nPress F to fill in an enclosed area with the current character\nand color. The area must be completely surrounded by characters\nother than the character beneath the cursor. For example, you\ncan fill over a solid square of As with something else. The\ncurrent fill command may not work right for very large and\ncomplex areas- In this case, you must move to the unfilled areas\nand press F to continue filling. However, this happens very\nrarely.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__AltO\"><span class=\"fE\">Alt+O - Edit Overlay</span></a>\n\nAlt+O will exit overlay mode and return to editing the main\nboard.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__AltS2\"><span class=\"fE\">Alt+S - Show Level</span></a>\n\nAlt+S will toggle whether the level beneath the overlay is\nshown (defaults to yes). Without the level, only the overlay is\nshown, and no board bits or underlying colors are seen through\nany holes or c0x characters in the overlay. However, it can be a\ngood idea to show the level to see how it looks beneath the\noverlay.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__F1\"><span class=\"fE\">F1 - Help</span></a>\n\nF1 will bring up context-relevant help. You can press F1 at\nalmost any time.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__F2\"><span class=\"fE\">F2 - Text</span></a>\n\nF2 will toggle text mode on and off. When text mode is on, Enter\nwill go to the next line, and Backspace will delete going\nbackwards. All printable characters will type in as text.\nPressing Space will place a char 32 on the overlay.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__Ar\"><span class=\"fE\">Arrow - Move</span></a>\n\nThe arrow keys will move the cursor. The edit window will scroll\nwhen necessary.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__AltAr\"><span class=\"fE\">Alt+Arrow - Move 10</span></a>\n\nAlt with the arrow keys will move the cursor ten spaces at a\ntime (or to the edge if under 10 spaces away in that direction).\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__BkSp\"><span class=\"fE\">Backspace - Delete</span></a>\n<a class=\"hA\" name=\"TOVERLAY.HLP__Del\"><span class=\"fE\">Del - Delete</span></a>\n\nThese two keys will delete any overlay under the cursor. The\ncurrent character is not affected.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__End\"><span class=\"fE\">End - L/R Corner</span></a>\n\nEnd will jump the cursor to the lower-right corner of the entire\noverlay.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__Enter2\"><span class=\"fE\">Enter - Character</span></a>\n\nEnter will alter the current character on the overlay. Select it\nfrom a menu and then press Enter to confirm your choice.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__ESC\"><span class=\"fE\">ESC - Exit/Cancel Mode</span></a>\n\nESC will exit overlay mode. If you are in block, text, or draw\nmode, ESC will instead cancel the current mode and return to\nnormal overlay editing.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__Home\"><span class=\"fE\">Home - U/L Corner</span></a>\n\nHome will jump the cursor to the upper-left corner of the\nentire overlay.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__Ins\"><span class=\"fE\">Ins - Grab</span></a>\n\nIns will select the character and color under the cursor\nas the current. The actual character is not affected.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__Sp\"><span class=\"fE\">Spacebar - Place</span></a>\n\nSpacebar will copy the current character and color to the\nlocation under the cursor. Other overlay will be deleted if it\nis under the cursor.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__Tab\"><span class=\"fE\">Tab - Draw</span></a>\n\nTab will toggle the current draw mode. When drawing is on, you\nwill place a copy of the current character every time you move\nthe cursor.\n\n<span class=\"fE\">P - Change Buffer Character</span>\n\nP will act much like Enter, but only change the character in the\nbuffer; no character on the overlay is changed with this action.\n\n<a class=\"hA\" name=\"TOVERLAY.HLP__CtrZ\"><span class=\"fE\">Ctrl+Z - Undo</span></a>\n<a class=\"hA\" name=\"TOVERLAY.HLP__CtrY\"><span class=\"fE\">Ctrl+Y - Redo</span></a>\n\nCtrl+Z will undo an action; Ctrl+Y will redo any previously\nundone action. Action depth is determined by config file\nsettings, with 100 as default.\n\nThe basic method of editing the overlay is to use C and Enter to\nselect characters and colors, and use Space, arrows, and Tab to\ndraw with them. Do not switch overlay mode to Off once you have\ndrawn your overlay, or it may be permanently erased.\n\nThe overlay and overlay mode can also be changed using Robotic;\nwith Robotic, not only can one powerfully manipulate the\noverlay, but change the overlay's type, and also copy directly\nto the board or vlayer from the overlay (and vice versa). A\nknowledge of Robotic basics is required, so see the following\nsections to learn about Robots.\n\n<a class=\"hL\" href=\"#ROBOTSWH.HLP__1st\">Robots- What They Are and How to Use Them</a>\n<a class=\"hL\" href=\"#ROBOTICT.HLP__1st\">Robotic Tutorial</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n\nThe following sections discuss individual commands relating to\noverlays.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP___c4\">CHANGE OVERLAY [color] [char] [color] [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c5\">CHANGE OVERLAY [color] [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cJ\">COPY BLOCK # # # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cL\">COPY OVERLAY BLOCK # # # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cR\">COPY OVERLAY BLOCK # # # # \"@filename\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cS\">COPY OVERLAY BLOCK \"#x\" \"#y\" # # \"@filename\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cU\">COPY OVERLAY BLOCK # # # # \"$string\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cV\">COPY OVERLAY BLOCK \"#x\" \"#y\" # # \"$string\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cX\">COPY OVERLAY BLOCK # # # # \"#x\" \"#y\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cZ\">COPY OVERLAY BLOCK \"#x\" \"#y\" # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cAA\">COPY OVERLAY BLOCK \"#x1\" \"#y1\" # # \"#x2\" \"#y2\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___o2\">OVERLAY ON</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___o3\">OVERLAY STATIC</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___o4\">OVERLAY TRANSPARENT</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___pE\">PUT [color] [char] OVERLAY # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___w7\">WRITE OVERLAY [color] \"string\" # #</a>\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"SCROLLSS.HLP\">\n<a class=\"hA\" name=\"SCROLLSS.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Signs and Scrolls in the Editor</span></p>\nSigns and scrolls are a simple way to get messages across in\nthe game. Signs can be read multiple times; scrolls can be read\nonce, then disappear. To place them in the editor, press F10 and\nselect Sign or Scroll from the list. You must then edit the text\nof the sign or scroll.\n\nThe Scroll editor is very simple. You can move the cursor among\nthe different lines and characters. Type to insert text\nanywhere. Press Ins to toggle between insert mode (default)\nand overwrite mode, where typed characters will overwrite any\ncharacters already there. Backspace and delete work as normal,\nas do PageDown, PageUp, End, and Home. Use Alt+C to insert a\nselected character from the current character set into the text.\nPress Alt+V to toggle between viewing the scroll elements with\nthe current game charset/palette and the editor charset/palette.\nFinally, use Enter to start or insert new lines.\n\nSigns and scrolls allow the standard color codes in messages\n(described in the Strings section of the help). Default color\ncoding for messages in scrolls is background color 8 and\nforeground color F.\n\nPress ESC when you are done editing your sign or scroll. It will\nbe placed at the current cursor position and will become the\ncurrent object in the buffer. Note that although you can now\ncopy the scroll freely, there is a limit of 255 scrolls and\nsigns per board, but this limit shouldn't ever be a problem.\n\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"DBGMODE.HLP\">\n<a class=\"hA\" name=\"DBGMODE.HLP__dbg\"> </a>\n<p class=\"hC\"><span class=\"f9\">Debug Modes</span></p>\nThe debug modes are powerful tools. MegaZeux contains two types\nof debug modes: the Counter Debug Mode and the Robotic\nDebugger.\n\n<p class=\"hC\">Counter Debug Mode</p><a class=\"hA\" name=\"DBGMODE.HLP__100\"> </a>\nThe counter debug mode - accessed by pressing F11 while running\na world during playtesting in the editor - not only can pause\nall current MZX world action and display all of the counters\nand strings the world has set, but also can manipulate these\nvalues and even add new ones.\n\nThe left side of the counter debug mode shows the current\nselection of counters/strings. Built-in counters ending with an\nasterisk (*) are read-only. Highlighting a counter/string and\neither clicking it or pressing Enter/Space while on it will\nbring up a box, allowing the value of the counter/string to be\nchanged. Type in the desired new value, and press Enter to set\nthe counter/string to the new value. Press Escape to cancel\nsetting a new value.\n\n(PLEASE NOTE: Strings can only show and set its first 68\ncharacters when attempting to set a new value. If a string\noriginally contained over 68 characters, none of the characters\npast the 68th will show in the value setting box, even when any\nof the first 68 characters are deleted. Simply choosing to set\na value, while not doing anything else, will truncate the\nstring down to its first 68 characters.)\n\nThese counters and strings are organized into various lists,\nshown in the upper-right. Selecting one of these lists will\ndisplay its contents in the left-side box. If a list has a plus\nsign next to it, it is a tree, and clicking on it or pressing\nSpace or Enter will expand it to show any of that tree's\nsub-lists (if they exist). Repeat this action to hide the\nsub-lists.\n\nThe lists are as follows:\n\n<span class=\"fA\">Counters</span><span class=\"fF\">: Lists all user-defined counters, as well as status</span>\ncounters (things like AMMO, HEALTH, etc) and menu counters,\nfollowed by their values. Expanding this tree will allow\nselection of lists sorted by starting character - individual A\nthrough Z lists contain only counters starting with their\nrespective characters, and # does the same for everything else.\nOnly the first 45 characters of a counter name will be shown.\n\n<span class=\"fA\">Strings</span><span class=\"fF\">: Lists all user-defined strings, followed by their</span>\nvalues. Expanding this tree will allow selection of lists\nsorted by starting character - individual A through Z lists for\nthe respective characters, and # for everything else. Only the\nfirst 16 characters of a string name (including the $) and the\nfirst 40 characters of a string value will be shown.\n\n<span class=\"fA\">Sprites</span><span class=\"fF\">: Lists the global sprite counters and their values.</span>\nExpanding this list will allow selection of lists sorted by\nrelevant sprite - \"spr0\", \"spr1\", \"spr2\", and so on - if any of\nthat sprite's counters have been set. Each spr# list shows all\nof each sprite's relevant counters. The number of\ncurrently-active sprites is given under the \"Active sprites\"\nentry.\n\n<span class=\"fA\">Universal</span><span class=\"fF\">: Lists counters which persist throughout an entire</span>\nsession of MegaZeux, as well as their values.\n\n<span class=\"fA\">World</span><span class=\"fF\">: Lists built-in world counters/strings and their values.</span>\nAlso displays the amount of RAM used by each component of the\ncurrently-running world under the \"RAM\" sub-list.\n\n<span class=\"fA\">Board</span><span class=\"fF\">: Lists the current board's built-in board counters/strings</span>\nand their values. Also can expand to show lists of scrolls/signs\n(both under \"Scrolls\") and sensors, if any of these items exist\non that board. For scrolls/signs, each list shows a \"Scroll\ntext\" entry; if clicked or Enter is pressed while highlighted,\nit views that scroll's/sign's text as it would be if encountered\nin-game (i.e. in the currently-loaded charset, not the protected\ncharset). For sensors, each list shows the sensor name and the\nRobot it messages.\n\n<span class=\"fA\">Robots</span><span class=\"fF\">: If directly highlighted, this option lists nothing.</span>\nHowever, expanding this tree will allow selection of lists\nsorted by the robot_id of the current board's Robots - starting\nwith \"0: &lt;name&gt;\" for the Global, and continuing on for each\nRobot on the board. Each list displays that Robot's local\ncounters and their values (as well as their robot_name\nstrings).\n\nIn addition, certain special values can be accessed in this\nmenu.\n-The number of commands that the given Robot has processed is\nlisted under its commands_total value.\n-The number of commands that the given Robot has executed in\nthe current cycle is listed under its commands_cycle value.\nThis value is read-only.\n-Whether the given Robot is locked or unlocked is denoted by\nthe value of lockself (0 for unlocked, 1 for locked).\n\nOutside of directly displaying and setting values of counters\nand strings, the counter debug mode has several other tools at\nits disposal. They are given buttons in the lower-right corner,\nand are as follows:\n\n<span class=\"fA\">Search</span><span class=\"fF\">: Locates names and values in any of the counters/strings</span>\nlisted in the counter debug mode. Type the desired search\nstring and select from the given options, then press Enter. The\nsearch begins from the last highlighted line. This menu can be\ndirectly accessed with Ctrl+F, and an instant repeat of the\nlast search can be done with Ctrl+R. Options:\n-Search names: Searches counter and string names.\n-Search values: Searches counter and string values.\n-Case sensitive: Returns results only if case matches.\n-Exact: Returns results only if the entire string or counter\nvalue matches (as opposed to if part of the string or counter\nvalue matches).\n-Reverse: Searches from bottom to top (as opposed to the\ndefault top to bottom).\n-Wrap search: If no results are found, starts from the opposite\nend and searches until a result is found or the original search\nlocation is reached.\n-Current list only: Only searches the current list. Child lists\nof the current list will not be searched.\n\n<span class=\"fA\">New</span><span class=\"fF\">: Creates a new counter or string with the given name. Its</span>\nvalue will be 0 for a new counter, and blank for a new string.\nIf any counter or string already exists with the given name,\nthis will be ignored and the original counter/string will keep\nits original value. You will not be notified of any such\nconflicts. This menu can be directly accessed with Alt+N.\n\n<span class=\"fA\">Toggle Empties</span><span class=\"fF\">: Sets whether counters with values of 0 and</span>\nblank strings are shown in the display lists. Defaults to Show\n(i.e. counters and strings with these values will be shown).\nSearching forces this setting to Show. This menu can be\ndirectly accessed with Alt+H.\n\n<span class=\"fA\">Export</span><span class=\"fF\">: Exports a text file list of SET commands that would set</span>\nthe current user-defined counters and strings, and the built-in\ncounters in the Counters parent list, to their current values.\nIf overwriting a current file is chosen, MZX will ask for\nconfirmation.\n\nIn addition to its own considerable power, accessing the counter\ndebug mode can allow access to the character editor (via Alt+C)\nor the palette editor (via Alt+E) for further manipulation and\ntesting.\n\n<p class=\"hC\">The Robotic Debugger</p><a class=\"hA\" name=\"DBGMODE.HLP__101\"> </a>\nEver have problems with a Robot choking your game to a crawl?\nCan't figure out how someone else's code works from a simple\nlookthrough? Are your Robots just not interacting the way you'd\nplanned? The Robotic Debugger might help. The Robotic Debugger\ncan enable controlled line-by-line execution of Robots, or even\nhalt Robot execution altogether.\n<a class=\"hA\" name=\"DBGMODE.HLP__102\"> </a>\nSince stepping through code line-by-line can be very, very\ntedious if unneeded, the debugger only starts allowing such\ncontrol after meeting given conditions. The conditions that\ntell the debugger when to begin stepping through lines are\ncalled breakpoints.\n\nMegaZeux does not provide default breakpoints (with one notable\nexception explained later), so suitable breakpoints must be\ncreated. Pressing Alt+F11 while in the editor or during an\neditor testplay will load the config screen for the Robotic\nDebugger.\n\nBreakpoints can be added by using Alt+N or Alt+A, or by clicking\nthe \"(new)\" text. Breakpoints will activate stepping when\ncharacters in a line of code match a given string, preventing\nthe line triggering the breakpoint from executing. Additionally,\nthe breakpoint can be set to trigger only for Robots whose names\ncontain the characters in another given string, or only when the\ncondition is met on a given line number.\n\nA blank string can be set as a breakpoint. In this case, all\nlines running in Robots matching the given Robot name string\nwill trigger the debugger, and all line numbers matching a given\nline number will likewise always trigger. However, leaving all\nstring fields blank and line number at 0 will have no notable\neffect.\n\nString matches for breakpoints, like MegaZeux string comparisons\nin general, do not have to match case. Also, as implied, the\nstring comparisons are not for the whole line or Robot name; the\nlines or names must merely contain the given strings.\n\nSimilar to breakpoints are watchpoints. Watchpoints activate\nstepping as well, but are based off of the values of counters\nand strings, and only activate stepping after a relevant value\nis changed, not right when one is seen. Unlike with breakpoints,\nthe watchpoint Variable string has to exactly match the counter\nor string name to trigger (although this check is case\ninsensitive). Local counters can be watched as well as global\ncounters; to watch local counters, check for rN.&lt;counter&gt;, where\nN is the robot_id, and &lt;counter&gt; is the relevant local counter\nname (without the angle brackets). For watching the global\nRobot's local counters, just check for the relevant local\ncounter name.\n\nWhen the Value field of a watchpoint is left blank, the\nwatchpoint triggers every time the relevant counter or string is\nchanged. Putting a value in the Value field has the watchpoint\ntrigger only when the relevant counter or string changes to that\nspecific value.\n\nWhen a counter watchpoint is triggered, the debugger will show\nthe original value and the changed value. String watchpoints\nwill only show that the watched string value has changed.\n\nEditing breakpoints/watchpoints is done by pressing Space.\nDeleting breakpoints/watchpoints is done by pressing Alt+D.\n\nThe Robotic debugger defaults to off. Selecting the Enable\nRobotic Debugger button, or adding a breakpoint or watchpoint,\nactivates the debugger from that point on. Once done editing\nbreakpoints and watchpoints, select the Done button by clicking\nit or by using Tab to highlight it and pressing Enter.\n\nIn addition to user-defined breakpoints and watchpoints,\nMegaZeux also activates stepping through the debugger when Robot\nprocessing reaches very high amounts; if the debugger was\ncurrently off, hitting this condition forces it on. By default,\nthis happens when a Robot executes over 2,000,000 commands in a\nsingle cycle, but this can be changed by changing the value of\nthe COMMANDS_STOP counter.\n\nOnce a breakpoint or watchpoint is hit, MegaZeux halts execution\nof all actions and opens up a menu. Actions, and the keys that\nactivate them, are as follows:\n\n<span class=\"fA\">Continue</span><span class=\"fF\">: Stops stepping through code until the next</span>\nbreakpoint/watchpoint is hit. (C or Escape)\n\n<span class=\"fA\">Step</span><span class=\"fF\">: Steps through the current line, executing it, and</span>\nprompts the user for further action at the next line. (S)\n\n<span class=\"fA\">Goto</span><span class=\"fF\">: Opens up a menu to send Robots to a given label. (G)</span>\nAll Robots that match the given Name string (or all Robots for\nblank) will be sent to the given label. The Goto option will\nsend the relevant Robots to the given label regardless of locked\nstatus; the Send option will act as a normal label send; the\nSend All option will act as a normal label send to all Robots.\nSelect the \"Send/send all ignores locked status?\" option to have\nthese ignore locked states as well.\n\nThe strings given here do not have to be constants: expression\noutputs and interpolated strings will also evaluate correctly\nfor these values.\n\n<span class=\"fA\">Halt</span><span class=\"fF\">: Makes the Robot running the current line immediately</span>\nstop processing commands. Note that this does not permanently\nstop the Robot from processing commands in the future (e.g. due\nto being sent to a label), but merely prevents it from running\nits currently planned commands. (H)\n\n<span class=\"fA\">Halt All</span><span class=\"fF\">: Immediately makes ALL Robots stop processing</span>\ncommands. (Alt+H)\n\n<span class=\"fA\">Counters</span><span class=\"fF\">: Opens up the Counter Debug Mode. (F11)</span>\n\n<span class=\"fA\">Breakpoints</span><span class=\"fF\">: Opens up the Robotic Debugger config. (Alt+F11)</span>\n\nThe PgUp and PgDn keys can shift the debugger window to the top\nor bottom of the screen, respectively.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"ERRORMES.HLP\">\n<a class=\"hA\" name=\"ERRORMES.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Error Messages</span></p>\nThe following is a list of error messages in alphabetical\norder, which links to descriptions of the error and possible\nremedies. MegaZeux silently ignores many other possible errors.\n\nMegaZeux General Errors:\n\n<a class=\"hL\" href=\"#ERRORMES.HLP__era\">Cannot decrypt write-protected world; check permissions</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__erb\">Cannot overwrite the player- move it first</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__erc\">Current renderer lacks advanced graphical features; features</a>\n  <span class=\"fF\">disabled</span>\n<a class=\"hL\" href=\"#ERRORMES.HLP__erd\">Directory rename failed.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ere\">Error exporting ANSi</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__erf\">Error exporting SFX file</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__erg\">Error exporting text</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__erh\">Error saving; file/directory may be write protected</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__eri\">Error swapping to next world</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__erj\">(Filename) already exists.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__erk\">File rename failed.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__erl\">Limited/missing extended charset support; some features may not</a>\n  <span class=\"fF\">work</span>\n<a class=\"hL\" href=\"#ERRORMES.HLP__erm\">Overlay mode is not on (see Board Info)</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ern\">Save would overwrite older world. Aborted.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ero\">Windowing code bug</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__erp\">You can only play this game via a swap from another game</a>\n\nMegaZeux Validation Errors:\n\n<a class=\"hL\" href=\"#ERRORMES.HLP__eva\">Any extra scrolls/signs/robots were replaced</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evb\">Board @ (hex location) could not be found</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evc\">Board @ (hex location): found (#) robots; expected (#)</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evd\">Board @ (hex location): found (#) scrolls/signs; expected (#)</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__eve\">Board @ (hex location): found (#) sensors; expected (#)</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evf\">Board @ (hex location) is irrecoverably truncated or corrupt</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evg\">Board @ (hex location) is truncated, but could be partially</a>\n  <span class=\"fF\">recovered</span>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evh\">Board file is from a future version (version)</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evi\">Bytecode file (filename) failed validation check</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evj\">Cannot load password protected world</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evk\">Error importing ANSi</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evl\">File doesn't exist</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evm\">File is invalid or is not an SFX file</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evn\">File is not a board file or is corrupt</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evo\">File is not an MZM or is corrupt</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evp\">File is not a valid .SAV file or is corrupt</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evq\">File is not a valid world file or is corrupt</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evr\">MZM contains runtime robots; dummying out</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evs\">MZM doesn't exist</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evt\">MZM from newer version (version); dummying out robots</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evu\">MZM is missing robots or contains corrupt robot</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evv\">Post validation IO error occurred</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evw\">Robot @ (hex location) could not be found</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evx\">Robot @ (hex location) is truncated or corrupt</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evy\">.SAV files from newer versions of MZX (version) are not</a>\n  <span class=\"fF\">supported</span>\n<a class=\"hL\" href=\"#ERRORMES.HLP__evz\">.SAV files from older versions of MZX (version) are not</a>\n  <span class=\"fF\">supported</span>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ev1\">Scroll @ (hex location) is truncated or corrupt</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ev2\">Sensor @ (hex location) is truncated or corrupt</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ev3\">This world may be password protected. Decrypt it?</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ev4\">Unknown error reading from file</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ev5\">Unknown error writing to file</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ev6\">World is from a more recent version (version)</a>\n\nMegaZeux Validation Errors (ZIP World Format):\n\n<a class=\"hL\" href=\"#ERRORMES.HLP__eza\">Board # (number) is corrupt</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ezb\">Board # (number) is missing data:</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ezc\">Robot # (number) contains duplicates on board # (number)</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ezd\">Robot # (number) does not exist on board # (number)</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__eze\">Robot # (number) exists on board # (number), but was not found</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ezf\">Robot # (number) on board # (number) is corrupt</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ezg\">Scroll # (number) on board # (number) is corrupt</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__ezh\">Sensor # (number) on board # (number) is corrupt</a>\n\nMegaZeux Updater Errors:\n\n<a class=\"hL\" href=\"#ERRORMES.HLP__eua\">Attempt to invoke self failed!</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__eub\">Failed to back up manifest. Check permissions.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__euc\">Failed to change back to user directory.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__eud\">Failed to change into install directory.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__eue\">Failed to compute update manifests</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__euf\">Failed to create (filename). Check permissions.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__eug\">Failed to create directories (path too long)</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__euh\">Failed to create TCP client socket.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__eui\">Failed to identify applicable update version.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__euj\">Failed to initialize network layer.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__euk\">Failed to remove (filename). Check permissions.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__eul\">Failed to roll back manifest. Check permissions.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__eum\">Failed to prune directories (path too long)</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__eun\">Transferred more than expected uncompressed size.</a>\n<a class=\"hL\" href=\"#ERRORMES.HLP__euo\">Unknown stat() error occurred</a>\n\n<span class=\"f9\">MZX General Errors</span>\n\n<a class=\"hA\" name=\"ERRORMES.HLP__era\"><span class=\"fA\">Cannot decrypt write-protected world; check permissions</span></a>\n\nThis can be caused by file errors or a hard disk space\nshortage. It can also result from an attempt to overwrite a\nread-only file.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__erb\"><span class=\"fA\">Cannot overwrite the player- move it first</span></a>\n\nThe user attempted to place an object over the player. The\nplayer must be moved from its current location if something\nelse is desired at that location.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__erc\"><span class=\"fA\">Current renderer lacks advanced graphical features; features</span></a>\n<span class=\"fA\">disabled</span>\n\nCertain graphics renderers (overlay2,gp2x) and platforms (NDS)\nare unable to utilize some newer graphical features (i.e. layer\nrendering). If this error message is displayed, these features\nwill not work.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__erd\"><span class=\"fA\">Directory rename failed.</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__erk\"><span class=\"fA\">File rename failed.</span></a>\n\nThe user attempted to rename a directory or file, respectively,\nbut failed. This could be due to trying to change a read-only\nfile, trying to change a locked file, or due to some other\nerror.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ere\"><span class=\"fA\">Error exporting ANSi</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__erf\"><span class=\"fA\">Error exporting SFX file</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__erg\"><span class=\"fA\">Error exporting text</span></a>\n\nThe user attempted to export a file in the relevant format, but\nno file was created. This can be caused by file errors or a hard\ndisk space shortage. It can also result from an attempt to\noverwrite a read-only file or write to a directory without write\npermissions.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__erh\"><span class=\"fA\">Error saving; file/directory may be write protected</span></a>\n\nThis can be caused by file errors or a hard disk space\nshortage. It can also result from an attempt to overwrite a\nread-only file or write to a directory without write\npermissions.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__eri\"><span class=\"fA\">Error swapping to next world</span></a>\n\nA Robotic command, SWAP to WORLD, was issued, but the stated\nworld could not be found or otherwise could not be swapped to,\ndue to version conflicts, file errors, etc.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__erj\"><span class=\"fA\">(Filename) already exists.</span></a>\n\nThe user attempted to create a new file, but a file with that\nname already exists.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__erl\"><span class=\"fA\">Limited/missing extended charset support; some features may not</span></a>\n<span class=\"fA\">work</span>\n\nWhen this error message is displayed, the currently-running\ninstance of MegaZeux cannot fully support extended character\nsets. Worlds that rely on this feature will still run in this\ncase, but without this feature.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__erm\"><span class=\"fA\">Overlay mode is not on (see Board Info)</span></a>\n\nYou cannot edit or copy to the Overlay if it is Off; go to\nBoard Info and turn it to Normal, Static, or Transparent.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ern\"><span class=\"fA\">Save would overwrite older world. Aborted.</span></a>\n\nA save was attempted in the debytecode version of MegaZeux that\nwould overwrite a non-debytecode world file. Due to the present\ninstability of debytecode, this is forcibly prevented.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ero\"><span class=\"fA\">Windowing code bug</span></a>\n\nThis is a critical internal error. Try to isolate the reason for\nthe problem and notify the maintainer(s), as this error\nsignifies a severe bug in MegaZeux.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__erp\"><span class=\"fA\">You can only play this game via a swap from another game</span></a>\n\nThis error occurs when the user tries to play a game that has\nthe option set to disallow normal gameplay. The world can only\nbe played as a swap from another game or in the editor. For\nexample, this may be the second half of another game, or a\nseparate world for showing the introduction.\n\n<span class=\"f9\">MZX Validation Errors</span>\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evc\"><span class=\"fA\">Board @ (hex location): found (#) robots; expected (#)</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__evd\"><span class=\"fA\">Board @ (hex location): found (#) scrolls/signs; expected (#)</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__eve\"><span class=\"fA\">Board @ (hex location): found (#) sensors; expected (#)</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__eva\"><span class=\"fA\">Any extra robots/scrolls/signs were replaced</span></a>\n\nMegaZeux found more instances of the given object than the\nworld file indicated it had. All instances past the expected\namount are tossed out.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evb\"><span class=\"fA\">Board @ (hex location) could not be found</span></a>\n\nThe MegaZeux world is indicating that there should be a board\nat the given location, but it is not actually in the world\nfile.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evf\"><span class=\"fA\">Board @ (hex location) is irrecoverably truncated or corrupt</span></a>\n\nThe board at the given location is corrupt beyond repair and\nwill be replaced with a blank board.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evg\"><span class=\"fA\">Board @ (hex location) is truncated, but could be partially</span></a>\n<span class=\"fA\">recovered</span>\n\nOnly part of the board was found, but what board info remained\nis placed at that location.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evh\"><span class=\"fA\">Board file is from a future version (version)</span></a>\n\nLoad of a board file from a more recent version of MegaZeux was\nattempted. You must use the version of MegaZeux listed or higher\nin order to import this board file.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evi\"><span class=\"fA\">Bytecode file failed validation check</span></a>\n\nAn attempt to load a bytecode file has failed validation checks\nand will not be loaded.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evj\"><span class=\"fA\">Cannot load password protected world</span></a>\n\nSince MegaZeux 2.80, worlds that are password protected have to\nbe stripped of the password to run. This error is seen when the\nuser refuses to decrypt the password-protected file.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evl\"><span class=\"fA\">File doesn't exist</span></a>\n\nAn import of a file was attempted, but the file does not exist.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evk\"><span class=\"fA\">Error importing ANSi</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__evm\"><span class=\"fA\">File is invalid or is not an SFX file</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__evn\"><span class=\"fA\">File is not a board file or is corrupt</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__evo\"><span class=\"fA\">File is not an MZM or is corrupt</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__evp\"><span class=\"fA\">File is not a valid .SAV file or is corrupt</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__evq\"><span class=\"fA\">File is not a valid world file or is corrupt</span></a>\n\nAn import of the given type was halted due to being unrecognized\nas a valid file, or due to corruption.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evr\"><span class=\"fA\">MZM contains runtime robots; dummying out</span></a>\n\nLoad of an MZM containing Robots and saved during a running\ngame was attempted in the editor. The Robots will be replaced\nwith CustomBlock facsimiles, as if the file were loaded as a\nlayer-type MZM.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evs\"><span class=\"fA\">MZM doesn't exist</span></a>\n\nLoading an MZM file was attempted, but the given file does not\nexist.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evt\"><span class=\"fA\">MZM from more recent version (version); dummying out robots</span></a>\n\nLoad of an MZM file with Robots from a more recent version of\nMZX was attempted. The Robots will be replaced with CustomBlock\nfacsimiles, as if the file were loaded as a layer-type MZM.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evu\"><span class=\"fA\">MZM is missing robots or contains corrupt robot</span></a>\n\nThe loaded MZM file indicates that a Robot should be at a\ncertain location of the MZM, but found either no code or\ncorrupt code.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evv\"><span class=\"fA\">Post validation IO error occurred</span></a>\n\nThe given file passed validation, but some other problem is\npreventing it from loading.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evw\"><span class=\"fA\">Robot @ (hex location) could not be found</span></a>\n\nThe board file indicates a Robot should be present at the given\nlocation, but no Robot exists there.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evx\"><span class=\"fA\">Robot @ (hex location) is truncated or corrupt</span></a>\n\nThe Robot at the given location is unrecoverable, and will be\nreplaced with an empty Robot at that location.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__evy\"><span class=\"fA\">.SAV files from newer versions of MZX (version) are not</span></a>\n<span class=\"fA\">supported</span>\n<a class=\"hA\" name=\"ERRORMES.HLP__evz\"><span class=\"fA\">.SAV files from older versions of MZX (version) are not</span></a>\n<span class=\"fA\">supported</span>\n\nThe .SAV file the user attempted to load is a different format\nthan a supported .SAV version and cannot be loaded. This\nconsists of all newer .SAV versions and all .SAV versions\npredating version 2.84.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ev1\"><span class=\"fA\">Scroll @ (hex location) is truncated or corrupt</span></a>\n\nThe Scroll at the given location is unrecoverable, and will be\nreplaced with an empty Scroll at that location.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ev2\"><span class=\"fA\">Sensor @ (hex location) is truncated or corrupt</span></a>\n\nThe Sensor at the given location is unrecoverable, and will be\nreplaced with a Sensor with default attributes at that location.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ev3\"><span class=\"fA\">This world may be password protected. Decrypt it?</span></a>\n\nWorlds saved with password protection (some worlds made before\nMZX2.51s3.2) cannot natively run in MegaZeux. This prompt asks\nthe user if they want to convert the world to a readable format.\nIf Yes is chosen, by default MegaZeux will create and run a\ndecrypted copy of the world in memory or as a temp file. The\noriginal MZX file will remain unchanged. If No is chosen, the\nworld is left alone and MZX keeps any currently-running world as\nits active world. The decryption method can be changed via the\nconfig file.\n\n<a class=\"hL\" href=\"#CONFGINI.HLP__1st\">The Config File</a>\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ev4\"><span class=\"fA\">Unknown error reading from file</span></a>\n\nA file read was attempted, but MZX failed for some undetermined\nreason.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ev5\"><span class=\"fA\">Unknown error writing to file</span></a>\n\nMZX attempted to write to a file, but failed for some\nundertermined reason.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ev6\"><span class=\"fA\">World is from a more recent version (version)</span></a>\n\nThe .MZX file you tried to load or import is from a more\ncurrent version of MegaZeux. You must upgrade MegaZeux to the\ngiven version or higher to play this worldfile.\n\n<span class=\"f9\">MegaZeux Validation Errors (ZIP World Format):</span>\n\n<a class=\"hA\" name=\"ERRORMES.HLP__eza\"><span class=\"fA\">Board # (number) is corrupt</span></a>\n\nThe board with the given number is corrupt beyond repair.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ezb\"><span class=\"fA\">Board # (number) is missing data:</span></a>\n\nThe given board lacks data that its structure indicates it\nshould have.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ezc\"><span class=\"fA\">Robot # (number) contains duplicates on board # (number)</span></a>\n\nA Robot is found in multiple places in the given board's data.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ezd\"><span class=\"fA\">Robot # (number) does not exist on board # (number)</span></a>\n\nA Robot should exist on the given board, according to the board\ndata, but does not.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__eze\"><span class=\"fA\">Robot # (number) exists on board # (number), but was not found</span></a>\n\nA Robot exists on the given board, but the board data for that\nRobot is absent.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ezf\"><span class=\"fA\">Robot # (number) on board # (number) is corrupt</span></a>\n\nThe Robot on the board with the given number is corrupt beyond\nrepair.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ezg\"><span class=\"fA\">Scroll # (number) on board # (number) is corrupt</span></a>\n\nThe Scroll on the board with the given number is corrupt beyond\nrepair.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__ezh\"><span class=\"fA\">Sensor # (number) on board # (number) is corrupt</span></a>\n\nThe Sensor on the board with the given number is corrupt beyond\nrepair.\n\n<span class=\"f9\">MZX Updater Errors</span>\n\n<a class=\"hA\" name=\"ERRORMES.HLP__eua\"><span class=\"fA\">Attempt to invoke self failed!</span></a>\n\nThis error occurs when MZX fails to reload itself after an\nupdate. This signifies a major bug in MegaZeux; please contact\nthe maintainer(s).\n\n<a class=\"hA\" name=\"ERRORMES.HLP__eub\"><span class=\"fA\">Failed to back up manifest. Check permissions.</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__euf\"><span class=\"fA\">Failed to create (filename). Check permissions.</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__euk\"><span class=\"fA\">Failed to remove (filename). Check permissions.</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__eul\"><span class=\"fA\">Failed to roll back manifest. Check permissions.</span></a>\n\nMZX tried to write/delete a file from the working directory,\nbut could not, likely due to the user's MZX working directory\ndisallowing write access.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__euc\"><span class=\"fA\">Failed to change back to user directory.</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__eud\"><span class=\"fA\">Failed to change into install directory.</span></a>\n\nThese errors occur when MZX fails to change active directories\nduring the updating process.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__eue\"><span class=\"fA\">Failed to compute update manifests</span></a>\n\nThis error occurs when the hash check in a manifest file fails.\nThis error can also appear if the download of the manifest file\ntimes out.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__eug\"><span class=\"fA\">Failed to create directories (path too long)</span></a>\n<a class=\"hA\" name=\"ERRORMES.HLP__eum\"><span class=\"fA\">Failed to prune directories (path too long)</span></a>\n\nThis error occurs when the pathnames of the subdirectories\nincluded in or deleted by the selected update are too long.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__euh\"><span class=\"fA\">Failed to create TCP client socket.</span></a>\n\nMZX failed to create a socket. This may be because the last one\ncreated is still in use. This can signify a major bug in\nMegaZeux; please contact the maintainer(s).\n\n<a class=\"hA\" name=\"ERRORMES.HLP__eui\"><span class=\"fA\">Failed to identify applicable update version.</span></a>\n\nMZX could not find an update in the branch the user selected.\nPlease check the update_branch_pin and update_host options in\nconfig.txt and make sure they are correct.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__euj\"><span class=\"fA\">Failed to initialize network layer.</span></a>\n\nMZX's networking code is unable to load. This signifies a major\nbug in MegaZeux; please contact the maintainer(s).\n\n<a class=\"hA\" name=\"ERRORMES.HLP__eun\"><span class=\"fA\">Transferred more than expected uncompressed size.</span></a>\n\nThe updater has sent the user more data than needed. The update\nmay be corrupt.\n\n<a class=\"hA\" name=\"ERRORMES.HLP__euo\"><span class=\"fA\">Unknown stat() error occurred</span></a>\n\nA fatal error outside of the ones mentioned occurred. Please\ncontact the developer(s) if you see this error.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"SENSORSW.HLP\">\n<a class=\"hA\" name=\"SENSORSW.HLP__094\"> </a>\n<p class=\"hC\"><span class=\"f9\">Sensors - What They Are and How to Use Them</span></p>\nSensors have two real purposes. The first is to act as save\npoints. The second is to interact with Robots as a hybrid of\nfloor and object. The first use is simple - just set the board\nto \"Save only on Sensors\", and then the player can only save\nwhen standing on a sensor.\n\nThe second use is more complex, and requires knowledge of\nRobotic. Sensors are like controllable CustomFloors (albeit\nCustomFloors that can be pushed by non-player objects). When\ncreating one, give it a name and a character, then enter the\nname of a Robot with which it will interact. This use of sensors\nis easily replicable through Robotic now, so this use of sensors\nis deprecated. However, if one wants to use sensors, this\ninformation will easily help.\n\nSensors interact with Robots using labels (messages) and SEND\ncommands, just like Robots interact with each other. Any Robot\ncan SEND a message to a sensor, but the sensor will only SEND\nmessages to the Robot stated in its settings. If you entered a\nRobot of ALL, then it will SEND messages to all Robots. Unlike\nRobots, sensors cannot send to a dynamic name; for instance, it\ncannot be set to send to \"&amp;fest&amp;ive\" (whereas a Robot would\nproperly send to \"1ive\" when \"fest\" is 1 and to \"5ive\" when\n\"fest\" is 5).\n\n<p class=\"hC\">Messages TO Sensors</p>\nThe following messages can be sent to a sensor. A sensor can\nreceive messages even when it is beneath the player.\n\n<span class=\"fE\">DIE</span>\n\nThis will cause the sensor to disappear forever.\n\n<span class=\"fE\">CHAR'X'</span>\n\nThis will cause the sensor to change its character to X.\n\n<span class=\"fE\">CHAR###</span>\n\nThis will cause the sensor to change its character to that\nrepresented by the number ###, from 0 to 255.\n\n<span class=\"fE\">COLORxx</span>\n\nThis will cause the sensor to change its color to the color\nrepresented by the code xx, a hexadecimal number from 00 to\nFF. The color coding is the same as used for Robotic commands,\nexcept without the \"c\" character. ?s are not allowed.\n\n<span class=\"fE\">N</span>\n<span class=\"fE\">S</span>\n<span class=\"fE\">E</span>\n<span class=\"fE\">W</span>\n\nThis will cause the sensor to move north, south, east, or west,\nrespectively. If the player is on top of the sensor, the player\nwill move along with it. If the sensor tries to move towards\nthe player, it will instead move beneath. If something pushable\nis in the direction of movement, the sensor will push it while\nmoving if there is room.\n\n<p class=\"hC\">Messages FROM Sensors</p>\nSensors will send Robots the following messages.\n\n<span class=\"fE\">SENSORON</span>\n\nThis is sent when the player steps onto the sensor, when the\nsensor is told to move and it ends up beneath the player, or\nwhen something pushes the player onto the sensor.\n\n<span class=\"fE\">SENSORTHUD</span>\n\nThis is sent when the sensor is told to move, but it is blocked.\n\n<span class=\"fE\">SENSORPUSHED</span>\n\nThis is sent when something pushes the sensor.\n\n<p class=\"hC\">Notes on Sensors</p>\nOnly the player can step onto a sensor; other things will push\nit. The sensor, when moving, will move UNDER the player if in\nthe way, or will take the player with it if the player is on\nthe sensor. Laying a bomb while on a sensor will destroy the\nsensor.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"ROBOTICR.HLP\">\n<a class=\"hA\" name=\"ROBOTICR.HLP__087\"> </a>\n<p class=\"hC\"><span class=\"f9\">Robotic Reference Manual</span></p>\nUse the following help links to see your desired topic(s).\n\n<p class=\"hC\"><span class=\"fC\">1) The Basics</span></p><a class=\"hL\" href=\"#ROBOTSWH.HLP__1st\">Robots - What They Are and How to Use Them</a>\n<a class=\"hL\" href=\"#ROBOTICT.HLP__1st\">Robotic Tutorial</a>\n\n<p class=\"hC\"><span class=\"fC\">2) Command List and Core Concepts</span></p><a class=\"hL\" href=\"#COMMANDR.HLP__1st\">Command Reference</a>\n<a class=\"hL\" href=\"#COMMANDS.HLP__1st\">Command Syntax</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP__1st\">Using the Editor</a>\n<a class=\"hL\" href=\"#THEGLOBL.HLP__gbl\">The Global</a>\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n<a class=\"hL\" href=\"#BUILTINL.HLP__1st\">Built-in Labels</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__1st\">Counters, Built-in Counters and Local Counters</a>\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n<a class=\"hL\" href=\"#COMMANDS.HLP__con\">Conditions</a>\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n<a class=\"hL\" href=\"#BULLETTY.HLP__1st\">Bullet Types</a>\n<a class=\"hL\" href=\"#BADPRACT.HLP__bad\">Robotic Usages That Should be Avoided</a>\n\n<p class=\"hC\"><span class=\"fC\">3) Extended Concepts</span></p><a class=\"hL\" href=\"#PROCESS.HLP__prc\">Cycles and Board Scans - How MZX Processes Robots</a>\n<a class=\"hL\" href=\"#SPRITES.HLP__spr\">Sprites</a>\n<a class=\"hL\" href=\"#EXPRESS.HLP__exp\">Expressions</a>\n<a class=\"hL\" href=\"#SUBROUTE.HLP__sub\">Subroutines</a>\n<a class=\"hL\" href=\"#VLAYER.HLP__vla\">The Vlayer</a>\n<a class=\"hL\" href=\"#SMZXMODE.HLP__095\">Super MZX Modes</a>\n<a class=\"hL\" href=\"#FILEACSS.HLP__fil\">File Access</a>\n<a class=\"hL\" href=\"#MZM.HLP__mzm\">Using MZMs</a>\n<a class=\"hL\" href=\"#TRIG.HLP__tri\">Trigonometric Functions</a>\n<a class=\"hL\" href=\"#PARTIAL.HLP__par\">Partial Character Sets</a>\n<a class=\"hL\" href=\"#CHANGECH.HLP__1st\">CHANGE CHAR ID - The CHAR ID Table</a>\n\n<p class=\"hC\"><span class=\"fC\">4) Legacy Coding Tools</span></p><a class=\"hL\" href=\"#COMMAND2.HLP__pre\">Prefixes</a>\n<a class=\"hL\" href=\"#SENSORSW.HLP__094\">Sensors</a>\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"COMMANDS.HLP\">\n<a class=\"hA\" name=\"COMMANDS.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Command Syntax</span></p>\nCommands in Robotic must conform to a certain syntax:\n\n  COMMAND [parameter] [parameter] ...\n\nCOMMAND is the words or symbol that specifies exactly which\ncommand you are using. Parameters are the values used to\nsupplement the command's function. This help section describes\nand details the various parameters that MegaZeux commands can\ntake.\n\n<span class=\"fE\">\"string\" or \"counter\" or \"label\" or \"Robot\" or \"file\"</span>\n\nStrings are a series of characters surrounded by quotes.\nCounters are strings representing the name of a counter. Labels\nare strings representing the name of a label, a point within a\nRobot. Robots are strings representing the name of a Robot.\nFiles are strings representing a file or folder on disk, always\nincluding the extension in the case of files. (Some string\nexamples: \"Hi\", \"Gems\", \"Label5\", \"*1230 +725\", \"robott.txt\",\netc.)\n\nThe quotes can be left off if the string contains no spaces and\nif the string is not the same as any word found in any command\nor parameter, i.e. is a word that the editor could not mistake\nfor a part of a command. MegaZeux will add the quotes for you if\nthe string meets these criteria. For example, \"fifty\" and \"HONK\"\nwill be auto-completed by MZX, but \"Ammo\", \"N\", \"Goto\", and most\nnotably \"loop\" have to have the quotes typed in manually.\n\n<span class=\"fE\">#</span>\n\nNumbers are allowed to be integers within the range of\n-2147483648 to 2147484647. Numbers can be replaced with a string\nrepresenting the name of a counter at any time. Examples of\nlegal numbers: 3200, \"Stuffs\", -19043, \"Ammo\". They can also be\nrepresented in hexadecimal, using $xxxx format. In this case,\nxxxx can be any number from 0 to FFFF.\n\nAny arithmatic that causes a number to go out of its range will\nnot cap the result at the limit, but will instead cause a number\nwraparound.\n\nNumbers can not directly be allowed to go past the 16-bit\nlimits (-32768 to 32767) in Robotic commands, but this can be\neasily circumvented by using constant expressions. Constant\nexpressions also allow larger hexadecimal numbers and octal\nnumbers; please view the expressions section for more detail.\n\nCertain commands will limit numbers to the range of 0 to 255.\n\n<a class=\"hL\" href=\"#EXPRESS.HLP__exp\">Expressions</a>\n\n<a class=\"hA\" name=\"COMMANDS.HLP__col\"><span class=\"fE\">[color]</span></a>\n\nThe format for colors is cXX, where X is 0-9, A-F, or ?. The\nfirst X represents the background color; the second X represents\nthe foreground color. The numbers and symbols represent the\nfollowing colors, by default:\n\n0 Black       <span class=\"f0\">(color #0)</span>\n1 Blue        <span class=\"f1\">(color #1)</span>\n2 Green       <span class=\"f2\">(color #2)</span>\n3 Cyan        <span class=\"f3\">(color #3)</span>\n4 Red         <span class=\"f4\">(color #4)</span>\n5 Purple      <span class=\"f5\">(color #5)</span>\n6 Brown       <span class=\"f6\">(color #6)</span>\n7 Lt. Gray    <span class=\"f7\">(color #7)</span>\n8 Dk. Gray    <span class=\"f8 bF\">(color #8)</span>\n9 Lt. Blue    <span class=\"f9\">(color #9)</span>\n10 Lt. Green  <span class=\"fA\">(color #10)</span>\n11 Lt. Cyan   <span class=\"fB\">(color #11)</span>\n12 Lt. Red    <span class=\"fC\">(color #12)</span>\n13 Lt. Purple <span class=\"fD\">(color #13)</span>\n14 Yellow     <span class=\"fE\">(color #14)</span>\n15 White      <span class=\"fF\">(color #15)</span>\n? Any color/No change in color\n\nThe UI uses its own set of protected colors to prevent general\ncolor edits from harming usability.\n\nThe use of ? is not always a logical option for some commands.\nWhen used, it signifies to replace it with the current color or\nignore that part of the color for that command (or if used in\nIF statements, signifies to accept any value in that field).\nColors may be selected from a menu within the Robot editor\nusing F2. Colors can also be replaced with the name of a\ncounter at any time. In this case, the value of the counter is\nBK*16+FG, where BK and FG are 0-15. To use ?, use the following\nvalues:\n\n   256+FG = c?X\n   272+BK = cX?\n   288    = c??\n\nA color of c?? will be inserted into commands when you do not\nput down anything for the color. Examples of legal colors: cF9,\nc02, c?5, c??, \"color_counter\".\n\nWhile detecting colors of ?? is generally an accepted practice,\nit's extremely discouraged to put colors of ?? or change to\nthem, with the notable exception of placing Sprites.\n\n<a class=\"hL\" href=\"#BADPRACT.HLP__bad\">Robotic Usages That Should be Avoided</a>\n\n<span class=\"fE\">[char]</span>\n\nCharacters (\"chars\" for short) are single characters surrounded\nby apostrophes, e.g. 'X'. X can be any character in the main\ncharacter set. A character can be selected from a menu in the\nRobot editor using F3. Numbers and counters can be used to\nrepresent characters as well. Number values range from 0 to 255\n(other values will wrap around to be 0-255). Examples of legal\ncharacters - 'a', '&#xE0B1;', 20, \"Count\". Characters can also\nrepresent numbers, so typing in commands like INC \"health\" '5'\nwill most likely yield _much_ different results than what was\nlikely intended. However, for some usages this use of chars is\nvastly superior to any other method (such as when reading from\nthe overlay onto strings).\n\nCertain characters must be inputted in specific ways to avoid\nproblems with Robotic:\n\n  \\0 for character 0\n  \\t for tab (character 9)\n  \\n for newline (character 10)\n  \\r for carriage return (character 13)\n  \\\" for quotation mark\n  \\\\ for backslash\n\nMZX handles these conversions for you when using F3 to select\na character from the current character set.\n\nThe commands that can manipulate extended char sets (CHAR EDIT,\nCOPY CHAR, SCROLL CHAR, FLIP CHAR) will directly accept the full\n16-bit integer range. (Numbers outside of the proper range of\n0-3839 will still display as inputted, but will internally\nwrap around that range.)\n\n<a class=\"hA\" name=\"COMMANDS.HLP__dir\"><span class=\"fE\">[dir]</span></a>\n\nDirections are used to denote a direction on the board. A\ndirection is one of the following:\n\n  NORTH (or N or UP)\n  SOUTH (or S or DOWN)\n  EAST (or E or RIGHT)\n  WEST (or W or LEFT)\n  IDLE\n  NODIR\n  ANYDIR\n  RANDNS\n  RANDEW\n  RANDNE\n  RANDNB\n  RANDB\n  SEEK\n  FLOW\n  RANDANY\n  UNDER (or BENEATH)\n\nThe four cardinal directions (N, S, E, W) are self-explanatory.\nBelow are descriptions of the other directions.\n\n<span class=\"fB\">IDLE</span>\n\nNo direction, as in the absence of any direction. Used with:\n\n<a class=\"hL\" href=\"#COMMANDR.HLP___w5\">WALK [dir]</a>\n\n<span class=\"fB\">NODIR</span>\n\nFor condition checks only. If no direction matches the\ncondition, the condition is met.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__con\">Conditions</a>\n\n<span class=\"fB\">ANYDIR</span>\n\nFor condition checks only. If any direction matches the\ncondition, the condition is met.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__con\">Conditions</a>\n\n<span class=\"fB\">RANDNS</span>\n\nRandomly either NORTH or SOUTH.\n\n<span class=\"fB\">RANDEW</span>\n\nRandomly either EAST or WEST.\n\n<span class=\"fB\">RANDNE</span>\n\nRandomly either NORTH or EAST.\n\n<span class=\"fB\">RANDNB</span>\n\nRandomly any direction where the Robot is not blocked by\nsomething.\n\n<span class=\"fB\">RANDB</span>\n\nRandomly any direction where the Robot is blocked.\n\n<span class=\"fB\">SEEK</span>\n\nThe direction closest to the player. If the player is on a\ndiagonal and is equally far away from the Robot vertically and\nhorizontally, the direction will randomly be one of the two\ndirections comprising the diagonal.\n\n<span class=\"fB\">FLOW</span>\n\nThe direction that the Robot is currently walking.\n\n<span class=\"fB\">RANDANY</span>\n\nRandomly one of NORTH, SOUTH, EAST, or WEST.\n\n<span class=\"fB\">UNDER</span>\n\nThe direction signifying whatever is BENEATH something, such\nas floors. Often used with:\n\n<a class=\"hL\" href=\"#COMMAND2.HLP___l1\">LAYBOMB [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l2\">LAYBOMB HIGH [dir]</a>\n\nMost directions can be used with the following modifying\nprefixes:\n\n<span class=\"fB\">OPP</span>\n\nThe opposite direction. NORTH becomes SOUTH, etc.\n\n<span class=\"fB\">CW</span>\n\nThe direction clockwise of the named direction. NORTH becomes\nEAST, etc.\n\n<span class=\"fB\">RANDP</span>\n\nRandomly a direction perpendicular to the named direction.\nNORTH becomes EAST or WEST, etc.\n\n<span class=\"fB\">RANDNOT</span>\n\nRandomly any direction OTHER than the given direction.\n\n<span class=\"fE\">[thing]</span>\n\nThe name of any object from the editor, other than the player.\nUse the name from the object list, minus any punctuation or\nspaces. Examples of legal things: Gem, CustomFloor, LitBomb,\nRobot.\n\nThere are some special cases:\n * OpenGate will place an already-open gate. The parameter field\n  determines how long it remains open (ranging from p00 for 1\n  cycle, up to pff for 256 cycles).\n * Lazer will place a single piece of lazer wall. The parameter\n  field determines both initial lazer character and duration.\n * Sprite and Sprite_Colliding relate to sprites, while\n  Image_File relates to MZMs. These are covered in their own\n  sections.\n\n<span class=\"fE\">[param]</span>\n\nA code representing the settings for a thing. (see above) The\nformat is p# or p??. # is a hexadecimal code from 0 to FF. ??\nrepresents settings that you don't care about or shouldn't\nchange. To enter parameter codes easily, use F4 in the Robot\neditor after you have typed in an object name. p?? will be\ninserted automatically any time you have a thing without a\nparameter. Counters can also be used as parameters, as can\ndecimal numbers; to insert decimal numbers, place the number\nin the proper location, but without the \"p\" before it. A value\nof 256 represents p??.\n\np?? in an IF command will allow that command to accept all\nparams.\n\n<span class=\"fE\">[item]</span>\n\nOne of the following: TIME, SCORE, GEMS, AMMOS, LIVES, LOBOMBS,\nHIBOMBS, COINS, HEALTHS. Note the plural format; this\ndistinguishes items from things.\n\n<span class=\"fE\">!&lt;&gt;=</span>\n\nA conditional for comparing two numbers, counters or strings:\n\n= or ==         Equal to\n&lt;               Less than\n&gt;               Greater than\n&lt;= or =&lt;        Less than or equal to\n&gt;= or =&gt;        Greater than or equal to\n!= or &lt;&gt; or &gt;&lt;  Not equal to\n\n<a class=\"hA\" name=\"COMMANDS.HLP__con\"><span class=\"fE\">[condition]</span></a>\n\nA word, sometimes followed by a direction, signifying a certain\ncondition. Use in IF [condition] and IF NOT [condition] commands\nto test whether a condition is currently present.\n\n<span class=\"fB\">WALKING [dir]</span>\n\nTests whether the Robot is currently walking in a given\ndirection. IDLE, NODIR, and ANYDIR are allowed here.\n\n<span class=\"fB\">SWIMMING</span>\n\nTests whether the Robot is currently in water.\n\n<span class=\"fB\">FIREWALKING</span>\n\nTests whether the Robot is currently in either lava or fire.\n\n<span class=\"fB\">TOUCHING [dir]</span>\n\nTests whether the player is next to the Robot in the given\ndirection. NODIR and ANYDIR are allowed here.\n\n<span class=\"fB\">BLOCKED [dir]</span>\n\nTests whether the Robot is blocked by something in the given\ndirection. NODIR and ANYDIR are allowed here. This condition\nhas a special mode: if the IF command has a REL PLAYER prefix\nbefore it, it will instead check next to the PLAYER for being\nblocked. If the IF command has a REL COUNTERS prefix before\nit, it will instead check next to the position pointed to by\nthe counters XPOS and YPOS.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP__pre\">REL COUNTERS</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r2\">REL PLAYER</a>\n\n<span class=\"fB\">ALIGNED</span>\n\nTests whether the player is aligned with the Robot either\nhorizontally or vertically.\n\n<span class=\"fB\">ALIGNEDNS</span>\n<span class=\"fB\">ALIGNEDEW</span>\n\nTests whether the player is aligned with the Robot, but only\ntests on the horizontal (EW) or vertical (NS) axis, not both\nat once.\n\n<span class=\"fB\">LASTSHOT [dir]</span>\n\nTests whether the last direction the Robot was shot in was\nthe given direction. For example, if the Robot had just been\nshot on the north side, then LASTSHOT NORTH would be true.\nAll bullet types are valid for this condition. NODIR/ANYDIR\nare not valid for this condition.\n\n<span class=\"fB\">LASTTOUCH [dir]</span>\n\nSimilar to the above; tests whether the last direction the\nRobot was touched by the player is the given direction.\nNODIR/ANYDIR are not valid for this condition.\n\n<span class=\"fB\">RIGHTPRESSED</span>\n<span class=\"fB\">LEFTPRESSED</span>\n<span class=\"fB\">UPPRESSED</span>\n<span class=\"fB\">DOWNPRESSED</span>\n<span class=\"fB\">SPACEPRESSED</span>\n<span class=\"fB\">DELPRESSED</span>\n\nTests whether the indicated key is currently being held down.\n\n<span class=\"fB\">MUSICON</span>\n\nTests whether digitized music and sound effects are currently\non.\n\n<span class=\"fB\">PCSFXON</span>\n\nTests whether PC speaker sound effects are currently on.\n\n<span class=\"fE\">Miscellaneous</span>\n\nWhen typing in a command, the following symbols and words, as\nwell as spaces, can be used freely. They have no effect on the\nfinal command and are only used to clarify its meaning.\n\n, (comma)\n; (semicolon)\nA\nAN\nAND\nAS\nAT\nBY\nELSE\nFOR\nFROM\nINTO\nIS\nOF\nTHE\nTHEN\nTHERE\nTHROUGH\nTHRU\nTO\nWITH\n\nThese will disappear from the line if \"disassemble_extras\" is\nset to 0 in the config file. See the config file for further\ndetails.\n\n<a class=\"hL\" href=\"#CONFGINI.HLP__1st\">The Config File</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"STRINGS.HLP\">\n<a class=\"hA\" name=\"STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n\n<p class=\"hC\">Simple Strings</p>\nGenerally, a string is a series of symbols within quotes, such\nas: \"BUG\", \"Hi there!\", and even \"&#xE0C5;&#xE081;EA9 _&#xE00F;&#xE0B1;&#xE0C7;\" or \"\". There are\ntwo special aspects or features of strings that may be useful:\ncolor coding, and counter interpolation.\n\n<p class=\"hC\">Color Codes</p>\nUsed in Robotic commands *, %, ?, and &amp;, color codes are used to\nplace colors in strings. There are two color symbols: ~ for\nchanging foreground, and @ for changing background. These should\nbe followed by one of these characters:\n\n    0 Black (color 0)        8 Dk. Gray (color 8)\n    1 Blue (color 1)         9 Lt. Blue (color 9)\n    2 Green (color 2)        A Lt. Green (color 10)\n    3 Cyan (color 3)         B Lt. Cyan (color 11)\n    4 Red (color 4)          C Lt. Red (color 12)\n    5 Purple (color 5)       D Lt. Purple (color 13)\n    6 Brown (color 6)        E Yellow (color 14)\n    7 Lt. Gray (color 7)     F White (color 15)\n\nTo show a ~, use ~~. To show a @, use @@.\n\n<p class=\"hC\">Counter Interpolation</p>\nStrings can show the values of counters by surrounding the\ncounter name with &amp;s. This is called counter interpolation. For\nexample, if the player has 55 gems, \"You have &amp;GEMS&amp; gems.\" will\nbecome \"You have 55 gems.\" Use &amp;&amp; to show a &amp;. This feature is\navailable in nearly ALL Robotic commands that use strings,\nincluding in the names of OTHER counters. (The sole exceptions\nare in label names and in strings for the PLAY commands.) This\ncan be used to simulate array-like constructs.\n\n<p class=\"hC\">Robotic Usage of Strings</p>\nIn Robotic, strings can be incredibly powerful. They primarily\nhold text information, such as names. (They can hold a single\nnumber like counters can, but counters are by far better-suited\nfor number use). A Robotic string is any counter prefixed by a $\nsign (e.g. \"$string\").\n\nCertain characters must be inputted in specific ways to avoid\nproblems with Robotic:\n\n  \\0 for character 0 (this won't be parsed correctly in strings)\n  \\t for tab (character 9)\n  \\n for newline (character 10)\n  \\r for carriage return (character 13)\n  \\\" for quotation mark\n  \\\\ for backslash\n\n<p class=\"hC\">Robotic Usage - Setting Strings</p>\n<span class=\"fB\">SET \"$string\" to \"text\"</span>\n\nSets the contents of the given string to the given line of text.\n\n<span class=\"fB\">SET \"$string\" to \"$string2\"</span>\n\nSets the contents of the given string to the contents of another\nstring.\n\n<span class=\"fB\">SET \"$string\" to #</span>\n\nSets the contents of the given string to an integer value.\n\n<p class=\"hC\">Robotic Usage - Outputting Strings</p>\nAnywhere you can output a counter, you can output a string.\nSimply encase the string name in ampersands. E.G.:\n\n<span class=\"fE\">SET \"$woohoo\" to \"My favorite song!\"</span>\n<span class=\"fE\">* \"&amp;$woohoo&amp;\"</span>\n\nWould output \"My favorite song!\" in the message line.\n\nThere are several special things you can set strings to for\ncertain functions. These are listed in the Counters section.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__stc\">String Counters</a>\n\n<p class=\"hC\">Robotic Usage - Comparing Strings</p>\nString comparisons are case-insensitive unless stated otherwise.\nAllowed comparisons are to a line of text, to another string and\nto a number. However, the string is required to be the first\nitem, and string counters such as \"MOD_NAME\", \"INPUT\", etc. are\nnot valid comparisons.\n\nStrings can be compared through these commands:\n\n<span class=\"fB\">IF \"$string\" = \"value\" then \"label\" (equality)</span>\n<span class=\"fB\">IF \"$string\" != \"value\" then \"label\" (inequality)</span>\n<span class=\"fB\">IF \"$string\" &gt; \"value\" \"label\" (greater than)</span>\n<span class=\"fB\">IF \"$string\" &lt; \"value\" \"label\" (less than)</span>\n<span class=\"fB\">IF \"$string\" &gt;= \"value\" \"label\" (greater than or equal to)</span>\n<span class=\"fB\">IF \"$string\" &lt;= \"value\" \"label\" (less than or equal to)</span>\n<span class=\"fB\">IF \"$string\" === \"value\" then \"label\" (case-sensitive equality)</span>\n\nStrings can also be compared using wildcards. There are two\nspecial wildcard characters: the ? character in the value being\ncompared against matches any one character, while the %\ncharacter matches any number of any characters (including zero).\nThe wildcard commands are:\n\n<span class=\"fB\">IF \"$string\" ?= \"value\" then \"label\"</span>\n<span class=\"fB\">IF \"$string\" ?== \"value\" then \"label\" (case-sensitive)</span>\n\nDue to their use as a token, actual ? and % characters must be\nescaped to be checked for in wildcard comparisons. Use \\\\? and\n\\\\% to have values that check for literal use of these\ncharacters.\n\n<p class=\"hC\">Robotic Usage - Manipulating Strings</p>\nFirstly, strings can be offset and limited (\"spliced\").\n\n<span class=\"fB\">$strname#X</span><span class=\"fF\"> will cap the string to X characters in length.</span>\n\n<span class=\"fB\">$strname+X</span><span class=\"fF\"> will offset the start of the string by X</span>\ncharacters. Using a negative number will offset the start of\nthe string backwards from the end of the string by X characters,\nwith -1 as the end character. (e.g. \"$str+-2\" would offset the\nstring \"str\" at two characters from its end.)\n\n<span class=\"fB\">$strname+X#Y</span><span class=\"fF\"> will offset the string by X characters and limit</span>\nthe string to Y characters in length.\n\nNormally string splicing is used to read a given selection of a\nstring; however, it can also be used for writing to parts of\nstrings. When you set an offset when setting a string, the\nlength of the string will not decrease, so you'll only\nmanipulate a portion of the string. For instance:\n\n<span class=\"fB\">set \"$str\" \"hello\"</span>\n<span class=\"fB\">set \"$str+1\" \"LOL\"</span>\n\nWill cause $str to have the value \"hLOLo\".\n\nWriting to parts of strings with an offset also changes how the\nstring limit works; any length cap will limit how much of the\nspliced-in material is inserted instead of limiting the string\nlength in total. For instance:\n\n<span class=\"fB\">set \"$str\" \"Baby steps.\"</span>\n<span class=\"fB\">set \"$str+0#4\" \"Long journey.\"</span>\n\nWill cause $str to have the value \"Long steps.\"\n\n<span class=\"fB\">$strname.X</span><span class=\"fF\"> will manipulate the Xth character in a string</span>\n(starting from 0). When used for output, it will give the\ncharacter value of the given character in a string. Any invalid\nnumbers (past the terminator) are given a value of 0. This\ncommand works well with char immediates:\nE.G. SET \"$string.0\" 'S' . When setting the character in a\nstring it will make the string length one larger than X, so be\ncareful when using very large values.\nUsing negative numbers will manipulate using the end of the\nstring as its basis: -X will manipulate the Xth character from\nthe end of the string, with -1 being the end character.\n\n<span class=\"fB\">$strname.X#Y</span><span class=\"fF\"> will manipulate characters in the string from the</span>\nXth character (starting from 0), up to character (X+Y-1). Y can\nbe a number from 1 to 4; all other values for Y are clamped to\nfit this range. The string given or output acts as a number 8*Y\nbits in length.\n\n<span class=\"fB\">$str.length</span><span class=\"fF\"> will give the length of the given string.</span>\n\nSecondly, strings can be clipped, appended or written to from\nthe board or overlay.\n\n<span class=\"fB\">INC \"$string\" \"$string2\"</span>\n<span class=\"fB\">INC \"$string\" \"text\"</span>\n\nThese two commands append another string's contents or text,\nrespectively, to the end of a given string.\n\n<span class=\"fB\">DEC \"$string\" #</span>\n\nThis command clips the given number of characters from the end\nof a string. # is treated as unsigned so using negative numbers\nwill probably cause the length to go down to zero.\n\n<span class=\"fB\">COPY BLOCK x y w h \"$string\" t</span>\n<span class=\"fB\">COPY OVERLAY BLOCK x y w h \"$string\" t</span>\n\nThese commands copy information from the board or overlay,\nrespectively, to a given string. X and Y are the coordinates\nof the upper-left corner of the block; W and H are its width\nand height; T is the terminating character. For T, one can put\nin a counter with the param value of the desired terminator\ncharacter or a char immediate in the form 'c' (not just the\ncharacter itself). If \"t\" is 0 there will be no terminator.\nThe letters are added from left to right and up to down until\nthe terminator character is reached, or until the end of the\ngiven block is reached. If x and y begin with a pound sign (#),\nthe string will instead be read from the vlayer.\nPlease use this method of scanning strings instead of\nBOARD_SCAN.\n\n<p class=\"hC\">Robotic Usage - File Access</p>\nFinally, strings can be used for loading and saving several MZX\nfile formats. The following formats can be loaded from a string:\n\n-Palettes with <span class=\"fB\">LOAD PALETTE \"$string\"</span>\n-Charsets with <span class=\"fB\">LOAD CHAR SET \"$string\"</span>\n-Robots with <span class=\"fB\">SET \"$string\" \"LOAD_ROBOT\"</span>\n-MZMs with <span class=\"fB\">PUT \"@$string\" image_file pNN x y</span>\n\nRobots can be saved into a string with this command:\n<span class=\"fB\">SET \"$string\" \"SAVE_ROBOT\"</span>\n\nMZMs can be saved into a string via these methods:\n\n<span class=\"fB\">COPY BLOCK x y w h \"@$string\" p</span>\n<span class=\"fB\">COPY OVERLAY BLOCK x y w h \"@$string\" p</span>\n<span class=\"fB\">COPY (OVERLAY) BLOCK \"#x\" \"#y\" w h \"@$string\" m</span>\n\nString splicing is valid for string file operations.\n\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"COMMANDR.HLP\">\n<a class=\"hA\" name=\"COMMANDR.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Command Reference</span></p>\nThe following tables are lists of all Robotic commands in\nMegaZeux. The commands are listed as help links and will jump\nto that specific command. After the tables are the actual\ncommand descriptions. Note that because of its size, the command\nreference is split into two parts.\n\nAny command that (typically) ends a cycle will be marked by a\n[<span class=\"fD\">!</span><span class=\"fF\">] symbol. For more precise information, view</span>\ncycles_and_commands.txt in the documentation.\n\nThe command listing is arranged by alphabetical order and by\ncommand type. Select the respective links to jump to that\narrangement.\n\n<a class=\"hL\" href=\"#COMMANDR.HLP__tcl\">Type-Based Command Listing</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__acl\">Alphabetical Command Listing</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tcl\"> </a>\n<p class=\"hC\"><span class=\"f9\">Type-Based Command Listing</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tc0\">Flow and Branching Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tc1\">General Color and Character Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tc2\">Item Color and Character Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tc3\">Counter and String Manipulation Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tc4\">Music and Sound Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tc5\">Textbox and Message Row Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tc6\">Board Placement and Copy Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tc7\">Overlay Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tc8\">View Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tc9\">Other Board Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tca\">Player Control and Item Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tcb\">Self Movement and Attack Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tcc\">Other Self-Oriented Commands</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP__tcd\">Miscellaneous Commands</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tc0\"> </a>\n<p class=\"hC\"><span class=\"fC\">Flow and Branching Commands</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP____6\">: \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___w1\">WAIT # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e1\">END [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___g5\">GOTO \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___g6\">GOTO \"#return\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___g7\">GOTO \"#top\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sB\">SEND # # \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sC\">SEND \"Robot\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sD\">SEND [dir] \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sE\">SEND [dir] PLAYER \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___i1\">IF \"counter\" !&lt;&gt;= # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i2\">IF [condition] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iB\">IF NOT [condition] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i3\">IF # # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iC\">IF PLAYER # # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i7\">IF ALIGNEDROBOT \"Robot\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i5\">IF [color] [thing] [param] # # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i6\">IF [color] [thing] [param] [dir] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i4\">IF [dir] PLAYER [color] [thing] [param] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iA\">IF NOT [color] [thing] [param] [dir] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i8\">IF ANY [color] [thing] [param] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i0\">IF NO [color] [thing] [param] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iO\">IF \"$string\" (equality) # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iK\">IF \"$string\" (equality) \"text\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iL\">IF \"$string\" (equality) \"$string2\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iG\">IF cNN Sprite_Colliding pNN # # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iH\">IF c?? Sprite pNN # # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iD\">IF STRING \"string\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iE\">IF STRING MATCHES \"string\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iF\">IF STRING NOT \"string\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i9\">IF FIRST STRING \"string\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___lA\">LOCKSELF</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___u3\">UNLOCKSELF</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___z1\">ZAP \"label\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___rA\">RESTORE \"label\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP____0\">| \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___lB\">LOOP #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___lC\">LOOP START</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___a1\">ABORT LOOP</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___a2\">ASK \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cP\">CYCLE # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___w2\">WAIT MOD FADE [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___w3\">WAIT PLAY [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tc1\"> </a>\n<p class=\"hC\"><span class=\"fC\">General Color and Character Commands</span></p>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l5\">LOAD PALETTE \"file\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l4\">LOAD CHAR SET \"file\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sK\">SET COLOR # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c0\">CHAR EDIT [char] # # # # # # # # # # # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cK\">COPY CHAR [char] [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s4\">SCROLL CHAR [char] [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___f3\">FLIP CHAR [char] [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cF\">COLOR INTENSITY # PERCENT</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cG\">COLOR INTENSITY # # PERCENT</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cD\">COLOR FADE OUT</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cE\">COLOR FADE IN</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tc2\"> </a>\n<p class=\"hC\"><span class=\"fC\">Item Color and Character Commands</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p0\">PLAYER CHAR [char]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p9\">PLAYER CHAR [dir] [char]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pA\">PLAYERCOLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c3\">CHANGE CHAR ID # [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b9\">BULLETCOLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b0\">BULLETE [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___bA\">BULLETN [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___bB\">BULLETS [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___bC\">BULLETW [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___p4\">PLAYER BULLETCOLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___p5\">PLAYER BULLETE [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___p6\">PLAYER BULLETN [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___p7\">PLAYER BULLETS [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___p8\">PLAYER BULLETW [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___n1\">NEUTRAL BULLETCOLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___n2\">NEUTRAL BULLETE [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___n3\">NEUTRAL BULLETN [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___n4\">NEUTRAL BULLETS [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___n5\">NEUTRAL BULLETW [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___e0\">ENEMY BULLETCOLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eA\">ENEMY BULLETE [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eB\">ENEMY BULLETN [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eC\">ENEMY BULLETS [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eD\">ENEMY BULLETW [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m2\">MISSILECOLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c7\">CHANGE THICK ARROW CHAR [dir] [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c8\">CHANGE THIN ARROW CHAR [dir] [char]</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tc3\"> </a>\n<p class=\"hC\"><span class=\"fC\">Counter and String Manipulation Commands</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sF\">SET \"counter\" #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sG\">SET \"counter\" RANDOM # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___iI\">INC \"counter\" #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___iJ\">INC \"counter\" RANDOM # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d1\">DEC \"counter\" #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d2\">DEC \"counter\" RANDOM # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___mC\">MULTIPLY \"counter\" #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d7\">DIVIDE \"counter\" #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___m9\">MODULO \"counter\" #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d8\">DOUBLE \"counter\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___h1\">HALF \"counter\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sH\">SET \"$string\" \"text\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sI\">SET \"$string1\" \"$string2\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sJ\">SET \"$string\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iM\">INC \"$string\" \"text\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iN\">INC \"$string\" \"$string2\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___dA\">DEC \"$string\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iP\">INPUT STRING \"string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cB\">CLIP INPUT</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tc4\"> </a>\n<p class=\"hC\"><span class=\"fC\">Music and Sound Commands</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP___m3\">MOD \"file\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___m8\">MOD \"*\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___v3\">VOLUME #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m4\">MOD FADE # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m5\">MOD FADE IN \"file\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m6\">MOD FADE OUT</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___j1\">JUMP MOD ORDER #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___s1\">SAM # \"file\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p2\">PLAY \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p3\">PLAY SFX \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___w4\">WAIT PLAY \"string\" [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sO\">SFX #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c6\">CHANGE SFX # \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e2\">END MOD</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e3\">END PLAY</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e4\">END SAM</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m7\">MOD SAM # #</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tc5\"> </a>\n<p class=\"hC\"><span class=\"fC\">Textbox and Message Row Commands</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP____9\">[ \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____1\">% \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____2\">&amp; \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____8\">? \"label\" \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____7\">? \"counter\" \"label\" \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____3\">* \"string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m1\">MESSAGE ROW #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sN\">SET MESG COLUMN #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c1\">CENTER MESG</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cA\">CLEAR MESG</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___e7\">ENABLE MESG EDGE</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___d5\">DISABLE MESG EDGE</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s5\">SCROLLARROW COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s6\">SCROLLBASE COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s7\">SCROLLCORNER COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s8\">SCROLLPOINTER COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s9\">SCROLLTITLE COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___a2\">ASK \"string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iP\">INPUT STRING \"string\"</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tc6\"> </a>\n<p class=\"hC\"><span class=\"fC\">Board Placement and Copy Commands</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pC\">PUT [color] [thing] [param] # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pD\">PUT [color] [thing] [param] [dir]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pF\">PUT [color] [thing] [param] [dir] PLAYER</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pI\">PUT \"@FILENAME.XXX\" Image_File [param] # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pJ\">PUT [color] Sprite [param] # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cH\">COPY # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cI\">COPY [dir] [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cJ\">COPY BLOCK # # # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cQ\">COPY BLOCK # # # # \"@filename\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cS\">COPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"@filename\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cT\">COPY BLOCK # # # # \"$string\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cV\">COPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"$string\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cW\">COPY BLOCK # # # # \"#x\" \"#y\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cY\">COPY BLOCK \"#x\" \"#y\" # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cAA\">COPY (OVERLAY) BLOCK \"#x1\" \"#y1\" # # \"#x2\" \"#y2\"</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tc7\"> </a>\n<p class=\"hC\"><span class=\"fC\">Overlay Commands</span></p>\n<a class=\"hL\" href=\"#COMMAND2.HLP___pE\">PUT [color] [char] OVERLAY # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___w7\">WRITE OVERLAY [color] \"string\" # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cL\">COPY OVERLAY BLOCK # # # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cR\">COPY OVERLAY BLOCK # # # # \"@filename\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cU\">COPY OVERLAY BLOCK # # # # \"$string\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cX\">COPY OVERLAY BLOCK # # # # \"#x\" \"#y\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cZ\">COPY OVERLAY BLOCK \"#x\" \"#y\" # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c5\">CHANGE OVERLAY [color] [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c4\">CHANGE OVERLAY [color] [char] [color] [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___o2\">OVERLAY ON</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___o3\">OVERLAY STATIC</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___o4\">OVERLAY TRANSPARENT</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tc8\"> </a>\n<p class=\"hC\"><span class=\"fC\">View Commands</span></p>\n<a class=\"hL\" href=\"#COMMAND2.HLP___v1\">VIEWPORT # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___v2\">VIEWPORT SIZE # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s0\">SCROLLVIEW [dir] #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sA\">SCROLLVIEW POSITION # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r0\">RESETVIEW</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l0\">LOCKSCROLL</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___u2\">UNLOCKSCROLL</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sL\">SET EDGE COLOR [color]</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tc9\"> </a>\n<p class=\"hC\"><span class=\"fC\">Other Board Commands</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP___c2\">CHANGE [color] [thing] [param] [color] [thing] [param]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m0\">MOVE ALL [color] [thing] [param] [dir] [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b7\">BOARD [dir] \"string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b8\">BOARD [dir] NONE</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___a3\">AVALANCHE</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___d6\">DISABLE SAVING</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___e8\">ENABLE SAVING</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___e9\">ENABLE SENSORONLY SAVING</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tca\"> </a>\n<p class=\"hC\"><span class=\"fC\">Player Control and Item Commands</span></p>\n<a class=\"hL\" href=\"#COMMAND2.HLP___g1\">GIVE # [item]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___t1\">TAKE # [item]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___t2\">TAKE # [item] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___g2\">GIVEKEY [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___g3\">GIVEKEY [color] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___t3\">TAKEKEY [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___t4\">TAKEKEY [color] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___t6\">TRADE # [item] # [item] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l6\">LOCKPLAYER</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l7\">LOCKPLAYER ATTACK</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l8\">LOCKPLAYER EW</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l9\">LOCKPLAYER NS</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___u1\">UNLOCKPLAYER</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___t5\">TELEPORT PLAYER \"string\" # # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___mA\">MOVE PLAYER [dir] [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___mB\">MOVE PLAYER [dir] \"label\" [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pG\">PUT PLAYER # # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pH\">PUT PLAYER [dir] [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e6\">ENDLIFE</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e5\">ENDGAME</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___f2\">FILLHEALTH</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sM\">SET MAXHEALTH #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s2\">SAVE PLAYER POSITION</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s3\">SAVE PLAYER POSITION #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___rB\">RESTORE PLAYER POSITION [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___rC\">RESTORE PLAYER POSITION # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___rD\">RESTORE PLAYER POSITION # DUPLICATE SELF [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eE\">EXCHANGE PLAYER POSITION [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eF\">EXCHANGE PLAYER POSITION # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eG\">EXCHANGE PLAYER POSITION # DUPLICATE SELF [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b6\">BLIND #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___f1\">FIREWALKER #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___w6\">WIND #</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tcb\"> </a>\n<p class=\"hC\"><span class=\"fC\">Self Movement and Attack Commands</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP____5\">/ \"string\" [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___w5\">WALK [dir]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___g4\">GO [dir] # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___g8\">GOTOXY # # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p1\">PERSISTENT GO \"string\" [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___t7\">TRY [dir] \"label\" [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sP\">SHOOT [dir]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sT\">SPITFIRE [dir]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sQ\">SHOOTMISSILE [dir]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sR\">SHOOTSEEKER [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l1\">LAYBOMB [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l2\">LAYBOMB HIGH [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l3\">LAZERWALL [dir] #</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tcc\"> </a>\n<p class=\"hC\"><span class=\"fC\">Other Self-Oriented Commands</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP___c9\">CHAR [char]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cC\">COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d3\">DIE</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d4\">DIE ITEM</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___eH\">EXPLODE #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d9\">DUPLICATE SELF # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d0\">DUPLICATE SELF [dir]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cM\">COPYROBOT \"Robot\" [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cN\">COPYROBOT # # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cO\">COPYROBOT [dir] [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___4b\">. \"@string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___pB\">PUSH [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___rE\">ROTATECW</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___rF\">ROTATECCW</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sW\">SWITCH [dir] [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___o1\">OPEN [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b2\">BECOME NONLAVAWALKER</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b3\">BECOME NONPUSHABLE</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b4\">BECOME LAVAWALKER</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b5\">BECOME PUSHABLE</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___b1\">BECOME [color] [thing] [param]</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__tcd\"> </a>\n<p class=\"hC\"><span class=\"fC\">Miscellaneous Commands</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP____4\">. \"string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sV\">SWAP WORLD \"file\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c3\">CHANGE CHAR ID # [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sU\">STATUS COUNTER # \"counter\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___f4\">FREEZETIME #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sS\">SLOWTIME #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP__pre\">REL COUNTERS</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r2\">REL PLAYER</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r3\">REL SELF</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r4\">REL COUNTERS FIRST</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r5\">REL PLAYER FIRST</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r6\">REL SELF FIRST</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r7\">REL COUNTERS LAST</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r8\">REL PLAYER LAST</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r9\">REL SELF LAST</a>\n<a class=\"hA\" name=\"COMMANDR.HLP__acl\"> </a>\n<p class=\"hC\"><span class=\"f9\">Alphabetical Command Listing</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP____1\">% \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____2\">&amp; \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____3\">* \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____4\">. \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___4b\">. \"@string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____5\">/ \"string\" [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____6\">: \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____7\">? \"counter\" \"label\" \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____8\">? \"label\" \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____9\">[ \"string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP____0\">| \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___a1\">ABORT LOOP</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___a2\">ASK \"string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___a3\">AVALANCHE</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___b1\">BECOME [color] [thing] [param]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b2\">BECOME NONLAVAWALKER</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b3\">BECOME NONPUSHABLE</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b4\">BECOME LAVAWALKER</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b5\">BECOME PUSHABLE</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b6\">BLIND #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b7\">BOARD [dir] \"string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b8\">BOARD [dir] NONE</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b9\">BULLETCOLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b0\">BULLETE [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___bA\">BULLETN [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___bB\">BULLETS [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___bC\">BULLETW [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c1\">CENTER MESG</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___c2\">CHANGE [color] [thing] [param] [color] [thing] [param]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c3\">CHANGE CHAR ID # [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c4\">CHANGE OVERLAY [color] [char] [color] [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c5\">CHANGE OVERLAY [color] [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c6\">CHANGE SFX # \"string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c7\">CHANGE THICK ARROW CHAR [dir] [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c8\">CHANGE THIN ARROW CHAR [dir] [char]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___c9\">CHAR [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___c0\">CHAR EDIT [char] # # # # # # # # # # # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cA\">CLEAR MESG</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cB\">CLIP INPUT</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cC\">COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cD\">COLOR FADE OUT</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cE\">COLOR FADE IN</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cF\">COLOR INTENSITY # PERCENT</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cG\">COLOR INTENSITY # # PERCENT</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cH\">COPY # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cI\">COPY [dir] [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cJ\">COPY BLOCK # # # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cK\">COPY CHAR [char] [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cL\">COPY OVERLAY BLOCK # # # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cQ\">COPY BLOCK # # # # \"@filename\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cR\">COPY OVERLAY BLOCK # # # # \"@filename\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cS\">COPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"@filename\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cT\">COPY BLOCK # # # # \"$string\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cU\">COPY OVERLAY BLOCK # # # # \"$string\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cV\">COPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"$string\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cW\">COPY BLOCK # # # # \"#x\" \"#y\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cX\">COPY OVERLAY BLOCK # # # # \"#x\" \"#y\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cY\">COPY BLOCK \"#x\" \"#y\" # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cZ\">COPY OVERLAY BLOCK \"#x\" \"#y\" # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___cAA\">COPY (OVERLAY) BLOCK \"#x1\" \"#y1\" # # \"#x2\" \"#y2\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cM\">COPYROBOT \"Robot\" [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cN\">COPYROBOT # # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cO\">COPYROBOT [dir] [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___cP\">CYCLE # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d1\">DEC \"counter\" #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d2\">DEC \"counter\" RANDOM # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___dA\">DEC \"$string\" #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d3\">DIE</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d4\">DIE ITEM</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___d5\">DISABLE MESG EDGE</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___d6\">DISABLE SAVING</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d7\">DIVIDE \"counter\" #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d8\">DOUBLE \"counter\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d9\">DUPLICATE SELF # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___d0\">DUPLICATE SELF [dir]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e1\">END [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e2\">END MOD</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e3\">END PLAY</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e4\">END SAM</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e5\">ENDGAME</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e6\">ENDLIFE</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___e7\">ENABLE MESG EDGE</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___e8\">ENABLE SAVING</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___e9\">ENABLE SENSORONLY SAVING</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___e0\">ENEMY BULLETCOLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eA\">ENEMY BULLETE [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eB\">ENEMY BULLETN [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eC\">ENEMY BULLETS [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eD\">ENEMY BULLETW [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eE\">EXCHANGE PLAYER POSITION [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eF\">EXCHANGE PLAYER POSITION # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___eG\">EXCHANGE PLAYER POSITION # DUPLICATE SELF [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___eH\">EXPLODE #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___f1\">FIREWALKER #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___f2\">FILLHEALTH</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___f3\">FLIP CHAR [char] [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___f4\">FREEZETIME #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___g1\">GIVE # [item]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___g2\">GIVEKEY [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___g3\">GIVEKEY [color] \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___g4\">GO [dir] # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___g5\">GOTO \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___g6\">GOTO \"#return\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___g7\">GOTO \"#top\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___g8\">GOTOXY # # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___h1\">HALF \"counter\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___i1\">IF \"counter\" !&lt;&gt;= # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i2\">IF [condition] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i3\">IF # # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i4\">IF [dir] PLAYER [color] [thing] [param] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i5\">IF [color] [thing] [param] # # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i6\">IF [color] [thing] [param] [dir] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i7\">IF ALIGNEDROBOT \"Robot\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i8\">IF ANY [color] [thing] [param] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i9\">IF FIRST STRING \"string\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___i0\">IF NO [color] [thing] [param] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iA\">IF NOT [color] [thing] [param] [dir] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iB\">IF NOT [condition] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iC\">IF PLAYER # # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iD\">IF STRING \"string\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iE\">IF STRING MATCHES \"string\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iF\">IF STRING NOT \"string\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iO\">IF \"$string\" (equality) # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iK\">IF \"$string\" (equality) \"text\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iL\">IF \"$string\" (equality) \"$string2\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iG\">IF cNN Sprite_Colliding pNN # # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iH\">IF c?? Sprite pNN # # \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iM\">INC \"$string\" \"text\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iN\">INC \"$string\" \"$string2\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___iI\">INC \"counter\" #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___iJ\">INC \"counter\" RANDOM # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___iP\">INPUT STRING \"string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___j1\">JUMP MOD ORDER #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l1\">LAYBOMB [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l2\">LAYBOMB HIGH [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l3\">LAZERWALL [dir] #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l4\">LOAD CHAR SET \"file\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l5\">LOAD PALETTE \"file\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l6\">LOCKPLAYER</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l7\">LOCKPLAYER ATTACK</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l8\">LOCKPLAYER EW</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l9\">LOCKPLAYER NS</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___l0\">LOCKSCROLL</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___lA\">LOCKSELF</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___lB\">LOOP #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___lC\">LOOP START</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m1\">MESSAGE ROW #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m2\">MISSILECOLOR [color]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___m3\">MOD \"file\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m4\">MOD FADE # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m5\">MOD FADE IN \"file\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m6\">MOD FADE OUT</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m7\">MOD SAM # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___m8\">MOD \"*\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___m9\">MODULO \"counter\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m0\">MOVE ALL [color] [thing] [param] [dir] [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___mA\">MOVE PLAYER [dir] [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___mB\">MOVE PLAYER [dir] \"label\" [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___mC\">MULTIPLY \"counter\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___n1\">NEUTRAL BULLETCOLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___n2\">NEUTRAL BULLETE [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___n3\">NEUTRAL BULLETN [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___n4\">NEUTRAL BULLETS [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___n5\">NEUTRAL BULLETW [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___o1\">OPEN [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___o2\">OVERLAY ON</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___o3\">OVERLAY STATIC</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___o4\">OVERLAY TRANSPARENT</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p1\">PERSISTENT GO \"string\" [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p2\">PLAY \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p3\">PLAY SFX \"string\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___p4\">PLAYER BULLETCOLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___p5\">PLAYER BULLETE [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___p6\">PLAYER BULLETN [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___p7\">PLAYER BULLETS [char]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___p8\">PLAYER BULLETW [char]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p9\">PLAYER CHAR [dir] [char]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p0\">PLAYER CHAR [char]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pA\">PLAYERCOLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___pB\">PUSH [dir]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pC\">PUT [color] [thing] [param] # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pD\">PUT [color] [thing] [param] [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___pE\">PUT [color] [char] OVERLAY # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pF\">PUT [color] [thing] [param] [dir] PLAYER</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pG\">PUT PLAYER # # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pH\">PUT PLAYER [dir] [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pI\">PUT \"@FILENAME.XXX\" Image_File [param] # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___pJ\">PUT [color] Sprite [param] # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP__pre\">REL COUNTERS</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r2\">REL PLAYER</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r3\">REL SELF</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r4\">REL COUNTERS FIRST</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r5\">REL PLAYER FIRST</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r6\">REL SELF FIRST</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r7\">REL COUNTERS LAST</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r8\">REL PLAYER LAST</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r9\">REL SELF LAST</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r0\">RESETVIEW</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___rA\">RESTORE \"label\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___rB\">RESTORE PLAYER POSITION [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___rC\">RESTORE PLAYER POSITION # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___rD\">RESTORE PLAYER POSITION # DUPLICATE SELF [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___rE\">ROTATECW</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___rF\">ROTATECCW</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___s1\">SAM # \"file\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s2\">SAVE PLAYER POSITION</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s3\">SAVE PLAYER POSITION #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s4\">SCROLL CHAR [char] [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s5\">SCROLLARROW COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s6\">SCROLLBASE COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s7\">SCROLLCORNER COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s8\">SCROLLPOINTER COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s9\">SCROLLTITLE COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___s0\">SCROLLVIEW [dir] #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sA\">SCROLLVIEW POSITION # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sB\">SEND # # \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sC\">SEND \"Robot\" \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sD\">SEND [dir] \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sE\">SEND [dir] PLAYER \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sF\">SET \"counter\" #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sG\">SET \"counter\" RANDOM # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sH\">SET \"$string\" \"text\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sI\">SET \"$string1\" \"$string2\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sJ\">SET \"$string\" #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sK\">SET COLOR # # # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sL\">SET EDGE COLOR [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sM\">SET MAXHEALTH #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sN\">SET MESG COLUMN #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sO\">SFX #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sP\">SHOOT [dir]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sQ\">SHOOTMISSILE [dir]</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sR\">SHOOTSEEKER [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sS\">SLOWTIME #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___sT\">SPITFIRE [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sU\">STATUS COUNTER # \"counter\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sV\">SWAP WORLD \"file\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sW\">SWITCH [dir] [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___t1\">TAKE # [item]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___t2\">TAKE # [item] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___t3\">TAKEKEY [color]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___t4\">TAKEKEY [color] \"label\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___t5\">TELEPORT PLAYER \"string\" # # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___t6\">TRADE # [item] # [item] \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___t7\">TRY [dir] \"label\" [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___u1\">UNLOCKPLAYER</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___u2\">UNLOCKSCROLL</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___u3\">UNLOCKSELF</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___v1\">VIEWPORT # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___v2\">VIEWPORT SIZE # #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___v3\">VOLUME #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___w1\">WAIT # [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___w2\">WAIT MOD FADE [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___w3\">WAIT PLAY [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___w4\">WAIT PLAY \"string\" [<span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___w5\">WALK [dir]</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___w6\">WIND #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___w7\">WRITE OVERLAY [color] \"string\" # #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___z1\">ZAP \"label\" #</a>\n\n<p class=\"hC\">Command Descriptions</p>\n<a class=\"hA\" name=\"COMMANDR.HLP____3\"><span class=\"fA\">* \"string\"</span></a>\n\nThis command displays the given string as a message on the\nmessage line, which by default is at the bottom of the screen\nand centered. This command will cycle the message in different\ncolors by default, but will also accept ~ and @ color codes.\nUsing the newline char (\\n) in this string will cause the\nmessage line to span another line underneath, allowing\nmulti-line messages.\n\nA * message will stay on screen on that board for either 160\ncycles, or until replaced by another * message, or until a CLEAR\nMESG command is ran, whichever comes first.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP___cA\">CLEAR MESG</a>\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP____5\"><span class=\"fA\">/ \"string\" </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___p1\"><span class=\"fA\">PERSISTENT GO \"string\" </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___g4\"><span class=\"fA\">GO [dir] # </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nUse / followed by a string to move the Robot around the board.\nThe string must consist of a series of N, S, E, W, and I. NSEW\nwill move the Robot in that direction, and I will wait for one\ncycle. One action is performed each cycle.\n\nUse PERSISTENT GO in the same way as /. However, if the Robot\nattempts to move in a given direction and it cannot, the Robot\nwill wait until it can before moving on to the next symbol.\n\nUse GO to move in a single direction for a given number of\nspaces (limited to 255; higher numbers and negatives wrap).\n\nThese commands can be used concurrently with <span class=\"fA\">WALK [dir]</span><span class=\"fF\">,</span>\nallowing Robots to move in double-speed steps. However, this\ncan only happen when the commands are in the same cycle and if\nthe given string/number for the moving command is small.\n\n<a class=\"hL\" href=\"#COMMANDR.HLP___w5\">WALK [dir]</a>\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP____6\"><span class=\"fA\">:</span><span class=\"fA\"> \"label\"</span></a>\n\nA : is used to denote actual LABELS within a Robotic program.\nThese labels are used as points within the program to branch\nto when a Robot receives a message from another Robot, goes to\na label of its own, or otherwise receives an external message.\nLabel names can be any length and consist of any characters.\nThey are not case-sensitive, and they can not take interpreted\ncounters (e.g. : \"&amp;label&amp;\").\nA Robot can have multiple active labels with the same name. In\nthese cases, the topmost of these labels in the Robot's code is\nthe one that is triggered.\nThere are several label names that are automatically jumped to\nunder certain conditions. These are detailed in the Built-in\nLabels section.\n\nLabels starting with a pound sign/hash (#) are subroutines.\nSee the Subroutine section for more information.\n\n<a class=\"hL\" href=\"#SUBROUTE.HLP__sub\">Subroutines</a>\n<a class=\"hL\" href=\"#BUILTINL.HLP__1st\">Built-in Labels</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___g5\"><span class=\"fA\">GOTO \"label\"</span></a>\n\nThis will attempt to send the current Robot to the label\ngiven; if the label does not exist, this command does nothing.\nIf the label begins with a # it will go to it as a subroutine.\nSeveral (but not all!) built-in labels also have subroutine\nversions.\n\n<a class=\"hA\" name=\"COMMANDR.HLP___g6\"><span class=\"fA\">GOTO \"#return\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___g7\"><span class=\"fA\">GOTO \"#top\"</span></a>\n\nThis will send the current Robot to the line after the\nnext-highest subroutine call and to the line after the first\nsubroutine call, respectively.\n\n<a class=\"hL\" href=\"#SUBROUTE.HLP__sub\">Subroutines</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___sC\"><span class=\"fA\">SEND \"Robot\" \"label\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___sD\"><span class=\"fA\">SEND [dir] \"label\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___sE\"><span class=\"fA\">SEND [dir] PLAYER \"label\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___sB\"><span class=\"fA\">SEND # # \"label\"</span></a>\n\nUse SEND to send another Robot to the label given. This is\ncalled \"messaging\" a Robot, and the label given is the\n\"message\". The target Robot can be given one of four ways,\nas listed above respectively:\n\n1. State the target Robot's name. Use \"ALL\" in place of the\nRobot name to message all Robots (including the Robot doing the\nsend). Set <span class=\"fA\">LOCKSELF</span><span class=\"fF\"> beforehand to prevent sending to yourself</span>\nwhen sending to \"ALL\".\n2. State the direction of the target Robot, relative to the\nsource Robot.\n3. State the direction of the target Robot, relative to the\nplayer.\n4. State the x,y coordinates of the target Robot.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___lA\">LOCKSELF</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP____9\"><span class=\"fA\">[ \"string\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP____2\"><span class=\"fA\">&amp; \"string\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP____1\"><span class=\"fA\">% \"string\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP____8\"><span class=\"fA\">? \"label\" \"string\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP____7\"><span class=\"fA\">? \"counter\" \"label\" \"string\"</span></a>\n\nUse these commands to bring up a message box during the game\ncontaining the given text. The message box will contain all\nconsecutive lines using the commands [, &amp;, %, or ?. Blank lines\nand labels will be skipped over. The box message commands are\nas follows:\n\n  [ will simply display the given message.\n  &amp; will display the given message, centered within the box.\n  % will display the given message, but will parse color codes.\n  ? will display an option that will send the current Robot to\n   the given label when selected. The counter is optional. If a\n   counter is included, the option will only be shown if the\n   counter is NOT zero.\n\nAll box-message commands can use ~ and @ color codes, EXCEPT\nfor [. The message box can show up to 64 characters per line;\nany characters past that will not be shown.\n\nNOTE: Take care when using any of these commands in a loop (and\nbe especially vigilant of possible unintended loops). Looping\nthese commands without any WAITs or similar cycle-breaking\ncommands (or repeatedly jumping to a label with said command)\ncan cause the dialogue box to re-appear as soon as it is\nclosed, making it very hard to resume normal action.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__1st\">Counters, Built-in Counters and Local Counters</a>\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP____4\"><span class=\"fA\">. \"string\"</span></a>\n\nThis command does nothing. It is used for comments and other\nnotes you may wish to make within your Robots. There is one\nexception - If \"string\" begins with a @. (see next) You may\ncomment or de-comment lines of code automatically with the\nCtrl+C command (comment mark). Comments can nonetheless have an\nactual effect on code speed, as they must be interpreted when\nencountered.\n\n<a class=\"hA\" name=\"COMMANDR.HLP___4b\"><span class=\"fA\">. \"@string\"</span></a>\n\nIf a comment's string begins with a @, then the rest of the\nstring becomes the new name for the Robot. For example,\n\"@Hiya\" would name the Robot \"Hiya\". This clips at the first\n14 characters of the string; for example, . \"@Robothasanewname\"\nwould cut off and rename the Robot \"Robothasanewna\".\n\n<a class=\"hA\" name=\"COMMANDR.HLP___e1\"><span class=\"fA\">END </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nThis command ends the Robot's program. The Robot will not run\nany further commands until an external event affects it.\n\n<a class=\"hA\" name=\"COMMANDR.HLP___d3\"><span class=\"fA\">DIE</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___d4\"><span class=\"fA\">DIE ITEM</span></a>\n\nThese commands destroy the Robot forever. DIE ITEM will also\nput the player at the location the Robot previously occupied,\nwhich is useful for creating Robots that simulate collectible\nitems.\n\n<a class=\"hA\" name=\"COMMANDR.HLP___w1\"><span class=\"fA\">WAIT # </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nThis command will cause the Robot to do absolutely nothing for\na given number of cycles (limited to 255; values over this will\nwrap around). Robots will still respond to label sends when\nwaiting, provided that the Robot is not locked.\n\n<a class=\"hL\" href=\"#PROCESS.HLP__prc\">Cycles and Board Scans - How MZX Processes Robots</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___c9\"><span class=\"fA\">CHAR [char]</span></a>\n\nThis command will change the Robot's character (appearance) to\nthe character given.\n\n<a class=\"hA\" name=\"COMMANDR.HLP___cC\"><span class=\"fA\">COLOR [color]</span></a>\n\nThis command will change the Robot's color to the color given.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___sF\"><span class=\"fA\">SET \"counter\" #</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___sG\"><span class=\"fA\">SET \"counter\" RANDOM # #</span></a>\n\nThese commands set a counter to a certain value. Counters are\ninternal variables that can be changed through Robots. They are\nusually used for custom purposes, although there are many\ncounters with pre-defined uses as well, such as \"Gems\". The\nRANDOM version will set the counter to a random number within\nthe given range, inclusive (first number minimum, second\nmaximum).\n\n<a class=\"hA\" name=\"COMMANDR.HLP___sH\"><span class=\"fA\">SET \"$string\" \"text\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___sI\"><span class=\"fA\">SET \"$string\" \"$string2\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___sJ\"><span class=\"fA\">SET \"$string\" number</span></a>\n\nThese commands set strings to a certain value: a line of text,\nanother string or a number, respectively.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__1st\">Counters, Built-in Counters and Local Counters</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___iI\"><span class=\"fA\">INC \"counter\" #</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___d1\"><span class=\"fA\">DEC \"counter\" #</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___iJ\"><span class=\"fA\">INC \"counter\" RANDOM # #</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___d2\"><span class=\"fA\">DEC \"counter\" RANDOM # #</span></a>\n\nThese commands will increase or decrease a given counter or\nstring by a given amount. The RANDOM versions will increase or\ndecrease by a random number within the given range, inclusive\n(first number minimum, second maximum).\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__1st\">Counters, Built-in Counters and Local Counters</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___i1\"><span class=\"fA\">IF \"counter\" !&lt;&gt;= # \"label\"</span></a>\n\nThis command tests to see if a given counter is equal to, less\nthan, etc. another counter or value. If the conditional is met,\nthe current Robot is sent to the given label. The following\nconditionals are allowed: = Equal, &lt; Less than, &gt; Greater than,\n&lt;= Less than/equal, &gt;= Greater than/equal, != (also &lt;&gt;) Not\nequal.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__1st\">Counters, Built-in Counters and Local Counters</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___cP\"><span class=\"fA\">CYCLE # </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nThis command changes the Robot's speed to one Robot cycle for\nevery given number of update cycles. An update cycle is how\noften everything onscreen is updated, such as most enemies.\nEvery time a given Robot gets to run commands is called a cycle\nin terms of that Robot; most commands can run several times in\na single Robot cycle. (CYCLE # values are limited to 255;\nvalues over this will wrap around.)\n\n<a class=\"hL\" href=\"#PROCESS.HLP__prc\">Cycles and Board Scans - How MZX Processes Robots</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___e6\"><span class=\"fA\">ENDLIFE</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___e5\"><span class=\"fA\">ENDGAME</span></a>\n\nThese commands will end the player's current life or the entire\ngame, just as if the player died or lost all of its lives.\n\n<a class=\"hA\" name=\"COMMANDR.HLP___w5\"><span class=\"fA\">WALK [dir]</span></a>\n\nThis will cause the Robot to attempt to move one space in the\ngiven direction every cycle. Use a direction of IDLE to turn\nwalking off.\n\nThis can be used concurrently with <span class=\"fA\">GO [dir] #</span><span class=\"fF\"> and its</span>\nrelatives to make Robots move in double-speed steps.\n\n<a class=\"hL\" href=\"#COMMANDR.HLP___g4\">GO [dir] #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP____5\">/ \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___p1\">PERSISTENT GO \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___sP\"><span class=\"fA\">SHOOT [dir]</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___sQ\"><span class=\"fA\">SHOOTMISSILE [dir]</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___sR\"><span class=\"fA\">SHOOTSEEKER [dir]</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___sT\"><span class=\"fA\">SPITFIRE [dir]</span></a>\n\nThese four commands all shoot a weapon in the given direction:\nA bullet, a missile, a seeker, or shooting fire, respectively.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___t7\"><span class=\"fA\">TRY [dir] \"label\" </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nThis will have the Robot attempt to move in the given\ndirection. If it can, it will. If it can't, it will jump to the\ngiven label.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___b1\"><span class=\"fA\">BECOME [color] [thing] [param]</span></a>\n\nThe Robot will end its program permanently, and instead become\na specified object with a given color and parameter.\nNOTE: Becoming another Robot type will not end or destroy the\nprogram, but there are far better commands for doing this:\n<span class=\"fA\">BECOME</span><span class=\"fF\"> </span><span class=\"fA\">PUSHABLE</span><span class=\"fF\"> and </span><span class=\"fA\">BECOME NONPUSHABLE</span><span class=\"fF\">.</span>\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b3\">BECOME NONPUSHABLE</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___b5\">BECOME PUSHABLE</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___c2\"><span class=\"fA\">CHANGE [color] [thing] [param] [color] [thing] [param]</span></a>\n\nAll on-board objects of the first given type will become\nobjects of the second given type.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___pC\"><span class=\"fA\">PUT [color] [thing] [param] # #</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___pD\"><span class=\"fA\">PUT [color] [thing] [param] [dir]</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___pF\"><span class=\"fA\">PUT [color] [thing] [param] [dir] PLAYER</span></a>\n\nThis will put a given object somewhere on the board. If anything\nelse is in that spot and layer, it will be placed under the new\nobject, if possible; otherwise, it will be replaced by this\nobject. (The exception is if the object were to replace the\nplayer; in this case, nothing happens.) The target location can\nbe given in one of three ways:\n\n1. State the target x,y coordinates.\n2. State the direction, relative to the Robot.\n3. State the direction, relative to the player.\n\nNote that this command is not suitable for placing Robots. For\nmoving a Robot, use <span class=\"fA\">GOTOXY # #</span><span class=\"fF\">; for placing a copy of a Robot,</span>\nuse <span class=\"fA\">DUPLICATE SELF # #</span><span class=\"fF\"> or </span><span class=\"fA\">DUPLICATE SELF [dir]</span><span class=\"fF\">.</span>\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___m3\"><span class=\"fA\">MOD \"file\"</span></a>\n\nThis command loads the given module/OGG/SAM file as background\nmusic. These files also can be loaded from a subdirectory with\nthe syntax of \"\"dir\"\\\\\"file\"\" or \"\"dir\"/\"file\"\" (without the\ninner quotes). MOD, unlike SAM, will loop audio, and is affected\nby related commands like VOLUME and MOD ORDER.\nNOTE: placing * at the end of the filename will set the current\nmod to the given filename, but institute MOD \"*\" (see below) for\nsubsequent board visits.\n\n<a class=\"hA\" name=\"COMMANDR.HLP___m8\"><span class=\"fA\">MOD \"*\"</span></a>\n\nSets the board's currently running music to \"wildcard\". This\nmeans that it will continue to play whatever was last playing.\nThis setting does not \"lock\" the board's music to any one song;\nfor instance, if 1.mod was playing on the last board, mod \"*\"\nwill continue playing 1.mod, but if the player then goes to a\nboard playing 2.mod and jumps back to the mod \"*\" board from\nthere, 2.mod will continue playing, and 1.mod will not start.\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___s1\"><span class=\"fA\">SAM # \"file\"</span></a>\n\nPlays the given SAM/WAV/OGG/module file at a given frequency.\nThese files also can be loaded from a subdirectory with the\nsyntax of \"\"dir\"\\\\\"file\"\" or \"\"dir\"/\"file\"\" (without the inner\nquotes). 0 plays the file at natural frequency. Music loaded\nwith the SAM command will not loop.\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___e2\"><span class=\"fA\">END MOD</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___e4\"><span class=\"fA\">END SAM</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___e3\"><span class=\"fA\">END PLAY</span></a>\n\nEnds the playing of the current audio set by MOD \"file\",\nthe current audio set by SAM # \"file\", or the currently playing\nPLAY statement (respectively).\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___d8\"><span class=\"fA\">DOUBLE \"counter\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___h1\"><span class=\"fA\">HALF \"counter\"</span></a>\n\nDoubles or halves (rounding down) the value of the given\ncounter, respectively.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__1st\">Counters, Built-in Counters and Local Counters</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___mC\"><span class=\"fA\">MULTIPLY \"counter\" #</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___d7\"><span class=\"fA\">DIVIDE \"counter\" #</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___m9\"><span class=\"fA\">MODULO \"counter\" #</span></a>\n\nMultiplies, divides, or performs a modulo on the value of the\ngiven counter by the given number (rounding down), and then sets\nthe counter to the result.\nTo modulo is to set to the remainder when dividing. Example: 7\nmodulo 3 is equal to 1, since 7 divided by 3 gives a remainder\nof 1.\n\nNOTE: The modulo command may output different numbers compared\nto the modulo expression function; they may act differently\nwhen negative numbers are involved. For the modulo command, the\noutput will give results similar to when both numbers are\npositive, but will simply have the sign of the dividend (i.e. a\ntruncated modulo).\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__1st\">Counters, Built-in Counters and Local Counters</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___p2\"><span class=\"fA\">PLAY \"string\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___p3\"><span class=\"fA\">PLAY SFX \"string\"</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___w4\"><span class=\"fA\">WAIT PLAY \"string\" </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nThese all play sound effects, PC speaker notes, or a\ncombination thereof. PLAY SFX will only play if there are no\nother notes currently playing, while PLAY will add its notes to\nthe end of the current sound effects queue. WAIT PLAY will wait\nuntil the queue is almost empty before adding its notes.\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___sO\"><span class=\"fA\">SFX #</span></a>\n\nPlays the given built-in sound effect.\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___eH\"><span class=\"fA\">EXPLODE #</span></a>\n\nThe Robot will explode, destroying it and its program forever.\nThe radius of the explosion in characters is stated in the\ncommand, with a maximum of 16 characters.\n\n<a class=\"hA\" name=\"COMMANDR.HLP___cD\"><span class=\"fA\">COLOR FADE OUT</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___cE\"><span class=\"fA\">COLOR FADE IN</span></a>\n\nFades the palette out to black or in to full color. Fading in\nwill not work if you haven't faded out, and fades affect\nneither the current palette nor current intensities.\n\n<a class=\"hA\" name=\"COMMANDR.HLP___g8\"><span class=\"fA\">GOTOXY # # </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nThe Robot will go to the given x,y coordinates on the current\nboard, destroying anything there that cannot go on the under\nlayer. If the player is at those coordinates, the Robot ignores\nthe command.\n\n<a class=\"hA\" name=\"COMMANDR.HLP___lC\"><span class=\"fA\">LOOP START</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___lB\"><span class=\"fA\">LOOP #</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___a1\"><span class=\"fA\">ABORT LOOP</span></a>\n\nThese three commands define a loop, which is a sequence of\ncommands to be repeated. LOOP START marks the start of a loop.\nLOOP # will loop back to LOOP START the given number of times.\nABORT LOOP will jump out of the loop, to the command after LOOP\n#. The loop count is kept in the local counter \"loopcount\".\nMake sure your number in LOOP # is one less than the amount for\nwhich you actually want to loop, as the initial iteration\nbefore first hitting LOOP # is not counted in the number.\n\nNOTE: Using ABORT LOOP outside of the loop (that is, outside of\nany block of code starting with LOOP START and ending in\nLOOP #) is very highly unadvised and will most likely cause\nunintended effects.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__1st\">Counters, Built-in Counters and Local Counters</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___mA\"><span class=\"fA\">MOVE PLAYER [dir] </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___mB\"><span class=\"fA\">MOVE PLAYER [dir] \"label\" </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nThis attempts to move the player in the given direction. If the\nplayer is blocked, and the command contains a label, the Robot\nwill jump to that label.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___p0\"><span class=\"fA\">PLAYER CHAR [char]</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___p9\"><span class=\"fA\">PLAYER CHAR [dir] [char]</span></a>\n\nChanges the character (appearance) of the player. The second\nform allows you to change the appearance of one of the four\nfacing directions at a time, while the first form changes all\nfour directions at once.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___pA\"><span class=\"fA\">PLAYERCOLOR [color]</span></a>\n\nChanges the color of the player.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___pG\"><span class=\"fA\">PUT PLAYER # # </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___pH\"><span class=\"fA\">PUT PLAYER [dir] </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nThe player will go to the given x,y coordinates or in the given\ndirection immediately relative to the Robot, destroying anything\nthere that cannot go on the under layer. If the x,y coordinates\nof the put command are out of bounds, the player is moved to the\nclosest-matching coordinates, and if a directional put is out of\nbounds it is ignored.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___pI\"><span class=\"fA\">PUT \"@FILENAME.XXX\" Image_File [param] # #</span></a>\n\nPlaces an MZM of the given filename. These files also can be\nloaded from a subdirectory with the syntax of \"\"@dir\"\\\\\"file\"\"\nor \"\"@dir\"/\"file\"\" (without the inner quotes). The @ is\nREQUIRED. The numbers mark the destination x,y coordinates of\nthe MZM's upper-left corner; the parameter determines the\nplacement of the MZM (00 = board, 01 = overlay, 02 = vlayer).\nSee the MZM section for further details.\n\n<a class=\"hL\" href=\"#MZM.HLP__mzm\">Using MZMs</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___pJ\"><span class=\"fA\">PUT [color] Sprite [param] # #</span></a>\n\nPuts a sprite on the board. The param number is the number of\nthe sprite in hex (auto-converted from decimal). If the color\nis c?? then the sprite is drawn with its reference's colors;\notherwise, it's painted with the given colors. # # marks the x,y\ncoordinates of the sprite's upper-left corner. See the Spites\nsection for further details.\n\n<a class=\"hL\" href=\"#SPRITES.HLP__spr\">Sprites</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___lA\"><span class=\"fA\">LOCKSELF</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___u3\"><span class=\"fA\">UNLOCKSELF</span></a>\n\nLocks or unlocks, respectively, the current Robot. When a Robot\nis locked, no external messages will be acknowledged - Only\ninternal GOTOs, etc. will be processed. (Built-in label calls\nto \"thud\" and \"edge\" are exceptions.)\n\n<a class=\"hA\" name=\"COMMANDR.HLP___v3\"><span class=\"fA\">VOLUME #</span></a>\n\nChanges the volume of the MOD playing, from 0 (silent) to 255.\nThis change is specific to the current board. The default when\nmusic is loaded is 255.\n\n<a class=\"hA\" name=\"COMMANDR.HLP___cM\"><span class=\"fA\">COPYROBOT \"Robot\" </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___cN\"><span class=\"fA\">COPYROBOT # # </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___cO\"><span class=\"fA\">COPYROBOT [dir] </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nThe current Robot becomes an exact duplicate of the noted\nRobot, starting from the beginning of the noted Robot's code.\nNote that if the source Robot was previously ended with the END\ncommand, or was at the end of its program, the destination\nRobot will be in an ended state as well. The Robot to copy can\nbe stated in one of three ways:\n\n1. State the target Robot's name. If multiple Robots on the same\n   board share the same name, the Robot chosen may vary, so\n   using this command in that situation is not advised.\n2. State the x,y coordinates of target Robot.\n3. State the direction of target Robot relative to the source\n   Robot.\n\nNOTE: Exact means just that, EXACT, down to its states and the\nvalues of local counters. The only thing that will differ will\nbe the robot_id. If one requires a group of objects differing\nonly in name, COPYROBOT is generally unsuitable for this purpose\nbecause even the Robot name is copied.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMANDR.HLP___d9\"><span class=\"fA\">DUPLICATE SELF # #</span></a>\n<a class=\"hA\" name=\"COMMANDR.HLP___d0\"><span class=\"fA\">DUPLICATE SELF [dir]</span></a>\n\nThe current Robot creates an exact duplicate of itself\n(including local counter values!) and places it at the noted\nx,y coordinates or relative direction. That Robot then begins\nits program at the first command.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<span class=\"fE\">The remaining Robotic commands are covered in another section.</span>\n\n<a class=\"hL\" href=\"#COMMAND2.HLP__1st\">Command Reference Part II</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"COMMAND2.HLP\">\n<a class=\"hA\" name=\"COMMAND2.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Command Reference Part II</span></p>\n<a class=\"hL\" href=\"#COMMANDR.HLP__1st\">Command Reference (Part I)</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___b9\"><span class=\"fA\">BULLETCOLOR [color]</span></a>\n\nChanges the color of all bullets.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___p4\"><span class=\"fA\">PLAYER BULLETCOLOR [color]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___e0\"><span class=\"fA\">ENEMY BULLETCOLOR [color]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___n1\"><span class=\"fA\">NEUTRAL BULLETCOLOR [color]</span></a>\n\nChanges the color of a specific type of bullet: Player, Enemy,\nor Neutral (ricocheted/Robot) bullets.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n<a class=\"hL\" href=\"#BULLETTY.HLP__1st\">Bullet Types</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___bA\"><span class=\"fA\">BULLETN [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___bB\"><span class=\"fA\">BULLETS [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___b0\"><span class=\"fA\">BULLETE [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___bC\"><span class=\"fA\">BULLETW [char]</span></a>\n\nChanges the character (appearance) of one of the four\ndirections of bullet, across all three types.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___p6\"><span class=\"fA\">PLAYER BULLETN [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___p7\"><span class=\"fA\">PLAYER BULLETS [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___p5\"><span class=\"fA\">PLAYER BULLETE [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___p8\"><span class=\"fA\">PLAYER BULLETW [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___eA\"><span class=\"fA\">ENEMY BULLETE [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___eB\"><span class=\"fA\">ENEMY BULLETN [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___eC\"><span class=\"fA\">ENEMY BULLETS [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___eD\"><span class=\"fA\">ENEMY BULLETW [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___n3\"><span class=\"fA\">NEUTRAL BULLETN [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___n4\"><span class=\"fA\">NEUTRAL BULLETS [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___n2\"><span class=\"fA\">NEUTRAL BULLETE [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___n5\"><span class=\"fA\">NEUTRAL BULLETW [char]</span></a>\n\nChanges the character of one of the four directions of bullet,\nfor one of the three types of bullet: Player, Enemy, or Neutral\n(ricocheted/Robot) bullets, respectively.\n\n<a class=\"hL\" href=\"#BULLETTY.HLP__1st\">Bullet Types</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___b5\"><span class=\"fA\">BECOME PUSHABLE</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___b3\"><span class=\"fA\">BECOME NONPUSHABLE</span></a>\n\nThe Robot becomes a pushable or nonpushable Robot,\nrespectively.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___i2\"><span class=\"fA\">IF [condition] \"label\"</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___iB\"><span class=\"fA\">IF NOT [condition] \"label\"</span></a>\n\nThese test whether a given condition is present or not, and if\nit is, the first will jump to the given label. If it isn't, the\nsecond will jump to the given label. Conditions are covered in\nanother section.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__con\">Conditions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___i3\"><span class=\"fA\">IF # # \"label\"</span></a>\n\nJumps to the given label if the current Robot is at the x,y\ncoordinates given.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___i4\"><span class=\"fA\">IF [dir] PLAYER [color] [thing] [param] \"label\"</span></a>\n\nJumps to the given label if the given thing is the given\ndirection relative to the player.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___i5\"><span class=\"fA\">IF [color] [thing] [param] # # \"label\"</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___i6\"><span class=\"fA\">IF [color] [thing] [param] [dir] \"label\"</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___iA\"><span class=\"fA\">IF NOT [color] [thing] [param] [dir] \"label\"</span></a>\n\nJumps to a label if a given thing is at the given x,y\ncoordinates or to the immediate given direction of the current\nRobot. The third form jumps if the thing is NOT in the\nimmediate given direction.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___i7\"><span class=\"fA\">IF ALIGNEDROBOT \"Robot\" \"label\"</span></a>\n\nJumps to the given label if the current Robot is aligned either\nvertically or horizontally with the named Robot.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___i8\"><span class=\"fA\">IF ANY [color] [thing] [param] \"label\"</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___i0\"><span class=\"fA\">IF NO [color] [thing] [param] \"label\"</span></a>\n\nIf there are ANY of a given thing with the given color and\nparameter on the current board, jumps to the given label. The\nsecond form triggers on the absence of the given thing.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___iC\"><span class=\"fA\">IF PLAYER # # \"label\"</span></a>\n\nIf the player is at the given x,y coordinates, jump to the given\nlabel.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___g2\"><span class=\"fA\">GIVEKEY [color]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___g3\"><span class=\"fA\">GIVEKEY [color] \"label\"</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___t3\"><span class=\"fA\">TAKEKEY [color]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___t4\"><span class=\"fA\">TAKEKEY [color] \"label\"</span></a>\n\nAttempts to give keys to or take keys from the player. The\nforms without labels will give or take the key if possible,\notherwise they will do nothing. The forms with labels will jump\nto the labels if the Robot tries to give a key when the player\nhas no room for keys, or if the Robot tries to take a key the\nplayer doesn't have.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___g1\"><span class=\"fA\">GIVE # [item]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___t1\"><span class=\"fA\">TAKE # [item]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___t2\"><span class=\"fA\">TAKE # [item] \"label\"</span></a>\n\nGives or takes a number of a certain item. If the player does\nnot have enough of this type of item to take, nothing is taken.\nThe third form jumps to a given label if the player doesn't have\nenough of the stated item.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___t6\"><span class=\"fA\">TRADE # [item] # [item] \"label\"</span></a>\n\nGives the player a number of the first item and takes a number\nof the second item in exchange. If the player doesn't have\nenough of the second item, this command jumps to the given label\nand has no other effect. This command eases the writing of\nsimple shops and vendors.\n\nKeep in mind that trades involving healths and lives can end up\nwith either at 0, leading to potential deaths and endgames if\nthe player has the exact amount of health/lives being traded\naway.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___l6\"><span class=\"fA\">LOCKPLAYER</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___l9\"><span class=\"fA\">LOCKPLAYER NS</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___l8\"><span class=\"fA\">LOCKPLAYER EW</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___l7\"><span class=\"fA\">LOCKPLAYER ATTACK</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___u1\"><span class=\"fA\">UNLOCKPLAYER</span></a>\n\nThe first four commands lock the player, preventing the player\nfrom doing any moving and attacking (i.e. default shooting and\nbombing), from moving north or south, from moving east or west,\nor from attacking, respectively. The fifth command will clear\nall locks on the player, allowing free movement and attacking.\nThese have no relation to the Board Info options \"Can Bomb\" and\n\"Can Shoot\". Player locking is local, only affecting the\ncurrent board.\n\nLocking the player will also prevent messages related to locked\nactions from showing (such as bomb switch messages or a \"Can't\n{shoot/bomb} here!\" message when the player is attack locked).\n\nAlso, locking the player will not cause the relevant input to\nbe ignored completely. MegaZeux will still detect that you are\npressing keys, even if the player is forbidden from responding\nto them, and will act accordingly. In short, even if a player\nis locked, Robots that take directional and space/delete inputs\nwill still act the same.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___b7\"><span class=\"fA\">BOARD [dir] \"string\"</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___b8\"><span class=\"fA\">BOARD [dir] NONE</span></a>\n\nChanges the board exit in the given direction to lead to the\ngiven board or to nowhere. Unlike with the Board Exits menu,\nlinking exits to the title board with this command is\nallowed.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___z1\"><span class=\"fA\">ZAP \"label\" #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___rA\"><span class=\"fA\">RESTORE \"label\" #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP____0\"><span class=\"fA\">| \"label\"</span></a>\n\nZAP will scan the Robotic program from the top down, changing\na given number of instances of the stated label to ZAPPED\nlabels. A ZAPPED label is inactive - it cannot be jumped to or\nsent to under any circumstance. This has two uses: It will\nprevent interference from a certain outside event, and it\nallows multiple instances of the same label. For example, you\ncould have four SHOT labels in a row, ZAP one each time the\nRobot was shot, and jump back to normal code, but have a fifth\nSHOT label lead to an explosion.\n\nRESTORE will scan the Robotic program from the bottom up,\nchanging a given number of the stated ZAPPED labels back to a\nnormal label. Essentially, this means that the most recently\nZAPPED labels are restored first. Both ZAP and RESTORE can have\na number larger than the number of instances of the stated\nlabel. The final command, |, is a pre-ZAPPED label, allowing you\nto have labels in your Robotic programs that are already ZAPPED\nand inactive.\n\n<a class=\"hA\" name=\"COMMAND2.HLP__pre\"><span class=\"fA\">REL COUNTERS</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___r2\"><span class=\"fA\">REL PLAYER</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___r3\"><span class=\"fA\">REL SELF</span></a>\n\nPLEASE NOTE: Judicious use of expressions and certain counters\n(THISX/THISY/PLAYERX/PLAYERY) is a less cumbersome alternative\nto using the REL set of commands, and is generally preferred.\n\nThese are a special class of commands, called PREFIXES. Prefixes\ndon't do a thing by themselves, but when placed before another\ncommand that uses x,y coordinates, they change the origin of the\nx,y coordinate system for that ONE following command. REL\nCOUNTERS moves the origin to the x,y location stated in the\ncounters XPOS and YPOS. REL PLAYER moves the origin to the\nx,y of the player. REL SELF moves the origin to the x,y of the\ncurrent Robot.\n\nThe movement of the origin changes the (0,0) coordinates from\nthe upper left of the board to somewhere else, effectively\nadding that x,y position to any coordinates entered. For\nexample, if the Robot is at the (5,5) position and uses REL\nSELF on the coordinates (2,2), it will effectively act on\ncoordinates (7,7). If the player is at the (7,2) position and a\nRobot uses REL PLAYER on coordinates (-3,6), the Robot will\neffectively act on coordinates (4,8).\n\nLet's look at some examples.\n\n<span class=\"fE\">PUT c0E GEM p?? 5 5</span>\n\nThis places a gem at the x,y coordinates (5,5).\n\n<span class=\"fE\">REL PLAYER</span>\n<span class=\"fE\">PUT c0E GEM p?? 5 5</span>\n\nThis places a gem 5 spaces to the right and 5 spaces below\nwhere the player currently is, or at the board edge if it is\nunder 5 spaces to the right/below the player.\n\n<span class=\"fE\">REL SELF</span>\n<span class=\"fE\">PUT c0E GEM p?? -2 -3</span>\n\nThis places a gem 2 spaces to the left and 3 spaces above\nwhere this Robot currently is, or at the board edge if it is\nunder 2 spaces to the left or 3 spaces above the Robot.\n\n<span class=\"fE\">REL COUNTERS</span>\n<span class=\"fE\">PUT c0E GEM p?? 1 -4</span>\n\nThis places a gem at the coordinates (XPOS+1,YPOS-4), or on a\nboard edge if either of these coordinates would extend past the\nboard. XPOS and YPOS are counters with amounts defined by the\nuser before using this command.\n\nPrefixes only have an effect on the very next command. This\nincludes comments.\n\nPrefixes also affect two (and only two) other things. The built\nin counters THISX and THISY will be offset by a prefix, and the\ncommand IF [dir] (NOT) BLOCKED will check next to the player or\nnext to the position noted by the counters if a prefix is\npresent. See their relevant sections for details.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___r4\"><span class=\"fA\">REL COUNTERS FIRST</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___r5\"><span class=\"fA\">REL PLAYER FIRST</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___r6\"><span class=\"fA\">REL SELF FIRST</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___r7\"><span class=\"fA\">REL COUNTERS LAST</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___r8\"><span class=\"fA\">REL PLAYER LAST</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___r9\"><span class=\"fA\">REL SELF LAST</span></a>\n\nThese prefixes are similar to the above prefixes, but they only\naffect the first or last x,y pair of coordinates in the next\ncommand. For example, COPY accepts two pairs of coordinates. If\nyou precede it with the following:\n\nREL PLAYER FIRST\nREL SELF LAST\n\nThen the coordinates of the object being copied will be relative\nto the player, and the destination coordinates will be relative\nto the Robot. There is one other difference with these commands\n- REL COUNTERS FIRST works off of the counters FIRSTXPOS and\nFIRSTYPOS, and REL COUNTERS LAST works off of the counters\nLASTXPOS and LASTYPOS.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___j1\"><span class=\"fA\">JUMP MOD ORDER #</span></a>\n\nThe current module jumps to the stated module order, if\napplicable. For example, JUMP MOD ORDER 3 will begin playing\nthe third order in the module, which is the fourth section of\nthe module. Sometimes your music editor will refer to this as\nthe 'Pattern' or 'Track'.\nWith OGG music, this command makes a jump to the stated frame.\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___l1\"><span class=\"fA\">LAYBOMB [dir]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___l2\"><span class=\"fA\">LAYBOMB HIGH [dir]</span></a>\n\nPlaces a lit bomb (high strength for the second form) next to\nthe Robot in the given direction. Use a direction of UNDER or\nBENEATH to place the bomb underneath the Robot.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___l3\"><span class=\"fA\">LAZERWALL [dir] #</span></a>\n\nThe Robot shoots a lazer in the given direction, and the lazer\nhas the color of the Robot and the duration given. The number of\ncycles the lazer is active is duration x 2.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___cH\"><span class=\"fA\">COPY # # # #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___cI\"><span class=\"fA\">COPY [dir] [dir]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___sW\"><span class=\"fA\">SWITCH [dir] [dir]</span></a>\n\nCopies the thing at one location to another, with the location\nstated through x,y coordinates or directions relative to the\ncurrent Robot. The third command will swap the things in the\ntwo directions.\n\nCOPY can copy between different layers by using the appropriate\nprefixes. Prefix coordinate numbers with \"+\" to indicate\noverlay coordinates, and with \"#\" to indicate vlayer\ncoordinates.\n\nNOTE: DO NOT normally use COPY # # # # to copy Robots; use\nDUPLICATE SELF or COPYROBOT. It is useful for copying by Robot\nid, however; use COPY rN.thisx and rN.thisy as the first pair\nof coordinates.\n\nUsing COPY # # # # on a Robot will no longer cause the Robot\ncopy to start where the parent left off, and will have new\nRobots start at the first line.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__rnc\">Rn.&lt;counter&gt;</a>\n<a class=\"hL\" href=\"#BADPRACT.HLP__bad\">Robotic Usages That Should be Avoided</a>\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___cJ\"><span class=\"fA\">COPY BLOCK # # # # # #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___cL\"><span class=\"fA\">COPY OVERLAY BLOCK # # # # # #</span></a>\n\nCopies a block of things from one location on the board or\noverlay to another section. The first two numbers state the\nupper-left corner of the block to be copied, the second two\nnumbers state the size of the block, and the third two numbers\nstate the destination block's upper-left corner.\n\nCOPY BLOCK and COPY OVERLAY BLOCK can copy from board to\noverlay and from overlay to board (respectively). The last two\nnumbers must be prefixed by plus signs, like so:\n\nCOPY BLOCK # # # # \"+#\" \"+#\"\nCOPY OVERLAY BLOCK # # # # \"+#\" \"+#\"\n\nValid Robotic block copies from the overlay to the board are\nalways CustomBlocks.\n\n<a class=\"hL\" href=\"#TOVERLAY.HLP__081\">Editing and Using the Overlay</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___cW\"><span class=\"fA\">COPY BLOCK # # # # \"#x\" \"#y\"</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___cX\"><span class=\"fA\">COPY OVERLAY BLOCK # # # # \"#x\" \"#y\"</span></a>\n\nThese commands copy from the board or overlay (respectively) to\nthe vlayer. The first two numbers are the x,y coordinates of the\nupper-left corner of the block to be copied; the next two\nnumbers are the respective width and height of the block; the\n\"#x\" and \"#y\" are the x,y coordinates of the destination block's\nupper-left corner. The pound sign in \"#x\" and \"#y\" MUST be\nincluded.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___cY\"><span class=\"fA\">COPY BLOCK \"#x\" \"#y\" # # # #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___cZ\"><span class=\"fA\">COPY OVERLAY BLOCK \"#x\" \"#y\" # # # #</span></a>\n\nThese commands similarly copy from the vlayer to the board or\noverlay (respectively).\n\n<a class=\"hL\" href=\"#TOVERLAY.HLP__081\">Editing and Using the Overlay</a>\n<a class=\"hL\" href=\"#VLAYER.HLP__vla\">The Vlayer and Its Uses</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___cAA\"><span class=\"fA\">COPY (OVERLAY) BLOCK \"#x1\" \"#y1\" # # \"#x2\" \"#y2\"</span></a>\n\nThis command copies a block from the vlayer and copies it onto\na different spot on the vlayer. \"#x1\" and \"#y1\" are the x,y\ncoordinates of the upper-left corner of the copied block; the\nnext two numbers are the respective width and height of the\ncopied area; \"#x2\" and \"#y2\" are the x,y coordinates of the\ndestination block's upper-left corner. The pound sign in #x1\",\n\"#y1\", \"#x2\" and \"#y2\" MUST be included.\n\n<a class=\"hL\" href=\"#VLAYER.HLP__vla\">The Vlayer and Its Uses</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___cQ\"><span class=\"fA\">COPY BLOCK # # # # \"@filename\" #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___cR\"><span class=\"fA\">COPY OVERLAY BLOCK # # # # \"@filename\" #</span></a>\n\nThese commands copy from the board or overlay (respectively)\nto an MZM. The first two numbers are the x,y coordinates of the\nblock's upper-left corner; # # is the respective width and\nheight of the block; filename is the name of the file - the @\nis required. Finally, the last number determines the MZM type.\n1 saves as layer; 0 saves as board. 0 is integral for copying\nnon-graphical data (primarily Robots); 1 is proper for copying\ngraphical data, as the block's appearance will stay as exactly\nas it was when saved.\n\n<a class=\"hL\" href=\"#MZM.HLP__mzm\">Using MZMs</a>\n<a class=\"hL\" href=\"#TOVERLAY.HLP__081\">Editing and Using the Overlay</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___cS\"><span class=\"fA\">COPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"@filename\" #</span></a>\n\nThese commands act much like the above two commands, but they\ncopy from the vlayer to an MZM instead.\n\n<a class=\"hL\" href=\"#MZM.HLP__mzm\">Using MZMs</a>\n<a class=\"hL\" href=\"#VLAYER.HLP__vla\">The Vlayer and Its Uses</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___cT\"><span class=\"fA\">COPY BLOCK # # # # \"$string\" #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___cU\"><span class=\"fA\">COPY OVERLAY BLOCK # # # # \"$string\" #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___cV\"><span class=\"fA\">COPY (OVERLAY) BLOCK \"#x\" \"#y\" # # \"$string\" #</span></a>\n\nThese commands copy information from the board, overlay, or\nvlayer (respectively) to a given string. The first two numbers\nare the x,y coordinates of the upper-left corner of the block;\nthe third and fourth are its respective width and height; the\nlast is the terminating character's char value (i.e. a number\nfrom 0-255). The operation will end before all characters in the\ngiven range are read if either the parameter matching the\nterminating character, or a parameter of p00, is found. For the\nvlayer command, the pound sign is REQUIRED.\n\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n<a class=\"hL\" href=\"#TOVERLAY.HLP__081\">Editing and Using the Overlay</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___s2\"><span class=\"fA\">SAVE PLAYER POSITION</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___s3\"><span class=\"fA\">SAVE PLAYER POSITION #</span></a>\n\nSaves the player's coordinates and board in one of eight\ninternal slots (1-8). If no number is given, slot one is used.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___rB\"><span class=\"fA\">RESTORE PLAYER POSITION </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___rC\"><span class=\"fA\">RESTORE PLAYER POSITION # </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___rD\"><span class=\"fA\">RESTORE PLAYER POSITION # DUPLICATE SELF </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nRelocates the player to the coordinates and board stored in\nthe given slot, or slot number one if none is given. The third\nform also duplicates the current Robot where the player used to\nbe; this Robot then starts its program at the first command.\nThis command will do transition fades on activation, even if the\ndestination is the same board.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___eE\"><span class=\"fA\">EXCHANGE PLAYER POSITION </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___eF\"><span class=\"fA\">EXCHANGE PLAYER POSITION # </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___eG\"><span class=\"fA\">EXCHANGE PLAYER POSITION # DUPLICATE SELF </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nExchanges the player's current coordinates and board with the\ncoordinates and board stored in the given slot, or slot number\none if none is given. The third form also duplicates the\ncurrent Robot where the player used to be; this Robot then\nstarts its program at the first command. This command will do\ntransition fades on activation, even if the destination is the\nsame board.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___f2\"><span class=\"fA\">FILLHEALTH</span></a>\n\nThe player's health is filled to its maximum.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___cA\"><span class=\"fA\">CLEAR MESG</span></a>\n\nThe message line, if currently displayed, stops displaying its\ncurrent message.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___m1\"><span class=\"fA\">MESSAGE ROW #</span></a>\n\nThe top row of the message line is moved to the stated row. The\nrange is 0 to 24.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___sN\"><span class=\"fA\">SET MESG COLUMN #</span></a>\n\nChanges the message line to display its messages starting from\nthe given column. The range is 0 to 79 and the changes affect\neach row. (The actual message will start at this location, not\nthe message edge.)\n\n<a class=\"hA\" name=\"COMMAND2.HLP___c1\"><span class=\"fA\">CENTER MESG</span></a>\n\nChanges the message line to center its messages on its rows.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___l4\"><span class=\"fA\">LOAD CHAR SET \"file\"</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___l5\"><span class=\"fA\">LOAD PALETTE \"file\"</span></a>\n\nLoads in the specified character set or palette, exported using\nAlt+X from the world editor. The new character set or palette\nis permanent and affects the entire world until a new one is\nloaded. These files also can be loaded from a subdirectory with\nthe syntax of \"\"dir\"\\\\\"file\"\" or \"\"dir\"/\"file\"\" (without the\ninner quotes).\n\nCharacter sets can be loaded to the middle of the current\ncharset by prefixing the \"file\" name with +offset, where offset\nis a hexadecimal number, or @offset where offset is a decimal\nnumber. Up to 14 additional charsets can be loaded for use with\nunbound sprites. To set and access these, set the decimal\noffset number to (256*extra charset number); hexadecimal\noffsetting cannot access additional charsets. Naturally,\noffsets within these charsets can be set as well by adding the\ndesired offset value to these numbers.\n\nThese commands can also take string inputs in place of\nhard-coded filenames.\n\n<a class=\"hL\" href=\"#PARTIAL.HLP__par\">Partial Character Sets</a>\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___t5\"><span class=\"fA\">TELEPORT PLAYER \"string\" # # </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nTeleports the player to the given board at the stated x,y\ncoordinates. This happens instantaneously, even if this causes a\nmove between boards. Same-board TELEPORT PLAYER will trigger any\n\"justloaded\" labels and reset board time limits and entry\npositions, but will not otherwise act as a new board entry.\n\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___c4\"><span class=\"fA\">CHANGE OVERLAY [color] [char] [color] [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___c5\"><span class=\"fA\">CHANGE OVERLAY [color] [color]</span></a>\n\nAll of the stated character and color on the overlay are\nchanged to the new character and color. The second form doesn't\ncare about the character, only the color.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n<a class=\"hL\" href=\"#TOVERLAY.HLP__081\">Editing and Using the Overlay</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___m0\"><span class=\"fA\">MOVE ALL [color] [thing] [param] [dir] </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nAll instances of the stated thing with the color and parameter\ngiven are moved in the given direction. This can force objects\nonto lava that normally don't go onto lava, but cannot force any\nobjects onto goop.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___o1\"><span class=\"fA\">OPEN [dir]</span></a>\n\nIf there is a gate or door in the given direction, it will\nbe opened. If the door is going to open towards the Robot, the\nRobot will be pushed out of the way if necessary and possible.\nIt is suggested to wait a couple cycles before then continuing\non through the door. (Gates aren't a problem.)\nIf the gate or door is locked, it will be treated as if the\nplayer attempted to open it. This means that whether the door is\nopened or not depends on the player's held keys, and if the\nRobot unlocks the door, the player will lose the relevant key.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___m7\"><span class=\"fA\">MOD SAM # #</span></a>\n\nPlays a sample (instrument) from the current module file. The\nfirst number states the frequency at which to play it; the\nsecond is the number of the sample, from 1 on.\n\nThis command will not work in current versions. Support for\nolder worlds using this command is implemented only for the XMP\nmodule engine (the engine used for most release builds).\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___l0\"><span class=\"fA\">LOCKSCROLL</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___u2\"><span class=\"fA\">UNLOCKSCROLL</span></a>\n\nLocks or unlocks scrolling on the current board. When scrolling\nis locked, the view will remain showing the same thing, even if\nthe player moves. The screen will not scroll.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___c0\"><span class=\"fA\">CHAR EDIT [char] # # # # # # # # # # # # # #</span></a>\n\nChanges the appearance of the given character, using a 14-number\ncode of 8-bit numbers derived from which pixels are set per\nhorizontal line. To insert these codes easily, use F5 in the\nRobot editor, select the desired character, and press ESC. This\ncommand can access extended character sets.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___m5\"><span class=\"fA\">MOD FADE IN \"file\"</span></a>\n\nFades in the stated module/OGG music file, from 0 volume to 255\nvolume. These files also can be loaded from a subdirectory with\nthe syntax of \"\"dir\"\\\\\"file\"\" or \"\"dir\"/\"file\"\" (without the\ninner quotes).\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___m6\"><span class=\"fA\">MOD FADE OUT</span></a>\n\nFades out the current module/OGG music file, from the current\nvolume to 0 volume. Although the mod will be inaudible, it will\nstill be the current music file after the fade out.\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___m4\"><span class=\"fA\">MOD FADE # #</span></a>\n\nFades the current module/OGG music file to a given volume at the\ngiven rate. The first number is the target volume, the second\nnumber is the speed at which to fade. All negative numbers and\nnumbers over 255 will wrap around.\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___m2\"><span class=\"fA\">MISSILECOLOR [color]</span></a>\n\nChanges the color of all missiles fired from that point on.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___pB\"><span class=\"fA\">PUSH [dir]</span></a>\n\nPushes anything next to the Robot in the given direction one\nspace forward, if the object in that direction is pushable. The\nRobot itself does not move.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___sL\"><span class=\"fA\">SET EDGE COLOR [color]</span></a>\n\nChanges the color of the edging shown around the viewport.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___sM\"><span class=\"fA\">SET MAXHEALTH #</span></a>\n\nChanges the player's health limit, or maximum health rating.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___w2\"><span class=\"fA\">WAIT MOD FADE </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nWaits for the module/OGG music to finish its fading. Does\nnothing if music is not fading. (see MOD FADE IN, MOD FADE OUT,\nand MOD FADE)\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m5\">MOD FADE IN \"file\"</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m6\">MOD FADE OUT</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___m4\">MOD FADE # #</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___w3\"><span class=\"fA\">WAIT PLAY </span><span class=\"fF\">[</span><span class=\"fD\">!</span><span class=\"fF\">]</span></a>\n\nWaits for all queued sound effects, if any, to finish playing.\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___a2\"><span class=\"fA\">ASK \"string\"</span></a>\n\nBrings up a dialog box asking the player a question. The player\ncan select YES or NO. The Robot is then sent to either the YES\nor NO label, depending on the choice. If the player presses\nESC, the NO label is used.\n\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___a3\"><span class=\"fA\">AVALANCHE</span></a>\n\nEnacts the Avalanche potion/ring effect, placing boulders\nrandomly about the board. Often precedes the CHANGE c?? Boulder\np?? to c?? Explosion p?? command.\n\n<a class=\"hL\" href=\"#BUILTINS.HLP__prx\">Potion and Ring Effects</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___b4\"><span class=\"fA\">BECOME LAVAWALKER</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___b2\"><span class=\"fA\">BECOME NONLAVAWALKER</span></a>\n\nSets whether the current Robot can or cannot move on lava.\nRobots can always move over fire.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___b6\"><span class=\"fA\">BLIND #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___f1\"><span class=\"fA\">FIREWALKER #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___f4\"><span class=\"fA\">FREEZETIME #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___sS\"><span class=\"fA\">SLOWTIME #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___w6\"><span class=\"fA\">WIND #</span></a>\n\nThe stated potion/ring effect activates for the given number of\ncycles. Potion/ring effects are global; that is, they affect the\nentire world. The only exception is with the global Robot, as\nFREEZETIME and SLOWTIME commands do not affect it.\n\n<a class=\"hL\" href=\"#THEGLOBL.HLP__gbl\">The Global</a>\n<a class=\"hL\" href=\"#BUILTINS.HLP__prx\">Potion and Ring Effects</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___c3\"><span class=\"fA\">CHANGE CHAR ID # [char]</span></a>\n\nChanges the character/color/number at the stated (numeric)\nlocation within the CHAR ID table. The CHAR ID table is\ninternal and is covered in another section.\n\n<a class=\"hL\" href=\"#CHANGECH.HLP__1st\">CHANGE CHAR ID - The CHAR ID Table</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___c6\"><span class=\"fA\">CHANGE SFX # \"string\"</span></a>\n\nThe sound effect referenced by the given number is changed to\nthe new string. The numbers refer to the sound events editable\nthrough ALT+F.\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">Sound and Music</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___c8\"><span class=\"fA\">CHANGE THIN ARROW CHAR [dir] [char]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___c7\"><span class=\"fA\">CHANGE THICK ARROW CHAR [dir] [char]</span></a>\n\nChanges the character used to represent thin arrows (for guns)\nor thick arrows (for pushers and spikes) for the specified\ndirection.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___iP\"><span class=\"fA\">INPUT STRING \"string\"</span></a>\n\nAsks the player to input a string, presenting the given string\nas a prompt. The inputted string is stored in memory. This\nstring can be displayed within box messages or on the message\nrow by using &amp;INPUT&amp; within a message. Inputted strings are\nlocal to the current board.\n\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___iD\"><span class=\"fA\">IF STRING \"string\" \"label\"</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___iF\"><span class=\"fA\">IF STRING NOT \"string\" \"label\"</span></a>\n\nJumps to the given label if the inputted string is or is not\nequal to the given string. Case is ignored.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___iE\"><span class=\"fA\">IF STRING MATCHES \"string\" \"label\"</span></a>\n\nIf the inputted string matches a given pattern, jump to the\ngiven label. (Case is ignored.) The pattern is a series of\ncharacters and symbols to match. Characters must match exactly.\nThe following symbols apply:\n\n# matches any number from 0-9\n_ matches any letter from A-Z or a-z\n? matches any character\n* matches the remainder of the input string\n\n<a class=\"hA\" name=\"COMMAND2.HLP___i9\"><span class=\"fA\">IF FIRST STRING \"string\" \"label\"</span></a>\n\nIf the first word in the inputted string (i.e. everything up\nto the first space (char 32) or end of input) matches the given\nstring, jump to the given label.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___cB\"><span class=\"fA\">CLIP INPUT</span></a>\n\nThis command chops off the first word of the inputted string\n(everything before and including the first instance of a\nspace, aka char 32). For example, \"Hello there\" will now read\nin memory as \"there\".\n\n<a class=\"hA\" name=\"COMMAND2.HLP___iM\"><span class=\"fA\">INC \"$string\" \"text\"</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___iN\"><span class=\"fA\">INC \"$string\" \"$string2\"</span></a>\n\nThis command appends the given text or string to the end of a\ngiven string.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___dA\"><span class=\"fA\">DEC \"$string\" #</span></a>\n\nThis command clips the given string by the amount of characters\ngiven. Decreasing by negative numbers will not add characters to\nthe string.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___iO\"><span class=\"fA\">IF \"$string\" (equality) # \"label\"</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___iK\"><span class=\"fA\">IF \"$string\" (equality) \"text\" \"label\"</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___iL\"><span class=\"fA\">IF \"$string\" (equality) \"$string2\" \"label\"</span></a>\n\nThese commands jump to a given label if the string matches the\ngiven equality conditions (ONLY equal or not equal; others such\nas less than are automatically parsed as FALSE). These commands\ncompare the given string to a number, a line of text, or\nanother string respectively.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___iG\"><span class=\"fA\">IF cNN Sprite_Colliding pNN x y \"label\"</span></a>\n\nThis command, when the color is c??, determines whether the\nsprite numbered NN would collide if the sprite is moved by x\nhorizontally and y vertically. If it would, the program goes to\nthe given label and spr_clistN counters are set to the detected\ncollisions. c values other than c?? check if the sprite would\ncollide with anything currently at absolute x,y coordinates\ninstead. If the sprite numbered NN is unbound, movement amounts\nand absolute coordinates will be in pixels.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___iH\"><span class=\"fA\">IF c?? Sprite pNN x y \"label\"</span></a>\n\nThis command checks if the sprite numbered NN is currently at\nthe location x,y; if it is, the program goes to the given\nlabel. For multi-character sprites, the label is jumped to if\nany part of the sprite is at the given coordinates. Use p?? to\ncheck for any sprite. If p?? is used, SPR_NUM is also set to\nthe lowest sprite number at that location (if any).\n\nFor static sprites, this check is valid against the coordinates\nwhere they visually appear, not their actual locations.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___cF\"><span class=\"fA\">COLOR INTENSITY # PERCENT</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___cG\"><span class=\"fA\">COLOR INTENSITY # # PERCENT</span></a>\n\nThe first command changes the intensity of the entire palette\nfrom 0 to 255 percent (100 percent is a palette's default). The\nsecond command only changes one color, with the first number\nbeing the chosen color and the second number the intensity.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___cK\"><span class=\"fA\">COPY CHAR [char] [char]</span></a>\n\nThe character picture of the first character is copied to the\nsecond character. This command can access extended character\nsets.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___d5\"><span class=\"fA\">DISABLE MESG EDGE</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___e7\"><span class=\"fA\">ENABLE MESG EDGE</span></a>\n\nDisables or enables the edging on the message line. When\nenabled, all messages have a space (char 32) tacked onto each\nend for display clarity. This is enabled by default.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___d6\"><span class=\"fA\">DISABLE SAVING</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___e8\"><span class=\"fA\">ENABLE SAVING</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___e9\"><span class=\"fA\">ENABLE SENSORONLY SAVING</span></a>\n\nDisables or enables manual saving on this board, or enables\nmanual saving on sensors only.\n\n<a class=\"hL\" href=\"#SENSORSW.HLP__094\">Sensors</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___f3\"><span class=\"fA\">FLIP CHAR [char] [dir]</span></a>\n\nThe character picture of the stated character is flipped\nvertically if the direction is north/south, or horizontally if\nthe direction is east/west. This command can access extended\nchar sets.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___o2\"><span class=\"fA\">OVERLAY ON</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___o3\"><span class=\"fA\">OVERLAY STATIC</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___o4\"><span class=\"fA\">OVERLAY TRANSPARENT</span></a>\n\nTurns the overlay on (to normal mode), to static mode, or to\ntransparent mode.\n\n<a class=\"hL\" href=\"#TOVERLAY.HLP__081\">Editing and Using the Overlay</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___rE\"><span class=\"fA\">ROTATECW</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___rF\"><span class=\"fA\">ROTATECCW</span></a>\n\nRotates all pushable items around the Robot in a clockwise or\ncounter-clockwise direction, respectively. This is done in the\nsame manner as a CW Rotate or CCW Rotate object, except that\nthese commands rotate objects only one space.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___s4\"><span class=\"fA\">SCROLL CHAR [char] [dir]</span></a>\n\nThe character picture of the stated character is scrolled one\npixel in the given direction, with wraparound in effect. This\ncommand can access extended char sets, but it will not work as\nexpected in SMZX modes.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___s5\"><span class=\"fA\">SCROLLARROW COLOR [color]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___s6\"><span class=\"fA\">SCROLLBASE COLOR [color]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___s7\"><span class=\"fA\">SCROLLCORNER COLOR [color]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___s8\"><span class=\"fA\">SCROLLPOINTER COLOR [color]</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___s9\"><span class=\"fA\">SCROLLTITLE COLOR [color]</span></a>\n\nChanges the color used to represent a specific element of the\nScroll and box-message system:\n\nARROW   = The arrows used to denote options\nBASE    = The overall box and text color\nCORNER  = The color of the corners of the box, and of the\n          options in the option box at the bottom\nPOINTER = The color of the pointers on the left and right\nTITLE   = The color of the scroll/box title\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___s0\"><span class=\"fA\">SCROLLVIEW [dir] #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___sA\"><span class=\"fA\">SCROLLVIEW POSITION # #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___r0\"><span class=\"fA\">RESETVIEW</span></a>\n\nThe first two commands jump the current view a number of spaces\nin one direction or until the given coordinates are in the\nupper-left corner. RESETVIEW resets the view to normal, showing\nthe player in the center of the screen when possible. All of\nthese change to the target view instantaneously.\n\n<a class=\"hL\" href=\"#COMMANDS.HLP__dir\">Directions</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___sK\"><span class=\"fA\">SET COLOR # # # #</span></a>\n\nThe given color (stated by the first number) is changed to the\nRGB values of the next three numbers. The RGB values must range\nfrom 0 to 63. These are the same numbers shown in the palette\neditor.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___sU\"><span class=\"fA\">STATUS COUNTER # \"counter\"</span></a>\n\nThe status screen counter with the stated position # is changed\nto the given counter. Valid positions for status counters range\nfrom 1 (top slot) to 6 (bottom slot).\nStatus counters have a name limit of 14 characters. If the given\ncounter has a name over 14 characters long, this command will be\nignored.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__1st\">Counters, Built-in Counters and Local Counters</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___sV\"><span class=\"fA\">SWAP WORLD \"file\"</span></a>\n\nTransfers play to the starting board of the stated world file,\nwith the target world starting as freshly loaded. In contrast to\na launch from the title screen, a SWAP WORLD will retain all\ncounters, items, keys, strings, etc. from the current world.\nThe target file can be the same world that is currently running;\nthis is a simple way to \"reset\" the world while keeping progress\nin the form of items and counters.\n\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___pE\"><span class=\"fA\">PUT [color] [char] OVERLAY # #</span></a>\n<a class=\"hA\" name=\"COMMAND2.HLP___w7\"><span class=\"fA\">WRITE OVERLAY [color] \"string\" # #</span></a>\n\nPuts the given color/character at the given coordinates on the\noverlay, or writes a message in the given color starting at the\ngiven coordinates on the overlay.\n\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n<a class=\"hL\" href=\"#TOVERLAY.HLP__081\">Editing and Using the Overlay</a>\n<a class=\"hL\" href=\"#COMMANDS.HLP__col\">Colors</a>\n\n<a class=\"hA\" name=\"COMMAND2.HLP___v1\"><span class=\"fA\">VIEWPORT # #</span></a>\n\nMoves the viewport's upper-left corner to the given screen\ncoordinates. X ranges from 0 to 79, and Y ranges from 0 to 24.\n\n<a class=\"hA\" name=\"COMMAND2.HLP___v2\"><span class=\"fA\">VIEWPORT SIZE # #</span></a>\n\nChanges the size of the viewport. X size must be from 1 to 80,\nand Y size must be from 1 to 25.\n\n<a class=\"hL\" href=\"#COMMANDR.HLP__1st\">Command Reference (Part I)</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"BULLETTY.HLP\">\n<a class=\"hA\" name=\"BULLETTY.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Bullet Types</span></p>\nThere are three different types of bullets in MegaZeux: Player,\nEnemy, and Neutral. The player shoots Player bullets, enemies\nshoot Enemy bullets, and ricocheted bullets become Neutral\nbullets. A Robot can shoot any type of bullet, according to its\nBULLETTYPE counter, but it defaults to Neutral bullets.\n\nAll fired Enemy bullets, even those shot from Robots, become\nNeutral bullets when the option \"Enemies' bullets can hurt\nenemies\" is set. Only pre-placed Enemy bullets will remain Enemy\nbullets in this case.\n\nPlayer bullets can only hurt enemies. Enemy bullets can only\nhurt the player. Neutral bullets can hurt either. Robots go to\ndifferent labels when shot with each one, or to a generic SHOT\nlabel if there are none of the specific PLAYERSHOT, ENEMYSHOT,\nor NEUTRALSHOT labels.\n\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"CHANGECH.HLP\">\n<a class=\"hA\" name=\"CHANGECH.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">CHANGE CHAR ID - The CHAR ID Table</span></p>\nThe CHAR ID table has many uses. The first area of usage - the\nCHANGE CHAR ID # [char] command - basically changes the value\n(a character, color, or number) of a given type of item. These\nvalues correspond to the Global Chars or Damage tables from\nGlobal Info. The first number in the command states what is to\nchange. (Note - if noted, the number corresponds to a color or\ndamage. Otherwise, it is a character.) These numbers are also\nlisted next to things in the Global Edit Chars menus.\n\nThe second area - using the BOARD_ID, BIDx,y or UIDx,y counters\n- will change an actual item instead of that entry's value;\ni.e. these counters transform the actual item (and ONLY that\nitem) at a specific pair of coordinates from one type to\nanother.\n\nWARNING: Changing certain CHAR ID values will have decidedly\nspecial effects:\n\n<span class=\"fA\">*</span><span class=\"fF\">Changing the value of any Custom type will turn ALL of that</span>\ntype into the given character; same with text.\n<span class=\"fA\">*</span><span class=\"fF\">There is a special exception to this, though. If the value is</span>\n(char) 255, then all items of that type act like Custom objects\nand assume a character based on their parameter (e.g. p01\nobjects will use character 1). This can allow built-ins of the\nsame type that display different characters in-game at the same\ntime. Some built-ins may show interesting effects if set to\nthis, such as gaining animation frames.\n<span class=\"fA\">*</span><span class=\"fF\">Changing the CHAR IDs of objects with animations or four</span>\ndirectional characters will fix ALL animations/chars of that\nobject type to the given character.\n<span class=\"fA\">*</span><span class=\"fF\">It is inadvisable to use any UNUSED values.</span>\n<span class=\"fA\">*</span><span class=\"fF\">Using CHAR ID to change between actual items only works</span>\nwith values &lt;122. For example, changing something into Ice by\nsetting BOARD_ID to 160 will not work, but setting BOARD_ID to\n25 will. However, there is no such restriction with changing\nCHAR ID table values using CHANGE CHAR ID.\n<span class=\"fA\">*</span><span class=\"fF\">Damage values start at 327. Not all values are listed, because</span>\ntechnically you can assign damage values for things that cannot\ngive damage (i.e. pretty much everything not listed in the\ntable). If you really want to manipulate damage values for such\nitems, to find the CHAR ID damage number for an item, just add\n327 to it.\n\nNumber  Changes\n------  -------\n0       Space\n1       Normal\n2       Solid\n3       Tree\n4       Line\n5       Custom Block\n6       Breakaway\n7       Custom Break\n8       Boulder\n9       Crate\n10      Custom Push\n11      Box\n12      Custom Box\n13      Fake\n14      Carpet\n15      Floor\n16      Tiles\n17      Custom Floor\n18      Web\n19      Thick Web\n20      Still Water\n21      N Water\n22      S Water\n23      E Water\n24      W Water\n25      Ice\n26      Lava\n27      Chest\n28      Gem\n29      Magic Gem\n30      Health\n31      Ring\n32      Potion\n33      Energizer\n34      Goop\n35      Ammo\n36      Bomb\n37      Lit Bomb\n38      Explosion\n39      Key\n40      Lock\n41      Door\n42      Open Door\n43      Stairs\n44      Cave\n45      CW Rotate\n46      CCW Rotate\n47      Gate\n48      Open Gate\n49      Transport\n50      Coin\n51      N Moving Wall\n52      S Moving Wall\n53      E Moving Wall\n54      W Moving Wall\n55      Pouch\n56      Pusher\n57      Slider NS\n58      Slider EW\n59      Lazer\n60      Lazer Gun\n61      Bullet\n62      Missile\n63      Fire\n64      UNUSED\n65      Forest\n66      Life\n67-70   Whirlpool Anim 1-4\n71      Invis Wall\n72      Ricochet Panel\n73      Ricochet\n74      Mine\n75      Spike\n76      Custom Hurt\n77      Text\n78      Shooting Fire\n79      Seeker\n80      Snake\n81      Eye\n82      Thief\n83      Slimeblob\n84      Runner\n85      Ghost\n86      Dragon\n87      Fish\n88      Shark\n89      Spider\n90      Goblin\n91      Spitting Tiger\n92      Bullet Gun\n93      Spinning Gun\n94      Bear\n95      Bear Cub\n96      UNUSED\n97      Missile Gun\n98      Sprite\n99      Sprite Collision\n100     Image File\n101-121 UNUSED\n122     Sensor\n123     Pushable Robot\n124     Robot\n125     Sign\n126     Scroll\n127     Player\n128-143 Thin Lines (Web) (see below)\n144-159 Thick Lines (Thick Web, Lines) (see below)\n160     Ice\n161-163 Ice Anim 1-3\n164-166 Lava Anim 1-3\n167     Small Ammo (below 10)\n168     Large Ammo (above or equal to 10)\n169-175 Lit Bomb Anim 1-7\n176-183 Energizer Color Anim 1-8\n184-187 Explosion Colors 1-4\n188     Horizontal Door\n189     Vertical Door\n190-193 CW Rotate Anim 1-4\n194-197 CCW Rotate Anim 1-4\n198-229 UNUSED\n230-233 Transport N Anim 1-4\n234-237 Transport S Anim 1-4\n238-241 Transport E Anim 1-4\n242-245 Transport W Anim 1-4\n246-249 Transport All-Dir Anim 1-4\n250     Thick Arrow N (Pusher/Spike)\n251     Thick Arrow S (Pusher/Spike)\n252     Thick Arrow E (Pusher/Spike)\n253     Thick Arrow W (Pusher/Spike)\n254     Thin Arrow N (Guns)\n255     Thin Arrow S (Guns)\n256     Thin Arrow E (Guns)\n257     Thin Arrow W (Guns)\n258-261 Horizontal Lazer Anim 1-4\n262-265 Vertical Lazer Anim 1-4\n266-271 Fire Anim 1-6\n272-277 Fire Color Anim 1-6\n278-281 Life Anim 1-4\n282-285 Life Color Anim 1-4\n286     Ricochet Panel \\\n287     Ricochet Panel /\n288-289 Mine Anim 1-2\n290-291 Shooting Fire Anim 1-2\n292-293 Shooting Fire Color Anim 1-2\n294-297 Seeker Anim 1-4\n298-301 Seeker Color Anim 1-4\n302-305 Whirlpool Color Anim 1-4\n306-309 Player Bullets, NSEW\n310-313 Neutral Bullets, NSEW\n314-317 Enemy Bullets, NSEW\n318-321 Player, NSEW\n322     Player Color\n323     Missile Color\n324     Player Bullet Color\n325     Neutral Bullet Color\n326     Enemy Bullet Color\n353     Lava Damage\n365     Explosion Damage\n386     Lazer Damage\n388     Bullet Damage\n389     Missile Damage\n390     Fire Damage\n402     Spike Damage\n403     Custom Hurt Damage\n405     Shooting Fire Damage\n406     Seeker Damage\n407     Snake Damage\n410     Slimeblob Damage\n411     Runner Damage\n412     Ghost Damage\n413     Dragon Damage\n414     Fish Damage\n415     Shark Damage\n416     Spider Damage\n417     Goblin Damage\n418     Spitting Tiger Damage\n421     Bear Damage\n422     Bear Cub Damage\n\nTo change a character, use the following form:\n\n<span class=\"fA\">CHANGE CHAR ID # [char]</span>\n\n# is the number corresponding to the character that you want to\nchange, or a counter representing that number. [char] is the\ncharacter (or counter representing the character) that you wish\nto change it to.\n\nTo change a color, use the following form:\n\n<span class=\"fA\">CHANGE CHAR ID # [color]</span>\n\n# is the same as above, but corresponding to a color. [color]\nis the color or a counter representing the color that you wish\nto change it to.\n\nTo change damage values, use a number or counter in place of\nthe [char] or [color].\n\nAfter typing in a [color] or damage value, it may change to a\ncharacter representation, but the value will still remain the\nsame.\n\nTo change line characters:\n\nTake the base number for the type of line you wish to change\n(128 for thin, 144 for thick) and add one of the following\nvalues to represent which sides are connected via other web or\nlines:\n\n0  -\n1  N\n2  S\n3  N S\n4  E\n5  E N\n6  E S\n7  E N S\n8  W\n9  W N\n10 W S\n11 W N S\n12 W E\n13 W E N\n14 W E S\n15 W E N S\n\n<a class=\"hL\" href=\"#COMMAND2.HLP___c3\">CHANGE CHAR ID # [char]</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"BUILTINL.HLP\">\n<a class=\"hA\" name=\"BUILTINL.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Built-in Labels</span></p>\nRobotic has many built-in labels (messages) that are activated\non certain external events. Each label, and what triggers it,\nis listed below.\n\nSubroutine versions of most labels exist. If a built-in label\nhas a subroutine version, [#] will be listed to the right of\nits name. If both forms exist, they will both be triggered,\nwith the subroutine version being triggered first, and the\nnormal version triggered as the subroutine is left.\n\nSpecial care is required when dealing with built-in subroutine\nlabels. To prevent abuse of the stack, using LOCKPLAYER and/or\nZAP to keep subroutine calls to an absolute minimum is highly\nadvised. Subroutines themselves are explained in another\nsection.\n\n<a class=\"hL\" href=\"#SUBROUTE.HLP__sub\">Subroutines</a>\n\n<span class=\"fE\">TOUCH [#]</span>\n\nThis is sent when the player touches the Robot, that is, when\nthe player is next to it and tries moving into it. If the player\nis locked in that direction, this label is not sent.\n\n<span class=\"fE\">THUD [#]</span>\n\nThis is sent when the Robot is walking and it runs into a\nnon-pushable obstacle. This label is not sent when the obstacle\nis hit during a <span class=\"fE\">GO [dir] #</span><span class=\"fF\"> or the </span><span class=\"fE\">/ \"string\"</span><span class=\"fF\"> command. THUD</span>\nignores LOCKSELF. This label will be constantly triggered if the\ncondition persists.\n\n<a class=\"hL\" href=\"#COMMANDR.HLP___w5\">WALK [dir]</a>\n\n<span class=\"fE\">EDGE [#]</span>\n\nThis is sent when the Robot is walking and it runs into the\nedge of the board. If EDGE is not found, the Robot is sent to\nTHUD. This label is not sent when the edge of the board is hit\nduring the <span class=\"fE\">GO [dir] #</span><span class=\"fF\"> or the </span><span class=\"fE\">/ \"string\"</span><span class=\"fF\"> commands. EDGE</span>\nignores LOCKSELF. This label will be constantly triggered if the\ncondition persists.\n\n<a class=\"hL\" href=\"#COMMANDR.HLP___w5\">WALK [dir]</a>\n\n<span class=\"fE\">BOMBED [#]</span>\n\nThis is sent when the Robot is hit with an explosion.\n\n<span class=\"fE\">KEY? [#]</span>\n\nThis is sent when the player presses the given key. ? should be\nreplaced with the key to be scanned for. Characters a to z and 0\nto 9 work as expected. Shifted alphabetic characters will NOT\nwork, and will actually scan as their non-shifted counterparts.\nAny other character that can be outputted directly, such as \";\"\nor \"*\" or \"=\", will work, and \"key \" will detect the space bar.\nOther characters can be entered by pressing F3 and selecting\nthem from the grid, but they are usually not guaranteed to work.\nSpecial-case characters that can work with KEY? are character 8\n(BackSpace), character 9 (Tab), character 27 (Esc), character\n127 (Delete) and character 13 (Enter, though there's a better\nlabel just for this mentioned in the very next entry).\n\nThere is another way to scan for key presses: using counters.\nKey-scanning counters such as KEY_PRESSED, KEY, and KEY_CODE\nare detailed in the Miscellaneous Counters section of the help\nfile.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__msc\">Miscellaneous Counters</a>\n\n<span class=\"fE\">KEYENTER [#]</span>\n\nThis is sent when the player presses the Enter key.\n\n<span class=\"fE\">INVINCO [#]</span>\n\nThis is sent when the player grabs an energizer.\n\n<span class=\"fE\">PUSHED [#]</span>\n\nThis is sent when a pushable Robot is pushed by something. If\nsomething tries to push the Robot and can't, this label is not\nsent.\n\n<span class=\"fE\">ENEMYSHOT [#]</span>\n<span class=\"fE\">PLAYERSHOT [#]</span>\n<span class=\"fE\">NEUTRALSHOT [#]</span>\n\nOne of these is sent when the Robot is shot, correlating to the\ntype of bullet used. If the appropriate label is not found, the\nRobot is sent to SHOT instead.\n\n<span class=\"fE\">SHOT [#]</span>\n\nThis is sent when the Robot is shot by a bullet and the more\nspecific label cannot be found.\n\n<span class=\"fE\">PLAYERHIT [#]</span>\n\nThis is sent when the player is shot with a bullet. If not\nfound, the Robot is sent to PLAYERHURT instead.\n\n<span class=\"fE\">PLAYERHURT [#]</span>\n\nThis is sent when the player is hurt by anything other than a\nbullet. (This includes when Robots successfully TAKE HEALTHS.)\nBullets send the Robot to PLAYERHIT, and only send it to\nPLAYERHURT if PLAYERHIT cannot be found.\n\n<span class=\"fE\">PLAYERDIED [#]</span>\n\nThis is sent when the player dies, whether through health being\nlowered to/below 0 or through the ENDLIFE command. This will not\ntrigger when the LIVES counter is decremented.\n\nNOTE: Unlike other built-in labels, this label is jumped to on\nthe cycle after it is sent, not the same cycle.\n\n<span class=\"fE\">SPITFIRE [#]</span>\n\nThis is sent when the Robot is hit with shooting fire.\n\n<span class=\"fE\">JUSTLOADED</span>\n\nThis is sent when the world was just loaded or restored from a\nsaved game. This is also sent upon swapping worlds; in that\ncase, it will only trigger if no JUSTENTERED label is set.\n\n<span class=\"fE\">JUSTENTERED</span>\n\nThis is sent when the board was just entered or the world was\njust started on this board. This is also sent upon swapping\nworlds; in that case, if no JUSTENTERED label is set,\nJUSTLOADED is triggered instead.\n\n<span class=\"fE\">SENSORON</span>\n\nThis is sent by a Sensor when it is stepped upon by the player,\nor when a Sensor moves beneath the player.\n\n<a class=\"hL\" href=\"#SENSORSW.HLP__094\">Sensors</a>\n\n<span class=\"fE\">SENSORTHUD</span>\n\nThis is sent by a Sensor when it tries to move and is blocked.\n\n<a class=\"hL\" href=\"#SENSORSW.HLP__094\">Sensors</a>\n\n<span class=\"fE\">SENSORPUSHED</span>\n\nThis is sent by a Sensor when it is pushed by something.\n\n<a class=\"hL\" href=\"#SENSORSW.HLP__094\">Sensors</a>\n\n<span class=\"fE\">LAZER [#]</span>\n\nThis is sent when the Robot is hit with a lazer.\n\n<span class=\"fE\">GOOPTOUCHED [#]</span>\n\nThis is sent when the player touches Goop.\n\n<span class=\"fE\">YES</span>\n\nThis is sent when the YES button is selected in an ASK box.\n\n<span class=\"fE\">NO</span>\n\nThis is sent when either the NO button is selected in an ASK\nbox, or when ESC exits an ASK box.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP___a2\">ASK \"string\"</a>\n\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#SENSORSW.HLP__094\">Sensors</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"COUNTERS.HLP\">\n<a class=\"hA\" name=\"COUNTERS.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Counters, Built-in Counters and Local Counters</span></p>\nCounters are built-in variables, or stored values with names.\nEach counter has a name, such as \"Yellow\" or \"Apple Pies\" or\neven \"100\". A counter can hold a value from -2147483648 to\n2147483647, in most cases. A counter is \"active\" when it has a\nvalue other than 0. There can be a practically unlimited amount\nof active counters at any one time, in addition to the built-in\ncounters. Counters retain values between different boards.\n\nLocal counters, on the other hand, only hold their values for\none Robot. These are useful because multiple Robots can set\nlocal counters with the exact same names without causing\nconflicts. There are several local counters explicitly reserved\nfor use; these are the \"local\" &amp; \"local2\" through \"local32\"\ncounters.\n\nTo display a counter's number, enclose the counter in\nampersands. (e.g. &amp;counter&amp;). This displays the counter value\nin decimal. Sometimes hexadecimal is needed, like in certain\nMegaZeux commands. To output a counter number in hex, add a\n+ to the front of the enclosed counter (e.g. &amp;+hexcount&amp;).\nLastly, other MegaZeux commands might require a two-digit\nhex number; for these, add a # to the front of the enclosed\ncounter (e.g. &amp;#twodigithex&amp;). Note that in this case, only the\ntwo most significant digits will be used (e.x. the number\n'abcdef' becomes 'AB') and single-digit numbers will gain a\nleading zero (e.g. the number '8' will become '08').\n\nAlso, characters can be used as counters in commands. For\nexample: SET \"boom\" to '0' would come out as SET \"boom\" to 48.\nIt's quite a difference!\n\nCommands that check or modify counters will accept numbers\nranging from -32768 to 32767. To use numbers outside of this\nrange with these commands, one must put the number in\nparentheses. For example: SET \"bonus\" to \"(1000000)\".\n\nThe following is a list of built-in counters, which all have\nspecial meanings. There are four types of built-in counters:\nGlobal, which affect everything; Local, which are different for\neach Robot and require special handling for other Robots to\nmanipulate; Board, which are specific to the current board; and\nUniversal, which persist for the entire time MegaZeux is\nrunning. Some built-in counters are read-only; a few are\nwrite-only.\n\nMegaZeux utilizes a large number of built-in counters to\nmaximize flexibility. For this reference, they have been\ndivided into distinct sections.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__ply\">Player Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__rob\">Robot Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__brd\">Board Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__auc\">Audio Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__mth\">Mathematical Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__fac\">File Access Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__spc\">Sprite Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__stc\">String Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__szc\">Super MZX Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__cha\">Character Edit Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__nkp\">Mouse/Joystick Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__def\">Defaults Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__vlc\">Vlayer Counters</a>\n<a class=\"hL\" href=\"#COUNTERS.HLP__msc\">Miscellaneous Counters</a>\n\n<p class=\"hC\">Player Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__ply\"> </a>\n<span class=\"fB\">GEMS, AMMO, LOBOMBS, HIBOMBS, COINS, LIVES, HEALTH</span>\n\nThese contain the current number of the stated item that the\nplayer currently has.\n\n<span class=\"fB\">INVINCO</span>\n\nThe number of cycles of invincibility the player currently has.\n\n<span class=\"fB\">SCORE</span>\n\nThe player's current score.\n\n<span class=\"fB\">PLAYERLASTDIR</span>\n\nThe last direction the player moved: 0 for none, or 1 2 3 4 for\nN S E W. Used to determine ice slippage and certain other\neffects.\n\n<span class=\"fB\">PLAYERFACEDIR</span>\n\nThe direction the player is facing: 0 1 2 3 for N S E W. Used\nto determine which player character to display.\n\n<span class=\"fB\">PLAYERX (read-only)</span>\n<span class=\"fB\">PLAYERY (read-only)</span>\n\nThe x or y coordinate of the player, respectively.\n\n<p class=\"hC\">Robot Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__rob\"> </a>\n<span class=\"fB\">PLAYERDIST (read-only, local)</span>\n\nThe distance from the Robot to the player, in spaces. The sum\nof HORIZPLD and VERTPLD.\n\n<span class=\"fB\">HORIZPLD (read-only, local)</span>\n<span class=\"fB\">VERTPLD (read-only, local)</span>\n\nThe horizontal or vertical distance from the Robot to the\nplayer, in spaces.\n\n<span class=\"fB\">THISX (read-only, local)</span>\n<span class=\"fB\">THISY (read-only, local)</span>\n\nThe current X or Y coordinate of the Robot. Affected by\nprefixes - If a command that uses THISX and THISY is prefaced\nwith a REL PLAYER command, THISX and THISY will be generated\nbased on the player's current position. For example, if the\nplayer is at (2,2) and the Robot is at (5,5), THISX and THISY\nwill be 3. If the player is at (8,8), THISX and THISY will be\n-3. If a command that uses THISX and THISY is prefaced with\na REL COUNTERS command, THISX and THISY will be generated\nbased on XPOS and YPOS. Basically, THISX and THISY will be\nset to THISX-XPOS and THISY-YPOS.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP__pre\">REL COUNTERS</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___r2\">REL PLAYER</a>\n\n<span class=\"fB\">BULLETTYPE (local)</span>\n\nThe current type of bullet shot by the Robot: 0 for Player, 1\nfor Neutral, and 2 for Enemy. Technically, this counter can\nhold any number from 0 to 255, but only 0, 1 and 2 have\ndefined behavior. The others essentially set the Robot to shoot\nEnemy bullets, if actually used to affect shot bullets.\n\n<a class=\"hL\" href=\"#BULLETTY.HLP__1st\">Bullet Types</a>\n\n<span class=\"fB\">COMMANDS</span>\n\nThe number of commands executed per Robot cycle (40 is default).\nThis number is global. It can severely slow MegaZeux if set too\nhigh and coupled with poor coding (especially busy loops lacking\ncycle-ending commands).\n\n<span class=\"fB\">LOOPCOUNT (local)</span>\n\nThe counter used for looping commands in Robotic - LOOP START,\nLOOP #, and ABORT LOOP.\n\n<a class=\"hL\" href=\"#COMMANDR.HLP___lC\">LOOP START</a>\n\n<span class=\"fB\">ROBOT_ID (read-only, local)</span>\n\nThe internal ID of the Robot. These numbers match the parameters\nof Robots on the board, but are in decimal. So, for example, if\na Robot on the board has a parameter of p0a, its ROBOT_ID would\nbe 10.\n\n<span class=\"fB\">RID&lt;Robot name&gt; (read-only)</span>\n<span class=\"fB\">ROBOT_ID_&lt;Robot name&gt; (read-only)</span>\n\nBoth of these give the robot_id number of the given named\nRobot. In the case of multiple Robots with the same name, the\nID number corresponds to only one of them. If no Robots have the\nname, or if the name in question only belongs to the global\nRobot, -1 is given.\n\nBEWARE: The existence of this counter makes it impossible to\nuse words which start with \"rid\" as counters. The port will NOT\nfall through and give the values of any counter that starts\nwith \"rid\"; instead, it ends at -1 (since the Robot the counter\nis trying to find, like \"dles\" (for \"riddles\"), will most\nlikely not exist). Older games will allow counters with names\nlike this, but only for compatibility reasons.\n<a class=\"hA\" name=\"COUNTERS.HLP__rnc\"> </a>\n<span class=\"fB\">Rn.&lt;counter&gt;</span>\n\nContains the value of the given counter (without the angle\nbrackets) in the Robot with the robot_id number n. If no Robot\nexists with this robot_id, the counter will return -1.\n\n<span class=\"fB\">THIS_COLOR (read-only, local)</span>\n<span class=\"fB\">THIS_CHAR (read-only, local)</span>\n\nThe current color and character of the Robot, respectively.\n\n<span class=\"fB\">LAVA_WALK (local)</span>\n\nControls whether the Robot can walk on lava or not. Setting\nthis to 1 is the equivalent of using the BECOME LAVAWALKER\ncommand. Setting it to 0 is the same as BECOME NONLAVAWALKER.\nLike BULLETTYPE, this counter can hold any value from 0 to 255,\nbut only 0 and 1 have defined behavior. This was a popular\nlocal counter before the local counters were plentiful, as most\nRobots didn't deal with lava.\n\n<a class=\"hL\" href=\"#COMMANDR.HLP___b4\">BECOME LAVAWALKER</a>\n\n<span class=\"fB\">LOCAL (local)</span>\n<span class=\"fB\">LOCAL2 (local)</span>\n..\n<span class=\"fB\">LOCAL32 (local)</span>\n\nThe non-special local counters each Robot can use. In effect,\nany number can be used, not just 2-32; the counter accessed by\nit would be N%32 (so, for example, any change to \"local0\" would\nhave the same effect as changing \"local32\"). LOCAL is the same\nas LOCAL1.\n\n<span class=\"fB\">GOOP_WALK (local)</span>\n\nControls whether the Robot can walk on goop or not. A value of 0\nprevents walking on goop; a non-zero value allows it. This\ncounter has an effective range of 0 to 255.\n\n<p class=\"hC\">Board Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__brd\"> </a>\n\n<span class=\"fB\">BOARD_X</span>\n<span class=\"fB\">BOARD_Y</span>\n\nBOARD_X and BOARD_Y represent the x,y coordinates of a target\nlocation on the current board. Several other counters use the\nvalues of these counters to read or alter parts of the board.\nThese counters default to 0.\n\n<span class=\"fB\">BOARD_COLOR (read-only, board)</span>\n<span class=\"fB\">BOARD_CHAR (read-only, board)</span>\n\nReads the color and character, respectively, of the object\nlocated at \"board_x\" \"board_y\". If board_x or board_y is an\ninvalid coordinate, BOARD_COLOR and BOARD_CHAR access the\nclosest valid coordinate. For example, if board_y is 60 and the\nboard is 50 characters tall, board_y is treated as if it were\n49.\n\nOne thing to note is that BOARD_COLOR will not take background\ncolor 0's transparency into account. For example, assuming\ndefault colors, if a white-on-black Robot walks onto a\nyellow-on-blue floor tile, the Robot appears as white-on-blue on\nthe screen, but this counter will still report it as\nwhite-on-black instead of the displayed colors.\n\n<span class=\"fB\">BOARD_ID</span>\n\nReads or alters the CHAR ID value of the object located at\n\"board_x\" \"board_y\". If board_x or board_y is an invalid\ncoordinate, BOARD_ID accesses the closest valid coordinate.\nFor example, if board_x is 60 and the board is 50 characters\nwide, board_x is treated as if it were 49.\n\n<a class=\"hL\" href=\"#CHANGECH.HLP__1st\">CHANGE CHAR ID - The CHAR ID Table</a>\n\n<span class=\"fB\">BOARD_PARAM</span>\n\nReads or alters the parameter of the object located at \"board_x\"\n\"board_y\". If board_x or board_y is an invalid coordinate,\nBOARD_PARAM accesses the closest valid coordinate. For example,\nif board_y is a negative number, board_y is treated as if it\nwere 0.\n\n<span class=\"fB\">BOARD_W (read-only, board)</span>\n<span class=\"fB\">BOARD_H (read-only, board)</span>\n\nThe current board's width and height, respectively.\n\n<span class=\"fB\">TIME (board)</span>\n\nThe current timer value, between 0 and the time limit for the\ncurrent board. If there is no time limit set, setting this\ncounter will cause the timer to tick down but turn off after it\nruns out.\n\n<span class=\"fB\">TIMERESET (board)</span>\n\nThe time limit for the current board. 0 for no time limit.\nNOTE: This timer was erroneously called \"TIMERSET\" in some\nversions. MZX will NOT use TIMERSET as a synonym for this\ncounter in current versions.\n\n<span class=\"fB\">SCROLLEDX (read-only, board)</span>\n<span class=\"fB\">SCROLLEDY (read-only, board)</span>\n\nThe top-left corner of the screen's x or y coordinates,\nrespectively.\n\n<span class=\"fB\">BCHx,y (read-only, board)</span>\n\nAllows the reading of single characters to the board with\nregards to coordinates x,y. The comma must be included. Returns\n-1 for invalid coordinates.\n\n<span class=\"fB\">BCOx,y (read-only, board)</span>\n\nAllows the reading of single colors to the board with regards\nto coordinates x,y. The comma must be included.  Returns -1 for\ninvalid coordinates. Much like BOARD_COLOR, BCOx,y will not take\ntransparency into account.\n\n<span class=\"fB\">BIDx,y (board)</span>\n\nAllows reading and writing of the CHAR ID of an object on the\nboard at the coordinates x,y. The comma must be included.\nReturns -1 for invalid coordinates.\n\n<span class=\"fB\">BPRx,y (board)</span>\n\nAllows reading and writing parameter values of an object on the\nboard at the coordinates x,y. The comma must be included.\nReturns -1 for invalid coordinates.\n\n<span class=\"fB\">UCHx,y (read-only, board)</span>\n\nAllows the reading of single characters on the under layer at\nthe coordinates x,y. The comma must be included.\n\n<span class=\"fB\">UCOx,y (read-only, board)</span>\n\nAllows the reading of single colors on the under layer at the\ncoordinates x,y. The comma must be included.\n\n<span class=\"fB\">UIDx,y (board)</span>\n\nAllows reading and writing of the CHAR ID of an object on the\nunder layer at the coordinates x,y. The comma must be included.\n\n<span class=\"fB\">UPRx,y (board)</span>\n\nAllows reading and writing parameter values of an object on the\nunder layer at the coordinates x,y. The comma must be included.\n\nAll of the above four under layer counters will return -1 if\nnothing is on the under layer at that location, as well as for\ninvalid coordinates. Floor-type objects by themselves (such as\nfake, floor, carpet, ice, lava, etc) count as being on the board\nlayer if nothing is on them.\n\n<span class=\"fB\">OVERLAY_X (board)</span>\n<span class=\"fB\">OVERLAY_Y (board)</span>\n<span class=\"fB\">OVERLAY_CHAR (read-only, board)</span>\n<span class=\"fB\">OVERLAY_COLOR (read-only, board)</span>\n\nThese work in the same way as the BOARD_xxx counters above, but\ndeal with the overlay instead. OVERLAY_CHAR and OVERLAY_COLOR\nreturn -1 if the overlay is off or if the coordinates are\ninvalid.\n\n<span class=\"fB\">OCHx,y (read-only, board)</span>\n\nAllows the reading of single characters on the overlay at the\ncoordinates x,y. The comma must be included. Returns -1 if the\noverlay is off or if the coordinates are invalid. \n\n<span class=\"fB\">OCOx,y (read-only, board)</span>\n\nAllows the reading of single colors on the overlay at the\ncoordinates x,y. The comma must be included. Returns -1 if the\noverlay is off or if the coordinates are invalid.\n\n<span class=\"fB\">OVERLAY_MODE (read-only, board)</span>\n\nShows the state of the overlay. 0 is off, 1 is normal, 2 is\nstatic and 3 is transparent.\n\n<span class=\"fB\">VIEWPORT_X (read-only, board)</span>\n<span class=\"fB\">VIEWPORT_Y (read-only, board)</span>\n\nContains the x,y coordinates of the upper-left corner of the\ncurrent board's viewport.\n\n<span class=\"fB\">VIEWPORT_WIDTH (read-only, board)</span>\n<span class=\"fB\">VIEWPORT_HEIGHT (read-only, board)</span>\n\nContains the respective width and height of the current board's\nviewport.\n\n<p class=\"hC\">Audio Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__auc\"> </a>\n\n<span class=\"fB\">MOD_FREQUENCY (board)</span>\n\nControls the frequency (in Hz) of the currently playing module/\nOGG music. Note that this value is NOT saved by save or counter\nfiles, so if necessary, it needs set again after a save gets\nloaded.\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__frq\">SAM, OGG, MOD and WAV Frequencies</a>\n\n<span class=\"fB\">MOD_ORDER (board)</span>\n\nControls the current mod order of the currently playing mod.\n\n<span class=\"fB\">MOD_POSITION (board)</span>\n\nControls the current row in tracked modules, or the current PCM\nsample in OGGs.\n\n<span class=\"fB\">MOD_LENGTH (read-only, board)</span>\n\nContains the length of the currently playing mod. This counter's\nvalue shows number of rows for module files and number of\nsamples for OGG files.\n\nNOTE: This counter will not work if MegaZeux was built with the\nMikmod engine. This is a minor concern due to Mikmod never being\nthe default module engine for MegaZeux.\n\n<span class=\"fB\">MOD_LOOPSTART</span>\n<span class=\"fB\">MOD_LOOPEND</span>\n\nThese counters control looping in the currently-playing music.\nMOD_LOOPSTART marks the beginning position of a loop;\nMOD_LOOPEND marks the end. If MOD_LOOPEND is 0, no looping will\noccur. Despite the counter names, they do not work on tracked\nmodules, only OGG files.\n\n<p class=\"hC\">Mathematical Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__mth\"> </a>\n\n<span class=\"fB\">ABSn</span>\n\nGives the absolute value of the number n.\n\n<span class=\"fB\">SQRTn</span>\n\nGives the integer approximation of the square root of n. Results\nare truncated, not rounded.\n\n<span class=\"fB\">MINx,y</span>\n\nTakes the numbers x and y and outputs the smaller number. The\ncomma is required.\n\n<span class=\"fB\">MAXx,y</span>\n\nTakes the numbers x and y and outputs the larger number. The\ncomma is required.\n\n<span class=\"fB\">MULTIPLIER</span>\n\nMultiplies values output by the normal trigonometric functions\n(sin, cos, tan) by the given number. Defaults to 10000.\n\n<span class=\"fB\">DIVIDER</span>\n\nDivides the number given to the inverse trigonometric functions\n(asin, acos, atan) by the given number. Defaults to 10000.\n\n<span class=\"fB\">C_DIVISIONS</span>\n\nDetermines the number of divisions of the base circle in\nMegaZeux's trigonometric functions. Defaults to 360.\n\n<span class=\"fB\">SINn</span>\n<span class=\"fB\">COSn</span>\n<span class=\"fB\">TANn</span>\n<span class=\"fB\">ASINn</span>\n<span class=\"fB\">ACOSn</span>\n<span class=\"fB\">ATANn</span>\n<span class=\"fB\">ARCTANdy,dx</span>\n\nAccesses MegaZeux's sine, cosine, tangent, inverse sine,\ninverse cosine, inverse tangent, and atan2 functions\n(respectively).\n\n<a class=\"hL\" href=\"#TRIG.HLP__tri\">Trigonometric Functions</a>\n\n<p class=\"hC\">File Access Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__fac\"> </a>\n\n<span class=\"fB\">FREAD_OPEN (function)</span>\n<span class=\"fB\">FWRITE_OPEN (function)</span>\n\nOpens a file for reading from or writing to, respectively. The\nsyntax is as such:\n\n<span class=\"fE\">set \"file.ext\" to \"FREAD_OPEN\"</span>\n\nFREAD_OPEN will also read subdirectories with the given syntax:\n\n<span class=\"fE\">set \"dir\" to \"FREAD_OPEN\"</span>\n\n<span class=\"fB\">FWRITE_MODIFY (function)</span>\n\nThis opens an existing file for reading without overwriting it,\nstarting at the beginning of the file.\n\n<span class=\"fB\">FWRITE_APPEND (function)</span>\n\nThis opens a file for writing at the END of the file. Note that\nall FWRITE_POS functionality is ignored in this mode.\n\n<span class=\"fB\">FREAD_COUNTER (function)</span>\n\nReads four bytes from the open file.\n\n<span class=\"fB\">FWRITE_COUNTER (function)</span>\n\nWrites four bytes to the open file.\n\nNOTE: The above two counters will have different functionality\nin worlds made before MZX 2.82 (in the old implementation, they\nuse two bytes and are unsigned). This is purely for\ncompatibility reasons.\n\n<span class=\"fB\">FREAD_POS</span>\n<span class=\"fB\">FWRITE_POS</span>\n\nThe current reading or writing position of the accessed file.\n0 is the beginning position; when no file is loaded, the\nrelevant counter is -1. If set to -1, the position is set to the\nend of the file.\n\n<span class=\"fB\">FREAD_DELIMITER</span>\n<span class=\"fB\">FWRITE_DELIMITER</span>\n\nThe character considered the terminator for fread and fwrite\noperations, respectively.\n\n<span class=\"fB\">FWRITE (function)</span>\n<span class=\"fB\">FREAD (function)</span>\n\nRespectively writes to and reads from the open file. With\ncounters, these counters only write/read a single byte at a\ntime; with strings, FWRITE will write a full string and tack the\nterminator character to the end, and FREAD will read a full\nstring up to the terminator.\n\n<span class=\"fB\">FWRITEn (function)</span>\n<span class=\"fB\">FREADn (function)</span>\n\nRespectively writes to and reads from the open file, but will\nlimit action to n number of characters. These counters only work\nwith strings, not with counters, and will neither place nor\nrespect terminator characters.\n\n<span class=\"fB\">FREAD_LENGTH</span>\n\nReturns the length in characters of the open file in read mode.\nIf this file does not exist, returns -1. If a directory is open,\nreturns the number of items (files and subdirectories) in that\ndirectory.\n\n<span class=\"fB\">FWRITE_LENGTH</span>\n\nReturns the length in characters of the open file in write mode.\n\n<span class=\"fB\">SAVE_ROBOT (function)</span>\n<span class=\"fB\">LOAD_ROBOT (function)</span>\n<span class=\"fB\">SAVE_ROBOTn (function)</span>\n<span class=\"fB\">LOAD_ROBOTn (function)</span>\n\nSaves or loads Robotic code to or from plaintext files,\nrespectively. The first forms save/load the current Robot,\nwhile the second forms save/load the Robot with the Robot ID of\nthe number n.\n\nOne can also load a string as a Robot instead of a text file, or\nsave a Robot into a given string.\n\n<span class=\"fB\">SAVE_BC (function)</span>\n<span class=\"fB\">LOAD_BC (function)</span>\n<span class=\"fB\">SAVE_BCn (function)</span>\n<span class=\"fB\">LOAD_BCn (function)</span>\n\nSaves or loads Robotic code to or from bytecode files,\nrespectively. The first forms save/load the current Robot,\nwhile the second forms save/load the Robot with the Robot ID of\nthe number n.\n\nKeep in mind that due to slated changes in MegaZeux, saving out\nRobots (in either text or bytecode form) is considered a\ndeprecated function and may be removed entirely in the future.\n\n<span class=\"fB\">SAVE_GAME (function)</span>\n<span class=\"fB\">LOAD_GAME (function)</span>\n\nSaves or loads MZX savegames, respectively.\n\n<span class=\"fB\">SAVE_COUNTERS (function)</span>\n<span class=\"fB\">LOAD_COUNTERS (function)</span>\n\nSaves or loads MZX counter files, respectively.\n\n<a class=\"hL\" href=\"#FILEACSS.HLP__fil\">File Access</a>\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n\n<p class=\"hC\">Sprite Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__spc\"> </a>\n\n<span class=\"fB\">SPRn_WIDTH</span>\n<span class=\"fB\">SPRn_HEIGHT</span>\n\nThe dimensions of sprite n. (x and y dimensions, respectively.)\n\n<span class=\"fB\">SPRn_X</span>\n<span class=\"fB\">SPRn_Y</span>\n\nThe current position of sprite n. These can be used to set the\nsprite outside the edges of the board, whereas PUT c?? Sprite\np?? X Y disallows this.\n\n<span class=\"fB\">SPRn_REFX</span>\n<span class=\"fB\">SPRn_REFY</span>\n\nWhere sprite n's appearance is stored - either on the board or\nthe vlayer (see SPRn_VLAYER). REFX is the x-coordinate, REFY is\nthe y-coordinate. Whatever this looks like at this position is\nwhat the sprite will look like when drawn, including the colors\nif c?? is used when placing the sprite.\n\n<span class=\"fB\">SPRn_CWIDTH</span>\n<span class=\"fB\">SPRn_CHEIGHT</span>\n\nThe dimensions of the collision box around sprite n. (X and Y\ndimensions, respectively)\n\n<span class=\"fB\">SPRn_CX</span>\n<span class=\"fB\">SPRn_CY</span>\n\nThe location, relative to sprite n's upper left corner, of the\nsprite's collision box (X and Y coordinates, respectively).\nThese have default values of 0.\n\n<span class=\"fB\">SPR_CLISTn (read-only)</span>\n\nA list of flagged collisions after a collision test was\nperformed. Values of n from 0 to (SPR_COLLISIONS - 1) are\nvalid. Each valid SPR_CLISTn value lists the number of a sprite\ninvolved in a collision with the checked sprite, arranged in\nascending sprite order. If the foreground collided, SPR_CLIST0\nis always set to -1 to indicate this.\n\n<span class=\"fB\">SPR_COLLISIONS (read-only)</span>\n\nThe number of sprite/foreground collisions reported by\nSPR_CLISTn counters.\n\n<span class=\"fB\">SPRn_CCHECK (write-only)</span>\n\nIf set to 1 for sprite n, overlapping char 32s of a sprite,\nregardless of whether char 32 is blank, will not cause a\ncollision between that sprite and other sprites or the\nforeground. If set to 2, fully blank chars of a sprite will not\ncollide, and blank characters of a sprite will not be drawn. If\nset to 3 - a setting only valid for unbound sprites - the given\nunbound sprite will undergo pixel-precise collision checks, and\ntransparent colors will not cause a collision. Setting to 0 will\ncancel any previously set ccheck modes for that given sprite.\n\nStandard sprites will apply only the checked sprite's ccheck\nvalue to all involved sprites in collision checks, while checks\nagainst unbound sprites will also apply the values of all\nsprites involved with it. Foreground char 32s and blank chars\nwill not be ignored in collision checks, regardless of a\nstandard sprite's ccheck value.\n\n<span class=\"fB\">SPRn_CLIST (write-only)</span>\n\nSetting this counter to any value will perform a collision test\non sprite n against its current location.\n\n<span class=\"fB\">SPRn_VLAYER (write-only)</span>\n\nIf set to 1, sprite n is referenced from the vlayer instead of\nthe board.\n\n<span class=\"fB\">SPRn_OFF</span>\n\nShows whether sprite n is inactive: a value of 1 means yes\n(the sprite is off), 0 means no (the sprite is on). Also, if set\nto any value, turns sprite n off if it is on.\n\n<span class=\"fB\">SPRn_OFFONEXIT</span>\n\nIf set to any non-zero value, turns the sprite off upon leaving\nthe current board. The sprite will be turned off even if the\ndestination board is also the current board.\n\n<span class=\"fB\">SPRn_OVERLAID (write-only)</span>\n<span class=\"fB\">SPRn_OVERLAY (write-only)</span>\n\nIf either is set to 1, these draw sprite n over the overlay as\nopposed to underneath. Note that this has different behavior\ndepending on whether unbound sprites are present. If there are\nany unbound sprites active, all overlaid sprites will be drawn\nabove all non-overlaid sprites (as well as over the overlay).\nIf unbound sprites are not in use, overlaid sprites will be\ndrawn above the overlay, but will otherwise respect the standard\nsprite draw order.\n\n<span class=\"fB\">SPRn_SETVIEW (write-only)</span>\n\nSetting this to 1 will cause the viewport to be centered around\nsprite n. This is not a permanent effect, but a one-time jump\nakin to the RESETVIEW command. This is useful for scrolling the\nscreen for (e.g.) a sprite-based player object.\n\n<span class=\"fB\">SPRn_STATIC (write-only)</span>\n\nIf set to 1, makes sprite n act static (like a static overlay, a\nstatic sprite always stays in a given position on the screen);\nif this is 0, the sprite scrolls with the board.\n\nOf note: any collision with a static sprite happens based on its\nvisually-apparent location, not its actual location. This means\nthat when the board scrolls, the valid coordinates for absolute\ncollision checks against a static sprite change even when the\nstatic sprite itself is not visually moved, and that relative\ncollision checks like SPRn_CLIST against a static sprite work\nbased on the static sprite's screen position.\n\n<span class=\"fB\">SPRn_Z</span>\n\nThese counters allow a user-defined sprite draw order. This\nmethod of determining draw order takes precedence over\nSPR_YORDER and sprite number. Higher SPRn_Z values are higher\npriority and will be drawn over sprites with lower SPRn_Z\nnumbers. Matching SPRn_Z values will fall back to SPR_YORDER\nprecedence if active, and sprite number precedence otherwise.\n\n<span class=\"fB\">SPR_NUM</span>\n\nWhen using p?? for placing a sprite, this counter's value\nbecomes the sprite's parameter. When IF c?? Sprite p?? X Y is\nused, SPR_NUM will be set to the number of the first sprite that\nmakes the IF statement true (if there are any). The sprites are\nchecked by sprite number in ascending order.\n\n<span class=\"fB\">SPR_YORDER (write-only)</span>\n\nIf set to 1, the sprites are drawn in order of lowest sum of\nSPRn_Y and SPRn_CY values to highest instead of in order of\ntheir sprite numbers. Any incidence of matching sums will have\ndraw order determined by sprite number. If any SPRn_Z values are\nset, those take precedence.\n\n<span class=\"fB\">SPRn_SWAP (write-only)</span>\n\nSwaps sprite n with the sprite number to which this function is\nset.\n\n<span class=\"fB\">SPRn_UNBOUND</span>\n\nIf set to 1, unbinds sprite n from the grid, making its\ncoordinates refer to pixel positions instead of tile positions.\nIts dimensions (SPRn_HEIGHT, SPRn_WIDTH) and reference\ncoordinates (SPRn_REFX, SPRn_REFY) will still be relative to\ntiles, but its current position (SPRn_X, SPRn_Y) and collision\nbox counters (SPRn_CX, SPRn_CY, SPRn_CWIDTH, SPRn_CHEIGHT) will\nadhere to this change. See the Unbound Sprites section in the\nSprites help for more information.\n\n<span class=\"fB\">SPRn_TCOL</span>\n\nSets which color provides transparency for unbound sprite n.\nSetting to -1 disables transparency for that sprite. Defaults to\n0.\n\n<span class=\"fB\">SPRn_OFFSET</span>\n\nInternally increments any number that refers to a character for\nsprite n by the counter's value. This only applies to unbound\nsprites. This number will often be a multiple of 256 in order to\naccess an extra charset, but offset values within char sets may\nalso be used.\n\n<a class=\"hL\" href=\"#SPRITES.HLP__spr\">Sprites</a>\n\n<p class=\"hC\">String Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__stc\"> </a>\n\n<span class=\"fB\">SET \"$string\" to \"INPUT\"</span>\n\nSets the given string to any input placed for the \"input string\"\ncommand.\n\n<span class=\"fB\">SET \"$string\" to \"BOARD_NAME\"</span>\n\nSets the given string to the name of the current board, or to a\nblank string when the board has no name.\n\n<span class=\"fB\">SET \"$string\" to \"MOD_NAME\"</span>\n\nSets the given string to the filename of the currently playing\nmodule, or to a blank string when no music is playing. If the\ncurrent board mod is mod *, this will contain the actual\nfilename of the currently-playing mod, not \"*\".\n\n<span class=\"fB\">SET \"$string\" to \"ROBOT_NAME\"</span>\n\nSets the given string to the name of the Robot this command\nis in, or to a blank string when the Robot has no name.\n\nNote: BOARD_NAME, MOD_NAME and ROBOT_NAME cannot be directly\ndisplayed, unlike &amp;INPUT&amp;.\n\n<span class=\"fB\">SET \"$string\" to \"FREAD\"</span>\n<span class=\"fB\">SET \"$string\" to \"FREADn\"</span>\n\nSets the given string to the contents of the currently open\nfile. FREAD stops at the terminator (*, or char 42, by default)\nor at the end of the file. FREADn reads the first n characters\nfrom the file. You can use FREAD with the #N string splicing\nformat to read up to N characters while still terminating on\nthe terminator.\n\n<span class=\"fB\">SET \"$string\" to \"FWRITE\"</span>\n<span class=\"fB\">SET \"$string\" to \"FWRITEn\"</span>\n\nPlaces the contents of the given string into the currently open\nfile. FWRITE places the entire string, ending it with the\nterminator character (*, or char 42, by default), while FWRITEn\nonly places the first n characters. If n goes over the string's\nlength, the entire string (and nothing else like whitespace or\ngarbage characters) will be placed.\n\n<span class=\"fB\">SET \"$string\" to \"BOARD_SCAN\"</span>\n\nSets the given string to a line of characters read directly\nfrom the board starting at \"board_x\" \"board_y\" and terminating\nat an asterisk (char 42), any instance of char 0, or after 63\ncharacters (whichever comes first). This command is\ndeprecated; please use copy block to strings instead.\n\n<p class=\"hC\">Super MZX Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__szc\"> </a>\n\n<span class=\"fB\">SMZX_MODE</span>\n\nDetermines whether Super MZX mode is off (0), 1, 2, or 3. All\nother values wrap around these four (so, for instance, trying to\nset SMZX_MODE to 4 would set it to 0, and trying to set\nSMZX_MODE to -1 would set it to 3).\n\n<span class=\"fB\">SMZX_Rn</span>\n<span class=\"fB\">SMZX_Bn</span>\n<span class=\"fB\">SMZX_Gn</span>\n\nChanges or reads the value of color n's red/blue/green value,\nrespectively. These counters range from 0-63; numbers outside\nthese bounds wrap around. These counters also work outside of\nSMZX modes; in normal mode, using numbers 0-15 for n alters the\ncurrent palette.\n\n<span class=\"fB\">SMZX_IDXx,y</span>\n\nReads or sets the color of a tile with a given color x's yth\ncolor. For example, <span class=\"fE\">SET \"SMZX_IDX191,0\" to 50</span><span class=\"fF\"> would set the</span>\ncolor number of the base color (color 0 out of 0-3) of a c1a\n(color 191) tile to color 50. This applies to SMZX mode 3 only,\nand only to renderers that support unbound sprites.\n\nKeep in mind that all re-ordered indices will be set to normal\nvalues when SMZX_MODE is changed.\n\n<span class=\"fB\">SMZX_INDICES (function)</span>\n\nLoads SMZX indices files. This only applies for SMZX Mode 3.\n\n<span class=\"fB\">SMZX_MESSAGE</span>\n\nControls whether messages in the message line are displayed with\nSMZX characters and colors in SMZX modes. 1 is yes, 0 is no\n(displays in normal mode); the default value is 1.\n\n<span class=\"fB\">SMZX_PALETTE (read-only)</span>\n\nLoads a Super MZX palette. This counter is deprecated; please\nuse load palette instead.\n\n<p class=\"hC\">Character Edit Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__cha\"> </a>\n\n<span class=\"fB\">CHAR_X</span>\n<span class=\"fB\">CHAR_Y</span>\n<span class=\"fB\">PIXEL</span>\n\nSet CHAR_X and CHAR_Y to a pixel location in the char set. You\ncan then set the PIXEL counter to one of the following to edit\nthe pixel:\n\n  0 Turns the pixel OFF\n  1 Turns the pixel ON\n  2 Toggles the pixel\n\nYou can also read the counter to see the current state of the\nchosen pixel. A value of 1 denotes that the pixel is set, and 0\nif not.\n\nThe pixel location system works on a 32 x 8 grid of characters,\nexactly like the 'Select Character' grid. Each character\nconsists of fourteen rows of eight pixels, which gives us 256\npixels across by 112 pixels down. Since we start counting at 0,\nthe top-left pixel of the top-left character (char 0) would be\n0,0. The bottom-right pixel of the bottom-right character\n(char 255) would be 255,111.\n\nThese counters become useless in SMZX modes.\n\n<a class=\"hL\" href=\"#CHAREDIT.HLP__079\">The Character Editor</a>\n\n<span class=\"fB\">CHAR_BYTE</span>\n<span class=\"fB\">CHAR</span>\n<span class=\"fB\">BYTE</span>\n\nSet CHAR to a character (0-255) and BYTE to one of the fourteen\nbytes within the char (0-13). You can then read or write that\nbyte using the CHAR_BYTE counter. Char_byte values range from\n0-255, and the meaning of each char_byte setting changes between\nnormal mode and SMZX modes. Normal MZX graphics mode sets bytes\nin binary (base 2); SMZX modes use quaternary (base 4).\n\nThese counters can also edit the extra character sets unbound\nsprites use by setting CHAR to the character numbers of the\nextra sets (e.g. 256 for the first char of the first extra set).\n\nIt helps to write the number in binary/quaternary first, then\nconvert to decimal. Writing the numbers this way helps\ndemonstrate that whatever is desired to be set is set. The\nvalues per pixel decrease going left to right.\n\nExamples:\n\n(Normal MZX mode, setting the third, fifth and seventh pixel)\n\nBinary: 00101010\n\n&#xE0DA;---&#xE0C2;--&#xE0C2;--&#xE0C2;--&#xE0C2;-&#xE0C2;-&#xE0C2;-&#xE0C2;-&#xE0BF;\n|128|64|32|16|8|4|2|1|\n&#xE0C3;---&#xE0C5;--&#xE0C5;--&#xE0C5;--&#xE0C5;-&#xE0C5;-&#xE0C5;-&#xE0C5;-&#xE0B4;\n|  0| 0| 1| 0|1|0|1|0|\n&#xE0C0;---&#xE0C1;--&#xE0C1;--&#xE0C1;--&#xE0C1;-&#xE0C1;-&#xE0C1;-&#xE0C1;-&#xE0D9;\n\nDecimal: ((128*0)+(64*0)+(32*1)+(16*0)+(8*1)+(4*0)+(2*1)+(1*0))\n         = 42\n\nNotice that the binary number shows which pixels are on, in\norder.\n\n(Super MZX Mode, setting first and second pixel to color 2,\nthird to color 4 and fourth to color 1)\n\nQuaternary: 1130\n\n&#xE0DA;--&#xE0C2;--&#xE0C2;-&#xE0C2;-&#xE0BF;\n|64|16|4|1|\n&#xE0C3;--&#xE0C5;--&#xE0C5;-&#xE0C5;-&#xE0B4;\n| 1| 1|3|0|\n&#xE0C0;--&#xE0C1;--&#xE0C1;-&#xE0C1;-&#xE0D9;\n\nDecimal: ((64*1)+(16*1)+(4*3)+(1*0)) = 92\n\nMuch like the binary numbers for normal MZX mode, the\nquaternary number shows what color each pixel is in order.\nHere, color values range from 0 to 3.\n\n<a class=\"hL\" href=\"#CHAREDIT.HLP__079\">The Character Editor</a>\n\n<p class=\"hC\">Mouse/Joystick Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__nkp\"> </a>\n\n<span class=\"fB\">MOUSEX</span>\n<span class=\"fB\">MOUSEY</span>\n\nReads/sets the mouse cursor's x or y-coordinates (respectively)\nrelative to the screen in characters.\n\n<span class=\"fB\">MOUSEPX</span>\n<span class=\"fB\">MOUSEPY</span>\n\nReads/sets the mouse cursor's x or y-coordinates (respectively)\nrelative to the screen in pixels. Always scales to terms of\n640x350, regardless of the actual resolution or current SMZX\nmode.\n\n<span class=\"fB\">MBOARDX (read-only, board)</span>\n<span class=\"fB\">MBOARDY (read-only, board)</span>\n\nReads the mouse cursor's x or y-coordinates (respectively)\nrelative to the board.\n\n<span class=\"fB\">BUTTONS (read-only)</span>\n\nReads which mouse buttons are currently being pressed. The\ncounter value is based as follows:\n0 for no input pressed\n+1 for left button\n+2 for right button\n+4 for middle button\n+8 for wheel up\n+16 for wheel down\n+32 for wheel left\n+64 for wheel right\n+128 for extra button 1\n+256 for extra button 2\nE.G., left by itself would output 1, and left+middle would\noutput 5 (1 + 4).\n\n<span class=\"fB\">CURSORSTATE</span>\n\nDetermines whether the default mouse cursor is on or off (0 is\noff, 1 (or any non-zero value) is on - defaults to off).\n\n<span class=\"fB\">JOY#ACTIVE (read-only)</span>\n\nDetermines the status of the given joystick numbered #. Returns\n1 for active, 0 for inactive, and -1 for an invalid index.\n\n<span class=\"fB\">JOY#.[ACTION] (read-only)</span>\n\nDetermines the status of the given joystick action for the given\njoystick numbered #. The square brackets are not included.\nDigital actions return 1 for pressed/held and 0 for\nunpressed/unheld. Analog actions will return their current\nvalue. Invalid actions will return -1.\n\nPlease read joystick.html for a full list of actions.\n\n<span class=\"fB\">JOY_SIMULATE_KEYS</span>\n\nAllows MegaZeux to simulate key presses with joystick actions\nwhen enabled. 0 is off, 1 (or any non-zero value) is on.\nDefaults to on.\n\n<p class=\"hC\">Defaults Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__def\"> </a>\n<span class=\"fB\">ENTER_MENU</span>\n\nDetermines whether the built-in status window is loaded when\nthe enter key is pressed. 1 (or any non-zero value) is yes, 0\nis no. The default is 1 (yes). If on the title screen, this\ncounter is ignored unless in standalone mode.\n\n<span class=\"fB\">ESCAPE_MENU</span>\n\nDetermines whether the built-in game exit dialog is loaded when\nthe escape key is pressed. 1 (or any non-zero value) is yes, 0\nis no. The default is 1 (yes). If on the title screen, this\ncounter is ignored unless in standalone mode.\n\n<span class=\"fB\">HELP_MENU</span>\n\nDetermines whether the built-in help window is loaded when the\nF1 key is pressed. 1 (or any non-zero value) is yes, 0 is no.\nThe default is 1 (yes). The help menu is still accessible from\nother default dialogs (such as the file menus) unless in\nstandalone mode. If on the title screen, this counter is ignored\nunless in standalone mode.\n\n<span class=\"fB\">F2_MENU</span>\n\nDetermines whether the built-in MegaZeux options window is\nloaded when the F2 key is pressed. 1 (or any non-zero value) is\nyes, 0 is no. The default is 1 (yes). If on the title screen,\nthis counter is ignored unless in standalone mode.\n\n<span class=\"fB\">LOAD_MENU</span>\n\nDetermines whether the built-in MegaZeux loadgame window is\nloaded when the F4 key is pressed. 1 (or any non-zero value) is\nyes, 0 is no. The default is 1 (yes). If on the title screen,\nthis counter is ignored unless in standalone mode.\n\n<span class=\"fB\">BIMESG (write-only)</span>\n\nControls built-in game messages (e.g. the message given when\ntouching goop); 1 (or any non-zero value) is on while 0 is off.\nThe default is 1 (on).\n\n<span class=\"fB\">SPACELOCK (write-only)</span>\n\nControls whether holding space locks player movement. If 1 (or\nany non-zero value), the player will be locked into place when\nspace is held; if 0, the player can move freely when space is\nheld, but will be unable to shoot bullets. The default is 1\n(on).\n\n<p class=\"hC\">Vlayer Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__vlc\"> </a>\n\n<span class=\"fB\">VLAYER_WIDTH</span>\n<span class=\"fB\">VLAYER_HEIGHT</span>\n\nSets the width and height of the vlayer, respectively. The\ndefaults are 256 for VLAYER_WIDTH and 128 for VLAYER_HEIGHT.\nSetting one will automatically set the other to the maximum\npossible for the current vlayer_size.\n\n<span class=\"fB\">VLAYER_SIZE</span>\n\nSets the maximum size of the vlayer. The default is 32768.\nMaking this larger won't change the values of VLAYER_WIDTH or\nVLAYER_HEIGHT but shrinking it may shrink both - height first,\nthen width.\n\n<span class=\"fB\">VCHx,y</span>\n\nAllows reading and writing of single characters to the vlayer\nat the coordinates x,y. The comma must be included. Returns -1\nfor invalid coordinates.\n\n<span class=\"fB\">VCOx,y</span>\n\nAllows reading and writing of single colors to the vlayer with\nat the coordinates x,y. The comma must be included. Returns -1\nfor invalid coordinates.\n\n<a class=\"hL\" href=\"#VLAYER.HLP__vla\">The Vlayer and Its Uses</a>\n\n<p class=\"hC\">Miscellaneous Counters</p><a class=\"hA\" name=\"COUNTERS.HLP__msc\"> </a>\n<span class=\"fB\">MZX_SPEED</span>\n\nThe current game speed (1-16) of MegaZeux. MZX_SPEED 1 is\nprocessor-bound and executes as many cycles per second as\npossible. Other speeds execute cycles according to this\nformula:\n\n<span class=\"fE\">62.5 / (MZX_SPEED - 1) = cycles per second</span>\n\nOnce the speed is set with this counter, the player cannot\nchange the speed in the F2 menu until MZX_SPEED is set to 0.\nSetting MZX_SPEED to 0 will not affect the current speed.\nMZX_SPEED will revert back to the default value listed in the\nconfiguration file upon loading a different game.\n\n<a class=\"hL\" href=\"#CONFGINI.HLP__1st\">The Config File</a>\n\n<span class=\"fB\">COMMANDS_STOP</span>\n\nThis is a special counter active only while playtesting in the\neditor. COMMANDS_STOP will activate stepping in the Robotic\ndebugger, even if the debugger was currently disabled, once the\nnumber of commands any specific Robot executes in a single cycle\nexceeds COMMANDS_STOP's value. By default, COMMANDS_STOP holds a\nvalue of 2000000.\n\n<span class=\"fB\">EXIT_GAME</span>\n\nIf this counter is set to a non-zero value, the current\nMegaZeux world will exit to the title screen, or back to the\neditor if playing from the editor. This counter does nothing on\nthe title screen unless in standalone mode, and if standalone\nmode is ran without a title screen, this counter will cause\nMegaZeux to exit entirely.\n\n<span class=\"fB\">PLAY_GAME</span>\n\nIf this counter is set to a non-zero value when MZXRun is in\nstandalone mode and on the title screen, the game will be\nstarted. Instances outside of the title screen and outside of\nMZXRun will be ignored.\n\n<span class=\"fB\">CURRENT_COLOR</span>\n<span class=\"fB\">RED_VALUE</span>\n<span class=\"fB\">GREEN_VALUE</span>\n<span class=\"fB\">BLUE_VALUE</span>\n\nWhen CURRENT_COLOR is set between 0 and 15 (between 0 and 255\nin Super MZX modes), the other three counters contain the red,\ngreen and blue values of that color.\n\n<a class=\"hL\" href=\"#SMZXMODE.HLP__095\">Super MegaZeux Modes</a>\n<a class=\"hL\" href=\"#PALEEDIT.HLP__093\">The Palette Editor</a>\n\n<span class=\"fB\">INPUT (board)</span>\n\nThe numerical value of the last string inputted in an INPUT\nSTRING box. Strings that are solely numbers and strings leading\nwith numbers (e.g. \"86ed\") set this counter to the relevant\nnumber. This counter returns 0 if the last string inputted did\nnot lead with a number. This function is separate from its use\nin messages with &amp;INPUT&amp;.\n\nNOTE: When this counter is interpolated, it will instead\ninterpolate the counter NAMED after the inputted string, so\nexercise caution.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP___iP\">INPUT STRING \"string\"</a>\n\n<span class=\"fB\">INPUTSIZE (board)</span>\n\nThe size, in characters, of the last string inputted in an INPUT\nSTRING box.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP___iP\">INPUT STRING \"string\"</a>\n\n<span class=\"fB\">DATE_DAY (read-only, universal)</span>\n<span class=\"fB\">DATE_MONTH (read-only, universal)</span>\n<span class=\"fB\">DATE_YEAR (read-only, universal)</span>\n\nContain the current day, month and year respectively, based on\nthe system clock.\n\n<span class=\"fB\">TIME_HOURS (read-only, universal)</span>\n<span class=\"fB\">TIME_MINUTES (read-only, universal)</span>\n<span class=\"fB\">TIME_SECONDS (read-only, universal)</span>\n<span class=\"fB\">TIME_MILLIS (read-only, universal)</span>\n\nContain the current hour, minute, second and millisecond\nrespectively, based on the system clock, in 24-hour format.\n\n<span class=\"fB\">DATE_WEEKDAY (read-only, universal)</span>\n\nContains the current day of the week as a number 0 through 6,\nstarting with Sunday (0) and ending with Saturday (6).\n\n<span class=\"fB\">INT</span>\n<span class=\"fB\">BIT_PLACE</span>\n<span class=\"fB\">INT2BIN</span>\n\nUsed to manipulate bits within a counter. Set INT to the number\nor counter you wish to edit. Set BIT_PLACE to a number between\n0 and 15 (which correspond to the bits in a 16-bit signed\ncounter), and then set INT2BIN to one of the following to edit\nthe bit:\n\n  1 Sets the bit ON\n  2 Sets the bit OFF\n  3 Toggles the bit\n\nThe edited value is placed into the INT counter. You can also\nread the INT2BIN counter to see the current state of the bit.\n\nThis method of bit manipulation is heavily deprecated in favor\nof expressions (especially considering it only works on 16 bits\ninstead of the current 32). Follow the link below to read more.\n\n<a class=\"hL\" href=\"#EXPRESS.HLP__exp\">Expressions</a>\n\n<span class=\"fB\">KEY_PRESSED (read-only)</span>\n<span class=\"fB\">KEY</span>\n<span class=\"fB\">KEY_CODE (read-only)</span>\n\nKEY_PRESSED lists whether any key (each referenced by a number)\nis currently being held down; for example, on a typical\nkeyboard, if the spacebar is being pressed the value of\nkey_pressed will be 32. If no key is currently held, its value\nis 0. Results are unsigned, so checking for negative numbers\nwill not work. \"Autorepeat stall\" affects KEY_PRESSED, so if you\nhold down a key you will get the value, then get zeros for a\nperiod, then get the values again. This is useful for many\napplications.\nKEY acts like KEY_PRESSED, but lacks autorepeat stall, returns\nuppercase letter values, and retains its value until another key\nis pressed.\nKEY_CODE acts similar to KEY_PRESSED (has an autorepeat stall,\nis 0 when nothing is held) but uses different keycodes. Its\nkeycode set is more limited, making KEY_PRESSED generally\npreferable.\nIn the case of multiple keys being held down at once, these\ncounters only report the value of the most recently-pressed key.\n\n<span class=\"fB\">KEYn (read-only)</span>\n<span class=\"fB\">KEY_RELEASE (read-only)</span>\n<span class=\"fB\">KEY_PRESSEDn (read-only)</span>\n\nKEYn counters check whether the key with the keycode n is\npressed; a counter value of 1 means yes, 0 means no. KEY_RELEASE\ncontains the keycode of the last key released, or is 0 if a key\nis currently being held. These display/use the same keycodes as\nKEY_CODE, but do not cause an autorepeat stall. Results are\nunsigned, so checking for negative numbers will not work.\nKEY_PRESSEDn counters function like KEYn counters, but use\nKEY_PRESSED's keycodes.\nAs multiple KEYn and KEY_PRESSEDn values can be 1 at the same\ntime, these counters can be very useful for detecting key\ncombinations.\n\n<span class=\"fB\">RANDOM_SEED0 (universal)</span>\n<span class=\"fB\">RANDOM_SEED1 (universal)</span>\n\nThese counters allow reading and controlling MegaZeux's random\nseed. RANDOM_SEED0 controls the low 32 bits of the seed, while\nRANDOM_SEED1 controls the high 32 bits. The random seed is not\nretained by save files.\n\n<span class=\"fB\">MAX_SAMPLES</span>\n\nSets number of samples that can play at once. -1 sets no limit,\n0 mutes all samples; -1 is the default setting. The actual\nsample limit will be the smaller of the limit set by this\ncounter and the limit set by the max_simultaneous_samples config\nfile setting. PC Speaker emulated sounds are unaffected.\n\n<span class=\"fB\">XPOS</span>\n<span class=\"fB\">YPOS</span>\n\nThe X or Y-coordinates (respectively) used by the REL COUNTERS\ncommand.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP__pre\">REL COUNTERS</a>\n\n<span class=\"fB\">FIRSTXPOS</span>\n<span class=\"fB\">FIRSTYPOS</span>\n\nThe X or Y-coordinates (respectively) used by the REL COUNTERS\nFIRST command.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP___r4\">REL COUNTERS FIRST</a>\n\n<span class=\"fB\">LASTXPOS</span>\n<span class=\"fB\">LASTYPOS</span>\n\nThe X or Y-coordinates (respectively) used by the REL COUNTERS\nLAST command.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP___r7\">REL COUNTERS LAST</a>\n\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"USINGTHE.HLP\">\n<a class=\"hA\" name=\"USINGTHE.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Using the Editor</span></p>\nThe Robotic editor is quite simple to use. After selecting your\nRobot's character and name, you will enter the editor screen.\nThere are three major sections to the editor screen: The top\nbox, showing current statistics; the middle box, showing the\nRobot's program with the current line highlighted in the\ncenter; and the bottom box, showing helpful key shortcuts.\n\nTo edit the Robot, you enter lines just as in editing scrolls.\nUse up and down or PageUp and PageDown to scroll between lines,\nand use Enter at the end of a line to insert new blank lines.\nPressing Enter in the middle of a line will remove everything\nto the right of the cursor and place it onto a new, following\nline. Press Backspace at the beginning of a line to place its\ncontents onto the preceding line (this be ignored if there is\nnot enough space).\n\nAfter you enter a line, it will be checked for proper syntax.\nIf it passes, it will be reformatted and redisplayed, and you\ncan then enter another line; if it does not, it will be marked\nas ignored. The reformatting is unpreventable - Since MegaZeux\ntokenizes Robotic lines to conserve space, all irrelevant\ninformation such as extra spaces is lost. The editor attempts\nto print the lines in a visually pleasing manner. It is possible\nfor MZX to reformat the line in a way you aren't intending, so\ndouble-check your line if MZX reformats it.\n\nAll lines other than the current line will have the various\nelements such as strings and numbers highlighted in different\ncolors. cXX-type color codes will be shown as their actual\ncolor using the current palette. The current line is shown in a\nsolid color and color codes are shown in the usual cXX format.\n\nThe following keys are active within the Robotic editor:\n\n<a class=\"hL\" href=\"#USINGTHE.HLP___ar\">F1 - Help</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___br\">F2 - Color</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___cr\">F3 - Character</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___dr\">F4 - Parameter</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___er\">F5 - Char Edit</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___fr\">F6-F10 - Macros</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___gr\">F12 - Take Screenshot</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___hr\">Alt+B OR Alt+Enter - Block Action</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___ir\">Alt+E OR Alt+End - Mark Block End</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___jr\">Alt+H - Hide Help / Borders</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___kr\">Alt+I - Import</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___lr\">Alt+M - Configure Macro</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___mr\">Alt+O - Edit Single Line Macros</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___nr\">Alt+S OR Alt+Home - Mark Block Top</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___or\">Alt+U - Unmark Block</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___pr\">Alt+V - Verify</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___qr\">Alt+X - Export</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___rr\">Alt+BackSpace - Delete Entire Line</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___sr\">Alt+Ins OR Alt+P - Paste</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___tr\">Ctrl+F - Search</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___ur\">Ctrl+H - Replace</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___vr\">Ctrl+R - Repeat Search/Replace</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___wr\">Ctrl+G - Goto Position</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___xr\">Ctrl+Y - Redo</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___yr\">Ctrl+Z - Undo</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___zr\">Ctrl+I/D/C - Mark Line</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___1r\">Ctrl+BackSpace - Delete Previous Word</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___2r\">Ctrl+LeftArrow - Jump to Previous Word</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___3r\">Ctrl+RightArrow - Jump to Next Word</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___4r\">Ctrl+Home - Jump to Program Start</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___5r\">Ctrl+End - Jump to Program End</a>\n<a class=\"hL\" href=\"#USINGTHE.HLP___6r\">Esc - Exit Robotic Editor</a>\n\nOn Mac platforms, any command that uses the Alt key can take &#xE0BD;&#xE0BE;\n(the command key) instead.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___ar\"><span class=\"fE\">F1 - Help</span></a>\n\nUse F1 to bring up context-sensitive help at any time.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___br\"><span class=\"fE\">F2 - Color</span></a>\n\nF2 will bring up a menu of colors representing the current\npalette. Select a color and press Enter to insert a cXX color\ncode into the current line corresponding to that color.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___cr\"><span class=\"fE\">F3 - Character</span></a>\n\nF3 will bring up a menu of characters representing the current\ncharacter set. Select one and press Enter to insert that\ncharacter into the current line. No quotes or apostrophes are\nadded.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___dr\"><span class=\"fE\">F4 - Parameter</span></a>\n\nIf you have just typed in the name of a thing, such as Ammo,\nF4 will allow you to select settings for it, placing a pXX\nparameter code for the chosen settings. This includes things\nthat are turned into Custom things by setting their CHAR ID\nvalues to 255. If the thing is a valid MZX thing without\nmeaningful parameter values, pressing F4 automatically outputs\n\" p00\".\n\n<a class=\"hA\" name=\"USINGTHE.HLP___er\"><span class=\"fE\">F5 - Char Edit</span></a>\n\nUse F5 to edit and select a character for use with the Robotic\nCHAR EDIT command. This outputs the numbers needed for that\ncommand to visually match the character selected.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___fr\"><span class=\"fE\">F6-F10 - Macro</span></a>\n\nF6 through F10 will insert short macros, customizable using\nAlt+O. If you have an extended macro set for these an input\nwindow for it will come up (see Configure Macro for more\ndetails).\n\n<a class=\"hA\" name=\"USINGTHE.HLP___gr\"><span class=\"fE\">F12 - Take Screenshot</span></a>\n\nPressing F12 will make a 640x350 screenshot (in PNG format by\ndefault) in the working directory, using the software renderer.\nThe file naming starts from \"screen0\".\n\n<a class=\"hA\" name=\"USINGTHE.HLP___hr\"><span class=\"fE\">Alt+B OR Alt+Enter - Block Action</span></a>\n\nAllows you to perform an action on the current block of lines.\nPossible actions are: Copy to the OS's clipboard, Cut to the\nOS's clipboard (copy it and then delete it), Clear from memory,\nor Export to a text file on disk. If no block is currently\nhighlighted, the actions will take effect on the current line.\nAlt+Enter is the old and deprecated hotkey, left in to ease use\nfor long-time users.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___nr\"><span class=\"fE\">Alt+S OR Alt+Home - Mark Block Top</span></a>\n<a class=\"hA\" name=\"USINGTHE.HLP___ir\"><span class=\"fE\">Alt+E OR Alt+End - Mark Block End</span></a>\n\nAlt+S/E will mark the top or bottom of a block of lines. You\ncan then perform various block actions on these lines. The\nmarked lines are highlighted. Alt+Home and Alt+End are the old\nand deprecated hotkeys, left in to ease use for long-time\nusers.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___jr\"><span class=\"fE\">Alt+H - Hide Help / Borders</span></a>\n\nAlt+H toggles the horizontal borders and help key reference.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___kr\"><span class=\"fE\">Alt+I - Import</span></a>\n\nUse Alt+I to import a text file or bytecode file into the\ncurrent Robot. The new lines will be inserted into the current\nprogram, starting from the current line.\n\n<a class=\"hA\" name=\"USINGTHE.HLP__lr_\"><span class=\"fE\">Alt+M - Configure Macro</span></a>\n\nAlt+M will load a box where you list the name of an extended\nmacro to be configured (for example, type \"1\" to edit macro_1).\nAfter choosing a macro to edit, you will be able to set the\nvarious variables for the macro. Select OK to place the macro,\nCancel to go back to the editor without placing the macro, and\nDefault to change all values to their defaults.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___mr\"><span class=\"fE\">Alt+O - Edit Single Line Macros</span></a>\n\nYou can change the values of the five single-line macros here.\nUse ^ for Enter in the macros. If there is an extended macro in\nplace of macros 1-5, it will override any single-line macro also\nset for that macro.\n\n<a class=\"hL\" href=\"#CONFGINI.HLP__1st\">The Config File</a>\n\n<a class=\"hA\" name=\"USINGTHE.HLP___or\"><span class=\"fE\">Alt+U - Unmark Block</span></a>\n\nThe current block is unmarked.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___pr\"><span class=\"fE\">Alt+V - Verify</span></a>\n\nUse Alt+V to determine if there are any invalid lines in the\nRobot, and if there are what their errors are. Each invalid line\ncan be set to I (ignore), D (delete) or C (comment), which will\nbe applied if the user selects OK. Only the first 256 errors\nwill be listed.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___qr\"><span class=\"fE\">Alt+X - Export</span></a>\n\nUse Alt+X to export either the current block or the entire\nRobot program to a text file or bytecode file on disk. If saved\nas text, one can then edit the program outside of MegaZeux or\nuse the text file in other ways.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___rr\"><span class=\"fE\">Alt+BackSpace - Delete Entire Line</span></a>\n\nThe currently selected line is blanked. The empty line itself\nwill remain.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___sr\"><span class=\"fE\">Alt+Ins OR Alt+P - Paste</span></a>\n\nAlt+Ins or Alt+P will paste in the block last copied to the\ninternal clipboard. The clipboard used is the OS clipboard, and\nas such is preserved between Robots and even between worlds.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___tr\"><span class=\"fE\">Ctrl+F - Search</span></a>\n\nUse Ctrl+F to activate the Search menu. After inputting a string\nto find in the Search box and choosing options, the Robotic\neditor will search for the term starting from the current line\nand jump to the first string that matches.\nWrap will ensure every line of the code is checked (otherwise\nall code above the current line will be ignored) and Case\nSensitive will require the search string and the results match\ncase exactly before giving results.\n\n<a class=\"hA\" name=\"USINGTHE.HLP__+ur\"><span class=\"fE\">Ctrl+H - Replace</span></a>\n\nUse Ctrl+H to activate the Replace menu. The Search box takes\nthe text to be changed, and the Replace box takes the text that\nreplaces it. Replace only changes the first seen instance of the\nsearched text, with the search starting from the current line.\nReplace All changes all of them at once. Once completed, the\nRobotic editor will jump to the last line changed by a replace\naction (if any).\nWrap will ensure every line of the code is checked (otherwise\nall code above the current line will be ignored) and Case\nSensitive will require any string matching the search string to\nalso match case before being replaced.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___vr\"><span class=\"fE\">Ctrl+R - Repeat Search/Replace</span></a>\n\nUse Ctrl+R after a Search or Replace action to repeat the\naction.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___wr\"><span class=\"fE\">Ctrl+G - Goto Position</span></a>\n\nUse Ctrl+G to jump to the given Robotic line/column (if too\nhigh of a number is given, the editor will jump to the last\npossible line/column).\n\n<a class=\"hA\" name=\"USINGTHE.HLP___yr\"><span class=\"fE\">Ctrl+Z - Undo</span></a>\n<a class=\"hA\" name=\"USINGTHE.HLP___xr\"><span class=\"fE\">Ctrl+Y - Redo</span></a>\n\nUse Ctrl+Z to undo the most recent editor action(s). Ctrl+Y\nreverts an undo.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___zr\"><span class=\"fE\">Ctrl+I/D/C - Mark Line</span></a>\n\nThese commands mark the current invalid line as to be ignored,\ndeleted, or commented out upon editor exit, respectively. If\nthe line is valid Ctrl + C will turn it into a comment\n(truncating the end of the line if commenting would make the\nline exceed 241 characters). Note that comment-marking a\ncomment will immediately strip the line.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___1r\"><span class=\"fE\">Ctrl+BackSpace - Delete Previous Word</span></a>\n\nUse Ctrl+BackSpace to delete the word to the left of the cursor.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___2r\"><span class=\"fE\">Ctrl+LeftArrow - Jump to Previous Word</span></a>\n\nUse Ctrl+LeftArrow to jump to the beginning of the previous\nword in the current line.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___3r\"><span class=\"fE\">Ctrl+RightArrow - Jump to Next Word</span></a>\n\nUse Ctrl+RightArrow to jump to the beginning of the next word\nin the current line. If on the last word of the line, this\ncommand jumps to the end of the line.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___4r\"><span class=\"fE\">Ctrl+Home - Jump to Program Start</span></a>\n\nUse Ctrl+Home to jump to the first line of the current program.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___5r\"><span class=\"fE\">Ctrl+End - Jump to Program End</span></a>\n\nUse Ctrl+End to jump to the last line of the current program.\n\n<a class=\"hA\" name=\"USINGTHE.HLP___6r\"><span class=\"fE\">Esc - Exit Robotic Editor</span></a>\n\nThis key will exit the Robotic Editor, unless any lines are\nmarked as invalid.\n\nThe statistics line across the top of the Robot editor shows\nthe current line number, the current column within the line, the\nsize of the Robot program versus the maximum size this Robot can\nreach, and the Robot's X/Y position.\n\nUse ESC to exit the Robot editor when you are done editing your\nRobot program.\n\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"ROBOTSWH.HLP\">\n<a class=\"hA\" name=\"ROBOTSWH.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Robots - What They Are and How to Use Them</span></p>\nRobots are the heart and soul of MegaZeux. Nearly anything you\ncould want to do in MegaZeux - anything you see done in a\nMegaZeux game, many things you didn't know were possible - can\nbe done with MegaZeux's Robots.\n\nRobots are programmed in their own programming language, called\nRobotic. Robotic is a fairly simple language to learn, but\nsomewhat moderate to completely master. It is somewhere along\nthe lines of BASIC, although more complex. If you've ever used\nEpic Megagame's ZZT, Robotic is vastly more complex than\nZZT-OOP, although there are vague similiarities. There's a\nkludge or dozen to get really powerful things working, but\nyou'll get used to them.\n\nTo place a Robot in the editor, press F10 and select Robot. (You\ncan also select Pushable Robot if you want things to be able to\npush the Robot.) Then name the Robot and select a character to\nrepresent it. You are then brought to the Robot editor, where\nyou can program the Robot in Robotic.\n\nRobots are limited to the board, and there can be 255 of them on\neach board. They can be pretty large - 2MB in size, enough code\nspace to create extraordinarily complex (or convoluted)\nprograms. There is an exception: the Global Robot, which is\noutside of the board and runs at all times.\n\n<a class=\"hL\" href=\"#ROBOTICT.HLP__1st\">Robotic Tutorial</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"ROBOTICT.HLP\">\n<a class=\"hA\" name=\"ROBOTICT.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">Robotic Tutorial</span></p>\nRobotic has a few concepts that you must understand before you\nbegin to program in it. You can skip this if you want, but it\nwill help you learn Robotic much faster. A large portion of\nthis will be obvious to anybody who has ever programmed in any\nlanguage or used computers extensively.\n\n<p class=\"hC\">Commands</p>\nYou can have one command per line. A command is an instruction\nto MegaZeux or the Robot telling it to do something. A program\nis a series of commands that are ran right after another,\nfrom top to bottom (until told otherwise).\n\n<p class=\"hC\">Parameters</p>\nA parameter is the part of the command that can change, such\nas numbers or colors. They specify how the command should be\nrun or what it should affect. For example, in the command:\n\nWAIT 6\n\nWAIT is the command, and 6 is the parameter. Many commands have\nmultiple parameters. Parameters are often specified using #,\n\"string\", or [color] [char], etc.\n\n<p class=\"hC\">Labels</p>\nA label is a point in a Robotic program that has been given a\nname. It is used as a reference point so the Robot can return\nto that point at any time, to allow actions like repeating\nsections of commands (looping) or reactions to certain events.\nLabels are written as follows - : \"label\"\n\n<p class=\"hC\">Messages</p>\nA message is something one Robot sends to another, or an\nexternal event sends to a Robot, to tell it to do something.\nThis involves jumping to the label corresponding to that\nmessage. For example, a Robot can tell another Robot to\n\"Bounce\"; That Robot will now be executing commands starting\nat the label : \"Bounce\" .\n\n<p class=\"hC\">Coordinates</p>\nx,y coordinates are a pair of numbers representing a point on\nthe current board. (0,0) is the upper-left corner of the board,\nand the first coordinate increases while going right, while the\nsecond increases while going down. You can find coordinates\neasily at the bottom of the screen when help is hidden, by\npressing Alt+Y, or by pressing F6 in the game (only in editor\nplaythroughs). x,y coordinates can be negative, but this is\nuncommon and used for special purposes only (e.g. using REL\ncommands).\n\n<p class=\"hC\">Strings</p>\nA string is a series of letters or characters, inside quotes.\nFor example, \"STRING\", \"Booga!\", \"-21_Trees-\", or \"[[]]\".\n\n<p class=\"hC\">Characters</p>\nA character is a single symbol, letter, number, punctuation\nmark, space, graphic, etc. All things are viewed as single\ncharacters. When using single characters in Robotic, they must\nbe enclosed in apostrophes- e.g. 'X'. Certain characters must\nbe inputted in specific ways to avoid problems with Robotic:\n\n  \\0 for character 0 (this won't work in strings, but will work\n  for single chars)\n  \\t for tab (character 9)\n  \\n for newline (character 10)\n  \\r for carriage return (character 13)\n  \\\" for quotation mark\n  \\\\ for backslash\n\n<p class=\"hC\">Colors</p>\nA color is a code representing a background/foreground color\npair. They follow the format cXX, where X is 0-9, A-F or ?\n(which stands for any color). To enter color codes easily, use\nF2 in the Robot editor. The exact format of color codes is\ncovered in another section.\n\nA quirk to keep in mind: any entity with a background color of 0\nwill display the background color of anything beneath it.\n\n<p class=\"hC\">Directions</p>\nDirections represent one of the four cardinal directions of\nNORTH, SOUTH, EAST, or WEST. They can be abbreviated to N, S,\nE, and W. There are other directions, but these are the most\ncommonly used.\n\nHopefully you understood the above material, as knowledge of it\nis essential to program in Robotic. If you don't quite grasp\nit, you can return to it later, but you will need to understand\nit eventually.\n\n<p class=\"hC\">Your First Robotic Program: HELLO WORLD!</p>\nFirst, open the Editor and create a Robot. Then open the\nRobot's programming box by pressing Enter twice while the Robot\nis highlighted.\n\nNow, type the following into the editor:\n\n<span class=\"fE\">* \"HELLO WORLD!\"</span>\n<span class=\"fE\">end</span>\n\nHere's what each line does.\n\n<span class=\"fE\">* \"HELLO WORLD!\"</span>\n\nThe * command puts whatever text is in the quotation marks on\nthe bottom of the screen. (Actually, it's placed on the given\nmessage line row, which is the bottom by default.) All Robotic\ncode starts immediately, so this particular statement happens\nright as the board with this Robot is loaded.\n\n<span class=\"fE\">end</span>\n\nThis ends the code. It is good practice to include the END\ncommand even when you don't think you need it; leaving the END\ncommand out can cause some problems such as text glitches or,\nworse, the running of any and all code after where you want\nyour Robot to stop!\n\nThat was really simple, wasn't it? Now let's step up to\nsomething a bit bigger.\n\n<p class=\"hC\">Your Second Robotic Program</p>\nCreate a Robot in the editor, and give it the following\nprogram:\n\n <span class=\"fE\">: \"start\"</span>\n <span class=\"fE\">GO NORTH 2</span>\n <span class=\"fE\">GO SOUTH 2</span>\n <span class=\"fE\">GOTO \"start\"</span>\n\nNow test the world. As you can see, this program makes the\nRobot move up and down. Let's analyze it line by line.\n\n<span class=\"fE\">: \"start\"</span>\n\nAs we know, this is a label. It marks the beginning of the\nprogram.\n\n<span class=\"fE\">GO NORTH 2</span>\n\nThis quite plainly tells the Robot to move north two spaces.\n\n<span class=\"fE\">GO SOUTH 2</span>\n\nThis tells the Robot to move south two spaces.\n\n<span class=\"fE\">GOTO \"start\"</span>\n\nGOTO tells the Robot to jump to a label. In this case, the\nRobot returns to the label \"start\", effectively restarting its\ncode. This particular Robot will be executing these lines of\ncode for the entirety of the game, or until destroyed.\n\nPretty easy so far, huh? You will probably find that Robotic's\nbasics are easy to grasp. Let's try something a little more\ncomplex.\n\n<p class=\"hC\">Your Third Robotic Program</p>\nTry the following Robot program:\n\n <span class=\"fE\">: \"loop\"</span>\n <span class=\"fE\">/ \"NNEESSWW\"</span>\n <span class=\"fE\">GOTO \"loop\"</span>\n <span class=\"fE\">: \"touch\"</span>\n <span class=\"fE\">* \"Hello!\"</span>\n <span class=\"fE\">CHAR 'X'</span>\n <span class=\"fE\">END</span>\n\nTesting will show you that this Robot runs around in a small\nsquare. Now try touching it. If you can catch it, the Robot\nwill stop moving, change to an X and greet you! Let's look at\nthis program.\n\n<span class=\"fE\">: \"loop\"</span>\n\nA label.\n\n<span class=\"fE\">/ \"NNEESSWW\"</span>\n\nThe / command tells the Robot to move along a given path. The\npath is represented with a string of the letters N, S, E, W,\nand I. NSEW represent the four cardinal directions, while I\ntells the Robot to wait for a bit before continuing. Our path\ntells the Robot to go north twice, east twice, south twice, and\nwest twice, forming a square path.\n\n<span class=\"fE\">GOTO \"loop\"</span>\n\nThe Robot will now return to the \"loop\" label, continuing in a\nsquare path forever. Without an outside stimulus, the Robot\nwill never do anything else.\n\n<span class=\"fE\">: \"touch\"</span>\n\nAnother label, but this one is special. When the player touches\na Robot, by standing next to it and pressing against it, the\nRobot is sent to the label \"touch\". MegaZeux has several labels\nlike this, called \"built-in labels\", that are triggered in\nspecific ways. The following commands will be run when the\nplayer touches the Robot.\n\n<span class=\"fE\">CHAR 'X'</span>\n\nThis tells the Robot to assume the appearance of character X.\nMake sure that the X is in apostrophes ' and NOT quotes \".\n\n<p class=\"hC\">Robot Interaction</p>\nLet's get some Robots to interact with each other. Put two\nRobots on a board, right next to each other. Name the left one\nLefty, and give it this program:\n\n <span class=\"fE\">: \"dance\"</span>\n <span class=\"fE\">/ \"WWWEEE\"</span>\n <span class=\"fE\">SEND \"Righty\" to \"dance\"</span>\n <span class=\"fE\">END</span>\n\nName the right one Righty, and give it this program:\n\n <span class=\"fE\">END</span>\n <span class=\"fE\">: \"dance\"</span>\n <span class=\"fE\">/ \"EEEWWW\"</span>\n <span class=\"fE\">SEND \"Lefty\" to \"dance\"</span>\n <span class=\"fE\">END</span>\n\nNow test this board out. You will see a silly pair of dancing\nRobots, as they bump each other left and right. Let's examine\ntheir programs more closely.\n\nSince Righty has an END command as the first line, he isn't\ngoing to be doing anything until told; so, we look at Lefty.\nIt's nothing we haven't seen, except for the SEND command.\n\n<span class=\"fE\">SEND \"Robot\" to \"message\"</span>\n\nThe SEND command allows one Robot to send messages to another,\ntelling it what to do. In this case, Lefty will tell Righty to\n\"dance\", sending Righty to the label \"dance\".\n\nKnowing this, the behavior of the two Robots should be clear.\nEach one, in turn, moves away and back again, then it tells\nthe other one to do this. This repeats ad infinitum.\n\nFor a little practice, see if you can get them each to display\na message at the start of their dance step. Make the messages\ndifferent for each Robot.\n\n<p class=\"hC\">Some New Commands</p>\nThe following Robot will demonstrate some new commands and\nfeatures.\n\n <span class=\"fE\">GIVE 6 AMMOS</span>\n <span class=\"fE\">GIVE 5 HIBOMBS</span>\n <span class=\"fE\">END</span>\n <span class=\"fE\">: \"shot\"</span>\n <span class=\"fE\">COLOR c2C</span>\n <span class=\"fE\">[ \"OKAY, I GET THE POINT. I NEED MORE IRON\"</span>\n <span class=\"fE\">[ \"...and now a liver transplant :&lt;\"</span>\n <span class=\"fE\">zap \"shot\" 1</span>\n <span class=\"fE\">END</span>\n <span class=\"fE\">: \"shot\"</span>\n <span class=\"fE\">IF \"AMMO\" = 0 \"lecture\"</span>\n <span class=\"fE\">[ \"...cut it out. =(\"</span>\n <span class=\"fE\">END</span>\n <span class=\"fE\">: \"bombed\"</span>\n <span class=\"fE\">. \"Check this out...\"</span>\n <span class=\"fE\">. \"(editor) I'm not impressed.\"</span>\n <span class=\"fE\">* \"ARRGHFRRAGGH I'M ALLERGIC TO BEING BOMBED\"</span>\n <span class=\"fE\">DIE</span>\n <span class=\"fE\">END</span>\n <span class=\"fE\">: \"touch\"</span>\n <span class=\"fE\">* \"Please leave me alone :/\"</span>\n <span class=\"fE\">WAIT 10</span>\n <span class=\"fE\">SHOOT SEEK</span>\n <span class=\"fE\">END</span>\n <span class=\"fE\">: \"lecture\"</span>\n <span class=\"fE\">[ \"Did you REALLY have to use all of your shots.\"</span>\n <span class=\"fE\">END</span>\n\nTest this Robot out, bombing, shooting, and touching it. The\nnew commands and labels are explained below.\n\n<span class=\"fE\">GIVE 6 AMMOS</span>\n\nSensibly enough, this gives the player 6 units of ammo.\n\n<span class=\"fE\">GIVE 5 HIBOMBS</span>\n\nThis gives the player 5 high-strength bombs.\n\n<span class=\"fE\">: \"shot\"</span>\n\nWhen the Robot is shot by a bullet, it jumps to this label.\n\n<span class=\"fE\">COLOR c2C</span>\n\nThis changes the Robot's color. The c2C represents Lt. Red on\nRed. To enter colors, press F2 in the Robot editor and select\nthe color you want. The appropriate cXX code will be typed in\nfor you.\n\n<span class=\"fE\">[ \"OKAY, I GET THE POINT. I NEED MORE IRON\"</span>\n<span class=\"fE\">[ \"...and now a liver transplant :&lt;\"</span>\n\nThe [ command is slick for a default. When encountered, all\nconsecutive [ commands are put together to form one long\nmessage, which is then displayed in a Scroll-like window.\nIt can be scrolled and viewed by the player.\n\n<span class=\"fE\">ZAP \"shot\" 1</span>\n\nThe ZAP command tells the Robot to ignore a certain number of\nlabels with the given name - in this case, the first \"shot\"\nlabel. The ZAP command always starts from the top. Using the\nZAP command lets programmers give out different reactions for\ngoing to the same label. In this case, we get to go to the\nsecond \"shot\" label after we went to the first.\n\n<span class=\"fE\">IF \"AMMO\" = 0 \"lecture\"</span>\n\nThe IF command will jump to a label (in this case, one called\n\"lecture\") if the given condition is met, and will continue to\nthe next line if not met. In this case, it will check to see if\nthe AMMO counter is 0. The AMMO counter is a built-in counter\nMZX uses to represent how many units of ammo the player holds.\n\n<span class=\"fE\">: \"bombed\"</span>\n\nWhen the Robot is bombed, or otherwise hit by an explosion,\nit is sent this message.\n\n<span class=\"fE\">. \"Check this out...\"</span>\n<span class=\"fE\">. \"(editor) I'm not impressed.\"</span>\n\nNotice how this message was not shown in any way. The . command\nis used for comments, i.e. messages that are not to be shown.\nThey are good for your own reference, so you know what you were\ntrying to do with this Robot, or so you can keep notes of\nimportant things.\n\n<span class=\"fE\">DIE</span>\n\nThis command destroys the Robot forever.\n\n<span class=\"fE\">WAIT 10</span>\n\nWAIT causes the Robot to stop and sit for an amount of time.\nThe time is in number of cycles (in this case, 10 cycles). A\ncycle is the amount of time it takes to update the board once,\nmoving all the enemies and bullets, etc. Robots can run at\ndifferent speeds; the default is cycle 1 (fastest, acts every\ncycle) unless a cycle command is placed inside the Robot.\n\n<span class=\"fE\">SHOOT SEEK</span>\n\nThe Robot will shoot a bullet in the indicated direction (in\nthis case, the nearest direction the player is in).\n\n<p class=\"hC\">Conclusion</p>\nYou now should know enough Robotic for simple programs. Even\nmore importantly, you should be able to scan the Robotic\nreference manual, including the command reference, and be able\nto learn many new commands. There are loads of important\ncommands and concepts in the command reference that we have not\nyet mentioned, such as TELEPORT PLAYER, strings, file access,\nexpressions, sprites, and the COPY class of statements. It is\nrecommended that you at least skim each of the Robotic reference\nmanual sections.\n\nMake sure you (eventually) read over every part of the\nreference manual, as there are many sections not covered in the\ntutorials. Especially of interest will be advanced topics like\n\"The Vlayer and Its Uses\", \"Using MZMs\", \"Subroutines\",\n\"Debug Modes\", and most newer help file additions.\n\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"THEGLOBL.HLP\">\n<a class=\"hA\" name=\"THEGLOBL.HLP__gbl\"> </a>\n<p class=\"hC\"><span class=\"f9\">The Global</span></p>\nThe global Robot is a special-case Robot that is always present\non every board of the game. It is immobile (technically off the\nboard, but listed as at (-1,-1)), has a ROBOT_ID of 0, and\ncannot be destroyed or changed into another object, even\nthrough deliberate attempts to do so such as the EXPLODE\ncommand. All other traits of Robots, such as 2MB of Robot\nmemory, reserved local counters and the ability to be copied,\napply to the global.\n\nThe global is accessed either through pressing G to access the\nGlobal Settings menu and then selecting Edit Global Robot, or by\nsimply pressing Alt+G on the editor screen.\n\nThe global is important because it not only exists on all\nboards, but also continues its state across boards. This allows\nfor easy management of continuous global engines such as an\ninventory, status bars, and sidescrolling control, among\nother possibilities. If the global did not exist, a copy of the\nengine would be needed on every board, and the states of each\nsuch Robot would be separate (possibly leading to management\nproblems).\n\nCertain commands (as hinted above) are worthless in the global.\nObvious commands such as BECOME, EXPLODE, COLOR/CHAR and all\ncommand forms that take a relative direction (like LAYBOMB and\nDUPLICATE SELF [dir]) will be ignored. DIE, DIE ITEM and GOTOXY\n# # are completely ignored as well. Any checks at (-1,-1) will\nfail to detect the global.\n\nFinally, the global is the only Robot that ignores FREEZETIME\nand SLOWTIME states. This is extremely handy in creating the\nability to pause and unpause a MegaZeux game, among other\nthings.\n\n<a class=\"hL\" href=\"#COMMAND2.HLP___f4\">FREEZETIME #</a>\n<a class=\"hL\" href=\"#COMMAND2.HLP___sS\">SLOWTIME #</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"BADPRACT.HLP\">\n<a class=\"hA\" name=\"BADPRACT.HLP__bad\"> </a>\n<p class=\"hC\"><span class=\"f9\">Robotic Usages That Should be Avoided</span></p>\nThere are several coding pitfalls in Robotic. While some are\ncomplex in nature, others can be easily avoided. In general,\nclean and efficient code is the least likely to cause\nunexpected errors as well as the easiest to repair. Listed here\nare several practices to avoid, as well as proper replacements.\n\n<span class=\"fc\">BAD: Creating anything with c?? or p??</span>\n  Anything that has an undefined value will use a default\n  that may be undesirable. The correct action would therefore\n  be to define the object's color and parameter values. c?? and\n  p?? generally exist for the sake of Robotic comparisons and\n  conditional branching, not for being applied to actual\n  objects.\n  Sprites are an exception regarding color, since they refer to\n  a set of placed characters with their own colors.\n<span class=\"f9\">GOOD: Putting in set values for colors and parameters when</span>\n  <span class=\"f9\">placing objects.</span>\n\n<span class=\"fc\">BAD: Using global counters for temporary or localized work.</span>\n  There are several counters that are Robot-specific and don't\n  eat up global counter space. local through local32 per Robot\n  should be enough for almost everyone, and the local counters\n  can be written to and read by other Robots as well (using the\n  rN.local# counter form). If global temp counters are used in\n  a Robot with several copies on the board, you will most likely\n  run into problems. Unfortunately, using local counters can\n  come at the cost of minor readability, since local counters\n  cannot be given descriptive names.\n<span class=\"f9\">GOOD: Using local counters for temporary or localized work.</span>\n\n<span class=\"fC\">BAD: Breaking a subroutine without returning or entering the</span>\n  <span class=\"fC\">middle of a subroutine.</span>\n<span class=\"f9\">GOOD: Fully finishing and starting subroutines.</span>\n  This is especially important for built-in labels which can\n  trigger very rapidly, such as #keyN.\n\n<span class=\"fc\">BAD: Using a high amount of if statements regarding one</span>\n  <span class=\"fC\">variable.</span>\n<span class=\"f9\">GOOD: Using \"label&amp;number&amp;\" as the destination label in IF</span>\n  <span class=\"f9\">statements regarding one variable.</span>\n\n<span class=\"fC\">BAD: Looping without using cycle-ending commands of any kind.</span>\n  Looping without CYCLE 1 or WAIT 1 in idle loops is more\n  severe than one might think. Coupled with the \"commands\"\n  counter set to a high number, loops without CYCLE 1 or WAIT 1\n  can absolutely choke a game's speed, especially idle loops,\n  and potentially FREEZE YOUR GAME.\n<span class=\"f9\">GOOD: Including CYCLE 1 or WAIT 1 in every loop except loops</span>\n  <span class=\"f9\">that are designed to get a finite length task done as</span>\n  <span class=\"f9\">quickly as possible.</span>\n  You should add a CYCLE 1 or WAIT 1 for any loop that\n  constantly checks for events (such as keypresses or board\n  status).\n\n<span class=\"fC\">BAD: Setting the COMMANDS value to maximum.</span>\n  It's generally unneeded, and a loop without a cycle-ending\n  command coupled with this change is a guaranteed way to cause\n  freezes in MZX. MZX has safeguards for when this happens, but\n  it is still very undesirable.\n<span class=\"f9\">GOOD: Setting the COMMANDS counter to a reasonably high value.</span>\n  Your code can utilize the extra processing speed higher\n  commands-per-cycle allows, and if a loop without a\n  cycle-ending command is accidentally created, it becomes\n  apparent without requiring severe intervention.\n\n<span class=\"fc\">BAD: Ending Robots with a label.</span>\n<span class=\"f9\">GOOD: Ending Robots with the END command.</span>\n  It's debatable whether putting END after commands that\n  destroy the Robot (e.g. DIE, EXPLODE #) is necessary, but it\n  is still recommended in order to prevent a habit of leaving\n  it out in less redundant places.\n\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"SUBROUTE.HLP\">\n<a class=\"hA\" name=\"SUBROUTE.HLP__sub\"> </a>\n<p class=\"hC\"><span class=\"f9\">Subroutines</span></p>\nSubroutines are returnable labels, in essence. After the\nsubroutine actions finish, the Robotic goes to the line right\nafter the subroutine call. Anywhere a label can be used, a\nsubroutine can be called.\n\nAnother way to think about it is a label with a place marker.\nGoing to this label marks your previous place; when done, the\nprogram goes straight to the place marker and returns where it\nleft off.\n\nSubroutines are accessed by using the # sign as the first\nletter. For example: <span class=\"fe\">goto \"#example\"</span><span class=\"ff\"> would call the</span>\nsubroutine at the label \"#example\". To return to the next\ncommand after the call use <span class=\"fe\">goto \"#return\"</span><span class=\"ff\">.</span>\n\nIn addition, one can go to the first subroutine's return\nposition using <span class=\"fe\">goto \"#top\"</span><span class=\"ff\">.</span>\n\nA quick example of subroutines:\n\n<span class=\"fE\">* \"YE SHALL NOT PASS UNLESS YOU GIVE ME SPACE.\"</span>\n<span class=\"fE\">goto \"#spacewait\"</span>\n<span class=\"fE\">* \"THAT'S BETTER BUT YOU'RE STILL HERE. _CORRECT THIS_\"</span>\n<span class=\"fE\">goto \"#spacewait\"</span>\n<span class=\"fE\">* \"THANKS FOR YOUR COMPLIANCE!\"</span>\n<span class=\"fE\">wait 25</span>\n<span class=\"fE\">* \"YOUR PASS WILL ARRIVE IN 6 TO 8 WEEKS! or now! byebyebye\"</span>\n<span class=\"fE\">set \"PASS\" to 1</span>\n<span class=\"fE\">die</span>\n<span class=\"fE\">end</span>\n<span class=\"fE\">: \"#spacewait\"</span>\n<span class=\"fE\">: \"spaceheld\"</span>\n<span class=\"fE\">cycle 1</span>\n<span class=\"fE\">. \"If space is held down, progress no further.\"</span>\n<span class=\"fE\">. \"We want to force space to be pressed each time.\"</span>\n<span class=\"fE\">if spacepressed \"spaceheld\"</span>\n<span class=\"fE\">: \"notspaceloop\"</span>\n<span class=\"fE\">cycle 1</span>\n<span class=\"fE\">if not spacepressed \"notspaceloop\"</span>\n<span class=\"fE\">goto \"#return\"</span>\n\nWhen sending other Robots to subroutines the behavior is\nslightly different. Because the Robot is pre-empted\n(interrupted) it will return to the same command it was at\nbefore going to the subroutine, not the instruction after.\n\nWhen using subroutines - especially with easily triggerable\nconditions - it is important to try to make sure that every\nsubroutine send ends in a #return, or that every set of\nsubroutine sends ends in a #top. The built-in labels can be\nespecially problematic, as some can trigger rapidly in a short\nperiod of time. State management with LOCKSELF and/or ZAP is\ncrucial.\n\nNOTE: Unlike older versions of MegaZeux, it is no longer needed\nto initialize the stack, so don't get confused looking at older\nexamples of code and seeing odd statements like <span class=\"fe\">. \"#*-1-2-3\"</span><span class=\"ff\">.</span>\nThese statements are now treated as comments, and you should\nmentally treat them the same way.\n\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"EXPRESS.HLP\">\n<a class=\"hA\" name=\"EXPRESS.HLP__exp\"> </a>\n<p class=\"hC\"><span class=\"f9\">Expressions</span></p>\nExpressions in MegaZeux allow users to set counters or a\ndisplayed number to a mathematical expression. The possible\ninputs are: counters, constants, or a combination. Any\nexpression in MegaZeux is bound in quotation marks and\nparentheses. For example, an expression adding 5 and 3 would\nbe done in MegaZeux by inputting \"(5 + 3)\".\n\nCounters can be used inside expressions using single quotes or\nampersands like so: \"('five' + &amp;three&amp;)\". Using single quotes is\npreferred to prevent confusion with the normal usage of\nampersands to interpolate counters.\n\nUnlike in many other programming languages, MegaZeux does not\nfollow a strict order of operations. Items in parentheses take\nprecedence, then unary ops; ternary is evaluated last, with all\nother operator precedence solely determined by their positions.\nUnary ops are handled right-to-left; everything else is handled\nleft-to-right.\n\nFor example, \"(5 - 4 * 20 / (21 - 16))\" would evaulate to 4.\n5 - 4 evaluates to 1; 1 * 20 evaluates to 20; 20 / (21 - 16)\nfirst evaluates to 20 / 5, which becomes 4.\n\nWhitespace between operators is not necessary. (5+3) is as valid\nas (5 + 3).\n\nMegaZeux cannot decipher directly nested ampersands or single\nquotes; such will cause MegaZeux to misinterpret your\nexpression in different ways, depending on which you used.\nTherefore, using expressions for nesting is another benefit.\n\nExample: We set \"r\" to 1, \"in\" to 8 and \"out18\" to 11. We want\nto output the value of \"out18\".\n\"(&amp;out&amp;r&amp;&amp;in&amp;&amp;)\" would output (0r&amp;in&amp;).\n\"('out'r''in'')\" would output ('out'r''in'')\n\"('out('r')('in')')\" would output the desired result - 11.\n\nNesting is limited only to the amount one can put in a single\neditor line - 241 characters.\n\nIn expressions, the following operators may be used:\n\nBinary operators (value-argument-value):\n\n +   Addition\n -   Subtraction\n *   Multiplication\n /   Division\n %   Modulo\n ^   Exponent (A^B is A to the Bth order/power)\n &gt;&gt;  Bitshift Right (logical, not arithmetic)\n &lt;&lt;  Bitshift Left (logical, not arithmetic)\n &gt;&gt;&gt; Signed Shift Right (arithmetic)\n &gt;   Greater Than\n &lt;   Less Than\n &gt;=  Greater Than or Equal To\n &lt;=  Less Than or Equal To\n =   Equal To\n !=  Not Equal To\n a   Bitwise AND (not logical AND)\n o   Bitwise OR (not logical OR)\n x   Bitwise XOR (not logical XOR)\n\n<span class=\"fE\">+ Addition</span><span class=\"fF\">: Outputs the sum of the two given values. (3+2)</span>\nwould return 5.\n<span class=\"fE\">- Subtraction</span><span class=\"fF\">: Outputs the difference of the two given values.</span>\n(3-2) would return 1.\n<span class=\"fE\">* Multiplication</span><span class=\"fF\">: Outputs the product of the two given values.</span>\n(3*2) would return 6.\n<span class=\"fE\">/ Division</span><span class=\"fF\">: Outputs the quotient of the two given values.</span>\n(3/2) would return 1. Note that any fractional values will be\ntruncated, i.e. have the decimal part of the number thrown out.\n<span class=\"fE\">% Modulo</span><span class=\"fF\">: Outputs the remainder of the two given values, when</span>\nboth values are positive. (5%3) would return 2. The modulo\nexpression function and the modulo command may output different\nresults if any number involved is negative; unlike the command,\nthe modulo expression function uses a floored modulo.\nTo explain, if the dividend is negative, than the result will\nbe the divisor minus the remainder (e.g. (-5%3) would return\n1); if the divisor is negative, than the result will be the\nadditive inverse of the difference of the divisor and the\nremainder (e.g. (5%-3) would return -1); if BOTH are negative,\nthe result will be the additive inverse of the remainder (e.g.\n(-5%-3) would return -2).\n<span class=\"fE\">^ Exponent</span><span class=\"fF\">: Outputs the result of taking the first number to</span>\nthe power of the second number. (3^2) would return 9.\n<span class=\"fE\">&gt;&gt; Bitshift Right</span><span class=\"fF\">: Shifts the first number rightward by [second</span>\nnumber] of bits. Info shifted off of the boundaries will NOT\nwrap around. Using an 8-bit counter for clarity's sake, (5&gt;&gt;2)\nwould turn 5 [00000101] into 1 [00000001] by shifting two bits\nrightward.\n<span class=\"fE\">&lt;&lt; Bitshift Left</span><span class=\"fF\">: Shifts the first number leftward by [second</span>\nnumber] of bits. Info shifted off of the boundaries will NOT\nwrap around. Using an 8-bit counter for clarity's sake, (5&lt;&lt;2)\nwould turn 5 [00000101] into 20 [00010100] by shifting two bits\nleftward.\n<span class=\"fE\">&gt;&gt;&gt; Signed Shift Right</span><span class=\"fF\">: Also known as Arithmetic Right Shift.</span>\nShifts the first number rightward by [second number] of bits,\nbut unlike normal bitshift right, shifts in whatever number was\nin the leftmost bit instead of always shifting in 0s. Info\nshifted off of the boundaries still will not wrap around. Using\nan 8-bit counter for clarity's sake, (5&gt;&gt;&gt;2) would turn 5\n[00000101] into 1 [00000001], while (-5&gt;&gt;&gt;2) would turn -5\n[11111011] into -2 [11111110].\n<span class=\"fE\">&gt; Greater Than</span><span class=\"fF\">: If the first number is greater than the second</span>\nnumber, outputs 1 (TRUE); otherwise, outputs 0 (FALSE). (3&gt;2)\nwould return 1, (2&gt;2) would return 0.\n<span class=\"fE\">&lt; Less Than</span><span class=\"fF\">: If the first number is less than the second</span>\nnumber, outputs 1 (TRUE); otherwise, outputs 0 (FALSE). (3&lt;2)\nwould return 0, as would (2&lt;2).\n<span class=\"fE\">&gt;= Greater Than or Equal To</span><span class=\"fF\">: If the first number is either</span>\ngreater than or equal to the second number, outputs 1 (TRUE);\notherwise, outputs 0 (FALSE). (3&gt;=2) would return 1, as would\n(2&gt;=2).\n<span class=\"fE\">&lt;= Less Than or Equal To</span><span class=\"fF\">: If the first number is either less</span>\nthan or equal to the second number, outputs 1 (TRUE);\notherwise, outputs 0 (FALSE). (3&lt;=2) would return 0, (2&lt;=2)\nwould return 1.\n<span class=\"fE\">= Equal To</span><span class=\"fF\">: If the first number is the same value as the second</span>\nnumber, outputs 1 (TRUE); otherwise, outputs 0 (FALSE). (3=2)\nwould return 0, (2=2) would return 1.\n<span class=\"fE\">!= Not Equal To</span><span class=\"fF\">: If the first number is a different value</span>\ncompared to the second number, outputs 1 (TRUE); otherwise,\noutputs 0 (FALSE). (3!=2) would return 1, (2!=2) would return\n0.\n<span class=\"fE\">a = Bitwise AND</span><span class=\"fF\">: Performs a bitwise AND operation on the two</span>\ngiven values; i.e. compares the bits of each value and sets to\n1 each bit that is 1 in both numbers. Using an 8-bit counter\nfor clarity's sake, (5a3) would return 1, as 5 [00000101] and 3\n[00000011] has only the bit in the ones place set in both\nnumbers, leaving 1 [00000001].\n<span class=\"fE\">o = Bitwise OR</span><span class=\"fF\">: Performs a bitwise OR operation on the two</span>\ngiven values; i.e. compares the bits of each value and sets to\n1 each bit that is 1 in either number. Using an 8-bit counter\nfor clarity's sake, (5o3) would return 7, as 5 [00000101] and\n3 [00000011] have bits set in the fours, twos, and ones places\namong them, leaving 7 [00000111].\n<span class=\"fE\">x = Bitwise XOR</span><span class=\"fF\">: Performs a bitwise XOR operation on the two</span>\ngiven values; i.e. compares the bits of each value and sets to\n1 each bit that is different between numbers. Using an 8-bit\ncounter for clarity's sake, (5x3) would return 6, as 5\n[00000101] and 3 [00000011] have different bits set in the\nfours and the twos places, leaving 6 [00000110].\n\nUnary operators (operator followed by value):\n\n<span class=\"fE\">- Unary Negation</span><span class=\"fF\">: returns the negative value of the operand</span>\n(two's complement); i.e. reverses the sign of a number. (-10)\nwould return -10.\n<span class=\"fE\">~ Bitwise Negation</span><span class=\"fF\">: returns the bitwise NOT value of the operand</span>\n(one's complement); i.e. changes each bit in a number. Using an\n8-bit counter for clarity's sake, (~10) [00001010] would return\n-11. [11110101]\n\nTernary operators (value - operator token - value - operator\ntoken - value):\n\n<span class=\"fE\">?: Conditional Operator</span><span class=\"fF\">: Evaluates expressions based on the</span>\nvalue of the first given expression. This operator is often\nsimply called the ternary operator. If the expression to the\nleft of the question mark is non-zero, the expression between\nthe question mark and colon is evaluated; otherwise, the\nexpression to the right of the colon is evaluated. For example,\nIf \"local\" is 35, SET \"local\" ('local'&lt;60?'local'+2:'local'+1)\nwould set the value of \"local\" to 37, as the first expression\nevaluates to 1 and the second expression ('local'+2) is then\nevaluated.\n\nSpecial usages:\n\n-Using a constant expression \"(n)\" is the only way to directly\nuse numbers over 32767 or under -32768. This can not be changed\ndue to the way the world file format is coded. This is also the\nonly way to use octal (base 8) and full-sized hexadecimal\nnumbers; octal numbers always begin with a 0 (ex: 01745) and hex\nnumbers begin with 0x (ex: 0xDEAF).\n\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"FILEACSS.HLP\">\n<a class=\"hA\" name=\"FILEACSS.HLP__fil\"> </a>\n<p class=\"hC\"><span class=\"f9\">File Access</span></p>\nMegaZeux allows somewhat unwieldy but powerful file access\nabilities. File access can do things that no other tools of\nMegaZeux can do, such as create high score tables or preserve\nand unlock game options without needing a save. An MZX world\ncan access any file in its own directory or deeper, including\nfor charset, palette, sound and MZM loading commands. To do\nthis, give MZX the subdirectory name as well as the file name.\nBoth slashes and backslashes are accepted, regardless of OS.\nExamples: \"subdir/subsubdir/file.txt\", \"subdir\\\\file.txt\".\nAccess of folders outside of this range is forbidden.\n\nMegaZeux has some ways of accessing standard formats in-game:\n\n<span class=\"fA\">SET \"file.txt\" to \"SAVE_ROBOT\"</span>\n<span class=\"fA\">SET \"file.txt\" to \"LOAD_ROBOT\"</span>\n<span class=\"fA\">SET \"file.txt\" to \"SAVE_ROBOTn\"</span>\n<span class=\"fA\">SET \"file.txt\" to \"LOAD_ROBOTn\"</span>\n\nThese commands can save Robotic code to text files and import\ntext files into a Robot. The first forms use the current Robot,\nwhile the second forms use the Robot with the given Robot ID n.\nString inputs are also accepted in place of hard-coded file\nnames.\n\n<span class=\"fA\">SET \"file.bc\" to \"SAVE_BC\"</span>\n<span class=\"fA\">SET \"file.bc\" to \"LOAD_BC\"</span>\n<span class=\"fA\">SET \"file.bc\" to \"SAVE_BCn\"</span>\n<span class=\"fA\">SET \"file.bc\" to \"LOAD_BCn\"</span>\n\nThese commands can save Robotic code to files in bytecode format\nand import bytecode files into a Robot. The first forms use the\ncurrent Robot, while the second forms use the Robot with the\ngiven Robot ID n. Bytecode is much more compact than plain text,\nbut cannot be easily read by the human eye.\n\nNote that for loading Robotic code via LOAD_ROBOT(n) or\nLOAD_BC(n), any and all local counters previously set in the\nRobot will retain their value.\n\n<span class=\"fA\">SET \"file.sav\" to \"SAVE_GAME\"</span>\n<span class=\"fA\">SET \"file.sav\" to \"LOAD_GAME\"</span>\n\nThese commands save or load MZX savegames, respectively.\n\n<span class=\"fA\">SET \"file.sav\" to \"SAVE_COUNTERS\"</span>\n<span class=\"fA\">SET \"file.sav\" to \"LOAD_COUNTERS\"</span>\n\nThese commands respectively save or load a counters file.\nCounter files only contain counter and string data (to be\nspecific, only global and current board counters/strings).\nUnlike save files, they do not require version checks. Saving\nwill dump the current contents of the current world's counters\nand strings; loading will set the current world's counter and\nstring values to the values given in the file.\n\n<span class=\"fA\">SET \"file.palidx\" to \"SMZX_INDICES\"</span>\n\nThis command loads an SMZX indices file. SMZX indices are\nessentially miniature palettes and will only load in SMZX Mode\n3.\n\nMegaZeux can manipulate any type of file with certain Robotic\ncommands. Here are some basic things to remember when using file\naccess.\n\n-A file can only be read or written to at one time, never both.\nThis means you can't select the same file to be the current\n\"FREAD_OPEN\" and \"FWRITE_OPEN\" targets.\n-Only one file can be read to and one can be written to at the\nsame time.\n-Writing and reading operations change the current fwrite and\nfread positions.\n-The writing and reading position is set to 0 (the beginning\nposition of a file) when a file is loaded, unless the\nFWRITE_APPEND command is chosen.\n-There is a special character called the terminator (char #42,\n'*', by default) placed at the end of a write when a string is\nwritten to a file with FWRITE. This character ends an FREAD to\na string when encountered (the terminator character itself will\nnot be in the string output). However, using FWRITEn will not\nplace a terminator, and FREADn will ignore any terminator\ncharacters encountered.\n-To find the length of a file, use the FREAD_LENGTH and\nFWRITE_LENGTH counters. They will report the length of the file\n(in characters) currently open in the respective mode. If a\ndirectory is open for reading, the counter will be the number of\nfiles and subdirectories in that directory. If there is no file\nor directory open, these counters will return -1.\n-Lastly, files need to be closed when the current task is\nfinished. Otherwise, the file remains open and causes problems.\nTo close a file, use SET \"\" to \"FREAD_OPEN\" to close a file for\nreading and SET \"\" to \"FWRITE_OPEN\" to close a file for\nwriting.\n\nHere are the file writing commands at your disposal.\n\n<span class=\"fA\">SET \"file.xxx\" to \"FREAD_OPEN\"</span>\n\nThis command loads a file for reading.\n\n<span class=\"fA\">SET \"file.xxx\" to \"FWRITE_OPEN\"</span>\n\nThis command creates a new file for writing. If a file by this\nname already exists, it is overwritten.\n\n<span class=\"fA\">SET \"file.xxx\" to \"FWRITE_MODIFY\"</span>\n\nThis command opens an existing file for reading without\noverwriting it, starting at the beginning of the file.\n\n<span class=\"fA\">SET \"file.xxx\" to \"FWRITE_APPEND\"</span>\n\nThis command opens a file for writing at the END of the file.\nPlease note that FWRITE_POS settings will not work in this\nmode.\n\n<span class=\"fA\">SET \"FREAD_POS\" to #</span>\n<span class=\"fA\">SET \"FWRITE_POS\" to #</span>\n\nThese commands change the current reading or writing position\nof the accessed files. The position is in DECIMAL, not\nhexadecimal, and the first position is 0; when no file is\nloaded, the relevant counter is -1.\n\nSetting \"FREAD_POS\" or \"FWRITE_POS\" to -1 is called \"file\nend seeking\" and will set the relevant counter to the final\nposition in the file (or -1 if the file does not exist). This is\nvery helpful for placing stuff at the end of a file.\n\n<span class=\"fA\">SET \"dir\" to \"FREAD_OPEN\"</span>\n\nThis command opens subdirectories for reading (by using the name\nof the subdirectory by itself, e.g. <span class=\"fA\">SET \"directory\" to</span>\n<span class=\"fA\">\"FREAD_OPEN\"</span><span class=\"fF\">). FREAD will then list the names of files in the</span>\ndirectory, with each read position being a different filename.\nTo read the directory the current world is in,\n<span class=\"fA\">SET \".\" to \"FREAD_OPEN\"</span><span class=\"fF\">. Note that the files are not guaranteed</span>\nto be read in any specific order.\n\n<span class=\"fA\">SET \"FREAD_DELIMITER\" to #</span>\n<span class=\"fA\">SET \"FWRITE_DELIMITER\" to #</span>\n\nThese commands change the character considered the terminator.\nThe FREAD command changes which character is detected as the\nterminator, and the FWRITE command changes which character is\nwritten as the terminator.\n\n<span class=\"fA\">SET \"FWRITE\" to \"counter\"</span>\n<span class=\"fA\">SET \"$string\" to \"FWRITE\"</span>\n<span class=\"fA\">SET \"$string\" to \"FWRITEn\"</span>\n<span class=\"fA\">SET \"counter\" to \"FREAD\"</span>\n<span class=\"fA\">SET \"$string\" to \"FREAD\"</span>\n<span class=\"fA\">SET \"$string\" to \"FREADn\"</span>\n\nThese commands write values to and read values from,\nrespectively, the open file relative to the FWRITE_POS or\nFREAD_POS counters. FWRITE and FREAD, when used with counters,\nonly write one byte at a time (from 0-255).\nThe SET \"$string\" to \"FWRITEn\" and \"FREADn\" commands will write\nthe first n characters (relative to FREAD_POS or FWRITE_POS)\nfrom the file to the given string or read the first n\ncharacters from the string into the open file, respectively.\nUsing <span class=\"fA\">SET \"$string.N\" to \"FWRITE(n)\"</span><span class=\"fF\"> will ensure that the</span>\nstring will be at least N characters long when written.\nFWRITE and FREAD, when used with counters, can be substituted\nby FWRITE_COUNTER and FREAD_COUNTER to read in longer values\nat a time (four bytes as opposed to one).\n\n<span class=\"fA\">SET \"counter\" to \"FREAD_COUNTER\"</span>\n\nReads from the open file to a counter. This will grab four\nbytes instead of one (treated as signed, so the range is from\n-2147483648 to 2147483647).\n\n<span class=\"fA\">SET \"FWRITE_COUNTER\" to \"counter\"</span>\n\nWrites from a counter to the open file. This will write all four\nbytes of the counter instead of just the first byte (treated as\nsigned, so the range is -2147483648 to 2147483647).\n\nHere's a quick example of file access.\n\n<span class=\"fE\">SET \"example.txt\" to \"FWRITE_OPEN\"</span>\n<span class=\"fE\">SET \"$zoosound\" to \"ROAAAAR\"</span>\n<span class=\"fE\">SET \"$zoosound\" to \"FWRITE\"</span>\n<span class=\"fE\">DEC \"FWRITE_POS\" by 1</span>\n<span class=\"fE\">. \"This line backs us up one space so that the terminator</span>\n<span class=\"fE\">. \"character will be overwritten, allowing us to display the\"</span>\n<span class=\"fE\">. \"whole contents of the file.\"</span>\n<span class=\"fE\">SET \"$ender\" to \" (click)\"</span>\n<span class=\"fE\">SET \"$ender\" to \"FWRITE\"</span>\n<span class=\"fE\">SET \"\" to \"FWRITE_OPEN\"</span>\n<span class=\"fE\">. \"We're closing the file because we're going to read from it.\"</span>\n<span class=\"fE\">SET \"example.txt\" to \"FREAD_OPEN\"</span>\n<span class=\"fE\">ASK \"Want to play your animal sounds tape?\"</span>\n<span class=\"fE\">END</span>\n<span class=\"fE\">: \"yes\"</span>\n<span class=\"fE\">SET \"$tape\" to \"FREAD\"</span>\n<span class=\"fE\">* \"&amp;$tape&amp;\"</span>\n<span class=\"fE\">WAIT for 30</span>\n<span class=\"fE\">ZAP \"yes\" 1</span>\n<span class=\"fE\">ASK \"Want to play it again?\"</span>\n<span class=\"fE\">END</span>\n<span class=\"fE\">: \"yes\"</span>\n<span class=\"fE\">SET \"\" to \"FREAD_OPEN\"</span>\n<span class=\"fE\">. \"Switching tasks for the same file again.\"</span>\n<span class=\"fE\">SET \"example.txt\" to \"FWRITE_OPEN\"</span>\n<span class=\"fE\">. \"We're wiping the file clean.\"</span>\n<span class=\"fE\">SET \"$winder\" to \"bzz-bz-zipfwip--(clik) \"</span>\n<span class=\"fE\">SET \"$winder\" to \"FWRITE\"</span>\n<span class=\"fE\">DEC \"FWRITE_POS\" by 1</span>\n<span class=\"fE\">SET \"$tape\" to \"FWRITE\"</span>\n<span class=\"fE\">SET \"\" to \"FWRITE_OPEN\"</span>\n<span class=\"fE\">SET \"example.txt\" to \"FREAD_OPEN\"</span>\n<span class=\"fE\">ZAP \"yes\" 1</span>\n<span class=\"fE\">: \"yes\"</span>\n<span class=\"fE\">SET \"FREAD_POS\" to 0</span>\n<span class=\"fE\">. \"We're forcing the beginning position because we want to\"</span>\n<span class=\"fE\">. \"read all of the file every possible time, not just the\"</span>\n<span class=\"fE\">. \"first time.\"</span>\n<span class=\"fE\">SET \"$tape\" to \"FREAD\"</span>\n<span class=\"fE\">* \"&amp;$tape&amp;\"</span>\n<span class=\"fE\">WAIT for 30</span>\n<span class=\"fE\">ASK \"Want to play it again?\"</span>\n<span class=\"fE\">END</span>\n<span class=\"fE\">: \"no\"</span>\n<span class=\"fE\">SET \"\" to \"FREAD_OPEN\"</span>\n<span class=\"fE\">END</span>\n\nThis Robot will display first a roar and the sound of a tape\nplayer clicking off before it asks if you want to play the tape\nagain. If no, the file is closed and the program ended. If yes,\nrewinding tape sounds are stuck on the front of the text. The\nplayer can then choose to re-view this text until \"No\" is\nchosen.\n\nHere is another example, for a basic and very common use of file\naccess: using and updating a high score list.\n\n<span class=\"fE\">SET \"game.hi\" \"FREAD_OPEN\"</span>\n<span class=\"fE\">SET \"NAME_LENGTH\" 3</span>\n<span class=\"fE\">SET \"SCORE_LENGTH\" 4</span>\n<span class=\"fE\">SET \"ENTRY_LENGTH\" 7</span>\n<span class=\"fE\">SET \"ENTRY_NUMBER\" 10</span>\n<span class=\"fE\">. \"We will allow 3 characters maximum for listed names here.\"</span>\n<span class=\"fE\">. \"File structure: initials (3 chars) and score (4 chars).\"</span>\n<span class=\"fE\">. \"Ten 7-char entries total, no chars between them, in order.\"</span>\n<span class=\"fE\">. \"This assumes a fully-populated default highscore list.\"</span>\n<span class=\"fE\">. \"However, it should be relatively simple to create your own.\"</span>\n<span class=\"fE\">SET \"FREAD_POS\" \"('FREAD_LENGTH'-'SCORE_LENGTH')\"</span>\n<span class=\"fE\">. \"First, we read the lowest high score value to compare.\"</span>\n<span class=\"fE\">. \"If our score is below it, no new high score.\"</span>\n<span class=\"fE\">SET \"local\" \"FREAD_COUNTER\"</span>\n<span class=\"fE\">IF \"SCORE\" &gt;= \"local\" \"newscore\"</span>\n<span class=\"fE\">SET \"\" \"FREAD_OPEN\"</span>\n<span class=\"fE\">END</span>\n<span class=\"fE\">: \"newscore\"</span>\n<span class=\"fE\">. \"Let's find out where it needs to go first.\"</span>\n<span class=\"fE\">SET \"FREAD_POS\" 0</span>\n<span class=\"fE\">: \"placecheck\"</span>\n<span class=\"fE\">INC \"FREAD_POS\" \"NAME_LENGTH\"</span>\n<span class=\"fE\">SET \"local\" \"FREAD_COUNTER\"</span>\n<span class=\"fE\">INC \"local2\" 1</span>\n<span class=\"fE\">IF \"SCORE\" &gt;= \"local\" \"placement\"</span>\n<span class=\"fE\">IF \"local2\" &lt; \"ENTRY_NUMBER\" \"placecheck\"</span>\n<span class=\"fE\">[ \"WTF\"</span>\n<span class=\"fE\">. \"We should never, ever, get here, but it's nice to check :)\"</span>\n<span class=\"fE\">SET \"\" \"FREAD_OPEN\"</span>\n<span class=\"fE\">END</span>\n<span class=\"fE\">: \"placement\"</span>\n<span class=\"fE\">DEC \"FREAD_POS\" \"ENTRY_LENGTH\"</span>\n<span class=\"fE\">SET \"$temp\" \"FREAD('FREAD_LENGTH'-('local2'*'ENTRY_LENGTH'))\"</span>\n<span class=\"fE\">. \"This places the lower, still-ranked entries into a string.\"</span>\n<span class=\"fE\">. \"We will write these scores to their new places later.\"</span>\n<span class=\"fE\">. \"We skip reading entry #10, as it no longer qualifies.\"</span>\n<span class=\"fE\">SET \"\" \"FREAD_OPEN\"</span>\n<span class=\"fE\">SET \"game.hi\" \"FWRITE_MODIFY\"</span>\n<span class=\"fE\">. \"This write mode keeps the previous contents intact.\"</span>\n<span class=\"fE\">SET \"FWRITE_POS\" \"('local2'-1*'ENTRY_LENGTH')\"</span>\n<span class=\"fE\">. \"Setting to write in the correct place.\"</span>\n<span class=\"fE\">INPUT STRING \"Hooray! You're #&amp;local2&amp;! Enter your initials.\"</span>\n<span class=\"fE\">SET \"$temp2\" \"INPUT\"</span>\n<span class=\"fE\">. \"The next line checks input length, as that needs to be 3+\"</span>\n<span class=\"fE\">IF \"$temp2.length\" &lt; \"NAME_LENGTH\" \"#correct\"</span>\n<span class=\"fE\">SET \"$temp2\" \"FWRITE&amp;NAME_LENGTH&amp;\"</span>\n<span class=\"fE\">. \"We only write the first 3 chars; ignore any extra.\"</span>\n<span class=\"fE\">SET \"FWRITE_COUNTER\" to \"SCORE\"</span>\n<span class=\"fE\">SET \"$temp\" \"FWRITE('$temp.length')\"</span>\n<span class=\"fE\">. \"The whole dang string goes into the rest of the file.\"</span>\n<span class=\"fE\">. \"Last example used FWRITE instead of FWRITEn for this.\"</span>\n<span class=\"fE\">. \"That leaves a terminator, though, which we don't want.\"</span>\n<span class=\"fE\">. \"This is another way to write it all with no terminator.\"</span>\n<span class=\"fE\">SET \"\" \"FWRITE_OPEN\"</span>\n<span class=\"fE\">. \"Closing the file doesn't require the same write type.\"</span>\n<span class=\"fE\">END</span>\n<span class=\"fE\">: \"#correct\"</span>\n<span class=\"fE\">IF \"$temp2.length\" = 0 \"#default\"</span>\n<span class=\"fE\">: \"add\"</span>\n<span class=\"fE\">. \"Input was under three chars long, and we need three.\"</span>\n<span class=\"fE\">INC \"$temp2\" \" \"</span>\n<span class=\"fE\">IF \"$temp2.length\" &lt; \"NAME_LENGTH\" \"add\"</span>\n<span class=\"fE\">GOTO \"#return\"</span>\n<span class=\"fE\">: \"#default\"</span>\n<span class=\"fE\">SET \"$temp2\" \"???\"</span>\n<span class=\"fE\">. \"If the user doesn't put in anything, this makes initials ???\"</span>\n<span class=\"fE\">GOTO \"#top\"</span>\n\nCode to properly display the scores will be an exercise left to\nthe reader.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__fac\">File Access Counters</a>\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"SPRITES.HLP\">\n<a class=\"hA\" name=\"SPRITES.HLP__spr\"> </a>\n<p class=\"hC\"><span class=\"f9\">Sprites</span></p>\nSprites allow the designer to have interactive entities on the\nboard composed of several characters. A sprite is a block of\ncharacters displayed on the board, either beneath or above the\noverlay.\nThe biggest benefit of sprites is that they are cohesive:\nunlike use of several Robots, there is absolutely no chance of\nthe sprite falling apart.\n\nSprites also have the benefit of a well-defined draw order. This\nmeans that if sprites overlap, it is easy to determine which\nsprite gets shown in the overlap. By default, sprites determine\nthis by sprite number. The sprites are drawn in order of sprite\nnumber, which means that parts of higher-numbered sprites will\nbe displayed over lower-numbered ones whenever they overlap.\n\nThere are up to 256 available sprites (globally, not per board),\nand sprites are controlled in the following ways:\n\n-Through sprite-specific counters named SPRn_value (\"n\" being\n the sprite number, e.g. SPR10_value modifies sprite number 10);\n-Through control counters for all of the sprites;\n-Through commands for placing sprites and testing for collision.\n\nA sprite's appearance is determined by a set of reference\ncharacters. These are characters on the board or the vlayer that\nmake up what the sprite will look like. By default, sprites look\nfor these characters on the board.\nAs with the overlay, any instance of char 32 in the sprite will\nnot be shown, instead displaying the next underlying layer.\nAlso, if the reference characters change, the sprite's\ncharacters will change to match, as will colors of a c?? sprite.\n\nTo set up a sprite you must set its SPRn_REFX / REFY / WIDTH /\nHEIGHT counters to define where the sprite's reference\ncharacters are and the sprite's size. (SPRn_REFX and SPRn_REFY\nare the x,y coordinates for the upper-left corner of the\nreference characters.) Then you use:\n\n<span class=\"fA\">PUT cXX sprite pNN x y</span>\n\nwhere NN is the number of the sprite in hexadecimal (if you\nwrite NN without the p in decimal it will be automatically\nconverted). If cXX is c?? then the sprite is drawn with the\ncolors of the reference characters, otherwise it's painted with\nthe given colors. Once a sprite is placed, it can be easily\nremoved by setting its SPRn_OFF counter to any value.\n\nSo, for example, to make a 2x2 sprite, you could base its\nreference characters on the board from the position (10, 10),\nassign it to sprite number 15, then have it drawn at position\n(40, 5). The Robotic for this is as follows:\n\n<span class=\"fA\">SET \"spr15_refx\" 10</span>\n<span class=\"fA\">SET \"spr15_refy\" 10</span>\n<span class=\"fA\">SET \"spr15_width\" 2</span>\n<span class=\"fA\">SET \"spr15_height\" 2</span>\n<span class=\"fA\">PUT c?? sprite p0f 40 5</span>\n\nYou can then move the sprite around by changing spr15_x and\nspr15_y. For example, using:\n\n<span class=\"fA\">INC \"spr15_x\" 1</span>\n<span class=\"fA\">DEC \"spr15_y\" 1</span>\n\nwill move the sprite to the northeast by 1.\n\nOutside of simple sprite motion such as this, you can do\ncollision tests. These check if moving sprites will cause\ncollision (i.e. overlapping) with either the foreground\n(customblocks) or with other sprites. This way, you can do\nthings such as prevent sprites from moving into walls or other\ncharacters, or allow a sprite to damage another one when\nstriking.\n\nFor these, sprite collision boxes must be set, as sprites will\nnot have one set by default. Use SPRn_CWIDTH and SPRn_CHEIGHT to\nset the respective width and height of the collision box (in\ntiles). By default, sprite collision boxes originate from the\nupper-left corner of the sprite, but the origin point can be\noffset horizonally and vertically by setting the SPRn_CX and\nSPRn_CY counters.\n\nOnce collision boxes are established, collision tests have to\nbe triggered. This is done by using the following command:\n\n<span class=\"fA\">IF c?? sprite_colliding pNN x y \"collision\"</span>\n\nThis will go to the label \"collision\" if moving sprite NN x by\ny from its current position would cause it to collide with\nsomething. If anything besides c?? is used then it tests whether\nit would collide with whatever is at absolute board coordinates\nx,y instead.\n\nIf collisions are detected, then the spr_clistN counters will be\nset to what sprite numbers it collided with - one counter set\nfor each other sprite in the collision, starting with spr_clist0\n- and spr_collisions will be set to the number of things that\nthe sprite collided with. If the sprite collided with the\nforeground, spr_clist0 will be set to -1.\n\nSo for instance, if the sprite collided with the foreground and\nsprites 2 and 3, spr_collisions will be set to 3, spr_clist0\nwill be -1, spr_clist1 will be 2, and spr_clist2 will be 3. If\nthe sprite collided only with sprites 2 and 3, spr_collisions\nwill be set to 2, spr_clist0 will be 2, and spr_clist1 will be\n3. The values of spr_clistN with values of N greater than\n(spr_collisions - 1) are undefined.\n\nAlternatively, a collision check for a sprite can be done by\nsetting that sprite n's \"SPRn_CLIST\" counter to anything. This\nchecks that sprite against its current location and does not\nforce a label jump on collision.\n\nSprites can be set to ignore certain types of sprite chars in\ncollision checks by setting the sprN_ccheck value of a given\nsprite N. In addition, setting a sprite's ccheck value to 2 will\nprevent blank characters in a sprite from being shown, much like\nchar 32.\n\nSince sprites are global, they will persist upon changing\nboards. (They can, however, be turned off on leaving a board by\nsetting the SPRn_OFFONEXIT counter to 1). Since the vlayer is\nglobal as well, using the vlayer to reference characters (by\nsetting the SPRn_VLAYER counter to 1) will keep sprite\ncharacters consistent between boards.\n\n<p class=\"hC\"><span class=\"f9\">Unbound Sprites</span></p><a class=\"hA\" name=\"SPRITES.HLP__ubs\"> </a>\nUnbound sprites are sprites with special implementations,\nallowing significantly greater precision and access to extra\ncharacter sets. They have their own setup, their own quirks, and\ntheir own unique restrictions.\n\nA sprite can be set as unbound by setting the SPRn_UNBOUND\ncounter to 1 for a given sprite number n. Once this is done, the\nsprite becomes \"unbound\" from the tile grid and immediately\nstarts using pixels as reference for the collision box (SPRn_CX,\nSPRn_CY, SPRn_CWIDTH, SPRn_CHEIGHT), for sprite location\n(SPRn_X, SPRn_Y), and for values in Sprite_Colliding checks.\nOther attributes, such as reference location and sprite\ndimensions, are left in terms of tiles. Essentially, this means\nthat sprite control and collision checks can now be easily\npixel-based.\n\n(If, for whatever reason, a sprite is given tile-based x/y\nparameters and placed before setting as an unbound sprite, the\nsprite will not adjust its x/y location in pixels to match and\nwill keep the literal x/y values, adjusting them to be in terms\nof pixels instead of tiles once the unbound status is set.)\n\nOn top of pixel-based manipulation, unbound sprites have\nextended display options at their disposal. Unlike other\nsprites, unbound sprites are allowed access to up to 14 \"extra\"\ncharacter sets.\n\nFirst, one must place characters in the extra locations.\nThis can be done in two ways: with the LOAD CHAR SET command or\nthe CHAR EDIT command. With LOAD CHAR SET, extra character sets\ncan be loaded into their proper places by using the @ prefix to\nload it into proper positions. The first extra character set\nbegins at position 256, the second at 512, the third at 768, and\nso on. (One could also load a character set file that is much\nlarger than normal to fill these positions.) CHAR EDIT can\nmanipulate individual characters of an extra charset by being\ngiven similar numbers.\n\nSecond, the sprite must be set to access these charsets. This is\ndone by setting an offset for that sprite using the SPRn_OFFSET\ncounter. For example, setting SPR0_OFFSET to 256 will change all\ncharacter references done by sprite 0 to the first extra\ncharacter set.\n\nAs this process implies, each unbound sprite is still limited to\none character set's worth of characters - 256 - simultaneously.\nHowever, different unbound sprites can accept different offsets\nand display their sets of characters at the same time, resulting\nin up to 3840 different characters loaded at once (not counting\nthe system set).\n\nIn addition to access to larger numbers of characters, unbound\nsprites can provide transparency effects on a per-sprite basis,\ninstead of merely treating certain characters as transparent.\nEach unbound sprite has its own transparent color, which is set\nwith the SPRn_TCOL counter. All pixels of that color in that\nunbound sprite will not display, and instead show the next\nunderlying layer.\n\nWhen assigned a ccheck value with SPRn_CCHECK, an unbound sprite\nwill apply all ccheck values of colliding sprites in collision\ntests instead of applying the value of the sprite that initiated\nthe check to all colliding sprites. In addition, unbound sprites\ncan use a special ccheck value (3) to make collision checks\npixel-based. A SPRn_CCHECK value of 3 will also ignore\ntransparent colors in collision checks.\n\nFinally, SMZX modes do not affect unbound sprite movement and\nmanipulation. This fact can be exploited to overlap SMZX sprites\nand potentially raise detail to normal MZX mode levels while\nretaining much of SMZX's palette benefits.\n\nSee the Sprite Counters and Robotic sections for other features\nof sprites not noted here.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__spc\">Sprite Counters</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"SMZXMODE.HLP\">\n<a class=\"hA\" name=\"SMZXMODE.HLP__095\"> </a>\n<p class=\"hC\"><span class=\"f9\">Super MegaZeux Modes</span></p>\nSuper MZX modes (a.k.a. SMZX) allow richer color options at the\nexpense of halved horizontal resolution per character. The\nnumber of colors per character doubles to four, while the number\nof total max colors on-screen at one time squares to 256.\nHowever, each mode works differently and has different\nlimitations and uses.\n\nIn the editor, F11 switches display modes. To set Super MZX\nmodes in a game, use the <span class=\"fA\">set \"smzx_mode\" # </span><span class=\"fF\">Robotic command,</span>\nwhere # is the SMZX mode (0 to revert to normal mode).\n\nSuper MZX mode 1 is the simplest mode and the easiest mode to\nuse. SMZX mode 1 simply blends the foreground and background\ncolors to get the third and fourth character colors. It is\nobviously limited, as this mode only allows editing of the 16\nbase colors, but is useful for anti-aliasing and shading\neffects.\n\nSuper MZX mode 2 is the most difficult mode to use, although it\nhas its benefits. It allows considerable control over the\npalette and allows for some overlay and sprite translucency\neffects. All 256 colors can be individually manipulated, and\neach color also represents a subpalette of four colors that is\nused by a character when an object is assigned that color.\nThe four colors (values in hex) are determined as such:\n\n-Color I is the background color number as the first and second\ndigits. (For example, if background color is 5, the hex number\nof color I is 55.)\n-Color II is the foreground color number as the first digit\nand the background color number as the second. (For example, if\nforeground color is 10 and background color is 5, the hex\nnumber of color II is A5.)\n-Color III is the background color number as the first digit and\nthe foreground color number as the second. (For example, if\nbackground color is 5 and foreground color is 10, the hex\nnumber of color III is 5A.)\n-Color IV is the foreground color number as the first and\nsecond digit. (For example, if foreground color is 10, the hex\nnumber of color IV is AA.)\n\nSuper MZX mode 3 is the best for dynamic usage of the palette,\nand the easiest to edit in general. By default, its four colors\nare determined around a base value in hex (background color\nnumber as first digit, foreground color number as second). Color\nI's number is the base, II's is base+1, III's is base+2, and\nIV's is base+3. The numbers wrap around (so if your base is, for\nexample, FF, color II would be 00).\n\nHowever, one can also directly change each value in this mode,\ndisregarding these limitations. This essentially allows 256\nuser-defined subpalettes of four colors each. These overrides,\ncalled SMZX indices, are loaded and saved out separately from\nthe normal palette. These changes can only apply for renderers\nthat can show unbound sprites (which is the vast majority of\nrenderers).\n\nCharacter editing in Super MZX modes is noticeably different.\nWhile the character set can be edited in MegaZeux's internal\ncharacter editor, certain changes occur. 1-4 selects the\ndesired color; space no longer toggles between colors (it\nalways sets here); right click no longer clears; clear mode is\ngone.\n\nPalette editing for SMZX modes is also accommodated by MZX's\npalette editor. However, the user needs to be in the relevant\nSMZX mode before loading the palette editor.\n\nOn top of direct editing through the editor, SMZX palettes can\nbe changed on the fly with Robotic. The counters for SMZX\npalettes are:\n\n<span class=\"fB\">SMZX_Rn</span>\n<span class=\"fB\">SMZX_Bn</span>\n<span class=\"fB\">SMZX_Gn</span>\n\nManipulation of these counters can change or read the value of\ncolor n's red/blue/green value, respectively. n ranges from 0\nto 255, and counter values range from 0 to 63.\n\nThe SMZX indices can also be directly read or changed with the\n<span class=\"fB\">SMZX_IDXx,y</span><span class=\"fF\"> counter. x is the palette color ranging from 0</span>\nto 255; y is one of the given color's indices ranging from 0 to\n3.\n\nLastly, SMZX palettes and character sets can be loaded and\nsaved like any other palette or character set. SMZX indices must\nbe loaded in a special manner in Robotic:\n<span class=\"fE\">SET \"filename\" \"SMZX_INDICES\"</span><span class=\"fF\">.</span>\nThere is a deprecated way to load an SMZX palette in Robotic\n(SET \"filename\" \"smzx_palette\") due to constraints DOS imposed,\nbut it is no longer necessary.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__szc\">Super MZX Counters</a>\n<a class=\"hL\" href=\"#PALEEDIT.HLP__093\">The Palette Editor</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#CHAREDIT.HLP__079\">The Character Editor</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"VLAYER.HLP\">\n<a class=\"hA\" name=\"VLAYER.HLP__vla\"> </a>\n<p class=\"hC\"><span class=\"f9\">The Vlayer and Its Uses</span></p>\nThe vlayer is an extra global graphical layer, but it is never\ndirectly seen in a game. It can only hold character and color\ninfo. The vlayer acts like a workspace allowing copying from/to\noverlay, copying from/to MZMs, copying between places in the\nvlayer, reading/writing individual graphical data to/from the\nvlayer, and reading strings from the vlayer.\n\nAnother important aspect of the vlayer is the ability to\nreference a sprite to the vlayer. This allows indirect display\nof the vlayer through display of sprites. This can be\nexploited to create the semblance of extra overlay layers.\n\nThe vlayer defaults to 32768 characters large (256x128); its\nsize can be as large as roughly 16.7 million characters (2^24).\nIt cannot be removed altogether.\n\nWhile in the editor, press Alt+V to enter the Vlayer Editor. The\nvlayer editor accepts the following commands:\n\n<a class=\"hA\" name=\"VLAYER.HLP__074\"><span class=\"fE\">Alt+B - Block</span></a>\n\nPress Alt+B to start block mode, then move the cursor to the\nopposite corner of a rectangular block and press Alt+B again.\nYou can then select an action to perform upon the block. In all\ncopy/move operations, the cursor marks the new UPPER LEFT\ncorner.\n\nCopy block will allow you to duplicate the block by moving the\ncursor to a destination and pressing Enter.\nCopy block (repeated) is the same as Copy block but can allow\ncopying of the same block to multiple places.\nMove block will allow you to move the block to a new location.\nClear block will erase the contents of the block.\nFlip block will flip the block upside down.\nMirror block will flip the block left to right.\nPaint block will change the entire block to the current color.\nCopy to board will copy the block to the given spot of the\nboard. You can choose to place it as either Custom Block, Custom\nFloor, or Text.\nCopy to overlay will copy the block to the given spot of the\noverlay.\nSave as ANSi/TXT will save the block as an ANSi or TXT file.\nSave as MZM will save the block as a layer-type MZM file.\n\nDestinations for Move and Copy can overlap the original block\nsafely. Ctrl+Dir is especially helpful when doing a repeated\ncopy block; it moves the cursor the width or height of the\nblock, ensuring no overlap when pasting.\n\nLike normal Block functions, one can copy between boards by\nselecting the board when the editor prompts the user for the\nblock's destination. Use the B key to select the destination\nboard.\n\n<a class=\"hA\" name=\"VLAYER.HLP___C\"><span class=\"fE\">C - Color</span></a>\n\nPress C to select a new current color from a menu. The thing\nunder the cursor is not affected. One can jump to a color by\ntyping its hex code in the color menu; for example, typing \"0D\"\nwould jump to color 013 (background color 0, foreground color\nD).\n\n<a class=\"hA\" name=\"VLAYER.HLP___F\"><span class=\"fE\">F - Fill</span></a>\n\nPress F to fill in an enclosed area with the current character\nand color. The area must be completely surrounded by characters\nother than the character beneath the cursor. For example, you\ncan fill over a solid square of As with something else. The\ncurrent fill command may not work correctly for very large and\ncomplex areas - in this case, you must move to the unfilled\nareas and press F to continue filling. However, this happens\nvery rarely.\n\n<a class=\"hA\" name=\"VLAYER.HLP__AltV\"><span class=\"fE\">Alt+V - Exit Vlayer Editor</span></a>\n\nAlt+V will exit vlayer mode and return to editing the main\nboard.\n\n<a class=\"hA\" name=\"VLAYER.HLP__103\"><span class=\"fE\">Alt+P - Size</span></a>\n\nAlt+P will open the vlayer size menu. From here, one can change\nthe dimensions of the vlayer; if the vlayer is shrank in one or\nboth dimensions, the user will be asked for confirmation. Any\ncharacters removed by shrinking vlayer dimensions are lost, and\nany dimension settings that would cause the vlayer to go over\nits maximum size will be limited to the largest size possible by\nreducing the smallest given dimension until the vlayer is within\nsize limits (if both given dimensions are the same size, width\nis reduced). Changing the size of the vlayer will clear the undo\nhistory of the vlayer.\n\n<a class=\"hA\" name=\"VLAYER.HLP__F1\"><span class=\"fE\">F1 - Help</span></a>\n\nF1 will bring up context-relevant help. You can press F1 at\nalmost any time.\n\n<a class=\"hA\" name=\"VLAYER.HLP__F2\"><span class=\"fE\">F2 - Text</span></a>\n\nF2 will toggle text mode on and off. When text mode is on, Enter\nwill go to the next line, and Backspace will delete going\nbackwards. All printable characters will type in as text.\n\n<a class=\"hA\" name=\"VLAYER.HLP__Ar\"><span class=\"fE\">Arrow - Move</span></a>\n\nThe arrow keys will move the cursor. The edit window will scroll\nwhen necessary.\n\n<a class=\"hA\" name=\"VLAYER.HLP__AltAr\"><span class=\"fE\">Alt+Arrow - Move 10</span></a>\n\nAlt with the arrow keys will move the cursor ten spaces at a\ntime (or to a vlayer edge if it is under 10 spaces away in that\ndirection).\n\n<a class=\"hA\" name=\"VLAYER.HLP__BkSp\"><span class=\"fE\">Backspace - Delete</span></a>\n<a class=\"hA\" name=\"VLAYER.HLP__Del\"><span class=\"fE\">Del - Delete</span></a>\n\nThese two keys will delete everything under the cursor. The\ncurrent character is not affected.\n\n<a class=\"hA\" name=\"VLAYER.HLP__End\"><span class=\"fE\">End - L/R Corner</span></a>\n\nEnd will jump the cursor to the lower-right corner of the entire\nvlayer.\n\n<a class=\"hA\" name=\"VLAYER.HLP__Enter2\"><span class=\"fE\">Enter - Character</span></a>\n\nEnter will alter the current character on the vlayer. Select it\nfrom a menu and then press Enter to confirm your choice.\n\n<a class=\"hA\" name=\"VLAYER.HLP__ESC\"><span class=\"fE\">ESC - Exit/Cancel Mode</span></a>\n\nESC will exit vlayer mode. If you are in block, text, or draw\nmode, ESC will instead cancel the current mode and return to\nnormal vlayer editing.\n\n<a class=\"hA\" name=\"VLAYER.HLP__Home\"><span class=\"fE\">Home - U/L Corner</span></a>\n\nHome will jump the cursor to the upper-left corner of the\nentire vlayer.\n\n<a class=\"hA\" name=\"VLAYER.HLP__Ins\"><span class=\"fE\">Ins - Grab</span></a>\n\nIns will select the character and color under the cursor\nas the current. The actual character is not affected.\n\n<a class=\"hA\" name=\"VLAYER.HLP__Sp\"><span class=\"fE\">Spacebar - Place</span></a>\n\nSpacebar will copy the current character and color to the\nlocation under the cursor. Other characters will be deleted if\nthey are under the cursor.\n\n<a class=\"hA\" name=\"VLAYER.HLP__Tab\"><span class=\"fE\">Tab - Draw</span></a>\n\nTab will toggle the current draw mode. When drawing is on, you\nwill place a copy of the current character every time you move\nthe cursor.\n\n<a class=\"hA\" name=\"VLAYER.HLP__CtrG\"><span class=\"fE\">Ctrl+G - Goto Position</span></a>\n\nCtrl+G will pop up a window, displaying target x,y coordinates.\nSet the coordinates by either typing in or selecting the\ndesired X and Y values, and select OK to go to those coordinates\non the vlayer. Choosing Cancel or pressing Escape cancels.\n\n<span class=\"fE\">P - Change Buffer Character</span>\n\nP will act much like Enter, but only change the character in the\nbuffer. No character on the vlayer is changed with this action.\n\n<span class=\"fE\">Alt+Z - Clear (Vlayer)</span>\n\nAlt+Z will clear the vlayer entirely. You will be asked for\nconfirmation. Clearing the vlayer will clear the vlayer undo\nhistory as well.\n\n<a class=\"hA\" name=\"VLAYER.HLP__CtrZ\"><span class=\"fE\">Ctrl+Z - Undo</span></a>\n<a class=\"hA\" name=\"VLAYER.HLP__CtrY\"><span class=\"fE\">Ctrl+Y - Redo</span></a>\n\nCtrl+Z will undo an action; Ctrl+Y will redo any previously\nundone action. Action depth is determined by config file\nsettings, with 100 as default.\n\n<span class=\"fE\">Ctrl+Number - Save Editor Position</span>\n<span class=\"fE\">Alt+Number  - Load Editor Position</span>\n\nThese save and load up to 10 cursor positions in the vlayer (0\nthrough 9). These positions save the current coordinates in\nthe vlayer. These positions are saved for that world in its\n.editor.cnf file.\n\n---\n\nOn top of being modifiable in the editor, the vlayer can be\nchanged in a running MZX world with Robotic. To manipulate the\nvlayer in Robotic, \"#n\" is used instead of pure numbers in copy\ncommands. For example, if copying a 2x2 block from (6,6) of the\nboard to (0,0) of the vlayer is wanted, the command would be:\n\n<span class=\"fE\">COPY BLOCK 6 6 2 2 \"#0\" \"#0\"</span>\n\nHere are the vlayer copy functions:\n\nBoard to vlayer: <span class=\"fA\">COPY BLOCK x1 y1 w h \"#x2\" \"#y2\"</span>\nOverlay to vlayer: <span class=\"fA\">COPY OVERLAY BLOCK x1 y1 w h \"#x2\" \"#y2\"</span>\nVlayer to board: <span class=\"fA\">COPY BLOCK \"#x1\" \"#y1\" w h x2 y2</span>\nVlayer to overlay: <span class=\"fA\">COPY OVERLAY BLOCK \"#x1\" \"#y1\" w h x2 y2</span>\n\nValid Robotic copies from vlayer to board are always\nCustomBlocks.\n\nIn the commands below, <span class=\"fe\">COPY BLOCK</span><span class=\"ff\"> and </span><span class=\"fe\">COPY OVERLAY BLOCK</span>\nare interchangeable.\n\nVlayer to MZM:\n<span class=\"fA\">COPY (OVERLAY) BLOCK \"#x1\" \"#y1\" w h \"@name.mzm\" m</span>\nVlayer to string:\n<span class=\"fA\">COPY (OVERLAY) BLOCK \"#x1\" \"#y1\" w h \"$stringN\" c</span>\nVlayer to itself:\n<span class=\"fA\">COPY (OVERLAY) BLOCK \"#x1\" \"#y1\" w h \"#x2\" \"#y2\"</span>\n\n\"m\" and \"c\" can be any value but must exist.\n\nThere are several ways to give the vlayer working coordinates:\n\nPure numbers - <span class=\"fE\">\"#xxx\"</span>\nCounter value - <span class=\"fE\">\"#&amp;counter&amp;\"</span>\nExpressions - <span class=\"fE\">\"#('counter' + x)\"</span>\n\nMZMs can be placed on the vlayer by using p02. Example:\n\n<span class=\"fE\">PUT \"@visuals.mzm\" image_file p02 x y</span>\n\nReading and writing single characters or colors from the vlayer\nrequires the vch and vco counters.\n\nFor reading and writing characters use <span class=\"fE\">\"vchX,Y\"</span><span class=\"fF\">.</span>\nFor reading and writing colors use <span class=\"fE\">\"vcoX,Y\"</span><span class=\"fF\">.</span>\n\nFor example:\n\n<span class=\"fE\">SET \"vch10,10\" '+'</span><span class=\"ff\"> sets the vlayer char at (10,10) to a plus</span>\nsign.\n<span class=\"fE\">SET \"vco10,10\" 15</span><span class=\"ff\"> sets the vlayer color at (10,10) to white on</span>\nblack.\n<span class=\"fE\">SET \"temp\" \"vch10,10\"</span><span class=\"ff\"> sets temp to whatever the character at</span>\n(10,10) of the vlayer is.\n\nVlayer size can be changed by setting the counter \"vlayer_size\"\nto the desired length of the vlayer. Doing this only sets the\nmaximum size; the actual vlayer will not be this large unless\nthe vlayer's dimensions are changed. All attempts to set the\nvlayer's size above the maximum will be ignored.\n\nTo change the vlayer's dimensions in Robotic, use the counters\n\"vlayer_width\" and \"vlayer_height\". When one dimension is\ngiven, the other becomes as large as possible to fill the given\nvlayer. For example, if one has a vlayer 50000 characters large\nand sets vlayer_height to 499, the vlayer's width will become\n100 characters wide (for a total vlayer size of 49900\ncharacters).\n\nAll bad values of vlayer_size, vlayer_height and vlayer_width\n(0 and below) will be changed to 1.\n\nRemember that the vlayer is global. If many vlayers are wanted,\nliberal usage of MZM saving and loading can help get around this\nproblem.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__vlc\">Vlayer Counters</a>\n<a class=\"hL\" href=\"#STRINGS.HLP__1st\">Strings, Special Formatting, and Their Place in Robotic</a>\n<a class=\"hL\" href=\"#MZM.HLP__mzm\">Using MZMs</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"MZM.HLP\">\n<a class=\"hA\" name=\"MZM.HLP__mzm\"> </a>\n<p class=\"hC\"><span class=\"f9\">Using MZMs</span></p>\nMZM files are rough equivalents of the ANSI files used in older\nMZX versions. MZMs, however, are much more powerful and can\nstore much more information. MZMs can be up to 65535 x 65535\ntiles in size. MZMs are useful for easy access of reused\ngraphics and reused, multi-Robot entities.\n\nMZMs come in two modes: \"board\" and \"layer\".\n  * Board mode saves color, param, ID, and under information.\n    However, it cannot save the player, scrolls or sensors.\n    Board mode can save most built-ins. As for Robots, MZMs\n    saved in the editor will save fresh Robots, while MZMs saved\n    in-game keep the Robots' current states.\n  * Layer mode saves only color and char information.\n\nAny information not saveable will be replaced by a customblock\nfacsimile.\n\nWhile in the editor, MZMs can be placed by using the import\nfunction (Alt+I) and choosing MZM. Layer MZMs can be placed on\nthe board layer as CustomBlock, CustomFloor, or Text.\n\nWhen loading a board MZM file to the overlay or vlayer, the\ncharacter used is the parameter stored. Outside of customblocks\nand related, different characters could appear due to different\nparam settings (especially built-ins and Robots).\n\nOne can save MZMs in the editor; use the block action function\n(Alt+B). MZMs saved in the editor from the board are saved as\nboard; from the overlay and vlayer, MZMs are always saved as\nlayer.\n\nRobotic methods of saving MZMs are:\n\n<span class=\"fA\">COPY BLOCK x y w h \"@filename\" p</span>\n<span class=\"fA\">COPY OVERLAY BLOCK x y w h \"@filename\" p</span>\n<span class=\"fA\">COPY (OVERLAY) BLOCK \"#x\" \"#y\" w h \"@filename\" m</span>\n\nX/Y are the coordinates of the block's upper-left corner; w/h\nare the width and height of the block; filename is the name of\nthe file (the @ is required); finally, p determines the MZM\ntype. 1 saves as layer; 0 saves as board. 0 is integral for\ncopying non-graphical data (especially Robots); 1 is proper for\ncopying graphical data, as the block's appearance will stay as\nexactly as it was when saved. The last case is for saving from\nthe vlayer to MZM. The x,y coordinates MUST be in \"#x\" \"#y\"\nformat, and the m can be any value but MUST exist. Any valid MZX\nstring can take the place of a filename, but the @ prefix is\nstill required.\n\nUnlike with board MZMs containing Robots saved in the editor,\nloading board MZMs containing runtime Robots in the editor is\nunsupported. Any such Robot will be replaced with a customblock\nfascimilie. If you want any such MZMs to be fully loaded, they\nmust also be loaded at runtime.\n\nThe Robotic method of loading MZMs is:\n\n<span class=\"fA\">PUT \"@filename\" image_file pNN x y</span>\n\nfilename is the name of the MZM file (after the required @),\nx and y are the upper-left corner's coordinates, and NN is 00\nfor board, 01 for overlay, 02 for vlayer. Again, any valid\nstring can take the place of a filename if preceded by the\nrequired @ prefix.\n\nFinally, MZMs will neither be saved nor loaded if at the edge\nof a board.\n\n<a class=\"hL\" href=\"#USINGTHE.HLP___g\">Alt+B OR Alt+Enter - Block action</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"TRIG.HLP\">\n<a class=\"hA\" name=\"TRIG.HLP__tri\"> </a>\n<p class=\"hC\"><span class=\"f9\">Trigonometric Functions</span></p>\nTrigonometry can simplify some complex engines, and MegaZeux\ncan access several trigonometric functions with fair precision.\nHowever, accessing trigonometric functions in MegaZeux requires\nsome setup.\n\nBecause MegaZeux works solely with integers, you need to set\nthe counter \"multiplier\" to determine how the non-integer\nworking value is converted. For example, if your function\nhas a value of .86602 and \"multiplier\" is set to 10000, the\nresulting value would be 8660. Results are always truncated\n(that is, have everything to the right of the decimal removed).\nThe default value for the \"multiplier\" counter is 10000.\n\nThe counter \"c_divisions\" determines the precision of the trig\nfunctions by determining how many times the circle is divided.\nSensible values for this include 360 (degree precision), 21600\n(arc-minute precision) and 1296000 (arc-second precision). The\ndefault value for the \"c_divisions\" counter is 360.\n\nThe \"divider\" counter works solely on inverse trigonometric\nfunctions. Its effect is to divide the number passed to an\ninverse function before it is calculated. For example, if you\nhave \"divider\" set to 100 and try to calculate \"acos50\", MZX\nwill calculate the arccosine of .50 (50 divided by 100),\noutputting 60 degrees. The default value for the \"divider\"\ncounter is 10000.\n\nThese are the trig functions MegaZeux can directly utilize.\n\n<span class=\"fA\">SIN - sine</span>\n<span class=\"fA\">COS - cosine</span>\n<span class=\"fA\">TAN - tangent</span>\n<span class=\"fA\">ASIN - arcsine (inverse sine)</span>\n<span class=\"fA\">ACOS - arccosine (inverse cosine)</span>\n<span class=\"fA\">ATAN - arctangent (inverse tangent, takes signs into account)</span>\n<span class=\"fA\">ARCTAN - atan2 (special form of arctangent, takes two values</span>\n<span class=\"fA\">in the form of ARCTANdy,dx (\"dy\" is the first input, \"dx\" the</span>\n<span class=\"fA\">second))</span>\n\nNOTE: The inverse functions output degrees, not radians.\n\nTo use the counters you put the unit number after the counter\nname. Here's an example. To display the cosine of 30 degrees\nwith degree-based precision, the needed line would be:\n\n<span class=\"fE\">* \"&amp;cos30&amp;\"</span>\n\nTrig functions can also take expressions and counters. However,\ncounters can only be inserted through expressions; a construct\nlike \"&amp;sinlocal&amp;\" will not work, but \"&amp;sin('local')&amp;\" will.\n\n<a class=\"hL\" href=\"#COUNTERS.HLP__mth\">Mathematical Counters</a>\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"PROCESS.HLP\">\n<a class=\"hA\" name=\"PROCESS.HLP__prc\"> </a>\n<p class=\"hC\"><span class=\"f9\">Cycles and Board Scans - How MZX Processes Robots</span></p>\nKnowing MZX's commands is important for coding, obviously, but\nknowing how and when they get executed can be just as\nparamount.\n\nAfter executing code in the global Robot, MegaZeux executes\nRobotic code by scanning the board from the top-leftmost Robot\nfirst, then proceeding from left-to-right, jumping down to the\nbeginning of the next line when at the end of its current line,\nand ending at the Robot in the bottom-right corner. This action\nis called a BOARD SCAN. MegaZeux then does a clean-up scan in\nreverse to catch certain actions. This complete act of scanning\na board and executing any Robotic commands is called a CYCLE,\nand when a Robot completes all of its commands for that cycle,\nit's called ENDING A CYCLE for that Robot. After a cycle is\nexecuted, the display is updated, so MegaZeux's internal frame\nrate is essentially the same rate as the number of cycles\nexecuted per second.\n\nBy default, MegaZeux will run up to 40 commands per Robot in a\ncycle. Setting the COMMANDS counter can set the number of\ncommands processed per Robot per cycle to be lower or\n(significantly) higher. Some commands, however, have the ability\nto end processing prematurely; these are called cycle-ending\ncommands. When the COMMANDS value is very high, MegaZeux can end\nup consuming large amounts of processing power in a single\ncycle. Cycle-ending commands can limit the number of commands\nprocessed in a cycle to only what is necessary. Otherwise, very\nhigh COMMANDS values can potentially slow down MegaZeux or even\ncause MegaZeux to trigger safeguards that prevent it from\nfreezing.\n\nThe two commands that are best suited for ending the cycle are\nWAIT 1 and CYCLE 1, because of their very low amounts of side\neffects. While WAIT 1 is preferred in most cases, mostly due to\nbeing far more intuitive, there is a subtle difference between\nprocessing the two due to its quirk with reverse scan detailed\nlater in this section.\n\nAs a simple rule of thumb, commands that move anything on the\nboard generally end a cycle, especially moving the player,\nthough this is not always the case.\n\nThe amount of cycles MegaZeux processes per second is set by the\nMZX_SPEED value. Speed 1 is completely processor-bound and will\nexecute as many cycles per second as possible. Other speeds\nprocess cycles according to this formula:\n\n<span class=\"fE\">62.5 / (MZX_SPEED - 1) = cycles per second</span>\n\nSimplified, this means that MegaZeux can be set to consistent\nrates ranging from 62 &amp; 1/2 cycles per second (speed 2) all the\nway down to 4 &amp; 1/6 cycles per second (speed 16).\n\nThe CYCLE command dictates how often a Robot is active. By\ndefault, Robots will execute commands on every cycle, but the\nCYCLE command can cause a Robot to spend a certain number of\ncycles idling before executing commands. Robots can go from\nexecuting 1 out of every 1 cycles (i.e. every cycle) to 1 out\nof every 255.\n\nSpecial care should be given to the scan that happens in reverse\nto clean up certain actions. This scan ensures that if a Robot\nsends a Robot that has already executed its commands to a label,\nthen the target Robot can actually be sent to the proper label.\nCertain commands in the target Robot - specifically, the\ncycle-ending commands that can act across multiple cycles - can\ncause that Robot to execute the new label commands in the very\nsame cycle. The global Robot is exempt from this process.\n\nCommands that can prompt this behavior are:\n\n<a class=\"hL\" href=\"#COMMANDR.HLP____5\">/ \"string\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___e1\">END</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___g4\">GO [dir] #</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___t7\">TRY [dir] \"label\"</a>\n<a class=\"hL\" href=\"#COMMANDR.HLP___w1\">WAIT #</a>\n\nAnything besides solely one of these commands ran in the target\nRobot's cycle (even blank lines!) will prevent the target Robot\nfrom executing in the same cycle.\nThere is an exception for Robots using CYCLE # values over 1:\nbeing in such a Robot's idle cycles will trigger a same-cycle\nexecution.\n\nFor more detailed information about what commands end cycles and\nwhen, read cycles_and_commands.txt in the additional\ndocumentation.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"PARTIAL.HLP\">\n<a class=\"hA\" name=\"PARTIAL.HLP__par\"> </a>\n<p class=\"hC\"><span class=\"f9\">Partial Character Sets</span></p>\nPartial character sets are smaller than full sets, as the name\ndefinitely implies, but they only overwrite the characters they\nare told to when loaded. This leaves the rest of the characters\nintact. Heavy animations and changes that keep part of the\ncharacter set intact (such as the alphanumeric characters) are\ntwo issues best tackled with partial character sets.\n\nOne can save a partial character set in the editor by selecting\na starting character value (aka \"offset\") and a length value.\nThese fields show up when you export a character set, under the\nfilename input.\n\nTo load partial character sets into your current set, you have\ntwo options. You can firstly load it as you would a normal\ncharacter set; this replaces all characters from the start of\nthe target character set until the end of the partial set.\nSecondly, you can load the partial set into a non-0 position of\nthe target character set. In the editor, this can be done by\nsetting the offset field in the import character dialog, under\nthe filename input. The @xxxx prefix allows this in Robotic;\nxxxx is the number (in decimal) of the first character to\nreplace in the target character set.\n\n<a class=\"hL\" href=\"#ROBOTICR.HLP__087\">Robotic Reference Manual</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"UPDATER.HLP\">\n<a class=\"hA\" name=\"UPDATER.HLP__099\"> </a>\n<p class=\"hC\"><span class=\"f9\">The MegaZeux Updater</span></p>\nModern MegaZeux versions on supported platforms, such as\nWindows, have the ability to update MegaZeux from MegaZeux\nitself. The updater can be set to run by pressing either F7 or U\non any title screen. It can also be configured to check for\nupdates every launch in config.txt settings, or disabled\naltogether.\n\nMegaZeux checks the sites listed under the config.txt settings,\nin order. By default, the updater checks the Stable branch of\nMegaZeux, but can be set in config.txt to other branches (such\nas Unstable or Debytecode).\n\nIf a host reports back that your version of MegaZeux is current,\nthe next host can be checked to see if they differ.\n\nIf a new version of MegaZeux is detected, MegaZeux offers the\nuser these choices:\n-Upgrading to the new version using the \"Upgrade\" button.\n-Updating the current version to whatever minor \"refreshed\"\n version may exist (where the version number remains the same,\n but a small issue or two not caught before release is fixed)\n using the \"Update Old\" button.\n-Cancelling out of the update process with the \"Cancel\" button.\n No changes are made.\n\nOnce the option is chosen, MegaZeux lists what files will be\nadded, changed, or deleted by the pending update. At this point,\nthe user can still cancel out of updating with the Escape key.\nPressing Enter at this point begins the updating process.\n\nThe updater will then attempt to download the necessary files\nfor the update. The download indicator lists the current file\nbeing downloaded, its size in bytes, and current file number\nout of total files to be downloaded. If all files are properly\ndownloaded, MegaZeux will apply the update and notify the user\nof a restart. Select \"OK\" and MegaZeux will restart, fully\nupdated.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"NEWINVER.HLP\">\n<a class=\"hA\" name=\"NEWINVER.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">NEW in MegaZeux!</span></p>\nJune 9th, 2025 - MZX 2.93d\n\nThis release includes several features and bugfixes to help with\nportability. Screen keyboard support from the NDS/3DS ports has\nbeen generalized and enabled for SDL ports; Android and Vita can\nnow open their respective screen keyboard interfaces by pressing\nright shoulder on the gamepad (configurable). The Android port\nno longer overwrites config.txt if it has changed. Other notes:\nbetter AltGr tolerance, faster software renderer performance,\nrendering fixes for softscale, glsl/glslscale, and opengl1/2.\n\nCompatibility changes include Spitting Tiger logic bugfixes and\ncorrected version locking for the KEY? and KEYENTER labels.\nSeveral out-of-bounds accesses in ID char resolution and in the\nMissile logic have been fixed, which may introduce (unfixable)\ndifferences in behavior for out-of-range parameters. There are\nalso various module playback improvements (libxmp 4.6.3).\n\nSDL3 is now selected by default for Windows, Emscripten,\nand Vita (thanks to Aeon17 for Vita testing). SDL3 adoption for\nmacOS has been delayed to 2.94 (requires increasing the minimum\nOS version to 10.14).\n\nUSERS\n\n+ MegaZeux on Android no longer overwrites config.txt if it has\n  more recent changes than the copy in assets.zip.\n+ Added support for system screen keyboards for SDL. This allows\n  opening the system screen keyboards with the Android and Vita\n  ports, as well as some Linux configurations e.g. Steam Deck.\n  The screen keyboard can be toggled with rshoulder (see below)\n  or, in Android, by performing a 3-point touch on the screen.\n+ Added config option \"joy#.show_screen_keyboard\" to configure\n  which button (if any) is used to open the screen keyboard for\n  platforms that support it (set to act_rshoulder by default,\n  except for Linux, where it's disabled by default).\n+ Added config options \"key_left_alt_is_altgr\" and\n  \"key_right_alt_is_altgr\". When set to 1, these prevent their\n  respective Alt keys from activating built-in UI and editor\n  keyboard shortcuts e.g. Ctrl+Alt+Enter. They do not alter the\n  keycodes emitted by these keys. This may be useful for Windows\n  international keyboard layouts and for macOS. These options\n  can be disregarded for Wayland/X11 (AltGr has its own keysym)\n  or other ports that don't have AltGr.\n+ More strict modifier checking for keyboard shortcuts that use\n  alphanumeric keys in places with text entry: dialog boxes,\n  editor, scroll editor, Robot editor, Robot debugger config,\n  and variable debugger. This should make it easier to use\n  Windows Ctrl+Alt style AltGr in these interfaces.\n+ Wayland/X11 AltGr now correctly emits internal keycode 313 and\n  PC XT keycode 56.\n+ The KEY? label is no longer sent for pre-2.62 worlds when\n  the char pressed is outside of the A-Z 1-9 range, and is no\n  longer sent for 1.x worlds when the char is outside of the A-Z\n  range. (Previously checked version for the KEY counter only.)\n+ The KEYENTER label is no longer sent for pre-2.62 worlds.\n+ Fixed bug where, after Alt+R or failure to reload __TEST.MZX,\n  the editor would not mark the new blank world as active. This\n  would cause the screen to not be drawn during Alt+T testing.\n+ Spitting Tigers from pre-2.80 worlds no longer move slower\n  than they're supposed to with low intelligence, and no longer\n  shoot themselves on these buggy idle cycles when \"Enemies'\n  bullets hurt other enemies\" is enabled.\n+ Spitting Tigers that shoot fire now play the fire SFX.\n+ Fixed out-of-bounds array reads when a Missile with an invalid\n  parameter attempted to turn after colliding with itself (no\n  compatibility checks for old behavior).\n+ Fixed out-of-bounds array reads when reading the char of or\n  displaying (no compat checks): Ice, Lava, CWRotate, CCWRotate,\n  Pusher, Missile, Spike, Bullet, Fire, Life, Ricochet Panel.\n+ Fixed out-of-bounds array reads when reading the color of or\n  displaying (no compat checks): Energizer, Fire, Life.\n+ ANSi export now replaces char 26 with a dash to avoid import\n  bugs and conflicts with other software.\n+ Added ANSi export support for Doorway mode, a non-standard\n  ANSI extension that allows displaying control characters that\n  would otherwise get stripped out. MegaZeux now supports\n  loading ANSi files that use Doorway mode, as well.\n+ Software layer renderer performance enhancements for repeat\n  colors, clipping, and unaligned renderering.\n+ Unaligned software layer rendering using 64-bit writes is\n  now enabled for x86-64, POWER8, Emscripten, and probably most\n  AArch64 builds. This optimizes out up to 48 renderers for\n  these architectures, and improves 8-bit, 16-bit, and 32-bit\n  rendering performance.\n+ Unaligned software layer rendering using 32-bit writes is\n  now enabled for x86, PowerPC, and M68k. This optimizes out up\n  to 24 renderers for these architectures, and improves 8-bit\n  and 16-bit rendering performance.\n+ Fixed slow softscale rendering for some drivers. Texture\n  streaming is now used for direct3d/consoles/software only.\n+ Fixed OpenGL rendering issues in Wayland caused by OpenGL's\n  transparent default clear color.\n+ Fixed opengl1 renderer colors missing alpha component.\n+ Fixed glsl, glslscale, opengl2 renderers sometimes showing a\n  thin white line on the edges of the screen.\n+ Fixed date and time counters on NDS (missing IRQ_NETWORK).\n+ Fixed VFS cache failing to init caused by getcwd returning\n  a root with no trailing slash.\n+ Unsupported mouse button presses are now ignored.\n+ Fixed the window icon for Flatpak builds.\n+ Fixed the directory mentioned in the PS Vita README.md\n  (claimed \"ux0:/data/MegaZeux\", actually \"ux0:/data/megazeux\").\n\nDEVELOPERS\n\n+ Added renderer set_window_caption, set_window_icon functions;\n  moved SDL-specific icon functionality out of graphics.c.\n+ Failure to query X11 no longer disables the icon in Linux/BSD.\n+ manifest.sh no longer requires bash. All scripts and Makefiles\n  should now be fully compatible with POSIX sh (with only a few\n  GNU extended options remaining where required).\n+ Fix compilation with glibc versions prior to 2.17 caused by\n  clock_gettime (disabling for now until -lrt can be tested).\n+ Fix compilation with libpng versions missing png_const_bytep\n  and png_set_add_alpha.\n+ Remove -Wno-missing-field-initializers to fix GCC 3.4 support.\n+ Remove redundant order-only prerequisites in src/Makefile.in\n  to fix building with older versions of GNU Make.\n+ Define _LARGEFILE_SOURCE in addition to _FILE_OFFSET_BITS=64\n  in the unix Makefile to fix builds with old glibcs that\n  predate _FILE_OFFSET_BITS.\n+ Numerous warnings fixes/workarounds affecting macOS, NetBSD,\n  OpenBSD, 3DS, GCC 4.x, SDL 1.2, and old versions of libpng.\n+ Fixed OS detection, unit tests, and testworlds for Haiku.\n+ Replaced return values of all VFS functions to fix issues with\n  using errno values here on Haiku.\n+ Add Emscripten Makefile support for SDL3.\n+ Add PS Vita Makefile support for SDL3 and SDL 1.2.\n+ make install now installs an AppStream .metainfo.xml file.\n+ Added --metainfodir [dir] option to config.sh. This setting\n  defaults to [sharedir]/metainfo for relevant platforms.\n+ --licensedir now defaults to [sharedir]/licenses instead of\n  [prefix]/share/doc.\n+ Fixed ansi.c compilation failure with --disable-datestamp.\n+ More accurate FPS counter for --enable-fps.\n+ Updated libxmp to 4.6.3+sample rate patch.\n+ Updated dependency builder scripts: SDL3 3.2.16, SDL2 2.32.8,\n  libpng 1.6.48.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nFebruary 28th, 2025 - MZX 2.93c\n\nYes, the text input bug and show thing keys are fixed now. :)\n\nInitial support for SDL3 has been added, and will be enabled for\nthe Fedora and AUR builds for this release. Other platforms will\ngradually transition to SDL3 over the next few releases.\n\nAs part of the preparation for SDL3, this release includes many\nlong-belated changes to the way MegaZeux resizes windows and\ncalculates the scaled display area within the window (including\nmouse coordinate conversion). This overhaul has fixed several\nbugs, but please be on the lookout for regressions.\n\nThe Xcode port has been updated to build with the latest version\nof SDL and other dependencies, and now requires OS X El Capitan\n(x86_64) or macOS Big Sur (arm64) to run. This port no longer\nsupports x86 (32-bit). The Darwin multiarchitecture port has\nalso been updated to fix arm64 and arm64e support (only arm64 is\nincluded in builds).\n\nAlso included are Android fixes for Android 11+ (thanks asie)\nand also a fix for a bug preventing MZX from starting on Jelly\nBean and KitKat.\n\nUSERS\n\n+ The DOS Sound Blaster driver now supports Sound Blaster with\n  DSP 2.0, Sound Blaster 2, and Sound Blaster Pro/16 mono mode.\n  Original Sound Blaster with the old DSP is still unsupported.\n+ The DOS port and SDL ports now support mono audio. Mono can be\n  selected with the new config option \"audio_output_channels\".\n+ Setting the config option \"system_mouse\" to \"1\" or \"on\" will\n  no longer disable the software cursor. The old behavior can be\n  restored by setting it to \"only\". The new behavior of \"1\" is\n  more useful, especially in Wayland (due to forced mouse grab\n  when the system cursor is hidden).\n+ macOS now enables fullscreen_windowed by default to work\n  around macOS bugs involving real fullscreen and maximizing.\n+ Fixed window resize bugs in Windows, macOS, Linux/BSD (both\n  X11 and Wayland), and probably other operating systems caused\n  by MZX recreating the window after every window resize event.\n+ Fixed junk display in the letterbox area after resizing using\n  the software, gp2x, or SDL 1.2 overlay renderers.\n+ The software and gp2x renderers now enable blitting when the\n  created window is smaller than expected instead of crashing.\n+ Fixed sai.frag not being installed by \"make install\" or\n  distributed with Unix-style packages for Linux/BSD/Darwin.\n+ Fixed the board editor show thing hotkeys (broken by 2.93b).\n+ Fixed nested riff behavior in the RAD replayer. The replayer\n  would fail to replace the outer riff with the inner riff in\n  some cases.\n+ Fixed a crash that would occur in builds using extra memory\n  hacks (DOS) when copying blocks between boards in the editor.\n+ Fixed text input bugs caused by a 2.93b change accidentally\n  reenabling old deadcode for exiting intake().\n+ Fixed crash when saving editor config files/\"Set as Default\"\n  after the file fails to open for write. If the \".editor.cnf\"\n  file fails to save or load, MZX will attempt to save or load a\n  \".cne\" file instead. This allows this feature to work in DOS\n  builds when there is no long filename (LFN) support.\n+ Fixed filesystem permissions on Android 11.0 and up. (asie)\n+ Fixed linkage bug preventing MegaZeux from starting in Android\n  Jelly Bean and KitKat due to a missing logging symbol.\n+ Added support for pre-2.70 \"put thing BENEATH\" being ignored.\n+ Added support for pre-2.80 \"put thing\" treating p?? as 0.\n+ Robo-P p0 now converts to either p00 or p?? when appropriate.\n+ Robo-P PUT commands now ignore the high thing bit instead of\n  combining it into the color.\n+ Fixed disassembly of out-of-bounds color and param values to\n  match their most common interpretation during gameplay.\n+ Fixed assembly of immediates in second char of CHANGE OVERLAY.\n+ Out-of-bounds char values are now disassembled to immediates\n  for CHAR EDIT, COPY CHAR, SCROLL CHAR, and FLIP CHAR. This\n  makes commands such as \"CHAR EDIT 256 [...]\" more usable since\n  they will persist in the robot editor.\n+ Temporarily revert the broken Vita SHAREDIR/startup dir split.\n  MegaZeux now expects assets to exist in ux0:/data/megazeux\n  like the README claims.\n\nDEVELOPERS\n\n+ Added initial support for SDL3. No architectures select SDL3\n  by default, but it is now possible to build and test MegaZeux\n  for platforms that already support it.\n+ Split the renderer function \"set_video_mode\" into two renderer\n  functions \"create_window\" and \"resize_window\" to more closely\n  reflect the reality of SDL2/3. The former is used during\n  renderer init and is mandatory; the latter is used to resize\n  and update other window settings like mouse grab/visibility\n  e.g. when switching to/from fullscreen, and is optional. If\n  it is not provided, switching to/from fullscreen will do\n  nothing. \"resize_window\" is also invoked in SDL 1.2 when a\n  window resize event is received. Some renderers (software,\n  gp2x) still use the same implementation for both functions.\n+ Renamed the renderer function \"resize_screen\" to\n  \"resize_callback\" to more accurately reflect its purpose. It\n  is called in response to SDL2/3 window resize events and after\n  \"resize_window\" (by the video functions that call it, see\n  above). This function is now responsible for post-resize\n  maintenance such as glViewport and selecting the filter mode,\n  and is no longer required to call update_screen.\n+ Replaced renderer functions \"get_screen_coords\" and\n  \"set_screen_coords\" with a single function \"set_viewport\" to\n  calculate the current window's display area. This area is used\n  in places where \"fix_viewport_ratio\" previously was and also\n  to convert mouse coordinates. Most renderers should use one of\n  the two prefab implementations of this function.\n+ SDL text event enablement now happens after the window is\n  created. This shouldn't really affect anything.\n+ Added detection for LoongArch64. 64-bit architectures that\n  platform_endian.h fails to identify as such should no longer\n  abort in the software layer renderer.\n+ Updated the Xcode project. Supported architectures are now\n  x86_64 and arm64. The x86_64 architecture requires OS X El\n  Capitan (minimum version supported by SDL) and arm64 requires\n  macOS Big Sur. The Xcode project format is still 8.x, but\n  Xcode 12.2 or newer is required to compile release builds.\n+ Tweaked the Darwin .app buildsystem to properly support arm64\n  and arm64e. The documentation has also been updated to note\n  linker issues in versions prior to 10.10 when combining\n  x86_64 and x86_64h into the same binary, and issues involving\n  arm64e builds being terminated by the kernel.\n+ The dependency builder scripts now support building Xcode\n  frameworks for both x86_64 and arm64. Updated SDL2 to 2.32.0,\n  libpng to 1.6.47, libogg to 1.3.5, and libvorbis to 1.3.7.\n  Added building SDL3 3.2.4 for all relevant platforms.\n+ Enabled 16k pages on Android to hopefully future-proof\n  MegaZeux against Android 15.0. (asie)\n+ Updated libxmp to 4.6.2+sample rate patch.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nSeptember 10th, 2024 - MZX 2.93b\n\nThis is mostly a big bugfix release, but there are a few new\nfeatures of note. First and probably of most interest, there is\na new feature to export a full screenshot of an entire board or\nthe entire vlayer from the editor. Renderers based on the\nsoftware layer renderer should see some performance improvement.\nThe Darwin multiarchitecture .app port has also been massively\noverhauled, and now targets up to 5 architectures (ARM still\npending). libxmp now plays stereo samples correctly.\n\nDOS port users should be happy to hear that Sound Blaster Pro\nsupport has been restored, and support for even more sound cards\nis coming. Additionally, some Ogg Vorbis-related crashes have\nbeen fixed, and the SVGA renderer should work better than prior.\nScreenshots have been enabled for this port, and the TIME/DATE\ncounters should now work properly.\n\nFinally, as usual, there are a large number of bugfixes, which\nincludes several crash fixes, scroll fixes, and FREAD fixes.\nNo compatibility checks have been added for the string splice\nFREAD/FREADn bugfixes, as the old behavior was unusably buggy.\n\nUSERS\n\n+ Added board and vlayer image export to the editor (Alt+X).\n  This feature will render the entire board (with overlay, if\n  applicable) or vlayer as it appears in the editor. This may\n  take a long time with large boards/vlayers.\n+ Builds without libpng (Android, DOS) now use a simple fallback\n  PNG exporter for screenshots instead of falling back to BMP.\n+ The DOS port now supports Sound Blaster Pro in stereo mode.\n  The driver should also support all other Sound Blasters with\n  DSP &gt;=2.00 and the mono/8-bit modes of the Sound Blaster 16,\n  but these are disabled for now (no software mixer support for\n  mono output). IRQs higher than 7 are also supported now.\n+ The DOS port now uses stb_vorbis for Ogg Vorbis playback,\n  which should fix most audio-related crash bugs. Related crash\n  bugs caused by stream management have also been fixed.\n+ All \"gamecontroller[...]\" config.txt options have been renamed\n  to \"gamepad[...]\". The old names are still supported.\n+ Windows UNC paths are now supported in the file manager.\n+ Paths with colons (:) are now explicitly supported in Linux\n  and macOS and always rejected in path strings used by worlds.\n+ Fixed blank screen bugs that occur with the glslscale renderer\n  in conjunction with Intel HD Graphics 2500 graphics drivers\n  and possibly some other old drivers.\n+ The softscale renderer now respects disable_screensaver.\n+ HTML5: fixed the poor performance of FREADn and other features\n  that rely on calculating the length of an open file.\n+ Clear world (Alt+R) now resets the world version.\n+ Fixed a crash bug and a false positive match bug in the\n  command IF ALIGNEDROBOT. The false positive bug has been\n  version locked to 2.80 through 2.92X (just in case).\n+ Software layer rendering performance for transparent sprites\n  has been improved, significantly in some cases.\n+ Fixed a software layer renderer bug where a buffer with a\n  pixel alignment that varies between lines would cause the\n  frame to render incorrectly.\n+ Fixed a software layer renderer bug in 32-bit builds where\n  SMZX sprites would sometimes render incorrectly when clipping\n  off the left side of the screen.\n+ Setting a string offset, limit, or splice to FREADn now\n  respects the provided limit. If n is larger than the limit,\n  n total bytes will still be read (up to the maximum string\n  size); any bytes read past the limit are discarded.\n+ Setting a string offset, limit, or splice to FREAD now\n  respects the provided limit and no longer pads the string with\n  spaces in some cases. Setting any string to FREAD now reads\n  the file until a terminator or the end of the file is found\n  instead of stopping when the maximum string size is reached;\n  any bytes read past the limit or maximum size are discarded.\n+ Setting a string splice to FREAD or FREADn with invalid offset\n  or size values no longer causes a crash.\n+ Setting a string splice to FREAD with a valid offset that\n  would write past the current length no longer causes a crash.\n+ Fixed a rare networking bug where fetching a compressed file\n  over HTTP could fail for particular compressed file sizes.\n+ The About MegaZeux dialog now shows both the compiled and\n  linked versions of libpng if they are different.\n+ Fixed flash thing layer draw order bug that would cause it to\n  display over editor elements using the game UI layer.\n+ Licenses are now properly loaded in the About MegaZeux dialog\n  for Darwin builds.\n+ Fixed potential heap overflows in mfread and mfwrite when the\n  current position is past the end. This should never actually\n  happen, but it's safer and shuts up _FORTIFY_SOURCE and ASan\n  compilation warnings.\n+ Fixed platform-dependent out-of-memory errors when loading\n  robots from &lt;=2.84 save files with corrupt stack sizes.\n+ Fixed hangs in the robot editor caused by trying to use\n  Replace All to replace quotes or extra words with nothing.\n+ Fixed the handling of lines over 64 characters long during\n  scroll editing. Scroll editing now previews colors and allows\n  lines up to 255 bytes in length. Like robot message boxes, any\n  characters past the first 64 will clip.\n+ Clicking a line in the scroll display and editor now jumps to\n  that line (same as the help file).\n+ You can now toggle between editing scrolls with the protected\n  palette/charset and the game palette/charset with Alt+V.\n+ Games for versions 2.80X through 2.90X now display scrolls\n  using the protected palette instead of the game palette (fixes\n  a scroll in Welkin).\n+ Fixed possible robot stack and scroll message memory leaks.\n+ Fixed a 2.93 regression where color strings with escapes like\n  \"~x\", where x is not a hex digit, would also print the ~ or @.\n+ Fixed clipping of the board message when it contains color\n  codes and when the message column is set to something besides\n  centered or 0. The next row will also use the correct color\n  code from the previous row (as if there had been no clipping)\n  instead of whatever the last color code displayed was. The old\n  behavior is version locked to &lt;=2.93, so this fix won't take\n  effect until 2.94.\n+ Added compatibility for MegaZeux 1.x's reverse send behavior\n  which allows robots to execute twice per cycle in some cases.\n+ Fixed COPY when used with REL COUNTERS for 1.x worlds.\n+ Added support for MegaZeux 1.x entrance precedence (color\n  match, then thing match only). This fixes an entrance bug in\n  MegaZeux Tutorial.\n+ Added support for MegaZeux 1.x entrances linking to other\n  entrances on the same board. This fixes the whirlpools in the\n  Aqua Cavern board in Caverns of Zeux.\n+ Added support for MegaZeux 1.x entrances not triggering when\n  the player is pushed onto them.\n+ Added support for MegaZeux 1.x TELEPORT interrupting the\n  current board scan and partial support for the same behavior\n  in RESTORE PLAYER POSITION/EXCHANGE PLAYER POSITION.\n+ The DJGPP port now locks all of its memory. This prevents\n  paging crashes related to audio, but increases memory usage.\n+ Fixed the TIME and DATE counters in the DOS port when the\n  environment variable TZ is not set.\n+ The SDL audio driver will now initialize the software mixer\n  frequency with the value returned by SDL_OpenAudioDevice.\n+ Fixed audible pops when setting MOD_FREQUENCY.\n+ ccv and png2smzx now return slightly more useful error\n  messages when an image fails to load.\n+ ccv and png2smzx now support BMPs that use BI_BITFIELDS and\n  BI_ALPHABITFIELDS.\n+ ccv and png2smzx now support Truevision TGA images.\n+ Fixed YUV subsample averaging for UYVY (little endian) and\n  YUY2/YVYU (big endian).\n+ ANSi file import/export options are now identified as both\n  ANSi and TXT, and the extension of the selected format is now\n  forced during export.\n+ Fixed taking screenshots when in the board editor text mode.\n+ The SVGA renderer now uses vsync palette/display page flips,\n  which improves performance and fixes display bugs. (asie)\n+ Fixed libxmp stereo samples not playing correctly.\n+ Fixed libxmp music not playing at high audio sample rates.\n\nDEVELOPERS\n\n+ Renamed \"compat_sdl.h\" to \"SDLmzx.h\". This header is now used\n  to abstract ALL trivial includes of SDL headers. This makes\n  supporting the future release of SDL 3 cleaner.\n+ The version of SDL to build with is now determined by the\n  config.sh options:\n    --enable-sdl (enables the default SDL version, usually 2);\n    --enable-sdl2 (enables SDL 2.x);\n    --enable-sdl1 (enables SDL 1.2.x);\n    --disable-sdl (disables SDL).\n  For compatibility, \"--disable-libsdl2\" is still recognized.\n+ Added SDL 2 support to the Wii port.\n+ Added SDL 2 and SDL 1.2 support to the 3DS port. (asie)\n+ Updated the \"sdlaccel\" renderer, which can now be enabled with\n  --enable-sdlaccel in config.sh. This renderer uses the SDL\n  renderer API for hardware rendering and has an experimental\n  threaded charset texture update routine. SMZX isn't currently\n  implemented and it's slow, so it's disabled by default.\n+ Moved SDL renderer window creation to render_sdl.c so both of\n  the SDL renderer-based renderers can benefit from it.\n+ Removed floating point division usage in render_gl2.c.\n+ Added --enable-lto to enable link-time optimizations.\n+ The sanitizer config.sh options can now be used with release\n  builds e.g. --enable-release --enable-asan.\n+ Simplified the configuration of system directories for ports\n  in config.sh, which now prints more information about them.\n+ Fixed config.sh output of \"/usr/bin/git\" and the Git HEAD.\n+ Fixed testworlds process running check edge cases.\n+ Fixed config.sh and platform_endian.h detection for IBM Z and\n  System/390 architecture.\n+ Software mixer configuration (rate, buffer, channels) is now\n  done using the function audio_mixer_init instead of each\n  driver handling it manually.\n+ The software mixer render function now takes the number of\n  frames and channels to render and the output format instead\n  of the number of bytes. Supported output formats are 16-bit\n  signed, 8-bit signed, and 8-bit unsigned.\n+ Audio streams with a null mix_data function are now supported.\n  These streams will be ignored by the software mixer.\n+ sampled_mix_data now properly handles output requests smaller\n  than the size of the audio buffer.\n+ Added software renderer regression tests.\n+ Removed PPAL compile time render_layer variants. These did not\n  help performance and increased the render_layer size by 50%.\n+ Added dependency builder makefile system in scripts/deps to\n  help with regenerating MinGW, DJGPP, darwin-dist, and Linux w/\n  MemorySanitizer dependencies.\n+ Overhauled the darwin-dist build system to allow for more\n  modern XcodeLegacy-based compilation, including code signing\n  and shared library support (via dylibbundler). The following\n  architectures are now supported: i386, x86_64, x86_64h, ppc,\n  ppc64, and arm64/arm64e (untested).\n+ Updated libxmp to 4.6.0+6cb48a4a+sample rate patch.\n+ Added more PowerPC64 GCC defines to platform_endian.h.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nDecember 31st, 2023 - MZX 2.93\n\nThis is the first MegaZeux release in about 3 years, so there\nare a lot of changes, including a large number of crash fixes.\nBrief summaries of each section:\n\nGeneral: worlds are decrypted in memory now, MegaZeux 1.x worlds\nare now supported, you can have more custom sound effects, the\n1/8th random movement chance of dragons has been readded, and\nstatic sprites now collide at their apparent position.\n\nRobotic: new label PLAYERDIED, new counter KEY_PRESSEDn, new\ncounter DATE_WEEKDAY, new counter SPRn_OFFONEXIT, new viewport\ncounters, and setting MOD_LOOPSTART after MOD_LOOPEND now works.\n\nEditor: the robot editor now has undo/redo, layer MZM block type\ncan now be selected, reimplemented ANSi/TXT import/export,\nwatchpoint improvements, a new variable debugger RAM menu, and\nvlayer saved positions.\n\nVideo/audio: most GLSL performance and graphical issues have\nbeen addressed and libxmp has received a massive bugfix update.\n\nPortability: new PS Vita, DOS, Wii U, and Dreamcast ports.\nNumerous NDS and 3DS improvements. The extra memory system from\nthe NDS port has been generalized to several ports and now\nsupports memory compression. Virtual filesystem support.\n\nUtilities: vastly expanded image file support for ccv/png2smzx\nand a new video conversion utility called y4m2smzx.\n\nGENERAL\n\n+ Added an \"About MegaZeux\" dialog, accessible from the main\n  menu. This menu contains extended version information, and\n  displays license information for MegaZeux and the 3rd party\n  software it uses.\n+ Protected worlds are now decrypted to RAM or a temporary file\n  when 'auto_decrypt_worlds' is enabled and the original file is\n  left unmodified. This setting is now enabled by default.\n+ Added experimental support for loading MZX 1.x worlds. Support\n  is not fully implemented yet, but all currently known 1.x\n  worlds are playable.\n+ Up to 256 custom sound effects are now supported instead of\n  50, and custom sound effects can be up to 255 chars in length.\n  Currently, the sound effects editor is restricted to 100 SFX\n  of length 68; the CHANGE SFX # to \"\" command may be used as a\n  temporary workaround.\n+ Custom sound effects can now be given custom names in the\n  sound effects editor. Currently names are limited to 9 chars\n  and are only useful when editing.\n+ Fixed dragon 1/8th chance of random movement, which has been\n  broken since 2.80. The original DOS behavior (random movement)\n  or the 2.80 behavior (no random movement) can be selected via\n  the new board setting \"Dragons randomly move\".\n+ Reset Board on Entry and load charset/palette on entry now\n  optionally will be performed even if re-entering a board from\n  itself (enabled by default).\n+ Fixed a Reset Board on Entry bug where a temporary board would\n  not be generated when loading the title screen or if the\n  starting board is the title board.\n+ Fixed bug where the second cycle of a board when entering from\n  a board edge or entrance would be shortened due to incorrect\n  gameplay framerate timing introduced in 2.91g.\n+ Fixed a bug where, if a world's title screen music is set but\n  fails to load when the world is loaded, the music from another\n  world could continue to play.\n+ Fixed a bug where boards wouldn't be pulled from extra memory\n  before saving, potentially causing save corruption on the NDS.\n+ Fixed a standalone mode crash on exit that occurred when using\n  Alt+F4 or the window close button.\n+ Fixed a crash that could occur when loading a save file with\n  the temporary board flag set but no temporary board data.\n+ Fixed a bug in the random number generation function that\n  could cause it to rarely return a number outside of the\n  expected range. This unfortunately breaks compatibility for\n  the RANDOM_SEED# counter.\n+ Static sprites now collide as if they are at the position on\n  the board where they are currently being drawn i.e. relative\n  to the viewport instead of at their actual X/Y position. This\n  behavior has also been added to the IF SPRITE AT # # command.\n+ Optimized ccheck 3 sprite collision performance by generating\n  visibility masks for characters directly instead of using an\n  intermediate render and skipping pixel checks for blank chars.\n+ Fixed a bug where worlds with SMZX mode selected would display\n  the previous world's palette after switching back to MZX mode.\n+ World and save files now save the MZX mode palette when in\n  SMZX modes 2 or 3. Save files now save the MZX mode palette\n  intensities when in SMZX modes 2 or 3. The palette and palette\n  intensities for SMZX modes 2 and 3 are now saved as \"palsmzx\"\n  and \"palints\" when one of these modes is active.\n+ Save files now save palette intensities for all modes as a raw\n  array of little endian DWORDs instead of bytes. The 2.92 and\n  2.93 intensities file formats are mutually incompatible.\n+ Scrolls can now display standard message ~@ color codes.\n+ Fixed warning messages caused by SDL controller CRCs.\n+ Fixed hangs that could be caused by malformed counters files.\n+ Fixed world/save/counters file corruption in rare edge cases\n  where the aforementioned files could break the zip format's\n  internal field bounds.\n+ Fixed loading of world/save/counters files over 4 GB.\n+ Fixed crashes when loading world or save files that contain\n  invalid current board IDs (both loaders), saved position\n  board IDs (both), start/death/game over board IDs (zip), board\n  exit board IDs (zip), robot program positions (both), robot\n  stack pointers (both), or robot stack sizes (legacy).\n+ The output file mode (FWRITE_OPEN, FWRITE_MODIFY, or\n  FWRITE_APPEND) is now stored in save files. This fixes a bug\n  where the output file would be reopened in the wrong mode (ab)\n  when reloading a saved game.\n+ Added a second Robotic command listing to the help file that\n  lists commands categorized by command type. (Terryn)\n+ Links in the help file table of contents and Robotic Reference\n  Manual are now grouped into categories. (Terryn)\n+ Other misc. help file corrections and maintenance. (Terryn)\n\nROBOTIC\n\n+ New built-in label: PLAYERDIED (and the subroutine equivalent\n  #PLAYERDIED). When the built-in player dies, this label is\n  sent on the board the player died on. Because player death\n  occurs at the end of the cycle, this is received during the\n  following cycle.\n+ New counter: KEY_PRESSEDn, to read internal keycode statuses.\n  This is like KEYn, but KEY_PRESSEDn accepts internal keycodes\n  instead of PC XT keycodes. KEY_PRESSEDn will return 1 if the\n  key is currently pressed, or 0 if it is not pressed.\n+ New counter: DATE_WEEKDAY, which returns the number of the\n  current day of the week. Values are 0=Sunday, 1=Monday,\n  2=Tuesday, 3=Wednesday, 4=Thursday, 5=Friday, 6=Saturday.\n+ New counters: VIEWPORT_X, VIEWPORT_Y, VIEWPORT_WIDTH, and\n  VIEWPORT_HEIGHT to read the current board's viewport settings.\n  (Sparkette)\n+ New counter: SPRn_OFFONEXIT. When enabled for a sprite, that\n  sprite will be automatically turned off when exiting the board\n  (same as setting SPRn_OFF). This occurs even if the new board\n  and the previous board are the same. (Sparkette)\n+ Fixed crash bugs caused by using CHANGE SFX # to \"\" with an\n  invalid sound effect number.\n+ Fixed loop detection for Ogg Vorbis files when MOD_LOOPSTART\n  is set to a value greater than MOD_LOOPEND.\n+ Fixed a crash bug when attempting to save MZMs over 4MB to an\n  existing string.\n+ Fixed a buffer overflow crash that could occur when using LOAD\n  CHAR SET with both a large input file and a large offset.\n+ Reading from and writing to extended character sets should no\n  longer display an \"advanced graphical features\" error message\n  when a renderer without layer rendering support is active.\n  This will, however, display a (new) error message specific to\n  the Nintendo DS port.\n+ Fixed CLIP INPUT and IF FIRST STRING \"\" \"\" bug where INPUTSIZE\n  would be used as the bound on the input string without also\n  checking for null chars, potentially allowing heap corruption.\n+ Fixed a hack where the TELEPORT command would temporarily\n  store the current board to extra memory, causing the game\n  update loop to potentially cause memory corruption with stale\n  board pointers on the NDS.\n+ Fixed string wildcard matching bug that could cause patterns\n  ending with ? to sometimes fail.\n+ Dividing the value -2147483648 by -1, either by the commands\n  DIVIDE and MODULO or through expression operators / and %, no\n  longer crashes MegaZeux.\n+ When the right operand to a bitshift operator in an expression\n  is less than 0 or greater than 31, &lt;&lt; and &gt;&gt; will return 0,\n  and &gt;&gt;&gt; will return 0 if the left operand is positive or -1 if\n  the left operand is negative. The old behavior of the bitshift\n  operators for these shift values was architecture dependent.\n+ The date and time for the Robotic date and time counters is\n  now sampled once per command maximum. When multiple date/time\n  counters are read in the same command, they are guaranteed to\n  represent the same time.\n+ The Robotic message box commands now properly display char 10.\n+ The Robotic command ? now correctly accounts for color codes\n  in its length bounding.\n+ Fixed crashes in the &amp; Robotic command caused by bad color\n  string length calculations.\n+ Fixed PrintScreen/SysRq and Menu/\"Right Click\" keycodes.\n+ Unsupported keys no longer return a KEY_PRESSED value of 1\n  for SDL 2 builds.\n+ Fixed crashes when executing the GIVE, TAKE, TAKE ELSE, and\n  TRADE commands with an invalid item type.\n+ Fixed crashes when disassembling a Robotic command with an\n  invalid condition.\n\nEDITOR\n\n+ Added robot editor undo (Ctrl+Z) and redo (Ctrl+Y). Like with\n  board/vlayer/charset editing, the size of the undo stack is\n  defined by the config setting \"undo_history_size\". Note that\n  extended macro expansion can sometimes clobber undo or redo\n  frames (this is not easily fixable, and prevents worse bugs).\n+ When loading a layer MZM to the board in the editor, instead\n  of always loading the MZM as CustomBlocks the conversion type\n  can now be selected from CustomBlock, CustomFloor, and Text.\n  Canceling the object type dialog no longer cancels the current\n  block or MZM placement.\n+ Reimplemented ANSi import/export support for the editor. ANSi\n  and TXT files can be imported in the editor with Alt+I and can\n  be exported by selecting a block (Alt+B).\n+ Creating a new world using N from the title screen or Alt+R\n  in the editor now clears the extended character sets.\n+ Improved the performance of Robotic debugger watchpoints,\n  especially for non-built-in counters and non-spliced strings.\n+ Robotic debugger watchpoints can now watch for particular\n  counter or string values. Leaving the value field blank will\n  still check for any value.\n+ Added a list of variables related to RAM usage to the counter\n  debug window. This list can be found in the \"World\" list.\n+ Added vlayer saved positions to the editor. Like regular saved\n  positions, these can be configured with the config file option\n  \"vlayer_position#\", and they work identically to board saved\n  positions.\n+ Fixed bugs caused by missing validation in the Import SFX\n  feature in the editor.\n+ Fixed a bug where multichar char tile movement could still\n  sometimes jump to the -/+/= chars in the main charset.\n+ Fixed another bug that would cause string searching in the\n  robot editor to sometimes skip matches.\n+ Replace all (Ctrl+H) in the robot editor should no longer skip\n  adjacent instances of the search string.\n+ Opening the block action menu in the robot editor (either with\n  Alt+B or Alt+Enter) now updates the current line, fixing bugs\n  where it could copy old line contents to the buffer.\n+ Fixed a robot editor crash that could occur when combining two\n  lines with backspace or delete if one of the lines contained\n  the start or end block mark.\n+ Fixed a bug in the robot editor where Ctrl+Home and Ctrl+End\n  would only set the current line (and not the current column).\n+ Fixed a robot editor extended macro crash that would occur:\n    when pasting a line containing a macro from the clipboard;\n    when importing a Robotic source file that invokes macros;\n    when invoking nested extended macros.\n+ Invoking an extended macro with enter/return no longer inserts\n  an extra blank line.\n+ Pressing enter at the start of a line with an extended macro\n  now invokes the macro instead of just inserting a line.\n+ Fixed editor extra memory crashes when importing and deleting\n  boards.\n+ editor_show_thing_toggles is now enabled by default.\n- board_editor_hide_help and robot_editor_hide_help are no\n  longer enabled by default for accessibility.\n\nVIDEO/AUDIO\n\n+ Added the \"glslscale\" renderer. This is the same as the GLSL\n  renderer, but uses software rendering with scaling shaders\n  instead of hardware rendering. This is faster on low end PCs\n  and embedded devices and is the new default renderer selected\n  by auto_glsl. (Hardware rendered GLSL is still available for\n  users with graphics cards by selecting the \"glsl\" renderer.)\n+ The software fallback renderer functions now support SMZX mode\n  3 indices, fixing graphical bugs in situations where the\n  fallback would be used and display the wrong colors in games\n  using custom indices.\n+ Added an \"auto\" mode for the force_bpp config option. Modern\n  SDL automatically selects a native window pixel format and a\n  fixed value can make the software renderer slower. Detecting\n  the BPP from the created window is more in line with reality.\n+ Fixed color inaccuracies in the softscale renderer on Mac OS\n  caused by using full-swing YUV values instead of the expected\n  studio-swing values for GL_YCBCR_422_APPLE textures.\n+ Simplified GLSL renderer texture data packing to improve\n  support for OpenGL ES and older OpenGL implementations.\n+ The GLSL tilemap fragment shaders will now use highp floats if\n  provided by the OpenGL ES driver. If highp is unavailable and\n  mediump has inadequate precision, MZX will print a warning.\n+ Fixed software renderer display issues caused by relying on\n  SDL_PixelFormat::BitsPerPixel instead of BytesPerPixel on\n  platforms with native 15 BPP display modes.\n+ Added disable_screensaver configuration option. MegaZeux now\n  leaves the screensaver enabled by default (fixes regression\n  caused by SDL 2.0.2+).\n+ Added an \"sdl_render_driver\" config setting. This setting can\n  be used to specify the underlying SDL renderer driver used by\n  the softscale renderer. This setting does not affect any other\n  renderers.\n+ libxmp playback improvements for GDM, AMF, and OctaMED modules\n  as well as general stability improvements.\n+ Updated libxmp to 4.6.0+ceb2d025.\n+ Fixed crashes in the RAD player that could be caused by\n  references to uninitialized instruments.\n+ Applied various Opal fixes from OpenMPT.\n- Removed GL4ES from the GLSL blacklist.\n\nPORTABILITY\n\n+ Added an experimental PlayStation Vita port. (Spectere)\n+ Added an experimental Wii U port. (asie)\n+ Added an experimental Dreamcast port. (asie, Lachesis)\n+ Added an experimental MS-DOS 32-bit port based on DJGPP.\n  (Mr_Alert, asie, Lachesis)\n+ Added support for BlocksDS to the NDS port. (asie)\n+ Added support for 800x240 mode to the 3DS port. This mode can\n  be toggled with the 3DS keyboard. (asie)\n+ Added support for using 352kB of previously unused NDS VRAM\n  as board storage. This area can be used for board storage even\n  when a slot 2 expansion cartridge is not present. (asie)\n+ Reduced RAM usage by 50k by removing variants of *printf and\n  *scanf with floating point support on the NDS. (asie)\n+ The MOD_ORDER counter is now readable on the NDS. (asie)\n+ save_slots is now enabled by default for consoles. (Spectere)\n+ Inactive boards and some robots are now compressed on the NDS,\n  which (combined with asie's VRAM patch) should vastly increase\n  the number of games that can be played on the original DS and\n  DS Lite without a slot 2 expansion cartridge. This is also\n  enabled for the PSP and DOS ports.\n+ The NDS, PSP, and DOS ports now write zip archives with the\n  fastest zlib compression level. This makes saved files that\n  use compression (worlds, saves, etc.) slightly larger.\n+ Added virtual filesystem support to MegaZeux. Currently, this\n  is only used for caching files, and is only enabled by default\n  on platforms with slow or buggy file IO (3DS, Vita). This\n  feature may be optionally configured with the config options\n  vfs_enable, vfs_enable_auto_cache, vfs_max_cache_size, and\n  vfs_max_cache_file_size.\n+ Fixed a bug where the 3DS and Switch ports would display .. in\n  the file manager when selecting a board module, board charset,\n  or board palette.\n+ Fixed a bug where, when a faulty dirent implementation returns\n  .. when listing the contents of a root directory, .. would be\n  displayed despite being meaningless.\n+ Fixed a bug where some checks in the file manager could fail\n  when getcwd returns different slashes than expected.\n+ Fixed a bug where stdio redirect could generate corrupted log\n  files on the Nintendo DS due to poor freopen support.\n+ Enabled the GLSL renderer for the Android port. The Android\n  port now uses the GLSL scaled software renderer by default.\n- Removed 3DS CIA support. (asie)\n\nUTILITIES\n\n+ ccv and png2smzx now both support the following image formats:\n  PNG (libpng builds only), GIF, BMP (uncompressed, RLE8, RLE4),\n  NetPBM (PBM, PGM, PPM, PNM, PAM), farbfeld. png2smzx will now\n  build when libpng support is disabled, though it won't support\n  loading PNGs with this configuration.\n+ ccv and png2smzx now both support streaming image data from\n  stdin using the input filename \"-\".\n+ Added y4m2smzx, a video converter frontend for the same image\n  converter that png2smzx uses. This is a rewrite of Mr_Alert's\n  original frontend from 2010. This uses a custom y4m loader\n  rather than MJPEG Tools for now, so it may be buggy. Usage:\n  ffmpeg -i input -f yuv4mpegpipe pipe:1 | y4m2smzx - out.mzv\n+ The downver utility now supports save files.\n+ Fixed a bug where checkres would not check the current dir\n  for files when provided with a MZX filename with no path\n  component.\n+ checkres now attempts to scan every board in &lt;=2.84 worlds\n  where a corrupt board/robot is found. Due to old world format\n  limitations, robots after a corrupt robot can not be scanned.\n+ checkres now supports scanning protected worlds.\n+ Added experimental checkres support for MZX 1.x worlds.\n\nDEVELOPERS\n\n+ Added --enable-extram config.sh flag to enable extra memory\n  hacks on arbitrary platforms. This is enabled by default in\n  CONFIG.NDS, CONFIG.PSP, and CONFIG.DJGPP. When enabled, non-\n  active boards are compressed in RAM. Certain platforms can use\n  platform-specific storage, like the NDS.\n+ Board input strings, charset paths, and palette paths are now\n  heap allocated on-demand to save RAM for low-memory systems.\n+ Status counters are now saved as a nested properties file.\n+ Refactored the 3DS renderer to use templates. (asie)\n+ intake2() now supports custom handling of intake events, i.e.\n  it can now be used without providing a fixed size buffer.\n+ The draw and click handlers have been removed from intake2().\n  This should provide more flexibility to the parent context for\n  displaying the string that is being edited.\n+ Added \"mzxout\" and \"mzxerr\" streams for printing console\n  messages. In most places these should be used instead of\n  \"stdout\" and \"stderr\" to allow debug and warning messages\n  print to the correct log files when stdio redirect is enabled.\n+ Fixed broken MIPS big endian detection inherited from SDL 1.2.\n+ Added architecture width detection for RISC-V RV32 and MIPS64.\n+ Replaced the macro-based mixers in sampled_stream.c with more\n  maintainable template-based mixers.\n+ Fixed the DSO -Wstrict-aliasing warnings in Socket.cpp and the\n  OpenGL renderers for GCC versions using -Wstrict-aliasing=2.\n+ Added --host config.sh option to manually specify a cross\n  compiler prefix. This is ignored or overriden by most ports\n  and is mainly just for Linux.\n+ Relicensed ccv from GPL 3 to GPL 2+ to match the rest of MZX.\n  (Lancer-X)\n+ If available, GetSystemTimePreciseAsFileTime and clock_gettime\n  are now used to calculate the system clock time.\n+ If available, SDL_GetTicks64 is now used by get_ticks().\n+ Minor performance improvements for run_robot, is_string, and\n  find_function_counter.\n+ _FILE_OFFSET_BITS=64 is now used for 64-bit fseeko, ftello,\n  readdir, and stat/fstat support for 32-bit Linux builds.\n+ Updated Android SDL to 2.28.2. (asie)\n+ Updated Android NDK to r23c. (asie)\n+ Fixed Android build system handling of missing libraries.\n+ Improved fileform.html, joystick.html, keycodes.html, and\n  platform_matrix.html readability on small/mobile displays.\n- String values are now allocated separately from the string\n  struct and name. This may make strings very slightly slower,\n  but means string pointers are now stable through an entire\n  gameplay session.\n\n<a class=\"hL\" href=\"#292CLOG.HLP__292\">New in Versions 2.92 to 2.92f</a>\n<a class=\"hL\" href=\"#2901CLOG.HLP__291\">New in Versions 2.90 to 2.91j</a>\n<a class=\"hL\" href=\"#284CLOG.HLP__284\">New in Versions 2.84 to 2.84c</a>\n<a class=\"hL\" href=\"#2823CLOG.HLP__283\">New in Versions 2.82 to 2.83</a>\n<a class=\"hL\" href=\"#281CLOG.HLP__281\">New in Versions 2.81 to 2.81h</a>\n<a class=\"hL\" href=\"#280CLOG.HLP__280\">New in Versions 2.80 to 2.80h</a>\n<a class=\"hL\" href=\"#OLDERVER.HLP__260\">New in Versions 2.60 to 2.70</a>\n<a class=\"hL\" href=\"#OLDESVER.HLP__201\">New in Versions 2.01 to 2.60</a>\n<a class=\"hL\" href=\"#NEWIN200.HLP__1st\">New in Version 2.00</a>\n<a class=\"hL\" href=\"#ANCENVER.HLP__1xx\">New in Versions Through 1.03</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"292CLOG.HLP\">\n<a class=\"hA\" name=\"292CLOG.HLP__292\"> </a>\nNew in Versions 2.92 to 2.92f\n\nNovember 22nd, 2020 - MZX 2.92f\n\nHere's another bugfix release. Highlights of this release are\ncrash fixes (including the 3DS OGG crash bugs), numerous SAM/WAV\nplayback bugfixes, some edge case compatibility fixes, world\ndecryption improvements, networking improvements (including\nexperimental Wii, 3DS, and Switch support), bugfixes for the\nfile manager in Nintendo Switch builds, and too many libxmp\nfixes/improvements to list. Since libxmp development is active\nagain, the number of MegaZeux hack patches is down to just two!\nThe editor is now enabled for 3DS builds, and some rudimentary\njoystick support has been added to the editor so it will (maybe)\nbe (almost) usable.\n\nOf special note are asie's improvements to the NDS port. New\nfeatures include protected character set and palette support,\nimproved touchscreen focus, reduced memory consumption, and\n*audio support*. Currently NDS audio requires preconversion of\nmusic files to .MAS and sample files to .SAM, but that it works\nat all is amazing by itself. Pre-converted .MAS files are\nincluded with the packed-in copy of Caverns of Zeux.\n\nUSERS\n\n+ Added audio support to the NDS port. PC speaker effects and\n  .SAM files will work out of the box, but .MOD/.S3M/.XM/.IT\n  files require preconversion to .MAS using mmutil. All other\n  audio formats are unsupported. (asie)\n  Example:\n\n    mmutil -d -m CV_TECH.MOD\n\n+ Added protected character set and palette support to the NDS\n  port and reduced overall RAM usage for the NDS port. (asie)\n+ Fixed a bug in the NDS port where MZX would immediately focus\n  the player after a touchscreen press event, preventing the\n  touchscreen from being used to scroll the upper screen. (asie)\n+ Fixed a crash that could occur sometimes when duplicating\n  robots on a board with Reset Board on Entry enabled.\n+ Added SET RANDOM, INC RANDOM, DEC RANDOM, and TAKE \"label\"\n  commands to the list of commands that should allow \"infinite\"\n  loops in pre-2.80 versions.\n+ Fixed SET RANDOM # TO # for large ranges on certain platforms\n  (example: Linux) confused by the 32-bit math used to calculate\n  the random range.\n+ Fixed crashes that could occur when attempting to run MegaZeux\n  without help.fil.\n+ Fixed graphics corruption when using the glsl and opengl2\n  renderers on big endian platforms.\n+ Fixed a bug that would cause string searches to sometimes skip\n  certain matches.\n+ Improved performance of saving ZIP worlds/save files/etc. for\n  some platforms.\n+ Platforms without a protected palette and the meter enabled\n  should now fade out properly before loading a world.\n+ Rewrote decrypt() to work better on low-memory platforms.\n+ Improved GLSL layer renderer performance slightly by sending\n  the palette and indices to the GPU fewer times per frame.\n+ Fixed a bug where the GLSL renderer could attempt to load the\n  framebuffer symbols from a driver that doesn't support them\n  when resizing the window.\n+ Fixed a bug where checkres wouldn't correctly detect\n  \"LOAD_ROBOT#\" when used to load to a specific robot.\n+ Fixed a memory leak when printing network error messages in\n  Windows builds.\n+ Fixed a potential crash bug in the netcode where socket IDs\n  could be reused after close() when attempting to connect.\n+ Fixed a crash bug affecting the compatibility implementation\n  of getaddrinfo() that would occur if NULL was returned by\n  gethostbyname() during DNS resolution.\n+ Fixed a potential crash in the compatibility implementation of\n  getaddrinfo() caused by gethostbyname() being thread-unsafe.\n+ Fixed a bug where the updater would hang for up to 10 seconds\n  due to connections timing out during the confirmation UI.\n+ Fixed a bug where the local manifest.txt would be overwritten\n  by the remote manifest, potentially causing bugs. The local\n  manifest will now be replaced only after a successful update.\n+ Fixed bugs where the HTTP layer would filter header names,\n  content/transfer coding values, and content type values too\n  strictly.\n+ Fixed a bug where INPUT STRING would display newlines from\n  an interpolated string.\n+ MZX now validates the world version of encrypted worlds before\n  offering to decrypt them. This should reduce the chance of\n  accidentally \"decrypting\" a corrupted file and prevents MZX\n  from attempting to decrypt MZX 1.x files (which store the\n  password differently).\n+ Added config settings \"editor_show_thing_blink_speed\" and\n  \"editor_show_thing_toggles\". The former controls the blinking\n  speed of the show thing (Shift+F2 et al.) editor keys and the\n  latter allows these keys to be treated as a toggle instead of\n  blocking input.\n+ Added SOCKS5 IPv6 and username/password support.\n+ Added \"network_address_family\" config setting to allow users\n  to force either IPv4 or IPv6 connections/name resolution. By\n  default, MZX will now request either or both depending on the\n  system IP address configuration (previously, it was hardcoded\n  to only allow IPv4 despite IPv6 support being implemented).\n+ The config setting \"updater_enabled\" has been added to turn\n  off the updater system entirely without disabling networking.\n+ The updater now allows update checks to be performed even when\n  a full update wouldn't be possible, and should be more helpful\n  about printing warnings to the console when this situation\n  occurs.\n+ Enabled rewinddir hack and .. file manager hacks for Nintendo\n  Switch builds to fix the file manager and directory seeking.\n+ Fixed a bug where importing a board over the title board would\n  not update the world title.\n+ Fixed a crash that could be caused by selecting a block on the\n  overlay/vlayer, changing boards, then pressing enter.\n+ Fixed a bug where the currently playing PC speaker note would\n  play for the rest of its duration after turning off PC speaker\n  sound effects, after \"end play\", after exiting gameplay, etc.\n+ Fixed a regression where turning off music in the settings\n  menu and then turning music back on would not start playing\n  the music file for the current board.\n+ Fixed multiple libvorbis and tremor crashes related to the\n  3DS platform_mutex_lock implementation not blocking.\n+ Fixed audio frequency bugs in .SAM file playback.\n+ Certain .WAV subtypes handled by SDL_LoadWav should now load\n  properly on big endian machines.\n+ Signed 16-bit samples should now work correctly with MOD SAM\n  on big endian machines.\n+ Fixed the white border present when using the GLSL renderer in\n  HTML5 builds.\n+ Added joystick input handlers for most editor interfaces.\n  Only very basic things are supported like navigating boards\n  and robots. This is intended for things like handheld consoles\n  without a keyboard.\n+ Fixed a bug where turning off the listening mod would load the\n  mod that was playing when the listening mod was loaded instead\n  of the current board's mod.\n+ Fixed a bug where setting a board mod to a partiular mod, then\n  setting the board mod to *, then changing to a different board\n  with the same mod would not restart the board mod.\n+ Fixed a bug where libxmp would not ignore zero volume samples\n  in STM files.\n+ Restored libxmp support for Soundtracker 2.6/IceTracker .MODs.\n  Currently only libxmp supports this .MOD variant.\n+ Fixed a bug where MZX would fail to load Noisetracker,\n  Octalyser, and Mod's Grave .MOD files if they were named using\n  the extensions .NST, .OCT, and .WOW (respectively). These can\n  now be selected with Alt+N/Ctrl+N in the editor.\n+ Enabled loading .XM and .AMF files with MikMod. Whatever the\n  issue was with these in 2007 seems to have since been fixed.\n+ Fixed loading Soundtracker 15-instrument .MODs with MikMod.\n+ Fixed MOD_POSITION and MOD_LENGTH counters for MikMod.\n+ MikMod now uses interpolation if module_resample_mode is set.\n- Disabled playing modules using the SAM command with MikMod to\n  avoid crashes due to MikMod using a global player state.\n\nDEVELOPERS\n\n+ The test worlds now run correctly when AddressSanitizer is\n  enabled.\n+ The SUPPRESS_BUILD Makefile flag has been split into several\n  flags, allowing more granularity for disabling default rules.\n  The Android and Darwin \"dist\" meta targets use these flags to\n  to disable the default \"all\" target, so \"make\" can now be used\n  in place of \"make dist\" for these platforms.\n+ Using \"make build\" will now remove the build/${SUBPLATFORM}\n  directory if it exists before preparing a build. Before, using\n  \"make build\" when the platform build directory already exists\n  would not copy any updated binaries/assets/etc., potentially\n  causing confusion.\n+ The check_alloc series of functions should now be thread safe.\n  The main thread will display a fatal error as usual when\n  allocation fails; others threads will print a warning and\n  return NULL.\n+ Added implementations of new/delete/__cxa_pure_virtual that\n  can be linked to avoid linking libstdc++/libc++ on platforms\n  and configurations that otherwise don't need those libraries.\n+ Refactored the netcode to use C++ instead of/in addition to C,\n  allowing some cleanup that otherwise wouldn't have worked.\n  Host::bind, Host::listen, Host::accept, and Host::poll are no\n  longer disabled. Other network deadcode functions have been\n  updated but are still disabled.\n+ Fixed FD_ISSET in the Windows netcode.\n+ Added a EAI_AGAIN define for the getaddrinfo() fallback.\n+ Fixed leak of updates.txt file pointers in the updater that\n  could occur when updates.txt fails to download. The updater\n  now downloads updates.txt to a buffer instead of to disk.\n+ Added graphics.renderer.hardware_cursor for platforms with\n  hardware cursors that may need to be updated during any frame\n  regardless of the current cursor state/blinking (i.e. DJGPP).\n+ Fixed \"make build\" error after using \"make debuglink\" with png\n  support disabled.\n+ Added const and restrict to the software render_graph and\n  render_layer implementations for a small performance boost.\n+ Added --disable-getaddrinfo and --disable-ipv6 config.sh flags\n  to allow disabling these for old platforms and consoles SDKs.\n  Amiga, Wii, and PSP network builds force-disable these. Switch\n  and 3DS builds currently force-disable IPv6.\n+ Added experimental network support for Wii, Switch, and 3DS.\n+ Updated libxmp to git-ab70ec9f, refactored libxmp patches.\n+ Removed unused libxmp Paula simulator code to save RAM. (asie)\n+ SDL is no longer required to build MZX with MikMod enabled.\n+ Fixed MikMod static linking on Windows.\n+ Added --disable-stack-protector config.sh option. Platforms\n  that previously disabled the stack protector in the Makefile\n  now force disable this config option. These force disable\n  hacks will probably be removed in the near future.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJuly 20th, 2020 - MZX 2.92e\n\nThis bugfix release includes miscellaneous audio fixes and fixes\nfor bugs in the configuration system. Other things of note are a\nfix for a graphical bug in the robot editor, a freeze bugfix in\nthe updater when updating from older MZX releases, and the title\nof the Alt+A (\"Select Char Set\") dialog has been reverted back\nto roughly what it was in DOS versions. A second default SMZX\ncharacter set with a different font has been added as well.\n\nUSERS\n\n+ Fixed Windows file handle leaks in audio_xmp.c caused by\n  MinGW's non-compliant fdopen.\n+ Fixed a bug where the title screen's intro message wouldn't\n  cycle the scroll color while no world was loaded.\n+ Added the missing \"ccode_chars\" config setting.\n+ Fixed a bug where the \"ccode_extras\" config setting would\n  change the strings color code instead.\n+ Fixed a bug where \"macro_#\" where # is 0 or between 6-9 would\n  corrupt various parts of the editor configuration. Notably,\n  \"macro_6\" would corrupt the extended macros and cause crashes.\n+ Fixed division-by-zero crashes and GL errors when using\n  invalid fullscreen_resolution or window_resolution values.\n+ Fixed a bug where files included by the config file include\n  directive would ignore editor settings. Also added a recursion\n  limit for includes and fixed a bug where the \"include=file\"\n  format would not work on some platforms.\n+ Fixed a libxmp bug where pattern jump/break could take effect\n  after using JUMP MOD ORDER or setting MOD_ORDER/MOD_POSITION.\n  This fixes a bug at the end of the Inmaportal scene in Cans 3.\n+ Zip files with the language encoding flag set no longer print\n  unsupported flag errors.\n+ Fixed a bug where recursive directory deletion in the updater\n  could get stuck in an infinite loop due to not checking the\n  return value of rmdir and path_get_directory not behaving the\n  same as get_path used to.\n+ Fixed a bug where the cursor could display over the color\n  selector and char editor when opened from the robot editor.\n+ Fixed a bug where the default character set selection menu was\n  inconsistently labeled \"Object type\".\n+ Added a second SMZX default character set with a more legible\n  font. The original is still available for those who prefer it.\n\nDEVELOPERS\n\n+ Fixed memcasecmp test alignment checks for the Motorola 68000.\n+ Improved validation of config setting inputs and added a unit\n  test for config file and command line parsing.\n+ Added buffered zip compression/decompression support.\n+ Added unit tests for the zip functions and (de)compressors.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nMay 8th, 2020 - MZX 2.92d\n\nThis release includes minor new features, an overhaul to the way\nMZX handles text input from SDL 2.0 which should fix various\ntext bugs (particularly with non-US keyboards), fixes required\nto get the Android port working, and a new file format document.\nOther things of note are two libxmp patches fixing issues with\nS3Ms saved by the original Modplug Tracker and with GDMs relying\non fine effect continue, checkres updates, and misc. fixes for\nthe Windows and HTML5 ports.\n\nFixed compatibility issues in this release are a string compare\nbug that prevented Mines of Madness from working and MODs with\nextended filenames working when referenced by their truncated\nDOS SFN in the MZX file. The latter fix only works when there is\na single unambiguous match, which turned out to be essentially\nevery affected game.\n\nIt is worth noting that the Android port is EXPERIMENTAL and is\nlikely to have compatibility issues on many devices. A USB OTG\nor Bluetooth keyboard or game controller is required at minimum.\nMany devices have key press issues that are likely caused by\ndrivers or SDL. SDL text input events are disabled as they cause\neven more keyboard bugs, and as a result non-US keyboard layouts\nprobably won't work with this port right now. Other problems\nwith this port that have been reported include graphical bugs\nand crashes when focusing/unfocusing MegaZeux.\n\nUSERS\n\n+ Added robot editor shortcuts CTRL+Home and CTRL+End to jump to\n  the start and the end of the current program.\n+ Char selector tile movement is now disabled outside of the\n  char editor and for 1x1 char selections. This allows typing\n  -/_/=/+ in other char selectors to jump to those characters.\n+ Added docs/fileform.html, a document describing all currently\n  supported MZX file formats in excessive detail.\n+ When startup_file is provided with both a directory and a\n  filename and no startup_path has been set, the directory will\n  now be used as startup_path. If startup_path is set, the\n  directory portion of startup_file will still be ignored.\n+ The flash thing editor functions (Show Robots, etc.) now use\n  the protected charset and will display with the protected\n  palette in SMZX mode and in regular mode situations where the\n  normal flashing would be difficult/impossible to see.\n+ Fixed a bug where input text with no corresponding internal\n  key would be ignored by the robot editor.\n+ Fixed a bug where some AltGr key combinations would not work\n  in the robot editor (note: SDL doesn't distinguish Alt from\n  AltGr, so any Alt key combos with a special meaning in the\n  robot editor will still be intercepted by MZX).\n+ Windows Alt+numpad sequences corresponding to ASCII characters\n  32-126 should now be recognized.\n+ Fixed an out-of-memory error in the save slot UI and improved\n  its mouse functionality.\n+ Added optional CRC-32 output to checkres. Currently this flag\n  only works for files in ZIP archives and no CRC-32 validation\n  is performed.\n+ Fixed checkres parsing for LOAD CHAR SET \"+&amp;+var&amp;file.chr\".\n+ Fixed checkres crash that could occur on some platforms when\n  attempting to checkres a directory.\n+ Fixed wrong robot board positions reported by checkres for\n  some files when run with -vv.\n+ Repurposed the old checkres -A flag to display everything but\n  unused files, which should be much more useful than the older\n  behavior (particularly with loose files in directory dumps).\n+ Fixed memory leaks in legacy_assemble_line.\n+ Fixed potential null termination issues in both world loaders\n  that could be triggered with invalid board name, robot name,\n  scroll, sensor, sfx, and status counter strings. Also improved\n  loaded robot stack bounds checks.\n+ Decreased the sizes of the counter and string structs and\n  fixed a crash that would occur when attempting to expand the\n  counters or strings lists beyond 1 billion. Checks have been\n  added to prevent them from expanding beyond 2^31 (please don't\n  actually use this many counters/strings).\n+ Fixed string comparison for pre-2.81 games that relied on\n  null-terminated string compares. This fixes Mines of Madness.\n+ Fixed hlp2html bug where certain chars wouldn't be escaped and\n  decreased the size of the output HTML files somewhat.\n+ Fixed libxmp playback for S3Ms containing ADPCM4 samples. This\n  fixes mm2flash.s3m in ZeuxDrop.\n+ Fixed libxmp playback for GDMs where 100/200/A00 should\n  continue the fine portamento and fine volume slide commands.\n  This fixes LB2_7.GDM from Kikan.\n+ fsafetranslate now attempts to detect matches for truncated\n  DOS SFNs on non-Windows platforms. If an unambiguous match for\n  a truncated SFN is found, the SFN will be expanded to the\n  match. This fixes numerous games relying on truncated SFNs\n  with unambiguous matches.\n+ Reimplemented COPY # # # # for layer-to-layer copies.\n+ Fixed a collision bug affecting unbound sprites with ccheck 3\n  set in SMZX mode where wrong colors could be treated as the\n  transparent color for the purposes of collision.\n+ Fixed softscale bug on some platforms where the screen border\n  wouldn't be cleared.\n+ The GLSL renderer will now attempt to use a framebuffer when\n  available. This fixes GL_INVALID_OPERATION bugs with certain\n  OpenGL ES 2.0 implementations.\n+ Directory navigation in the file manager now checks directory\n  access permissions before allowing navigation and displays an\n  error when navigation fails.\n+ Fixed windowing code bug in extended macro editor that would\n  occur after selecting \"Default\" multiple times.\n+ Fixed a bug where long extended macros could sometimes break.\n+ Fixed extended macros not working when the params line is\n  indented by more than one space or tab.\n+ Windows builds now attempt to load and call SetProcessDPIAware\n  on startup. This should fix undesirable DPI-related scaling\n  and fullscreen bugs.\n+ Fixed Emscripten crashes on startup when built with Emscripten\n  1.39.5 and up caused by usage of deprecated canvas behavior.\n+ Fixed a COPY BLOCK $string bug where the buffer could be\n  allocated at the empty destination string's value on some\n  platforms and a faulty bounds check would result in the string\n  being expanded but otherwise blank.\n\nDEVELOPERS\n\n+ Added a set of C++-based unit tests for portions of MZX that\n  can't be reached/tested easily by MZX worlds. These tests will\n  be built and run when \"make test\" is used prior to running the\n  test worlds.\n+ Decoupled SDL 2 text input handling from regular key handling\n  and added a text input buffer. Text keys can now be requested\n  multiple times and either as ASCII or unicode; the next key in\n  the buffer is returned on subsequent calls. intake and editor\n  text entry can now accept multiple text chars per frame.\n+ Added SDL 1.2 implementation for __peek_exit_input.\n+ Contributed sound engines (libxmp, libmodplug) should now\n  properly rebuild after config.sh is run.\n+ Fixed a bug where khash tables couldn't expand beyond 2^31,\n  added hash caching, made the MZX copy of khash use MZX style,\n  and better documented the changes made to it.\n+ Added fast sprintf replacements for Robotic counter to string\n  conversion (counter interpolation, string/counter compares).\n+ Fixed alignment issues in memcasecmp.\n+ Fixed alignment issues in get_cursor_color.\n+ Added a config.sh flag for UndefinedBehaviorSanitizer.\n+ Categorized config.sh flags list.\n+ Added a trace logging level that is disabled by default and\n  changed most dns.c, fsafeopen.c, and checkres.c debug logging\n  to use it. Use the --enable-trace config.sh flag to enable\n  trace logging for debug builds.\n+ Added a config.sh flag to build using GCC's -fanalyzer flag\n  (disabled by default).\n+ Refactored path functions out of util.c.\n+ Replaced the Android build scripts with an improved Makefile\n  system. The Android Makefile now uses the NDK toolchains\n  directly. Also added support for APK signing and fixed several\n  Android port bugs.\n+ MZX will now exit on startup if no world could be loaded and\n  the SDL dummy video driver is active.\n- Removed -fpermissive from CXXFLAGS (pointless).\n- Removed -fbounds-check from debug builds (pointless; MegaZeux\n  doesn't use GCC to build any Fortran or Java code).\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nMarch 8th, 2020 - MZX 2.92c\n\nHere's another bugfix release, this time mostly oriented towards\nfixing bugs in the Emscripten/HTML5 port and checkres. Other\nfixes of note are the module_resample_mode (formerly 'modplug')\nconfig setting has been fixed, numerous optimizations have been\nmade to the software layer renderer, and yet another ternary\noperator bug has been fixed.\n\nFinally, Spectere has added an experimental save slots UI that\ncan be enabled with the config setting \"save_slots\". This\nfeature is intended primarily for consoles and will likely be\nenabled by default for consoles in the future.\n\nUSERS\n\n+ Added an optional save slots interface to complement the file\n  manager. This can be enabled by setting the \"save_slots\"\n  config setting to 1, and is disabled by default. (Spectere)\n+ Board list typing now allows non-alphanumeric keys like spaces\n  and periods. Typing a space first will match zero or more\n  prefixed spaces while seeking.\n+ Added the config setting \"auto_decrypt_worlds\". When set to 1,\n  this allows MegaZeux to automatically attempt to decrypt\n  protected worlds. For most platforms this option is disabled\n  by default.\n+ MegaZeux now silently ignores robots in older worlds with\n  corrupt programs that are marked unused. This fixes an error\n  message that would display when loading Wes.\n+ Renamed \"modplug_resample_mode\" to \"module_resample_mode\".\n  Fixed a bug where the xmp interpolation mode wouldn't be set\n  correctly from this setting and instead would always reset to\n  \"linear\". The default for this setting in config.txt has been\n  corrected (the default has been \"cubic\" since 2.82b).\n+ Fixed a ternary operator bug where the expression could break\n  with certain counter names in the second and third terms.\n+ Added a comment to the Emscripten frontend for how to disable\n  the \"Click to Play\" splash.\n+ Fixed Emscripten frontend issues when MegaZeux is embedded in\n  an iframe making it difficult to regain focus after losing it.\n+ Fixed an Emscripten frontend bug where certain filenames could\n  cause false positives when checking for a directory name,\n  preventing fsafetranslate from detecting the correct path.\n  This fixes various things in Eternal Eclipse Taoyarin.\n+ The Emscripten frontend should now fall back to memory storage\n  if IndexedDB or local storage fails to initialize. This fixes\n  an error when using MZX in a private browser window.\n+ Fixed a robot editor regression where certain numpad symbols\n  could not be typed.\n+ Added \"GL4ES wrapper\" to the auto_glsl blacklist.\n+ Added \"Software Rasterizer\" to the auto_glsl blacklist.\n+ checkres no longer requires zip archive filenames to end with\n  the .ZIP extension.\n+ Added optional CSV output to checkres.\n+ Added error to checkres for 1.xx worlds.\n+ Fix checkres errors when encountering VER1TO2 empty robots.\n+ ZIP support now ignores __MACOSX/, .DS_Store, and Thumbs.db.\n+ Added decompression support for various legacy ZIP compression\n  methods and deflate64.\n+ The Emscripten frontend now falls back to a wrapper for MZX's\n  internal ZIP support when UZIP fails to extract an archive.\n+ Fixed IndexedDB support for legacy Edge versions. (asie)\n+ Fixed an 8bpp software layer renderer bug where transparent UI\n  colors would not be transparent in SMZX mode.\n+ General software layer renderer (software, softscale, opengl1)\n  performance improvements.\n+ Fixed out-of-bounds CHAR_BYTE access with BYTE values over 14,\n  added compatibility check for games relying on this. (asie)\n+ Fixed faulty bounding on CHAR_X/CHAR_Y/PIXEL.\n- Removed the options \"overlay1\" and \"overlay2\" from the list of\n  selectable renderer options except when SDL 1.2 is enabled.\n\nDEVELOPERS\n\n+ Refactored render_layer_code to use C++ templates.\n+ render_layer now disables the (unused) 64-aligned renderers\n  for 32-bit platforms and Emscripten builds.\n+ Fixed buggy render_layer clipping that required clipping\n  renderers to unconditionally use a suboptimal alignment.\n+ Switched from xmp_load_module to xmp_load_module_from_file.\n+ Fixed various clang unused argument compiler warnings.\n+ Updated MSVC dirent.h implementation to latest version.\n+ Moved dir_* functions from util.c to dir.c.\n+ Added Windows UTF-8 compatibility layer to vfs.c and dir.c.\n  UTF-8 support for Windows is still limited.\n+ Moved Makefile compiler version checks to arch/compat.inc.\n+ Added cycles_and_commands.txt to build and install targets.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nSeptember 23rd, 2019 - MZX 2.92b\n\nThis release fixes several bugs, notably 3DS and Wii rendering\nissues and a bug where the wrong palette would be saved while\nCOLOR FADE OUT was active. Also in this release are performance\nand usability improvements for the NDS and 3DS ports and a new\nfrontend for HTML5/Emscripten builds (special thanks to asie).\n\nUSERS\n\n+ Fixed a bug where mouse warping both mouse coordinates would\n  not actually warp both coordinates.\n+ Fixed a port regression where save files would be saved with\n  the wrong color intensities while COLOR FADE OUT was active\n  (or during the first cycle of a board for &lt;2.90 worlds). This\n  fixes fading bugs in Ruin Diver 3.\n+ Added compatibility for a bug where the robot message box\n  would disable COLOR FADE OUT in versions 2.83 through 2.90X.\n  This fixes several BKZX games.\n+ Fixed checkres bugs separately preventing detection of global\n  robots in both world formats.\n+ Fixed 3DS renderer bug where the UI wouldn't properly display\n  char foregrounds with extended graphics enabled.\n+ Fixed 3DS renderer bug where sprites containing chars with\n  transparent foregrounds wouldn't draw any char backgrounds.\n+ Fixed 3DS renderer crashes that would occur when large numbers\n  of sprites were active.\n+ Improved file manager directory read times for 3DS/NDS/Wii.\n+ File IO for world saving/loading is now buffered, which should\n  improve save/load times on the 3DS and Switch.\n+ Enabled writing zip data descriptors for the Switch.\n+ Added classic/stretch video ratio support for the 3DS and NDS\n  lower screens. (asie)\n+ \"classic\" is now the default ratio for the 3DS, NDS, and Wii.\n+ Fixed GX renderer bug where chars with both colors transparent\n  could cause various graphical bugs in normal MZX mode.\n+ Fixed bug where board exits could be cleared when deleting a\n  board or after loading a world containing null boards.\n+ Fixed bugs when playing STARDSTM.GDM with the libxmp engine.\n+ Fixed a crash that could occur when importing a world in the\n  editor containing a null board.\n+ Fixed a bug where null boards wouldn't be properly refactored\n  on the NDS while using a memory cart, causing entrances to\n  point to the wrong boards and possibly other problems.\n+ Fixed a bug that could corrupt the ends of robot programs when\n  using an NDS memory cart.\n+ Improved world loading times when using an NDS memory cart.\n+ Improved NDS memory cart transfer speed. (asie)\n+ Disabled NDS logging hacks when stdio redirect is enabled.\n+ Exiting MZX on the NDS should now power off the NDS.\n+ Added Emscripten frontend. (asie)\n+ Fixed bug where errors in the title screen Load World window\n  could clear the rest of the screen.\n+ Fixed crash that could occur when attempting to load an\n  invalid zip world.\n+ Non-recursive directory deletion in the file manager now tries\n  to delete the directory itself instead of its contents. Added\n  error messages for failed file and directory deletes.\n\nDEVELOPERS\n\n+ Added initial support for the dirent d_type field to help\n  avoid stat calls when reading directories.\n+ Replaced most fseek calls in the legacy world loader.\n+ Refactored move_current_board to be more readable.\n+ Corrected some of the editor's bad extram handling and added\n  warnings to debug builds to help debug it (no platforms at the\n  present support both the editor and extram).\n+ Replaced error wait_event() with update_event_status_delay().\n+ Added preliminary VFS code to replace function pointer casting\n  in zip.c.\n+ Fixed Wayland/X11 detection for setups that don't allow X or\n  Xorg to be run normally.\n+ Unix builds should now attempt to load the icon from the\n  installed path instead of from a hardcoded path. Builds with\n  no sharedir set will use \"contrib/icons/quantump.png\" instead.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJuly 22nd, 2019 - MZX 2.92\n\nHere's another inexcusably big minor version release. First off,\nthere are two new ports (Switch and Emscripten) and the Android\nport has been updated to be actually usable. Second, joystick\nsupport has been significantly overhauled (and depending on the\ncontroller/joystick you plug in, it may work with no config\nsettings required!). Game joystick settings should no longer\noverride the new UI joystick behavior. Also, controllers can now\nbe used directly via Robotic. Read docs/joystick.html for more\ninformation on the new features.\n\nOther changes of note include RAD music support (v1 and v2),\npalette editor improvements, and some new counters. There's a\nnew SDL 2 exclusive renderer to replace overlay1/overlay2 called\n\"softscale\" that performs much better than either did and with\nfewer crashes. Also of note are the massive number of fixed bugs\nand compatibility improvements, fixing regressions and error\nmessages that affected around 20 different games (or more). A\nmajor bug was fixed that caused the updater to not initialize\nin Windows has been fixed too, so basically, update already.\n\nFEATURES\n\n+ Added numerous improvements to MZX's joystick handling:\n  + Joystick inputs can now be bound to generic actions. These\n    actions correspond to XInput buttons and can interact with\n    MegaZeux's various windows in a context-specific manner.\n    They can be used directly in Robotic with the new counter\n    \"joy#.[action]\" or can be bound to keycodes in the config\n    file to allow them to be used with older games. The NDS,\n    3DS, Wii, PSP, and GP2X pad.config files have been updated\n    to use actions. See the help file or config file for more\n    info on the config options and available actions.\n  + Added automatic joystick mapping for Windows/Mac/Linux/etc.\n    Joysticks recognized as SDL game controllers are assigned\n    joystick actions when detected. This behavior can be altered\n    or disabled via config options.\n  + New counter: \"joy#active\" will return 1 if a given joystick\n    is availabled, 0 if not, and -1 if the index is invalid.\n  + New counter: \"joy_simulate_keys\". When set to 1 (default),\n    joysticks can produce simulated key presses, as in older MZX\n    versions. When set to 0, this behavior is disabled.\n  + Joystick settings in game.cnf files are now loaded as game-\n    specific joystick bindings. These bindings will no longer\n    override global/UI bindings defined by the config file or\n    pad.config. The default bindings are restored when any world\n    is loaded from the title screen; they are not restored when\n    a save is loaded, between world swaps, or in the editor.\n  + Joystick config options can be assigned to key names in the\n    format of \"key_X\" (without quotes), where X is the key name.\n  + Joystick config options for a particular controller now work\n    with ranges of controllers e.g. \"joy[1,16].lshoulder = 13\".\n  + Added the config setting \"joy_axis_threshold\". This setting\n    allows the threshold for joystick axis mapped presses to be\n    configured.\n  + The NDS directional pad is now considered a joystick hat.\n  + The 3DS directional pad is now considered a joystick hat.\n    The circle pad is now mappable as axes 1 and 2.\n  + The Wii Classic Controller triggers are now recognized as\n    axes 7 and 8. The Guitar Hero axes have been shifted to 9,\n    10, and 11, and a likely bug with the whammy bar axis has\n    been fixed. The Gamecube controller triggers are recognized\n    as axes 5 and 6, and all 8 controllers now have default\n    bindings in pad.config.\n+ Added/updated Android port. (asie)\n+ Added Emscripten port. (asie)\n+ Added Nintendo Switch port. (Lachesis, asie)\n+ The main menu and game menu are now usable with keyboard,\n  mouse, or joystick. Pressing a navigation key/joystick button\n  or clicking an option in these menus will enable a cursor to\n  select one of the options. Most listed options are selectable\n  unless disabled by the current game, in which case they will\n  be hidden from the menu entirely. However, the settings and\n  exit options appear regardless of their respective counters.\n+ MegaZeux now supports Reality Adlib Tracker (RAD) music files.\n  Both 1.x and 2.x variants are fully supported.\n+ New counter: TIME_MILLIS. Returns the milliseconds of the\n  current system time.\n+ New counters: MOD_LOOPSTART and MOD_LOOPEND. These counters\n  return the start and end of the loop for the current playing\n  music and can be set to change the loop while the music is\n  playing. Setting MOD_LOOPEND to 0 disables the loop. These\n  counters only work for Ogg Vorbis and WAV audio (not modules).\n+ Added support for the LOOPEND tag for Ogg Vorbis files. This\n  tag can be used to define the end of the loop directly, which\n  may be more convenient for some users. This tag takes lower\n  precedence than LOOPLENGTH if both are present, and will be\n  ignored if its value is less than LOOPSTART.\n+ The SPR#_OFF counter is now readable through Robotic.\n+ New sprite counter: SPR#_Z. Sprites are displayed in order\n  of their z value. Sprites with a higher z value will appear in\n  front of sprites with a lower z value. This ordering takes\n  precedence over both Y ordering (if SPR_YORDER is enabled) and\n  sprite number. Removes the need to use SPR#_SWAP for managing\n  sprite draw order. (Lancer-X)\n+ New counters: FREAD_LENGTH and FWRITE_LENGTH. These counters\n  return the length of the FREAD file/directory (if open) or the\n  current length of the FWRITE file (if open).\n+ New counter: SET \"$string\" \"SAVE_ROBOT\". This does the same\n  thing as SET \"filename\" \"SAVE_ROBOT\", except it saves the\n  robot to the specified string.\n+ The default boot.dol for Wii builds with the editor enabled\n  is now MegaZeux instead of MZXRun.\n+ New GLSL scaling shader: \"sai\". This shader implements the\n  \"Scale 2xSaI\" algorithm by Kreed with a modified interpolation\n  function to produce sharper edges. This shader can scale up to\n  any size larger than 640x350 and is comparable to hqscale\n  (though it should look and perform a little better).\n+ Fullscreen mode will now attempt to match a supported screen\n  resolution if no \"fullscreen_resolution\" setting is specified.\n  For scaling renderers, this favors the current desktop\n  resolution. For \"software\", this favors smaller resolutions.\n+ Added the config setting \"fullscreen_windowed\". When set to 1,\n  MZX will create a borderless window with the current desktop\n  resolution instead of regular fullscreen. Disabled by default.\n+ Added the config setting \"grab_mouse\". When set to 1, MZX will\n  trap the mouse cursor within the window while the window is\n  active. Disabled by default.\n+ The current renderer can now be set from the settings menu.\n+ Added the 'softscale' renderer. This renderer is a generalized\n  rewrite of the former SDL 2 overlay renderer implementation.\n  It should perform significantly better and be more portable.\n+ Added several palette editor usability improvements:\n  + The current color stored with F2 is now displayed in the\n    bottom-right corner of the palette editor help menu.\n  + In SMZX mode 2 or 3 editing, the four colors of the current\n    subpalette can be stored with F5. Stored subpalette colors\n    can be placed at the current subpalette with F6. These are\n    stored independently of the stored current color. The help\n    menu color display will cycle through the stored colors.\n  + In SMZX mode 3 editing, the current subpalette's indices can\n    be stored with F7 and placed at the current subpalette with\n    F8. The stored indices are displayed in the help menu next\n    to the stored color display.\n  + The shortcut for toggling the subpalette cursors has been\n    changed to Insert. The subpalette cursors are now displayed\n    in SMZX mode 3 editing and are enabled by default.\n  + Palettes (and/or indices) can be imported into and exported\n    from the palette editor with Alt+I and Alt+X, respectively.\n  + Added a secondary editor-only palette which can be switched\n    to with Tab. This palette is not saved with any world, but\n    can be edited and imported to/exported from and can be used\n    to copy colors/subpalettes between different sources.\n+ checkres now displays the board, sfx, and robot (with line and\n  position on the board) each file was referenced in.\n+ checkres can now be used with a directory as the base file.\n  Like a ZIP archive, checkres will recursively search the\n  directory for .MZX/.MZB files and display information for all\n  of them.\n+ checkres has more display options: only display missing (-M)\n  (default), only display created (-C), only display found (-F),\n  also display missing (-m), also display created (-c), and also\n  display found (-f).\n+ checkres can now wildcard match resources from commands that\n  use expressions or interpolation in filenames. This can be\n  enabled with -w or enabled exclusively with -W.\n+ checkres can now display unused resources in a path/zip after\n  the other resources are listed (-u), or display only unused\n  resources (-U).\n+ checkres can now display every reference to a particular\n  resource in a world instead of just the first with varying\n  levels of information (-v, -vv). Added the option -s, which\n  is the default and equivalent to older versions of checkres,\n  and -1 as an alternate flag for -q.\n+ checkres can now sort its output either by the name of the\n  expected resource file (-N) (default) or by the location in\n  the world the file was referenced (-L).\n+ checkres option flags can now be combined (e.g. -aL). This\n  does not apply to the \"-extra\" and \"-in\" flags.\n+ New config options: \"test_mode\" and \"test_mode_start_board\".\n  If \"test_mode\" is set to 1, MegaZeux will start the given file\n  in testing mode, exactly as if the user used Alt+T on the\n  first board from the editor. Setting \"test_mode_start_board\"\n  to a valid board number in the world will start instead from\n  that board. Exiting testing will exit MegaZeux entirely.\n- SDL 2 support for the overlay renderers has been removed. When\n  specified for video_output, softscale will be enabled instead.\n- Removed WAV from the list of board mod extensions.\n\nFIXES\n\n+ SET \"$string\" TO \"fwrite\" will now always write the delimiter\n  regardless of the length of $string. Previously, this command\n  would only write a delimiter if $string was a length greater\n  than zero, requiring extra checks by the user to account for\n  this special case. Compatibility checks have been added for\n  for worlds made in older versions.\n+ Fixed bug where robots attempting to copy the player over\n  themselves with COPY x y x y or COPY dir dir would get stuck\n  in an infinite loop instead of executing the next command.\n+ SPR_YORDER and the sprite collision list are properly saved\n  and loaded from saves again.\n+ The SPR_NUM counter now has the full range of a regular\n  counter and is saved. Out-of-range values are ignored for the\n  purposes of its legacy IF and PUT uses. This only affects\n  2.92+ worlds. Additionally, 2.65 through 2.69c worlds treat\n  its range as -128 through 127, fixing a bug in Sprite Catcher.\n+ The active sprites count is properly updated when SPR#_OFF is\n  set and is properly loaded from saves again. This value can be\n  viewed from the variable debug screen now, though it may be\n  inaccurate for older saves.\n+ Fixed various bugs related to the robot position not being\n  internally tracked correctly. This could be caused by WALK,\n  SWITCH dir dir, OPEN, MOVE ALL, moving through a transport,\n  pushing, and rotation. This fixes issues with IF ALIGNEDROBOT,\n  THISX, THISY, THIS_COLOR, PLAYERDIST, HORIZPLD, VERTPLD, RID#,\n  ROBOT_ID_#, the robot's coordinates displayed in the counter\n  and robot debuggers, and MZM saving. Due to the number of\n  features affected by these bugs, the fix only affects worlds\n  saved in 2.92 or later.\n+ Fixed a regression where rotating could sometimes fail to\n  rotate the player, sensors, pushable robots, or transports.\n+ When a robot uses the OPEN command and is pushed through a\n  transport, MZX no longer crashes or creates invalid robots.\n+ Fixed crash that could sometimes occur when pressing Alt+Up\n  while drawing near the top of a board in the editor.\n+ Fixed bug where canceling 'Alt+C' char selection in the\n  robot editor would comment the current line.\n+ Fixed a bug where F5 in the robot editor would not print data\n  for extended chars.\n+ Clicking through board name windows and other interfaces with\n  the mouse should no longer place the current buffer.\n+ Fixed a bug where the updater could fail to initialize due to\n  a relative argv[0] in Windows. MegaZeux attempts to get the\n  executable path from Win32 first now.\n+ Improved warning message when no HTTP response is received.\n+ The software renderer no longer tries to use an SDL_Renderer\n  to draw the surface returned by SDL_GetWindowSurface. This\n  fixes a crash when switching to fullscreen with the software\n  renderer on some platforms.\n+ Fixed a software renderer mouse crash bug caused by moving the\n  cursor off the screen with a window bigger than the screen.\n+ Fixed a bug where setting SMZX_MESSAGE to 0 wouldn't always\n  draw the message correctly.\n+ If a referenced SAM or GDM file isn't present, MegaZeux will\n  attempt to load a corresponding WAV or S3M (respectively) with\n  the same filename minus the extension. This should fix some\n  music/sounds in older games that referenced SAM/GDM files but\n  were packed with the post-conversion WAV/S3M files only.\n+ The behavior for reseting the player movement repeat timers\n  has been reverted to requiring all four movement keys released\n  (as in 2.80 to 2.91i). The related player locking fix has not\n  been modified.\n+ IF \"&amp;$string&amp;\" and COPY BLOCK # # # # \"&amp;$string&amp;\" now properly\n  resolve &amp;$string&amp; before checking if the first operand or the\n  copy destination are a string (respectively), bringing their\n  behavior more in line with SET/INC/DEC and other commands.\n+ Fixed a regression where the COLOR INTENSITY commands would\n  update the screen if used the same cycle as TELEPORT PLAYER.\n  This fixes a graphical issue in Thanatos Insignia.\n+ Improved string wildcard comparison performance.\n+ Changed the debug window to display \"2.65/2.68\" for worlds\n  from MZX 2.65 and MZX 2.68. Features from 2.68 still check for\n  the intended MZX 2.68 magic; the very few existing 2.68 worlds\n  that use these features need to be resaved in 2.69.\n+ MegaZeux now attempts to fix Robotic that fails bytecode\n  validation. This resolves error messages and bugs in Eternal\n  Eclipse Taoyarin v1.0, Loco, Manuel the Manx, SaintZZT 7th,\n  Slave Pit, and Ep+Arp World MZX.\n+ The MOD SAM command has been restored for compatibility and\n  is version locked to &lt;2.80 worlds. This fixes missing sound\n  effects in Crisis in Stuffyou City (original), Sprite Catcher,\n  and Talon's Tale. This only works for libxmp builds.\n+ Fixed a sprite collision bounding bug where the collision\n  height bounding for the reference checked using the collision\n  width instead. This fixes Manuel the Manx.\n+ Fixed several memory leaks in the editor related to editing,\n  placing, grab, and undo/redo operations on robots and scrolls.\n+ Fixed vlayer memory leak when loading 2.84 saves.\n+ Fixed a regression where the 'if c?? Sprite' legacy fix wasn't\n  locked to 2.69c and up. This fixes Depravity (2003 DoZ #666).\n+ PUT Sprite now works unconditionally for &lt;2.80 games, fixing\n  bugs where games relied on placing 0-height sprites or placing\n  sprites before initializing their dimensions: Hackers Can Turn\n  Your Computer Into a Bomb!, Lethal Recurse (2003 DoZ #215),\n  Serum (2003 DoZ #404), Trashman Dan (Summer 2004 DoZ #18349).\n+ The LOCAL and LOCAL# counters are now versioned separately,\n  fixing bugs in Freedom Bound (Summer 2000 DoZ #4096) and\n  Impact (2001 DoZ #027).\n+ SHOOT now properly updates the blocked array. This fixes a bug\n  in Captain Proton and the Reality Rippers (1999 WEoZ #173).\n+ Fixed a bug where the custom SFX flag and max samples wouldn't\n  be reset when creating a new world in the editor.\n\nDEVELOPERS\n\n+ Fixed -Wunused-result warnings for Linux builds.\n+ Added \"Intel EMGD\" to auto_glsl blacklist (questionable\n  OpenGL 2.0 support).\n+ Added platform-independent joystick event functions and\n  updated the SDL, NDS, 3DS, and Wii event handlers for them.\n+ MinGW builds now use -Wl,-Bstatic and -Wl,-Bdynamic to enforce\n  the preferred linking when both static and dynamic builds of\n  libraries are present.\n+ For MSYS2 shells, config.sh now chooses the prefix /mingw32\n  or /mingw64 by default if $MSYSTEM is \"MINGW32\" or \"MINGW64\"\n  and either the \"win32\" or \"win64\" arch is selected.\n+ MegaZeux now uses the renderer free_video function on exit.\n+ GL_LoadLibrary no longer fails if the library is already\n  loaded in SDL 1.2. (Lancer-X)\n+ GL renderers free textures and revert to fixed function\n  pipeline where appropriate. (Lancer-X)\n+ OpenGL ES support can now be enabled for SDL builds with the\n  --enable-gles option. This option is force-enabled for\n  platforms that only support OpenGL ES.\n+ opengl1 renderer now always uses power of 2 textures, same as\n  opengl2 and glsl. This provides a tiny performance improvement\n  on most cards and a massive performance improvement on some\n  other cards. (Lancer-X)\n+ Added debug build sanitizer options for AddressSanitizer,\n  MemorySanitizer, and ThreadSanitizer. MemorySanitizer requires\n  clang and all currently require Linux/BSD/Mac OS X/similar.\n  Support for all three is very experimental; they may require\n  disabling options like modular builds or may not work at all.\n+ Moved several param functions from robot.c to run_robot.c.\n  Aside from a few exceptions, these functions are largely used\n  in run_robot.c only. This should improve Robotic performance\n  in release builds.\n+ Updated MSVC project for Visual Studio 2019. (elig, Spectere)\n+ Added experimental pledge(2) and unveil(2) support for OpenBSD\n  builds. By default, this is enabled for the utils but not for\n  MZX. Additionally, config.sh now detects OpenBSD properly and\n  several warnings have been fixed.\n+ Fixed faulty '!' rule logic in match_function_counter and\n  changed 'key?' to 'key!' to prevent potential false matches.\n- Removed uthash as a build option.\n\n<a class=\"hL\" href=\"#NEWINVER.HLP__1st\">NEW in MegaZeux!</a>\n<a class=\"hL\" href=\"#2901CLOG.HLP__291\">New in Versions 2.90 to 2.91j</a>\n<a class=\"hL\" href=\"#284CLOG.HLP__284\">New in Versions 2.84 to 2.84c</a>\n<a class=\"hL\" href=\"#2823CLOG.HLP__283\">New in Versions 2.82 to 2.83</a>\n<a class=\"hL\" href=\"#281CLOG.HLP__281\">New in Versions 2.81 to 2.81h</a>\n<a class=\"hL\" href=\"#280CLOG.HLP__280\">New in Versions 2.80 to 2.80h</a>\n<a class=\"hL\" href=\"#OLDERVER.HLP__260\">New in Versions 2.60 to 2.70</a>\n<a class=\"hL\" href=\"#OLDESVER.HLP__201\">New in Versions 2.01 to 2.60</a>\n<a class=\"hL\" href=\"#NEWIN200.HLP__1st\">New in Version 2.00</a>\n<a class=\"hL\" href=\"#ANCENVER.HLP__1xx\">New in Versions Through 1.03</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"2901CLOG.HLP\">\n<a class=\"hA\" name=\"2901CLOG.HLP__291\"> </a>\n<p class=\"hC\"><span class=\"f9\">New in Versions 2.90 to 2.91j</span></p>\nFebruary 20th, 2019 - MZX 2.91j\n\nHere's a huge collection of bugfixes and a couple of features\nthat needed to be split off from 2.92.\n\nFeatures: every release has an HTML copy of the help file now,\nthe editor keeps separate board/overlay/vlayer buffers now, and\nanyone who still uses scrolls/sensors can view their info in the\ncounter debugger. The global volume settings use an exponential\ncurve now, range from 0 to 10, and have updated UI elements.\n\nFixes in this release include: the updater won't complain about\nmzx_help.fil anymore, the NDS and 3DS ports have keyboards that\nwork again, several bugs that would lead to worlds/saves with\n\"Robot # does not exist on board #\" errors have been fixed, and\nseveral crashes have been fixed.\n\nUSERS\n\n+ A complete HTML copy of the MegaZeux help file is now packaged\n  with releases for all platforms. It can be found in the docs/\n  folder.\n+ Nvidia and AMD switchable graphics drivers should now detect\n  MegaZeux and MZXRun properly on Windows.\n+ The editor now remembers the contents of the buffer when\n  switching between board, overlay, and vlayer editing. For\n  example, if a robot is selected on the board before switching\n  to overlay mode, the robot will still be in the buffer when\n  the user switches back to board mode.\n+ The editor now remembers the last filename for importing and\n  exporting MZM, MZB, CHR, PAL, and PALIDX files.\n+ Scrolls and sensors can now be viewed in the counter debugger.\n  They can be found by expanding the Board list (when present).\n+ Readded \"#version 110\" directives to all GLSL shaders, fixing\n  warnings on some Intel HD drivers. OpenGL ES builds should now\n  comment out these directives.\n+ Fixed bug where using Modify+Grab on built-ins could clear the\n  floor under the built-in.\n+ Fixed bug where key repeat wouldn't work for any text field in\n  the NDS/3DS/Wii ports. This also affected the robot editor in\n  Wii builds.\n+ Fixed NDS touch screen support. Touching the screen no longer\n  acts like escape and the keyboard properly works again.\n+ Fixed a bug where the NDS keyboard would drop keypresses on\n  games not running at speed 2.\n+ The counter debugger now correctly displays the value of\n  local0.\n+ Fixed a bug where the counter debugger would not reopen to\n  the correct robot local# value.\n+ MZX now attempts to retry removal of an old file when updating\n  if deletion fails initially. Error messages related to the\n  failed deletion of \"mzx_help.fil\" or \"assets/help.fil\" are now\n  suppressed as they are caused by a bug in older MZX versions.\n+ The help file is now closed before the updater restarts MZX.\n+ Fixed crash bug that could occur when removing old files after\n  an update.\n+ Improved manifest validity checks.\n+ Fixed board editor crash caused by invalid thing IDs.\n+ Player movement key repeat no longer resets when the player is\n  locked. It will reset for a direction only when the arrow key\n  corresponding to that direction is released. This fixes a bug\n  in Brotherhood where the player would move at a fraction of\n  the speed they were supposed to while swimming.\n+ Added version checking for legacy IF c?? Sprite behavior. In\n  versions 2.82b and prior, c?? would make this command ignore\n  the provided param and use SPR_NUM instead. This fixes Project\n  MoveZig.\n+ Fixed spelling error in missile param dialog.\n+ Switching boards in the editor now properly translates the new\n  board's mod name, fixing cases where board mods would restart\n  sometimes.\n+ Fixed a bug where the current board mod wouldn't play after\n  starting a listening mod, testing, returning to the editor,\n  and then disabling the listening mod.\n+ Replaced the linear volume setting curve with an exponential\n  curve to make lower volumes less loud. The volume settings can\n  now be set from 0 to 10 instead of from 1 to 8.\n+ The config file setting pc_speaker_on=0 no longer prevents\n  turning on PC speaker SFX at runtime.\n+ Added temporary workaround for numpad 5 key detection when\n  numlock is disabled.\n+ \"Forest to floor\" and \"Collect bombs\" are properly enabled\n  again for new boards by default. This fix doesn't alter config\n  settings or saved .editor.cnf defaults.\n+ The \"Downver. world (MZX)\" option in the \"Export as\" dialog\n  is now completely highlighted when selected. (Lancer-X)\n+ Starting MegaZeux with \"startup_editor=1\" no longer loads the\n  default palette over the startup world's palette.\n+ The move block action and undo/redo operations should now\n  preserve the element beneath the player.\n+ Fixed crashes caused by using block actions or COPY BLOCK on\n  the player while the player is standing on a sensor.\n+ Fixed several bugs where editor block actions and the commands\n  COPY/COPY BLOCK would not check for the player or clean up\n  robots/scrolls/sensors. Worlds/saves saved after these bugs\n  occured could display \"Robot # does not exist on board\"\n  errors when loaded.\n+ The DUPLICATE SELF x y and DUPLICATE SELF dir commands now\n  properly check for the player before attempting to duplicate\n  a robot. This fixes a bug where MZX could create saves with\n  \"Robot # does not exist on board\" errors.\n+ Fixed a crash that could occur when importing a new board.\n+ Fixed a bug where the 3DS onscreen keyboard would send key\n  releases instead of key presses.\n+ The 3DS onscreen keyboard now returns shifted chars when shift\n  is active.\n+ Fixed a bug where the libxmp implementation of MOD_ORDER and\n  JUMP MOD ORDER could not restart the current order.\n+ Fixed a crash with the libxmp implementation of MOD_POSITION\n  when providing an out-of-bounds value.\n+ Fixed a bug where Ctrl+[key] shortcuts would not work while a\n  dialog list element was active on the Wii.\n+ Fixed a bug where setting the board mod/charset/palette could\n  truncate the first letter of the path on the Wii.\n+ \"mask_midchars\" no longer masks the non-text char 127.\n+ The message box title and the command [ now properly display\n  the graphic for char 9 instead of a tab.\n+ Added newline escaping to robot, counter, and string names in\n  the counter debugger to work around formatting bugs.\n\nDEVELOPERS\n\n+ Added the hlp2html utility, which can be used to convert\n  WIPHelp.txt into web and printable HTML files.\n+ Updated to latest NDS ARM7 template.\n+ Updated SDL2 for Windows release builds to SDL 2.0.9.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nDecember 9th, 2018 - MZX 2.91i\n\nA small release with a bunch of fixes for bugs introduced mostly\nwithin the past few versions. Other highlights: joysticks should\nbe hot pluggable now and Alt shortcuts now also work with the\ncommand key in Mac builds.\n\nUSERS\n\n+ MZX now properly detects when joysticks are connected and\n  disconnected at runtime for platforms using SDL 2.\n+ Fixed bug where the robot editor would open with the cursor at\n  the end of the line instead of the start of the line.\n+ Fixed a bug where the robot box could display using MZX mode\n  but SMZX colors if opened on the first frame SMZX was enabled.\n+ The built-in text box title should display correctly again in\n  SMZX modes.\n+ Fixed regression where keys could trigger wrong \"keyN\" labels.\n+ Alt+F2 can now be used to open the settings dialog.\n+ Mac users can now optionally use command in place of alt.\n+ Improved editor cursor color selection for SMZX modes 2 and 3.\n\nDEVELOPERS\n\n+ Fixed make test for SDL 1.2 builds.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nNovember 14th, 2018 - MZX 2.91h\n\nA minor release primarily focused on improving counter/string\nlookups and fixing bugs in the robot editor and counter debug\nmenu.\n\nUSERS\n\n+ auto_glsl correctly switches to glsl on startup now.\n+ Fix bug where \"(editor)\" wouldn't always display in the\n  caption when editing.\n+ The single line macro dialog now allows the full size of the\n  single line macros to be edited. This fixes a crash caused by\n  having a macro configured to be longer than the edit box could\n  display.\n+ Setting a counter to a string no longer duplicates the entire\n  string in memory.\n+ Fixed faulty BOARD_X and BOARD_Y bounding for values greater\n  than the board width or height. Fixes potential crashes with\n  BOARD_CHAR, BOARD_COLOR, BOARD_ID, and BOARD_PARAM.\n+ The value of SPR_YORDER is now correctly displayed in the\n  counter debug menu.\n+ Improved the performance of the counter debug search.\n+ Fixed cosmetic counter debug menu issue where the the \"String\"\n  tree appeared to be expandable/collapsable when empty.\n+ Fixed faulty substring searching algorithm used in the counter\n  debug menu search and for breakpoints.\n+ Added \"Search\" and \"Cancel\" buttons to the counter debug menu\n  search so it can be used with the mouse.\n+ When testing from the editor, if \"__test.mzx\" exists MegaZeux\n  will now choose a numbered name for this world backup (e.g.\n  \"__test2.mzx\") instead of potentially overwriting work.\n+ Fixed bug where six-digit line numbers would display in the\n  robot editor incorrectly.\n+ Inserting text in the robot editor using the F2/F3/F4/F5\n  shortcuts can no longer exceed the line length limit, fixing\n  related crashes.\n+ Using F5 in the robot editor generates decimal numbers instead\n  of hex numbers now.\n+ Fixed robot editor bug where pressing delete at the end of a\n  line wouldn't join lines unless the current line was empty.\n+ Restored the ability to use the F7/F8 cheats outside of the\n  editor. This feature was broken in 2.91g. To enable cheats\n  outside of the editor, the config option \"allow_cheats\" must\n  now be set to \"mzxrun\" or 1. The former will enable them for\n  MZXRun only (same as from 2.82b to 2.91f) and the latter will\n  enable them for both executables (same as the -t flag in DOS\n  versions).\n+ Fixed bounding bug on the \"board_default_width\" and\n  \"board_default_height\" config file options.\n+ Separated robot editor \"Search and Replace\" dialog into a\n  \"Search\" dialog and a \"Replace\" dialog. Both dialogs are\n  smaller in size than the original. The order of the elements\n  in these dialogs has also been altered to be more user-\n  friendly. The hotkey for the new replace dialog is Ctrl+H.\n+ Fixed Ctrl+R repeating for the \"Replace All\" robot editor\n  search operation. Previously, it would act like \"Replace\".\n+ Canceling the robot editor search/replace menus no longer\n  disables the repeat feature.\n+ Searching in the robot editor now properly preserves the case\n  of the search string if case sensitive search is disabled.\n+ Fixed bug where toggling the robot debugger position wouldn't\n  take effect immediately.\n+ Exiting the editor with the \"startup_editor\" config setting\n  enabled should no longer inappropriately close MZX.\n+ Selecting \"tile\" for the char editor import mode works again.\n+ The bounds for the main editor charset import/export offsets\n  and size have been increased to allow for extended charset\n  values.\n+ Fixed the \"KEY?\" labels for keys that no longer have a unicode\n  representation in SDL2.\n+ Fixed SET \"$string\" \"FWRITE#\" bug where, if # exceeded the\n  length of $string, the output would be clipped to the wrong\n  length.\n+ The missile color ID char is now correctly set back to its\n  default value when a new world is created from the editor.\n\nDEVELOPERS\n\n+ Cleaned up the counter debug menu code.\n+ Switched from uthash to a modified version of khash for\n  counter and string lookups. Counters/strings now consume less\n  memory and counter/string lookups should perform slightly\n  better on most platforms.\n+ Replaced toupper/tolower in memcasecmp and substring searching\n  with a lookup table-based implementation of tolower.\n+ Refactored the following contexts to use the main loop: robot\n  editor, intake, thing menu. For compatibility purposes the old\n  intake still exists, but all robot editor hacks have been\nremoved from it.\n  \n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nOctober 7th, 2018 - MZX 2.91g\n\nThis bugfix release fixes various minor-to-moderate bugs in\nvarious areas of MegaZeux. The title/game update code has gone\nthrough a fairly major overhaul and several parts of MegaZeux\nhave been combined to use the same loop. The conversion of\nMegaZeux's interfaces to be compatible with this loop is an\nongoing process expected to occur over several versions.\n\nUSERS\n\n+ Added \"allow_screenshots\" config file option. Setting this\n  option to 0 will disable the built-in screenshot feature.\n+ The F12 key can now be detected by Robotic.\n+ Loading saves from the title screen no longer resets TIME.\n+ Loading saves from the title screen no longer erroneously\n  changes the player restart position.\n+ Swapping worlds now sets the correct TIME and player restart\n  position values.\n+ Fixed a bug where the fade out effect from COLOR FADE OUT\n  would be reset after any Robotic dialog.\n+ Undo for overlay and vlayer mouse drawing now works properly.\n+ Mouse drawing in the editor now draws smooth lines instead of\n  lines with gaps.\n+ Fixed move block bug when the source and destination both\n  overlapped the player.\n+ Palette editor component entry now accepts most of the same\n  inputs as dialog number boxes.\n+ The board name in the caption now updates to reflect the\n  current board while testing.\n+ Fixed bug where TIME would either decrement normally or not\n  decrement at all while the slow time effect was active.\n+ Added compatibility for pre-port endgame teleport behavior.\n  In DOS versions of MZX, the endgame teleport would disable\n  itself after being triggered for the first time and any\n  following endgame would instead trigger a game over.\n+ Reduced stutter in NDS and 3DS main screen scrolling.\n+ Fixed a bug where creating a string MZM would not correctly\n  set the string's length for preexisting strings.\n+ Fixed a bug where loading robot source code from a string\n  wouldn't work correctly with the robot debugger.\n+ The help system is now accessible from the main menu and game\n  menu.\n+ The settings screen is now accessible from the main menu, game\n  menu, editor, and palette editor.\n+ The shortcut Ctrl+F2 can be used to open the settings screen\n  from anywhere the settings screen is accessible. This shortcut\n  works even if F2 is used for a different feature and will also\n  ignore the value of the F2_MENU counter.\n+ Fix a crash that would occur decrypting a world with a\n  password exactly 15 chars long.\n+ The label for the INPUT STRING command should no longer be\n  able to overflow outside of the window.\n+ Fix a crash that would occur attempting to open the help file\n  from the world decryption confirm dialog.\n+ Fix bug where saved layer MZMs and board MZMs without robots\n  would have useless extra data at the end of the file.\n+ Scroll contents properly display game colors during gameplay.\n+ An error message is now displayed for failed board exports.\n+ Enabled writing zip data descriptors on the 3DS to decrease\n  saving time.\n- The \"password protected\" error message has been removed as it\n  was redundant with the confirmation dialog following it.\n- The \"disassemble_extras\" and \"disassemble_base\" options no\n  longer affect Robotic output during gameplay. SAVE_ROBOT will\n  always output extra words and base 10 numbers regardless of\n  user configuration.\n\nDEVELOPERS\n\n+ Implemented a main event loop to replace the various separate\n  event loops scattered around MegaZeux. See core.h and core.c\n  for more info.\n+ Refactored the following contexts to use the main loop:\n  titlescreen, gameplay, main menu, game menu, editor, palette\n  editor.\n+ When enabled, the debug FPS display will now update from any\n  interface using the main loop. If fullscreen mode is active,\n  the FPS display will now appear in the top-left corner of the\n  screen for any main loop interface aside from the editor.\n+ The --disable-screenshots config.sh option can be used to\n  disable screenshot support. This allows platforms that don't\n  use render_layer.c to stop building and linking it.\n+ Cleaned up m_show()/m_hide() overuse.\n+ Replaced all uses of \"bool\" with \"boolean\" to avoid potential\n  C/C++ compatibility issues.\n- Removed most old/unused source code in contrib/unzip, src/old,\nand src/vfs.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nSeptember 17th, 2018 - MZX 2.91f\n\nA bugfix release focusing primarily on renderers, the 3DS and\nWii ports, and the Makefile build system. Highlights: the 3DS\nport should perform better now, the Wii port's GX renderer has\nbeen restored, the 'darwin' platform has more Unix-like options\nnow, and several crashes have been fixed.\n\nUSERS\n\n+ The config setting 'audio_buffer' can also be specified with\n  'audio_buffer_samples' now. The default value for this setting\n  is now 1024 (instead of 4096).\n+ Fixed a crash that could occur in the counter debugger.\n+ Fixed a crash that occurs when attempting to add a watchpoint\n  for robot local counters, loopcount, etc. Using these counters\n  as watchpoints will watch the global robot's instances of\n  these variables (for other robots, use e.g. r#.local1).\n+ Using SMZX_INDICES or LOAD_ROBOT on an unset string no longer\n  causes a crash.\n+ png2smzx is now properly bundled with Linux builds.\n+ Improved the performance of loading partial charsets for the\n  glsl, opengl2, and 3DS renderers. Other renderers were not\n  affected by this issue.\n+ Fix opengl2 unbound sprite regression introduced in 2.91e.\n+ The opengl2 renderer now correctly draws unbound sprites\n  containing chars with a transparent foreground color.\n+ 3DS renderer optimizations. (asie)\n+ 3DS screen focusing now mimics the NDS port's behavior. (asie)\n+ Fixed bug where games could fail to open files on some\n  platforms if the paths contained duplicate slashes. (asie)\n+ Added a Wii software layer renderer. This renderer can be\n  selected with \"video_output = xfb\" in the config file.\n+ Wii GX renderer optimizations.\n+ Added layer rendering support to the GX renderer.\n+ The Wii GX renderer now uses the gl_vsync config option to\n  toggle vsync. For the Wii, this feature is on by default.\n  Disabling it may increase the framerate of some games but may\n  also cause other problems.\n+ The loading bar on console platforms now redraws the screen\n  less often.\n\nDEVELOPERS\n\n+ GAMESDIR/BINDIR/SHAREDIR now properly apply PREFIX when they\n  are not explicitly set by config.sh.\n+ LIBDIR is now user-definable like BINDIR et al. Variations\n  such as \"lib64\" currently must be explicitly provided for\n  platforms that still use them.\n+ Remove src folder from CFLAGS to fix a bug where system\n  includes could be mistaken for MegaZeux headers. All includes\n  of MegaZeux headers must be done using relative paths now.\n+ Fixed include bugs for X11 and Carbon clipboard handlers.\n+ Makefile now attempts to respect the prefix when running\n  sdl-config, sdl2-config, and libpng-config.\n+ Renamed the \"darwin\" config.sh platform to \"darwin-dist\".\n+ Added \"darwin\" and \"darwin-devel\" config.sh platforms. These\n  act like Mac OS X versions of the \"unix\" and \"unix-devel\"\n  options.\n+ The darwin-dist build system and instructions are now somewhat\n  more clear and robust.\n+ Fixed a bug where zlib wouldn't necessarily be linked to\n  png2smzx.\n+ Renderers will now use render_layer to draw the text_video\n  fallback if no render_graph function is present.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nSeptember 3rd, 2018 - MZX 2.91e\n\nBugfix release fixing some crashes, port bugs, and some other\nsignificant issues affecting a variety of things.\n\nUSERS\n\n+ Fixed bug where loading a board charset would also clear all\n  of the extended charsets.\n+ Fixed bug where loading a default charset in the editor would\n  also clear all of the extended charsets.\n+ Loading a 2.91 world when a renderer with no layer support is\n  active no longer triggers an error message.\n+ Fixed a memory corruption bug in the non-SDL Wii port caused\n  by faulty threading code.\n+ Fixed crash that could occur when updating a board undo frame.\n+ Fixed crash when changing to a board with overlay disabled in\n  overlay editing mode.\n+ Fixed a bug where the protected charset could get cleared for\n  renderers without layer rendering support.\n+ Enabled C99-compliant stdio functions for mingw, fixing at\n  least one crash bug and possibly improving performance.\n+ The NDS port now attempts to detect argv[0].\n+ The 3DS port now attempts to detect argv[0] and otherwise will\n  start in /3ds/megazeux instead of /.\n+ The 3DS port .3dsx file is now located in /3ds/megazeux.\n+ The MOVE PLAYER [dir] and MOVE PLAYER [dir] [label] commands\n  now update the commands cycle and commands total values.\n+ Fixed a bug where MZX and checkres wouldn't accept some\n  DEFLATE-related ZIP flags.\n+ checkres no longer crashes on failing to open a resource zip.\n+ The window caption now updates correctly when using either of\n  the overlay renderers.\n+ Update checking will now display an error instead of silently\n  hiding the updater when the updater fails to initialize.\n+ Fixed an issue where UIs would execute redundant frames.\n+ Fixed a bug where string comparison would not order strings of\n  different lengths correctly. (GreaseMonkey)\n\nDEVELOPERS\n\n+ Code style has been cleaned up in numerous files.\n+ Moved GLSL shaders from assets/shaders/ to assets/glsl/.\n+ Added missing GPL headers to GLSL shaders and added slightly\n  better documentation to them.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJuly 12th, 2018 - MZX 2.91d\n\nThis is a minor bugfix release to get a handful of fixes out\nprimarily concerning the updater. The NDS/3DS/PSP/Wii ports now\ninclude a copy of Caverns of Zeux.\n\nUSERS\n\n+ Added timeouts to updater network operations.\n+ Fixed bug where the updater would retry downloads without\n  regard to the status code.\n+ The updater is now force-disabled for all non-Windows\n  platforms in config.sh.\n+ Fixed bug where breakpoints could trigger on the wrong line.\n+ Amended VOLUME/MOD FADE # # bounding to clamp values in port\n  versions. DOS versions will still wrap between 0 and 255.\n+ Added DOS compatibility fix for sprN_off.\n+ Window caption is now properly updated when loading a world\n  from an unedited world in the editor.\n+ Fixed bug where \"..\" would change to the current directory if\n  the current path ended in a path separator.\n- Removed undocumented support for \"title.cnf\", \"game.cnf\", and\n  \"editor.cnf\" config files, which would have been loaded from\n  the same directory as the main config (not to be confused with\n  world-specific config files, which are still supported).\n\nDEVELOPERS\n\n+ The MSYS2 buildscripts have been overhauled to work with the\n  new devkitPro pacman repositories.\n+ Added config options to select vorbis, tremor, tremor-lowmem,\n  or to disable ogg vorbis file support. The 3DS and Wii ports\n  now use tremor instead of tremor-lowmem.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nMarch 4th, 2018 - MZX 2.91c\n\nThis release contains an assortment of bugfixes ranging from\nplayer clone fixes to obscure compatibility patches. A handful\nof updater issues have been fixed (or at least addressed).\n\nMegaZeux can now automatically check for updates on startup on\nWindows platforms. This behavior can be configured, and by\ndefault will simply leave a message in the window caption.\n\nUSERS\n\n+ MegaZeux can now automatically run the updater on startup\n  on applicable platforms. This can be configured with the\n  'update_auto_check' config file option.\n+ Fixed a bug where several .MOD variants stopped working with\n  libxmp enabled.\n+ Sound effects in subdirectories can now be used in the SFX\n  editor.\n+ Fixed a bug where checkres would fail on legacy worlds with\n  very long custom SFX.\n+ Checkres no longer ignores the SFX tables of 2.90+ worlds.\n+ Fixed a bug where interpolated expressions containing ternary\n  operators could terminate counter names early.\n+ Fixed faulty IF ANY compatibility behavior.\n+ Fixed a bug where FWRITE functions could change the case of\n  user-defined filenames when creating new files on non-Windows\n  platforms.\n+ SMZX mode 1 palettes are now exported correctly as 16 colors\n  instead of as 256 colors.\n+ Fixed subtly inconsistent timing in certain built-ins.\n+ Fixed bug where wind and transporters could clone the player.\n+ MegaZeux now restarts correctly in paths containing spaces\n  when the updater is run.\n+ Subroutines in pre-port MZX worlds now always return to the\n  command after the subroutine was sent.\n+ Typed color components now wrap from the maximum value to zero\n  like dialog number inputs, and pressing minus will now negate\n  the input number.\n+ Copying chars in the char editor between regular and SMZX\n  screen modes now works properly.\n+ Fixed a crash that could occur when placing things on a vlayer\n  larger than the current board.\n+ Sharks and SpittingTigers with firing rates greater than 4 now\n  retain their firing rate when edited.\n+ Fixed a bug where world decryption could crash. (Revvy)\n+ If the glsl renderer is selected by default, the detection of\n  a software opengl driver will result in falling back to\n  software. (Lancer-X)\n+ Fixed a bug in the GLSL renderer where an additional window is\n  created if the OpenGL version is low enough to cause a\n  fallback to software. (Lancer-X)\n\nDEVELOPERS\n\n+ Added .MZX-based unit testing system. Run with \"make test\".\n  See testworlds/README.md for more information.\n+ The default renderer is now auto_glsl, which turns into glsl\n  if not using a blacklisted opengl driver. (Lancer-X)\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJanuary 6, 2018 - MZX 2.91b\n\nHere's a fairly significant update for MegaZeux 2.91, fixing\nseveral major Robotic, audio, and editor related bugs, as well\nas issues that mostly affected Linux platforms. Also of note is\nthe updated MSVC and Xcode support by Spectere.\n\nUSERS\n\n+ Added the config file option \"editor_thing_menu_places\". When\n  set to 0, selecting an object from the thing menus (F3-F10)\n  will place the object in the buffer but not on the board.\n  Defaults to 1.\n+ Fixed a bug where MZMs could sometimes fail to load in the\n  editor.\n+ MZMs loaded in the editor now behave correctly when undone.\n+ Fixed bug where GOOP_WALK wouldn't always be 0 when loading\n  old worlds.\n+ Values of GOOP_WALK greater than 1 no longer cause issues.\n+ Board time limits greater than 255 are no longer truncated\n  when saving worlds/saves.\n+ Fixed bug where \"robot not found\" errors could result from\n  using backspace on robots in the editor.\n+ Fixed backwards string inequality evaluation.\n+ String inequality compares are now endian-safe.\n+ Fixed a bug where certain mods (e.g. FR_TOWER.MOD, Neve.s3m)\n  could repeat to the wrong order after reaching the end.\n+ Fixed a bug where certain .it channels could partially ignore\n  the volume command.\n+ Setting mod_position in libxmp now works for positions in the\n  middle of orders (NoSuck).\n\nDEVELOPERS\n\n+ Updated MSVC for Visual Studio 2017. (Spectere)\n+ Added xcode support. (Spectere)\n+ \"make clean\" now deletes ccv. (pgimeno)\n+ Fixed creeping CRLF usage. (Lachesis, pgimeno, Spectere)\n+ Fixed a bug where the MSYS2 build scripts could inadvertently\ninclude non-portable SDL2 binaries in Windows builds.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nNovember 22, 2017 - MZX 2.91\n\nThis release introduces mostly new editor features, but also a\na few Robotic features to note. It also fixes over 30 bugs.\n\nMegaZeux now features a palette editor for SMZX modes 2 and 3,\nand worlds can be saved and loaded in SMZX mode. The char editor\nhas been changed to allow preview palettes (mostly to aid with\nSMZX editing), and can now access the extended character sets.\nThe new vlayer editor allows the vlayer to be edited directly.\n\nOn the Robotic side of things, LOAD_COUNTERS actually works now,\nSMZX mode 3 indices can be loaded from a file, the message line\ncan be configured to use normal MZX mode in SMZX modes, and new\nstring features such as simple wildcard matching and negative\noffsets have been added. String support has been improved to\ndisallow invalid or nonsense string splices that previously had\nundefined behavior.\n\nAdditionally, asie's new Nintendo 3DS port has been merged into\nMZX, and optional SDL support has been added for the Wii.\n\nFEATURES\n\n+ Added SMZX_INDICES special counter to load SMZX indices. This\n  counter works with either a filename or a string, e.g.\n  set \"file.palidx\" \"SMZX_INDICES\", and will do nothing outside\n  of mode 3.\n+ Added MOD_LENGTH counter. The value of this counter is length\n  of the current playing music in rows for modules, or in\n  samples for PCM audio.\n+ Added MAX_SAMPLES counter. When set, MegaZeux will limit the\n  maximum number of samples that will play simultaneously. Set\n  to -1 to disable the sample limit.\n+ Added 'max_simultaneous_samples' config file option. This\n  behaves the same as the MAX_SAMPLES counter, but applies\n  globally.\n+ Added SMZX_MESSAGE counter. When set to 0 with SMZX active,\n  messages will display in normal MZX mode instead of SMZX\n  using the first 16 colors of the SMZX palette.\n+ Added 'random_seed#' counters to read and write the random\n  seed, 32 bits at a time (random_seed0 controls the low 32 bits\n  and random_seed1 controls the high 32 bits. (Lancer-X)\n+ Added a case-sensitive string equality operator:\n  IF \"$string\" === \"ABC\" then \"label\".\n+ Added wildcard-matching string equality operators:\n  IF \"$string\" ?= \"a?%\" then \"label\"\n  IF \"$string\" ?== \"a?%\" then \"label\" (case-sensitive).\n  The character '?' will match exactly one of any character in\n  $string, and '%' will match any number of any character in\n  $string (including no characters).\n+ Added negative indexing for strings, e.g. SET \"$string.-X\" 32.\n  This manipulates the string at the Xth character starting from\n  the end of the string (with -1 for the final character).\n+ Added negative offsets for strings, e.g. SET \"$string+-X\" \"a\".\n+ Added multiple character indexing for strings. When a length\n  is provided with a string index, e.g. SET \"$string.X#Y\" 12345,\n  the characters at positions X, X+1, ..., X+Y-1 will be treated\n  as a single 8*Y bit number. This works for values of Y between\n  1 and 4, with 4 characters providing the same functionality as\n  a counter.\n+ The palette editor and character editor are now accessible\n  while testing. Press Alt+E or Alt+C respectively to access\n  them from the counter debugger.\n+ Extended the palette editor to SMZX modes 2 and 3. The updated\n  palette editor allows the editing of all 256 SMZX mode 2/3\n  colors and editing color indices for SMZX 3.\n+ Component numbers can now be typed in the palette editor by\n  clicking the component name or its number, e.g. clicking \"Red\"\n  allows you to type a red value.\n+ SMZX indices can now be imported/exported while editing in\n  mode 3 using import/export palette.\n+ Added a new vlayer editor. In the world editor, press Alt+V to\n  switch to the vlayer editor. The vlayer is something like an\n  invisible global overlay that can be used to store and\n  retrieve graphical data through Robotic. See the editor help\n  and the Robotic reference manual for more details.\n+ Added color selection to the character editor. Press C to\n  choose a color to preview and edit chars with, and press Alt+C\n  to revert back to the default grey.\n+ Added extended charset support to the char editor. Extended\n  charsets can be selected during char selection. Note that in\n  gameplay these chars can be accessed only by unbound sprites.\n+ Selected blocks of chars with a height greater than one\n  (before selecting a subdivision) are now properly supported.\n  Using -/+ with these selections will move in a tiled manner,\n  with no overlap between chars in \"tiles\". Charsets can also be\n  exported/imported using this tiling behavior.\n+ The -/+ keys now behave the same in the char selection screen\n  as they do in the char editor.\n+ The character editor help now covers several shortcuts that\n  were previously missing (Alt+B, Shift+Arrows, etc...).\n+ The undo/redo shortcuts in the character editor are now Ctrl+Z\n  for undo and Ctrl+Y for redo.\n+ Added undo/redo functionality to the world editor. Use Ctrl+Z\n  to undo changes to the board/overlay/vlayer and Ctrl+Y to\n  redo.\n+ The default editor undo history stack size has been extended\n  to 100 levels.\n+ The editor will now prompt the user to create a new starting\n  board when opened. If a new board is created, the global first\n  board will be set to the new board and the title board will be\n  renamed.\n+ Saved positions now have confirmation dialogs. Saved positions\n  are saved to/loaded from the editor.cnf file for each world.\n+ Pressing Enter/Return on the overlay (and vlayer) now acts the\n  same as on the board, changing both the buffer and the current\n  layer being edited.\n+ Pressing P on the overlay (and vlayer) now acts the same as on\n  the board, changing the character in the buffer but NOT on the\n  current layer. This is equivalent to the original overlay\n  behavior for Enter.\n+ Block tiling movement (Ctrl+Arrows) now works with most block\n  actions, and does not require an initial block placement to\n  activate.\n+ Restoring its DOS functionality, Alt+D now toggles the default\n  colors of built-in types in the editor. When disabled,\n  built-ins placed from thing menus will use the buffer color\n  instead of their default colors, and a red dot will appear on\n  the right side of the status bar.\n+ Color selection now supports typing in the hex value of a\n  color/subpalette to select the given color/subpalette. For\n  example, typing \"4c\" will select background color 4 and\n  foreground color 12.\n+ Readded the downver utility.\n- Removed the undocumented Shift+F7 shortcut in the editor. This\n  shortcut was redundant with F11.\n\nFIXES\n\n+ Fixed a bug that would cause Linux binaries to fail to find\n  most resources.\n+ Fixed a bug where loading a save from the titlescreen, exiting\n  gameplay to a world that doesn't exist, and then loading a\n  second save would corrupt the counter and string hash tables.\n+ Fixed crash that would occur when loading a saved position on\n  the same board referring to out-of-bounds coordinates.\n+ Fixed a freeze that could occur opening the counter debugger\n  to an empty list or searching for a counter in an empty list.\n+ LOAD_COUNTERS now works as intended.\n+ Fixed a bug where key repeat would be prematurely terminated\n  when releasing a key while holding another key.\n+ Attempting to open the robot validator with no errors will no\n  longer crash MegaZeux.\n+ Backspace now mirrors delete in overlay mode instead of\n  affecting the board.\n+ Moving an overlay block partially out of bounds in the editor\n  will now correctly clear the block's original position.\n+ The color selector will now correctly display SMZX palettes in\n  SMZX mode instead of the default char 254.\n+ Fixed bug where the mouse cursor would vanish after using the\n  robot debugger configuration dialog in the editor.\n+ Fixed a bug where the counter debugger could sometimes display\n  behind the robot debugger after exiting the counter debugger.\n+ Undo in the char editor now works correctly with the mouse.\n+ The char editor no longer forces the screen to SMZX mode 1\n  when editing in SMZX modes 2 or 3.\n+ Fixed screen corruption bug when resizing the char editor in\n  SMZX modes.\n+ Fixed a bug where multichar editing wouldn't correctly wrap to\n  the start of the charset.\n+ The editing area outside of the current board is now correctly\n  drawn when SMZX is enabled.\n+ The viewport is now correctly drawn when SMZX is enabled and\n  the UI or unbound sprites are active.\n+ The editor bar no longer appears while SMZX is enabled in view\n  mode.\n+ Messages are now drawn correctly in SMZX mode.\n+ Fixed bug where centering the viewport could change the width\n  and height of the viewport.\n+ Unbound sprites are now clipped correctly against the viewport\n  when the viewport origin is not 0,0. (Lancer-X)\n+ The VLAYER_SIZE counter now clears new vlayer area added when\n  it is increased. This applies only to 2.91+ worlds.\n+ The VLAYER_WIDTH and VLAYER_HEIGHT counters now preserve data\n  on the vlayer when set. This applies only to 2.91+ worlds.\n+ Fixed several bugs with string length and offset parsing that\n  would allow invalid string accesses to work in certain cases.\n+ COPY BLOCK $string now works correctly with string splices.\n+ Fixed a bug where saving and loading 2.90X files wouldn't work\n  on the Nintendo DS.\n+ Fixed a bug where mouse clicks would carry through from the UI\n  into games.\n+ Invalid gl_scaling_shader values should no longer appear as\n  the current loaded scaler.\n+ MegaZeux now falls back to the default scaling shader when a\n  selected scaling shader fails to compile.\n+ Blank sprite chars are now drawn when ccheck is 3. (Lancer-X)\n+ The sprite color is used with ccheck 3 collisions. (Lancer-X)\n+ When spr_yorder is enabled, sprites with the same (sprN_y +\n  sprN_cy) value are now consistently ordered by their sprite\n  numbers.\n+ The collision rectangles of ccheck 3 sprites now constrained\n  to the sprite dimensions (Lancer-X).\n+ MZX no longer crashes when a line of length 512 characters or\n  longer is pasted into the robot editor.\n+ Fixed a crash bug when trying to swap a sprite with an\n  invalid sprite index.\n\nDEVELOPERS\n\n+ Added Nintendo 3DS port. (asiekierka)\n+ Added \"make uninstall\" option to the Linux makefile.\n+ Added devkitPro portlibs paths to the NDS and Wii Makefiles.\n- Removed the depackers and non-MegaZeux formats from libxmp.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nSeptember 4, 2017 - MZX 2.90d\n\nHere's another bugfix release. This is mostly assorted small\nfixes, but there are also a couple of major fixes here: first,\nthe ternary operator now works correctly when nested both with\nand without expressions, and second, sprN_setview does not break\nin conjunction with certain unbound sprites. Additionally, SAMs\nwill not be converted to WAV files anymore (and are now natively\nsupported) and the robot debugger config is accessible from the\neditor.\n\nFEATURES\n\n+ The current MegaZeux version is now visible from the enter\n  menu on the title screen. This is to assist identifying the\n  version on platforms that don't respect window title changes\n  or have no window border.\n+ Updated checkres.bat and checkres documentation.\n+ The robot debugger configuration screen is now accessible from\n  the editor via Alt+F11.\n+ Removed SAM to WAV converter. MegaZeux now has native SAM\n  support. (asiekierka)\n+ Added crt-wave.frag, updated crt.frag. (astral)\n\nFIXES\n\n+ Fixed a bug where MegaZeux would crash when editing a robot\n  with an invalid IF command operator.\n+ Fixed a bug where the ternary operator would fail to find the\n  correct colon when the middle term contained a second ternary\n  operator in a nested expression.\n+ Nested ternary operators should now behave as expected.\n+ Fixed a bug where the built-in cursor would disappear after\n  exiting testing with EXIT_GAME.\n+ Fixed a bug where the listening mod would not restart after\n  exiting testing.\n+ Fixed a bug where the robot debugger would not wrap long lines\n  of Robotic code correctly.\n+ Attempting to save a world in a write-protected location\n  correctly displays an error message again.\n+ Fixed a bug where key repeat would not work when scrolling\n  through the counter debugger tree list.\n+ checkres will no longer report empty filenames as dependencies\n  e.g. SET \"\" TO \"FWRITE_OPEN\".\n+ The -q flag in checkres will now correctly output filenames\n  instead of nothing.\n+ Fixed a bug where sprN_setview would scroll the viewport to\n  the wrong side of the screen if the sprite is unbound and is\n  close to the top or left edge of the board. (Lancer-X)\n\nDEVELOPERS\n\n+ Fixed PSP and NDS ports. (asiekierka)\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJuly 25, 2017 - MZX 2.90c\n\nThis is a small fix release mostly cleaning up issues from the\nprevious release.\n\nNotable fixes include a crash on certain instances of string\ninterpolation into counters/labels/text. Playing mods as sound\neffects works again, and the audio_sample_rate config file\noption now works correctly.\n\nThis release also includes support for loading save files from\nMegaZeux 2.84X and experimental editor behavior for handling\nboard charsets and palettes.\n\nFEATURES\n\n+ Save files from 2.84X worlds can now be loaded.\n+ The editor can be configured to automatically load board\n  charsets and palettes now. Use the 'editor_load_board_assets'\n  config file option to enable this behavior. This behavior is\n  disabled by default. Note that this will OVERWRITE THE CURRENT\n  WORLD CHARSET AND PALETTE and your changes will NOT be saved\n  automatically.\n+ GLSL is now the default renderer. (Lancer-X)\n\nFIXES\n\n+ Fixed bug where --disable-modular builds would fail to link.\n+ Fixed bug with libxmp integration where mods could not be\n  played as sound effects.\n+ Fixed bug with libxmp integration where MZX would assume\n  audio_sample_rate was 44100, causing mods to play at incorrect\n  pitches with other rates.\n+ Fixed bug where editor config file options would be ignored at\n  the command line.\n+ Fixed bug where string interpolation into a label/counter name\ncould cause MZX to crash with very long strings.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJuly 16, 2017 - MZX 2.90b\n\nIt's been two weeks, so here's a bugfix release for MZX 2.90.\nThere isn't a whole lot that's new to talk about.\n\nFirstly, the palette editor has gone through an overhaul. This\nis mostly internal preparation for an eventual SMZX palette\neditor, but the new features include the partial restoration of\nmouse input (which was removed in 2.80X), the addition of color\nsliders, the ability to hide the palette editor help, and two\nnew colorspaces (HSL and CIELAB) to aid in the selection of\npalette colors.\n\nNext, the robot debugger introduced in 2.90 has new features.\nYou can now set \"watchpoints\" to watch the status of particular\nvariables, and you can send labels/goto from the robot debugger.\nThe robot debugger config screen has been visually improved, and\nline number breakpoints can be defined. Another notable feature\nis that KEY_PRESSED and KEY_CODE values are now displayed in the\ndebug window during gameplay.\n\nlibxmp is now the default sound engine for modules, fixing a\nlongstanding bug where certain S3Ms would have muted channels.\nGDM modules are now supported by MZX again. If you notice any\ninaccuracies with mod files, please report them to the tracker.\n\nFixed bugs include a major overhaul of the glsl shaders for\ncompatibility with almost any system, various sprite, text box,\nand input bugs, a crash fix when MZMs were saved from out of\nbounds board locations, and a bug where opening worlds or\ntesting from the editor could cause MegaZeux to exit.\n\nFEATURES\n\n+ The \"shaders/extra\" folder is now \"shaders/scalers\". The\n  default vertex shader is still located in the shaders folder.\n+ The default scaling shader can now be set in the config file\n  using the gl_scaling_shader option. If not defined, MegaZeux\n  will load assets/shaders/scalers/semisoft.frag.\n+ The robot debugger now supports monitoring the values of\n  counters and strings. Use the config menu/breakpoint editor to\n  add watchpoints; when the debugger detects a change in a\n  watchpoint counter/string AFTER a command has been executed,\n  the robot debugger will open.\n+ The robot debugger can now send robots labels. Use 'G' or\n  select 'Goto' in the robot debugger to use this feature. The\n  name and label inputs support expression parsing and string/\n  counter interpolation.\n+ The robot debugger can now use line numbers in breakpoints.\n+ The palette editor help can now be hidden with Alt+H. The\n  default behavior for this can be set in the config file. The\n  palette editor help is visible by default.\n+ The palette editor's mouse functionality has been improved.\n  Behavior when clicking the palette has been restored, and\n  component sliders have been added.\n+ The palette editor now supports alternate color spaces.\n+ The debug window now displays the last key_code (green) and\n  key_pressed (magenta) values detected during gameplay. These\n  values are located in the bottom right corner of the window.\n+ The default fullscreen resolution for scalable renderers is\n  now 1280,720. The default fullscreen resolution for 'software'\n  is still 640,480. The default aspect ratio is now 'modern'.\n+ The checkres utility has been updated to support 2.90X worlds,\n  worlds in subdirectories of zip archives, multiple input files\n  to test, and the ability to specify secondary directories and\n  zips to search (e.g. for games with separate music zips).\n\nBUGFIXES\n\n+ Fixed a bug where certain \"keyN\" labels, such as \"key$\", would\n  not work correctly.\n+ Integer comparisons between numbers greater than 2^31-1 apart\n  now work correctly. (Lancer-X)\n+ The palette editor now displays the SMZX mode 1 palette\n  correctly.\n+ Sprite collisions now correctly ignore the sprite's visual\n  width and height.\n+ Blank lines are no longer skipped when copy-pasting text into\n  the robot editor. (Lancer-X)\n+ Fixed a bug where key presses in dialogs could be detected in\n  games, and keypresses could still carry between other dialogs.\n+ [ message boxes now use the same screen mode as everything\n  else. Same with scrolls. (Lancer-X)\n+ spr#_setview now works with unbound sprites. It's pretty nasty\n  as the viewport can't scroll with pixel precision, but the new\n  behavior should still be better than the old. (Lancer-X)\n+ Fixed a bug affecting certain Intel HD Graphics cards and the\n  GLSL renderer that appears to have been around since that\n  renderer was first introduced. (Lancer-X)\n+ Fixed a bug where the numeric numpad keys were inappropriately\n  translated to regular keys based on numlock when they were\n  read from KEY_PRESSED. This behavior is now locked to worlds\n  from 2.82X through 2.84X.\n+ Fixed a bug where warping the mouse on one axis would snap the\n  mouse to the nearest pixel on the other axis, causing problems\n  for upscaled windows.\n+ Fixed a bug where mouse control would not respect video_ratio\n  settings aside from \"stretch\".\n* Fixed a bug where MZX would attempt to load certain invalid\n  worlds that should have been caught by validation.\n+ Copy block to MZM no longer crashes when the MZM is partially\n  overlapping the board edge. (Lancer-X)\n+ Fixed bug where MegaZeux would sometimes exit immediately on\n  loading a game or would immediately exit testing.\n\nDEVELOPERS\n\n+ libxmp is now the default module sound engine. Use \"make xmp\"\n  to make and install libxmp if it is not available on your\n  platform.\n+ Mac OS X portability improved. (Spectere and Why-Fi)\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJune 29, 2017 - MZX 2.90\n\nHey, it's been a while! Didn't mean to keep everyone waiting.\nTo make up for it as much as possible, we've prepared an extra\nspecial release of MegaZeux here for you. Yeah, you!\n\nI don't even know where to start with this release, as there's\nsimply a lot to talk about. MZX's world formats and rendering\narchitecture have gone through major overhauls, and a multitude\nof bugs have been fixed (60+). Particularly the MZX UI graphical\nglitch that occurred when an SMZX mode was enabled.\n\nSome notable new features are unbound sprites (MZX's new native\npixel precision support), loading character sets/palettes/source\ncode/MZMs from strings, saving MZMs to strings, a new Robotic\ndebugger, the SAVE_COUNTERS and LOAD_COUNTERS function counters,\noptions to help create standalone game releases, the ability to\nassign character sets/palettes to a board to load on entry, and\nresetting boards on entry. The size limit of Robotic bytecode\nhas been increased to 2MB.\n\nOther important mentions: MegaZeux has been updated to use\nSDL 2.0 and libmodplug 0.8.9.0. The SAVE_WORLD function counter\nhas been permanently removed. The downver utility has been\ntemporarily discontinued, as adapting it to this release would\nhave been a considerable amount of work. Instead, there is a new\noption in the Export menu in the editor to export a 2.84 world.\n\nEnjoy, and try not to get overwhelmed by all of the new stuff!\n\nFEATURES\n\n+ Added a built-in robot debugger. See the Debug Mode section of\n  the help file for details.\n+ Added the COMMANDS_STOP special counter to MegaZeux. When a\n  robot executes a number of commands exceeding this value while\n  testing, the robot debugger will be enabled automatically and\n  will open. During regular gameplay, it will be ignored. The\n  default value of COMMANDS_STOP is 2000000.\n+ The counter debugger now remembers its previous position after\n  being reopened.\n+ Added commands_cycle and commands_total variables to the Robot\n  section of the counter debugger. See the Debug Mode section of\n  the help file for details.\n+ Certain types specified as 255 on the char ID table are now\n  treated as Custom* types when placed and selected in the\n  editor. If you switched a type to char ID 255 in a game that\n  already had that type, the parameters will not be changed\n  (and the types will still be char 0 if they were already\n  placed in the world). When making a type a Custom* type from\n  the global chars dialog, it will appear visually different in\n  the list and in the character selection window, and also ask\n  for confirmation.\n+ The enter menu can be closed with escape now.\n+ In the editor, the Backspace key now removes the top layer\n  and brings the under layer to the top, as a compliment to Del.\n  This does not affect the text entry behavior.\n+ Seeking in the String section of the counter debugger will now\n  ignore the $ prefix.\n+ Tentative joypad POV hat support added: joyNhat = U, D, L, R\n+ The BUTTONS counter has been extended to support the mouse\n  wheel and X1/X2 buttons.\n+ Strings can now be used in the LOAD PALETTE \"file\" command in\n  place of a file name, i.e. LOAD PALETTE \"$string+10\".\n+ Strings can now be used in the LOAD CHAR SET \"file\" command in\n  place of a file name, i.e. LOAD CHAR SET \"@240$chars\".\n+ Strings can now be used for the special counter LOAD_ROBOT in\n  place of a file name, i.e. set \"$str\" \"LOAD_ROBOT\".\n+ Strings can now be used for saving and loading MZMs in place\n  of a file name, i.e. put \"@$str\" image_file... (Lancer-X)\n+ Added ESCAPE_MENU counter. When set to 0, this counter will\n  prevent pressing escape from opening the exit gameplay menu.\n  This will not affect any other escape menus in MZX, and the\n  exit gameplay menu can still be opened by other means, such as\n  ALT+F4, CTRL+C, the window close button, etc.\n+ Added EXIT_GAME function counter. When set to anything but 0,\n  this will cause MegaZeux to exit to the title screen. This has\n  no effect on the title screen.\n+ Added the ternary operator (?:) to expressions. If the value\n  to the left of ? is not equal to zero, the expression between\n  the ? and the : will be evaluated. If the value to the left of\n  ? is equal to zero, the expression to the right of : will be\n  evaluated.\n+ Added SAVE_COUNTERS and LOAD_COUNTERS special counters. These\n  will save and load files containing every counter and string\n  from the world they're used in, respectively. These files are\n  not version checked, and may be used like save files.\n+ Robots now keep their robot IDs after reloading saves.\n+ Added standalone mode. The config parameters standalone_mode\n  and no_titlescreen can now be used (from MZXRun only) to\n  create standalone versions of MZX with the ability to fully\n  customise the player's experience. See config.txt for details.\n  (Lancer-X)\n+ The minimal help bar now displays robot memory and the current\n  board mod when contextually appropriate.\n+ The minimal help options for the board and robot editors are\n  now enabled by default.\n+ Shaders can now be changed from the F2 menu when using the\n  GLSL renderer. The user can select a fragment shader from the\n  assets/shaders/extra/ directory. If a matching vertex shader\n  exists, it will be loaded alongside the fragment shader.\n  Otherwise, MZX will fall back to the default vertex shader.\n+ On Linux and Mac OS X the configuration file will be copied\n  into the user's home directory and given the name\n  .megazeux-config if it is not already present. This config\n  file will then be used instead of the global one. (Lancer-X)\n+ Added unbound sprites. Sprites can be unbound from the grid by\n  setting spr#_unbound to 1. Their coordinates will now refer to\n  the sprite's location in pixels, not tiles. spr#_width,\n  height, refx and refy still refer to chars; however, spr#_x,\n  y, cx, cy, cwidth and cheight are all in pixels. Unbound\n  sprites do not work with all renderers; currently, out of the\n  renderers available on the PC platforms, the overlay2 renderer\n  lacks this functionality. (Lancer-X)\n+ Unbound sprites do not let char 32 or blank characters (when\n  in ccheck 2) through like regular sprites do. Instead, set\n  spr#_tcol to a color that will be transparent when the unbound\n  sprite is drawn. (Lancer-X)\n+ Unbound sprites can make use of additional hidden charsets.\n  There are an additional 14 charsets beyond the default that\n  can be modified through using load char set or char edit.\n  (e.g. load char set \"@256charset.chr\")\n  A sprite can be set to refer to these later chars with the\n  spr#_offset counter. The offset value is then added to each\n  char in the sprite. You can use this to refer to higher char\n  sets.\n  (e.g. set \"spr0_offset\" 256)\n  Offset values can also be set to locations within a char set.\n  (Lancer-X)\n+ To allow you to access the extended charsets, the @ option to\n  load char set now takes up to 4 digits, rather than merely 3.\n  The + option still only allows 2 hex digits. (Lancer-X)\n+ The colors used by tiles of a given color # in SMZX mode 3\n  can now be rebound using the \"smzx_idx#,#\" counters.\n  (e.g. set \"smzx_idx7,0\" 32)\n  Now if you put down a c07 tile, all 00 pixels will refer to\n  color 32 (as opposed to color 0 in SMZX mode 2 or color 7 in\n  SMZX mode 1). These values are reset whenever the SMZX mode is\n  changed. Once again, this is only available to renderers that\n  support unbound sprites. (Lancer-X)\n+ Edited worlds now retain their world version until resaved.\n  Before, they would lose their world version after testing.\n+ Added world version display to the debug window.\n+ Added GOOP_WALK counter for robots.\n+ Added \"Reset board on entry\" parameter for boards. When set,\n  the board will reset to its original state when the board is\n  entered during gameplay. This setting can be saved as a board\n  default.\n+ Added \"Load charset on entry\" parameter for boards. When set,\n  MegaZeux will load the selected charset file when the board\n  is entered during gameplay. This setting can be saved as a\n  board default.\n+ Added \"Load palette on entry\" parameter for boards. When set,\n  MegaZeux will load the selected palette file when the board\n  is entered during gameplay. This setting can be saved as a\n  board default.\n+ Maximum robot size has been increased from 64kb to 2mb.\n  (Lancer-X)\n\n+ Debytecode: the LOAD_SOURCE_FILE special counter can now be\n  used to load and compile a robot program from source code.\n  This can be used with either a file or a string as input.\n+ Debytecode: the robot editor's import robot menu now supports\n  loading legacy source code.\n\nBUGFIXES\n\n+ Fixed crash bug when using ALT+Z (Clear Board).\n+ Fixed crash bug when attempting to view the help file in the\n  updater.\n+ Fixed crash bug when attempting to use SMZX_R/G/B with out-of-\n  range indexes.\n+ Fixed crash on exit when the config file did not explicitly\n  specify update hosts.\n+ Fixed memory leak involving bounds-breaking FREADn calls.\n+ Fixed counter debugger issue where counters and strings\n  starting with chars over 'Z' would cause everything in their\n  section to be placed into the '#' list.\n+ Dialog window labels can not be put into focus by clicking\n  them with the mouse anymore.\n+ Dialog windows closed by pressing escape will now close only\n  on a new press instead of whenever the key is held.\n+ Load (F4)/quickload (F10) on invalid save files in-game leaves\n  the current game running instead of exiting to the titlescreen\n  or editor.\n+ Fixed issue where counter debugger would display wrong values\n  for the robot LOCAL counters.\n+ Fixed robot editor issue where global robot coords would be\n  reloaded as 65535.\n+ Fixed issue where board defaults would be reset after leaving\n  testing until the world was manually reloaded.\n+ Fixed issue where the Board Info dialog would not close when\n  'Cancel' was selected.\n+ Fixed bug where chars 254 and 255 would not be reverted back\n  to their defaults via F4/F5.\n+ Fixed bug where player would move onto goop when walking into\n  enemys on top of goop.\n+ Fixed bug where 'enter' would be detected in loaded save files\n  when loading from a title screen.\n+ Fixed issue where MZX was using the world file version instead\n  of the internal MZX version when saving MZMs and determining\n  whether it could load MZMs both within the editor and in game.\n+ Changed the caption behavior in the world editor as to refresh\n  only when board values are synchronized.\n+ Fixed bug where, when exiting gameplay, the world would keep\n  running on the title screen if MegaZeux lost read access to\n  the world file.\n+ Fixed minor bug where trying to load a nonexistant mod would\n  change MOD_NAME's output while the previous mod still played.\n+ Fixed a Windows-only bug, where attempting to load a Robotic\n  file in the Robot Editor or with LOAD_ROBOT would not load the\n  entire file if the file contained 0x1A (char 26).\n+ Fixed a bug where the lazer animation would fail to complete.\n+ Fixed bug where the VOLUME # and MOD FADE # # inputs could go\n  out of range, resulting in ear-piercing white noise. Inputs\n  now wrap between 0 and 255 for compatibility reasons.\n+ Fixed bug where certain unicode values could crash MZX in the\n  character selection dialog.\n+ Fixed bug where : \"keyN\" would not work on the Pandora.\n+ Fixed a bug where FREAD could create a string larger than the\n  maximum string size.\n+ In the sprite section of the counter debugger, the spr_clistN\n  values were incorrectly labelled as \"spr_collisionN\". Fixed.\n+ Fixed counter debugger crash when selecting certain strings\n  containing escaped values.\n+ Added compatibility for the cycle-ending SHOOT, SHOOTMISSILE,\n  SHOOTSEEKER, and SPITFIRE commands from MegaZeux 2.83.\n+ Fixed crash bug when attempting to use file counters with\n  paths equal to or longer than 512 chars in length. MegaZeux\n  will now ignore such paths.\n+ Fixed bug where MZX would crash with out-of-bounds joystick\n  key mappings.\n+ Fixed a bug where robots could not push the SliderNS type to\n  the south.\n+ Fixed a bug where a robot using #return or #top multiple times\n  within a single command could cause a crash.\n+ Fixed a port regression where IF ANY would continue to iterate\n  over the board after finding a match, potentially triggering\n  multiple subroutines, #return, or #top labels.\n+ Fixed a bug where MZM error messages could repeat indefinitely\n  and lock up MZX.\n+ Fixed a bug where the (#)PUSHED label would not be sent to the\n  first object in a row of pushed objects if that object was a\n  robot.\n+ Fixed support for PPC Linux builds. (Insidious)\n+ Fixed a bug where a robot sent a subroutine would restart the\n  command it was executing upon #return if the command was a\n  multi-cycle command. This fix applies to worlds saved in 2.90\n  and later only.\n+ Fixed a bug where ALT+F4 would cause menus to open instead of/\n  in addition to triggering an exit event.\n+ Fixed a bug where subdirectory mods with different types of\n  slashes in their names would fail to be recognized as the same\n  mod.\n+ Fixed a bug where SMZX palette intensities were not saved.\n+ Made the opengl2 and glsl renderers endian-correct. (Lancer-X)\n+ Fixed a bug where keystrokes were getting lost during event\n  processing. (Lancer-X)\n+ The UI now uses the regular MZX mode and the protected palette\n  when playing SMZX games. This is only available to renderers\n  that support unbound sprites. (Lancer-X)\n+ Changing from SMZX mode 1 to 0 correctly restores the palette.\n  (Lancer-X)\n+ Fixed a bug where the tree list in the counter debugger would\n  rapidly scroll through elements when clicked.\n+ Fixed a bug where the updater, on failing to receive data from\n  the server, would consume large amounts of CPU before timing\n  out. (Lancer-X)\n+ Fixed various bugs related to the pushing of sensors.\n  (Lancer-X)\n+ Fixed a bug where MZX would crash in some cases when testing\n  after adding/importing new boards into a world.\n+ Made KEY a board counter &lt;=2.70. Also added masking &lt;=2.62 to\n  make Oath Demo work again. (Lancer-X)\n+ Bullet types shot by robots clamped to 0-2. (Lancer-X)\n+ SAVE_GAME and SAVE_WORLD now happen at the end of a cycle, not\n  immediately. (Lancer-X)\n+ SAVE_WORLD no longer exists. (Lancer-X)\n+ Labels at the end of a program no longer get randomly sorted\n  first and stop the actual first instance of that label being\n  called. This fixes a bug in the Dark Corner 'Zane' demo.\n  (Lancer-X)\n+ Copy and copy block in pre-port MZX games now preserves the\n  state of any robots copied. (Lancer-X)\n+ Copying and pasting robot code with LF line endings on Windows\n  now works. (Lancer-X)\n+ MegaZeux can now be exited during an infinite loop. (Lancer-X)\n+ Fixed a bug where SAVE_GAME on the first cycle of a board\n  would cause the game to be faded out on load. This still\n  affects legacy worlds. (Lancer-X)\n+ Fixed a bug where trying to create new counters called \"fread\"\n  or \"fread_counter\" in the counter debugger would cause the\n  file being read to advance.\n+ Fixed a bug where the numpad was unable to be used to control\n  the player, despite being identical to the cursor keys for\n  every other situation. (Lancer-X)\n+ Fixed a bug where pre-port worlds could play any number of\n  concurrent samples. This makes portions of Bernard the Bard\n  quite cacophonous. (Lancer-X)\n+ Fixed a bug where per-game config files could set options they\n  weren't supposed to be able to.\n\nDEVELOPERS\n\n+ Fixed linking bug when attempting to make a modular build of\n  MegaZeux in Debian/armhf. -fPIC is now enabled for modular\n  builds on all platforms. (Insidious, ajs)\n+ Added SDL 2.0 support. At the moment, the overlay renderers\n  are not accelerated by SDL 2.0 and may be slower than their\n  SDL 1.2 counterparts. Other minor inconsistencies may exist\n  between the two versions. (ajs)\n+ Keycode help diagram replaced with an HTML file (from the\n  older PNG file). This will make future updates much easier.\n+ Updated NDS, Wii and PSP ports to latest devkitpro toolchains.\n+ Updated libmodplug to version 0.8.9.0. (asiekierka)\n+ Added experimental libxmp support. (asiekierka)\n+ Added experimental libopenmpt support. (asiekierka)\n+ New rendering architecture that mostly sits alongside the old\n  rendering architecture. There is a new function implemented by\n  supporting renderers: render_layer(). This provides a layer\n  in a similar form to the text_video array in graphics and\n  expects that to be drawn on the screen. This is called once\n  for each layer that is drawn- this means the board, overlay,\n  UI and sprites. Each layer can have a different SMZX mode,\n  although at the moment this is only used to allow the UI to\n  remain in regular MZX mode while the game or editor is in SMZX\n  mode. The render_layer() function is only called if it is\n  implemented by the renderer and if there is something on the\n  screen that can only be drawn by the renderer (e.g. unbound\n  sprites, or UI elements in an SMZX game) (Lancer-X)\n+ LibSDL2 support is now the default. It can still be disabled\n  by passing --disable-libsdl2 to config.sh. (Lancer-X)\n\n<a class=\"hL\" href=\"#NEWINVER.HLP__1st\">NEW in MegaZeux!</a>\n<a class=\"hL\" href=\"#292CLOG.HLP__292\">New in Versions 2.92 to 2.92f</a>\n<a class=\"hL\" href=\"#284CLOG.HLP__284\">New in Versions 2.84 to 2.84c</a>\n<a class=\"hL\" href=\"#2823CLOG.HLP__283\">New in Versions 2.82 to 2.83</a>\n<a class=\"hL\" href=\"#281CLOG.HLP__281\">New in Versions 2.81 to 2.81h</a>\n<a class=\"hL\" href=\"#280CLOG.HLP__280\">New in Versions 2.80 to 2.80h</a>\n<a class=\"hL\" href=\"#OLDERVER.HLP__260\">New in Versions 2.60 to 2.70</a>\n<a class=\"hL\" href=\"#OLDESVER.HLP__201\">New in Versions 2.01 to 2.60</a>\n<a class=\"hL\" href=\"#NEWIN200.HLP__1st\">New in Version 2.00</a>\n<a class=\"hL\" href=\"#ANCENVER.HLP__1xx\">New in Versions Through 1.03</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"284CLOG.HLP\">\n<a class=\"hA\" name=\"284CLOG.HLP__284\"> </a>\n<p class=\"hC\"><span class=\"f9\">New in Versions 2.84 to 2.84c</span></p>\nDecember 24, 2012 - MZX 2.84c\n\nHey all, new version of MegaZeux here! And hopefully without any\ncrashes this time around!  Haha yeah right.\n\nThe most notable thing this release (besides fixed crash bugs,\nas usual) is the new counter debugger! Give it a try and tell us\nwhat you think!\n\nCounter binary search has been replaced with a hash table in\nversions of MegaZeux for most platforms. This has resulted in\nslightly faster counter lookups, and MUCH faster creation of\ncounters, since MegaZeux doesn't have to keep the list ordered.\nThe hash table is turned off for NDS builds for now due to tight\nmemory constraints.\n\nOtherwise, most of the new features are editor enhancements and\nmodifications. Notable mentions are reordering the board list\nwith 'M' and a custom undo history size.\n\nFEATURES\n\n+ The functionality of the F11 counter/string debugger has been\n  expanded to include sprites, robots, and miscellaneous world\n  and board variables. The ability to modify many of these vars\n  is limited and a large portion are read-only. In addition, you\n  may add new counters and strings, hide empty counters/strings\n  (does not affect built-in variables), and search by name and\n  contents. Export is still limited to counters and strings.\n+ Max string length has been increased from 1MiB (1048576 bytes)\n  to 4MiB (4194304 bytes).\n+ Board mods may now be selected from subdirectories of the game\n  folder in the ALT+N dialog.\n+ The title bar now displays the world and editing board/robot\n  names based on context.\n+ Fire Burns Space and Fire Burns Forever are off by default.\n+ You may now specify the size of the undo history in the char\n  editor, and the history affects the entire char set. Undo is\n  still ALT+U, redo is now ALT+R.\n+ You may now use the -/+ keys in the world editor to move to\n  the previous/next boards in the board list, and Shift+Arrows\n  to change to the boards linked by board edge.\n+ You may now move the current board anywhere in the board list\n  with M. You may not move the title screen board.\n+ View mode in the board editor (V) will start from the current\n  location of the screen now rather than at the top-left corner.\n+ Config file options for default board settings added, as well\n  as the ability to save these per-world from the editor.\n+ Ctrl+G - Goto board position at X,Y.\n+ png2smzx now requires less arguments and has an option to skip\n  a char (generally char 32 will be useful to skip).\n\n+ Debytecode: The robotic editor now asks for a confirmation to\n  save the program on exit.\n\nBUGFIXES\n\n+ Fixed a bug in the 'glsl' renderer that caused the cursor\n  color to be ignored (always white). This caused the cursor to\n  not be visible if shown on a white background. (ajs)\n+ Setting $string.length will not cause memory corruption now.\n  In addition, its pre-2.84 behavior of intentionally allocating\n  the string past the set length has been restored, but the\n  length itself will still be set to the correct value.\n+ Added bounds check to INC \"$string\" \"value\". Attempts to\n  increase a string past its maximum length (4 MiB, or 4194304\n  bytes) will now fail.\n+ Fixed MegaZeux crash that could sometimes occur when a string\n  was increased by itself.\n+ Fixed MegaZeux crash that could occur when exiting the editor\n  to a world file that failed validation.\n+ Rolled string-&gt;storage_space into string-&gt;name to prevent\n  buffer overflow errors and crashes. (Mr_Alert)\n+ Fixed crash that would occur when attempting to type in a dir\n  in the ALT+N dialog.\n+ Fixed a bug where copy block $string would apply REL twice.\n+ Thanatos Insignia (DoZ Q1 2011, 98485) will now play without\n  freezing both normally AND after loading a save. Robots will\n  not be incorrectly versioned with the save format magic now.\n+ Fixed bug where ALT+M wouldn't always edit non-stored types.\n+ Fixed bug where ! could not be used as a substitute for ~.\n+ Fixed a bug where tab-draw and block actions would carry\n  between board and overlay editing. ALT+O now sets the drawing\n  mode back to normal on a switch.\n+ ALT+M now edits the overlay instead of the board while editing\n  the overlay.\n+ Quick-load (F10 during gameplay) will not work if the load\n  menu (F4) has been disabled with the LOAD_MENU counter.\n+ Fixed a bug where the listening mod directory would stay the\n  same as the directory the editor was opened in.\n+ The listening mod now continues to play after world and board\n  changes, and also after the board mod has been changed by\n  ALT+N or Shift+8/*.\n+ LOAD_GAME will not incorrectly trigger JUSTENTERED anymore.\n+ Palette/intensity changes made on the same cycle as a teleport\n  player command are now properly taken into account. This fixes\n  a game-stopping regression in Sponkgo's Legendary Journey\n  where leaving the second stage's bonus area would leave the\n  color intensity at 0.\n+ SMZX mode is now disabled when leaving the editor if it was\n  enabled in the editor via F11.\n+ Fixed potential crash bug where robot bytecode files were not\n  being validated before being loaded with LOAD_BC.\n+ Fixed minor message box bug dating back to DOS where a message\n  box starting with an unavailable option would begin with the\n  cursor on a blank line.\n+ Fixed crash that happened when typing over bounds while\n  renaming a file in a file manager dialog. File and directory\n  renaming now use a popup dialog akin to ALT+N.\n+ Files with names longer than 55 chars may now be selected in\n  file managers. The limit for typing in a file name is still 55\n  chars.  You may not enter a blank line as a file name anymore.\n+ Fixed segfault when attempting to read THIS_COLOR for the\n  global robot. It will now always return -1.\n\nDEVELOPERS\n\n+ Overhauled txt2hlp. Error messages now differentiate between\n  hyperlinks and labels and will take the display chars ~ and &amp;\n  into account. Eventually, the goal should be to get rid of\n  txt2hlp altogether and have MZX load straight from the .txt\n  file.\n+ Added compile option to use uthash for counter/string lookups\n  as opposed to binary search. Ideally, this will vastly improve\n  lookup speeds for large numbers of counters/strings, but will\n  consume more memory. (thanks to Lancer-X for the modified\n  uthash header file)\n  \n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJune 20, 2012 - MZX 2.84b\n\nLess than three weeks after the last one, MegaZeux 2.84b is\nhere a few weeks early to fix a key regression regarding the\nuse of REL commands with the COPY x y dx dy command.  While\nseveral new features have been added, this release was mostly\nabout rooting out as many crash bugs from MegaZeux as possible.\nAn official public beta release of debytecode has been pushed\noff once again due to time constraints.\n\nMost notable as far as new features go, pressing 'E' while a\ngame's title screen is running will now take you directly to\nthat world in the editor. A blank new world may still be\ncreated with 'F8', and now with the 'N' key. The function key\ncorresponding to the new 'E' functionality is 'F9'.\n\nA major new aspect of this version, which could be seen as a\nbugfix or as a new feature, is MegaZeux's ability to validate\nthe MZX, MZB, MZM, and SAV file formats. Validation has been\nrigorously tested and refined, and a comprehensive list of most\nworld files that fail any check is available on the MZX Wiki.\nFile load crashes and force-quit \"Out of memory\" errors in\nthese instances are nearly a thing of the past.\n\nOne final major change regards the file manager dialogs (Load /\nSave Game, etc). These dialogs have been internally overhauled\nto avoid permanent directory changes unless a valid world or\nsave file has been loaded.  If any bugs are experienced using\nthese, they should be reported to the MZX Bug Tracker.\n\nFEATURES\n\n+ Pressing 'E' or 'F9' on a title screen will now open the\n  current world for editing.  Press 'N' or 'F8' to create a new\n  world.\n+ Multiple hosts may now be defined in config.txt. Update\n  attempts will be carried out in the order they are defined.\n+ A startup path may be defined in config.txt (\"startup_path\").\n+ Specifying the backup filename with a directory, ex.\n  \"backup/file\", will now silently attempt to create the\n  directory if it does not exist.\n+ MZM3 is now forward compatible (robots will be dummied out).\n\nBUGFIXES\n\n+ World validation has been strengthened, preventing\n  disasterous loads of most non-world files.  Mostly\n  intact/valid worlds, such as HUNTDRAK.MZX, can be loaded --\n  corrupt/missing boards will be replaced with blank boards,\n  corrupt robots will be replaced by robots with empty\n  programs, and so on.  If more robots/scrolls/signs are found\n  on a board than their data suggests there should be, the\n  extras will be replaced with customblocks. Back up the\n  version of the world with errors in this instance, as the\n  robots' code or scroll text may still be salvageable from the\n  world data.\n+ Fixed a long-standing memory corruption bug in the shoot\n  command introduced by the port.\n+ Mac OS X: MegaZeux now uses /Users/[username] as the default\n  starting directory. Apps previously would always start at the\n  filesystem root. (ajs)\n+ Fixed dangerous crash-causing bug where the editor would not\n  chdir back to the correct directory after testing.\n+ MegaZeux, after saving a world to a new directory, now chdirs\n  to the new current world file's directory.\n+ Changed behavior where a failed save file load would fade the\n  still-running world out of focus. MegaZeux now leaves the\n  world in focus.\n+ All failed world/save loads leave the current world running\n  and in the same working directory.\n+ When startup_file is defined as a directory at the command\n  line, MZX chdirs to the directory instead of attempting to\n  open it as a world file.  In the config file, the directory\n  is pruned off since there's a new config option for the\n  startup path.\n+ Fixed regression where the rel commands would be ignored for\n  board to board copy x y dx dy.\n+ Updated MZX_SPEED in the counter debugger, where it was still\n  getting clamped from 1 to 9.\n+ COLOR FADE IN and COLOR FADE OUT (and all built-in uses of\n  them) now correctly respect any COLOR INTENSITY changes made\n  before they are used.\n+ In file selection dialogs, when attempting to make a\n  directory that already exists with ALT+N, MZX now shows the\n  correct file name and does not force the user to quit\n  MegaZeux.\n+ In file selection dialogs, when pressing ALT+D with a\n  directory selected, MZX now correctly prompts to delete the\n  selected directory instead of a file.\n+ The option to import world files in the board editor now\n  correctly takes the world version into account.\n+ Savegame MZMs loaded into the editor now have their robots\n  dummied out for safety purposes.\n+ Fixed bug where INPUT STRING would not terminate lines longer\n  than 71 chars after clipping them.\n+ INPUT STRING and ASK do not allow either tabs (INPUT only) or\n  line breaks (both) anymore.\n+ Fixed bug where putting a scroll/sign in the editor buffer\n  and then selecting something else could cause a crash on\n  leaving the editor.\n\n+ Temporary fix for MSVC bug where all window dialogs would\n  freeze. (MZXGiant)\n+ Temporary fix: board block actions will not corrupt robot\n  source code in DBC anymore. (MZXGiant)\n\nDEVELOPERS\n\n+ Switched debian prereq. and darwin libpng12 to libpng,\n  switched darwin and default ldflags to use '--ldflags'\n  instead of '--libs'.\n+ Darwin CC/CXX compiler can now be specified on the command\n  line. (ajs)\n+ Cleaned up broken ifeq structure in darwin Makefile.in so\n  ARCHes other than i686 can be built.\n+ Updated MSVC dirent.h to the latest version (MZXGiant)\n+ Added updated MSVC dependencies. (MZXGiant)\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJune 01, 2012 - MZX 2.84\n\nThe first version of MegaZeux to be released in two and a half\nyears, this time with a vast number of bugfixes, several new\nfeatures, and hopefully no new bugs.\n\nThere's a new port to the Pandora platform from Exophase. There\nare no binaries for this platform yet, as ajs has not had time\nto set up the cross-compiler. Same goes for Android.\n\nAnother major (internal) change this time around is that\nExophase's experimental \"debytecode\" language modification has\nbeen merged. This still has some major bugs open against it,\nand missing features, so I won't be doing official releases\nyet. You can add support for this feature by passing\n\"--enable-debytecode\" to config.sh on all platforms.\n\nThanks to Terryn, Exophase and MZXGiant for their contributions\nand to Lancer-X, Old-Sckool and Lachesis who reported and\ntracked the majority of bugs this time round.\n\nFEATURES\n+ Added experimental port to Pandora. See arch/pandora/README\n  for more information. (Exophase)\n+ Directories may now be opened with FREAD_OPEN.  This\n  functionality can be used in conjunction with FREAD_POS and\n  SET \"$str\" FREAD. FREAD will set the string to \"\" when it has\n  reached the end of the file listing.\n+ MZX_SPEED can now be set up to 16 by a robot or from the F2\n  dialog menu. (Lachesis)\n+ Numbers can now be temporarily backspaced past their minimum\n  value to make typing a new number more intuitive in dialogs.\n  (Lachesis)\n+ Chars 0 and 255 can now be selected from the Edit Chars\n  submenu of the Global Info menu. Using char 255 on a kind\n  that would have previously denied it now gives a warning\n  dialog. (Lachesis)\n+ New Counter: SPACELOCK represents the default space\n  functionality for the built-in player. Setting this counter\n  to 0 disables it, allowing the player to move as normal when\n  space is pressed. Setting it back to 1 enables it again.\n  Defaults to 1. (Lachesis)\n+ New Counter: FREAD_DELIMITER allows you to change the\n  terminating char for the string FREAD function.  The\n  terminating char still defaults to '*'. A complementary\n  FWRITE_DELIMITER function has been added as well. (Lachesis)\n+ New Counter: ARCTANdy,dx takes two values and returns the\n  angle with corrected quadrants as an alternative to using\n  ATANdy with DIVIDER as dx, which was less intuitive and never\n  documented properly. (Lachesis)\n+ New Counters: MINv1,v2 and MAXv1,v2 return the minimum or\n  maximum value between two inputs, respectively. Chain several\n  of these in an expression or a loop for more arguments.\n  (Lachesis)\n+ New Counters: bchX,Y; bcoX,Y; bidX,Y; and bprX,Y are new\n  shorthand access counters for BOARD_CHAR, BOARD_COLOR,\n  BOARD_ID, and BOARD_PARAM.  The same limitations to those\n  counters apply to the new ones. (Lachesis)\n+ New Counters: uchX,Y; ucoX,Y; uidX,Y; and uprX,Y are new\n  shorthand access counters for the board's under layer.  The\n  same limitations apply to these as to their board\n  counterparts.  Additionally, these counters will fail if the\n  same spot on the normal board is occupied by a floor-type\n  (space, [dir]water, lava, fake, etc...). (Lachesis)\n+ New Counters: ochX,Y and ocoX,Y are new shorthand access\n  counters for the overlay.  Like OVERLAY_CHAR and\n  OVERLAY_COLOR, these are read-only to discourage the user\n  from writing to these instead of using the much faster PUT\n  [color] [char] OVERLAY [x] [y]. (Lachesis)\n+ Pressing ALT+G from the world editor now goes directly to the\n  Global Robot without having to skip through the Global Info\n  menu. (Lachesis)\n+ MZM3 has been enabled for 2.84 and all following versions.\n  The difference between MZM3 and MZM2 is that MZM3 stores a\n  copy of the world version, allowing the robot format to\n  change. (Lachesis)\n+ COPY can now take + and # prefixes to its arguments. COPY\n  BLOCK and COPY OVERLAY BLOCK can now take + prefixes to their\n  first set of coordinates. (Lachesis)\n+ Subroutine versions of TOUCH, BOMBED, INVINCO, PUSHED,\n  PLAYERSHOT, NEUTRALSHOT, ENEMYSHOT, SHOT, PLAYERHIT, LAZER,\n  SPITFIRE, GOOPTOUCHED, PLAYERHURT, KEY[char], KEYENTER, THUD,\n  and EDGE have been added. These should ALWAYS be used in\n  conjunction with LOCKSELF/ZAP and #RETURN or #TOP to keep the\n  robot stack under control.  Please remember that THUD and\n  EDGE ignore LOCKSELF and their subroutine versions must be\n  ZAPped. JUSTENTERED, JUSTLOADED, and the sensor labels have\n  been excluded from this due to various reasons. (Lachesis)\n\nBUGFIXES\n\n+ Fixed a bug where LOAD_ROBOT or LOAD_BC would not reset the\n  stack pointer for newly loaded programs. This could cause\n  crashes if a robot popped the stack in the new program.\n+ Fixed a bug where range checking of BOARD_X and BOARD_Y would\n  sometimes not be done correctly, leading to crashes.\n+ Strings in the debug menu list no longer interpret any color\n  codes they may contain.\n+ Fixed a bug where a string would not be interpreted correctly\n  if it used a '.' character in a splice parameter expression.\n  Expressions such as IF \"$str#('$str2.length'-4)\" = \"blah\"\n  THEN \"label\" will now work correctly.\n+ Fixed negative sprN_cheight et al from crashing. (Exophase)\n+ Placement of objects on the player will be blocked with an\n  error dialog like DOS versions, instead of silently failing\n  after setup. (MZXGiant)\n+ Fixed a bug where LOAD_ROBOT would not properly parse lines\n  of imported code that had leading whitespace. (MZXGiant)\n+ Fixed an integer wrapping bug in debytecode, disallowed\n  numeric literals outside of the bounds of a signed short.\n  (MZXGiant)\n+ Fixed a bug that would corrupt the UI palette if \"set color\"\n  was run against a color index over 15. (MZXGiant)\n+ If either board dimension is less than the editor viewport,\n  the character and colors used to indicate space outside of\n  the board are taken from protected sets. In game, they are\n  taken from the game's sets.\n+ Increase limit on difference for RANDOM \"A\" TO \"B\" to\n  UINT_MAX rather than INT_MAX - 1 as it was previously. Since\n  the entire range represented by a counter is now usable,\n  there are no cases where RANDOM will \"break\".\n+ Fixed avalanche rings and potions to limit boulder placement\n  to 1/18, matching the AVALANCHE command.\n+ Fixed corruption and possible crashes when using VIEWPORT\n  SIZE to set the viewport to a size less than 80x25 but\n  greater than the current board dimensions. The viewport will\n  now always be clamped to board size.\n+ Fixed incorrectly changing horizontal mouse position on\n  setting MOUSEY. (Mr_Alert)\n+ Fixed overflow into protected character set when \"Revert\n  to...\" is selected in the character editing dialog.\n  (MZXGiant)\n+ Music and SFX now mute when the updater launches and are\n  restored when it is complete. (MZXGiant)\n+ Fixed odd string behaviour when copying between strings that\n  happen to be stored close to each other in memory. This fixes\n  a regression introduced by the \"crash when pasting to and\n  from the same string\" fix in 2.82b.\n+ The size/offset parameters for strings can now be specified\n  in either order (#+ vs +#) and will behave correctly.\n+ Reverted bogus cycle-ending behaviour for SHOOT,\n  SHOOTMISSILE, SHOOTSEEKER and SPITFIRE.\n+ Improved cycle-ending compatibility with MZX versions prior\n  to 2.80. Fixes games such as Kya's Sword and Stones &amp; Roks\n  II.\n+ Restored shark's ability to move in goop.\n+ MZX now clears SPR_YORDER upon loading a new world.\n+ When transitioning between boards, MZX now compares the\n  module filenames of the source and destination boards\n  case-insensitively. A difference in case will no longer cause\n  the board module to be incorrectly restarted.\n+ Fixed rare rendering corruption in the load game dialog.\n+ Fixed a bug where SEND \"robot\" TO \"#return\" could corrupt the\n  program counter of the target robot if it had a stack pointer\n  of zero.\n+ Fixed some security issues with SMZX_PALETTE and LOAD_BC\n  counters.\n+ MZX now only lists/opens regular files or symlinks to regular\n  files in file dialogs. Special files are now ignored.\n+ Opening directories with FWRITE_OPEN is now rejected properly\n  on all platforms.\n+ Fixed a bug where a file would be re-opened for read/write,\n  even if the file was missing or an I/O error occurred before\n  saving.\n+ Fixed a bug where MZX could occasionally crash due to label\n  list corruption when copying robots from heap locations\n  greater than 2^32 bytes apart (only affected 64bit builds).\n+ Stopped SET \"var\" &lt;command&gt; from assembling. Some invalid\n  uses of command tokens were already being ignored, but this\n  was just luck.\n+ Sprites with color c?? (inherited \"natural\" colors) will\n  correctly inherit the colors of special characters such as\n  the player and other self-colored built-ins.\n+ Debytecode's legacy expression converter should use\n  is_string() instead of its own (buggy) hand-rolled version.\n  Fixes crash when converting CoAZ.\n+ The editor no longer incorrectly clamps the intelligence of\n  sharks, spitting tigers and spiders to &lt;=4, and no longer\n  clamps the HP of dragons to &lt;=4. (Lancer-X)\n+ MZX now accepts SET EDGE COLOR \"string\" in addition to SET\n  EDGE COLOR c??.\n+ Fixed NDS port initialization on DSi devices. (asiekierka)\n+ Fixed a bug in the joystick code where centering an axis\n  clears the previous axis button. (iamgreaser)\n+ Fixed a bug that allowed vlayer-&gt;board COPY BLOCK to\n  overwrite the player. Blocks that would overwrite the player\n  are now ignored.\n+ Fixed a 2.81e regression that allowed SENDs to self to ignore\n  LOCKSELF.\n+ Setting $str.length now makes $str the length specified.\n  (Lachesis)\n+ Caps Lock no longer interferes with dialog box text input.\n  (Lachesis)\n+ Increasing the size of a string with $str.length, $str.N, or\n  with a splice now wipes old string data with char 32s.\n+ Dialogs (especially \"Exit gameplay - Are you sure?\") now\n  require the user to have actually hit ESC to close, making\n  escaping busyloops and message loops much easier. (Lachesis)\n+ Added a compatibility fix for different label caching in 2.80\n  through 2.83 that allowed constructs such as : \"LABEL\" / SEND\n  \"ALL\" \"LABEL\" on an unlocked robot to continue instead of\n  getting caught in a loop. The altered label caching caused\n  #98485 Thanatos Insignia to lock up in an unescapable\n  busyloop in GIT versions. (Lachesis)\n+ MOD \"[lead-in file]*\" now works properly. In previous\n  versions of the port, this construct would result in the\n  lead-in file failing to play and the wildcard mod restarting\n  when the player re-entered and re-exited the board.\n  (Lachesis)\n+ Fixed a bug where mod \"*\" would cause the mod to restart when\n  entering another board with the same playing mod. (Lachesis)\n+ ALT+D (Default palette) now requires a confirmation.\n  (Lachesis)\n+ The COMMANDS counter is now saved as a 32-bit variable.\n  (Lachesis)\n+ The LOOPCOUNT counter has been moved to save-only data and is\n  now saved as a 32-bit variable. (Lachesis)\n+ Fixed a bug where not all whirlpools were being considered as\n  such, notably during the transport board scan. (Lachesis)\n+ The abilities of PLAY \"&amp;file&amp;\" to play at multiple\n  frequencies and to parse multiple files have been restored.\n  (Lachesis)\n+ As of MZX 2.84, BOARD_COLOR will now ignore the under color\n  of any object with a BG color of 0 that is on top of\n  something.  Worlds that relied on this between 2.80 and 2.83\n  are unaffected. (Lachesis)\n+ Setting BIMESG to 0 will now disable Game Over's\n  auto-centering of the message row. (Lachesis)\n+ The DIVIDER counter's documentation has been updated to\n  explain its true purpose.\n+ Fixed an editor bug where canceling a world load could cause\n  MZX to forget the filename of the current world. (Lachesis)\n+ Pressing ALT+M in the world editor now edits anything with\n  parameters, not just Robots, Signs/Scrolls, and Sensors.\n\nDEVELOPERS\n\n+ Source tarballs are now generated in XZ (LZMA2) format.\n+ Make hlp2txt utility work correctly on Windows platforms.\n+ Updated and repaired MSVC project for Visual Studio 2010.\n  (MZXGiant)\n+ Win32 binaries are built ASLR-capable (via pefix).\n+ Version control was changed from SVN to Git; the repo is at:\n  http://github.com/ajs1984/megazeux\n+ The EGL backend now supports Mesa's EGL implementation on\n  X11.\n+ Imported libmodplug 0.8.8.4 and rebased all patches.\n+ Introduced SOCKS4/4a/5 support transparently into the network\n  layer. (MZXGiant)\n\n<a class=\"hL\" href=\"#NEWINVER.HLP__1st\">NEW in MegaZeux!</a>\n<a class=\"hL\" href=\"#292CLOG.HLP__292\">New in Versions 2.92 to 2.92f</a>\n<a class=\"hL\" href=\"#2901CLOG.HLP__291\">New in Versions 2.90 to 2.91j</a>\n<a class=\"hL\" href=\"#2823CLOG.HLP__283\">New in Versions 2.82 to 2.83</a>\n<a class=\"hL\" href=\"#281CLOG.HLP__281\">New in Versions 2.81 to 2.81h</a>\n<a class=\"hL\" href=\"#280CLOG.HLP__280\">New in Versions 2.80 to 2.80h</a>\n<a class=\"hL\" href=\"#OLDERVER.HLP__260\">New in Versions 2.60 to 2.70</a>\n<a class=\"hL\" href=\"#OLDESVER.HLP__201\">New in Versions 2.01 to 2.60</a>\n<a class=\"hL\" href=\"#NEWIN200.HLP__1st\">New in Version 2.00</a>\n<a class=\"hL\" href=\"#ANCENVER.HLP__1xx\">New in Versions Through 1.03</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n\n<hr></div>\n\n<div class=\"hF\" id=\"2823CLOG.HLP\">\n<a class=\"hA\" name=\"2823CLOG.HLP__283\"> </a>\n<p class=\"hC\"><span class=\"f9\">New in Versions 2.82 to 2.83</span></p>\nDecember 29, 2009 - MZX 2.83\n\nIt's been a year since the last release, due in part to my\nreduced free time, and less contribution from other developers\nin 2009. I'd also like to believe that 2.82b was such a good\nrelease, there was no need to rush.\n\nThere's over 30 bugs fixed this time. A few features I had to\nwithhold for 2.82b have been added; sample loop markers and\nsome changes to the board file format necessitated the bump to\n2.83. Logicow's GLSL renderer has finally made it in (various\nbits had to be re-written to extend portability to other\nplatforms).\n\nThere's a new (semi-complete) Android port this time; I hope to\ncomplete it, and provide binaries for Android 2.0 phones, in\n2.83b.\n\nThanks go out to the usual suspects -- Terryn, Mr_Alert,\nLogicow, kvance, Lancer-X, revvy and Exophase -- for supporting\ndevelopment this year.\n\nUSERS\n\n+ Added support for loop markers in WAV and OGG files. The WAV\n  loop support uses the \"smpl\" chunk used by ModPlug Tracker\n  and Wavosaur among others. Only the first loop is used, and\n  only forward looping is supported. The OGG loop support uses\n  the \"LOOPSTART\" and \"LOOPLENGTH\" tags as used by RPG Maker\n  VX. (Mr_Alert)\n+ Added OpenGL Shader Language (glsl) renderer which uses\n  shaders to render and scale the video. This renderer is\n  compatible with Open GL &gt;=2.0 and Open GL-ES 2.0 video cards\n  only. A variety of shader programs have been provided and\n  these can be customized. Performance of all MZX modes\n  (including SMZX) is excellent. (Logicow, ajs)\n+ Files will no longer be silently overwritten by save dialogs\n  if the user enters an existing filename without the default\n  extension. (revvy)\n+ The string editor in the counter debug menu (F11) now escapes\n  newlines and backslashes to prevent UI corruption.\n+ Fixed a bug where the LOAD_GAME counter handler could\n  continue to use the old board state after load, causing\n  crashes. (Lancer-X)\n+ Fixed a bug where status counters containing numbers &gt;6\n  characters would cause MegaZeux to crash or behave strangely.\n+ Fixed a bug in the robot editor's find/replace function that\n  caused crashes when replacing a string with another longer\n  string, with a replacement at the end of a line.\n+ Programmatically writing to a read-only \"built-in\" counter\n  will no longer allocate it general heap space. This prevents\n  writes from showing up in the F11 counter debugger that are\n  inaccessible from robotic.\n+ Fixed a bug that caused the SMZX mode 3 palette to become\n  corrupted upon entering the char editor (the editor would\n  re-write colours 2-4 and not restore them from backup\n  correctly).\n+ Fixed a bug where a robot program would never progress if the\n  subroutine stack was popped more times than it was pushed\n  (via return or top).\n+ On UNIX platforms a desktop/menu entry is now installed by\n  default, using the existing icon. (Sci-freak)\n+ Clamp score to &gt;= 0 if world &lt;= 2.70. Fixes \"Gates: The\n  Puzzles\" and possibly other old titles depending on this\n  behavior. (Exophase)\n+ Fullscreen modes will now automatically use your current\n  desktop resolution if using any hardware renderer (i.e. not\n  the default software renderer). To get the old behaviour back\n  you must set fullscreen_resolution explicitly.\n+ Fixed a bug where web and thick web would be treated the\n  same.\n+ Fixed a bug in the updater where modified/replaced files\n  would be considered for deletion.\n+ Fixed a bug on case-sensitive filesystems where saving a\n  game, world or MZM could fail to overwrite any existing file\n  by the same name (if matched case insensitively).\n+ MZX no longer applies masking to chars 32-127 in signs or\n  scrolls when playing a world. Previously, even the \n  mask_midchars option would have no effect on the display of\n  signs or scrolls. This has been broken since 2.80g.\n+ Re-worked board editor's Alt+H option to provide minimal\n  editor (one row) status info, rather than completely hiding\n  the help.\n+ The checkres utility now checks the global robot for missing\n  resources too.\n+ Chests can be added with Hi Bombs (omission noted by zzo38).\n+ Fix IF c?? Sprite p?? # # \"label\" so that a non-wildcard\n  parameter is respected (previously it would always just check\n  sprite 0).\n+ NDS port updated from dsmzx2 release. (kvance, ajs)\n+ Updated SDL to 1.2.14 in Windows x86, Windows x64 and Mac OS\n  X builds.\n+ Security checks are no longer applied to filenames in module\n  or sample playback in \"listening only\" modes in the editor.\n+ Module volume is applied immediately before playback upon\n  switching boards. This prevents one cycle of audio \"leaking\"\n  at the wrong volume.\n+ Prevent crash with negative string clip where clip + offset =\n  0. Clip is now correctly limited to total string length.\n+ Help file is now optional for MZXRun, even with\n  CONFIG_HELPSYS=1 builds.\n+ Fixed crash when robot editor macros expanded other macros.\n+ The lock icon is no longer missing from the Items THING menu\n  (F4).\n+ A world to start up with can now be passed to MegaZeux\n  without the startup_file= prefix. This makes megazeux\n  consistent with other applications.\n+ SHOOT, SHOOTMISSILE, SHOOTSEEKER and SPITFIRE now end the\n  cycle, to restore compatibility with pre-port MZX and fix\n  games such as Kya's Sword and Stones &amp; Roks II.\n+ Have IF [dir] PLAYER [color] [thing] [param] \"label\"\n  interpret SEEK direction wrt robot coordinates, rather than\n  player coordinates. Other directions are not affected.\n+ Zapping a label at the end of a robot program will no longer\n  corrupt the robot list (which usually caused crashes).\n+ Entering lines in the robot editor with leading or trailing\n  spaces will be trimmed before the line is compiled.\n+ The single quote characters encasing S_CHARACTER parameters\n  in the robot editor will now use the protected (GUI) charset\n  rather than the game one.\n+ Added a \"system_mouse\" config.txt option that allows the\n  mouse cursor to be replaced with the system mouse cursor,\n  rather than being drawn by MegaZeux.\n+ Disallowed placing player clones with SET \"board_id\" 127.\n+ Relaxed file name limit on board MOD file. The board MOD can\n  now be as long as the limit imposed by file dialog's input\n  box (previously limited to 12 characters).\n+ Truncation of currently open input/output file names will now\n  only occur at MAX_PATH bytes (typically 512 characters). The\n  previous limit was 13 characters.\n+ Relaxed limit of INPUT string and bottom (\"*\") messages from\n  80 characters to ROBOT_MAX_TR (512) characters. In the case\n  of bottom messages this can be usefully exploited to ~200\n  characters.\n+ Progress meter will be shown for world decrypt on console\n  platforms.\n+ Fixed a bug where a malformatted BMP header would be written\n  (length too short, didn't include dummy channel in BMP\n  palette). (Mr_Alert)\n+ Optimized audio locking; do file I/O outside of critical\n  sections to decrease stalling, particularly on platforms with\n  slow I/O. (Mr_Alert)\n+ Added support for MacOS 10.6 (Snow Leopard) and removed\n  support for 10.3.\n+ Loading a save game from robotic will now correctly restore\n  intensities to their saved values.\n+ Copy/pasting a block either with COPY BLOCK or the editor,\n  where the copy would exceed the limit on robots/signs/scrolls\n  /sensors, will no longer place junk at the target\n  co-ordinates. Instead, the object's background will be copied\n  in isolation.\n+ Pasting from the clipboard, expanding a macro,  or importing\n  .txt or .bc files that would cause a robot to exceed the 64k\n  limit now has the operation ignored at the point it exceeds\n  the limit, rather than adding an unlimited number of\n  unrecognized lines.\n\nDEVELOPERS\n\n+ MZXRun compilation can now be disabled. Compilation of\n  pre-2.82b style non-modular builds requires\n  `--disable-modular --disable-mzxrun'.\n+ Disabling SDL can now be done with --disable-sdl and the\n  resulting configuration will automatically disable any\n  SDL-dependent components. This is useless to anybody except\n  developers doing new ports.\n+ Game directory, utility directory and resource directories\n  can now be specified and will be respected on \"make install\".\n  (Sci-freak, ajs)\n+ Added experimental port to Android. See arch/android/README\n  for more information.\n+ Ported opengl1 and opengl2 renderers to OpenGL ES 1.x and\n  glsl renderer to OpenGL ES 2.0, used increasingly by mobile\n  devices.\n+ Removed SDL dependency from NDS port. It only used it for\n  timing and stuffed events.\n+ On Windows platforms, binaries are processed with the `pefix'\n  in-tree tool to eliminate data section differences in\n  programs with identical texts. This minimizes the amount of\n  content required to be sent for updates.\n+ Updated Wii port: improved audio and video support, added USB\n  mouse support, numerous optimizations and improved file\n  selector. (Mr_Alert)\n  \n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nDecember 29, 2008 - MZX 2.82b\n\nThis release contains plenty of important bug fixes, ranging\nfrom regressions such as the broken command-line editor macro\nexpansion to third party bugs like the Windows \"directx\" SDL\nvideo driver breakage.\n\nThere are also some new ports and features. MegaZeux now runs\non the Wii (port by Mr_Alert) and AmigaOS 4.x (port by myself\nand Spot from os4depot). The Windows x64 port has matured\nimmensely and can now be considered stable. MacOS X builds now\nhave clipboard support. The hardware stretching renderers now\nhave a couple of fixed aspect ratio modes.\n\nThe biggest feature of this release is the introduction of a\nportable network layer, which is currently being tested by the\nnew built-in updater (F7/U).\n\nInternally, MegaZeux is now modularized and builds as several\nDLL and EXE files, which should make redesigning parts like the\nhelp system and editor a little easier, as well as allowing us\nto ship a \"mzxrun\" executable for the first time since 2.69c.\nThis \"mzxrun\" executable is now used by a majority of the\nconsole ports.\n\nContributions from Revvy, Mr_Alert, Terryn and Exophase have\nhelped make this another solid release.\n\nUSERS\n\n+ Writing to $str.length (which previously did undefined\n  things) will now truncate or enlarge the string to the size\n  specified.\n+ Removed filename size limit for FWRITE_MODIFY and\n  FWRITE_APPEND. (Revvy)\n+ Added support to the checkres tool to check worlds in\n  non-local directories. (Revvy)\n+ Fixed an old bug with saving games and worlds from Robotic\n  where a board could be prematurely \"optimized\", renumbering\n  robot IDs within the same cycle. For commands like DIE this\n  could cause unpredictable behaviour or simply crashes (if\n  invoked in the same cycle). As a special case, end the cycle\n  if either of these SET specials are used.\n+ Mistaken or malicious file I/O such as set \"$test\" to\n  \"fread(-1&gt;&gt;1)\" will no longer crash MegaZeux. The read size\n  will be truncated to a contextual maximum for the current\n  file.\n+ Fixed a crash using \"fwrite0\" in conjunction with an empty\n  string.\n+ Fixed a bug where checking sprite_collisions on a disabled\n  target sprite would unconditionally trigger (regardless of\n  whether a collision was present or not).\n+ Un-grouped the handling of the KEY and KEYn counters so that\n  different compatibility checks can be applied to either\n  counter. Fixes \"Bocco Chronicles 1\" and probably several\n  other titles.\n+ Fixed a crash when using RIDn or ROBOT_ID_n in the same cycle\n  as DIE for another robot positioned earlier in the board\n  scan.\n+ Fixed poor sanity checks on BOARD_ID counter writes. Illegal\n  character IDs such as -1 can no longer be used to bypass the\n  check (causing subsequent crashes).\n+ Windows builds now use a patched version of SDL 1.2.13\n  containing a fix for the directx+F10 issue.\n+ Fixed a bug where one robot could send another robot to\n  \"#return\", with an address outside its program. In such\n  cases, the robot will now terminate.\n+ An \"mzxrun\" binary is now shipped alongside the\n  editor-capable MZX binary.\n+ Fixed TIME/TIMERESET overflows with very large values. Board\n  timeout is now programmatically limited to 32767, which is\n  consistent with the Board Info control.\n+ Clamped CHAR_X/CHAR_Y properly so that negative numbers can\n  no longer be used to corrupt the editor charset and\n  potentially other process memory.\n+ Fixed recent breakage of SHIFT+F{1,2,3,4} so that the\n  percentage of time spent displaying the original character\n  and the '!' are equal.\n+ Removed some bogus handling of lines containing \"only\" ';',\n  ',' or ' '.\n+ Honor user's robot character selection if they are holding\n  shift when pressing return or space (would previously always\n  return char 247).\n+ Backspacing a line and then expanding a macro no longer\n  restores the original line contents immediately after the\n  expansion.\n+ MacOS 10.x clipboard support (via Cocoa Pasteboard). Alt+Ins\n  can be entered with Fn+Alt+Numpad0 on a Macbook or Powerbook\n  keyboard.\n+ Robot editor S_CHARACTER fields no longer bogusly escape\n  characters such as \". In addition, rendering glitches are no\n  longer encountered when using the ' S_CHARACTER (which is\n  completely reasonable).\n+ Fixed robot editor glitches where the game charset SPACE\n  would be used in places where the protected UI charset should\n  be used instead.\n+ The introductory help message is displayed if the load dialog\n  is cancelled prior to loading a game. Hopefully the screen is\n  now never totally blank.\n+ The F7/F8 cheats can now be used freely in MZXRun (in\n  MegaZeux proper they remain usable only in editor tests).\n+ Saving to a directory above the MegaZeux startup directory,\n  then attempting to save to this location again, will no\n  longer crash MegaZeux. Instead, the parent directory will be\n  changed into before the dialog is displayed.\n+ Fixed numerous crash bugs with the scroll editor; it should\n  be relatively usable now.\n+ Writing to $str+0 is no longer interpreted in the same way as\n  a plain write to $str. Instead, it behaves like writes to\n  non-zero offsets (as more of a paste than a replace).\n+ Display of current X,Y position of robot in the robot editor\n  status bar.\n+ Fixed directory rename so that it no longer displays garbage\n  and/or crashes MegaZeux (Alt+R to rename a directory in any\n  file picker).\n+ In the robot editor, lines can now be split at a midpoint\n  with enter and two consecutive lines merged together with\n  backspace. (Exophase)\n+ Fixed use of status counter 6 and display of status counters\n  in general, which has been broken since 2.80.\n+ Fixed swapping to encrypted worlds if initially the user\n  decides to not decrypt the world. Previously, this would\n  either crash, or loop forever.\n+ When using the OpenGL or overlay renderers, in either\n  windowed or fullscreen mode, the aspect ratio can now be\n  preserved as either 4:3 (most similar to DOS) or 64:35 (most\n  similar to the port). The display will be letterboxed or\n  margins applied as appropriate. See the \"video_ratio\"\n  configuration option for more information.\n+ Fixed a bug on some systems where numlock could not be used\n  as a key, only as a flag. The numlock \"key\" is now masked out\n  of \"key_code\" and similar; hopefully this won't break any\n  games.\n+ Restored the meter widget from the old DOS MZX for use with\n  the world loader and saver routines. This reassures users,\n  especially on consoles, where loading a world can take a long\n  time. (Mr_Alert)\n+ On Windows, directx.bat now passes %cd% through to `start' so\n  that features such as the updater continue to work.\n  (MZXGiant)\n+ Pasting into a string with set \"$str+N\" with an N &gt; \n  str.length will no longer crash MZX.\n+ \"Exit to DOS\" is now \"Exit MegaZeux\" to reflect the\n  multi-platform nature of the program.\n+ Setting a substring size to zero with $string#0 will no\n  longer return the whole string; it will instead return the\n  empty string.\n+ Accessing a substring with an offset &gt;= $string.length will\n  no longer return the last character from the string; it will\n  instead return the empty string.\n+ Writing beyond MAX_STRING_LEN (1MB) or using negative offsets\n  (which has the same effect) no longer crashes. Instead, the\n  write is ignored.\n+ Fixed crash when pasting to and from the same string,\n  specifically in conjunction with $str+offset.\n- Removed the legacy \"force_resolution\" option which was\n  replaced long ago by the more accurate\n  \"fullscreen_resolution\" option.\n\nDEVELOPERS\n\n+ Ported to OpenSolaris. You need to install `SUNWxorg-headers'\n  if you want X11 clipboard support.\n+ Removed dependency on SDL_image on non-win32 platforms when\n  enabling the icon branding feature (see pngops.c).\n+ Ported to AmigaOS. You need to install the clib2 version of\n  libSDL and miniGL, and the build system assumes you are using\n  a cross compiler.\n+ Added experimental port to the Wii. See arch/wii/README for\n  more information. (Mr_Alert)\n+ get_path() in util.c now returns &lt;0 for failure, or the\n  length of the path for the given file. (Revvy)\n+ Added the make time WHOLE_PROGRAM=1 flag which enables\n  compilation of the core binary in GCC's \"-fwhole-program\n  --combine\" mode. This makes all symbols static and improves\n  optimization, somewhat like MSVC's LTO does.\n+ Added a valgrind.supp file to suppress bugs in third party\n  libraries when valgrinding MegaZeux.\n+ Cleaned up all the ports and documented making new ports. The\n  platforms \"linux\", \"solaris\" and \"obsd\" are now called\n  \"unix\", and the \"linux-static\" platform is now \"unix-devel\"\n  and available on all UNIX derivatives/clones.\n+ Added a special hack to enable linking with --as-needed for\n  DT_NEEDED link optimization for GNU ld platforms.\n+ Updated MSVC projects. Fixed all warnings emitted by MSVC\n  2008, and implemented icon support with existing mingw\n  resource files.\n+ Now uses the GNU ld \"debuglink\" feature on all platforms to\n  enable shipping of a side-by-side symbol package. Optimized\n  release builds can now be debugged with minimal user effort.\n+ MegaZeux now provides the option for \"modular\" linkage,\n  factoring out the \"core\", \"editor\" and \"network\" features to\n  shared objects that other binaries can link against. This\n  feature works on the unix, mingw, amiga and darwin ports.\n+ Added RPM .spec file. Capable of building (at least) Fedora\n  10 RPMs.\n- Removed HOST_CC feature for cross compilation; since the\n  utilities now intimately depend on the MZX runtime, they must\n  be built with the same compiler.\n  \n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJune 10, 2008 - MZX 2.82\n\nDespite the increase in minor version, this release mostly\ntargets bug and regression fixes. However, there ARE some\nadditional new features, such as the introduction of the\nLOAD_MENU and mouse pixel counters, and refinement of the\n{FREAD,FWRITE}_COUNTER counters. (There are several other\nsmaller features that are documented in the changelog.)\n\nSAVs from older worlds (requiring compatibility hacks) no\nlonger fail to play (Darkness, etc. are affected). We've also\ndone a good bit to fix compatibility with 2.70 and older.\n\nA new tool, \"checkres\", is now routinely packaged, allowing you\nto check your games for missing resource files (PALs, CHRs,\netc.) before passing them on to other people. This should be\nespecially handy for DoZ game submissions.\n\nThe Nintendo DS port (Kevin Vance's \"DsMZX\") has been merged\ninto this release. I'll provide binaries for GP2X, PSP and NDS\nthis time, but I can't guarantee they'll work.\n\nBoth of the snags from the last DoZ have been addressed -- the\nhelp system should no longer crash and the Block Action crashes\nshould be reduced in frequency. However, there are still issues\nwith pasting in the robot editor that remain unfixed (they're\njust really hard to reproduce). With your bug reports, I look\nforward to fixing this.\n\nAs usual, thanks go out to Revvy and Mr_Alert for their\ncontributions to the bug-fixing effort, and to Terryn for his\nunwavering dedication to creating and organising bug reports,\nand for testing our bug fixes.\n\nUSERS\n\n+ Fixed and improved quality of the half-width renderer for the\n  GP2X port (Mr_Alert).\n+ The numpad now works correctly when numlock is disabled. Keys\n  are no longer ignored by the MZX editor, and games should\n  recognize them as before.\n+ Added a tool, \"checkres\", which extracts all resources from a\n  MegaZeux world or board file and lists them (or lists only\n  those which are not found in the game directory). ZIP files\n  are also supported (to a more limited extent). (ajs &amp; Revvy,\n  ideas from Exophase &amp; Terryn).\n+ Removed the bogus \"F1 for Help\" option from error dialogs,\n  and finally got rid of the \"** BETA **\" banner on title\n  boards in play mode.\n+ Obsoleted support for the AMS, DBM, DMF, MDL, MT2, PSM, PTM\n  and UMX module formats. As noted for several versions in the\n  help file, these are not loadable by MikMod. It is extremely\n  unlikely any game uses these obscure formats, but denying\n  their use is now enforced (at a robotic level).\n+ Fixed crash when writing to an MZX string at an illegal\n  offset (&lt; 0).\n+ Fixed returning from a subroutine invoked by a jump from a\n  MZX text box class command so that it no longer skips the\n  next impending line (after the text box).\n+ Assembled single non-alphanum/punctuation characters as\n  bytecode CHARACTER instead of bytecode STRING. Fixes bogus\n  auto-quoting for commands like SCROLL CHAR (Revvy).\n+ Switched the Win32 package back to using the \"windib\" SDL\n  video driver, instead of the \"directx\" SDL video driver. The\n  windib.bat file has been replaced with directx.bat, which has\n  opposing semantics.\n+ SAM/GDMs with converted WAV/S3M counterparts of zero length\n  will be automatically re-converted. This hack can be used to\n  procedurally regenerate WAV files from SAMs, or transparently\n  work around on-disk corruption.\n+ Strings are now limited to a maximum length of 1M. I'm open\n  to suggestions over a better limit, but there must be a limit\n  (set \"$string.X\" notation grows a string arbitrarily, so\n  robotic can crash MZX when a string is resized beyond a\n  reasonable limit).\n+ Strings, when grown, will fill gaps with ' ' instead of\n  garbage. This can be useful when the string grows after using\n  the set \"$string.X\" notation; the rest of the string is no\n  longer garbage, allowing the debugger to be used.\n+ A robot that does a \"put c?? Thing p?? [dir] player\" and\n  overwrites itself will no longer leak commands. Instead, if\n  the robot overwrites itself, its program will end.\n+ Fixed message edges always showing up black, instead of \n  whatever color 0 is. (Revvy)\n+ Changed starting/max health and lives minimum to 1 instead of\n  0. (Revvy)\n+ Some help system (F1) bugs have been fixed, hopefully\n  mitigating some of the crashes people have been seeing.\n+ Fixed a bug on Linux where fclose() on a robot-opened file\n  could, on world reload, occassionally crash (due to a stale\n  handle). Fixes loading Taoyarin saves multiple times in a\n  row.\n+ The new option \"gl_vsync\" has been added to allow the SDL\n  \"flip on vsync\" in the OpenGL renderers to be forcibly\n  enabled or disabled. This fixes a problem where speed 1 would\n  only be as fast as the video refresh rate.\n+ Setting the music volume to 0 (when using the ModPlug engine)\n  now ensures that no music is audible. Previously, setting the\n  volume to 0 would be equivalent to setting the volume to 1,\n  which was still audible.\n+ Upon exiting the initial load screen, and not entering the\n  editor, the screen is now updated. This fixes rendering\n  glitches in the MZX game window when overlapping the window\n  with another, at the slight expense of CPU time.\n+ If loading a save game from the title screen (or when no\n  world has been loaded) MZX no longer sends JUSTENTERED to all\n  robots. This restores compatibility with MZX 2.70 and is\n  consistent with loading a save from another board.\n+ Counters with 10 digits and a negative sign are no longer\n  truncated in the debug menu.\n+ Correctly clamped (rather than truncating) the value passed\n  through to a SET COLOR. Restores compatibility with 2.70, and\n  fixes Xenogenesis.\n+ Improved clipboard copy behaviour on Linux. Some actions are\n  still mysteriously broken.\n+ Fixed replacing with a blank string in conjunction with the\n  replace all Ctrl+F action in the robot editor. The cursor can\n  now no longer become negative, fixing numerous possible\n  crashes on search/replace.\n+ Fixed loading the intrinsic SMZX palette when switching to\n  SMZX modes from a game not in the same directory as the\n  \"smzx.pal\" file.\n+ Reloading a world that requires switching between SMZX and\n  non-SMZX modes will now respect the world's intrinsic palette\n  on the title screen. Fixes problems loading non-SMZX games\n  after having an SMZX game loaded.\n+ Clamped array offsets on boards. Some older MZX worlds are\n  corrupted and have the endgame_{x,y} coordinates outside of\n  the limits of the endgame board. Fixes \"Fourth Power\".\n+ Where possible, versioned all counters that the port\n  understands. This ensures that in the unlikely case that a\n  game made with an older version of MZX (actually, with an\n  older world magic) uses a counter that did not exist in that\n  game's era, the port will no longer try to interpret it.\n  Previously, only rid? and key? were versioned.\n+ SAV files will now be stamped with the world magic of the\n  world they were loaded from. This allows compatibility hacks\n  to apply to SAV files as they would to worlds (ajs, Terryn,\n  Mr_Alert).\n+ Added LOAD_MENU counter like ENTER_MENU, F2_MENU et al. to\n  allow control (from Robotic) over whether the LOAD_MENU can\n  be brought up.\n+ Made FREAD_COUNTER and FWRITE_COUNTER read in a DOS dword\n  (32bit) instead of a DOS word (16bit). This allows modern\n  post-port MZX counters to be fully represented in files.\n  Compatibility with older worlds is preserved.\n+ Added a new config option \"board_editor_hide_help\" which\n  changes the default hide setting of the help text on the\n  primary board editor.\n+ Numerous fixes for bugs found by valgrind. (Nightwatch)\n+ Icon support is now fixed and works on all platforms. On\n  Windows, the icon cannot currently be changed (it is loaded\n  from the EXE's resource section). Use ResHacker if you really\n  want to change it.\n+ Fixed a bug where either LOAD_ROBOTn or LOAD_BCn (where n was\n  equal to ROBOT_ID) would alter the robot's line number rather\n  than completely restart it. Due to complexities in robot\n  context, this lead to the first line being skipped.\n+ Added a new tool \"downver\" which supports drag-and-drop\n  downgrading of a world or board from the version of MZX it is\n  packaged with to the previous version of MZX. This tool may\n  be unsafe to use -- be careful.\n+ Fixed a bug in the robotic assembler which would\n  occassionally emit corrupt programs with SAVE_ROBOT. These\n  programs, if loaded by LOAD_ROBOT, could cause a crash.\n+ Added a config.txt (or command line) option \"startup_editor\"\n  which, if set to a non-zero value, will start MegaZeux in the\n  editor with a blank world.\n+ Fixed a bug where a robot's WALK processing, on entering a\n  transporter, could allow subsequent commands (such as GO) to\n  corrupt the board. WALK now ends the cycle in the special\n  case that a robot goes through a transporter.\n+ You can now directly import bytecode into the robot editor\n  via the Alt+I menu. The extension for the bytecode file must\n  be .bc for it to be loaded.\n+ A game loading SAVs via the LOAD_GAME counter will no longer\n  crash MZX if the SAV attempted is from an incompatible\n  version of MZX, or in any way corrupted.\n+ Fixed a crash when auto-completing lines that were greater\n  than 241 characters in length after completion.\n+ Added mouse pixel counters MOUSEPX and MOUSEPY. (Mr_Alert)\n+ Commenting a line of maximum length (241 characters) can no\n  longer grow the length of the line beyond this limit.\n+ Fixed a bug causing the software renderer to fail to center\n  when using a boxed fullscreen resolution. Also fixes a bug\n  where the PSP platform would ignore an override of the\n  force_bpp option. (Mr_Alert)\n+ Fixed a bug causing macros loaded from config.txt to be\n  expanded incorrectly. Relatedly, fixed a bug where #&lt;string&gt;\n  in the robot exitor would \"disappear\" on entry, if there was\n  no correspondingly named macro.\n\nDEVELOPERS\n\n+ Builds no longer initialize the SDL audio subsystem if audio\n  is permanently disabled with --disable-audio.\n+ Added fixes for OpenBSD to allow PNG screenshots and X11\n  clipboard support to work. Tested with OpenBSD 4.2 and \n  GCC 3.3.5.\n+ Updated Win32 builds with SDL 1.2.13.\n+ Build and package two utilities, txt2hlp (for helpfile\n  generation) and checkres, on Win32.\n+ Dependencies are now correctly tracked in the build system.\n  Modifying a header will automatically regenerate the minimal\n  set of object files that depend on this header.\n+ Out of the box MSVC support. The file \"msvc.zip\" in the root\n  of the source package now provides a Visual Studio 2005\n  project and pre-compiled dependencies. There may be stability\n  issues with the resulting binary. See also the documentation\n  in arch/msvc/README.txt.\n+ The Nintendo DS port (a.k.a. 'dsmzx') has been merged. This\n  is the most exotic port thus far, and adds features such as\n  player focus (on the second display). Sound isn't working\n  yet, and large games still won't play (due to lack of\n  memory). See docs/nds.txt for more information. (kvance)\n+ Many stack-heavy functions have been de-bloated and allocate\n  large storage on the heap (if performance is not critical).\n  This helps out platforms with a small, fixed stack size (such\n  as NDS).\n+ The built-in help system can now be disabled for embedded\n  platforms. The startup check for the help file will not be\n  performed if the help system is disabled, and so this file\n  can be omitted from distributions.\n+ The package.sh script now supports OS X, PSP, GP2X and NDS\n  packaging.\n+ The OS X port no longer requires Xcode. The new build system\n  and package.sh can create a universal Application and\n  corresponding DMG file. The new infrastructure deprecates the\n  old macosx.zip method.\n+ Most of the internal dependency on SDL has been removed.\n  Therefore, MZX can be built (but not yet work) without SDL\n  present. The only remaining component to convert is MikMod,\n  but this can be disabled, so port authors can start using the\n  feature right away (see config.sh). (Mr_Alert)\n\n<a class=\"hL\" href=\"#NEWINVER.HLP__1st\">NEW in MegaZeux!</a>\n<a class=\"hL\" href=\"#292CLOG.HLP__292\">New in Versions 2.92 to 2.92f</a>\n<a class=\"hL\" href=\"#2901CLOG.HLP__291\">New in Versions 2.90 to 2.91j</a>\n<a class=\"hL\" href=\"#284CLOG.HLP__284\">New in Versions 2.84 to 2.84c</a>\n<a class=\"hL\" href=\"#281CLOG.HLP__281\">New in Versions 2.81 to 2.81h</a>\n<a class=\"hL\" href=\"#280CLOG.HLP__280\">New in Versions 2.80 to 2.80h</a>\n<a class=\"hL\" href=\"#OLDERVER.HLP__260\">New in Versions 2.60 to 2.70</a>\n<a class=\"hL\" href=\"#OLDESVER.HLP__201\">New in Versions 2.01 to 2.60</a>\n<a class=\"hL\" href=\"#NEWIN200.HLP__1st\">New in Version 2.00</a>\n<a class=\"hL\" href=\"#ANCENVER.HLP__1xx\">New in Versions Through 1.03</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"281CLOG.HLP\">\n<a class=\"hA\" name=\"281CLOG.HLP__281\"> </a>\n<p class=\"hC\"><span class=\"f9\">New in Versions 2.81 to 2.81h</span></p>\nDecember 8, 2007 - MZX 2.81h\n\nAnother bugfix release with a couple of new features, in time\nfor the Winter 2007 Dualstream Day of Zeux. The major new\nfeatures of this release are automatic module renaming in the\neditor, PNG screenshots and many improvements to MZX on\nembedded platforms (like PSP and DS).\n\nMegaZeux can now be compiled in MZXRun mode (like the old DOS\nimplementation) and by disabling features such as unnecessary\nrenderers and audio support, can be made approximately 70%\nsmaller.\n\nThanks again to Terryn for relentlessly tracking down many\nserious bugs; we've tried to fix all the issues that have crept\nup in the last 5 months.\n\nThanks too to Exophase, Mr_Alert and Wervyn for contributing to\nthis release; your time and help is invaluable.\n\nHappy Holidays!\n\nUSERS\n\n+ Added a more lenient WAV file loader so that ModPlug isn't\n  relied on as much to play malformed WAV files (mostly old SAM\n  conversions) (Mr_Alert).\n+ Added SCORE and mzx_speed to the counter debugger\n  (Mr_Alert, ajs).\n+ Added a 16-bit software renderer and a half-width renderer\n  for the GP2X port (Mr_Alert).\n+ Made the mouse cursor in the \"opengl2\" renderer look more\n  like the mouse cursor in the other renderers (Mr_Alert).\n+ Setting vlayer_size, vlayer_width or vlayer_height to values\n  less than or equal to zero would crash MegaZeux. Limited the\n  smallest vlayer size to 1x1.\n+ Setting vlayer_width or vlayer_height to a value larger than\n  vlayer_size would crash MegaZeux. Limited the largest size of\n  either dimension to a maximum of vlayer_size.\n+ If selecting a module with a non 8.3 filename, MZX will now\n  ask you if you want to rename it to 8.3, and do so in an\n  intelligent way. This means that music can be selected in the\n  editor and correctly saved (Wervyn).\n+ The OpenBSD compiler detected some serious string bugs in\n  MegaZeux. These have now been fixed and should eliminate some\n  more potential crashes.\n+ Fixed a bug where an ENERGIZER item or use of the INVINCO\n  counter would cause the original player color to be corrupted\n  at the end of the colour blitz.\n+ Fixed a long-standing bug where set \"$string\" to \"FWRITEn\"\n  would be cheerfully ignored.\n+ Fixed a bug where a corrupt robot list could crash MegaZeux\n  (e.g. the list from Star Quest from DoZ'02).\n+ FEATURE: Screenshots are now saved in a palettized PNG file\n  format. For platforms without libpng, PNG support can be\n  compiled out, and BMP will be used instead.\n+ Fixed a bug where changing boards in the editor could\n  sometimes corrupt memory, later causing a crash (either\n  testing or coming out of testing a board).\n+ Fixed a sensor bug that happens when a sensor can't go\n  anywhere it is told to, and the player is on it (Exophase).\n+ Fixed using ABORT LOOP in some situations. Using it outside\n  of a loop still has undefined semantics and this has been\n  documented in the help file (Exophase).\n+ Setting a board option below its numeric limit is no longer\n  possible (Exophase).\n+ Fixed problems with going over Robot name character limits\n  using the .@ command (Exophase).\n+ Fixed problems with LOAD_ROBOT freezing on a robot with no\n  newline at the end of the file.\n+ Fixed a problem with \"Replace All\" in the robotic editor that\n  could sometimes cause a line to exceed 240 characters and\n  crash the editor.\n+ Fixed a problem with \"Replace\" in the robotic editor which\n  could cause a line to temporarily become 241 characters and\n  then truncate silently to 240 characters.\n+ Fixed a bug that caused the original game palette to be lost\n  when testing a game in the editor that switched between\n  Regular/SMZX1 and SMZX2/3 modes. MegaZeux should now try much\n  harder to preserve the user palette, regardless of game\n  edits.\n+ Fixed a bug causing board switching to not correctly alter\n  the x,y viewport scroll leading to the display of raw memory\n  and potential crashes, with differently sized boards.\n+ Fixed stack corruption caused by SCROLL CHAR SOUTH, detected\n  by Ubuntu's SSP (Stack Smashing Protection) enabled binary.\n\nDEVELOPERS\n\n+ Made the build system less verbose by default (like Linux).\n  This should help make warnings (due to coding errors) easier\n  to identify. If you don't like the new syntax, or need the\n  command debug, you can build with \"make V=1\".\n+ Updated Win32 builds with SDL 1.2.12.\n+ Rewrote the build system to not use recursive Makefiles.\n  Variable propagation was starting to be a problem, and\n  recursive designs are generally discouraged.\n+ Refactored the graphics rendering code to modularize the\n  renderers and reduce code duplication (Mr_Alert).\n+ GDM2S3M switched over to use inttypes.h instead of home-brew\n  types.\n+ MegaZeux now compiles on OpenBSD (and probably other BSDs).\n+ Made all unnecessary global symbols static. This should\n  improve compiler optimisations and correctness (Mr_Alert,\n  ajs).\n+ Fixed compilation of MegaZeux against SDL 1.3 SVN. However,\n  this SDL version is still in development, and MegaZeux does\n  not work correctly when compiled against it.\n+ MegaZeux now builds with the experimental MINGW-x64 branch,\n  enabling x64 binaries for Windows.\n+ MegaZeux now builds with MSVC if you apply the patch from\n  contrib/,megazeux-r326-replace-c99-variable-arrays-with-\n  malloc-free.diff . This patch is required for MSVC because it\n  makes non-compiler-specific changes (which involve converting\n  from C99 variable length arrays to malloc/free) which are\n  slower and should not be used with competent C99 compilers\n  like GCC. Microsoft Visual C++ Express Edition 2005 was used\n  to build libogg, libvorbis, libsdl and MegaZeux itself. Only\n  32bit builds were tested.\n+ MegaZeux now has size optimisations which can reduce binary\n  size when features are disabled. For example, all renderers\n  can now be disabled, and when module engines are disabled,\n  audio will not export any symbols.\n+ The entire audio subsystem can now be disabled. This further\n  reduces binary size on embedded platforms. However, SFX\n  editing still remains enabled (though useless) until editor\n  modularity is implemented.\n+ The PSP port is now officially supported, and compiles out of\n  the box. See docs/psp.txt.\n+ Renamed macos platform \"darwin\", to reflect its true nature\n  (use Xcode to build as a real Application, instead of just a\n  UNIX binary). Also fixed some bogosities with robo_ed's X11\n  includes on OS 10.5.\n+ The editor can now be disabled, a la MZXRUN from the old DOS\n  versions. Configure with --disable-editor to shrink MZX by\n  about 150k.\n+ MegaZeux can now be compiled with size optimisations\n  (--optimize-size to config.sh) for a 20% space saving.\n+ MegaZeux's core now builds with -W (basically all GCC\n  warnings) plus some additional warnings that aren't switched\n  on by this flag. All warnings have been fixed.\n  \n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJuly 4, 2007 - MZX 2.81g\n\nAgain, no significant new features have been introduced in this\nrelease. However, there have been many essential bugfixes,\nincluding improved compatibility with games made in older\nversions of MegaZeux.\n\nAdditionally, improvements have been made to the opengl2 and\noverlay2 renderers, improving performance for most users. A\nport of MegaZeux to the GP2X console has been added. MegaZeux\nhas been backported to C (rather than C++) and can operate\ncorrectly on a CPU without a floating-point unit.\n\nParticular thanks go out to Mr_Alert (for his valuable bug\nfixes), Lancer-X (for fixing what I was too lazy to) and Terryn\n(for finding many annoying bugs that nobody else could).\n\nUSERS\n\n+ Fixed a bug in the audio code. The linear resampler was not\n  taking volume into account, which broke changing the volume\n  of samples (WAV and Vorbis) which cannot natively alter their\n  volumes.\n+ Fixed a regression in the overlay editor caused by the new\n  editor space semantics.\n+ Screenshots are now rendered to a separate texture using the\n  8bit software renderer. This means that the hardware scalers\n  will not affect the quality of the screenshot. It also fixes\n  a bug when using opengl2, which would dump only a white\n  screen.\n+ Temporarily reverted a bugfix that broke Zeux IV - Forest of\n  Ruin. I'm not dropping the bugfix, I just can't immediately\n  see what's wrong.\n+ Fixed a bug where setting the viewport to negative\n  coordinates would crash MegaZeux. There was code to handle\n  this, but it was wrong.\n+ Fixed a bug that permitted the mouse y coordinate to be\n  warped to row 25, which does not exist. This bug caused some\n  of the renderers to crash, and the software renderer to draw\n  in memory it did not possess.\n+ Fixed a bug where games made before 2.68 could have available\n  the \"key?\" counters, unsupported in that version. This caused\n  collisions with counters with the key? name used with\n  inc/dec/mul/div/mod. Fixes \"Doom Keep\".\n+ Imported libmodplug 0.8.4, which adds MIDI/PAT and ABC format\n  support, fixes some bugs in the mixer, and should build on\n  more platforms.\n  NOTE: MID files currently cannot be selected in the editor,\n  because they do not play correctly.\n+ Improved the performance of the \"opengl2\" renderer, by\n  removing the convoluted 3D drawing commands and replacing\n  them with 2D ones. Reduced the quad count by using an\n  intermediary 80x25 texture. MegaZeux now depends on fewer GL\n  features. [LogiCow]\n+ Introduced an \"fsafegets\" to work around problems where\n  robots exported by a Windows version of MegaZeux would not\n  load on other platforms. This was due to differing EOL style\n  and broke at least one game (Termination).\n+ Renamed \"force_resolution\" to \"fullscreen_resolution\" to\n  better match its semantics with the scaling renderers. The\n  new name is less accurate for software render modes, but most\n  people using software will not want to change it from the\n  default anyway.\n+ Fixed a bug where the variable-length string allocator would\n  prematurely bail out when reading a string (of indeterminate\n  length) from a file with the set \"$var\" to \"FREAD\" syntax.\n+ Fixed a bug where more than 256 errors would crash the\n  robotic checker.\n+ Improved performance of the overlay2 (faster) renderer\n  (Mr_Alert).\n+ Made the transparent overlay \"really\" transparent when used\n  in conjunction with sprites (Mr_Alert).\n+ Fixed a bug reported by Mr_Alert where MZX would not handle\n  short, non-looping mods in the editor. The editor would try\n  to destroy the mod again, even after the callback had\n  destroyed it (premature termination).\n+ Fixed a bug with SWAP WORLD where file translation would\n  occur but the result would mistakenly not be used. This broke\n  some uses of SWAP WORLD on non-Windows platforms (Mr_Alert).\n+ Fixed a bug where using JUMP to MOD ORDER right after\n  switching boards would fail due to the board music not having\n  been loaded yet (Mr_Alert).\n+ Fixed a bug where games made before 2.80 would inadvertently\n  trigger \"PLAYERHURT\" due to using the SET command to reduce\n  the amount of health (Mr_Alert).\n+ Fixed a bug where player clones were generated when entering\n  transports during FREEZETIME (Lancer-X).\n+ Debug menu is now eradicated on leaving the editor\n  (Lancer-X).\n+ Debug menu is now properly painted over when the board size\n  is &lt; the editor viewport. Fixes various graphical glitches\n  (Lancer-X).\n+ Fixed a crash bug when playing older MZX games from read-only\n  media (such as a CD) or where file-system permissions\n  prohibited creating SAM conversions (Lancer-X).\n+ Fixed bug where certain file formats would not be\n  automatically converted if their extensions were mixed or\n  upper case (e.g. OGG/SAM/GDM).\n+ Restored functionality of \"if lasttouch DIR\" which has been\n  broken since MZX 2.02.\n+ Fixed a bug where attempting to decrypt a read-only world\n  file would result in a crash (Mr_Alert).\n+ Fixed several bugs where an error loading a world file would\n  result in crashes in several different situations (Mr_Alert).\n+ Fixed a bug where a robot using the BECOME command to change\n  into a PushableRobot or vice versa would freeze (Mr_Alert).\n+ Fixed memory leaks in the file selection dialog, the counter\n  debugger, the collision list and the global robot (Mr_Alert).\n+ Updated counter list (see docs/counter_list.txt in the\n  source) (Terryn).\n+ Fixed a bug where pressing escape when editing the effect of\n  a ring or potion would result in an invalid parameter which\n  would later cause a crash if edited again (Mr_Alert).\n+ Fixed a bug in which robot-driven text boxes using option\n  commands (the ? command) could overflow by two characters and\n  spill over the side (Lancer-X).\n+ Fixed the list box searching mechanism (used in the file\n  manager and F11 counter list) and made the existing function\n  more understandable. (Lancer-X).\n+ Fixed a bug in which the message string given to the 'ask'\n  command could spill over. Now, the 'ask' dialog resizes if\n  possible, and clips when no further resizing can be performed\n  (Lancer-X).\n+ Clipped the 'input string' message properly, to prevent\n  similar overflow.\n+ Fixed a bug with the EXPLODE, DIE, DIE ITEM and BECOME\n  commands when used with the global robot (would clear the\n  global robot, eventually corrupting memory when in the\n  editor). Presumably, these commands are bogus for the global\n  robot, and have been disabled.\n\nDEVELOPERS\n\n+ Rewrote config.sh to use POSIX sh compatible functions, so\n  that there is no dependency on the BASH interpreter.\n  Surprisingly, some distributions still don't enable BASH by\n  default (using csh, ash or zsh instead).\n+ Ported most of MegaZeux back to C. Many more changes were\n  required than I anticipated; MZX was using more C++ features\n  than I expected. The only exception is audio.cpp, which\n  cannot be ported back to C because it uses ModPlug's C++\n  classes directly (but I plan to split this file up shortly).\n  NOTE: The changes required were enormous, so I might have\n  introduced some weird bugs! Please test!\n+ Enabled GCC's -W flag for even more warnings, switching off\n  unused parameter warnings (useful for delegates). Mostly\n  typing fixes, but it found a bug in string handling.\n+ No longer suppresses char-subscript warnings, and fixed up\n  any remaining abuses in the tree.\n+ Added manpages for 'megazeux' and other binaries for the\n  Debian packages. Complied with the Debian packaging\n  guidelines by providing a copyright note, listing significant\n  contributors to MegaZeux.\n+ Added support to the build system for supporting icons\n  modularly. See contrib/icons/README for more information.\n+ The debug build (make DEBUG=1) now enables GCC 4.x's stack\n  protector. This breaks compatibility with GCC 3.x, but you\n  can just remove the flag if you don't want to use it (the\n  stack protector will improve stack corruption detection and\n  provide more usable debug traces).\n+ Custom Random() implementation to provide a more uniform\n  number distribution. Factored out for future (better)\n  implementations.\n+ The audio backend (audio.cpp) has been modularised to support\n  the use of mikmod instead of modplug. This should enable\n  ports of MZX to platforms without an FPU, and improve\n  performance on platforms with weak FPUs.\n+ Added GP2X port to config.sh, based on work done by Simon\n  Parzer.\n  \n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJanuary 30, 2007 - MZX 2.81f\n\nThis release is mostly about the new renderers, the first of\nwhich was introduced in the previous version. There's also a\nfew important bugfixes, and a lot of internal tidy-up work. I'd\nlike to thank Mr_Alert, Quantum P. and LogiCow for contributing\nto this release. Thanks guys.\n\nUSERS\n\n+ Renamed the force_32bpp config option to force_bpp, in\n  preparation for 16bit OpenGL render modes. This option now\n  takes 8, 16 or 32. 16 is reportedly broken on Windows, so\n  stick to 32 for now.\n+ Added infrastructure for \"pluggable\" renderers. This code\n  isn't perfect, but it's far better than the mess in 'e'.\n  Defaults to the 'software' render mode.\n+ Added Logicow's alternative OpenGL renderer. For more\n  information about this renderer, see config.txt. NOTE: This\n  code may be buggy! Please test!\n+ Added Mr Alert's YUV overlay renderers. One does full YUV\n  macropixel approximation, the other (faster) render does\n  chroma supersampling. See config.txt for more information.\n  NOTE: This code may be buggy! Please test!\n+ Simplified Exophase's OpenGL renderer present in 'e', and\n  fixed a few bugs that caused it to not work for some people.\n+ Really made MegaZeux use 'directx' by default on Windows. The\n  code in 'e' was non-functional. Use 'windib.bat' to run\n  MegaZeux with the SDL windib driver.\n+ The OpenGL renderers now have a 'filter' option that allows\n  you to choose linear (where pixels are interpolated, looks\n  \"blurred\") or nearest (where nearest-neighbour approximation\n  occurs, looks \"sharp\").\n+ Mouse warping was broken when using any of the hardware\n  renderers. There should be code in there now to take account\n  of this (thanks Mr_Alert).\n+ Added an option 'editor_spaces_replace' which allows you to\n  revert MZX's space overwrite behavior to the semantics of 'd'\n  (the feature was removed in 'e'). By default, the behavior is\n  unchanged (the same as 'e').\n+ F6 (the debug menu) can now no longer be enabled anywhere but\n  in the editor Alt-T test mode. In 'e', it was possible to\n  enable on the title screen, but could not be enabled in a\n  game. Like the cheats, this option is now visible only in\n  test mode.\n+ Fixed a bug where the global robot could be exited via some\n  legal commands, in an abnormal fashion. The bug resulted in\n  all the code up to the offending command being executed over\n  and over.\n+ Fixed a bug in the audio code. The linear resampler was not\n  taking volume into account, which broke changing the volume\n  of samples (WAV and Vorbis) which cannot natively alter their\n  volumes.\n+ Fixed a regression in the overlay editor caused by the new\n  editor space semantics.\n+ Temporarily reverted a bugfix that broke Zeux IV - Forest of\n  Ruin. I'm not dropping the bugfix, I just can't immediately\n  see what's wrong.\n+ Screenshots are now rendered to a separate texture using the\n  8bit software renderer. This means that the hardware scalers\n  will not affect the quality of the screenshot. It also fixes\n  a bug when using opengl2, which would dump only a white\n  screen.\n- The force_height_multiplier option has been removed. A lot of\n  code wasn't properly designed to handle it, there have been\n  mouse warp bugs with it for years, and nobody seems to use\n  it. If people want stretching, they can choose one of the\n  four hardware renderers to achieve this.\n- Removed the 'lame/1337' menu feature.\n\nDEVELOPERS\n\n+ OpenGL can now be disabled via config.sh. This allows\n  MegaZeux ebuilds to be constructed on systems that do not\n  have any form of OpenGL support. (Although MZX runtime loads\n  the OpenGL library, 'e' required the headers to build\n  correctly. This is now no longer the case.)\n+ On Windows, due to an ATi driver bug, I have provided a means\n  of linking directly to opengl32.dll, instead of relying on\n  the dynamic loader. This reduces binary portability, but\n  fixes many bug reports of being unable to fullscreen on ATi\n  video cards. See OPENGL_LINKING for more information.\n+ Improved support for cross compiling with mingw32 on Linux,\n  combined the win32 Makefile with this new support.\n+ Rewrote the config.sh script. All of the options have\n  changed, and the broken platform auto-detection has been\n  removed. See ./config.sh for more information.\n+ Rejigged MegaZeux's headers so that they can be used in both\n  C and C++ mode. Renamed fsafeopen.cpp to fsafeopen.c.\n  Hopefully by 'g' most of MegaZeux should be ported back to C,\n  instead of the \"C++\" it is now.\n+ Fixed up the 'txt2hlp' utility which Terryn has been using a\n  version of to build the internal MZX help system. This binary\n  is built in the source distribution, but it is not\n  distributed with the MegaZeux binaries.\n+ Moved some antiquated Alexis code out into 'old'. No attempt\n  has been made to make it compile, it is provided purely for\n  reference.\n+ For the windows binaries, \"windib.bat\" is now generated by\n  package.sh and auto-generated for the name of the MegaZeux\n  executable.\n+ Updated Xcode package from Quantum P. (see macosx.zip).\n+ Fixed an invalid assumption in config.sh where /bin/sh was\n  chosen as the shell script interpreter. This should have been\n  /bin/bash, as 'sh' is not required to support functions,\n  which config.sh uses.\n  \n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nJanuary 19, 2007 - MZX 2.81e\n \n+ Made grabbing in the editor not combine background color\n  - only uses \"special\" in game colors for player. tell me if\n  anything ends up being weird because of this.\n+ Possibly fixed an obscure bug where moving something happened\n  immediately if it was sent to a label by a robot further\n  east/south than it and it moved north or west (has to do with\n  the way robots are reverse scanned). Tell me if this changed\n  any behavior for the worse and I'll change it back or try to\n  work out something new.\n+ Added GDMs to ctrl + n; this will, of course, auto convert\n  and play the s3m.\n+ Added ability to preset player locked status from board\n  settings.\n+ Instead added ability to debug variables (counters and\n  strings) ingame with F11. There's also an option to export\n  the current variables to Robotic program that sets them.\n+ Fixed bug where moving a block with the player into an\n  overlapping region leaves a space where the player was.\n+ Fixed bug with a robot indirectly sending itself to a\n  subroutine via send all or send name causing it to loop the\n  send.\n+ Added compatability hacks for key# prior to MZX 2.69 worlds\n  and ridNAME falling through in MZX 2.70 and earlier worlds.\n+ F6, F7, F8, and F11 debug/cheat keys only work in editing\n  mode now (as things were prior to the port) You can still\n  save/load in the editor so if you want all of these things\n  you can play the game from the start there.\n+ Space in the editor no longer deletes something of similar\n  type that is beneath; not sure what the point of this was\n  anyway.\n+ Fixed bug causing cursor to clipped be out of bounds in SMZX\n  char editor if changing to smaller multichar edit region.\n+ Accidentally messed up screen centering in fullscreen for\n  32bpp mode, fixed.\n+ Added hardware scaling option. You can now supply a window\n  resolution besides 640x350 and allow for window resizing if\n  hardware scaling is on; this will also scale fullscreen\n  output to fill the entire screen. This can slow down\n  rendering somewhat.\n+ Fixed bug causing flip block to crash in the editor.\n+ Made blocked directions relative to the player for put dir\n  player.\n+ Fixed bug where putting something to a direction relative the\n  player overwriting the robot could crash MZX.\n+ Fixed ability to input in input boxes by clicking on their\n  question string.\n+ Removed the bogus patch to Modplug and correctly fixed it in\n  the build system.\n+ Added 'debian' subdirectory for building Debian and Ubuntu\n  upstream packages. Hopefully MegaZeux will be in the primary\n  pool in a few months.\n+ Added OS X xcode project files (see 'macosx.zip'). Fixed many\n  bugs relating to endian that caused MegaZeux to be buggy on\n  big-endian architectures (like PPC). Credit goes to Quantum P\n  for finding these bugs and engineering high quality\n  solutions.\n+ Made 'directx' the default video render again on Windows.\n  NOTE: This overrides the default SDL behaviour, but will not\n  be applied if you set SDL_VIDEODRIVER yourself.\n+ Repaired the 'linux-static' target so that it no longer\n  includes a system C++ library, which caused unpredictable\n  results on distros without a static version.\n+ Fixed a locking bug with the audio code that caused hangs at\n  startup on OS X. Also provided a mutex implementation using\n  GNU pthreads as a temporary workaround for an SDL bug on the\n  Linux platform.\n+ Added PlayStation Portable (PSP) port. This code was written\n  by Exophase and is highly experimental. It may not work at\n  all for you. Please see docs/build.txt for more information\n  regarding this port.\n+ Fixed mouse movement from being affected by height_multiplier\n  when not in fullscreen mode.\n+ Fixed height_multiplier config.txt option allowing you to\n  enter really stupid values (like negatives, 0, and values too\n  large for the resolution).\n+ Added in an extra video mode check to stop MZX from crashing\n  on video modes that the video card can not reproduce.\n+ Fixed Avalanche to a constant placement rate of 1/18 (this\n  caused MZX to deliver an uneven number of boulders, and to\n  crash with certain board sizes).\n+ Fixed sprite collision box to stop MZX from crashing when\n  stupid values are entered.\n+ Fixed setting the viewport size to weird values like some old\n  MZX games do.\n+ Default fullscreen resolution is now 640x480; this can be\n  changed in config.txt .\n+ The config.txt option force_32bpp is now enabled by default.\n+ Seeking with mod_position when using a .WAV file as\n  background music fixed (thanks Mr_Alert).\n- Removed ability to change SMZX mode ingame (F11).\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nDecember 10, 2006 - MZX 2.81d\n\nNOTE: This release was made by Alistair Strachan (ajs) and not\n      by Exophase. As such, any problems present in this\n      release that were not present in 2.81c should be reported\n      directly to ajs.\n\n+ Fixed a compilation failure on Linux, due to SDL no longer\n  depending on libX11. Now we manually link X11 into MZX if\n  necessary.\n+ Various build system improvements, fixing bugs in the\n  prefixing of dependencies.\n+ New libmodplug 0.8 imported, fixing many endian problems on\n  big-endian machines, integrating all of our local patches to\n  0.7.\n+ Fixed bug causing MZX to freeze when starting up on Win9x\n  machines.\n+ Fixed a warning generated by GCC 4.1.\n+ Updated the GPL boilerplates project-wide to the newest FSF\n  address.\n+ Fixed a string range check causing an obscure crash in\n  certain games.\n+ Updated the build.txt documentation.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nDecember 14, 2005 - MZX 2.81c\n\n+ Oops, accidentally broke shift + F2. Fixed that.\n+ Also accidentally broke &amp;+counter&amp; for full hex\n  representation. Fixed.\n+ Fixed memory leak problem with playing certain WAVs in a\n  loop.\n+ Fixed inconsistency of bad viewport sizes behaving\n  differently on current versions from old DOS versions.\n+ Accidentally broke joystick stuff in config.txt (has to do\n  with way configure options were being read), fixed.\n+ Fixed bug causing crash when loading MZBs larger than the\n  current board size.\n+ Made cursor hidden in alt + V in editor.\n+ String comparison failed with nulls in the strings, fixed.\n  Also should be slightly more optimal.\n+ Fixed bug when using negative numbers for\n  if sprite_colliding \"counter\".\n+ Fixed math operations (inc, dec, etc) not working on string\n  indeces. \n+ Added ability to force screen to 32bpp. Fixes some slight\n  rendering issues, and if you have problems with fullscreen\n  let me know if this helps (try it without first though). See\n  force_32bpp in config.txt. \n+ Fixed sprite clipping bug with respect to overlay. \n+ Fixed bug where pressing enter on things besides robots,\n  scrolls/signs, or sensors in the editor would clear whatever\n  was underneath it. \n+ Accidentally broke SFX with optional PC speaker chains\n  (played both, should only play PC speaker when digital music\n  is off, fixes Bernard the Bard).\n+ Made last character in char selection for F3 and alt + C\n  remembered (note that they're remembered in two different\n  places for both).\n+ Accidentally broke life animations, fixed.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nNovember 26, 2005 - MZX 2.81b\n\n+ Fixed inability to make proper .savs of worlds with strings.\n  (they'd crash when loaded..)\n+ Fixed PC speaker audio bug causing a constant high pitched\n  noise to be played instead of PC speaker audio sometimes.\n+ Fixed some issues with long pathnames.\n+ Fixed a bug causing Caverns to crash in recent versions (long\n  story, it was most likely due to an error in ver1to2).\n+ Now when you set mzx_speed in a game you can no longer change\n  the speed from the F2 menu. Setting mzx_speed to 0 reallows\n  this (and doesn't set the speed).\n+ When loading a game its speed is now set to the speed MZX\n  started with (whatever's in config.txt, or the default of 4).\n+ Added backup_ext config.txt option to specify the extension\n  of backup files (default is .mzx).\n+ Fixed backup_interval for config.txt possibly being broken.\n+ Fixed a bug messing up the death board on some old MZX games\n  (like Nick Brick 2)\n+ Escaped more things and made displays always in escaped form\n  for certain character sequences. It should be impossible to\n  type non-escaped forms. The following should be used:\n  \\0 for 0 (this probably won't work in strings, but in chars\n  should)\n  \\t for tab (character 9)\n  \\n for newline (character 10)\n  \\r for carriage return (character 13)\n  \\\" for double quote\n  \\\\ for slash\n+ Copy + paste on escaped character won't unescape them\n  anymore.\n+ Fixed error message for invalid lines in Robotic.\n+ Fixed inability to import text files from other directories.\n+ Huge overhaul of the source (proper types for things,\n  directions, equalities, conditions, chest items, and\n  potions), if anything is suddenly broken now let me know.\n+ Made scrolls/signs only display text (letters, numbers, etc).\n  in the default char set. That should be enough for now.\n+ Added mousewheel support for robot editor and robot box\n  display.\n+ Fixed inability to load MZMs from other directories in the\n  editor.\n+ Wrapped audio stuff in proper mutex, hopefully this fixes\n  some issues (like crashing when changing mod_frequency a\n  lot).\n+ Long current directory paths no longer write out too much in\n  the file loader (instead the last bit is shown with a ...\n  prefixing the beginning)\n+ Decided to be nice and make board_scan not crash. Don't use\n  it. It's only there to make one legacy game work. If you use\n  it I will personally scold you. And don't tell other people\n  to use it (that means you CJA). Use\n  copy block x y w h \"$str\" t\n  instead. If you don't know what that means read the help\n  file, it explains everything.\n+ Removed ability to copy + paste after changing board\n  dimensions of the source under any circumstances (alt + R,\n  alt + Z, import world, import board).\n+ Fixed appearance of ghosts in F10 menu.\n+ Prevented char editor from counting moving the cursor as an\n  undo step if nothing was actually drawn.\n+ Made pressing escape on initial char selection/board\n  selection/param selection for things cause it to cancel\n  placing anything.\n+ Made it impossible to set board width/height to 0 again\n  (oops).\n+ Made starting lives and starting health take effect\n  immediately for the first alt + t.\n+ Added ability to play OGG from alt + l (but not the other\n  mods, don't want to clutter that up).\n+ Made it so if no note follows an embedded SAM in a play\n  string it's played at native frequency.\n+ Accidentally made loading worlds in the editor not change the\n  current directory, fixed that.\n  \n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nNovember 20, 2005 - MZX 2.81\n\n+ Fixed a bug where MZX world/save names &gt; 12 chars could cause\n  weird things to happen (like doors breaking).\n+ Fixed problems with help file/charsets loading when loading\n  MZX outside of the directory MZX is in. This should fix file\n  associations on Windows as well.\n+ Changed board selector so when board 0 is \"(no board)\" it\n  doesn't actually refer to the title but to no board.\n+ Made import world not overwrite the title string.\n+ Fixed bug that causes crash when trying to flood fill an area\n  with the color it already is in the SMZX char editor.\n+ Redid audio engine. Everything is unified now, meaning that\n  anything you can use as a mod you can use as a sam and\n  vice-versa.\n+ The new audio engine uses its own master resampler that has\n  three interpolation modes - see config.txt for more\n  information.\n+ sam 0 filename will play a file at its native frequency (note\n  that SAMs that have been converted from WAVs are set to be\n  played at 8363Hz).\n+ Added support for OGG vorbis audio files.\n+ Fixed bug causing SFX volume control in F2 menu to not work.\n+ Removed limitation on number of SAMs that can be played\n  simultaneously.\n+ Fixed bug where the mouse got \"stuck\" in the black border\n  edges of non 640x350 fullscreen resolutions.\n+ Fixed issues with message boxes being part default palette\n  part current palette, they now always use the current\n  palette.\n+ Added mod_position counter. What these actually set/return is\n  dependant on the type of file loaded. Modules use the current\n  row, OGGs use the current PCM sample.\n+ Added mod_frequency counter. There are a few things to note\n  here: Modules have a \"nominal\" frequency of 44.1KHz. Other\n  data types have their own nominal frequency to prevent output\n  from sounding differently depending on the audio_sample_rate\n  in config.txt. For OGGs and WAVs the nominal frequency is the\n  one the file is encoded at. Changing the frequency can cause\n  a noticeable one time popping sound, so it might not be\n  desirable to slide it. This is much more prominent with\n  lowering the frequency than raising it. This value is capped\n  so it can't reach below 16.\n+ Changed alt + L to play back at natural frequency instead of\n  8363Hz.\n+ Fixed bug causing sensor deletion while the player is on top\n  to destroy the player.\n+ Fixed bug causing imported boards to possibly crash after\n  being tested.\n+ Fixed bug causing save_game and save_world to not work if a\n  file with the given name isn't already present.\n+ Changed function counter matching removing restriction on\n  number of digits for parameters. 10+ digit inputs should no\n  longer fail (for instance, abs-123456789)\n+ Made counter/string names internally variable length instead\n  of a fixed 14 chars. There is now no longer a name length\n  limitation.\n+ Changed alt+8 for mod * to just * in the hotkey listing.\n+ Fixed crash when referencing (by param) sprites &gt; 256.\n+ The string system has been redone. Strings are now\n  dynamically sized and don't have an artificial maximum\n  length. Writing to string.N will guarantee that the size of\n  the string becomes at least N, while reading in this way will\n  return 0 if out of bounds to maintain the illusion of null\n  termination. Be careful when using this.\n+ $str.length returns the length of string $str (this is faster\n  than iterating through it to find when chars hit 0).\n+ Vlayer is dynamically sized. The vlayer_width/vlayer_height\n  counters still work as per usual, but the vlayer_size counter\n  has been added to adjust the maximum size. The default is\n  32768.\n+ Fixed bug not allowing things to move over goop.\n+ Fixed bugs causing current directory to be changed when\n  importing things from other directories.\n+ Properly implemented support for volume \"string\".\n+ Fixed a few commands not working when they should from the\n  global robot (such as put to dir of player).\n+ Fixed a bug where going to a label at the end of a robot\n  would treat it as if it's the first of its name in a sequence\n  of labels.\n+ Fixed a bug involving moving the a block with the player not\n  moving what was underneath the player.\n+ New help file, thanks MUCHLY to Terryn for pulling off this\n  enormous effort! (This is what you're reading from.)\n- Save files from 2.80 are not compatible due to several\n  changes in the save format.\n\n<a class=\"hL\" href=\"#NEWINVER.HLP__1st\">NEW in MegaZeux!</a>\n<a class=\"hL\" href=\"#292CLOG.HLP__292\">New in Versions 2.92 to 2.92f</a>\n<a class=\"hL\" href=\"#2901CLOG.HLP__291\">New in Versions 2.90 to 2.91j</a>\n<a class=\"hL\" href=\"#284CLOG.HLP__284\">New in Versions 2.84 to 2.84c</a>\n<a class=\"hL\" href=\"#2823CLOG.HLP__283\">New in Versions 2.82 to 2.83</a>\n<a class=\"hL\" href=\"#280CLOG.HLP__280\">New in Versions 2.80 to 2.80h</a>\n<a class=\"hL\" href=\"#OLDERVER.HLP__260\">New in Versions 2.60 to 2.70</a>\n<a class=\"hL\" href=\"#OLDESVER.HLP__201\">New in Versions 2.01 to 2.60</a>\n<a class=\"hL\" href=\"#NEWIN200.HLP__1st\">New in Version 2.00</a>\n<a class=\"hL\" href=\"#ANCENVER.HLP__1xx\">New in Versions Through 1.03</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"280CLOG.HLP\">\n<a class=\"hA\" name=\"280CLOG.HLP__280\"> </a>\n<p class=\"hC\"><span class=\"f9\">New in Versions 2.80 to 2.80h</span></p>\nJune 6, 2005 - MZX 2.80h\n\n+ Fixed a bug which could cause crashes when quitting the game.\n+ Fixed some bugs when changing boards and other things that\n  can cause duplicate players.. I think.\n+ Fixed a bug that could cause crashes when adding boards.\n+ Improved response time in editor for slower computers/high\n  load situations.\n+ Fixed some endian issues with the GUI.\n+ Fixed some crash when moving the mouse cursor around in the\n  editor.\n+ Fixed bug where you if you had a robot whose name is the same\n  as the global robot it wouldn't get messages (fixes yoyo in\n  Weirdness).\n+ Fixed debug box not moving with text input.\n+ Fixed bug with duplicate player appearing when killed and a\n  new one can't be put at 0, 0.\n+ Added copy/paste for outside of MZX to/from the robot editor.\n  It only works in Windows and X11, and functionality may be\n  limited in X11 right now (currently seems to work in native\n  X11 apps and GTK 2.6 apps but not earlier GTK or QT, also try\n  shift + insert to paste).\n+ Fixed bug in resizing involving overlay blanking.\n+ Fixed clear messages/projectiles not working (and damaging\n  the game instead).\n+ Fixed behavior of P key in editor for wildweasel.\n+ Fixed random in Robotic not correctly swapping the range if\n  they're given in the wrong order.\n+ Fixed clip length in [ messages.\n+ Fixed crash when changing volume without a game loaded.\n+ Redid internal GUI system, fixes some minor things.\n+ New file loading/saving window - press del to delete a\n  file/dir, alt + n to create a new directory, alt + r to\n  rename a file/dir.\n+ Added PC speaker volume control to F2 settings and\n  config.txt.\n+ Fixed yet another crash bug with resizing boards.\n+ Fixed inability to type * in text placement in the editor\n  (although this adds inability to turn on mod * while F2 is\n  on...).\n+ Added ctrl + n in the editor to load a module for listening\n  only (won't set the current board's module, and will let you\n  choose ones from different directories).\n+ Fixed crash on macros with more variables than can be\n  displayed in their configuration.\n+ Fixed bug that causes char selection cursor to reset to 0 on\n  unhandled keys (and continuously do so for lock keys).\n+ Tweaked ctrl + dir in text entry boxes.\n+ Added gdm2s3m in-tree to the contrib/ directory. gdm2s3m no\n  longer needs to be installed on the system before compiling\n  mzx.\n+ Improved the build system to automatically build .c and .cpp\n  files with compound system CFLAGS/CXXFLAGS, respectively.\n+ Made package.sh automatically ship the source package with a\n  Makefile.dist to warn the user that they need to run\n  config.sh before 'make'.\n+ Rectified inconsistency in source copyrights.\n+ Added multi-character editor. Select multiple keys in the\n  character selection with shift. The char editor also now has\n  the ability to perform operations (delete, copy, scroll, etc)\n  on subblocks. Hold down shift or press alt + b to highlight a\n  region (press escape to remove the latter). Blocks copied\n  like this will be pasted to where the cursor is at. Other\n  small things in chareditor tweaked/changed... No longer press\n  tab to toggle through set/clear/toggle draw modes, instead\n  tab for set mode and shift + tab for clear (no more toggle).\n  Mouse behavior is modified as well. In non-SMZX left click\n  sets, right click clears. Shift + F2 will cut a block (clear\n  + copy).\n\n  alt + x/alt + i can now be used to import/export partial\n  charsets while in the char editor. You can do so for several\n  in series: put a # in the name of the charset then set the\n  First for the first number # will be replaced with and the\n  Count value to indicate how many in series to work with. For\n  instance, saving s#.chr with first = 0, count = 3, starting\n  at offset 100, with a 2x2 char selection will save charsets\n  s0.chr from 100, s1.chr from 104, s2.chr from 108, and\n  s3.chr from 112.\n\n  *** NOTE *** Series import/export will only work correctly\n  with char selections that are one in height (they can still\n  be split up another way in the editor itself). If you want to\n  use partial charsets on your edits, it's important that you\n  select all the chars in a row.\n\n+ Made characters for the editor/GUI use another charset that's\n  protected. Please notify me if any characters are incorrect.\n  Modify mzx_edit.chr to change this charset. The same thing\n  goes for colors. It doesn't work for SMZX, which also might\n  look a bit different in the editor...\n+ Added option (defaults on) to protect chars 32-127 in input\n  boxes and strings in the robot editor.\n+ Mouse warping goes to middle instead of top corner now, so\n  there isn't a bias towards moving up.\n+ Hopefully fixed another bug with the cursor and changing\n  boards...\n+ Fixed module looping problem in modplug...\n+ Added libmodplug 0.7 with both patches (see contrib/)\n  in-tree. Removes system dependency on libmodplug.\n+ Made auto-backup on by default. (3 count)\n+ Made if touching idle, beneath always false instead of like\n  nodir.\n+ Fixed bug that caused bad things to happen if you pressed too\n  many different keys too rapidly.\n+ Made mouse wheel emulate up/down in dialog boxes and list\n  menu.\n+ Added ctrl + backspace to intake (delete previous word).\n+ Made modulo operator use floored instead of truncated mod\n  (uses positive remainder instead of negative).\n+ Fixed crash when testing after using ctrl + z to clear a\n  board.\n+ Fixed bug where sending other robots to subroutines caused\n  the return address to be to the next instruction like local\n  subroutine calling works.\n+ Fixed bug where going to a label on the last line of the\n  robot could screw the game up.\n+ Fixed a bug where the editor froze if you tried to fill the\n  board with players. (eheh...)\n+ Fixed a bug that could cause crashes when sending all sensors\n  something.\n+ Fixed some crashes when exporting/saving fails.\n+ Fixed import world's ability to go over the board limit and\n  cause crashes.\n+ Allowed input of decimal numbers for params.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nApril 1, 2005 - MZX 2.80g\n\n+ Fixed crash on alt + x in robot editor.\n+ Fixed missing line on alt + h in robot editor.\n+ Introduced incorrect enter action in robot editor (didn't\n  reset to beginning of the line), fixed.\n+ Fixed garbage appearing when moving from a larger to smaller\n  board and being outside of that board's scroll region.\n+ Fixed bug that could cause glitches/crashing when resizing\n  the board.\n+ Fixed error with global next option not retaining the three\n  checkmark options correctly.\n+ Added work around so that move block moves the player (won't\n  move it on inter-board moves).\n+ Fixed bad palette loading for Linux introduced in 2.80e or f\n  or something\n+ Made it so block highlighting doesn't highlight the debug\n  window.\n+ Made the debug window move if necessary when home/end is\n  pressed.\n+ Added autorepeat buffering so previous keys can be resumed.\n+ Fixed bug with swap world possibly not working (crashing??)\n  off Windows.\n+ Fixed more problems with garbage/crashes when resizing with\n  the cursor in a position causing the scroll to go off the\n  edge.\n+ Fixed incorrect text cursor offset with\n  force_height_multiplier on.\n+ Redid way directories are loaded internally so you can load\n  dirs with over 4096 entries now. Might be faster (unsure)\n+ Chest contents list menu looked funny, fixed.\n+ Changed default.spl to smzx.pal so you can load it more\n  sanely.\n+ Export block wasn't getting the last selected line. Fixed.\n+ Hacked scroll editor so it wouldn't crash when removing\n  lines. Scroll code either needs to be 100% overhauled or\n  replaced by robots somehow...\n+ Fixed config files not being closed.\n+ Added include file option for config files. Use it like this:\n  include \"config file\"; e.g., \"include subconfig.cnf\" will\n  load subconfig.cnf's options.\n+ Fixed freadN not terminating strings.\n+ Fixed graphical glitch when using the mouse in the char\n  selector.\n+ Fixed save games crashing when they can't load fopened files.\n+ Fixed some other problems with save games and fopened files.\n+ Accidentally had title screen running a bit slow...\n+ Value strings starting with ( not parsed as an expression if\n  they don't end with the ).\n+ Fixed problem with key_code being triggered for keys that\n  aren't in-game.\n+ Added extended macros. This allows for parameter based macros\n  to be entered in the robot editor via a window or by command.\n  See macro.txt for more information.\n+ Fixed player cloning after flip/mirror and player placing.\n+ Added random seeding that was mysteriously missing...\n+ Finally added drive changing for Windows builds.\n+ Fixed mousex/mousey for resolutions other than 640x350 (only\n  applies to fullscreen).\n+ Fixed crash on weird invalid death/endgame boards...\n+ % and &amp; messages clip correctly now.\n+ Fixed potential crash on double closing the files.\n+ Fixed crash bugs with placing sensors and maybe scrolls.\n+ Fixed sending sensors when you have robots of the same name\n  (fixes Weirdness chapter 1).\n- Removed the unimplemented if player dir and if not player dir\n  commands from RASM.\n  \n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nDecember 26, 2004 - MZX 2.80f\n\n+ Fixed a bug that could cause crashes when auto-quoting params\n  in the robot editor (eg, set x 1 -&gt; set \"x\" to 1).\n+ Fixed a bug that could crash the robot editor if you added a\n  new line prior to the first line of a marked block, then did\n  an action on it.\n+ Fixed a bug where clearing the first and only line could\n  cause it to appear as if it hadn't been cleared at all.\n+ Unified global and global next parameter setting so that\n  nothing is lost between first/next but information can be\n  cancelled without application.\n+ Fixed E/S block markers appearing in the robot editor when\n  they should be off the screen.\n+ Left click position in robot editor mysteriously disappeared\n  after having been added somewhere after 2.80d. Re-added.\n+ Added option to hide the hotkeys help and horizontal border\n  in the robot editor with alt + h. Also added a config.txt\n  option to have it default this way.\n+ Search/replace in the robot editor. ctrl + f to find or\n  replace/replace all, ctrl + r to repeat either search or\n  replace (depending the last one you did, if you cancelled\n  this does nothing).\n+ The load_game counter sequence was broken; fixed.\n+ Hopefully fixed all means of overrunning the current line max\n  length in the robot editor...\n+ Fixed robot editor validation not showing every 13th line.\n+ Fixed aesthetic problem with validation report...\n+ Fixed crash with setting message column less than 0.\n+ A couple things added for 2.80e mysteriously disappeared in\n  source handling. Re-added.\n+ Changed max board size prevention to auto resize the lower\n  dimension to the max that can be handled with the higher\n  (ex, 30000x25000 becomes 30000x559).\n+ Added flood fill to char editors (alt + f).\n+ Added single depth undo to char editors (alt + u).\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nDecember 19, 2004 - MZX 2.80e\n\n+ Fixed a bug causing problems with static overlay if a\n  non-overlaid sprite is displayed so it's clipped off the edge\n  of the screen.\n+ Fixed a bug in the display of c?x color boxes in the F2 menu\n  in the robot editor.\n+ Fixed a bug that caused incorrect thisx/thisy for one cycle\n  after copyblock.\n+ Fixed a bug preventing calls to nonexistent subroutines from\n  passing that point in the robot.\n+ Fixed crash on sam 0 \"file\".\n+ Fixed a bug where loading new SFX may not correctly overwrite\n  previous ones.\n+ Fixed a bug where you could only load/unload so many mods\n  before MZX couldn't load anymore.. same bug as the SAMs but\n  went unnoticed!\n+ Fixed a bug that caused you to be infinitely stuck in the\n  global settings dialog box when you press previous on the\n  next page.\n+ Fixed a bug where going to next then exiting would not save\n  the changes from the previous page.\n+ Fixed a bug that could cause crashes while ending modules.\n+ Fixed a bug that could do the same kind of thing with sams.\n+ Fixed an allocation bug when loading MZX worlds that could\n  lead to crashes.\n+ Fixed a bug that caused MZX to crash if you interpolated an\n  expression with a value equal to or greater than 1 billion.\n+ Fixed a bug where mixing ccheck1/2 with sprites from board\n  and vlayer could cause problems (that's the short version of\n  the explanation, I'll spare you the long one).\n+ Fixed a bug that could cause certain old MZX games to crash\n  after the title screen\n+ Somewhere broke missiles between 2.80c and 2.80d. Fixed.\n+ Fixed error in lit bomb anim sequence setup in char ID\n  editor.\n+ Reworked a lot of robot editor code; adding/deleting lines\n  while marked areas are active should work more naturally now\n  and it's hopefully no longer possible to crash it in the same\n  ways it was previously.\n+ Fixed crash when setting mesg row to less than 0.\n+ Fixed mouse presses not working in the robot editor.\n+ Made MZX ignore alt + tab so you can safely switch in your WM\n  without it triggering...\n+ Added numerical key entry for number boxes. Use 0-9 to add to\n  the most significant digit and backspace to take it away.\n+ Added config.txt option to make MZX pause when key focus is\n  lost (when clicking on another window, perhaps) or when it's\n  minimized. Music will still continue.\n+ Added save/load position to the editor. Works for loads\n  in between boards as well. Press ctrl + num to save to slots\n  0 through 9 and alt + num to load from that slot. Please\n  press shift + 8 or the numpad * key instead of alt + 8 to set\n  mod wildcard.\n+ Fixed a further bug that could cause playing samples to\n  crash.\n+ Added config file option to revert the robot editor to the\n  default palette when loaded.\n+ Fixed bug in shoot command.\n+ Fixed error when making save name in editor but cancelling.\n+ Auto-backup - see config.txt for details.\n+ Joystick key mapping - see config.txt for details.\n+ You can now load game-specific config files by creating\n  game.cnf for the corresponding game.cnf (for instance,\n  caverns.cnf). This is mainly useful for joystick key mapping.\n  Note that these settings will NOT go away if another game is\n  loaded that doesn't have a .cnf.\n+ Alt+enter finally works as block action in the robot editor.\n+ Loading a .mzx/.sav from another directory indirectly (via\n  command-line or robotic) will now actually change the current\n  working directory.\n+ Fixed bug that crashed MZX with ctrl + i in the robot editor.\n- Fixed maximum board size to about 16.7 million tiles (128MB)\n  for now.\n- MZX now ignores the mouse scroll wheel instead of\n  interpreting it as a click.\n  \n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nOctober 9, 2004 - MZX 2.80d\n\n+ Fixed cursor going invisible when escaping from import in the\n  editor.\n+ Fixed robot editor entry when pressing OK on global info.\n+ Fixed lack of name for MZB import/export (any MZB's exported\n  in prior beta versions still won't have a name).\n+ Fixed some problems with setting the mouse position.\n+ Fixed problem with exits not bringing you all the way to the\n  edge if width over 400 or height over 200.\n+ Fixed bug that cleared too much when increasing both width\n  and height while resizing the board.\n+ Fixed problem with 1 char shortcut commands with spaces\n  immediately after them.\n+ Fixed problems with load_robot and load_bc (caused crashes\n  and infinite loops).\n+ Optimized RASM heavily (this should be most noticeable when\n  doing a lot of external robot loading from text files).\n+ Fixed inability to use absolute paths in loading a game from\n  command line.\n+ Fixed lastshot/lasttouch conditions with directions not\n  working.\n+ Fixed char editor in robots not going into SMZX mode when\n  proper.\n+ Cleaned up source code so it passes -Wall without complaint\n  and in the process corrected some glaring code errors that\n  may have corrected random problems.\n+ File opening broken in 2.80c, fixed.\n+ Implemented MZM2 saving and loading and rewrote mzm.cpp (if\n  anything is changed or fixed regarding MZMs, attribute it to\n  this). MZM2s can be of larger dimensions, smaller filesize\n  for same amount of data, and can store robots.\n+ Fixed bug that could cause MZX to crash when making new\n  strings.\n+ Block operations to overlay when overlay was off caused\n  crashes - fixed.\n+ Fixed a problem with sprite ccheck2 against other sprites.\n+ Optimized function counter lookups a bit; speed gain for all\n  counter accesses (especially ones that begin with certain\n  characters such as _).\n+ Fixed disassembly error with ' ' character.\n+ Fixed assembly error where condition extended dir (such as\n  blocked opp seek) was not getting compiled with the dir\n  extension.\n+ Fixed editor bug where the param was not being cleared when\n  overwriting things by double placement.\n+ Fixed inability to use counters with\n  playercolor/bulletcolor/missilecolor.\n+ Added ability to use counters in place of p?? in the robot\n  editor. Note - even though this expands functionality of the\n  editor this does not require a version number change because\n  the worlds will still be playable in older MZX versions (and\n  will display correctly in the robot editor - you simply won't\n  be able to correctly edit the commands).\n+ Mouse correctly limited to screen edges now.\n+ Fixed inability to overwrite robots with pushable robots and\n  vice-versa, as well as scrolls with signs and vice-versa.\n+ Possibly fixed problem with windowing error when editing\n  global robot (?).\n+ Fixed disappearing cursor after color selection box with\n  mouse (and other places?).\n+ Fixed bug in sprite clipping that caused some to be clipped\n  off inappropriately.\n+ Made board_id/board_param counters readable.\n+ Added bound checks for all counters using\n  board_x/board_y/overlay_x/overlay_y.\n+ Fixed potential direction corruption bug causing directions\n  not to work sometimes even if they display correctly in the\n  robot editor.\n+ Fixed copy overlay to MZM copying to overlay too.\n+ Fixed a bug where debug window could display the wrong amount\n  of robot mem and potentially even crash MZX.\n+ Fixed help_menu counter not doing anything (durr).\n+ Changed sprite draw order so they're drawn underneath the\n  message bar, debug box, and time remaining display.\n+ Changed put p?? in Robotic so it will put default params if\n  available.\n+ Fixed a bug that could cause copies from overlay to vlayer to\n  not end up at the correct destination.\n+ Fixed a bug where c?x and cx? would not display correctly in\n  the robot editor.\n+ Optimized copy blocks a bit using variable length arrays\n  instead of malloc.\n- Removed ability to put robots, scrolls/signs, and sensors\n  (with the put command in Robotic).\n  \n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nAugust 16, 2004 - MZX 2.80c\n+ Fixed issues with the commands counter not being reset.\n+ Color intensity now gets reset when you enter the editor\n+ SAMs got cut off sometimes now.. fixed.\n+ Fixed bug where loading a world with empty boards could\n  change the starting, endgame, and death boards.\n+ Fixed bug where you could text enter off the bottom of the\n  board, causing problems.\n+ Fixed bug involving cutting/clearing the entire robot in the\n  robot editor while not at the first line.\n+ Fixed robot name entry for global robot not disappearing on\n  small boards.\n+ Fixed bug where you could duplicate the player by holding\n  down a direction as a saved game loads.\n+ Fixed bug where you could go to line 0 in the robot editor.\n+ Saving an MZM now auto-adds the .mzm extension...\n+ Fixed black screen on quicksave.\n+ Fixed bug where opening a file didn't close the old one if\n  one was open (so it'd eventually crash MZX).\n+ Changed Alt+backspace behavior in intake so it doesn't exit.\n+ Added clipping for refx/refy/width/height for sprites (less\n  than 0 at initialization, greater than board width/height at\n  draw).\n+ Fixed direction parsing for move all.\n+ Fixed bug where creating things on top of the player would\n  use a slot for robots/scrolls/signs/sensors instead of just\n  copy to the buffer.\n+ Added ability to use chars as immediates in Robotic commands\n  (ie, set \"$str.0\" 'a').\n+ Added options to enable oversampling and specify resampling\n  mode in the config file (higher quality audio).\n+ Building with patched modplug that fixes loading 2-channel\n  mods outputted by FT2. If you're building yourself, see\n  build.txt.\n+ Fixed inability to mouse click in alt + h mode.\n+ Fixed ability to mouse click outside of board range.\n+ Should work better for Linux users; case insensitivity for\n  file opens has been added.\n+ Fixed close bug that was affecting Linux builds (may affect\n  more).\n+ Keypad enter works where normal enter works now.\n+ Fixed disappearing cursor when cancelling out of abandon\n  changes box when loading a new world in the editor.\n+ Fixed problems when loading/saving robots outside of ID range\n  (do not hardcode ID's people).\n+ Fixed problem with NO BOARD exits being set to something else\n  when empty boards were being stripped or when worlds were\n  being imported.\n+ Fixed bug where auto-decrypting worlds didn't work if the XOR\n  value was negative.\n+ Fixed problem with rid not working the first cycle.\n+ Fixed inability to interpolate (with &amp;&amp;'s) counter names\n  larger than 14.\n+ Added new robot mem counter in debug box (only kb precise,\n  rounds up).\n+ Fixed ability to clone the player on non-title board after\n  testing.\n+ Lengthened size of mod name buffers.\n+ Fixed bug where send x y doesn't work from the global robot.\n+ Fixed a few bugs that could cause MZX to crash.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nAugust 11, 2004 - MZX 2.80b\n\n+ Made it possible for robots to move through teleporters.\n+ Fixed bug with pressing shift in text entry boxes.\n+ Made it so alt + tab does not switch draw modes in editor.\n+ Fixed a disassembly error for color intensity N percent\n  command.\n+ Fixed problem with looping on mods that do not loop\n  explicitly.\n+ Fixed alt + dir scrolling in the char editor.\n+ Fixed not being able to click the rightmost char in the char\n  editor.\n+ Re-added unmark (alt + u) to robot editor (mysteriously\n  disappeared??).\n+ Fixed key label so it returns proper unicode values.\n+ The player and pushable robots can now be pushed by the push\n  command.\n+ Fixed bug where you could clone the player by switching\n  boards.\n+ Fixed bug where you could either turn off overlay or switch\n  to boards that don't have it while in overlay edit mode...\n+ Fixed bug where remains of debug window would not be cleared\n  in editor if the board width is too small.\n+ Fixed bug where turning off the menu with a board too small\n  would mess things up.\n+ Fixed bug where run lengths were saved one too large... this\n  could fix stability problems in at least occasional cases\n  (with saved worlds or save games, at least).\n+ Fixed placing solid things beneath robots (like bombs).\n+ Added support for a keyboard plus in the char editors.\n+ Fixed previous button in SFX editor.\n+ Made robot name box disappear when robot char box comes up...\n+ Fixed bug where mods restart after pressing P if they're the\n  same mod as what was playing before.\n+ Fixed problem with changing params (with P) in the editor.\n+ Fixed bug where null boards were not being pruned from old\n  worlds upon load.\n+ Made file name saving box larger (for saving games and\n  worlds).\n+ Fixed bug where default (100%) palette intensity values would\n  not be applied to the palette a game loads with.\n+ Fixed bug where exporting char sets that are full size caused\n  a 0 byte charset to be exported (8bit wraparound).\n+ Added support for forms such as :line.\n+ Fixed sporadic incompletion of strings without trailing\n  quotes at the end of the line.\n+ Fixed bug where clearing/cutting the last line of a robot\n  crashed MZX.\n+ F4 in robot editor now works more generally.\n+ Made line numbers in robot editor error report start at 1.\n+ Added ctrl + G to go to a line in the robot editor (ala\n  nano).\n+ Made it possible to change a robot's color in the editor\n+ Fixed bug where spitfire, seekers, and missiles didn't hurt\n  something immediate adjacent to whatever shot it.\n+ Fixed editing of spitfires.\n+ Made default speed 4 again (5, bleh).\n+ Re-added quicksave/quickload.\n+ Re-added F8 clear (always works - be careful).\n+ Fixed autorepeat problems for spacepressed/delpressed.\n+ Wrapped around sprite values so LogiCow's bad code would work\n  (HTMCIAB).\n+ Cleared block commands in between board changing and other\n  things like that.\n+ Fixed bug where MZX would crash if put dir player overwrote\n  the robot doing it.\n+ Fixed bug where playing SAMs would eventually crash MZX\n+ Fixed some mod * problems (hopefully?).\n+ Fixed bug where pasting blocks over the edge of the board in\n  the editor would cause MZX to crash.\n+ Uses new GDM2S3M source that fixes some bugs. If your\n  converted GDM's have problems, delete the S3Ms it generated.\n- Removed export text in the board editor. Don't think anyone\n  wanted it...\n  \n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\nAugust 9, 2004 - First release, MZX 2.80 BETA\n\n+ All ASM files have been rewritten to ANSI C compliant C++;\n  this mainly involves the mammoth game2.asm.\n+ Everything graphics related has been replaced with an SDL\n  driven engine, including text mode rendering (also capable of\n  rendering SMZX modes).\n+ All keyboard and mouse events are now also handled by an SDL\n  driven engine. All internal keyboard/mouse routines use SDL\n  keycodes; no attempt was made to convert to MZX's old format\n  at this level.\n+ All world/board/robot loading and saving code has been\n  entirely rewritten and is more modular now.\n+ All robot handling code has been rewritten from scratch, and\n  is more sane/efficient now (allocation, message sending,\n  sensors, etc.)\n+ All counter handling code has also been rewritten from\n  scratch. This includes the functionality of builtin and\n  \"function\" counters. It also includes a new string framework.\n+ The robot interpreter has been almost entirely rewritten.\n+ The main game playing code (game.cpp) has been almost\n  entirely rewritten.\n+ All module/sample playing code has been rewritten to use\n  modplug and SDL. PC speaker effects are emulated.\n+ The main part of the editor (edit.cpp) has been almost\n  entirely rewritten.\n+ The robot editor has been entirely rewritten, utilizing a\n  modified (fixed) version of RASM (released by me over a year\n  ago)\n+ All other source files (for instance, window handling code,\n  scroll displaying, char editor, etc.) have been largely\n  overhauled, some to more of a degree than others. At the\n  very least, everything has been reformatted and made to work\n  with the new systems.\n+ key_pressed and key_code now return unsigned chars instead of\n  signed, so if the game checks for negative values, it won't\n  work. The same goes for reading back characters for strings.\n+ Boards may now be as large as 32767x32767. Please do not make\n  boards this large. :)\n+ You may now have up to 250 boards.\n+ Boards may have up to 255 robots, 255 scrolls, and 255\n  sensors.\n+ There is no robot memory per board limit. However, individual\n  robots may only be up to 64k large. If you include the global\n  robot, this means that you effectively can have up to 16MB of\n  robot memory per board.\n+ The same applies to scrolls, which have separate memory\n  spaces and may only be up to 64k in length each (so you could\n  have almost 16MB of scroll text per board, but who would want\n  that??).\n+ The robot stack need not be initialized, and will be\n  dynamically resized as necessary (useful for recursion). This\n  refers to lines such as\n  . \"#*-1-2-3-4-5-6-7-8\"\n  at the beginning of robots. They're no longer necessary or\n  useful (they will be ignored, like any other comment).\n+ You may have a virtually unlimited number of counters active.\n  The theoretical limit is some 4 billion. The effective limit\n  depends on memory available.\n+ The same goes for strings, which may now have any name, so\n  long as it begins with a dollar sign ($). The same\n  conventions regarding strings in 2.68+ still apply (see\n  268_info.txt for more information).\n+ Local2 and local3 no longer have dangerous side-effects.\n  Local4 through local32 are also available per-robot now.\n+ The following are now natively supported: XM, S3M, MOD, MED,\n  MTM, STM, IT, 669, ULT, WAV, DSM, FAR, AMS, MDL, OKT, DMF,\n  PTM, DBM, AMF, MT2, PSM, J2B, and UMX.\n+ You can now perform repeated copies by selecting \"Copy\n  (repeated)\" from the block command window. This will cause\n  you to repeat the copy indefinitely until you press escape.\n  Also, after the first paste is made, you may press ctrl +\n  direction to move in steps that are the size of the copy (you\n  really have to try it to get a good idea of what I mean).\n+ Press alt + H to hide/unhide the menu/information occupying\n  the bottom 6 lines of the screen.\n+ Right click is similar to pressing insert (grabs whatever's\n  under the cursor).\n+ Exporting char sets now gives direct options to set the\n  size/offset for partial charsets. The old format (prefixing\n  the name) will not work.\n+ For saving files, you may make filenames larger and use mixed\n  case (as opposed to all caps).\n+ Now when you import worlds their exits will actually work.\n+ Loading worlds results in all empty boards and empty\n  robots/scrolls/sensors within the boards being removed. When\n  you delete a board, you won't be presented with a gap in the\n  board list anymore.\n+ You may once again select ASCII as a default charset and\n  revert to it in the char editor. You may also select a\n  default SMZX charset.\n+ Anywhere you can enter a line of text you can now press ctrl\n  + left and ctrl + right to go to the previous/next word.\n+ This isn't totally limited to the editor, but now everywhere\n  a selection box shows up (such as for files, boards, items)\n  you can press multiple keys to seek to a specific entry.\n  Furthermore, in file selection boxes you can press backspace\n  to go up a directory.\n+ The SMZX char editor has been changed quite a bit. First, to\n  use, you must be in SMZX modes 1-3 (toggle using F11). Within\n  the editor, press keys 1 through 4 to change the \"current\n  color.\" You can place this color with space, and you can\n  toggle repeated placement on/off using tab (sorry, no\n  clear/toggle in this mode, they got in the way far more than\n  they helped). You now move in increments of 1 char naturally\n  instead of \"half chars.\" Right click grabs the current\n  \"color\" underneath the cursor.\n+ You are now able to type invalid code within a robotic line.\n  You will see that it's invalid due to the way the line is\n  color coded. If you try to escape while any lines are invalid\n  or if you press alt + V, a window will be brought up\n  describing the nature of all erroneous lines. It will also\n  let you mark each line to be deleted or commented out on\n  exit. If all invalid lines are marked with anything besides\n  ignore, you can exit safely. You may also mark invalid lines\n  using ctrl + I, D, and C (to mark as ignore, delete, and\n  comment respectively). ctrl + C may also be used to comment\n  out a normal line, and then uncomment.\n+ alt + O is still available, but you may only change macros\n  here (see the config file for the other rarely used options).\n  This does give you more room to modify macros, though. They\n  may be up to 64 characters in length, and you may set up to\n  around 45 or so in this window. (To get larger, you'll have\n  to use the config file.).\n+ Command line params have changed.\n+ MegaZeux configuration information is now stored in a file\n  called config.txt.\n- There are some slight discrepancies in robot size; if you\n  highlight the last line then leave it, it will register as a\n  compilation of that line. Yet, when you exit, the line will\n  be discarded, so this addition is not permanent. This\n  shouldn't be problematic.\n- alt + enter and alt + escape no longer work; use the\n  alternatives (alt + b and alt + u).\n- You cannot click on help options to make it happen (for\n  instance, you can't click on L:Load and expect it to bring up\n  the load window).\n- You cannot import or export ANS files. Use MZM instead.\n- Some things that worked in the DOS version only worked due to\n  chance, such as there being default (but valid) values for\n  sprite widths or heights and thus sprites could be displayed\n  before new values were set. They will not work here.\n\n<a class=\"hL\" href=\"#NEWINVER.HLP__1st\">NEW in MegaZeux!</a>\n<a class=\"hL\" href=\"#292CLOG.HLP__292\">New in Versions 2.92 to 2.92f</a>\n<a class=\"hL\" href=\"#2901CLOG.HLP__291\">New in Versions 2.90 to 2.91j</a>\n<a class=\"hL\" href=\"#284CLOG.HLP__284\">New in Versions 2.84 to 2.84c</a>\n<a class=\"hL\" href=\"#2823CLOG.HLP__283\">New in Versions 2.82 to 2.83</a>\n<a class=\"hL\" href=\"#281CLOG.HLP__281\">New in Versions 2.81 to 2.81h</a>\n<a class=\"hL\" href=\"#OLDERVER.HLP__260\">New in Versions 2.60 to 2.70</a>\n<a class=\"hL\" href=\"#OLDESVER.HLP__201\">New in Versions 2.01 to 2.60</a>\n<a class=\"hL\" href=\"#NEWIN200.HLP__1st\">New in Version 2.00</a>\n<a class=\"hL\" href=\"#ANCENVER.HLP__1xx\">New in Versions Through 1.03</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"OLDERVER.HLP\">\n<a class=\"hA\" name=\"OLDERVER.HLP__260\"> </a>\n<p class=\"hC\"><span class=\"f9\">New in Versions 2.60 to 2.70</span></p>\n**NEW in 2.70***\n\n+ Robotic code now loadable and savable to files, either to\n  text files or compact but human-unreadable bytecode files.\n  Other robots' code can be written to or read as well.\n+ Defaults changed to more accepted values.\n+ The swap world bug should be fixed. You should now be able to\n  swap worlds back and forth with no trouble.\n+ \"atan\" now works with any input/output value.\n+ The \"load_game\" command now should work without any problems.\n+ It should now be easier to compile MZX from the source.\n+ It's not really a bug fix, but the palette is now returned to\n  its original colors.\n+ Robots can now 'put' things underneath themselves: eg. put\n  c04 carpet p?? under.\n+ The title screen menu now works: e.g. the menu you get when\n  pressing 'Enter' on the title screen.\n+ \"key_pressed\" now returns the twos-complement of the key\n  value, i.e. the one used for :key?\n+ The mouse can scroll across the whole screen after a screen\n  refresh / transition to/from SMZX mode.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n**NEW in 2.69c***\n\n+ Sprite limit expanded from 64 to 256.\n+ New sprite flag added: sprN_ccheck mode 2.\n+ Vlayer added.  This is a virtual, unseen layer 32767 in size\n  used to store graphical data.\n+ World loading and saving through Robotic now possible.\n+ Special class fwrite_modify; this allows editing of a file as\n  opposed to overwriting.\n+ File end seeking added.\n+ Char file offsetting added.\n+ File seeking added to editor.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n***NEW in 2.69b***\n\n+ Fixed the fwrite_append problem RoSS mentioned.\n+ Fixed the string problem Nanobot mentioned.\n+ Added ridNAME as an alternative to robot_id_NAME.\n+ Added mouse_mx and mouse_my to determine mouse motion in\n  \"mickey\" units.\n+ Added two local counters \"local2\" and \"local3\", local2 uses\n  walk_dir and is_locked (so walking and locking will affect\n  it), local3 uses last_shot_dir and last_touch_dir (so\n  shooting and being touched will affect it).\n+ Added the builtin string \"robot_name\" to determine the name\n  of the robot invoking it.\n+ Reverted back to BWSB 1.20a again, in the hopes that it will\n  alleviate swap world crashing a bit.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n***NEW in 2.69***\n\n+ if \"$stringN\" = \"literal\" should now work.\n+ Embedding &amp;&amp; or () in names for save/load files for MZM\n  saving and the like should work now.\n+ Expressions won't be activated in pre-2.68 games, meaning if\n  they used the constructs which would be valid expressions\n  they should be okay.\n+ More minutiae.\n+ key_code, a more useful key_pressed alternative, added.\n+ SMZX mode 1 returns.\n+ SMZX mode 2 added.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n***NEW in 2.68***\n\n+ Expressions added.\n+ Trigonometric capabilities added.\n+ MZM file capabilities added.\n+ Board and overlay can now copy block to each other.\n+ Saving and loading of SAV files through Robotic added.\n+ String commands totally redone.\n+ Now, instead of using value/sqrt_value/abs_value you may use\n  a value with the counter directly as such:\n   * sqrtN\n   * absN\n+ fread_pos/fwrite_pos and page should work correctly now; when\n  you increase the pos it should bump up the page too.\n+ pixel_x, pixel_y, char were broken in 2.65; fixed in 2.68\n+ strings had some bugs in 2.62b+, should be fixed now because\n  of a separate implementation.\n+ MZX Robot files are now saved to save files, so if you have a\n  read and/or write file open and the game is saved, if the\n  game is loaded the file will be restored at the position it\n  was at.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n***NEW in 2.65***\n\n+ Shows current mod playing in debug window\n+ Misc new counters: (ro = read only, wo = write only)\n * fread_counter (ro), fwrite_counter (wo): reads/writes a full\n   counter from file\n * board_w (ro), board_h (ro): returns current board\n   width/height\n * robot_id_(name) (ro): returns the ID number of robot \"name\"\n * r(number).(counter) (ro): returns the value of the local\n   counter for the robot with the given ID. Note that you\n   cannot write to another robot's counter.\n+ You can now save partial charsets in the editor.\n+ Sprites added; limit set at 64 global.\n+ Counters now representable in hex.\n+ Subroutines added.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n***NEW in 2.62***\n\n+ Strings added; limited to ten strings.\n+ \"mod_order\" counter: reports the current order (that is,\n   pattern) the playing module is at. Used in conjunction with\n   the jump mod order command it can be used to save/restore a\n   mod position between boards...\n- MZX 2.62 files are no longer forward compatible. That means\n  that a file made in MZX 2.62 will NOT work in any previous\n  versions (but it WILL work in future versions, so long as\n  they set the version string correctly).\n- The string reading from files may not work correctly on the\n  first read (that is, it might truncate the first character of\n  the string). For this reason put a junk string in the first\n  part of the file and read it first.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n***NEW in 2.61***\n\n+ Game speeds have been normalized.\n+ Original startup color scheme implemented!\n+ Redocumentation of a few features left undocumented.\n- Password protection has been fully removed.\n\n<p class=\"hC\">***</p>\n<a class=\"hL\" href=\"#NEWINVER.HLP__1st\">NEW in MegaZeux!</a>\n<a class=\"hL\" href=\"#292CLOG.HLP__292\">New in Versions 2.92 to 2.92f</a>\n<a class=\"hL\" href=\"#2901CLOG.HLP__291\">New in Versions 2.90 to 2.91j</a>\n<a class=\"hL\" href=\"#284CLOG.HLP__284\">New in Versions 2.84 to 2.84c</a>\n<a class=\"hL\" href=\"#2823CLOG.HLP__283\">New in Versions 2.82 to 2.83</a>\n<a class=\"hL\" href=\"#281CLOG.HLP__281\">New in Versions 2.81 to 2.81h</a>\n<a class=\"hL\" href=\"#280CLOG.HLP__280\">New in Versions 2.80 to 2.80h</a>\n<a class=\"hL\" href=\"#OLDESVER.HLP__201\">New in Versions 2.01 to 2.60</a>\n<a class=\"hL\" href=\"#NEWIN200.HLP__1st\">New in Version 2.00</a>\n<a class=\"hL\" href=\"#ANCENVER.HLP__1xx\">New in Versions Through 1.03</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"OLDESVER.HLP\">\n<a class=\"hA\" name=\"OLDESVER.HLP__201\"> </a>\n<p class=\"hC\"><span class=\"f9\">New in Versions 2.01 to 2.60</span></p>\n<span class=\"fE\">2.60 release:</span>\n\n+ File access\n+ Menu activation/deactivation\n+ Current key pressed detection\n+ Easy access to all 16 bits of a counter\n+ Pixel editing of a char set\n+ Single Byte editing of a char.\n+ Real player distance\n+ :key# added: gives twos-compliment of key value.\n- SMZX removed.\n- inter robot targeting removed.\n- Random color startup removed.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">2.51ak1.0 release:</span>\n + Added Numerous new Counters\n + Added new inter robot targeting system\n + Added new SMZX mode, dynamic resolution setting\n + Fixed fatal crash when taking a picture, also increased\n  the number of picture you can take to 99\n + Removed Built-in ASCII charset\n + Added Random color start up\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">2.51s3.2 release:</span>\n\n + Removed password protection\n + Added new counter BIMesg to turn off built-in messages\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">2.51s3.1 release:</span>\n\n + Fixed damage table, now works correctly\n + Fixed unworking Alt+s\n + Fixed screenshot name\n + Fixed strange missile color when firing\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">2.51s3 release:</span>\n\n + Fixed bug involving savegame name being overwritten by mod\n  name\n + Fixed overlay transparency weirdness. Now, background of\n  lower layer will ALWAYS show through if background of\n  overlay is 0\n + Added in kev's refresh screen support. Press Alt+w on the\n  title screen or editor screen, and = in the game(don't ask,\n  has to do with mzx's keyboard handler). Use this if your\n  charset gets corrupted in windows.\n + Screen shot saving rotates file name(starts with screen0.pcx)\n + Fixed bug involving incorrect loading of mzx health etc.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">2.51s3final release:</span>\n\n + Fixed bug involving mod * settings being lost when loading\n + Should have fixed ems problems some were reporting, if not,\n just try to get more conventional memory. Enclosed document\n (convmem.txt) should help you.\n + Fixed a bug involving direction checking.\n + Pressing ']' at most any time will now save a screenshot to\n  screen.pcx\n + Pressing Alt+8 in the editor will set the current mod to *.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">2.5.1s2beta release:</span>\n\n + Added MOD \"*\", which allows a board to use whatever module\n  the previous board was using; this necessitated a change in\n  the .SAV file format (still need changes in the editor UI\n  to access this feature outside of Robotic)\n + inmate's semantics for MOD \"SOMETHIN.MOD*\" aren't\n  immediately possible, although a rough hack is in already\n + char edits are now not displayed until the beginning of the\n  next cycle; the deferred display should eliminate a lot of\n  flickering\n + an (at least partial) fix for the infamous UNDER bug\n + MOUSEX, MOUSEY and BUTTONS are now buffered.\n + more sane magic handling\n + can load MZX 2.51 and 2.51S1 worlds, but will only save\n  2.5.1spider2 format\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">2.51s1beta release:</span>\n\n + Increased counters to 1000.\n + Added following counters: (ro) is read only, (r&amp;w) is read +\n   write\n   * (r&amp;w) MOUSEX, MOUSEY - Location of mouse cursor over the\n     screen.\n   * (ro)  BUTTONS - Status of the buttons (none=0,left=1,\n     right=2,both=3)\n   * (ro)  MBOARDX, MBOARDy - Location of mouse cursor over the\n     board.\n   * (ro)  SCROLLEDX, SCROLLEDY - Length the screen has been\n     scrolled in each direction.\n   * (ro)  PLAYERX, PLAYERY - Location of the player\n   * (r&amp;w) CURSORSTATE - Turns on &amp; off the hardware cursor\n     (0= off, 1= on, 0 default)\n + Added new independent counter like LOOPCOUNT, named LOCAL,\n   it's now possible for a robot to search the screen or\n   something without wasting a counter.\n + New .MZX and .SAV formats for MZX2.51S1.\n + .SAV files from 2.51S1 are incompatible with 2.51, and\n   vice-versa\n + 2.51S1 is capable of loading 2.51 .MZX world files.\n - 2.51 is not capable of loading .MZX files created or opened &amp;\n   saved in 2.51S1\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<p class=\"hC\"><span class=\"fE\">NEW in version 2.51: ALL NEW music/sound code! 32-channels;</span></p><p class=\"hC\"><span class=\"fE\">bug-free; support for stereo, 16-bit, GUS, and PAS-16;</span></p><p class=\"hC\"><span class=\"fE\">support for many formats and effects; up to four</span></p><p class=\"hC\"><span class=\"fE\">simultaneous sound effects!</span></p>\nNote that the new music support requires that you CONVERT all\nnon-MOD files using the included program, 2GDM. See 'MegaZeux's\nSound System' for details on the NEW music system.\n\n<a class=\"hL\" href=\"#SOUNDEFX.HLP__1st\">MegaZeux's Sound System</a>\n\nNote- This is a direct translation from the file WHATSNEW.251,\nincluded with MegaZeux. If you haven't used version 1.03 or\nbefore, you probably won't understand much of this list.\n\n<span class=\"fE\">New in version 2.51: (quick fix before I do 3.00!)</span>\n\n * Bug where moving north or west towards a board that is\n  larger than 100 tiles in a dimension may not work properly\n  fixed.\n * Bugs in 2GDM.EXE preventing proper conversion of some S3Ms\n  fixed.\n * A rare bug preventing internal MOD conversion fixed.\n * \"Error opening MOD\" will no longer appear during normal\n  gameplay.\n * Exporting ANSis now adds a color to the end returning text\n  to normal grey.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">New in version 2.50:</span>\n\n * FOR ALL YOU 2.07 USERS- THE MAJOR UPGRADE is the new sound\n  code! Support for stereo, 16-bit, more cards, 4 sound\n  effects channels, 32 music channels, higher quality, and\n  faster sound code! Less bugs! More formats supported! All\n  non-MOD files MUST be converted to GDM using the included\n  2GDM.EXE program. Formats supported- MOD (up to 32 channels),\n  WOW/OCT/NST, S3M, 669, and MTM.\n\n * Volume controls (overall and SAM) added to F2-Settings (saved\n  in .CFG file)\n * SAMs play at 2x volume.\n * MTM support added to 2GDM.EXE. Other formats aren't added\n  because they aren't needed (not used often enough) or in the\n  case of XM, because they support too many advanced features\n  that MZX does not support. Partial XM support MAY be added\n  later.\n * Many minor bug fixes in 2GDM.EXE's conversion routines.\n * Internal MOD conversion (in MegaZeux) sped up considerably.\n * For all those that upgraded from 2.07, see 2.49g and 2.48b\n  WHATS-NEW for more stuff, including a couple interesting\n  new Robotic features\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">New in version 2.49g:</span>\n\n * New Import function added- You can import an ANSi to any\n  position on the board.\n * Full SAM sound effect support as in the original 2.07 version\n  (but using the new MOD code for up to 4 simultaneous\n  channels)\n * The OPEN Robotic command will now push the Robot out of the\n  way if the door would be blocked by the robot.\n * REL PLAYER and REL COUNTERS now affects the THISX/THISY\n  counters (giving the distance FROM the player/counters TO\n  the Robot) and the prefixes also affect the IF [dir] BLOCKED\n  command, allowing you to check for blocked status next to\n  the player or an arbitrary position.\n * [ box message statements now clip their message to 64\n  characters (the maximum) on display. Note that all other\n  box-message statements are NOT clipped!\n * Color codes are allowed in ? and &amp; box message statements\n * Invalid sound card settings no longer cause a lock-up\n * You can hold down the mouse button to cycle through RGB\n  values in the palette editor\n * Changing something to an explosion no longer can cause weird\n  colors\n * What's New section of help sorted by version\n * Placing a robot over the player is prevented with a warning\n * The \"Alt+N- Music\" lights up properly in the editor now\n * When changing from a larger board to a smaller board, you\n  can no longer accidentally have the cursor outside of the\n  board size.\n * GUS owners don't need to enter # of SFX channels, since\n  MegaZeux cannot support GUS sound effects anyways.\n * File boxes (loading MODs, worlds, etc.) can now hold as\n   many filenames as memory allows.\n * GUS setup shown properly on configuration screen\n * 2GDM.EXE rewritten- In THIS release, it can only convert\n  MOD, NST, WOW, OCT, 669, and S3M files! The final (2.50)\n  release will have support for the remaining file formats.\n  It is being rewritten to fix many small bugs and make it\n  smaller and faster, as well as possibly add more file\n  formats.\n * 2GDM.EXE- F00 effect in MODs deleted during conversion\n * 2GDM.EXE- Bad effects S8x, S0x, and Xxx deleted during S3M\n  conversion\n * 2GDM.EXE now works properly from other directories\n * INTERNAL MOD loading support. You can now load MODs, NSTs,\n  WOWs, and OCTs from within the game without having to\n  convert them to GDM first. The only exception is 15-\n  instrument MODs- they must first be converted. Note that\n  all other formats, such as S3M, 669, etc. must still be\n  converted. NST/WOW/OCT support was only added because they\n  are extremely similar to MODs, and MODs were required for\n  backward compatibility.\n * Insert doesn't display those \"Lo bomb selected\" messages,\n  etc. if the player is Attack Locked or settings say he\n  cannot bomb.\n * Minor bugs in ANSi import corrected. (problem fixed- caused\n  errors on import of ANSis with more or the same number of\n  lines as the current board size maximum)\n * \"Ammo &lt;10\" and \"Ammo &gt;9\" entries in Global Chars changed to\n  \"Small Ammo\" and \"Large Ammo\" to prevent confusion.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">New in version 2.48b:</span>\n\n * ALL NEW music code! Features include faster, NO BUGs, 32\n  channels, more formats supported (sorry, no MID or XM) etc.\n * Bug where selecting \"(no board)\" to add a board caused an\n  error if that wasn't the first \"(no board)\" on the list.\n * Bug where if a robot line entered consisted of only\n  semicolons, spaces, and commas, it would screw up that\n  robot's program, fixed.\n * Command line options (-port,-dma,-irq) added for setting\n  sound card parameters. Only needed if auto-detection fails.\n * Minor help corrections.\n * Save/load during testing in editor disabled.\n * Pressing \\ no longer causes problems during gameplay.\n * Bug where \"Restart board\" for death option in global\n  options didn't function properly on first board, nor did\n  restart-if-zapped. (fixed)\n * Filling in the overlay no longer locks up if you fill over\n  something of the same color and picture.\n * Overlay no longer messed up when resizing board.\n * Lock up on Import World removed.\n * Setting the counter \"INVINCO\" to 0 works properly.\n * Hopefully fixed rare lockups after robot box messages.\n * Added confirmation for board deletion in editor.\n * All included worlds are NO LONGER password protected. (The\n  old password was \"megahertz\".)\n * Minor bugs in passage search algorithms corrected.\n * Obscure bug involving REL prefixes if used in \"infinite-\n  loop\" type constructs (fixed)\n * In editor- Alt+Dir to move 10 spaces now works as expected\n  with draw mode on. (IE it will draw in ALL 10 spaces)\n * Bug where passages on screens wider than 256 spaces didn't\n  work properly. (fixed)\n * Bug where fire-to-player's-right would lock up and kill him\n  FIXED.\n * Counters can be used in ALL strings in &amp;COUNTER&amp; notation.\n  For example, SEND \"ROBOT\" \"LABEL&amp;COUNTER&amp;\" will replace\n  &amp;COUNTER&amp; with a number. This works for ALL strings -\n  counter names, labels, robots, etc. See Robotic help for\n  details.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">New in version 2.07:</span>\n\n * Labels and NON-valid counter-regulated options in robot box\n  messages are not blank lines; Instead they are just removed\n  from the message.\n * Passage (stairs/caves/whirlpools) search algorithm\n  corrected.\n * Digitized sample SFX works properly with the note B now.\n * Bug where getting multiple energizers in a row screwed up\n  player's color is fixed.\n * Bug where chests with invinco potions will die when taken\n  is fixed.\n * Filling in editor, with a robot/scroll/sensor over itself,\n  is now handled properly.\n * Whee!! More minor help typos fixed.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">New in version 2.06:</span>\n\n * Yet more cursor safeguards inserted. (Where do you GET\n  these problems!? :)\n * Saving a game no longer asks for overwrite confirmation if\n  the file doesn't really exist.\n * Help and tutorial- Minor textual errors fixed\n * Save dialog boxes only allow entry of 12 characters now,\n  instead of 13.\n * Help file can be accessed from any directory.\n * -l cmd line option will no longer cause a loading error with\n  an oversized filename. (it will instead be ignored)\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">New in version 2.05:</span>\n\n * Cursor bug fixed. (If it isn't, then get a new BIOS! :p)\n * MegaZeux now utilizes overlaid code, for almost 50k core\n  memory savings.\n * LOCKSCROLL cmd fixed\n * Moving into the lower-right corner of full-size boards no\n  longer warps you to the upper-left corner.\n * SET COLOR and COLOR INTENSITY don't blow up if the color is\n  not from 0 to 16.\n * Code for activating 16 background colors is now shorter and\n  uses BIOS calls for compatibility.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">New in version 2.04:</span>\n\n * Slight modifications to Robotic Tutorial (help file)\n * Sending a robot a message, when it hadn't done anything yet\n  that turn, activates it immediately. This helps with\n  synchronized activities, such as large, multi-robot\n  creatures.\n * Alt+Numerics feature of BIOS keyboard routine DISABLED (IE\n  Alt+3 will no longer break to DOS)\n * Counters' code optimized.\n * The temp file <span class=\"fE\">DITRSZ.TMP created when resizing a board is</span>\n  now deleted when finished.\n * a KEYBOARD CODE kink was worked out (hopefully got 'em\n  all...)\n * Made printer code more general (should work on any printer\n  in text mode)\n * PALETTE BUG on some SVGA cards FIXED. (black now looks like\n  black) If it still isn't, please notify me.\n * HOME and END will jump to the top/bottom of a box\n  message/help/scroll.\n * PgUp/PgDn/Mouse navigation work properly in box\n  messages/help/scrolls\n * HOME and END in dialogs works properly now- Unless you are\n  editing a number, they will jump to the FIRST item and the\n  NEXT or OK button, respectively. In a string, they will\n  still jump to the start/end unless you are already there,\n  then they will jump to the proper dialog location.\n * Robots (especially when there are lots of them) sped up.\n * DUPLICATE SELF and COPY ROBOT cmds sped up some, except when\n  used by global robot.\n * Palette intensities reset when going into editor after the\n  title screen changes them.\n * MODULO \"str\" # will no longer crash if # is 0.\n * Extreme palette activity no longer causes snow or (on EGA)\n  screen breakup. Palette activity also sped up.\n * Bug fixed- If a SAM/MOD command had to free up board memory\n  (showing the little \"freeing up board memory...\" meter) then\n  that robot stopped running.\n * Bug fixed- If the robot changed it's surroundings (IE\n  Putting a SPACE to it's NORTH) and then did a RANDNB or\n  RANDB, the new surroundings weren't always taken into\n  consideration.\n * DIVIDE ERROR crash/bug fixed. (It was related to usage of\n  RANDNB and RANDB)\n * CHAR \"A\" vs. CHAR 'A' question added to F.A.Q.\n * README.TXT changed- Boot disk/support info added.\n * Help on cmd .\"@string\" corrected.\n * SPEED defaults to 4 (settings) and is saved in MEGAZEUX.CFG.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">New in version 2.03:</span>\n\n * Intensity of palette (and other stuff?) now resets after a\n  test game.\n * Messed-up black color on certain graphics cards- Attempted\n  to fix.\n * Minor bug in VER1TO2.EXE fixed\n * Robot section of Tutorial corrected (the descriptor scrolls)\n * Speeds based on real time (IE speed 3 will be the same on\n  ANY computer, unless the computer itself is so slow that it\n  forces a slower speed.)\n * CAVERNS has game over screen fixed\n * MOVE PLAYER [dir] \"label\" command now works properly.\n  (Before, the label was almost always ignored)\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">New in version 2.02:</span>\n\n * Bugs with Enter in text mode on small boards fixed\n * Bugs with slime fixed\n * If you have screen faded out (COLOR FADE OUT) and go to\n  another screen, it no longer fades it in for you\n  automatically.\n * HELP.DOC- CHANGE CHAR ID section fixed\n * Fill really does now fill properly on boards larger than\n  127x127. :)\n * Sped up sensor interactions somewhat\n * Fixed problems with boards not scrolling with the player,\n  in the game on over-sized boards. Run FIX.EXE in all\n  directories containing version 2.01 or 2.00 .MZX/.MZB files.\n * Nothing can give you negative coins, gems, etc. (such as\n  thieves)\n * Minor robot speed improvements\n * Turning Music Off (Settings in Game) really keeps it\n  off... :)\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<span class=\"fE\">New in version 2.01:</span>\n\n * Sensor command CHAR'X' fixed\n * TAKEing health now counts as hurting the player for the\n  :playerhurt label\n * Can load help from any drive/directory\n * Copy block in editor now properly clips the block's\n  destination\n * Move block in editor now properly clips the block's\n  destination\n * .\"@new_robot_name\" command added\n * If the player runs up against a player bullet, it won't\n  hurt him\n * JUSTENTERED, JUSTLOADED, and GOOPTOUCHED labels fixed\n * UNLOCKSCROLL and locked scrolling in general fixed\n * Global info now properly sets endgame/death boards\n * Robot ASK command works properly now\n * Help- internet address/address validity date corrected\n * Help- CHANGE CHAR ID help section- added note about how the\n  numbers are also listed in the Global Edit Chars menus\n * ENTER can now exit robot box messages/scrolls\n * Message line (bottom) shows color correctly\n * EXCHANGE/RESTORE w/DUPLICATE SELF fixed\n * Attempted to fix cursor problems in editor. If it doesn't\n  work now, then I have no solution, as I use documented BIOS\n  routines with many safegaurds.\n * Minor speed improvements\n * Minor size reduction\n * Fill in editor now works with board sizes over 127x127\n\n<a class=\"hL\" href=\"#NEWINVER.HLP__1st\">NEW in MegaZeux!</a>\n<a class=\"hL\" href=\"#292CLOG.HLP__292\">New in Versions 2.92 to 2.92f</a>\n<a class=\"hL\" href=\"#2901CLOG.HLP__291\">New in Versions 2.90 to 2.91j</a>\n<a class=\"hL\" href=\"#284CLOG.HLP__284\">New in Versions 2.84 to 2.84c</a>\n<a class=\"hL\" href=\"#2823CLOG.HLP__283\">New in Versions 2.82 to 2.83</a>\n<a class=\"hL\" href=\"#281CLOG.HLP__281\">New in Versions 2.81 to 2.81h</a>\n<a class=\"hL\" href=\"#280CLOG.HLP__280\">New in Versions 2.80 to 2.80h</a>\n<a class=\"hL\" href=\"#OLDERVER.HLP__260\">New in Versions 2.60 to 2.70</a>\n<a class=\"hL\" href=\"#NEWIN200.HLP__1st\">New in Version 2.00</a>\n<a class=\"hL\" href=\"#ANCENVER.HLP__1xx\">New in Versions Through 1.03</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"NEWIN200.HLP\">\n<a class=\"hA\" name=\"NEWIN200.HLP__1st\"> </a>\n<p class=\"hC\"><span class=\"f9\">New in Version 2.00:</span></p>\n * All code has been rewritten or at least stepped through line\n  by line, except for the music code.\n * New user interface style and startup screen. Lots of neat\n  little items like shadows, nicer colors, etc. Mostly\n  aesthetic but nice. Mouse support also improved.\n * Keyboard code rewritten (again) All Alt+lock, shift-lock,\n  and ctrl+lock problems, as well as most other keyboard\n  problems, should not be a problem anymore. You may have to\n  use the -keyb2 command line option. If a key locks, tap it\n  a couple times.\n * No flicker- The game engine now page flips.\n * Better string input- ANYWHERE you can enter a string, you\n  can now move with the cursors and insert in the middle...\n * Better character selection box- Shows all the characters.\n * New default character set- Much nicer, more general use\n  characters.\n * Now detects processor to avoid lock ups on an old 8086/8088.\n  If an invalid graphics card is found, DOS services are used\n  for printing.\n * Different command line options. (use -? to get info on them)\n * Context sensitive help.\n * Bombs and Sensors under the player no longer mess up floors,\n  etc.\n * CHANGEing something to lava, fire, etc. now works correctly.\n * Transporting onto a Sensor works properly.\n * New conditions- MUSICON and PCSFXON\n * Changing things to spaces CLEARS them, to avoid screwing up\n  floors.\n * Restart position not changed during a save/load\n * Label- :playerhurt for when player is hurt, not sent on\n  invincible hurts\n * Maximum Robot commands per cycle is 40, not 25\n * Global Robot runs normally in freeze/slow time\n * Choice of 100x100, 200x50, 400x25, 80x125, or 60x166 for\n  each board.\n * Character editor remembers character you were editing.\n * Robo-P renamed to Robotic\n * Scrolls now allow proper use of mouse, pgup, and pgdn.\n * Scrollborder became Scrollcorner.\n * :gooptouched auto label added, for when the player touches\n  Goop.\n * Messages like \"You got a red key\" are now \"You got a key\" so\n  palette fiddling won't make strange words. :)\n * Within Robotic messages (box/line) a &amp;INPUT&amp; will be\n  replaced by the exact text of the currently inputted string\n * Notes in play of same freq. won't run together\n * Explosions won't destroy entrances, lava, water, ice, or\n  goop\n * Robots are stopped from firing if there is already a bullet\n  of the same dir/type in that dir.\n * Other things will push the player ONTO a Sensor.\n * The above will trigger the SENSORON label.\n * If player is on an entrance without having been there before\n  the update, use it. (I.E. can now be pushed onto entrances)\n * Teleporting or walking onto a screen and starting on a\n  Sensor will trigger the SENSORON label\n * Non-players/puzzle pieces now transport properly.\n * PLAYERLASTDIR (0-4) and PLAYERFACEDIR (0-3) as counters.\n * Points for killing enemies (3) and points for rings/potions\n  (5)\n * Way to disable edging spaces on message row- ENABLE MESG\n  EDGE, DISABLE MESG EDGE.\n * Cmds- LOOP START, LOOP # TIMES, ABORT LOOP, uses Robot\n  counter LOOPCOUNT\n * Counter limit increased to 50 plus built-in.\n * New passage search order- 1) Same type, same color 2) Same\n  color 3) Same type, same foreground 4) Same foreground 5)\n  Same type 6) Default player position\n * Palette import/export\n * If the starting board is deleted, change starting board to\n  title.\n * \"Sets of five\" numerical input actually increase by fives\n * Holding the mouse button on a numeric arrow button cycles\n  the num.\n * Removed the \".\" Directory from file menus\n * Six (not four) status counters.\n * CHANGE blah p?? blah p?? (notice the p?? added for the first\n  thing)\n * Doors, if can't move, don't advance in anim. I.E., doors\n  won't get \"stuck\" if they are blocked.\n * PERSISTENT GO command, like /\"nsew\" but WAITS to move if\n  blocked.\n * No chest message on empty.\n * Startup \"help\" screen on first use.\n * Explosions Leave Empty doesn't mess up ANY floors, etc.\n * Much better fill routine.\n * Entering a non-cmd with the first character as a [, ., :,\n  etc. automatically formats it. Leading and trailing spaces\n  and quotes are cleared first.\n * Save/Load game/etc. are accessed through F-keys in the game.\n * Removed special menu for Alt+M (modify) in editor\n * In editor, keeps track of whether world has been modified.\n * Ceiling layer - non-interactive, but overlays things and\n  looks neat. Included in editor- Edit, display toggle.\n  Robots - changing it by character, copying areas of it, and\n  filling an area of it by cmd or string (I.E. set area to \"A\n  string\") Char of 32 is see-thru. Layer during game can be on,\n  off, or static (I.E. not Scrolling, just showing upper left\n  portion).\n * Robot commands to change the mode of saving.\n * Limit any one Robot/Scroll to 31k.\n * Allow placing shooting fire in editor.\n * Make all enemies without speed arguments move 1/2 their\n  speed.\n * Player CANNOT be overwritten, you must place him anew to\n  move him.\n * First line of Robot now shows if it is a box mesg.\n * Speed 1 in game does NOT page flip, for speed.\n * Inputted strings allow input of spaces.\n * Time limits- Out of time zaps to entrance automatically,\n  THEN RESETS TIMER. Counter TIMERESET holds reset value for\n  timer, and the TIME counter holds the current time.\n * MOD code has only an error on no memory. Errors for loading\n  MODs and SAMs (I.E. not enough memory) are active if the\n  debug menu (F6) is shown. SAMs have errors for file errors/\n  out of memory. These errors are always in the editor/testing\n  games.\n * First test of a game doesn't give GAME OVER.\n * Sound effects aren't cut off across screens.\n * Placing a bomb while upon a passage no longed warps to title.\n * Sensors are pushable by things other than the player.\n * New Sensor-activated label- SENSORPUSHED, when the Sensor is\n  pushed, with or without the player on it.\n * Label- JUSTENTERED when the player just entered the screen\n  or the game is started. (NOT restored)\n * Command- Can lock/unlock board Scrolling temporarily-\n  LOCKSCROLL, UNLOCKSCROLL.\n * IF ALIGNEDROBOT \"Robot\" \"label\"\n * Damage editing- Changing the amount of damage things\n  inflict.\n * Load title screen directly when loading game for title.\n  Prevents music on first board from playing, and saves time.\n * New Robot-specific auto-counters- (read only) THISX, THISY.\n * Anywhere a number can go in a Robotic command, a counter can\n  too. Wherever there is a number, character, or color, you\n  can use a counter name in quotes instead.\n * Cmd line- load MZX file. (-lxxxxxxxx.MZX)\n * Config file for options, ask \"OK?\" on startup. Removed all\n  config cmd line options.\n * Cmd- SCROLLVIEW X Y. (upper left hand corner is specified)\n  Based off of current player position.\n * Cmd- SWAP TO WORLD \"world.mzx\" as if starting the world up.\n  Skips any title screen. (you could put a message there) The\n  other world can have a \"Only play from swap\" option set.\n * Quicksave key in game- Saves without asking for filename and\n  confirmation.\n * Quickload key in game- Same idea.\n * Option (default off) on world to clear messages, bullets,\n  and spitfires from a screen when exited.\n * Label JUSTLOADED sent to as soon as the game is started or\n  restored. (including the title screen and actual playing)\n * Shows character number on char edit and char selection.\n * Shows color number on color selection.\n * Multiple spots to SAVE/RESTORE/EXCHANGE PLAYER POSITION. (8)\n * RESTORE/EXCHANGE PLAYER POSITION with the option to\n  duplicate the Robot to take his place. When the player moves\n  back, the Robot is, of course, deleted.\n * Remove RANDOM POS/SIZE options.\n * Allow labels to interrupt in a Robot's box-message code and\n  have the message still show uninterrupted.\n * Show a pic of the item next to it in the THINGS menus.\n * Different bullet pics/colors for player, neutral, and enemy\n  bullets.\n * Status shown counters won't show if the value is 0.\n * Prefixes that affect only the first or last x y pair of a\n  command. (REL COUNTERS LAST, REL SELF FIRST, etc.)\n * Mod fading commands (background)\n * Allow lives/health to max out at 65535.\n * Score as a counter.\n * Maximum of 150 boards. (not 127)\n * Cmd- COPY BLOCK x y x y x y.\n * Label for SpitFire hitting a Robot- \"spitfire\"\n * Label for Lazer hitting a Robot- \"lazer\"\n * Cmd- CLIP INPUT (chops first word + spaces off of input)\n * Cmd- IF FIRST INPUT \"str\" \"label\"\n * KEY1 through KEY9 labels. (like KEYA thru KEYZ)\n * Allow viewport sizes down to 1x1.\n * PUSH Robot command- push things to dir without moving there.\n * ONE global Robot. (No ID) Stored separate from a board, and\n  is active on ALL boards.\n * Robots- Allow importing of a character into a CHAR EDIT\n  command.\n * Robots- Have SCROLL CHAR, FLIP CHAR, and COPY CHAR commands.\n * Full backward compatibility w/old MegaZeux via a conversion\n  program. (VER1TO2.EXE)\n * Editable built-in sfx. (including to digitized)\n * Elements- Goop, which is like Water from ZZT.\n * EGA/VGA palette editor, with easy fading. Commands- COLOR\n  INTENSITY # PERCENT, COLOR INTENSITY \"counter\" PERCENT,\n  COLOR FADE IN, COLOR FADE OUT, SET COLOR # TO r g b, SET\n  COLOR # TO INTENSITY # PERCENT, SET COLOR \"counter\" TO\n  INTENSITY \"counter\" PERCENT. (note- the FADE OUT/IN cmds\n  are \"quick\" fades, IE not in the bk, but they actually\n  stall the game a bit. They also end the current cycle.)\n * Display counters within strings, IE. * \"You have &amp;GEMS&amp;\n  gems.\" (use &amp;&amp; for &amp;)\n * Robot command- LOAD CHAR SET \"file.chr\"\n * New default characters for global chars and char set.\n * When adding another board, copy most of the options (can\n  attack, etc.) from the current board, including MOD file.\n * Edit ANY character/color from Global Chars.\n * Char editor- Revert to ASCII, Revert to MegaZeux, changed\n  REVERSE to NEGATIVE.\n * Alt+Y Debug info LABELs each line.\n * Block command- Paint (w/color).\n * Board editor- Scroll when cursor is five spaces from the\n  edge.\n * Fade in/out between screens and program areas.\n * Ice does NOT keep pushing you against something. If you are\n  blocked, cease movement.\n * Make sound test available in editor as an option, from the\n  sound effects editing screen.\n * Can't test a save-locked game w/o pw, and it no longer locks\n  up from this.\n * REMOVED Scroll coloring on line by line. (using ! codes)\n * Export .ANS file.\n * Energizer will return player color to old color.\n * Capture and throw away ctrl+C, PrintScreen, SysRq, and\n  Pause.\n * Return in editor at end of program can now add a blank line.\n * Allow marking of a section of Robot, in lines. You can now\n  Copy, Cut, Clear, or Paste blocks.\n * Remove useless \"pro\" mode.\n * Robot commands- DIVIDE, MULTIPLY, MODULO.\n * Different player pics per direction.\n\n<a class=\"hL\" href=\"#NEWINVER.HLP__1st\">NEW in MegaZeux!</a>\n<a class=\"hL\" href=\"#292CLOG.HLP__292\">New in Versions 2.92 to 2.92f</a>\n<a class=\"hL\" href=\"#2901CLOG.HLP__291\">New in Versions 2.90 to 2.91j</a>\n<a class=\"hL\" href=\"#284CLOG.HLP__284\">New in Versions 2.84 to 2.84c</a>\n<a class=\"hL\" href=\"#2823CLOG.HLP__283\">New in Versions 2.82 to 2.83</a>\n<a class=\"hL\" href=\"#281CLOG.HLP__281\">New in Versions 2.81 to 2.81h</a>\n<a class=\"hL\" href=\"#280CLOG.HLP__280\">New in Versions 2.80 to 2.80h</a>\n<a class=\"hL\" href=\"#OLDERVER.HLP__260\">New in Versions 2.60 to 2.70</a>\n<a class=\"hL\" href=\"#OLDESVER.HLP__201\">New in Versions 2.01 to 2.60</a>\n<a class=\"hL\" href=\"#ANCENVER.HLP__1xx\">New in Versions Through 1.03</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n<div class=\"hF\" id=\"ANCENVER.HLP\">\n<a class=\"hA\" name=\"ANCENVER.HLP__1xx\"> </a>\n<p class=\"hC\"><span class=\"f9\">New in Versions Through 1.03</span></p>\n1.03 release: QUICK FIXES, LIMITED DISTRIBUTION\n\n * TELEPORT command fixed\n * Gives error on attempt to test pw-protected world\n * \"Explosions leave Empty\" won't screw up floors, etc. (except\n   web)\n * Placing a bomb over an entrance no longer warps to the title\n   screen\n * INPUT STRING allows input of spaces\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n1.02 release: NEW:\n\n * Miscellaneous help errors fixed\n * Miscellaneous errors in Caverns fixed\n * Miscellaneous speed/efficiency code updated\n * AVALANCHE fully fixed, now can be entered into a robot\n * Timing code fixed (may fix some problems)\n * Slight modification to keyboard code (may fix some problems)\n * Added -keyboard2 option\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n1.01 release: FIRST \"NON-BETA\" RELEASE:\n\n * JUMP MOD ORDER 'num' has now been documented.\n * AVALANCE corrected to AVALANCHE.\n * Robot command GO DIR \"LABEL\" fixed.\n * Some minor help errors fixed.\n * Fixed bug where a BECOME THING command, if becoming a space,\n   fake, or other \"under\"-type object, would not erase anything\n   already under the robot.\n * REL TO PLAYER and REL TO SELF now work with the TELEPORT\n   command.\n * If a robot (from WALKing) is sent to the EDGE label and none\n   exists, it then attempts to find a THUD label as well.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n1.00g release: FIRST PUBLIC RELEASE:\n\n * Removed ALL joystick support- Didn't work, couldn't get it\n   to.\n * PW protect- lot simpler, faster, and easier to hack. :)\n * GLOBAL INFO- Toggleable game over music (you should correct\n   this in all your pre-1.00g games)\n * Fixed misc. bugs in games. SOMEONE NEEDS TO TEST THEM ALL\n   AGAIN FROM START TO FINISH.\n * Strings w/o quotes that begin with C or P are not\n   capitilized funny.\n * Settings box no longer turns off music.\n * Alt-A in editor (param) gives error if there is not a legal\n   \"thing\".\n * ALLIGNED fixed to ALIGNED (spelling error inherited from\n   ZZT)\n * Fixed bug where if slow time or freeze time was active when\n   player touched a door, player was copied.\n * Help- fixed slight bugs\n * Fixed minor problems in Caverns, Forest, and Chronos\n * MOD not required for SAM\n * Level not speeded up if MOD not playing\n * MOD not require a speed command (speed properly done)\n * Help uses Alt-P to print\n * Speed defaults to 3 not 4\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n6th beta release: NEW:\n\n * Fixed bug where slow/stop effects didn't work on title.\n * Fixed bug in command MOVE PLAYER where it would occasionally\n   create junk.\n * Fixed bug in robot editor where if \"Show Colors\" option\n   (Alt-O) was off, any color with a ? for the second digit\n   showed an incorrect first digit- cV? for cF?, etc.\n * The direction UNDER now works with the PUT thing dir PLAYER\n   command.\n * Dir/drive support added to loading MZX fileboxes.\n * HELP IS IN ONE FILE- MZX_HELP.PKG (note- memory minimum is\n   still maintained.)\n * Fixed bug where fire could hurt you indirectly even if\n   lavawalker was in effect.\n * Fixed bug where nothing (except player) could push a\n   pushable robot head on. (only if it was in a string of\n   things to push)\n * Fixed bug where shooting fire would destroy entrances.\n * Fixed bug in Put commands that would erase before placing,\n   so putting things over a fake or floor erased the\n   fake/floor. (etc)\n * Change command- Didn't change params properly if you were\n   changing between like objects (IE text to text) Also made\n   more robust against attempts to change things to\n   robots/scrolls/signs/sensors/players.\n * Put commands- Made more robust against attempts to put\n   robots/scrolls/signs/sensors/players.\n * Fixed bug where no player on a board would really mean no\n   player!\n * With Shift-F1 through Shift-F4 in editor, the key used to\n   stop the flashing is not interpreted but ignored.\n * Sensor/Robot interactions! (See help)\n * PageUp will change menus in the editor now. (along with\n   PageDown)\n * Removed all refrences (except in file format) to double\n   speed robots and projectiles. Code increase is not worth it.\n * RANDB, RANDNB directions added!\n * Misc. help corrections and bug fixes, finished help. (except\n   for minor changes)\n * Alt-O (options) no longer clears the command line at the\n   bottom\n * Character insert in robot editor now inserts the characters\n   corresponding to 10 and 13.\n * If you select base 16 in Alt-O options (robot editor) then\n   ALL numbers are shown in hex, including when editing the\n   line. (previously, the current line was shown in base 10 at\n   all times.)\n * Chronos-Stasis finished. (not tested outside of myself)\n * Forest of Ruin finished except for ending. (see above)\n * Repeat (F4) and Delete line (Alt-D) added to robot editor\n * Copy, Cut, and Paste line (Alt-C/T/P) added to robot editor\n   (note- paste REPLACES current line)\n * Export robot (Alt-X) added to editor. Exporting from current\n   line or top of program both supported.\n * Import robot (Alt-I) added to editor. Importing only looks\n   for *.TXT in the filebox. Import always REPLACES the current\n   robot program.\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n5th beta release: NEW:\n\n * Fixed bug in teleport command where screen was not updated\n   if player ended up in the same position.\n * Fixed bug where mouse didn't select right item on game menu\n * Fixed bug where adding a new robot/scroll/sensor when the\n   current item was a robot/scroll/sensor would reset the color\n   to lt.grey\n * Caverns all finished (hope I got all the bugs)\n * The DEF.COLORS option doesn't reset when changing/adding\n   boards now\n * SAM now works right...\n * In fact, added new code for MOD and VOC- a LOT less \"out of\n   mem\" or \"error loading\" errors.\n * Fixed bug with locked doors (not locks, but real doors)\n * Saving games and files is MUCH faster in almost all\n   circumstances. Save meter more accurate as well.\n * Board import/export fixed- robots/scrolls/sensors were\n   messed up\n * Pgdn, Pgup, Ctrl/Alt Home, Ctrl/Alt End added to robot\n   editor.\n * Ctrl/Alt Left + Right used to jump 10 chars in character\n   selection boxes.\n * Save area in character editor (F-2 and F-3) stays the same\n   between calls to the character editor, including when\n   loading games.\n * Character editor shows not only current char pic, but those\n   of the three chars before and three chars after as well.\n * Fixed bug where a robot with no program would majorly mess\n   up things.\n * Misc. bug fixes, help typos, and optimizations.\n !ADDED 14 COMMANDS (all commands finished)\n !ADDED  2 DIRECTIONS (randp, randnot)\n * Help brings up Robot Table of Contents within robot editor\n * Quotes not required around strings of one word not matching\n   any reserved word, IE a word used for ANYTHING else,\n   anywhere. They are added automaticaly, though.\n * EDITOR OPTIONS in robot editor (Alt-O) Choose number base,\n   upper case or lower case, and whether to display colors.\n * Bug fixed in editor- Now colors like c7? (with ? at end)\n   work right.\n * Calibrate Joystick button added to Game options (F2) Tony-\n   See if this helps.\n * Speeded up certain file operations. This MAY fix the\n   \"lockup\" bug when accessing Help during a MOD- I think that\n   the lockup may of been just a really slow file access.\n * Added X position indicator to bottom left of robot window,\n   to facilate creating box messages that aren't too long.\n * Fixed bug that allowed entering ANY character at the mixing\n   rate prompt.\n * INPUT STRING, IF STRING \"str\" \"label\", IF STRING NOT \"str\"\n   \"label\" robot commands added.\n * UNDER/BENEATH direction added to IF dir PLAYER thing \"label\"\n   command.\n * Clarified options at startup- Music device became Device for\n   Digitized Music and Sound.\n * Added four more mixing rates. Choose from 7500 up to 25000\n   in jumps of 2500. (eight rates total)\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n4th beta release: NEW:\n\n * Note- SAVE GAME FORMAT CHANGED. Delete all your .SAV files.\n   .MZX file format NOT changed- compatibility still\n   maintained.\n * Scroll border/etc. colors changable using robot commands-\n   SCROLLBASE COLOR, SCROLLBORDER COLOR, etc. (see help)\n * Removed Alt-Minus and Alt-Plus keys in editor- Useless and\n   dangerous. (they were only for testing purposes.)\n * Various bug fixes\n * Joystick support- UNTESTED! Tony- I need feedback!\n * Mixing rate options at startup.\n * A warning is given and verification is necessary if you try\n   to save a .MZX file over an already existing file.\n * You can now choose from some additional music output\n   devices.\n * Help is in seperate files, MZX*.HLP, to save memory. (lots!)\n * I FINALLY THINK THE MEMORY AND FILE CODE WORKS PERFECT!\n   Except for speed. Something to work on...\n * Char editor shows values of bytes along left side, for use\n   with robot command CHAR EDIT.\n * Yet more help\n * Robot editor! See the top box for keys to use (Import/eXport\n   not implemented yet)\n * Robots as well! See Help, Robo-P Reference, Command\n   Reference, and look at commands for help using them. Some\n   are not done yet.\n * Video mode returns to default VGA mode (text 16x9) on exit\n * File lists sorted\n * Directory support and DRIVE support in file boxes WAS added,\n   but was taken out due to problems since changing the current\n   directory makes it almost impossible to find the current\n   .MZX file to read boards from.\n * Custom Critical Error Handler (no dos grey messages if the\n   drive is invalid or something like that)\n * Password displayed as *'s when inputting, except on\n   protection menu. (Do YOU think it should be *'s on\n   protection menu? 'T'? CP?)\n * Enemys-hurt-enemys option added (GLOBAL info, SPEC. button)\n * \"Show Robots\" option added (Shift F2)\n * More help added\n * Fixed sensor bug (The player couldn't move onto it in most\n   cases)\n * Fixed a bug where if you tried to Grab something via the\n   Modify menu and the current thing was a scroll, sign, or\n   robot that had no copy anywhere else on the current board,\n   then the memory allocated to that object was not freed.\n   (whew!)\n * A few more optimizations\n * Mouse disapears after a certain period of time of non-use\n * Boulders from the AVALANCHE potion, and explosions from the\n   BLAST potion can't appear over an entrance anymore\n * Fixed some bugs in Text Export\n * Removed .BIN import- Waste of program space\n * Made ALL options (protection, ANSi import) available in\n   unregistered- now registration is only for the games\n * Removed \"suicide key\" from game. Think about how it could\n   screw up carefully planned cinemas, story sequences, etc.\n * Mouse support in character editor\n * Test function added\n * Speeded up saves and board switches\n * Slowed down all file access (okay, so it's not a feature,\n   but it's more reliable, as is memory management as well)\n * Made save meter more accurate\n * BLOCK COMMANDS! (in editor)\n * ZZT board import (not perfectly accurate, but...)\n * Bug fixed where games with the title screen title at maximum\n   length would mess up the file list box\n * MOD (SB &amp; speaker) support! (&amp; SAM)\n * Explosion-meets-player bug fixed\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n3rd beta release: NEW:\n\n * Full mouse support (except in character editing)\n * No \"pointer\" mouse...\n * Scrolls and sensors\n * Robot code (memory management) mostly transparent to user\n * GLOBAL Info- Death &amp; endgame options\n * GLOBAL Info- Removed \"Health Only/Lives Only\" options\n * CW/CCW support\n * Sound always the same speed\n * Bug fixes and optimizations\n * Status Info added\n\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n2nd beta release: NEW:\n\n * Cmd line options\n * Mouse support (not finished)\n * -Bios option\n * Hopefully fixed Tony's graphics bugs (try it with and\n   without -bios)\n * Bug fixes\n * Tutorial.mzx has creatures/guns sections\n * Fixed/better/changed keyboard routines\n * Optimized code\n\n<a class=\"hL\" href=\"#NEWINVER.HLP__1st\">NEW in MegaZeux!</a>\n<a class=\"hL\" href=\"#292CLOG.HLP__292\">New in Versions 2.92 to 2.92f</a>\n<a class=\"hL\" href=\"#2901CLOG.HLP__291\">New in Versions 2.90 to 2.91j</a>\n<a class=\"hL\" href=\"#284CLOG.HLP__284\">New in Versions 2.84 to 2.84c</a>\n<a class=\"hL\" href=\"#2823CLOG.HLP__283\">New in Versions 2.82 to 2.83</a>\n<a class=\"hL\" href=\"#281CLOG.HLP__281\">New in Versions 2.81 to 2.81h</a>\n<a class=\"hL\" href=\"#280CLOG.HLP__280\">New in Versions 2.80 to 2.80h</a>\n<a class=\"hL\" href=\"#OLDERVER.HLP__260\">New in versions 2.60 to 2.70</a>\n<a class=\"hL\" href=\"#OLDESVER.HLP__201\">New in versions 2.01 to 2.60</a>\n<a class=\"hL\" href=\"#NEWIN200.HLP__1st\">New in version 2.00</a>\n<a class=\"hL\" href=\"#MAIN.HLP__072\">Table of Contents</a>\n\n<hr></div>\n\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "docs/old/betatest.txt",
    "content": "\nMZX 2.50 (2.48g/2.49b) beta testers-\n\nName (Email) Computer::Sound card or \"Compatible\" -- Notes\n\nGreg Beauchesne (zapyo@aol.com) Pentium P60::SB-16\nAdam Parrish (myth@alinc.com) 486DX4-100::SB-32\n\"Adam Smith (Aldar)\" (smitha@edmonds.wednet.edu) 386-33::\"SB-Pro\"\nJohn Howard Davison (ohio1@imcnet.net) 486DX2-50::SB 1.x\n\"Geoff Paulson\" (gpaulson@3-cities.com) 486DX::SB-16\nScott Hammack (shammack@goldinc.com) 486DX2-100::SB-16\n\"Ben Krause\" (subzero@neont.com) 486::SB-16\nKevin Vance (kevvance@aol.com) 486DX-60::\"SB 1.x\"/\"SB-Pro\"\n\t  Pentium 75::\"SB 1.x\"\nKevin Rohleder (krohleder2@aol.com) Pentium 75::\"SB 1.x\"/\"SB 2.x\"\n\"Brad Faler\" (falerb@ida.net) Pentium 133::SB-32\nMike Sambrone (mgs@indirect.com) 386DX-60:SB 2.x\nLuke Drelick (duky@worldnet.att.net) Pentium 75::SB-16\nPeter Holzaepfel (user377367@gnn.com) Pentium 90::\"SB 1.x\"\n\t  Pentium 75::SB-Pro\n\t  486-75::SB-16\n\t  Pentium 75::None\n\t  386-16::None\nJacob Page (jpages@srv.net) Pentium 60::\"SB 1.x\"\nEvan Furchtgott (thecreator@pipeline.com) Pentium 166::SB-32\nDave Bishop (dnbishop@aol.com) 486DX2-50::SB-16\nWilliam Lovas (wlovas@pantek.com) 486DX4-100::\"SB 1.x\"\nMatthew McGee (thedianoga@aol.com) 486SX-33::SB-16/SB-Pro\n\t  586-100::\"SB-16\"\nTimothy Fernandez (fergie@sorcererisle.com) 486SX-25::SB-16\nDan Patalano (mogthe1st@aol.com) Pentium 100::SB-Pro\n\"Luc French\" (lfrench106@aol.com) 486-??::\"SB 1.x\"\n\"DeadPhrog (J.W. Kaufmann)\" (deadphrog@sinewave.com) 486DX-66::\"SB-Pro\"\n\"James \"Raptor\" Wong\" (gwong@ozemail.com.au) 486DX-66::SB-16\n\"Jason E. Neufeld\"(73267.3322@compuserve.com) 586-100::SB-32\n\n(zapyo@aol.com)\n(myth@alinc.com)\n(smitha@edmonds.wednet.edu)\n(ohio1@imcnet.net)\n(gpaulson@3-cities.com)\n(shammack@goldinc.com)\n(subzero@neont.com)\n(kevvance@aol.com)\n(krohleder2@aol.com)\n(falerb@ida.net)\n(mgs@indirect.com)\n(duky@worldnet.att.net)\n(user377367@gnn.com)\n(jpages@srv.net)\n(thecreator@pipeline.com)\n(dnbishop@aol.com)\n(wlovas@pantek.com)\n(thedianoga@aol.com)\n(fergie@sorcererisle.com)\n(mogthe1st@aol.com)\n(lfrench106@aol.com)\n(deadphrog@sinewave.com)\n(gwong@ozemail.com.au)\n(73267.3322@compuserve.com)\n\n\n\n\n\n\n\n"
  },
  {
    "path": "docs/old/filef200.txt",
    "content": "\nFile format for the .MZX world file (w/o password protection)\n-----------------------------------\n\nThis file format is for version 2.00. No attempt at compatibility with\nversion 1.0? is made within the format. Megazeux version 2.00 will,\nhowever, recognize and load version 1.0? .MZX files.\n\nByte(s)\t\tIs\n\n00-24\t\t\tTitle of game (name of first board) w/terminating null. Padded\n\t\t\t\twith junk.\n25\t\t\t\tProtection method (0=none, 1=no-save/test, 2=no-edit, 3=total)\n\t\t\t\tIf not a 0, followed by a fifteen character password\n\t\t\t\t(encrypted) The rest of the file, after the \"MZ2\", is entirely\n\t\t\t\tencrypted.\n26-28\t\t\tThe letters \"MZ2\". If the last letter is an X, this is a ver\n\t\t\t\t1.0? file. If the last letter is anything else, it is a more\n\t\t\t\trecent, incompatible version file. 3-9 will represent versions\n\t\t\t\t3.00 to 9.00, and A-Z for anything after that.\n29-3612\t\tThe character set (14 bytes per character, 256 characters)\n\t\t\t\tEach character is 14 bytes, from the top row to the bottom\n\t\t\t\trow, one byte per pixel row.\n3613-4067\tIdChars array (455 bytes) This is the info in Global Chars,\n\t\t\t\tas well as anything that can be edited with CHANGE CHAR ID.\n4068-4157\tThe SIX status counters, IE those counters shown on the\n\t\t\t\tstatus screen. Each is 15 chars long, including a terminating\n\t\t\t\tnull. Padded with junk.\n4158\t\t\tColor of edge border during play.\n4159\t\t\tStarting board number.\n4160\t\t\tBoard for end game. (255 for no board)\n4161\t\t\tBoard for death. (255 to restart screen, 254 for same pos)\n4162-4163\tX position for end game.\n4164-4165\tY position for end game.\n4166\t\t\t1 if game over sfx are played.\n4167-4168\tX position for death.\n4169-4170\tY position for death.\n4171-4172\tStarting number of lives. (word)\n4173-4174   Limit to lives. (word)\n4175-4176\tStarting amount of health. (word)\n4177-4178\tLimit to health. (word)\n4179\t\t\t1 if enemies can hurt each other, 0 if not.\n4180\t\t\t1 if all messages and projectiles are cleared between screens.\n4181\t\t\t1 if the game is only playable via a robot SWAP WORLD command.\n4182-4229\tPalette- colors 0-15, R G B per. RGB are from 0 to 63.\n4230-4233\tPosition within file of global robot.\n4234\t\t\tCode- 0 means SFXs follow. 1-150 means use normal SFX and this\n\t\t\t\tnumber is equal to the number of boards.\n\n\t\t\t\tSFXs- 50 of them (0-49) Each is any length up to 68, preceded by\n\t\t\t\ta length byte (1-69) of the string w/null termination. Normal\n\t\t\t\tSFX format. The very first two bytes is a WORD of how long\n\t\t\t\tthis entire section is, not including the first WORD. After\n\t\t\t\tthis section is a number for the number of boards.\n\n4235-????\tPer board- 25 char title, including null terminator. Padded\n\t\t\t\twith junk.\n????-????\tPer board- Length of board in bytes (dword) and position of\n\t\t\t\tboard within file. (byte number, dword)\n\nAt location of board- The board itself. (see .MZB files, exactly the same)\nAt location of global robot- The robot itself. (See .MZB files)\n\nA board of 0 length is a deleted board and has junk at it's \"position\nwithin the file\".\n\nFormat for a .MZB board file\n----------------------------\n\nThis file format is for version 2.00. No attempt at compatibility with\nversion 1.0? is made within the format. Megazeux version 2.00 will,\nhowever, recognize and load version 1.0? .MZB files.\n\nNote that INTERNAL storage of boards (other than the current board) is\nvery similar except there the four id bytes at the start are not present\nand the board name is not repeated at the end.\n\nBytes 0-3:\tAn 0FFh, followed by the string \"MB2\" (not present in .MZX and\n\t\t\t\t.SAV files)\nByte 4:     0-4 for overall sizing of board- 60x166,80x125,100x100,200x50,\n\t\t\t\tor 400x25.\n\nThen a byte code- if it is 0, it signifies that an overlay is present. It\nis followed by the overlay mode as a byte (1=normal, 2=static, 3=transparent)\nand the overlay, RLE2 encoded, and the overlay colors, RLE2 encoded. Blank\nspots in the overlay are spaces (32) of any color. The overlay is the same\nsize of the board. This is followed as normal by the level ids, etc.\n\nIf the byte is NOT 0, it is the first encoded byte of the level ids, as\nseen below.\n\nNext-  Level ids, RLE2 encoded.\nThen-  Level colors, RLE2 encoded.\nThen-  Level params, RLE2 encoded.\nThen-  Ids under stuff, RLE2 encoded.\nThen-  Colors of ids under stuff, RLE2 encoded.\nLast-  Params of ids under stuff, RLE2 encoded.\n\nLevel id/color/param is a number representing an object, a number for it's\ncolor, (bk and fg) and a number representing it's stats. (IE robot id, or\nenemy stats, or custom chars, or passage destination, etc) These are all\nbytes. The \"under stuff\" is for things beneath other things, like floors and\ncarpets. All six sets are RLE2 encoded, a simple compression method. The\nmethod works as follows- The first word is the x size (of the board) and the\nsecond word is the y size. Then the actual data follows- Read a byte. If the\nMSB (+128) is NOT set, then it is that byte. Otherwise, reset the MSB to get\nthe number of times to repeat the next byte. This is called a run. Single\nbytes of values over 127 are represented as a run of 1. Runs can wrap around\nlines. The board is over when X * Y bytes have been recovered.\n\nAfter board design-\n\n00-12    \tName of default MOD file, w/extension and null terminator.\n\t\t\t\tPadded with junk.\n13\t\t\t\tX position of upper left corner of the viewport.\n14\t\t\t\tY position of upper left corner of the viewport.\n15\t\t\t\tX size of the viewport.\n16\t\t\t\tY size of the viewport.\n17\t\t\t\t1 if you can shoot here, 0 otherwise.\n18\t\t\t\t1 if you can bomb here, 0 otherwise.\n19\t\t\t\t1 if fire burns through brown things, 0 otherwise.\n20\t\t\t\t1 if fire burns through spaces, 0 otherwise.\n21\t\t\t\t1 if fire burns through fakes, 0 otherwise.\n22\t\t\t\t1 if fire burns through trees, 0 otherwise.\n23          Code for what an explosion leaves- 0 for nothing, 1 for ash,\n\t\t\t\t2 for fire.\n24\t\t\t\t1 if you can't save here, 2 if you can only save on a sensor,\n\t\t\t\t0 for normal saving.\n25\t\t\t\t0 if forest becomes empty, 1 if forest becomes floor.\n26\t\t\t\t1 if you collect bombs normally, 0 if they automatically light.\n27          1 if fire burns forever, 0 otherwise.\n28-31\t\t\tBoard numbers for boards to the N S E and W. 255 for no board.\n32\t\t\t\t1 if you restart the screen when zapped, 0 otherwise.\n33-34\t\t\tTime limit. (word) Current time is stored as a counter (TIME)\n35\t\t\t\tThe last alphabetic key that was pressed. (A-Z or 0-9)\n36-37\t\t\tNumeric form of the last input. (word)\n38\t\t\t\tSize in characters of the last input.\n39-119\t\t81 char string (including null terminator and junk padding) of\n\t\t\t\tthe last string inputted.\n120\t\t\tLast direction the player moved. (0=None, 1=N, 2=S, 3=E, 4=W)\n\t\t\t\tUpper nybble (*16) is the direction faced. (0-3=NSEW)\n121-201\t\t81 char string (including null terminator and junk padding) of\n\t\t\t\tcurrent message at the bottom of the screen.\n202\t\t\tNumber of cycles until the message at the bottom of the screen\n\t\t\t\tdisappears.\n203\t\t\tLazer wall timer, from 0 to 7. (increases once per cycle and\n\t\t\t\tloops from 7 to 0)\n204\t\t\tRow for the message row.\n205\t\t\tColumn for the message row, 255 for centered.\n206-207\t\tX scroll of the screen, signed word. (From SCROLL VIEW)\n208-209\t\tY scroll of the screen, signed word. (From SCROLL VIEW)\n210-211\t\tLocked X position for the screen. Used for the upper left corner.\n\t\t\t\t65535 for normal scrolling. Not affected by SCROLL VIEW.\n212-213\t\tLocked Y position for the screen.\n214\t\t\t1 if the player is locked in the ns direction.\n215\t\t\t1 if the player is locked in the ew direction.\n216\t\t\t1 if the player is locked from attacking.\n217\t\t\tCurrent MOD volume\n218\t\t\tCurrent MOD volume change amount\n219\t\t\tCurrent MOD volume target\n220\t\t\tNumber of robots.\n\nPer robot- (these robots start counting at ID #1. ID #0 is reserved internal\n\tuse.)\n\n0-1         (unsigned word) Length of robot program\n2-3\t\t\tJunk.\n4-18\t\t\t15 character robot name, including null terminator. Padded with\n\t\t\t\tjunk.\n19\t\t\t\tRobot character.\n20-21\t\t\t(unsigned word) Location within program of current line\n22\t\t\t\t(unsigned byte) Location within current line, IE countdown\n\t\t\t\tfor WAIT and GO commands.\n23\t\t\t\t(unsigned byte) Robot cycle number.\n24\t\t\t\t(unsigned byte) Robot cycle count. Reset to 0 after robot runs\n\t\t\t\ta program section, then each cycle counts up 1. When it reaches\n\t\t\t\tthe Robot cycle number, program continues and this is again\n\t\t\t\treset to 0.\n25\t\t\t\tBullet type code- 0 player, 1 neutral, 2 enemy.\n26\t\t\t\tSet to one if robot is locked from recieving messages.\n27\t\t\t\tSet to one for a lava walking robot.\n28\t\t\t\tCurrent direction of walk. (1 North 2 South 3 East 4 West)\n29\t\t\t\tLast direction the robot was touched. (1-4)\n30\t\t\t\tLast direction the robot was shot. (1-4)\n31-32\t\t\tRobot's x position\n33-34\t\t\tRobot's y position\n35\t\t\t\tReserved for internal use (Value of 0, 1, or 2)\n36\t\t\t\tFuture expansion (0)\n37\t\t\t\tFuture expansion (0)\n38\t\t\t\tSet to 1 if actually used onscreen, 0 if this robot is\n\t\t\t\tirrevelant. (even irrevelant robots store a program though)\n39-40\t\t\tLoop count for the robot's current loop. Not reset after the\n\t\t\t\tloop.\n\nThis is now followed by the robot's program according to it's length.\n\nNext-\n0\t\t\t\tNumber of scrolls (counting from id #1)\n\nPer scroll-\n0-1\t\t\t(unsigned word) Number of lines in the scroll\n2-3\t\t\tJunk.\n4-5\t\t\t(unsigned word) Length in chars of the scroll.\n6\t\t\t\tSet to 1 if actually used onscreen, 0 if this one is irrevelant\n\nThis is now followed by the scroll's text. Signs count as scrolls.\n\nNext-\n0\t\t\t\tNumber of sensors (counting from id #1)\n\nPer sensor-\n0-14\t\t\t15 char name of sensor, including null terminator. Padded with\n\t\t\t\tjunk.\n15\t\t\t\tSensor char.\n16-30\t\t\t15 char name of robot to send messages to, including null\n\t\t\t\tterminator. Padded with junk.\n31\t\t\t\tSet to 1 if actually used onscreen, 0 if this one is irrevelant\n\nFinally, the board name (25 chars including null and junk padding) is at the\nend of the board ONLY IN A .MZB FILE. Within a .MZX or .SAV file, this is not\npresent.\n\nFormat for a .SAV saved game file (Megazeux)\n---------------------------------\n\nThis file format is for version 2.00. No attempt at compatibility with\nversion 1.0? is made within the format. Megazeux version 2.00 will NOT\nrecognize or load version 1.0? .SAV files.\n\n00-05\t\t\tThe letters \"MZSV2\" and a null terminator.\n06\t\t\t\tCurrent board number.\n07-3590\t\tThe character set (14 bytes per character, 256 characters)\n\t\t\t\tEach character is 14 bytes, from the top row to the bottom\n\t\t\t\trow, one byte per pixel row.\n3591-4045\tIdChars array (455 bytes) This is the info in Global Chars,\n\t\t\t\tas well as anything that can be edited with CHANGE CHAR ID.\n4046-4105\tThe SIX status counters, IE those counters shown on the\n\t\t\t\tstatus screen. Each is 15 chars long, including a terminating\n\t\t\t\tnull. Padded with junk.\n4106-4121   Current keys held. Each byte is either 0-15 for a key or 127\n\t\t\t\tfor no key.\n4122-4125\t(dword) Current score.\n4126\t\t\tNumber of cycles remaining of blindness.\n4127\t\t\tNumber of cycles remaining of firewalking.\n4128\t\t\tNumber of cycles remaining of frozen time.\n4129\t\t\tNumber of cycles remaining of slowed time.\n4130\t\t\tNumber of cycles remaining of wind.\n4131-4146\tX positions of saved player positions. (SAVE PLAYER POSITION cmd)\n4147-4162\tY positions of saved player positions. (one word each)\n4163-4170\tBoard numbers (0-149) of saved player positions.\n4171\t\t\tSaved color of the player for when an energizer is active.\n4172\t\t\tId under player's bomb/sensor\n4173\t\t\tColor under player's bomb/sensor\n4174\t\t\tParam under player's bomb/sensor\n4175\t\t\t1 if message edges are enabled\n4176\t\t\tBase color for scrolls.\n4177\t\t\tCorner color for scrolls.\n4178\t\t\tPointer color for scrolls.\n4179\t\t\tTitle color for scrolls.\n4180\t\t\tArrow color for scrolls.\n4181\t\t\tColor of edge border during play.\n4182\t\t\tStarting board number.\n4183\t\t\tBoard for end game. (255=none)\n4184\t\t\tBoard for death. (255=restart, 254=same pos)\n4185-86\t\tX position for end game.\n4187-88\t\tY position for end game.\n4189\t\t\tIf sound fx play on game over.\n4190-91\t\tX position for death.\n4192-93\t\tY position for death.\n4194-95\t\tStarting number of lives. (word)\n4196-97 \t  \tLimit to lives. (word)\n4198-99\t\tStarting amount of health. (word)\n4200-01\t\tLimit to health. (word)\n4202\t\t\t1 if enemies can hurt each other, 0 if not.\n4203\t\t\t1 if all messages and projectiles are cleared between screens.\n4204\t\t\t1 if the game is only playable via a robot SWAP WORLD command.\n4205-68\t\tPalette- colors 0-15, R G B. Each is 0-63.\n4269-84\t\tCurrent intensity palette, colors 0-15, each 0 to 100%.\n4285\t\t\tCurrent faded bit- 0 if faded in, 1 if faded out.\n4286-87\t\tX position of player's entry point on this screen.\n4288-89\t\tY position of player's entry point on this screen.\n4290\t\t\tID of what is under the under of the player.\n4291\t\t\tParam of what is under the under of the player.\n4292\t\t\tColor of what is under the under of the player.\n4293\t\t\tNumber of counters\nPer counter-\n00-14\t\t\t15 char name of counter, including null terminator. Padded with\n\t\t\t\tjunk.\n15-16\t\t\t(signed word) Value of counter.\nThen-\n00-01\t\t\tPosition within file of global robot. (Stored as ID #0)\n02\t\t\t\tCode- 0 means SFXs follow. 1-150 means use normal SFX and this\n\t\t\t\tnumber is equal to the number of boards.\n\n\t\t\t\tSFXs- 50 of them (0-49) Each is any length up to 68, preceded by\n\t\t\t\ta length byte (1-69) of the string w/null termination. Normal\n\t\t\t\tSFX format. The very first two bytes is a WORD of how long\n\t\t\t\tthis entire section is, not including the first WORD. After\n\t\t\t\tthis section is a number for the number of boards.\n\n03-????\t\tPer board- 25 char title, including null terminator. Padded\n\t\t\t\twith junk.\n????-????\tPer board- Length of board in bytes (dword) and position of\n\t\t\t\tboard within file. (byte number, dword)\n\nAt location of board- The board itself. (see .MZB files, exactly the same)\n"
  },
  {
    "path": "docs/old/filef268.txt",
    "content": "MZX save file format extensions\n\nThese all are stored after the counters in the save file.\n\nByte(s)\t\t\n\n00-1023\t\t\t\tSprites (16 bytes each * 64)\n1024\t\t\t\tTotal sprite count\n1025\t\t\t\tSprite y_order flag\n1026-1091\t\t\tSprite collision info (66 bytes)\n1092-2115\t\t\tStrings (16 bytes each * 64)\n2116-2117\t\t\tMath multiplier\n2118-2119\t\t\tMath divider\n2120-2121\t\t\tCircle divider\n2122-2133\t\t\tInput file name\n2134-2137\t\t\tInput file position\n2138-2149\t\t\tOutput file name\n2150-2153\t\t\tOutput file position\n\n\n\n"
  },
  {
    "path": "docs/old/fileform.txt",
    "content": "\nFile format for the .MZX world file (w/o password protection)\n-----------------------------------\n\nThis file format is only accurate up through version 1.03.\n\nByte(s)\t\tIs\n\n00-24\t\t\tTitle of game (name of first board) w/terminating null. Padded\n\t\t\t\twith junk.\n25\t\t\t\tProtection method (0=none, 1=no-save, 2=no-edit, 3=total)\n\t\t\t\tIf not a 0, followed by the length of the password, then the\n\t\t\t\tfifteen character password (encrypted) The rest of the file is\n\t\t\t\tunaffected.\n26-28\t\t\tThe letters \"MZX\"\n29-3612\t\tThe character set (14 bytes per character, 256 characters)\n\t\t\t\tEach character is 14 bytes, from the top row to the bottom\n\t\t\t\trow, one byte per pixel row.\n3613-3918\tIdChars array (306 bytes) This is the info in Global Chars,\n\t\t\t\tas well as anything that can be edited with CHANGE CHAR ID.\n3919-3978\tThe four status counters, IE those counters shown on the\n\t\t\t\tstatus screen. Each is 15 chars long, including a terminating\n\t\t\t\tnull. Padded with junk.\n3979-3982\tBullet characters- N S E W.\n3983\t\t\tPlayer character.\n3984\t\t\tX position of saved player position. (SAVE PLAYER POSITION cmd)\n3985\t\t\tY position of saved player position.\n3986\t\t\tBoard number (0-127) of saved player position.\n3987\t\t\tColor of edge border during play.\n3988\t\t\tColor of player.\n3989\t\t\tColor of bullets.\n3990\t\t\tColor of missiles.\n3991\t\t\tStarting board number.\n3992\t\t\tBoard for end game. (128 for none)\n3993\t\t\tBoard for death. (128 for restart board, 129 for same pos)\n3994\t\t\tX position for end game.\n3995\t\t\tY position for end game.\n3996\t\t\tX position for death.\n3997\t\t\tY position for death.\n3998-3999\tStarting number of lives. (word)\n4000-4001   Limit to lives. (word)\n4002-4003\tStarting amount of health. (word)\n4004-4005\tLimit to health. (word)\n4006\t\t\t1 if enemies can hurt each other, 0 if not.\n4007\t\t\t1 if game over sfx are on, 0 if not.\n4008\t\t\t00.\n4009\t\t\tNumber of boards. (1 to 127)\n4010-????\tPer board- 25 char title, including null terminator. Padded\n\t\t\t\twith junk.\n????-????\tPer board- Length of board in bytes (dword) and position of\n\t\t\t\tboard within file. (byte number, dword)\n\nAt location of board- The board itself. (see .MZB files, exactly the same)\n\n\nFormat for a .MZB board file\n----------------------------\n\nThis file format is only accurate up through version 1.03.\n\nFirst- Level ids, RLE encoded.\nThen-  Level colors, RLE encoded.\nThen-  Level params, RLE encoded.\nThen-  Ids under stuff, RLE encoded.\nThen-  Colors of ids under stuff, RLE encoded.\nLast-  Params of ids under stuff, RLE encoded.\n\nLevel id/color/param is a number representing an object, a number for it's\ncolor, (bk and fg) and a number representing it's stats. (IE robot id, or\nenemy stats, or custom chars, or passage destination, etc) These are all\nbytes. The \"under stuff\" is for things beneath other things, like floors and\ncarpets. All six sets are RLE encoded, a simple compression method. The\nmethod works as follows- The first byte is the x size (of the board) and the\nsecond byte is the y size. After that, read two bytes. The first is the runs\nbyte, the second is the data byte. The runs byte tells how many times to use\nthe data byte. This repeats for the entire x/y size. Runs can wrap across\nmultiple lines.\n\nAfter board design-\n\n00-12    \tName of default MOD file, w/extension and null terminator.\n\t\t\t\tPadded with junk.\n13\t\t\t\tX position of upper left corner of the viewport.\n14\t\t\t\tY position of upper left corner of the viewport.\n15\t\t\t\tX size of the viewport.\n16\t\t\t\tY size of the viewport.\n17\t\t\t\t1 if you can shoot here, 0 otherwise.\n18\t\t\t\t1 if you can bomb here, 0 otherwise.\n19\t\t\t\t1 if fire burns through brown things, 0 otherwise.\n20\t\t\t\t1 if fire burns through spaces, 0 otherwise.\n21\t\t\t\t1 if fire burns through fakes, 0 otherwise.\n22\t\t\t\t1 if fire burns through trees, 0 otherwise.\n23          Code for what an explosion leaves- 0 for spaces, 1 for ash,\n\t\t\t\t2 for fire.\n24\t\t\t\t1 if you can't save here, 2 if you can only save on a sensor,\n\t\t\t\t0 for normal saving.\n25\t\t\t\t0 if forest becomes empty, 1 if forest becomes floor.\n26\t\t\t\t1 if you collect bombs normally, 0 if they automatically light.\n27          1 if fire burns forever, 0 otherwise.\n28-31\t\t\tBoard numbers for boards to the N S E and W. 255 for no board.\n32\t\t\t\t1 if you restart the screen when zapped, 0 otherwise.\n33-34\t\t\tTime limit. (word)\n35\t\t\t\tThe last alphabetic key that was pressed. (A-Z)\n36-37\t\t\tNumeric form of the last input. (word)\n38\t\t\t\tSize in characters of the last input.\n39\t\t\t\tVolume for MOD files.\n40\t\t\t\t1 if the player is locked in the ns direction.\n41\t\t\t\t1 if the player is locked in the ew direction.\n42\t\t\t\t1 if the player is locked from attacking.\n43-123\t\t81 char string (including null terminator and junk padding) of\n\t\t\t\tthe last string inputted.\n124\t\t\tNumber of cycles remaining of blindness.\n125\t\t\tNumber of cycles remaining of firewalking.\n126\t\t\t00.\n127\t\t\tNumber of cycles remaining of frozen time.\n128\t\t\tNumber of cycles remaining of slowed time.\n129-131\t\t00.\n132\t\t\tNumber of cycles remaining of wind.\n133\t\t\tLast direction the player moved. (0=N, 1=S, 2=E, 3=W)\n134-214\t\t81 char string (including null terminator and junk padding) of\n\t\t\t\tcurrent message at the bottom of the screen.\n215\t\t\tNumber of cycles until the message at the bottom of the screen\n\t\t\t\tdisappears.\n216\t\t\tLazer wall timer, from 0 to 7. (increases once per cycle and\n\t\t\t\tloops from 7 to 0)\n217\t\t\tRow for the message row.\n218\t\t\tColumn for the message row, 0 for centered.\n219\t\t\tX scroll of the screen, signed byte. (From SCROLL VIEW)\n220\t\t\tY scroll of the screen, signed byte. (From SCROLL VIEW)\n221\t\t\tNumber of robots.\n\nPer robot- (these robots start counting at id #1. There is no id #0.)\n\n0-3         (signed dword) Length of robot program\n4-7\t\t\tJunk\n8-22\t\t\t15 character robot name, including null terminator. Padded with\n\t\t\t\tjunk.\n23\t\t\t\tRobot character.\n24-27\t\t\t(signed dword) Location within program of current line\n28\t\t\t\t(unsigned byte) Location within current line, IE countdown\n\t\t\t\tfor WAIT and GO commands.\n29\t\t\t\t(unsigned byte) Robot cycle number.\n30\t\t\t\t(unsigned byte) Robot cycle count. Reset to 0 after robot runs\n\t\t\t\ta program section, then each cycle counts up 1. When it reaches\n\t\t\t\tthe Robot cycle number, program continues and this is again\n\t\t\t\treset to 0.\n31\t\t\t\tBullet type code- 0 player, 1 neutral, 2 enemy.\n32\t\t\t\tSet to one if robot is locked from receiving messages.\n33\t\t\t\tSet to one for a lava walking robot.\n34\t\t\t\tCurrent direction of walk. (1 North 2 South 3 East 4 West)\n35\t\t\t\tLast direction the robot was touched. (1-4)\n36\t\t\t\tLast direction the robot was shot. (1-4)\n\nThis is now followed by the robot's program according to it's length.\n\nNext-\n0\t\t\t\tNumber of scrolls (counting from id #1)\n\nPer scroll-\n0-1\t\t\t(unsigned word) Number of lines in the scroll\n2-5\t\t\tJunk.\n6-9\t\t\t(unsigned dword) Length in chars of the scroll.\n\nThis is now followed by the scroll's text. Signs count as scrolls.\n\nNext-\n0\t\t\t\tNumber of sensors (counting from id #1)\n\nPer sensor-\n0-14\t\t\t15 char name of sensor, including null terminator. Padded with\n\t\t\t\tjunk.\n15\t\t\t\tSensor char.\n16-30\t\t\t15 char name of robot to send messages to, including null\n\t\t\t\tterminator. Padded with junk.\n\nFinally, the board name (25 chars including null and junk padding) is at the\nend of the board ONLY IN A .MZB FILE. Within a .MZX or .SAV file, this is not\npresent.\n\n\nFormat for a .SAV saved game file (Megazeux)\n---------------------------------\n\nThis file format is only accurate up through version 1.03.\n\n00-05\t\t\tThe letters \"MZSAV\" and a null terminator.\n06\t\t\t\tCurrent board number.\n07-3590\t\tThe character set (14 bytes per character, 256 characters)\n\t\t\t\tEach character is 14 bytes, from the top row to the bottom\n\t\t\t\trow, one byte per pixel row.\n3591-3896\tIdChars array (306 bytes) This is the info in Global Chars,\n\t\t\t\tas well as anything that can be edited with CHANGE CHAR ID.\n3897-3912   Current keys held. Each byte is either 0-15 for a key or 127\n\t\t\t\tfor no key.\n3913-3962\tThe four status counters, IE those counters shown on the\n\t\t\t\tstatus screen. Each is 15 chars long, including a terminating\n\t\t\t\tnull. Padded with junk.\n3963-3966\tBullet characters- N S E W.\n3967\t\t\tPlayer character.\n3968\t\t\tX position of saved player position. (SAVE PLAYER POSITION cmd)\n3969\t\t\tY position of saved player position.\n3970\t\t\tBoard number (0-127) of saved player position.\n3971\t\t\tColor of edge border during play.\n3972\t\t\tColor of player.\n3973\t\t\tColor of bullets.\n3974\t\t\tColor of missiles.\n3975\t\t\tBase color for scrolls.\n3976\t\t\tBorder color for scrolls.\n3977\t\t\tPointer color for scrolls.\n3978\t\t\tTitle color for scrolls.\n3979\t\t\tArrow color for scrolls.\n3980-3983\t(dword) Current score.\n3984\t\t\tStarting board number.\n3985\t\t\tBoard for end game.\n3986\t\t\tBoard for death.\n3987\t\t\tX position for end game.\n3988\t\t\tY position for end game.\n3989\t\t\tX position for death.\n3990\t\t\tY position for death.\n3991-3992\tStarting number of lives. (word)\n3993-3994   Limit to lives. (word)\n3995-3996\tStarting amount of health. (word)\n3997-3998\tLimit to health. (word)\n3999\t\t\t1 if enemies can hurt each other, 0 if not.\n4000\t\t\tJunk byte.\n4001\t\t\t00.\n4002\t\t\tNumber of counters\nPer counter-\n00-14\t\t\t15 char name of counter, including null terminator. Padded with\n\t\t\t\tjunk.\n15-16\t\t\t(unsigned word) Value of counter.\nThen-\n00\t\t\t\tNumber of boards. (1 to 127)\n01-????\t\tPer board- 25 char title, including null terminator. Padded\n\t\t\t\twith junk.\n????-????\tPer board- Length of board in bytes (dword) and position of\n\t\t\t\tboard within file. (byte number, dword)\n\nAt location of board- The board itself. (see .MZB files, exactly the same)\n"
  },
  {
    "path": "docs/old/megazeux.doc",
    "content": "\nMegaZeux ver 2.51 source -\n\n\t\t  Copyright (C) 1996 by Alexis Janson\n\t\t  Copyright (C) 1998 by Matthew D. Williams\n\n\n  REQUIREMENTS:\n\t\t  IBM PC (286 or higher) compatible computer\n\t\t  640K RAM\n\t\t  EGA or higher display adapter\n\t\t  SoundBlaster/PAS-16/GUS sound card optional\n\t\t  Mouse optional\n\t\t  Dos 2.1 or greater\n\t\t  Hard drive w/at least 5 megabytes free\n\n              Internet: dbwilli@scsn.net\n\t\t  WWW: http://www.scsn.net/users/dbwilli/\n\t\n  This game package may be copied or distributed according to the\n  terms and conditions stated in GNUGPL.DOC.\n\nMegaZeux --- Table of Contents\n\n  Section\n\n\t  I.  License Agreement and Support\n\t II.  Installing and Starting the Program\n\tIII.  Overview of MegaZeux\n       IV.  Controls\n\t  V.  Advanced Play Info, Tips\n\t VI.  The World Editor\n\tVII.  General Editing Tips\n     VIII.  Robots- What They Are and How to Use Them\n\t IX.  FREQUENTLY ASKED QUESTIONS\n\t  X.  Command Line Options\n\t XI.  EMS Memory/Boot Disk Tips\n\tXII.  Known Bugs and Quirks\n     XIII.  Thanks, Acknowledgements, and Miscellaneous\n\nI. LICENSE AGREEMENT AND SUPPORT\n\n  --- LICENSE ---\n \n  This program is free software; you can redistribute it and/or\n  modify it under the terms of the GNU General Public License as\n  published by the Free Software Foundation; either version 2 of\n  the License, or (at your option) any later version.\n \n  This program is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n  General Public License for more details.\n \n  You should have received a copy of the GNU General Public License\n  (GNUGPL.DOC) along with this program; if not, write to the Free Software\n  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n\n  --- SUPPORT ---\n\n  This source code is supplied as is.  The current copyright holder is not\n  required in any way to assist anyone who chooses to modify this code.\n\n  However, the current copyright holder can be reached via email:\n  Matt Williams\n  dbwilli@scsn.net\n  And via the World Wide Web:\n  http://www.scsn.net/users/dbwilli/\n\nII. INSTALLING AND STARTING THE PROGRAM\n\n  It is recommended that you install MegaZeux in its own sub-directory\n  on your hard disk. Do NOT run the program directly from a distribution\n  disk, as disks sometimes fail.  Always make a copy and run the program\n  from it!\n\n  To install the registered version of MegaZeux, simply change to the proper\n  drive (usually A: or B:) after inserting the installation disk. Type\n  INSTALL and select the program to install. A recommended directory is\n  MEGAZEUX. Then you must wait as the program is unarchived and copied to\n  the given directory.\n\n  You should always use INSTALL to install registered products from Software\n  Visions, as they are in archived form.\n\n  Once the program is installed, you can change to that sub-directory at\n  any time and run the program.\n\n  MegaZeux is started by typing\n\n\t\t  MEGAZEUX\n\n  You will then be prompted for an output device to use for all digitized\n  music and sound. If you do not pick PC Speaker, you will then be prompted\n  for whether you wish PC Speaker sound IN ADDITION to digitized music and\n  sound. Then, if you did not pick No Sound for digitized output, you will\n  be prompted to pick a mixing rate for digitized music. Lower numbers are\n  more staticy and may distort percussion sounds, but higher numbers require\n  more processor power. The high end numbers will slow down the game (OK on\n  a 386 or 486) or even stop it on a 286 or very slow 386. Experiment for\n  the best compromise.\n\n  You will then be whisked to the title screen for the first included game,\n  Caverns of Zeux. Press P to play, or L to load another world. Press F1\n  for comprehensive help AT ANY TIME. Help is context sensitive, and much\n  more comprehensive than this document.\n\nIII.  Overview of MegaZeux\n\n  Welcome to MegaZeux!\n\n  As you may already know, MegaZeux is a game system which\n  allows you to play almost limitless worlds in EGA graphics\n  and with beautiful digitized music and sound. MegaZeux comes\n  with worlds, and new worlds are being uploaded every day to\n  major on-line services such as America Online. But the most\n  fabulous feature of MegaZeux is the World Editor.\n\n  Using the World Editor, ANYONE can create the world of their\n  dreams. Make it as simple or complex, as easy or difficult,\n  as long or short as you please. And we aren't just talking\n  about worlds made up of petty, pre-programmed enemies and\n  objects. MegaZeux has it's own, easy to use PROGRAMMING\n  LANGUAGE called Robotic that allows you to create enemies,\n  objects, and worlds that do almost anything you desire.\n\n  For the new user, I recommend you play Caverns, the first\n  included game, to get the feel of MegaZeux. You may wish\n  to read the section entitled \"Controls\" to learn how\n  to play MegaZeux.\n\n  Once you have the feel for the game, however, feel free\n  to dive into the World Editor and get messy! You should\n  probably read the section entitled \"The World Editor\"\n  first.\n\nIV.  Controls\n\n  MegaZeux is very easy to play, once you understand the\n  simple control system. After that, experimentation will\n  prove to be the best teacher, although you can discover\n  everything within Help as well.\n\n  To start a game, press 'P' during the title screen. To\n  select another world, press 'L' during the title screen.\n  Use ESC during the title screen to exit to DOS, or during\n  the game to return to the title screen. You know it is\n  the title screen if a message on the bottom says \"Press\n  F1 for help, Enter for menu\". Enter will bring up a menu\n  of options on the title screen, also.\n\n  Your character is represented by a little smiley face\n  in the included game(s). In other games, he may be\n  a stick person, animal, spaceship, or other entity.\n  Usually it is obvious where you are, since you are usually\n  colored to stand out.\n\n  To move your character, press one of the arrow keys on\n  the keyboard or numeric keypad. If you hold it down, you\n  will continue to move in that direction.\n\n  To interact with objects and other characters, you usually\n  must stand next to them and touch them. This basically\n  means push against them using the arrow keys. Watch out\n  for enemies and evil creatures, who will often hurt you\n  when you touch them.\n\n  To stop enemies, you will usually have some type of weapon,\n  often with limited ammunition. The actual weapon depends\n  upon the game, and may even differ from game to game, but\n  by default you have a small pistol. To fire your weapon,\n  hold Space and press an arrow key. You can also hold the\n  arrow key and press Space. You must press both keys TOGETHER.\n\n  To see your current status, such as Health points, Coins,\n  Gems, Ammunition, and Keys, press Enter. This will bring up\n  a box containing all your stats, as well as a menu detailing\n  other options you have available.\n\n  In some games, you may encounter bombs. (as usual, push\n  against them to pick them up.) To use a bomb, press the\n  Delete key. Then run! The bomb will not actually burn its\n  fuse until you step away from it. There are two styles of\n  bomb- Low strength and High strength. You can tell which\n  kind you pick up by the pitch of the sound it makes. To\n  change which TYPE of bomb you are using, press Insert. A\n  message at the bottom of the screen will inform you of\n  your new selection.\n\n  Other games may have other controls, such as 'S' to cast\n  a spell, or 'J' to jump, but they will be detailed within\n  the game. Some basic play tips:\n\n  * Make sure you visit every screen. To get to other screens,\n\t enter passageways, stairs, caves, or doors. (by pushing\n\t against them.) You can also often reach another screen\n\t by pushing against the edge of the screen, leading to an\n\t adjacent screen.\n\n  * Touch everything! Even things of seemingly little value may\n\t prove worthy of your attention. If it kills you, then...\n\t well... hope you saved.\n\n  * Save your game! It is rather simple- Press F3 to save your\n\t game to disk at the EXACT point you are at. Press F4 to\n\t reload a game off of disk. You can use F9 and F10 to\n\t quick-save and quick-load, which work on the last game\n\t you saved. Remember to save often- If something kills you,\n\t you'll want a game to go back to.\n\n  * Collect supplies! Make sure you grab every coin, gem,\n\t ammo dump, bomb, chest, and pouch you see! You will often\n\t be in want of supplies, so don't push things.\n\n  * Remember how things work. Most things in MegaZeux have\n\t patterns, and the same object will usually do the same\n\t thing all the time. (although two objects may look alike\n\t and not really be the same thing)\n\nV.  Advanced Play Info, Tips\n\n  You've tried some MegaZeux worlds, and they are fun, but you\n  have the feeling you are missing something. This is it- the\n  remaining knowledge you might need to survive in a given world.\n\n  First, let me warn you that many worlds you get from public\n  services or BBSes may not be of a very good quality. They may\n  be unfair, boring, have bugs, and/or not give proper\n  instructions. My advice is to E-Mail the author and tell him\n  what you think of poor quality MegaZeux games. There is no\n  problem in MAKING them, but PLEASE don't upload them. Don't\n  let a few losers ruin the fun.\n\n  Now to cover the game keys, in detail-\n\n  F1- Help\n  Use this at any time to bring up help relating to the current\n  situation. Within the game, you will go to the Controls\n  section.\n\n  Enter- Menu/status\n  Use this to bring up a menu of options and a list of your\n  current stats, such as Score, Gems, etc.\n\n  ESC- Exit to title\n  This will quit the current game and return to it's title\n  screen.\n\n  F2- Settings\n  This will bring up a dialog box where you can change the\n  current game settings, such as Game Speed, Music, and\n  Sound. To speed up the game, lower the speed and/or turn\n  music off. A speed of 1 will cause flicker in most machines,\n  so it is not recommended unless it is your only option.\n\n  F3- Save game\n  This will prompt you for a filename, then save the exact\n  state of your current game. Some games may not allow saving\n  on some or all of its screens. This lets you quit your game\n  in the middle or take precautions against unknown dangers.\n\n  F4- Restore game\n  This will let you select a saved game from a list of filenames.\n  The game will then be reloaded from the same point you left off\n  at.\n\n  F5 or Insert- Toggle bomb type\n  This will switch your current bomb type between High Strength\n  and Low Strength.\n\n  F6- Debug mode\n  This will bring up a small box in the lower left corner of the\n  screen, detailing your position and the current memory\n  situation. It is generally only necessary when testing and\n  fixing your own games. Press F6 again to turn the box off. The\n  debug menu, when active, will also increase the sensitivity to\n  module and SAM problems. Normally during the game, no errors\n  relating to modules and SAMs will be shown. With the debug menu on,\n  errors loading modules and SAMs and out of memory conditions will\n  be shown.\n\n  F9- Quicksave\n  This will save the game, like F3, but to the last filename you\n  used, without prompting you for a filename. The old file will\n  be overwritten. If no game has been saved since MegaZeux was\n  started, a filename of SAVED.SAV will be used.\n\n  F10- Quickload\n  This will reload the last game saved using F3 or F9. If no\n  game has been saved since MegaZeux was started, a filename of\n  SAVED.SAV will be used.\n\n  Arrows- Move\n  The arrow keys will move your character and allow him to\n  interact with most objects.\n\n  Space- Shoot\n  Use this key in conjunction with an arrow key to fire your\n  weapon in the selected direction. Weapons vary from world to\n  world.\n\n  Delete- Bomb\n  This will drop a bomb of the current type BENEATH the player.\n  Move out of the way to see it, then run before it explodes!\n\n  The following keys are active at the title screen-\n\n  F1- Help (see above)\n\n  Enter- Menu\n  This is similar to Enter within the game. However, there is\n  no status screen.\n\n  ESC- Exit to DOS\n  Pressing ESC will exit MegaZeux, returning to the DOS prompt\n  or other operating system.\n\n  F2 or S- Settings (see above)\n\n  F3 or L- Load world\n  This will allow you to load up the title screen of any MegaZeux\n  world. A list of choices will be presented to you to select\n  from. After the world is loaded, you may watch the title\n  screen, then press 'P' to play.\n\n  F4 or R- Restore game (see above)\n\n  F5 or P- Play game\n  This will stop the title screen and actually begin game play.\n\n  F6- Debug menu (see above)\n\n  F8 or E- World editor\n  This will quit the gaming portion of MegaZeux and enter the\n  integrated World Editor. For detailed info on using the World\n  Editor, view the appropriate help sections.\n\n  F10- Quickload (see above)\n\n  We'll close this section with some more playing tips, some\n  general hints and others specific to certain sections of\n  Caverns of Zeux, the first included game.\n\n  * Don't take anything for granted. If it looks like a spike,\n\t it PROBABLY is. PROBABLY. Although it is rare that these\n\t types of puzzles must be solved to complete a game, there\n\t are often bonuses, hidden rooms, etc. behind illusions.\n\n  * Don't trust anyone. All characters in a game, (including\n\t yourself) have the potential to lie, cheat, and backstab.\n\n  * Try things twice. Sometimes objects respond differently a\n\t second time. If you get a new treasure, go talk to all\n\t the citizens- maybe one of them will say something new!\n\n  * Caverns hint- (DON'T READ UNLESS YOU ARE STUCK!) Many people\n\t often get stuck early in the game, after a couple of bosses.\n\t If you have the three amulets, did you talk to the dwarf in\n\t town who wants them? Make sure you change back to a dwarf\n\t by talking to the spirit. Later make sure to lavawalk across\n\t the lava on the left of the room where you got the three\n\t amulets.\n\nVI.  The World Editor\n\n  Ready to start creating your own worlds? Then let's get\n  started! This section is a short editor tutorial, it will teach\n  you the basics of creating your own worlds.\n\n  To get into the editor, press E from the title screen. You will\n  be presented with a blank board with a small menu at the bottom.\n  You can use PgUp and PgDn to change the currently shown menu,\n  and you will be presented with various keys and options. The\n  mouse also works here. Feel free to fool around with these\n  various options. Press Alt+R to restart and clear everything.\n\n  For your first world, you should start simple. The screen you\n  begin on is your title screen, so pick a simple name for your\n  game, such as \"Mike's World\". Move the flashing cursor down a\n  few lines and over a few spaces, using the arrow keys. Press C\n  and use the arrow keys to select an appropriate color. Press\n  Enter to select this color. Now press F2 to write Text. Type\n  in the name of your world, and then press F2 to stop writing\n  text. This screen will be your title screen.\n\n  Now you need to create the first board, or location, of your\n  game. Press A to add a board, then type in a short description\n  of the board, such as \"Starting Board\". Press Enter to go to\n  this new board.\n\n  Now you are free to doodle around. Use C to change the active\n  color. Use F3 through F9 to bring up menus of items, terrains,\n  and creatures. Selecting one with the arrows and Enter will\n  allow you to place it using arrow keys and space.\n\n  For example, press F3 for terrains, and select Line. (This is\n  a form of wall.) Now move around, placing walls. To ease this,\n  you can press Tab to toggle draw mode. When draw mode is active,\n  every move of the cursor will place the current item/color. The\n  current item and color is shown on the top line of the menu.\n\n  Try to create a pleasing-looking screen, with walls, items, and\n  creatures. Some items will require that you set settings to\n  determine their behavior. To place the player's starting\n  position, move the cursor to the destination, press F10, and\n  select Player.\n\n  When you are done, press G to go to the Global Info screen. TAB\n  to the Next button and press Enter. You will now be highlighting\n  the option \"Starting Board\". Press Enter, and select the\n  starting board (NOT the title screen) from the list and press\n  Enter. Then TAB to OK and press Enter. This tells MegaZeux which\n  board you want the game to start on.\n\n  You could now press Alt+N to select a module (music) file for the\n  board, if you wished. Then press S to save the world, and type in a\n  filename of eight or less characters. (The extension of .MZX\n  will automatically be added.) Press Enter twice to save. Press\n  ESC to exit the editor, and now you can play your game! You can\n  use L to reload your world in the editor to make changes, if\n  necessary. See General Editing Tips for more advanced editing\n  info.\n\nVII.  General Editing Tips\n\n  The following is a list of important editing tips. They assume\n  you are familiar with MegaZeux's dialog box system, and that\n  you can navigate the editor's menus.\n\n\t  Linking Boards\n\n  One-board games get boring REAL fast. There are two ways to\n  connect multiple boards. The first way is to add stairs, doors,\n  and whirlpools using the Transport (F7) menu. Then you select a\n  destination board. The destination board should contain a\n  similar transport, leading back. The two entrances will now lead\n  to each other.\n\n  The other method of connecting boards is with X, the Board Exits\n  dialog. Here, you can select boards that you will reach if you\n  walk off of the screen in a given direction. The destination\n  board shouldn't have anything in the way, and will not\n  automatically lead back- You must set the exit on that board\n  too.\n\n  To add boards, press A, or select (new board) from a board list.\n  To switch to other boards, use B.\n\n\t  Board Sizes\n\n  You can change the maximum size, and the view position, of a\n  board, with Alt+P. Here you can move and re-size the view, or\n  center it on-screen.\n\n  You can also change the actual size of the board, as well as\n  the maximum, overall size. The lowermost radio control\n  determines the MAXIMUM size possible for the board, one of\n  60x166, 80x125, 100x100, 200x50, or 400x25. The Virtual size\n  is the ACTUAL size of the board, which is always equal to or\n  less than the MAXIMUM size. Example- If you wanted a board\n  that was 180 wide and 40 high, you would select a MAXIMUM\n  size of 200x50, then set the Virtual size to 180x40.\n\n  Note that reducing the size of a board will permanently\n  destroy anything outside the new limits.\n\n\t  Other Important Editing Keys\n\n  You can use Insert to \"grab\" the object beneath the cursor, or\n  Enter to edit it and then grab it as well. Use P to modify the\n  current object's settings. Use Alt+N to select music for the\n  current board, or turn the music off if it is already selected.\n  Use Alt+Z to clear the current board entirely. You can edit\n  important Board Options with I, and important Global (world)\n  Options with G.\n\nVIII.  Robots- What They Are and How to Use Them\n\n  Robots are the heart and soul of MegaZeux. Anything you could\n  want to do in MegaZeux, anything you see done in a MegaZeux\n  game, many things you didn't know were possible, can be done\n  with MegaZeux's Robots.\n\n  Robots are programmed in their own programming language, called\n  Robotic. Robotic is a fairly simple language to learn, somewhere\n  along the lines of BASIC, although a bit more complex. If you've\n  ever used Epic Megagame's ZZT, Robotic is a bit more complex\n  than ZZT-OOP, although it is similar and more logical in style\n  and syntax.\n\n  To place a Robot in the editor, press F10 and select Robot, or\n  Pushable Robot if you want things to be able to push the Robot.\n  Then name the Robot, and select a character to represent it. Now\n  you are brought to the Robot editor, where you can program the\n  Robot in Robotic.\n\n  Complete info and a tutorial on Robotic is included in the online\n  Help.\n\nIX.  FREQUENTLY ASKED QUESTIONS\n\n  The following is a list of questions that I have received\n  through mail or E-mail about MegaZeux repetitively.\n\n  Q: My robot can't change it's/the player's characters! I\n\t  use CHAR \"A\" but it turns invisible!\n\n  A: Use CHAR 'A' instead. You MUST use single quotes\n\t  (apostrophes) or it will use the value of the COUNTER\n\t  A which is probably 0.\n\n  Q: Can I distribute a world I made with MegaZeux? Is it\n\t  legal? Must I/Can I include MegaZeux with it? Can they\n\t  be shareware worlds?\n\n  A: Of course you can distribute your worlds! That is what\n\t  MegaZeux is for! Please refrain from uploading worlds\n\t  that you made as a novice user, if they contain only\n\t  built-in enemies, items, and no plot/etc. People will\n\t  only yell at you. Get creative and use Robots! Please\n\t  include any SAM, MOD, and CHR files with the games,\n\t  as well as a short TXT description file if necessary.\n\t  Don't include MegaZeux, but make sure MegaZeux itself\n\t  is available on any BBS or online system that you\n\t  upload worlds to. (MegaZeux is available on AOL) Your\n\t  worlds can be freeware, public domain, or shareware,\n\t  whichever you prefer.\n\n  Q: Can I use the music from Caverns in my games? How about\n\t  the music from the registered games? How about the SAM\n\t  files?\n\n  A: I would prefer it if you did not use Caverns music in\n\t  any games you distribute, because most people have\n\t  heard it and it is getting old. The music from the\n\t  registered games is illegal to distribute. The SAM\n\t  files are public domain and may be used as you please.\n\n  Q: Where can I get module files?\n\n  A: If you can't make them, search public BBSes and online\n\t  services for public domain ones. AOL has a large MOD/S3M\n\t  library, as do many CD-ROM collections. Or employ a\n\t  friend to do it for you. I myself (Alexis Janson) am\n\t  not available to create module files unless I am being\n\t  paid well for my time.\n\n  Q: What is some good software to create module files?\n\n  A: For modules, Fast Tracker II by Triton is fairly good, but\n\t  you have to avoid using the XM features. ScreamTracker 3.0\n\t  is good for making S3M files.\n\n  Q: How do I make SAM files?\n\n  A: There are three easy ways to create SAM files. First,\n\t  you get recording software, a sound card, and a mike.\n\t  Hook 'em all up (read the instructions) and go for it.\n\t  Second, you can download them or get them from module\n\t  files or other games. Third, you can take existing files\n\t  and change them with effects like echo. Blaster Master\n\t  is a good piece of software to handle numbers one and\n\t  three.\n\n  Q: But I can only find software to make WAVs! (or VOCs)\n\n  A: There is a nice small program called CVT2SAM which can\n\t  convert most WAVs and VOCs to SAMs. I believe it is in\n\t  the public domain. It is available on AOL.\n\n  Q: The TELEPORT PLAYER command doesn't work!\n\n  A: It does now. That was a small (er... big) bug in version\n\t  1.02, which was corrected in version 1.03 and 2.51.\n\n  Q: Is the world design contest still going?\n\n  A: Until you see a Best of MegaZeux series OR the calendar\n\t  reaches 1-22-97 without sufficient entries, the contest\n\t  will continue. So far I've received only one board,\n\t  which wasn't even acceptable. However, I've received\n\t  E-mail from several interested parties, so the contest\n\t  continues. See CONTEST.TXT for details.\n\n  Q: I need more than 256 characters!\n\n  A: Then you'll need to carefully utilize the Robot command\n\t  LOAD CHAR SET and ration your characters carefully. The\n\t  limit of 256 is a hardware limit and cannot easily be\n\t  worked around.\n\nX.  Command Line Options\n\n  The following command line options are available for use when\n  starting MegaZeux. To use them, type them in after MEGAZEUX\n  at the DOS prompt. Separate all options with spaces.\n\n  -?\n\n  Bring up command line option help screen.\n\n  -lxxxxxxxx.MZX\n\n  Loads up xxxxxxxx.MZX at start up. (the .MZX is optional)\n\n  -nomouse\n\n  Don't activate the mouse, even if one is present. Use if the\n  mouse gets in the way or MegaZeux seems to think you have a\n  mouse when you don't.\n\n  -noems\n\n  Don't use any EMS memory. Use if MegaZeux's use of EMS memory\n  is causing a problem. Note that with this option, you will\n  probably not have enough memory for module and SAM files.\n\n  -ega\n\n  Use EGA mode even on VGA or higher. This only affects the\n  granularity of palette fades and color changes.\n\n  -keyb2\n\n  Use another style of keyboard handler. Use if your keyboard is\n  locking up or otherwise causing problems.\n\n  ** Sound Card Options\n\n  -port###\n  -irq#\n  -dma#\n\n  Allows you to specify the settings for your sound card. -port\n  sets the base port and must be followed by a hexadecimal number\n  such as 220. -irq sets the IRQ line number, and should be\n  followed by a single digit such as 7. -dma sets the DMA number\n  and should be followed by a single digit such as 1. See your\n  sound card manual for details. These options are only neccesary\n  if autodetection fails. An example of a command line for the\n  default SoundBlaster settings-\n\n  MEGAZEUX -port220 -irq7 -dma1\n\nXI.  EMS Memory/Boot Disk Tips\n\n  MegaZeux makes extensive use of EMS and conventional (first\n  640k) memory. The more you have available, the better it will\n  run.\n\n  To have EMS memory available, you must have an EMS driver\n  installed. Most EMS drivers also require an XMS/HIMEM driver\n  loaded first. You will need lines similar to the following in\n  your CONFIG.SYS to load HIMEM and an EMS driver, respectively-\n\n  DEVICE=C:\\DOS\\HIMEM.SYS\n  DEVICEHIGH=C:\\DOS\\EMM386.EXE 3072 H=160\n\n  The 3072 tells DOS to reserve up to 3072K of memory (3\n  megabytes) for EMS. Feel free to lower or raise this value,\n  but a minimum of 2048K (2 megabytes) is recommended.\n\n  It is also recommended that you load SmartDrive to run MegaZeux.\n  SmartDrive is a disk-cache utility that is usually loaded,\n  because it greatly speeds up repeat disk accesses. You probably\n  already have a line such as SMARTDRV.EXE /X in your AUTOEXEC.BAT;\n  if not, you should consider adding it somewhere after the PATH\n  statement.\n\n  To help free up conventional memory, you may need to create a\n  boot disk. This is a common procedure for games that use less\n  conventional setups (such as MegaZeux), and if a certain boot disk\n  works for one game, it may work just fine for MegaZeux. However,\n  you must remember to include HIMEM and an EMS driver, and include\n  a mouse driver if you want your mouse to be available.\n\n  To create a boot disk, stick a blank disk into your A: drive and\n  type the following:\n\n  FORMAT A: /S <enter>\n\n  Answer Yes to all questions. Give the disk a label if you wish.\n  Answer No to \"Format another?\" and then type:\n\n  COPY CON A:CONFIG.SYS <enter>\n  DEVICE=C:\\DOS\\HIMEM.SYS <enter>\n  DEVICEHIGH=C:\\DOS\\EMM386.EXE 4096 H=160 <enter>\n  DOS=HIGH <enter>\n  <ctrl+z>\n\n  COPY CON A:AUTOEXEC.BAT <enter>\n  PATH C:\\DOS;C:\\WINDOWS <enter>\n  SMARTDRV /X <enter>\n  C:\\MOUSE\\MOUSE <enter>\n  C: <enter>\n  CD \\MEGAZEUX <enter>\n  MEGAZEUX <enter>\n  <ctrl+z>\n\n  The line to load the mouse driver is optional, and may vary on\n  different computers. The drive and directory you have MegaZeux\n  in may differ as well. Your windows and dos paths may differ.\n\n  Once you've followed these instructions, you can insert the\n  disk and reboot the computer to run MegaZeux. It is best if you\n  get a computer-wise friend to help you, if you can't get the\n  above to work.\n\n  One note- most newer sound cards require drivers or other\n  programs to be run before they can be used. Check your existing\n  configuration files and/or manuals to find out what these\n  commands are if you intend to use MegaZeux with a boot disk.\n\nXII.  Known Bugs and Quirks\n\n  Note that the graphics quirks are more likely to appear on laptops\n  as their graphics cards are usually proprietary and may contain\n  non-standard quirks.\n\n  A. A couple of computers/graphics cards display MegaZeux in the\n\t  \"9-pixel-character\" mode. This basically means that thin black\n\t  lines appear between each column of most characters. This doesn't\n\t  affect gameplay but can be annoying. Known offenders- Zenith\n\t  Z-Note MX laptop.\n\n  B. A couple computers/graphics cards may display characters with the\n\t  bottom of every character stretched down a little (2 additional\n\t  rows of pixels added). This does not affect gameplay but can be\n\t  annoying. Known offenders- Sharp 8700 Laptop\n\n  C. Certain SVGA cards display black as deep red. Again, this doesn't\n\t  affect gameplay but can be annoying.\n\n  D. The Global Robot's code has been reported to be trashed while\n\t  music is on. (in the editor.) It happened very rarely and I cannot\n\t  seem to reproduce or pinpoint the bug. It may be an isolated\n\t  cause.\n\n  E. Certain modules may play slightly off-tune.\n\n  F. MegaZeux does not always run under certain Windows or Win95\n\t  configurations, or if it does, sound does not work.\n\n  If you have something to add here or might be able to help solve one\n  of these quirks, drop me a line.\n\nXIII.  THANKS, ACKNOWLEDGEMENTS, AND MISCELLANEOUS\n\n  ** Credits and Acknowledgments **\n\n  Programming and Overall Design by Alexis Janson\n  Music/Sound code by Edward Schlunder\n\n  Included Game Modules by Alexis Janson\n  Included Music by Alexis Janson\n\n  Beta Testing and Special thanks:\n\n  Jason Albanese\n  Alek Benedict\n  Chris Bromley\n  Christopher Christensen *\n  Chris Cooper\n  Carlos DaSilva\n  Jacob Farmer\n  Geoff Friesen *\n  Tim Gallagher\n  Themie Gouthas *\n  Jamie Holub\n  Jason Kim\n  Dave Kirsch *\n  Elbert Lim\n  Joe McManis\n  Vic Putz *\n  Robin Rudge *\n  Brian Schweitzer\n  Allen Shirvanian\n  Tim Sweeny (author of ZZT)\n  Tony Vanvranken\n\n  * = Thanks for the public domain code!\n\n  AOL Beta Testers\n\n  Compukid\n  Shammydog\n  DMcgee1008\n  SpectreB1\n  NL Aric\n  JustJoshua\n  SkreenNme\n  MegaKev\n  Herbie III\n  Spider124\n \n\t\t\t\t- Originally by Alexis Janson\n\t\t\t\t  ZZT'er at heart\n\n\t\t\t\t- Modified by Matt Williams\n\t\t\t\t  also a ZZT'er at heart\n\t\t\t\t  on March 1, 1998\n"
  },
  {
    "path": "docs/old/mzm.txt",
    "content": "MZM file format: MZX image\n\n//////////////////////////////////////////////////////////////////////////\nNOTE: These formats have been superceded by MZM3. Please use MZM3 instead.\n//////////////////////////////////////////////////////////////////////////\n\nThis file format is a simple format that describes graphically a \n rectangular area.\n \nb = byte (8bit)\nw = word (16bit)\nd = dword (32bit)\nsN = string of N size (ie N bytes)\nbN = N bytes\nwN = N words\ndN = N dwords\n\nheader:\n\npos  size  description\n0     s4    \"MZMX\" tag\n4     b     width\n5     b     height\n6\n.           reserved\nf           \n\ndata:\n\nThe data is composed of w * h (width * height) blocks, where each\n block is 6 bytes and contains the following:\n\npos  size  description\n0     b     ID (5 for overlay)\n1     b     param (char for overlay)\n2     b     color\n3     b     under ID (0 for overlay)\n4     b     under param (0 for overlay)\n5     b     under color (0 for overlay)\n\n\n\nMZM2 file format:\n\nThis file format is a simple format that describes graphically a \n rectangular area.\n \nb = byte (8bit)\nw = word (16bit)\nd = dword (32bit)\nsN = string of N size (ie N bytes)\n\nheader:\n\npos  size  description\n0     s4    \"MZM2\" tag\n4     w     width\n6     w     height\n8\t\t\td\t\t\tlocation in file of robot table (0 for no table)\nc\t\t\tb\t\t\tnumber of robots\nd\t\t\tb\t\t\tstorage mode\ne\t\t\tb\t\t\trobot storage mode\nf           reserved           \n\ndata:\n\nWidth * height cells are then stored.\n\nIn storage mode 0 the cells consist of the following 6 bytes:\n\npos  size  description\n0     b     ID\n1     b     param\n2     b     color\n3     b     under ID\n4     b     under param\n5     b     under color\n\nAll robots are stored with param 0.\n\nIn storage mode 1, the cells consist of the following 2 bytes:\n\npos  size  description\n0     b\t\t  character\n1\t\t\tb\t\t  color\t\n\nThen, an optional robot table may be stored. The table consists of n\nrobots listed in the same order that their representative ID's appear\nin the cell table.\n\nThe robots comply to the MZX 2 world format in mode 0 and MZX 2.80\nsave format in mode 1. Reserved fields are in place to retain compatability\nwith the MZX 2 world format (and thus can be loaded by an MZX 2 compatabile\nloader)\n\nIf the robot storage mode is 0, the robot data is as follows:\n\npos  size  description\n 0    w     program length\n 2          reserved\n 4\t\ts15\t\trobot name\n 13   b     robot character\n .\n .\t\t\t\t\treserved\n 29   \n . \t\t\t\t  robot program, stored in MZX2 Robotic bytecode format\n ??\n\n\nIf the robot storage mode is 1, the robot data is as follows:\n\npos  size  description\n 0    w     program length\n 2          reserved\n 4\t\ts15\t\trobot name\n 13   b     robot character\n 14\t\tw\t\t  current line\n 16\t\tb\t\t  current position in line\n 17\t\tb\t\t  current cycle\n 18\t\tb\t\t\tcycle total\n 19\t\tb\t\t\tbullet type\n 1A\t\tb\t\t  locked status\n 1B\t\tb\t\t  lavawalk status\n 1C\t\tb\t\t\twalking direction\n 1D\t\tb\t\t  last touched direction\n 1E\t\tb\t\t  last shot direction\n 1F\t\t\t\t\t \n .\t\t\t\t  reserved\n .\n 23\t\tb\t\t  status\n 24\t\t\t\t\t \n .\t\t\t\t  reserved\n .\n 27\t\t\t\t  loop counter\n 29   d32\t\tlocal counters\n 49   d\t\t\tstack size\n 4D\t\td\t\t\tstack pointer\n 51\t\t\n .\t\t\t\t\tstack (stack size bytes)\n ??\n . \t\t\t\t  robot program, stored in MZX2 Robotic bytecode format\n ??\n\n\n\n"
  },
  {
    "path": "docs/old/port.txt",
    "content": "MegaZeux 2.80 public BETA\r\n\r\n\r\n-- Introduction --\r\n\r\nMegaZeux 2.80 is a hybrid port/rewrite of MegaZeux 2.70 for DOS (actually,\r\ndevelopment for 2.80 began a bit prior to 2.70's release, but it's 100%\r\nfeature compatible). It is not a complete rewrite, in that the existing\r\nMZX source was used as a base for its development. Nonetheless, quite a lot\r\nof the code has been rewritten, and what hasn't been has been reformatted\r\nand carefully examined. As much as 75% or more of the current source is new\r\ncode or code that has been heavily modified from the original. For this\r\nreason, it is not adequete to simply refer to it as a port, because a\r\ntypical port will only have the bare minimum changes necessary to make the\r\nprogram run seamlessly on other platforms (or sometimes, just one other\r\nplatform, such as MZX32). This does not fully realize this project's short\r\nand long-term goals, which include:\r\n\r\n- First and foremost, as stated, allowing MZX in its current state to run\r\n  seamlessly on other platforms with no handicaps (for instance, missing\r\n  robot editor)\r\n- Removing unnatural limitations within MZX itself.\r\n- Providing a relatively sane and efficient implementation of MZX's\r\n  functionality.\r\n- Providing source code which is decently readable, maintainable, and\r\n  can easily be modified and expanded upon.\r\n- As much portability as possible, placing portability over other ideals.\r\n\r\nIn case anyone is actually interested, here is a more technical overview\r\nof what has actually been done so far on this project:\r\n\r\n- All ASM files have been rewritten to ANSI C compliant C++; this mainly\r\n  involves the mammoth game2.asm.\r\n- Everything graphics related has been replaced with an SDL driven engine,\r\n  including text mode rendering (also capable of rendering SMZX modes)\r\n- All keyboard and mouse events are now also handled by an SDL driven\r\n  engine. All internal keyboard/mouse routines use SDL keycodes; no\r\n  attempt was made to convert to MZX's old format at this level.\r\n- All world/board/robot loading and saving code has been entirely\r\n  rewritten and is more modular now.\r\n- All robot handling code has been rewritten from scratch, and is more\r\n  sane/efficient now (allocation, message sending, sensors, etc.)\r\n- All counter handling code has also been rewritten from scratch. This\r\n  includes the functionality of builtin and \"function\" counters. It also\r\n  includes a new string framework.\r\n- The robot interpreter has been almost entirely rewritten.\r\n- The main game playing code (game.cpp) has been almost entirely rewritten.\r\n- All module/sample playing code has been rewritten to use modplug and\r\n  SDL. PC speaker effects are emulated.\r\n- The main part of the editor (edit.cpp) has been almost entirely rewritten.\r\n- The robot editor has been entirely rewritten, utilizing a modifed (fixed)\r\n  version of RASM (released by me over a year ago)\r\n- All other source files (for instance, window handling code, scroll\r\n  displaying, char editor, etc.) have been largely overhauled, some to more\r\n  of a degree than others. At the very least, everything has been\r\n  reformatted and made to work with the new systems.\r\n\r\nNow, for a disclaimer. This release is very much in beta status. What that\r\nmeans is that it will almost definitely have several bugs. I have every\r\nintention of fixing such bugs as soon as I can, and if I don't, someone\r\nelse definitely will. The port has been tested internally by private beta\r\ntesters for some time now, and I suspect that there is at least a 90%\r\ncompatability rate. Nonetheless, MZX has such a diverse number of\r\napplications and worlds have so many variables that it's impossible to\r\ntest exhaustively, especially with the features added after version 2.51s3.\r\nSee more about bugs/reporting procedure further down.\r\n\r\n\r\n-- Platforms --\r\n\r\nSee platforms_matrix.html for a list of supported platforms and the options\r\navailable to each respective platform.\r\n\r\n\r\n-- Minimum Requirements --\r\n\r\nI really can't give any exact numbers here, because for one thing it depends\r\nentirely on the game being ran. Chances are the requirements will only be\r\nslightly higher than it would be for a machine that can run the DOS version\r\ncorrectly, and sometimes may even be slightly lower. As compared to the\r\noriginal, the port has to manually draw the screen pixel by pixel without\r\nacceleration. However, the renderer is optimized and quite fast.\r\n\r\nFor the record, I believe the lowest specs machine that has ran the port so\r\nfar is a 266MHz mobile Pentium 2 with 32-64MB of RAM. Chances are it can\r\nrun fine on something even slower than this. Right now, I don't know, but\r\nin the future this section will likely have more information. If you have\r\nany specific success or failure stories on older hardware, let me know.\r\n\r\n\r\n-- Gameplay --\r\n\r\nGameplay should be more or less the same as it always was. Some keys\r\nare changed though:\r\n\r\n- Use ctrl + alt + enter to toggle between fullscreen and windowed mode\r\n  (read the disclaimer on this further down!)\r\n- Use F12 to dump the screen to a BMP; this can be done at ANY time.\r\n\r\nThe default values (speed, volumes, etc) are the same as they were before,\r\nbut if you change them in MZX they won't stay that way the next time you\r\nload. To change them permanently, you have to change the config file\r\n(see below).\r\n\r\nPassword protected worlds are not supported natively. However, if you try\r\nto load one, you will be presented with an option to permanently decrypt\r\nthe world and then load it. With that said, password protection is now\r\n100% removed from MZX, and no trace of encryption code exists outside of\r\nthe decryption module.\r\n\r\nThere are a couple discrepencies that break compatability that should be\r\nnoted:\r\n\r\n- key_pressed and key_code now return unsigned chars instead of signed,\r\n  so if the game checks for negative values, it won't work.\r\n- The same goes for reading back characters for strings.\r\n- Some things that worked in the DOS version only worked due to chance,\r\n  such as there being default (but valid) values for sprite widths or\r\n  heights and thus sprites could be displayed before new values were\r\n  set.\r\n\r\nThese shouldn't apply to very many games (I know of one.. in the recent\r\nDoZ. The game was already really broken anyway :p)\r\n\r\n\r\n-- Limitations --\r\n\r\nThe MZX port removes many limitations of the original. However, some\r\nstill remain due to the world format which has not been changed. These\r\nare the changes:\r\n\r\n- Boards may now be as large as 32767x32767. Please do not make boards\r\n  this large. :)\r\n- You may now have up to 250 boards.\r\n- Boards may have up to 255 robots, 255 scrolls, and 255 sensors.\r\n- There is no robot memory per board limit. However, individual robots\r\n  may only be up to 64k large. If you include the global robot, this\r\n  means that you effectively can have up to 16MB of robot memory per\r\n  board.\r\n- The same applies to scrolls, which have separate memory spaces and\r\n  may only be up to 64k in length each (so you could have almost\r\n  16MB of scroll text per board, but who would want that??)\r\n- The robot stack need not be initialized, and will be dynamically\r\n  resized as necessary (useful for recursion). This refers to lines\r\n  such as\r\n  . \"#*-1-2-3-4-5-6-7-8\"\r\n  at the beginning of robots. They're no longer necessary or useful\r\n  (they will be ignored, like any other comment).\r\n- You may have a virtually unlimited number of counters active. The\r\n  theoretical limit is some 4 billion. The effective limit depends on\r\n  memory available.\r\n- The same goes for strings, which may now have any name, so long as\r\n  it begins with a dollar sign ($). The same conventions regarding\r\n  strings in 2.68+ still apply (see 268_info.txt for more information)\r\n- Local2 and local3 no longer have dangerous side-effects. Local4\r\n  through local32 are also available per-robot now.\r\n\r\nA warning: I have tried my best to make it impossible to exceed these\r\nlimits, however, you may be able to somehow. If you do, don't save the\r\ngame. You will probably corrupt it. Just exit.\r\n\r\n\r\n-- Video --\r\n\r\nThe port by default runs in a window that is 640x350. It can run fullscreen,\r\nbut chances are you won't get 640x350. It's very likely that you'll get\r\n640x400, which only has minor black bars at the top and bottom (I find it to\r\nbe entirely satisfactory). If you're not so lucky, you might be limited to\r\n640x480. You can manually specify a resolution in the config file. In fact,\r\nbefore going into fullscreen, I recommend forcing 640x480 then 640x400. So\r\nfar we've experienced temporary monitor problems when trying to go into\r\n640x350 fullscreen for one user - it may be a good idea to force 640x400's\r\nrefresh rate using a tool like hztool (win9x) or NVRefresh (win2k+). You\r\ncan also set these with PowerStrip, as well as experiment with custom\r\nrefresh rates. It is worth trying to add 640x350 and 640x700 in this\r\nutility.\r\n\r\nIf you don't get 640x400, there might be a remedy to this.. but as of now\r\nI don't know any. I believe that most video cards can handle it, though.\r\nIn the cases where you can't, it's probably a driver/OS/configuration issue.\r\n\r\nIt is also possible to set a multiplier for the height, for the image to be\r\nstretched. Be sure that the multiplication of 350 is within the resolution\r\nchosen or MZX will crash! On my machine, I can run MZX at 640x700 with a 2x\r\nheight multiplier and it looks great.\r\n\r\n\r\n-- Audio --\r\n\r\nWith this version, many more module formats are playable than compared to\r\nthe previous version. The following are natively supported:\r\n\r\nXM, S3M, MOD, MED, MTM, STM, IT, 669, ULT, WAV, DSm, FAR, AMS, MDL, OKT,\r\nDMF, PTM, DBM, AMF, MT2, PSM, J2B, and UMX.\r\n\r\nYes, WAVs can be loaded as mods, and although this is not generally\r\nrecommended it could be useful sometimes (such as for short musical clips).\r\n\r\nGDM is not supported natively. However, if a GDM file is attempted to be\r\nloaded, an attempt will be made to load an alternate .S3M version, and if\r\nit does not exist, one will be created by converting the GDM file.\r\n\r\nThe same goes for SAM; they will be converted to WAV. Therefore, you can\r\nnow use WAV files natively as samples.\r\n\r\nIt is strongly recommended that you do not include any GDM or SAM files in\r\ndistributions of new MZX games; instead include only their conversions.\r\nYou cannot load GDM files with alt + N in the editor at all anymore.\r\nHowever, you can convert SAM files by testing them with alt + L, and you\r\ncan convert GDM files by loading them with a robot. Or, you can use an\r\nexternal convertor.\r\n\r\nAudio is always sampled to 16bit, 44KHz. If your computer cannot support\r\nthis, then I believe SDL will emulate it, although I'm not 100% certain\r\n(this remains to be seen). If this is the case, you're in dire need of a\r\nnew soundcard anyway.\r\n\r\nThe size of the audio buffer can be changed in the config file. See the\r\ncomments therein to determine the affect of this.\r\n\r\n\r\n-- Editor --\r\n\r\nAlthough quite a lot of the editor has been rewritten from scratch, a\r\nsincere attempt has been made to make it as compatible and similar to the\r\noriginal as possible. These are the differences:\r\n\r\n- You cannot click on help options to make it happen (for instance, you\r\n  can't click on L:Load and expect it to bring up the load window).\r\n- You cannot import or export ANS files. Use MZM instead.\r\n- You can now perform repeated copies by selecting \"Copy (repeated)\"\r\n  from the block command window. This will cause you to repeat the copy\r\n  indefinitely until you press escape. Also, after the first paste is\r\n  made, you may press ctrl + direction to move in steps that are the\r\n  size of the copy (you really have to try it to get a good idea of what\r\n  I mean)\r\n- Press alt + H to hide/unhide the menu/information occupying the bottom\r\n  6 lines of the screen.\r\n- Right click is similar to pressing insert (grabs whatever's under the\r\n  cursor)\r\n- Exporting char sets now gives direct options to set the size/offset\r\n  for partial charsets. The old format (prefixing the name) will not\r\n  work.\r\n- For saving files, you may make filenames larger and use mixed case\r\n  (as opposed to all caps).\r\n- Now when you import worlds their exits will actually work.\r\n- Loading worlds results in all empty boards and empty\r\n  robots/scrolls/sensors within the boards being removed. When you delete\r\n  a board, you won't be presented with a gap in the board list anymore.\r\n- You may once again select ASCII as a default charset and revert to it\r\n  in the char editor. You may also select a default SMZX charset.\r\n- Anywhere you can enter a line of text you can now press ctrl + left\r\n  and ctrl + right to go to the previous/next word.\r\n- This isn't totally limited to the editor, but now everywhere a\r\n  selection box shows up (such as for files, boards, items) you can\r\n  press multiple keys to seek to a specific entry. Furthermore, in\r\n  file selection boxes you can press backspace to go up a directory.\r\n\r\n- The SMZX char editor has been changed quite a bit. First, to use, you\r\n  must be in SMZX modes 1-3 (toggle using F11).\r\n  Within the editor, press keys 1 through 4 to change the \"current color.\"\r\n  You can place this color with space, and you can toggle repeated\r\n  placement on/off using tab (sorry, no clear/toggle in this mode, they\r\n  got in the way far more than they helped).\r\n  You now move in increments of 1 char naturally instead of \"half chars.\"\r\n  Right click grabs the current \"color\" underneath the cursor.\r\n\r\n- The robotic editor has been rewritten from scratch and is therefore\r\n  quite different. Some options do not appear in the help at the bottom\r\n  (not enough room), so refer here for information:\r\n  \r\n  You are now able to type invalid code within a robotic line. You will\r\n  see that it's invalid due to the way the line is color coded. If you\r\n  try to escape while any lines are invalid or if you press alt + V, a \r\n  window will be brought up describing the nature of all erroneous lines.\r\n  It will also let you mark each line to be deleted or commented out on\r\n  exit. If all invalid lines are marked with anything besides ignore,\r\n  you can exit safely. You may also mark invalid lines using ctrl + I, D,\r\n  and C (to mark as ignore, delete, and comment respectively).\r\n  \r\n  ctrl + C may also be used to comment out a normal line, and then\r\n  uncomment.\r\n\r\n  alt + O is still available, but you may only change macros here (see\r\n  the config file for the other rarely used options). This does give\r\n  you more room to modify macros, though. They may be up to 64 characters\r\n  in length, and you may set up to around 45 or so in this window. (To\r\n  get larger, you'll have to use the config file.)\r\n\r\n  There are some slight discrepencies in robot size; if you highlight the\r\n  last line then leave it it will register as a compilation of that line.\r\n  Yet, when you exit, the line will be discarded, so this addition is not\r\n  permanent. This shouldn't be problematic.\r\n\r\n  alt + enter and alt + escape no longer work; use the alternatives\r\n  (alt + b and alt + u)\r\n\r\n\r\n-- Config File --\r\n\r\nThe config file is named config.txt and is a plain-text file that is\r\nintended to be edited in a text editor. All information about it and\r\noptions are described in the file itself. It's a good idea to keep a\r\nbackup of it in case you want to be made aware of the defaults again,\r\nbut if you simply delete the file these defaults will be used.\r\n\r\nYou may also set config file options at commandline. Note that you\r\nmay not use whitespace at all here (if you do, different words will\r\nbe counted as separate options). For instance, you may run from\r\ncommand line:\r\n\r\nmzx280 startup_file=test.mzx mzx_speed=7\r\n\r\nTo start MZX with test.mzx at speed 7.\r\n\r\nIt's probably worth the time of any new user to check out the config\r\nfile options.\r\n\r\n\r\n-- Misc --\r\n\r\nThe default char sets for MZX, SMZX, ASCII, and even blank are stored\r\nas actual charsets in the same directory as MZX (mzx_default.chr,\r\nmzx_smzx.chr, mzx_ascii.chr, and mzx_blank.chr respectively). They\r\nare not read-only by default, but you may want to make them so. Don't\r\nchange them unless you know what you're doing.\r\n\r\nSee build.txt for information about building from source.\r\n\r\n\r\n-- Known Bugs --\r\n\r\nAs of this release I don't KNOW of any bugs, and in general I don't\r\nmake a habit of releasing something when I know of specific bugs. Since\r\nI wrote much of the code and have a good understanding of virtually all\r\nof it, and because it's much easier to debug outside of DOS, chances are\r\nI should be able to fix any bugs that crop up.\r\n\r\n\r\n-- Bug Reporting Procedure --\r\n\r\nIf you find a bug, report it on DigitalMZX (there will be a special\r\npinned thread for this) or e-mail me at exophase@adelphia.net. Here are\r\nsome guidelines to consider:\r\n\r\n- Describe exactly what is happening to you. Do not attempt to diagnose\r\n  the nature of the bug, because you may be wrong, and this could throw\r\n  me off significantly.\r\n- I may ask for you to then send the world file where this is happening,\r\n  or even better, a save file.\r\n- Include directions of how to reproduce the bug. If the bug doesn't\r\n  happen every time, try to find out what is causing it some of the time\r\n  and reproduce it regularly.\r\n- If possible, it would be very helpful if you were to create a new world\r\n  which synthesizes the bug in as little code as possible. This may \r\n  require some debugging of your own on the game in question to find out\r\n  exactly what's not working.\r\n\r\nPlease do NOT make reports such as:\r\n\r\n\"HELP ME MZX IS CRASHING HELP THIS SUCKS\"\r\n\r\nBecause they won't help anything.\r\n\r\n\r\n-- Credits --\r\n\r\nMain author: Exophase (Gilead Kutnick, exophase@adelphia.net)\r\n\r\nOriginal MZX base code: Alexis Janson\r\n\r\nAdditional MZX contributions: Spider124, MenTaLguY, CapnKev, JZig,\r\n  madbrain, Akwende, Koji\r\n\r\n(I'd also like to thank MenTaLguY for idput.cpp, the first converted\r\n piece of MZX's code base, which has been included in the DOS build\r\n since version 2.51s3)\r\n\r\nOriginal SAM2WAV code: madbrain\r\n\r\nGDM2S3M library code: ajs\r\n\r\nDefault SMZX charset: LogiCow\r\n\r\nBeta Testers:\r\n  Wervyn\r\n  Quasar84\r\n  Terryn\r\n  asgromo\r\n\r\nI'd really like to give a special thanks to my beta testers,\r\nespecially Terryn. They really worked very hard in not only finding\r\nbugs but helping crush them. If not for them, the port would have\r\nnever progressed at all. They were very patient and hardworking\r\nthroughout this whole ordeal.\r\n\r\n"
  },
  {
    "path": "docs/platform_matrix.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n<title>MegaZeux Platform Support Matrix</title>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"title\" content=\"MegaZeux Platform Support Matrix\">\n<meta name=\"description\" content=\"Information about platforms supported by MegaZeux and their configuration/toolchain details.\">\n<meta name=\"twitter:card\" content=\"summary\">\n<meta name=\"twitter:title\" content=\"MegaZeux Platform Support Matrix\">\n<meta name=\"twitter:description\" content=\"Information about platforms supported by MegaZeux and their configuration/toolchain details.\">\n<script language=\"JavaScript\">\n\nfunction note_link(note) {\n  return '<a href=\"#note' + note + '\">[' + note + ']</a>';\n}\n\nfunction defunct(_v) {\n  // Historically supported platform\n  return _v + note_link(1);\n}\n\nfunction _yes(_v=\"YES\", note=0) {\n  // Ideal\n  if(note) _v += note_link(note);\n  return {c: \"yes\", v: _v};\n}\n\nfunction std(_v, note=0) {\n  // Normal\n  if(note) _v += note_link(note);\n  return {c: \"std\", v: _v};\n}\n\nfunction subopt(_v=\"NO\", note=0)\n{\n  // Supoptimal, trivially fixable\n  if(note) _v += note_link(note);\n  return {c: \"no2\", v: _v};\n}\n\nfunction _FAULTY(_v=\"NO\", note=0)\n{\n  // Faulty, not trivially fixable\n  if(note) _v += note_link(note);\n  return {c: \"no\", v: _v};\n}\n\n// Some common values\nvar yes = _yes();\nvar no = std(\"NO\");\nvar tba = {c: \"tba\", v: \"TBA\"};\n\nvar stack_protector_strong = _yes(\"YES\", 4);\nvar no_low_memory = subopt(\"NO\", 5);\nvar no_updater = subopt(\"NO\", 6);\nvar no_updater_unix = subopt(\"NO\", 7);\nvar optional_sdl = std(\"YES\");\nvar optional_sdl_3ds = std(\"YES\", 8);\nvar optional_sdl_wii = std(\"YES\", 9);\nvar yes_but_8bpp = subopt(\"YES (8bpp)\", 10);\nvar no_gl_switch = subopt(\"NO\", 11);\nvar no_gl_fixed_android = subopt(\"NO\", 12);\nvar no_libpng = std(\"NO\", 14);\nvar no_libpng_wiiu = std(\"NO\", 15);\n\nvar ZIP = _yes(\"ZIP\");\nvar DMG = _yes(\"DMG\");\nvar LHA = _yes(\"LHA\");\nvar APK = _yes(\"APK\");\n\nvar xmp = std(\"xmp\");\nvar maxmod = std(\"Maxmod\");\nvar rad = std(\"RAD\");\nvar libvorbis = std(\"libvorbis\");\nvar tremor = std(\"tremor\");\nvar tremor_lowmem = std(\"tremor<br>(lowmem)\");\nvar stb_vorbis = std(\"stb_vorbis\");\n\nvar render_ctr = std(\"render_ctr\");\nvar render_nds = std(\"render_nds\");\nvar render_gp2x = std(\"render_gp2x\");\nvar render_gx = std(\"render_gx<br>render_xfb\");\n\nvar speed = \"Speed\";\nvar size = \"Size\";\n\n\nvar fields =\n{\n  platform:         \"$PLATFORM\",\n  description:      \"Description\",\n  architecture:     \"Architecture(s)<br>(Tested Only)\",\n  format:           \"Executable Format\",\n  endian:           \"Endian\",\n  toolchain:        \"Toolchain\",\n  libc:             \"libc\",\n  packaged:         \"Packaged\",\n  visibility:       \"Optimized Visibility\" + note_link(3),\n  stack_protector:  \"Stack Protector\",\n  layer_rendering:  \"Layer Rendering\",\n  module_engine:    \"Module Engine\",\n  adlib_engine:     \"Adlib Engine\",\n  ogg_vorbis:       \"Ogg Vorbis\",\n  optimization:     \"Optimization\",\n  sdl3:             \"SDL3\",\n  sdl2:             \"SDL2\",\n  sdl:              \"SDL 1.2\",\n  editor:           \"EDITOR\",\n  helpsys:          \"HELPSYS\",\n  audio:            \"AUDIO\",\n  software:         \"SOFTWARE\",\n  overlay:          \"SOFTSCALE\",\n  gl:               \"GL\",\n  glsl:             \"GLSL\",\n  network:          \"NETWORK\" + note_link(13),\n  updater:          \"UPDATER\",\n  modular:          \"MODULAR\",\n  png:              \"LIBPNG\",\n  x11:              \"X11\",\n  hashtables:       \"HASH TABLES\",\n  loadsave_meter:   \"LOADSAVE_METER\",\n};\n\nvar archs =\n{\n/*\n  // NOTE: If a field isn't applicable to your arch, it can be omitted entirely.\n  // An omitted field will appear greyed out and read \"N/A\" in the table.\n\n  your_arch_here:\n  {\n    platform:         // config platform name\n    description:      // short description\n    architecture:     // target processor architecture\n    format:           // executable file format\n    endian:           // \"Little\" or \"Big\"\n    toolchain:        // gcc/clang/binutils/etc versions\n    libc:             // C standard library implementation\n    packaged:         // ZIP or other\n    visibility:       // Optimized visibility (See note 3)\n    stack_protector:  // Is the stack protector enabled? (usually _FAULTY())\n    module_engine:    // xmp or other\n    adlib_engine:     // rad\n    ogg_vorbis:       // vorbis, tremor, tremor_lowmem or _FAULTY()\n    optimization:     // speed or size\n    sdl3:             // Does this platform support SDL3?\n    sdl2:             // Does this platform support SDL2?\n    sdl:              // Does this platform support SDL 1.2?\n    editor:           // Does this platform ship with the editor enabled?\n    helpsys:          // Does this platform ship with the help file enabled?\n    audio:            // Does this platform ship with audio enabled?\n    software:         // Does this platform have a software/proprietary renderer?\n    overlay:          // Does this platform use the softscale (SDL 2) or overlay (SDL 1.2) renderer(s)?\n    gl:               // Does this platform use the OpenGL fixed function renderers?\n    glsl:             // Does this platform use the GLSL renderer?\n    network:          // Does this platform have general network support?\n    updater:          // Is this platform compatible with the updater?\n    modular:          // Does this platform use modular builds?\n    png:              // Does this platform build with libpng?\n    x11:              // Can this platform link to X11?\n    hashtables:       // Does this platform use hash tables for counter lookups?\n    loadsave_meter:   // Does this platform ship with the loadsave meter enabled?\n  },\n*/\n\n  _3ds:\n  {\n    platform:         \"3ds\",\n    description:      \"Nintendo 3DS\",\n    architecture:     \"ARM11\",\n    format:           \"ELF\",\n    endian:           \"Little\",\n    toolchain:        \"gcc 14.2.0 <br> binutils&nbsp;2.43.1 <br> (dk r65)\",\n    libc:             \"Newlib\",\n    packaged:         ZIP,\n    stack_protector:  _FAULTY(),\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       tremor,\n    optimization:     speed,\n    sdl2:             optional_sdl_3ds,\n    sdl:              optional_sdl_3ds,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         render_ctr,\n    network:          yes,\n    updater:          no_updater,\n    png:              yes,\n    hashtables:       yes,\n    loadsave_meter:   yes,\n  },\n\n  android:\n  {\n    platform:         \"android\",\n    description:      \"Android\",\n    architecture:     \"ARMv7-a <br> AArch64 <br> i686 <br> x86_64\",\n    format:           \"ELF\",\n    endian:           \"Little\",\n    toolchain:        \"clang&nbsp;12.0.9 <br> llvm 12.0.9 <br> (ndk r23)\",\n    libc:             \"Bionic\",\n    packaged:         APK,\n    stack_protector:  stack_protector_strong,\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       libvorbis,\n    optimization:     speed,\n    sdl3:             tba,\n    sdl2:             yes,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         yes,\n    overlay:          yes,\n    gl:               no_gl_fixed_android,\n    glsl:             yes,\n    network:          yes,\n    updater:          no_updater,\n    png:              no_libpng,\n    hashtables:       yes,\n    loadsave_meter:   no,\n  },\n\n  darwin:\n  {\n    platform:         \"darwin\",\n    description:      \"Mac OS X/macOS (compatibility)\",\n    architecture:     \"Various <br>\" + note_link(\"macOS\"),\n    format:           \"Mach&#x2011;O\",\n    endian:           \"Varies\",\n    toolchain:        \"Various\",\n    libc:             \"Apple libc\",\n    packaged:         DMG,\n    visibility:       yes,\n    stack_protector:  stack_protector_strong,\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       libvorbis,\n    optimization:     speed,\n    sdl3:             tba,\n    sdl2:             yes,\n    sdl:              optional_sdl,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         yes,\n    gl:               yes,\n    overlay:          yes,\n    glsl:             yes,\n    network:          yes,\n    updater:          no_updater,\n    modular:          yes,\n    png:              yes,\n    hashtables:       yes,\n    loadsave_meter:   no,\n  },\n\n  djgpp:\n  {\n    platform:         \"djgpp\",\n    description:      \"MS-DOS (32&#x2011;bit)\",\n    architecture:     \"i586\",\n    format:           \"MZ / COFF\",\n    endian:           \"Little\",\n    toolchain:        \"gcc 12.2.0 <br /> binutils 2.30\",\n    libc:             \"DJGPP libc\",\n    packaged:         ZIP,\n    stack_protector:  _FAULTY(),\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       stb_vorbis,\n    optimization:     speed,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         _yes(\"render_svga\"),\n    png:              subopt(\"NO\", 14),\n    hashtables:       yes,\n    loadsave_meter:   yes,\n  },\n\n  dreamcast:\n  {\n    platform:         \"dreamcast\",\n    description:      \"Sega Dreamcast\",\n    architecture:     \"SH-4\",\n    format:           \"ELF\",\n    endian:           \"Little\",\n    toolchain:        \"gcc 13.2.0 <br> binutils&nbsp;2.43 <br> (KallistiOS)\",\n    libc:             \"Newlib\",\n    packaged:         _FAULTY(),\n    stack_protector:  _FAULTY(),\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       tremor,\n    optimization:     size,\n    editor:           no_low_memory,\n    helpsys:          no_low_memory,\n    audio:            yes,\n    software:         yes,\n    network:          _FAULTY(),\n    updater:          no_updater,\n    png:              no_libpng,\n    hashtables:       yes,\n    loadsave_meter:   yes,\n  },\n\n  emscripten:\n  {\n    platform:         \"emscripten\",\n    description:      \"HTML5 (Emscripten)\",\n    architecture:     \"JavaScript <br> WebAssembly\",\n    format:           \"&mdash;\",\n    endian:           \"Little\",\n    toolchain:        \"clang 21.0.0 <br> (sdk 4.0.7)\",\n    libc:             \"musl\",\n    packaged:         ZIP,\n    stack_protector:  _FAULTY(),\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       libvorbis,\n    optimization:     speed,\n    sdl3:             yes,\n    sdl2:             optional_sdl,\n    editor:           _FAULTY(),\n    helpsys:          yes,\n    audio:            yes,\n    software:         yes,\n    gl:               no,\n    overlay:          yes,\n    glsl:             yes,\n    network:          no,\n    updater:          no,\n    modular:          no,\n    png:              no_libpng,\n    hashtables:       yes,\n    loadsave_meter:   no,\n  },\n\n  mingw:\n  {\n    platform:         \"mingw\",\n    description:      \"Windows <br> (MinGW)\",\n    architecture:     \"x64 <br> x86\",\n    format:           \"PE32&nbsp;/&nbsp;PE32+\",\n    endian:           \"Little\",\n    toolchain:        \"gcc 14.2.1 <br> binutils&nbsp;2.42\",\n    libc:             \"MSVCRT\",\n    packaged:         ZIP,\n    visibility:       yes,\n    stack_protector:  stack_protector_strong,\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       libvorbis,\n    optimization:     speed,\n    sdl3:             yes,\n    sdl2:             optional_sdl,\n    sdl:              optional_sdl,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         yes,\n    gl:               yes,\n    overlay:          yes,\n    glsl:             yes,\n    network:          yes,\n    updater:          yes,\n    modular:          yes,\n    png:              yes,\n    hashtables:       yes,\n    loadsave_meter:   no,\n  },\n\n  msvc:\n  {\n    platform:         \"msvc\",\n    description:      \"Windows <br> (Visual Studio)\",\n    architecture:     \"x64 <br> x86 \",\n    format:           \"PE32&nbsp;/&nbsp;PE32+\",\n    endian:           \"Little\",\n    toolchain:        \"Visual Studio 2017\",\n    libc:             \"VC 14.0 RT\",\n    packaged:         subopt(\"NO\", 2),\n    visibility:       yes,\n    stack_protector:  _FAULTY(),\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       libvorbis,\n    optimization:     speed,\n    sdl3:             _FAULTY(),\n    sdl2:             yes,\n    sdl:              no,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         yes,\n    gl:               yes,\n    overlay:          yes,\n    glsl:             yes,\n    network:          yes,\n    updater:          yes,\n    modular:          yes,\n    png:              yes,\n    hashtables:       yes,\n    loadsave_meter:   no,\n  },\n\n  nds:\n  {\n    platform:         \"nds\",\n    description:      \"Nintendo DS\",\n    architecture:     \"ARM9\",\n    format:           \"ELF\",\n    endian:           \"Little\",\n    toolchain:        \"gcc 15.2.1 <br> binutils&nbsp;2.45 <br> (blocksds&nbsp;1.15.3)\",\n    libc:             \"Picolibc\",\n    packaged:         ZIP,\n    stack_protector:  no_low_memory,\n    layer_rendering:  _FAULTY(),\n    module_engine:    maxmod,\n    adlib_engine:     _FAULTY(),\n    ogg_vorbis:       _FAULTY(),\n    optimization:     size,\n    editor:           no_low_memory,\n    helpsys:          no_low_memory,\n    audio:            yes,\n    software:         render_nds,\n    network:          _FAULTY(),\n    updater:          no_updater,\n    png:              no_libpng,\n    hashtables:       no_low_memory,\n    loadsave_meter:   yes,\n  },\n\n  psp:\n  {\n    platform:         \"psp\",\n    description:      \"PlayStation Portable\",\n    architecture:     \"MIPS\",\n    format:           \"ELF\",\n    endian:           \"Little\",\n    toolchain:        \"gcc 4.6.3 <br> binutils&nbsp;2.22 <br> (dk r17)\",\n    libc:             \"Newlib\",\n    packaged:         ZIP,\n    stack_protector:  _FAULTY(),\n    layer_rendering:  yes_but_8bpp,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       tremor_lowmem,\n    optimization:     size,\n    sdl3:             _FAULTY(),\n    sdl2:             _FAULTY(),\n    sdl:              yes,\n    editor:           no_low_memory,\n    helpsys:          no_low_memory,\n    audio:            yes,\n    software:         yes,\n    network:          _FAULTY(),\n    updater:          no_updater,\n    png:              yes,\n    hashtables:       yes,\n    loadsave_meter:   yes,\n  },\n\n  psvita:\n  {\n    platform:         \"psvita\",\n    description:      \"PlayStation Vita\",\n    architecture:     \"ARMv7\",\n    format:           \"ELF\",\n    endian:           \"Little\",\n    toolchain:        \"gcc 10.3.0 <br> binutils&nbsp;2.34 <br> (sdk&nbsp;v2.529)\",\n    libc:             \"Newlib\",\n    packaged:         ZIP,\n    stack_protector:  _FAULTY(),\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       libvorbis,\n    optimization:     speed,\n    sdl3:             yes,\n    sdl2:             optional_sdl,\n    sdl:              optional_sdl,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         yes,\n    overlay:          yes,\n    network:          yes,\n    updater:          no_updater,\n    png:              yes,\n    hashtables:       yes,\n    loadsave_meter:   yes,\n  },\n\n  switch:\n  {\n    platform:         \"switch\",\n    description:      \"Nintendo Switch\",\n    architecture:     \"AArch64\",\n    format:           \"ELF\",\n    endian:           \"Little\",\n    toolchain:        \"gcc 14.2.0 <br> binutils&nbsp;2.43.1 <br> (dk r27)\",\n    libc:             \"Newlib\",\n    packaged:         ZIP,\n    stack_protector:  stack_protector_strong,\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       libvorbis,\n    optimization:     speed,\n    sdl2:             yes,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         yes,\n    overlay:          yes,\n    gl:               no_gl_switch,\n    glsl:             no_gl_switch,\n    network:          yes,\n    updater:          no_updater,\n    png:              yes,\n    hashtables:       yes,\n    loadsave_meter:   yes,\n  },\n\n  unix:\n  {\n    platform:         \"unix\",\n    description:      \"Linux, BSD, Haiku, etc.\",\n    architecture:     \"AMD64 <br> i386 <br> AArch64 <br> \" + note_link(\"Others\"),\n    format:           \"ELF\",\n    endian:           \"Varies\",\n    toolchain:        \"Various\",\n    libc:             \"Various\",\n    packaged:         _yes(\"Various\"),\n    visibility:       yes,\n    stack_protector:  stack_protector_strong,\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       libvorbis,\n    optimization:     speed,\n    sdl3:             yes,\n    sdl2:             optional_sdl,\n    sdl:              optional_sdl,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         yes,\n    gl:               yes,\n    overlay:          yes,\n    glsl:             yes,\n    network:          yes,\n    updater:          no_updater_unix,\n    modular:          yes,\n    png:              yes,\n    x11:              yes,\n    hashtables:       yes,\n    loadsave_meter:   no,\n  },\n\n  wii:\n  {\n    platform:         \"wii\",\n    description:      \"Nintendo Wii\",\n    architecture:     \"PowerPC\",\n    format:           \"ELF\",\n    endian:           \"Big\",\n    toolchain:        \"gcc 13.1.0 <br> binutils&nbsp;2.42 <br> (dk r45.2)\",\n    libc:             \"Newlib\",\n    packaged:         ZIP,\n    stack_protector:  _FAULTY(),\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       tremor,\n    optimization:     speed,\n    sdl2:             optional_sdl_wii,\n    sdl:              optional_sdl_wii,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         render_gx,\n    network:          yes,\n    updater:          no_updater,\n    png:              yes,\n    hashtables:       yes,\n    loadsave_meter:   yes,\n  },\n\n  wiiu:\n  {\n    platform:         \"wiiu\",\n    description:      \"Nintendo Wii U\",\n    architecture:     \"PowerPC\",\n    format:           \"ELF\",\n    endian:           \"Big\",\n    toolchain:        \"gcc 13.1.0 <br> binutils&nbsp;2.42 <br> (dk r45.2)\",\n    libc:             \"Newlib\",\n    packaged:         ZIP,\n    stack_protector:  _FAULTY(),\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       tremor,\n    optimization:     speed,\n    sdl2:             yes,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         yes,\n    overlay:          yes,\n    network:          _FAULTY(),\n    updater:          no_updater,\n    png:              no_libpng_wiiu,\n    hashtables:       yes,\n    loadsave_meter:   yes,\n  },\n\n  xcode:\n  {\n    platform:         \"xcode\",\n    description:      \"macOS 10.11+ (Xcode)\",\n    architecture:     \"x86_64 <br> AArch64 <br>\" + note_link(\"macOS\"),\n    format:           \"Mach&#x2011;O\",\n    endian:           \"Little\",\n    toolchain:        \"clang 1400.0.29.202 <br> LLVM 14.0.0\",\n    libc:             \"Apple libc\",\n    packaged:         DMG,\n    visibility:       yes,\n    stack_protector:  yes,\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       libvorbis,\n    optimization:     speed,\n    sdl3:             tba,\n    sdl2:             yes,\n    sdl:              no,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         yes,\n    gl:               yes,\n    overlay:          yes,\n    glsl:             yes,\n    network:          yes,\n    updater:          no_updater,\n    modular:          yes,\n    png:              yes,\n    hashtables:       yes,\n    loadsave_meter:   no,\n  },\n\n  _0: \"spacer\",\n\n  amiga:\n  {\n    platform:         defunct(\"amiga\"),\n    description:      \"AmigaOS 4.x\",\n    architecture:     \"PowerPC\",\n    format:           \"ELF\",\n    endian:           \"Big\",\n    toolchain:        \"gcc 4.2.2 <br> binutils&nbsp;2.14\",\n    libc:             \"clib2\",\n    packaged:         LHA,\n    visibility:       yes,\n    stack_protector:  stack_protector_strong,\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       libvorbis,\n    optimization:     speed,\n    sdl:              yes,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         yes,\n    gl:               yes,\n    overlay:          yes,\n    glsl:             yes,\n    network:          yes,\n    updater:          no_updater,\n    modular:          yes,\n    png:              yes,\n    hashtables:       yes,\n    loadsave_meter:   no,\n  },\n\n  gp2x:\n  {\n    platform:         defunct(\"gp2x\"),\n    description:      \"GP2x\",\n    architecture:     \"ARM9\",\n    format:           \"ELF\",\n    endian:           \"Little\",\n    toolchain:        \"gcc 4.1.1 <br> binutils&nbsp;2.16.1 <br> (open2x)\",\n    libc:             \"glibc 2.3.7\",\n    packaged:         ZIP,\n    stack_protector:  _FAULTY(),\n    layer_rendering:  _FAULTY(),\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       tremor_lowmem,\n    optimization:     size,\n    sdl:              yes,\n    editor:           no_low_memory,\n    helpsys:          no_low_memory,\n    audio:            yes,\n    software:         render_gp2x,\n    png:              yes,\n    hashtables:       yes,\n    loadsave_meter:   yes,\n  },\n\n  pandora:\n  {\n    platform:         defunct(\"pandora\"),\n    description:      \"Pandora\",\n    architecture:     \"ARMv7-a\",\n    format:           \"ELF\",\n    endian:           \"Little\",\n    toolchain:        \"gcc 4.4.1 <br> binutils&nbsp;2.19.51 <br> (CS 2009q3)\",\n    libc:             \"glibc\",\n    packaged:         subopt(\"ZIP (PND?)\"),\n    visibility:       yes,\n    stack_protector:  stack_protector_strong,\n    layer_rendering:  yes,\n    module_engine:    xmp,\n    adlib_engine:     rad,\n    ogg_vorbis:       tremor_lowmem,\n    optimization:     speed,\n    sdl:              yes,\n    editor:           yes,\n    helpsys:          yes,\n    audio:            yes,\n    software:         yes,\n    gl:               _FAULTY(),\n    overlay:          _FAULTY(),\n    glsl:             _FAULTY(),\n    network:          yes,\n    updater:          no_updater,\n    png:              yes,\n    x11:              subopt(),\n    hashtables:       yes,\n    loadsave_meter:   no,\n  },\n};\n\nfunction build_table()\n{\n  var content = \"\"\n  var i = 0;\n\n  function th(_data)\n  {\n    content += '<th>';\n\n    if(typeof(_data) === 'object')\n    {\n      content += _data.v;\n    }\n    else\n\n    if(typeof(_data) === 'string')\n    {\n      content += _data;\n    }\n\n    else\n    {\n      content += 'N/A';\n    }\n    content += '</th>\\n';\n  }\n\n  function td(_data)\n  {\n    if(typeof(_data) === 'object')\n    {\n      content += '<td class=\"' + _data.c + '\">';\n      content += _data.v;\n    }\n    else\n\n    if(typeof(_data) === 'string')\n    {\n      content += '<td>';\n      content += _data;\n    }\n\n    else\n    {\n      content += '<td class=\"na\">';\n      content += 'N/A';\n    }\n    content += '</td>\\n';\n  }\n\n  function spacer()\n  {\n    content += '<td class=\"spacer\"></td>\\n';\n  }\n\n  for(var f in fields)\n  {\n    content += '<tr>\\n';\n    th(fields[f]);\n\n    if(i == 0)\n    {\n      // Header row\n      for(var a in archs)\n      {\n        if(archs[a] === \"spacer\")\n          spacer();\n\n        else\n          th(archs[a][f]);\n      }\n\n      i = 1;\n    }\n    else\n    {\n      // Data row\n      for(var a in archs)\n      {\n        if(archs[a] === \"spacer\")\n          spacer();\n\n        else\n          td(archs[a][f]);\n      }\n    }\n\n    content += \"</tr>\\n\";\n  }\n\n  document.getElementById('matrix').innerHTML = content;\n}\n\nwindow.onload = build_table;\n\n</script>\n<style type=\"text/css\">\nbody {\n\tfont-family : \"Verdana\", \"Bitstream Vera Sans\";\n\tmargin-left : 0.5em;\n\tmargin-right : 0.5em;\n}\n\np#legend {\n\tmargin-left : 1em;\n\tmargin-right : 1em;\n}\n\ntable {\n\tborder-collapse : collapse;\n\tborder : 2px solid black;\n\tborder-spacing : 0;\n\tfont-size : 9pt;\n}\n\ntable#matrix {\n\tmin-width : 1880px;\n\twidth : 99%;\n}\n\ntd, th {\n\ttext-align : center;\n\tborder-width : 1px 1px 1px 1px;\n\tborder-style : inset inset inset inset;\n\tborder-color : gray;\n\tpadding : 4px;\n\tcolor : black;\n  max-width : 100px;\n}\n\ntd {\n\tvertical-align : top;\n}\n\nth {\n\tbackground-color : rgb(200,200,200);\n  max-width: 120px;\n}\n\ntd.yes, td.no, td.no2, td.na, td.std, td.tba {\n\tvertical-align : middle;\n}\n\nspan.yes, span.no, span.no2, span.na, span.std, span.tba {\n\tborder : 1px solid black;\n\tpadding-right : 1em;\n\tpadding-left : 1em;\n\twhite-space: nowrap;\n}\n\n.yes {\n\tbackground-color : rgb(0,255,0);\n}\n\n.no {\n\tbackground-color : rgb(255,0,0);\n\tfont-weight : bold;\n\tcolor : white;\n}\n\n.no2 {\n\tbackground-color : rgb(255,150,150);\n\tcolor : black;\n}\n\n.na {\n\tbackground-color : rgb(220,220,200);\n}\n\n.std {\n\tbackground-color : rgb(150,255,150);\n}\n\n.tba {\n\tbackground-color: yellow;\n}\n\n.spacer {\n  border-top : none;\n  border-bottom : none;\n  padding-left : 4px;\n  width : 0.5%;\n}\n\na {\n\tfont-weight : normal;\n\tfont-size : 8pt;\n}\n\nli {\n\tpadding : 2px;\n\tmax-width: 960px;\n}\n</style>\n</head>\n\n<body>\n\n<h1>MegaZeux Platform Support Matrix</h1>\n<p>Best viewed with &gt;=1920 pixel monitor. Requires Javascript.</p>\n\n<p id=\"legend\">\n<b>Legend:</b>\n<span class=\"yes\">Ideal</span>\n<span class=\"std\">Normal</span>\n<span class=\"na\">Not applicable</span>\n<span class=\"tba\">Pending triage</span>\n<span class=\"no2\">Suboptimal, Trivially fixable</span>\n<span class=\"no\">Faulty, Not trivially fixable</span>\n</p>\n\n<table id=\"matrix\"></table>\n\n<hr>\n\n<h2>Platform Notes</h2>\n\n<ol>\n\n<li id=\"note1\">These platforms are currently unsupported due to lack\nof available hardware to test on, lack of working toolchains, inherent\ncompatibility issues with MegaZeux, or simply lack of interest. Information\nbased on last known working builds (if any).\n</li>\n\n<li id=\"note2\">Could use existing `package.sh' however this does\nnot handle PDB files.<br>\nMSVC binaries are replicate of MinGW binaries and require a proprietary non\ncross-capable compiler.\n</li>\n\n<li id=\"note3\">&quot;Optimized Visibility&quot; refers to the\nhiding of library symbols which are not required or referenced\noutside of that library. An &quot;optimized&quot; library is smaller\nand loads faster.<br>\nRequired on Win32. Only applicable with CONFIG_MODULAR=y builds.\n</li>\n\n<li id=\"note4\">The GCC option `-fstack-protector-strong` is preferred when\navailable; otherwise, `-fstack-protector-all` will be selected. The config.sh\noption `--disable-stack-protector` should be used for old MinGW compilers (due\nto miscompilation in conjunction with C++ exceptions) and for platforms where\nGCC does not support the stack protector at all (e.g. DEC Alpha).\n</li>\n\n<li id=\"note5\">The editor and help system features are disabled\nto conserve cache on embedded platforms. The features build and work\non these platforms, but they are basically unusable due to the lack of any\non-screen keyboard. Additionally, hash tables and stack protection have been\ndisabled on the NDS to conserve memory.\n</li>\n\n<li id=\"note6\">The updater may or may not work on these platforms, but\nno builds are currently provided by the official update hosts.\n</li>\n\n<li id=\"note7\">The updater is typically disabled in the Linux\nbinaries that are shipped (for distributions like Debian or Fedora), even\nthough the feature can easily be enabled.<br>The issue is that MegaZeux is\ninstalled system-wide on these platforms and overwrites cannot be\nguaranteed.<br>Additionally, it is felt that the advanced package management\non these platforms supercedes any usefulness of the built-in updater.\n</li>\n\n<li id=\"note8\">Building MZX with SDL on the 3DS is viable, but the\nsoftware renderer is far slower, currently offers no additional functionality,\nand doesn't allow for special features such as screen dragging. Furthermore,\nSDL significantly increases the size of the MZX executable.\n</li>\n\n<li id=\"note9\">Building MZX with SDL Wii (1.2) or SDL2 is viable, but\nproduces larger binaries than desired and relies on the software renderer.\nSDL Wii (1.2) is also considered unstable. Also, the SDL controller mappings\nare far less flexible than MZX's built-in support and may omit features (like\nthe classic controller's analog triggers).\n</li>\n\n<li id=\"note10\">Layer rendering is supported on this platform by as it\nuses the SDL software renderer. By default, the 8bpp software renderer is\nselected for performance, so the UI palette will not be protected in SMZX mode.\nSwitching to a higher force_bpp value will fix this issue.\n</li>\n\n<li id=\"note11\">The GL renderers work on the Switch but have been\ndisabled because they cause system crashes when exiting MegaZeux. They generally\nseem to perform worse than softscale, so there isn't much reason to fix this.\n</li>\n\n<li id=\"note12\">The GL fixed-function renderers currently do not work with\nAndroid. This hasn't been investigated because the softscale and GLSL based\nrenderers are essentially guaranteed to work with the supported API levels.\n</li>\n\n<li id=\"note13\">Networking support is only enabled when some feature that\nrequires it is also enabled. The only feature that currently uses networking\nis the updater.\n</li>\n\n<li id=\"note14\">libpng is used to save screenshots/editor image exports, to\nload icons for some platforms, to load the onscreen keyboard in the 3DS port,\nand for loading PNGs with the utilities <b>ccv</b> and <b>png2smzx</b>.\nMegaZeux has a fallback PNG exporter, so libpng can be disabled to reduce\nexecutable size if it isn't really needed and/or if the worse PNG output is\nacceptable. Currently, only DJGPP is truly deficient here (utilities).\n</li>\n\n<li id=\"note15\">There is currently a toolchain conflict between two versions of\nzlib - one (1.2.5) included in the Wii U operating system, one (1.2.11) included\nin devkitPro. As such, the devkitPro-provided version of libpng fails to link.\n</li>\n</ol>\n\n<hr>\n\n<h2 id=\"noteOthers\">Other Unix Architectures</h2>\n<p>\n  MegaZeux is regularly tested with Linux, BSD, and/or Android on hardware\n  (or semi-regularly tested in QEMU) for the following architectures:\n</p>\n<table style=\"min-width:800px\">\n  <tr>\n    <th></th>\n    <th>Debian GNU/Linux</th>\n    <th>Alpine Linux</th>\n    <th>NetBSD</th>\n    <th>Android</th>\n    <th>Related ports</th>\n  </tr>\n  <tr>\n    <th>AMD64 / x86-64</th>\n    <td class=\"std\">YES</td>\n    <td class=\"std\">YES</td>\n    <td class=\"std\">YES</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n  <tr>\n    <th>i386 / i686 / etc.</th>\n    <td class=\"std\">YES</td>\n    <td class=\"std\">YES</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n  <tr>\n    <th>AArch64</th>\n    <td class=\"std\">Le Potato<br>Raspberry Pi</td>\n    <td class=\"tba\">qemu-system-aarch64</td>\n    <td>&mdash;</td>\n    <td class=\"std\">YES</td>\n    <td>Nintendo Switch</td>\n  </tr>\n  <tr>\n    <th>ARMv7</th>\n    <td class=\"std\">Raspberry Pi</td>\n    <td class=\"tba\">qemu-system-arm</td>\n    <td>&mdash;</td>\n    <td class=\"std\">YES</td>\n    <td>PlayStation Vita<br>Pandora</td>\n  </tr>\n  <tr>\n    <th>ARMv6 / ARMhf</th>\n    <td class=\"std\">Raspberry Pi<br>qemu-system-arm</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>Nintendo 3DS</td>\n  </tr>\n  <tr>\n    <th><=ARMv5</th>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>Nintendo DS<br>GP2X</td>\n  </tr>\n  <tr>\n    <th>RISC-V RV64GC</th>\n    <td class=\"std\">VisionFive 2<br>qemu-system-riscv64</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n\n  <tr><td colspan=\"6\"></td></tr>\n\n  <tr>\n    <th>DEC Alpha</th>\n    <td class=\"tba\">qemu-system-alpha</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n  <tr>\n    <th>LoongArch64</th>\n    <td>&mdash;</td>\n    <td class=\"tba\">qemu-system-loong64</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n  <tr>\n    <th>MIPSeb</th>\n    <td class=\"tba\">qemu-system-mips<br>(Debian 10.13)</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n  <tr>\n    <th>MIPSel</th>\n    <td class=\"tba\">qemu-system-mipsel<br>(Debian 12.x)</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>PlayStation Portable</td>\n  </tr>\n  <tr>\n    <th>MIPS64el</th>\n    <td class=\"tba\">qemu-system-mipsel</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n  <tr>\n    <th>Motorola 68000</th>\n    <td class=\"tba\">qemu-system-m68k</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n  <tr>\n    <th>PA-RISC</th>\n    <td class=\"tba\">qemu-system-hppa</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n  <tr>\n    <th>PowerPC</th>\n    <td class=\"tba\">qemu-system-ppc</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>Mac OS X<br>Nintendo Wii<br>Nintendo Wii U<br>AmigaOS 4</td>\n  </tr>\n  <tr>\n    <th>PowerPC 64</th>\n    <td class=\"tba\">qemu-system-ppc64</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>Mac OS X</td>\n  </tr>\n  <tr>\n    <th>POWER8</th>\n    <td>&mdash;</td>\n    <td class=\"tba\">qemu-system-ppc64</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n  <tr>\n    <th>SPARC</th>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td class=\"tba\">qemu-system-sparc</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n  <tr>\n    <th>SPARC64</th>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td class=\"tba\">qemu-system-sparc64</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n  <tr>\n    <th>SuperH SH-4</th>\n    <td class=\"tba\">qemu-sh4 (binfmt_misc)</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>Sega Dreamcast</td>\n  </tr>\n  <tr>\n    <th>IBM Z</th>\n    <td>&mdash;</td>\n    <td class=\"tba\">qemu-system-s390x</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n    <td>&mdash;</td>\n  </tr>\n</table>\n\n<hr>\n\n<h2 id=\"notemacOS\">Darwin/Xcode Architectures</h2>\n<p>\n  The following architectures and SDL versions are currently supported by the macOS builds.\n  Listed toolchain/SDK values are the ones used for release builds currently:\n</p>\n<table style=\"min-width:800px\">\n  <tr>\n    <th rowspan=\"2\"></th>\n    <th colspan=\"3\">Xcode</th>\n    <th colspan=\"4\">darwin-dist</th>\n  </tr>\n  <tr>\n    <th>Toolchain/SDK</th>\n    <th>Minimum OS</th>\n    <th>SDL</th>\n    <th>Toolchain/SDK</th>\n    <th>Minimum OS</th>\n    <th>SDL</th>\n    <th>Enabled in<br>release</th>\n  </tr>\n  <tr>\n    <th>ppc</th>\n    <td colspan=\"3\">&mdash;</td>\n    <td>Xcode 3.2.6<br>10.4u SDK</td>\n    <td>10.4</td>\n    <td>SDL 1.2.15<br>SDL 2.0.3</td>\n    <td class=\"std\">YES (1.2.15)</td>\n  </tr>\n  <tr>\n    <th>ppc64</th>\n    <td colspan=\"3\">&mdash;</td>\n    <td>Xcode 3.2.6<br>10.5 SDK</td>\n    <td>10.5</td>\n    <td>SDL 1.2.15<br>SDL 2.0.3</td>\n    <td class=\"std\">YES (1.2.15)</td>\n  </tr>\n  <tr>\n    <th>i386</th>\n    <td colspan=\"3\">&mdash;</td>\n    <td>Xcode 9.4.1<br>10.13 SDK</td>\n    <td>10.6</td>\n    <td>SDL 2.0.22</td>\n    <td class=\"std\">YES</td>\n  </tr>\n  <tr>\n    <th>x86_64</th>\n    <td>Xcode 14.2<br>13.1 SDK</td>\n    <td>10.11<br>10.14</td>\n    <td>SDL2 (latest)<br>SDL3 (latest)</td>\n    <td>Xcode 9.4.1<br>10.13 SDK</td>\n    <td>10.6</td>\n    <td>SDL 2.0.22</td>\n    <td class=\"std\">YES</td>\n  </tr>\n  <tr>\n    <th>x86_64h</th>\n    <td colspan=\"3\">&mdash;</td>\n    <td>Xcode 9.4.1<br>10.13 SDK</td>\n    <td>10.9</td>\n    <td>SDL2 (latest)<br>SDL3 (requires newer Xcode)</td>\n    <td class=\"no2\">NO</td>\n  </tr>\n  <tr>\n    <th>arm64</th>\n    <td>Xcode 14.2<br>13.1 SDK</td>\n    <td>11.0<br>11.0</td>\n    <td>SDL2 (latest)<br>SDL3 (latest)</td>\n    <td>Xcode 14.2<br>13.1 SDK</td>\n    <td>11.0</td>\n    <td>SDL2 (latest)<br>SDL3 (latest)</td>\n    <td class=\"std\">YES</td>\n  </tr>\n  <tr>\n    <th>arm64e</th>\n    <td colspan=\"3\">&mdash;</td>\n    <td>Xcode 14.2<br>13.1 SDK</td>\n    <td>11.0</td>\n    <td>SDL2 (latest)<br>SDL3 (latest)</td>\n    <td class=\"no2\">NO</td>\n  </tr>\n</table>\n\n<hr>\n\n<h2>Copyright</h2>\n\n<p>This document may be copied and redistributed without limitation\nor reservation.</p>\n<br><br>\n</body>\n\n</html>\n"
  },
  {
    "path": "docs/push_and_transport.txt",
    "content": "Chests       : Never\nLocks        : Never\nOther Items  : Pushed\nSlime Blobs  : Never\nSharks       : Never\nDragons      : Never\nRunners      : Pushed/Willing\nAll Guns     : Never\nOther Foes   : Pushed\nRobots       : Willing (Pushed if pushable)\nMines        : Never\nSensors      : Pushed/Willing\nMissiles     : Never\nSeekers      : Pushed\nBullets      : Never\nSpitfires    : Never\nScrolls      : Pushed\nMovingWalls  : Willing\nBoulders     : Pushed\nBoxes        : Pushed\nCrates       : Pushed\nCustomBoxes  : Pushed\nCustomPushes : Pushed\nPushers      : Never\n\nNOTES:\n\n-Whatever can be pushed can be pushed through a transporter as well.\n~\"Willing\" means they went through the transporter through natural behavior (most things) or were directed through Robotic to go through (only applies to Robots and sensors).\n~Nonhostile fish and invincible ghosts can only be pushed indirectly, not directly by the player. Current cannot force fish through transporters.\n~Sliders cannot traverse lava or goop."
  },
  {
    "path": "megazeux.spec",
    "content": "Name:\t\tmegazeux\nVersion:\t2.93d\nRelease:\t1%{?dist}\n\nSummary:\tA simple game creation system (GCS)\n\nGroup:\t\tAmusements/Games\nLicense:\tGPLv2+\nURL:\t\thttps://www.digitalmzx.com/\nSource:\t\tmegazeux-2.93d.tar.xz\nBuildRoot:\t%{_tmppath}/%{name}-%{version}-%{release}-root\n\nBuildRequires:\tSDL3-devel\nBuildRequires:\tlibvorbis-devel\nBuildRequires:\tlibpng-devel\nBuildRequires:\tzlib-devel\nBuildRequires:\tlibappstream-glib\n\n%description\n\nMegaZeux is a Game Creation System (GCS), inspired by Epic MegaGames' ZZT,\nfirst released by Alexis Janson in 1994. It has since been ported from 16-bit\nMS DOS C/ASM to portable SDL C/C++, improving its platform support and\nhardware compatibility.\n\nMany features have been added since the original DOS version, and with the\nhelp of an intuitive editor and a simple but powerful object-oriented\nprogramming language, MegaZeux allows you to create your own ANSI-esque games\nregardless of genre.\n\nSee https://www.digitalmzx.com/ for more information.\n\n%prep\n%setup -q -n mzx293d\n\n%build\n./config.sh --platform unix --enable-release --enable-lto --as-needed-hack \\\n            --enable-sdl3 \\\n            --libdir %{_libdir} \\\n            --gamesdir %{_bindir} \\\n            --sharedir %{_datadir} \\\n            --licensedir %{_datadir}/licenses \\\n            --metainfodir %{_metainfodir} \\\n            --bindir %{_libdir}/megazeux/bin \\\n            --sysconfdir %{_sysconfdir}\n%make_build\n\n%install\nrm -rf \"$RPM_BUILD_ROOT\"\n%make_install V=1\nappstream-util validate-relax %{buildroot}%{_metainfodir}/megazeux.metainfo.xml\n\n%check\n%make_build test\n\n%clean\nrm -rf \"$RPM_BUILD_ROOT\"\n\n%files\n%defattr(-,root,root,-)\n%doc docs/changelog.txt docs/macro.txt\n%{_bindir}/megazeux\n%{_bindir}/mzxrun\n%{_libdir}/megazeux\n%{_datadir}/megazeux\n%{_datadir}/applications/megazeux.desktop\n%{_datadir}/applications/mzxrun.desktop\n%{_datadir}/doc/megazeux\n%{_datadir}/licenses/megazeux\n%{_datadir}/icons/hicolor/16x16/apps/megazeux.png\n%{_datadir}/icons/hicolor/22x22/apps/megazeux.png\n%{_datadir}/icons/hicolor/24x24/apps/megazeux.png\n%{_datadir}/icons/hicolor/32x32/apps/megazeux.png\n%{_datadir}/icons/hicolor/48x48/apps/megazeux.png\n%{_datadir}/icons/hicolor/64x64/apps/megazeux.png\n%{_datadir}/icons/hicolor/128x128/apps/megazeux.png\n%{_datadir}/icons/hicolor/256x256/apps/megazeux.png\n%{_datadir}/icons/hicolor/scalable/apps/megazeux.svg\n%{_metainfodir}/megazeux.metainfo.xml\n%{_sysconfdir}/megazeux-config\n\n%changelog\n# TODO\n# - add 16, 22, 24, 32, 48, 64, 256, SVG icons\n\n* Mon Jun 09 2025 Alice Rowan <petrifiedrowan@gmail.com> 2.93d-1\n- new upstream version\n- add megazeux.metainfo.xml\n- fix licenses and utils dirs\n\n* Fri Feb 28 2025 Alice Rowan <petrifiedrowan@gmail.com> 2.93c-1\n- new upstream version, switch to SDL3\n\n* Tue Sep 10 2024 Alice Rowan <petrifiedrowan@gmail.com> 2.93b-1\n- new upstream version, add --enable-lto\n\n* Sun Dec 31 2023 Alice Rowan <petrifiedrowan@gmail.com> 2.93-1\n- new upstream version, fix icon path\n\n* Sun Nov 22 2020 Alice Rowan <petrifiedrowan@gmail.com> 2.92f-1\n- new upstream version\n\n* Sun Jul 19 2020 Alice Rowan <petrifiedrowan️@gmail.com> 2.92e-1\n- new upstream version, replace __make with make_build.\n\n* Fri May 08 2020 Alice Rowan <petrifiedrowan@gmail.com> 2.92d-1\n- new upstream version, add check\n\n* Sun Mar 08 2020 Alice Rowan <petrifiedrowan@gmail.com> 2.92c-1\n- new upstream version, fix outdated info\n\n* Sat Dec 22 2012 Alistair John Strachan <alistair@devzero.co.uk> 2.84c-1\n- new upstream version\n\n* Sat Jun 02 2012 Alistair John Strachan <alistair@devzero.co.uk> 2.84-1\n- new upstream version\n\n* Sat Nov 26 2009 Alistair John Strachan <alistair@devzero.co.uk> 2.83-1\n- new upstream version\n\n* Thu Nov 27 2008 Alistair John Strachan <alistair@devzero.co.uk> 2.82b-1\n- initial RPM release\n"
  },
  {
    "path": "scripts/ajs-buildscripts/README",
    "content": "These scripts probably only work for me, but you might find them useful\nto adapt. They basically allow me to use a variety of cross compilers to build\nMZX for multiple non-Linux platforms, on Linux. This greatly speeds up the\nbuilding of new release binaries.\n\nThe deps/ folder contains some binaries for darwin, windows, nds and wii which\naren't included in the MegaZeux tree for various licensing reasons. They're\nnecessary to build binaries for these platforms.\n\n--ajs.\n"
  },
  {
    "path": "scripts/ajs-buildscripts/amiga.sh",
    "content": "platform_enter_hook() {\n\texport PATH=$HOME/bin/amiga/bin:/usr/local/amiga/SDK/local/clib2/bin:$PATH\n\n\tBUILD_FLAGS=\"debuglink\"\n\tCONFIG_FLAGS=\"$CONFIG_FLAGS --platform amiga \\\n\t              --prefix /usr/local/amiga --disable-libpng \\\n\t              --disable-utils\"\n}\n\nplatform_exit_hook() {\n\tDUMMY=1\n}\n\nplatform_build_test_hook() {\n\tif [ ! -f mzxrun.exe -o ! -f megazeux.exe ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n\n\tfile mzxrun.exe | egrep -q \"ELF 32-bit.*MSB.*PowerPC.*dynamically\"\n\tif [ \"$?\" != \"0\" ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n\n\tfile megazeux.exe | egrep -q \"ELF 32-bit.*MSB.*PowerPC.*dynamically\"\n\tif [ \"$?\" != \"0\" ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n}\n\nplatform_archive_test_hook() {\n\t[ ! -f build/dist/amiga/*amiga.lha ] && ERRNO=2\n}\n"
  },
  {
    "path": "scripts/ajs-buildscripts/build-rev.sh",
    "content": "#!/bin/bash\n\nif [ \"$1\" = \"\" -o \"$2\" = \"\" ]; then\n\techo \"usage: $0 <datestr> <revision> <outdir> [branch]\"\n\texit 0\nfi\n\nDATE=$1\nshift\n\nREVISION=$1\nshift\n\nGITBASE=$HOME/megazeux\n\nif [ \"$2\" = \"\" ]; then\n\tOUTDIR=$1/trunk-$DATE-$REVISION\nelse\n\tOUTDIR=$1/$2-$DATE-$REVISION\nfi\n\nif [ ! -d $GITBASE ]; then\n\techo \"Could not find specified Git basedir, aborting.\"\n\texit 1\nfi\n\n[ -d \"$OUTDIR\" ] && rm -rf $OUTDIR\nmkdir -p $OUTDIR/source\n\npushd $GITBASE >/dev/null\n\ngit reset --hard $REVISION >/dev/null 2>&1\nif [ \"$?\" != \"0\" ]; then\n\techo \"git reset --hard failed; aborting.\"\n\texit 1\nfi\n\nrm -rf build\nmake source >/dev/null 2>&1\n\nSRCPKG=$GITBASE/build/dist/source/*.tar.xz\nif [ -f \"$SRCPKG\" ]; then\n\techo \"Failed to compile source package; aborting.\"\n\texit 2\nfi\n\n[ -d /tmp/megazeux ] && rm -rf /tmp/megazeux\nmkdir /tmp/megazeux\n\nfor PLATFORM in amiga gp2x nds psp wii windows-x86 windows-x64; do\n\ttar -C/tmp/megazeux -xf $SRCPKG\n\n\tpushd /tmp/megazeux/mzx* >/dev/null\n\t$GITBASE/scripts/build.sh $PLATFORM $PWD >/dev/null 2>&1\n\n\tif [ -d build/dist/$PLATFORM ]; then\n\t\tmv build/dist/$PLATFORM /tmp/megazeux\n\telse\n\t\tmkdir /tmp/megazeux/$PLATFORM\n\tfi\n\n\tmv build/logs/$PLATFORM/* /tmp/megazeux/$PLATFORM\n\n\tpopd >/dev/null\n\n\trm -rf /tmp/megazeux/mzx*\ndone\n\npushd /tmp/megazeux >/dev/null\n\nexport CONCURRENCY_LEVEL=3\n\nmkdir ubuntu\n\nfor CHROOT in ubuntu-i386 ubuntu-amd64; do\n\ttar -xf $SRCPKG\n\tMZX=`echo mzx* | sed 's,^mzx,megazeux-,' | sed 's,-2,-2.,'`\n\tmv mzx* $MZX\n\n\tpushd $MZX >/dev/null\n\tdchroot -d -c $CHROOT -- \\\n\t\tdebuild --preserve-envvar=CONFIG_FLAGS -us -uc >/dev/null 2>&1\n\tpopd >/dev/null\n\n\trm -rf $MZX\n\tmv megazeux* ubuntu\ndone\n\npopd >/dev/null\n\nmv $SRCPKG $OUTDIR/source\nrm -rf build\n\nmv /tmp/megazeux/* $OUTDIR\nrm -rf /tmp/megazeux\n\npopd >/dev/null\n"
  },
  {
    "path": "scripts/ajs-buildscripts/build.sh",
    "content": "#!/bin/bash\n\nif [ \"$1\" = \"\" ]; then\n\techo \"usage: $0 <platform> [<sourcedir>]\"\n\texit 0\nelse\n\tPLATFORM=\"$1\"\n\tshift\nfi\n\nBASEDIR=`dirname $0`\n\nif [ ! -f \"$BASEDIR/$PLATFORM.sh\" ]; then\n\techo \"Unknown port: $PLATFORM\"\n\texit 0\nfi\n\n. \"$BASEDIR/global.sh\"\n. \"$BASEDIR/$PLATFORM.sh\"\n\nif [ \"$1\" = \"\" ]; then\n\tpushd $HOME/megazeux >/dev/null\nelse\n\tpushd $1 >/dev/null\n\tshift\nfi\n\nLOGDIR=build/logs/$PLATFORM\nrm -rf build/$PLATFORM build/dist/$PLATFORM $LOGDIR\nmkdir -p $LOGDIR\n\nexport ERRNO=0\n\nplatform_enter_hook\n\n./config.sh $CONFIG_FLAGS 2>&1 | tee $LOGDIR/config.log\n\nmake -j3 $BUILD_FLAGS 2>&1 | tee $LOGDIR/build.log\nplatform_build_test_hook\n\nif [ \"$ERRNO\" = \"0\" ]; then\n\tmake archive 2>&1 | tee $LOGDIR/archive.log\n\tplatform_archive_test_hook\n\n\tif [ \"$ERRNO\" != \"0\" ]; then\n\t\techo \"Archive test failed, aborting..\"\n\tfi\nelse\n\techo \"Build test failed, aborting..\"\nfi\n\nmake distclean >/dev/null 2>&1\nplatform_exit_hook\n\npopd >/dev/null\n\nexit $ERRNO\n"
  },
  {
    "path": "scripts/ajs-buildscripts/darwin.sh",
    "content": "platform_enter_hook() {\n\tBUILD_FLAGS=\"dist\"\n\tCONFIG_FLAGS=\"$CONFIG_FLAGS --platform darwin \\\n\t              --prefix blah --disable-utils --disable-x11\"\n}\n\nplatform_exit_hook() {\n        DUMMY=1\n}\n\nplatform_build_test_hook() {\n        if [ ! -f megazeux -o ! -f mzxrun ]; then\n                ERRNO=1\n                return\n        fi\n\n        file megazeux | egrep -q \"Mach-O universal binary\"\n        if [ \"$?\" != \"0\" ]; then\n                ERRNO=1\n                return\n        fi\n\n        file mzxrun | egrep -q \"Mach-O universal binary\"\n        if [ \"$?\" != \"0\" ]; then\n                ERRNO=1\n                return\n        fi\n}\n\nplatform_archive_test_hook() {\n        [ ! -f build/dist/darwin/*.dmg ] && ERRNO=2\n}\n"
  },
  {
    "path": "scripts/ajs-buildscripts/global.sh",
    "content": "#\n# All platform builds inherit these flags\n#\nCONFIG_FLAGS=\"--enable-release $CONFIG_FLAGS\"\n"
  },
  {
    "path": "scripts/ajs-buildscripts/gp2x.sh",
    "content": "platform_enter_hook() {\n\texport PATH=$HOME/bin/open2x/gcc-4.1.1-glibc-2.3.6/arm-open2x-linux/bin:$PATH\n\n\tBUILD_FLAGS=\"debuglink\"\n\tCONFIG_FLAGS=\"$CONFIG_FLAGS --platform gp2x \\\n\t              --prefix $HOME/bin/open2x/gcc-4.1.1-glibc-2.3.6/arm-open2x-linux \\\n\t              --optimize-size --disable-editor --disable-helpsys\n\t              --disable-utils --enable-mikmod --enable-meter\"\n}\n\nplatform_exit_hook() {\n\tDUMMY=1\n}\n\nplatform_build_test_hook() {\n\tif [ ! -f mzxrun.gpe ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n\n\tfile mzxrun.gpe | egrep -q \"ELF 32-bit.*LSB.*ARM.*statically\"\n\tif [ \"$?\" != \"0\" ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n}\n\nplatform_archive_test_hook() {\n\t[ ! -f build/dist/gp2x/*gp2x.zip ] && ERRNO=2\n}\n"
  },
  {
    "path": "scripts/ajs-buildscripts/nds.sh",
    "content": "platform_enter_hook() {\n\tpushd arch/nds >/dev/null\n\t\tunzip -qq $HOME/megazeux/scripts/deps/nds.zip\n\tpopd >/dev/null\n\n\texport DEVKITPRO=$HOME/bin/devkitpro\n\texport DEVKITARM=$HOME/bin/devkitpro/devkitARM\n\texport PATH=$DEVKITARM/bin:$PATH\n\n\tBUILD_FLAGS=\"debuglink\"\n\tCONFIG_FLAGS=\"$CONFIG_FLAGS --platform nds --prefix $DEVKITARM \\\n\t              --optimize-size --disable-editor --disable-helpsys \\\n\t              --disable-utils --disable-libpng --enable-meter\"\n}\n\nplatform_exit_hook() {\n\trm -rf arch/nds/ndsLibfat arch/nds/ndsDebug\n\trm -rf arch/nds/ndsScreens/build arch/nds/ndsScreens/lib\n}\n\nplatform_build_test_hook() {\n\tif [ ! -f mzxrun ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n\n\tfile mzxrun | egrep -q \"ELF 32-bit.*LSB.*ARM.*statically\"\n\tif [ \"$?\" != \"0\" ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n}\n\nplatform_archive_test_hook() {\n\t[ ! -f build/dist/nds/*nds.zip ] && ERRNO=2\n}\n"
  },
  {
    "path": "scripts/ajs-buildscripts/psp.sh",
    "content": "platform_enter_hook() {\n\texport DEVKITPRO=$HOME/bin/devkitpro\n\texport DEVKITPSP=$HOME/bin/devkitpro/devkitPSP\n\texport PATH=$DEVKITPSP/bin:$DEVKITPSP/psp/bin:$PATH\n\n\tBUILD_FLAGS=\"debuglink\"\n\tCONFIG_FLAGS=\"$CONFIG_FLAGS --platform psp --prefix $DEVKITPSP/psp \\\n\t              --optimize-size --disable-editor --disable-helpsys \\\n\t              --disable-utils --disable-libpng --enable-meter\"\n}\n\nplatform_exit_hook() {\n\tDUMMY=1\n}\n\nplatform_build_test_hook() {\n\tif [ ! -f mzxrun ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n\n\tfile mzxrun | egrep -q \"ELF 32-bit.*LSB.*MIPS.*statically\"\n\tif [ \"$?\" != \"0\" ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n}\n\nplatform_archive_test_hook() {\n\t[ ! -f build/dist/psp/*psp.zip ] && ERRNO=2\n}\n"
  },
  {
    "path": "scripts/ajs-buildscripts/wii.sh",
    "content": "platform_enter_hook() {\n\tpushd arch/wii >/dev/null\n\t\tunzip -qq $HOME/megazeux/scripts/deps/wii.zip\n\tpopd >/dev/null\n\n\texport DEVKITPRO=$HOME/bin/devkitpro\n\texport DEVKITPPC=$HOME/bin/devkitpro/devkitPPC\n\texport PATH=$DEVKITPPC/bin:$PATH\n\n\tBUILD_FLAGS=\"debuglink\"\n\tCONFIG_FLAGS=\"$CONFIG_FLAGS --platform wii --prefix $DEVKITPPC \\\n\t              --enable-release --disable-utils --enable-tremor \\\n\t              --enable-meter\"\n}\n\nplatform_exit_hook() {\n\trm -rf arch/wii/{libfat,libogg,libvorbis}\n}\n\nplatform_build_test_hook() {\n\tif [ ! -f mzxrun -o ! -f megazeux ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n\n\tfile mzxrun | egrep -q \"ELF 32-bit.*MSB.*PowerPC.*statically\"\n\tif [ \"$?\" != \"0\" ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n\n\tfile megazeux | egrep -q \"ELF 32-bit.*MSB.*PowerPC.*statically\"\n\tif [ \"$?\" != \"0\" ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n}\n\nplatform_archive_test_hook() {\n\t[ ! -f build/dist/wii/*wii.zip ] && ERRNO=2\n}\n"
  },
  {
    "path": "scripts/ajs-buildscripts/windows-x64.sh",
    "content": "platform_enter_hook() {\n\texport PATH=$HOME/bin/mingw64/bin:$HOME/bin/mingw64/local/bin:$PATH\n\n\tBUILD_FLAGS=\"debuglink\"\n\tCONFIG_FLAGS=\"$CONFIG_FLAGS --platform mingw64 \\\n\t              --prefix $HOME/bin/mingw64\"\n}\n\nplatform_exit_hook() {\n\tDUMMY=1\n}\n\nplatform_build_test_hook() {\n\tif [ ! -f mzxrun.exe -o ! -f megazeux.exe ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n\n\tfile mzxrun.exe | egrep -q \"PE32\\+.*MS.Windows\"\n\tif [ \"$?\" != \"0\" ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n\n\tfile megazeux.exe | egrep -q \"PE32\\+.*MS.Windows\"\n\tif [ \"$?\" != \"0\" ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n}\n\nplatform_archive_test_hook() {\n\t[ ! -f build/dist/windows-x64/*-x64.zip ] && ERRNO=2\n}\n"
  },
  {
    "path": "scripts/ajs-buildscripts/windows-x86.sh",
    "content": "platform_enter_hook() {\n\texport PATH=$HOME/bin/mingw32/bin:$PATH\n\n\tBUILD_FLAGS=\"debuglink\"\n\tCONFIG_FLAGS=\"$CONFIG_FLAGS --platform mingw32 \\\n\t              --prefix $HOME/bin/mingw32\"\n}\n\nplatform_exit_hook() {\n\tDUMMY=1\n}\n\nplatform_build_test_hook() {\n\tif [ ! -f mzxrun.exe -o ! -f megazeux.exe ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n\n\tfile mzxrun.exe | egrep -q \"PE32.*Intel.80386.*MS.Windows\"\n\tif [ \"$?\" != \"0\" ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n\n\tfile megazeux.exe | egrep -q \"PE32.*Intel.80386.*MS.Windows\"\n\tif [ \"$?\" != \"0\" ]; then\n\t\tERRNO=1\n\t\treturn\n\tfi\n}\n\nplatform_archive_test_hook() {\n\t[ ! -f build/dist/windows-x86/*-x86.zip ] && ERRNO=2\n}\n"
  },
  {
    "path": "scripts/ajs-buildscripts/zip-split-debug.sh",
    "content": "#!/bin/sh\n\nif [ \"$1\" == \"\" ]; then\n\techo \"usage: $0 zipfile\"\n\texit 0\nfi\n\nSRCZIP=$PWD/$1\nshift\n\n[ -d /tmp/split ] && rm -rf /tmp/split\nmkdir -p /tmp/split/debug\nmkdir -p /tmp/split/release\n\ncd /tmp/split/debug\nunzip $SRCZIP *.debug manifest.txt\n7za -tzip a $SRCZIP.debug *\n\ncd /tmp/split/release\nrm -rf /tmp/split/debug\n\nunzip $SRCZIP\negrep -v \".debug$\" manifest.txt >manifest.txt.new\nmv manifest.txt.new manifest.txt\nfind -name \"*.debug\" -exec rm '{}' ';'\n7za -tzip a $SRCZIP.release *\n\nrm -rf /tmp/split\n"
  },
  {
    "path": "scripts/buildscripts/1_PrepareReleaseEnvironment.bat",
    "content": "usr\\bin\\bash -l /mzx-init.sh\r\n\r\npause\r\n"
  },
  {
    "path": "scripts/buildscripts/2_CreateReleases.bat",
    "content": "usr\\bin\\bash -l /mzx-build.sh\r\n\r\npause\r\n"
  },
  {
    "path": "scripts/buildscripts/3_PackageReleases.bat",
    "content": "usr\\bin\\bash -l /mzx-updates.sh\r\n\r\npause\r\n"
  },
  {
    "path": "scripts/buildscripts/4_UploadReleases.bat",
    "content": "usr\\bin\\bash -l /mzx-upload.sh\r\n\r\npause\r\n"
  },
  {
    "path": "scripts/buildscripts/README.md",
    "content": "# MegaZeux Build Scripts\n\nThis set of scripts is meant to compile a (mostly) complete set of MegaZeux\nbuilds and updater files. They are only tested and known to work with MSYS2 but\nthe scripts here should be fairly POSIX-friendly.\n\nThe following platforms can currently be built by this script:\n\n- Windows x64 and x86 (MinGW)\n- Nintendo DS\n- Nintendo 3DS\n- Nintendo Wii\n- Nintendo Wii U\n- Nintendo Switch\n- PlayStation Portable\n- PlayStation Vita\n- Sega Dreamcast\n- HTML5/Emscripten\n- Android\n- DOS (DJGPP)\n- Source package\n\nAny platforms requiring chroots (Debian, Ubuntu, Raspbian), rpmbuild (Fedora),\nobscure toolchains (Mac OS X x86/PPC, Amiga, Pandora, GP2X), or a separate build\nsystem (Xcode, MSVC) are not currently supported by these scripts. Support for\nsome of these may be added at a later date.\n\n## Usage\nFor MSYS2, the easiest way to do this is to copy all of these files to the MSYS\nroot folder. The following batch scripts can then be run, mostly automating the\nbuild and updates process.\n\n- `1_PrepareReleaseEnvironment.bat` (runs `mzx-init.sh`): create the `mzx-workingdir/`\n  folder and clone an MegaZeux repository to build MZX with. For MSYS2 this can\n  also install all required dependencies for the Windows builds, but other builds\n  require additional work (see below).\n- `2_CreateReleases.bat` (runs `mzx-build.sh`): run a complete set of MZX builds\n  (see `mzx-build.sh` for a list of builds). This will produce a set of archives\n  in `TARGET/zips/` and a release tree in `TARGET/releases/` for processing by\n  the next script. Note that this script will typically take several minutes to run.\n- `3_PackageReleases.bat` (runs `mzx-updates.sh`): convert `TARGET/releases/`\n  into the format expected by the MZX updater. Files are gzipped and renamed to\n  their SHA-256 sum with no extension; manifest.txt is gzipped with the same filename,\n  and config.txt is removed. This will also create `updates.txt` (in gzipped form).\n  This process occurs in the intermediate directory `TARGET/releases-update/`.\n  Finally, the contents of this directory are archived with `tar` for distribution.\n- `4_UploadReleases.bat` (runs `mzx-upload.sh`): uploads the updates tarball to\n  user-specified update hosts and extracts it. The file `mzx-upload.sh` must be\n  edited for this step to work.\n\nNOTE: the batch scripts only work if the entire buildscript system is copied to\nthe root MSYS2 directory. To run the scripts from a different directory, use the\nshell scripts directly.\n\nThese scripts can optionally use an existing MZX repository if run from the base\ndir, but they should be copied to a separate place first (to prevent them from\nbeing deleted when the current git branch/tag changes). The build script WILL\nreset any changes in the MZX repository used.\n\n## Environment setup\n\n### MSYS2\n\nBefore running the init script above, you should run the following command:\n`pacman -Syu`\n\nThis will make sure the base MSYS2 system is up to date. This command will likely\nask you to close the window; after doing so, simply open MSYS2 again and run\n`pacman -Su` to finish the update.\n\n### devkitPro\n\nFollow the instructions [here](https://devkitpro.org/wiki/devkitPro_pacman) to\nset up your existing pacman install (MSYS2, Arch) or a dkp-pacman install for\nuse with the devkitPro repositories. Add `export DEVKITPRO=/opt/devkitpro` to\nyour `.profile` or `.bashrc`. Finally, run the init script to install the\nrequired packages.\n\nThe init script intentionally does not set up devkitPro pacman because devkitPro\ndoesn't want their package system to be used for automated scripts. It's also\neasy enough to set up that there's no real reason to automate it.\n\nNOTE:\nThese scripts no longer support devkitPSP as support for it has been officially\ndropped. Old devkitPSP binaries can not be distributed as they contain devkitPro\ntrademarks. Use pspdev directly instead.\n\n### pspdev\n\nTODO: Versions of psptoolchain since April 2020 now successfully build in MSYS2.\nSupport for the newer toolchain has been patched into the PSP Makefile, but\nbuilds produced with this fail to start in ppsspp with a black screen and no\nconsole output. This needs to be investigated further.\n\nIf you have an old copy of devkitPSP, you can also simply define\n`PSPDEV=/opt/devkitpro/devkitPSP` until then.\n\nTo set up pspdev, first add the following to your .profile or .bashrc:\n```\nexport PSPDEV=\"/opt/pspdev\"\nexport PATH=\"$PATH:$PSPDEV/bin\"\n```\n\nMake sure the following packages are installed (MSYS2; use a MINGW64 terminal):\n```\npacman --needed --noconfirm -S git svn wget patch tar unzip bzip2 xz diffutils python\npacman --needed --noconfirm -S autoconf automake cmake make m4 bison flex tcl texinfo doxygen\npacman --needed --noconfirm -S mingw-w64-x86_64-{gcc,SDL}\npacman --needed --noconfirm -S mingw-w64-x86_64-{libelf,libusb,ncurses,curl,gpgme,dlfcn}\npacman --needed --noconfirm -S mingw-w64-x86_64-{libarchive,openssl,readline,libtool}\n```\n\nThen clone the pspdev repository somewhere, `cd` to it, and run `./pspdev.sh`\nto generate a pspdev build. Note that for MSYS2, this currently requires manual\nintervention to finish linking GDB and certain utilities currently don't build.\n```\ngit clone \"https://github.com/pspdev/pspdev\" /opt/pspdev-repo\ncd /opt/pspdev-repo\n./pspdev.sh\n```\n"
  },
  {
    "path": "scripts/buildscripts/mzx-build.sh",
    "content": "#!/bin/sh\n\nexport MZX_SCRIPTS_BASE=\"$(cd \"$(dirname \"$0\")\" || true; pwd)\"\nexport MZX_SCRIPTS=\"${MZX_SCRIPTS_BASE%/}/mzx-scripts\"\nexport MZX_WORKINGDIR=\"${MZX_SCRIPTS_BASE%/}/mzx-workingdir\"\nexport MZX_TARGET_BASE=\"$(pwd)\"\nexport MZX_TARGET=\"${MZX_TARGET_BASE%/}/TARGET\"\n\n. \"$MZX_SCRIPTS/version.sh\"\n. \"$MZX_SCRIPTS/build.sh\"\n\nbuild_init\n\n# Type          Subplatform     Branch          Update Branch           Extra config flags\n# -----------   -------------   -------------   ---------------------   ------------------\nbuild_release   \"windows-x86\"   \"$STABLE\"       \"$STABLE_UPDATES\"\nbuild_debug     \"windows-x86\"   \"$UNSTABLE\"     \"$UNSTABLE_UPDATES\"\nbuild_debug     \"windows-x86\"   \"$DEBYTECODE\"   \"$DEBYTECODE_UPDATES\"   \"--enable-debytecode\"\nbuild_release   \"windows-x64\"   \"$STABLE\"       \"$STABLE_UPDATES\"\nbuild_debug     \"windows-x64\"   \"$UNSTABLE\"     \"$UNSTABLE_UPDATES\"\nbuild_debug     \"windows-x64\"   \"$DEBYTECODE\"   \"$DEBYTECODE_UPDATES\"   \"--enable-debytecode\"\nbuild_release   \"nds\"           \"$STABLE\"\nbuild_release   \"nds-blocksds\"  \"$STABLE\"\nbuild_release   \"3ds\"           \"$STABLE\"\nbuild_release   \"wii\"           \"$STABLE\"\nbuild_release   \"wiiu\"          \"$STABLE\"\nbuild_release   \"switch\"        \"$STABLE\"\nbuild_release   \"psp\"           \"$STABLE\"\nbuild_release   \"psvita\"        \"$STABLE\"\nbuild_release   \"emscripten\"    \"$STABLE\"\nbuild_release   \"android\"       \"$STABLE\"\nbuild_release   \"djgpp\"         \"$STABLE\"\nbuild_release   \"dreamcast\"     \"$STABLE\"\nbuild_release   \"source\"        \"$STABLE\"\n"
  },
  {
    "path": "scripts/buildscripts/mzx-check.sh",
    "content": "#!/bin/sh\n\n#\n# Script to run shellcheck on these scripts to check for portability issues\n# and errors.\n#\n# Ignored warnings:\n#\n# SC1090 - Can't follow non-constant source--no way to fix this without\n#          polluting the scripts with directive comments.\n#          v0.7.2 is supposed to be able to work around this with the -P flags,\n#          but Fedora has v0.7.0 so I can't really verify right now.\n# SC1091 - Can't follow external file references. Don't want to anyway, since\n#          these might not even be for this platform.\n# SC2155 - Avoid masking return values for \"export\" doesn't matter because this\n#          code never checks the return value of \"export\".\n#\nshellcheck \\\n -e \"SC2155\" -e \"SC1090\" -e \"SC1091\" \\\n -P \"mzx-scripts/\" \\\n -P \"mzx-scripts/platforms/\" \\\n ./*.sh \\\n mzx-scripts/*.sh \\\n mzx-scripts/platforms/*.sh\n"
  },
  {
    "path": "scripts/buildscripts/mzx-init.sh",
    "content": "#!/bin/sh\n\nexport MZX_SCRIPTS_BASE=\"$(cd \"$(dirname \"$0\")\" || true; pwd)\"\nexport MZX_SCRIPTS=\"${MZX_SCRIPTS_BASE%/}/mzx-scripts\"\nexport MZX_WORKINGDIR=\"${MZX_SCRIPTS_BASE%/}/mzx-workingdir\"\nexport MZX_TARGET_BASE=\"$(pwd)\"\nexport MZX_TARGET=\"${MZX_TARGET_BASE%/}/TARGET\"\n\n. \"$MZX_SCRIPTS/setup.sh\"\n\nsetup_environment windows-x64 windows-x86 nds 3ds wii switch psp android emscripten\n"
  },
  {
    "path": "scripts/buildscripts/mzx-nightly.sh",
    "content": "#!/bin/sh\n\nexport MZX_SCRIPTS_BASE=\"$(cd \"$(dirname \"$0\")\" || true; pwd)\"\nexport MZX_SCRIPTS=\"${MZX_SCRIPTS_BASE%/}/mzx-scripts\"\nexport MZX_WORKINGDIR=\"${MZX_SCRIPTS_BASE%/}/mzx-workingdir\"\nexport MZX_TARGET_BASE=\"$(pwd)\"\nexport MZX_TARGET=\"${MZX_TARGET_BASE%/}/TARGET\"\n\n. \"$MZX_SCRIPTS/version.sh\"\n. \"$MZX_SCRIPTS/build.sh\"\n\n#\n# Script to check if updated debug builds are required. If they are, build them\n# and push updates files for them.\n#\nbuild_init\nif ! build_check_branch_updates \"$UNSTABLE\" \"$DEBYTECODE\"; then exit 0; fi\n\n#\n# Clear out the old release dir so the updates script doesn't process anything\n# that's already uploaded.\n#\nrm -rf \"$MZX_TARGET/releases\"\n\n# Type          Subplatform     Branch          Update Branch           Extra config flags\n# -----------   -------------   -------------   ---------------------   ------------------\nbuild_debug     \"windows-x86\"   \"$UNSTABLE\"     \"$UNSTABLE_UPDATES\"\nbuild_debug     \"windows-x86\"   \"$DEBYTECODE\"   \"$DEBYTECODE_UPDATES\"   \"--enable-debytecode\"\nbuild_debug     \"windows-x64\"   \"$UNSTABLE\"     \"$UNSTABLE_UPDATES\"\nbuild_debug     \"windows-x64\"   \"$DEBYTECODE\"   \"$DEBYTECODE_UPDATES\"   \"--enable-debytecode\"\n\ncd \"$MZX_SCRIPTS_BASE\" || exit 1\n\n./mzx-updates.sh || exit 1\n./mzx-upload.sh || exit 1\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/build.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n. \"$MZX_SCRIPTS/common.sh\"\n. \"$MZX_SCRIPTS/caverns.sh\"\n. \"$MZX_SCRIPTS/crlf.sh\"\n\nOLD_PATH=\"$PATH\"\n\nbuild_init()\n{\n\t#\n\t# Check if the current working directory is the MZX repo.\n\t# Otherwise, use the repo that (should) exist in the working directory.\n\t#\n\tif [ -f \"config.sh\" ]; then\n\t\tMZX_BUILD_DIR=\"$(pwd)\"\n\telse\n\t\tMZX_BUILD_DIR=\"$MZX_WORKINGDIR/megazeux\"\n\n\t\t[ -d \"$MZX_BUILD_DIR\" ] || { mzx_log \"Couldn't find MegaZeux repository dir!\"; exit 1; }\n\t\tcd \"$MZX_BUILD_DIR\" || { mzx_log \"failed to cd to build dir!\"; exit 1; }\n\t\t[ -f \"config.sh\" ] || { mzx_log \"Couldn't find config.sh!\"; exit 1; }\n\t\tMZX_BUILD_DIR=\"$(pwd)\"\n\tfi\n\tmzx_log \"Using MegaZeux repository at $MZX_BUILD_DIR for builds\"\n\texport MZX_BUILD_DIR\n\n\t#\n\t# Make sure the target dir exists.\n\t#\n\tmkdir -p \"$MZX_TARGET\"\n\n\t#\n\t# Reset the git repository and make sure all branches/tags are up-to-date.\n\t#\n\tcd \"$MZX_BUILD_DIR\" || { mzx_log \"failed to cd to build dir!\"; exit 1; }\n\tgit reset --hard\n\tgit fetch\n\tgit fetch --tags\n}\n\n#\n# Perform a check to see if git branch(es) have updates. Use after build_init.\n# Returns 0 if there are updates or 1 if there are no updates.\n#\n# $@ - branches to check\n#\nbuild_check_branch_updates()\n{\n\tif [ -z \"$MZX_BUILD_DIR\" ] || [ ! -d \"$MZX_BUILD_DIR\" ]\n\tthen\n\t\tmzx_error \"Use after build_init!\" 1\n\t\texit 1\n\tfi\n  cd \"$MZX_BUILD_DIR\" || { mzx_error \"failed to cd to build dir!\" 2; exit 1; }\n\n  RETVAL=1\n  while [ -n \"$1\" ]\n  do\n    git checkout \"$1\"\n    shift\n\n    CURRENT=$(git rev-parse \"@\")\n    REMOTE=$(git rev-parse \"@{u}\")\n\n    if [ \"$CURRENT\" = \"$REMOTE\" ]; then\n      mzx_log \"No updates required for branch '$1'.\"\n    else\n      mzx_log \"Branch '$1' has updates.\"\n      RETVAL=0\n    fi\n  done\n  return $RETVAL\n}\n\nbuild_remove_debug()\n{\n\t#\n\t# Clear the debug files out of the updates dir (if applicable).\n\t#\n\tif [ -d \"build/$SUBPLATFORM/\" ]; then\n\t\tfind \"build/$SUBPLATFORM/\" -type f -name '*.debug' -delete\n\tfi\n\n\t#\n\t# Clear the debug files out of the release archive and put them in their own archive.\n\t#\n\tcd \"build/dist/$SUBPLATFORM/\" || { mzx_error \"failed to cd to build/dist/$SUBPLATFORM\" 14; return; }\n\n\tFILES=$(find . -name \"*.zip\")\n\tfor SRC in $FILES\n\tdo\n\t\t# NOTE: 7za uses its own internal globs, hence the quotes.\n\t\tif [ -n \"$SRC\" ] && 7za l \"$SRC\" \"*.debug\" -r | grep -q \".debug\"\n\t\tthen\n\t\t\tDEST=$(echo \"$SRC\" | sed \"s/\\.zip\\$/\\.debug\\.zip/g\")\n\t\t\t7za e \"$SRC\"        \"*.debug\" -r\n\t\t\t7za d \"$SRC\"        \"*.debug\" -r\n\t\t\t7za a -tzip \"$DEST\" ./*.debug\n\t\t\trm ./*.debug\n\t\tfi\n\tdone\n\tcd \"$MZX_BUILD_DIR\" || { mzx_error \"failed to cd to build dir\" 15; return; }\n}\n\n# $1 subplatform\n# $2 tag or branch\n# $3 update branch or \"\"\n# $4 extra configuration flags or \"\"\n# $5 \"release\" or \"debug\"\nbuild_common()\n{\n\texport SUBPLATFORM=$1\n\tMZX_GIT_BRANCH=$2\n\tMZX_UPDATE_BRANCH=$3\n\tMZX_EXTRA_CONFIG_FLAGS=$4\n\tMZX_RELEASE_TYPE=$5\n\t[ -n \"$SUBPLATFORM\" ] || { mzx_error \"no SUBPLATFORM defined\" 1; return; }\n\t[ -n \"$MZX_GIT_BRANCH\" ] || { mzx_error \"no git branch/tag provided\" 2; return; }\n\t[ -f \"$MZX_SCRIPTS/platforms/$SUBPLATFORM.sh\" ] ||\\\n\t { mzx_error \"couldn't find $MZX_SCRIPTS/platforms/$SUBPLATFORM.sh\" 3; return; }\n\n\t# This may be set from a previous build...\n\tunset SDL_PREFIX\n\n\texport PATH=\"$OLD_PATH\"\n\t. \"$MZX_SCRIPTS/platforms/default.sh\"\n\t. \"$MZX_SCRIPTS/platforms/$SUBPLATFORM.sh\"\n\n\texport ERRNO=0\n\texport IS_HOST=\"false\"\n\texport PLATFORM_CRLF=\"\"\n\texport PLATFORM_CAVERNS_EXEC=\"\"\n\texport PLATFORM_CAVERNS_BASE=\"\"\n\texport PLATFORM_CAVERNS_WHICH=\"\"\n\n\tmzx_log \"\"\n\tmzx_log \"Starting build:\"\n\tmzx_log \"  Platform:      $SUBPLATFORM\"\n\tmzx_log \"  Type:          $MZX_RELEASE_TYPE\"\n\tmzx_log \"  Branch/tag:    $MZX_GIT_BRANCH\"\n\tmzx_log \"  Update branch: $MZX_UPDATE_BRANCH\"\n\tmzx_log \"  Options:       $MZX_EXTRA_CONFIG_FLAGS\"\n\n\tcd \"$MZX_BUILD_DIR\" || { mzx_error \"failed to cd to build dir\" 4; return; }\n\n\t#\n\t# Initialize platform-dependent variables and perform platform-dependent\n\t# checks to make sure MegaZeux will be able to build.\n\t#\n\tplatform_init\n\t[ \"$ERRNO\" = \"0\" ] || { mzx_warn \"failed to initialize platform; skipping\" $ERRNO; return; }\n\n\t#\n\t# Check out the requested branch.\n\t#\n\tgit reset --hard\n\tgit checkout \"$MZX_GIT_BRANCH\"\n\tgit merge\n\t$MZX_MAKE distclean\n\trm -rf build\n\n\t#\n\t# Run config.sh.\n\t#\n\tif [ \"$MZX_RELEASE_TYPE\" = \"release\" ]; then\n\t\tplatform_config_release \"$MZX_EXTRA_CONFIG_FLAGS\"\n\telse\n\t\tplatform_config_debug \"$MZX_EXTRA_CONFIG_FLAGS\"\n\tfi\n\t[ \"$ERRNO\" = \"0\" ] || { mzx_error \"failed to configure for $MZX_RELEASE_TYPE\" $ERRNO; return; }\n\t[ -e \"platform.inc\" ] || { mzx_error \"config.sh failed to create platform.inc for $MZX_RELEASE_TYPE\" 10; return; }\n\n\t#\n\t# Build MegaZeux.\n\t#\n\tplatform_make\n\t[ \"$ERRNO\" = \"0\" ] || { mzx_error \"make failed\" $ERRNO; return; }\n\n\t#\n\t# Perform make test (if applicable).\n\t# If this fails it's not a fatal error but it definitely should be looked at.\n\t#\n\tplatform_make_test\n\t[ \"$ERRNO\" = \"0\" ] || { mzx_warn \"make test failed\" $ERRNO; ERRNO=0; }\n\n\t#\n\t# Perform any other checks on the build that may be necessary.\n\t#\n\tplatform_check_build\n\t[ \"$ERRNO\" = \"0\" ] || { mzx_error \"build check failed\" $ERRNO; return; }\n\n\t#\n\t# Perform LF->CRLF conversion for text files if requested by this platform.\n\t# This needs to be done before packaging so manifest.sh can compute the\n\t# correct SHA-256 sums.\n\t#\n\tif [ -n \"$PLATFORM_CRLF\" ]; then\n\t\tcrlf_convert_repository\n\tfi\n\n\t#\n\t# Package the build.\n\t# This will generally produce a build/SUBPLATFORM/ folder containing the\n\t# build and a build/dist/SUBPLATFORM/ folder containing the release archive.\n\t# Only check for the build/dist/SUBPLATFORM/ right now.\n\t#\n\tplatform_package\n\t[ \"$ERRNO\" = \"0\" ] || { mzx_error \"package failed\" $ERRNO; return; }\n\t[ -d \"build/dist/$SUBPLATFORM\" ] || { mzx_error \"couldn't find build/dist/$SUBPLATFORM/\" 11; return; }\n\n\t#\n\t# If this platform wants to package games in its release archive, add it now.\n\t# Skip this for testing builds since it's just bloat.\n\t#\n\tif [ \"$MZX_RELEASE_TYPE\" = \"release\" ] && [ -n \"$PLATFORM_CAVERNS_EXEC\" ]; then\n\t\tbuild_package_caverns\n\t\t[ \"$ERRNO\" = \"0\" ] || { mzx_warn \"build_package_caverns failed\", $ERRNO; }\n\tfi\n\n\t#\n\t# If this is a release build, separate the debug symbols to their own archive\n\t# and remove them from the updates directory (if applicable).\n\t#\n\tif [ \"$MZX_RELEASE_TYPE\" = \"release\" ]; then\n\t\tbuild_remove_debug\n\tfi\n\n\t#\n\t# All files in build/dist/SUBPLATFORM/ (there may be multiple depending on the\n\t# platform or if there is a .debug zip) should be moved to the $MZX_TARGET/zips/ folder.\n\t#\n\tZIP_DIR=\"$MZX_GIT_BRANCH\"\n\tif [ \"$MZX_GIT_BRANCH\" = \"master\" ] && [ \"$MZX_UPDATE_BRANCH\" != \"\" ]; then\n\t\tZIP_DIR=\"$MZX_UPDATE_BRANCH\"\n\tfi\n\tmkdir -p \"$MZX_TARGET/zips/$ZIP_DIR/\"\n\tmv build/dist/\"$SUBPLATFORM\"/* \"$MZX_TARGET/zips/$ZIP_DIR/\"\n\n\t#\n\t# The build/SUBPLATFORM/ folder should be moved to the $MZX_TARGET/releases/ folder\n\t# if the platform has requested that updates be generated from this build. If\n\t# updates were requested, there additionally should be a file called \"manifest.txt\"\n\t# in the build path.\n\t#\n\tif [ -n \"$MZX_UPDATE_BRANCH\" ]; then\n\t\tif [ ! -d \"build/$SUBPLATFORM\" ]; then\n\t\t\tmzx_error \"couldn't find build/$SUBPLATFORM/\" 12\n\t\t\treturn\n\t\tfi\n\t\tif ! find \"build/$SUBPLATFORM\" -name \"manifest.txt\" | grep -q \"manifest.txt\"; then\n\t\t\tmzx_error \"couldn't find manifest.txt for $MZX_UPDATE_BRANCH/$SUBPLATFORM\" 13\n\t\t\treturn\n\t\tfi\n\t\tmkdir -p \"$MZX_TARGET/releases/$MZX_UPDATE_BRANCH/\"\n\t\trm -rf \"$MZX_TARGET/releases/$MZX_UPDATE_BRANCH/$SUBPLATFORM/\"\n\t\tmv \"build/$SUBPLATFORM/\" \"$MZX_TARGET/releases/$MZX_UPDATE_BRANCH/\"\n\tfi\n\n\t#\n\t# Clean the working tree.\n\t#\n\t$MZX_MAKE distclean\n}\n\n# $1 subplatform\n# $2 tag or branch\n# $3 update branch (optional)\n# $4 extra configuration flags (optional)\nbuild_release()\n{\n\tbuild_common \"$1\" \"$2\" \"$3\" \"$4\" \"release\"\n}\n\n# $1 subplatform\n# $2 tag or branch\n# $3 update branch (optional)\n# $4 extra configuration flags (optional)\nbuild_debug()\n{\n\tbuild_common \"$1\" \"$2\" \"$3\" \"$4\" \"debug\"\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/caverns.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n#\n# Functions for packaging Caverns of Zeux into a build archive.\n#\n\nCAVERNS_URL=\"https://www.digitalmzx.com/download/182/26f59999fc55953e81a66cf01d0207fe3b124387a4a4c9288a155f6a44134641/\"\nCAVERNS_BASE=\"$MZX_WORKINGDIR/caverns\"\n\nCAVERNS_PATH=\"caverns\"\nCAVERNS_MMUTIL_PATH=\"caverns_mmutil\"\n\n. \"$MZX_SCRIPTS/common.sh\"\n. \"$MZX_SCRIPTS/common-dkp.sh\"\n\ncaverns_init()\n{\n\tcmd_check 7za wget\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tmkdir -p \"$CAVERNS_BASE\"\n\n\tif [ ! -f \"$CAVERNS_BASE/caverns.zip\" ]; then\n\t\twget \"$CAVERNS_URL\" -O \"$CAVERNS_BASE/caverns.zip\"\n\tfi\n\n\tif [ ! -d \"$CAVERNS_BASE/$CAVERNS_PATH\" ]; then\n\t\t7za e \"$CAVERNS_BASE/caverns.zip\" \"-o$CAVERNS_BASE/$CAVERNS_PATH\"\n\tfi\n\n\tdkp_dependency_check mmutil\n\tif [ \"$ERRNO\" = \"0\" ]; then\n\t\tif [ ! -d \"$CAVERNS_BASE/$CAVERNS_MMUTIL_PATH\" ]; then\n\t\t\t7za e \"$CAVERNS_BASE/caverns.zip\" \"-o$CAVERNS_BASE/$CAVERNS_MMUTIL_PATH\"\n\t\t\tOLD_DIR=\"$(pwd)\"\n\t\t\tcd \"$CAVERNS_BASE/$CAVERNS_MMUTIL_PATH\" || { ERRNO=\"CV-1\"; return; }\n\t\t\tfor f in ./*.MOD; do\n\t\t\t\t\"$DEVKITPRO/tools/bin/mmutil\" -d -m \"$f\"\n\t\t\tdone\n\t\t\tcd \"$OLD_DIR\" || { ERRNO=\"CV-2\"; return; }\n\t\tfi\n\tfi\n\tERRNO=\"0\"\n}\n\ncaverns_platform_init()\n{\n\t# A pretty convoluted directory structure is required to get 7za to\n\t# put anything where we want it to go.\n\n\tCAVERNS_SETUP=$CAVERNS_PATH\n\tif [ -n \"$PLATFORM_CAVERNS_WHICH\" ]; then\n\t\tCAVERNS_SETUP=$PLATFORM_CAVERNS_WHICH\n\tfi\n\n\trm -rf \"${CAVERNS_BASE:?}/$SUBPLATFORM\"\n\tmkdir -p \"$CAVERNS_BASE/$SUBPLATFORM/$PLATFORM_CAVERNS_BASE\"\n\tcp -r \"$CAVERNS_BASE/$CAVERNS_SETUP/\"* \"$CAVERNS_BASE/$SUBPLATFORM/$PLATFORM_CAVERNS_BASE\"\n}\n\n# $1 - archive to check\n# $2 - filename to check for\ncaverns_check_archive()\n{\n\t7za l \"$1\" \"$2\" -r | grep -q \"$2\"\n\treturn $?\n}\n\n# $1 - archive to check\ncaverns_add()\n{\n\t# 7za actually requires changing to the directory for the copy to not\n\t# include the containing directory. No, really.\n\n\tOLD_DIR=\"$(pwd)\"\n\tcd \"$CAVERNS_BASE/$SUBPLATFORM\" || { ERRNO=\"CV-1\"; return; }\n\n\t7za a \"$1\" ./* -y -bsp0 -bso0\n\n\tcd \"$OLD_DIR\" || { ERRNO=\"CV-2\"; return; }\n}\n\nbuild_package_caverns()\n{\n\tcaverns_init\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tcaverns_platform_init\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tfor f in \"$MZX_BUILD_DIR/build/dist/$SUBPLATFORM\"/*.zip\n\tdo\n\t\tif caverns_check_archive \"$f\" \"$PLATFORM_CAVERNS_EXEC\"\n\t\tthen\n\t\t\tmzx_log \"Found '$PLATFORM_CAVERNS_EXEC' in '$f'; adding Caverns of Zeux at '$PLATFORM_CAVERNS_BASE'\"\n\t\t\tcaverns_add \"$f\"\n\t\t\t[ \"$ERRNO\" = \"0\" ] || { mzx_error \"failed to add caverns to '$f'\" $ERRNO; }\n\t\tfi\n\tdone\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/common-dkp.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n#\n# Common functions for platforms using devkitPro toolchains.\n#\n\ndkp_init_check()\n{\n\tif [ -z \"$DEVKITPRO\" ]; then\n\t\tERRNO=\"DK-1\"\n\t\treturn\n\tfi\n}\n\ndkp_pacman_check()\n{\n\tdkp_init_check\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tif [ -z \"$DKP_PACMAN\" ]; then\n\t\tif command -v pacman >/dev/null 2>&1; then\n\t\t\texport DKP_PACMAN=\"pacman\"\n\t\tfi\n\n\t\tif command -v dkp-pacman >/dev/null 2>&1; then\n\t\t\texport DKP_PACMAN=\"dkp-pacman\"\n\t\tfi\n\n\t\tif [ -z \"$DKP_PACMAN\" ]; then\n\t\t\tERRNO=\"DK-2\"\n\t\t\treturn\n\t\tfi\n\tfi\n}\n\n# $1 - package to check for\ndkp_dependency_check()\n{\n\tdkp_pacman_check\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tif ! $DKP_PACMAN -Qi \"$1\" >/dev/null 2>&1; then\n\t\tERRNO=\"DK-3:$1\"\n\t\treturn\n\tfi\n}\n\n# $@ - packages to install\ndkp_install()\n{\n\tdkp_pacman_check\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\t$DKP_PACMAN --needed --noconfirm -S \"$@\"\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/common-mingw.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n#\n# Common functions for MinGW.\n#\n\nMZX_SDL_MINGW_VERSION=\"1.2.15\"\nMZX_SDL_MINGW_FILENAME=\"SDL-devel-$MZX_SDL_MINGW_VERSION-mingw32.tar.gz\"\nMZX_SDL2_MINGW_VERSION=\"2.0.12\"\nMZX_SDL2_MINGW_FILENAME=\"SDL2-devel-$MZX_SDL2_MINGW_VERSION-mingw.tar.gz\"\n\n# $1 - toolchain prefix\n# $2 - dependencies path\nmingw_check()\n{\n\t#\n\t# Check for the MinGW toolchain.\n\t#\n\tcmd_check \"$1gcc\" \"$1g++\" \"$1ar\" \"$1ld\" \"$1strip\" \"$1objcopy\" \"$1windres\"\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\t#\n\t# Check for MinGW prebuilt dependencies.\n\t#\n\tMISSING=\"\"\n\tfor DEP in \"bin/SDL2.dll\" \"bin/sdl2-config\" \"bin/libpng-config\" \"lib/libSDL2main.a\" \\\n\t \"lib/libz.a\" \"lib/libpng16.a\" \"lib/libogg.a\" \"lib/libvorbis.a\" \"lib/libvorbisfile.a\"\n\tdo\n\t\tif [ ! -e \"$2/$DEP\" ]; then\n\t\t\tMISSING=\"$MISSING:$DEP\"\n\t\tfi\n\tdone\n\tif [ -n \"$MISSING\" ]; then\n\t\tERRNO=\"MINGW-DEPS-MISSING$MISSING\";\n\tfi\n}\n\nmingw32_check()\n{\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\tmingw_check \"\" \"/mingw32\"\n\telse\n\t\tmingw_check \"i686-w64-mingw32-\" \"$MINGW32_PREFIX\"\n\tfi\n}\n\nmingw64_check()\n{\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\tmingw_check \"\" \"/mingw64\"\n\telse\n\t\tmingw_check \"x86_64-w64-mingw32-\" \"$MINGW64_PREFIX\"\n\tfi\n}\n\nmingw_setup_environment()\n{\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\t#\n\t\t# Can just install the toolchains and packages for both builds...\n\t\t#\n\t\tM32=\"mingw-w64-i686\"\n\t\tM64=\"mingw-w64-x86_64\"\n\t\tpacman --needed --noconfirm -S \\\n\t\t $M32-gcc $M32-zlib $M32-libpng $M32-libogg $M32-libvorbis $M32-SDL2 \\\n\t\t $M64-gcc $M64-zlib $M64-libpng $M64-libogg $M64-libvorbis $M64-SDL2\n\n\t\t#\n\t\t# ...except SDL. The MSYS2 SDL builds unfortunately have some issues that\n\t\t# make them unsuitable for release builds. Just grab the official MinGW SDL\n\t\t# packages and extract them; the environment variable SDL_PREFIX will be set\n\t\t# during the MinGW builds to make sure the correct SDL gets used instead.\n\t\t#\n\t\tif [ ! -f \"$MZX_WORKINGDIR/$MZX_SDL_MINGW_FILENAME\" ]; then\n\t\t\tcd \"$MZX_WORKINGDIR\" || { ERRNO=\"MINGW-CD\"; return; }\n\t\t\twget \"https://www.libsdl.org/release/$MZX_SDL_MINGW_FILENAME\"\n\t\t\ttar -xzf \"$MZX_SDL_MINGW_FILENAME\"\n\n\t\t\trm -rf \"sdl1-mingw\"\n\t\t\tmv \"SDL-$MZX_SDL_MINGW_VERSION\" \"sdl1-mingw\"\n\n\t\tfi\n\n\t\tif [ ! -f \"$MZX_WORKINGDIR/$MZX_SDL2_MINGW_FILENAME\" ]; then\n\t\t\tcd \"$MZX_WORKINGDIR\" || { ERRNO=\"MINGW-CD\"; return; }\n\t\t\twget \"https://www.libsdl.org/release/$MZX_SDL2_MINGW_FILENAME\"\n\t\t\ttar -xzf \"$MZX_SDL2_MINGW_FILENAME\"\n\n\t\t\trm -rf \"sdl2-mingw\"\n\t\t\tmv \"SDL2-$MZX_SDL2_MINGW_VERSION\" \"sdl2-mingw\"\n\t\tfi\n\tfi\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/common.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n#\n# Common checks and defines for the buildscripts.\n#\n\nif git ls-files --error-unmatch \"$0\" \"$MZX_SCRIPTS\" >/dev/null 2>&1; then\n\techo \"ERROR: attempting to run this script from 'scripts/'.\"\n\techo \"The scripts directory is tracked by Git and may be modified when\"\n\techo \"checking out different tags/branches for MZX builds.\"\n\techo \"\"\n\techo \"You should duplicate 'scripts/buildscripts/' to another directory and\"\n\techo \"run this script from that directory instead.\"\n\texit 1\nfi\n\nif [ ! -w \"$MZX_SCRIPTS\" ]; then\n\techo \"ERROR: current user lacks write permissions for '$MZX_SCRIPTS'.\"\n\texit 1\nfi\n\nif [ ! -w \"$(dirname \"$MZX_TARGET\")\" ]; then\n\techo \"ERROR: current user lacks write permissions for '$(dirname \"$MZX_TARGET\")'.\"\n\texit 1\nfi\n\n#\n# Determine whether to use gmake or make.\n# Both MZX_MAKE and MZX_MAKE_PARALLEL can be overriden by the user.\n#\nif [ -z \"$MZX_MAKE\" ]; then\n\tif command -v gmake >/dev/null 2>&1; then\n\t\texport MZX_MAKE=\"gmake\"\n\telse\n\t\texport MZX_MAKE=\"make\"\n\tfi\nfi\nif [ -z \"$MZX_MAKE_PARALLEL\" ]; then\n\texport MZX_MAKE_PARALLEL=\"$MZX_MAKE -j8\"\nfi\n\nmzx_log()\n{\n\techo \"$1\" 1>&2\n}\n\nmzx_warn()\n{\n\techo \"WARN:\" \"$1\" \"(code $2)\" 1>&2\n}\n\nmzx_error()\n{\n\techo \"ERROR:\" \"$1\" \"(code $2)\" 1>&2\n}\n\n# $@ commands to check for (e.g. \"7za\")\ncmd_check()\n{\n\tMSG=\"\"\n\twhile [ -n \"$1\" ]; do\n\t\tif ! command -v \"$1\" >/dev/null 2>&1; then\n\t\t\tMSG=\"$MSG$1;\"\n\t\tfi\n\t\tshift\n\tdone\n\tif [ -n \"$MSG\" ]; then\n\t\tERRNO=\"CMD:$MSG\"\n\tfi\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/crlf.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n. \"$MZX_SCRIPTS/common.sh\"\n\nCARRIAGE_RETURN=$(printf '\\r')\n\n#\n# Replace LF with CRLF in a POSIX-compatible way. Since the POSIX versions of\n# sed and numerous other utilities don't support \\r, it needs to be inserted via\n# the printf variable above. Using ERE allows this method to insert \\r only\n# when a \\n is found without a \\r, meaning theoretically this should be safe to\n# use on files that already use CRLF. This is still a hack though and unix2dos\n# (below) should be preferred over this.\n#\n# $1 - file to convert.\n#\ncrlf_convert_posix()\n{\n\tmv \"$1\" \"$1~\" || { return 1; }\n\tsed -E \"s/(^|[^$CARRIAGE_RETURN])$/\\1$CARRIAGE_RETURN/\" \"$1~\" > \"$1\" || { return 1; }\n\trm \"$1~\"\n}\n\n#\n# unix2dos isn't a standard POSIX utility but it's fairly common. This likely\n# does a better job than the hack above, so generally it should be used instead.\n#\n# $1 - file to convert.\n#\ncrlf_convert_unix2dos()\n{\n\tunix2dos \"$1\" >/dev/null 2>&1 || { return 1; }\n}\n\n#\n# Convert certain LF files to CRLF within the repository.\n# This generally includes anything users are expected to read directly or to\n# modify in Windows and in any platforms that will generally be configured from\n# Windows by most users (e.g. consoles, HTML5).\n#\n# This needs to be done before \"make build\"/\"make archive\"/etc. because on\n# platforms with updates, those Makefile rules will generate manifest.txt.\n#\n# Note that this isn't necessary the other way around. The source tarball has\n# any CRLFs added by a Windows host removed in \"make source\" (via git's autocrlf\n# setting). Other packages that should not use CRLF (Debian, Fedora, etc.)\n# are generally created using their respective platforms as the host.\n#\ncrlf_convert_repository()\n{\n\t#\n\t# This is pretty pointless in MSYS2; you should enable git autocrlf instead.\n\t#\n\t[ -z \"$MSYSTEM\" ] || { return; }\n\n\tFILES=$(find config.txt LICENSE* arch/LICENSE* assets docs arch/mingw arch/*/pad.config* arch/emscripten/web/res/index.html \\\n\t\t-name \"*.txt\" -o \\\n\t\t-name \"*.frag\" -o \\\n\t\t-name \"*.vert\" -o \\\n\t\t-name \"*.bat\" -o \\\n\t\t-name \"index.html\" -o \\\n\t\t-name \"*LICENSE*\" -o \\\n\t\t-name \"pad.config*\")\n\n\tif command -v unix2dos >/dev/null\n\tthen\n\t\tCONVERT_FUNCTION=\"crlf_convert_unix2dos\"\n\telse\n\t\tCONVERT_FUNCTION=\"crlf_convert_posix\"\n\tfi\n\n\tfor F in $FILES\n\tdo\n\t\tif \"$CONVERT_FUNCTION\" \"$F\"\n\t\tthen\n\t\t\tmzx_log \"LF->CRLF '$F'\"\n\t\telse\n\t\t\tmzx_warn \"LF->CRLF '$F' failed\" 1310\n\t\tfi\n\tdone\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/patches/pspge.patch",
    "content": "16a17,18\n> #include <psptypes.h>\n> \n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/3ds.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n. \"$MZX_SCRIPTS/common-dkp.sh\"\n\nplatform_init()\n{\n\tdkp_init_check\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tdkp_dependency_check devkitARM\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\texport DEVKITPRO=\"$(cygpath -u \"$DEVKITPRO\")\"\n\tfi\n\n\texport PATH=\"$PATH:$DEVKITPRO/devkitARM/bin\"\n\texport PATH=\"$PATH:$DEVKITPRO/tools/bin\"\n\n\texport PLATFORM_CAVERNS_EXEC=\"mzxrun.3dsx\"\n\texport PLATFORM_CAVERNS_BASE=\"3ds/megazeux\"\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_release()\n{\n\tarch/3ds/CONFIG.3DS \"$@\"\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun\" ] || ERRNO=25;\n}\n\nplatform_setup_environment()\n{\n\tdkp_install devkitARM libctru citro3d picasso 3dstools general-tools\n\tdkp_install 3ds-zlib 3ds-libpng 3ds-libogg 3ds-libvorbisidec\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/android.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\nJNI_LIB_DIR=arch/android/project/app/jni/lib\nJNI_LIB_SO=libmain.so\n\n. \"$MZX_SCRIPTS/common.sh\"\n\nplatform_init()\n{\n\t[ -z \"$NDK_PATH\" ] && { ERRNO=20; return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\texport NDK_PATH=\"$(cygpath -u \"$NDK_PATH\")\"\n\tfi\n}\n\nplatform_config_release()\n{\n\tarch/android/CONFIG.ANDROID \"$@\"\n}\n\nplatform_make()\n{\n\t$MZX_MAKE_PARALLEL dist || ERRNO=23;\n}\n\nplatform_check_build()\n{\n\t[ -f \"$JNI_LIB_DIR/armeabi-v7a/$JNI_LIB_SO\" ] || ERRNO=24;\n\t[ -f \"$JNI_LIB_DIR/arm64-v8a/$JNI_LIB_SO\" ] || ERRNO=24;\n\t[ -f \"$JNI_LIB_DIR/x86/$JNI_LIB_SO\" ] || ERRNO=24;\n\t[ -f \"$JNI_LIB_DIR/x86_64/$JNI_LIB_SO\" ] || ERRNO=24;\n}\n\nplatform_package()\n{\n\t$MZX_MAKE apk || ERRNO=25;\n}\n\nplatform_setup_environment()\n{\n\tplatform_init\n\t[ \"$ERRNO\" -eq \"0\" ] || { return; }\n\n\tarch/android/CONFIG.ANDROID\n\t$MZX_MAKE_PARALLEL deps-install || ERRNO=26;\n\n\t$MZX_MAKE distclean\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/default.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\nplatform_init()\n{\n\t# STUB - IMPLEMENT FOR THIS PLATFORM\n\t#\n\t# Should perform any platform-dependent checks\n\t# required to make sure this platform will build.\n\t# Any missing dependency should error here (this\n\t# should not attempt to install the dependencies).\n\tERRNO=20\n}\n\nplatform_config_debug()\n{\n\t# STUB - IMPLEMENT FOR THIS PLATFORM\n\tERRNO=21\n}\n\nplatform_config_release()\n{\n\t# STUB - IMPLEMENT FOR THIS PLATFORM\n\tERRNO=22\n}\n\nplatform_make()\n{\n\tif [ \"$MZX_RELEASE_TYPE\" = \"release\" ]; then\n\t\t$MZX_MAKE_PARALLEL debuglink || ERRNO=23;\n\telse\n\t\t$MZX_MAKE_PARALLEL || ERRNO=23;\n\tfi\n}\n\nplatform_make_test()\n{\n\tif [ \"$IS_HOST\" = \"true\" ]; then\n\t\t$MZX_MAKE_PARALLEL test || ERRNO=24;\n\tfi\n}\n\nplatform_check_build()\n{\n\t# STUB - IMPLEMENT FOR THIS PLATFORM\n\tERRNO=25;\n}\n\nplatform_package()\n{\n\t$MZX_MAKE archive || ERRNO=26;\n}\n\nplatform_setup_environment()\n{\n\t# STUB - IMPLEMENT FOR THIS PLATFORM\n\t#\n\t# Initialize any dependencies and the environment for this\n\t# platform. This may use any of the above hooks as-needed.\n\t# If missing dependencies are trivial to fetch this may\n\t# do so, but note that e.g. devkitPro hates having their\n\t# package servers used in automated CI build systems\n\t# and would prefer that you use Docker containers or a\n\t# preconfigured environment for that sort of thing. In other\n\t# words, use this to set up your environment once, but don't\n\t# repeatedly use it in automated systems.\n\tERRNO=26\n}\n\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/djgpp.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\nplatform_init()\n{\n\t[ -z \"$DJGPP\" ] && { ERRNO=20; return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\texport DJGPP=\"$(cygpath -u \"$DJGPP\")\"\n\tfi\n\n\texport PATH=\"$PATH:$DJGPP/bin\"\n\n\texport PLATFORM_CAVERNS_EXEC=\"mzxrun.exe\"\n\texport PLATFORM_CAVERNS_BASE=\".\"\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_release()\n{\n\tarch/djgpp/CONFIG.DJGPP \"$@\"\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun.exe\" ] || ERRNO=25;\n\t[ -f \"megazeux.exe\" ] || ERRNO=25;\n}\n\nplatform_setup_environment()\n{\n\t# FIXME pretty much involves building all deps manually.\n\t# https://www.digitalmzx.com/wiki/Compiling_MegaZeux#DJGPP\n\tERRNO=26;\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/dreamcast.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2022 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\nplatform_init()\n{\n\t[ -z \"$KOS_BASE\" ] && { ERRNO=20; return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\texport KOS_BASE=\"$(cygpath -u \"$KOS_BASE\")\"\n\tfi\n\n\t. \"$KOS_BASE\"/environ.sh\n\n\texport PLATFORM_CAVERNS_EXEC=\"1ST_READ.BIN\"\n\texport PLATFORM_CAVERNS_BASE=\".\"\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_release()\n{\n\tarch/dreamcast/CONFIG.DC \"$@\"\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun\" ] || ERRNO=25;\n}\n\nplatform_setup_environment()\n{\n\t# KallistiOS pretty much needs to be set up manually, including deps.\n\t# https://www.digitalmzx.com/wiki/Compiling_MegaZeux#Dreamcast\n\tERRNO=26;\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/emscripten.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\nplatform_init()\n{\n\tif ! command -v emcc; then\n\t\tERRNO=20;\n\t\treturn;\n\tfi\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_release()\n{\n\tarch/emscripten/CONFIG.HTML5 \"$@\"\n}\n\n# debuglink breaks for this platform so always use 'all'...\nplatform_make()\n{\n\t$MZX_MAKE_PARALLEL || ERRNO=23;\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun.js\" ] || ERRNO=25;\n\t[ -f \"mzxrun.wasm\" ] || ERRNO=25;\n\t[ -f \"emzip.js\" ] || ERRNO=25;\n\t[ -f \"emzip.wasm\" ] || ERRNO=25;\n}\n\nplatform_setup_environment()\n{\n\t# The Emscripten SDK handles pretty much all of this automatically.\n\tERRNO=0;\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/nds-blocksds.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2025 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\nplatform_init()\n{\n\t[ -z \"$BLOCKSDS\" ] && { ERRNO=20; return; }\n\t[ -z \"$WONDERFUL_TOOLCHAIN\" ] && { ERRNO=20; return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\texport BLOCKDS=\"$(cygpath -u \"$BLOCKSDS\")\"\n\t\texport WONDERFUL_TOOLCHAIN=\"$(cygpath -u \"$WONDERFUL_TOOLCHAIN\")\"\n\tfi\n\n\texport PATH=\"$PATH:$WONDERFUL_TOOLCHAIN/bin\"\n\n\texport PLATFORM_CAVERNS_EXEC=\"mzxrun.nds\"\n\texport PLATFORM_CAVERNS_BASE=\"games/megazeux\"\n\texport PLATFORM_CAVERNS_WHICH=\"caverns_mmutil\"\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_release()\n{\n\tarch/nds-blocksds/CONFIG.NDS \"$@\"\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun\" ] || ERRNO=25;\n}\n\nplatform_setup_environment()\n{\n\treturn\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/nds.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n. \"$MZX_SCRIPTS/common-dkp.sh\"\n\nplatform_init()\n{\n\tdkp_init_check\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tdkp_dependency_check devkitARM mmutil\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\texport DEVKITPRO=\"$(cygpath -u \"$DEVKITPRO\")\"\n\tfi\n\n\texport PATH=\"$PATH:$DEVKITPRO/devkitARM/bin\"\n\texport PATH=\"$PATH:$DEVKITPRO/tools/bin\"\n\n\texport PLATFORM_CAVERNS_EXEC=\"mzxrun.nds\"\n\texport PLATFORM_CAVERNS_BASE=\"games/megazeux\"\n\texport PLATFORM_CAVERNS_WHICH=\"caverns_mmutil\"\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_release()\n{\n\tarch/nds/CONFIG.NDS \"$@\"\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun\" ] || ERRNO=25;\n}\n\nplatform_setup_environment()\n{\n\tdkp_install devkitARM ndstool dstools mmutil\n\tdkp_install libnds libfat-nds maxmod-nds nds-zlib\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/psp.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\nplatform_init()\n{\n\t[ -z \"$PSPDEV\" ] && { ERRNO=20; return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\texport PSPDEV=\"$(cygpath -u \"$PSPDEV\")\"\n\tfi\n\n\texport PATH=\"$PATH:$PSPDEV/bin\"\n\n\texport PLATFORM_CAVERNS_EXEC=\"EBOOT.PBP\"\n\texport PLATFORM_CAVERNS_BASE=\".\"\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_release()\n{\n\tarch/psp/CONFIG.PSP \"$@\"\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun\" ] || ERRNO=25;\n}\n\nplatform_setup_environment()\n{\n\tplatform_init\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\t#\n\t# Can just install these for MSYS2.\n\t# This probably shouldn't be extended to support other systems because this sucks.\n\t#\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\tpacman --needed --noconfirm -S patch mingw-w64-x86_64-imagemagick\n\tfi\n\n\tcmd_check convert\n\n\t#\n\t# The copy of the SDK formerly distributed with the Windows version of\n\t# devkitPro was missing an include. If the include is missing, patch\n\t# it back in.\n\t#\n\tif ! grep -q \"psptypes\" \"$PSPDEV/psp/sdk/include/pspge.h\" ; then\n\t\tpatch \"$PSPDEV/psp/sdk/include/pspge.h\" \"$MZX_SCRIPTS/patches/pspge.patch\"\n\tfi\n\n\tPSP_MISSING=\"\"\n\n\tif [ ! -f \"$PSPDEV/psp/lib/libz.a\" ]; then\n\t\tPSP_MISSING=\"$PSP_MISSING zlib\"\n\tfi\n\n\tif [ ! -f \"$PSPDEV/psp/lib/libpng.a\" ]; then\n\t\tPSP_MISSING=\"$PSP_MISSING libpng\"\n\tfi\n\n\tif [ ! -f \"$PSPDEV/psp/lib/libvorbisidec.a\" ]; then\n\t\tPSP_MISSING=\"$PSP_MISSING libtremor\"\n\tfi\n\n\tif [ ! -f \"$PSPDEV/psp/lib/libSDL.a\" ]; then\n\t\tPSP_MISSING=\"$PSP_MISSING SDL\"\n\tfi\n\n\tif [ ! -f \"$PSPDEV/psp/lib/libSDL2.a\" ]; then\n\t\tPSP_MISSING=\"$PSP_MISSING SDL2\"\n\tfi\n\n\tif [ ! -f \"$PSPDEV/psp/sdk/lib/libpspirkeyb.a\" ]; then\n\t\tPSP_MISSING=\"$PSP_MISSING pspirkeyb\"\n\tfi\n\n\tif [ ! -f \"$PSPDEV/psp/lib/libGL.a\" ]; then\n\t\tPSP_MISSING=\"$PSP_MISSING pspgl\"\n\tfi\n\n\tif [ -n \"$PSP_MISSING\" ]; then\n\t\techo \"Missing PSP dependencies from the current pspdev install. Use:\"\n\t\techo \"\"\n\t\techo \"  ./libraries.sh\" \"$PSP_MISSING\"\n\t\techo \"\"\n\t\techo \"to install the missing libraries.\"\n\t\tERRNO=26;\n\tfi\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/psvita.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\nplatform_init()\n{\n\t[ -z \"$VITASDK\" ] && { ERRNO=20; return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\texport VITASDK=\"$(cygpath -u \"$VITASDK\")\"\n\tfi\n\n\texport PATH=\"$PATH:$VITASDK/bin\"\n\n\texport PLATFORM_CAVERNS_EXEC=\"megazeux.vpk\"\n\texport PLATFORM_CAVERNS_BASE=\".\"\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_release()\n{\n\tarch/psvita/CONFIG.PSVITA \"$@\"\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun\" ] || ERRNO=25;\n}\n\nplatform_setup_environment()\n{\n\t# FIXME lol\n\tERRNO=26;\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/source.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\nplatform_init()\n{\n\t# Not applicable.\n\tERRNO=0;\n}\n\nplatform_config_release()\n{\n\t# Not applicable.\n\tERRNO=0;\n}\n\nplatform_make()\n{\n\t$MZX_MAKE source || ERRNO=23;\n}\n\nplatform_make_test()\n{\n\t# Not applicable.\n\tERRNO=0;\n}\n\nplatform_check_build()\n{\n\t# Not applicable.\n\tERRNO=0;\n}\n\nplatform_package()\n{\n\t# Not applicable.\n\tERRNO=0;\n}\n\nplatform_setup_environment()\n{\n\t# Not applicable.\n\tERRNO=0;\n}\n\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/switch.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n. \"$MZX_SCRIPTS/common-dkp.sh\"\n\nplatform_init()\n{\n\tdkp_init_check\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tdkp_dependency_check devkitA64\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\texport DEVKITPRO=\"$(cygpath -u \"$DEVKITPRO\")\"\n\tfi\n\n\texport PATH=\"$PATH:$DEVKITPRO/devkitA64/bin\"\n\texport PATH=\"$PATH:$DEVKITPRO/tools/bin\"\n\n\texport PLATFORM_CAVERNS_EXEC=\"megazeux.nro\"\n\texport PLATFORM_CAVERNS_BASE=\"switch/megazeux\"\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_release()\n{\n\tarch/switch/CONFIG.SWITCH \"$@\"\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun.elf\" ] || ERRNO=25;\n\t[ -f \"megazeux.elf\" ] || ERRNO=25;\n}\n\nplatform_setup_environment()\n{\n\tdkp_install devkitA64 libnx switch-tools\n\tdkp_install switch-glad switch-glm switch-mesa switch-libdrm_nouveau\n\tdkp_install switch-zlib switch-libpng switch-libogg switch-libvorbis switch-sdl2\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/wii.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n. \"$MZX_SCRIPTS/common-dkp.sh\"\n\nplatform_init()\n{\n\tdkp_init_check\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tdkp_dependency_check devkitPPC\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\texport DEVKITPRO=\"$(cygpath -u \"$DEVKITPRO\")\"\n\tfi\n\n\texport PATH=\"$PATH:$DEVKITPRO/devkitPPC/bin\"\n\texport PATH=\"$PATH:$DEVKITPRO/tools/bin\"\n\n\texport PLATFORM_CAVERNS_EXEC=\"boot.dol\"\n\texport PLATFORM_CAVERNS_BASE=\"apps/megazeux\"\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_release()\n{\n\tarch/wii/CONFIG.WII \"$@\"\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun\" ] || ERRNO=25;\n\t[ -f \"megazeux\" ] || ERRNO=25;\n}\n\nplatform_setup_environment()\n{\n\tdkp_install devkitPPC libogc libfat-ogc gamecube-tools\n\tdkp_install ppc-zlib ppc-libpng ppc-libogg ppc-libvorbisidec\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/wiiu.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n. \"$MZX_SCRIPTS/common-dkp.sh\"\n\nplatform_init()\n{\n\tdkp_init_check\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tdkp_dependency_check devkitPPC wut\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\texport DEVKITPRO=\"$(cygpath -u \"$DEVKITPRO\")\"\n\tfi\n\n\texport PATH=\"$PATH:$DEVKITPRO/devkitPPC/bin\"\n\texport PATH=\"$PATH:$DEVKITPRO/tools/bin\"\n\n\texport PLATFORM_CAVERNS_EXEC=\"megazeux.rpx\"\n\texport PLATFORM_CAVERNS_BASE=\"wiiu/apps/megazeux\"\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_release()\n{\n\tarch/wiiu/CONFIG.WIIU \"$@\"\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun.elf\" ] || ERRNO=25;\n\t[ -f \"megazeux.elf\" ] || ERRNO=25;\n}\n\nplatform_setup_environment()\n{\n\tdkp_install devkitPPC wut wut-tools\n\tdkp_install ppc-zlib ppc-libpng ppc-libogg ppc-libvorbis wiiu-sdl2\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/windows-x64.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n. \"$MZX_SCRIPTS/common.sh\"\n. \"$MZX_SCRIPTS/common-mingw.sh\"\n\nMINGW64_PLATFORM=\"mingw64\"\nMINGW64_CONFIG=\"--prefix $MINGW64_PREFIX\"\nif [ -n \"$MSYSTEM\" ]; then\n\tMINGW64_PLATFORM=\"win64\"\n\tMINGW64_CONFIG=\"\"\n\tMSYSTEM=\"MINGW64\"\n\t. /etc/profile\n\n\texport SDL_PREFIX=\"$MZX_WORKINGDIR/sdl3-mingw/x86_64-w64-mingw32/\"\n\t[ -d \"$SDL_PREFIX\" ] || { mzx_warn \"Failed to find MinGW SDL dir!\" \"MINGW64-1\"; }\nfi\n\nplatform_init()\n{\n\tmingw64_check\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\tIS_HOST=\"true\"\n\telse\n\t\texport SDL_PREFIX=\"$MINGW64_PREFIX\"\n\tfi\n\n\texport PLATFORM_CAVERNS_EXEC=\"mzxrun.exe\"\n\texport PLATFORM_CAVERNS_BASE=\".\"\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_debug()\n{\n\t./config.sh --platform $MINGW64_PLATFORM $MINGW64_CONFIG --enable-sdl3 --enable-fps --enable-trace \"$@\"\n}\n\nplatform_config_release()\n{\n\t./config.sh --platform $MINGW64_PLATFORM $MINGW64_CONFIG --enable-sdl3 --enable-release --enable-lto \"$@\"\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun.exe\" ] || { ERRNO=25; }\n\t[ -f \"megazeux.exe\" ] || { ERRNO=25; }\n}\n\nplatform_setup_environment()\n{\n\tmingw_setup_environment\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/platforms/windows-x86.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n. \"$MZX_SCRIPTS/common.sh\"\n. \"$MZX_SCRIPTS/common-mingw.sh\"\n\nMINGW32_PLATFORM=\"mingw32\"\nMINGW32_CONFIG=\"--prefix $MINGW32_PREFIX\"\nif [ -n \"$MSYSTEM\" ]; then\n\tMINGW32_PLATFORM=\"win32\"\n\tMINGW32_CONFIG=\"\"\n\tMSYSTEM=\"MINGW32\"\n\t. /etc/profile\n\n\texport SDL_PREFIX=\"$MZX_WORKINGDIR/sdl3-mingw/i686-w64-mingw32/\"\n\t[ -d \"$SDL_PREFIX\" ] || { mzx_warn \"Failed to find MinGW SDL dir!\" \"MINGW32-1\"; }\nfi\n\nplatform_init()\n{\n\tmingw32_check\n\t[ \"$ERRNO\" = \"0\" ] || { return; }\n\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\tIS_HOST=\"true\"\n\telse\n\t\texport SDL_PREFIX=\"$MINGW32_PREFIX\"\n\tfi\n\n\texport PLATFORM_CAVERNS_EXEC=\"mzxrun.exe\"\n\texport PLATFORM_CAVERNS_BASE=\".\"\n\texport PLATFORM_CRLF=1\n}\n\nplatform_config_debug()\n{\n\t./config.sh --platform $MINGW32_PLATFORM $MINGW32_CONFIG --enable-sdl3 --enable-fps --enable-trace \"$@\"\n}\n\nplatform_config_release()\n{\n\t./config.sh --platform $MINGW32_PLATFORM $MINGW32_CONFIG --enable-sdl3 --enable-release --enable-lto \"$@\"\n}\n\nplatform_check_build()\n{\n\t[ -f \"mzxrun.exe\" ] || { ERRNO=25; }\n\t[ -f \"megazeux.exe\" ] || { ERRNO=25; }\n}\n\nplatform_setup_environment()\n{\n\tmingw_setup_environment\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/setup.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\nexport MZX_REPOSITORY=\"https://github.com/AliceLR/megazeux.git\"\n\n. \"$MZX_SCRIPTS/common.sh\"\n\ncommon_setup_environment()\n{\n\tif [ -n \"$MSYSTEM\" ]; then\n\t\tpacman --needed --noconfirm -S git make tar curl p7zip\n\tfi\n\tcmd_check git make tar curl 7za\n\tif [ \"$ERRNO\" != \"0\" ]; then\n\t\tmzx_error \"failed setup; missing required tools\" \"$ERRNO\"\n\t\treturn\n\tfi\n\n\tmkdir -p \"$MZX_WORKINGDIR\"\n\n\t#\n\t# Set up the MegaZeux repository if this isn't being run from one.\n\t#\n\tif [ ! -f \"config.sh\" ]; then\n\t\tif [ ! -d \"$MZX_WORKINGDIR/megazeux\" ]; then\n\t\t\tif ! git clone \"$MZX_REPOSITORY\" \"$MZX_WORKINGDIR/megazeux\"; then\n\t\t\t\tERRNO=\"GIT-1\"\n\t\t\t\tmzx_error \"failed to create MZX repository\" \"$ERRNO\"\n\t\t\t\treturn\n\t\t\tfi\n\t\tfi\n\tfi\n}\n\n# $@ - platforms to initialize.\nsetup_environment()\n{\n\texport ERRNO=0\n\tcommon_setup_environment\n\t[ \"$ERRNO\" = \"0\" ] || { exit 1; }\n\n\twhile [ -n \"$1\" ]; do\n\t\tmzx_log \"Performing setup for: $1\"\n\t\tif [ -f \"$MZX_SCRIPTS/platforms/$1.sh\" ]; then\n\n\t\t\t. \"$MZX_SCRIPTS/platforms/default.sh\"\n\t\t\t. \"$MZX_SCRIPTS/platforms/$1.sh\"\n\n\t\t\texport ERRNO=0\n\t\t\tplatform_setup_environment\n\t\t\tif [ ! \"$ERRNO\" = \"0\" ]; then\n\t\t\t\tmzx_error \"failed $1\" \"$ERRNO\"\n\t\t\tfi\n\t\tfi\n\t\tshift\n\tdone\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/updates.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n. \"$MZX_SCRIPTS/common.sh\"\n\n#\n# Convert the current working directory from release dir to an updater dir.\n#\nprocess_release_dir()\n{\n\t#\n\t# Run some checks to make sure this isn't something other than a\n\t# build directory, because this will totally nuke the directory\n\t# it is run in.\n\t#\n\t# Make sure there's no Makefile or config.sh or anything else\n\t# that should only appear in a development directory.\n\t#\n\tif [ -f config.sh ] || [ -f Makefile ] || [ -d src ]\n\tthen\n\t\tmzx_error \"this appears to be a source dir. Aborting.\" 3\n\t\texit 1\n\tfi\n\n\t#\n\t# There needs to be a manifest.txt file for this to be a valid update dir.\n\t#\n\tif [ ! -f manifest.txt ]\n\tthen\n\t\tmzx_error \"no manifest.txt found in '$(pwd)'. Aborting.\" 4\n\t\texit 1\n\tfi\n\n\t#\n\t# Compress everything to its sha256sum.\n\t#\n\tFILES=$(find . -mindepth 1 -type f -not -name manifest.txt -not -name config.txt)\n\tfor file in $FILES\n\tdo\n\t\t#\n\t\t# Calculate the sha256sum and make sure it's something that's actually in\n\t\t# the manifest. If not, it doesn't matter what it is--just abort.\n\t\t#\n\t\tsha256sum=$(sha256sum -b \"$file\" | cut -f1 -d' ')\n\t\tif ! grep -q \"$sha256sum\" manifest.txt\n\t\tthen\n\t\t\tmzx_error \"file '$file' sha256sum '$sha256sum' not found in manifest.txt! Aborting.\" 5\n\t\t\texit 1\n\t\tfi\n\n\t\tgzip < \"$file\" > \"$sha256sum\"\n\t\trm -f \"$file\"\n\tdone\n\n\t#\n\t# Compress manifest.txt but keep the same filename.\n\t#\n\tmv manifest.txt __tmp_manifest.txt\n\tgzip < __tmp_manifest.txt > manifest.txt\n\trm -f __tmp_manifest.txt\n\n\t#\n\t# Prune directories and config.txt.\n\t#\n\tfind . -mindepth 1 -type d -exec rm -rf '{}' ';' 2>/dev/null\n\trm -f config.txt\n\tchmod 0644 ./*\n}\n\n#\n# $@ - list of tags-to-branches to process in the format \"tag:branch\".\n#      example: process_updates Stable:2.90 Unstable:master Debytecode:debytecode\n#\nprocess_updates()\n{\n\tmkdir -p \"$MZX_TARGET\"\n\tcd \"$MZX_TARGET\" || { mzx_error \"failed to cd to '$MZX_TARGET'\" 1; exit 1; }\n\n\t[ -d \"releases\" ] || \\\n\t { mzx_error \"Couldn't find 'releases' dir. Must run mzx-build.sh first!\" 2; exit 1; }\n\trm -rf \"uploads\"\n\tmkdir -p \"uploads\"\n\trm -rf \"releases-update\"\n\tcp -r \"releases\" \"releases-update\"\n\n\t#\n\t# Process each tag:branch pair.\n\t#\n\twhile [ -n \"$1\" ]\n\tdo\n\t\tUPDATE_TAG=$(echo \"$1\" | cut -d: -f1)\n\t\tUPDATE_BRANCH=$(echo \"$1\" | cut -d: -f2)\n\n\t\t#\n\t\t# Handle each architecture found in the branch directory if it exists.\n\t\t#\n\t\tif [ -d \"$MZX_TARGET/releases-update/$UPDATE_BRANCH\" ]\n\t\tthen\n\t\t\tfor F in \"$MZX_TARGET/releases-update/$UPDATE_BRANCH\"/*\n\t\t\tdo\n\t\t\t\tif [ -d \"$F\" ]\n\t\t\t\tthen\n\t\t\t\t\tmzx_log \"Processing '$F'...\"\n\t\t\t\t\t(\n\t\t\t\t\t\tif ! cd \"$F\"\n\t\t\t\t\t\tthen\n\t\t\t\t\t\t\tmzx_error \"failed to cd to '$F'\" 3\n\t\t\t\t\t\t\texit 1\n\t\t\t\t\t\tfi\n\t\t\t\t\t\tprocess_release_dir\n\t\t\t\t\t) &\n\t\t\t\tfi\n\t\t\tdone\n\t\tfi\n\n\t\t#\n\t\t# Add each type to the updates file regardless of whether or not it was\n\t\t# processed. This way, individual release tags can be updated without\n\t\t# breaking other existing release tags.\n\t\t#\n\t\techo \"Current-$UPDATE_TAG: $UPDATE_BRANCH\" >> \"$MZX_TARGET/releases-update/updates-uncompressed.txt\"\n\t\tshift\n\tdone\n\twait\n\n\tcd \"$MZX_TARGET/releases-update\" || { mzx_error \"failed to cd to '$MZX_TARGET/releases-update'\" 4; exit 1; }\n\tif [ -f \"updates-uncompressed.txt\" ]\n\tthen\n\t\tgzip < updates-uncompressed.txt > updates.txt\n\t\trm -f updates-uncompressed.txt\n\t\ttar -cvf \"$MZX_TARGET/uploads/updates.tar\" ./*\n\telse\n\t\tmzx_error \"No updates to process! Aborting.\" 5\n\t\texit 1\n\tfi\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/uploads.sh",
    "content": "#!/bin/sh\n\n# Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n# Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; either version 2 of\n# the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n\n. \"$MZX_SCRIPTS/common.sh\"\n\n# $1 - File to POST.\n# $2 - URL to POST the updates to.\nupload_post_file()\n{\n\thash=$(sha256sum \"$1\" | cut -f 1 -d \" \")\n\tcurl --progress-bar \\\n\t  -H \"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36\" \\\n\t  -F \"hash=$hash\" \\\n\t  -F \"upload=@$1\" \\\n\t  \"$2\"\n}\n\n# $1 - URL to POST the updates to.\nupload_curl()\n{\n\tupload_post_file \"$MZX_TARGET/uploads/updates.tar\" \"$1\"\n}\n\n# $1 - SSH login.\nupload_ssh()\n{\n\tscp \"$MZX_TARGET/uploads/updates.tar\" \"$1:updates.tar\"\n\tssh \"$1\" \"tar -xvf updates.tar && rm -f updates.tar\"\n}\n"
  },
  {
    "path": "scripts/buildscripts/mzx-scripts/version.sh",
    "content": "#!/bin/sh\n\n#\n# Git branches for each update pin.\n#\nexport STABLE=\"v2.93d\"\nexport UNSTABLE=\"master\"\nexport DEBYTECODE=\"master\"\n\n#\n# Update branches for each update pin.\n#\n# Typically stable updates for each new MZX release and unstable/debytecode\n# stay the same. In the case of MZX releases, this string must correspond\n# exactly to the MZX version in version.inc.\n#\nexport STABLE_UPDATES=\"2.93d\"\nexport UNSTABLE_UPDATES=\"master\"\nexport DEBYTECODE_UPDATES=\"debytecode\"\n"
  },
  {
    "path": "scripts/buildscripts/mzx-updates.sh",
    "content": "#!/bin/sh\n\nexport MZX_SCRIPTS_BASE=\"$(cd \"$(dirname \"$0\")\" || true; pwd)\"\nexport MZX_SCRIPTS=\"${MZX_SCRIPTS_BASE%/}/mzx-scripts\"\nexport MZX_WORKINGDIR=\"${MZX_SCRIPTS_BASE%/}/mzx-workingdir\"\nexport MZX_TARGET_BASE=\"$(pwd)\"\nexport MZX_TARGET=\"${MZX_TARGET_BASE%/}/TARGET\"\n\n. \"$MZX_SCRIPTS/version.sh\"\n. \"$MZX_SCRIPTS/updates.sh\"\n\nprocess_updates \"Stable:$STABLE_UPDATES\" \"Unstable:$UNSTABLE_UPDATES\" \"Debytecode:$DEBYTECODE_UPDATES\"\n"
  },
  {
    "path": "scripts/buildscripts/mzx-upload.sh",
    "content": "#!/bin/sh\n\nexport MZX_SCRIPTS_BASE=\"$(cd \"$(dirname \"$0\")\" || true; pwd)\"\nexport MZX_SCRIPTS=\"${MZX_SCRIPTS_BASE%/}/mzx-scripts\"\nexport MZX_WORKINGDIR=\"${MZX_SCRIPTS_BASE%/}/mzx-workingdir\"\nexport MZX_TARGET_BASE=\"$(pwd)\"\nexport MZX_TARGET=\"${MZX_TARGET_BASE%/}/TARGET\"\n\n. \"$MZX_SCRIPTS/uploads.sh\"\n\n#\n# Add lines to configure upload targets here.\n#\n# \"upload_curl\" will POST the uploads via curl to a remote HTTP script.\n#\n# \"upload-ssh\" will transfer the uploads via scp and then extract them via ssh.\n# Note that upload-ssh will upload and extract the .tar file directly into the\n# user directory.\n#\n\n# upload_curl \"http://Put the URL of your upload PHP script here/.php\"\n# upload_ssh  \"username@example.com\"\n"
  },
  {
    "path": "scripts/deps/Makefile",
    "content": "#\n# MegaZeux common dependency builder code.\n#\n\nifeq (${PLATFORM},)\nall:\n\t$(error usage: make PLATFORM=<platform> [ARCH=<arch>] [all|<arch>|fetch|package|clean])\n\nendif\n\nVERSION\t\t= 2.93e-r0\nDIST\t\t= megazeux-dependencies-${VERSION}\nTARGET\t\t= TARGET\n\n# 1.3.2 is broken on DJGPP (EWOULDBLOCK) and Amiga m68k (-fPIC).\nZLIB\t\t= zlib-1.3.1\nPNG\t\t= libpng-1.6.57\nOGG\t\t= libogg-1.3.6\nVORBIS\t\t= libvorbis-1.3.7\nTREMOR\t\t= tremor\nSTB\t\t= stb\nSDL12\t\t= SDL-1.2.15\nSDL2_TIGER\t= panther_sdl2\nSDL2_22\t\t= SDL2-2.0.22\nSDL2_LATEST\t= SDL2-2.32.10\nSDL3\t\t= SDL3-3.4.4\n\n# Mac OS X/macOS compatibility builds rely on different versions of SDL2\n# for each architecture. Override ${SDL2} to select a different version.\nSDL2\t\t:= ${SDL2_LATEST}\n\n#ZLIB_URL\t= https://www.zlib.net/${ZLIB}.tar.gz\nZLIB_URL\t= https://github.com/madler/zlib/releases/download/v1.3.1/${ZLIB}.tar.gz\nPNG_URL\t\t= http://download.sourceforge.net/libpng/${PNG}.tar.gz\nOGG_URL\t\t= https://downloads.xiph.org/releases/ogg/${OGG}.tar.gz\nVORBIS_URL\t= https://downloads.xiph.org/releases/vorbis/${VORBIS}.tar.gz\nSDL12_URL\t= https://www.libsdl.org/release/${SDL12}.tar.gz\nSDL2_22_URL\t= https://www.libsdl.org/release/${SDL2_22}.tar.gz\nSDL2_URL\t= https://www.libsdl.org/release/${SDL2_LATEST}.tar.gz\nSDL3_URL\t= https://www.libsdl.org/release/${SDL3}.tar.gz\n\n# xiph still hasn't fixed the crash bugs in tremor yet\n#TREMOR_GIT\t= https://gitlab.xiph.org/xiph/tremor\nTREMOR_GIT\t= https://github.com/sezero/tremor\n\n# stb_vorbis upstream is out of date and sezero's fork includes every\n# patch from libxmp (including mandatory alloca removal)\n#STB_GIT\t= https://github.com/nothings/stb\nSTB_GIT\t\t= https://github.com/sezero/stb\n\n# Officially, SDL 2.0.2 is the last version to support Mac OS X Panther/Tiger.\n# panther_SDL2 is a backport of SDL 2.0.3 to these Mac OS X releases.\n#SDL2_TIGER_GIT\t= https://github.com/panther_SDL2/panther_SDL2\nSDL2_TIGER_GIT\t= https://github.com/AliceLR/panther_SDL2\n\nLIB\t\t= ${PLATFORM}/${ARCH}/lib\nBIN\t\t= ${PLATFORM}/${ARCH}/bin\nINCLUDE\t\t= ${PLATFORM}/${ARCH}/include\nZLIB_TARGET\t= ${LIB}/libz.a\nPNG_TARGET\t= ${LIB}/libpng16.a\nOGG_TARGET\t= ${LIB}/libogg.a\nVORBIS_TARGET\t= ${LIB}/libvorbis.a\nTREMOR_TARGET\t= ${LIB}/libvorbisidec.a\nSTB_TARGET\t= ${INCLUDE}/stb/stb_vorbis.c\nSDL12_TARGET\t= ${LIB}/libSDL.a\nSDL2_TARGET\t= ${LIB}/libSDL2.a\nSDL3_TARGET\t= ${LIB}/libSDL3.a\nTARGETS\t\t= ${ZLIB_TARGET} \\\n\t\t  ${PNG_TARGET} \\\n\t\t  ${OGG_TARGET} \\\n\t\t  ${VORBIS_TARGET} \\\n\t\t  ${SDL12_TARGET} \\\n\t\t  ${SDL2_TARGET} \\\n\nall:\n\n-include Makefile.${PLATFORM}.in\n\nZLIB_PACKAGE\t= ${TARGET}/${LIB}/libz.a \\\n\t\t  ${TARGET}/${INCLUDE}/zlib.h \\\n\t\t  ${TARGET}/${INCLUDE}/zconf.h\nPNG_PACKAGE\t= ${TARGET}/${LIB}/libpng16.a \\\n\t\t  ${TARGET}/${INCLUDE}/libpng16 \\\n\t\t  ${TARGET}/${BIN}/libpng-config\nVORBIS_PACKAGE\t= ${TARGET}/${LIB}/libogg.a \\\n\t\t  ${TARGET}/${LIB}/libvorbis.a \\\n\t\t  ${TARGET}/${LIB}/libvorbisfile.a \\\n\t\t  ${TARGET}/${INCLUDE}/ogg \\\n\t\t  ${TARGET}/${INCLUDE}/vorbis\nTREMOR_PACKAGE\t= ${TARGET}/${LIB}/libvorbisidec.a \\\n\t\t  ${TARGET}/${INCLUDE}/tremor\nifneq (${TREMOR_LOWMEM},1)\nTREMOR_PACKAGE\t+= \\\n\t\t  ${TARGET}/${LIB}/libogg.a \\\n\t\t  ${TARGET}/${INCLUDE}/ogg\nendif\nSTB_PACKAGE\t= ${TARGET}/${INCLUDE}/stb\nSDL12_PACKAGE\t= ${TARGET}/${LIB}/libSDL.a \\\n\t\t  ${TARGET}/${INCLUDE}/SDL \\\n\t\t  ${TARGET}/${BIN}/sdl-config\nSDL2_PACKAGE\t= ${TARGET}/${LIB}/libSDL2.a \\\n\t\t  ${TARGET}/${INCLUDE}/SDL2 \\\n\t\t  ${TARGET}/${BIN}/sdl2-config\nSDL3_PACKAGE\t= ${TARGET}/${LIB}/libSDL3.a \\\n\t\t  ${TARGET}/${INCLUDE}/SDL3\n\n.PHONY: all\n.PHONY: package package_dir package_clean package_lazy\n.PHONY: zlib_package png_package\n.PHONY: vorbis_package tremor_package stb_package\n.PHONY: SDL12_package SDL2_package SDL3_package\n.PHONY: clean zlib_clean png_clean ogg_clean vorbis_clean tremor_clean\n.PHONY: SDL12_clean SDL2_tiger_clean SDL2_22_clean SDL2_clean SDL3_clean\n.PHONY: fetch distclean\n\n.NOTPARALLEL:\n.SUFFIXES:\n\n#\n# Initial setup rules.\n#\n\nbuild:\n\tmkdir -p build/cmake\n\nbuild/${ZLIB}.tar.gz: | build\n\tcd build && \\\n\twget '${ZLIB_URL}'\n\nbuild/${PNG}.tar.gz: | build\n\tcd build && \\\n\twget '${PNG_URL}'\n\nbuild/${OGG}.tar.gz: | build\n\tcd build && \\\n\twget '${OGG_URL}'\n\nbuild/${VORBIS}.tar.gz: | build\n\tcd build && \\\n\twget '${VORBIS_URL}'\n\nbuild/${SDL12}.tar.gz: | build\n\tcd build && \\\n\twget '${SDL12_URL}'\n\nbuild/${SDL2_22}.tar.gz: | build\n\tcd build && \\\n\twget '${SDL2_22_URL}'\n\nbuild/${SDL2_LATEST}.tar.gz: | build\n\tcd build && \\\n\twget '${SDL2_URL}'\n\nbuild/${SDL3}.tar.gz: | build\n\tcd build && \\\n\twget '${SDL3_URL}'\n\nfetch: build/${ZLIB}\nbuild/${ZLIB}: build/${ZLIB}.tar.gz\n\tcd build && \\\n\ttar -xzf '${ZLIB}.tar.gz'\n\nfetch: build/${PNG}\nbuild/${PNG}: build/${PNG}.tar.gz | build\n\tcd build && \\\n\ttar -xzf '${PNG}.tar.gz'\n\nfetch: build/${OGG}\nbuild/${OGG}: build/${OGG}.tar.gz | build\n\tcd build && \\\n\ttar -xzf '${OGG}.tar.gz'\n\nfetch: build/${VORBIS}\nbuild/${VORBIS}: build/${VORBIS}.tar.gz | build\n\tcd build && \\\n\ttar -xzf '${VORBIS}.tar.gz'\nifeq (${VORBIS},libvorbis-1.3.7)\n\tpatch build/${VORBIS}/lib/CMakeLists.txt fix-vorbis-1.3.7-build-framework.patch\nendif\n\nfetch: build/${TREMOR}\nbuild/${TREMOR}: | build\n\tcd build && \\\n\tgit clone \"${TREMOR_GIT}\"\n\nfetch: build/${STB}\nbuild/${STB}: | build\n\tcd build && \\\n\tgit clone \"${STB_GIT}\"\n\nfetch: build/${SDL12}\nbuild/${SDL12}: build/${SDL12}.tar.gz | build\n\tcd build && \\\n\ttar -xzf '${SDL12}.tar.gz'\n\n# fetch: build/${SDL2_TIGER}\nbuild/${SDL2_TIGER}: | build\n\tcd build && \\\n\tgit clone \"${SDL2_TIGER_GIT}\" && \\\n\tcd \"${SDL2_TIGER}\" && \\\n\tgit apply ../../panther_SDL2_fixes.patch\n\nfetch: build/${SDL2_22}\nbuild/${SDL2_22}: build/${SDL2_22}.tar.gz | build\n\tcd build && \\\n\ttar -xzf '${SDL2_22}.tar.gz'\n\t# SDL 2.0.22 doesn't actually need -fobjc-weak but CMakeLists.txt\n\t# tries to force it. This doesn't affect autotools.\n\tsed -ibak 's/^.*fobjc-weak.*$$//g' build/${SDL2_22}/CMakeLists.txt\n\t# SDL 2.0.22 doesn't need AVFoundation.framework either, but\n\t# the CMakeLists.txt links it anyway just to break Snow Leopard.\n\t# This also does not affect autotools.\n\tsed -ibak 's/set(SDL_FRAMEWORK_AVFOUNDATION 1)//g' build/${SDL2_22}/CMakeLists.txt\n\nfetch: build/${SDL2_LATEST}\nbuild/${SDL2_LATEST}: build/${SDL2_LATEST}.tar.gz | build\n\tcd build && \\\n\ttar -xzf '${SDL2_LATEST}.tar.gz'\n\nfetch: build/${SDL3}\nbuild/${SDL3}: build/${SDL3}.tar.gz | build\n\tcd build && \\\n\ttar -xzf '${SDL3}.tar.gz'\n\nzlib_clean:\n\tcd build/${ZLIB} && make distclean || true; make -fwin32/Makefile.gcc clean || true\n\npng_clean:\n\trm -rf build/cmake/${PNG}\n\tcd build/${PNG} && make distclean || true\n\nogg_clean:\n\trm -rf build/cmake/${OGG}\n\tcd build/${OGG} && make distclean || true\n\nvorbis_clean:\n\trm -rf build/cmake/${VORBIS}\n\tcd build/${VORBIS} && make distclean || true\n\ntremor_clean:\n\tcd build/${TREMOR} && make distclean || true\n\nSDL12_clean:\n\tcd build/${SDL12} && make distclean || true\n\nSDL2_tiger_clean:\n\trm -rf build/cmake/${SDL2_TIGER}\n\tcd build/${SDL2_TIGER} && make distclean || true\n\nSDL2_22_clean:\n\trm -rf build/cmake/${SDL2_22}\n\tcd build/${SDL2_22} && make distclean || true\n\nSDL2_clean:\n\trm -rf build/cmake/${SDL2_LATEST}\n\tcd build/${SDL2_LATEST} && make distclean || true\n\nSDL3_clean:\n\trm -rf build/cmake/${SDL3}\n\nifeq (${ARCH},)\n#\n# Architecture meta targets.\n#\n\nall: ${ALL_ARCH}\n\n${ALL_ARCH}:\n\t${MAKE} ARCH='$@'\n\nclean: zlib_clean png_clean ogg_clean vorbis_clean\nclean: SDL12_clean SDL2_tiger_clean SDL2_22_clean SDL2_clean SDL3_clean\n\ndistclean:\n\trm -rf ${TARGET}\n\trm -rf build\n\trm -rf djgpp\n\trm -rf macos\n\trm -rf mingw\n\npackage:\nifeq (${PLATFORM},)\n\t$(error define a platform!)\nendif\n\tfor ARCH in ${ALL_ARCH}; do \\\n\t\t${MAKE} PLATFORM='${PLATFORM}' ARCH=\"$$ARCH\" package_clean; \\\n\t\t${MAKE} PLATFORM='${PLATFORM}' ARCH=\"$$ARCH\" package; \\\n\tdone\n\tcp ../../arch/LICENSE.3rd ${TARGET}/${PLATFORM}/\n\tA=''; \\\n\tfor ARCH in ${ALL_ARCH}; do \\\n\t\tif [ -d \"${PLATFORM}/$$ARCH\" ]; then \\\n\t\t\tA=\"$$A ${PLATFORM}/$$ARCH\"; \\\n\t\tfi; \\\n\tdone; \\\n\tmkdir -p \"PACKAGES\"; \\\n\tcd ${TARGET}; \\\n\tXZ_OPT=\"-T0 -9\" tar -cJf '../PACKAGES/${DIST}-${PLATFORM}.tar.xz' \\\n\t\t\t\t ${TAR_FLAGS} $$A ${PLATFORM}/LICENSE.3rd\n\nelse\n\n#\n# Per-architecture targets.\n#\n# For libpng, libogg, and libvorbis, an explicit --build value is\n# required to prevent Autoconf's broken interaction with binfmt_misc\n# when autodetecting cross compilation. This doesn't work with SDL.\n#\n\nPREFIX\t\t:= $(shell pwd)/${PLATFORM}/${ARCH}\nCONFIGURE_ARGS\t= --prefix='${PREFIX}'\nifneq (${HOST},)\nZLIB_CONFIGURE\t= CHOST='${HOST}'\nCONFIGURE_ARGS\t+= --build=$$(./config.guess) --host='${HOST}'\nendif\nCONFIGURE_ARGS\t+= ${ARCH_CONFIGURE_ARGS}\n\nifneq (${FORCE_STATIC_LIBS},)\nZLIB_ARGS\t+= --static\nCONFIGURE_ARGS\t+= --disable-shared --enable-static\nendif\n\nCONFIGURE\t?= ./configure\nCMAKE\t\t?= cmake\n\nCONFIGURE\t+= ${CONFIGURE_ARGS}\nCMAKE\t\t+= -S \"$$SRCDIR\" -B . -D CMAKE_INSTALL_PREFIX='${PREFIX}' ${ARCH_CMAKE_ARGS}\n\nifeq (${ARCH_INIT},)\n$(error must define ARCH_INIT.)\nendif\n\n.PHONY: zlib png ogg vorbis tremor SDL12 SDL2 SDL3\n\nzlib: ${ZLIB_TARGET}\npng: ${PNG_TARGET}\nogg: ${OGG_TARGET}\nvorbis: ${VORBIS_TARGET}\ntremor: ${TREMOR_TARGET}\nstb: ${STB_TARGET}\nSDL12: ${SDL12_TARGET}\nSDL2: ${SDL2_TARGET}\nSDL3: ${SDL3_TARGET}\n\n# zlib has its own buildsystem.\n# MinGW requires some involved special handling...\n${ZLIB_TARGET}: | build/${ZLIB} zlib_clean\nifeq (${PLATFORM},mingw)\n\tcd build/${ZLIB}; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\texport DESTDIR='${PREFIX}/'; \\\n\texport INCLUDE_PATH=include; \\\n\texport LIBRARY_PATH=lib; \\\n\texport BINARY_PATH=bin; \\\n\t${MAKE} -fwin32/Makefile.gcc PREFIX='${HOST}-' prefix='${PREFIX}'; \\\n\t${MAKE} -fwin32/Makefile.gcc PREFIX='${HOST}-' prefix='${PREFIX}' install SHARED_MODE=1;\nelse\n\tcd build/${ZLIB}; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\t${ZLIB_CONFIGURE} ./configure --prefix='${PREFIX}' ${ZLIB_ARGS}; \\\n\t${MAKE}; \\\n\t${MAKE} install;\nendif\n\n# libpng supports Autotools and CMake.\n# Its ./configure is barely capable of finding the correct zlib at the\n# best of times and doesn't use pkg-config.\n${PNG_TARGET}: | build/${PNG} png_clean\nifeq (${USE_CMAKE},1)\n\tmkdir -p build/cmake/${PNG}\n\tcd build/cmake/${PNG}; \\\n\texport SRCDIR='../../${PNG}'; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\t${CMAKE}; \\\n\t${MAKE}; \\\n\t${MAKE} install;\nelse\n\tcd build/${PNG}; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\tCFLAGS=\"$$CFLAGS -I${PREFIX}/include\" \\\n\tCPPFLAGS=\"$$CPPFLAGS -I${PREFIX}/include\" \\\n\tLDFLAGS=\"$$LDFLAGS -L${PREFIX}/lib -lz\" \\\n\t${CONFIGURE}; \\\n\t${MAKE}; \\\n\t${MAKE} install;\nendif\n# Doesn't seem to be required anymore\n#ifeq (${PLATFORM},macos)\n#\tinstall_name_tool -change /usr/lib/libz.1.dylib \"${PREFIX}/lib/libz.dylib\" \"${PREFIX}/lib/libpng.dylib\"\n#endif\n\n# libogg supports Autotools and CMake.\n# The Xiph CMake buildsystems can't build static and shared at the same time.\n${OGG_TARGET}: | build/${OGG} ogg_clean\nifeq (${USE_CMAKE},1)\n\tmkdir -p build/cmake/${OGG}\n\tcd build/cmake/${OGG}; \\\n\texport SRCDIR='../../${OGG}'; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\t${CMAKE} -D BUILD_SHARED_LIBS=ON; \\\n\t${MAKE}; \\\n\t${MAKE} install; \\\n\t${CMAKE} -D BUILD_SHARED_LIBS=OFF; \\\n\t${MAKE}; \\\n\t${MAKE} install;\nelse\n\tcd build/${OGG}; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\t${CONFIGURE}; \\\n\t${MAKE}; \\\n\t${MAKE} install;\nendif\n\n# libvorbis supports Autotools and CMake.\n# The Xiph CMake buildsystems can't build static and shared at the same time.\n${VORBIS_TARGET}: | build/${VORBIS} vorbis_clean\nifeq (${USE_CMAKE},1)\n\tmkdir -p build/cmake/${VORBIS}\n\tcd build/cmake/${VORBIS}; \\\n\texport SRCDIR='../../${VORBIS}'; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\t${CMAKE} -D BUILD_SHARED_LIBS=ON; \\\n\t${MAKE}; \\\n\t${MAKE} install; \\\n\t${CMAKE} -D BUILD_SHARED_LIBS=OFF; \\\n\t${MAKE}; \\\n\t${MAKE} install;\nelse\n\tcd build/${VORBIS}; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\t${CONFIGURE} --disable-oggtest; \\\n\t${MAKE}; \\\n\t${MAKE} install;\nendif\n\n# libvorbisidec supports Autotools.\n# The lowmem branch can be selected optionally.\n${TREMOR_TARGET}: | build/${TREMOR} tremor_clean\nifeq (${TREMOR_LOWMEM},1)\n\tcd build/${TREMOR}; \\\n\tgit checkout lowmem\nelse\n\tcd build/${TREMOR}; \\\n\tgit checkout master\nendif\n\tcd build/${TREMOR}; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\t./autogen.sh ${CONFIGURE_ARGS}; \\\n\t${MAKE}; \\\n\t${MAKE} install;\n\n# stb_vorbis is just a .c source file include.\n${STB_TARGET}: | build/${STB}\n\tmkdir -p \"${PREFIX}/include/stb\"\n\tcd build/${STB} && git checkout stb_vorbis-sezero\n\tcp build/${STB}/stb_vorbis.c \"${PREFIX}/include/stb\"\n\n# SDL1.2 only supports Autotools.\n# Rerun autogen.sh to fix ./configure's --build/--host cross compilation detection.\n${SDL12_TARGET}: | build/${SDL12} SDL12_clean\n\tcd build/${SDL12}; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\t./autogen.sh; \\\n\tln -sf build-scripts/config.guess config.guess; \\\n\t${CONFIGURE} ${ARCH_CONFIGURE_SDL12_ARGS}; \\\n\t${MAKE}; \\\n\t${MAKE} install;\n\n# SDL2 supports Autotools and CMake. Early versions have a broken CMakeFiles.txt.\n${SDL2_TARGET}: | build/${SDL2} SDL2_tiger_clean SDL2_22_clean SDL2_clean\nifeq (${USE_CMAKE},1)\n\tmkdir -p build/cmake/${SDL2}\n\tcd build/cmake/${SDL2}; \\\n\texport SRCDIR='../../${SDL2}'; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\t${CMAKE}; \\\n\t${MAKE}; \\\n\t${MAKE} install;\nelse\n\tcd build/${SDL2}; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\tln -sf build-scripts/config.guess config.guess; \\\n\t${CONFIGURE} ${ARCH_CONFIGURE_SDL2_ARGS}; \\\n\t${MAKE}; \\\n\t${MAKE} install;\nendif\n\n# SDL3 only supports CMake.\n${SDL3_TARGET}: | build/${SDL3} SDL3_clean\n\tmkdir -p build/cmake/${SDL3}\n\tcd build/cmake/${SDL3}; \\\n\texport SRCDIR='../../${SDL3}'; \\\n\tset -e; \\\n\t${ARCH_INIT}; \\\n\t${CMAKE} ${ARCH_CMAKE_SDL3_ARGS}; \\\n\t${MAKE}; \\\n\t${MAKE} install;\n\n#\n# Packaging rules.\n#\n\npackage_clean:\n\trm -rf ${TARGET}/${LIB}\n\trm -rf ${TARGET}/${INCLUDE}\n\trm -rf ${TARGET}/${BIN}\n\npackage_dir:\n\tif [ -d \"${PLATFORM}/${ARCH}\" ]; then \\\n\t\tmkdir -p ${TARGET}/${LIB}; \\\n\t\tmkdir -p ${TARGET}/${INCLUDE}; \\\n\t\tmkdir -p ${TARGET}/${BIN}; \\\n\tfi\n\n# macOS dylibbundler requires libraries with intact full names,\n# which the normal package targets can't handle. This copies the\n# entirety of bin, lib, and include instead, and then strips\n# out known bloat.\npackage_lazy: | package_dir\n\tcp -RP \"${LIB}\" \"${TARGET}/${LIB}/../\"\n\tcp -RP \"${INCLUDE}\" \"${TARGET}/${INCLUDE}/../\"\n\tcp -RP \"${BIN}\" \"${TARGET}/${BIN}/../\"\n\trm -rf \"${TARGET}/${LIB}/png.framework\"\n\trm -f \"${TARGET}/${BIN}/png\"*\n\n%.debug: % | package_dir\n\t${HOST}-objcopy --only-keep-debug $< $@\n\t${HOST}-strip --strip-unneeded $<\n\t${HOST}-objcopy --add-gnu-debuglink=$@ $<\n\n${TARGET}/%: % | package_dir\n\tif [ -d \"$<\" ]; then \\\n\t\tcp -r $< $@; \\\n\telse \\\n\t\tcp -L $< $@; \\\n\tfi\n\npackage: ${TARGET}/${LIB}/pkgconfig | package_dir\n\nzlib_package: ${ZLIB_PACKAGE}\npng_package: ${PNG_PACKAGE}\nvorbis_package: ${VORBIS_PACKAGE}\ntremor_package: ${TREMOR_PACKAGE}\nstb_package: ${STB_PACKAGE}\nSDL12_package: ${SDL12_PACKAGE}\nSDL2_package: ${SDL2_PACKAGE}\nSDL3_package: ${SDL3_PACKAGE}\n\nendif # ARCH != ''\n"
  },
  {
    "path": "scripts/deps/Makefile.djgpp.in",
    "content": "#\n# DJGPP dependency builder support.\n#\n# Some byte order macros need to be predefined for tremor.\n#\n\nALL_ARCH = i386\n\nHOST\t = i586-pc-msdosdjgpp\nCFLAGS\t = -O3 -g -DBYTE_ORDER=1 -DLITTLE_ENDIAN=1 -DBIG_ENDIAN=0\nCXXFLAGS = ${CFLAGS}\n\nifneq (${USE_CMAKE},)\n$(error use Autotools for this arch.)\nendif\n\nifneq (${ARCH},)\nARCH = ${ALL_ARCH}\nARCH_INIT = \\\n\texport CFLAGS='${CFLAGS}'; \\\n\texport CXXFLAGS='${CXXFLAGS}'; \\\n\texport PKG_CONFIG_PATH='${PREFIX}/lib/pkgconfig'; \\\n\ttrue\n\nFORCE_STATIC_LIBS\t= 1\nTREMOR_LOWMEM\t\t= 1\n\nall: ${ZLIB_TARGET} ${PNG_TARGET}\nall: ${OGG_TARGET} ${VORBIS_TARGET} ${TREMOR_TARGET} ${STB_TARGET}\n\npackage: zlib_package png_package vorbis_package tremor_package stb_package\nendif\n"
  },
  {
    "path": "scripts/deps/Makefile.linux-msan.in",
    "content": "#\n# linux MemorySanitizer dependency builder support.\n#\n# Every dependency of MegaZeux needs to be instrumented with\n# MemorySanitizer for MemorySanitizer builds to work.\n# Because SDL uses so many external libraries, this means\n# that MSan builds don't support SDL.\n# Due to complications when dynamically linking, build\n# static libraries only.\n#\n\nTHIS_ARCH = $(shell uname -m)\nALL_ARCH = ${THIS_ARCH}\n\nCC\t = clang\nCXX\t = clang++\nCFLAGS\t = -O3 -fsanitize=memory -fno-omit-frame-pointer -fPIC \\\n\t  -fsanitize-memory-track-origins=2 -g\nCXXFLAGS = ${CFLAGS}\n\nifneq (${ARCH},)\nifneq (${ARCH},$(shell uname -m))\nHOST     = ${ARCH}-linux-gnu\nCC       = ${HOST}-${CC}\nCXX      = ${HOST}-${CXX}\nendif\n\nARCH_INIT = \\\n\texport CC='${CC}'; \\\n\texport CXX='${CXX}'; \\\n\texport CFLAGS='${CFLAGS}'; \\\n\texport CXXFLAGS='${CXXFLAGS}'; \\\n\ttrue\n\nFORCE_STATIC_LIBS := 1\nUSE_CMAKE := 0\n\nall: ${ZLIB_TARGET} ${PNG_TARGET} ${OGG_TARGET} ${VORBIS_TARGET} ${STB_TARGET}\npackage: zlib_package png_package vorbis_package stb_package\nendif\n"
  },
  {
    "path": "scripts/deps/Makefile.macos.in",
    "content": "#\n# macOS Darwin port dependency builder support.\n#\n\nALL_ARCH = ppc ppc64 i686 x86_64 x86_64h arm64 arm64e\n\ninclude ../../arch/darwin/Makefile.arch\n\nifneq (${ARCH},)\nall: ${ZLIB_TARGET} ${PNG_TARGET} ${OGG_TARGET} ${VORBIS_TARGET} ${STB_TARGET}\npackage: package_lazy\n\nall: ${SDL2_TARGET}\n\nifneq ($(filter ppc ppc64,${ARCH}),)\nall: ${SDL12_TARGET}\nHOST=powerpc-apple-darwin10\nendif\n\nifneq ($(filter arm64 arm64e,${ARCH}),)\nall: ${SDL3_TARGET}\nendif\n\nCFLAGS = -O3\nCXXFLAGS = ${CFLAGS}\nUSE_CMAKE := 1\n\n# Note: OBJC and OBJCXX are CMake-specific.\nARCH_INIT = \\\n\texport CC=\"${CC}\"; \\\n\texport CXX=\"${CXX}\"; \\\n\texport CFLAGS=\"${CFLAGS}\"; \\\n\texport CXXFLAGS=\"${CXXFLAGS}\"; \\\n\texport OBJC=\"${CC}\"; \\\n\texport OBJCXX=\"${CXX}\"; \\\n\texport PKG_CONFIG_PATH=\"${PREFIX}/lib/pkgconfig\"; \\\n\texport CMAKE_LIBRARY_PATH=\"${PREFIX}\"; \\\n\texport MACOSX_DEPLOYMENT_TARGET=\"${MINVER}\"; \\\n\ttrue\n\nARCH_CONFIGURE_ARGS\t=\nARCH_CONFIGURE_SDL12_ARGS = --disable-video-x11\nARCH_CONFIGURE_SDL2_ARGS = --without-x\nARCH_CMAKE_SDL3_ARGS = -DCMAKE_BUILD_TYPE=Release -DSDL_STATIC=1 -DSDL_SHARED=1\n\nARCH_CMAKE_ARGS = \\\n\t-D SDL_X11=OFF \\\n\t-D CMAKE_INSTALL_NAME_DIR=\"${PREFIX}/lib\" \\\n\t-D CMAKE_BUILD_WITH_INSTALL_NAME_DIR=TRUE \\\n\t-D CMAKE_MACOSX_RPATH=FALSE\n\n# CMAKE_OSX_ARCHITECTURES is required to make libpng successfully link ARM.\n# However, this breaks PowerPC64.\n# CMake 3.31 tries to check -arch arguments and refuses to accept i686 now\n# (doesn't seem to be aware that i686 is an alias for i386), so do not\n# use ${ARCH} in this case either.\nifeq ($(filter ppc ppc64 i686,${ARCH}),)\nARCH_CMAKE_ARGS += -D CMAKE_OSX_ARCHITECTURES=\"${ARCH}\"\nendif\n\nifneq (${SYSROOT},)\nARCH_CMAKE_ARGS += -D CMAKE_OSX_SYSROOT=\"${SYSROOT}\"\nendif\n\nifeq (${ARCH},arm64)\nendif\n\nifeq (${ARCH},arm64e)\nendif\n\nifeq (${ARCH},i686)\nSDL2 := ${SDL2_22}\nendif\n\nifeq (${ARCH},x86_64)\nSDL2 := ${SDL2_22}\nendif\n\nifeq (${ARCH},x86_64h)\nFIX_SDL_CMAKE += -D SDL_VULKAN=FALSE\nendif\n\nifeq (${ARCH},ppc)\nSDL2 := ${SDL2_TIGER}\nARCH_INIT += ; export MACOSX_DEPLOYMENT_TARGET=10.4; true\nARCH_CMAKE_ARGS += -D SDL_JOYSTICK=FALSE -D SDL_HAPTIC=FALSE -D SSEMATH=FALSE -D ALTIVEC=FALSE\nendif\n\nifeq (${ARCH},ppc64)\nSDL2 := ${SDL2_TIGER}\nARCH_INIT += ; export MACOSX_DEPLOYMENT_TARGET=10.5; true\nARCH_CMAKE_ARGS += -D SDL_JOYSTICK=FALSE -D SDL_HAPTIC=FALSE -D SSEMATH=FALSE -D CMAKE_OSX_DEPLOYMENT_TARGET=${MINVER}\nendif\nendif # ARCH != ''\n"
  },
  {
    "path": "scripts/deps/Makefile.mingw.in",
    "content": "#\n# MinGW dependency builder support.\n#\n# CMake doesn't really work properly for libogg and libvorbis,\n# and MinGW has proper triples anyway, so probably avoid it for\n# this platform.\n#\n\nALL_ARCH = x86 x64\n\nifeq (${ARCH},x86)\nHOST\t = i686-w64-mingw32\nCC\t = ${HOST}-gcc\nCXX\t = ${HOST}-g++\nCFLAGS\t = -O3 -g -march=i686\nCXXFLAGS = ${CFLAGS}\nARCH_CONFIGURE_SDL2_ARGS = --disable-sse --disable-3dnow\nARCH_CMAKE_SDL3_ARGS = -DCMAKE_TOOLCHAIN_FILE=build-scripts/cmake-toolchain-mingw64-i686.cmake\nifneq (${MSYSTEM},)\nCMAKE\t = /mingw32/bin/cmake\nendif\nendif\n\nifeq (${ARCH},x64)\nHOST\t = x86_64-w64-mingw32\nCC\t = ${HOST}-gcc\nCXX\t = ${HOST}-g++\nCFLAGS\t = -O3 -g -march=x86-64\nCXXFLAGS = ${CFLAGS}\nARCH_CONFIGURE_SDL2_ARGS = --disable-sse3 --disable-3dnow\nARCH_CMAKE_SDL3_ARGS = -DCMAKE_TOOLCHAIN_FILE=build-scripts/cmake-toolchain-mingw64-x86_64.cmake\nifneq (${MSYSTEM},)\nCMAKE\t = /mingw64/bin/cmake\nendif\nendif\n\nifneq (${ARCH},)\nifeq (${HOST},)\n$(error valid ARCH values: ${ALL_ARCH})\nendif\n\nARCH_INIT = \\\n\texport CC='${CC}'; \\\n\texport CXX='${CXX}'; \\\n\texport CFLAGS='${CFLAGS}'; \\\n\texport CXXFLAGS='${CXXFLAGS}'; \\\n\texport LDSHAREDLIBC=''; \\\n\ttrue\n\nARCH_CONFIGURE_ARGS\t=\nARCH_CMAKE_ARGS\t\t= -D CMAKE_SYSTEM_NAME=Windows-GNU\nARCH_CMAKE_SDL3_ARGS\t+= -DCMAKE_BUILD_TYPE=Release -DSDL_SHARED=1 -DSDL_STATIC=1\n\nall: ${ZLIB_TARGET} ${PNG_TARGET} ${OGG_TARGET} ${VORBIS_TARGET} ${STB_TARGET} \\\n\t${SDL12_TARGET} ${SDL2_TARGET} ${SDL3_TARGET}\n\npackage: ${TARGET}/${BIN}/SDL.dll ${TARGET}/${BIN}/SDL.dll.debug ${TARGET}/${LIB}/libSDL.dll.a\npackage: ${TARGET}/${LIB}/libSDLmain.a\npackage: ${TARGET}/${BIN}/SDL2.dll ${TARGET}/${BIN}/SDL2.dll.debug ${TARGET}/${LIB}/libSDL2.dll.a\npackage: ${TARGET}/${LIB}/libSDL2main.a\npackage: ${TARGET}/${BIN}/SDL3.dll ${TARGET}/${BIN}/SDL3.dll.debug ${TARGET}/${LIB}/libSDL3.dll.a\n\npackage: zlib_package png_package vorbis_package stb_package\npackage: SDL12_package SDL2_package SDL3_package\n\nendif\n"
  },
  {
    "path": "scripts/deps/Makefile.xcode.in",
    "content": "#\n# macOS Xcode port dependency builder support.\n#\n\nALL_ARCH :=\n\nall: png.framework Ogg.framework Vorbis.framework SDL2.framework SDL3.framework\n.PHONY: png.framework Ogg.framework Vorbis.framework SDL2.framework SDL3.framework frameworks\n\nZLIB_DIR := $(shell xcrun --show-sdk-path)/usr\n\nFRAMEWORK_PREFIX := $(shell pwd)/${PLATFORM}\nFRAMEWORK_FLAGS_X86_64 := -O3 -flto -arch x86_64\nFRAMEWORK_FLAGS_ARM := -O3 -flto -arch arm64\nFRAMEWORK_CMAKE_FLAGS := \\\n\t-D CMAKE_FRAMEWORK_PATH='${PREFIX}' \\\n\t-D CMAKE_MACOSX_RPATH=TRUE \\\n\nFRAMEWORK_CMAKE_LIBPNG_FLAGS := \\\n\t-D PNG_SHARED=OFF -D PNG_STATIC=OFF -D PNG_TESTS=OFF -D PNG_TOOLS=OFF \\\n\t-D ZLIB_ROOT='${ZLIB_DIR}' \\\n\nCMAKE_X86_64 := \\\n\tmake clean; \\\n\trm -rf CMakeCache.txt; \\\n\texport MACOSX_DEPLOYMENT_TARGET='10.11'; \\\n\tCFLAGS='${FRAMEWORK_FLAGS_X86_64}' \\\n\tCXXFLAGS='${FRAMEWORK_FLAGS_X86_64}' \\\n\tLDFLAGS='${FRAMEWORK_FLAGS_X86_64}' \\\n\tcmake ${FRAMEWORK_CMAKE_FLAGS} \\\n\t\t-D CMAKE_INSTALL_PREFIX='${FRAMEWORK_PREFIX}' \\\n\t\t-D CMAKE_OSX_ARCHITECTURES='x86_64'\n\nCMAKE_ARM := \\\n\tmake clean; \\\n\trm -rf CMakeCache.txt; \\\n\texport MACOSX_DEPLOYMENT_TARGET='11.0'; \\\n\tCFLAGS='${FRAMEWORK_FLAGS_ARM}' \\\n\tCXXFLAGS='${FRAMEWORK_FLAGS_ARM}' \\\n\tLDFLAGS='${FRAMEWORK_FLAGS_ARM}' \\\n\tcmake ${FRAMEWORK_CMAKE_FLAGS} \\\n\t\t-D CMAKE_INSTALL_PREFIX='${FRAMEWORK_PREFIX}/arm' \\\n\t\t-D CMAKE_OSX_ARCHITECTURES='arm64'\n\n# Fix CMake's poor comprehension of the Versions folder.\n# This has to be patched out PRIOR to build in the CMakeLists.txt,\n# otherwise it's more annoying to fix afterward.\n#\n# CMake will also leave trash in the wrong Versions subfolder anyway\n# that needs to be cleaned up after the fact.\nCMAKE_VERSION_A_BUGFIX := \\\n\tsed -i '' 's/FRAMEWORK_VERSION .*/FRAMEWORK_VERSION \"A\"/g' CMakeLists.txt\n\n# No Xcode project\npng.framework: build/${PNG}\n\trm -rf '${FRAMEWORK_PREFIX}/$@'\n\tcd build/${PNG} && ${CMAKE_VERSION_A_BUGFIX}\n\tmkdir -p build/cmake/${PNG}\n\tcd build/cmake/${PNG}; \\\n\t${CMAKE_X86_64} -B . -S ../../${PNG} ${FRAMEWORK_CMAKE_LIBPNG_FLAGS} && ${MAKE} && ${MAKE} install\n\tcd build/cmake/${PNG}; \\\n\t${CMAKE_ARM} -B . -S ../../${PNG} ${FRAMEWORK_CMAKE_LIBPNG_FLAGS} && ${MAKE} && ${MAKE} install\n\tcd ${FRAMEWORK_PREFIX}; \\\n\t\tlipo -create lib/png.framework/png arm/lib/png.framework/png -output _png && \\\n\t\tmv _png lib/png.framework/Versions/Current/png\n\tmv '${FRAMEWORK_PREFIX}'/lib/png.framework '${FRAMEWORK_PREFIX}'\n\n# Previously had an Xcode project, but deleted it because \"you can just use CMake\"\nOgg.framework: build/${OGG}\n\trm -rf '${FRAMEWORK_PREFIX}/$@'\n\tcd build/${OGG} && ${CMAKE_VERSION_A_BUGFIX}\n\tmkdir -p build/cmake/${OGG}\n\tcd build/cmake/${OGG}; \\\n\t${CMAKE_X86_64} -B . -S ../../${OGG} -D BUILD_FRAMEWORK=1 && ${MAKE} && ${MAKE} install\n\tcd build/cmake/${OGG}; \\\n\t${CMAKE_ARM} -B . -S ../../${OGG} -D BUILD_FRAMEWORK=1 && ${MAKE} && ${MAKE} install\n\tcd ${FRAMEWORK_PREFIX}; \\\n\t\tlipo -create Ogg.framework/Ogg arm/Ogg.framework/Ogg -output _Ogg && \\\n\t\tmv _Ogg Ogg.framework/Versions/Current/Ogg\n\n# Has an Xcode project, but see above\nVorbis.framework: Ogg.framework\n\trm -rf '${FRAMEWORK_PREFIX}/$@'\n\tcd build/${VORBIS}/lib && ${CMAKE_VERSION_A_BUGFIX}\n\tmkdir -p build/cmake/${VORBIS}\n\tcd build/cmake/${VORBIS}; \\\n\tcp -r ../../${VORBIS}/include .; \\\n\t${CMAKE_X86_64} -B . -S ../../${VORBIS} -D BUILD_FRAMEWORK=1 && ${MAKE} && ${MAKE} install\n\tcd build/cmake/${VORBIS}; \\\n\texport SRCDIR='../../${VORBIS}'; \\\n\t${CMAKE_ARM} -B . -S ../../${VORBIS} -D BUILD_FRAMEWORK=1 && ${MAKE} && ${MAKE} install\n\tcd ${FRAMEWORK_PREFIX}; \\\n\t\tlipo -create Vorbis.framework/Vorbis arm/Vorbis.framework/Vorbis -output _Vorbis && \\\n\t\tmv _Vorbis Vorbis.framework/Versions/Current/Vorbis\n\n# The only one that doesn't make this hard for some reason\nSDL2.framework: build/${SDL2_LATEST}\n\trm -rf '${FRAMEWORK_PREFIX}/$@'\n\tcd build/${SDL2_LATEST}/Xcode/SDL && \\\n\txcodebuild -scheme Framework -configuration Release install DSTROOT='${FRAMEWORK_PREFIX}'\n\tmv '${FRAMEWORK_PREFIX}'/Library/Frameworks/SDL2.framework '${FRAMEWORK_PREFIX}'\n\nSDL3.framework: build/${SDL3}\n\trm -rf '${FRAMEWORK_PREFIX}/$@'\n\tcd build/${SDL3}/Xcode/SDL && \\\n\txcodebuild -scheme SDL3 -configuration Release install DSTROOT='${FRAMEWORK_PREFIX}'\n\tmv '${FRAMEWORK_PREFIX}'/Library/Frameworks/SDL3.framework '${FRAMEWORK_PREFIX}'\n\n# Use the platform directory directly to avoid issues caused by copying symlinks.\nframeworks:\n\trm -rf ${PLATFORM}/*.framework/Versions/1.*\n\tcp ../../arch/LICENSE.3rd ${PLATFORM}/\n\tmkdir -p PACKAGES\n\tcd ${PLATFORM}; \\\n\tXZ_OPT=\"-T0 -9\" tar -cJf '../PACKAGES/${DIST}-${PLATFORM}.tar.xz' ${TAR_FLAGS} *.framework LICENSE.3rd\n\trm -rf ${PLATFORM}/x86_64; \\\n\tmkdir -p ${PLATFORM}/x86_64; \\\n\tcd ${PLATFORM}/x86_64; \\\n\ttar -xJf '../../PACKAGES/${DIST}-${PLATFORM}.tar.xz' && \\\n\t(cd png.framework/Versions/Current && lipo -remove arm64 png -o png) && \\\n\t(cd Ogg.framework/Versions/Current && lipo -remove arm64 Ogg -o Ogg) && \\\n\t(cd Vorbis.framework/Versions/Current && lipo -remove arm64 Vorbis -o Vorbis) && \\\n\t(cd SDL2.framework/Versions/Current && lipo -remove arm64 SDL2 -o SDL2) && \\\n\t(cd SDL3.framework/Versions/Current && lipo -remove arm64 SDL3 -o SDL3) && \\\n\tXZ_OPT=\"-T0 -9\" tar -cJf '../../PACKAGES/${DIST}-${PLATFORM}-el-capitan.tar.xz' ${TAR_FLAGS} *.framework LICENSE.3rd\n"
  },
  {
    "path": "scripts/deps/README.md",
    "content": "# MegaZeux Dependency Makefiles\n\nThis directory contains generalized Makefiles to build dependencies for\nsome platforms supported by MegaZeux which don't provide these dependencies\nthrough other means (e.g. built-in package manager). These Makefiles are\nnot guaranteed to work with every setup; rather, they're whatever Makefiles\nworked the last time I had to rebuild dependencies for MegaZeux. Note that\nthe Android port uses its own integrated dependency builder and the\nMSVC port still requires manual generation of dependencies.\n\nOfficial builds can be found here: https://github.com/AliceLR/megazeux-dependencies\n\n## Usage of tarballs\n\n* MinGW: extract to some directory `DIR`. Set the environment variables `MINGW32_PREFIX`\n  and `MINGW64_PREFIX` to `DIR/x86` and `DIR/x64`, respectively, and configure with\n  `arch/mingw/CONFIG.MINGW32` or `arch/mingw/CONFIG.MINGW64`.\n* macOS: extract to some directory `DIR`. Configure with\n  `./config.sh --platform darwin-dist`, then provide `PREFIX_[target]=DIR/[target_dir]`\n  to Make for each platform to build.\n* DJGPP: extract to some directory `DIR`. Set the environment variable `DJGPP`\n  to `DIR/i386` and configure with `arch/djgpp/CONFIG.DJGPP`.\n* linux (MSan): extract to some directory `DIR`. Configure with\n  `./config.sh --platform unix-devel --prefix DIR --disable-sdl --enable-msan` and make.\n  You may have to build your own depending on the available glibc version.\n\n## Building\n\nGNU Make is required for all targets.\n\nTo clean all source build directories (located in `./build`), use `make clean`.\nTo delete all build directories, use `make distclean`.\n\n### MinGW\nThe MinGW-w64 toolchains should be on `$PATH`.\n\nBuilds the following libraries: zlib, libpng, libogg, libvorbis, libSDL, libSDL2, libSDL3.\nThe x86 binaries *should* target Windows XP on  `-march=i686` and\nthe x64 binaries *should* target Windows XP on `-march=x86-64`.\n\n```sh\nmake PLATFORM=mingw\nmake PLATFORM=mingw package\n```\n\n### macOS (darwin-dist)\nInstall any of the following Xcode versions/SDKs.\nRecommended setup: High Sierra running 9.4.1 with the 3.2.6 SDKs installed via XcodeLegacy,\nplus any macOS version capable of running Xcode 12.2 or higher for ARM support.\n\n| Target    | Xcode\t| SDK\t\t| macOS Target\t| Supported SDL\t| Notes |\n|-----------|-----------|---------------|---------------|---------------|-------|\n| `arm64`   | 12.2+\t| 11.0+\t\t| 11.0\t\t| 2.x, 3.x\t|\n| `arm64e`  | 12.2+\t| 11.0+\t\t| 11.0\t\t| 2.x, 3.x\t| See below.\n| `i686`    | 9.4.1\t| 10.13.4\t| 10.6\t\t| 2.0.22\t| Last SDK to target 10.6, x86\n| `x86_64`  | 9.4.1\t| 10.13.4\t| 10.6\t\t| 2.0.22\t| Last SDK to target 10.6\n| `x86_64h` | 9.4.1\t| 10.13.4\t| 10.9\t\t| 2.x, 3.x\t| See below.\n| `ppc`     | 3.2.6\t| 10.4u\t\t| 10.4\t\t| 1.2.15, 2.0.3\t| Last SDK to target PPC\n| `ppc64`   | 3.2.6\t| 10.5\t\t| 10.5\t\t| 1.2.15, 2.0.3\t| Last SDK to target PPC\n\nNOTE: XcodeLegacy will replace the current Xcode ld with a wrapper that reroutes all\nPowerPC and <=10.6 usage to the Xcode 3.2.6 ld. This script will be located at\n/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld\nand the check that redirects <=10.6 needs to be changed to <=10.5 instead. Otherwise,\nlinking i686 and x86_64 binaries will fail because ld doesn't understand the .tbd files\nin newer SDKs.\n\nNOTE: Versions earlier than OS X Yosemite may have runtime linker issues\ndistinguishing x86_64 and x86_64h binaries. Manually change the minimum OS\nversion to 10.10 for BOTH when combining them in the same build. This bug\nwas found in OS X Mavericks 10.9.5 and is not present in OS X Yosemite 10.10.5.\n\nNOTE: SDL3 for x86_64 or x86_64h should be built with the same Xcode/SDK/target\nas arm64/arm64e. It will not work with Xcode 9.4.1.\n\nNOTE: It has been reported that macOS Monterey (and probably other versions)\nwill immediately terminate arm64e binaries. Building for this architecture\nis not recommended until we have more information about this.\n\nBuilds the following libraries: zlib, libpng, libogg, libvorbis, libSDL, libSDL2, libSDL3.\n\n```sh\n# Select only the required architectures, e.g.\nmake PLATFORM=macos ARCH=i686\nmake PLATFORM=macos ARCH=x86_64\nmake PLATFORM=macos ARCH=ppc\nmake PLATFORM=macos ARCH=ppc64\nmake PLATFORM=macos package\n```\n\n### Xcode\nInstall Xcode 12.2+ on any macOS version capable of running it. The dependency\nbuilder targets x86_64 and arm64 only, but it should be possible to link x86_64h and arm64e\nagainst these binaries.\n\nUnlike the other platforms here, the tarball produced for this platform is designed\nto be extracted directly into arch/xcode/, where the Xcode project expects the frameworks to be.\n\nBuilds the following libraries: libpng, libogg, libvorbis, libSDL2, libSDL3.\n```sh\n# Compile all frameworks.\nmake PLATFORM=xcode\n# Package the frameworks. Do not use the 'package' target for this platform.\nmake PLATFORM=xcode frameworks\n```\n\n### DJGPP\nThe DJGPP toolchain `bin` directory should be on `$PATH`.\n\nBuilds the following libraries: zlib, libpng, libogg, libvorbis,\nlibvorbisidec (Tremor lowmem).\nNote that stb_vorbis should be used due to limitations of running\naudio code in an interrupt.\nThe binaries *should* target DOS/Windows 9x with CWSDPMI.EXE on `-march=i386`.\n\n```sh\nmake PLATFORM=djgpp\nmake PLATFORM=djgpp package\n```\n"
  },
  {
    "path": "scripts/deps/fix-vorbis-1.3.7-build-framework.patch",
    "content": "161a162,170\n> \n>     target_include_directories(vorbis\n>         PUBLIC\n>             $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>\n>             $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>\n>         PRIVATE\n>             ${CMAKE_CURRENT_SOURCE_DIR}\n>     )\n> \n162a172,179\n> \n>     install(TARGETS vorbis\n>         EXPORT VorbisTargets\n>         RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}\n>         LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}\n>         ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}\n>         FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}\n>     )\n"
  },
  {
    "path": "scripts/deps/panther_SDL2_fixes.patch",
    "content": "diff --git a/CMakeLists.txt b/CMakeLists.txt\nindex 75a8937..d88c3aa 100755\n--- a/CMakeLists.txt\n+++ b/CMakeLists.txt\n@@ -572,18 +572,18 @@ foreach(_SUB ${SDL_SUBSYSTEMS})\n     set(SDL_${_OPT}_DISABLED 1)\n   endif()\n endforeach()\n-if(SDL_JOYSTICK)\n+#if(SDL_JOYSTICK)\n   file(GLOB JOYSTICK_SOURCES ${SDL2_SOURCE_DIR}/src/joystick/*.c)\n   set(SOURCE_FILES ${SOURCE_FILES} ${JOYSTICK_SOURCES})\n-endif()\n-if(SDL_HAPTIC)\n-  if(NOT SDL_JOYSTICK)\n+#endif()\n+#if(SDL_HAPTIC)\n+#  if(NOT SDL_JOYSTICK)\n     # Haptic requires some private functions from the joystick subsystem.\n-    message_error(\"SDL_HAPTIC requires SDL_JOYSTICK, which is not enabled\")\n-  endif()\n+#    message_error(\"SDL_HAPTIC requires SDL_JOYSTICK, which is not enabled\")\n+#  endif()\n   file(GLOB HAPTIC_SOURCES ${SDL2_SOURCE_DIR}/src/haptic/*.c)\n   set(SOURCE_FILES ${SOURCE_FILES} ${HAPTIC_SOURCES})\n-endif()\n+#endif()\n if(SDL_POWER)\n   file(GLOB POWER_SOURCES ${SDL2_SOURCE_DIR}/src/power/*.c)\n   set(SOURCE_FILES ${SOURCE_FILES} ${POWER_SOURCES})\n@@ -1085,11 +1085,11 @@ endif()\n # src/X/*.c does not get included.\n if(NOT HAVE_SDL_JOYSTICK)\n   set(SDL_JOYSTICK_DISABLED 1)\n-  if(SDL_JOYSTICK AND NOT APPLE) # results in unresolved symbols on OSX\n+  #if(SDL_JOYSTICK AND NOT APPLE) # results in unresolved symbols on OSX\n \n     file(GLOB JOYSTICK_SOURCES ${SDL2_SOURCE_DIR}/src/joystick/dummy/*.c)\n     set(SOURCE_FILES ${SOURCE_FILES} ${JOYSTICK_SOURCES})\n-  endif()\n+  #endif()\n endif()\n if(NOT HAVE_SDL_HAPTIC)\n   set(SDL_HAPTIC_DISABLED 1)\n@@ -1290,8 +1290,8 @@ if(NOT WINDOWS OR CYGWIN)\n   if(SDL_SHARED)\n     install(CODE \"\n       execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink\n-      \\\"libSDL2-2.0.so\\\" \\\"libSDL2.so\\\")\")\n-    install(FILES ${SDL2_BINARY_DIR}/libSDL2.so DESTINATION \"lib${LIB_SUFFIX}\")\n+      \\\"libSDL2-2.0.dylib\\\" \\\"libSDL2.dylib\\\")\")\n+    install(FILES ${SDL2_BINARY_DIR}/libSDL2.dylib DESTINATION \"lib${LIB_SUFFIX}\")\n   endif()\n   if(FREEBSD)\n     # FreeBSD uses ${PREFIX}/libdata/pkgconfig\ndiff --git a/include/SDL_syswm.h b/include/SDL_syswm.h\nindex d20aa6f..d7a2347 100755\n--- a/include/SDL_syswm.h\n+++ b/include/SDL_syswm.h\n@@ -83,7 +83,7 @@ struct SDL_SysWMinfo;\n \n #if defined(SDL_VIDEO_DRIVER_COCOA)\n #ifdef __OBJC__\n-#if defined(__ALTIVEC__) && !defined(MAC_OS_X_VERSION_10_5)\n+#if defined(__ALTIVEC__) && MAC_OS_X_VERSION_MIN_REQUIRED < 1050\n /* to cricumvent a bug in Mac OS X 10.4 SDK */\n #define vector __vector\n #include <CoreServices/CoreServices.h>\ndiff --git a/src/audio/coreaudio/SDL_coreaudio.h b/src/audio/coreaudio/SDL_coreaudio.h\nindex 52c49f0..4064f9f 100755\n--- a/src/audio/coreaudio/SDL_coreaudio.h\n+++ b/src/audio/coreaudio/SDL_coreaudio.h\n@@ -34,7 +34,7 @@\n \n #if defined(MACOSX_COREAUDIO) || defined(MACOSX_PANTHER_COREAUDIO)\n #include <CoreAudio/CoreAudio.h>\n-#if defined(__ALTIVEC__) && !defined(MAC_OS_X_VERSION_10_5)\n+#if defined(__ALTIVEC__) && MAC_OS_X_VERSION_MIN_REQUIRED < 1050\n /* to cricumvent a bug in Mac OS X 10.4 SDK */\n #define vector __vector\n #include <CoreServices/CoreServices.h>\ndiff --git a/src/file/cocoa/SDL_rwopsbundlesupport.m b/src/file/cocoa/SDL_rwopsbundlesupport.m\nindex 8830367..3b215ad 100755\n--- a/src/file/cocoa/SDL_rwopsbundlesupport.m\n+++ b/src/file/cocoa/SDL_rwopsbundlesupport.m\n@@ -21,7 +21,7 @@\n #include \"../../SDL_internal.h\"\n \n #ifdef __APPLE__\n-#if defined(__ALTIVEC__) && !defined(MAC_OS_X_VERSION_10_5)\n+#if defined(__ALTIVEC__) && MAC_OS_X_VERSION_MIN_REQUIRED < 1050\n /* to cricumvent a bug in Mac OS X 10.4 SDK */\n #define vector __vector\n #include <CoreServices/CoreServices.h>\ndiff --git a/src/filesystem/cocoa/SDL_sysfilesystem.m b/src/filesystem/cocoa/SDL_sysfilesystem.m\nindex 8b87e37..971e6ec 100755\n--- a/src/filesystem/cocoa/SDL_sysfilesystem.m\n+++ b/src/filesystem/cocoa/SDL_sysfilesystem.m\n@@ -25,7 +25,7 @@\n /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */\n /* System dependent filesystem routines                                */\n \n-#if defined(__ALTIVEC__) && !defined(MAC_OS_X_VERSION_10_5)\n+#if defined(__ALTIVEC__) && MAC_OS_X_VERSION_MIN_REQUIRED < 1050\n /* to cricumvent a bug in Mac OS X 10.4 SDK */\n #define vector __vector\n #include <CoreServices/CoreServices.h>\ndiff --git a/src/power/macosx/SDL_syspower.c b/src/power/macosx/SDL_syspower.c\nindex 944e452..026ea71 100755\n--- a/src/power/macosx/SDL_syspower.c\n+++ b/src/power/macosx/SDL_syspower.c\n@@ -23,7 +23,7 @@\n #ifndef SDL_POWER_DISABLED\n #if SDL_POWER_MACOSX\n \n-#if defined(__ALTIVEC__) && !defined(MAC_OS_X_VERSION_10_5)\n+#if defined(__ALTIVEC__) && MAC_OS_X_VERSION_MIN_REQUIRED < 1050\n /* to cricumvent a bug in Mac OS X 10.4 SDK */\n #define vector __vector\n #include <CoreServices/CoreServices.h>\ndiff --git a/src/video/cocoa/SDL_cocoakeyboard.m b/src/video/cocoa/SDL_cocoakeyboard.m\nindex e973ef4..39e75f5 100755\n--- a/src/video/cocoa/SDL_cocoakeyboard.m\n+++ b/src/video/cocoa/SDL_cocoakeyboard.m\n@@ -426,7 +426,7 @@ HandleModifiers(_THIS, unsigned short scancode, unsigned int modifierFlags)\n static void\n UpdateKeymap(SDL_VideoData *data)\n {\n-#if defined(MAC_OS_X_VERSION_10_5)\n+#if MAC_OS_X_VERSION_REQUIRED >= 1050\n     TISInputSourceRef key_layout;\n     const void *chr_data;\n     int i;\ndiff --git a/src/video/cocoa/SDL_cocoamessagebox.m b/src/video/cocoa/SDL_cocoamessagebox.m\nindex 730d952..e528e9c 100755\n--- a/src/video/cocoa/SDL_cocoamessagebox.m\n+++ b/src/video/cocoa/SDL_cocoamessagebox.m\n@@ -22,7 +22,7 @@\n \n #if SDL_VIDEO_DRIVER_COCOA\n \n-#if defined(__APPLE__) && defined(__POWERPC__) && !defined(__APPLE_ALTIVEC__)\n+#if defined(__APPLE__) && defined(__POWERPC__) && defined(__ALTIVEC__)\n #include <altivec.h>\n #undef bool\n #undef vector\ndiff --git a/src/video/cocoa/SDL_cocoamouse.h b/src/video/cocoa/SDL_cocoamouse.h\nindex 03f1d50..2f5e87b 100755\n--- a/src/video/cocoa/SDL_cocoamouse.h\n+++ b/src/video/cocoa/SDL_cocoamouse.h\n@@ -25,7 +25,7 @@\n \n #include \"SDL_cocoavideo.h\"\n \n-#if !defined(MAC_OS_X_VERSION_10_5)\n+#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 && !defined(CGFLOAT_DEFINED)\n typedef float CGFloat;\n #endif\n \ndiff --git a/src/video/cocoa/SDL_cocoavideo.h b/src/video/cocoa/SDL_cocoavideo.h\nindex 757d8e5..6e27bac 100755\n--- a/src/video/cocoa/SDL_cocoavideo.h\n+++ b/src/video/cocoa/SDL_cocoavideo.h\n@@ -25,7 +25,7 @@\n \n #include \"SDL_opengl.h\"\n \n-#if defined(__ALTIVEC__) && !defined(MAC_OS_X_VERSION_10_5)\n+#if defined(__ALTIVEC__) && MAC_OS_X_VERSION_MIN_REQUIRED < 1050\n /* to cricumvent a bug in Mac OS X 10.4 SDK */\n #define vector __vector\n #include <CoreServices/CoreServices.h>\n@@ -45,7 +45,7 @@\n #include \"SDL_cocoaopengl.h\"\n #include \"SDL_cocoawindow.h\"\n \n-#if !defined(MAC_OS_X_VERSION_10_5)\n+#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 && !defined(NSINTEGER_DEFINED)\n typedef long int NSInteger;\n typedef unsigned int NSUInteger;\n #endif\ndiff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m\nindex 6766b71..1380192 100755\n--- a/src/video/cocoa/SDL_cocoavideo.m\n+++ b/src/video/cocoa/SDL_cocoavideo.m\n@@ -22,7 +22,7 @@\n \n #if SDL_VIDEO_DRIVER_COCOA\n \n-#if defined(__APPLE__) && defined(__POWERPC__) && !defined(__APPLE_ALTIVEC__)\n+#if defined(__APPLE__) && defined(__POWERPC__) && defined(__ALTIVEC__)\n #include <altivec.h>\n #undef bool\n #undef vector\ndiff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m\nindex ae125b4..3e9ea77 100755\n--- a/src/video/cocoa/SDL_cocoawindow.m\n+++ b/src/video/cocoa/SDL_cocoawindow.m\n@@ -102,7 +102,7 @@ ScheduleContextUpdates(SDL_WindowData *data)\n     NSOpenGLContext *currentContext = [NSOpenGLContext currentContext];\n     NSMutableArray *contexts = data->nscontexts;\n     @synchronized (contexts) {\n-#if defined(MAC_OS_X_VERSION_10_5)\n+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050\n         for (SDLOpenGLContext *context in contexts) {\n #else\n         /* old way to iterate */\n@@ -363,7 +363,7 @@ SetWindowStyle(SDL_Window * window, unsigned int style)\n        !!! FIXME:   http://bugzilla.libsdl.org/show_bug.cgi?id=1825\n     */\n     windows = [NSApp orderedWindows];\n-#if defined(MAC_OS_X_VERSION_10_5)\n+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050\n     for (NSWindow *win in windows) {\n #else\n     /* old way to iterate */\n@@ -1521,7 +1521,7 @@ Cocoa_DestroyWindow(_THIS, SDL_Window * window)\n         }\n \n         NSArray *contexts = [[data->nscontexts copy] autorelease];\n-#if defined(MAC_OS_X_VERSION_10_5)\n+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050\n         for (SDLOpenGLContext *context in contexts) {\n #else\n         /* old way to iterate */\n"
  },
  {
    "path": "scripts/pkg/README.md",
    "content": "# MegaZeux package scripts mirror\n\nMirrors of upstream package scripts and unsubmitted package scripts.\n\nSee /megazeux.spec and /debian for Fedora and Debian/Ubuntu, respectively.\n\n| Platform\t| Author(s)\t\t| Status\t\t| Upstream URL\t|\n|---------------|-----------------------|-----------------------|---------------|\n| alpine\t| asie\t\t\t| orphaned\t\t| https://gitlab.alpinelinux.org/alpine/aports/-/tree/master/testing/megazeux\n| archlinux\t| CyberShadow/GetDizzy\t| ok\t\t\t| https://aur.archlinux.org/packages/megazeux/\n| flatpak\t| Lachesis\t\t| not submitted\t\t| —\n| macports\t| Lachesis\t\t| not submitted\t\t| —\n| nix\t\t| Why-Fi\t\t| not submitted\t\t| —\n| voidlinux\t| asie\t\t\t| orphaned\t\t| https://github.com/void-linux/void-packages/tree/master/srcpkgs/megazeux\n"
  },
  {
    "path": "scripts/pkg/alpine/APKBUILD",
    "content": "# Contributor: Adrian Siekierka <kontakt@asie.pl>\n# Maintainer: Adrian Siekierka <kontakt@asie.pl>\npkgname=megazeux\npkgver=2.93b\npkgrel=0\npkgdesc=\"Game creation system\"\nurl=\"https://www.digitalmzx.com/\"\n# loongarch64: Test failure\narch=\"all !loongarch64\"\nlicense=\"GPL-2.0-or-later\"\nmakedepends=\"libpng-dev libvorbis-dev sdl2-dev zlib-dev\"\nsubpackages=\"$pkgname-doc\"\n_srcprefix=mzx${pkgver/.}\nsource=\"${_srcprefix}src.tar.xz::https://www.digitalmzx.com/download.php?latest=src&ver=$pkgver\"\nbuilddir=\"$srcdir/$_srcprefix/\"\n\nbuild() {\n\t./config.sh \\\n\t\t--platform unix \\\n\t\t--prefix /usr \\\n\t\t--bindir /usr/lib/megazeux \\\n\t\t--libdir /usr/lib \\\n\t\t--gamesdir /usr/bin\n\n\tmake\n}\n\ncheck() {\n\tmake test\n}\n\npackage() {\n\tmake DESTDIR=\"$pkgdir/\" install\n}\n\nsha512sums=\"\n23c7888d8e18f8002da0fc737182fdb67ffc6d7325670a86dcd7398a1634b118025154ca84c4013bfabc1ba50aaed863a0bf7f4013961f0e0c85e08328172cd7  mzx293bsrc.tar.xz\n\"\n"
  },
  {
    "path": "scripts/pkg/archlinux/PKGBUILD.template",
    "content": "# Original AUR Maintainer: Vladimir Panteleev <arch-pkg at thecybershadow.net>\n# Current Maintainer: Dylan Morrison <dylanjmorrison611 at gmail.com>\n\npkgname=megazeux\npkgver=UNSET\npkgrel=1\npkgdesc=\"Game creation system\"\narch=('i686' 'x86_64')\nurl=\"https://www.digitalmzx.com/\"\nlicense=('gpl')\ngroups=('games')\ndepends=(libvorbis libpng sdl2)\nmakedepends=()\n_base=mzx${pkgver/.}\n_filename=${_base}src.tar.xz\n_sha256sum=UNSET\n\n##old version\n#source=(\"${_filename}::https://www.digitalmzx.com/download.php?rid=2197&f=${_sha256sum}\")\n\n##new version\nsource=(\"${_filename}::https://www.digitalmzx.com/download.php?latest=src&ver=${pkgver}\")\n\nsha256sums=(\"${_sha256sum}\")\nDLAGENTS=(\"http::/usr/bin/curl -A 'Mozilla' -fLC - -o %o %u\")\n\nbuild() {\n  cd \"${srcdir}/${_base}\"\n\n  ./config.sh \\\n\t  --platform unix \\\n\t  --prefix /usr \\\n\t  --bindir /usr/lib/megazeux \\\n\t  --gamesdir /usr/bin\n\n  echo 'RAWLIBDIR=lib' >> platform.inc\n  echo 'LIBDIR=/usr/lib' >> platform.inc\n\n  make\n}\npackage() {\n  cd \"${srcdir}/${_base}\"\n\n  make DESTDIR=\"$pkgdir/\" install\n}\n\n# Local Variables:\n# pkgbuild-update-sums-on-save: nil\n# End:\n"
  },
  {
    "path": "scripts/pkg/archlinux/update-pkgbuild.sh",
    "content": "#!/usr/bin/env bash\n# Produce a working PKGBUILD for the AUR from a template given a tarball.\n# Requirements: mzx source tarball named in typical megazeux fashion like so:  mzx(version)src.tar.xz, bash, getopt.\n# e.g., mzx291gsrc.tar.xz\n\nMYFN=`basename \"$0\"`\n_V=0\n\nfunction usage () {\n\tcat <<EOUSAGE\nUsage:\n $MYFN <path>\n \nCreates a PKGBUILD by sha256summing specified MegaZeux tarball and editing a template.\n\nOptions:\n -h, --help    display this help and exit\"\n\nEOUSAGE\n}\n\nfunction log () {\n\tif [[ $_V -eq 1 ]]; then\n\t\techo \"$@\"\n\tfi\n}\n\nwhile getopts :hv opt ; do\n\tcase $opt in\n\t\tv)\n\t\t\t_V=1\n\t\t\t;;\n\t\th)\n\t\t\tusage\n\t\t\texit 1\n\t\t\t;;\n\t\t*)\n\t\t\techo \"Invalid option: -$OPTARG\" >&2\n\t\t\tusage\n\t\t\texit 2\n\t\t\t;;\n\tesac\ndone\nshift $((OPTIND - 1))\nif [[ \"$1\" == \"\" ]]\nthen\n\tusage\n\texit 1\nelse\n\tlog \"Generating PKGBUILD...\"\n\tsha256sum=($(sha256sum \"$1\"))\n\tlog \"Our checksum is $sha256sum\"\n\tregex=\"mzx([0-9])([0-9a-z]+)src\\.tar.+\"\n\tif [[ $1 =~ $regex ]]\n\tthen\n\t\tversion=\"${BASH_REMATCH[1]}.${BASH_REMATCH[2]}\"\n\t\tlog \"Our version is $version\"\n\t\tcp PKGBUILD.template PKGBUILD\n\t\tsed -i \"s/pkgver=UNSET/pkgver=$version/g\" PKGBUILD\n\t\tsed -i \"s/_sha256sum=UNSET/_sha256sum=$sha256sum/g\" PKGBUILD\n\t\techo \"Done.\"\n\telse\n\t\techo \"Error: Tarball does not appear to be MegaZeux. Read script for requirements.\"\n\tfi\nfi\n"
  },
  {
    "path": "scripts/pkg/flatpak/com.digitalmzx.MegaZeux.yml",
    "content": "app-id: com.digitalmzx.MegaZeux\nruntime: org.freedesktop.Platform\nruntime-version: '24.08'\nsdk: org.freedesktop.Sdk\ncommand: megazeux\nrename-desktop-file: megazeux.desktop\nrename-appdata-file: megazeux.metainfo.xml\nrename-icon: megazeux\ncleanup:\n  - /share/doc\n\nfinish-args:\n  # SDL application that expects OpenGL, Wayland, audio, gamepads to work.\n  # Network support exists but is currently disabled in Linux (not used yet).\n  - --device=dri\n  - --device=all\n  - --share=ipc\n  #- --share=network\n  - --socket=wayland\n  - --socket=fallback-x11\n  - --socket=pulseaudio\n  # MegaZeux is both a game engine and development kit and\n  # users may store game folders or create games anywhere.\n  - --filesystem=home\n  - --filesystem=/media\n  - --filesystem=/run/media\n\nmodules:\n  - name: MegaZeux\n    buildsystem: simple\n    build-commands:\n      - ./config.sh --platform unix --enable-release --enable-lto\n          --prefix /app\n          --sysconfdir /app/etc\n          --gamesdir /app/bin\n          --bindir /app/bin\n          --libdir /app/lib\n          --sharedir /app/share\n          --licensedir /app/share/licenses\n          --metainfodir /app/share/metainfo\n      - make -j${FLATPAK_BUILDER_N_JOBS}\n      - make -j${FLATPAK_BUILDER_N_JOBS} test\n      - make install\n    sources:\n       - type: git\n         url: https://github.com/AliceLR/megazeux.git\n         tag: v2.93d\n"
  },
  {
    "path": "scripts/pkg/macports/Portfile",
    "content": "# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4\n\nPortSystem          1.0\n\nname                MegaZeux\nversion             2.93c\ncategories          games\nplatforms           darwin\nlicense             GPL-2+\nmaintainers         @AliceLR\ndescription         Text mode game creation system\nlong_description    MegaZeux is a Game Creation System originally written for DOS in 1994.\\\n                    Hundreds of MegaZeux games are available for download at https://www.digitalmzx.com/\nhomepage            https://www.digitalmzx.com/\n\nmaster_sites        https://github.com/AliceLR/megazeux/releases/download/v2.93c/\ndistfiles           mzx293csrc.tar.xz\nuse_xz              yes\nworksrcdir          mzxgit\nchecksums           rmd160   4f751319ac1283ac1793472eae247592472d16e3 \\\n                    sha256   a878d0937324c6d8ff057348d4efd96effaf070b16b314b57031d7fae3b75974 \\\n                    size     4519284\n\ndepends_lib         port:zlib \\\n                    port:libpng \\\n                    port:libogg \\\n                    port:libvorbis \\\n                    port:libsdl2\n\nconfigure.cmd       ./config.sh\nconfigure.args      --platform darwin               \\\n                    --prefix ${prefix}              \\\n                    --gamesdir ${prefix}/bin        \\\n                    --bindir ${prefix}/lib/megazeux \\\n                    --sysconfdir ${prefix}/etc      \\\n                    --enable-release --enable-lto\n\nuse_parallel_build  yes\n\n# TODO: untested\nplatform darwin 8 {\n\tdepends_lib-append port:libsdl\n\tconfigure.args-append --enable-sdl1\n}\nplatform darwin 9 {\n\tdepends_lib-append port:libsdl\n\tconfigure.args-append --enable-sdl1\n}\nplatform darwin 10 {\n\tdepends_lib-append port:libsdl2-snowleopard\n}\n"
  },
  {
    "path": "scripts/pkg/nix/default.nix",
    "content": "{ stdenv, fetchurl, SDL2, libvorbis, libogg, libpng, which }:\n\nstdenv.mkDerivation rec {\n  pname = \"megazeux\";\n  version = \"2.92f\";\n\n  src = fetchurl {\n    name = \"${pname}-${version}.tar.xz\";\n    url = \"https://www.digitalmzx.com/download.php?latest=src&ver=${version}\";\n    sha256 = \"1g1r6dkf27b6pz85m0ykglrrm97yymfc45licw42rav4s0gar6r3\";\n  };\n\n  configureScript = \"./config.sh\";\n\n  dontAddPrefix = true;\n\n  # Platform will need to by dynamic for Darwin\n  preConfigure = ''\n    configureFlagsArray+=(\n      --platform unix\n      --prefix $prefix\n      --sysconfdir $out/etc/\n      --gamesdir $out/bin\n      --bindir $out/lib/megazeux/${version}/\n      --enable-release\n    )\n  '';\n\n  nativeBuildInputs = [\n    SDL2\n    libvorbis\n    libogg\n    libpng\n    which\n  ];\n  \n  enableParallelBuilding = true;\n\n  meta = with stdenv.lib; {\n    description = \"A Game Creation System (GCS)\";\n    longDescription = \"MegaZeux is a Game Creation System (GCS) originally developed in 1994 by Alexis Janson and still maintained today.\\n\";\n    homepage = \"https://www.digitalmzx.com/\";\n    license = licenses.gpl2;\n  };\n}\n"
  },
  {
    "path": "scripts/pkg/voidlinux/template",
    "content": "# Template file for 'megazeux'\npkgname=megazeux\nversion=2.92f\nrevision=1\nbuild_style=configure\nconfigure_script=\"./config.sh\"\nconfigure_args=\"--platform unix --enable-release --bindir /usr/lib/megazeux\n --gamesdir /usr/bin --disable-updater\"\nmakedepends=\"SDL2-devel libvorbis-devel libpng-devel\"\nshort_desc=\"Game creation system\"\nmaintainer=\"Orphaned <orphan@voidlinux.org>\"\nlicense=\"GPL-2.0-or-later\"\nhomepage=\"http://www.digitalmzx.net/\"\ndistfiles=\"http://vault.digitalmzx.net/download.php?latest=src&ver=${version}>${pkgname}-${version}.tar.xz\"\nchecksum=239bac1ed064ab2c08679116c25cf5fea49a337dd3835ad0bf661de1663339bc\n"
  },
  {
    "path": "scripts/pkg/voidlinux/update",
    "content": "pattern='latest=src\"[^>]*>\\K[.\\da-z]+(?=[<])'\n"
  },
  {
    "path": "src/Makefile.in",
    "content": "##\n# MegaZeux Makefile fragment\n##\n\ninclude contrib/gdm2s3m/src/Makefile.in\ninclude contrib/icons/Makefile.in\ninclude contrib/libmodplug/src/Makefile.in\ninclude contrib/libxmp/Makefile.megazeux\n\n.PHONY: mzx_clean core_target_clean\n\n#\n# This allows selection of g++ for linking instead of gcc.\n# Linking with g++ is only necessary if linking the C++ standard library\n# (libstdc++ or libc++) is required; usually it isn't, and gcc can be used.\n#\nLINK_CC ?= ${CC}\n\ncore_src = src\ncore_obj = src/.build\n\naudio_src = src/audio\naudio_obj = src/audio/.build\n\nio_src = src/io\nio_obj = src/io/.build\n\nnetwork_src = src/network\nnetwork_obj = src/network/.build\n\ncore_flags   += -ffast-math -funsigned-char\ncore_flags   += -Wmissing-format-attribute\ncore_cflags  += ${CFLAGS}\ncore_cxxflags = ${CXXFLAGS}\n\nifneq (${PLATFORM},emscripten)\ncore_flags   += -I${PREFIX}/include\nendif\n\nifeq (${PLATFORM},mingw)\ncore_spec := -DCORE_LIBSPEC=\"__declspec(dllexport)\"\nendif\n\n${core_obj}/main.o: ${core_src}/main.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} -c $< -o $@\n\n${core_obj}/run_stubs.o: ${core_src}/run_stubs.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} -c $< -o $@\n\n${core_obj}/updater.o: ${core_src}/updater.cpp\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CC} -MD ${core_cxxflags} ${core_flags} -c $< -o $@\n\n${audio_obj}/%.o: ${audio_src}/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} ${core_spec} -c $< -o $@\n\n${audio_obj}/%.o: ${audio_src}/%.cpp\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CC} -MD ${core_cxxflags} ${core_flags} ${core_spec} -c $< -o $@\n\n${io_obj}/%.o: ${io_src}/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} ${core_spec} -c $< -o $@\n\n${network_obj}/%.o: ${network_src}/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} ${core_spec} -c $< -o $@\n\n${network_obj}/%.o: ${network_src}/%.cpp\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CXX} -MD ${core_cxxflags} ${core_flags} ${core_spec} -c $< -o $@\n\n${core_obj}/%.o: ${core_src}/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${core_flags} ${core_spec} -c $< -o $@\n\n${core_obj}/%.o: ${core_src}/%.cpp\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CXX} -MD ${core_cxxflags} ${core_flags} ${core_spec} -c $< -o $@\n\n#\n# Lists mandatory C language sources (mangled to object names) required\n# to build the main binary. Please keep this sorted alphabetically.\n#\ncore_cobjs := \\\n  ${core_obj}/about.o             \\\n  ${core_obj}/block.o             \\\n  ${core_obj}/board.o             \\\n  ${core_obj}/caption.o           \\\n  ${core_obj}/configure.o         \\\n  ${core_obj}/core.o              \\\n  ${core_obj}/core_task.o         \\\n  ${core_obj}/counter.o           \\\n  ${core_obj}/data.o              \\\n  ${core_obj}/error.o             \\\n  ${core_obj}/event.o             \\\n  ${core_obj}/expr.o              \\\n  ${core_obj}/game.o              \\\n  ${core_obj}/game_menu.o         \\\n  ${core_obj}/game_ops.o          \\\n  ${core_obj}/game_player.o       \\\n  ${core_obj}/game_update.o       \\\n  ${core_obj}/game_update_board.o \\\n  ${core_obj}/graphics.o          \\\n  ${core_obj}/idarray.o           \\\n  ${core_obj}/idput.o             \\\n  ${core_obj}/intake.o            \\\n  ${core_obj}/intake_num.o        \\\n  ${core_obj}/legacy_rasm.o       \\\n  ${core_obj}/legacy_board.o      \\\n  ${core_obj}/legacy_robot.o      \\\n  ${core_obj}/legacy_world.o      \\\n  ${core_obj}/mzm.o               \\\n  ${core_obj}/platform_time.o     \\\n  ${core_obj}/pngops.o            \\\n  ${core_obj}/render.o            \\\n  ${core_obj}/robot.o             \\\n  ${core_obj}/run_robot.o         \\\n  ${core_obj}/scrdisp.o           \\\n  ${core_obj}/settings.o          \\\n  ${core_obj}/sprite.o            \\\n  ${core_obj}/str.o               \\\n  ${core_obj}/util.o              \\\n  ${core_obj}/window.o            \\\n  ${core_obj}/world.o             \\\n  ${io_obj}/fsafeopen.o           \\\n  ${io_obj}/path.o                \\\n  ${io_obj}/vfs.o                 \\\n  ${io_obj}/vio.o                 \\\n  ${io_obj}/zip.o                 \\\n  ${io_obj}/zip_stream.o\n\n#\n# Lists mandatory C++ language sources (mangled to object names) required\n# to build the main binary. Currently there are only optional sources.\n#\ncore_cxxobjs :=\n\nrender_layer_software = 0\n\n#\n# These are really just hacks and should be moved elsewhere\n#\nifeq (${BUILD_NDS},1)\ncore_cobjs += arch/nds/protected_palette.bin.o\ncore_cobjs += arch/nds/audio.o\ncore_cobjs += arch/nds/platform.o arch/nds/event.o\ncore_cobjs += arch/nds/render.o arch/nds/evq.o\nifeq (${BUILD_EXTRAM},1)\ncore_cobjs += arch/nds/dlmalloc.o\ncore_cobjs += arch/nds/ram.o arch/nds/extmem.o\nendif\nendif\n\nifeq (${BUILD_3DS},1)\n# SDL and platform_sdl.c takes care of all of this when enabled.\nifeq (${BUILD_SDL},)\ncore_cobjs += arch/3ds/shader_2d.shbin.o arch/3ds/shader_playfield.shbin.o\ncore_cobjs += arch/3ds/platform.o arch/3ds/render.o\ncore_cobjs += arch/3ds/event.o arch/3ds/audio.o\ncore_cobjs += arch/3ds/keyboard.o\nendif\nendif\n\nifeq (${BUILD_WII},1)\n# SDL and platform_sdl.c takes care of all of this when enabled.\nifeq (${BUILD_SDL},)\ncore_cobjs += arch/wii/event.o arch/wii/platform.o\ncore_cobjs += arch/wii/render_xfb.o\nrender_layer_software = 1\nifeq (${BUILD_RENDER_GX},1)\ncore_cobjs += arch/wii/render_gx.o\nendif\nifeq (${BUILD_AUDIO},1)\ncore_cobjs += arch/wii/audio.o\nendif\nendif\n# Required regardless of whether SDL is enabled or not...\nifeq (${BUILD_NETWORK},1)\ncore_cxxobjs += arch/wii/network.o\nendif\nendif\n\nifeq (${BUILD_SWITCH},1)\n# Switch libraries require C++\nLINK_CC := ${CXX}\nendif\n\nifeq (${BUILD_PSP},1)\ncore_cobjs += arch/psp/platform.o\nendif\n\nifeq (${BUILD_DJGPP},1)\ncore_cobjs += arch/djgpp/event.o arch/djgpp/interrupt.o arch/djgpp/platform.o\ncore_cobjs += arch/djgpp/audio.o\ncore_cobjs += arch/djgpp/render_ega.o\nifeq (${BUILD_DOS_SVGA},1)\ncore_cobjs += arch/djgpp/render_svga.o\nrender_layer_software = 1\nendif\nendif\n\nifeq (${BUILD_DREAMCAST},1)\ncore_cobjs += arch/dreamcast/platform.o arch/dreamcast/render_fb.o\ncore_cobjs += arch/dreamcast/audio.o arch/dreamcast/event.o\ncore_cobjs += arch/dreamcast/render.o\nrender_layer_software = 1\nendif\n\n#\n# Other modular stuff\n#\n\nifeq (${BUILD_HELPSYS},1)\ncore_cobjs += ${core_obj}/helpsys.o\nendif\n\nifeq (${BUILD_EXTRAM},1)\ncore_cobjs += ${core_obj}/extmem.o\nendif\n\nifeq (${BUILD_RENDER_SOFT},1)\ncore_cobjs += ${core_obj}/render_soft.o\nrender_layer_software = 1\nendif\n\n# Accelerated SDL software renderer\nifeq (${BUILD_RENDER_SOFTSCALE},1)\ncore_cobjs += ${core_obj}/render_softscale.o\nrender_layer_software = 1\nendif\n\n# Accelerated SDL fixed function hardware renderer\nifeq (${BUILD_RENDER_SDLACCEL},1)\ncore_cobjs += ${core_obj}/render_sdlaccel.o\nendif\n\n# GL shared\nifneq ($(or ${BUILD_RENDER_GL_FIXED},${BUILD_RENDER_GL_PROGRAM}),)\ncore_cobjs += ${core_obj}/render_gl.o\nendif\n\n# Fixed function renderers (OGL=1, OGLES=1)\nifeq (${BUILD_RENDER_GL_FIXED},1)\ncore_cobjs += ${core_obj}/render_gl1.o ${core_obj}/render_gl2.o\nrender_layer_software = 1\nendif\n\n# Shader renderers (OGL>=2, OGLES=2)\nifeq (${BUILD_RENDER_GL_PROGRAM},1)\ncore_cobjs += ${core_obj}/render_glsl.o\nrender_layer_software = 1\nendif\n\n#\n# Currently only usable with GL, but could theoretically be used\n# with OpenVG in the future.\n#\nifeq (${BUILD_EGL},1)\ncore_cobjs += ${core_obj}/render_egl.o\ncore_ldflags += -lEGL\nendif\n\n# Overlay renderers\nifeq (${BUILD_RENDER_YUV},1)\ncore_cobjs += ${core_obj}/render_yuv.o\nrender_layer_software = 1\nendif\n\n# GP2X Half-resolution renderer\nifeq (${BUILD_RENDER_GP2X},1)\ncore_cobjs += ${core_obj}/render_gp2x.o\nendif\n\n# Screenshots require the software layer renderer.\nifeq (${BUILD_ENABLE_SCREENSHOTS},1)\nrender_layer_software = 1\nendif\n\n# The editor requires the software layer renderer\n# (board screenshot export).\nifeq (${BUILD_EDITOR},1)\nrender_layer_software = 1\nendif\n\n# Software layer renderer\n# This one tends to take forever for many builds. Prefix it to\n# the list so hopefully it starts sooner during parallel builds.\nifeq (${render_layer_software},1)\ncore_cobjs := ${core_obj}/render_layer.o ${core_cobjs}\nendif\n\nifeq (${BUILD_MODPLUG},1)\ncore_flags += -I${libmodplug_src} -I${libmodplug_src}/libmodplug -DHAVE_INTTYPES_H\ncore_cxxobjs += ${audio_obj}/audio_modplug.o ${libmodplug_objs}\nendif\n\nifeq (${BUILD_MIKMOD},1)\ncore_flags += ${MIKMOD_CFLAGS}\ncore_ldflags += ${MIKMOD_LDFLAGS}\ncore_cobjs += ${audio_obj}/audio_mikmod.o\nendif\n\nifeq (${BUILD_XMP},1)\ncore_cflags += ${XMP_CFLAGS}\ncore_ldflags += ${XMP_LDFLAGS}\ncore_cobjs += ${audio_obj}/audio_xmp.o\ncore_flags += ${libxmp_include}\ncore_cobjs += ${libxmp_objs}\nendif\n\nifeq (${BUILD_OPENMPT},1)\ncore_flags += ${OPENMPT_CFLAGS}\ncore_ldflags += ${OPENMPT_LDFLAGS}\ncore_cobjs += ${audio_obj}/audio_openmpt.o\nendif\n\nifeq (${BUILD_REALITY},1)\ncore_cxxobjs += ${audio_obj}/audio_reality.o\nendif\n\nifeq (${LIBPNG},1)\ncore_flags += $(LIBPNG_CFLAGS)\ncore_ldflags += $(LIBPNG_LDFLAGS)\nendif\n\nifeq (${BUILD_AUDIO},1)\nifeq (${BUILD_NDS},1)\n# NDS has a custom audio implementation\naudio_cobjs := \\\n ${audio_obj}/audio.o          \\\n ${audio_obj}/ext.o            \\\n ${audio_obj}/sfx.o\nelse\naudio_cobjs := \\\n ${audio_obj}/audio.o          \\\n ${audio_obj}/audio_pcs.o      \\\n ${audio_obj}/audio_wav.o      \\\n ${audio_obj}/ext.o            \\\n ${audio_obj}/sfx.o\n\naudio_cxxobjs := \\\n ${audio_obj}/sampled_stream.o\n\nifneq (${VORBIS},)\ncore_flags += ${VORBIS_CFLAGS}\ncore_ldflags += ${VORBIS_LDFLAGS}\naudio_cobjs += \\\n ${audio_obj}/audio_vorbis.o\nendif\nendif\n\ncore_cobjs += ${audio_cobjs}\ncore_cxxobjs += ${audio_cxxobjs}\nelse\n# The editor needs this for sfx_strs...\nifeq (${BUILD_EDITOR},1)\ncore_cobjs += ${audio_obj}/sfx.o\nendif\nendif\n\nifneq (${BUILD_SDL},)\n\ncore_cobjs += ${core_obj}/event_sdl.o ${core_obj}/platform_sdl.o\ncore_cobjs += ${core_obj}/render_sdl.o\n\nifeq (${BUILD_AUDIO},1)\nifeq (${BUILD_SDL},3)\ncore_cobjs += ${audio_obj}/driver_sdl3.o\nelse\ncore_cobjs += ${audio_obj}/audio_sdl.o\nendif\nendif\n\ncore_flags   += $(SDL_CFLAGS)\ncore_ldflags += $(SDL_LDFLAGS)\n\nifeq (${BUILD_MODULAR},1)\nmzx_ldflags    += $(SDL_LDFLAGS)\nmzxrun_ldflags += $(SDL_LDFLAGS)\nendif\n\nelse\n\nifeq ($(or ${BUILD_WII},${BUILD_NDS},${BUILD_3DS},${BUILD_DJGPP},${BUILD_DREAMCAST}),)\n\ncore_cobjs += ${core_obj}/platform_dummy.o\n\n#\n# Normally SDL would provide us with pthread, but in the case\n# we're not using SDL or any other platform mutex implementation,\n# we must link pthread ourselves.\n#\nifeq (${PTHREAD},1)\ncore_ldflags += ${PTHREAD_LDFLAGS}\nendif\n\nendif\n\nendif\n\n# X11 (headers included by SDL_syswm.h).\n# Libraries are only required for EGL, pre-SDL2 clipboard (see editor).\nifneq (${X11DIR},)\ncore_flags += ${X11_CFLAGS}\nifeq (${BUILD_EGL},1)\ncore_ldflags += ${X11_LDFLAGS}\nendif\nendif\n\nifeq (${BUILD_NETWORK},1)\ncore_cobjs += ${network_obj}/sha256.o\ncore_cxxobjs += \\\n  ${network_obj}/DNS.o \\\n  ${network_obj}/Host.o \\\n  ${network_obj}/HTTPHost.o \\\n  ${network_obj}/Manifest.o \\\n  ${network_obj}/Socket.o \\\n  ${network_obj}/network.o\n\nifeq (${BUILD_UPDATER},1)\nmzx_objs += ${core_obj}/updater.o\nendif\n\nifeq (${PLATFORM},amiga)\ncore_ldflags += -lnet\nendif\n\nendif\n\n#\n# Append mandatory libraries last.\n#\ncore_cflags := ${core_cflags} ${ZLIB_CFLAGS}\ncore_ldflags := ${core_ldflags} ${ZLIB_LDFLAGS} -lm\n\nifeq (${BUILD_DEBYTECODE},1)\ncore_cobjs += ${core_obj}/rasm.o\nendif\n\n#\n# Link libstdc++-less compatibility implementations of various functions.\n# Doing this instead of linking libstdc++ when possible avoids pulling in\n# external libraries for MinGW and (particularly) Android builds.\n#\nifneq (${core_cxxobjs},)\n# Exception: force C++ linking when building against sanitizers.\nifneq (${SANITIZER},)\nLINK_CC := ${CXX}\nendif\nifneq (${LINK_CC},${CXX})\ncore_cxxobjs := ${core_obj}/nostdc++.o ${core_cxxobjs}\nendif\nendif\n\ncore_objs := ${core_cobjs} ${core_cxxobjs}\n\n-include ${core_objs:.o=.d}\n\n${core_objs}: $(filter-out $(wildcard ${core_obj}), ${core_obj})\n${core_objs}: $(filter-out $(wildcard ${audio_obj}), ${audio_obj})\n${core_objs}: $(filter-out $(wildcard ${io_obj}), ${io_obj})\n${core_objs}: $(filter-out $(wildcard ${network_obj}), ${network_obj})\n\nifeq (${BUILD_MODULAR},1)\n\ncore_target := ${DSOPRE}core${DSOPOST}\n\n${core_target}: ${core_objs}\n\t$(if ${V},,@echo \"  LINK    \" $@)\n\t${LINK_CC} ${DSOLDFLAGS} -o $@ ${core_objs} ${LDFLAGS} \\\n\t  ${DSOSONAME}$@ ${core_ldflags} ${ARCH_LIBS}\n\ncore_target_clean:\n\t$(if ${V},,@echo \"  RM      \" ${core_target} ${core_target}.debug)\n\t${RM} ${core_target} ${core_target}.debug\n\nelse\n\ncore_target := ${core_objs}\n\ncore_target_clean:\n\nendif\n\nifeq (${BUILD_EDITOR},1)\n\ninclude src/editor/Makefile.in\n\nmzx_objs := ${mzx_objs} ${core_obj}/main.o\n\n-include ${mzx_objs:.o=.d}\n\nifeq (${EMBED_ICONS},1)\nmzx_objs += ${icons}\nendif\n\nifeq (${BUILD_MODULAR},1)\n${mzx}.debug: ${core_target}.debug ${editor_target}.debug\nendif\n\n${mzx}: ${core_target} ${editor_target} ${mzx_objs}\n\t$(if ${V},,@echo \"  LINK    \" ${mzx})\nifeq (${BUILD_MODULAR},1)\n\t${LINK_CC} ${mzx_objs} -o ${mzx} ${ARCH_EXE_LDFLAGS} ${LDFLAGS} \\\n\t  ${DSORPATH} -L. -lcore -leditor \\\n\t  ${mzx_ldflags} ${ARCH_LIBS}\nelse\n\t${LINK_CC} ${mzx_objs} ${core_target} ${editor_target} \\\n\t  -o ${mzx} ${ARCH_EXE_LDFLAGS} ${LDFLAGS} \\\n\t  ${core_ldflags} ${editor_ldflags} ${ARCH_LIBS}\nendif\n\nmzx_clean: editor_clean\n\nelse\n\n.PHONY: ${mzx}.debug\n${mzx}:\n${mzx}.debug:\n\nendif\n\nifeq (${BUILD_MZXRUN},1)\n\nmzxrun_objs := ${core_obj}/main.o ${core_obj}/run_stubs.o\n\n-include ${mzxrun_objs:.o=.d}\n\nifeq (${EMBED_ICONS},1)\nmzxrun_objs += ${icons}\nendif\n\nifeq (${BUILD_MODULAR},1)\n${mzxrun}.debug: ${core_target}.debug\nendif\n\n${mzxrun}: ${core_target} ${mzxrun_objs}\n\t$(if ${V},,@echo \"  LINK    \" ${mzxrun})\nifeq (${BUILD_MODULAR},1)\n\t${LINK_CC} ${mzxrun_objs} -o ${mzxrun} ${ARCH_EXE_LDFLAGS} ${LDFLAGS} \\\n\t  ${DSORPATH} -L. -lcore ${mzxrun_ldflags} ${ARCH_LIBS}\nelse\n\t${LINK_CC} ${mzxrun_objs} ${core_target} \\\n\t  -o ${mzxrun} ${ARCH_EXE_LDFLAGS} ${LDFLAGS} ${core_ldflags} ${ARCH_LIBS}\nendif\n\nelse\n\n.PHONY: ${mzxrun}.debug\n${mzxrun}:\n${mzxrun}.debug:\n\nendif\n\nmzx_clean: core_target_clean gdm2s3m_clean icons_clean libmodplug_clean libxmp_clean\n\t$(if ${V},,@echo \"  RM      \" ${core_obj} ${audio_obj} ${io_obj} ${network_obj})\n\t${RM} -r ${core_obj} ${audio_obj} ${io_obj} ${network_obj}\n\t$(if ${V},,@echo \"  RM      \" ${mzxrun} ${mzxrun}.debug)\n\t${RM} ${mzxrun} ${mzxrun}.debug\n\t$(if ${V},,@echo \"  RM      \" ${mzx} ${mzx}.debug)\n\t${RM} ${mzx} ${mzx}.debug\n"
  },
  {
    "path": "src/SDLmzx.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __SDLMZX_H\n#define __SDLMZX_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#if defined(CONFIG_SDL)\n#if CONFIG_SDL == 3\n#include <SDL3/SDL.h>\n#include <SDL3/SDL_version.h>\n#else\n#include <SDL.h>\n#if defined(_WIN32) || defined(CONFIG_X11)\n#include <SDL_syswm.h>\n#endif\n#endif\n\n#include <limits.h>\n\n/* SDL1 backwards compatibility for SDL2 ************************************/\n\n#if !SDL_VERSION_ATLEAST(2,0,0)\n\n// This block remaps anything that is EXACTLY equivalent to its new SDL 2.0\n// counterpart. More complex changes are handled with #ifdefs \"in situ\".\n\n// Data types\ntypedef SDLKey SDL_Keycode;\ntypedef int (*SDL_ThreadFunction)(void *);\ntypedef Uint32 SDL_threadID;\n// Use a macro because sdl1.2-compat typedefs SDL_Window...\n#define SDL_Window void\n\n// Macros / enumerants\n\n#define SDLK_KP_0         SDLK_KP0\n#define SDLK_KP_1         SDLK_KP1\n#define SDLK_KP_2         SDLK_KP2\n#define SDLK_KP_3         SDLK_KP3\n#define SDLK_KP_4         SDLK_KP4\n#define SDLK_KP_5         SDLK_KP5\n#define SDLK_KP_6         SDLK_KP6\n#define SDLK_KP_7         SDLK_KP7\n#define SDLK_KP_8         SDLK_KP8\n#define SDLK_KP_9         SDLK_KP9\n#define SDLK_NUMLOCKCLEAR SDLK_NUMLOCK\n#define SDLK_SCROLLLOCK   SDLK_SCROLLOCK\n#define SDLK_PAUSE        SDLK_BREAK\n\n#define SDL_WINDOW_FULLSCREEN  SDL_FULLSCREEN\n#define SDL_WINDOW_RESIZABLE   SDL_RESIZABLE\n#define SDL_WINDOW_OPENGL      SDL_OPENGL\n\n// API functions\n\n#define SDL_SetEventFilter(filter, userdata) SDL_SetEventFilter(filter)\n\n#if defined(_WIN32) || defined(CONFIG_X11)\nstatic inline SDL_bool SDL_GetWindowWMInfo(SDL_Window *window,\n                                           SDL_SysWMinfo *info)\n{\n  return SDL_GetWMInfo(info) == 1 ? SDL_TRUE : SDL_FALSE;\n}\n#endif\n\nstatic inline SDL_Window *SDL_GetWindowFromID(Uint32 id)\n{\n  return NULL;\n}\n\nstatic inline void SDL_WarpMouseInWindow(SDL_Window *window, int x, int y)\n{\n  SDL_WarpMouse(x, y);\n}\n\nstatic inline void SDL_SetWindowTitle(SDL_Window *window, const char *title)\n{\n  SDL_WM_SetCaption(title, \"\");\n}\n\nstatic inline void SDL_SetWindowIcon(SDL_Window *window, SDL_Surface *icon)\n{\n   SDL_WM_SetIcon(icon, NULL);\n}\n\nstatic inline char *SDL_GetCurrentVideoDriver(void)\n{\n  static char namebuf[16];\n  return SDL_VideoDriverName(namebuf, 16);\n}\n\nstatic inline int SDL_JoystickInstanceID(SDL_Joystick *joystick)\n{\n  return SDL_JoystickIndex(joystick);\n}\n\n#if defined(CONFIG_RENDER_GL_FIXED) || defined(CONFIG_RENDER_GL_PROGRAM)\nstatic inline int SDL_GL_SetSwapInterval(int interval)\n{\n#if SDL_VERSION_ATLEAST(1,2,10)\n  return SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, interval);\n#endif\n  return 0;\n}\n#endif\n\n#ifdef _WIN32\nstatic inline HWND SDL_GetWindowProperty_HWND(SDL_Window *window)\n{\n  SDL_SysWMinfo info;\n  SDL_VERSION(&info.version);\n  SDL_GetWindowWMInfo(window, &info);\n  return info.window;\n}\n#endif /* _WIN32 */\n\n#elif !SDL_VERSION_ATLEAST(3,0,0)\n\n#ifdef _WIN32\nstatic inline HWND SDL_GetWindowProperty_HWND(SDL_Window *window)\n{\n  SDL_SysWMinfo info;\n  SDL_VERSION(&info.version);\n  SDL_GetWindowWMInfo(window, &info);\n  return info.info.win.window;\n}\n#endif /* _WIN32 */\n\n#else /* SDL_VERSION_ATLEAST(3,0,0) */\n\n#ifdef _WIN32\nstatic inline void *SDL_GetWindowProperty_HWND(SDL_Window *window)\n{\n  return SDL_GetPointerProperty(SDL_GetWindowProperties(window),\n   SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);\n}\n#endif /* _WIN32 */\n\n#endif /* SDL_VERSION_ATLEAST(3,0,0) */\n\n\n/* SDL 1/2 backwards compatibility for SDL3 *********************************/\n\n/**\n * SDL_audio.h\n */\n#if !SDL_VERSION_ATLEAST(3,0,0)\n#define SDL_AUDIO_U8    AUDIO_U8\n#define SDL_AUDIO_S8    AUDIO_S8\n#define SDL_AUDIO_S16   AUDIO_S16SYS\n#define SDL_AUDIO_S16LE AUDIO_S16LSB\n#define SDL_AUDIO_S16BE AUDIO_S16MSB\n#else\n/* SDL3 defines SDL_FreeWAV to an old name type, but MZX needs to keep it to\n * transparently support older versions. */\n#undef SDL_FreeWAV\n#define SDL_FreeWAV(w)  SDL_free(w)\n#endif\n\n/**\n * SDL_event.h\n */\n#if !SDL_VERSION_ATLEAST(3,0,0)\n#define SDL_EVENT_FINGER_DOWN           SDL_FINGERDOWN\n#define SDL_EVENT_FINGER_MOTION         SDL_FINGERMOTION\n#define SDL_EVENT_FINGER_UP             SDL_FINGERUP\n#define SDL_EVENT_GAMEPAD_AXIS_MOTION   SDL_CONTROLLERAXISMOTION\n#define SDL_EVENT_JOYSTICK_ADDED        SDL_JOYDEVICEADDED\n#define SDL_EVENT_JOYSTICK_REMOVED      SDL_JOYDEVICEREMOVED\n#define SDL_EVENT_JOYSTICK_AXIS_MOTION  SDL_JOYAXISMOTION\n#define SDL_EVENT_JOYSTICK_BUTTON_DOWN  SDL_JOYBUTTONDOWN\n#define SDL_EVENT_JOYSTICK_BUTTON_UP    SDL_JOYBUTTONUP\n#define SDL_EVENT_JOYSTICK_HAT_MOTION   SDL_JOYHATMOTION\n#define SDL_EVENT_KEY_DOWN              SDL_KEYDOWN\n#define SDL_EVENT_KEY_UP                SDL_KEYUP\n#define SDL_EVENT_MOUSE_BUTTON_DOWN     SDL_MOUSEBUTTONDOWN\n#define SDL_EVENT_MOUSE_BUTTON_UP       SDL_MOUSEBUTTONUP\n#define SDL_EVENT_MOUSE_MOTION          SDL_MOUSEMOTION\n#define SDL_EVENT_MOUSE_WHEEL           SDL_MOUSEWHEEL\n#define SDL_EVENT_TEXT_INPUT            SDL_TEXTINPUT\n#define SDL_EVENT_QUIT                  SDL_QUIT\n#define SDL_EVENT_FIRST                 SDL_FIRSTEVENT\n#define SDL_EVENT_LAST                  SDL_LASTEVENT\n#define TFINGER_TOUCH_ID(tfinger)       ((tfinger).touchId)\n#endif\n#if SDL_VERSION_ATLEAST(3,0,0)\n#define TFINGER_TOUCH_ID(tfinger)       ((tfinger).touchID)\n#endif\n\n/**\n * SDL_gamepad.h (formerly SDL_gamecontroller.h)\n */\n#if !SDL_VERSION_ATLEAST(3,0,0) && SDL_VERSION_ATLEAST(2,0,0)\ntypedef SDL_GameController        SDL_Gamepad;\ntypedef SDL_GameControllerAxis    SDL_GamepadAxis;\ntypedef SDL_GameControllerButton  SDL_GamepadButton;\n#define SDL_INIT_GAMEPAD                  SDL_INIT_GAMECONTROLLER\n#define SDL_GAMEPAD_AXIS_INVALID          SDL_CONTROLLER_AXIS_INVALID\n#define SDL_GAMEPAD_AXIS_LEFTX            SDL_CONTROLLER_AXIS_LEFTX\n#define SDL_GAMEPAD_AXIS_LEFTY            SDL_CONTROLLER_AXIS_LEFTY\n#define SDL_GAMEPAD_AXIS_RIGHTX           SDL_CONTROLLER_AXIS_RIGHTX\n#define SDL_GAMEPAD_AXIS_RIGHTY           SDL_CONTROLLER_AXIS_RIGHTY\n#define SDL_GAMEPAD_AXIS_LEFT_TRIGGER     SDL_CONTROLLER_AXIS_TRIGGERLEFT\n#define SDL_GAMEPAD_AXIS_RIGHT_TRIGGER    SDL_CONTROLLER_AXIS_TRIGGERRIGHT\n#define SDL_GAMEPAD_AXIS_COUNT            SDL_CONTROLLER_AXIS_MAX\n#define SDL_GAMEPAD_BUTTON_INVALID        SDL_CONTROLLER_BUTTON_INVALID\n#define SDL_GAMEPAD_BUTTON_SOUTH          SDL_CONTROLLER_BUTTON_A\n#define SDL_GAMEPAD_BUTTON_EAST           SDL_CONTROLLER_BUTTON_B\n#define SDL_GAMEPAD_BUTTON_WEST           SDL_CONTROLLER_BUTTON_X\n#define SDL_GAMEPAD_BUTTON_NORTH          SDL_CONTROLLER_BUTTON_Y\n#define SDL_GAMEPAD_BUTTON_BACK           SDL_CONTROLLER_BUTTON_BACK\n#define SDL_GAMEPAD_BUTTON_GUIDE          SDL_CONTROLLER_BUTTON_GUIDE\n#define SDL_GAMEPAD_BUTTON_START          SDL_CONTROLLER_BUTTON_START\n#define SDL_GAMEPAD_BUTTON_LEFT_STICK     SDL_CONTROLLER_BUTTON_LEFTSTICK\n#define SDL_GAMEPAD_BUTTON_RIGHT_STICK    SDL_CONTROLLER_BUTTON_RIGHTSTICK\n#define SDL_GAMEPAD_BUTTON_LEFT_SHOULDER  SDL_CONTROLLER_BUTTON_LEFTSHOULDER\n#define SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER SDL_CONTROLLER_BUTTON_RIGHTSHOULDER\n#define SDL_GAMEPAD_BUTTON_DPAD_UP        SDL_CONTROLLER_BUTTON_DPAD_UP\n#define SDL_GAMEPAD_BUTTON_DPAD_DOWN      SDL_CONTROLLER_BUTTON_DPAD_DOWN\n#define SDL_GAMEPAD_BUTTON_DPAD_LEFT      SDL_CONTROLLER_BUTTON_DPAD_LEFT\n#define SDL_GAMEPAD_BUTTON_DPAD_RIGHT     SDL_CONTROLLER_BUTTON_DPAD_RIGHT\n#define SDL_GAMEPAD_BUTTON_MISC1          SDL_CONTROLLER_BUTTON_MISC1\n#define SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1  SDL_CONTROLLER_BUTTON_PADDLE1\n#define SDL_GAMEPAD_BUTTON_LEFT_PADDLE1   SDL_CONTROLLER_BUTTON_PADDLE2\n#define SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2  SDL_CONTROLLER_BUTTON_PADDLE3\n#define SDL_GAMEPAD_BUTTON_LEFT_PADDLE2   SDL_CONTROLLER_BUTTON_PADDLE4\n#define SDL_GAMEPAD_BUTTON_TOUCHPAD       SDL_CONTROLLER_BUTTON_TOUCHPAD\n#define SDL_GAMEPAD_BUTTON_COUNT          SDL_CONTROLLER_BUTTON_MAX\n#define SDL_IsGamepad(device_id)          SDL_IsGameController(device_id)\n#define SDL_OpenGamepad(device_id)        SDL_GameControllerOpen(device_id)\n#define SDL_CloseGamepad(g)               SDL_GameControllerClose(g)\n#define SDL_GetGamepadAxisFromString(s)   SDL_GameControllerGetAxisFromString(s)\n#define SDL_GetGamepadButtonFromString(s) SDL_GameControllerGetButtonFromString(s)\n#define SDL_AddGamepadMapping(m)          SDL_GameControllerAddMapping(m)\n#define SDL_AddGamepadMappingsFromFile(f) SDL_GameControllerAddMappingsFromFile(f)\n\nstatic inline void SDL_SetGamepadEventsEnabled(boolean enabled)\n{\n  SDL_GameControllerEventState(enabled ? SDL_ENABLE : SDL_DISABLE);\n}\n#endif\n\n/**\n * SDL_joystick.h\n */\n#if !SDL_VERSION_ATLEAST(3,0,0)\n#define SDL_GUID                          SDL_JoystickGUID\n#define SDL_GUIDToString                  SDL_GetJoystickGUIDString\n#define SDL_OpenJoystick(device_id)       SDL_JoystickOpen(device_id)\n#define SDL_CloseJoystick(j)              SDL_JoystickClose(j)\n#define SDL_GetJoystickGUID(j)            SDL_JoystickGetGUID(j)\n#define SDL_GetJoystickGUIDString(g,b,l)  SDL_JoystickGetGUIDString(g,b,l)\n#define SDL_GetJoystickID(j)              SDL_JoystickInstanceID(j)\n\nstatic inline void SDL_SetJoystickEventsEnabled(boolean enabled)\n{\n  SDL_JoystickEventState(enabled ? SDL_ENABLE : SDL_DISABLE);\n}\n#endif\n\n/**\n * SDL_keyboard.h and SDL_keycode.h\n */\n#if !SDL_VERSION_ATLEAST(2,0,0)\n#define SDL_HasScreenKeyboardSupport()  (0)\n#define SDL_IsScreenKeyboardShown(w)    (0)\n#endif\n#if !SDL_VERSION_ATLEAST(3,0,0)\n#define SDLK_A                SDLK_a\n#define SDLK_B                SDLK_b\n#define SDLK_C                SDLK_c\n#define SDLK_D                SDLK_d\n#define SDLK_E                SDLK_e\n#define SDLK_F                SDLK_f\n#define SDLK_G                SDLK_g\n#define SDLK_H                SDLK_h\n#define SDLK_I                SDLK_i\n#define SDLK_J                SDLK_j\n#define SDLK_K                SDLK_k\n#define SDLK_L                SDLK_l\n#define SDLK_M                SDLK_m\n#define SDLK_N                SDLK_n\n#define SDLK_O                SDLK_o\n#define SDLK_P                SDLK_p\n#define SDLK_Q                SDLK_q\n#define SDLK_R                SDLK_r\n#define SDLK_S                SDLK_s\n#define SDLK_T                SDLK_t\n#define SDLK_U                SDLK_u\n#define SDLK_V                SDLK_v\n#define SDLK_W                SDLK_w\n#define SDLK_X                SDLK_x\n#define SDLK_Y                SDLK_y\n#define SDLK_Z                SDLK_z\n#define SDLK_APOSTROPHE       SDLK_QUOTE\n#define SDLK_GRAVE            SDLK_BACKQUOTE\n#define SDL_KMOD_CTRL         KMOD_CTRL\n#define SDL_KMOD_ALT          KMOD_ALT\n#define SDL_KMOD_NUM          KMOD_NUM\n#define SDL_KMOD_CAPS         KMOD_CAPS\n#define SDL_KMOD_MODE         KMODE_MODE\n#define SDL_StartTextInput(w) SDL_StartTextInput()\n#define SDL_StopTextInput(w)  SDL_StopTextInput()\n#define SDL_ScreenKeyboardShown(w) SDL_IsScreenKeyboardShown(w)\n#endif\n\n/**\n *  SDL_pixels.h\n */\n#if !SDL_VERSION_ATLEAST(2,0,5)\n/* Convenience macros added in 2.0.5. */\n#if SDL_BYTEORDER == SDL_BIG_ENDIAN\n#define SDL_PIXELFORMAT_RGBA32                      SDL_PIXELFORMAT_RGBA8888\n#else\n#define SDL_PIXELFORMAT_RGBA32                      SDL_PIXELFORMAT_ABGR8888\n#endif\n#endif\n#if !SDL_VERSION_ATLEAST(2,0,14)\n/* Added in 2.0.14 with the old names remaining; old names removed in SDL3. */\n#define SDL_PIXELFORMAT_XRGB4444                    SDL_PIXELFORMAT_RGB444\n#define SDL_PIXELFORMAT_XBGR4444                    SDL_PIXELFORMAT_BGR444\n#define SDL_PIXELFORMAT_XRGB1555                    SDL_PIXELFORMAT_RGB555\n#define SDL_PIXELFORMAT_XBGR1555                    SDL_PIXELFORMAT_BGR555\n#define SDL_PIXELFORMAT_XRGB8888                    SDL_PIXELFORMAT_RGB888\n#define SDL_PIXELFORMAT_XBGR8888                    SDL_PIXELFORMAT_BGR888\n#endif\n#if !SDL_VERSION_ATLEAST(3,0,0)\n#define SDL_PixelFormatDetails                      SDL_PixelFormat\n#define SDL_CreatePalette(s)                        SDL_AllocPalette(s)\n#define SDL_DestroyPalette(p)                       SDL_FreePalette(p)\n#define SDL_GetMasksForPixelFormat(f,b,R,G,B,A)     SDL_PixelFormatEnumToMasks(f,b,R,G,B,A)\n\nstatic inline Uint32 SDL_MapSurfaceRGBA(SDL_Surface *surface,\n Uint8 r, Uint8 g, Uint8 b, Uint8 a)\n{\n  return SDL_MapRGBA(surface->format, r, g, b, a);\n}\n#endif\n\n/**\n * SDL_rect.h\n */\n#if !SDL_VERSION_ATLEAST(2,0,10)\ntypedef struct { float x; float y; float w; float h; } SDL_FRect;\n#endif\n\n#if SDL_VERSION_ATLEAST(3,0,0)\ntypedef SDL_FRect SDL_Rect_mzx;\nstatic inline SDL_Rect_mzx sdl_render_rect(int x, int y, int w, int h)\n{\n  SDL_FRect tmp = { (float)x, (float)y, (float)w, (float)h };\n  return tmp;\n}\n#elif SDL_VERSION_ATLEAST(2,0,0)\ntypedef SDL_Rect SDL_Rect_mzx;\nstatic inline SDL_Rect_mzx sdl_render_rect(int x, int y, int w, int h)\n{\n  SDL_Rect tmp = { x, y, w, h };\n  return tmp;\n}\n#endif\n\n/**\n * SDL_render.h\n */\n#if !SDL_VERSION_ATLEAST(3,0,0) && SDL_VERSION_ATLEAST(2,0,0)\ntypedef int SDL_RendererLogicalPresentation;\n#define SDL_LOGICAL_PRESENTATION_DISABLED 0\n#define SDL_SCALEMODE_BEST                SDL_ScaleModeBest\n#define SDL_SCALEMODE_LINEAR              SDL_ScaleModeLinear\n#define SDL_SCALEMODE_NEAREST             SDL_ScaleModeNearest\n\n#if !SDL_VERSION_ATLEAST(2,0,12)\ntypedef int SDL_ScaleMode;\n#endif\n\nstatic inline boolean SDL_SetRenderClipRect(SDL_Renderer *render,\n SDL_Rect *rect)\n{\n  return SDL_RenderSetClipRect(render, rect) == 0;\n}\n\nstatic inline boolean SDL_SetRenderLogicalPresentation(SDL_Renderer *render,\n int w, int h, SDL_RendererLogicalPresentation p, SDL_ScaleMode s)\n{\n  return SDL_RenderSetLogicalSize(render, w, h) == 0;\n}\n\nstatic inline boolean SDL_RenderTexture(SDL_Renderer *renderer,\n SDL_Texture *texture, const SDL_Rect_mzx *src_rect, const SDL_Rect_mzx *dest_rect)\n{\n  return SDL_RenderCopy(renderer, texture, src_rect, dest_rect) == 0;\n}\n#endif\n\n/**\n * SDL_surface.h\n */\n#if !SDL_VERSION_ATLEAST(3,0,0)\n#define SDL_SCALEMODE_NEAREST       SDL_ScaleModeNearest\n#define SDL_SCALEMODE_LINEAR        SDL_ScaleModeLinear\n#define SDL_DestroySurface(s)       SDL_FreeSurface(s)\n\nstatic inline boolean SDL_FillSurfaceRect(SDL_Surface *surface, SDL_Rect *rect,\n Uint32 color)\n{\n  return SDL_FillRect(surface, rect, color) == 0;\n}\n\nstatic inline boolean SDL_GetSurfaceClipRect(SDL_Surface *surface, SDL_Rect *rect)\n{\n  *rect = surface->clip_rect;\n  return true;\n}\n\n#if SDL_VERSION_ATLEAST(2,0,0)\nstatic inline SDL_Surface *SDL_CreateSurface(int width, int height, Uint32 format)\n{\n  Uint32 rmask, gmask, bmask, amask;\n  int bpp;\n\n  SDL_PixelFormatEnumToMasks(format, &bpp, &rmask, &gmask, &bmask, &amask);\n  return SDL_CreateRGBSurface(0, width, height, bpp, rmask, gmask, bmask, amask);\n}\n#endif\n#endif\n\n/**\n * SDL_thread.h symbols were mostly the same for SDL 1 and SDL 2.\n */\n#if !SDL_VERSION_ATLEAST(3,0,0)\ntypedef SDL_cond  SDL_Condition;\ntypedef SDL_mutex SDL_Mutex;\ntypedef SDL_sem   SDL_Semaphore;\n#define SDL_CreateCondition()           SDL_CreateCond()\n#define SDL_DestroyCondition(c)         SDL_DestroyCond(c)\n#define SDL_WaitCondition(c,m)          SDL_CondWait(c,m)\n#define SDL_WaitConditionTimeout(c,m,t) SDL_CondWaitTimeout(c,m,t)\n#define SDL_SignalCondition(c)          SDL_CondSignal(c)\n#define SDL_BroadcastCondition(c)       SDL_CondBroadcast(c)\n#define SDL_WaitSemaphore(s)            SDL_SemWait(s)\n#define SDL_SignalSemaphore(s)          SDL_SemPost(s)\n#define SDL_GetCurrentThreadID()        SDL_ThreadID()\n#endif\n\n/**\n * SDL_touch.h\n */\n#if SDL_VERSION_ATLEAST(2,0,0) && !SDL_VERSION_ATLEAST(3,0,0)\nstatic inline SDL_Finger **SDL_GetTouchFingers(SDL_TouchID touchID, int *count)\n{\n  SDL_Finger **ret;\n  int num = SDL_GetNumTouchFingers(touchID);\n  int i;\n\n  if(num <= 0)\n    return NULL;\n\n  ret = (SDL_Finger **)SDL_calloc(num + 1, sizeof(SDL_Finger *));\n  for(i = 0; i < num; i++)\n    ret[i] = SDL_GetTouchFinger(touchID, i);\n\n  *count = num;\n  return ret;\n}\n#endif\n\n/**\n * SDL_version.h\n */\n#if !SDL_VERSION_ATLEAST(3,0,0)\n#define SDL_MICRO_VERSION SDL_PATCHLEVEL\n// This macro *should* be identical to SDL3's SDL_VERSIONNUM.\n#define SDL_VERSIONNUM_MZX(x,y,z) ((x) * 1000000 + (y) * 1000 + (z))\n#define SDL_VERSIONNUM_MAJOR(v) ((v) / 1000000)\n#define SDL_VERSIONNUM_MINOR(v) (((v) / 1000) % 1000)\n#define SDL_VERSIONNUM_MICRO(v) ((v) % 1000)\n#else\n#define SDL_VERSIONNUM_MZX(x,y,z) SDL_VERSIONNUM((x),(y),(z))\n#endif\n\nstatic inline int sdl_compiled_version(void)\n{\n  return SDL_VERSIONNUM_MZX(SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION);\n}\n\nstatic inline int sdl_linked_version(void)\n{\n#if SDL_VERSION_ATLEAST(3,0,0)\n  return SDL_GetVersion();\n#else\n  SDL_version ver;\n#if SDL_VERSION_ATLEAST(2,0,0)\n  SDL_GetVersion(&ver);\n#else\n  ver = *SDL_Linked_Version();\n#endif\n  return SDL_VERSIONNUM_MZX(ver.major, ver.minor, ver.patch);\n#endif\n}\n\n/**\n * SDL_video.h\n */\n#if !SDL_VERSION_ATLEAST(2,0,16)\n#define SDL_SetWindowMouseGrab(w,b) SDL_SetWindowGrab(w,b)\n#endif\n#if !SDL_VERSION_ATLEAST(3,0,0)\n#define SDL_GL_DestroyContext(gl)   SDL_GL_DeleteContext(gl)\n\n/* SDL3 removed the coordinate arguments to SDL_CreateWindow.\n * Windows are created centered by default instead. */\n#define SDL_CreateWindow(title, w, h, flags) SDL_CreateWindow((title), \\\n SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, (w), (h), (flags))\n#endif\n\n#endif /* CONFIG_SDL */\n\n__M_END_DECLS\n\n#endif /* __SDLMZX_H */\n"
  },
  {
    "path": "src/about.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2023-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"about.h\"\n#include \"platform_attribute.h\" /* ATTRIBUTE_PRINTF */\n#include \"util.h\"\n#include \"window.h\"\n#include \"io/path.h\"\n#include \"io/vio.h\"\n\n#include <ctype.h>\n#include <zlib.h>\n\n#ifdef CONFIG_SDL\n#include \"SDLmzx.h\"\n#endif\n#ifdef CONFIG_XMP\n#include <xmp.h>\n#endif\n#ifdef CONFIG_MIKMOD\n#include <mikmod.h>\n#endif\n#ifdef CONFIG_OPENMPT\n#include <libopenmpt/libopenmpt.h>\n#endif\n#ifdef CONFIG_PNG\n#include <png.h>\n#endif\n#ifdef CONFIG_VORBIS\n#if !defined(CONFIG_TREMOR) && !defined(CONFIG_STB_VORBIS)\n#include <vorbis/codec.h>\n#endif\n#endif\n\n#define ABOUT_X       0\n#define ABOUT_Y       1\n#define ABOUT_WIDTH   80\n#define ABOUT_HEIGHT  23\n#define MAX_FILES     32\n#define MAX_VERSIONS  32\n#define MAX_LICENSE   (1 << 18) /* 256k should be enough for a license... */\n\n#ifdef CONFIG_PNG\nstatic char *png_version_string(char version_str[16], png_uint_32 version)\n{\n  int v = (int)version;\n  snprintf(version_str, 16, \"%d.%d.%d\", v / 10000, (v / 100) % 100, v % 100);\n\n  return version_str;\n}\n#endif\n\nATTRIBUTE_PRINTF(1, 2)\nstatic char *about_line(const char *fmt, ...)\n{\n  va_list args;\n  char buf[ABOUT_WIDTH - 5];\n  char *str;\n  size_t len;\n\n  va_start(args, fmt);\n  vsnprintf(buf, sizeof(buf), fmt, args);\n  va_end(args);\n\n  len = strlen(buf) + 1;\n  str = (char *)malloc(len);\n  if(str)\n    memcpy(str, buf, len);\n\n  return str;\n}\n\n/**\n * Build the \"About MegaZeux\" list text, including all version information.\n */\nstatic char **about_text(int *num_lines)\n{\n  char **lines = (char  **)malloc(sizeof(char *) * MAX_VERSIONS);\n  int i = 0;\n  if(!lines)\n    return NULL;\n\n#ifdef VERSION_DATE\n  lines[i++] = about_line(\"MegaZeux \" VERSION VERSION_DATE);\n#else\n  lines[i++] = about_line(\"MegaZeux \" VERSION);\n#endif\n  lines[i++] = about_line(\"Platform: \" PLATFORM);\n#ifdef VERSION_HEAD\n  lines[i++] = about_line(\"Build head: \" VERSION_HEAD);\n#endif\n  lines[i++] = about_line(\" \");\n  lines[i++] = about_line(\"MegaZeux is free software licensed under the GNU GPL version 2 and\");\n  lines[i++] = about_line(\"comes with NO WARRANTY. See \\\"License\\\" for more information.\");\n  lines[i++] = about_line(\" \");\n\n#ifdef CONFIG_SDL\n  {\n    int ver_compiled = sdl_compiled_version();\n    int ver_linked = sdl_linked_version();\n\n    if(ver_compiled != ver_linked)\n    {\n      lines[i++] = about_line(\"SDL: %u.%u.%u (compiled: %u.%u.%u)\",\n       SDL_VERSIONNUM_MAJOR(ver_linked),\n       SDL_VERSIONNUM_MINOR(ver_linked),\n       SDL_VERSIONNUM_MICRO(ver_linked),\n       SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION);\n    }\n    else\n    {\n      lines[i++] = about_line(\"SDL: %u.%u.%u\",\n       SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION);\n    }\n  }\n#endif\n\n#ifdef CONFIG_XMP\n  if(strcmp(XMP_VERSION, xmp_version))\n    lines[i++] = about_line(\"libxmp: %s (compiled: \" XMP_VERSION \")\", xmp_version);\n  else\n    lines[i++] = about_line(\"libxmp: %s\", xmp_version);\n#endif\n\n#ifdef CONFIG_MIKMOD\n  {\n    long compiled = LIBMIKMOD_VERSION;\n    long ver = MikMod_GetVersion();\n\n    if(compiled != ver)\n    {\n      lines[i++] = about_line(\"MikMod: %ld.%ld.%ld (compiled: %ld.%ld.%ld)\",\n       ver >> 16, (ver >> 8) & 0xff, ver & 0xff,\n       compiled >> 16, (compiled >> 8) & 0xff, compiled & 0xff);\n    }\n    else\n      lines[i++] = about_line(\"MikMod: %ld.%ld.%ld\", ver >> 16, (ver >> 8) & 0xff, ver & 0xff);\n  }\n#endif\n\n#ifdef CONFIG_MODPLUG\n  lines[i++] = about_line(\"libmodplug\");\n#endif\n\n#ifdef CONFIG_OPENMPT\n  {\n    const char *compiled = OPENMPT_API_VERSION_STRING;\n    const char *ver = openmpt_get_string(\"library_version\");\n\n    if(strcmp(ver, compiled))\n      lines[i++] = about_line(\"libopenmpt: %s (compiled: %s)\", ver, compiled);\n    else\n      lines[i++] = about_line(\"libopenmpt: %s\", ver);\n  }\n#endif\n\n  if(strcmp(ZLIB_VERSION, zlibVersion()))\n    lines[i++] = about_line(\"zlib: %s (compiled: \" ZLIB_VERSION \")\", zlibVersion());\n  else\n    lines[i++] = about_line(\"zlib: \" ZLIB_VERSION);\n\n#ifdef CONFIG_PNG\n  {\n    png_uint_32 version_link = png_access_version_number();\n    png_uint_32 version_comp = PNG_LIBPNG_VER;\n    char version_a[16];\n    char version_b[16];\n\n    png_version_string(version_a, version_link);\n    png_version_string(version_b, version_comp);\n\n    if(version_link != version_comp)\n      lines[i++] = about_line(\"libpng: %s (compiled: %s)\", version_a, version_b);\n    else\n      lines[i++] = about_line(\"libpng: %s\", version_a);\n  }\n#endif\n\n#ifdef CONFIG_VORBIS\n#if defined(CONFIG_STB_VORBIS)\n  lines[i++] = about_line(\"stb_vorbis\");\n#elif defined(CONFIG_TREMOR)\n  lines[i++] = about_line(\"libtremor\");\n#else\n  lines[i++] = about_line(\"libvorbis: %s\", vorbis_version_string());\n#endif\n#endif\n\n  lines[i++] = about_line(\" \");\n  lines[i++] = about_line(\"CONFDIR: \" CONFDIR);\n  lines[i++] = about_line(\"CONFFILE: \" CONFFILE);\n  lines[i++] = about_line(\"SHAREDIR: \" SHAREDIR);\n  lines[i++] = about_line(\"LICENSEDIR: \" LICENSEDIR);\n\n  *num_lines = i;\n  return lines;\n}\n\n/**\n * Get the license directory.\n */\nstatic const char *get_license_dir(char *license_dir, size_t len)\n{\n  if(!path_is_absolute(LICENSEDIR))\n  {\n    const char *exec_dir = mzx_res_get_by_id(MZX_EXECUTABLE_DIR);\n    if(!exec_dir)\n      return \"\";\n\n    if(path_join(license_dir, len, exec_dir, LICENSEDIR) < 0)\n      return \"\";\n\n    return license_dir;\n  }\n  else\n    return LICENSEDIR;\n}\n\n/**\n * Scan license_dir for all applicable license files.\n */\nstatic void load_license_list(char *names[MAX_FILES], char *files[MAX_FILES],\n const char *license_dir)\n{\n  vdir *dir;\n  enum vdir_type type;\n  char buf[MAX_PATH];\n  char *license = NULL;\n  char *license_3rd = NULL;\n  int num_files = 0;\n\n  dir = vdir_open(license_dir);\n  if(dir)\n  {\n    // Pass 1: find LICENSE (or LICENSE.) and LICENCE.3rd.\n    while(num_files < MAX_FILES)\n    {\n      if(!vdir_read(dir, buf, sizeof(buf), &type))\n        break;\n      if(type == DIR_TYPE_DIR)\n        continue;\n\n      if((!strcasecmp(buf, \"LICENSE\") || !strcasecmp(buf, \"LICENSE.\")) && !license)\n      {\n        license = about_line(\"%s\", buf);\n      }\n      else\n\n      if(!strcasecmp(buf, \"LICENSE.3rd\") && !license_3rd)\n      {\n        license_3rd = about_line(\"%s\", buf);\n      }\n    }\n    // MegaZeux's license should always go first.\n    if(license)\n    {\n      names[num_files] = about_line(\"License\");\n      files[num_files++] = license;\n    }\n    if(license_3rd)\n    {\n      names[num_files] = about_line(\"3rd Party\");\n      files[num_files++] = license_3rd;\n    }\n\n    // Pass 2: add all other license files.\n    vdir_rewind(dir);\n    while(num_files < MAX_FILES)\n    {\n      if(!vdir_read(dir, buf, sizeof(buf), &type))\n        break;\n      if(type == DIR_TYPE_DIR)\n        continue;\n\n      if(!strncasecmp(buf, \"LICENSE.\", 8) && strcasecmp(buf, \"LICENSE.3rd\") && buf[8])\n      {\n        names[num_files] = about_line(\"%-.16s\", buf + 8);\n        files[num_files++] = about_line(\"%s\", buf);\n      }\n#ifdef CONFIG_DJGPP\n      else\n\n      /* Even if the extensions are completely different, having multiple of\n       * these licenses might cause SFN numbers greater than 1. */\n      if(!strncasecmp(buf, \"LICENS~\", 7) && isdigit((unsigned char)buf[7]) &&\n       buf[8] == '.' && buf[9] != '\\0')\n      {\n        names[num_files] = about_line(\"%-.16s\", buf + 9);\n        files[num_files++] = about_line(\"%s\", buf);\n      }\n#endif\n    }\n\n    vdir_close(dir);\n  }\n}\n\nstatic void destroy_license_list(char *names[MAX_FILES], char *files[MAX_FILES])\n{\n  int i;\n  for(i = 0; i < MAX_FILES; i++)\n  {\n    free(names[i]);\n    free(files[i]);\n  }\n}\n\n// TODO: text editor element would be preferable here.\n// please kill this entire function with fire in a future release\nstatic char **load_license(const char *filename, int *_lines)\n{\n  vfile *fp;\n  char **arr = (char **)malloc(32 * sizeof(char *));\n  char **tmp;\n  boolean tab;\n  size_t lines_alloc = 32;\n  size_t lines = 0;\n  size_t total = 0;\n  size_t len;\n  size_t sz;\n  size_t i;\n  size_t j;\n\n  if(!arr)\n    return NULL;\n\n  fp = vfopen_unsafe(filename, \"r\");\n  if(fp)\n  {\n    char buf[256];\n\n    while(vfsafegets(buf, sizeof(buf), fp))\n    {\n      if(lines >= lines_alloc)\n      {\n        lines_alloc *= 2;\n        tmp = (char **)realloc(arr, lines_alloc * sizeof(char *));\n        if(!tmp)\n          break;\n        arr = tmp;\n      }\n      tab = false;\n      len = strlen(buf);\n      sz = len + 1;\n      for(i = 0; i < len; i++)\n      {\n        if(buf[i] == '\\t')\n        {\n          sz += 7;\n          tab = true;\n        }\n      }\n      total += sz;\n      if(total > MAX_LICENSE)\n        break;\n\n      arr[lines] = (char *)malloc(sz);\n      if(!arr[lines])\n        break;\n\n      if(tab)\n      {\n        for(i = 0, j = 0; i < len && j < sz - 1; i++)\n        {\n          if(buf[i] == '\\t')\n          {\n            memset(arr[lines] + j, ' ', 8);\n            j += 8;\n          }\n          else\n            arr[lines][j++] = buf[i];\n        }\n        arr[lines][j] = '\\0';\n      }\n      else\n        memcpy(arr[lines], buf, sz);\n\n      lines++;\n    }\n    vfclose(fp);\n  }\n  *_lines = lines;\n  return arr;\n}\n\nstatic void destroy_lines(char **list, int lines)\n{\n  int i;\n  if(list)\n  {\n    for(i = 0; i < lines; i++)\n      free(list[i]);\n    free(list);\n  }\n}\n\n/**\n * Display the \"About MegaZeux\" dialog box.\n */\nvoid about_megazeux(context *parent)\n{\n  struct dialog src;\n  struct element *elements[MAX_FILES + 3];\n  char **current = NULL;\n  char *names[MAX_FILES];\n  char *files[MAX_FILES];\n  const char *license_dir;\n  char tmp[MAX_PATH];\n  char path[MAX_PATH];\n  int current_lines = 0;\n  int num_elements;\n  int list_pos = 0;\n  int list_scroll = 0;\n  int show = 1;\n  int prev = -1;\n  int x = 2;\n  int y = 1;\n  int i;\n\n  license_dir = get_license_dir(tmp, sizeof(tmp));\n\n  memset(names, 0, sizeof(names));\n  memset(files, 0, sizeof(files));\n  load_license_list(names, files, license_dir);\n\n  while(show > 0)\n  {\n    if(show != prev)\n    {\n      list_pos = 0;\n      list_scroll = 0;\n      prev = show;\n    }\n\n    if(show == 1)\n    {\n      current = about_text(&current_lines);\n    }\n    else\n    {\n      if(path_join(path, sizeof(path), license_dir, files[show - 2]) < 0)\n        path[0] = '\\0';\n\n      current = load_license(path, &current_lines);\n    }\n\n    if(!current)\n      current_lines = 0;\n\n    elements[0] = construct_button(74, 1, \"OK\", 0);\n    elements[1] = construct_list_box(1, 3, (const char **)current, current_lines, 19,\n     ABOUT_WIDTH - 2, show, &list_pos, &list_scroll, false);\n    elements[2] = construct_button(2, 1, \"About\", 1);\n\n    num_elements = 3;\n    x = 11;\n    y = 1;\n\n    for(i = 0; i < MAX_FILES; i++)\n    {\n      if(files[i])\n      {\n        int sz = strlen(names[i]) + 4;\n        if(x + sz > ABOUT_WIDTH - 6)\n        {\n          x = 2;\n          y++;\n          if(y >= 3)\n            break;\n        }\n        elements[num_elements++] = construct_button(x, y, names[i], i + 2);\n        x += sz;\n      }\n    }\n\n    construct_dialog(&src, \"About MegaZeux\",\n     ABOUT_X, ABOUT_Y, ABOUT_WIDTH, ABOUT_HEIGHT, elements, num_elements, 1);\n\n    show = run_dialog(parent->world, &src);\n    destruct_dialog(&src);\n\n    destroy_lines(current, current_lines);\n  }\n  destroy_license_list(names, files);\n}\n"
  },
  {
    "path": "src/about.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __ABOUT_H\n#define __ABOUT_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"core.h\"\n\nvoid about_megazeux(context *parent);\n\n__M_END_DECLS\n\n#endif // __ABOUT_H\n"
  },
  {
    "path": "src/audio/audio.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2004 madbrain\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Code to handle module playing, sample playing, and PC speaker\n// sfx emulation.\n\n#include <stdlib.h>\n#include <string.h>\n#include <math.h>\n\n#include <sys/stat.h>\n\n#include \"audio.h\"\n#include \"audio_pcs.h\"\n#include \"audio_struct.h\"\n#include \"ext.h\"\n#include \"sampled_stream.h\"\n#include \"sfx.h\"\n\n#include \"../configure.h\"\n#include \"../data.h\"\n#include \"../platform.h\"\n#include \"../util.h\"\n#include \"../io/fsafeopen.h\"\n\n#if defined(CONFIG_MODPLUG) + defined(CONFIG_MIKMOD) + \\\n defined(CONFIG_XMP) + defined(CONFIG_OPENMPT) > 1\n\n#error Can only have one module system enabled concurrently!\n#endif\n\n#include \"audio_wav.h\"\n\n#if defined(CONFIG_VORBIS)\n#include \"audio_vorbis.h\"\n#endif\n\n#ifdef CONFIG_MODPLUG\n#include \"audio_modplug.h\"\n#endif\n\n#ifdef CONFIG_MIKMOD\n#include \"audio_mikmod.h\"\n#endif\n\n#ifdef CONFIG_XMP\n#include \"audio_xmp.h\"\n#endif\n\n#ifdef CONFIG_OPENMPT\n#include \"audio_openmpt.h\"\n#endif\n\n#ifdef CONFIG_REALITY\n#include \"audio_reality.h\"\n#endif\n\n// May be used by audio plugins\nstruct audio audio;\n\n#ifndef DEBUG\n\n#define LOCK()   platform_mutex_lock(&audio.audio_mutex)\n#define UNLOCK() platform_mutex_unlock(&audio.audio_mutex)\n\n#else /* DEBUG */\n\n#include \"../thread_debug.h\"\n\nstatic platform_mutex_debug mutex_debug;\n\n#define LOCK()   platform_mutex_lock_debug(&audio.audio_mutex, \\\n                  &audio.audio_debug_mutex, &mutex_debug, __FILE__, __LINE__)\n#define UNLOCK() platform_mutex_unlock_debug(&audio.audio_mutex, \\\n                  &audio.audio_debug_mutex, &mutex_debug, __FILE__, __LINE__)\n\n#endif // DEBUG\n\nstatic const int freq_conversion = 3579364;\n\nint audio_get_real_frequency(int period)\n{\n  /* Convert MZX's \"frequencies\" to an actual usable frequency. */\n  return freq_conversion / period;\n}\n\nstatic unsigned int volume_function(int input, int volume_setting)\n{\n  /* Adjust volume (0-255) exponentially according to a given setting (0-10).\n   * 0 is no volume whatsoever and 10 is maximum volume. */\n\n  double setting_f = (double)volume_setting / 10.0;\n  double setting_exp = (exp(setting_f) - 1.0) / (M_E - 1.0);\n  int output = (int)((double)input * setting_exp + 0.5);\n\n  return CLAMP(output, 0, 255);\n}\n\n// Disable most of the standard audio implementation on NDS, where\n// hardware mixing is utilized.\n#ifndef CONFIG_NDS\n\nstatic void audio_stream_insert_list(struct audio_stream **base,\n struct audio_stream **end, struct audio_stream *a_src)\n{\n  if(*base == NULL)\n  {\n    *base = a_src;\n  }\n  else\n  {\n    (*end)->next = a_src;\n  }\n\n  a_src->next = NULL;\n  a_src->previous = *end;\n  *end = a_src;\n}\n\nstatic void audio_stream_remove_from_lists(struct audio_stream *a_src)\n{\n  if(a_src == audio.primary_stream)\n    audio.primary_stream = NULL;\n\n  if(a_src == audio.stream_list_base)\n    audio.stream_list_base = a_src->next;\n\n  if(a_src == audio.stream_list_end)\n    audio.stream_list_end = a_src->previous;\n\n#ifdef AUDIO_GARBAGE_COLLECTOR\n  if(a_src == audio.garbage_list_base)\n    audio.garbage_list_base = a_src->next;\n\n  if(a_src == audio.garbage_list_end)\n    audio.garbage_list_end = a_src->previous;\n#endif\n\n  if(a_src->next)\n    a_src->next->previous = a_src->previous;\n\n  if(a_src->previous)\n    a_src->previous->next = a_src->next;\n}\n\nstatic void audio_garbage_collect(void)\n{\n#ifdef AUDIO_GARBAGE_COLLECTOR\n  struct audio_stream *a_src;\n  struct audio_stream *next;\n\n  for(a_src = audio.garbage_list_base; a_src; a_src = next)\n  {\n    next = a_src->next;\n    a_src->destruct(a_src);\n  }\n  audio.garbage_list_base = audio.garbage_list_end = NULL;\n#endif\n}\n\n// DOS audio is handled during an interrupt and can never be allowed to\n// manage memory. This means audio stream cleanup needs to be delayed until\n// the main thread creates a new stream or explicitly destroys other streams.\nstatic void audio_garbage_queue(struct audio_stream *a_src)\n{\n#ifdef AUDIO_GARBAGE_COLLECTOR\n  audio_stream_remove_from_lists(a_src);\n  audio_stream_insert_list(&audio.garbage_list_base,\n   &audio.garbage_list_end, a_src);\n#else\n  a_src->destruct(a_src);\n#endif\n}\n\nvoid destruct_audio_stream(struct audio_stream *a_src)\n{\n  audio_stream_remove_from_lists(a_src);\n  free(a_src);\n}\n\nvoid initialize_audio_stream(struct audio_stream *a_src,\n struct audio_stream_spec *a_spec, unsigned int volume, boolean repeat)\n{\n  // TODO should probably just memcpy into a spec in the audio_stream instead.\n  a_src->mix_data = a_spec->mix_data;\n  a_src->set_volume = a_spec->set_volume;\n  a_src->set_repeat = a_spec->set_repeat;\n  a_src->set_order = a_spec->set_order;\n  a_src->set_position = a_spec->set_position;\n  a_src->set_loop_start = a_spec->set_loop_start;\n  a_src->set_loop_end = a_spec->set_loop_end;\n  a_src->get_order = a_spec->get_order;\n  a_src->get_position = a_spec->get_position;\n  a_src->get_length = a_spec->get_length;\n  a_src->get_loop_start = a_spec->get_loop_start;\n  a_src->get_loop_end = a_spec->get_loop_end;\n  a_src->get_sample = a_spec->get_sample;\n  a_src->destruct = a_spec->destruct;\n  a_src->is_spot_sample = false;\n\n  if(a_src->set_volume)\n    a_src->set_volume(a_src, volume);\n\n  if(a_src->set_repeat)\n    a_src->set_repeat(a_src, repeat);\n\n  a_src->next = NULL;\n\n  LOCK();\n\n  audio_garbage_collect();\n  audio_stream_insert_list(&audio.stream_list_base,\n   &audio.stream_list_end, a_src);\n\n  UNLOCK();\n}\n\nstatic void clip_buffer_u8(uint8_t *dest, int32_t *src, size_t samples)\n{\n  int32_t cur_sample;\n  size_t i;\n\n  for(i = 0; i < samples; i++)\n  {\n    cur_sample = src[i];\n    if(cur_sample > 32767)\n      cur_sample = 32767;\n\n    if(cur_sample < -32768)\n      cur_sample = -32768;\n\n    dest[i] = (uint8_t)(cur_sample >> 8) + 128;\n  }\n}\n\nstatic void clip_buffer_s8(int8_t *dest, int32_t *src, size_t samples)\n{\n  int32_t cur_sample;\n  size_t i;\n\n  for(i = 0; i < samples; i++)\n  {\n    cur_sample = src[i];\n    if(cur_sample > 32767)\n      cur_sample = 32767;\n\n    if(cur_sample < -32768)\n      cur_sample = -32768;\n\n    dest[i] = cur_sample >> 8;\n  }\n}\n\nstatic void clip_buffer_s16(int16_t *dest, int32_t *src, size_t samples)\n{\n  int32_t cur_sample;\n  size_t i;\n\n  for(i = 0; i < samples; i++)\n  {\n    cur_sample = src[i];\n    if(cur_sample > 32767)\n      cur_sample = 32767;\n\n    if(cur_sample < -32768)\n      cur_sample = -32768;\n\n    dest[i] = cur_sample;\n  }\n}\n\n/**\n * Render `frames` number of audio frames with the software mixer. The\n * output buffer must be able to hold a number of bytes equal to the\n * requested frames times the requested channels times the size in bytes\n * of the requested sample type. Destroys any audio streams that end\n * while rendering the output.\n * TODO: the mixer requires exactly 2 output channels currently.\n *\n * Returns the number of frames successfully renderered.\n */\nsize_t audio_mixer_render_frames(void *stream, unsigned frames,\n unsigned channels, unsigned format)\n{\n  struct audio_stream *current_astream;\n  size_t frames_chn;\n  boolean destroy_flag;\n\n  LOCK();\n\n  current_astream = audio.stream_list_base;\n\n  if(current_astream && audio.mix_buffer && frames && channels)\n  {\n    // Due to how resampling is implemented, the requested frames\n    // should never exceed the buffer's number of frames. If it does,\n    // this call can't complete the entire request.\n    if(frames > audio.buffer_frames)\n      frames = audio.buffer_frames;\n\n    // Additionally, if the output channels exceed the configured\n    // number of channels, the frames may need to be limited further.\n    frames_chn = frames * channels;\n    if(frames_chn * sizeof(int32_t) > audio.buffer_bytes)\n    {\n      frames = audio.buffer_bytes / (channels * sizeof(int32_t));\n      frames_chn = frames * channels;\n    }\n\n    memset(audio.mix_buffer, 0, frames_chn * sizeof(int32_t));\n\n    while(current_astream != NULL)\n    {\n      struct audio_stream *next_astream = current_astream->next;\n\n      if(current_astream->mix_data)\n      {\n        destroy_flag = current_astream->mix_data(current_astream,\n         audio.mix_buffer, frames, channels);\n\n        if(destroy_flag)\n          audio_garbage_queue(current_astream);\n      }\n\n      current_astream = next_astream;\n    }\n\n    switch(format)\n    {\n      case SAMPLE_U8:\n        clip_buffer_u8((uint8_t *)stream, audio.mix_buffer, frames_chn);\n        break;\n\n      case SAMPLE_S8:\n        clip_buffer_s8((int8_t *)stream, audio.mix_buffer, frames_chn);\n        break;\n\n      case SAMPLE_S16:\n        clip_buffer_s16((int16_t *)stream, audio.mix_buffer, frames_chn);\n        break;\n\n      default:\n        warn(\"clip_buffer unimplemented for sample format %d!\\n\", format);\n    }\n  }\n\n  UNLOCK();\n\n  return frames;\n}\n\n/**\n * This function initializes audio.output_frequency, audio.mix_buffer,\n * and audio.buffer_frames for software mixing. This function may reconfigure\n * the provided values of any of these fields.\n *\n * This should be called when initializing a sound driver but before unpausing\n * audio, and any time the sample rate, number of buffer frames, or the number\n * of output channels changes (also while audio is paused). Sample format\n * changes are handled in the audio callback directly.\n */\nboolean audio_mixer_init(unsigned rate, unsigned frames, unsigned channels)\n{\n  boolean ret = false;\n  size_t sz;\n\n  debug(\"--MIXER-- attempting init with rate=%u, frames=%u, channels=%u\\n\",\n   rate, frames, channels);\n\n  if(rate == 0)\n    rate = 44100;\n  if(rate < 256 || rate > 384000)\n    goto err;\n\n  if(frames == 0)\n    frames = 1024;\n  frames = CLAMP(frames, 8, 65536);\n  frames = round_to_power_of_two(frames);\n\n  if(channels == 0)\n    channels = 2;\n  if(channels > 2)\n    goto err;\n\n  sz = sizeof(int32_t) * frames * channels;\n\n  LOCK();\n\n  if(!audio.mix_buffer || audio.buffer_bytes != sz)\n  {\n    int32_t *buffer = (int32_t *)crealloc(audio.mix_buffer, sz);\n    if(!buffer)\n    {\n      debug(\"--MIXER-- failed buffer alloc of size %zu\\n\", sz);\n      goto err_unlock;\n    }\n\n    audio.mix_buffer = buffer;\n    audio.buffer_bytes = sz;\n  }\n\n  audio.output_frequency = rate;\n  audio.buffer_frames = frames;\n  audio.buffer_channels = channels;\n  ret = true;\n\n  // TODO: if there are any open audio streams, they should sampled_set_buffer\n  // since they rely on output_frequency and buffer_frames.\n\nerr_unlock:\n  UNLOCK();\n\nerr:\n  debug(\"--MIXER-- init %s with rate=%u, frames=%u, channels=%u\\n\",\n   ret ? \"OK\" : \"FAILURE\", rate, frames, channels);\n  return ret;\n}\n\n/**\n * Free the software mixer buffer. This does not need to be called\n * in driver exit functions; quit_audio will call it for them.\n */\nvoid audio_mixer_free(void)\n{\n  free(audio.mix_buffer);\n  audio.mix_buffer = NULL;\n  audio.buffer_bytes = 0;\n  audio.buffer_frames = 0;\n  audio.buffer_channels = 0;\n  audio.output_frequency = 44100;\n}\n\nvoid init_audio(struct config_info *conf)\n{\n  platform_mutex_init(&audio.audio_mutex);\n  platform_mutex_init(&audio.audio_sfx_mutex);\n#ifdef DEBUG\n  platform_mutex_init(&audio.audio_debug_mutex);\n#endif\n\n  audio.output_frequency = 44100; // Dummy value.\n  audio.global_resample_mode = conf->resample_mode;\n\n  audio.max_simultaneous_samples = -1;\n  audio.max_simultaneous_samples_config = conf->max_simultaneous_samples;\n\n  init_wav(conf);\n\n#ifdef CONFIG_VORBIS\n  init_vorbis(conf);\n#endif\n\n#ifdef CONFIG_REALITY\n  init_reality(conf);\n#endif\n\n  // Generic module players may misidentify files due to the ambiguity of\n  // formats like Soundtracker MOD, so register them last.\n#ifdef CONFIG_XMP\n  init_xmp(conf);\n#endif\n\n#ifdef CONFIG_MODPLUG\n  init_modplug(conf);\n#endif\n\n#ifdef CONFIG_MIKMOD\n  init_mikmod(conf);\n#endif\n\n#ifdef CONFIG_OPENMPT\n  init_openmpt(conf);\n#endif\n\n  audio_set_music_volume(conf->music_volume);\n  audio_set_sound_volume(conf->sam_volume);\n  audio_set_music_on(conf->music_on);\n  audio_set_pcs_on(conf->pc_speaker_on);\n\n  init_pc_speaker(conf);\n\n  audio_set_pcs_volume(conf->pc_speaker_volume);\n\n  init_audio_platform(conf);\n}\n\nvoid quit_audio(void)\n{\n  // Signal the audio thread to stop and wait for it to release the lock.\n  quit_audio_platform();\n\n  LOCK();\n\n  audio_garbage_collect();\n  audio_mixer_free();\n  audio_ext_free_registry();\n  free(audio.pcs_stream);\n  audio.pcs_stream = NULL;\n\n  UNLOCK();\n\n#ifdef DEBUG\n  platform_mutex_destroy(&audio.audio_debug_mutex);\n#endif\n  platform_mutex_destroy(&audio.audio_sfx_mutex);\n  platform_mutex_destroy(&audio.audio_mutex);\n}\n\n/* If the mod was successfully changed, return 1.  This value is used\n*  to determine whether to change real_mod_playing.\n*/\nint audio_play_module(char *filename, boolean safely, int volume)\n{\n  char translated_filename[MAX_PATH];\n  struct audio_stream *a_src;\n  int real_volume;\n\n  if(!filename || !filename[0])\n  {\n    debug(\"audio_play_module received empty filename!\\n\");\n    return 0;\n  }\n\n  if(safely)\n  {\n    if(fsafetranslate(filename, translated_filename, MAX_PATH) != FSAFE_SUCCESS &&\n     audio_legacy_translate(filename, translated_filename, MAX_PATH) != FSAFE_SUCCESS)\n    {\n      debug(\"Module filename '%s' failed safety checks\\n\", filename);\n      return 0;\n    }\n\n    filename = translated_filename;\n  }\n\n  audio_end_module();\n\n  real_volume = volume_function(volume, audio.music_volume);\n  a_src = audio_ext_construct_stream(filename, 0, real_volume, 1);\n\n  LOCK();\n\n  audio.primary_stream = a_src;\n\n  UNLOCK();\n  return 1;\n}\n\nvoid audio_end_module(void)\n{\n  if(audio.primary_stream)\n  {\n    struct audio_stream *current_astream;\n\n    LOCK();\n\n    // Ensure that this didn't change while waiting for the lock.\n    if(audio.primary_stream)\n    {\n      audio.primary_stream->destruct(audio.primary_stream);\n      audio.primary_stream = NULL;\n    }\n\n    // Also end any sound effects attached to the mod.\n    current_astream = audio.stream_list_base;\n    while(current_astream)\n    {\n      struct audio_stream *next_astream = current_astream->next;\n\n      if(current_astream->is_spot_sample)\n        current_astream->destruct(current_astream);\n\n      current_astream = next_astream;\n    }\n    audio_garbage_collect();\n\n    UNLOCK();\n  }\n}\n\nvoid audio_set_max_samples(int max_samples)\n{\n  // -1 is unlimited\n  int max_samples_config = audio.max_simultaneous_samples_config;\n\n  if(max_samples_config >= 0)\n  {\n    if((max_samples_config < max_samples) || (max_samples < 0))\n      max_samples = max_samples_config;\n  }\n\n  audio.max_simultaneous_samples = max_samples;\n}\n\nint audio_get_max_samples(void)\n{\n  return audio.max_simultaneous_samples;\n}\n\nstatic void limit_samples(int max)\n{\n  int samples_playing = 0;\n  int cancel_num = 0;\n  struct audio_stream *current_astream;\n  struct audio_stream *next_astream;\n\n  // Don't limit samples if the max samples setting is -1.\n  if(max < 0)\n    return;\n\n  LOCK();\n\n  current_astream = audio.stream_list_base;\n  while(current_astream)\n  {\n    next_astream = current_astream->next;\n\n    if((current_astream != audio.primary_stream) &&\n     (current_astream != (struct audio_stream *)(audio.pcs_stream)))\n    {\n      samples_playing++;\n    }\n\n    current_astream = next_astream;\n  }\n\n  cancel_num = samples_playing - max;\n  if(cancel_num > 0)\n  {\n    current_astream = audio.stream_list_base;\n    while(current_astream && cancel_num > 0)\n    {\n      next_astream = current_astream->next;\n\n      if((current_astream != audio.primary_stream) &&\n       (current_astream != (struct audio_stream *)(audio.pcs_stream)))\n      {\n        current_astream->destruct(current_astream);\n        cancel_num--;\n      }\n\n      current_astream = next_astream;\n    }\n  }\n\n  UNLOCK();\n}\n\nvoid audio_play_sample(char *filename, boolean safely, int period)\n{\n  unsigned int vol = volume_function(255, audio.sound_volume);\n  char translated_filename[MAX_PATH];\n\n  if(safely)\n  {\n    if(fsafetranslate(filename, translated_filename, MAX_PATH) != FSAFE_SUCCESS &&\n     audio_legacy_translate(filename, translated_filename, MAX_PATH) != FSAFE_SUCCESS)\n    {\n      debug(\"Sample filename '%s' failed safety checks\\n\", filename);\n      return;\n    }\n\n    filename = translated_filename;\n  }\n\n  if(period == 0)\n  {\n    // Use 0 to instruct handler to get default frequency\n    audio_ext_construct_stream(filename, 0, vol, 0);\n  }\n  else\n  {\n    /**\n     * NOTE: the period is doubled here to compensate for a SAM to WAV\n     * conversion bug introduced in MZX 2.80. Unfortunately this has\n     * permanently affected the way the frequency field has been used for WAV\n     * and OGG samples and needs to be carried forward.\n     *\n     * Note that WAVs generated from the buggy SAM to WAV conversion routine\n     * are treated as stereo and must also have this buggy doubling. In other\n     * words, just double the frequency in the SAM loader.\n     */\n    audio_ext_construct_stream(filename,\n     audio_get_real_frequency(period * 2), vol, 0);\n  }\n\n  limit_samples(audio.max_simultaneous_samples);\n}\n\nvoid audio_spot_sample(int period, int which)\n{\n  // Play a sample from the current playing mod.\n  // Currently only works with libxmp (and maybe only ever will).\n\n  unsigned int vol = volume_function(255, audio.sound_volume);\n  struct wav_info wav;\n  boolean ret = false;\n\n  memset(&wav, 0, sizeof(struct wav_info));\n\n  LOCK();\n\n  if(audio.primary_stream && audio.primary_stream->get_sample)\n    ret = audio.primary_stream->get_sample(audio.primary_stream, which, &wav);\n\n  UNLOCK();\n\n  if(ret)\n  {\n    /**\n     * NOTE: see above for why the period is being multiplied by 2 here.\n     * The player implementation of get_sample should enable the sam frequency\n     * hack to compensate for this.\n     */\n    struct audio_stream *a_src = construct_wav_stream_direct(&wav,\n     audio_get_real_frequency(period * 2), vol, !!(wav.loop_end));\n    a_src->is_spot_sample = true;\n\n    limit_samples(audio.max_simultaneous_samples);\n  }\n}\n\nvoid audio_end_sample(void)\n{\n  // Destroy all samples - something is a sample if it's not a\n  // primary or PC speaker stream. This is a bit of a dirty way\n  // to do it though (might want to keep multiple lists instead)\n\n  struct audio_stream *current_astream;\n  struct audio_stream *next_astream;\n\n  LOCK();\n\n  current_astream = audio.stream_list_base;\n  while(current_astream)\n  {\n    next_astream = current_astream->next;\n\n    if((current_astream != audio.primary_stream) &&\n     (current_astream != (struct audio_stream *)(audio.pcs_stream)))\n    {\n      current_astream->destruct(current_astream);\n    }\n\n    current_astream = next_astream;\n  }\n  audio_garbage_collect();\n\n  UNLOCK();\n}\n\nvoid audio_set_module_order(int order)\n{\n  // This is intended for modules only, and should not be supported for any\n  // other formats.\n\n  LOCK();\n\n  if(audio.primary_stream && audio.primary_stream->set_order)\n    audio.primary_stream->set_order(audio.primary_stream, order);\n\n  UNLOCK();\n}\n\nint audio_get_module_order(void)\n{\n  int order = 0;\n\n  LOCK();\n\n  if(audio.primary_stream && audio.primary_stream->get_order)\n    order = audio.primary_stream->get_order(audio.primary_stream);\n\n  UNLOCK();\n\n  return order;\n}\n\nvoid audio_set_module_volume(int volume)\n{\n  int real_volume = volume_function(volume, audio.music_volume);\n\n  LOCK();\n\n  if(audio.primary_stream)\n    audio.primary_stream->set_volume(audio.primary_stream, real_volume);\n\n  UNLOCK();\n}\n\nvoid audio_set_module_frequency(int freq)\n{\n  // Primary had better be a sampled stream (in reality I can't imagine\n  // ever letting it be anything but, but if it comes up a type\n  // enumeration could weed this out)\n\n  // Note that shifting the frequency dynamically messes up the phase\n  // counters somewhat producing an audible pop. I've tried to reduce\n  // this without too much success... This might be less noticeable\n  // when interpolation isn't used (but the tradeoff is hardly worth it)\n\n  if(freq >= 16)\n  {\n    LOCK();\n\n    if(audio.primary_stream)\n    {\n      struct sampled_stream *s = (struct sampled_stream *)audio.primary_stream;\n      s->set_frequency(s, freq);\n    }\n\n    UNLOCK();\n  }\n}\n\nint audio_get_module_frequency(void)\n{\n  int freq = 0;\n\n  LOCK();\n\n  if(audio.primary_stream)\n  {\n    struct sampled_stream *s = (struct sampled_stream *)audio.primary_stream;\n    freq = s->get_frequency(s);\n  }\n\n  UNLOCK();\n\n  return freq;\n}\n\nvoid audio_set_module_position(int pos)\n{\n  // Position isn't a universal thing and instead depends on the\n  // medium and what it supports.\n\n  LOCK();\n\n  if(audio.primary_stream && audio.primary_stream->set_position)\n    audio.primary_stream->set_position(audio.primary_stream, pos);\n\n  UNLOCK();\n}\n\nint audio_get_module_position(void)\n{\n  int pos = 0;\n\n  LOCK();\n\n  if(audio.primary_stream && audio.primary_stream->get_position)\n    pos = audio.primary_stream->get_position(audio.primary_stream);\n\n  UNLOCK();\n\n  return pos;\n}\n\nint audio_get_module_length(void)\n{\n  int length = 0;\n\n  LOCK();\n\n  if(audio.primary_stream && audio.primary_stream->get_length)\n    length = audio.primary_stream->get_length(audio.primary_stream);\n\n  UNLOCK();\n\n  return length;\n}\n\nvoid audio_set_module_loop_start(int pos)\n{\n  LOCK();\n\n  if(audio.primary_stream && audio.primary_stream->set_loop_start)\n    audio.primary_stream->set_loop_start(audio.primary_stream, pos);\n\n  UNLOCK();\n}\n\nint audio_get_module_loop_start(void)\n{\n  int loop_start = 0;\n\n  LOCK();\n\n  if(audio.primary_stream && audio.primary_stream->get_loop_start)\n    loop_start = audio.primary_stream->get_loop_start(audio.primary_stream);\n\n  UNLOCK();\n\n  return loop_start;\n}\n\nvoid audio_set_module_loop_end(int pos)\n{\n  LOCK();\n\n  if(audio.primary_stream && audio.primary_stream->set_loop_end)\n    audio.primary_stream->set_loop_end(audio.primary_stream, pos);\n\n  UNLOCK();\n}\n\nint audio_get_module_loop_end(void)\n{\n  int loop_end = 0;\n\n  LOCK();\n\n  if(audio.primary_stream && audio.primary_stream->get_loop_end)\n    loop_end = audio.primary_stream->get_loop_end(audio.primary_stream);\n\n  UNLOCK();\n\n  return loop_end;\n}\n\n#endif\n\nvoid audio_set_music_on(int val)\n{\n  LOCK();\n  audio.music_on = val;\n  UNLOCK();\n}\n\nvoid audio_set_pcs_on(int val)\n{\n  LOCK();\n  audio.pcs_on = val;\n  UNLOCK();\n}\n\n// These don't have to be locked because only one thread can\n// modify them.\n\nint audio_get_music_on(void)\n{\n  return audio.music_on;\n}\n\nint audio_get_pcs_on(void)\n{\n  return audio.pcs_on;\n}\n\nint audio_get_music_volume(void)\n{\n  return audio.music_volume;\n}\n\nint audio_get_sound_volume(void)\n{\n  return audio.sound_volume;\n}\n\nint audio_get_pcs_volume(void)\n{\n  return audio.pcs_volume;\n}\n\nvoid audio_set_music_volume(int volume)\n{\n  LOCK();\n  audio.music_volume = volume;\n  UNLOCK();\n}\n\nvoid audio_set_sound_volume(int volume)\n{\n  struct audio_stream *current_astream;\n  int real_volume;\n\n  LOCK();\n\n  audio.sound_volume = volume;\n  real_volume = volume_function(255, audio.sound_volume);\n\n  current_astream = audio.stream_list_base;\n  while(current_astream)\n  {\n    if((current_astream != audio.primary_stream) &&\n     (current_astream != audio.pcs_stream))\n    {\n      current_astream->set_volume(current_astream, real_volume);\n    }\n\n    current_astream = current_astream->next;\n  }\n\n  UNLOCK();\n}\n\nvoid audio_set_pcs_volume(int volume)\n{\n  int real_volume;\n\n  LOCK();\n\n  audio.pcs_volume = volume;\n  real_volume = volume_function(255, audio.pcs_volume);\n\n  if(audio.pcs_stream)\n    audio.pcs_stream->set_volume(audio.pcs_stream, real_volume);\n\n  UNLOCK();\n}\n\n/**\n * Wrapper for fsafetranslate. Prior to 2.83, the audio code would apply\n * special translations to certain filenames BEFORE the fsafetranslate step\n * that is now in audio_play_module and audio_play_sample; due to this change,\n * certain quirks of the old engine relied on by some games stopped working:\n *\n * + Requests to play files with the .SAM extension would first try to play a\n *   .WAV with the same name even if the .SAM didn't exist at all.\n * + Requests to play files with the .GDM extension would first try to play an\n *   .S3M with the same name even if the .GDM didn't exist at all.\n *\n * This function provides a compatibility layer for this old behavior; use\n * after the initial fsafetranslate fails.\n */\nint audio_legacy_translate(const char *path, char *newpath, size_t buffer_len)\n{\n  char temp[MAX_PATH];\n  ssize_t ext_pos = strlen(path) - 4;\n\n  if(ext_pos >= 0 && (size_t)(ext_pos + 4) < buffer_len)\n  {\n    if(!strcasecmp(path + ext_pos, \".SAM\"))\n    {\n      strcpy(temp, path);\n      strcpy(temp + ext_pos, \".WAV\");\n      return fsafetranslate(temp, newpath, buffer_len);\n    }\n    else\n\n    if(!strcasecmp(path + ext_pos, \".GDM\"))\n    {\n      strcpy(temp, path);\n      strcpy(temp + ext_pos, \".S3M\");\n      return fsafetranslate(temp, newpath, buffer_len);\n    }\n  }\n  return -FSAFE_MATCH_FAILED;\n}\n"
  },
  {
    "path": "src/audio/audio.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Definitions for audio.c\n\n#ifndef __AUDIO_H\n#define __AUDIO_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#ifdef CONFIG_AUDIO\n\n#include <stdint.h>\n\n// Default period for .SAM files.\n#define SAM_DEFAULT_PERIOD 428\n\nstruct config_info;\nstruct audio_stream;\nstruct audio_stream_spec;\n\nCORE_LIBSPEC void init_audio(struct config_info *conf);\nCORE_LIBSPEC void quit_audio(void);\nCORE_LIBSPEC int audio_play_module(char *filename, boolean safely, int volume);\nCORE_LIBSPEC void audio_end_module(void);\nCORE_LIBSPEC void audio_play_sample(char *filename, boolean safely, int period);\nCORE_LIBSPEC void audio_spot_sample(int period, int which);\n\nCORE_LIBSPEC void audio_set_module_volume(int volume);\nvoid audio_set_module_order(int order);\nint audio_get_module_order(void);\nvoid audio_set_module_position(int pos);\nint audio_get_module_position(void);\nvoid audio_set_module_frequency(int freq);\nint audio_get_module_frequency(void);\nint audio_get_module_length(void);\nvoid audio_set_module_loop_start(int pos);\nint audio_get_module_loop_start(void);\nvoid audio_set_module_loop_end(int pos);\nint audio_get_module_loop_end(void);\n\nvoid audio_end_sample(void);\nint audio_get_max_samples(void);\nvoid audio_set_max_samples(int max_samples);\n\nint audio_get_music_on(void);\nint audio_get_pcs_on(void);\nint audio_get_music_volume(void);\nint audio_get_sound_volume(void);\nint audio_get_pcs_volume(void);\nvoid audio_set_music_on(int val);\nvoid audio_set_pcs_on(int val);\nvoid audio_set_music_volume(int volume);\nvoid audio_set_sound_volume(int volume);\nvoid audio_set_pcs_volume(int volume);\n\nint audio_legacy_translate(const char *path, char *newpath, size_t buffer_len);\n\n// Internal functions\nint audio_get_real_frequency(int period);\nvoid destruct_audio_stream(struct audio_stream *a_src);\nvoid initialize_audio_stream(struct audio_stream *a_src,\n struct audio_stream_spec *a_spec, unsigned int volume, boolean repeat);\n\nsize_t audio_mixer_render_frames(void *stream, unsigned frames,\n unsigned channels, unsigned format);\nboolean audio_mixer_init(unsigned rate, unsigned frames, unsigned channels);\nvoid audio_mixer_free(void);\n\n// Platform-related functions.\nvoid init_audio_platform(struct config_info *conf);\nvoid quit_audio_platform(void);\n\n#else // !CONFIG_AUDIO\n\nstatic inline void init_audio(struct config_info *conf) {}\nstatic inline void quit_audio(void) {}\nstatic inline int audio_play_module(char *filename, boolean safely, int volume)\n { return 1; }\nstatic inline void audio_end_module(void) {}\nstatic inline void audio_play_sample(char *filename, boolean safely, int period)\n {}\nstatic inline void audio_spot_sample(int period, int which) {}\n\nstatic inline void audio_set_module_volume(int vol) {}\nstatic inline void audio_set_module_order(int order) {}\nstatic inline int audio_get_module_order(void) { return 0; }\nstatic inline void audio_set_module_position(int pos) {}\nstatic inline int audio_get_module_position(void) { return 0; }\nstatic inline void audio_set_module_frequency(int freq) {}\nstatic inline int audio_get_module_frequency(void) { return 0; }\nstatic inline int audio_get_module_length(void) { return 0; }\nstatic inline void audio_set_module_loop_start(int pos) {}\nstatic inline int audio_get_module_loop_start(void) { return 0; }\nstatic inline void audio_set_module_loop_end(int pos) {}\nstatic inline int audio_get_module_loop_end(void) { return 0; }\n\nstatic inline void audio_end_sample(void) {}\nstatic inline void audio_set_max_samples(int max_samples) {}\nstatic inline int audio_get_max_samples(void) { return 0; }\n\nstatic inline int audio_get_music_on(void) { return 0; }\nstatic inline int audio_get_pcs_on(void) { return 0; }\nstatic inline int audio_get_music_volume(void) { return 0; }\nstatic inline int audio_get_sound_volume(void) { return 0; }\nstatic inline int audio_get_pcs_volume(void) { return 0; }\nstatic inline void audio_set_music_on(int val) {}\nstatic inline void audio_set_pcs_on(int val) {}\nstatic inline void audio_set_music_volume(int volume) {}\nstatic inline void audio_set_sound_volume(int volume) {}\nstatic inline void audio_set_pcs_volume(int volume) {}\n\nstatic inline int audio_legacy_translate(const char *path,\n char *newpath, size_t buffer_len) { return -1; }\n\n#endif // CONFIG_AUDIO\n\n__M_END_DECLS\n\n#endif // __AUDIO_H\n"
  },
  {
    "path": "src/audio/audio_mikmod.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Simon Parzer <simon.parzer@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Provides a Mikmod module stream backend\n\n#include \"audio.h\"\n#include \"audio_mikmod.h\"\n#include \"audio_struct.h\"\n#include \"ext.h\"\n#include \"sampled_stream.h\"\n\n#include \"../const.h\"\n#include \"../util.h\"\n#include \"../io/vio.h\"\n\n#ifdef _WIN32\n#define MIKMOD_STATIC\n#endif\n\n#include <mikmod.h>\n\nstruct mikmod_stream\n{\n  struct sampled_stream s;\n  MODULE *module_data;\n  uint32_t num_orders;\n  uint32_t *order_to_pos_table;\n};\n\nstruct LMM_MREADER\n{\n  MREADER mr;\n  vfile *vf;\n  int eof;\n};\n\nstatic BOOL LMM_Seek(struct MREADER *mr, long to, int dir)\n{\n  struct LMM_MREADER *lmmmr = (struct LMM_MREADER *)mr;\n  return vfseek(lmmmr->vf, to, dir);\n}\n\nstatic long LMM_Tell(struct MREADER *mr)\n{\n  struct LMM_MREADER *lmmmr = (struct LMM_MREADER *)mr;\n  return vftell(lmmmr->vf);\n}\n\nstatic BOOL LMM_Read(struct MREADER *mr, void *buf, size_t sz)\n{\n  struct LMM_MREADER *lmmmr = (struct LMM_MREADER *)mr;\n  return vfread(buf, sz, 1, lmmmr->vf);\n}\n\nstatic int LMM_Get(struct MREADER *mr)\n{\n  struct LMM_MREADER *lmmmr = (struct LMM_MREADER *)mr;\n  return vfgetc(lmmmr->vf);\n}\n\nstatic BOOL LMM_Eof(struct MREADER *mr)\n{\n  struct LMM_MREADER *lmmmr = (struct LMM_MREADER*)mr;\n  return vftell(lmmmr->vf) >= lmmmr->eof;\n}\n\nstatic MODULE *mm_load_vfile(vfile *vf, int maxchan)\n{\n  struct LMM_MREADER lmmmr;\n\n  memset(&lmmmr, 0, sizeof(struct LMM_MREADER));\n  lmmmr.mr.Seek = LMM_Seek;\n  lmmmr.mr.Tell = LMM_Tell;\n  lmmmr.mr.Read = LMM_Read;\n  lmmmr.mr.Get = LMM_Get;\n  lmmmr.mr.Eof = LMM_Eof;\n\n  lmmmr.vf = vf;\n  lmmmr.eof = vfilelength(vf, false);\n\n  return Player_LoadGeneric((MREADER *)&lmmmr, maxchan, 0);\n}\n\n/**\n * Handle the resample mode for MikMod.\n * This is a global rather than a per-module setting.\n */\nstatic void mm_set_resample_mode(void)\n{\n  const struct config_info *conf = get_config();\n  switch(conf->module_resample_mode)\n  {\n    case RESAMPLE_MODE_NONE:\n      md_mode = md_mode & ~(DMODE_INTERP);\n      break;\n\n    case RESAMPLE_MODE_LINEAR:\n    case RESAMPLE_MODE_CUBIC:\n    case RESAMPLE_MODE_FIR:\n    default:\n      md_mode |= DMODE_INTERP;\n      break;\n  }\n}\n\n/**\n * Use MikMod internal data to provide support for length and position.\n * TODO: Dunno how stable this is :-(\n */\nstatic void mm_init_order_table(struct mikmod_stream *mm_stream, MODULE *mm_data)\n{\n  uint32_t current = 0;\n  uint32_t i;\n  uint32_t *tbl;\n  uint32_t num_orders;\n\n  num_orders = mm_data->numpos;\n  tbl = cmalloc((num_orders + 1) * sizeof(uint32_t));\n\n  for(i = 0; i < num_orders; i++)\n  {\n    uint32_t pattern = mm_data->positions[i];\n\n    tbl[i] = current;\n\n    if(pattern < mm_data->numpat)\n      current += mm_data->pattrows[pattern];\n  }\n  tbl[num_orders] = current;\n\n  mm_stream->num_orders = num_orders;\n  mm_stream->order_to_pos_table = tbl;\n}\n\nstatic boolean mm_position_to_order_row(struct mikmod_stream *mm_stream,\n uint32_t position, uint32_t *order, uint32_t *row)\n{\n  uint32_t *tbl = mm_stream->order_to_pos_table;\n  uint32_t i;\n\n  for(i = 0; i < mm_stream->num_orders; i++)\n  {\n    if(tbl[i] <= position && tbl[i + 1] > position)\n    {\n      *order = i;\n      *row = position - tbl[i];\n      return true;\n    }\n  }\n  return false;\n}\n\nstatic boolean mm_mix_data(struct audio_stream *a_src, int32_t * RESTRICT buffer,\n size_t frames, unsigned int channels)\n{\n  struct mikmod_stream *mm_stream = (struct mikmod_stream *)a_src;\n  void *read_buffer;\n  size_t read_wanted;\n\n  read_buffer = sampled_get_buffer(&mm_stream->s, &read_wanted);\n\n  VC_WriteBytes((SBYTE *)read_buffer, read_wanted);\n  sampled_mix_data((struct sampled_stream *)mm_stream, buffer, frames, channels);\n  return false;\n}\n\nstatic void mm_set_volume(struct audio_stream *a_src, unsigned int volume)\n{\n  a_src->volume = volume;\n  Player_SetVolume((SWORD)(volume/2));\n}\n\nstatic void mm_set_repeat(struct audio_stream *a_src, boolean repeat)\n{\n  MODULE *mm_file = ((struct mikmod_stream *)a_src)->module_data;\n\n  a_src->repeat = repeat;\n\n  if(repeat)\n    mm_file->wrap = 1;\n  else\n    mm_file->wrap = 0;\n}\n\nstatic void mm_set_order(struct audio_stream *a_src, uint32_t order)\n{\n  Player_SetPosition(order);\n}\n\nstatic void mm_set_position(struct audio_stream *a_src, uint32_t position)\n{\n  struct mikmod_stream *mm_stream = (struct mikmod_stream *)a_src;\n  uint32_t order, row;\n\n  if(mm_position_to_order_row(mm_stream, position, &order, &row))\n  {\n    Player_SetPosition(order);\n\n    /**\n     * TODO: DIRTY HACK!\n     *\n     * Setting the row directly doesn't affect anything because SetPosition\n     * sets data->posjmp, which will make MikMod reset the row during the next\n     * tick to data->patbrk % data->numrow. So instead of setting the row\n     * directly, set patbrk after using Player_SetPosition to force MikMod to\n     * jump to this row when it processes the next tick.\n     */\n    mm_stream->module_data->patbrk = row;\n  }\n}\n\nstatic void mm_set_frequency(struct sampled_stream *s_src, uint32_t frequency)\n{\n  sampled_set_buffer(s_src, frequency, 44100);\n}\n\nstatic uint32_t mm_get_order(struct audio_stream *a_src)\n{\n  struct mikmod_stream *mm_stream = (struct mikmod_stream *)a_src;\n  return mm_stream->module_data->sngpos;\n}\n\nstatic uint32_t mm_get_position(struct audio_stream *a_src)\n{\n  struct mikmod_stream *mm_stream = (struct mikmod_stream *)a_src;\n  MODULE *mm_data = mm_stream->module_data;\n  uint32_t order = CLAMP(mm_data->sngpos, 0, (int)mm_stream->num_orders);\n\n  return mm_stream->order_to_pos_table[order] + mm_data->patpos;\n}\n\nstatic uint32_t mm_get_length(struct audio_stream *a_src)\n{\n  struct mikmod_stream *mm_stream = (struct mikmod_stream *)a_src;\n  return mm_stream->order_to_pos_table[mm_stream->num_orders];\n}\n\nstatic uint32_t mm_get_frequency(struct sampled_stream *s_src)\n{\n  return s_src->relative_frequency;\n}\n\nstatic void mm_destruct(struct audio_stream *a_src)\n{\n  struct mikmod_stream *mm_stream = (struct mikmod_stream *)a_src;\n  Player_Stop();\n  Player_Free(mm_stream->module_data);\n  free(mm_stream->order_to_pos_table);\n  sampled_destruct(a_src);\n}\n\nstatic struct audio_stream *construct_mikmod_stream(vfile *vf,\n const char *filename, uint32_t frequency, unsigned int volume, boolean repeat)\n{\n  struct mikmod_stream *mm_stream;\n  struct sampled_stream_spec s_spec;\n  struct audio_stream_spec a_spec;\n  MODULE *open_file;\n\n  /**\n   * FIXME since MikMod uses a global player state, attempting to play\n   * multiple modules at the same time predictably causes crashes. Use\n   * this hack to ignore any modules played as samples for now. :(\n   */\n  if(!repeat)\n    return NULL;\n\n  open_file = mm_load_vfile(vf, 64);\n  if(!open_file)\n  {\n    debug(\"MikMod failed to open: %s\\n\", MikMod_strerror(MikMod_errno));\n    return NULL;\n  }\n\n  mm_stream = (struct mikmod_stream *)malloc(sizeof(struct mikmod_stream));\n  if(!mm_stream)\n  {\n    Player_Free(open_file);\n    return NULL;\n  }\n\n  mm_stream->module_data = open_file;\n  Player_Start(mm_stream->module_data);\n\n  mm_init_order_table(mm_stream, open_file);\n  mm_set_resample_mode();\n\n  memset(&a_spec, 0, sizeof(struct audio_stream_spec));\n  a_spec.mix_data     = mm_mix_data;\n  a_spec.set_volume   = mm_set_volume;\n  a_spec.set_repeat   = mm_set_repeat;\n  a_spec.set_order    = mm_set_order;\n  a_spec.set_position = mm_set_position;\n  a_spec.get_order    = mm_get_order;\n  a_spec.get_position = mm_get_position;\n  a_spec.get_length   = mm_get_length;\n  a_spec.destruct     = mm_destruct;\n\n  memset(&s_spec, 0, sizeof(struct sampled_stream_spec));\n  s_spec.set_frequency = mm_set_frequency;\n  s_spec.get_frequency = mm_get_frequency;\n\n  initialize_sampled_stream((struct sampled_stream *)mm_stream, &s_spec,\n   md_mixfreq, frequency, 2, false);\n\n  initialize_audio_stream((struct audio_stream *)mm_stream, &a_spec,\n   volume, repeat);\n\n  vfclose(vf);\n  return (struct audio_stream *)mm_stream;\n}\n\nvoid init_mikmod(struct config_info *conf)\n{\n  md_mixfreq = audio.output_frequency;\n  md_mode = DMODE_16BITS;\n  md_mode |= DMODE_STEREO;\n  md_device = 0;\n  md_volume = 96;\n  md_musicvolume = 128;\n  md_sndfxvolume = 128;\n  md_pansep = 128;\n  md_reverb = 0;\n  md_mode |= DMODE_SOFT_MUSIC | DMODE_SURROUND;\n\n  MikMod_RegisterDriver(&drv_nos);\n\n  /**\n   * These are arranged in the same order as MikMod_RegisterAllLoaders\n   * registers them--alphabetical, with the exception of SoundTracker 15-sample\n   * .MODs last (presumably since they do not have a magic string).\n   */\n  MikMod_RegisterLoader(&load_669);\n  MikMod_RegisterLoader(&load_amf); // DSMI .AMF\n  MikMod_RegisterLoader(&load_asy); // ASYLUM .AMF\n  MikMod_RegisterLoader(&load_dsm);\n  MikMod_RegisterLoader(&load_far);\n  MikMod_RegisterLoader(&load_gdm);\n  MikMod_RegisterLoader(&load_it);\n  MikMod_RegisterLoader(&load_mod);\n  MikMod_RegisterLoader(&load_med);\n  MikMod_RegisterLoader(&load_mtm);\n  MikMod_RegisterLoader(&load_okt);\n  MikMod_RegisterLoader(&load_stm);\n  MikMod_RegisterLoader(&load_s3m);\n  MikMod_RegisterLoader(&load_ult);\n  MikMod_RegisterLoader(&load_xm);\n  MikMod_RegisterLoader(&load_m15); // Soundtracker .MOD\n\n  if(MikMod_Init(NULL) == 0)\n  {\n    audio_ext_register(NULL, construct_mikmod_stream);\n  }\n  else\n    warn(\"MikMod Init failed: %s\\n\", MikMod_strerror(MikMod_errno));\n}\n"
  },
  {
    "path": "src/audio/audio_mikmod.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declarations */\n\n#ifndef __AUDIO_MIKMOD_H\n#define __AUDIO_MIKMOD_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\nvoid init_mikmod(struct config_info *conf);\n\n__M_END_DECLS\n\n#endif  // __AUDIO_MIKMOD_H\n"
  },
  {
    "path": "src/audio/audio_modplug.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Provides a ModPlug module stream backend\n\n#include <modplug.h>\n#include <stdafx.h>\n#include <sndfile.h>\n\n#include \"audio.h\"\n#include \"audio_modplug.h\"\n#include \"audio_struct.h\"\n#include \"ext.h\"\n#include \"sampled_stream.h\"\n\n#include \"../const.h\"\n#include \"../configure.h\"\n#include \"../util.h\"\n#include \"../io/fsafeopen.h\"\n#include \"../io/vio.h\"\n\nstruct _ModPlugFile\n{\n  CSoundFile mSoundFile;\n};\n\nstruct modplug_stream\n{\n  struct sampled_stream s;\n  ModPlugFile *module_data;\n};\n\nstatic void init_modplug_settings(void)\n{\n  const struct config_info *conf = get_config();\n  ModPlug_Settings mod_settings;\n\n  memset(&mod_settings, 0, sizeof(ModPlug_Settings));\n\n  if(conf->oversampling_on)\n    mod_settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING;\n\n  mod_settings.mFrequency = audio.output_frequency;\n  mod_settings.mChannels = audio.buffer_channels < 2 ? 1 : 2;\n  mod_settings.mBits = 16;\n\n  switch(conf->module_resample_mode)\n  {\n    case RESAMPLE_MODE_NONE:\n      mod_settings.mResamplingMode = MODPLUG_RESAMPLE_NEAREST;\n      break;\n\n    case RESAMPLE_MODE_LINEAR:\n    default:\n      mod_settings.mResamplingMode = MODPLUG_RESAMPLE_LINEAR;\n      break;\n\n    case RESAMPLE_MODE_CUBIC:\n      mod_settings.mResamplingMode = MODPLUG_RESAMPLE_SPLINE;\n      break;\n\n    case RESAMPLE_MODE_FIR:\n      mod_settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;\n      break;\n  }\n\n  mod_settings.mLoopCount = -1;\n\n  ModPlug_SetSettings(&mod_settings);\n}\n\nstatic boolean mp_mix_data(struct audio_stream *a_src, int32_t * RESTRICT buffer,\n size_t frames, unsigned int channels)\n{\n  struct modplug_stream *mp_stream = (struct modplug_stream *)a_src;\n  void *read_buffer;\n  size_t read_wanted;\n  size_t read_len;\n  boolean r_val = false;\n\n  read_buffer = sampled_get_buffer(&mp_stream->s, &read_wanted);\n\n  read_len = ModPlug_Read(mp_stream->module_data, read_buffer, read_wanted);\n\n  if(!a_src->volume)\n  {\n    // Modplug doesn't support a volume of zero; the lowest volume usable\n    // via SetMasterVolume is 1. If the volume is 0, zero the buffer we\n    // just read. We read the buffer to ensure the mod still progresses\n    // in the background, maintaining the old semantics.\n    memset(read_buffer, 0, read_len);\n  }\n\n  if(read_len < read_wanted)\n  {\n    // Modplug still fails to repeat on some mods, even after already\n    // restarting it still ends the thing (even fades it). This won't\n    // eliminate the fade but at least it'll stop the mod from\n    // ending..\n\n    if(a_src->repeat)\n    {\n      a_src->set_position(a_src, a_src->get_position(a_src));\n    }\n    else\n    {\n      // FIXME: I think this memset should always be done?\n      memset((uint8_t *)read_buffer + read_len, 0, read_wanted - read_len);\n      r_val = true;\n    }\n\n    read_len = 0;\n  }\n\n  sampled_mix_data((struct sampled_stream *)mp_stream, buffer, frames, channels);\n\n  return r_val;\n}\n\nstatic void mp_set_volume(struct audio_stream *a_src, unsigned int volume)\n{\n  ModPlugFile *mp_file = ((struct modplug_stream *)a_src)->module_data;\n\n  a_src->volume = volume;\n  mp_file->mSoundFile.SetMasterVolume(volume);\n}\n\nstatic void mp_set_repeat(struct audio_stream *a_src, boolean repeat)\n{\n  ModPlugFile *mp_file = ((struct modplug_stream *)a_src)->module_data;\n\n  a_src->repeat = repeat;\n\n  if(repeat)\n    mp_file->mSoundFile.SetRepeatCount(-1);\n  else\n    mp_file->mSoundFile.SetRepeatCount(0);\n}\n\nstatic void mp_set_order(struct audio_stream *a_src, uint32_t order)\n{\n  ((struct modplug_stream *)a_src)->module_data->\n   mSoundFile.SetCurrentOrder(order);\n}\n\nstatic void mp_set_position(struct audio_stream *a_src, uint32_t position)\n{\n  ((struct modplug_stream *)a_src)->module_data->\n   mSoundFile.SetCurrentPos(position);\n}\n\nstatic void mp_set_frequency(struct sampled_stream *s_src, uint32_t frequency)\n{\n  sampled_set_buffer(s_src, frequency, 44100);\n}\n\nstatic uint32_t mp_get_order(struct audio_stream *a_src)\n{\n  return ((struct modplug_stream *)a_src)->module_data->\n   mSoundFile.GetCurrentOrder();\n}\n\nstatic uint32_t mp_get_position(struct audio_stream *a_src)\n{\n  return ((struct modplug_stream *)a_src)->module_data->\n   mSoundFile.GetCurrentPos();\n}\n\nstatic uint32_t mp_get_length(struct audio_stream *a_src)\n{\n  return ((struct modplug_stream *)a_src)->module_data->\n   mSoundFile.GetMaxPosition();\n}\n\nstatic uint32_t mp_get_frequency(struct sampled_stream *s_src)\n{\n  return s_src->relative_frequency;\n}\n\nstatic void mp_destruct(struct audio_stream *a_src)\n{\n  struct modplug_stream *mp_stream = (struct modplug_stream *)a_src;\n  ModPlug_Unload(mp_stream->module_data);\n  sampled_destruct(a_src);\n}\n\nstatic struct audio_stream *construct_modplug_stream(vfile *vf,\n const char *filename, uint32_t frequency, unsigned int volume, boolean repeat)\n{\n  struct modplug_stream *mp_stream;\n  struct sampled_stream_spec s_spec;\n  struct audio_stream_spec a_spec;\n  void *input_buffer;\n\n  size_t file_size = vfilelength(vf, false);\n  ModPlugFile *open_file;\n\n  init_modplug_settings();\n\n  input_buffer = malloc(file_size);\n  if(!input_buffer)\n    return NULL;\n\n  if(!vfread(input_buffer, file_size, 1, vf))\n  {\n    free(input_buffer);\n    return NULL;\n  }\n\n  open_file = ModPlug_Load(input_buffer, file_size);\n  free(input_buffer);\n\n  if(!open_file)\n    return NULL;\n\n  mp_stream = (struct modplug_stream *)malloc(sizeof(struct modplug_stream));\n  if(!mp_stream)\n  {\n    ModPlug_Unload(open_file);\n    return NULL;\n  }\n\n  mp_stream->module_data = open_file;\n\n  memset(&a_spec, 0, sizeof(struct audio_stream_spec));\n  a_spec.mix_data     = mp_mix_data;\n  a_spec.set_volume   = mp_set_volume;\n  a_spec.set_repeat   = mp_set_repeat;\n  a_spec.set_order    = mp_set_order;\n  a_spec.set_position = mp_set_position;\n  a_spec.get_order    = mp_get_order;\n  a_spec.get_position = mp_get_position;\n  a_spec.get_length   = mp_get_length;\n  a_spec.destruct     = mp_destruct;\n\n  memset(&s_spec, 0, sizeof(struct sampled_stream_spec));\n  s_spec.set_frequency = mp_set_frequency;\n  s_spec.get_frequency = mp_get_frequency;\n\n  initialize_sampled_stream((struct sampled_stream *)mp_stream, &s_spec,\n   audio.output_frequency, frequency, 2, false);\n\n  initialize_audio_stream((struct audio_stream *)mp_stream, &a_spec,\n   volume, repeat);\n\n  vfclose(vf);\n  return (struct audio_stream *)mp_stream;\n}\n\nvoid init_modplug(struct config_info *conf)\n{\n  audio_ext_register(NULL, construct_modplug_stream);\n}\n"
  },
  {
    "path": "src/audio/audio_modplug.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declarations */\n\n#ifndef __AUDIO_MODPLUG_H\n#define __AUDIO_MODPLUG_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\nvoid init_modplug(struct config_info *conf);\n\n__M_END_DECLS\n\n#endif  // __AUDIO_MODPLUG_H\n"
  },
  {
    "path": "src/audio/audio_openmpt.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Simon Parzer <simon.parzer@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Provides a libopenmpt module stream backend\n\n#include \"audio.h\"\n#include \"audio_openmpt.h\"\n#include \"audio_struct.h\"\n#include \"ext.h\"\n#include \"sampled_stream.h\"\n\n#include \"../util.h\"\n#include \"../io/path.h\"\n#include \"../io/vio.h\"\n\n#include <libopenmpt/libopenmpt.h>\n#include <libopenmpt/libopenmpt_stream_callbacks_file.h>\n#include <limits.h>\n#include <stdlib.h>\n\nstruct openmpt_stream\n{\n  struct sampled_stream s;\n  openmpt_module *module_data;\n  uint32_t *row_tbl;\n  uint32_t row_tbl_size;\n  uint32_t total_rows;\n};\n\n// OpenMPT's resampling option takes interpolation taps instead of an enum.\nstatic int omp_get_resample_mode(void)\n{\n  const struct config_info *conf = get_config();\n\n  switch(conf->module_resample_mode)\n  {\n    case RESAMPLE_MODE_NONE:\n      return 1;\n\n    case RESAMPLE_MODE_LINEAR:\n    default:\n      return 2;\n\n    case RESAMPLE_MODE_CUBIC:\n      return 4;\n\n    case RESAMPLE_MODE_FIR:\n      return 8;\n  }\n}\n\nstatic boolean omp_mix_data(struct audio_stream *a_src, int32_t *buffer,\n size_t frames, unsigned int channels)\n{\n  struct openmpt_stream *omp_stream = (struct openmpt_stream *)a_src;\n  struct sampled_stream *s = (struct sampled_stream *)a_src;\n\n  int16_t *read_buffer;\n  size_t read_wanted;\n  boolean r_val = false;\n  uint32_t read_len;\n\n  read_buffer = (int16_t *)sampled_get_buffer(&omp_stream->s, &read_wanted);\n\n  if(s->channels >= 2)\n  {\n    read_len = openmpt_module_read_interleaved_stereo(omp_stream->module_data,\n     s->input_frequency, read_wanted / 4, read_buffer) * 4;\n  }\n  else\n  {\n    read_len = openmpt_module_read_mono(omp_stream->module_data,\n     s->input_frequency, read_wanted / 2, read_buffer) * 2;\n  }\n\n  if(read_len < read_wanted && !a_src->repeat)\n  {\n    memset(read_buffer + read_len, 0, read_wanted - read_len);\n    r_val = true;\n  }\n\n  sampled_mix_data(s, buffer, frames, channels);\n\n  return r_val;\n}\n\nstatic void omp_set_volume(struct audio_stream *a_src, unsigned int volume)\n{\n  struct openmpt_stream *omp_stream = (struct openmpt_stream *)a_src;\n  a_src->volume = volume;\n\n  if(volume == 0)\n    volume = INT_MIN;\n\n  else\n    volume = (volume - 255) * 15;\n\n  openmpt_module_set_render_param(omp_stream->module_data,\n    OPENMPT_MODULE_RENDER_MASTERGAIN_MILLIBEL, volume);\n}\n\nstatic void omp_set_repeat(struct audio_stream *a_src, boolean repeat)\n{\n  struct openmpt_stream *omp_stream = (struct openmpt_stream *)a_src;\n  a_src->repeat = repeat;\n\n  openmpt_module_set_repeat_count(omp_stream->module_data, repeat ? -1 : 0);\n}\n\nstatic void omp_set_order(struct audio_stream *a_src, uint32_t order)\n{\n  struct openmpt_stream *omp_stream = (struct openmpt_stream *)a_src;\n\n  openmpt_module_set_position_order_row(omp_stream->module_data, order, 0);\n}\n\nstatic void omp_set_position(struct audio_stream *a_src, uint32_t position)\n{\n  struct openmpt_stream *omp_stream = (struct openmpt_stream *)a_src;\n  uint32_t ord;\n  uint32_t row;\n\n  for(ord = 1; ord < omp_stream->row_tbl_size; ord++)\n  {\n    if(position < omp_stream->row_tbl[ord])\n    {\n      row = position - omp_stream->row_tbl[ord - 1];\n\n      openmpt_module_set_position_order_row(omp_stream->module_data, ord, row);\n      return;\n    }\n  }\n\n  // fallback - position out of range\n  openmpt_module_set_position_order_row(omp_stream->module_data, 0, 0);\n}\n\nstatic void omp_set_frequency(struct sampled_stream *s_src, uint32_t frequency)\n{\n  sampled_set_buffer(s_src, frequency, 44100);\n}\n\nstatic uint32_t omp_get_order(struct audio_stream *a_src)\n{\n  struct openmpt_stream *omp_stream = (struct openmpt_stream *)a_src;\n\n  return openmpt_module_get_current_order(omp_stream->module_data);\n}\n\nstatic uint32_t omp_get_position(struct audio_stream *a_src)\n{\n  struct openmpt_stream *omp_stream = (struct openmpt_stream *)a_src;\n\n  int ord = openmpt_module_get_current_order(omp_stream->module_data);\n\n  return omp_stream->row_tbl[ord] +\n   openmpt_module_get_current_row(omp_stream->module_data);\n}\n\nstatic uint32_t omp_get_length(struct audio_stream *a_src)\n{\n  struct openmpt_stream *omp_stream = (struct openmpt_stream *)a_src;\n\n  return omp_stream->row_tbl[omp_stream->row_tbl_size - 1];\n}\n\nstatic uint32_t omp_get_frequency(struct sampled_stream *s_src)\n{\n  return s_src->relative_frequency;\n}\n\nstatic void omp_destruct(struct audio_stream *a_src)\n{\n  struct openmpt_stream *omp_stream = (struct openmpt_stream *)a_src;\n\n  openmpt_module_destroy(omp_stream->module_data);\n  free(omp_stream->row_tbl);\n  sampled_destruct(a_src);\n}\n\nstatic void omp_log(const char *message, void *data)\n{\n  if(message)\n     fprintf(mzxerr, \"%s\\n\", message);\n}\n\nstatic size_t omp_read_fn(void *stream, void *dest, size_t bytes)\n{\n  vfile *vf = (vfile *)stream;\n  return vfread(dest, 1, bytes, vf);\n}\n\nstatic int omp_seek_fn(void *stream, int64_t offset, int whence)\n{\n  vfile *vf = (vfile *)stream;\n  return vfseek(vf, offset, whence);\n}\n\nstatic int64_t omp_tell_fn(void *stream)\n{\n  vfile *vf = (vfile *)stream;\n  return vftell(vf);\n}\n\nstatic const struct openmpt_stream_callbacks omp_callbacks =\n{\n  omp_read_fn,\n  omp_seek_fn,\n  omp_tell_fn\n};\n\nstatic struct audio_stream *construct_openmpt_stream(vfile *vf,\n const char *filename, uint32_t frequency, unsigned int volume, boolean repeat)\n{\n  struct openmpt_stream *omp_stream;\n  struct sampled_stream_spec s_spec;\n  struct audio_stream_spec a_spec;\n  uint32_t *row_tbl;\n  uint32_t row_pos;\n  uint32_t ord;\n  uint32_t i;\n  int row_tbl_size;\n  int chn = audio.buffer_channels < 2 ? 1 : 2;\n\n  openmpt_module *open_file = openmpt_module_create2(omp_callbacks, vf,\n   &omp_log, NULL, NULL, NULL, NULL, NULL, NULL);\n  if(!open_file)\n    return NULL;\n\n  row_tbl_size = openmpt_module_get_num_orders(open_file) + 1;\n  if(row_tbl_size < 1)\n    row_tbl_size = 1;\n\n  omp_stream = (struct openmpt_stream *)malloc(sizeof(struct openmpt_stream));\n  row_tbl = (uint32_t *)malloc(row_tbl_size * sizeof(uint32_t));\n  if(!omp_stream || !row_tbl)\n  {\n    openmpt_module_destroy(open_file);\n    free(omp_stream);\n    free(row_tbl);\n    return NULL;\n  }\n\n  omp_stream->module_data = open_file;\n  omp_stream->row_tbl = row_tbl;\n  omp_stream->row_tbl_size = row_tbl_size;\n\n  for(i = 0, row_pos = 0; i < (uint32_t)row_tbl_size; i++)\n  {\n    row_tbl[i] = row_pos;\n    ord = openmpt_module_get_order_pattern(open_file, i);\n    if(i < (uint32_t)row_tbl_size - 1)\n      row_pos += openmpt_module_get_pattern_num_rows(open_file, ord);\n  }\n\n  openmpt_module_set_render_param(omp_stream->module_data,\n   OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH,\n   omp_get_resample_mode());\n\n  openmpt_module_set_repeat_count(omp_stream->module_data, -1);\n\n  memset(&a_spec, 0, sizeof(struct audio_stream_spec));\n  a_spec.mix_data     = omp_mix_data;\n  a_spec.set_volume   = omp_set_volume;\n  a_spec.set_repeat   = omp_set_repeat;\n  a_spec.set_order    = omp_set_order;\n  a_spec.set_position = omp_set_position;\n  a_spec.get_order    = omp_get_order;\n  a_spec.get_position = omp_get_position;\n  a_spec.get_length   = omp_get_length;\n  a_spec.destruct     = omp_destruct;\n\n  memset(&s_spec, 0, sizeof(struct sampled_stream_spec));\n  s_spec.set_frequency = omp_set_frequency;\n  s_spec.get_frequency = omp_get_frequency;\n\n  initialize_sampled_stream((struct sampled_stream *)omp_stream, &s_spec,\n   audio.output_frequency, frequency, chn, false);\n\n  initialize_audio_stream((struct audio_stream *)omp_stream, &a_spec,\n   volume, repeat);\n\n  vfclose(vf);\n  return (struct audio_stream *)omp_stream;\n}\n\nstatic boolean test_openmpt_stream(vfile *vf, const char *filename)\n{\n  /* TODO: OpenMPT doesn't have a way to whitelist individual formats yet,\n   * and having to add Symphonie/etc. support to libxmp because a package\n   * maintainer liked OpenMPT better doesn't sound fun, so filter by extension.\n   * This is not foolproof.\n   */\n  static const char * const ext_list[] =\n  {\n    \"669\", \"amf\", \"dsm\", \"far\", \"gdm\", \"it\", \"med\", \"mod\", \"mtm\",\n    \"nst\", \"oct\", \"okt\", \"s3m\", \"stm\", \"ult\", \"wow\", \"xm\"\n  };\n  ssize_t ext_pos = path_get_ext_offset(filename);\n  size_t i;\n  if(ext_pos < 0)\n    return false;\n\n  for(i = 0; i < ARRAY_SIZE(ext_list); i++)\n  {\n    if(!strcasecmp(filename + ext_pos + 1, ext_list[i]))\n      return true;\n  }\n  return false;\n}\n\nvoid init_openmpt(struct config_info *conf)\n{\n  audio_ext_register(test_openmpt_stream, construct_openmpt_stream);\n}\n"
  },
  {
    "path": "src/audio/audio_openmpt.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declarations */\n\n#ifndef __AUDIO_OPENMPT_H\n#define __AUDIO_OPENMPT_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\nvoid init_openmpt(struct config_info *conf);\n\n__M_END_DECLS\n\n#endif  // __AUDIO_OPENMPT_H\n"
  },
  {
    "path": "src/audio/audio_pcs.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2004 madbrain\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2018-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"audio.h\"\n#include \"audio_pcs.h\"\n#include \"audio_struct.h\"\n#include \"sfx.h\"\n\n#include \"../configure.h\"\n\n#include <string.h>\n\nstruct pc_speaker_stream\n{\n  struct audio_stream a;\n  uint32_t volume;\n  uint32_t playing;\n  uint32_t frequency;\n  uint32_t last_frequency;\n  uint32_t note_duration;\n  uint32_t last_duration;\n  uint32_t last_playing;\n  uint32_t sample_cutoff;\n  uint32_t last_increment_buffer;\n};\n\nstatic boolean pcs_mix_data(struct audio_stream *a_src, int32_t * RESTRICT buffer,\n size_t dest_frames, unsigned int dest_channels)\n{\n  struct pc_speaker_stream *pcs_stream = (struct pc_speaker_stream *)a_src;\n  uint32_t offset = 0, i, j;\n  uint32_t sample_duration = pcs_stream->last_duration;\n  uint32_t end_duration;\n  uint32_t increment_value, increment_buffer;\n  uint32_t sfx_scale = (pcs_stream->volume ? pcs_stream->volume + 1 : 0) * 32;\n  uint32_t sfx_scale_half = sfx_scale / 2;\n  int32_t *mix_dest_ptr = buffer;\n  int16_t cur_sample;\n\n  /**\n   * Cancel the current playing note if PC speaker effects were turned off or\n   * if the sfx queue was cleared.\n   */\n  if(!audio_get_pcs_on() || sfx_should_cancel_note())\n  {\n    pcs_stream->last_playing = 0;\n    pcs_stream->last_duration = 0;\n    sample_duration = 0;\n  }\n\n  if(sample_duration >= dest_frames)\n  {\n    end_duration = dest_frames;\n    pcs_stream->last_duration = sample_duration - end_duration;\n    sample_duration = end_duration;\n  }\n\n  if(pcs_stream->last_playing)\n  {\n    increment_value =\n     (uint32_t)((double)pcs_stream->last_frequency /\n     (audio.output_frequency) * 4294967296.0);\n\n    increment_buffer = pcs_stream->last_increment_buffer;\n\n    for(i = 0; i < sample_duration; i++)\n    {\n      cur_sample = (uint32_t)((increment_buffer & 0x80000000) >> 31) *\n       sfx_scale - sfx_scale_half;\n      for(j = 0; j < dest_channels; j++)\n        *(mix_dest_ptr++) += cur_sample;\n      increment_buffer += increment_value;\n    }\n    pcs_stream->last_increment_buffer = increment_buffer;\n  }\n  else\n  {\n    mix_dest_ptr += (sample_duration * dest_channels);\n  }\n\n  offset += sample_duration;\n\n  if(offset < dest_frames)\n    pcs_stream->last_playing = 0;\n\n  while(offset < dest_frames)\n  {\n    int playing, frequency, note_duration;\n    sfx_next_note(&playing, &frequency, &note_duration);\n    pcs_stream->playing = playing;\n    pcs_stream->frequency = frequency;\n    pcs_stream->note_duration = note_duration;\n\n    // Minimum note duration is 1 to prevent locking up the audio thread.\n    if(pcs_stream->note_duration < 1)\n      pcs_stream->note_duration = 1;\n\n    sample_duration = (uint32_t)((double)audio.output_frequency / 500.0 *\n     pcs_stream->note_duration);\n\n    if(offset + sample_duration >= dest_frames)\n    {\n      end_duration = dest_frames - offset;\n      pcs_stream->last_duration = sample_duration - end_duration;\n      pcs_stream->last_playing = pcs_stream->playing;\n      pcs_stream->last_frequency = pcs_stream->frequency;\n\n      sample_duration = end_duration;\n    }\n\n    offset += sample_duration;\n\n    if(pcs_stream->playing)\n    {\n      increment_value =\n       (uint32_t)((double)pcs_stream->frequency /\n       audio.output_frequency * 4294967296.0);\n      increment_buffer = 0;\n\n      for(i = 0; i < sample_duration; i++)\n      {\n        cur_sample = (uint32_t)((increment_buffer & 0x80000000) >> 31) *\n         sfx_scale - sfx_scale_half;\n        for(j = 0; j < dest_channels; j++)\n          *(mix_dest_ptr++) += cur_sample;\n        increment_buffer += increment_value;\n      }\n      pcs_stream->last_increment_buffer = increment_buffer;\n    }\n    else\n    {\n      mix_dest_ptr += (sample_duration * dest_channels);\n    }\n  }\n\n  return 0;\n}\n\nstatic void pcs_set_volume(struct audio_stream *a_src, unsigned int volume)\n{\n  ((struct pc_speaker_stream *)a_src)->volume = volume;\n}\n\nstatic void pcs_destruct(struct audio_stream *a_src)\n{\n  destruct_audio_stream(a_src);\n}\n\nstatic struct audio_stream *construct_pc_speaker_stream(void)\n{\n  struct pc_speaker_stream *pcs_stream;\n  struct audio_stream_spec a_spec;\n\n  pcs_stream = ccalloc(1, sizeof(struct pc_speaker_stream));\n\n  memset(&a_spec, 0, sizeof(struct audio_stream_spec));\n  a_spec.mix_data   = pcs_mix_data;\n  a_spec.set_volume = pcs_set_volume;\n  a_spec.destruct   = pcs_destruct;\n\n  // The volume here will be corrected after initialization...\n  initialize_audio_stream((struct audio_stream *)pcs_stream, &a_spec, 255, 0);\n\n  return (struct audio_stream *)pcs_stream;\n}\n\nvoid init_pc_speaker(struct config_info *conf)\n{\n  audio.pcs_stream = construct_pc_speaker_stream();\n}\n"
  },
  {
    "path": "src/audio/audio_pcs.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __AUDIO_PCS_H\n#define __AUDIO_PCS_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\nvoid init_pc_speaker(struct config_info *conf);\n\n__M_END_DECLS\n\n#endif /* __AUDIO_PCS_H */\n"
  },
  {
    "path": "src/audio/audio_reality.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Parts of this code are based off of the RAD v2 example.cpp player by Reality.\n\n#include \"audio.h\"\n#include \"audio_reality.h\"\n#include \"audio_struct.h\"\n#include \"ext.h\"\n#include \"sampled_stream.h\"\n\n#include \"../util.h\"\n#include \"../io/vio.h\"\n\n#include <stdint.h>\n#include <cstdio>\n#include <cstdlib>\n#include <new>\n\n#ifdef __APPLE__\n// Mac OS X 10.5 SDK somehow defines this to unsigned int __vector__...\n#undef bool\n#endif\n\n// Yes, this is how the Reality player is intended to be included.\n#define RAD_DETECT_REPEATS 1\n#include \"../../contrib/rad/opal.cpp\"\n#include \"../../contrib/rad/player20.cpp\"\n#include \"../../contrib/rad/validate20.cpp\"\n\n#define OPL_FREQUENCY 49716\n\n// Every order in a RAD is fixed length. This makes a few operations for this\n// player much easier.\n#define ORDER_LINES 64\n\nclass FastOpal : public Opal\n{\npublic:\n  FastOpal() : Opal(OPL_FREQUENCY) {}\n\n  /**\n   * This skips Opal's built-in linear resampler. The built-in resampler uses\n   * integer division, which is slow, particularly on platforms like the 3DS\n   * that lack an integer division instruction. This means that the output will\n   * always be 49716Hz and thus needs resampling (which still should be faster).\n   */\n  void Sample(int16_t *left, int16_t *right)\n  {\n    Output(*left, *right);\n  }\n};\n\nstruct rad_stream_cls\n{\n  FastOpal adlib;\n  RADPlayer player;\n};\n\nstruct rad_stream\n{\n  struct sampled_stream s;\n  struct rad_stream_cls *cls;\n  uint8_t *data;\n  size_t data_length;\n  uint32_t sample_update_timer;\n  uint32_t sample_update_max;\n};\n\nstatic boolean rad_mix_data(struct audio_stream *a_src, int32_t * RESTRICT buffer,\n size_t frames, unsigned int channels)\n{\n  struct rad_stream *rad_stream = (struct rad_stream *)a_src;\n  int16_t *read_buffer;\n  size_t read_wanted;\n  boolean rval = false;\n  uint32_t i;\n\n  read_buffer = (int16_t *)sampled_get_buffer(&rad_stream->s, &read_wanted);\n\n  for(i = 0; i < read_wanted; i += 4)\n  {\n    rad_stream->cls->adlib.Sample(read_buffer, read_buffer + 1);\n    read_buffer += 2;\n\n    rad_stream->sample_update_timer++;\n    if(rad_stream->sample_update_timer >= rad_stream->sample_update_max)\n    {\n      // Play the next line of the RAD.\n      boolean repeated = rad_stream->cls->player.Update();\n      rad_stream->sample_update_timer = 0;\n\n      if(repeated && !a_src->repeat)\n        rval = true;\n    }\n  }\n\n  sampled_mix_data((struct sampled_stream *)a_src, buffer, frames, channels);\n  return rval;\n}\n\nstatic void rad_set_volume(struct audio_stream *a_src, unsigned int volume)\n{\n  //struct rad_stream *rad_stream = (struct rad_stream *)a_src;\n\n  //rad_stream->cls->player.SetMasterVolume(volume * 64 / 255);\n\n  // Use sampled_stream volume since the RAD player master volume doesn't\n  // take effect fast enough.\n  a_src->volume = volume * 256 / 255;\n}\n\nstatic void rad_set_repeat(struct audio_stream *a_src, boolean repeat)\n{\n  a_src->repeat = repeat;\n}\n\nstatic void rad_set_order(struct audio_stream *a_src, uint32_t order)\n{\n  struct rad_stream *rad_stream = (struct rad_stream *)a_src;\n\n  rad_stream->cls->player.SetTunePos(order, 0);\n}\n\nstatic void rad_set_position(struct audio_stream *a_src, uint32_t position)\n{\n  struct rad_stream *rad_stream = (struct rad_stream *)a_src;\n  uint32_t order = position / ORDER_LINES;\n  uint32_t line = position % ORDER_LINES;\n\n  rad_stream->cls->player.SetTunePos(order, line);\n}\n\nstatic void rad_set_frequency(struct sampled_stream *s_src, uint32_t frequency)\n{\n  sampled_set_buffer(s_src, frequency, 44100);\n}\n\nstatic uint32_t rad_get_order(struct audio_stream *a_src)\n{\n  struct rad_stream *rad_stream = (struct rad_stream *)a_src;\n\n  return rad_stream->cls->player.GetTunePos();\n}\n\nstatic uint32_t rad_get_position(struct audio_stream *a_src)\n{\n  struct rad_stream *rad_stream = (struct rad_stream *)a_src;\n  uint32_t order = rad_stream->cls->player.GetTunePos();\n  uint32_t line = rad_stream->cls->player.GetTuneLine();\n\n  return order * ORDER_LINES + line;\n}\n\nstatic uint32_t rad_get_length(struct audio_stream *a_src)\n{\n  struct rad_stream *rad_stream = (struct rad_stream *)a_src;\n  uint32_t length = rad_stream->cls->player.GetTuneEffectiveLength();\n\n  return length * ORDER_LINES;\n}\n\nstatic uint32_t rad_get_frequency(struct sampled_stream *s_src)\n{\n  return s_src->relative_frequency;\n}\n\nstatic void rad_destruct(struct audio_stream *a_src)\n{\n  struct rad_stream *rad_stream = (struct rad_stream *)a_src;\n  free(rad_stream->data);\n  delete rad_stream->cls;\n  sampled_destruct(a_src);\n}\n\nstatic void rad_player_callback(void *arg, uint16_t reg, uint8_t data)\n{\n  struct rad_stream_cls *cls = (struct rad_stream_cls *)arg;\n  cls->adlib.Port(reg, data);\n}\n\nstatic struct audio_stream *construct_rad_stream(vfile *vf,\n const char *filename, uint32_t frequency, unsigned int volume, boolean repeat)\n{\n  struct rad_stream *rad_stream = NULL;\n  struct rad_stream_cls *cls;\n  struct audio_stream_spec a_spec;\n  struct sampled_stream_spec s_spec;\n  const char *validate;\n  size_t length;\n  uint8_t *data;\n  uint32_t rate;\n\n  /**\n   * NOTE: some legacy RAD files in the Reality public archive have a single\n   * byte truncated off of the end for no apparent reason. These files load\n   * and play normally otherwise. Allocate an extra null byte so they load.\n   */\n  length = vfilelength(vf, false);\n  data = (uint8_t *)malloc(length + 1);\n  if(!data)\n    return NULL;\n\n  data[length] = 0;\n\n  if(!vfread(data, length, 1, vf))\n  {\n    free(data);\n    return NULL;\n  }\n\n  validate = RADValidate(data, length);\n  if(validate != NULL)\n  {\n    debug(\"RAD failed to load module '%s': %s\\n\", filename, validate);\n    free(data);\n    return NULL;\n  }\n\n  rad_stream = (struct rad_stream *)calloc(1, sizeof(struct rad_stream));\n  if(!rad_stream)\n  {\n    free(data);\n    return NULL;\n  }\n\n  cls = new rad_stream_cls();\n\n  cls->player.Init(data, rad_player_callback, cls);\n  rate = cls->player.GetHertz();\n\n  rad_stream->cls = cls;\n  rad_stream->data_length = length;\n  rad_stream->data = data;\n  rad_stream->sample_update_timer = 0;\n  rad_stream->sample_update_max = OPL_FREQUENCY / rate;\n\n  memset(&a_spec, 0, sizeof(struct audio_stream_spec));\n  a_spec.mix_data     = rad_mix_data;\n  a_spec.set_volume   = rad_set_volume;\n  a_spec.set_repeat   = rad_set_repeat;\n  a_spec.set_order    = rad_set_order;\n  a_spec.set_position = rad_set_position;\n  a_spec.get_order    = rad_get_order;\n  a_spec.get_position = rad_get_position;\n  a_spec.get_length   = rad_get_length;\n  a_spec.destruct     = rad_destruct;\n\n  memset(&s_spec, 0, sizeof(struct sampled_stream_spec));\n  s_spec.set_frequency = rad_set_frequency;\n  s_spec.get_frequency = rad_get_frequency;\n\n  initialize_sampled_stream((struct sampled_stream *)rad_stream, &s_spec,\n   OPL_FREQUENCY, frequency, 2, true);\n\n  initialize_audio_stream((struct audio_stream *)rad_stream, &a_spec,\n   volume, repeat);\n\n  vfclose(vf);\n  return (struct audio_stream *)rad_stream;\n}\n\nstatic boolean test_rad_stream(vfile *vf, const char *filename)\n{\n  char tmp[16];\n  if(!vfread(tmp, 16, 1, vf))\n    return false;\n\n  return memcmp(tmp, \"RAD by REALiTY!!\", 16) == 0;\n}\n\nvoid init_reality(struct config_info *conf)\n{\n  audio_ext_register(test_rad_stream, construct_rad_stream);\n}\n"
  },
  {
    "path": "src/audio/audio_reality.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __AUDIO_REALITY_H\n#define __AUDIO_REALITY_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\nvoid init_reality(struct config_info *conf);\n\n__M_END_DECLS\n\n#endif\n"
  },
  {
    "path": "src/audio/audio_sdl.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"audio.h\"\n#include \"audio_struct.h\"\n\n#include \"../SDLmzx.h\"\n#include \"../util.h\"\n\n#include <stdlib.h>\n\n#ifdef __EMSCRIPTEN__\n#include <emscripten/emscripten.h>\n#endif\n\nstatic SDL_AudioSpec audio_settings;\n#if SDL_VERSION_ATLEAST(2,0,0)\nstatic SDL_AudioDeviceID audio_device;\n#endif\n\nstatic void sdl_audio_callback(void *userdata, Uint8 *stream, int len)\n{\n  // TODO: 8-bit?\n  size_t frames;\n  switch(audio_settings.channels)\n  {\n    case 0:\n    case 1:\n      frames = (unsigned)len / sizeof(int16_t);\n      break;\n    case 2:\n      frames = (unsigned)len / (2u * sizeof(int16_t));\n      break;\n    default:\n      frames = (unsigned)len / (audio_settings.channels * sizeof(int16_t));\n      break;\n  }\n  audio_mixer_render_frames(stream, frames, audio_settings.channels, SAMPLE_S16);\n}\n\nvoid init_audio_platform(struct config_info *conf)\n{\n  SDL_AudioSpec desired_spec =\n  {\n    conf->audio_sample_rate,\n    AUDIO_S16SYS,\n    conf->audio_output_channels ? conf->audio_output_channels : 2,\n    0,\n    conf->audio_buffer_samples,\n    0,\n    0,\n    sdl_audio_callback,\n    NULL\n  };\n\n  // Reject very low sample rates to avoid PulseAudio hangs.\n  if(conf->audio_sample_rate > 0 && conf->audio_sample_rate < 2048)\n    desired_spec.freq = 2048;\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n  audio_device = SDL_OpenAudioDevice(NULL, 0, &desired_spec, &audio_settings, 0);\n  if(!audio_device)\n  {\n    warn(\"failed to init SDL audio device: %s\\n\", SDL_GetError());\n    return;\n  }\n#else\n  SDL_OpenAudio(&desired_spec, &audio_settings);\n#endif\n\n  if(!audio_mixer_init(audio_settings.freq, audio_settings.samples,\n   audio_settings.channels))\n    return;\n  // If the mixer doesn't like SDL's selected config, exit to avoid bugs...\n  if(audio.output_frequency != (size_t)audio_settings.freq ||\n   audio.buffer_channels != audio_settings.channels)\n    return;\n\n  // now set the audio going\n#if SDL_VERSION_ATLEAST(2,0,0)\n  SDL_PauseAudioDevice(audio_device, 0);\n#else\n  SDL_PauseAudio(0);\n#endif\n\n#ifdef __EMSCRIPTEN__\n  // Safari doesn't unlock the audio context unless a sound is played\n  // immediately after an input event has been fired. This takes an audio\n  // context that we set up earlier in JavaScript and sets up SDL2 to use that\n  // instead of the one that it creates (since SDL's context will never be\n  // unlocked).\n  //\n  // This swap should only happen if it's required by the web browser.\n  EM_ASM({ window.replaceSdlAudioContext(Module[\"SDL2\"]); });\n#endif\n}\n\nvoid quit_audio_platform(void)\n{\n#if SDL_VERSION_ATLEAST(2,0,0)\n  if(audio_device)\n  {\n    SDL_PauseAudioDevice(audio_device, 1);\n    SDL_CloseAudioDevice(audio_device);\n    audio_device = 0;\n  }\n#else\n  SDL_PauseAudio(1);\n#endif\n}\n"
  },
  {
    "path": "src/audio/audio_struct.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __AUDIO_STRUCT_H\n#define __AUDIO_STRUCT_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stdint.h>\n\n#include \"../configure.h\"\n#include \"../platform.h\"\n\n#ifdef CONFIG_DJGPP\n#define AUDIO_GARBAGE_COLLECTOR\n#endif\n\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n#define SAMPLE_S16 SAMPLE_S16BE\n#else\n#define SAMPLE_S16 SAMPLE_S16LE\n#endif\n\nenum wav_format\n{\n  SAMPLE_U8,\n  SAMPLE_S8,\n  SAMPLE_S16LE,\n  SAMPLE_S16BE\n};\n\nstruct wav_info\n{\n  /* Note: WAV fields are limited to 32-bits by the file format. */\n  uint8_t *wav_data;\n  uint32_t data_length;\n  uint32_t channels;\n  uint32_t freq;\n  uint32_t loop_start;\n  uint32_t loop_end;\n  enum wav_format format;\n  boolean enable_sam_frequency_hack;\n};\n\nstruct audio_stream\n{\n  struct audio_stream *next;\n  struct audio_stream *previous;\n  unsigned int volume;\n  boolean is_spot_sample;\n  boolean repeat;\n  boolean   (* mix_data)(struct audio_stream *a_src, int32_t * RESTRICT buffer,\n                         size_t dest_frames, unsigned int dest_channels);\n  void      (* set_volume)(struct audio_stream *a_src, unsigned int volume);\n  void      (* set_repeat)(struct audio_stream *a_src, boolean repeat);\n  void      (* set_order)(struct audio_stream *a_src, uint32_t order);\n  void      (* set_position)(struct audio_stream *a_src, uint32_t pos);\n  void      (* set_loop_start)(struct audio_stream *a_src, uint32_t pos);\n  void      (* set_loop_end)(struct audio_stream *a_src, uint32_t pos);\n  uint32_t  (* get_order)(struct audio_stream *a_src);\n  uint32_t  (* get_position)(struct audio_stream *a_src);\n  uint32_t  (* get_length)(struct audio_stream *a_src);\n  uint32_t  (* get_loop_start)(struct audio_stream *a_src);\n  uint32_t  (* get_loop_end)(struct audio_stream *a_src);\n  boolean   (* get_sample)(struct audio_stream *a_src, unsigned int which,\n             struct wav_info *dest);\n  void      (* destruct)(struct audio_stream *a_src);\n};\n\nstruct audio_stream_spec\n{\n  boolean   (* mix_data)(struct audio_stream *a_src, int32_t * RESTRICT buffer,\n                         size_t dest_frames, unsigned int dest_channels);\n  void      (* set_volume)(struct audio_stream *a_src, unsigned int volume);\n  void      (* set_repeat)(struct audio_stream *a_src, boolean repeat);\n  void      (* set_order)(struct audio_stream *a_src, uint32_t order);\n  void      (* set_position)(struct audio_stream *a_src, uint32_t pos);\n  void      (* set_loop_start)(struct audio_stream *a_src, uint32_t pos);\n  void      (* set_loop_end)(struct audio_stream *a_src, uint32_t pos);\n  uint32_t  (* get_order)(struct audio_stream *a_src);\n  uint32_t  (* get_position)(struct audio_stream *a_src);\n  uint32_t  (* get_length)(struct audio_stream *a_src);\n  uint32_t  (* get_loop_start)(struct audio_stream *a_src);\n  uint32_t  (* get_loop_end)(struct audio_stream *a_src);\n  boolean   (* get_sample)(struct audio_stream *a_src, unsigned int which,\n             struct wav_info *dest);\n  void      (* destruct)(struct audio_stream *a_src);\n};\n\nstruct audio\n{\n  int32_t *mix_buffer;\n  size_t buffer_bytes;\n  unsigned buffer_frames;\n  unsigned buffer_channels;\n\n  size_t output_frequency;\n  unsigned int global_resample_mode;\n  int max_simultaneous_samples;\n  int max_simultaneous_samples_config;\n\n  struct audio_stream *primary_stream;\n  struct audio_stream *pcs_stream;\n  struct audio_stream *stream_list_base;\n  struct audio_stream *stream_list_end;\n#ifdef AUDIO_GARBAGE_COLLECTOR\n  struct audio_stream *garbage_list_base;\n  struct audio_stream *garbage_list_end;\n#endif\n\n  platform_mutex audio_mutex;\n  platform_mutex audio_sfx_mutex;\n#ifdef DEBUG\n  platform_mutex audio_debug_mutex;\n#endif\n\n  boolean music_on;\n  boolean pcs_on;\n  unsigned int music_volume;\n  unsigned int sound_volume;\n  unsigned int pcs_volume;\n};\n\nextern struct audio audio;\n\n__M_END_DECLS\n\n#endif /* __AUDIO_STRUCT_H */\n"
  },
  {
    "path": "src/audio/audio_vorbis.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2004 madbrain\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2018, 2024-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"audio.h\"\n#include \"audio_struct.h\"\n#include \"audio_vorbis.h\"\n#include \"ext.h\"\n#include \"sampled_stream.h\"\n\n#include \"../io/memfile.h\"\n#include \"../io/vio.h\"\n\n// Safety check: DJGPP absolutely can not use vorbis/tremor here because they\n// are not interrupt-safe. stb_vorbis can be used under the condition that\n// alloca is patched out (probably not interrupt-safe) and stdio is disabled\n// (not interrupt-safe). All allocations in the patched stb_vorbis are\n// performed during setup. Additionally, a patch is required to add sample-\n// accurate position tracking.\n//\n// alloca removal patch (not yet contributed upstream):\n// https://github.com/sezero/stb/commit/7dde771bc8181796d516b67b5e5391594674b041\n\n#if defined(CONFIG_DJGPP) && !defined(CONFIG_STB_VORBIS)\n#error Vorbis/Tremor are not interrupt-safe, please fix your config!\n#endif\n\n#if defined(CONFIG_STB_VORBIS)\n#define STB_VORBIS_NO_PUSHDATA_API\n#define STB_VORBIS_NO_STDIO\n#include <stb/stb_vorbis.c>\n#elif defined(CONFIG_TREMOR)\n#include <tremor/ivorbiscodec.h>\n#include <tremor/ivorbisfile.h>\n#else\n#include <vorbis/codec.h>\n#include <vorbis/vorbisfile.h>\n#endif\n\ntypedef struct\n{\n  size_t stream_length;\n  int rate;\n  int channels;\n} audio_vorbis_info;\n\n#ifdef CONFIG_STB_VORBIS\ntypedef stb_vorbis *audio_vorbis_handle;\n\nstatic boolean audio_vorbis_handle_init(audio_vorbis_handle *f, vfile *vf)\n{\n  struct memfile tmp;\n  int64_t len;\n  int err;\n\n  // stb_vorbis is mainly for the DOS port and file IO isn't\n  // interrupt safe, so force the entire file to RAM.\n  if(!vfile_force_to_memory(vf))\n    return false;\n\n  len = vfilelength(vf, true);\n  if(len < 0)\n    return false;\n\n  // TODO: stb_vorbis should really accept callbacks instead of this hack.\n  // This may potentially be disastrous with builds that use VFS caching.\n  if(!vfile_get_memfile_block(vf, len, &tmp))\n    return false;\n\n  *f = stb_vorbis_open_memory(tmp.start, len, &err, NULL);\n  return *f != NULL;\n}\n\nstatic void audio_vorbis_handle_close(audio_vorbis_handle *f)\n{\n  stb_vorbis_close(*f);\n  *f = NULL;\n}\n\nstatic boolean audio_vorbis_handle_info(audio_vorbis_info *dest,\n audio_vorbis_handle *f)\n{\n  stb_vorbis_info info = stb_vorbis_get_info(*f);\n  dest->stream_length = stb_vorbis_stream_length_in_samples(*f);\n  dest->channels = info.channels;\n  dest->rate = info.sample_rate;\n  return true;\n}\n\nstatic char **audio_vorbis_handle_comments(int *num, audio_vorbis_handle *f)\n{\n  stb_vorbis_comment comment = stb_vorbis_get_comment(*f);\n  *num = comment.comment_list_length;\n  return comment.comment_list;\n}\n\nstatic int audio_vorbis_handle_rewind(audio_vorbis_handle *f)\n{\n  return stb_vorbis_seek_start(*f);\n}\n\nstatic int audio_vorbis_handle_seek(audio_vorbis_handle *f, int64_t position)\n{\n  return stb_vorbis_seek(*f, (uint32_t)position);\n}\n\nstatic int64_t audio_vorbis_handle_tell(audio_vorbis_handle *f)\n{\n  // Requires https://github.com/nothings/stb/pull/1295\n  return stb_vorbis_get_playback_sample_offset(*f);\n}\n\nstatic size_t audio_vorbis_handle_read(void * RESTRICT dest,\n size_t bytes_to_read, unsigned channels, audio_vorbis_handle *f)\n{\n  return stb_vorbis_get_samples_short_interleaved(*f, channels, dest,\n   bytes_to_read >> 1) * channels * sizeof(int16_t);\n}\n\n#else /* libvorbis/tremor */\n\n// ov_read() documentation states the 'bigendianp' argument..\n//   \"Specifies big or little endian byte packing.\n//   0 for little endian, 1 for big endian. Typical value is 0.\"\n\n// Tremor (as of r19494) doesn't use this, so suppress the warning...\n#ifndef CONFIG_TREMOR\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\nstatic const int ENDIAN_PACKING = 1;\n#else\nstatic const int ENDIAN_PACKING = 0;\n#endif\n#endif // !CONFIG_TREMOR\n\ntypedef OggVorbis_File audio_vorbis_handle;\n\nstatic size_t vorbis_read_fn(void *ptr, size_t size, size_t count, void *stream)\n{\n  vfile *vf = (vfile *)stream;\n  return vfread(ptr, size, count, vf);\n}\n\nstatic int vorbis_seek_fn(void *stream, ogg_int64_t offset, int whence)\n{\n  vfile *vf = (vfile *)stream;\n  return vfseek(vf, offset, whence);\n}\n\nstatic int vorbis_close_fn(void *stream)\n{\n  // Do nothing; tremor lowmem will CRASH if this function isn't provided.\n  return 0;\n}\n\nstatic long vorbis_tell_fn(void *stream)\n{\n  vfile *vf = (vfile *)stream;\n  return vftell(vf);\n}\n\nstatic const ov_callbacks vorbis_callbacks =\n{\n  vorbis_read_fn,\n  vorbis_seek_fn,\n  vorbis_close_fn,\n  vorbis_tell_fn,\n};\n\nstatic boolean audio_vorbis_handle_init(audio_vorbis_handle *f, vfile *vf)\n{\n  if(ov_open_callbacks(vf, f, NULL, 0, vorbis_callbacks) != 0)\n    return false;\n\n  return true;\n}\n\nstatic void audio_vorbis_handle_close(audio_vorbis_handle *f)\n{\n  ov_clear(f);\n}\n\nstatic boolean audio_vorbis_handle_info(audio_vorbis_info *dest,\n audio_vorbis_handle *f)\n{\n  vorbis_info *vorbis_file_info = ov_info(f, -1);\n  if(!vorbis_file_info)\n    return false;\n\n  dest->stream_length = ov_pcm_total(f, -1);\n  dest->channels = vorbis_file_info->channels;\n  dest->rate = vorbis_file_info->rate;\n  return true;\n}\n\nstatic char **audio_vorbis_handle_comments(int *num, audio_vorbis_handle *f)\n{\n  vorbis_comment *comment = ov_comment(f, -1);\n  if(!comment)\n    return NULL;\n\n  *num = comment->comments;\n  return comment->user_comments;\n}\n\nstatic int audio_vorbis_handle_rewind(audio_vorbis_handle *f)\n{\n  return ov_raw_seek(f, 0);\n}\n\nstatic int audio_vorbis_handle_seek(audio_vorbis_handle *f, int64_t pos)\n{\n  return ov_pcm_seek(f, (ogg_int64_t)pos);\n}\n\nstatic int64_t audio_vorbis_handle_tell(audio_vorbis_handle *f)\n{\n  return ov_pcm_tell(f);\n}\n\nstatic size_t audio_vorbis_handle_read(void * RESTRICT dest,\n size_t bytes_to_read, unsigned channels, audio_vorbis_handle *f)\n{\n  int current_bitstream;\n#ifdef CONFIG_TREMOR\n  return ov_read(f, dest, bytes_to_read, &current_bitstream);\n#else\n  return ov_read(f, dest, bytes_to_read,\n   ENDIAN_PACKING, sizeof(int16_t), 1, &current_bitstream);\n#endif\n}\n#endif /* libvorbis/tremor */\n\nstruct vorbis_stream\n{\n  struct sampled_stream s;\n  audio_vorbis_handle handle;\n  audio_vorbis_info info;\n  vfile *input_file;\n  uint32_t loop_start;\n  uint32_t loop_end;\n};\n\nstatic boolean vorbis_mix_data(struct audio_stream *a_src,\n int32_t * RESTRICT buffer, size_t frames, unsigned int channels)\n{\n  uint32_t read_len = 0;\n  struct vorbis_stream *v_stream = (struct vorbis_stream *)a_src;\n  char *read_buffer;\n  size_t read_wanted;\n  size_t read_channels = v_stream->s.channels;\n  uint32_t pos = 0;\n\n  read_buffer = (char *)sampled_get_buffer(&v_stream->s, &read_wanted);\n\n  do\n  {\n    read_wanted -= read_len;\n\n    if(a_src->repeat && v_stream->loop_end)\n      pos = (uint32_t)audio_vorbis_handle_tell(&v_stream->handle);\n\n    read_len = audio_vorbis_handle_read(read_buffer, read_wanted,\n     read_channels, &(v_stream->handle));\n\n    if(a_src->repeat && (pos < v_stream->loop_end) &&\n     (pos + read_len / read_channels / sizeof(int16_t) >= v_stream->loop_end))\n    {\n      read_len = (v_stream->loop_end - pos) * read_channels * sizeof(int16_t);\n      audio_vorbis_handle_seek(&(v_stream->handle), v_stream->loop_start);\n    }\n\n    // If it hit the end go back to the beginning if repeat is on\n\n    if(read_len == 0)\n    {\n      if(a_src->repeat)\n      {\n        audio_vorbis_handle_rewind(&(v_stream->handle));\n\n        if(read_wanted)\n        {\n          read_len = audio_vorbis_handle_read(read_buffer, read_wanted,\n           read_channels, &(v_stream->handle));\n        }\n      }\n      else\n      {\n        memset(read_buffer, 0, read_wanted);\n      }\n    }\n\n    read_buffer += read_len;\n  } while((read_len < read_wanted) && read_len);\n\n  sampled_mix_data((struct sampled_stream *)v_stream, buffer, frames, channels);\n\n  if(read_len == 0)\n    return true;\n\n  return false;\n}\n\nstatic void vorbis_set_volume(struct audio_stream *a_src, unsigned int volume)\n{\n  a_src->volume = volume * 256 / 255;\n}\n\nstatic void vorbis_set_repeat(struct audio_stream *a_src, boolean repeat)\n{\n  a_src->repeat = repeat;\n}\n\nstatic void vorbis_set_position(struct audio_stream *a_src, uint32_t position)\n{\n  struct vorbis_stream *v = (struct vorbis_stream *)a_src;\n  audio_vorbis_handle_seek(&v->handle, (int64_t)position);\n}\n\nstatic void vorbis_set_loop_start(struct audio_stream *a_src, uint32_t position)\n{\n  ((struct vorbis_stream *)a_src)->loop_start = position;\n}\n\nstatic void vorbis_set_loop_end(struct audio_stream *a_src, uint32_t position)\n{\n  ((struct vorbis_stream *)a_src)->loop_end = position;\n}\n\nstatic void vorbis_set_frequency(struct sampled_stream *s_src, uint32_t frequency)\n{\n  struct vorbis_stream *v_stream = (struct vorbis_stream *)s_src;\n\n  sampled_set_buffer(s_src, frequency, v_stream->info.rate);\n}\n\nstatic uint32_t vorbis_get_position(struct audio_stream *a_src)\n{\n  struct vorbis_stream *v = (struct vorbis_stream *)a_src;\n  return (uint32_t)audio_vorbis_handle_tell(&v->handle);\n}\n\nstatic uint32_t vorbis_get_length(struct audio_stream *a_src)\n{\n  return ((struct vorbis_stream *)a_src)->info.stream_length;\n}\n\nstatic uint32_t vorbis_get_loop_start(struct audio_stream *a_src)\n{\n  return ((struct vorbis_stream *)a_src)->loop_start;\n}\n\nstatic uint32_t vorbis_get_loop_end(struct audio_stream *a_src)\n{\n  return ((struct vorbis_stream *)a_src)->loop_end;\n}\n\nstatic uint32_t vorbis_get_frequency(struct sampled_stream *s_src)\n{\n  return s_src->relative_frequency;\n}\n\nstatic void vorbis_destruct(struct audio_stream *a_src)\n{\n  struct vorbis_stream *v_stream = (struct vorbis_stream *)a_src;\n  audio_vorbis_handle_close(&(v_stream->handle));\n  vfclose(v_stream->input_file);\n  sampled_destruct(a_src);\n}\n\nstatic void get_loopstart_loopend(struct vorbis_stream *v_stream)\n{\n  char **comments;\n  int loopstart = -1;\n  int looplength = -1;\n  int loopend = -1;\n  int num;\n  int i;\n\n  comments = audio_vorbis_handle_comments(&num, &(v_stream->handle));\n  if(!comments)\n    return;\n\n  for(i = 0; i < num; i++)\n  {\n    if(!strncasecmp(\"loopstart=\", comments[i], 10))\n      loopstart = atoi(comments[i] + 10);\n    else\n\n    if(!strncasecmp(\"loopend=\", comments[i], 8))\n      loopend = atoi(comments[i] + 8);\n    else\n\n    if(!strncasecmp(\"looplength=\", comments[i], 11))\n      looplength = atoi(comments[i] + 11);\n  }\n\n  if(loopstart >= 0 && (looplength > 0 || loopend > loopstart))\n  {\n    v_stream->loop_start = loopstart;\n\n    // looplength takes priority since it's older and more \"standard\"\n    if(looplength > 0)\n      v_stream->loop_end = loopstart + looplength;\n    else\n      v_stream->loop_end = loopend;\n  }\n}\n\nstatic struct audio_stream *construct_vorbis_stream(vfile *vf,\n const char *filename, uint32_t frequency, unsigned int volume, boolean repeat)\n{\n  struct vorbis_stream *v_stream;\n  struct sampled_stream_spec s_spec;\n  struct audio_stream_spec a_spec;\n\n  audio_vorbis_handle handle;\n  audio_vorbis_info info;\n\n  if(!audio_vorbis_handle_init(&handle, vf))\n    return NULL;\n\n  // Surround OGGs not supported yet..\n  if(!audio_vorbis_handle_info(&info, &handle) || info.channels > 2)\n  {\n    audio_vorbis_handle_close(&handle);\n    return NULL;\n  }\n\n  v_stream = (struct vorbis_stream *)cmalloc(sizeof(struct vorbis_stream));\n  if(!v_stream)\n  {\n    audio_vorbis_handle_close(&handle);\n    return NULL;\n  }\n\n  v_stream->handle = handle;\n  v_stream->info = info;\n  v_stream->input_file = vf;\n\n  v_stream->loop_start = 0;\n  v_stream->loop_end = 0;\n  get_loopstart_loopend(v_stream);\n\n  memset(&a_spec, 0, sizeof(struct audio_stream_spec));\n  a_spec.mix_data       = vorbis_mix_data;\n  a_spec.set_volume     = vorbis_set_volume;\n  a_spec.set_repeat     = vorbis_set_repeat;\n  a_spec.set_position   = vorbis_set_position;\n  a_spec.set_loop_start = vorbis_set_loop_start;\n  a_spec.set_loop_end   = vorbis_set_loop_end;\n  a_spec.get_position   = vorbis_get_position;\n  a_spec.get_length     = vorbis_get_length;\n  a_spec.get_loop_start = vorbis_get_loop_start;\n  a_spec.get_loop_end   = vorbis_get_loop_end;\n  a_spec.destruct       = vorbis_destruct;\n\n  memset(&s_spec, 0, sizeof(struct sampled_stream_spec));\n  s_spec.set_frequency = vorbis_set_frequency;\n  s_spec.get_frequency = vorbis_get_frequency;\n\n  initialize_sampled_stream((struct sampled_stream *)v_stream, &s_spec,\n   info.rate, frequency, info.channels, true);\n\n  initialize_audio_stream((struct audio_stream *)v_stream, &a_spec,\n   volume, repeat);\n\n  return (struct audio_stream *)v_stream;\n}\n\nstatic boolean test_vorbis_stream(vfile *vf, const char *filename)\n{\n  char buf[4];\n  if(!vfread(buf, 4, 1, vf))\n    return false;\n\n  return memcmp(buf, \"OggS\", 4) == 0;\n}\n\nvoid init_vorbis(struct config_info *conf)\n{\n  audio_ext_register(test_vorbis_stream, construct_vorbis_stream);\n}\n"
  },
  {
    "path": "src/audio/audio_vorbis.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __AUDIO_VORBIS_H\n#define __AUDIO_VORBIS_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\nvoid init_vorbis(struct config_info *conf);\n\n__M_END_DECLS\n\n#endif /* __AUDIO_VORBIS_H */\n"
  },
  {
    "path": "src/audio/audio_wav.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2004 madbrain\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2018-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"audio.h\"\n#include \"audio_struct.h\"\n#include \"audio_wav.h\"\n#include \"ext.h\"\n#include \"sampled_stream.h\"\n\n#include \"../SDLmzx.h\" // SDL WAV loader fallback\n#include \"../util.h\"\n#include \"../io/path.h\"\n#include \"../io/vio.h\"\n\n// If the WAV/SAM is larger than this, print a warning to the console.\n// (Right now only do this for debug builds because a lot more games than\n// anticipated use big WAVs and it could get annoying for end users.)\n#define WARN_FILESIZE (1<<22)\n\nstruct wav_stream\n{\n  struct sampled_stream s;\n  uint8_t *wav_data;\n  uint32_t data_offset;\n  uint32_t data_length;\n  uint32_t channels;\n  uint32_t bytes_per_sample;\n  uint32_t natural_frequency;\n  uint32_t loop_start;\n  uint32_t loop_end;\n  enum wav_format format;\n};\n\nstatic uint32_t wav_read_data(struct wav_stream *w_stream,\n uint8_t * RESTRICT buffer, uint32_t len, boolean repeat)\n{\n  const uint8_t *src = (uint8_t *)w_stream->wav_data + w_stream->data_offset;\n  uint32_t data_read = 0;\n  uint32_t read_len = len;\n  uint32_t new_offset = w_stream->data_offset;\n  uint32_t i;\n\n  switch(w_stream->format)\n  {\n    case SAMPLE_S8:\n    case SAMPLE_U8:\n    {\n      int16_t *dest = (int16_t *)buffer;\n\n      read_len /= 2;\n\n      new_offset = w_stream->data_offset + read_len;\n\n      if(w_stream->data_offset + read_len >= w_stream->data_length)\n      {\n        read_len = w_stream->data_length - w_stream->data_offset;\n        if(repeat)\n          new_offset = 0;\n        else\n          new_offset = w_stream->data_length;\n      }\n\n      if(repeat && (w_stream->data_offset < w_stream->loop_end) &&\n       (w_stream->data_offset + read_len >= w_stream->loop_end))\n      {\n        read_len = w_stream->loop_end - w_stream->data_offset;\n        new_offset = w_stream->loop_start;\n      }\n\n      data_read = read_len * 2;\n\n      if(w_stream->format == SAMPLE_U8)\n      {\n        for(i = 0; i < read_len; i++)\n          dest[i] = (int16_t)((src[i] - 128) << 8);\n      }\n      else\n      {\n        for(i = 0; i < read_len; i++)\n          dest[i] = (int16_t)(src[i] << 8);\n      }\n\n      break;\n    }\n\n    case SAMPLE_S16LE:\n    case SAMPLE_S16BE:\n    {\n      uint8_t *dest = (uint8_t *)buffer;\n\n      new_offset = w_stream->data_offset + read_len;\n\n      if(w_stream->data_offset + read_len >= w_stream->data_length)\n      {\n        read_len = w_stream->data_length - w_stream->data_offset;\n        if(repeat)\n          new_offset = 0;\n        else\n          new_offset = w_stream->data_length;\n      }\n\n      if(repeat && (w_stream->data_offset < w_stream->loop_end)\n       && (w_stream->data_offset + read_len >= w_stream->loop_end))\n      {\n        read_len = w_stream->loop_end - w_stream->data_offset;\n        new_offset = w_stream->loop_start;\n      }\n\n      if(w_stream->format != SAMPLE_S16)\n      {\n        // Swap bytes to match the current platform endianness...\n        for(i = 0; i < read_len; i += 2)\n        {\n          dest[i] = src[i + 1];\n          dest[i + 1] = src[i];\n        }\n      }\n      else\n        memcpy(dest, src, read_len);\n\n      data_read = read_len;\n      break;\n    }\n  }\n\n  w_stream->data_offset = new_offset;\n\n  return data_read;\n}\n\nstatic boolean wav_mix_data(struct audio_stream *a_src, int32_t * RESTRICT buffer,\n size_t frames, unsigned int channels)\n{\n  uint32_t read_len = 0;\n  struct wav_stream *w_stream = (struct wav_stream *)a_src;\n  uint8_t *read_buffer;\n  size_t read_wanted;\n\n  read_buffer = (uint8_t *)sampled_get_buffer(&w_stream->s, &read_wanted);\n\n  read_len = wav_read_data(w_stream, read_buffer, read_wanted, a_src->repeat);\n\n  if(read_len < read_wanted)\n  {\n    read_buffer += read_len;\n    read_wanted -= read_len;\n\n    if(a_src->repeat)\n    {\n      read_len = wav_read_data(w_stream, read_buffer, read_wanted, true);\n    }\n    else\n    {\n      memset(read_buffer, 0, read_wanted);\n      read_len = 0;\n    }\n  }\n\n  sampled_mix_data((struct sampled_stream *)w_stream, buffer, frames, channels);\n\n  if(read_len == 0)\n    return true;\n\n  return false;\n}\n\nstatic void wav_set_volume(struct audio_stream *a_src, unsigned int volume)\n{\n  a_src->volume = volume * 256 / 255;\n}\n\nstatic void wav_set_repeat(struct audio_stream *a_src, boolean repeat)\n{\n  a_src->repeat = repeat;\n}\n\nstatic void wav_set_position(struct audio_stream *a_src, uint32_t position)\n{\n  struct wav_stream *w_stream = (struct wav_stream *)a_src;\n\n  if(position < (w_stream->data_length / w_stream->bytes_per_sample))\n    w_stream->data_offset = position * w_stream->bytes_per_sample;\n}\n\nstatic void wav_set_loop_start(struct audio_stream *a_src, uint32_t position)\n{\n  ((struct wav_stream *)a_src)->loop_start = position;\n}\n\nstatic void wav_set_loop_end(struct audio_stream *a_src, uint32_t position)\n{\n  ((struct wav_stream *)a_src)->loop_end = position;\n}\n\nstatic void wav_set_frequency(struct sampled_stream *s_src, uint32_t frequency)\n{\n  struct wav_stream *w_stream = (struct wav_stream *)s_src;\n\n  sampled_set_buffer(s_src, frequency, w_stream->natural_frequency);\n}\n\nstatic uint32_t wav_get_position(struct audio_stream *a_src)\n{\n  struct wav_stream *w_stream = (struct wav_stream *)a_src;\n\n  return w_stream->data_offset / w_stream->bytes_per_sample;\n}\n\nstatic uint32_t wav_get_length(struct audio_stream *a_src)\n{\n  struct wav_stream *w_stream = (struct wav_stream *)a_src;\n\n  return w_stream->data_length / w_stream->bytes_per_sample;\n}\n\nstatic uint32_t wav_get_loop_start(struct audio_stream *a_src)\n{\n  return ((struct wav_stream *)a_src)->loop_start;\n}\n\nstatic uint32_t wav_get_loop_end(struct audio_stream *a_src)\n{\n  return ((struct wav_stream *)a_src)->loop_end;\n}\n\nstatic uint32_t wav_get_frequency(struct sampled_stream *s_src)\n{\n  return s_src->relative_frequency;\n}\n\nstatic void wav_destruct(struct audio_stream *a_src)\n{\n  struct wav_stream *w_stream = (struct wav_stream *)a_src;\n  free(w_stream->wav_data);\n  sampled_destruct(a_src);\n}\n\nstatic uint32_t read_little_endian32(char *buf)\n{\n  unsigned char *b = (unsigned char *)buf;\n  uint32_t s = 0;\n  int i;\n  for(i = 3; i >= 0; i--)\n    s = (s << 8) | b[i];\n  return s;\n}\n\nstatic int read_little_endian16(char *buf)\n{\n  unsigned char *b = (unsigned char *)buf;\n  return (b[1] << 8) | b[0];\n}\n\nstatic void *get_riff_chunk(vfile *vf, size_t filesize, char *id, uint32_t *size)\n{\n  long pos = vftell(vf);\n  size_t maxsize = filesize - pos - 8;\n  char size_buf[4];\n  void *buf;\n\n  if(pos < 0 || (size_t)(pos + 8) > filesize)\n    return NULL;\n\n  if(id)\n  {\n    if(vfread(id, 1, 4, vf) < 4)\n      return NULL;\n  }\n  else\n  {\n    vfseek(vf, 4, SEEK_CUR);\n  }\n\n  if(vfread(size_buf, 1, 4, vf) < 4)\n    return NULL;\n\n  *size = read_little_endian32(size_buf);\n  if(*size > maxsize)\n    *size = maxsize;\n\n  buf = malloc(*size);\n  if(!buf)\n    return NULL;\n\n  if(vfread(buf, 1, *size, vf) < *size)\n  {\n    free(buf);\n    return NULL;\n  }\n\n  // Realign if odd size unless padding byte isn't 0\n  if(*size & 1)\n  {\n    int c = vfgetc(vf);\n    if((c != 0) && (c != EOF))\n      vungetc(c, vf);\n  }\n\n  return buf;\n}\n\nstatic boolean get_next_riff_chunk_id(vfile *vf, size_t filesize, char *id)\n{\n  long pos = vftell(vf);\n\n  if(pos < 0 || (size_t)(pos + 8) > filesize)\n    return false;\n\n  if(vfread(id, 1, 4, vf) < 4)\n    return false;\n\n  vfseek(vf, -4, SEEK_CUR);\n  return true;\n}\n\nstatic void skip_riff_chunk(vfile *vf, size_t filesize)\n{\n  long pos = vftell(vf);\n  size_t maxsize = filesize - pos - 8;\n  char size_buf[4];\n  uint32_t s;\n\n  if(pos > 0 && (size_t)(pos + 8) <= filesize)\n  {\n    vfseek(vf, 4, SEEK_CUR);\n    if(vfread(size_buf, 1, 4, vf) < 4)\n      return;\n\n    s = read_little_endian32(size_buf);\n    if(s > maxsize)\n      s = maxsize;\n    vfseek(vf, s, SEEK_CUR);\n\n    // Realign if odd size unless padding byte isn't 0\n    if(s & 1)\n    {\n      int c = vfgetc(vf);\n      if((c != 0) && (c != EOF))\n        vungetc(c, vf);\n    }\n  }\n}\n\nstatic void *get_riff_chunk_by_id(vfile *vf, size_t filesize,\n const char *id, uint32_t *size)\n{\n  boolean i;\n  char id_buf[4];\n\n  vfseek(vf, 12, SEEK_SET);\n\n  while((i = get_next_riff_chunk_id(vf, filesize, id_buf)))\n  {\n    if(!memcmp(id_buf, id, 4))\n      break;\n\n    skip_riff_chunk(vf, filesize);\n  }\n\n  if(!i)\n    return NULL;\n\n  return get_riff_chunk(vf, filesize, NULL, size);\n}\n\n// Simple SAM loader.\n\nstatic boolean load_sam_file(vfile *vf, const char *filename, struct wav_info *spec)\n{\n  size_t source_length;\n  size_t read_length;\n  void *buf;\n\n  source_length = vfilelength(vf, false);\n  if(source_length > WARN_FILESIZE)\n  {\n    trace(\"Size of SAM file '%s' is %zu; OGG should be used instead.\\n\",\n     filename, source_length);\n  }\n\n  // Default to no loop\n  spec->channels = 1;\n  spec->freq = audio_get_real_frequency(SAM_DEFAULT_PERIOD);\n  spec->format = SAMPLE_S8;\n  spec->loop_start = 0;\n  spec->loop_end = 0;\n  spec->enable_sam_frequency_hack = true;\n\n  buf = malloc(source_length);\n  if(!buf)\n    return false;\n\n  read_length = vfread(buf, 1, source_length, vf);\n  if(read_length < source_length)\n  {\n    free(buf);\n    return false;\n  }\n  spec->data_length = source_length;\n  spec->wav_data = buf;\n  return true;\n}\n\n#ifdef CONFIG_SDL\n// SDL-based WAV loader, used as a fallback if the regular loader fails.\n// It supports more formats than the regular loader.\n\nstatic boolean load_wav_file_sdl(const char *filename, struct wav_info *spec)\n{\n  SDL_AudioSpec sdlspec;\n  void *copy_buf;\n\n  if(!SDL_LoadWAV(filename, &sdlspec, &(spec->wav_data), &(spec->data_length)))\n    return false;\n\n  copy_buf = malloc(spec->data_length);\n  if(!copy_buf)\n  {\n    SDL_FreeWAV(spec->wav_data);\n    return false;\n  }\n\n  memcpy(copy_buf, spec->wav_data, spec->data_length);\n  SDL_FreeWAV(spec->wav_data);\n\n  spec->wav_data = copy_buf;\n  spec->channels = sdlspec.channels;\n  spec->freq = sdlspec.freq;\n  switch(sdlspec.format)\n  {\n    case SDL_AUDIO_U8:\n      spec->format = SAMPLE_U8;\n      break;\n    case SDL_AUDIO_S8:\n      spec->format = SAMPLE_S8;\n      break;\n    case SDL_AUDIO_S16LE:\n      spec->format = SAMPLE_S16LE;\n      break;\n    // May be returned by SDL on big endian machines.\n    case SDL_AUDIO_S16BE:\n      spec->format = SAMPLE_S16BE;\n      break;\n    /**\n     * TODO: SDL 2.0 can technically return AUDIO_S32LSB or AUDIO_F32LSB.\n     * Support for these would be trivial to add but might encourage worse\n     * abuse of .WAV support (as those formats are twice the size of S16).\n     */\n    default:\n      warn(\"Unsupported WAV SDL_AudioFormat 0x%x! Report this!\\n\", sdlspec.format);\n      free(copy_buf);\n      return false;\n  }\n\n  return true;\n}\n#endif\n\n// More lenient than SDL's WAV loader, but only supports\n// uncompressed PCM files (for now.)\n\nstatic boolean load_wav_file(vfile *vf, const char *filename, struct wav_info *spec)\n{\n  int channels, srate, sbytes, numloops;\n  uint32_t riffsize, data_size, fmt_size, smpl_size;\n  size_t loop_start, loop_end;\n  char *fmt_chunk, *smpl_chunk, tmp_buf[4];\n  size_t file_size;\n\n  file_size = vfilelength(vf, false);\n  if(file_size > WARN_FILESIZE)\n  {\n    trace(\"This WAV is too big sempai OwO;;;\\n\");\n    trace(\"Size of WAV file '%s' is %zu; OGG should be used instead.\\n\",\n     filename, file_size);\n  }\n\n  // If it doesn't start with \"RIFF\", it's not a WAV file.\n  if(vfread(tmp_buf, 1, 4, vf) < 4)\n    return false;\n\n  if(memcmp(tmp_buf, \"RIFF\", 4))\n    return false;\n\n  // Read reported file size (if the file turns out to be larger, this will be\n  // used instead of the real file size.)\n  if(vfread(tmp_buf, 1, 4, vf) < 4)\n    return false;\n\n  riffsize = read_little_endian32(tmp_buf) + 8;\n\n  // If the RIFF type isn't \"WAVE\", it's not a WAV file.\n  if(vfread(tmp_buf, 1, 4, vf) < 4)\n    return false;\n\n  if(memcmp(tmp_buf, \"WAVE\", 4))\n    return false;\n\n  vrewind(vf);\n  if(file_size > riffsize)\n    file_size = riffsize;\n\n  fmt_chunk = get_riff_chunk_by_id(vf, file_size, \"fmt \", &fmt_size);\n\n  // If there's no \"fmt \" chunk, or it's less than 16 bytes, it's not a valid\n  // WAV file.\n  if(!fmt_chunk || (fmt_size < 16))\n  {\n    free(fmt_chunk);\n    return false;\n  }\n\n  // Default to no loop\n  spec->loop_start = 0;\n  spec->loop_end = 0;\n\n  // Not a SAM, so don't enable this hack.\n  spec->enable_sam_frequency_hack = false;\n\n  // If the WAV file isn't uncompressed PCM (format 1), let SDL handle it.\n  if(read_little_endian16(fmt_chunk) != 1)\n  {\n    free(fmt_chunk);\n\n#ifdef CONFIG_SDL\n    if(load_wav_file_sdl(filename, spec))\n      return true;\n#endif\n\n    return false;\n  }\n\n  // Get the data we need from the \"fmt \" chunk.\n  channels = read_little_endian16(fmt_chunk + 2);\n  srate = read_little_endian32(fmt_chunk + 4);\n\n  // Average bytes per second go here (4 bytes)\n  // Block align goes here (2 bytes)\n  // Round up when dividing by 8\n  sbytes = (read_little_endian16(fmt_chunk + 14) + 7) / 8;\n  free(fmt_chunk);\n\n  // If I remember correctly, we can't do anything past stereo, and 0 channels\n  // isn't valid.  We can't do anything past 16-bit either, and 0-bit isn't\n  // valid. 0Hz sample rate is invalid too.\n  if(!channels || (channels > 2) || !sbytes || (sbytes > 2) || !srate)\n    return false;\n\n  // Everything seems to check out, so let's load the \"data\" chunk.\n  spec->wav_data = get_riff_chunk_by_id(vf, file_size, \"data\", &data_size);\n  spec->data_length = data_size;\n\n  // No \"data\" chunk?! FAIL!\n  if(!spec->wav_data)\n    return false;\n\n  // Empty \"data\" chunk?! ALSO FAIL!\n  if(!data_size)\n  {\n    free(spec->wav_data);\n    return false;\n  }\n\n  spec->freq = srate;\n  if(sbytes == 1)\n    spec->format = SAMPLE_U8;\n  else\n    spec->format = SAMPLE_S16LE;\n  spec->channels = channels;\n\n  // Check for \"smpl\" chunk for looping info\n  smpl_chunk = get_riff_chunk_by_id(vf, file_size, \"smpl\", &smpl_size);\n\n  // If there's no \"smpl\" chunk or it's less than 60 bytes, there's no valid\n  // loop data\n  if(!smpl_chunk || (smpl_size < 60))\n  {\n    free(smpl_chunk);\n    return true;\n  }\n\n  numloops = read_little_endian32(smpl_chunk + 28);\n  // First loop is at 36\n  loop_start = read_little_endian32(smpl_chunk + 44) * channels * sbytes;\n  loop_end = read_little_endian32(smpl_chunk + 48) * channels * sbytes;\n  free(smpl_chunk);\n\n  // If the number of loops is less than 1, the loop data's invalid\n  if(numloops < 1)\n    return true;\n\n  // Boundary check loop points\n  if((loop_start >= spec->data_length) || (loop_end > spec->data_length)\n   || (loop_start >= loop_end))\n    return true;\n\n  spec->loop_start = loop_start;\n  spec->loop_end = loop_end;\n  return true;\n}\n\nstruct audio_stream *construct_wav_stream_direct(struct wav_info *w_info,\n uint32_t frequency, unsigned int volume, boolean repeat)\n{\n  struct wav_stream *w_stream;\n  struct sampled_stream_spec s_spec;\n  struct audio_stream_spec a_spec;\n\n  w_stream = (struct wav_stream *)malloc(sizeof(struct wav_stream));\n  if(!w_stream)\n  {\n    free(w_info->wav_data);\n    return NULL;\n  }\n\n  w_stream->wav_data = w_info->wav_data;\n  w_stream->data_length = w_info->data_length;\n  w_stream->channels = w_info->channels;\n  w_stream->data_offset = 0;\n  w_stream->format = w_info->format;\n  w_stream->natural_frequency = w_info->freq;\n  w_stream->bytes_per_sample = w_info->channels;\n  w_stream->loop_start = w_info->loop_start;\n  w_stream->loop_end = w_info->loop_end;\n\n  /**\n   * Due to a bug in the old SAM to WAV conversion code, the frequency provided\n   * has been halved with respect to what it should have been in DOS versions.\n   * If this wav spec was loaded directly from a SAM or via audio_spot_sample,\n   * reverse this \"fix\" that is now a permanent compatibility concern.\n   */\n  if(w_info->enable_sam_frequency_hack)\n    frequency *= 2;\n\n  if((w_info->format != SAMPLE_U8) && (w_info->format != SAMPLE_S8))\n    w_stream->bytes_per_sample *= 2;\n\n  memset(&a_spec, 0, sizeof(struct audio_stream_spec));\n  a_spec.mix_data       = wav_mix_data;\n  a_spec.set_volume     = wav_set_volume;\n  a_spec.set_repeat     = wav_set_repeat;\n  a_spec.set_position   = wav_set_position;\n  a_spec.set_loop_start = wav_set_loop_start;\n  a_spec.set_loop_end   = wav_set_loop_end;\n  a_spec.get_position   = wav_get_position;\n  a_spec.get_length     = wav_get_length;\n  a_spec.get_loop_start = wav_get_loop_start;\n  a_spec.get_loop_end   = wav_get_loop_end;\n  a_spec.destruct       = wav_destruct;\n\n  memset(&s_spec, 0, sizeof(struct sampled_stream_spec));\n  s_spec.set_frequency = wav_set_frequency;\n  s_spec.get_frequency = wav_get_frequency;\n\n  initialize_sampled_stream((struct sampled_stream *)w_stream, &s_spec,\n   w_info->freq, frequency, w_info->channels, true);\n\n  initialize_audio_stream((struct audio_stream *)w_stream, &a_spec,\n   volume, repeat);\n\n  return (struct audio_stream *)w_stream;\n}\n\nstatic struct audio_stream *construct_wav_stream(vfile *vf,\n const char *filename, uint32_t frequency, unsigned int volume, boolean repeat)\n{\n  struct audio_stream *a_src;\n  struct wav_info w_info;\n  memset(&w_info, 0, sizeof(struct wav_info));\n\n  if(!load_wav_file(vf, filename, &w_info))\n    return NULL;\n\n  // Surround WAVs not supported yet..\n  if(w_info.channels > 2)\n    return NULL;\n\n  a_src = construct_wav_stream_direct(&w_info, frequency, volume, repeat);\n  if(a_src)\n    vfclose(vf);\n\n  return a_src;\n}\n\nstatic struct audio_stream *construct_sam_stream(vfile *vf,\n const char *filename, uint32_t frequency, unsigned int volume, boolean repeat)\n{\n  struct audio_stream *a_src;\n  struct wav_info w_info;\n  memset(&w_info, 0, sizeof(struct wav_info));\n\n  if(!load_sam_file(vf, filename, &w_info))\n    return NULL;\n\n  a_src = construct_wav_stream_direct(&w_info, frequency, volume, repeat);\n  if(a_src)\n    vfclose(vf);\n\n  return a_src;\n}\n\nstatic boolean test_wav_stream(vfile *vf, const char *filename)\n{\n  char buf[12];\n  if(!vfread(buf, 12, 1, vf))\n    return false;\n\n  if(memcmp(buf, \"RIFF\", 4) != 0 || memcmp(buf + 8, \"WAVE\", 4) != 0)\n    return false;\n\n  return true;\n}\n\nstatic boolean test_sam_stream(vfile *vf, const char *filename)\n{\n  // .SAM files are raw 8363Hz signed 8-bit samples and can only be identified\n  // by their .SAM extension.\n  ssize_t ext_pos = path_get_ext_offset(filename);\n  if(ext_pos < 0)\n    return false;\n\n  return strcasecmp(filename + ext_pos, \".sam\") == 0;\n}\n\nvoid init_wav(struct config_info *conf)\n{\n  audio_ext_register(test_sam_stream, construct_sam_stream);\n  audio_ext_register(test_wav_stream, construct_wav_stream);\n}\n"
  },
  {
    "path": "src/audio/audio_wav.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __AUDIO_WAV_H\n#define __AUDIO_WAV_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n// For use by audio_spot_sample.\nstruct audio_stream *construct_wav_stream_direct(struct wav_info *w_info,\n uint32_t frequency, unsigned int volume, boolean repeat);\n\nvoid init_wav(struct config_info *conf);\n\n__M_END_DECLS\n\n#endif /* __AUDIO_WAV_H */\n"
  },
  {
    "path": "src/audio/audio_xmp.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Simon Parzer <simon.parzer@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Provides a libxmp module stream backend\n\n#include \"audio.h\"\n#include \"audio_struct.h\"\n#include \"audio_xmp.h\"\n#include \"audio_wav.h\"\n#include \"ext.h\"\n#include \"sampled_stream.h\"\n\n#include \"../const.h\"\n#include \"../util.h\"\n#include \"../io/vio.h\"\n\n//#ifdef __WIN32__\n//#define BUILDING_STATIC\n//#endif\n\n#include <xmp.h>\n\nstruct xmp_stream\n{\n  struct sampled_stream s;\n  xmp_context ctx;\n  uint32_t row_tbl[XMP_MAX_MOD_LENGTH];\n  size_t total_rows;\n  boolean repeat;\n};\n\nstatic int get_xmp_resample_mode(void)\n{\n  const struct config_info *conf = get_config();\n\n  switch(conf->module_resample_mode)\n  {\n    case RESAMPLE_MODE_NONE:\n      return XMP_INTERP_NEAREST;\n\n    case RESAMPLE_MODE_LINEAR:\n    default:\n      return XMP_INTERP_LINEAR;\n\n    case RESAMPLE_MODE_CUBIC:\n    case RESAMPLE_MODE_FIR:\n      return XMP_INTERP_SPLINE;\n  }\n}\n\nstatic boolean audio_xmp_mix_data(struct audio_stream *a_src,\n int32_t * RESTRICT buffer, size_t frames, unsigned int channels)\n{\n  struct xmp_stream *stream = (struct xmp_stream *)a_src;\n  void *read_buffer;\n  size_t read_wanted;\n  boolean r_val = false;\n  int xmp_r_val;\n\n  read_buffer = sampled_get_buffer(&stream->s, &read_wanted);\n\n  xmp_r_val =\n   xmp_play_buffer(stream->ctx, read_buffer, read_wanted, a_src->repeat ? 0 : 1);\n\n  if(xmp_r_val == -XMP_END && !a_src->repeat)\n  {\n    r_val = true;\n  }\n\n  sampled_mix_data((struct sampled_stream *)stream, buffer, frames, channels);\n\n  return r_val;\n}\n\nstatic void audio_xmp_set_volume(struct audio_stream *a_src, unsigned int volume)\n{\n  struct xmp_stream *stream = (struct xmp_stream *)a_src;\n  a_src->volume = volume;\n\n  // xmp uses SMIX volume for all extra channels, but some files can generate\n  // these channels through normal behavior. Since we don't use SMIX, we can\n  // safely set both volumes to the same value.\n  xmp_set_player(stream->ctx, XMP_PLAYER_VOLUME, volume * 100 / 255);\n  xmp_set_player(stream->ctx, XMP_PLAYER_SMIX_VOLUME, volume * 100 / 255);\n}\n\nstatic void audio_xmp_set_repeat(struct audio_stream *a_src, boolean repeat)\n{\n  a_src->repeat = repeat;\n}\n\nstatic void audio_xmp_set_position(struct audio_stream *a_src, uint32_t position)\n{\n  struct xmp_stream *stream = (struct xmp_stream *)a_src;\n  struct xmp_frame_info info;\n  int i;\n\n  xmp_get_frame_info(stream->ctx, &info);\n\n  for(i = 1; i < XMP_MAX_MOD_LENGTH; i++)\n  {\n    if(stream->row_tbl[i] > position)\n    {\n      int position_row = position - stream->row_tbl[i - 1];\n      xmp_set_position(stream->ctx, i - 1);\n      xmp_set_row(stream->ctx, position_row);\n      return;\n    }\n  }\n  xmp_set_position(stream->ctx, 0);\n}\n\nstatic void audio_xmp_set_order(struct audio_stream *a_src, uint32_t order)\n{\n  struct xmp_stream *stream = (struct xmp_stream *)a_src;\n  // XMP won't jump to the start of the specified order it's already playing\n  // most of the time, so set the row too.\n  xmp_set_position(stream->ctx, order);\n  xmp_set_row(stream->ctx, 0);\n}\n\nstatic void audio_xmp_set_frequency(struct sampled_stream *s_src,\n uint32_t frequency)\n{\n  sampled_set_buffer(s_src, frequency, 44100);\n}\n\nstatic uint32_t audio_xmp_get_order(struct audio_stream *a_src)\n{\n  struct xmp_frame_info info;\n  xmp_get_frame_info(((struct xmp_stream *)a_src)->ctx, &info);\n  return info.pos;\n}\n\nstatic uint32_t audio_xmp_get_position(struct audio_stream *a_src)\n{\n  struct xmp_frame_info info;\n  xmp_get_frame_info(((struct xmp_stream *)a_src)->ctx, &info);\n  return ((struct xmp_stream *)a_src)->row_tbl[info.pos] + info.row;\n}\n\nstatic uint32_t audio_xmp_get_length(struct audio_stream *a_src)\n{\n  struct xmp_stream *stream = (struct xmp_stream *)a_src;\n  return stream->total_rows;\n}\n\nstatic uint32_t audio_xmp_get_frequency(struct sampled_stream *s_src)\n{\n  return s_src->relative_frequency;\n}\n\n// Return a duplicate of a sample from the current playing mod.\nstatic boolean audio_xmp_get_sample(struct audio_stream *a_src, unsigned int which,\n struct wav_info *dest)\n{\n  struct xmp_stream *stream = (struct xmp_stream *)a_src;\n\n  struct xmp_module_info info;\n  xmp_get_module_info(stream->ctx, &info);\n\n  if(info.mod && info.mod->xxs && (info.mod->smp > (int)which))\n  {\n    struct xmp_sample *sam = &(info.mod->xxs[which]);\n\n    // I don't know if it's even possible for synth samples to get this far,\n    // but check for them so they can be ignored anyway.\n    if(sam->len && !(sam->flg & XMP_SAMPLE_SYNTH))\n    {\n      dest->channels = 1;\n      dest->freq = audio_get_real_frequency(SAM_DEFAULT_PERIOD);\n      dest->data_length = sam->len;\n\n      // The period provided to audio_spot_sample was doubled for consistency\n      // with the incorrect behavior added to audio_play_sample, so enable SAM\n      // hacks to fix the frequency.\n      dest->enable_sam_frequency_hack = true;\n\n      // If the sample loops, the sample this returns needs to loop too.\n      if(sam->flg & XMP_SAMPLE_LOOP)\n      {\n        dest->loop_start = sam->lps;\n        dest->loop_end = sam->lpe;\n      }\n      else\n        dest->loop_start = dest->loop_end = 0;\n\n      // XMP samples are signed, and if 16bit, use the system byte order.\n      // MZX supports all of these, so just copy the sample directly.\n      if(sam->flg & XMP_SAMPLE_16BIT)\n      {\n        dest->format = SAMPLE_S16;\n        dest->data_length *= 2;\n      }\n      else\n        dest->format = SAMPLE_S8;\n\n      dest->wav_data = cmalloc(dest->data_length);\n      memcpy(dest->wav_data, sam->data, dest->data_length);\n      return true;\n    }\n  }\n  return false;\n}\n\nstatic void audio_xmp_destruct(struct audio_stream *a_src)\n{\n  xmp_end_player(((struct xmp_stream *)a_src)->ctx);\n  xmp_release_module(((struct xmp_stream *)a_src)->ctx);\n  xmp_free_context(((struct xmp_stream *)a_src)->ctx);\n  sampled_destruct(a_src);\n}\n\nstatic unsigned long read_func(void *dest, unsigned long len, unsigned long nmemb, void *priv)\n{\n  vfile *vf = (vfile *)priv;\n  return vfread(dest, len, nmemb, vf);\n}\n\nstatic int seek_func(void *priv, long offset, int whence)\n{\n  vfile *vf = (vfile *)priv;\n  return vfseek(vf, offset, whence);\n}\n\nstatic long tell_func(void *priv)\n{\n  vfile *vf = (vfile *)priv;\n  return vftell(vf);\n}\n\nstatic struct xmp_callbacks xmp_callbacks =\n{\n  read_func,\n  seek_func,\n  tell_func,\n  NULL\n};\n\nstatic struct audio_stream *construct_xmp_stream(vfile *vf,\n const char *filename, uint32_t frequency, unsigned int volume, boolean repeat)\n{\n  struct xmp_stream *xmp_stream;\n  struct sampled_stream_spec s_spec;\n  struct audio_stream_spec a_spec;\n  struct xmp_module_info info;\n  xmp_context ctx;\n  unsigned char ord;\n  size_t row_pos;\n  int num_orders;\n  int i;\n  int chn = audio.buffer_channels < 2 ? 1 : 2;\n  int flags = chn == 1 ? XMP_FORMAT_MONO : 0;\n\n  ctx = xmp_create_context();\n  if(!ctx)\n    return NULL;\n\n  xmp_set_player(ctx, XMP_PLAYER_DEFPAN, 50);\n\n  /* This function does not close the provided file pointer. */\n  if(xmp_load_module_from_callbacks(ctx, vf, xmp_callbacks) < 0)\n  {\n    xmp_free_context(ctx);\n    return NULL;\n  }\n\n  xmp_stream = (struct xmp_stream *)ccalloc(1, sizeof(struct xmp_stream));\n  if(!xmp_stream)\n  {\n    xmp_free_context(ctx);\n    return NULL;\n  }\n\n  xmp_stream->ctx = ctx;\n  xmp_start_player(ctx, audio.output_frequency, flags);\n  xmp_set_player(ctx, XMP_PLAYER_INTERP, get_xmp_resample_mode());\n\n  xmp_get_module_info(ctx, &info);\n  num_orders = info.mod->len;\n\n  for(i = 0, row_pos = 0; i < num_orders; i++)\n  {\n    xmp_stream->row_tbl[i] = row_pos;\n    ord = info.mod->xxo[i];\n    if(ord < info.mod->pat)\n      row_pos += info.mod->xxp[ord]->rows;\n  }\n  xmp_stream->total_rows = row_pos;\n\n  // xmp uses sequences to make secondary loops in modules playable, but this\n  // breaks looping from the end of the module to the start. Since MZX doesn't\n  // use sequences and games rely on normal looping, zero all entry points.\n  for(i = 0; i < info.num_sequences; i++)\n  {\n    info.seq_data[i].entry_point = 0;\n  }\n\n  memset(&a_spec, 0, sizeof(struct audio_stream_spec));\n  a_spec.mix_data     = audio_xmp_mix_data;\n  a_spec.set_volume   = audio_xmp_set_volume;\n  a_spec.set_repeat   = audio_xmp_set_repeat;\n  a_spec.set_order    = audio_xmp_set_order;\n  a_spec.set_position = audio_xmp_set_position;\n  a_spec.get_order    = audio_xmp_get_order;\n  a_spec.get_position = audio_xmp_get_position;\n  a_spec.get_length   = audio_xmp_get_length;\n  a_spec.get_sample   = audio_xmp_get_sample;\n  a_spec.destruct     = audio_xmp_destruct;\n\n  memset(&s_spec, 0, sizeof(struct sampled_stream_spec));\n  s_spec.set_frequency = audio_xmp_set_frequency;\n  s_spec.get_frequency = audio_xmp_get_frequency;\n\n  initialize_sampled_stream((struct sampled_stream *)xmp_stream, &s_spec,\n   audio.output_frequency, frequency, chn, false);\n\n  initialize_audio_stream((struct audio_stream *)xmp_stream, &a_spec,\n   volume, repeat);\n\n  vfclose(vf);\n  return (struct audio_stream *)xmp_stream;\n}\n\nvoid init_xmp(struct config_info *conf)\n{\n  audio_ext_register(NULL, construct_xmp_stream);\n}\n"
  },
  {
    "path": "src/audio/audio_xmp.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declarations */\n\n#ifndef __AUDIO_XMP_H\n#define __AUDIO_XMP_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\nvoid init_xmp(struct config_info *conf);\n\n__M_END_DECLS\n\n#endif  // __AUDIO_XMP_H\n"
  },
  {
    "path": "src/audio/driver_sdl3.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2024-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <assert.h>\n#include <stdlib.h>\n\n#include \"../SDLmzx.h\"\n#include \"../util.h\"\n\n#include \"audio.h\"\n#include \"audio_struct.h\"\n\nstatic SDL_AudioSpec audio_settings;\nstatic SDL_AudioStream *audio_stream;\nstatic void *audio_buffer;\nstatic unsigned audio_format;\n\nstatic void sdl_audio_callback(void *userdata, SDL_AudioStream *stream,\n int required_bytes, int requested_bytes)\n{\n  size_t framesize = SDL_AUDIO_FRAMESIZE(audio_settings);\n  size_t frames = MAX(0, required_bytes) / framesize;\n\n  //fprintf(mzxerr, \"  #:%d fr:%zu frsz:%zu\\n\", required_bytes, frames, framesize);\n  while(frames > 0)\n  {\n    size_t out = audio_mixer_render_frames(userdata, frames,\n     audio_settings.channels, audio_format);\n\n    assert(out <= frames);\n    SDL_PutAudioStreamData(audio_stream, userdata, out * framesize);\n    frames -= out;\n  }\n}\n\nvoid init_audio_platform(struct config_info *conf)\n{\n  SDL_AudioDeviceID audio_device = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;\n  int frames = 0;\n  char hint[16];\n  void *tmp;\n  // TODO: 8-bit?\n\n  debug(\"--AUDIO_DRIVER_SDL3-- init_audio_platform\\n\");\n\n  audio_format = SAMPLE_S16;\n\n  memset(&audio_settings, 0, sizeof(audio_settings));\n  if(!SDL_GetAudioDeviceFormat(audio_device, &audio_settings, &frames))\n  {\n    debug(\"--AUDIO_DRIVER_SDL3-- failed to query default device format: %s\\n\",\n     SDL_GetError());\n    // Can't query, try to continue anyway...\n    audio_settings.freq = 48000;\n    frames = 1024;\n  }\n  audio_settings.format = SDL_AUDIO_S16;\n  audio_settings.channels = 2;\n\n  if(conf->audio_sample_rate != 0)\n  {\n    // Reject very low sample rates to avoid PulseAudio hangs.\n    audio_settings.freq = MAX(conf->audio_sample_rate, 2048);\n  }\n\n  if(conf->audio_buffer_samples != 0)\n    frames = conf->audio_buffer_samples;\n\n  if(conf->audio_output_channels != 0)\n    audio_settings.channels = MIN(conf->audio_output_channels, 2);\n\n  if(!audio_mixer_init(audio_settings.freq, frames, audio_settings.channels))\n    return;\n\n  audio_settings.freq = audio.output_frequency;\n\n  tmp = crealloc(audio_buffer,\n   audio.buffer_frames * audio.buffer_channels * sizeof(int16_t));\n  if(!tmp)\n  {\n    warn(\"--AUDIO_DRIVER_SDL3-- failed to allocate buffer\\n\");\n    return;\n  }\n\n  // The buffer frames need to be configured with this hack.\n  // This value may be ignored or modified by some platforms.\n  snprintf(hint, sizeof(hint), \"%u\", audio.buffer_frames);\n  SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, hint);\n\n  debug(\"--AUDIO_DRIVER_SDL3-- initializing default audio device %\" PRIu32 \": \"\n   \"S16 %dch %dHz %dfr\\n\", audio_device, audio_settings.channels,\n   audio_settings.freq, audio.buffer_frames);\n\n  audio_buffer = tmp;\n  audio_stream = SDL_OpenAudioDeviceStream(audio_device, &audio_settings,\n   sdl_audio_callback, tmp);\n  if(!audio_stream)\n  {\n    warn(\"--AUDIO_DRIVER_SDL3-- failed to initialize default audio device: %s\\n\",\n     SDL_GetError());\n    goto err;\n  }\n\n  audio_device = SDL_GetAudioStreamDevice(audio_stream);\n  if(audio_device == 0)\n  {\n    debug(\"--AUDIO_DRIVER_SDL3-- failed to get device ID to resume: %s\\n\",\n     SDL_GetError());\n    audio_device = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;\n  }\n\n  if(SDL_ResumeAudioDevice(audio_device))\n  {\n    debug(\"--AUDIO_DRIVER_SDL3-- audio device %\" PRIu32\n     \" initialized successfully\\n\", audio_device);\n  }\n  else\n  {\n    warn(\"--AUDIO_DRIVER_SDL3-- failed to resume audio device %\" PRIu32 \": %s\\n\",\n     audio_device, SDL_GetError());\n  }\n  return;\n\nerr:\n  free(tmp);\n  audio_buffer = NULL;\n  return;\n}\n\nvoid quit_audio_platform(void)\n{\n  if(audio_stream)\n  {\n    SDL_DestroyAudioStream(audio_stream);\n    audio_stream = NULL;\n  }\n  free(audio_buffer);\n  audio_buffer = NULL;\n}\n"
  },
  {
    "path": "src/audio/ext.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2004 madbrain\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2018-2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"audio.h\"\n#include \"audio_struct.h\"\n#include \"ext.h\"\n\n#include \"../util.h\"\n#include \"../io/vio.h\"\n\nstruct registry_entry\n{\n  filter_stream_fn test;\n  construct_stream_fn constructor;\n};\n\nstatic struct registry_entry *registry = NULL;\nstatic size_t registry_size = 0;\nstatic size_t registry_alloc = 0;\n\nvoid audio_ext_register(filter_stream_fn test, construct_stream_fn constructor)\n{\n  assert(constructor);\n  if(registry_alloc <= registry_size)\n  {\n    struct registry_entry *tmp;\n\n    if(!registry_alloc)\n      registry_alloc = 8;\n    else\n      registry_alloc <<= 1;\n\n    tmp = (struct registry_entry *)realloc(registry,\n     registry_alloc * sizeof(struct registry_entry));\n    if(!tmp)\n    {\n      warn(\"failed to allocate memory for audio format registry.\\n\");\n      return;\n    }\n    registry = tmp;\n  }\n\n  registry[registry_size].test = test;\n  registry[registry_size].constructor = constructor;\n  registry_size++;\n}\n\nvoid audio_ext_free_registry(void)\n{\n  free(registry);\n  registry = NULL;\n  registry_size = 0;\n  registry_alloc = 0;\n}\n\nstruct audio_stream *audio_ext_construct_stream(const char *filename,\n uint32_t frequency, unsigned int volume, boolean repeat)\n{\n  struct audio_stream *a_return = NULL;\n  vfile *vf;\n  unsigned i;\n\n  if(!audio.music_on)\n    return NULL;\n\n  // Using a buffer vastly improves module load times on some architectures.\n  vf = vfopen_unsafe_ext(filename, \"rb\", V_LARGE_BUFFER);\n  if(!vf)\n    return NULL;\n\n  // Find a constructor in the registry\n  for(i = 0; i < registry_size; i++)\n  {\n    construct_stream_fn constructor = registry[i].constructor;\n    if(registry[i].test)\n    {\n      boolean result = registry[i].test(vf, filename);\n      vrewind(vf);\n      if(!result)\n        continue;\n    }\n\n    a_return = constructor(vf, filename, frequency, volume, repeat);\n    if(a_return)\n      break;\n\n    vrewind(vf);\n  }\n\n  // The constructor function is responsible for closing or\n  // retaining the file handle on successful loads.\n  if(!a_return)\n    vfclose(vf);\n\n  return a_return;\n}\n"
  },
  {
    "path": "src/audio/ext.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __AUDIO_EXT_H\n#define __AUDIO_EXT_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"audio.h\"\n#include \"../io/vfile.h\"\n\ntypedef boolean (*filter_stream_fn)(vfile *vf, const char *filename);\ntypedef struct audio_stream *(*construct_stream_fn)(vfile *vf, const char *,\n uint32_t frequency, unsigned int volume, boolean repeat);\n\nvoid audio_ext_register(filter_stream_fn test, construct_stream_fn constructor);\nvoid audio_ext_free_registry(void);\n\nstruct audio_stream *audio_ext_construct_stream(const char *filename,\n uint32_t frequency, unsigned int volume, boolean repeat);\n\n__M_END_DECLS\n\n#endif /* __AUDIO_EXT_H */\n"
  },
  {
    "path": "src/audio/sampled_stream.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2004 madbrain\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2018, 2024-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Common functions for sampled streams.\n\n#include <assert.h>\n#include <math.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"audio.h\"\n#include \"audio_struct.h\"\n#include \"sampled_stream.h\"\n\n#define FP_SHIFT      13\n#define FP_AND        ((1 << FP_SHIFT) - 1)\n#define FP_ONE        (1 << FP_SHIFT)\n\n/**\n * This uses C++ templates to generate optimized mixer variants depending on\n * the number of channels, whether or not volume is required, and whether or\n * not resampling is required. This helps avoid redundant code.\n *\n * Likewise, fixed point is used to improve performance. The FP_SHIFT value is\n * the maximum amount an int16_t can be shifted into an int32_t while still\n * leaving enough space for the required math in the linear and cubic mixers.\n * The cubic mixer currently works with values shifted by 2x FP_SHIFT when\n * computing its polynomial.\n *\n * NOTE: FP_SHIFT values of 16 or 32 theoretically might help performance but\n *       some experimentation suggests it's not enough to be worth the effort.\n * TODO: FIR/Sinc-Lanczos resampler!!!!!111one\n * TODO: surround (runtime channel mapping only)\n */\n\nenum mixer_resample\n{\n  FLAT        = 0,\n  NEAREST     = 1,\n  LINEAR      = 2,\n  CUBIC       = 3\n};\n\nenum mixer_channels\n{\n  MONO        = 1,\n  STEREO      = 2,\n  SURROUND    = 3\n};\n\nenum mixer_volume\n{\n  FULL        = 0,\n  DYNAMIC     = 1\n};\n\n/* Cubic resampling needs one prologue sample and three epilogue samples. */\n#define PROLOGUE_LENGTH (1 * STEREO * sizeof(int16_t))\n#define EPILOGUE_LENGTH (3 * STEREO * sizeof(int16_t))\n\ntemplate<mixer_volume VOLUME>\nstatic int32_t volume_function(int32_t sample, int volume)\n{\n  /**\n   * NOTE: previous versions of MZX did a /256 here. Just do the bitshift--the\n   * rounding difference is more or less irrelevant and it performs better than\n   * GCC's optimization for signed constant division.\n   */\n  if(VOLUME)\n    return (sample * volume) >> 8;\n\n  return sample;\n}\n\ntemplate<mixer_channels DEST_CHANNELS, mixer_channels SRC_CHANNELS,\n mixer_volume VOLUME>\nstatic void flat_mix_loop(struct sampled_stream *s_src,\n int32_t * RESTRICT dest, size_t dest_frames, const int16_t *src, int volume)\n{\n  //size_t chn = DEST_CHANNELS > STEREO ? s_src->dest_channels : (size_t)DEST_CHANNELS;\n  for(size_t i = 0; i < dest_frames; i++)\n  {\n    if(SRC_CHANNELS == DEST_CHANNELS && DEST_CHANNELS <= STEREO)\n    {\n      for(size_t j = 0; j < DEST_CHANNELS; j++)\n        *(dest++) += volume_function<VOLUME>(*(src++), volume);\n    }\n    else\n\n    if(DEST_CHANNELS == STEREO && SRC_CHANNELS == MONO)\n    {\n      int32_t smpl = volume_function<VOLUME>(*(src++), volume);\n      *(dest++) += smpl;\n      *(dest++) += smpl;\n    }\n    else\n\n    if(DEST_CHANNELS == MONO && SRC_CHANNELS == STEREO)\n    {\n      int32_t mix = 0;\n      mix += volume_function<VOLUME>(*(src++), volume);\n      mix += volume_function<VOLUME>(*(src++), volume);\n      *(dest++) += mix >> 1;\n    }\n    //else TODO: surround not implemented\n  }\n  s_src->sample_index = dest_frames << FP_SHIFT;\n}\n\n/**\n * C++98 requires that these have external linkage to be used as template\n * parameters. This is pointless but fortunately doesn't hurt C++11 builds.\n */\ntypedef int32_t (*mix_function)(const int16_t *src_offset, ssize_t frac_index);\n\ntemplate<mixer_channels SRC_CHANNELS>\nint32_t nearest_mix(const int16_t *src_offset, ssize_t frac_index)\n{\n  return src_offset[0];\n}\n\n// TODO: linear and cubic need external s_chn for surround.\ntemplate<mixer_channels SRC_CHANNELS>\nint32_t linear_mix(const int16_t *src_offset, ssize_t frac_index)\n{\n  int chn = static_cast<int>(SRC_CHANNELS);\n  int32_t left = src_offset[0];\n  int32_t right = src_offset[chn];\n  return left + ((right - left) * frac_index >> FP_SHIFT);\n}\n\ntemplate<mixer_channels SRC_CHANNELS>\nint32_t cubic_mix(const int16_t *src_offset, ssize_t frac_index)\n{\n  int chn = static_cast<int>(SRC_CHANNELS);\n  /**\n   * NOTE: copied mostly verbatim from the old mixer code, with cleanup.\n   * This uses ssize_t instead of int32_t since it seems to be faster for\n   * 64-bit machines in this particular resampler.\n   *\n   * This uses a Hermite spline. This is somewhat faster to compute and\n   * generally considered better quality than a Lagrange cubic.\n   */\n  ssize_t s0 = (ssize_t)src_offset[-chn]    << FP_SHIFT;\n  ssize_t s1 = (ssize_t)src_offset[0]       << FP_SHIFT;\n  ssize_t s2 = (ssize_t)src_offset[chn]     << FP_SHIFT;\n  ssize_t s3 = (ssize_t)src_offset[chn * 2] << FP_SHIFT;\n\n  int64_t a = (((3 * (s1 - s2)) - s0 + s3) / 2);\n  int64_t b = ((2 * s2) + s0 - (((5 * s1) + s3) / 2));\n  int64_t c = ((s2 - s0) / 2);\n\n  a = ((a * frac_index) >> FP_SHIFT) + b;\n  a = ((a * frac_index) >> FP_SHIFT) + c;\n  a = ((a * frac_index) >> FP_SHIFT) + s1;\n  return (a >> FP_SHIFT);\n}\n\ntemplate<mixer_channels DEST_CHANNELS, mixer_channels SRC_CHANNELS,\n mixer_volume VOLUME, mix_function MIX>\nstatic void resample_mix_loop(struct sampled_stream *s_src,\n int32_t * RESTRICT dest, size_t dest_frames, const int16_t *src, int volume)\n{\n  //size_t d_chn = DEST_CHANNELS > STEREO ? s_src->dest_channels : (size_t)DEST_CHANNELS;\n  size_t s_chn = SRC_CHANNELS > STEREO ? s_src->channels : (size_t)SRC_CHANNELS;\n  int64_t sample_index = s_src->sample_index;\n  int64_t delta = s_src->frequency_delta;\n\n  for(size_t i = 0; i < dest_frames; i++, sample_index += delta)\n  {\n    ssize_t int_index = (sample_index >> FP_SHIFT) * s_chn;\n    ssize_t frac_index = sample_index & FP_AND;\n\n    if(SRC_CHANNELS == DEST_CHANNELS && DEST_CHANNELS <= STEREO)\n    {\n      for(size_t j = 0; j < DEST_CHANNELS; j++)\n      {\n        int32_t mix = MIX(src + int_index + j, frac_index);\n        *(dest++) += volume_function<VOLUME>(mix, volume);\n      }\n    }\n    else\n\n    if(DEST_CHANNELS == STEREO && SRC_CHANNELS == MONO)\n    {\n      int32_t mix = MIX(src + int_index, frac_index);\n      int32_t smpl = volume_function<VOLUME>(mix, volume);\n      *(dest++) += smpl;\n      *(dest++) += smpl;\n    }\n    else\n\n    if(DEST_CHANNELS == MONO && SRC_CHANNELS == STEREO)\n    {\n      int32_t mix = 0;\n      for(size_t chn = 0; chn < SRC_CHANNELS; chn++)\n      {\n        int32_t smpl = MIX(src + int_index + chn, frac_index);\n        mix += volume_function<VOLUME>(smpl, volume);\n      }\n      *(dest++) += mix >> 1;\n    }\n    //else TODO: surround not implemented\n  }\n  s_src->sample_index = sample_index;\n}\n\ntemplate<mixer_channels DEST_CHANNELS, mixer_channels SRC_CHANNELS, mixer_volume VOLUME>\nstatic void mixer_function(struct sampled_stream *s_src,\n int32_t * RESTRICT dest, size_t dest_frames, const int16_t *src, int volume,\n int resample_mode)\n{\n  switch((mixer_resample)resample_mode)\n  {\n    case FLAT:\n      flat_mix_loop<DEST_CHANNELS, SRC_CHANNELS, VOLUME>(\n       s_src, dest, dest_frames, src, volume);\n      break;\n\n    case NEAREST:\n      resample_mix_loop<DEST_CHANNELS, SRC_CHANNELS, VOLUME, nearest_mix<SRC_CHANNELS> >(\n       s_src, dest, dest_frames, src, volume);\n      break;\n\n    case LINEAR:\n      resample_mix_loop<DEST_CHANNELS, SRC_CHANNELS, VOLUME, linear_mix<SRC_CHANNELS> >(\n       s_src, dest, dest_frames, src, volume);\n      break;\n\n    case CUBIC:\n      resample_mix_loop<DEST_CHANNELS, SRC_CHANNELS, VOLUME, cubic_mix<SRC_CHANNELS> >(\n       s_src, dest, dest_frames, src, volume);\n      break;\n  }\n}\n\ntemplate<mixer_channels DEST_CHANNELS, mixer_channels SRC_CHANNELS>\nstatic void mixer_function(struct sampled_stream *s_src,\n int32_t * RESTRICT dest, size_t dest_frames, const int16_t *src, int volume,\n int resample_mode, mixer_volume volume_mode)\n{\n  switch(volume_mode)\n  {\n    case FULL:\n      mixer_function<DEST_CHANNELS, SRC_CHANNELS, FULL>(\n       s_src, dest, dest_frames, src, volume, resample_mode);\n      break;\n\n    case DYNAMIC:\n      mixer_function<DEST_CHANNELS, SRC_CHANNELS, DYNAMIC>(\n       s_src, dest, dest_frames, src, volume, resample_mode);\n      break;\n  }\n}\n\ntemplate<mixer_channels DEST_CHANNELS>\nstatic void mixer_function(struct sampled_stream *s_src,\n int32_t * RESTRICT dest, size_t dest_frames, const int16_t *src, int volume,\n int resample_mode, mixer_channels src_channels, mixer_volume volume_mode)\n{\n  switch(src_channels)\n  {\n    case MONO:\n      mixer_function<DEST_CHANNELS, MONO>(\n       s_src, dest, dest_frames, src, volume, resample_mode, volume_mode);\n      break;\n\n    case STEREO:\n      mixer_function<DEST_CHANNELS, STEREO>(\n       s_src, dest, dest_frames, src, volume, resample_mode, volume_mode);\n      break;\n\n    case SURROUND:\n      mixer_function<DEST_CHANNELS, SURROUND>(\n       s_src, dest, dest_frames, src, volume, resample_mode, volume_mode);\n      break;\n  }\n}\n\nstatic void mixer_function(struct sampled_stream *s_src,\n int32_t * RESTRICT dest, size_t dest_frames, const int16_t *src, int volume,\n int resample_mode, mixer_channels dest_channels, mixer_channels src_channels,\n mixer_volume volume_mode)\n{\n  switch(dest_channels)\n  {\n    case MONO:\n      mixer_function<MONO>(\n       s_src, dest, dest_frames, src, volume, resample_mode, src_channels, volume_mode);\n      break;\n\n    case STEREO:\n      mixer_function<STEREO>(\n       s_src, dest, dest_frames, src, volume, resample_mode, src_channels, volume_mode);\n      break;\n\n    case SURROUND:\n      mixer_function<SURROUND>(\n       s_src, dest, dest_frames, src, volume, resample_mode, src_channels, volume_mode);\n      break;\n  }\n}\n\nvoid *sampled_get_buffer(struct sampled_stream *s_src, size_t *needed)\n{\n  size_t total = s_src->data_window_length + PROLOGUE_LENGTH + EPILOGUE_LENGTH;\n  *needed = (total > s_src->stream_offset) ? total - s_src->stream_offset : 0;\n  return ((uint8_t *)s_src->output_data) + s_src->stream_offset;\n}\n\nvoid sampled_set_buffer(struct sampled_stream *s_src, uint32_t rel_frequency,\n uint32_t rel_frequency_base)\n{\n  size_t data_window_length;\n  size_t allocated_data_length;\n  int64_t frequency;\n  int64_t frequency_base;\n  int64_t frequency_delta;\n\n  frequency = (int64_t)s_src->input_frequency;\n  frequency_base = (int64_t)audio.output_frequency;\n\n  /* The relative frequency base is whatever value should be reported for \"1:1\"\n   * rate to the game engine, usually 44100 for modules and native for PCM.\n   * This value should never be zero, but check it just in case.\n   *\n   * Relative frequency of 0 acts the same as 1:1.\n   */\n  if(rel_frequency != 0 && rel_frequency_base != 0)\n  {\n    frequency *= (int64_t)rel_frequency;\n    frequency_base *= (int64_t)rel_frequency_base;\n  }\n  else\n    rel_frequency = rel_frequency_base;\n\n  frequency_delta = (frequency << FP_SHIFT) / frequency_base;\n\n  data_window_length =\n   (size_t)(ceil((double)audio.buffer_frames *\n   frequency / frequency_base) * s_src->bytes_per_frame);\n\n  allocated_data_length = data_window_length + PROLOGUE_LENGTH + EPILOGUE_LENGTH;\n\n  if(allocated_data_length < s_src->bytes_per_frame)\n    allocated_data_length = s_src->bytes_per_frame;\n\n  /* If the buffer still contains data past the desired allocation, leave it.\n   * Otherwise, the audio will audibly skip. */\n  if(s_src->stream_offset > allocated_data_length)\n    allocated_data_length = s_src->stream_offset;\n\n  s_src->relative_frequency = rel_frequency;\n  s_src->frequency_delta = frequency_delta;\n  s_src->data_window_length = data_window_length;\n  s_src->allocated_data_length = allocated_data_length;\n\n  s_src->output_data =\n   (int16_t *)crealloc(s_src->output_data, allocated_data_length);\n}\n\nvoid sampled_mix_data(struct sampled_stream *s_src,\n int32_t * RESTRICT dest_buffer, size_t dest_frames, unsigned int dest_channels)\n{\n  uint8_t *output_data = (uint8_t *)s_src->output_data;\n  int16_t *src_buffer = (int16_t *)(output_data + PROLOGUE_LENGTH);\n  int volume = ((struct audio_stream *)s_src)->volume;\n  int resample_mode = audio.global_resample_mode + 1;\n  enum mixer_volume   volume_mode  = DYNAMIC;\n  enum mixer_channels src_channels = STEREO;\n  enum mixer_channels out_channels = STEREO;\n  size_t new_index;\n  size_t new_index_bytes;\n  size_t leftover_bytes = 0;\n\n  // MZX currently only supports mono and stereo output.\n  assert(dest_channels == 1 || dest_channels == 2);\n\n  if(s_src->frequency_delta == FP_ONE)\n    resample_mode = FLAT;\n\n  if(!s_src->use_volume || volume == 256)\n    volume_mode = FULL;\n\n  if(s_src->channels < 2)\n    src_channels = MONO;\n  if(dest_channels < 2)\n    out_channels = MONO;\n\n  s_src->dest_channels = dest_channels;\n  mixer_function(s_src, dest_buffer, dest_frames, src_buffer, volume,\n   resample_mode, out_channels, src_channels, volume_mode);\n\n  /* Relocate the leftovers--plus the extra prologue and epilogue samples for\n   * the resamplers--back to the start of the buffer. */\n  new_index = s_src->sample_index >> FP_SHIFT;\n  new_index_bytes = new_index * s_src->bytes_per_frame;\n  if(new_index_bytes > s_src->data_window_length)\n    new_index_bytes = s_src->data_window_length;\n\n  leftover_bytes = s_src->data_window_length - new_index_bytes;\n\n  /* Note new_index_bytes doesn't count the prologue, so both the source and\n   * the dest include it implicitly. */\n  memmove(output_data,\n   output_data + new_index_bytes,\n   leftover_bytes + PROLOGUE_LENGTH + EPILOGUE_LENGTH);\n\n  s_src->stream_offset = leftover_bytes + PROLOGUE_LENGTH + EPILOGUE_LENGTH;\n  s_src->sample_index &= FP_AND;\n}\n\nvoid sampled_destruct(struct audio_stream *a_src)\n{\n  struct sampled_stream *s_stream = (struct sampled_stream *)a_src;\n  free(s_stream->output_data);\n  destruct_audio_stream(a_src);\n}\n\nvoid initialize_sampled_stream(struct sampled_stream *s_src,\n struct sampled_stream_spec *s_spec, uint32_t input_frequency,\n uint32_t relative_frequency, uint32_t channels, boolean use_volume)\n{\n  s_src->set_frequency = s_spec->set_frequency;\n  s_src->get_frequency = s_spec->get_frequency;\n  s_src->channels = channels;\n  s_src->output_data = NULL;\n  s_src->use_volume = use_volume;\n  s_src->bytes_per_frame = channels * sizeof(int16_t);\n  s_src->stream_offset = PROLOGUE_LENGTH;\n  s_src->sample_index = 0;\n\n  s_src->input_frequency = input_frequency;\n  s_src->set_frequency(s_src, relative_frequency);\n\n  memset(s_src->output_data, 0, PROLOGUE_LENGTH);\n}\n"
  },
  {
    "path": "src/audio/sampled_stream.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __AUDIO_SAMPLED_STREAM_H\n#define __AUDIO_SAMPLED_STREAM_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"audio_struct.h\"\n\nstruct sampled_stream\n{\n  struct audio_stream a;\n  uint32_t relative_frequency;\n  uint32_t input_frequency;\n  int16_t *output_data;\n  size_t data_window_length;\n  size_t allocated_data_length;\n  size_t stream_offset;\n  size_t channels;\n  size_t dest_channels;\n  unsigned bytes_per_frame;\n  boolean use_volume;\n  int64_t frequency_delta;\n  int64_t sample_index;\n  void      (* set_frequency)(struct sampled_stream *s_src, uint32_t frequency);\n  uint32_t  (* get_frequency)(struct sampled_stream *s_src);\n};\n\nstruct sampled_stream_spec\n{\n  void      (* set_frequency)(struct sampled_stream *s_src, uint32_t frequency);\n  uint32_t  (* get_frequency)(struct sampled_stream *s_src);\n};\n\nvoid *sampled_get_buffer(struct sampled_stream *s_src, size_t *needed);\nvoid sampled_set_buffer(struct sampled_stream *s_src, uint32_t rel_frequency,\n uint32_t rel_frequency_base);\nvoid sampled_mix_data(struct sampled_stream *s_src,\n int32_t * RESTRICT dest_buffer, size_t dest_frames, unsigned int dest_channels);\nvoid sampled_destruct(struct audio_stream *a_src);\n\n/**\n *                      input frequency * relative frequency\n * frequency ratio = ------------------------------------------\n *                   output frequency * relative frequency base\n *\n * This function will call the provided set_frequency function with the\n * provided relative frequency to recalculate the frequency ratio and allocate\n * a mixing buffer.\n *\n * @param s_src               audio_stream/sampled_stream struct\n * @param s_spec              override member function pointers\n * @param input_frequency     the native rate of the input stream. For module\n *                            players, this is set to the output frequency\n *                            (to allow them to use internal resamplers); for\n *                            RAD, this is 49716; for PCM files, this is the\n *                            sample rate of the file.\n * @param relative_frequency  the logical playback rate. For module players,\n *                            this is relative to 44100; for PCM files, this\n *                            is relative to the sample rate of the file.\n * @param channels            channels in stream (1 or 2).\n * @param use_volume          if true, compute volume, otherwise always max.\n */\nvoid initialize_sampled_stream(struct sampled_stream *s_src,\n struct sampled_stream_spec *s_spec, uint32_t input_frequency,\n uint32_t relative_frequency, uint32_t channels, boolean use_volume);\n\n__M_END_DECLS\n\n#endif /* __AUDIO_SAMPLED_STREAM_H */\n"
  },
  {
    "path": "src/audio/sfx.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996  Alexis Janson\n * Copyright (C) 2004  Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2018-2023  Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Sound effects system- PLAY, SFX, and bkground code */\n\n#include <string.h>\n#include <stdint.h>\n#include <stdlib.h>\n\n#include \"audio.h\"\n#include \"audio_pcs.h\"\n#include \"audio_struct.h\"\n#include \"sfx.h\"\n\n#include \"../data.h\"\n#include \"../platform.h\"\n#include \"../world.h\"\n#include \"../world_format.h\"\n#include \"../world_struct.h\"\n#include \"../util.h\"\n\n#if defined(CONFIG_AUDIO) || defined(CONFIG_EDITOR)\n\n/**\n * The longest built-in SFX is the ring/potion sound, which is 68+1 chars long.\n *\n * This is also the legacy maximum SFX length. Custom SFX may potentially be\n * longer in the future, and/or SFX beyond the 50 built-in SFX may be possible.\n * Aside from potentially repurposing SFX 49, further built-ins are not likely.\n *\n * FIXME: make const\n * TODO: move the queue into the audio system and move the rest of this back to\n * the main source folder, as it is otherwise game behavior and file format.\n */\n__editor_maybe_static char sfx_strs[NUM_BUILTIN_SFX][LEGACY_SFX_SIZE] =\n{\n  \"5c-gec-gec\", // Gem\n  \"5c-gec-gec\", // Magic Gem\n  \"cge-zcge\", // Health\n  \"-c+c+c-g-g-g\", // Ammo\n  \"6a#a#zx\", // Coin\n  \"-cegeg+c-g+cecegeg+c\", // Life\n  \"-cc#dd#e\", // Lo Bomb\n  \"cc#dd#e\", // Hi Bomb\n  \"4gec-g+ec-ge+c-gec\", // Key\n  \"-gc#-a#a\", // Full Keys\n  \"ceg+c-eg+ce-g+ceg\", // Unlock\n  \"s1a#z+a#-a#x\", // Can't Unlock\n  \"-a-a+e-e+c-c\", // Invis. Wall\n  \"zax\", // Forest\n  \"0a#+a#-a#b+b-b\", // Gate Locked\n  \"0a+a-a+a-a\", // Opening Gate\n  \"-g+g-g+g+g-g+g-g-g+g-g-tg+g\", // Invinco Start\n  \"sc-cqxsg-g+a#-a#xx+g-g+a#-a#\", // Invinco Beat\n  \"sc-cqxsg-g+f-f+d#-d#+c-ca#-a#2c-c\", // Invinco End\n  \"0g+g-gd#+d#-d#\", // 19-Door locked\n  \"0g+gd#+d#\", // Door opening\n  \"c-zgd#c-gd#c\", // Hurt\n  \"a#e-a#e-a#e-a#e\", // AUGH!\n  \"+c-gd#cd#c-gd#gd#c-g+c-gd#cd#c-gd#gd#c\", // Death\n  \"ic1c+ca#+c1c+cx+d#1c+ca#+c1c+cx\"\n  \"+c1c+ca#+c1c+cx+d#1c+c+fc1c+cx\", // Game over\n  \"0c+c-c+c-c\", //25 - Gate closing\n  \"1d#x\", //26-Push\n  \"2c+c+c2d#+d#+d#2g+g+g3cd#g\", // 27-Transport\n  \"z+c-c\",// 28-shoot\n  \"1a+a+a\", // 29-break\n  \"-af#-f#a\", // 30-out of ammo\n  \"+f#\", // 31-ricochet\n  \"s-d-d-d\", // 32-out of bombs\n  \"c-aec-a\", // 33-place bomb (lo)\n  \"+c-aec-a\", // 34-place bomb (hi)\n  \"+g-ege-ege\", // 35-switch bomb type\n  \"1c+c0d#+d#-g+g-c#\", // 36-explosion\n  \"2cg+e+c2c#g#+f+c#2da+f#+d2d#a#+g+d#\", // 37-Entrance\n  \"cge+c-g+ecge+c-g+ec\", // 38-Pouch\n  \"zcd#gd#cd#gd#cd#gd#cd#gd#\"\n  \"cfg#fcfg#fcfg#fcfg#f\"\n  \"cga#gcga#gcga#gcga#g\"\n  \"s+c\",//39-ring/potion\n  \"z-a-a-aa-aa\", // 40-Empty chest\n  \"1c+c-c#+c#-d+d-d#+d#-e+e-ec\", // 41-Chest\n  \"c-gd#c-zd#gd#c\", // 42-Out of time\n  \"zc-d#g-cd#\", // 43-Fire ouch\n  \"cd#g+cd#g\", // 44-Stolen gem\n  \"z1d#+d#+d#\", // 45-Enemy HP down\n  \"z0ca#f+d#cf#\", // 46-Dragon fire\n  \"cg+c-eda+d-f#eb+e-g#\", // 47-Scroll/sign\n  \"1c+c+c+c+c\", // 48-Goop\n  \"\" // 49-Unused\n};\n\n#endif // CONFIG_AUDIO || CONFIG_EDITOR\n\n#ifdef CONFIG_AUDIO\n\n// Size of sound queue\n#define NOISEMAX        4096\n\n// Special freqs\n#define F_REST          1\n\n/**\n * The circular buffer here was originally designed to keep this queue safe\n * in DOS, where the dequeues occurred in an interrupt that had no chance\n * of running concurrently with the enqueues.\n *\n * In a parallel environment circular buffers are prone to very subtle bugs\n * without the use of atomics or locks. While normally that wouldn't cause\n * serious issues here, to make things worse both the main thread and audio\n * thread can cancel the entire SFX queue arbitrarily (and were previously\n * modifying the other thread's queue index where this occurred!).\n *\n * Just to be sure this isn't a problem going forward, the queue is now\n * protected with a mutex. The locked functions are very small so this\n * shouldn't cause much of a performance issue.\n */\n#ifndef DEBUG\n\n#define SFX_LOCK()   platform_mutex_lock(&audio.audio_sfx_mutex)\n#define SFX_UNLOCK() platform_mutex_unlock(&audio.audio_sfx_mutex)\n\n#else /* DEBUG */\n\n#include \"../thread_debug.h\"\n\nstatic platform_mutex_debug mutex_debug;\n\n#define SFX_LOCK()   platform_mutex_lock_debug(&audio.audio_sfx_mutex, \\\n                      &audio.audio_debug_mutex, &mutex_debug, __FILE__, __LINE__)\n#define SFX_UNLOCK() platform_mutex_unlock_debug(&audio.audio_sfx_mutex, \\\n                      &audio.audio_debug_mutex, &mutex_debug, __FILE__, __LINE__)\n\n#endif /* DEBUG */\n\nstatic int topindex = 0;  // Marks the top of the queue\nstatic int backindex = 0; // Marks bottom of queue\n\n/**\n * NOTE: these were signed 16-bit ints in DOS versions and signed 32-bit ints\n * up through 2.92e. It is highly unlikely anything ever relied on a duration\n * over 65535 (~131 seconds at 500 Hz).\n *\n * The frequency is guaranteed to never actually need anything higher than the\n * table below, so there's no reason for it to be 32-bit.\n */\nstruct noise\n{\n  uint16_t duration;\n  uint16_t freq;\n};\n\n// Frequencies of 6C thru 6B\nstatic const int note_freq[12] =\n { 2032, 2152, 2280, 2416, 2560, 2712, 2880, 3048, 3232, 3424, 3624, 3840 };\n\n// Sample frequencies of 0C thru 0B\nstatic const int sam_freq[12] =\n { 3424, 3232, 3048, 2880, 2712, 2560, 2416, 2280, 2152, 2032, 1920, 1812 };\n\nstatic struct noise background[NOISEMAX];\n\n/**\n * Allows the audio thread to determine whether the note held over from the\n * previous pcs_mix_data call should be cancelled.\n */\nstatic boolean cancel_current_note = false;\n\n#ifdef CONFIG_DEBYTECODE\nstatic const char open_char  = '(';\nstatic const char close_char = ')';\n#else\nstatic const char open_char  = '&';\nstatic const char close_char = '&';\n#endif\n\nstatic boolean sound_in_queue(void)\n{\n  return topindex != backindex;\n}\n\nstatic void submit_sound(int freq, int delay)\n{\n  int nextindex;\n  delay = CLAMP(delay, 0, UINT16_MAX);\n\n  SFX_LOCK();\n\n  nextindex = (topindex < NOISEMAX - 1) ? topindex + 1 : 0;\n  if(nextindex != backindex)\n  {\n    background[topindex].freq = freq;\n    background[topindex].duration = delay;\n    topindex = nextindex;\n  }\n\n  SFX_UNLOCK();\n}\n\nstatic void play_note(int note, int octave, int delay)\n{\n  // Note is # 1-12\n  // Octave #0-6\n  submit_sound(note_freq[note - 1] >> (6 - octave), delay);\n}\n\nvoid play_sfx(struct world *mzx_world, int sfxn)\n{\n  // TODO: pass the sfx_list here directly?\n  struct sfx_list *sfx_list = &mzx_world->custom_sfx;\n  if(sfx_list->list)\n  {\n    const struct custom_sfx *sfx = sfx_get(sfx_list, sfxn);\n    if(sfx)\n      play_string((char *)sfx->string, 1);\n  }\n  else\n\n  if(sfxn >= 0 && sfxn < NUM_BUILTIN_SFX)\n  {\n    play_string(sfx_strs[sfxn], 1);\n  }\n}\n\n// sfx_play = 1 to NOT play non-digi unless queue empty\n\nvoid play_string(char *str, int sfx_play)\n{\n  int t1, oct = 3, note = 1, dur = 18, t2, last_note = -1,\n   digi_st = -1, digi_end = -1, digi_played = 0;\n  char chr;\n  // Note trans. table from 1-7 (a-z) to 1-12\n  char nn[7] = { 10, 12, 1, 3, 5, 6, 8 };\n\n  /**\n   * There is a bug in the SFX code where every duration is unconditionally\n   * increased by 1 dating back to 2.51 or earlier. For compatibility, this\n   * is emulated, but at some point in the future it might be nice to have a\n   * world option to use the correct durations.\n   */\n  static const int extra_duration = 1;\n\n  if(!audio_get_pcs_on())\n  {\n    sfx_play = 1; // SFX off\n  }\n  else\n  {\n    if(!sound_in_queue())\n      sfx_play = 0;\n  }\n\n  // Now, if sfx_play, only play digi\n  for(t1 = 0; (unsigned int)t1 < strlen((char *)str); t1++)\n  {\n    chr = str[t1];\n    if((chr >= 'a') && (chr <= 'z'))\n      chr -= 32;\n\n    if(chr == '-')\n    {\n      // Octave down\n      if(oct > 0) oct--;\n      continue;\n    }\n    if(chr == '+')\n    {\n      // Octave up\n      if(oct < 6) oct++;\n      continue;\n    }\n    if(chr == 'X')\n    {\n      // Rest\n      if(!sfx_play && !(digi_st > 0))\n        submit_sound(F_REST, dur + extra_duration);\n\n      continue;\n    }\n    if((chr > 47) && (chr < 55))\n      oct = chr - 48; // Set octave\n\n    if(chr == '.')\n      dur += (dur >> 1); // Dot (duration 150%)\n\n    if(chr == '!')\n      dur /= 3; // Triplet (cut duration to 33%)\n\n    if((chr > 64) && (chr < 72))\n    {\n      // Note\n      note = nn[chr - 65]; // Convert to 1-12\n      t2 = oct; // Save old octave in case # or $ changes it\n      if(str[t1 + 1] == '#')\n      {\n        // Increase if sharp\n        t1++;\n        if(++note == 13)\n        {\n          // \"B#\" := \"+C-\"\n          note = 1;\n          if(++oct == 7) oct = 6;\n        }\n      }\n      else\n\n      if(str[t1 + 1] == '$')\n      {\n        // Decrease if flat\n        t1++;\n        if(--note == 0)\n        {\n          // \"C$\" := \"-B+\"\n          note = 12;\n          if(--oct == -1) oct = 0;\n        }\n      }\n      // Digi\n      if(digi_st > 0)\n      {\n        if(audio_get_music_on())\n        {\n          str[digi_end] = 0;\n\n          audio_play_sample(str + digi_st, true, sam_freq[note - 1] >> oct);\n\n          str[digi_end] = close_char;\n          digi_played = 1;\n          continue;\n        }\n      }\n      else\n\n      if(sfx_play)\n      {\n        continue;\n      }\n      else\n\n      if(last_note == note)\n      {\n        if(dur <= 9)\n        {\n          submit_sound(F_REST, 1 + extra_duration);\n          play_note(note, oct, dur - 1 + extra_duration);\n        }\n        else\n        {\n          submit_sound(F_REST, 2 + extra_duration);\n          play_note(note, oct, dur - 2 + extra_duration);\n        }\n      }\n      else\n      {\n        play_note(note, oct, dur + extra_duration);\n        last_note = note;\n      }\n      oct = t2; // Restore old octave\n    }\n\n    switch(chr)\n    {\n      case 'Z':\n        dur = 9;\n        break;\n\n      case 'T':\n        dur = 18;\n        break;\n\n      case 'S':\n        dur = 36;\n        break;\n\n      case 'I':\n        dur = 72;\n        break;\n\n      case 'Q':\n        dur = 144;\n        break;\n\n      case 'H':\n        dur = 288;\n        break;\n\n      case 'W':\n        dur = 576;\n        break;\n    }\n\n    if(chr == open_char)\n    {\n      // Digitized\n      t1++;\n      digi_st = t1;\n\n      if(str[t1] == 0)\n        break;\n\n      do\n      {\n        t1++;\n        if(str[t1] == 0)\n          break;\n\n        if(str[t1] == close_char)\n          break;\n\n      } while(1);\n\n      digi_end = t1;\n      digi_played = 0;\n    }\n\n    if(chr == '_')\n    {\n      if(audio_get_music_on())\n        break;\n\n      digi_st = -1;\n    }\n  }\n\n  // Pending digital music?\n  if((digi_st > 0) && (digi_played == 0))\n  {\n    str[digi_end] = 0;\n\n    audio_play_sample(str + digi_st, true, 0);\n\n    str[digi_end] = close_char;\n  }\n}\n\nvoid sfx_clear_queue(void)\n{\n  SFX_LOCK();\n\n  /**\n   * In DOS versions, clearing the SFX queue would also stop the current\n   * note. The best that can be done here is to alert the PC speaker stream\n   * to cancel the current playing sound the next time pcs_mix_data runs.\n   */\n  cancel_current_note = true;\n  topindex = 0;\n  backindex = 0;\n\n  SFX_UNLOCK();\n}\n\n// Called by audio_pcs.c under lock.\nvoid sfx_next_note(int *is_playing, int *freq, int *duration)\n{\n  int sfx_on = audio_get_pcs_on();\n\n  SFX_LOCK();\n\n  cancel_current_note = false;\n  if(sound_in_queue())\n  {\n    /**\n     * NOTE: originally, 1 was unconditionally added to the durations here.\n     * This is now done in play_string instead to keep things less messy.\n     */\n    if(background[backindex].freq == F_REST)\n    {\n      if(sfx_on)\n      {\n        *is_playing = false;\n        *duration = background[backindex].duration;\n      }\n    }\n    else\n\n    if(sfx_on)\n    {\n      // Start sound\n      *is_playing = true;\n      *freq = background[backindex].freq;\n      *duration = background[backindex].duration;\n    }\n\n    if(backindex < NOISEMAX - 1)\n      backindex++;\n    else\n      backindex = 0;\n\n    /**\n     * NOTE: originally, would reset topindex and backindex to 0 here if they\n     * were equal. There's not really much of a point to doing this.\n     */\n  }\n  else\n  {\n    if(sfx_on)\n    {\n      *is_playing = false;\n      *duration = 1;\n    }\n  }\n\n  SFX_UNLOCK();\n\n  // Silence the emulated PC speaker when sounds are disabled.\n  // NOTE: there was not a corresponding nosound() call here in DOS versions.\n  if(!sfx_on)\n  {\n    *is_playing = false;\n    *duration = 1;\n  }\n}\n\n// Called by audio_pcs.c under lock.\nboolean sfx_should_cancel_note(void)\n{\n  boolean ret;\n\n  SFX_LOCK();\n\n  ret = cancel_current_note;\n\n  SFX_UNLOCK();\n\n  return ret;\n}\n\nboolean sfx_is_playing(void)\n{\n  return sound_in_queue();\n}\n\nint sfx_length_left(void)\n{\n  int left = topindex - backindex;\n\n  if(left < 0)\n    left += NOISEMAX;\n\n  return left;\n}\n\n#endif // CONFIG_AUDIO\n\n#ifdef CONFIG_DEBYTECODE\n\nstatic void convert_sfx_strs(char *sfx_buf)\n{\n  char *start, *end = sfx_buf - 1, str_buf_len = strlen(sfx_buf);\n\n  while(true)\n  {\n    // no starting & was found\n    start = strchr(end + 1, '&');\n    if(!start || start - sfx_buf + 1 > str_buf_len)\n      break;\n\n    // no ending & was found\n    end = strchr(start + 1, '&');\n    if(!end || end - sfx_buf + 1 > str_buf_len)\n      break;\n\n    // Wipe out the &s to get a token\n    *start = '(';\n    *end = ')';\n  }\n}\n\nstatic void legacy_convert_sfx(struct sfx_list *sfx_list)\n{\n  int i;\n  for(i = 0; i < sfx_list->num_alloc; i++)\n  {\n    const struct custom_sfx *sfx = sfx_get(sfx_list, i);\n    if(sfx)\n      convert_sfx_strs((char *)sfx->string);\n  }\n}\n\n#endif /* CONFIG_DEBYTECODE */\n\nstatic size_t sfx_alloc_size(size_t src_len)\n{\n  return MAX(sizeof(struct custom_sfx),\n   offsetof(struct custom_sfx, string) + src_len + 1);\n}\n\nstatic boolean sfx_alloc_at_pos(struct sfx_list *sfx_list, int num,\n size_t src_len)\n{\n  struct custom_sfx **tmp;\n  struct custom_sfx *tmp2;\n  size_t sz;\n\n  if(!sfx_list->list || num >= sfx_list->num_alloc)\n  {\n    int num_alloc = 32;\n    int i;\n\n    for(i = 0; i < 16 && num >= num_alloc; i++)\n      num_alloc <<= 1;\n\n    num_alloc = MAX(num_alloc, NUM_BUILTIN_SFX);\n    tmp = (struct custom_sfx **)\n     crealloc(sfx_list->list, num_alloc * sizeof(struct custom_sfx *));\n    if(!tmp)\n      return false;\n\n    memset(tmp + sfx_list->num_alloc, 0,\n     sizeof(struct custom_sfx *) * (num_alloc - sfx_list->num_alloc));\n\n    sfx_list->list = (struct custom_sfx **)tmp;\n    sfx_list->num_alloc = num_alloc;\n  }\n\n  if(sfx_list->list[num] && src_len == 0)\n    return true;\n\n  sz = sfx_alloc_size(src_len);\n  tmp2 = (struct custom_sfx *)crealloc(sfx_list->list[num], sz);\n  if(!tmp2)\n    return false;\n\n  if(!sfx_list->list[num])\n  {\n    tmp2->string[0] = '\\0';\n    tmp2->label[0] = '\\0';\n  }\n\n  sfx_list->list[num] = tmp2;\n  return true;\n}\n\nconst struct custom_sfx *sfx_get(const struct sfx_list *sfx_list, int num)\n{\n  if(num < 0 || num >= sfx_list->num_alloc)\n    return NULL;\n\n  return sfx_list->list[num];\n}\n\nboolean sfx_set_string(struct sfx_list *sfx_list, int num,\n const char *src, size_t src_len)\n{\n  struct custom_sfx *sfx;\n\n  if(num < 0 || num >= MAX_NUM_SFX)\n    return false;\n\n  sfx = (struct custom_sfx *)sfx_get(sfx_list, num);\n  if(src_len == 0 && (!sfx || !sfx->label[0]))\n  {\n    sfx_unset(sfx_list, num);\n    return true;\n  }\n\n  if(!sfx_alloc_at_pos(sfx_list, num, src_len))\n    return false;\n\n  sfx = sfx_list->list[num];\n\n  src_len = MIN(src_len, MAX_SFX_SIZE - 1);\n  memcpy(sfx->string, src, src_len);\n  sfx->string[src_len] = '\\0';\n  return true;\n}\n\nboolean sfx_set_label(struct sfx_list *sfx_list, int num,\n const char *src, size_t src_len)\n{\n  struct custom_sfx *sfx;\n\n  if(num < 0 || num >= MAX_NUM_SFX)\n    return false;\n  if(!sfx_alloc_at_pos(sfx_list, num, 0))\n    return false;\n\n  sfx = sfx_list->list[num];\n  src_len = MIN(src_len, sizeof(sfx->label) - 1);\n  memcpy(sfx->label, src, src_len);\n  sfx->label[src_len] = '\\0';\n  return true;\n}\n\nvoid sfx_unset(struct sfx_list *custom_sfx, int num)\n{\n  if(num < 0 || num >= custom_sfx->num_alloc)\n    return;\n\n  free(custom_sfx->list[num]);\n  custom_sfx->list[num] = NULL;\n  return;\n}\n\nvoid sfx_free(struct sfx_list *custom_sfx)\n{\n  if(custom_sfx->list)\n  {\n    int i;\n    for(i = 0; i < custom_sfx->num_alloc; i++)\n      free(custom_sfx->list[i]);\n\n    free(custom_sfx->list);\n    custom_sfx->list = NULL;\n  }\n  custom_sfx->num_alloc = 0;\n}\n\n/**\n * Block-SFX style that was used for <=2.92X .SFX files and for worlds\n * saved between 2.90X and 2.92X.\n */\nstatic void save_sfx_array(const struct sfx_list *sfx_list,\n char custom_sfx[NUM_BUILTIN_SFX * LEGACY_SFX_SIZE])\n{\n  const struct custom_sfx *sfx;\n  char *dest;\n  size_t i;\n\n  memset(custom_sfx, 0, NUM_BUILTIN_SFX * LEGACY_SFX_SIZE);\n\n  // Ignore everything past the null terminator.\n  dest = custom_sfx;\n  for(i = 0; i < NUM_BUILTIN_SFX; i++, dest += LEGACY_SFX_SIZE)\n  {\n    sfx = sfx_get(sfx_list, i);\n    if(sfx)\n    {\n      size_t len = strlen(sfx->string);\n      len = MIN(len, LEGACY_SFX_SIZE - 1);\n      memcpy(dest, sfx->string, len);\n    }\n  }\n}\n\nstatic boolean load_sfx_array(struct sfx_list *sfx_list,\n const char custom_sfx[NUM_BUILTIN_SFX * LEGACY_SFX_SIZE])\n{\n  uint8_t lens[NUM_BUILTIN_SFX];\n  const char *src;\n  size_t i;\n  size_t j;\n\n  src = custom_sfx;\n  for(i = 0; i < NUM_BUILTIN_SFX; i++)\n  {\n    // Must contain a null terminator within the read block.\n    // It's possible for a malicious file to contain no terminator.\n    for(j = 0; j < LEGACY_SFX_SIZE; j++)\n      if(src[j] == '\\0')\n        break;\n\n    if(j >= LEGACY_SFX_SIZE)\n      return false;\n\n    lens[i] = j;\n    src += LEGACY_SFX_SIZE;\n  }\n\n  src = custom_sfx;\n  for(i = 0; i < NUM_BUILTIN_SFX; i++)\n  {\n    sfx_set_string(sfx_list, i, src, lens[i]);\n    src += LEGACY_SFX_SIZE;\n  }\n  return true;\n}\n\nstatic size_t save_sfx_properties(const struct sfx_list *sfx_list,\n int file_version, char *dest, size_t dest_len)\n{\n  struct memfile mf;\n  const struct custom_sfx *sfx;\n  size_t label_len;\n  size_t str_len;\n  int i;\n\n  // Bounding was already verified in `save_sfx_memory`.\n  mfopen_wr(dest, dest_len, &mf);\n\n  for(i = 0; i < sfx_list->num_alloc; i++)\n  {\n    sfx = sfx_list->list[i];\n    if(sfx && (sfx->label[0] || sfx->string[0]))\n    {\n      label_len = strlen(sfx->label);\n      str_len = strlen(sfx->string);\n      save_prop_c(SFXPROP_SET_ID, i, &mf);\n      if(str_len)\n        save_prop_a(SFXPROP_STRING, sfx->string, str_len, 1, &mf);\n      if(label_len)\n        save_prop_a(SFXPROP_LABEL, sfx->label, label_len, 1, &mf);\n    }\n  }\n  save_prop_eof(&mf);\n  return mftell(&mf);\n}\n\nstatic boolean load_sfx_properties(struct sfx_list *sfx_list,\n int file_version, const char *src, size_t src_len)\n{\n  struct memfile mf;\n  struct memfile prop;\n  int ident;\n  int len;\n  unsigned num = 0;\n\n  sfx_free(sfx_list);\n\n  mfopen(src, src_len, &mf);\n  if(!check_properties_file(&mf, SFXPROP_LABEL))\n    return false;\n\n  while(next_prop(&prop, &ident, &len, &mf))\n  {\n    switch(ident)\n    {\n      case SFXPROP_SET_ID:\n        num = load_prop_int(&prop);\n        break;\n\n      case SFXPROP_STRING:\n        sfx_set_string(sfx_list, num, (char *)prop.start, len);\n        break;\n\n      case SFXPROP_LABEL:\n        sfx_set_label(sfx_list, num, (char *)prop.start, len);\n        break;\n    }\n  }\n  return true;\n}\n\n/**\n * Save the world custom SFX to a memory buffer for file export.\n *\n * @param mzx_world     world data.\n * @param file_version  MegaZeux version for compatibility.\n *                      `V293`+ writes a properties file with header that can\n *                      be loaded by the specified MZX version and up; lower\n *                      writes the legacy fixed size array format.\n * @param dest          buffer to write SFX to.\n * @param dest_len      size of `dest`.\n * @param required      if non-`NULL`, the minimum buffer size is returned here.\n * @return              number of bytes written to `dest`, or 0 on failure.\n */\nsize_t sfx_save_to_memory(const struct sfx_list *sfx_list,\n int file_version, char *dest, size_t dest_len, size_t *required)\n{\n  if(!sfx_list->list)\n  {\n    if(required)\n      *required = 0;\n    return 0;\n  }\n\n  if(file_version >= V293)\n  {\n    const struct custom_sfx *sfx;\n    size_t size = 0;\n    int i;\n\n    for(i = 0; i < sfx_list->num_alloc; i++)\n    {\n      sfx = sfx_list->list[i];\n      if(sfx && (sfx->label[0] || sfx->string[0]))\n      {\n        size += PROP_HEADER_SIZE + 1;\n        if(sfx->label[0])\n          size += PROP_HEADER_SIZE + strlen(sfx->label);\n        if(sfx->string[0])\n          size += PROP_HEADER_SIZE + strlen(sfx->string);\n      }\n    }\n    size += 2 + 8; // eof + header\n\n    if(required)\n      *required = size;\n    if(dest_len < size || !dest)\n      return 0;\n\n    memcpy(dest, \"MZFX\\x1a\", 6);\n    dest[6] = file_version >> 8;\n    dest[7] = file_version & 0xff;\n\n    return save_sfx_properties(sfx_list, file_version, dest + 8, dest_len - 8) + 8;\n  }\n  else\n  {\n    if(required)\n      *required = NUM_BUILTIN_SFX * LEGACY_SFX_SIZE;\n    if(dest_len < NUM_BUILTIN_SFX * LEGACY_SFX_SIZE || !dest)\n      return 0;\n\n    save_sfx_array(sfx_list, dest);\n    return NUM_BUILTIN_SFX * LEGACY_SFX_SIZE;\n  }\n}\n\n/**\n * Import the world custom SFX from a memory buffer.\n *\n * @param mzx_world     world data.\n * @param file_version  MegaZeux version for compatibility.\n *                      `V293`+ allows loading either a legacy file or a\n *                      properties file, in which case the embedded header\n *                      will override this version; lower reads a legacy file.\n * @param src           buffer to read SFX data from.\n * @param src_len       length of `src` in bytes.\n * @return              `true` on success, otherwise `false`. The world data\n *                      will not be modified if `false` is returned.\n */\nboolean sfx_load_from_memory(struct sfx_list *sfx_list,\n int file_version, const char *src, size_t src_len)\n{\n  if(file_version >= V293 && src_len >= 8 && !memcmp(src, \"MZFX\\x1a\", 6))\n  {\n    // Currently make an attempt at loading any plausible future version file.\n    // Due to the nature of this format, a breaking change is unlikely.\n    file_version = ((uint8_t)src[6] << 8) | (uint8_t)src[7];\n    if(file_version < V293)\n      return false;\n\n    if(!load_sfx_properties(sfx_list, file_version, src + 8, src_len - 8))\n      return false;\n  }\n  else\n  {\n    if(src_len != NUM_BUILTIN_SFX * LEGACY_SFX_SIZE)\n      return false;\n\n    if(!load_sfx_array(sfx_list, src))\n      return false;\n  }\n\n#ifdef CONFIG_DEBYTECODE\n  if(file_version < VERSION_SOURCE)\n    legacy_convert_sfx(sfx_list);\n#endif\n\n  return true;\n}\n\n#ifdef CONFIG_EDITOR\n\nsize_t sfx_ram_usage(const struct sfx_list *sfx_list)\n{\n  const struct custom_sfx *sfx;\n  size_t total = 0;\n  int i;\n\n  if(sfx_list->list)\n  {\n    total = sfx_list->num_alloc * sizeof(struct custom_sfx *);\n\n    for(i = 0; i < sfx_list->num_alloc; i++)\n    {\n      sfx = sfx_list->list[i];\n      if(sfx)\n        total += sfx_alloc_size(strlen(sfx->string));\n    }\n  }\n  return total;\n}\n\n#endif /* CONFIG_EDITOR */\n"
  },
  {
    "path": "src/audio/sfx.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Prototypes for SFX.CPP */\n\n#ifndef __AUDIO_SFX_H\n#define __AUDIO_SFX_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n// Number of unique sound effects and length of sound effects\n// SFX sizes include the \\0 terminator.\n#define MAX_SFX_SIZE    256\n#define LEGACY_SFX_SIZE 69\n\nenum sfx_id\n{\n  SFX_GEM               = 0,\n  SFX_MAGIC_GEM         = 1,\n  SFX_HEALTH            = 2,\n  SFX_AMMO              = 3,\n  SFX_COIN              = 4,\n  SFX_LIFE              = 5,\n  SFX_LO_BOMB           = 6,\n  SFX_HI_BOMB           = 7,\n  SFX_KEY               = 8,\n  SFX_FULL_KEYS         = 9,\n  SFX_UNLOCK            = 10,\n  SFX_CANT_UNLOCK       = 11,\n  SFX_INVIS_WALL        = 12,\n  SFX_FOREST            = 13,\n  SFX_GATE_LOCKED       = 14,\n  SFX_OPEN_GATE         = 15,\n  SFX_INVINCO_START     = 16,\n  SFX_INVINCO_BEAT      = 17,\n  SFX_INVINCO_END       = 18,\n  SFX_DOOR_LOCKED       = 19,\n  SFX_OPEN_DOOR         = 20,\n  SFX_HURT              = 21,\n  SFX_LAVA              = 22,\n  SFX_DEATH             = 23,\n  SFX_GAME_OVER         = 24,\n  SFX_GATE_CLOSE        = 25,\n  SFX_PUSH              = 26,\n  SFX_TRANSPORT         = 27,\n  SFX_SHOOT             = 28,\n  SFX_BREAK             = 29,\n  SFX_OUT_OF_AMMO       = 30,\n  SFX_RICOCHET          = 31,\n  SFX_OUT_OF_BOMBS      = 32,\n  SFX_PLACE_LO_BOMB     = 33,\n  SFX_PLACE_HI_BOMB     = 34,\n  SFX_SWITCH_BOMB_TYPE  = 35,\n  SFX_EXPLOSION         = 36,\n  SFX_ENTRANCE          = 37,\n  SFX_POUCH             = 38,\n  SFX_RING_POTION       = 39,\n  SFX_EMPTY_CHEST       = 40,\n  SFX_CHEST             = 41,\n  SFX_OUT_OF_TIME       = 42,\n  SFX_FIRE_HURT         = 43,\n  SFX_STOLEN_GEM        = 44,\n  SFX_ENEMY_HP_DOWN     = 45,\n  SFX_DRAGON_FIRE       = 46,\n  SFX_SCROLL_SIGN       = 47,\n  SFX_GOOP              = 48,\n  SFX_UNUSED_49         = 49,\n  NUM_BUILTIN_SFX       = 50,\n  MAX_NUM_SFX           = 256\n};\n\nstruct custom_sfx\n{\n  char label[12];\n  char string[1]; // Flexible array member\n};\n\nstruct sfx_list\n{\n  struct custom_sfx **list;\n  int num_alloc;\n};\n\n// Requires struct custom_sfx, so don't include.\nstruct world;\n\n#ifdef CONFIG_EDITOR\nCORE_LIBSPEC extern char sfx_strs[NUM_BUILTIN_SFX][LEGACY_SFX_SIZE];\n\nCORE_LIBSPEC size_t sfx_ram_usage(const struct sfx_list *sfx_list);\n#endif // CONFIG_EDITOR\n\n#ifdef CONFIG_AUDIO\n\n// Called by audio_pcs.c under lock.\nvoid sfx_next_note(int *is_playing, int *freq, int *duration);\nboolean sfx_should_cancel_note(void);\n\nvoid play_sfx(struct world *mzx_world, int sfx);\nvoid play_string(char *str, int sfx_play);\nvoid sfx_clear_queue(void);\nboolean sfx_is_playing(void);\nint sfx_length_left(void);\n\n#else // !CONFIG_AUDIO\n\nstatic inline void play_sfx(struct world *mzx_world, int sfxn) {}\nstatic inline void play_string(char *str, int sfx_play) {}\nstatic inline void sfx_clear_queue(void) {}\nstatic inline boolean sfx_is_playing(void) { return false; }\nstatic inline int sfx_length_left(void) { return 0; }\n\n#endif // CONFIG_AUDIO\n\nCORE_LIBSPEC const struct custom_sfx *sfx_get(\n const struct sfx_list *sfx_list, int num);\nCORE_LIBSPEC boolean sfx_set_string(struct sfx_list *sfx_list, int num,\n const char *src, size_t src_len);\nCORE_LIBSPEC boolean sfx_set_label(struct sfx_list *sfx_list, int num,\n const char *src, size_t src_len);\nCORE_LIBSPEC void sfx_unset(struct sfx_list *sfx_list, int num);\nCORE_LIBSPEC void sfx_free(struct sfx_list *sfx_list);\nCORE_LIBSPEC size_t sfx_save_to_memory(const struct sfx_list *sfx_list,\n int file_version, char *dest, size_t dest_len, size_t *required);\nCORE_LIBSPEC boolean sfx_load_from_memory(struct sfx_list *sfx_list,\n int file_version, const char *src, size_t src_len);\n\n__M_END_DECLS\n\n#endif // __AUDIO_SFX_H\n"
  },
  {
    "path": "src/block.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2019 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"block.h\"\n\n#include \"data.h\"\n#include \"idarray.h\"\n#include \"idput.h\"\n#include \"robot.h\"\n#include \"world_struct.h\"\n\nstatic inline int duplicate_storage_object(struct world *mzx_world,\n struct board *dest_board, struct board *src_board, int src_id, int src_param)\n{\n  if(is_robot(src_id))\n  {\n    struct robot *src_robot = src_board->robot_list[src_param];\n    return duplicate_robot(mzx_world, dest_board, src_robot, 0, 0, true);\n  }\n  else\n\n  if(is_signscroll(src_id))\n  {\n    struct scroll *src_scroll = src_board->scroll_list[src_param];\n    return duplicate_scroll(dest_board, src_scroll);\n  }\n  else\n\n  if(src_id == SENSOR)\n  {\n    struct sensor *src_sensor = src_board->sensor_list[src_param];\n    return duplicate_sensor(dest_board, src_sensor);\n  }\n\n  return src_param;\n}\n\nstatic inline void copy_board_to_board_buffer(struct world *mzx_world,\n struct board *src_board, int src_offset, struct board *dest_board,\n int block_width, int block_height, char *buffer_id, char *buffer_color,\n char *buffer_param, char *buffer_under_id, char *buffer_under_color,\n char *buffer_under_param)\n{\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n  char *level_color = src_board->level_color;\n  char *level_under_id = src_board->level_under_id;\n  char *level_under_param = src_board->level_under_param;\n  char *level_under_color = src_board->level_under_color;\n\n  int src_width = src_board->board_width;\n  int src_skip = src_width - block_width;\n\n  int buffer_offset = 0;\n\n  enum thing src_id;\n  enum thing src_under_id;\n  int src_param;\n  int src_under_param;\n  int i, i2;\n\n  for(i = 0; i < block_height; i++)\n  {\n    for(i2 = 0; i2 < block_width; i2++)\n    {\n      src_id = (enum thing)level_id[src_offset];\n      src_param = level_param[src_offset];\n      src_under_id = (enum thing)level_under_id[src_offset];\n      src_under_param = level_under_param[src_offset];\n\n      // Storage objects need to be copied to the destination board\n      if(!is_storageless(src_id))\n        src_param = duplicate_storage_object(mzx_world, dest_board,\n         src_board, src_id, src_param);\n\n      // Storage objects can also exist under, unfortunately.\n      if(!is_storageless(src_under_id))\n        src_under_param = duplicate_storage_object(mzx_world, dest_board,\n         src_board, src_under_id, src_under_param);\n\n      // Copy to the buffer\n      if(src_param != -1)\n      {\n        buffer_id[buffer_offset] = src_id;\n        buffer_param[buffer_offset] = src_param;\n        buffer_color[buffer_offset] = level_color[src_offset];\n        buffer_under_id[buffer_offset] = src_under_id;\n        buffer_under_param[buffer_offset] = src_under_param;\n        buffer_under_color[buffer_offset] = level_under_color[src_offset];\n      }\n      else\n\n      // If the storage object failed to allocate, copy under data instead\n      if(src_under_param != -1)\n      {\n        buffer_id[buffer_offset] = src_under_id;\n        buffer_param[buffer_offset] = src_under_param;\n        buffer_color[buffer_offset] = level_under_color[src_offset];\n        buffer_under_id[buffer_offset] = SPACE;\n        buffer_under_param[buffer_offset] = 0;\n        buffer_under_color[buffer_offset] = 7;\n      }\n\n      // No under either? Just put a space...\n      else\n      {\n        buffer_id[buffer_offset] = SPACE;\n        buffer_param[buffer_offset] = 0;\n        buffer_color[buffer_offset] = 7;\n        buffer_under_id[buffer_offset] = SPACE;\n        buffer_under_param[buffer_offset] = 0;\n        buffer_under_color[buffer_offset] = 7;\n      }\n\n      src_offset++;\n      buffer_offset++;\n    }\n\n    src_offset += src_skip;\n  }\n}\n\nstatic void clear_storage_object(struct board *dest_board, int id, int param)\n{\n  if(id == SENSOR)\n  {\n    clear_sensor_id(dest_board, param);\n  }\n  else\n\n  if(is_signscroll(id))\n  {\n    clear_scroll_id(dest_board, param);\n  }\n  else\n\n  if(is_robot(id))\n  {\n    clear_robot_id(dest_board, param);\n  }\n}\n\nstatic inline void copy_board_buffer_to_board(\n struct board *dest_board, int dest_offset, int block_width, int block_height,\n char *buffer_id, char *buffer_color, char *buffer_param, char *buffer_under_id,\n char *buffer_under_color, char *buffer_under_param)\n{\n  char *level_id = dest_board->level_id;\n  char *level_param = dest_board->level_param;\n  char *level_color = dest_board->level_color;\n  char *level_under_id = dest_board->level_under_id;\n  char *level_under_param = dest_board->level_under_param;\n  char *level_under_color = dest_board->level_under_color;\n\n  int dest_width = dest_board->board_width;\n  int dest_skip = dest_width - block_width;\n\n  int buffer_offset = 0;\n\n  enum thing buffer_id_cur;\n  enum thing dest_id;\n  int dest_param;\n  int i, i2;\n\n  for(i = 0; i < block_height; i++)\n  {\n    for(i2 = 0; i2 < block_width; i2++)\n    {\n      dest_id = (enum thing)level_id[dest_offset];\n      buffer_id_cur = (enum thing)buffer_id[buffer_offset];\n\n      if(dest_id != PLAYER)\n      {\n        dest_param = level_param[dest_offset];\n\n        // Storage objects need to be deleted from the destination board\n        if(!is_storageless(dest_id))\n          clear_storage_object(dest_board, dest_id, dest_param);\n\n        if(is_robot(buffer_id_cur))\n        {\n          struct robot *cur_robot =\n           dest_board->robot_list[(int)buffer_param[buffer_offset]];\n\n          int thisx = dest_offset % dest_width;\n          int thisy = dest_offset / dest_width;\n          cur_robot->xpos = thisx;\n          cur_robot->ypos = thisy;\n          cur_robot->compat_xpos = thisx;\n          cur_robot->compat_ypos = thisy;\n        }\n\n        // Copy off of the buffer onto the board\n        if(buffer_id_cur != PLAYER)\n        {\n          level_id[dest_offset] = buffer_id_cur;\n          level_param[dest_offset] = buffer_param[buffer_offset];\n          level_color[dest_offset] = buffer_color[buffer_offset];\n          level_under_id[dest_offset] = buffer_under_id[buffer_offset];\n          level_under_param[dest_offset] = buffer_under_param[buffer_offset];\n          level_under_color[dest_offset] = buffer_under_color[buffer_offset];\n        }\n\n        // Unless the buffer contains a player, in which case, copy under data\n        else\n        {\n          level_id[dest_offset] = buffer_under_id[buffer_offset];\n          level_param[dest_offset] = buffer_under_param[buffer_offset];\n          level_color[dest_offset] = buffer_under_color[buffer_offset];\n        }\n      }\n\n      else\n      {\n        // This can't be placed on top of the player. Free any storage\n        // objects so this isn't leaking storage object IDs.\n        if(!is_storageless(buffer_id_cur))\n        {\n          clear_storage_object(dest_board, buffer_id_cur,\n           buffer_param[buffer_offset]);\n        }\n\n        // Also make sure there isn't a storage object under in the extremely\n        // unlikely event one got there.\n        if(!is_storageless(buffer_under_id[buffer_offset]))\n        {\n          clear_storage_object(dest_board, buffer_under_id[buffer_offset],\n           buffer_under_param[buffer_offset]);\n        }\n      }\n\n      dest_offset++;\n      buffer_offset++;\n    }\n\n    dest_offset += dest_skip;\n  }\n}\n\nvoid copy_board_to_board(struct world *mzx_world,\n struct board *src_board, int src_offset,\n struct board *dest_board, int dest_offset,\n int block_width, int block_height)\n{\n  // While we could not use buffering if the boards are different, this\n  // is a bit more complex than layer copying, so use buffers anyway.\n  // The different boards case only affects the editor anyway.\n\n  char *buffer_id = cmalloc(block_width * block_height);\n  char *buffer_color = cmalloc(block_width * block_height);\n  char *buffer_param = cmalloc(block_width * block_height);\n  char *buffer_under_id = cmalloc(block_width * block_height);\n  char *buffer_under_color = cmalloc(block_width * block_height);\n  char *buffer_under_param = cmalloc(block_width * block_height);\n\n  copy_board_to_board_buffer(mzx_world,\n   src_board, src_offset, dest_board, block_width, block_height,\n   buffer_id, buffer_color, buffer_param, buffer_under_id,\n   buffer_under_color, buffer_under_param);\n\n  copy_board_buffer_to_board(\n   dest_board, dest_offset, block_width, block_height,\n   buffer_id, buffer_color, buffer_param, buffer_under_id,\n   buffer_under_color, buffer_under_param);\n\n  free(buffer_id);\n  free(buffer_color);\n  free(buffer_param);\n  free(buffer_under_id);\n  free(buffer_under_color);\n  free(buffer_under_param);\n}\n\nstatic void copy_layer_to_layer_buffered(\n char *src_char, char *src_color, int src_width, int src_offset,\n char *dest_char, char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height)\n{\n  char *buffer_char = cmalloc(block_width * block_height);\n  char *buffer_color = cmalloc(block_width * block_height);\n  int buffer_offset = 0;\n\n  int src_skip = src_width - block_width;\n  int dest_skip = dest_width - block_width;\n\n  // Copying to the same layer; don't worry about char 32\n  int i, i2;\n\n  // Copy layer to buffer\n  for(i = 0; i < block_height; i++)\n  {\n    for(i2 = 0; i2 < block_width; i2++)\n    {\n      buffer_char[buffer_offset] = src_char[src_offset];\n      buffer_color[buffer_offset] = src_color[src_offset];\n\n      src_offset++;\n      buffer_offset++;\n    }\n\n    src_offset += src_skip;\n  }\n\n  buffer_offset = 0;\n\n  // Copy buffer back to layer\n  for(i = 0; i < block_height; i++)\n  {\n    for(i2 = 0; i2 < block_width; i2++)\n    {\n      dest_char[dest_offset] = buffer_char[buffer_offset];\n      dest_color[dest_offset] = buffer_color[buffer_offset];\n\n      dest_offset++;\n      buffer_offset++;\n    }\n\n    dest_offset += dest_skip;\n  }\n\n  free(buffer_char);\n  free(buffer_color);\n}\n\nstatic void copy_layer_to_layer_direct(\n char *src_char, char *src_color, int src_width, int src_offset,\n char *dest_char, char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height)\n{\n  int src_skip = src_width - block_width;\n  int dest_skip = dest_width - block_width;\n\n  // Copying to a different layer; track char 32s\n  int src_char_cur;\n  int i, i2;\n\n  for(i = 0; i < block_height; i++)\n  {\n    for(i2 = 0; i2 < block_width; i2++)\n    {\n      src_char_cur = src_char[src_offset];\n      if(src_char_cur != 32)\n      {\n        dest_char[dest_offset] = src_char_cur;\n        dest_color[dest_offset] = src_color[src_offset];\n      }\n      src_offset++;\n      dest_offset++;\n    }\n    src_offset += src_skip;\n    dest_offset += dest_skip;\n  }\n}\n\nvoid copy_layer_to_layer(\n char *src_char, char *src_color, int src_width, int src_offset,\n char *dest_char, char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height)\n{\n  // Same pointer? Use a buffer\n  if(src_char == dest_char || src_color == dest_color)\n  {\n    copy_layer_to_layer_buffered(\n     src_char, src_color, src_width, src_offset,\n     dest_char, dest_color, dest_width, dest_offset,\n     block_width, block_height);\n  }\n  // Otherwise, it's safe to just copy direct\n  else\n  {\n    copy_layer_to_layer_direct(\n     src_char, src_color, src_width, src_offset,\n     dest_char, dest_color, dest_width, dest_offset,\n     block_width, block_height);\n  }\n}\n\nvoid copy_board_to_layer(\n struct board *src_board, int src_offset,\n char *dest_char, char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height)\n{\n  int src_width = src_board->board_width;\n\n  int src_skip = src_width - block_width;\n  int dest_skip = dest_width - block_width;\n\n  int src_char;\n  int i, i2;\n\n  for(i = 0; i < block_height; i++)\n  {\n    for(i2 = 0; i2 < block_width; i2++)\n    {\n      src_char = get_id_char(src_board, src_offset);\n      if(src_char != 32)\n      {\n        dest_char[dest_offset] = src_char;\n        dest_color[dest_offset] = get_id_color(src_board, src_offset);\n      }\n      src_offset++;\n      dest_offset++;\n    }\n    src_offset += src_skip;\n    dest_offset += dest_skip;\n  }\n}\n\nvoid copy_layer_to_board(\n char *src_char, char *src_color, int src_width, int src_offset,\n struct board *dest_board, int dest_offset,\n int block_width, int block_height, enum thing convert_id)\n{\n  char *level_id = dest_board->level_id;\n  char *level_param = dest_board->level_param;\n  char *level_color = dest_board->level_color;\n\n  int dest_width = dest_board->board_width;\n\n  int src_skip = src_width - block_width;\n  int dest_skip = dest_width - block_width;\n\n  enum thing dest_id;\n  char src_char_cur;\n  int i, i2;\n\n  for(i = 0; i < block_height; i++)\n  {\n    for(i2 = 0; i2 < block_width; i2++)\n    {\n      src_char_cur = src_char[src_offset];\n      dest_id = level_id[dest_offset];\n\n      if(src_char_cur != 32 && dest_id != PLAYER)\n      {\n        // Storage objects need to be deleted from the destination board\n        if(!is_storageless(dest_id))\n          clear_storage_object(dest_board, dest_id, level_param[dest_offset]);\n\n        level_id[dest_offset] = (char)convert_id;\n        level_param[dest_offset] = src_char_cur;\n        level_color[dest_offset] = src_color[src_offset];\n      }\n      src_offset++;\n      dest_offset++;\n    }\n    src_offset += src_skip;\n    dest_offset += dest_skip;\n  }\n}\n\n#ifdef CONFIG_EDITOR\n\n// Place the player on the board. Intended for editor operations that move the\n// player but need to preserve the thing under the player. Only use after the\n// old player has been removed; THIS FUNCTION WILL NOT REMOVE IT.\nvoid copy_replace_player(struct world *mzx_world, int x, int y)\n{\n  struct board *cur_board = mzx_world->current_board;\n  enum thing src_id;\n  int offset;\n\n  if(x >= cur_board->board_width)\n    x = cur_board->board_width - 1;\n\n  if(y >= cur_board->board_height)\n    y = cur_board->board_height - 1;\n\n  offset = x + (y * cur_board->board_width);\n\n  src_id = (enum thing)cur_board->level_id[offset];\n  if(is_robot(src_id) || is_signscroll(src_id))\n    clear_storage_object(cur_board, src_id, cur_board->level_param[offset]);\n\n  id_place(mzx_world, x, y, PLAYER, 0, 0);\n  mzx_world->player_x = x;\n  mzx_world->player_y = y;\n}\n\n// This goes here so the block buffer monstrosity can be inlined.\nvoid move_board_block(struct world *mzx_world,\n struct board *src_board, int src_x, int src_y,\n struct board *dest_board, int dest_x, int dest_y,\n int block_width, int block_height,\n int clear_width, int clear_height)\n{\n  char *buffer_id = cmalloc(block_width * block_height);\n  char *buffer_color = cmalloc(block_width * block_height);\n  char *buffer_param = cmalloc(block_width * block_height);\n  char *buffer_under_id = cmalloc(block_width * block_height);\n  char *buffer_under_color = cmalloc(block_width * block_height);\n  char *buffer_under_param = cmalloc(block_width * block_height);\n\n  // The source board needs to be cleared, so set that up too.\n  // This is pretty much copied from clear_board_block, but I think\n  // that's a fair sacrifice.\n\n  // The section of the source board that gets cleared can also be\n  // larger than the block being copied due to bounds clipping.\n\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n  char *level_color = src_board->level_color;\n  char *level_under_id = src_board->level_under_id;\n  char *level_under_param = src_board->level_under_param;\n  char *level_under_color = src_board->level_under_color;\n\n  int src_width = src_board->board_width;\n  int src_skip = src_width - clear_width;\n\n  enum thing src_id;\n  int src_param;\n  int i, i2;\n\n  int src_offset = src_x + (src_y * src_width);\n  int dest_offset = dest_x + (dest_y * dest_board->board_width);\n\n  boolean replace_player = false;\n  int player_x = 0;\n  int player_y = 0;\n\n  // Work around to move the player\n  if((mzx_world->player_x >= src_x) &&\n   (mzx_world->player_y >= src_y) &&\n   (mzx_world->player_x < (src_x + clear_width)) &&\n   (mzx_world->player_y < (src_y + clear_height)) &&\n   (src_board == dest_board))\n  {\n    player_x = mzx_world->player_x - src_x + dest_x;\n    player_y = mzx_world->player_y - src_y + dest_y;\n    replace_player = true;\n\n    id_remove_top(mzx_world, mzx_world->player_x, mzx_world->player_y);\n  }\n\n  copy_board_to_board_buffer(mzx_world,\n   src_board, src_offset, dest_board, block_width, block_height,\n   buffer_id, buffer_color, buffer_param, buffer_under_id,\n   buffer_under_color, buffer_under_param);\n\n  for(i = 0; i < clear_height; i++)\n  {\n    for(i2 = 0; i2 < clear_width; i2++)\n    {\n      src_id = (enum thing)level_id[src_offset];\n      if(src_id != PLAYER)\n      {\n        src_param = level_param[src_offset];\n\n        if(!is_storageless(src_id))\n          clear_storage_object(src_board, src_id, src_param);\n\n        level_id[src_offset] = (char)SPACE;\n        level_param[src_offset] = 0;\n        level_color[src_offset] = 7;\n      }\n      else\n\n      if(!is_storageless(level_under_id[src_offset]))\n        clear_storage_object(src_board, level_under_id[src_offset],\n         level_under_param[src_offset]);\n\n      level_under_id[src_offset] = (char)SPACE;\n      level_under_param[src_offset] = 0;\n      level_under_color[src_offset] = 7;\n\n      src_offset++;\n    }\n\n    src_offset += src_skip;\n  }\n\n  copy_board_buffer_to_board(\n   dest_board, dest_offset, block_width, block_height,\n   buffer_id, buffer_color, buffer_param, buffer_under_id,\n   buffer_under_color, buffer_under_param);\n\n  if(replace_player)\n    copy_replace_player(mzx_world, player_x, player_y);\n\n  free(buffer_id);\n  free(buffer_color);\n  free(buffer_param);\n  free(buffer_under_id);\n  free(buffer_under_color);\n  free(buffer_under_param);\n}\n\n#endif //CONFIG_EDITOR\n"
  },
  {
    "path": "src/block.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2019 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __BLOCK_H\n#define __BLOCK_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"data.h\"\n#include \"world_struct.h\"\n\nCORE_LIBSPEC void copy_board_to_board(struct world *mzx_world,\n struct board *src_board, int src_offset,\n struct board *dest_board, int dest_offset,\n int block_width, int block_height);\n\nCORE_LIBSPEC void copy_layer_to_layer(\n char *src_char, char *src_color, int src_width, int src_offset,\n char *dest_char, char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height);\n\nCORE_LIBSPEC void copy_board_to_layer(\n struct board *src_board, int src_offset,\n char *dest_char, char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height);\n\nCORE_LIBSPEC void copy_layer_to_board(\n char *src_char, char *src_color, int src_width, int src_offset,\n struct board *dest_board, int dest_offset,\n int block_width, int block_height, enum thing convert_id);\n\n#ifdef CONFIG_EDITOR\n\nCORE_LIBSPEC void copy_replace_player(struct world *mzx_world, int x, int y);\n\nCORE_LIBSPEC void move_board_block(struct world *mzx_world,\n struct board *src_board, int src_x, int src_y,\n struct board *dest_board, int dest_x, int dest_y,\n int block_width, int block_height,\n int clear_width, int clear_height);\n\n#endif //CONFIG_EDITOR\n\n__M_END_DECLS\n\n#endif // __BLOCK_H\n"
  },
  {
    "path": "src/board.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"board.h\"\n#include \"const.h\"\n#include \"error.h\"\n#include \"legacy_board.h\"\n#include \"robot.h\"\n#include \"world.h\"\n#include \"world_format.h\"\n#include \"util.h\"\n#include \"world_struct.h\"\n#include \"io/memfile.h\"\n#include \"io/zip.h\"\n\n\nstatic int save_board_info(struct board *cur_board, struct zip_archive *zp,\n int savegame, int file_version, int world_version, const char *name)\n{\n  const char *tmp;\n  char *buffer;\n  struct memfile mf;\n  int result;\n\n  size_t size = BOARD_PROPS_SIZE;\n\n  if(savegame)\n    size += BOARD_SAVE_PROPS_SIZE;\n\n  buffer = cmalloc(size);\n\n  mfopen_wr(buffer, size, &mf);\n\n  save_prop_s_293(BPROP_BOARD_NAME, cur_board->board_name, BOARD_NAME_SIZE, &mf);\n  save_prop_w(BPROP_BOARD_WIDTH, cur_board->board_width, &mf);\n  save_prop_w(BPROP_BOARD_HEIGHT, cur_board->board_height, &mf);\n  save_prop_c(BPROP_OVERLAY_MODE, cur_board->overlay_mode, &mf);\n  save_prop_c(BPROP_NUM_ROBOTS, cur_board->num_robots, &mf);\n  save_prop_c(BPROP_NUM_SCROLLS, cur_board->num_scrolls, &mf);\n  save_prop_c(BPROP_NUM_SENSORS, cur_board->num_sensors, &mf);\n  save_prop_w(BPROP_FILE_VERSION, file_version, &mf);\n\n  save_prop_s(BPROP_MOD_PLAYING, cur_board->mod_playing, &mf);\n  save_prop_c(BPROP_VIEWPORT_X, cur_board->viewport_x, &mf);\n  save_prop_c(BPROP_VIEWPORT_Y, cur_board->viewport_y, &mf);\n  save_prop_c(BPROP_VIEWPORT_WIDTH, cur_board->viewport_width, &mf);\n  save_prop_c(BPROP_VIEWPORT_HEIGHT, cur_board->viewport_height, &mf);\n  save_prop_c(BPROP_CAN_SHOOT, cur_board->can_shoot, &mf);\n  save_prop_c(BPROP_CAN_BOMB, cur_board->can_bomb, &mf);\n  save_prop_c(BPROP_FIRE_BURN_BROWN, cur_board->fire_burn_brown, &mf);\n  save_prop_c(BPROP_FIRE_BURN_SPACE, cur_board->fire_burn_space, &mf);\n  save_prop_c(BPROP_FIRE_BURN_FAKES, cur_board->fire_burn_fakes, &mf);\n  save_prop_c(BPROP_FIRE_BURN_TREES, cur_board->fire_burn_trees, &mf);\n  save_prop_c(BPROP_EXPLOSIONS_LEAVE, cur_board->explosions_leave, &mf);\n  save_prop_c(BPROP_SAVE_MODE, cur_board->save_mode, &mf);\n  save_prop_c(BPROP_FOREST_BECOMES, cur_board->forest_becomes, &mf);\n  save_prop_c(BPROP_COLLECT_BOMBS, cur_board->collect_bombs, &mf);\n  save_prop_c(BPROP_FIRE_BURNS, cur_board->fire_burns, &mf);\n  save_prop_c(BPROP_BOARD_N, cur_board->board_dir[0], &mf);\n  save_prop_c(BPROP_BOARD_S, cur_board->board_dir[1], &mf);\n  save_prop_c(BPROP_BOARD_E, cur_board->board_dir[2], &mf);\n  save_prop_c(BPROP_BOARD_W, cur_board->board_dir[3], &mf);\n  save_prop_c(BPROP_RESTART_IF_ZAPPED, cur_board->restart_if_zapped, &mf);\n  save_prop_w(BPROP_TIME_LIMIT, cur_board->time_limit, &mf);\n  save_prop_c(BPROP_PLAYER_NS_LOCKED, cur_board->player_ns_locked, &mf);\n  save_prop_c(BPROP_PLAYER_EW_LOCKED, cur_board->player_ew_locked, &mf);\n  save_prop_c(BPROP_PLAYER_ATTACK_LOCKED, cur_board->player_attack_locked, &mf);\n  save_prop_c(BPROP_RESET_ON_ENTRY, cur_board->reset_on_entry, &mf);\n\n  tmp = cur_board->charset_path ? cur_board->charset_path : \"\";\n  save_prop_s(BPROP_CHARSET_PATH, tmp, &mf);\n\n  tmp = cur_board->palette_path ? cur_board->palette_path : \"\";\n  save_prop_s(BPROP_PALETTE_PATH, tmp, &mf);\n\n  if(world_version >= V293)\n  {\n    save_prop_c(BPROP_RESET_ON_ENTRY_SAME_BOARD,\n     cur_board->reset_on_entry_same_board, &mf);\n    save_prop_c(BPROP_DRAGONS_CAN_RANDOMLY_MOVE,\n     cur_board->dragons_can_randomly_move, &mf);\n  }\n\n  if(savegame)\n  {\n    save_prop_w(BPROP_SCROLL_X, cur_board->scroll_x, &mf);\n    save_prop_w(BPROP_SCROLL_Y, cur_board->scroll_y, &mf);\n    save_prop_w(BPROP_LOCKED_X, cur_board->locked_x, &mf);\n    save_prop_w(BPROP_LOCKED_Y, cur_board->locked_y, &mf);\n    save_prop_c(BPROP_PLAYER_LAST_DIR, cur_board->player_last_dir, &mf);\n    save_prop_c(BPROP_LAZWALL_START, cur_board->lazwall_start, &mf);\n    save_prop_c(BPROP_LAST_KEY, cur_board->last_key, &mf);\n    save_prop_d(BPROP_NUM_INPUT, cur_board->num_input, &mf);\n    save_prop_d(BPROP_INPUT_SIZE, cur_board->input_size, &mf);\n\n    tmp = cur_board->input_string ? cur_board->input_string : \"\";\n    save_prop_s(BPROP_INPUT_STRING, tmp, &mf);\n\n    save_prop_s(BRPOP_BOTTOM_MESG, cur_board->bottom_mesg, &mf);\n    save_prop_c(BPROP_BOTTOM_MESG_TIMER, cur_board->b_mesg_timer, &mf);\n    save_prop_c(BPROP_BOTTOM_MESG_ROW, cur_board->b_mesg_row, &mf);\n    save_prop_c(BPROP_BOTTOM_MESG_COL, cur_board->b_mesg_col, &mf);\n    save_prop_c(BPROP_VOLUME, cur_board->volume, &mf);\n    save_prop_c(BPROP_VOLUME_INC, cur_board->volume_inc, &mf);\n    save_prop_c(BPROP_VOLUME_TARGET, cur_board->volume_target, &mf);\n\n    // Don't bother saving these compatibility fields unless they're needed.\n    if(file_version >= V293 && world_version < V200)\n    {\n      save_prop_c(BPROP_BLIND_DUR, cur_board->blind_dur_v1, &mf);\n      save_prop_c(BPROP_FIREWALKER_DUR, cur_board->firewalker_dur_v1, &mf);\n      save_prop_c(BPROP_FREEZE_TIME_DUR, cur_board->freeze_time_dur_v1, &mf);\n      save_prop_c(BPROP_SLOW_TIME_DUR, cur_board->slow_time_dur_v1, &mf);\n      save_prop_c(BPROP_WIND_DUR, cur_board->wind_dur_v1, &mf);\n    }\n  }\n\n  save_prop_eof(&mf);\n\n  size = mftell(&mf);\n\n  result = zip_write_file(zp, name, buffer, size, ZIP_M_NONE);\n\n  free(buffer);\n  return result;\n}\n\nint save_board(struct world *mzx_world, struct board *cur_board,\n struct zip_archive *zp, int savegame, int file_version, int board_id)\n{\n  int board_size = cur_board->board_width * cur_board->board_height;\n  char name[10];\n  int count;\n  int i;\n\n  sprintf(name, \"b%2.2X\", (unsigned char)board_id);\n\n  if(save_board_info(cur_board, zp, savegame, file_version, mzx_world->version, name))\n    goto err;\n\n  sprintf(name+3, \"bid\");\n  if(zip_write_file(zp, name, cur_board->level_id,\n   board_size, ZIP_M_DEFLATE))\n    goto err;\n\n  sprintf(name+3, \"bpr\");\n  if(zip_write_file(zp, name, cur_board->level_param,\n   board_size, ZIP_M_DEFLATE))\n    goto err;\n\n  sprintf(name+3, \"bco\");\n  if(zip_write_file(zp, name, cur_board->level_color,\n   board_size, ZIP_M_DEFLATE))\n    goto err;\n\n  sprintf(name+3, \"uid\");\n  if(zip_write_file(zp, name, cur_board->level_under_id,\n   board_size, ZIP_M_DEFLATE))\n    goto err;\n\n  sprintf(name+3, \"upr\");\n  if(zip_write_file(zp, name, cur_board->level_under_param,\n   board_size, ZIP_M_DEFLATE))\n    goto err;\n\n  sprintf(name+3, \"uco\");\n  if(zip_write_file(zp, name, cur_board->level_under_color,\n   board_size, ZIP_M_DEFLATE))\n    goto err;\n\n  if(cur_board->overlay_mode)\n  {\n    sprintf(name+3, \"och\");\n    if(zip_write_file(zp, name, cur_board->overlay,\n     board_size, ZIP_M_DEFLATE))\n      goto err;\n\n    sprintf(name+3, \"oco\");\n    if(zip_write_file(zp, name, cur_board->overlay_color,\n     board_size, ZIP_M_DEFLATE))\n      goto err;\n  }\n\n  name[3] = 'r';\n  count = cur_board->num_robots;\n  if(count)\n  {\n    struct robot *cur_robot;\n\n    for(i = 1; i <= count; i++)\n    {\n      cur_robot = cur_board->robot_list[i];\n      if(cur_robot)\n      {\n        sprintf(name+4, \"%2.2X\", (unsigned char) i);\n        save_robot(mzx_world, cur_robot, zp, savegame, file_version, name);\n      }\n    }\n  }\n\n  sprintf(name+3, \"sc\");\n  count = cur_board->num_scrolls;\n  if(count)\n  {\n    struct scroll *cur_scroll;\n\n    for(i = 1; i <= count; i++)\n    {\n      cur_scroll = cur_board->scroll_list[i];\n      if(cur_scroll)\n      {\n        sprintf(name+5, \"%2.2X\", (unsigned char) i);\n        save_scroll(cur_scroll, zp, file_version, name);\n      }\n    }\n  }\n\n  sprintf(name+3, \"se\");\n  count = cur_board->num_sensors;\n  if(count)\n  {\n    struct sensor *cur_sensor;\n\n    for(i = 1; i <= count; i++)\n    {\n      cur_sensor = cur_board->sensor_list[i];\n      if(cur_sensor)\n      {\n        sprintf(name+5, \"%2.2X\", (unsigned char) i);\n        save_sensor(cur_sensor, zp, file_version, name);\n      }\n    }\n  }\n\n  return 0;\n\nerr:\n  return -1;\n}\n\nvoid default_board_settings(struct world *mzx_world, struct board *cur_board)\n{\n  cur_board->mod_playing[0] = 0;\n  cur_board->viewport_x = 0;\n  cur_board->viewport_y = 0;\n  cur_board->viewport_width = 80;\n  cur_board->viewport_height = 25;\n  cur_board->can_shoot = 0;\n  cur_board->can_bomb = 0;\n  cur_board->fire_burn_brown = 0;\n  cur_board->fire_burn_space = 0;\n  cur_board->fire_burn_fakes = 0;\n  cur_board->fire_burn_trees = 0;\n  cur_board->explosions_leave = 0;\n  cur_board->save_mode = 1;\n  cur_board->forest_becomes = 0;\n  cur_board->collect_bombs = 0;\n  cur_board->fire_burns = 0;\n  cur_board->dragons_can_randomly_move =\n   (mzx_world->version < VERSION_PORT || mzx_world->version >= V293) ? true : false;\n  cur_board->spittingtiger_moves =\n   (mzx_world->version >= V294)         ? SPITTINGTIGER_MOVES_SLOWLY :\n   (mzx_world->version >= VERSION_PORT) ? SPITTINGTIGER_MOVES_SLOWLY_SELF_DESTRUCTS\n                                        : SPITTINGTIGER_MOVES_NORMALLY;\n  cur_board->board_dir[0] = NO_BOARD;\n  cur_board->board_dir[1] = NO_BOARD;\n  cur_board->board_dir[2] = NO_BOARD;\n  cur_board->board_dir[3] = NO_BOARD;\n  cur_board->restart_if_zapped = 0;\n  cur_board->reset_on_entry = 0;\n  cur_board->reset_on_entry_same_board = (mzx_world->version >= V293);\n  cur_board->time_limit = 0;\n  cur_board->charset_path = NULL;\n  cur_board->palette_path = NULL;\n  cur_board->charset_path_allocated = 0;\n  cur_board->palette_path_allocated = 0;\n\n  cur_board->scroll_x = 0;\n  cur_board->scroll_y = 0;\n  cur_board->locked_x = -1;\n  cur_board->locked_y = -1;\n\n  cur_board->player_last_dir = 0x10;\n  cur_board->player_ns_locked = 0;\n  cur_board->player_ew_locked = 0;\n  cur_board->player_attack_locked = 0;\n\n  cur_board->volume = 255;\n  cur_board->volume_inc = 0;\n  cur_board->volume_target = 255;\n\n  cur_board->lazwall_start = 7;\n  cur_board->last_key = '?';\n  cur_board->num_input = 0;\n  cur_board->input_size = 0;\n  cur_board->input_allocated = 0;\n  cur_board->input_string = NULL;\n  cur_board->bottom_mesg[0] = 0;\n  cur_board->b_mesg_timer = 0;\n  cur_board->b_mesg_row = 24;\n  cur_board->b_mesg_col = -1;\n  cur_board->blind_dur_v1 = 0;\n  cur_board->firewalker_dur_v1 = 0;\n  cur_board->freeze_time_dur_v1 = 0;\n  cur_board->slow_time_dur_v1 = 0;\n  cur_board->wind_dur_v1 = 0;\n\n#if defined(DEBUG) || defined(CONFIG_EXTRAM)\n  cur_board->is_extram = false;\n#endif\n}\n\nvoid dummy_board(struct world *mzx_world, struct board *cur_board)\n{\n  // Allocate placeholder data for broken boards so they will run\n  int size = 2000;\n  cur_board->overlay_mode = OVERLAY_OFF;\n  cur_board->board_width = 80;\n  cur_board->board_height = 25;\n\n  default_board_settings(mzx_world, cur_board);\n\n  cur_board->level_id = ccalloc(1, size);\n  cur_board->level_color = ccalloc(1, size);\n  cur_board->level_param = ccalloc(1, size);\n  cur_board->level_under_id = ccalloc(1, size);\n  cur_board->level_under_color = ccalloc(1, size);\n  cur_board->level_under_param = ccalloc(1, size);\n\n  cur_board->level_id[0] = 127;\n\n  cur_board->num_robots = 0;\n  cur_board->num_robots_active = 0;\n  cur_board->num_robots_allocated = 0;\n  cur_board->robot_list = ccalloc(1, sizeof(struct robot *));\n  cur_board->robot_list_name_sorted = ccalloc(1, sizeof(struct robot *));\n  cur_board->num_scrolls = 0;\n  cur_board->num_scrolls_allocated = 0;\n  cur_board->scroll_list = ccalloc(1, sizeof(struct scroll *));\n  cur_board->num_sensors = 0;\n  cur_board->num_sensors_allocated = 0;\n  cur_board->sensor_list = ccalloc(1, sizeof(struct sensor *));\n}\n\nvoid board_set_input_string(struct board *cur_board, const char *input, size_t len)\n{\n  if(!len || !input || !input[0])\n  {\n    if(cur_board->input_string)\n      cur_board->input_string[0] = '\\0';\n    return;\n  }\n\n  if(len + 1 > cur_board->input_allocated)\n  {\n    size_t size = MAX(len + 1, 81);\n    cur_board->input_string = crealloc(cur_board->input_string, size);\n    cur_board->input_allocated = size;\n  }\n  // The provided input may be a part of the input string!\n  memmove(cur_board->input_string, input, len);\n  cur_board->input_string[len] = '\\0';\n}\n\n__editor_maybe_static void board_set_charset_path(struct board *cur_board,\n const char *path, size_t path_len)\n{\n  if(!path_len || !path || !path[0])\n  {\n    if(cur_board->charset_path)\n      cur_board->charset_path[0] = '\\0';\n    return;\n  }\n\n  if(path_len + 1 > cur_board->charset_path_allocated)\n  {\n    size_t size = MAX(path_len + 1, 32);\n    cur_board->charset_path = crealloc(cur_board->charset_path, size);\n    cur_board->charset_path_allocated = size;\n  }\n  memcpy(cur_board->charset_path, path, path_len);\n  cur_board->charset_path[path_len] = '\\0';\n}\n\n__editor_maybe_static void board_set_palette_path(struct board *cur_board,\n const char *path, size_t path_len)\n{\n  if(!path_len || !path || !path[0])\n  {\n    if(cur_board->palette_path)\n      cur_board->palette_path[0] = '\\0';\n    return;\n  }\n\n  if(path_len + 1 > cur_board->palette_path_allocated)\n  {\n    size_t size = MAX(path_len + 1, 32);\n    cur_board->palette_path = crealloc(cur_board->palette_path, size);\n    cur_board->palette_path_allocated = size;\n  }\n  memcpy(cur_board->palette_path, path, path_len);\n  cur_board->palette_path[path_len] = '\\0';\n}\n\n#define err_if_missing(expected) if(last_ident < expected) { goto err_free; }\n\nstatic int load_board_info(struct world *mzx_world, struct board *cur_board,\n struct zip_archive *zp, int savegame, int *file_version)\n{\n  char *buffer;\n  size_t actual_size;\n  struct memfile mf;\n  struct memfile prop;\n  int info_file_version;\n  int last_ident = -1;\n  int ident;\n  int size;\n  int v;\n\n  if(zip_get_next_uncompressed_size(zp, &actual_size) != ZIP_SUCCESS)\n    return -1;\n\n  buffer = cmalloc(actual_size);\n\n  zip_read_file(zp, buffer, actual_size, &actual_size);\n\n  mfopen(buffer, actual_size, &mf);\n\n  while(next_prop(&prop, &ident, &size, &mf))\n  {\n    switch(ident)\n    {\n      case BPROP_EOF:\n        mfseek(&mf, 0, SEEK_END);\n        break;\n\n      // Essential\n      case BPROP_BOARD_NAME:\n        size = MIN(size, BOARD_NAME_SIZE - 1);\n        mfread(cur_board->board_name, size, 1, &prop);\n        cur_board->board_name[size] = 0;\n        break;\n\n      case BPROP_BOARD_WIDTH:\n        err_if_missing(BPROP_BOARD_NAME);\n        cur_board->board_width = load_prop_int(&prop);\n        if(cur_board->board_width < 1 || cur_board->board_width > 32767)\n          goto err_free;\n        break;\n\n      case BPROP_BOARD_HEIGHT:\n        err_if_missing(BPROP_BOARD_WIDTH);\n        cur_board->board_height = load_prop_int(&prop);\n        if(cur_board->board_height < 1 || cur_board->board_height > 32767)\n          goto err_free;\n        break;\n\n      case BPROP_OVERLAY_MODE:\n        err_if_missing(BPROP_BOARD_HEIGHT);\n        cur_board->overlay_mode =\n         load_prop_int_u(&prop, OVERLAY_OFF, OVERLAY_TRANSPARENT);\n        break;\n\n      case BPROP_NUM_ROBOTS:\n        err_if_missing(BPROP_OVERLAY_MODE);\n        v = load_prop_int_u(&prop, 0, 255);\n        cur_board->num_robots = v;\n        cur_board->num_robots_allocated = v;\n        break;\n\n      case BPROP_NUM_SCROLLS:\n        err_if_missing(BPROP_NUM_ROBOTS);\n        v = load_prop_int_u(&prop, 0, 255);\n        cur_board->num_scrolls = v;\n        cur_board->num_scrolls_allocated = v;\n        break;\n\n      case BPROP_NUM_SENSORS:\n        err_if_missing(BPROP_NUM_SCROLLS);\n        v = load_prop_int_u(&prop, 0, 255);\n        cur_board->num_sensors = v;\n        cur_board->num_sensors_allocated = v;\n        break;\n\n      case BPROP_FILE_VERSION:\n        err_if_missing(BPROP_NUM_SENSORS);\n        info_file_version = load_prop_int(&prop);\n        // Rearchived worlds may contain boards from older verisons, so just\n        // let version mismatches pass through. This will override the file\n        // version for this board and its components only.\n        if(info_file_version < V290 || info_file_version > MZX_VERSION)\n          goto err_free;\n\n        *file_version = info_file_version;\n        break;\n\n\n      // Non-essential\n      case BPROP_MOD_PLAYING:\n        size = MIN(size, MAX_PATH-1);\n        mfread(cur_board->mod_playing, size, 1, &prop);\n        cur_board->mod_playing[size] = 0;\n        break;\n\n      case BPROP_VIEWPORT_X:\n        cur_board->viewport_x = load_prop_int_u(&prop, 0, 79);\n        break;\n\n      case BPROP_VIEWPORT_Y:\n        cur_board->viewport_y = load_prop_int_u(&prop, 0, 24);\n        break;\n\n      case BPROP_VIEWPORT_WIDTH:\n        cur_board->viewport_width = load_prop_int_u(&prop, 1, 80);\n        break;\n\n      case BPROP_VIEWPORT_HEIGHT:\n        cur_board->viewport_height = load_prop_int_u(&prop, 1, 25);\n        break;\n\n      case BPROP_CAN_SHOOT:\n        cur_board->can_shoot = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_CAN_BOMB:\n        cur_board->can_bomb = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_FIRE_BURN_BROWN:\n        cur_board->fire_burn_brown = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_FIRE_BURN_SPACE:\n        cur_board->fire_burn_space = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_FIRE_BURN_FAKES:\n        cur_board->fire_burn_fakes = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_FIRE_BURN_TREES:\n        cur_board->fire_burn_trees = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_EXPLOSIONS_LEAVE:\n        cur_board->explosions_leave =\n         load_prop_int_u(&prop, EXPL_LEAVE_SPACE, EXPL_LEAVE_FIRE);\n        break;\n\n      case BPROP_SAVE_MODE:\n        cur_board->save_mode =\n         load_prop_int_u(&prop, CAN_SAVE, CAN_SAVE_ON_SENSOR);\n        break;\n\n      case BPROP_FOREST_BECOMES:\n        cur_board->forest_becomes =\n         load_prop_int_u(&prop, FOREST_TO_EMPTY, FOREST_TO_FLOOR);\n        break;\n\n      case BPROP_COLLECT_BOMBS:\n        cur_board->collect_bombs = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_FIRE_BURNS:\n        cur_board->fire_burns =\n         load_prop_int_u(&prop, FIRE_BURNS_LIMITED, FIRE_BURNS_FOREVER);\n        break;\n\n      case BPROP_BOARD_N:\n        cur_board->board_dir[0] = load_prop_int_u(&prop, 0, NO_BOARD);\n        break;\n\n      case BPROP_BOARD_S:\n        cur_board->board_dir[1] = load_prop_int_u(&prop, 0, NO_BOARD);\n        break;\n\n      case BPROP_BOARD_E:\n        cur_board->board_dir[2] = load_prop_int_u(&prop, 0, NO_BOARD);\n        break;\n\n      case BPROP_BOARD_W:\n        cur_board->board_dir[3] = load_prop_int_u(&prop, 0, NO_BOARD);\n        break;\n\n      case BPROP_RESTART_IF_ZAPPED:\n        cur_board->restart_if_zapped = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_TIME_LIMIT:\n        cur_board->time_limit = load_prop_int(&prop);\n        break;\n\n      case BPROP_PLAYER_NS_LOCKED:\n        cur_board->player_ns_locked = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_PLAYER_EW_LOCKED:\n        cur_board->player_ew_locked = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_PLAYER_ATTACK_LOCKED:\n        cur_board->player_attack_locked = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_RESET_ON_ENTRY:\n        cur_board->reset_on_entry = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_CHARSET_PATH:\n        size = MIN(size, MAX_PATH - 1);\n        board_set_charset_path(cur_board, (const char *)prop.start, size);\n        break;\n\n      case BPROP_PALETTE_PATH:\n        size = MIN(size, MAX_PATH - 1);\n        board_set_palette_path(cur_board, (const char *)prop.start, size);\n        break;\n\n      case BPROP_RESET_ON_ENTRY_SAME_BOARD:\n        if(*file_version >= V293 && mzx_world->version >= V293)\n          cur_board->reset_on_entry_same_board = load_prop_boolean(&prop);\n        break;\n\n      case BPROP_DRAGONS_CAN_RANDOMLY_MOVE:\n        if(*file_version >= V293 && mzx_world->version >= V293)\n          cur_board->dragons_can_randomly_move = load_prop_boolean(&prop);\n        break;\n\n\n      // Savegame only\n      case BPROP_SCROLL_X:\n        cur_board->scroll_x = load_prop_int_s(&prop, -32768, 32767);\n        break;\n\n      case BPROP_SCROLL_Y:\n        cur_board->scroll_y = load_prop_int_s(&prop, -32768, 32767);\n        break;\n\n      case BPROP_LOCKED_X:\n        cur_board->locked_x = load_prop_int_s(&prop, -1, 32767);\n        break;\n\n      case BPROP_LOCKED_Y:\n        cur_board->locked_y = load_prop_int_s(&prop, -1, 32767);\n        break;\n\n      case BPROP_PLAYER_LAST_DIR:\n        cur_board->player_last_dir = load_prop_int_u(&prop, 0, 255);\n        break;\n\n      case BPROP_LAZWALL_START:\n        cur_board->lazwall_start = load_prop_int(&prop);\n        break;\n\n      case BPROP_LAST_KEY:\n        cur_board->last_key = load_prop_int(&prop);\n        break;\n\n      case BPROP_NUM_INPUT:\n        cur_board->num_input = load_prop_int(&prop);\n        break;\n\n      case BPROP_INPUT_SIZE:\n        cur_board->input_size = load_prop_int(&prop);\n        break;\n\n      case BPROP_INPUT_STRING:\n        size = MIN(size, ROBOT_MAX_TR-1);\n        board_set_input_string(cur_board, (const char *)prop.start, size);\n        break;\n\n      case BRPOP_BOTTOM_MESG:\n        size = MIN(size, ROBOT_MAX_TR-1);\n        mfread(cur_board->bottom_mesg, size, 1, &prop);\n        cur_board->bottom_mesg[size] = 0;\n        break;\n\n      case BPROP_BOTTOM_MESG_TIMER:\n        cur_board->b_mesg_timer = load_prop_int(&prop);\n        break;\n\n      case BPROP_BOTTOM_MESG_ROW:\n        cur_board->b_mesg_row = load_prop_int_u(&prop, 0, 24);\n        break;\n\n      case BPROP_BOTTOM_MESG_COL:\n        cur_board->b_mesg_col = load_prop_int_s(&prop, -1, 79);\n        break;\n\n      case BPROP_VOLUME:\n        cur_board->volume = load_prop_int_u(&prop, 0, 255);\n        break;\n\n      case BPROP_VOLUME_INC:\n        cur_board->volume_inc = load_prop_int_u(&prop, 0, 255);\n        break;\n\n      case BPROP_VOLUME_TARGET:\n        cur_board->volume_target = load_prop_int_u(&prop, 0, 255);\n        break;\n\n      case BPROP_BLIND_DUR:\n        if(*file_version >= V293)\n          cur_board->blind_dur_v1 = load_prop_int_u(&prop, 0, 255);\n        break;\n\n      case BPROP_FIREWALKER_DUR:\n        if(*file_version >= V293)\n          cur_board->firewalker_dur_v1 = load_prop_int_u(&prop, 0, 255);\n        break;\n\n      case BPROP_FREEZE_TIME_DUR:\n        if(*file_version >= V293)\n          cur_board->freeze_time_dur_v1 = load_prop_int_u(&prop, 0, 255);\n        break;\n\n      case BPROP_SLOW_TIME_DUR:\n        if(*file_version >= V293)\n          cur_board->slow_time_dur_v1 = load_prop_int_u(&prop, 0, 255);\n        break;\n\n      case BPROP_WIND_DUR:\n        if(*file_version >= V293)\n          cur_board->wind_dur_v1 = load_prop_int_u(&prop, 0, 255);\n        break;\n\n      default:\n        break;\n    }\n    last_ident = ident;\n  }\n\n  err_if_missing(BPROP_FILE_VERSION);\n\n  size = cur_board->board_width * cur_board->board_height;\n\n  if(size <= 0 || size > MAX_BOARD_SIZE)\n    goto err_free;\n\n  // Fix viewport width/height (these might have been out-of-order).\n  cur_board->viewport_width  = CLAMP(cur_board->viewport_width, 1, 80 - cur_board->viewport_x);\n  cur_board->viewport_width  = CLAMP(cur_board->viewport_width, 1, cur_board->board_width);\n  cur_board->viewport_height = CLAMP(cur_board->viewport_height, 1, 25 - cur_board->viewport_y);\n  cur_board->viewport_height = CLAMP(cur_board->viewport_height, 1, cur_board->board_height);\n\n  free(buffer);\n  return 0;\n\nerr_free:\n  free(buffer);\n  return 1;\n}\n\n\nstatic int cmp_robots(const void *dest, const void *src)\n{\n  struct robot *rsrc = *((struct robot **)src);\n  struct robot *rdest = *((struct robot **)dest);\n  return strcasecmp(rdest->robot_name, rsrc->robot_name);\n}\n\n__editor_maybe_static\nint load_board_direct(struct world *mzx_world, struct board *cur_board,\n struct zip_archive *zp, int savegame, int file_version, unsigned int board_id)\n{\n  unsigned int file_id;\n  unsigned int board_id_read;\n  unsigned int robot_id_read;\n\n  size_t board_size = 0;\n\n  struct robot **robot_list = NULL;\n  struct robot **robot_list_name_sorted = NULL;\n  struct scroll **scroll_list = NULL;\n  struct sensor **sensor_list = NULL;\n\n  char found_robots[256] = {0};\n  char found_scrolls[256] = {0};\n  char found_sensors[256] = {0};\n\n  unsigned int num_robots = 0;\n  unsigned int num_scrolls = 0;\n  unsigned int num_sensors = 0;\n  unsigned int num_robots_active = 0;\n\n  unsigned int i;\n\n  int has_base = 0;\n  int has_bid = 0;\n  int has_bpr = 0;\n  int has_bco = 0;\n  int has_uid = 0;\n  int has_upr = 0;\n  int has_uco = 0;\n  int has_och = 0;\n  int has_oco = 0;\n\n  default_board_settings(mzx_world, cur_board);\n\n  while(ZIP_SUCCESS == zip_get_next_mzx_file_id(zp, &file_id, &board_id_read, &robot_id_read))\n  {\n    if(board_id_read != board_id)\n      break;\n\n    switch(file_id)\n    {\n      case FILE_ID_BOARD_INFO:\n      {\n        if(load_board_info(mzx_world, cur_board, zp, savegame, &file_version))\n          goto err_invalid;\n\n        has_base = 1;\n        board_size = cur_board->board_width * cur_board->board_height;\n\n        cur_board->level_id = ccalloc(1, board_size);\n        cur_board->level_param = ccalloc(1, board_size);\n        cur_board->level_color = ccalloc(1, board_size);\n        cur_board->level_under_id = ccalloc(1, board_size);\n        cur_board->level_under_param = ccalloc(1, board_size);\n        cur_board->level_under_color = ccalloc(1, board_size);\n\n        if(cur_board->overlay_mode)\n        {\n          cur_board->overlay = ccalloc(1, board_size);\n          cur_board->overlay_color = ccalloc(1, board_size);\n        }\n\n        num_robots = cur_board->num_robots;\n        num_scrolls = cur_board->num_scrolls;\n        num_sensors = cur_board->num_sensors;\n\n        robot_list = ccalloc(num_robots + 1, sizeof(struct robot *));\n\n        robot_list_name_sorted =\n         ccalloc(num_robots, sizeof(struct robot *));\n\n        scroll_list = ccalloc(num_scrolls + 1, sizeof(struct scroll *));\n\n        sensor_list = ccalloc(num_sensors + 1, sizeof(struct sensor *));\n\n        cur_board->robot_list = robot_list;\n        cur_board->robot_list_name_sorted = robot_list_name_sorted;\n        cur_board->scroll_list = scroll_list;\n        cur_board->sensor_list = sensor_list;\n\n        break;\n      }\n\n      case FILE_ID_BOARD_BID:\n      {\n        if(!has_base)\n          goto err_invalid;\n\n        has_bid = 1;\n        zip_read_file(zp, cur_board->level_id, board_size, NULL);\n        break;\n      }\n\n      case FILE_ID_BOARD_BPR:\n      {\n        if(!has_base)\n          goto err_invalid;\n\n        has_bpr = 1;\n        zip_read_file(zp, cur_board->level_param, board_size, NULL);\n        break;\n      }\n\n      case FILE_ID_BOARD_BCO:\n      {\n        if(!has_base)\n          goto err_invalid;\n\n        has_bco = 1;\n        zip_read_file(zp, cur_board->level_color, board_size, NULL);\n        break;\n      }\n\n      case FILE_ID_BOARD_UID:\n      {\n        if(!has_base)\n          goto err_invalid;\n\n        has_uid = 1;\n        zip_read_file(zp, cur_board->level_under_id, board_size, NULL);\n        break;\n      }\n\n      case FILE_ID_BOARD_UPR:\n      {\n        if(!has_base)\n          goto err_invalid;\n\n        has_upr = 1;\n        zip_read_file(zp, cur_board->level_under_param, board_size, NULL);\n        break;\n      }\n\n      case FILE_ID_BOARD_UCO:\n      {\n        if(!has_base)\n          goto err_invalid;\n\n        has_uco = 1;\n        zip_read_file(zp, cur_board->level_under_color, board_size, NULL);\n        break;\n      }\n\n      case FILE_ID_BOARD_OCH:\n      {\n        if(!has_base)\n          goto err_invalid;\n\n        if(cur_board->overlay_mode)\n        {\n          has_och = 1;\n          zip_read_file(zp, cur_board->overlay, board_size, NULL);\n        }\n        else\n        {\n          zip_skip_file(zp);\n        }\n\n        break;\n      }\n\n      case FILE_ID_BOARD_OCO:\n      {\n        if(!has_base)\n          goto err_invalid;\n\n        if(cur_board->overlay_mode)\n        {\n          has_oco = 1;\n          zip_read_file(zp, cur_board->overlay_color, board_size, NULL);\n        }\n        else\n        {\n          zip_skip_file(zp);\n        }\n\n        break;\n      }\n\n      case FILE_ID_ROBOT:\n      {\n        if(!has_base)\n          goto err_invalid;\n\n        if(robot_id_read <= num_robots && !robot_list[robot_id_read])\n        {\n          struct robot *cur_robot;\n\n          cur_robot = load_robot_allocate(mzx_world, zp, savegame, file_version);\n\n          robot_list[robot_id_read] = cur_robot;\n          robot_list_name_sorted[num_robots_active] = cur_robot;\n\n          num_robots_active++;\n        }\n        else\n        {\n          zip_skip_file(zp);\n        }\n        break;\n      }\n\n      case FILE_ID_SCROLL:\n      {\n        if(!has_base)\n          goto err_invalid;\n\n        if(robot_id_read <= num_scrolls && !scroll_list[robot_id_read])\n        {\n          scroll_list[robot_id_read] = load_scroll_allocate(zp);\n        }\n        else\n        {\n          zip_skip_file(zp);\n        }\n        break;\n      }\n\n      case FILE_ID_SENSOR:\n      {\n        if(!has_base)\n          goto err_invalid;\n\n        if(robot_id_read <= num_sensors && !sensor_list[robot_id_read])\n        {\n          sensor_list[robot_id_read] = load_sensor_allocate(zp);\n        }\n        else\n        {\n          zip_skip_file(zp);\n        }\n        break;\n      }\n\n      default:\n      {\n        // Whatever it is, it can't be loaded here.\n        zip_skip_file(zp);\n        break;\n      }\n    }\n  }\n\n  if(!has_base)\n    goto err_invalid;\n\n  if(!has_bid)\n    error_message(E_ZIP_BOARD_MISSING_DATA, (int)board_id, \"bid\");\n\n  if(!has_bpr)\n    error_message(E_ZIP_BOARD_MISSING_DATA, (int)board_id, \"bpr\");\n\n  if(!has_bco)\n    error_message(E_ZIP_BOARD_MISSING_DATA, (int)board_id, \"bco\");\n\n  if(!has_uid)\n    error_message(E_ZIP_BOARD_MISSING_DATA, (int)board_id, \"uid\");\n\n  if(!has_upr)\n    error_message(E_ZIP_BOARD_MISSING_DATA, (int)board_id, \"upr\");\n\n  if(!has_uco)\n    error_message(E_ZIP_BOARD_MISSING_DATA, (int)board_id, \"uco\");\n\n  if(!has_och && cur_board->overlay_mode)\n    error_message(E_ZIP_BOARD_MISSING_DATA, (int)board_id, \"och\");\n\n  if(!has_oco && cur_board->overlay_mode)\n    error_message(E_ZIP_BOARD_MISSING_DATA, (int)board_id, \"oco\");\n\n  /* Sort the name sorted list.\n   * Do not shorten it; the name sorted list is expected to be num_robots in\n   * length at all times, though it's not obvious from the way the legacy loader\n   * works. After loading, the legacy loader will perform optimize_null_objects,\n   * which removes the unused robots. However, this loader does not, to preserve\n   * robot IDs when loading saves. Just leave nulls in the spaces at the end.\n   */\n  if(num_robots_active > 0)\n  {\n    qsort(robot_list_name_sorted, num_robots_active,\n     sizeof(struct robot *), cmp_robots);\n  }\n\n  cur_board->num_robots_active = num_robots_active;\n\n  // Insert the global robot.\n  robot_list[0] = &(mzx_world->global_robot);\n\n  // Validate the data on the board.\n  if(has_bid)\n  {\n    char *level_id = cur_board->level_id;\n    char *level_param = cur_board->level_param;\n    char *level_color = cur_board->level_color;\n    char *level_under_id = cur_board->level_under_id;\n    unsigned char id;\n    unsigned char pr;\n    int err;\n\n    // These IDs aren't allowed.\n    found_robots[0] = 1;\n    found_scrolls[0] = 1;\n    found_sensors[0] = 1;\n\n    for(i = 0; i < board_size; i++)\n    {\n      id = level_id[i];\n\n      switch(id)\n      {\n        case ROBOT:\n        case ROBOT_PUSHABLE:\n        {\n          pr = level_param[i];\n\n          if(!found_robots[pr] && pr<=num_robots && robot_list[pr])\n          {\n            found_robots[pr] = 1;\n          }\n          else\n          {\n            err = (board_id << 8)|pr;\n\n            if(found_robots[pr])\n              error_message(E_ZIP_ROBOT_DUPLICATED, err, NULL);\n            else\n              error_message(E_ZIP_ROBOT_MISSING_FROM_DATA, err, NULL);\n\n            level_id[i] = CUSTOM_BLOCK;\n            level_param[i] = 'R';\n            level_color[i] = 0xCF;\n          }\n\n          break;\n        }\n\n        case SIGN:\n        case SCROLL:\n        {\n          pr = level_param[i];\n\n          if(!found_scrolls[pr] && pr<=num_scrolls && scroll_list[pr])\n          {\n            found_scrolls[pr] = 1;\n          }\n          else\n          {\n            // Silently fix.\n            level_id[i] = CUSTOM_BLOCK;\n            level_param[i] = 'S';\n            level_color[i] = 0xCF;\n          }\n          break;\n        }\n\n        case SENSOR:\n        {\n          pr = level_param[i];\n\n          if(!found_sensors[pr] && pr<=num_sensors && sensor_list[pr])\n          {\n            found_sensors[pr] = 1;\n          }\n          else\n          {\n            // Silently fix.\n            level_id[i] = CUSTOM_FLOOR;\n            level_param[i] = 'S';\n            level_color[i] = 0xDF;\n          }\n          break;\n        }\n\n        default:\n        {\n          // If for any reason extkinds find their way into a 2.90+ file...\n          if(id > 127)\n            level_id[i] = CUSTOM_BLOCK;\n          break;\n        }\n      }\n\n      id = level_under_id[i];\n\n      switch(id)\n      {\n        case SENSOR:\n        {\n          pr = cur_board->level_under_param[i];\n\n          if(!found_sensors[pr] && pr<=num_sensors && sensor_list[pr])\n          {\n            found_sensors[pr] = 1;\n          }\n          else\n          {\n            // Silently fix.\n            level_under_id[i] = CUSTOM_FLOOR;\n            cur_board->level_param[i] = 'S';\n            cur_board->level_color[i] = 0xDF;\n          }\n          break;\n        }\n\n        default:\n        {\n          if(level_under_id[i] > 127)\n            level_under_id[i] = CUSTOM_FLOOR;\n          break;\n        }\n      }\n    }\n  }\n\n  // Now, make sure everything loaded was found on the board.\n  for(i = 1; i <= num_robots; i++)\n  {\n    if(robot_list[i] && !found_robots[i])\n    {\n      // Deleting these is a pain. Mark them to be cleaned on save.\n      error_message(E_ZIP_ROBOT_MISSING_FROM_BOARD, (board_id << 8)|i, NULL);\n      robot_list[i]->used = 0;\n    }\n  }\n\n  for(i = 1; i <= num_scrolls; i++)\n  {\n    if(scroll_list[i] && !found_scrolls[i])\n    {\n      // Silently fix.\n      clear_scroll(scroll_list[i]);\n      scroll_list[i] = NULL;\n    }\n  }\n\n  for(i = 1; i <= num_sensors; i++)\n  {\n    if(sensor_list[i] && !found_sensors[i])\n    {\n      // Silently fix.\n      clear_sensor(sensor_list[i]);\n      sensor_list[i] = NULL;\n    }\n  }\n\n  // Correct scroll/sensor counts.\n  while(num_scrolls && !scroll_list[num_scrolls])\n    num_scrolls--;\n\n  while(num_sensors && !sensor_list[num_sensors])\n    num_sensors--;\n\n  cur_board->num_scrolls = num_scrolls;\n  cur_board->num_sensors = num_sensors;\n  return VAL_SUCCESS;\n\nerr_invalid:\n  error_message(E_ZIP_BOARD_CORRUPT, (int)board_id, NULL);\n  dummy_board(mzx_world, cur_board);\n\n  return VAL_INVALID;\n}\n\nstruct board *load_board_allocate(struct world *mzx_world,\n struct zip_archive *zp, int savegame, int file_version, unsigned int board_id)\n{\n  struct board *cur_board = cmalloc(sizeof(struct board));\n\n  load_board_direct(mzx_world, cur_board, zp, savegame, file_version, board_id);\n\n  return cur_board;\n}\n\nstruct board *duplicate_board(struct world *mzx_world,\n struct board *src_board)\n{\n  // Create a deep copy of a board into a new board\n  struct board *dest_board;\n  struct robot **dest_robot_list;\n  struct robot **dest_robot_name_list;\n  struct scroll **dest_scroll_list;\n  struct sensor **dest_sensor_list;\n\n  struct robot *src_robot;\n  struct robot *dest_robot;\n  struct scroll *src_scroll;\n  struct scroll *dest_scroll;\n  struct sensor *src_sensor;\n  struct sensor *dest_sensor;\n\n  int size = src_board->board_width * src_board->board_height;\n  int num_robots_active = 0;\n  int i;\n\n  dest_board = cmalloc(sizeof(struct board));\n  memcpy(dest_board, src_board, sizeof(struct board));\n\n  // Level data\n  dest_board->level_id = cmalloc(size);\n  dest_board->level_param = cmalloc(size);\n  dest_board->level_color = cmalloc(size);\n  dest_board->level_under_id = cmalloc(size);\n  dest_board->level_under_param = cmalloc(size);\n  dest_board->level_under_color = cmalloc(size);\n\n  memcpy(dest_board->level_id, src_board->level_id, size);\n  memcpy(dest_board->level_param, src_board->level_param, size);\n  memcpy(dest_board->level_color, src_board->level_color, size);\n  memcpy(dest_board->level_under_id, src_board->level_under_id, size);\n  memcpy(dest_board->level_under_param, src_board->level_under_param, size);\n  memcpy(dest_board->level_under_color, src_board->level_under_color, size);\n\n  if(src_board->overlay_mode)\n  {\n    dest_board->overlay = cmalloc(size);\n    dest_board->overlay_color = cmalloc(size);\n\n    memcpy(dest_board->overlay, src_board->overlay, size);\n    memcpy(dest_board->overlay_color, src_board->overlay_color, size);\n  }\n\n  // Buffers\n  if(src_board->input_string)\n  {\n    dest_board->input_string = NULL;\n    dest_board->input_allocated = 0;\n    board_set_input_string(dest_board, src_board->input_string,\n     src_board->input_allocated);\n  }\n\n  if(src_board->charset_path)\n  {\n    dest_board->charset_path = NULL;\n    dest_board->charset_path_allocated = 0;\n    board_set_charset_path(dest_board, src_board->charset_path,\n     src_board->charset_path_allocated);\n  }\n\n  if(src_board->palette_path)\n  {\n    dest_board->palette_path = NULL;\n    dest_board->palette_path_allocated = 0;\n    board_set_palette_path(dest_board, src_board->palette_path,\n     src_board->palette_path_allocated);\n  }\n\n  // Robots\n  dest_robot_list =\n   ccalloc(src_board->num_robots_allocated + 1, sizeof(struct robot *));\n\n  dest_robot_name_list =\n   ccalloc(src_board->num_robots_allocated, sizeof(struct robot *));\n\n  dest_board->robot_list = dest_robot_list;\n  dest_board->robot_list_name_sorted = dest_robot_name_list;\n\n  for(i = 1; i <= src_board->num_robots; i++)\n  {\n    src_robot = src_board->robot_list[i];\n\n    if(src_robot)\n    {\n      dest_robot = cmalloc(sizeof(struct robot));\n\n      duplicate_robot_direct(mzx_world, src_robot, dest_robot,\n       src_robot->xpos, src_robot->ypos, 0);\n\n      dest_robot_list[i] = dest_robot;\n      dest_robot_name_list[num_robots_active] = dest_robot;\n      num_robots_active++;\n    }\n  }\n\n  // Sort the new list.\n  qsort(dest_robot_name_list, num_robots_active,\n   sizeof(struct robot *), cmp_robots);\n\n  // Should be the same, but just in case:\n  dest_board->num_robots_active = num_robots_active;\n\n  // Global robot\n  dest_board->robot_list[0] = src_board->robot_list[0];\n\n  // Scrolls\n  dest_scroll_list =\n   ccalloc(src_board->num_scrolls_allocated + 1, sizeof(struct scroll *));\n\n  dest_board->scroll_list = dest_scroll_list;\n\n  for(i = 1; i <= src_board->num_scrolls; i++)\n  {\n    src_scroll = src_board->scroll_list[i];\n\n    if(src_scroll)\n    {\n      dest_scroll = cmalloc(sizeof(struct scroll));\n      dest_scroll_list[i] = dest_scroll;\n\n      duplicate_scroll_direct(src_scroll, dest_scroll);\n    }\n  }\n\n  // Sensors\n  dest_sensor_list =\n   ccalloc(src_board->num_sensors_allocated + 1, sizeof(struct sensor *));\n\n  dest_board->sensor_list = dest_sensor_list;\n\n  for(i = 1; i <= src_board->num_sensors; i++)\n  {\n    src_sensor = src_board->sensor_list[i];\n\n    if(src_sensor)\n    {\n      dest_sensor = cmalloc(sizeof(struct sensor));\n      dest_sensor_list[i] = dest_sensor;\n\n      duplicate_sensor_direct(src_sensor, dest_sensor);\n    }\n  }\n\n  return dest_board;\n}\n\nvoid clear_board(struct board *cur_board)\n{\n  int i;\n  int num_robots_active = cur_board->num_robots_active;\n  int num_scrolls = cur_board->num_scrolls;\n  int num_sensors = cur_board->num_sensors;\n  struct robot **robot_list = cur_board->robot_list;\n  struct robot **robot_name_list = cur_board->robot_list_name_sorted;\n  struct scroll **scroll_list = cur_board->scroll_list;\n  struct sensor **sensor_list = cur_board->sensor_list;\n\n  free(cur_board->level_id);\n  free(cur_board->level_param);\n  free(cur_board->level_color);\n  free(cur_board->level_under_id);\n  free(cur_board->level_under_param);\n  free(cur_board->level_under_color);\n\n  free(cur_board->input_string);\n  free(cur_board->charset_path);\n  free(cur_board->palette_path);\n\n  if(cur_board->overlay_mode)\n  {\n    free(cur_board->overlay);\n    free(cur_board->overlay_color);\n  }\n\n  for(i = 0; i < num_robots_active; i++)\n    if(robot_name_list[i])\n      clear_robot(robot_name_list[i]);\n\n  free(robot_name_list);\n  free(robot_list);\n\n  for(i = 1; i <= num_scrolls; i++)\n    if(scroll_list[i])\n      clear_scroll(scroll_list[i]);\n\n  free(scroll_list);\n\n  for(i = 1; i <= num_sensors; i++)\n    if(sensor_list[i])\n      clear_sensor(sensor_list[i]);\n\n  free(sensor_list);\n  free(cur_board);\n}\n\n// Just a linear search. Boards aren't addressed by name very often.\nint find_board(struct world *mzx_world, char *name)\n{\n  struct board **board_list = mzx_world->board_list;\n  struct board *current_board;\n  int num_boards = mzx_world->num_boards;\n  int i;\n  for(i = 0; i < num_boards; i++)\n  {\n    current_board = board_list[i];\n    if(current_board && !strcasecmp(name, current_board->board_name))\n      break;\n  }\n\n  if(i != num_boards)\n  {\n    return i;\n  }\n  else\n  {\n    return NO_BOARD;\n  }\n}\n"
  },
  {
    "path": "src/board.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __BOARD_H\n#define __BOARD_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"world_struct.h\"\n\nstruct zip_archive;\n\nCORE_LIBSPEC int save_board(struct world *mzx_world, struct board *cur_board,\n struct zip_archive *zp, int savegame, int file_version, int board_id);\n\nCORE_LIBSPEC struct board *load_board_allocate(struct world *mzx_world,\n struct zip_archive *zp, int savegame, int file_version, unsigned int board_id);\n\nCORE_LIBSPEC void board_set_input_string(struct board *cur_board,\n const char *input, size_t len);\nCORE_LIBSPEC void clear_board(struct board *cur_board);\n\nstruct board *duplicate_board(struct world *mzx_world,\n struct board *src_board);\n\nvoid default_board_settings(struct world *mzx_world, struct board *cur_board);\nvoid dummy_board(struct world *mzx_world, struct board *cur_board);\n\nint find_board(struct world *mzx_world, char *name);\n\n#ifdef CONFIG_EDITOR\nCORE_LIBSPEC int load_board_direct(struct world *mzx_world,\n struct board *cur_board,  struct zip_archive *zp, int savegame,\n int file_version, unsigned int board_id);\n\nCORE_LIBSPEC void board_set_charset_path(struct board *cur_board,\n const char *path, size_t path_len);\nCORE_LIBSPEC void board_set_palette_path(struct board *cur_board,\n const char *path, size_t path_len);\n#endif // CONFIG_EDITOR\n\n__M_END_DECLS\n\n#endif // __BOARD_H\n"
  },
  {
    "path": "src/board_struct.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __BOARD_STRUCT_H\n#define __BOARD_STRUCT_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"robot_struct.h\"\n\nstruct board\n{\n  char board_name[32];\n\n  int board_width;\n  int board_height;\n\n  int overlay_mode;\n  char *level_id;\n  char *level_param;\n  char *level_color;\n  char *level_under_id;\n  char *level_under_param;\n  char *level_under_color;\n  char *overlay;\n  char *overlay_color;\n\n  char mod_playing[MAX_PATH];\n  int viewport_x;\n  int viewport_y;\n  int viewport_width;\n  int viewport_height;\n  int can_shoot;\n  int can_bomb;\n  int fire_burn_brown;\n  int fire_burn_space;\n  int fire_burn_fakes;\n  int fire_burn_trees;\n  int explosions_leave;\n  int save_mode;\n  int forest_becomes;\n  int collect_bombs;\n  int fire_burns;\n  int dragons_can_randomly_move;\n  int spittingtiger_moves;\n  int board_dir[4];\n  int restart_if_zapped;\n  int reset_on_entry;\n  int reset_on_entry_same_board;\n  int time_limit;\n  int last_key;\n  int num_input;\n  size_t input_size;\n  size_t input_allocated;\n  char *input_string;\n  int player_last_dir;\n  char bottom_mesg[ROBOT_MAX_TR];\n  int b_mesg_timer;\n  int lazwall_start;\n  int b_mesg_row;\n  int b_mesg_col;\n  int scroll_x;\n  int scroll_y;\n  int locked_x;\n  int locked_y;\n  int player_ns_locked;\n  int player_ew_locked;\n  int player_attack_locked;\n  int volume;\n  int volume_inc;\n  int volume_target;\n  char *charset_path;\n  char *palette_path;\n  size_t charset_path_allocated;\n  size_t palette_path_allocated;\n  unsigned char blind_dur_v1;\n  unsigned char firewalker_dur_v1;\n  unsigned char freeze_time_dur_v1;\n  unsigned char slow_time_dur_v1;\n  unsigned char wind_dur_v1;\n\n  int num_robots;\n  int num_robots_active;\n  int num_robots_allocated;\n  struct robot **robot_list;\n  struct robot **robot_list_name_sorted;\n  int num_scrolls;\n  int num_scrolls_allocated;\n  struct scroll **scroll_list;\n  int num_sensors;\n  int num_sensors_allocated;\n  struct sensor **sensor_list;\n#if defined(DEBUG) || defined(CONFIG_EXTRAM)\n  boolean is_extram;\n#endif\n};\n\n__M_END_DECLS\n\n#endif // __BOARD_STRUCT_H\n"
  },
  {
    "path": "src/caption.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdarg.h>\n#include <string.h>\n\n#include \"caption.h\"\n#include \"graphics.h\"\n#include \"platform_attribute.h\" /* ATTRIBUTE_PRINTF */\n#include \"world_struct.h\"\n\n// TODO this might benefit from a dirty flag + graphics.c hook and/or\n// integration into mzx_world to remove the statics.\n\n#define MAX_CAPTION_SIZE 120\n#define CAPTION_SPACER \" :: \"\n\nstatic char caption_world[MAX_CAPTION_SIZE];\nstatic char caption_board[MAX_CAPTION_SIZE];\nstatic char caption_robot[MAX_CAPTION_SIZE];\nstatic int caption_robot_x;\nstatic int caption_robot_y;\nstatic boolean caption_editing;\nstatic boolean caption_modified;\n#ifdef CONFIG_UPDATER\nstatic boolean caption_updates;\n#endif\n#ifdef CONFIG_FPS\nstatic double caption_fps;\n#endif\n\n/**\n * Strip a string for display in the caption.\n */\n\nstatic boolean strip_caption_string(char *output, const char *input)\n{\n  int len = strlen(input);\n  int i, j;\n  output[0] = '\\0';\n\n  for(i = 0, j = 0; i < len; i++)\n  {\n    if(input[i] < 32 || input[i] > 126)\n      continue;\n\n    if(input[i] == '~' || input[i] == '@')\n    {\n      i++;\n      if(input[i - 1] != input[i])\n        continue;\n    }\n\n    output[j] = input[i];\n\n    if(output[j] != ' ' || (j > 0 && output[j - 1] != ' '))\n      j++;\n  }\n\n  if(j > 0 && output[j - 1] == ' ')\n    j--;\n\n  output[j] = '\\0';\n  return (j > 0);\n}\n\n/**\n * Append a string to the caption.\n */\nATTRIBUTE_PRINTF(2, 3)\nstatic inline void caption_append(char caption[MAX_CAPTION_SIZE],\n const char *f, ...)\n{\n  size_t len = strlen(caption);\n\n  if(len < MAX_CAPTION_SIZE - 1)\n  {\n    va_list args;\n    va_start(args, f);\n\n    vsnprintf(caption + len, MAX_CAPTION_SIZE - len, f, args);\n\n    va_end(args);\n  }\n}\n\n/**\n * Sets the caption to reflect current MegaZeux state information.\n */\n\nstatic void update_caption(void)\n{\n  char caption[MAX_CAPTION_SIZE];\n  caption[0] = '\\0';\n\n  if(caption_modified)\n    strcpy(caption, \"*\");\n\n  if(caption_robot[0])\n    caption_append(caption, \"%s (%i,%i)\" CAPTION_SPACER,\n     caption_robot, caption_robot_x, caption_robot_y);\n\n  if(caption_board[0])\n    caption_append(caption, \"%s\" CAPTION_SPACER, caption_board);\n\n  if(caption_world[0])\n    caption_append(caption, \"%s\" CAPTION_SPACER, caption_world);\n\n  caption_append(caption, \"%s\", video_get_default_caption());\n\n  if(caption_editing)\n    caption_append(caption, \" (editor)\");\n\n#ifdef CONFIG_UPDATER\n  if(caption_updates)\n    caption_append(caption, \" *** UPDATES AVAILABLE ***\");\n#endif\n\n#ifdef CONFIG_FPS\n  if(caption_fps > 0.001)\n    caption_append(caption, CAPTION_SPACER \"FPS: %.2f\", caption_fps);\n#endif /* CONFIG_FPS */\n\n  caption[MAX_CAPTION_SIZE - 1] = 0;\n  video_set_window_caption(NULL, caption);\n}\n\nstatic void set_caption_world_string(struct world *mzx_world)\n{\n  if(mzx_world && mzx_world->active)\n  {\n    if(!strip_caption_string(caption_world, mzx_world->name))\n      strcpy(caption_world, \"Untitled world\");\n  }\n  else\n  {\n    caption_world[0] = '\\0';\n  }\n}\n\nstatic void set_caption_board_string(struct board *board)\n{\n  if(board)\n  {\n    if(!strip_caption_string(caption_board, board->board_name))\n      strcpy(caption_board, \"Untitled board\");\n  }\n  else\n  {\n    caption_board[0] = '\\0';\n  }\n}\n\nstatic void set_caption_robot_string(struct robot *robot)\n{\n  if(robot)\n  {\n    if(!strip_caption_string(caption_robot, robot->robot_name))\n      strcpy(caption_robot, \"Untitled robot\");\n\n    caption_robot_x = robot->xpos;\n    caption_robot_y = robot->ypos;\n  }\n  else\n  {\n    caption_robot[0] = '\\0';\n  }\n}\n\nvoid caption_set_world(struct world *mzx_world)\n{\n  set_caption_world_string(mzx_world);\n  set_caption_board_string(NULL);\n  set_caption_robot_string(NULL);\n  caption_modified = false;\n  caption_editing = false;\n  update_caption();\n}\n\n#ifdef CONFIG_EDITOR\nvoid caption_set_board(struct world *mzx_world, struct board *board)\n{\n  set_caption_world_string(mzx_world);\n  set_caption_board_string(board);\n  set_caption_robot_string(NULL);\n  caption_editing = true;\n  update_caption();\n}\n\nvoid caption_set_robot(struct world *mzx_world, struct robot *robot)\n{\n  set_caption_world_string(mzx_world);\n  set_caption_board_string(mzx_world->current_board);\n  set_caption_robot_string(robot);\n  caption_editing = true;\n  update_caption();\n}\n\nvoid caption_set_modified(boolean modified)\n{\n  caption_modified = modified;\n  caption_editing = true;\n  update_caption();\n}\n#endif\n\n#ifdef CONFIG_UPDATER\nvoid caption_set_updates_available(boolean available)\n{\n  caption_updates = available;\n  update_caption();\n}\n#endif\n\n#ifdef CONFIG_FPS\nvoid caption_set_fps(double fps)\n{\n  caption_fps = fps;\n  update_caption();\n}\n#endif\n"
  },
  {
    "path": "src/caption.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __CAPTION_H\n#define __CAPTION_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"world_struct.h\"\n\n/**\n * Set the current world to display in the caption.\n * Clears all editor-related caption values.\n *\n * @param mzx_world   The active world.\n */\n\nCORE_LIBSPEC void caption_set_world(struct world *mzx_world);\n\n#ifdef CONFIG_EDITOR\n\n/**\n * Set the current board to display in the caption in the editor.\n * Clears the caption robot value.\n *\n * @param mzx_world   The active world.\n * @param board       The active board, or NULL for no board.\n */\n\nCORE_LIBSPEC void caption_set_board(struct world *mzx_world,\n struct board *board);\n\n/**\n * Set the current robot to display in the caption in the editor.\n *\n * @param mzx_world   The active world.\n * @param robot       The active robot, or NULL for no robot.\n */\n\nCORE_LIBSPEC void caption_set_robot(struct world *mzx_world,\n struct robot *robot);\n\n/**\n * Set the modified value of the current world.\n *\n * @param modified    True if the world has been modified.\n */\n\nCORE_LIBSPEC void caption_set_modified(boolean modified);\n\n#endif\n\n/**\n * Set the updates available value for the caption.\n *\n * @param available   True if updates are available.\n */\n\n#ifdef CONFIG_UPDATER\nCORE_LIBSPEC void caption_set_updates_available(boolean available);\n#endif\n\n/**\n * Set the current FPS value.\n *\n * @param fps         The current FPS of MegaZeux, or <=0.0 to hide the display.\n */\n\n#ifdef CONFIG_FPS\nvoid caption_set_fps(double fps);\n#endif\n\n__M_END_DECLS\n\n#endif // __CAPTION_H\n"
  },
  {
    "path": "src/compat.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Provide some compatibility macros for combined C/C++ binary\n\n#ifndef __COMPAT_H\n#define __COMPAT_H\n\n#include \"config.h\"\n\n#ifdef __cplusplus\n\n#define __M_BEGIN_DECLS extern \"C\" {\n#define __M_END_DECLS   }\n\n/**\n * Compatibility define for restrict in C++, where it is a (commonly supported)\n * compiler extension. This has to be defined as 'RESTRICT' because defining\n * 'restrict' conflicts with MSVC's __declspec(restrict) attribute.\n */\n#ifndef RESTRICT\n#define RESTRICT __restrict\n#endif\n\n#if __cplusplus >= 201103\n#define IS_CXX_11 1\n#define maybe_constexpr constexpr\n#define maybe_explicit explicit\n#else /* !IS_CXX_11 */\n// Compatibility defines so certain C++11 features can be used without checks.\n// Don't create any variables named \"final\" or \"noexcept\"...\n#include <stddef.h>\n#ifndef nullptr\n#define nullptr (NULL)\n#endif\n#define final\n#define noexcept\n#define override\n#define maybe_constexpr\n#define maybe_explicit\n#endif /* !IS_CXX_11 */\n\n#ifdef _MSC_VER\n#define __PRETTY_FUNCTION__ __FUNCSIG__\n#endif\n\n#else\n\n#define __M_BEGIN_DECLS\n#define __M_END_DECLS\n\n#if !defined(CONFIG_WII) && !defined(CONFIG_NDS) && !defined(CONFIG_3DS) && \\\n !defined(CONFIG_DREAMCAST)\n\n#undef false\n#undef true\n#undef bool\n\nenum\n{\n  false = 0,\n  true  = 1,\n};\n\n#endif // !CONFIG_WII && !CONFIG_NDS\n\n#endif /* __cplusplus */\n\ntypedef unsigned char boolean;\n\n#ifdef CONFIG_3DS\n#include <3ds.h>\n#endif\n\n#ifdef CONFIG_NDS\n#include <nds.h>\n\n#ifndef CONFIG_NDS_BLOCKSDS\n// Use iprintf/iscanf on NDS to save ~50 KB\n#define sscanf siscanf\n#define printf iprintf\n#define fprintf fiprintf\n#define sprintf siprintf\n#define snprintf sniprintf\n#define vsnprintf vsniprintf\n#endif\n#endif\n\n#ifdef CONFIG_WII\n#define BOOL _BOOL\n#include <gcbool.h>\n#include <gctypes.h>\n#undef BOOL\n#undef FIXED\n#endif\n\n#ifdef CONFIG_EDITOR\n#define __editor_maybe_static\n#else\n#define __editor_maybe_static static\n#endif\n\n#ifdef CONFIG_UPDATER\n#define __updater_maybe_static\n#else\n#define __updater_maybe_static static\n#endif\n\n#ifdef CONFIG_UTILS\n#define __utils_maybe_static\n#else\n#define __utils_maybe_static static\n#endif\n\n#ifdef _MSC_VER\n#include \"msvc.h\"\n#ifndef RESTRICT\n#define RESTRICT __restrict\n#endif\n#endif\n\n#ifdef __APPLE__\n// Patch in some potentially missing defines for older platforms that\n// may be required. Fixes older versions of SDL2 headers on Snow Leopard.\n#include <TargetConditionals.h>\n#include <AvailabilityMacros.h>\n#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050\n#include <Availability.h>\n#endif\n\n#ifndef TARGET_OS_TV\n#define TARGET_OS_TV 0\n#endif\n\n#ifndef __has_feature\n#define __has_feature(f) 0\n#endif\n#endif\n\n#ifdef __WIN32__\n// Usually defined in Windows headers but somehow those always seem to add 30%\n// or more build time and we don't include them globally for any other purpose.\n#define MAX_PATH 260\n#endif\n\n#ifdef __OpenBSD__\n#include <sys/param.h>\n// These macros conflict with internally-defined MZX macros\n#undef MIN\n#undef MAX\n\n// unveil added in OpenBSD 6.3\n#ifdef OpenBSD\n#if OpenBSD >= 201805\n#define PLEDGE_HAS_UNVEIL\n#endif\n\n#endif /* OpenBSD */\n#endif /* __OpenBSD__ */\n\n#ifndef MAX_PATH\n#define MAX_PATH 512\n#endif\n\n#ifndef RESTRICT\n#define RESTRICT restrict\n#endif\n\n#if defined(CONFIG_MODULAR) && defined(__WIN32__)\n#define LIBSPEC __declspec(dllimport)\n#elif defined(CONFIG_MODULAR)\n#define LIBSPEC __attribute__((visibility(\"default\")))\n#else\n#define LIBSPEC\n#endif\n\n#ifndef CORE_LIBSPEC\n#define CORE_LIBSPEC LIBSPEC\n#endif\n\n#ifndef EDITOR_LIBSPEC\n#define EDITOR_LIBSPEC LIBSPEC\n#endif\n\n#ifndef AUDIO_LIBSPEC\n#define AUDIO_LIBSPEC LIBSPEC\n#endif\n\n#ifndef UTILS_LIBSPEC\n#define UTILS_LIBSPEC CORE_LIBSPEC\n#endif\n\n#ifdef CONFIG_UPDATER\n#define UPDATER_LIBSPEC CORE_LIBSPEC\n#else\n#define UPDATER_LIBSPEC\n#endif\n\n__M_BEGIN_DECLS\n\n#ifdef __GNUC__\n\n// On GNU compilers we can mark fopen() as \"deprecated\" to remind callers\n// to properly audit calls to it.\n//\n// Any case where a file to open is specified by a game, the path must first\n// be translated, so that any case-insensitive matches will be used and\n// safety checks will be applied. Callers should use either\n// fsafetranslate()+fopen_unsafe(), or call fsafeopen() directly.\n//\n// Any case where a file comes from the editor, or from MZX-proper, it\n// probably should not be translated. In this case fopen_unsafe() can be\n// used directly.\n\n#include <stdio.h>\n\nstatic inline FILE *fopen_unsafe(const char *path, const char *mode)\n { return fopen(path, mode); }\nstatic inline FILE *check_fopen(const char *path, const char *mode)\n __attribute__((deprecated));\nstatic inline FILE *check_fopen(const char *path, const char *mode)\n { return fopen_unsafe(path, mode); }\n\n#define fopen(path, mode) check_fopen(path, mode)\n\n// Also deprecate some notoriously bad string functions.\n// strncpy and strncat are unsafe functions that get cargo cult usage as \"safe\"\n// versions of strcpy and strcat (which they aren't). strtok is non-reentrant.\n\n#ifdef _WIN32\n// Some MinGW headers use these functions inline for some reason...\n#include <io.h>\n#endif\n#include <string.h>\nstatic inline char *check_strncpy(char *, const char *, size_t)\n __attribute__((deprecated));\nstatic inline char *check_strncat(char *, const char *, size_t)\n __attribute__((deprecated));\nstatic inline char *check_strtok(char *, const char *)\n __attribute__((deprecated));\nstatic inline char *check_strncpy(char *d, const char *s, size_t n)\n { return strncpy(d,s,n); }\nstatic inline char *check_strncat(char *d, const char *s, size_t n)\n { return strncat(d,s,n); }\nstatic inline char *check_strtok(char *d, const char *delim)\n { return strtok(d,delim); }\n#undef strncpy\n#undef strncat\n#undef strtok\n#define strncpy(d,s,n) check_strncpy(d,s,n)\n#define strncat(d,s,n) check_strncat(d,s,n)\n#define strtok(d,delim) check_strtok(d,delim)\n\n#else // !__GNUC__\n\n#define fopen_unsafe(file, mode) fopen(file, mode)\n\n#endif // __GNUC__\n\n#ifdef CONFIG_CHECK_ALLOC\n\n#include <stddef.h>\n\nCORE_LIBSPEC void check_alloc_init(void);\n\n#define ccalloc(nmemb, size) check_calloc(nmemb, size, __FILE__, __LINE__)\nCORE_LIBSPEC void *check_calloc(size_t nmemb, size_t size, const char *file,\n int line);\n\n#define cmalloc(size) check_malloc(size, __FILE__, __LINE__)\nCORE_LIBSPEC void *check_malloc(size_t size, const char *file,\n int line);\n\n#define crealloc(ptr, size) check_realloc(ptr, size, __FILE__, __LINE__)\nCORE_LIBSPEC void *check_realloc(void *ptr, size_t size, const char *file,\n int line);\n\n#else /* CONFIG_CHECK_ALLOC */\n\n#include <stdlib.h>\n\nstatic inline void check_alloc_init(void) {}\n\nstatic inline void *ccalloc(size_t nmemb, size_t size)\n{\n  return calloc(nmemb, size);\n}\n\nstatic inline void *cmalloc(size_t size)\n{\n  return malloc(size);\n}\n\nstatic inline void *crealloc(void *ptr, size_t size)\n{\n  return realloc(ptr, size);\n}\n\n#endif /* CONFIG_CHECK_ALLOC */\n\n__M_END_DECLS\n\n#endif // __COMPAT_H\n"
  },
  {
    "path": "src/configure.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2018-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Config file options, which can be given either through config.txt\n// or at the command line.\n\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/stat.h>\n\n#include \"configure.h\"\n#include \"counter.h\"\n#include \"event.h\"\n#include \"rasm.h\"\n#include \"util.h\"\n#include \"io/fsafeopen.h\"\n#include \"io/path.h\"\n#include \"io/vio.h\"\n\n#ifdef CONFIG_SDL\n#include \"SDLmzx.h\"\n#endif\n\n#define MAX_INCLUDE_DEPTH 16\n#define MAX_CONFIG_REGISTERED 2\n\n#if defined(_WIN32) || defined(CONFIG_DJGPP)\n/* Do not hang attempting to include devices like COM1, etc. */\n#define CONF_SAFETY_CHECK_FAILURE(p) path_safety_check((p), PATH_SAFE_DOS_DEVICE)\n#else\n#define CONF_SAFETY_CHECK_FAILURE(p) false\n#endif\n\n// Arch-specific config.\n#ifdef _WIN32\n#define DEFAULT_SDL_RENDER_DRIVER \"direct3d\"\n#define UPDATE_AUTO_CHECK_DEFAULT UPDATE_AUTO_CHECK_SILENT\n#endif\n\n#ifdef __APPLE__\n// Regular fullscreen behaves badly in conjunction with the maximized\n// fullscreen behavior in Mac OS X Lion and later, and can lock the\n// computer on a black screen in Monterey.\n#define FULLSCREEN_WINDOWED_DEFAULT true\n// TODO: this should be on by default, as Option is closer to AltGr than Alt.\n//#define ALT_IS_ALTGR_DEFAULT true\n#endif\n\n#ifdef CONFIG_NDS\n#define VIDEO_OUTPUT_DEFAULT \"nds\"\n#define VIDEO_RATIO_DEFAULT RATIO_CLASSIC_4_3\n#define SAVE_SLOTS_DEFAULT true\n#endif\n\n#ifdef CONFIG_DREAMCAST\n#define VIDEO_OUTPUT_DEFAULT \"dreamcast\"\n#define AUDIO_BUFFER_SAMPLES 2048 // the value KOS seems to like best\n#define AUDIO_SAMPLE_RATE 44100\n#define SAVE_SLOTS_DEFAULT true\n#endif\n\n#ifdef CONFIG_GP2X\n#define VIDEO_OUTPUT_DEFAULT \"gp2x\"\n#define AUDIO_BUFFER_SAMPLES 128\n#define SAVE_SLOTS_DEFAULT true\n#endif\n\n#ifdef CONFIG_PSP\n#define FULLSCREEN_WIDTH_DEFAULT 640\n#define FULLSCREEN_HEIGHT_DEFAULT 363\n#define FORCE_BPP_DEFAULT 8\n#define FULLSCREEN_DEFAULT 1\n#define SAVE_SLOTS_DEFAULT true\n#endif\n\n#ifdef CONFIG_PSVITA\n#define VIDEO_OUTPUT_DEFAULT \"softscale\"\n#define FULLSCREEN_WIDTH_DEFAULT 960\n#define FULLSCREEN_HEIGHT_DEFAULT 544\n#define FULLSCREEN_DEFAULT 1\n#define VFS_ENABLE_DEFAULT true\n#define VFS_MAX_CACHE_SIZE_DEFAULT (1 << 27) /* 128 MiB */\n#define SAVE_SLOTS_DEFAULT true\n#endif\n\n#ifdef CONFIG_WII\n#define AUDIO_SAMPLE_RATE 48000\n#define FULLSCREEN_DEFAULT 1\n#define GL_VSYNC_DEFAULT 1\n#define VIDEO_RATIO_DEFAULT RATIO_CLASSIC_4_3\n#define SAVE_SLOTS_DEFAULT true\n#ifdef CONFIG_SDL\n#define VIDEO_OUTPUT_DEFAULT \"software\"\n#define FULLSCREEN_WIDTH_DEFAULT 640\n#define FULLSCREEN_HEIGHT_DEFAULT 480\n#define FORCE_BPP_DEFAULT 16\n#endif /* CONFIG_SDL */\n#endif\n\n#ifdef CONFIG_3DS\n#ifdef CONFIG_SDL\n#define VIDEO_OUTPUT_DEFAULT \"gp2x\"\n#define FULLSCREEN_WIDTH_DEFAULT 400\n#define FULLSCREEN_HEIGHT_DEFAULT 240\n#define FORCE_BPP_DEFAULT 16\n#else\n#define VIDEO_OUTPUT_DEFAULT \"3ds\"\n#endif\n#define VIDEO_RATIO_DEFAULT RATIO_CLASSIC_4_3\n#define VFS_ENABLE_DEFAULT true\n#define VFS_MAX_CACHE_SIZE_DEFAULT (1 << 25) /* 32 MiB */\n#define SAVE_SLOTS_DEFAULT true\n#endif\n\n#ifdef CONFIG_WIIU\n#define FULLSCREEN_WIDTH_DEFAULT 1280\n#define FULLSCREEN_HEIGHT_DEFAULT 720\n#define FULLSCREEN_DEFAULT 1\n#define SAVE_SLOTS_DEFAULT true\n#endif\n\n#ifdef CONFIG_SWITCH\n// Switch SDL needs this resolution or else weird things start happening...\n#define FULLSCREEN_WIDTH_DEFAULT 1920\n#define FULLSCREEN_HEIGHT_DEFAULT 1080\n#define FULLSCREEN_DEFAULT 1\n#define SAVE_SLOTS_DEFAULT true\n#endif\n\n#ifdef ANDROID\n#define FULLSCREEN_DEFAULT 1\n#endif\n\n#ifdef __EMSCRIPTEN__\n#define AUDIO_SAMPLE_RATE 48000\n#endif\n\n#ifdef CONFIG_DJGPP\n#define RESAMPLE_MODE_DEFAULT RESAMPLE_MODE_NONE\n#define MOD_RESAMPLE_MODE_DEFAULT RESAMPLE_MODE_LINEAR\n#define FULLSCREEN_DEFAULT 1\n// Uncomment if Ogg Vorbis files need to be forced into memory.\n//#define VFS_ENABLE_DEFAULT true\n//#define VFS_ENABLE_AUTO_CACHE_DEFAULT false\n#endif\n\n// End arch-specific config.\n\n#ifndef FORCE_BPP_DEFAULT\n#define FORCE_BPP_DEFAULT BPP_AUTO\n#endif\n\n#ifndef GL_VSYNC_DEFAULT\n#define GL_VSYNC_DEFAULT 0\n#endif\n\n#ifndef VIDEO_OUTPUT_DEFAULT\n#if defined(CONFIG_RENDER_GL_PROGRAM)\n#define VIDEO_OUTPUT_DEFAULT \"auto_glsl\"\n#elif defined(CONFIG_RENDER_SOFTSCALE)\n#define VIDEO_OUTPUT_DEFAULT \"softscale\"\n#else\n#define VIDEO_OUTPUT_DEFAULT \"software\"\n#endif\n#endif\n\n#ifndef DEFAULT_SDL_RENDER_DRIVER\n#define DEFAULT_SDL_RENDER_DRIVER \"\"\n#endif\n\n#ifndef AUDIO_BUFFER_SAMPLES\n#define AUDIO_BUFFER_SAMPLES 1024\n#endif\n\n#ifndef AUDIO_SAMPLE_RATE\n#define AUDIO_SAMPLE_RATE 44100\n#endif\n\n#ifndef AUDIO_OUTPUT_CHANNELS\n#define AUDIO_OUTPUT_CHANNELS 2\n#endif\n\n#ifndef RESAMPLE_MODE_DEFAULT\n#define RESAMPLE_MODE_DEFAULT RESAMPLE_MODE_LINEAR\n#endif\n\n#ifndef MOD_RESAMPLE_MODE_DEFAULT\n#define MOD_RESAMPLE_MODE_DEFAULT RESAMPLE_MODE_CUBIC\n#endif\n\n#ifndef FULLSCREEN_WIDTH_DEFAULT\n#define FULLSCREEN_WIDTH_DEFAULT -1\n#endif\n\n#ifndef FULLSCREEN_HEIGHT_DEFAULT\n#define FULLSCREEN_HEIGHT_DEFAULT -1\n#endif\n\n#ifndef FULLSCREEN_DEFAULT\n#define FULLSCREEN_DEFAULT 0\n#endif\n\n#ifndef FULLSCREEN_WINDOWED_DEFAULT\n#define FULLSCREEN_WINDOWED_DEFAULT false\n#endif\n\n#ifndef VIDEO_RATIO_DEFAULT\n#define VIDEO_RATIO_DEFAULT RATIO_MODERN_64_35\n#endif\n\n#ifndef ALT_IS_ALTGR_DEFAULT\n#define ALT_IS_ALTGR_DEFAULT false\n#endif\n\n#ifndef VFS_ENABLE_DEFAULT\n#define VFS_ENABLE_DEFAULT false\n#endif\n\n#ifndef VFS_ENABLE_AUTO_CACHE_DEFAULT\n#define VFS_ENABLE_AUTO_CACHE_DEFAULT true\n#endif\n\n#ifndef VFS_MAX_CACHE_SIZE_DEFAULT\n#define VFS_MAX_CACHE_SIZE_DEFAULT (1 << 24)\n#endif\n\n#ifndef VFS_MAX_CACHE_FILE_SIZE_DEFAULT\n#define VFS_MAX_CACHE_FILE_SIZE_DEFAULT (VFS_MAX_CACHE_SIZE_DEFAULT >> 2)\n#endif\n\n#ifndef AUTO_DECRYPT_WORLDS\n#define AUTO_DECRYPT_WORLDS true\n#endif\n\n#ifndef SAVE_SLOTS_DEFAULT\n#define SAVE_SLOTS_DEFAULT false\n#endif\n\n#ifdef CONFIG_UPDATER\n#ifndef MAX_UPDATE_HOSTS\n#define MAX_UPDATE_HOSTS 16\n#endif\n\n#ifndef UPDATE_HOST_COUNT\n#define UPDATE_HOST_COUNT 4\n#endif\n\nstatic const char *default_update_hosts[] =\n{\n  \"updates.digitalmzx.com\",\n  \"updates.digitalmzx.net\",\n  \"updates.megazeux.org\",\n  \"updates.megazeux.net\",\n};\n\n#ifndef UPDATE_BRANCH_PIN\n#ifdef CONFIG_DEBYTECODE\n#define UPDATE_BRANCH_PIN \"Debytecode\"\n#else\n#define UPDATE_BRANCH_PIN \"Stable\"\n#endif\n#endif\n\n#ifndef UPDATE_AUTO_CHECK_DEFAULT\n#define UPDATE_AUTO_CHECK_DEFAULT UPDATE_AUTO_CHECK_OFF\n#endif\n\n#endif /* CONFIG_UPDATER */\n\nstatic enum config_type current_config_type;\nstatic int current_include_depth = 0;\n\nstatic struct config_info user_conf;\n\nstatic const struct config_info user_conf_default =\n{\n  // Video options\n  FULLSCREEN_DEFAULT,           // fullscreen\n  FULLSCREEN_WINDOWED_DEFAULT,  // fullscreen_windowed\n  FULLSCREEN_WIDTH_DEFAULT,     // resolution_width\n  FULLSCREEN_HEIGHT_DEFAULT,    // resolution_height\n  640,                          // window_width\n  350,                          // window_height\n  1,                            // allow_resize\n  VIDEO_OUTPUT_DEFAULT,         // video_output\n  FORCE_BPP_DEFAULT,            // force_bpp\n  VIDEO_RATIO_DEFAULT,          // video_ratio\n  CONFIG_GL_FILTER_LINEAR,      // opengl filter method\n  GL_VSYNC_DEFAULT,             // opengl vsync mode\n  \"\",                           // opengl default scaling shader\n  DEFAULT_SDL_RENDER_DRIVER,    // sdl_render_driver\n  CURSOR_MODE_HINT,             // cursor_hint_mode\n  SCREENSAVER_ENABLE,           // disable_screensaver\n  true,                         // allow screenshots\n\n  // Audio options\n  AUDIO_SAMPLE_RATE,            // audio_sample_rate\n  AUDIO_BUFFER_SAMPLES,         // audio_buffer_samples\n  AUDIO_OUTPUT_CHANNELS,        // audio_output_channels\n  0,                            // oversampling_on\n  RESAMPLE_MODE_DEFAULT,        // resample_mode\n  MOD_RESAMPLE_MODE_DEFAULT,    // module_resample_mode\n  -1,                           // max_simultaneous_samples\n  8,                            // music_volume\n  8,                            // sam_volume\n  8,                            // pc_speaker_volume\n  true,                         // music_on\n  true,                         // pc_speaker_on\n\n  // Event options\n  true,                         // allow_gamepad\n  false,                        // pause_on_unfocus\n  ALT_IS_ALTGR_DEFAULT,         // key_left_alt_is_altgr\n  ALT_IS_ALTGR_DEFAULT,         // key_right_alt_is_altgr\n  1,                            // num_buffered_events\n\n  // Virtual filesystem options\n  VFS_ENABLE_DEFAULT,           // vfs_enable\n  VFS_ENABLE_AUTO_CACHE_DEFAULT,// vfs_enable_auto_cache\n  VFS_MAX_CACHE_SIZE_DEFAULT,   // vfs_max_cache_size\n  VFS_MAX_CACHE_FILE_SIZE_DEFAULT, // vfs_max_cache_file_size\n\n  // Game options\n  \"\",                           // startup_path\n  \"caverns.mzx\",                // startup_file\n  \"saved.sav\",                  // default_save_name\n  4,                            // mzx_speed\n  ALLOW_CHEATS_NEVER,           // allow_cheats\n  AUTO_DECRYPT_WORLDS,          // auto_decrypt_worlds\n  false,                        // startup_editor\n  false,                        // standalone_mode\n  false,                        // no_titlescreen\n  SYSTEM_MOUSE_OFF,             // system_mouse\n  false,                        // grab_mouse\n  SAVE_SLOTS_DEFAULT,           // save_slots\n  \"%w.\",                        // save_slots_name\n  \".sav\",                       // save_slots_ext\n\n  // Editor options\n  false,                        // test_mode\n  NO_BOARD,                     // test_mode_start_board\n  true,                         // mask_midchars\n\n#ifdef CONFIG_NETWORK\n  true,                         // network_enabled\n  HOST_FAMILY_ANY,              // network_address_family\n  \"\",                           // socks_host\n  \"\",                           // socks_username\n  \"\",                           // socks_password\n  1080,                         // socks_port\n#endif\n#if defined(CONFIG_UPDATER)\n  true,                         // updater_enabled\n  UPDATE_HOST_COUNT,            // update_host_count\n  default_update_hosts,         // update_hosts\n  UPDATE_BRANCH_PIN,            // update_branch_pin\n  UPDATE_AUTO_CHECK_DEFAULT,    // update_auto_check\n#endif /* CONFIG_UPDATER */\n};\n\ntypedef void (*config_function)(struct config_info *conf, const char *name,\n const char *value, const char *extended_data);\n\nstruct config_entry\n{\n  const char *option_name;\n  config_function change_option;\n  boolean allow_in_game_config;\n};\n\nstatic const struct config_enum boolean_values[] =\n{\n  { \"0\", false },\n  { \"1\", true }\n};\n\nstatic const struct config_enum allow_cheats_values[] =\n{\n  { \"0\", ALLOW_CHEATS_NEVER },\n  { \"1\", ALLOW_CHEATS_ALWAYS },\n  { \"never\", ALLOW_CHEATS_NEVER },\n  { \"always\", ALLOW_CHEATS_ALWAYS },\n  { \"mzxrun\", ALLOW_CHEATS_MZXRUN }\n};\n\nstatic const struct config_enum force_bpp_values[] =\n{\n  { \"0\", BPP_AUTO },\n  { \"8\", 8 },\n  { \"16\", 16 },\n  { \"32\", 32 },\n  { \"auto\", BPP_AUTO },\n};\n\nstatic const struct config_enum gl_filter_method_values[] =\n{\n  { \"nearest\", CONFIG_GL_FILTER_NEAREST },\n  { \"linear\", CONFIG_GL_FILTER_LINEAR }\n};\n\nstatic const struct config_enum gl_vsync_values[] =\n{\n  { \"-1\", -1 },\n  { \"0\", 0 },\n  { \"1\", 1 },\n  { \"default\", -1 }\n};\n\nstatic const struct config_enum module_resample_mode_values[] =\n{\n  { \"none\", RESAMPLE_MODE_NONE },\n  { \"linear\", RESAMPLE_MODE_LINEAR },\n  { \"cubic\", RESAMPLE_MODE_CUBIC },\n  { \"fir\", RESAMPLE_MODE_FIR }\n};\n\nstatic const struct config_enum resample_mode_values[] =\n{\n  { \"none\", RESAMPLE_MODE_NONE },\n  { \"linear\", RESAMPLE_MODE_LINEAR },\n  { \"cubic\", RESAMPLE_MODE_CUBIC }\n};\n\nstatic const struct config_enum cursor_hint_type_values[] =\n{\n  { \"0\", CURSOR_MODE_INVISIBLE },\n  { \"1\", CURSOR_MODE_HINT },\n  { \"off\", CURSOR_MODE_INVISIBLE },\n  { \"hidden\", CURSOR_MODE_HINT },\n  { \"underline\", CURSOR_MODE_UNDERLINE },\n  { \"solid\", CURSOR_MODE_SOLID },\n};\n\nstatic const struct config_enum system_mouse_values[] =\n{\n  { \"0\", SYSTEM_MOUSE_OFF },\n  { \"1\", SYSTEM_MOUSE_ON },\n  { \"off\", SYSTEM_MOUSE_OFF },\n  { \"on\", SYSTEM_MOUSE_ON },\n  { \"only\", SYSTEM_MOUSE_HIDE_SOFTWARE_MOUSE },\n};\n\nstatic const struct config_enum screensaver_disable_values[] =\n{\n  { \"0\", SCREENSAVER_ENABLE },\n  { \"1\", SCREENSAVER_DISABLE },\n  //{ \"ingame\", SCREENSAVER_DISABLE_IN_GAME }\n};\n\n#ifdef CONFIG_NETWORK\nstatic const struct config_enum network_address_family_values[] =\n{\n  { \"0\", HOST_FAMILY_ANY },\n  { \"any\", HOST_FAMILY_ANY },\n  { \"ipv4\", HOST_FAMILY_IPV4 },\n  { \"ipv6\", HOST_FAMILY_IPV6 }\n};\n#endif\n\n#ifdef CONFIG_UPDATER\nstatic const struct config_enum update_auto_check_values[] =\n{\n  { \"0\", UPDATE_AUTO_CHECK_OFF },\n  { \"1\", UPDATE_AUTO_CHECK_ON },\n  { \"off\", UPDATE_AUTO_CHECK_OFF },\n  { \"on\", UPDATE_AUTO_CHECK_ON },\n  { \"silent\", UPDATE_AUTO_CHECK_SILENT }\n};\n#endif\n\nstatic const struct config_enum video_ratio_values[] =\n{\n  { \"classic\", RATIO_CLASSIC_4_3 },\n  { \"modern\", RATIO_MODERN_64_35 },\n  { \"stretch\", RATIO_STRETCH }\n};\n\nstruct config_registry_data\n{\n  void *conf;\n  find_change_option handler;\n};\n\nstruct config_registry_entry\n{\n  int num_registered;\n  struct config_registry_data registered[MAX_CONFIG_REGISTERED];\n};\n\nstatic struct config_registry_entry config_registry[NUM_CONFIG_TYPES];\n\n__editor_maybe_static\nvoid register_config(enum config_type type, void *conf, find_change_option handler)\n{\n  if(type < NUM_CONFIG_TYPES)\n  {\n    struct config_registry_entry *e = &config_registry[type];\n    if(e->num_registered < MAX_CONFIG_REGISTERED)\n    {\n      e->registered[e->num_registered].conf = conf;\n      e->registered[e->num_registered].handler = handler;\n      e->num_registered++;\n    }\n  }\n}\n\n__editor_maybe_static\nboolean config_int(int *dest, const char *value, int min, int max)\n{\n  int result;\n  int n;\n\n  if(sscanf(value, \"%d%n\", &result, &n) != 1 || value[n] != 0)\n    return false;\n\n  if(result < min || result > max)\n    return false;\n\n  *dest = result;\n  return true;\n}\n\nstatic boolean config_long_long(long long *dest, const char *value,\n long long min, long long max)\n{\n  long long result;\n  int n;\n\n  if(sscanf(value, \"%lld%n\", &result, &n) != 1 || value[n] != 0)\n    return false;\n\n  if(result < min || result > max)\n    return false;\n\n  *dest = result;\n  return true;\n}\n\n#define config_enum(d, v, a) _config_enum(d, v, a, ARRAY_SIZE(a))\n\n__editor_maybe_static\nboolean _config_enum(int *dest, const char *value,\n const struct config_enum *allowed, size_t num)\n{\n  size_t i;\n  for(i = 0; i < num; i++)\n  {\n    if(!strcasecmp(value, allowed[i].key))\n    {\n      *dest = allowed[i].value;\n      return true;\n    }\n  }\n  return false;\n}\n\n__editor_maybe_static\nboolean config_boolean(boolean *dest, const char *value)\n{\n  int result;\n  if(config_enum(&result, value, boolean_values))\n  {\n    *dest = (boolean)result;\n    return true;\n  }\n  return false;\n}\n\n#define config_string(d, v) _config_string(d, ARRAY_SIZE(d), v)\n\n__editor_maybe_static\nboolean _config_string(char *dest, size_t dest_len, const char *value)\n{\n  snprintf(dest, dest_len, \"%s\", value);\n  dest[dest_len - 1] = '\\0';\n  return true;\n}\n\n#ifdef CONFIG_NETWORK\n\nstatic void config_set_network_enabled(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->network_enabled, value);\n}\n\nstatic void config_set_network_address_family(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, network_address_family_values))\n    conf->network_address_family = result;\n}\n\nstatic void config_set_socks_host(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->socks_host, value);\n}\n\nstatic void config_set_socks_port(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 65535))\n    conf->socks_port = result;\n}\n\nstatic void config_set_socks_username(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->socks_username, value);\n}\n\nstatic void config_set_socks_password(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->socks_password, value);\n}\n\n#endif // CONFIG_NETWORK\n\n#ifdef CONFIG_UPDATER\n\nstatic void config_update_host(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  if(!conf->update_hosts || conf->update_hosts == default_update_hosts)\n  {\n    conf->update_hosts = (const char **)ccalloc(MAX_UPDATE_HOSTS, sizeof(char *));\n    conf->update_host_count = 0;\n  }\n\n  if(conf->update_host_count < MAX_UPDATE_HOSTS)\n  {\n    size_t size = strlen(value) + 1;\n    char *host = (char *)cmalloc(size);\n    memcpy(host, value, size);\n    conf->update_hosts[conf->update_host_count] = host;\n    conf->update_host_count++;\n  }\n}\n\nstatic void config_update_branch_pin(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->update_branch_pin, value);\n}\n\nstatic void config_update_auto_check(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, update_auto_check_values))\n    conf->update_auto_check = result;\n}\n\nstatic void config_set_updater_enabled(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->updater_enabled, value);\n}\n\n#endif // CONFIG_UPDATER\n\nstatic void config_set_audio_buffer(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 1, INT_MAX))\n    conf->audio_buffer_samples = result;\n}\n\nstatic void config_set_audio_channels(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 1, 2))\n    conf->audio_output_channels = result;\n}\n\nstatic void config_set_resolution(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int width;\n  int height;\n  int n;\n\n  if(sscanf(value, \"%d, %d%n\", &width, &height, &n) != 2 || value[n] ||\n   width < -1 || height < -1)\n    return;\n\n  conf->resolution_width = width;\n  conf->resolution_height = height;\n}\n\nstatic void config_set_dialog_cursor_hints(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, cursor_hint_type_values))\n    conf->cursor_hint_mode = result;\n}\n\nstatic void config_set_fullscreen(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->fullscreen, value);\n}\n\nstatic void config_set_fullscreen_windowed(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->fullscreen_windowed, value);\n}\n\nstatic void config_set_music(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->music_on, value);\n}\n\nstatic void config_set_mod_volume(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 10))\n    conf->music_volume = result;\n}\n\nstatic void config_set_mzx_speed(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 1, 16))\n    conf->mzx_speed = result;\n}\n\nstatic void config_set_pc_speaker(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->pc_speaker_on, value);\n}\n\nstatic void config_set_sam_volume(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 10))\n    conf->sam_volume = result;\n}\n\nstatic void config_save_file(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  if(CONF_SAFETY_CHECK_FAILURE(value))\n    return;\n\n  config_string(conf->default_save_name, value);\n}\n\nstatic void config_startup_file(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  if(CONF_SAFETY_CHECK_FAILURE(value))\n    return;\n\n  // If no startup_path has been set, set both startup_path and startup_file\n  // from this path. Otherwise, set startup_file and discard the directory\n  // portion of the path.\n  if(!conf->startup_path[0])\n  {\n    struct stat stat_info;\n\n    path_get_directory_and_filename(\n      conf->startup_path, sizeof(conf->startup_path),\n      conf->startup_file, sizeof(conf->startup_file),\n      value\n    );\n\n    // Make sure the startup path actually exists.\n    if(conf->startup_path[0] &&\n     (vstat(conf->startup_path, &stat_info) < 0 || !S_ISDIR(stat_info.st_mode)))\n      conf->startup_path[0] = '\\0';\n  }\n  else\n    path_get_filename(conf->startup_file, sizeof(conf->startup_file), value);\n}\n\nstatic void config_startup_path(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  struct stat stat_info;\n  if(vstat(value, &stat_info) || !S_ISDIR(stat_info.st_mode))\n    return;\n\n  snprintf(conf->startup_path, 256, \"%s\", value);\n  conf->startup_path[255] = '\\0';\n}\n\nstatic void config_system_mouse(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, system_mouse_values))\n    conf->system_mouse = result;\n}\n\nstatic void config_grab_mouse(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->grab_mouse, value);\n}\n\nstatic void config_disable_screensaver(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, screensaver_disable_values))\n    conf->disable_screensaver = result;\n}\n\nstatic void config_save_slots(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->save_slots, value);\n}\n\nstatic void config_save_slots_name(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->save_slots_name, value);\n}\n\nstatic void config_save_slots_ext(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->save_slots_ext, value);\n}\n\nstatic void config_enable_oversampling(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  boolean result;\n  if(config_boolean(&result, value))\n    conf->oversampling_on = result;\n}\n\nstatic void config_resample_mode(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, resample_mode_values))\n    conf->resample_mode = result;\n}\n\nstatic void config_mod_resample_mode(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, module_resample_mode_values))\n    conf->module_resample_mode = result;\n}\n\n#define JOY_ENUM \"%15[0-9A-Za-z_]\"\n\nstatic boolean joy_num(const char **name, unsigned int *first, unsigned int *last)\n{\n  int next = 0;\n  if(!strncasecmp(*name, \"joy\", 3))\n  {\n    *name += 3;\n\n    if(sscanf(*name, \"[%u,%u]%n\", first, last, &next) == 2)\n    {\n      *name += next;\n      return true;\n    }\n    else\n\n    if(sscanf(*name, \"%u%n\", first, &next) == 1)\n    {\n      *last = *first;\n      *name += next;\n      return true;\n    }\n  }\n  return false;\n}\n\nstatic boolean joy_button_name(const char *rest, unsigned int *button_num)\n{\n  int next = 0;\n  if(sscanf(rest, \"button%u%n\", button_num, &next) != 1 || (rest[next] != 0))\n    return false;\n  return true;\n}\n\nstatic boolean joy_axis_name(const char *rest, unsigned int *axis_num)\n{\n  int next = 0;\n  if(sscanf(rest, \"axis%u%n\", axis_num, &next) != 1 || (rest[next] != 0))\n    return false;\n  return true;\n}\n\nstatic boolean joy_axis_value(const char *value, char min[16], char max[16])\n{\n  int next = 0;\n  if(sscanf(value, JOY_ENUM \", \" JOY_ENUM \"%n\", min, max, &next) != 2\n   || (value[next] != 0))\n    return false;\n  return true;\n}\n\nstatic boolean joy_hat_value(const char *value, char up[16], char down[16],\n char left[16], char right[16])\n{\n  int next = 0;\n  if(sscanf(value, JOY_ENUM \", \" JOY_ENUM \", \" JOY_ENUM \", \" JOY_ENUM \"%n\",\n   up, down, left, right, &next) != 4 || (value[next] != 0))\n    return false;\n  return true;\n}\n\nstatic void joy_axis_set(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  unsigned int first, last, axis;\n  char min[16], max[16];\n\n  if(joy_num(&name, &first, &last) && joy_axis_name(name, &axis) &&\n   joy_axis_value(value, min, max))\n  {\n    // Right now do a global binding at startup and a game binding otherwise.\n    boolean is_startup = (current_config_type == SYSTEM_CNF);\n    joystick_map_axis(first - 1, last - 1, axis - 1, min, max, is_startup);\n  }\n}\n\nstatic void joy_button_set(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  unsigned int first, last, button;\n\n  if(joy_num(&name, &first, &last) && joy_button_name(name, &button))\n  {\n    // Right now do a global binding at startup and a game binding otherwise.\n    boolean is_startup = (current_config_type == SYSTEM_CNF);\n    joystick_map_button(first - 1, last - 1, button - 1, value, is_startup);\n  }\n}\n\nstatic void joy_hat_set(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  unsigned int first, last;\n  char up[16], down[16], left[16], right[16];\n\n  if(joy_num(&name, &first, &last) && !strcasecmp(name, \"hat\") &&\n   joy_hat_value(value, up, down, left, right))\n  {\n    // Right now do a global binding at startup and a game binding otherwise.\n    boolean is_startup = (current_config_type == SYSTEM_CNF);\n    joystick_map_hat(first - 1, last - 1, up, down, left, right, is_startup);\n  }\n}\n\nstatic void joy_action_set(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  unsigned int first, last;\n\n  if(joy_num(&name, &first, &last) && (*name == '.'))\n  {\n    // Right now do a global binding at startup and a game binding otherwise.\n    boolean is_startup = (current_config_type == SYSTEM_CNF);\n    joystick_map_action(first - 1, last - 1, name + 1, value, is_startup);\n  }\n}\n\nstatic void config_set_joy_axis_threshold(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  unsigned int threshold;\n  int read = 0;\n\n  if(sscanf(value, \"%u%n\", &threshold, &read) != 1 || (value[read] != 0) ||\n   threshold > 32767)\n    return;\n\n  joystick_set_axis_threshold(threshold);\n}\n\n#ifdef CONFIG_SDL\n#if SDL_VERSION_ATLEAST(2,0,0)\n#define GAMEPAD_ENUM \"%15[-+0-9A-Za-z_]\"\n\nstatic void config_gamepad_set(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  char gamepad_sym[16];\n  char key[16];\n  int read = 0;\n\n  // Option may be either \"gamepad.[enum]\" or \"gamecontroller.[enum]\".\n  if(!strncmp(name, \"gamepad\", 7))\n    name += 7;\n  else\n  if(!strncmp(name, \"gamecontroller\", 14))\n    name += 14;\n\n  if(sscanf(name, \".\" GAMEPAD_ENUM \"%n\", gamepad_sym, &read) != 1\n   || (name[read] != 0))\n    return;\n\n  read = 0;\n  if(sscanf(value, JOY_ENUM \"%n\", key, &read) != 1 || (value[read] != 0))\n    return;\n\n  gamepad_map_sym(gamepad_sym, key);\n}\n\nstatic void config_gamepad_add(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  gamepad_add_mapping(value);\n}\n\nstatic void config_gamepad_enable(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->allow_gamepad, value);\n}\n\n#endif // SDL_VERSION_ATLEAST(2,0,0)\n#endif // CONFIG_SDL\n\nstatic void pause_on_unfocus(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->pause_on_unfocus, value);\n}\n\nstatic void config_set_left_alt_is_altgr(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->key_left_alt_is_altgr, value);\n}\n\nstatic void config_set_right_alt_is_altgr(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->key_right_alt_is_altgr, value);\n}\n\nstatic void include_config(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  if(current_include_depth < MAX_INCLUDE_DEPTH)\n  {\n    current_include_depth++;\n\n    // The format \"include FILENAME.EXT\" is condensed into the name.\n    // The format \"include=FILENAME.EXT\" uses the value instead.\n    if(name[7])\n    {\n      set_config_from_file(current_config_type, name + 7);\n    }\n    else\n      set_config_from_file(current_config_type, value);\n\n    current_include_depth--;\n  }\n  else\n    warn(\"Failed to include '%s' (maximum recursion depth exceeded)\\n\", name + 7);\n}\n\nstatic void config_set_pcs_volume(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 10))\n    conf->pc_speaker_volume = result;\n}\n\nstatic void config_mask_midchars(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  // TODO move to editor config when non-editor code stops relying on it\n  config_boolean(&conf->mask_midchars, value);\n}\n\nstatic void config_set_audio_freq(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 1, INT_MAX))\n    conf->audio_sample_rate = result;\n}\n\nstatic void config_force_bpp(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, force_bpp_values))\n    conf->force_bpp = result;\n}\n\nstatic void config_window_resolution(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int width;\n  int height;\n  int n;\n\n  if(sscanf(value, \"%d, %d%n\", &width, &height, &n) != 2 || value[n] ||\n   width < -1 || height < -1)\n    return;\n\n  conf->window_width = width;\n  conf->window_height = height;\n}\n\nstatic void config_set_video_output(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->video_output, value);\n}\n\nstatic void config_enable_resizing(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->allow_resize, value);\n}\n\nstatic void config_set_gl_filter_method(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, gl_filter_method_values))\n    conf->gl_filter_method = (enum gl_filter_type)result;\n}\n\nstatic void config_set_gl_scaling_shader(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->gl_scaling_shader, value);\n}\n\nstatic void config_gl_vsync(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, gl_vsync_values))\n    conf->gl_vsync = result;\n}\n\nstatic void config_sdl_render_driver(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->sdl_render_driver, value);\n}\n\nstatic void config_set_allow_screenshots(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->allow_screenshots, value);\n}\n\nstatic void config_startup_editor(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->startup_editor, value);\n}\n\nstatic void config_standalone_mode(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->standalone_mode, value);\n}\n\nstatic void config_no_titlescreen(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->no_titlescreen, value);\n}\n\nstatic void config_set_allow_cheats(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, allow_cheats_values))\n    conf->allow_cheats = (enum allow_cheats_type)result;\n}\n\nstatic void config_set_auto_decrypt_worlds(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->auto_decrypt_worlds, value);\n}\n\nstatic void config_set_video_ratio(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, video_ratio_values))\n    conf->video_ratio = (enum ratio_type)result;\n}\n\nstatic void config_set_num_buffered_events(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 1, 256))\n    conf->num_buffered_events = result;\n}\n\nstatic void config_max_simultaneous_samples(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, -1, INT_MAX))\n    conf->max_simultaneous_samples = result;\n}\n\nstatic void config_test_mode(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->test_mode, value);\n}\n\nstatic void config_test_mode_start_board(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, MAX_BOARDS - 1))\n    conf->test_mode_start_board = result;\n}\n\nstatic void config_set_vfs_enable(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->vfs_enable, value);\n}\n\nstatic void config_set_vfs_enable_auto_cache(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->vfs_enable_auto_cache, value);\n}\n\nstatic void config_set_vfs_max_cache_size(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  long long result;\n  if(config_long_long(&result, value, 0, LLONG_MAX))\n    conf->vfs_max_cache_size = result;\n}\n\nstatic void config_set_vfs_max_cache_file_size(struct config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  long long result;\n  if(config_long_long(&result, value, 0, LLONG_MAX))\n    conf->vfs_max_cache_file_size = result;\n}\n\n/* NOTE: This is searched as a binary tree, the nodes must be\n *       sorted alphabetically, or they risk being ignored.\n */\nstatic const struct config_entry config_options[] =\n{\n  { \"allow_cheats\", config_set_allow_cheats, false },\n  { \"allow_screenshots\", config_set_allow_screenshots, false },\n  { \"audio_buffer\", config_set_audio_buffer, false },\n  { \"audio_buffer_samples\", config_set_audio_buffer, false },\n  { \"audio_output_channels\", config_set_audio_channels, false },\n  { \"audio_sample_rate\", config_set_audio_freq, false },\n  { \"auto_decrypt_worlds\", config_set_auto_decrypt_worlds, false },\n  { \"dialog_cursor_hints\", config_set_dialog_cursor_hints, false },\n  { \"disable_screensaver\", config_disable_screensaver, false },\n  { \"enable_oversampling\", config_enable_oversampling, false },\n  { \"enable_resizing\", config_enable_resizing, false },\n  { \"force_bpp\", config_force_bpp, false },\n  { \"fullscreen\", config_set_fullscreen, false },\n  { \"fullscreen_resolution\", config_set_resolution, false },\n  { \"fullscreen_windowed\", config_set_fullscreen_windowed, false },\n#ifdef CONFIG_SDL\n#if SDL_VERSION_ATLEAST(2,0,0)\n  { \"gamecontroller.*\", config_gamepad_set, false },\n  { \"gamecontroller_add\", config_gamepad_add, false },\n  { \"gamecontroller_enable\", config_gamepad_enable, false },\n  { \"gamepad.*\", config_gamepad_set, false },\n  { \"gamepad_add\", config_gamepad_add, false },\n  { \"gamepad_enable\", config_gamepad_enable, false },\n#endif\n#endif\n  { \"gl_filter_method\", config_set_gl_filter_method, false },\n  { \"gl_scaling_shader\", config_set_gl_scaling_shader, true },\n  { \"gl_vsync\", config_gl_vsync, false },\n  { \"grab_mouse\", config_grab_mouse, false },\n  { \"include*\", include_config, true },\n  { \"joy!.*\", joy_action_set, true },\n  { \"joy!axis!\", joy_axis_set, true },\n  { \"joy!button!\", joy_button_set, true },\n  { \"joy!hat\", joy_hat_set, true },\n  { \"joy[!,!].*\", joy_action_set, true },\n  { \"joy[!,!]axis!\", joy_axis_set, true },\n  { \"joy[!,!]button!\", joy_button_set, true },\n  { \"joy[!,!]hat\", joy_hat_set, true },\n  { \"joy_axis_threshold\", config_set_joy_axis_threshold, false },\n  { \"key_left_alt_is_altgr\", config_set_left_alt_is_altgr, false },\n  { \"key_right_alt_is_altgr\", config_set_right_alt_is_altgr, false },\n  { \"mask_midchars\", config_mask_midchars, false },\n  { \"max_simultaneous_samples\", config_max_simultaneous_samples, false },\n  { \"modplug_resample_mode\", config_mod_resample_mode, false },\n  { \"module_resample_mode\", config_mod_resample_mode, false },\n  { \"music_on\", config_set_music, false },\n  { \"music_volume\", config_set_mod_volume, false },\n  { \"mzx_speed\", config_set_mzx_speed, true },\n#ifdef CONFIG_NETWORK\n  { \"network_address_family\", config_set_network_address_family, false },\n  { \"network_enabled\", config_set_network_enabled, false },\n#endif\n  { \"no_titlescreen\", config_no_titlescreen, false },\n  { \"num_buffered_events\", config_set_num_buffered_events, false },\n  { \"pause_on_unfocus\", pause_on_unfocus, false },\n  { \"pc_speaker_on\", config_set_pc_speaker, false },\n  { \"pc_speaker_volume\", config_set_pcs_volume, false },\n  { \"resample_mode\", config_resample_mode, false },\n  { \"sample_volume\", config_set_sam_volume, false },\n  { \"save_file\", config_save_file, false },\n  { \"save_slots\", config_save_slots, false },\n  { \"save_slots_ext\", config_save_slots_ext, false },\n  { \"save_slots_name\", config_save_slots_name, false },\n  { \"sdl_render_driver\", config_sdl_render_driver, false },\n#ifdef CONFIG_NETWORK\n  { \"socks_host\", config_set_socks_host, false },\n  { \"socks_password\", config_set_socks_password, false },\n  { \"socks_port\", config_set_socks_port, false },\n  { \"socks_username\", config_set_socks_username, false },\n#endif\n  { \"standalone_mode\", config_standalone_mode, false },\n  { \"startup_editor\", config_startup_editor, false },\n  { \"startup_file\", config_startup_file, false },\n  { \"startup_path\", config_startup_path, false },\n  { \"system_mouse\", config_system_mouse, false },\n  { \"test_mode\", config_test_mode, false },\n  { \"test_mode_start_board\", config_test_mode_start_board, false },\n#ifdef CONFIG_UPDATER\n  { \"updater_enabled\", config_set_updater_enabled, false },\n  { \"update_auto_check\", config_update_auto_check, false },\n  { \"update_branch_pin\", config_update_branch_pin, false },\n  { \"update_host\", config_update_host, false },\n#endif\n  { \"vfs_enable\", config_set_vfs_enable, false },\n  { \"vfs_enable_auto_cache\", config_set_vfs_enable_auto_cache, false },\n  { \"vfs_max_cache_file_size\", config_set_vfs_max_cache_file_size, false },\n  { \"vfs_max_cache_size\", config_set_vfs_max_cache_size, false },\n  { \"video_output\", config_set_video_output, false },\n  { \"video_ratio\", config_set_video_ratio, false },\n  { \"window_resolution\", config_window_resolution, false }\n};\n\nstatic const struct config_entry *find_option(const char *name,\n const struct config_entry options[], int num_options)\n{\n  int cmpval, top = num_options - 1, middle, bottom = 0;\n  const struct config_entry *base = options;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    cmpval = match_function_counter(name, (base[middle]).option_name);\n\n    if(cmpval > 0)\n      bottom = middle + 1;\n    else\n\n    if(cmpval < 0)\n      top = middle - 1;\n    else\n      return base + middle;\n  }\n\n  return NULL;\n}\n\nstatic boolean config_change_option(void *_conf, const char *name,\n const char *value, const char *extended_data)\n{\n  const struct config_entry *current_option = find_option(name,\n   config_options, ARRAY_SIZE(config_options));\n\n  if(current_option)\n  {\n    if(current_option->allow_in_game_config || (current_config_type == SYSTEM_CNF))\n    {\n      struct config_info *conf = (struct config_info *)_conf;\n      current_option->change_option(conf, name, value, extended_data);\n      return true;\n    }\n  }\n  return false;\n}\n\n#define LINE_BUFFER_SIZE 512\n\nvoid set_config_from_file(enum config_type type, const char *conf_file_name)\n{\n  char current_char, *input_position, *output_position, *use_extended_buffer;\n  int line_size, extended_size, extended_allocate_size = 512;\n  char line_buffer_alternate[LINE_BUFFER_SIZE], line_buffer[LINE_BUFFER_SIZE];\n  int extended_buffer_offset, peek_char;\n  char *extended_buffer;\n  char *equals_position, *value;\n  char *output_end_position = line_buffer + LINE_BUFFER_SIZE;\n  vfile *conf_file;\n  int i;\n\n  if(type >= NUM_CONFIG_TYPES)\n    return;\n\n  if(CONF_SAFETY_CHECK_FAILURE(conf_file_name))\n  {\n    warn(\"Refused to load config file '%s'\\n\", conf_file_name);\n    return;\n  }\n\n  conf_file = vfopen_unsafe(conf_file_name, \"rb\");\n  if(!conf_file)\n    return;\n\n  extended_buffer = (char *)cmalloc(extended_allocate_size);\n\n  while(vfsafegets(line_buffer_alternate, LINE_BUFFER_SIZE, conf_file))\n  {\n    if(line_buffer_alternate[0] != '#')\n    {\n      input_position = line_buffer_alternate;\n      output_position = line_buffer;\n      equals_position = NULL;\n\n      do\n      {\n        current_char = *input_position;\n\n        if(!isspace((int)current_char))\n        {\n          if((current_char == '\\\\') &&\n            (input_position[1] == 's'))\n          {\n            input_position++;\n            current_char = ' ';\n          }\n\n          if(output_position < output_end_position)\n          {\n            if((current_char == '=') && (equals_position == NULL))\n              equals_position = output_position;\n\n            *output_position = current_char;\n            output_position++;\n          }\n        }\n        input_position++;\n      } while(current_char);\n\n      line_buffer[LINE_BUFFER_SIZE - 1] = 0;\n\n      if(equals_position)\n      {\n        *equals_position = 0;\n        value = equals_position + 1;\n      }\n      else\n      {\n        value = (char *)\"1\";\n      }\n\n      if(line_buffer[0])\n      {\n        // There might be extended information too - get it.\n        peek_char = vfgetc(conf_file);\n        extended_size = 1;\n        extended_buffer_offset = 0;\n        use_extended_buffer = NULL;\n        extended_buffer[0] = '\\0';\n\n        while((peek_char == ' ') || (peek_char == '\\t'))\n        {\n          // Extended data line\n          use_extended_buffer = extended_buffer;\n          if(vfsafegets(line_buffer_alternate, 254, conf_file))\n          {\n            // Skip any extra whitespace at the start of the line...\n            char *line_buffer_pos = line_buffer_alternate;\n            while(*line_buffer_pos && isspace((int)*line_buffer_pos))\n              line_buffer_pos++;\n\n            line_size = (int)strlen(line_buffer_pos);\n            line_buffer_pos[line_size++] = '\\n';\n            line_buffer_pos[line_size] = '\\0';\n\n            extended_size += line_size;\n            if(extended_size >= extended_allocate_size)\n            {\n              extended_allocate_size *= 2;\n              extended_buffer = (char *)crealloc(extended_buffer, extended_allocate_size);\n              use_extended_buffer = extended_buffer;\n            }\n\n            memcpy(extended_buffer + extended_buffer_offset,\n             line_buffer_pos, line_size + 1);\n            extended_buffer_offset += line_size;\n          }\n\n          peek_char = vfgetc(conf_file);\n        }\n        vungetc(peek_char, conf_file);\n\n        for(i = 0; i < config_registry[type].num_registered; i++)\n        {\n          struct config_registry_data *d = &config_registry[type].registered[i];\n          current_config_type = type;\n          if(d->handler(d->conf, line_buffer, value, use_extended_buffer))\n            break;\n        }\n      }\n    }\n  }\n\n  free(extended_buffer);\n  vfclose(conf_file);\n}\n\nvoid set_config_from_command_line(int *argc, char *argv[])\n{\n  char current_char, *input_position, *output_position;\n  char *equals_position, line_buffer[LINE_BUFFER_SIZE], *value;\n  char *output_end_position = line_buffer + LINE_BUFFER_SIZE;\n  int i = 1;\n  int j;\n  int k;\n\n  while(i < *argc)\n  {\n    input_position = argv[i];\n    output_position = line_buffer;\n    equals_position = NULL;\n\n    do\n    {\n      current_char = *input_position;\n\n      if((current_char == '\\\\') &&\n       (input_position[1] == 's'))\n      {\n        input_position++;\n        current_char = ' ';\n      }\n\n      if(output_position < output_end_position)\n      {\n        if((current_char == '=') && (equals_position == NULL))\n          equals_position = output_position;\n\n        *output_position = current_char;\n        output_position++;\n      }\n      input_position++;\n    } while(current_char);\n\n    line_buffer[LINE_BUFFER_SIZE - 1] = 0;\n\n    if(equals_position && line_buffer[0])\n    {\n      *equals_position = 0;\n      value = equals_position + 1;\n\n      for(k = 0; k < config_registry[SYSTEM_CNF].num_registered; k++)\n      {\n        struct config_registry_data *d = &config_registry[SYSTEM_CNF].registered[k];\n        current_config_type = SYSTEM_CNF;\n        if(d->handler(d->conf, line_buffer, value, NULL))\n        {\n          // Found the option; remove it from argv and make sure i stays the same\n          for(j = i; j < *argc - 1; j++)\n            argv[j] = argv[j + 1];\n          (*argc)--;\n          i--;\n          break;\n        }\n      }\n    }\n\n    i++;\n  }\n}\n\n/* Set both the startup path and startup file from a string (usually argv[1]).\n * This should be called from the startup directory, as the input path may\n * be relative.\n */\nvoid set_config_startup_path_and_file(const char *path)\n{\n  char path_backup[sizeof(user_conf.startup_path)];\n  memcpy(path_backup, user_conf.startup_path, sizeof(path_backup));\n\n  /* Use the startup_file path splitting logic instead of duplicating it. */\n  user_conf.startup_path[0] = '\\0';\n  config_startup_file(&user_conf, \"startup_file\", path, NULL);\n\n  /* Restore old path if that didn't add one. */\n  if(user_conf.startup_path[0] == '\\0')\n    memcpy(user_conf.startup_path, path_backup, sizeof(path_backup));\n}\n\nstruct config_info *get_config(void)\n{\n  return &user_conf;\n}\n\nvoid default_config(void)\n{\n  static boolean registered = false;\n  memcpy(&user_conf, &user_conf_default, sizeof(struct config_info));\n\n  if(!registered)\n  {\n    register_config(SYSTEM_CNF, &user_conf, config_change_option);\n    register_config(GAME_CNF, &user_conf, config_change_option);\n    register_config(GAME_EDITOR_CNF, &user_conf, config_change_option);\n    registered = true;\n  }\n}\n\nvoid free_config(void)\n{\n#ifdef CONFIG_UPDATER\n  // Custom updater hosts might have been allocated\n  if(user_conf.update_hosts != default_update_hosts)\n  {\n    int i;\n\n    for(i = 0; i < user_conf.update_host_count; i++)\n      free((char *)user_conf.update_hosts[i]);\n\n    free(user_conf.update_hosts);\n    user_conf.update_hosts = default_update_hosts;\n  }\n#endif\n}\n"
  },
  {
    "path": "src/configure.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __CONFIGURE_H\n#define __CONFIGURE_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stdint.h>\n\nenum config_type\n{\n  SYSTEM_CNF,\n  GAME_CNF,\n  GAME_EDITOR_CNF,\n  NUM_CONFIG_TYPES\n};\n\nenum force_bpp_special\n{\n  BPP_AUTO = 0\n};\n\nenum ratio_type\n{\n  RATIO_CLASSIC_4_3,\n  RATIO_MODERN_64_35,\n  RATIO_STRETCH,\n  NUM_RATIO_TYPES\n};\n\nenum resample_mode\n{\n  RESAMPLE_MODE_NONE,\n  RESAMPLE_MODE_LINEAR,\n  RESAMPLE_MODE_CUBIC,\n  RESAMPLE_MODE_FIR,\n  NUM_RESAMPLE_MODES\n};\n\nenum gl_filter_type\n{\n  CONFIG_GL_FILTER_NEAREST,\n  CONFIG_GL_FILTER_LINEAR,\n  NUM_GL_FILTER_TYPES\n};\n\nenum system_mouse_type\n{\n  SYSTEM_MOUSE_OFF,\n  SYSTEM_MOUSE_ON,\n  SYSTEM_MOUSE_HIDE_SOFTWARE_MOUSE,\n  NUM_SYSTEM_MOUSE_TYPES\n};\n\nenum cursor_mode_types\n{\n  CURSOR_MODE_UNDERLINE,  // Underline for text entry (insert).\n  CURSOR_MODE_SOLID,      // Solid for text entry (overwrite) and editing.\n  CURSOR_MODE_HINT,       // Hidden cursor for active UI element hints.\n  CURSOR_MODE_INVISIBLE,  // Cursor disabled.\n  NUM_CURSOR_MODE_TYPES\n};\n\nenum screensaver_disable_mode\n{\n  SCREENSAVER_ENABLE,\n  SCREENSAVER_DISABLE,\n  SCREENSAVER_DISABLE_IN_GAME,\n  NUM_SCREENSAVER_MODES\n};\n\nenum allow_cheats_type\n{\n  ALLOW_CHEATS_NEVER,\n  ALLOW_CHEATS_MZXRUN,\n  ALLOW_CHEATS_ALWAYS,\n  NUM_ALLOW_CHEATS_TYPES\n};\n\nenum host_family\n{\n  /** Prefer IPv4 address resolution for hostnames */\n  HOST_FAMILY_IPV4,\n  /** Prefer IPv6 address resolution for hostnames */\n  HOST_FAMILY_IPV6,\n  /** Allow either IPv4 or IPv6 addresses. */\n  HOST_FAMILY_ANY,\n  NUM_HOST_FAMILIES\n};\n\nenum update_auto_check_mode\n{\n  UPDATE_AUTO_CHECK_OFF = 0,\n  UPDATE_AUTO_CHECK_ON,\n  UPDATE_AUTO_CHECK_SILENT\n};\n\nstruct config_info\n{\n  // Video options\n  boolean fullscreen;\n  boolean fullscreen_windowed;\n  int resolution_width;\n  int resolution_height;\n  int window_width;\n  int window_height;\n  boolean allow_resize;\n  char video_output[16];\n  int force_bpp;\n  enum ratio_type video_ratio;\n  enum gl_filter_type gl_filter_method;\n  int gl_vsync;\n  char gl_scaling_shader[32];\n  char sdl_render_driver[16];\n  enum cursor_mode_types cursor_hint_mode;\n  enum screensaver_disable_mode disable_screensaver;\n  boolean allow_screenshots;\n\n  // Audio options\n  int audio_sample_rate;\n  int audio_buffer_samples;\n  int audio_output_channels;\n  boolean oversampling_on;\n  enum resample_mode resample_mode;\n  enum resample_mode module_resample_mode;\n  int max_simultaneous_samples;\n  int music_volume;\n  int sam_volume;\n  int pc_speaker_volume;\n  boolean music_on;\n  boolean pc_speaker_on;\n\n  // Event options\n  boolean allow_gamepad;\n  boolean pause_on_unfocus;\n  boolean key_left_alt_is_altgr;\n  boolean key_right_alt_is_altgr;\n  int num_buffered_events;\n\n  // Virtual filesystem options\n  boolean vfs_enable;\n  boolean vfs_enable_auto_cache;\n  int64_t vfs_max_cache_size;\n  int64_t vfs_max_cache_file_size;\n\n  // Game options\n  char startup_path[256];\n  char startup_file[256];\n  char default_save_name[256];\n  int mzx_speed;\n  enum allow_cheats_type allow_cheats;\n  boolean auto_decrypt_worlds;\n  boolean startup_editor;\n  boolean standalone_mode;\n  boolean no_titlescreen;\n  enum system_mouse_type system_mouse;\n  boolean grab_mouse;\n  boolean save_slots;\n  char save_slots_name[256];\n  char save_slots_ext[256];\n\n  // Editor options\n  boolean test_mode;\n  unsigned char test_mode_start_board;\n  // TODO: two places outside of the editor currently require access to this.\n  boolean mask_midchars;\n\n  // Network layer options\n#ifdef CONFIG_NETWORK\n  boolean network_enabled;\n  enum host_family network_address_family;\n  char socks_host[256];\n  char socks_username[256];\n  char socks_password[256];\n  int socks_port;\n#endif\n\n#ifdef CONFIG_UPDATER\n  boolean updater_enabled;\n  int update_host_count;\n  const char **update_hosts;\n  char update_branch_pin[256];\n  int update_auto_check;\n#endif\n};\n\n/**\n * Used to create arrays containing all allowed values for certain options.\n */\nstruct config_enum\n{\n  const char * const key;\n  const int value;\n};\n\nCORE_LIBSPEC struct config_info *get_config(void);\nCORE_LIBSPEC void default_config(void);\nCORE_LIBSPEC void set_config_from_file(enum config_type type,\n const char *conf_file_name);\nCORE_LIBSPEC void set_config_from_command_line(int *argc, char *argv[]);\nCORE_LIBSPEC void set_config_startup_path_and_file(const char *path);\nCORE_LIBSPEC void free_config(void);\n\ntypedef boolean (*find_change_option)(void *conf, const char *name,\n const char *value, const char *extended_data);\n\n#ifdef CONFIG_EDITOR\n\nCORE_LIBSPEC void register_config(enum config_type type, void *conf,\n find_change_option handler);\n\nCORE_LIBSPEC boolean config_int(int *dest, const char *value, int min, int max);\nCORE_LIBSPEC boolean _config_enum(int *dest, const char *value,\n const struct config_enum *allowed, size_t num);\nCORE_LIBSPEC boolean config_boolean(boolean *dest, const char *value);\nCORE_LIBSPEC boolean _config_string(char *dest, size_t dest_len, const char *value);\n\n#define config_enum(d, v, a) _config_enum(d, v, a, ARRAY_SIZE(a))\n#define config_string(d, v) _config_string(d, ARRAY_SIZE(d), v)\n\n#endif // CONFIG_EDITOR\n\n__M_END_DECLS\n\n#endif // __CONFIGURE_H\n"
  },
  {
    "path": "src/const.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Constants */\n#ifndef __CONST_H\n#define __CONST_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\nenum\n{\n  EXPL_LEAVE_SPACE,\n  EXPL_LEAVE_ASH,\n  EXPL_LEAVE_FIRE\n};\n\nenum\n{\n  CAN_SAVE,\n  CANT_SAVE,\n  CAN_SAVE_ON_SENSOR\n};\n\nenum\n{\n  FOREST_TO_EMPTY,\n  FOREST_TO_FLOOR\n};\n\nenum\n{\n  FIRE_BURNS_LIMITED,\n  FIRE_BURNS_FOREVER\n};\n\nenum\n{\n  OVERLAY_OFF,\n  OVERLAY_ON,\n  OVERLAY_STATIC,\n  OVERLAY_TRANSPARENT,\n  OVERLAY_FLAG_HIDE_BOARD = (1 << 6),\n  OVERLAY_FLAG_HIDE_OVERLAY = (1 << 7),\n  OVERLAY_MODE_MASK = 0x03\n};\n\nenum spittingtiger_moves\n{\n  SPITTINGTIGER_MOVES_NORMALLY              = 0,\n  SPITTINGTIGER_MOVES_SLOWLY                = 1,\n  SPITTINGTIGER_MOVES_SLOWLY_SELF_DESTRUCTS = 2\n};\n\nenum\n{\n  PLAYER_BULLET,\n  NEUTRAL_BULLET,\n  ENEMY_BULLET\n};\n\n#define NO_BOARD              255\n#define NO_ENDGAME_BOARD      255\n#define NO_DEATH_BOARD        255\n#define TEMPORARY_BOARD       255\n#define DEATH_SAME_POS        254\n\n#define NO_KEY                127\n\n// Length of time to display the message and the intro message.\n#define MESG_TIMEOUT          160\n\n// \"BOARD_NAME_SIZE\"/\"ROBOT_NAME_SIZE\"/\"COUNTER_NAME_SIZE\" include terminator\n#define MAX_BOARDS            250\n#define MAX_BOARD_SIZE        16 * 1024 * 1024\n#define BOARD_NAME_SIZE       25\n#define ROBOT_NAME_SIZE       15\n#define COUNTER_NAME_SIZE     15 // This is legacy, for status counters only\n#define NUM_KEYS              16\n\n// Safe duplicates of the above strings in case those limits are lifted.\n#define LEGACY_BOARD_NAME_SIZE 25\n#define LEGACY_ROBOT_NAME_SIZE 15\n\n// Attribute flags\n#define A_PUSHNS              (1 << 0)\n#define A_PUSHEW              (1 << 1)\n#define A_PUSHABLE            (A_PUSHNS | A_PUSHEW)\n#define A_ITEM                (1 << 2)\n#define A_UPDATE              (1 << 3)\n#define A_HURTS               (1 << 4)\n#define A_UNDER               (1 << 5)\n#define A_ENTRANCE            (1 << 6)\n#define A_EXPLODE             (1 << 7)\n#define A_BLOW_UP             (1 << 8)\n#define A_SHOOTABLE           (1 << 9)\n#define A_ENEMY               (1 << 10)\n#define A_AFFECT_IF_STOOD     (1 << 11)\n#define A_SPEC_SHOT           (1 << 12)\n#define A_SPEC_PUSH           (1 << 13)\n#define A_SPEC_BOMB           (1 << 14)\n#define A_SPEC_STOOD          (1 << 15)\n\n#define NUM_STATUS_COUNTERS   6\n\n#define ROBOT_MAX_TR 512\n\n__M_END_DECLS\n\n#endif // __CONST_H\n"
  },
  {
    "path": "src/core.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* MegaZeux main loop and context handling code. */\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"caption.h\"\n#include \"counter.h\"\n#include \"configure.h\"\n#include \"core.h\"\n#include \"error.h\"\n#include \"event.h\"\n#include \"game_menu.h\"\n#include \"graphics.h\"\n#include \"helpsys.h\"\n#include \"platform.h\"\n#include \"settings.h\"\n#include \"util.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n\n#define MAX_NUM_CALLBACKS 8\n\nstatic int unique = 0;\n\nstruct context_stack\n{\n  context **contents;\n  int alloc;\n  int size;\n  int pos;\n  boolean removed;\n};\n\n/**\n * Used as a context to help create the first actual context.\n * Stores the context stack.\n */\nstruct core_context\n{\n  context ctx;\n  boolean first_run;\n  boolean full_exit;\n  boolean restart_on_exit;\n  boolean context_changed;\n  struct context_stack stack;\n};\n\n/**\n * Contains data for callbacks added to a context.\n */\nstruct context_callback_data\n{\n  context *ctx_for_callback;\n  context_callback_param *param_for_callback;\n  context_callback_function callback;\n};\n\n\n/**\n * Contains internal context data used to run contexts.\n */\nstruct context_data\n{\n  context *parent;\n  boolean is_subcontext;\n  enum context_type context_type;\n  enum framerate_type framerate;\n  struct context_stack stack;\n  struct context_spec functions;\n  struct context_callback_data callbacks[MAX_NUM_CALLBACKS];\n  int cur_callback;\n  int num_callbacks;\n  int unique;\n  int priority;\n};\n\n#ifdef CONFIG_FPS\n#define FPS_HISTORY_SIZE 5\n#define FPS_INTERVAL 1000\n#include <math.h>\n\nstruct fps_history_entry\n{\n  int frames;\n  int delta;\n  double ratio;\n};\n\nstatic double average_fps;\n\n/**\n * Track the current speed MegaZeux is running in cycles.\n */\nstatic void update_fps(int64_t current_ticks)\n{\n  static struct fps_history_entry history[FPS_HISTORY_SIZE];\n  static int64_t fps_previous_ticks = -1;\n  static int frames_counted;\n  static int pos;\n  int i;\n\n  int delta_ticks = current_ticks - fps_previous_ticks;\n  frames_counted++;\n\n  if(fps_previous_ticks == -1)\n  {\n    fps_previous_ticks = current_ticks;\n  }\n  else\n\n  if(delta_ticks >= FPS_INTERVAL)\n  {\n    double min_ratio = INFINITY;\n    double max_ratio = -INFINITY;\n    int min_pos = 0;\n    int max_pos = 0;\n    int total_frames = 0;\n    int total_delta = 0;\n    int count = 0;\n\n    history[pos].frames = frames_counted;\n    history[pos].delta = delta_ticks;\n    history[pos].ratio = (double)frames_counted / (double)delta_ticks;\n    if((++pos) >= FPS_HISTORY_SIZE)\n      pos = 0;\n\n    for(i = 0; i < FPS_HISTORY_SIZE; i++)\n    {\n      if(history[i].delta <= 0)\n        continue;\n\n      if(history[i].ratio > max_ratio)\n      {\n        max_ratio = history[i].ratio;\n        max_pos = i;\n      }\n      if(history[i].ratio < min_ratio)\n      {\n        min_ratio = history[i].ratio;\n        min_pos = i;\n      }\n      total_frames += history[i].frames;\n      total_delta += history[i].delta;\n      count++;\n    }\n    if(count > 2)\n    {\n      // Subtract off highest and lowest scores (outliers)\n      total_frames -= history[min_pos].frames + history[max_pos].frames;\n      total_delta -= history[min_pos].delta + history[max_pos].delta;\n      average_fps = (double)total_frames * 1000.0 / (double)total_delta;\n\n      caption_set_fps(average_fps);\n    }\n    fps_previous_ticks = current_ticks;\n    frames_counted = 0;\n  }\n}\n#endif\n\n#define CTX_NAME_MAX_SIZE 16\n\n/**\n * Get the name of a context from its ID (see core.h)\n */\nstatic const char *get_ctx_name(enum context_type id)\n{\n  switch(id)\n  {\n    // Core contexts.\n    case CTX_TITLE_SCREEN:      return \"Title screen\";\n    case CTX_MAIN:              return \"(help main page)\";\n    case CTX_PLAY_GAME:         return \"Gameplay\";\n    case CTX_CONFIGURE:         return \"Settings ed.\";\n    case CTX_DIALOG_BOX:        return \"Dialog\";\n    case CTX_HELP_SYSTEM:       return \"Help system\";\n    case CTX_MAIN_MENU:         return \"Main menu\";\n    case CTX_GAME_MENU:         return \"Game menu\";\n    case CTX_INTAKE_NUM:        return \"(intake number)\";\n    case CTX_TASK:              return \"(running task)\";\n\n    // Network contexts.\n    case CTX_UPDATER:           return \"Updater\";\n\n    // Editor contexts.\n    case CTX_EDITOR:            return \"Editor\";\n    case CTX_EDITOR_VIEW_BOARD: return \"(view board)\";\n    case CTX_THING_MENU:        return \"Thing menu\";\n    case CTX_BLOCK_CMD:         return \"Block command\";\n    case CTX_BLOCK_TYPE:        return \"Block type\";\n    case CTX_CHOOSE_CHARSET:    return \"Select charset\";\n    case CTX_IMPORTEXPORT_TYPE: return \"Import/export\";\n    case CTX_CHAR_EDIT:         return \"Char editor\";\n    case CTX_STATUS_COUNTERS:   return \"Status counters\";\n    case CTX_BOARD_EXITS:       return \"Board exits\";\n    case CTX_BOARD_SIZES:       return \"Board sizes\";\n    case CTX_BOARD_INFO:        return \"Board info\";\n    case CTX_CHANGE_CHAR_IDS:   return \"Char ID table\";\n    case CTX_CHANGE_DAMAGE:     return \"Damage table\";\n    case CTX_GLOBAL_SETTINGS:   return \"Global settings1\";\n    case CTX_GLOBAL_SETTINGS_2: return \"Global settings2\";\n    case CTX_ROBO_ED:           return \"Robot editor\";\n    case CTX_PALETTE_EDITOR:    return \"Palette editor\";\n    case CTX_SENSOR_EDITOR:     return \"Sensor param\";\n    case CTX_SUPER_MEGAZEUX:    return \"Select SMZX mode\";\n    case CTX_SFX_EDITOR:        return \"SFX editor\";\n    case CTX_COUNTER_DEBUG:     return \"Counter debugger\";\n    case CTX_ROBOT_DEBUG:       return \"Robot debugger\";\n    case CTX_BREAKPOINT_EDITOR: return \"Robot dbg. conf.\";\n    case CTX_VLAYER_SIZES:      return \"Vlayer sizes\";\n  }\n  return \"?????\";\n}\n\n/**\n * Debug: print a line of the context stack.\n */\nstatic void print_ctx_line(context_data *ctx_data)\n{\n  char name[CTX_NAME_MAX_SIZE + 1] = \"  -> subcontext\";\n  char cbs_str[12] = \"\";\n  char pri_str[12] = \"\";\n  const char *framerate_str = \"-\";\n  boolean click_drag_same = false;\n\n  if(!ctx_data->is_subcontext)\n  {\n    snprintf(name, CTX_NAME_MAX_SIZE, \"%s\",\n     get_ctx_name(ctx_data->context_type));\n\n    if(ctx_data->num_callbacks)\n      snprintf(cbs_str, 12, \"%d\", ctx_data->num_callbacks);\n\n    switch(ctx_data->framerate)\n    {\n      case FRAMERATE_UI:            framerate_str = \"UI \"; break;\n      case FRAMERATE_UI_INTERRUPT:  framerate_str = \"Int\"; break;\n      case FRAMERATE_MZX_SPEED:     framerate_str = \"MZX\"; break;\n      default:                      framerate_str = \"???\"; break;\n    }\n  }\n\n  if(ctx_data->priority)\n  {\n    snprintf(pri_str, 12, \"%d\", ctx_data->priority);\n    pri_str[11] = 0;\n  }\n\n  if(ctx_data->functions.click == ctx_data->functions.drag)\n    click_drag_same = true;\n\n  fprintf(mzxerr, \"%-*.*s | %3s %3s %3s %3s %3s %3s %3s %3s | %-3s %3s %s\\n\",\n    16, 16, name,\n    ctx_data->functions.resume    ? \"Yes\" : \"\",\n    ctx_data->functions.draw      ? \"Yes\" : \"\",\n    ctx_data->functions.idle      ? \"Yes\" : \"\",\n    ctx_data->functions.key       ? \"Yes\" : \"\",\n    ctx_data->functions.joystick  ? \"Yes\" : \"\",\n    ctx_data->functions.click     ? \"Yes\" : \"\",\n    ctx_data->functions.drag      ? click_drag_same ? \"<- \" : \"Yes\" : \"\",\n    ctx_data->functions.destroy   ? \"Yes\" : \"\",\n    framerate_str,\n    cbs_str,\n    pri_str\n  );\n}\n\n/**\n * Debug: print the entire context stack to the terminal.\n */\n\n#define print_core_stack(ctx) __print_core_stack(ctx, __FILE__, __LINE__)\n\nstatic void __print_core_stack(context *_ctx, const char *file, int line)\n{\n  core_context *root;\n  context_data *ctx_data;\n  context_data *sub_data;\n  int i;\n  int i2;\n\n  if(file)\n    fprintf(mzxerr, \"%s line %d:\\n\", file, line);\n\n  if(!_ctx)\n  {\n    fprintf(mzxerr, \"Context is NULL!\\n\");\n    fflush(mzxerr);\n    return;\n  }\n  else\n\n  if(!_ctx->root)\n  {\n    fprintf(mzxerr, \"Context root is NULL!\\n\");\n    fflush(mzxerr);\n    return;\n  }\n\n  root = _ctx->root;\n\n  fprintf(mzxerr,\n   \"CONTEXT STACK    | Res Drw Idl Key Joy Clk Drg Dst | Fr. CbQ Pri\\n\"\n   \"-----------------|---------------------------------|-------------\\n\");\n\n  for(i = root->stack.size - 1; i >= 0; i--)\n  {\n    ctx_data = root->stack.contents[i]->internal_data;\n\n    for(i2 = ctx_data->stack.size - 1; i2 >= 0; i2--)\n    {\n      sub_data = ctx_data->stack.contents[i2]->internal_data;\n      print_ctx_line(sub_data);\n    }\n\n    print_ctx_line(ctx_data);\n  }\n  fprintf(mzxerr, \"\\n\");\n  fflush(mzxerr);\n}\n\n/**\n * Add an element to a stack\n */\nstatic void add_stack(struct context_stack *stack, context *add)\n{\n  if(stack->size >= stack->alloc)\n  {\n    if(stack->alloc == 0)\n      stack->alloc = 8;\n\n    while(stack->size >= stack->alloc)\n      stack->alloc *= 2;\n\n    stack->contents = crealloc(stack->contents, stack->alloc * sizeof(void *));\n  }\n\n  stack->contents[stack->size] = add;\n  stack->size++;\n}\n\n/**\n * Remove an element from a stack.\n */\nstatic int remove_stack(struct context_stack *stack, context *del)\n{\n  int i;\n\n  for(i = stack->size - 1; i >= 0; i--)\n  {\n    if(stack->contents[i] == del)\n    {\n      if(i < stack->size - 1)\n        memmove(stack->contents + i, stack->contents + i + 1,\n         stack->size - i - 1);\n\n      stack->size--;\n      stack->removed = true;\n      return i;\n    }\n  }\n\n  error_message(E_CORE_FATAL_BUG, 6, NULL);\n  return -1;\n}\n\n/**\n * Configures a provided context and adds it to the context stack to be run\n * by the main loop. The assumption here is that ctx will be some other local\n * struct casted to a context.\n *\n * At least one update function needs to be provided capable of destroying the\n * context; otherwise, MegaZeux would hang up trying to execute this context.\n */\nvoid create_context(context *ctx, context *parent,\n struct context_spec *ctx_spec, enum context_type context_type)\n{\n  core_context *root;\n  context_data *ctx_data;\n\n  if(parent == NULL || !ctx_spec ||\n   (ctx_spec->resume == NULL && ctx_spec->draw == NULL &&\n    ctx_spec->key == NULL && ctx_spec->joystick == NULL &&\n    ctx_spec->click == NULL && ctx_spec->drag == NULL &&\n    ctx_spec->idle == NULL))\n  {\n    print_core_stack(parent);\n    error_message(E_CORE_FATAL_BUG, 1, NULL);\n    return;\n  }\n\n  // If the parent is a subcontext, try to find the real parent context.\n  while(parent->internal_data && parent->internal_data->parent &&\n   parent->internal_data->is_subcontext)\n    parent = parent->internal_data->parent;\n\n  // Root needs to exist so this context can be added to the stack.\n  if(!parent->root)\n  {\n    print_core_stack(parent);\n    error_message(E_CORE_FATAL_BUG, 7, NULL);\n    return;\n  }\n\n  if(!ctx) ctx = cmalloc(sizeof(struct context));\n  ctx_data = cmalloc(sizeof(struct context_data));\n\n  ctx->root = parent->root;\n  ctx->internal_data = ctx_data;\n  ctx->world = parent->world;\n  ctx_data->parent = NULL;\n  ctx_data->context_type = context_type;\n  ctx_data->framerate = ctx_spec->framerate_mode;\n  ctx_data->is_subcontext = false;\n  ctx_data->cur_callback = 0;\n  ctx_data->num_callbacks = 0;\n  ctx_data->unique = unique++;\n  ctx_data->priority = ctx_spec->priority;\n  memset(&(ctx_data->stack), 0, sizeof(struct context_stack));\n  memcpy(&(ctx_data->functions), ctx_spec, sizeof(struct context_spec));\n\n  // Add the new context to the stack.\n  root = parent->root;\n  add_stack(&(root->stack), ctx);\n\n  root->context_changed = true;\n}\n\n/**\n * Creates a subcontext and adds it to the parent context.\n */\nCORE_LIBSPEC void create_subcontext(subcontext *sub, context *parent,\n struct context_spec *sub_spec)\n{\n  core_context *root;\n  context_data *parent_data;\n  context_data *sub_data;\n\n  // If the parent is a subcontext, try to find the real parent context.\n  while(parent && parent->internal_data && parent->internal_data->is_subcontext)\n    parent = parent->internal_data->parent;\n\n  // Root context must exit, parent must not be the root context, and make sure\n  // this isn't some weird glitched context.\n  if(!parent || !parent->root || parent == (context *)(parent->root) ||\n   !parent->internal_data || parent->internal_data->parent || !sub_spec)\n  {\n    print_core_stack(parent);\n    error_message(E_CORE_FATAL_BUG, 8, NULL);\n    return;\n  }\n\n  root = parent->root;\n  parent_data = parent->internal_data;\n\n  if(!sub) sub = cmalloc(sizeof(struct context));\n  sub_data = cmalloc(sizeof(struct context_data));\n\n  sub->root = root;\n  sub->internal_data = sub_data;\n  sub->world = parent->world;\n  sub_data->parent = parent;\n  sub_data->is_subcontext = true;\n  sub_data->unique = unique++;\n  sub_data->priority = sub_spec->priority;\n  memcpy(&(sub_data->functions), sub_spec, sizeof(struct context_spec));\n\n  // Add the subcontext to the parent's stack.\n  add_stack(&(parent_data->stack), sub);\n}\n\n/**\n * Destroy the target context or subcontext from its parent stack.\n * Flag the core_context to abort further execution of the cycle if the root\n * context stack is changed.\n */\nvoid destroy_context(context *ctx)\n{\n  core_context *root = ctx->root;\n  context_data *ctx_data = ctx->internal_data;\n\n  if(!ctx_data)\n    return;\n\n  if(!ctx_data->parent || !ctx_data->is_subcontext)\n  {\n    // This is a root-level context, so remove it from the context stack.\n\n    // If the context isn't on the stack, this will error.\n    remove_stack(&(root->stack), ctx);\n    root->context_changed = true;\n\n    // Also, destroy all children.\n    if(ctx_data->stack.size)\n    {\n      int i;\n      for(i = ctx_data->stack.size - 1; i >= 0; i--)\n        destroy_context(ctx_data->stack.contents[i]);\n    }\n\n    free(ctx_data->stack.contents);\n  }\n  else\n  {\n    int removed;\n    // This is a subcontext, so remove it from its parent context.\n    context_data *parent_data = ctx_data->parent->internal_data;\n\n    // If the subcontext isn't on the stack, this will error.\n    removed = remove_stack(&(parent_data->stack), ctx);\n\n    // Adjust the current position in case this is mid-iteration.\n    if(removed <= parent_data->stack.pos)\n      parent_data->stack.pos--;\n  }\n\n  if(ctx_data->functions.destroy)\n    ctx_data->functions.destroy(ctx);\n\n  free(ctx_data);\n  free(ctx);\n}\n\n/**\n * Find out if a context change has occurred.\n */\nboolean has_context_changed(context *ctx)\n{\n  if(!ctx || !ctx->root)\n  {\n    print_core_stack(ctx);\n    error_message(E_CORE_FATAL_BUG, 9, NULL);\n    return true;\n  }\n\n  return (ctx->root->context_changed);\n}\n\n/**\n * Find out if the given context is a particular context.\n */\nboolean is_context(context *ctx, enum context_type context_type)\n{\n  if(!ctx || !ctx->internal_data)\n  {\n    print_core_stack(ctx);\n    error_message(E_CORE_FATAL_BUG, 2, NULL);\n    return false;\n  }\n\n  if(ctx->internal_data->is_subcontext)\n    return false;\n\n  return (ctx->internal_data->context_type == context_type);\n}\n\n/**\n * Add a resume callback to the context (or its parent). Alternatively, call\n * it immediately if no context change has occurred.\n */\nvoid context_callback(context *ctx, context_callback_param *param,\n context_callback_function func)\n{\n  context *target = ctx;\n\n  // If the context is a subcontext, try to find its parent context.\n  while(target && target->internal_data && target->internal_data->is_subcontext)\n    target = target->internal_data->parent;\n\n  if(!target || !target->root || !target->internal_data || !func ||\n   target->internal_data->num_callbacks >= MAX_NUM_CALLBACKS)\n  {\n    print_core_stack(ctx);\n    error_message(E_CORE_FATAL_BUG, 10, NULL);\n    return;\n  }\n\n  // If there has been no context change, execute this callback immediately.\n  if(!target->root->context_changed)\n    func(ctx, param);\n\n  else\n  {\n    struct context_data *ctx_data = target->internal_data;\n    struct context_callback_data *cb_data;\n    int id = (ctx_data->cur_callback + ctx_data->num_callbacks) %\n     MAX_NUM_CALLBACKS;\n\n    cb_data = &(ctx_data->callbacks[id]);\n\n    cb_data->callback = func;\n    cb_data->ctx_for_callback = ctx;\n    cb_data->param_for_callback = param;\n    ctx_data->num_callbacks++;\n  }\n}\n\n/**\n * Set the framerate mode for the current context.\n */\nvoid set_context_framerate_mode(context *ctx, enum framerate_type framerate)\n{\n  if(!ctx || !ctx->internal_data || ctx->internal_data->is_subcontext)\n  {\n    print_core_stack(ctx);\n    error_message(E_CORE_FATAL_BUG, 3, NULL);\n    return;\n  }\n\n  ctx->internal_data->framerate = framerate;\n}\n\n/**\n * Generate the core_context struct. This struct imitates a regular context so\n * it can be used as a parent, but it should not be started like a context.\n */\ncore_context *core_init(struct world *mzx_world)\n{\n  core_context *root = cmalloc(sizeof(core_context));\n  context *ctx = (context *)root;\n\n  ctx->root = root;\n  ctx->world = mzx_world;\n  ctx->internal_data = NULL;\n\n  memset(&(root->stack), 0, sizeof(struct context_stack));\n  root->first_run = true;\n  root->full_exit = false;\n  root->restart_on_exit = false;\n  root->context_changed = false;\n\n  return root;\n}\n\n/**\n * Execute the next callback for a context and remove it from the list.\n */\nstatic void execute_next_context_callback(context_data *ctx_data)\n{\n  struct context_callback_data *cb_data;\n  int i = ctx_data->cur_callback;\n\n  cb_data = &(ctx_data->callbacks[i]);\n  ctx_data->cur_callback = (ctx_data->cur_callback + 1) % MAX_NUM_CALLBACKS;\n  ctx_data->num_callbacks--;\n\n  cb_data->callback(cb_data->ctx_for_callback, cb_data->param_for_callback);\n}\n\n/**\n * Execute the resume function for the current context after a context change.\n */\nstatic void core_resume(core_context *root)\n{\n  context *ctx = root->stack.contents[root->stack.size - 1];\n  context_data *ctx_data = ctx->internal_data;\n  context_data *sub_data;\n  subcontext *sub;\n\n  // Execute callbacks before doing anything else.\n  while(ctx_data->num_callbacks)\n  {\n    execute_next_context_callback(ctx_data);\n\n    if(root->context_changed || root->full_exit)\n      return;\n  }\n\n  if(ctx_data->functions.resume)\n    ctx_data->functions.resume(ctx);\n\n  if(root->context_changed || root->full_exit)\n    return;\n\n  ctx_data->stack.pos = 0;\n\n  while(ctx_data->stack.pos < ctx_data->stack.size)\n  {\n    sub = ctx_data->stack.contents[ctx_data->stack.pos];\n    sub_data = ((context *)sub)->internal_data;\n\n    if(sub_data->functions.resume)\n      sub_data->functions.resume((context *)sub);\n\n    if(root->context_changed || root->full_exit)\n      return;\n\n    ctx_data->stack.pos++;\n  }\n}\n\n/**\n * Draw the current context.\n */\nstatic boolean core_draw(core_context *root)\n{\n  context *ctx = root->stack.contents[root->stack.size - 1];\n  context_data *ctx_data = ctx->internal_data;\n  context_data *sub_data;\n  subcontext *sub;\n  boolean ret = false;\n\n  if(ctx_data->functions.draw)\n    ret |= ctx_data->functions.draw(ctx);\n\n  if(root->context_changed || root->full_exit)\n    return ret;\n\n  ctx_data->stack.pos = 0;\n\n  while(ctx_data->stack.pos < ctx_data->stack.size)\n  {\n    sub = ctx_data->stack.contents[ctx_data->stack.pos];\n    sub_data = ((context *)sub)->internal_data;\n\n    if(sub_data->functions.draw)\n    {\n      select_layer(UI_LAYER);\n      ret |= sub_data->functions.draw((context *)sub);\n    }\n\n    if(root->context_changed || root->full_exit)\n      return ret;\n\n    ctx_data->stack.pos++;\n  }\n\n#ifdef CONFIG_FPS\n  if(video_is_fullscreen() && ctx_data->context_type != CTX_EDITOR)\n  {\n    // If we're in fullscreen mode, draw an onscreen FPS display.\n    char fpsbuf[32];\n    snprintf(fpsbuf, 32, \"  %.2f  \", average_fps);\n\n    select_layer(UI_LAYER);\n    write_string(fpsbuf, 0, 0, 0x0f, false);\n    return true;\n  }\n#endif\n  return ret;\n}\n\n/**\n * Determine if a given context exists on the stack.\n */\nstatic boolean is_on_stack(core_context *root, enum context_type type)\n{\n  int i = root->stack.size - 1;\n  context *ctx;\n\n  for(; i >= 0; i--)\n  {\n    ctx = root->stack.contents[i];\n    if(ctx->internal_data->context_type == type)\n      return true;\n  }\n\n  return false;\n}\n\n#ifdef CONFIG_HELPSYS\n/**\n * Determine if the help system is currently allowed. In addition to the\n * regular checks, this should not be allowed to open if it's already open!\n */\nstatic boolean core_allow_help_system(core_context *root)\n{\n  struct world *mzx_world = ((context *)root)->world;\n  boolean is_titlescreen = !is_on_stack(root, CTX_PLAY_GAME);\n\n  if(is_on_stack(root, CTX_HELP_SYSTEM))\n    return false;\n\n  if(mzx_world->active && !allow_help_system(mzx_world, is_titlescreen))\n    return false;\n\n  return true;\n}\n#endif\n\n/**\n * Determine if the settings menu is currently allowed. In addition to the\n * regular checks, this should not be allowed to open if it's already open!\n */\nstatic boolean core_allow_settings_menu(core_context *root)\n{\n  struct world *mzx_world = ((context *)root)->world;\n  boolean is_titlescreen = !is_on_stack(root, CTX_PLAY_GAME);\n  boolean is_override =\n   get_alt_status(keycode_internal) || get_ctrl_status(keycode_internal);\n\n  if(is_on_stack(root, CTX_CONFIGURE))\n    return false;\n\n  if(is_on_stack(root, CTX_HELP_SYSTEM))\n    return false;\n\n  if(mzx_world->active &&\n   !allow_settings_menu(mzx_world, is_titlescreen, is_override))\n    return false;\n\n  return true;\n}\n\n/**\n * Update sort function.\n */\nstatic int core_update_sort_fn(const void *A, const void *B)\n{\n  const context *a = *((const context **)A);\n  const context *b = *((const context **)B);\n  const context_data *a_data = a->internal_data;\n  const context_data *b_data = b->internal_data;\n  int diff = b_data->priority - a_data->priority;\n  return diff ? diff : a_data->unique - b_data->unique;\n}\n\n/**\n * Update the current context, handling input and idle activity.\n * If any subcontexts are attached to the context, they will also be updated.\n * Notes:\n *\n * 1) Execution order: Idle -> Click OR Drag -> Joystick -> Key\n * 2) A true return value of idle cancels key, mouse, and joystick handling.\n * 3) A true return value of key cancels key handling.\n * 4) A true return value of click or drag cancels mouse handling.\n * 5) A true return value of joystick handling cancels joystick handling.\n *\n * If a subcontext is added during the update, it will not be updated.\n * If a subcontext is removed during the update, the update will terminate.\n */\nstatic void core_update(core_context *root)\n{\n  context *_update_order[8];\n  context **update_order = _update_order;\n\n  context *ctx = root->stack.contents[root->stack.size - 1];\n  context_data *ctx_data = ctx->internal_data;\n  context_data *cur_data;\n  context *cur;\n\n  struct context_stack *stack = &(ctx_data->stack);\n\n  boolean joystick_handled = false;\n  boolean mouse_handled = false;\n  boolean key_handled = false;\n  boolean sorted = true;\n  int update_count = stack->size + 1;\n  int last_priority = ctx_data->priority;\n\n  boolean exit_status = get_exit_status();\n  int key = get_key(keycode_internal_wrt_numlock);\n  int joystick = get_joystick_ui_action();\n  int mouse_press = get_mouse_press_ext();\n  int mouse_drag_state = get_mouse_drag();\n  int mouse_x;\n  int mouse_y;\n  int i;\n\n  get_mouse_position(&mouse_x, &mouse_y);\n\n  // The key handler needs to be called when there is text input but no\n  // regular key input. In this situation, use the IKEY_UNICODE keycode.\n  if(!key && has_unicode_input())\n    key = IKEY_UNICODE;\n\n  if(update_count > (int)ARRAY_SIZE(_update_order))\n    update_order = cmalloc(update_count * sizeof(context *));\n\n  update_order[0] = ctx;\n  for(i = 0; i < stack->size; i++)\n  {\n    cur = stack->contents[i];\n    update_order[i + 1] = cur;\n\n    if(cur->internal_data->priority > last_priority)\n      sorted = false;\n\n    last_priority = cur->internal_data->priority;\n  }\n\n  if(!sorted)\n    qsort(update_order, update_count, sizeof(context *), core_update_sort_fn);\n\n  stack->removed = false;\n  stack->pos = 0;\n\n  for(i = 0; i < update_count; i++)\n  {\n    cur = update_order[i];\n    cur_data = cur->internal_data;\n\n    if(cur_data->functions.idle)\n    {\n      if(cur_data->functions.idle(cur))\n      {\n        joystick_handled = true;\n        mouse_handled = true;\n        key_handled = true;\n      }\n\n      if(root->context_changed || root->full_exit || stack->removed)\n        break;\n    }\n\n    if(!mouse_handled)\n    {\n      if(mouse_drag_state && cur_data->functions.drag)\n      {\n        mouse_handled |=\n         cur_data->functions.drag(cur, &key, mouse_press, mouse_x, mouse_y);\n      }\n      else\n\n      if(mouse_press && cur_data->functions.click)\n      {\n        mouse_handled |=\n         cur_data->functions.click(cur, &key, mouse_press, mouse_x, mouse_y);\n      }\n\n      if(root->context_changed || root->full_exit || stack->removed)\n        break;\n    }\n\n    if(!joystick_handled)\n    {\n      if(joystick && cur_data->functions.joystick)\n        joystick_handled |= cur_data->functions.joystick(cur, &key, joystick);\n\n      if(root->context_changed || root->full_exit || stack->removed)\n        break;\n    }\n\n    if(!key_handled)\n    {\n      if((key || exit_status) && cur_data->functions.key)\n        key_handled |= cur_data->functions.key(cur, &key);\n\n      if(root->context_changed || root->full_exit || stack->removed)\n        break;\n    }\n  }\n\n  if(update_order != _update_order)\n    free(update_order);\n\n  // Global key handler.\n  if(!root->context_changed && !root->full_exit && !key_handled)\n  {\n    switch(key)\n    {\n#ifdef CONFIG_HELPSYS\n      case IKEY_F1:\n      {\n        // Display help.\n        if(core_allow_help_system(root))\n          help_system(ctx, ctx->world);\n\n        break;\n      }\n#endif\n\n      case IKEY_F2:\n      {\n        // Display settings menu.\n        if(core_allow_settings_menu(root))\n          game_settings(ctx->world);\n\n        break;\n      }\n\n      case IKEY_F12:\n      {\n#ifdef DEBUG\n        if(get_alt_status(keycode_internal_wrt_numlock))\n        {\n          print_core_stack(ctx);\n          break;\n        }\n#endif\n\n#ifdef CONFIG_ENABLE_SCREENSHOTS\n        // Take screenshot.\n        if(get_config()->allow_screenshots)\n          dump_screen();\n#endif\n        break;\n      }\n    }\n  }\n}\n\n/**\n * Run the main game loop.\n */\nvoid core_run(core_context *root)\n{\n  const struct config_info *conf = get_config();\n  context *ctx;\n  // FIXME: Because not everything can be converted to use the main loop at\n  // once, this function needs to be used from places other than main(). So\n  // this doesn't break MZX, this function stops once the number of contexts\n  // on the stack has dropped below the initial value.\n  int initial_stack_size = root->stack.size;\n  int64_t start_ticks;\n  int64_t delta_ticks;\n  int64_t total_ticks;\n  boolean need_update_screen = true;\n#ifdef __EMSCRIPTEN__\n  uint64_t emscripten_prev_ticks = get_ticks();\n#endif\n\n  // If there aren't any contexts on the stack, there's no reason to be here.\n  if(initial_stack_size <= 0)\n    return;\n\n  // First run-- only terminate if all contexts are terminated.\n  if(root->first_run)\n  {\n    initial_stack_size = 1;\n    root->first_run = false;\n  }\n\n  // FIXME hack\n  enable_f12_hack = conf->allow_screenshots;\n\n  do\n  {\n    // Resume might trigger additional context changes, exit, or empty the\n    // context stack, so continue the main loop after handling the resume.\n    if(root->context_changed)\n    {\n      root->context_changed = false;\n      force_release_all_keys();\n      core_resume(root);\n      continue;\n    }\n\n    // Gameplay framerate timing starts immediately before the board update,\n    // which must be done in the draw function!\n    start_ticks = get_ticks();\n\n#ifdef CONFIG_FPS\n    update_fps(start_ticks);\n#endif\n\n    need_update_screen = core_draw(root);\n\n    // Context changed or an exit occurred? Skip the screen update and delay\n    if(root->context_changed || root->full_exit)\n      continue;\n\n    if(need_update_screen)\n      update_screen();\n\n    // Delay and then handle events.\n    ctx = root->stack.contents[root->stack.size - 1];\n\n    // Special- enable joystick gameplay bindings if this is the gameplay ctx.\n    joystick_set_game_mode(ctx->internal_data->context_type == CTX_PLAY_GAME);\n\n    // FIXME legacy loop hacks; remove when legacy loops are gone\n    joystick_set_legacy_loop_hacks(false);\n    enable_f12_hack = false;\n    // FIXME end legacy loop hacks\n\n    switch(ctx->internal_data->framerate)\n    {\n      case FRAMERATE_UI:\n      {\n        // Delay for a standard (fixed) amount of time.\n        update_event_status_delay();\n        break;\n      }\n\n      case FRAMERATE_UI_INTERRUPT:\n      {\n        // Delay for the standard amount of time or until an event is detected.\n        // Use for interfaces that need precise keypress detection, like typing.\n        update_event_status_intake();\n        break;\n      }\n\n      case FRAMERATE_MZX_SPEED:\n      {\n        // Delay according to mzx_speed and execution time.\n        if(ctx->world->mzx_speed > 1)\n        {\n          // Number of ms the update cycle took\n          delta_ticks = get_ticks() - start_ticks;\n          total_ticks = (16 * (ctx->world->mzx_speed - 1)) - delta_ticks;\n          if(total_ticks < 0)\n            total_ticks = 0;\n\n          // Delay for 16 * (speed - 1) since the beginning of the update\n          delay(total_ticks);\n        }\n#ifdef __EMSCRIPTEN__\n        else\n        {\n          // Emscripten must yield occasionally, or else the asynchronous queue\n          // will never get processed, causing a freeze.\n          delta_ticks = get_ticks() - emscripten_prev_ticks;\n          if(delta_ticks >= 8) // 16 - 8 = 8\n          {\n            delay(8);\n            emscripten_prev_ticks = get_ticks();\n          }\n        }\n#endif\n\n        update_event_status();\n        break;\n      }\n\n      default:\n      {\n        print_core_stack(ctx);\n        error_message(E_CORE_FATAL_BUG, 5, NULL);\n        break;\n      }\n    }\n\n    // FIXME legacy loop hacks; remove when legacy loops are gone\n    joystick_set_game_mode(false);\n    joystick_set_legacy_loop_hacks(true);\n    enable_f12_hack = conf->allow_screenshots;\n    // FIXME end legacy loop hacks\n\n    core_update(root);\n  }\n  while(!root->full_exit && root->stack.size >= initial_stack_size);\n\n  // Reset first run variable if the stack empties.\n  if(!root->stack.size)\n    root->first_run = true;\n}\n\n/**\n * Indicate that the core loop should exit.\n */\nvoid core_full_exit(context *ctx)\n{\n  if(!ctx || !ctx->root)\n  {\n    print_core_stack(ctx);\n    error_message(E_CORE_FATAL_BUG, 4, NULL);\n    return;\n  }\n\n  ctx->root->full_exit = true;\n}\n\n/**\n * Exit the core loop and restart MegaZeux.\n */\nvoid core_full_restart(context *ctx)\n{\n  if(!ctx || !ctx->root)\n  {\n    print_core_stack(ctx);\n    error_message(E_CORE_FATAL_BUG, 11, NULL);\n    return;\n  }\n\n  ctx->root->full_exit = true;\n  ctx->root->restart_on_exit = true;\n}\n\n/**\n * Find out if an exit has been requested.\n */\nboolean core_restart_requested(core_context *root)\n{\n  if(!root)\n  {\n    error_message(E_CORE_FATAL_BUG, 12, NULL);\n    return false;\n  }\n\n  return root->restart_on_exit;\n}\n\n/**\n * Free all context data that still exists.\n */\nvoid core_free(core_context *root)\n{\n  int i;\n\n  // Destroy all contexts on the stack.\n  for(i = root->stack.size - 1; i >= 0; i--)\n    destroy_context(root->stack.contents[i]);\n\n  free(root->stack.contents);\n  free(root);\n}\n\n// Deprecated.\nstatic enum context_type indices[128] = { CTX_MAIN };\nstatic int curr_index = 0;\n\n/**\n * Get the most recent context ID associated with the help system.\n * TODO rename after the deprecated features are no longer necessary.\n */\nenum context_type get_context(context *ctx)\n{\n  if(!curr_index && ctx)\n  {\n    core_context *root = ctx->root;\n    enum context_type ctx_type;\n    int i;\n\n    for(i = root->stack.size - 1; i >= 0; i--)\n    {\n      ctx_type = root->stack.contents[i]->internal_data->context_type;\n\n      // Help system only cares about positive context values.\n      if(ctx_type > 0)\n        return ctx_type;\n    }\n\n    return CTX_MAIN;\n  }\n\n  return indices[curr_index];\n}\n\n// Deprecated.\nvoid set_context(enum context_type idx)\n{\n  curr_index++;\n  indices[curr_index] = idx;\n}\n\n// Deprecated.\nvoid pop_context(void)\n{\n  if(curr_index > 0)\n    curr_index--;\n}\n\n// Editor external function pointers (NULL by default).\n\nvoid (*edit_world)(context *parent, boolean reload_curr_file);\nvoid (*debug_counters)(context *ctx);\nvoid (*draw_debug_box)(struct world *mzx_world, int x, int y, int d_x, int d_y,\n int show_keys);\nint (*debug_robot_break)(context *ctx, struct robot *cur_robot,\n int id, int lines_run);\nint (*debug_robot_watch)(context *ctx, struct robot *cur_robot,\n int id, int lines_run);\nvoid (*debug_robot_config)(struct world *mzx_world);\nvoid (*debug_robot_reset)(struct world *mzx_world);\n\n// Network external function pointers (NULL by default).\n\nboolean (*check_for_updates)(context *ctx, boolean is_automatic);\n"
  },
  {
    "path": "src/core.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n * Copyright (C) 2018-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __CORE_H\n#define __CORE_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"world_struct.h\"\n\n/**\n * The type of a given context. Used to identify particular contexts and to open\n * help files contextually. Do not sort this enum by value.\n *\n * Numbers >0 correspond directly to a hyperlink in the help system.\n * Numbers <=0 are used by contexts with no unique link in the help system.\n */\nenum context_type\n{\n  // Core contexts.\n  CTX_TITLE_SCREEN          = -1,\n  CTX_MAIN                  = 72,\n  CTX_PLAY_GAME             = 91,\n  CTX_CONFIGURE             = 92,\n  CTX_DIALOG_BOX            = 98,\n  CTX_HELP_SYSTEM           = -2,\n  CTX_MAIN_MENU             = -3,\n  CTX_GAME_MENU             = -4,\n  CTX_INTAKE_NUM            = -6,\n  CTX_TASK                  = -7,\n\n  // Network contexts.\n  CTX_UPDATER               = 99,\n\n  // Editor contexts.\n  CTX_EDITOR                = -100,\n  CTX_EDITOR_VIEW_BOARD     = -101,\n  CTX_THING_MENU            = -102,\n  CTX_BLOCK_CMD             = 73,\n  CTX_BLOCK_TYPE            = 74,\n  CTX_CHOOSE_CHARSET        = 75,\n  CTX_IMPORTEXPORT_TYPE     = 77,\n  CTX_CHAR_EDIT             = 79,\n  CTX_STATUS_COUNTERS       = 82,\n  CTX_BOARD_EXITS           = 83,\n  CTX_BOARD_SIZES           = 84,\n  CTX_BOARD_INFO            = 85,\n  CTX_CHANGE_CHAR_IDS       = 89,\n  CTX_CHANGE_DAMAGE         = 90,\n  CTX_GLOBAL_SETTINGS       = 86,\n  CTX_GLOBAL_SETTINGS_2     = 88,\n  CTX_ROBO_ED               = 87,\n  CTX_PALETTE_EDITOR        = 93,\n  CTX_SENSOR_EDITOR         = 94,\n  CTX_SUPER_MEGAZEUX        = 95,\n  CTX_SFX_EDITOR            = 97,\n  CTX_COUNTER_DEBUG         = 100,\n  CTX_ROBOT_DEBUG           = 101,\n  CTX_BREAKPOINT_EDITOR     = 102,\n  CTX_VLAYER_SIZES          = 103,\n};\n\n/** The framerate rule that should be used for the active context. */\nenum framerate_type\n{\n  FRAMERATE_UI,             // Standard delay time.\n  FRAMERATE_UI_INTERRUPT,   // Standard delay time or until an event occurs.\n  FRAMERATE_MZX_SPEED,      // Delay according to mzx_speed.\n};\n\n/**\n * Core types.\n * \"subcontext\" is an alternate type for context that should be used for\n * functions that deal with subcontexts instead of contexts.\n */\ntypedef struct context context;\ntypedef struct context subcontext;\ntypedef struct context_data context_data;\ntypedef struct core_context core_context;\n\ntypedef void context_callback_param;\ntypedef void (*context_callback_function)(context *, context_callback_param *);\n\n/**\n * Contains information related to the current MegaZeux state/interface/etc.\n */\nstruct context\n{\n  struct world *world;\n  core_context *root;\n  context_data *internal_data;\n};\n\n/**\n * Contains extra function/variable information for a new context or subcontext.\n * All variables should be set to NULL or zero unless explicitly provided.\n * A return value of true from the draw function will update the screen (this\n * is almost always what you want to return, for an exception see draw_world).\n * A return value of true to an update function will treat the indicated input\n * type(s) as having been fully handled. If no functions aside from destroy are\n * provided, an error will display.\n *\n * resume           Optional function called when this context enters focus.\n * draw             Optional function to draw this context every frame.\n * idle             Update function called every frame (all).\n * key              Update function called to handle a keypress (key).\n * joystick         Update function called to handle a joystick (joystick).\n * click            Update function called to handle a mouse click (mouse).\n * drag             Update function called to handle a mouse drag (mouse).\n * destroy          Optional function to be called on destruction.\n * framerate_mode   Framerate mode this context should use (context only).\n * priority         Alters the update order. higher => updates earlier.\n */\nstruct context_spec\n{\n  void (*resume)(context *);\n  boolean (*draw)(context *);\n  boolean (*idle)(context *);\n  boolean (*key)(context *, int *key);\n  boolean (*joystick)(context *, int *key, int action);\n  boolean (*click)(context *, int *key, int button, int x, int y);\n  boolean (*drag)(context *, int *key, int button, int x, int y);\n  void (*destroy)(context *);\n  enum framerate_type framerate_mode;\n  int priority;\n};\n\n/**\n * Sets up a new context and adds it to the context stack. At least one\n * update function must be provided. If \"parent\" is a subcontext, this function\n * will use its parent context as the parent instead.\n *\n * @param ctx               Optional newly allocated context to be initialized.\n * @param parent            The context which created this context.\n * @param ctx_spec          Specification for context functions/variables.\n * @param context_type      Used to identify contexts (also, by the help system)\n */\nCORE_LIBSPEC void create_context(context *ctx, context *parent,\n struct context_spec *ctx_spec, enum context_type context_type);\n\n/**\n * Sets up a new subcontext and adds it to a parent context. A subcontext will\n * be executed at the same time as its parent context and will be terminated if\n * its parent context is terminated. Each context operation will execute as\n * follows:\n *\n * Resume:  parent, oldest subcontext -> newest subcontext\n * Draw:    parent, oldest subcontext -> newest subcontext\n * Update:  parent, oldest subcontext -> newest subcontext (see priority above)\n * Destroy: newest subcontext -> oldest subcontext, parent\n *\n * If \"parent\" is a subcontext, this function will use the parent of that\n * subcontext as the parent of the new subcontext instead.\n *\n * @param sub               Optional newly allocated subcontext.\n * @param parent            The context which created this context.\n * @param sub_spec          Specification for subcontext functions/variables.\n */\nCORE_LIBSPEC void create_subcontext(subcontext *sub, context *parent,\n struct context_spec *sub_spec);\n\n/**\n * Destroys a context and its subcontexts. This will call the destroy function\n * for every subcontext destroyed, and finally for this context itself.\n * Subcontexts will be destroyed from the top of the stack down. If given a\n * subcontext, this will destroy the given subcontext only.\n *\n * @param ctx           The context or subcontext to destroy.\n */\nCORE_LIBSPEC void destroy_context(context *ctx);\n\n/**\n * Determine if a context change has occurred.\n *\n *\n * @param ctx           Current context\n * @return              true if a context change has occurred.\n */\nCORE_LIBSPEC boolean has_context_changed(context *ctx);\n\n/**\n * Determine if the given context matches an expected type.\n *\n * @param ctx           A context\n * @param context_type  A context type\n * @return              true if the context has the given context type.\n */\nCORE_LIBSPEC boolean is_context(context *ctx, enum context_type context_type);\n\n/**\n * Add a callback to a context if a context change has occurred. This callback\n * will be executed when focus returns to the given context, but before the\n * resume function of the context is executed. Multiple callbacks may be\n * provided to a context; they will be executed in the order they were added.\n * If this function receives a subcontext pointer, this callback will be added\n * to the parent context but the subcontext will be used as the parameter.\n *\n * If no change of context has occurred, this function is executed immediately\n * instead of during the context resume process.\n *\n * @param ctx           The context to assign this callback to.\n * @param param         A parameter to be provided to the callback.\n * @param func          The callback to be executed.\n */\nCORE_LIBSPEC void context_callback(context *ctx, context_callback_param *param,\n context_callback_function func);\n\n/**\n * Sets the framerate mode of the current context. See enum framerate_type for\n * a list of valid values. This function will error if given a subcontext.\n *\n * @param ctx           The current context.\n * @param framerate     The new framerate mode.\n */\nvoid set_context_framerate_mode(context *ctx, enum framerate_type framerate);\n\n/**\n * Initializes data for the core.\n *\n * @param mzx_world     MegaZeux world information struct.\n * @param data          Global information struct.\n * @return              A core context struct.\n */\nCORE_LIBSPEC core_context *core_init(struct world *mzx_world);\n\n/**\n * Run MegaZeux using given context stack information.\n *\n * @param root          The core context to run.\n */\nCORE_LIBSPEC void core_run(core_context *root);\n\n/**\n * Signal the core to abort all contexts and exit the current game loop.\n *\n * @param ctx           The current context.\n */\nCORE_LIBSPEC void core_full_exit(context *ctx);\n\n/**\n * Signal the core to abort all contexts, exit the loop, and restart MegaZeux.\n *\n * @param ctx           The current context.\n */\nCORE_LIBSPEC void core_full_restart(context *ctx);\n\n/**\n * Determine if the a restart was requested.\n *\n * @param root          The core context.\n * @return              True if a restart was requested.\n */\nCORE_LIBSPEC boolean core_restart_requested(core_context *root);\n\n/**\n * Clean up MegaZeux all context data for a given core context.\n *\n * @param root          The core context to free.\n */\nCORE_LIBSPEC void core_free(core_context *root);\n\n// Deprecated functions.\n\nCORE_LIBSPEC enum context_type get_context(context *);\nCORE_LIBSPEC void set_context(enum context_type idx);\nCORE_LIBSPEC void pop_context(void);\n\n// Editor external function pointers.\n\nCORE_LIBSPEC extern void (*edit_world)(context *parent,\n boolean reload_curr_file);\nCORE_LIBSPEC extern void (*debug_counters)(context *ctx);\nCORE_LIBSPEC extern void (*draw_debug_box)(struct world *mzx_world,\n int x, int y, int d_x, int d_y, int show_keys);\nCORE_LIBSPEC extern int (*debug_robot_break)(context *ctx,\n struct robot *cur_robot, int id, int lines_run);\nCORE_LIBSPEC extern int (*debug_robot_watch)(context *ctx,\n struct robot *cur_robot, int id, int lines_run);\nCORE_LIBSPEC extern void (*debug_robot_config)(struct world *mzx_world);\nCORE_LIBSPEC extern void (*debug_robot_reset)(struct world *mzx_world);\n\n// Network external function pointers.\n\nCORE_LIBSPEC extern boolean (*check_for_updates)(context *ctx,\n boolean is_automatic);\n\n__M_END_DECLS\n\n#endif /* __CORE_H */\n"
  },
  {
    "path": "src/core_task.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Context that blocks and draws a progress meter while waiting for a task.\n * If possible, the task will be run asynchronously in a second thread. */\n\n#include \"core_task.h\"\n\n#include \"event.h\"\n#include \"graphics.h\"\n#include \"platform.h\"\n#include \"util.h\"\n#include \"window.h\"\n\n#ifndef PLATFORM_NO_THREADING\n#define TASK_THREADED\n#endif\n\nstruct task_context\n{\n  context ctx;\n#ifdef TASK_THREADED\n  platform_thread thd;\n  platform_mutex lock;\n#endif\n  uint64_t ticks;\n  char title[80];\n  int progress;\n  int progress_max;\n  boolean async;\n  boolean redraw;\n  boolean cancel;\n  boolean first_draw;\n  boolean done;\n  boolean return_status;\n  boolean (*task_callback)(context *ctx, void *priv);\n  void (*task_complete)(void *priv, boolean ret);\n  void *task_priv;\n};\n\nstatic boolean task_lock(struct task_context *task)\n{\n#ifdef TASK_THREADED\n  if(task->async)\n    return platform_mutex_lock(&task->lock);\n#endif\n  return true;\n}\n\nstatic boolean task_unlock(struct task_context *task)\n{\n#ifdef TASK_THREADED\n  if(task->async)\n    return platform_mutex_unlock(&task->lock);\n#endif\n  return true;\n}\n\nstatic boolean task_draw(context *ctx)\n{\n  struct task_context *task = (struct task_context *)ctx;\n  char title[80];\n  int progress;\n  int progress_max;\n  boolean done;\n  boolean no_draw;\n  boolean redraw;\n\n  task_lock(task);\n  done = task->done;\n  no_draw = task->first_draw || task->cancel;\n  redraw = task->redraw;\n  progress = task->progress;\n  progress_max = task->progress_max;\n\n  if(no_draw)\n  {\n    task->first_draw = false;\n  }\n  else\n\n  if(redraw)\n  {\n    memcpy(title, task->title, sizeof(title));\n    task->redraw = false;\n  }\n  task_unlock(task);\n\n  if(done)\n  {\n    destroy_context((context *)task);\n    return true;\n  }\n\n  /* Modern users expect no save/load meter: skip the first would-be frame. */\n  if(no_draw)\n    return true;\n\n  if(redraw)\n    meter(title, progress, progress_max);\n  else\n    meter_interior(progress, progress_max);\n\n  return true;\n}\n\nstatic boolean task_key(context *ctx, int *key)\n{\n  struct task_context *task = (struct task_context *)ctx;\n\n  // Request cancelation--the task may or may not check this.\n  if(get_exit_status() || *key == IKEY_ESCAPE)\n  {\n    task_lock(task);\n    task->cancel = true;\n    task_unlock(task);\n  }\n\n  // capture all other inputs, including help file, settings\n  if(*key != IKEY_F12)\n    return true;\n\n  return false;\n}\n\nstatic void task_destroy(context *ctx)\n{\n  struct task_context *task = (struct task_context *)ctx;\n#ifdef TASK_THREADED\n  if(task->async)\n  {\n    task_lock(task);\n    task->cancel = true;\n    task_unlock(task);\n    platform_thread_join(&task->thd);\n    platform_mutex_destroy(&task->lock);\n  }\n#endif\n  task->task_complete(task->task_priv, task->return_status);\n}\n\n/**\n * Call during a task to update the meter. Called from task thread if threaded.\n * If the task is running synchronously, either because the task initialization\n * failed to create a thread or because the platform does not support\n * threading, this function may update the screen and poll events. Because of\n * this, tasks should call this function regularly.\n *\n * @param ctx           the task context provided to the task callback.\n * @param progress      the current progress of the task.\n * @param progress_max  expected final value of the progress of the task.\n * @param status        new title for the progress bar (`NULL` for no change).\n * @returns             `true` if the task should continue, otherwise `false`.\n */\nboolean core_task_tick(context *ctx,\n int progress, int progress_max, const char *status)\n{\n  struct task_context *task = (struct task_context *)ctx;\n\n  if(!task_lock(task))\n    return false;\n\n  task->progress = MIN(progress, progress_max);\n  task->progress_max = progress_max;\n  if(status)\n  {\n    snprintf(task->title, sizeof(task->title), \"%s\", status);\n    task->redraw = true;\n  }\n  task_unlock(task);\n\n#ifdef TASK_THREADED\n  if(!task->async)\n#endif\n  {\n    uint64_t ticks = get_ticks();\n    if(ticks - task->ticks >= UPDATE_DELAY)\n    {\n      int key;\n      task->ticks = ticks;\n      task_draw(ctx);\n      update_screen();\n      update_event_status();\n      key = get_key(keycode_internal_wrt_numlock);\n      task_key(ctx, &key);\n    }\n  }\n  return (task->cancel == false);\n}\n\nstatic THREAD_RES task_execute(void *priv)\n{\n  struct task_context *task = (struct task_context *)priv;\n  boolean status = task->task_callback((context *)task, task->task_priv);\n\n  task_lock(task);\n  task->return_status = status;\n  task->done = true;\n  task_unlock(task);\n\n  THREAD_RETURN;\n}\n\n/**\n * Create a context which blocks and executes a task. This context displays a\n * simple progress meter and allows the task to be canceled. The task itself\n * will be executed asynchronous from the main loop, if possible. Because of\n * this, the caller should ensure the task owns ALL data it will use during\n * execution. The task should regularly call `core_task_tick` to provide\n * progress information to the user and to update the screen (if synchronous).\n *\n * @param parent          parent context for the new task.\n * @param title           initial title of the progress bar.\n * @param task_callback   called to start execution of the task. Caller\n *                        should assume this will be called in a new thread.\n * @param task_complete   called after the execution of the task by the\n *                        main thread. Caller should should assume the task\n *                        context has been destroyed.\n * @param priv            private data for the task callbacks.\n */\nvoid core_task_context(context *parent, const char *title,\n boolean (*task_callback)(context *ctx, void *priv),\n void (*task_complete)(void *priv, boolean ret), void *priv)\n{\n  struct task_context *task =\n   (struct task_context *)ccalloc(1, sizeof(struct task_context));\n  struct context_spec spec;\n  if(!task)\n    return;\n\n  task->redraw = true;\n  task->first_draw = true;\n  task->ticks = get_ticks();\n  task->task_callback = task_callback;\n  task->task_complete = task_complete;\n  task->task_priv = priv;\n\n#ifdef TASK_THREADED\n  if(platform_mutex_init(&task->lock))\n    task->async = true;\n#endif\n\n  memset(&spec, 0, sizeof(spec));\n  spec.draw     = task_draw;\n  spec.key      = task_key;\n  spec.destroy  = task_destroy;\n\n  create_context((context *)task, parent, &spec, CTX_TASK);\n  core_task_tick((context *)task, 0, 1, title);\n\n#ifdef TASK_THREADED\n  if(task->async)\n  {\n   if(platform_thread_create(&task->thd, task_execute, task))\n      return;\n\n    platform_mutex_destroy(&task->lock);\n    task->async = false;\n  }\n  warn(\"falling back to synchronous task execution--report this!\\n\");\n#endif\n  /* Do not allow synchronous tasks in HTML5 */\n#ifndef __EMSCRIPTEN__\n  task_execute(task);\n#endif\n  destroy_context((context *)task);\n}\n"
  },
  {
    "path": "src/core_task.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __CORE_TASK_H\n#define __CORE_TASK_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"core.h\"\n\nCORE_LIBSPEC boolean core_task_tick(context *ctx,\n int progress, int progress_max, const char *status);\n\nCORE_LIBSPEC void core_task_context(context *parent, const char *title,\n boolean (*task_callback)(context *ctx, void *priv),\n void (*task_complete)(void *priv, boolean ret), void *priv);\n\n__M_END_DECLS\n\n#endif /* __CORE_TASK_H */\n"
  },
  {
    "path": "src/counter.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// New counter.cpp. Sorted lists make for faster searching.\n// Builtins are also cleaned up by being put on a seperate list.\n\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n#include <math.h>\n#include <ctype.h>\n#include <limits.h>\n#include <time.h>\n\n#include \"board.h\"\n#include \"configure.h\"\n#include \"counter.h\"\n#include \"data.h\"\n#include \"error.h\"\n#include \"event.h\"\n#include \"expr.h\"\n#include \"game_ops.h\"\n#include \"graphics.h\"\n#include \"idarray.h\"\n#include \"idput.h\"\n#include \"memcasecmp.h\"\n#include \"platform.h\"\n#include \"rasm.h\"\n#include \"robot.h\"\n#include \"sprite.h\"\n#include \"str.h\"\n#include \"util.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n#include \"io/fsafeopen.h\"\n#include \"io/vio.h\"\n\n#include \"audio/audio.h\"\n\n/**\n * TODO: Counter lookups are currently case-insensitive, which is somewhat of\n * a performance concern. A good future (3.xx) feature might be to lowercase\n * all counter names.\n */\n\n#ifdef CONFIG_COUNTER_HASH_TABLES\n#include \"hashtable.h\"\nHASH_SET_INIT(COUNTER, struct counter *, name, name_length)\n#endif\n\n#ifndef M_PI\n#define M_PI 3.14159265358979323846\n#endif\n\nstruct function_counter\n{\n  const char *const name;\n  int minimum_version;\n  int (*const function_read)(struct world *mzx_world,\n   const struct function_counter *counter, const char *name, int id);\n  void (*const function_write)(struct world *mzx_world,\n   const struct function_counter *counter, const char *name, int value,\n   int id);\n};\n\nstatic unsigned int get_board_x_board_y_offset(struct world *mzx_world, int id)\n{\n  int board_x = get_counter(mzx_world, \"board_x\", id);\n  int board_y = get_counter(mzx_world, \"board_y\", id);\n\n  board_x = CLAMP(board_x, 0, mzx_world->current_board->board_width - 1);\n  board_y = CLAMP(board_y, 0, mzx_world->current_board->board_height - 1);\n\n  return board_y * mzx_world->current_board->board_width + board_x;\n}\n\n//TODO: make this work with any number of params\nstatic int get_counter_params(const char *src, //unsigned int num,\n int *v1, int *v2)\n{\n  char *next;\n\n  *v1 = strtol(src, &next, 10);\n  if(*next == ',')\n  {\n    *v2 = strtol(next + 1, NULL, 10);\n    return 0;\n  }\n  else\n  {\n    return -1;\n  }\n}\n\nstatic int translate_coordinates(const char *src, unsigned int *x,\n                                 unsigned int *y)\n{\n  char *next;\n\n  *x = strtol(src, &next, 10);\n  if(*next == ',')\n  {\n    *y = strtol(next + 1, NULL, 10);\n    return 0;\n  }\n  else\n  {\n    return -1;\n  }\n}\n\nstatic int string_counter_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  // TODO: the only uses of get_counter() that can make it here should be using\n  // a temporary buffer already, so this cast should be safe... however, it is\n  // still very tacky.\n  return string_read_as_counter(mzx_world, (char *)name, id);\n}\n\nstatic void string_counter_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  // TODO: the only uses of set_counter() that can make it here should be using\n  // a temporary buffer already, so this cast should be safe... however, it is\n  // still very tacky.\n  string_write_as_counter(mzx_world, (char *)name, value, id);\n}\n\nstatic int local_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return (mzx_world->current_board->robot_list[id])->local[0];\n}\n\nstatic void local_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  (mzx_world->current_board->robot_list[id])->local[0] = value;\n}\n\nstatic int localn_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int local_num = 0;\n\n  if(name[5])\n    local_num = (strtol(name + 5, NULL, 10) - 1) & 31;\n\n  return (mzx_world->current_board->robot_list[id])->local[local_num];\n}\n\nstatic void localn_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int local_num = 0;\n\n  if(name[5])\n    local_num = (strtol(name + 5, NULL, 10) - 1) & 31;\n\n  (mzx_world->current_board->robot_list[id])->local[local_num] = value;\n}\n\nstatic int loopcount_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return (mzx_world->current_board->robot_list[id])->loop_count;\n}\n\nstatic void loopcount_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  (mzx_world->current_board->robot_list[id])->loop_count = value;\n}\n\nstatic int playerdist_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *src_board = mzx_world->current_board;\n  struct robot *cur_robot = src_board->robot_list[id];\n\n  int player_x = mzx_world->player_x;\n  int player_y = mzx_world->player_y;\n  int thisx, thisy;\n  get_robot_position(cur_robot, &thisx, &thisy);\n\n  return abs(player_x - thisx) + abs(player_y - thisy);\n}\n\nstatic int sin_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int theta = strtol(name + 3, NULL, 10);\n  return (int)(sin(theta * (2 * M_PI) / mzx_world->c_divisions)\n   * mzx_world->multiplier);\n}\n\nstatic int cos_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int theta = strtol(name + 3, NULL, 10);\n  return (int)(cos(theta * (2 * M_PI) / mzx_world->c_divisions)\n   * mzx_world->multiplier);\n}\n\nstatic int tan_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int theta = strtol(name + 3, NULL, 10);\n  return (int)(tan(theta * (2 * M_PI) / mzx_world->c_divisions)\n   * mzx_world->multiplier);\n}\n\nstatic int asin_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int val = strtol(name + 4, NULL, 10);\n  return (int)((asinf((float)val / mzx_world->divider) *\n   mzx_world->c_divisions) / (2 * M_PI));\n}\n\nstatic int acos_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int val = strtol(name + 4, NULL, 10);\n  return (int)((acosf((float)val / mzx_world->divider) *\n   mzx_world->c_divisions) / (2 * M_PI));\n}\n\nstatic int atan_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int val = strtol(name + 4, NULL, 10);\n  return (int)((atan2f((float)val, (float)mzx_world->divider) *\n   mzx_world->c_divisions) / (2 * M_PI));\n}\n\nstatic int atan2_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int dy = 0, dx = 0;\n  get_counter_params(name + 6, &dy, &dx);\n  return (int)((atan2f((float)dy, (float)dx) *\n   mzx_world->c_divisions) / (2 * M_PI));\n}\n\nstatic int c_divisions_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->c_divisions;\n}\n\nstatic int divider_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->divider;\n}\n\nstatic int multiplier_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->multiplier;\n}\n\nstatic void c_divisions_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->c_divisions = value;\n}\n\nstatic void divider_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->divider = value;\n}\n\nstatic void multiplier_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->multiplier = value;\n}\n\nstatic int thisx_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct robot *cur_robot = mzx_world->current_board->robot_list[id];\n  int thisx, thisy;\n  get_robot_position(cur_robot, &thisx, &thisy);\n\n  if(mzx_world->mid_prefix == REL_TO_PLAYER)\n    return thisx - mzx_world->player_x;\n\n  return thisx;\n}\n\nstatic int thisy_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct robot *cur_robot = mzx_world->current_board->robot_list[id];\n  int thisx, thisy;\n  get_robot_position(cur_robot, &thisx, &thisy);\n\n  if(mzx_world->mid_prefix == REL_TO_PLAYER)\n    return thisy - mzx_world->player_y;\n\n  return thisy;\n}\n\nstatic int playerx_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->player_x;\n}\n\nstatic int playery_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->player_y;\n}\n\nstatic int this_char_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return (mzx_world->current_board->robot_list[id])->robot_char;\n}\n\nstatic int this_color_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *src_board = mzx_world->current_board;\n  struct robot *cur_robot = src_board->robot_list[id];\n  int thisx, thisy;\n  int offset;\n  get_robot_position(cur_robot, &thisx, &thisy);\n  offset = thisx + (thisy * src_board->board_width);\n\n  // No global\n  if(id == 0)\n    return -1;\n\n  return src_board->level_color[offset];\n}\n\nstatic int sqrt_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int val = strtol(name + 4, NULL, 10);\n  return (int)(sqrt(val));\n}\n\nstatic int abs_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int val = strtol(name + 3, NULL, 10);\n  return abs(val);\n}\n\nstatic int maxval_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int v1 = 0, v2 = 0;\n  get_counter_params(name + 3, &v1, &v2);\n\n  if(v1 > v2)\n    return v1;\n  else\n    return v2;\n}\n\nstatic int minval_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int v1 = 0, v2 = 0;\n  get_counter_params(name + 3, &v1, &v2);\n\n  if (v1 < v2)\n    return v1;\n  else\n    return v2;\n}\n\nstatic int playerfacedir_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->current_board->player_last_dir >> 4;\n}\n\nstatic void playerfacedir_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  struct board *src_board = mzx_world->current_board;\n\n  if(value < 0)\n    value = 0;\n\n  if(value > 3)\n    value = 3;\n\n  src_board->player_last_dir =\n   (src_board->player_last_dir & 0x0F) | (value << 4);\n}\n\nstatic int playerlastdir_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->current_board->player_last_dir & 0x0F;\n}\n\nstatic void playerlastdir_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  struct board *src_board = mzx_world->current_board;\n\n  if(value < 0)\n    value = 0;\n\n  if(value > 4)\n    value = 4;\n\n  src_board->player_last_dir =\n   (src_board->player_last_dir & 0xF0) | value;\n}\n\nstatic int horizpld_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct robot *cur_robot = mzx_world->current_board->robot_list[id];\n  int thisx, thisy;\n  get_robot_position(cur_robot, &thisx, &thisy);\n\n  return abs(mzx_world->player_x - thisx);\n}\n\nstatic int vertpld_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct robot *cur_robot = mzx_world->current_board->robot_list[id];\n  int thisx, thisy;\n  get_robot_position(cur_robot, &thisx, &thisy);\n\n  return abs(mzx_world->player_y - thisy);\n}\n\nstatic int board_char_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  unsigned int offset = get_board_x_board_y_offset(mzx_world, id);\n  return get_id_char(mzx_world->current_board, offset);\n}\n\nstatic int board_color_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  unsigned int offset = get_board_x_board_y_offset(mzx_world, id);\n  int ignore_under = 1;\n\n  if((mzx_world->version >= V280) && (mzx_world->version <= V283))\n    ignore_under = 0;\n\n  return get_id_board_color(mzx_world->current_board, offset, ignore_under);\n}\n\nstatic int board_w_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->current_board->board_width;\n}\n\nstatic int board_h_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->current_board->board_height;\n}\n\nstatic int board_id_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  unsigned int offset = get_board_x_board_y_offset(mzx_world, id);\n  return mzx_world->current_board->level_id[offset];\n}\n\nstatic void board_id_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  unsigned int offset = get_board_x_board_y_offset(mzx_world, id);\n  struct board *src_board = mzx_world->current_board;\n  char cvalue = value;\n\n  if((cvalue < SENSOR) && (src_board->level_id[offset] < SENSOR))\n    src_board->level_id[offset] = cvalue;\n}\n\nstatic int board_param_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  unsigned int offset = get_board_x_board_y_offset(mzx_world, id);\n  return mzx_world->current_board->level_param[offset];\n}\n\nstatic void board_param_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  unsigned int offset = get_board_x_board_y_offset(mzx_world, id);\n  struct board *src_board = mzx_world->current_board;\n\n  if(src_board->level_id[offset] < 122)\n    src_board->level_param[offset] = value;\n}\n\nstatic int red_value_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int cur_color = get_counter(mzx_world, \"current_color\", id) & (SMZX_PAL_SIZE - 1);\n  return get_red_component(cur_color);\n}\n\nstatic int green_value_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int cur_color = get_counter(mzx_world, \"current_color\", id) & (SMZX_PAL_SIZE - 1);\n  return get_green_component(cur_color);\n}\n\nstatic int blue_value_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int cur_color = get_counter(mzx_world, \"current_color\", id) & (SMZX_PAL_SIZE - 1);\n  return get_blue_component(cur_color);\n}\n\nstatic void red_value_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int cur_color = get_counter(mzx_world, \"current_color\", id) & (SMZX_PAL_SIZE - 1);\n  set_red_component(cur_color, value);\n}\n\nstatic void green_value_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int cur_color = get_counter(mzx_world, \"current_color\", id) & (SMZX_PAL_SIZE - 1);\n  set_green_component(cur_color, value);\n}\n\nstatic void blue_value_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int cur_color = get_counter(mzx_world, \"current_color\", id) & (SMZX_PAL_SIZE - 1);\n  set_blue_component(cur_color, value);\n}\n\nstatic int overlay_mode_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->current_board->overlay_mode;\n}\n\nstatic int mzx_speed_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->mzx_speed;\n}\n\nstatic void mzx_speed_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(value == 0)\n  {\n    mzx_world->lock_speed = 0;\n  }\n  else if(value >= 1 && value <= 16)\n  {\n    mzx_world->mzx_speed = value;\n    mzx_world->lock_speed = 1;\n  }\n}\n\nstatic int overlay_char_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *src_board = mzx_world->current_board;\n  int offset = get_counter(mzx_world, \"overlay_x\", id) +\n   (get_counter(mzx_world, \"overlay_y\", id) * src_board->board_width);\n  int board_size = src_board->board_width * src_board->board_height;\n\n  if((offset >= 0) && (offset < board_size))\n    return src_board->overlay[offset];\n\n  return -1;\n}\n\nstatic int overlay_color_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *src_board = mzx_world->current_board;\n  int offset = get_counter(mzx_world, \"overlay_x\", id) +\n   (get_counter(mzx_world, \"overlay_y\", id) * src_board->board_width);\n  int board_size = src_board->board_width * src_board->board_height;\n\n  if((offset >= 0) && (offset < board_size))\n    return src_board->overlay_color[offset];\n\n  return -1;\n}\n\nstatic int smzx_mode_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return get_screen_mode();\n}\n\nstatic void smzx_mode_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  set_screen_mode(value);\n}\n\nstatic int smzx_r_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int cur_color = strtol(name + 6, NULL, 10) & (SMZX_PAL_SIZE - 1);\n  return get_red_component(cur_color);\n}\n\nstatic int smzx_g_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int cur_color = strtol(name + 6, NULL, 10) & (SMZX_PAL_SIZE - 1);\n  return get_green_component(cur_color);\n}\n\nstatic int smzx_b_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int cur_color = strtol(name + 6, NULL, 10) & (SMZX_PAL_SIZE - 1);\n  return get_blue_component(cur_color);\n}\n\nstatic void smzx_r_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int cur_color = strtol(name + 6, NULL, 10) & (SMZX_PAL_SIZE - 1);\n  set_red_component(cur_color, value);\n}\n\nstatic void smzx_g_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int cur_color = strtol(name + 6, NULL, 10) & (SMZX_PAL_SIZE - 1);\n  set_green_component(cur_color, value);\n}\n\nstatic void smzx_b_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int cur_color = strtol(name + 6, NULL, 10) & (SMZX_PAL_SIZE - 1);\n  set_blue_component(cur_color, value);\n}\n\nstatic int smzx_idx_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int col = 0, offset = 0;\n  get_counter_params(name + 8, &col, &offset);\n  return get_smzx_index(col, offset);\n}\n\nstatic void smzx_idx_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int col = 0, offset = 0;\n  get_counter_params(name + 8, &col, &offset);\n  set_smzx_index(col, offset, value);\n}\n\nstatic int smzx_message_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->smzx_message;\n}\n\nstatic void smzx_message_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(value)\n    mzx_world->smzx_message = 1;\n  else\n    mzx_world->smzx_message = 0;\n}\n\nstatic int spr_clist_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int clist_num = strtol(name + 9, NULL, 10) & (MAX_SPRITES - 1);\n  return mzx_world->collision_list[clist_num];\n}\n\nstatic int spr_collisions_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->collision_count;\n}\n\nstatic int spr_num_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  // This was a signed char in versions prior to 2.70.\n  // Note the 2.69c source code claims it's an unsigned char too, but it can\n  // be verified that the build everyone relied on still treats it as signed.\n  if(mzx_world->version < V270)\n    return (signed char)mzx_world->sprite_num;\n\n  return mzx_world->sprite_num;\n}\n\nstatic int spr_cx_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->col_x;\n}\n\nstatic int spr_cy_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->col_y;\n}\n\nstatic int spr_tcol_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->transparent_color;\n}\n\nstatic int spr_offset_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->offset;\n}\n\nstatic int spr_unbound_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->flags & SPRITE_UNBOUND ? 1 : 0;\n}\n\nstatic int spr_width_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->width;\n}\n\nstatic int spr_height_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->height;\n}\n\nstatic int spr_refx_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->ref_x;\n}\n\nstatic int spr_refy_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->ref_y;\n}\n\nstatic int spr_x_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->x;\n}\n\nstatic int spr_y_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->y;\n}\n\nstatic int spr_z_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->z;\n}\n\nstatic int spr_off_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  struct sprite *cur_sprite = mzx_world->sprite_list[spr_num];\n\n  // This counter has existed as long as sprites have but was never\n  // actually readable until 2.92.\n  if(mzx_world->version >= V292 && !(cur_sprite->flags & SPRITE_INITIALIZED))\n    return 1;\n\n  return 0;\n}\n\nstatic int spr_offonexit_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if((mzx_world->sprite_list[spr_num])->flags & SPRITE_OFF_ON_EXIT)\n    return 1;\n\n  return 0;\n}\n\nstatic int spr_cwidth_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->col_width;\n}\n\nstatic int spr_cheight_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  return (mzx_world->sprite_list[spr_num])->col_height;\n}\n\nstatic void spr_num_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  // This only used the lowest byte prior to 2.92.\n  if(mzx_world->version < V292)\n    value &= 0xFF;\n\n  mzx_world->sprite_num = value;\n}\n\nstatic void spr_yorder_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->sprite_y_order = value & 1;\n}\n\nstatic void spr_ccheck_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  struct sprite *cur_sprite = mzx_world->sprite_list[spr_num];\n\n  if(mzx_world->version < V290)\n  {  // Old (somewhat dodgy) behaviour tied to <2.90\n    value %= 3;\n    switch(value)\n    {\n      case 0:\n      {\n        cur_sprite->flags &= ~(SPRITE_CHAR_CHECK | SPRITE_CHAR_CHECK2);\n        break;\n      }\n\n      case 1:\n      {\n        cur_sprite->flags |= SPRITE_CHAR_CHECK;\n        break;\n      }\n\n      case 2:\n      {\n        cur_sprite->flags |= SPRITE_CHAR_CHECK2;\n        break;\n      }\n    }\n  }\n  else\n  { // 2.90 makes use of both flags to allow 3 different ccheck modes\n    value %= 4;\n    cur_sprite->flags &= ~(SPRITE_CHAR_CHECK | SPRITE_CHAR_CHECK2);\n    switch(value)\n    {\n      case 0: break;\n      case 1: cur_sprite->flags |= SPRITE_CHAR_CHECK; break;\n      case 2: cur_sprite->flags |= SPRITE_CHAR_CHECK2; break;\n      case 3: cur_sprite->flags |= SPRITE_CHAR_CHECK | SPRITE_CHAR_CHECK2; break;\n    }\n  }\n}\n\nstatic void spr_clist_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  struct sprite *cur_sprite = mzx_world->sprite_list[spr_num];\n  sprite_colliding_xy(mzx_world, cur_sprite, cur_sprite->x,\n   cur_sprite->y);\n}\n\nstatic void spr_cx_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if(mzx_world->version < V290) // Before 2.90 these fields were chars.\n    value = (signed char) value;\n  (mzx_world->sprite_list[spr_num])->col_x = value;\n}\n\nstatic void spr_cy_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if(mzx_world->version < V290) // Before 2.90 these fields were chars.\n    value = (signed char) value;\n  (mzx_world->sprite_list[spr_num])->col_y = value;\n}\n\nstatic void spr_tcol_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  (mzx_world->sprite_list[spr_num])->transparent_color = value;\n}\n\nstatic void spr_offset_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if(layer_renderer_check(true))\n    (mzx_world->sprite_list[spr_num])->offset = value;\n}\n\nstatic void spr_unbound_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if(layer_renderer_check(true))\n  {\n    (mzx_world->sprite_list[spr_num])->flags &= ~SPRITE_UNBOUND;\n    (mzx_world->sprite_list[spr_num])->flags |= value ? SPRITE_UNBOUND : 0;\n  }\n}\n\nstatic void spr_height_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if(mzx_world->version < V290) // Before 2.90 these fields were chars.\n    value = (char) value;\n  (mzx_world->sprite_list[spr_num])->height = value;\n}\n\nstatic void spr_width_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if(mzx_world->version < V290) // Before 2.90 these fields were chars.\n    value = (char) value;\n  (mzx_world->sprite_list[spr_num])->width = value;\n}\n\nstatic void spr_refx_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n\n  if(value < 0)\n    value = 0;\n\n  (mzx_world->sprite_list[spr_num])->ref_x = value;\n}\n\nstatic void spr_refy_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n\n  if(value < 0)\n    value = 0;\n\n  (mzx_world->sprite_list[spr_num])->ref_y = value;\n}\n\nstatic void spr_x_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  (mzx_world->sprite_list[spr_num])->x = value;\n}\n\nstatic void spr_y_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  (mzx_world->sprite_list[spr_num])->y = value;\n}\n\nstatic void spr_z_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  (mzx_world->sprite_list[spr_num])->z = value;\n}\n\nstatic void spr_vlayer_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if(value)\n    (mzx_world->sprite_list[spr_num])->flags |= SPRITE_VLAYER;\n  else\n    (mzx_world->sprite_list[spr_num])->flags &= ~SPRITE_VLAYER;\n}\n\nstatic void spr_static_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if(value)\n    (mzx_world->sprite_list[spr_num])->flags |= SPRITE_STATIC;\n  else\n    (mzx_world->sprite_list[spr_num])->flags &= ~SPRITE_STATIC;\n}\n\nstatic void spr_overlaid_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if(value)\n    (mzx_world->sprite_list[spr_num])->flags |= SPRITE_OVER_OVERLAY;\n  else\n    (mzx_world->sprite_list[spr_num])->flags &= ~SPRITE_OVER_OVERLAY;\n}\n\nstatic void spr_off_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  struct sprite *cur_sprite = mzx_world->sprite_list[spr_num];\n\n  // In DOS versions of MZX, this would be ignored if set to 0.\n  if(value || mzx_world->version >= VERSION_PORT)\n  {\n    if(cur_sprite->flags & SPRITE_INITIALIZED)\n    {\n      // Note- versions prior to 2.92 wouldn't decrement active_sprites here.\n      cur_sprite->flags &= ~SPRITE_INITIALIZED;\n      mzx_world->active_sprites--;\n    }\n  }\n}\n\nstatic void spr_offonexit_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if(value)\n    (mzx_world->sprite_list[spr_num])->flags |= SPRITE_OFF_ON_EXIT;\n  else\n    (mzx_world->sprite_list[spr_num])->flags &= ~SPRITE_OFF_ON_EXIT;\n}\n\nstatic void spr_swap_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  struct sprite *src;\n  struct sprite *dest;\n\n  value &= (MAX_SPRITES - 1);\n\n  src = mzx_world->sprite_list[spr_num];\n  dest = mzx_world->sprite_list[value];\n  mzx_world->sprite_list[value] = src;\n  mzx_world->sprite_list[spr_num] = dest;\n}\n\nstatic void spr_cwidth_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if(mzx_world->version < V290) // Before 2.90 these fields were chars.\n    value = (char) value;\n  (mzx_world->sprite_list[spr_num])->col_width = value;\n}\n\nstatic void spr_cheight_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  if(mzx_world->version < V290) // Before 2.90 these fields were chars.\n    value = (char) value;\n  (mzx_world->sprite_list[spr_num])->col_height = value;\n}\n\nstatic void spr_setview_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  struct board *src_board = mzx_world->current_board;\n  int n_scroll_x, n_scroll_y;\n  int spr_num = strtol(name + 3, NULL, 10) & (MAX_SPRITES - 1);\n  struct sprite *cur_sprite = mzx_world->sprite_list[spr_num];\n  src_board->scroll_x = 0;\n  src_board->scroll_y = 0;\n  calculate_xytop(mzx_world, &n_scroll_x, &n_scroll_y);\n  if(cur_sprite->flags & SPRITE_UNBOUND)\n  {\n    src_board->scroll_x = ((cur_sprite->x + (signed)cur_sprite->width * 4\n     - src_board->viewport_width * 4) - n_scroll_x * 8) / 8;\n    src_board->scroll_y = ((cur_sprite->y + (signed)cur_sprite->height * 7\n     - src_board->viewport_height * 7) - n_scroll_x * 14) / 14;\n  }\n  else\n  {\n    src_board->scroll_x =\n    (cur_sprite->x + (cur_sprite->width >> 1)) -\n    (src_board->viewport_width >> 1) - n_scroll_x;\n    src_board->scroll_y =\n    (cur_sprite->y + (cur_sprite->height >> 1)) -\n    (src_board->viewport_height >> 1) - n_scroll_y;\n  }\n  return;\n}\n\nstatic int bullettype_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return (mzx_world->current_board->robot_list[id])->bullet_type;\n}\n\nstatic void bullettype_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  (mzx_world->current_board->robot_list[id])->bullet_type = value;\n}\n\nstatic int inputsize_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return (int)mzx_world->current_board->input_size;\n}\n\nstatic void inputsize_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->current_board->input_size = value;\n}\n\nstatic int input_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->current_board->num_input;\n}\n\nstatic void input_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  char buf[12];\n  sprintf(buf, \"%d\", value);\n  board_set_input_string(mzx_world->current_board, buf, strlen(buf));\n  mzx_world->current_board->num_input = value;\n}\n\nstatic int key_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  // In DOS versions key was a board counter\n  if(mzx_world->version < VERSION_PORT)\n  {\n    return mzx_world->current_board->last_key;\n  }\n  return toupper(get_last_key(keycode_internal));\n}\n\nstatic void key_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  // In DOS versions key was a board counter\n  if(mzx_world->version < VERSION_PORT)\n  {\n    mzx_world->current_board->last_key = CLAMP(value, 0, 255);\n  }\n  force_last_key(keycode_internal, toupper(value));\n}\n\nstatic int keyn_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int key_num = strtol(name + 3, NULL, 10);\n  return get_key_status(keycode_pc_xt, key_num);\n}\n\nstatic int key_code_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return get_key(keycode_pc_xt);\n}\n\nstatic int key_pressed_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  // In 2.82X through 2.84X, this erroneously applied\n  // numlock translations to the keycode.\n  if(mzx_world->version >= V282 && mzx_world->version <= V284)\n    return get_key(keycode_internal_wrt_numlock);\n\n  return get_key(keycode_internal);\n}\n\nstatic int key_release_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return get_last_key_released(keycode_pc_xt);\n}\n\nstatic int key_pressedn_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int key_num = strtol(name + 11, NULL, 10);\n  return get_key_status(keycode_internal, key_num) > 0;\n}\n\nstatic int joyn_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  char *dot_ptr;\n  int joystick = strtol(name + 3, &dot_ptr, 10) - 1;\n  boolean is_active;\n  int16_t value;\n\n  if(*dot_ptr == '.')\n  {\n    if(joystick_is_active(joystick, &is_active) && is_active &&\n     joystick_get_status(joystick, dot_ptr + 1, &value))\n    {\n      // -1 should always mean the status is invalid or unavailable,\n      // so remap a legitimate value of -1 to -2. This only affects axes,\n      // which should rarely return this exact value anyway.\n      if(value == -1)\n        value--;\n\n      return value;\n    }\n  }\n  return -1;\n}\n\nstatic int joyn_active_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int joystick = strtol(name + 3, NULL, 10) - 1;\n  boolean is_active;\n\n  if(joystick_is_active(joystick, &is_active))\n    return is_active;\n  return -1;\n}\n\nstatic void joy_simulate_keys_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->joystick_simulate_keys = !!value;\n  joystick_set_game_bindings(!!value);\n}\n\nstatic int scrolledx_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int scroll_x, scroll_y;\n  calculate_xytop(mzx_world, &scroll_x, &scroll_y);\n  return scroll_x;\n}\n\nstatic int scrolledy_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int scroll_x, scroll_y;\n  calculate_xytop(mzx_world, &scroll_x, &scroll_y);\n  return scroll_y;\n}\n\nstatic int timereset_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->current_board->time_limit;\n}\n\nstatic void timereset_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->current_board->time_limit = CLAMP(value, 0, 32767);\n}\n\nstatic void refresh_time(struct world *mzx_world)\n{\n  if(!(mzx_world->command_cache & COMMAND_CACHE_CURRENT_TIME))\n  {\n    mzx_world->command_cache |= COMMAND_CACHE_CURRENT_TIME;\n\n    memset(&(mzx_world->current_time), 0, sizeof(mzx_world->current_time));\n    mzx_world->current_time_epoch = 0;\n    mzx_world->current_time_nano = 0;\n\n    platform_system_time(&(mzx_world->current_time),\n     &(mzx_world->current_time_epoch), &(mzx_world->current_time_nano));\n  }\n}\n\nstatic int date_day_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  refresh_time(mzx_world);\n  return mzx_world->current_time.tm_mday;\n}\n\nstatic int date_year_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  refresh_time(mzx_world);\n  return mzx_world->current_time.tm_year + 1900;\n}\n\nstatic int date_month_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  refresh_time(mzx_world);\n  return mzx_world->current_time.tm_mon + 1;\n}\n\nstatic int date_weekday_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  refresh_time(mzx_world);\n  return mzx_world->current_time.tm_wday;\n}\n\nstatic int time_hours_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  refresh_time(mzx_world);\n  return mzx_world->current_time.tm_hour;\n}\n\nstatic int time_minutes_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  refresh_time(mzx_world);\n  return mzx_world->current_time.tm_min;\n}\n\nstatic int time_seconds_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  refresh_time(mzx_world);\n  return mzx_world->current_time.tm_sec;\n}\n\nstatic int time_millis_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  refresh_time(mzx_world);\n  return mzx_world->current_time_nano / 1000000;\n}\n\nstatic int random_seed_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int idx = strtol(name + 11, NULL, 10) % 2;\n  unsigned int seed = rng_get_seed() >> (32 * idx) & 0xFFFFFFFF;\n  return *((signed int *)&seed);\n}\n\nstatic void random_seed_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int idx = strtol(name + 11, NULL, 10) % 2;\n  uint64_t seed = rng_get_seed();\n  uint64_t mask = ~(0xFFFFFFFFULL << (32 * idx));\n  uint64_t insert = *((unsigned int *)&value);\n  seed = (seed & mask) | (insert << (32 * idx));\n  rng_set_seed(seed);\n}\n\nstatic int vch_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  unsigned int x = 0, y = 0;\n  unsigned int vlayer_width = mzx_world->vlayer_width;\n  unsigned int vlayer_height = mzx_world->vlayer_height;\n  translate_coordinates(name + 3, &x, &y);\n\n  if((x < vlayer_width) && (y < vlayer_height))\n  {\n    int offset = x + (y * vlayer_width);\n    return mzx_world->vlayer_chars[offset];\n  }\n\n  return -1;\n}\n\nstatic void vch_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  unsigned int x = 0, y = 0;\n  unsigned int vlayer_width = mzx_world->vlayer_width;\n  unsigned int vlayer_height = mzx_world->vlayer_height;\n  translate_coordinates(name + 3, &x, &y);\n\n  if((x < vlayer_width) && (y < vlayer_height))\n  {\n    int offset = x + (y * vlayer_width);\n    mzx_world->vlayer_chars[offset] = value;\n  }\n}\n\nstatic int vco_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  unsigned int x = 0, y = 0;\n  unsigned int vlayer_width = mzx_world->vlayer_width;\n  unsigned int vlayer_height = mzx_world->vlayer_height;\n  translate_coordinates(name + 3, &x, &y);\n\n  if((x < vlayer_width) && (y < vlayer_height))\n  {\n    int offset = x + (y * vlayer_width);\n    return mzx_world->vlayer_colors[offset];\n  }\n\n  return -1;\n}\n\nstatic void vco_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  unsigned int x = 0, y = 0;\n  unsigned int vlayer_width = mzx_world->vlayer_width;\n  unsigned int vlayer_height = mzx_world->vlayer_height;\n  translate_coordinates(name + 3, &x, &y);\n\n  if((x < vlayer_width) && (y < vlayer_height))\n  {\n    int offset = x + (y * vlayer_width);\n    mzx_world->vlayer_colors[offset] = value;\n  }\n}\n\n/***** BOARD READING COUNTERS START *****/\nstatic int och_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset = 0;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n\n  if((board->overlay_mode > 0) && (x < board_width) && (y < board_height))\n  {\n    offset = x + (y * board_width);\n    return board->overlay[offset];\n  }\n\n  return -1;\n}\n\nstatic int oco_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset = 0;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n\n  if((board->overlay_mode > 0) && (x < board_width) && (y < board_height))\n  {\n    offset = x + (y * board_width);\n    return board->overlay_color[offset];\n  }\n\n  return -1;\n}\n\n// vco-syle board access\nstatic int bch_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset = 0;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n\n  if((x < board_width) && (y < board_height))\n  {\n    offset = x + (y * board_width);\n    return get_id_char(board, offset);\n  }\n\n  return -1;\n}\n\nstatic int bco_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset = 0;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n\n  if((x < board_width) && (y < board_height))\n  {\n    offset = x + (y * board_width);\n    return get_id_board_color(board, offset, 1); //1 == ignore_under\n  }\n\n  return -1;\n}\n\nstatic int bid_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n\n  if((x < board_width) && (y < board_height))\n  {\n    offset = x + (y * board_width);\n    return board->level_id[offset];\n  }\n\n  return -1;\n}\n\nstatic void bid_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  char cvalue = value;\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n\n  if((x < board_width) && (y < board_height))\n  {\n    offset = x + (y * board_width);\n    if((cvalue < SENSOR) && (board->level_id[offset] < SENSOR))\n      board->level_id[offset] = cvalue;\n  }\n}\n\nstatic int bpr_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n\n  if((x < board_width) && (y < board_height))\n  {\n    offset = x + (y * board_width);\n    return board->level_param[offset];\n  }\n\n  return -1;\n}\n\nstatic void bpr_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n\n  if((x < board_width) && (y < board_height))\n  {\n    offset = x + (y * board_width);\n    if(board->level_id[offset] < 122)\n      board->level_param[offset] = value;\n  }\n}\n\n//vco style under-board access.  Ignore all attempts if upper-board is A_UNDER\nstatic int uch_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n    offset = x + (y * board_width);\n\n  if((x < board_width) && (y < board_height) &&\n   !(flags[(int)board->level_id[offset]] & A_UNDER))\n    return get_id_under_char(board, offset);\n\n  return -1;\n}\n\nstatic int uco_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n  offset = x + (y * board_width);\n\n  if((x < board_width) && (y < board_height) &&\n   !(flags[(int)board->level_id[offset]] & A_UNDER))\n    return get_id_under_color(board, offset);\n\n  return -1;\n}\n\nstatic int uid_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n  offset = x + (y * board_width);\n\n  if((x < board_width) && (y < board_height) &&\n   !(flags[(int)board->level_id[offset]] & A_UNDER))\n    return board->level_under_id[offset];\n\n  return -1;\n}\n\nstatic void uid_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  char cvalue = value;\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n  offset = x + (y * board_width);\n\n  if((x < board_width) && (y < board_height) &&\n   !(flags[(int)board->level_id[offset]] & A_UNDER) &&\n   (cvalue < SENSOR) && (board->level_under_id[offset] < SENSOR))\n    board->level_under_id[offset] = cvalue;\n}\n\nstatic int upr_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n  offset = x + (y * board_width);\n\n  if((x < board_width) && (y < board_height) &&\n   !(flags[(int)board->level_id[offset]] & A_UNDER))\n    return board->level_under_param[offset];\n\n  return -1;\n}\n\nstatic void upr_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  struct board *board = mzx_world->current_board;\n  unsigned int x = 0, y = 0, offset;\n  unsigned int board_width = board->board_width;\n  unsigned int board_height = board->board_height;\n  translate_coordinates(name + 3, &x, &y);\n  offset = x + (y * board_width);\n\n  if((x < board_width) && (y < board_height) &&\n   !(flags[(int)board->level_id[offset]] & A_UNDER) &&\n   (board->level_under_id[offset] < 122))\n    board->level_under_param[offset] = value;\n}\n\n/***** END THE STUPID BOARD ACCESS COUNTERS *****/\n\nstatic int char_byte_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  uint16_t char_num = get_counter(mzx_world, \"CHAR\", id);\n  uint8_t byte_num = get_counter(mzx_world, \"BYTE\", id);\n\n  // Prior to 2.90 char params are clipped.\n  if(mzx_world->version < V290)\n    char_num &= 0xFF;\n\n  if(byte_num >= 14)\n  {\n    // Old port releases are missing a bounds check (see: Day of Zeux Invitation).\n    if(mzx_world->version >= VERSION_PORT && mzx_world->version < V292)\n      char_num += byte_num / 14;\n\n    byte_num %= 14;\n  }\n\n  return ec_read_byte(char_num, byte_num);\n}\n\nstatic void char_byte_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  uint16_t char_num = get_counter(mzx_world, \"CHAR\", id);\n  uint8_t byte_num = get_counter(mzx_world, \"BYTE\", id);\n\n  // Prior to 2.90 char params are clipped.\n  if(mzx_world->version < V290)\n    char_num &= 0xFF;\n\n  if(byte_num >= 14)\n  {\n    // Old port releases are missing a bounds check (see: Day of Zeux Invitation).\n    // This may write the byte into the extended charsets, which doesn't really\n    // matter for these games (and is better than corrupting the protected set).\n    if(mzx_world->version >= VERSION_PORT && mzx_world->version < V292)\n      char_num += byte_num / 14;\n\n    byte_num %= 14;\n  }\n\n  ec_change_byte(char_num, byte_num, value);\n}\n\nstatic int pixel_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int pixel_x = CLAMP(get_counter(mzx_world, \"CHAR_X\", id), 0, 255);\n  int pixel_y = CLAMP(get_counter(mzx_world, \"CHAR_Y\", id), 0, 111);\n  char sub_x, sub_y, current_byte, current_char;\n  int pixel_mask;\n\n  sub_x = pixel_x % 8;\n  sub_y = pixel_y % 14;\n  current_char = ((pixel_y / 14) * 32) + (pixel_x / 8);\n  current_byte = ec_read_byte(current_char, sub_y);\n  pixel_mask = (128 >> sub_x);\n\n  return (current_byte & pixel_mask) >> (7 - sub_x);\n}\n\nstatic void pixel_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int pixel_x = CLAMP(get_counter(mzx_world, \"CHAR_X\", id), 0, 255);\n  int pixel_y = CLAMP(get_counter(mzx_world, \"CHAR_Y\", id), 0, 111);\n  char sub_x, sub_y, current_byte, current_char;\n\n  sub_x = pixel_x & 7;\n  sub_y = pixel_y % 14;\n  current_char = ((pixel_y / 14) << 5) + (pixel_x >> 3);\n  current_byte = ec_read_byte(current_char, sub_y);\n\n  switch(value)\n  {\n    case 0:\n    {\n      //clear\n      current_byte &= ~(128 >> sub_x);\n      ec_change_byte(current_char, sub_y, current_byte);\n      break;\n    }\n    case 1:\n    {\n      //set\n      current_byte |= (128 >> sub_x);\n      ec_change_byte(current_char, sub_y, current_byte);\n      break;\n    }\n    case 2:\n    {\n      //toggle\n      current_byte ^= (128 >> sub_x);\n      ec_change_byte(current_char, sub_y, current_byte);\n      break;\n    }\n  }\n  return;\n}\n\nstatic int int2bin_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int place = get_counter(mzx_world, \"BIT_PLACE\", id) & 15;\n  return ((get_counter(mzx_world, \"INT\", id) >> place) & 1);\n}\n\nstatic void int2bin_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  unsigned int integer;\n  int place;\n  place = (get_counter(mzx_world, \"BIT_PLACE\", id) & 15);\n  integer = get_counter(mzx_world, \"INT\", id);\n\n  switch(value & 2)\n  {\n    case 0:\n    {\n      // clear\n      integer &= ~(1 << place);\n      break;\n    }\n\n    case 1:\n    {\n      // set\n      integer |= (1 << place);\n      break;\n    }\n\n    case 2:\n    {\n      // toggle\n      integer ^= (1 << place);\n      break;\n    }\n  }\n\n  set_counter(mzx_world, \"INT\", integer, id);\n}\n\nstatic int commands_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->commands;\n}\n\nstatic void commands_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->commands = value;\n}\n\nstatic int commands_stop_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->commands_stop;\n}\n\nstatic void commands_stop_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->commands_stop = value;\n}\n\nstatic int fread_open_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_FREAD;\n  return 0;\n}\n\nstatic int fwrite_open_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_FWRITE;\n  return 0;\n}\n\nstatic int fwrite_append_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_FAPPEND;\n  return 0;\n}\n\nstatic int fwrite_modify_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_FMODIFY;\n  return 0;\n}\n\nstatic int smzx_palette_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_SMZX_PALETTE;\n  return 0;\n}\n\nstatic int smzx_indices_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_SMZX_INDICES;\n  return 0;\n}\n\nstatic void exit_game_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(value)\n  {\n    // Signal the main loop that the game state should change.\n    mzx_world->change_game_state = CHANGE_STATE_EXIT_GAME_ROBOTIC;\n  }\n}\n\nstatic void play_game_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(value && get_config()->standalone_mode)\n  {\n    // Signal the main loop that the game state should change.\n    mzx_world->change_game_state = CHANGE_STATE_PLAY_GAME_ROBOTIC;\n  }\n}\n\nstatic int load_game_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_LOAD_GAME;\n  return 0;\n}\n\nstatic int save_game_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_SAVE_GAME;\n  return 0;\n}\n\nstatic int load_counters_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_LOAD_COUNTERS;\n  return 0;\n}\n\nstatic int save_counters_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_SAVE_COUNTERS;\n  return 0;\n}\n\nstatic int load_robot_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_LOAD_ROBOT;\n  if(name[10])\n    return strtol(name + 10, NULL, 10);\n\n  return -1;\n}\n\nstatic int load_bc_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_LOAD_BC;\n  if(name[7])\n    return strtol(name + 7, NULL, 10);\n\n  return -1;\n}\n\nstatic int save_robot_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_SAVE_ROBOT;\n  if(name[10])\n    return strtol(name + 10, NULL, 10);\n\n  return -1;\n}\n\nstatic int save_bc_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  mzx_world->special_counter_return = FOPEN_SAVE_BC;\n  if(name[7])\n    return strtol(name + 7, NULL, 10);\n\n  return -1;\n}\n\nstatic int fread_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  if(!mzx_world->input_is_dir && mzx_world->input_file)\n    return vfgetc(mzx_world->input_file);\n  return -1;\n}\n\nstatic int fread_counter_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  if(!mzx_world->input_is_dir && mzx_world->input_file)\n  {\n    if(mzx_world->version < V282)\n      return vfgetw(mzx_world->input_file);\n    else\n      return vfgetd(mzx_world->input_file);\n  }\n  return -1;\n}\n\nstatic int fread_pos_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  if(!mzx_world->input_is_dir && mzx_world->input_file)\n  {\n    int64_t pos = vftell(mzx_world->input_file);\n    return CLAMP(pos, -1, INT_MAX);\n  }\n  else\n\n  if(mzx_world->input_is_dir)\n  {\n    return vdir_tell(mzx_world->input_directory);\n  }\n  else\n    return -1;\n}\n\nstatic void fread_pos_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(!mzx_world->input_is_dir && mzx_world->input_file)\n  {\n    if(value == -1)\n      vfseek(mzx_world->input_file, 0, SEEK_END);\n    else\n      vfseek(mzx_world->input_file, value, SEEK_SET);\n  }\n  else if(mzx_world->input_is_dir)\n  {\n    if(value >= 0)\n      vdir_seek(mzx_world->input_directory, value);\n  }\n}\n\nstatic int fread_length_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  if(mzx_world->input_is_dir)\n    return vdir_length(mzx_world->input_directory);\n\n  if(mzx_world->input_file)\n  {\n    int64_t len = vfilelength(mzx_world->input_file, false);\n    return CLAMP(len, -1, INT_MAX);\n  }\n\n  return -1;\n}\n\nstatic void fread_delim_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->fread_delimiter = value % 256;\n}\n\nstatic void fwrite_delim_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->fwrite_delimiter = value % 256;\n}\n\nstatic int fwrite_pos_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  if(mzx_world->output_file)\n  {\n    int64_t pos = vftell(mzx_world->output_file);\n    return CLAMP(pos, -1, INT_MAX);\n  }\n  else\n    return -1;\n}\n\nstatic void fwrite_pos_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(mzx_world->output_file)\n  {\n    if(value == -1)\n      vfseek(mzx_world->output_file, 0, SEEK_END);\n    else\n      vfseek(mzx_world->output_file, value, SEEK_SET);\n  }\n}\n\nstatic void fwrite_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(mzx_world->output_file)\n    vfputc(value, mzx_world->output_file);\n}\n\nstatic void fwrite_counter_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(mzx_world->output_file)\n  {\n    if(mzx_world->version < V282)\n      vfputw(value, mzx_world->output_file);\n    else\n      vfputd(value, mzx_world->output_file);\n  }\n}\n\nstatic int fwrite_length_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  if(mzx_world->output_file)\n  {\n    int64_t len = vfilelength(mzx_world->output_file, false);\n    return CLAMP(len, -1, INT_MAX);\n  }\n  return -1;\n}\n\nstatic int lava_walk_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return (mzx_world->current_board->robot_list[id])->can_lavawalk;\n}\n\nstatic void lava_walk_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  (mzx_world->current_board->robot_list[id])->can_lavawalk = value;\n}\n\nstatic int goop_walk_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return (mzx_world->current_board->robot_list[id])->can_goopwalk;\n}\n\nstatic void goop_walk_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  (mzx_world->current_board->robot_list[id])->can_goopwalk = value;\n}\n\nstatic int robot_id_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return id;\n}\n\nstatic int robot_id_n_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return get_robot_id(mzx_world->current_board, name + 9);\n}\n\nstatic int rid_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return get_robot_id(mzx_world->current_board, name + 3);\n}\n\nstatic int r_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  struct board *src_board = mzx_world->current_board;\n  char *next;\n  unsigned int robot_num = strtol(name + 1, &next, 10);\n\n  if((robot_num <= (unsigned int)src_board->num_robots) &&\n   (src_board->robot_list[robot_num] != NULL))\n  {\n    return get_counter(mzx_world, next + 1, robot_num);\n  }\n\n  return -1;\n}\n\nstatic void r_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  struct board *src_board = mzx_world->current_board;\n  char *next;\n  unsigned int robot_num = strtol(name + 1, &next, 10);\n\n  if((robot_num <= (unsigned int)src_board->num_robots) &&\n   (src_board->robot_list[robot_num] != NULL))\n  {\n    set_counter(mzx_world, next + 1, value, robot_num);\n  }\n}\n\nstatic int viewport_x_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->current_board->viewport_x;\n}\n\nstatic int viewport_y_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->current_board->viewport_y;\n}\n\nstatic int viewport_width_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->current_board->viewport_width;\n}\n\nstatic int viewport_height_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->current_board->viewport_height;\n}\n\nstatic int vlayer_size_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->vlayer_size;\n}\n\nstatic int vlayer_height_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->vlayer_height;\n}\n\nstatic int vlayer_width_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return mzx_world->vlayer_width;\n}\n\nstatic void vlayer_size_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(value <= 0)\n    value = 1;\n\n  if(value <= MAX_BOARD_SIZE)\n  {\n    int vlayer_width = mzx_world->vlayer_width;\n    int vlayer_height = mzx_world->vlayer_height;\n    int old_size = mzx_world->vlayer_size;\n\n    mzx_world->vlayer_chars = crealloc(mzx_world->vlayer_chars, value);\n    mzx_world->vlayer_colors = crealloc(mzx_world->vlayer_colors, value);\n    mzx_world->vlayer_size = value;\n\n    if(old_size > value)\n    {\n      vlayer_height = value / vlayer_width;\n      mzx_world->vlayer_height = vlayer_height;\n\n      if(vlayer_height == 0)\n      {\n        mzx_world->vlayer_width = value;\n        mzx_world->vlayer_height = 1;\n      }\n    }\n    else\n\n    if(old_size < value && mzx_world->version >= V291)\n    {\n      // Clear new vlayer area (2.91+)\n      memset(mzx_world->vlayer_chars + old_size, 32, value - old_size);\n      memset(mzx_world->vlayer_colors + old_size, 7, value - old_size);\n    }\n  }\n}\n\nstatic void vlayer_height_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int new_width;\n  int new_height;\n\n  if(value <= 0)\n    value = 1;\n  if(value > mzx_world->vlayer_size)\n    value = mzx_world->vlayer_size;\n\n  new_width = mzx_world->vlayer_size / value;\n  new_height = value;\n\n  if(mzx_world->version >= V291)\n  {\n    // Manipulate vlayer data so its contents change little as possible (2.91+)\n    remap_vlayer(mzx_world, new_width, new_height);\n  }\n  else\n  {\n    mzx_world->vlayer_width = new_width;\n    mzx_world->vlayer_height = new_height;\n  }\n}\n\nstatic void vlayer_width_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  int new_width;\n  int new_height;\n\n  if(value <= 0)\n    value = 1;\n  if(value > mzx_world->vlayer_size)\n    value = mzx_world->vlayer_size;\n\n  new_width = value;\n  new_height = mzx_world->vlayer_size / value;\n\n  if(mzx_world->version >= V291)\n  {\n    // Manipulate vlayer data so its contents change little as possible (2.91+)\n    remap_vlayer(mzx_world, new_width, new_height);\n  }\n  else\n  {\n    mzx_world->vlayer_width = new_width;\n    mzx_world->vlayer_height = new_height;\n  }\n}\n\nstatic int buttons_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int buttons_formatted = 0;\n\n  // This is sufficient for the buttons.\n  int raw_status = get_mouse_status();\n\n  // Wheel presses are immediately released. We need this too.\n  int raw_status_ext = get_mouse_press_ext();\n\n  /* Since button values are inconsistent even for the same SDL\n   * version across platforms, and we also want right to be the\n   * second bit and middle the third, we'll map every button.\n   */\n\n  if(raw_status & MOUSE_BUTTON(MOUSE_BUTTON_LEFT))\n    buttons_formatted |= MOUSE_BUTTON(1);\n\n  if(raw_status & MOUSE_BUTTON(MOUSE_BUTTON_RIGHT))\n    buttons_formatted |= MOUSE_BUTTON(2);\n\n  if(raw_status & MOUSE_BUTTON(MOUSE_BUTTON_MIDDLE))\n    buttons_formatted |= MOUSE_BUTTON(3);\n\n  // For 2.90+, also map the wheel and side buttons\n  if(mzx_world->version >= V290)\n  {\n    if(raw_status_ext == MOUSE_BUTTON_WHEELUP)\n      buttons_formatted |= MOUSE_BUTTON(4);\n\n    if(raw_status_ext == MOUSE_BUTTON_WHEELDOWN)\n      buttons_formatted |= MOUSE_BUTTON(5);\n\n    if(raw_status_ext == MOUSE_BUTTON_WHEELLEFT)\n      buttons_formatted |= MOUSE_BUTTON(6);\n\n    if(raw_status_ext == MOUSE_BUTTON_WHEELRIGHT)\n      buttons_formatted |= MOUSE_BUTTON(7);\n\n    if(raw_status & MOUSE_BUTTON(MOUSE_BUTTON_X1))\n      buttons_formatted |= MOUSE_BUTTON(8);\n\n    if(raw_status & MOUSE_BUTTON(MOUSE_BUTTON_X2))\n      buttons_formatted |= MOUSE_BUTTON(9);\n  }\n\n  return buttons_formatted;\n}\n\nstatic int mousex_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return get_mouse_x();\n}\n\nstatic int mousey_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return get_mouse_y();\n}\n\nstatic void mousex_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(value > 79)\n    value = 79;\n\n  if(value < 0)\n    value = 0;\n\n  warp_mouse_x(value);\n}\n\nstatic void mousey_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(value > 24)\n    value = 24;\n\n  if(value < 0)\n    value = 0;\n\n  warp_mouse_y(value);\n}\n\nstatic int mousepx_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return get_mouse_pixel_x();\n}\n\nstatic int mousepy_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return get_mouse_pixel_y();\n}\n\nstatic void mousepx_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(value > 639)\n    value = 639;\n\n  if(value < 0)\n    value = 0;\n\n  warp_mouse_pixel_x(value);\n}\n\nstatic void mousepy_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(value > 349)\n    value = 349;\n\n  if(value < 0)\n    value = 0;\n\n  warp_mouse_pixel_y(value);\n}\n\nstatic int mboardx_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int scroll_x, scroll_y;\n  calculate_xytop(mzx_world, &scroll_x, &scroll_y);\n  return get_mouse_x() +\n   scroll_x - mzx_world->current_board->viewport_x;\n}\n\nstatic int mboardy_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  int scroll_x, scroll_y;\n  calculate_xytop(mzx_world, &scroll_x, &scroll_y);\n  return get_mouse_y() +\n   scroll_y - mzx_world->current_board->viewport_y;\n}\n\nstatic void spacelock_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->bi_shoot_status = value & 1;\n}\n\nstatic void bimesg_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  mzx_world->bi_mesg_status = value & 1;\n}\n\nstatic int mod_order_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return audio_get_module_order();\n}\n\nstatic void mod_order_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  audio_set_module_order(value);\n}\n\nstatic int mod_position_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return audio_get_module_position();\n}\n\nstatic void mod_position_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  audio_set_module_position(value);\n}\n\nstatic int mod_length_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return audio_get_module_length();\n}\n\nstatic int mod_loopend_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return audio_get_module_loop_end();\n}\n\nstatic void mod_loopend_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(value >= 0)\n    audio_set_module_loop_end(value);\n}\n\nstatic int mod_loopstart_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return audio_get_module_loop_start();\n}\n\nstatic void mod_loopstart_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if(value >= 0)\n    audio_set_module_loop_start(value);\n}\n\nstatic int mod_freq_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return audio_get_module_frequency();\n}\n\nstatic void mod_freq_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  if((value == 0) || ((value >= 16) && (value <= (2 << 20))))\n    audio_set_module_frequency(value);\n}\n\nstatic int max_samples_read(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int id)\n{\n  return audio_get_max_samples();\n}\n\nstatic void max_samples_write(struct world *mzx_world,\n const struct function_counter *counter, const char *name, int value, int id)\n{\n  // -1 for unlimited samples, or 0+ for an explicit number of samples\n  value = MAX(-1, value);\n\n  mzx_world->max_samples = value;\n  audio_set_max_samples(value);\n}\n\n// Make sure this is in the right alphabetical (non-case sensitive) order\n// ? specifies 0 or more numbers\n// ! specifies 1 or more numbers\n// * skips any characters at or after this point, including a null terminator\n// This must be sorted in a case insensitive way, that has special applications\n// for non-letters:\n// _ comes after letters\n// ? comes before letters\n// numbers come before letters (and before ?)\n\n/* FIXME: \"MOD *\" will still work, even in worlds < 2.51s1 */\n\n/* NOTE: FREAD_LENGTH and FWRITE_LENGTH were removed in 2.65 and no\n *       compatibility layer was implemented. The reimplementation of these\n *       counters is versioned to 2.92.\n *       ABS_VALUE, R_PLAYERDIST, SQRT_VALUE, WRAP, VALUE were removed in\n *       2.68 and no compatibility layer was implemented.\n *       FREAD_PAGE, FWRITE_PAGE were removed in 2.80 and no compatibility\n *       layer was implemented.\n *       SAVE_WORLD was removed in 2.90 and no compatibility layer was\n *       implemented.\n */\n\n/* NOTE: fread_counter/fwrite_counter read only a single DOS word (16bit)\n *       in MZX versions prior to 2.82. Newer versions read in a single\n *       DOS dword (32bit). Since the counter was introduced in versions\n *       prior to 2.82, it is not versioned as such below (see the read/write\n *       functions for the exact semantics).\n */\n\n/* NOTE: Compatibility with worlds made between 2.51s2 and 2.61 (inclusive)\n *       is only partial, due to the MZX/SAV magic not being incremented\n *       in these versions. This is unfortunately not fixable.\n */\n\nstatic const struct function_counter builtin_counters[] =\n{\n  { \"$*\",               V262,   string_counter_read,  string_counter_write },\n  { \"abs!\",             V268,   abs_read,             NULL },\n  { \"acos!\",            V268,   acos_read,            NULL },\n  { \"arctan!,!\",        V284,   atan2_read,           NULL },\n  { \"asin!\",            V268,   asin_read,            NULL },\n  { \"atan!\",            V268,   atan_read,            NULL },\n  { \"bch!,!\",           V284,   bch_read,             NULL },\n  { \"bco!,!\",           V284,   bco_read,             NULL },\n  { \"bid!,!\",           V284,   bid_read,             bid_write },\n  { \"bimesg\",           V251s3, NULL,                 bimesg_write }, // s3.2\n  { \"blue_value\",       V260,   blue_value_read,      blue_value_write },\n  { \"board_char\",       V260,   board_char_read,      NULL },\n  { \"board_color\",      V260,   board_color_read,     NULL },\n  { \"board_h\",          V265,   board_h_read,         NULL },\n  { \"board_id\",         V265,   board_id_read,        board_id_write },\n  { \"board_param\",      V265,   board_param_read,     board_param_write },\n  { \"board_w\",          V265,   board_w_read,         NULL },\n  { \"bpr!,!\",           V284,   bpr_read,             bpr_write },\n  { \"bullettype\",       V100,   bullettype_read,      bullettype_write },\n  { \"buttons\",          V251s1, buttons_read,         NULL },\n  { \"char_byte\",        V260,   char_byte_read,       char_byte_write },\n  { \"commands\",         V260,   commands_read,        commands_write },\n  { \"commands_stop\",    V290,   commands_stop_read,   commands_stop_write },\n  { \"cos!\",             V268,   cos_read,             NULL },\n  { \"c_divisions\",      V268,   c_divisions_read,     c_divisions_write },\n  { \"date_day\",         V260,   date_day_read,        NULL },\n  { \"date_month\",       V260,   date_month_read,      NULL },\n  { \"date_weekday\",     V293,   date_weekday_read,    NULL },\n  { \"date_year\",        V260,   date_year_read,       NULL },\n  { \"divider\",          V268,   divider_read,         divider_write },\n  { \"exit_game\",        V290,   NULL,                 exit_game_write },\n  { \"fread\",            V260,   fread_read,           NULL },\n  { \"fread_counter\",    V265,   fread_counter_read,   NULL },\n  { \"fread_delimiter\",  V284,   NULL,                 fread_delim_write },\n  { \"fread_length\",     V292,   fread_length_read,    NULL },\n  { \"fread_open\",       V260,   fread_open_read,      NULL },\n  { \"fread_pos\",        V260,   fread_pos_read,       fread_pos_write },\n  { \"fwrite\",           V260,   NULL,                 fwrite_write },\n  { \"fwrite_append\",    V260,   fwrite_append_read,   NULL },\n  { \"fwrite_counter\",   V265,   NULL,                 fwrite_counter_write },\n  { \"fwrite_delimiter\", V284,   NULL,                 fwrite_delim_write },\n  { \"fwrite_length\",    V292,   fwrite_length_read,   NULL },\n  { \"fwrite_modify\",    V269c,  fwrite_modify_read,   NULL },\n  { \"fwrite_open\",      V260,   fwrite_open_read,     NULL },\n  { \"fwrite_pos\",       V260,   fwrite_pos_read,      fwrite_pos_write },\n  { \"goop_walk\",        V290,   goop_walk_read,       goop_walk_write },\n  { \"green_value\",      V260,   green_value_read,     green_value_write },\n  { \"horizpld\",         V100,   horizpld_read,        NULL },\n  { \"input\",            V100,   input_read,           input_write },\n  { \"inputsize\",        V100,   inputsize_read,       inputsize_write },\n  { \"int2bin\",          V260,   int2bin_read,         int2bin_write },\n  { \"joy!.*\",           V292,   joyn_read,            NULL },\n  { \"joy!active\",       V292,   joyn_active_read,     NULL },\n  { \"joy_simulate_keys\",V292,   NULL,                 joy_simulate_keys_write },\n  { \"key\",              V100,   key_read,             key_write },\n  { \"key!\",             V269,   keyn_read,            NULL },\n  { \"key_code\",         V269,   key_code_read,        NULL },\n  { \"key_pressed\",      V260,   key_pressed_read,     NULL },\n  { \"key_pressed!\",     V293,   key_pressedn_read,    NULL },\n  { \"key_release\",      V269,   key_release_read,     NULL },\n  { \"lava_walk\",        V260,   lava_walk_read,       lava_walk_write },\n  { \"load_bc?\",         V270,   load_bc_read,         NULL },\n  { \"load_counters\",    V290,   load_counters_read,   NULL },\n  { \"load_game\",        V268,   load_game_read,       NULL },\n  { \"load_robot?\",      V270,   load_robot_read,      NULL },\n  { \"local\",            V251s1, local_read,           local_write },\n  { \"local!\",           V269b,  localn_read,          localn_write },\n  { \"loopcount\",        V200,   loopcount_read,       loopcount_write },\n  { \"max!,!\",           V284,   maxval_read,          NULL },\n  { \"max_samples\",      V291,   max_samples_read,     max_samples_write },\n  { \"mboardx\",          V251s1, mboardx_read,         NULL },\n  { \"mboardy\",          V251s1, mboardy_read,         NULL },\n  { \"min!,!\",           V284,   minval_read,          NULL },\n  { \"mod_frequency\",    V281,   mod_freq_read,        mod_freq_write },\n  { \"mod_length\",       V291,   mod_length_read,      NULL },\n  { \"mod_loopend\",      V292,   mod_loopend_read,     mod_loopend_write },\n  { \"mod_loopstart\",    V292,   mod_loopstart_read,   mod_loopstart_write },\n  { \"mod_order\",        V262,   mod_order_read,       mod_order_write },\n  { \"mod_position\",     V281,   mod_position_read,    mod_position_write },\n  { \"mousepx\",          V282,   mousepx_read,         mousepx_write },\n  { \"mousepy\",          V282,   mousepy_read,         mousepy_write },\n  { \"mousex\",           V251s1, mousex_read,          mousex_write },\n  { \"mousey\",           V251s1, mousey_read,          mousey_write },\n  { \"multiplier\",       V268,   multiplier_read,      multiplier_write },\n  { \"mzx_speed\",        V260,   mzx_speed_read,       mzx_speed_write },\n  { \"och!,!\",           V284,   och_read,             NULL },\n  { \"oco!,!\",           V284,   oco_read,             NULL },\n  { \"overlay_char\",     V260,   overlay_char_read,    NULL },\n  { \"overlay_color\",    V260,   overlay_color_read,   NULL },\n  { \"overlay_mode\",     V260,   overlay_mode_read,    NULL },\n  { \"pixel\",            V260,   pixel_read,           pixel_write },\n  { \"playerdist\",       V100,   playerdist_read,      NULL },\n  { \"playerfacedir\",    V200,   playerfacedir_read,   playerfacedir_write },\n  { \"playerlastdir\",    V200,   playerlastdir_read,   playerlastdir_write },\n  { \"playerx\",          V251s1, playerx_read,         NULL },\n  { \"playery\",          V251s1, playery_read,         NULL },\n  { \"play_game\",        V290,   NULL,                 play_game_write },\n  { \"r!.*\",             V265,   r_read,               r_write },\n  { \"random_seed!\",     V291,   random_seed_read,     random_seed_write },\n  { \"red_value\",        V260,   red_value_read,       red_value_write },\n  { \"rid*\",             V269b,  rid_read,             NULL },\n  { \"robot_id\",         V260,   robot_id_read,        NULL },\n  { \"robot_id_*\",       V265,   robot_id_n_read,      NULL },\n  { \"save_bc?\",         V270,   save_bc_read,         NULL },\n  { \"save_counters\",    V290,   save_counters_read,   NULL },\n  { \"save_game\",        V268,   save_game_read,       NULL },\n  { \"save_robot?\",      V270,   save_robot_read,      NULL },\n  { \"scrolledx\",        V251s1, scrolledx_read,       NULL },\n  { \"scrolledy\",        V251s1, scrolledy_read,       NULL },\n  { \"sin!\",             V268,   sin_read,             NULL },\n  { \"smzx_b!\",          V269,   smzx_b_read,          smzx_b_write },\n  { \"smzx_g!\",          V269,   smzx_g_read,          smzx_g_write },\n  { \"smzx_idx!,!\",      V290,   smzx_idx_read,        smzx_idx_write },\n  { \"smzx_indices\",     V291,   smzx_indices_read,    NULL },\n  { \"smzx_message\",     V291,   smzx_message_read,    smzx_message_write },\n  { \"smzx_mode\",        V269,   smzx_mode_read,       smzx_mode_write },\n  { \"smzx_palette\",     V269,   smzx_palette_read,    NULL },\n  { \"smzx_r!\",          V269,   smzx_r_read,          smzx_r_write },\n  { \"spacelock\",        V284,   NULL,                 spacelock_write },\n  { \"spr!_ccheck\",      V265,   NULL,                 spr_ccheck_write },\n  { \"spr!_cheight\",     V265,   spr_cheight_read,     spr_cheight_write },\n  { \"spr!_clist\",       V265,   NULL,                 spr_clist_write },\n  { \"spr!_cwidth\",      V265,   spr_cwidth_read,      spr_cwidth_write },\n  { \"spr!_cx\",          V265,   spr_cx_read,          spr_cx_write },\n  { \"spr!_cy\",          V265,   spr_cy_read,          spr_cy_write },\n  { \"spr!_height\",      V265,   spr_height_read,      spr_height_write },\n  { \"spr!_off\",         V265,   spr_off_read,         spr_off_write },\n  { \"spr!_offonexit\",   V293,   spr_offonexit_read,   spr_offonexit_write },\n  { \"spr!_offset\",      V290,   spr_offset_read,      spr_offset_write },\n  { \"spr!_overlaid\",    V265,   NULL,                 spr_overlaid_write },\n  { \"spr!_overlay\",     V269c,  NULL,                 spr_overlaid_write },\n  { \"spr!_refx\",        V265,   spr_refx_read,        spr_refx_write },\n  { \"spr!_refy\",        V265,   spr_refy_read,        spr_refy_write },\n  { \"spr!_setview\",     V265,   NULL,                 spr_setview_write },\n  { \"spr!_static\",      V265,   NULL,                 spr_static_write },\n  { \"spr!_swap\",        V265,   NULL,                 spr_swap_write },\n  { \"spr!_tcol\",        V290,   spr_tcol_read,        spr_tcol_write },\n  { \"spr!_unbound\",     V290,   spr_unbound_read,     spr_unbound_write },\n  { \"spr!_vlayer\",      V269c,  NULL,                 spr_vlayer_write },\n  { \"spr!_width\",       V265,   spr_width_read,       spr_width_write },\n  { \"spr!_x\",           V265,   spr_x_read,           spr_x_write },\n  { \"spr!_y\",           V265,   spr_y_read,           spr_y_write },\n  { \"spr!_z\",           V292,   spr_z_read,           spr_z_write },\n  { \"spr_clist!\",       V265,   spr_clist_read,       NULL },\n  { \"spr_collisions\",   V265,   spr_collisions_read,  NULL },\n  { \"spr_num\",          V265,   spr_num_read,         spr_num_write },\n  { \"spr_yorder\",       V265,   NULL,                 spr_yorder_write },\n  { \"sqrt!\",            V268,   sqrt_read,            NULL },\n  { \"tan!\",             V268,   tan_read,             NULL },\n  { \"thisx\",            V200,   thisx_read,           NULL },\n  { \"thisy\",            V200,   thisy_read,           NULL },\n  { \"this_char\",        V260,   this_char_read,       NULL },\n  { \"this_color\",       V260,   this_color_read,      NULL },\n  { \"timereset\",        V200,   timereset_read,       timereset_write },\n  { \"time_hours\",       V260,   time_hours_read,      NULL },\n  { \"time_millis\",      V292,   time_millis_read,     NULL },\n  { \"time_minutes\",     V260,   time_minutes_read,    NULL },\n  { \"time_seconds\",     V260,   time_seconds_read,    NULL },\n  { \"uch!,!\",           V284,   uch_read,             NULL },\n  { \"uco!,!\",           V284,   uco_read,             NULL },\n  { \"uid!,!\",           V284,   uid_read,             uid_write },\n  { \"upr!,!\",           V284,   upr_read,             upr_write },\n  { \"vch!,!\",           V269c,  vch_read,             vch_write },\n  { \"vco!,!\",           V269c,  vco_read,             vco_write },\n  { \"vertpld\",          V100,   vertpld_read,         NULL },\n  { \"viewport_height\",  V293,   viewport_height_read, NULL },\n  { \"viewport_width\",   V293,   viewport_width_read,  NULL },\n  { \"viewport_x\",       V293,   viewport_x_read,      NULL },\n  { \"viewport_y\",       V293,   viewport_y_read,      NULL },\n  { \"vlayer_height\",    V269c,  vlayer_height_read,   vlayer_height_write },\n  { \"vlayer_size\",      V281,   vlayer_size_read,     vlayer_size_write },\n  { \"vlayer_width\",     V269c,  vlayer_width_read,    vlayer_width_write },\n};\n\nstatic const int num_builtin_counters = ARRAY_SIZE(builtin_counters);\nstatic int counter_first_letter[512];\n\nvoid counter_fsg(void)\n{\n  char cur_char = (builtin_counters[0]).name[0];\n  char old_char;\n  int i, i2;\n\n  for(i = 0, i2 = 0; i < 256; i++)\n  {\n    if(i != cur_char)\n    {\n      counter_first_letter[i * 2] = -1;\n      counter_first_letter[(i * 2) + 1] = -1;\n    }\n    else\n    {\n      counter_first_letter[i * 2] = i2;\n      old_char = cur_char;\n\n      while(cur_char == old_char)\n      {\n        i2++;\n        if(i2 == num_builtin_counters)\n          break;\n        cur_char = builtin_counters[i2].name[0];\n      }\n\n      counter_first_letter[(i * 2) + 1] = i2 - 1;\n    }\n  }\n}\n\nint match_function_counter(const char *dest, const char *src)\n{\n  int difference;\n  char cur_src, cur_dest;\n\n  while(1)\n  {\n    cur_src = *src;\n    cur_dest = *dest;\n\n    // Skip 1 or more letters\n    switch(cur_src)\n    {\n      // Make sure the first character is a number\n      case '!':\n      {\n        if(((cur_dest < '0') || (cur_dest > '9')) &&\n         (cur_dest != '-'))\n        {\n          // Don't want to accidentally match this char...\n          if(cur_dest == '!')\n            return 1;\n\n          break;\n        }\n\n        dest++;\n        cur_dest = *dest;\n\n        // Fall through, skip remaining numbers\n      }\n\n      /* fallthrough */\n\n      // Skip 0 or more number characters\n      case '?':\n      {\n        src++;\n        cur_src = *src;\n\n        while(((cur_dest >= '0') && (cur_dest <= '9')) ||\n         (cur_dest == '-'))\n        {\n          dest++;\n          cur_dest = *dest;\n        }\n        break;\n      }\n\n      // Match anything, instant winner\n      case '*':\n      {\n        return 0;\n      }\n    }\n\n    if((cur_src | cur_dest) == 0)\n    {\n      // Both hit the null terminator\n      return 0;\n    }\n\n    difference = (int)((cur_dest & 0xDF) - (cur_src & 0xDF));\n\n    if(difference)\n      return difference;\n\n    src++;\n    dest++;\n  }\n\n  return 0;\n}\n\nstatic const struct function_counter *find_function_counter(const char *name)\n{\n  const struct function_counter *base = builtin_counters;\n  int first_letter = memtolower((unsigned char)name[0]) * 2;\n  int bottom, top, middle;\n  int cmpval;\n\n  bottom = counter_first_letter[first_letter];\n  top = counter_first_letter[first_letter + 1];\n\n  if(bottom != -1)\n  {\n    while(bottom <= top)\n    {\n      middle = (top + bottom) / 2;\n      cmpval = match_function_counter(name + 1, (base[middle]).name + 1);\n\n      if(cmpval > 0)\n        bottom = middle + 1;\n      else\n\n      if(cmpval < 0)\n        top = middle - 1;\n      else\n        return base + middle;\n    }\n  }\n\n  return NULL;\n}\n\nstatic struct robot *get_robot_by_id(struct world *mzx_world, int id)\n{\n  if(id >= 0 && id <= mzx_world->current_board->num_robots)\n    return mzx_world->current_board->robot_list[id];\n  else\n    return NULL;\n}\n\nstatic void fread_close(struct world *mzx_world)\n{\n  if(!mzx_world->input_is_dir && mzx_world->input_file)\n    vfclose(mzx_world->input_file);\n\n  if(mzx_world->input_is_dir && mzx_world->input_directory)\n    vdir_close(mzx_world->input_directory);\n\n  mzx_world->input_file_name[0] = '\\0';\n  mzx_world->input_file = NULL;\n  mzx_world->input_directory = NULL;\n  mzx_world->input_is_dir = false;\n}\n\nstatic void fwrite_close(struct world *mzx_world)\n{\n  if(mzx_world->output_file)\n    vfclose(mzx_world->output_file);\n\n  mzx_world->output_file_name[0] = '\\0';\n  mzx_world->output_file = NULL;\n  mzx_world->output_mode = FWRITE_MODE_UNKNOWN;\n}\n\nint set_counter_special(struct world *mzx_world, char *char_value,\n int value, int id)\n{\n  struct robot *cur_robot = get_robot_by_id(mzx_world, id);\n\n  if(strlen(char_value) >= MAX_PATH)\n    return 0; // haha nope\n\n  switch(mzx_world->special_counter_return)\n  {\n    case FOPEN_FREAD:\n    {\n      fread_close(mzx_world);\n\n      if(char_value[0])\n      {\n        char *translated_path = (char *)cmalloc(MAX_PATH);\n        int err;\n\n        if(!translated_path)\n          return 0;\n\n        err = fsafetranslate(char_value, translated_path, MAX_PATH);\n\n        if(err == -FSAFE_MATCHED_DIRECTORY)\n        {\n          mzx_world->input_directory = vdir_open(translated_path);\n          if(mzx_world->input_directory)\n            mzx_world->input_is_dir = true;\n        }\n        else\n\n        if(err == -FSAFE_SUCCESS)\n          mzx_world->input_file = vfopen_unsafe(translated_path, \"rb\");\n\n        if(mzx_world->input_file || mzx_world->input_is_dir)\n          strcpy(mzx_world->input_file_name, translated_path);\n\n        free(translated_path);\n      }\n      break;\n    }\n\n    case FOPEN_FWRITE:\n    {\n      fwrite_close(mzx_world);\n\n      if(char_value[0])\n      {\n        mzx_world->output_file = fsafeopen(char_value, \"wb\");\n        if(mzx_world->output_file)\n        {\n          strcpy(mzx_world->output_file_name, char_value);\n          mzx_world->output_mode = FWRITE_MODE_TRUNCATE;\n        }\n      }\n      break;\n    }\n\n    case FOPEN_FAPPEND:\n    {\n      fwrite_close(mzx_world);\n\n      if(char_value[0])\n      {\n        mzx_world->output_file = fsafeopen(char_value, \"ab\");\n        if(mzx_world->output_file)\n        {\n          strcpy(mzx_world->output_file_name, char_value);\n          mzx_world->output_mode = FWRITE_MODE_APPEND;\n        }\n      }\n      break;\n    }\n\n    case FOPEN_FMODIFY:\n    {\n      fwrite_close(mzx_world);\n\n      if(char_value[0])\n      {\n        mzx_world->output_file = fsafeopen(char_value, \"r+b\");\n        if(mzx_world->output_file)\n        {\n          strcpy(mzx_world->output_file_name, char_value);\n          mzx_world->output_mode = FWRITE_MODE_MODIFY;\n        }\n      }\n      break;\n    }\n\n    case FOPEN_SMZX_PALETTE:\n    {\n      char *translated_path = cmalloc(MAX_PATH);\n      int err;\n\n      err = fsafetranslate(char_value, translated_path, MAX_PATH);\n\n      if(err == -FSAFE_SUCCESS)\n        load_palette(translated_path);\n\n      free(translated_path);\n      break;\n    }\n\n    case FOPEN_SMZX_INDICES:\n    {\n      char *translated_path = cmalloc(MAX_PATH);\n      int err;\n\n      err = fsafetranslate(char_value, translated_path, MAX_PATH);\n\n      if(err == -FSAFE_SUCCESS)\n        load_index_file(translated_path);\n\n      free(translated_path);\n      break;\n    }\n\n    case FOPEN_SAVE_COUNTERS:\n    {\n      char *translated_path = cmalloc(MAX_PATH);\n      int err;\n\n      err = fsafetranslate(char_value, translated_path, MAX_PATH);\n      if(err == -FSAFE_SUCCESS || err == -FSAFE_MATCH_FAILED)\n        save_counters_file(mzx_world, translated_path);\n\n      free(translated_path);\n      break;\n    }\n\n    case FOPEN_LOAD_COUNTERS:\n    {\n      char *translated_path = cmalloc(MAX_PATH);\n      int err;\n\n      err = fsafetranslate(char_value, translated_path, MAX_PATH);\n      if(err == -FSAFE_SUCCESS)\n        load_counters_file(mzx_world, translated_path);\n\n      free(translated_path);\n      break;\n    }\n\n    case FOPEN_SAVE_GAME:\n    {\n      char *translated_path;\n      int err;\n\n      if(mzx_world->version < V290)\n      {\n        // Prior to 2.90, save_game took place immediately\n        translated_path = cmalloc(MAX_PATH);\n        if(cur_robot)\n        {\n          // Advance the program so that loading a SAV doesn't re-run this line\n          cur_robot->cur_prog_line +=\n          cur_robot->program_bytecode[cur_robot->cur_prog_line] + 2;\n        }\n\n        err = fsafetranslate(char_value, translated_path, MAX_PATH);\n        if(err == -FSAFE_SUCCESS || err == -FSAFE_MATCH_FAILED)\n          save_world(mzx_world, translated_path, true, MZX_VERSION);\n\n        free(translated_path);\n      }\n      else\n      {\n        // From 2.90 onward, save_game takes place at the end\n        // of the cycle\n        mzx_world->robotic_save_type = SAVE_NONE;\n        err = fsafetranslate(char_value, mzx_world->robotic_save_path, MAX_PATH);\n        if(err == -FSAFE_SUCCESS || err == -FSAFE_MATCH_FAILED)\n          mzx_world->robotic_save_type = SAVE_GAME;\n      }\n      break;\n    }\n\n    case FOPEN_LOAD_GAME:\n    {\n      char *translated_path = cmalloc(MAX_PATH);\n      boolean faded;\n\n      if(!fsafetranslate(char_value, translated_path, MAX_PATH))\n      {\n        if(reload_savegame(mzx_world, translated_path, &faded))\n        {\n          if(faded)\n            insta_fadeout();\n          else\n            insta_fadein();\n\n          // Let the main loop know we're loading a save from Robotic\n          mzx_world->change_game_state = CHANGE_STATE_LOAD_GAME_ROBOTIC;\n        }\n      }\n\n      free(translated_path);\n      break;\n    }\n\n#ifdef CONFIG_DEBYTECODE\n\n    case FOPEN_LOAD_ROBOT:\n    {\n      // Load legacy source code.\n\n      if(value >= 0)\n        cur_robot = get_robot_by_id(mzx_world, value);\n\n      if(cur_robot)\n      {\n        int new_length = 0;\n        char *new_source = NULL;\n\n        // Source world? Assume new source. Otherwise, assume old source.\n        // TODO issues caused by this will be resolved when these counters get\n        // translated into actual commands eventually.\n        if(mzx_world->version >= VERSION_SOURCE)\n        {\n          vfile *vf = fsafeopen(char_value, \"rb\");\n          if(vf)\n          {\n            int64_t vf_len = vfilelength(vf, true);\n            if(vf_len < 0 || vf_len > INT_MAX)\n            {\n              vfclose(vf);\n              break;\n            }\n\n            new_length = (int)vf_len;\n            new_source = cmalloc(new_length + 1);\n            if(!new_source)\n            {\n              vfclose(vf);\n              break;\n            }\n            new_source[new_length] = 0;\n\n            if(vfread(new_source, 1, new_length, vf) != (size_t)new_length)\n            {\n              free(new_source);\n              new_source = NULL;\n            }\n            vfclose(vf);\n          }\n        }\n        else\n          new_source = legacy_convert_file(char_value, &new_length,\n           SAVE_ROBOT_DISASM_EXTRAS, SAVE_ROBOT_DISASM_BASE);\n\n        if(new_source)\n        {\n          if(cur_robot->program_source)\n            free(cur_robot->program_source);\n\n          cur_robot->program_source = new_source;\n          cur_robot->program_source_length = new_length;\n\n          // TODO: Move this outside of here.\n          if(cur_robot->program_bytecode)\n          {\n            free(cur_robot->program_bytecode);\n            cur_robot->program_bytecode = NULL;\n          }\n\n          cur_robot->stack_pointer = 0;\n          cur_robot->cur_prog_line = 1;\n          prepare_robot_bytecode(mzx_world, cur_robot);\n\n          // Restart this robot if either it was just a LOAD_ROBOT\n          // OR LOAD_ROBOTn was used where n is &robot_id&.\n          if(value == -1 || value == id)\n            return 1;\n        }\n      }\n      break;\n    }\n\n    case FOPEN_LOAD_BC:\n    {\n      // Although this pseudo-command is still usable, it'll no longer be\n      // possible to actually export bytecode. So this means that the bytecode\n      // is always from a version that legacy_disassemble_program\n\n      // It's basically like LOAD_ROBOT except that it first has to disassmble\n      // the bytecode.\n\n      vfile *bc_file = fsafeopen(char_value, \"rb\");\n\n      if(bc_file)\n      {\n        if(value >= 0)\n          cur_robot = get_robot_by_id(mzx_world, value);\n\n        if(cur_robot)\n        {\n          int64_t bc_len = vfilelength(bc_file, true);\n          char *program_legacy_bytecode;\n          int program_bytecode_length;\n\n          if(bc_len < 0 || bc_len > MAX_OBJ_SIZE)\n            break;\n\n          program_bytecode_length = (int)bc_len;\n          program_legacy_bytecode = cmalloc(program_bytecode_length + 1);\n          if(!program_legacy_bytecode)\n            break;\n\n          if(vfread(program_legacy_bytecode, 1, program_bytecode_length,\n           bc_file) != (size_t)program_bytecode_length)\n          {\n            free(program_legacy_bytecode);\n            break;\n          }\n\n          if(!validate_legacy_bytecode(&program_legacy_bytecode,\n           &program_bytecode_length, NULL))\n          {\n            error_message(E_LOAD_BC_CORRUPT, 0, char_value);\n            free(program_legacy_bytecode);\n            break;\n          }\n\n          cur_robot->program_source =\n           legacy_disassemble_program(program_legacy_bytecode,\n           program_bytecode_length, &(cur_robot->program_source_length),\n           SAVE_ROBOT_DISASM_EXTRAS, SAVE_ROBOT_DISASM_BASE);\n          free(program_legacy_bytecode);\n\n          // TODO: Move this outside of here.\n          if(cur_robot->program_bytecode)\n          {\n            free(cur_robot->program_bytecode);\n            cur_robot->program_bytecode = NULL;\n          }\n\n          cur_robot->cur_prog_line = 1;\n          cur_robot->stack_pointer = 0;\n          prepare_robot_bytecode(mzx_world, cur_robot);\n\n          // Restart this robot if either it was just a LOAD_BC\n          // OR LOAD_BCn was used where n is &robot_id&.\n          if(value == -1 || value == id)\n          {\n            vfclose(bc_file);\n            return 1;\n          }\n        }\n\n        vfclose(bc_file);\n      }\n      break;\n    }\n\n    case FOPEN_SAVE_ROBOT:\n    {\n      // FIXME old worlds will save new source. Fixing this would ideally\n      // involve allowing new source, old source, or old bytecode to all be\n      // considered the current program \"source\", but doing this in a clean\n      // way probably relies on separating programs from robots internally.\n\n      if(value >= 0)\n        cur_robot = get_robot_by_id(mzx_world, value);\n\n      if(cur_robot && cur_robot->program_source)\n      {\n        vfile *vf = fsafeopen(char_value, \"wb\");\n        size_t len = cur_robot->program_source_length;\n\n        if(vf)\n        {\n          // TODO: this doesn't apply zaps...\n          vfwrite(cur_robot->program_source, len, 1, vf);\n          vfclose(vf);\n        }\n      }\n      break;\n    }\n\n    /* This can't really exist anymore... In practice I doubt any games\n     * use it, but they can't  be supported - there's no really easy fix\n     * for this.\n     */\n    case FOPEN_SAVE_BC:\n    {\n      error_message(E_DBC_SAVE_ROBOT_UNSUPPORTED, 0, NULL);\n      set_error_suppression(E_DBC_SAVE_ROBOT_UNSUPPORTED, 1);\n      break;\n    }\n\n#else // !CONFIG_DEBYTECODE\n\n    case FOPEN_LOAD_ROBOT:\n    {\n      char *new_program;\n      int new_size;\n\n      new_program = assemble_file(char_value, &new_size);\n      if(new_program)\n      {\n        if(value >= 0)\n          cur_robot = get_robot_by_id(mzx_world, value);\n\n        if(cur_robot)\n        {\n          reallocate_robot(cur_robot, new_size);\n          clear_label_cache(cur_robot);\n\n          memcpy(cur_robot->program_bytecode, new_program, new_size);\n          cur_robot->stack_pointer = 0;\n          cur_robot->cur_prog_line = 1;\n          cache_robot_labels(cur_robot);\n\n          // Free the robot's source and command map\n          free(cur_robot->program_source);\n          cur_robot->program_source = NULL;\n          cur_robot->program_source_length = 0;\n#ifdef CONFIG_EDITOR\n          free(cur_robot->command_map);\n          cur_robot->command_map_length = 0;\n          cur_robot->command_map = NULL;\n#endif\n\n          // Restart this robot if either it was just a LOAD_ROBOT\n          // OR LOAD_ROBOTn was used where n is &robot_id&.\n          if(value == -1 || value == id)\n          {\n            free(new_program);\n            return 1;\n          }\n        }\n\n        free(new_program);\n      }\n      break;\n    }\n\n    case FOPEN_LOAD_BC:\n    {\n      vfile *bc_file = fsafeopen(char_value, \"rb\");\n\n      if(bc_file)\n      {\n        if(value >= 0)\n          cur_robot = get_robot_by_id(mzx_world, value);\n\n        if(cur_robot)\n        {\n          int64_t bc_len = vfilelength(bc_file, true);\n          char *program_bytecode;\n          int new_size;\n\n          if(bc_len < 0 || bc_len > MAX_OBJ_SIZE)\n            break;\n\n          new_size = (int)bc_len;\n          program_bytecode = cmalloc(new_size + 1);\n          if(!program_bytecode)\n            break;\n\n          if(vfread(program_bytecode, 1, new_size, bc_file) != (size_t)new_size)\n          {\n            free(program_bytecode);\n            break;\n          }\n\n          if(!validate_legacy_bytecode(&program_bytecode, &new_size, NULL))\n          {\n            error_message(E_LOAD_BC_CORRUPT, 0, char_value);\n            free(program_bytecode);\n            break;\n          }\n\n          clear_label_cache(cur_robot);\n          free(cur_robot->program_bytecode);\n          cur_robot->program_bytecode = program_bytecode;\n          cur_robot->program_bytecode_length = new_size;\n          cur_robot->cur_prog_line = 1;\n          cur_robot->stack_pointer = 0;\n          cache_robot_labels(cur_robot);\n\n          // Free the robot's source and command map\n          free(cur_robot->program_source);\n          cur_robot->program_source = NULL;\n          cur_robot->program_source_length = 0;\n#ifdef CONFIG_EDITOR\n          free(cur_robot->command_map);\n          cur_robot->command_map_length = 0;\n          cur_robot->command_map = NULL;\n#endif\n\n          // Restart this robot if either it was just a LOAD_BC\n          // OR LOAD_BCn was used where n is &robot_id&.\n          if(value == -1 || value == id)\n          {\n            vfclose(bc_file);\n            return 1;\n          }\n        }\n\n        vfclose(bc_file);\n      }\n      break;\n    }\n\n    case FOPEN_SAVE_ROBOT:\n    {\n      if(value >= 0)\n        cur_robot = get_robot_by_id(mzx_world, value);\n\n      if(cur_robot)\n      {\n        disassemble_file(char_value, cur_robot->program_bytecode,\n         cur_robot->program_bytecode_length, SAVE_ROBOT_DISASM_EXTRAS,\n         SAVE_ROBOT_DISASM_BASE);\n      }\n      break;\n    }\n\n    case FOPEN_SAVE_BC:\n    {\n      vfile *bc_file = fsafeopen(char_value, \"wb\");\n\n      if(bc_file)\n      {\n        if(value >= 0)\n          cur_robot = get_robot_by_id(mzx_world, value);\n\n        if(cur_robot)\n        {\n          vfwrite(cur_robot->program_bytecode,\n           cur_robot->program_bytecode_length, 1, bc_file);\n        }\n\n        vfclose(bc_file);\n      }\n      break;\n    }\n\n#endif // !CONFIG_DEBYTECODE\n\n    default:\n      debug(\"Invalid special_counter_return!\\n\");\n      break;\n  }\n\n  return 0;\n}\n\nstatic struct counter *find_counter(struct counter_list *counter_list,\n const char *name, int *next)\n{\n  struct counter *current = NULL;\n\n#ifdef CONFIG_COUNTER_HASH_TABLES\n  size_t name_length = strlen(name);\n  HASH_FIND(COUNTER, counter_list->hash_table, name, name_length, current);\n  *next = counter_list->num_counters;\n  return current;\n\n#else\n  struct counter **base = counter_list->counters;\n  int bottom = 0, top = (counter_list->num_counters) - 1, middle = 0;\n  int cmpval = 0;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    current = base[middle];\n    cmpval = strcasecmp(name, current->name);\n\n    if(cmpval > 0)\n      bottom = middle + 1;\n    else\n\n    if(cmpval < 0)\n      top = middle - 1;\n    else\n      return current;\n  }\n\n  if(cmpval > 0)\n    *next = middle + 1;\n  else\n    *next = middle;\n\n  return NULL;\n#endif\n}\n\nstatic int hurt_player(struct world *mzx_world, int value)\n{\n  // Must not be invincible\n  if(get_counter(mzx_world, \"INVINCO\", 0) <= 0)\n  {\n    struct board *src_board = mzx_world->current_board;\n    send_robot_def(mzx_world, 0, LABEL_PLAYERHURT);\n    if(src_board->restart_if_zapped)\n    {\n      int player_restart_x = mzx_world->player_restart_x;\n      int player_restart_y = mzx_world->player_restart_y;\n      int player_x = mzx_world->player_x;\n      int player_y = mzx_world->player_y;\n      //Restart since we were hurt\n      if((player_restart_x != player_x) || (player_restart_y != player_y))\n      {\n        id_remove_top(mzx_world, player_x, player_y);\n        player_x = player_restart_x;\n        player_y = player_restart_y;\n        id_place(mzx_world, player_x, player_y, PLAYER, 0, 0);\n        mzx_world->was_zapped = true;\n        mzx_world->player_x = player_x;\n        mzx_world->player_y = player_y;\n      }\n    }\n  }\n  else\n  {\n    value = 0;\n  }\n  return value;\n}\n\ntypedef int (*gateway_write_function)(struct world *mzx_world,\n struct counter *counter, const char *name, int value, int id, boolean is_dec);\n\nstatic int health_gateway(struct world *mzx_world, struct counter *counter,\n const char *name, int value, int id, boolean is_dec)\n{\n  // Trying to decrease health? (legacy support)\n  // In pre-port MZX worlds, only the dec command triggers PLAYERHURT/zap.\n  // Additionally, any decrement of >0 triggers this, so do it before the clamp.\n  if((value < counter->value) && (mzx_world->version < VERSION_PORT) && is_dec)\n  {\n    value = counter->value - hurt_player(mzx_world, counter->value - value);\n  }\n\n  // Make sure health is within 0 and max health\n  if(value > mzx_world->health_limit)\n  {\n    value = mzx_world->health_limit;\n  }\n  if(value < 0)\n  {\n    value = 0;\n  }\n\n  // Trying to decrease health?\n  // In port MZX worlds, any method of reducing health triggers PLAYERHURT/zap.\n  // This only happens if the clamped value is less than the old value.\n  if((value < counter->value) && (mzx_world->version >= VERSION_PORT))\n  {\n    value = counter->value - hurt_player(mzx_world, counter->value - value);\n  }\n\n  return value;\n}\n\nstatic int lives_gateway(struct world *mzx_world, struct counter *counter,\n const char *name, int value, int id, boolean is_dec)\n{\n  // Make sure lives is within 0 and max lives\n  if(value > mzx_world->lives_limit)\n  {\n    value = mzx_world->lives_limit;\n  }\n  if(value < 0)\n  {\n    value = 0;\n  }\n\n  return value;\n}\n\nstatic int invinco_gateway(struct world *mzx_world, struct counter *counter,\n const char *name, int value, int id, boolean is_dec)\n{\n  if(!counter->value)\n  {\n    mzx_world->saved_pl_color = id_chars[player_color];\n  }\n  else\n  {\n    if(!value)\n    {\n      sfx_clear_queue();\n      play_sfx(mzx_world, SFX_INVINCO_END);\n      id_chars[player_color] = mzx_world->saved_pl_color;\n    }\n  }\n\n  return value;\n}\n\nstatic int score_gateway(struct world *mzx_world, struct counter *counter,\n const char *name, int value, int id, boolean is_dec)\n{\n  // Protection for score < 0, as per the behavior in DOS MZX.\n  if((value < 0) && (mzx_world->version < VERSION_PORT))\n    return 0;\n\n  return value;\n}\n\nstatic int time_gateway(struct world *mzx_world, struct counter *counter,\n const char *name, int value, int id, boolean is_dec)\n{\n  return CLAMP(value, 0, 32767);\n}\n\nstatic int builtin_gateway(struct world *mzx_world, struct counter *counter,\n const char *name, int value, int id, boolean is_dec)\n{\n  // The other builtins simply can't go below 0\n  return MAX(value, 0);\n}\n\nenum gateway_function_id\n{\n  NO_GATEWAY,\n  GATEWAY_BUILTIN,\n  GATEWAY_HEALTH,\n  GATEWAY_INVINCO,\n  GATEWAY_LIVES,\n  GATEWAY_SCORE,\n  GATEWAY_TIME,\n  NUM_GATEWAYS\n};\n\nstatic gateway_write_function gateways[NUM_GATEWAYS] =\n{\n  [NO_GATEWAY]      = NULL,\n  [GATEWAY_BUILTIN] = builtin_gateway,\n  [GATEWAY_HEALTH]  = health_gateway,\n  [GATEWAY_INVINCO] = invinco_gateway,\n  [GATEWAY_LIVES]   = lives_gateway,\n  [GATEWAY_SCORE]   = score_gateway,\n  [GATEWAY_TIME]    = time_gateway\n};\n\n// Setup builtin gateway functions\nstatic void set_gateway(struct counter_list *counter_list,\n const char *name, enum gateway_function_id gateway_id)\n{\n  int next;\n  struct counter *cdest = find_counter(counter_list, name, &next);\n  if(cdest)\n    cdest->gateway_write = gateway_id;\n}\n\nvoid initialize_gateway_functions(struct world *mzx_world)\n{\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  set_gateway(counter_list, \"AMMO\", GATEWAY_BUILTIN);\n  set_gateway(counter_list, \"COINS\", GATEWAY_BUILTIN);\n  set_gateway(counter_list, \"GEMS\", GATEWAY_BUILTIN);\n  set_gateway(counter_list, \"HEALTH\", GATEWAY_HEALTH);\n  set_gateway(counter_list, \"HIBOMBS\", GATEWAY_BUILTIN);\n  set_gateway(counter_list, \"INVINCO\", GATEWAY_INVINCO);\n  set_gateway(counter_list, \"LIVES\", GATEWAY_LIVES);\n  set_gateway(counter_list, \"LOBOMBS\", GATEWAY_BUILTIN);\n  set_gateway(counter_list, \"SCORE\", GATEWAY_SCORE);\n  set_gateway(counter_list, \"TIME\", GATEWAY_TIME);\n}\n\nstatic size_t get_counter_alloc_size(int name_length)\n{\n  // Attempt to reclaim any padding bytes at the end of the struct...\n  return MAX(sizeof(struct counter),\n   offsetof(struct counter, name) + name_length + 1);\n}\n\nstatic struct counter *allocate_new_counter(const char *name, size_t name_length,\n int value)\n{\n  struct counter *dest = (struct counter *)cmalloc(get_counter_alloc_size(name_length));\n  if(!dest)\n    return NULL;\n\n  memcpy(dest->name, name, name_length);\n  dest->name[name_length] = 0;\n\n  dest->gateway_write = NO_GATEWAY;\n  dest->name_length = name_length;\n  dest->value = value;\n  return dest;\n}\n\nstatic void add_counter(struct counter_list *counter_list, const char *name,\n int value, unsigned int position)\n{\n  unsigned int count = counter_list->num_counters;\n  unsigned int allocated = counter_list->num_counters_allocated;\n  struct counter **base = counter_list->counters;\n  struct counter *dest;\n  unsigned int name_length = strlen(name);\n\n  // Need a reallocation?\n  if(count == allocated)\n  {\n    if(allocated)\n    {\n      // Gracefully fail if this tries to go over 2b...\n      if(allocated >= (size_t)(INT32_MAX))\n        return;\n\n      allocated *= 2;\n    }\n    else\n      allocated = MIN_COUNTER_ALLOCATE;\n\n    base = (struct counter **)crealloc(base, sizeof(struct counter *) * allocated);\n    if(!base)\n      return;\n\n    counter_list->counters = base;\n    counter_list->num_counters_allocated = allocated;\n  }\n\n  // Doesn't exist, so create it\n  // First make space for it if it's not at the top\n  if(position != count)\n  {\n    base += position;\n    memmove((char *)(base + 1), (char *)base,\n     (count - position) * sizeof(struct counter *));\n  }\n\n  dest = allocate_new_counter(name, name_length, value);\n  if(!dest)\n    return;\n\n  counter_list->counters[position] = dest;\n  counter_list->num_counters = count + 1;\n\n#ifdef CONFIG_COUNTER_HASH_TABLES\n  HASH_ADD(COUNTER, counter_list->hash_table, dest);\n#endif\n}\n\nvoid set_counter(struct world *mzx_world, const char *name, int value, int id)\n{\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  const struct function_counter *fdest;\n  struct counter *cdest;\n  int next = 0;\n\n  fdest = find_function_counter(name);\n\n  if(fdest && (mzx_world->version >= fdest->minimum_version))\n  {\n    // If we're a function counter and we have a write method,\n    // use it. However, if we don't have a write method, this is\n    // intentionally read-only counter and no storage space should\n    // be allocated for it. Fall through without error in this case.\n    if(fdest->function_write)\n      fdest->function_write(mzx_world, fdest, name, value, id);\n    else\n      assert(fdest->function_read);\n  }\n  else\n  {\n    cdest = find_counter(counter_list, name, &next);\n\n    if(cdest)\n    {\n      // See if there's a gateway\n      if(cdest->gateway_write && cdest->gateway_write < NUM_GATEWAYS)\n      {\n        gateway_write_function gateway_write = gateways[cdest->gateway_write];\n        value = gateway_write(mzx_world, cdest, name, value, id, false);\n      }\n\n      cdest->value = value;\n    }\n    else\n    {\n      add_counter(counter_list, name, value, next);\n    }\n  }\n}\n\n// Creates a new counter if it doesn't already exist; otherwise, sets the\n// old counter's value. Basically, set_counter without the function check.\nvoid new_counter(struct world *mzx_world, const char *name, int value, int id)\n{\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  struct counter *cdest;\n  int next;\n\n  cdest = find_counter(counter_list, name, &next);\n\n  if(cdest)\n  {\n    // See if there's a gateway\n    if(cdest->gateway_write && cdest->gateway_write < NUM_GATEWAYS)\n    {\n      gateway_write_function gateway_write = gateways[cdest->gateway_write];\n      value = gateway_write(mzx_world, cdest, name, value, id, false);\n    }\n\n    cdest->value = value;\n  }\n\n  else\n  {\n    add_counter(counter_list, name, value, next);\n  }\n}\n\nint get_counter(struct world *mzx_world, const char *name, int id)\n{\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  const struct function_counter *fdest;\n  struct counter *cdest;\n  int next;\n\n  fdest = find_function_counter(name);\n\n  if(fdest && fdest->function_read &&\n   (mzx_world->version >= fdest->minimum_version))\n  {\n    // Call read function\n    if(fdest->function_read)\n      return fdest->function_read(mzx_world, fdest, name, id);\n    else\n      return 0;\n  }\n\n  cdest = find_counter(counter_list, name, &next);\n\n  if(cdest)\n    return cdest->value;\n\n  return 0;\n}\n\n/**\n * Get a counter by name and return a pointer to it. This function does not\n * work with function counters or other special counters; use get_string or\n * get_string_safe (editor) instead. The pointer this function returns is\n * guaranteed to be stable for the duration of the gameplay session.\n *\n * @param mzx_world   World data.\n * @param name        Name of counter to look up.\n * @param id          Current robot ID or 0 for global.\n * @return            counter pointer if found, otherwise NULL.\n */\nconst struct counter *get_counter_pointer(struct world *mzx_world,\n const char *name, int id)\n{\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  int next;\n\n  return find_counter(counter_list, name, &next);\n}\n\nvoid inc_counter(struct world *mzx_world, const char *name, int value, int id)\n{\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  const struct function_counter *fdest;\n  struct counter *cdest;\n  int current_value;\n  int next = 0;\n\n  fdest = find_function_counter(name);\n\n  if(fdest && fdest->function_read && fdest->function_write &&\n   (mzx_world->version >= fdest->minimum_version))\n  {\n    current_value =\n     fdest->function_read(mzx_world, fdest, name, id);\n    fdest->function_write(mzx_world, fdest, name,\n     current_value + value, id);\n  }\n  else\n  {\n    cdest = find_counter(counter_list, name, &next);\n\n    if(cdest)\n    {\n      value += cdest->value;\n\n      if(cdest->gateway_write && cdest->gateway_write < NUM_GATEWAYS)\n      {\n        gateway_write_function gateway_write = gateways[cdest->gateway_write];\n        value = gateway_write(mzx_world, cdest, name, value, id, false);\n      }\n\n      cdest->value = value;\n    }\n    else\n    {\n      add_counter(counter_list, name, value, next);\n    }\n  }\n}\n\nvoid dec_counter(struct world *mzx_world, const char *name, int value, int id)\n{\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  const struct function_counter *fdest;\n  struct counter *cdest;\n  int current_value;\n  int next = 0;\n\n  fdest = find_function_counter(name);\n\n  if(fdest && fdest->function_read && fdest->function_write &&\n   (mzx_world->version >= fdest->minimum_version))\n  {\n    current_value =\n     fdest->function_read(mzx_world, fdest, name, id);\n    fdest->function_write(mzx_world, fdest, name,\n     current_value - value, id);\n  }\n  else\n  {\n    cdest = find_counter(counter_list, name, &next);\n\n    if(cdest)\n    {\n      value = cdest->value - value;\n\n      if(cdest->gateway_write && cdest->gateway_write < NUM_GATEWAYS)\n      {\n        gateway_write_function gateway_write = gateways[cdest->gateway_write];\n        value = gateway_write(mzx_world, cdest, name, value, id, true);\n      }\n\n      cdest->value = value;\n    }\n    else\n    {\n      add_counter(counter_list, name, -value, next);\n    }\n  }\n}\n\nvoid mul_counter(struct world *mzx_world, const char *name, int value, int id)\n{\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  const struct function_counter *fdest;\n  struct counter *cdest;\n  int current_value;\n  int next;\n\n  fdest = find_function_counter(name);\n\n  if(fdest && fdest->function_read && fdest->function_write &&\n   (mzx_world->version >= fdest->minimum_version))\n  {\n    current_value =\n     fdest->function_read(mzx_world, fdest, name, id);\n    fdest->function_write(mzx_world, fdest, name,\n     current_value * value, id);\n  }\n  else\n  {\n    cdest = find_counter(counter_list, name, &next);\n\n    if(cdest)\n    {\n      value *= cdest->value;\n      if(cdest->gateway_write && cdest->gateway_write < NUM_GATEWAYS)\n      {\n        gateway_write_function gateway_write = gateways[cdest->gateway_write];\n        value = gateway_write(mzx_world, cdest, name, value, id, false);\n      }\n\n      cdest->value = value;\n    }\n  }\n}\n\nvoid div_counter(struct world *mzx_world, const char *name, int value, int id)\n{\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  const struct function_counter *fdest;\n  struct counter *cdest;\n  int current_value;\n  int next;\n\n  if(value == 0)\n    return;\n\n  fdest = find_function_counter(name);\n\n  if(fdest && fdest->function_read && fdest->function_write &&\n   (mzx_world->version >= fdest->minimum_version))\n  {\n    current_value =\n     fdest->function_read(mzx_world, fdest, name, id);\n    fdest->function_write(mzx_world, fdest, name,\n     safe_divide_32(current_value, value), id);\n  }\n  else\n  {\n    cdest = find_counter(counter_list, name, &next);\n\n    if(cdest)\n    {\n      value = safe_divide_32(cdest->value, value);\n\n      if(cdest->gateway_write && cdest->gateway_write < NUM_GATEWAYS)\n      {\n        gateway_write_function gateway_write = gateways[cdest->gateway_write];\n        value = gateway_write(mzx_world, cdest, name, value, id, false);\n      }\n\n      cdest->value = value;\n    }\n  }\n}\n\nvoid mod_counter(struct world *mzx_world, const char *name, int value, int id)\n{\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  const struct function_counter *fdest;\n  struct counter *cdest;\n  int current_value;\n  int next;\n\n  if(value == 0)\n    return;\n\n  fdest = find_function_counter(name);\n\n  if(fdest && fdest->function_read && fdest->function_write &&\n   (mzx_world->version >= fdest->minimum_version))\n  {\n    current_value =\n     fdest->function_read(mzx_world, fdest, name, id);\n\n    fdest->function_write(mzx_world, fdest, name,\n     safe_modulo_32(current_value, value), id);\n  }\n  else\n  {\n    cdest = find_counter(counter_list, name, &next);\n\n    if(cdest)\n      cdest->value = safe_modulo_32(cdest->value, value);\n  }\n}\n\n// Create a new counter from loading a save file. This skips find_counter.\nvoid load_new_counter(struct counter_list *counter_list, int index,\n const char *name, int name_length, int value)\n{\n  struct counter *dest = allocate_new_counter(name, name_length, value);\n\n  counter_list->counters[index] = dest;\n\n#ifdef CONFIG_COUNTER_HASH_TABLES\n  HASH_ADD(COUNTER, counter_list->hash_table, dest);\n#endif\n}\n\nstatic int counter_sort_fcn(const void *a, const void *b)\n{\n  return strcasecmp(\n   (*(const struct counter **)a)->name,\n   (*(const struct counter **)b)->name);\n}\n\nvoid sort_counter_list(struct counter_list *counter_list)\n{\n  qsort(counter_list->counters, (size_t)counter_list->num_counters,\n   sizeof(struct counter *), counter_sort_fcn);\n}\n\nvoid clear_counter_list(struct counter_list *counter_list)\n{\n  size_t i;\n\n#ifdef CONFIG_COUNTER_HASH_TABLES\n  HASH_CLEAR(COUNTER, counter_list->hash_table);\n  counter_list->hash_table = NULL;\n#endif\n\n  for(i = 0; i < counter_list->num_counters; i++)\n    free(counter_list->counters[i]);\n\n  free(counter_list->counters);\n\n  counter_list->num_counters = 0;\n  counter_list->num_counters_allocated = 0;\n  counter_list->counters = NULL;\n}\n\n#ifdef CONFIG_EDITOR\n\nvoid counter_list_size(struct counter_list *counter_list,\n size_t *list_size, size_t *table_size, size_t *counters_size)\n{\n  if(list_size)\n    *list_size = counter_list->num_counters_allocated * sizeof(struct counter *);\n\n  if(table_size)\n  {\n    *table_size = 0;\n#ifdef CONFIG_COUNTER_HASH_TABLES\n    HASH_MEMORY_USAGE(COUNTER, counter_list->hash_table, *table_size);\n#endif\n  }\n\n  if(counters_size)\n  {\n    size_t total = 0;\n    size_t i;\n\n    if(counter_list->counters)\n    {\n      for(i = 0; i < counter_list->num_counters; i++)\n      {\n        struct counter *c = counter_list->counters[i];\n        if(c)\n          total += get_counter_alloc_size(c->name_length);\n      }\n    }\n    *counters_size = total;\n  }\n}\n\n#endif /* CONFIG_EDITOR */\n"
  },
  {
    "path": "src/counter.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __COUNTER_H\n#define __COUNTER_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"counter_struct.h\"\n#include \"world_struct.h\"\n\n// Settings SAVE_ROBOT should use for disassembling Robotic.\n#define SAVE_ROBOT_DISASM_EXTRAS  true\n#define SAVE_ROBOT_DISASM_BASE    10\n\nCORE_LIBSPEC void counter_fsg(void);\nCORE_LIBSPEC int match_function_counter(const char *dest, const char *src);\nCORE_LIBSPEC int get_counter(struct world *mzx_world, const char *name, int id);\nCORE_LIBSPEC const struct counter *get_counter_pointer(struct world *mzx_world,\n const char *name, int id);\nCORE_LIBSPEC void set_counter(struct world *mzx_world, const char *name,\n int value, int id);\nCORE_LIBSPEC void new_counter(struct world *mzx_world, const char *name,\n int value, int id);\nCORE_LIBSPEC void sort_counter_list(struct counter_list *counter_list);\nCORE_LIBSPEC void counter_list_size(struct counter_list *counter_list,\n size_t *list_size, size_t *table_size, size_t *counters_size);\n\nvoid initialize_gateway_functions(struct world *mzx_world);\nvoid inc_counter(struct world *mzx_world, const char *name, int value, int id);\nvoid dec_counter(struct world *mzx_world, const char *name, int value, int id);\nvoid mul_counter(struct world *mzx_world, const char *name, int value, int id);\nvoid div_counter(struct world *mzx_world, const char *name, int value, int id);\nvoid mod_counter(struct world *mzx_world, const char *name, int value, int id);\n\nint set_counter_special(struct world *mzx_world, char *char_value,\n int value, int id);\n\nvoid load_new_counter(struct counter_list *counter_list, int index,\n const char *name, int name_length, int value);\n\nvoid clear_counter_list(struct counter_list *counter_list);\n\n// Even old games tended to use at least this many.\n#define MIN_COUNTER_ALLOCATE 32\n\n__M_END_DECLS\n\n#endif // __COUNTER_H\n"
  },
  {
    "path": "src/counter_struct.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2012-2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __COUNTER_STRUCT_H\n#define __COUNTER_STRUCT_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <inttypes.h>\n\nstruct counter\n{\n  int32_t value;\n#ifdef CONFIG_COUNTER_HASH_TABLES\n  uint32_t hash;\n#endif\n  uint16_t name_length;\n  uint8_t gateway_write;\n  uint8_t unused;\n\n  /**\n   * This struct will be allocated with extra space to contain the entire\n   * counter name, null-terminated. This field MUST be at least 4-aligned and\n   * it must be the last field (any extra padding after it will be used).\n   */\n  char name[1];\n};\n\nstruct counter_list\n{\n  unsigned int num_counters;\n  unsigned int num_counters_allocated;\n  struct counter **counters;\n#ifdef CONFIG_COUNTER_HASH_TABLES\n  void *hash_table;\n#endif\n};\n\n// Like counters, string names are allocated as part of the struct.\n// The storage space is ALWAYS allocated separately from the rest\n// of the string in normal strings, which allows string struct\n// pointers to remain stable throughout their entire lifetime.\n// The storage space must always be allocated for normal strings.\n\n// Strings can also be used as pointers to hold intermediate or immediate\n// values. For instance, string literals and spliced\n// strings. Anyone who uses strings in this way has a few\n// responsibilities, however:\n// The string must be properly initialized so length and\n// value are set (allocated_length need not be set)\n// The actual string list must not contain any of these\n// kinds of temporary strings.\n// Any deallocation (of value?) must be done by the\n// initializer.\n// get_string returns temporary strings like this (for\n// speed and memory efficiency reasons), and the string\n// mutating functions work on strings of this format as\n// the second operand.\n\n// In a normal (persistent string, initialized by\n// add_string) string the struct will be over-initialized\n// to contain room for both the name and the value; value\n// will then point into the storage space somewhere. The\n// name comes first and is a fixed size. The value comes\n// after it and is of a dynamic length (allocated_length).\n// Allocated_length will typically end up larger than\n// length if the string's size is changed.\n\nstruct string\n{\n  char *value;\n  uint32_t length;\n  uint32_t allocated_length;\n\n#ifdef CONFIG_COUNTER_HASH_TABLES\n  uint32_t hash;\n#endif\n\n  uint16_t name_length;\n  uint8_t unused;\n  uint8_t unused2;\n\n  /**\n   * This struct will be allocated with extra space to contain the entire\n   * string name, null-terminated. This field MUST be at least 4-aligned and\n   * it must be the last field (any extra padding after it will be used).\n   */\n  char name[1];\n};\n\nstruct string_list\n{\n  unsigned int num_strings;\n  unsigned int num_strings_allocated;\n  struct string **strings;\n#ifdef CONFIG_COUNTER_HASH_TABLES\n  void *hash_table;\n#endif\n};\n\n// Special counter returns for opening files\n\nenum special_counter_return\n{\n  FOPEN_NONE = 0,\n  FOPEN_FREAD,\n  FOPEN_FWRITE,\n  FOPEN_FAPPEND,\n  FOPEN_FMODIFY,\n  FOPEN_SMZX_PALETTE,\n  FOPEN_SMZX_INDICES,\n  FOPEN_LOAD_GAME,\n  FOPEN_SAVE_GAME,\n  FOPEN_LOAD_COUNTERS,\n  FOPEN_SAVE_COUNTERS,\n  FOPEN_LOAD_ROBOT,\n  FOPEN_LOAD_BC,\n  FOPEN_SAVE_ROBOT,\n  FOPEN_SAVE_BC\n};\n\n__M_END_DECLS\n\n#endif // __COUNTER_STRUCT_H\n"
  },
  {
    "path": "src/data.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 B.D.A\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Translation of data.asm to C\n\n#include \"data.h\"\n#include \"const.h\"\n\n// Some globals that still lurk.. should be moved to other places.\n\n// Current MZX file information\nchar curr_file[MAX_PATH] = \"CAVERNS.MZX\";\nchar curr_sav[MAX_PATH] = \"SAVED.SAV\";\nchar current_dir[MAX_PATH];\nchar config_dir[MAX_PATH];\n\n// Some global world values that haven't been removed yet.\n\nunsigned char scroll_color = 15;\n\n// Array of flags for things\nconst unsigned int flags[] =\n{\n  A_UNDER,                                                    // 0x00 Space\n  0,                                                          // 0x01 Normal\n  0,                                                          // 0x02 Solid\n  0,                                                          // 0x03 Tree\n  0,                                                          // 0x04 Line\n  0,                                                          // 0x05 Customblock\n  A_SHOOTABLE | A_BLOW_UP,                                    // 0x06 Breakaway\n  A_SHOOTABLE | A_BLOW_UP,                                    // 0x07 Customblock\n  A_PUSHABLE | A_BLOW_UP,                                     // 0x08 Boulder\n  A_PUSHABLE | A_BLOW_UP,                                     // 0x09 Crate\n  A_PUSHABLE | A_BLOW_UP,                                     // 0x0A Custompush\n  A_PUSHABLE,                                                 // 0x0B Box\n  A_PUSHABLE,                                                 // 0x0C Customsolidpush\n  A_UNDER,                                                    // 0x0D Fake\n  A_UNDER,                                                    // 0x0E Carpet\n  A_UNDER,                                                    // 0x0F Floor\n  A_UNDER,                                                    // 0x10 Tiles\n  A_UNDER,                                                    // 0x11 Customfloor\n  A_UNDER | A_BLOW_UP,                                        // 0x12 Web\n  A_UNDER | A_BLOW_UP,                                        // 0x13 Thickweb\n  A_UNDER,                                                    // 0x14 Stillwater\n  A_UNDER | A_AFFECT_IF_STOOD,                                // 0x15 NWater\n  A_UNDER | A_AFFECT_IF_STOOD,                                // 0x16 SWater\n  A_UNDER | A_AFFECT_IF_STOOD,                                // 0x17 EWater\n  A_UNDER | A_AFFECT_IF_STOOD,                                // 0x18 WWater\n  A_UNDER | A_AFFECT_IF_STOOD | A_UPDATE,                     // 0x19 Ice\n  A_UNDER | A_AFFECT_IF_STOOD | A_UPDATE,                     // 0x1A Lava\n  A_ITEM | A_BLOW_UP,                                         // 0x1B Chest\n  A_ITEM | A_PUSHABLE | A_BLOW_UP | A_SHOOTABLE,              // 0x1C Gem\n  A_ITEM | A_PUSHABLE | A_BLOW_UP | A_SHOOTABLE,              // 0x1D Magicgem\n  A_ITEM | A_PUSHABLE,                                        // 0x1E Health\n  A_ITEM | A_PUSHABLE,                                        // 0x1F Ring\n  A_ITEM | A_PUSHABLE,                                        // 0x20 Potion\n  A_ITEM | A_PUSHABLE | A_UPDATE,                             // 0x21 Energizer\n  A_UNDER | A_ITEM,                                           // 0x22 Goop\n  A_ITEM | A_PUSHABLE,                                        // 0x23 Ammo\n  A_ITEM | A_PUSHABLE | A_EXPLODE,                            // 0x24 Bomb\n  A_PUSHABLE | A_EXPLODE | A_UPDATE,                          // 0x25 LitBomb\n  A_HURTS | A_UPDATE,                                         // 0x26 Explosion\n  A_ITEM | A_PUSHABLE,                                        // 0x27 Key\n  A_ITEM,                                                     // 0x28 Lock\n  A_ITEM,                                                     // 0x29 Door\n  A_UPDATE,                                                   // 0x2A Opening/closing door\n  A_ENTRANCE | A_UNDER,                                       // 0x2B Stairs\n  A_ENTRANCE | A_UNDER,                                       // 0x2C Cave\n  A_UPDATE,                                                   // 0x2D CW\n  A_UPDATE,                                                   // 0x2E CCW\n  A_ITEM,                                                     // 0x2F Gate\n  A_UPDATE | A_UNDER,                                         // 0x30 OpenGate\n  A_SPEC_PUSH | A_ITEM | A_UPDATE,                            // 0x31 Transport\n  A_ITEM | A_PUSHABLE,                                        // 0x32 Coin\n  A_UPDATE,                                                   // 0x33 MovingWall N\n  A_UPDATE,                                                   // 0x34 MovingWall S\n  A_UPDATE,                                                   // 0x35 MovingWall E\n  A_UPDATE,                                                   // 0x36 MovingWall W\n  A_ITEM | A_PUSHABLE | A_BLOW_UP,                            // 0x37 Pouch\n  A_UPDATE,                                                   // 0x38 Pusher\n  A_PUSHNS,                                                   // 0x39 SliderNS\n  A_PUSHEW,                                                   // 0x3A SliderEW\n  A_UPDATE | A_HURTS,                                         // 0x3B Lazer\n  A_UPDATE,                                                   // 0x3C LazerWallShooter\n  A_UPDATE | A_BLOW_UP | A_SHOOTABLE | A_ITEM |  A_ENEMY,     // 0x3D Bullet\n  A_UPDATE | A_HURTS | A_EXPLODE,                             // 0x3E Missile\n  A_UPDATE | A_UNDER | A_AFFECT_IF_STOOD,                     // 0x3F Fire\n  0,                                                          // 0x40\n  A_ITEM,                                                     // 0x41 Forest\n  A_PUSHABLE | A_ITEM | A_UPDATE,                             // 0x42 Life\n  A_UPDATE | A_UNDER | A_ENTRANCE,                            // 0x43 Whirlpool 1\n  A_UPDATE | A_UNDER | A_ENTRANCE,                            // 0x44 Whirlpool 2\n  A_UPDATE | A_UNDER | A_ENTRANCE,                            // 0x45 Whirlpool 3\n  A_UPDATE | A_UNDER | A_ENTRANCE,                            // 0x46 Whirlpool 4\n  A_ITEM,                                                     // 0x47 Invisible\n  A_SPEC_SHOT,                                                // 0x48 RicochetPanel\n  A_SPEC_SHOT,                                                // 0x49 Ricochet\n  A_ITEM | A_SPEC_SHOT | A_SPEC_BOMB | A_UPDATE,              // 0x4A Mine\n  A_HURTS,                                                    // 0x4B Spike\n  A_HURTS,                                                    // 0x4C Customhurt\n  0,                                                          // 0x4D Text\n  A_UPDATE | A_ENEMY | A_BLOW_UP,                             // 0x4E ShootingFire\n  A_UPDATE | A_ENEMY | A_BLOW_UP | A_PUSHABLE,                // 0x4F Seeker\n  A_UPDATE | A_PUSHABLE | A_ENEMY | A_SHOOTABLE | A_BLOW_UP,  // 0x50 Snake\n  A_UPDATE | A_EXPLODE | A_ITEM | A_SPEC_SHOT | A_PUSHABLE,   // 0x51 Eye\n  A_UPDATE | A_BLOW_UP | A_PUSHABLE | A_ITEM | A_SHOOTABLE,   // 0x52 Thief\n  A_UPDATE | A_ITEM | A_SPEC_BOMB | A_SPEC_SHOT,              // 0x53 SlimeBlob\n  A_UPDATE | A_ENEMY | A_BLOW_UP | A_PUSHABLE | A_SPEC_SHOT,  // 0x54 Runner\n  A_UPDATE | A_ITEM | A_PUSHABLE | A_SPEC_BOMB | A_SPEC_SHOT, // 0x55 Ghost\n  A_UPDATE | A_SPEC_SHOT | A_SPEC_BOMB | A_ITEM,              // 0x56 Dragon\n  A_UPDATE | A_ITEM | A_BLOW_UP | A_SPEC_SHOT | A_PUSHABLE,   // 0x57 Fish\n  A_UPDATE | A_BLOW_UP | A_SHOOTABLE | A_ENEMY,               // 0x58 Shark\n  A_UPDATE | A_ENEMY | A_BLOW_UP | A_SPEC_SHOT | A_PUSHABLE,  // 0x59 Spider\n  A_UPDATE | A_ENEMY | A_SHOOTABLE | A_PUSHABLE | A_BLOW_UP,  // 0x5A Goblin (90)\n  A_UPDATE | A_PUSHABLE | A_SHOOTABLE | A_ENEMY | A_BLOW_UP,  // 0x5B Tiger\n  A_UPDATE,                                                   // 0x5C BulletGun\n  A_UPDATE,                                                   // 0x5D SpinningGun\n  A_UPDATE | A_ENEMY | A_SPEC_SHOT | A_PUSHABLE | A_BLOW_UP,  // 0x5E Bear\n  A_UPDATE | A_BLOW_UP | A_ENEMY | A_SHOOTABLE | A_PUSHABLE,  // 0x5F BearCub\n  0,                                                          // 0x60 '?'\n  A_UPDATE,                                                   // 0x61 MissileGun\n  0,                                                          // 0x62 98\n  0,                                                          // 0x63 99\n  0,                                                          // 0x64 100\n  0,                                                          // 0x65 101\n  0,                                                          // 0x66 102\n  0,                                                          // 0x67 103\n  0,                                                          // 0x68 104\n  0,                                                          // 0x69 105\n  0,                                                          // 0x6A 106\n  0,                                                          // 0x6B 107\n  0,                                                          // 0x6C 108\n  0,                                                          // 0x6D 109\n  0,                                                          // 0x6E 110\n  0,                                                          // 0x6F 111\n  0,                                                          // 0x70 112\n  0,                                                          // 0x71 113\n  0,                                                          // 0x72 114\n  0,                                                          // 0x73 115\n  0,                                                          // 0x74 116\n  0,                                                          // 0x75 117\n  0,                                                          // 0x76 118\n  0,                                                          // 0x77 119\n  0,                                                          // 0x78 120\n  0,                                                          // 0x79 x0C 121\n  A_SPEC_STOOD | A_SPEC_PUSH,                                 // 0x7A x0D Sensor\n  A_ITEM | A_UPDATE | A_SPEC_SHOT | A_SPEC_BOMB | A_SPEC_PUSH,// 0x7B x0E Robot (pushable)\n  A_ITEM | A_UPDATE | A_SPEC_SHOT | A_SPEC_BOMB,              // 0x7C x0F Robot\n  A_ITEM,                                                     // 0x7D x00 Sign\n  A_ITEM | A_PUSHABLE,                                        // 0x7E x01 Scroll\n  A_SPEC_SHOT | A_SPEC_PUSH | A_SPEC_BOMB                     // 0x7F x02 Player\n};\n\n//Names for all things\nconst char *const thing_names[128] =\n{\n  \"Space\",\n  \"Normal\",\n  \"Solid\",\n  \"Tree\",\n  \"Line\",\n  \"CustomBlock\",\n  \"Breakaway\",\n  \"CustomBreak\",\n  \"Boulder\",\n  \"Crate\",\n  \"CustomPush\",\n  \"Box\",\n  \"CustomBox\",\n  \"Fake\",\n  \"Carpet\",\n  \"Floor\",\n  \"Tiles\",\n  \"CustomFloor\",\n  \"Web\",\n  \"ThickWeb\",\n  \"StillWater\",\n  \"NWater\",\n  \"SWater\",\n  \"EWater\",\n  \"WWater\",\n  \"Ice\",\n  \"Lava\",\n  \"Chest\",\n  \"Gem\",\n  \"MagicGem\",\n  \"Health\",\n  \"Ring\",\n  \"Potion\",\n  \"Energizer\",\n  \"Goop\",\n  \"Ammo\",\n  \"Bomb\",\n  \"LitBomb\",\n  \"Explosion\",\n  \"Key\",\n  \"Lock\",\n  \"Door\",\n  \"OpenDoor\",\n  \"Stairs\",\n  \"Cave\",\n  \"CWRotate\",\n  \"CCWRotate\",\n  \"Gate\",\n  \"OpenGate\",\n  \"Transport\",\n  \"Coin\",\n  \"NMovingWall\",\n  \"SMovingWall\",\n  \"EMovingWall\",\n  \"WMovingWall\",\n  \"Pouch\",\n  \"Pusher\",\n  \"SliderNS\",\n  \"SliderEW\",\n  \"Lazer\",\n  \"LazerGun\",\n  \"Bullet\",\n  \"Missile\",\n  \"Fire\",\n  \"[unknown]\",\n  \"Forest\",\n  \"Life\",\n  \"Whirlpool\",\n  \"Whirlpool2\",\n  \"Whirlpool3\",\n  \"Whirlpool4\",\n  \"InvisWall\",\n  \"RicochetPanel\",\n  \"Ricochet\",\n  \"Mine\",\n  \"Spike\",\n  \"CustomHurt\",\n  \"Text\",\n  \"ShootingFire\",\n  \"Seeker\",\n  \"Snake\",\n  \"Eye\",\n  \"Thief\",\n  \"Slimeblob\",\n  \"Runner\",\n  \"Ghost\",\n  \"Dragon\",\n  \"Fish\",\n  \"Shark\",\n  \"Spider\",\n  \"Goblin\",\n  \"SpittingTiger\",\n  \"BulletGun\",\n  \"SpinningGun\",\n  \"Bear\",\n  \"BearCub\",\n  \"[unknown]\",\n  \"MissileGun\",\n  \"Sprite\",\n  \"Sprite_colliding\",\n  \"Image_file\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"__unused\",\n  \"Sensor\",\n  \"PushableRobot\",\n  \"Robot\",\n  \"Sign\",\n  \"Scroll\",\n  \"Player\"\n};\n"
  },
  {
    "path": "src/data.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __DATA_H\n#define __DATA_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"const.h\"\n#include <stdio.h>\n#include <stdlib.h>\n\nCORE_LIBSPEC extern char curr_file[MAX_PATH];\nCORE_LIBSPEC extern char curr_sav[MAX_PATH];\nCORE_LIBSPEC extern char current_dir[MAX_PATH];\nCORE_LIBSPEC extern char config_dir[MAX_PATH];\n\nCORE_LIBSPEC extern unsigned char scroll_color;\nCORE_LIBSPEC extern const char *const thing_names[128];\nCORE_LIBSPEC extern const unsigned int flags[128];\n\nenum thing\n{\n  SPACE           = 0,\n  NORMAL          = 1,\n  SOLID           = 2,\n  TREE            = 3,\n  LINE            = 4,\n  CUSTOM_BLOCK    = 5,\n  BREAKAWAY       = 6,\n  CUSTOM_BREAK    = 7,\n  BOULDER         = 8,\n  CRATE           = 9,\n  CUSTOM_PUSH     = 10,\n  BOX             = 11,\n  CUSTOM_BOX      = 12,\n  FAKE            = 13,\n  CARPET          = 14,\n  FLOOR           = 15,\n  TILES           = 16,\n  CUSTOM_FLOOR    = 17,\n  WEB             = 18,\n  THICK_WEB       = 19,\n  STILL_WATER     = 20,\n  N_WATER         = 21,\n  S_WATER         = 22,\n  E_WATER         = 23,\n  W_WATER         = 24,\n  ICE             = 25,\n  LAVA            = 26,\n  CHEST           = 27,\n  GEM             = 28,\n  MAGIC_GEM       = 29,\n  HEALTH          = 30,\n  RING            = 31,\n  POTION          = 32,\n  ENERGIZER       = 33,\n  GOOP            = 34,\n  AMMO            = 35,\n  BOMB            = 36,\n  LIT_BOMB        = 37,\n  EXPLOSION       = 38,\n  KEY             = 39,\n  LOCK            = 40,\n  DOOR            = 41,\n  OPEN_DOOR       = 42,\n  STAIRS          = 43,\n  CAVE            = 44,\n  CW_ROTATE       = 45,\n  CCW_ROTATE      = 46,\n  GATE            = 47,\n  OPEN_GATE       = 48,\n  TRANSPORT       = 49,\n  COIN            = 50,\n  N_MOVING_WALL   = 51,\n  S_MOVING_WALL   = 52,\n  E_MOVING_WALL   = 53,\n  W_MOVING_WALL   = 54,\n  POUCH           = 55,\n  PUSHER          = 56,\n  SLIDER_NS       = 57,\n  SLIDER_EW       = 58,\n  LAZER           = 59,\n  LAZER_GUN       = 60,\n  BULLET          = 61,\n  MISSILE         = 62,\n  FIRE            = 63,\n  FOREST          = 65,\n  LIFE            = 66,\n  WHIRLPOOL_1     = 67,\n  WHIRLPOOL_2     = 68,\n  WHIRLPOOL_3     = 69,\n  WHIRLPOOL_4     = 70,\n  INVIS_WALL      = 71,\n  RICOCHET_PANEL  = 72,\n  RICOCHET        = 73,\n  MINE            = 74,\n  SPIKE           = 75,\n  CUSTOM_HURT     = 76,\n  TEXT_ID         = 77,\n  SHOOTING_FIRE   = 78,\n  SEEKER          = 79,\n  SNAKE           = 80,\n  EYE             = 81,\n  THIEF           = 82,\n  SLIMEBLOB       = 83,\n  RUNNER          = 84,\n  GHOST           = 85,\n  DRAGON          = 86,\n  FISH            = 87,\n  SHARK           = 88,\n  SPIDER          = 89,\n  GOBLIN          = 90,\n  SPITTING_TIGER  = 91,\n  BULLET_GUN      = 92,\n  SPINNING_GUN    = 93,\n  BEAR            = 94,\n  BEAR_CUB        = 95,\n  MISSILE_GUN     = 97,\n  SPRITE          = 98,\n  SPR_COLLISION   = 99,\n  IMAGE_FILE      = 100,\n  SENSOR          = 122,\n  ROBOT_PUSHABLE  = 123,\n  ROBOT           = 124,\n  SIGN            = 125,\n  SCROLL          = 126,\n  PLAYER          = 127,\n  NO_ID           = 255\n};\n\nstatic inline boolean is_fake(enum thing id)\n{\n  return (id == SPACE) || ((id >= FAKE) && (id <= THICK_WEB));\n}\n\nstatic inline boolean is_robot(enum thing id)\n{\n  return (id == ROBOT) || (id == ROBOT_PUSHABLE);\n}\n\nstatic inline boolean is_signscroll(enum thing id)\n{\n  return (id == SIGN) || (id == SCROLL);\n}\n\nstatic inline boolean is_water(enum thing id)\n{\n  return (id >= STILL_WATER) && (id <= W_WATER);\n}\n\nstatic inline boolean is_whirlpool(enum thing id)\n{\n  return (id >= WHIRLPOOL_1) && (id <= WHIRLPOOL_4);\n}\n\nstatic inline boolean is_enemy(enum thing id)\n{\n  return (id >= SNAKE) && (id <= BEAR_CUB) &&\n   (id != BULLET_GUN) && (id != SPINNING_GUN);\n}\n\nstatic inline boolean is_storageless(enum thing id)\n{\n  return id < SENSOR;\n}\n\nenum dir\n{\n  IDLE    = 0,\n  NORTH   = 1,\n  SOUTH   = 2,\n  EAST    = 3,\n  WEST    = 4,\n  RANDNS  = 5,\n  RANDEW  = 6,\n  RANDNE  = 7,\n  RANDNB  = 8,\n  SEEK    = 9,\n  RANDANY = 10,\n  BENEATH = 11,\n  ANYDIR  = 12,\n  FLOW    = 13,\n  NODIR   = 14,\n  RANDB   = 15,\n  RANDP   = 16,\n  CW      = 32,\n  OPP     = 64,\n  RANDNOT = 128\n};\n\nstatic inline boolean is_cardinal_dir(enum dir d)\n{\n  return (d >= NORTH) && (d <= WEST);\n}\n\nstatic inline int dir_to_int(enum dir d)\n{\n  return (int)d - 1;\n}\n\nstatic inline enum dir int_to_dir(int d)\n{\n  return (enum dir)(d + 1);\n}\n\n#define CAN_PUSH      (1 << 0)\n#define CAN_TRANSPORT (1 << 1)\n#define CAN_LAVAWALK  (1 << 2)\n#define CAN_FIREWALK  (1 << 3)\n#define CAN_WATERWALK (1 << 4)\n#define MUST_WEB      (1 << 5)\n#define MUST_THICKWEB (1 << 6)\n#define REACT_PLAYER  (1 << 7)\n#define MUST_WATER    (1 << 8)\n#define MUST_LAVAGOOP (1 << 9)\n#define CAN_GOOPWALK  (1 << 10)\n#define SPITFIRE      (1 << 11)\n\nenum move_status\n{\n  NO_HIT      = 0,\n  HIT         = 1,\n  HIT_PLAYER  = 2,\n  HIT_EDGE    = 3\n};\n\nenum equality\n{\n  EQUAL                 = 0,\n  LESS_THAN             = 1,\n  GREATER_THAN          = 2,\n  GREATER_THAN_OR_EQUAL = 3,\n  LESS_THAN_OR_EQUAL    = 4,\n  NOT_EQUAL             = 5,\n  EXACTLY_EQUAL         = 6,\n  WILD_EQUAL            = 7,\n  WILD_EXACTLY_EQUAL    = 8\n};\n\nenum condition\n{\n  WALKING               = 0,\n  SWIMMING              = 1,\n  FIRE_WALKING          = 2,\n  TOUCHING              = 3,\n  BLOCKED               = 4,\n  ALIGNED               = 5,\n  ALIGNED_NS            = 6,\n  ALIGNED_EW            = 7,\n  LASTSHOT              = 8,\n  LASTTOUCH             = 9,\n  RIGHTPRESSED          = 10,\n  LEFTPRESSED           = 11,\n  UPPRESSED             = 12,\n  DOWNPRESSED           = 13,\n  SPACEPRESSED          = 14,\n  DELPRESSED            = 15,\n  MUSICON               = 16,\n  SOUNDON               = 17\n};\n\nenum chest_contents\n{\n  CHEST_EMPTY            = 0,\n  CHEST_KEY              = 1,\n  CHEST_COINS            = 2,\n  CHEST_LIVES            = 3,\n  CHEST_AMMO             = 4,\n  CHEST_GEMS             = 5,\n  CHEST_HEALTH           = 6,\n  CHEST_POTION           = 7,\n  CHEST_RING             = 8,\n  CHEST_LOBOMBS          = 9,\n  CHEST_HIBOMBS          = 10\n};\n\nenum give_item\n{\n  GIVE_GEMS             = 0,\n  GIVE_AMMO             = 1,\n  GIVE_TIME             = 2,\n  GIVE_SCORE            = 3,\n  GIVE_HEALTH           = 4,\n  GIVE_LIVES            = 5,\n  GIVE_LOBOMBS          = 6,\n  GIVE_HIBOMBS          = 7,\n  GIVE_COINS            = 8\n};\n\nenum potion\n{\n  POTION_DUD            = 0,\n  POTION_INVINCO        = 1,\n  POTION_BLAST          = 2,\n  POTION_SMALL_HEALTH   = 3,\n  POTION_BIG_HEALTH     = 4,\n  POTION_POISON         = 5,\n  POTION_BLIND          = 6,\n  POTION_KILL           = 7,\n  POTION_FIREWALK       = 8,\n  POTION_DETONATE       = 9,\n  POTION_BANISH         = 10,\n  POTION_SUMMON         = 11,\n  POTION_AVALANCHE      = 12,\n  POTION_FREEZE         = 13,\n  POTION_WIND           = 14,\n  POTION_SLOW           = 15\n};\n\nenum board_target\n{\n  TARGET_NONE           = 0,\n  TARGET_POSITION       = 1,\n  TARGET_ENTRANCE       = 2,\n  TARGET_TELEPORT       = 3\n};\n\nenum robot_command_name\n{\n  ROBOTIC_CMD_END                                       = 0,\n  ROBOTIC_CMD_DIE                                       = 1,\n  ROBOTIC_CMD_WAIT                                      = 2,\n  ROBOTIC_CMD_CYCLE                                     = 3,\n  ROBOTIC_CMD_GO                                        = 4,\n  ROBOTIC_CMD_WALK                                      = 5,\n  ROBOTIC_CMD_BECOME                                    = 6,\n  ROBOTIC_CMD_CHAR                                      = 7,\n  ROBOTIC_CMD_COLOR                                     = 8,\n  ROBOTIC_CMD_GOTOXY                                    = 9,\n  ROBOTIC_CMD_SET                                       = 10,\n  ROBOTIC_CMD_INC                                       = 11,\n  ROBOTIC_CMD_DEC                                       = 12,\n  ROBOTIC_CMD_UNUSED_13                                 = 13,\n  ROBOTIC_CMD_UNUSED_14                                 = 14,\n  ROBOTIC_CMD_UNUSED_15                                 = 15,\n  ROBOTIC_CMD_IF                                        = 16,\n  ROBOTIC_CMD_UNUSED_17                                 = 17,\n  ROBOTIC_CMD_IF_CONDITION                              = 18,\n  ROBOTIC_CMD_IF_NOT_CONDITION                          = 19,\n  ROBOTIC_CMD_IF_ANY                                    = 20,\n  ROBOTIC_CMD_IF_NO                                     = 21,\n  ROBOTIC_CMD_IF_THING_DIR                              = 22,\n  ROBOTIC_CMD_IF_NOT_THING_DIR                          = 23,\n  ROBOTIC_CMD_IF_THING_XY                               = 24,\n  ROBOTIC_CMD_IF_AT                                     = 25,\n  ROBOTIC_CMD_IF_DIR_OF_PLAYER                          = 26,\n  ROBOTIC_CMD_DOUBLE                                    = 27,\n  ROBOTIC_CMD_HALF                                      = 28,\n  ROBOTIC_CMD_GOTO                                      = 29,\n  ROBOTIC_CMD_SEND                                      = 30,\n  ROBOTIC_CMD_EXPLODE                                   = 31,\n  ROBOTIC_CMD_PUT_DIR                                   = 32,\n  ROBOTIC_CMD_GIVE                                      = 33,\n  ROBOTIC_CMD_TAKE                                      = 34,\n  ROBOTIC_CMD_TAKE_OR                                   = 35,\n  ROBOTIC_CMD_ENDGAME                                   = 36,\n  ROBOTIC_CMD_ENDLIFE                                   = 37,\n  ROBOTIC_CMD_MOD                                       = 38,\n  ROBOTIC_CMD_SAM                                       = 39,\n  ROBOTIC_CMD_VOLUME                                    = 40,\n  ROBOTIC_CMD_END_MOD                                   = 41,\n  ROBOTIC_CMD_END_SAM                                   = 42,\n  ROBOTIC_CMD_PLAY                                      = 43,\n  ROBOTIC_CMD_END_PLAY                                  = 44,\n  ROBOTIC_CMD_WAIT_THEN_PLAY                            = 45,\n  ROBOTIC_CMD_WAIT_PLAY                                 = 46,\n  ROBOTIC_CMD_BLANK_LINE                                = 47,\n  ROBOTIC_CMD_SFX                                       = 48,\n  ROBOTIC_CMD_PLAY_IF_SILENT                            = 49,\n  ROBOTIC_CMD_OPEN                                      = 50,\n  ROBOTIC_CMD_LOCKSELF                                  = 51,\n  ROBOTIC_CMD_UNLOCKSELF                                = 52,\n  ROBOTIC_CMD_SEND_DIR                                  = 53,\n  ROBOTIC_CMD_ZAP                                       = 54,\n  ROBOTIC_CMD_RESTORE                                   = 55,\n  ROBOTIC_CMD_LOCKPLAYER                                = 56,\n  ROBOTIC_CMD_UNLOCKPLAYER                              = 57,\n  ROBOTIC_CMD_LOCKPLAYER_NS                             = 58,\n  ROBOTIC_CMD_LOCKPLAYER_EW                             = 59,\n  ROBOTIC_CMD_LOCKPLAYER_ATTACK                         = 60,\n  ROBOTIC_CMD_MOVE_PLAYER_DIR                           = 61,\n  ROBOTIC_CMD_MOVE_PLAYER_DIR_OR                        = 62,\n  ROBOTIC_CMD_PUT_PLAYER_XY                             = 63,\n  ROBOTIC_CMD_OBSOLETE_IF_PLAYER_DIR                    = 64,\n  ROBOTIC_CMD_OBSOLETE_IF_NOT_PLAYER_DIR                = 65,\n  ROBOTIC_CMD_IF_PLAYER_XY                              = 66,\n  ROBOTIC_CMD_PUT_PLAYER_DIR                            = 67,\n  ROBOTIC_CMD_TRY_DIR                                   = 68,\n  ROBOTIC_CMD_ROTATECW                                  = 69,\n  ROBOTIC_CMD_ROTATECCW                                 = 70,\n  ROBOTIC_CMD_SWITCH                                    = 71,\n  ROBOTIC_CMD_SHOOT                                     = 72,\n  ROBOTIC_CMD_LAYBOMB                                   = 73,\n  ROBOTIC_CMD_LAYBOMB_HIGH                              = 74,\n  ROBOTIC_CMD_SHOOTMISSILE                              = 75,\n  ROBOTIC_CMD_SHOOTSEEKER                               = 76,\n  ROBOTIC_CMD_SPITFIRE                                  = 77,\n  ROBOTIC_CMD_LAZERWALL                                 = 78,\n  ROBOTIC_CMD_PUT_XY                                    = 79,\n  ROBOTIC_CMD_DIE_ITEM                                  = 80,\n  ROBOTIC_CMD_SEND_XY                                   = 81,\n  ROBOTIC_CMD_COPYROBOT_NAMED                           = 82,\n  ROBOTIC_CMD_COPYROBOT_XY                              = 83,\n  ROBOTIC_CMD_COPYROBOT_DIR                             = 84,\n  ROBOTIC_CMD_DUPLICATE_SELF_DIR                        = 85,\n  ROBOTIC_CMD_DUPLICATE_SELF_XY                         = 86,\n  ROBOTIC_CMD_BULLETN                                   = 87,\n  ROBOTIC_CMD_BULLETS                                   = 88,\n  ROBOTIC_CMD_BULLETE                                   = 89,\n  ROBOTIC_CMD_BULLETW                                   = 90,\n  ROBOTIC_CMD_GIVEKEY                                   = 91,\n  ROBOTIC_CMD_GIVEKEY_OR                                = 92,\n  ROBOTIC_CMD_TAKEKEY                                   = 93,\n  ROBOTIC_CMD_TAKEKEY_OR                                = 94,\n  ROBOTIC_CMD_INC_RANDOM                                = 95,\n  ROBOTIC_CMD_DEC_RANDOM                                = 96,\n  ROBOTIC_CMD_SET_RANDOM                                = 97,\n  ROBOTIC_CMD_TRADE                                     = 98,\n  ROBOTIC_CMD_SEND_DIR_PLAYER                           = 99,\n  ROBOTIC_CMD_PUT_DIR_PLAYER                            = 100,\n  ROBOTIC_CMD_SLASH                                     = 101,\n  ROBOTIC_CMD_MESSAGE_LINE                              = 102,\n  ROBOTIC_CMD_MESSAGE_BOX_LINE                          = 103,\n  ROBOTIC_CMD_MESSAGE_BOX_OPTION                        = 104,\n  ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION                  = 105,\n  ROBOTIC_CMD_LABEL                                     = 106,\n  ROBOTIC_CMD_COMMENT                                   = 107,\n  ROBOTIC_CMD_ZAPPED_LABEL                              = 108,\n  ROBOTIC_CMD_TELEPORT                                  = 109,\n  ROBOTIC_CMD_SCROLLVIEW                                = 110,\n  ROBOTIC_CMD_INPUT                                     = 111,\n  ROBOTIC_CMD_IF_INPUT                                  = 112,\n  ROBOTIC_CMD_IF_INPUT_NOT                              = 113,\n  ROBOTIC_CMD_IF_INPUT_MATCHES                          = 114,\n  ROBOTIC_CMD_PLAYER_CHAR                               = 115,\n  ROBOTIC_CMD_MESSAGE_BOX_COLOR_LINE                    = 116,\n  ROBOTIC_CMD_MESSAGE_BOX_CENTER_LINE                   = 117,\n  ROBOTIC_CMD_MOVE_ALL                                  = 118,\n  ROBOTIC_CMD_COPY                                      = 119,\n  ROBOTIC_CMD_SET_EDGE_COLOR                            = 120,\n  ROBOTIC_CMD_BOARD                                     = 121,\n  ROBOTIC_CMD_BOARD_IS_NONE                             = 122,\n  ROBOTIC_CMD_CHAR_EDIT                                 = 123,\n  ROBOTIC_CMD_BECOME_PUSHABLE                           = 124,\n  ROBOTIC_CMD_BECOME_NONPUSHABLE                        = 125,\n  ROBOTIC_CMD_BLIND                                     = 126,\n  ROBOTIC_CMD_FIREWALKER                                = 127,\n  ROBOTIC_CMD_FREEZETIME                                = 128,\n  ROBOTIC_CMD_SLOWTIME                                  = 129,\n  ROBOTIC_CMD_WIND                                      = 130,\n  ROBOTIC_CMD_AVALANCHE                                 = 131,\n  ROBOTIC_CMD_COPY_DIR                                  = 132,\n  ROBOTIC_CMD_BECOME_LAVAWALKER                         = 133,\n  ROBOTIC_CMD_BECOME_NONLAVAWALKER                      = 134,\n  ROBOTIC_CMD_CHANGE                                    = 135,\n  ROBOTIC_CMD_PLAYERCOLOR                               = 136,\n  ROBOTIC_CMD_BULLETCOLOR                               = 137,\n  ROBOTIC_CMD_MISSILECOLOR                              = 138,\n  ROBOTIC_CMD_MESSAGE_ROW                               = 139,\n  ROBOTIC_CMD_REL_SELF                                  = 140,\n  ROBOTIC_CMD_REL_PLAYER                                = 141,\n  ROBOTIC_CMD_REL_COUNTERS                              = 142,\n  ROBOTIC_CMD_SET_ID_CHAR                               = 143,\n  ROBOTIC_CMD_JUMP_MOD_ORDER                            = 144,\n  ROBOTIC_CMD_ASK                                       = 145,\n  ROBOTIC_CMD_FILLHEALTH                                = 146,\n  ROBOTIC_CMD_THICK_ARROW                               = 147,\n  ROBOTIC_CMD_THIN_ARROW                                = 148,\n  ROBOTIC_CMD_SET_MAX_HEALTH                            = 149,\n  ROBOTIC_CMD_SAVE_PLAYER_POSITION                      = 150,\n  ROBOTIC_CMD_RESTORE_PLAYER_POSITION                   = 151,\n  ROBOTIC_CMD_EXCHANGE_PLAYER_POSITION                  = 152,\n  ROBOTIC_CMD_MESSAGE_COLUMN                            = 153,\n  ROBOTIC_CMD_CENTER_MESSAGE                            = 154,\n  ROBOTIC_CMD_CLEAR_MESSAGE                             = 155,\n  ROBOTIC_CMD_RESETVIEW                                 = 156,\n  ROBOTIC_CMD_MOD_SAM                                   = 157,\n  ROBOTIC_CMD_VOLUME2                                   = 158,\n  ROBOTIC_CMD_SCROLLBASE                                = 159,\n  ROBOTIC_CMD_SCROLLCORNER                              = 160,\n  ROBOTIC_CMD_SCROLLTITLE                               = 161,\n  ROBOTIC_CMD_SCROLLPOINTER                             = 162,\n  ROBOTIC_CMD_SCROLLARROW                               = 163,\n  ROBOTIC_CMD_VIEWPORT                                  = 164,\n  ROBOTIC_CMD_VIEWPORT_WIDTH                            = 165,\n  ROBOTIC_CMD_UNUSED_166                                = 166,\n  ROBOTIC_CMD_UNUSED_167                                = 167,\n  ROBOTIC_CMD_SAVE_PLAYER_POSITION_N                    = 168,\n  ROBOTIC_CMD_RESTORE_PLAYER_POSITION_N                 = 169,\n  ROBOTIC_CMD_EXCHANGE_PLAYER_POSITION_N                = 170,\n  ROBOTIC_CMD_RESTORE_PLAYER_POSITION_N_DUPLICATE_SELF  = 171,\n  ROBOTIC_CMD_EXCHANGE_PLAYER_POSITION_N_DUPLICATE_SELF = 172,\n  ROBOTIC_CMD_PLAYER_BULLETN                            = 173,\n  ROBOTIC_CMD_PLAYER_BULLETS                            = 174,\n  ROBOTIC_CMD_PLAYER_BULLETE                            = 175,\n  ROBOTIC_CMD_PLAYER_BULLETW                            = 176,\n  ROBOTIC_CMD_NEUTRAL_BULLETN                           = 177,\n  ROBOTIC_CMD_NEUTRAL_BULLETS                           = 178,\n  ROBOTIC_CMD_NEUTRAL_BULLETE                           = 179,\n  ROBOTIC_CMD_NEUTRAL_BULLETW                           = 180,\n  ROBOTIC_CMD_ENEMY_BULLETN                             = 181,\n  ROBOTIC_CMD_ENEMY_BULLETS                             = 182,\n  ROBOTIC_CMD_ENEMY_BULLETE                             = 183,\n  ROBOTIC_CMD_ENEMY_BULLETW                             = 184,\n  ROBOTIC_CMD_PLAYER_BULLET_COLOR                       = 185,\n  ROBOTIC_CMD_NEUTRAL_BULLET_COLOR                      = 186,\n  ROBOTIC_CMD_ENEMY_BULLET_COLOR                        = 187,\n  ROBOTIC_CMD_UNUSED_188                                = 188,\n  ROBOTIC_CMD_UNUSED_189                                = 189,\n  ROBOTIC_CMD_UNUSED_190                                = 190,\n  ROBOTIC_CMD_UNUSED_191                                = 191,\n  ROBOTIC_CMD_UNUSED_192                                = 192,\n  ROBOTIC_CMD_REL_SELF_FIRST                            = 193,\n  ROBOTIC_CMD_REL_SELF_LAST                             = 194,\n  ROBOTIC_CMD_REL_PLAYER_FIRST                          = 195,\n  ROBOTIC_CMD_REL_PLAYER_LAST                           = 196,\n  ROBOTIC_CMD_REL_COUNTERS_FIRST                        = 197,\n  ROBOTIC_CMD_REL_COUNTERS_LAST                         = 198,\n  ROBOTIC_CMD_MOD_FADE_OUT                              = 199,\n  ROBOTIC_CMD_MOD_FADE_IN                               = 200,\n  ROBOTIC_CMD_COPY_BLOCK                                = 201,\n  ROBOTIC_CMD_CLIP_INPUT                                = 202,\n  ROBOTIC_CMD_PUSH                                      = 203,\n  ROBOTIC_CMD_SCROLL_CHAR                               = 204,\n  ROBOTIC_CMD_FLIP_CHAR                                 = 205,\n  ROBOTIC_CMD_COPY_CHAR                                 = 206,\n  ROBOTIC_CMD_UNUSED_207                                = 207,\n  ROBOTIC_CMD_UNUSED_208                                = 208,\n  ROBOTIC_CMD_UNUSED_209                                = 209,\n  ROBOTIC_CMD_CHANGE_SFX                                = 210,\n  ROBOTIC_CMD_COLOR_INTENSITY_ALL                       = 211,\n  ROBOTIC_CMD_COLOR_INTENSITY_N                         = 212,\n  ROBOTIC_CMD_COLOR_FADE_OUT                            = 213,\n  ROBOTIC_CMD_COLOR_FADE_IN                             = 214,\n  ROBOTIC_CMD_SET_COLOR                                 = 215,\n  ROBOTIC_CMD_LOAD_CHAR_SET                             = 216,\n  ROBOTIC_CMD_MULTIPLY                                  = 217,\n  ROBOTIC_CMD_DIVIDE                                    = 218,\n  ROBOTIC_CMD_MODULO                                    = 219,\n  ROBOTIC_CMD_PLAYER_CHAR_DIR                           = 220,\n  ROBOTIC_CMD_UNUSED_221                                = 221,\n  ROBOTIC_CMD_LOAD_PALETTE                              = 222,\n  ROBOTIC_CMD_UNUSED_223                                = 223,\n  ROBOTIC_CMD_MOD_FADE_TO                               = 224,\n  ROBOTIC_CMD_SCROLLVIEW_XY                             = 225,\n  ROBOTIC_CMD_SWAP_WORLD                                = 226,\n  ROBOTIC_CMD_IF_ALIGNEDROBOT                           = 227,\n  ROBOTIC_CMD_UNUSED_228                                = 228,\n  ROBOTIC_CMD_LOCKSCROLL                                = 229,\n  ROBOTIC_CMD_UNLOCKSCROLL                              = 230,\n  ROBOTIC_CMD_IF_FIRST_INPUT                            = 231,\n  ROBOTIC_CMD_PERSISTENT_GO                             = 232,\n  ROBOTIC_CMD_WAIT_MOD_FADE                             = 233,\n  ROBOTIC_CMD_UNUSED_234                                = 234,\n  ROBOTIC_CMD_ENABLE_SAVING                             = 235,\n  ROBOTIC_CMD_DISABLE_SAVING                            = 236,\n  ROBOTIC_CMD_ENABLE_SENSORONLY_SAVING                  = 237,\n  ROBOTIC_CMD_STATUS_COUNTER                            = 238,\n  ROBOTIC_CMD_OVERLAY_ON                                = 239,\n  ROBOTIC_CMD_OVERLAY_STATIC                            = 240,\n  ROBOTIC_CMD_OVERLAY_TRANSPARENT                       = 241,\n  ROBOTIC_CMD_OVERLAY_PUT_OVERLAY                       = 242,\n  ROBOTIC_CMD_COPY_OVERLAY_BLOCK                        = 243,\n  ROBOTIC_CMD_UNUSED_244                                = 244,\n  ROBOTIC_CMD_CHANGE_OVERLAY                            = 245,\n  ROBOTIC_CMD_CHANGE_OVERLAY_COLOR                      = 246,\n  ROBOTIC_CMD_WRITE_OVERLAY                             = 247,\n  ROBOTIC_CMD_UNUSED_248                                = 248,\n  ROBOTIC_CMD_UNUSED_249                                = 249,\n  ROBOTIC_CMD_UNUSED_250                                = 250,\n  ROBOTIC_CMD_LOOP_START                                = 251,\n  ROBOTIC_CMD_LOOP_FOR                                  = 252,\n  ROBOTIC_CMD_ABORT_LOOP                                = 253,\n  ROBOTIC_CMD_DISABLE_MESG_EDGE                         = 254,\n  ROBOTIC_CMD_ENABLE_MESG_EDGE                          = 255\n};\n\n__M_END_DECLS\n\n#endif // __DATA_H\n"
  },
  {
    "path": "src/editor/Makefile.in",
    "content": "##\n# MegaZeux Editor Makefile fragment\n##\n\n.PHONY: editor_clean editor_target_clean\n\neditor_src = src/editor\neditor_obj = src/editor/.build\n\neditor_flags := ${core_flags}\n\neditor_ldflags += ${ZLIB_LDFLAGS}\n\nifeq (${PLATFORM},mingw)\neditor_spec := -DEDITOR_LIBSPEC=\"__declspec(dllexport)\"\nendif\n\n${editor_obj}/%.o: ${editor_src}/%.c\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${core_cflags} ${editor_flags} ${editor_spec} -c $< -o $@\n\neditor_objs := \\\n  ${editor_obj}/ansi.o          \\\n  ${editor_obj}/block.o         \\\n  ${editor_obj}/board.o         \\\n  ${editor_obj}/buffer.o        \\\n  ${editor_obj}/char_ed.o       \\\n  ${editor_obj}/configure.o     \\\n  ${editor_obj}/debug.o         \\\n  ${editor_obj}/edit.o          \\\n  ${editor_obj}/edit_di.o       \\\n  ${editor_obj}/edit_export.o   \\\n  ${editor_obj}/edit_menu.o     \\\n  ${editor_obj}/fill.o          \\\n  ${editor_obj}/graphics.o      \\\n  ${editor_obj}/macro.o         \\\n  ${editor_obj}/pal_ed.o        \\\n  ${editor_obj}/param.o         \\\n  ${editor_obj}/robo_debug.o    \\\n  ${editor_obj}/robo_ed.o       \\\n  ${editor_obj}/robot.o         \\\n  ${editor_obj}/select.o        \\\n  ${editor_obj}/sfx_edit.o      \\\n  ${editor_obj}/stringsearch.o  \\\n  ${editor_obj}/undo.o          \\\n  ${editor_obj}/window.o        \\\n  ${editor_obj}/world.o\n\n# Win32 clipboard handler (takes precedence over SDL 2)\nifeq (${PLATFORM},mingw)\nclipboard_objs    ?= ${editor_obj}/clipboard_win32.o\nclipboard_cflags  ?=\nclipboard_ldflags ?=\nendif\n\n# Mac OS X clipboard handler via Cocoa\nifeq (${PLATFORM},darwin)\nclipboard_objs    ?= ${editor_obj}/clipboard_cocoa.o\nclipboard_cflags  ?=\nclipboard_ldflags ?= -framework Cocoa\n\n${editor_obj}/%.o: ${editor_src}/%.m\n\t$(if ${V},,@echo \"  OBJC    \" $<)\n\t${CC} -MD ${core_cflags} ${editor_flags} ${editor_spec} -c $< -o $@\nendif\n\n# Use the SDL2+ clipboard handler\nifneq (${BUILD_SDL},)\nifneq (${BUILD_SDL},1)\nclipboard_objs    ?= ${editor_obj}/clipboard_sdl2.o\nclipboard_cflags  ?=\nclipboard_ldflags ?= ${SDL_LDFLAGS}\nendif\nendif\n\n# X11 clipboard handler (requires SDL 1.2)\nifneq (${X11DIR},)\nifneq (${BUILD_SDL},)\nclipboard_objs    ?= ${editor_obj}/clipboard_x11.o\nclipboard_cflags  ?= ${X11_CFLAGS}\nclipboard_ldflags ?= ${X11_LDFLAGS} ${SDL_LDFLAGS}\nendif\nendif\n\n# No clipboard handler\nclipboard_objs    ?= ${editor_obj}/clipboard_null.o\n\neditor_objs += ${clipboard_objs}\neditor_flags += ${clipboard_cflags}\neditor_ldflags += ${clipboard_ldflags}\n\n-include ${editor_objs:.o=.d}\n\n${editor_objs}: $(filter-out $(wildcard ${editor_obj}), ${editor_obj})\n\nifeq (${BUILD_MODULAR},1)\n\neditor_target := ${DSOPRE}editor${DSOPOST}\n\n${editor_target}: ${editor_objs} ${core_target}\n\t$(if ${V},,@echo \"  LINK    \" $@)\n\t${CC} ${DSOLDFLAGS} -o $@ ${editor_objs} ${editor_ldflags} \\\n\t  ${LDFLAGS} ${DSOSONAME}$@ ${DSORPATH} $(LINK_DYNAMIC_IF_MIXED) -L. -lcore ${ARCH_LIBS}\n\neditor_target_clean:\n\t$(if ${V},,@echo \"  RM      \" ${editor_target} ${editor_target}.debug)\n\t${RM} ${editor_target} ${editor_target}.debug\n\nelse\n\neditor_target := ${editor_objs}\n\neditor_target_clean:\n\nendif\n\neditor_clean: editor_target_clean\n\t$(if ${V},,@echo \"  RM      \" ${editor_obj})\n\t${RM} -r ${editor_obj}\n"
  },
  {
    "path": "src/editor/ansi.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1998 Matthew D. Williams - dbwilli@scsn.net\n * Copyright (C) 2020-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n */\n\n#include \"ansi.h\"\n#include \"buffer.h\"\n#include \"edit.h\"\n\n#include \"../error.h\"\n#include \"../idput.h\"\n#include \"../robot.h\"\n#include \"../util.h\"\n#include \"../world_struct.h\"\n#include \"../io/vio.h\"\n\n#include <stdio.h>\n#include <string.h>\n#include <time.h>\n\nenum sauce_flags\n{\n  SAUCE_NO_BLINK      = (1<<0), // aka \"iCE colors\".\n  SAUCE_DEFAULT_FONT  = (0<<1),\n  SAUCE_8_PIXEL_FONT  = (1<<1),\n  SAUCE_9_PIXEL_FONT  = (2<<1),\n  SAUCE_DEFAULT_RATIO = (0<<3),\n  SAUCE_CLASSIC_RATIO = (1<<3),\n  SAUCE_MODERN_RATIO  = (2<<3),\n  // Flags that correspond (roughly) to how MZX displays graphics.\n  SAUCE_MZX_FLAGS = SAUCE_NO_BLINK | SAUCE_8_PIXEL_FONT | SAUCE_MODERN_RATIO,\n};\n\nstatic const char ansi_csi_prefix[3] = \"\\x1b[\";\n\n/**\n * Convert a reserved character into the closest CP-437 equivalent.\n *\n * @param chr     character to convert, 0-255.\n * @return        `chr`, or the closest equivalent if `chr` is reserved.\n */\nstatic int convert_reserved_character(int chr)\n{\n  switch(chr)\n  {\n    case '\\0':\n      return ' ';   // blank (nul) -> space\n    case '\\a':\n      return 249;   // bullet (bell) -> interpunct\n    case '\\b':\n      return 219;   // inverted bullet (backspace) -> block\n    case '\\t':\n      return 'o';   // circle (tab) -> lowercase o\n    case '\\n':\n      return 219;   // inverted cricle (line feed) -> block\n    /* TODO: MegaZeux wasn't stripping this, should it be?\n    case '\\f':\n      return 157;   // female sign (form feed) -> yen\n    */\n    case '\\r':\n      return 14;    // single 8th note (carriage return) -> double 8th note\n    /* TODO: need more investigation for this one. */\n    case 26:\n      return '-';   // right thin arrow (EOF for some implementations) -> dash\n    case 27:\n      return '-';   // left thin arrow (escape) -> dash\n  }\n  return chr;\n}\n\n/**\n * Output meta codes to transform the current color into the destination color.\n *\n * @param curr    color to change from.\n * @param dest    color to change to.\n * @param vf      vfile handle.\n * @return        size of the written meta code (0 if none), or -1 on error.\n */\nstatic ssize_t issue_color_meta_codes(int curr, int dest, vfile *vf)\n{\n  static const char ega_to_ansi_colors[8] =\n  {\n    '0', '4', '2', '6', '1', '5', '3', '7'\n  };\n\n  int size = 2;\n  boolean reset = false;\n\n  if(curr == dest)\n    return 0;\n\n  vfwrite(ansi_csi_prefix, 1, 2, vf);\n\n  if((curr & 128) && !(dest & 128))\n    reset = true;\n\n  if((curr & 8) && !(dest & 8))\n    reset = true;\n\n  if(reset)\n  {\n    curr = 7;\n    vfputc('0', vf);\n    size++;\n  }\n\n  if(dest & 128)\n  {\n    // Blink\n    if(size > 2)\n    {\n      vfputc(';', vf);\n      size++;\n    }\n    vfputc('5', vf);\n    size++;\n  }\n\n  if(dest & 8)\n  {\n    // Bold\n    if(size > 2)\n    {\n      vfputc(';', vf);\n      size++;\n    }\n    vfputc('1', vf);\n    size++;\n  }\n\n  if((curr & 7) != (dest & 7))\n  {\n    // FG color\n    if(size > 2)\n    {\n      vfputc(';', vf);\n      size++;\n    }\n    vfputc('3', vf);\n    vfputc(ega_to_ansi_colors[dest & 7], vf);\n    size += 2;\n  }\n\n  if((curr & 112) != (dest & 112))\n  {\n    // BG color\n    if(size > 2)\n    {\n      vfputc(';', vf);\n      size++;\n    }\n    vfputc('4', vf);\n    vfputc(ega_to_ansi_colors[(dest >> 4) & 7], vf);\n    size += 2;\n  }\n\n  if(vfputc('m', vf) < 0)\n    return -1;\n\n  return size;\n}\n\n/**\n * Export a block of ANSi (or plaintext) from the current board. This is\n * assumed to not go over the edges.\n *\n * @param mzx_world   MZX world.\n * @param filename    filename of ANSi or plaintext to write to.\n * @param start_x     position of block to write to ANSi.\n * @param start_y     position of block to write to ANSi.\n * @param width       width of block to write to ANSi.\n * @param height      height of block to write to ANSi.\n * @param text_only   `true`: write plaintext; `false`: write ANSi.\n * @return            `true` on success, otherwise `false`.\n */\nboolean export_ansi(struct world *mzx_world, const char *filename,\n enum editor_mode mode, int start_x, int start_y, int width, int height,\n boolean text_only, boolean doorway_mode, const char *title, const char *author)\n{\n  struct board *cur_board = mzx_world->current_board;\n  vfile *vf;\n  ssize_t color_size;\n  int x, y, line_size;\n  int terminal_width = -1;\n  int curr_color;\n  int col, chr, replace_chr;\n  int offset;\n  int board_width;\n  int line_skip;\n\n  trace(\"--ANSI-- export_ansi\\n\");\n\n  vf = vfopen_unsafe_ext(filename, \"wb\", V_SMALL_BUFFER);\n  if(!vf)\n    goto err;\n\n  /**\n   * NOTE: DOS versions limited the dimensions to 78x23 here but there's not\n   * much reason to do this anymore (terminals are variable width now).\n   */\n\n  if(mode == EDIT_VLAYER)\n    board_width = mzx_world->vlayer_width;\n  else\n    board_width = cur_board->board_width;\n\n  offset = start_x + start_y * board_width;\n  line_skip = board_width - width;\n\n  curr_color = 7;\n  line_size = 0;\n\n  if(!text_only)\n  {\n    vfwrite(ansi_csi_prefix, 1, 2, vf);\n    vfwrite(\"0m\", 1, 2, vf);\n    vfwrite(ansi_csi_prefix, 1, 2, vf);\n    vfwrite(\"2J\", 1, 2, vf);\n    line_size += 8;\n\n    if(doorway_mode)\n    {\n      /* Enable Doorway mode. */\n      vfwrite(ansi_csi_prefix, 1, 2, vf);\n      vfwrite(\"=255h\", 1, 5, vf);\n      line_size += 7;\n    }\n\n    /**\n     * TODO: it might be useful to allow a user-specified width here so e.g.\n     * line ends are inserted.\n     */\n    terminal_width = width;\n  }\n  else\n    doorway_mode = false;\n\n  // Longest meta code issuable is \\x1b[0;5;1;30;40m, or 14 characters.\n  for(y = 0; y < height; y++)\n  {\n    for(x = 0; x < width; x++, offset++)\n    {\n      switch(mode)\n      {\n        case EDIT_BOARD:\n          chr = get_id_char(cur_board, offset);\n          col = get_id_color(cur_board, offset);\n          break;\n\n        case EDIT_OVERLAY:\n          chr = cur_board->overlay[offset];\n          col = cur_board->overlay_color[offset];\n          break;\n\n        case EDIT_VLAYER:\n          chr = mzx_world->vlayer_chars[offset];\n          col = mzx_world->vlayer_colors[offset];\n          break;\n\n        default:\n          goto err;\n      }\n\n      if(!text_only)\n      {\n        color_size = issue_color_meta_codes(curr_color, col, vf);\n        if(color_size < 0)\n          goto err;\n        line_size += color_size;\n      }\n      curr_color = col;\n\n      // Print alternate characters for reserved chars.\n      replace_chr = convert_reserved_character(chr);\n      if(replace_chr != chr && doorway_mode)\n      {\n        /* Doorway mode: write nul to escape reserved character. */\n        vfputc(0, vf);\n        line_size++;\n      }\n      else\n        chr = replace_chr;\n\n      vfputc(chr, vf);\n      line_size++;\n\n      /**\n       * If the line length is near 256, use save/restore to start a new line.\n       * TODO: it's not 100% clear why this was added (it was present in 2.51)\n       * but it was likely due to a DOS ANSi viewer or editor using fixed size\n       * line buffer. More information on this would be great.\n       */\n      if(!text_only && line_size > 230)\n      {\n        // Issue save/restore\n        vfwrite(ansi_csi_prefix, 1, 2, vf);\n        vfputc('s', vf);\n        vfputc('\\r', vf);\n        vfputc('\\n', vf);\n        vfwrite(ansi_csi_prefix, 1, 2, vf);\n        vfputc('u', vf);\n        line_size = 3;\n      }\n    }\n    // Issue CR/LF if this isn't the edge of the terminal.\n    if(width != terminal_width)\n    {\n      vfputc('\\r', vf);\n      vfputc('\\n', vf);\n      line_size = 0;\n    }\n    offset += line_skip;\n  }\n\n  if(!text_only)\n  {\n    time_t t;\n    struct tm *tm;\n    long total_len;\n    char datebuf[9];\n    char buffer[192];\n\n    color_size = issue_color_meta_codes(curr_color, 7, vf);\n    if(color_size < 0)\n      goto err;\n\n    if(doorway_mode)\n    {\n      /* Disable Doorway mode. */\n      vfwrite(ansi_csi_prefix, 1, 2, vf);\n      vfwrite(\"=255l\", 1, 5, vf);\n    }\n\n    t = time(NULL);\n    tm = localtime(&t);\n    strftime(datebuf, ARRAY_SIZE(datebuf), \"%Y%m%d\", tm);\n\n    total_len = vftell(vf);\n\n// macOS makes snprintf a macro for some reason so define this separately.\n#ifdef VERSION_DATE\n#define COMMENT_LINE \"Created with MegaZeux \" VERSION VERSION_DATE \".\"\n#else\n#define COMMENT_LINE \"Created with MegaZeux \" VERSION \".\"\n#endif\n\n    // SAUCE record.\n    vfputc(0x1A, vf);\n    snprintf(buffer, ARRAY_SIZE(buffer),\n      \"COMNT%-64.64sSAUCE00%-35.35s%-20.20s%-20.20s%-8.8s\",\n      COMMENT_LINE, // Comment line 1.\n      title,\n      author,\n      \"\",     // Group\n      datebuf\n    );\n    vfputs(buffer, vf);\n    vfputd(total_len, vf);\n    vfputc(1, vf);                // DataType (Character)\n    vfputc(1, vf);                // FileType (ANSi)\n    vfputw(terminal_width, vf);   // TInfo1 (terminal width in characters)\n    vfputw(height, vf);           // TInfo2 (height in rows)\n    vfputw(0, vf);                // TInfo3\n    vfputw(0, vf);                // TInfo4\n    vfputc(1, vf);                // Comment line count.\n    vfputc(SAUCE_MZX_FLAGS, vf);  // TFlags\n    snprintf(buffer, ARRAY_SIZE(buffer),\n      \"%-22.22s\",\n      \"IBM EGA\"\n    );\n    vfputs(buffer, vf);           // TInfoS\n  }\n  vfclose(vf);\n  return true;\n\nerr:\n  if(vf)\n    vfclose(vf);\n\n  error_message(text_only ? E_TEXT_EXPORT : E_ANSI_EXPORT, 0, NULL);\n  return false;\n}\n\nenum ansi_eol_type\n{\n  EOL_UNKNOWN,\n  EOL_DOS,\n  EOL_UNIX,\n  EOL_MAC,\n};\n\nenum ansi_retvals\n{\n  ANSI_EOF    = -1,\n  ANSI_ESCAPE = -2,\n  ANSI_ERROR  = -3,\n};\n\nenum ansi_erase\n{\n  ANSI_NO_ERASE       = -1,\n  ANSI_ERASE_TO_END   = 0,\n  ANSI_ERASE_TO_START = 1,\n  ANSI_ERASE_ALL      = 2,\n};\n\nstruct ansi_data\n{\n  int color;\n  int x;\n  int y;\n  int terminal_width;\n  int scan_width;\n  int scan_height;\n  int saved_x;\n  int saved_y;\n  enum ansi_eol_type eol;\n  enum ansi_erase erase_display;\n  enum ansi_erase erase_line;\n  boolean is_scan;\n  boolean doorway_mode;\n};\n\nstatic const struct ansi_data default_state =\n{\n  7,  // Color\n  0,  // x\n  0,  // y\n  -1, // terminal_width\n  0,  // scan_width\n  0,  // scan_height,\n  0,  // saved_x\n  0,  // saved_y,\n  EOL_UNKNOWN,\n  ANSI_NO_ERASE,\n  ANSI_NO_ERASE,\n  false,\n  false\n};\n\n/**\n * Apply an SGR (Select Graphic Rendition) parameter. Only colors are handled.\n *\n * @param ansi    ANSi data.\n * @param param   SGR parameter to apply.\n */\nstatic void apply_ansi_sgr_code(struct ansi_data *ansi, int param)\n{\n  static const int ansi_to_ega_color[8] =\n  {\n    0, 4, 2, 6, 1, 5, 3, 7\n  };\n\n  switch(param)\n  {\n    case 0:\n      ansi->color = 7;\n      break;\n\n    case 1:\n      // Bold enable.\n      ansi->color |= 8;\n      break;\n\n    case 5:\n      // Blink enable.\n      ansi->color |= 128;\n      break;\n\n    case 30:\n    case 31:\n    case 32:\n    case 33:\n    case 34:\n    case 35:\n    case 36:\n    case 37:\n      // FG color.\n      ansi->color = (ansi->color & 248) | ansi_to_ega_color[param - 30];\n      break;\n\n    case 40:\n    case 41:\n    case 42:\n    case 43:\n    case 44:\n    case 45:\n    case 46:\n    case 47:\n      // BG color.\n      ansi->color = (ansi->color & 143) | (ansi_to_ega_color[param - 40] << 4);\n      break;\n  }\n}\n\n/**\n * Read a single char or escape code from an ANSI file.\n *\n * @param ansi  ANSi data.\n * @param vf    vfile handle.\n * @return      -1 for EOF, -2 for escape code, -3 for error, otherwise a char.\n */\nstatic int read_ansi(struct ansi_data *ansi, vfile *vf)\n{\n  int sym = vfgetc(vf);\n  if(sym < 0)\n    return ANSI_EOF;\n\n  if(ansi->terminal_width > 0 && ansi->x >= ansi->terminal_width && (sym == '\\r' || sym == '\\n'))\n  {\n    trace(\"--ANSI-- line end at %d (term width %d)\\n\", ansi->x, ansi->terminal_width);\n  }\n\n  if(ansi->eol == EOL_UNKNOWN && (sym == '\\r' || sym == '\\n'))\n  {\n    int tmp = vfgetc(vf);\n    if(sym == '\\r' && tmp == '\\n')\n    {\n      trace(\"--ANSI-- Detected DOS line ends.\\n\");\n      ansi->eol = EOL_DOS;\n    }\n    else\n\n    if(sym == '\\r')\n    {\n      trace(\"--ANSI-- Detected MAC line ends.\\n\");\n      ansi->eol = EOL_MAC;\n    }\n    else\n    {\n      trace(\"--ANSI-- Detected Unix line ends.\\n\");\n      ansi->eol = EOL_UNIX;\n    }\n\n    vungetc(tmp, vf);\n  }\n\n  if(sym == '\\r')\n  {\n    ansi->x = 0;\n    if(ansi->eol == EOL_MAC)\n      ansi->y++;\n\n    return ANSI_ESCAPE;\n  }\n  else\n\n  if(sym == '\\n')\n  {\n    if(ansi->eol == EOL_UNIX)\n      ansi->x = 0;\n\n    ansi->y++;\n    return ANSI_ESCAPE;\n  }\n  else\n\n  // Nul (escape if Doorway mode is active; otherwise print a space).\n  if(sym == '\\0')\n  {\n    if(ansi->doorway_mode)\n    {\n      sym = vfgetc(vf);\n      return sym >= 0 ? sym : ANSI_EOF;\n    }\n  }\n  else\n\n  // TODO: handle bell/backspace/form feed/tab instead of printing them?\n\n  if(sym == 0x1A)\n  {\n    // TODO: TYPE treats this as an unconditional EOF, PabloDraw refuses to\n    // load it, and other sources claim it's just a display character.\n    return ANSI_EOF;\n  }\n  else\n\n  // ANSi escape sequence.\n  if(sym == 0x1B)\n  {\n    if(ansi->eol != EOL_DOS)\n    {\n      trace(\"--ANSI-- Detected ANSi, forcing DOS line ends.\\n\");\n      ansi->eol = EOL_DOS;\n    }\n\n    sym = vfgetc(vf);\n    if(sym == '[')\n    {\n      /**\n       * CSI sequence.\n       *\n       * TODO: not following the spec, which is:\n       * Format: any number of characters 0x30 - 0x3f;\n       *         any number of characters 0x20 - 0x2f;\n       *         terminating byte         0x40 - 0x7e;\n       *         any sequence which includes <=>? or ends in p-z{|}~ is\n       *         \"private\" (the only supported one here is Doorway mode).\n       *         If the parameter doesn't start with <=>?, it should only\n       *         contain 0-9 and ;.\n       */\n      int param[32] =\n      {\n        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\n      };\n      int cur_param = 0;\n      int i;\n\n      sym = vfgetc(vf);\n      while(1)\n      {\n        if(sym == '=')\n        {\n          // The only extension commands supported in this format are\n          // =255h (begin Doorway mode) and =255l (end Doorway mode).\n          if(vfgetc(vf) != '2')\n            return ANSI_ERROR;\n          if(vfgetc(vf) != '5')\n            return ANSI_ERROR;\n          if(vfgetc(vf) != '5')\n            return ANSI_ERROR;\n\n          sym = vfgetc(vf);\n          if(sym == 'h')\n          {\n            trace(\"--ANSI-- enabled Doorway mode.\\n\");\n            ansi->doorway_mode = true;\n            return ANSI_ESCAPE;\n          }\n          else\n\n          if(sym == 'l')\n          {\n            trace(\"--ANSI-- disabled Doorway mode.\\n\");\n            ansi->doorway_mode = false;\n            return ANSI_ESCAPE;\n          }\n          else\n            return ANSI_ERROR;\n        }\n        else\n\n        if(sym == ';')\n        {\n          // End param.\n          if(param[cur_param] < 0)\n            return ANSI_ERROR;\n\n          if(cur_param >= (int)ARRAY_SIZE(param))\n            return ANSI_ERROR;\n\n          cur_param++;\n          sym = vfgetc(vf);\n          continue;\n        }\n        else\n\n        if(sym == 'm')\n        {\n          for(i = 0; i <= cur_param; i++)\n            apply_ansi_sgr_code(ansi, param[i]);\n\n          return ANSI_ESCAPE;\n        }\n        else\n\n        if(sym == 's')\n        {\n          // Save position.\n          ansi->saved_x = ansi->x;\n          ansi->saved_y = ansi->y;\n          return ANSI_ESCAPE;\n        }\n        else\n\n        if(sym == 'u')\n        {\n          // Restore position.\n          ansi->x = ansi->saved_x;\n          ansi->y = ansi->saved_y;\n          return ANSI_ESCAPE;\n        }\n        else\n\n        if(sym == 'J')\n        {\n          // Erase display.\n          param[0] = CLAMP(param[0], 0, 2);\n\n          ansi->erase_display = param[0];\n          return ANSI_ESCAPE;\n        }\n        else\n\n        if(sym == 'K')\n        {\n          // Erase line.\n          param[0] = CLAMP(param[0], 0, 2);\n\n          ansi->erase_line = param[0];\n          return ANSI_ESCAPE;\n        }\n        else\n\n        if(sym == 'A' || sym == 'F')\n        {\n          // Move up (F is non-standard).\n          if(sym == 'F')\n            ansi->x = 0;\n\n          if(param[0] <= 0)\n            param[0] = 1;\n\n          if(ansi->y >= param[0])\n            ansi->y -= param[0];\n          else\n            ansi->y = 0;\n\n          return ANSI_ESCAPE;\n        }\n        else\n\n        if(sym == 'B' || sym == 'E')\n        {\n          // Move down (E is non-standard).\n          if(sym == 'E')\n            ansi->x = 0;\n\n          if(param[0] <= 0)\n            param[0] = 1;\n\n          ansi->y += param[0];\n          return ANSI_ESCAPE;\n        }\n        else\n\n        if(sym == 'C')\n        {\n          // Move right.\n          if(param[0] <= 0)\n            param[0] = 1;\n\n          ansi->x += param[0];\n          return ANSI_ESCAPE;\n        }\n        else\n\n        if(sym == 'D')\n        {\n          // Move left.\n          if(param[0] <= 0)\n            param[0] = 1;\n\n          if(ansi->x >= param[0])\n            ansi->x -= param[0];\n          else\n            ansi->x = 0;\n\n          return ANSI_ESCAPE;\n        }\n        else\n\n        if(sym == 'G')\n        {\n          // Cursor column (non-standard).\n          if(param[0] <= 0)\n            param[0] = 1;\n\n          ansi->x = param[0] - 1;\n          return ANSI_ESCAPE;\n        }\n        else\n\n        if(sym == 'H' || sym == 'f')\n        {\n          // Cursor position.\n          if(param[0] <= 0)\n            param[0] = 1;\n          if(param[1] <= 0)\n            param[1] = 1;\n\n          ansi->x = param[0] - 1;\n          ansi->y = param[1] - 1;\n          return ANSI_ESCAPE;\n        }\n        else\n\n        // Ignore unsupported CSI sequences.\n        if((sym < '0') || (sym > '9'))\n          return ANSI_ESCAPE;\n\n        // Read parameter.\n        param[cur_param] = 0;\n        while(sym >= '0' && sym <= '9')\n        {\n          param[cur_param] = (param[cur_param] * 10) + (sym - '0');\n          sym = vfgetc(vf);\n        }\n      }\n    }\n  }\n  return sym;\n}\n\n/**\n * Read SAUCE from an incoming file, if present.\n * `ansi->terminal_width` should be initialized prior to calling this function.\n * This function will set `ansi->terminal_width` depending on the SAUCE width\n * and the initial value of `ansi->terminal_width`. If no SAUCE record is found,\n * no values in the ANSi data will be modified.\n *\n * @param ansi  ANSi data.\n * @param vf    vfile handle.\n * @return      `true` if relevant SAUCE data was found, otherwise `false`.\n */\nstatic boolean read_sauce(struct ansi_data *ansi, vfile *vf)\n{\n  long file_len = vfilelength(vf, false);\n  char magic[7];\n\n  trace(\"--ANSI-- read_sauce\\n\");\n\n  // Check for a SAUCE record. If found and valid, prefer its requested\n  // wrap width over the provided wrap width.\n  if(file_len > 128)\n  {\n    vfseek(vf, file_len - 128, SEEK_SET);\n\n    if(vfread(magic, 7, 1, vf) && !strncmp(magic, \"SAUCE00\", 7))\n    {\n      int data_type;\n      int file_type;\n      int sauce_width;\n      int sauce_height;\n\n      // Skip optional fields.\n      vfseek(vf, 35+20+20+8+4, SEEK_CUR);\n\n      data_type = vfgetc(vf);\n      file_type = vfgetc(vf);\n      sauce_width = vfgetw(vf);\n      sauce_height = vfgetw(vf);\n\n      if(sauce_height >= 0 && data_type == 1 /* Character */ && file_type == 1 /* ANSi */)\n      {\n        /*\n        // The width value is the terminal width and the line count is\n        // apparently unreliable, so don't set the scan dimensions from either.\n        ansi->scan_width = sauce_width;\n        ansi->scan_height = sauce_height;\n        */\n        trace(\"--ANSI-- SAUCE width=%d, height=%d\\n\", sauce_width, sauce_height);\n\n        if(sauce_width &&\n         (ansi->terminal_width < 1 || ansi->terminal_width > sauce_width))\n        {\n          ansi->terminal_width = sauce_width;\n          trace(\"--ANSI-- using SAUCE width as ANSi terminal width.\\n\");\n        }\n        else\n\n        if(!sauce_width && ansi->terminal_width < 1)\n        {\n          ansi->terminal_width = 80;\n          trace(\"--ANSI-- SAUCE record with width 0--using default width \"\n           \"of 80 as ANSi terminal width.\\n\");\n        }\n        return true;\n      }\n    }\n  }\n  trace(\"--ANSI-- no relevant SAUCE data found.\\n\");\n  return false;\n}\n\n/**\n * Test an ANSi file for correctness and determine its output dimensions.\n * The expected output dimensions are required to correctly load the ANSi later.\n *\n * @param filename    filename of ANSi or plaintext file.\n * @param wrap_width  force line wrap after this many columsn (<=0 for none).\n * @param width       pointer to store width at.\n * @param height      pointer to store height at.\n * @return            `true` if the ANSi file is valid, otherwise `false`.\n */\nboolean validate_ansi(const char *filename, int wrap_width, int *width, int *height)\n{\n  struct ansi_data ansi = default_state;\n  vfile *vf;\n  int chr;\n\n  trace(\"--ANSI-- validate_ansi\\n\");\n\n  *width = -1;\n  *height = -1;\n\n  vf = vfopen_unsafe_ext(filename, \"rb\", V_SMALL_BUFFER);\n  if(!vf)\n    goto err;\n\n  ansi.is_scan = true;\n  ansi.terminal_width = wrap_width;\n\n  read_sauce(&ansi, vf);\n  vrewind(vf);\n\n  while(1)\n  {\n    chr = read_ansi(&ansi, vf);\n    if(chr == ANSI_EOF)\n      break;\n\n    if(chr == ANSI_ERROR)\n      goto err;\n\n    if(chr >= 0)\n    {\n      if(ansi.x + 1 > ansi.scan_width)\n        ansi.scan_width = ansi.x + 1;\n      if(ansi.y + 1 > ansi.scan_height)\n        ansi.scan_height = ansi.y + 1;\n\n      ansi.x++;\n    }\n\n    if(ansi.terminal_width > 0 && ansi.x >= ansi.terminal_width)\n    {\n      ansi.x = 0;\n      ansi.y++;\n    }\n  }\n  trace(\"--ANSI-- scan width=%d, height=%d\\n\", ansi.scan_width, ansi.scan_height);\n\n  if(ansi.terminal_width <= 0 || ansi.scan_width < ansi.terminal_width)\n  {\n    /**\n     * If no terminal width was used, a line end was present at the end of the\n     * longest line. Add one to the width to avoid line wrapping. This should\n     * only affect TXT files or ANSi files in the case where 1) no SAUCE record\n     * exists and 2) a wrap width wasn't explicitly provided by the user.\n     */\n    trace(\"--ANSI-- adding 1 to final width to avoid line wrapping: %d, %d\\n\",\n     ansi.scan_width + 1, ansi.scan_height);\n    ansi.scan_width++;\n  }\n\n  vfclose(vf);\n  *width = ansi.scan_width;\n  *height = ansi.scan_height;\n  return true;\n\nerr:\n  if(vf)\n    vfclose(vf);\n\n  error_message(E_ANSI_IMPORT, 0, NULL);\n  return false;\n}\n\n/**\n * Import an ANSi or text graphic into the board as the selected type.\n * Undo history should be managed separately for this block. This function\n * clips the ANSi to the size of the destination board/vlayer.\n *\n * @param mzx_world   MZX world.\n * @param filename    filename of ANSi or plaintext file.\n * @param mode        location to load ANSi to (board, overlay, or vlayer).\n * @param start_x     position to load ANSi to.\n * @param start_y     position to load ANSi to.\n * @param width       width of the ANSi to load (see `validate_ansi`).\n *                    Note this isn't the width of the area to load to.\n * @param height      height of the ANSi to load (see `validate_ansi`).\n *                    Note this isn't the height of the area to load to.\n * @param convert_id  thing to load ANSi chars as (board only).\n * @return            `true` on success, otherwise `false`.\n */\nboolean import_ansi(struct world *mzx_world, const char *filename,\n enum editor_mode mode, int start_x, int start_y, int width, int height,\n enum thing convert_id)\n{\n  struct ansi_data ansi = default_state;\n  struct buffer_info ansi_buffer;\n  struct buffer_info clear_buffer;\n  struct board *cur_board = mzx_world->current_board;\n  int board_width = cur_board->board_width;\n  int board_height = cur_board->board_height;\n  int wrap_width = width;\n  boolean need_erase = true;\n  vfile *vf;\n  int chr;\n\n  trace(\"--ANSI-- import_ansi\\n\");\n\n  // Bound the area to the board (or vlayer).\n  if(mode == EDIT_VLAYER)\n  {\n    board_width = mzx_world->vlayer_width;\n    board_height = mzx_world->vlayer_height;\n  }\n\n  if(start_x >= board_width || start_y >= board_height ||\n   start_x < 0 || start_y < 0 || width < 1 || height < 1)\n    return false;\n\n  trace(\"--ANSI-- image is %d by %d\\n\", width, height);\n\n  if(start_x + width > board_width)\n    width = board_width - start_x;\n\n  if(start_y + height > board_height)\n    height = board_height - start_y;\n\n  memset(&ansi_buffer, 0, sizeof(struct buffer_info));\n  memset(&clear_buffer, 0, sizeof(struct buffer_info));\n\n  if(!is_storageless(convert_id))\n    convert_id = CUSTOM_BLOCK;\n\n  ansi_buffer.id = convert_id;\n  ansi_buffer.color = ansi.color;\n  clear_buffer.id = convert_id;\n  clear_buffer.color = 7;\n  clear_buffer.param = ' ';\n\n  vf = vfopen_unsafe_ext(filename, \"rb\", V_SMALL_BUFFER);\n  if(!vf)\n    goto err;\n\n  if(!read_sauce(&ansi, vf))\n  {\n    // If no SAUCE terminal width is found, the provided width from the scan\n    // should be the correct width.\n    ansi.terminal_width = wrap_width;\n  }\n  vrewind(vf);\n\n  trace(\"--ANSI-- importing @ %d,%d (%d by %d) with terminal width=%d\\n\",\n   start_x, start_y, width, height, ansi.terminal_width);\n\n  while(1)\n  {\n    if(need_erase)\n    {\n      // MZX ANSI files contain an initial display clear but many others do not.\n      // Inject an erase display escape at the start of the file so the drawing\n      // area is guaranteed to be filled with the selected ID.\n      ansi.erase_display = ANSI_ERASE_ALL;\n      chr = ANSI_ESCAPE;\n      need_erase = false;\n    }\n    else\n      chr = read_ansi(&ansi, vf);\n\n    if(chr == ANSI_EOF)\n      break;\n\n    if(chr == ANSI_ERROR) // Shouldn't happen.\n      goto err;\n\n    if(chr == ANSI_ESCAPE)\n    {\n      if(ansi.erase_display != ANSI_NO_ERASE)\n      {\n        int x1 = 0;\n        int x2 = width - 1;\n        int y1 = 0;\n        int y2 = height - 1;\n\n        switch(ansi.erase_display)\n        {\n          case ANSI_ERASE_TO_START:\n            x2 = ansi.x;\n            y2 = ansi.y;\n            break;\n          case ANSI_ERASE_TO_END:\n            x1 = ansi.x;\n            y1 = ansi.y;\n            break;\n          default:\n            ansi.x = 0;\n            ansi.y = 0;\n            break;\n        }\n\n        while(1)\n        {\n          if(x1 < width && y1 < height)\n          {\n            place_current_at_xy(mzx_world, &clear_buffer,\n             start_x + x1, start_y + y1, mode, NULL);\n          }\n\n          if(x1 >= x2)\n          {\n            if(y1 >= y2)\n              break;\n            x1 = 0;\n            y1++;\n          }\n          else\n            x1++;\n        }\n      }\n      else\n\n      if(ansi.erase_line != ANSI_NO_ERASE)\n      {\n        int x1 = (ansi.erase_line == ANSI_ERASE_TO_END) ? ansi.x : 0;\n        int x2 = (ansi.erase_line == ANSI_ERASE_TO_START) ? ansi.x : width - 1;\n\n        while(x1 <= x2)\n        {\n          if(x1 < width && ansi.y < height)\n            place_current_at_xy(mzx_world, &clear_buffer,\n             start_x + x1, start_y + ansi.y, mode, NULL);\n          x1++;\n        }\n      }\n\n      ansi_buffer.color = ansi.color;\n      ansi.erase_line = ANSI_NO_ERASE;\n      ansi.erase_display = ANSI_NO_ERASE;\n    }\n    else\n    {\n      if(ansi.x < width && ansi.y < height)\n      {\n        ansi_buffer.param = chr;\n\n        if(place_current_at_xy(mzx_world, &ansi_buffer,\n         start_x + ansi.x, start_y + ansi.y, mode, NULL) == -1)\n        {\n          goto err; // Shouldn't ever happen--can't place storage objects.\n        }\n      }\n      ansi.x++;\n    }\n\n    if(ansi.terminal_width > 0 && ansi.x >= ansi.terminal_width)\n    {\n      //trace(\"force wrap at %d,%d\\n\", ansi.x, ansi.y);\n      ansi.x = 0;\n      ansi.y++;\n    }\n  }\n  vfclose(vf);\n  return true;\n\nerr:\n  if(vf)\n    vfclose(vf);\n\n  error_message(E_ANSI_IMPORT, 0, NULL);\n  return false;\n}\n"
  },
  {
    "path": "src/editor/ansi.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n */\n\n#ifndef __EDITOR_ANSI_H\n#define __EDITOR_ANSI_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"edit.h\"\n\nboolean validate_ansi(const char *filename, int wrap_width, int *width, int *height);\n\nboolean import_ansi(struct world *mzx_world, const char *filename,\n enum editor_mode mode, int start_x, int start_y, int width, int height,\n enum thing convert_id);\n\nboolean export_ansi(struct world *mzx_world, const char *filename,\n enum editor_mode mode, int start_x, int start_y, int width, int height,\n boolean text_only, boolean doorway_mode, const char *title, const char *author);\n\n__M_END_DECLS\n\n#endif /* __EDITOR_ANSI_H */\n"
  },
  {
    "path": "src/editor/block.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Block actions */\n/* For the selection dialogs that used to be called \"block.c\", see select.c */\n\n#include <string.h>\n\n#include \"ansi.h\"\n#include \"block.h\"\n#include \"undo.h\"\n\n#include \"../block.h\"\n#include \"../core.h\"\n#include \"../data.h\"\n#include \"../event.h\"\n#include \"../extmem.h\"\n#include \"../mzm.h\"\n#include \"../robot.h\"\n#include \"../window.h\"\n#include \"../world_struct.h\"\n\nstatic void clear_layer_block(\n char *dest_char, char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height)\n{\n  int dest_skip = dest_width - block_width;\n  int i, i2;\n\n  for(i = 0; i < block_height; i++)\n  {\n    for(i2 = 0; i2 < block_width; i2++)\n    {\n      dest_char[dest_offset] = 32;\n      dest_color[dest_offset] = 7;\n      dest_offset++;\n    }\n\n    dest_offset += dest_skip;\n  }\n}\n\nstatic void clear_board_block(struct board *dest_board, int dest_offset,\n int block_width, int block_height)\n{\n  char *level_id = dest_board->level_id;\n  char *level_param = dest_board->level_param;\n  char *level_color = dest_board->level_color;\n  char *level_under_id = dest_board->level_under_id;\n  char *level_under_param = dest_board->level_under_param;\n  char *level_under_color = dest_board->level_under_color;\n\n  int dest_width = dest_board->board_width;\n  int dest_skip = dest_width - block_width;\n\n  enum thing dest_id;\n  int dest_param;\n  int i, i2;\n\n  for(i = 0; i < block_height; i++)\n  {\n    for(i2 = 0; i2 < block_width; i2++)\n    {\n      dest_id = (enum thing)level_id[dest_offset];\n      if(dest_id != PLAYER)\n      {\n        dest_param = level_param[dest_offset];\n\n        if(dest_id == SENSOR)\n        {\n          clear_sensor_id(dest_board, dest_param);\n        }\n        else\n\n        if(is_signscroll(dest_id))\n        {\n          clear_scroll_id(dest_board, dest_param);\n        }\n        else\n\n        if(is_robot(dest_id))\n        {\n          clear_robot_id(dest_board, dest_param);\n        }\n\n        level_id[dest_offset] = (char)SPACE;\n        level_param[dest_offset] = 0;\n        level_color[dest_offset] = 7;\n      }\n\n      level_under_id[dest_offset] = (char)SPACE;\n      level_under_param[dest_offset] = 0;\n      level_under_color[dest_offset] = 7;\n\n      dest_offset++;\n    }\n\n    dest_offset += dest_skip;\n  }\n}\n\nstatic void flip_layer_block(\n char *dest_char, char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height)\n{\n  char *buffer = cmalloc(sizeof(char) * block_width);\n\n  int start_offset = dest_offset;\n  int end_offset = dest_offset + dest_width * (block_height - 1);\n\n  while(start_offset < end_offset)\n  {\n    memcpy(buffer, dest_char + start_offset,\n     block_width);\n    memcpy(dest_char + start_offset, dest_char + end_offset,\n     block_width);\n    memcpy(dest_char + end_offset, buffer,\n     block_width);\n\n    memcpy(buffer, dest_color + start_offset,\n     block_width);\n    memcpy(dest_color + start_offset, dest_color + end_offset,\n     block_width);\n    memcpy(dest_color + end_offset, buffer,\n     block_width);\n\n    start_offset += dest_width;\n    end_offset -= dest_width;\n  }\n\n  free(buffer);\n}\n\nstatic void flip_board_block(struct board *dest_board, int dest_offset,\n int block_width, int block_height)\n{\n  char *buffer = cmalloc(sizeof(char) * block_width);\n\n  char *level_id = dest_board->level_id;\n  char *level_color = dest_board->level_color;\n  char *level_param = dest_board->level_param;\n  char *level_under_id = dest_board->level_under_id;\n  char *level_under_color = dest_board->level_under_color;\n  char *level_under_param = dest_board->level_under_param;\n\n  int dest_width = dest_board->board_width;\n\n  int start_offset = dest_offset;\n  int end_offset = dest_offset + dest_width * (block_height - 1);\n\n  while(start_offset < end_offset)\n  {\n    memcpy(buffer, level_id + start_offset,\n     block_width);\n    memcpy(level_id + start_offset, level_id + end_offset,\n     block_width);\n    memcpy(level_id + end_offset, buffer,\n     block_width);\n\n    memcpy(buffer, level_color + start_offset,\n     block_width);\n    memcpy(level_color + start_offset, level_color + end_offset,\n     block_width);\n    memcpy(level_color + end_offset, buffer,\n     block_width);\n\n    memcpy(buffer, level_param + start_offset,\n     block_width);\n    memcpy(level_param + start_offset,\n     level_param + end_offset, block_width);\n    memcpy(level_param + end_offset, buffer,\n     block_width);\n\n    memcpy(buffer, level_under_id + start_offset,\n     block_width);\n    memcpy(level_under_id + start_offset, level_under_id + end_offset,\n     block_width);\n    memcpy(level_under_id + end_offset, buffer,\n     block_width);\n\n    memcpy(buffer, level_under_color + start_offset,\n     block_width);\n    memcpy(level_under_color + start_offset, level_under_color + end_offset,\n     block_width);\n    memcpy(level_under_color + end_offset, buffer,\n     block_width);\n\n    memcpy(buffer, level_under_param + start_offset,\n     block_width);\n    memcpy(level_under_param + start_offset, level_under_param + end_offset,\n     block_width);\n    memcpy(level_under_param + end_offset, buffer,\n     block_width);\n\n    start_offset += dest_width;\n    end_offset -= dest_width;\n  }\n\n  free(buffer);\n}\n\nstatic void mirror_layer_block(\n char *dest_char, char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height)\n{\n  char t;\n  int i;\n  int a;\n  int b;\n\n  for(i = 0; i < block_height; i++)\n  {\n    a = dest_offset;\n    b = dest_offset + block_width - 1;\n\n    while(a < b)\n    {\n      t = dest_char[a];\n      dest_char[a] = dest_char[b];\n      dest_char[b] = t;\n\n      t = dest_color[a];\n      dest_color[a] = dest_color[b];\n      dest_color[b] = t;\n\n      a++;\n      b--;\n    }\n\n    dest_offset += dest_width;\n  }\n}\n\nstatic void mirror_board_block(struct board *dest_board, int dest_offset,\n int block_width, int block_height)\n{\n  char *level_id = dest_board->level_id;\n  char *level_color = dest_board->level_color;\n  char *level_param = dest_board->level_param;\n  char *level_under_id = dest_board->level_under_id;\n  char *level_under_color = dest_board->level_under_color;\n  char *level_under_param = dest_board->level_under_param;\n\n  int dest_width = dest_board->board_width;\n\n  char t;\n  int i;\n  int a;\n  int b;\n\n  for(i = 0; i < block_height; i++)\n  {\n    a = dest_offset;\n    b = dest_offset + block_width - 1;\n\n    while(a < b)\n    {\n      t = level_id[a];\n      level_id[a] = level_id[b];\n      level_id[b] = t;\n\n      t = level_color[a];\n      level_color[a] = level_color[b];\n      level_color[b] = t;\n\n      t = level_param[a];\n      level_param[a] = level_param[b];\n      level_param[b] = t;\n\n      t = level_under_id[a];\n      level_under_id[a] = level_under_id[b];\n      level_under_id[b] = t;\n\n      t = level_under_color[a];\n      level_under_color[a] = level_under_color[b];\n      level_under_color[b] = t;\n\n      t = level_under_param[a];\n      level_under_param[a] = level_under_param[b];\n      level_under_param[b] = t;\n\n      a++;\n      b--;\n    }\n\n    dest_offset += dest_width;\n  }\n}\n\nstatic void paint_layer_block(char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height, int paint_color)\n{\n  int dest_skip = dest_width - block_width;\n  int i;\n  int i2;\n\n  for(i = 0; i < block_height; i++)\n  {\n    for(i2 = 0; i2 < block_width; i2++)\n    {\n      dest_color[dest_offset] = paint_color;\n      dest_offset++;\n    }\n\n    dest_offset += dest_skip;\n  }\n}\n\nvoid copy_layer_buffer_to_buffer(\n char *src_char, char *src_color, int src_width, int src_offset,\n char *dest_char, char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height)\n{\n  int src_skip = src_width - block_width;\n  int dest_skip = dest_width - block_width;\n  int i, i2;\n\n  // Like a direct layer-to-layer copy, but without the char 32 checks\n  for(i = 0; i < block_height; i++)\n  {\n    for(i2 = 0; i2 < block_width; i2++)\n    {\n      dest_char[dest_offset] = src_char[src_offset];\n      dest_color[dest_offset] = src_color[src_offset];\n\n      src_offset++;\n      dest_offset++;\n    }\n\n    src_offset += src_skip;\n    dest_offset += dest_skip;\n  }\n}\n\nstatic void move_layer_block(\n char *src_char, char *src_color, int src_width, int src_offset,\n char *dest_char, char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height,\n int clear_width, int clear_height)\n{\n  // Similar to copy_layer_to_layer_buffered, but deletes the source\n  // after copying to the buffer.\n\n  char *buffer_char = cmalloc(block_width * block_height);\n  char *buffer_color = cmalloc(block_width * block_height);\n\n  // Copy source to buffer\n  copy_layer_buffer_to_buffer(\n   src_char, src_color, src_width, src_offset,\n   buffer_char, buffer_color, block_width, 0,\n   block_width, block_height);\n\n  // Clear the source\n  clear_layer_block(\n   src_char, src_color, src_width, src_offset,\n   clear_width, clear_height);\n\n  // Copy buffer to destination\n  copy_layer_buffer_to_buffer(\n   buffer_char, buffer_color, block_width, 0,\n   dest_char, dest_color, dest_width, dest_offset,\n   block_width, block_height);\n\n  free(buffer_char);\n  free(buffer_color);\n}\n\nvoid do_block_command(struct world *mzx_world, struct block_info *block,\n struct undo_history *dest_history, char *mzm_name_buffer, int current_color)\n{\n  // Source is optional and mostly only matters for copy/move\n  struct board *src_board = NULL;\n  char *src_char = NULL;\n  char *src_color = NULL;\n  int src_width = 0;\n  int src_offset;\n  int src_x = block->src_x;\n  int src_y = block->src_y;\n\n  struct board *dest_board;\n  char *dest_char;\n  char *dest_color;\n  int dest_width;\n  int dest_height;\n  int dest_offset;\n  int dest_x = block->dest_x;\n  int dest_y = block->dest_y;\n\n  // History size is generally the block size, but move block adds complications\n  int history_x = block->dest_x;\n  int history_y = block->dest_y;\n  int history_width;\n  int history_height;\n  int history_offset;\n\n  // Trim the block size to fit the destination, but keep the old\n  // size so the old block can be cleared during the move command\n  int block_width = block->width;\n  int block_height = block->height;\n  int dest_block_width = block_width;\n  int dest_block_height = block_height;\n  boolean loaded_src_board = false;\n\n  if(block->command == BLOCK_CMD_NONE)\n    return;\n\n  // Set up source\n  switch(block->src_mode)\n  {\n    case EDIT_BOARD:\n      if(block->src_board == NULL) break;\n      if(block->src_board != mzx_world->current_board)\n      {\n        retrieve_board_from_extram(block->src_board);\n        loaded_src_board = true;\n      }\n      src_board = block->src_board;\n      src_char = NULL;\n      src_color = src_board->level_color;\n      src_width = src_board->board_width;\n      break;\n\n    case EDIT_OVERLAY:\n      if(block->src_board == NULL) break;\n      src_board = block->src_board;\n      src_char = src_board->overlay;\n      src_color = src_board->overlay_color;\n      src_width = src_board->board_width;\n      break;\n\n    case EDIT_VLAYER:\n      src_board = NULL;\n      src_char = mzx_world->vlayer_chars;\n      src_color = mzx_world->vlayer_colors;\n      src_width = mzx_world->vlayer_width;\n      break;\n\n    default:\n      return;\n  }\n\n  // Set up destination\n  switch(block->dest_mode)\n  {\n    case EDIT_BOARD:\n      dest_board = mzx_world->current_board;\n      dest_char = NULL;\n      dest_color = dest_board->level_color;\n      dest_width = dest_board->board_width;\n      dest_height = dest_board->board_height;\n      break;\n\n    case EDIT_OVERLAY:\n      dest_board = mzx_world->current_board;\n      dest_char = dest_board->overlay;\n      dest_color = dest_board->overlay_color;\n      dest_width = dest_board->board_width;\n      dest_height = dest_board->board_height;\n      break;\n\n    case EDIT_VLAYER:\n      dest_board = NULL;\n      dest_char = mzx_world->vlayer_chars;\n      dest_color = mzx_world->vlayer_colors;\n      dest_width = mzx_world->vlayer_width;\n      dest_height = mzx_world->vlayer_height;\n      break;\n\n    default:\n      return;\n  }\n\n  // Trim the block for the destination\n  if((dest_x + dest_block_width) > dest_width)\n    dest_block_width = dest_width - dest_x;\n\n  if((dest_y + dest_block_height) > dest_height)\n    dest_block_height = dest_height - dest_y;\n\n  history_width = dest_block_width;\n  history_height = dest_block_height;\n\n  // In-place move block adds some complications to history.\n  // For now, just save the smallest possible space containing both blocks.\n  if((block->command == BLOCK_CMD_MOVE) &&\n   (block->src_mode == block->dest_mode) &&\n   ((block->src_mode == EDIT_VLAYER) || (src_board == dest_board)))\n  {\n    if(src_x > dest_x)\n    {\n      history_x = dest_x;\n      history_width = dest_block_width + src_x - dest_x;\n    }\n    else\n    {\n      history_x = src_x;\n      history_width = dest_block_width + dest_x - src_x;\n    }\n\n    if(src_y > dest_y)\n    {\n      history_y = dest_y;\n      history_height = dest_block_height + src_y - dest_y;\n    }\n    else\n    {\n      history_y = src_y;\n      history_height = dest_block_height + dest_y - src_y;\n    }\n  }\n\n  src_offset = src_x + (src_y * src_width);\n  dest_offset = dest_x + (dest_y * dest_width);\n  history_offset = history_x + (history_y * dest_width);\n\n  // Perform the block action\n  if(block->dest_mode == EDIT_BOARD)\n  {\n    add_block_undo_frame(mzx_world, dest_history, dest_board,\n     history_offset, history_width, history_height);\n\n    switch(block->command)\n    {\n      case BLOCK_CMD_NONE:\n        return;\n\n      case BLOCK_CMD_COPY:\n      case BLOCK_CMD_COPY_REPEATED:\n      {\n        switch(block->src_mode)\n        {\n          case EDIT_BOARD:\n            copy_board_to_board(mzx_world,\n             src_board, src_offset,\n             dest_board, dest_offset,\n             dest_block_width, dest_block_height);\n            break;\n\n          case EDIT_OVERLAY:\n          case EDIT_VLAYER:\n          {\n            copy_layer_to_board(\n             src_char, src_color, src_width, src_offset,\n             dest_board, dest_offset,\n             dest_block_width, dest_block_height, block->convert_id);\n            break;\n          }\n        }\n        break;\n      }\n\n      case BLOCK_CMD_MOVE:\n      {\n        // Has to take x and y so it can manipulate the player position.\n        move_board_block(mzx_world,\n         src_board, src_x, src_y,\n         dest_board, dest_x, dest_y,\n         dest_block_width, dest_block_height,\n         block_width, block_height); // Dimensions to clear old block\n        break;\n      }\n\n      case BLOCK_CMD_CLEAR:\n      {\n        clear_board_block(dest_board, dest_offset,\n         dest_block_width, dest_block_height);\n        break;\n      }\n\n      case BLOCK_CMD_FLIP:\n      {\n        flip_board_block(dest_board, dest_offset,\n         dest_block_width, dest_block_height);\n        break;\n      }\n\n      case BLOCK_CMD_MIRROR:\n      {\n        mirror_board_block(dest_board, dest_offset,\n         dest_block_width, dest_block_height);\n        break;\n      }\n\n      case BLOCK_CMD_PAINT:\n      {\n        paint_layer_block(\n         dest_color, dest_width, dest_offset,\n         dest_block_width, dest_block_height, current_color);\n        break;\n      }\n\n      case BLOCK_CMD_SAVE_MZM:\n      case BLOCK_CMD_SAVE_ANSI:\n      {\n        // not handled here\n        break;\n      }\n\n      case BLOCK_CMD_LOAD_MZM:\n      {\n        load_mzm(mzx_world, mzm_name_buffer, dest_x, dest_y, block->dest_mode,\n         false, block->convert_id);\n        break;\n      }\n\n      case BLOCK_CMD_LOAD_ANSI:\n      {\n        clear_board_block(dest_board, dest_offset,\n         dest_block_width, dest_block_height);\n        // This function takes the original block size, not the clipped size.\n        import_ansi(mzx_world, mzm_name_buffer, block->dest_mode, dest_x,\n         dest_y, block_width, block_height, block->convert_id);\n        break;\n      }\n    }\n\n    update_undo_frame(dest_history);\n  }\n  else\n\n  if((block->dest_mode == EDIT_OVERLAY) || (block->dest_mode == EDIT_VLAYER))\n  {\n    add_layer_undo_frame(dest_history, dest_char, dest_color, dest_width,\n     history_offset, history_width, history_height);\n\n    switch(block->command)\n    {\n      case BLOCK_CMD_NONE:\n        break;\n\n      case BLOCK_CMD_COPY:\n      case BLOCK_CMD_COPY_REPEATED:\n      {\n        switch(block->src_mode)\n        {\n          case EDIT_BOARD:\n            copy_board_to_layer(\n             src_board, src_offset,\n             dest_char, dest_color, dest_width, dest_offset,\n             dest_block_width, dest_block_height);\n            break;\n\n          case EDIT_OVERLAY:\n          case EDIT_VLAYER:\n            copy_layer_to_layer(\n             src_char, src_color, src_width, src_offset,\n             dest_char, dest_color, dest_width, dest_offset,\n             dest_block_width, dest_block_height);\n            break;\n        }\n        break;\n      }\n\n      case BLOCK_CMD_MOVE:\n      {\n        move_layer_block(\n         src_char, src_color, src_width, src_offset,\n         dest_char, dest_color, dest_width, dest_offset,\n         dest_block_width, dest_block_height,\n         block_width, block_height); // Dimensions to clear old block\n        break;\n      }\n\n      case BLOCK_CMD_CLEAR:\n      {\n        clear_layer_block(\n         dest_char, dest_color, dest_width, dest_offset,\n         dest_block_width, dest_block_height);\n        break;\n      }\n\n      case BLOCK_CMD_FLIP:\n      {\n        flip_layer_block(\n         dest_char, dest_color, dest_width, dest_offset,\n         dest_block_width, dest_block_height);\n        break;\n      }\n\n      case BLOCK_CMD_MIRROR:\n      {\n        mirror_layer_block(\n         dest_char, dest_color, dest_width, dest_offset,\n         dest_block_width, dest_block_height);\n        break;\n      }\n\n      case BLOCK_CMD_PAINT:\n      {\n        paint_layer_block(\n         dest_color, dest_width, dest_offset,\n         dest_block_width, dest_block_height, current_color);\n        break;\n      }\n\n      case BLOCK_CMD_SAVE_MZM:\n      case BLOCK_CMD_SAVE_ANSI:\n      {\n        // not handled here\n        break;\n      }\n\n      case BLOCK_CMD_LOAD_MZM:\n      {\n        load_mzm(mzx_world, mzm_name_buffer, dest_x, dest_y, block->dest_mode,\n         false, NO_ID);\n        break;\n      }\n\n      case BLOCK_CMD_LOAD_ANSI:\n      {\n        clear_layer_block(\n         dest_char, dest_color, dest_width, dest_offset,\n         dest_block_width, dest_block_height);\n        // This function takes the original block size, not the clipped size.\n        import_ansi(mzx_world, mzm_name_buffer, block->dest_mode, dest_x,\n         dest_y, block_width, block_height, NO_ID);\n        break;\n      }\n    }\n\n    update_undo_frame(dest_history);\n  }\n\n  // TODO: it might be better to keep it in regular memory for repeat copies.\n  if(loaded_src_board)\n    store_board_to_extram(src_board);\n}\n\n//--------------------------\n//\n// ( ) Copy block\n// ( ) Copy block (repeated)\n// ( ) Move block\n// ( ) Clear block\n// ( ) Flip block\n// ( ) Mirror block\n// ( ) Paint block\n// ( ) Copy to...\n// ( ) Copy to...\n// ( ) Save as ANSi\n// ( ) Save as MZM\n//\n//    _OK_      _Cancel_\n//\n//--------------------------\nboolean select_block_command(struct world *mzx_world, struct block_info *block,\n enum editor_mode mode)\n{\n  int dialog_result;\n  struct element *elements[3];\n  struct dialog di;\n  int selection = 0;\n  const char *radio_button_strings[] =\n  {\n    \"Copy block\",\n    \"Copy block (repeated)\",\n    \"Move block\",\n    \"Clear block\",\n    \"Flip block\",\n    \"Mirror block\",\n    \"Paint block\",\n    NULL,\n    NULL,\n    \"Save as ANSi/TXT\",\n    \"Save as MZM\"\n  };\n\n  switch(mode)\n  {\n    default:\n    case EDIT_BOARD:\n      radio_button_strings[7] = \"Copy to overlay\";\n      radio_button_strings[8] = \"Copy to vlayer\";\n      break;\n\n    case EDIT_OVERLAY:\n      radio_button_strings[7] = \"Copy to board\";\n      radio_button_strings[8] = \"Copy to vlayer\";\n      break;\n\n    case EDIT_VLAYER:\n      radio_button_strings[7] = \"Copy to board\";\n      radio_button_strings[8] = \"Copy to overlay\";\n      break;\n  }\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_BLOCK_CMD);\n  elements[0] = construct_radio_button(2, 2, radio_button_strings,\n   11, 21, &selection);\n  elements[1] = construct_button(5, 14, \"OK\", 0);\n  elements[2] = construct_button(15, 14, \"Cancel\", -1);\n\n  construct_dialog(&di, \"Choose block command\", 26, 3, 29, 17,\n   elements, 3, 0);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  pop_context();\n\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  if(dialog_result)\n  {\n    block->selected = false;\n    return false;\n  }\n\n  // Translate selection to a real block command.\n  block->selected = true;\n  block->command = BLOCK_CMD_NONE;\n  block->src_mode = mode;\n  block->dest_mode = mode;\n\n  switch(selection)\n  {\n    case 0:\n      block->command = BLOCK_CMD_COPY;\n      break;\n\n    case 1:\n      block->command = BLOCK_CMD_COPY_REPEATED;\n      break;\n\n    case 2:\n      block->command = BLOCK_CMD_MOVE;\n      break;\n\n    case 3:\n      block->command = BLOCK_CMD_CLEAR;\n      break;\n\n    case 4:\n      block->command = BLOCK_CMD_FLIP;\n      break;\n\n    case 5:\n      block->command = BLOCK_CMD_MIRROR;\n      break;\n\n    case 6:\n      block->command = BLOCK_CMD_PAINT;\n      break;\n\n    case 7:\n    {\n      block->command = BLOCK_CMD_COPY;\n      switch(mode)\n      {\n        case EDIT_BOARD:\n          block->dest_mode = EDIT_OVERLAY;\n          break;\n\n        case EDIT_OVERLAY:\n        case EDIT_VLAYER:\n          block->dest_mode = EDIT_BOARD;\n          break;\n      }\n      break;\n    }\n\n    case 8:\n    {\n      block->command = BLOCK_CMD_COPY;\n      switch(mode)\n      {\n        case EDIT_BOARD:\n        case EDIT_OVERLAY:\n          block->dest_mode = EDIT_VLAYER;\n          break;\n\n        case EDIT_VLAYER:\n          block->dest_mode = EDIT_OVERLAY;\n          break;\n      }\n      break;\n    }\n\n    case 9:\n      block->command = BLOCK_CMD_SAVE_ANSI;\n      break;\n\n    case 10:\n      block->command = BLOCK_CMD_SAVE_MZM;\n      break;\n  }\n  return true;\n}\n\nenum thing layer_to_board_object_type(struct world *mzx_world)\n{\n  int dialog_result;\n  struct element *elements[3];\n  struct dialog di;\n  int object_type = 0;\n  const char *radio_button_strings[] =\n  {\n    \"Custom Block\",\n    \"Custom Floor\",\n    \"Text\"\n  };\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_BLOCK_TYPE);\n  elements[0] = construct_radio_button(6, 4, radio_button_strings,\n   3, 12, &object_type);\n  elements[1] = construct_button(5, 11, \"OK\", 0);\n  elements[2] = construct_button(15, 11, \"Cancel\", -1);\n\n  construct_dialog(&di, \"Object type\", 26, 4, 28, 14,\n   elements, 3, 0);\n\n  dialog_result = run_dialog(mzx_world, &di);\n\n  destruct_dialog(&di);\n  pop_context();\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  if(dialog_result)\n    return NO_ID;\n\n  switch(object_type)\n  {\n    case 0: return CUSTOM_BLOCK;\n    case 1: return CUSTOM_FLOOR;\n    case 2: return TEXT_ID;\n  }\n\n  return NO_ID;\n}\n"
  },
  {
    "path": "src/editor/block.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_BLOCK_H\n#define __EDITOR_BLOCK_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../world_struct.h\"\n#include \"edit.h\"\n#include \"undo.h\"\n\nenum block_command\n{\n  BLOCK_CMD_NONE,\n  BLOCK_CMD_COPY,\n  BLOCK_CMD_COPY_REPEATED,\n  BLOCK_CMD_MOVE,\n  BLOCK_CMD_CLEAR,\n  BLOCK_CMD_FLIP,\n  BLOCK_CMD_MIRROR,\n  BLOCK_CMD_PAINT,\n  BLOCK_CMD_SAVE_MZM,\n  BLOCK_CMD_LOAD_MZM,\n  BLOCK_CMD_SAVE_ANSI,\n  BLOCK_CMD_LOAD_ANSI,\n};\n\nstruct block_info\n{\n  enum block_command command;\n  enum editor_mode src_mode;\n  enum editor_mode dest_mode;\n  int src_x;\n  int src_y;\n  int dest_x;\n  int dest_y;\n  int width;\n  int height;\n  enum thing convert_id;\n  struct board *src_board;\n  struct board *dest_board;\n  boolean selected;\n};\n\nvoid copy_layer_buffer_to_buffer(\n char *src_char, char *src_color, int src_width, int src_offset,\n char *dest_char, char *dest_color, int dest_width, int dest_offset,\n int block_width, int block_height);\n\nvoid do_block_command(struct world *mzx_world, struct block_info *block,\n struct undo_history *dest_history, char *mzm_name_buffer, int current_color);\n\nboolean select_block_command(struct world *mzx_world, struct block_info *block,\n enum editor_mode mode);\nenum thing layer_to_board_object_type(struct world *mzx_world);\n\n__M_END_DECLS\n\n#endif  // __EDITOR_BLOCK_H\n"
  },
  {
    "path": "src/editor/board.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"board.h\"\n\n#include \"../board.h\"\n#include \"../error.h\"\n#include \"../extmem.h\"\n#include \"../legacy_board.h\"\n#include \"../robot.h\"\n#include \"../world.h\"\n#include \"../world_format.h\"\n#include \"../util.h\"\n#include \"../io/vio.h\"\n#include \"../io/zip.h\"\n\n#include \"world.h\"\n#include \"configure.h\"\n\n#include <string.h>\n\n\nstatic int board_magic(const char magic_string[4])\n{\n  if(magic_string[0] == 0xFF)\n  {\n    if(magic_string[1] == 'M')\n    {\n      // MZX versions >= 2.00 && <= 2.51S1\n      if(magic_string[2] == 'B' && magic_string[3] == '2')\n        return V251;\n\n      // MZX versions >= 2.51S2 && <= 9.xx\n      if((magic_string[2] > 1) && (magic_string[2] < 10))\n        return ((int)magic_string[2] << 8) + (int)magic_string[3];\n    }\n  }\n\n  // Not a recognized board magic\n  return 0;\n}\n\nstatic boolean legacy_check_v1_rle(vfile *vf, int board_width, int board_height)\n{\n  int size = board_width * board_height;\n  int w;\n  int h;\n  int i;\n\n  w = vfgetc(vf);\n  h = vfgetc(vf);\n  if(w == 0) // VER1TO2 hack\n  {\n    w = vfgetc(vf);\n    h = vfgetc(vf);\n  }\n  if(w != board_width || h != board_height)\n    return false;\n\n  for(i = 0; i < size;)\n  {\n    int runsize = vfgetc(vf);\n    int current_char = vfgetc(vf);\n    if(current_char < 0 || runsize > size - i)\n      return false;\n\n    i += runsize;\n  }\n  return true;\n}\n\nstatic boolean legacy_check_v1_board(vfile *vf)\n{\n  int w;\n  int h;\n  int i;\n\n  vrewind(vf);\n\n  w = vfgetc(vf);\n  h = vfgetc(vf);\n  if(w < 1 || w > 100 || h < 1 || h > 100)\n    return false;\n\n  vrewind(vf);\n  for(i = 0; i < 6; i++)\n    if(!legacy_check_v1_rle(vf, w, h))\n      return false;\n\n  // There is at least usable plane data in this file, so allow it to load.\n  vrewind(vf);\n  return true;\n}\n\nvoid save_board_file(struct world *mzx_world, struct board *cur_board,\n const char *name)\n{\n  vfile *vf = vfopen_unsafe(name, \"wb\");\n  struct zip_archive *zp;\n  boolean success = false;\n\n  if(vf)\n  {\n    vfputc(0xFF, vf);\n\n    vfputc('M', vf);\n    vfputc((MZX_VERSION >> 8) & 0xFF, vf);\n    vfputc(MZX_VERSION & 0xFF, vf);\n\n    zp = zip_open_vf_write(vf);\n    if(zp)\n    {\n      if(!save_board(mzx_world, cur_board, zp, 0, MZX_VERSION, 0))\n        success = true;\n\n      zip_close(zp, NULL);\n    }\n    else\n      vfclose(vf);\n  }\n\n  if(!success)\n    error_message(E_WORLD_IO_SAVING, 0, NULL);\n}\n\nstatic struct board *legacy_load_board_allocate_direct(struct world *mzx_world,\n vfile *vf, int version)\n{\n  struct board *cur_board = cmalloc(sizeof(struct board));\n  int board_start, board_end;\n\n  board_start = vftell(vf);\n  board_end = vfilelength(vf, false);\n\n  legacy_load_board_direct(mzx_world, cur_board, vf, (board_end - board_start), 0,\n   version);\n\n  if(!vfread(cur_board->board_name, 25, 1, vf))\n    cur_board->board_name[0] = 0;\n\n  return cur_board;\n}\n\nstatic int find_first_board(struct zip_archive *zp)\n{\n  unsigned int file_id;\n\n  world_assign_file_ids(zp, false);\n\n  // The first file after sorting should be the board info for ID 0.\n  // If it isn't, this isn't a board file.\n\n  if(ZIP_SUCCESS == zip_get_next_mzx_file_id(zp, &file_id, NULL, NULL))\n  {\n    if(file_id == FILE_ID_BOARD_INFO)\n    {\n      // Loading board ID 0:\n      return 0;\n    }\n  }\n\n  return -1;\n}\n\nvoid replace_current_board(struct world *mzx_world, const char *name)\n{\n  int current_board_id = mzx_world->current_board_id;\n  struct board *src_board = mzx_world->current_board;\n\n  vfile *vf = vfopen_unsafe(name, \"rb\");\n  struct zip_archive *zp;\n\n  char version_string[4];\n  int file_version;\n  int success = 0;\n\n  if(vf)\n  {\n    if(!vfread(version_string, 4, 1, vf))\n    {\n      error_message(E_IO_READ, 0, NULL);\n      vfclose(vf);\n      return;\n    }\n\n    file_version = board_magic(version_string);\n\n    // 1.x doesn't have a magic string and needs a custom check.\n    if(file_version == 0 && legacy_check_v1_board(vf))\n      file_version = V100;\n\n    if(file_version > 0 && file_version <= MZX_LEGACY_FORMAT_VERSION)\n    {\n      // Legacy board.\n      clear_board(src_board);\n\n      src_board =\n       legacy_load_board_allocate_direct(mzx_world, vf, file_version);\n\n      success = 1;\n      vfclose(vf);\n    }\n    else\n\n    if(file_version <= MZX_VERSION)\n    {\n      int board_id;\n\n      // Regular board or maybe not a board at all.\n      zp = zip_open_vf_read(vf);\n\n      // Make sure it's an actual zip.\n      if(zp)\n      {\n        // Make sure the zip contains a board.\n        board_id = find_first_board(zp);\n\n        if(board_id >= 0)\n        {\n          clear_board(src_board);\n\n          src_board =\n           load_board_allocate(mzx_world, zp, 0, file_version, board_id);\n\n          success = 1;\n        }\n      }\n\n      if(!success)\n        error_message(E_BOARD_FILE_INVALID, 0, NULL);\n\n      zip_close(zp, NULL);\n    }\n\n    else\n    {\n      error_message(E_BOARD_FILE_FUTURE_VERSION, file_version, NULL);\n      vfclose(vf);\n    }\n\n    if(success)\n    {\n      // Set up the newly allocated board.\n      optimize_null_objects(src_board);\n\n      if(src_board->robot_list)\n        src_board->robot_list[0] = &mzx_world->global_robot;\n\n      set_update_done_current(mzx_world);\n\n      mzx_world->current_board = NULL;\n      set_current_board(mzx_world, src_board);\n      mzx_world->board_list[current_board_id] = src_board;\n    }\n  }\n}\n\nstruct board *create_blank_board(const struct editor_config_info *conf)\n{\n  struct board *cur_board = ccalloc(1, sizeof(struct board));\n  int layer_size = conf->board_width * conf->board_height;\n  int i;\n\n  cur_board->board_width =       conf->board_width;\n  cur_board->board_height =      conf->board_height;\n  cur_board->overlay_mode =      conf->overlay_enabled;\n  cur_board->level_id =          cmalloc(layer_size);\n  cur_board->level_param =       cmalloc(layer_size);\n  cur_board->level_color =       cmalloc(layer_size);\n  cur_board->level_under_id =    cmalloc(layer_size);\n  cur_board->level_under_param = cmalloc(layer_size);\n  cur_board->level_under_color = cmalloc(layer_size);\n  if(cur_board->overlay_mode)\n  {\n    cur_board->overlay =         cmalloc(layer_size);\n    cur_board->overlay_color =   cmalloc(layer_size);\n  }\n  cur_board->mod_playing[0] = 0;\n\n  cur_board->viewport_x =        conf->viewport_x;\n  cur_board->viewport_y =        conf->viewport_y;\n  cur_board->viewport_width =    conf->viewport_w;\n  cur_board->viewport_height =   conf->viewport_h;\n  cur_board->can_shoot =         conf->can_shoot;\n  cur_board->can_bomb =          conf->can_bomb;\n  cur_board->fire_burn_brown =   conf->fire_burns_brown;\n  cur_board->fire_burn_space =   conf->fire_burns_spaces;\n  cur_board->fire_burn_fakes =   conf->fire_burns_fakes;\n  cur_board->fire_burn_trees =   conf->fire_burns_trees;\n  cur_board->explosions_leave =  conf->explosions_leave;\n  cur_board->save_mode =         conf->saving_enabled;\n  cur_board->forest_becomes =    conf->forest_to_floor;\n  cur_board->collect_bombs =     conf->collect_bombs;\n  cur_board->fire_burns =        conf->fire_burns_forever;\n  cur_board->dragons_can_randomly_move  = conf->dragons_can_randomly_move;\n\n  for(i = 0; i < 4; i++)\n  {\n    cur_board->board_dir[i] = NO_BOARD;\n  }\n\n  cur_board->reset_on_entry =    conf->reset_on_entry;\n  cur_board->reset_on_entry_same_board  = conf->reset_on_entry_same_board;\n  cur_board->restart_if_zapped = conf->restart_if_hurt;\n  cur_board->time_limit =        conf->time_limit;\n  cur_board->last_key = '?';\n  cur_board->num_input = 0;\n  cur_board->input_size = 0;\n  cur_board->input_allocated = 0;\n  cur_board->input_string = NULL;\n  cur_board->player_last_dir = 0x10;\n  cur_board->bottom_mesg[0] = 0;\n  cur_board->b_mesg_timer = 0;\n  cur_board->lazwall_start = 7;\n  cur_board->b_mesg_row = 24;\n  cur_board->b_mesg_col = -1;\n  cur_board->scroll_x = 0;\n  cur_board->scroll_y = 0;\n  cur_board->locked_x = -1;\n  cur_board->locked_y = -1;\n  cur_board->player_ns_locked =     conf->player_locked_ns;\n  cur_board->player_ew_locked =     conf->player_locked_ew;\n  cur_board->player_attack_locked = conf->player_locked_att;\n  cur_board->volume = 255;\n  cur_board->volume_inc = 0;\n  cur_board->volume_target = 255;\n\n  board_set_charset_path(cur_board, conf->charset_path, strlen(conf->charset_path));\n  board_set_palette_path(cur_board, conf->palette_path, strlen(conf->palette_path));\n\n  cur_board->num_robots = 0;\n  cur_board->num_robots_active = 0;\n  cur_board->num_robots_allocated = 0;\n  cur_board->robot_list = cmalloc(sizeof(struct robot *));\n  cur_board->robot_list_name_sorted = NULL;\n  cur_board->num_scrolls = 0;\n  cur_board->num_scrolls_allocated = 0;\n  cur_board->scroll_list = cmalloc(sizeof(struct scroll *));\n  cur_board->num_sensors = 0;\n  cur_board->num_sensors_allocated = 0;\n  cur_board->sensor_list = cmalloc(sizeof(struct sensor *));\n\n  memset(cur_board->level_id, 0, layer_size);\n  memset(cur_board->level_color, 7, layer_size);\n  memset(cur_board->level_param, 0, layer_size);\n  memset(cur_board->level_under_id, 0, layer_size);\n  memset(cur_board->level_under_color, 7, layer_size);\n  memset(cur_board->level_under_param, 0, layer_size);\n  if(cur_board->overlay_mode)\n  {\n    memset(cur_board->overlay, 32, layer_size);\n    memset(cur_board->overlay_color, 7, layer_size);\n  }\n\n  cur_board->level_id[0] = 127;\n\n#ifdef DEBUG\n  cur_board->is_extram = false;\n#endif\n\n  return cur_board;\n}\n\nstruct board *create_buffer_board(int width, int height)\n{\n  // Create a dummy board to be used as a buffer for undo block actions\n  struct board *src_board = ccalloc(1, sizeof(struct board));\n  int layer_size = width * height;\n\n  src_board->board_width = width;\n  src_board->board_height = height;\n\n  src_board->robot_list = ccalloc(1, sizeof(struct robot *));\n  src_board->scroll_list = ccalloc(1, sizeof(struct scroll *));\n  src_board->sensor_list = ccalloc(1, sizeof(struct sensor *));\n\n  src_board->level_id = ccalloc(1, layer_size);\n  src_board->level_color = ccalloc(1, layer_size);\n  src_board->level_param = ccalloc(1, layer_size);\n  src_board->level_under_id = ccalloc(1, layer_size);\n  src_board->level_under_color = ccalloc(1, layer_size);\n  src_board->level_under_param = ccalloc(1, layer_size);\n\n  return src_board;\n}\n\nvoid change_board_size(struct board *src_board, int new_width, int new_height)\n{\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n\n  if(new_width == 0)\n    new_width = 1;\n\n  if(new_height == 0)\n    new_height = 1;\n\n  if((board_width != new_width) || (board_height != new_height))\n  {\n    char *level_id = src_board->level_id;\n    char *level_color = src_board->level_color;\n    char *level_param = src_board->level_param;\n    char *level_under_id = src_board->level_under_id;\n    char *level_under_color = src_board->level_under_color;\n    char *level_under_param = src_board->level_under_param;\n    char *overlay = src_board->overlay;\n    char *overlay_color = src_board->overlay_color;\n    int overlay_mode = src_board->overlay_mode;\n    int board_size = board_width * board_height;\n    int new_size = new_width * new_height;\n    int i;\n\n    // Shrinking height? Remove any robots/scrolls/sensors from excess lines\n    if(new_height < board_height)\n    {\n      int offset;\n      int check_id;\n\n      for(offset = new_height * board_width; offset < board_size;\n       offset++)\n      {\n        check_id = level_id[offset];\n\n        if(check_id == 122)\n          clear_sensor_id(src_board, level_param[offset]);\n        else\n\n        if((check_id == 126) || (check_id == 125))\n          clear_scroll_id(src_board, level_param[offset]);\n        else\n\n        if((check_id == 123) || (check_id == 124))\n          clear_robot_id(src_board, level_param[offset]);\n      }\n    }\n\n    if(new_width < board_width)\n    {\n      // Take out pieces from each width, then reallocate.\n      // Only do this for the current height or the new height, whichever is\n      // less.\n\n      int src_offset, dest_offset;\n      int max_height = new_height;\n      int check_id, offset;\n\n      if(new_height > board_height)\n        max_height = board_height;\n\n      for(i = 0, src_offset = 0, dest_offset = 0; i < max_height;\n       i++, src_offset += board_width, dest_offset += new_width)\n      {\n        // First, remove any robots/scrolls/sensors from excess chunks\n        for(offset = src_offset + new_width; offset < src_offset + board_width;\n          offset++)\n        {\n          check_id = level_id[offset];\n\n          if(check_id == 122)\n            clear_sensor_id(src_board, level_param[offset]);\n          else\n\n          if((check_id == 126) || (check_id == 125))\n            clear_scroll_id(src_board, level_param[offset]);\n          else\n\n          if((check_id == 123) || (check_id == 124))\n            clear_robot_id(src_board, level_param[offset]);\n        }\n\n        memmove(level_id + dest_offset, level_id + src_offset, new_width);\n        memmove(level_param + dest_offset, level_param + src_offset, new_width);\n        memmove(level_color + dest_offset, level_color + src_offset, new_width);\n        memmove(level_under_id + dest_offset,\n         level_under_id + src_offset, new_width);\n        memmove(level_under_param + dest_offset,\n         level_under_param + src_offset, new_width);\n        memmove(level_under_color + dest_offset,\n         level_under_color + src_offset, new_width);\n      }\n\n      // Resize layers\n      src_board->level_id = crealloc(level_id, new_size);\n      src_board->level_color = crealloc(level_color, new_size);\n      src_board->level_param = crealloc(level_param, new_size);\n      src_board->level_under_id = crealloc(level_under_id, new_size);\n      src_board->level_under_color = crealloc(level_under_color, new_size);\n      src_board->level_under_param = crealloc(level_under_param, new_size);\n\n      level_id = src_board->level_id;\n      level_color = src_board->level_color;\n      level_param = src_board->level_param;\n      level_under_id = src_board->level_under_id;\n      level_under_color = src_board->level_under_color;\n      level_under_param = src_board->level_under_param;\n\n      // Do the overlay too, if it exists\n      if(overlay_mode)\n      {\n        for(i = 0, src_offset = 0, dest_offset = 0; i < max_height;\n         i++, src_offset += board_width, dest_offset += new_width)\n        {\n          memmove(overlay + dest_offset, overlay + src_offset, new_width);\n          memmove(overlay_color + dest_offset, overlay_color + src_offset, new_width);\n        }\n\n        src_board->overlay = crealloc(overlay, new_size);\n        src_board->overlay_color = crealloc(overlay_color, new_size);\n\n        overlay = src_board->overlay;\n        overlay_color = src_board->overlay_color;\n      }\n    }\n    else\n\n    if(new_width > board_width)\n    {\n      // Reallocate first, then copy over pieces, padding in widths\n      // Only do this for the current height or the new height, whichever is\n      // less.\n      int src_offset, dest_offset;\n      int max_height = new_height;\n      int width_difference = new_width - board_width;\n\n      if(new_height > board_height)\n        max_height = board_height;\n\n      // Resize first this time.\n      src_board->level_id = crealloc(level_id, new_size);\n      src_board->level_color = crealloc(level_color, new_size);\n      src_board->level_param = crealloc(level_param, new_size);\n      src_board->level_under_id = crealloc(level_under_id, new_size);\n      src_board->level_under_color = crealloc(level_under_color, new_size);\n      src_board->level_under_param = crealloc(level_under_param, new_size);\n\n      // Resize the overlay too, if it exists\n      if(overlay_mode)\n      {\n        src_board->overlay = crealloc(overlay, new_size);\n        src_board->overlay_color = crealloc(overlay_color, new_size);\n        overlay = src_board->overlay;\n        overlay_color = src_board->overlay_color;\n      }\n\n      level_id = src_board->level_id;\n      level_color = src_board->level_color;\n      level_param = src_board->level_param;\n      level_under_id = src_board->level_under_id;\n      level_under_color = src_board->level_under_color;\n      level_under_param = src_board->level_under_param;\n\n      // And start at the end instead of the beginning\n      src_offset = board_width * (max_height - 1);\n      dest_offset = new_width * (max_height - 1);\n\n      for(i = 0; i < max_height; i++, src_offset -= board_width, dest_offset -= new_width)\n      {\n        memmove(level_id + dest_offset, level_id + src_offset, board_width);\n        memmove(level_param + dest_offset, level_param + src_offset, board_width);\n        memmove(level_color + dest_offset, level_color + src_offset, board_width);\n        memmove(level_under_id + dest_offset,\n         level_under_id + src_offset, board_width);\n        memmove(level_under_param + dest_offset,\n         level_under_param + src_offset, board_width);\n        memmove(level_under_color + dest_offset,\n         level_under_color + src_offset, board_width);\n        // And fill in the remainder with blanks\n        memset(level_id + dest_offset + board_width, 0, width_difference);\n        memset(level_param + dest_offset + board_width, 0, width_difference);\n        memset(level_color + dest_offset + board_width, 7, width_difference);\n        memset(level_under_id + dest_offset + board_width, 0, width_difference);\n        memset(level_under_param + dest_offset + board_width, 0, width_difference);\n        memset(level_under_color + dest_offset + board_width, 7, width_difference);\n      }\n\n      src_offset = board_width * (max_height - 1);\n      dest_offset = new_width * (max_height - 1);\n\n      // Do the overlay too, if it exists\n      if(overlay_mode)\n      {\n        for(i = 0; i < max_height; i++, src_offset -= board_width, dest_offset -= new_width)\n        {\n          memmove(overlay + dest_offset, overlay + src_offset, board_width);\n          memmove(overlay_color + dest_offset, overlay_color + src_offset, board_width);\n          // And fill in the remainder with blanks\n          memset(overlay + dest_offset + board_width, 32, width_difference);\n          memset(overlay_color + dest_offset + board_width, 7, width_difference);\n        }\n      }\n    }\n    else\n    {\n      // Width remains the same, just a straight resize\n\n      src_board->level_id = crealloc(level_id, new_size);\n      src_board->level_color = crealloc(level_color, new_size);\n      src_board->level_param = crealloc(level_param, new_size);\n      src_board->level_under_id = crealloc(level_under_id, new_size);\n      src_board->level_under_color = crealloc(level_under_color, new_size);\n      src_board->level_under_param = crealloc(level_under_param, new_size);\n\n      // Resize the overlay too, if it exists\n      if(overlay_mode)\n      {\n        src_board->overlay = crealloc(overlay, new_size);\n        src_board->overlay_color = crealloc(overlay_color, new_size);\n      }\n\n      level_id = src_board->level_id;\n      level_color = src_board->level_color;\n      level_param = src_board->level_param;\n      level_under_id = src_board->level_under_id;\n      level_under_color = src_board->level_under_color;\n      level_under_param = src_board->level_under_param;\n      overlay = src_board->overlay;\n      overlay_color = src_board->overlay_color;\n    }\n\n    // Fill in any blanks\n    if(new_height > board_height)\n    {\n      int offset = new_width * board_height;\n      int size_difference = new_size - offset;\n\n      memset(level_id + offset, 0, size_difference);\n      memset(level_param + offset, 0, size_difference);\n      memset(level_color + offset, 7, size_difference);\n      memset(level_under_id + offset, 0, size_difference);\n      memset(level_under_param + offset, 0, size_difference);\n      memset(level_under_color + offset, 7, size_difference);\n\n      if(overlay_mode)\n      {\n        memset(overlay + offset, 32, size_difference);\n        memset(overlay_color + offset, 7, size_difference);\n      }\n    }\n\n    // Set the new sizes\n    src_board->board_width = new_width;\n    src_board->board_height = new_height;\n  }\n}\n"
  },
  {
    "path": "src/editor/board.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_BOARD_H\n#define __EDITOR_BOARD_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../world_struct.h\"\n\n#include \"configure.h\"\n\nvoid replace_current_board(struct world *mzx_world, const char *name);\nstruct board *create_blank_board(const struct editor_config_info *conf);\nstruct board *create_buffer_board(int width, int height);\nvoid save_board_file(struct world *mzx_world, struct board *cur_board, const char *name);\nvoid change_board_size(struct board *src_board, int new_width, int new_height);\n\n__M_END_DECLS\n\n#endif // __EDITOR_BOARD_H\n"
  },
  {
    "path": "src/editor/buffer.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2018-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <string.h>\n\n#include \"buffer.h\"\n#include \"configure.h\"\n#include \"edit.h\"\n#include \"param.h\"\n#include \"robot.h\"\n#include \"undo.h\"\n#include \"window.h\"\n\n#include \"../core.h\"\n#include \"../data.h\"\n#include \"../error.h\"\n#include \"../idarray.h\"\n#include \"../idput.h\"\n#include \"../robot.h\"\n#include \"../scrdisp.h\"\n#include \"../world_struct.h\"\n\n// This can be increased if necessary.\n#define MAX_CHOICES 20\n\nstruct thing_menu_item\n{\n  const char *const option;\n  int id;\n};\n\n/**\n * Titles of the thing menus. The order must correspond to the enum in buffer.h.\n */\n\nstatic const char *const thing_menu_titles[NUM_THING_MENUS] =\n{\n  \"Terrains\",\n  \"Items\",\n  \"Creatures\",\n  \"Puzzle Pieces\",\n  \"Transport\",\n  \"Elements\",\n  \"Miscellaneous\",\n  \"Objects\",\n};\n\n/**\n * Contents of the menus. The order must correspond to the enum in buffer.h.\n * Keep these NULL-terminated.\n */\n\nstatic struct thing_menu_item thing_menus[NUM_THING_MENUS][MAX_CHOICES] =\n{\n  // Terrain (F3)\n  {\n    { \"Space           ~1 \",      SPACE           },\n    { \"Normal          ~E\\xB2\",   NORMAL          },\n    { \"Solid           ~D\\xDB\",   SOLID           },\n    { \"Tree            ~A\\x06\",   TREE            },\n    { \"Line            ~B\\xCD\",   LINE            },\n    { \"Custom Block    ~F?\",      CUSTOM_BLOCK    },\n    { \"Breakaway       ~C\\xB1\",   BREAKAWAY       },\n    { \"Custom Break    ~F?\",      CUSTOM_BREAK    },\n    { \"Fake            ~9\\xB2\",   FAKE            },\n    { \"Carpet          ~4\\xB1\",   CARPET          },\n    { \"Floor           ~6\\xB0\",   FLOOR           },\n    { \"Tiles           ~0\\xFE\",   TILES           },\n    { \"Custom Floor    ~F?\",      CUSTOM_FLOOR    },\n    { \"Web             ~7\\xC5\",   WEB             },\n    { \"Thick Web       ~7\\xCE\",   THICK_WEB       },\n    { \"Forest          ~2\\xB2\",   FOREST          },\n    { \"Invis. Wall     ~1 \",      INVIS_WALL      },\n    { NULL, 0 }\n  },\n\n  // Item (F4)\n  {\n    { \"Gem             ~A\\x04\",   GEM             },\n    { \"Magic Gem       ~E\\x04\",   MAGIC_GEM       },\n    { \"Health          ~C\\x03\",   HEALTH          },\n    { \"Ring            ~E\\x09\",   RING            },\n    { \"Potion          ~B\\x96\",   POTION          },\n    { \"Energizer       ~D\\x07\",   ENERGIZER       },\n    { \"Ammo            ~3\\xA4\",   AMMO            },\n    { \"Bomb            ~8\\x0B\",   BOMB            },\n    { \"Key             ~F\\x0C\",   KEY             },\n    { \"Lock            ~F\\x0A\",   LOCK            },\n    { \"Coin            ~E\\x07\",   COIN            },\n    { \"Life            ~B\\x9B\",   LIFE            },\n    { \"Pouch           ~7\\x9F\",   POUCH           },\n    { \"Chest           ~6\\xA0\",   CHEST           },\n    { NULL, 0 }\n  },\n\n  // Creature (F5)\n  {\n    { \"Snake           ~2\\xE7\",   SNAKE           },\n    { \"Eye             ~F\\xE8\",   EYE             },\n    { \"Thief           ~C\\x05\",   THIEF           },\n    { \"Slime Blob      ~A*\",      SLIMEBLOB       },\n    { \"Runner          ~4\\x02\",   RUNNER          },\n    { \"Ghost           ~7\\xE6\",   GHOST           },\n    { \"Dragon          ~4\\x15\",   DRAGON          },\n    { \"Fish            ~E\\xE0\",   FISH            },\n    { \"Shark           ~7\\x7F\",   SHARK           },\n    { \"Spider          ~7\\x95\",   SPIDER          },\n    { \"Goblin          ~D\\x05\",   GOBLIN          },\n    { \"Spitting Tiger  ~B\\xE3\",   SPITTING_TIGER  },\n    { \"Bear            ~6\\xAC\",   BEAR            },\n    { \"Bear Cub        ~6\\xAD\",   BEAR_CUB        },\n    { \"Lazer Gun       ~4\\xCE\",   LAZER_GUN       },\n    { \"Bullet Gun      ~F\\x1B\",   BULLET_GUN      },\n    { \"Spinning Gun    ~F\\x1B\",   SPINNING_GUN    },\n    { \"Missile Gun     ~8\\x11\",   MISSILE_GUN     },\n    { NULL, 0 }\n  },\n\n  // Puzzle (F6)\n  {\n    { \"Boulder         ~7\\xE9\",   BOULDER         },\n    { \"Crate           ~6\\xFE\",   CRATE           },\n    { \"Custom Push     ~F?\",      CUSTOM_PUSH     },\n    { \"Box             ~E\\xFE\",   BOX             },\n    { \"Custom Box      ~F?\",      CUSTOM_BOX      },\n    { \"Pusher          ~D\\x1F\",   PUSHER          },\n    { \"Slider NS       ~D\\x12\",   SLIDER_NS       },\n    { \"Slider EW       ~D\\x1D\",   SLIDER_EW       },\n    { NULL, 0 }\n  },\n\n  // Tranport (F7)\n  {\n    { \"Stairs          ~A\\xA2\",   STAIRS          },\n    { \"Cave            ~6\\xA1\",   CAVE            },\n    { \"Transport       ~E<\",      TRANSPORT       },\n    { \"Whirlpool       ~B\\x97\",   WHIRLPOOL_1     },\n    { \"CWRotate        ~9/\",      CW_ROTATE       },\n    { \"CCWRotate       ~9\\\\\",     CCW_ROTATE      },\n    { NULL, 0 }\n  },\n\n  // Element (F8)\n  {\n    { \"Still Water     ~9\\xB0\",   STILL_WATER     },\n    { \"N Water         ~9\\x18\",   N_WATER         },\n    { \"S Water         ~9\\x19\",   S_WATER         },\n    { \"E Water         ~9\\x1A\",   E_WATER         },\n    { \"W Water         ~9\\x1B\",   W_WATER         },\n    { \"Ice             ~B\\xFD\",   ICE             },\n    { \"Lava            ~C\\xB2\",   LAVA            },\n    { \"Fire            ~E\\xB1\",   FIRE            },\n    { \"Goop            ~8\\xB0\",   GOOP            },\n    { \"Lit Bomb        ~8\\xAB\",   LIT_BOMB        },\n    { \"Explosion       ~E\\xB1\",   EXPLOSION       },\n    { NULL, 0 }\n  },\n\n  // Misc (F9)\n  {\n    { \"Door            ~6\\xC4\",   DOOR            },\n    { \"Gate            ~8\\x16\",   GATE            },\n    { \"Ricochet Panel  ~9/\",      RICOCHET_PANEL  },\n    { \"Ricochet        ~A*\",      RICOCHET        },\n    { \"Mine            ~C\\x8F\",   MINE            },\n    { \"Spike           ~8\\x1E\",   SPIKE           },\n    { \"Custom Hurt     ~F?\",      CUSTOM_HURT     },\n    { \"Text            ~F?\",      TEXT_ID         },\n    { \"N Moving Wall   ~F?\",      N_MOVING_WALL   },\n    { \"S Moving Wall   ~F?\",      S_MOVING_WALL   },\n    { \"E Moving Wall   ~F?\",      E_MOVING_WALL   },\n    { \"W Moving Wall   ~F?\",      W_MOVING_WALL   },\n    { NULL, 0 }\n  },\n\n  // Objects (F10)\n  {\n    { \"Robot           ~F?\",      ROBOT           },\n    { \"Pushable Robot  ~F?\",      ROBOT_PUSHABLE  },\n    { \"Player          ~B\\x02\",   PLAYER          },\n    { \"Scroll          ~F\\xE4\",   SCROLL          },\n    { \"Sign            ~6\\xE2\",   SIGN            },\n    { \"Sensor          ~F?\",      SENSOR          },\n    { \"Bullet          ~F\\xF9\",   BULLET          },\n    { \"Missile         ~8\\x10\",   MISSILE         },\n    { \"Seeker          ~A/\",      SEEKER          },\n    { \"Shooting Fire   ~E\\x0F\",   SHOOTING_FIRE   },\n    { NULL, 0 }\n  }\n};\n\n/**\n * Default colors for each type ID.\n */\n\nstatic const char def_colors[128] =\n{\n  7 , 0 , 0 , 10, 0 , 0 , 0 , 0 , 7 , 6 , 0 , 0 , 0 , 0 , 0 , 0 ,   // 0x00-0x0F\n  0 , 0 , 7 , 7 , 25, 25, 25, 25, 25, 59, 76, 6 , 0 , 0 , 12, 14,   // 0x10-0x1F\n  11, 15, 24, 3 , 8 , 8 , 239,0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 8 ,   // 0x20-0x2F\n  8 , 0 , 14, 0 , 0 , 0 , 0 , 7 , 0 , 0 , 0 , 0 , 4 , 15, 8 , 12,   // 0x30-0x3F\n  0 , 2 , 11, 31, 31, 31, 31, 0 , 9 , 10, 12, 8 , 0 , 0 , 14, 10,   // 0x40-0x4F\n  2 , 15, 12, 10, 4 , 7 , 4 , 14, 7 , 7 , 2 , 11, 15, 15, 6 , 6 ,   // 0x50-0x5F\n  0 , 8 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,   // 0x60-0x6F\n  0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 6 , 15, 27    // 0x70-0x7F\n};\n\n// It's important that after changing the param the thing is placed (using\n// place_current_at_xy) so that scrolls/sensors/robots get copied.\n\nvoid change_param(context *ctx, struct buffer_info *buffer, int *new_param)\n{\n  struct world *mzx_world = ctx->world;\n\n  if(buffer->id == SENSOR)\n  {\n    *new_param = edit_sensor(mzx_world, buffer->sensor);\n  }\n  else\n\n  if(is_robot(buffer->id))\n  {\n    edit_robot(ctx, buffer->robot, new_param);\n  }\n  else\n\n  if(is_signscroll(buffer->id))\n  {\n    *new_param = edit_scroll(mzx_world, buffer->scroll);\n  }\n\n  else\n  {\n    *new_param = edit_param(mzx_world, buffer->id, buffer->param);\n  }\n}\n\nvoid free_edit_buffer(struct buffer_info *buffer)\n{\n  clear_robot_contents(buffer->robot);\n  clear_scroll_contents(buffer->scroll);\n\n  memset(buffer->robot, 0, sizeof(struct robot));\n  memset(buffer->scroll, 0, sizeof(struct scroll));\n  memset(buffer->sensor, 0, sizeof(struct sensor));\n}\n\n/**\n * Place the contents of the buffer onto the board or a layer.\n * Returns the parameter or char of the newly placed item.\n */\n\nint place_current_at_xy(struct world *mzx_world, struct buffer_info *buffer,\n int x, int y, enum editor_mode mode, struct undo_history *history)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int offset = x + (y * cur_board->board_width);\n  int return_param = buffer->param;\n\n  if(mode == EDIT_BOARD)\n  {\n    char *level_id = cur_board->level_id;\n    enum thing old_id = (enum thing)level_id[offset];\n\n    if(old_id != PLAYER)\n    {\n      if(history)\n        add_board_undo_frame(mzx_world, history, buffer, x, y);\n\n      if(buffer->id == PLAYER)\n      {\n        id_remove_top(mzx_world, mzx_world->player_x, mzx_world->player_y);\n        mzx_world->player_x = x;\n        mzx_world->player_y = y;\n      }\n      else\n\n      if(is_robot(buffer->id))\n      {\n        if(is_robot(old_id))\n        {\n          int old_param = cur_board->level_param[offset];\n          replace_robot_source(mzx_world, cur_board, buffer->robot, old_param);\n          cur_board->level_color[offset] = buffer->color;\n          cur_board->level_id[offset] = buffer->id;\n\n          if(history)\n            update_undo_frame(history);\n\n          return old_param;\n        }\n\n        return_param =\n         duplicate_robot_source(mzx_world, cur_board, buffer->robot, x, y);\n\n        if(return_param != -1)\n        {\n          (cur_board->robot_list[return_param])->xpos = x;\n          (cur_board->robot_list[return_param])->ypos = y;\n          (cur_board->robot_list[return_param])->used = true;\n        }\n      }\n      else\n\n      if(is_signscroll(buffer->id))\n      {\n        if(is_signscroll(old_id))\n        {\n          int old_param = cur_board->level_param[offset];\n          replace_scroll(cur_board, buffer->scroll, old_param);\n          cur_board->level_color[offset] = buffer->color;\n          cur_board->level_id[offset] = buffer->id;\n\n          if(history)\n            update_undo_frame(history);\n\n          return old_param;\n        }\n\n        return_param = duplicate_scroll(cur_board, buffer->scroll);\n        if(return_param != -1)\n          (cur_board->scroll_list[return_param])->used = true;\n      }\n      else\n\n      if(buffer->id == SENSOR)\n      {\n        if(old_id == SENSOR)\n        {\n          int old_param = cur_board->level_param[offset];\n          replace_sensor(cur_board, buffer->sensor, old_param);\n          cur_board->level_color[offset] = buffer->color;\n\n          if(history)\n            update_undo_frame(history);\n\n          return old_param;\n        }\n\n        return_param = duplicate_sensor(cur_board, buffer->sensor);\n        if(return_param != -1)\n          (cur_board->sensor_list[return_param])->used = true;\n      }\n\n      if(return_param != -1)\n      {\n        place_at_xy(mzx_world, buffer->id, buffer->color, return_param, x, y);\n      }\n\n      if(history)\n        update_undo_frame(history);\n    }\n  }\n  else\n\n  if(mode == EDIT_OVERLAY)\n  {\n    if(history)\n      add_layer_undo_frame(history, cur_board->overlay,\n       cur_board->overlay_color, cur_board->board_width, offset, 1, 1);\n\n    cur_board->overlay[offset] = buffer->param;\n    cur_board->overlay_color[offset] = buffer->color;\n\n    if(history)\n      update_undo_frame(history);\n  }\n\n  else // EDIT_VLAYER\n  {\n    offset = x + (y * mzx_world->vlayer_width);\n\n    if(history)\n      add_layer_undo_frame(history, mzx_world->vlayer_chars,\n       mzx_world->vlayer_colors, mzx_world->vlayer_width, offset, 1, 1);\n\n    mzx_world->vlayer_chars[offset] = buffer->param;\n    mzx_world->vlayer_colors[offset] = buffer->color;\n\n    if(history)\n      update_undo_frame(history);\n  }\n\n  return return_param;\n}\n\n/**\n * Replace the contents of the board or layer at a position with the contents\n * of the provided buffer. Preserves anything under the replaced thing on the\n * the board.\n */\n\nint replace_current_at_xy(struct world *mzx_world, struct buffer_info *buffer,\n int x, int y, enum editor_mode mode, struct undo_history *history)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int offset = x + (y * cur_board->board_width);\n\n  if(mode == EDIT_BOARD)\n  {\n    enum thing old_id = cur_board->level_id[offset];\n\n    /**\n     * Need special handling for storageless objects or storage objects of\n     * different types--if placed normally, they could destroy whatever is\n     * under them, and we don't want this. We also want the under included in\n     * the history, which requires the use a block frame instead of a regular\n     * frame (which would just place_current_at_xy when redo is used).\n     */\n    if(is_storageless(old_id) || old_id != buffer->id)\n    {\n      if(history)\n        add_block_undo_frame(mzx_world, history, cur_board, offset, 1, 1);\n\n      id_remove_top(mzx_world, x, y);\n      place_current_at_xy(mzx_world, buffer, x, y, mode, NULL);\n\n      if(history)\n        update_undo_frame(history);\n\n      return buffer->param;\n    }\n  }\n\n  // Our requirements here are actually equivalent to the behavior of\n  // place_current_xy in most cases, so use it directly.\n  return place_current_at_xy(mzx_world, buffer, x, y, mode, history);\n}\n\n/**\n * Copy the contents of the board/layer at a location into the buffer.\n */\n\nvoid grab_at_xy(struct world *mzx_world, struct buffer_info *buffer,\n int x, int y, enum editor_mode mode)\n{\n  struct board *src_board = mzx_world->current_board;\n  int board_width = src_board->board_width;\n  int offset = x + (y * board_width);\n  enum thing old_id = buffer->id;\n\n  if(mode == EDIT_BOARD)\n  {\n    enum thing grab_id = (enum thing)src_board->level_id[offset];\n    int grab_param = src_board->level_param[offset];\n\n    // Hack.\n    if(grab_id != PLAYER)\n      buffer->color = src_board->level_color[offset];\n    else\n      buffer->color = get_id_color(src_board, offset);\n\n    // First, clear the existing copies. Previously, this would conditionally\n    // be ignored if the buffer thing and param were the same, but since it's\n    // always being duplicated over the buffer now, always clear it.\n\n    if(is_robot(old_id))\n    {\n      clear_robot_contents(buffer->robot);\n    }\n    else\n\n    if(is_signscroll(old_id))\n    {\n      clear_scroll_contents(buffer->scroll);\n    }\n\n    if(is_robot(grab_id))\n    {\n      struct robot *src_robot = src_board->robot_list[grab_param];\n      duplicate_robot_direct_source(mzx_world, src_robot, buffer->robot, 0, 0);\n    }\n    else\n\n    if(is_signscroll(grab_id))\n    {\n      struct scroll *src_scroll = src_board->scroll_list[grab_param];\n      duplicate_scroll_direct(src_scroll, buffer->scroll);\n    }\n    else\n\n    if(grab_id == SENSOR)\n    {\n      struct sensor *src_sensor = src_board->sensor_list[grab_param];\n      duplicate_sensor_direct(src_sensor, buffer->sensor);\n    }\n\n    buffer->id = grab_id;\n    buffer->param = grab_param;\n  }\n  else\n\n  if(mode == EDIT_OVERLAY)\n  {\n    buffer->param = src_board->overlay[offset];\n    buffer->color = src_board->overlay_color[offset];\n  }\n\n  else // EDIT_VLAYER\n  {\n    offset = x + (y * mzx_world->vlayer_width);\n\n    buffer->param = mzx_world->vlayer_chars[offset];\n    buffer->color = mzx_world->vlayer_colors[offset];\n  }\n}\n\nstruct thing_menu_context\n{\n  context ctx;\n  enum thing_menu_id menu_number;\n  struct buffer_info *buffer;\n  int chosen_pos;\n  int chosen_color;\n  int chosen_param;\n\n  // Placement info\n  struct undo_history *place_history;\n  boolean use_default_color;\n  int place_x;\n  int place_y;\n\n  // Backup buffer info (if the user cancels)\n  enum thing old_id;\n  int old_color;\n  int old_param;\n};\n\n/**\n * Update the buffer with the new color and parameter (and place the buffer on\n * the board if that feature is enabled).\n */\n\nstatic void thing_menu_place_callback(context *ctx, context_callback_param *p)\n{\n  struct thing_menu_context *thing_menu = (struct thing_menu_context *)ctx;\n  const struct editor_config_info *editor_conf = get_editor_config();\n  struct world *mzx_world = ctx->world;\n  struct buffer_info *buffer = thing_menu->buffer;\n\n  if(thing_menu->chosen_param >= 0)\n  {\n    // id was already set for change_param.\n    buffer->param = thing_menu->chosen_param;\n    buffer->color = thing_menu->chosen_color;\n\n    if(editor_conf->editor_thing_menu_places)\n    {\n      buffer->param = place_current_at_xy(mzx_world, buffer,\n       thing_menu->place_x, thing_menu->place_y, EDIT_BOARD,\n       thing_menu->place_history);\n    }\n  }\n  else\n  {\n    // Restore the old buffer if param editing failed.\n    // Note this should never happen for the storage types.\n    buffer->id = thing_menu->old_id;\n    buffer->param = thing_menu->old_param;\n    buffer->color = thing_menu->old_color;\n  }\n}\n\n/**\n * Now that a thing has been chosen, set up the buffer for it and request a\n * new parameter.\n */\n\nstatic void thing_menu_edit_callback(context *ctx, context_callback_param *p)\n{\n  struct thing_menu_context *thing_menu = (struct thing_menu_context *)ctx;\n  struct buffer_info *buffer = thing_menu->buffer;\n  enum thing chosen_id;\n\n  // If <0 was chosen, the user canceled without selecting anything.\n  if(thing_menu->chosen_pos < 0)\n    return;\n\n  chosen_id = thing_menus[thing_menu->menu_number][thing_menu->chosen_pos].id;\n\n  if(def_colors[chosen_id] && thing_menu->use_default_color)\n  {\n    thing_menu->chosen_color = def_colors[chosen_id];\n  }\n  else\n  {\n    thing_menu->chosen_color = buffer->color;\n  }\n\n  // Perhaps put a new blank scroll, robot, or sensor in one of the copies\n  if(chosen_id == SENSOR)\n  {\n    create_blank_sensor_direct(buffer->sensor);\n  }\n  else\n\n  if(is_robot(chosen_id))\n  {\n    if(is_robot(buffer->id))\n      clear_robot_contents(buffer->robot);\n\n    create_blank_robot_direct(buffer->robot,\n     thing_menu->place_x, thing_menu->place_y);\n  }\n  else\n\n  if(is_signscroll(chosen_id))\n  {\n    if(is_signscroll(buffer->id))\n      clear_scroll_contents(buffer->scroll);\n\n    create_blank_scroll_direct(buffer->scroll);\n  }\n\n  buffer->id = chosen_id;\n  buffer->param = -1;\n  change_param(ctx, buffer, &(thing_menu->chosen_param));\n\n  context_callback(ctx, NULL, thing_menu_place_callback);\n}\n\n/**\n * Select a thing from the specified thing menu.\n */\n\nstatic void thing_menu_choose_thing(struct thing_menu_context *thing_menu,\n enum thing_menu_id menu_number)\n{\n  const char *options[MAX_CHOICES];\n  int count;\n  int pos;\n\n  // Find the size of the current menu and make a list readable by the list menu\n  for(count = 0; thing_menus[menu_number][count].option; count++)\n    options[count] = thing_menus[menu_number][count].option;\n\n  pos = list_menu(options, 20, thing_menu_titles[menu_number], 0, count, 27, 0);\n  thing_menu->chosen_pos = pos;\n\n  context_callback((context *)thing_menu, NULL, thing_menu_edit_callback);\n}\n\n/**\n * Exit this context once the callback chain finishes.\n */\n\nstatic void thing_menu_resume(context *ctx)\n{\n  destroy_context(ctx);\n}\n\n/**\n * Wrapper for list_menu. Select and configure type from one of the thing menus\n * into the buffer. Place it onto the board afterward.\n */\n\nvoid thing_menu(context *parent, enum thing_menu_id menu_number,\n struct buffer_info *buffer, boolean use_default_color, int x, int y,\n struct undo_history *history)\n{\n  struct thing_menu_context *thing_menu;\n  struct context_spec spec;\n\n  const struct editor_config_info *editor_conf = get_editor_config();\n  struct world *mzx_world = parent->world;\n  struct board *cur_board = mzx_world->current_board;\n\n  enum thing thing_at_pos =\n   (enum thing)cur_board->level_id[x + (y * cur_board->board_width)];\n\n  if(thing_at_pos == PLAYER && editor_conf->editor_thing_menu_places)\n  {\n    error_message(E_CANT_OVERWRITE_PLAYER, 0, NULL);\n    return;\n  }\n\n  thing_menu = cmalloc(sizeof(struct thing_menu_context));\n  thing_menu->menu_number = menu_number;\n  thing_menu->buffer = buffer;\n  thing_menu->place_x = x;\n  thing_menu->place_y = y;\n  thing_menu->place_history = history;\n  thing_menu->use_default_color = use_default_color;\n  thing_menu->old_id = buffer->id;\n  thing_menu->old_param = buffer->param;\n  thing_menu->old_color = buffer->color;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.resume = thing_menu_resume;\n\n  create_context((context *)thing_menu, parent, &spec, CTX_THING_MENU);\n\n  thing_menu_choose_thing(thing_menu, menu_number);\n}\n"
  },
  {
    "path": "src/editor/buffer.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2018-2019 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_BUFFER_H\n#define __EDITOR_BUFFER_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../core.h\"\n#include \"../world_struct.h\"\n#include \"buffer_struct.h\"\n#include \"edit.h\"\n#include \"undo.h\"\n\nenum thing_menu_id\n{\n  THING_MENU_TERRAIN,\n  THING_MENU_ITEMS,\n  THING_MENU_CREATURES,\n  THING_MENU_PUZZLE,\n  THING_MENU_TRANSPORT,\n  THING_MENU_ELEMENTS,\n  THING_MENU_MISC,\n  THING_MENU_OBJECTS,\n  NUM_THING_MENUS\n};\n\nvoid change_param(context *ctx, struct buffer_info *buffer, int *new_param);\nvoid free_edit_buffer(struct buffer_info *buffer);\n\nint place_current_at_xy(struct world *mzx_world, struct buffer_info *buffer,\n int x, int y, enum editor_mode mode, struct undo_history *history);\nint replace_current_at_xy(struct world *mzx_world, struct buffer_info *buffer,\n int x, int y, enum editor_mode mode, struct undo_history *history);\n\nvoid grab_at_xy(struct world *mzx_world, struct buffer_info *buffer,\n int x, int y, enum editor_mode mode);\n\nvoid thing_menu(context *parent, enum thing_menu_id menu_number,\n struct buffer_info *buffer, boolean use_default_color, int x, int y,\n struct undo_history *history);\n\n__M_END_DECLS\n\n#endif /* __EDITOR_BUFFER_H */\n"
  },
  {
    "path": "src/editor/buffer_struct.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_BUFFER_STRUCT_H\n#define __EDITOR_BUFFER_STRUCT_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../data.h\"\n#include \"../robot_struct.h\"\n\nstruct buffer_info\n{\n  struct robot *robot;\n  struct scroll *scroll;\n  struct sensor *sensor;\n  enum thing id;\n  int color;\n  int param;\n};\n\n__M_END_DECLS\n\n#endif /* __EDITOR_BUFFER_STRUCT_H */\n"
  },
  {
    "path": "src/editor/char_ed.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick - exophase@adelphia.net\n * Copyright (C) 2017-2022 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"char_ed.h\"\n\n#include \"../core.h\"\n#include \"../data.h\"\n#include \"../event.h\"\n#include \"../graphics.h\"\n#include \"../helpsys.h\"\n#include \"../util.h\"\n#include \"../window.h\"\n#include \"../world.h\"\n#include \"../io/path.h\"\n#include \"../io/vio.h\"\n\n#include \"configure.h\"\n#include \"graphics.h\"\n#include \"undo.h\"\n#include \"window.h\"\n\n#include <limits.h>\n#include <math.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n\n//----------------------------------------------\n//  +----------------+ Current char-     (#000)\n//  |................|    ..........0..........   Alternate:\n//  |................|              ^\n//  |................| %%      Move cursor        Alt+F   'Fill'\n//  |................| -+      Change char        Alt+%%  Shift char\n//  |................| Space   Toggle pixel       Ctrl+Z  Undo\n//  |................| Enter   Select char        Ctrl+Y  Redo\n//  |................| Del     Clear char         Alt+B   Block (Shift+%%)\n//  |................| N       'Negative'         F2      Copy to buffer\n//  |................| M       'Mirror'           F3      Copy from buffer\n//  |................| F       'Flip'             F4      Revert to ASCII\n//  |................| H       More help          F5      Revert to MZX\n//  |................|\n//  |................| C       Palette [ ]\n//  |................| Tab     Draw    (off)\n//  +----------------+ Ins/1-4 Select  [  ]\n//----------------------------------------------\n\n//Mouse- Menu=Command\n//       Grid=Toggle\n//       \"Current Char-\"=Enter\n//       \"..0..\"=Change char\n\n#define CHARS_SHOW_WIDTH 22\n\nuint32_t mini_draw_layer = -1;\nuint32_t mini_highlight_layer = -1;\nstatic char char_copy_buffer[8 * 14 * 32];\nstatic int char_copy_width = 8;\nstatic int char_copy_height = 14;\nstatic int char_copy_mode = 0;\nstatic char current_char = 1;\nstatic int current_width = 1;\nstatic int current_height = 1;\nstatic int highlight_width = 1;\nstatic int highlight_height = 1;\nstatic int current_charset = 0;\nstatic int current_palette = 0x08;\nstatic int use_default_palette = 1;\nstatic int export_mode = 0;\nstatic int num_files = 1;\n\nstatic int char_copy_use_offset = 0;\n\nstatic int selected_subdivision[19] =\n{\n  -1, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, -1, 0, -1, -1, 0, -1, -1, 0\n};\n\nstatic const char *chr_ext[] = { \".CHR\", NULL };\n\nstatic const char *help_text[3] =\n{\n  \"\\x12\\x1d\\t Move cursor\\n\"\n  \"-+\\t Change char\\n\"\n  \"Space   Toggle pixel\\n\"\n  \"Enter   Select char\\n\"\n  \"Del\\tClear char\\n\"\n  \"N\\t  'Negative'\\n\"\n  \"M\\t  'Mirror'\\n\"\n  \"F\\t  'Flip'\\n\"\n  \"H\\t  More help\\n\"\n  \"\\n\"\n  \"C\\t  Palette (   )\\n\"\n  \"Tab\\tDraw\"\n  ,\n  \"Alt+F   'Fill'\\n\"\n  \"Alt+\\x12\\x1d  Shift char\\n\"\n  \"Alt+B   Block (Shift+\\x12\\x1d)\\n\"\n  \"F2\\t Copy to buffer\\n\"\n  \"F3\\t Copy from buffer\\n\"\n  \"F4\\t Revert to ASCII\\n\"\n  \"F5\\t Revert to MZX\\n\"\n  \"\\n\"\n  \"H\\t  More help\\n\"\n  \"\\n\"\n  \"C\\t  Palette (   )\\n\"\n  \"Tab\\tDraw\"\n  ,\n  \"PgUp    Prev. charset\\n\"\n  \"PgDn    Next charset\\n\"\n  \"Alt+X   Export\\n\"\n  \"Alt+I   Import\\n\"\n  \"Ctrl+Z  Undo\\n\"\n  \"Ctrl+Y  Redo\\n\"\n  \"\\n\"\n  \"\\n\"\n  \"H\\t  Back\\n\"\n  \"\\n\"\n  \"C\\t  Palette (   )\\n\"\n  \"Tab\\tDraw\"\n};\n\nstatic const char *help_text_smzx = \"Ins/1-4 Select\";\n\nstatic const int prev_pixel[] = { 3, 2, 0, 1 };\nstatic const int next_pixel[] = { 2, 3, 1, 0 };\n\nstatic void char_editor_default_colors(void)\n{\n  // Char display\n  set_protected_rgb(2, 21, 21, 21);\n  set_protected_rgb(3, 35, 35, 35);\n  set_protected_rgb(4, 28, 28, 28);\n  set_protected_rgb(5, 42, 42, 42);\n  // Selection\n  set_protected_rgb(6, 14, 42, 56);\n  set_protected_rgb(13, 7, 21, 49);\n}\n\nstatic void copy_color_to_protected(unsigned int from, unsigned int to)\n{\n  // Hack\n  memcpy(&(graphics.protected_palette[to]), &(graphics.palette[from]),\n   sizeof(struct rgb_color));\n}\n\nstatic void char_editor_update_colors(void)\n{\n  // Char display\n  switch(get_screen_mode())\n  {\n    case 1:\n    {\n      // Interpolate the middle colors\n      unsigned int upper = (current_palette & 0xF0) >> 4;\n      unsigned int lower = (current_palette & 0x0F);\n      uint8_t r0, g0, b0;\n      uint8_t r3, g3, b3;\n\n      get_rgb(upper, &r0, &g0, &b0);\n      get_rgb(lower, &r3, &g3, &b3);\n\n      copy_color_to_protected(upper, 2);\n      set_protected_rgb(3, (r3*2 + r0)/3, (g3*2 + g0)/3, (b3*2 + b0)/3);\n      set_protected_rgb(4, (r0*2 + r3)/3, (g0*2 + g3)/3, (b0*2 + b3)/3);\n      copy_color_to_protected(lower, 5);\n      break;\n    }\n\n    case 2:\n    case 3:\n    {\n      // Hack\n      uint8_t *index = graphics.smzx_indices + (current_palette * 4);\n\n      copy_color_to_protected(*(index++), 2);\n      copy_color_to_protected(*(index++), 3);\n      copy_color_to_protected(*(index++), 4);\n      copy_color_to_protected(*(index++), 5);\n      break;\n    }\n  }\n\n  // Selection\n  set_protected_rgb(6, 14, 42, 56);\n  set_protected_rgb(13, 7, 21, 49);\n}\n\nstatic void fill_region(char *buffer, int x, int y,\n int buffer_width, int buffer_height, int check, int draw)\n{\n  int offset = x + (y * buffer_width);\n  if((x >= 0) && (x < buffer_width) &&\n   (y >= 0) && (y < buffer_height) &&\n   (buffer[offset] == check))\n  {\n    buffer[offset] = draw;\n\n    fill_region(buffer, x - 1, y, buffer_width,\n     buffer_height, check, draw);\n    fill_region(buffer, x + 1, y, buffer_width,\n     buffer_height, check, draw);\n    fill_region(buffer, x, y - 1, buffer_width,\n     buffer_height, check, draw);\n    fill_region(buffer, x, y + 1, buffer_width,\n     buffer_height, check, draw);\n  }\n}\n\nstatic void expand_buffer(char *buffer, int width, int height,\n int cwidth, int cheight, int current_char, int current_charset)\n{\n  char char_buffer[32 * 14];\n  char *buffer_ptr = buffer;\n  char *cbuffer_ptr = char_buffer;\n  int cx, cy, x, y, i, shift;\n  int cskip = 32 - cwidth;\n  char *last_ptr, *last_ptr2;\n\n  for(cy = 0; cy < cheight; cy++, current_char += cskip)\n  {\n    for(cx = 0; cx < cwidth; cx++, current_char++, cbuffer_ptr += 14)\n    {\n      // Bind to the current charset\n      current_char &= 0xFF;\n      ec_read_char(current_char + (current_charset * 256), cbuffer_ptr);\n    }\n  }\n\n  if(get_screen_mode())\n  {\n    int skip = (width - 1) * 4;\n    int skip2 = width * 4 * 14;\n\n    for(cbuffer_ptr = char_buffer, y = 0; y < height; y++)\n    {\n      last_ptr = buffer_ptr;\n      for(x = 0; x < width; x++)\n      {\n        last_ptr2 = buffer_ptr;\n        for(i = 0; i < 14; i++, cbuffer_ptr++,\n         buffer_ptr += skip)\n        {\n          for(shift = 6; shift >= 0; shift -= 2, buffer_ptr++)\n          {\n            *buffer_ptr = ((*cbuffer_ptr) >> shift) & 3;\n          }\n        }\n        buffer_ptr = last_ptr2 + 4;\n      }\n\n      buffer_ptr = last_ptr + skip2;\n    }\n  }\n  else\n  {\n    int skip = (width - 1) * 8;\n    int skip2 = width * 8 * 14;\n\n    for(cbuffer_ptr = char_buffer, y = 0; y < height; y++)\n    {\n      last_ptr = buffer_ptr;\n      for(x = 0; x < width; x++)\n      {\n        last_ptr2 = buffer_ptr;\n        for(i = 0; i < 14; i++, cbuffer_ptr++,\n         buffer_ptr += skip)\n        {\n          for(shift = 7; shift >= 0; shift--, buffer_ptr++)\n          {\n            *buffer_ptr = ((*cbuffer_ptr) >> shift) & 1;\n          }\n        }\n        buffer_ptr = last_ptr2 + 8;\n      }\n\n      buffer_ptr = last_ptr + skip2;\n    }\n  }\n}\n\nstatic void collapse_buffer(char *buffer, int width, int height,\n int cwidth, int cheight, int current_char, int current_charset,\n struct undo_history *h)\n{\n  char char_buffer[32 * 14];\n  char *buffer_ptr = buffer;\n  char *cbuffer_ptr = char_buffer;\n  int cx, cy, x, y, i, shift;\n  int cskip = 32 - cwidth;\n  char *last_ptr, *last_ptr2;\n  int current_row;\n\n  if(h)\n    add_charset_undo_frame(h, current_charset, current_char, cwidth, cheight);\n\n  if(get_screen_mode())\n  {\n    int skip = (width - 1) * 4;\n    int skip2 = width * 4 * 14;\n\n    for(y = 0; y < height; y++)\n    {\n      last_ptr = buffer_ptr;\n      for(x = 0; x < width; x++)\n      {\n        last_ptr2 = buffer_ptr;\n        for(i = 0; i < 14; i++, cbuffer_ptr++,\n         buffer_ptr += skip)\n        {\n          current_row = 0;\n          for(shift = 6; shift >= 0; shift -= 2, buffer_ptr++)\n          {\n            current_row |= (*buffer_ptr) << shift;\n          }\n          *cbuffer_ptr = current_row;\n        }\n        buffer_ptr = last_ptr2 + 4;\n      }\n\n      buffer_ptr = last_ptr + skip2;\n    }\n  }\n  else\n  {\n    int skip = (width - 1) * 8;\n    int skip2 = width * 8 * 14;\n\n    for(y = 0; y < height; y++)\n    {\n      last_ptr = buffer_ptr;\n      for(x = 0; x < width; x++)\n      {\n        last_ptr2 = buffer_ptr;\n        for(i = 0; i < 14; i++, cbuffer_ptr++,\n         buffer_ptr += skip)\n        {\n          current_row = 0;\n          for(shift = 7; shift >= 0; shift--, buffer_ptr++)\n          {\n            current_row |= (*buffer_ptr) << shift;\n          }\n          *cbuffer_ptr = current_row;\n        }\n        buffer_ptr = last_ptr2 + 8;\n      }\n\n      buffer_ptr = last_ptr + skip2;\n    }\n  }\n\n  cbuffer_ptr = char_buffer;\n\n  for(cy = 0; cy < cheight; cy++, current_char += cskip)\n  {\n    for(cx = 0; cx < cwidth; cx++, current_char++, cbuffer_ptr += 14)\n    {\n      // Bind to the current charset\n      current_char &= 0xFF;\n      ec_change_char(current_char + (current_charset * 256), cbuffer_ptr);\n    }\n  }\n\n  if(h)\n    update_undo_frame(h);\n}\n\nstatic void change_copy_buffer_mode(int new_mode)\n{\n  char *old_pos;\n  char *new_pos;\n\n  /* The SMZX copy buffer is stored different from the regular MZX copy buffer,\n   * which causes issues when attempting to copy between the two. Convert\n   * between them as-needed to fix this.\n   */\n\n  if(new_mode == 0 && char_copy_mode != 0)\n  {\n    old_pos = char_copy_buffer + char_copy_width * char_copy_height;\n    new_pos = char_copy_buffer + char_copy_width * char_copy_height * 2;\n\n    while(old_pos > char_copy_buffer)\n    {\n      old_pos--;\n      new_pos -= 2;\n      new_pos[0] = (*old_pos & 0x02) >> 1;\n      new_pos[1] = (*old_pos & 0x01);\n    }\n    char_copy_width *= 2;\n  }\n  else\n\n  if(new_mode != 0 && char_copy_mode == 0)\n  {\n    char *stop = char_copy_buffer + char_copy_width * char_copy_height;\n    old_pos = char_copy_buffer;\n    new_pos = char_copy_buffer;\n\n    while(old_pos < stop)\n    {\n      *new_pos = ((old_pos[0] & 0x01) << 1) | (old_pos[1] & 0x01);\n      old_pos += 2;\n      new_pos++;\n    }\n    char_copy_width /= 2;\n  }\n\n  char_copy_mode = new_mode;\n}\n\nstatic void draw_multichar(char *buffer, int start_x, int start_y,\n int width, int height, int current_x, int current_y,\n int highlight_width, int highlight_height)\n{\n  char *buffer_ptr = buffer;\n  int buffer_width = width * 8;\n  char highlight[2] = { 0x11, 0x1B };\n  char colors[2];\n  int c_offset;\n  int color;\n  int x, y;\n\n  if(use_default_palette)\n  {\n    colors[0] = 0x18;\n    colors[1] = 0x17;\n    color = 0x87;\n    c_offset = 16;\n  }\n  else\n  {\n    color = current_palette;\n    colors[0] = (color & 0xF0) >> 4;\n    colors[1] = (color & 0x0F);\n    c_offset = 0;\n  }\n\n  if((height == 1) && (width <= 3))\n  {\n    char chars[2] = { 250, 219 };\n    int index;\n\n    for(y = 0; y < 14; y++)\n    {\n      for(x = 0; x < (width * 8); x++, buffer_ptr++)\n      {\n        int draw_x = start_x + (x * 2);\n        int draw_y = start_y + y;\n        index = *buffer_ptr;\n        draw_char_ext(chars[index], color, draw_x,     draw_y, PRO_CH, c_offset);\n        draw_char_ext(chars[index], color, draw_x + 1, draw_y, PRO_CH, c_offset);\n      }\n    }\n\n    for(y = 0; y < highlight_height; y++)\n    {\n      color_line(highlight_width * 2, start_x + (current_x * 2),\n       start_y + current_y + y, 0x1B);\n    }\n  }\n  else\n  {\n    char half_chars[4] = { 0x20, 0xDF, 0xDC, 0xDB };\n    int current_char;\n    int upper;\n    int lower;\n\n    for(y = 0; y < (height * 7); y++, buffer_ptr += buffer_width)\n    {\n      for(x = 0; x < buffer_width; x++, buffer_ptr++)\n      {\n        upper = *buffer_ptr;\n        lower = *(buffer_ptr + buffer_width);\n        current_char = (lower << 1) + upper;\n\n        draw_char_ext(half_chars[current_char], color,\n         start_x + x, start_y + y, PRO_CH, c_offset);\n      }\n    }\n\n    // Start at the top\n    y = highlight_height;\n    if(current_y & 1)\n    {\n      buffer_ptr = buffer + ((current_y - 1) * buffer_width) + current_x;\n\n      for(x = 0; x < highlight_width; x++, buffer_ptr++)\n      {\n        upper = *buffer_ptr;\n        lower = *(buffer_ptr + buffer_width);\n\n        draw_char_mixed_pal_ext(half_chars[2], colors[upper], highlight[lower],\n         start_x + x + current_x, start_y + (current_y / 2), PRO_CH);\n      }\n      current_y++;\n      y--;\n    }\n\n    while(y > 1)\n    {\n      buffer_ptr = buffer + (current_y * buffer_width) + current_x;\n      color_line(highlight_width, start_x + current_x,\n       start_y + (current_y / 2), 0x1B);\n      current_y += 2;\n      y -= 2;\n    }\n\n    if(y)\n    {\n      buffer_ptr = buffer + (current_y * buffer_width) + current_x;\n\n      for(x = 0; x < highlight_width; x++, buffer_ptr++)\n      {\n        upper = *buffer_ptr;\n        lower = *(buffer_ptr + buffer_width);\n\n        draw_char_mixed_pal_ext(half_chars[1], colors[lower], highlight[upper],\n         start_x + x + current_x, start_y + (current_y / 2), PRO_CH);\n      }\n    }\n  }\n}\n\nstatic void draw_multichar_smzx(char *buffer, int start_x, int start_y,\n int width, int height, int current_x, int current_y,\n int highlight_width, int highlight_height)\n{\n  char *buffer_ptr = buffer;\n  char current_color;\n  char base_colors[4] = { 0x2, 0x3, 0x4, 0x5 };\n  char highlight_colors[4] = { 0x1, 0x6, 0xD, 0xB };\n  int bg_color = (base_colors[0] << 4) + base_colors[3];\n  int buffer_width = width * 4;\n\n  int x, y;\n  int draw_x, draw_y;\n\n  if((height == 1) && (width <= 3))\n  {\n    int skip = buffer_width - highlight_width;\n\n    int current_pixel;\n    for(y = 0; y < 14; y++)\n    {\n      for(x = 0; x < buffer_width; x++, buffer_ptr++)\n      {\n        current_pixel = *buffer_ptr;\n\n        if(current_pixel)\n        {\n          write_string(\"\\xDB\\xDB\\xDB\\xDB\", start_x + (x * 4),\n           start_y + y, base_colors[current_pixel], WR_NONE);\n        }\n        else\n        {\n          write_string(\"\\xFA\\xFA\\xFA\\xFA\", start_x + (x * 4),\n           start_y + y, bg_color, WR_NONE);\n        }\n      }\n    }\n\n    buffer_ptr = buffer + current_x + ((current_y) * buffer_width);\n\n    for(y = 0; y < highlight_height; y++, buffer_ptr += skip)\n    {\n      for(x = 0; x < highlight_width; x++, buffer_ptr++)\n      {\n        current_pixel = *buffer_ptr;\n\n        if(current_pixel)\n        {\n          write_string(\"\\xDB\\xDB\\xDB\\xDB\", start_x +\n           ((x + current_x) * 4), start_y + current_y + y,\n           highlight_colors[current_pixel], WR_NONE);\n        }\n        else\n        {\n          write_string(\"\\xFA\\xFA\\xFA\\xFA\", start_x +\n           ((x + current_x) * 4), start_y + y + current_y,\n           0x1B, WR_NONE);\n        }\n      }\n    }\n  }\n  else\n  {\n    int skip = buffer_width - highlight_width;\n\n    for(y = 0; y < (height * 7); y++, buffer_ptr += buffer_width)\n    {\n      for(x = 0; x < buffer_width; x++, buffer_ptr++)\n      {\n        current_color = (base_colors[(int)(*buffer_ptr)]) |\n         ((base_colors[(int)(*(buffer_ptr + buffer_width))]) << 4);\n\n        draw_char(223, current_color, start_x + (x * 2),     start_y + y);\n        draw_char(223, current_color, start_x + (x * 2) + 1, start_y + y);\n      }\n    }\n\n    // Start at the top\n    y = highlight_height;\n    if(current_y & 1)\n    {\n      buffer_ptr = buffer + current_x +\n       ((current_y & ~1) * buffer_width);\n\n      for(x = 0; x < highlight_width; x++, buffer_ptr++)\n      {\n        current_color = (base_colors[(int)(*buffer_ptr)]) |\n         ((highlight_colors[(int)(*(buffer_ptr + buffer_width))]) << 4);\n\n        draw_x = start_x + (current_x + x) * 2;\n        draw_y = start_y + (current_y / 2);\n        draw_char(223, current_color, draw_x,     draw_y);\n        draw_char(223, current_color, draw_x + 1, draw_y);\n      }\n\n      buffer_ptr += (skip * 2);\n      y--;\n\n      current_y++;\n    }\n\n    while(y > 1)\n    {\n      buffer_ptr = buffer + current_x +\n       ((current_y & ~1) * buffer_width);\n\n      for(x = 0; x < highlight_width; x++, buffer_ptr++)\n      {\n        current_color = (highlight_colors[(int)(*buffer_ptr)]) |\n         ((highlight_colors[(int)(*(buffer_ptr + buffer_width))]) << 4);\n\n        draw_x = start_x + (current_x + x) * 2;\n        draw_y = start_y + (current_y / 2);\n        draw_char(223, current_color, draw_x,     draw_y);\n        draw_char(223, current_color, draw_x + 1, draw_y);\n      }\n      buffer_ptr += (skip * 2);\n      current_y += 2;\n      y -= 2;\n    }\n\n    if(y)\n    {\n      buffer_ptr = buffer + current_x +\n       ((current_y & ~1) * buffer_width);\n\n      for(x = 0; x < highlight_width; x++, buffer_ptr++)\n      {\n        current_color = (highlight_colors[(int)(*buffer_ptr)]) |\n         ((base_colors[(int)(*(buffer_ptr + buffer_width))]) << 4);\n\n        draw_x = start_x + (current_x + x) * 2;\n        draw_y = start_y + (current_y / 2);\n        draw_char(223, current_color, draw_x,     draw_y);\n        draw_char(223, current_color, draw_x + 1, draw_y);\n      }\n    }\n  }\n}\n\nstatic void draw_mini_buffer(int info_x, int info_y, int current_charset,\n int current_char, int current_width, int current_height,\n int highlight_width, int highlight_height)\n{\n  char mini_buffer[32];\n  int chars_show_width;\n  int chars_show_num;\n  int chars_show_x;\n  int chars_show_y;\n  int highlight_num;\n  int highlight_pos;\n  uint8_t draw_char;\n  int offset;\n  int x;\n  int y;\n  int i;\n\n  // 0- not highlight, 1- highlight\n  char use_color[] = { 0x80, 0x8F };\n  char use_offset[] = { 16, 16 };\n  uint32_t use_layer[] = { 0, 0 };\n  int is_highlight;\n\n  mini_draw_layer = create_layer(0, 0, 80, 25, LAYER_DRAWORDER_UI - 10,\n   -1, (current_charset * 256), 1);\n\n  set_layer_mode(mini_draw_layer, 0);\n  use_layer[0] = mini_draw_layer;\n  use_layer[1] = mini_draw_layer;\n\n  if(!use_default_palette)\n  {\n    // Apply user-defined palette to the highlight and create a layer for it\n    mini_highlight_layer = create_layer(0, 0, 80, 25, LAYER_DRAWORDER_UI - 9,\n     -1, (current_charset * 256), 1);\n\n    use_layer[1] = mini_highlight_layer;\n    use_color[1] = current_palette;\n    use_offset[1] = 0;\n  }\n  else\n  {\n    // The protected palette and SMZX modes simply do not work well together.\n    mini_highlight_layer = -1;\n  }\n\n  // Mini buffer\n  for(y = 0, offset = 0; y < highlight_height; y++)\n  {\n    for(x = 0; x < highlight_width; x++, offset++)\n    {\n      mini_buffer[offset] = current_char + x + (y * 32);\n    }\n  }\n\n  for(y = 0, offset = 0; y < current_height; y++)\n  {\n    for(x = 0; x < current_width; x++, offset++)\n    {\n      select_layer(UI_LAYER);\n      erase_char(info_x + x, info_y + y + 1);\n\n      select_layer(use_layer[1]);\n\n      draw_char_ext(mini_buffer[offset], use_color[1],\n       info_x + x, info_y + y + 1, 0, use_offset[1]);\n    }\n  }\n\n  // Charset display\n  chars_show_width = CHARS_SHOW_WIDTH - current_width;\n  chars_show_x = info_x + current_width + 2;\n  chars_show_y = info_y + 1;\n\n  chars_show_num = (chars_show_width + highlight_width - 1) / highlight_width;\n  chars_show_num |= 1;\n\n  highlight_pos = (chars_show_width - highlight_width) / 2;\n  highlight_num = (chars_show_num + 1) / 2 - 1;\n\n  // Select \"tiles\" backward from the highlight\n  for(i = 0; i < highlight_num; i++)\n    current_char = char_select_next_tile(current_char, -1,\n     highlight_width, highlight_height);\n\n  for(i = 0; i < chars_show_num; i++)\n  {\n    // Adjust by highlight_pos; the current char should be in the center\n    int adj_x = highlight_pos + (i - highlight_num)*highlight_width;\n\n    is_highlight = (i == highlight_num);\n\n    // Draw the current \"tile\"\n    for(x = 0; x < highlight_width; x++)\n    {\n      // Ignore chars outside of the drawing area\n      if((adj_x + x < 0) || (adj_x + x >= chars_show_width))\n        continue;\n\n      for(y = 0; y < highlight_height; y++)\n      {\n        draw_char = current_char + x + (y * 32);\n\n        select_layer(UI_LAYER);\n        erase_char(chars_show_x + adj_x + x, chars_show_y + y);\n\n        select_layer(use_layer[is_highlight]);\n\n        draw_char_ext(draw_char, use_color[is_highlight],\n         chars_show_x + adj_x + x, chars_show_y + y,\n         0, use_offset[is_highlight]);\n      }\n    }\n\n    // Select the next \"tile\"\n    current_char = char_select_next_tile(current_char, 1,\n     highlight_width, highlight_height);\n  }\n}\n\nstatic boolean replace_filenum(char *dest, size_t dest_sz, const char *src, int num)\n{\n  char *pos = dest;\n  size_t pos_left = dest_sz;\n\n  while(*src)\n  {\n    size_t next = strcspn(src, \"#\");\n    if(next > 0)\n    {\n      if(next >= pos_left)\n        return false;\n\n      memcpy(pos, src, next);\n      pos[next] = '\\0';\n\n      src += next;\n      pos += next;\n      pos_left -= next;\n    }\n\n    if(*src == '#')\n    {\n      int out = snprintf(pos, pos_left, \"%d\", num);\n      if((size_t)out >= pos_left)\n        return false;\n\n      pos += out;\n      pos_left -= out;\n      src++;\n    }\n  }\n  *pos = '\\0';\n\n  if(!path_force_ext(dest, dest_sz, \".chr\"))\n    return false;\n\n#if defined(_WIN32) || defined(CONFIG_DJGPP)\n  /* Do not attempt to read/write to DOS devices. */\n  if(path_safety_check(dest, PATH_SAFE_DOS_DEVICE))\n    return false;\n#endif\n\n  return true;\n}\n\nstatic int select_export_mode(struct world *mzx_world, const char *title)\n{\n  struct dialog di;\n  int result;\n\n  const char *choices[] =\n  {\n    \"Current selection (tile)\",\n    \"Offset (linear)\",\n  };\n\n  struct element *elements[] =\n  {\n    construct_button( 5, 5, \"  OK  \", 0),\n    construct_button(19, 5, \"Cancel\", -1),\n    construct_radio_button(2, 2, choices, ARRAY_SIZE(choices), 24, &export_mode)\n  };\n\n  construct_dialog(&di, title, 24, 9, 32, 7,\n   elements, ARRAY_SIZE(elements), 2);\n\n  result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  force_release_all_keys();\n\n  if(!result)\n  {\n    return export_mode;\n  }\n\n  return result;\n}\n\nstatic int char_import_tile(const char *name, int char_offset, int charset,\n int highlight_width, int highlight_height)\n{\n  vfile *vf = vfopen_unsafe(name, \"rb\");\n  if(vf)\n  {\n    size_t buffer_size = highlight_width * highlight_height * CHAR_SIZE;\n    char *buffer = ccalloc(buffer_size, 1);\n\n    size_t data_size = vfilelength(vf, true);\n\n    if(data_size > buffer_size)\n      data_size = buffer_size;\n\n    data_size = vfread(buffer, 1, data_size, vf);\n    vfclose(vf);\n\n    ec_change_block((uint8_t)char_offset, (uint8_t)charset,\n     (uint8_t)highlight_width, (uint8_t)highlight_height, buffer);\n\n    free(buffer);\n    return 0;\n  }\n  return -1;\n}\n\nstatic void char_export_tile(const char *name, int char_offset, int charset,\n int highlight_width, int highlight_height)\n{\n  vfile *vf = vfopen_unsafe(name, \"wb\");\n  if(vf)\n  {\n    size_t buffer_size = highlight_width * highlight_height * CHAR_SIZE;\n    char *buffer = ccalloc(buffer_size, 1);\n\n    ec_read_block((uint8_t)char_offset, (uint8_t)charset,\n     (uint8_t)highlight_width, (uint8_t)highlight_height, buffer);\n\n    vfwrite(buffer, 1, buffer_size, vf);\n    vfclose(vf);\n\n    free(buffer);\n  }\n}\n\nstatic void char_import(struct world *mzx_world, int char_offset, int charset,\n int highlight_width, int highlight_height, struct undo_history *h)\n{\n  static char saved_import_string[MAX_PATH];\n  char import_string[256] = { 0, };\n  char import_name[256];\n  int current_file = 0;\n  int i;\n\n  struct element *elements[5];\n  char offset_buf[20];\n  char size_buf[20];\n\n  int import_mode = 1;\n\n  // Default to linear for 1x1 selections, otherwise prompt.\n  if(highlight_width * highlight_height != 1)\n    import_mode = select_export_mode(mzx_world, \"Import mode\");\n\n  switch(import_mode)\n  {\n    case -1:\n      return;\n\n    case 0:\n      sprintf(offset_buf, \"~9Offset:  ~f%d\", charset * 256 + char_offset);\n      sprintf(size_buf,   \"~9Size:    ~f%d x %d\",\n       highlight_width, highlight_height);\n\n      elements[0] = construct_label(3, 20, offset_buf);\n      elements[1] = construct_label(3, 21, size_buf);\n      break;\n\n    case 1:\n      elements[0] = construct_number_box(3, 20, \"Offset:  \", 0, 255, NUMBER_BOX,\n       &char_offset);\n      elements[1] = construct_label(3, 21, \"~9Size:    auto\");\n      break;\n  }\n\n  elements[2] =\n   construct_number_box(28, 20, \"First: \", 0, 255, NUMBER_BOX, &current_file);\n  elements[3] =\n   construct_number_box(52, 20, \"Count: \", 1, 256, NUMBER_BOX, &num_files);\n  elements[4] =\n   construct_label(28, 21, \"~9Use \\\"file#.chr\\\" to import multiple charsets.\");\n\n  strcpy(import_string, saved_import_string);\n  if(!file_manager(mzx_world, chr_ext, NULL, import_string,\n   \"Import character set(s)\", ALLOW_ALL_DIRS, ALLOW_WILDCARD_FILES,\n   elements, ARRAY_SIZE(elements), 2))\n  {\n    int num_files_present = num_files;\n\n    strcpy(saved_import_string, import_string);\n    if(strchr(import_string, '#') == NULL)\n      num_files_present = 1;\n\n    // Save the whole charset to the history to keep things simple\n    add_charset_undo_frame(h, charset, 0, 32, 8);\n\n    for(i = 0; i < num_files_present; i++, current_file++)\n    {\n      if(!replace_filenum(import_name, sizeof(import_name), import_string, current_file))\n        continue;\n\n      if(import_mode)\n      {\n        // Version needs to be sufficiently high for extended charsets\n        int char_size =\n         ec_load_set_var(import_name, charset * 256 + char_offset, INT_MAX);\n\n        if(char_size != -1)\n        {\n          char_offset += char_size;\n          char_offset &= 0xFF;\n        }\n      }\n      else\n      {\n        int ret = char_import_tile(import_name, char_offset, charset,\n         highlight_width, highlight_height);\n\n        if(ret != -1)\n        {\n          char_offset = char_select_next_tile(char_offset, 1,\n           highlight_width, highlight_height);\n        }\n      }\n    }\n\n    update_undo_frame(h);\n  }\n}\n\nstatic void char_export(struct world *mzx_world, int char_offset, int charset,\n int highlight_width, int highlight_height)\n{\n  static char saved_export_string[MAX_PATH];\n  char export_string[256] = { 0, };\n  char export_name[256];\n  int current_file = 0;\n  int char_size = highlight_width * highlight_height;\n  int i;\n\n  struct element *elements[5];\n  char offset_buf[20];\n  char size_buf[20];\n  int export_mode = 1;\n\n  if(highlight_height > 1)\n    export_mode = select_export_mode(mzx_world, \"Export mode\");\n\n  switch(export_mode)\n  {\n    case -1:\n      return;\n\n    case 0:\n      snprintf(offset_buf, 20, \"~9Offset:  ~f%d\", charset * 256 + char_offset);\n      snprintf(size_buf,   20, \"~9Size:    ~f%d x %d\",\n       highlight_width, highlight_height);\n\n      elements[0] = construct_label(3, 20, offset_buf);\n      elements[1] = construct_label(3, 21, size_buf);\n      break;\n\n    case 1:\n      elements[0] = construct_number_box(3, 20, \"Offset:  \", 0, 255, NUMBER_BOX,\n       &char_offset);\n      elements[1] = construct_number_box(3, 21, \"Size:    \", 1, 256, NUMBER_BOX,\n       &char_size);\n      break;\n  }\n\n  elements[2] =\n   construct_number_box(28, 20, \"First: \", 0, 255, NUMBER_BOX, &current_file);\n  elements[3] =\n   construct_number_box(52, 20, \"Count: \", 1, 256, NUMBER_BOX, &num_files);\n  elements[4] =\n   construct_label(28, 21, \"~9Use \\\"file#.chr\\\" to export multiple charsets.\");\n\n  strcpy(export_string, saved_export_string);\n  if(!file_manager(mzx_world, chr_ext, \".chr\", export_string,\n   \"Export character set(s)\", ALLOW_ALL_DIRS, ALLOW_NEW_FILES,\n   elements, ARRAY_SIZE(elements), 2))\n  {\n    int num_files_present = num_files;\n\n    strcpy(saved_export_string, export_string);\n    if(strchr(export_string, '#') == NULL)\n      num_files_present = 1;\n\n    for(i = 0; i < num_files_present; i++, current_file++)\n    {\n      if(!replace_filenum(export_name, sizeof(export_name), export_string, current_file))\n        continue;\n\n      if(export_mode)\n      {\n        ec_save_set_var(export_name, charset * 256 + char_offset, char_size);\n        char_offset += char_size;\n        char_offset &= 0xFF;\n      }\n      else\n      {\n        char_export_tile(export_name, char_offset, charset,\n         highlight_width, highlight_height);\n\n        char_offset = char_select_next_tile(char_offset, 1,\n         highlight_width, highlight_height);\n      }\n    }\n  }\n}\n\nint char_editor(struct world *mzx_world)\n{\n  struct undo_history *h;\n  int changed = 1;\n\n  int x = 0;\n  int y = 0;\n  int draw = 0;\n  int draw_new;\n  int chars_width, chars_height;\n  int chars_x, chars_y;\n  int info_height;\n  int info_x, info_y;\n  int buffer_width = current_width * 8;\n  int buffer_height = current_height * 14;\n  int dialog_width, dialog_height;\n  int dialog_x, dialog_y;\n  char *buffer = cmalloc(8 * 14 * 32);\n  int last_x = -1, last_y = 0;\n  int block_x = 0;\n  int block_y = 0;\n  int block_start_x = 0;\n  int block_start_y = 0;\n  int block_width = buffer_width;\n  int block_height = buffer_height;\n  int block_mode = 0;\n  int shifted = 0;\n  int i, i2, key;\n  int joystick_key;\n  int pad_height;\n  int small_chars;\n  int screen_mode = get_screen_mode();\n  int current_pixel = 0;\n  char smzx_colors[4] = { 0x2, 0x3, 0x4, 0x5 };\n  int help_page = 0;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  // Prepare the history\n  h = construct_charset_undo_history(get_editor_config()->undo_history_size);\n\n  // Make sure the copy buffer is in a usable format\n  change_copy_buffer_mode(screen_mode);\n\n  if(screen_mode)\n  {\n    // Set up colors for SMZX char editing\n    if(use_default_palette)\n      char_editor_default_colors();\n\n    else\n      char_editor_update_colors();\n  }\n\n  set_context(CTX_CHAR_EDIT);\n\n  m_show();\n  // Get char\n  expand_buffer(buffer, current_width, current_height,\n   highlight_width, highlight_height, current_char,\n   current_charset);\n\n  save_screen();\n\n  do\n  {\n    buffer_width = current_width * 8;\n    buffer_height = current_height * 14;\n\n    if((current_height == 1) &&\n     (current_width <= 3))\n    {\n      small_chars = 0;\n      mouse_size(8, 14);\n      chars_width = buffer_width * 2;\n      chars_height = buffer_height;\n    }\n    else\n    {\n      small_chars = 1;\n      mouse_size(8, 7);\n      chars_width = buffer_width;\n      chars_height = buffer_height / 2;\n    }\n\n    if(screen_mode)\n      buffer_width /= 2;\n\n    if(x >= buffer_width)\n      x = buffer_width - 1;\n\n    if(y >= buffer_height)\n      y = buffer_height - 1;\n\n    dialog_width = chars_width + 4 + 27;\n    dialog_height = chars_height + 4;\n    pad_height = current_height;\n    if(highlight_height > pad_height)\n      pad_height = highlight_height;\n\n    info_height = 15 + pad_height;\n\n    if(dialog_height < (info_height + 2))\n      dialog_height = info_height + 2;\n\n    if(dialog_width == 79)\n      dialog_width++;\n\n    dialog_x = 40 - (dialog_width / 2);\n    dialog_y = 12 - (dialog_height / 2);\n\n    chars_x = dialog_x + 3;\n    chars_y = dialog_y + (dialog_height / 2) - (chars_height / 2);\n    info_x = chars_x + chars_width + 2;\n    info_y = dialog_y + (dialog_height / 2) - (info_height / 2);\n\n    restore_screen();\n    save_screen();\n\n    draw_window_box(dialog_x, dialog_y,\n     dialog_x + dialog_width - 1,\n     dialog_y + dialog_height - 1, 143, 128, 135, 1, 1);\n    draw_window_box(chars_x - 1, chars_y - 1,\n     chars_x + chars_width, chars_y + chars_height,\n     128, 143, 135, 0, 0);\n\n    // Current character\n    write_string(\"Current char-\\t(#000)\", info_x, info_y, 0x8F, WR_TAB);\n    write_number(current_char + (current_charset * 256), 0x8F,\n     info_x + 22, info_y, 3, 3, 10);\n\n    // Help\n    write_string(help_text[help_page],\n     info_x, info_y + pad_height + 2, 0x8F, WR_TAB | WR_NEWLINE);\n\n    if(screen_mode)\n    {\n      // Correct \"Toggle pixel\" to \"Place pixel\"\n      if(help_page == 0)\n        write_string(\"Place pixel \",\n         info_x + 8, info_y + pad_height + 4, 0x8F, WR_TAB);\n\n      // Correct F5 to \"SMZX\"\n      if(help_page == 1)\n        write_string(\"SMZX\",\n         info_x + 18, info_y + pad_height + 8, 0x8F, WR_TAB);\n\n      // 1-4 Select\n      write_string(help_text_smzx,\n       info_x, info_y + pad_height + 14, 0x8F, WR_TAB);\n\n      if(current_pixel)\n      {\n        write_string(\"\\xDB\\xDB\\xDB\\xDB\", info_x + 16,\n         info_y + pad_height + 14, smzx_colors[current_pixel], WR_NONE);\n      }\n      else\n      {\n        write_string(\"\\xFA\\xFA\\xFA\\xFA\", info_x + 16,\n         info_y + pad_height + 14, (smzx_colors[0] << 4) + smzx_colors[3], WR_NONE);\n      }\n    }\n\n    draw_color_box(current_palette, 0, info_x + 17,\n     info_y + pad_height + 12, info_x + 20);\n\n    switch(draw)\n    {\n      case 0:\n        write_string(\"(off)   \", info_x + 16,\n         info_y + pad_height + 13, 0x8F, WR_NONE);\n        break;\n\n      case 1:\n        write_string(\"(set)   \", info_x + 16,\n         info_y + pad_height + 13, 0x8F, WR_NONE);\n        break;\n\n      case 2:\n        write_string(\"(clear) \", info_x + 16,\n         info_y + pad_height + 13, 0x8F, WR_NONE);\n        break;\n    }\n\n    // Update char\n    if(block_mode)\n    {\n      if(screen_mode)\n      {\n        draw_multichar_smzx(buffer, chars_x, chars_y,\n         current_width, current_height, block_start_x,\n         block_start_y, block_width, block_height);\n      }\n      else\n      {\n        draw_multichar(buffer, chars_x, chars_y,\n         current_width, current_height, block_start_x,\n         block_start_y, block_width, block_height);\n      }\n    }\n    else\n    {\n      if(screen_mode)\n      {\n        draw_multichar_smzx(buffer, chars_x, chars_y,\n         current_width, current_height, x, y, 1, 1);\n      }\n      else\n      {\n        draw_multichar(buffer, chars_x, chars_y,\n         current_width, current_height, x, y, 1, 1);\n      }\n    }\n\n    draw_mini_buffer(info_x, info_y, current_charset, current_char,\n     current_width, current_height, highlight_width, highlight_height);\n\n    select_layer(UI_LAYER);\n\n    update_screen();\n    update_event_status_delay();\n\n    key = get_key(keycode_internal_wrt_numlock);\n\n    joystick_key = get_joystick_ui_key();\n    if(joystick_key)\n      key = joystick_key;\n\n    // Exit event -- mimic Escape\n    if(get_exit_status())\n      key = IKEY_ESCAPE;\n\n    draw_new = 0;\n\n    if(get_shift_status(keycode_internal) || (block_mode == 2))\n    {\n      if(!shifted)\n      {\n        block_x = x;\n        block_y = y;\n        block_start_x = x;\n        block_start_y = y;\n        block_mode = 1;\n        block_width = 1;\n        block_height = 1;\n        shifted = 1;\n      }\n      else\n      {\n        block_start_x = block_x;\n        block_start_y = block_y;\n\n        if(block_start_x > x)\n        {\n          block_start_x = x;\n          block_width = block_x - x + 1;\n        }\n        else\n        {\n          block_width = x - block_x + 1;\n        }\n\n        if(block_start_y > y)\n        {\n          block_start_y = y;\n          block_height = block_y - y + 1;\n        }\n        else\n        {\n          block_height = y - block_y + 1;\n        }\n      }\n    }\n    else\n    {\n      shifted = 0;\n      block_mode = 0;\n\n      block_width = buffer_width;\n      block_height = buffer_height;\n      block_start_x = 0;\n      block_start_y = 0;\n    }\n\n    if(get_mouse_press_ext())\n    {\n      int mouse_press = get_mouse_press_ext();\n\n      if(mouse_press == MOUSE_BUTTON_WHEELUP)\n      {\n        // Previous pixel\n        current_pixel = prev_pixel[current_pixel];\n      }\n      else\n\n      if(mouse_press == MOUSE_BUTTON_WHEELDOWN)\n      {\n        // Next pixel\n        current_pixel = next_pixel[current_pixel];\n      }\n      else\n\n      if(mouse_press == MOUSE_BUTTON_MIDDLE)\n      {\n        // Char selector\n        key = IKEY_RETURN;\n      }\n    }\n\n    if(get_mouse_status())\n    {\n      int mouse_status = get_mouse_status();\n      int mouse_x;\n      int mouse_y;\n      int draw_pixel;\n\n      get_mouse_position(&mouse_x, &mouse_y);\n\n      if(\n       (mouse_x >= chars_x) &&\n       (mouse_y >= chars_y) &&\n       (mouse_x < chars_x + chars_width) &&\n       (mouse_y < chars_y + chars_height))\n      {\n        // Grid.\n        if(small_chars)\n        {\n          get_mouse_pixel_position(&mouse_x, &mouse_y);\n\n          x = (mouse_x / 8) - chars_x;\n          y = (mouse_y / 7) - (chars_y * 2);\n        }\n        else\n        {\n          x = (mouse_x - chars_x) / 2;\n          y = mouse_y - chars_y;\n        }\n\n        if(screen_mode)\n        {\n          x /= 2;\n          draw_pixel = current_pixel;\n        }\n        else\n        {\n          draw_pixel = 1;\n        }\n\n        if(!block_mode)\n        {\n          int mouse_draw = 0;\n\n          if(mouse_status & MOUSE_BUTTON(MOUSE_BUTTON_LEFT))\n          {\n            // Draw current pixel\n            mouse_draw = 1;\n          }\n          else\n\n          if(mouse_status & MOUSE_BUTTON(MOUSE_BUTTON_RIGHT))\n          {\n            if(screen_mode)\n            {\n              // Grab\n              current_pixel = buffer[x + (buffer_width * y)];\n            }\n            else\n            {\n              // Clear\n              draw_pixel = 0;\n              mouse_draw = 1;\n            }\n          }\n\n          if(mouse_draw && (last_x != x || last_y != y))\n          {\n            if(last_x != -1)\n            {\n              int dx = x - last_x;\n              int dy = y - last_y;\n              int draw_x;\n              int draw_y;\n\n              int mx = MAX(abs(dx), abs(dy));\n\n              for(i = 0; i <= mx; i++)\n              {\n                draw_x = (dx * i / mx) + last_x;\n                draw_y = (dy * i / mx) + last_y;\n                buffer[draw_x + (buffer_width * draw_y)] = draw_pixel;\n              }\n            }\n            else\n            {\n              buffer[x + (buffer_width * y)] = draw_pixel;\n            }\n\n            if(changed)\n              add_charset_undo_frame(h, current_charset, current_char,\n               highlight_width, highlight_height);\n\n            collapse_buffer(buffer, current_width, current_height,\n             highlight_width, highlight_height, current_char,\n             current_charset, NULL);\n\n            update_undo_frame(h);\n\n            changed = 0;\n            last_x = x;\n            last_y = y;\n          }\n        }\n      }\n      else\n      {\n        // Mouse not in frame\n        last_x = -1;\n      }\n    }\n    else\n    {\n      // Mouse not pressed\n      changed = 1;\n      last_x = -1;\n    }\n\n    switch(key)\n    {\n      case IKEY_ESCAPE:\n      {\n        if(block_mode == 2)\n        {\n          block_mode = 0;\n          key = 0;\n        }\n        break;\n      }\n\n      case IKEY_LEFT:\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          char *buffer_ptr = buffer +\n           block_start_x + (block_start_y * buffer_width);\n          int wrap;\n\n          for(i = 0; i < block_height; i++,\n           buffer_ptr += buffer_width)\n          {\n            wrap = buffer_ptr[0];\n            memmove(buffer_ptr, buffer_ptr + 1,\n             block_width - 1);\n            buffer_ptr[block_width - 1] = wrap;\n          }\n\n          collapse_buffer(buffer, current_width, current_height,\n           highlight_width, highlight_height, current_char,\n           current_charset, h);\n        }\n        else\n        {\n          x--;\n          if(x < 0)\n            x = buffer_width - 1;\n\n          if(draw)\n            draw_new = 1;\n        }\n        break;\n      }\n\n      case IKEY_RIGHT:\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          char *buffer_ptr = buffer +\n           block_start_x + (block_start_y * buffer_width);\n          int wrap;\n\n          for(i = 0; i < block_height; i++,\n           buffer_ptr += buffer_width)\n          {\n            wrap = buffer_ptr[block_width - 1];\n            memmove(buffer_ptr + 1, buffer_ptr,\n             block_width - 1);\n            buffer_ptr[0] = wrap;\n          }\n\n          collapse_buffer(buffer, current_width, current_height,\n           highlight_width, highlight_height, current_char,\n           current_charset, h);\n        }\n        else\n        {\n          x++;\n          if(x >= buffer_width)\n            x = 0;\n\n          if(draw)\n            draw_new = 1;\n        }\n        break;\n      }\n\n      case IKEY_UP:\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          char *buffer_ptr = buffer +\n           block_start_x + (block_start_y * buffer_width);\n          char *wrap_row = cmalloc(block_width);\n\n          memcpy(wrap_row, buffer_ptr, block_width);\n\n          for(i = 0; i < block_height - 1; i++,\n           buffer_ptr += buffer_width)\n          {\n            memcpy(buffer_ptr, buffer_ptr + buffer_width,\n             block_width);\n          }\n\n          memcpy(buffer_ptr, wrap_row, block_width);\n          collapse_buffer(buffer, current_width, current_height,\n           highlight_width, highlight_height, current_char,\n           current_charset, h);\n\n          free(wrap_row);\n        }\n        else\n        {\n          y--;\n          if(y < 0)\n            y = buffer_height - 1;\n\n          if(draw)\n            draw_new = 1;\n        }\n        break;\n      }\n\n      case IKEY_DOWN:\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          char *buffer_ptr = buffer +\n           block_start_x + ((block_start_y + (block_height - 1)) *\n           buffer_width);\n          char *wrap_row = cmalloc(block_width);\n\n          memcpy(wrap_row, buffer_ptr, block_width);\n\n          for(i = 0; i < block_height - 1; i++,\n           buffer_ptr -= buffer_width)\n          {\n            memcpy(buffer_ptr, buffer_ptr - buffer_width,\n             block_width);\n          }\n\n          memcpy(buffer_ptr, wrap_row, block_width);\n          collapse_buffer(buffer, current_width, current_height,\n           highlight_width, highlight_height, current_char,\n           current_charset, h);\n\n          free(wrap_row);\n        }\n        else\n        {\n          y++;\n          if(y >= buffer_height)\n            y = 0;\n\n          if(draw)\n            draw_new = 1;\n        }\n        break;\n      }\n\n      case IKEY_SPACE:\n      {\n        if(screen_mode)\n          buffer[x + (y * buffer_width)] = current_pixel;\n        else\n          buffer[x + (y * buffer_width)] ^= 1;\n\n        collapse_buffer(buffer, current_width, current_height,\n         highlight_width, highlight_height, current_char,\n         current_charset, h);\n\n        break;\n      }\n\n      case IKEY_RETURN:\n      {\n        int show_palette = use_default_palette ? -1 : current_palette;\n\n        int new_char =\n         char_selection_ext(current_char, 1, &highlight_width,\n         &highlight_height, &current_charset, show_palette);\n\n        int highlight_size = highlight_width * highlight_height;\n        int search_order[16];\n        int factors[6];\n        int num_factors = 0;\n        int low, high;\n        int subdivision = selected_subdivision[highlight_size];\n\n        // The first choice should be about the square root,\n        // biased high.\n        search_order[0] = (int)(sqrt(highlight_size) + 0.75);\n\n        // Subsequent choices should expand outwards\n        low = search_order[0];\n        high = search_order[0];\n        i = 1;\n        while((low > 1) || (high < 6))\n        {\n          if(high < 6)\n          {\n            high++;\n            search_order[i] = high;\n            i++;\n          }\n\n          if(low > 1)\n          {\n            low--;\n            search_order[i] = low;\n            i++;\n          }\n        }\n\n        for(i = 0; i < 6; i++)\n        {\n          if(!(highlight_size % search_order[i]) &&\n           (highlight_size / search_order[i] <= 3))\n          {\n            factors[num_factors] = search_order[i];\n            num_factors++;\n          }\n        }\n\n        if(num_factors > 1)\n        {\n          struct element **elements =\n           cmalloc(sizeof(struct element *) * num_factors);\n          char **radio_button_strings =\n           cmalloc(sizeof(char *) * num_factors);\n          char **radio_button_substrings =\n           cmalloc(sizeof(char *) * num_factors);\n          struct dialog di;\n\n          for(i = 0; i < num_factors; i++)\n          {\n            radio_button_substrings[i] = cmalloc(32);\n            radio_button_strings[i] = radio_button_substrings[i];\n            sprintf(radio_button_strings[i], \"%d x %d\",\n             factors[i], highlight_size / factors[i]);\n          }\n\n          elements[0] =\n           construct_radio_button(8, 2,\n           (const char **)radio_button_strings,\n           num_factors, 5, &subdivision);\n          elements[1] = construct_button(10, num_factors + 3,\n           \"OK\", 0);\n\n          construct_dialog(&di, \"Choose subdivision\", 28,\n           8, 24, 6 + num_factors, elements, 2, 0);\n\n          run_dialog(mzx_world, &di);\n          destruct_dialog(&di);\n\n          // Prevent UI keys from carrying through.\n          force_release_all_keys();\n\n          for(i = 0; i < num_factors; i++)\n            free(radio_button_substrings[i]);\n          free(radio_button_substrings);\n          free(radio_button_strings);\n          free(elements);\n        }\n\n        selected_subdivision[highlight_size] = subdivision;\n\n        current_width = factors[subdivision];\n        current_height = highlight_size / factors[subdivision];\n\n        if(new_char >= 0)\n          current_char = new_char;\n\n        expand_buffer(buffer, current_width, current_height,\n         highlight_width, highlight_height, current_char,\n         current_charset);\n\n        changed = 1;\n        break;\n      }\n\n      case IKEY_HOME:\n      {\n        x = 0;\n        y = 0;\n        break;\n      }\n\n      case IKEY_END:\n      {\n        x = buffer_width - 1;\n        y = buffer_height - 1;\n        break;\n      }\n\n      case IKEY_INSERT:\n      {\n        current_pixel = buffer[x + (y * buffer_width)];\n        break;\n      }\n\n      case IKEY_DELETE:\n      {\n        char *buffer_ptr = buffer +\n         block_start_x + (block_start_y * buffer_width);\n\n        for(i = 0; i < block_height; i++,\n         buffer_ptr += buffer_width)\n        {\n          memset(buffer_ptr, 0, block_width);\n        }\n        collapse_buffer(buffer, current_width, current_height,\n         highlight_width, highlight_height, current_char,\n         current_charset, h);\n\n        break;\n      }\n\n      case IKEY_TAB:\n      {\n        if(draw)\n        {\n          draw = 0;\n        }\n        else\n        {\n          if(get_shift_status(keycode_internal))\n            draw = 2;\n          else\n            draw = 1;\n\n          draw_new = 1;\n        }\n\n        break;\n      }\n\n#ifdef CONFIG_HELPSYS\n      case IKEY_F1:\n      {\n        // FIXME context\n        help_system(NULL, mzx_world);\n        break;\n      }\n#endif\n\n      case IKEY_F2:\n      {\n        // Copy buffer\n        char *buffer_ptr = buffer +\n         block_start_x + (block_start_y * buffer_width);\n        char *dest_ptr = char_copy_buffer;\n\n        if(block_mode)\n          char_copy_use_offset = 1;\n        else\n          char_copy_use_offset = 0;\n\n        char_copy_width = block_width;\n        char_copy_height = block_height;\n\n        if(get_ctrl_status(keycode_internal))\n        {\n          // Cut to buffer\n          for(i = 0; i < block_height; i++,\n           buffer_ptr += buffer_width, dest_ptr += block_width)\n          {\n            memcpy(dest_ptr, buffer_ptr, block_width);\n            memset(buffer_ptr, 0, block_width);\n          }\n\n          collapse_buffer(buffer, current_width, current_height,\n           highlight_width, highlight_height, current_char,\n           current_charset, h);\n        }\n        else\n        {\n          for(i = 0; i < block_height; i++,\n           buffer_ptr += buffer_width, dest_ptr += block_width)\n          {\n            memcpy(dest_ptr, buffer_ptr, block_width);\n          }\n        }\n        break;\n      }\n\n      case IKEY_F3:\n      {\n        // Paste buffer\n        int copy_width = char_copy_width;\n        int copy_height = char_copy_height;\n        int dest_x = x;\n        int dest_y = y;\n        char *src_ptr = char_copy_buffer;\n        char *buffer_ptr;\n\n        if(!char_copy_use_offset)\n        {\n          dest_x = 0;\n          dest_y = 0;\n        }\n\n        buffer_ptr = buffer +\n         dest_x + (dest_y * buffer_width);\n\n        if((dest_x + copy_width) > buffer_width)\n          copy_width = buffer_width - dest_x;\n\n        if((dest_y + copy_height) > buffer_height)\n          copy_height = buffer_height - dest_y;\n\n        for(i = 0; i < copy_height; i++,\n         buffer_ptr += buffer_width, src_ptr += char_copy_width)\n        {\n          memcpy(buffer_ptr, src_ptr, copy_width);\n        }\n\n        collapse_buffer(buffer, current_width, current_height,\n         highlight_width, highlight_height, current_char,\n         current_charset, h);\n\n        break;\n      }\n\n      case IKEY_F4:\n      {\n        // ASCII Default\n        int charset_offset = current_charset * 256;\n        int char_offset = current_char;\n        int skip = 32 - highlight_width;\n\n        // ALT+F4 - do nothing.\n        if(get_alt_status(keycode_internal))\n          break;\n\n        add_charset_undo_frame(h, current_charset, current_char,\n         highlight_width, highlight_height);\n\n        for(i = 0; i < highlight_height; i++,\n         char_offset += skip)\n        {\n          for(i2 = 0; i2 < highlight_width; i2++,\n           char_offset++)\n          {\n            // Wrap to current charset\n            char_offset = char_offset % 256;\n            ec_load_char_ascii(char_offset + charset_offset);\n          }\n        }\n\n        update_undo_frame(h);\n\n        expand_buffer(buffer, current_width, current_height,\n         highlight_width, highlight_height, current_char,\n         current_charset);\n\n        break;\n      }\n\n      case IKEY_F5:\n      {\n        // MZX Default\n        int charset_offset = current_charset * 256;\n        int char_offset = current_char;\n        int skip = 32 - highlight_width;\n\n        add_charset_undo_frame(h, current_charset, current_char,\n         highlight_width, highlight_height);\n\n        for(i = 0; i < highlight_height; i++,\n         char_offset += skip)\n        {\n          for(i2 = 0; i2 < highlight_width; i2++,\n           char_offset++)\n          {\n            // Wrap to current charset\n            char_offset = char_offset % 256;\n            ec_load_char_mzx(char_offset + charset_offset);\n          }\n        }\n\n        update_undo_frame(h);\n\n        expand_buffer(buffer, current_width, current_height,\n         highlight_width, highlight_height, current_char,\n         current_charset);\n\n        break;\n      }\n\n      case IKEY_KP1:\n      case IKEY_1:\n      {\n        current_pixel = 0;\n        break;\n      }\n\n      case IKEY_KP2:\n      case IKEY_2:\n      {\n        current_pixel = 2;\n        break;\n      }\n\n      case IKEY_KP3:\n      case IKEY_3:\n      {\n        current_pixel = 1;\n        break;\n      }\n\n      case IKEY_KP4:\n      case IKEY_4:\n      {\n        current_pixel = 3;\n        break;\n      }\n\n      case IKEY_KP_MINUS:\n      case IKEY_MINUS:\n      {\n        current_char = char_select_next_tile(current_char, -1,\n         highlight_width, highlight_height);\n\n        expand_buffer(buffer, current_width, current_height,\n         highlight_width, highlight_height, current_char,\n         current_charset);\n        changed = 1;\n        break;\n      }\n\n      case IKEY_KP_PLUS:\n      case IKEY_EQUALS:\n      {\n        current_char = char_select_next_tile(current_char, 1,\n         highlight_width, highlight_height);\n\n        expand_buffer(buffer, current_width, current_height,\n         highlight_width, highlight_height, current_char,\n         current_charset);\n        changed = 1;\n        break;\n      }\n\n      case IKEY_PAGEUP:\n      {\n        current_charset--;\n\n        if(current_charset < 0)\n          current_charset = NUM_CHARSETS - 2;\n\n        expand_buffer(buffer, current_width, current_height,\n         highlight_width, highlight_height, current_char,\n         current_charset);\n        changed = 1;\n        break;\n      }\n\n      case IKEY_PAGEDOWN:\n      {\n        current_charset++;\n\n        if(current_charset >= NUM_CHARSETS - 1)\n          current_charset = 0;\n\n        expand_buffer(buffer, current_width, current_height,\n         highlight_width, highlight_height, current_char,\n         current_charset);\n        changed = 1;\n        break;\n      }\n\n      case IKEY_b:\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          block_mode = 2;\n          block_x = x;\n          block_y = y;\n          block_start_x = x;\n          block_start_y = y;\n          block_width = 1;\n          block_height = 1;\n          shifted = 1;\n        }\n\n        break;\n      }\n\n      case IKEY_c:\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          if(screen_mode)\n            char_editor_default_colors();\n\n          use_default_palette = 1;\n        }\n        else\n        {\n          int new_color = color_selection(current_palette, 0);\n          if(new_color >= 0)\n          {\n            current_palette = new_color;\n            use_default_palette = 0;\n\n            if(screen_mode)\n              char_editor_update_colors();\n          }\n        }\n        break;\n      }\n\n      case IKEY_f:\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          // Flood fill\n          int check = buffer[x + (y * buffer_width)];\n          int fill = check ^ 1;\n\n          if(screen_mode)\n            fill = current_pixel;\n\n          if(check != fill)\n          {\n            fill_region(buffer, x, y, buffer_width, buffer_height,\n             check, fill);\n            collapse_buffer(buffer, current_width, current_height,\n             highlight_width, highlight_height, current_char,\n             current_charset, h);\n          }\n        }\n        else\n        {\n          // Flip\n          char *temp_buffer = cmalloc(sizeof(char) * block_width);\n          int start_offset = block_start_x +\n           (block_start_y * buffer_width);\n          int end_offset = start_offset +\n           ((block_height - 1) * buffer_width);\n\n          for(i = 0; i < (block_height / 2); i++,\n           start_offset += buffer_width, end_offset -= buffer_width)\n          {\n            memcpy(temp_buffer, buffer + start_offset,\n             block_width);\n            memcpy(buffer + start_offset, buffer + end_offset,\n             block_width);\n            memcpy(buffer + end_offset, temp_buffer,\n             block_width);\n          }\n\n          collapse_buffer(buffer, current_width, current_height,\n           highlight_width, highlight_height, current_char,\n           current_charset, h);\n\n          free(temp_buffer);\n        }\n        break;\n      }\n\n      case IKEY_h:\n      {\n        // Switch help page\n        help_page = (help_page + 1) % 3;\n        break;\n      }\n\n      case IKEY_i:\n      {\n        // Import\n        if(get_alt_status(keycode_internal))\n        {\n          char_import(mzx_world, current_char, current_charset,\n           highlight_width, highlight_height, h);\n\n          expand_buffer(buffer, current_width, current_height,\n           highlight_width, highlight_height, current_char,\n           current_charset);\n        }\n        break;\n      }\n\n      case IKEY_m:\n      {\n        // Mirror\n        int temp, i2;\n        int start_offset = block_start_x +\n         (block_start_y * buffer_width);\n        int previous_offset;\n        int end_offset;\n\n        for(i = 0; i < block_height; i++)\n        {\n          previous_offset = start_offset;\n          end_offset = start_offset + block_width - 1;\n          for(i2 = 0; i2 < (block_width / 2);\n           i2++, start_offset++, end_offset--)\n          {\n            temp = buffer[start_offset];\n            buffer[start_offset] = buffer[end_offset];\n            buffer[end_offset] = temp;\n          }\n          start_offset = previous_offset + buffer_width;\n        }\n\n        collapse_buffer(buffer, current_width, current_height,\n         highlight_width, highlight_height, current_char,\n         current_charset, h);\n\n        break;\n      }\n\n      case IKEY_n:\n      {\n        // Negative\n        char *buffer_ptr = buffer +\n         block_start_x + (block_start_y * buffer_width);\n\n        if(screen_mode)\n        {\n          for(i = 0; i < block_height; i++,\n           buffer_ptr += (buffer_width - block_width))\n          {\n            for(i2 = 0; i2 < block_width; i2++, buffer_ptr++)\n            {\n              *buffer_ptr = 3 - *buffer_ptr;\n            }\n          }\n        }\n        else\n        {\n          for(i = 0; i < block_height; i++,\n           buffer_ptr += (buffer_width - block_width))\n          {\n            for(i2 = 0; i2 < block_width; i2++, buffer_ptr++)\n            {\n              *buffer_ptr ^= 1;\n            }\n          }\n        }\n\n        collapse_buffer(buffer, current_width, current_height,\n         highlight_width, highlight_height, current_char,\n         current_charset, h);\n\n        break;\n      }\n\n      case IKEY_q:\n      {\n        key = IKEY_ESCAPE;\n        break;\n      }\n\n      case IKEY_x:\n      {\n        // Export\n        if(get_alt_status(keycode_internal))\n        {\n          char_export(mzx_world, current_char, current_charset,\n           highlight_width, highlight_height);\n        }\n        break;\n      }\n\n      case IKEY_y:\n      {\n        // Redo\n        if(get_ctrl_status(keycode_internal))\n        {\n          apply_redo(h);\n\n          expand_buffer(buffer, current_width, current_height,\n           highlight_width, highlight_height, current_char,\n           current_charset);\n          changed = 1;\n        }\n        break;\n      }\n\n      case IKEY_z:\n      {\n        // Undo\n        if(get_ctrl_status(keycode_internal))\n        {\n          apply_undo(h);\n\n          expand_buffer(buffer, current_width, current_height,\n           highlight_width, highlight_height, current_char,\n           current_charset);\n          changed = 1;\n        }\n        break;\n      }\n    }\n\n    if(draw_new)\n    {\n      switch(draw)\n      {\n        case 1:\n        {\n          if(screen_mode)\n            buffer[x + (y * buffer_width)] = current_pixel;\n          else\n            buffer[x + (y * buffer_width)] = 1;\n          break;\n        }\n\n        case 2:\n        {\n          buffer[x + (y * buffer_width)] = 0;\n          break;\n        }\n      }\n      collapse_buffer(buffer, current_width, current_height,\n       highlight_width, highlight_height, current_char,\n       current_charset, h);\n    }\n\n    // Destroy the mini buffer layers\n    destruct_extra_layers(mini_draw_layer);\n    destruct_extra_layers(mini_highlight_layer);\n\n  } while(key != IKEY_ESCAPE);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  mouse_size(8, 14);\n\n  restore_screen();\n  update_screen();\n  pop_context();\n\n  if(screen_mode)\n  {\n    // Reset the protected palette\n    default_protected_palette();\n  }\n\n  destruct_undo_history(h);\n\n  free(buffer);\n\n  return current_char + (current_charset * CHARSET_SIZE);\n}\n"
  },
  {
    "path": "src/editor/char_ed.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Declaration\n\n#ifndef __EDITOR_CHAR_ED_H\n#define __EDITOR_CHAR_ED_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../world_struct.h\"\n\nint char_editor(struct world *mzx_world);\n\n__M_END_DECLS\n\n#endif // __EDITOR_CHAR_ED_H\n"
  },
  {
    "path": "src/editor/clipboard.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_CLIPBOARD_H\n#define __EDITOR_CLIPBOARD_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\nvoid copy_buffer_to_clipboard(char **buffer, int lines, int total_length);\n\nchar *get_clipboard_buffer(void);\nvoid free_clipboard_buffer(char *buffer);\n\n__M_END_DECLS\n\n#endif // __EDITOR_CLIPBOARD_H\n"
  },
  {
    "path": "src/editor/clipboard_carbon.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017 Ian Burgmyer <spectere@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"clipboard.h\"\n\n#define decimal decimal_\n#define Random Random_\n#include <Carbon/Carbon.h>\n\nstatic const CFStringRef PLAIN = CFSTR(\"com.apple.traditional-mac-plain-text\");\n\nvoid copy_buffer_to_clipboard(char **buffer, int lines, int total_length)\n{\n  PasteboardSyncFlags syncFlags;\n  char *dest_data, *dest_ptr;\n  PasteboardRef clipboard;\n  CFDataRef textData;\n  int i, line_length;\n\n  if(PasteboardCreate(kPasteboardClipboard, &clipboard) != noErr)\n    return;\n\n  if(PasteboardClear(clipboard) != noErr)\n    goto err_release;\n\n  syncFlags = PasteboardSynchronize(clipboard);\n  if((syncFlags & kPasteboardModified) ||\n    !(syncFlags & kPasteboardClientIsOwner))\n    goto err_release;\n\n  dest_data = cmalloc(total_length + 1);\n  dest_ptr = dest_data;\n\n  for(i = 0; i < lines - 1; i++)\n  {\n    line_length = strlen(buffer[i]);\n    memcpy(dest_ptr, buffer[i], line_length);\n    dest_ptr += line_length;\n    dest_ptr[0] = '\\n';\n    dest_ptr++;\n  }\n\n  line_length = strlen(buffer[i]);\n  memcpy(dest_ptr, buffer[i], line_length);\n  dest_ptr[line_length] = 0;\n\n  textData = CFDataCreate(kCFAllocatorDefault,\n   (const UInt8 *)dest_data, total_length + 1);\n  free(dest_data);\n\n  if(!textData)\n    goto err_release;\n\n  if(PasteboardPutItemFlavor(clipboard, (PasteboardItemID)1,\n   PLAIN, textData, 0) != noErr)\n    goto err_release;\n\nerr_release:\n  CFRelease(clipboard);\n}\n\nchar *get_clipboard_buffer(void)\n{\n  PasteboardRef clipboard;\n  ItemCount itemCount;\n  UInt32 itemIndex;\n  char *dest_data = NULL;\n\n  if(PasteboardCreate(kPasteboardClipboard, &clipboard) != noErr)\n    return NULL;\n\n  if(PasteboardGetItemCount(clipboard, &itemCount) != noErr)\n    goto err_release;\n\n  for(itemIndex = 1; itemIndex <= itemCount; itemIndex++)\n  {\n    CFIndex flavorCount, flavorIndex;\n    CFArrayRef flavorTypeArray;\n    PasteboardItemID itemID;\n\n    if(PasteboardGetItemIdentifier(clipboard, itemIndex, &itemID) != noErr)\n      goto err_release;\n\n    if(PasteboardCopyItemFlavors(clipboard, itemID, &flavorTypeArray) != noErr)\n      goto err_release;\n\n    flavorCount = CFArrayGetCount(flavorTypeArray);\n\n    for(flavorIndex = 0; flavorIndex < flavorCount; flavorIndex++)\n    {\n      CFDataRef flavorData;\n      char *src_data;\n      size_t length;\n\n      CFStringRef flavorType =\n       (CFStringRef)CFArrayGetValueAtIndex(flavorTypeArray, flavorIndex);\n\n      if(!UTTypeConformsTo(flavorType, PLAIN))\n        continue;\n\n      if(PasteboardCopyItemFlavorData(clipboard, itemID,\n       flavorType, &flavorData) != noErr)\n        continue;\n\n      src_data = (char *)CFDataGetBytePtr(flavorData);\n      length = (size_t)CFDataGetLength(flavorData);\n\n      dest_data = (char *)cmalloc(length + 1);\n      memcpy(dest_data, src_data, length);\n      dest_data[length] = 0;\n\n      CFRelease(flavorData);\n    }\n  }\n\nerr_release:\n  CFRelease(clipboard);\n  return dest_data;\n}\n\nvoid free_clipboard_buffer(char *buffer)\n{\n  free(buffer);\n}\n"
  },
  {
    "path": "src/editor/clipboard_cocoa.m",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2021-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <Foundation/Foundation.h>\n#include <AppKit/NSPasteboard.h>\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"clipboard.h\"\n\n/**\n * Native MacOS clipboard handler using AppKit. Unlike the SDL2 clipboard\n * handler, this handler treats the input text as being encoded in Mac OS Roman\n * (unlike ISO Latin-1, Windows 1292, etc, all 256 codepoints of Mac OS Roman\n * will be encoded as valid UTF-8 symbols).\n *\n * Notes:\n * - Literal arrays require LLVM 3.1, so explicitly allocate NSArrays.\n * - @autoreleasepool requires LLVM 3.0, so use NSAutoreleasePool directly.\n */\n\n/**\n * Send an array of extended ASCII lines to the system clipboard via Cocoa.\n * These lines will be re-encoded to UTF-8 using Mac OS Roman codepoints.\n */\nvoid copy_buffer_to_clipboard(char **buffer, int lines, int total_length)\n{\n  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];\n  NSPasteboard *pasteboard = NULL;\n  NSString *string = NULL;\n\n  char *buf = cmalloc(total_length + 1);\n  char *pos = buf;\n  for(int i = 0; i < lines; i++)\n  {\n    size_t len = strlen(buffer[i]);\n    memcpy(pos, buffer[i], len);\n    pos += len;\n    *(pos++) = '\\n';\n  }\n  buf[total_length] = '\\0';\n\n  /* Convert to NSString. NOTE: this function requires 10.3+, haven't\n   * looked for a compelling way to do this for 10.0.\n   */\n  string = [[[NSString alloc] initWithBytes:buf length:total_length\n   encoding:NSMacOSRomanStringEncoding] autorelease];\n  free(buf);\n\n  pasteboard = [NSPasteboard generalPasteboard];\n\n#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060\n\n  [pasteboard clearContents];\n  [pasteboard writeObjects:[NSArray arrayWithObject:string]];\n\n#else /* VERSION_MIN < 1060 */\n\n  [pasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];\n  [pasteboard setString:string forType:NSStringPboardType];\n\n#endif /* VERSION_MIN < 1060 */\n\n  [pool release];\n}\n\n/**\n * Fetch an extended ASCII buffer from the system clipboard via Cocoa.\n * These lines will be re-encoded from UTF-8 using Mac OS Roman codepoints.\n */\nchar *get_clipboard_buffer(void)\n{\n  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];\n  NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];\n  NSString *string = NULL;\n  NSData *chrdata = NULL;\n  size_t buf_len;\n  char *buf;\n\n#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060\n\n  NSArray *for_classes = [NSArray arrayWithObject:[NSString class]];\n  NSArray *items = [pasteboard readObjectsForClasses:for_classes options:nil];\n\n  string = items && [items count] ? [items objectAtIndex:0] : nil;\n  if(!string)\n    goto err;\n\n#else /* VERSION_MIN < 1060 */\n\n  NSArray *types = [NSArray arrayWithObject:NSStringPboardType];\n  if(![pasteboard availableTypeFromArray:types])\n    goto err;\n\n  string = [pasteboard stringForType:NSStringPboardType];\n  if(!string)\n    goto err;\n\n#endif /* VERSION_MIN < 1060 */\n\n  chrdata = [string dataUsingEncoding:NSMacOSRomanStringEncoding\n   allowLossyConversion:true];\n  if(!chrdata)\n    goto err;\n\n  buf_len = [chrdata length];\n  buf = (char *)cmalloc(buf_len + 1);\n\n  [chrdata getBytes:buf length:buf_len];\n  buf[buf_len] = '\\0';\n  [pool release];\n  return buf;\n\nerr:\n  [pool release];\n  return NULL;\n}\n\nvoid free_clipboard_buffer(char *buffer)\n{\n  free(buffer);\n}\n"
  },
  {
    "path": "src/editor/clipboard_null.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"clipboard.h\"\n#include <stdlib.h>\n\nvoid copy_buffer_to_clipboard(char **buffer, int lines, int total_length) {}\n\nchar *get_clipboard_buffer(void)\n{\n  return NULL;\n}\n\nvoid free_clipboard_buffer(char *buffer)\n{\n  free(buffer);\n}\n"
  },
  {
    "path": "src/editor/clipboard_sdl2.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017 Ian Burgmyer <spectere@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"clipboard.h\"\n\n#include \"../SDLmzx.h\"\n#include \"../util.h\"\n\nvoid copy_buffer_to_clipboard(char **buffer, int lines, int total_length)\n{\n  int i;\n  unsigned long line_length;\n  char *dest_data, *dest_ptr;\n\n  dest_data = cmalloc(total_length + 1);\n  dest_ptr = dest_data;\n\n  for(i = 0; i < lines; i++)\n  {\n    line_length = strlen(buffer[i]);\n    memcpy(dest_ptr, buffer[i], line_length);\n    dest_ptr += line_length;\n    dest_ptr[0] = '\\n';\n    dest_ptr++;\n  }\n\n  dest_ptr[-1] = 0;\n#if SDL_VERSION_ATLEAST(3,0,0)\n  if(!SDL_SetClipboardText(dest_data))\n#else\n  if(SDL_SetClipboardText(dest_data) < 0)\n#endif\n    warn(\"SDL_SetClipboardText failed: %s\\n\", SDL_GetError());\n\n  free(dest_data);\n}\n\nchar *get_clipboard_buffer(void)\n{\n  return SDL_GetClipboardText();\n}\n\nvoid free_clipboard_buffer(char *buffer)\n{\n  SDL_free(buffer);\n}\n"
  },
  {
    "path": "src/editor/clipboard_win32.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"clipboard.h\"\n\nvoid copy_buffer_to_clipboard(char **buffer, int lines, int total_length)\n{\n  HANDLE global_memory;\n  size_t line_length;\n  char *dest_data;\n  char *dest_ptr;\n  int i;\n\n  // Room for \\rs\n  total_length += lines - 1;\n\n  global_memory = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, total_length);\n  if(!global_memory)\n    return;\n\n  if(!OpenClipboard(NULL))\n    return;\n\n  dest_data = (char *)GlobalLock(global_memory);\n  dest_ptr = dest_data;\n\n  for(i = 0; i < lines - 1; i++)\n  {\n    line_length = strlen(buffer[i]);\n    memcpy(dest_ptr, buffer[i], line_length);\n    dest_ptr += line_length;\n    dest_ptr[0] = '\\r';\n    dest_ptr[1] = '\\n';\n    dest_ptr += 2;\n  }\n\n  line_length = strlen(buffer[i]);\n  memcpy(dest_ptr, buffer[i], line_length);\n  dest_ptr[line_length] = 0;\n\n  GlobalUnlock(global_memory);\n  EmptyClipboard();\n  SetClipboardData(CF_TEXT, global_memory);\n\n  CloseClipboard();\n}\n\nchar *get_clipboard_buffer(void)\n{\n  HANDLE global_memory;\n  char *dest_data;\n  char *src_data;\n  size_t length;\n\n  if(!IsClipboardFormatAvailable(CF_TEXT) || !OpenClipboard(NULL))\n    return NULL;\n\n  global_memory = GetClipboardData(CF_TEXT);\n\n  if(global_memory != NULL)\n  {\n    src_data = (char *)GlobalLock(global_memory);\n\n    // CF_TEXT is guaranteed to be null-terminated.\n    length = strlen(src_data);\n\n    dest_data = (char *)cmalloc(length + 1);\n    strcpy(dest_data, src_data);\n\n    GlobalUnlock(global_memory);\n    CloseClipboard();\n\n    return dest_data;\n  }\n\n  return NULL;\n}\n\nvoid free_clipboard_buffer(char *buffer)\n{\n  free(buffer);\n}\n"
  },
  {
    "path": "src/editor/clipboard_x11.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n#include <limits.h>\n\n#include \"clipboard.h\"\n\n#if defined(CONFIG_SDL)\n#include \"../SDLmzx.h\"\n#include \"../render_sdl.h\"\n#endif\n\nstatic char **copy_buffer;\nstatic int copy_buffer_lines;\nstatic int copy_buffer_total_length;\n\n/* Forward declaration so the SDL parts can stay separate. */\nstatic int copy_buffer_to_X11_selection(Display *display, XEvent *xevent);\n\n#ifdef CONFIG_SDL\n#if SDL_VERSION_ATLEAST(1,2,0) && !SDL_VERSION_ATLEAST(3,0,0)\n\nstatic inline boolean get_X11_display_and_window(SDL_Window *window,\n Display **display, Window *xwindow)\n{\n  SDL_SysWMinfo info;\n  SDL_VERSION(&info.version);\n\n  if(!window)\n    window = sdl_get_current_window();\n\n  if(!window || !SDL_GetWindowWMInfo(window, &info) || info.subsystem != SDL_SYSWM_X11)\n    return false;\n\n  if(display)\n    *display = info.info.x11.display;\n  if(window)\n    *xwindow = info.info.x11.window;\n  return true;\n}\n\n#if SDL_VERSION_ATLEAST(2,0,0)\nstatic inline int event_callback(void *userdata, SDL_Event *event)\n#else\nstatic inline int event_callback(const SDL_Event *event)\n#endif\n{\n#if SDL_VERSION_ATLEAST(2,0,0)\n  SDL_Window *window = (SDL_Window *)userdata;\n#else\n  SDL_Window *window = NULL;\n#endif\n  Display *display;\n  XEvent *xevent;\n  if(event->type != SDL_SYSWMEVENT)\n    return 0;\n\n  if(!get_X11_display_and_window(window, &display, NULL))\n    return 0;\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n  xevent = &(event->syswm.msg->msg.x11.event);\n#else\n  xevent = &(event->syswm.msg->event.xevent);\n#endif\n\n  return copy_buffer_to_X11_selection(display, xevent);\n}\n\nstatic inline void set_X11_event_callback(void)\n{\n  SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);\n  SDL_SetEventFilter(event_callback, sdl_get_current_window());\n}\n\n#endif /* SDL_VERSION_ATLEAST(1,2,0) && !SDL_VERSION_ATLEAST(3,0,0) */\n#endif /* CONFIG_SDL */\n\n\nstatic int copy_buffer_to_X11_selection(Display *display, XEvent *xevent)\n{\n  XSelectionRequestEvent *request;\n  char *dest_data, *dest_ptr;\n  XEvent response;\n  int i, line_length;\n\n  if(xevent->type != SelectionRequest || !copy_buffer)\n    return 0;\n\n  dest_data = cmalloc(copy_buffer_total_length + 1);\n  dest_ptr = dest_data;\n\n  for(i = 0; i < copy_buffer_lines - 1; i++)\n  {\n    line_length = strlen(copy_buffer[i]);\n    memcpy(dest_ptr, copy_buffer[i], line_length);\n    dest_ptr += line_length;\n    dest_ptr[0] = '\\n';\n    dest_ptr++;\n  }\n\n  line_length = strlen(copy_buffer[i]);\n  memcpy(dest_ptr, copy_buffer[i], line_length);\n  dest_ptr[line_length] = 0;\n\n  request = &(xevent->xselectionrequest);\n  response.xselection.type = SelectionNotify;\n  response.xselection.display = request->display;\n  response.xselection.selection = request->selection;\n  response.xselection.target = XA_STRING;\n  response.xselection.property = request->property;\n  response.xselection.requestor = request->requestor;\n  response.xselection.time = request->time;\n\n  XChangeProperty(display, request->requestor, request->property,\n    XA_STRING, 8, PropModeReplace, (const unsigned char *)dest_data,\n    copy_buffer_total_length);\n\n  free(dest_data);\n\n  XSendEvent(display, request->requestor, True, 0, &response);\n  XFlush(display);\n  return 0;\n}\n\nvoid copy_buffer_to_clipboard(char **buffer, int lines, int total_length)\n{\n  Display *display;\n  Window xwindow;\n\n  if(!get_X11_display_and_window(NULL, &display, &xwindow))\n    return;\n\n  copy_buffer = buffer;\n  copy_buffer_lines = lines;\n  copy_buffer_total_length = total_length;\n\n  XSetSelectionOwner(display, XA_PRIMARY, xwindow, CurrentTime);\n  set_X11_event_callback();\n}\n\nchar *get_clipboard_buffer(void)\n{\n  int selection_format, ret_type;\n  unsigned long int nbytes, overflow;\n  unsigned char *src_data;\n  char *dest_data = NULL;\n  Window xwindow, owner;\n  Atom selection_type;\n  Display *display;\n\n  if(!get_X11_display_and_window(NULL, &display, &xwindow))\n    return NULL;\n\n  owner = XGetSelectionOwner(display, XA_PRIMARY);\n\n  if((owner == None) || (owner == xwindow))\n    return NULL;\n\n  XConvertSelection(display, XA_PRIMARY, XA_STRING, None,\n    owner, CurrentTime);\n\n  XLockDisplay(display);\n\n  ret_type =\n    XGetWindowProperty(display, owner,\n    XA_STRING, 0, INT_MAX / 4, False, AnyPropertyType, &selection_type,\n    &selection_format, &nbytes, &overflow, &src_data);\n\n  if((ret_type != Success) || ((selection_type != XA_STRING) &&\n    (selection_type != XInternAtom(display, \"TEXT\", False)) &&\n    (selection_type != XInternAtom(display, \"COMPOUND_TEXT\", False)) &&\n    (selection_type != XInternAtom(display, \"UTF8_STRING\", False))))\n    goto err_unlock;\n\n  dest_data = cmalloc(nbytes + 1);\n  memcpy(dest_data, src_data, nbytes);\n  dest_data[nbytes] = 0;\n\n  XFree(src_data);\n\nerr_unlock:\n  XUnlockDisplay(display);\n  return dest_data;\n}\n\nvoid free_clipboard_buffer(char *buffer)\n{\n  free(buffer);\n}\n"
  },
  {
    "path": "src/editor/configure.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n#include \"configure.h\"\n#include \"robo_ed.h\"\n\n#include \"../counter.h\"\n#include \"../configure.h\"\n#include \"../const.h\"\n#include \"../util.h\"\n#include \"../io/vio.h\"\n\n#include <ctype.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n\nstatic struct editor_config_info editor_conf;\nstatic struct editor_config_info editor_conf_backup;\n\nstatic const struct editor_config_info editor_conf_default =\n{\n  // Board editor options\n  false,                        // board_editor_hide_help\n  false,                        // editor_space_toggles\n  false,                        // editor_tab_focuses_view\n  false,                        // editor_load_board_assets\n  true,                         // editor_thing_menu_places\n  true,                         // editor_show_thing_toggles\n  4,                            // editor_show_thing_blink_speed\n  100,                          // Undo history size\n\n  // Defaults for new boards\n  0,                            // viewport_x\n  0,                            // viewport_y\n  80,                           // viewport_w\n  25,                           // viewport_h\n  100,                          // board_width\n  100,                          // board_height\n  1,                            // can_shoot\n  1,                            // can_bomb\n  0,                            // fire_burns_spaces\n  1,                            // fire_burns_fakes\n  1,                            // fire_burns_trees\n  0,                            // fire_burns_brown\n  0,                            // fire_burns_forever\n  1,                            // forest_to_floor\n  1,                            // collect_bombs\n  true,                         // dragons_can_randomly_move\n  0,                            // restart_if_hurt\n  0,                            // reset_on_entry\n  true,                         // reset_on_entry_same_board\n  0,                            // player_locked_ns\n  0,                            // player_locked_ew\n  0,                            // player_locked_att\n  0,                            // time_limit\n  EXPL_LEAVE_ASH,               // explosions_leave (default = ash)\n  CAN_SAVE,                     // saving_enabled (default = enabled)\n  OVERLAY_ON,                   // overlay_enabled (default = enabled)\n  \"\",                           // charset_path\n  \"\",                           // palette_path\n\n  // Palette editor options\n  false,                        // palette_editor_hide_help\n\n  // Robot editor options\n  true,                         // editor_enter_splits\n  { 11, 10, 10, 14, 255, 3, 11, 2, 14, 0, 15, 11, 7, 15, 1, 2, 3 },\n  true,                         // color_coding_on\n  1,                            // default_invalid_status\n  true,                         // disassemble_extras\n  10,                           // disassemble_base\n  false,                        // robot_editor_hide_help\n\n  // Backup options\n  3,                            // backup_count\n  60,                           // backup_interval\n  \"backup\",                     // backup_name\n  \".mzx\",                       // backup_ext\n\n  // Macro options\n  { \"char \", \"color \", \"goto \", \"send \", \": playershot^\" },\n  0,                            // num_extended_macros\n  0,\n  NULL,\n\n  // saved_positions\n  { { 0, 0, 0, 0, 0, 0 } },\n  // vlayer_positions\n  { { 0, 0, 0, 0, 0, 0 } },\n};\n\ntypedef void (* editor_config_function)(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data);\n\nstruct editor_config_entry\n{\n  const char *option_name;\n  editor_config_function change_option;\n};\n\nstatic const struct config_enum default_invalid_status_values[] =\n{\n#ifndef CONFIG_DEBYTECODE\n  { \"ignore\", invalid_uncertain },\n  { \"delete\", invalid_discard },\n  { \"comment\", invalid_comment }\n#else\n  { \"\", 0 }\n#endif\n};\n\nstatic const struct config_enum disassemble_base_values[] =\n{\n  { \"10\", 10 },\n  { \"16\", 16 }\n};\n\nstatic const struct config_enum explosions_leave_values[] =\n{\n  { \"space\", EXPL_LEAVE_SPACE },\n  { \"ash\", EXPL_LEAVE_ASH },\n  { \"fire\", EXPL_LEAVE_FIRE }\n};\n\nstatic const struct config_enum overlay_enabled_values[] =\n{\n  { \"disabled\", OVERLAY_OFF },\n  { \"enabled\", OVERLAY_ON },\n  { \"static\", OVERLAY_STATIC },\n  { \"transparent\", OVERLAY_TRANSPARENT },\n};\n\nstatic const struct config_enum saving_enabled_values[] =\n{\n  { \"disabled\", CANT_SAVE },\n  { \"enabled\", CAN_SAVE },\n  { \"sensoronly\", CAN_SAVE_ON_SENSOR }\n};\n\n// Default colors for color coding:\n// 0 current line - 11\n// 1 immediate  - 10\n// 2 immediates - 10 (unused?)\n// 3 characters - 14\n// 4 colors - color box (255) or 2\n// 5 directions - 3\n// 6 things - 11\n// 7 params - 2\n// 8 strings - 14\n// 9 equalities - 0\n// 10 conditions - 15\n// 11 items - 11\n// 12 extras - 7\n// 13 commands and command fragments - 15\n// 14 invalid line (ignore)\n// 15 invalid line (delete)\n// 16 invalid line (comment)\n\nstatic void config_ccode_chars(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n    conf->color_codes[ROBO_ED_CC_CHARACTERS] = result;\n}\n\nstatic void config_ccode_colors(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n  {\n    conf->color_codes[ROBO_ED_CC_COLORS] = result;\n  }\n  else\n\n  // Special case- 255 instructs the robot editor to use a color box.\n  if(!strcmp(value, \"255\"))\n    conf->color_codes[ROBO_ED_CC_COLORS] = 255;\n}\n\nstatic void config_ccode_commands(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n    conf->color_codes[ROBO_ED_CC_COMMANDS] = result;\n}\n\nstatic void config_ccode_conditions(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n    conf->color_codes[ROBO_ED_CC_CONDITIONS] = result;\n}\n\nstatic void config_ccode_current_line(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n    conf->color_codes[ROBO_ED_CC_CURRENT_LINE] = result;\n}\n\nstatic void config_ccode_directions(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n    conf->color_codes[ROBO_ED_CC_DIRECTIONS] = result;\n}\n\nstatic void config_ccode_equalities(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n    conf->color_codes[ROBO_ED_CC_EQUALITIES] = result;\n}\n\nstatic void config_ccode_extras(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n    conf->color_codes[ROBO_ED_CC_EXTRAS] = result;\n}\n\nstatic void config_ccode_on(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->color_coding_on, value);\n}\n\nstatic void config_ccode_immediates(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n  {\n    conf->color_codes[ROBO_ED_CC_IMMEDIATES] = result;\n    conf->color_codes[ROBO_ED_CC_IMMEDIATES_2] = result;\n  }\n}\n\nstatic void config_ccode_items(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n    conf->color_codes[ROBO_ED_CC_ITEMS] = result;\n}\n\nstatic void config_ccode_params(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n    conf->color_codes[ROBO_ED_CC_PARAMS] = result;\n}\n\nstatic void config_ccode_strings(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n    conf->color_codes[ROBO_ED_CC_STRINGS] = result;\n}\n\nstatic void config_ccode_things(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 15))\n    conf->color_codes[ROBO_ED_CC_THINGS] = result;\n}\n\nstatic void config_default_invalid(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, default_invalid_status_values))\n    conf->default_invalid_status = result;\n}\n\nstatic void config_disassemble_extras(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->disassemble_extras, value);\n}\n\nstatic void config_disassemble_base(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, disassemble_base_values))\n    conf->disassemble_base = result;\n}\n\nstatic void config_macro(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  const char *macro_name = name + 6;\n\n  if(isdigit((int)macro_name[0]) && !macro_name[1] && !extended_data)\n  {\n    size_t macro_num = macro_name[0] - '1';\n    if(macro_num < ARRAY_SIZE(conf->default_macros))\n      config_string(conf->default_macros[macro_num], value);\n  }\n  else\n  {\n    if(extended_data)\n      add_ext_macro(conf, macro_name, extended_data, value);\n  }\n}\n\nstatic void board_editor_hide_help(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->board_editor_hide_help, value);\n}\n\nstatic void robot_editor_hide_help(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->robot_editor_hide_help, value);\n}\n\nstatic void palette_editor_hide_help(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->palette_editor_hide_help, value);\n}\n\nstatic void backup_count(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, INT_MAX))\n    conf->backup_count = result;\n}\n\nstatic void backup_interval(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, INT_MAX))\n    conf->backup_interval = result;\n}\n\nstatic void backup_name(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->backup_name, value);\n}\n\nstatic void backup_ext(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->backup_ext, value);\n}\n\nstatic void config_editor_space_toggles(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->editor_space_toggles, value);\n}\n\nstatic void config_editor_enter_splits(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->editor_enter_splits, value);\n}\n\nstatic void config_editor_load_board_assets(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->editor_load_board_assets, value);\n}\n\nstatic void config_editor_tab_focus(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->editor_tab_focuses_view, value);\n}\n\nstatic void config_editor_thing_menu_places(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->editor_thing_menu_places, value);\n}\n\nstatic void config_editor_show_thing_toggles(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->editor_show_thing_toggles, value);\n}\n\nstatic void config_editor_show_thing_blink_speed(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, INT_MAX))\n    conf->editor_show_thing_blink_speed = result;\n}\n\nstatic void config_undo_history_size(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 1000))\n    conf->undo_history_size = result;\n}\n\nstatic void config_saved_positions(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  unsigned int pos;\n  unsigned int board_num;\n  unsigned int board_x;\n  unsigned int board_y;\n  unsigned int scroll_x;\n  unsigned int scroll_y;\n  unsigned int debug_x;\n  int n;\n\n  if(sscanf(name, \"saved_position%u\", &pos) != 1)\n    return;\n\n  if(sscanf(value, \"%u, %u, %u, %u, %u, %u%n\",\n   &board_num, &board_x, &board_y, &scroll_x, &scroll_y, &debug_x, &n) != 6 ||\n   value[n])\n    return;\n\n  // Check for sane values only. This is not guaranteed to properly bound these.\n  if(pos < NUM_SAVED_POSITIONS && (board_num < MAX_BOARDS) &&\n   (board_x < 32768) && (board_y < 32768) && (scroll_x < 32768) &&\n   (scroll_y < 32768) && (debug_x < 80))\n  {\n    struct saved_position *s = &(conf->saved_positions[pos]);\n    s->board_id = board_num;\n    s->cursor_x = board_x;\n    s->cursor_y = board_y;\n    s->scroll_x = scroll_x;\n    s->scroll_y = scroll_y;\n    s->debug_x = debug_x ? 60 : 0;\n  }\n}\n\nstatic void config_vlayer_positions(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  unsigned int pos;\n  unsigned int vlayer_x;\n  unsigned int vlayer_y;\n  unsigned int scroll_x;\n  unsigned int scroll_y;\n  unsigned int debug_x;\n  int n;\n\n  if(sscanf(name, \"vlayer_position%u\", &pos) != 1)\n    return;\n\n  if(sscanf(value, \"%u, %u, %u, %u, %u%n\",\n   &vlayer_x, &vlayer_y, &scroll_x, &scroll_y, &debug_x, &n) != 5 ||\n   value[n])\n    return;\n\n  // Check for sane values only. The editor needs to properly bound these later.\n  if(pos < NUM_SAVED_POSITIONS && (vlayer_x < 32768) && (vlayer_y < 32768) &&\n   (scroll_x < 32768) && (scroll_y < 32768) && (debug_x < 80))\n  {\n    struct saved_position *s = &(conf->vlayer_positions[pos]);\n    s->board_id = 0;\n    s->cursor_x = vlayer_x;\n    s->cursor_y = vlayer_y;\n    s->scroll_x = scroll_x;\n    s->scroll_y = scroll_y;\n    s->debug_x = debug_x ? 60 : 0;\n  }\n}\n\n/******************/\n/* BOARD DEFAULTS */\n/******************/\n\nstatic void config_board_width(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 1, 32767))\n  {\n    conf->board_width = result;\n    if(conf->viewport_w > conf->board_width)\n      conf->viewport_w = conf->board_width;\n    if(conf->board_width * conf->board_height > MAX_BOARD_SIZE)\n      conf->board_height = MAX_BOARD_SIZE / conf->board_width;\n  }\n}\n\nstatic void config_board_height(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 1, 32767))\n  {\n    conf->board_height = result;\n    if(conf->viewport_h > conf->board_height)\n      conf->viewport_h = conf->board_height;\n    if(conf->board_width * conf->board_height > MAX_BOARD_SIZE)\n      conf->board_width = MAX_BOARD_SIZE / conf->board_height;\n  }\n}\n\nstatic void config_board_viewport_w(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 1, 80))\n  {\n    conf->viewport_w = MIN(result, conf->board_width);\n    if(conf->viewport_x + conf->viewport_w > 80)\n      conf->viewport_x = 80 - conf->viewport_w;\n  }\n}\n\nstatic void config_board_viewport_h(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 1, 25))\n  {\n    conf->viewport_h = MIN(result, conf->board_height);\n    if(conf->viewport_y + conf->viewport_h > 25)\n      conf->viewport_y = 25 - conf->viewport_h;\n  }\n}\n\nstatic void config_board_viewport_x(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 79))\n    conf->viewport_x = MIN(result, 80 - conf->viewport_w);\n}\n\nstatic void config_board_viewport_y(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 24))\n    conf->viewport_y = MIN(result, 25 - conf->viewport_h);\n}\n\nstatic void config_board_can_shoot(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->can_shoot, value);\n}\n\nstatic void config_board_can_bomb(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->can_bomb, value);\n}\n\nstatic void config_board_fire_spaces(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->fire_burns_spaces, value);\n}\n\nstatic void config_board_fire_fakes(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->fire_burns_fakes, value);\n}\n\nstatic void config_board_fire_trees(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->fire_burns_trees, value);\n}\n\nstatic void config_board_fire_brown(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->fire_burns_brown, value);\n}\n\nstatic void config_board_fire_forever(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->fire_burns_forever, value);\n}\n\nstatic void config_board_forest(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->forest_to_floor, value);\n}\n\nstatic void config_board_collect_bombs(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->collect_bombs, value);\n}\n\nstatic void config_board_dragons_random_move(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->dragons_can_randomly_move, value);\n}\n\nstatic void config_board_restart(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->restart_if_hurt, value);\n}\n\nstatic void config_board_reset_on_entry(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->reset_on_entry, value);\n}\n\nstatic void config_board_reset_on_entry_same(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->reset_on_entry_same_board, value);\n}\n\nstatic void config_board_locked_ns(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->player_locked_ns, value);\n}\n\nstatic void config_board_locked_ew(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->player_locked_ew, value);\n}\n\nstatic void config_board_locked_att(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_boolean(&conf->player_locked_att, value);\n}\n\nstatic void config_board_time_limit(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_int(&result, value, 0, 32767))\n    conf->time_limit = result;\n}\n\nstatic void config_board_charset(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->charset_path, value);\n}\n\nstatic void config_board_palette(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  config_string(conf->palette_path, value);\n}\n\nstatic void config_board_explosions(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, explosions_leave_values))\n    conf->explosions_leave = result;\n}\n\nstatic void config_board_saving(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, saving_enabled_values))\n    conf->saving_enabled = result;\n}\n\nstatic void config_board_overlay(struct editor_config_info *conf,\n const char *name, const char *value, const char *extended_data)\n{\n  int result;\n  if(config_enum(&result, value, overlay_enabled_values))\n    conf->overlay_enabled = result;\n}\n\n/**********************/\n/* END BOARD DEFAULTS */\n/**********************/\n\n/* FAT NOTE: This is searched as a binary tree, the nodes must be\n *           sorted alphabetically, or they risk being ignored.\n */\nstatic const struct editor_config_entry editor_config_options[] =\n{\n  { \"backup_count\", backup_count },\n  { \"backup_ext\", backup_ext },\n  { \"backup_interval\", backup_interval },\n  { \"backup_name\", backup_name },\n  { \"board_default_can_bomb\", config_board_can_bomb },\n  { \"board_default_can_shoot\", config_board_can_shoot },\n  { \"board_default_charset_path\", config_board_charset },\n  { \"board_default_collect_bombs\", config_board_collect_bombs },\n  { \"board_default_dragons_can_randomly_move\", config_board_dragons_random_move },\n  { \"board_default_explosions_leave\", config_board_explosions },\n  { \"board_default_fire_burns_brown\", config_board_fire_brown },\n  { \"board_default_fire_burns_fakes\", config_board_fire_fakes },\n  { \"board_default_fire_burns_forever\", config_board_fire_forever },\n  { \"board_default_fire_burns_spaces\", config_board_fire_spaces },\n  { \"board_default_fire_burns_trees\", config_board_fire_trees },\n  { \"board_default_forest_to_floor\", config_board_forest },\n  { \"board_default_height\", config_board_height },\n  { \"board_default_overlay\", config_board_overlay },\n  { \"board_default_palette_path\", config_board_palette },\n  { \"board_default_player_locked_att\", config_board_locked_att },\n  { \"board_default_player_locked_ew\", config_board_locked_ew },\n  { \"board_default_player_locked_ns\", config_board_locked_ns },\n  { \"board_default_reset_on_entry\", config_board_reset_on_entry },\n  { \"board_default_reset_on_entry_same_board\", config_board_reset_on_entry_same },\n  { \"board_default_restart_if_hurt\", config_board_restart },\n  { \"board_default_saving\", config_board_saving },\n  { \"board_default_time_limit\", config_board_time_limit },\n  { \"board_default_viewport_h\", config_board_viewport_h },\n  { \"board_default_viewport_w\", config_board_viewport_w },\n  { \"board_default_viewport_x\", config_board_viewport_x },\n  { \"board_default_viewport_y\", config_board_viewport_y },\n  { \"board_default_width\", config_board_width },\n  { \"board_editor_hide_help\", board_editor_hide_help },\n  { \"ccode_chars\", config_ccode_chars },\n  { \"ccode_colors\", config_ccode_colors },\n  { \"ccode_commands\", config_ccode_commands },\n  { \"ccode_conditions\", config_ccode_conditions },\n  { \"ccode_current_line\", config_ccode_current_line },\n  { \"ccode_directions\", config_ccode_directions },\n  { \"ccode_equalities\", config_ccode_equalities },\n  { \"ccode_extras\", config_ccode_extras },\n  { \"ccode_immediates\", config_ccode_immediates },\n  { \"ccode_items\", config_ccode_items },\n  { \"ccode_params\", config_ccode_params },\n  { \"ccode_strings\", config_ccode_strings },\n  { \"ccode_things\", config_ccode_things },\n  { \"color_coding_on\", config_ccode_on },\n  { \"default_invalid_status\", config_default_invalid },\n  { \"disassemble_base\", config_disassemble_base },\n  { \"disassemble_extras\", config_disassemble_extras },\n  { \"editor_enter_splits\", config_editor_enter_splits },\n  { \"editor_load_board_assets\", config_editor_load_board_assets },\n  { \"editor_show_thing_blink_speed\", config_editor_show_thing_blink_speed },\n  { \"editor_show_thing_toggles\", config_editor_show_thing_toggles },\n  { \"editor_space_toggles\", config_editor_space_toggles },\n  { \"editor_tab_focuses_view\", config_editor_tab_focus },\n  { \"editor_thing_menu_places\", config_editor_thing_menu_places },\n  { \"macro_*\", config_macro },\n  { \"palette_editor_hide_help\", palette_editor_hide_help },\n  { \"robot_editor_hide_help\", robot_editor_hide_help },\n  { \"saved_position!\", config_saved_positions },\n  { \"undo_history_size\", config_undo_history_size },\n  { \"vlayer_position!\", config_vlayer_positions },\n};\n\nstatic const struct editor_config_entry *find_editor_option(const char *name,\n const struct editor_config_entry options[], int num_options)\n{\n  int cmpval, top = num_options - 1, middle, bottom = 0;\n  const struct editor_config_entry *base = options;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    cmpval = match_function_counter(name, (base[middle]).option_name);\n\n    if(cmpval > 0)\n      bottom = middle + 1;\n    else\n\n    if(cmpval < 0)\n      top = middle - 1;\n    else\n      return base + middle;\n  }\n\n  return NULL;\n}\n\nstatic boolean editor_config_change_option(void *conf, const char *name,\n const char *value, const char *extended_data)\n{\n  const struct editor_config_entry *current_option = find_editor_option(name,\n   editor_config_options, ARRAY_SIZE(editor_config_options));\n\n  if(current_option)\n  {\n    current_option->change_option(conf, name, value, extended_data);\n    return true;\n  }\n  return false;\n}\n\nstruct editor_config_info *get_editor_config(void)\n{\n  return &editor_conf;\n}\n\nvoid default_editor_config(void)\n{\n  static boolean registered = false;\n  memcpy(&editor_conf, &editor_conf_default, sizeof(struct editor_config_info));\n\n  if(!registered)\n  {\n    register_config(SYSTEM_CNF, &editor_conf, editor_config_change_option);\n    register_config(GAME_EDITOR_CNF, &editor_conf, editor_config_change_option);\n    registered = true;\n  }\n}\n\nvoid store_editor_config_backup(void)\n{\n  // Back up the config.\n  memcpy(&editor_conf_backup, &editor_conf, sizeof(struct editor_config_info));\n  editor_conf_backup.extended_macros = NULL;\n}\n\nvoid load_editor_config_backup(void)\n{\n  // Restore the editor config (but keep the macros).\n  int num_macros = editor_conf.num_extended_macros;\n  int num_macros_alloc = editor_conf.num_macros_allocated;\n  struct ext_macro **macros = editor_conf.extended_macros;\n\n  memcpy(&editor_conf, &editor_conf_backup, sizeof(struct editor_config_info));\n\n  editor_conf.extended_macros = macros;\n  editor_conf.num_extended_macros = num_macros;\n  editor_conf.num_macros_allocated = num_macros_alloc;\n}\n\nstatic void __free_editor_config(struct editor_config_info *conf)\n{\n  // Extended Macros\n  if(conf->extended_macros)\n  {\n    int i;\n    for(i = 0; i < conf->num_extended_macros; i++)\n      free_macro(conf->extended_macros[i]);\n\n    free(conf->extended_macros);\n    conf->extended_macros = NULL;\n  }\n}\n\nvoid free_editor_config(void)\n{\n  __free_editor_config(&editor_conf);\n  __free_editor_config(&editor_conf_backup);\n}\n\nstatic size_t editor_config_full_name(char *dest, size_t dest_len,\n const char *path, size_t path_len)\n{\n  size_t ret = snprintf(dest, dest_len, \"%.*s.editor.cnf\",\n   (int)path_len - 4, path);\n  dest[dest_len - 1] = '\\0';\n  return ret;\n}\n\nstatic size_t editor_config_dos_name(char *dest, size_t dest_len,\n const char *path, size_t path_len)\n{\n  size_t ret = snprintf(dest, dest_len, \"%.*s.cne\",\n   (int)path_len - 4, path);\n  dest[dest_len - 1] = '\\0';\n  return ret;\n}\n\n/* Get the existing world file editor config name, if present. It may have\n * the extension .editor.cnf (usual) or the extension .cne (DOS). */\nsize_t get_local_editor_config_name(char *dest, size_t dest_len,\n const char *mzx_file_path)\n{\n  struct stat file_info;\n  size_t path_len = strlen(mzx_file_path);\n  size_t ret;\n\n  if(path_len < 4)\n    return 0;\n\n  ret = editor_config_full_name(dest, dest_len, mzx_file_path, path_len);\n  if(ret < dest_len &&\n   vstat(dest, &file_info) >= 0 && S_ISREG(file_info.st_mode))\n  {\n    debug(\"found editor config: %s\\n\", dest);\n    return ret;\n  }\n\n  ret = editor_config_dos_name(dest, dest_len, mzx_file_path, path_len);\n  if(ret < dest_len &&\n   vstat(dest, &file_info) >= 0 && S_ISREG(file_info.st_mode))\n  {\n    debug(\"found editor config: %s\\n\", dest);\n    return ret;\n  }\n\n  return 0;\n}\n\n// TODO: update this function once there's a good config rewriting routine.\nvoid save_local_editor_config(struct editor_config_info *conf,\n const char *mzx_file_path)\n{\n  size_t mzx_file_len = strlen(mzx_file_path);\n  char config_file_name[MAX_PATH];\n\n  const char comment[]   = \"\\n############################################################\";\n  const char comment_a[] = \"\\n#####  Editor generated configuration - do not modify  #####\";\n  const char comment_b[] = \"\\n#####        End editor generated configuration        #####\";\n  const char *value;\n  vfile *vf = NULL;\n  boolean exists = false;\n  int i;\n\n  if(mzx_file_len < 4)\n    return;\n\n  // Does it exist?\n  if(get_local_editor_config_name(config_file_name, MAX_PATH, mzx_file_path) > 0)\n    vf = vfopen_unsafe(config_file_name, \"rb\");\n\n  if(vf)\n  {\n    char *a, *b;\n    char *config_file = NULL;\n    char *config_file_b = NULL;\n    int config_file_size = 0;\n    int config_file_size_b = 0;\n\n    exists = true;\n\n    config_file_size = vfilelength(vf, true);\n    config_file = cmalloc(config_file_size + 1);\n\n    config_file_size = vfread(config_file, 1, config_file_size, vf);\n    config_file[config_file_size] = 0;\n    vfclose(vf);\n\n    a = strstr(config_file, comment_a);\n    b = strstr(config_file, comment_b);\n\n    // Start of special section\n    if(a)\n    {\n      a[0] = '\\0';\n      a = strrchr(config_file, '\\n');\n      if(a)\n      {\n        if(a > config_file && a[-1] == '\\r')\n          a[-1] = '\\0';\n        else\n          a[0] = '\\0';\n\n        config_file_size = strlen(config_file);\n      }\n      else\n      {\n        config_file[0] = '\\0';\n        config_file_size = 0;\n      }\n\n      // End of special section\n      // Skip matched line and comment line afterward\n      if(b && (b = strchr(b + 1, '\\n')) && (b = strchr(b + 1, '\\n')))\n      {\n        config_file_b = b + 1;\n        config_file_size_b = strlen(config_file_b);\n      }\n    }\n\n    vf = vfopen_unsafe(config_file_name, \"wb\");\n    if(!vf)\n    {\n      warn(\"failed to rewrite editor config file\\n\");\n      return;\n    }\n\n    if(config_file_size)\n      vfwrite(config_file, 1, config_file_size, vf);\n    if(config_file_size_b)\n      vfwrite(config_file_b, 1, config_file_size_b, vf);\n\n    vfclose(vf);\n    free(config_file);\n  }\n\n  if(!exists)\n  {\n    // Try creating a file with the normal name first. This may fail in DOS.\n    editor_config_full_name(config_file_name, MAX_PATH,\n     mzx_file_path, mzx_file_len);\n  }\n\n  vf = vfopen_unsafe(config_file_name, \"a\");\n  if(!vf)\n  {\n    if(!exists)\n    {\n      editor_config_dos_name(config_file_name, MAX_PATH,\n       mzx_file_path, mzx_file_len);\n      vf = vfopen_unsafe(config_file_name, \"a\");\n    }\n    if(!vf)\n    {\n      warn(\"failed to rewrite editor config file\\n\");\n      return;\n    }\n  }\n  debug(\"writing editor config: %s\\n\", config_file_name);\n\n  vf_printf(vf, \"%s%s%s\\n\\n\", comment, comment_a, comment);\n\n  vf_printf(vf, \"board_default_width = %d\\n\", conf->board_width);\n  vf_printf(vf, \"board_default_height = %d\\n\", conf->board_height);\n  vf_printf(vf, \"board_default_viewport_w = %d\\n\", conf->viewport_w);\n  vf_printf(vf, \"board_default_viewport_h = %d\\n\", conf->viewport_h);\n  vf_printf(vf, \"board_default_viewport_x = %d\\n\", conf->viewport_x);\n  vf_printf(vf, \"board_default_viewport_y = %d\\n\", conf->viewport_y);\n  vf_printf(vf, \"board_default_can_shoot = %d\\n\", conf->can_shoot);\n  vf_printf(vf, \"board_default_can_bomb = %d\\n\", conf->can_bomb);\n  vf_printf(vf, \"board_default_fire_burns_spaces = %d\\n\", conf->fire_burns_spaces);\n  vf_printf(vf, \"board_default_fire_burns_fakes = %d\\n\", conf->fire_burns_fakes);\n  vf_printf(vf, \"board_default_fire_burns_trees = %d\\n\", conf->fire_burns_trees);\n  vf_printf(vf, \"board_default_fire_burns_brown = %d\\n\", conf->fire_burns_brown);\n  vf_printf(vf, \"board_default_fire_burns_forever = %d\\n\", conf->fire_burns_forever);\n  vf_printf(vf, \"board_default_forest_to_floor = %d\\n\", conf->forest_to_floor);\n  vf_printf(vf, \"board_default_collect_bombs = %d\\n\", conf->collect_bombs);\n  vf_printf(vf, \"board_default_dragons_can_randomly_move = %d\\n\",\n   conf->dragons_can_randomly_move);\n  vf_printf(vf, \"board_default_restart_if_hurt = %d\\n\", conf->restart_if_hurt);\n  vf_printf(vf, \"board_default_reset_on_entry = %d\\n\", conf->reset_on_entry);\n  vf_printf(vf, \"board_default_reset_on_entry_same_board = %d\\n\",\n   conf->reset_on_entry_same_board);\n  vf_printf(vf, \"board_default_player_locked_ns = %d\\n\", conf->player_locked_ns);\n  vf_printf(vf, \"board_default_player_locked_ew = %d\\n\", conf->player_locked_ew);\n  vf_printf(vf, \"board_default_player_locked_att = %d\\n\", conf->player_locked_att);\n  vf_printf(vf, \"board_default_time_limit = %d\\n\", conf->time_limit);\n  vf_printf(vf, \"board_default_charset_path = %s\\n\", conf->charset_path);\n  vf_printf(vf, \"board_default_palette_path = %s\\n\", conf->palette_path);\n\n  switch(conf->explosions_leave)\n  {\n    default:\n    case EXPL_LEAVE_SPACE:\n      value = \"space\";\n      break;\n    case EXPL_LEAVE_ASH:\n      value = \"ash\";\n      break;\n    case EXPL_LEAVE_FIRE:\n      value = \"fire\";\n      break;\n  }\n  vf_printf(vf, \"board_default_explosions_leave = %s\\n\", value);\n\n  switch(conf->saving_enabled)\n  {\n    default:\n    case CANT_SAVE:\n      value = \"disabled\";\n      break;\n    case CAN_SAVE:\n      value = \"enabled\";\n      break;\n    case CAN_SAVE_ON_SENSOR:\n      value = \"sensoronly\";\n      break;\n  }\n  vf_printf(vf, \"board_default_saving = %s\\n\", value);\n\n  switch(conf->overlay_enabled)\n  {\n    default:\n    case OVERLAY_OFF:\n      value = \"disabled\";\n      break;\n    case OVERLAY_ON:\n      value = \"enabled\";\n      break;\n    case OVERLAY_STATIC:\n      value = \"static\";\n      break;\n    case OVERLAY_TRANSPARENT:\n      value = \"transparent\";\n      break;\n  }\n  vf_printf(vf, \"board_default_overlay = %s\\n\", value);\n\n  vfputs(\"\\n\", vf);\n  for(i = 0; i < NUM_SAVED_POSITIONS; i++)\n  {\n    struct saved_position *s = &(conf->saved_positions[i]);\n    vf_printf(vf, \"saved_position%u = %u, %u, %u, %u, %u, %u\\n\", i,\n     s->board_id, s->cursor_x, s->cursor_y, s->scroll_x, s->scroll_y, s->debug_x);\n  }\n  for(i = 0; i < NUM_SAVED_POSITIONS; i++)\n  {\n    struct saved_position *s = &(conf->vlayer_positions[i]);\n    vf_printf(vf, \"vlayer_position%u = %u, %u, %u, %u, %u\\n\", i,\n     s->cursor_x, s->cursor_y, s->scroll_x, s->scroll_y, s->debug_x);\n  }\n\n  vf_printf(vf, \"%s%s%s\\n\", comment, comment_b, comment);\n  vfclose(vf);\n}\n"
  },
  {
    "path": "src/editor/configure.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_CONFIGURE_H\n#define __EDITOR_CONFIGURE_H\n\n#include \"../compat.h\"\n#include \"../const.h\"\n\n__M_BEGIN_DECLS\n\n#include \"macro.h\"\n\n#define NUM_SAVED_POSITIONS 10\n\nenum robo_ed_color_codes\n{\n  ROBO_ED_CC_CURRENT_LINE,\n  ROBO_ED_CC_IMMEDIATES,\n  ROBO_ED_CC_IMMEDIATES_2,\n  ROBO_ED_CC_CHARACTERS,\n  ROBO_ED_CC_COLORS,\n  ROBO_ED_CC_DIRECTIONS,\n  ROBO_ED_CC_THINGS,\n  ROBO_ED_CC_PARAMS,\n  ROBO_ED_CC_STRINGS,\n  ROBO_ED_CC_EQUALITIES,\n  ROBO_ED_CC_CONDITIONS,\n  ROBO_ED_CC_ITEMS,\n  ROBO_ED_CC_EXTRAS,\n  ROBO_ED_CC_COMMANDS,\n  ROBO_ED_CC_INVALID_IGNORE,\n  ROBO_ED_CC_INVALID_DELETE,\n  ROBO_ED_CC_INVALID_COMMENT\n};\n\nstruct saved_position\n{\n  int board_id;\n  int cursor_x;\n  int cursor_y;\n  int scroll_x;\n  int scroll_y;\n  int debug_x;\n};\n\nstruct editor_config_info\n{\n  // Board editor options\n  boolean board_editor_hide_help;\n  boolean editor_space_toggles;\n  boolean editor_tab_focuses_view;\n  boolean editor_load_board_assets;\n  boolean editor_thing_menu_places;\n  boolean editor_show_thing_toggles;\n  int editor_show_thing_blink_speed;\n  int undo_history_size;\n\n  // Defaults for new boards\n  int viewport_x;\n  int viewport_y;\n  int viewport_w;\n  int viewport_h;\n  int board_width;\n  int board_height;\n  boolean can_shoot;\n  boolean can_bomb;\n  boolean fire_burns_spaces;\n  boolean fire_burns_fakes;\n  boolean fire_burns_trees;\n  boolean fire_burns_brown;\n  boolean fire_burns_forever;\n  boolean forest_to_floor;\n  boolean collect_bombs;\n  boolean dragons_can_randomly_move;\n  boolean restart_if_hurt;\n  boolean reset_on_entry;\n  boolean reset_on_entry_same_board;\n  boolean player_locked_ns;\n  boolean player_locked_ew;\n  boolean player_locked_att;\n  int time_limit;\n  int explosions_leave;\n  int saving_enabled;\n  int overlay_enabled;\n  char charset_path[MAX_PATH];\n  char palette_path[MAX_PATH];\n\n  // Palette editor options\n  boolean palette_editor_hide_help;\n\n  // Robot editor options\n  boolean editor_enter_splits;\n  char color_codes[32];\n  boolean color_coding_on;\n  int default_invalid_status;\n  boolean disassemble_extras;\n  int disassemble_base;\n  boolean robot_editor_hide_help;\n\n  // Backup options\n  int backup_count;\n  int backup_interval;\n  char backup_name[256];\n  char backup_ext[256];\n\n  // Macro options\n  char default_macros[5][64];\n  int num_extended_macros;\n  int num_macros_allocated;\n  struct ext_macro **extended_macros;\n\n  // Saved positions\n  struct saved_position saved_positions[NUM_SAVED_POSITIONS];\n  struct saved_position vlayer_positions[NUM_SAVED_POSITIONS];\n};\n\nEDITOR_LIBSPEC void default_editor_config(void);\nEDITOR_LIBSPEC void store_editor_config_backup(void);\nEDITOR_LIBSPEC void free_editor_config(void);\n\nEDITOR_LIBSPEC struct editor_config_info *get_editor_config(void);\nvoid load_editor_config_backup(void);\n\nsize_t get_local_editor_config_name(char *dest, size_t dest_len,\n const char *mzx_file_path);\nvoid save_local_editor_config(struct editor_config_info *conf,\n const char *mzx_file_path);\n\n__M_END_DECLS\n\n#endif // __EDITOR_CONFIGURE_H\n"
  },
  {
    "path": "src/editor/debug.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2012-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"debug.h\"\n\n#include \"char_ed.h\"\n#include \"pal_ed.h\"\n#include \"robo_debug.h\"\n#include \"stringsearch.h\"\n#include \"window.h\"\n\n#include \"../core.h\"\n#include \"../counter.h\"\n#include \"../event.h\"\n#include \"../extmem.h\"\n#include \"../graphics.h\"\n#include \"../intake.h\"\n#include \"../memcasecmp.h\"\n#include \"../scrdisp.h\"\n#include \"../sprite.h\"\n#include \"../str.h\"\n#include \"../util.h\"\n#include \"../window.h\"\n#include \"../world.h\"\n#include \"../io/vio.h\"\n\n#include \"../audio/audio.h\"\n\n#include <ctype.h>\n#include <stdint.h>\n#include <string.h>\n\n#define TREE_LIST_X 62\n#define TREE_LIST_Y 2\n#define TREE_LIST_WIDTH 16\n#define TREE_LIST_HEIGHT 15\n#define VAR_LIST_X 2\n#define VAR_LIST_Y 2\n#define VAR_LIST_WIDTH 58\n#define VAR_LIST_HEIGHT 21\n#define BUTTONS_X 62\n#define BUTTONS_Y 18\n#define CVALUE_SIZE 11\n#define LVALUE_SIZE 21\n#define SVALUE_SIZE 40\n#define CVALUE_COL_OFFSET (VAR_LIST_WIDTH - CVALUE_SIZE - 1)\n#define LVALUE_COL_OFFSET (VAR_LIST_WIDTH - LVALUE_SIZE - 1)\n#define SVALUE_COL_OFFSET (VAR_LIST_WIDTH - SVALUE_SIZE - 1)\n\n#define VAR_SEARCH_DIALOG_X 4\n#define VAR_SEARCH_DIALOG_Y 9\n#define VAR_SEARCH_DIALOG_W 71\n#define VAR_SEARCH_DIALOG_H 5\n#define VAR_SEARCH_MAX 48\n#define VAR_SEARCH_NAMES    1\n#define VAR_SEARCH_VALUES   2\n#define VAR_SEARCH_CASESENS 4\n#define VAR_SEARCH_REVERSE  8\n#define VAR_SEARCH_WRAP     16\n#define VAR_SEARCH_LOCAL    32\n#define VAR_SEARCH_EXACT    64\n\n#define VAR_ADD_DIALOG_X 20\n#define VAR_ADD_DIALOG_Y 8\n#define VAR_ADD_DIALOG_W 40\n#define VAR_ADD_DIALOG_H 6\n#define VAR_ADD_MAX 30\n\nstatic const char asc[17] = \"0123456789ABCDEF\";\n\n// Escape \\n. Use for most debug var name/text fields. This is intended for\n// display only and doesn't escape \\. For anything that needs to be edited\n// or unescaped, use copy_substring_escaped instead. FIXME it'd be nice if\n// list box elements could print these chars instead of making this necessary.\nstatic void copy_name_escaped(char *dest, size_t dest_len, const char *src,\n size_t src_len)\n{\n  unsigned int i, j, left;\n\n  for(i = 0, j = 0; j < src_len; i++, j++)\n  {\n    left = dest_len - i;\n    if(src[j] == '\\n')\n    {\n      if(left < 3)\n        break;\n\n      dest[i++] = '\\\\';\n      dest[i] = 'n';\n    }\n    else\n    {\n      if(left < 2)\n        break;\n\n      dest[i] = src[j];\n    }\n  }\n\n  dest[i] = 0;\n}\n\n// Escape all non-ASCII chars. Use for string values.\nstatic void copy_substring_escaped(char *dest, size_t dest_len, const char *src,\n size_t src_len)\n{\n  unsigned int i, j, left;\n\n  for(i = 0, j = 0; j < src_len; i++, j++)\n  {\n    left = dest_len - i;\n    if(src[j] == '\\\\')\n    {\n      if(left < 3)\n        break;\n\n      dest[i++] = '\\\\';\n      dest[i] = '\\\\';\n    }\n    else\n\n    if(src[j] == '\\n')\n    {\n      if(left < 3)\n        break;\n\n      dest[i++] = '\\\\';\n      dest[i] = 'n';\n    }\n    else\n\n    if(src[j] == '\\r')\n    {\n      if(left < 3)\n        break;\n\n      dest[i++] = '\\\\';\n      dest[i] = 'r';\n    }\n    else\n\n    if(src[j] == '\\t')\n    {\n      if(left < 3)\n        break;\n\n      dest[i++] = '\\\\';\n      dest[i] = 't';\n    }\n    else\n\n    if(src[j] < 32 || src[j] > 126)\n    {\n      if(left < 5)\n        break;\n\n      dest[i++] = '\\\\';\n      dest[i++] = 'x';\n      dest[i++] = asc[src[j] >> 4];\n      dest[i] = asc[src[j] & 15];\n    }\n    else\n    {\n      if(left < 2)\n        break;\n\n      dest[i] = src[j];\n    }\n  }\n\n  dest[i] = 0;\n}\n\n// Unescape a string escaped by copy_substring_escaped.\nstatic void unescape_string(char *buf, int *len)\n{\n  size_t i = 0, j, old_len = strlen(buf);\n  char tmp[3];\n\n  for(j = 0; j < old_len; i++, j++)\n  {\n    if(buf[j] != '\\\\')\n    {\n      buf[i] = buf[j];\n      continue;\n    }\n\n    j++;\n\n    if(j == old_len)\n      break;\n\n    switch(buf[j])\n    {\n      case 'n':\n        buf[i] = '\\n';\n        break;\n      case 'r':\n        buf[i] = '\\r';\n        break;\n      case 't':\n        buf[i] = '\\t';\n        break;\n      case '\\\\':\n        buf[i] = '\\\\';\n        break;\n\n      case 'x':\n        if(j + 2 >= old_len)\n        {\n          j = old_len;\n          break;\n        }\n        tmp[0] = buf[j + 1];\n        tmp[1] = buf[j + 2];\n        tmp[2] = '\\0';\n        buf[i] = strtol(tmp, NULL, 16);\n        j += 2;\n        break;\n\n      default:\n        buf[i] = buf[j];\n        break;\n    }\n  }\n\n  (*len) = i;\n}\n\n/***********************\n * Var reading/setting *\n ***********************/\n\nenum virtual_var\n{\n  VIR_RAM_COUNTER_LIST,\n  VIR_RAM_COUNTER_TABLE,\n  VIR_RAM_COUNTERS,\n  VIR_RAM_STRING_LIST,\n  VIR_RAM_STRING_TABLE,\n  VIR_RAM_STRINGS,\n  VIR_RAM_SFX,\n  VIR_RAM_SPRITES,\n  VIR_RAM_VLAYER,\n  VIR_RAM_BOARD_INFO,\n  VIR_RAM_BOARD_DATA,\n  VIR_RAM_BOARD_OVERLAY,\n  VIR_RAM_ROBOT_INFO,\n  VIR_RAM_ROBOT_STACK,\n  VIR_RAM_ROBOT_SOURCE,\n  VIR_RAM_ROBOT_PROGRAMS,\n  VIR_RAM_ROBOT_PROGRAM_LABELS,\n  VIR_RAM_SCROLLS_SENSORS,\n  VIR_RAM_VIDEO_LAYERS,\n  VIR_RAM_DEBUGGER_ROBOTS,\n  VIR_RAM_DEBUGGER_VARIABLES,\n  VIR_RAM_EXTRAM_DELTA,\n  VIR_RAM_VIRTUAL_FILESYSTEM,\n  VIR_RAM_VIRTUAL_FILESYSTEM_CACHED_ONLY,\n};\n\nstatic const char * const virtual_var_names[] =\n{\n  \"Counter list*\",\n  \"Counter table*\",\n  \"Counters*\",\n  \"String list*\",\n  \"String table*\",\n  \"Strings*\",\n  \"Custom SFX*\",\n  \"Sprites*\",\n  \"Vlayer*\",\n  \"Board info*\",\n  \"Board data*\",\n  \"Board overlay*\",\n  \"Robot info*\",\n  \"Robot stack*\",\n  \"Robot program source*\",\n  \"Robot program bytecode*\",\n  \"Robot program labels*\",\n  \"Scrolls and sensors*\",\n  \"Video layers*\",\n  \"Debug (robots)*\",\n  \"Debug (variables)*\",\n  \"ExtRAM compression delta*\",\n  \"Virtual filesystem (total)*\",\n  \"Virtual filesystem (cached only)*\",\n};\n\n// We'll read off of these when we construct the tree\nstatic const char *universal_var_list[] =\n{\n  \"random_seed0\",\n  \"random_seed1\",\n  \"date_year*\",\n  \"date_month*\",\n  \"date_day*\",\n  \"date_weekday*\",\n  \"time_hours*\",\n  \"time_minutes*\",\n  \"time_seconds*\",\n  \"time_millis*\",\n};\n\nstatic const char *world_var_list[] =\n{\n  \"bimesg\", //no read\n  \"c_divisions\",\n  \"commands\",\n  \"commands_stop\",\n  \"divider\",\n  \"fread_delimiter\", //no read\n  \"fwrite_delimiter\", //no read\n  \"joy_simulate_keys\", //no read\n  \"max_samples\",\n  \"mod_frequency\",\n  \"mod_length*\",\n  \"mod_loopend\",\n  \"mod_loopstart\",\n  \"mod_name*\",\n  \"mod_order\",\n  \"mod_position\",\n  \"multiplier\",\n  \"mzx_speed\",\n  \"smzx_message\",\n  \"smzx_mode*\",\n  \"spacelock\", //no read\n  \"vlayer_size*\",\n  \"vlayer_width*\",\n  \"vlayer_height*\",\n};\n\nstatic const enum virtual_var world_ram_var_list[] =\n{\n  VIR_RAM_COUNTER_LIST,\n  VIR_RAM_COUNTER_TABLE,\n  VIR_RAM_COUNTERS,\n  VIR_RAM_STRING_LIST,\n  VIR_RAM_STRING_TABLE,\n  VIR_RAM_STRINGS,\n  VIR_RAM_SFX,\n  VIR_RAM_SPRITES,\n  VIR_RAM_VLAYER,\n  VIR_RAM_BOARD_INFO,\n  VIR_RAM_BOARD_DATA,\n  VIR_RAM_BOARD_OVERLAY,\n  VIR_RAM_ROBOT_INFO,\n  VIR_RAM_ROBOT_STACK,\n#ifdef CONFIG_DEBYTECODE\n  VIR_RAM_ROBOT_SOURCE,\n#endif\n  VIR_RAM_ROBOT_PROGRAMS,\n  VIR_RAM_ROBOT_PROGRAM_LABELS,\n  VIR_RAM_SCROLLS_SENSORS,\n  VIR_RAM_VIDEO_LAYERS,\n  VIR_RAM_DEBUGGER_ROBOTS,\n  VIR_RAM_DEBUGGER_VARIABLES,\n#ifdef CONFIG_EXTRAM\n  VIR_RAM_EXTRAM_DELTA,\n#endif\n  VIR_RAM_VIRTUAL_FILESYSTEM,\n  VIR_RAM_VIRTUAL_FILESYSTEM_CACHED_ONLY,\n};\n\nstatic const char *board_var_list[] =\n{\n  \"board_name*\",\n  \"board_mod*\", //no read\n  \"board_w*\",\n  \"board_h*\",\n  \"input\", // as a counter\n  \"input*\", // as a string (no read function; handled in interpolation code)\n  \"inputsize\",\n  \"key\",\n  \"overlay_mode*\",\n  \"playerfacedir\",\n  \"playerlastdir\",\n  \"playerx*\",\n  \"playery*\",\n  \"scrolledx*\",\n  \"scrolledy*\",\n  \"timereset\",\n  \"volume\", //no read or write\n};\n\n// Locals are added onto the end later.\nstatic const char *robot_var_list[] =\n{\n  \"robot_name*\",\n  \"commands_total\", //no read or write\n  \"commands_cycle*\", //no read\n  \"bullettype\",\n  \"lava_walk\",\n  \"goop_walk\",\n  \"lockself\", //no read or write\n  \"loopcount\",\n  \"thisx*\",\n  \"thisy*\",\n  \"this_char*\",\n  \"this_color*\",\n  \"playerdist*\",\n  \"horizpld*\",\n  \"vertpld*\",\n};\n\n// Sprite parent list (note: clist# added to end)\nstatic const char *sprite_parent_var_list[] =\n{\n  \"Active sprites*\", // no read/write\n  \"spr_num\",\n  \"spr_yorder\",\n  \"spr_collisions*\",\n};\n\n// The following will all be added to the end of 'sprN_'\nstatic const char *sprite_var_list[] =\n{\n  \"x\",\n  \"y\",\n  \"z\",\n  \"refx\",\n  \"refy\",\n  \"width\",\n  \"height\",\n  \"cx\",\n  \"cy\",\n  \"cwidth\",\n  \"cheight\",\n  \"ccheck\", // no read\n  \"off\",\n  \"offonexit\",\n  \"offset\",\n  \"overlay\", // no read\n  \"static\", // no read\n  \"tcol\",\n  \"unbound\",\n  \"vlayer\", // no read\n};\n\nstatic const char *sensor_var_list[] =\n{\n  \"Sensor name*\", // no read/write\n  \"Robot to mesg*\", // no read/write\n};\n\nstatic const char *scroll_text_var = \"Scroll text*\";\n\nstatic const int num_universal_vars = ARRAY_SIZE(universal_var_list);\nstatic const int num_world_vars = ARRAY_SIZE(world_var_list);\nstatic const int num_world_ram_vars = ARRAY_SIZE(world_ram_var_list);\nstatic const int num_board_vars = ARRAY_SIZE(board_var_list);\nstatic const int num_robot_vars = ARRAY_SIZE(robot_var_list);\nstatic const int num_sprite_parent_vars = ARRAY_SIZE(sprite_parent_var_list);\nstatic const int num_sprite_vars = ARRAY_SIZE(sprite_var_list);\nstatic const int num_sensor_vars = ARRAY_SIZE(sensor_var_list);\n\nenum debug_var_type\n{\n  V_COUNTER,\n  V_STRING,\n  V_VAR,\n  V_VIRTUAL_VAR,\n  V_SPRITE_VAR,\n  V_SPRITE_CLIST,\n  V_LOCAL_VAR,\n  V_SCROLL_TEXT,\n};\n\nstruct debug_var\n{\n  // This goes first so this struct can act like a char * for the dialog.\n  char text[VAR_LIST_WIDTH + 1];\n\n  // Internal data\n  unsigned char type;\n  unsigned char id;\n  boolean is_empty;\n  union\n  {\n    struct counter *counter;\n    struct string *string;\n    const char *var_name;\n    enum virtual_var virtual_var;\n    int clist_num;\n    int local_num;\n  }\n  data;\n};\n\nstruct debug_ram_data\n{\n  size_t counter_list_size;\n  size_t counter_table_size;\n  size_t counter_struct_size;\n  size_t string_list_size;\n  size_t string_table_size;\n  size_t string_struct_size;\n  size_t sfx_size;\n  size_t sprites_size;\n  size_t vlayer_size;\n  size_t board_list_and_struct_size;\n  size_t board_data_size;\n  size_t board_overlay_size;\n  size_t robot_list_and_struct_size;\n  size_t robot_stack_size;\n  size_t robot_source_size;\n  size_t robot_program_size;\n  size_t robot_program_labels_size;\n  size_t scroll_sensor_total_size;\n  size_t video_layer_size;\n  size_t debug_robot_total_size;\n  size_t debug_variables_total_size;\n  size_t extram_uncompressed_size;\n  size_t extram_compressed_size;\n  size_t virtual_filesystem_size;\n  size_t virtual_filesystem_cached_size;\n};\n\nstatic struct debug_ram_data ram_data;\n\nstatic void robot_ram_usage(struct robot *robot, struct debug_ram_data *ram_data)\n{\n  if(robot->stack)\n    ram_data->robot_stack_size += robot->stack_size * sizeof(robot->stack[0]);\n\n#ifdef CONFIG_DEBYTECODE\n  if(robot->program_source)\n    ram_data->robot_source_size += robot->program_source_length;\n#else\n  // Count this in debug since it's only used for that...\n  if(robot->program_source)\n    ram_data->debug_robot_total_size += robot->program_source_length;\n#endif /* !CONFIG_DEBYTECODE */\n\n  if(robot->program_bytecode)\n    ram_data->robot_program_size += robot->program_bytecode_length;\n\n  if(robot->command_map)\n  {\n    ram_data->debug_robot_total_size +=\n     robot->command_map_length * sizeof(struct command_mapping);\n  }\n\n  if(robot->label_list)\n  {\n    ram_data->robot_program_labels_size +=\n     robot->num_labels * (sizeof(struct label *) + sizeof(struct label));\n  }\n}\n\nstatic void update_ram_usage_data(struct world *mzx_world,\n size_t var_debug_usage)\n{\n  int i, j;\n  size_t u;\n\n  memset(&ram_data, 0, sizeof(struct debug_ram_data));\n\n  counter_list_size(&mzx_world->counter_list, &ram_data.counter_list_size,\n   &ram_data.counter_table_size, &ram_data.counter_struct_size);\n\n  string_list_size(&mzx_world->string_list, &ram_data.string_list_size,\n   &ram_data.string_table_size, &ram_data.string_struct_size);\n\n  ram_data.sfx_size = sfx_ram_usage(&mzx_world->custom_sfx);\n\n  ram_data.sprites_size =\n   mzx_world->num_sprites_allocated * sizeof(struct sprite *) +\n   mzx_world->num_sprites * sizeof(struct sprite);\n\n  ram_data.vlayer_size = mzx_world->vlayer_size * 2;\n\n  ram_data.board_list_and_struct_size =\n   mzx_world->num_boards_allocated * sizeof(struct board *) +\n   mzx_world->num_boards * sizeof(struct board);\n\n  robot_ram_usage(&mzx_world->global_robot, &ram_data);\n\n  for(i = 0; i < mzx_world->num_boards; i++)\n  {\n    struct board *b = mzx_world->board_list[i];\n    // Board data.\n    ram_data.board_data_size += b->board_width * b->board_height * 6;\n    if(b->overlay_mode)\n      ram_data.board_overlay_size += b->board_width * b->board_height * 2;\n\n    // Buffers (split off the board struct).\n    ram_data.board_list_and_struct_size +=\n     b->input_allocated + b->charset_path_allocated + b->palette_path_allocated;\n\n    // Robot list (+1) and robot name sorted list.\n    ram_data.robot_list_and_struct_size +=\n     ((b->num_robots_allocated + 1) + b->num_robots_active) * sizeof(struct robot *);\n\n    // Scroll and sensor lists (both +1).\n    ram_data.scroll_sensor_total_size +=\n     (b->num_scrolls_allocated + 1) * sizeof(struct scroll *) +\n     (b->num_sensors_allocated + 1) * sizeof(struct sensor *);\n\n    for(j = 1; j <= b->num_robots; j++)\n    {\n      if(b->robot_list[j])\n      {\n        ram_data.robot_list_and_struct_size += sizeof(struct robot);\n        robot_ram_usage(b->robot_list[j], &ram_data);\n      }\n    }\n\n    for(j = 1; j <= b->num_scrolls; j++)\n      if(b->scroll_list[j])\n        ram_data.scroll_sensor_total_size +=\n         sizeof(struct scroll) + b->scroll_list[j]->mesg_size;\n\n    for(j = 1; j <= b->num_sensors; j++)\n      if(b->sensor_list[j])\n        ram_data.scroll_sensor_total_size += sizeof(struct sensor);\n\n#ifdef CONFIG_EXTRAM\n    {\n      size_t c;\n      size_t u;\n      if(board_extram_usage(b, &c, &u))\n      {\n        ram_data.extram_compressed_size += c;\n        ram_data.extram_uncompressed_size += u;\n      }\n    }\n#endif\n  }\n\n  for(u = 0; u < graphics.layer_count; u++)\n  {\n    struct video_layer *layer = &(graphics.video_layers[u]);\n    if(layer->data)\n      ram_data.video_layer_size +=\n       layer->w * layer->h * sizeof(struct char_element);\n  }\n\n  ram_data.debug_variables_total_size = var_debug_usage;\n\n  ram_data.virtual_filesystem_size = vio_filesystem_total_memory_usage();\n  ram_data.virtual_filesystem_cached_size = vio_filesystem_total_cached_usage();\n}\n\n#define match_counter(_name) (strlen(_name) == len && !strcasecmp(name, _name))\n\nint get_counter_safe(struct world *mzx_world, const char *name, int id)\n{\n  // This is a safe wrapper for get_counter(). Some counters, when read,\n  // could alter world data in a way we don't want to happen here.\n  // So detect and block them.\n\n  size_t len = strlen(name);\n\n  if(match_counter(\"fread\"))          return -1;\n  if(match_counter(\"fread_counter\"))  return -1;\n\n  return get_counter(mzx_world, name, id);\n}\n\nstatic void get_var_name(struct debug_var *v, const char **name, int *len,\n char buffer[VAR_LIST_WIDTH + 1])\n{\n  switch((enum debug_var_type)v->type)\n  {\n    case V_COUNTER:\n      if(name) *name = v->data.counter->name;\n      if(len)  *len = v->data.counter->name_length;\n      return;\n\n    case V_STRING:\n      if(name) *name = v->data.string->name;\n      if(len)  *len = v->data.string->name_length;\n      return;\n\n    case V_VAR:\n    case V_SCROLL_TEXT:\n      if(name) *name = (char *)v->data.var_name;\n      if(len)  *len = strlen(*name);\n      return;\n\n    case V_VIRTUAL_VAR:\n      if(name) *name = (char *)virtual_var_names[v->data.virtual_var];\n      if(len)  *len = strlen(*name);\n      return;\n\n    case V_SPRITE_VAR:\n      snprintf(buffer, 32, \"spr%d_%s\", v->id, v->data.var_name);\n      if(name) *name = buffer;\n      if(len)  *len = strlen(buffer);\n      return;\n\n    case V_SPRITE_CLIST:\n      snprintf(buffer, 32, \"spr_clist%d*\", v->data.clist_num);\n      if(name) *name = buffer;\n      if(len)  *len = strlen(buffer);\n      return;\n\n    case V_LOCAL_VAR:\n      snprintf(buffer, 32, \"local%d\", v->data.local_num);\n      if(name) *name = buffer;\n      if(len)  *len = strlen(buffer);\n      return;\n  }\n}\n\n#define match_var(_name) (strlen(_name) == len && !memcmp(var, _name, len))\n\n// The buffer param is used for any vars that need to generate char values.\nstatic void get_var_value(struct world *mzx_world, struct debug_var *v,\n const char **char_value, int *int_value, int64_t *long_value,\n char buffer[VAR_LIST_WIDTH + 1])\n{\n  struct board *cur_board = mzx_world->current_board;\n  char real_var[32];\n\n  *char_value = NULL;\n\n  switch((enum debug_var_type)v->type)\n  {\n    case V_COUNTER:\n    {\n      v->is_empty = false;\n      if(v->data.counter->value == 0)\n        v->is_empty = true;\n\n      *int_value = v->data.counter->value;\n      break;\n    }\n\n    case V_STRING:\n    {\n      v->is_empty = false;\n      if(!v->data.string->length)\n        v->is_empty = true;\n\n      *char_value = v->data.string->value;\n      *int_value = v->data.string->length;\n      break;\n    }\n\n    // It's a built-in var.  Since some of these don't have\n    // read functions, we have to map several manually.\n    case V_VAR:\n    {\n      const char *var = v->data.var_name;\n      size_t len = strlen(var);\n      int index = v->id;\n\n      if(match_var(\"bimesg\"))\n      {\n        *int_value = mzx_world->bi_mesg_status;\n      }\n      else\n\n      if(match_var(\"spacelock\"))\n      {\n        *int_value = mzx_world->bi_shoot_status;\n      }\n      else\n\n      if(match_var(\"fread_delimiter\"))\n      {\n        *int_value = mzx_world->fread_delimiter;\n      }\n      else\n\n      if(match_var(\"fwrite_delimiter\"))\n      {\n        *int_value = mzx_world->fwrite_delimiter;\n      }\n      else\n\n      if(match_var(\"Active sprites*\"))\n      {\n        *int_value = mzx_world->active_sprites;\n      }\n      else\n\n      if(match_var(\"spr_yorder\"))\n      {\n        *int_value = mzx_world->sprite_y_order;\n      }\n      else\n\n      if(match_var(\"mod_name*\"))\n      {\n        *char_value = mzx_world->real_mod_playing;\n        *int_value = strlen(*char_value);\n      }\n      else\n\n      if(match_var(\"board_name*\"))\n      {\n        *char_value = cur_board->board_name;\n        *int_value = strlen(*char_value);\n      }\n      else\n\n      if(match_var(\"board_mod*\"))\n      {\n        *char_value = cur_board->mod_playing;\n        *int_value = strlen(*char_value);\n      }\n      else\n\n      if(match_var(\"input*\"))\n      {\n        // the starred version of input is the input string!\n        *char_value = cur_board->input_string ? cur_board->input_string : \"\";\n        *int_value = strlen(*char_value);\n      }\n      else\n\n      if(match_var(\"volume\"))\n      {\n        *int_value = cur_board->volume;\n      }\n      else\n\n      if(match_var(\"robot_name*\"))\n      {\n        *char_value = cur_board->robot_list[index]->robot_name;\n        *int_value = strlen(*char_value);\n      }\n      else\n\n      if(match_var(\"commands_total\"))\n      {\n        *int_value = cur_board->robot_list[index]->commands_total;\n      }\n      else\n\n      if(match_var(\"commands_cycle*\"))\n      {\n        *int_value = cur_board->robot_list[index]->commands_cycle;\n      }\n      else\n\n      if(match_var(\"lockself\"))\n      {\n        *int_value = cur_board->robot_list[index]->is_locked;\n      }\n      else\n\n      if(match_var(\"Sensor name*\"))\n      {\n        *char_value = cur_board->sensor_list[index]->sensor_name;\n        *int_value = strlen(*char_value);\n      }\n      else\n\n      if(match_var(\"Robot to mesg*\"))\n      {\n        *char_value = cur_board->sensor_list[index]->robot_to_mesg;\n        *int_value = strlen(*char_value);\n      }\n      else\n\n      if(match_var(\"joy_simulate_keys\"))\n      {\n        *int_value = mzx_world->joystick_simulate_keys;\n      }\n\n      else\n      {\n        // All other vars try to use a regular counter lookup.\n        memcpy(real_var, var, len + 1);\n\n        if(real_var[len - 1] == '*')\n          real_var[len - 1] = 0;\n\n        *int_value = get_counter_safe(mzx_world, real_var, index);\n      }\n      break;\n    }\n\n    // Subset of builtin vars that represent aggregate data or other info that\n    // doesn't actually exist in the world.\n    case V_VIRTUAL_VAR:\n    {\n      enum virtual_var vvar = v->data.virtual_var;\n      int64_t value = 0;\n\n      switch(vvar)\n      {\n        case VIR_RAM_COUNTER_LIST:\n          value = ram_data.counter_list_size;\n          break;\n        case VIR_RAM_COUNTER_TABLE:\n          value = ram_data.counter_table_size;\n          break;\n        case VIR_RAM_COUNTERS:\n          value = ram_data.counter_struct_size;\n          break;\n        case VIR_RAM_STRING_LIST:\n          value = ram_data.string_list_size;\n          break;\n        case VIR_RAM_STRING_TABLE:\n          value = ram_data.string_table_size;\n          break;\n        case VIR_RAM_STRINGS:\n          value = ram_data.string_struct_size;\n          break;\n        case VIR_RAM_VLAYER:\n          value = ram_data.vlayer_size;\n          break;\n        case VIR_RAM_SFX:\n          value = ram_data.sfx_size;\n          break;\n        case VIR_RAM_SPRITES:\n          value = ram_data.sprites_size;\n          break;\n        case VIR_RAM_BOARD_INFO:\n          value = ram_data.board_list_and_struct_size;\n          break;\n        case VIR_RAM_BOARD_DATA:\n          value = ram_data.board_data_size;\n          break;\n        case VIR_RAM_BOARD_OVERLAY:\n          value = ram_data.board_overlay_size;\n          break;\n        case VIR_RAM_ROBOT_INFO:\n          value = ram_data.robot_list_and_struct_size;\n          break;\n        case VIR_RAM_ROBOT_STACK:\n          value = ram_data.robot_stack_size;\n          break;\n        case VIR_RAM_ROBOT_SOURCE:\n          value = ram_data.robot_source_size;\n          break;\n        case VIR_RAM_ROBOT_PROGRAMS:\n          value = ram_data.robot_program_size;\n          break;\n        case VIR_RAM_ROBOT_PROGRAM_LABELS:\n          value = ram_data.robot_program_labels_size;\n          break;\n        case VIR_RAM_SCROLLS_SENSORS:\n          value = ram_data.scroll_sensor_total_size;\n          break;\n        case VIR_RAM_VIDEO_LAYERS:\n          value = ram_data.video_layer_size;\n          break;\n        case VIR_RAM_DEBUGGER_ROBOTS:\n          value = ram_data.debug_robot_total_size;\n          break;\n        case VIR_RAM_DEBUGGER_VARIABLES:\n          value = ram_data.debug_variables_total_size;\n          break;\n        case VIR_RAM_EXTRAM_DELTA:\n          value = (int64_t)ram_data.extram_compressed_size -\n           (int64_t)ram_data.extram_uncompressed_size;\n          break;\n        case VIR_RAM_VIRTUAL_FILESYSTEM:\n          value = ram_data.virtual_filesystem_size;\n          break;\n        case VIR_RAM_VIRTUAL_FILESYSTEM_CACHED_ONLY:\n          value = ram_data.virtual_filesystem_cached_size;\n          break;\n      }\n      *long_value = value;\n      break;\n    }\n\n    case V_SPRITE_VAR:\n    {\n      const char *var = v->data.var_name;\n      size_t len = strlen(var);\n      int sprite_num = v->id;\n\n      if(match_var(\"overlay\"))\n      {\n        if(mzx_world->sprite_list[sprite_num]->flags & SPRITE_OVER_OVERLAY)\n          *int_value = 1;\n      }\n      else\n\n      if(match_var(\"static\"))\n      {\n        if(mzx_world->sprite_list[sprite_num]->flags & SPRITE_STATIC)\n          *int_value = 1;\n      }\n      else\n\n      if(match_var(\"vlayer\"))\n      {\n        if(mzx_world->sprite_list[sprite_num]->flags & SPRITE_VLAYER)\n          *int_value = 1;\n      }\n      else\n\n      if(match_var(\"ccheck\"))\n      {\n        int flags = mzx_world->sprite_list[sprite_num]->flags;\n        *int_value = 0;\n\n        if(flags & SPRITE_CHAR_CHECK)\n          *int_value |= 1;\n\n        if(flags & SPRITE_CHAR_CHECK2)\n          *int_value |= 2;\n      }\n\n      // Matched sprN but no var matched\n      else\n      {\n        snprintf(real_var, 32, \"spr%d_%s\", sprite_num, var);\n\n        if(real_var[len - 1] == '*')\n          real_var[len - 1] = 0;\n\n        *int_value = get_counter_safe(mzx_world, real_var, 0);\n      }\n      break;\n    }\n\n    case V_SPRITE_CLIST:\n    {\n      // These are generated separately from the vars list.\n      int index = v->data.clist_num;\n      *int_value = mzx_world->collision_list[index];\n      break;\n    }\n\n    case V_LOCAL_VAR:\n    {\n      // These are a special case mostly because their names are generated.\n      // NOTE: Annoyingly, local1 is at index 0, local2 is at index 1, etc...\n      // NOTE: Awful C modulo-- add 31 instead of subtracting 1...\n      int local_num = (v->data.local_num + 31) % 32;\n      int index = v->id;\n\n      *int_value = cur_board->robot_list[index]->local[local_num];\n      break;\n    }\n\n    case V_SCROLL_TEXT:\n    {\n      struct scroll *src_scroll = cur_board->scroll_list[v->id];\n      *char_value = src_scroll->mesg + 1;\n      *int_value = src_scroll->mesg_size;\n      break;\n    }\n  }\n}\n\nstatic void read_var(struct world *mzx_world, struct debug_var *v)\n{\n  char buffer[VAR_LIST_WIDTH + 1];\n  char *char_dest = v->text + SVALUE_COL_OFFSET;\n  const char *char_value = NULL;\n  int64_t long_value = INT64_MIN;\n  int int_value = 0;\n\n  get_var_value(mzx_world, v, &char_value, &int_value, &long_value, buffer);\n\n  if(v->type == V_STRING)\n  {\n    // Add special escaping to string values to match how they're edited.\n    const char *src = v->data.string->value;\n    size_t src_len = v->data.string->length;\n    copy_substring_escaped(char_dest, SVALUE_SIZE + 1, src, src_len);\n  }\n  else\n\n  if(char_value)\n  {\n    // Use minimal escaping to avoid display bugs.\n    copy_name_escaped(char_dest, SVALUE_SIZE + 1, char_value, int_value);\n  }\n  else\n\n  if(long_value != INT64_MIN)\n  {\n    snprintf(v->text + LVALUE_COL_OFFSET, LVALUE_SIZE + 1, \"%\"PRId64, long_value);\n  }\n\n  else\n  {\n    snprintf(v->text + CVALUE_COL_OFFSET, CVALUE_SIZE + 1, \"%i\", int_value);\n  }\n}\n\nstatic void write_var(struct world *mzx_world, struct debug_var *v, int int_val,\n char *char_val)\n{\n  switch((enum debug_var_type)v->type)\n  {\n    case V_COUNTER:\n    {\n      // As tempting as it is to directly write, we need to respect gateways\n      set_counter(mzx_world, v->data.counter->name, int_val, 0);\n      break;\n    }\n\n    case V_STRING:\n    {\n      //set string -- int_val is the length here\n      char buffer[ROBOT_MAX_TR];\n      struct string temp;\n\n      memset(&temp, '\\0', sizeof(struct string));\n      temp.length = int_val;\n      temp.value = char_val;\n\n      memcpy(buffer, v->data.string->name, v->data.string->name_length + 1);\n      set_string(mzx_world, buffer, &temp, 0);\n      break;\n    }\n\n    case V_VAR:\n    {\n      // It's a built-in var\n      struct board *cur_board = mzx_world->current_board;\n      const char *var = v->data.var_name;\n      size_t len = strlen(var);\n      int index = v->id;\n\n      if(var[len - 1] != '*')\n      {\n        // Special cases: not an actual counter, but needs to be writable\n\n        if(match_var(\"commands_total\"))\n        {\n          cur_board->robot_list[index]->commands_total = int_val;\n        }\n        else\n\n        if(match_var(\"volume\"))\n        {\n          int_val = int_val & 255;\n\n          cur_board->volume = int_val;\n          cur_board->volume_target = int_val;\n\n          audio_set_module_volume(int_val);\n        }\n        else\n\n        if(match_var(\"lockself\"))\n        {\n          cur_board->robot_list[index]->is_locked = (int_val != 0);\n        }\n\n        // Everything else\n        else\n        {\n          set_counter(mzx_world, var, int_val, index);\n        }\n      }\n      break;\n    }\n\n    case V_VIRTUAL_VAR:\n    {\n      // Virtual vars (read-only)\n      return;\n    };\n\n    case V_SPRITE_VAR:\n    {\n      // Sprite variable\n      char real_var[32];\n      snprintf(real_var, 32, \"spr%d_%s\", v->id, v->data.var_name);\n\n      set_counter(mzx_world, real_var, int_val, 0);\n      break;\n    }\n\n    case V_SPRITE_CLIST:\n    {\n      // Sprite clist (read-only)\n      return;\n    }\n\n    case V_LOCAL_VAR:\n    {\n      // Robot local variable\n      char real_var[32];\n      snprintf(real_var, 32, \"local%d\", v->data.local_num);\n\n      set_counter(mzx_world, real_var, int_val, v->id);\n      break;\n    }\n\n    case V_SCROLL_TEXT:\n    {\n      // Scroll text (read-only)\n      return;\n    }\n  }\n\n  // Now update debug_var to reflect the new value.\n  read_var(mzx_world, v);\n}\n\nstatic void init_counter_var(struct debug_var *v, struct counter *src)\n{\n  char buf[CVALUE_COL_OFFSET];\n  v->type = V_COUNTER;\n  v->is_empty = (src->value ? false : true);\n  v->data.counter = src;\n\n  copy_name_escaped(buf, CVALUE_COL_OFFSET, src->name, src->name_length);\n  snprintf(v->text, VAR_LIST_WIDTH, \"%-*.*s %\"PRId32,\n   CVALUE_COL_OFFSET - 1, CVALUE_COL_OFFSET - 1, buf, src->value);\n}\n\nstatic void init_string_var(struct debug_var *v, struct string *src)\n{\n  char buf[SVALUE_COL_OFFSET];\n  v->type = V_STRING;\n  v->is_empty = (src->length ? false : true);\n  v->data.string = src;\n\n  copy_name_escaped(buf, SVALUE_COL_OFFSET, src->name, src->name_length);\n  memset(v->text, 32, VAR_LIST_WIDTH);\n  memcpy(v->text, buf, strlen(buf));\n  copy_substring_escaped(v->text + SVALUE_COL_OFFSET, SVALUE_SIZE + 1,\n   src->value, src->length);\n  v->text[VAR_LIST_WIDTH] = 0;\n}\n\nstatic void init_builtin_var(struct debug_var *v, const char *name, int robot)\n{\n  v->type = V_VAR;\n  v->is_empty = false;\n  v->id = (char)robot;\n  v->data.var_name = name;\n  memset(v->text, 32, VAR_LIST_WIDTH);\n  memcpy(v->text, name, strlen(name));\n  v->text[VAR_LIST_WIDTH] = 0;\n}\n\nstatic void init_virtual_var(struct debug_var *v, enum virtual_var virtual_var)\n{\n  const char *name = virtual_var_names[virtual_var];\n  v->type = V_VIRTUAL_VAR;\n  v->is_empty = false;\n  v->data.virtual_var = virtual_var;\n  memset(v->text, 32, VAR_LIST_WIDTH);\n  memcpy(v->text, name, strlen(name));\n  v->text[VAR_LIST_WIDTH] = 0;\n}\n\nstatic void init_sprite_var(struct debug_var *v, const char *name, int spr)\n{\n  char buf[32];\n  v->type = V_SPRITE_VAR;\n  v->is_empty = false;\n  v->id = (char)spr;\n  v->data.var_name = name;\n  sprintf(buf, \"spr%d_%s\", spr, name);\n  memset(v->text, 32, VAR_LIST_WIDTH);\n  memcpy(v->text, buf, strlen(buf));\n  v->text[VAR_LIST_WIDTH] = 0;\n}\n\nstatic void init_sprite_clist_var(struct debug_var *v, int pos)\n{\n  char buf[32];\n  v->type = V_SPRITE_CLIST;\n  v->is_empty = false;\n  v->data.clist_num = pos;\n  sprintf(buf, \"spr_clist%d*\", pos);\n  memset(v->text, 32, VAR_LIST_WIDTH);\n  memcpy(v->text, buf, strlen(buf));\n  v->text[VAR_LIST_WIDTH] = 0;\n}\n\nstatic void init_local_var(struct debug_var *v, int robot, int num)\n{\n  char buf[32];\n  v->type = V_LOCAL_VAR;\n  v->is_empty = false;\n  v->id = (char)robot;\n  v->data.local_num = num;\n  sprintf(buf, \"local%d\", num);\n  memset(v->text, 32, VAR_LIST_WIDTH);\n  memcpy(v->text, buf, strlen(buf));\n  v->text[VAR_LIST_WIDTH] = 0;\n}\n\nstatic void init_scroll_text(struct debug_var *v, int scroll)\n{\n  v->type = V_SCROLL_TEXT;\n  v->is_empty = false;\n  v->id = (char)scroll;\n  v->data.var_name = scroll_text_var;\n  memset(v->text, 32, VAR_LIST_WIDTH);\n  memcpy(v->text, scroll_text_var, strlen(scroll_text_var));\n  v->text[VAR_LIST_WIDTH] = 0;\n}\n\n/************************\n * Debug tree functions *\n ************************/\n\n/* (root)-----16-S|---- for 58 now instead of 75.\n * - Counters\n *     a\n *     b\n *     c\n * - Strings\n *     $t\n * - Sprites\n *     spr0\n *     spr1\n *   World\n *   Board\n * + Robots\n *     0 Global\n *     1 (12,184)\n *     2 (139,18)\n */\n\n#define DEBUG_NODE_NAME_SIZE 32\n\nstruct debug_node\n{\n   char name[DEBUG_NODE_NAME_SIZE];\n   boolean opened;\n   boolean refresh_on_focus;\n   boolean show_child_contents;\n   boolean delete_child_nodes;\n   int num_nodes;\n   int num_vars;\n   struct debug_node *parent;\n   struct debug_node *nodes;\n   struct debug_var *vars;\n};\n\n// Build the tree display on the left side of the screen\nstatic void build_tree_list(struct debug_node *node,\n char ***tree_list, int *tree_size, int level)\n{\n  int i;\n  int level_offset = 1;\n  char *name;\n\n  // Skip empty nodes entirely unless they're root-level.\n  if(level > 1 && node->num_nodes == 0 && node->num_vars == 0)\n    return;\n\n  if(level > 0)\n  {\n    // Display name and a real name so the menu can find it later.\n    int buffer_len = TREE_LIST_WIDTH + strlen(node->name) + 1;\n    int pad_len = level * level_offset;\n    name = cmalloc(buffer_len);\n\n    snprintf(name, buffer_len, \"%*.*s%-*.*s %s\",\n      pad_len, pad_len, \"\",\n      TREE_LIST_WIDTH - pad_len - 1, TREE_LIST_WIDTH - pad_len - 1, node->name,\n      node->name\n    );\n    name[TREE_LIST_WIDTH - 1] = '\\0';\n    name[buffer_len - 1] = '\\0';\n\n    if(node->num_nodes)\n    {\n      if(node->opened)\n        name[pad_len - 1] = '-';\n      else\n        name[pad_len - 1] = '+';\n    }\n\n    (*tree_list) = crealloc(*tree_list, sizeof(char *) * (*tree_size + 1));\n\n    (*tree_list)[*tree_size] = name;\n    (*tree_size)++;\n  }\n\n  if(node->num_nodes && node->opened)\n    for(i = 0; i < node->num_nodes; i++)\n      build_tree_list(&(node->nodes[i]), tree_list, tree_size, level+1);\n}\n\n// Free the tree list and all of its lines.\nstatic void free_tree_list(char **tree_list, int tree_size)\n{\n  int i;\n\n  for(i = 0; i < tree_size; i++)\n    free(tree_list[i]);\n\n  free(tree_list);\n}\n\n// Free and build in one package\nstatic void rebuild_tree_list(struct debug_node *node,\n char ***tree_list, int *tree_size)\n{\n  if(*tree_list)\n  {\n    free_tree_list(*tree_list, *tree_size);\n    *tree_list = NULL;\n    *tree_size = 0;\n  }\n  build_tree_list(node, tree_list, tree_size, 0);\n}\n\n/**\n * From a node, populate the list of all counters that should appear.\n */\n\nstatic void init_var_list(struct debug_node *node,\n struct debug_var **var_list, int num_vars, int *_index, boolean hide_empty)\n{\n  struct debug_var *current = node->vars;\n  int index = *_index;\n  int i;\n\n  if(node->num_vars)\n  {\n    for(i = 0; i < node->num_vars; i++)\n    {\n      if(!hide_empty || !current->is_empty)\n        var_list[index++] = &(node->vars[i]);\n\n      current++;\n    }\n  }\n\n  *_index = index;\n\n  if(node->num_nodes && node->show_child_contents)\n  {\n    for(i = 0; i < node->num_nodes; i++)\n      init_var_list(&(node->nodes[i]), var_list, num_vars, _index, hide_empty);\n  }\n}\n\n/**\n * Calculate the number of vars contained inside of this node and its\n * children.\n */\n\nstatic void get_var_count(struct debug_node *node, int *num, boolean hide_empty)\n{\n  int my_num = node->num_vars;\n  int i;\n\n  if(hide_empty)\n    for(i = 0; i < node->num_vars; i++)\n      if(node->vars[i].is_empty)\n        my_num--;\n\n  *num += my_num;\n\n  if(node->num_nodes && node->show_child_contents)\n  {\n    int i;\n    for(i = 0; i < node->num_nodes; i++)\n      get_var_count(&(node->nodes[i]), num, hide_empty);\n  }\n}\n\n/**\n * Reallocate an existing var list or create a new var list.\n */\n\nstatic void rebuild_var_list(struct debug_node *node,\n struct debug_var ***var_list, int *num_vars, boolean hide_empty)\n{\n  int index = 0;\n\n  free(*var_list);\n  *num_vars = 0;\n  get_var_count(node, num_vars, hide_empty);\n  *var_list = cmalloc(*num_vars * sizeof(struct debug_var *));\n\n  init_var_list(node, *var_list, *num_vars, &index, hide_empty);\n}\n\n/**\n * Delete all vars in the tree. If delete_all is true, all child nodes will\n * also be deleted; otherwise, only delete children of nodes explicitly marked.\n */\nstatic void clear_debug_tree(struct debug_node *node, boolean delete_all)\n{\n  int i;\n  if(node->num_vars)\n  {\n    free(node->vars);\n    node->vars = NULL;\n    node->num_vars = 0;\n  }\n\n  if(node->num_nodes)\n  {\n    // If this node deletes its children, its children also need to delete\n    // their children recursively.\n    delete_all |= node->delete_child_nodes;\n\n    for(i = 0; i < node->num_nodes; i++)\n      clear_debug_tree(&(node->nodes[i]), delete_all);\n\n    if(delete_all)\n    {\n      free(node->nodes);\n      node->nodes = NULL;\n      node->num_nodes = 0;\n    }\n  }\n}\n\n/**\n * Get the total size of a debug tree's node and var structs.\n */\nstatic size_t get_debug_tree_ram_size(struct debug_node *node)\n{\n  size_t sz = node->num_vars * sizeof(struct debug_var) +\n   node->num_nodes * sizeof(struct debug_node);\n  int i;\n\n  for(i = 0; i < node->num_nodes; i++)\n    sz += get_debug_tree_ram_size(&node->nodes[i]);\n\n  return sz;\n}\n\n/****************************/\n/* Tree Searching functions */\n/****************************/\n\n// Use this when somebody selects something from the tree list\nstatic struct debug_node *find_node(struct debug_node *node, char *name)\n{\n  if(!strcmp(node->name, name))\n    return node;\n\n  if(node->nodes)\n  {\n    int i;\n    struct debug_node *result;\n    for(i = 0; i < node->num_nodes; i++)\n    {\n      result = find_node(&(node->nodes[i]), name);\n      if(result)\n        return result;\n    }\n  }\n\n  return NULL;\n}\n\nstatic void get_node_name(struct debug_node *node, char *label, int max_length)\n{\n  if(node->parent && node->parent->parent)\n  {\n    get_node_name(node->parent, label, max_length);\n    snprintf(label + strlen(label), max_length - strlen(label), \" :: \");\n  }\n\n  snprintf(label + strlen(label), max_length - strlen(label), \"%s\", node->name);\n}\n\nstatic boolean match_debug_var_name(struct debug_var *v, char *name,\n size_t name_length)\n{\n  switch((enum debug_var_type)v->type)\n  {\n    case V_COUNTER:\n    {\n      if((size_t)v->data.counter->name_length == name_length &&\n       !memcmp(v->data.counter->name, name, name_length))\n        return true;\n      return false;\n    }\n\n    case V_STRING:\n    {\n      if((size_t)v->data.string->name_length == name_length &&\n       !memcmp(v->data.string->name, name, name_length))\n        return true;\n      return false;\n    }\n\n    default:\n    {\n      // Just compare against the display area.\n      if(!strcmp(v->text, name))\n        return true;\n      return false;\n    }\n  }\n}\n\nstatic boolean select_debug_var(struct debug_node *node, char *var_name,\n size_t var_len, struct debug_node **new_focus, int *new_var_pos)\n{\n  struct debug_var *current = node->vars;\n  int i;\n\n  if(node->num_vars)\n  {\n    for(i = 0; i < node->num_vars; i++)\n    {\n      if(match_debug_var_name(current, var_name, var_len))\n      {\n        *new_focus = node;\n        *new_var_pos = i;\n        return true;\n      }\n      current++;\n    }\n  }\n\n  if(node->num_nodes)\n    for(i = 0; i < node->num_nodes; i++)\n      if(select_debug_var(&(node->nodes[i]), var_name, var_len, new_focus,\n       new_var_pos))\n        return true;\n\n  return false;\n}\n\n/******************/\n/* Counter search */\n/******************/\n\nstatic boolean search_match(const char *var_text, const size_t var_text_length,\n const char *match_text, const struct string_search_data *match_text_index,\n const size_t match_length, int search_flags)\n{\n  boolean ignore_case = (search_flags & VAR_SEARCH_CASESENS) == 0;\n\n  if(search_flags & VAR_SEARCH_EXACT)\n  {\n    if(var_text_length == match_length)\n    {\n      if(ignore_case)\n      {\n        if(!memcasecmp(var_text, match_text, match_length))\n          return true;\n      }\n      else\n\n      if(!memcmp(var_text, match_text, match_length))\n        return true;\n    }\n  }\n  else\n  {\n    if(string_search(var_text, var_text_length, match_text, match_length,\n     match_text_index, ignore_case))\n      return true;\n  }\n\n  return false;\n}\n\nstatic boolean search_vars(struct world *mzx_world, struct debug_node *node,\n struct debug_var **res_var, struct debug_node **res_node, int *res_pos,\n const char *match_text, const struct string_search_data *match_text_index,\n const size_t match_length, int search_flags, struct debug_var **stop_var)\n{\n  boolean matched = false;\n  struct debug_var *current;\n  int stop = node->num_vars;\n  int start = 0;\n  int inc = 1;\n  int i;\n\n  char var_text_buffer[VAR_LIST_WIDTH + 1];\n  int var_text_length;\n  const char *var_text;\n\n  if(search_flags & VAR_SEARCH_REVERSE)\n  {\n    start = node->num_vars - 1;\n    stop = -1;\n    inc = -1;\n  }\n\n  // If there's an initial var, see if it's in this node.\n  if(*res_var)\n  {\n    if((*res_var >= node->vars) && (*res_var < node->vars + node->num_vars))\n    {\n      // It is, so skip directly to it.\n      start = *res_var - node->vars;\n    }\n    else\n    {\n      // It isn't, so we can just skip this node altogether.\n      return false;\n    }\n  }\n\n  current = node->vars + start;\n\n  for(i = start; i != stop; i += inc, current += inc)\n  {\n    // Skip entries until we find the initial var that was provided.\n    if(*res_var)\n    {\n      // This should be the very first var we try.\n      if(*res_var == current)\n        *res_var = NULL;\n\n      continue;\n    }\n\n    if(search_flags & VAR_SEARCH_NAMES)\n    {\n      get_var_name(current, &var_text, &var_text_length, var_text_buffer);\n\n      matched = search_match(var_text, var_text_length, match_text,\n       match_text_index, match_length, search_flags);\n    }\n\n    if((search_flags & VAR_SEARCH_VALUES) && !matched)\n    {\n      int64_t long_value = INT64_MIN;\n      get_var_value(mzx_world, current, &var_text, &var_text_length, &long_value,\n       var_text_buffer);\n      if(!var_text)\n      {\n        // Use the number already printed onto the var text area.\n        if(long_value != INT64_MIN)\n          var_text = current->text + LVALUE_COL_OFFSET;\n        else\n          var_text = current->text + CVALUE_COL_OFFSET;\n\n        var_text_length = strlen(var_text);\n      }\n\n      matched = search_match(var_text, var_text_length, match_text,\n       match_text_index, match_length, search_flags);\n    }\n\n    if(matched)\n    {\n      *res_var = current;\n      *res_node = node;\n      *res_pos = i;\n      return true;\n    }\n\n    if(current == *stop_var)\n    {\n      *res_var = NULL;\n      *res_node = NULL;\n      *res_pos = -1;\n      return true;\n    }\n\n    if(!*stop_var)\n      *stop_var = current;\n  }\n  return false;\n}\n\nstatic boolean search_node(struct world *mzx_world, struct debug_node *node,\n struct debug_var **res_var, struct debug_node **res_node, int *res_pos,\n const char *match_text, const struct string_search_data *match_text_index,\n const size_t match_length, int search_flags, struct debug_var **stop_var)\n{\n  boolean r = false;\n  int i;\n\n  if(search_flags & VAR_SEARCH_REVERSE)\n  {\n    for(i = node->num_nodes - 1; i >= 0; i--)\n    {\n      r = search_node(mzx_world, &(node->nodes[i]), res_var, res_node, res_pos,\n        match_text, match_text_index, match_length, search_flags, stop_var);\n      if(r)\n        return r;\n    }\n  }\n\n  if(*res_node)\n  {\n    // We may have started in an empty child node of the provided parent.\n    // In this case, we want to find the starting position before we begin\n    // actually searching.\n    if(node == *res_node)\n    {\n      *res_var = NULL;\n      *res_node = NULL;\n    }\n  }\n\n  if(node->num_vars && !*res_node)\n  {\n    r = search_vars(mzx_world, node, res_var, res_node, res_pos,\n     match_text, match_text_index, match_length, search_flags, stop_var);\n    if(r)\n      return r;\n  }\n\n  if(!(search_flags & VAR_SEARCH_REVERSE))\n  {\n    for(i = 0; i < node->num_nodes; i++)\n    {\n      r = search_node(mzx_world, &(node->nodes[i]), res_var, res_node, res_pos,\n        match_text, match_text_index, match_length, search_flags, stop_var);\n      if(r)\n        return r;\n    }\n  }\n  return false;\n}\n\nstatic struct debug_var *find_first_var(struct debug_node *node)\n{\n  struct debug_var *res = NULL;\n\n  if(node->num_vars)\n    return node->vars;\n\n  if(node->num_nodes)\n  {\n    int i;\n    for(i = 0; i < node->num_nodes && !res; i++)\n      res = find_first_var(&(node->nodes[i]));\n  }\n  return res;\n}\n\nstatic struct debug_var *find_last_var(struct debug_node *node)\n{\n  struct debug_var *res = NULL;\n\n  if(node->num_nodes)\n  {\n    int i;\n    for(i = node->num_nodes - 1; i >= 0 && !res; i--)\n      res = find_last_var(&(node->nodes[i]));\n  }\n\n  if(node->num_vars && !res)\n    return &(node->vars[node->num_vars - 1]);\n\n  return res;\n}\n\n/**\n * If a var is currently selected, *res_var should be the pointer to the\n * current selected var. Otherwise, *res_node should be the pointer to the\n * current selected node. On a match, *res_var, *res_node, and *res_pos will\n * be pointers to the new selected var, node, and position in the node's var\n * list, respectively.\n */\n\nstatic boolean search_debug(struct world *mzx_world, struct debug_node *node,\n struct debug_var **res_var, struct debug_node **res_node, int *res_pos,\n char *match_text, size_t match_length, int search_flags)\n{\n  struct debug_var *stop_var = NULL;\n  boolean matched = false;\n\n  struct string_search_data match_text_index;\n  boolean ignore_case = (search_flags & VAR_SEARCH_CASESENS) == 0;\n\n  // Generate an index to help speed up substring searches.\n  if(!(search_flags & VAR_SEARCH_EXACT))\n    string_search_index(match_text, match_length, &match_text_index, ignore_case);\n\n  if(search_flags & VAR_SEARCH_WRAP)\n  {\n    // If we're wrapping, stop at the current selected var. May be NULL, in\n    // which case the search will pick a stopping variable automatically.\n    stop_var = *res_var;\n  }\n  else\n\n  if(search_flags & VAR_SEARCH_REVERSE)\n  {\n    stop_var = find_first_var(node);\n  }\n  else\n  {\n    stop_var = find_last_var(node);\n  }\n\n  matched = search_node(mzx_world, node, res_var, res_node, res_pos,\n   match_text, &match_text_index, match_length, search_flags, &stop_var);\n\n  // Wrap? Try again from the start\n  if(!matched && (search_flags & VAR_SEARCH_WRAP))\n    matched = search_node(mzx_world, node, res_var, res_node, res_pos,\n     match_text, &match_text_index, match_length, search_flags, &stop_var);\n\n  // If we stopped on a non-match, return false.\n  if(*res_var == NULL)\n    return false;\n\n  return matched;\n}\n\n/**********************************/\n/******* MAKE THE TREE CODE *******/\n/**********************************/\n\n#define NUM_ROLODEX 27\n\nstatic const char rolodex_letters[NUM_ROLODEX + 1] =\n \"#ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\nstatic int get_rolodex_position(struct world *mzx_world, int first)\n{\n  first = tolower(first);\n\n  if(first < 'a')\n    return 0;\n\n  if(first > 'z')\n    return 0;\n\n  return (first - 'a') + 1;\n}\n\nstatic struct debug_node *create_rolodex_nodes(struct debug_node *parent,\n size_t counts[NUM_ROLODEX], const char *name_prefix)\n{\n  struct debug_node *nodes = ccalloc(NUM_ROLODEX, sizeof(struct debug_node));\n  int i;\n\n  parent->num_nodes = NUM_ROLODEX;\n  parent->nodes = nodes;\n\n  for(i = 0; i < NUM_ROLODEX; i++)\n  {\n    if(counts[i])\n    {\n      snprintf(nodes[i].name, DEBUG_NODE_NAME_SIZE, \"%s%c\", name_prefix,\n       rolodex_letters[i]);\n      nodes[i].name[DEBUG_NODE_NAME_SIZE - 1] = '\\0';\n      nodes[i].parent = parent;\n      nodes[i].num_nodes = 0;\n      nodes[i].num_vars = 0;\n      nodes[i].nodes = NULL;\n      nodes[i].vars = cmalloc(counts[i] * sizeof(struct debug_var));\n    }\n  }\n  return nodes;\n}\n\nstatic void init_counters_node(struct world *mzx_world, struct debug_node *dest)\n{\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  struct debug_node *nodes;\n  struct debug_node *current_node = NULL;\n  struct debug_var *current_var;\n  size_t var_counts[NUM_ROLODEX] = { 0 };\n  size_t num_counters = counter_list->num_counters;\n  int first_prev = -1;\n  int first;\n  int n = 0;\n  size_t i;\n\n  // The counter list may not be in the order we want to display it in.\n  sort_counter_list(counter_list);\n\n  // Get the number of vars for each node so they can be allocated in one pass.\n  for(i = 0; i < num_counters; i++)\n  {\n    first = counter_list->counters[i]->name[0];\n    if(first != first_prev)\n    {\n      n = get_rolodex_position(mzx_world, first);\n      first_prev = first;\n    }\n    var_counts[n]++;\n  }\n\n  // Allocate and initialize subtree nodes.\n  nodes = create_rolodex_nodes(dest, var_counts, \"\");\n\n  // Insert vars into the newly allocated nodes.\n  first_prev = -1;\n  for(i = 0; i < num_counters; i++)\n  {\n    first = counter_list->counters[i]->name[0];\n    if(first != first_prev)\n    {\n      n = get_rolodex_position(mzx_world, first);\n      first_prev = first;\n      current_node = &(nodes[n]);\n    }\n\n    current_var = &(current_node->vars[current_node->num_vars++]);\n\n    init_counter_var(current_var, counter_list->counters[i]);\n  }\n}\n\nstatic void init_strings_node(struct world *mzx_world, struct debug_node *dest)\n{\n  struct string_list *string_list = &(mzx_world->string_list);\n  struct debug_node *nodes;\n  struct debug_node *current_node = NULL;\n  struct debug_var *current_var;\n  size_t var_counts[NUM_ROLODEX] = { 0 };\n  size_t num_strings = string_list->num_strings;\n  int first_prev = -1;\n  int first;\n  int n = 0;\n  size_t i;\n\n  // Don't create any child nodes if there are no strings.\n  if(!num_strings)\n    return;\n\n  // The string list may not be in the order we want to display it in.\n  sort_string_list(string_list);\n\n  // Get the number of vars for each node so they can be allocated in one pass.\n  for(i = 0; i < num_strings; i++)\n  {\n    first = string_list->strings[i]->name[1];\n    if(first != first_prev)\n    {\n      n = get_rolodex_position(mzx_world, first);\n      first_prev = first;\n    }\n    var_counts[n]++;\n  }\n\n  // Allocate and initialize subtree nodes.\n  nodes = create_rolodex_nodes(dest, var_counts, \"$\");\n\n  // Insert vars into the newly allocated nodes.\n  first_prev = -1;\n  for(i = 0; i < num_strings; i++)\n  {\n    first = string_list->strings[i]->name[1];\n    if(first != first_prev)\n    {\n      n = get_rolodex_position(mzx_world, first);\n      first_prev = first;\n      current_node = &(nodes[n]);\n    }\n    current_var = &(current_node->vars[current_node->num_vars++]);\n\n    init_string_var(current_var, string_list->strings[i]);\n  }\n}\n\nstatic void init_sprite_vars_node(struct world *mzx_world,\n struct debug_node *parent, struct debug_node *dest, int sprite_num)\n{\n  struct debug_var *vars =\n   cmalloc(num_sprite_vars * sizeof(struct debug_var));\n  struct debug_var *current_var;\n  int i;\n\n  for(i = 0; i < num_sprite_vars; i++)\n  {\n    current_var = &(vars[i]);\n    init_sprite_var(current_var, sprite_var_list[i], sprite_num);\n    read_var(mzx_world, current_var);\n  }\n\n  snprintf(dest->name, DEBUG_NODE_NAME_SIZE, \"spr%d\", sprite_num);\n  dest->name[DEBUG_NODE_NAME_SIZE - 1] = '\\0';\n  dest->parent = parent;\n  dest->num_nodes = 0;\n  dest->nodes = NULL;\n  dest->num_vars = num_sprite_vars;\n  dest->vars = vars;\n}\n\nstatic void init_sprites_node(struct world *mzx_world, struct debug_node *dest)\n{\n  struct sprite **sprite_list = mzx_world->sprite_list;\n  int num_sprites_active = 0;\n\n  struct debug_node *nodes;\n  struct debug_var *parent_vars;\n  struct debug_var *current_var;\n  int num_parent_vars = mzx_world->collision_count + num_sprite_parent_vars;\n  int spr;\n  int i;\n\n  parent_vars = cmalloc(num_parent_vars * sizeof(struct debug_var));\n\n  dest->num_vars = num_parent_vars;\n  dest->vars = parent_vars;\n\n  // Default parent vars\n  for(i = 0; i < num_sprite_parent_vars; i++)\n  {\n    current_var = &(parent_vars[i]);\n    init_builtin_var(current_var, sprite_parent_var_list[i], 0);\n    read_var(mzx_world, current_var);\n  }\n\n  // Add the clist vars\n  for(i = 0; i < mzx_world->collision_count; i++)\n  {\n    current_var = &(parent_vars[num_sprite_parent_vars + i]);\n    init_sprite_clist_var(current_var, i);\n    read_var(mzx_world, current_var);\n  }\n\n  // Get number of active sprites\n  for(i = 0; i < MAX_SPRITES; i++)\n    if(sprite_list[i]->width != 0 || sprite_list[i]->height != 0)\n      num_sprites_active++;\n\n  dest->num_nodes = num_sprites_active;\n  if(num_sprites_active)\n  {\n    nodes = ccalloc(num_sprites_active, sizeof(struct debug_node));\n    dest->nodes = nodes;\n\n    // Populate the sprite nodes.\n    for(spr = 0, i = 0; i < MAX_SPRITES; i++)\n    {\n      if(sprite_list[i]->width == 0 && sprite_list[i]->height == 0)\n        continue;\n\n      init_sprite_vars_node(mzx_world, dest, &(nodes[spr]), i);\n      spr++;\n    }\n  }\n  else\n    dest->nodes = NULL;\n}\n\nstatic void init_builtin_node(struct world *mzx_world,\n struct debug_node *dest, const char **var_list, int num_vars, int robot_id)\n{\n  struct debug_var *vars = cmalloc(num_vars * sizeof(struct debug_var));\n  struct debug_var *current_var;\n  int i;\n\n  for(i = 0; i < num_vars; i++)\n  {\n    current_var = &(vars[i]);\n    init_builtin_var(current_var, var_list[i], robot_id);\n    read_var(mzx_world, current_var);\n  }\n\n  dest->num_vars = num_vars;\n  dest->vars = vars;\n}\n\nstatic void init_virtual_node(struct world *mzx_world,\n struct debug_node *dest, const enum virtual_var *var_list, int num_vars)\n{\n  struct debug_var *vars = cmalloc(num_vars * sizeof(struct debug_var));\n  struct debug_var *current_var;\n  int i;\n\n  for(i = 0; i < num_vars; i++)\n  {\n    current_var = &(vars[i]);\n    init_virtual_var(current_var, var_list[i]);\n    read_var(mzx_world, current_var);\n  }\n\n  dest->num_vars = num_vars;\n  dest->vars = vars;\n}\n\nstatic void init_universal_node(struct world *mzx_world, struct debug_node *dest)\n{\n  init_builtin_node(mzx_world, dest, universal_var_list,\n   num_universal_vars, 0);\n}\n\nstatic void init_world_node(struct world *mzx_world, struct debug_node *dest)\n{\n  init_builtin_node(mzx_world, dest, world_var_list, num_world_vars, 0);\n}\n\nstatic void init_world_ram_node(struct world *mzx_world, struct debug_node *dest)\n{\n  init_virtual_node(mzx_world, dest, world_ram_var_list, num_world_ram_vars);\n}\n\nstatic void init_board_node(struct world *mzx_world, struct debug_node *dest)\n{\n  init_builtin_node(mzx_world, dest, board_var_list, num_board_vars, 0);\n}\n\nstatic void init_robot_vars_node(struct world *mzx_world,\n struct debug_node *parent, struct debug_node *dest, struct robot *src_robot,\n int robot_id)\n{\n  int num_vars = num_robot_vars + 32;\n  struct debug_var *vars = cmalloc(num_vars * sizeof(struct debug_var));\n  struct debug_var *current_var;\n  char temp[DEBUG_NODE_NAME_SIZE];\n  int i;\n\n  // Init the default vars first\n  for(i = 0; i < num_robot_vars; i++)\n  {\n    current_var = &(vars[i]);\n    init_builtin_var(current_var, robot_var_list[i], robot_id);\n    read_var(mzx_world, current_var);\n  }\n\n  // Add the locals\n  for(i = 0; i < 32; i++)\n  {\n    current_var = &(vars[num_robot_vars + i]);\n    init_local_var(current_var, robot_id, i);\n    read_var(mzx_world, current_var);\n  }\n\n  snprintf(temp, DEBUG_NODE_NAME_SIZE, \"%d:%s\", robot_id, src_robot->robot_name);\n  temp[DEBUG_NODE_NAME_SIZE - 1] = '\\0';\n  copy_name_escaped(dest->name, DEBUG_NODE_NAME_SIZE, temp, strlen(temp));\n  dest->parent = parent;\n  dest->num_nodes = 0;\n  dest->nodes = NULL;\n  dest->num_vars = num_vars;\n  dest->vars = vars;\n}\n\nstatic void init_robot_node(struct world *mzx_world, struct debug_node *dest)\n{\n  struct debug_node *nodes;\n  struct robot **robot_list = mzx_world->current_board->robot_list;\n  int max_robots = mzx_world->current_board->num_robots + 1;\n  int num_robots = 0;\n  int cur;\n  int i;\n\n  // Get the number of robots to display\n  for(i = 0; i < max_robots; i++)\n    if(robot_list[i] && robot_list[i]->used)\n      num_robots++;\n\n  nodes = ccalloc(num_robots, sizeof(struct debug_node));\n  dest->num_nodes = num_robots;\n  dest->nodes = nodes;\n\n  // Populate the robot nodes.\n  for(cur = 0, i = 0; i < max_robots; i++)\n    if(robot_list[i] && robot_list[i]->used)\n      init_robot_vars_node(mzx_world, dest, &(nodes[cur++]), robot_list[i], i);\n}\n\nstatic void init_scroll_var_node(struct world *mzx_world,\n struct debug_node *parent, struct debug_node *dest, int scroll_id)\n{\n  struct debug_var *var_list = ccalloc(1, sizeof(struct debug_var));\n\n  init_scroll_text(&(var_list[0]), scroll_id);\n  read_var(mzx_world, &(var_list[0]));\n\n  snprintf(dest->name, DEBUG_NODE_NAME_SIZE, \"Scroll %d\", scroll_id);\n  dest->name[DEBUG_NODE_NAME_SIZE - 1] = '\\0';\n  dest->parent = parent;\n  dest->num_nodes = 0;\n  dest->nodes = NULL;\n  dest->num_vars = 1;\n  dest->vars = var_list;\n}\n\nstatic void init_scroll_node(struct world *mzx_world, struct debug_node *dest)\n{\n  struct debug_node *nodes;\n  struct scroll **scroll_list = mzx_world->current_board->scroll_list;\n  int max_scrolls = mzx_world->current_board->num_scrolls;\n  int num_scrolls = 0;\n  int cur;\n  int i;\n\n  for(i = 1; i <= max_scrolls; i++)\n    if(scroll_list[i] && scroll_list[i]->used)\n      num_scrolls++;\n\n  // Don't create any child nodes if there are no scrolls...\n  if(!num_scrolls)\n    return;\n\n  nodes = ccalloc(num_scrolls, sizeof(struct debug_node));\n  dest->num_nodes = num_scrolls;\n  dest->nodes = nodes;\n\n  // Populate scroll nodes.\n  for(cur = 0, i = 1; i <= max_scrolls; i++)\n    if(scroll_list[i] && scroll_list[i]->used)\n      init_scroll_var_node(mzx_world, dest, &(nodes[cur++]), i);\n}\n\nstatic void init_sensor_var_node(struct world *mzx_world,\n struct debug_node *parent, struct debug_node *dest, int sensor_id)\n{\n  init_builtin_node(mzx_world, dest, sensor_var_list, num_sensor_vars,\n   sensor_id);\n\n  snprintf(dest->name, DEBUG_NODE_NAME_SIZE, \"Sensor %d\", sensor_id);\n  dest->name[DEBUG_NODE_NAME_SIZE - 1] = '\\0';\n  dest->parent = parent;\n  dest->num_nodes = 0;\n  dest->nodes = NULL;\n}\n\nstatic void init_sensor_node(struct world *mzx_world, struct debug_node *dest)\n{\n  struct debug_node *nodes;\n  struct sensor **sensor_list = mzx_world->current_board->sensor_list;\n  int max_sensors = mzx_world->current_board->num_sensors;\n  int num_sensors = 0;\n  int cur;\n  int i;\n\n  for(i = 1; i <= max_sensors; i++)\n    if(sensor_list[i] && sensor_list[i]->used)\n      num_sensors++;\n\n  // Don't create any child nodes if there are no sensors...\n  if(!num_sensors)\n    return;\n\n  nodes = ccalloc(num_sensors, sizeof(struct debug_node));\n  dest->num_nodes = num_sensors;\n  dest->nodes = nodes;\n\n  // Populate the sensor nodes.\n  for(cur = 0, i = 1; i <= max_sensors; i++)\n    if(sensor_list[i] && sensor_list[i]->used)\n      init_sensor_var_node(mzx_world, dest, &(nodes[cur++]), i);\n}\n\nenum root_node_ids\n{\n  NODE_COUNTERS,\n  NODE_STRINGS,\n  NODE_SPRITES,\n  NODE_UNIVERSAL,\n  NODE_WORLD,\n  NODE_BOARD,\n  NODE_ROBOTS,\n  NUM_ROOT_NODES\n};\n\nenum world_node_ids\n{\n  NODE_RAM,\n  NUM_WORLD_NODES\n};\n\nenum board_node_ids\n{\n  NODE_SCROLLS,\n  NODE_SENSORS,\n  NUM_BOARD_NODES\n};\n\n/**\n * Some variable values may be cached, such as the clock time.\n * This resets those values so they will update when the var list refreshes.\n */\nstatic void clear_cached_data(struct world *mzx_world)\n{\n  mzx_world->command_cache = 0;\n}\n\n// Create new counter lists.\n// (Re)make the child nodes\nstatic void repopulate_tree(struct world *mzx_world, struct debug_node *root)\n{\n  struct debug_node *world = &(root->nodes[NODE_WORLD]);\n  struct debug_node *board = &(root->nodes[NODE_BOARD]);\n  size_t sz;\n\n  // Clear the debug tree recursively (but preserve the base structure).\n  clear_debug_tree(root, false);\n\n  clear_cached_data(mzx_world);\n\n  // Initialize the tree.\n  init_counters_node(mzx_world,   &(root->nodes[NODE_COUNTERS]));\n  init_strings_node(mzx_world,    &(root->nodes[NODE_STRINGS]));\n  init_sprites_node(mzx_world,    &(root->nodes[NODE_SPRITES]));\n  init_universal_node(mzx_world,  &(root->nodes[NODE_UNIVERSAL]));\n  init_world_node(mzx_world,      &(root->nodes[NODE_WORLD]));\n  init_board_node(mzx_world,      &(root->nodes[NODE_BOARD]));\n  init_robot_node(mzx_world,      &(root->nodes[NODE_ROBOTS]));\n\n  if(board->nodes)\n  {\n    init_scroll_node(mzx_world,   &(board->nodes[NODE_SCROLLS]));\n    init_sensor_node(mzx_world,   &(board->nodes[NODE_SENSORS]));\n  }\n\n  if(world->nodes)\n  {\n    /**\n     * The RAM node should be done last so the rest of the tree is finished\n     * for the debug variables RAM calculation.\n     *\n     * TODO this doesn't include the size of the var/node list allocations.\n     */\n    sz = get_debug_tree_ram_size(root);\n    sz += num_world_ram_vars * sizeof(struct debug_var);\n    update_ram_usage_data(mzx_world, sz);\n\n    init_world_ram_node(mzx_world,   &(world->nodes[NODE_RAM]));\n  }\n}\n\n// Create the base tree structure, except for sprites and robots\nstatic void build_debug_tree(struct world *mzx_world, struct debug_node *root)\n{\n  struct debug_node *nodes = ccalloc(NUM_ROOT_NODES, sizeof(struct debug_node));\n\n  struct debug_node root_node =\n  {\n    \"\",\n    true,  //opened\n    false, //refresh_on_focus\n    false, //show_child_contents\n    false, //delete_child_nodes\n    NUM_ROOT_NODES,\n    0,\n    NULL,  //parent\n    nodes,\n    NULL\n  };\n\n  struct debug_node counters =\n  {\n    \"Counters\",\n    false,\n    false,\n    true,\n    true,\n    0,\n    0,\n    root,\n    NULL,\n    NULL\n  };\n\n  struct debug_node strings =\n  {\n    \"Strings\",\n    false,\n    false,\n    true,\n    true,\n    0,\n    0,\n    root,\n    NULL,\n    NULL\n  };\n\n  struct debug_node sprites =\n  {\n    \"Sprites\",\n    false,\n    true,\n    false,\n    true,\n    0,\n    0,\n    root,\n    NULL,\n    NULL\n  };\n\n  struct debug_node universal =\n  {\n    \"Universal\",\n    false,\n    true,\n    false,\n    false,\n    0,\n    0,\n    root,\n    NULL,\n    NULL\n  };\n\n  struct debug_node world =\n  {\n    \"World\",\n    false,\n    true,\n    false,\n    false,\n    0,\n    0,\n    root,\n    NULL,\n    NULL\n  };\n\n  struct debug_node world_ram =\n  {\n    \"RAM\",\n    false,\n    true,\n    false,\n    false,\n    0,\n    0,\n    &(nodes[NODE_WORLD]),\n    NULL,\n    NULL\n  };\n\n  struct debug_node board =\n  {\n    \"Board\",\n    false,\n    true,\n    false,\n    false,\n    0,\n    0,\n    root,\n    NULL,\n    NULL\n  };\n\n  struct debug_node robots =\n  {\n    \"Robots\",\n    false,\n    false,\n    false,\n    true,\n    0,\n    0,\n    root,\n    NULL,\n    NULL\n  };\n\n  struct debug_node scrolls =\n  {\n    \"Scrolls/Signs\",\n    false,\n    false,\n    false,\n    true,\n    0,\n    0,\n    &(nodes[NODE_BOARD]),\n    NULL,\n    NULL\n  };\n\n  struct debug_node sensors =\n  {\n    \"Sensors\",\n    false,\n    false,\n    false,\n    true,\n    0,\n    0,\n    &(nodes[NODE_BOARD]),\n    NULL,\n    NULL\n  };\n\n  struct debug_node *world_n = ccalloc(NUM_WORLD_NODES, sizeof(struct debug_node));\n  world.num_nodes = NUM_WORLD_NODES;\n  world.nodes = world_n;\n  world_n[NODE_RAM] = world_ram;\n\n  if(mzx_world->current_board->num_scrolls ||\n   mzx_world->current_board->num_sensors)\n  {\n    // Only create these if there are actually scrolls/sensors on the board.\n    struct debug_node *n = ccalloc(NUM_BOARD_NODES, sizeof(struct debug_node));\n    board.num_nodes = NUM_BOARD_NODES;\n    board.nodes = n;\n\n    n[NODE_SCROLLS] = scrolls;\n    n[NODE_SENSORS] = sensors;\n  }\n\n  *root = root_node;\n  nodes[NODE_COUNTERS] = counters;\n  nodes[NODE_STRINGS] = strings;\n  nodes[NODE_SPRITES] = sprites;\n  nodes[NODE_UNIVERSAL] = universal;\n  nodes[NODE_WORLD] = world;\n  nodes[NODE_BOARD] = board;\n  nodes[NODE_ROBOTS] = robots;\n\n  repopulate_tree(mzx_world, root);\n}\n\n/*****************************/\n/* Set Counter/String Dialog */\n/*****************************/\n\nstatic void input_counter_value(struct world *mzx_world, struct debug_var *v)\n{\n  char new_value[71];\n  char dialog_name[71];\n\n  new_value[0] = 0;\n  dialog_name[0] = 0;\n\n  switch((enum debug_var_type)v->type)\n  {\n    case V_COUNTER:\n    {\n      const struct counter *src = v->data.counter;\n      const char *mesg = \"Edit: counter \";\n      char *dest = dialog_name + strlen(mesg);\n      size_t dest_len = sizeof(dialog_name) - strlen(mesg);\n\n      strcpy(dialog_name, mesg);\n      copy_name_escaped(dest, dest_len, src->name, src->name_length);\n      sprintf(new_value, \"%\"PRId32, src->value);\n      break;\n    }\n\n    case V_STRING:\n    {\n      const struct string *src = v->data.string;\n      const char *mesg = \"Edit: string \";\n      char *dest = dialog_name + strlen(mesg);\n      size_t dest_len = sizeof(dialog_name) - strlen(mesg);\n\n      strcpy(dialog_name, mesg);\n      copy_name_escaped(dest, dest_len, src->name, src->name_length);\n      copy_substring_escaped(new_value, 71, src->value, src->length);\n      break;\n    }\n\n    case V_VAR:\n    {\n      const char *src_var = v->data.var_name;\n      size_t len = strlen(src_var);\n\n      // Ignore write-only variable names.\n      if(src_var[len - 1] == '*')\n        return;\n\n      snprintf(dialog_name, 70, \"Edit: variable %s\", src_var);\n\n      // Just strcpy it off of the debug_var for simplicity\n      strcpy(new_value, v->text + CVALUE_COL_OFFSET);\n      break;\n    }\n\n    case V_VIRTUAL_VAR:\n    {\n      // Virtual var (read-only)\n      return;\n    }\n\n    case V_SPRITE_VAR:\n    {\n      snprintf(dialog_name, 70, \"Edit: variable spr%d_%s\",\n       v->id, v->data.var_name);\n\n      // Just strcpy it off of the debug_var for simplicity\n      strcpy(new_value, v->text + CVALUE_COL_OFFSET);\n      break;\n    }\n\n    case V_SPRITE_CLIST:\n    {\n      // Sprite clist (read-only)\n      return;\n    }\n\n    case V_LOCAL_VAR:\n    {\n      snprintf(dialog_name, 70, \"Edit: variable local%d\", v->data.local_num);\n\n      // Just strcpy it off of the debug_var for simplicity\n      strcpy(new_value, v->text + CVALUE_COL_OFFSET);\n      break;\n    }\n\n    case V_SCROLL_TEXT:\n    {\n      // Too much of a pain to figure out if it's a sign or scroll so\n      scroll_edit(mzx_world, mzx_world->current_board->scroll_list[v->id], 0);\n      return;\n    }\n  }\n\n  // Prompt user to edit value\n  if(!input_window(mzx_world, dialog_name, new_value, 70))\n  {\n    if(v->type == V_STRING)\n    {\n      int len;\n      unescape_string(new_value, &len);\n      write_var(mzx_world, v, len, new_value);\n    }\n    else\n    {\n      int counter_value = strtol(new_value, NULL, 10);\n      write_var(mzx_world, v, counter_value, NULL);\n    }\n  }\n}\n\n/*****************/\n/* Search Dialog */\n/*****************/\n\nstatic int counter_search_dialog_idle_function(struct world *mzx_world,\n struct dialog *di, int key)\n{\n  if((key == IKEY_RETURN) && (di->current_element == 0))\n  {\n    di->return_value = 0;\n    di->done = 1;\n    return 0;\n  }\n\n  return key;\n}\n\nstatic int counter_search_dialog(struct world *mzx_world, char *string,\n int *search_flags)\n{\n  int result = 0;\n  struct dialog di;\n\n  static const char *title = \"Search variables (repeat: Ctrl+R)\";\n  static const char *name_opt[] = { \"Search names\" };\n  static const char *value_opt[] = { \"Search values\" };\n  static const char *case_opt[] = { \"Case sensitive\" };\n  static const char *exact_opt[] = { \"Exact\" };\n  static const char *reverse_opt[] = { \"Reverse\" };\n  static const char *wrap_opt[] = { \"Wrap\" };\n  static const char *local_opt[] = { \"Current list\" };\n\n  int names =    (*search_flags & VAR_SEARCH_NAMES) > 0;\n  int values =   (*search_flags & VAR_SEARCH_VALUES) > 0;\n  int casesens = (*search_flags & VAR_SEARCH_CASESENS) > 0;\n  int exact =    (*search_flags & VAR_SEARCH_EXACT) > 0;\n  int reverse =  (*search_flags & VAR_SEARCH_REVERSE) > 0;\n  int wrap =     (*search_flags & VAR_SEARCH_WRAP) > 0;\n  int local =    (*search_flags & VAR_SEARCH_LOCAL) > 0;\n\n  struct element *elements[] =\n  {\n    construct_input_box( 2, 1, \"Search: \", VAR_SEARCH_MAX, string),\n    construct_button(61, 1, \"Search\", 0),\n    construct_check_box( 3, 2, name_opt,    1, strlen(*name_opt),    &names),\n    construct_check_box(21, 2, value_opt,   1, strlen(*value_opt),   &values),\n    construct_check_box(40, 2, case_opt,    1, strlen(*case_opt),    &casesens),\n    construct_check_box( 5, 3, exact_opt,   1, strlen(*exact_opt),   &exact),\n    construct_check_box(16, 3, reverse_opt, 1, strlen(*reverse_opt), &reverse),\n    construct_check_box(29, 3, wrap_opt,    1, strlen(*wrap_opt),    &wrap),\n    construct_check_box(39, 3, local_opt,   1, strlen(*local_opt),   &local),\n    construct_button(61, 3, \"Cancel\", -1),\n  };\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  construct_dialog_ext(&di, title, VAR_SEARCH_DIALOG_X, VAR_SEARCH_DIALOG_Y,\n   VAR_SEARCH_DIALOG_W, VAR_SEARCH_DIALOG_H, elements, ARRAY_SIZE(elements),\n   0, 0, 0, counter_search_dialog_idle_function);\n\n  result = run_dialog(mzx_world, &di);\n\n  *search_flags = (names * VAR_SEARCH_NAMES) + (values * VAR_SEARCH_VALUES) +\n   (casesens * VAR_SEARCH_CASESENS) + (reverse * VAR_SEARCH_REVERSE) +\n   (wrap * VAR_SEARCH_WRAP) + (exact * VAR_SEARCH_EXACT) +\n   (local * VAR_SEARCH_LOCAL);\n\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  return result;\n}\n\n/**********************/\n/* New Counter Dialog */\n/**********************/\n\n// Returns pointer to name if successful or NULL if cancelled or already existed\nstatic int new_counter_dialog(struct world *mzx_world, char *name)\n{\n  int result;\n  struct element *elements[3];\n  struct dialog di;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  elements[0] = construct_input_box(2, 2, \"Name:\", VAR_ADD_MAX, name);\n  elements[1] = construct_button(9, 4, \"Confirm\", 0);\n  elements[2] = construct_button(23, 4, \"Cancel\", -1);\n\n  construct_dialog_ext(&di, \"New Counter/String\",\n   VAR_ADD_DIALOG_X, VAR_ADD_DIALOG_Y, VAR_ADD_DIALOG_W, VAR_ADD_DIALOG_H,\n   elements, ARRAY_SIZE(elements), 0, 0, 0, NULL);\n\n  result = run_dialog(mzx_world, &di);\n\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  if(!result && (strlen(name) > 0))\n  {\n    // String\n    if(name[0] == '$')\n    {\n      struct string temp;\n      memset(&temp, '\\0', sizeof(struct string));\n\n      if(strchr(name, '.') || strchr(name, '+') || strchr(name, '#'))\n      {\n        confirm(mzx_world, \"Characters .+# not allowed in string name.\");\n        return 0;\n      }\n\n      if(!get_string(mzx_world, name, &temp, 0))\n      {\n        //Doesn't exist -- set string\n        temp.value = (char *)\"\";\n        temp.length = 0;\n\n        set_string(mzx_world, name, &temp, 0);\n      }\n\n      return 1;\n    }\n    // Counter\n    else\n    {\n      if(!get_counter_safe(mzx_world, name, 0))\n        set_counter(mzx_world, name, 0, 0);\n\n      return 1;\n    }\n  }\n\n  return 0;\n}\n\n/********************/\n/* Counter Debugger */\n/********************/\n\n/*-3 - Repeat search\n *-2 - Update selected tree\n *-1 - Close\n * 0 - Selected var in var list\n * 1 - Selected node in tree list\n * 2 - Search\n * 3 - Add new counter\n * 4 - Hide empties\n * 5 - Export\n */\nstatic int last_node_selected = 0;\nstatic context *ctx_for_pal_char_editors = NULL; // FIXME hack\n\nstatic int counter_debugger_idle_function(struct world *mzx_world,\n struct dialog *di, int key)\n{\n  struct list_box *tree_list = (struct list_box *)di->elements[1];\n\n  if((di->current_element == 1) && (*(tree_list->result) != last_node_selected))\n  {\n    di->return_value = -2;\n    di->done = 1;\n  }\n  // List element, so filter unhandled modifiers.\n  if(get_shift_status(keycode_internal))\n    return key;\n\n  switch(key)\n  {\n    // Char editor\n    case IKEY_c:\n    {\n      if(get_alt_status(keycode_internal) && !get_ctrl_status(keycode_internal))\n      {\n        char_editor(mzx_world);\n        return 0;\n      }\n      break;\n    }\n    // Palette editor\n    case IKEY_e:\n    {\n      if(get_alt_status(keycode_internal) && !get_ctrl_status(keycode_internal))\n      {\n        // FIXME hack\n        if(ctx_for_pal_char_editors)\n        {\n          palette_editor(ctx_for_pal_char_editors);\n          core_run(ctx_for_pal_char_editors->root);\n        }\n        return 0;\n      }\n      break;\n    }\n    // Search dialog\n    case IKEY_f:\n    {\n      if(get_ctrl_status(keycode_internal) && !get_alt_status(keycode_internal))\n      {\n        di->return_value = 2;\n        di->done = 1;\n        return 0;\n      }\n      break;\n    }\n    // Repeat search\n    case IKEY_r:\n    {\n      if(get_ctrl_status(keycode_internal) && !get_alt_status(keycode_internal))\n      {\n        di->return_value = -3;\n        di->done = 1;\n        return 0;\n      }\n      break;\n    }\n    // Hide empties\n    case IKEY_h:\n    {\n      if(get_alt_status(keycode_internal) && !get_ctrl_status(keycode_internal))\n      {\n        di->return_value = 4;\n        di->done = 1;\n        return 0;\n      }\n      break;\n    }\n    // New counter/string\n    case IKEY_n:\n    {\n      if(get_alt_status(keycode_internal) && !get_ctrl_status(keycode_internal))\n      {\n        di->return_value = 3;\n        di->done = 1;\n        return 0;\n      }\n      break;\n    }\n  }\n\n  return key;\n}\n\nstatic struct debug_node root;\nstatic char previous_var[VAR_SEARCH_MAX + 1];\nstatic char previous_node_name[DEBUG_NODE_NAME_SIZE];\n\nvoid __debug_counters(context *ctx)\n{\n  struct world *mzx_world = ctx->world;\n  int i;\n\n  int num_vars = 0;\n  int tree_size = 0;\n  struct debug_var **var_list = NULL;\n  char **tree_list = NULL;\n\n  int window_focus = 0;\n  int var_selected = 0;\n  int node_selected = 0;\n  int node_scroll_offset = 0;\n  int dialog_result;\n  struct element *elements[8];\n  struct dialog di;\n\n  char label[80] = \"Counters\";\n  struct debug_node *focus;\n  boolean hide_empty_vars = false;\n\n  char search_text[VAR_SEARCH_MAX + 1] = { 0 };\n  int search_flags = VAR_SEARCH_NAMES + VAR_SEARCH_VALUES + VAR_SEARCH_WRAP;\n\n  boolean reopened = false;\n  boolean refocus_tree_list = false;\n\n  // FIXME hack\n  ctx_for_pal_char_editors = ctx;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_COUNTER_DEBUG);\n\n  build_debug_tree(mzx_world, &root);\n\n  focus = &(root.nodes[node_selected]);\n\n  // If the debugger was opened before, try to pick up where we left off.\n  if(previous_var[0] && previous_node_name[0])\n  {\n    struct debug_node *previous_node = find_node(&root, previous_node_name);\n\n    if(previous_node)\n    {\n      focus = previous_node;\n      snprintf(search_text, VAR_SEARCH_MAX, \"%s\", previous_var);\n      search_flags =\n       VAR_SEARCH_NAMES | VAR_SEARCH_LOCAL | VAR_SEARCH_WRAP | VAR_SEARCH_EXACT;\n\n      reopened = true;\n      refocus_tree_list = true;\n    }\n  }\n\n  rebuild_tree_list(&root, &tree_list, &tree_size);\n  rebuild_var_list(focus, &var_list, &num_vars, hide_empty_vars);\n\n  do\n  {\n    last_node_selected = node_selected;\n\n    if(!reopened)\n    {\n      int bx = BUTTONS_X;\n      int by = BUTTONS_Y;\n\n      elements[0] = construct_list_box(\n         VAR_LIST_X, VAR_LIST_Y, (const char **)var_list, num_vars,\n         VAR_LIST_HEIGHT, VAR_LIST_WIDTH, 0, &var_selected, NULL, false);\n      elements[1] = construct_list_box(\n         TREE_LIST_X, TREE_LIST_Y, (const char **)tree_list, tree_size,\n         TREE_LIST_HEIGHT, TREE_LIST_WIDTH, 1, &node_selected,\n         &node_scroll_offset, false);\n      elements[2] = construct_button(bx + 0, by + 0, \"Search\", 2);\n      elements[3] = construct_button(bx +11, by + 0, \"New\", 3);\n      elements[4] = construct_button(bx + 0, by + 2, \"Toggle Empties\", 4);\n      elements[5] = construct_button(bx + 0, by + 4, \"Export\", 5);\n      elements[6] = construct_button(bx +10, by + 4, \"Done\", -1);\n      elements[7] = construct_label(VAR_LIST_X, VAR_LIST_Y - 1, label);\n\n      construct_dialog_ext(&di, \"Debug Variables\", 0, 0,\n       80, 25, elements, ARRAY_SIZE(elements), 0, 0, window_focus,\n       counter_debugger_idle_function);\n\n      dialog_result = run_dialog(mzx_world, &di);\n    }\n\n    else\n    {\n      // Reopened? Force a search\n      dialog_result = -3;\n    }\n\n    if(node_selected != last_node_selected)\n      focus = find_node(&root, tree_list[node_selected] + TREE_LIST_WIDTH);\n\n    switch(dialog_result)\n    {\n      // Var list\n      case 0:\n      {\n        window_focus = 0;\n        if(var_selected < num_vars)\n          input_counter_value(mzx_world, var_list[var_selected]);\n\n        break;\n      }\n\n      // Switch to a new view (called by idle function)\n      case -2:\n      {\n        // Tree list\n        window_focus = 1;\n\n        if(last_node_selected != node_selected)\n        // && (focus->num_counters || focus->show_child_contents))\n        {\n          rebuild_var_list(focus, &var_list, &num_vars, hide_empty_vars);\n          var_selected = 0;\n\n          label[0] = '\\0';\n          get_node_name(focus, label, 80);\n        }\n        break;\n      }\n\n      // Tree node\n      case 1:\n      {\n        window_focus = 1;\n\n        // Collapse/Expand selected view if applicable\n        if(focus->num_nodes)\n        {\n          focus->opened = !(focus->opened);\n\n          rebuild_tree_list(&root, &tree_list, &tree_size);\n        }\n        break;\n      }\n\n      // Search (Ctrl+F)\n      case 2:\n      {\n        if(counter_search_dialog(mzx_world, search_text, &search_flags))\n          break;\n      }\n\n      /* fallthrough */\n\n      // Repeat search (Ctrl+R)\n      case -3:\n      {\n        boolean matched = false;\n        struct debug_var *search_var = NULL;\n        struct debug_node *search_node = NULL;\n        struct debug_node *search_targ = &root;\n        char search_text_unescaped[VAR_SEARCH_MAX + 1];\n        int search_text_length = 0;\n        int search_pos = 0;\n\n        // There is a var currently selected.\n        if(var_selected < num_vars)\n          search_var = var_list[var_selected];\n        // The current view is empty.\n        else\n          search_node = focus;\n\n        memcpy(search_text_unescaped, search_text, VAR_SEARCH_MAX);\n        search_text_unescaped[VAR_SEARCH_MAX] = '\\0';\n        unescape_string(search_text_unescaped, &search_text_length);\n\n        if(!search_text_length)\n          break;\n\n        // Local search?\n        if(search_flags & VAR_SEARCH_LOCAL)\n          search_targ = focus;\n\n        matched = search_debug(mzx_world, search_targ,\n         &search_var, &search_node, &search_pos,\n         search_text_unescaped, search_text_length, search_flags);\n\n        if(matched)\n        {\n          // First, is it in the current list? If so, we don't want to switch\n          // focus nodes (and possibly spend time rebuilding the list).\n          // We want to start from the current selected var and check in the\n          // direction of the search for best results.\n          if(num_vars)\n          {\n            int inc = !(search_flags & VAR_SEARCH_REVERSE) ? 1 : -1;\n            boolean found = false;\n\n            i = var_selected;\n            do\n            {\n              i = (num_vars + i + inc) % num_vars;\n              if(var_list[i] == search_var)\n              {\n                window_focus = 0; // Var list\n                var_selected = i;\n                found = true;\n                break;\n              }\n            }\n            while(i != var_selected);\n\n            if(found)\n              break;\n          }\n\n          // Nothing in the local context in a local search?  Abandon ship!\n          if((search_flags & VAR_SEARCH_LOCAL) && (search_node != focus))\n            break;\n\n          // The var we matched might be hidden, so disable var hiding.\n          hide_empty_vars = false;\n\n          // Open search_node in var list\n          rebuild_var_list(search_node, &var_list, &num_vars, hide_empty_vars);\n          var_selected = search_pos;\n\n          window_focus = 0; // Var list\n          focus = search_node;\n          refocus_tree_list = true;\n        }\n        break;\n      }\n\n      // Add counter/string\n      case 3:\n      {\n        char add_name[VAR_ADD_MAX + 1] = { 0 };\n        window_focus = 3;\n\n        if(new_counter_dialog(mzx_world, add_name))\n        {\n          size_t add_len = strlen(add_name);\n\n          // Hope it was worth it!\n          repopulate_tree(mzx_world, &root);\n\n          // The new counter is empty, so\n          hide_empty_vars = false;\n\n          // Find the counter/string we just made\n          select_debug_var(&root, add_name, add_len, &focus, &var_selected);\n\n          rebuild_var_list(focus, &var_list, &num_vars, hide_empty_vars);\n\n          window_focus = 0;\n          refocus_tree_list = true;\n        }\n        break;\n      }\n\n      // Toggle Empties\n      case 4:\n      {\n        struct debug_var *current = NULL;\n        hide_empty_vars = !hide_empty_vars;\n\n        if(num_vars)\n          current = var_list[var_selected];\n\n        rebuild_var_list(focus, &var_list, &num_vars, hide_empty_vars);\n        var_selected = 0;\n\n        for(i = 0; i < num_vars; i++)\n        {\n          if(var_list[i] == current)\n          {\n            var_selected = i;\n            break;\n          }\n        }\n\n        label[0] = '\\0';\n        get_node_name(focus, label, 80);\n\n        window_focus = 4;\n        break;\n      }\n\n      // Export\n      case 5:\n      {\n        char export_name[MAX_PATH];\n        const char *txt_ext[] = { \".TXT\", NULL };\n\n        export_name[0] = 0;\n\n        if(!new_file(mzx_world, txt_ext, \".txt\", export_name,\n         \"Export counters/strings text file...\", ALLOW_ALL_DIRS))\n        {\n          struct counter_list *counter_list = &(mzx_world->counter_list);\n          struct string_list *string_list = &(mzx_world->string_list);\n          vfile *vf;\n          size_t i;\n\n          vf = vfopen_unsafe(export_name, \"wb\");\n\n          for(i = 0; i < counter_list->num_counters; i++)\n          {\n            struct counter *ctr = counter_list->counters[i];\n            vf_printf(vf, \"set \\\"%s\\\" to %\" PRId32 \"\\n\", ctr->name, ctr->value);\n          }\n\n          for(i = 0; i < string_list->num_strings; i++)\n          {\n            struct string *str = string_list->strings[i];\n            vf_printf(vf, \"set \\\"%s\\\" to \\\"\", str->name);\n            vfwrite(str->value, str->length, 1, vf);\n            vfputs(\"\\\"\\n\", vf);\n          }\n\n          vfclose(vf);\n        }\n\n        window_focus = 5;\n        break;\n      }\n    }\n    if(focus->refresh_on_focus)\n    {\n      clear_cached_data(mzx_world);\n      for(i = 0; i < focus->num_vars; i++)\n        read_var(mzx_world, &(focus->vars[i]));\n    }\n\n    // If the current position in the tree was changed by a search, bring it\n    // to focus in the tree list. This should only be used after a search.\n    if(refocus_tree_list)\n    {\n      // Open all parents to ensure that this node will exist in the tree list.\n      struct debug_node *node = focus;\n      while(node && node->parent)\n      {\n        node = node->parent;\n        node->opened = true;\n      }\n\n      // Clear and rebuild tree list.\n      rebuild_tree_list(&root, &tree_list, &tree_size);\n\n      // Find our node in the new tree list.\n      for(i = 0; i < tree_size; i++)\n      {\n        if(!strcmp(tree_list[i] + TREE_LIST_WIDTH, focus->name))\n        {\n          node_selected = i;\n          node_scroll_offset = node_selected - (TREE_LIST_HEIGHT/2);\n          break;\n        }\n      }\n\n      // Fix the current node name label.\n      label[0] = '\\0';\n      get_node_name(focus, label, 80);\n\n      refocus_tree_list = false;\n    }\n\n    // If the debug menu was just reopened, reset searching\n    if(reopened)\n    {\n      search_text[0] = 0;\n      search_flags = VAR_SEARCH_NAMES + VAR_SEARCH_VALUES + VAR_SEARCH_WRAP;\n      reopened = false;\n      // Also don't destruct the (uninitialized) dialog\n      continue;\n    }\n\n    destruct_dialog(&di);\n\n  } while(dialog_result != -1);\n\n  pop_context();\n\n  // Make sure the robot debugger watchpoints have updated values\n  update_watchpoint_last_values(mzx_world);\n\n  // Copy the last selected var to the previous field.\n  if(var_selected < num_vars)\n  {\n    char buffer[VAR_LIST_WIDTH + 1];\n    const char *var_name;\n    int len;\n\n    get_var_name(var_list[var_selected], &var_name, &len, buffer);\n    if(len > VAR_SEARCH_MAX)\n      len = VAR_SEARCH_MAX;\n\n    memcpy(previous_node_name, focus->name, DEBUG_NODE_NAME_SIZE);\n    previous_node_name[DEBUG_NODE_NAME_SIZE - 1] = '\\0';\n    memcpy(previous_var, var_name, len + 1);\n    previous_var[len] = '\\0';\n  }\n\n  // Clear the big dumb tree first\n  clear_debug_tree(&root, true);\n\n  // Get rid of the tree view\n  free_tree_list(tree_list, tree_size);\n\n  // We don't need to free anything this list points\n  // to because clear_debug_tree already did it.\n  free(var_list);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n}\n\n/************************************************/\n/* Debug box, which shows actually useful stuff */\n/************************************************/\n\nvoid __draw_debug_box(struct world *mzx_world, int x, int y, int d_x, int d_y,\n int show_keys)\n{\n  struct board *src_board = mzx_world->current_board;\n  char version_string[16];\n  int version_string_len;\n  char key_string[4];\n  int key;\n  int robot_mem = 0;\n  int i;\n\n  draw_window_box(x, y, x + 19, y + 5, DI_DEBUG_BOX, DI_DEBUG_BOX_DARK,\n   DI_DEBUG_BOX_CORNER, 0, 1);\n\n  write_string\n  (\n    \"X/Y:        /     \\n\"\n    \"Board:            \\n\"\n    \"Robot mem:      kb\\n\",\n    x + 1, y + 1, DI_DEBUG_LABEL, WR_NEWLINE\n  );\n\n  version_string_len = get_version_string(version_string, mzx_world->version);\n  write_string(version_string, x + 19 - version_string_len, y, DI_DEBUG_BOX, WR_NONE);\n\n  if(show_keys)\n  {\n    // key_code\n    key = get_last_key(keycode_pc_xt);\n    if(key && show_keys)\n    {\n      sprintf(key_string, \"%d\", key);\n      write_string(key_string, x + 15 - strlen(key_string), y + 5,\n       DI_DEBUG_BOX_DARK + 0x0A, WR_NONE);\n    }\n\n    // key_pressed\n\n    // In 2.82X through 2.84X, this erroneously applied\n    // numlock translations to the keycode.\n    if(mzx_world->version >= V282 && mzx_world->version <= V284)\n      key = get_last_key(keycode_internal_wrt_numlock);\n\n    else\n      key = get_last_key(keycode_internal);\n\n    if(key && show_keys)\n    {\n      sprintf(key_string, \"%d\", key);\n      write_string(key_string, x + 19 - strlen(key_string), y + 5,\n       DI_DEBUG_BOX_DARK + 0x0D, WR_NONE);\n    }\n  }\n\n  write_number(d_x, DI_DEBUG_NUMBER, x + 8, y + 1, 5, 0, 10);\n  write_number(d_y, DI_DEBUG_NUMBER, x + 14, y + 1, 5, 0, 10);\n  write_number(mzx_world->current_board_id, DI_DEBUG_NUMBER,\n   x + 18, y + 2, 0, 1, 10);\n\n  for(i = 0; i < src_board->num_robots_active; i++)\n  {\n    robot_mem +=\n#ifdef CONFIG_DEBYTECODE\n     (src_board->robot_list_name_sorted[i])->program_source_length;\n#else\n     (src_board->robot_list_name_sorted[i])->program_bytecode_length;\n#endif\n  }\n\n  write_number((robot_mem + 512) / 1024, DI_DEBUG_NUMBER,\n   x + 12, y + 3, 5, 0, 10);\n\n  if(*(src_board->mod_playing) != 0)\n  {\n    if(strlen(src_board->mod_playing) > 18)\n    {\n      char tempc = src_board->mod_playing[18];\n      src_board->mod_playing[18] = 0;\n      write_string(src_board->mod_playing, x + 1, y + 4,\n       DI_DEBUG_NUMBER, WR_NONE);\n      src_board->mod_playing[18] = tempc;\n    }\n    else\n    {\n      write_string(src_board->mod_playing, x + 1, y + 4,\n       DI_DEBUG_NUMBER, WR_NONE);\n    }\n  }\n  else\n  {\n    write_string(\"(no module)\", x + 2, y + 4, DI_DEBUG_NUMBER, WR_NONE);\n  }\n}\n"
  },
  {
    "path": "src/editor/debug.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __DEBUG_H\n#define __DEBUG_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../core.h\"\n\nint get_counter_safe(struct world *mzx_world, const char *name, int id);\n\nvoid __debug_counters(context *ctx);\nvoid __draw_debug_box(struct world *mzx_world, int x, int y, int d_x, int d_y,\n int show_keys);\n\n__M_END_DECLS\n\n#endif // __DEBUG_H\n"
  },
  {
    "path": "src/editor/edit.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../board.h\"\n#include \"../caption.h\"\n#include \"../const.h\"\n#include \"../core.h\"\n#include \"../counter.h\"\n#include \"../data.h\"\n#include \"../error.h\"\n#include \"../event.h\"\n#include \"../extmem.h\"\n#include \"../game.h\"\n#include \"../game_player.h\"\n#include \"../graphics.h\"\n#include \"../idarray.h\"\n#include \"../idput.h\"\n#include \"../mzm.h\"\n#include \"../platform.h\"\n#include \"../robot.h\"\n#include \"../scrdisp.h\"\n#include \"../util.h\"\n#include \"../window.h\"\n#include \"../world.h\"\n#include \"../io/path.h\"\n#include \"../io/vio.h\"\n\n#include \"../audio/audio.h\"\n#include \"../audio/sfx.h\"\n\n#include \"ansi.h\"\n#include \"block.h\"\n#include \"board.h\"\n#include \"buffer.h\"\n#include \"char_ed.h\"\n#include \"configure.h\"\n#include \"debug.h\"\n#include \"edit.h\"\n#include \"edit_di.h\"\n#include \"edit_export.h\"\n#include \"edit_menu.h\"\n#include \"fill.h\"\n#include \"graphics.h\"\n#include \"pal_ed.h\"\n#include \"param.h\"\n#include \"robo_debug.h\"\n#include \"robot.h\"\n#include \"select.h\"\n#include \"sfx_edit.h\"\n#include \"undo.h\"\n#include \"window.h\"\n#include \"world.h\"\n\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/stat.h>\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#define NEW_WORLD_TITLE       \"Untitled world\"\n#define TEST_WORLD_FILENAME   \"__test.mzx\"\n#define TEST_WORLD_PATTERN    \"__test%d.mzx\"\n\nstatic const char *const world_ext[] = { \".MZX\", NULL };\nstatic const char *const mzb_ext[] = { \".MZB\", NULL };\nstatic const char *const mzm_ext[] = { \".MZM\", NULL };\nstatic const char *const chr_ext[] = { \".CHR\", NULL };\nstatic const char *const ans_ext[] = { \".ANS\", \".TXT\", NULL };\nstatic const char *const mod_ext[] =\n{ \".ogg\", \".mod\", \".s3m\", \".xm\", \".it\",\n  \".669\", \".amf\", \".dsm\", \".far\", \".gdm\",\n  \".med\", \".mtm\", \".nst\", \".oct\", \".okt\",\n  \".stm\", \".ult\", \".wow\", \".rad\",\n  NULL\n};\nstatic const char *const sam_ext[] =\n{ \".WAV\", \".SAM\", \".OGG\",\n  NULL\n};\n\nstatic const char *const image_ext[] =\n{\n  \".PNG\",\n  NULL\n};\n\nstruct editor_context\n{\n  context ctx;\n  subcontext *edit_menu;\n\n  char current_world[MAX_PATH];\n  char mzb_name_buffer[MAX_PATH];\n  char mzm_name_buffer[MAX_PATH];\n  char chr_name_buffer[MAX_PATH];\n  char current_listening_dir[MAX_PATH];\n  char current_listening_mod[MAX_PATH];\n\n  // Editor state\n  enum editor_mode mode;\n  boolean modified;\n  boolean modified_prev;\n  boolean first_board_prompt;\n  boolean listening_mod_active;\n  boolean show_board_under_overlay; // = 1\n  int backup_timestamp;             // = get_ticks()\n  int backup_num;\n  int screen_height;\n\n  // Buffer\n  struct buffer_info buffer;\n  struct robot buffer_robot;\n  struct scroll buffer_scroll;\n  struct sensor buffer_sensor;\n  boolean use_default_color;        // = 1\n  enum thing stored_board_id;\n  char stored_board_color;\n  char stored_board_param;\n  char stored_overlay_char;\n  char stored_overlay_color;\n  char stored_vlayer_char;\n  char stored_vlayer_color;\n\n  // Modify in-place buffer\n  struct buffer_info temp_buffer;\n  struct robot temp_robot;\n  struct scroll temp_scroll;\n  struct sensor temp_sensor;\n  boolean modified_storage;\n  int modify_param;\n\n  // Current board\n  int board_width;\n  int board_height;\n\n  // Cursor\n  int cursor_x;\n  int cursor_y;\n  int scroll_x;\n  int scroll_y;\n  int debug_x;                      // = 60\n  int text_start_x;\n  struct block_info block;\n  enum cursor_mode cursor_mode;\n  int mouse_last_x;\n  int mouse_last_y;\n\n  // Temporary stored cursor positions\n  int stored_board_x;\n  int stored_board_y;\n  int stored_vlayer_x;\n  int stored_vlayer_y;\n  int stored_scroll_x;\n  int stored_scroll_y;\n  int stored_vscroll_x;\n  int stored_vscroll_y;\n\n  // Undo history\n  struct undo_history *cur_history;\n  struct undo_history *board_history;\n  struct undo_history *overlay_history;\n  struct undo_history *vlayer_history;\n  boolean continue_mouse_history;\n\n  // Flash thing\n  boolean flashing;\n  enum thing flash_start;\n  int flash_len;\n  int flash_char_a;\n  int flash_char_b;\n  int flash_timer;\n  int flash_timer_swap;\n  int flash_timer_max;\n  uint32_t flash_layer;\n\n  // ANSi settings\n  int ansi_line_wrap_column; // = 80\n  boolean ansi_line_wrap;\n  boolean ansi_save_text_only;\n  boolean ansi_save_doorway_mode;\n  char ansi_save_title[36];\n  char ansi_save_author[21];\n\n  // Testing\n  int test_reload_board;\n  int test_reload_version;\n  char test_reload_dir[MAX_PATH];\n  char test_reload_file[MAX_PATH];\n  boolean reload_after_testing;\n};\n\n/**\n * Wrapper for reload_world that also loads world-specific editor config files.\n */\nstatic boolean editor_reload_world(struct editor_context *editor,\n const char *file)\n{\n  struct world *mzx_world = ((context *)editor)->world;\n\n  char config_file_name[MAX_PATH];\n  boolean ignore;\n\n  if(!reload_world(mzx_world, file, &ignore))\n    return false;\n\n  // Part 1: Reset the config file.\n  load_editor_config_backup();\n\n  // Part 2: Now load the new world.editor.cnf.\n  if(get_local_editor_config_name(config_file_name, MAX_PATH, file) > 0)\n    set_config_from_file(GAME_EDITOR_CNF, config_file_name);\n\n  edit_menu_show_board_mod(editor->edit_menu);\n  return true;\n}\n\n/**\n * Get the filename for the test world file that will be reloaded after testing.\n * Attempts to ensure that previous test files left behind due to crashes or\n * other instances of MegaZeux that are also testing aren't overwritten.\n */\nstatic void get_test_world_filename(struct editor_context *editor)\n{\n  struct stat file_info;\n  int i;\n\n  strcpy(editor->test_reload_file, TEST_WORLD_FILENAME);\n\n  // If the regular file exists, attempt numbered names.\n  for(i = 2; !vstat(editor->test_reload_file, &file_info); i++)\n    snprintf(editor->test_reload_file, MAX_PATH, TEST_WORLD_PATTERN, i);\n}\n\n/**\n * Update the editor to the current undo history.\n */\nstatic void fix_history(struct editor_context *editor)\n{\n  switch(editor->mode)\n  {\n    case EDIT_BOARD:\n      editor->cur_history = editor->board_history;\n      return;\n\n    case EDIT_OVERLAY:\n      editor->cur_history = editor->overlay_history;\n      return;\n\n    case EDIT_VLAYER:\n      editor->cur_history = editor->vlayer_history;\n      return;\n\n    default:\n      editor->cur_history = NULL;\n      return;\n  }\n}\n\n/**\n * Clear or reset the current board undo history.\n */\nstatic void clear_board_history(struct editor_context *editor)\n{\n  destruct_undo_history(editor->board_history);\n  editor->board_history = NULL;\n  fix_history(editor);\n}\n\n/**\n * Clear or reset the current overlay undo history.\n */\nstatic void clear_overlay_history(struct editor_context *editor)\n{\n  destruct_undo_history(editor->overlay_history);\n  editor->overlay_history = NULL;\n  fix_history(editor);\n}\n\n/**\n * Clear or reset the current vlayer undo history.\n */\nstatic void clear_vlayer_history(struct editor_context *editor)\n{\n  destruct_undo_history(editor->vlayer_history);\n  editor->vlayer_history = NULL;\n  fix_history(editor);\n}\n\n/**\n * Set the buffer to the default id/color/param for the current mode.\n */\nstatic void default_buffer(struct editor_context *editor)\n{\n  struct buffer_info *buffer = &(editor->buffer);\n\n  if(editor->mode == EDIT_BOARD)\n  {\n    buffer->id = SPACE;\n    buffer->color = 7;\n    buffer->param = 0;\n  }\n  else\n  {\n    buffer->id = SPACE;\n    buffer->color = 7;\n    buffer->param = 32;\n  }\n}\n\n/**\n * Switch the current editing mode. If switching to or from the vlayer,\n * the current cursor and scroll position needs to be stored.\n */\nstatic void set_editor_mode(struct editor_context *editor,\n enum editor_mode new_mode)\n{\n  if(editor->mode != EDIT_VLAYER && new_mode == EDIT_VLAYER)\n  {\n    editor->stored_board_x = editor->cursor_x;\n    editor->stored_board_y = editor->cursor_y;\n    editor->stored_scroll_x = editor->scroll_x;\n    editor->stored_scroll_y = editor->scroll_y;\n\n    editor->cursor_x = editor->stored_vlayer_x;\n    editor->cursor_y = editor->stored_vlayer_y;\n    editor->scroll_x = editor->stored_vscroll_x;\n    editor->scroll_y = editor->stored_vscroll_y;\n  }\n  else\n\n  if(editor->mode == EDIT_VLAYER && new_mode != EDIT_VLAYER)\n  {\n    editor->stored_vlayer_x = editor->cursor_x;\n    editor->stored_vlayer_y = editor->cursor_y;\n    editor->stored_vscroll_x = editor->scroll_x;\n    editor->stored_vscroll_y = editor->scroll_y;\n\n    editor->cursor_x = editor->stored_board_x;\n    editor->cursor_y = editor->stored_board_y;\n    editor->scroll_x = editor->stored_scroll_x;\n    editor->scroll_y = editor->stored_scroll_y;\n  }\n\n  // When switching modes, store the buffer for the old mode and load\n  // the buffer for the new mode. Previously, switching modes would\n  // (inconsistently) set the default buffer instead.\n  if(editor->mode != new_mode)\n  {\n    struct buffer_info *buffer = &(editor->buffer);\n\n    switch(editor->mode)\n    {\n      case EDIT_BOARD:\n        editor->stored_board_id = buffer->id;\n        editor->stored_board_color = buffer->color;\n        editor->stored_board_param = buffer->param;\n        break;\n\n      case EDIT_OVERLAY:\n        editor->stored_overlay_char = buffer->param;\n        editor->stored_overlay_color = buffer->color;\n        break;\n\n      case EDIT_VLAYER:\n        editor->stored_vlayer_char = buffer->param;\n        editor->stored_vlayer_color = buffer->color;\n        break;\n    }\n\n    switch(new_mode)\n    {\n      case EDIT_BOARD:\n        buffer->id = editor->stored_board_id;\n        buffer->color = editor->stored_board_color;\n        buffer->param = editor->stored_board_param;\n        break;\n\n      case EDIT_OVERLAY:\n        buffer->id = SPACE;\n        buffer->param = editor->stored_overlay_char;\n        buffer->color = editor->stored_overlay_color;\n        break;\n\n      case EDIT_VLAYER:\n        buffer->id = SPACE;\n        buffer->param = editor->stored_vlayer_char;\n        buffer->color = editor->stored_vlayer_color;\n        break;\n    }\n  }\n\n  editor->mode = new_mode;\n  fix_history(editor);\n}\n\n/**\n * Update the editor board values to match the current board.\n */\nstatic void synchronize_board_values(struct editor_context *editor)\n{\n  struct world *mzx_world = ((context *)editor)->world;\n  struct board *cur_board = mzx_world->current_board;\n\n  editor->board_width = cur_board->board_width;\n  editor->board_height = cur_board->board_height;\n\n  if(editor->mode == EDIT_VLAYER)\n  {\n    // During vlayer editing, treat the vlayer as if it's the board.\n    editor->board_width = mzx_world->vlayer_width;\n    editor->board_height = mzx_world->vlayer_height;\n  }\n}\n\n/**\n * Fix the world title after an operation that may have modified the current\n * board's title. This should be done before fix_caption.\n */\nstatic void fix_world_title(struct editor_context *editor)\n{\n  struct world *mzx_world = ((context *)editor)->world;\n  struct board *cur_board = mzx_world->current_board;\n\n  if(mzx_world->current_board_id == 0)\n  {\n    snprintf(mzx_world->name, BOARD_NAME_SIZE, \"%s\", cur_board->board_name);\n    mzx_world->name[BOARD_NAME_SIZE - 1] = '\\0';\n  }\n}\n\n/**\n * Set the caption for the editor.\n */\nstatic void fix_caption(struct editor_context *editor)\n{\n  struct world *mzx_world = ((context *)editor)->world;\n  struct board *cur_board = mzx_world->current_board;\n\n  caption_set_board(mzx_world, cur_board);\n}\n\n/**\n * Bound the cursor within the current board and update the debug window\n * position.\n */\nstatic void fix_cursor(struct editor_context *editor)\n{\n  if(editor->cursor_x >= editor->board_width)\n    editor->cursor_x = (editor->board_width - 1);\n\n  if(editor->cursor_y >= editor->board_height)\n    editor->cursor_y = (editor->board_height - 1);\n\n  if((editor->cursor_x - editor->scroll_x) < (editor->debug_x + 25))\n    editor->debug_x = 60;\n\n  if((editor->cursor_x - editor->scroll_x) > (editor->debug_x - 5))\n    editor->debug_x = 0;\n}\n\n/**\n * Bound the editor viewport within the board.\n */\nstatic void fix_scroll(struct editor_context *editor)\n{\n  // Ensure the cursor is visible in the editor viewport.\n  if(editor->cursor_x - editor->scroll_x < 5)\n    editor->scroll_x = editor->cursor_x - 5;\n\n  if(editor->cursor_x - editor->scroll_x > 74)\n    editor->scroll_x = editor->cursor_x - 74;\n\n  if(editor->cursor_y - editor->scroll_y < 3)\n    editor->scroll_y = editor->cursor_y - 3;\n\n  if(editor->cursor_y - editor->scroll_y > (editor->screen_height - 5))\n    editor->scroll_y = editor->cursor_y - editor->screen_height + 5;\n\n  // The edge bounds checks need to be done in this order.\n  if(editor->scroll_x + 80 > editor->board_width)\n    editor->scroll_x = (editor->board_width - 80);\n\n  if(editor->scroll_x < 0)\n    editor->scroll_x = 0;\n\n  if(editor->scroll_y + editor->screen_height > editor->board_height)\n    editor->scroll_y = (editor->board_height - editor->screen_height);\n\n  if(editor->scroll_y < 0)\n    editor->scroll_y = 0;\n\n  fix_cursor(editor);\n}\n\n/**\n * Play the current board's mod if the listening mod isn't playing.\n * This will restart the board mod even if it's already playing.\n */\nstatic void fix_mod(struct editor_context *editor)\n{\n  struct world *mzx_world = ((context *)editor)->world;\n\n  if(!editor->listening_mod_active)\n  {\n    if(!strcmp(mzx_world->current_board->mod_playing, \"*\"))\n    {\n      mzx_world->real_mod_playing[0] = '\\0';\n      audio_end_module();\n    }\n\n    else\n    {\n      load_board_module(mzx_world);\n    }\n  }\n}\n\n/**\n * Set the active world's current board.\n */\nstatic void editor_set_current_board(struct editor_context *editor,\n int new_board, boolean is_extram)\n{\n  const struct editor_config_info *editor_conf = get_editor_config();\n  struct world *mzx_world = ((context *)editor)->world;\n  struct board *cur_board;\n\n  if(new_board >= 0)\n  {\n    // Hopefully we won't be getting anything out of range but just in case\n    if(new_board >= mzx_world->num_boards)\n      new_board = mzx_world->num_boards - 1;\n\n    mzx_world->current_board_id = new_board;\n    cur_board = mzx_world->board_list[new_board];\n\n    if(is_extram)\n      set_current_board_ext(mzx_world, cur_board);\n    else\n      set_current_board(mzx_world, cur_board);\n\n    if((editor->mode == EDIT_OVERLAY && !cur_board->overlay_mode) ||\n     editor->mode == EDIT_VLAYER)\n    {\n      set_editor_mode(editor, EDIT_BOARD);\n\n      // If there's a block action active, cancel it.\n      if(editor->block.selected)\n      {\n        editor->block.selected = false;\n        editor->cursor_mode = CURSOR_PLACE;\n      }\n    }\n\n    synchronize_board_values(editor);\n    fix_scroll(editor);\n    fix_caption(editor);\n\n    // We want mod switching from changing boards to act the same as gameplay.\n    if(!editor->listening_mod_active)\n      load_game_module(mzx_world, cur_board->mod_playing, true);\n\n    // Load the board charset and palette\n    if(editor_conf->editor_load_board_assets)\n      change_board_load_assets(mzx_world);\n\n    // Reset the local undo histories\n    clear_board_history(editor);\n    clear_overlay_history(editor);\n\n    edit_menu_show_board_mod(editor->edit_menu);\n    editor->modified = true;\n  }\n}\n\n/**\n * Move the cursor on the board.\n */\nstatic void move_edit_cursor(struct editor_context *editor,\n int move_x, int move_y)\n{\n  const struct editor_config_info *editor_conf = get_editor_config();\n  struct world *mzx_world = ((context *)editor)->world;\n  struct board *cur_board = mzx_world->current_board;\n  struct buffer_info *buffer = &(editor->buffer);\n\n  // Bound the movement within the board\n  if(editor->cursor_x + move_x < 0)\n    move_x = -editor->cursor_x;\n\n  if(move_x + editor->cursor_x > editor->board_width - 1)\n    move_x = editor->board_width - editor->cursor_x - 1;\n\n  if(editor->cursor_y + move_y < 0)\n    move_y = -editor->cursor_y;\n\n  if(move_y + editor->cursor_y > editor->board_height - 1)\n    move_y = editor->board_height - editor->cursor_y - 1;\n\n  if(move_x)\n    move_y = 0;\n\n  if(move_x || move_y)\n  {\n    int offset = editor->cursor_x + (editor->cursor_y * editor->board_width);\n    int step_x = SGN(move_x);\n    int step_y = SGN(move_y);\n    int width = abs(move_x) + 1;\n    int height = abs(move_y) + 1;\n\n    if(step_x < 0)\n      offset -= (width - 1);\n\n    if(step_y < 0)\n      offset -= editor->board_width * (height - 1);\n\n    if(editor->cursor_mode == CURSOR_DRAW)\n    {\n      switch(editor->mode)\n      {\n        case EDIT_BOARD:\n          add_block_undo_frame(\n           mzx_world, editor->cur_history, cur_board, offset, width, height);\n          break;\n\n        case EDIT_OVERLAY:\n          add_layer_undo_frame(editor->cur_history, cur_board->overlay,\n           cur_board->overlay_color, editor->board_width, offset, width, height);\n          break;\n\n        case EDIT_VLAYER:\n          add_layer_undo_frame(editor->cur_history, mzx_world->vlayer_chars,\n           mzx_world->vlayer_colors, editor->board_width, offset, width, height);\n          break;\n      }\n    }\n\n    do\n    {\n      editor->cursor_x += step_x;\n      editor->cursor_y += step_y;\n      move_x -= step_x;\n      move_y -= step_y;\n\n      if(editor->cursor_mode == CURSOR_DRAW)\n      {\n        // We're creating a history frame manually, so don't pass it.\n        buffer->param = place_current_at_xy(mzx_world, buffer,\n         editor->cursor_x, editor->cursor_y, editor->mode, NULL);\n      }\n    }\n    while(move_x || move_y);\n\n    if(editor->cursor_mode == CURSOR_DRAW)\n    {\n      update_undo_frame(editor->cur_history);\n      editor->modified = true;\n\n      if(editor_conf->editor_tab_focuses_view)\n      {\n        // Out-of-bounds will be fixed by fix_scroll\n        editor->scroll_x = editor->cursor_x - 40;\n        editor->scroll_y = editor->cursor_y - (editor->screen_height / 2);\n      }\n    }\n\n    fix_scroll(editor);\n  }\n}\n\n/**\n * Place typed text on the board.\n */\nstatic void place_text(struct editor_context *editor)\n{\n  struct world *mzx_world = ((context *)editor)->world;\n  struct buffer_info temp_buffer;\n  int num_placed = 0;\n\n  temp_buffer.id = TEXT_ID;\n  temp_buffer.color = editor->buffer.color;\n\n  while(num_placed < KEY_UNICODE_MAX)\n  {\n    temp_buffer.param = get_key(keycode_text_ascii);\n    if(temp_buffer.param)\n    {\n      place_current_at_xy(mzx_world, &temp_buffer,\n       editor->cursor_x, editor->cursor_y, editor->mode, editor->cur_history);\n\n      if(editor->cursor_x < (editor->board_width - 1))\n        editor->cursor_x++;\n\n      num_placed++;\n      editor->modified = true;\n    }\n    else\n      break;\n  }\n  fix_scroll(editor);\n}\n\n/**\n * Context to view the current board as if currently playing the board.\n */\nstruct view_board_context\n{\n  context ctx;\n  int x;\n  int y;\n  int max_x;\n  int max_y;\n};\n\nstatic boolean view_board_draw(context *ctx)\n{\n  struct view_board_context *vb = (struct view_board_context *)ctx;\n  struct world *mzx_world = ctx->world;\n\n  blank_layers();\n  draw_viewport(mzx_world->current_board, mzx_world->edge_color);\n  draw_game_window(mzx_world->current_board, vb->x, vb->y);\n  return true;\n}\n\nstatic boolean view_board_key(context *ctx, int *key)\n{\n  struct view_board_context *vb = (struct view_board_context *)ctx;\n  int move_times = 1;\n\n  if(get_alt_status(keycode_internal))\n    move_times = 10;\n\n  if(get_exit_status())\n    *key = IKEY_ESCAPE;\n\n  switch(*key)\n  {\n    case IKEY_ESCAPE:\n    {\n      destroy_context(ctx);\n      return true;\n    }\n\n    case IKEY_LEFT:\n    {\n      for(; move_times > 0; move_times--)\n        if(vb->x)\n          (vb->x)--;\n      return true;\n    }\n\n    case IKEY_RIGHT:\n    {\n      for(; move_times > 0; move_times--)\n        if(vb->x < vb->max_x)\n          (vb->x)++;\n      return true;\n    }\n\n    case IKEY_UP:\n    {\n      for(; move_times > 0; move_times--)\n        if(vb->y)\n          (vb->y)--;\n      return true;\n    }\n\n    case IKEY_DOWN:\n    {\n      for(; move_times > 0; move_times--)\n        if(vb->y < vb->max_y)\n          (vb->y)++;\n      return true;\n    }\n  }\n  return false;\n}\n\nstatic void view_board(struct editor_context *editor)\n{\n  struct view_board_context *vb = cmalloc(sizeof(struct view_board_context));\n  struct world *mzx_world = ((context *)editor)->world;\n  struct board *cur_board = mzx_world->current_board;\n  struct context_spec spec;\n\n  vb->max_x = cur_board->board_width - cur_board->viewport_width;\n  vb->max_y = cur_board->board_height - cur_board->viewport_height;\n  vb->x = CLAMP(editor->scroll_x, 0, vb->max_x);\n  vb->y = CLAMP(editor->scroll_y, 0, vb->max_y);\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw = view_board_draw;\n  spec.key  = view_board_key;\n\n  create_context((context *)vb, (context *)editor, &spec,\n   CTX_EDITOR_VIEW_BOARD);\n\n  cursor_off();\n  m_hide();\n}\n\nenum flash_thing_type\n{\n  FLASH_THING_NORMAL,\n  FLASH_THING_CLOSE_COLORS,\n  FLASH_THING_CLOSE_CHAR,\n  FLASH_THING_SMZX,\n};\n\n/**\n * Flash a range of types to make them more visible onscreen.\n */\nstatic void flash_thing(struct editor_context *editor,\n enum thing start, enum thing end, int flash_char_a, int flash_char_b)\n{\n  const struct editor_config_info *editor_conf = get_editor_config();\n\n  // If the same type of flashing is already enabled, toggle it off instead.\n  if(editor_conf->editor_show_thing_toggles && editor->flashing &&\n   (editor->flash_start == start))\n  {\n    editor->flashing = false;\n    return;\n  }\n\n  editor->flashing = true;\n  editor->flash_start = start;\n  editor->flash_len = end - start + 1;\n  editor->flash_char_a = flash_char_a;\n  editor->flash_char_b = flash_char_b;\n  editor->flash_timer = 0;\n  editor->flash_timer_swap = MAX(1, editor_conf->editor_show_thing_blink_speed);\n  editor->flash_timer_max = MAX(1, editor_conf->editor_show_thing_blink_speed * 2);\n}\n\n/**\n * Initialize flashing for the current cycle and return the current flash char.\n */\nstatic int flash_init(struct editor_context *editor)\n{\n  const struct editor_config_info *editor_conf = get_editor_config();\n  int chr = editor->flash_char_a;\n\n  if(editor->flash_timer >= editor->flash_timer_swap)\n    chr = editor->flash_char_b;\n\n  editor->flash_layer = 0;\n  if(!editor_conf->editor_show_thing_toggles)\n    cursor_off();\n  return chr;\n}\n\n/**\n * Increment the flash timer and clear any flash values that shouldn't persist.\n */\nstatic void flash_done(struct editor_context *editor)\n{\n  editor->flash_timer = (editor->flash_timer + 1) % editor->flash_timer_max;\n  editor->flash_layer = 0;\n}\n\n/**\n * Determine if a protected palette char flash is required.\n */\nstatic enum flash_thing_type flash_get_type(struct editor_context *editor,\n struct board *cur_board, uint8_t chr, uint8_t color, uint8_t flash_chr)\n{\n  int screen_mode = get_screen_mode();\n  if(!screen_mode)\n  {\n    unsigned int fg = color & 0x0F;\n    unsigned int bg = (color & 0xF0) >> 4;\n\n    ssize_t diff;\n\n    // Are the MZX mode colors the same?\n    if(fg == bg)\n      return FLASH_THING_CLOSE_COLORS;\n\n    // Are the MZX mode colors too close?\n    diff = (ssize_t)get_color_luma(fg) - (ssize_t)get_color_luma(bg);\n    if(diff >= -64 && diff <= 64)\n      return FLASH_THING_CLOSE_COLORS;\n  }\n\n  // If one of the chars to display is the board char, is it too close to the\n  // flash char?\n  if(!editor->flash_char_a || !editor->flash_char_b)\n    if(compare_char(chr, (uint16_t)flash_chr + PRO_CH) >= (112*3/4))\n      return FLASH_THING_CLOSE_CHAR;\n\n  // SMZX modes should always display protected...\n  if(screen_mode)\n    return FLASH_THING_SMZX;\n\n  return FLASH_THING_NORMAL;\n}\n\n/**\n * Check if the current board element should be flashed. If it should be,\n * flash it. This should be performed after id_put.\n */\nstatic void flash_draw(struct editor_context *editor, struct board *cur_board,\n int x, int y, int array_x, int array_y, int flash_chr)\n{\n  int offset = array_y * cur_board->board_width + array_x;\n  enum thing id = (enum thing)cur_board->level_id[offset];\n\n  if(id >= editor->flash_start && id < editor->flash_start + editor->flash_len)\n  {\n    uint8_t color = get_id_color(cur_board, offset);\n    uint8_t chr = get_id_char(cur_board, offset);\n    enum flash_thing_type type;\n\n    type = flash_get_type(editor, cur_board, chr, color, flash_chr);\n    if(type != FLASH_THING_NORMAL)\n    {\n      int avg_luma = get_char_average_luma(chr, color, -1, flash_chr + PRO_CH);\n\n      if(!editor->flash_layer)\n      {\n        editor->flash_layer = create_layer(0, 0, 80, editor->screen_height,\n         LAYER_DRAWORDER_GAME_UI - 10, graphics.protected_pal_position + 5,\n         PRO_CH, true);\n        set_layer_mode(editor->flash_layer, 0);\n      }\n\n      if(type == FLASH_THING_CLOSE_CHAR)\n      {\n        // Blank out the robot char so there's less visual conflict...\n        select_layer(BOARD_LAYER);\n        draw_char_ext(' ', color, x, y, PRO_CH, 0);\n      }\n\n      color = (avg_luma < 128) ? 0x5F : 0x50;\n\n      select_layer(editor->flash_layer);\n      draw_char(flash_chr, color, x, y);\n    }\n    else\n    {\n      select_layer(BOARD_LAYER);\n      draw_char_ext(flash_chr, color, x, y, PRO_CH, 0);\n    }\n  }\n}\n\n/**\n * Draw the editing window (board version).\n */\nstatic void draw_edit_window(struct editor_context *editor)\n{\n  struct world *mzx_world = ((context *)editor)->world;\n  struct board *cur_board = mzx_world->current_board;\n  int viewport_width = 80;\n  int viewport_height = editor->screen_height;\n  int x, y;\n  int a_x, a_y;\n  int flash_char = 0;\n\n  if(editor->flashing)\n    flash_char = flash_init(editor);\n\n  blank_layers();\n\n  if(viewport_width > cur_board->board_width)\n    viewport_width = cur_board->board_width;\n\n  if(viewport_height > cur_board->board_height)\n    viewport_height = cur_board->board_height;\n\n  for(y = 0, a_y = editor->scroll_y; y < viewport_height; y++, a_y++)\n  {\n    for(x = 0, a_x = editor->scroll_x; x < viewport_width; x++, a_x++)\n    {\n      id_put(cur_board, x, y, a_x, a_y, a_x, a_y);\n\n      // Don't display flashing unless in board edit mode.\n      if(flash_char && editor->mode == EDIT_BOARD)\n        flash_draw(editor, cur_board, x, y, a_x, a_y, flash_char);\n    }\n  }\n  select_layer(UI_LAYER);\n\n  if(editor->flashing)\n    flash_done(editor);\n}\n\n/**\n * Draw the editing window (vlayer version).\n */\nstatic void draw_vlayer_window(struct editor_context *editor)\n{\n  struct world *mzx_world = ((context *)editor)->world;\n  int offset = editor->scroll_x + (editor->scroll_y * mzx_world->vlayer_width);\n  int skip;\n\n  char *vlayer_chars = mzx_world->vlayer_chars;\n  char *vlayer_colors = mzx_world->vlayer_colors;\n  int viewport_height = editor->screen_height;\n  int viewport_width = 80;\n  int x;\n  int y;\n\n  blank_layers();\n\n  select_layer(BOARD_LAYER);\n\n  if(viewport_width > mzx_world->vlayer_width)\n    viewport_width = mzx_world->vlayer_width;\n\n  if(viewport_height > mzx_world->vlayer_height)\n   viewport_height = mzx_world->vlayer_height;\n\n  skip = mzx_world->vlayer_width - viewport_width;\n\n  for(y = 0; y < viewport_height; y++)\n  {\n    for(x = 0; x < viewport_width; x++)\n    {\n      draw_char_ext(vlayer_chars[offset], vlayer_colors[offset], x, y, 0, 0);\n      offset++;\n    }\n    offset += skip;\n  }\n\n  select_layer(UI_LAYER);\n}\n\n/**\n * Draw the part of the editor viewport that is outside of the board/vlayer.\n */\nstatic void draw_out_of_bounds(int in_x, int in_y, int in_width, int in_height)\n{\n  int after_x;\n  int after_y;\n  int after_width;\n  int y;\n\n  if(in_x < 0 || in_x >= SCREEN_W || in_y < 0 || in_y >= SCREEN_H)\n    return;\n\n  if(in_x + in_width > SCREEN_W)\n    in_width = SCREEN_W - in_x;\n\n  if(in_y + in_height > SCREEN_H)\n    in_height = SCREEN_H - in_y;\n\n  after_x = in_x + in_width;\n  after_y = in_y + in_height;\n  after_width = SCREEN_W - after_x;\n\n  // Clear any lines prior to the first line.\n  for(y = 0; y < in_y; y++)\n    fill_line(SCREEN_W, 0, y, 177, 1);\n\n  // Clear any area before and after the in-bounds area.\n  for(; y < after_y; y++)\n  {\n    if(in_x > 0)\n      fill_line(in_x, 0, y, 177, 1);\n\n    if(after_width > 0)\n      fill_line(after_width, after_x, y, 177, 1);\n  }\n\n  // Clear any lines after the last line.\n  for(; y < SCREEN_H; y++)\n    fill_line(SCREEN_W, 0, y, 177, 1);\n}\n\n/**\n * Draw the editor viewport and handle some other things that need\n * to happen at draw time.\n */\nstatic boolean editor_draw(context *ctx)\n{\n  struct editor_context *editor = (struct editor_context *)ctx;\n  struct buffer_info *buffer = &(editor->buffer);\n  struct block_info *block = &(editor->block);\n  struct world *mzx_world = ctx->world;\n  struct board *cur_board = mzx_world->current_board;\n\n  int cursor_screen_x = editor->cursor_x - editor->scroll_x;\n  int cursor_screen_y = editor->cursor_y - editor->scroll_y;\n  int saved_overlay_mode = cur_board->overlay_mode;\n  int i;\n\n  if(get_fade_status())\n    insta_fadein();\n\n  m_show();\n  cursor_solid(cursor_screen_x, cursor_screen_y);\n\n  // Draw the board/vlayer\n  if(editor->mode == EDIT_BOARD)\n  {\n    cur_board->overlay_mode |= OVERLAY_FLAG_HIDE_OVERLAY;\n    draw_edit_window(editor);\n  }\n  else\n\n  if(editor->mode == EDIT_OVERLAY)\n  {\n    cur_board->overlay_mode = OVERLAY_ON;\n\n    if(!editor->show_board_under_overlay)\n      cur_board->overlay_mode |= OVERLAY_FLAG_HIDE_BOARD;\n\n    draw_edit_window(editor);\n  }\n  else // EDIT_VLAYER\n  {\n    draw_vlayer_window(editor);\n  }\n\n  draw_out_of_bounds(0, 0, editor->board_width, editor->board_height);\n\n  cur_board->overlay_mode = saved_overlay_mode;\n\n  // Highlight block for block selection\n  if(editor->cursor_mode == CURSOR_BLOCK_SELECT)\n  {\n    int block_screen_x = block->src_x - editor->scroll_x;\n    int block_screen_y = block->src_y - editor->scroll_y;\n    int start_x, start_y;\n    int block_width, block_height;\n\n    if(block_screen_x < 0)\n      block_screen_x = 0;\n\n    if(block_screen_y < 0)\n      block_screen_y = 0;\n\n    if(block_screen_x >= 80)\n      block_screen_x = 79;\n\n    if(block_screen_y >= editor->screen_height)\n      block_screen_y = editor->screen_height - 1;\n\n    if(block_screen_x < cursor_screen_x)\n    {\n      start_x = block_screen_x;\n      block_width = cursor_screen_x - block_screen_x + 1;\n    }\n    else\n    {\n      start_x = cursor_screen_x;\n      block_width = block_screen_x - cursor_screen_x + 1;\n    }\n\n    if(block_screen_y < cursor_screen_y)\n    {\n      start_y = block_screen_y;\n      block_height = cursor_screen_y - block_screen_y + 1;\n    }\n    else\n    {\n      start_y = cursor_screen_y;\n      block_height = block_screen_y - cursor_screen_y + 1;\n    }\n\n    for(i = 0; i < block_height; i++)\n    {\n      color_line(block_width, start_x, start_y + i, 0x9F);\n    }\n  }\n\n  if(mzx_world->debug_mode)\n  {\n    draw_debug_box(mzx_world, editor->debug_x, editor->screen_height - 6,\n      editor->cursor_x, editor->cursor_y, 0);\n  }\n\n  if(editor->modified != editor->modified_prev)\n  {\n    caption_set_modified(editor->modified);\n    editor->modified_prev = editor->modified;\n  }\n\n  // Fix invalid buffer params before they get drawn.\n  if(buffer->param == -1)\n    default_buffer(editor);\n\n  // Give the edit menu up-to-date information before it is drawn.\n  update_edit_menu(editor->edit_menu, editor->mode, editor->cursor_mode,\n   editor->cursor_x, editor->cursor_y, editor->screen_height,\n   &(editor->buffer), editor->use_default_color);\n\n  return true;\n}\n\n/**\n * Cancel drawing with the mouse.\n */\nstatic void cancel_mouse_draw(struct editor_context *editor)\n{\n  if(editor->continue_mouse_history)\n    update_undo_frame(editor->cur_history);\n\n  editor->continue_mouse_history = false;\n}\n\n/**\n * Draw at a position with the mouse.\n */\nstatic void mouse_draw_at_position(struct editor_context *editor, int x, int y)\n{\n  struct buffer_info *buffer = &(editor->buffer);\n  struct world *mzx_world = ((context *)editor)->world;\n  struct board *cur_board = mzx_world->current_board;\n  enum editor_mode mode = editor->mode;\n\n  if(!editor->continue_mouse_history)\n  {\n    // Add new frame\n    switch(mode)\n    {\n      case EDIT_BOARD:\n        add_board_undo_frame(mzx_world, editor->cur_history, buffer, x, y);\n        break;\n\n      case EDIT_OVERLAY:\n        add_layer_undo_pos_frame(editor->cur_history, cur_board->overlay,\n         cur_board->overlay_color, cur_board->board_width, buffer, x, y);\n        break;\n\n      case EDIT_VLAYER:\n        add_layer_undo_pos_frame(editor->cur_history,\n         mzx_world->vlayer_chars, mzx_world->vlayer_colors,\n         mzx_world->vlayer_width, buffer, x, y);\n        break;\n    }\n    editor->continue_mouse_history = true;\n  }\n  else\n  {\n    // Continue frame\n    add_undo_position(editor->cur_history, x, y);\n  }\n\n  // Tracking history separately, so don't pass it here.\n  buffer->param = place_current_at_xy(mzx_world, buffer, x, y, mode, NULL);\n  editor->mouse_last_x = x;\n  editor->mouse_last_y = y;\n  editor->modified = true;\n}\n\n/**\n * Draw to a position with the mouse, starting from the previous position\n * of the mouse if it is being dragged.\n */\nstatic void mouse_draw(struct editor_context *editor, int x, int y)\n{\n  if(editor->continue_mouse_history)\n  {\n    // Linear draw from previous position to current\n    int mouse_last_x = editor->mouse_last_x;\n    int mouse_last_y = editor->mouse_last_y;\n    int x_diff = x - mouse_last_x;\n    int y_diff = y - mouse_last_y;\n    int iter = MAX(abs(x_diff), abs(y_diff));\n    int i;\n\n    if(x == mouse_last_x && y == mouse_last_y)\n      return;\n\n    // Draw every position up to the current\n    for(i = 1; i < iter; i++)\n    {\n      mouse_draw_at_position(editor,\n        mouse_last_x + i * x_diff / iter,\n        mouse_last_y + i * y_diff / iter\n      );\n    }\n  }\n\n  // Draw at the current mouse position\n  mouse_draw_at_position(editor, x, y);\n}\n\n/**\n * Update the editor at the start of each frame.\n */\nstatic boolean editor_idle(context *ctx)\n{\n  const struct editor_config_info *editor_conf = get_editor_config();\n  struct editor_context *editor = (struct editor_context *)ctx;\n  struct world *mzx_world = ctx->world;\n\n  find_player(mzx_world);\n\n  // The user may be prompted to create a second board at the start\n  // of editing.\n  if(editor->first_board_prompt)\n  {\n    if(add_board(mzx_world, 1) >= 0)\n    {\n      // Also name the title board for disambiguation.\n      strcpy(mzx_world->name, NEW_WORLD_TITLE);\n      strcpy(mzx_world->board_list[0]->board_name, NEW_WORLD_TITLE);\n      mzx_world->first_board = 1;\n      editor_set_current_board(editor, 1, false);\n    }\n\n    editor->first_board_prompt = false;\n    return true;\n  }\n\n  // Save a backup world.\n  if(editor_conf->backup_count)\n  {\n    int ticks_delta = get_ticks() - editor->backup_timestamp;\n\n    if(ticks_delta > (editor_conf->backup_interval * 1000))\n    {\n      char backup_name_formatted[MAX_PATH];\n      int backup_num = editor->backup_num + 1;\n      int ret;\n\n      snprintf(backup_name_formatted, MAX_PATH, \"%s%d%s\",\n       editor_conf->backup_name, backup_num, editor_conf->backup_ext);\n\n      // Ensure any subdirectories exist before saving.\n      ret = path_create_parent_recursively(editor_conf->backup_name);\n      if(ret != PATH_CREATE_SUCCESS)\n        warn(\"Failed to create backup directory for backup_name='%s' (ret=%d)!\\n\",\n         editor_conf->backup_name, ret);\n\n      save_world(mzx_world, backup_name_formatted, false, MZX_VERSION);\n      editor->backup_num = backup_num % editor_conf->backup_count;\n      editor->backup_timestamp = get_ticks();\n    }\n  }\n\n  // Create undo history stacks if they don't currently exist.\n  if(!editor->board_history)\n  {\n    editor->board_history =\n     construct_board_undo_history(editor_conf->undo_history_size);\n  }\n\n  if(!editor->overlay_history)\n  {\n    editor->overlay_history =\n     construct_layer_undo_history(editor_conf->undo_history_size);\n  }\n\n  if(!editor->vlayer_history)\n  {\n    editor->vlayer_history =\n     construct_layer_undo_history(editor_conf->undo_history_size);\n  }\n\n  fix_history(editor);\n\n  // Preempt inputs to cancel flashing mode if active.\n  if(editor->flashing && !editor_conf->editor_show_thing_toggles &&\n   (get_mouse_status() || get_exit_status() || get_key(keycode_internal_wrt_numlock)))\n  {\n    editor->flashing = false;\n    return true;\n  }\n\n  // Cancel mouse draw if the left mouse button isn't being held.\n  if(editor->continue_mouse_history && !get_mouse_held(MOUSE_BUTTON_LEFT))\n  {\n    cancel_mouse_draw(editor);\n  }\n\n  return false;\n}\n\n/**\n * Handle mouse clicks and drags for the editor.\n */\nstatic boolean editor_mouse(context *ctx, int *key, int button, int x, int y)\n{\n  struct editor_context *editor = (struct editor_context *)ctx;\n  struct buffer_info *buffer = &(editor->buffer);\n  struct world *mzx_world = ctx->world;\n\n  // Allow keys to take precedence\n  if(*key)\n    return true;\n\n  if(y < editor->screen_height)\n  {\n    x += editor->scroll_x;\n    y += editor->scroll_y;\n\n    editor->cursor_x = x;\n    editor->cursor_y = y;\n    fix_cursor(editor);\n\n    if((editor->cursor_x == x) && (editor->cursor_y == y))\n    {\n      // Mouse is in-bounds\n\n      if(get_mouse_held(MOUSE_BUTTON_RIGHT))\n      {\n        cancel_mouse_draw(editor);\n        grab_at_xy(mzx_world, buffer, x, y, editor->mode);\n        return true;\n      }\n      else\n\n      if(get_mouse_held(MOUSE_BUTTON_LEFT))\n      {\n        // Mouse draw. Only start if this is an initial click to prevent it\n        // from carrying through other interfaces.\n        if(editor->continue_mouse_history || !get_mouse_drag())\n          mouse_draw(editor, x, y);\n        return true;\n      }\n    }\n    else\n\n    if(editor->continue_mouse_history && get_mouse_held(MOUSE_BUTTON_LEFT))\n    {\n      // We want mouse draw to continue even if the cursor goes out of bounds.\n      mouse_draw(editor, editor->cursor_x, editor->cursor_y);\n      return true;\n    }\n  }\n  return false;\n}\n\n/**\n * Handle joystick actions for the editor.\n * Currently support here is fairly weak and only exists to allow basic\n * features like moving the cursor, peeking at robots, or exiting.\n */\nstatic boolean editor_joystick(context *ctx, int *key, int action)\n{\n  struct editor_context *editor = (struct editor_context *)ctx;\n\n  switch(action)\n  {\n    // Place\n    case JOY_A:\n      *key = IKEY_SPACE;\n      return true;\n\n    // Delete\n    case JOY_B:\n      *key = IKEY_DELETE;\n      return true;\n\n    // Modify+Grab\n    case JOY_X:\n      *key = IKEY_RETURN;\n      return true;\n\n    // Grab\n    case JOY_Y:\n      *key = IKEY_INSERT;\n      return true;\n\n    // Board menu\n    case JOY_START:\n      *key = IKEY_b;\n      return true;\n\n    // Global info\n    case JOY_LSHOULDER:\n      *key = IKEY_g;\n      return true;\n\n    // Board info\n    case JOY_RSHOULDER:\n      *key = IKEY_i;\n      return true;\n\n    // Previous menu\n    case JOY_LTRIGGER:\n    {\n      if(editor->screen_height != EDIT_SCREEN_NORMAL)\n      {\n        editor->screen_height = EDIT_SCREEN_NORMAL;\n        fix_scroll(editor);\n      }\n      else\n        *key = IKEY_PAGEUP;\n\n      return true;\n    }\n\n    // Next menu\n    case JOY_RTRIGGER:\n    {\n      if(editor->screen_height != EDIT_SCREEN_NORMAL)\n      {\n        editor->screen_height = EDIT_SCREEN_NORMAL;\n        fix_scroll(editor);\n      }\n      else\n        *key = IKEY_PAGEDOWN;\n\n      return true;\n    }\n\n    // Defaults for select, arrows, etc.\n    default:\n    {\n      enum keycode ui_key = get_joystick_ui_key();\n      if(ui_key)\n      {\n        *key = ui_key;\n        return true;\n      }\n      break;\n    }\n  }\n  return false;\n}\n\n/**\n * Edit the parameter of the buffer and update it if the user doesn't cancel.\n */\nstatic void change_buffer_param_callback(context *ctx, context_callback_param *p)\n{\n  struct editor_context *editor = (struct editor_context *)ctx;\n  struct buffer_info *buffer = &(editor->buffer);\n\n  if(is_storageless(buffer->id) && editor->modify_param >= 0)\n  {\n    buffer->param = editor->modify_param;\n  }\n}\n\nstatic void change_buffer_param(struct editor_context *editor)\n{\n  change_param((context *)editor, &(editor->buffer), &(editor->modify_param));\n  context_callback((context *)editor, NULL, change_buffer_param_callback);\n}\n\n/**\n * Get the thing at the cursor position on the board and edit it. If it was\n * modified, place it back on the board.\n */\nstatic void get_modify_place_callback(context *ctx, context_callback_param *p)\n{\n  struct editor_context *editor = (struct editor_context *)ctx;\n  struct buffer_info *buffer = &(editor->buffer);\n  int new_param = editor->modify_param;\n\n  // Ignore non-storage objects with no change.\n  if((new_param >= 0 && new_param != buffer->param) || editor->modified_storage)\n  {\n    // Update the buffer for the new param.\n    buffer->param = new_param;\n\n    // Place the buffer back on the board/layer.\n    // Use place_current_at_xy wrapper that ensures the under is untouched.\n    new_param = replace_current_at_xy(ctx->world, buffer, editor->cursor_x,\n     editor->cursor_y, editor->mode, editor->cur_history);\n\n    // Placement might have required that we change the buffer param again.\n    buffer->param = new_param;\n\n    editor->modified = true;\n  }\n}\n\nstatic void get_modify_place(struct editor_context *editor)\n{\n  struct world *mzx_world = ((context *)editor)->world;\n  struct buffer_info *buffer = &(editor->buffer);\n\n  grab_at_xy(mzx_world, buffer, editor->cursor_x, editor->cursor_y,\n   editor->mode);\n\n  if(editor->mode == EDIT_BOARD)\n  {\n    // Board- param\n    editor->modified_storage = !is_storageless(buffer->id);\n    buffer->robot->xpos = editor->cursor_x;\n    buffer->robot->ypos = editor->cursor_y;\n\n    change_param((context *)editor, buffer, &(editor->modify_param));\n  }\n  else\n  {\n    // Overlay and vlayer- char\n    editor->modified_storage = false;\n    editor->modify_param = char_selection(buffer->param);\n  }\n  context_callback((context *)editor, NULL, get_modify_place_callback);\n}\n\n/**\n * Modifies the thing at a given position without altering the buffer.\n * This is done with a temporary buffer to avoid replicating much of the\n * code of place_current_at_xy.\n */\nstatic void modify_thing_callback(context *ctx, context_callback_param *p)\n{\n  struct editor_context *editor = (struct editor_context *)ctx;\n  struct buffer_info *temp_buffer = &(editor->temp_buffer);\n  int new_param = editor->modify_param;\n\n  // Ignore non-storage objects with no change.\n  if((new_param >= 0 && new_param != temp_buffer->param) ||\n   editor->modified_storage)\n  {\n    // Update the buffer for the new param.\n    temp_buffer->param = new_param;\n\n    // place_current_at_xy wrapper that ensures the under is untouched.\n    replace_current_at_xy(ctx->world, temp_buffer, editor->cursor_x,\n     editor->cursor_y, editor->mode, editor->cur_history);\n\n    editor->modified = true;\n  }\n  free_edit_buffer(temp_buffer);\n}\n\nstatic void modify_thing_at_cursor(struct editor_context *editor)\n{\n  struct world *mzx_world = ((context *)editor)->world;\n  struct buffer_info *temp_buffer = &(editor->temp_buffer);\n\n  memset(temp_buffer, 0, sizeof(struct buffer_info));\n  temp_buffer->robot = &(editor->temp_robot);\n  temp_buffer->scroll = &(editor->temp_scroll);\n  temp_buffer->sensor = &(editor->temp_sensor);\n\n  grab_at_xy(mzx_world, temp_buffer, editor->cursor_x, editor->cursor_y,\n   editor->mode);\n\n  if(editor->mode == EDIT_BOARD)\n  {\n    // Board- param\n    editor->modified_storage = !is_storageless(temp_buffer->id);\n    temp_buffer->robot->xpos = editor->cursor_x;\n    temp_buffer->robot->ypos = editor->cursor_y;\n\n    change_param((context *)editor, temp_buffer, &(editor->modify_param));\n  }\n  else\n  {\n    // Overlay and vlayer- char\n    editor->modified_storage = false;\n    editor->modify_param = char_selection(temp_buffer->param);\n  }\n  context_callback((context *)editor, NULL, modify_thing_callback);\n}\n\n/**\n * Editor keyhandler.\n */\nstatic boolean editor_key(context *ctx, int *key)\n{\n  struct editor_config_info *editor_conf = get_editor_config();\n  struct editor_context *editor = (struct editor_context *)ctx;\n  struct buffer_info *buffer = &(editor->buffer);\n  struct block_info *block = &(editor->block);\n  struct world *mzx_world = ctx->world;\n  struct board *cur_board = mzx_world->current_board;\n\n  // Temporary vars\n  struct buffer_info temp_buffer;\n  boolean exit_status;\n  int i;\n\n  memset(&temp_buffer, 0, sizeof(struct buffer_info));\n\n  // If the mouse is trying to draw something, stop it first.\n  if(editor->continue_mouse_history)\n    cancel_mouse_draw(editor);\n\n  // Exit event - ignore other input\n  exit_status = get_exit_status();\n  if(exit_status)\n    *key = 0;\n\n  // Saved positions\n  if((*key >= IKEY_0) && (*key <= IKEY_9))\n  {\n    char mesg[80];\n\n    int s_num = *key - IKEY_0;\n    struct saved_position *s;\n\n    if(editor->mode == EDIT_VLAYER)\n      s = &(editor_conf->vlayer_positions[s_num]);\n    else\n      s = &(editor_conf->saved_positions[s_num]);\n\n    if(get_ctrl_status(keycode_internal) &&\n     !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n    {\n      if(editor->mode == EDIT_VLAYER)\n      {\n        sprintf(mesg, \"Save vlayer @ %d,%d to pos %d?\",\n         editor->cursor_x, editor->cursor_y, s_num);\n      }\n      else\n      {\n        sprintf(mesg, \"Save '%s' @ %d,%d to pos %d?\",\n         mzx_world->current_board->board_name, editor->cursor_x,\n         editor->cursor_y, s_num);\n      }\n\n      if(!confirm(mzx_world, mesg))\n      {\n        s->board_id = mzx_world->current_board_id;\n        s->cursor_x = editor->cursor_x;\n        s->cursor_y = editor->cursor_y;\n        s->scroll_x = editor->scroll_x;\n        s->scroll_y = editor->scroll_y;\n        s->debug_x = editor->debug_x;\n        save_local_editor_config(editor_conf, curr_file);\n      }\n      return true;\n    }\n    else\n\n    if(get_alt_status(keycode_internal) &&\n     !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n    {\n      int s_board = s->board_id;\n\n      if(editor->mode == EDIT_VLAYER || (s_board < mzx_world->num_boards &&\n       mzx_world->board_list[s_board]))\n      {\n        if(editor->mode == EDIT_VLAYER)\n        {\n          sprintf(mesg, \"Go to vlayer @ %d,%d (pos %d)?\",\n           s->cursor_x, s->cursor_y, s_num);\n        }\n        else\n        {\n          sprintf(mesg, \"Go to '%s' @ %d,%d (pos %d)?\",\n           mzx_world->board_list[s_board]->board_name,\n           s->cursor_x, s->cursor_y, s_num);\n        }\n\n        if(!confirm(mzx_world, mesg))\n        {\n          editor->cursor_x = s->cursor_x;\n          editor->cursor_y = s->cursor_y;\n          editor->scroll_x = s->scroll_x;\n          editor->scroll_y = s->scroll_y;\n          editor->debug_x = s->debug_x;\n\n          if(editor->mode != EDIT_VLAYER && mzx_world->current_board_id != s_board)\n          {\n            editor_set_current_board(editor, s_board, true);\n          }\n          else\n          {\n            // The saved position might be out of bounds. Board changing\n            // fixes this separately.\n            fix_scroll(editor);\n          }\n        }\n      }\n      return true;\n    }\n  }\n\n  switch(*key)\n  {\n    case IKEY_UP:\n    {\n      int move_y = -1;\n\n      if(get_shift_status(keycode_internal))\n      {\n        // Move to adjacent board\n        if(cur_board->board_dir[0] != NO_BOARD && editor->mode != EDIT_VLAYER)\n          editor_set_current_board(editor, cur_board->board_dir[0], true);\n        return true;\n      }\n      else\n\n      if(get_alt_status(keycode_internal))\n        move_y = -10;\n\n      if(get_ctrl_status(keycode_internal) && block->selected)\n        move_y = -block->height;\n\n      move_edit_cursor(editor, 0, move_y);\n      return true;\n    }\n\n    case IKEY_DOWN:\n    {\n      int move_y = 1;\n\n      if(get_shift_status(keycode_internal))\n      {\n        // Move to adjacent board\n        if(cur_board->board_dir[1] != NO_BOARD && editor->mode != EDIT_VLAYER)\n          editor_set_current_board(editor, cur_board->board_dir[1], true);\n        return true;\n      }\n\n      if(get_alt_status(keycode_internal))\n        move_y = 10;\n\n      if(get_ctrl_status(keycode_internal) && block->selected)\n        move_y = block->height;\n\n      move_edit_cursor(editor, 0, move_y);\n      return true;\n    }\n\n    case IKEY_LEFT:\n    {\n      int move_x = -1;\n\n      if(get_shift_status(keycode_internal))\n      {\n        // Move to adjacent board\n        if(cur_board->board_dir[3] != NO_BOARD && editor->mode != EDIT_VLAYER)\n          editor_set_current_board(editor, cur_board->board_dir[3], true);\n        return true;\n      }\n\n      if(get_alt_status(keycode_internal))\n        move_x = -10;\n\n      if(get_ctrl_status(keycode_internal) && block->selected)\n        move_x = -block->width;\n\n      move_edit_cursor(editor, move_x, 0);\n      return true;\n    }\n\n    case IKEY_RIGHT:\n    {\n      int move_x = 1;\n\n      if(get_shift_status(keycode_internal))\n      {\n        // Move to adjacent board\n        if(cur_board->board_dir[2] != NO_BOARD && editor->mode != EDIT_VLAYER)\n          editor_set_current_board(editor, cur_board->board_dir[2], true);\n        return true;\n      }\n\n      if(get_alt_status(keycode_internal))\n        move_x = 10;\n\n      if(get_ctrl_status(keycode_internal) && block->selected)\n        move_x = block->width;\n\n      move_edit_cursor(editor, move_x, 0);\n      return true;\n    }\n\n    case IKEY_SPACE:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        /* There are two editor placement modes. This behavior can be\n         * toggled with the \"editor_space_toggles\" config.txt option.\n         *\n         * The legacy mode (editor_space_toggles = 1) allows users to\n         * toggle between their param buffer and space, under any\n         * situation.\n         *\n         * The new mode (editor_space_toggles = 0) disallows accidental\n         * rewriting of robots (you must press Delete instead) and otherwise\n         * implicitly writes the param. This makes placing new blocks with\n         * the same ID (but different colour, say) faster.\n         */\n        int offset =\n         editor->cursor_x + (editor->cursor_y * editor->board_width);\n\n        /* If it's not an overlay edit, and we're placing an identical\n         * block, and the user has selected legacy placement mode, toggle\n         * the current param type with SPACE.\n         */\n        int at_match = (editor->mode == EDIT_BOARD) &&\n         (buffer->id == cur_board->level_id[offset]) &&\n         (buffer->color == cur_board->level_color[offset]);\n\n        if(at_match && editor_conf->editor_space_toggles)\n        {\n          temp_buffer.id = SPACE;\n          temp_buffer.param = 0;\n          temp_buffer.color = 7;\n          place_current_at_xy(mzx_world, &temp_buffer,\n           editor->cursor_x, editor->cursor_y, editor->mode,\n           editor->cur_history);\n        }\n        else\n        {\n          /* If what we're trying to overwrite is a robot, allow the\n           * user to overwrite it only if legacy placement is enabled.\n           */\n          int at_robot = (editor->mode == EDIT_BOARD) &&\n           is_robot(cur_board->level_id[offset]);\n\n          if(!at_robot || editor_conf->editor_space_toggles)\n          {\n            buffer->param = place_current_at_xy(mzx_world, buffer,\n             editor->cursor_x, editor->cursor_y, editor->mode,\n             editor->cur_history);\n          }\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n      editor->modified = true;\n      return true;\n    }\n\n    case IKEY_BACKSPACE:\n    {\n      if(editor->cursor_mode == CURSOR_TEXT)\n      {\n        if(editor->cursor_x)\n          editor->cursor_x--;\n\n        if(((editor->cursor_x - editor->scroll_x) < 5) && editor->scroll_x)\n          editor->scroll_x--;\n\n        temp_buffer.id = TEXT_ID;\n        temp_buffer.param = ' ';\n        temp_buffer.color = buffer->color;\n        place_current_at_xy(mzx_world, &temp_buffer,\n         editor->cursor_x, editor->cursor_y, editor->mode,\n         editor->cur_history);\n\n        editor->modified = true;\n      }\n      else\n      {\n        int offset =\n         editor->cursor_x + (editor->cursor_y * editor->board_width);\n\n        if(editor->mode != EDIT_BOARD)\n        {\n          // Delete under cursor (overlay and vlayer)\n          temp_buffer.id = SPACE;\n          temp_buffer.param = 32;\n          temp_buffer.color = 7;\n          place_current_at_xy(mzx_world, &temp_buffer,\n           editor->cursor_x, editor->cursor_y, editor->mode,\n           editor->cur_history);\n\n          editor->modified = true;\n        }\n        else\n\n        if(cur_board->level_id[offset] != PLAYER)\n        {\n          // Remove from top (board only)\n          // Actually just places the under on top, but it's functionally\n          // equivalent.\n          enum thing id = cur_board->level_under_id[offset];\n          int color = cur_board->level_under_color[offset];\n          int param = cur_board->level_under_param[offset];\n\n          add_block_undo_frame(mzx_world, editor->board_history,\n           cur_board, offset, 1, 1);\n\n          place_at_xy(mzx_world, id, color, param,\n           editor->cursor_x, editor->cursor_y);\n\n          update_undo_frame(editor->board_history);\n          editor->modified = true;\n        }\n      }\n      return true;\n    }\n\n    case IKEY_TAB:\n    {\n      if(!get_alt_status(keycode_internal))\n      {\n        if(editor->cursor_mode)\n        {\n          editor->cursor_mode = CURSOR_PLACE;\n          block->selected = false;\n        }\n        else\n        {\n          buffer->param = place_current_at_xy(mzx_world, buffer,\n           editor->cursor_x, editor->cursor_y, editor->mode,\n           editor->cur_history);\n\n          editor->cursor_mode = CURSOR_DRAW;\n          editor->modified = true;\n        }\n      }\n\n      return true;\n    }\n\n    case IKEY_INSERT:\n    {\n      grab_at_xy(mzx_world, buffer, editor->cursor_x, editor->cursor_y,\n       editor->mode);\n      return true;\n    }\n\n    case IKEY_DELETE:\n    {\n      if(editor->cursor_mode == CURSOR_TEXT)\n      {\n        temp_buffer.id = TEXT_ID;\n        temp_buffer.param = ' ';\n        temp_buffer.color = buffer->color;\n        place_current_at_xy(mzx_world, &temp_buffer,\n         editor->cursor_x, editor->cursor_y, editor->mode,\n         editor->cur_history);\n      }\n      else\n      {\n        int new_param = 0;\n\n        // On layers, the blank char is a space char\n        if(editor->mode != EDIT_BOARD)\n          new_param = 32;\n\n        temp_buffer.id = SPACE;\n        temp_buffer.param = new_param;\n        temp_buffer.color = 7;\n        place_current_at_xy(mzx_world, &temp_buffer,\n         editor->cursor_x, editor->cursor_y, editor->mode,\n         editor->cur_history);\n      }\n      editor->modified = true;\n      return true;\n    }\n\n    case IKEY_HOME:\n    {\n      editor->cursor_x = 0;\n      editor->cursor_y = 0;\n      fix_scroll(editor);\n      return true;\n    }\n\n    case IKEY_END:\n    {\n      editor->cursor_x = editor->board_width - 1;\n      editor->cursor_y = editor->board_height - 1;\n      fix_scroll(editor);\n      return true;\n    }\n\n    case IKEY_ESCAPE:\n    {\n      if(editor->cursor_mode)\n      {\n        editor->cursor_mode = CURSOR_PLACE;\n        block->selected = false;\n        key = 0;\n        return true;\n      }\n      else\n\n      if(editor->mode)\n      {\n        // Exit overlay/vlayer editing\n        set_editor_mode(editor, EDIT_BOARD);\n        synchronize_board_values(editor);\n        fix_scroll(editor);\n        key = 0;\n        return true;\n      }\n      else\n\n      if(editor->flashing)\n      {\n        editor->flashing = false;\n        return true;\n      }\n\n      // Defer to exit handler.\n      exit_status = true;\n      break;\n    }\n\n    case IKEY_F1:\n    {\n      if(get_shift_status(keycode_internal))\n      {\n        // Show invisible walls\n        if(editor->mode == EDIT_BOARD)\n        {\n          flash_thing(editor, INVIS_WALL, INVIS_WALL, 178, 176);\n        }\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_F2:\n    {\n      // Ignore Alt+F2, Ctrl+F2\n      if(get_alt_status(keycode_internal) || get_ctrl_status(keycode_internal))\n        break;\n\n      if(get_shift_status(keycode_internal))\n      {\n        // Show robots\n        if(editor->mode == EDIT_BOARD)\n        {\n          flash_thing(editor, ROBOT_PUSHABLE, ROBOT, '!', 0);\n        }\n      }\n      else\n      {\n        // Toggle text mode\n        if(editor->cursor_mode != CURSOR_TEXT)\n        {\n          block->selected = false;\n          editor->cursor_mode = CURSOR_TEXT;\n          editor->text_start_x = editor->cursor_x;\n        }\n        else\n        {\n          editor->cursor_mode = CURSOR_PLACE;\n        }\n      }\n      return true;\n    }\n\n    // Terrain\n    case IKEY_F3:\n    {\n      if(editor->mode == EDIT_BOARD)\n      {\n        if(get_shift_status(keycode_internal))\n        {\n          // Show fakes\n          flash_thing(editor, FAKE, THICK_WEB, '#', 177);\n        }\n        else\n        {\n          thing_menu(ctx, THING_MENU_TERRAIN, buffer,\n           editor->use_default_color, editor->cursor_x, editor->cursor_y,\n           editor->board_history);\n          editor->modified = true;\n        }\n      }\n      return true;\n    }\n\n    // Item\n    case IKEY_F4:\n    {\n      // ALT+F4 - do nothing.\n      if(get_alt_status(keycode_internal))\n        break;\n\n      if(editor->mode == EDIT_BOARD)\n      {\n        if(get_shift_status(keycode_internal))\n        {\n          // Show spaces\n          flash_thing(editor, SPACE, SPACE, 'O', '*');\n        }\n        else\n        {\n          thing_menu(ctx, THING_MENU_ITEMS, buffer,\n           editor->use_default_color, editor->cursor_x, editor->cursor_y,\n           editor->board_history);\n          editor->modified = true;\n        }\n      }\n      return true;\n    }\n\n    // Creature\n    case IKEY_F5:\n    {\n      if(editor->mode == EDIT_BOARD)\n      {\n        thing_menu(ctx, THING_MENU_CREATURES, buffer,\n         editor->use_default_color, editor->cursor_x, editor->cursor_y,\n         editor->board_history);\n        editor->modified = true;\n      }\n      return true;\n    }\n\n    // Puzzle\n    case IKEY_F6:\n    {\n      if(editor->mode == EDIT_BOARD)\n      {\n        thing_menu(ctx, THING_MENU_PUZZLE, buffer,\n         editor->use_default_color, editor->cursor_x, editor->cursor_y,\n         editor->board_history);\n        editor->modified = true;\n      }\n      return true;\n    }\n\n    // Transport\n    case IKEY_F7:\n    {\n      if(editor->mode == EDIT_BOARD)\n      {\n        thing_menu(ctx, THING_MENU_TRANSPORT, buffer,\n         editor->use_default_color, editor->cursor_x, editor->cursor_y,\n         editor->board_history);\n        editor->modified = true;\n      }\n      return true;\n    }\n\n    // Element\n    case IKEY_F8:\n    {\n      if(editor->mode == EDIT_BOARD)\n      {\n        thing_menu(ctx, THING_MENU_ELEMENTS, buffer,\n         editor->use_default_color, editor->cursor_x, editor->cursor_y,\n         editor->board_history);\n        editor->modified = true;\n      }\n      return true;\n    }\n\n    // Misc\n    case IKEY_F9:\n    {\n      if(editor->mode == EDIT_BOARD)\n      {\n        thing_menu(ctx, THING_MENU_MISC, buffer,\n         editor->use_default_color, editor->cursor_x, editor->cursor_y,\n         editor->board_history);\n        editor->modified = true;\n      }\n      return true;\n    }\n\n    // Object\n    case IKEY_F10:\n    {\n      if(editor->mode == EDIT_BOARD)\n      {\n        thing_menu(ctx, THING_MENU_OBJECTS, buffer,\n         editor->use_default_color, editor->cursor_x, editor->cursor_y,\n         editor->board_history);\n        editor->modified = true;\n      }\n      return true;\n    }\n\n    case IKEY_F11:\n    {\n      if(get_alt_status(keycode_internal))\n      {\n        // Robot debugger configuration\n        debug_robot_config(mzx_world);\n      }\n      else\n      {\n        // SMZX Mode\n        int selected_mode = select_screen_mode(mzx_world);\n\n        if(selected_mode >= 0)\n        {\n          set_screen_mode(selected_mode);\n\n          editor->modified = true;\n        }\n      }\n      return true;\n    }\n\n    case IKEY_F12:\n    {\n      // don't block in text mode :(\n      break;\n    }\n\n    case IKEY_8:\n    case IKEY_KP_MULTIPLY:\n    {\n      if(editor->cursor_mode == CURSOR_TEXT)\n      {\n        place_text(editor);\n      }\n      else\n\n      if(get_shift_status(keycode_internal) ||\n       (*key == IKEY_KP_MULTIPLY))\n      {\n        cur_board->mod_playing[0] = '*';\n        cur_board->mod_playing[1] = 0;\n        fix_mod(editor);\n        edit_menu_show_board_mod(editor->edit_menu);\n\n        editor->modified = true;\n      }\n\n      return true;\n    }\n\n    case IKEY_KP_MINUS:\n    case IKEY_MINUS:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        // Previous board\n        // Doesn't make sense on the vlayer.\n        if(mzx_world->current_board_id > 0 && editor->mode != EDIT_VLAYER)\n          editor_set_current_board(editor, mzx_world->current_board_id - 1, true);\n      }\n      else\n        place_text(editor);\n\n      return true;\n    }\n\n    case IKEY_KP_PLUS:\n    case IKEY_EQUALS:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        // Next board\n        // Doesn't make sense on the vlayer.\n        if(mzx_world->current_board_id < mzx_world->num_boards - 1\n         && editor->mode != EDIT_VLAYER)\n          editor_set_current_board(editor, mzx_world->current_board_id + 1, true);\n      }\n      else\n        place_text(editor);\n\n      return true;\n    }\n\n    case IKEY_RETURN:\n    {\n      if(editor->cursor_mode == CURSOR_BLOCK_SELECT)\n      {\n        // Block selection\n        if(!select_block_command(mzx_world, block, editor->mode))\n          return true;\n\n        // Correct the coordinates so x,y are the lowest values\n        if(block->src_x > editor->cursor_x)\n        {\n          block->width = block->src_x - editor->cursor_x + 1;\n          block->src_x = editor->cursor_x;\n        }\n        else\n        {\n          block->width = editor->cursor_x - block->src_x + 1;\n        }\n\n        if(block->src_y > editor->cursor_y)\n        {\n          block->height = block->src_y - editor->cursor_y + 1;\n          block->src_y = editor->cursor_y;\n        }\n        else\n        {\n          block->height = editor->cursor_y - block->src_y + 1;\n        }\n\n        // Several commands have the same source and destination.\n        block->src_board = cur_board;\n        block->dest_board = cur_board;\n        block->dest_x = block->src_x;\n        block->dest_y = block->src_y;\n\n        if(block->dest_mode == EDIT_OVERLAY && !cur_board->overlay_mode)\n        {\n          error(\"Overlay mode is not on (see Board Info)\",\n           ERROR_T_WARNING, ERROR_OPT_OK, 0x1103);\n          editor->cursor_mode = CURSOR_PLACE;\n          block->selected = false;\n          return true;\n        }\n\n        // Some of these are done automatically\n        switch(block->command)\n        {\n          case BLOCK_CMD_NONE:\n          {\n            editor->cursor_mode = CURSOR_PLACE;\n            block->selected = false;\n            break;\n          }\n\n          case BLOCK_CMD_COPY:\n          {\n            if(block->dest_mode != block->src_mode)\n            {\n              set_editor_mode(editor, block->dest_mode);\n              synchronize_board_values(editor);\n              fix_scroll(editor);\n            }\n          }\n\n          /* fall-through */\n\n          case BLOCK_CMD_COPY_REPEATED:\n          case BLOCK_CMD_MOVE:\n          {\n            editor->cursor_mode = CURSOR_BLOCK_PLACE;\n            break;\n          }\n\n          case BLOCK_CMD_CLEAR:\n          case BLOCK_CMD_MIRROR:\n          case BLOCK_CMD_FLIP:\n          case BLOCK_CMD_PAINT:\n          {\n            do_block_command(mzx_world, block, editor->cur_history,\n             editor->mzm_name_buffer, buffer->color);\n\n            block->selected = false;\n            editor->cursor_mode = CURSOR_PLACE;\n            editor->modified = true;\n            break;\n          }\n\n          case BLOCK_CMD_SAVE_MZM:\n          {\n            // Save as MZM\n            char export_name[MAX_PATH];\n            strcpy(export_name, editor->mzm_name_buffer);\n\n            if(!new_file(mzx_world, mzm_ext, \".mzm\", export_name,\n             \"Export MZM\", ALLOW_ALL_DIRS))\n            {\n              enum mzm_save_mode save_mode;\n\n              switch(editor->mode)\n              {\n                default:\n                case EDIT_BOARD:\n                  save_mode = MZM_BOARD_TO_BOARD_STORAGE;\n                  break;\n\n                case EDIT_OVERLAY:\n                  save_mode = MZM_OVERLAY_TO_LAYER_STORAGE;\n                  break;\n\n                case EDIT_VLAYER:\n                  save_mode = MZM_VLAYER_TO_LAYER_STORAGE;\n                  break;\n              }\n              strcpy(editor->mzm_name_buffer, export_name);\n              save_mzm(mzx_world, editor->mzm_name_buffer, block->src_x,\n               block->src_y, block->width, block->height, save_mode, false);\n            }\n            block->selected = false;\n            editor->cursor_mode = CURSOR_PLACE;\n            break;\n          }\n\n          case BLOCK_CMD_SAVE_ANSI:\n          {\n            // Save as ANSi.\n            char title[ARRAY_SIZE(editor->ansi_save_title)];\n            char author[ARRAY_SIZE(editor->ansi_save_author)];\n            char export_name[MAX_PATH];\n            int text_only = editor->ansi_save_text_only;\n            int doorway_mode = editor->ansi_save_doorway_mode;\n            static const char *radio_strings[] =\n            {\n              \"Save ANSi\",\n              \"Save TXT\"\n            };\n            static const char *checkbox_strings[] =\n            {\n              \"Doorway mode\"\n            };\n            struct element *elements[] =\n            {\n              construct_radio_button(4, 19, radio_strings, ARRAY_SIZE(radio_strings),\n               9, &text_only),\n              construct_input_box(23, 19, \"Title (ANSi):\", ARRAY_SIZE(title) - 1, title),\n              construct_input_box(22, 20, \"Author (ANSi):\", ARRAY_SIZE(author) - 1, author),\n              construct_check_box(58, 20, checkbox_strings, ARRAY_SIZE(checkbox_strings),\n               12, &doorway_mode),\n            };\n\n            memcpy(export_name, editor->mzm_name_buffer, MAX_PATH);\n            memcpy(title, editor->ansi_save_title, ARRAY_SIZE(title));\n            memcpy(author, editor->ansi_save_author, ARRAY_SIZE(author));\n\n            if(!file_manager(mzx_world, ans_ext, NULL, export_name,\n             \"Export ANSi or TXT\", 1, 1, elements, ARRAY_SIZE(elements), 3))\n            {\n              path_force_ext(export_name, sizeof(export_name),\n               text_only ? \".txt\" : \".ans\");\n              memcpy(editor->mzm_name_buffer, export_name, MAX_PATH);\n              memcpy(editor->ansi_save_title, title, ARRAY_SIZE(title));\n              memcpy(editor->ansi_save_author, author, ARRAY_SIZE(author));\n              editor->ansi_save_text_only = text_only;\n              editor->ansi_save_doorway_mode = doorway_mode;\n\n              export_ansi(mzx_world, export_name, editor->mode, block->src_x,\n               block->src_y, block->width, block->height, text_only, doorway_mode,\n               title, author);\n            }\n            block->selected = false;\n            editor->cursor_mode = CURSOR_PLACE;\n            break;\n          }\n\n          case BLOCK_CMD_LOAD_MZM:\n          case BLOCK_CMD_LOAD_ANSI:\n          {\n            // ignore\n            break;\n          }\n        }\n      }\n      else\n\n      if(editor->cursor_mode == CURSOR_BLOCK_PLACE ||\n       editor->cursor_mode == CURSOR_MZM_PLACE ||\n       editor->cursor_mode == CURSOR_ANSI_PLACE)\n      {\n        // Block placement\n        block->dest_board = cur_board;\n        block->dest_x = editor->cursor_x;\n        block->dest_y = editor->cursor_y;\n        block->convert_id = CUSTOM_BLOCK;\n\n        if(block->dest_mode == EDIT_BOARD && block->src_mode != EDIT_BOARD)\n        {\n          block->convert_id = layer_to_board_object_type(mzx_world);\n          if(block->convert_id == NO_ID)\n            break;\n        }\n\n        do_block_command(mzx_world, block, editor->cur_history,\n         editor->mzm_name_buffer, 0);\n\n        // Reset the block mode unless this is repeat copy\n        if(block->command != BLOCK_CMD_COPY_REPEATED)\n        {\n          block->selected = false;\n          editor->cursor_mode = CURSOR_PLACE;\n        }\n\n        editor->modified = true;\n      }\n      else\n\n      if(editor->cursor_mode == CURSOR_TEXT)\n      {\n        // Go to next line\n        editor->cursor_x = editor->text_start_x;\n\n        if(editor->cursor_y < (editor->board_height - 1))\n          editor->cursor_y++;\n\n        fix_scroll(editor);\n      }\n\n      // Normal/draw - modify+get\n      else\n      {\n        get_modify_place(editor);\n      }\n      return true;\n    }\n\n    case IKEY_a:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        int charset_load = choose_char_set(mzx_world);\n\n        switch(charset_load)\n        {\n          case 0:\n          {\n            ec_load_mzx();\n            break;\n          }\n\n          case 1:\n          {\n            ec_load_ascii();\n            break;\n          }\n\n          case 2:\n          {\n            ec_load_smzx();\n            break;\n          }\n\n          case 3:\n          {\n            ec_load_smzx2();\n            break;\n          }\n\n          case 4:\n          {\n            ec_load_blank();\n            break;\n          }\n        }\n\n        editor->modified = true;\n      }\n      else\n\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        // Add board, find first free\n        for(i = 0; i < mzx_world->num_boards; i++)\n        {\n          if(mzx_world->board_list[i] == NULL)\n            break;\n        }\n\n        if(i < MAX_BOARDS)\n        {\n          if(add_board(mzx_world, i) >= 0)\n            editor_set_current_board(editor, i, false);\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_b:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          block->src_x = editor->cursor_x;\n          block->src_y = editor->cursor_y;\n          editor->cursor_mode = CURSOR_BLOCK_SELECT;\n        }\n        else\n        {\n          int change_to_board =\n           choose_board(mzx_world, mzx_world->current_board_id,\n           \"Select current board:\", 0);\n\n          editor_set_current_board(editor, change_to_board, true);\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_c:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        cursor_off();\n        if(get_alt_status(keycode_internal))\n        {\n          char_editor(mzx_world);\n          editor->modified = true;\n        }\n        else\n        {\n          int new_color = color_selection(buffer->color, 0);\n          if(new_color >= 0)\n            buffer->color = new_color;\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_d:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          // Toggle default built-in colors\n          editor->use_default_color ^= 1;\n        }\n        else\n        {\n          int del_board =\n           choose_board(mzx_world, mzx_world->current_board_id,\n           \"Delete board:\", 0);\n\n          if((del_board > 0) &&\n           !confirm(mzx_world, \"DELETE BOARD - Are you sure?\"))\n          {\n            int current_board_id = mzx_world->current_board_id;\n            clear_board(mzx_world->board_list[del_board]);\n            mzx_world->board_list[del_board] = NULL;\n\n            // Remove null board from list\n            optimize_null_boards(mzx_world);\n\n            if(del_board == current_board_id)\n            {\n              mzx_world->current_board = NULL;\n              editor_set_current_board(editor, 0, true);\n            }\n            else\n            {\n              synchronize_board_values(editor);\n            }\n          }\n        }\n\n        editor->modified = true;\n      }\n      else\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_e:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          palette_editor(ctx);\n          editor->modified = true;\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n\n      return true;\n    }\n\n    case IKEY_f:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          sfx_edit(mzx_world);\n          editor->modified = true;\n        }\n        else\n        {\n          fill_area(mzx_world, buffer, editor->cursor_x, editor->cursor_y,\n           editor->mode, editor->cur_history);\n\n          editor->modified = true;\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n\n      return true;\n    }\n\n    case IKEY_g:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        if(get_ctrl_status(keycode_internal))\n        {\n          // Goto board position\n          if(!board_goto(mzx_world, editor->mode,\n           &(editor->cursor_x), &(editor->cursor_y)))\n          {\n            // This will get bounded by fix_scroll\n            editor->scroll_x = editor->cursor_x - 39;\n            editor->scroll_y = editor->cursor_y - editor->screen_height / 2;\n            fix_scroll(editor);\n          }\n          return true;\n        }\n        else\n\n        if(get_alt_status(keycode_internal))\n        {\n          edit_global_robot(ctx);\n        }\n\n        else\n        {\n          global_info(ctx);\n        }\n\n        editor->modified = true;\n      }\n      else\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_h:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        if(editor->screen_height == EDIT_SCREEN_NORMAL)\n        {\n          editor->screen_height = EDIT_SCREEN_MINIMAL;\n        }\n        else\n        {\n          editor->screen_height = EDIT_SCREEN_NORMAL;\n        }\n        fix_scroll(editor);\n      }\n      else\n\n      if(editor->cursor_mode == CURSOR_TEXT)\n      {\n        place_text(editor);\n      }\n\n      return true;\n    }\n\n    case IKEY_i:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        int import_number = import_type(mzx_world);\n        if(import_number >= 0)\n        {\n          char import_name[MAX_PATH];\n          import_name[0] = 0;\n\n          switch(import_number)\n          {\n            case 0:\n            {\n              strcpy(import_name, editor->mzb_name_buffer);\n              if(!choose_file(mzx_world, mzb_ext, import_name,\n               \"Choose board to import\", ALLOW_ALL_DIRS))\n              {\n                strcpy(editor->mzb_name_buffer, import_name);\n                replace_current_board(mzx_world, import_name);\n\n                // Exit vlayer mode if necessary.\n                set_editor_mode(editor, EDIT_BOARD);\n                synchronize_board_values(editor);\n                fix_world_title(editor);\n                fix_scroll(editor);\n                fix_caption(editor);\n\n                // This check works because cur_board is still the old pointer.\n                if(block->selected && (block->src_board == cur_board))\n                {\n                  block->selected = false;\n                  editor->cursor_mode = CURSOR_PLACE;\n                }\n\n                // Need the new pointer here, though.\n                cur_board = mzx_world->current_board;\n                if(strcmp(cur_board->mod_playing, \"*\") &&\n                 strcasecmp(cur_board->mod_playing,\n                 mzx_world->real_mod_playing))\n                  fix_mod(editor);\n\n                clear_board_history(editor);\n                clear_overlay_history(editor);\n\n                editor->modified = true;\n              }\n              break;\n            }\n\n            case 1:\n            {\n              // Character set\n              int char_offset = 0;\n              struct element *elements[] =\n              {\n                construct_number_box(21, 20, \"Offset:  \",\n                 0, (PRO_CH - 1), NUMBER_BOX, &char_offset),\n              };\n\n              strcpy(import_name, editor->chr_name_buffer);\n              if(!file_manager(mzx_world, chr_ext, NULL, import_name,\n               \"Choose character set to import\", ALLOW_ALL_DIRS, NO_NEW_FILES,\n               elements, ARRAY_SIZE(elements), 2))\n              {\n                strcpy(editor->chr_name_buffer, import_name);\n                ec_load_set_var(import_name, char_offset, MZX_VERSION);\n              }\n              editor->modified = true;\n              break;\n            }\n\n            case 2:\n            {\n              // World file\n              if(!choose_file(mzx_world, world_ext, import_name,\n               \"Choose world to import\", ALLOW_ALL_DIRS))\n              {\n                // FIXME: Check retval?\n                append_world(mzx_world, import_name);\n              }\n\n              if(block->selected)\n              {\n                block->selected = false;\n                editor->cursor_mode = CURSOR_PLACE;\n              }\n\n              editor->modified = true;\n              break;\n            }\n\n            case 3:\n            {\n              // Palette (let the palette editor handle this one)\n              import_palette(ctx);\n              break;\n            }\n\n            case 4:\n            {\n              // Sound effects\n              import_sfx(ctx, &editor->modified);\n              break;\n            }\n\n            case 5:\n            {\n              // ANSi file.\n              int wrap_width = editor->ansi_line_wrap_column;\n              int enable_wrap = editor->ansi_line_wrap;\n              static const char *labels[] = { \"Force wrap\" };\n              struct element *elements[] =\n              {\n                construct_check_box(14, 20, labels, 1, strlen(labels[0]), &enable_wrap),\n                construct_number_box(37, 20, \"Wrap width: \", 1, 32767, NUMBER_BOX, &wrap_width)\n              };\n\n              strcpy(import_name, editor->mzm_name_buffer);\n              if(!file_manager(mzx_world, ans_ext, NULL, import_name,\n               \"Choose ANSi or TXT file to import\", 1, 0,\n               elements, ARRAY_SIZE(elements), 2))\n              {\n                int width = -1;\n                int height = -1;\n                editor->ansi_line_wrap = enable_wrap;\n                editor->ansi_line_wrap_column = wrap_width;\n\n                if(validate_ansi(import_name, enable_wrap ? wrap_width : -1,\n                 &width, &height) && width > 0 && height > 0)\n                {\n                  strcpy(editor->mzm_name_buffer, import_name);\n                  editor->cursor_mode = CURSOR_ANSI_PLACE;\n                  block->command = BLOCK_CMD_LOAD_ANSI;\n                  block->selected = true;\n                  block->src_board = NULL;\n                  block->src_mode = EDIT_OVERLAY; // Force ID selection for board.\n                  block->dest_mode = editor->mode;\n                  block->width = width;\n                  block->height = height;\n                }\n              }\n              break;\n            }\n\n            case 6:\n            {\n              // MZM file\n              strcpy(import_name, editor->mzm_name_buffer);\n              if(!choose_file(mzx_world, mzm_ext, import_name,\n               \"Choose image file to import\", ALLOW_ALL_DIRS))\n              {\n                struct mzm_header mzm;\n                if(load_mzm_header(import_name, &mzm))\n                {\n                  strcpy(editor->mzm_name_buffer, import_name);\n                  editor->cursor_mode = CURSOR_MZM_PLACE;\n                  block->command = BLOCK_CMD_LOAD_MZM;\n                  block->selected = true;\n                  block->src_board = NULL;\n                  block->src_mode = editor->mode;\n                  block->dest_mode = editor->mode;\n                  block->width = mzm.width;\n                  block->height = mzm.height;\n\n                  // Enable conversion ID selection menu upon placement.\n                  if(editor->mode == EDIT_BOARD &&\n                   mzm.storage_mode == MZM_STORAGE_MODE_LAYER)\n                    block->src_mode = EDIT_OVERLAY;\n                }\n              }\n              break;\n            }\n          }\n        }\n      }\n      else\n\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        if(editor->mode != EDIT_VLAYER)\n        {\n          board_info(mzx_world);\n\n          synchronize_board_values(editor);\n          fix_world_title(editor);\n          fix_caption(editor);\n\n          if(!cur_board->overlay_mode && editor->mode == EDIT_OVERLAY)\n          {\n            clear_overlay_history(editor);\n            editor->mode = EDIT_BOARD;\n          }\n\n          editor->modified = true;\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_l:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        char test_wav[MAX_PATH] = { 0, };\n\n        if(!choose_file(mzx_world, sam_ext, test_wav,\n         \"Choose a wav file\", ALLOW_ALL_DIRS))\n        {\n          audio_play_sample(test_wav, false, 0);\n        }\n      }\n      else\n\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        if(!editor->modified || !confirm(mzx_world,\n         \"Load: World has not been saved, are you sure?\"))\n        {\n          char prev_world[MAX_PATH];\n          char load_world[MAX_PATH];\n          strcpy(prev_world, editor->current_world);\n          strcpy(load_world, editor->current_world);\n\n          if(!choose_file_ch(mzx_world, world_ext, load_world,\n           \"Load World\", ALLOW_ALL_DIRS))\n          {\n            // Load world curr_file\n            strcpy(editor->current_world, load_world);\n            if(!editor_reload_world(editor, load_world))\n            {\n              strcpy(editor->current_world, prev_world);\n\n              fix_mod(editor);\n              return true;\n            }\n            strcpy(curr_file, load_world);\n\n            mzx_world->current_board_id = mzx_world->first_board;\n            set_current_board_ext(mzx_world,\n             mzx_world->board_list[mzx_world->current_board_id]);\n            cur_board = mzx_world->current_board;\n\n            insta_fadein();\n\n            // Switch to board editing if necessary.\n            if(!cur_board->overlay_mode || editor->mode == EDIT_VLAYER)\n              set_editor_mode(editor, EDIT_BOARD);\n\n            synchronize_board_values(editor);\n            fix_scroll(editor);\n            fix_mod(editor);\n            fix_caption(editor);\n\n            if(block->selected)\n            {\n              block->selected = false;\n              editor->cursor_mode = CURSOR_PLACE;\n            }\n\n            clear_board_history(editor);\n            clear_overlay_history(editor);\n            clear_vlayer_history(editor);\n\n            editor->modified = false;\n          }\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_m:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        // Modify\n        if(get_alt_status(keycode_internal))\n        {\n          modify_thing_at_cursor(editor);\n        }\n        else\n\n        // Move current board\n        // Doesn't make sense on the vlayer\n        if(editor->mode != EDIT_VLAYER)\n        {\n          int new_position;\n\n          if(mzx_world->current_board_id == 0)\n            return true;\n\n          new_position =\n           choose_board(mzx_world, mzx_world->current_board_id,\n           \"Move board to position:\", 0);\n\n          if((new_position > 0) && (new_position < mzx_world->num_boards) &&\n           (new_position != mzx_world->current_board_id))\n          {\n            move_current_board(mzx_world, new_position);\n            editor_set_current_board(editor, new_position, true);\n            editor->modified = true;\n          }\n        }\n      }\n      else\n        place_text(editor);\n\n      return true;\n    }\n\n    case IKEY_n:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        // Board mod\n        // Doesn't make sense on the vlayer\n        if(get_alt_status(keycode_internal) && editor->mode != EDIT_VLAYER)\n        {\n          if(!cur_board->mod_playing[0])\n          {\n            char new_mod[MAX_PATH] = { 0 };\n\n            if(!choose_file(mzx_world, mod_ext, new_mod,\n             \"Choose a module file\", ALLOW_SUBDIRS))\n            {\n              const char *ext_pos = new_mod + strlen(new_mod) - 4;\n              if(ext_pos >= new_mod && !strcasecmp(ext_pos, \".WAV\"))\n              {\n                error(\"Using OGG instead of WAV is recommended.\",\n                 ERROR_T_WARNING, ERROR_OPT_OK, 0xc0c5);\n              }\n\n              strcpy(cur_board->mod_playing, new_mod);\n              strcpy(mzx_world->real_mod_playing, new_mod);\n              fix_mod(editor);\n              edit_menu_show_board_mod(editor->edit_menu);\n            }\n          }\n          else\n          {\n            cur_board->mod_playing[0] = 0;\n            mzx_world->real_mod_playing[0] = 0;\n            fix_mod(editor);\n            edit_menu_show_board_mod(editor->edit_menu);\n          }\n          editor->modified = true;\n        }\n        else\n\n        if(get_ctrl_status(keycode_internal))\n        {\n          if(!editor->listening_mod_active)\n          {\n            char current_dir[MAX_PATH];\n            char new_mod[MAX_PATH] = { 0 } ;\n\n            vgetcwd(current_dir, MAX_PATH);\n            vchdir(editor->current_listening_dir);\n\n            if(!choose_file(mzx_world, mod_ext, new_mod,\n             \"Choose a module file (listening only)\", ALLOW_ALL_DIRS))\n            {\n              audio_play_module(new_mod, false, 255);\n              strcpy(editor->current_listening_mod, new_mod);\n              path_get_directory(editor->current_listening_dir, MAX_PATH,\n               new_mod);\n              editor->listening_mod_active = true;\n            }\n\n            vchdir(current_dir);\n          }\n          else\n          {\n            audio_end_module();\n            editor->listening_mod_active = false;\n\n            fix_mod(editor);\n          }\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_o:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          if(editor->mode != EDIT_OVERLAY)\n          {\n            if(!cur_board->overlay_mode)\n            {\n              error(\"Overlay mode is not on (see Board Info)\",\n               ERROR_T_WARNING, ERROR_OPT_OK, 0x1103);\n            }\n            else\n            {\n              set_editor_mode(editor, EDIT_OVERLAY);\n              synchronize_board_values(editor);\n              fix_scroll(editor);\n\n              editor->cursor_mode = CURSOR_PLACE;\n              block->selected = false;\n            }\n          }\n          else\n          {\n            set_editor_mode(editor, EDIT_BOARD);\n            editor->cursor_mode = CURSOR_PLACE;\n            block->selected = false;\n          }\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_p:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          // Size and position\n          if(editor->mode == EDIT_VLAYER)\n          {\n            if(size_pos_vlayer(mzx_world))\n              clear_vlayer_history(editor);\n          }\n          else\n          {\n            if(size_pos(mzx_world))\n            {\n              set_update_done_current(mzx_world);\n              clear_board_history(editor);\n              clear_overlay_history(editor);\n            }\n          }\n\n          synchronize_board_values(editor);\n          fix_scroll(editor);\n\n          if(block->selected)\n          {\n            editor->cursor_mode = CURSOR_PLACE;\n            block->selected = false;\n          }\n\n          // Uh oh, we might need a new player\n          if((mzx_world->player_x >= cur_board->board_width) ||\n           (mzx_world->player_y >= cur_board->board_height))\n            replace_player(mzx_world);\n\n          editor->modified = true;\n        }\n        else\n\n        if(editor->mode == EDIT_BOARD)\n        {\n          // Board- buffer param\n          if(is_storageless(buffer->id))\n          {\n            change_buffer_param(editor);\n          }\n        }\n\n        else\n        {\n          // Layer- buffer char\n          int new_param = char_selection(buffer->param);\n\n          if(new_param >= 0)\n            buffer->param = new_param;\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_r:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        // Clear world\n        if(!confirm(mzx_world, \"Clear ALL - Are you sure?\"))\n        {\n          clear_world(mzx_world);\n          create_blank_world(mzx_world);\n\n          if(block->selected)\n          {\n            editor->cursor_mode = CURSOR_PLACE;\n            block->selected = false;\n          }\n\n          set_editor_mode(editor, EDIT_BOARD);\n          synchronize_board_values(editor);\n          fix_scroll(editor);\n          fix_caption(editor);\n\n          clear_board_history(editor);\n          clear_overlay_history(editor);\n          clear_vlayer_history(editor);\n\n          audio_end_module();\n\n          editor->modified = true;\n        }\n      }\n      else\n\n      if(editor->cursor_mode == CURSOR_TEXT)\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_s:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          if(editor->mode == EDIT_OVERLAY)\n          {\n            editor->show_board_under_overlay ^= 1;\n          }\n          else\n          {\n            status_counter_info(mzx_world);\n            editor->modified = true;\n          }\n        }\n        else\n        {\n          char world_name[MAX_PATH];\n          char new_path[MAX_PATH];\n          strcpy(world_name, editor->current_world);\n          if(!new_file(mzx_world, world_ext, \".mzx\", world_name,\n           \"Save world\", ALLOW_ALL_DIRS))\n          {\n            debug(\"Save path: %s\\n\", world_name);\n\n            // It's now officially the current MZX version\n            mzx_world->version = MZX_VERSION;\n\n            // Save entire game\n            strcpy(editor->current_world, world_name);\n            strcpy(curr_file, world_name);\n            save_world(mzx_world, world_name, false, MZX_VERSION);\n\n            if(path_get_directory(new_path, MAX_PATH, world_name) > 0)\n              vchdir(new_path);\n\n            editor->modified = false;\n          }\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n\n      return true;\n    }\n\n    case IKEY_t:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        // Test world\n        if(block->selected)\n        {\n          editor->cursor_mode = CURSOR_PLACE;\n          block->selected = false;\n        }\n\n        clear_board_history(editor);\n        clear_overlay_history(editor);\n        clear_vlayer_history(editor);\n\n        get_test_world_filename(editor);\n\n        if(!save_world(mzx_world, editor->test_reload_file, false, MZX_VERSION))\n        {\n          vgetcwd(editor->test_reload_dir, MAX_PATH);\n          editor->test_reload_version = mzx_world->version;\n          editor->test_reload_board = mzx_world->current_board_id;\n          editor->reload_after_testing = true;\n\n          vquick_fadeout();\n          cursor_off();\n\n          set_update_done(mzx_world);\n\n          // Changes to the board, duplicates if reset on entry\n          change_board(mzx_world, mzx_world->current_board_id);\n          change_board_set_values(mzx_world);\n          change_board_load_assets(mzx_world);\n          load_board_module(mzx_world);\n\n          send_robot_def(mzx_world, 0, LABEL_JUSTENTERED);\n          send_robot_def(mzx_world, 0, LABEL_JUSTLOADED);\n\n          debug_robot_reset(mzx_world);\n\n          play_game(ctx, NULL);\n        }\n      }\n      else\n\n      if(editor->cursor_mode == CURSOR_TEXT)\n      {\n        place_text(editor);\n      }\n\n      return true;\n    }\n\n    case IKEY_v:\n    {\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          // Toggle vlayer editing\n          if(editor->mode == EDIT_VLAYER)\n          {\n            set_editor_mode(editor, EDIT_BOARD);\n          }\n          else\n          {\n            set_editor_mode(editor, EDIT_VLAYER);\n          }\n\n          synchronize_board_values(editor);\n          fix_scroll(editor);\n\n          editor->cursor_mode = CURSOR_PLACE;\n          block->selected = false;\n        }\n        else\n\n        if(editor->mode != EDIT_VLAYER)\n        {\n          view_board(editor);\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_x:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        int export_number = export_type(mzx_world);\n        if(export_number >= 0)\n        {\n          char export_name[MAX_PATH];\n          export_name[0] = 0;\n\n          switch(export_number)\n          {\n            case 0:\n            {\n              // Board file\n              strcpy(export_name, editor->mzb_name_buffer);\n              if(!new_file(mzx_world, mzb_ext, \".mzb\", export_name,\n               \"Export board file\", ALLOW_ALL_DIRS))\n              {\n                strcpy(editor->mzb_name_buffer, export_name);\n                save_board_file(mzx_world, cur_board, export_name);\n              }\n              break;\n            }\n\n            case 1:\n            {\n              // Character set\n              int char_offset = 0;\n              int char_size = 256;\n              struct element *elements[] =\n              {\n                construct_number_box(9, 20, \"Offset:  \",\n                 0, (PRO_CH - 1), NUMBER_BOX, &char_offset),\n                construct_number_box(35, 20, \"Size: \",\n                 1, (PRO_CH), NUMBER_BOX, &char_size)\n              };\n\n              strcpy(export_name, editor->chr_name_buffer);\n              if(!file_manager(mzx_world, chr_ext, NULL, export_name,\n               \"Export character set\", ALLOW_ALL_DIRS, ALLOW_NEW_FILES,\n               elements, ARRAY_SIZE(elements), 2))\n              {\n                path_force_ext(export_name, MAX_PATH, \".chr\");\n                ec_save_set_var(export_name, char_offset, char_size);\n                strcpy(editor->chr_name_buffer, export_name);\n              }\n\n              break;\n            }\n\n            case 2:\n            {\n              // Palette (let the palette editor handle this one)\n              export_palette(ctx);\n              break;\n            }\n\n            case 3:\n            {\n              // Sound effects\n              export_sfx(ctx);\n              break;\n            }\n\n            case 4:\n            {\n              // Downver. world\n              char title[80];\n\n              sprintf(title, \"Export world to previous version (%d.%d)\",\n               (MZX_VERSION_PREV >> 8) & 0xFF, MZX_VERSION_PREV & 0xFF);\n\n              if(!new_file(mzx_world, world_ext, \".mzx\", export_name,\n               title, ALLOW_ALL_DIRS))\n              {\n                save_world(mzx_world, export_name, false, MZX_VERSION_PREV);\n              }\n              break;\n            }\n\n            case 5:\n            {\n              // Board/vlayer image\n              const char *title = (editor->mode == EDIT_VLAYER) ?\n               \"Export vlayer image\" : \"Export board image\";\n\n              if(!new_file(mzx_world, image_ext, image_ext[0], export_name,\n               title, ALLOW_ALL_DIRS))\n              {\n                if(editor->mode == EDIT_VLAYER)\n                  export_vlayer_image(ctx, mzx_world, export_name);\n                else\n                  export_board_image(ctx, cur_board, export_name);\n              }\n              break;\n            }\n          }\n        }\n      }\n      else\n\n      if(editor->cursor_mode != CURSOR_TEXT)\n      {\n        // Doesn't make sense on the vlayer\n        if(editor->mode != EDIT_VLAYER)\n        {\n          board_exits(mzx_world);\n          editor->modified = true;\n        }\n      }\n      else\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case IKEY_y:\n    {\n      if(get_ctrl_status(keycode_internal) &&\n       !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        // Redo\n        editor->modified |= apply_redo(editor->cur_history);\n      }\n      else\n\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        mzx_world->debug_mode = !(mzx_world->debug_mode);\n      }\n      else\n\n      if(editor->cursor_mode == CURSOR_TEXT)\n      {\n        place_text(editor);\n      }\n\n      return true;\n    }\n\n    case IKEY_z:\n    {\n      if(get_ctrl_status(keycode_internal) &&\n       !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        // Undo\n        editor->modified |= apply_undo(editor->cur_history);\n      }\n      else\n\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        if(editor->mode == EDIT_VLAYER)\n        {\n          // Clear vlayer\n          if(!confirm(mzx_world, \"Clear vlayer - Are you sure?\"))\n          {\n            memset(mzx_world->vlayer_chars, 0, mzx_world->vlayer_size);\n            memset(mzx_world->vlayer_colors, 0, mzx_world->vlayer_size);\n\n            if(block->selected)\n            {\n              editor->cursor_mode = CURSOR_PLACE;\n              block->selected = false;\n            }\n\n            clear_vlayer_history(editor);\n          }\n        }\n        else\n\n        // Clear board\n        if(!confirm(mzx_world, \"Clear board - Are you sure?\"))\n        {\n          clear_board(cur_board);\n          cur_board = create_blank_board(editor_conf);\n          mzx_world->current_board = cur_board;\n          mzx_world->current_board->robot_list[0] = &mzx_world->global_robot;\n          mzx_world->board_list[mzx_world->current_board_id] = cur_board;\n          synchronize_board_values(editor);\n          fix_scroll(editor);\n\n          audio_end_module();\n          cur_board->mod_playing[0] = 0;\n          strcpy(mzx_world->real_mod_playing,\n           cur_board->mod_playing);\n\n          fix_world_title(editor);\n          fix_caption(editor);\n\n          clear_board_history(editor);\n          clear_overlay_history(editor);\n\n          if(block->selected)\n          {\n            editor->cursor_mode = CURSOR_PLACE;\n            block->selected = false;\n          }\n        }\n\n        editor->modified = true;\n      }\n      else\n\n      if(editor->cursor_mode == CURSOR_TEXT)\n      {\n        place_text(editor);\n      }\n      return true;\n    }\n\n    case 0:\n    {\n      break;\n    }\n\n    default:\n    {\n      if(editor->cursor_mode == CURSOR_TEXT)\n      {\n        place_text(editor);\n        return true;\n      }\n      break;\n    }\n  }\n\n  // Exit event and Escape\n  if(exit_status)\n  {\n    if(!editor->modified ||\n     !confirm(mzx_world, \"Exit: World has not been saved, are you sure?\"))\n    {\n      destroy_context(ctx);\n    }\n  }\n  return false;\n}\n\n/**\n * The editor needs to reload a temporary world after testing.\n */\nstatic void editor_resume(context *ctx)\n{\n  struct editor_context *editor = (struct editor_context *)ctx;\n  struct world *mzx_world = ctx->world;\n  boolean ignore;\n\n  if(editor->reload_after_testing)\n  {\n    editor->reload_after_testing = false;\n    vchdir(editor->test_reload_dir);\n\n    if(!reload_world(mzx_world, editor->test_reload_file, &ignore))\n    {\n      if(!editor_reload_world(editor, editor->current_world))\n        create_blank_world(mzx_world);\n\n      set_editor_mode(editor, EDIT_BOARD);\n\n      editor->modified = false;\n    }\n\n    else\n    {\n      // Set it back to the original version.\n      mzx_world->version = editor->test_reload_version;\n    }\n\n    scroll_color = 15;\n    mzx_world->current_board_id = editor->test_reload_board;\n    set_current_board_ext(mzx_world,\n     mzx_world->board_list[editor->test_reload_board]);\n\n    synchronize_board_values(editor);\n    fix_scroll(editor);\n\n    if(editor->listening_mod_active)\n    {\n      audio_play_module(editor->current_listening_mod, false, 255);\n    }\n    else\n    {\n      fix_mod(editor);\n    }\n\n    vunlink(editor->test_reload_file);\n  }\n\n  // These may have changed if a robot was edited.\n  edit_menu_show_robot_memory(editor->edit_menu);\n  fix_caption(editor);\n}\n\n/**\n * Handle exiting the editor.\n */\nstatic void editor_destroy(context *ctx)\n{\n  struct editor_context *editor = (struct editor_context *)ctx;\n  struct buffer_info *buffer = &(editor->buffer);\n  struct world *mzx_world = ctx->world;\n\n  strcpy(curr_file, editor->current_world);\n\n  mzx_world->editing = false;\n  mzx_world->debug_mode = false;\n\n  clear_world(mzx_world);\n  clear_global_data(mzx_world);\n  audio_end_module();\n\n  // Clear any stored buffer data.\n  free_edit_buffer(buffer);\n\n  // Free the undo data\n  clear_board_history(editor);\n  clear_overlay_history(editor);\n  clear_vlayer_history(editor);\n\n  // Free the robot debugger data\n  free_breakpoints();\n\n  insta_fadeout();\n  set_screen_mode(0);\n  default_palette();\n  clear_screen();\n  cursor_off();\n  m_hide();\n\n  // Reset the caption\n  caption_set_world(mzx_world);\n}\n\n/**\n * Initialize and run the editor context.\n */\nstatic void __edit_world(context *parent, boolean reload_curr_file)\n{\n  struct editor_context *editor = ccalloc(1, sizeof(struct editor_context));\n  struct buffer_info *buffer = &(editor->buffer);\n  struct block_info *block = &(editor->block);\n  struct context_spec spec;\n\n  const struct editor_config_info *editor_conf = get_editor_config();\n  struct world *mzx_world = ((context *)parent)->world;\n\n  struct stat stat_res;\n\n  vgetcwd(editor->current_listening_dir, MAX_PATH);\n\n  if(editor_conf->board_editor_hide_help)\n    editor->screen_height = EDIT_SCREEN_MINIMAL;\n  else\n    editor->screen_height = EDIT_SCREEN_NORMAL;\n\n  editor->use_default_color = true;\n  editor->show_board_under_overlay = true;\n  editor->backup_timestamp = get_ticks();\n  editor->debug_x = 60;\n  editor->ansi_line_wrap_column = 80;\n\n  buffer->robot = &(editor->buffer_robot);\n  buffer->scroll = &(editor->buffer_scroll);\n  buffer->sensor = &(editor->buffer_sensor);\n  editor->stored_overlay_char = 32;\n  editor->stored_overlay_color = 7;\n  editor->stored_vlayer_char = 32;\n  editor->stored_vlayer_color = 7;\n  default_buffer(editor);\n\n  block->command = BLOCK_CMD_NONE;\n  block->selected = false;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.resume   = editor_resume;\n  spec.draw     = editor_draw;\n  spec.idle     = editor_idle;\n  spec.key      = editor_key;\n  spec.joystick = editor_joystick;\n  spec.click    = editor_mouse;\n  spec.drag     = editor_mouse;\n  spec.destroy  = editor_destroy;\n\n  create_context((context *)editor, parent, &spec, CTX_EDITOR);\n\n  editor->edit_menu = create_edit_menu((context *)editor);\n\n  // Reload the current world or create a blank world.\n  if(curr_file[0] && vstat(curr_file, &stat_res))\n    curr_file[0] = '\\0';\n\n  if(reload_curr_file && curr_file[0] &&\n   editor_reload_world(editor, curr_file))\n  {\n    memcpy(editor->current_world, curr_file, MAX_PATH);\n\n    mzx_world->current_board_id = mzx_world->first_board;\n    set_current_board_ext(mzx_world,\n     mzx_world->board_list[mzx_world->current_board_id]);\n\n    fix_mod(editor);\n  }\n  else\n  {\n    editor->current_world[0] = 0;\n\n    if(mzx_world->active)\n    {\n      clear_world(mzx_world);\n      clear_global_data(mzx_world);\n    }\n\n    create_blank_world(mzx_world);\n\n    // Prompt for the creation of a first board.\n    // Holding alt while opening the editor bypasses this.\n    if(!get_alt_status(keycode_internal))\n      editor->first_board_prompt = true;\n\n    audio_end_module();\n    default_palette();\n  }\n\n  synchronize_board_values(editor);\n  fix_caption(editor);\n\n  set_palette_intensity(100);\n\n  mzx_world->editing = true;\n}\n\nvoid editor_init(void)\n{\n  edit_world = __edit_world;\n  draw_debug_box = __draw_debug_box;\n  debug_counters = __debug_counters;\n  debug_robot_break = __debug_robot_break;\n  debug_robot_watch = __debug_robot_watch;\n  debug_robot_config = __debug_robot_config;\n  debug_robot_reset = __debug_robot_reset;\n  load_editor_charsets();\n}\n\nboolean is_editor(void)\n{\n  return true;\n}\n"
  },
  {
    "path": "src/editor/edit.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declarations for EDIT.CPP */\n\n#ifndef __EDITOR_EDIT_H\n#define __EDITOR_EDIT_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../world_struct.h\"\n\n#define EDIT_SCREEN_MINIMAL     24\n#define EDIT_SCREEN_NORMAL      19\n\nenum editor_mode\n{\n  EDIT_BOARD = 0,\n  EDIT_OVERLAY = 1,\n  EDIT_VLAYER = 2,\n};\n\nenum cursor_mode\n{\n  CURSOR_PLACE,\n  CURSOR_DRAW,\n  CURSOR_TEXT,\n  CURSOR_BLOCK_SELECT,\n  CURSOR_BLOCK_PLACE,\n  CURSOR_MZM_PLACE,\n  CURSOR_ANSI_PLACE,\n  MAX_CURSOR_MODE\n};\n\nEDITOR_LIBSPEC void editor_init(void);\nEDITOR_LIBSPEC boolean is_editor(void);\n\n#define EC_MAIN_BOX           25\n#define EC_MAIN_BOX_DARK      16\n#define EC_MAIN_BOX_CORNER    24\n#define EC_MENU_NAME          27\n#define EC_CURR_MENU_NAME     159\n#define EC_MODE_STR           30\n#define EC_MODE_HELP          31\n#define EC_CURR_THING         31\n#define EC_CURR_PARAM         23\n#define EC_OPTION             26\n#define EC_HIGHLIGHTED_OPTION 31\n#define EC_DEFAULT_COLOR      28\n#define EC_NA_FILL            1\n\n__M_END_DECLS\n\n#endif // __EDITOR_EDIT_H\n"
  },
  {
    "path": "src/editor/edit_di.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2017-2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Dialogs for the world editor.\n\n#include <string.h>\n\n#include \"../board.h\"\n#include \"../const.h\"\n#include \"../core.h\"\n#include \"../counter.h\"\n#include \"../event.h\"\n#include \"../idput.h\"\n#include \"../robot.h\"\n#include \"../util.h\"\n#include \"../window.h\"\n#include \"../world.h\"\n#include \"../world_struct.h\"\n\n#include \"board.h\"\n#include \"configure.h\"\n#include \"edit_di.h\"\n#include \"param.h\"\n#include \"window.h\"\n\n// The 8th bit set indicates that it's a color, not a char\nstatic const int char_values[8][24] =\n{\n  {\n    0,\n    1,\n    2,\n    3,\n    6,\n    8,\n    9,\n    11,\n    13,\n    14,\n    15,\n    16,\n    20,\n    21,\n    22,\n    23,\n    24,\n    27,\n    28,\n    29,\n    30,\n    31,\n    32,\n    33\n  },\n  {\n    34,\n    36,\n    38,\n    39,\n    40,\n    43,\n    44,\n    47,\n    48,\n    50,\n    55,\n    57,\n    58,\n    60,\n    65,\n    67,\n    68,\n    69,\n    70,\n    71,\n    73,\n    80,\n    81,\n    82\n  },\n  {\n    83,\n    84,\n    85,\n    86,\n    87,\n    88,\n    89,\n    90,\n    91,\n    94,\n    95,\n    125,\n    126,\n    160,\n    161,\n    162,\n    163,\n    164,\n    165,\n    166,\n    167,\n    168,\n    169, // Special\n    176 | 512\n  },\n  {\n    177 | 512,\n    178 | 512,\n    179 | 512,\n    180 | 512,\n    181 | 512,\n    182 | 512,\n    183 | 512,\n    184 | 512,\n    185 | 512,\n    186 | 512,\n    187 | 512,\n    188, // Special\n    189, // Special\n    198, // Special\n    200, // Special\n    190,\n    191,\n    192,\n    193,\n    194,\n    195,\n    196,\n    197,\n    230\n  },\n  {\n    231,\n    232,\n    233,\n    234,\n    235,\n    236,\n    237,\n    238,\n    239,\n    240,\n    241,\n    242,\n    243,\n    244,\n    245,\n    246,\n    247,\n    248,\n    249,\n    250,\n    251,\n    252,\n    253,\n    254\n  },\n  {\n    255,\n    256,\n    257,\n    258,\n    259,\n    260,\n    261,\n    262,\n    263,\n    264,\n    265,\n    266,\n    267,\n    268,\n    269,\n    270,\n    271,\n    272 | 512,\n    273 | 512,\n    274 | 512,\n    275 | 512,\n    276 | 512,\n    277 | 512,\n    278\n  },\n  {\n    279,\n    280,\n    281,\n    282 | 512,\n    283 | 512,\n    284 | 512,\n    285 | 512,\n    286,\n    287,\n    288,\n    289,\n    290,\n    291,\n    292 | 512,\n    293 | 512,\n    294,\n    295,\n    296,\n    297,\n    298 | 512,\n    299 | 512,\n    300 | 512,\n    301 | 512,\n    302 | 512\n  },\n  {\n    303 | 512,\n    304 | 512,\n    305 | 512,\n    306,\n    307,\n    308,\n    309,\n    310,\n    311,\n    312,\n    313,\n    314,\n    315,\n    316,\n    317,\n    318,\n    319,\n    320,\n    321,\n    322 | 512,\n    323 | 512,\n    324 | 512,\n    325 | 512,\n    326 | 512\n  }\n};\n\n// Info for char funcs, 192 selections (24 per func, 8 funcs)\nstatic const char *const char_strs[8][24] =\n{\n  {\n    \"Space (000)-\",\n    \"Normal (001)-\",\n    \"Solid (002)-\",\n    \"Tree (003)-\",\n    \"Breakaway (006)-\",\n    \"Boulder (008)-\",\n    \"Crate (009)-\",\n    \"Box (011)-\",\n    \"Fake (013)-\",\n    \"Carpet (014)-\",\n    \"Floor (015)-\",\n    \"Tiles (016)-\",\n    \"Still Water (020)-\",\n    \"N Water (021)-\",\n    \"S Water (022)-\",\n    \"E Water (023)-\",\n    \"W Water (024)-\",\n    \"Chest (027)-\",\n    \"Gem (028)-\",\n    \"Magic Gem (029)-\",\n    \"Health (030)-\",\n    \"Ring (031)-\",\n    \"Potion (032)-\",\n    \"Energizer (033)-\"\n  },\n  {\n    \"Goop (034)-\",\n    \"Bomb (036)-\",\n    \"Explosion (038)-\",\n    \"Key (039)-\",\n    \"Lock (040)-\",\n    \"Stairs (043)-\",\n    \"Cave (044)-\",\n    \"Gate (047)-\",\n    \"Open Gate (048)-\",\n    \"Coin (050)-\",\n    \"Pouch (055)-\",\n    \"Slider NS (057)-\",\n    \"Slider EW (058)-\",\n    \"Lazer Gun (060)-\",\n    \"Forest (065)-\",\n    \"Whirlpool 1 (067)-\",\n    \"Whirlpool 2 (068)-\",\n    \"Whirlpool 3 (069)-\",\n    \"Whirlpool 4 (070)-\",\n    \"Invis. Wall (071)-\",\n    \"Ricochet (073)-\",\n    \"Snake (080)-\",\n    \"Eye (081)-\",\n    \"Thief (082)-\"\n  },\n  {\n    \"Slime Blob (083)-\",\n    \"Runner (084)-\",\n    \"Ghost (085)-\",\n    \"Dragon (086)-\",\n    \"Fish (087)-\",\n    \"Shark (088)-\",\n    \"Spider (089)-\",\n    \"Goblin (090)-\",\n    \"Spitting Tiger (091)-\",\n    \"Bear (094)-\",\n    \"Bear Cub (095)-\",\n    \"Sign (125)-\",\n    \"Scroll (126)-\",\n    \"Blank Ice (160)-\",\n    \"Ice Anim 1 (161)-\",\n    \"Ice Anim 2 (162)-\",\n    \"Ice Anim 3 (163)-\",\n    \"Lava Anim 1 (164)-\",\n    \"Lava Anim 2 (165)-\",\n    \"Lava Anim 3 (166)-\",\n    \"Small Ammo (167)-\",\n    \"Large Ammo (168)-\",\n    \"Lit Bomb Anim 1 (169)-\", // Special\n    \"Energizer Color 1 (176)-\"\n  },\n  {\n    \"Energizer Color 2 (177)-\",\n    \"Energizer Color 3 (178)-\",\n    \"Energizer Color 4 (179)-\",\n    \"Energizer Color 5 (180)-\",\n    \"Energizer Color 6 (181)-\",\n    \"Energizer Color 7 (182)-\",\n    \"Energizer Color 8 (183)-\",\n    \"Explosion Stage 1 (184)-\",\n    \"Explosion Stage 2 (185)-\",\n    \"Explosion Stage 3 (186)-\",\n    \"Explosion Stage 4 (187)-\",\n    \"Horizontal Door (\\?\\?\\?)-\",//Special\n    \"Vertical Door (\\?\\?\\?)-\",//Special\n    \"Diagonal Door / (\\?\\?\\?)-\",//Special\n    \"Diagonal Door \\\\ (\\?\\?\\?)-\",//Special\n    \"CW Anim 1 (190)-\",\n    \"CW Anim 2 (191)-\",\n    \"CW Anim 3 (192)-\",\n    \"CW Anim 4 (193)-\",\n    \"CCW Anim 1 (194)-\",\n    \"CCW Anim 2 (195)-\",\n    \"CCW Anim 3 (196)-\",\n    \"CCW Anim 4 (197)-\",\n    \"N Transport Anim 1 (230)-\"\n  },\n  {\n    \"N Transport Anim 2 (231)-\",\n    \"N Transport Anim 3 (232)-\",\n    \"N Transport Anim 4 (233)-\",\n    \"S Transport Anim 1 (234)-\",\n    \"S Transport Anim 2 (235)-\",\n    \"S Transport Anim 3 (236)-\",\n    \"S Transport Anim 4 (237)-\",\n    \"E Transport Anim 1 (238)-\",\n    \"E Transport Anim 2 (239)-\",\n    \"E Transport Anim 3 (240)-\",\n    \"E Transport Anim 4 (241)-\",\n    \"W Transport Anim 1 (242)-\",\n    \"W Transport Anim 2 (243)-\",\n    \"W Transport Anim 3 (244)-\",\n    \"W Transport Anim 4 (245)-\",\n    \"A.Transport Anim 1 (246)-\",\n    \"A.Transport Anim 2 (247)-\",\n    \"A.Transport Anim 3 (248)-\",\n    \"A.Transport Anim 4 (249)-\",\n    \"N Thick Arrow (250)-\",\n    \"S Thick Arrow (251)-\",\n    \"E Thick Arrow (252)-\",\n    \"W Thick Arrow (253)-\",\n    \"N Thin Arrow (254)-\"\n  },\n  {\n    \"S Thin Arrow (255)-\",\n    \"E Thin Arrow (256)-\",\n    \"W Thin Arrow (257)-\",\n    \"Horiz Lazer Anim 1 (258)-\",\n    \"Horiz Lazer Anim 2 (259)-\",\n    \"Horiz Lazer Anim 3 (260)-\",\n    \"Horiz Lazer Anim 4 (261)-\",\n    \"Vert Lazer Anim 1 (262)-\",\n    \"Vert Lazer Anim 2 (263)-\",\n    \"Vert Lazer Anim 3 (264)-\",\n    \"Vert Lazer Anim 4 (265)-\",\n    \"Fire Anim 1 (266)-\",\n    \"Fire Anim 2 (267)-\",\n    \"Fire Anim 3 (268)-\",\n    \"Fire Anim 4 (269)-\",\n    \"Fire Anim 5 (270)-\",\n    \"Fire Anim 6 (271)-\",\n    \"Fire Color 1 (272)-\",\n    \"Fire Color 2 (273)-\",\n    \"Fire Color 3 (274)-\",\n    \"Fire Color 4 (275)-\",\n    \"Fire Color 5 (276)-\",\n    \"Fire Color 6 (277)-\",\n    \"Life Anim 1 (278)-\"\n  },\n  {\n    \"Life Anim 2 (279)-\",\n    \"Life Anim 3 (280)-\",\n    \"Life Anim 4 (281)-\",\n    \"Life Color 1 (282)-\",\n    \"Life Color 2 (283)-\",\n    \"Life Color 3 (284)-\",\n    \"Life Color 4 (285)-\",\n    \"Ricochet Panel \\\\ (286)-\",\n    \"Ricochet Panel / (287)-\",\n    \"Mine Anim 1 (288)-\",\n    \"Mine Anim 2 (289)-\",\n    \"Spit Fire Anim 1 (290)-\",\n    \"Spit Fire Anim 2 (291)-\",\n    \"Spit Fire Color 1 (292)-\",\n    \"Spit Fire Color 2 (293)-\",\n    \"Seeker Anim 1 (294)-\",\n    \"Seeker Anim 2 (295)-\",\n    \"Seeker Anim 3 (296)-\",\n    \"Seeker Anim 4 (297)-\",\n    \"Seeker Color 1 (298)-\",\n    \"Seeker Color 2 (299)-\",\n    \"Seeker Color 3 (300)-\",\n    \"Seeker Color 4 (301)-\",\n    \"Whirlpool Color 1 (302)-\"\n  },\n  {\n    \"Whirlpool Color 2 (303)-\",\n    \"Whirlpool Color 3 (304)-\",\n    \"Whirlpool Color 4 (305)-\",\n    \"N Player Bullet (306)-\",\n    \"S Player Bullet (307)-\",\n    \"E Player Bullet (308)-\",\n    \"W Player Bullet (309)-\",\n    \"N Neutral Bullet (310)-\",\n    \"S Neutral Bullet (311)-\",\n    \"E Neutral Bullet (312)-\",\n    \"W Neutral Bullet (313)-\",\n    \"N Enemy Bullet (314)-\",\n    \"S Enemy Bullet (315)-\",\n    \"E Enemy Bullet (316)-\",\n    \"W Enemy Bullet (317)-\",\n    \"N Player Char (318)-\",\n    \"S Player Char (319)-\",\n    \"E Player Char (320)-\",\n    \"W Player Char (321)-\",\n    \"Player Color (322)-\",\n    \"Missile Color (323)-\",\n    \"Player Bullet Color (324)-\",\n    \"Neutral Bullet Color (325)-\",\n    \"Enemy Bullet Color (326)-\"\n  }\n};\n\n// Info for damage editing\nstatic const char dmg_ids[22] =\n{\n  26,\n  38,\n  59,\n  61,\n  62,\n  63,\n  75,\n  76,\n  78,\n  79,\n  80,\n  83,\n  84,\n  85,\n  86,\n  87,\n  88,\n  89,\n  90,\n  91,\n  94,\n  95\n};\n\nstatic const char *const dmg_strs[22] =\n{\n  \"Lava-\",\n  \"Explosion-\",\n  \"Lazer-\",\n  \"Bullet-\",\n  \"Missile-\",\n  \"Fire-\",\n  \"Spike-\",\n  \"Custom Hurt-\",\n  \"Shooting Fire-\",\n  \"Seeker-\",\n  \"Snake-\",\n  \"Slime Blob-\",\n  \"Runner-\",\n  \"Ghost-\",\n  \"Dragon-\",\n  \"Fish-\",\n  \"Shark-\",\n  \"Spider-\",\n  \"Goblin-\",\n  \"Spitting Tiger-\",\n  \"Bear-\",\n  \"Bear Cub-\"\n};\n\nvoid set_confirm_buttons(struct element **elements)\n{\n  elements[0] = construct_button(15, 15, \"OK\", 0);\n  elements[1] = construct_button(37, 15, \"Cancel\", 1);\n}\n\nvoid status_counter_info(struct world *mzx_world)\n{\n  struct dialog di;\n  struct element *elements[2 + NUM_STATUS_COUNTERS];\n  const char *status_counters_strings[NUM_STATUS_COUNTERS] =\n  {\n    \"Status counter 1: \", \"2: \", \"3: \",\n    \"4: \", \"5: \", \"6: \"\n  };\n  int i;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_confirm_buttons(elements);\n  set_context(CTX_STATUS_COUNTERS);\n\n  elements[2] = construct_input_box(12, 5,\n   status_counters_strings[0], COUNTER_NAME_SIZE - 1,\n   mzx_world->status_counters_shown[0]);\n\n  for(i = 1; i < NUM_STATUS_COUNTERS; i++)\n  {\n    elements[i + 2] = construct_input_box(27, 5 + i,\n     status_counters_strings[i], COUNTER_NAME_SIZE - 1,\n     mzx_world->status_counters_shown[i]);\n  }\n\n  construct_dialog(&di, \"Status Counters\", 10, 4, 60, 18,\n   elements, 2 + NUM_STATUS_COUNTERS, 2);\n\n  run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  pop_context();\n}\n\nvoid board_exits(struct world *mzx_world)\n{\n  struct dialog di;\n  struct element *elements[6];\n  struct board *src_board = mzx_world->current_board;\n  int dialog_result;\n  int exits[4];\n\n  memcpy(exits, src_board->board_dir, sizeof(int) * 4);\n\n  set_confirm_buttons(elements);\n  set_context(CTX_BOARD_EXITS);\n\n  elements[2] = construct_board_list(12, 4, \"Board to north:\",\n   1, exits + 0);\n  elements[3] = construct_board_list(12, 6, \"Board to south:\",\n   1, exits + 1);\n  elements[4] = construct_board_list(12, 8, \"Board to east:\",\n   1, exits + 2);\n  elements[5] = construct_board_list(12, 10, \"Board to west:\",\n   1, exits + 3);\n  construct_dialog(&di, \"Board Exits\", 10, 4, 60, 18,\n   elements, 6, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(!dialog_result)\n  {\n    memcpy(src_board->board_dir, exits, sizeof(int) * 4);\n  }\n\n  pop_context();\n}\n\nstatic void bound_board_size(int *width, int *height)\n{\n  if((*width) * (*height) > MAX_BOARD_SIZE)\n  {\n    if(*width > *height)\n      *height = MAX_BOARD_SIZE / *width;\n\n    else\n      *width = MAX_BOARD_SIZE / *height;\n  }\n}\n\n// Size/pos of board/viewport\n// Returns 1 if the board was resized\nint size_pos(struct world *mzx_world)\n{\n  struct board *src_board = mzx_world->current_board;\n  int dialog_result;\n  struct element *elements[9];\n  struct dialog di;\n  int resized = 0;\n\n  int redo = 1;\n\n  int results[6] =\n  {\n    src_board->viewport_x, src_board->viewport_y,\n    src_board->viewport_width, src_board->viewport_height,\n    src_board->board_width, src_board->board_height\n  };\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_BOARD_SIZES);\n\n  do\n  {\n    elements[0] = construct_button(13, 15, \"OK\", 0);\n    elements[1] = construct_button(19, 15, \"Center\", 1);\n    elements[2] = construct_button(29, 15, \"Set as defaults\", 2);\n    elements[3] = construct_number_box(15, 4, \"Viewport X pos: \",\n     0, 79, NUMBER_BOX, results + 0);\n    elements[4] = construct_number_box(15, 5, \"Viewport Y pos: \",\n     0, 24, NUMBER_BOX, results + 1);\n    elements[5] = construct_number_box(15, 6, \"Viewport Width: \",\n     1, 80, NUMBER_BOX, results + 2);\n    elements[6] = construct_number_box(15, 7, \"Viewport Height:\",\n     1, 25, NUMBER_BOX, results + 3);\n    elements[7] = construct_number_box(15, 11, \"Board Width:    \",\n     1, 32767, NUMBER_BOX, results + 4);\n    elements[8] = construct_number_box(15, 12, \"Board Height:   \",\n     1, 32767, NUMBER_BOX, results + 5);\n\n    construct_dialog(&di, \"Board Sizes/Positions\", 10, 4, 60, 18,\n     elements, 9, 3);\n\n    dialog_result = run_dialog(mzx_world, &di);\n    destruct_dialog(&di);\n\n    // Prevent previous keys from carrying through.\n    force_release_all_keys();\n\n    // Center\n    if(dialog_result == 1)\n    {\n      results[0] = 40 - (results[2] / 2);\n      results[1] = 12 - (results[3] / 2);\n    }\n\n    // Fix sizes\n    if(results[2] > results[4])\n      results[2] = results[4];\n    if(results[3] > results[5])\n      results[3] = results[5];\n    if((results[2] + results[0]) > 80)\n      results[2] = 80 - results[0];\n    if((results[3] + results[1]) > 25)\n      results[3] = 25 - results[1];\n\n    switch(dialog_result)\n    {\n      case -1:\n      {\n        redo = 0;\n        break;\n      }\n\n      case 0:\n      {\n        // Change the size; if it's not an enlargement, make sure\n        // the player confirms first.\n\n        // Hack to prevent multiplies of 256, for now - only relevant\n        // with overlay off, but we want to avoid complications if\n        // the user turns the overlay off later\n\n        // As of the new format, this isn't really necessary.\n        if((results[4] % 256) == 0)\n          results[4]++;\n\n        bound_board_size(results + 4, results + 5);\n\n        if(((results[4] >= src_board->board_width) &&\n          (results[5] >= src_board->board_height)) ||\n          !confirm(mzx_world, \"Reduce board size- Are you sure?\"))\n        {\n          if(results[4] != src_board->board_width ||\n           results[5] != src_board->board_height)\n          {\n            change_board_size(src_board, results[4], results[5]);\n            resized = 1;\n          }\n\n          src_board->viewport_x = results[0];\n          src_board->viewport_y = results[1];\n          src_board->viewport_width = results[2];\n          src_board->viewport_height = results[3];\n          redo = 0;\n        }\n        break;\n      }\n      // Set defaults\n      case 2:\n      {\n        struct editor_config_info *conf = get_editor_config();\n        conf->viewport_x = results[0];\n        conf->viewport_y = results[1];\n        conf->viewport_w = results[2];\n        conf->viewport_h = results[3];\n        conf->board_width = results[4];\n        conf->board_height = results[5];\n        save_local_editor_config(conf, curr_file);\n        break;\n      }\n    }\n  } while(redo);\n\n  pop_context();\n\n  return resized;\n}\n\n// Size of vlayer\n// Returns 1 if the vlayer was resized\nint size_pos_vlayer(struct world *mzx_world)\n{\n  int dialog_result;\n  struct element *elements[4];\n  struct dialog di;\n  int resized = 0;\n\n  int results[2] = {\n    mzx_world->vlayer_width,\n    mzx_world->vlayer_height\n  };\n\n  int redo = 1;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_VLAYER_SIZES);\n\n  do\n  {\n    elements[0] = construct_button(23, 8, \"OK\", 0);\n    elements[1] = construct_button(29, 8, \"Cancel\", -1);\n\n    elements[2] = construct_number_box(15, 4, \"Vlayer Width:    \",\n     1, 32767, NUMBER_BOX, results + 0);\n    elements[3] = construct_number_box(15, 5, \"Vlayer Height:   \",\n     1, 32767, NUMBER_BOX, results + 1);\n\n    construct_dialog(&di, \"Vlayer Size\", 10, 7, 60, 11,\n     elements, 4, 2);\n\n    dialog_result = run_dialog(mzx_world, &di);\n    destruct_dialog(&di);\n\n    // Prevent UI keys from carrying through.\n    force_release_all_keys();\n\n    switch(dialog_result)\n    {\n      // Cancel\n      case -1:\n      {\n        redo = 0;\n        break;\n      }\n\n      // OK\n      case 0:\n      {\n        // The vlayer has the same size restrictions as boards.\n        bound_board_size(results + 0, results + 1);\n\n        if(results[0] == mzx_world->vlayer_width &&\n         results[1] == mzx_world->vlayer_height)\n        {\n          resized = 0;\n          redo = 0;\n        }\n        else\n\n        if(((results[0] >= mzx_world->vlayer_width) &&\n          (results[1] >= mzx_world->vlayer_height)) ||\n          !confirm(mzx_world, \"Reduce vlayer size- Are you sure?\"))\n        {\n          int size = results[0] * results[1];\n          int old_size = mzx_world->vlayer_size;\n\n          redo = 0;\n\n          // Decreasing size-- remap first\n          if(size < old_size)\n            remap_vlayer(mzx_world, results[0], results[1]);\n\n          mzx_world->vlayer_size = size;\n          mzx_world->vlayer_chars = crealloc(mzx_world->vlayer_chars, size);\n          mzx_world->vlayer_colors = crealloc(mzx_world->vlayer_colors, size);\n\n          // Increasing size-- remap after\n          if(size >= old_size)\n          {\n            // Clear new area\n            memset(mzx_world->vlayer_chars + old_size, 32, size - old_size);\n            memset(mzx_world->vlayer_colors + old_size, 7, size - old_size);\n            remap_vlayer(mzx_world, results[0], results[1]);\n          }\n\n          resized = 1;\n        }\n        break;\n      }\n    }\n  }\n  while(redo);\n\n  pop_context();\n\n  return resized;\n}\n\n//Dialog- (board info)\n//----------------------------------------------------------//\n//       Board name- __________________________\n//\n//  [ ] Can shoot               ( ) Explosions to space     //\n//  [ ] Can bomb                ( ) Explosions to ash\n//  [ ] Fire burns space        ( ) Explosions to fire\n//  [ ] Fire burns fakes\n//  [ ] Fire burns trees        ( ) Can save\n//  [ ] Fire burns brown        ( ) Can't save\n//  [ ] Forest to floor         ( ) Can save on sensors\n//  [ ] Collect bombs\n//  [ ] Fire burns forever\n//  [ ] Dragons randomly move   ( ) No overlay\n//  [ ] Restart if hurt         ( ) Normal overlay\n//  [ ] Reset board on entry    ( ) Static overlay\n//  [ ] Player locked N/S       ( ) Transparent overlay\n//  [ ] Player locked E/W\n//  [ ] Player attack locked    Time limit- _00000_[!][!]   //\n//\n//  Charset to load-            Palette to load-\n//  _______________________[v]  _______________________[v]  //\n//     [ ] Reset/load when entering from the same board\n//\n//            _OK_  _Cancel_  _Set as defaults_             //\n//\n//----------------------------------------------------------//\n\n// Board info\nvoid board_info(struct world *mzx_world)\n{\n  struct board *src_board = mzx_world->current_board;\n  int dialog_result;\n  struct element *elements[12];\n  struct dialog di;\n  int check_box_results[15] =\n  {\n    src_board->can_shoot, src_board->can_bomb,\n    src_board->fire_burn_space, src_board->fire_burn_fakes,\n    src_board->fire_burn_trees, src_board->fire_burn_brown,\n    src_board->forest_becomes, src_board->collect_bombs,\n    src_board->fire_burns,\n    src_board->dragons_can_randomly_move,\n    src_board->restart_if_zapped,\n    src_board->reset_on_entry, src_board->player_ns_locked,\n    src_board->player_ew_locked, src_board->player_attack_locked\n  };\n  int check_box_results_2[] =\n  {\n    src_board->reset_on_entry_same_board,\n  };\n  const char *check_box_strings[] =\n  {\n    \"Can shoot\", \"Can bomb\", \"Fire burns space\",\n    \"Fire burns fakes\", \"Fire burns trees\", \"Fire burns brown\",\n    \"Forest to floor\", \"Collect bombs\", \"Fire burns forever\",\n    \"Dragons move randomly\",\n    \"Restart if hurt\", \"Reset board on entry\", \"Player locked N/S\",\n    \"Player locked E/W\", \"Player attack locked\"\n  };\n  const char *check_box_strings_2[] =\n  {\n    \"Reset/load when entering from the same board\"\n  };\n  const char *radio_strings_1[] =\n  {\n    \"Explosions to space\", \"Explosions to ash\",\n    \"Explosions to fire\"\n  };\n  const char *radio_strings_2[] =\n  {\n    \"Can save\", \"Can't save\", \"Can save on sensors\"\n  };\n  const char *radio_strings_3[] =\n  {\n    \"No overlay\", \"Normal overlay\", \"Static overlay\",\n    \"Transparent overlay\"\n  };\n  int radio_result_1 = src_board->explosions_leave;\n  int radio_result_2 = src_board->save_mode;\n  int radio_result_3 = src_board->overlay_mode;\n  int time_limit = src_board->time_limit;\n  char title_string[BOARD_NAME_SIZE];\n  const char *charset_exts[] =\n  {\n    \".chr\", NULL\n  };\n  const char *palette_exts[] =\n  {\n    \".pal\", NULL\n  };\n  char charset_string[MAX_PATH];\n  char palette_string[MAX_PATH];\n\n  int pos = 3;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_BOARD_INFO);\n\n  strcpy(title_string, src_board->board_name);\n\n  charset_string[0] = '\\0';\n  palette_string[0] = '\\0';\n  if(src_board->charset_path)\n  {\n    snprintf(charset_string, MAX_PATH, \"%s\", src_board->charset_path);\n    charset_string[MAX_PATH - 1] = '\\0';\n  }\n  if(src_board->palette_path)\n  {\n    snprintf(palette_string, MAX_PATH, \"%s\", src_board->palette_path);\n    palette_string[MAX_PATH - 1] = '\\0';\n  }\n\n  do\n  {\n    elements[0] = construct_button(13, 22, \"OK\", 0);\n    elements[1] = construct_button(19, 22, \"Cancel\", -1);\n    elements[2] = construct_button(29, 22, \"Set as defaults\", 1);\n\n    elements[3] = construct_input_box(7, 1, \"Board name- \",\n     BOARD_NAME_SIZE - 1, title_string);\n\n    elements[4] = construct_check_box(3, 2, check_box_strings,\n     15, 21, check_box_results);\n\n    elements[5] = construct_radio_button(31, 3, radio_strings_1,\n     3, 19, &radio_result_1);\n    elements[6] = construct_radio_button(31, 7, radio_strings_2,\n     3, 19, &radio_result_2);\n    elements[7] = construct_radio_button(31, 11, radio_strings_3,\n     4, 19, &radio_result_3);\n\n    elements[8] = construct_number_box(31, 16, \"Time limit- \",\n     0, 32767, NUMBER_BOX, &time_limit);\n\n    elements[9] = construct_file_selector(3, 18, \"Load charset on entry-\",\n     \"Select a character set...\", charset_exts, \"(none)\", 23, 1, \"\",\n     charset_string, 2);\n\n    elements[10] = construct_file_selector(31, 18, \"Load palette on entry-\",\n     \"Select a palette...\", palette_exts, \"(none)\", 23, 1, \"\",\n     palette_string, 2);\n\n    elements[11] = construct_check_box(6, 20, check_box_strings_2,\n     1, 46, check_box_results_2);\n\n    construct_dialog(&di, \"Board Settings\", 10, 0, 60, 24,\n     elements, ARRAY_SIZE(elements), pos);\n\n    dialog_result = run_dialog(mzx_world, &di);\n    pos = di.current_element;\n    destruct_dialog(&di);\n\n    // Prevent UI keys from carrying through.\n    force_release_all_keys();\n\n    // Save defaults\n    if(dialog_result == 1)\n    {\n      struct editor_config_info *conf = get_editor_config();\n      conf->can_shoot = check_box_results[0];\n      conf->can_bomb = check_box_results[1];\n      conf->fire_burns_spaces = check_box_results[2];\n      conf->fire_burns_fakes = check_box_results[3];\n      conf->fire_burns_trees = check_box_results[4];\n      conf->fire_burns_brown = check_box_results[5];\n      conf->forest_to_floor = check_box_results[6];\n      conf->collect_bombs = check_box_results[7];\n      conf->fire_burns_forever = check_box_results[8];\n      conf->dragons_can_randomly_move = check_box_results[9];\n      conf->restart_if_hurt = check_box_results[10];\n      conf->reset_on_entry = check_box_results[11];\n      conf->player_locked_ns = check_box_results[12];\n      conf->player_locked_ew = check_box_results[13];\n      conf->player_locked_att = check_box_results[14];\n      conf->reset_on_entry_same_board = check_box_results_2[0];\n      conf->explosions_leave = radio_result_1;\n      conf->saving_enabled = radio_result_2;\n      conf->overlay_enabled = radio_result_3;\n      conf->time_limit = time_limit;\n      strcpy(conf->charset_path, charset_string);\n      strcpy(conf->palette_path, palette_string);\n      save_local_editor_config(conf, curr_file);\n      // Setting focus back to the board name shows the user feedback.\n      pos = 3;\n    }\n  } while(dialog_result > 0);\n\n  if(!dialog_result)\n  {\n    strcpy(src_board->board_name, title_string);\n    src_board->can_shoot = check_box_results[0];\n    src_board->can_bomb = check_box_results[1];\n    src_board->fire_burn_space = check_box_results[2];\n    src_board->fire_burn_fakes = check_box_results[3];\n    src_board->fire_burn_trees = check_box_results[4];\n    src_board->fire_burn_brown = check_box_results[5];\n    src_board->forest_becomes = check_box_results[6];\n    src_board->collect_bombs = check_box_results[7];\n    src_board->fire_burns = check_box_results[8];\n    src_board->dragons_can_randomly_move = check_box_results[9];\n    src_board->restart_if_zapped = check_box_results[10];\n    src_board->reset_on_entry = check_box_results[11];\n    src_board->player_ns_locked = check_box_results[12];\n    src_board->player_ew_locked = check_box_results[13];\n    src_board->player_attack_locked = check_box_results[14];\n    src_board->reset_on_entry_same_board = check_box_results_2[0];\n    src_board->explosions_leave = radio_result_1;\n    src_board->save_mode = radio_result_2;\n\n    if(src_board->overlay_mode != radio_result_3)\n    {\n      setup_overlay(src_board, radio_result_3);\n      src_board->overlay_mode = radio_result_3;\n    }\n\n    src_board->time_limit = time_limit;\n    board_set_charset_path(src_board, charset_string, strlen(charset_string));\n    board_set_palette_path(src_board, palette_string, strlen(palette_string));\n  }\n\n  pop_context();\n}\n\n// Chars #1-8\nstatic void global_chars(struct world *mzx_world)\n{\n  int i;\n  int current_id, dialog_result;\n  int current_menu = 0;\n  int results[24];\n  struct element *elements[27];\n  struct dialog di;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_CHANGE_CHAR_IDS);\n\n  do\n  {\n    elements[0] = construct_button(15, 15, \"Next\", 1);\n    elements[1] = construct_button(25, 15, \"Previous\", 2);\n    elements[2] = construct_button(39, 15, \"Done\", 3);\n\n    for(i = 0; i < 12; i++)\n    {\n      if(char_values[current_menu][i] & 512)\n      {\n        elements[i + 3] = construct_color_box(2 +\n         (25 - (int)strlen(char_strs[current_menu][i])), 2 + i,\n         char_strs[current_menu][i], 0, results + i);\n      }\n      else\n      {\n        elements[i + 3] = construct_char_box(2 +\n         (25 - (int)strlen(char_strs[current_menu][i])), 2 + i,\n         char_strs[current_menu][i],\n         char_values[current_menu][i] > 127, results + i);\n      }\n\n      current_id = char_values[current_menu][i] & 511;\n      if(current_id == 323)\n        results[i] = missile_color;\n      else\n\n      if((current_id >= 324) && (current_id <= 326))\n        results[i] = bullet_color[current_id - 324];\n      else\n        results[i] = id_chars[current_id];\n    }\n\n    for(; i < 24; i++)\n    {\n      if(char_values[current_menu][i] & 512)\n      {\n        elements[i + 3] = construct_color_box(32 +\n         (25 - (int)strlen(char_strs[current_menu][i])), i - 10,\n         char_strs[current_menu][i], 0, results + i);\n      }\n      else\n      {\n        elements[i + 3] = construct_char_box(32 +\n         (25 - (int)strlen(char_strs[current_menu][i])), i - 10,\n         char_strs[current_menu][i],\n         char_values[current_menu][i] > 127, results + i);\n      }\n\n      current_id = char_values[current_menu][i] & 511;\n      if(current_id == 323)\n        results[i] = missile_color;\n      else\n\n      if((current_id >= 324) && (current_id <= 326))\n        results[i] = bullet_color[current_id - 324];\n      else\n        results[i] = id_chars[current_id];\n    }\n\n    construct_dialog(&di, \"Edit Characters\", 9, 4, 62, 18,\n     elements, 27, 3);\n\n    // Run\n    dialog_result = run_dialog(mzx_world, &di);\n    destruct_dialog(&di);\n\n    // Prevent UI keys from carrying through.\n    force_release_all_keys();\n\n    if(dialog_result == -1)\n      break;\n\n    // Get from storage\n    for(i = 0; i < 24; i++)\n    {\n      current_id = char_values[current_menu][i] & 511;\n      if(current_id == 323)\n        missile_color = results[i];\n      else\n\n      if((current_id >= 324) && (current_id <= 326))\n        bullet_color[current_id - 324] = results[i];\n      else\n        id_chars[current_id] = results[i];\n    }\n\n    // Setup lit bomb sequence or doors\n    if(current_menu == 2)\n    {\n      id_chars[170] = id_chars[169] - 1;\n      id_chars[171] = id_chars[170] - 1;\n      id_chars[172] = id_chars[171] - 1;\n      id_chars[173] = id_chars[172] - 1;\n      id_chars[174] = id_chars[173] - 1;\n      id_chars[175] = id_chars[174] - 1;\n    }\n    else\n\n    if(current_menu == 3)\n    {\n      // Doors\n      id_chars[199] = id_chars[198]; // '/'\n      id_chars[201] = id_chars[200]; // '\\'\n      id_chars[202] = id_chars[200]; // '\\'\n      id_chars[203] = id_chars[200]; // '\\'\n      id_chars[204] = id_chars[198]; // '/'\n      id_chars[205] = id_chars[198]; // '/'\n      id_chars[206] = id_chars[189]; // '|'\n      id_chars[207] = id_chars[188]; // '-'\n      id_chars[208] = id_chars[189]; // '|'\n      id_chars[209] = id_chars[188]; // '-'\n      id_chars[210] = id_chars[189]; // '|'\n      id_chars[211] = id_chars[188]; // '-'\n      id_chars[212] = id_chars[189]; // '|'\n      id_chars[213] = id_chars[188]; // '-'\n      id_chars[214] = id_chars[189]; // '|'\n      id_chars[215] = id_chars[188]; // '-'\n      id_chars[216] = id_chars[189]; // '|'\n      id_chars[217] = id_chars[188]; // '-'\n      id_chars[218] = id_chars[189]; // '|'\n      id_chars[219] = id_chars[188]; // '-'\n      id_chars[220] = id_chars[189]; // '|'\n      id_chars[221] = id_chars[188]; // '-'\n      id_chars[222] = id_chars[198]; // '/'\n      id_chars[223] = id_chars[198]; // '/'\n      id_chars[224] = id_chars[200]; // '\\'\n      id_chars[225] = id_chars[200]; // '\\'\n      id_chars[226] = id_chars[200]; // '\\'\n      id_chars[227] = id_chars[200]; // '\\'\n      id_chars[228] = id_chars[198]; // '/'\n      id_chars[229] = id_chars[198]; // '/'\n    }\n\n    // Next, prev, or done\n    if(dialog_result == 1)\n    {\n      current_menu++;\n      if((current_menu) > 7)\n        current_menu = 0;\n    }\n    else\n\n    if(dialog_result == 2)\n    {\n      current_menu--;\n\n      if((current_menu) < 0)\n        current_menu = 7;\n    }\n  } while((dialog_result == 1) || (dialog_result == 2));\n\n  pop_context();\n}\n\nstatic void global_dmg(struct world *mzx_world)\n{\n  int dialog_result;\n  struct element *elements[24];\n  struct dialog di;\n  int results[22];\n  int i;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_CHANGE_DAMAGE);\n  set_confirm_buttons(elements);\n\n  for(i = 0; i < 11; i++)\n  {\n    results[i] = id_dmg[(int)dmg_ids[i]];\n    elements[i + 2] = construct_number_box(2 +\n     (14 - (int)strlen(dmg_strs[i])), 2 + i,\n     dmg_strs[i], 0, 255, NUMBER_BOX, results + i);\n  }\n\n  for(; i < 22; i++)\n  {\n    results[i] = id_dmg[(int)dmg_ids[i]];\n    elements[i + 2] = construct_number_box(31 +\n     (14 - (int)strlen(dmg_strs[i])), i - 9,\n     dmg_strs[i], 0, 255, NUMBER_BOX, results + i);\n  }\n\n  construct_dialog(&di, \"Edit Damage\", 10, 4, 60, 18,\n   elements, 24, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(!dialog_result)\n  {\n    for(i = 0; i < 22; i++)\n    {\n      id_dmg[(int)dmg_ids[i]] = results[i];\n    }\n  }\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  pop_context();\n}\n\nvoid global_info(context *ctx)\n{\n  struct world *mzx_world = ctx->world;\n  int death_board = mzx_world->death_board;\n  int endgame_board = mzx_world->endgame_board;\n  int death_x = mzx_world->death_x;\n  int death_y = mzx_world->death_y;\n  int endgame_x = mzx_world->endgame_x;\n  int endgame_y = mzx_world->endgame_y;\n  int first_board = mzx_world->first_board;\n  int edge_color = mzx_world->edge_color;\n  int starting_lives = mzx_world->starting_lives;\n  int lives_limit = mzx_world->lives_limit;\n  int starting_health = mzx_world->starting_health;\n  int health_limit = mzx_world->health_limit;\n  int radio_result_1 = 2;\n  int radio_result_2 = 1;\n  int check_box_results_1[1] = { mzx_world->game_over_sfx };\n  int check_box_results_2[3] =\n  {\n    mzx_world->enemy_hurt_enemy, mzx_world->clear_on_exit,\n    mzx_world->only_from_swap\n  };\n  const char *radio_strings_1[] =\n  {\n    \"Death- Same position\", \"Death- Restart board\",\n    \"Death- Teleport\"\n  };\n  const char *radio_strings_2[] =\n  {\n    \"Endgame- Game over\", \"Endgame- Teleport\"\n  };\n  const char *check_box_strings_1[] = { \"Play game over sfx\" };\n  const char *check_box_strings_2[] =\n  {\n    \"Enemies' bullets hurt other enemies\",\n    \"Clear messages and projectiles on exit\",\n    \"Can only play world from a 'SWAP WORLD'\"\n  };\n  struct dialog a_di;\n  struct dialog b_di;\n  struct element *a_elements[15];\n  struct element *b_elements[12];\n  int dialog_result;\n  int redo = 0;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  do\n  {\n    set_confirm_buttons(a_elements);\n    a_elements[2] = construct_board_list(5, 2, \"First board-\",\n     0, &first_board);\n    a_elements[3] = construct_color_box(38, 3, \"Edging color- \",\n     0, &edge_color);\n    a_elements[4] = construct_label(5+10+4, 5, \"Lives\"),\n    a_elements[5] = construct_label(32+10+3, 5, \"Health\"),\n    a_elements[6] = construct_number_box(5, 6, \"Starting- \",\n     1, 32767, NUMBER_BOX, &starting_lives);\n    a_elements[7] = construct_number_box(5, 7, \"Maximum-  \",\n     1, 32767, NUMBER_BOX, &lives_limit);\n    a_elements[8] = construct_number_box(32, 6, \"Starting- \",\n     1, 32767, NUMBER_BOX, &starting_health);\n    a_elements[9] = construct_number_box(32, 7, \"Maximum-  \",\n     1, 32767, NUMBER_BOX, &health_limit);\n    a_elements[10] = construct_check_box(7, 9, check_box_strings_2,\n     3, 39, check_box_results_2);\n    a_elements[11] = construct_button(5, 13, \"More\", 2);\n    a_elements[12] = construct_button(12, 13, \"Edit Chars\", 3);\n    a_elements[13] = construct_button(25, 13, \"Edit Dmg\", 4);\n    a_elements[14] = construct_button(36, 13, \"Edit Global Robot\", 5);\n\n    construct_dialog(&a_di, \"Global Settings\", 10, 4, 60, 18,\n     a_elements, 15, 2);\n\n    redo = 0;\n    set_context(CTX_GLOBAL_SETTINGS);\n\n    dialog_result = run_dialog(mzx_world, &a_di);\n    destruct_dialog(&a_di);\n\n    // Prevent UI keys from carrying through.\n    force_release_all_keys();\n\n    pop_context();\n\n    switch(dialog_result)\n    {\n      case 2:\n      {\n        // Returns 1 for previous\n        set_context(CTX_GLOBAL_SETTINGS_2);\n\n        if(death_board == DEATH_SAME_POS)\n        {\n          death_board = 0;\n          radio_result_1 = 0;\n        }\n\n        if(death_board == NO_DEATH_BOARD)\n        {\n          death_board = 0;\n          radio_result_1 = 1;\n        }\n\n        if(endgame_board == NO_ENDGAME_BOARD)\n        {\n          endgame_board = 0;\n          radio_result_2 = 0;\n        }\n\n        set_confirm_buttons(b_elements);\n        b_elements[2] = construct_board_list(1, 2, \"Death board-\",\n         0, &death_board);\n        b_elements[3] = construct_number_box(1, 5, \"Death X- \",\n         0, 32767, NUMBER_BOX, &death_x);\n        b_elements[4] = construct_number_box(1, 6, \"Death Y- \",\n         0, 32767, NUMBER_BOX, &death_y);\n        b_elements[5] = construct_radio_button(1, 8, radio_strings_1,\n         3, 20, &radio_result_1);\n        b_elements[6] = construct_board_list(30, 2, \"Endgame board-\",\n         0, &endgame_board);\n        b_elements[7] = construct_number_box(30, 5, \"Endgame X- \",\n         0, 32767, NUMBER_BOX, &endgame_x);\n        b_elements[8] = construct_number_box(30, 6, \"Endgame Y- \",\n         0, 32767, NUMBER_BOX, &endgame_y);\n        b_elements[9] = construct_radio_button(30, 8, radio_strings_2,\n         2, 18, &radio_result_2);\n        b_elements[10] = construct_check_box(30, 11, check_box_strings_1,\n         1, 18, check_box_results_1);\n\n        b_elements[11] = construct_button(25, 13, \"Previous\", 2); //38, 9\n\n        construct_dialog(&b_di, \"Global Settings (Continued)\", 10, 4,\n         60, 18, b_elements, 12, 2);\n\n        dialog_result = run_dialog(mzx_world, &b_di);\n        destruct_dialog(&b_di);\n\n        pop_context();\n\n        if(dialog_result == 2)\n        {\n          redo = 1;\n          break;\n        }\n\n        // If okay, fall through and apply settings\n        if(dialog_result == 1)\n          break;\n      }\n\n      /* fallthrough */\n\n      case 0:\n      {\n        if(radio_result_1 == 2)\n          mzx_world->death_board = death_board;\n        else\n\n        if(radio_result_1 == 1)\n          mzx_world->death_board = NO_DEATH_BOARD;\n\n        else\n          mzx_world->death_board = DEATH_SAME_POS;\n\n        if(radio_result_2 == 1)\n          mzx_world->endgame_board = endgame_board;\n        else\n          mzx_world->endgame_board = NO_ENDGAME_BOARD;\n\n        mzx_world->death_x = death_x;\n        mzx_world->endgame_x = endgame_x;\n        mzx_world->death_y = death_y;\n        mzx_world->endgame_y = endgame_y;\n        mzx_world->game_over_sfx = check_box_results_1[0];\n\n        mzx_world->first_board = first_board;\n        mzx_world->edge_color = edge_color;\n\n        // These are the only parts of the default global data\n        // that the editor can change, so it has to be updated\n        // so testing won't screw things up.\n        set_counter(mzx_world, \"LIVES\", starting_lives, 0);\n        set_counter(mzx_world, \"HEALTH\", starting_health, 0);\n\n        mzx_world->starting_lives = starting_lives;\n        mzx_world->lives_limit = lives_limit;\n        mzx_world->starting_health = starting_health;\n        mzx_world->health_limit = health_limit;\n        mzx_world->enemy_hurt_enemy = check_box_results_2[0];\n        mzx_world->clear_on_exit = check_box_results_2[1];\n        mzx_world->only_from_swap = check_box_results_2[2];\n\n        if(starting_lives > lives_limit)\n          mzx_world->starting_lives = lives_limit;\n\n        if(starting_health > health_limit)\n          mzx_world->starting_health = health_limit;\n\n        break;\n      }\n\n      case 3:\n      {\n        global_chars(mzx_world); // Chars\n        break;\n      }\n\n      case 4:\n      {\n        global_dmg(mzx_world); // Dmg\n        break;\n      }\n\n      case 5:\n      {\n        edit_global_robot(ctx);\n        break;\n      }\n    }\n  } while(redo);\n}\n\n/*\n* +-Goto-------------------------------+\n* |\n* |  X-[00000][-][+]  Y-[00000][-][+]  |\n* |\n* |      [  OK  ]        [Cancel]      |\n* |\n* +------------------------------------+\n*/\n\nint board_goto(struct world *mzx_world, int overlay_edit,\n int *cursor_board_x, int *cursor_board_y)\n{\n  int result = 0;\n  int goto_x = *cursor_board_x;\n  int goto_y = *cursor_board_y;\n  struct element *elements[4];\n  struct dialog di;\n\n  struct board *cur_board = mzx_world->current_board;\n  int board_width = cur_board->board_width;\n  int board_height = cur_board->board_height;\n\n  const char * const titles[] =\n  {\n    \"Goto board location\",\n    \"Goto overlay location\",\n    \"Goto vlayer location\"\n  };\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  if(overlay_edit == 2)\n  {\n    // Vlayer editing\n    board_width = mzx_world->vlayer_width;\n    board_height = mzx_world->vlayer_height;\n  }\n\n  elements[0] = construct_button( 7, 4, \"  OK  \", 0);\n  elements[1] = construct_button(24, 4, \"Cancel\", 1);\n  elements[2] = construct_number_box( 3, 2, \"X-\",\n   0, board_width - 1, NUMBER_BOX, &goto_x);\n  elements[3] = construct_number_box(20, 2, \"Y-\",\n   0, board_height - 1, NUMBER_BOX, &goto_y);\n\n  construct_dialog(&di, titles[overlay_edit],\n   21, 7, 38, 7, elements, ARRAY_SIZE(elements), 2);\n\n  result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(!result)\n  {\n    *cursor_board_x = goto_x;\n    *cursor_board_y = goto_y;\n  }\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  return result;\n}\n"
  },
  {
    "path": "src/editor/edit_di.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n//EDIT_DI.CPP prototypes\n\n#ifndef __EDITOR_EDIT_DI_H\n#define __EDITOR_EDIT_DI_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../core.h\"\n#include \"../window.h\"\n\nint board_goto(struct world *mzx_world, int overlay_edit,\n int *cursor_board_x, int *cursor_board_y);\nvoid board_info(struct world *mzx_world);\nvoid board_exits(struct world *mzx_world);\nvoid global_info(context *ctx);\nint size_pos(struct world *mzx_world);\nint size_pos_vlayer(struct world *mzx_world);\nvoid set_confirm_buttons(struct element **elements);\nvoid status_counter_info(struct world *mzx_world);\n\n__M_END_DECLS\n\n#endif // __EDITOR_EDIT_DI_H\n"
  },
  {
    "path": "src/editor/edit_export.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Misc. export functionality not better suited to other places. */\n\n#include \"edit_export.h\"\n\n#include \"../core_task.h\"\n#include \"../error.h\"\n#include \"../graphics.h\"\n#include \"../idput.h\"\n#include \"../util.h\"\n\n#include <string.h>\n\nstatic boolean export_image_status_callback(void *priv, size_t progress,\n size_t progress_max)\n{\n  return core_task_tick((context *)priv, progress, progress_max, NULL);\n}\n\nstruct export_image_task_data\n{\n  char *filename;\n  unsigned width_ch;\n  unsigned height_ch;\n  struct char_element *layer;\n};\n\nstatic boolean export_image_task_run(context *ctx, void *priv)\n{\n  struct export_image_task_data *data = (struct export_image_task_data *)priv;\n\n  return dump_layer_to_image(data->filename,\n   data->width_ch, data->height_ch, data->layer,\n   export_image_status_callback, ctx);\n}\n\nstatic void export_image_task_complete(void *priv, boolean ret)\n{\n  struct export_image_task_data *data = (struct export_image_task_data *)priv;\n  free(data->filename);\n  free(data->layer);\n  free(data);\n\n  if(!ret)\n    error_message(E_IMAGE_EXPORT_CANCELED, 0, NULL);\n}\n\nstatic void export_image_task(context *parent, const char *filename,\n unsigned width_ch, unsigned height_ch, struct char_element *layer)\n{\n  struct export_image_task_data *d;\n  char *name;\n  size_t len = strlen(filename);\n  char title[80];\n\n  d = (struct export_image_task_data *)cmalloc(sizeof(struct export_image_task_data));\n  name = (char *)cmalloc(len + 1);\n  if(!d || !name)\n  {\n    free(d);\n    free(name);\n    error_message(E_IMAGE_EXPORT, 0, NULL);\n    return;\n  }\n\n  d->filename = name;\n  d->width_ch = width_ch;\n  d->height_ch = height_ch;\n  d->layer = layer;\n\n  memcpy(d->filename, filename, len + 1);\n  if(len > 55)\n  {\n    filename += len - 52;\n    snprintf(title, sizeof(title), \"Saving ...%.52s\", filename);\n  }\n  else\n    snprintf(title, sizeof(title), \"Saving %.55s\", filename);\n\n  core_task_context(parent, title, export_image_task_run,\n   export_image_task_complete, d);\n}\n\n/**\n * Export a board to an image file.\n */\nvoid export_board_image(context *parent, struct board *src_board,\n const char *file)\n{\n  struct char_element *image;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  int overlay_mode = src_board->overlay_mode & OVERLAY_MODE_MASK;\n  size_t offset;\n  size_t sz;\n  int ch, co;\n  int x, y;\n\n  sz = (size_t)board_width * board_height;\n  image = (struct char_element *)cmalloc(sz * sizeof(struct char_element));\n  if(!image)\n    return;\n\n  offset = 0;\n  for(y = 0; y < board_height; y++)\n  {\n    for(x = 0; x < board_width; x++, offset++)\n    {\n      /* simplified version of id_put */\n      if(overlay_mode == OVERLAY_ON || overlay_mode == OVERLAY_STATIC)\n      {\n        ch = src_board->overlay[offset];\n        co = src_board->overlay_color[offset];\n        if(ch != 32)\n        {\n          image[offset].char_value = ch;\n          image[offset].fg_color = co & 0xf;\n\n          if(co & 0xf0)\n            image[offset].bg_color = co >> 4;\n          else\n            image[offset].bg_color = get_id_color(src_board, offset) >> 4;\n\n          continue;\n        }\n      }\n\n      ch = get_id_char(src_board, offset);\n      co = get_id_color(src_board, offset);\n      image[offset].char_value = ch;\n      image[offset].fg_color = co & 0xf;\n      image[offset].bg_color = co >> 4;\n    }\n  }\n\n  export_image_task(parent, file, board_width, board_height, image);\n}\n\n/**\n * Export the vlayer to an image file.\n */\nvoid export_vlayer_image(context *parent, struct world *mzx_world,\n const char *file)\n{\n  struct char_element *image;\n  int board_width = mzx_world->vlayer_width;\n  int board_height = mzx_world->vlayer_height;\n  size_t offset;\n  size_t sz;\n  int x, y;\n\n  sz = (size_t)board_width * board_height;\n  image = (struct char_element *)cmalloc(sz * sizeof(struct char_element));\n  if(!image)\n    return;\n\n  offset = 0;\n  for(y = 0; y < board_height; y++)\n  {\n    for(x = 0; x < board_width; x++, offset++)\n    {\n      int color = mzx_world->vlayer_colors[offset];\n      image[offset].char_value = mzx_world->vlayer_chars[offset];\n      image[offset].fg_color = color & 0x0f;\n      image[offset].bg_color = color >> 4;\n    }\n  }\n\n  export_image_task(parent, file, board_width, board_height, image);\n}\n"
  },
  {
    "path": "src/editor/edit_export.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_EDIT_EXPORT_H\n#define __EDITOR_EDIT_EXPORT_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../core.h\"\n\nvoid export_board_image(context *parent, struct board *src_board,\n const char *file);\nvoid export_vlayer_image(context *parent, struct world *mzx_world,\n const char *file);\n\n__M_END_DECLS\n\n#endif /* __EDITOR_EDIT_EXPORT_H */\n"
  },
  {
    "path": "src/editor/edit_menu.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2018-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../core.h\"\n#include \"../event.h\"\n#include \"../idput.h\"\n#include \"../graphics.h\"\n#include \"../util.h\"\n#include \"../window.h\"\n#include \"../world_struct.h\"\n\n#include \"buffer_struct.h\"\n#include \"edit.h\"\n#include \"edit_menu.h\"\n#include \"window.h\"\n\n#include <stdint.h>\n#include <string.h>\n\n#define NUM_MENUS 6\n\n#define ROBOT_MEMORY_TIMER_MAX  120\n#define BOARD_MOD_TIMER_MAX     300\n\nstruct edit_menu_subcontext\n{\n  subcontext ctx;\n\n  int current_menu;\n  int robot_memory_timer;\n  int board_mod_timer;\n  size_t robot_mem;\n\n  // Provided by edit.c\n  struct buffer_info *buffer;\n  enum editor_mode mode;\n  enum cursor_mode cursor_mode;\n  int cursor_x;\n  int cursor_y;\n  int screen_height;\n  boolean use_default_color;\n};\n\nstatic const char menu_names[NUM_MENUS][9] =\n{\n  \" WORLD \", \" BOARD \", \" THING \", \" CURSOR \", \" SHOW \", \" MISC \"\n};\n\nstatic const char menu_positions[] =\n  \"11111112222222333333344444444555555666666\";\n\nstatic const char *const menu_lines[NUM_MENUS][2]=\n{\n  {\n    \" L:Load   S:Save  G:Global Info  Alt+G:Global Robot  Alt+R:Reset   Alt+T:Test\",\n    \" Alt+C:Char Edit  Alt+E:Palette  Alt+S:Status Info   Alt+V:Vlayer  Alt+F:SFX\"\n  },\n  {\n    \" Alt+Z:Clear  Alt+I:Import  Alt+P:Size/Pos  I:Info   M:Move  A:Add  D:Delete\",\n    \" Ctrl+G:Goto  Alt+X:Export  Alt+O:Overlay   X:Exits  V:View  B:Select Board\"\n  },\n  {\n    \" F3:Terrain  F4:Item      F5:Creature  F6:Puzzle  F7:Transport  F8:Element\",\n    \" F9:Misc     F10:Objects  P:Parameter  C:Color\"\n  },\n  {\n    \" \\x12\\x1d:Move  Space:Place  Enter:Modify+Grab  Alt+M:Modify  Ins:Grab  Del:Delete\",\n          \" F:Fill   Tab:Draw     F2:Text            Alt+B:Block   Alt+\\x12\\x1d:Move 10\"\n  },\n  {\n    \" Shift+F1:Show InvisWalls   Shift+F2:Show Robots   Alt+F11:Robot Debugger\",\n    \" Shift+F3:Show Fakes        Shift+F4:Show Spaces   Alt+Y:Debug Window\"\n  },\n  {\n    \" F1:Help    Home/End:Corner  Alt+A:Select Char Set  Alt+D:Default Colors\",\n    \" ESC:Exit   F11:Screen Mode  Alt+L:Test SAM         Alt+N:Music  *:Mod *\"\n  }\n};\n\nstatic const char *const overlay_menu_lines[3] =\n{\n  \" OVERLAY EDITING- (Alt+O to end)\",\n  \" \\x12\\x1d:Move  Enter:Modify+Grab  Space:Place  Ins:Grab  Del:Delete        F:Fill\",\n  \" C:Color  Alt+\\x12\\x1d:Move 10     Alt+B:Block  Tab:Draw  Alt+S:Show level  F2:Text\"\n};\n\nstatic const char *const vlayer_menu_lines[3] =\n{\n  \" VLAYER EDITING- (Alt+V to end)\",\n  \" \\x12\\x1d:Move  Enter:Modify+Grab  Space:Place  Ins:Grab  Del:Delete  F:Fill\",\n  \" C:Color  Alt+\\x12\\x1d:Move 10     Alt+B:Block  Tab:Draw  Alt+P:Size  F2:Text\"\n};\n\nstatic const char *const minimal_help_mode_mesg[3] =\n{\n  \"Alt+H : Editor Menu\",\n  \"Alt+H : Overlay Menu\",\n  \"Alt+H : Vlayer Menu\",\n};\n\nstatic const char cursor_mode_names[MAX_CURSOR_MODE][10] =\n{\n  \" Current:\",\n  \" Drawing:\",\n  \"    Text:\",\n  \"   Block:\",\n  \"   Block:\",\n  \"  Import:\",\n  \"  Import:\",\n};\n\nstatic const char cursor_mode_help[MAX_CURSOR_MODE][32] =\n{\n  \"\",\n  \"\",\n  \"Type to place text\",\n  \"Press ENTER on other corner\",\n  \"Press ENTER to place block\",\n  \"Press ENTER to place MZM\",\n  \"Press ENTER to place ANSi\",\n};\n\n#define num2hex(x) ((x) > 9 ? 87 + (x) : 48 + (x))\n\nstatic void write_hex_byte(char byte, char color, int x, int y)\n{\n  int t1, t2;\n  t1 = (byte & 240) >> 4;\n  t1 = num2hex(t1);\n  t2 = byte & 15;\n  t2 = num2hex(t2);\n  draw_char(t1, color, x, y);\n  draw_char(t2, color, x + 1, y);\n}\n\nstatic void draw_menu_status(struct edit_menu_subcontext *edit_menu, int line)\n{\n  struct world *mzx_world = ((context *)edit_menu)->world;\n  struct board *cur_board = mzx_world->current_board;\n  struct buffer_info *buffer = edit_menu->buffer;\n  unsigned int display_next_pos;\n  const char *str;\n\n  str = cursor_mode_names[edit_menu->cursor_mode];\n  write_string(str, 42, line, EC_MODE_STR, false);\n  display_next_pos = 42 + strlen(str);\n\n  switch(edit_menu->cursor_mode)\n  {\n    case MAX_CURSOR_MODE:\n      return;\n\n    case CURSOR_TEXT:\n    case CURSOR_BLOCK_SELECT:\n    case CURSOR_BLOCK_PLACE:\n    case CURSOR_MZM_PLACE:\n    case CURSOR_ANSI_PLACE:\n    {\n      write_string(cursor_mode_help[edit_menu->cursor_mode],\n       display_next_pos, line, EC_MODE_HELP, false);\n      break;\n    }\n\n    case CURSOR_PLACE:\n    case CURSOR_DRAW:\n    {\n      int display_color;\n      int display_char;\n\n      if(edit_menu->mode == EDIT_BOARD)\n      {\n        if(buffer->id == SENSOR)\n        {\n          display_char = buffer->sensor->sensor_char;\n          display_color = buffer->color;\n        }\n        else\n\n        if(is_robot(buffer->id))\n        {\n          display_char = buffer->robot->robot_char;\n          display_color = buffer->color;\n        }\n        else\n        {\n          // TODO Clean this up. Requires a refactor of idput.c\n          int temp_char = cur_board->level_id[0];\n          int temp_param = cur_board->level_param[0];\n          int temp_color = cur_board->level_color[0];\n          cur_board->level_id[0] = buffer->id;\n          cur_board->level_param[0] = buffer->param;\n          cur_board->level_color[0] = buffer->color;\n\n          display_char = get_id_char(cur_board, 0);\n          display_color = get_id_board_color(cur_board, 0, true);\n\n          cur_board->level_id[0] = temp_char;\n          cur_board->level_param[0] = temp_param;\n          cur_board->level_color[0] = temp_color;\n        }\n      }\n      else\n      {\n        display_char = buffer->param;\n        display_color = buffer->color;\n      }\n\n      draw_char(' ', 7, display_next_pos, line);\n      erase_char(display_next_pos + 1, line);\n\n      select_layer(GAME_UI_LAYER);\n      draw_char_ext(display_char, display_color, display_next_pos + 1, line, 0, 0);\n      select_layer(UI_LAYER);\n\n      draw_char(' ', 7, display_next_pos + 2, line);\n      display_next_pos += 4;\n\n      draw_char('(', EC_CURR_THING, display_next_pos, line);\n      draw_color_box(display_color, 0, display_next_pos + 1, line, 80);\n      display_next_pos += 5;\n\n      if(edit_menu->mode != EDIT_BOARD)\n      {\n        // \"Character\" is in the overlay/vlayer arrays\n        write_string(\"Character\", display_next_pos, line, EC_CURR_THING, 0);\n        display_next_pos += strlen(\"Character\") + 1;\n\n        write_hex_byte(buffer->param, EC_CURR_PARAM, display_next_pos, line);\n        display_next_pos += 2;\n      }\n      else\n      {\n        char unknown[20];\n\n        if(buffer->id < ARRAY_SIZE(thing_names))\n        {\n          str = thing_names[buffer->id];\n        }\n        else\n        {\n          sprintf(unknown, \"unknown_%02x\", buffer->id);\n          str = unknown;\n        }\n\n        write_string(str, display_next_pos, line, EC_CURR_THING, false);\n        display_next_pos += strlen(str) + 1;\n\n        draw_char('p', EC_CURR_PARAM, display_next_pos, line);\n        display_next_pos++;\n\n        write_hex_byte(buffer->param, EC_CURR_PARAM, display_next_pos, line);\n        display_next_pos += 2;\n      }\n\n      draw_char(')', EC_CURR_THING, display_next_pos, line);\n      display_next_pos++;\n\n      if(!edit_menu->use_default_color)\n      {\n        draw_char('\\x07', EC_DEFAULT_COLOR, display_next_pos, line);\n      }\n      break;\n    }\n  }\n}\n\nstatic void draw_menu_normal(struct edit_menu_subcontext *edit_menu)\n{\n  draw_window_box(0, 19, 79, 24, EC_MAIN_BOX, EC_MAIN_BOX_DARK,\n   EC_MAIN_BOX_CORNER, 0, 1);\n  draw_window_box(0, 21, 79, 24, EC_MAIN_BOX, EC_MAIN_BOX_DARK,\n   EC_MAIN_BOX_CORNER, 0, 1);\n\n  if(edit_menu->mode == EDIT_BOARD)\n  {\n    int i, write_color, x;\n    x = 1; // X position\n\n    for(i = 0; i < NUM_MENUS; i++)\n    {\n      if(i == edit_menu->current_menu)\n        write_color = EC_CURR_MENU_NAME;\n      else\n        write_color = EC_MENU_NAME; // Pick the color\n\n      // Write it\n      write_string(menu_names[i], x, 20, write_color, 0);\n      // Add to x\n      x += (int)strlen(menu_names[i]);\n    }\n\n    write_string(menu_lines[edit_menu->current_menu][0], 1, 22, EC_OPTION, 1);\n    write_string(menu_lines[edit_menu->current_menu][1], 1, 23, EC_OPTION, 1);\n    write_string(\"Pgup/Pgdn:Menu\", 64, 24, EC_CURR_PARAM, 1);\n  }\n  else\n\n  if(edit_menu->mode == EDIT_OVERLAY)\n  {\n    write_string(overlay_menu_lines[0], 1, 20, EC_MENU_NAME, 1);\n    write_string(overlay_menu_lines[1], 1, 22, EC_OPTION, 1);\n    write_string(overlay_menu_lines[2], 1, 23, EC_OPTION, 1);\n  }\n\n  else // EDIT_VLAYER\n  {\n    write_string(vlayer_menu_lines[0], 1, 20, EC_MENU_NAME, 1);\n    write_string(vlayer_menu_lines[1], 1, 22, EC_OPTION, 1);\n    write_string(vlayer_menu_lines[2], 1, 23, EC_OPTION, 1);\n  }\n\n  draw_menu_status(edit_menu, EDIT_SCREEN_NORMAL + 1);\n\n  draw_char(196, EC_MAIN_BOX_CORNER, 78, 21);\n  draw_char(217, EC_MAIN_BOX_DARK, 79, 21);\n}\n\nstatic void draw_menu_minimal(struct edit_menu_subcontext *edit_menu)\n{\n  struct world *mzx_world = ((context *)edit_menu)->world;\n  struct board *cur_board = mzx_world->current_board;\n\n  fill_line(80, 0, EDIT_SCREEN_MINIMAL, ' ', EC_MAIN_BOX);\n\n  // Display robot memory where the Alt+H message would usually go.\n  if(edit_menu->robot_memory_timer > 0)\n  {\n    int robot_mem_kb = (edit_menu->robot_mem + 512) / 1024;\n\n    write_string(\"Robot mem:       kb\", 2, EDIT_SCREEN_MINIMAL, EC_MODE_STR,\n     false);\n    write_number(robot_mem_kb, 31, 2+11, EDIT_SCREEN_MINIMAL, 6, 0, 10);\n  }\n  else\n\n  // Display the board mod where the Alt+H message would usually go.\n  if(edit_menu->board_mod_timer > 0)\n  {\n    char *mod_name = cur_board->mod_playing;\n    int len = strlen(mod_name);\n    int off = 0;\n    char temp;\n\n    write_string(\"Mod:               \", 2, EDIT_SCREEN_MINIMAL, EC_MODE_STR,\n     false);\n\n    if(len > 14)\n    {\n      off = (BOARD_MOD_TIMER_MAX - edit_menu->board_mod_timer)/10;\n      if(off > len - 14)\n        off = len - 14;\n    }\n\n    temp = mod_name[off+14];\n    mod_name[off+14] = 0;\n    write_string(mod_name+off, 2+5, EDIT_SCREEN_MINIMAL, 31, 1);\n    mod_name[off+14] = temp;\n  }\n\n  // Display the Alt+H message.\n  else\n  {\n    write_string(minimal_help_mode_mesg[edit_menu->mode], 2,\n     EDIT_SCREEN_MINIMAL, EC_OPTION, false);\n  }\n\n  write_string(\"X/Y:      /     \", 3+21, EDIT_SCREEN_MINIMAL,\n   EC_MODE_STR, false);\n\n  write_number(edit_menu->cursor_x, 31, 3+21+5, EDIT_SCREEN_MINIMAL,\n   5, false, 10);\n  write_number(edit_menu->cursor_y, 31, 3+21+11, EDIT_SCREEN_MINIMAL,\n   5, false, 10);\n\n  draw_menu_status(edit_menu, EDIT_SCREEN_MINIMAL);\n}\n\nstatic boolean edit_menu_draw(subcontext *ctx)\n{\n  struct edit_menu_subcontext *edit_menu = (struct edit_menu_subcontext *)ctx;\n\n  if(edit_menu->screen_height == EDIT_SCREEN_NORMAL)\n  {\n    draw_menu_normal(edit_menu);\n  }\n  else\n  {\n    draw_menu_minimal(edit_menu);\n  }\n  return true;\n}\n\nstatic boolean edit_menu_idle(subcontext *ctx)\n{\n  struct edit_menu_subcontext *edit_menu = (struct edit_menu_subcontext *)ctx;\n\n  if(edit_menu->robot_memory_timer > 0)\n    edit_menu->robot_memory_timer--;\n\n  if(edit_menu->board_mod_timer > 0)\n    edit_menu->board_mod_timer--;\n\n  return false;\n}\n\nstatic boolean edit_menu_mouse(subcontext *ctx, int *key, int button,\n int x, int y)\n{\n  struct edit_menu_subcontext *edit_menu = (struct edit_menu_subcontext *)ctx;\n  struct buffer_info *buffer = edit_menu->buffer;\n  int status_y = EDIT_SCREEN_MINIMAL;\n  boolean minimal = true;\n\n  if(edit_menu->screen_height == EDIT_SCREEN_NORMAL)\n  {\n    status_y = EDIT_SCREEN_NORMAL + 1;\n    minimal = false;\n  }\n\n  // Mouse actions on buffer row\n  if(y == status_y)\n  {\n    if(!minimal && (x >= 1) && (x <= 41))\n    {\n      // Select current help menu\n      edit_menu->current_menu = menu_positions[x - 1] - '1';\n      return true;\n    }\n    else\n\n    if((x >= 56) && (x <= 58))\n    {\n      // Change current color\n      int new_color = color_selection(buffer->color, 0);\n      if(new_color >= 0)\n        buffer->color = new_color;\n\n      return true;\n    }\n  }\n  return false;\n}\n\nstatic boolean edit_menu_key(subcontext *ctx, int *key)\n{\n  struct edit_menu_subcontext *edit_menu = (struct edit_menu_subcontext *)ctx;\n\n  switch(*key)\n  {\n    case IKEY_PAGEDOWN:\n    {\n      edit_menu->current_menu++;\n\n      if(edit_menu->current_menu == NUM_MENUS)\n        edit_menu->current_menu = 0;\n\n      return true;\n    }\n\n    case IKEY_PAGEUP:\n    {\n      if(edit_menu->current_menu == 0)\n        edit_menu->current_menu = NUM_MENUS;\n\n      edit_menu->current_menu--;\n      return true;\n    }\n  }\n\n  return false;\n}\n\nsubcontext *create_edit_menu(context *parent)\n{\n  struct edit_menu_subcontext *edit_menu =\n   ccalloc(1, sizeof(struct edit_menu_subcontext));\n  struct context_spec spec;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw   = edit_menu_draw;\n  spec.idle   = edit_menu_idle;\n  spec.key    = edit_menu_key;\n  spec.click  = edit_menu_mouse;\n  spec.drag   = edit_menu_mouse;\n\n  create_subcontext((subcontext *)edit_menu, parent, &spec);\n  return (subcontext *)edit_menu;\n}\n\nvoid update_edit_menu(subcontext *ctx, enum editor_mode mode,\n enum cursor_mode cursor_mode, int cursor_x, int cursor_y, int screen_height,\n struct buffer_info *buffer, boolean use_default_color)\n{\n  struct edit_menu_subcontext *edit_menu = (struct edit_menu_subcontext *)ctx;\n\n  if(edit_menu->screen_height != screen_height)\n  {\n    edit_menu->board_mod_timer = 0;\n    edit_menu->robot_memory_timer = 0;\n  }\n\n  edit_menu->mode = mode;\n  edit_menu->cursor_mode = cursor_mode;\n  edit_menu->cursor_x = cursor_x;\n  edit_menu->cursor_y = cursor_y;\n  edit_menu->screen_height = screen_height;\n  edit_menu->buffer = buffer;\n  edit_menu->use_default_color = use_default_color;\n}\n\nvoid edit_menu_show_board_mod(subcontext *ctx)\n{\n  struct edit_menu_subcontext *edit_menu = (struct edit_menu_subcontext *)ctx;\n  edit_menu->board_mod_timer = BOARD_MOD_TIMER_MAX;\n}\n\nstatic size_t compute_robot_memory(struct edit_menu_subcontext *edit_menu)\n{\n  struct world *mzx_world = ((context *)edit_menu)->world;\n  struct board *cur_board = mzx_world->current_board;\n  size_t robot_mem = 0;\n  int i;\n\n  for(i = 0; i < cur_board->num_robots_active; i++)\n  {\n    robot_mem +=\n#ifdef CONFIG_DEBYTECODE\n     (cur_board->robot_list_name_sorted[i])->program_source_length;\n#else\n     (cur_board->robot_list_name_sorted[i])->program_bytecode_length;\n#endif\n  }\n\n  return robot_mem;\n}\n\nvoid edit_menu_show_robot_memory(subcontext *ctx)\n{\n  struct edit_menu_subcontext *edit_menu = (struct edit_menu_subcontext *)ctx;\n  size_t new_robot_mem = compute_robot_memory(edit_menu);\n\n  // If the robot memory hasn't changed, there isn't much point it showing it.\n  if(edit_menu->robot_mem != new_robot_mem)\n  {\n    edit_menu->robot_memory_timer = ROBOT_MEMORY_TIMER_MAX;\n    edit_menu->robot_mem = new_robot_mem;\n  }\n}\n"
  },
  {
    "path": "src/editor/edit_menu.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_EDIT_MENU_H\n#define __EDITOR_EDIT_MENU_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../core.h\"\n#include \"buffer_struct.h\"\n\nsubcontext *create_edit_menu(context *parent);\n\nvoid update_edit_menu(subcontext *ctx, enum editor_mode mode,\n enum cursor_mode cursor_mode, int cursor_x, int cursor_y, int screen_height,\n struct buffer_info *buffer, boolean use_default_color);\n\nvoid edit_menu_show_board_mod(subcontext *ctx);\nvoid edit_menu_show_robot_memory(subcontext *ctx);\n\n__M_END_DECLS\n\n#endif // __EDITOR_EDIT_MENU_H\n"
  },
  {
    "path": "src/editor/fill.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2017-2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Fill function. */\n\n#include \"buffer.h\"\n#include \"edit.h\"\n#include \"fill.h\"\n#include \"undo.h\"\n\n#include \"../util.h\"\n#include \"../world_struct.h\"\n\nstruct queue_elem\n{\n  short int x;\n  short int y;\n};\n\n#define QUEUE_SIZE 8192\n\n#define MATCHING(off) (               \\\n (level_id[off] == fill_id) &&        \\\n (level_color[off] == fill_color) &&  \\\n (level_param[off] == fill_param)     \\\n)\n\n#define QUEUE_NEXT() {                              \\\n  x = queue[queue_first].x;                         \\\n  y = queue[queue_first].y;                         \\\n  queue_first = (queue_first + 1) % QUEUE_SIZE;     \\\n  offset = x + (y * board_width);                   \\\n}\n\n#define QUEUE_ADD(addx,addy) {                      \\\n  queue[queue_next].x = addx;                       \\\n  queue[queue_next].y = addy;                       \\\n  queue_next = (queue_next + 1) % QUEUE_SIZE;       \\\n}\n\nvoid fill_area(struct world *mzx_world, struct buffer_info *buffer,\n int x, int y, enum editor_mode mode, struct undo_history *history)\n{\n  struct board *cur_board = mzx_world->current_board;\n\n  struct queue_elem *queue;\n  int queue_first = 0;\n  int queue_next = 1;\n  boolean matched_down;\n  boolean matched_up;\n\n  char *level_id;\n  char *level_color;\n  char *level_param;\n  int board_width;\n  int board_height;\n  int offset;\n\n  enum thing fill_id = 0;\n  int fill_color = 0;\n  int fill_param = 0;\n\n  // Do nothing if the player is in the buffer\n  if(buffer->id == PLAYER)\n    return;\n\n  switch(mode)\n  {\n    case EDIT_BOARD:\n      level_id = cur_board->level_id;\n      level_color = cur_board->level_color;\n      level_param = cur_board->level_param;\n      board_width = cur_board->board_width;\n      board_height = cur_board->board_height;\n      break;\n\n    case EDIT_OVERLAY:\n      // Use chars for id so we don't have to check for NULL\n      level_id = cur_board->overlay;\n      level_color = cur_board->overlay_color;\n      level_param = cur_board->overlay;\n      board_width = cur_board->board_width;\n      board_height = cur_board->board_height;\n      buffer->id = buffer->param;\n      break;\n\n    default:\n    case EDIT_VLAYER:\n      // Use chars for id so we don't have to check for NULL\n      level_id = mzx_world->vlayer_chars;\n      level_color = mzx_world->vlayer_colors;\n      level_param = mzx_world->vlayer_chars;\n      board_width = mzx_world->vlayer_width;\n      board_height = mzx_world->vlayer_height;\n      buffer->id = buffer->param;\n      break;\n  }\n\n  offset = x + (y * board_width);\n  fill_id = level_id[offset];\n  fill_color = level_color[offset];\n  fill_param = level_param[offset];\n\n  // Fill same as buffer? Do nothing\n  if((fill_id == buffer->id) &&\n   (fill_color == buffer->color) &&\n   (fill_param == buffer->param))\n    return;\n\n  // Start the undo frame for this fill\n  if(mode == EDIT_BOARD)\n  {\n    add_board_undo_frame(mzx_world, history, buffer, x, y);\n  }\n  else\n  {\n    add_layer_undo_pos_frame(history, level_id, level_color, board_width,\n     buffer, x, y);\n  }\n\n  queue = cmalloc(QUEUE_SIZE * sizeof(struct queue_elem));\n  queue[0].x = x;\n  queue[0].y = y;\n\n  /**\n   * Strategy: for a given position, seek the left edge of its fillable region,\n   * then fill right. While filling right, check up and down for new fillable\n   * regions. When a new fillable region is detected in the row above or below,\n   * add the position it was detected at to the queue. Repeat until the queue\n   * is empty.\n   */\n\n  // Perform the fill.\n  do\n  {\n    // Get next queue element\n    QUEUE_NEXT();\n\n    if(!MATCHING(offset))\n      continue;\n\n    // Seek left\n    do\n    {\n      offset--;\n      x--;\n    }\n    while((x >= 0) && MATCHING(offset));\n\n    matched_down = false;\n    matched_up = false;\n    offset++;\n    x++;\n\n    // Scan right\n    do\n    {\n      // Update the undo frame for the new position, then fill\n      add_undo_position(history, x, y);\n\n      // If we're filling storage objects, this will return -1 when we're out\n      // of IDs to assign to them. In this case, abort.\n      if(place_current_at_xy(mzx_world, buffer, x, y, mode, NULL) == -1)\n        goto out_free;\n\n      if(y > 0 && MATCHING(offset - board_width))\n      {\n        if(!matched_up)\n        {\n          QUEUE_ADD(x, y-1);\n          matched_up = true;\n        }\n      }\n      else\n      {\n        matched_up = false;\n      }\n\n      if(y+1 < board_height && MATCHING(offset + board_width))\n      {\n        if(!matched_down)\n        {\n          QUEUE_ADD(x, y+1);\n          matched_down = true;\n        }\n      }\n      else\n      {\n        matched_down = false;\n      }\n\n      offset++;\n      x++;\n    }\n    while((x < board_width) && MATCHING(offset));\n  }\n  while(queue_first != queue_next);\n\nout_free:\n  // Finalize the undo frame\n  update_undo_frame(history);\n\n  free(queue);\n}\n"
  },
  {
    "path": "src/editor/fill.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declaration for FILL.CPP */\n\n#ifndef __EDITOR_FILL_H\n#define __EDITOR_FILL_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"buffer.h\"\n#include \"edit.h\"\n#include \"undo.h\"\n\n#include \"../world_struct.h\"\n\nvoid fill_area(struct world *mzx_world, struct buffer_info *buffer,\n int x, int y, enum editor_mode mode, struct undo_history *history);\n\n__M_END_DECLS\n\n#endif // __EDITOR_FILL_H\n"
  },
  {
    "path": "src/editor/graphics.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * YUV Renderers:\n *   Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * OpenGL #2 Renderer:\n *   Copyright (C) 2007 Joel Bouchard Lamontagne <logicow@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"graphics.h\"\n\n#include \"../graphics.h\"\n#include \"../util.h\"\n#include \"../io/vio.h\"\n\n#include <stdint.h>\n#include <string.h>\n\nstatic uint8_t ascii_charset[CHAR_SIZE * CHARSET_SIZE];\nstatic uint8_t blank_charset[CHAR_SIZE * CHARSET_SIZE];\nstatic uint8_t smzx_charset[CHAR_SIZE * CHARSET_SIZE];\nstatic uint8_t smzx_charset2[CHAR_SIZE * CHARSET_SIZE];\n\nvoid store_backup_palette(char dest[SMZX_PAL_SIZE * 3])\n{\n  uint32_t i;\n  uint8_t *r;\n  uint8_t *g;\n  uint8_t *b;\n\n  for(i = 0; i < graphics.protected_pal_position; i++)\n  {\n    r = (uint8_t *)(dest++);\n    g = (uint8_t *)(dest++);\n    b = (uint8_t *)(dest++);\n    get_rgb(i, r, g, b);\n  }\n}\n\nvoid load_backup_palette(const char src[SMZX_PAL_SIZE * 3])\n{\n  load_palette_mem(src, SMZX_PAL_SIZE * 3);\n}\n\nvoid store_backup_indices(char dest[SMZX_PAL_SIZE * 4])\n{\n  memcpy(dest, graphics.smzx_indices, SMZX_PAL_SIZE * 4);\n}\n\nvoid load_backup_indices(const char src[SMZX_PAL_SIZE * 4])\n{\n  memcpy(graphics.smzx_indices, src, SMZX_PAL_SIZE * 4);\n}\n\nvoid save_palette(char *fname)\n{\n  vfile *pal_file = vfopen_unsafe(fname, \"wb\");\n\n  if(pal_file)\n  {\n    int num_colors = PAL_SIZE;\n    int i;\n\n    if(graphics.screen_mode >= 2)\n      num_colors = SMZX_PAL_SIZE;\n\n    for(i = 0; i < num_colors; i++)\n    {\n      vfputc(get_red_component(i), pal_file);\n      vfputc(get_green_component(i), pal_file);\n      vfputc(get_blue_component(i), pal_file);\n    }\n\n    vfclose(pal_file);\n  }\n}\n\nvoid save_index_file(char *fname)\n{\n  vfile *idx_file = vfopen_unsafe(fname, \"wb\");\n\n  if(idx_file)\n  {\n    int i;\n\n    for(i = 0; i < SMZX_PAL_SIZE; i++)\n    {\n      vfputc(get_smzx_index(i, 0), idx_file);\n      vfputc(get_smzx_index(i, 1), idx_file);\n      vfputc(get_smzx_index(i, 2), idx_file);\n      vfputc(get_smzx_index(i, 3), idx_file);\n    }\n\n    vfclose(idx_file);\n  }\n}\n\nvoid ec_save_set_var(const char *name, uint16_t first_chr, unsigned int num)\n{\n  vfile *vf = vfopen_unsafe(name, \"wb\");\n\n  if(vf)\n  {\n    if(num + first_chr > PROTECTED_CHARSET_POSITION)\n    {\n      if(first_chr > PROTECTED_CHARSET_POSITION)\n        first_chr = PROTECTED_CHARSET_POSITION;\n\n      num = PROTECTED_CHARSET_POSITION - first_chr;\n    }\n\n    vfwrite(graphics.charset + (first_chr * CHAR_SIZE), CHAR_SIZE, num, vf);\n    vfclose(vf);\n  }\n}\n\nvoid ec_change_block(uint8_t offset, uint8_t charset,\n uint8_t width, uint8_t height, const char *matrix)\n{\n  // Change a block of chars on the 32x8 charset\n  int skip;\n  int x;\n  int y;\n\n  width = CLAMP(width, 1, 32);\n  height = CLAMP(height, 1, 8);\n\n  skip = 32 - width;\n\n  // No need to bound offset (uint8_t)\n  for(y = 0; y < height; y++)\n  {\n    for(x = 0; x < width; x++)\n    {\n      ec_change_char((charset * 256) + offset, matrix);\n      matrix += CHAR_SIZE;\n      offset++;\n    }\n    offset += skip;\n  }\n}\n\nvoid ec_read_block(uint8_t offset, uint8_t charset,\n uint8_t width, uint8_t height, char *matrix)\n{\n  // Read a block of chars from the 32x8 charset\n  int skip;\n  int x;\n  int y;\n\n  width = CLAMP(width, 1, 32);\n  height = CLAMP(height, 1, 8);\n\n  skip = 32 - width;\n\n  for(y = 0; y < height; y++)\n  {\n    for(x = 0; x < width; x++)\n    {\n      ec_read_char((charset * 256) + offset, matrix);\n      matrix += CHAR_SIZE;\n      offset++;\n    }\n    offset += skip;\n  }\n}\n\nvoid load_editor_charsets(void)\n{\n  ec_load_set_secondary(mzx_res_get_by_id(MZX_ASCII_CHR), ascii_charset);\n  ec_load_set_secondary(mzx_res_get_by_id(MZX_BLANK_CHR), blank_charset);\n  ec_load_set_secondary(mzx_res_get_by_id(MZX_SMZX_CHR),  smzx_charset);\n  ec_load_set_secondary(mzx_res_get_by_id(MZX_SMZX2_CHR), smzx_charset2);\n}\n\nvoid ec_load_smzx(void)\n{\n  ec_mem_load_set(smzx_charset, CHAR_SIZE * CHARSET_SIZE);\n}\n\nvoid ec_load_smzx2(void)\n{\n  ec_mem_load_set(smzx_charset2, CHAR_SIZE * CHARSET_SIZE);\n}\n\nvoid ec_load_blank(void)\n{\n  ec_mem_load_set(blank_charset, CHAR_SIZE * CHARSET_SIZE);\n}\n\nvoid ec_load_ascii(void)\n{\n  ec_mem_load_set(ascii_charset, CHAR_SIZE * CHARSET_SIZE);\n}\n\nvoid ec_load_char_ascii(uint16_t char_number)\n{\n  unsigned int ascii_number = char_number & 0xFF;\n  uint8_t *ascii_char = ascii_charset + ascii_number * CHAR_SIZE;\n\n  ec_change_char(char_number, (char *)ascii_char);\n}\n\nvoid ec_load_char_mzx(uint16_t char_number)\n{\n  unsigned int default_number = char_number & 0xFF;\n  uint8_t *default_char = graphics.default_charset + default_number * CHAR_SIZE;\n\n  ec_change_char(char_number, (char *)default_char);\n}\n\n/**\n * Returns the number of pixels (from 0 to 112) that are the same between two\n * chars.\n */\nunsigned int compare_char(uint16_t chr_a, uint16_t chr_b)\n{\n  uint8_t *a = graphics.charset + (chr_a % FULL_CHARSET_SIZE) * CHAR_SIZE;\n  uint8_t *b = graphics.charset + (chr_b % FULL_CHARSET_SIZE) * CHAR_SIZE;\n  unsigned int same = 0;\n  int i;\n  int mask;\n\n  if(get_screen_mode())\n  {\n    for(i = 0; i < CHAR_SIZE; i++)\n      for(mask = 0xC0; mask > 0; mask >>= 2)\n        same += (a[i] & mask) == (b[i] & mask);\n\n    same *= 2;\n  }\n  else\n  {\n    for(i = 0; i < CHAR_SIZE; i++)\n      for(mask = 0x80; mask > 0; mask >>= 1)\n        same += (a[i] & mask) == (b[i] & mask);\n  }\n  return same;\n}\n"
  },
  {
    "path": "src/editor/graphics.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_GRAPHICS_H\n#define __EDITOR_GRAPHICS_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../graphics.h\"\n\nvoid store_backup_palette(char dest[SMZX_PAL_SIZE * 3]);\nvoid load_backup_palette(const char src[SMZX_PAL_SIZE * 3]);\nvoid store_backup_indices(char dest[SMZX_PAL_SIZE * 4]);\nvoid load_backup_indices(const char src[SMZX_PAL_SIZE * 4]);\n\nvoid save_palette(char *fname);\nvoid save_index_file(char *fname);\n\nvoid ec_save_set_var(const char *name, uint16_t first_chr, unsigned int num);\nvoid ec_change_block(uint8_t offset, uint8_t charset,\n uint8_t width, uint8_t height, const char *matrix);\nvoid ec_read_block(uint8_t offset, uint8_t charset,\n uint8_t width, uint8_t height, char *matrix);\nvoid load_editor_charsets(void);\nvoid ec_load_smzx(void);\nvoid ec_load_smzx2(void);\nvoid ec_load_blank(void);\nvoid ec_load_ascii(void);\nvoid ec_load_char_ascii(uint16_t char_number);\nvoid ec_load_char_mzx(uint16_t char_number);\nunsigned int compare_char(uint16_t chr_a, uint16_t chr_b);\n\n__M_END_DECLS\n\n#endif // __EDITOR_GRAPHICS_H\n"
  },
  {
    "path": "src/editor/macro.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Extended macro support\n\n#include \"configure.h\"\n#include \"macro.h\"\n#include \"macro_struct.h\"\n#include \"../rasm.h\"\n#include \"../util.h\"\n\n#include <string.h>\n#include <stdlib.h>\n#include <ctype.h>\n\nstatic int cmp_variables(const void *dest, const void *src)\n{\n  struct macro_variable *m_src = *((struct macro_variable **)src);\n  struct macro_variable *m_dest = *((struct macro_variable **)dest);\n\n  return strcasecmp(m_dest->name, m_src->name);\n}\n\nvoid free_macro(struct ext_macro *macro_src)\n{\n  int i, i2;\n\n  free(macro_src->name);\n  free(macro_src->label);\n\n  for(i = 0; i < macro_src->num_lines; i++)\n  {\n    free(macro_src->lines[i]);\n    free(macro_src->variable_references[i]);\n  }\n\n  free(macro_src->lines);\n  free(macro_src->variable_references);\n\n  free(macro_src->line_element_count);\n\n  for(i = 0; i < macro_src->num_types; i++)\n  {\n    if(macro_src->types[i].type == string)\n    {\n      for(i2 = 0; i2 < macro_src->types[i].num_variables; i2++)\n      {\n        free(macro_src->types[i].variables[i2].storage.str_storage);\n\n        if(macro_src->types[i].variables[i2].def.str_storage)\n          free(macro_src->types[i].variables[i2].def.str_storage);\n      }\n    }\n\n    free(macro_src->types[i].variables_sorted);\n    free(macro_src->types[i].variables);\n  }\n\n  free(macro_src->text);\n  free(macro_src);\n}\n\n#ifdef CONFIG_DEBYTECODE\nstatic\n#endif\nchar *skip_to_next(char *src, char t, char a, char b)\n{\n  char *current = src;\n  char current_char = *current;\n\n  while(current_char && (current_char != t) && (current_char != '\\n') &&\n   (current_char != a) && (current_char != b))\n  {\n    if(current_char == '\\r')\n      *current = 0;\n    current++;\n    current_char = *current;\n  }\n\n  return current;\n}\n\n#ifdef CONFIG_DEBYTECODE\nstatic\n#endif\nchar *skip_whitespace(char *src)\n{\n  char *current = src;\n  char current_char = *current;\n\n  while(isspace((int)current_char))\n  {\n    current++;\n    current_char = *current;\n  }\n\n  return current;\n}\n\n#ifdef CONFIG_DEBYTECODE\nstatic\n#endif\nunion variable_storage *find_macro_variable(const char *name,\n const struct macro_type *m)\n{\n  int bottom = 0, top = m->num_variables - 1, middle = 0;\n  int cmpval = 0;\n  struct macro_variable **base = m->variables_sorted;\n  struct macro_variable *current;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    current = base[middle];\n    cmpval = strcasecmp(name, current->name);\n\n    if(cmpval > 0)\n      bottom = middle + 1;\n    else\n\n    if(cmpval < 0)\n      top = middle - 1;\n    else\n      return &(current->storage);\n  }\n\n  return NULL;\n}\n\nstatic struct ext_macro *process_macro(const char *line_data,\n const char *name, const char *label)\n{\n  char *line_position, *line_position_old;\n  struct macro_variable *variables;\n  char ***text_lines;\n  char **line_text_segments;\n  struct macro_variable_reference *line_variable_references;\n  union variable_storage *current_storage;\n  struct macro_variable_reference **variable_references;\n  int *line_variables_count;\n  int num_variables;\n  int num_types = 0;\n  int num_line_variables;\n  int num_lines = 0;\n  int num_lines_allocated = 32;\n  int total_variables = 0;\n  struct macro_type *current_type;\n  char current_char;\n  struct ext_macro *macro_dest;\n  int i;\n  int def_val;\n\n  line_text_segments = ccalloc(256, sizeof(char *));\n  variables = ccalloc(256, sizeof(struct macro_variable));\n  line_variable_references = ccalloc(256,\n   sizeof(struct macro_variable_reference));\n\n  text_lines = ccalloc(num_lines_allocated, sizeof(char **));\n  variable_references =\n   ccalloc(num_lines_allocated, sizeof(struct macro_variable_reference *));\n  line_variables_count = ccalloc(num_lines_allocated, sizeof(int));\n\n  macro_dest = cmalloc(sizeof(struct ext_macro));\n  macro_dest->name = cmalloc(strlen(name) + 1);\n  strcpy(macro_dest->name, name);\n\n  macro_dest->label = cmalloc(strlen(label) + 1);\n  strcpy(macro_dest->label, label);\n\n  current_type = macro_dest->types;\n\n  macro_dest->text = cmalloc(strlen(line_data) + 1);\n  strcpy(macro_dest->text, line_data);\n  line_position = macro_dest->text;\n\n  do\n  {\n    if(*line_position == '(')\n    {\n      // Line is for variables\n      line_position++;\n\n      // Determine type\n      if(!strncmp(line_position, \"number\", 6))\n      {\n        // Number\n        current_type->type = number;\n        current_type->type_attributes[1] =\n         strtol(line_position + 6, &line_position, 10);\n\n        if(*line_position == '-')\n        {\n          current_type->type_attributes[0] =\n           current_type->type_attributes[1];\n          current_type->type_attributes[1] =\n           strtol(line_position + 1, &line_position, 10);\n        }\n        else\n        {\n          current_type->type_attributes[0] = 0;\n        }\n      }\n\n      if(!strncmp(line_position, \"string\", 6))\n      {\n        // Number\n        current_type->type = string;\n        current_type->type_attributes[0] =\n         strtol(line_position + 6, &line_position, 10);\n      }\n\n      if(!strncmp(line_position, \"char\", 4))\n      {\n        // Character\n        current_type->type = character;\n        line_position += 4;\n      }\n\n      if(!strncmp(line_position, \"color\", 5))\n      {\n        // Character\n        current_type->type = color;\n        line_position += 5;\n      }\n\n      num_variables = 0;\n\n      // Parse names\n      while(1)\n      {\n        line_position = skip_whitespace(line_position);\n        line_position_old = line_position;\n        line_position = skip_to_next(line_position, ',', ')', '=');\n        variables[num_variables].name = line_position_old;\n\n        if(*line_position == '=')\n        {\n          // Initialize default value\n          def_val = 1;\n          *line_position = 0;\n          line_position++;\n\n          switch(current_type->type)\n          {\n            case number:\n            {\n              variables[num_variables].def.int_storage =\n               strtol(line_position, &line_position, 10);\n              break;\n            }\n\n            case string:\n            {\n              line_position_old = line_position;\n              break;\n            }\n\n            case character:\n            {\n              if(*line_position == '\\'')\n              {\n                variables[num_variables].def.int_storage =\n                 *(line_position + 1);\n                line_position += 2;\n              }\n              else\n              {\n                variables[num_variables].def.int_storage =\n                 (char)strtol(line_position, &line_position, 10);\n              }\n\n              break;\n            }\n\n            case color:\n            {\n              variables[num_variables].def.int_storage =\n               get_color(line_position);\n              line_position += 3;\n              break;\n            }\n\n            default:\n            {\n              break;\n            }\n          }\n\n          line_position = skip_to_next(line_position, ',', ')', 0);\n        }\n        else\n        {\n          switch(current_type->type)\n          {\n            case number:\n            {\n              variables[num_variables].def.int_storage =\n               current_type->type_attributes[0];\n              break;\n            }\n\n            case character:\n            {\n              variables[num_variables].def.int_storage = 0;\n              break;\n            }\n\n            case color:\n            {\n              variables[num_variables].def.int_storage = 288;\n              break;\n            }\n\n            default:\n            {\n              break;\n            }\n          }\n          def_val = 0;\n        }\n\n        current_char = *line_position;\n         *line_position = 0;\n\n        if(current_type->type == string)\n        {\n          variables[num_variables].storage.str_storage =\n           cmalloc(current_type->type_attributes[0] + 1);\n\n          if(def_val)\n          {\n            variables[num_variables].def.str_storage =\n             cmalloc(current_type->type_attributes[0] + 1);\n            memcpy(variables[num_variables].def.str_storage,\n             line_position_old, current_type->type_attributes[0]);\n            variables[num_variables].def.\n             str_storage[current_type->type_attributes[0]] = 0;\n            memcpy(variables[num_variables].storage.str_storage,\n             variables[num_variables].def.str_storage,\n             current_type->type_attributes[0] + 1);\n          }\n          else\n          {\n            variables[num_variables].def.str_storage = NULL;\n            variables[num_variables].storage.str_storage[0] = 0;\n          }\n        }\n        else\n        {\n          memcpy(&(variables[num_variables].storage),\n           &(variables[num_variables].def), sizeof(union variable_storage));\n        }\n\n        num_variables++;\n\n        if(current_char == ')')\n        {\n          line_position++;\n          break;\n        }\n        else\n\n        if(current_char)\n        {\n          line_position++;\n        }\n        else\n        {\n          break;\n        }\n      }\n\n      current_type->num_variables = num_variables;\n      current_type->variables =\n       ccalloc(num_variables, sizeof(struct macro_variable));\n      current_type->variables_sorted =\n       ccalloc(num_variables, sizeof(struct macro_variable *));\n      memcpy(current_type->variables, variables,\n       sizeof(struct macro_variable) * num_variables);\n\n      for(i = 0; i < num_variables; i++)\n      {\n        current_type->variables_sorted[i] = current_type->variables + i;\n      }\n\n      total_variables += num_variables;\n\n      qsort(current_type->variables_sorted,\n       num_variables, sizeof(struct macro_variable *), cmp_variables);\n\n      current_type++;\n      num_types++;\n\n      line_position = skip_whitespace(line_position);\n    }\n    else\n    {\n      current_char = *line_position;\n\n      while((current_char == '\\n') || (current_char == '\\r'))\n      {\n        line_position++;\n        current_char = *line_position;\n      }\n\n      num_line_variables = 0;\n      line_text_segments[0] = line_position;\n\n      while(current_char && (current_char != '\\n'))\n      {\n        current_char = *line_position;\n        while(current_char && (current_char != '\\n'))\n        {\n          line_position = skip_to_next(line_position, '!', 0, 0);\n          current_char = *line_position;\n\n          if(current_char == '!')\n          {\n            // Embedded variable, close off\n            *line_position = 0;\n            line_position_old = line_position + 1;\n\n            if(*line_position_old == '#')\n            {\n              line_position_old++;\n              line_variable_references[num_line_variables].ref_mode =\n               hexidecimal;\n            }\n            else\n            {\n              line_variable_references[num_line_variables].ref_mode =\n               decimal;\n            }\n\n            // Get name\n            line_position = skip_to_next(line_position_old, '!', 0, 0);\n            // Close off end\n            *line_position = 0;\n            line_position++;\n\n            // Find macro element this corresponds to and link.\n            for(i = 0; i < num_types; i++)\n            {\n              current_storage = find_macro_variable(line_position_old,\n               macro_dest->types + i);\n              if(current_storage)\n              {\n                line_variable_references[num_line_variables].storage =\n                 current_storage;\n                line_variable_references[num_line_variables].type =\n                 macro_dest->types + i;\n                break;\n              }\n            }\n\n            if(i == num_types)\n            {\n              line_variable_references[num_line_variables].type =\n               NULL;\n            }\n\n            line_text_segments[num_line_variables + 1] = line_position;\n            num_line_variables++;\n          }\n\n          current_char = *line_position;\n        }\n        *line_position = 0;\n        line_position++;\n      }\n\n      line_variables_count[num_lines] = num_line_variables;\n\n      variable_references[num_lines] =\n       ccalloc(num_line_variables, sizeof(struct macro_variable_reference));\n      memcpy(variable_references[num_lines], line_variable_references,\n       sizeof(struct macro_variable_reference) * num_line_variables);\n\n      text_lines[num_lines] =\n       ccalloc(num_line_variables + 1, sizeof(char *));\n      memcpy(text_lines[num_lines], line_text_segments,\n       sizeof(char *) * (num_line_variables + 1));\n\n      num_lines++;\n\n      if(num_lines == num_lines_allocated)\n      {\n        num_lines_allocated *= 2;\n\n        text_lines = crealloc(text_lines,\n         sizeof(char **) * num_lines_allocated);\n        variable_references = crealloc(variable_references,\n         sizeof(struct macro_variable_reference *) * num_lines_allocated);\n        line_variables_count = crealloc(line_variables_count,\n         sizeof(int) * num_lines_allocated);\n      }\n    }\n  } while(*line_position);\n\n  macro_dest->num_lines = num_lines;\n  macro_dest->total_variables = total_variables;\n\n  macro_dest->lines = ccalloc(num_lines, sizeof(char **));\n  memcpy(macro_dest->lines, text_lines, sizeof(char **) * num_lines);\n\n  macro_dest->variable_references =\n   ccalloc(num_lines, sizeof(struct macro_variable_reference *));\n  memcpy(macro_dest->variable_references, variable_references,\n   sizeof(struct macro_variable_reference *) * num_lines);\n\n  macro_dest->line_element_count = ccalloc(num_lines, sizeof(int));\n  memcpy(macro_dest->line_element_count, line_variables_count,\n   sizeof(int) * num_lines);\n\n  macro_dest->num_types = num_types;\n\n  free(line_variable_references);\n  free(line_variables_count);\n  free(line_text_segments);\n  free(variable_references);\n  free(variables);\n  free(text_lines);\n\n  return macro_dest;\n}\n\nstatic struct ext_macro *_find_macro(const struct editor_config_info *conf,\n const char *name, int *next)\n{\n  int bottom = 0, top = (conf->num_extended_macros) - 1, middle = 0;\n  int cmpval = 0;\n  struct ext_macro **base = conf->extended_macros;\n  struct ext_macro *current;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    current = base[middle];\n    cmpval = strcasecmp(name, current->name);\n\n    if(cmpval > 0)\n      bottom = middle + 1;\n    else\n\n    if(cmpval < 0)\n      top = middle - 1;\n    else\n      return current;\n  }\n\n  if(cmpval > 0)\n    *next = middle + 1;\n  else\n    *next = middle;\n\n  return NULL;\n}\n\nconst struct ext_macro *find_macro(const struct editor_config_info *conf,\n const char *name, int *next)\n{\n  return _find_macro(conf, name, next);\n}\n\nvoid add_ext_macro(struct editor_config_info *conf, const char *name,\n const char *line_data, const char *label)\n{\n  struct ext_macro *macro_dest;\n  struct ext_macro **macro_list;\n  int next = 0;\n\n  if(!(conf->num_macros_allocated))\n  {\n    conf->extended_macros = cmalloc(sizeof(struct ext_macro *));\n    conf->extended_macros[0] = process_macro(line_data, name, label);\n    conf->num_extended_macros = 1;\n    conf->num_macros_allocated = 1;\n  }\n  else\n  {\n    macro_dest = _find_macro(conf, name, &next);\n\n    if(macro_dest)\n    {\n      free_macro(macro_dest);\n      conf->extended_macros[next] =\n       process_macro(line_data, name, label);\n    }\n    else\n    {\n      if(conf->num_extended_macros == conf->num_macros_allocated)\n      {\n        conf->num_macros_allocated *= 2;\n        conf->extended_macros =\n         crealloc(conf->extended_macros,\n         sizeof(struct ext_macro *) * conf->num_macros_allocated);\n      }\n\n      macro_list = conf->extended_macros;\n\n      if(next != conf->num_extended_macros)\n      {\n        macro_list += next;\n        memmove((char *)(macro_list + 1),\n         (char *)macro_list, (conf->num_extended_macros - next) *\n         sizeof(struct ext_macro *));\n      }\n\n      conf->extended_macros[next] =\n       process_macro(line_data, name, label);\n\n      conf->num_extended_macros++;\n    }\n  }\n}\n"
  },
  {
    "path": "src/editor/macro.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_MACRO_H\n#define __EDITOR_MACRO_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\nstruct ext_macro;\nstruct macro_type;\nstruct editor_config_info;\n\nvoid free_macro(struct ext_macro *macro_src);\nvoid add_ext_macro(struct editor_config_info *conf, const char *name,\n const char *line_data, const char *label);\nconst struct ext_macro *find_macro(const struct editor_config_info *conf,\n const char *name, int *next);\n\n#ifndef CONFIG_DEBYTECODE\nunion variable_storage *find_macro_variable(const char *name,\n const struct macro_type *m);\nchar *skip_whitespace(char *src);\nchar *skip_to_next(char *src, char t, char a, char b);\n#endif\n\n__M_END_DECLS\n\n#endif // __EDITOR_MACRO_H\n"
  },
  {
    "path": "src/editor/macro_struct.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_MACRO_STRUCT_H\n#define __EDITOR_MACRO_STRUCT_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\nenum variable_type\n{\n  number,\n  string,\n  character,\n  color\n};\n\nenum reference_mode\n{\n  decimal,\n  hexidecimal\n};\n\nunion variable_storage\n{\n  int int_storage;\n  char *str_storage;\n};\n\nstruct macro_variable\n{\n  union variable_storage storage;\n  union variable_storage def;\n  char *name;\n};\n\nstruct macro_type\n{\n  int type_attributes[16];\n  enum variable_type type;\n  int num_variables;\n  struct macro_variable *variables;\n  struct macro_variable **variables_sorted;\n};\n\nstruct macro_variable_reference\n{\n  struct macro_type *type;\n  union variable_storage *storage;\n  enum reference_mode ref_mode;\n};\n\nstruct ext_macro\n{\n  char *name;\n  char *label;\n  int num_lines;\n  char ***lines;\n  struct macro_variable_reference **variable_references;\n  int *line_element_count;\n  int num_types;\n  int total_variables;\n  struct macro_type types[32];\n  char *text;\n};\n\n__M_END_DECLS\n\n#endif // __EDITOR_MACRO_STRUCT_H\n"
  },
  {
    "path": "src/editor/pal_ed.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2017-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Palette editor */\n\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <math.h>\n\n#include \"../core.h\"\n#include \"../data.h\"\n#include \"../event.h\"\n#include \"../graphics.h\"\n#include \"../intake_num.h\"\n#include \"../util.h\"\n#include \"../window.h\"\n#include \"../io/vio.h\"\n\n#include \"configure.h\"\n#include \"graphics.h\"\n#include \"pal_ed.h\"\n#include \"window.h\"\n\n// Color editor (right) and 16-color palette (left)\n\n//----------------------------------//------------------------------------------/\n// ##..##..##..##..##..##..##..##.. // Color # 000           RGB  HSL  CIELAB   /\n// ##..##..##..##..##..#1.1#1.1#1.1 // Red   0 [----|----|----|----|----|----|] /\n// #0.1#2.3#4.5#6.7#8.9#0.1#2.3#4.5 // G     0 [----|----|----|----|----|----|] /\n// ##..##..##..##..##..##..##..##.. // B     0 [----|----|----|----|----|----|] /\n//-^^-------------------------------//------------------------------------------/\n\n// 16-color help\n\n//------------------------------------------------------------------/\n//\n//  %- Select color    Alt+D- Default pal.      PgUp- Prev. mode\n//  R- Increase Red    Alt+R- Decrease Red      PgDn- Next mode\n//  G- Increase Green  Alt+G- Decrease Green\n//  B- Increase Blue   Alt+B- Decrease Blue        Q- Quit editing  /\n//  A- Increase All    Alt+A- Decrease All\n//  0- Blacken color   Alt+H- Hide menu          Tab- Switch to\n//  F2- Store color    Alt+I- Import                  temp palette  /\n//  F3- Place color    Alt+X- Export                                /\n//                                                   /--------------/\n//  Left click-       edit component/activate        | Buffer ##### /\n//  Right click+Drag- change color                   |        ##### /\n//                                                   |        ##### /\n//---------------------------------------------------+--------------/\n\n// 256-color subpalette\n\n//----------------------------------/\n// Subpal.  1:123 2:123 3:123 4:123 /\n//  # 000   ##### ##### ##### ##### /\n//          ##### ##### ##### ##### /\n// Hex: FF  ##### ##### ##### ##### /\n//----------------------------------/\n\n// 256-color palette\n\n//----------------------------------/ 36*18, color pos + 8\n// [][][][][][][][][][][][][][][][] /\n// ... 16 + 2\n\n// 256-color help\n\n//------------------------------------------/\n// %%- Select color   Alt+D- Default pal.   /\n// R- Increase Red    Alt+R- Decrease Red   /\n// G- Increase Green  Alt+G- Decrease Green /\n// B- Increase Blue   Alt+B- Decrease Blue  /\n// A- Increase All    Alt+A- Decrease All   /\n// 0- Blacken color   Alt+H- Hide menu      /\n// F2- Store color       F5- Store colors\n// F3- Place color       F6- Load colors\n// Ins- Cursors (off)  PgUp- Prev. mode\n// Tab- Switch to      PgDn- Next mode\n//      temp. palette Alt+I- Import\n// Q- Quit editing    Alt+X- Export\n//\n\n// Mode 3- this replaces 'Hide menu'/'Import'/'Export'...\n//\n//Space- Subpalette\n//   F5- Store colors\n//   F6- Place colors\n//   F7- Store indices\n//   F8- Place indices\n//  1-4- Current index\n//       to subpalette\n\n//                           /--------------/\n// Left click- activate      | Buffer ##### /\n// Right click- color (drag) | 1####3 ##### /\n// Middle/wheel- subpalette  | 2####4 ##### /\n//---------------------------+--------------/\n\n// Note: the help menu mouse functionality has been broken since 2.80.\n\n#define CHAR_LINE_HORIZ '\\xC4'\n#define CHAR_CORNER_TL  '\\xDA'\n#define CHAR_CORNER_TR  '\\xBF'\n#define CHAR_CORNER_BL  '\\xC0'\n#define CHAR_CORNER_BR  '\\xD9'\n\nstruct color_status\n{\n  unsigned char r;\n  unsigned char g;\n  unsigned char b;\n\n  unsigned int h;\n  unsigned char s;\n  unsigned char l;\n\n  unsigned char CL;\n  int Ca;\n  int Cb;\n};\n\nstatic boolean saved_color_active = false;\nstatic boolean saved_subpalette_active = false;\nstatic boolean saved_subpalette_display = false;\n\nstatic struct color_status saved_color;\nstatic struct color_status saved_subpal[4];\n\nstatic int saved_indices[4] =\n{\n  0, 1, 2, 3\n};\n\nstatic boolean startup = false;\nstatic boolean minimal_help = false;\nstatic boolean subpalette_cursors = true;\n\nstatic unsigned int current_id = 0;\nstatic unsigned int current_subpalette = 0;\nstatic int current_mode_id = 0;\n\nstatic int temp_palette_mode = -1;\nstatic char temp_palette[SMZX_PAL_SIZE * 3];\nstatic char temp_indices[SMZX_PAL_SIZE * 4];\n\nstatic char pal_filename[MAX_PATH];\nstatic char palidx_filename[MAX_PATH];\n\nstruct pal_ed_context\n{\n  context ctx;\n  struct color_status internal_palette[SMZX_PAL_SIZE];\n  char game_palette_backup[SMZX_PAL_SIZE * 3];\n  char game_indices[SMZX_PAL_SIZE * 4];\n  char *current_indices;\n  enum\n  {\n    GAME_PALETTE,\n    TEMP_PALETTE,\n  } current;\n  //uint32_t cursor_fg_layer[5];\n  //uint32_t cursor_bg_layer[5];\n  int editing_component;\n};\n\nstruct pal_ed_subcontext\n{\n  subcontext ctx;\n  struct pal_ed_context *pal_ed;\n  // Border corner coordinates\n  int border_x;\n  int border_y;\n  int border_x2;\n  int border_y2;\n  // Start of contents coordinates\n  int x;\n  int y;\n};\n\nstatic const char *palette_labels[] =\n{\n  \"game palette\",\n  \"temp palette\"\n};\n\nstatic const char *const pal_ext[] = { \".PAL\", NULL };\nstatic const char *const idx_ext[] = { \".PALIDX\", NULL };\n\n\n// -----------------------------------------------------------------------------\n\n\n/**\n * RGB-HSL conversion functions.\n */\n\nstatic void rgb_to_hsl(struct color_status *current)\n{\n  double r = (double)current->r / 63.0;\n  double g = (double)current->g / 63.0;\n  double b = (double)current->b / 63.0;\n\n  double M = MAX(r, MAX(g, b));\n  double m = MIN(r, MIN(g, b));\n  double c = M - m;\n\n  double h =\n   (c == 0) ? 0 :\n   (M == r) ? fmod((g - b)/c + 6, 6.0)  : // Add 6 to bypass C's shit modulo\n   (M == g) ? ((b - r)/c) + 2           :\n   (M == b) ? ((r - g)/c) + 4           : 0;\n\n  double l = (M + m) / 2.0;\n\n  current->h = (unsigned int) round( fmod(h * 60, 360.0) );\n\n  current->s = (unsigned int) round( l < 1  ?  c/(1 - fabs(M+m-1)) * 100 : 0 );\n\n  current->l = (unsigned int) round( l * 100 );\n}\n\nstatic void hsl_to_rgb(struct color_status *current)\n{\n  double l = (double)current->l / 100.0;\n  double s = (double)current->s / 100.0;\n  double h = (double)current->h / 60.0;\n\n  double c = (1 - fabs(2 * l - 1)) * s;\n\n  double m = l - c / 2.0;\n\n  double x = c * (1 - fabs(fmod(h, 2.0) - 1));\n\n  int fh = floor(h);\n\n  double r =\n   (fh == 0) || (fh == 5) ? c+m :\n   (fh == 1) || (fh == 4) ? x+m : m;\n\n  double g =\n   (fh == 1) || (fh == 2) ? c+m :\n   (fh == 0) || (fh == 3) ? x+m : m;\n\n  double b =\n   (fh == 3) || (fh == 4) ? c+m :\n   (fh == 2) || (fh == 5) ? x+m : m;\n\n  current->r = (unsigned char) round(r * 63.0);\n  current->g = (unsigned char) round(g * 63.0);\n  current->b = (unsigned char) round(b * 63.0);\n}\n\n/**\n * RGB-CIELAB conversion functions.\n */\n\nstatic const double Xn = 0.9505;\nstatic const double Yn = 1.0000;\nstatic const double Zn = 1.0890;\n\nstatic inline double srgb_comp_to_linear(double c)\n{\n  return (c <= 0.04045)  ?  c / 12.92  :  pow((c + 0.055)/(1.055), 2.4);\n}\n\nstatic inline double srgb_linear_to_comp(double l)\n{\n  return (l <= 0.0031308)  ?  l * 12.92  :  (1.055)*pow(l, 1/2.4) - 0.055;\n}\n\nstatic inline double xyz_to_lab_comp_transform(double c)\n{\n  return (c > 0.008856)  ?  cbrt(c)  :  (903.3 * c + 16)/116.0;\n}\n\nstatic inline double lab_to_xyz_comp_transform(double c)\n{\n  return (c > 0.205893)  ?  pow(c, 3.0)  :  0.128414 * (c - 0.137931);\n}\n\nstatic void rgb_to_lab(struct color_status *current)\n{\n  double r = srgb_comp_to_linear( (double)(current->r) / 63.0 );\n  double g = srgb_comp_to_linear( (double)(current->g) / 63.0 );\n  double b = srgb_comp_to_linear( (double)(current->b) / 63.0 );\n\n  double x = 0.4124564*r + 0.3575761*g + 0.1804375*b;\n  double y = 0.2126729*r + 0.7151522*g + 0.0721750*b;\n  double z = 0.0193339*r + 0.1191920*g + 0.9503041*b;\n\n  double fx = xyz_to_lab_comp_transform( x / Xn );\n  double fy = xyz_to_lab_comp_transform( y / Yn );\n  double fz = xyz_to_lab_comp_transform( z / Zn );\n\n  current->CL = (unsigned int) round( 116 * fy - 16 );\n  current->Ca = (int) round( 500 * (fx - fy) );\n  current->Cb = (int) round( 200 * (fy - fz) );\n}\n\nstatic void lab_to_rgb(struct color_status *current)\n{\n  double CL = (double)(current->CL);\n  double Ca = current->Ca;\n  double Cb = current->Cb;\n\n  double fy = (CL + 16) / 116.0;\n  double fx = fy + Ca / 500.0;\n  double fz = fy - Cb / 200.0;\n\n  double x = Xn * lab_to_xyz_comp_transform( fx );\n  double y = Yn * lab_to_xyz_comp_transform( fy );\n  double z = Zn * lab_to_xyz_comp_transform( fz );\n\n  double r =  3.2404542*x + -1.5371385*y + -0.4985314*z;\n  double g = -0.9692660*x +  1.8760108*y +  0.0415560*z;\n  double b =  0.0556434*x + -0.2040259*y +  1.0572252*z;\n\n  r = srgb_linear_to_comp( r );\n  g = srgb_linear_to_comp( g );\n  b = srgb_linear_to_comp( b );\n\n  current->r = (unsigned int) round(CLAMP(r, 0.0, 1.0) * 63.0);\n  current->g = (unsigned int) round(CLAMP(g, 0.0, 1.0) * 63.0);\n  current->b = (unsigned int) round(CLAMP(b, 0.0, 1.0) * 63.0);\n}\n\n/**\n * Color management functions.\n */\n\nstatic void load_color(struct color_status *current, int id)\n{\n  get_rgb(id, &(current->r), &(current->g), &(current->b));\n  rgb_to_hsl(current);\n  rgb_to_lab(current);\n}\n\nstatic void store_color_pos(struct color_status *current, int id)\n{\n  set_rgb(id, current->r, current->g, current->b);\n}\n\nstatic void store_color_rgb(struct color_status *current)\n{\n  rgb_to_hsl(current);\n  rgb_to_lab(current);\n  set_rgb(current_id, current->r, current->g, current->b);\n}\n\nstatic void store_color_hsl(struct color_status *current)\n{\n  hsl_to_rgb(current);\n  rgb_to_lab(current);\n  set_rgb(current_id, current->r, current->g, current->b);\n}\n\nstatic void store_color_lab(struct color_status *current)\n{\n  lab_to_rgb(current);\n  rgb_to_hsl(current);\n  set_rgb(current_id, current->r, current->g, current->b);\n}\n\nstatic int get_color_rgb(struct color_status *current, int component)\n{\n  switch(component)\n  {\n    case 0: return (int)current->r;\n    case 1: return (int)current->g;\n    case 2: return (int)current->b;\n  }\n  return -1;\n}\n\nstatic int get_color_hsl(struct color_status *current, int component)\n{\n  switch(component)\n  {\n    case 0: return (int)current->h;\n    case 1: return (int)current->s;\n    case 2: return (int)current->l;\n  }\n  return -1;\n}\n\nstatic int get_color_lab(struct color_status *current, int component)\n{\n  switch(component)\n  {\n    case 0: return (int)current->CL;\n    case 1: return (int)current->Ca;\n    case 2: return (int)current->Cb;\n  }\n  return -1;\n}\n\nstatic void set_color_rgb(struct color_status *current, int component,\n int value)\n{\n  switch(component)\n  {\n    case 0:\n      current->r = value;\n      break;\n\n    case 1:\n      current->g = value;\n      break;\n\n    case 2:\n      current->b = value;\n      break;\n  }\n  store_color_rgb(current);\n}\n\nstatic void set_color_hsl(struct color_status *current, int component,\n int value)\n{\n  switch(component)\n  {\n    case 0:\n      current->h = value;\n      break;\n\n    case 1:\n      current->s = value;\n      break;\n\n    case 2:\n      current->l = value;\n      break;\n  }\n  store_color_hsl(current);\n}\n\nstatic void set_color_lab(struct color_status *current, int component,\n int value)\n{\n  switch(component)\n  {\n    case 0:\n      current->CL = value;\n      break;\n\n    case 1:\n      current->Ca = value;\n      break;\n\n    case 2:\n      current->Cb = value;\n      break;\n  }\n  store_color_lab(current);\n}\n\nstruct color_mode_component\n{\n  const char *name_long;  // Long name for menu, max 5 chars\n  const char *name_short; // Short name for selector, max 3 chars\n  const char *name_key;   // Key name, max 1 char\n  enum keycode key;       // Keycode to trigger inc/dec\n  char left_col;\n  char right_col;\n  int min_val;\n  int max_val;\n  boolean wrap;\n};\n\nstruct color_mode\n{\n  const char *name;\n  int draw_x;\n  struct color_mode_component components[3];\n  void (*store_function)(struct color_status *);\n  int (*get_function)(struct color_status *, int);\n  void (*set_function)(struct color_status *, int, int);\n  enum keycode all_key;\n};\n\nstatic const struct color_mode mode_list[] =\n{\n  { \"RGB\", 22,\n    {\n      { \"Red\",   \"Red \", \"R\", IKEY_r, 12,  0,    0,  63, false },\n      { \"Green\", \"Grn \", \"G\", IKEY_g, 10,  0,    0,  63, false },\n      { \"Blue\",  \"Blu \", \"B\", IKEY_b,  9,  0,    0,  63, false },\n    },\n    store_color_rgb,\n    get_color_rgb,\n    set_color_rgb,\n    IKEY_a\n  },\n\n  { \"HSL\", 27,\n    {\n      { \"Hue\",   \"Hue\",  \"C\", IKEY_c,  4,  4,    0, 359, true  },\n      { \"Sat.\",  \"Sat\",  \"S\", IKEY_s,  7,  0,    0, 100, false },\n      { \"Light\", \"Lgt\",  \"V\", IKEY_v, 15,  0,    0, 100, false },\n    },\n    store_color_hsl,\n    get_color_hsl,\n    set_color_hsl,\n    IKEY_UNKNOWN\n  },\n\n  { \"CIELAB\", 32,\n    {\n      { \"L*\",    \"L*\",   \"V\", IKEY_v, 15,  0,    0, 100, false },\n      { \"a*\",    \"a*\",   \"A\", IKEY_a,  4,  2, -128, 128, false },\n      { \"b*\",    \"b*\",   \"B\", IKEY_b,  6,  1, -128, 128, false },\n    },\n    store_color_lab,\n    get_color_lab,\n    set_color_lab,\n    IKEY_UNKNOWN\n  },\n};\n\n/**\n * Utility functions.\n */\n\nstatic struct color_status *get_current_color(struct pal_ed_context *pal_ed)\n{\n  return &(pal_ed->internal_palette[current_id]);\n}\n\nstatic struct color_mode *get_current_mode(struct pal_ed_context *pal_ed)\n{\n  return (struct color_mode *)&(mode_list[current_mode_id]);\n}\n\nstatic boolean get_current_indices(struct pal_ed_context *pal_ed,\n int *a, int *b, int *c, int *d)\n{\n  int screen_mode = get_screen_mode();\n  if(screen_mode < 2)\n    return false;\n\n  if(screen_mode == 2)\n  {\n    int lo = current_id & (0x0F);\n    int hi = (current_id & (0xF0)) >> 4;\n    *a = (hi << 4) | hi;\n    *b = (lo << 4) | hi;\n    *c = (hi << 4) | lo;\n    *d = (lo << 4) | lo;\n  }\n  else\n  {\n    if(!pal_ed->current_indices)\n      return false;\n\n    *a = pal_ed->current_indices[current_subpalette * 4 + 0];\n    *b = pal_ed->current_indices[current_subpalette * 4 + 2];\n    *c = pal_ed->current_indices[current_subpalette * 4 + 1];\n    *d = pal_ed->current_indices[current_subpalette * 4 + 3];\n  }\n  return true;\n}\n\n#define MOUSE_IN(b_x, b_y, b_w, b_h) \\\n  ((mouse_x >= (b_x)) && (mouse_x < (b_x) + (b_w)) && \\\n   (mouse_y >= (b_y)) && (mouse_y < (b_y) + (b_h)))\n\n\n// -----------------------------------------------------------------------------\n\n\nstatic char hue_chars[32] =\n{\n  0xDB, 0xB0, 0xB0, 0xB1, 0xB2,  0xDB, 0xDB, 0xB0, 0xB1, 0xB2,\n  0xDB, 0xDB, 0xB0, 0xB1, 0xB2,  0xDB, 0xB0, 0xB1, 0xB1, 0xB2,\n  0xB2, 0xDB, 0xB0, 0xB1, 0xB2,  0xDB, 0xDB, 0xB0, 0xB1, 0xB2,\n  0xDB, 0xDB\n};\n\nstatic char hue_colors[32] =\n{\n  0xCC, 0xC6, 0xCE, 0xCE, 0xCE,  0xEE, 0xEE, 0xEA, 0xEA, 0xEA,\n  0xAA, 0xAA, 0xAB, 0xAB, 0xAB,  0xBB, 0xB3, 0xB3, 0xB9, 0xB9,\n  0x39, 0x99, 0x9D, 0x9D, 0x9D,  0xDD, 0xDD, 0xDC, 0xDC, 0xDC,\n  0xCC, 0xCC\n};\n\nstatic void draw_hue_bar(int value, unsigned int x, unsigned int y,\n int min_val, int max_val)\n{\n  int i;\n\n  value = ((value - min_val)*31 + (max_val - min_val)/2) / (max_val - min_val);\n\n  for(i = 0; i < 32; i++)\n  {\n    draw_char(hue_chars[i], hue_colors[i], x+i, y);\n  }\n\n  // TODO this could be a layer\n  draw_char(0xDB, 0xFF, x+value, y);\n}\n\nstatic void draw_color_bar(int value, unsigned int x, unsigned int y,\n int min_val, int max_val, char left_col, char right_col)\n{\n  char bg_col;\n  char fg_col;\n  int i;\n  int j;\n\n  value = ((value - min_val)*63 + (max_val - min_val)/2) / (max_val - min_val);\n\n  for(i = 0, j = 0; i < 32; i++)\n  {\n    bg_col =\n     (value > j) ? left_col  :\n     (value < j) ? right_col : 15;\n    j++;\n\n    fg_col =\n     (value > j) ? left_col  :\n     (value < j) ? right_col : 15;\n    j++;\n\n    draw_char('\\xDE', (bg_col << 4) + fg_col, x+i, y);\n  }\n}\n\n/**\n * Draw the components inside of the color editor.\n */\n\nstatic void draw_color_components(struct pal_ed_subcontext *current)\n{\n  struct pal_ed_context *pal_ed = current->pal_ed;\n  struct color_status *current_color = get_current_color(pal_ed);\n  struct color_mode *current_mode = get_current_mode(pal_ed);\n\n  const struct color_mode_component *c;\n  char buffer[5];\n  int value;\n  int i;\n\n  for(i = 0; i < 3; i++)\n  {\n    value = current_mode->get_function(current_color, i);\n    c = &(current_mode->components[i]);\n\n    sprintf(buffer, \"%4d\", value);\n\n    write_string(\n     buffer,\n     current->x + 3,\n     current->y + 1 + i,\n     DI_GREY_NUMBER,\n     WR_NONE\n    );\n\n    write_string(\n     c->name_short,\n     current->x,\n     current->y + 1 + i,\n     DI_GREY_TEXT,\n     WR_NONE\n    );\n\n    if(c->min_val == 0 && c->max_val == 359)\n    {\n      // Special case - the hue bar\n      draw_hue_bar(value, current->x + 8, current->y + 1 + i,\n       c->min_val, c->max_val);\n    }\n\n    else\n    {\n      // Otherwise, draw a regular bar\n      draw_color_bar(value, current->x + 8, current->y + 1 + i,\n       c->min_val, c->max_val, c->left_col, c->right_col);\n    }\n  }\n}\n\n/**\n * Draw the color editor.\n */\n\nstatic boolean color_editor_draw(subcontext *ctx)\n{\n  struct pal_ed_subcontext *current = (struct pal_ed_subcontext *)ctx;\n  char color;\n  int i;\n\n  draw_window_box(\n   current->border_x,  current->border_y,\n   current->border_x2, current->border_y2,\n   DI_GREY, DI_GREY_DARK, DI_GREY_CORNER, true, true\n  );\n\n  // Write Color #\n  write_string(\n   \"Color #\",\n   current->x,\n   current->y,\n   DI_GREY_TEXT,\n   WR_NONE\n  );\n\n  write_number(current_id, DI_GREY_TEXT,\n   current->x + 10, current->y, 3, 1, 10);\n\n  // Modes\n  for(i = 0; i < (int)ARRAY_SIZE(mode_list); i++)\n  {\n    color = (i == current_mode_id) ? DI_GREY : DI_GREY_DARK;\n\n    write_string(\n     mode_list[i].name,\n     current->x + mode_list[i].draw_x,\n     current->y,\n     color,\n     WR_NONE\n    );\n  }\n\n  draw_color_components(current);\n  return true;\n}\n\nstatic void inc_component(struct color_status *current_color,\n struct color_mode *current_mode, int component, int mod)\n{\n  struct color_mode_component *c = &(current_mode->components[component]);\n  int value = current_mode->get_function(current_color, component) + mod;\n\n  if(value < c->min_val)\n  {\n    if(c->wrap)\n      value = c->max_val;\n    else\n      value = c->min_val;\n  }\n  else\n\n  if(value > c->max_val)\n  {\n    if(c->wrap)\n      value = c->min_val;\n    else\n      value = c->max_val;\n  }\n\n  current_mode->set_function(current_color, component, value);\n}\n\n/**\n * Key function for the color editor.\n * These keys are mostly configured on a per-color mode basis and need\n * to be handled specially.\n */\n\nstatic boolean color_editor_key(subcontext *ctx, int *key)\n{\n  struct pal_ed_subcontext *current = (struct pal_ed_subcontext *)ctx;\n  struct pal_ed_context *pal_ed = current->pal_ed;\n  struct color_status *current_color = get_current_color(pal_ed);\n  struct color_mode *current_mode = get_current_mode(pal_ed);\n  int i;\n\n  // Components\n  for(i = 0; i < 3; i++)\n  {\n    if(*key == (int)current_mode->components[i].key)\n    {\n      if(get_alt_status(keycode_internal))\n        inc_component(current_color, current_mode, i, -1);\n\n      else\n        inc_component(current_color, current_mode, i, 1);\n\n      return true;\n    }\n  }\n\n  // All\n  if(current_mode->all_key && (int)current_mode->all_key == *key)\n  {\n    if(get_alt_status(keycode_internal))\n    {\n      inc_component(current_color, current_mode, 0, -1);\n      inc_component(current_color, current_mode, 1, -1);\n      inc_component(current_color, current_mode, 2, -1);\n    }\n    else\n    {\n      inc_component(current_color, current_mode, 0, 1);\n      inc_component(current_color, current_mode, 1, 1);\n      inc_component(current_color, current_mode, 2, 1);\n    }\n    return true;\n  }\n\n  switch(*key)\n  {\n    case IKEY_F2:\n    {\n      // Ignore Alt+F2, Ctrl+F2\n      if(get_alt_status(keycode_internal) || get_ctrl_status(keycode_internal))\n        break;\n\n      memcpy(&saved_color, current_color, sizeof(struct color_status));\n      saved_subpalette_display = false;\n      saved_color_active = true;\n      return true;\n    }\n\n    case IKEY_F3:\n    {\n      if(saved_color_active)\n      {\n        memcpy(current_color, &saved_color, sizeof(struct color_status));\n        store_color_rgb(current_color);\n        saved_subpalette_display = false;\n      }\n      return true;\n    }\n\n    case IKEY_0:\n    {\n      current_color->r = 0;\n      current_color->g = 0;\n      current_color->b = 0;\n      store_color_rgb(current_color);\n      return true;\n    }\n\n    case IKEY_PAGEUP:\n    {\n      if(current_mode_id == 0)\n        current_mode_id = ARRAY_SIZE(mode_list);\n\n      current_mode_id--;\n      return true;\n    }\n\n    case IKEY_PAGEDOWN:\n    {\n      current_mode_id++;\n\n      if(current_mode_id == ARRAY_SIZE(mode_list))\n        current_mode_id = 0;\n\n      return true;\n    }\n  }\n  return false;\n}\n\n/**\n * Callback for component editing.\n */\n\nstatic void edit_component_callback(subcontext *ctx, int new_value)\n{\n  struct pal_ed_context *pal_ed = ((struct pal_ed_subcontext *)ctx)->pal_ed;\n  struct color_status *current_color = get_current_color(pal_ed);\n  struct color_mode *current_mode = get_current_mode(pal_ed);\n  int component = pal_ed->editing_component;\n\n  current_mode->set_function(current_color, component, new_value);\n}\n\n/**\n * Click function for color editor.\n */\n\nstatic boolean color_editor_click(subcontext *ctx, int *key, int button,\n int mouse_x, int mouse_y)\n{\n  struct pal_ed_subcontext *current = (struct pal_ed_subcontext *)ctx;\n\n  if(button == MOUSE_BUTTON_LEFT)\n  {\n    // Component numbers\n\n    if(MOUSE_IN(current->x, current->y + 1, 7, 3))\n    {\n      int component = mouse_y - current->y - 1;\n      struct pal_ed_context *pal_ed = current->pal_ed;\n      struct color_status *current_color = get_current_color(pal_ed);\n      struct color_mode *current_mode = get_current_mode(pal_ed);\n      struct color_mode_component *c = &(current_mode->components[component]);\n      int value;\n\n      current->pal_ed->editing_component = component;\n      value = current_mode->get_function(current_color, component);\n\n      intake_num(ctx, value, c->min_val, c->max_val,\n       current->x + 3, mouse_y, 3, DI_GREY_EDIT, edit_component_callback);\n\n      return true;\n    }\n    else\n\n    // Mode select\n\n    if(mouse_y == current->y)\n    {\n      int len;\n      int i;\n\n      for(i = 0; i < (int)ARRAY_SIZE(mode_list); i++)\n      {\n        const struct color_mode *m = &(mode_list[i]);\n        len = strlen(m->name);\n\n        if(MOUSE_IN(current->x + m->draw_x, current->y, len, 1))\n        {\n          current_mode_id = i;\n          return true;\n        }\n      }\n    }\n  }\n  return false;\n}\n\n/**\n * Drag function for color editor.\n */\n\nstatic boolean color_editor_drag(subcontext *ctx, int *key, int button,\n int mouse_x, int mouse_y)\n{\n  struct pal_ed_subcontext *current = (struct pal_ed_subcontext *)ctx;\n  struct color_status *current_color = get_current_color(current->pal_ed);\n  struct color_mode *current_mode = get_current_mode(current->pal_ed);\n\n  int mouse_px;\n  int mouse_py;\n\n  if(get_mouse_held(MOUSE_BUTTON_LEFT))\n  {\n    // Component bars\n\n    // extra char on each side of the bar past the actual bounds to\n    // make setting to the minimum and maximum values easier.\n\n    get_mouse_pixel_position(&mouse_px, &mouse_py);\n\n    if(MOUSE_IN(current->x + 7, current->y + 1, 34, 3))\n    {\n      const struct color_mode_component *c;\n      int component = mouse_y - current->y - 1;\n\n      double value =\n       CLAMP((mouse_x - current->x - 8) + ((mouse_px % 8) / 8.0), 0, 32);\n\n      c = &(current_mode->components[component]);\n\n      value = (value * (c->max_val - c->min_val) + 16) / 32.0 + c->min_val;\n\n      current_mode->set_function(current_color, component, (int)value);\n\n      // Snap the mouse to the center of the bar.\n      warp_mouse_pixel_y(mouse_y * 14 + 7);\n      return -1;\n    }\n  }\n  return false;\n}\n\n/**\n * Start the color editor subcontext.\n */\n\nstatic subcontext *create_color_editor(struct pal_ed_context *pal_ed)\n{\n  struct pal_ed_subcontext *sub = cmalloc(sizeof(struct pal_ed_subcontext));\n  struct context_spec spec;\n\n  sub->pal_ed = pal_ed;\n  sub->border_x = 36;\n  sub->border_y = 0;\n  sub->border_x2 = 79;\n  sub->border_y2 = 5;\n\n  sub->x = sub->border_x + 2;\n  sub->y = sub->border_y + 1;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw   = color_editor_draw;\n  spec.key    = color_editor_key;\n  spec.click  = color_editor_click;\n  spec.drag   = color_editor_drag;\n\n  create_subcontext((subcontext *)sub, (context *)pal_ed, &spec);\n  return (subcontext *)sub;\n}\n\n\n// -----------------------------------------------------------------------------\n\n/**\n * Draw the buffer portion of the menu window.\n */\nstatic void menu_buffer_draw(int x, int y, boolean show_indices)\n{\n  static unsigned int timer = 0;\n  int mode = get_screen_mode();\n  int i;\n\n  draw_window_box(\n    x, y, x + 15, y + 4,\n    DI_GREY, DI_GREY_DARK, DI_GREY_CORNER, false, false\n  );\n\n  write_string(\"Buffer\", x + 2, y + 1, DI_GREY_TEXT, WR_NONE);\n  write_string(\"     \\n     \\n     \", x + 9, y + 1, 0x55, WR_NEWLINE);\n\n  if(mode >= 2 && saved_subpalette_display)\n  {\n    unsigned int which = ((timer++) / 60) % 4;\n    uint8_t fg_color;\n    struct color_status *col = &saved_subpal[which];\n\n    set_protected_rgb(5, col->r, col->g, col->b);\n\n    fg_color =\n     (get_color_luma(graphics.protected_pal_position + 5) < 128) ? 31 : 16;\n    draw_char(which + 49, (0x50) | fg_color, x + 11, y + 1);\n  }\n  else\n  {\n    set_protected_rgb(5, saved_color.r, saved_color.g, saved_color.b);\n    timer = 0;\n  }\n\n  if(show_indices && mode >= 2)\n  {\n    char c = (mode == 2) ? CHAR_SMZX_C2 : 0;\n    int x2;\n    int y2;\n\n    write_string(\"1    3\\n2    4\", x + 2, y + 2, DI_GREY_CORNER, WR_NEWLINE);\n    for(i = 0; i < 4; i++)\n    {\n      x2 = x + 3 + (i/2*2);\n      y2 = y + 2 + (i%2);\n\n      select_layer(UI_LAYER);\n      erase_char(x2, y2);\n      erase_char(x2 + 1, y2);\n\n      select_layer(GAME_UI_LAYER);\n      draw_char(c, saved_indices[i], x2, y2);\n      draw_char(c, saved_indices[i], x2 + 1, y2);\n    }\n  }\n  select_layer(UI_LAYER);\n}\n\n/**\n * Draw the 16 color editor menu window.\n */\n\nstatic boolean menu_16_draw(subcontext *ctx)\n{\n  struct pal_ed_subcontext *menu = (struct pal_ed_subcontext *)ctx;\n  struct pal_ed_context *pal_ed = menu->pal_ed;\n  struct color_mode *current_mode = get_current_mode(menu->pal_ed);\n  struct color_mode_component *c;\n  int x;\n  int y;\n  int i;\n\n  if(minimal_help)\n    return false;\n\n  draw_window_box(\n    menu->border_x,  menu->border_y,\n    menu->border_x2, menu->border_y2,\n    DI_GREY_DARK, DI_GREY, DI_GREY_CORNER, true, true\n  );\n\n  // Write menu\n  write_string(\n    \"\\x1d- Select color    Alt+D- Default pal.     PgUp- Prev. mode\\n\"\n    \" - Increase        Alt+ - Decrease         PgDn- Next mode\\n\"\n    \" - Increase        Alt+ - Decrease            \\n\"\n    \" - Increase        Alt+ - Decrease            Q- Quit editing\\n\"\n    \"\\n\"\n    \"0- Blacken color   Alt+H- Hide menu         Tab- Switch to\\n\"\n    \"F2- Store color    Alt+I- Import                 XXXX palette\\n\"\n    \"F3- Place color    Alt+X- Export\\n\"\n    \"\\n\"\n    \"Left click-       edit component/activate\\n\"\n    \"Right click+Drag- change color\\n\",\n    menu->x,\n    menu->y,\n    DI_GREY_TEXT,\n    WR_NEWLINE\n  );\n\n  write_string(palette_labels[!pal_ed->current], menu->x + 49, menu->y + 6,\n   DI_GREY_TEXT, WR_NONE);\n\n  // Component instructions\n  y = menu->y + 1;\n  for(i = 0; i < 3; i++, y++)\n  {\n    c = &(current_mode->components[i]);\n    x = menu->x;\n\n    write_string(c->name_key, x, y, DI_GREY_TEXT, WR_NONE);\n    x += 12;\n\n    write_string(c->name_long, x, y, DI_GREY_TEXT, WR_NONE);\n    x += 11;\n\n    write_string(c->name_key, x, y, DI_GREY_TEXT, WR_NONE);\n    x += 12;\n\n    write_string(c->name_long, x, y, DI_GREY_TEXT, WR_NONE);\n  }\n\n  // All\n  if(current_mode->all_key)\n  {\n    write_string(\n     \"A- Increase All    Alt+A- Decrease All\",\n     menu->x,\n     menu->y + 4,\n     DI_GREY_TEXT,\n     WR_NEWLINE\n    );\n  }\n\n  // Buffer\n  menu_buffer_draw(menu->border_x2 - 15, menu->border_y2 - 4, false);\n  return true;\n}\n\n/**\n * Create the menu window for the 16 color editor.\n */\n\nstatic subcontext *create_menu_16(struct pal_ed_context *pal_ed)\n{\n  struct pal_ed_subcontext *sub = cmalloc(sizeof(struct pal_ed_subcontext));\n  struct context_spec spec;\n\n  sub->pal_ed = pal_ed;\n  sub->border_x = 7;\n  sub->border_y = 7;\n  sub->border_x2 = 73;\n  sub->border_y2 = 21;\n\n  sub->x = sub->border_x + 3;\n  sub->y = sub->border_y + 2;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw = menu_16_draw;\n\n  create_subcontext((subcontext *)sub, (context *)pal_ed, &spec);\n  return (subcontext *)sub;\n}\n\n\n// -----------------------------------------------------------------------------\n\n\n/**\n * Draw the palette window for the 16 color editor.\n */\n\nstatic boolean palette_16_draw(subcontext *ctx)\n{\n  struct pal_ed_subcontext *pal = (struct pal_ed_subcontext *)ctx;\n  struct pal_ed_context *pal_ed = pal->pal_ed;\n  int screen_mode = get_screen_mode();\n  unsigned int bg_color;\n  unsigned int fg_color;\n  unsigned int chr;\n  int i;\n  int x;\n  int y;\n\n  draw_window_box(\n    pal->border_x,  pal->border_y,\n    pal->border_x2, pal->border_y2,\n    DI_GREY_DARK, DI_GREY, DI_GREY_CORNER, true, true\n  );\n\n  // Temporary palette warning\n  if(pal_ed->current != GAME_PALETTE)\n  {\n    write_string(\"TEMPORARY -- WILL NOT BE SAVED\", pal->border_x + 3,\n     pal->border_y, 0x8E, WR_NONE);\n  }\n\n  // Draw palette bars\n  for(bg_color = 0; bg_color < 16; bg_color++)\n  {\n    x = bg_color * 2 + pal->x;\n    y = pal->y;\n\n    // The foreground color is white or black in the protected palette.\n\n    fg_color = (get_color_luma(bg_color) < 128) ? 31 : 16;\n\n    // Draw the palette colors\n    for(i = 0; i < 8; i++)\n    {\n      chr = ' ';\n\n      // These look ugly in SMZX mode 1, so only draw them in regular mode.\n      if(!screen_mode)\n      {\n        if((i == 5) && (bg_color >= 10))\n          chr = '1';\n\n        if(i == 6)\n          chr = '0' + (bg_color % 10);\n      }\n\n      select_layer(UI_LAYER);\n      erase_char(x + (i/4), y + (i%4));\n\n      select_layer(GAME_UI_LAYER);\n      draw_char_mixed_pal_ext(chr, bg_color, fg_color,\n       x + (i/4), y + (i%4), PRO_CH);\n    }\n\n    select_layer(UI_LAYER);\n\n    // Clear the bottom\n    draw_char(CHAR_LINE_HORIZ, DI_GREY, x, y + 4);\n    draw_char(CHAR_LINE_HORIZ, DI_GREY, x+1, y + 4);\n\n    if(bg_color == current_id)\n    {\n      // Draw '^^'\n      write_string(\"\\x1e\\x1e\",\n       x, y + 4, DI_GREY_TEXT, WR_NONE);\n    }\n  }\n  return true;\n}\n\n/**\n * Key input for the 16 color palette window.\n */\n\nstatic boolean palette_16_key(subcontext *ctx, int *key)\n{\n  switch(*key)\n  {\n    case IKEY_LEFT:\n    case IKEY_MINUS:\n    case IKEY_KP_MINUS:\n    {\n      if(current_id > 0)\n        current_id--;\n      return true;\n    }\n\n    case IKEY_RIGHT:\n    case IKEY_EQUALS:\n    case IKEY_KP_PLUS:\n    {\n      if(current_id < 15)\n        current_id++;\n      return true;\n    }\n  }\n  return false;\n}\n\n/**\n * Translate mouse movement into a keypress and move the mouse\n * cursor back to its original position if it moved.\n */\n\nstatic void mouse_slide_cursor(int *_key, int mouse_x, int mouse_y)\n{\n  int delta_x;\n  int delta_y;\n  int key = IKEY_UNKNOWN;\n\n  get_mouse_movement(&delta_x, &delta_y);\n\n  if(abs(delta_x) > abs(delta_y))\n  {\n    if(delta_x > 0)\n      key = IKEY_RIGHT;\n\n    if(delta_x < 0)\n      key = IKEY_LEFT;\n  }\n  else\n  {\n    if(delta_y > 0)\n      key = IKEY_DOWN;\n\n    if(delta_y < 0)\n      key = IKEY_UP;\n  }\n\n  if(key != IKEY_UNKNOWN)\n  {\n    // Only warp the mouse to its old spot if there's movement registered.\n    // Otherwise, it will be unresponsive with precise mouse movements.\n    warp_mouse(mouse_x - delta_x, mouse_y - delta_y);\n\n    *_key = key;\n  }\n}\n\n/**\n * Mouse input for the 16 color palette window.\n */\n\nstatic boolean palette_16_mouse(subcontext *ctx, int *key, int button,\n int mouse_x, int mouse_y)\n{\n  struct pal_ed_subcontext *pal = (struct pal_ed_subcontext *)ctx;\n\n  if(button == MOUSE_BUTTON_LEFT &&\n   MOUSE_IN(pal->x, pal->y, 32, 4))\n  {\n    current_id = (mouse_x - pal->x) / 2;\n    return true;\n  }\n  else\n\n  if(get_mouse_status() & MOUSE_BUTTON(MOUSE_BUTTON_RIGHT))\n  {\n    mouse_slide_cursor(key, mouse_x, mouse_y);\n    return true;\n  }\n  return false;\n}\n\n/**\n * Create the 16 color palette window.\n */\n\nstatic subcontext *create_palette_16(struct pal_ed_context *pal_ed)\n{\n  struct pal_ed_subcontext *sub = cmalloc(sizeof(struct pal_ed_subcontext));\n  struct context_spec spec;\n\n  sub->pal_ed = pal_ed;\n  sub->border_x = 0;\n  sub->border_y = 0;\n  sub->border_x2 = 35;\n  sub->border_y2 = 5;\n\n  sub->x = sub->border_x + 2;\n  sub->y = sub->border_y + 1;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw   = palette_16_draw;\n  spec.key    = palette_16_key;\n  spec.click  = palette_16_mouse;\n  spec.drag   = palette_16_mouse;\n\n  create_subcontext((subcontext *)sub, (context *)pal_ed, &spec);\n  return (subcontext *)sub;\n}\n\n\n// -----------------------------------------------------------------------------\n\n\n/**\n * Draw the 256 color palette editor menu window.\n */\n\nstatic boolean menu_256_draw(subcontext *ctx)\n{\n  struct pal_ed_subcontext *menu = (struct pal_ed_subcontext *)ctx;\n  struct pal_ed_context *pal_ed = menu->pal_ed;\n  struct color_mode *current_mode = get_current_mode(menu->pal_ed);\n  struct color_mode_component *c;\n  int smzx_mode = get_screen_mode();\n  int i;\n  int x;\n  int y;\n\n  // Always enable help during smzx_mode 3; the preview area is useless.\n  if(minimal_help && smzx_mode == 2)\n    return false;\n\n  draw_window_box(\n    menu->border_x,  menu->border_y,\n    menu->border_x2, menu->border_y2,\n    DI_GREY_DARK, DI_GREY, DI_GREY_CORNER, true, true\n  );\n\n  // Write menu\n  write_string(\n    \"\\x12\\x1d- Select color   Alt+D- Default pal.   \\n\"\n    \" - Increase        Alt+ - Decrease       \\n\"\n    \" - Increase        Alt+ - Decrease       \\n\"\n    \" - Increase        Alt+ - Decrease       \\n\"\n    \"\\n\"\n    \"0- Blacken color\\n\"\n    \"F2- Store color\\n\"\n    \"F3- Place color\\n\"\n    \"Ins- Cursors (off)\\n\"\n    \"Tab- Switch to\\n\"\n    \"     XXXX palette\\n\"\n    \"Q- Quit editing\\n\"\n    \"\\n\"\n    \"Left click- activate\\n\"\n    \"Right click+Drag- color\\n\",\n    menu->x,\n    menu->y,\n    DI_GREY_TEXT,\n    WR_NEWLINE\n  );\n\n  write_string(palette_labels[!pal_ed->current], menu->x + 5, menu->y + 10,\n   DI_GREY_TEXT, WR_NONE);\n\n  if(subpalette_cursors)\n    write_string(\"(on) \", menu->x + 13, menu->y + 8, DI_GREY_TEXT, WR_NONE);\n\n  if(smzx_mode == 2)\n  {\n    // SMZX mode 2 specific help\n    write_string(\n      \"Alt+H- Hide menu\\n\"\n      \"   F5- Store colors\\n\"\n      \"   F6- Place colors\\n\"\n      \" PgUp- Prev. mode\\n\"\n      \" PgDn- Next mode\\n\"\n      \"Alt+I- Import\\n\"\n      \"Alt+X- Export\\n\",\n      menu->x + 19,\n      menu->y + 5,\n      DI_GREY_TEXT,\n      WR_NEWLINE\n    );\n  }\n  else\n  {\n    // SMZX mode 3 specific help\n    write_string(\n      \"Space- Subpalette\\n\"\n      \"   F5- Store colors\\n\"\n      \"   F6- Place colors\\n\"\n      \"   F7- Store indices\\n\"\n      \"   F8- Place indices\\n\"\n      \"  1-4- Current index\\n\"\n      \"       to subpalette\\n\",\n      menu->x + 19,\n      menu->y + 5,\n      DI_GREY_TEXT,\n      WR_NEWLINE\n    );\n    write_string(\n      \"Middle/wheel- subpalette\\n\",\n      menu->x,\n      menu->y + 15,\n      DI_GREY_TEXT,\n      WR_NEWLINE\n    );\n  }\n\n  // Component instructions\n  y = menu->y + 1;\n  for(i = 0; i < 3; i++, y++)\n  {\n    c = &(current_mode->components[i]);\n    x = menu->x;\n\n    write_string(c->name_key, x, y, DI_GREY_TEXT, WR_NONE);\n    x += 12;\n\n    write_string(c->name_long, x, y, DI_GREY_TEXT, WR_NONE);\n    x += 11;\n\n    write_string(c->name_key, x, y, DI_GREY_TEXT, WR_NONE);\n    x += 12;\n\n    write_string(c->name_long, x, y, DI_GREY_TEXT, WR_NONE);\n  }\n\n  // All\n  if(current_mode->all_key)\n  {\n    write_string(\n      \"A- Increase All    Alt+A- Decrease All\",\n      menu->x,\n      menu->y + 4,\n      DI_GREY_TEXT,\n      WR_NEWLINE\n    );\n  }\n\n  // Buffer\n  menu_buffer_draw(menu->border_x2 - 15, menu->border_y2 - 4, smzx_mode==3);\n  return true;\n}\n\n/**\n * Create the 256 color editor menu window.\n */\n\nstatic subcontext *create_menu_256(struct pal_ed_context *pal_ed)\n{\n  struct pal_ed_subcontext *sub = cmalloc(sizeof(struct pal_ed_subcontext));\n  struct context_spec spec;\n\n  sub->pal_ed = pal_ed;\n  sub->border_x = 36;\n  sub->border_y = 7;\n  sub->border_x2 = 79;\n  sub->border_y2 = 24;\n\n  sub->x = sub->border_x + 2;\n  sub->y = sub->border_y + 1;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw = menu_256_draw;\n\n  create_subcontext((subcontext *)sub, (context *)pal_ed, &spec);\n  return (subcontext *)sub;\n}\n\n\n// -----------------------------------------------------------------------------\n\n\nstatic void draw_unbound_cursor_chars(uint32_t layer, int color)\n{\n  select_layer(layer);\n  set_layer_mode(layer, 0);\n  // Note: mode 0, so offset by 16 (PAL_SIZE) for the protected palette.\n  draw_char_to_layer(CHAR_CORNER_TL,  color, 0, 0, PRO_CH, PAL_SIZE);\n  draw_char_to_layer(CHAR_CORNER_TR,  color, 2, 0, PRO_CH, PAL_SIZE);\n  draw_char_to_layer(CHAR_CORNER_BL,  color, 0, 1, PRO_CH, PAL_SIZE);\n  draw_char_to_layer(CHAR_CORNER_BR,  color, 2, 1, PRO_CH, PAL_SIZE);\n  draw_char_to_layer(CHAR_LINE_HORIZ, color, 1, 0, PRO_CH, PAL_SIZE);\n  draw_char_to_layer(CHAR_LINE_HORIZ, color, 1, 1, PRO_CH, PAL_SIZE);\n}\n\nstatic void draw_unbound_cursor(int x, int y, int fg, int bg, int offset)\n{\n  // Cursor for mode 2/3 palette selector\n  uint32_t layer;\n  int order;\n\n  x = x * CHAR_W - 3;\n  y = y * CHAR_H - 5;\n  fg += 0xD0;\n  bg += 0xD0;\n\n  // Shadow\n  order = LAYER_DRAWORDER_UI + offset*2 + 1;\n  layer = create_layer(x, y, 3, 2, order, 0x10D, 0, 1);\n  draw_unbound_cursor_chars(layer, bg);\n\n  // FG\n  x -= 2;\n  y--;\n  order++;\n  layer = create_layer(x, y, 3, 2, order, 0x10D, 0, 1);\n  draw_unbound_cursor_chars(layer, fg);\n}\n\nstatic void destroy_unbound_cursors(struct pal_ed_context *pal_ed)\n{\n  // TODO: this destroys everything, which might not be okay eventually.\n  destruct_extra_layers(0);\n}\n\n/**\n * Draw the 256 color palette window.\n */\n\nstatic boolean palette_256_draw(subcontext *ctx)\n{\n  struct pal_ed_subcontext *pal = (struct pal_ed_subcontext *)ctx;\n  struct pal_ed_context *pal_ed = pal->pal_ed;\n  int blink_cursor = graphics.cursor_flipflop;\n  char chr;\n  int col;\n  int x;\n  int y;\n  int subcursor[4] = { -1, -1, -1, -1 };\n  int ignore;\n\n  int lo = current_id & 0x0F;\n  int hi = (current_id & 0xF0) >> 4;\n\n  draw_window_box(\n    pal->border_x,  pal->border_y,\n    pal->border_x2, pal->border_y2,\n    DI_GREY, DI_GREY_DARK, DI_GREY_CORNER, true, true\n  );\n\n  // Temporary palette warning\n  if(pal_ed->current != GAME_PALETTE)\n  {\n    write_string(\"TEMPORARY -- WILL NOT BE SAVED\", pal->border_x + 3,\n     pal->border_y, 0x8E, WR_NONE);\n  }\n\n  // Erase the spot where the palette will go.\n  for(y = 0; y < 16; y++)\n    for(x = 0; x < 32; x++)\n      erase_char(x + pal->x, y + pal->y);\n\n  // Destroy the cursors if they already exist.\n  destroy_unbound_cursors(pal->pal_ed);\n\n  select_layer(GAME_UI_LAYER);\n\n  if(get_screen_mode() == 2)\n  {\n    // Draw the palette\n    for(y = 0; y < 16; y++)\n    {\n      for(x = 0; x < 16; x++)\n      {\n        col = y << 4 | x;\n        chr = CHAR_SMZX_C2;\n        draw_char_ext(chr, col, (x*2 + pal->x), (y + pal->y), PRO_CH, 0);\n        draw_char_ext(chr, col, (x*2+1 + pal->x), (y + pal->y), PRO_CH, 0);\n      }\n    }\n\n    get_current_indices(pal_ed,\n     &subcursor[0], &subcursor[1], &ignore, &subcursor[2]);\n  }\n\n  // Mode 3\n  else\n  {\n    // Draw the palette\n    for(y = 0; y < 16; y++)\n    {\n      for(x = 0; x < 16; x++)\n      {\n        col = y << 4 | x;\n        draw_char_ext(0, col, (x*2 + pal->x), (y + pal->y), PRO_CH, 0);\n        draw_char_ext(0, col, (x*2+1 + pal->x), (y + pal->y), PRO_CH, 0);\n      }\n    }\n\n    if(pal_ed->current_indices)\n    {\n      get_current_indices(pal_ed,\n       &subcursor[0], &subcursor[1], &subcursor[2], &subcursor[3]);\n    }\n  }\n\n  // Subpalette cursors\n  if(subpalette_cursors)\n  {\n    unsigned char cursor_fg = blink_cursor ? 0xC : 0x4;\n    unsigned char cursor_bg = blink_cursor ? 0x4 : 0x0;\n    int i;\n\n    for(i = 0; i < 4; i++)\n    {\n      if(subcursor[i] >= 0)\n      {\n        draw_unbound_cursor(\n          (subcursor[i] & 15) * 2 + pal->x,\n          (subcursor[i] >> 4) + pal->y,\n          cursor_fg, cursor_bg, 0\n        );\n      }\n    }\n  }\n\n  // Main cursor\n  if(blink_cursor)\n  {\n    draw_unbound_cursor(lo * 2 + pal->x, hi + pal->y, 0xF, 0x8, 4);\n  }\n  else\n  {\n    draw_unbound_cursor(lo * 2 + pal->x, hi + pal->y, 0x7, 0x0, 4);\n  }\n\n  select_layer(UI_LAYER);\n  return true;\n}\n\n/**\n * Key input for the 256 color palette window.\n */\n\nstatic boolean palette_256_key(subcontext *ctx, int *key)\n{\n  switch(*key)\n  {\n    case IKEY_INSERT:\n    {\n      subpalette_cursors = !subpalette_cursors;\n      break;\n    }\n\n    case IKEY_UP:\n    {\n      if(current_id / 16 > 0)\n        current_id -= 16;\n      return true;\n    }\n\n    case IKEY_DOWN:\n    {\n      if(current_id / 16 < 15)\n        current_id += 16;\n      return true;\n    }\n\n    case IKEY_LEFT:\n    {\n      if(current_id % 16 > 0)\n        current_id--;\n      return true;\n    }\n\n    case IKEY_RIGHT:\n    {\n      if(current_id % 16 < 15)\n        current_id++;\n      return true;\n    }\n  }\n  return false;\n}\n\n/**\n * Mouse input for the 256 color palette window (click + drag).\n */\n\nstatic boolean palette_256_mouse(subcontext *ctx, int *key, int button,\n int mouse_x, int mouse_y)\n{\n  struct pal_ed_subcontext *pal = (struct pal_ed_subcontext *)ctx;\n\n  if(get_mouse_status() & MOUSE_BUTTON(MOUSE_BUTTON_RIGHT))\n  {\n    mouse_slide_cursor(key, mouse_x, mouse_y);\n    return true;\n  }\n\n  if(button == MOUSE_BUTTON_LEFT &&\n   MOUSE_IN(pal->x, pal->y, 32, 16))\n  {\n    // Palette- select color\n    current_id = (mouse_x - pal->x)/2 + (mouse_y - pal->y) * 16;\n    return true;\n  }\n\n  return false;\n}\n\n/**\n * Clean up the cursor layers on exit.\n */\n\nstatic void palette_256_destroy(subcontext *ctx)\n{\n  struct pal_ed_subcontext *pal = (struct pal_ed_subcontext *)ctx;\n  destroy_unbound_cursors(pal->pal_ed);\n}\n\n/**\n * Create the 256 color palette window.\n */\n\nstatic subcontext *create_palette_256(struct pal_ed_context *pal_ed)\n{\n  struct pal_ed_subcontext *sub = cmalloc(sizeof(struct pal_ed_subcontext));\n  struct context_spec spec;\n\n  sub->pal_ed = pal_ed;\n  sub->border_x = 0;\n  sub->border_y = 7;\n  sub->border_x2 = 35;\n  sub->border_y2 = 24;\n\n  sub->x = sub->border_x + 2;\n  sub->y = sub->border_y + 1;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw     = palette_256_draw;\n  spec.key      = palette_256_key;\n  spec.click    = palette_256_mouse;\n  spec.drag     = palette_256_mouse;\n  spec.destroy  = palette_256_destroy;\n\n  create_subcontext((subcontext *)sub, (context *)pal_ed, &spec);\n  return (subcontext *)sub;\n}\n\n\n// -----------------------------------------------------------------------------\n\n\n/**\n * Draw the 256 color subpalette window.\n */\n\nstatic boolean subpalette_256_draw(subcontext *ctx)\n{\n  struct pal_ed_subcontext *spal = (struct pal_ed_subcontext *)ctx;\n  struct pal_ed_context *pal_ed = spal->pal_ed;\n  int subpalette_num;\n  int c0 = 0;\n  int c1 = 0;\n  int c2 = 0;\n  int c3 = 0;\n  int x;\n  int y;\n\n  draw_window_box(\n    spal->border_x,  spal->border_y,\n    spal->border_x2, spal->border_y2,\n    DI_GREY_DARK, DI_GREY, DI_GREY_CORNER, true, true\n  );\n\n  // Fill the char missed by the shadow (it looks bad, but also shows the\n  // board which probably had its indices overwritten in mode 3!)\n  draw_char(0, 0, spal->border_x, spal->border_y2 + 1);\n\n  // Palette View contents\n  write_string(\n   \"Subpal.\\n #\\n\\nHex:\",\n   spal->x,\n   spal->y,\n   DI_GREY_TEXT,\n   WR_NEWLINE\n  );\n\n  write_string(\n   \"1:    2:    3:    4:\",\n   spal->x + 9,\n   spal->y,\n   DI_GREY_CORNER,\n   WR_NONE\n  );\n\n  // Erase spots where palette colors will go\n  for(y = 0; y < 3; y++)\n  {\n    for(x = 0; x < 5; x++)\n    {\n      erase_char(spal->x + x + 9, spal->y + y + 1);\n      erase_char(spal->x + x + 15, spal->y + y + 1);\n      erase_char(spal->x + x + 21, spal->y + y + 1);\n      erase_char(spal->x + x + 27, spal->y + y + 1);\n    }\n  }\n\n  select_layer(GAME_UI_LAYER);\n\n  if(get_screen_mode() == 2)\n  {\n    subpalette_num = current_id;\n    get_current_indices(pal_ed, &c0, &c1, &c2, &c3);\n\n    // Draw the current subpalette\n    for(y = 0; y < 3; y++)\n    {\n      for(x = 0; x < 5; x++)\n      {\n        int x2 = spal->x + x;\n        int y2 = spal->y + y + 1;\n        draw_char_ext(CHAR_SMZX_C0, current_id, (x2 + 9), y2, PRO_CH, 0);\n        draw_char_ext(CHAR_SMZX_C1, current_id, (x2 + 15), y2, PRO_CH, 0);\n        draw_char_ext(CHAR_SMZX_C2, current_id, (x2 + 21), y2, PRO_CH, 0);\n        draw_char_ext(CHAR_SMZX_C3, current_id, (x2 + 27), y2, PRO_CH, 0);\n      }\n    }\n  }\n  else\n  {\n    // Note indices 1 and 2 are swapped to make sense.\n    subpalette_num = current_subpalette;\n    if(pal_ed->current_indices)\n      get_current_indices(pal_ed, &c0, &c1, &c2, &c3);\n\n    // Draw the current subpalette\n    for(y = 0; y < 3; y++)\n    {\n      for(x = 0; x < 5; x++)\n      {\n        draw_char_ext(0, c0, (spal->x + x + 9), (spal->y + y + 1), PRO_CH, 0);\n        draw_char_ext(0, c1, (spal->x + x + 15), (spal->y + y + 1), PRO_CH, 0);\n        draw_char_ext(0, c2, (spal->x + x + 21), (spal->y + y + 1), PRO_CH, 0);\n        draw_char_ext(0, c3, (spal->x + x + 27), (spal->y + y + 1), PRO_CH, 0);\n      }\n    }\n  }\n\n  select_layer(UI_LAYER);\n\n  // Current subpalette number\n  write_number(subpalette_num, DI_GREY_TEXT, spal->x + 3, spal->y + 1, 3, false, 10);\n  write_number(subpalette_num, DI_GREY_TEXT, spal->x + 5, spal->y + 3, 2, false, 16);\n\n  // Subpalette indices\n  write_number(c0, DI_GREY_TEXT, spal->x + 11, spal->y, 3, false, 10);\n  write_number(c1, DI_GREY_TEXT, spal->x + 17, spal->y, 3, false, 10);\n  write_number(c2, DI_GREY_TEXT, spal->x + 23, spal->y, 3, false, 10);\n  write_number(c3, DI_GREY_TEXT, spal->x + 29, spal->y, 3, false, 10);\n  return true;\n}\n\n/**\n * Key input for the 256 color subpalette window.\n */\n\nstatic boolean subpalette_256_key(subcontext *ctx, int *key)\n{\n  struct pal_ed_subcontext *spal = (struct pal_ed_subcontext *)ctx;\n  struct pal_ed_context *pal_ed = spal->pal_ed;\n  struct color_status *palette = pal_ed->internal_palette;\n  int smzx_mode = get_screen_mode();\n\n  switch(*key)\n  {\n    case IKEY_SPACE:\n    {\n      if(smzx_mode == 3)\n      {\n        // Mode 3- select subpalette\n        int new_subpal;\n\n        // Make sure there aren't layers displaying over the UI.\n        destroy_unbound_cursors(spal->pal_ed);\n\n        // Load the current active indices\n        load_backup_indices(pal_ed->current_indices);\n\n        new_subpal = color_selection(current_subpalette, false);\n\n        // Reset indices for editor\n        set_screen_mode(3);\n\n        if(new_subpal >= 0)\n          current_subpalette = new_subpal;\n\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_MINUS:\n    case IKEY_KP_MINUS:\n    {\n      if(smzx_mode == 3)\n      {\n        if(current_subpalette > 0)\n          current_subpalette--;\n      }\n      else\n      {\n        if(current_id > 0)\n          current_id--;\n      }\n      return true;\n    }\n\n    case IKEY_EQUALS:\n    case IKEY_KP_PLUS:\n    {\n      if(smzx_mode == 3)\n      {\n        if(current_subpalette < 255)\n          current_subpalette++;\n      }\n      else\n      {\n        if(current_id < 255)\n          current_id++;\n      }\n      return true;\n    }\n\n    case IKEY_1:\n    case IKEY_2:\n    case IKEY_3:\n    case IKEY_4:\n    {\n      if(smzx_mode == 3 && pal_ed->current_indices)\n      {\n        // Assign current color to subpalette index\n        int index = (*key - IKEY_1);\n\n        // Indices 1 and 2 are swapped to make actual sense.\n        if(index == 1)      index = 2;\n        else if(index == 2) index = 1;\n\n        index += current_subpalette * 4;\n\n        pal_ed->current_indices[index] = current_id;\n      }\n      return true;\n    }\n\n    case IKEY_F5:\n    {\n      // Store subpalette colors\n      int c0, c1, c2, c3;\n      if(get_current_indices(pal_ed, &c0, &c1, &c2, &c3))\n      {\n        memcpy(&saved_subpal[0], &palette[c0], sizeof(struct color_status));\n        memcpy(&saved_subpal[1], &palette[c1], sizeof(struct color_status));\n        memcpy(&saved_subpal[2], &palette[c2], sizeof(struct color_status));\n        memcpy(&saved_subpal[3], &palette[c3], sizeof(struct color_status));\n        saved_subpalette_active = true;\n        saved_subpalette_display = true;\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_F6:\n    {\n      // Place subpalette colors\n      int c0, c1, c2, c3;\n      if(saved_subpalette_active &&\n       get_current_indices(pal_ed, &c0, &c1, &c2, &c3))\n      {\n        memcpy(&palette[c0], &saved_subpal[0], sizeof(struct color_status));\n        memcpy(&palette[c1], &saved_subpal[1], sizeof(struct color_status));\n        memcpy(&palette[c2], &saved_subpal[2], sizeof(struct color_status));\n        memcpy(&palette[c3], &saved_subpal[3], sizeof(struct color_status));\n        store_color_pos(&palette[c0], c0);\n        store_color_pos(&palette[c1], c1);\n        store_color_pos(&palette[c2], c2);\n        store_color_pos(&palette[c3], c3);\n        saved_subpalette_display = true;\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_F7:\n    {\n      if(smzx_mode == 3 && pal_ed->current_indices)\n      {\n        // Store subpalette indices\n        get_current_indices(pal_ed,\n         &saved_indices[0], &saved_indices[1],\n         &saved_indices[2], &saved_indices[3]);\n      }\n      break;\n    }\n\n    case IKEY_F8:\n    {\n      if(smzx_mode == 3 && pal_ed->current_indices)\n      {\n        // Place subpalette indices\n        // NOTE: \"get_current_indices\" swaps 1 and 2 to make more sense to the\n        // user, so swap them here too.\n        int index = current_subpalette * 4;\n        pal_ed->current_indices[index + 0] = saved_indices[0];\n        pal_ed->current_indices[index + 2] = saved_indices[1];\n        pal_ed->current_indices[index + 1] = saved_indices[2];\n        pal_ed->current_indices[index + 3] = saved_indices[3];\n      }\n      break;\n    }\n  }\n  return false;\n}\n\n/**\n * Mouse input for the 256 color subpalette window (click only).\n */\n\nstatic boolean subpalette_256_click(subcontext *ctx, int *key, int button,\n int mouse_x, int mouse_y)\n{\n  struct pal_ed_subcontext *spal = (struct pal_ed_subcontext *)ctx;\n  int smzx_mode = get_screen_mode();\n\n  if(button == MOUSE_BUTTON_WHEELUP)\n  {\n    // Previous palette\n    *key = IKEY_MINUS;\n    return true;\n  }\n\n  if(button == MOUSE_BUTTON_WHEELDOWN)\n  {\n    // Next palette\n    *key = IKEY_EQUALS;\n    return true;\n  }\n\n  if(button == MOUSE_BUTTON_MIDDLE)\n  {\n    // Palette selector\n    *key = IKEY_SPACE;\n    return true;\n  }\n\n  if(smzx_mode == 3)\n  {\n    if(button == MOUSE_BUTTON_LEFT &&\n     MOUSE_IN(spal->x - 1, spal->y, 9, 4))\n    {\n      // Select subpalette\n      *key = IKEY_SPACE;\n      return true;\n    }\n\n    if(button == MOUSE_BUTTON_LEFT &&\n     MOUSE_IN(spal->x + 9, spal->y, 24, 4) &&\n     (mouse_x - spal->x - 8)%6 != 0)\n    {\n      // Assign current color to a subpalette index\n      // Fake a press of the 1-4 keys\n      *key = IKEY_1 + (mouse_x - spal->x - 8)/6;\n      return true;\n    }\n  }\n\n  return false;\n}\n\n/**\n * Create the 256 color subpalette window.\n */\n\nstatic subcontext *create_subpalette_256(struct pal_ed_context *pal_ed)\n{\n  struct pal_ed_subcontext *sub = cmalloc(sizeof(struct pal_ed_subcontext));\n  struct context_spec spec;\n\n  sub->pal_ed = pal_ed;\n  sub->border_x = 0;\n  sub->border_y = 0;\n  sub->border_x2 = 35;\n  sub->border_y2 = 5;\n\n  sub->x = sub->border_x + 2;\n  sub->y = sub->border_y + 1;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw   = subpalette_256_draw;\n  spec.key    = subpalette_256_key;\n  spec.click  = subpalette_256_click;\n\n  create_subcontext((subcontext *)sub, (context *)pal_ed, &spec);\n  return (subcontext *)sub;\n}\n\n\n// -----------------------------------------------------------------------------\n\n\n/**\n * Calculate color mode information for every color in the current palette.\n */\nstatic struct color_status *rebuild_palette(struct color_status *palette)\n{\n  int palette_size = (get_screen_mode() >= 2) ? 256 : 16;\n  int i;\n\n  for(i = 0; i < palette_size; i++)\n  {\n    load_color(&(palette[i]), i);\n  }\n\n  current_id %= palette_size;\n\n  return palette;\n}\n\n/**\n * Swap the current active palette.\n */\nstatic void swap_palettes(struct pal_ed_context *pal_ed)\n{\n  if(pal_ed->current == GAME_PALETTE)\n  {\n    // Save the game palette, load the temp palette.\n    store_backup_palette(pal_ed->game_palette_backup);\n    load_backup_palette(temp_palette);\n\n    pal_ed->current_indices = temp_indices;\n    pal_ed->current = TEMP_PALETTE;\n  }\n  else\n  {\n    // Save the temp palette, load the game palette.\n    store_backup_palette(temp_palette);\n    load_backup_palette(pal_ed->game_palette_backup);\n\n    pal_ed->current_indices = pal_ed->game_indices;\n    pal_ed->current = GAME_PALETTE;\n  }\n\n  rebuild_palette(pal_ed->internal_palette);\n}\n\n/**\n * Reset the palettes to their state before the palette editor started.\n */\nstatic void reset_pal_ed_palettes(struct pal_ed_context *pal_ed)\n{\n  if(pal_ed->current != GAME_PALETTE)\n  {\n    // Reload the game palette.\n    swap_palettes(pal_ed);\n  }\n\n  if(get_screen_mode() == 3)\n  {\n    // Make the game indices active.\n    load_backup_indices(pal_ed->game_indices);\n  }\n\n  // Fix protected color 5\n  default_protected_palette();\n}\n\n/**\n * Initialize the palette editor's backup palettes (and indices).\n */\nstatic void init_pal_ed_palettes(struct pal_ed_context *pal_ed)\n{\n  int screen_mode = get_screen_mode();\n  int i;\n  int j;\n\n  pal_ed->current = GAME_PALETTE;\n  pal_ed->current_indices = pal_ed->game_indices;\n\n  // Rebuild the internal palette off the currently loaded (game) palette.\n  rebuild_palette(pal_ed->internal_palette);\n\n  if(screen_mode == 3)\n  {\n    // Special: mode 3 editing doesn't work while the game indices are actually\n    // loaded, so they need to be backed up and edited from the backup.\n    store_backup_indices(pal_ed->game_indices);\n\n    // Reset the indices to default for display purposes.\n    set_screen_mode(3);\n  }\n\n  if(screen_mode != temp_palette_mode)\n  {\n    // Initialize the temp palette.\n    temp_palette_mode = screen_mode;\n    memset(temp_palette, 0, sizeof(temp_palette));\n\n    if(screen_mode < 2)\n    {\n      // Copy in the protected palette...\n      for(i = 0, j = 0; i < PAL_SIZE; i++)\n      {\n        temp_palette[j++] = graphics.protected_palette[i].r * 63 / 255;\n        temp_palette[j++] = graphics.protected_palette[i].g * 63 / 255;\n        temp_palette[j++] = graphics.protected_palette[i].b * 63 / 255;\n      }\n    }\n    else\n    {\n      // Load the external SMZX palette.\n      vfile *vf = vfopen_unsafe(mzx_res_get_by_id(SMZX_PAL), \"rb\");\n      if(vf)\n      {\n        if(!vfread(temp_palette, sizeof(temp_palette), 1, vf))\n          memset(temp_palette, 0, sizeof(temp_palette));\n\n        vfclose(vf);\n      }\n    }\n\n    if(screen_mode == 3)\n    {\n      // The active indices will be the default mode 3 indices since the\n      // mode changed above.\n      store_backup_indices(temp_indices);\n    }\n  }\n}\n\n/**\n * Basic joystick handler for the palette editor.\n * This one intentionally does not do much because there's no way to get here\n * without a keyboard right now.\n */\nstatic boolean pal_ed_joystick(context *ctx, int *key, int action)\n{\n  enum keycode ui_key = get_joystick_ui_key();\n  if(ui_key)\n  {\n    *key = ui_key;\n    return true;\n  }\n  return false;\n}\n\n/**\n * Basic key handler for the palette editor.\n */\nstatic boolean pal_ed_key(context *ctx, int *key)\n{\n  struct pal_ed_context *pal_ed = (struct pal_ed_context *)ctx;\n  int screen_mode = get_screen_mode();\n\n  // Exit event -- mimic Escape\n  if(get_exit_status())\n    *key = IKEY_ESCAPE;\n\n  switch(*key)\n  {\n    case IKEY_ESCAPE:\n    case IKEY_q:\n    {\n      destroy_context(ctx);\n      return true;\n    }\n\n    case IKEY_F1:\n    case IKEY_F2:\n    {\n      // Defer to the global handler for the help/settings interfaces.\n      // There might be layers displaying over the UI, which we don't want.\n      destroy_unbound_cursors(pal_ed);\n      return false;\n    }\n\n    case IKEY_h:\n    {\n      if(get_alt_status(keycode_internal))\n      {\n        // SMZX mode 3 always has the help enabled, so don't allow toggling\n        if(get_screen_mode() != 3)\n        {\n          restore_screen();\n          save_screen();\n          minimal_help ^= 1;\n        }\n\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_d:\n    {\n      if(get_alt_status(keycode_internal))\n      {\n        // SMZX modes 0 and 1 use the default palette\n        if(screen_mode <= 1)\n        {\n          default_palette();\n        }\n        // SMZX modes 2 and 3 need the externally stored SMZX palette\n        else\n        {\n          load_palette(mzx_res_get_by_id(SMZX_PAL));\n        }\n\n        rebuild_palette(pal_ed->internal_palette);\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_TAB:\n    {\n      swap_palettes(pal_ed);\n      return true;\n    }\n\n    case IKEY_i:\n    {\n      if(get_alt_status(keycode_internal))\n      {\n        // Make sure there aren't layers displaying over the UI.\n        destroy_unbound_cursors(pal_ed);\n\n        if(screen_mode == 3)\n        {\n          // Set these to active in case the user doesn't load them.\n          // Then, they'll just copy over themselves when stored again.\n          load_backup_indices(pal_ed->current_indices);\n\n          import_palette(ctx);\n\n          // Save the indices and reset them for display.\n          store_backup_indices(pal_ed->current_indices);\n          set_screen_mode(3);\n        }\n        else\n          import_palette(ctx);\n\n        rebuild_palette(pal_ed->internal_palette);\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_x:\n    {\n      if(get_alt_status(keycode_internal))\n      {\n        // Make sure there aren't layers displaying over the UI.\n        destroy_unbound_cursors(pal_ed);\n\n        if(screen_mode == 3)\n        {\n          // Temporarily make these the active indices while exporting...\n          load_backup_indices(pal_ed->current_indices);\n\n          export_palette(ctx);\n\n          // Reset the indices for display.\n          set_screen_mode(3);\n        }\n        else\n          export_palette(ctx);\n\n        return true;\n      }\n      break;\n    }\n  }\n\n  return false;\n}\n\n/**\n * Clean up palette editor info and restore the screen on exit.\n */\nstatic void pal_ed_destroy(context *ctx)\n{\n  reset_pal_ed_palettes((struct pal_ed_context *)ctx);\n  restore_screen();\n}\n\n/**\n * Create and run the palette editor.\n */\nvoid palette_editor(context *parent)\n{\n  struct pal_ed_context *pal_ed = ccalloc(1, sizeof(struct pal_ed_context));\n  int smzx_mode = get_screen_mode();\n  struct context_spec spec;\n\n  init_pal_ed_palettes(pal_ed);\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.joystick = pal_ed_joystick;\n  spec.key      = pal_ed_key;\n  spec.destroy  = pal_ed_destroy;\n\n  create_context((context *)pal_ed, parent, &spec, CTX_PALETTE_EDITOR);\n\n  if(smzx_mode >= 2)\n  {\n    create_subpalette_256(pal_ed);\n    create_palette_256(pal_ed);\n    create_color_editor(pal_ed);\n    create_menu_256(pal_ed);\n  }\n  else\n  {\n    create_palette_16(pal_ed);\n    create_color_editor(pal_ed);\n    create_menu_16(pal_ed);\n  }\n\n  if(startup == false)\n  {\n    minimal_help = get_editor_config()->palette_editor_hide_help;\n    startup = true;\n  }\n\n  m_show();\n  cursor_off();\n  save_screen();\n}\n\n/**\n * Prompt the user to import a .PAL (and/or .PALIDX) file.\n * NOTE: This is used by both the main editor and the palette editor.\n */\nvoid import_palette(context *ctx)\n{\n  char filename_buffer[MAX_PATH];\n\n  // Palette\n  strcpy(filename_buffer, pal_filename);\n  if(!choose_file(ctx->world, pal_ext, filename_buffer,\n   \"Choose palette to import\", ALLOW_ALL_DIRS))\n  {\n    strcpy(pal_filename, filename_buffer);\n    load_palette(filename_buffer);\n  }\n\n  // Indices (mode 3 only)\n  strcpy(filename_buffer, palidx_filename);\n  if((get_screen_mode() == 3) &&\n   !choose_file(ctx->world, idx_ext, filename_buffer,\n    \"Choose indices to import (.PALIDX)\", ALLOW_ALL_DIRS))\n  {\n    strcpy(palidx_filename, filename_buffer);\n    load_index_file(filename_buffer);\n  }\n}\n\n/**\n * Prompt the user to export a .PAL (and/or .PALIDX) file.\n * NOTE: This is used by both the main editor and the palette editor.\n */\nvoid export_palette(context *ctx)\n{\n  char filename_buffer[MAX_PATH];\n\n  // Palette\n  strcpy(filename_buffer, pal_filename);\n  if(!new_file(ctx->world, pal_ext, \".pal\", filename_buffer,\n   \"Export palette\", ALLOW_ALL_DIRS))\n  {\n    strcpy(pal_filename, filename_buffer);\n    save_palette(filename_buffer);\n  }\n\n  // Indices (mode 3 only)\n  strcpy(filename_buffer, palidx_filename);\n  if((get_screen_mode() == 3) &&\n   !new_file(ctx->world, idx_ext, \".palidx\", filename_buffer,\n    \"Export indices (.PALIDX)\", ALLOW_ALL_DIRS))\n  {\n    strcpy(palidx_filename, filename_buffer);\n    save_index_file(filename_buffer);\n  }\n}\n"
  },
  {
    "path": "src/editor/pal_ed.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declaration for PAL_ED.CPP */\n\n#ifndef __EDITOR_PAL_ED_H\n#define __EDITOR_PAL_ED_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../core.h\"\n\nvoid palette_editor(context *parent);\nvoid import_palette(context *ctx);\nvoid export_palette(context *ctx);\n\n__M_END_DECLS\n\n#endif // __EDITOR_PAL_ED_H\n"
  },
  {
    "path": "src/editor/param.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2018-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* PARAM.CPP- Millions of dialog boxes for inputting parameters of\n          a specified id. If a parameter is already available,\n          it is edited. Special functions are called in other\n          files for robots, scrolls, and signs. */\n\n#include <string.h>\n\n#include \"../core.h\"\n#include \"../data.h\"\n#include \"../event.h\"\n#include \"../idput.h\"\n#include \"../robot.h\"\n#include \"../scrdisp.h\"\n#include \"../util.h\"\n#include \"../window.h\"\n\n#include \"edit_di.h\"\n#include \"param.h\"\n#include \"robo_ed.h\"\n#include \"window.h\"\n#include \"world.h\"\n\nstatic const char *const potion_fx[16] =\n{\n  [POTION_DUD]          = \"No Effect   \",\n  [POTION_INVINCO]      = \"Invinco     \",\n  [POTION_BLAST]        = \"Blast       \",\n  [POTION_SMALL_HEALTH] = \"Health x10  \",\n  [POTION_BIG_HEALTH]   = \"Health x50  \",\n  [POTION_POISON]       = \"Poison      \",\n  [POTION_BLIND]        = \"Blind       \",\n  [POTION_KILL]         = \"Kill Enemies\",\n  [POTION_FIREWALK]     = \"Lava Walker \",\n  [POTION_DETONATE]     = \"Detonate    \",\n  [POTION_BANISH]       = \"Banish      \",\n  [POTION_SUMMON]       = \"Summon      \",\n  [POTION_AVALANCHE]    = \"Avalanche   \",\n  [POTION_FREEZE]       = \"Freeze Time \",\n  [POTION_WIND]         = \"Wind        \",\n  [POTION_SLOW]         = \"Slow Time   \"\n};\n\nstatic int pe_chest(struct world *mzx_world, int param)\n{\n  int type = param & 0x0F;\n  int var = param >> 4;\n\n  const char *list[] =\n  {\n    [CHEST_EMPTY]   = \"Empty               \",\n    [CHEST_KEY]     = \"Key                 \",\n    [CHEST_COINS]   = \"Coins               \",\n    [CHEST_LIVES]   = \"Lives               \",\n    [CHEST_AMMO]    = \"Ammo                \",\n    [CHEST_GEMS]    = \"Gems                \",\n    [CHEST_HEALTH]  = \"Health              \",\n    [CHEST_POTION]  = \"Potion              \",\n    [CHEST_RING]    = \"Ring                \",\n    [CHEST_LOBOMBS] = \"Lo Bombs            \",\n    [CHEST_HIBOMBS] = \"Hi Bombs            \"\n  };\n\n  // First, pick chest contents\n  type =\n   list_menu(list, 21, \"Choose chest contents\", type, ARRAY_SIZE(list), 27, 0);\n\n  if(type < 0)\n    return -1;\n\n  switch(type)\n  {\n    case CHEST_EMPTY:\n    {\n      var = 0;\n      break;\n    }\n\n    case CHEST_KEY:\n    {\n      var = color_selection(var, 0);\n      if(var > 0)\n        var = var & 0x0F;\n      break;\n    }\n\n    case CHEST_POTION:\n    case CHEST_RING:\n    {\n      var = list_menu(potion_fx, 13, \"Choose effect\", var, 16, 31, 0);\n      break;\n    }\n\n    case CHEST_COINS:\n    case CHEST_LIVES:\n    case CHEST_AMMO:\n    case CHEST_GEMS:\n    case CHEST_HEALTH:\n    case CHEST_LOBOMBS:\n    case CHEST_HIBOMBS: // Choose amount\n    {\n      struct dialog di;\n      struct element *elements[3];\n      int dialog_result;\n      enum number_box_type input_type = NUMBER_BOX_MULT_FIVE;\n      const char *question = \"Amount (multiple of five): \";\n\n      set_confirm_buttons(elements);\n      if(type == CHEST_LIVES)\n      {\n        question = \"Number of lives: \";\n        input_type = NUMBER_BOX;\n      }\n\n      elements[2] = construct_number_box(5, 6, question,\n       0, 15, input_type, &var);\n\n      construct_dialog(&di, \"Set Quantity\", 10, 5, 60, 18,\n       elements, 3, 2);\n\n      dialog_result = run_dialog(mzx_world, &di);\n\n      if(dialog_result)\n        var = -1;\n\n      destruct_dialog(&di);\n      break;\n    }\n  }\n\n  if(var < 0)\n    return -1;\n\n  return (var << 4) | type;\n}\n\nstatic int pe_health(struct world *mzx_world, int param)\n{\n  struct dialog di;\n  struct element *elements[3];\n  int dialog_result;\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 6, \"Amount: \",\n   0, 255, NUMBER_BOX, &param);\n\n  construct_dialog(&di, \"Set Health/Ammo\", 10, 5, 60, 18,\n   elements, 3, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return param;\n}\n\nstatic int pe_ring(struct world *mzx_world, int param)\n{\n  int p;\n  if(param >= 16)\n    param = 0;\n  p = list_menu(potion_fx, 13, \"Choose effect\", param, 16, 31, 0);\n  if(p < 0)\n    return -1;\n  else\n    return p;\n}\n\nstatic int pe_bomb(struct world *mzx_world, int param)\n{\n  struct dialog di;\n  struct element *elements[3];\n  int dialog_result;\n  const char *radio_strings[] =\n  {\n    \"Low strength bomb\", \"High strength bomb\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_radio_button(15, 6, radio_strings,\n   2, 18, &param);\n\n  construct_dialog(&di, \"Set Bomb\", 10, 5, 60, 18,\n   elements, 3, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return param;\n}\n\nstatic int pe_lit_bomb(struct world *mzx_world, int param)\n{\n  struct dialog di;\n  struct element *elements[4];\n  int dialog_result;\n  int type = param >> 7;\n  int stage = 7 - (param & 0x3F);\n  const char *radio_strings[] =\n  {\n    \"Low strength bomb\", \"High strength bomb\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_radio_button(15, 6, radio_strings,\n   2, 18, &type);\n  elements[3] = construct_number_box(15, 9, \"Fuse length: \",\n   1, 7, NUMBER_LINE, &stage);\n\n  construct_dialog(&di, \"Set Lit Bomb\", 10, 5, 60, 18,\n   elements, 4, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (type << 7) | (7 - stage);\n}\n\nstatic int pe_explosion(struct world *mzx_world, int param)\n{\n  int size = param >> 4;\n  int stage = (param & 0x0F) + 1;\n  struct dialog di;\n  struct element *elements[4];\n  int dialog_result;\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 6, \"Explosion stage: \",\n   1, 4, NUMBER_LINE, &stage);\n  elements[3] = construct_number_box(15, 8, \"Explosion size: \",\n   0, 15, NUMBER_BOX, &size);\n\n  construct_dialog(&di, \"Set Explosion\", 10, 5, 60, 18,\n   elements, 4, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (size << 4) | (stage - 1);\n}\n\nstatic int pe_door(struct world *mzx_world, int param)\n{\n  int alignment = param & 1;\n  int opens = (param >> 1) & 0x03;\n  int check_results[1] = { (param >> 3) & 0x01 };\n  struct dialog di;\n  struct element *elements[5];\n  int dialog_result;\n  const char *radio_strings_1[] =\n  {\n    \"Horizontal\", \"Vertical\"\n  };\n  const char *radio_strings_2[] =\n  {\n    \"Opens N/W\", \"Opens N/E\", \"Opens S/W\", \"Opens S/E\"\n  };\n  const char *check_strings[] = { \"Locked door\" };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_radio_button(15, 3, radio_strings_1,\n   2, 18, &alignment);\n  elements[3] = construct_radio_button(15, 6, radio_strings_2,\n   4, 9, &opens);\n  elements[4] = construct_check_box(15, 11, check_strings,\n   1, 11, check_results);\n\n  construct_dialog(&di, \"Set Door\", 10, 5, 60, 18,\n   elements, 5, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return alignment | (opens << 1) | (check_results[0] << 3);\n}\n\nstatic int pe_gate(struct world *mzx_world, int param)\n{\n  int check_results[1] = { param };\n  struct dialog di;\n  struct element *elements[3];\n  int dialog_result;\n  const char *check_strings[] = { \"Locked gate\" };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_check_box(15, 7, check_strings,\n   1, 11, check_results);\n\n  construct_dialog(&di, \"Set Gate\", 10, 5, 60, 18,\n   elements, 3, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return check_results[0];\n}\n\nstatic int pe_transport(struct world *mzx_world, int param)\n{\n  struct dialog di;\n  struct element *elements[3];\n  int dialog_result;\n  const char *radio_strings[] =\n  {\n    \"North\", \"South\", \"East\", \"West\", \"All\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_radio_button(15, 6, radio_strings,\n   5, 5, &param);\n\n  construct_dialog(&di, \"Set Transport\", 10, 5, 60, 18,\n   elements, 3, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return param;\n}\n\nstatic int pe_pouch(struct world *mzx_world, int param)\n{\n  int coins = param >> 4;\n  int gems = param & 0x0F;\n  struct dialog di;\n  struct element *elements[4];\n  int dialog_result;\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(6, 6,\n   \"Number of gems (mult. of 5):  \", 0, 15, NUMBER_BOX_MULT_FIVE, &gems);\n  elements[3] = construct_number_box(6, 8,\n   \"Number of coins (mult. of 5): \", 0, 15, NUMBER_BOX_MULT_FIVE, &coins);\n\n  construct_dialog(&di, \"Set Pouch\", 10, 5, 60, 18,\n   elements, 4, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (coins << 4) | gems;\n}\n\nstatic int pe_pusher(struct world *mzx_world, int param)\n{\n  struct dialog di;\n  struct element *elements[3];\n  int dialog_result;\n  const char *radio_strings[] =\n  {\n    \"North\", \"South\", \"East\", \"West\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_radio_button(15, 6, radio_strings,\n   4, 5, &param);\n\n  construct_dialog(&di, \"Set Pusher/Missile/Spike\", 10, 5, 60, 18,\n   elements, 3, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return param;\n}\n\nstatic int pe_lazer_gun(struct world *mzx_world, int param)\n{\n  int dir = param & 0x03;\n  int start = ((param >> 2) & 0x07) + 1;\n  int end = (param >> 5) + 1;\n  struct dialog di;\n  struct element *elements[5];\n  int dialog_result;\n  const char *radio_strings[] =\n  {\n    \"North\", \"South\", \"East\", \"West\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_radio_button(15, 4, radio_strings,\n   4, 5, &dir);\n  elements[3] = construct_number_box(15, 9, \"Start time: \",\n   1, 8, NUMBER_LINE, &start);\n  elements[4] = construct_number_box(15, 11, \"Length on:  \",\n   1, 7, NUMBER_LINE, &end);\n\n  construct_dialog(&di, \"Set 'Lazer' Gun\", 10, 5, 60, 18,\n   elements, 5, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return dir | (((start - 1) << 2) + ((end - 1) << 5));\n}\n\nstatic int pe_bullet(struct world *mzx_world, int param)\n{\n  int dir = param & 0x03;\n  int type = param >> 2;\n  struct dialog di;\n  struct element *elements[4];\n  int dialog_result;\n  const char *radio_strings_1[] =\n  {\n    \"North\", \"South\", \"East\", \"West\"\n  };\n  const char *radio_strings_2[] =\n  {\n    \"Player\", \"Neutral\", \"Enemy\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_radio_button(15, 5, radio_strings_1,\n   4, 5, &dir);\n  elements[3] = construct_radio_button(15, 10, radio_strings_2,\n   3, 7, &type);\n\n  construct_dialog(&di, \"Set Bullet\", 10, 5, 60, 18,\n   elements, 4, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return dir | (type << 2);\n}\n\nstatic int pe_ricochet_panel(struct world *mzx_world, int param)\n{\n  struct dialog di;\n  struct element *elements[3];\n  int dialog_result;\n  const char *radio_strings[] =\n  {\n    \"Orientation \\\\\", \"Orientation /\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_radio_button(15, 7, radio_strings,\n   2, 13, &param);\n\n  construct_dialog(&di, \"Set Ricochet Panel\", 10, 5, 60, 18,\n   elements, 3, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return param;\n}\n\nstatic int pe_mine(struct world *mzx_world, int param)\n{\n  struct dialog di;\n  struct element *elements[3];\n  int dialog_result;\n\n  param = (param >> 4) + 1;\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 7, \"Blast radius:  \",\n   1, 8, NUMBER_LINE, &param);\n\n  construct_dialog(&di, \"Set Mine\", 10, 5, 60, 18,\n   elements, 3, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (param - 1) << 4;\n}\n\nstatic int pe_snake(struct world *mzx_world, int param)\n{\n  int dir = param & 0x3;\n  int intel = (param >> 4) + 1;\n  int check_results[1] = { ((param >> 2) & 1) ^ 1 };\n  struct dialog di;\n  struct element *elements[5];\n  int dialog_result;\n  const char *radio_strings[] =\n  {\n    \"North\", \"South\", \"East\", \"West\"\n  };\n  const char *check_strings[] = { \"Fast movement \" };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_radio_button(15, 4, radio_strings,\n   4, 5, &dir);\n  elements[3] = construct_number_box(15, 9, \"Intelligence:  \",\n   1, 8, NUMBER_LINE, &intel);\n  elements[4] = construct_check_box(15, 11, check_strings,\n   1, 13, check_results);\n\n  construct_dialog(&di, \"Set Snake\", 10, 5, 60, 18,\n   elements, 5, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return dir | ((check_results[0] ^ 1) << 2) | ((intel - 1) << 4);\n}\n\nstatic int pe_eye(struct world *mzx_world, int param)\n{\n  int intel = (param & 0x07) + 1;\n  int radius = ((param >> 3) & 0x07) + 1;\n  int check_results[1] = { (param >> 6) ^ 1 };\n  struct dialog di;\n  struct element *elements[5];\n  int dialog_result;\n  const char *check_strings[] = { \"Fast movement \" };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 6, \"Intelligence: \",\n   1, 8, NUMBER_LINE, &intel);\n  elements[3] = construct_number_box(15, 8, \"Blast radius: \",\n   1, 8, NUMBER_LINE, &radius);\n  elements[4] = construct_check_box(15, 10, check_strings,\n   1, 13, check_results);\n\n  construct_dialog(&di, \"Set Eye\", 10, 5, 60, 18,\n   elements, 5, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (intel - 1) | ((radius - 1) << 3) | ((check_results[0] ^ 1) << 6);\n}\n\nstatic int pe_thief(struct world *mzx_world, int param)\n{\n  int intel = (param & 0x07) + 1;\n  int speed = ((param >> 3) & 0x03) + 1;\n  int gems = (param >> 7) + 1;\n  struct dialog di;\n  struct element *elements[5];\n  int dialog_result;\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 6,  \"Intelligence:   \",\n   1, 8, NUMBER_LINE, &intel);\n  elements[3] = construct_number_box(15, 8,  \"Movement speed: \",\n   1, 4, NUMBER_LINE, &speed);\n  elements[4] = construct_number_box(15, 10, \"Gems stolen:    \",\n   1, 2, NUMBER_LINE, &gems);\n\n  construct_dialog(&di, \"Set Thief\", 10, 5, 60, 18,\n   elements, 5, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (intel - 1) | ((speed - 1) << 3) | ((gems - 1) << 7);\n}\n\nstatic int pe_slime_blob(struct world *mzx_world, int param)\n{\n  int speed = (param & 0x07) + 1;\n  int check_results[2] = { (param >> 6) & 0x01, (param >> 7) & 0x01 };\n  struct dialog di;\n  struct element *elements[4];\n  int dialog_result;\n  const char *check_strings[] =\n  {\n    \"Hurts player\", \"Invincible\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 6, \"Spread speed: \",\n   1, 8, NUMBER_LINE, &speed);\n  elements[3] = construct_check_box(15, 8, check_strings,\n   2, 12, check_results);\n\n  construct_dialog(&di, \"Set Slime Blob\", 10, 5, 60, 18,\n   elements, 4, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (speed - 1) | (check_results[0] << 6) |\n   (check_results[1] << 7);\n}\n\nstatic int pe_runner(struct world *mzx_world, int param)\n{\n  int dir = param & 0x03;\n  int hp = (param >> 6) + 1;\n  int speed = ((param >> 2) & 3) + 1;\n  struct dialog di;\n  struct element *elements[5];\n  int dialog_result;\n  const char *radio_strings[] =\n  {\n    \"North\", \"South\", \"East\", \"West\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_radio_button(15, 4, radio_strings,\n   4, 5, &dir);\n  elements[3] = construct_number_box(15, 9, \"Hit points: \",\n   1, 4, NUMBER_LINE, &hp);\n  elements[4] = construct_number_box(15, 11, \"Movement speed: \",\n   1, 4, NUMBER_LINE, &speed);\n\n  construct_dialog(&di, \"Set Runner\", 10, 5, 60, 18,\n   elements, 5, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return dir | ((speed - 1) << 2) | ((hp - 1) << 6);\n}\n\nstatic int pe_ghost(struct world *mzx_world, int param)\n{\n  int intel = (param & 0x07) + 1;\n  int speed = ((param >> 4) & 0x03) + 1;\n  int check_results[] = { (param >> 3) & 0x01 };\n  struct dialog di;\n  struct element *elements[5];\n  int dialog_result;\n  const char *check_strings[] = { \"Invincible\" };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 6, \"Intelligence:  \",\n   1, 8, NUMBER_LINE, &intel);\n  elements[3] = construct_number_box(15, 8, \"Movement speed: \",\n   1, 4, NUMBER_LINE, &speed);\n  elements[4] = construct_check_box(15, 10, check_strings,\n   1, 10, check_results);\n\n  construct_dialog(&di, \"Set Ghost\", 10, 5, 60, 18,\n   elements, 5, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (intel - 1) | ((speed - 1) << 4) | (check_results[0] << 3);\n}\n\nstatic int pe_dragon(struct world *mzx_world, int param)\n{\n  int fire_rate = (param & 0x03) + 1;\n  int hp = ((param >> 5) & 0x07) + 1;\n  int check_results[1] = { ((param >> 2) & 0x01) };\n  struct dialog di;\n  struct element *elements[5];\n  int dialog_result;\n  const char *check_strings[] = { \"Moves\" };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 6, \"Firing rate: \",\n   1, 4, NUMBER_LINE, &fire_rate);\n  elements[3] = construct_number_box(15, 8, \"Hit points:  \",\n   1, 8, NUMBER_LINE, &hp);\n  elements[4] = construct_check_box(15, 10, check_strings,\n   1, 5, check_results);\n\n  construct_dialog(&di, \"Set Dragon\", 10, 5, 60, 18,\n   elements, 5, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (fire_rate - 1) | ((hp - 1) << 5) | (check_results[0] << 2);\n}\n\nstatic int pe_fish(struct world *mzx_world, int param)\n{\n  int intel = (param & 0x07) + 1;\n  int check_results[4] =\n  {\n    (param >> 6) & 0x01, (param >> 5) & 0x01,\n    (param >> 7) & 0x01, ((param >> 3) & 0x01) ^ 1\n  };\n\n  struct dialog di;\n  struct element *elements[4];\n  int dialog_result;\n  const char *check_strings[] =\n  {\n    \"Hurts player\", \"Affected by current\",\n    \"2 hit points\", \"Fast movement\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 5, \"Intelligence: \",\n   1, 8, NUMBER_LINE, &intel);\n  elements[3] = construct_check_box(15, 7, check_strings,\n   4, 19, check_results);\n\n  construct_dialog(&di, \"Set Fish\", 10, 5, 60, 18,\n   elements, 4, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (intel - 1) | (check_results[0] << 6) |\n   (check_results[1] << 5) | (check_results[2] << 7) |\n   ((check_results[3] ^1) << 3);\n}\n\nstatic int pe_shark(struct world *mzx_world, int param)\n{\n  int intel = (param & 0x07) + 1;\n  int fire_rate = ((param >> 5) & 0x07) + 1;\n  int fires = ((param >> 3) & 0x03);\n  struct dialog di;\n  struct element *elements[5];\n  int dialog_result;\n  const char *radio_strings[] =\n  {\n    \"Fires bullets\", \"Fires seekers\",\n    \"Fires fire\", \"Fires nothing\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 4, \"Intelligence: \",\n   1, 8, NUMBER_LINE, &intel);\n  elements[3] = construct_number_box(15, 6, \"Firing rate:  \",\n   1, 8, NUMBER_LINE, &fire_rate);\n  elements[4] = construct_radio_button(15, 8, radio_strings,\n   4, 13, &fires);\n\n  construct_dialog(&di, \"Set Shark/Spitting Tiger\", 10, 5, 60, 18,\n   elements, 5, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (intel - 1) | ((fire_rate - 1) << 5) | (fires << 3);\n}\n\nstatic int pe_spider(struct world *mzx_world, int param)\n{\n  int intel = (param & 0x07) + 1;\n  int web = ((param >> 3) & 0x03);\n  int check_results[2] =\n  {\n    ((param >> 5) & 0x01) ^ 1, ((param >> 7) & 0x01)\n  };\n\n  struct dialog di;\n  struct element *elements[5];\n  int dialog_result;\n  const char *radio_strings[] =\n  {\n    \"Thin web only\", \"Thick web only\", \"Any web\", \"Anywhere\"\n  };\n  const char *check_strings[] =\n  {\n    \"Fast movement\", \"2 hit points\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 3, \"Intelligence: \",\n   1, 8, NUMBER_LINE, &intel);\n  elements[3] = construct_radio_button(15, 5, radio_strings,\n   4, 14, &web);\n  elements[4] = construct_check_box(15, 10, check_strings,\n   2, 13, check_results);\n\n  construct_dialog(&di, \"Set Spider\", 10, 5, 60, 18,\n   elements, 5, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (intel - 1) | (web << 3) |\n   ((check_results[0] ^ 1) << 5) | (check_results[1] << 7);\n}\n\nstatic int pe_goblin(struct world *mzx_world, int param)\n{\n  int intel = ((param >> 6) & 0x03) + 1;\n  int rest_len = (param & 0x03) + 1;\n  struct dialog di;\n  struct element *elements[4];\n  int dialog_result;\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 7, \"Intelligence: \",\n   1, 4, NUMBER_LINE, &intel);\n  elements[3] = construct_number_box(15, 9, \"Rest length:  \",\n   1, 4, NUMBER_LINE, &rest_len);\n\n  construct_dialog(&di, \"Set Goblin\", 10, 5, 60, 18,\n   elements, 4, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (rest_len - 1) | ((intel - 1) << 6);\n}\n\nstatic int pe_bullet_gun(struct world *mzx_world, int param)\n{\n  int dir = (param >> 3) & 0x03;\n  int intel = ((param >> 5) & 0x03) + 1;\n  int fire_rate = (param & 0x07) + 1;\n  int type = (param >> 7);\n  struct dialog di;\n  struct element *elements[6];\n  int dialog_result;\n  const char *radio_strings_1[] =\n  {\n    \"North\", \"South\", \"East\", \"West\"\n  };\n  const char *radio_strings_2[] =\n  {\n    \"Fires bullets\", \"Fires fire\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_radio_button(15, 2, radio_strings_1,\n   4, 5, &dir);\n  elements[3] = construct_number_box(15, 7, \"Intelligence: \",\n   1, 4, NUMBER_LINE, &intel);\n  elements[4] = construct_number_box(15, 9, \"Firing rate:  \",\n   1, 8, NUMBER_LINE, &fire_rate);\n  elements[5] = construct_radio_button(15, 11, radio_strings_2,\n   2, 13, &type);\n\n  construct_dialog(&di, \"Set Bullet Gun/Spinning Gun\", 10, 5, 60, 18,\n   elements, 6, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (fire_rate - 1) | ((dir << 3) + ((intel - 1) << 5) + (type << 7));\n}\n\nstatic int pe_bear(struct world *mzx_world, int param)\n{\n  int sens = (param & 0x07) + 1;\n  int speed = ((param >> 3) & 0x03) + 1;\n  int check_results[1] = { (param >> 7) };\n  struct dialog di;\n  struct element *elements[5];\n  int dialog_result;\n  const char *check_strings[] = { \"2 hit points\" };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 6, \"Sensitivity:   \",\n   1, 8, NUMBER_LINE, &sens);\n  elements[3] = construct_number_box(15, 8, \"Movement speed: \",\n   1, 4, NUMBER_LINE, &speed);\n  elements[4] = construct_check_box(15, 10, check_strings,\n   1, 12, check_results);\n\n  construct_dialog(&di, \"Set Bear\", 10, 5, 60, 18,\n   elements, 5, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (sens - 1) | ((speed - 1) << 3) | (check_results[0] << 7);\n}\n\nstatic int pe_bear_cub(struct world *mzx_world, int param)\n{\n  int intel = (param & 0x03) + 1;\n  int switch_rate = ((param >> 2) & 0x03) + 1;\n  struct dialog di;\n  struct element *elements[4];\n  int dialog_result;\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_number_box(15, 7, \"Intelligence: \",\n   1, 4, NUMBER_LINE, &intel);\n  elements[3] = construct_number_box(15, 9, \"Switch rate:  \",\n   1, 4, NUMBER_LINE, &switch_rate);\n\n  construct_dialog(&di, \"Set Bear Cub\", 10, 5, 60, 18,\n   elements, 4, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (intel - 1) | ((switch_rate - 1) << 2);\n}\n\nstatic int pe_missile_gun(struct world *mzx_world, int param)\n{\n  int dir = (param >> 3) & 0x03;\n  int intel = ((param >> 5) & 0x03) + 1;\n  int fire_rate = (param & 0x07) + 1;\n  int type = (param >> 7);\n  struct dialog di;\n  struct element *elements[6];\n  int dialog_result;\n  const char *radio_strings_1[] =\n  {\n    \"North\", \"South\", \"East\", \"West\"\n  };\n  const char *radio_strings_2[] =\n  {\n    \"Fires once\", \"Fires multiple\"\n  };\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_radio_button(15, 2, radio_strings_1,\n   4, 5, &dir);\n  elements[3] = construct_number_box(15, 7, \"Intelligence:  \",\n   1, 4, NUMBER_LINE, &intel);\n  elements[4] = construct_number_box(15, 9, \"Firing rate:   \",\n   1, 8, NUMBER_LINE, &fire_rate);\n  elements[5] = construct_radio_button(15, 11, radio_strings_2,\n   2, 14, &type);\n\n  construct_dialog(&di, \"Set Missile Gun\", 10, 5, 60, 18,\n   elements, 6, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result)\n    return -1;\n\n  return (fire_rate - 1) | (dir << 3) |\n   ((intel - 1) << 5) | (type << 7);\n}\n\n// Returns parameter or -1 for ESC. Must pass a legit parameter, or -1 to\n// use default or load up a new robot/scroll/sign.\nint edit_param(struct world *mzx_world, int id, int param)\n{\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  // Switch default params to handle the basic stuff\n  switch(def_params[id])\n  {\n    // No editing allowed\n    case -2:\n    {\n      // If the char ID is 255 fall through; char ID 255 things act like customs\n      if(id_chars[id] != 255)\n      {\n        // If has a param, return it, otherwise return 0\n        if(param > -1)\n          return param;\n\n        return 0;\n      }\n    }\n\n    /* fallthrough */\n\n    // Character\n    case -3:\n    {\n      int new_param = param;\n\n      // Use default char.\n      if(new_param < 0)\n      {\n        char def = get_default_id_char(id);\n\n        // If this type is custom* by default (usually the case here), use 177\n        if(def == 255)\n          new_param = 177;\n        else\n          new_param = def;\n      }\n\n      new_param = char_selection(new_param);\n\n      if(new_param < 0)\n        new_param = param;\n\n      return new_param;\n    }\n\n    // Board\n    case -4:\n    {\n      int new_param = param;\n      if(new_param < 0)\n        new_param = 0;\n\n      new_param = choose_board(mzx_world, new_param,\n       \"Choose destination board\", 0);\n      if(new_param < 0)\n        new_param = param;\n\n      return new_param;\n    }\n  }\n\n  if(param < 0)\n    param = def_params[id];\n\n  // Switch according to thing\n  switch(id)\n  {\n    case CHEST:\n    {\n      param = pe_chest(mzx_world, param);\n      break;\n    }\n\n    case HEALTH:\n    case AMMO:\n    {\n      param = pe_health(mzx_world, param);\n      break;\n    }\n\n    case RING:\n    case POTION:\n    {\n      param = pe_ring(mzx_world, param);\n      break;\n    }\n\n    case BOMB:\n    {\n      param = pe_bomb(mzx_world, param);\n      break;\n    }\n\n    case LIT_BOMB:\n    {\n      param = pe_lit_bomb(mzx_world, param);\n      break;\n    }\n\n    case EXPLOSION:\n    {\n      param = pe_explosion(mzx_world, param);\n      break;\n    }\n\n    case DOOR:\n    {\n      param = pe_door(mzx_world, param);\n      break;\n    }\n\n    case GATE:\n    {\n      param = pe_gate(mzx_world, param);\n      break;\n    }\n\n    case TRANSPORT:\n    {\n      param = pe_transport(mzx_world, param);\n      break;\n    }\n\n    case POUCH:\n    {\n      param = pe_pouch(mzx_world, param);\n      break;\n    }\n\n    case PUSHER:\n    case MISSILE:\n    case SPIKE:\n    {\n      param = pe_pusher(mzx_world, param);\n      break;\n    }\n\n    case SHOOTING_FIRE:\n    {\n      param = pe_pusher(mzx_world, param >> 1) << 1;\n      break;\n    }\n\n    case LAZER_GUN:\n    {\n      param = pe_lazer_gun(mzx_world, param);\n      break;\n    }\n\n    case BULLET:\n    {\n      param = pe_bullet(mzx_world, param);\n      break;\n    }\n\n    case RICOCHET_PANEL:\n    {\n      param = pe_ricochet_panel(mzx_world, param);\n      break;\n    }\n\n    case MINE:\n    {\n      param = pe_mine(mzx_world, param);\n      break;\n    }\n\n    case SNAKE:\n    {\n      param = pe_snake(mzx_world, param);\n      break;\n    }\n\n    case EYE:\n    {\n      param = pe_eye(mzx_world, param);\n      break;\n    }\n\n    case THIEF:\n    {\n      param = pe_thief(mzx_world, param);\n      break;\n    }\n\n    case SLIMEBLOB:\n    {\n      param = pe_slime_blob(mzx_world, param);\n      break;\n    }\n\n    case RUNNER:\n    {\n      param = pe_runner(mzx_world, param);\n      break;\n    }\n\n    case GHOST:\n    {\n      param = pe_ghost(mzx_world, param);\n      break;\n    }\n\n    case DRAGON:\n    {\n      param = pe_dragon(mzx_world, param);\n      break;\n    }\n\n    case FISH:\n    {\n      param = pe_fish(mzx_world, param);\n      break;\n    }\n\n    case SHARK:\n    case SPITTING_TIGER:\n    {\n      param = pe_shark(mzx_world, param);\n      break;\n    }\n\n    case SPIDER:\n    {\n      param = pe_spider(mzx_world, param);\n      break;\n    }\n\n    case GOBLIN:\n    {\n      param = pe_goblin(mzx_world, param);\n      break;\n    }\n\n    case BULLET_GUN:\n    case SPINNING_GUN:\n    {\n      param = pe_bullet_gun(mzx_world, param);\n      break;\n    }\n\n    case BEAR:\n    {\n      param = pe_bear(mzx_world, param);\n      break;\n    }\n\n    case BEAR_CUB:\n    {\n      param = pe_bear_cub(mzx_world, param);\n      break;\n    }\n\n    case MISSILE_GUN:\n    {\n      param = pe_missile_gun(mzx_world, param);\n      break;\n    }\n  }\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  // Return param\n  return param;\n}\n\nint edit_sensor(struct world *mzx_world, struct sensor *cur_sensor)\n{\n  char sensor_name[ROBOT_NAME_SIZE];\n  char sensor_robot[ROBOT_NAME_SIZE];\n  int sensor_char = cur_sensor->sensor_char;\n  struct dialog di;\n  struct element *elements[5];\n  int dialog_result;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_SENSOR_EDITOR);\n  strcpy(sensor_name, cur_sensor->sensor_name);\n  strcpy(sensor_robot, cur_sensor->robot_to_mesg);\n\n  set_confirm_buttons(elements);\n  elements[2] = construct_input_box(15, 6, \"Sensor's name:    \",\n   ROBOT_NAME_SIZE - 1, sensor_name);\n  elements[3] = construct_input_box(15, 8, \"Robot to message: \",\n   ROBOT_NAME_SIZE - 1, sensor_robot);\n  elements[4] = construct_char_box(15, 10, \"Sensor character: \",\n   1, &sensor_char);\n\n  construct_dialog(&di, \"Set Sensor\", 10, 5, 60, 18,\n   elements, 5, 2);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  pop_context();\n\n  if(dialog_result)\n    return -1;\n\n  strcpy(cur_sensor->sensor_name, sensor_name);\n  strcpy(cur_sensor->robot_to_mesg, sensor_robot);\n  cur_sensor->sensor_char = sensor_char;\n  return 0;\n}\n\nint edit_scroll(struct world *mzx_world, struct scroll *cur_scroll)\n{\n  scroll_edit(mzx_world, cur_scroll, 2);\n  return 0;\n}\n\nvoid edit_robot(context *ctx, struct robot *cur_robot, int *ret_value)\n{\n  struct world *mzx_world = ctx->world;\n  char *name = cur_robot->robot_name;\n  int new_char;\n\n  // Edit name.\n  if(!input_window(mzx_world, \"Name for robot:\", name, ROBOT_NAME_SIZE - 1))\n  {\n    // Edit character.\n    new_char = char_selection(cur_robot->robot_char);\n    if(new_char < 0)\n    {\n      // ESC means keep char but don't edit robot\n      if(new_char == -256)\n        new_char = 0;\n      cur_robot->robot_char = -new_char;\n    }\n    else\n    {\n      cur_robot->robot_char = new_char;\n      // Now edit the program.\n      robot_editor(ctx, cur_robot);\n    }\n\n    if(ret_value)\n      *ret_value = 0;\n\n    return;\n  }\n\n  if(ret_value)\n    *ret_value = -1;\n}\n\nvoid edit_global_robot(context *ctx)\n{\n  struct world *mzx_world = ctx->world;\n  struct robot *cur_robot = &(mzx_world->global_robot);\n  char *name = cur_robot->robot_name;\n\n  if(!input_window(mzx_world, \"Name for robot:\", name, ROBOT_NAME_SIZE - 1))\n    robot_editor(ctx, cur_robot);\n}\n"
  },
  {
    "path": "src/editor/param.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declarations for PARAM.CPP */\n\n#ifndef __EDITOR_PARAM_H\n#define __EDITOR_PARAM_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../core.h\"\n\nint edit_param(struct world *mzx_world, int id, int param);\nint edit_scroll(struct world *mzx_world, struct scroll *cur_scroll);\nint edit_sensor(struct world *mzx_world, struct sensor *cur_sensor);\nvoid edit_robot(context *ctx, struct robot *cur_robot, int *ret_value);\nvoid edit_global_robot(context *ctx);\n\n__M_END_DECLS\n\n#endif // __EDITOR_PARAM_H\n"
  },
  {
    "path": "src/editor/robo_debug.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <assert.h>\n#include <ctype.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"debug.h\"\n#include \"robot.h\"\n#include \"robo_debug.h\"\n#include \"stringsearch.h\"\n#include \"window.h\"\n\n#include \"../core.h\"\n#include \"../counter.h\"\n#include \"../event.h\"\n#include \"../expr.h\"\n#include \"../graphics.h\"\n#include \"../robot.h\"\n#include \"../str.h\"\n#include \"../util.h\"\n#include \"../window.h\"\n#include \"../world_struct.h\"\n\n#define MATCH_STRING_MAX 61\n\nstruct breakpoint\n{\n  char match_name[ROBOT_NAME_SIZE];\n  char match_string[MATCH_STRING_MAX];\n  struct string_search_data index;\n  int line_number;\n  int match_name_len;\n  int match_string_len;\n};\n\nstruct watchpoint\n{\n  char match_name[MATCH_STRING_MAX];\n  char match_value[MATCH_STRING_MAX];\n  boolean is_string;\n  boolean tried_lookup;\n  boolean watch_value;\n  const struct counter *ctr;\n  const struct string *str;\n  int last_value;\n  int match_value_i;\n};\n\nstatic int num_breakpoints = 0;\nstatic int num_breakpoints_allocated = 0;\nstatic struct breakpoint **breakpoints = NULL;\n\nstatic int num_watchpoints = 0;\nstatic int num_watchpoints_allocated = 0;\nstatic struct watchpoint **watchpoints = NULL;\n\n// Whether or not the robot debugger is enabled\nstatic int robo_debugger_enabled = 0;\n\n// Whether or not the user has explicitly enabled/disabled it yet\nstatic int robo_debugger_override = 0;\n\n// Whether or not the debugger is stepping through code\nstatic boolean step = false;\n\n// Last positions selected\nstatic int br_selected = 0;\nstatic int wt_selected = 0;\n\n// The last debugger option chosen\nstatic int selected = 0;\n\n// The debugger's position on-screen (1=bottom)\nstatic int position = 1;\n\n\nstatic inline int hash_string(const char *data, size_t length)\n{\n  unsigned int i;\n  int x = length ? *data : 0;\n\n  for(i = 1; i < length; i++)\n  {\n    data++;\n    x ^= (x << 5) + (x >> 2) + *data;\n  }\n\n  return x;\n}\n\nstatic inline int get_watchpoint_value(struct world *mzx_world,\n struct watchpoint *wt, struct string *dest)\n{\n  /**\n   * TODO maybe find a way to make watchpoints that rely on the robot ID to\n   * work consistently instead of defaulting to the global robot. As this is\n   * implemented now, there would be strange behavior when control passes from\n   * one robot to another. Updating every watchpoint at the start of every robot\n   * execution would fix this except if the counter debugger is used mid-cycle,\n   * which would cause similar unexpected (to the user) behavior. For now, just\n   * assume every local counter access is done by the global robot.\n   */\n\n  if(wt->is_string)\n  {\n    struct string src_string;\n    char tmp[MATCH_STRING_MAX];\n\n    if(!dest)\n      dest = &src_string;\n\n    if(wt->str)\n    {\n      dest->value = wt->str->value;\n      dest->length = wt->str->length;\n      return hash_string(wt->str->value, wt->str->length);\n    }\n\n    memcpy(tmp, wt->match_name, MATCH_STRING_MAX);\n    if(!get_string(mzx_world, tmp, dest, 0))\n    {\n      dest->value = NULL;\n      dest->length = 0;\n      return 0;\n    }\n\n    if(!wt->tried_lookup)\n    {\n      wt->str = get_string_pointer(mzx_world, wt->match_name, 0);\n      wt->tried_lookup = true;\n    }\n\n    return hash_string(dest->value, dest->length);\n  }\n  else\n  {\n    int value;\n\n    if(wt->ctr)\n      return wt->ctr->value;\n\n    value = get_counter_safe(mzx_world, wt->match_name, 0);\n    if(!value)\n      return 0;\n\n    // Counter isn't guaranteed to exist unless the value is non-zero!\n    // This might also be a function counter, so only attempt once.\n    if(!wt->tried_lookup)\n    {\n      wt->ctr = get_counter_pointer(mzx_world, wt->match_name, 0);\n      wt->tried_lookup = true;\n    }\n\n    return value;\n  }\n}\n\nvoid update_watchpoint_last_values(struct world *mzx_world)\n{\n  struct watchpoint *wt;\n  int i;\n\n  for(i = 0; i < num_watchpoints; i++)\n  {\n    wt = watchpoints[i];\n    wt->last_value = get_watchpoint_value(mzx_world, wt, NULL);\n  }\n}\n\n\n/*********************/\n/* Breakpoint editor */\n/*********************/\n\nstatic int edit_breakpoint_dialog(struct world *mzx_world,\n struct breakpoint *br, const char *title)\n{\n  char match_string[MATCH_STRING_MAX];\n  char match_name[ROBOT_NAME_SIZE];\n  int line_number;\n\n  struct element *elements[5];\n  struct dialog di;\n  int result;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  memcpy(match_string, br->match_string, MATCH_STRING_MAX);\n  memcpy(match_name, br->match_name, ROBOT_NAME_SIZE);\n  line_number = br->line_number;\n\n  elements[0] = construct_input_box(2, 1,\n   \"Match text:\", 60, match_string);\n\n  elements[1] = construct_input_box(2, 3,\n   \"Match robot name:\", ROBOT_NAME_SIZE - 1, match_name);\n\n  elements[2] = construct_number_box(38, 3,\n   \"Match line number:\", 0, 99999, NUMBER_BOX, &line_number);\n\n  elements[3] = construct_button(22, 5, \"Confirm\", 0);\n  elements[4] = construct_button(45, 5, \"Cancel\", -1);\n\n  construct_dialog(&di, title, 2, 7, 76, 7, elements, ARRAY_SIZE(elements), 0);\n\n  result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(!result)\n  {\n    memcpy(br->match_string, match_string, MATCH_STRING_MAX);\n    memcpy(br->match_name, match_name, ROBOT_NAME_SIZE);\n    br->line_number = line_number;\n    br->match_string_len = strlen(match_string);\n    br->match_name_len = strlen(match_name);\n  }\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  return result;\n}\n\nstatic int edit_watchpoint_dialog(struct world *mzx_world,\n struct watchpoint *wt, const char *title)\n{\n  char match_name[MATCH_STRING_MAX];\n  char match_value[MATCH_STRING_MAX];\n\n  struct element *elements[4];\n  struct dialog di;\n  int result;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  memcpy(match_name, wt->match_name, MATCH_STRING_MAX);\n  memcpy(match_value, wt->match_value, MATCH_STRING_MAX);\n\n  elements[0] = construct_input_box(3, 1,\n   \"Variable:\", 60, match_name);\n  elements[1] = construct_input_box(3, 2,\n   \"Value:   \", 60, match_value);\n\n  elements[2] = construct_button(22, 4, \"Confirm\", 0);\n  elements[3] = construct_button(45, 4, \"Cancel\", -1);\n\n  construct_dialog(&di, title, 2, 8, 76, 5, elements, ARRAY_SIZE(elements), 0);\n\n  result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(!result)\n  {\n    memset(wt, 0, sizeof(struct watchpoint));\n    memcpy(wt->match_name, match_name, MATCH_STRING_MAX);\n    memcpy(wt->match_value, match_value, MATCH_STRING_MAX);\n    wt->is_string = is_string(match_name);\n\n    if(match_value[0])\n    {\n      wt->watch_value = true;\n      if(wt->is_string)\n        wt->match_value_i = strlen(match_value);\n      else\n        wt->match_value_i = strtol(match_value, NULL, 10);\n    }\n  }\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  return result;\n}\n\nstatic void new_breakpoint(struct world *mzx_world)\n{\n  struct breakpoint *br = (struct breakpoint *)ccalloc(1, sizeof(struct breakpoint));\n\n  if(!edit_breakpoint_dialog(mzx_world, br, \"New Breakpoint\"))\n  {\n    if(num_breakpoints == num_breakpoints_allocated)\n    {\n      num_breakpoints_allocated = MAX(32, num_breakpoints_allocated * 2);\n      breakpoints = (struct breakpoint **)crealloc(breakpoints,\n       num_breakpoints_allocated * sizeof(struct breakpoint *));\n    }\n\n    string_search_index(br->match_string, br->match_string_len,\n     &(br->index), true);\n\n    breakpoints[num_breakpoints] = br;\n    num_breakpoints++;\n\n    if(!robo_debugger_override)\n      robo_debugger_enabled = 1;\n  }\n\n  else\n  {\n    free(br);\n  }\n}\n\nstatic void new_watchpoint(struct world *mzx_world)\n{\n  struct watchpoint *wt = (struct watchpoint *)ccalloc(1, sizeof(struct watchpoint));\n\n  if(!edit_watchpoint_dialog(mzx_world, wt, \"New Watchpoint\"))\n  {\n    if(num_watchpoints == num_watchpoints_allocated)\n    {\n      num_watchpoints_allocated = MAX(32, num_watchpoints_allocated * 2);\n      watchpoints = (struct watchpoint **)crealloc(watchpoints,\n       num_watchpoints_allocated * sizeof(struct watchpoint *));\n    }\n\n    watchpoints[num_watchpoints] = wt;\n    num_watchpoints++;\n\n    if(!robo_debugger_override)\n      robo_debugger_enabled = 1;\n  }\n\n  else\n  {\n    free(wt);\n  }\n}\n\nstatic int debug_config_idle_function(struct world *mzx_world,\n struct dialog *di, int key)\n{\n  // List element, so filter unhandled modifiers.\n  if(get_ctrl_status(keycode_internal) || get_shift_status(keycode_internal))\n    return key;\n\n  switch(key)\n  {\n    // Add\n    case IKEY_a:\n    case IKEY_n:\n    {\n      if(get_alt_status(keycode_internal))\n      {\n        di->return_value = 1;\n        di->done = 1;\n        return 0;\n      }\n      break;\n    }\n    // Delete selection\n    case IKEY_d:\n    {\n      if(get_alt_status(keycode_internal))\n      {\n        di->return_value = 2;\n        di->done = 1;\n        return 0;\n      }\n      break;\n    }\n    // Edit selection\n    case IKEY_e:\n    {\n      if(get_alt_status(keycode_internal))\n      {\n        di->return_value = 0;\n        di->done = 1;\n        return 0;\n      }\n      break;\n    }\n  }\n\n  return key;\n}\n\n/* Results:\n * -1 : close\n *  0 : edit\n *  1 : add/new\n *  2 : delete\n *  3 : enable/disable debugger\n */\n\nvoid __debug_robot_config(struct world *mzx_world)\n{\n  const char *enable_text[] = {\n   \"Enable Debugger \",\n   \"Disable Debugger\",\n  };\n\n  struct breakpoint *br;\n  struct watchpoint *wt;\n  int i;\n\n  int result = 0;\n  struct element *elements[10];\n  struct dialog di;\n\n  int br_element = 6;\n  int wt_element = 7;\n\n  int focus = br_element;\n\n  br_selected = MIN(br_selected, num_breakpoints);\n  wt_selected = MIN(wt_selected, num_watchpoints);\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_BREAKPOINT_EDITOR);\n\n  do\n  {\n    char **br_list = ccalloc(num_breakpoints + 1, sizeof(char *));\n    char **wt_list = ccalloc(num_watchpoints + 1, sizeof(char *));\n    int br_list_size = num_breakpoints;\n    int wt_list_size = num_watchpoints;\n\n    for(i = 0; i < num_breakpoints; i++)\n    {\n      char *line = ccalloc(77, 1);\n      memset(line, ' ', 60);\n\n      br = breakpoints[i];\n\n      memcpy(line, br->match_string, br->match_string_len);\n\n      if(br->line_number)\n      {\n        sprintf(line + 53, \"%d\", br->line_number);\n        line[strlen(line)] = ' ';\n      }\n\n      strcpy(line + 60, br->match_name);\n\n      br_list[i] = line;\n    }\n\n    for(i = 0; i < num_watchpoints; i++)\n    {\n      char *line = ccalloc(77, 1);\n      memset(line, ' ', 60);\n\n      wt = watchpoints[i];\n\n      memcpy(line, wt->match_name, strlen(wt->match_name));\n\n      if(is_string(wt->match_name))\n      {\n        sprintf(line + 60, \"<string>\");\n      }\n      else\n      {\n        snprintf(line + 60, 12, \"%d\", wt->last_value);\n      }\n\n      wt_list[i] = line;\n    }\n\n    br_list[num_breakpoints] = (char *) \"(new)\";\n    wt_list[num_watchpoints] = (char *) \"(new)\";\n\n    elements[0] = construct_label(3, 23,\n     \"~9Alt+N : New   Enter : Edit   Alt+D : Delete\");\n\n    elements[1] = construct_label(2, 1, \"Breakpoint substring\");\n    elements[2] = construct_label(55, 1, \"Line\");\n    elements[3] = construct_label(62, 1, \"Robot name\");\n\n    elements[4] = construct_label(2, 12, \"Watchpoint variable\");\n    elements[5] = construct_label(62, 12, \"Last value\");\n\n    elements[br_element] = construct_list_box(2, 2,\n     (const char **)br_list, num_breakpoints + 1,\n     9, 76, 0, &br_selected, NULL, false);\n\n    elements[wt_element] = construct_list_box(2, 13,\n     (const char **)wt_list, num_watchpoints + 1,\n     9, 76, 0, &wt_selected, NULL, false);\n\n    elements[8] = construct_button(50, 23,\n     enable_text[robo_debugger_enabled], 3);\n\n    elements[9] = construct_button(70, 23, \"Done\", -1);\n\n    construct_dialog_ext(&di, \"Configure Robot Debugger\", 0, 0, 80, 25,\n     elements, ARRAY_SIZE(elements), 0, 0, focus, debug_config_idle_function);\n\n    result = run_dialog(mzx_world, &di);\n    focus = di.current_element;\n\n    switch(result)\n    {\n      // Edit\n      case 0:\n      {\n        if(focus == br_element)\n        {\n          if(br_selected < num_breakpoints)\n          {\n            br = breakpoints[br_selected];\n\n            if(!edit_breakpoint_dialog(mzx_world, br, \"Edit Breakpoint\"))\n            {\n              string_search_index(br->match_string, br->match_string_len,\n               &(br->index), true);\n            }\n          }\n          else\n\n          if(br_selected == num_breakpoints)\n          {\n            new_breakpoint(mzx_world);\n          }\n        }\n        else\n\n        if(focus == wt_element)\n        {\n          if(wt_selected < num_watchpoints)\n          {\n            wt = watchpoints[wt_selected];\n\n            edit_watchpoint_dialog(mzx_world, wt, \"Edit Watchpoint\");\n          }\n          else\n\n          if(wt_selected == num_watchpoints)\n          {\n            new_watchpoint(mzx_world);\n          }\n        }\n        break;\n      }\n\n      // Add\n      case 1:\n      {\n        if(focus == br_element)\n        {\n          new_breakpoint(mzx_world);\n        }\n        else\n\n        if(focus == wt_element)\n        {\n          new_watchpoint(mzx_world);\n        }\n        break;\n      }\n\n      // Delete\n      case 2:\n      {\n        if(focus == br_element && br_selected < num_breakpoints)\n        {\n          if(!confirm(mzx_world, \"Delete breakpoint?\"))\n          {\n            free(breakpoints[br_selected]);\n            memmove(breakpoints + br_selected, breakpoints + br_selected + 1,\n             (num_breakpoints - br_selected - 1) * sizeof(struct breakpoint *));\n\n            num_breakpoints--;\n            if(br_selected == num_breakpoints && br_selected > 0)\n              br_selected--;\n          }\n        }\n        else\n\n        if(focus == wt_element && wt_selected < num_watchpoints)\n        {\n          if(!confirm(mzx_world, \"Delete watchpoint?\"))\n          {\n            free(watchpoints[wt_selected]);\n            memmove(watchpoints + wt_selected, watchpoints + wt_selected + 1,\n             (num_watchpoints - wt_selected - 1) * sizeof(struct watchpoint *));\n\n            num_watchpoints--;\n            if(wt_selected == num_watchpoints && wt_selected > 0)\n              wt_selected--;\n          }\n        }\n        break;\n      }\n\n      // Enable/Disable\n      case 3:\n      {\n        robo_debugger_enabled ^= 1;\n        robo_debugger_override = 1;\n        break;\n      }\n    }\n\n    destruct_dialog(&di);\n\n    update_watchpoint_last_values(mzx_world);\n\n    for(i = 0; i < br_list_size; i++)\n      free(br_list[i]);\n\n    for(i = 0; i < wt_list_size; i++)\n      free(wt_list[i]);\n\n    free(br_list);\n    free(wt_list);\n  }\n  while(result > -1);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  pop_context();\n}\n\n// Reset the debugger without clearing the breakpoints/watchpoints.\n// For now, this just resets the step status and cached watchpoint values\n// (so MegaZeux doesn't crash).\nvoid __debug_robot_reset(struct world *mzx_world)\n{\n  int i;\n\n  for(i = 0; i < num_watchpoints; i++)\n  {\n    watchpoints[i]->last_value = 0;\n    watchpoints[i]->tried_lookup = false;\n    watchpoints[i]->ctr = NULL;\n    watchpoints[i]->str = NULL;\n  }\n\n  step = false;\n}\n\n// Called when the editor exits\nvoid free_breakpoints(void)\n{\n  int i;\n\n  if(breakpoints)\n    for(i = 0; i < num_breakpoints; i++)\n      free(breakpoints[i]);\n\n  if(watchpoints)\n    for(i = 0; i < num_watchpoints; i++)\n      free(watchpoints[i]);\n\n  free(breakpoints);\n  free(watchpoints);\n\n  breakpoints = NULL;\n  num_breakpoints = 0;\n  num_breakpoints_allocated = 0;\n\n  watchpoints = NULL;\n  num_watchpoints = 0;\n  num_watchpoints_allocated = 0;\n\n  robo_debugger_enabled = 0;\n  robo_debugger_override = 0;\n  step = false;\n}\n\n\n/********************/\n/* Robotic debugger */\n/********************/\n\nstatic int goto_send_dialog(struct world *mzx_world, int robot_id)\n{\n  const char *target_strs[] = {\n    \"Goto\",\n    \"Send\",\n    \"Send all\",\n  };\n\n  const char *ignore_strs[] = {\n    \"Send/send all ignores locked status?\"\n  };\n\n  int target = 1;\n  int ignore[] = { 0 };\n\n  char name_in[52] = { 0 };\n  char label_in[52] = { 0 };\n\n  char *name_tr;\n  char *label_tr;\n\n  struct element *elements[6];\n  struct dialog di;\n  int result;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  elements[0] = construct_radio_button(2, 1,\n   target_strs, ARRAY_SIZE(target_strs), 8, &target);\n\n  elements[1] = construct_input_box(17, 1,\n   \"Name:\", 51, name_in);\n\n  elements[2] = construct_input_box(16, 2,\n   \"Label:\", 51, label_in);\n\n  elements[3] = construct_check_box(21, 3,\n   ignore_strs, ARRAY_SIZE(ignore_strs), 50, ignore);\n\n  elements[4] = construct_button(26, 4, \"Confirm\", 0);\n  elements[5] = construct_button(42, 4, \"Cancel\", -1);\n\n  construct_dialog(&di, \"Goto/Send\", 2, 9, 76, 6,\n   elements, ARRAY_SIZE(elements), 0);\n\n  result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  if(!result)\n  {\n    label_tr = cmalloc(ROBOT_MAX_TR);\n    tr_msg(mzx_world, label_in, robot_id, label_tr);\n\n    switch(target)\n    {\n      // Goto\n      case 0:\n      {\n        send_robot_id(mzx_world, robot_id, label_tr, 1);\n        break;\n      }\n\n      // Send\n      case 1:\n      {\n        name_tr = cmalloc(ROBOT_MAX_TR);\n        tr_msg(mzx_world, name_in, robot_id, name_tr);\n\n        send_robot(mzx_world, name_tr, label_tr, ignore[0]);\n\n        free(name_tr);\n        break;\n      }\n\n      // Send all\n      case 2:\n      {\n        sprintf(name_in, \"all\");\n        send_robot(mzx_world, name_in, label_tr, ignore[0]);\n        break;\n      }\n    }\n\n    free(label_tr);\n  }\n\n  return result;\n}\n\nenum operation {\n  OP_DO_NOTHING = -2,\n  OP_CONTINUE   = -1,\n  OP_STEP       =  0,\n  OP_GOTO       =  1,\n  OP_HALT       =  2,\n  OP_HALT_ALL   =  3,\n  OP_COUNTERS   =  4,\n  OP_CONFIG     =  5,\n};\n\nstatic int debug_robot_idle_function(struct world *mzx_world,\n struct dialog *di, int key)\n{\n  // For most of these, do nothing if the key is being held.\n  int is_press = (get_key_status(keycode_internal_wrt_numlock, key) == 1);\n\n  switch(key)\n  {\n    case IKEY_c:\n    {\n      if(!is_press)\n        return 0;\n\n      di->return_value = OP_CONTINUE;\n      di->done = 1;\n      break;\n    }\n    case IKEY_s:\n    {\n      if(!is_press)\n        return 0;\n\n      di->return_value = OP_STEP;\n      di->done = 1;\n      break;\n    }\n    case IKEY_g:\n    {\n      if(!is_press)\n        return 0;\n\n      di->return_value = OP_GOTO;\n      di->done = 1;\n      break;\n    }\n    case IKEY_h:\n    {\n      if(!is_press)\n        return 0;\n\n      if(get_alt_status(keycode_internal))\n        di->return_value = OP_HALT_ALL;\n      else\n        di->return_value = OP_HALT;\n      di->done = 1;\n      break;\n    }\n    case IKEY_F11:\n    {\n      if(!is_press)\n        return 0;\n\n      if(get_alt_status(keycode_internal))\n        di->return_value = OP_CONFIG;\n      else\n        di->return_value = OP_COUNTERS;\n      di->done = 1;\n      return 0;\n    }\n    case IKEY_PAGEUP:\n    {\n      if(!is_press)\n        return 0;\n\n      position = 0;\n      di->return_value = OP_DO_NOTHING;\n      di->done = 1;\n      break;\n    }\n    case IKEY_PAGEDOWN:\n    {\n      if(!is_press)\n        return 0;\n\n      position = 1;\n      di->return_value = OP_DO_NOTHING;\n      di->done = 1;\n      break;\n    }\n  }\n\n  if(di->done)\n    return 0;\n\n  return key;\n}\n\nenum actions {\n  ACTION_STEPPED,\n  ACTION_MATCHED,\n  ACTION_CAUGHT,\n  ACTION_CAUGHT_AUTO,\n  ACTION_WATCH,\n};\n\n// Don't exceed 8 chars.\nstatic const char *action_strings[] = {\n  \"next:\",\n  \"matched\",\n  \"caught\",\n  \"enabled;\",\n  \"watch:\",\n};\n\nstatic void debug_robot_title(char buffer[77], struct robot *cur_robot, int id,\n int action, int line_number)\n{\n  snprintf(buffer, 76, \"Robot Debugger - %s `%s` (%i@%i,%i) at line %i:\",\n   action_strings[action],\n   cur_robot->robot_name, id, cur_robot->xpos, cur_robot->ypos, line_number);\n\n  buffer[76] = 0;\n}\n\n// If the return value != 0, the robot ignores the current command and ends\nstatic int debug_robot(context *ctx, struct robot *cur_robot, int id,\n char title[77], char info[77], char *src_ptr, int src_length, int lines_run)\n{\n  struct world *mzx_world = ctx->world;\n\n  // TODO: when limitations on command length are lifted,\n  // this needs to be extended.\n  char buffer[ROBOT_MAX_TR] = { 0 };\n  int buffer_len = ARRAY_SIZE(buffer);\n\n  char *buffer_pos;\n  int buffer_left;\n\n  char *src_end = src_ptr + src_length;\n  char *src_pos;\n\n  struct dialog di;\n  struct element *elements[8];\n  int num_elements = ARRAY_SIZE(elements);\n\n  int dialog_result;\n  int dialog_y = 0;\n\n  int ret_val = 0;\n  int height;\n  int ypos;\n  int len;\n\n  int line_len;\n  char t;\n  int i;\n\n  // Build the code/info text display\n  src_pos = src_ptr;\n  buffer_pos = buffer;\n  buffer_left = buffer_len - 1;\n  ypos = 0;\n\n  if(info && info[0])\n  {\n    snprintf(buffer_pos, 77, \"%s\", info);\n    buffer_pos[76] = 0;\n\n    len = strlen(buffer_pos);\n    buffer_pos[len++] = '\\n';\n    buffer_pos[len++] = '\\n';\n    buffer_pos[len] = 0;\n\n    buffer_pos += len;\n    buffer_left -= len;\n    ypos += 2;\n  }\n\n  strcpy(buffer_pos, \"~b\");\n  len = strlen(buffer_pos);\n  buffer_pos += len;\n  buffer_left -= len;\n\n  line_len = 0;\n  while(buffer_left && src_pos < src_end && ypos <= 20)\n  {\n    // Wrap line.\n    if(line_len >= 76 && buffer_left > 1)\n    {\n      *buffer_pos = '\\n';\n      buffer_pos++;\n      buffer_left--;\n      line_len = 0;\n      ypos++;\n      continue;\n    }\n\n    t = *src_pos;\n    src_pos++;\n\n    switch(t)\n    {\n      // Insert an extra ~ or @ so they display instead of changing the color.\n      case '~':\n      {\n        if(buffer_left > 1)\n        {\n          *buffer_pos = '~';\n          buffer_pos++;\n          buffer_left--;\n        }\n        break;\n      }\n\n      case '@':\n      {\n        if(buffer_left > 1)\n        {\n          *buffer_pos = '@';\n          buffer_pos++;\n          buffer_left--;\n        }\n        break;\n      }\n\n      case '\\n':\n        line_len = 0;\n        ypos++;\n    }\n\n    *buffer_pos = t;\n    buffer_pos++;\n    buffer_left--;\n    line_len++;\n  }\n\n  *buffer_pos = 0;\n\n  // Add 3 to get the button position. ypos may go over 20, so clamp it.\n  ypos = MIN(ypos, 20) + 3;\n\n  height = ypos + 2;\n\n  // Keep running track of the last position we were caught at.\n  cur_robot->commands_caught = lines_run;\n\n  // Update these temporarily for the counter debugger.\n  cur_robot->commands_total += lines_run;\n  cur_robot->commands_cycle = lines_run;\n\n  dialog_fadein();\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_ROBOT_DEBUG);\n\n  // Open debug dialog\n  do\n  {\n    if(position)\n      dialog_y = 25 - height;\n    else\n      dialog_y = 0;\n\n    elements[0]  = construct_button( 3, ypos, \"Continue\", OP_CONTINUE);\n    elements[1]  = construct_button(15, ypos, \"Step\", OP_STEP);\n    elements[2]  = construct_button(23, ypos, \"Goto\", OP_GOTO);\n    elements[3]  = construct_button(31, ypos, \"Halt\", OP_HALT);\n    elements[4]  = construct_button(39, ypos, \"Halt all\", OP_HALT_ALL);\n    elements[5]  = construct_button(57, ypos, \"Counters\", OP_COUNTERS);\n    elements[6]  = construct_button(69, ypos, \"Config\", OP_CONFIG);\n    elements[7]  = construct_label(2, 1, buffer);\n\n    construct_dialog_ext(&di, title, 0, dialog_y, 80, height,\n     elements, num_elements, 0, 0, selected, debug_robot_idle_function);\n\n    dialog_result = run_dialog(mzx_world, &di);\n\n    if(dialog_result > OP_DO_NOTHING)\n      selected = dialog_result + 1;\n\n    switch(dialog_result)\n    {\n      case OP_DO_NOTHING:\n      {\n        break;\n      }\n\n      case OP_CONTINUE:\n      {\n        ret_val = DEBUG_EXIT;\n        step = false;\n        break;\n      }\n\n      case OP_STEP:\n      {\n        ret_val = DEBUG_EXIT;\n        step = true;\n        break;\n      }\n\n      case OP_GOTO:\n      {\n        if(!goto_send_dialog(mzx_world, id))\n        {\n          ret_val = DEBUG_GOTO;\n          step = true;\n        }\n        break;\n      }\n\n      case OP_HALT_ALL:\n      {\n        struct board *cur_board = mzx_world->current_board;\n        // We pretty much need to simulate exactly what happens when\n        // a robot hits the end command, here.\n        for(i = 0; i < cur_board->num_robots + 1; i++)\n        {\n          struct robot *r = cur_board->robot_list[i];\n          if(r)\n          {\n            r->status = 1;\n            r->walk_dir = 0;\n            r->cur_prog_line = 0;\n            r->pos_within_line = 0;\n          }\n        }\n        // continue to OP_HALT\n      }\n\n      /* fallthrough */\n\n      case OP_HALT:\n      {\n        cur_robot->status = 1;\n        cur_robot->walk_dir = 0;\n        cur_robot->cur_prog_line = 0;\n        cur_robot->pos_within_line = 0;\n\n        ret_val = DEBUG_HALT;\n        step = false;\n        break;\n      }\n\n      case OP_COUNTERS:\n      {\n        __debug_counters(ctx);\n        break;\n      }\n\n      case OP_CONFIG:\n      {\n        __debug_robot_config(mzx_world);\n        break;\n      }\n    }\n\n    destruct_dialog(&di);\n  }\n  while(!ret_val);\n\n  pop_context();\n\n  dialog_fadeout();\n\n  // These aren't final yet, so change them back.\n  cur_robot->commands_total -= lines_run;\n  cur_robot->commands_cycle = 0;\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  return ret_val;\n}\n\nstatic inline void get_src_line(struct robot *cur_robot, char **_src_ptr,\n int *_src_length, int *_real_line_num)\n{\n  struct command_mapping *cmd_map = cur_robot->command_map;\n  int line_num = get_program_command_num(cur_robot, cur_robot->cur_prog_line);\n\n  char *src_ptr;\n  int src_length;\n  int offset;\n\n  if(cmd_map)\n  {\n    offset = cmd_map[line_num].src_pos;\n    src_ptr = cur_robot->program_source + offset;\n\n    if(line_num + 1 < cur_robot->command_map_length)\n    {\n      src_length = cmd_map[line_num + 1].src_pos - offset - 1;\n    }\n    else\n    {\n      src_length = strlen(src_ptr);\n    }\n\n    // Remove any trailing newlines that might have made it in\n    while(src_length > 0 && isspace((unsigned char)src_ptr[src_length - 1]))\n      src_length--;\n\n    *_src_ptr = src_ptr;\n    *_src_length = src_length;\n    *_real_line_num = cmd_map[line_num].real_line;\n  }\n\n  else\n  {\n    *_src_ptr = (char *) \"<no source available>\";\n    *_src_length = strlen(*_src_ptr);\n    *_real_line_num = line_num;\n  }\n}\n\nint __debug_robot_break(context *ctx, struct robot *cur_robot,\n int id, int lines_run)\n{\n  struct world *mzx_world = ctx->world;\n  char title[77];\n  char info[77];\n\n  int action = ACTION_STEPPED;\n  int line_number = 0;\n  int src_length = 0;\n  char *src_ptr = NULL;\n\n  int i;\n\n  info[0] = 0;\n\n  // Enable and trigger a break if a robot seems to be out of control.\n  if(lines_run - cur_robot->commands_caught >= mzx_world->commands_stop)\n  {\n    if(!robo_debugger_enabled)\n    {\n      action = ACTION_CAUGHT_AUTO;\n    }\n\n    else\n    {\n      action = ACTION_CAUGHT;\n    }\n\n    sprintf(info, \"~9Commands run exceeded ~c`COMMANDS_STOP`~9 value ~a%d~9.\",\n     mzx_world->commands_stop);\n\n    robo_debugger_enabled = 1;\n    step = 1;\n  }\n\n  if(!robo_debugger_enabled)\n    return 0;\n\n#ifndef CONFIG_DEBYTECODE\n  // Make sure source exists for debugging.\n  prepare_robot_source(cur_robot);\n#endif\n\n  // Get the current line.\n  get_src_line(cur_robot, &src_ptr, &src_length, &line_number);\n\n  if(cur_robot->program_source)\n  {\n    // Attempt to match a breakpoint\n    if(!step)\n    {\n      struct breakpoint *b;\n      int match_line;\n\n      boolean match = false;\n\n      for(i = 0; i < num_breakpoints; i++)\n      {\n        b = breakpoints[i];\n        match_line = b->line_number;\n\n        // Ignore empty breakpoints\n        if(!b->match_string_len && !b->match_name_len && !match_line)\n          continue;\n\n        // Make sure the line number is correct\n        if(match_line && match_line != line_number)\n          continue;\n\n        // Make sure the robot name is correct\n        if(b->match_name_len &&\n         strcasecmp(cur_robot->robot_name, b->match_name))\n          continue;\n\n        // Try to find the match pattern in the line\n        if(b->match_string_len)\n          if(!string_search((void *)src_ptr, src_length,\n           (void *)b->match_string, b->match_string_len, &(b->index), true))\n            continue;\n\n        action = ACTION_MATCHED;\n        match = true;\n        break;\n      }\n\n      if(!match)\n        return 0;\n    }\n  }\n\n  else\n  {\n    // If there isn't any source information available, we can't do much.\n    if(!step)\n      return 0;\n  }\n\n  // Run the robot debugger\n  debug_robot_title(title, cur_robot, id, action, line_number);\n  return debug_robot(ctx, cur_robot, id, title, info, src_ptr, src_length,\n   lines_run);\n}\n\nint __debug_robot_watch(context *ctx, struct robot *cur_robot,\n int id, int lines_run)\n{\n  struct world *mzx_world = ctx->world;\n  char title[77];\n  char info[77];\n\n  int line_number = 0;\n  int src_length = 0;\n  char *src_ptr = NULL;\n\n  struct watchpoint *wt;\n  struct string src_string;\n  boolean match = false;\n  int value = 0;\n  int prev = 0;\n  int i;\n\n  if(!robo_debugger_enabled)\n    return 0;\n\n  info[0] = 0;\n\n  for(i = 0; i < num_watchpoints; i++)\n  {\n    wt = watchpoints[i];\n\n    value = get_watchpoint_value(mzx_world, wt, &src_string);\n    if(value != wt->last_value)\n    {\n      prev = wt->last_value;\n      wt->last_value = value;\n\n      if(wt->watch_value)\n      {\n        // Only match if the variable is exactly the requested value.\n        if(wt->is_string)\n        {\n          if((size_t)wt->match_value_i != src_string.length ||\n           (src_string.value && memcmp(wt->match_value, src_string.value, src_string.length)))\n            continue;\n        }\n        else\n        {\n          if(wt->match_value_i != value)\n            continue;\n        }\n      }\n      match = true;\n      break;\n    }\n  }\n\n  if(!match)\n    return 0;\n\n  if(wt->is_string)\n  {\n    snprintf(info, sizeof(info), \"~a@0 changed ~9@1: watch ~c`%.47s`\",\n     wt->match_name);\n  }\n  else\n  {\n    snprintf(info, sizeof(info), \"~a@0 %d ~8\\x1A ~a%d ~9@1: watch ~c`%.45s`\",\n     prev, value, wt->match_name);\n  }\n  info[sizeof(info) - 1] = '\\0';\n\n  // Get the current line.\n  get_src_line(cur_robot, &src_ptr, &src_length, &line_number);\n\n  // Run the robot debugger\n  debug_robot_title(title, cur_robot, id, ACTION_WATCH, line_number);\n  return debug_robot(ctx, cur_robot, id, title, info, src_ptr, src_length,\n   lines_run);\n}\n"
  },
  {
    "path": "src/editor/robo_debug.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017-2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_ROBO_DEBUG_H\n#define __EDITOR_ROBO_DEBUG_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../core.h\"\n\nEDITOR_LIBSPEC void __debug_robot_config(struct world *mzx_world);\nEDITOR_LIBSPEC void __debug_robot_reset(struct world *mzx_world);\n\nEDITOR_LIBSPEC int __debug_robot_break(context *ctx,\n struct robot *cur_robot, int id, int lines_run);\nEDITOR_LIBSPEC int __debug_robot_watch(context *ctx,\n struct robot *cur_robot, int id, int lines_run);\n\nEDITOR_LIBSPEC void free_breakpoints(void);\n\nvoid update_watchpoint_last_values(struct world *mzx_world);\n\n__M_END_DECLS\n\n#endif // __EDITOR_ROBO_DEBUG_H\n"
  },
  {
    "path": "src/editor/robo_ed.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2021-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// TODO: Confirmation dialog for mainstream releases (currently DBC-only)\n// FIXME: Confirmation dialog appears when the source hasn't been modified sometimes\n\n// Reconstructed robot editor. This is only a shell - the actual\n// robot assembly/disassembly code is in rasm.cpp.\n\n#include <ctype.h>\n#include <limits.h>\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"../caption.h\"\n#include \"../configure.h\"\n#include \"../core.h\"\n#include \"../error.h\"\n#include \"../event.h\"\n#include \"../graphics.h\"\n#include \"../helpsys.h\"\n#include \"../intake.h\"\n#include \"../rasm.h\"\n#include \"../robot.h\"\n#include \"../util.h\"\n#include \"../window.h\"\n#include \"../world.h\"\n#include \"../io/fsafeopen.h\"\n#include \"../io/path.h\"\n#include \"../io/vio.h\"\n\n#include \"char_ed.h\"\n#include \"clipboard.h\"\n#include \"configure.h\"\n#include \"edit.h\"\n#include \"param.h\"\n#include \"macro.h\"\n#include \"macro_struct.h\"\n#include \"robo_ed.h\"\n#include \"stringsearch.h\"\n#include \"undo.h\"\n#include \"window.h\"\n\n#define combine_colors(a, b)  \\\n  (a) | (b << 4)              \\\n\n#define MAX_COMMAND_LEN 240\n#define MAX_MACRO_RECURSION 16\n#define MAX_MACRO_REPEAT 128\n#define MAX_IDLE_TIMER 15\n\nenum search_option\n{\n  SEARCH_OPTION_NONE,\n  SEARCH_OPTION_SEARCH,\n  SEARCH_OPTION_REPLACE,\n  SEARCH_OPTION_REPLACE_ALL\n};\n\nstatic int copy_buffer_lines;\nstatic int copy_buffer_total_length;\nstatic char **copy_buffer;\n\nstatic int last_color_selected = 0;\nstatic int last_char_selected = 0;\n\nstatic enum search_option last_search_action = SEARCH_OPTION_NONE;\nstatic struct string_search_data search_index;\nstatic char search_string[256];\nstatic char replace_string[256];\nstatic boolean search_wrap_enabled = true;\nstatic boolean search_ignore_case_enabled = true;\n\nstatic const char key_help[(81 * 3) + 1] =\n{\n  \" F1:Help  F2:Color  F3:Char  F4:Param  F5:Char edit  F6-F10:Macros  (see Alt+O) \\n\"\n  \" Alt+Home/S:Mark start  Alt+End/E:Mark end  Alt+U:Unmark   Alt+B:Block Action   \\n\"\n  \" Alt+Ins:Paste  Alt+X:Export  Alt+I:Import  Alt+V:Verify  Ctrl+I/D/C:Valid Mark \\n\"\n};\n\nstatic const char key_help_hide[82] =\n  \"     Press Alt + H to view hotkey information.  Press F1 for extended help.     \\n\";\n\n// Default colors for color coding:\n// current line - 11\n// immediates - 10\n// characters - 14\n// colors - color box or 2\n// directions - 3\n// things - 11\n// params - 2\n// strings - 14\n// equalities - 0\n// conditions - 15\n// items - 11\n// extras - 7\n// commands and command fragments - 15\n\n#define bg_color 8\nstatic const char top_line_connect = 194;\nstatic const char bottom_line_connect = 193;\nstatic const char vertical_line = 179;\nstatic const char horizontal_line = 196;\nstatic const char top_char = 219;\nstatic const char bg_char = 32;\nstatic const char bg_color_solid = combine_colors(0, bg_color);\nstatic const char top_color = 4;\nstatic const char line_color = combine_colors(15, bg_color);\nstatic const char top_text_color = combine_colors(15, 4);\nstatic const char bottom_text_color = combine_colors(15, 1);\nstatic const char top_highlight_color = combine_colors(14, 4);\nstatic const char mark_color = combine_colors(0, 7);\n\nstatic char macros[5][64];\n\nstatic void add_blank_line(struct robot_editor_context *rstate, int relation)\n{\n#ifndef CONFIG_DEBYTECODE\n  if(rstate->size + 3 < rstate->max_size)\n#endif\n  {\n    struct robot_line *new_rline = cmalloc(sizeof(struct robot_line));\n    struct robot_line *current_rline = rstate->current_rline;\n    int current_line = rstate->current_line;\n\n    new_rline->line_text_length = 0;\n    new_rline->line_text = cmalloc(1);\n    new_rline->line_text[0] = 0;\n\n#ifdef CONFIG_DEBYTECODE\n    new_rline->command_type = COMMAND_TYPE_BLANK_LINE;\n    new_rline->color_codes = NULL;\n#else\n    new_rline->line_bytecode = cmalloc(3);\n    new_rline->line_bytecode[0] = 1;\n    new_rline->line_bytecode[1] = 47;\n    new_rline->line_bytecode[2] = 1;\n    new_rline->line_bytecode_length = 3;\n    new_rline->validity_status = valid;\n    rstate->size += 3;\n#endif\n\n    rstate->total_lines++;\n\n    if(relation > 0)\n    {\n      new_rline->next = current_rline->next;\n      new_rline->previous = current_rline;\n\n      if(current_rline->next)\n        current_rline->next->previous = new_rline;\n\n      current_rline->next = new_rline;\n\n      rstate->current_rline = new_rline;\n\n      if(rstate->mark_mode)\n      {\n        if(rstate->mark_start > current_line)\n          rstate->mark_start++;\n\n        if(rstate->mark_end > current_line)\n          rstate->mark_end++;\n      }\n    }\n    else\n    {\n      new_rline->next = current_rline;\n      new_rline->previous = current_rline->previous;\n\n      current_rline->previous->next = new_rline;\n      current_rline->previous = new_rline;\n\n      if(rstate->mark_mode)\n      {\n        if(rstate->mark_start >= current_line)\n          rstate->mark_start++;\n\n        if(rstate->mark_end >= current_line)\n          rstate->mark_end++;\n      }\n    }\n\n    rstate->current_line = current_line + 1;\n  }\n}\n\nstatic void delete_line_contents(struct robot_line *delete_rline)\n{\n#ifdef CONFIG_DEBYTECODE\n  free(delete_rline->color_codes);\n#else\n  free(delete_rline->line_bytecode);\n#endif\n  free(delete_rline->line_text);\n  free(delete_rline);\n}\n\nstatic void delete_current_line(struct robot_editor_context *rstate, int move)\n{\n  if(rstate->total_lines == 1)\n  {\n    add_blank_line(rstate, -1);\n    move = -1;\n  }\n\n  if(rstate->total_lines > 1)\n  {\n    struct robot_line *current_rline = rstate->current_rline;\n    struct robot_line *next = current_rline->next;\n    struct robot_line *previous = current_rline->previous;\n\n#ifndef CONFIG_DEBYTECODE\n    rstate->size -= current_rline->line_bytecode_length;\n#endif\n\n    previous->next = next;\n    if(next)\n      next->previous = previous;\n\n    delete_line_contents(current_rline);\n\n    if(rstate->mark_mode)\n    {\n      if(rstate->mark_start_rline == current_rline)\n        rstate->mark_start_rline = next;\n\n      if(rstate->mark_end_rline == current_rline)\n        rstate->mark_end_rline = previous;\n\n      if(rstate->mark_start > rstate->current_line)\n        rstate->mark_start--;\n\n      if(rstate->mark_end >= rstate->current_line)\n        rstate->mark_end--;\n\n      if(rstate->mark_start > rstate->mark_end)\n        rstate->mark_mode = 0;\n    }\n\n    if(move > 0)\n    {\n      if(next)\n      {\n        rstate->current_rline = next;\n      }\n      else\n      {\n        rstate->current_rline = previous;\n        rstate->current_line--;\n      }\n    }\n    else\n    {\n      if(rstate->current_line != 1)\n      {\n        rstate->current_rline = previous;\n        rstate->current_line--;\n      }\n      else\n      {\n        rstate->current_rline = next;\n      }\n    }\n\n    strcpy(rstate->command_buffer, rstate->current_rline->line_text);\n\n    rstate->total_lines--;\n  }\n}\n\nstatic void delete_robot_lines(struct robot *cur_robot,\n struct robot_editor_context *rstate)\n{\n  struct robot_line *current_rline = rstate->base.next;\n  struct robot_line *next_rline;\n#ifndef CONFIG_DEBYTECODE\n  char *object_code_position;\n\n  object_code_position = cur_robot->program_bytecode;\n  object_code_position[0] = 0xFF;\n  object_code_position++;\n#endif\n\n  while(current_rline)\n  {\n#ifndef CONFIG_DEBYTECODE\n    if((current_rline->next != NULL) || (current_rline->line_text[0]))\n    {\n      if(current_rline->validity_status == invalid_comment)\n      {\n        object_code_position[0] = current_rline->line_text_length + 3;\n        object_code_position[1] = 107;\n        object_code_position[2] = current_rline->line_text_length + 1;\n\n        strcpy(object_code_position + 3, current_rline->line_text);\n        object_code_position[current_rline->line_text_length + 4] =\n         current_rline->line_text_length + 3;\n        object_code_position += current_rline->line_text_length + 5;\n      }\n      else\n\n      if(current_rline->validity_status == valid)\n      {\n        memcpy(object_code_position, current_rline->line_bytecode,\n         current_rline->line_bytecode_length);\n        object_code_position += current_rline->line_bytecode_length;\n      }\n    }\n    else\n    {\n      // Get rid of trailing three bytes if present\n      rstate->size -= current_rline->line_bytecode_length;\n      reallocate_robot(cur_robot, rstate->size);\n      object_code_position = cur_robot->program_bytecode + rstate->size - 1;\n    }\n#endif /* !CONFIG_DEBYTECODE */\n\n    next_rline = current_rline->next;\n    delete_line_contents(current_rline);\n    current_rline = next_rline;\n  }\n\n#ifndef CONFIG_DEBYTECODE\n  object_code_position[0] = 0;\n\n  if(rstate->size > 2)\n  {\n    cur_robot->used = 1;\n    cur_robot->cur_prog_line = 1;\n    clear_label_cache(cur_robot);\n    cache_robot_labels(cur_robot);\n  }\n#endif\n}\n\nstatic void macro_default_values(struct robot_editor_context *rstate,\n const struct ext_macro *macro_src)\n{\n  const struct macro_type *current_type;\n  int i, i2;\n\n  for(i = 0, current_type = macro_src->types;\n   i < macro_src->num_types; i++, current_type++)\n  {\n    for(i2 = 0; i2 < current_type->num_variables; i2++)\n    {\n      if(current_type->type == string)\n      {\n        if(current_type->variables[i2].def.str_storage)\n        {\n          strcpy(current_type->variables[i2].storage.str_storage,\n           current_type->variables[i2].def.str_storage);\n        }\n        else\n        {\n          current_type->variables[i2].storage.str_storage[0] = 0;\n        }\n      }\n      else\n      {\n        memcpy(&(current_type->variables[i2].storage),\n         &(current_type->variables[i2].def), sizeof(union variable_storage));\n      }\n    }\n  }\n}\n\n/**\n * Finish the current intake-based undo frame (if any).\n */\nstatic void end_intake_undo_frame(struct robot_editor_context *rstate)\n{\n  if(rstate->current_frame_type != INTK_NO_EVENT)\n  {\n    update_undo_frame(rstate->h);\n    rstate->current_frame_type = INTK_NO_EVENT;\n    rstate->idle_timer = 0;\n  }\n}\n\n/**\n * Add a sequence of lines to a line-based undo frame.\n * The type should be either TX_NEW_LINE or TX_SAME_LINE.\n */\nstatic void add_undo_frame_lines(struct robot_editor_context *rstate,\n enum text_undo_line_type type, int start_line, int end_line)\n{\n  struct robot_line *rline = rstate->current_rline;\n  int current_line = rstate->current_line;\n\n  if(start_line > end_line)\n    return;\n\n  while(current_line > start_line && rline)\n    current_line--, rline = rline->previous;\n  while(current_line < start_line && rline)\n    current_line++, rline = rline->next;\n\n  while(current_line <= end_line && rline)\n  {\n    int pos = (current_line == rstate->current_line) ? rstate->current_x : 0;\n    add_robot_editor_undo_line(rstate->h, type, current_line, pos,\n     rline->line_text, rline->line_text_length);\n\n    rline = rline->next;\n    current_line++;\n  }\n}\n\n#ifdef CONFIG_DEBYTECODE\n\nstatic char *package_program(struct robot_line *start_rline,\n struct robot_line *end_rline, int *_source_size, char *existing_source)\n{\n  // TODO: Have some option for giving a size maybe.\n  struct robot_line *current_rline = start_rline;\n  char *packaged_program = NULL;\n  char *source_pos;\n  int source_size = 0;\n\n  while(current_rline != end_rline)\n  {\n    source_size += current_rline->line_text_length + 1;\n    current_rline = current_rline->next;\n  }\n\n  if(source_size)\n    packaged_program = crealloc(existing_source, source_size);\n\n  if(packaged_program != NULL)\n  {\n    current_rline = start_rline;\n    source_pos = packaged_program;\n\n    while(current_rline != end_rline)\n    {\n      memcpy(source_pos, current_rline->line_text,\n       current_rline->line_text_length);\n\n      source_pos += current_rline->line_text_length;\n      current_rline = current_rline->next;\n\n      *source_pos = '\\n';\n      source_pos++;\n    }\n    source_pos[-1] = 0;\n\n    if(_source_size != NULL)\n      *_source_size = source_size;\n  }\n  else\n    free(existing_source);\n\n  return packaged_program;\n}\n\nstatic void update_program_status(struct robot_editor_context *rstate,\n struct robot_line *start_rline, struct robot_line *end_rline)\n{\n  struct robot_line *current_rline = start_rline;\n  char *source_block;\n  char *next;\n  char *parse_next;\n  char *line_start;\n  char *command_start;\n  struct token *parse_tokens;\n  struct token *current_token;\n  struct color_code_pair *color_codes = NULL;\n  int source_block_length;\n  int sub_block_length;\n  int num_parse_tokens;\n  int num_color_codes = 0;\n  int line_token_residual;\n  int token_offset_from_line_start;\n  int token_length;\n  int token_position;\n  int line_length;\n  int first_line;\n\n  source_block = package_program(start_rline, end_rline, &source_block_length,\n   NULL);\n\n  next = source_block;\n\n  current_rline = start_rline;\n\n  do\n  {\n    if(*next == 0)\n    {\n      if(current_rline != NULL)\n      {\n        current_rline->command_type = COMMAND_TYPE_BLANK_LINE;\n        current_rline->num_color_codes = 0;\n      }\n      break;\n    }\n\n    if(*next == '\\n')\n    {\n      if(current_rline == NULL)\n        break;\n\n      current_rline->command_type = COMMAND_TYPE_BLANK_LINE;\n      current_rline->num_color_codes = 0;\n      next = strchr(next, '\\n');\n\n      if(next == NULL)\n        break;\n\n      current_rline = current_rline->next;\n\n      next++;\n    }\n\n    parse_tokens = parse_command(next, &parse_next, &num_parse_tokens);\n\n    if(parse_tokens)\n    {\n      command_start = next;\n      next = parse_next;\n\n      sub_block_length = parse_next - command_start;\n      num_color_codes = 0;\n\n      // The number of color codes for any given line is at most the number\n      // of tokens.\n      color_codes =\n       cmalloc(sizeof(struct color_code_pair) * num_parse_tokens);\n\n      // Where the current program line begins.\n      line_start = command_start;\n      // Start at first line.\n      first_line = 1;\n\n      // How much of that token got split over the previous line (and wraps\n      // around to the current one). We start out with none.\n      line_token_residual = 0;\n\n      // Which token we're currently looking at from the list. Some will\n      // get added to multiple color code lists, if they get split over\n      // multiple lines.\n      token_position = 0;\n\n      // This loop goes for every line in the command.\n      while(sub_block_length > 0)\n      {\n        if(first_line)\n        {\n          current_rline->command_type = COMMAND_TYPE_COMMAND_START;\n          first_line = 0;\n        }\n        else\n        {\n          current_rline->command_type = COMMAND_TYPE_COMMAND_CONTINUE;\n        }\n\n        line_length = current_rline->line_text_length;\n\n        // Pull out as many tokens as will fit on the line.\n        while(1)\n        {\n          current_token = &(parse_tokens[token_position]);\n          token_offset_from_line_start = current_token->value - line_start;\n          token_length = current_token->length;\n\n          // If there's a residual then we already took this amount off.\n          if(line_token_residual)\n          {\n            token_offset_from_line_start = 0;\n            token_length -= line_token_residual;\n          }\n\n          if(token_offset_from_line_start < line_length)\n          {\n            // Okay, the token starts on the line. It might not end on this\n            // line, in which case it'll get split over onto the next line.\n            color_codes[num_color_codes].arg_type_indexed =\n             current_token->arg_type_indexed;\n            color_codes[num_color_codes].offset = token_offset_from_line_start;\n\n            if((token_offset_from_line_start + token_length) > line_length)\n            {\n              // Token got split over a line. The length here is only the length\n              // until the end of the line and the rest is saved as residual.\n              // We do not advance to the next token because it has to go again\n              // for the next line.\n\n              // This also means there's no more hope for getting any more tokens\n              // out of this line so we bail.\n\n              int new_residual = line_length - token_offset_from_line_start;\n              color_codes[num_color_codes].length = new_residual;\n              line_token_residual += new_residual;\n              num_color_codes++;\n\n              break;\n            }\n            else\n            {\n              // Token can fit on the next line, swallow it whole and go to\n              // the next token.\n              line_token_residual = 0;\n              color_codes[num_color_codes].length = token_length;\n\n              num_color_codes++;\n              token_position++;\n\n              // If we ran out of tokens then that's it.\n              if(token_position >= num_parse_tokens)\n                break;\n            }\n          }\n          else\n          {\n            break;\n          }\n        }\n\n        if(num_color_codes)\n        {\n          current_rline->color_codes = crealloc(current_rline->color_codes,\n           num_color_codes * sizeof(struct color_code_pair));\n\n          memcpy(current_rline->color_codes, color_codes,\n           num_color_codes * sizeof(struct color_code_pair));\n        }\n        else\n        {\n          free(current_rline->color_codes);\n          current_rline->color_codes = NULL;\n        }\n        current_rline->num_color_codes = num_color_codes;\n\n        num_color_codes = 0;\n\n        line_start += line_length + 1;\n        sub_block_length -= line_length + 1;\n        current_rline = current_rline->next;\n\n        // Propagate out.\n        if(token_position == num_parse_tokens)\n          break;\n      }\n\n      free(color_codes);\n      free(parse_tokens);\n    }\n    else\n    {\n      current_rline->command_type = COMMAND_TYPE_INVALID;\n      next = strchr(next, '\\n');\n\n      current_rline = current_rline->next;\n\n      if(next == NULL)\n        break;\n\n      next++;\n    }\n  } while(1);\n\n  free(source_block);\n}\n\nstatic boolean update_current_line(struct robot_editor_context *rstate,\n boolean ignore)\n{\n  char *command_buffer = rstate->command_buffer;\n  int line_text_length = strlen(command_buffer);\n  struct robot_line *current_rline = rstate->current_rline;\n\n  // Check to see if we're actually changing anything.\n  if((current_rline->line_text_length != line_text_length) ||\n   (current_rline->line_text == NULL) ||\n   strcmp(current_rline->line_text, command_buffer))\n  {\n    current_rline->line_text =\n     crealloc(current_rline->line_text, line_text_length + 1);\n\n    current_rline->line_text_length = line_text_length;\n    memcpy(current_rline->line_text, command_buffer, line_text_length + 1);\n\n    rstate->program_modified = true;\n  }\n\n  return true;\n}\n\n#else /* !CONFIG_DEBYTECODE */\n\nstatic void trim_whitespace(char *buffer, int length)\n{\n  int i;\n\n  // trim any leading spaces\n\n  for(i = 0; i < length; i++)\n    if(buffer[i] != ' ')\n      break;\n\n  if(i != 0)\n    memmove(buffer, buffer + i, length - i + 1);\n\n  // and any trailing spaces\n  for (i = length - 1; i >= 0; i--)\n    if(buffer[i] != ' ')\n      break;\n\n  // bounds should be OK as caller has already nul-terminated\n  buffer[i + 1] = '\\0';\n}\n\n#ifndef CONFIG_DEBYTECODE\n// fix cyclic dependency (could be done in several ways)\nstatic boolean execute_named_macro(struct robot_editor_context *rstate,\n char *macro_name);\n\nstatic boolean named_macro_exists(struct robot_editor_context *rstate,\n char *macro_name)\n{\n  const struct editor_config_info *editor_conf = get_editor_config();\n  const struct ext_macro *macro_src;\n  char *line_pos;\n  char *lone_name;\n  int next;\n\n  line_pos = skip_to_next(macro_name, ',', '(', 0);\n\n  // extract just the name of the macro, if valid\n  lone_name = cmalloc(line_pos - macro_name + 1);\n  memcpy(lone_name, macro_name, line_pos - macro_name);\n  lone_name[line_pos - macro_name] = 0;\n\n  // see if such a macro exists\n  macro_src = find_macro(editor_conf, lone_name, &next);\n  free(lone_name);\n\n  return macro_src != NULL;\n}\n#endif\n\nstatic boolean update_current_line(struct robot_editor_context *rstate,\n boolean allow_macro_undo_frame)\n{\n  const struct editor_config_info *editor_conf = get_editor_config();\n  char bytecode_buffer[COMMAND_BUFFER_LEN];\n  char error_buffer[COMMAND_BUFFER_LEN];\n  char new_command_buffer[COMMAND_BUFFER_LEN];\n  char arg_types[32];\n  struct robot_line *current_rline = rstate->current_rline;\n  char *command_buffer = rstate->command_buffer;\n  int arg_count;\n  int bytecode_length;\n  int last_bytecode_length = current_rline->line_bytecode_length;\n  int current_size = rstate->size;\n  enum validity_types use_type = rstate->default_invalid;\n  int line_text_length;\n\n  line_text_length = (int)strlen(command_buffer);\n  if(line_text_length > MAX_COMMAND_LEN)\n  {\n    line_text_length = MAX_COMMAND_LEN;\n    command_buffer[MAX_COMMAND_LEN] = 0;\n  }\n  trim_whitespace(command_buffer, line_text_length);\n\n  bytecode_length = legacy_assemble_line(command_buffer, bytecode_buffer,\n   error_buffer, arg_types, &arg_count);\n\n  // Trigger macro expansion; if the macro exists, return false to let the\n  // caller know this line can be discarded if necessary.\n  if(command_buffer[0] == '#' && named_macro_exists(rstate, command_buffer + 1))\n  {\n    int start_line = rstate->current_line;\n    boolean macro_success;\n\n    if(allow_macro_undo_frame)\n    {\n      int command_buffer_len = strlen(command_buffer);\n      end_intake_undo_frame(rstate);\n      add_robot_editor_undo_frame(rstate->h, rstate);\n      add_robot_editor_undo_line(rstate->h, TX_OLD_LINE, rstate->current_line,\n       command_buffer_len, command_buffer, command_buffer_len);\n    }\n\n    macro_success = execute_named_macro(rstate, command_buffer + 1);\n\n    if(allow_macro_undo_frame)\n    {\n      add_undo_frame_lines(rstate, TX_NEW_LINE, start_line, rstate->current_line);\n      update_undo_frame(rstate->h);\n    }\n\n    if(macro_success)\n      return false;\n  }\n\n  if((bytecode_length != -1) &&\n   (current_size + bytecode_length - last_bytecode_length) <=\n   rstate->max_size)\n  {\n    char *next;\n    int line_text_length;\n\n    disassemble_line(bytecode_buffer, &next, new_command_buffer,\n     error_buffer, &line_text_length, editor_conf->disassemble_extras,\n     arg_types, &arg_count, editor_conf->disassemble_base);\n\n    if(line_text_length <= MAX_COMMAND_LEN)\n    {\n      current_rline->line_text_length = line_text_length;\n      current_rline->line_text =\n       crealloc(current_rline->line_text, line_text_length + 1);\n\n      current_rline->line_bytecode_length = bytecode_length;\n      current_rline->line_bytecode =\n       crealloc(current_rline->line_bytecode, bytecode_length);\n      current_rline->num_args = arg_count;\n      current_rline->validity_status = valid;\n\n      memcpy(current_rline->arg_types, arg_types, 20);\n      strcpy(current_rline->line_text, new_command_buffer);\n      memcpy(current_rline->line_bytecode, bytecode_buffer, bytecode_length);\n\n      rstate->size = current_size + bytecode_length - last_bytecode_length;\n    }\n    else\n    {\n      current_rline->line_text_length = MAX_COMMAND_LEN;\n      current_rline->line_text =\n       crealloc(current_rline->line_text, MAX_COMMAND_LEN + 1);\n      memcpy(current_rline->line_text, new_command_buffer, MAX_COMMAND_LEN);\n      current_rline->line_text[MAX_COMMAND_LEN] = 0;\n\n      if((current_rline->validity_status != valid) &&\n       (current_rline->validity_status != invalid_comment))\n      {\n        use_type = current_rline->validity_status;\n      }\n\n      current_rline->line_bytecode_length = 0;\n      current_rline->validity_status = use_type;\n      rstate->size = current_size - last_bytecode_length;\n    }\n  }\n  else\n  {\n    if(current_rline->validity_status != valid)\n      use_type = current_rline->validity_status;\n\n    current_rline->line_text =\n     crealloc(current_rline->line_text, line_text_length + 1);\n\n    if(use_type == invalid_comment &&\n     (current_size + (line_text_length + 5) - last_bytecode_length) >\n     rstate->max_size)\n    {\n      use_type = invalid_uncertain;\n    }\n\n    if((use_type == invalid_comment) && (line_text_length > 236))\n    {\n      line_text_length = 236;\n      command_buffer[236] = 0;\n    }\n\n    current_rline->line_text_length = line_text_length;\n    strcpy(current_rline->line_text, command_buffer);\n\n    if(use_type != invalid_comment)\n    {\n      current_rline->line_bytecode_length = 0;\n      current_rline->validity_status = use_type;\n      rstate->size = current_size - last_bytecode_length;\n    }\n    else\n    {\n      current_rline->validity_status = invalid_comment;\n      current_rline->line_bytecode_length = line_text_length + 5;\n      rstate->size =\n       current_size + (line_text_length + 5) - last_bytecode_length;\n    }\n  }\n\n  return true;\n}\n\n#endif /* !CONFIG_DEBYTECODE */\n\n// TODO: I'd like to unify this with what add_blank_line does, but\n// unfortunately that just isn't working that well right now. Needs to\n// be very carefully modularized down.\n\nstatic void add_line(struct robot_editor_context *rstate, char *value, int relation)\n{\n#ifndef CONFIG_DEBYTECODE\n  if(rstate->size + 3 + (int)strlen(value) < rstate->max_size)\n#endif\n  {\n    struct robot_line *new_rline = cmalloc(sizeof(struct robot_line));\n    struct robot_line *current_rline = rstate->current_rline;\n    char *tmp = rstate->command_buffer;\n    rstate->command_buffer = value;\n\n    new_rline->line_text_length = 0;\n    new_rline->line_text = NULL;\n\n#ifdef CONFIG_DEBYTECODE\n    new_rline->color_codes = NULL;\n    new_rline->num_color_codes = 0;\n#else\n    new_rline->line_bytecode = NULL;\n    new_rline->line_bytecode_length = 0;\n    new_rline->validity_status = valid;\n#endif\n\n    rstate->current_rline = new_rline;\n    new_rline->next = current_rline;\n    new_rline->previous = current_rline->previous;\n\n    if(relation > 0)\n    {\n      new_rline->next = current_rline->next;\n      new_rline->previous = current_rline;\n\n      if(current_rline->next)\n        current_rline->next->previous = new_rline;\n\n      current_rline->next = new_rline;\n    }\n    else\n    {\n      current_rline->previous->next = new_rline;\n      current_rline->previous = new_rline;\n    }\n\n    if(update_current_line(rstate, false))\n    {\n      int current_line = rstate->current_line;\n\n      if(relation < 0)\n        rstate->current_rline = current_rline;\n\n      rstate->current_line++;\n\n      if(rstate->mark_mode)\n      {\n        if(relation > 0)\n        {\n          if(rstate->mark_start > current_line)\n            rstate->mark_start++;\n\n          if(rstate->mark_end > current_line)\n            rstate->mark_end++;\n        }\n        else\n        {\n          if(rstate->mark_start >= current_line)\n            rstate->mark_start++;\n\n          if(rstate->mark_end >= current_line)\n            rstate->mark_end++;\n        }\n      }\n\n      rstate->total_lines++;\n    }\n    else\n    {\n      // Line was consumed by update_current_line (likely because of a macro),\n      // so remove it. Space and bytecode size might have been counted for this\n      // as part of processing the macro.\n#ifndef CONFIG_DEBYTECODE\n      rstate->size -= new_rline->line_bytecode_length;\n#endif\n      rstate->current_rline = current_rline;\n      if(new_rline->previous)\n        new_rline->previous->next = new_rline->next;\n      if(new_rline->next)\n        new_rline->next->previous = new_rline->previous;\n      delete_line_contents(new_rline);\n    }\n\n    rstate->command_buffer = tmp;\n  }\n}\n\nstatic void split_current_line(struct robot_editor_context *rstate)\n{\n  char *command_buffer = rstate->command_buffer;\n  int start_line = rstate->current_line;\n  size_t remainder_len;\n  char tmp;\n\n  /* undo */\n  {\n    end_intake_undo_frame(rstate);\n    add_robot_editor_undo_frame(rstate->h, rstate);\n    add_robot_editor_undo_line(rstate->h, TX_OLD_LINE, start_line,\n     rstate->current_x, command_buffer, strlen(command_buffer));\n  }\n\n  remainder_len = strlen(command_buffer + rstate->current_x);\n  if(remainder_len > MAX_COMMAND_LEN)\n    remainder_len = MAX_COMMAND_LEN;\n\n  tmp = command_buffer[rstate->current_x];\n  command_buffer[rstate->current_x] = '\\0';\n  add_line(rstate, command_buffer, -1);\n  command_buffer[rstate->current_x] = tmp;\n\n  if(rstate->current_x)\n    memmove(command_buffer, command_buffer + rstate->current_x, remainder_len);\n  command_buffer[remainder_len] = '\\0';\n  rstate->current_x = 0;\n  update_current_line(rstate, false);\n\n  /* undo */\n  {\n    add_undo_frame_lines(rstate, TX_NEW_LINE, start_line, rstate->current_line);\n    update_undo_frame(rstate->h);\n  }\n}\n\nstatic void combine_current_line(struct robot_editor_context *rstate, int move)\n{\n  struct robot_line *rline;\n  char *command_buffer = rstate->command_buffer;\n  char line_buffer[MAX_COMMAND_LEN + 1];\n  size_t line_len;\n  size_t command_buffer_len;\n  int start_line;\n\n  update_current_line(rstate, true);\n  line_len = strlen(command_buffer);\n\n  rline = rstate->current_rline;\n  if(move > 0)\n    rline = rline->next;\n  else\n    rline = rline->previous;\n\n  if(!rline || !rline->line_text)\n    return;\n\n  command_buffer_len = strlen(rline->line_text);\n\n  // Only attempt to merge lines if there is space.\n  if(line_len + command_buffer_len <= MAX_COMMAND_LEN)\n  {\n    /* undo */\n    {\n      struct robot_line *current_rline = rstate->current_rline;\n      end_intake_undo_frame(rstate);\n      add_robot_editor_undo_frame(rstate->h, rstate);\n      add_robot_editor_undo_line(rstate->h, TX_OLD_LINE, rstate->current_line,\n       rstate->current_x, current_rline->line_text, current_rline->line_text_length);\n\n      start_line = rstate->current_line + ((move > 0) ? 0 : -1);\n      add_robot_editor_undo_line(rstate->h, TX_OLD_LINE, start_line, 0,\n       rline->line_text, rline->line_text_length);\n    }\n\n    memcpy(line_buffer, command_buffer, line_len);\n    line_buffer[line_len] = 0;\n\n    delete_current_line(rstate, move);\n    start_line = rstate->current_line;\n\n    if(move > 0)\n    {\n      // Insert in front of following line\n      rstate->current_x = line_len;\n      memmove(command_buffer + line_len, command_buffer, command_buffer_len);\n      memcpy(command_buffer, line_buffer, line_len);\n    }\n    else\n    {\n      // Append to previous line\n      rstate->current_x = command_buffer_len;\n      memcpy(command_buffer + command_buffer_len, line_buffer, line_len);\n    }\n\n    command_buffer[command_buffer_len + line_len] = 0;\n    update_current_line(rstate, false);\n\n    /* undo */\n    {\n      add_undo_frame_lines(rstate, TX_NEW_LINE, start_line, rstate->current_line);\n      update_undo_frame(rstate->h);\n    }\n  }\n}\n\nstatic void insert_string(struct robot_editor_context *rstate, const char *src,\n char linebreak_char)\n{\n  while(src)\n  {\n    src = intake_input_string(rstate->intk, src, linebreak_char);\n    if(src)\n      split_current_line(rstate);\n  }\n}\n\nstatic void output_macro(struct robot_editor_context *rstate,\n const struct ext_macro *macro_src)\n{\n  int num_lines = macro_src->num_lines;\n  char line_buffer[COMMAND_BUFFER_LEN];\n  char number_buffer[16];\n  char *line_pos, *line_pos_old;\n  struct macro_variable_reference *current_reference;\n  int i, i2;\n  size_t len;\n\n  if(rstate->macro_recurse_level == MAX_MACRO_RECURSION ||\n   rstate->macro_repeat_level == MAX_MACRO_REPEAT)\n  {\n    rstate->command_buffer[0] = 0;\n    update_current_line(rstate, false);\n    return;\n  }\n\n  rstate->macro_recurse_level++;\n  rstate->macro_repeat_level++;\n\n  // OK, output the lines\n\n  for(i = 0; i < num_lines; i++)\n  {\n    line_pos = line_buffer;\n    line_pos_old = line_pos;\n\n    len = strlen(macro_src->lines[i][0]);\n    if((line_pos - line_pos_old) + len >= COMMAND_BUFFER_LEN)\n      goto err_cancel_expansion;\n\n    strcpy(line_pos, macro_src->lines[i][0]);\n    line_pos += len;\n\n    for(i2 = 0; i2 < macro_src->line_element_count[i]; i2++)\n    {\n      current_reference = &(macro_src->variable_references[i][i2]);\n      if(current_reference->type)\n      {\n        switch(current_reference->type->type)\n        {\n          case number:\n          {\n            if(current_reference->ref_mode == hexidecimal)\n            {\n              sprintf(number_buffer, \"%02x\",\n               current_reference->storage->int_storage);\n            }\n            else\n            {\n              sprintf(number_buffer, \"%d\",\n               current_reference->storage->int_storage);\n            }\n\n            len = strlen(number_buffer);\n            if((line_pos - line_pos_old) + len >= COMMAND_BUFFER_LEN)\n              goto err_cancel_expansion;\n\n            memcpy(line_pos, number_buffer, len);\n            line_pos += len;\n            break;\n          }\n\n          case string:\n          {\n            len = strlen(current_reference->storage->str_storage);\n            if((line_pos - line_pos_old) + len >= COMMAND_BUFFER_LEN)\n              goto err_cancel_expansion;\n\n            memcpy(line_pos, current_reference->storage->str_storage, len);\n            line_pos += len;\n            break;\n          }\n\n          case character:\n          {\n            if((line_pos - line_pos_old) + 3 >= COMMAND_BUFFER_LEN)\n              goto err_cancel_expansion;\n\n            sprintf(line_pos, \"'%c'\",\n             current_reference->storage->int_storage);\n            line_pos += 3;\n            break;\n          }\n\n          case color:\n          {\n            if((line_pos - line_pos_old) + 3 >= COMMAND_BUFFER_LEN)\n              goto err_cancel_expansion;\n\n            print_color(current_reference->storage->int_storage, line_pos);\n            line_pos += 3;\n            break;\n          }\n\n          default:\n            break;\n        }\n      }\n      else\n      {\n        const char undef[] = \"(undef)\";\n\n        len = strlen(undef);\n        if((line_pos - line_pos_old) + len >= COMMAND_BUFFER_LEN)\n          goto err_cancel_expansion;\n\n        strcpy(line_pos, undef);\n        line_pos += len;\n      }\n\n      len = strlen(macro_src->lines[i][i2 + 1]);\n      if((line_pos - line_pos_old) + len >= COMMAND_BUFFER_LEN)\n        goto err_cancel_expansion;\n\n      memcpy(line_pos, macro_src->lines[i][i2 + 1], len);\n      line_pos += len;\n    }\n\nerr_cancel_expansion:\n    *line_pos = 0;\n    add_line(rstate, line_buffer, -1);\n  }\n\n  rstate->macro_recurse_level--;\n}\n\n#ifndef CONFIG_DEBYTECODE\n\nstatic boolean execute_named_macro(struct robot_editor_context *rstate,\n char *macro_name)\n{\n  const struct editor_config_info *editor_conf = get_editor_config();\n  char *line_pos, *line_pos_old, *lone_name;\n  const struct macro_type *param_type = NULL;\n  const struct ext_macro *macro_src;\n  char last_char;\n  int next;\n\n  line_pos = skip_to_next(macro_name, ',', '(', 0);\n\n  // extract just the name of the macro, if valid\n  lone_name = cmalloc(line_pos - macro_name + 1);\n  memcpy(lone_name, macro_name, line_pos - macro_name);\n  lone_name[line_pos - macro_name] = 0;\n\n  // see if such a macro exists\n  macro_src = find_macro(editor_conf, lone_name, &next);\n  free(lone_name);\n\n  // it doesn't, carefully abort\n  if(!macro_src)\n    return false;\n\n  last_char = *line_pos;\n\n  if(macro_src->num_types && last_char)\n  {\n    union variable_storage *param_storage;\n    int param_current_type = 0;\n    int param_current_var = 0;\n    char *value = NULL;\n    int i;\n\n    // Fill in parameters\n    macro_default_values(rstate, macro_src);\n\n    // Get name, may stop at equals sign first\n    while(1)\n    {\n      line_pos = skip_whitespace(line_pos + 1);\n      line_pos_old = line_pos;\n      line_pos = skip_to_next(line_pos, '=', ',', ')');\n\n      last_char = *line_pos;\n      *line_pos = 0;\n\n      param_storage = NULL;\n\n      if(last_char == '=')\n      {\n        // It's named, find which index this corresponds to.\n        *line_pos = 0;\n        param_type = macro_src->types;\n\n        for(i = 0; i < macro_src->num_types; i++, param_type++)\n        {\n          param_storage = find_macro_variable(line_pos_old,\n           param_type);\n          if(param_storage)\n            break;\n        }\n\n        line_pos++;\n        value = line_pos;\n        line_pos = skip_to_next(line_pos, '=', ',', ')');\n        last_char = *line_pos;\n        *line_pos = 0;\n      }\n      else\n\n      if(*(line_pos - 1) != '(')\n      {\n        param_type = macro_src->types + param_current_type;\n\n        if(param_current_var == param_type->num_variables)\n        {\n          param_current_type++;\n          param_type++;\n          param_current_var = 0;\n        }\n\n        if(param_current_type < macro_src->num_types)\n        {\n          param_storage =\n           &((param_type->variables[param_current_var]).storage);\n        }\n\n        value = line_pos_old;\n        param_current_var++;\n      }\n\n      if(param_storage)\n      {\n        switch(param_type->type)\n        {\n          case number:\n          {\n            param_storage->int_storage = strtol(value, NULL, 10);\n\n            if(param_storage->int_storage <\n             param_type->type_attributes[0])\n            {\n              param_storage->int_storage =\n               param_type->type_attributes[0];\n            }\n\n            if(param_storage->int_storage >\n             param_type->type_attributes[1])\n            {\n              param_storage->int_storage =\n               param_type->type_attributes[1];\n            }\n\n            break;\n          }\n\n          case string:\n          {\n            value[param_type->type_attributes[0]] = 0;\n            strcpy(param_storage->str_storage, value);\n            break;\n          }\n\n          case character:\n          {\n            if(*value == '\\'')\n            {\n              param_storage->int_storage =\n               *(value + 1);\n            }\n            else\n            {\n              param_storage->int_storage =\n               strtol(value, NULL, 10);\n            }\n            break;\n          }\n\n          case color:\n          {\n            param_storage->int_storage =\n             get_color(value);\n            break;\n          }\n        }\n      }\n\n      if((last_char == ')') || (!last_char))\n        break;\n    }\n  }\n\n  // Wipe any existing buffered content for this line\n  rstate->command_buffer[0] = '\\0';\n  update_current_line(rstate, false);\n\n  // And replace it with the macro contents\n  output_macro(rstate, macro_src);\n  return true;\n}\n\n#endif /* !CONFIG_DEBYTECODE */\n\nstatic int block_menu(struct robot_editor_context *rstate)\n{\n  struct world *mzx_world = ((context *)rstate)->world;\n  int dialog_result, block_op = 0;\n  struct dialog di;\n  const char *radio_strings[] =\n  {\n    \"Copy block\", \"Cut block\",\n    \"Clear block\", \"Export block\"\n  };\n  struct element *elements[3] =\n  {\n    construct_radio_button(2, 2, radio_strings,\n     4, 21, &block_op),\n    construct_button(5, 7, \"OK\", 0),\n    construct_button(15, 7, \"Cancel\", -1)\n  };\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  construct_dialog(&di, \"Choose Block Command\", 26, 6, 28, 10,\n   elements, 3, 0);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  if(dialog_result == -1)\n    return -1;\n  else\n    return block_op;\n}\n\nstatic void copy_block_to_buffer(struct robot_editor_context *rstate)\n{\n  struct robot_line *current_rline = rstate->mark_start_rline;\n  int num_lines = (rstate->mark_end - rstate->mark_start) + 1;\n  int line_length;\n  int i;\n\n  copy_buffer_total_length = 0;\n\n  /* First, if there's something already there, clear all the\n   * lines and the buffer.\n   */\n  if(copy_buffer)\n  {\n    for(i = 0; i < copy_buffer_lines; i++)\n      free(copy_buffer[i]);\n    free(copy_buffer);\n  }\n\n  copy_buffer = ccalloc(num_lines, sizeof(char *));\n  copy_buffer_lines = num_lines;\n\n  for(i = 0; i < num_lines; i++)\n  {\n    line_length = current_rline->line_text_length + 1;\n    copy_buffer[i] = cmalloc(line_length);\n    memcpy(copy_buffer[i], current_rline->line_text, line_length);\n    current_rline = current_rline->next;\n\n    copy_buffer_total_length += line_length;\n  }\n\n  /* It may be possible to copy this buffer out of process,\n   * to an operating system clipboard.\n   */\n  copy_buffer_to_clipboard(copy_buffer, copy_buffer_lines,\n   copy_buffer_total_length);\n}\n\nstatic void paste_buffer(struct robot_editor_context *rstate)\n{\n  char *ext_buffer = get_clipboard_buffer();\n  int start_line = rstate->current_line;\n  int i;\n\n  // If we can use an OS buffer, do so\n  if(ext_buffer)\n  {\n    char *src_ptr;\n    int line_length;\n    int copy_length;\n    char tmp;\n\n    src_ptr = ext_buffer;\n\n    while(*src_ptr)\n    {\n      line_length = (int)strcspn(src_ptr, \"\\r\\n\");\n\n      copy_length = line_length;\n      if(copy_length >= COMMAND_BUFFER_LEN)\n        copy_length = COMMAND_BUFFER_LEN - 1;\n\n      tmp = src_ptr[copy_length];\n      src_ptr[copy_length] = '\\0';\n      add_line(rstate, src_ptr, -1);\n      src_ptr[copy_length] = tmp;\n      src_ptr += line_length;\n\n#ifdef __WIN32__\n      if(*src_ptr == '\\r')\n        src_ptr++;\n\n      if(*src_ptr == '\\n')\n        src_ptr++;\n#else\n      if(*src_ptr)\n        src_ptr++;\n#endif\n    }\n\n    /* This may need SDL_free if it was allocated by SDL. */\n    free_clipboard_buffer(ext_buffer);\n  }\n  else\n\n  if(copy_buffer)\n  {\n    for(i = 0; i < copy_buffer_lines; i++)\n      add_line(rstate, copy_buffer[i], -1);\n  }\n\n  /* undo */\n  if(start_line < rstate->current_line)\n  {\n    end_intake_undo_frame(rstate);\n    add_robot_editor_undo_frame(rstate->h, rstate);\n    add_undo_frame_lines(rstate, TX_NEW_LINE, start_line, rstate->current_line - 1);\n    update_undo_frame(rstate->h);\n  }\n}\n\nstatic void clear_block(struct robot_editor_context *rstate)\n{\n  struct robot_line *current_rline = rstate->mark_start_rline;\n  struct robot_line *line_after = rstate->mark_end_rline->next;\n  struct robot_line *line_before = current_rline->previous;\n  struct robot_line *next_rline;\n  int num_lines = rstate->mark_end - rstate->mark_start + 1;\n  int i;\n\n  end_intake_undo_frame(rstate);\n  add_robot_editor_undo_frame(rstate->h, rstate);\n\n  for(i = 0; i < num_lines; i++)\n  {\n    next_rline = current_rline->next;\n\n#ifndef CONFIG_DEBYTECODE\n    rstate->size -= current_rline->line_bytecode_length;\n#endif\n\n    add_robot_editor_undo_line(rstate->h, TX_OLD_LINE, rstate->mark_start,\n     current_rline->line_text_length, current_rline->line_text,\n     current_rline->line_text_length);\n\n    delete_line_contents(current_rline);\n\n    current_rline = next_rline;\n  }\n\n  update_undo_frame(rstate->h);\n\n  line_before->next = line_after;\n  if(line_after)\n    line_after->previous = line_before;\n\n  rstate->total_lines -= num_lines;\n\n  if(!rstate->total_lines)\n  {\n    rstate->current_line = 0;\n    rstate->current_rline = &(rstate->base);\n    add_blank_line(rstate, 1);\n  }\n  else\n  {\n    if(rstate->current_line >= rstate->mark_start)\n    {\n      if(rstate->current_line <= rstate->mark_end)\n      {\n        // Current line got swallowed.\n        if(line_after)\n        {\n          rstate->current_line = rstate->mark_start;\n          rstate->current_rline = line_after;\n        }\n        else\n        {\n          rstate->current_line = rstate->mark_start - 1;\n          rstate->current_rline = line_before;\n        }\n      }\n      else\n      {\n        rstate->current_line -= num_lines;\n      }\n    }\n  }\n\n  strcpy(rstate->command_buffer, rstate->current_rline->line_text);\n}\n\nstatic void export_block(struct robot_editor_context *rstate,\n int region_default)\n{\n  struct world *mzx_world = ((context *)rstate)->world;\n  struct robot_line *current_rline;\n  struct robot_line *end_rline;\n  int export_region = region_default;\n  int export_type = 0;\n  char export_name[MAX_PATH];\n  int num_formats = 1;\n  int num_elements;\n  const char *export_ext[] = { \".TXT\", NULL, NULL };\n  const char *radio_strings_1[] =\n  {\n    \"Text\", NULL\n  };\n  const char *radio_strings_2[] =\n  {\n    \"Entire robot\", \"Current block\"\n  };\n  struct element *elements[4];\n\n#ifndef CONFIG_DEBYTECODE\n  export_ext[1] = \".BC\";\n  radio_strings_1[1] = \"Bytecode\";\n  num_formats++;\n#endif\n\n  if(region_default)\n  {\n    elements[0] = construct_label(45, 19,\n     \"Export\\nregion as: \");\n    elements[1] = construct_radio_button(56,\n     19, radio_strings_1, num_formats, 8, &export_type);\n    elements[2] = construct_label(4, 19,\n     \"Export the\\nfollowing region: \");\n    elements[3] = construct_radio_button(22, 19,\n     radio_strings_2, 2, 13, &export_region);\n    num_elements = 4;\n  }\n  else\n  {\n    elements[0] = construct_label(25, 19,\n     \"Export\\nregion as: \");\n    elements[1] = construct_radio_button(36,\n     19, radio_strings_1, num_formats, 8, &export_type);\n    num_elements = 2;\n  }\n\n  export_name[0] = 0;\n\n  if(!file_manager(mzx_world, export_ext, NULL, export_name,\n   \"Export robot\", ALLOW_ALL_DIRS, ALLOW_NEW_FILES, elements, num_elements, 3))\n  {\n    vfile *export_file;\n\n    if(!export_region)\n    {\n      current_rline = rstate->base.next;\n      end_rline = NULL;\n    }\n    else\n    {\n      current_rline = rstate->mark_start_rline;\n      end_rline = rstate->mark_end_rline->next;\n    }\n\n#ifndef CONFIG_DEBYTECODE\n    if(export_type)\n    {\n      path_force_ext(export_name, MAX_PATH, \".bc\");\n      export_file = vfopen_unsafe(export_name, \"wb\");\n\n      vfputc(0xFF, export_file);\n\n      while(current_rline != end_rline)\n      {\n        vfwrite(current_rline->line_bytecode,\n         current_rline->line_bytecode_length, 1, export_file);\n        current_rline = current_rline->next;\n      }\n\n      vfputc(0, export_file);\n    }\n    else\n#endif\n    {\n      path_force_ext(export_name, MAX_PATH, \".txt\");\n      export_file = vfopen_unsafe(export_name, \"w\");\n\n      while(current_rline != end_rline)\n      {\n        vfputs(current_rline->line_text, export_file);\n        vfputc('\\n', export_file);\n        current_rline = current_rline->next;\n      }\n    }\n\n    vfclose(export_file);\n  }\n}\n\nstatic void import_block(struct robot_editor_context *rstate)\n{\n  const struct editor_config_info *editor_conf = get_editor_config();\n  struct world *mzx_world = ((context *)rstate)->world;\n  const char *txt_ext[] = { \".TXT\", NULL, NULL };\n  char import_name[MAX_PATH];\n  char line_buffer[256];\n  vfile *import_file;\n  int start_line = rstate->current_line;\n#ifndef CONFIG_DEBYTECODE\n  ssize_t ext_pos;\n\n  txt_ext[1] = \".BC\";\n\n  if(choose_file(mzx_world, txt_ext, import_name, \"Import Robot\", ALLOW_ALL_DIRS))\n    return;\n\n#else // CONFIG_DEBYTECODE\n  const char *label[] = { \"Legacy source code\" };\n  int is_legacy = 0;\n\n  struct element *elements[] = {\n    construct_check_box(21, 20, label, 1, strlen(label[0]), &is_legacy)\n  };\n\n  if(file_manager(mzx_world, txt_ext, NULL, import_name, \"Import Robot\",\n   ALLOW_ALL_DIRS, NO_NEW_FILES, elements, 1, 2))\n    return;\n\n#endif\n\n  import_file = vfopen_unsafe(import_name, \"rb\");\n  if(!import_file)\n    return;\n\n#ifndef CONFIG_DEBYTECODE\n  ext_pos = (ssize_t)strlen(import_name) - 3;\n\n  if(ext_pos >= 1 && !strcasecmp(import_name + ext_pos, \".BC\"))\n  {\n    long file_size = vfilelength(import_file, true);\n\n    // 0xff + length + cmd + length + 0x00\n    if(file_size >= 5)\n    {\n       char *buffer = cmalloc(file_size - 1);\n       size_t ret;\n\n       // skip 0xff\n       vfgetc(import_file);\n\n       // put the rest in a buffer for disassemble_line()\n       ret = vfread(buffer, file_size - 1, 1, import_file);\n\n       // copied to buffer, must now disassemble\n       if(ret == 1)\n       {\n         char *current_robot_pos = buffer, *next;\n         char error_buffer[256];\n         int line_text_length;\n\n         while(1)\n         {\n           int new_line = disassemble_line(current_robot_pos, &next,\n            line_buffer, error_buffer, &line_text_length,\n            editor_conf->disassemble_extras, NULL, NULL,\n            editor_conf->disassemble_base);\n\n           if(new_line)\n             add_line(rstate, line_buffer, -1);\n           else\n             break;\n\n           current_robot_pos = next;\n         }\n       }\n\n       free(buffer);\n    }\n  }\n\n#else //CONFIG_DEBYTECODE\n  if(is_legacy)\n  {\n    char command_buffer[512];\n    char bytecode_buffer[256];\n    char errors[256];\n\n    int disasm_length;\n\n    while(vfsafegets(line_buffer, 256, import_file) != NULL)\n    {\n      legacy_assemble_line(line_buffer, bytecode_buffer, errors,\n       NULL, NULL);\n\n      legacy_disassemble_command(bytecode_buffer, command_buffer,\n       &disasm_length, 256, editor_conf->disassemble_extras,\n       editor_conf->disassemble_base\n      );\n\n      command_buffer[disasm_length] = 0;\n\n      add_line(rstate, command_buffer, -1);\n    }\n  }\n\n#endif //CONFIG_DEBYTECODE\n  else\n  {\n    // fsafegets ensures that no line terminators are present\n    while(vfsafegets(line_buffer, 255, import_file) != NULL)\n      add_line(rstate, line_buffer, -1);\n  }\n\n  vfclose(import_file);\n\n  /* undo */\n  if(start_line < rstate->current_line)\n  {\n    end_intake_undo_frame(rstate);\n    add_robot_editor_undo_frame(rstate->h, rstate);\n    add_undo_frame_lines(rstate, TX_NEW_LINE, start_line, rstate->current_line - 1);\n    update_undo_frame(rstate->h);\n  }\n}\n\nstatic void edit_single_line_macros(struct robot_editor_context *rstate)\n{\n  struct world *mzx_world = ((context *)rstate)->world;\n  int dialog_result;\n  struct dialog di;\n  struct element *elements[] =\n  {\n    construct_input_box(3, 2, \"F6-  \", 63, macros[0]),\n    construct_input_box(3, 3, \"F7-  \", 63, macros[1]),\n    construct_input_box(3, 4, \"F8-  \", 63, macros[2]),\n    construct_input_box(3, 5, \"F9-  \", 63, macros[3]),\n    construct_input_box(3, 6, \"F10- \", 63, macros[4]),\n    construct_button(25, 8, \"OK\", 0),\n    construct_button(45, 8, \"Cancel\", -1)\n  };\n  char new_macros[5][64];\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  memcpy(new_macros, macros, 64 * 5);\n\n  construct_dialog(&di, \"Edit Single Line Macros\", 3, 7, 74, 10,\n   elements, ARRAY_SIZE(elements), 0);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  if(dialog_result)\n    memcpy(macros, new_macros, 64 * 5);\n}\n\nstatic void block_action(struct robot_editor_context *rstate)\n{\n  int block_command = block_menu(rstate);\n\n  if(!rstate->mark_mode)\n  {\n    rstate->mark_start_rline = rstate->current_rline;\n    rstate->mark_end_rline = rstate->current_rline;\n    rstate->mark_start = rstate->current_line;\n    rstate->mark_end = rstate->current_line;\n  }\n\n  switch(block_command)\n  {\n    case 0:\n    {\n      // Copy block into buffer\n      copy_block_to_buffer(rstate);\n      break;\n    }\n\n    case 1:\n    {\n      // Copy block into buffer, then clear block\n      copy_block_to_buffer(rstate);\n      clear_block(rstate);\n      rstate->mark_mode = 0;\n      break;\n    }\n\n    case 2:\n    {\n      // Clear block\n      clear_block(rstate);\n      rstate->mark_mode = 0;\n      break;\n    }\n\n    case 3:\n    {\n      // Export block - text only\n      export_block(rstate, 1);\n      break;\n    }\n  }\n}\n\nstatic void move_line_up(struct robot_editor_context *rstate, int count)\n{\n  int i;\n\n  end_intake_undo_frame(rstate);\n  for(i = 0; (i < count); i++)\n  {\n    if(rstate->current_rline->previous == &(rstate->base))\n      break;\n\n    rstate->current_rline = rstate->current_rline->previous;\n  }\n\n  rstate->current_line -= i;\n}\n\nstatic void move_line_down(struct robot_editor_context *rstate, int count)\n{\n  int i;\n\n  end_intake_undo_frame(rstate);\n  for(i = 0; (i < count); i++)\n  {\n    if(rstate->current_rline->next == NULL)\n    {\n      // Add a new line if the last line isn't blank.\n      if(rstate->current_rline->line_text[0])\n        add_blank_line(rstate, 1);\n\n      break;\n    }\n\n    rstate->current_rline = rstate->current_rline->next;\n  }\n\n  rstate->current_line += i;\n}\n\nstatic void move_and_update(struct robot_editor_context *rstate, int count)\n{\n  update_current_line(rstate, true);\n  if(count < 0)\n  {\n    move_line_up(rstate, -count);\n  }\n  else\n  {\n    move_line_down(rstate, count);\n  }\n\n  strcpy(rstate->command_buffer, rstate->current_rline->line_text);\n}\n\nstatic void goto_line(struct robot_editor_context *rstate, int line, int column)\n{\n  if(line > rstate->total_lines)\n    line = rstate->total_lines;\n\n  if(line < 1)\n    line = 1;\n\n  move_and_update(rstate, line - rstate->current_line);\n  rstate->current_x = CLAMP(column, 0, rstate->current_rline->line_text_length);\n}\n\nstatic void goto_position(struct robot_editor_context *rstate)\n{\n  struct world *mzx_world = ((context *)rstate)->world;\n  int dialog_result;\n  int line_number = rstate->current_line;\n  int column_number = rstate->current_x + 1;\n  struct dialog di;\n\n  struct element *elements[4] =\n  {\n    construct_number_box(2, 2, \"Line:   \", 1, rstate->total_lines, NUMBER_BOX,\n     &line_number),\n    construct_number_box(2, 3, \"Column: \", 1, MAX_COMMAND_LEN + 1, NUMBER_BOX,\n     &column_number),\n    construct_button(3, 5, \"OK\", 0),\n    construct_button(14, 5, \"Cancel\", -1)\n  };\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  construct_dialog(&di, \"Goto position\", 28, 8, 25, 7,\n   elements, 4, 0);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(dialog_result != -1)\n    goto_line(rstate, line_number, column_number - 1);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n}\n\n/**\n * Replace a span of the current Robotic line with a replacement string.\n * The current Robotic line should be synchronized (e.g. by move_and_update).\n */\nstatic void replace_current_line(struct robot_editor_context *rstate,\n int r_pos, const char *str, size_t str_size, const char *replace,\n size_t replace_size)\n{\n  struct robot_line *current_rline = rstate->current_rline;\n  int start_line = rstate->current_line;\n  size_t tail_size;\n\n  /* undo (NOTE: frame must be set up externally). */\n  add_robot_editor_undo_line(rstate->h, TX_OLD_LINE, start_line, r_pos,\n   current_rline->line_text, current_rline->line_text_length);\n\n  if(replace_size > (size_t)(COMMAND_BUFFER_LEN - r_pos - 1))\n    replace_size = (size_t)(COMMAND_BUFFER_LEN - r_pos - 1);\n\n  tail_size = current_rline->line_text_length - r_pos - str_size;\n  if(tail_size > COMMAND_BUFFER_LEN - r_pos - replace_size - 1)\n    tail_size = COMMAND_BUFFER_LEN - r_pos - replace_size - 1;\n\n  memcpy(rstate->command_buffer + r_pos, replace, replace_size);\n  memcpy(rstate->command_buffer + r_pos + replace_size,\n   current_rline->line_text + r_pos + str_size, tail_size);\n  rstate->command_buffer[r_pos + replace_size + tail_size] = '\\0';\n\n  update_current_line(rstate, false);\n  strcpy(rstate->command_buffer, rstate->current_rline->line_text);\n\n  /* undo */\n  add_undo_frame_lines(rstate, TX_SAME_LINE, start_line, rstate->current_line);\n}\n\nstatic int robo_ed_find_string(struct robot_editor_context *rstate, char *str,\n struct string_search_data *data, int *position, boolean wrap,\n boolean ignore_case, boolean allow_current)\n{\n  struct robot_line *current_rline = rstate->current_rline;\n  int current_line = rstate->current_line;\n  const char *text = rstate->command_buffer;\n  const char *pos = NULL;\n  size_t text_len;\n  size_t str_len = strlen(str);\n  int first_pos;\n\n  update_current_line(rstate, true);\n  strcpy(rstate->command_buffer, current_rline->line_text);\n\n  if(!str_len)\n    return -1;\n\n  // Check the first line first\n  text_len = strlen(text);\n  first_pos = (allow_current) ? rstate->current_x : rstate->current_x + 1;\n  if(first_pos < (int)text_len)\n  {\n    text += first_pos;\n    text_len -= first_pos;\n    pos = string_search(text, text_len, str, str_len, data, ignore_case);\n\n    text = rstate->command_buffer;\n  }\n\n  if(pos == NULL)\n  {\n    current_rline = current_rline->next;\n    current_line++;\n\n    // Now check the next lines\n    while(current_rline != NULL)\n    {\n      text = current_rline->line_text;\n      pos = string_search(text, strlen(text), str, str_len, data, ignore_case);\n\n      if(pos)\n        break;\n\n      current_rline = current_rline->next;\n      current_line++;\n    }\n  }\n\n  // Wrap around?\n  if(wrap && (pos == NULL))\n  {\n    current_rline = rstate->base.next;\n    current_line = 1;\n    while(current_rline != rstate->current_rline->next)\n    {\n      text = current_rline->line_text;\n      pos = string_search(text, strlen(text), str, str_len, data, ignore_case);\n\n      if(pos)\n        break;\n\n      current_rline = current_rline->next;\n      current_line++;\n    }\n  }\n\n  if(pos)\n  {\n    *position = (int)(pos - text);\n    return current_line;\n  }\n\n  return -1;\n}\n\nstatic void robo_ed_search_action(struct robot_editor_context *rstate,\n enum search_option action)\n{\n  switch(action)\n  {\n    case SEARCH_OPTION_NONE:\n    {\n      return;\n    }\n\n    case SEARCH_OPTION_SEARCH:\n    {\n      // Find\n      int l_pos;\n      int l_num = robo_ed_find_string(rstate, search_string, &search_index,\n       &l_pos, search_wrap_enabled, search_ignore_case_enabled, false);\n\n      if(l_num != -1)\n        goto_line(rstate, l_num, l_pos);\n\n      break;\n    }\n\n    case SEARCH_OPTION_REPLACE:\n    {\n      // Find & Replace\n      int l_pos;\n      int l_num = robo_ed_find_string(rstate, search_string, &search_index,\n       &l_pos, search_wrap_enabled, search_ignore_case_enabled, false);\n\n      if(l_num != -1)\n      {\n        goto_line(rstate, l_num, l_pos);\n\n        end_intake_undo_frame(rstate);\n        add_robot_editor_undo_frame(rstate->h, rstate);\n\n        replace_current_line(rstate, l_pos, search_string, strlen(search_string),\n         replace_string, strlen(replace_string));\n\n        update_undo_frame(rstate->h);\n      }\n\n      break;\n    }\n\n    case SEARCH_OPTION_REPLACE_ALL:\n    {\n      char old_buffer[COMMAND_BUFFER_LEN];\n      int l_pos;\n      int l_num;\n      int r_len = (int)strlen(replace_string);\n      int s_len = (int)strlen(search_string);\n      int start_line = rstate->current_line;\n      int start_pos = rstate->current_x;\n      int last_line = start_line;\n      int last_pos = start_pos;\n      int dif = r_len - s_len;\n      int wrapped = 0;\n      boolean undo_started = false;\n\n      do\n      {\n        l_num = robo_ed_find_string(rstate, search_string, &search_index,\n         &l_pos, search_wrap_enabled, search_ignore_case_enabled, true);\n\n        // Is it on the starting line and below the starting cursor?\n        // If so modify the starting cursor because the line was\n        // changed.\n        if((l_num == start_line) && (l_num <= start_pos))\n        {\n          start_pos += dif;\n        }\n\n        // A decrease had better indicate a wrap, and you only get one\n        if(((l_num == last_line) && (l_pos < last_pos)) || (l_num < last_line))\n        {\n          if((((l_num == start_line) && (l_pos <= start_pos)) ||\n           (l_num < start_line)) && (!wrapped))\n          {\n            wrapped = 1;\n          }\n          else\n          {\n            break;\n          }\n        }\n\n        // If it has already wrapped don't let it past the start\n        if((((l_num == start_line) && (l_pos > start_pos)) ||\n         (l_num > start_line)) && wrapped)\n        {\n          break;\n        }\n\n        if(l_num != -1 && l_pos <= MAX_COMMAND_LEN)\n        {\n          goto_line(rstate, l_num, l_pos);\n\n          if(!undo_started)\n          {\n            end_intake_undo_frame(rstate);\n            add_robot_editor_undo_frame(rstate->h, rstate);\n            undo_started = true;\n          }\n\n          if(r_len == 0)\n            memcpy(old_buffer, rstate->command_buffer, COMMAND_BUFFER_LEN);\n\n          replace_current_line(rstate, l_pos, search_string, s_len,\n           replace_string, r_len);\n\n          if(r_len == 0 && !strcmp(old_buffer, rstate->command_buffer))\n          {\n            /* Anti-hang hack: if the line didn't change due to compilation\n             * adding quotes or extras and the replacement length is 0, the\n             * loop will get stuck. \"Help\" it out of this situation. */\n            l_pos++;\n          }\n\n          l_pos += r_len;\n          rstate->current_x = l_pos;\n\n          last_line = l_num;\n          last_pos = l_pos;\n        }\n        else\n        {\n          break;\n        }\n      } while(1);\n\n      if(undo_started)\n        update_undo_frame(rstate->h);\n\n      break;\n    }\n  }\n\n  last_search_action = action;\n}\n\nstatic void robo_ed_search_dialog(struct robot_editor_context *rstate)\n{\n  struct world *mzx_world = ((context *)rstate)->world;\n  struct dialog di;\n  const char *wrap_opt[] = { \"Wrap\" };\n  const char *case_opt[] = { \"Case sensitive\" };\n  int wrap = search_wrap_enabled;\n  int casesens = !search_ignore_case_enabled;\n  struct element *elements[] =\n  {\n    construct_check_box(14, 3, wrap_opt, 1, strlen(wrap_opt[0]), &wrap),\n    construct_check_box(30, 3, case_opt, 1, strlen(case_opt[0]), &casesens),\n    construct_button(60, 3, \"Cancel\", -1),\n    construct_input_box(2, 1, \"Search: \", 47, search_string),\n    construct_button(60, 1, \"Search\", SEARCH_OPTION_SEARCH),\n  };\n  int num_elements = ARRAY_SIZE(elements);\n  int result;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  construct_dialog(&di, \"Search (repeat: Ctrl+R; replace: Ctrl+H)\", 5, 10, 70, 5,\n   elements, num_elements, 3);\n\n  result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  search_wrap_enabled = wrap;\n  search_ignore_case_enabled = !casesens;\n  string_search_index(search_string, strlen(search_string), &search_index,\n   search_ignore_case_enabled);\n\n  if(result != -1)\n    robo_ed_search_action(rstate, result);\n}\n\nstatic void robo_ed_replace_dialog(struct robot_editor_context *rstate)\n{\n  struct world *mzx_world = ((context *)rstate)->world;\n  struct dialog di;\n  const char *wrap_opt[] = { \"Wrap\" };\n  const char *case_opt[] = { \"Case sensitive\" };\n  int wrap = search_wrap_enabled;\n  int casesens = !search_ignore_case_enabled;\n  struct element *elements[] =\n  {\n    construct_check_box(14, 4, wrap_opt, 1, strlen(wrap_opt[0]), &wrap),\n    construct_check_box(30, 4, case_opt, 1, strlen(case_opt[0]), &casesens),\n    construct_button(61, 4, \"Cancel\", -1),\n    construct_input_box(2, 1, \"Search:  \", 47, search_string),\n    construct_input_box(2, 2, \"Replace: \", 47, replace_string),\n    construct_button(61, 1, \"Replace\", SEARCH_OPTION_REPLACE),\n    construct_button(61, 2, \"Replace All\", SEARCH_OPTION_REPLACE_ALL),\n  };\n  int num_elements = ARRAY_SIZE(elements);\n  int result;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  construct_dialog(&di, \"Replace (repeat: Ctrl+R; search: Ctrl+F)\", 2, 9, 76, 6,\n   elements, num_elements, 3);\n\n  result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  search_wrap_enabled = wrap;\n  search_ignore_case_enabled = !casesens;\n  string_search_index(search_string, strlen(search_string), &search_index,\n   search_ignore_case_enabled);\n\n  if(result != -1)\n    robo_ed_search_action(rstate, result);\n}\n\nstatic void execute_macro(struct robot_editor_context *rstate,\n const struct ext_macro *macro_src)\n{\n  struct world *mzx_world = ((context *)rstate)->world;\n  const struct macro_type *current_type;\n  int i, i2, i3;\n\n  // First, the dialogue box must be generated. This will have a text\n  // label for the variable name and an input widget for the variable's\n  // value itself. An attempt will be made to make this look as nice\n  // as possible, but these will be compromised (in this order) if\n  // space is critical.\n  // 1) All variables within a type group will be be aligned at a\n  //    a fixed with (of whatever the largest width of the name +\n  //    input widgit is) so that if they take multiple lines the\n  //    boxes will line up.\n  // 2) A blank line will be placed between each type group\n  // 3) The width/height ratio will be made as close to 4:3 as possible,\n  //    and the box will be centered.\n  // The first two dialogue elements are reserved for okay/cancel\n\n  int total_dialog_elements = macro_src->total_variables + 3;\n  int num_types = macro_src->num_types;\n  struct element **elements =\n   cmalloc(sizeof(struct element *) * total_dialog_elements);\n  int *nominal_column_widths = cmalloc(sizeof(int) * num_types);\n  int *nominal_column_subwidths = cmalloc(sizeof(int) * num_types);\n  int *vars_per_line = cmalloc(sizeof(int) * num_types);\n  int largest_column_width = 0;\n  int total_width;\n  int largest_total_width;\n  int *lines_needed = cmalloc(sizeof(int) * num_types);\n  int nominal_width = 77, old_width = 77;\n  int nominal_height, old_height = 25;\n  int total_lines_needed;\n  int largest;\n  int current_len;\n  double optimal_delta, old_optimal_delta = 1000000.0;\n  int x, y, dialog_index = 2;\n  struct macro_variable *current_variable;\n  int draw_on_line;\n  int start_x, start_y;\n  struct dialog di;\n  int dialog_value;\n  int label_size = (int)strlen(macro_src->label) + 1;\n  int subwidths[3];\n  int start_line = rstate->current_line;\n\n  // No variables? Just print bare lines...\n  if(!num_types)\n  {\n    output_macro(rstate, macro_src);\n    goto exit_free;\n  }\n\n  current_type = macro_src->types;\n\n  for(i = 0; i < num_types; i++, current_type++)\n  {\n    largest = (int)strlen(current_type->variables[0].name);\n    for(i2 = 1; i2 < current_type->num_variables; i2++)\n    {\n      current_len = (int)strlen(current_type->variables[i2].name);\n      if(current_len > largest)\n        largest = current_len;\n    }\n\n    nominal_column_subwidths[i] = largest;\n\n    switch(current_type->type)\n    {\n      case number:\n      {\n        largest += 15;\n        break;\n      }\n\n      case string:\n      {\n        largest += current_type->type_attributes[0] + 3;\n        break;\n      }\n\n      case character:\n      {\n        largest += 5;\n        break;\n      }\n\n      case color:\n      {\n        largest += 5;\n        break;\n      }\n    }\n\n    nominal_column_widths[i] = largest;\n\n    if(largest > largest_column_width)\n      largest_column_width = largest;\n  }\n\n  do\n  {\n    largest_total_width = 0;\n    total_lines_needed = 0;\n\n    // Determine how many lines each type needs\n    for(i = 0; i < num_types; i++)\n    {\n      total_width = nominal_width / nominal_column_widths[i];\n      if(total_width == 0)\n      {\n        lines_needed[i] = 1;\n      }\n      else\n      {\n        lines_needed[i] = (macro_src->types[i].num_variables +\n         (total_width - 1)) / total_width;\n      }\n\n      if(total_width > macro_src->types[i].num_variables)\n        total_width = macro_src->types[i].num_variables;\n\n      total_width *= nominal_column_widths[i];\n\n      total_lines_needed += lines_needed[i] + 1;\n      if(total_width > largest_total_width)\n        largest_total_width = total_width;\n    }\n\n    nominal_width = largest_total_width;\n    // Add a line between each, a top line, two for buttons, and\n    // two for borders\n    nominal_height = total_lines_needed + 5;\n\n    optimal_delta = fabs((80.0 / 25) -\n     (double)(nominal_width + 3) / nominal_height);\n\n    if((old_optimal_delta < optimal_delta) ||\n     (nominal_width < largest_column_width) || (nominal_height > 25))\n    {\n      // Use the old one instead\n      nominal_width = old_width;\n      nominal_height = old_height;\n      break;\n    }\n\n    old_width = nominal_width;\n    old_height = nominal_height;\n    old_optimal_delta = optimal_delta;\n\n    nominal_width -= largest_column_width;\n  } while(1);\n\n  for(i = 0; i < num_types; i++)\n  {\n    vars_per_line[i] = nominal_width / nominal_column_widths[i];\n  }\n\n  if(nominal_width < label_size)\n    nominal_width = label_size;\n\n  if(nominal_width < 25)\n    nominal_width = 25;\n\n  if(nominal_width < 30)\n  {\n    subwidths[0] = nominal_width - 20;\n    subwidths[1] = 10;\n  }\n  else\n  {\n    subwidths[0] = nominal_width / 3;\n    subwidths[1] = subwidths[0];\n  }\n\n  nominal_width += 3;\n\n  do\n  {\n    // Generate the dialogue box\n    start_x = 40 - (nominal_width / 2);\n    start_y = 12 - (nominal_height / 2);\n\n    x = 2;\n    y = 2;\n\n    current_type = macro_src->types;\n\n    for(i = 0, dialog_index = 3; i < num_types; i++, current_type++)\n    {\n      current_variable = current_type->variables;\n      // Draw this many\n      draw_on_line = vars_per_line[i];\n      for(i2 = current_type->num_variables; i2 > 0; i2 -= draw_on_line,\n       y++)\n      {\n        if(y >= (nominal_height - 3))\n        {\n          total_dialog_elements = dialog_index;\n          break;\n        }\n\n        if(i2 < draw_on_line)\n          draw_on_line = i2;\n\n        x += ((nominal_width - 3) / 2) -\n         ((nominal_column_widths[i] * draw_on_line) / 2);\n\n        for(i3 = 0; i3 < draw_on_line; i3++, current_variable++,\n         dialog_index++)\n        {\n          current_len = nominal_column_subwidths[i] -\n           (int)strlen(current_variable->name);\n          x += current_len;\n\n          switch(current_type->type)\n          {\n            case number:\n            {\n              elements[dialog_index] = construct_number_box(x, y,\n                current_variable->name,\n                current_type->type_attributes[0],\n                current_type->type_attributes[1], NUMBER_BOX,\n                &(current_variable->storage.int_storage)\n              );\n              break;\n            }\n\n            case string:\n            {\n              elements[dialog_index] = construct_input_box(x, y,\n                current_variable->name,\n                current_type->type_attributes[0],\n                current_variable->storage.str_storage\n              );\n              break;\n            }\n\n            case character:\n            {\n              elements[dialog_index] = construct_char_box(x, y,\n                current_variable->name,\n                1, &(current_variable->storage.int_storage)\n              );\n              break;\n            }\n\n            case color:\n            {\n              elements[dialog_index] = construct_color_box(x, y,\n                current_variable->name,\n                1, &(current_variable->storage.int_storage)\n              );\n              break;\n            }\n          }\n\n          x += nominal_column_widths[i] - current_len;\n        }\n        // Start over on next line\n        x = 2;\n      }\n      // Start over after skipping a line\n      x = 2;\n\n      if(y >= (nominal_height - 3))\n        break;\n\n      y++;\n    }\n\n    // Place OK/Cancel/Default\n\n    elements[0] =\n     construct_button(subwidths[0] / 2, y, \"OK\", 0);\n    elements[1] =\n     construct_button(subwidths[0] + (subwidths[1] / 2) - 2, y, \"Cancel\", -1);\n    elements[2] =\n     construct_button(subwidths[0] + subwidths[1] + (subwidths[1] / 2) - 3, y,\n     \"Default\", -2);\n\n    construct_dialog_ext(&di, macro_src->label, start_x,\n     start_y, nominal_width, nominal_height, elements,\n     total_dialog_elements, 0, 1, 2, NULL);\n\n    // Prevent previous keys from carrying through.\n    force_release_all_keys();\n\n    dialog_value = run_dialog(mzx_world, &di);\n    destruct_dialog(&di);\n\n    // Prevent UI keys from carrying through.\n    force_release_all_keys();\n\n    switch(dialog_value)\n    {\n      case 0:\n      {\n        // OK, output the lines\n        output_macro(rstate, macro_src);\n        break;\n      }\n\n      case -2:\n      {\n        // Plug in default values, allow to run again\n        macro_default_values(rstate, macro_src);\n        break;\n      }\n    }\n  } while(dialog_value == -2);\n\nexit_free:\n  free(lines_needed);\n  free(vars_per_line);\n  free(nominal_column_subwidths);\n  free(nominal_column_widths);\n  free(elements);\n\n  /* undo */\n  if(start_line < rstate->current_line)\n  {\n    end_intake_undo_frame(rstate);\n    add_robot_editor_undo_frame(rstate->h, rstate);\n    add_undo_frame_lines(rstate, TX_NEW_LINE, start_line,\n     rstate->current_line - 1);\n    update_undo_frame(rstate->h);\n  }\n}\n\nstatic void execute_numbered_macro(struct robot_editor_context *rstate, int num)\n{\n  const struct editor_config_info *editor_conf = get_editor_config();\n  const struct ext_macro *macro_src;\n  char macro_name[32];\n  int next;\n\n  sprintf(macro_name, \"%d\", num);\n  macro_src = find_macro(editor_conf, macro_name, &next);\n\n  if(macro_src)\n    execute_macro(rstate, macro_src);\n  else\n    insert_string(rstate, macros[num - 1], '^');\n}\n\nstatic void toggle_current_line_comment(struct robot_editor_context *rstate)\n{\n  /* undo */\n  end_intake_undo_frame(rstate);\n  add_robot_editor_undo_frame(rstate->h, rstate);\n  add_robot_editor_undo_line(rstate->h, TX_OLD_BUFFER, rstate->current_line,\n   rstate->current_x, rstate->command_buffer, strlen(rstate->command_buffer));\n\n#ifdef CONFIG_DEBYTECODE\n\n  if((rstate->command_buffer[0] == '/') && (rstate->command_buffer[1] == '/'))\n  {\n    size_t line_length = strlen(rstate->command_buffer + 2);\n    memmove(rstate->command_buffer, rstate->command_buffer + 2, line_length + 1);\n  }\n  else\n  {\n    size_t line_length = strlen(rstate->command_buffer);\n    memmove(rstate->command_buffer + 2, rstate->command_buffer, line_length + 1);\n    rstate->command_buffer[0] = '/';\n    rstate->command_buffer[1] = '/';\n  }\n  update_current_line(rstate, false);\n\n#else /* !CONFIG_DEBYTECODE */\n\n  if(rstate->command_buffer[0] != '.')\n  {\n    char comment_buffer[COMMAND_BUFFER_LEN];\n    char current_char;\n    char *in_position = rstate->command_buffer;\n    char *out_position = comment_buffer + 3;\n\n    comment_buffer[0] = '.';\n    comment_buffer[1] = ' ';\n    comment_buffer[2] = '\"';\n\n    do\n    {\n      current_char = *in_position;\n      if((current_char == '\\\"') || (current_char == '\\\\'))\n      {\n        *out_position = '\\\\';\n        out_position++;\n      }\n\n      *out_position = current_char;\n      out_position++;\n      in_position++;\n    } while(out_position - comment_buffer < MAX_COMMAND_LEN && current_char);\n\n    *(out_position - 1) = '\"';\n    *out_position = 0;\n\n    strcpy(rstate->command_buffer, comment_buffer);\n  }\n  else\n\n  if((rstate->command_buffer[0] == '.') &&\n   (rstate->command_buffer[1] == ' ') &&\n   (rstate->command_buffer[2] == '\"') &&\n   (rstate->command_buffer[strlen(rstate->command_buffer) - 1] == '\"'))\n  {\n    char uncomment_buffer[COMMAND_BUFFER_LEN];\n    char current_char;\n    char *in_position = rstate->command_buffer + 3;\n    char *out_position = uncomment_buffer;\n\n    do\n    {\n      current_char = *in_position;\n      if((current_char == '\\\\') && (in_position[1] == '\"'))\n      {\n        current_char = '\"';\n        in_position++;\n      }\n\n      if((current_char == '\\\\') && (in_position[1] == '\\\\'))\n      {\n        current_char = '\\\\';\n        in_position++;\n      }\n\n      *out_position = current_char;\n      out_position++;\n      in_position++;\n    } while(current_char);\n\n    *(out_position - 2) = 0;\n\n    strcpy(rstate->command_buffer, uncomment_buffer);\n  }\n\n#endif /* !CONFIG_DEBYTECODE */\n\n  /* undo */\n  intake_sync(rstate->intk);\n  add_robot_editor_undo_line(rstate->h, TX_SAME_BUFFER, rstate->current_line,\n   rstate->current_x, rstate->command_buffer, strlen(rstate->command_buffer));\n  update_undo_frame(rstate->h);\n}\n\nstatic boolean robot_editor_intake_callback(void *priv, subcontext *sub,\n enum intake_event_type type, int old_pos, int new_pos, int value, const char *data)\n{\n  struct robot_editor_context *rstate = (struct robot_editor_context *)priv;\n\n  if(rstate->current_frame_type != type)\n    end_intake_undo_frame(rstate);\n\n  switch(type)\n  {\n    case INTK_NO_EVENT:\n    case INTK_MOVE:\n    case INTK_MOVE_WORDS:\n      intake_apply_event_fixed(sub, type, new_pos, value, data);\n      break;\n    case INTK_INSERT:\n    case INTK_OVERWRITE:\n    case INTK_DELETE:\n    case INTK_BACKSPACE:\n    case INTK_BACKSPACE_WORDS:\n    case INTK_CLEAR:\n    case INTK_INSERT_BLOCK:\n    case INTK_OVERWRITE_BLOCK:\n      if(rstate->current_frame_type != type)\n      {\n        rstate->current_frame_type = type;\n        add_robot_editor_undo_frame(rstate->h, rstate);\n        add_robot_editor_undo_line(rstate->h, TX_OLD_BUFFER, rstate->current_line,\n         old_pos, rstate->command_buffer, strlen(rstate->command_buffer));\n      }\n      intake_apply_event_fixed(sub, type, new_pos, value, data);\n      add_robot_editor_undo_line(rstate->h, TX_SAME_BUFFER, rstate->current_line,\n       new_pos, rstate->command_buffer, strlen(rstate->command_buffer));\n\n      rstate->idle_timer = MAX_IDLE_TIMER;\n      break;\n  }\n  return true;\n}\n\n#ifdef CONFIG_DEBYTECODE\n\n// Just a place holder until the config file is adapted to the new system.\n\n// From configure.c:\n\n// Default colors for color coding:\n// 0 current line - 11\n// 1 immediates - 10\n// 2 characters - 14\n// 3 colors - color box or 2\n// 4 directions - 3\n// 5 things - 11\n// 6 params - 2\n// 7 strings - 14\n// 8 equalities - 0\n// 9 conditions - 15\n// 10 items - 11\n// 11 extras - 7\n// 12 commands and command fragments - 15\n\nstatic const char _new_color_code_table[] =\n{\n  11,   // current line\n  10,   // ARG_TYPE_INDEXED_IMMEDIATE,\n  14,   // ARG_TYPE_INDEXED_STRING,\n  14,   // ARG_TYPE_INDEXED_CHARACTER,\n  2,    // ARG_TYPE_INDEXED_COLOR,\n  14,   // ARG_TYPE_INDEXED_PARAM,\n  12,   // ARG_TYPE_INDEXED_NAME,\n  3,    // ARG_TYPE_INDEXED_DIRECTION,\n  11,   // ARG_TYPE_INDEXED_THING,\n  0,    // ARG_TYPE_INDEXED_EQUALITY,\n  15,   // ARG_TYPE_INDEXED_CONDITION,\n  11,   // ARG_TYPE_INDEXED_ITEM,\n  7,    // ARG_TYPE_INDEXED_IGNORE,\n  15,   // ARG_TYPE_INDEXED_FRAGMENT,\n  15,   // ARG_TYPE_INDEXED_COMMAND,\n  13,   // ARG_TYPE_INDEXED_EXPRESSION\n  2,    // ARG_TYPE_INDEXED_LABEL\n  0,    // ARG_TYPE_INDEXED_COMMENT\n};\n\n// TODO: write_string seriously needs to have a length field,\n// so we don't have to keep stuffing null terminators in the thing.\n\nstatic void display_robot_line(struct robot_editor_context *rstate,\n struct robot_line *current_rline, int y)\n{\n  int x = 2;\n  int color_code = 1;\n  const char *color_code_table = _new_color_code_table;\n  int line_color = color_code_table[ARG_TYPE_INDEXED_FRAGMENT + 1];\n  char *line_text = current_rline->line_text;\n  char temp_char;\n\n  switch(current_rline->command_type)\n  {\n    case COMMAND_TYPE_BLANK_LINE:\n      // No need to print a blank line.\n      return;\n\n    case COMMAND_TYPE_INVALID:\n    case COMMAND_TYPE_UNKNOWN:\n      color_code = 0;\n      line_color = combine_colors(1, bg_color);\n      break;\n\n    case COMMAND_TYPE_COMMAND_START:\n    case COMMAND_TYPE_COMMAND_CONTINUE:\n      color_code = combine_colors(1, bg_color);\n      break;\n  }\n\n  if(color_code == 0)\n  {\n    if(current_rline->line_text[0] != 0)\n    {\n      if(strlen(current_rline->line_text) > 76)\n      {\n        temp_char = line_text[76];\n        line_text[76] = 0;\n        write_string(line_text, x, y, line_color, WR_MASK);\n        line_text[76] = temp_char;\n      }\n      else\n      {\n        write_string(line_text, x, y, line_color, WR_MASK);\n      }\n    }\n  }\n  else\n  {\n    struct color_code_pair *color_codes = current_rline->color_codes;\n    enum arg_type_indexed arg_type_indexed;\n    int offset;\n    int length;\n    int color;\n    int i;\n\n    for(i = 0; i < current_rline->num_color_codes; i++)\n    {\n      offset = color_codes[i].offset;\n      length = color_codes[i].length;\n      arg_type_indexed = color_codes[i].arg_type_indexed;\n\n      if(arg_type_indexed == ARG_TYPE_INDEXED_COLOR)\n      {\n        color = get_color(line_text + offset);\n        draw_color_box(color & 0xFF, color >> 8, x + offset, y, 78);\n      }\n      else\n      {\n        color = color_code_table[arg_type_indexed + 1];\n        color = combine_colors(color, bg_color);\n\n        if((offset + length) > 76)\n        {\n          if(offset < 76)\n          {\n            temp_char = line_text[76];\n            line_text[76] = 0;\n\n            write_string(line_text + offset, x + offset, y, color, WR_MASK);\n            line_text[76] = temp_char;\n          }\n          break;\n        }\n\n        temp_char = line_text[offset + length];\n        line_text[offset + length] = 0;\n        write_string(line_text + offset, x + offset, y, color, WR_MASK);\n        line_text[offset + length] = temp_char;\n      }\n    }\n  }\n}\n\nstatic inline int validate_lines(struct robot_editor_context *rstate,\n int show_none)\n{\n  return 0;\n}\n\n#else /* !CONFIG_DEBYTECODE */\n\nstatic void display_robot_line(struct robot_editor_context *rstate,\n struct robot_line *current_rline, int y)\n{\n  const struct editor_config_info *editor_conf = get_editor_config();\n  int i;\n  int x = 2;\n  int current_color, current_arg;\n  boolean color_coding_on = editor_conf->color_coding_on;\n  const char *color_codes = editor_conf->color_codes;\n  char temp_char;\n  char temp_buffer[COMMAND_BUFFER_LEN];\n  char *line_pos = current_rline->line_text;\n  int chars_offset = 0;\n  size_t arg_length;\n  int use_mask;\n\n  if(current_rline->line_text[0] != 0)\n  {\n    if(current_rline->validity_status != valid)\n    {\n      current_color =\n       combine_colors(color_codes[S_CMD + current_rline->validity_status + 1],\n       bg_color);\n\n      if(strlen(current_rline->line_text) > 76)\n      {\n        temp_char = current_rline->line_text[76];\n        current_rline->line_text[76] = 0;\n        write_string(current_rline->line_text, x, y, current_color, WR_MASK);\n        current_rline->line_text[76] = temp_char;\n      }\n      else\n      {\n        write_string(current_rline->line_text, x, y, current_color, WR_MASK);\n      }\n    }\n    else\n    {\n      use_mask = 0;\n      arg_length = 0;\n\n      if(!color_coding_on)\n      {\n        current_color =\n         combine_colors(color_codes[S_STRING + 1], bg_color);\n      }\n\n      if(color_coding_on)\n        current_color = combine_colors(color_codes[S_CMD + 1], bg_color);\n\n      arg_length = strcspn(line_pos, \" \") + 1;\n      memcpy(temp_buffer, line_pos, arg_length);\n      temp_buffer[arg_length] = 0;\n\n      write_string(temp_buffer, x, y, current_color, WR_NONE);\n\n      line_pos += arg_length;\n      x += (int)arg_length;\n\n      for(i = 0; i < current_rline->num_args; i++)\n      {\n        current_arg = current_rline->arg_types[i];\n\n        if(current_arg == S_STRING)\n        {\n          char current;\n          arg_length = 1;\n\n          do\n          {\n            current = line_pos[arg_length];\n            if((current == '\\\\') && ((line_pos[arg_length + 1] == '\"') ||\n             (line_pos[arg_length + 1] == '\\\\')))\n            {\n              arg_length++;\n            }\n            arg_length++;\n\n          } while((current != '\"') && current);\n\n          arg_length++;\n\n          chars_offset = 0;\n\n          use_mask = get_config()->mask_midchars;\n        }\n        else\n\n        if(current_arg == S_CHARACTER)\n        {\n          int offset_hack = (line_pos[1] == '\\'') ? 1 : 0;\n          arg_length = strcspn(line_pos + 1, \"\\'\") + 3 + offset_hack;\n          chars_offset = 0;\n        }\n        else\n        {\n          arg_length = strcspn(line_pos, \" \") + 1;\n          chars_offset = PRO_CH;\n        }\n\n        if((current_arg == S_COLOR) && (color_codes[current_arg + 1] == 255))\n        {\n          unsigned int color = get_color(line_pos);\n          draw_color_box(color & 0xFF, color >> 8, x, y, 78);\n        }\n        else\n        {\n          if(color_coding_on)\n          {\n            current_color =\n             combine_colors(color_codes[current_arg + 1], bg_color);\n          }\n\n          memcpy(temp_buffer, line_pos, arg_length);\n          temp_buffer[arg_length - 1] = 0;\n\n          if((x + arg_length) > 78)\n            temp_buffer[78 - x] = 0;\n\n          if(use_mask)\n          {\n            write_string(temp_buffer, x, y, current_color, WR_MASK);\n          }\n          else\n          {\n            if(current_arg == S_CHARACTER)\n            {\n              temp_buffer[arg_length - 2] = 0;\n              write_string(\"'\", x, y, current_color, WR_MASK);\n              write_string_ext(temp_buffer + 1, x + 1, y, current_color,\n               WR_NONE, chars_offset, 16);\n              write_string(\"'\", x + (int)arg_length - 2, y, current_color, WR_MASK);\n            }\n            else\n            {\n              write_string_ext(temp_buffer, x, y, current_color,\n               WR_NONE, chars_offset, 16);\n            }\n          }\n\n          if((x + arg_length) > 78)\n            break;\n        }\n\n        line_pos += (int)arg_length;\n        x += (int)arg_length;\n      }\n    }\n  }\n}\n\n#define VALIDATE_ELEMENTS 60\n#define MAX_ERRORS 256\n\n// This will only check for errors after current_rline, so the\n// base should be passed.\n\nstatic int validate_lines(struct robot_editor_context *rstate, int show_none)\n{\n  // 70x5-21 square that displays up to 13 error messages,\n  // and up to 48 buttons for delete/comment out/ignore. In total,\n  // the elements are text display to tell how many erroroneous\n  // lines there are, 13 text bars to display the error message\n  // and validity status, 13 delete buttons, 13 comment buttons,\n  // 13 ignore buttons, a next button, a previous button,\n  // and yes/no buttons. There can thus be up to 57 or so elements.\n  // The first one is always text, and the second are always\n  // yes/no buttons. The rest are variable depending on how\n  // many errors there are. The text button positions cannot be\n  // determined until the number of errors is.\n  // Doesn't take any more errors after the number of erroneous\n  // lines exceeds MAX_ERRORS.\n\n  struct world *mzx_world = ((context *)rstate)->world;\n  struct element *elements[VALIDATE_ELEMENTS];\n  struct dialog di;\n  char information[64] = { 0 };\n  int start_line = 0;\n  int num_errors = 0;\n  char error_messages[MAX_ERRORS][64];\n  char null_buffer[256];\n  struct robot_line *line_pointers[MAX_ERRORS];\n  struct robot_line *current_rline = rstate->base.next;\n  enum validity_types validity_options[MAX_ERRORS];\n  int redo = 1;\n  int element_pos;\n  int errors_shown;\n  int multi_pages = 0;\n  int line_number = 0;\n  int dialog_result;\n  int num_ignore = 0;\n  int current_size = rstate->size;\n  int current_element = 4;\n  int i;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  // First, collect the number of errors, and process error messages\n  // by calling assemble_line.\n\n  while(current_rline != NULL)\n  {\n    if(current_rline->validity_status != valid)\n    {\n      size_t err_len, i;\n      memset(error_messages[num_errors], ' ', 64);\n      sprintf(error_messages[num_errors], \"%05d: \", line_number + 1);\n      legacy_assemble_line(current_rline->line_text, null_buffer,\n       error_messages[num_errors] + 7, NULL, NULL);\n      /* Filter out control chars. */\n      err_len = strlen(error_messages[num_errors]);\n      for(i = 0; i < err_len; i++)\n        if(error_messages[num_errors][i] == '\\n')\n          error_messages[num_errors][i] = ' ';\n\n      error_messages[num_errors][err_len] = ' ';\n      line_pointers[num_errors] = current_rline;\n      validity_options[num_errors] = current_rline->validity_status;\n\n      if(current_rline->validity_status == invalid_uncertain)\n        num_ignore++;\n\n      num_errors++;\n      if(num_errors == MAX_ERRORS)\n        break;\n    }\n    current_rline = current_rline->next;\n    line_number++;\n  }\n\n  // The button return messages are as such:\n  // 0 - ok\n  // -1 - cancel\n  // 1 - previous\n  // 2 - next\n  // 100-113 - ignore line\n  // 200-213 - delete line\n  // 300-313 - comment line\n\n  if(num_errors > 12)\n  {\n    multi_pages = 1;\n  }\n\n  if(num_ignore || show_none)\n  {\n    do\n    {\n      errors_shown = num_errors - start_line;\n\n      if(errors_shown > 12)\n        errors_shown = 12;\n\n      elements[0] = construct_label(5, 2, information);\n      elements[1] = construct_button(28, 18, \"OK\", 0);\n      elements[2] = construct_button(38, 18, \"Cancel\", -1);\n\n      for(i = 0, element_pos = 3; i < errors_shown; i++,\n       element_pos += 4)\n      {\n        switch(validity_options[start_line + i])\n        {\n          case invalid_uncertain:\n          {\n            sprintf(error_messages[start_line + i] + 44, \" (ignore)\");\n            break;\n          }\n\n          case invalid_discard:\n          {\n            sprintf(error_messages[start_line + i] + 44, \" (delete)\");\n            break;\n          }\n\n          case invalid_comment:\n          {\n            sprintf(error_messages[start_line + i] + 44, \"(comment)\");\n            break;\n          }\n\n          default:\n          {\n            break;\n          }\n        }\n\n        elements[element_pos] = construct_label(2, i + 4,\n         error_messages[start_line + i]);\n        elements[element_pos + 1] =\n         construct_button(56, i + 4, \"I\", start_line + i + 100);\n        elements[element_pos + 2] =\n         construct_button(60, i + 4, \"D\", start_line + i + 200);\n        elements[element_pos + 3] =\n         construct_button(64, i + 4, \"C\", start_line + i + 300);\n      }\n\n      if(multi_pages)\n      {\n        if(start_line)\n        {\n          elements[element_pos] =\n           construct_button(5, 17, \"Previous\", 1);\n          element_pos++;\n        }\n\n        if((start_line + 12) < num_errors)\n        {\n          elements[element_pos] =\n           construct_button(61, 17, \"Next\", 2);\n          element_pos++;\n        }\n\n        sprintf(information, \"%d errors found; displaying %d through %d.\\n\",\n         num_errors, start_line + 1, start_line + errors_shown);\n      }\n      else\n      {\n        if(num_errors == 1)\n        {\n          sprintf(information, \"1 error found.\");\n        }\n        else\n\n        if(!num_errors)\n        {\n          sprintf(information, \"No errors found.\");\n        }\n        else\n        {\n          sprintf(information, \"%d errors found.\", num_errors);\n        }\n      }\n\n      if(current_element >= element_pos)\n        current_element = 1;\n\n      construct_dialog(&di, \"Command Summary\", 5, 2, 70, 21,\n       elements, element_pos, current_element);\n\n      dialog_result = run_dialog(mzx_world, &di);\n      current_element = di.current_element;\n      destruct_dialog(&di);\n\n      if(dialog_result == -1)\n      {\n        // Cancel - bails\n        redo = 0;\n        break;\n      }\n      else\n\n      if(dialog_result == 0)\n      {\n        // Okay, update validity options\n        redo = 0;\n        for(i = 0; i < num_errors; i++)\n        {\n          (line_pointers[i])->validity_status = validity_options[i];\n\n          if(validity_options[i] == invalid_comment)\n          {\n            (line_pointers[i])->line_bytecode_length =\n             (line_pointers[i])->line_text_length + 5;\n          }\n          else\n          {\n            (line_pointers[i])->line_bytecode_length = 0;\n            (line_pointers[i])->line_text_length = 0;\n          }\n        }\n\n        rstate->size = current_size;\n        continue;\n      }\n      else\n\n      if((dialog_result >= 100) && (dialog_result < 200))\n      {\n        // If the last is comment, adjust size\n        if(validity_options[dialog_result - 100] == invalid_comment)\n        {\n          current_size -=\n           (line_pointers[dialog_result - 100])->line_text_length + 5;\n        }\n\n        // Make it ignore\n        validity_options[dialog_result - 100] = invalid_uncertain;\n      }\n      else\n\n      if((dialog_result >= 200) && (dialog_result < 300))\n      {\n        // If the last is comment, adjust size\n        if(validity_options[dialog_result - 200] == invalid_comment)\n        {\n          current_size -=\n           (line_pointers[dialog_result - 200])->line_text_length + 5;\n        }\n\n        // Make it delete\n        validity_options[dialog_result - 200] = invalid_discard;\n      }\n      else\n\n      if((dialog_result >= 300) && (dialog_result < 400))\n      {\n        if(current_size +\n         ((line_pointers[dialog_result - 300])->line_text_length + 5) <\n         MAX_OBJ_SIZE)\n        {\n          // If the last is not comment, adjust size\n          if(validity_options[dialog_result - 300] != invalid_comment)\n          {\n            current_size +=\n             (line_pointers[dialog_result - 300])->line_text_length + 5;\n          }\n\n          // Make it comment\n          validity_options[dialog_result - 300] = invalid_comment;\n        }\n      }\n      else\n\n      if(dialog_result == 1)\n      {\n        start_line -= 12;\n        current_element = 4;\n        continue;\n      }\n      else\n\n      if(dialog_result == 2)\n      {\n        start_line += 12;\n        current_element = 4;\n        continue;\n      }\n\n      // Next issue\n      current_element += 4;\n      if(current_element >= element_pos)\n      {\n        // Next\n        if(num_errors - start_line > 12)\n          current_element = element_pos - 1;\n\n        // Okay\n        else\n          current_element = 1;\n      }\n\n    } while(redo);\n  }\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  update_current_line(rstate, false);\n  return num_ignore;\n}\n\n#endif /* !CONFIG_DEBYTECODE */\n\nstatic void init_robot_lines(struct robot_editor_context *rstate,\n struct robot *cur_robot)\n{\n#ifdef CONFIG_DEBYTECODE\n  char *source_pos;\n  char *newline_pos;\n  int line_length;\n#else\n  const struct editor_config_info *editor_conf = get_editor_config();\n  char text_buffer[COMMAND_BUFFER_LEN], error_buffer[COMMAND_BUFFER_LEN];\n  int line_text_length, line_bytecode_length, new_line, arg_count;\n  char *current_robot_pos = cur_robot->program_bytecode + 1;\n  struct robot_line *previous_rline = &(rstate->base);\n  struct robot_line *current_rline = NULL;\n  char arg_types[32], *next;\n#endif\n\n  rstate->base.previous = NULL;\n  rstate->base.next = NULL;\n\n#ifdef CONFIG_DEBYTECODE\n  // This should take care of everything for loading the program from\n  // source.\n  source_pos = cur_robot->program_source;\n\n  do\n  {\n    newline_pos = strchr(source_pos, '\\n');\n\n    if(newline_pos == NULL)\n    {\n      strcpy(rstate->command_buffer, source_pos);\n    }\n    else\n    {\n      line_length = newline_pos - source_pos;\n      if(line_length)\n        memcpy(rstate->command_buffer, source_pos, line_length);\n\n      rstate->command_buffer[line_length] = 0;\n    }\n\n    add_line(rstate, rstate->command_buffer, 1);\n    source_pos = newline_pos + 1;\n  }\n  while(newline_pos);\n\n  // Move it back to the start\n  rstate->current_rline = rstate->base.next;\n  rstate->current_line = 1;\n\n#else /* !CONFIG_DEBYTECODE */\n\n  rstate->base.line_bytecode_length = -1;\n\n  // Disassemble robots into lines\n  do\n  {\n    new_line = disassemble_line(current_robot_pos, &next,\n     text_buffer, error_buffer, &line_text_length,\n     editor_conf->disassemble_extras, arg_types, &arg_count,\n     editor_conf->disassemble_base);\n\n    if(new_line)\n    {\n      current_rline = cmalloc(sizeof(struct robot_line));\n\n      line_bytecode_length = (int)(next - current_robot_pos);\n      current_rline->line_text_length = line_text_length;\n      current_rline->line_bytecode_length = line_bytecode_length;\n      current_rline->line_text = cmalloc(line_text_length + 1);\n      current_rline->line_bytecode = cmalloc(line_bytecode_length);\n      current_rline->num_args = arg_count;\n      current_rline->validity_status = valid;\n\n      memcpy(current_rline->arg_types, arg_types, 20);\n      memcpy(current_rline->line_bytecode, current_robot_pos,\n       line_bytecode_length);\n      memcpy(current_rline->line_text, text_buffer, line_text_length + 1);\n\n      previous_rline->next = current_rline;\n      current_rline->previous = previous_rline;\n\n      rstate->total_lines++;\n\n      current_robot_pos = next;\n      previous_rline = current_rline;\n\n      rstate->size += line_bytecode_length;\n    }\n    else\n    {\n      previous_rline->next = NULL;\n    }\n  }\n  while(new_line);\n\n  // Add a blank line to the end too if there aren't any lines\n  if(!rstate->total_lines)\n  {\n    add_blank_line(rstate, 1);\n  }\n  else\n  {\n    move_line_down(rstate, 1);\n  }\n#endif /* !CONFIG_DEBYTECODE */\n}\n\nstatic boolean robot_editor_draw(context *ctx)\n{\n  struct robot_editor_context *rstate = (struct robot_editor_context *)ctx;\n\n  const struct editor_config_info *editor_conf = get_editor_config();\n  int intk_color = combine_colors(editor_conf->color_codes[0], bg_color);\n  boolean use_mask = get_config()->mask_midchars;\n\n  struct robot_line *draw_rline;\n  int first_line_draw_position;\n  int first_line_count_back;\n  int middle_line_len;\n  int start_offset = 0;\n  int temp_char = 0;\n  int temp_pos = 0;\n  int cursor_x;\n  int i;\n\n#ifdef CONFIG_DEBYTECODE\n  // Update program status if it has been modified.\n  // This needs to be done after all input and prior to drawing lines.\n  if(rstate->program_modified)\n  {\n    update_program_status(rstate, rstate->base.next, NULL);\n    rstate->program_modified = false;\n    rstate->confirm_changes = true;\n  }\n#endif\n\n  fill_line(80, 0, 0, top_char, top_color);\n\n  if(rstate->scr_hide_mode)\n  {\n    write_string(key_help_hide, 0, 24, bottom_text_color, WR_NEWLINE);\n    rstate->scr_line_start = 1;\n    rstate->scr_line_middle = 12;\n    rstate->scr_line_end = 23;\n  }\n  else\n  {\n    write_string(key_help, 0, 22, bottom_text_color, WR_NEWLINE);\n    rstate->scr_line_start = 2;\n    rstate->scr_line_middle = 11;\n    rstate->scr_line_end = 20;\n  }\n\n  draw_char(top_line_connect, line_color, 0, 1);\n  draw_char(top_line_connect, line_color, 79, 1);\n  draw_char(bottom_line_connect, line_color, 0, 21);\n  draw_char(bottom_line_connect, line_color, 79, 21);\n\n  fill_line(78, 1, 1, horizontal_line, line_color);\n  fill_line(78, 1, 21, horizontal_line, line_color);\n\n  for(i = rstate->scr_line_start; i < rstate->scr_line_middle; i++)\n  {\n    draw_char(vertical_line, line_color, 0, i);\n    draw_char(vertical_line, line_color, 79, i);\n    fill_line(78, 1, i, bg_char, bg_color_solid);\n  }\n\n  for(i++; i <= rstate->scr_line_end; i++)\n  {\n    draw_char(vertical_line, line_color, 0, i);\n    draw_char(vertical_line, line_color, 79, i);\n    fill_line(78, 1, i, bg_char, bg_color_solid);\n  }\n\n  // Make sure the line length and current char are up-to-date.\n  intake_sync(rstate->intk);\n\n  write_string(\"Line:\", 2, 0, top_highlight_color, WR_NONE);\n  write_string(\"/\", 14, 0, top_highlight_color, WR_NONE);\n  write_number(rstate->current_line, top_text_color, 8, 0, 6, false, 10);\n  write_number(rstate->total_lines, top_text_color, 15, 0, 6, false, 10);\n\n  write_string(\"Col:\", 23, 0, top_highlight_color, WR_NONE);\n  write_string(\"/\", 31, 0, top_highlight_color, WR_NONE);\n  write_number(rstate->current_x + 1, top_text_color, 28, 0, 3, false, 10);\n  write_number(rstate->current_line_len + 1, top_text_color, 32, 0, 3, false,\n   10);\n\n  write_string(\"Size:\", 37, 0, top_highlight_color, WR_NONE);\n  write_string(\"/\", 50, 0, top_highlight_color, WR_NONE);\n  write_number(rstate->size, top_text_color, 43, 0, 7, false, 10);\n  write_number(rstate->max_size, top_text_color, 51, 0, 7, false, 10);\n\n  write_string(\"X:\", 60, 0, top_highlight_color, WR_NONE);\n  write_number(rstate->cur_robot->xpos, top_text_color, 63, 0, 5, false, 10);\n\n  write_string(\"Y:\", 70, 0, top_highlight_color, WR_NONE);\n  write_number(rstate->cur_robot->ypos, top_text_color, 73, 0, 5, false, 10);\n\n  // Now, draw the lines. Start with 9 back from the current.\n\n  if(rstate->current_line >\n   (rstate->scr_line_middle - rstate->scr_line_start))\n  {\n    first_line_draw_position = rstate->scr_line_start;\n    first_line_count_back =\n     rstate->scr_line_middle - rstate->scr_line_start;\n  }\n  else\n  {\n    // Or go back as many as we can\n    first_line_draw_position = 1 +\n     (rstate->scr_line_middle - rstate->current_line);\n    first_line_count_back = rstate->current_line - 1;\n  }\n\n  draw_rline = rstate->current_rline;\n  for(i = 0; i < first_line_count_back; i++)\n  {\n    draw_rline = draw_rline->previous;\n  }\n\n  // Now draw start - scr_end + 1 lines, or until bails\n  for(i = first_line_draw_position;\n   (i <= rstate->scr_line_end) && draw_rline; i++)\n  {\n    if(i != rstate->scr_line_middle)\n      display_robot_line(rstate, draw_rline, i);\n\n    draw_rline = draw_rline->next;\n  }\n\n  // Mark block selection area\n  if(rstate->mark_mode)\n  {\n    int draw_start = rstate->current_line -\n     (rstate->scr_line_middle - rstate->scr_line_start);\n    int draw_end = rstate->current_line +\n     (rstate->scr_line_end - rstate->scr_line_middle) + 1;\n\n    if(rstate->mark_start <= draw_end)\n    {\n      if(rstate->mark_start >= draw_start)\n      {\n        draw_start = rstate->mark_start +\n         rstate->scr_line_middle - rstate->current_line;\n        draw_char('S', mark_color, 0, draw_start);\n      }\n      else\n      {\n        draw_start += rstate->scr_line_middle - rstate->current_line;\n      }\n\n      if(rstate->mark_end <= draw_end)\n      {\n        draw_end = rstate->mark_end + rstate->scr_line_middle -\n        rstate->current_line;\n      }\n      else\n      {\n        draw_end += rstate->scr_line_middle - rstate->current_line;\n      }\n\n      if(draw_start < rstate->scr_line_start)\n      {\n        draw_start = rstate->scr_line_start;\n      }\n\n      if(draw_end > rstate->scr_line_end)\n      {\n        draw_end = rstate->scr_line_end;\n      }\n      else\n      {\n        if((rstate->mark_start != rstate->mark_end) &&\n         (draw_end >= rstate->scr_line_start))\n          draw_char('E', mark_color, 0, draw_end);\n      }\n\n      if(draw_end >= rstate->scr_line_start)\n      {\n        for(i = draw_start; i <= draw_end; i++)\n        {\n          color_line(80, 0, i, mark_color);\n        }\n\n        if((draw_start <= rstate->scr_line_middle) &&\n         (draw_end >= rstate->scr_line_middle))\n          intk_color = mark_color;\n      }\n    }\n  }\n\n  // Current line arrows\n  if(rstate->current_x < 76)\n    draw_char('\\x10', intk_color, 0, rstate->scr_line_middle);\n  else\n    draw_char('\\xae', intk_color, 0, rstate->scr_line_middle);\n\n  if(rstate->current_line_len < 76 ||\n   rstate->current_x == rstate->current_line_len)\n    draw_char('\\x11', intk_color, 79, rstate->scr_line_middle);\n  else\n    draw_char('\\xaf', intk_color, 79, rstate->scr_line_middle);\n\n  // Current line.\n  middle_line_len = rstate->current_line_len;\n  cursor_x = rstate->current_x;\n  if(rstate->current_x >= 76)\n  {\n    if(rstate->command_buffer[rstate->current_x])\n    {\n      temp_pos = rstate->current_x + 1;\n      temp_char = rstate->command_buffer[temp_pos];\n      rstate->command_buffer[temp_pos] = 0;\n    }\n    start_offset = rstate->current_x - 76 + 1;\n    middle_line_len = strlen(rstate->command_buffer + start_offset);\n    cursor_x = 75;\n  }\n  else\n\n  if(middle_line_len > 76)\n  {\n    temp_pos = 76;\n    temp_char = rstate->command_buffer[temp_pos];\n    rstate->command_buffer[temp_pos] = 0;\n    middle_line_len = 76;\n  }\n\n  if(intake_get_insert())\n    cursor_underline(cursor_x + 2, rstate->scr_line_middle);\n  else\n    cursor_solid(cursor_x + 2, rstate->scr_line_middle);\n\n  if(use_mask)\n  {\n    write_string(rstate->command_buffer + start_offset,\n     2, rstate->scr_line_middle, intk_color, WR_MASK);\n  }\n  else\n  {\n    write_string_ext(rstate->command_buffer + start_offset,\n     2, rstate->scr_line_middle, intk_color, WR_NONE, 0, 16);\n  }\n\n  // Fill non-text portions of the middle line.\n  fill_line(76 + 1 - middle_line_len,\n   2 + middle_line_len, rstate->scr_line_middle, 32, intk_color);\n  draw_char(bg_char, intk_color, 1, rstate->scr_line_middle);\n\n  if(temp_pos)\n    rstate->command_buffer[temp_pos] = temp_char;\n\n  return true;\n}\n\nstatic boolean robot_editor_idle(context *ctx)\n{\n  struct robot_editor_context *rstate = (struct robot_editor_context *)ctx;\n\n  /* Time out the current editing undo frame if no text has been entered. */\n  if(rstate->idle_timer > 0)\n  {\n    rstate->idle_timer--;\n    if(!rstate->idle_timer)\n      end_intake_undo_frame(rstate);\n  }\n\n  // Disable the cursor so it doesn't display over other interfaces.\n  // The draw function will enable it again if needed.\n  cursor_off();\n\n  rstate->macro_repeat_level = 0;\n  rstate->macro_recurse_level = 0;\n  return false;\n}\n\nstatic boolean robot_editor_mouse(context *ctx, int *key, int button,\n int x, int y)\n{\n  struct robot_editor_context *rstate = (struct robot_editor_context *)ctx;\n\n  if(button && (button <= MOUSE_BUTTON_RIGHT))\n  {\n    if((y >= rstate->scr_line_start) && (y <= rstate->scr_line_end) &&\n     (x >= 2) && (x <= 78))\n    {\n      move_and_update(rstate, y - rstate->scr_line_middle);\n      rstate->current_x = x - 2;\n\n      if(y != rstate->scr_line_middle)\n        warp_mouse(x, rstate->scr_line_middle);\n      return true;\n    }\n  }\n  else\n\n  if(button == MOUSE_BUTTON_WHEELUP)\n  {\n    move_and_update(rstate, -3);\n    return true;\n  }\n  else\n\n  if(button == MOUSE_BUTTON_WHEELDOWN)\n  {\n    move_and_update(rstate, 3);\n    return true;\n  }\n\n  return false;\n}\n\nstatic boolean robot_editor_joystick(context *ctx, int *key, int action)\n{\n  struct robot_editor_context *rstate = (struct robot_editor_context *)ctx;\n\n  // Note: intake context also handles X, Y.\n  switch(action)\n  {\n    // Special case for backspace at the start of a line.\n    // Otherwise, intake should handle this.\n    case JOY_Y:\n    {\n      if(rstate->current_x == 0 && rstate->current_line > 1)\n      {\n        combine_current_line(rstate, -1);\n        return true;\n      }\n      break;\n    }\n\n    // Thing param menu\n    case JOY_LSHOULDER:\n      *key = IKEY_F4;\n      return true;\n\n    // Defaults for everything else...\n    default:\n    {\n      enum keycode ui_key = get_joystick_ui_key();\n      if(ui_key)\n      {\n        *key = ui_key;\n        return true;\n      }\n      break;\n    }\n  }\n  return false;\n}\n\nstatic boolean robot_editor_key(context *ctx, int *key)\n{\n  struct robot_editor_context *rstate = (struct robot_editor_context *)ctx;\n  const struct editor_config_info *editor_conf = get_editor_config();\n  struct world *mzx_world = ((context *)rstate)->world;\n\n  // Exit event - ignore other input\n  boolean exit_status = get_exit_status();\n  if(exit_status)\n    *key = 0;\n\n  switch(*key)\n  {\n    case IKEY_UP:\n    {\n      move_and_update(rstate, -1);\n      return true;\n    }\n\n    case IKEY_DOWN:\n    {\n      move_and_update(rstate, 1);\n      return true;\n    }\n\n    case IKEY_PAGEUP:\n    {\n      move_and_update(rstate, -9);\n      return true;\n    }\n\n    case IKEY_PAGEDOWN:\n    {\n      move_and_update(rstate, 9);\n      return true;\n    }\n\n    case IKEY_BACKSPACE:\n    {\n      // Let intake handle Alt+Backspace and Ctrl+Backspace.\n      if(get_alt_status(keycode_internal) || get_ctrl_status(keycode_internal))\n        break;\n\n      if(rstate->current_x == 0 && rstate->current_line > 1)\n      {\n        combine_current_line(rstate, -1);\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_DELETE:\n    {\n      if(rstate->command_buffer[rstate->current_x] == 0 &&\n       rstate->current_rline->next)\n      {\n        combine_current_line(rstate, 1);\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_RETURN:\n    {\n      // Block action menu (also see Alt+B).\n      if(get_alt_status(keycode_internal))\n      {\n        end_intake_undo_frame(rstate);\n        update_current_line(rstate, true);\n        block_action(rstate);\n      }\n      else\n\n      // Split line into two\n      // NOTE: versions prior to 2.93 always inserted a blank line at column 0.\n      // That behavior was merged into this to simplify the undo code.\n      if(rstate->current_x == 0 || editor_conf->editor_enter_splits)\n      {\n        split_current_line(rstate);\n      }\n\n      // Old way, just navigate down a line\n      else\n        move_and_update(rstate, 1);\n\n      return true;\n    }\n\n    case IKEY_F2:\n    {\n      int new_color;\n\n      // Ignore Alt+F2, Ctrl+F2\n      if(get_alt_status(keycode_internal) || get_ctrl_status(keycode_internal))\n        break;\n\n      new_color = color_selection(last_color_selected, 1);\n      if(new_color >= 0)\n      {\n        char color_buffer[16];\n        last_color_selected = new_color;\n        print_color(new_color, color_buffer);\n        insert_string(rstate, color_buffer, 0);\n      }\n\n      return true;\n    }\n\n    case IKEY_F3:\n    {\n      int new_char = char_selection(last_char_selected);\n\n      if(new_char >= 0)\n      {\n        char char_buffer[16];\n        int chars_length = unescape_char(char_buffer, new_char);\n        char_buffer[chars_length] = 0;\n\n        insert_string(rstate, char_buffer, 0);\n\n        last_char_selected = new_char;\n      }\n      return true;\n    }\n\n    case IKEY_F4:\n    {\n      int thing_index, end_x, start_x = rstate->current_x;\n\n      // ALT+F4 - do nothing.\n      if(get_alt_status(keycode_internal))\n        return false;\n\n      if(!rstate->command_buffer[start_x])\n        start_x--;\n\n      while(start_x && (rstate->command_buffer[start_x] == ' '))\n        start_x--;\n\n      end_x = start_x + 1;\n\n      while(start_x && (rstate->command_buffer[start_x] != ' '))\n        start_x--;\n\n      if(start_x)\n        start_x++;\n\n#ifdef CONFIG_DEBYTECODE\n      {\n        thing_index =\n         get_thing(rstate->command_buffer + start_x, end_x - start_x);\n\n        if((thing_index == -1) || (thing_index >= SENSOR))\n          return true;\n      }\n#else /* !CONFIG_DEBYTECODE */\n      {\n        const struct search_entry_short *matched_arg;\n        char temp_char;\n\n        temp_char = rstate->command_buffer[end_x];\n        rstate->command_buffer[end_x] = 0;\n        matched_arg = find_argument(rstate->command_buffer + start_x);\n        rstate->command_buffer[end_x] = temp_char;\n\n        if(!matched_arg || (matched_arg->type != S_THING) ||\n         (matched_arg->offset >= 122))\n          return true;\n\n        thing_index = matched_arg->offset;\n      }\n#endif /* !CONFIG_DEBYTECODE */\n\n      {\n        int new_param = edit_param(mzx_world, thing_index, -1);\n        if(new_param >= 0)\n        {\n          char param_buffer[16];\n          sprintf(param_buffer, \" p%02x\", new_param);\n          insert_string(rstate, param_buffer, 0);\n        }\n      }\n      return true;\n    }\n\n    case IKEY_F5:\n    {\n      int i;\n      int char_edited;\n      char char_buffer[14];\n      char char_string_buffer[64];\n      char *buffer_pos = char_string_buffer;\n\n      char_edited = char_editor(mzx_world);\n\n      ec_read_char(char_edited, char_buffer);\n\n      for(i = 0; i < 14; i++)\n      {\n        sprintf(buffer_pos, \" %d\", char_buffer[i]);\n        buffer_pos += strlen(buffer_pos);\n      }\n\n      insert_string(rstate, char_string_buffer, 0);\n      return true;\n    }\n\n    case IKEY_F6:\n    {\n      execute_numbered_macro(rstate, 1);\n      return true;\n    }\n\n    case IKEY_F7:\n    {\n      execute_numbered_macro(rstate, 2);\n      return true;\n    }\n\n    case IKEY_F8:\n    {\n      execute_numbered_macro(rstate, 3);\n      return true;\n    }\n\n    case IKEY_F9:\n    {\n      execute_numbered_macro(rstate, 4);\n      return true;\n    }\n\n    case IKEY_F10:\n    {\n      execute_numbered_macro(rstate, 5);\n      return true;\n    }\n\n    case IKEY_v:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        end_intake_undo_frame(rstate);\n        update_current_line(rstate, true);\n        validate_lines(rstate, 1);\n        return true;\n      }\n      break;\n    }\n\n#ifdef CONFIG_DEBYTECODE\n    case IKEY_c:\n    {\n      if(get_ctrl_status(keycode_internal) &&\n       !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        toggle_current_line_comment(rstate);\n        return true;\n      }\n      break;\n    }\n#else /* !CONFIG_DEBYTECODE */\n    case IKEY_c:\n    {\n      if(!get_ctrl_status(keycode_internal) ||\n       get_alt_status(keycode_internal) || get_shift_status(keycode_internal))\n        break;\n\n      if(rstate->current_rline->validity_status != valid)\n      {\n        rstate->current_rline->validity_status = invalid_comment;\n        update_current_line(rstate, true);\n      }\n      else\n        toggle_current_line_comment(rstate);\n\n      return true;\n    }\n\n    case IKEY_d:\n    {\n      if(get_ctrl_status(keycode_internal) &&\n       !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        if(rstate->current_rline->validity_status != valid)\n        {\n          rstate->current_rline->validity_status = invalid_discard;\n          update_current_line(rstate, true);\n        }\n        return true;\n      }\n      break;\n    }\n#endif /* !CONFIG_DEBYTECODE */\n\n    case IKEY_ESCAPE:\n    {\n      exit_status = 1;\n      break;\n    }\n\n    // Mark start/end\n    case IKEY_s:\n    case IKEY_e:\n    case IKEY_HOME:\n    case IKEY_END:\n    {\n      if(get_ctrl_status(keycode_internal) &&\n       !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        if(*key == IKEY_HOME)\n        {\n          // Jump to the first line of the program.\n          goto_line(rstate, 1, 0);\n          return true;\n        }\n        else\n\n        if(*key == IKEY_END)\n        {\n          // Jump to the last line of the program.\n          goto_line(rstate, rstate->total_lines, INT_MAX);\n          return true;\n        }\n      }\n      else\n\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        int mark_switch;\n\n        update_current_line(rstate, true);\n\n        if(rstate->mark_mode == 2)\n        {\n          if((*key == IKEY_HOME) || (*key == IKEY_s))\n            mark_switch = 0;\n          else\n            mark_switch = 1;\n        }\n        else\n        {\n          mark_switch = rstate->mark_mode;\n          rstate->mark_mode++;\n        }\n\n        switch(mark_switch)\n        {\n          case 0:\n          {\n            // Always mark the start\n            rstate->mark_start = rstate->current_line;\n            rstate->mark_start_rline = rstate->current_rline;\n            if(rstate->mark_mode == 1)\n            {\n              // If this is the real start, mark the end too\n              rstate->mark_end = rstate->current_line;\n              rstate->mark_end_rline = rstate->current_rline;\n            }\n            break;\n          }\n\n          case 1:\n          {\n            // Always mark the end\n            rstate->mark_end = rstate->current_line;\n            rstate->mark_end_rline = rstate->current_rline;\n            break;\n          }\n        }\n\n        // We have more than a start, so possibly swap\n        if(rstate->mark_mode && (rstate->mark_start > rstate->mark_end))\n        {\n          int mark_swap;\n          struct robot_line *mark_swap_rline;\n\n          mark_swap = rstate->mark_start;\n          rstate->mark_start = rstate->mark_end;\n          rstate->mark_end = mark_swap;\n          mark_swap_rline = rstate->mark_start_rline;\n          rstate->mark_start_rline = rstate->mark_end_rline;\n          rstate->mark_end_rline = mark_swap_rline;\n        }\n        return true;\n      }\n      break;\n    }\n\n    // Block action menu (also see Alt+Enter).\n    case IKEY_b:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        end_intake_undo_frame(rstate);\n        update_current_line(rstate, true);\n        block_action(rstate);\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_g:\n    {\n      if(get_ctrl_status(keycode_internal) &&\n       !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        goto_position(rstate);\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_p:\n    case IKEY_INSERT:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        paste_buffer(rstate);\n        return true;\n      }\n      break;\n    }\n\n    // Import file\n    case IKEY_i:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        import_block(rstate);\n        return true;\n      }\n#ifndef CONFIG_DEBYTECODE\n      else\n\n      if(get_ctrl_status(keycode_internal) &\n       !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        if(rstate->current_rline->validity_status != valid)\n        {\n          rstate->current_rline->validity_status = invalid_uncertain;\n        }\n        return true;\n      }\n#endif\n      break;\n    }\n\n    // Export file\n    case IKEY_x:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        if(rstate->mark_mode)\n        {\n          export_block(rstate, 1);\n        }\n        else\n        {\n          export_block(rstate, 0);\n        }\n        return true;\n      }\n      break;\n    }\n\n    // Single line macros\n    case IKEY_o:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        edit_single_line_macros(rstate);\n        return true;\n      }\n      break;\n    }\n\n    // Unmark\n    case IKEY_u:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        rstate->mark_mode = 0;\n        return true;\n      }\n      break;\n    }\n\n    // Hide\n    case IKEY_h:\n    {\n      if(get_ctrl_status(keycode_internal) &&\n       !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        robo_ed_replace_dialog(rstate);\n        return true;\n      }\n      else\n\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        rstate->scr_hide_mode = !rstate->scr_hide_mode;\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_f:\n    {\n      if(get_ctrl_status(keycode_internal) &&\n       !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        robo_ed_search_dialog(rstate);\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_r:\n    {\n      if(get_ctrl_status(keycode_internal) &&\n       !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        robo_ed_search_action(rstate, last_search_action);\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_m:\n    {\n      if(get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        char macro_line[256];\n        macro_line[0] = 0;\n\n        if(!input_window(mzx_world, \"Configure macro:\", macro_line, 29))\n        {\n          const struct ext_macro *macro_src;\n          int next;\n\n          macro_src = find_macro(editor_conf, macro_line, &next);\n\n          if(macro_src)\n            execute_macro(rstate, macro_src);\n        }\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_y:\n    {\n      if(get_ctrl_status(keycode_internal) &&\n       !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        // Update the current line here to avoid repeat macro expansion bugs.\n        // Unfortunately, this means macro expansion here will clobber the\n        // redo stack.\n        update_current_line(rstate, true);\n        end_intake_undo_frame(rstate);\n        apply_redo(rstate->h);\n      }\n      break;\n    }\n\n    case IKEY_z:\n    {\n      if(get_ctrl_status(keycode_internal) &&\n       !get_alt_status(keycode_internal) && !get_shift_status(keycode_internal))\n      {\n        // See above. If the current frame is a buffer-based modification,\n        // it will not trigger macro expansion and should be safe.\n        if(robot_editor_undo_frame_type(rstate->h) != TX_SAME_BUFFER)\n          update_current_line(rstate, true);\n        end_intake_undo_frame(rstate);\n        apply_undo(rstate->h);\n      }\n      break;\n    }\n  }\n\n  if(exit_status)\n  {\n    update_current_line(rstate, true);\n\n#ifdef CONFIG_DEBYTECODE\n    if(rstate->confirm_changes)\n    {\n      char confirm_prompt[80] = \"Program modified. Save changes?\";\n      int confirm_changes_res = ask_yes_no(mzx_world, confirm_prompt);\n\n      if(confirm_changes_res < 0)\n        exit_status = false;\n\n      if(!confirm_changes_res)\n      {\n        struct robot *cur_robot = rstate->cur_robot;\n        cur_robot->program_source = package_program(rstate->base.next,\n         NULL, &(cur_robot->program_source_length), cur_robot->program_source);\n      }\n    }\n#endif\n    if(validate_lines(rstate, 0))\n      exit_status = false;\n\n    if(exit_status)\n      destroy_context(ctx);\n\n    return true;\n  }\n\n  return false;\n}\n\nstatic void robot_editor_destroy(context *ctx)\n{\n  struct robot_editor_context *rstate = (struct robot_editor_context *)ctx;\n\n#ifndef CONFIG_DEBYTECODE\n  // Package time\n  reallocate_robot(rstate->cur_robot, rstate->size);\n#endif\n\n  delete_robot_lines(rstate->cur_robot, rstate);\n  destruct_undo_history(rstate->h);\n\n  restore_screen();\n  cursor_off();\n}\n\nvoid robot_editor(context *parent, struct robot *cur_robot)\n{\n  struct robot_editor_context *rstate =\n   ccalloc(1, sizeof(struct robot_editor_context));\n  struct context_spec spec;\n\n  const struct editor_config_info *editor_conf = get_editor_config();\n\n  rstate->cur_robot = cur_robot;\n  rstate->current_line = 0;\n  rstate->current_rline = &(rstate->base);\n  rstate->total_lines = 0;\n  rstate->max_size = MAX_OBJ_SIZE;\n  rstate->mark_mode = 0;\n  rstate->mark_start = -1;\n  rstate->mark_end = -1;\n  rstate->mark_start_rline = NULL;\n  rstate->mark_end_rline = NULL;\n  rstate->current_x = 0;\n  rstate->command_buffer = rstate->command_buffer_space;\n  rstate->scr_hide_mode = editor_conf->robot_editor_hide_help;\n\n#ifdef CONFIG_DEBYTECODE\n  rstate->size = 0;\n  rstate->program_modified = false;\n  rstate->confirm_changes = false;\n#else\n  rstate->size = 2;\n  rstate->default_invalid =\n   (enum validity_types)(editor_conf->default_invalid_status);\n#endif\n\n  init_robot_lines(rstate, cur_robot);\n  strcpy(rstate->command_buffer, rstate->current_rline->line_text);\n\n  rstate->h = construct_robot_editor_undo_history(editor_conf->undo_history_size);\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw           = robot_editor_draw;\n  spec.idle           = robot_editor_idle;\n  spec.click          = robot_editor_mouse;\n  spec.joystick       = robot_editor_joystick;\n  spec.key            = robot_editor_key;\n  spec.destroy        = robot_editor_destroy;\n  create_context((context *)rstate, parent, &spec, CTX_ROBO_ED);\n\n  rstate->intk =\n   intake2((context *)rstate, rstate->command_buffer, MAX_COMMAND_LEN,\n   &(rstate->current_x), &(rstate->current_line_len));\n  intake_event_callback(rstate->intk, rstate, robot_editor_intake_callback);\n\n  caption_set_robot(parent->world, cur_robot);\n  save_screen();\n}\n\nvoid init_macros(void)\n{\n  const struct editor_config_info *editor_conf = get_editor_config();\n  memcpy(macros, editor_conf->default_macros, 5 * 64);\n}\n\n/**\n * Exposed internal functions for undo implementation.\n */\n\nvoid robo_ed_goto_line(struct robot_editor_context *rstate, int line, int column)\n{\n  goto_line(rstate, line, column);\n}\n\nvoid robo_ed_delete_current_line(struct robot_editor_context *rstate, int move)\n{\n  delete_current_line(rstate, move);\n}\n\nvoid robo_ed_add_line(struct robot_editor_context *rstate, char *value, int relation)\n{\n  add_line(rstate, value, relation);\n  // If relation > 0 then the current line is set to the new line, so update\n  // the command buffer as well.\n  if(relation > 0)\n  {\n    snprintf(rstate->command_buffer, COMMAND_BUFFER_LEN, \"%s\", value);\n    rstate->command_buffer[MAX_COMMAND_LEN] = '\\0';\n  }\n}\n"
  },
  {
    "path": "src/editor/robo_ed.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_ROBO_ED_H\n#define __EDITOR_ROBO_ED_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../core.h\"\n#include \"../intake.h\"\n#include \"../rasm.h\"\n\n#define COMMAND_BUFFER_LEN 512\n\n#ifdef CONFIG_DEBYTECODE\n\nenum command_type\n {\n  COMMAND_TYPE_COMMAND_START,\n  COMMAND_TYPE_COMMAND_CONTINUE,\n  COMMAND_TYPE_BLANK_LINE,\n  COMMAND_TYPE_INVALID,\n  COMMAND_TYPE_UNKNOWN,\n};\n\nstruct color_code_pair\n{\n  enum arg_type_indexed arg_type_indexed;\n  int offset;\n  int length;\n};\n\n#else /* !CONFIG_DEBYTECODE */\n\nenum validity_types\n{\n  valid,\n  invalid_uncertain,\n  invalid_discard,\n  invalid_comment\n};\n\n#endif /* !CONFIG_DEBYTECODE */\n\nstruct robot_line\n{\n  int line_text_length;\n  char *line_text;\n\n#ifdef CONFIG_DEBYTECODE\n  struct color_code_pair *color_codes;\n  enum command_type command_type;\n  int num_color_codes;\n#else\n  enum validity_types validity_status;\n  int line_bytecode_length;\n  char *line_bytecode;\n  char arg_types[20];\n  int num_args;\n#endif\n\n  struct robot_line *next;\n  struct robot_line *previous;\n};\n\nstruct undo_history;\n\nstruct robot_editor_context\n{\n  context ctx;\n  subcontext *intk;\n  struct robot *cur_robot;\n\n  int current_line;\n  struct robot_line *current_rline;\n  int total_lines;\n  int size;\n  int max_size;\n  int current_x;\n  int current_line_len;\n  int mark_mode;\n  int mark_start;\n  int mark_end;\n  boolean scr_hide_mode;\n  int scr_line_start;\n  int scr_line_middle;\n  int scr_line_end;\n  struct robot_line base;\n  struct robot_line *mark_start_rline;\n  struct robot_line *mark_end_rline;\n  char *command_buffer;\n  char command_buffer_space[COMMAND_BUFFER_LEN];\n  int macro_recurse_level;\n  int macro_repeat_level;\n\n#ifdef CONFIG_DEBYTECODE\n  boolean program_modified;\n  boolean confirm_changes;\n#else\n  enum validity_types default_invalid;\n#endif\n\n  /* Undo history and related variables. */\n  struct undo_history *h;\n  enum intake_event_type current_frame_type;\n  int idle_timer;\n};\n\nvoid robot_editor(context *parent, struct robot *cur_robot);\n\nEDITOR_LIBSPEC void init_macros(void);\n\n/* Internal functions. */\nvoid robo_ed_goto_line(struct robot_editor_context *rstate, int line, int column);\nvoid robo_ed_delete_current_line(struct robot_editor_context *rstate, int move);\nvoid robo_ed_add_line(struct robot_editor_context *rstate, char *value, int relation);\n\n__M_END_DECLS\n\n#endif // __EDITOR_ROBO_ED_H\n"
  },
  {
    "path": "src/editor/robot.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"robot.h\"\n\n#include \"../board_struct.h\"\n#include \"../legacy_rasm.h\"\n#include \"../robot.h\"\n#include \"../util.h\"\n\n#include <string.h>\n\n// TODO: This might have to be something different.\nvoid create_blank_robot_direct(struct robot *cur_robot, int x, int y)\n{\n  memset(cur_robot, 0, sizeof(struct robot));\n  cur_robot->robot_name[0] = 0;\n\n#ifdef CONFIG_DEBYTECODE\n  {\n    const char empty_robot_program[] = \"\";\n    int program_source_length = strlen(empty_robot_program) + 1;\n\n    cur_robot->program_source = cmalloc(program_source_length);\n    cur_robot->program_source_length = program_source_length;\n\n    memcpy(cur_robot->program_source,\n     empty_robot_program, program_source_length);\n  }\n#else\n  cur_robot->program_bytecode = cmalloc(2);\n  cur_robot->program_bytecode_length = 2;\n  cur_robot->program_bytecode[0] = 0xFF;\n  cur_robot->program_bytecode[1] = 0x00;\n#endif\n\n  cur_robot->xpos = x;\n  cur_robot->ypos = y;\n  cur_robot->robot_char = 2;\n  cur_robot->bullet_type = 1;\n  cur_robot->used = 1;\n  cur_robot->cur_prog_line = 1;\n\n  cur_robot->commands_total = 0;\n  cur_robot->commands_cycle = 0;\n}\n\nvoid create_blank_scroll_direct(struct scroll *cur_scroll)\n{\n  char *message = cmalloc(3);\n\n  memset(cur_scroll, 0, sizeof(struct scroll));\n\n  cur_scroll->num_lines = 1;\n  cur_scroll->mesg_size = 3;\n  cur_scroll->mesg = message;\n  cur_scroll->used = 1;\n  message[0] = 0x01;\n  message[1] = '\\n';\n  message[2] = 0x00;\n}\n\nvoid create_blank_sensor_direct(struct sensor *cur_sensor)\n{\n  memset(cur_sensor, 0, sizeof(struct sensor));\n  cur_sensor->used = 1;\n}\n\nvoid clear_scroll_contents(struct scroll *cur_scroll)\n{\n  free(cur_scroll->mesg);\n  cur_scroll->mesg = NULL;\n  cur_scroll->used = 0;\n}\n\nvoid replace_scroll(struct board *src_board, struct scroll *src_scroll,\n int dest_id)\n{\n  struct scroll *cur_scroll = src_board->scroll_list[dest_id];\n  clear_scroll_contents(cur_scroll);\n  duplicate_scroll_direct(src_scroll, cur_scroll);\n}\n\nvoid replace_sensor(struct board *src_board, struct sensor *src_sensor,\n int dest_id)\n{\n  struct sensor *cur_sensor = src_board->sensor_list[dest_id];\n  duplicate_sensor_direct(src_sensor, cur_sensor);\n}\n\n#ifdef CONFIG_DEBYTECODE\n\n// For the editor: only copies the source and static parameters, doesn't\n// worry about program or labels, but it needs the runtime stuff,\n// for now (this should change later)\n\nvoid duplicate_robot_direct_source(struct world *mzx_world,\n struct robot *cur_robot, struct robot *copy_robot, int x, int y)\n{\n  int program_source_length = cur_robot->program_source_length;\n\n  // Copy all the base contents.\n  memcpy(copy_robot, cur_robot, sizeof(struct robot));\n\n  copy_robot->program_source = cmalloc(program_source_length);\n  memcpy(copy_robot->program_source, cur_robot->program_source,\n   program_source_length);\n\n  // Bytecode starts as NULL.\n  copy_robot->program_bytecode = NULL;\n\n  // Give the robot a new, fresh stack\n  copy_robot->stack = NULL;\n  copy_robot->stack_size = 0;\n  copy_robot->stack_pointer = 0;\n  copy_robot->xpos = x;\n  copy_robot->ypos = y;\n\n  copy_robot->pos_within_line = 0;\n  copy_robot->status = 0;\n}\n\nvoid replace_robot_source(struct world *mzx_world, struct board *src_board,\n struct robot *src_robot, int dest_id)\n{\n  char old_name[64];\n  int x = (src_board->robot_list[dest_id])->xpos;\n  int y = (src_board->robot_list[dest_id])->ypos;\n  struct robot *cur_robot = src_board->robot_list[dest_id];\n\n  strcpy(old_name, cur_robot->robot_name);\n\n  clear_robot_contents(cur_robot);\n  duplicate_robot_direct_source(mzx_world, src_robot, cur_robot, x, y);\n  strcpy(cur_robot->robot_name, old_name);\n\n  if(dest_id)\n    change_robot_name(src_board, cur_robot, src_robot->robot_name);\n}\n\nint duplicate_robot_source(struct world *mzx_world, struct board *src_board,\n struct robot *cur_robot, int x, int y)\n{\n  int dest_id = find_free_robot(src_board);\n  if(dest_id != -1)\n  {\n    struct robot *copy_robot = cmalloc(sizeof(struct robot));\n    duplicate_robot_direct_source(mzx_world, cur_robot, copy_robot, x, y);\n    add_robot_name_entry(src_board, copy_robot, copy_robot->robot_name);\n    src_board->robot_list[dest_id] = copy_robot;\n  }\n\n  return dest_id;\n}\n\n#else // !CONFIG_DEBYTECODE\n\nvoid prepare_robot_source(struct robot *cur_robot)\n{\n  // Disassemble the robot to source. Used by the robot debugger.\n  if(!cur_robot->program_source)\n  {\n    disassemble_program(\n     cur_robot->program_bytecode, cur_robot->program_bytecode_length,\n     &cur_robot->program_source, &cur_robot->program_source_length,\n     &cur_robot->command_map, &cur_robot->command_map_length\n    );\n  }\n}\n\n#endif // !CONFIG_DEBYTECODE\n"
  },
  {
    "path": "src/editor/robot.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_ROBOT_H\n#define __EDITOR_ROBOT_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../world_struct.h\"\n\nvoid create_blank_robot_direct(struct robot *cur_robot, int x, int y);\nvoid create_blank_scroll_direct(struct scroll *cur_croll);\nvoid create_blank_sensor_direct(struct sensor *cur_sensor);\nvoid clear_scroll_contents(struct scroll *cur_scroll);\nvoid replace_scroll(struct board *src_board, struct scroll *src_scroll,\n int dest_id);\nvoid replace_sensor(struct board *src_board, struct sensor *src_sensor,\n int dest_id);\n\n#ifdef CONFIG_DEBYTECODE\n\nvoid duplicate_robot_direct_source(struct world *mzx_world,\n struct robot *cur_robot, struct robot *copy_robot, int x, int y);\nint duplicate_robot_source(struct world *mzx_world,\n struct board *src_board, struct robot *cur_robot, int x, int y);\nvoid replace_robot_source(struct world *mzx_world,\n struct board *src_board, struct robot *src_robot, int dest_id);\n\n#else // !CONFIG_DEBYTECODE\n\n#include \"../robot.h\"\n\nstatic inline void duplicate_robot_direct_source(struct world *mzx_world,\n struct robot *cur_robot, struct robot *copy_robot, int x, int y)\n{\n  duplicate_robot_direct(mzx_world, cur_robot, copy_robot, x, y, 0);\n}\n\nstatic inline int duplicate_robot_source(struct world *mzx_world,\n struct board *src_board, struct robot *cur_robot, int x, int y)\n{\n  return duplicate_robot(mzx_world, src_board, cur_robot, x, y, 0);\n}\n\nstatic inline void replace_robot_source(struct world *mzx_world,\n struct board *src_board, struct robot *src_robot, int dest_id)\n{\n  replace_robot(mzx_world, src_board, src_robot, dest_id);\n}\n\n#endif // !CONFIG_DEBYTECODE\n\n#ifndef CONFIG_DEBYTECODE\nvoid prepare_robot_source(struct robot *cur_robot);\n#endif\n\n__M_END_DECLS\n\n#endif // __EDITOR_ROBOT_H\n"
  },
  {
    "path": "src/editor/select.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Selection dialogs */\n/* Prior to 2.91, this was block.c */\n\n#include \"edit.h\"\n#include \"select.h\"\n\n#include \"../core.h\"\n#include \"../data.h\"\n#include \"../error.h\"\n#include \"../event.h\"\n#include \"../graphics.h\"\n#include \"../idarray.h\"\n#include \"../idput.h\"\n#include \"../util.h\"\n#include \"../window.h\"\n#include \"../world_struct.h\"\n\n#include <stdio.h>\n\nint select_screen_mode(struct world *mzx_world)\n{\n  int dialog_result;\n  struct element *elements[4];\n  struct dialog di;\n\n  int current_mode = get_screen_mode();\n  int new_mode = current_mode;\n\n  const char *radio_button_strings[] =\n  {\n    \"Regular MZX      - 16 color palette\",\n    \"Super MZX Mode 1 - 16 color palette, interpolated\",\n    \"Super MZX Mode 2 - 256 color palette, fixed indexing\",\n    \"Super MZX Mode 3 - 256 color palette, variable indexing\",\n  };\n\n  const char *help_message =\n   \"~9  For additional information on the SMZX modes, press F1.\";\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_SUPER_MEGAZEUX);\n\n  do\n  {\n    elements[0] = construct_radio_button(2, 2, radio_button_strings,\n     4, 55, &new_mode);\n    elements[1] = construct_button(21, 9, \"OK\", 0);\n    elements[2] = construct_button(35, 9, \"Cancel\", -1);\n    elements[3] = construct_label(2, 7, help_message);\n\n    construct_dialog(&di, \"Select Screen Mode\", 8, 5, 64, 12,\n     elements, 4, 0);\n\n    dialog_result = run_dialog(mzx_world, &di);\n\n    if(dialog_result == 0 && current_mode == 3 && new_mode != 3 &&\n     confirm(mzx_world, \"This will delete all palette index data. Proceed?\"))\n    {\n      dialog_result = 1;\n    }\n\n    destruct_dialog(&di);\n  }\n  while(dialog_result > 0);\n\n  if(new_mode == current_mode || dialog_result == -1)\n    new_mode = -1;\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  pop_context();\n\n  return new_mode;\n}\n\nint choose_char_set(struct world *mzx_world)\n{\n  int dialog_result;\n  struct element *elements[4];\n  struct dialog di;\n  int charset_type = 0;\n  const char *radio_button_strings[] =\n  {\n    \"MegaZeux default\",\n    \"ASCII set\",\n    \"SMZX Classic set\",\n    \"SMZX Modern set\",\n    \"Blank set\"\n  };\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_CHOOSE_CHARSET);\n  elements[0] = construct_radio_button(4, 6, radio_button_strings,\n   ARRAY_SIZE(radio_button_strings), 16, &charset_type);\n  elements[1] = construct_button(5, 12, \"OK\", 0);\n  elements[2] = construct_button(15, 12, \"Cancel\", -1);\n  elements[3] = construct_label(4, 2,\n   \"~9Note: this will clear\\nany changes made to\\nthe character set.\");\n\n  construct_dialog(&di, \"Select Character Set\", 26, 3, 28, 15,\n   elements, ARRAY_SIZE(elements), 0);\n\n  dialog_result = run_dialog(mzx_world, &di);\n\n  destruct_dialog(&di);\n  pop_context();\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  if(dialog_result)\n    return -1;\n\n  return charset_type;\n}\n\nint export_type(struct world *mzx_world)\n{\n  int export_choice = 0;\n  int dialog_result;\n  struct element *elements[3];\n  struct dialog di;\n  const char *radio_strings[] =\n  {\n    \"Board file (MZB)\",\n    \"Character set (CHR)\",\n    \"Palette (PAL)\",\n    \"Sound effects (SFX)\",\n    \"Downver. world (MZX)\",\n    \"Board/vlayer image\",\n  };\n  int num_choices = 6;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_IMPORTEXPORT_TYPE);\n\n  elements[0] = construct_radio_button(2, 8 - num_choices, radio_strings,\n   num_choices, 20, &export_choice);\n  elements[1] = construct_button(5, 9, \"OK\", 0);\n  elements[2] = construct_button(15, 9, \"Cancel\", -1);\n\n  construct_dialog(&di, \"Export as:\", 26, 5, 28, 12,\n   elements, 3, 0);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n  pop_context();\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  if(dialog_result)\n    return -1;\n\n  return export_choice;\n}\n\nint import_type(struct world *mzx_world)\n{\n  int import_choice = 0;\n  int dialog_result;\n  struct element *elements[3];\n  struct dialog di;\n  const char *radio_strings[] =\n  {\n    \"Board file (MZB)\",\n    \"Character set (CHR)\",\n    \"World file (MZX)\",\n    \"Palette (PAL)\",\n    \"Sound effects (SFX)\",\n    \"ANSi/TXT (choose pos.)\",\n    \"MZM (choose pos.)\"\n  };\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_IMPORTEXPORT_TYPE);\n\n  elements[0] = construct_radio_button(2, 2, radio_strings,\n   ARRAY_SIZE(radio_strings), 23, &import_choice);\n  elements[1] = construct_button(6, 10, \"OK\", 0);\n  elements[2] = construct_button(16, 10, \"Cancel\", -1);\n\n  construct_dialog(&di, \"Import:\", 25, 4, 30, 13,\n   elements, 3, 0);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n  pop_context();\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  if(dialog_result)\n    return -1;\n\n  return import_choice;\n}\n"
  },
  {
    "path": "src/editor/select.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declarations */\n\n#ifndef __EDITOR_SELECT_H\n#define __EDITOR_SELECT_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../world_struct.h\"\n\nint select_screen_mode(struct world *mzx_world);\nint choose_char_set(struct world *mzx_world);\nint export_type(struct world *mzx_world);\nint import_type(struct world *mzx_world);\n\n__M_END_DECLS\n\n#endif  // __EDITOR_BLOCK_H\n"
  },
  {
    "path": "src/editor/sfx_edit.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2023-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Code to edit (via edit dialog) and test custom sound effects\n\n#include <assert.h>\n#include <string.h>\n\n#include \"../audio/sfx.h\"\n\n#include \"../core.h\"\n#include \"../data.h\"\n#include \"../error.h\"\n#include \"../event.h\"\n#include \"../window.h\"\n#include \"../world.h\"\n#include \"../util.h\"\n#include \"../io/vio.h\"\n\n#include \"sfx_edit.h\"\n#include \"window.h\"\n\n// 8 char names per sfx\n\nstatic const char sfx_names[NUM_BUILTIN_SFX][10] =\n{\n  \"Gem      \",\n  \"MagicGem \",\n  \"Health   \",\n  \"Ammo     \",\n  \"Coin     \",\n  \"Life     \",\n  \"Lo Bomb  \",\n  \"Hi Bomb  \",\n  \"Key      \",\n  \"FullKeys \",\n  \"Unlock   \",\n  \"Need Key \",\n  \"InvsWall \",\n  \"Forest   \",\n  \"GateLock \",\n  \"OpenGate \",\n  \"Invinco1 \",\n  \"Invinco2 \",\n  \"Invinco3 \",\n  \"DoorLock \",\n  \"OpenDoor \",\n  \"Ouch     \",\n  \"Lava     \",\n  \"Death    \",\n  \"GameOver \",\n  \"GateClse \",\n  \"Push     \",\n  \"Trnsport \",\n  \"Shoot    \",\n  \"Break    \",\n  \"NeedAmmo \",\n  \"Ricochet \",\n  \"NeedBomb \",\n  \"Place Lo \",\n  \"Place Hi \",\n  \"BombType \",\n  \"Explsion \",\n  \"Entrance \",\n  \"Pouch    \",\n  \"Potion   \",\n  \"EmtyChst \",\n  \"Chest    \",\n  \"Time Out \",\n  \"FireOuch \",\n  \"StolenGm \",\n  \"EnemyHit \",\n  \"DrgnFire \",\n  \"Scroll   \",\n  \"Goop     \",\n  \"Unused   \"\n};\n\nstatic const char *const sfx_ext[] = { \".SFX\", NULL };\n\n//--------------------------\n//\n// ( ) Default internal SFX\n// ( ) Custom SFX\n//\n//    _OK_      _Cancel_\n//\n//--------------------------\n\n\n//(full screen)\n//\n//|--\n//|\n//|Explsion_________//_____|\n//|MagicGem_________//_____|\n//|etc.    _________//_____|\n//|\n//|\n//|\n//| Next Prev Done\n//|\n//|--\n\n// 17 on pg 1/2, 16 on pg 3\n\nstatic const int offsets[] = { 0, 17, 34, 50, 67, 84 };\nstatic const int counts[] = { 17, 17, 16, 17, 17, 16 };\n\n// FIXME\nstatic int page = 0;\nstatic char labels[17][12];\n\nstatic void update_label(const struct custom_sfx *sfx, int pos, int i)\n{\n  labels[i][0] = '\\0';\n  if(sfx && sfx->label[0])\n    snprintf(labels[i], sizeof(labels[i]), \"%-9.9s\", sfx->label);\n\n  if(labels[i][0] == '\\0')\n  {\n    if(pos < NUM_BUILTIN_SFX)\n      snprintf(labels[i], sizeof(labels[i]), \"%-9.9s\", sfx_names[pos]);\n    else\n      snprintf(labels[i], sizeof(labels[i]), \"%-9d\", pos);\n  }\n}\n\nstatic int sfx_edit_idle(struct world *mzx_world, struct dialog *di, int key)\n{\n  struct sfx_list *sfx_list = &mzx_world->custom_sfx;\n  const struct custom_sfx *sfx;\n  char title[32];\n  char tmp[12];\n  int pos;\n\n  if(di->current_element >= counts[page])\n    return key;\n\n  switch(key)\n  {\n    case IKEY_F3:\n      pos = offsets[page] + di->current_element;\n      sfx = sfx_get(sfx_list, pos);\n      if(sfx)\n        memcpy(tmp, sfx->label, sizeof(sfx->label));\n      else\n        tmp[0] = '\\0';\n\n      snprintf(title, sizeof(title), \"Name for SFX %d:\", pos);\n      if(input_window(mzx_world, title, tmp, 9))\n        return 0;\n\n      sfx_set_label(sfx_list, pos, tmp, strlen(tmp));\n      // SFX may have been (re)allocated, fetch again.\n      update_label(sfx_get(sfx_list, pos), pos, di->current_element);\n      return 0;\n  }\n  return key;\n}\n\nvoid sfx_edit(struct world *mzx_world)\n{\n  // First, ask whether sfx should be INTERNAL or CUSTOM. If change is made\n  // from CUSTOM to INTERNAL, verify deletion of CUSTOM sfx. If changed to\n  // CUSTOM, copy over INTERNAL to CUSTOM. If changed to or left at CUSTOM,\n  // go to sfx editing.\n  struct dialog a_di, b_di;\n  struct sfx_list *custom_sfx = &mzx_world->custom_sfx;\n  int i;\n  int pos;\n  int new_sfx_mode = !!custom_sfx->list;\n  int dialog_result;\n  int num_sfx;\n  int num_elements;\n  const char *radio_strings[] =\n  {\n    \"Default internal SFX\", \"Custom SFX\"\n  };\n  struct element *a_elements[3] =\n  {\n    construct_radio_button(2, 2, radio_strings, 2, 20,\n     &new_sfx_mode),\n    construct_button(5, 5, \"OK\", 0),\n    construct_button(15, 5, \"Cancel\", -1)\n  };\n\n  struct element *b_elements[21];\n\n  char buffers[17][LEGACY_SFX_SIZE];\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_SFX_EDITOR);\n\n  construct_dialog(&a_di, \"Choose SFX mode\", 26, 7, 28, 8,\n   a_elements, 3, 0);\n\n  dialog_result = run_dialog(mzx_world, &a_di);\n\n  if(!dialog_result)\n  {\n    // Custom to internal\n    if(custom_sfx->list && !new_sfx_mode)\n    {\n      if(!confirm(mzx_world, \"Delete current custom SFX?\"))\n        sfx_free(custom_sfx);\n    }\n    else\n\n    // Internal to custom\n    if(!custom_sfx->list && new_sfx_mode)\n    {\n      for(i = 0; i < NUM_BUILTIN_SFX; i++)\n        sfx_set_string(custom_sfx, i, sfx_strs[i], strlen(sfx_strs[i]));\n    }\n\n    // Edit custom\n    if(custom_sfx->list)\n    {\n      do\n      {\n        // Setup page\n        num_elements = counts[page];\n        num_sfx = num_elements;\n\n        memset(buffers, 0, sizeof(buffers));\n        for(i = 0, pos = offsets[page]; i < num_sfx; i++, pos++)\n        {\n          const struct custom_sfx *sfx = sfx_get(custom_sfx, pos);\n\n          update_label(sfx, pos, i);\n          if(sfx)\n            snprintf(buffers[i], sizeof(buffers[i]), \"%s\", sfx->string);\n\n          b_elements[i] = construct_input_box(1, i + 2,\n           labels[i], LEGACY_SFX_SIZE - 1, buffers[i]);\n        }\n\n        b_elements[i] = construct_label(13, 20,\n         \"Press Alt+T to test a sound effect. Press F3 to rename.\");\n        b_elements[i + 1] = construct_button(16, 22, \"Next\", 1);\n        b_elements[i + 2] = construct_button(36, 22, \"Previous\", 2);\n        b_elements[i + 3] = construct_button(60, 22, \"Done\", 0);\n\n        num_elements += 4;\n\n        construct_dialog_ext(&b_di, \"Edit custom SFX\", 0, 0, 80, 25,\n         b_elements, num_elements, 1, 0, 0, sfx_edit_idle);\n        // Run dialog\n        dialog_result = run_dialog(mzx_world, &b_di);\n        destruct_dialog(&b_di);\n\n        // Commit changes...\n        for(i = 0, pos = offsets[page]; i < num_sfx; i++, pos++)\n          sfx_set_string(custom_sfx, pos, buffers[i], strlen(buffers[i]));\n\n        // Next/prev page?\n        switch(dialog_result)\n        {\n          case 1:\n          {\n            page++;\n            if(page > 5)\n              page = 0;\n            break;\n          }\n\n          case 2:\n          {\n            page--;\n            if(page < 0)\n              page = 5;\n            break;\n          }\n        }\n      } while(dialog_result > 0);\n    }\n  }\n\n  destruct_dialog(&a_di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  // Done!\n  pop_context();\n}\n\n/**\n * Import world SFX strings.\n */\nvoid import_sfx(context *parent, boolean *modified)\n{\n  struct world *mzx_world = parent->world;\n  struct sfx_list *custom_sfx = &mzx_world->custom_sfx;\n  char import_name[MAX_PATH];\n  import_name[0] = '\\0';\n\n  if(!choose_file(mzx_world, sfx_ext, import_name,\n   \"Choose SFX file to import\", ALLOW_ALL_DIRS))\n  {\n    char *tmp;\n    vfile *sfx_file;\n    int64_t len;\n    size_t n;\n\n    sfx_file = vfopen_unsafe(import_name, \"rb\");\n    if(!sfx_file)\n      goto err;\n\n    len = vfilelength(sfx_file, true);\n    if(len < 0 || len > 1024 * 1024)\n    {\n      vfclose(sfx_file);\n      goto err;\n    }\n\n    tmp = (char *)cmalloc(len);\n    if(!tmp)\n    {\n      vfclose(sfx_file);\n      goto err;\n    }\n\n    n = vfread(tmp, 1, len, sfx_file);\n    vfclose(sfx_file);\n\n    if(!sfx_load_from_memory(custom_sfx, MZX_VERSION, tmp, n))\n      goto err;\n\n    *modified = true;\n    return;\nerr:\n    error_message(E_SFX_IMPORT, 0, NULL);\n  }\n}\n\n/**\n * Export world SFX strings.\n */\nvoid export_sfx(context *parent)\n{\n  struct world *mzx_world = parent->world;\n  const struct sfx_list *custom_sfx = &mzx_world->custom_sfx;\n  struct element *elements[1];\n  const char *opt_names[1] = { \"Legacy export (for versions <2.93)\" };\n  int results[1];\n  char export_name[MAX_PATH];\n  export_name[0] = '\\0';\n\n  results[0] = false;\n  elements[0] = construct_check_box(2, 21, opt_names, 1, 40, results);\n\n  if(!file_manager(mzx_world, sfx_ext, \".sfx\", export_name,\n   \"Export SFX file\", ALLOW_ALL_DIRS, ALLOW_NEW_FILES,\n   elements, ARRAY_SIZE(elements), 1))\n  {\n    vfile *sfx_file;\n    const char *out;\n    char *tmp = NULL;\n    size_t size;\n    size_t size_written;\n    int ver = results[0] ? V251 : MZX_VERSION;\n\n    if(custom_sfx->list)\n    {\n      size_t required = 0;\n      size = sfx_save_to_memory(custom_sfx, ver, NULL, 0, &required);\n\n      if(!required)\n        goto err;\n\n      tmp = (char *)cmalloc(required);\n      if(!tmp)\n        goto err;\n\n      out = tmp;\n      size = sfx_save_to_memory(custom_sfx, ver, tmp, required, NULL);\n      if(size == 0)\n        goto err;\n    }\n    else\n    {\n      // TODO: hack relies on sfx_strs matching the legacy format.\n      out = (const char *)sfx_strs;\n      size = sizeof(sfx_strs);\n      assert(size == NUM_BUILTIN_SFX * LEGACY_SFX_SIZE);\n    }\n\n    sfx_file = vfopen_unsafe(export_name, \"wb\");\n    if(!sfx_file)\n      goto err;\n\n    size_written = vfwrite(out, 1, size, sfx_file);\n    vfclose(sfx_file);\n\n    if(size_written < size)\n      error_message(E_IO_WRITE, 0, NULL);\n\n    free(tmp);\n    return;\nerr:\n    error_message(E_SFX_EXPORT, 0, NULL);\n    free(tmp);\n    return;\n  }\n}\n"
  },
  {
    "path": "src/editor/sfx_edit.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_SFX_EDIT_H\n#define __EDITOR_SFX_EDIT_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../core.h\"\n\nvoid sfx_edit(struct world *mzx_world);\n\nvoid import_sfx(context *parent, boolean *modified);\nvoid export_sfx(context *parent);\n\n__M_END_DECLS\n\n#endif // __EDITOR_SFX_EDIT_H\n"
  },
  {
    "path": "src/editor/stringsearch.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2012, 2020 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Boyer-Moore string search implementation.\n * These functions are currently only used by the editor.\n *\n * Portions of this implementation are based on the text description of the\n * Boyer-Moore algorithm on Wikipedia:\n *\n * https://en.wikipedia.org/wiki/Boyer–Moore_string-search_algorithm\n */\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"../memcasecmp.h\"\n#include \"../util.h\"\n\n#include \"stringsearch.h\"\n\n/**\n * Determine if the position of B starting at pos is also a prefix of B.\n */\nstatic boolean prefix_check(const unsigned char *B, const size_t b_len,\n const size_t pos, boolean ignore_case)\n{\n  if(ignore_case)\n    return !memcasecmp(B, B + pos, b_len - pos);\n  else\n    return !memcmp(B, B + pos, b_len - pos);\n}\n\n/**\n * If the position of B starting at pos is also a suffix of B, return the\n * length of this suffix. Otherwise, return 0.\n */\nstatic size_t get_suffix_length(const unsigned char *B, const size_t b_len,\n const size_t pos, boolean ignore_case)\n{\n  size_t i;\n\n  if(ignore_case)\n  {\n    for(i = 0; i < pos; i++)\n      if(memtolower(B[pos - i]) != memtolower(B[b_len - i - 1]))\n        break;\n  }\n  else\n    for(i = 0; i < pos; i++)\n      if(B[pos - i] != B[b_len - i - 1])\n        break;\n\n  return i;\n}\n\n/**\n * Initialize a string search index for a needle substring to be used in\n * string_search(). This only needs to be performed once per search string.\n * This may be called subsequently on the same search_string_data struct to\n * prepare it for a different needle string. The needle string does not need\n * to be null-terminated.\n *\n * @param B             Pointer to needle substring.\n * @param b_len         Length of needle substring.\n * @param data          Struct to store string search index info to.\n * @param ignore_case   True for case-insensitive; false for case-sensitive.\n */\nvoid string_search_index(const void *B, const size_t b_len,\n struct string_search_data *data, boolean ignore_case)\n{\n  unsigned int *bad_char = data->bad_char;\n  unsigned int *good_suffix = data->good_suffix;\n  const unsigned char *start = (const unsigned char *)B;\n  const unsigned char *end = start + b_len - 1;\n  const unsigned char *pos;\n  ssize_t i;\n\n  assert(B);\n  assert(data);\n\n  /**\n   * Bad characters rule.\n   *\n   * If a character is encountered that doesn't exist in the pattern string,\n   * the current pattern position can be advanced entirely past the bad\n   * character. If the bad character encountered is in the pattern string but\n   * is out of sequence, advance the current position such that the last\n   * occurence of the bad character in the pattern is aligned with the position\n   * where the mismatch occurred.\n   *\n   * If the position of the bad character in the pattern string is to the right\n   * of the mismatch position the algorithm must rely on either the good prefix\n   * and good suffix rules (see below) or advance the current pattern position\n   * right by a single character (see string_search).\n   */\n  for(i = 0; i < 256; i++)\n    bad_char[i] = b_len;\n\n  if(!ignore_case)\n  {\n    for(pos = start; pos < end; pos++)\n      bad_char[*pos] = end - pos;\n  }\n  else\n  {\n    for(pos = start; pos < end; pos++)\n      bad_char[memtolower(*pos)] = end - pos;\n\n    // Duplicating the lowercase values over the uppercase values helps avoid\n    // an extra tolower in the search function.\n    memcpy(bad_char + 'A', bad_char + 'a', sizeof(unsigned int) * 26);\n  }\n\n  /**\n   * Currently the space for the good prefix/suffix table is statically\n   * allocated as part of the data struct, so only generate it if it will fit\n   * in the provided buffer. The currently provided buffer size is good enough\n   * for all current applications of this algorithm (as of writing this).\n   */\n  data->has_good_suffix_table = (b_len <= STRINGSEARCH_SUFFIX_MAX);\n\n  if(data->has_good_suffix_table)\n  {\n    /**\n     * Good prefix rule.\n     *\n     * Given a partial match in pattern string \"AAAbbb*bbbCCC\" where CCC is\n     * a suffix of the matched portion of the pattern, * is the current position\n     * and is a mismatch, and AAA is a prefix of the pattern string the same\n     * length as CCC, shift the current position such that AAA aligns with the\n     * old position of CCC. In the loop below, i == the position of *.\n     * CCC can NOT be the entire matched portion of the prefix.\n     *\n     * Otherwise, the current position can be shifted entirely past CCC (except\n     * for the case below).\n     */\n    size_t last_prefix_position = b_len;\n\n    for(i = (ssize_t)b_len - 1; i >= 0; i--)\n    {\n      if(prefix_check(start, b_len, i + 1, ignore_case))\n        last_prefix_position = i + 1;\n\n      good_suffix[i] = MIN(b_len, b_len - 1 - i + last_prefix_position);\n    }\n\n    /**\n     * Good suffix rule (takes precedence over the good prefix rule as the good\n     * prefix rule could otherwise skip matches in this situation).\n     *\n     * Given a partial match in pattern string \"bbbAAXbbb*CCC\" where CCC is\n     * a suffix of the pattern string, AAX is a suffix of some position within\n     * the pattern string, and * is the current position and is a mismatch,\n     * if AAX is equal to CCC then shift the current position so AAX aligns\n     * with the old position of CCC. CCC and AAX can overlap. In the loop\n     * below, i == the position of X, but the position that will use this value\n     * is the position of *.\n     *\n     * Otherwise, do not modify the table (leaving the good prefix value\n     * generated above unmodified).\n     */\n    for(i = 0; i < (ssize_t)b_len - 1; i++)\n    {\n      size_t suffix_len = get_suffix_length(start, b_len, i, ignore_case);\n      if(start[i - suffix_len] != start[b_len - suffix_len - 1])\n        good_suffix[b_len - suffix_len - 1] = b_len - i - 1 + suffix_len;\n    }\n  }\n}\n\n/**\n * Search for needle substring B in haystack A. This implementation uses the\n * Boyer-Moore algorithm to improve search performance, particularly for\n * repeated searches. The provided string_search_data struct must be\n * initialized by calling string_search_index() before it is used. Neither the\n * needle string nor the haystack string need to be null-terminated.\n *\n * If data is NULL, it will be allocated and initialized within this function\n * and freed prior to returning. Only provide NULL if this search will occur\n * once and you don't care about the allocation overhead.\n *\n * If a string search will only be performed once for a given needle or if the\n * needle is short, and if both strings are null-terminated, the standard\n * library function strstr should probably be used instead.\n *\n * @param A             Pointer to haystack string.\n * @param a_len         Length of haystack string.\n * @param B             Pointer to needle substring.\n * @param b_len         Length of needle substring.\n * @param data          Struct containing string search index information.\n * @param ignore_case   True for case-insensitive; false for case-sensitive.\n */\nconst void *string_search(const void *A, const size_t a_len,\n const void *B, const size_t b_len, const struct string_search_data *data,\n boolean ignore_case)\n{\n  const unsigned char *a = (const unsigned char *)A;\n  const unsigned char *b = (const unsigned char *)B;\n  struct string_search_data *alloc = NULL;\n  const unsigned int *bad_char_index;\n  const unsigned int *good_suffix_index;\n  const void *result = NULL;\n  ssize_t i = b_len - 1;\n  ssize_t idx, idx2;\n  ssize_t j;\n  boolean has_good_suffix_table;\n\n  assert(A);\n  assert(B);\n\n  if(!data)\n  {\n    alloc = (struct string_search_data *)cmalloc(sizeof(struct string_search_data));\n    string_search_index(B, b_len, alloc, ignore_case);\n    data = alloc;\n  }\n  bad_char_index = data->bad_char;\n  good_suffix_index = data->good_suffix;\n  has_good_suffix_table = data->has_good_suffix_table;\n\n  /**\n   * Note: b_len - j is the \"naive\" skip amount, i.e. it results in effectively\n   * shifting the pattern position to the right by a single character. If the\n   * good prefix/suffix table is not available it must be used in place of the\n   * value that would have been read from that table.\n   */\n  if(!ignore_case)\n  {\n    while((size_t)i < a_len)\n    {\n      j = b_len - 1;\n\n      while(j >= 0 && a[i] == b[j])\n        j--, i--;\n\n      if(j == -1)\n      {\n        result = (const void *)(a + i + 1);\n        break;\n      }\n\n      idx = bad_char_index[a[i]];\n      idx2 = has_good_suffix_table ? good_suffix_index[j] : b_len - j;\n      i += MAX(idx, idx2);\n    }\n  }\n  else\n  {\n    while((size_t)i < a_len)\n    {\n      j = b_len - 1;\n\n      while(j >= 0 && memtolower(a[i]) == memtolower(b[j]))\n        j--, i--;\n\n      if(j == -1)\n      {\n        result = (const void *)(a + i + 1);\n        break;\n      }\n\n      idx = bad_char_index[a[i]];\n      idx2 = has_good_suffix_table ? good_suffix_index[j] : b_len - j;\n      i += MAX(idx, idx2);\n    }\n  }\n\n  if(alloc)\n    free(alloc);\n\n  return result;\n}\n"
  },
  {
    "path": "src/editor/stringsearch.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2012 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_STRINGSEARCH_H\n#define __EDITOR_STRINGSEARCH_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stddef.h>\n\n#define STRINGSEARCH_SUFFIX_MAX 63\n\nstruct string_search_data\n{\n  unsigned int bad_char[256];\n  unsigned int good_suffix[STRINGSEARCH_SUFFIX_MAX];\n  boolean has_good_suffix_table;\n};\n\nvoid string_search_index(const void *B, const size_t b_len,\n struct string_search_data *data, boolean ignore_case);\nconst void *string_search(const void *A, const size_t a_len,\n const void *B, const size_t b_len, const struct string_search_data *data,\n boolean ignore_case);\n\n__M_END_DECLS\n\n#endif /* __EDITOR_STRINGSEARCH_H */\n"
  },
  {
    "path": "src/editor/undo.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdint.h>\n#include <string.h>\n\n#include \"block.h\"\n#include \"board.h\"\n#include \"buffer.h\"\n#include \"buffer_struct.h\"\n#include \"edit.h\"\n#include \"graphics.h\"\n#include \"robo_ed.h\"\n#include \"robot.h\"\n#include \"undo.h\"\n\n#include \"../block.h\"\n#include \"../board.h\"\n#include \"../graphics.h\"\n#include \"../idarray.h\"\n#include \"../robot.h\"\n#include \"../util.h\"\n#include \"../world.h\"\n#include \"../world_struct.h\"\n\n/* Operations for handling undo histories:\n *   Construct a history buffer of some size\n *   Add a new frame to the history, clearing old frames as-needed\n *   Add a position to the current frame (optional, primarily for mouse input)\n *   Update the current frame of the history (primarily for mouse input)\n *   Perform an undo operation: apply previous version, then step back\n *   Perform a redo operation: step forward, then apply current version\n *   Destruct a history buffer\n *\n * Most of these options are generalizable and use the same functions,\n * but the creation of a history buffer and adding a new frame require\n * implementation-specific functions.\n *\n * All necessary data regarding the scope of the frame must be given when\n * a new frame is created; this information can't change easily due to the\n * way e.g. boards are stored.\n *\n * A special case currently exists where the undo actions will operate\n * outside of the undo frame; moving the player on the board.\n */\n\nenum undo_frame_type\n{\n  POS_FRAME,\n  BLOCK_FRAME,\n};\n\nstruct undo_frame\n{\n  enum undo_frame_type type;\n};\n\nstruct undo_history\n{\n  struct undo_frame **frames;\n  struct undo_frame *current_frame;\n  int current;\n  int first;\n  int last;\n  int size;\n  void (*add_pos_function)(struct undo_frame *, int, int);\n  void (*undo_function)(struct undo_frame *);\n  void (*redo_function)(struct undo_frame *);\n  void (*update_function)(struct undo_frame *);\n  void (*clear_function)(struct undo_frame *);\n};\n\nstatic struct undo_history *construct_undo_history(int max_size)\n{\n  struct undo_history *h = cmalloc(sizeof(struct undo_history));\n  h->frames = ccalloc(max_size, sizeof(struct undo_frame *));\n  h->current_frame = NULL;\n  h->current = -1;\n  h->first = -1;\n  h->last = -1;\n  h->size = max_size;\n  h->add_pos_function = NULL;\n  h->undo_function = NULL;\n  h->redo_function = NULL;\n  h->update_function = NULL;\n  h->clear_function = NULL;\n  return h;\n}\n\nstatic void add_undo_frame(struct undo_history *h, void *f)\n{\n  struct undo_frame *temp;\n  int i, j;\n\n  h->current_frame = f;\n\n  if(h->current != h->last)\n  {\n    // Clear everything after the current frame\n    // Stop at the position after the last frame.\n    j = h->last + 1;\n    if(j >= h->size)\n      j = 0;\n\n    // Might be -1 (e.g. every frame has been undone)\n    if(h->current > -1)\n    {\n      i = h->current + 1;\n      if(i >= h->size)\n        i = 0;\n\n      h->last = h->current;\n    }\n    else\n    {\n      i = h->first;\n      h->first = -1;\n    }\n\n    // Clear everything after the current frame\n    while(i != j)\n    {\n      temp = h->frames[i];\n      h->frames[i] = NULL;\n      h->clear_function(temp);\n\n      i++;\n      if(i == h->size)\n        i = 0;\n    }\n  }\n\n  if(h->first == -1)\n  {\n    // First frame in the history\n    h->frames[0] = f;\n    h->first = 0;\n    h->current = 0;\n    h->last = 0;\n    return;\n  }\n\n  h->last++;\n  if(h->last == h->size)\n    h->last = 0;\n\n  if(h->last == h->first)\n  {\n    // History full? Delete the first\n    temp = h->frames[h->first];\n    h->clear_function(temp);\n\n    h->first++;\n    if(h->first == h->size)\n      h->first = 0;\n  }\n\n  h->frames[h->last] = f;\n  h->current = h->last;\n}\n\nboolean apply_undo(struct undo_history *h)\n{\n  // If current is -1, we're at the start of the history and can't undo\n  if(h && h->current > -1)\n  {\n    // Apply the undo\n    h->undo_function(h->current_frame);\n\n    // Reverse to the previous frame\n    if(h->current == h->first)\n    {\n      h->current = -1;\n      h->current_frame = NULL;\n      return true;\n    }\n\n    else if(h->current == 0)\n      h->current = h->size - 1;\n\n    else\n      h->current--;\n\n    h->current_frame = h->frames[h->current];\n    return true;\n  }\n  return false;\n}\n\nboolean apply_redo(struct undo_history *h)\n{\n  // Only works if frames exist and we're not on the last one\n  if(h && h->current != h->last)\n  {\n    // Advance to the next frame\n    if(h->current == -1)\n      h->current = h->first;\n\n    else if(h->current + 1 == h->size)\n      h->current = 0;\n\n    else\n      h->current++;\n\n    h->current_frame = h->frames[h->current];\n\n    // Apply the redo\n    h->redo_function(h->current_frame);\n    return true;\n  }\n  return false;\n}\n\nvoid add_undo_position(struct undo_history *h, int x, int y)\n{\n  struct undo_frame *f;\n\n  if(h && h->add_pos_function)\n  {\n    f = h->current_frame;\n\n    if(f && f->type == POS_FRAME)\n      h->add_pos_function(f, x, y);\n  }\n}\n\nvoid update_undo_frame(struct undo_history *h)\n{\n  if(h && h->current_frame)\n    h->update_function(h->current_frame);\n}\n\nvoid destruct_undo_history(struct undo_history *h)\n{\n  struct undo_frame *f;\n  int i;\n\n  if(h)\n  {\n    for(i = 0; i < h->size; i++)\n    {\n      f = h->frames[i];\n      if(f)\n        h->clear_function(f);\n    }\n\n    free(h->frames);\n    free(h);\n  }\n}\n\n\n/******************************/\n/* Charset specific functions */\n/******************************/\n\nstruct charset_undo_frame\n{\n  struct undo_frame f;\n  uint8_t offset;\n  uint8_t charset;\n  uint8_t width;\n  uint8_t height;\n  char *prev_chars;\n  char *current_chars;\n};\n\nstatic void apply_charset_undo(struct undo_frame *f)\n{\n  struct charset_undo_frame *current = (struct charset_undo_frame *)f;\n\n  ec_change_block(current->offset, current->charset,\n   current->width, current->height, current->prev_chars);\n}\n\nstatic void apply_charset_redo(struct undo_frame *f)\n{\n  struct charset_undo_frame *current = (struct charset_undo_frame *)f;\n\n  ec_change_block(current->offset, current->charset,\n   current->width, current->height, current->current_chars);\n}\n\nstatic void apply_charset_update(struct undo_frame *f)\n{\n  struct charset_undo_frame *current = (struct charset_undo_frame *)f;\n\n  ec_read_block(current->offset, current->charset,\n   current->width, current->height, current->current_chars);\n}\n\nstatic void apply_charset_clear(struct undo_frame *f)\n{\n  struct charset_undo_frame *current = (struct charset_undo_frame *)f;\n\n  free(current->prev_chars);\n  free(current->current_chars);\n  free(current);\n}\n\nstruct undo_history *construct_charset_undo_history(int max_size)\n{\n  if(max_size)\n  {\n    struct undo_history *h = construct_undo_history(max_size);\n\n    h->undo_function = apply_charset_undo;\n    h->redo_function = apply_charset_redo;\n    h->update_function = apply_charset_update;\n    h->clear_function = apply_charset_clear;\n    return h;\n  }\n  return NULL;\n}\n\nvoid add_charset_undo_frame(struct undo_history *h, int charset, int first_char,\n int width, int height)\n{\n  if(h)\n  {\n    struct charset_undo_frame *current\n     = cmalloc(sizeof(struct charset_undo_frame));\n\n    add_undo_frame(h, current);\n\n    current->offset = first_char;\n    current->charset = charset;\n    current->width = width;\n    current->height = height;\n\n    current->prev_chars = cmalloc(width * height * CHAR_SIZE);\n    current->current_chars = cmalloc(width * height * CHAR_SIZE);\n\n    ec_read_block(current->offset, current->charset,\n     current->width, current->height, current->prev_chars);\n  }\n}\n\n\n/****************************/\n/* Board specific functions */\n/****************************/\n\n// \"board_undo_frame\" is for small updates or updates that take place over\n// a difficult-to-define area of the board (e.g. mouse placement, flood fill).\n// This version takes special updates with the new positions drawn, but still\n// requires a normal update after placement. This version requires something\n// to be placed from the buffer to work\n\n// \"block_undo_frame\" is for larger updates that occur on a well-known\n// area of the board, and use fake boards and block copies. This version does\n// not require anything to be placed from the buffer\n\n// storage_obj is a pointer to a robot, scroll, or sensor\nstruct board_undo_pos\n{\n  int16_t x;\n  int16_t y;\n  char id;\n  char color;\n  char param;\n  char under_id;\n  char under_color;\n  char under_param;\n  void *storage_obj;\n};\n\nstruct board_undo_frame\n{\n  struct undo_frame f;\n  struct world *mzx_world;\n  int move_player;\n  int prev_player_x;\n  int prev_player_y;\n  int current_player_x;\n  int current_player_y;\n  struct board_undo_pos replace;\n  struct board_undo_pos *prev;\n  int prev_alloc;\n  int prev_size;\n};\n\nstruct block_undo_frame\n{\n  struct undo_frame f;\n  struct world *mzx_world;\n  int move_player;\n  int prev_player_x;\n  int prev_player_y;\n  int current_player_x;\n  int current_player_y;\n  int width;\n  int height;\n  int src_offset;\n  struct board *src_board;\n  struct board *prev_board;\n  struct board *current_board;\n};\n\nstatic void alloc_board_undo_pos(struct board_undo_pos *pos)\n{\n  // An undo position that is going to take a storage object needs\n  // to have that storage object manually allocated for it\n  enum thing id = (enum thing)pos->id;\n\n  pos->storage_obj = NULL;\n\n  if(is_robot(id))\n  {\n    pos->param = -1;\n    pos->storage_obj = cmalloc(sizeof(struct robot));\n    memset(pos->storage_obj, 0, sizeof(struct robot));\n  }\n  else\n\n  if(is_signscroll(id))\n  {\n    pos->param = -1;\n    pos->storage_obj = cmalloc(sizeof(struct scroll));\n    memset(pos->storage_obj, 0, sizeof(struct scroll));\n  }\n  else\n\n  if(id == SENSOR)\n  {\n    pos->param = -1;\n    pos->storage_obj = cmalloc(sizeof(struct sensor));\n    memset(pos->storage_obj, 0, sizeof(struct sensor));\n  }\n}\n\nstatic void get_board_undo_pos_storage(struct board_undo_pos *pos,\n struct buffer_info *temp_buffer)\n{\n  enum thing id = (enum thing)pos->id;\n\n  temp_buffer->robot = NULL;\n  temp_buffer->scroll = NULL;\n  temp_buffer->sensor = NULL;\n\n  if(is_robot(id))\n  {\n    temp_buffer->robot = pos->storage_obj;\n  }\n  else\n\n  if(is_signscroll(id))\n  {\n    temp_buffer->scroll = pos->storage_obj;\n  }\n  else\n\n  if(id == SENSOR)\n  {\n    temp_buffer->sensor = pos->storage_obj;\n  }\n}\n\nstatic void clear_board_undo_pos(struct board_undo_pos *pos)\n{\n  enum thing id = (enum thing)pos->id;\n\n  if(is_robot(id))\n  {\n    clear_robot_contents(pos->storage_obj);\n  }\n  else\n\n  if(is_signscroll(id))\n  {\n    clear_scroll_contents(pos->storage_obj);\n  }\n\n  free(pos->storage_obj);\n}\n\nstatic void apply_board_undo(struct undo_frame *f)\n{\n  struct board_undo_frame *current = (struct board_undo_frame *)f;\n  struct world *mzx_world = current->mzx_world;\n\n  // Can't overwrite the player, so move the player first. This needs to be\n  // done for both types of operations, as either may require relocating the\n  // player to the player's previous position.\n  if(current->move_player)\n    id_remove_top(mzx_world, mzx_world->player_x, mzx_world->player_y);\n\n  switch(f->type)\n  {\n    case POS_FRAME:\n    {\n      struct board_undo_pos *prev;\n      struct buffer_info temp_buffer;\n      int i;\n\n      for(i = current->prev_size - 1; i >= 0; i--)\n      {\n        // Place the under, then the regular\n        prev = &(current->prev[i]);\n        temp_buffer.id = prev->under_id;\n        temp_buffer.param = prev->under_param;\n        temp_buffer.color = prev->under_color;\n        temp_buffer.robot = NULL;\n        temp_buffer.scroll = NULL;\n        temp_buffer.sensor = NULL;\n\n        place_current_at_xy(mzx_world, &temp_buffer, prev->x, prev->y,\n         EDIT_BOARD, NULL);\n\n        temp_buffer.id = prev->id;\n        temp_buffer.param = prev->param;\n        temp_buffer.color = prev->color;\n        get_board_undo_pos_storage(prev, &temp_buffer);\n\n        place_current_at_xy(mzx_world, &temp_buffer, prev->x, prev->y,\n         EDIT_BOARD, NULL);\n      }\n      break;\n    }\n\n    case BLOCK_FRAME:\n    {\n      struct block_undo_frame *current = (struct block_undo_frame *)f;\n\n      copy_board_to_board(mzx_world,\n       current->prev_board, 0, current->src_board, current->src_offset,\n       current->width, current->height\n      );\n      break;\n    }\n  }\n\n  if(current->move_player)\n    copy_replace_player(mzx_world, current->prev_player_x,\n     current->prev_player_y);\n}\n\nstatic void apply_board_redo(struct undo_frame *f)\n{\n  struct board_undo_frame *current = (struct board_undo_frame *)f;\n  struct world *mzx_world = current->mzx_world;\n\n  switch(f->type)\n  {\n    case POS_FRAME:\n    {\n      // Don't need any special handling for the player here. The only thing\n      // that can move the player during this redo operation is if the player\n      // is the thing being placed; place_current_at_xy can handle that.\n      struct board_undo_pos *next = &(current->replace);\n      struct board_undo_pos *prev;\n      struct buffer_info temp_buffer;\n      int i;\n\n      temp_buffer.id = next->id;\n      temp_buffer.param = next->param;\n      temp_buffer.color = next->color;\n      get_board_undo_pos_storage(next, &temp_buffer);\n\n      for(i = 0; i < current->prev_size; i++)\n      {\n        prev = &(current->prev[i]);\n        place_current_at_xy(mzx_world, &temp_buffer, prev->x, prev->y,\n         EDIT_BOARD, NULL);\n      }\n      break;\n    }\n\n    case BLOCK_FRAME:\n    {\n      struct block_undo_frame *current = (struct block_undo_frame *)f;\n\n      // Copy won't overwrite the player, so remove the player first.\n      if(current->move_player)\n        id_remove_top(mzx_world, mzx_world->player_x, mzx_world->player_y);\n\n      copy_board_to_board(current->mzx_world,\n       current->current_board, 0, current->src_board, current->src_offset,\n       current->width, current->height\n      );\n\n      if(current->move_player)\n        copy_replace_player(mzx_world, current->current_player_x,\n         current->current_player_y);\n      break;\n    }\n  }\n}\n\nstatic void apply_board_update(struct undo_frame *f)\n{\n  struct board_undo_frame *current = (struct board_undo_frame *)f;\n\n  switch(f->type)\n  {\n    case POS_FRAME:\n    {\n      // Trim extra size off of the allocation\n      int size = MAX(current->prev_size, 1);\n\n      current->prev_alloc = size;\n      current->prev =\n       crealloc(current->prev, size * sizeof(struct board_undo_pos));\n      break;\n    }\n\n    case BLOCK_FRAME:\n    {\n      struct block_undo_frame *current = (struct block_undo_frame *)f;\n\n      // Update the block frame\n      if(current->current_board)\n        clear_board(current->current_board);\n\n      current->current_board =\n       create_buffer_board(current->width, current->height);\n\n      copy_board_to_board(current->mzx_world,\n       current->src_board, current->src_offset, current->current_board, 0,\n       current->width, current->height\n      );\n      break;\n    }\n  }\n\n  // Handle player position changes\n  current->current_player_x = current->mzx_world->player_x;\n  current->current_player_y = current->mzx_world->player_y;\n\n  if((current->current_player_x != current->prev_player_x) ||\n   (current->current_player_y != current->prev_player_y))\n    current->move_player = 1;\n\n  else\n    current->move_player = 0;\n}\n\nstatic void apply_board_clear(struct undo_frame *f)\n{\n  switch(f->type)\n  {\n    case POS_FRAME:\n    {\n      struct board_undo_frame *current = (struct board_undo_frame *)f;\n      struct board_undo_pos *prev = current->prev;\n      int i;\n\n      clear_board_undo_pos(&(current->replace));\n\n      for(i = 0; i < current->prev_size; i++)\n      {\n        clear_board_undo_pos(prev);\n        prev++;\n      }\n\n      free(current->prev);\n      break;\n    }\n\n    case BLOCK_FRAME:\n    {\n      struct block_undo_frame *current = (struct block_undo_frame *)f;\n      clear_board(current->prev_board);\n      clear_board(current->current_board);\n    }\n  }\n  free(f);\n}\n\nstatic void add_board_undo_position(struct undo_frame *f, int x, int y)\n{\n  struct board_undo_frame *current = (struct board_undo_frame *)f;\n\n  struct world *mzx_world = current->mzx_world;\n  struct board *src_board = mzx_world->current_board;\n  int offset = x + (src_board->board_width * y);\n\n  struct board_undo_pos *pos;\n  struct board_undo_pos *prev = current->prev;\n  int prev_alloc = current->prev_alloc;\n  int prev_size = current->prev_size;\n\n  struct buffer_info temp_buffer;\n\n  // Can't place over player\n  if(src_board->level_id[offset] == PLAYER)\n    return;\n\n  if(prev_size == prev_alloc)\n  {\n    if(!prev_alloc)\n      prev_alloc = 1;\n\n    else\n      prev_alloc *= 2;\n\n    prev = crealloc(prev, prev_alloc * sizeof(struct board_undo_pos));\n  }\n\n  pos = &(prev[prev_size]);\n  prev_size++;\n\n  pos->x = x;\n  pos->y = y;\n  pos->id = src_board->level_id[offset];\n  pos->color = -1;\n  pos->param = -1;\n  pos->under_id = src_board->level_under_id[offset];\n  pos->under_color = src_board->level_under_color[offset];\n  pos->under_param = src_board->level_under_param[offset];\n\n  alloc_board_undo_pos(pos);\n  memset(&temp_buffer, 0, sizeof(struct buffer_info));\n  temp_buffer.robot = pos->storage_obj;\n  temp_buffer.scroll = pos->storage_obj;\n  temp_buffer.sensor = pos->storage_obj;\n\n  grab_at_xy(mzx_world, &temp_buffer, x, y, EDIT_BOARD);\n\n  pos->id = temp_buffer.id;\n  pos->color = temp_buffer.color;\n  pos->param = temp_buffer.param;\n\n  current->prev_alloc = prev_alloc;\n  current->prev_size = prev_size;\n  current->prev = prev;\n}\n\nstruct undo_history *construct_board_undo_history(int max_size)\n{\n  if(max_size)\n  {\n    struct undo_history *h = construct_undo_history(max_size);\n\n    h->add_pos_function = add_board_undo_position;\n    h->undo_function = apply_board_undo;\n    h->redo_function = apply_board_redo;\n    h->update_function = apply_board_update;\n    h->clear_function = apply_board_clear;\n    return h;\n  }\n  return NULL;\n}\n\nvoid add_board_undo_frame(struct world *mzx_world, struct undo_history *h,\n struct buffer_info *buffer, int x, int y)\n{\n  if(h)\n  {\n    struct board_undo_frame *current =\n     cmalloc(sizeof(struct board_undo_frame));\n\n    struct board_undo_pos *replace = &(current->replace);\n\n    add_undo_frame(h, current);\n    current->f.type = POS_FRAME;\n\n    // The player might be moved by the frame, so back up the position\n    current->mzx_world = mzx_world;\n    current->prev_player_x = mzx_world->player_x;\n    current->prev_player_y = mzx_world->player_y;\n    current->move_player = 0;\n\n    // We only care about a handful of things here\n    replace->id = buffer->id;\n    replace->color = buffer->color;\n    replace->param = buffer->param;\n    alloc_board_undo_pos(replace);\n\n    if(is_robot(buffer->id))\n      duplicate_robot_direct_source(mzx_world, buffer->robot,\n       replace->storage_obj, 0, 0);\n\n    else\n    if(is_signscroll(buffer->id))\n      duplicate_scroll_direct(buffer->scroll, replace->storage_obj);\n\n    else\n    if(buffer->id == SENSOR)\n      duplicate_sensor_direct(buffer->sensor, replace->storage_obj);\n\n    current->prev = NULL;\n    current->prev_size = 0;\n    current->prev_alloc = 0;\n\n    // Most places this is used don't want to bother doing this manually,\n    // so do it here for the first position.\n    add_undo_position(h, x, y);\n  }\n}\n\nvoid add_block_undo_frame(struct world *mzx_world, struct undo_history *h,\n struct board *src_board, int src_offset, int width, int height)\n{\n  if(h)\n  {\n    struct block_undo_frame *current =\n     cmalloc(sizeof(struct block_undo_frame));\n\n    add_undo_frame(h, current);\n    current->f.type = BLOCK_FRAME;\n\n    // The player might be moved by the frame, so back up the position\n    current->mzx_world = mzx_world;\n    current->prev_player_x = mzx_world->player_x;\n    current->prev_player_y = mzx_world->player_y;\n    current->move_player = 0;\n\n    current->width = width;\n    current->height = height;\n    current->src_offset = src_offset;\n    current->src_board = src_board;\n\n    current->prev_board = create_buffer_board(width, height);\n    current->current_board = NULL;\n\n    copy_board_to_board(mzx_world,\n     src_board, src_offset, current->prev_board, 0,\n     width, height\n    );\n  }\n}\n\n\n/****************************/\n/* Layer specific functions */\n/****************************/\n\nstruct layer_undo_pos\n{\n  int16_t x;\n  int16_t y;\n  char chr;\n  char color;\n};\n\nstruct layer_undo_pos_frame\n{\n  struct undo_frame f;\n  int layer_width;\n  char *layer_chars;\n  char *layer_colors;\n  struct layer_undo_pos replace;\n  struct layer_undo_pos *prev;\n  int prev_alloc;\n  int prev_size;\n};\n\nstruct layer_undo_frame\n{\n  struct undo_frame f;\n  int width;\n  int height;\n  int layer_width;\n  int layer_offset;\n  char *layer_chars;\n  char *layer_colors;\n  char *prev_chars;\n  char *prev_colors;\n  char *current_chars;\n  char *current_colors;\n};\n\nstatic void apply_layer_undo(struct undo_frame *f)\n{\n  switch(f->type)\n  {\n    case POS_FRAME:\n    {\n      struct layer_undo_pos_frame *current = (struct layer_undo_pos_frame *)f;\n      struct layer_undo_pos *prev;\n      int offset;\n      int i;\n\n      for(i = current->prev_size - 1; i >= 0; i--)\n      {\n        prev = &(current->prev[i]);\n        offset = prev->x + (prev->y * current->layer_width);\n        current->layer_chars[offset] = prev->chr;\n        current->layer_colors[offset] = prev->color;\n      }\n      break;\n    }\n\n    case BLOCK_FRAME:\n    {\n      struct layer_undo_frame *lf = (struct layer_undo_frame *)f;\n\n      copy_layer_buffer_to_buffer(\n       lf->prev_chars, lf->prev_colors, lf->width, 0,\n       lf->layer_chars, lf->layer_colors, lf->layer_width, lf->layer_offset,\n       lf->width, lf->height\n      );\n      break;\n    }\n  }\n}\n\nstatic void apply_layer_redo(struct undo_frame *f)\n{\n  switch(f->type)\n  {\n    case POS_FRAME:\n    {\n      struct layer_undo_pos_frame *current = (struct layer_undo_pos_frame *)f;\n      struct layer_undo_pos *replace = &(current->replace);\n      struct layer_undo_pos *prev;\n      int size = current->prev_size;\n      int offset;\n      int i;\n\n      for(i = 0; i < size; i++)\n      {\n        prev = &(current->prev[i]);\n        offset = prev->x + (prev->y * current->layer_width);\n        current->layer_chars[offset] = replace->chr;\n        current->layer_colors[offset] = replace->color;\n      }\n      break;\n    }\n\n    case BLOCK_FRAME:\n    {\n      struct layer_undo_frame *lf = (struct layer_undo_frame *)f;\n\n      copy_layer_buffer_to_buffer(\n       lf->current_chars, lf->current_colors, lf->width, 0,\n       lf->layer_chars, lf->layer_colors, lf->layer_width, lf->layer_offset,\n       lf->width, lf->height\n      );\n      break;\n    }\n  }\n}\n\nstatic void apply_layer_update(struct undo_frame *f)\n{\n  switch(f->type)\n  {\n    case POS_FRAME:\n    {\n      // Shrink alloc to trim unused space.\n      struct layer_undo_pos_frame *current = (struct layer_undo_pos_frame *)f;\n      int size = MAX(current->prev_size, 1);\n\n      current->prev_alloc = size;\n      current->prev =\n       crealloc(current->prev, size * sizeof(struct layer_undo_pos));\n      break;\n    }\n\n    case BLOCK_FRAME:\n    {\n      struct layer_undo_frame *lf = (struct layer_undo_frame *)f;\n\n      copy_layer_buffer_to_buffer(\n       lf->layer_chars, lf->layer_colors, lf->layer_width, lf->layer_offset,\n       lf->current_chars, lf->current_colors, lf->width, 0,\n       lf->width, lf->height\n      );\n      break;\n    }\n  }\n}\n\nstatic void apply_layer_clear(struct undo_frame *f)\n{\n  switch(f->type)\n  {\n    case POS_FRAME:\n    {\n      struct layer_undo_pos_frame *current = (struct layer_undo_pos_frame *)f;\n      free(current->prev);\n      break;\n    }\n\n    case BLOCK_FRAME:\n    {\n      struct layer_undo_frame *current = (struct layer_undo_frame *)f;\n      free(current->prev_chars);\n      free(current->prev_colors);\n      free(current->current_chars);\n      free(current->current_colors);\n      break;\n    }\n  }\n  free(f);\n}\n\nstatic void add_layer_undo_position(struct undo_frame *f, int x, int y)\n{\n  struct layer_undo_pos_frame *current = (struct layer_undo_pos_frame *)f;\n\n  struct layer_undo_pos *prev = current->prev;\n  int prev_size = current->prev_size;\n  int prev_alloc = current->prev_alloc;\n\n  int offset = x + (y * current->layer_width);\n\n  if(prev_size == prev_alloc)\n  {\n    if(!prev_alloc)\n      prev_alloc = 1;\n\n    else\n      prev_alloc *= 2;\n\n    prev = crealloc(prev, prev_alloc * sizeof(struct board_undo_pos));\n  }\n\n  prev[prev_size].x = x;\n  prev[prev_size].y = y;\n  prev[prev_size].chr = current->layer_chars[offset];\n  prev[prev_size].color = current->layer_colors[offset];\n\n  prev_size++;\n\n  current->prev = prev;\n  current->prev_size = prev_size;\n  current->prev_alloc = prev_alloc;\n}\n\nstruct undo_history *construct_layer_undo_history(int max_size)\n{\n  if(max_size)\n  {\n    struct undo_history *h = construct_undo_history(max_size);\n\n    h->add_pos_function = add_layer_undo_position;\n    h->undo_function = apply_layer_undo;\n    h->redo_function = apply_layer_redo;\n    h->update_function = apply_layer_update;\n    h->clear_function = apply_layer_clear;\n    return h;\n  }\n  return NULL;\n}\n\nvoid add_layer_undo_pos_frame(struct undo_history *h, char *layer_chars,\n char *layer_colors, int layer_width, struct buffer_info *buffer, int x, int y)\n{\n  if(h)\n  {\n    struct layer_undo_pos_frame *current =\n     cmalloc(sizeof(struct layer_undo_pos_frame));\n\n    add_undo_frame(h, current);\n    current->f.type = POS_FRAME;\n\n    current->layer_width = layer_width;\n    current->layer_chars = layer_chars;\n    current->layer_colors = layer_colors;\n\n    current->replace.chr = buffer->param;\n    current->replace.color = buffer->color;\n\n    current->prev = NULL;\n    current->prev_alloc = 0;\n    current->prev_size = 0;\n\n    // Most places this is used don't want to bother doing this manually,\n    // so do it here for the first position.\n    add_undo_position(h, x, y);\n  }\n}\n\nvoid add_layer_undo_frame(struct undo_history *h, char *layer_chars,\n char *layer_colors, int layer_width, int layer_offset, int width, int height)\n{\n  if(h)\n  {\n    struct layer_undo_frame *current =\n     cmalloc(sizeof(struct layer_undo_frame));\n\n    add_undo_frame(h, current);\n    current->f.type = BLOCK_FRAME;\n\n    current->width = width;\n    current->height = height;\n    current->layer_width = layer_width;\n    current->layer_offset = layer_offset;\n    current->layer_chars = layer_chars;\n    current->layer_colors = layer_colors;\n\n    current->prev_chars = cmalloc(width * height);\n    current->prev_colors = cmalloc(width * height);\n    current->current_chars = cmalloc(width * height);\n    current->current_colors = cmalloc(width * height);\n\n    copy_layer_buffer_to_buffer(\n     layer_chars, layer_colors, layer_width, layer_offset,\n     current->prev_chars, current->prev_colors, width, 0,\n     width, height\n    );\n  }\n}\n\n\n/************************************/\n/* Robot editor specific functions. */\n/************************************/\n\nstruct text_undo_line\n{\n  enum text_undo_line_type type;\n  int line;\n  int pos;\n  unsigned int length;\n  unsigned int length_allocated;\n  char *value;\n};\n\nstruct text_undo_line_list\n{\n  struct text_undo_line *lines;\n  size_t count;\n  size_t allocated;\n};\n\nstatic void add_text_undo_line(struct text_undo_line_list *list,\n enum text_undo_line_type type, int line, int pos, char *value, size_t length)\n{\n  struct text_undo_line *tl;\n  if(list->count >= list->allocated)\n  {\n    list->allocated = MAX(4, list->allocated);\n    while(list->count >= list->allocated)\n      list->allocated *= 2;\n\n    list->lines = crealloc(list->lines, list->allocated * sizeof(struct text_undo_line));\n  }\n\n  tl = &(list->lines[list->count++]);\n  tl->type = type;\n  tl->line = line;\n  tl->length = length;\n  tl->length_allocated = length + 1;\n  tl->value = cmalloc(length + 1);\n  memcpy(tl->value, value, length);\n  tl->value[length] = '\\0';\n}\n\nstatic void update_text_undo_line(struct text_undo_line_list *list,\n char *value, size_t length)\n{\n  if(list->count > 0)\n  {\n    struct text_undo_line *tl = &(list->lines[list->count - 1]);\n\n    if(tl->length_allocated < length + 1)\n    {\n      while(tl->length_allocated < length + 1)\n        tl->length_allocated *= 2;\n\n      tl->value = crealloc(tl->value, tl->length_allocated);\n    }\n    memcpy(tl->value, value, length);\n    tl->value[length] = '\\0';\n    tl->length = length;\n  }\n}\n\nstatic void handle_text_undo_line(struct text_undo_line_list *list,\n enum text_undo_line_type type, int line, int pos, char *value, size_t length)\n{\n  // If the previous line is TX_SAME_LINE/TX_SAME_BUFFER, this line is the\n  // same, and they both have the same line number, merge this into the\n  // previous line instead of adding a new line.\n  if(list->count > 0 && (type == TX_SAME_LINE || type == TX_SAME_BUFFER))\n  {\n    struct text_undo_line *tl = &(list->lines[list->count - 1]);\n    if(tl->type == type && tl->line == line)\n    {\n      update_text_undo_line(list, value, length);\n      return;\n    }\n  }\n  add_text_undo_line(list, type, line, pos, value, length);\n}\n\nstatic void shrink_text_undo_line_array(struct text_undo_line_list *list)\n{\n  size_t i;\n\n  // Shrink the lines array.\n  if(list->allocated > list->count)\n  {\n    list->lines = crealloc(list->lines, list->count * sizeof(struct text_undo_line));\n    list->allocated = list->count;\n  }\n\n  // Shrink individual line contents.\n  for(i = 0; i < list->count; i++)\n  {\n    struct text_undo_line *pos = &(list->lines[i]);\n    if(pos->length_allocated > pos->length + 1)\n    {\n      pos->value = crealloc(pos->value, pos->length + 1);\n      pos->length_allocated = pos->length + 1;\n      pos->value[pos->length] = '\\0';\n    }\n  }\n}\n\nstatic void free_text_undo_line_array(struct text_undo_line_list *list)\n{\n  size_t i;\n  for(i = 0; i < list->count; i++)\n    free(list->lines[i].value);\n  free(list->lines);\n  list->lines = NULL;\n  list->count = 0;\n  list->allocated = 0;\n}\n\nstruct robot_editor_undo_frame\n{\n  struct undo_frame f;\n  struct robot_editor_context *rstate;\n  struct text_undo_line_list list;\n  // Cursor position info.\n  int prev_line;\n  int prev_col;\n  int current_line;\n  int current_col;\n  // Is this entire frame editing a single line as the active buffer?\n  boolean single_buffer;\n};\n\nstatic void apply_robot_editor_undo(struct undo_frame *f)\n{\n  struct robot_editor_undo_frame *current = (struct robot_editor_undo_frame *)f;\n  struct robot_editor_context *rstate = current->rstate;\n  boolean goto_final_line = true;\n  ssize_t i;\n  int dir;\n\n  if(current->single_buffer)\n    goto_final_line = false;\n\n  // Apply line operations in reverse.\n  for(i = current->list.count - 1; i >= 0; i--)\n  {\n    struct text_undo_line *tl = &(current->list.lines[i]);\n\n    switch(tl->type)\n    {\n      case TX_OLD_LINE:\n        robo_ed_goto_line(rstate, tl->line, 0);\n        dir = (tl->line > rstate->current_line) ? 1 : -1;\n        if(i == 0)\n        {\n          // Hack: undo macro lines and other things without updating them.\n          char tmp[1] = \"\";\n          robo_ed_add_line(rstate, tmp, dir);\n          robo_ed_goto_line(rstate, tl->line, 0);\n          snprintf(rstate->command_buffer, 241, \"%s\", tl->value);\n          rstate->command_buffer[240] = '\\0';\n          goto_final_line = false;\n          break;\n        }\n        robo_ed_add_line(rstate, tl->value, dir);\n        break;\n\n      case TX_NEW_LINE:\n      case TX_SAME_LINE:\n        robo_ed_goto_line(rstate, tl->line, 0);\n        if(rstate->current_line == tl->line)\n          robo_ed_delete_current_line(rstate, -1);\n        break;\n\n      case TX_OLD_BUFFER:\n        if(rstate->current_line != tl->line)\n          robo_ed_goto_line(rstate, tl->line, 0);\n        snprintf(rstate->command_buffer, 241, \"%s\", tl->value);\n        rstate->command_buffer[240] = '\\0';\n        break;\n\n      case TX_INVALID_LINE_TYPE:\n      case TX_SAME_BUFFER:\n        break;\n    }\n  }\n\n  if(goto_final_line)\n  {\n    // Jump to the start line/column of the frame and update this line.\n    robo_ed_goto_line(rstate, current->prev_line, current->prev_col);\n  }\n  else\n  {\n    // Just set the current column within the line.\n    rstate->current_x = current->prev_col;\n  }\n}\n\nstatic void apply_robot_editor_redo(struct undo_frame *f)\n{\n  struct robot_editor_undo_frame *current = (struct robot_editor_undo_frame *)f;\n  struct robot_editor_context *rstate = current->rstate;\n  size_t i;\n  int dir;\n\n  // Apply line operations forward.\n  for(i = 0; i < current->list.count; i++)\n  {\n    struct text_undo_line *tl = &(current->list.lines[i]);\n\n    switch(tl->type)\n    {\n      case TX_OLD_LINE:\n        robo_ed_goto_line(rstate, tl->line, 0);\n        if(rstate->current_line == tl->line)\n          robo_ed_delete_current_line(rstate, -1);\n        break;\n\n      case TX_NEW_LINE:\n      case TX_SAME_LINE:\n        robo_ed_goto_line(rstate, tl->line, 0);\n        dir = (tl->line > rstate->current_line) ? 1 : -1;\n        robo_ed_add_line(rstate, tl->value, dir);\n        break;\n\n      case TX_INVALID_LINE_TYPE:\n      case TX_OLD_BUFFER:\n        break;\n\n      case TX_SAME_BUFFER:\n        if(rstate->current_line != tl->line)\n          robo_ed_goto_line(rstate, tl->line, 0);\n        snprintf(rstate->command_buffer, 241, \"%s\", tl->value);\n        rstate->command_buffer[240] = '\\0';\n        break;\n    }\n  }\n\n  if(!current->single_buffer)\n  {\n    // Jump to the end line/column of the frame and update this line.\n    robo_ed_goto_line(rstate, current->current_line, current->current_col);\n  }\n  else\n  {\n    // Just set the current column within the line.\n    rstate->current_x = current->current_col;\n  }\n}\n\nstatic void apply_robot_editor_update(struct undo_frame *f)\n{\n  struct robot_editor_undo_frame *current = (struct robot_editor_undo_frame *)f;\n  shrink_text_undo_line_array(&current->list);\n}\n\nstatic void apply_robot_editor_clear(struct undo_frame *f)\n{\n  struct robot_editor_undo_frame *current = (struct robot_editor_undo_frame *)f;\n\n  free_text_undo_line_array(&current->list);\n  free(f);\n}\n\nstruct undo_history *construct_robot_editor_undo_history(int max_size)\n{\n  if(max_size)\n  {\n    struct undo_history *h = construct_undo_history(max_size);\n\n    // Note: uses text undo lines instead of standard positions.\n    h->undo_function = apply_robot_editor_undo;\n    h->redo_function = apply_robot_editor_redo;\n    h->update_function = apply_robot_editor_update;\n    h->clear_function = apply_robot_editor_clear;\n    return h;\n  }\n  return NULL;\n}\n\nvoid add_robot_editor_undo_frame(struct undo_history *h, struct robot_editor_context *rstate)\n{\n  if(h)\n  {\n    struct robot_editor_undo_frame *current =\n     cmalloc(sizeof(struct robot_editor_undo_frame));\n\n    add_undo_frame(h, current);\n    current->f.type = POS_FRAME;\n\n    current->rstate = rstate;\n\n    current->list.lines = NULL;\n    current->list.count = 0;\n    current->list.allocated = 0;\n\n    current->prev_line = -1;\n    current->prev_col = -1;\n    current->current_line = -1;\n    current->current_col = -1;\n\n    current->single_buffer = true;\n  }\n}\n\nvoid add_robot_editor_undo_line(struct undo_history *h, enum text_undo_line_type type,\n int line, int pos, char *value, size_t length)\n{\n  if(h && h->current_frame)\n  {\n    struct robot_editor_undo_frame *current =\n     (struct robot_editor_undo_frame *)h->current_frame;\n    struct text_undo_line_list *list = &current->list;\n\n    if(current->prev_line < 0)\n    {\n      current->prev_line = line;\n      current->prev_col = pos;\n    }\n    current->current_line = line;\n    current->current_col = pos;\n\n    if((type != TX_OLD_BUFFER && type != TX_SAME_BUFFER) || line != current->prev_line)\n      current->single_buffer = false;\n\n    handle_text_undo_line(list, type, line, pos, value, length);\n  }\n}\n\nenum text_undo_line_type robot_editor_undo_frame_type(struct undo_history *h)\n{\n  if(h && h->current_frame)\n  {\n    struct robot_editor_undo_frame *current =\n     (struct robot_editor_undo_frame *)h->current_frame;\n    struct text_undo_line_list *list = &current->list;\n\n    if(list->count)\n      return list->lines[list->count - 1].type;\n  }\n  return TX_INVALID_LINE_TYPE;\n}\n"
  },
  {
    "path": "src/editor/undo.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_UNDO_H\n#define __EDITOR_UNDO_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../world_struct.h\"\n#include \"buffer_struct.h\"\n\nstruct undo_history;\n\nboolean apply_undo(struct undo_history *h);\nboolean apply_redo(struct undo_history *h);\nvoid add_undo_position(struct undo_history *h, int x, int y);\nvoid update_undo_frame(struct undo_history *h);\nvoid destruct_undo_history(struct undo_history *h);\n\nstruct undo_history *construct_charset_undo_history(int max_size);\nstruct undo_history *construct_board_undo_history(int max_size);\nstruct undo_history *construct_layer_undo_history(int max_size);\n\nvoid add_charset_undo_frame(struct undo_history *h, int charset, int first_char,\n int width, int height);\n\nvoid add_board_undo_frame(struct world *mzx_world, struct undo_history *h,\n struct buffer_info *buffer, int x, int y);\n\nvoid add_block_undo_frame(struct world *mzx_world, struct undo_history *h,\n struct board *src_board, int src_offset, int width, int height);\n\nvoid add_layer_undo_pos_frame(struct undo_history *h, char *layer_chars,\n char *layer_colors, int layer_width, struct buffer_info *buffer, int x, int y);\n\nvoid add_layer_undo_frame(struct undo_history *h, char *layer_chars,\n char *layer_colors, int layer_width, int layer_offset, int width, int height);\n\nenum text_undo_line_type\n{\n  TX_INVALID_LINE_TYPE,\n  TX_OLD_LINE,\n  TX_NEW_LINE,\n  TX_SAME_LINE,\n  TX_OLD_BUFFER,\n  TX_SAME_BUFFER,\n};\n\nstruct robot_editor_context;\nstruct undo_history *construct_robot_editor_undo_history(int max_size);\nvoid add_robot_editor_undo_frame(struct undo_history *h, struct robot_editor_context *rstate);\nvoid add_robot_editor_undo_line(struct undo_history *h, enum text_undo_line_type type,\n int line, int pos, char *value, size_t length);\nenum text_undo_line_type robot_editor_undo_frame_type(struct undo_history *h);\n\n__M_END_DECLS\n\n#endif // __EDITOR_UNDO_H\n"
  },
  {
    "path": "src/editor/window.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <string.h>\n\n#include \"board.h\"\n#include \"configure.h\"\n#include \"window.h\"\n\n#include \"../board.h\"\n#include \"../core.h\"\n#include \"../event.h\"\n#include \"../extmem.h\"\n#include \"../graphics.h\"\n#include \"../platform.h\"\n#include \"../intake.h\"\n#include \"../window.h\"\n\n#define list_button \" \\x1F \"\n\n#define check_on \"[\\xFB]\"\n#define check_off \"[ ]\"\n#define char_custom '\\x3F'\n\n//Foreground colors that look nice for each background color\nstatic const char fg_per_bk[16] =\n { 15, 15, 15, 15, 15, 15, 15, 0, 15, 15, 0, 0, 15, 15, 0, 0 };\n\n// For list/choice menus-\n\n// List/choice menu colors- Box same as dialog, elements same as\n// inactive question, current element same as active question, pointer\n// same as text, title same as title.\n\n// Put up a box listing choices. Move pointer and enter to pick. choice\n// size is the size in bytes per choice in the array of choices. This size\n// includes null terminator and any junk padding. All work is done on\n// page given, nopage flipping. Choices are drawn using color_string.\n// On ESC, returns negative current choice, unless current choice is 0,\n// then returns -32767. Returns OUT_OF_WIN_MEM if out of screen storage space.\n\n// A progress column is displayed directly to the right of the list, using\n// the following characters. This is mainly used for mouse support.\n\n// Mouse support- Click on a choice to move there auto, click on progress\n// column arrows to move one line, click on progress meter itself to move\n// there percentage-wise. If you click on the current choice, it exits.\n// If you click on a choice to move there, the mouse cursor moves with it.\nint list_menu(const char *const *choices, int choice_size, const char *title,\n int current, int num_choices, int xpos, int ypos)\n{\n  int exit;\n  char key_buffer[64];\n  int mouse_press;\n  int key_position = 0;\n  int last_keypress_time = 0;\n  int ticks;\n  int width = choice_size + 6;\n  int joystick_key;\n  int key;\n  int i;\n  int j;\n\n  cursor_off();\n\n  if(title && strlen(title) > (unsigned int)choice_size)\n    width = (int)strlen(title) + 6;\n\n  save_screen();\n\n  // Display box\n  draw_window_box(xpos, ypos + 2, xpos + width - 1, 22, DI_MAIN, DI_DARK,\n   DI_CORNER, 1, 1);\n\n  if(title)\n  {\n    // Add title\n    write_string(title, xpos + 3, ypos + 2, DI_TITLE, 0);\n    draw_char(' ', DI_TITLE, xpos + 2, ypos + 2);\n    draw_char(' ', DI_TITLE, xpos + 3 + (unsigned int)strlen(title), ypos + 2);\n  }\n\n  // Add pointer\n  draw_char(arrow_char, DI_TEXT, xpos + 2, (ypos >> 1) + 12);\n\n  // Add meter arrows\n  if(num_choices > 1)\n  {\n    draw_char(pc_top_arrow, DI_PCARROW, xpos + 4 + choice_size, ypos + 3);\n    draw_char(pc_bottom_arrow, DI_PCARROW, xpos + 4 + choice_size, 21);\n  }\n\n  do\n  {\n    // Fill over old menu\n    draw_window_box(xpos + 3, ypos + 3, xpos + 3 + choice_size, 21,\n     DI_DARK, DI_MAIN, DI_CORNER, 0, 1);\n\n    // Draw current\n    color_string(choices[current], xpos + 4, (ypos >> 1) + 12, DI_ACTIVE);\n    cursor_hint(xpos + 4, ypos / 2 + 12);\n\n    // Draw above current\n    for(i = 1; i < 9 - (ypos >> 1); i++)\n    {\n      if((current - i) < 0)\n        break;\n      color_string(choices[current - i], xpos + 4,\n       (ypos >> 1) + 12 - i, DI_NONACTIVE);\n    }\n\n    // Draw below current\n    for(i = 1; i < 9 - (ypos >> 1); i++)\n    {\n      if((current + i) >= num_choices)\n        break;\n      color_string(choices[current + i], xpos + 4,\n       (ypos >> 1) + 12 + i, DI_NONACTIVE);\n    }\n\n    // Draw meter (xpos 9 + choice_size, ypos 4 thru 20)\n    if(num_choices > 1)\n    {\n      for(i = 4; i < 21 - ypos; i++)\n        draw_char(pc_filler, DI_PCFILLER, xpos + 4 + choice_size, ypos + i);\n      // Add progress dot\n      i = (current * (16 - ypos)) / (num_choices - 1);\n      draw_char(pc_dot, DI_PCDOT, xpos + 4 + choice_size, ypos + i + 4);\n    }\n\n    update_screen();\n\n    // Get keypress\n    update_event_status_delay();\n\n    // Act upon it\n    key = get_key(keycode_internal_wrt_numlock);\n\n    joystick_key = get_joystick_ui_key();\n    if(joystick_key)\n      key = joystick_key;\n\n    exit = get_exit_status();\n\n    mouse_press = get_mouse_press_ext();\n\n    if(mouse_press && (mouse_press <= MOUSE_BUTTON_RIGHT))\n    {\n      int mouse_x, mouse_y;\n      get_mouse_position(&mouse_x, &mouse_y);\n\n      // Clicking on- List, column, arrow, or nothing?\n      if((mouse_x == xpos + 4 + choice_size) && (mouse_y > ypos + 3) &&\n       (mouse_y < 21))\n      {\n        // Column\n        int ny = mouse_y - (ypos + 4);\n        current = (ny * (num_choices - 1)) / (16 - ypos);\n      }\n      else\n\n      if((mouse_y > ypos + 3) && (mouse_y < 21) &&\n       (mouse_x > xpos + 3) && (mouse_x < xpos + 3 + choice_size))\n      {\n        // List\n        if(mouse_y == ypos + 12)\n          key = IKEY_RETURN;\n\n        current += mouse_y - (ypos + 12);\n        if(current < 0)\n          current = 0;\n        if(current >= num_choices)\n          current = num_choices - 1;\n        // Move mouse with choices\n        warp_mouse(mouse_x, ypos + 12);\n      }\n      else\n\n      if((mouse_x == xpos + 4 + choice_size) && (mouse_y == ypos + 3))\n      {\n        // Top arrow\n        if(current > 0)\n          current--;\n      }\n      else\n\n      if((mouse_x == xpos + 4 + choice_size) && (mouse_y == 21))\n      {\n        // Bottom arrow\n        if(current < (num_choices - 1))\n         current++;\n      }\n    }\n    else\n\n    if(mouse_press == MOUSE_BUTTON_WHEELUP)\n    {\n      key = IKEY_UP;\n    }\n    else\n\n    if(mouse_press == MOUSE_BUTTON_WHEELDOWN)\n    {\n      key = IKEY_DOWN;\n    }\n\n    switch(key)\n    {\n      case IKEY_ESCAPE:\n      {\n        exit = 1;\n        break;\n      }\n\n      case IKEY_BACKSPACE:\n      {\n        // Goto .. if it's there\n        for(i = 0; i < num_choices; i++)\n        {\n          if(!strncmp(choices[i], \" .. \", 4))\n            break;\n        }\n\n        if(i == num_choices)\n          break;\n\n        current = i;\n        restore_screen();\n        return current;\n      }\n\n      case IKEY_RETURN:\n      {\n        // Selected\n        restore_screen();\n        return current;\n      }\n\n      case IKEY_UP:\n      {\n        if(current > 0)\n          current--;\n        break;\n      }\n\n      case IKEY_DOWN:\n      {\n        if(current < (num_choices - 1))\n          current++;\n        break;\n      }\n\n      case IKEY_PAGEUP:\n      {\n        current -= 8;\n        if(current < 0)\n          current = 0;\n        break;\n      }\n\n      case IKEY_PAGEDOWN:\n      {\n        current += 8;\n        if(current >= num_choices)\n          current = num_choices - 1;\n        break;\n      }\n\n      case IKEY_HOME:\n      {\n        current = 0;\n        break;\n      }\n\n      case IKEY_END:\n      {\n        current = num_choices - 1;\n        break;\n      }\n\n      default:\n      {\n        int key_char = get_key(keycode_text_ascii);\n\n        if(!get_alt_status(keycode_internal) &&\n         !get_ctrl_status(keycode_internal) && (key_char >= 32))\n        {\n          ticks = get_ticks();\n          if(((ticks - last_keypress_time) >= TIME_SUSPEND) ||\n           (key_position == 63))\n          {\n            // Go back to zero\n            key_position = 0;\n          }\n          last_keypress_time = ticks;\n\n          key_buffer[key_position] = key_char;\n          key_position++;\n          key_buffer[key_position] = 0;\n\n          if(key_buffer[0] == ' ')\n          {\n            const char *key_buffer_nospace = key_buffer;\n\n            // Skip prefixed spaces for the buffer...\n            while(*key_buffer_nospace == ' ')\n              key_buffer_nospace++;\n\n            if(!key_buffer_nospace[0])\n              break;\n\n            for(i = 0; i < num_choices; i++)\n            {\n              // Skip prefixed spaces for the choices...\n              j = 0;\n              while(choices[i][j] == ' ')\n                j++;\n\n              if(!strncasecmp(choices[i] + j, key_buffer_nospace,\n               strlen(key_buffer_nospace)))\n              {\n                current = i;\n                break;\n              }\n            }\n          }\n          else\n          {\n            for(i = 0; i < num_choices; i++)\n            {\n              if(!strncasecmp(choices[i], key_buffer, key_position))\n              {\n                current = i;\n                break;\n              }\n            }\n          }\n        }\n        break;\n      }\n    }\n\n    // Exit event or Escape\n    if(exit)\n    {\n      restore_screen();\n      cursor_off();\n      if(current == 0)\n        return -32767;\n      else\n        return -current;\n    }\n\n  } while(1);\n}\n\n// Color selection screen colors- see char selection screen.\n\n// Put up a color selection box. Returns selected, or negative selected\n// for ESC, -512 for -0. Returns OUT_OF_WIN_MEM if out of window mem.\n// Display is 16 across, 16 down. All work done on given page. Current\n// uses bit 256 for the wild bit.\n\n// Mouse support- Click on a color to select it. If it is the current color,\n// it exits.\n\nint color_selection(int current, int allow_wild)\n{\n  int x, y;\n  int key;\n  int joystick_key;\n  int selected;\n  int currx, curry;\n  int last_keypress_time = 0;\n  int last_input = 0;\n\n  char palette_char = get_screen_mode() ? CHAR_PAL_SMZX : CHAR_PAL_REG;\n\n  // Save screen\n  save_screen();\n\n  if(get_context(NULL) == CTX_MAIN)\n    set_context(CTX_DIALOG_BOX);\n  else\n    set_context(get_context(NULL));\n\n  // Ensure allow_wild is 0 or 1\n  if(allow_wild)\n    allow_wild = 1;\n\n  // Calculate current x/y\n  if((current & 256) && (allow_wild))\n  {\n    // Wild\n    current -= 256;\n    if(current < 16)\n    {\n      currx = current;\n      curry = 16;\n    }\n    else\n    {\n      curry = current - 16;\n      currx = 16;\n    }\n  }\n  else\n\n  if(current & 256)\n  {\n    // Wild when not allowed to be\n    current -= 256;\n    currx = current & 15;\n    curry = current >> 4;\n  }\n  else\n  {\n    // Normal\n    currx = current & 15;\n    curry = current >> 4;\n  }\n\n  // Draw box\n  draw_window_box(12, 2, 33 + allow_wild, 21 + allow_wild,\n   DI_MAIN, DI_DARK, DI_CORNER, 1, 1);\n  // Add title\n  write_string(\" Select a color \", 14, 2, DI_TITLE, 0);\n\n  do\n  {\n    // Draw outer edge\n    draw_window_box(14, 3, 31 + allow_wild, 20 + allow_wild,\n     DI_DARK, DI_MAIN, DI_CORNER, 0, 0);\n\n    // Clear place for main palette\n    for(y = 0; y < 16; y++)\n      for(x = 0; x < 16; x++)\n        erase_char(x + 15, y + 4);\n\n    select_layer(GAME_UI_LAYER);\n\n    // Draw main palette\n    for(y = 0; y < 16; y++)\n      for(x = 0; x < 16; x++)\n        draw_char_ext(palette_char, x + (y * 16), x + 15, y + 4, PRO_CH, 0);\n\n    select_layer(UI_LAYER);\n\n    // Draw wildcards\n    if(allow_wild)\n    {\n      int wild_pal = get_screen_mode() ? 16 : 0;\n\n      for(x = 1, y = 16; x < 16; x++)\n        draw_char_ext(CHAR_PAL_WILD, x, x + 15, 20, PRO_CH, wild_pal);\n\n      for(y = 0, x = 16; y < 16; y++)\n        draw_char_ext(CHAR_PAL_WILD, fg_per_bk[y] + y * 16,\n         31, y + 4, PRO_CH, wild_pal);\n\n      // x = 0, y = 16\n      draw_char_ext(CHAR_PAL_WILD, 128, 15, 20, PRO_CH, wild_pal);\n\n      // x = 16, y = 16\n      draw_char_ext(CHAR_PAL_WILD, 135, 31, 20, PRO_CH, wild_pal);\n    }\n\n    // Add selection box\n    draw_window_box(currx + 14, curry + 3, currx + 16,\n     curry + 5, DI_MAIN, DI_MAIN, DI_MAIN, 0, 0);\n    cursor_hint(currx + 15, curry + 4);\n\n    // Write number of color\n    if((allow_wild) && ((currx == 16) || (curry == 16)))\n    {\n      // Convert wild\n      if(currx == 16)\n      {\n        if(curry == 16)\n          selected = 288;\n        else\n          selected = 272 + curry;\n      }\n      else\n      {\n        selected = 256 + currx;\n      }\n    }\n    else\n    {\n      selected = currx + (curry * 16);\n    }\n\n    write_number(selected, DI_MAIN, 28 + allow_wild, 21 + allow_wild, 3, 0, 10);\n    update_screen();\n\n    // Get key\n\n    update_event_status_delay();\n    key = get_key(keycode_internal_wrt_numlock);\n\n    joystick_key = get_joystick_ui_key();\n    if(joystick_key)\n      key = joystick_key;\n\n    // Exit event -- mimic Escape\n    if(get_exit_status())\n      key = IKEY_ESCAPE;\n\n    if(get_mouse_press())\n    {\n      int mouse_x, mouse_y;\n      get_mouse_position(&mouse_x, &mouse_y);\n\n      if((mouse_x >= 15) && (mouse_x <= (30 + allow_wild)) &&\n       (mouse_y >= 4) && (mouse_y <= (19 + allow_wild)))\n      {\n        int new_x = mouse_x - 15;\n        int new_y = mouse_y - 4;\n        if((currx == new_x) && (curry == new_y))\n          key = IKEY_RETURN;\n\n        currx = new_x;\n        curry = new_y;\n      }\n    }\n\n    // Process\n    switch(key)\n    {\n      // ESC\n      case IKEY_ESCAPE:\n      case IKEY_SPACE:\n      case IKEY_RETURN:\n      {\n        pop_context();\n        // Selected\n        restore_screen();\n        cursor_off();\n        if((allow_wild) && ((currx == 16) || (curry == 16)))\n        {\n          // Convert wild\n          if(currx == 16)\n          {\n            if(curry == 16)\n              current = 288;\n            else\n              current = 272 + curry;\n          }\n          else\n          {\n            current = 256 + currx;\n          }\n        }\n        else\n        {\n          current = currx + curry * 16;\n        }\n\n        if(key == IKEY_ESCAPE)\n        {\n          if(current == 0)\n            current = -512;\n          else\n            current = -current;\n        }\n        return current;\n      }\n\n      case IKEY_UP:\n      {\n        // Up\n        if(curry > 0)\n          curry--;\n\n        break;\n      }\n\n      case IKEY_DOWN:\n      {\n        // Down\n        if(curry < (15 + allow_wild))\n          curry++;\n\n        break;\n      }\n\n      case IKEY_LEFT:\n      {\n        // Left\n        if(currx > 0)\n          currx--;\n\n        break;\n      }\n\n      case IKEY_RIGHT:\n      {\n        // Right\n        if(currx < (15 + allow_wild))\n          currx++;\n\n        break;\n      }\n\n      case IKEY_HOME:\n      {\n        // Home\n        currx = 0;\n        curry = 0;\n        break;\n      }\n\n      case IKEY_END:\n      {\n        // End\n        currx = 15 + allow_wild;\n        curry = 15 + allow_wild;\n        break;\n      }\n\n      default:\n      {\n        int value = -1;\n\n        if(key >= IKEY_0 && key <= IKEY_9)\n        {\n          value = key - IKEY_0;\n        }\n        else\n\n        if(key >= IKEY_a && key <= IKEY_f)\n        {\n          value = 10 + (key - IKEY_a);\n        }\n\n        if(key == IKEY_SLASH && allow_wild)\n          value = 16;\n\n        if(value >= 0)\n        {\n          int ticks = get_ticks();\n          if(ticks - last_keypress_time >= TIME_SUSPEND)\n          {\n            last_input = 0;\n            currx = 0;\n            curry = 0;\n          }\n\n          switch(last_input)\n          {\n            case 0:\n              curry = value;\n              break;\n\n            case 1:\n              currx = value;\n              break;\n          }\n\n          last_keypress_time = ticks;\n          last_input++;\n        }\n        break;\n      }\n    }\n  } while(1);\n}\n\n// Short function to display a color as a colored box\nvoid draw_color_box(int color, int q_bit, int x, int y, int x_limit)\n{\n  char palette_char = get_screen_mode() ? CHAR_PAL_SMZX : CHAR_PAL_REG;\n\n  // If q_bit is set, there are unknowns\n  if(q_bit)\n  {\n    if(color < 16)\n    {\n      // Unknown background\n      // Use black except for black fg, which uses grey\n      if(color == 0)\n        color = 8;\n\n      if(x < x_limit)\n        draw_char_ext(CHAR_PAL_WILD, color, x, y, PRO_CH, 0);\n\n      if(x + 1 < x_limit)\n        draw_char_ext(CHAR_PAL_REG, color, x + 1, y, PRO_CH, 0);\n\n      if(x + 2 < x_limit)\n        draw_char_ext(CHAR_PAL_WILD, color, x + 2, y, PRO_CH, 0);\n    }\n    else\n\n    if(color < 32)\n    {\n      // Unkown foreground\n      // Use foreground from array\n      color -= 16;\n      color = (color << 4) + fg_per_bk[color];\n\n      if(x < x_limit)\n        draw_char_ext(CHAR_PAL_WILD, color, x, y, PRO_CH, 0);\n\n      if(x + 1 < x_limit)\n        draw_char_ext(CHAR_PAL_WILD, color, x + 1, y, PRO_CH, 0);\n\n      if(x + 2 < x_limit)\n        draw_char_ext(CHAR_PAL_WILD, color, x + 2, y, PRO_CH, 0);\n    }\n    else\n    {\n      // Both unknown\n      if(x < x_limit)\n        draw_char(CHAR_PAL_WILD, 8, x, y);\n\n      if(x + 1 < x_limit)\n        draw_char(CHAR_PAL_WILD, 135, x + 1, y);\n\n      if(x + 2 < x_limit)\n        draw_char(CHAR_PAL_WILD, 127, x + 2, y);\n    }\n  }\n  else\n  {\n    // To respect SMZX, this needs to draw on the game UI layer.\n    // If a color box is ever planned to be drawn NOT on the UI layer,\n    // this needs to change.\n\n    if(x < x_limit)\n    {\n      erase_char(x, y);\n      select_layer(GAME_UI_LAYER);\n      draw_char_ext(0, color, x, y, PRO_CH, 0);\n      select_layer(UI_LAYER);\n    }\n\n    if(x + 1 < x_limit)\n    {\n      erase_char(x+1, y);\n      select_layer(GAME_UI_LAYER);\n      draw_char_ext(palette_char, color, x + 1, y, PRO_CH, 0);\n      select_layer(UI_LAYER);\n    }\n\n    if(x + 2 < x_limit)\n    {\n      erase_char(x+2, y);\n      select_layer(GAME_UI_LAYER);\n      draw_char_ext(0, color, x + 2, y, PRO_CH, 0);\n      select_layer(UI_LAYER);\n    }\n  }\n}\n\nstatic void draw_check_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int color, int active)\n{\n  struct check_box *src = (struct check_box *)e;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n  int i;\n\n  for(i = 0; i < src->num_choices; i++)\n  {\n    if(src->results[i])\n      write_string(check_on, x, y + i, DI_NONACTIVE, 0);\n    else\n      write_string(check_off, x, y + i, DI_NONACTIVE, 0);\n\n    write_string(src->choices[i], x + 4, y + i,\n     DI_NONACTIVE, 0);\n  }\n\n  if(active)\n  {\n    color_line(src->max_length + 4, x, y + src->current_choice,\n     DI_ACTIVE);\n    cursor_hint(x + 1, y + src->current_choice);\n  }\n}\n\nstatic void draw_char_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int color, int active)\n{\n  struct char_box *src = (struct char_box *)e;\n  unsigned char result = *(src->result);\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n  int question_len = (int)strlen(src->question) + di->pad_space;\n\n  write_string(src->question, x, y, color, 0);\n  if(active)\n    cursor_hint(x, y);\n\n  // Special case: display for char ID value 255 custom behavior\n  if((result == 255) && !(src->allow_char_255))\n  {\n    draw_char(char_custom, 0x05, x + question_len + 0, y);\n    draw_char(char_custom, 0x0D, x + question_len + 1, y);\n    draw_char(char_custom, 0x05, x + question_len + 2, y);\n  }\n\n  else\n  {\n    draw_char_ext(*(src->result), DI_CHAR,\n     x + question_len + 1, y, 0, 16);\n    draw_char(' ', DI_CHAR, x + question_len, y);\n    draw_char(' ', DI_CHAR, x + question_len + 2, y);\n  }\n}\n\nstatic void draw_color_box_element(struct world *mzx_world, struct dialog *di,\n struct element *e, int color, int active)\n{\n  struct color_box *src = (struct color_box *)e;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n  int current_color = *(src->result);\n\n  write_string(src->question, x, y, color, 0);\n  draw_color_box(current_color & 0xFF, current_color >> 8,\n   x + (int)strlen(src->question) + di->pad_space, y, 80);\n\n  if(active)\n    cursor_hint(x, y);\n}\n\nstatic void draw_board_list(struct world *mzx_world, struct dialog *di,\n struct element *e, int color, int active)\n{\n  struct board_list *src = (struct board_list *)e;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n  int current_board = *(src->result);\n\n  struct board *src_board;\n  char board_name[BOARD_NAME_SIZE] = \"(none)\";\n\n  if(current_board || (!src->board_zero_as_none))\n  {\n    if(current_board <= mzx_world->num_boards)\n    {\n      src_board = mzx_world->board_list[current_board];\n      snprintf(board_name, BOARD_NAME_SIZE, \"%s\", src_board->board_name);\n    }\n    else\n    {\n      snprintf(board_name, BOARD_NAME_SIZE, \"(no board)\");\n    }\n    board_name[BOARD_NAME_SIZE - 1] = '\\0';\n  }\n\n  write_string(src->title, x, y, color, 0);\n  fill_line(BOARD_NAME_SIZE + 1, x, y + 1, 32, DI_LIST);\n  if(active)\n    cursor_hint(x, y);\n\n  color_string(board_name, x + 1, y + 1, DI_LIST);\n\n  // Draw button\n  write_string(list_button, x + BOARD_NAME_SIZE + 1, y + 1,\n   DI_ARROWBUTTON, 0);\n}\n\nstatic int key_check_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int key)\n{\n  struct check_box *src = (struct check_box *)e;\n\n  switch(key)\n  {\n    case IKEY_SPACE:\n    case IKEY_RETURN:\n    {\n      src->results[src->current_choice] ^= 1;\n      break;\n    }\n\n    case IKEY_PAGEUP:\n    {\n      src->current_choice = 0;\n      break;\n    }\n\n    case IKEY_LEFT:\n    case IKEY_UP:\n    {\n      if(src->current_choice)\n        src->current_choice--;\n\n      break;\n    }\n\n    case IKEY_PAGEDOWN:\n    {\n      src->current_choice = src->num_choices - 1;\n      break;\n    }\n\n    case IKEY_RIGHT:\n    case IKEY_DOWN:\n    {\n      if(src->current_choice < (src->num_choices - 1))\n        src->current_choice++;\n\n      break;\n    }\n\n    default:\n    {\n      return key;\n    }\n  }\n\n  return 0;\n}\n\nstatic int key_char_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int key)\n{\n  struct char_box *src = (struct char_box *)e;\n\n  switch(key)\n  {\n    case IKEY_SPACE:\n    case IKEY_RETURN:\n    {\n      int current_char =\n       char_selection_ext(*(src->result), src->allow_char_255,\n        NULL, NULL, NULL, -1);\n\n      if((current_char == 255) && !(src->allow_char_255))\n      {\n        // Don't change the char if the user cancels.\n        if(confirm(mzx_world,\n         \"Use param for the char of this type (like CustomBlock)?\"))\n          current_char = -1;\n      }\n\n      if(current_char >= 0)\n        *(src->result) = current_char;\n      break;\n    }\n\n    default:\n    {\n      int key_char = get_key(keycode_text_ascii);\n\n      if(key_char >= 32)\n      {\n        *(src->result) = key_char;\n        break;\n      }\n\n      return key;\n    }\n  }\n\n  return 0;\n}\n\nstatic int key_color_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int key)\n{\n  struct color_box *src = (struct color_box *)e;\n\n  switch(key)\n  {\n    case IKEY_SPACE:\n    case IKEY_RETURN:\n    {\n      int current_color =\n       color_selection(*(src->result), src->allow_wildcard);\n\n      if(current_color >= 0)\n        *(src->result) = current_color;\n      break;\n    }\n\n    default:\n    {\n      return key;\n    }\n  }\n\n  return 0;\n}\n\nstatic int key_board_list(struct world *mzx_world, struct dialog *di,\n struct element *e, int key)\n{\n  struct board_list *src = (struct board_list *)e;\n\n  switch(key)\n  {\n    case IKEY_SPACE:\n    case IKEY_RETURN:\n    {\n      int current_board =\n       choose_board(mzx_world, *(src->result),\n       src->title, src->board_zero_as_none);\n\n      if(current_board >= 0)\n        *(src->result) = current_board;\n\n      break;\n    }\n\n    default:\n    {\n      return key;\n    }\n  }\n\n  return 0;\n}\n\nstatic int click_check_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y,\n int new_active)\n{\n  struct check_box *src = (struct check_box *)e;\n\n  src->current_choice = mouse_y;\n  src->results[src->current_choice] ^= 1;\n\n  return 0;\n}\n\nstatic int click_char_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y,\n int new_active)\n{\n  return IKEY_RETURN;\n}\n\nstatic int click_color_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y,\n int new_active)\n{\n  return IKEY_RETURN;\n}\n\nstatic int click_board_list(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y,\n int new_active)\n{\n  return IKEY_RETURN;\n}\n\nstruct element *construct_check_box(int x, int y, const char **choices,\n int num_choices, int max_length, int *results)\n{\n  struct check_box *src = cmalloc(sizeof(struct check_box));\n  src->current_choice = 0;\n  src->choices = choices;\n  src->num_choices = num_choices;\n  src->results = results;\n  src->max_length = max_length;\n  construct_element(&(src->e), x, y, max_length + 4, num_choices,\n   draw_check_box, key_check_box, click_check_box, NULL, NULL);\n\n  return (struct element *)src;\n}\n\nstruct element *construct_char_box(int x, int y, const char *question,\n int allow_char_255, int *result)\n{\n  struct char_box *src = cmalloc(sizeof(struct char_box));\n  src->question = question;\n  src->allow_char_255 = allow_char_255;\n  src->result = result;\n  construct_element(&(src->e), x, y, (int)strlen(question) + 4,\n   1, draw_char_box, key_char_box, click_char_box, NULL, NULL);\n\n  return (struct element *)src;\n}\n\nstruct element *construct_color_box(int x, int y,\n const char *question, int allow_wildcard, int *result)\n{\n  struct color_box *src = cmalloc(sizeof(struct color_box));\n  src->question = question;\n  src->allow_wildcard = allow_wildcard;\n  src->result = result;\n  construct_element(&(src->e), x, y, (int)strlen(question) + 4,\n   1, draw_color_box_element, key_color_box, click_color_box,\n   NULL, NULL);\n\n  return (struct element *)src;\n}\n\nstruct element *construct_board_list(int x, int y,\n const char *title, int board_zero_as_none, int *result)\n{\n  struct board_list *src = cmalloc(sizeof(struct board_list));\n  src->title = title;\n  src->board_zero_as_none = board_zero_as_none;\n  src->result = result;\n  construct_element(&(src->e), x, y, BOARD_NAME_SIZE + 3,\n   2, draw_board_list, key_board_list, click_board_list,\n   NULL, NULL);\n\n  return (struct element *)src;\n}\n\nint add_board(struct world *mzx_world, int current)\n{\n  struct board *new_board;\n  char name[BOARD_NAME_SIZE];\n  memset(name, 0, BOARD_NAME_SIZE);\n\n  if(input_window(mzx_world, \"Name for new board:\", name, BOARD_NAME_SIZE - 1))\n    return -1;\n\n  if(mzx_world->num_boards == mzx_world->num_boards_allocated)\n  {\n    mzx_world->num_boards_allocated *= 2;\n    mzx_world->board_list = crealloc(mzx_world->board_list,\n     mzx_world->num_boards_allocated * sizeof(struct board *));\n  }\n\n  mzx_world->num_boards++;\n  new_board = create_blank_board(get_editor_config());\n  mzx_world->board_list[current] = new_board;\n  memcpy(new_board->board_name, name, BOARD_NAME_SIZE);\n\n  // Link global robot!\n  new_board->robot_list[0] = &mzx_world->global_robot;\n\n  return current;\n}\n\n// Shell for list_menu()\nint choose_board(struct world *mzx_world, int current, const char *title,\n int board0_none)\n{\n  char **board_names = ccalloc(mzx_world->num_boards + 1, sizeof(char *));\n  int num_boards = mzx_world->num_boards;\n  int i;\n\n  // Go through - blank boards get a (no board) marker. t2 keeps track\n  // of the number of boards.\n  for(i = 0; i < num_boards; i++)\n  {\n    board_names[i] = cmalloc(BOARD_NAME_SIZE);\n\n    if(mzx_world->board_list[i] == NULL)\n      snprintf(board_names[i], BOARD_NAME_SIZE, \"(no board)\");\n    else\n      snprintf(board_names[i], BOARD_NAME_SIZE, \"%s\",\n       (mzx_world->board_list[i])->board_name);\n\n    board_names[i][BOARD_NAME_SIZE - 1] = '\\0';\n  }\n\n  board_names[i] = cmalloc(BOARD_NAME_SIZE);\n\n  if((current < 0) || (current >= mzx_world->num_boards))\n    current = 0;\n\n  // Add (new board) to bottom\n  if(i < MAX_BOARDS)\n  {\n    snprintf(board_names[i], BOARD_NAME_SIZE, \"(add board)\");\n    board_names[i][BOARD_NAME_SIZE - 1] = '\\0';\n    i++;\n  }\n\n  // Change top to (none) if needed\n  if(board0_none)\n  {\n    snprintf(board_names[0], BOARD_NAME_SIZE, \"(no board)\");\n    board_names[0][BOARD_NAME_SIZE - 1] = '\\0';\n  }\n\n  // Run the list_menu()\n  current = list_menu((const char **)board_names,\n   BOARD_NAME_SIZE, title, current, i, 27, 0);\n\n  // New board? (if select no board or add board)\n  if((current == num_boards) ||\n   ((current >= 0) && (mzx_world->board_list[current] == NULL)))\n  {\n    current = add_board(mzx_world, current);\n    if(current >= 0)\n    {\n      // This board is expected to reside in extra memory (if applicable).\n      store_board_to_extram(mzx_world->board_list[current]);\n    }\n  }\n\n  for(i = 0; i < num_boards + 1; i++)\n  {\n    free(board_names[i]);\n  }\n\n  free(board_names);\n\n  if(board0_none && (current == 0))\n  {\n    return NO_BOARD;\n  }\n\n  return current;\n}\n\nint choose_file(struct world *mzx_world, const char *const *wildcards,\n char *ret, const char *title, enum allow_dirs allow_dirs)\n{\n  return file_manager(mzx_world, wildcards, NULL, ret, title, allow_dirs,\n   NO_NEW_FILES, NULL, 0, 0);\n}\n"
  },
  {
    "path": "src/editor/window.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_WINDOW_H\n#define __EDITOR_WINDOW_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../window.h\"\n#include \"../world_struct.h\"\n\n#define CHAR_PAL_REG    254\n#define CHAR_PAL_SMZX   184\n#define CHAR_PAL_WILD    63\n\n#define CHAR_SMZX_C0      0\n#define CHAR_SMZX_C1    182\n#define CHAR_SMZX_C2    183\n#define CHAR_SMZX_C3    219\n\nEDITOR_LIBSPEC int list_menu(const char *const *choices, int choice_size,\n const char *title, int current, int num_choices, int xpos, int ypos);\nint color_selection(int current, int allow_wild);\nvoid draw_color_box(int color, int q_bit, int x, int y, int x_limit);\nstruct element *construct_check_box(int x, int y, const char **choices,\n int num_choices, int max_length, int *results);\nstruct element *construct_char_box(int x, int y, const char *question,\n int allow_char_255, int *result);\nstruct element *construct_color_box(int x, int y,\n const char *question, int allow_wildcard, int *result);\nstruct element *construct_board_list(int x, int y,\n const char *title, int board_zero_as_none, int *result);\nint add_board(struct world *mzx_world, int current);\nint choose_board(struct world *mzx_world, int current, const char *title,\n int board0_none);\nint choose_file(struct world *mzx_world, const char *const *wildcards,\n char *ret, const char *title, enum allow_dirs allow_dirs);\n\n__M_END_DECLS\n\n#endif // __EDITOR_WINDOW_H\n"
  },
  {
    "path": "src/editor/world.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"world.h\"\n\n#include \"../board.h\"\n#include \"../const.h\"\n#include \"../error.h\"\n#include \"../extmem.h\"\n#include \"../graphics.h\"\n#include \"../robot.h\"\n#include \"../window.h\"\n#include \"../world.h\"\n#include \"../legacy_board.h\"\n#include \"../idput.h\"\n#include \"../util.h\"\n#include \"../io/memfile.h\"\n#include \"../io/vio.h\"\n#include \"../io/zip.h\"\n\n#include \"../world_format.h\"\n\n#include \"board.h\"\n#include \"configure.h\"\n#include \"robot.h\"\n\n#include <string.h>\n\nstatic const unsigned char def_id_chars[ID_CHARS_SIZE] =\n{\n  /* id_chars */\n  32,178,219,6,0,255,177,255,233,254,255,254,255,178,177,176, /* 0-15 */\n  254,255,0,0,176,24,25,26,27,0,0,160,4,4,3,9,                /* 16-31 */\n  150,7,176,0,11,0,177,12,10,0,0,162,161,0,0,22,              /* 32-47 */\n  196,0,7,255,255,255,255,159,0,18,29,0,206,0,0,0,            /* 48-63 */\n  '?',178,0,151,152,153,154,32,0,42,0,0,255,255,0,0,          /* 64-79 */\n  235,236,5,42,2,234,21,224,127,149,5,227,0,0,172,173,        /* 80-95 */\n  '?',0, /* 96-97 */\n  '?','?','?','?','?','?','?','?', /* 98-121 */\n  '?','?','?','?','?','?','?','?',\n  '?','?','?','?','?','?','?','?',\n  0,0,0,226,232,0, /* 122-127 */\n  /* thin_line */\n  249,179,179,179, /* None, N, S, NS */\n  196,192,218,195, /* E, NE, SE, NSE */\n  196,217,191,180, /* W, NW, SW, NSW */\n  196,193,194,197, /* EW, NEW, SEW, NSEW */\n  /* thick_line */\n  249,186,186,186, /* None, N, S, NS */\n  205,200,201,204, /* E, NE, SE, NSE */\n  205,188,187,185, /* W, NW, SW, NSW */\n  205,202,203,206, /* EW, NEW, SEW, NSEW */\n  /* ice_anim */\n  32,252,253,255, /* Ice animation table */\n  /* lava_anim */\n  176,177,178, /* Lava animation table */\n  /* low_ammo, hi_ammo */\n  163, /* < 10 ammunition pic */\n  164, /* > 9 ammunition pic */\n  /* lit_bomb */\n  171,170,169,168,167,166,165, /* Lit bomb animation */\n  /* energizer_glow */\n  1,9,3,11,15,11,3,9, /* Energizer Glow */\n  /* explosion_colors */\n  239,206,76,4, /* Explosion color table */\n  /* horiz_door, vert_door */\n  196, /* Horizontal door pic */\n  179, /* Vertical door pic */\n  /* cw_anim, ccw_anim */\n  47,196,92,179, /* CW animation table */\n  47,179,92,196, /* CCW animation table */\n  /* open_door */\n  47,47, /* Open 1/2 of type 0 */\n  92,92, /* Open 1/2 of type 1 */\n  92,92, /* Open 1/2 of type 2 */\n  47,47, /* Open 1/2 of type 3 */\n  179,196,179,196,179,196,179,196, /* Open full of all types */\n  179,196,179,196,179,196,179,196, /* Open full of all types */\n  47,47, /* Close 1/2 of type 0 */\n  92,92, /* Close 1/2 of type 1 */\n  92,92, /* Close 1/2 of type 2 */\n  47,47, /* Close 1/2 of type 3 */\n  /* transport_anims */\n  45,94,126,94, /* North */\n  86,118,95,118, /* South */\n  93,41,62,41, /* East */\n  91,40,60,40, /* West */\n  94,62,118,60, /* All directions */\n  /* thick_arrow, thin_arrow */\n  30,31,16,17, /* Thick arrows (pusher-style) NSEW */\n  24,25,26,27, /* Thin arrows (gun-style) NSEW */\n  /* horiz_lazer, vert_lazer */\n  130,196,131,196, /* Horizontal Lazer Anim Table */\n  128,179,129,179, /* Vertical Lazer Anim Table */\n  /* fire_anim, fire_colors */\n  177,178,178,178,177,176, /* Fire animation */\n  4,6,12,14,12,6, /* Fire colors */\n  /* life_anim, life_colors */\n  155,156,157,158, /* Free life animation */\n  15,11,3,11, /* Free life colors */\n  /* ricochet_panels */\n  28,23, /* Ricochet pics */\n  /* mine_anim */\n  143,144, /* Mine animation */\n  /* shooting_fire_anim, shooting_fire_colors */\n  15,42, /* Shooting Fire Anim */\n  12,14, /* Shooting Fire Colors */\n  /* seeker_anim, seeker_colors */\n  '/','-','\\\\','|', /* Seeker animation */\n  11,14,10,12, /* Seeker colors */\n  /* whirlpool_glow */\n  11,3,9,15, /* Whirlpool colors (this ends at 306 bytes, where ver */\n  /* 1.02 used to end) */\n  /* bullet_char */\n  145,146,147,148, /* Player */\n  145,146,147,148, /* Neutral */\n  145,146,147,148, /* Enemy */\n  /* player_char */\n  2, 2, 2, 2, /*N S E W */\n  /* player_color */\n  27,\n};\n\nstatic const unsigned char def_missile_color = 8;\n\nstatic const unsigned char def_bullet_color[ID_BULLET_COLOR_SIZE] =\n{\n  15, /* Player */\n  15, /* Neutral */\n  15, /* Enemy */\n};\n\nstatic const unsigned char def_id_dmg[ID_DMG_SIZE] =\n{\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100,  0, 0, 0, 0, 0, /* 16-31 */\n  0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 32-47 */\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 10, 5, 5, /* 48-63 */\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 10, 10, /* 64-79 */\n  10, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 10, 10, /* 80-95 */\n  0, 0, /* 96-97 */\n  0, 0, 0, 0, 0, 0, 0, 0, /* 98-121 */\n  0, 0, 0, 0, 0, 0, 0, 0,\n  0, 0, 0, 0, 0, 0, 0, 0,\n  0, 0, 0, 0, 0, 0  /* 122-127 */\n};\n\n\nstatic void append_world_refactor_board(struct world *mzx_world,\n struct board *cur_board, int old_num_boards)\n{\n  int board_width = cur_board->board_width;\n  int board_height = cur_board->board_height;\n  char *level_id = cur_board->level_id;\n  char *level_param = cur_board->level_param;\n  int board_dir;\n  int offset;\n  int d_flag;\n  int i;\n\n  // Offset all entrances and exits\n  for(offset = 0; offset < board_width * board_height; offset++)\n  {\n    d_flag = flags[(int)level_id[offset]];\n    board_dir = level_param[offset];\n\n    if((d_flag & A_ENTRANCE) && (board_dir != NO_BOARD))\n    {\n      if(board_dir + old_num_boards < mzx_world->num_boards)\n        level_param[offset] += old_num_boards;\n      else\n        level_param[offset] = NO_BOARD;\n    }\n  }\n\n  for(i = 0; i < 4; i++)\n  {\n    board_dir = cur_board->board_dir[i];\n    if(board_dir != NO_BOARD)\n      board_dir += old_num_boards;\n\n    if(board_dir >= mzx_world->num_boards)\n      board_dir = NO_BOARD;\n\n    cur_board->board_dir[i] = board_dir;\n  }\n}\n\n\nstatic boolean append_world_legacy(struct world *mzx_world, vfile *vf,\n int file_version)\n{\n  int i, j;\n  int num_boards, old_num_boards = mzx_world->num_boards;\n  int board_names_pos;\n  struct board *cur_board;\n  unsigned int *board_offsets;\n  unsigned int *board_sizes;\n\n  // The board list is 100% compatible between 1.x and 2.x, so the only\n  // differences that need to be handled here are the location and custom SFX.\n  if(file_version >= V200)\n  {\n    vfseek(vf, 4234, SEEK_SET);\n  }\n  else\n  {\n    int protection_method = 0;\n\n    vfseek(vf, LEGACY_BOARD_NAME_SIZE, SEEK_SET);\n    protection_method = vfgetc(vf);\n\n    vfseek(vf, 4009, SEEK_SET);\n    if(protection_method)\n      vfseek(vf, 16, SEEK_CUR);\n  }\n\n  num_boards = vfgetc(vf);\n\n  if(num_boards == 0 && file_version >= V200)\n  {\n    int sfx_size;\n    // Sfx skip word size\n    vfseek(vf, 2, SEEK_CUR);\n\n    // Skip sfx\n    for(i = 0; i < NUM_BUILTIN_SFX; i++)\n    {\n      sfx_size = vfgetc(vf);\n      vfseek(vf, sfx_size, SEEK_CUR);\n    }\n    num_boards = vfgetc(vf);\n  }\n\n  // Shouldn't happen...\n  if(num_boards < 1)\n    return false;\n\n  // Skip the names for now\n  board_names_pos = vftell(vf);\n  vfseek(vf, num_boards * BOARD_NAME_SIZE, SEEK_CUR);\n\n  if(num_boards + old_num_boards >= MAX_BOARDS)\n    num_boards = MAX_BOARDS - old_num_boards;\n\n  mzx_world->num_boards += num_boards;\n  mzx_world->num_boards_allocated += num_boards;\n  mzx_world->board_list =\n   (struct board **)crealloc(mzx_world->board_list,\n    sizeof(struct board *) * (old_num_boards + num_boards));\n\n  // Read the board offsets/sizes preemptively to reduce the amount of seeking.\n  board_offsets = (unsigned int *)cmalloc(sizeof(int) * num_boards);\n  board_sizes = (unsigned int *)cmalloc(sizeof(int) * num_boards);\n  for(i = 0; i < num_boards; i++)\n  {\n    board_sizes[i] = vfgetd(vf);\n    board_offsets[i] = vfgetd(vf);\n  }\n\n  // Append boards\n  for(i = 0, j = old_num_boards; j < old_num_boards + num_boards; i++, j++)\n  {\n    cur_board =\n     legacy_load_board_allocate(mzx_world, vf, board_offsets[i], board_sizes[i],\n      false, file_version);\n\n    mzx_world->board_list[j] = cur_board;\n    if(cur_board)\n    {\n      // Also patch a pointer to the global robot\n      if(cur_board->robot_list)\n      {\n        cur_board->robot_list[0] = &mzx_world->global_robot;\n      }\n      // Also optimize out null objects\n      optimize_null_objects(cur_board);\n\n      // Fix exits\n      append_world_refactor_board(mzx_world, cur_board, old_num_boards);\n\n      store_board_to_extram(cur_board);\n    }\n  }\n\n  free(board_offsets);\n  free(board_sizes);\n\n  // Go back to where the names are\n  vfseek(vf, board_names_pos, SEEK_SET);\n  for(i = old_num_boards; i < old_num_boards + num_boards; i++)\n  {\n    char ignore[BOARD_NAME_SIZE];\n    char *dest = ignore;\n\n    cur_board = mzx_world->board_list[i];\n    if(cur_board)\n      dest = cur_board->board_name;\n\n    if(!vfread(dest, BOARD_NAME_SIZE, 1, vf))\n      dest[0] = '\\0';\n\n    dest[BOARD_NAME_SIZE - 1] = '\\0';\n  }\n\n  vfclose(vf);\n  return true;\n}\n\n\nstatic int append_world_zip_get_num_boards(const void *buffer, int buf_size)\n{\n  struct memfile mf;\n  struct memfile prop;\n  int ident;\n  int size;\n\n  mfopen(buffer, buf_size, &mf);\n\n  while(next_prop(&prop, &ident, &size, &mf))\n    if(ident == WPROP_NUM_BOARDS)\n      return load_prop_int_u(&prop, 0, MAX_BOARDS);\n\n  return 0;\n}\n\n\nstatic boolean append_world_zip(struct world *mzx_world, struct zip_archive *zp,\n int file_version)\n{\n  struct board *cur_board;\n\n  unsigned int file_id;\n  unsigned int board_id;\n\n  int old_num_boards = mzx_world->num_boards;\n  int num_boards;\n  int result;\n  int i;\n\n  // Since we went through try_load_world, the world info has been loaded\n  // and is valid. This is all we need it for, so throw it away afterward.\n  num_boards = append_world_zip_get_num_boards(\n   mzx_world->raw_world_info, mzx_world->raw_world_info_size);\n\n  free(mzx_world->raw_world_info);\n  mzx_world->raw_world_info = NULL;\n  mzx_world->raw_world_info_size = 0;\n\n  if(num_boards + old_num_boards >= MAX_BOARDS)\n    num_boards = MAX_BOARDS - old_num_boards;\n\n  mzx_world->num_boards += num_boards;\n  mzx_world->num_boards_allocated += num_boards;\n  mzx_world->board_list =\n   (struct board **)crealloc(mzx_world->board_list,\n    sizeof(struct board *) * (old_num_boards + num_boards));\n\n  for(i = old_num_boards; i < old_num_boards + num_boards; i++)\n  {\n    while(1)\n    {\n      result = zip_get_next_mzx_file_id(zp, &file_id, &board_id, NULL);\n\n      if(result != ZIP_SUCCESS)\n      {\n        mzx_world->board_list[i] = NULL;\n        break;\n      }\n      else\n\n      if(file_id != FILE_ID_BOARD_INFO)\n      {\n        zip_skip_file(zp);\n        continue;\n      }\n\n      else\n      {\n        cur_board = load_board_allocate(mzx_world, zp, 0,\n         file_version, board_id);\n\n        mzx_world->board_list[i] = cur_board;\n\n        if(cur_board)\n        {\n          // Fix exits.\n          append_world_refactor_board(mzx_world, cur_board, old_num_boards);\n\n          store_board_to_extram(cur_board);\n        }\n        break;\n      }\n    }\n  }\n\n  zip_close(zp, NULL);\n  return true;\n}\n\n\nboolean append_world(struct world *mzx_world, const char *file)\n{\n  char ignore[BOARD_NAME_SIZE];\n  int file_version;\n\n  boolean ret = false;\n\n  struct zip_archive *zp;\n  vfile *vf;\n\n  try_load_world(mzx_world, &zp, &vf, file, false, &file_version, ignore);\n\n  if(zp)\n  {\n    ret = append_world_zip(mzx_world, zp, file_version);\n  }\n  else\n\n  if(vf)\n  {\n    ret = append_world_legacy(mzx_world, vf, file_version);\n  }\n\n  // Remove any null boards\n  optimize_null_boards(mzx_world);\n\n  // Make sure update_done is adequately sized\n  set_update_done(mzx_world);\n\n  return ret;\n}\n\n\n// Create a new, blank, world, suitable for editing.\nvoid create_blank_world(struct world *mzx_world)\n{\n  // Make default global data\n  // Make a blank board\n  int i;\n\n  mzx_world->version = MZX_VERSION;\n  mzx_world->active = 1;\n\n  mzx_world->num_boards = 1;\n  mzx_world->num_boards_allocated = 1;\n  mzx_world->board_list = cmalloc(sizeof(struct board *));\n  mzx_world->board_list[0] = create_blank_board(get_editor_config());\n  mzx_world->current_board_id = 0;\n  mzx_world->current_board = mzx_world->board_list[0];\n\n  mzx_world->edge_color = 8;\n  mzx_world->first_board = 0;\n  mzx_world->endgame_board = NO_ENDGAME_BOARD;\n  mzx_world->death_board = NO_DEATH_BOARD;\n  mzx_world->endgame_x = 0;\n  mzx_world->endgame_y = 0;\n  mzx_world->game_over_sfx = 1;\n  mzx_world->death_x = 0;\n  mzx_world->death_y = 0;\n  mzx_world->starting_lives = 7;\n  mzx_world->lives_limit = 99;\n  mzx_world->starting_health = 100;\n  mzx_world->health_limit = 200;\n  mzx_world->enemy_hurt_enemy = 0;\n  mzx_world->clear_on_exit = 0;\n  mzx_world->only_from_swap = 0;\n  memcpy(id_chars, def_id_chars, ID_CHARS_SIZE);\n  missile_color = def_missile_color;\n  memcpy(bullet_color, def_bullet_color, ID_BULLET_COLOR_SIZE);\n  memcpy(id_dmg, def_id_dmg, ID_DMG_SIZE);\n\n  create_blank_robot_direct(&mzx_world->global_robot, -1, -1);\n  mzx_world->current_board->robot_list[0] = &mzx_world->global_robot;\n\n  for(i = 0; i < 6; i++)\n  {\n    mzx_world->status_counters_shown[i][0] = 0;\n  }\n\n  memset(mzx_world->name, 0, sizeof(mzx_world->name));\n\n  // Defaults for things set by load_world\n  sfx_free(&mzx_world->custom_sfx);\n  mzx_world->max_samples = -1;\n  mzx_world->joystick_simulate_keys = true;\n\n  set_update_done(mzx_world);\n\n  set_screen_mode(0);\n  smzx_palette_loaded(false);\n  set_palette_intensity(100);\n\n  ec_clear_set();\n  ec_load_mzx();\n  default_palette();\n  default_vlayer(mzx_world);\n  default_global_data(mzx_world);\n}\n\nvoid set_update_done_current(struct world *mzx_world)\n{\n  struct board *current_board = mzx_world->current_board;\n  int size = current_board->board_width * current_board->board_height;\n\n  if(size > mzx_world->update_done_size)\n  {\n    if(mzx_world->update_done == NULL)\n    {\n     mzx_world->update_done = cmalloc(size);\n    }\n    else\n    {\n      mzx_world->update_done =\n       crealloc(mzx_world->update_done, size);\n    }\n\n    mzx_world->update_done_size = size;\n  }\n}\n\n/**\n * Move the current board to a new position in the board list.\n * The current board is updated afterward if necessary.\n */\nvoid move_current_board(struct world *mzx_world, int new_position)\n{\n  int old_position = mzx_world->current_board_id;\n\n  if(new_position != old_position)\n  {\n    int num_boards = mzx_world->num_boards;\n    struct board **board_list = mzx_world->board_list;\n    struct board **new_board_list = ccalloc(num_boards, sizeof(struct board *));\n    int *board_id_translation_list = ccalloc(num_boards, sizeof(int));\n    int i;\n    int j;\n\n    // Copy the list and shift all boards necessary.\n    for(i = 0; i < num_boards; i++)\n    {\n      j = i;\n\n      if(new_position > old_position)\n      {\n        // Move the board back.\n        if(i > old_position && i <= new_position)\n          j = i - 1;\n      }\n      else\n\n      if(new_position < old_position)\n      {\n        // Move the board forward.\n        if(i >= new_position && i < old_position)\n          j = i + 1;\n      }\n\n      // Ignore the current board for now...\n      if(i != old_position)\n      {\n        board_id_translation_list[i] = j;\n        new_board_list[j] = board_list[i];\n      }\n    }\n\n    // Insert the current board at its new position.\n    board_id_translation_list[old_position] = new_position;\n    new_board_list[new_position] = board_list[old_position];\n\n    refactor_board_list(mzx_world, new_board_list, num_boards,\n     board_id_translation_list);\n\n    free(board_id_translation_list);\n  }\n}\n\nchar get_default_id_char(int id)\n{\n  return def_id_chars[id];\n}\n"
  },
  {
    "path": "src/editor/world.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EDITOR_WORLD_H\n#define __EDITOR_WORLD_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../world_struct.h\"\n\nboolean append_world(struct world *mzx_world, const char *file);\nvoid create_blank_world(struct world *mzx_world);\nvoid set_update_done_current(struct world *mzx_world);\n\nvoid move_current_board(struct world *mzx_world, int new_position);\n\nchar get_default_id_char(int id);\n\n__M_END_DECLS\n\n#endif // __EDITOR_WORLD_H\n"
  },
  {
    "path": "src/error.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2017-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n// Error code\n\n#include <string.h>\n#include <stdlib.h>\n#include <stdio.h>\n\n#include \"core.h\"\n#include \"error.h\"\n#include \"event.h\"\n#include \"graphics.h\"\n#include \"helpsys.h\"\n#include \"platform.h\"\n#include \"util.h\"\n#include \"window.h\"\n#include \"world.h\"\n\n// Error type names by type code:\nstatic const char *const error_type_names[] =\n{\n  \" WARNING: \",         // 0\n  \" ERROR: \",           // 1\n  \" FATAL ERROR: \",     // 2\n  \" CRITICAL ERROR: \",  // 3, unused\n};\n\n// Note: ERROR_OPT_SUPPRESS should only be used by error_message().\n\nint error(const char *string, enum error_type type, unsigned int options,\n unsigned int code)\n{\n  boolean skip_error_ui = false;\n  const char *type_name;\n  int t1 = 9, ret = 0;\n  int joystick_key;\n  int x;\n\n  // Find the name of this error type.\n  if((unsigned int)type >= ARRAY_SIZE(error_type_names))\n    type = ERROR_T_WARNING;\n\n  type_name = error_type_names[type];\n\n  // If graphics couldn't initialize, print the error to stderr and abort.\n  if(!has_video_initialized())\n    skip_error_ui = true;\n\n#ifdef __EMSCRIPTEN__\n  // TODO: For Emscripten, some paths here are safe and others will crash.\n  // These can be detected through their error codes for now.\n  {\n    static const enum error_type safe_codes[] = { 0x3101, 0x2C01, 0x0000 };\n    skip_error_ui = true;\n\n    for(x = 0; x < (int)ARRAY_SIZE(safe_codes); x++)\n      if(code == safe_codes[x])\n        skip_error_ui = false;\n  }\n#endif\n\n  if(skip_error_ui)\n  {\n    int scode = code ? (int)code : -1;\n\n    fprintf(stderr, \"%s%s\\n\", type_name, string);\n    fflush(stderr);\n\n    // Attempt to automatically handle this error if possible.\n    if(options & ERROR_OPT_EXIT) exit(scode);\n    if(options & ERROR_OPT_SUPPRESS) return ERROR_OPT_SUPPRESS;\n    if(options & ERROR_OPT_OK) return ERROR_OPT_OK;\n    if(options & ERROR_OPT_FAIL) return ERROR_OPT_FAIL;\n    exit(scode);\n    return ERROR_OPT_FAIL; // __EMSCRIPTEN__\n  }\n\n  // Use the high byte of the error code to link to a context in the help file.\n  set_context(((code >> 8) & 0xFF) + 700);\n\n  m_hide();\n  save_screen();\n\n  dialog_fadein();\n\n  draw_window_box(1, 10, 78, 14, 76, 64, 72, 1, 1);\n  // Add title and error name\n  x = 40 - (int)strlen(type_name)/2;\n  write_string(type_name, x, 10, 78, 0);\n\n  write_string(string, 40 - ((unsigned int)strlen(string) / 2), 11, 79, 0);\n\n  // Add options\n  write_string(\"Press\", 4, 13, 78, 0);\n\n  if(options & ERROR_OPT_FAIL)\n  {\n    write_string(\", F for Fail\", t1, 13, 78, 0);\n    t1 += 12;\n  }\n  if(options & ERROR_OPT_RETRY)\n  {\n    write_string(\", R to Retry\", t1, 13, 78, 0);\n    t1 += 12;\n  }\n  if(options & ERROR_OPT_EXIT)\n  {\n    write_string(\", E to Exit\", t1, 13, 78, 0);\n    t1 += 11;\n  }\n  if(options & ERROR_OPT_OK)\n  {\n    write_string(\", O for OK\", t1, 13, 78, 0);\n    t1 += 10;\n  }\n  if(options & ERROR_OPT_SUPPRESS)\n  {\n    write_string(\", S for Suppress\", t1, 13, 78, 0);\n    t1 += 16;\n  }\n  // FIXME no context or mzx_world here\n  if(!(options & ERROR_OPT_NO_HELP) && false)\n  {\n    write_string(\", F1 for Help\", t1, 13, 78, 0);\n    t1 += 13;\n  }\n\n  draw_char('.', 78, t1, 13);\n  draw_char(':', 78, 9, 13);\n\n  // Add code if not 0\n\n  if(code != 0)\n  {\n    char temp[32];\n    snprintf(temp, 32, \" Debug code:%4.4xh \", code);\n    write_string(temp, 30, 14, 64, 0);\n  }\n\n  // Get key\n  do\n  {\n    update_screen();\n    update_event_status_delay();\n    t1 = get_key(keycode_internal_wrt_numlock);\n\n    joystick_key = get_joystick_ui_key();\n    if(joystick_key)\n      t1 = joystick_key;\n\n    //Exit event--mimic Escape\n    if(get_exit_status())\n      t1 = IKEY_ESCAPE;\n\n    //Process\n    switch(t1)\n    {\n      case IKEY_f:\n        fail:\n        if(!(options & ERROR_OPT_FAIL)) break;\n        ret = ERROR_OPT_FAIL;\n        break;\n      case IKEY_r:\n        retry:\n        if(!(options & ERROR_OPT_RETRY)) break;\n        ret = ERROR_OPT_RETRY;\n        break;\n      case IKEY_e:\n        exit:\n        if(!(options & ERROR_OPT_EXIT)) break;\n        ret = ERROR_OPT_EXIT;\n        break;\n      case IKEY_o:\n        ok:\n        if(!(options & ERROR_OPT_OK)) break;\n        ret = ERROR_OPT_OK;\n        break;\n      case IKEY_s:\n        if(!(options & ERROR_OPT_SUPPRESS)) break;\n        ret = ERROR_OPT_SUPPRESS;\n        break;\n      case IKEY_F1:\n        if(options & ERROR_OPT_NO_HELP) break;\n        // Call help\n        // FIXME context, no mzx_world here\n        //help_system(NULL, mzx_world);\n        break;\n      case IKEY_ESCAPE:\n        // Escape. Order of options this applies to-\n        // Fail, Ok, Retry, Exit.\n        if(options & ERROR_OPT_FAIL ) goto fail;\n        if(options & ERROR_OPT_OK   ) goto ok;\n        if(options & ERROR_OPT_RETRY) goto retry;\n        goto exit;\n      case IKEY_RETURN:\n        // Enter. Order of options this applies to-\n        // OK, Retry, Fail, Exit.\n        if(options & ERROR_OPT_OK   ) goto ok;\n        if(options & ERROR_OPT_RETRY) goto retry;\n        if(options & ERROR_OPT_FAIL ) goto fail;\n        goto exit;\n    }\n  } while(ret == 0);\n\n  pop_context();\n\n  // Restore screen and exit appropriately\n  dialog_fadeout();\n\n  restore_screen();\n  m_show();\n  if(ret == ERROR_OPT_EXIT) // Exit the program\n  {\n#ifdef CONFIG_STDIO_REDIRECT\n    redirect_stdio_exit();\n#endif\n    platform_quit();\n    exit(-1);\n  }\n\n  return ret;\n}\n\n\nstatic boolean suppress_errors[NUM_ERROR_CODES];\nstatic int error_count = 0;\n\n// Wrapper for error().\nint error_message(enum error_code id, int parameter, const char *string)\n{\n  char error_mesg[80];\n  char version_string[16];\n  int hi = (parameter & 0xFF00) >> 8;\n  int lo = (parameter & 0xFF);\n  int opts = ERROR_OPT_OK | ERROR_OPT_SUPPRESS;\n  int severity = ERROR_T_ERROR;\n  int code = id;\n  int result;\n\n  get_version_string(version_string, parameter);\n  error_count++;\n\n  switch (id)\n  {\n    case E_INVOKE_SELF_FAILED:\n      sprintf(error_mesg, \"Attempt to invoke self failed!\");\n      code = 0xADA1;\n      break;\n\n    case E_CORE_FATAL_BUG:\n      sprintf(error_mesg, \"Context code bug\");\n      severity = ERROR_T_FATAL;\n      opts = ERROR_OPT_EXIT;\n      code = 0x2B00 | (parameter & 0xFF);\n      break;\n\n    case E_FILE_DOES_NOT_EXIST:\n      sprintf(error_mesg, \"File doesn't exist\");\n      break;\n\n    case E_IO_READ:\n      sprintf(error_mesg, \"Unknown error reading from file\");\n      break;\n\n    case E_IO_WRITE:\n      sprintf(error_mesg, \"Unknown error writing to file\");\n      break;\n\n    case E_SAVE_FILE_INVALID:\n      sprintf(error_mesg, \"File is not a valid .SAV file or is corrupt\");\n      code = 0x2101;\n      break;\n\n    case E_SAVE_VERSION_OLD:\n      sprintf(error_mesg,\n       \".SAV files from older versions of MZX (%s) are not supported\",\n       version_string);\n      code = 0x2101;\n      break;\n\n    case E_SAVE_VERSION_TOO_RECENT:\n      sprintf(error_mesg,\n       \".SAV files from newer versions of MZX (%s) are not supported\",\n       version_string);\n      code = 0x2101;\n      break;\n\n    case E_WORLD_FILE_INVALID:\n      sprintf(error_mesg, \"File is not a valid world file or is corrupt\");\n      code = 0x0D02;\n      break;\n\n    case E_WORLD_FILE_VERSION_OLD:\n      sprintf(error_mesg,\n       \"Invalid world version (%s)\",\n       version_string);\n      code = 0x0D02;\n      break;\n\n    case E_WORLD_FILE_VERSION_TOO_RECENT:\n      sprintf(error_mesg,\n       \"World is from a more recent version (%s)\",\n       version_string);\n      code = 0x0D02;\n      break;\n\n    case E_WORLD_DECRYPT_WRITE_PROTECTED:\n      sprintf(error_mesg, \"Cannot decrypt write-protected world; \"\n       \"check permissions\");\n      code = 0x0DD5;\n      break;\n\n    case E_WORLD_LOCKED:\n      sprintf(error_mesg, \"Cannot load password protected world\");\n      code = 0x0D02;\n      break;\n\n    case E_WORLD_IO_POST_VALIDATION:\n      sprintf(error_mesg, \"Post validation IO error occurred\");\n      code = 0x0D01;\n      break;\n\n    case E_WORLD_IO_SAVING:\n      sprintf(error_mesg,\n       \"Error saving; file/directory may be write protected\");\n      code = 0x0D01;\n      break;\n\n    case E_WORLD_BOARD_MISSING:\n      sprintf(error_mesg,\n       \"Board @ %Xh could not be found\", parameter);\n      code = 0x0D03;\n      break;\n\n    case E_WORLD_BOARD_CORRUPT:\n      sprintf(error_mesg,\n       \"Board @ %Xh is irrecoverably truncated or corrupt\", parameter);\n      code = 0x0D03;\n      break;\n\n    case E_WORLD_BOARD_TRUNCATED_SAFE:\n      sprintf(error_mesg,\n       \"Board @ %Xh is truncated, but could be partially recovered\", parameter);\n      code = 0x0D03;\n      break;\n\n    case E_WORLD_ROBOT_MISSING:\n      sprintf(error_mesg, \"Robot @ %Xh could not be found\", parameter);\n      code = 0x0D03;\n      break;\n\n    case E_BOARD_FILE_INVALID:\n      sprintf(error_mesg, \"File is not a board file or is corrupt\");\n      code = 0x4040;\n      break;\n\n    case E_BOARD_FILE_FUTURE_VERSION:\n      sprintf(error_mesg, \"Board file is from a future version (%s)\",\n       version_string);\n      code = 0x4041;\n      break;\n\n    case E_BOARD_ROBOT_CORRUPT:\n      sprintf(error_mesg, \"Robot @ %Xh is truncated or corrupt\", parameter);\n      code = 0x0D03;\n      break;\n\n    case E_BOARD_SCROLL_CORRUPT:\n      sprintf(error_mesg, \"Scroll @ %Xh is truncated or corrupt\", parameter);\n      code = 0x0D03;\n      break;\n\n    case E_BOARD_SENSOR_CORRUPT:\n      sprintf(error_mesg, \"Sensor @ %Xh is truncated or corrupt\", parameter);\n      code = 0x0D03;\n      break;\n\n    case E_BOARD_SUMMARY:\n      sprintf(error_mesg, \"Board @ %Xh summary\", parameter);\n      code = 0x4041;\n      break;\n\n    case E_MZM_DOES_NOT_EXIST:\n      sprintf(error_mesg, \"MZM doesn't exist\");\n      code = 0x0D01;\n      break;\n\n    case E_MZM_FILE_INVALID:\n      sprintf(error_mesg, \"File is not an MZM or is corrupt\");\n      code = 0x6660;\n      break;\n\n    case E_MZM_FILE_FROM_SAVEGAME:\n      sprintf(error_mesg, \"MZM contains runtime robots; dummying out\");\n      code = 0x6661;\n      break;\n\n    case E_MZM_FILE_VERSION_TOO_RECENT:\n      sprintf(error_mesg,\n       \"MZM from newer version (%s); dummying out robots\",\n       version_string);\n      code = 0x6661;\n      break;\n\n    case E_MZM_ROBOT_CORRUPT:\n      sprintf(error_mesg, \"MZM is missing robots or contains corrupt robots\");\n      code = 0x6662;\n      break;\n\n    case E_LOAD_BC_CORRUPT:\n      sprintf(error_mesg, \"Bytecode file failed validation check\");\n      code = 0xD0D0;\n      break;\n\n    case E_NO_LAYER_RENDERER:\n      sprintf(error_mesg, \"Current renderer lacks advanced graphical features; \"\n       \"features disabled\");\n      code = 0x2563;\n      break;\n\n    case E_NO_EXTENDED_CHARSETS:\n      sprintf(error_mesg, \"Limited/missing extended charset support; \"\n       \"some features may not work\");\n      code = 0x2564;\n      break;\n\n    case E_ZIP_BOARD_CORRUPT:\n      sprintf(error_mesg, \"Board # %d is corrupt\", lo);\n      code = 0x9000;\n      break;\n\n    case E_ZIP_BOARD_MISSING_DATA:\n      sprintf(error_mesg, \"Board # %d is missing data:\", lo);\n      code = 0x9001;\n      break;\n\n    case E_ZIP_ROBOT_CORRUPT:\n      sprintf(error_mesg, \"Robot # %d on board # %d is corrupt\", lo, hi);\n      code = 0x9002;\n      break;\n\n    case E_ZIP_SCROLL_CORRUPT:\n      sprintf(error_mesg, \"Scroll # %d on board # %d is corrupt\", lo, hi);\n      code = 0x9003;\n      break;\n\n    case E_ZIP_SENSOR_CORRUPT:\n      sprintf(error_mesg, \"Sensor # %d on board # %d is corrupt\", lo, hi);\n      code = 0x9004;\n      break;\n\n    case E_ZIP_ROBOT_MISSING_FROM_BOARD:\n      sprintf(error_mesg, \"Robot # %d does not exist on board # %d\", lo, hi);\n      code = 0x9005;\n      break;\n\n    case E_ZIP_ROBOT_MISSING_FROM_DATA:\n      sprintf(error_mesg,\n       \"Robot # %d exists on board # %d, but was not found\", lo, hi);\n      code = 0x9006;\n      break;\n\n    case E_ZIP_ROBOT_DUPLICATED:\n      sprintf(error_mesg,\n       \"Robot # %d contains duplicates on board # %d\", lo, hi);\n      code = 0x9007;\n      break;\n\n#ifdef CONFIG_EDITOR\n    case E_CANT_OVERWRITE_PLAYER:\n      sprintf(error_mesg, \"Cannot overwrite the player- move it first\");\n      severity = ERROR_T_WARNING;\n      code = 0x0000;\n      break;\n\n    case E_ANSI_IMPORT:\n      sprintf(error_mesg, \"Error importing ANSi\");\n      code = 0x1901;\n      break;\n\n    case E_ANSI_EXPORT:\n      sprintf(error_mesg, \"Error exporting ANSi\");\n      code = 0x0F01;\n      break;\n\n    case E_TEXT_EXPORT:\n      sprintf(error_mesg, \"Error exporting text\");\n      code = 0x1401;\n      break;\n\n    case E_SFX_IMPORT:\n      sprintf(error_mesg, \"File is invalid or is not an SFX file\");\n      code = 0;\n      break;\n\n    case E_SFX_EXPORT:\n      sprintf(error_mesg, \"Error exporting SFX file\");\n      code = 0;\n      break;\n\n    case E_IMAGE_EXPORT:\n      sprintf(error_mesg, \"Error exporting image\");\n      code = 0;\n      break;\n\n    case E_IMAGE_EXPORT_CANCELED:\n      sprintf(error_mesg, \"Error exporting image or export was canceled\");\n      code = 0;\n      break;\n#endif\n\n#ifdef CONFIG_UPDATER\n    case E_UPDATE_RETRY:\n      opts = ERROR_OPT_RETRY | ERROR_OPT_FAIL;\n\n      /* fallthrough */\n\n    case E_UPDATE:\n      snprintf(error_mesg, 79, \"%s\", string);\n      code = 0xA200 | (parameter & 0xFF);\n      string = NULL;\n      break;\n#endif\n\n#ifdef CONFIG_DEBYTECODE\n    case E_DBC_WORLD_OVERWRITE_OLD:\n      sprintf(error_mesg,\n       \"Save would overwrite older version (%s); aborted\",\n       version_string);\n      code = 0x0fac;\n      break;\n\n    case E_DBC_SAVE_ROBOT_UNSUPPORTED:\n      sprintf(error_mesg,\n       \"SAVE_BC is no longer supported\");\n      code = 0x0fac;\n      break;\n#endif\n\n    case E_ZIP:\n      code = 0xEEEE;\n      /* fallthrough */\n\n    case E_DEFAULT:\n    default:\n      snprintf(error_mesg, 79, \"%s\", string);\n      string = NULL;\n      break;\n  }\n\n  // This needs to happen after the switch, unfortunately, since we need to\n  // check the options. Suppress errors that allow OK only.\n  if(suppress_errors[id] && (opts & ERROR_OPT_OK))\n    return ERROR_OPT_OK;\n\n  if(string)\n  {\n    int offset = strlen(error_mesg);\n    int left = 79 - offset;\n    if(left > 0)\n    {\n      snprintf(error_mesg + offset, left, \": %s\", string);\n    }\n  }\n\n  result = error(error_mesg, severity, opts, code);\n\n  if(result == ERROR_OPT_SUPPRESS)\n  {\n    suppress_errors[id] = true;\n    return ERROR_OPT_OK;\n  }\n\n  return result;\n}\n\nint get_and_reset_error_count(void)\n{\n  int count = error_count;\n  error_count = 0;\n  return count;\n}\n\nvoid set_error_suppression(enum error_code id, boolean enable)\n{\n  suppress_errors[id] = enable;\n}\n\nvoid reset_error_suppression(void)\n{\n  int i;\n  for(i = 0; i < NUM_ERROR_CODES; i++)\n    suppress_errors[i] = false;\n}\n"
  },
  {
    "path": "src/error.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declaration for ERROR.CPP */\n\n#ifndef __ERROR_H\n#define __ERROR_H\n\n#include \"compat.h\"\n\n#define ERROR_OPT_FAIL      1\n#define ERROR_OPT_RETRY     2\n#define ERROR_OPT_EXIT      4\n#define ERROR_OPT_OK        8\n#define ERROR_OPT_NO_HELP  16\n#define ERROR_OPT_SUPPRESS 32\n\n__M_BEGIN_DECLS\n\nenum error_type\n{\n  ERROR_T_WARNING   = 0,\n  ERROR_T_ERROR     = 1,\n  ERROR_T_FATAL     = 2,\n};\n\nenum error_code\n{\n  E_DEFAULT,\n  E_INVOKE_SELF_FAILED,\n  E_CORE_FATAL_BUG,\n  E_FILE_DOES_NOT_EXIST,\n  E_IO_READ,\n  E_IO_WRITE,\n  E_SAVE_FILE_INVALID,\n  E_SAVE_VERSION_OLD,\n  E_SAVE_VERSION_TOO_RECENT,\n  E_WORLD_FILE_INVALID,\n  E_WORLD_FILE_VERSION_OLD,\n  E_WORLD_FILE_VERSION_TOO_RECENT,\n  E_WORLD_DECRYPT_WRITE_PROTECTED,\n  E_WORLD_LOCKED,\n  E_WORLD_IO_POST_VALIDATION,\n  E_WORLD_IO_SAVING,\n  E_WORLD_BOARD_MISSING,\n  E_WORLD_BOARD_CORRUPT,\n  E_WORLD_BOARD_TRUNCATED_SAFE,\n  E_WORLD_ROBOT_MISSING,\n  E_BOARD_FILE_INVALID,\n  E_BOARD_FILE_FUTURE_VERSION,\n  E_BOARD_ROBOT_CORRUPT,\n  E_BOARD_SCROLL_CORRUPT,\n  E_BOARD_SENSOR_CORRUPT,\n  E_BOARD_SUMMARY,\n  E_MZM_DOES_NOT_EXIST,\n  E_MZM_FILE_INVALID,\n  E_MZM_FILE_FROM_SAVEGAME,\n  E_MZM_FILE_VERSION_TOO_RECENT,\n  E_MZM_ROBOT_CORRUPT,\n  E_LOAD_BC_CORRUPT,\n  E_NO_LAYER_RENDERER,\n  E_NO_EXTENDED_CHARSETS,\n  E_ZIP,\n  E_ZIP_BOARD_CORRUPT,\n  E_ZIP_BOARD_MISSING_DATA,\n  E_ZIP_ROBOT_CORRUPT,\n  E_ZIP_SCROLL_CORRUPT,\n  E_ZIP_SENSOR_CORRUPT,\n  E_ZIP_ROBOT_MISSING_FROM_BOARD,\n  E_ZIP_ROBOT_MISSING_FROM_DATA,\n  E_ZIP_ROBOT_DUPLICATED,\n#ifdef CONFIG_EDITOR\n  E_CANT_OVERWRITE_PLAYER,\n  E_ANSI_IMPORT,\n  E_ANSI_EXPORT,\n  E_TEXT_EXPORT,\n  E_SFX_IMPORT,\n  E_SFX_EXPORT,\n  E_IMAGE_EXPORT,\n  E_IMAGE_EXPORT_CANCELED,\n#endif\n#ifdef CONFIG_UPDATER\n  E_UPDATE,\n  E_UPDATE_RETRY,\n#endif\n#ifdef CONFIG_DEBYTECODE\n  E_DBC_WORLD_OVERWRITE_OLD,\n  E_DBC_SAVE_ROBOT_UNSUPPORTED,\n#endif\n  NUM_ERROR_CODES\n};\n\nCORE_LIBSPEC int error(const char *string, enum error_type type,\n unsigned int options, unsigned int code);\n\nCORE_LIBSPEC int error_message(enum error_code id, int parameter,\n const char *string);\n\nCORE_LIBSPEC void set_error_suppression(enum error_code id, boolean enable);\n\nint get_and_reset_error_count(void);\nvoid reset_error_suppression(void);\n\n__M_END_DECLS\n\n#endif // __ERROR_H\n"
  },
  {
    "path": "src/event.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2019-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"configure.h\"\n#include \"event.h\"\n#include \"graphics.h\"\n#include \"platform.h\"\n#include \"util.h\"\n\n#include <assert.h>\n#include <limits.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n\n#define KEY_REPEAT_START    250\n#define KEY_REPEAT_RATE     33\n\n#define MOUSE_REPEAT_START  200\n#define MOUSE_REPEAT_RATE   10\n\n#define JOYSTICK_REPEAT_START KEY_REPEAT_START\n#define JOYSTICK_REPEAT_RATE  KEY_REPEAT_RATE\n\n#if defined(__linux__) && !defined(__ANDROID__)\n/* TODO: Steam Deck hack. SteamOS apparently broke this and the fix has\n * not been backported to SDL2. Additionally, SteamOS has its own shortcut\n * to open the screen keyboard, so it's not necessary to sacrifice a button.\n * Platforms that don't support this don't need to worry about it.\n * If Linux SDL ever gets another source of screen keyboard this will\n * need to be revised. */\n#define DEFAULT_SCREEN_KEYBOARD_ACTION JOY_NO_ACTION\n#else\n#define DEFAULT_SCREEN_KEYBOARD_ACTION JOY_RSHOULDER\n#endif\n\nstatic uint32_t last_update_time;\n\nstruct input_status input;\n\nstatic unsigned int num_buffered_events = 1;\n\nboolean enable_f12_hack;\n\nstatic boolean joystick_legacy_loop_hacks = false;\nstatic boolean joystick_game_mode = false;\nstatic boolean joystick_game_bindings = true;\n\nstatic enum keycode joystick_action_map_default[NUM_JOYSTICK_ACTIONS] =\n{\n  [JOY_A] = IKEY_SPACE,\n  [JOY_B] = IKEY_DELETE,\n  [JOY_X] = IKEY_RETURN,\n  [JOY_Y] = IKEY_s,\n  [JOY_SELECT] = IKEY_ESCAPE,\n  [JOY_START] = IKEY_RETURN,\n  [JOY_LSTICK] = 0,\n  [JOY_RSTICK] = 0,\n  [JOY_LSHOULDER] = IKEY_INSERT,\n  [JOY_RSHOULDER] = IKEY_p,\n  [JOY_LTRIGGER] = IKEY_F3,\n  [JOY_RTRIGGER] = IKEY_F4,\n  [JOY_UP] = IKEY_UP,\n  [JOY_DOWN] = IKEY_DOWN,\n  [JOY_LEFT] = IKEY_LEFT,\n  [JOY_RIGHT] = IKEY_RIGHT,\n  [JOY_L_UP] = IKEY_UP,\n  [JOY_L_DOWN] = IKEY_DOWN,\n  [JOY_L_LEFT] = IKEY_LEFT,\n  [JOY_L_RIGHT] = IKEY_RIGHT,\n  [JOY_R_UP] = IKEY_UP,\n  [JOY_R_DOWN] = IKEY_DOWN,\n  [JOY_R_LEFT] = IKEY_LEFT,\n  [JOY_R_RIGHT] = IKEY_RIGHT,\n};\n\nstatic enum keycode joystick_action_map_ui[NUM_JOYSTICK_ACTIONS] =\n{\n  [JOY_A] = IKEY_RETURN,\n  [JOY_B] = IKEY_ESCAPE,\n  [JOY_X] = 0,\n  [JOY_Y] = 0,\n  [JOY_SELECT] = IKEY_ESCAPE,\n  [JOY_START] = IKEY_RETURN,\n  [JOY_LSHOULDER] = IKEY_TAB,\n  [JOY_RSHOULDER] = IKEY_F2,\n  [JOY_LTRIGGER] = IKEY_PAGEUP,\n  [JOY_RTRIGGER] = IKEY_PAGEDOWN,\n  [JOY_LSTICK] = IKEY_HOME,\n  [JOY_RSTICK] = IKEY_END,\n  [JOY_UP] = IKEY_UP,\n  [JOY_DOWN] = IKEY_DOWN,\n  [JOY_LEFT] = IKEY_LEFT,\n  [JOY_RIGHT] = IKEY_RIGHT,\n  [JOY_L_UP] = IKEY_UP,\n  [JOY_L_DOWN] = IKEY_DOWN,\n  [JOY_L_LEFT] = IKEY_LEFT,\n  [JOY_L_RIGHT] = IKEY_RIGHT,\n  [JOY_R_UP] = IKEY_UP,\n  [JOY_R_DOWN] = IKEY_DOWN,\n  [JOY_R_LEFT] = IKEY_LEFT,\n  [JOY_R_RIGHT] = IKEY_RIGHT,\n};\n\nstruct buffered_status *store_status(void)\n{\n  return &input.buffer[input.store_offset];\n}\n\n#ifndef CONFIG_NDS\nstatic\n#endif\nconst struct buffered_status *load_status(void)\n{\n  return (const struct buffered_status *)&input.buffer[input.load_offset];\n}\n\nstatic void bump_status(void)\n{\n  unsigned int last_store_offset = input.store_offset;\n  uint32_t *unicode;\n\n  input.store_offset = (input.store_offset + 1) % num_buffered_events;\n  input.load_offset = (input.store_offset + 1) % num_buffered_events;\n\n  // No event buffering; nothing to do\n  if(input.store_offset == input.load_offset)\n    return;\n\n  // Some events can \"echo\" from the previous buffer\n  unicode = store_status()->unicode;\n  memcpy(store_status(), &input.buffer[last_store_offset],\n         sizeof(struct buffered_status));\n  store_status()->unicode = unicode;\n}\n\nvoid init_event(struct config_info *conf)\n{\n  int i, i2;\n\n  // Get defaults from config.\n  num_buffered_events = MAX(1, conf->num_buffered_events);\n  input.unfocus_pause = conf->pause_on_unfocus;\n  input.left_alt_is_altgr = conf->key_left_alt_is_altgr;\n  input.right_alt_is_altgr = conf->key_right_alt_is_altgr;\n\n  input.buffer = ccalloc(num_buffered_events, sizeof(struct buffered_status));\n  input.load_offset = num_buffered_events - 1;\n  input.store_offset = 0;\n\n  // Load default action keybindings for gameplay.\n  // Config has already loaded, so there might be user mappings here already.\n  for(i = 0; i < MAX_JOYSTICKS; i++)\n    for(i2 = 0; i2 < NUM_JOYSTICK_ACTIONS; i2++)\n      if(!input.joystick_global_map.action_is_conf[i][i2])\n        input.joystick_global_map.action[i][i2] =\n         joystick_action_map_default[i2];\n\n  for(i = 0; i < MAX_JOYSTICKS; i++)\n    if(!input.joystick_global_map.show_keyboard_is_conf[i])\n      input.joystick_global_map.show_screen_keyboard_action[i] =\n       DEFAULT_SCREEN_KEYBOARD_ACTION;\n\n  if(!input.joystick_axis_threshold)\n    input.joystick_axis_threshold = AXIS_DEFAULT_THRESHOLD;\n\n  // Write the new global bindings over the game bindings.\n  joystick_reset_game_map();\n\n  // Platform-specific events system initialization.\n  platform_init_event();\n}\n\nuint32_t convert_internal_unicode(enum keycode key, boolean caps_lock)\n{\n  if(KEYCODE_IS_ASCII(key))\n  {\n    boolean shift_status = get_shift_status(keycode_internal);\n\n    if(caps_lock && (key >= IKEY_a) && (key <= IKEY_z))\n    {\n      if(!shift_status)\n        return (key - 32);\n      return key;\n    }\n\n    if(shift_status)\n    {\n      if((key >= IKEY_a) && (key <= IKEY_z))\n        return (key - 32);\n\n      // TODO based on a US keyboard right now since that's as good as any\n      // default and possibly what most users are going to expect. It would\n      // be nice to have more locales at some point if someone requests them.\n      switch(key)\n      {\n        case IKEY_BACKQUOTE:    return '~';\n        case IKEY_1:            return '!';\n        case IKEY_2:            return '@';\n        case IKEY_3:            return '#';\n        case IKEY_4:            return '$';\n        case IKEY_5:            return '%';\n        case IKEY_6:            return '^';\n        case IKEY_7:            return '&';\n        case IKEY_8:            return '*';\n        case IKEY_9:            return '(';\n        case IKEY_0:            return ')';\n        case IKEY_MINUS:        return '_';\n        case IKEY_EQUALS:       return '+';\n        case IKEY_LEFTBRACKET:  return '{';\n        case IKEY_RIGHTBRACKET: return '}';\n        case IKEY_BACKSLASH:    return '|';\n        case IKEY_SEMICOLON:    return ':';\n        case IKEY_QUOTE:        return '\"';\n        case IKEY_COMMA:        return '<';\n        case IKEY_PERIOD:       return '>';\n        case IKEY_SLASH:        return '?';\n        default:                return IKEY_UNKNOWN;\n      }\n    }\n    return key;\n  }\n  return IKEY_UNKNOWN;\n}\n\nstatic uint32_t convert_internal_xt(enum keycode key)\n{\n  switch(key)\n  {\n    case IKEY_ESCAPE: return 0x01;\n    case IKEY_F1: return 0x3B;\n    case IKEY_F2: return 0x3C;\n    case IKEY_F3: return 0x3D;\n    case IKEY_F4: return 0x3E;\n    case IKEY_F5: return 0x3F;\n    case IKEY_F6: return 0x40;\n    case IKEY_F7: return 0x41;\n    case IKEY_F8: return 0x42;\n    case IKEY_F9: return 0x43;\n    case IKEY_F10: return 0x44;\n    case IKEY_F11: return 0x57;\n    case IKEY_F12: return 0x58;\n    case IKEY_BACKQUOTE: return 0x29;\n    case IKEY_1: return 0x02;\n    case IKEY_2: return 0x03;\n    case IKEY_3: return 0x04;\n    case IKEY_4: return 0x05;\n    case IKEY_5: return 0x06;\n    case IKEY_6: return 0x07;\n    case IKEY_7: return 0x08;\n    case IKEY_8: return 0x09;\n    case IKEY_9: return 0x0A;\n    case IKEY_0: return 0x0B;\n    case IKEY_MINUS: return 0x0C;\n    case IKEY_EQUALS: return 0x0D;\n    case IKEY_BACKSLASH: return 0x2B;\n    case IKEY_BACKSPACE: return 0x0E;\n    case IKEY_TAB: return 0x0F;\n    case IKEY_q: return 0x10;\n    case IKEY_w: return 0x11;\n    case IKEY_e: return 0x12;\n    case IKEY_r: return 0x13;\n    case IKEY_t: return 0x14;\n    case IKEY_y: return 0x15;\n    case IKEY_u: return 0x16;\n    case IKEY_i: return 0x17;\n    case IKEY_o: return 0x18;\n    case IKEY_p: return 0x19;\n    case IKEY_LEFTBRACKET: return 0x1A;\n    case IKEY_RIGHTBRACKET: return 0x1B;\n    case IKEY_CAPSLOCK: return 0x3A;\n    case IKEY_a: return 0x1E;\n    case IKEY_s: return 0x1F;\n    case IKEY_d: return 0x20;\n    case IKEY_f: return 0x21;\n    case IKEY_g: return 0x22;\n    case IKEY_h: return 0x23;\n    case IKEY_j: return 0x24;\n    case IKEY_k: return 0x25;\n    case IKEY_l: return 0x26;\n    case IKEY_SEMICOLON: return 0x27;\n    case IKEY_QUOTE: return 0x28;\n    case IKEY_RETURN: return 0x1C;\n    case IKEY_LSHIFT: return 0x2A;\n    case IKEY_z: return 0x2C;\n    case IKEY_x: return 0x2D;\n    case IKEY_c: return 0x2E;\n    case IKEY_v: return 0x2F;\n    case IKEY_b: return 0x30;\n    case IKEY_n: return 0x31;\n    case IKEY_m: return 0x32;\n    case IKEY_COMMA: return 0x33;\n    case IKEY_PERIOD: return 0x34;\n    case IKEY_SLASH: return 0x35;\n    case IKEY_RSHIFT: return 0x36;\n    case IKEY_LCTRL: return 0x1D;\n    case IKEY_LSUPER: return 0x5B;\n    case IKEY_LALT: return 0x38;\n    case IKEY_SPACE: return 0x39;\n    case IKEY_RALT: return 0x38;\n    case IKEY_ALTGR: return 0x38;\n    case IKEY_RSUPER: return 0x5C;\n    case IKEY_MENU: return 0x5D;\n    case IKEY_RCTRL: return 0x1D;\n    case IKEY_SYSREQ: return 0x37;\n    case IKEY_SCROLLOCK: return 0x46;\n    case IKEY_BREAK: return 0xC5;\n    case IKEY_INSERT: return 0x52;\n    case IKEY_HOME: return 0x47;\n    case IKEY_PAGEUP: return 0x49;\n    case IKEY_DELETE: return 0x53;\n    case IKEY_END: return 0x4F;\n    case IKEY_PAGEDOWN: return 0x51;\n    case IKEY_NUMLOCK: return 0x45;\n    case IKEY_KP_DIVIDE: return 0x35;\n    case IKEY_KP_MULTIPLY: return 0x37;\n    case IKEY_KP_MINUS: return 0x4A;\n    case IKEY_KP7: return 0x47;\n    case IKEY_KP8: return 0x48;\n    case IKEY_KP9: return 0x49;\n    case IKEY_KP4: return 0x4B;\n    case IKEY_KP5: return 0x4C;\n    case IKEY_KP6: return 0x4D;\n    case IKEY_KP_PLUS: return 0x4E;\n    case IKEY_KP1: return 0x4F;\n    case IKEY_KP2: return 0x50;\n    case IKEY_KP3: return 0x51;\n    case IKEY_KP_ENTER: return 0x1C;\n    case IKEY_KP0: return 0x52;\n    case IKEY_KP_PERIOD: return 0x53;\n    case IKEY_UP: return 0x48;\n    case IKEY_LEFT: return 0x4B;\n    case IKEY_DOWN: return 0x50;\n    case IKEY_RIGHT: return 0x4D;\n\n    // All others are currently undefined; returns 0\n    default: return 0;\n  }\n}\n\nstatic enum keycode convert_xt_internal(uint32_t key, enum keycode *second,\n enum keycode *third)\n{\n  *second = IKEY_UNKNOWN;\n  *third = IKEY_UNKNOWN;\n  switch(key)\n  {\n    case 0x01: return IKEY_ESCAPE;\n    case 0x3B: return IKEY_F1;\n    case 0x3C: return IKEY_F2;\n    case 0x3D: return IKEY_F3;\n    case 0x3E: return IKEY_F4;\n    case 0x3F: return IKEY_F5;\n    case 0x40: return IKEY_F6;\n    case 0x41: return IKEY_F7;\n    case 0x42: return IKEY_F8;\n    case 0x43: return IKEY_F9;\n    case 0x44: return IKEY_F10;\n    case 0x57: return IKEY_F11;\n    case 0x58: return IKEY_F12;\n    case 0x29: return IKEY_BACKQUOTE;\n    case 0x02: return IKEY_1;\n    case 0x03: return IKEY_2;\n    case 0x04: return IKEY_3;\n    case 0x05: return IKEY_4;\n    case 0x06: return IKEY_5;\n    case 0x07: return IKEY_6;\n    case 0x08: return IKEY_7;\n    case 0x09: return IKEY_8;\n    case 0x0A: return IKEY_9;\n    case 0x0B: return IKEY_0;\n    case 0x0C: return IKEY_MINUS;\n    case 0x0D: return IKEY_EQUALS;\n    case 0x2B: return IKEY_BACKSLASH;\n    case 0x0E: return IKEY_BACKSPACE;\n    case 0x0F: return IKEY_TAB;\n    case 0x10: return IKEY_q;\n    case 0x11: return IKEY_w;\n    case 0x12: return IKEY_e;\n    case 0x13: return IKEY_r;\n    case 0x14: return IKEY_t;\n    case 0x15: return IKEY_y;\n    case 0x16: return IKEY_u;\n    case 0x17: return IKEY_i;\n    case 0x18: return IKEY_o;\n    case 0x19: return IKEY_p;\n    case 0x1A: return IKEY_LEFTBRACKET;\n    case 0x1B: return IKEY_RIGHTBRACKET;\n    case 0x3A: return IKEY_CAPSLOCK;\n    case 0x1E: return IKEY_a;\n    case 0x1F: return IKEY_s;\n    case 0x20: return IKEY_d;\n    case 0x21: return IKEY_f;\n    case 0x22: return IKEY_g;\n    case 0x23: return IKEY_h;\n    case 0x24: return IKEY_j;\n    case 0x25: return IKEY_k;\n    case 0x26: return IKEY_l;\n    case 0x27: return IKEY_SEMICOLON;\n    case 0x28: return IKEY_QUOTE;\n    case 0x1C:\n      *second = IKEY_KP_ENTER;\n      return IKEY_RETURN;\n    case 0x2A: return IKEY_LSHIFT;\n    case 0x2C: return IKEY_z;\n    case 0x2D: return IKEY_x;\n    case 0x2E: return IKEY_c;\n    case 0x2F: return IKEY_v;\n    case 0x30: return IKEY_b;\n    case 0x31: return IKEY_n;\n    case 0x32: return IKEY_m;\n    case 0x33: return IKEY_COMMA;\n    case 0x34: return IKEY_PERIOD;\n    case 0x35:\n      *second = IKEY_KP_DIVIDE;\n      return IKEY_SLASH;\n    case 0x36: return IKEY_RSHIFT;\n    case 0x1D:\n      *second = IKEY_RCTRL;\n      return IKEY_LCTRL;\n    case 0x5B: return IKEY_LSUPER;\n    case 0x38:\n      *second = IKEY_RALT;\n      *third = IKEY_ALTGR;\n      return IKEY_LALT;\n    case 0x39: return IKEY_SPACE;\n    case 0x5C: return IKEY_RSUPER;\n    case 0x5D: return IKEY_MENU;\n    case 0x37:\n      *second = IKEY_KP_MULTIPLY;\n      return IKEY_SYSREQ;\n    case 0x46: return IKEY_SCROLLOCK;\n    case 0xC5: return IKEY_BREAK;\n    case 0x52:\n      *second = IKEY_KP0;\n      return IKEY_INSERT;\n    case 0x47:\n      *second = IKEY_KP7;\n      return IKEY_HOME;\n    case 0x49:\n      *second = IKEY_KP9;\n      return IKEY_PAGEUP;\n    case 0x53:\n      *second = IKEY_KP_PERIOD;\n      return IKEY_DELETE;\n    case 0x4F:\n      *second = IKEY_KP1;\n      return IKEY_END;\n    case 0x51:\n      *second = IKEY_KP3;\n      return IKEY_PAGEDOWN;\n    case 0x45: return IKEY_NUMLOCK;\n    case 0x4A: return IKEY_KP_MINUS;\n    case 0x48:\n      *second = IKEY_UP;\n      return IKEY_KP8;\n    case 0x4B:\n      *second = IKEY_LEFT;\n       return IKEY_KP4;\n    case 0x4C: return IKEY_KP5;\n    case 0x4D:\n      *second = IKEY_RIGHT;\n      return IKEY_KP6;\n    case 0x4E: return IKEY_KP_PLUS;\n    case 0x50:\n      *second = IKEY_DOWN;\n      return IKEY_KP2;\n    default: return IKEY_UNKNOWN;\n  }\n}\n\nstatic boolean update_autorepeat(void)\n{\n  // The repeat key may not be a \"valid\" keycode due to the unbounded nature\n  // of joypad support.  All invalid keys use the last position because that's\n  // better than crashing.\n  struct buffered_status *status = store_status();\n  enum keycode status_key =\n   MIN((unsigned int) status->key_repeat, STATUS_NUM_KEYCODES - 1);\n  boolean rval = false;\n\n  // Repeat code\n  int last_key_state = status->keymap[status_key];\n  int last_mouse_state = status->mouse_repeat_state;\n  int last_joystick_state = status->joystick_repeat_state;\n\n  // If you enable SDL 2.0 key repeat, uncomment these lines:\n//#ifdef CONFIG_SDL\n//#if SDL_VERSION_ATLEAST(2,0,0)\n  //last_key_state = 0;\n  //input.repeat_stack_pointer = 0;\n//#endif\n//#endif\n\n  if(last_key_state)\n  {\n    uint32_t new_time = get_ticks();\n    uint32_t ms_difference = new_time - status->keypress_time;\n\n    if(last_key_state == 1)\n    {\n      if(ms_difference > KEY_REPEAT_START)\n      {\n        status->keypress_time = new_time;\n        status->keymap[status_key] = 2;\n        status->key = status->key_repeat;\n        key_press_unicode(status, status->unicode_repeat, false);\n        rval = true;\n      }\n    }\n    else\n    {\n      if(ms_difference > KEY_REPEAT_RATE)\n      {\n        status->keypress_time = new_time;\n        status->key = status->key_repeat;\n        key_press_unicode(status, status->unicode_repeat, false);\n        rval = true;\n      }\n    }\n  }\n  else\n\n  if(input.repeat_stack_pointer)\n  {\n    // Take repeat off the stack.\n    input.repeat_stack_pointer--;\n    status->key_repeat =\n     input.key_repeat_stack[input.repeat_stack_pointer];\n    status->unicode_repeat =\n     input.unicode_repeat_stack[input.repeat_stack_pointer];\n\n    status->keypress_time = get_ticks();\n  }\n\n  if(last_mouse_state)\n  {\n    uint32_t new_time = get_ticks();\n    uint32_t ms_difference = new_time - status->mouse_time;\n\n    if(status->mouse_drag_state == -1)\n      status->mouse_drag_state = 0;\n    else\n      status->mouse_drag_state = 1;\n\n    if(last_mouse_state == 1)\n    {\n      if(ms_difference > MOUSE_REPEAT_START)\n      {\n        status->mouse_time = new_time;\n        status->mouse_repeat_state = 2;\n        status->mouse_button = status->mouse_repeat;\n        rval = true;\n      }\n    }\n    else\n    {\n      if(ms_difference > MOUSE_REPEAT_RATE)\n      {\n        status->mouse_time = new_time;\n        status->mouse_button = status->mouse_repeat;\n        rval = true;\n      }\n    }\n  }\n\n  if(last_joystick_state)\n  {\n    uint32_t new_time = get_ticks();\n    uint32_t ms_difference = new_time - status->joystick_time;\n\n    if(last_joystick_state == 1)\n    {\n      if(ms_difference > JOYSTICK_REPEAT_START)\n      {\n        status->joystick_time = new_time;\n        status->joystick_repeat_state = 2;\n        status->joystick_action = status->joystick_repeat;\n        rval = true;\n      }\n    }\n    else\n    {\n      if(ms_difference > JOYSTICK_REPEAT_RATE)\n      {\n        status->joystick_time = new_time;\n        status->joystick_action = status->joystick_repeat;\n        rval = true;\n      }\n    }\n  }\n\n  bump_status();\n  return rval;\n}\n\nstatic void start_frame_event_status(void)\n{\n  struct buffered_status *status = store_status();\n\n  status->key = IKEY_UNKNOWN;\n  status->unicode_pos = 0;\n  status->unicode_length = 0;\n  status->mouse_moved = 0;\n  status->mouse_button = 0;\n  status->joystick_action = 0;\n  status->exit_status = false;\n\n  status->mouse_last_x = status->mouse_x;\n  status->mouse_last_y = status->mouse_y;\n  status->mouse_last_pixel_x = status->mouse_pixel_x;\n  status->mouse_last_pixel_y = status->mouse_pixel_y;\n  status->warped_mouse_x = -1;\n  status->warped_mouse_y = -1;\n}\n\nboolean update_event_status(void)\n{\n  boolean rval;\n\n  start_frame_event_status();\n\n  rval  = __update_event_status();\n  rval |= update_autorepeat();\n\n  return rval;\n}\n\nboolean peek_exit_input(void)\n{\n  return __peek_exit_input();\n}\n\nvoid wait_event(int timeout)\n{\n  start_frame_event_status();\n\n  if(timeout > 0)\n  {\n    int ticks_start = get_ticks();\n    int ticks_end;\n\n    while(timeout > 0)\n    {\n      delay(1);\n\n      if(__update_event_status())\n        break;\n\n      ticks_end = get_ticks();\n      timeout -= ticks_end - ticks_start;\n      ticks_start = ticks_end;\n    }\n  }\n  else\n    __wait_event();\n\n  update_autorepeat();\n}\n\nboolean update_event_status_delay(void)\n{\n  int delay_ticks;\n\n  if(!last_update_time)\n    last_update_time = get_ticks();\n\n  delay_ticks = UPDATE_DELAY - (get_ticks() - last_update_time);\n\n  if(delay_ticks < 0)\n    delay_ticks = 0;\n\n  delay(delay_ticks);\n  last_update_time = get_ticks();\n\n  return update_event_status();\n}\n\nvoid update_event_status_intake(void)\n{\n  int delay_ticks;\n\n  if(!last_update_time)\n    last_update_time = get_ticks();\n\n  delay_ticks = UPDATE_DELAY - (get_ticks() - last_update_time);\n\n  // wait_event will wait indefinitely unless the timeout is greater than 0.\n  if(delay_ticks < 1)\n    delay_ticks = 1;\n\n  wait_event(delay_ticks);\n  last_update_time = get_ticks();\n}\n\nstatic enum keycode emit_keysym_wrt_numlock(enum keycode key)\n{\n  const struct buffered_status *status = load_status();\n\n  if(status->numlock_status)\n  {\n    switch(key)\n    {\n      case IKEY_KP0:         return IKEY_0;\n      case IKEY_KP1:         return IKEY_1;\n      case IKEY_KP2:         return IKEY_2;\n      case IKEY_KP3:         return IKEY_3;\n      case IKEY_KP4:         return IKEY_4;\n      case IKEY_KP5:         return IKEY_5;\n      case IKEY_KP6:         return IKEY_6;\n      case IKEY_KP7:         return IKEY_7;\n      case IKEY_KP8:         return IKEY_8;\n      case IKEY_KP9:         return IKEY_9;\n      case IKEY_KP_PERIOD:   return IKEY_PERIOD;\n      case IKEY_KP_ENTER:    return IKEY_RETURN;\n      default: break;\n    }\n  }\n  else\n  {\n    switch(key)\n    {\n      case IKEY_KP0:         return IKEY_INSERT;\n      case IKEY_KP1:         return IKEY_END;\n      case IKEY_KP2:         return IKEY_DOWN;\n      case IKEY_KP3:         return IKEY_PAGEDOWN;\n      case IKEY_KP4:         return IKEY_LEFT;\n      case IKEY_KP5:         return IKEY_SPACE; // kinda arbitrary\n      case IKEY_KP6:         return IKEY_RIGHT;\n      case IKEY_KP7:         return IKEY_HOME;\n      case IKEY_KP8:         return IKEY_UP;\n      case IKEY_KP9:         return IKEY_PAGEUP;\n      case IKEY_KP_PERIOD:   return IKEY_DELETE;\n      case IKEY_KP_ENTER:    return IKEY_RETURN;\n      default: break;\n    }\n  }\n  return key;\n}\n\nstatic enum keycode reverse_keysym_numlock(enum keycode key)\n{\n  const struct buffered_status *status = load_status();\n\n  if(status->numlock_status)\n  {\n    switch(key)\n    {\n      case IKEY_0:         return IKEY_KP0;\n      case IKEY_1:         return IKEY_KP1;\n      case IKEY_2:         return IKEY_KP2;\n      case IKEY_3:         return IKEY_KP3;\n      case IKEY_4:         return IKEY_KP4;\n      case IKEY_5:         return IKEY_KP5;\n      case IKEY_6:         return IKEY_KP6;\n      case IKEY_7:         return IKEY_KP7;\n      case IKEY_8:         return IKEY_KP8;\n      case IKEY_9:         return IKEY_KP9;\n      case IKEY_PERIOD:    return IKEY_KP_PERIOD;\n      case IKEY_RETURN:    return IKEY_KP_ENTER;\n      default: break;\n    }\n  }\n  else\n  {\n    switch(key)\n    {\n      case IKEY_INSERT:    return IKEY_KP0;\n      case IKEY_END:       return IKEY_KP1;\n      case IKEY_DOWN:      return IKEY_KP2;\n      case IKEY_PAGEDOWN:  return IKEY_KP3;\n      case IKEY_LEFT:      return IKEY_KP4;\n      case IKEY_SPACE:     return IKEY_KP5; // kinda arbitrary\n      case IKEY_RIGHT:     return IKEY_KP6;\n      case IKEY_HOME:      return IKEY_KP7;\n      case IKEY_UP:        return IKEY_KP8;\n      case IKEY_PAGEUP:    return IKEY_KP9;\n      case IKEY_DELETE:    return IKEY_KP_PERIOD;\n      case IKEY_RETURN:    return IKEY_KP_ENTER;\n      default: break;\n    }\n  }\n  return key;\n}\n\nuint32_t get_key(enum keycode_type type)\n{\n  struct buffered_status *status = (struct buffered_status *)load_status();\n\n  switch(type)\n  {\n    case keycode_pc_xt:\n      return convert_internal_xt(status->key);\n\n    case keycode_internal:\n      return status->key;\n\n    case keycode_internal_wrt_numlock:\n      return emit_keysym_wrt_numlock(status->key);\n\n    case keycode_text_ascii:\n    {\n      while(status->unicode_pos < status->unicode_length)\n      {\n        uint32_t unicode = status->unicode[status->unicode_pos++];\n        if(KEYCODE_IS_ASCII(unicode))\n          return unicode;\n      }\n      break;\n    }\n\n    case keycode_text_unicode:\n    {\n      if(status->unicode_pos < status->unicode_length)\n        return status->unicode[status->unicode_pos++];\n\n      break;\n    }\n  }\n  return 0;\n}\n\nuint32_t get_key_status(enum keycode_type type, uint32_t index)\n{\n  const struct buffered_status *status = load_status();\n  index = MIN((int)index, STATUS_NUM_KEYCODES - 1);\n\n  switch(type)\n  {\n    case keycode_pc_xt:\n    {\n      enum keycode first, second, third;\n      first = convert_xt_internal(index, &second, &third);\n      return status->keymap[first] || status->keymap[second] || status->keymap[third];\n    }\n\n    case keycode_internal:\n      return status->keymap[index];\n\n    case keycode_internal_wrt_numlock:\n      return status->keymap[index] ||\n        status->keymap[reverse_keysym_numlock(index)];\n\n    default:\n      return 0;\n  }\n}\n\nuint32_t get_last_key(enum keycode_type type)\n{\n  const struct buffered_status *status = load_status();\n\n  switch(type)\n  {\n    case keycode_pc_xt:\n      return convert_internal_xt(status->key_pressed);\n\n    case keycode_internal:\n      return status->key_pressed;\n\n    case keycode_internal_wrt_numlock:\n      return emit_keysym_wrt_numlock(status->key_pressed);\n\n    default:\n      return 0;\n  }\n}\n\nuint32_t get_last_key_released(enum keycode_type type)\n{\n  const struct buffered_status *status = load_status();\n\n  switch(type)\n  {\n    case keycode_pc_xt:\n      return convert_internal_xt(status->key_release);\n\n    case keycode_internal:\n      return status->key_release;\n\n    case keycode_internal_wrt_numlock:\n      return emit_keysym_wrt_numlock(status->key_release);\n\n    default:\n      return 0;\n  }\n}\n\nvoid get_mouse_position(int *char_x, int *char_y)\n{\n  const struct buffered_status *status = load_status();\n  *char_x = status->mouse_x;\n  *char_y = status->mouse_y;\n}\n\nvoid get_mouse_pixel_position(int *pixel_x, int *pixel_y)\n{\n  const struct buffered_status *status = load_status();\n  *pixel_x = status->mouse_pixel_x;\n  *pixel_y = status->mouse_pixel_y;\n}\n\nint get_mouse_x(void)\n{\n  const struct buffered_status *status = load_status();\n  return status->mouse_x;\n}\n\nint get_mouse_y(void)\n{\n  const struct buffered_status *status = load_status();\n  return status->mouse_y;\n}\n\nint get_mouse_pixel_x(void)\n{\n  const struct buffered_status *status = load_status();\n  return status->mouse_pixel_x;\n}\n\nint get_mouse_pixel_y(void)\n{\n  const struct buffered_status *status = load_status();\n  return status->mouse_pixel_y;\n}\n\nvoid get_mouse_movement(int *delta_x, int *delta_y)\n{\n  const struct buffered_status *status = load_status();\n  *delta_x = status->mouse_x - status->mouse_last_x;\n  *delta_y = status->mouse_y - status->mouse_last_y;\n}\n\nvoid get_mouse_pixel_movement(int *delta_x, int *delta_y)\n{\n  const struct buffered_status *status = load_status();\n  *delta_x = status->mouse_pixel_x - status->mouse_last_pixel_x;\n  *delta_y = status->mouse_pixel_y - status->mouse_last_pixel_y;\n}\n\nboolean get_mouse_drag(void)\n{\n  const struct buffered_status *status = load_status();\n  return status->mouse_drag_state > 0;\n}\n\nenum mouse_button get_mouse_press(void)\n{\n  const struct buffered_status *status = load_status();\n\n  if(status->mouse_button <= MOUSE_BUTTON_RIGHT)\n    return status->mouse_button;\n\n  return MOUSE_NO_BUTTON;\n}\n\nenum mouse_button get_mouse_press_ext(void)\n{\n  const struct buffered_status *status = load_status();\n  return status->mouse_button;\n}\n\nboolean get_mouse_held(int button)\n{\n  const struct buffered_status *status = load_status();\n\n  if(button >= 1 && button <= 32)\n    if(status->mouse_button_state & MOUSE_BUTTON(button))\n      return true;\n\n  return false;\n}\n\nuint32_t get_mouse_status(void)\n{\n  const struct buffered_status *status = load_status();\n  return status->mouse_button_state;\n}\n\nvoid warp_mouse(int char_x, int char_y)\n{\n  int real_x, real_y;\n  int pixel_x = (char_x * CHAR_W) + CHAR_W / 2;\n  int pixel_y = (char_y * CHAR_H) + CHAR_H / 2;\n  struct buffered_status *status = store_status();\n\n  status->mouse_x = char_x;\n  status->mouse_y = char_y;\n  status->mouse_pixel_x = pixel_x;\n  status->mouse_pixel_y = pixel_y;\n\n  set_screen_coords(pixel_x, pixel_y, &real_x, &real_y);\n  status->warped_mouse_x = real_x;\n  status->warped_mouse_y = real_y;\n\n  __warp_mouse(real_x, real_y);\n}\n\nvoid warp_mouse_x(int char_x)\n{\n  warp_mouse_pixel_x(char_x * CHAR_W + CHAR_W / 2);\n}\n\nvoid warp_mouse_y(int char_y)\n{\n  warp_mouse_pixel_y(char_y * CHAR_H + CHAR_H / 2);\n}\n\nvoid warp_mouse_pixel_x(int pixel_x)\n{\n  struct buffered_status *status = store_status();\n  int real_x, real_y;\n  int char_x = pixel_x / CHAR_W;\n\n  status->mouse_x = char_x;\n  status->mouse_pixel_x = pixel_x;\n\n  set_screen_coords(pixel_x, status->mouse_pixel_y, &real_x, &real_y);\n  status->warped_mouse_x = real_x;\n\n  __warp_mouse(real_x, status->warped_mouse_y);\n}\n\nvoid warp_mouse_pixel_y(int pixel_y)\n{\n  struct buffered_status *status = store_status();\n  int real_x, real_y;\n  int char_y = pixel_y / CHAR_H;\n\n  status->mouse_y = char_y;\n  status->mouse_pixel_y = pixel_y;\n\n  set_screen_coords(status->mouse_pixel_x, pixel_y, &real_x, &real_y);\n  status->warped_mouse_y = real_y;\n\n  __warp_mouse(status->warped_mouse_x, real_y);\n}\n\nvoid force_last_key(enum keycode_type type, int val)\n{\n  struct buffered_status *status = store_status();\n\n  switch(type)\n  {\n    case keycode_pc_xt:\n    {\n      enum keycode second, third;\n      status->key_pressed = convert_xt_internal(val, &second, &third);\n      break;\n    }\n\n    case keycode_internal:\n    case keycode_internal_wrt_numlock:\n    {\n      status->key_pressed = (enum keycode)val;\n      break;\n    }\n\n    default:\n      break;\n  }\n}\n\nvoid force_release_all_keys(void)\n{\n  struct buffered_status *status = store_status();\n\n  force_last_key(keycode_internal, 0);\n  memset(status->keymap, 0, sizeof(status->keymap));\n\n  status->key = 0;\n  status->mouse_button = 0;\n  status->mouse_repeat = 0;\n  status->mouse_button_state = 0;\n  status->mouse_repeat_state = 0;\n  status->mouse_drag_state = 0;\n  status->joystick_action = 0;\n  status->joystick_repeat = 0;\n  status->joystick_repeat_state = 0;\n}\n\nboolean get_alt_status(enum keycode_type type)\n{\n  return\n#ifdef __APPLE__\n  // Some Mac users have indicated that they prefer to use the command key.\n   get_key_status(type, IKEY_LSUPER) ||\n   get_key_status(type, IKEY_RSUPER) ||\n#endif\n   (!input.left_alt_is_altgr && get_key_status(type, IKEY_LALT)) ||\n   (!input.right_alt_is_altgr && get_key_status(type, IKEY_RALT));\n}\n\nboolean get_altgr_status(enum keycode_type type)\n{\n  return\n   (input.left_alt_is_altgr && get_key_status(type, IKEY_LALT)) ||\n   (input.right_alt_is_altgr && get_key_status(type, IKEY_RALT)) ||\n   get_key_status(type, IKEY_ALTGR);\n}\n\nboolean get_shift_status(enum keycode_type type)\n{\n  return\n   get_key_status(type, IKEY_LSHIFT) ||\n   get_key_status(type, IKEY_RSHIFT);\n}\n\nboolean get_ctrl_status(enum keycode_type type)\n{\n  return\n   get_key_status(type, IKEY_LCTRL) ||\n   get_key_status(type, IKEY_RCTRL);\n}\n\n// Nothing uses this right now, but it needs to be settable for networking.\nvoid set_unfocus_pause(boolean value)\n{\n  input.unfocus_pause = value;\n}\n\n// Nothing uses this right now, but it needs to be settable for networking.\n// TODO: when set this ought to actually reallocate the buffered events.\nvoid set_num_buffered_events(unsigned int value)\n{\n  num_buffered_events = MAX(1, value);\n}\n\nvoid key_press(struct buffered_status *status, enum keycode key)\n{\n  // Prevent invalid keycodes from writing out-of-bounds.\n  enum keycode map_key = MIN((int)key, STATUS_NUM_KEYCODES - 1);\n  status->keymap[map_key] = 1;\n  status->key_pressed = key;\n  status->key = key;\n  status->key_repeat = key;\n  status->keypress_time = get_ticks();\n  status->key_release = IKEY_UNKNOWN;\n}\n\nvoid key_press_unicode(struct buffered_status *status, uint32_t unicode,\n boolean repeating)\n{\n  if(unicode)\n  {\n    if(status->unicode_length < KEY_UNICODE_MAX)\n    {\n      if(status->unicode_length >= status->unicode_alloc)\n      {\n        int alloc = MAX(4, status->unicode_alloc << 1);\n        uint32_t *unicode =\n         (uint32_t *)crealloc(status->unicode, alloc * sizeof(uint32_t));\n        if(unicode)\n        {\n          status->unicode = unicode;\n          status->unicode_alloc = alloc;\n        }\n      }\n      if(status->unicode_length < status->unicode_alloc)\n        status->unicode[status->unicode_length++] = unicode;\n    }\n    else\n      status->unicode[KEY_UNICODE_MAX - 1] = unicode;\n  }\n  if(repeating)\n    status->unicode_repeat = unicode;\n}\n\nvoid key_release(struct buffered_status *status, enum keycode key)\n{\n  // Prevent invalid keycodes from writing out-of-bounds.\n  enum keycode map_key = MIN((int)key, STATUS_NUM_KEYCODES - 1);\n  status->keymap[map_key] = 0;\n  status->key_release = key;\n\n  if(status->key_repeat == key)\n  {\n    status->key_repeat = IKEY_UNKNOWN;\n    status->unicode_repeat = 0;\n  }\n}\n\nboolean has_unicode_input(void)\n{\n  const struct buffered_status *status = load_status();\n  return status->unicode_length > status->unicode_pos;\n}\n\nboolean get_exit_status(void)\n{\n  const struct buffered_status *status = load_status();\n  return status->exit_status;\n}\n\nboolean set_exit_status(boolean value)\n{\n  struct buffered_status *status = store_status();\n  boolean exit_status = status->exit_status;\n  status->exit_status = value;\n  return exit_status;\n}\n\n/**\n * @return true if the screen keyboard is active, otherwise false.\n */\nboolean screen_keyboard_is_active(void)\n{\n  return platform_is_screen_keyboard_active();\n}\n\n/**\n * Toggle the state of the screen keyboard and report whether or\n * not the event was handled.\n *\n * Note platforms may not actually change the keyboard state\n * (e.g. during an animation), but will still report the change\n * as handled. A return value of false should be interpreted as\n * lack of a functioning screen keyboard.\n *\n * @return true if the event is supported and handled, otherwise false.\n */\nboolean screen_keyboard_toggle_state(void)\n{\n  if(platform_has_screen_keyboard())\n  {\n    if(!platform_is_screen_keyboard_active())\n    {\n      if(platform_show_screen_keyboard())\n        return true;\n    }\n    else\n    {\n      if(platform_hide_screen_keyboard())\n        return true;\n    }\n  }\n  return false;\n}\n\nstruct keycode_name\n{\n  const char * const name;\n  const enum keycode value;\n};\n\nstruct joystick_action_name\n{\n  const char * const name;\n  const int value;\n};\n\nstatic const struct keycode_name keycode_names[] =\n{\n  { \"0\",            IKEY_0 },\n  { \"1\",            IKEY_1 },\n  { \"2\",            IKEY_2 },\n  { \"3\",            IKEY_3 },\n  { \"4\",            IKEY_4 },\n  { \"5\",            IKEY_5 },\n  { \"6\",            IKEY_6 },\n  { \"7\",            IKEY_7 },\n  { \"8\",            IKEY_8 },\n  { \"9\",            IKEY_9 },\n  { \"a\",            IKEY_a },\n  { \"b\",            IKEY_b },\n  { \"backquote\",    IKEY_BACKQUOTE },\n  { \"backslash\",    IKEY_BACKSLASH },\n  { \"backspace\",    IKEY_BACKSPACE },\n  { \"break\",        IKEY_BREAK },\n  { \"c\",            IKEY_c },\n  { \"capslock\",     IKEY_CAPSLOCK },\n  { \"comma\",        IKEY_COMMA },\n  { \"d\",            IKEY_d },\n  { \"delete\",       IKEY_DELETE },\n  { \"down\",         IKEY_DOWN },\n  { \"e\",            IKEY_e },\n  { \"end\",          IKEY_END },\n  { \"equals\",       IKEY_EQUALS },\n  { \"escape\",       IKEY_ESCAPE },\n  { \"f\",            IKEY_f },\n  { \"f1\",           IKEY_F1 },\n  { \"f10\",          IKEY_F10 },\n  { \"f11\",          IKEY_F11 },\n  { \"f12\",          IKEY_F12 },\n  { \"f2\",           IKEY_F2 },\n  { \"f3\",           IKEY_F3 },\n  { \"f4\",           IKEY_F4 },\n  { \"f5\",           IKEY_F5 },\n  { \"f6\",           IKEY_F6 },\n  { \"f7\",           IKEY_F7 },\n  { \"f8\",           IKEY_F8 },\n  { \"f9\",           IKEY_F9 },\n  { \"g\",            IKEY_g },\n  { \"h\",            IKEY_h },\n  { \"home\",         IKEY_HOME },\n  { \"i\",            IKEY_i },\n  { \"insert\",       IKEY_INSERT },\n  { \"j\",            IKEY_j },\n  { \"k\",            IKEY_k },\n  { \"kp0\",          IKEY_KP0 },\n  { \"kp1\",          IKEY_KP1 },\n  { \"kp2\",          IKEY_KP2 },\n  { \"kp3\",          IKEY_KP3 },\n  { \"kp4\",          IKEY_KP4 },\n  { \"kp5\",          IKEY_KP5 },\n  { \"kp6\",          IKEY_KP6 },\n  { \"kp7\",          IKEY_KP7 },\n  { \"kp8\",          IKEY_KP8 },\n  { \"kp9\",          IKEY_KP9 },\n  { \"kp_divide\",    IKEY_KP_DIVIDE },\n  { \"kp_enter\",     IKEY_KP_ENTER },\n  { \"kp_minus\",     IKEY_KP_MINUS },\n  { \"kp_multiply\",  IKEY_KP_MULTIPLY },\n  { \"kp_period\",    IKEY_KP_PERIOD },\n  { \"kp_plus\",      IKEY_KP_PLUS },\n  { \"l\",            IKEY_l },\n  { \"lalt\",         IKEY_LALT },\n  { \"lctrl\",        IKEY_LCTRL },\n  { \"left\",         IKEY_LEFT },\n  { \"leftbracket\",  IKEY_LEFTBRACKET },\n  { \"lshift\",       IKEY_LSHIFT },\n  { \"lsuper\",       IKEY_LSUPER },\n  { \"m\",            IKEY_m },\n  { \"menu\",         IKEY_MENU },\n  { \"minus\",        IKEY_MINUS },\n  { \"n\",            IKEY_n },\n  { \"numlock\",      IKEY_NUMLOCK },\n  { \"o\",            IKEY_o },\n  { \"p\",            IKEY_p },\n  { \"pagedown\",     IKEY_PAGEDOWN },\n  { \"pageup\",       IKEY_PAGEUP },\n  { \"period\",       IKEY_PERIOD },\n  { \"q\",            IKEY_q },\n  { \"quote\",        IKEY_QUOTE },\n  { \"r\",            IKEY_r },\n  { \"ralt\",         IKEY_RALT },\n  { \"rctrl\",        IKEY_RCTRL },\n  { \"return\",       IKEY_RETURN },\n  { \"right\",        IKEY_RIGHT },\n  { \"rightbracket\", IKEY_RIGHTBRACKET },\n  { \"rshift\",       IKEY_RSHIFT },\n  { \"rsuper\",       IKEY_RSUPER },\n  { \"s\",            IKEY_s },\n  { \"scrolllock\",   IKEY_SCROLLOCK },\n  { \"semicolon\",    IKEY_SEMICOLON },\n  { \"slash\",        IKEY_SLASH },\n  { \"space\",        IKEY_SPACE },\n  { \"sysreq\",       IKEY_SYSREQ },\n  { \"t\",            IKEY_t },\n  { \"tab\",          IKEY_TAB },\n  { \"u\",            IKEY_u },\n  { \"up\",           IKEY_UP },\n  { \"v\",            IKEY_v },\n  { \"w\",            IKEY_w },\n  { \"x\",            IKEY_x },\n  { \"y\",            IKEY_y },\n  { \"z\",            IKEY_z },\n};\n\nstatic const struct joystick_action_name joystick_action_names[] =\n{\n  { \"a\",        JOY_A },\n  { \"b\",        JOY_B },\n  { \"down\",     JOY_DOWN },\n  { \"l_down\",   JOY_L_DOWN },\n  { \"l_left\",   JOY_L_LEFT },\n  { \"l_right\",  JOY_L_RIGHT },\n  { \"l_up\",     JOY_L_UP },\n  { \"left\",     JOY_LEFT },\n  { \"lshoulder\",JOY_LSHOULDER },\n  { \"lstick\",   JOY_LSTICK },\n  { \"ltrigger\", JOY_LTRIGGER },\n  { \"r_down\",   JOY_R_DOWN },\n  { \"r_left\",   JOY_R_LEFT },\n  { \"r_right\",  JOY_R_RIGHT },\n  { \"r_up\",     JOY_R_UP },\n  { \"right\",    JOY_RIGHT },\n  { \"rshoulder\",JOY_RSHOULDER },\n  { \"rstick\",   JOY_RSTICK },\n  { \"rtrigger\", JOY_RTRIGGER },\n  { \"select\",   JOY_SELECT },\n  { \"start\",    JOY_START },\n  { \"up\",       JOY_UP },\n  { \"x\",        JOY_X },\n  { \"y\",        JOY_Y }\n};\n\nstatic const struct joystick_action_name joystick_axis_names[] =\n{\n  { \"ltrigger\", JOY_AXIS_LEFT_TRIGGER },\n  { \"lx\",       JOY_AXIS_LEFT_X },\n  { \"ly\",       JOY_AXIS_LEFT_Y },\n  { \"rtrigger\", JOY_AXIS_RIGHT_TRIGGER },\n  { \"rx\",       JOY_AXIS_RIGHT_X },\n  { \"ry\",       JOY_AXIS_RIGHT_Y },\n};\n\n/* Get an internal keycode by config name. This does NOT include the key_\n * prefix used by the config file. */\nstatic enum keycode keycode_by_name(const char *name)\n{\n  int top = ARRAY_SIZE(keycode_names) - 1;\n  int bottom = 0;\n  int middle;\n  int cmpval;\n\n  while(bottom <= top)\n  {\n    middle = (bottom + top) / 2;\n    cmpval = strcasecmp(name, keycode_names[middle].name);\n\n    if(cmpval > 0)\n      bottom = middle + 1;\n    else\n\n    if(cmpval < 0)\n      top = middle - 1;\n\n    else\n      return keycode_names[middle].value;\n  }\n  return IKEY_UNKNOWN;\n}\n\n/* Get a joystick action by name. This does NOT include the joy#. or act_\n * prefix used by Robotic and the config file, respectively. */\nstatic enum joystick_action joystick_action_by_name(const char *name)\n{\n  int top = ARRAY_SIZE(joystick_action_names) - 1;\n  int bottom = 0;\n  int middle;\n  int cmpval;\n  if(!name)\n    return JOY_NO_ACTION;\n\n  while(bottom <= top)\n  {\n    middle = (bottom + top) / 2;\n    cmpval = strcasecmp(name, joystick_action_names[middle].name);\n\n    if(cmpval > 0)\n      bottom = middle + 1;\n    else\n\n    if(cmpval < 0)\n      top = middle - 1;\n\n    else\n      return joystick_action_names[middle].value;\n  }\n  return JOY_NO_ACTION;\n}\n\n/* Get a joystick axis by name. This does NOT include the joy#.axis_ or axis_\n * prefix used by Robotic and the config file, respectively. */\nstatic enum joystick_special_axis joystick_axis_by_name(const char *name)\n{\n  size_t i;\n  for(i = 0; i < ARRAY_SIZE(joystick_axis_names); i++)\n    if(!strcasecmp(name, joystick_axis_names[i].name))\n      return (enum joystick_special_axis)joystick_axis_names[i].value;\n\n  return JOY_NO_AXIS;\n}\n\n/**\n * Provide direct access to the joystick map structure. This is currently only\n * used for config file unit tests but it might be useful later for the\n * settings menu.\n */\nstruct joystick_map *get_joystick_map(boolean is_global)\n{\n  if(is_global)\n    return &(input.joystick_global_map);\n  else\n    return &(input.joystick_game_map);\n}\n\n/**\n * A joystick can be mapped to either an int from 0 to 32767 (a key binding),\n * a key enum string (also a key binding), or to a joystick action enum string\n * (action binding). Read either from an input value string.\n */\nboolean joystick_parse_map_value(const char *value, int16_t *binding)\n{\n  char *next;\n  enum joystick_action action_value;\n  unsigned int key_value;\n\n  key_value = strtoul(value, &next, 10);\n  if((key_value <= INT16_MAX) && (!next[0]))\n  {\n    *binding = key_value;\n    return true;\n  }\n\n  if(!strncmp(value, \"act_\", 4))\n  {\n    action_value = joystick_action_by_name(value + 4);\n    if(action_value)\n    {\n      *binding = -((int16_t)action_value);\n      return true;\n    }\n  }\n  else\n\n  if(!strncmp(value, \"key_\", 4))\n  {\n    key_value = keycode_by_name(value + 4);\n    if(key_value)\n    {\n      *binding = key_value;\n      return true;\n    }\n  }\n  return false;\n}\n\n/**\n * Map a joystick button to a key or action value. The input value should be\n * null-terminated.\n */\nvoid joystick_map_button(int first, int last, int button, const char *value,\n boolean is_global)\n{\n  if((first <= last) && (first >= 0) && (first < MAX_JOYSTICKS) &&\n   (button >= 0) && (button < MAX_JOYSTICK_BUTTONS))\n  {\n    int16_t binding;\n    int i;\n\n    if(joystick_parse_map_value(value, &binding))\n    {\n      last = MIN(last, MAX_JOYSTICKS - 1);\n      for(i = first; i <= last; i++)\n      {\n        if(is_global)\n        {\n          input.joystick_global_map.button[i][button] = binding;\n          input.joystick_global_map.button_is_conf[i][button] = true;\n        }\n        else\n        {\n          input.joystick_game_map.button[i][button] = binding;\n          input.joystick_game_map.button_is_conf[i][button] = true;\n        }\n      }\n    }\n  }\n}\n\n/**\n * Map a joystick axis to keys or action values. The input values should be\n * null-terminated.\n */\nvoid joystick_map_axis(int first, int last, int axis, const char *neg,\n const char *pos, boolean is_global)\n{\n  if((first <= last) && (first >= 0) && (first < MAX_JOYSTICKS) &&\n   (axis >= 0) && (axis < MAX_JOYSTICK_AXES))\n  {\n    int16_t binding_neg, binding_pos;\n    int i;\n\n    if(joystick_parse_map_value(neg, &binding_neg) &&\n     joystick_parse_map_value(pos, &binding_pos))\n    {\n      last = MIN(last, MAX_JOYSTICKS - 1);\n      for(i = first; i <= last; i++)\n      {\n        if(is_global)\n        {\n          input.joystick_global_map.axis[i][axis][0] = binding_neg;\n          input.joystick_global_map.axis[i][axis][1] = binding_pos;\n          input.joystick_global_map.axis_is_conf[i][axis] = true;\n        }\n        else\n        {\n          input.joystick_game_map.axis[i][axis][0] = binding_neg;\n          input.joystick_game_map.axis[i][axis][1] = binding_pos;\n          input.joystick_game_map.axis_is_conf[i][axis] = true;\n        }\n      }\n    }\n  }\n}\n\n/**\n * Map a joystick hat to keys or action values. The input values should be\n * null-terminated.\n */\nvoid joystick_map_hat(int first, int last, const char *up, const char *down,\n const char *left, const char *right, boolean is_global)\n{\n  if((first <= last) && (first >= 0) && (first < MAX_JOYSTICKS))\n  {\n    int16_t binding_up, binding_down, binding_left, binding_right;\n    int i;\n\n    if(joystick_parse_map_value(up, &binding_up) &&\n     joystick_parse_map_value(down, &binding_down) &&\n     joystick_parse_map_value(left, &binding_left) &&\n     joystick_parse_map_value(right, &binding_right))\n    {\n      last = MIN(last, MAX_JOYSTICKS - 1);\n      for(i = first; i <= last; i++)\n      {\n        if(is_global)\n        {\n          input.joystick_global_map.hat[i][JOYHAT_UP] = binding_up;\n          input.joystick_global_map.hat[i][JOYHAT_DOWN] = binding_down;\n          input.joystick_global_map.hat[i][JOYHAT_LEFT] = binding_left;\n          input.joystick_global_map.hat[i][JOYHAT_RIGHT] = binding_right;\n          input.joystick_global_map.hat_is_conf[i] = true;\n        }\n        else\n        {\n          input.joystick_game_map.hat[i][JOYHAT_UP] = binding_up;\n          input.joystick_game_map.hat[i][JOYHAT_DOWN] = binding_down;\n          input.joystick_game_map.hat[i][JOYHAT_LEFT] = binding_left;\n          input.joystick_game_map.hat[i][JOYHAT_RIGHT] = binding_right;\n          input.joystick_game_map.hat_is_conf[i] = true;\n        }\n      }\n    }\n  }\n}\n\n/**\n * Map a generic action to a key. The input action should be null-terminated.\n * Only allow key bindings; if value resolves to an action, it is ignored.\n *\n * Alternatively, the action provided may be an axis, in which case the named\n * axis is being assigned to a real axis number.\n *\n * This function also handles the per-joystick setting show_screen_keyboard.\n * In this case, the value must resolve to an action or to 0.\n */\nvoid joystick_map_action(int first, int last, const char *action,\n const char *value, boolean is_global)\n{\n  if((first <= last) && (first >= 0) && (first < MAX_JOYSTICKS))\n  {\n    enum joystick_action action_value;\n    int16_t binding;\n    int i;\n\n    if(!joystick_parse_map_value(value, &binding))\n      return;\n\n    last = MIN(last, MAX_JOYSTICKS - 1);\n\n    if(!strcasecmp(action, \"show_screen_keyboard\"))\n    {\n      // Must be an action or 0. Valid for the global map only.\n      if(binding > 0 || !is_global)\n        return;\n\n      for(i = first; i <= last; i++)\n      {\n        input.joystick_global_map.show_screen_keyboard_action[i] = -binding;\n        input.joystick_global_map.show_keyboard_is_conf[i] = true;\n      }\n      return;\n    }\n\n    action_value = joystick_action_by_name(action);\n    if(action_value != JOY_NO_ACTION && binding > 0)\n    {\n      for(i = first; i <= last; i++)\n      {\n        if(is_global)\n        {\n          input.joystick_global_map.action[i][action_value] = binding;\n          input.joystick_global_map.action_is_conf[i][action_value] = true;\n        }\n        else\n          input.joystick_game_map.action[i][action_value] = binding;\n      }\n    }\n    else\n\n    if(!strncasecmp(action, \"axis_\", 5) && (binding > 0) &&\n     (binding <= MAX_JOYSTICK_AXES))\n    {\n      enum joystick_special_axis axis_value = joystick_axis_by_name(action + 5);\n\n      if(axis_value == JOY_NO_AXIS)\n        return;\n\n      for(i = first; i <= last; i++)\n      {\n        if(is_global)\n          input.joystick_global_map.special_axis[i][binding - 1] = axis_value;\n      }\n    }\n  }\n}\n\n/**\n * Set a fallback screen keyboard button for a joystick (console ports)\n * if pad.config failed to load. Call during/after init_event.\n */\nvoid joystick_map_fallback_keyboard_button(int joystick, int button)\n{\n  assert(joystick >= 0 && joystick < MAX_JOYSTICKS &&\n   button >= 0 && button < MAX_JOYSTICK_BUTTONS);\n\n  if(!input.joystick_global_map.button_is_conf[joystick][button] &&\n   input.joystick_global_map.button[joystick][button] == 0)\n  {\n    /* Positive show_screen_keyboard action -> negative button */\n    input.joystick_global_map.button[joystick][button] =\n     -input.joystick_global_map.show_screen_keyboard_action[joystick];\n  }\n}\n\n/**\n * Reset the gameplay joystick maps to the global maps. Use this before\n * loading game-specific joystick bindings.\n */\nvoid joystick_reset_game_map(void)\n{\n  joystick_game_bindings = true;\n  memcpy(&(input.joystick_game_map), &(input.joystick_global_map),\n   sizeof(struct joystick_map));\n}\n\n/**\n * Switch between global and gameplay joystick binding modes. Use 'true' to\n * enable gameplay mode joystick handling.\n */\nvoid joystick_set_game_mode(boolean enable)\n{\n  joystick_game_mode = enable;\n}\n\n/**\n * Enable or disable gameplay bindings in gameplay mode.\n */\nvoid joystick_set_game_bindings(boolean enable)\n{\n  joystick_game_bindings = enable;\n}\n\n/**\n * Enable or disable joystick binding hacks for legacy event loops. Enabling\n * this fixes bindings for non-context event loops, but this should be disabled\n * when processing events for the main loop.\n */\nvoid joystick_set_legacy_loop_hacks(boolean enable)\n{\n  joystick_legacy_loop_hacks = enable;\n}\n\n/**\n * Set the threshold for joystick mapped axis presses. Higher values require\n * more movement to trigger a press.\n */\nvoid joystick_set_axis_threshold(uint16_t threshold)\n{\n  input.joystick_axis_threshold = threshold;\n}\n\n/**\n * Determine which key (if any) a joystick press should bind to\n * within the current context.\n */\nstatic void joystick_resolve_bindings(int joystick, int16_t global_binding,\n int16_t game_binding, enum keycode *key, enum joystick_action *action)\n{\n  // Global key bindings\n  // HACK: places where the no context hacks are enabled are never gameplay,\n  // but may be reached while the game mode flag is still enabled. We always\n  // want to use global bindings outside of contexts.\n  if(!joystick_game_mode || joystick_legacy_loop_hacks)\n  {\n    if(global_binding > 0)\n      *key = (enum keycode)global_binding;\n  }\n\n  // Gameplay bindings\n  else\n  {\n    if(game_binding < 0 && (-game_binding < NUM_JOYSTICK_ACTIONS))\n    {\n      *action = (enum joystick_action)(-game_binding);\n\n      if(joystick_game_bindings &&\n       input.joystick_game_map.action[joystick][-game_binding] > 0)\n        *key = (enum keycode)input.joystick_game_map.action[joystick][*action];\n    }\n    else\n\n    if(game_binding > 0 && joystick_game_bindings)\n      *key = (enum keycode)game_binding;\n  }\n}\n\nenum joy_press_type\n{\n  JOY_BUTTON,\n  JOY_AXIS,\n  JOY_HAT\n};\n\n/**\n * \"Press\" a joystick button/axis/hat/etc; fake a key press and/or activate\n * the global action used by UI contexts where appropriate.\n */\nstatic void joystick_press(struct buffered_status *status, int joystick,\n enum joy_press_type type, int num, int16_t global_binding, int16_t game_binding)\n{\n  int pos = status->joystick_press_count[joystick];\n\n  /* Special: hijack the show screen keyboard button. */\n  int16_t kbd = input.joystick_global_map.show_screen_keyboard_action[joystick];\n  if(global_binding < 0 && -global_binding == kbd)\n    if(screen_keyboard_toggle_state())\n      return;\n\n  if(pos < MAX_JOYSTICK_PRESS)\n  {\n    enum joystick_action press_action = JOY_NO_ACTION;\n    enum keycode press_key = IKEY_UNKNOWN;\n\n    joystick_resolve_bindings(joystick, global_binding, game_binding,\n     &press_key, &press_action);\n\n    if(press_key || press_action)\n    {\n      status->joystick_press[joystick][pos].type = type;\n      status->joystick_press[joystick][pos].num = num;\n      status->joystick_press[joystick][pos].key = press_key;\n      status->joystick_press[joystick][pos].action = press_action;\n      status->joystick_press_count[joystick]++;\n\n      if(press_action)\n        status->joystick_action_status[joystick][press_action] = true;\n      if(press_key)\n      {\n        uint32_t unicode = KEYCODE_IS_ASCII(press_key) ? press_key : 0;\n        key_press(status, press_key);\n        key_press_unicode(status, unicode, true);\n      }\n    }\n  }\n\n  // Global action press.\n  if(global_binding < 0 && (-global_binding < NUM_JOYSTICK_ACTIONS))\n  {\n    status->joystick_action = -global_binding;\n    status->joystick_repeat = -global_binding;\n    status->joystick_repeat_id = joystick;\n    status->joystick_repeat_state = 1;\n    status->joystick_time = get_ticks();\n  }\n}\n\n/**\n * \"Release\" a joystick button/axis/hat/etc; fake a key release and/or disable\n * the global action used by UI contexts where appropriate.\n */\nstatic void joystick_release(struct buffered_status *status, int joystick,\n enum joy_press_type type, int num, int16_t global_binding, int16_t game_binding)\n{\n  int count = status->joystick_press_count[joystick];\n  if(count > 0)\n  {\n    struct joystick_press *p;\n    int i;\n\n    for(i = 0; i < count; i++)\n    {\n      p = &(status->joystick_press[joystick][i]);\n\n      if(p->type == type && p->num == num)\n      {\n        if(p->action)\n          status->joystick_action_status[joystick][p->action] = false;\n        if(p->key)\n          key_release(status, p->key);\n\n        count--;\n        status->joystick_press_count[joystick] = count;\n        status->joystick_press[joystick][i] =\n         status->joystick_press[joystick][count];\n        i--;\n      }\n    }\n  }\n\n  // Global action release.\n  if(global_binding < 0 && (-global_binding < NUM_JOYSTICK_ACTIONS))\n  {\n    if(status->joystick_repeat_id == joystick &&\n     (int)status->joystick_repeat == -global_binding)\n    {\n      status->joystick_repeat = JOY_NO_ACTION;\n      status->joystick_repeat_state = 0;\n    }\n  }\n}\n\n/**\n * Press a joystick button. Event handlers should use this function.\n */\nvoid joystick_button_press(struct buffered_status *status,\n int joystick, int button)\n{\n  if((joystick >= 0) && (joystick < MAX_JOYSTICKS) &&\n   (button >= 0) && (button < MAX_JOYSTICK_BUTTONS) &&\n   !status->joystick_button[joystick][button])\n  {\n    int16_t global_binding = input.joystick_global_map.button[joystick][button];\n    int16_t game_binding = input.joystick_game_map.button[joystick][button];\n\n    status->joystick_button[joystick][button] = true;\n    joystick_press(status, joystick, JOY_BUTTON, button,\n     global_binding, game_binding);\n  }\n}\n\n/**\n * Release a joystick button. Event handlers should use this function.\n */\nvoid joystick_button_release(struct buffered_status *status,\n int joystick, int button)\n{\n  if((joystick >= 0) && (joystick < MAX_JOYSTICKS) &&\n   (button >= 0) && (button < MAX_JOYSTICK_BUTTONS))\n  {\n    int16_t global_binding = input.joystick_global_map.button[joystick][button];\n    int16_t game_binding = input.joystick_game_map.button[joystick][button];\n\n    status->joystick_button[joystick][button] = false;\n    joystick_release(status, joystick, JOY_BUTTON, button,\n     global_binding, game_binding);\n  }\n}\n\n/**\n * Update a joystick hat for a given direction. Event handlers should use this\n * function.\n */\nvoid joystick_hat_update(struct buffered_status *status,\n int joystick, enum joystick_hat dir, boolean dir_active)\n{\n  if((joystick >= 0) && (joystick < MAX_JOYSTICKS) &&\n   (dir < NUM_JOYSTICK_HAT_DIRS))\n  {\n    int16_t global_binding = input.joystick_global_map.hat[joystick][dir];\n    int16_t game_binding = input.joystick_game_map.hat[joystick][dir];\n\n    if(dir_active)\n    {\n      if(!status->joystick_hat[joystick][dir])\n        joystick_press(status, joystick, JOY_HAT, dir,\n         global_binding, game_binding);\n    }\n    else\n    {\n      if(status->joystick_hat[joystick][dir])\n        joystick_release(status, joystick, JOY_HAT, dir,\n         global_binding, game_binding);\n    }\n\n    status->joystick_hat[joystick][dir] = !!dir_active;\n  }\n}\n\n/**\n * We store analog axis values, but the axis maps are digital. Return\n * the axis map position for an analog axis value or -1 for the dead zone.\n */\nstatic int joystick_axis_to_digital(int16_t value)\n{\n  if(value > input.joystick_axis_threshold)\n    return 1;\n  else\n\n  if(value < -input.joystick_axis_threshold)\n    return 0;\n\n  return -1;\n}\n\nstatic void joystick_axis_press(struct buffered_status *status,\n int joystick, int axis, int dir)\n{\n  int16_t global_binding = input.joystick_global_map.axis[joystick][axis][dir];\n  int16_t game_binding = input.joystick_game_map.axis[joystick][axis][dir];\n\n  joystick_press(status, joystick, JOY_AXIS, (axis * 2) + dir,\n   global_binding, game_binding);\n}\n\nstatic void joystick_axis_release(struct buffered_status *status,\n int joystick, int axis, int dir)\n{\n  int16_t global_binding = input.joystick_global_map.axis[joystick][axis][dir];\n  int16_t game_binding = input.joystick_game_map.axis[joystick][axis][dir];\n\n  joystick_release(status, joystick, JOY_AXIS, (axis * 2) + dir,\n   global_binding, game_binding);\n}\n\n/**\n * Update a joystick axis. Event handlers should use this function.\n */\nvoid joystick_axis_update(struct buffered_status *status,\n int joystick, int axis, int16_t value)\n{\n  if((joystick >= 0) && (joystick < MAX_JOYSTICKS) &&\n   (axis >= 0) && (axis < MAX_JOYSTICK_AXES))\n  {\n    int16_t last_value = status->joystick_axis[joystick][axis];\n    int last_digital_value = joystick_axis_to_digital(last_value);\n    int digital_value = joystick_axis_to_digital(value);\n\n    status->joystick_axis[joystick][axis] = value;\n\n    // Might be a special axis.\n    if(input.joystick_global_map.special_axis[joystick][axis])\n      joystick_special_axis_update(status, joystick,\n       input.joystick_global_map.special_axis[joystick][axis], value);\n\n    if(digital_value != -1)\n    {\n      if(last_digital_value != digital_value)\n        joystick_axis_press(status, joystick, axis, digital_value);\n\n      if(last_digital_value == (digital_value ^ 1))\n        joystick_axis_release(status, joystick, axis, last_digital_value);\n    }\n    else\n\n    if(last_digital_value != -1)\n    {\n      joystick_axis_release(status, joystick, axis, last_digital_value);\n    }\n  }\n}\n\n/**\n * Update the value of a named axis. This usually corresponds to a regular axis.\n */\nvoid joystick_special_axis_update(struct buffered_status *status,\n int joystick, enum joystick_special_axis axis, int16_t value)\n{\n  if((joystick >= 0) && (joystick < MAX_JOYSTICKS) &&\n   (axis > JOY_NO_AXIS) && (axis < NUM_JOYSTICK_SPECIAL_AXES))\n  {\n    status->joystick_special_axis_status[joystick][axis] = value;\n  }\n}\n\n/**\n * Set the current active status for a joystick.\n */\nvoid joystick_set_active(struct buffered_status *status, int joystick,\n boolean active)\n{\n  if(joystick >= 0 && joystick < MAX_JOYSTICKS)\n    status->joystick_active[joystick] = active;\n}\n\n/**\n * Release any active inputs for a given joystick and clear other info.\n * Use this function if a joystick is removed.\n */\nvoid joystick_clear(struct buffered_status *status, int joystick)\n{\n  int count = status->joystick_press_count[joystick];\n  int i;\n\n  for(i = 0; i < count; i++)\n    key_release(status, status->joystick_press[joystick][i].key);\n\n  status->joystick_active[joystick] = false;\n  status->joystick_press_count[joystick] = 0;\n\n  memset(status->joystick_button[joystick], 0,\n   sizeof(status->joystick_button[joystick]));\n\n  memset(status->joystick_axis[joystick], 0,\n   sizeof(status->joystick_axis[joystick]));\n\n  memset(status->joystick_hat[joystick], 0,\n   sizeof(status->joystick_hat[joystick]));\n\n  memset(status->joystick_action_status[joystick], 0,\n   sizeof(status->joystick_action_status[joystick]));\n\n  memset(status->joystick_special_axis_status[joystick], 0,\n   sizeof(status->joystick_special_axis_status));\n\n  if(status->joystick_repeat_id == joystick)\n  {\n    status->joystick_action = JOY_NO_ACTION;\n    status->joystick_repeat = JOY_NO_ACTION;\n    status->joystick_repeat_state = 0;\n  }\n}\n\n/**\n * Get the current UI joystick action.\n */\nenum joystick_action get_joystick_ui_action(void)\n{\n  const struct buffered_status *status = load_status();\n  return status->joystick_action;\n}\n\n/**\n * Get the standard UI key for the current joystick action.\n * Most places that use joystick actions want similar bindings or only have\n * a few exceptions.\n */\nenum keycode get_joystick_ui_key(void)\n{\n  const struct buffered_status *status = load_status();\n  return joystick_action_map_ui[status->joystick_action];\n}\n\n/**\n * Get the active status of a joystick. Returns false if the joystick number\n * is invalid. If the joystick is valid, is_active will be set to the active\n * status and the function will return true.\n */\nboolean joystick_is_active(int joystick, boolean *is_active)\n{\n  const struct buffered_status *status = load_status();\n  if((joystick >= 0) && (joystick < MAX_JOYSTICKS))\n  {\n    *is_active = status->joystick_active[joystick];\n    return true;\n  }\n  return false;\n}\n\n/**\n * Get the status of a joystick control by name. Returns false if the joystick\n * number is invalid or if the control is invalid. If the joystick and control\n * are both valid, value will be set to the status value of the control and the\n * function will return true.\n */\nboolean joystick_get_status(int joystick, char *name, int16_t *value)\n{\n  const struct buffered_status *status = load_status();\n\n  if((joystick >= 0) && (joystick < MAX_JOYSTICKS))\n  {\n    if(!strncasecmp(name, \"axis_\", 5))\n    {\n      enum joystick_special_axis axis = joystick_axis_by_name(name + 5);\n\n      if(axis != JOY_NO_AXIS)\n      {\n        *value = status->joystick_special_axis_status[joystick][axis];\n        return true;\n      }\n    }\n    else\n    {\n      enum joystick_action action = joystick_action_by_name(name);\n\n      if(action != JOY_NO_ACTION)\n      {\n        *value = status->joystick_action_status[joystick][action];\n        return true;\n      }\n    }\n  }\n  return false;\n}\n"
  },
  {
    "path": "src/event.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EVENT_H\n#define __EVENT_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"keysym.h\"\n\n#include <stdint.h>\n\n#define UPDATE_DELAY 16\n\n#define KEY_REPEAT_STACK_SIZE 32\n#define KEY_UNICODE_MAX 1024\n\n#define STATUS_NUM_KEYCODES 512\n\n#if defined(CONFIG_NDS) || defined(CONFIG_3DS)\n#define MAX_JOYSTICKS           1\n#define MAX_JOYSTICK_AXES       4\n#define MAX_JOYSTICK_BUTTONS    16\n#else\n#define MAX_JOYSTICKS           16\n#define MAX_JOYSTICK_AXES       16\n#define MAX_JOYSTICK_BUTTONS    256\n#endif\n\n#define MAX_JOYSTICK_PRESS      16\n#define AXIS_DEFAULT_THRESHOLD  10000\n\n// Capture F12 presses to save screenshots if true.\nextern boolean enable_f12_hack;\n\n// Store joystick presses in a list so their proper key will be released.\nstruct joystick_press\n{\n  uint8_t type;\n  uint8_t num;\n  int16_t key;\n  uint8_t action;\n};\n\nstruct buffered_status\n{\n  enum keycode key_pressed;\n  enum keycode key;\n  enum keycode key_repeat;\n  enum keycode key_release;\n  uint32_t *unicode;\n  uint32_t unicode_repeat;\n  int unicode_pos;\n  int unicode_length;\n  int unicode_alloc;\n  uint32_t keypress_time;\n  int mouse_x;\n  int mouse_y;\n  int mouse_last_x;\n  int mouse_last_y;\n  int mouse_pixel_x;\n  int mouse_pixel_y;\n  int mouse_last_pixel_x;\n  int mouse_last_pixel_y;\n  int warped_mouse_x;\n  int warped_mouse_y;\n  enum mouse_button mouse_button;\n  enum mouse_button mouse_repeat;\n  int mouse_repeat_state;\n  int mouse_drag_state;\n  uint32_t mouse_button_state;\n  uint32_t mouse_time;\n  boolean caps_status;\n  boolean numlock_status;\n  boolean mouse_moved;\n  boolean exit_status;\n  enum joystick_action joystick_action;\n  enum joystick_action joystick_repeat;\n  int joystick_repeat_state;\n  int joystick_repeat_id;\n  uint32_t joystick_time;\n\n  // NOTE: These arrays track raw joystick state but this is only used to\n  // determine if an action status or a simulated press should be altered.\n  // So, these arrays probably don't need to be processed in a network context.\n  boolean joystick_hat[MAX_JOYSTICKS][4];\n  boolean joystick_button[MAX_JOYSTICKS][MAX_JOYSTICK_BUTTONS];\n  int16_t joystick_axis[MAX_JOYSTICKS][MAX_JOYSTICK_AXES];\n\n  // NOTE: These arrays track abstracted joystick state and are the ones that\n  // simulated keypresses, games, and event buffering need to care about.\n  boolean joystick_active[MAX_JOYSTICKS];\n  boolean joystick_action_status[MAX_JOYSTICKS][NUM_JOYSTICK_ACTIONS];\n  int16_t joystick_special_axis_status[MAX_JOYSTICKS][NUM_JOYSTICK_SPECIAL_AXES];\n  struct joystick_press joystick_press[MAX_JOYSTICKS][MAX_JOYSTICK_PRESS];\n  uint8_t joystick_press_count[MAX_JOYSTICKS];\n\n  uint8_t keymap[512];\n};\n\nstruct joystick_map\n{\n  int16_t button[MAX_JOYSTICKS][MAX_JOYSTICK_BUTTONS];\n  int16_t axis[MAX_JOYSTICKS][MAX_JOYSTICK_AXES][2];\n  int16_t hat[MAX_JOYSTICKS][4];\n  int16_t action[MAX_JOYSTICKS][NUM_JOYSTICK_ACTIONS];\n  uint8_t special_axis[MAX_JOYSTICKS][MAX_JOYSTICK_AXES];\n  int16_t show_screen_keyboard_action[MAX_JOYSTICKS];\n\n  boolean button_is_conf[MAX_JOYSTICKS][MAX_JOYSTICK_BUTTONS];\n  boolean axis_is_conf[MAX_JOYSTICKS][MAX_JOYSTICK_AXES];\n  boolean hat_is_conf[MAX_JOYSTICKS];\n  boolean action_is_conf[MAX_JOYSTICKS][NUM_JOYSTICK_ACTIONS];\n  boolean show_keyboard_is_conf[MAX_JOYSTICKS];\n};\n\nstruct input_status\n{\n  struct buffered_status *buffer;\n  unsigned int load_offset;\n  unsigned int store_offset;\n\n  enum keycode key_repeat_stack[KEY_REPEAT_STACK_SIZE];\n  uint32_t unicode_repeat_stack[KEY_REPEAT_STACK_SIZE];\n  unsigned int repeat_stack_pointer;\n\n  struct joystick_map joystick_global_map;\n  struct joystick_map joystick_game_map;\n  uint16_t joystick_axis_threshold;\n\n  boolean unfocus_pause;\n  boolean left_alt_is_altgr;\n  boolean right_alt_is_altgr;\n  boolean showing_screen_keyboard; /* optional, used by platform drivers */\n};\n\n// regular keycode_internal treats numpad keys as unique keys\n// wrt_numlock translates them to numeric/navigation based on numlock status\n// keycode_text_ascii masks unicode chars from 32 to 126; keycode_text_unicode\n// will return any unicode char encountered >= 32.\nenum keycode_type\n{\n  keycode_pc_xt,\n  keycode_internal,\n  keycode_internal_wrt_numlock,\n  keycode_text_ascii,\n  keycode_text_unicode\n};\n\n#define KEYCODE_IS_ASCII(key) ((key) >= 32 && (key) < 127)\n\nstruct config_info;\n\nCORE_LIBSPEC void init_event(struct config_info *conf);\n\nstruct buffered_status *store_status(void);\n\nCORE_LIBSPEC boolean update_event_status(void);\nCORE_LIBSPEC boolean update_event_status_delay(void);\nCORE_LIBSPEC void update_event_status_intake(void);\nCORE_LIBSPEC void force_release_all_keys(void);\nCORE_LIBSPEC uint32_t get_key(enum keycode_type type);\nCORE_LIBSPEC uint32_t get_last_key(enum keycode_type type);\nCORE_LIBSPEC uint32_t get_key_status(enum keycode_type type, uint32_t index);\nCORE_LIBSPEC void get_mouse_position(int *char_x, int *char_y);\nCORE_LIBSPEC void get_mouse_pixel_position(int *pixel_x, int *pixel_y);\nCORE_LIBSPEC void get_mouse_movement(int *delta_x, int *delta_y);\nCORE_LIBSPEC void get_mouse_pixel_movement(int *delta_x, int *delta_y);\nCORE_LIBSPEC enum mouse_button get_mouse_press(void);\nCORE_LIBSPEC enum mouse_button get_mouse_press_ext(void);\nCORE_LIBSPEC boolean get_mouse_held(int button);\nCORE_LIBSPEC uint32_t get_mouse_status(void);\nCORE_LIBSPEC void warp_mouse(int char_x, int char_y);\nCORE_LIBSPEC void warp_mouse_pixel_x(int pixel_x);\nCORE_LIBSPEC void warp_mouse_pixel_y(int pixel_y);\nCORE_LIBSPEC boolean get_mouse_drag(void);\nCORE_LIBSPEC boolean get_alt_status(enum keycode_type type);\nCORE_LIBSPEC boolean get_altgr_status(enum keycode_type type);\nCORE_LIBSPEC boolean get_shift_status(enum keycode_type type);\nCORE_LIBSPEC boolean get_ctrl_status(enum keycode_type type);\nCORE_LIBSPEC boolean get_exit_status(void);\nCORE_LIBSPEC boolean set_exit_status(boolean value);\nCORE_LIBSPEC boolean peek_exit_input(void);\nCORE_LIBSPEC struct joystick_map *get_joystick_map(boolean is_global);\nCORE_LIBSPEC enum joystick_action get_joystick_ui_action(void);\nCORE_LIBSPEC enum keycode get_joystick_ui_key(void);\n\nboolean has_unicode_input(void);\nuint32_t convert_internal_unicode(enum keycode key, boolean caps_lock);\n\n// Implemented by \"drivers\" (SDL, Wii, NDS, 3DS, etc.)\nvoid platform_init_event(void);\nboolean platform_has_screen_keyboard(void);\nboolean platform_show_screen_keyboard(void);\nboolean platform_hide_screen_keyboard(void);\nboolean platform_is_screen_keyboard_active(void);\nboolean __update_event_status(void);\nboolean __peek_exit_input(void);\nvoid __wait_event(void);\nvoid __warp_mouse(int x, int y);\n\n// \"Driver\" functions currently only supported by SDL.\nvoid sdl_init_window_text_events(unsigned sdl_window_id);\nvoid gamepad_map_sym(const char *sym, const char *value);\nvoid gamepad_add_mapping(const char *mapping);\n\n#ifdef CONFIG_NDS\nconst struct buffered_status *load_status(void);\n#endif\n\nvoid wait_event(int timeout);\nvoid force_last_key(enum keycode_type type, int val);\nvoid warp_mouse_x(int char_x);\nvoid warp_mouse_y(int char_y);\nint get_mouse_x(void);\nint get_mouse_y(void);\nint get_mouse_pixel_x(void);\nint get_mouse_pixel_y(void);\nuint32_t get_last_key_released(enum keycode_type type);\nvoid set_unfocus_pause(boolean value);\nvoid set_num_buffered_events(unsigned int value);\n\nvoid key_press(struct buffered_status *status, enum keycode key);\nvoid key_press_unicode(struct buffered_status *status,\n uint32_t unicode, boolean repeating);\nvoid key_release(struct buffered_status *status, enum keycode key);\nboolean screen_keyboard_is_active(void);\nboolean screen_keyboard_toggle_state(void);\n\nboolean joystick_parse_map_value(const char *value, int16_t *binding);\nvoid joystick_map_button(int first, int last, int button, const char *value,\n boolean is_global);\nvoid joystick_map_axis(int first, int last, int axis, const char *neg,\n const char *pos, boolean is_global);\nvoid joystick_map_hat(int first, int last, const char *up, const char *down,\n const char *left, const char *right, boolean is_global);\nvoid joystick_map_action(int first, int last, const char *action,\n const char *value, boolean is_global);\nvoid joystick_map_fallback_keyboard_button(int joystick, int button);\nvoid joystick_reset_game_map(void);\nvoid joystick_set_game_mode(boolean enable);\nvoid joystick_set_game_bindings(boolean enable);\nvoid joystick_set_legacy_loop_hacks(boolean enable);\nvoid joystick_set_axis_threshold(uint16_t threshold);\nvoid joystick_button_press(struct buffered_status *status,\n int joystick, int button);\nvoid joystick_button_release(struct buffered_status *status,\n int joystick, int button);\nvoid joystick_hat_update(struct buffered_status *status,\n int joystick, enum joystick_hat dir, boolean dir_active);\nvoid joystick_axis_update(struct buffered_status *status,\n int joystick, int axis, int16_t value);\nvoid joystick_special_axis_update(struct buffered_status *status,\n int joystick, enum joystick_special_axis axis, int16_t value);\nvoid joystick_set_active(struct buffered_status *status, int joystick,\n boolean active);\nvoid joystick_clear(struct buffered_status *status, int joystick);\nboolean joystick_is_active(int joystick, boolean *is_active);\nboolean joystick_get_status(int joystick, char *name, int16_t *value);\n\n__M_END_DECLS\n\n#endif // __EVENT_H\n"
  },
  {
    "path": "src/event_sdl.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Kevin Vance <kvance@kvance.com>\n * Copyright (C) 2019, 2024-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"SDLmzx.h\"\n#include \"configure.h\"\n#include \"event.h\"\n#include \"graphics.h\"\n#include \"platform.h\"\n#include \"render_sdl.h\"\n#include \"util.h\"\n\n#include <ctype.h>\n#include <math.h>\n#include <stdint.h>\n#include <stdlib.h>\n\n#ifdef CONFIG_WIIU\n#include <whb/proc.h>\n#endif\n\n/* Several ports don't or did not send joystick added events:\n *\n * - SDL 1.2 doesn't have joystick added/removed events.\n * - The 3DS port didn't send them until 3.x\n * - The PS2 port didn't send them until 3.x\n * - The PSP port didn't send them until 2.32.0 and 3.x\n * - The Vita port didn't send them until 2.0.22 and 3.x\n * - The unofficial Switch port doesn't bother at all.\n */\n#if !SDL_VERSION_ATLEAST(2,0,0) || \\\n (!SDL_VERSION_ATLEAST(3,0,0) && defined(CONFIG_3DS)) || \\\n (!SDL_VERSION_ATLEAST(3,0,0) && defined(CONFIG_PS2)) || \\\n (!SDL_VERSION_ATLEAST(2,32,0) && defined(CONFIG_PSP)) || \\\n (!SDL_VERSION_ATLEAST(2,0,22) && defined(CONFIG_PSVITA)) || \\\n defined(CONFIG_SWITCH)\n#define NO_JOYSTICK_ADDED_EVENTS\n#endif\n\nextern struct input_status input;\n\n/* Enable converting keycodes to fake unicode presses when text input isn't\n * active. Enabling text input also enables an onscreen keyboard in some\n * ports, so it isn't always desired.\n *\n * SDL 1.2 builds will disable this automatically if unicode is detected.\n */\nstatic boolean unicode_fallback;\n\nstatic boolean numlock_status_initialized;\nstatic unsigned last_text_window_id;\nstatic int joystick_instance_ids[MAX_JOYSTICKS];\nstatic SDL_Joystick *joysticks[MAX_JOYSTICKS];\n\nstatic enum keycode convert_SDL_internal(SDL_Keycode key)\n{\n  switch(key)\n  {\n    case SDLK_BACKSPACE: return IKEY_BACKSPACE;\n    case SDLK_TAB: return IKEY_TAB;\n    case SDLK_RETURN: return IKEY_RETURN;\n    case SDLK_ESCAPE: return IKEY_ESCAPE;\n    case SDLK_SPACE: return IKEY_SPACE;\n    case SDLK_APOSTROPHE: return IKEY_QUOTE;\n    case SDLK_PLUS: return IKEY_EQUALS;\n    case SDLK_COMMA: return IKEY_COMMA;\n    case SDLK_MINUS: return IKEY_MINUS;\n    case SDLK_PERIOD: return IKEY_PERIOD;\n    case SDLK_SLASH: return IKEY_SLASH;\n    case SDLK_0: return IKEY_0;\n    case SDLK_1: return IKEY_1;\n    case SDLK_2: return IKEY_2;\n    case SDLK_3: return IKEY_3;\n    case SDLK_4: return IKEY_4;\n    case SDLK_5: return IKEY_5;\n    case SDLK_6: return IKEY_6;\n    case SDLK_7: return IKEY_7;\n    case SDLK_8: return IKEY_8;\n    case SDLK_9: return IKEY_9;\n    case SDLK_SEMICOLON: return IKEY_SEMICOLON;\n    case SDLK_EQUALS: return IKEY_EQUALS;\n    case SDLK_LEFTBRACKET: return IKEY_LEFTBRACKET;\n    case SDLK_BACKSLASH: return IKEY_BACKSLASH;\n    case SDLK_RIGHTBRACKET: return IKEY_RIGHTBRACKET;\n    case SDLK_GRAVE: return IKEY_BACKQUOTE;\n    case SDLK_A: return IKEY_a;\n    case SDLK_B: return IKEY_b;\n    case SDLK_C: return IKEY_c;\n    case SDLK_D: return IKEY_d;\n    case SDLK_E: return IKEY_e;\n    case SDLK_F: return IKEY_f;\n    case SDLK_G: return IKEY_g;\n    case SDLK_H: return IKEY_h;\n    case SDLK_I: return IKEY_i;\n    case SDLK_J: return IKEY_j;\n    case SDLK_K: return IKEY_k;\n    case SDLK_L: return IKEY_l;\n    case SDLK_M: return IKEY_m;\n    case SDLK_N: return IKEY_n;\n    case SDLK_O: return IKEY_o;\n    case SDLK_P: return IKEY_p;\n    case SDLK_Q: return IKEY_q;\n    case SDLK_R: return IKEY_r;\n    case SDLK_S: return IKEY_s;\n    case SDLK_T: return IKEY_t;\n    case SDLK_U: return IKEY_u;\n    case SDLK_V: return IKEY_v;\n    case SDLK_W: return IKEY_w;\n    case SDLK_X: return IKEY_x;\n    case SDLK_Y: return IKEY_y;\n    case SDLK_Z: return IKEY_z;\n    case SDLK_DELETE: return IKEY_DELETE;\n    case SDLK_KP_0: return IKEY_KP0;\n    case SDLK_KP_1: return IKEY_KP1;\n    case SDLK_KP_2: return IKEY_KP2;\n    case SDLK_KP_3: return IKEY_KP3;\n    case SDLK_KP_4: return IKEY_KP4;\n    case SDLK_KP_5: return IKEY_KP5;\n    case SDLK_KP_6: return IKEY_KP6;\n    case SDLK_KP_7: return IKEY_KP7;\n    case SDLK_KP_8: return IKEY_KP8;\n    case SDLK_KP_9: return IKEY_KP9;\n    case SDLK_KP_PERIOD: return IKEY_KP_PERIOD;\n    case SDLK_KP_DIVIDE: return IKEY_KP_DIVIDE;\n    case SDLK_KP_MULTIPLY: return IKEY_KP_MULTIPLY;\n    case SDLK_KP_MINUS: return IKEY_KP_MINUS;\n    case SDLK_KP_PLUS: return IKEY_KP_PLUS;\n    case SDLK_KP_ENTER: return IKEY_KP_ENTER;\n    case SDLK_UP: return IKEY_UP;\n    case SDLK_DOWN: return IKEY_DOWN;\n    case SDLK_RIGHT: return IKEY_RIGHT;\n    case SDLK_LEFT: return IKEY_LEFT;\n    case SDLK_INSERT: return IKEY_INSERT;\n    case SDLK_HOME: return IKEY_HOME;\n    case SDLK_END: return IKEY_END;\n    case SDLK_PAGEUP: return IKEY_PAGEUP;\n    case SDLK_PAGEDOWN: return IKEY_PAGEDOWN;\n    case SDLK_F1: return IKEY_F1;\n    case SDLK_F2: return IKEY_F2;\n    case SDLK_F3: return IKEY_F3;\n    case SDLK_F4: return IKEY_F4;\n    case SDLK_F5: return IKEY_F5;\n    case SDLK_F6: return IKEY_F6;\n    case SDLK_F7: return IKEY_F7;\n    case SDLK_F8: return IKEY_F8;\n    case SDLK_F9: return IKEY_F9;\n    case SDLK_F10: return IKEY_F10;\n    case SDLK_F11: return IKEY_F11;\n    case SDLK_F12: return IKEY_F12;\n    case SDLK_NUMLOCKCLEAR: return IKEY_NUMLOCK;\n    case SDLK_CAPSLOCK: return IKEY_CAPSLOCK;\n    case SDLK_SCROLLLOCK: return IKEY_SCROLLOCK;\n    case SDLK_RSHIFT: return IKEY_RSHIFT;\n    case SDLK_LSHIFT: return IKEY_LSHIFT;\n    case SDLK_RCTRL: return IKEY_RCTRL;\n    case SDLK_LCTRL: return IKEY_LCTRL;\n    case SDLK_RALT: return IKEY_RALT;\n    case SDLK_LALT: return IKEY_LALT;\n    case SDLK_MODE: return IKEY_ALTGR;\n#if !SDL_VERSION_ATLEAST(2,0,0)\n    // SDL 1.2 had two different versions of the same pair of keys.\n    // Because of this, we can't just #define these to the new values.\n    case SDLK_LMETA: return IKEY_LSUPER;\n    case SDLK_RMETA: return IKEY_RSUPER;\n    case SDLK_LSUPER: return IKEY_LSUPER;\n    case SDLK_RSUPER: return IKEY_RSUPER;\n#else\n    case SDLK_LGUI: return IKEY_LSUPER;\n    case SDLK_RGUI: return IKEY_RSUPER;\n#endif\n    case SDLK_SYSREQ: return IKEY_SYSREQ;\n    case SDLK_PAUSE: return IKEY_BREAK;\n    case SDLK_MENU: return IKEY_MENU;\n#if SDL_VERSION_ATLEAST(2,0,0)\n    // SDL 2.0 leaves the old keysyms around but doesn't use them?\n    case SDLK_PRINTSCREEN: return IKEY_SYSREQ;\n    case SDLK_APPLICATION: return IKEY_MENU;\n#endif\n#ifdef __WIN32__\n#if SDL_VERSION_ATLEAST(2,0,6) && !SDL_VERSION_ATLEAST(2,0,10)\n    // Dumb hack for a Windows virtual keycode bug. TODO remove.\n    case SDLK_CLEAR: return IKEY_KP5;\n#endif\n#endif\n    default: return IKEY_UNKNOWN;\n  }\n}\n\nstatic enum mouse_button convert_SDL_mouse_internal(uint32_t button)\n{\n  switch(button)\n  {\n    case SDL_BUTTON_LEFT:   return MOUSE_BUTTON_LEFT;\n    case SDL_BUTTON_MIDDLE: return MOUSE_BUTTON_MIDDLE;\n    case SDL_BUTTON_RIGHT:  return MOUSE_BUTTON_RIGHT;\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n    case SDL_BUTTON_X1:     return MOUSE_BUTTON_X1;\n    case SDL_BUTTON_X2:     return MOUSE_BUTTON_X2;\n\n    // Some SDL 2 versions have a bug where these values are carried through\n    // from X11.\n#ifdef CONFIG_X11\n    case 8:                 return MOUSE_BUTTON_X1;\n    case 9:                 return MOUSE_BUTTON_X2;\n#endif /* CONFIG_X11 */\n\n#else /* !SDL_VERSION_ATLEAST(2,0,0) */\n\n    // SDL 1.2 didn't get the defines until 1.2.13.\n    // Also, the wheel is handled here, and X11 does its own thing.\n#ifndef CONFIG_X11\n    case 4: return MOUSE_BUTTON_WHEELUP;\n    case 5: return MOUSE_BUTTON_WHEELDOWN;\n    case 6: return MOUSE_BUTTON_X1;\n    case 7: return MOUSE_BUTTON_X2;\n    case 8: return MOUSE_BUTTON_WHEELLEFT;\n    case 9: return MOUSE_BUTTON_WHEELRIGHT;\n#else /* CONFIG_X11 */\n    case 4: return MOUSE_BUTTON_WHEELUP;\n    case 5: return MOUSE_BUTTON_WHEELDOWN;\n    case 6: return MOUSE_BUTTON_WHEELLEFT;\n    case 7: return MOUSE_BUTTON_WHEELRIGHT;\n    case 8: return MOUSE_BUTTON_X1;\n    case 9: return MOUSE_BUTTON_X2;\n#endif\n\n#endif /* !SDL_VERSION_ATLEAST(2,0,0) */\n  }\n  return MOUSE_NO_BUTTON;\n}\n\n#ifdef CONFIG_PANDORA\nstatic int get_pandora_joystick_button(SDL_Keycode key)\n{\n  // Pandora hack. The home, end, page up, page down, right shift,\n  // and right control keys are actually joystick buttons.\n  switch(key)\n  {\n    case SDLK_HOME:     return 0;\n    case SDLK_END:      return 1;\n    case SDLK_PAGEUP:   return 2;\n    case SDLK_PAGEDOWN: return 3;\n    case SDLK_RSHIFT:   return 4;\n    case SDLK_RCTRL:    return 5;\n  }\n  return -1;\n}\n#endif\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n/**\n * The SDL gamepad API puts this in an interesting place. The best way\n * to populate MZX action mappings is to parse the SDL mapping string directly.\n * The API offers no other way to get detailed mapping information on half axes\n * and axis inversions (these were added after the API was designed; no new\n * functions were added to read this extended info), and using gamepad\n * events to simulate presses could result in situations where the joystick\n * events and the gamepad events create different simultaneous presses.\n *\n * However, the axis mappings can be so convoluted it's better to open the\n * controller with the API anyway just to populate the analog axis values. This\n * appears to not affect the joystick events received whatsoever.\n *\n * No equivalent of this API exists for SDL 1.x.\n */\n\nstatic SDL_Gamepad *gamepads[MAX_JOYSTICKS];\n\nstatic enum joystick_special_axis sdl_axis_map[SDL_GAMEPAD_AXIS_COUNT] =\n{\n  [SDL_GAMEPAD_AXIS_LEFTX]         = JOY_AXIS_LEFT_X,\n  [SDL_GAMEPAD_AXIS_LEFTY]         = JOY_AXIS_LEFT_Y,\n  [SDL_GAMEPAD_AXIS_RIGHTX]        = JOY_AXIS_RIGHT_X,\n  [SDL_GAMEPAD_AXIS_RIGHTY]        = JOY_AXIS_RIGHT_Y,\n  [SDL_GAMEPAD_AXIS_LEFT_TRIGGER]  = JOY_AXIS_LEFT_TRIGGER,\n  [SDL_GAMEPAD_AXIS_RIGHT_TRIGGER] = JOY_AXIS_RIGHT_TRIGGER\n};\n\nstatic int16_t sdl_axis_action_map[SDL_GAMEPAD_AXIS_COUNT][2] =\n{\n  [SDL_GAMEPAD_AXIS_LEFTX]         = { -JOY_L_LEFT,  -JOY_L_RIGHT },\n  [SDL_GAMEPAD_AXIS_LEFTY]         = { -JOY_L_UP,    -JOY_L_DOWN },\n  [SDL_GAMEPAD_AXIS_RIGHTX]        = { -JOY_R_LEFT,  -JOY_R_RIGHT },\n  [SDL_GAMEPAD_AXIS_RIGHTY]        = { -JOY_R_UP,    -JOY_R_DOWN },\n  [SDL_GAMEPAD_AXIS_LEFT_TRIGGER]  = { 0,            -JOY_LTRIGGER },\n  [SDL_GAMEPAD_AXIS_RIGHT_TRIGGER] = { 0,            -JOY_RTRIGGER },\n};\n\nstatic int16_t sdl_action_map[SDL_GAMEPAD_BUTTON_COUNT] =\n{\n  [SDL_GAMEPAD_BUTTON_SOUTH]          = -JOY_A,\n  [SDL_GAMEPAD_BUTTON_EAST]           = -JOY_B,\n  [SDL_GAMEPAD_BUTTON_WEST]           = -JOY_X,\n  [SDL_GAMEPAD_BUTTON_NORTH]          = -JOY_Y,\n  [SDL_GAMEPAD_BUTTON_BACK]           = -JOY_SELECT,\n//[SDL_GAMEPAD_BUTTON_GUIDE]          = -JOY_GUIDE,\n  [SDL_GAMEPAD_BUTTON_START]          = -JOY_START,\n  [SDL_GAMEPAD_BUTTON_LEFT_STICK]     = -JOY_LSTICK,\n  [SDL_GAMEPAD_BUTTON_RIGHT_STICK]    = -JOY_RSTICK,\n  [SDL_GAMEPAD_BUTTON_LEFT_SHOULDER]  = -JOY_LSHOULDER,\n  [SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER] = -JOY_RSHOULDER,\n  [SDL_GAMEPAD_BUTTON_DPAD_UP]        = -JOY_UP,\n  [SDL_GAMEPAD_BUTTON_DPAD_DOWN]      = -JOY_DOWN,\n  [SDL_GAMEPAD_BUTTON_DPAD_LEFT]      = -JOY_LEFT,\n  [SDL_GAMEPAD_BUTTON_DPAD_RIGHT]     = -JOY_RIGHT\n};\n\nenum\n{\n  GAMEPAD_NONE,\n  GAMEPAD_BUTTON,\n  GAMEPAD_AXIS,\n  GAMEPAD_HAT,\n};\n\nstruct gamepad_map\n{\n  char *dbg;\n  uint8_t feature;\n  uint8_t which;\n  uint8_t pos;\n};\n\nstruct gamepad_axis_map\n{\n  struct gamepad_map neg;\n  struct gamepad_map pos;\n};\n\nstatic int sdl_hat_to_dir(int hat_mask)\n{\n  switch(hat_mask)\n  {\n    case SDL_HAT_UP:\n      return JOYHAT_UP;\n\n    case SDL_HAT_DOWN:\n      return JOYHAT_DOWN;\n\n    case SDL_HAT_LEFT:\n      return JOYHAT_LEFT;\n\n    case SDL_HAT_RIGHT:\n      return JOYHAT_RIGHT;\n\n    default:\n      return -1;\n  }\n}\n\nstatic void parse_gamepad_read_value(char *key, char *value,\n struct gamepad_map *single, struct gamepad_map *neg, struct gamepad_map *pos)\n{\n  // Joystick axes may have half-axis prefixes + or -.\n  // Joystick axes may also have an inversion suffix ~.\n  char half_axis = 0;\n\n  if(*value == '+' || *value == '-')\n    half_axis = *(value++);\n\n  switch(*value)\n  {\n    case 'a':\n    {\n      // Axis- a# or a#~\n      unsigned int axis;\n\n      if(!isdigit((uint8_t)value[1]))\n        break;\n\n      axis = strtoul(value + 1, &value, 10);\n      if(axis >= MAX_JOYSTICK_AXES)\n        break;\n\n      // Only one provided output and no half axis specified? Map + to it.\n      if(half_axis == 0 && single)\n      {\n        neg = NULL;\n        pos = single;\n      }\n      else\n\n      if(half_axis == '+')\n      {\n        pos = single ? single : pos ? pos : neg;\n      }\n      else\n\n      if(half_axis == '-')\n      {\n        neg = single ? single : neg ? neg : pos;\n      }\n\n      if(value && *value == '~')\n      {\n        // Invert\n        single = neg;\n        neg = pos;\n        pos = single;\n      }\n\n      if(neg)\n      {\n        neg->feature = GAMEPAD_AXIS;\n        neg->which = axis;\n        neg->pos = 0;\n        neg->dbg = key;\n      }\n      if(pos)\n      {\n        pos->feature = GAMEPAD_AXIS;\n        pos->which = axis;\n        pos->pos = 1;\n        pos->dbg = key;\n      }\n      return;\n    }\n\n    case 'b':\n    {\n      // Button- b#\n      unsigned int button;\n\n      if(!isdigit((uint8_t)value[1]))\n        break;\n\n      button = strtoul(value + 1, NULL, 10);\n      if(button >= MAX_JOYSTICK_BUTTONS)\n        break;\n\n      if(!single)\n        single = pos ? pos : neg;\n\n      if(single)\n      {\n        single->feature = GAMEPAD_BUTTON;\n        single->which = button;\n        single->dbg = key;\n      }\n      return;\n    }\n\n    case 'h':\n    {\n      // Hat- h#.#\n      unsigned int hat;\n      unsigned int hat_mask;\n      int dir;\n\n      if(!isdigit((uint8_t)value[1]))\n        break;\n\n      hat = strtoul(value + 1, &value, 10);\n      if(hat != 0 || !value[0] || !isdigit((uint8_t)value[1]))\n        break;\n\n      hat_mask = strtoul(value + 1, NULL, 10);\n      dir = sdl_hat_to_dir(hat_mask);\n      if(dir < 0)\n        break;\n\n      if(!single)\n        single = pos ? pos : neg;\n\n      if(single)\n      {\n        single->feature = GAMEPAD_HAT;\n        single->which = hat;\n        single->pos = dir;\n        single->dbg = key;\n      }\n      return;\n    }\n  }\n  debug(\"--JOYSTICK-- ignoring '%s' -> '%s'\\n\", value, key);\n  return;\n}\n\nstatic void parse_gamepad_read_entry(char *key, char *value,\n struct gamepad_axis_map *axes, struct gamepad_map *buttons)\n{\n  SDL_GamepadAxis a;\n  SDL_GamepadButton b;\n  struct gamepad_map *single = NULL;\n  struct gamepad_map *neg = NULL;\n  struct gamepad_map *pos = NULL;\n  char half_axis = 0;\n\n  // Gamepad axes may have half-axis prefixes + or -.\n  if(*key == '+' || *key == '-')\n    half_axis = *(key++);\n\n  a = SDL_GetGamepadAxisFromString(key);\n  b = SDL_GetGamepadButtonFromString(key);\n  if(a != SDL_GAMEPAD_AXIS_INVALID)\n  {\n    if(half_axis == '+')\n      single = &(axes[a].pos);\n\n    if(half_axis == '-')\n      single = &(axes[a].neg);\n\n    if(half_axis == 0)\n    {\n      neg = &(axes[a].neg);\n      pos = &(axes[a].pos);\n    }\n  }\n  else\n\n  if(b != SDL_GAMEPAD_BUTTON_INVALID)\n  {\n    // This button isn't really useful to MZX.\n    if(b == SDL_GAMEPAD_BUTTON_GUIDE)\n      return;\n\n    single = &(buttons[b]);\n  }\n  else\n\n  if(!strcasecmp(key, \"platform\") || !strcasecmp(key, \"crc\"))\n  {\n    // ignore- field used by SDL.\n    return;\n  }\n\n  else\n  {\n    debug(\"--JOYSTICK-- Invalid control '%s'! Report this!\\n\", key);\n    return;\n  }\n\n  parse_gamepad_read_value(key, value, single, neg, pos);\n}\n\nstatic void parse_gamepad_read_string(char *map,\n struct gamepad_axis_map *axes, struct gamepad_map *buttons)\n{\n  // Format: entry,entry,...\n  // Entry:  value or key:value\n  char *end = map + strlen(map);\n  char *key;\n  char *value;\n\n  while(map < end)\n  {\n    key = map;\n    value = NULL;\n\n    while(*map != ',')\n    {\n      if(!*map) break;\n\n      if(*map == ':' && !value)\n      {\n        *map = 0;\n        value = map + 1;\n      }\n\n      map++;\n    }\n    *(map++) = 0;\n\n    if(value)\n      parse_gamepad_read_entry(key, value, axes, buttons);\n  }\n}\n\nstatic void parse_gamepad_apply(int joy, int16_t mapping,\n struct gamepad_map *target, boolean *select_mapped, boolean *select_used)\n{\n  uint8_t which = target->which;\n  uint8_t pos = target->pos;\n\n  if(mapping == -JOY_SELECT || mapping == -JOY_START)\n    *select_mapped = true;\n\n  switch(target->feature)\n  {\n    case GAMEPAD_NONE:\n      return;\n\n    case GAMEPAD_BUTTON:\n    {\n      debug(\"--JOYSTICK--  b%u -> '%s' (%d)\\n\", which, target->dbg, mapping);\n      if(!input.joystick_global_map.button_is_conf[joy][which])\n        input.joystick_global_map.button[joy][which] = mapping;\n\n      if(!input.joystick_game_map.button_is_conf[joy][which])\n        input.joystick_game_map.button[joy][which] = mapping;\n      break;\n    }\n\n    case GAMEPAD_AXIS:\n    {\n      debug(\"--JOYSTICK--  a%u%s -> '%s' (%d)\\n\", which, pos?\"+\":\"-\",\n       target->dbg, mapping);\n\n      if(!input.joystick_global_map.axis_is_conf[joy][which])\n        input.joystick_global_map.axis[joy][which][pos] = mapping;\n\n      if(!input.joystick_game_map.axis_is_conf[joy][which])\n        input.joystick_game_map.axis[joy][which][pos] = mapping;\n      break;\n    }\n\n    case GAMEPAD_HAT:\n    {\n      debug(\"--JOYSTICK--  hd%u -> '%s' (%d)\\n\", pos, target->dbg, mapping);\n      if(!input.joystick_global_map.hat_is_conf[joy])\n        input.joystick_global_map.hat[joy][pos] = mapping;\n\n      if(!input.joystick_game_map.hat_is_conf[joy])\n        input.joystick_game_map.hat[joy][pos] = mapping;\n      break;\n    }\n  }\n  if(mapping == -JOY_SELECT || mapping == -JOY_START)\n    *select_used = true;\n  return;\n}\n\nstatic void parse_gamepad_map(int joystick_index, char *map)\n{\n  struct gamepad_axis_map axes[SDL_GAMEPAD_AXIS_COUNT];\n  struct gamepad_map buttons[SDL_GAMEPAD_BUTTON_COUNT];\n  boolean select_mapped = false;\n  boolean select_used = false;\n  size_t i;\n\n  memset(axes, 0, sizeof(axes));\n  memset(buttons, 0, sizeof(buttons));\n\n  parse_gamepad_read_string(map, axes, buttons);\n\n  // Apply axes.\n  for(i = 0; i < SDL_GAMEPAD_AXIS_COUNT; i++)\n  {\n    parse_gamepad_apply(joystick_index,\n     sdl_axis_action_map[i][0], &(axes[i].neg), &select_mapped, &select_used);\n\n    parse_gamepad_apply(joystick_index,\n     sdl_axis_action_map[i][1], &(axes[i].pos), &select_mapped, &select_used);\n  }\n\n  // Apply buttons.\n  for(i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; i++)\n  {\n    parse_gamepad_apply(joystick_index,\n     sdl_action_map[i], &(buttons[i]), &select_mapped, &select_used);\n  }\n\n  if(select_mapped && !select_used)\n  {\n    // TODO originally this was going to try to place JOY_SELECT on another\n    // button. That was kind of a bad idea, so just print a warning for now.\n    info(\"--JOYSTICK-- %d doesn't have any gamepad button that binds \"\n     \"'select' or 'start' (by default, these are the SDL gamecontoller buttons \"\n     \"'back' and 'start', respectively). Since these buttons are used to open \"\n     \"the joystick menu, you may want to override this controller mapping.\\n\",\n      joystick_index + 1);\n  }\n}\n\n/**\n * `sdl_joystick_id` is a device ID for SDL1/SDL2, but an instance ID for SDL3.\n */\nstatic void init_gamepad(SDL_Joystick *joystick, int sdl_joystick_id,\n int joystick_index)\n{\n  SDL_GUID guid = SDL_GetJoystickGUID(joystick);\n  char guid_string[33];\n\n  SDL_GUIDToString(guid, guid_string, 33);\n  gamepads[joystick_index] = NULL;\n\n  if(SDL_IsGamepad(sdl_joystick_id))\n  {\n    SDL_Gamepad *gamepad = SDL_OpenGamepad(sdl_joystick_id);\n\n    if(gamepad)\n    {\n      const struct config_info *conf = get_config();\n      char *mapping = NULL;\n      gamepads[joystick_index] = gamepad;\n\n#if SDL_VERSION_ATLEAST(3,0,0)\n      // This is the equivalent to [...]ForDeviceIndex() and is also currently\n      // the only way to get the default generated mapping.\n      mapping = (char *)SDL_GetGamepadMappingForID(sdl_joystick_id);\n#elif SDL_VERSION_ATLEAST(2,0,9)\n      // NOTE: the other functions for this will not return the default mapping\n      // string; this is the only one that can return everything. Right now,\n      // this only matters for the Emscripten port.\n      mapping = (char *)SDL_GameControllerMappingForDeviceIndex(sdl_joystick_id);\n#else\n      mapping = (char *)SDL_GameControllerMapping(gamepad);\n#endif\n\n#ifdef __EMSCRIPTEN__\n      if(!mapping)\n      {\n        // The only function that can return the default mapping was added\n        // in SDL 2.0.9, but I'm not sure Emscripten actually has SDL 2.0.9.\n        // This string is copied from SDL_gamecontrollerdb.h\n        static const char default_mapping[] =\n         \"default,Standard Gamepad,a:b0,b:b1,back:b8,dpdown:b13,dpleft:b14,\"\n         \"dpright:b15,dpup:b12,guide:b16,leftshoulder:b4,leftstick:b10,\"\n         \"lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,\"\n         \"righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,\";\n\n        mapping = SDL_malloc(sizeof(default_mapping));\n        if(mapping)\n          strcpy(mapping, default_mapping);\n      }\n#endif\n\n      if(mapping)\n      {\n        info(\"--JOYSTICK-- joystick %d has an SDL mapping: %s\\n\",\n         joystick_index + 1, mapping);\n\n        if(strncmp(mapping, guid_string, strlen(guid_string)))\n          info(\"--JOYSTICK-- GUID: %s\\n\", guid_string);\n\n        if(conf->allow_gamepad)\n          parse_gamepad_map(joystick_index, mapping);\n\n        SDL_free(mapping);\n        return;\n      }\n    }\n  }\n  info(\"--JOYSTICK-- joystick %d does not have an SDL mapping or could not be \"\n   \"opened as a gamepad (GUID: %s).\\n\", joystick_index + 1, guid_string);\n}\n\n// Clean up auto-generated bindings so they don't cause problems for other\n// controllers that might end up using this position.\nstatic void gamepad_clean_map(int joy)\n{\n  int i;\n\n  for(i = 0; i < MAX_JOYSTICK_AXES; i++)\n  {\n    if(!input.joystick_global_map.axis_is_conf[joy][i])\n    {\n      input.joystick_global_map.axis[joy][i][0] = 0;\n      input.joystick_global_map.axis[joy][i][1] = 0;\n    }\n\n    if(!input.joystick_game_map.axis_is_conf[joy][i])\n    {\n      input.joystick_game_map.axis[joy][i][0] = 0;\n      input.joystick_game_map.axis[joy][i][1] = 0;\n    }\n  }\n\n  for(i = 0; i < MAX_JOYSTICK_BUTTONS; i++)\n  {\n    if(!input.joystick_global_map.button_is_conf[joy][i])\n      input.joystick_global_map.button[joy][i] = 0;\n\n    if(!input.joystick_game_map.button_is_conf[joy][i])\n      input.joystick_game_map.button[joy][i] = 0;\n  }\n\n  if(!input.joystick_global_map.hat_is_conf[joy])\n  {\n    for(i = 0; i < NUM_JOYSTICK_HAT_DIRS; i++)\n      input.joystick_global_map.hat[joy][i] = 0;\n  }\n\n  if(!input.joystick_game_map.hat_is_conf[joy])\n  {\n    for(i = 0; i < NUM_JOYSTICK_HAT_DIRS; i++)\n      input.joystick_game_map.hat[joy][i] = 0;\n  }\n}\n\n/**\n * Load gamecontrollerdb.txt if configured and if it isn't already loaded.\n * This adds more gamepad mappings so MZX can support more controllers.\n * The function this uses wasn't added until 2.0.2.\n */\nstatic void load_gamecontrollerdb(void)\n{\n#if defined(CONFIG_GAMECONTROLLERDB) && SDL_VERSION_ATLEAST(2,0,2)\n  static boolean gamecontrollerdb_loaded = false;\n\n  if(!gamecontrollerdb_loaded)\n  {\n    const char *path = mzx_res_get_by_id(GAMECONTROLLERDB_TXT);\n\n    if(path)\n    {\n      int result = SDL_AddGamepadMappingsFromFile(path);\n      if(result >= 0)\n        debug(\"--JOYSTICK-- Added %d mappings from '%s'.\\n\", result, path);\n    }\n\n    gamecontrollerdb_loaded = true;\n  }\n#endif\n}\n\n/**\n * Change one of the default SDL to MZX mapping values.\n */\nvoid gamepad_map_sym(const char *sym, const char *value)\n{\n  SDL_GamepadAxis a;\n  SDL_GamepadButton b;\n  int16_t binding = 0;\n\n  if(joystick_parse_map_value(value, &binding))\n  {\n    char dir = 0;\n    if(*sym == '+' || *sym == '-')\n      dir = *(sym++);\n\n    // Digital axis (default to + if no dir specified).\n    a = SDL_GetGamepadAxisFromString(sym);\n    if(a != SDL_GAMEPAD_AXIS_INVALID)\n    {\n      int pos = (dir != '-') ? 1 : 0;\n      sdl_axis_action_map[a][pos] = binding;\n    }\n\n    // Button\n    b = SDL_GetGamepadButtonFromString(sym);\n    if(b != SDL_GAMEPAD_BUTTON_INVALID)\n      sdl_action_map[b] = binding;\n  }\n\n  // TODO analog axes\n}\n\n/**\n * Add a mapping string to SDL.\n */\nvoid gamepad_add_mapping(const char *mapping)\n{\n  // Make sure this is loaded first so it doesn't override the user mapping.\n  load_gamecontrollerdb();\n\n  if(SDL_AddGamepadMapping(mapping) < 0)\n    warn(\"Failed to add gamepad mapping: %s\\n\", SDL_GetError());\n}\n\n#endif /* SDL_VERSION_ATLEAST(2,0,0) */\n\n/**\n * SDL 2+ uses joystick instance IDs instead of the device ID (index) for all\n * purposes (except for initialization in SDL 2, but not SDL 3). These instance\n * IDs need to be mapped to MegaZeux joystick indices. For SDL 1.2, the SDL\n * device ID is used as a substitute for the instance ID.\n */\nstatic int get_joystick_index(int sdl_instance_id)\n{\n  int i;\n  for(i = 0; i < MAX_JOYSTICKS; i++)\n    if((joystick_instance_ids[i] == sdl_instance_id) && joysticks[i])\n      return i;\n\n  return -1;\n}\n\nstatic int get_next_unused_joystick_index(void)\n{\n  int i;\n  for(i = 0; i < MAX_JOYSTICKS; i++)\n    if(!joysticks[i])\n      return i;\n\n  return -1;\n}\n\n/**\n * `sdl_joystick_id` is a device ID for SDL1/SDL2, but an instance ID for SDL3.\n */\nstatic void init_joystick(int sdl_joystick_id)\n{\n  struct buffered_status *status = store_status();\n  int joystick_index = get_next_unused_joystick_index();\n\n  if(joystick_index >= 0)\n  {\n    SDL_Joystick *joystick = SDL_OpenJoystick(sdl_joystick_id);\n    if(joystick)\n    {\n      joystick_instance_ids[joystick_index] = SDL_GetJoystickID(joystick);\n      joysticks[joystick_index] = joystick;\n      joystick_set_active(status, joystick_index, true);\n\n      debug(\"--JOYSTICK-- Opened %d (SDL instance ID: %d)\\n\",\n       joystick_index + 1, joystick_instance_ids[joystick_index]);\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n      init_gamepad(joystick, sdl_joystick_id, joystick_index);\n#endif\n    }\n  }\n}\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n// TODO: swappable joysticks in SDL <2\nstatic void close_joystick(int joystick_index)\n{\n  if(joystick_index >= 0)\n  {\n    debug(\"--JOYSTICK-- Closing %d (SDL instance ID: %d)\\n\",\n     joystick_index + 1, joystick_instance_ids[joystick_index]);\n\n    // SDL_GameControllerClose also closes the joystick.\n    if(gamepads[joystick_index])\n    {\n      SDL_CloseGamepad(gamepads[joystick_index]);\n      gamepad_clean_map(joystick_index);\n      gamepads[joystick_index] = NULL;\n    }\n    else\n      SDL_CloseJoystick(joysticks[joystick_index]);\n\n    joystick_instance_ids[joystick_index] = -1;\n    joysticks[joystick_index] = NULL;\n  }\n}\n#endif\n\nstatic inline void read_key_event(int *key, int *mod, int *scancode,\n const SDL_KeyboardEvent *ev)\n{\n#if SDL_VERSION_ATLEAST(3,0,0)\n  *key = ev->key;\n  *mod = ev->mod;\n  *scancode = ev->scancode;\n#else\n  *key = ev->keysym.sym;\n  *mod = ev->keysym.mod;\n  *scancode = ev->keysym.scancode;\n#endif\n}\n\n#if SDL_VERSION_ATLEAST(2,0,0)\nstatic inline uint32_t utf8_next_char(uint8_t **_src)\n{\n  uint8_t *src = *_src;\n  uint32_t unicode;\n\n  if(!*src)\n    return 0;\n\n  unicode = *(src++);\n\n  if(unicode & 0x80)\n  {\n    uint32_t extra = 1;\n    uint32_t next;\n    uint32_t i;\n\n    if(!(unicode & 0x40))\n      goto err_invalid;\n\n    unicode = unicode & ~0xC0;\n    if(unicode & 0x20)\n    {\n      unicode = unicode & ~0x20;\n      extra++;\n      if(unicode & 0x10)\n      {\n        unicode = unicode & ~0x10;\n        extra++;\n      }\n    }\n\n    for(i = 0; i < extra; i++)\n    {\n      if(!*src)\n        goto err_invalid;\n\n      next = *src++;\n      if((next & 0xC0) != 0x80)\n        goto err_invalid;\n\n      unicode = (unicode << 6) | (next & 0x3F);\n    }\n  }\n  *_src = src;\n  return unicode;\n\nerr_invalid:\n  *_src = src;\n  return 0;\n}\n#endif /* SDL_VERSION_ATLEAST(2,0,0) */\n\nstatic boolean process_event(SDL_Event *event)\n{\n  struct buffered_status *status = store_status();\n  int key, mod, scancode;\n  enum keycode ckey;\n\n  /* SDL's numlock keyboard modifier handling seems to be broken on X11,\n   * and it will only get numlock's status right on application init. We\n   * can trust this value once, and then toggle based on user presses of\n   * the numlock key.\n   *\n   * On Windows, KEYDOWN/KEYUP seem to be sent separately, to indicate\n   * enabling or disabling of numlock. But on X11, both KEYDOWN/KEYUP are\n   * sent for each toggle, so this must be handled differently.\n   *\n   * What a mess!\n   */\n  if(!numlock_status_initialized)\n  {\n    status->numlock_status = !!(SDL_GetModState() & SDL_KMOD_NUM);\n    numlock_status_initialized = true;\n  }\n\n  switch(event->type)\n  {\n    case SDL_EVENT_QUIT:\n    {\n      trace(\"--EVENT_SDL-- SDL_EVENT_QUIT\\n\");\n      // Set the exit status\n      status->exit_status = true;\n      break;\n    }\n\n#if SDL_VERSION_ATLEAST(3,0,0)\n    case SDL_EVENT_WINDOW_RESIZED:\n    {\n      unsigned window_id = video_window_by_platform_id(event->window.windowID);\n\n      trace(\"--EVENT_SDL-- SDL_EVENT_WINDOW_RESIZED: %u (%d,%d)\\n\",\n       event->window.windowID, event->window.data1, event->window.data2);\n      video_sync_window_size(window_id,\n       event->window.data1, event->window.data2);\n      break;\n    }\n\n    case SDL_EVENT_WINDOW_FOCUS_LOST:\n    {\n      trace(\"--EVENT_SDL-- SDL_EVENT_WINDOW_FOCUS_LOST\\n\");\n      // Pause while minimized\n      if(input.unfocus_pause)\n      {\n        while(1)\n        {\n          SDL_WaitEvent(event);\n          if(event->type == SDL_EVENT_WINDOW_FOCUS_GAINED)\n            break;\n        }\n        trace(\"--EVENT_SDL-- SDL_WINDOWEVENT_FOCUS_GAINED\\n\");\n      }\n      break;\n    }\n#elif SDL_VERSION_ATLEAST(2,0,0)\n    case SDL_WINDOWEVENT:\n    {\n      Uint32 sdl_window_id = event->window.windowID;\n      switch(event->window.event)\n      {\n        case SDL_WINDOWEVENT_RESIZED:\n        {\n          unsigned window_id = video_window_by_platform_id(sdl_window_id);\n          trace(\n            \"--EVENT_SDL-- SDL_WINDOWEVENT_RESIZED: %u (%u,%u)\\n\",\n            sdl_window_id, event->window.data1, event->window.data2\n          );\n          video_sync_window_size(window_id,\n           event->window.data1, event->window.data2);\n          break;\n        }\n\n        case SDL_WINDOWEVENT_FOCUS_LOST:\n        {\n          trace(\"--EVENT_SDL-- SDL_WINDOWEVENT_FOCUS_LOST: %u\\n\", sdl_window_id);\n          // Pause while minimized\n          if(input.unfocus_pause)\n          {\n            while(1)\n            {\n              SDL_WaitEvent(event);\n\n              if(event->type == SDL_WINDOWEVENT &&\n                 event->window.event == SDL_WINDOWEVENT_FOCUS_GAINED)\n                break;\n            }\n            trace(\"--EVENT_SDL-- SDL_WINDOWEVENT_FOCUS_GAINED: %u\\n\", sdl_window_id);\n          }\n          break;\n        }\n      }\n      break;\n    }\n#else // !SDL_VERSION_ATLEAST(2,0,0)\n    case SDL_VIDEORESIZE:\n    {\n      trace(\n        \"--EVENT_SDL-- SDL_VIDEORESIZE: (%u,%u)\\n\",\n        event->resize.w, event->resize.h\n      );\n      if(get_config()->allow_resize)\n        video_resize_window(1, event->resize.w, event->resize.h);\n      break;\n    }\n\n    case SDL_ACTIVEEVENT:\n    {\n      trace(\"--EVENT_SDL-- SDL_ACTIVEEVENT: %u\\n\", event->active.state);\n      if(input.unfocus_pause)\n      {\n        // Pause while minimized\n        if(event->active.state & (SDL_APPACTIVE | SDL_APPINPUTFOCUS))\n        {\n          // Wait for SDL_APPACTIVE with gain of 1\n          do\n          {\n            SDL_WaitEvent(event);\n          } while((event->type != SDL_ACTIVEEVENT) ||\n           (event->active.state & ~(SDL_APPACTIVE | SDL_APPINPUTFOCUS)));\n        }\n      }\n      break;\n    }\n#endif // !SDL_VERSION_ATLEAST(2,0,0)\n\n    case SDL_EVENT_MOUSE_MOTION:\n    {\n      SDL_Window *window = sdl_get_current_window();\n      int mx_real = event->motion.x;\n      int my_real = event->motion.y;\n      int mx, my, min_x, min_y, max_x, max_y;\n      get_screen_coords(mx_real, my_real, &mx, &my, &min_x,\n       &min_y, &max_x, &max_y);\n\n      if(mx >= SCREEN_PIX_W)\n      {\n        SDL_WarpMouseInWindow(window, max_x, my_real);\n        mx = SCREEN_PIX_W - 1;\n      }\n\n      if(mx < 0)\n      {\n        SDL_WarpMouseInWindow(window, min_x, my_real);\n        mx = 0;\n      }\n\n      if(my >= SCREEN_PIX_H)\n      {\n        SDL_WarpMouseInWindow(window, mx_real, max_y);\n        my = SCREEN_PIX_H - 1;\n      }\n\n      if(my < 0)\n      {\n        SDL_WarpMouseInWindow(window, mx_real, min_y);\n        my = 0;\n      }\n\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_MOUSE_MOTION: (%d,%d) -> (%d,%d)\\n\",\n        mx_real, my_real, mx, my\n      );\n      status->mouse_pixel_x = mx;\n      status->mouse_pixel_y = my;\n      status->mouse_x = mx / CHAR_W;\n      status->mouse_y = my / CHAR_H;\n      status->mouse_moved = true;\n      break;\n    }\n\n    case SDL_EVENT_MOUSE_BUTTON_DOWN:\n    {\n      enum mouse_button button = convert_SDL_mouse_internal(event->button.button);\n      trace(\"--EVENT_SDL-- SDL_EVENT_MOUSE_BUTTON_DOWN: %u\\n\", event->button.button);\n      if(button == MOUSE_NO_BUTTON)\n        break;\n      status->mouse_button = button;\n      status->mouse_repeat = button;\n      status->mouse_button_state |= MOUSE_BUTTON(button);\n      status->mouse_repeat_state = 1;\n      status->mouse_drag_state = -1;\n      status->mouse_time = get_ticks();\n      break;\n    }\n\n    case SDL_EVENT_MOUSE_BUTTON_UP:\n    {\n      enum mouse_button button = convert_SDL_mouse_internal(event->button.button);\n      trace(\"--EVENT_SDL-- SDL_EVENT_MOUSE_BUTTON_UP: %u\\n\", event->button.button);\n      if(button == MOUSE_NO_BUTTON)\n        break;\n      status->mouse_button_state &= ~MOUSE_BUTTON(button);\n      status->mouse_repeat = MOUSE_NO_BUTTON;\n      status->mouse_drag_state = 0;\n      status->mouse_repeat_state = 0;\n      break;\n    }\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n    // emulate the X11-style \"wheel is a button\" that SDL 1.2 used\n    // SDL3 uses floats, SDL2 uses ints.\n    case SDL_EVENT_MOUSE_WHEEL:\n    {\n      uint32_t button;\n      float wheel_x = (float)event->wheel.x;\n      float wheel_y = (float)event->wheel.y;\n\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_MOUSE_WHEEL: x=%.2f y=%.2f\\n\",\n        wheel_x, wheel_y\n      );\n\n      if(fabsf(wheel_x) > fabsf(wheel_y))\n      {\n        if(wheel_x < 0.0f)\n          button = MOUSE_BUTTON_WHEELLEFT;\n        else\n          button = MOUSE_BUTTON_WHEELRIGHT;\n      }\n      else\n      {\n        if(wheel_y < 0.0f)\n          button = MOUSE_BUTTON_WHEELDOWN;\n        else\n          button = MOUSE_BUTTON_WHEELUP;\n      }\n\n      // Wheel \"presses\" are immediately \"released\", and don't affect the state\n      // bitmask. Just set the current mouse button and clear everything else.\n      status->mouse_button = button;\n      status->mouse_repeat = MOUSE_NO_BUTTON;\n      status->mouse_repeat_state = 0;\n      status->mouse_drag_state = 0;\n      status->mouse_time = get_ticks();\n      break;\n    }\n\n    /* Finger events--mostly just debug for now. */\n    case SDL_EVENT_FINGER_MOTION:\n    {\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_FINGER_MOTION: id:%ld w:%u \"\n        \"x:%.2f y:%.2f dx:%.2f dy:%.2f p:%.2f\\n\",\n        (long)TFINGER_TOUCH_ID(event->tfinger), event->tfinger.windowID,\n        event->tfinger.x, event->tfinger.y,\n        event->tfinger.dx, event->tfinger.dy, event->tfinger.pressure\n      );\n      break;\n    }\n\n    case SDL_EVENT_FINGER_DOWN:\n    {\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_FINGER_DOWN: id:%ld w:%u \"\n        \"x:%.2f y:%.2f dx:%.2f dy:%.2f p:%.2f\\n\",\n        (long)TFINGER_TOUCH_ID(event->tfinger), event->tfinger.windowID,\n        event->tfinger.x, event->tfinger.y,\n        event->tfinger.dx, event->tfinger.dy, event->tfinger.pressure\n      );\n#ifdef __ANDROID__\n      {\n        /* There may be no physical keyboard or gamepad present. While this\n         * configuration is useless, this screen keyboard toggle gesture at\n         * least allows opening the main menu and exiting from the title. */\n        int count = 0;\n        SDL_TouchID tid = TFINGER_TOUCH_ID(event->tfinger);\n        SDL_Finger **fingers = SDL_GetTouchFingers(tid, &count);\n        if(fingers)\n        {\n          trace(\"--EVENT_SDL-- queried %d active finger(s)\\n\", count);\n          if(count == 3)\n            screen_keyboard_toggle_state();\n          SDL_free(fingers);\n        }\n        else\n          debug(\"--EVENT_SDL-- couldn't query finger(s): %s\\n\", SDL_GetError());\n      }\n#endif\n      break;\n    }\n\n    case SDL_EVENT_FINGER_UP:\n    {\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_FINGER_UP: id:%ld w:%u \"\n        \"x:%.2f y:%.2f dx:%.2f dy:%.2f p:%.2f\\n\",\n        (long)TFINGER_TOUCH_ID(event->tfinger), event->tfinger.windowID,\n        event->tfinger.x, event->tfinger.y,\n        event->tfinger.dx, event->tfinger.dy, event->tfinger.pressure\n      );\n      break;\n    }\n#endif // SDL_VERSION_ATLEAST(2,0,0)\n\n    case SDL_EVENT_KEY_DOWN:\n    {\n      uint32_t unicode = 0;\n      read_key_event(&key, &mod, &scancode, &event->key);\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n      // SDL 2.0 uses proper key repeat, but derives its timing from the OS.\n      // Our hand-rolled key repeat is more friendly to use than SDL 2.0's\n      // (with Windows, at least) and is consistent across all platforms.\n\n      // To enable SDL 2.0 key repeat, remove this check/break and see the\n      // update_autorepeat() function in event.c\n\n      if(event->key.repeat)\n        break;\n#endif\n\n#ifdef CONFIG_PANDORA\n      {\n        // Pandora hack. Certain keys are actually joystick buttons.\n        int button = get_pandora_joystick_button(key);\n        if(button >= 0)\n        {\n          joystick_button_press(status, 0, button);\n          break;\n        }\n      }\n#endif\n\n      ckey = convert_SDL_internal(key);\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_KEY_DOWN: scancode:%d sym:%xh mod:%xh -> %d\\n\",\n        scancode, key, mod, ckey\n      );\n      if(!ckey)\n      {\n#if !SDL_VERSION_ATLEAST(2,0,0)\n        ckey = IKEY_UNICODE;\n        if(!event->key.keysym.unicode)\n#endif\n          break;\n      }\n\n#if !SDL_VERSION_ATLEAST(2,0,0)\n      unicode = event->key.keysym.unicode;\n      if(unicode && unicode_fallback)\n      {\n        // Clear any unicode keys on the buffer generated from the fallback...\n        status->unicode_length = 0;\n        unicode_fallback = false;\n      }\n#endif\n\n#ifdef CONFIG_PSVITA\n      // Vita SDL keyboard will send an enter press immediately following\n      // text input, which screws up several things. Suppress this:\n      if(ckey == IKEY_RETURN && has_unicode_input())\n        break;\n#endif\n\n      // Some platforms don't implement SDL_TEXTINPUT (SDL 2.0) or the unicode\n      // field (SDL 1.2); until usage of those is detected, fake the text key\n      // using the internal keycode.\n      if(unicode_fallback && KEYCODE_IS_ASCII(ckey))\n      {\n        boolean caps_lock = !!(SDL_GetModState() & SDL_KMOD_CAPS);\n        unicode = convert_internal_unicode(ckey, caps_lock);\n      }\n      if(unicode)\n        trace(\"--EVENT_SDL--                     unicode:%d\\n\", (int)unicode);\n\n      if((ckey == IKEY_RETURN) &&\n       get_alt_status(keycode_internal) &&\n       get_ctrl_status(keycode_internal))\n      {\n        video_toggle_fullscreen();\n        break;\n      }\n\n      if(ckey == IKEY_CAPSLOCK)\n      {\n        status->caps_status = true;\n      }\n\n      if(ckey == IKEY_NUMLOCK)\n      {\n#if !SDL_VERSION_ATLEAST(2,0,0) && defined(__WIN32__)\n        status->numlock_status = true;\n#endif\n        break;\n      }\n\n#ifdef CONFIG_ENABLE_SCREENSHOTS\n      if(ckey == IKEY_F12 && enable_f12_hack)\n      {\n        dump_screen();\n        break;\n      }\n#endif\n\n      // Ignore alt + tab\n      if((ckey == IKEY_TAB) && get_alt_status(keycode_internal))\n      {\n        break;\n      }\n\n      if(status->key_repeat &&\n       (status->key_repeat != IKEY_LSHIFT) &&\n       (status->key_repeat != IKEY_RSHIFT) &&\n       (status->key_repeat != IKEY_LSUPER) &&\n       (status->key_repeat != IKEY_RSUPER) &&\n       (status->key_repeat != IKEY_LALT) &&\n       (status->key_repeat != IKEY_RALT) &&\n       (status->key_repeat != IKEY_ALTGR) &&\n       (status->key_repeat != IKEY_LCTRL) &&\n       (status->key_repeat != IKEY_RCTRL))\n      {\n        // Stack current repeat key if it isn't shift, super, alt, or ctrl\n        if(input.repeat_stack_pointer != KEY_REPEAT_STACK_SIZE)\n        {\n          input.key_repeat_stack[input.repeat_stack_pointer] =\n           status->key_repeat;\n          input.unicode_repeat_stack[input.repeat_stack_pointer] =\n           status->unicode_repeat;\n          input.repeat_stack_pointer++;\n        }\n      }\n\n      key_press(status, ckey);\n      key_press_unicode(status, unicode, true);\n      break;\n    }\n\n    case SDL_EVENT_KEY_UP:\n    {\n      read_key_event(&key, &mod, &scancode, &event->key);\n\n#ifdef CONFIG_PANDORA\n      {\n        // Pandora hack. Certain keys are actually joystick buttons.\n        int button = get_pandora_joystick_button(key);\n        if(button >= 0)\n        {\n          joystick_button_release(status, 0, button);\n          break;\n        }\n      }\n#endif\n\n      ckey = convert_SDL_internal(key);\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_KEY_UP: scancode:%d sym:%xh mod:%xh -> %d\\n\",\n        scancode, key, mod, ckey\n      );\n      if(!ckey)\n      {\n#if !SDL_VERSION_ATLEAST(2,0,0)\n        ckey = IKEY_UNICODE;\n        if(!status->keymap[IKEY_UNICODE])\n#endif\n          break;\n      }\n\n      if(ckey == IKEY_NUMLOCK)\n      {\n#if !SDL_VERSION_ATLEAST(2,0,0) && defined(__WIN32__)\n        status->numlock_status = false;\n#else\n        status->numlock_status = !status->numlock_status;\n#endif\n        break;\n      }\n\n      if(ckey == IKEY_CAPSLOCK)\n      {\n        status->caps_status = false;\n      }\n      key_release(status, ckey);\n      break;\n    }\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n    /**\n     * SDL 2 sends repeat key press events. In the case of SDL_TEXTINPUT, these\n     * can't be distinguished from regular key presses, so key_press_unicode\n     * needs to be called without repeating enabled.\n     */\n    case SDL_EVENT_TEXT_INPUT:\n    {\n      uint8_t *text = (uint8_t *)event->text.text;\n\n      trace(\"--EVENT_SDL-- SDL_EVENT_TEXT_INPUT: %s\\n\", text);\n\n      if(unicode_fallback)\n      {\n        // Clear any unicode keys on the buffer generated from the fallback...\n        status->unicode_length = 0;\n        unicode_fallback = false;\n      }\n\n      // Decode the input UTF-8 string into UTF-32 for the event buffer.\n      while(*text)\n      {\n        uint32_t unicode = utf8_next_char(&text);\n\n        if(unicode)\n          key_press_unicode(status, unicode, false);\n      }\n      break;\n    }\n\n    case SDL_EVENT_JOYSTICK_ADDED:\n    {\n      // Add a new joystick.\n      // \"which\" for this event (but not for any other joystick event) is not\n      // a joystick instance ID, but instead an index for SDL_JoystickOpen().\n\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_JOYSTICK_ADDED\\n\"\n      );\n\n      init_joystick(event->jdevice.which);\n      break;\n    }\n\n    case SDL_EVENT_JOYSTICK_REMOVED:\n    {\n      // Close a disconnected joystick.\n      int which = event->jdevice.which;\n      int joystick_index = get_joystick_index(which);\n\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_JOYSTICK_REMOVED: j%d\\n\",\n        joystick_index\n      );\n\n      close_joystick(joystick_index);\n\n      // Joysticks can be trivially disconnected while holding a button and\n      // the corresponding release event will never be sent for it. Release\n      // all of this joystick's inputs.\n      joystick_clear(status, joystick_index);\n      break;\n    }\n\n    case SDL_EVENT_GAMEPAD_AXIS_MOTION:\n    {\n      // Since gamepad axis mappings can be complicated, use\n      // the gamepad events to update the named axis values.\n      const struct config_info *conf = get_config();\n#if SDL_VERSION_ATLEAST(3,0,0)\n      int value = event->gaxis.value;\n      int which = event->gaxis.which;\n      int axis = event->gaxis.axis;\n#else\n      int value = event->caxis.value;\n      int which = event->caxis.which;\n      int axis = event->caxis.axis;\n#endif\n      enum joystick_special_axis special_axis = sdl_axis_map[axis];\n\n      int joystick_index = get_joystick_index(which);\n      if(joystick_index < 0 || !special_axis || !conf->allow_gamepad)\n        break;\n\n#if 0\n      /* May be sent almost nonstop, especially for Windows for some reason. */\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_GAMEPAD_AXIS_MOTION: j%d sa%d = %d\\n\",\n        joystick_index, special_axis, value\n      );\n#endif\n\n      joystick_special_axis_update(status, joystick_index, special_axis, value);\n      break;\n    }\n#endif\n\n    case SDL_EVENT_JOYSTICK_AXIS_MOTION:\n    {\n      int axis_value = event->jaxis.value;\n      int which = event->jaxis.which;\n      int axis = event->jaxis.axis;\n\n      // Get the real joystick index from the SDL instance ID\n      int joystick_index = get_joystick_index(which);\n      if(joystick_index < 0)\n        break;\n\n#if 0\n      /* May be sent almost nonstop, especially for Windows for some reason. */\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_JOYSTICK_AXIS_MOTION: j%d a%d = %d\\n\",\n        joystick_index, axis, axis_value\n      );\n#endif\n\n      joystick_axis_update(status, joystick_index, axis, axis_value);\n      break;\n    }\n\n    case SDL_EVENT_JOYSTICK_BUTTON_DOWN:\n    {\n      int which = event->jbutton.which;\n      int button = event->jbutton.button;\n\n      // Get the real joystick index from the SDL instance ID\n      int joystick_index = get_joystick_index(which);\n      if(joystick_index < 0)\n        break;\n\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_JOYSTICK_BUTTON_DOWN: j%d b%d\\n\",\n        joystick_index, button\n      );\n\n#ifdef CONFIG_SWITCH\n      // Ignore fake axis \"buttons\".\n      if((button >= 16) && (button <= 23))\n        break;\n#endif\n\n      joystick_button_press(status, joystick_index, button);\n      break;\n    }\n\n    case SDL_EVENT_JOYSTICK_BUTTON_UP:\n    {\n      int which = event->jbutton.which;\n      int button = event->jbutton.button;\n\n      // Get the real joystick index from the SDL instance ID\n      int joystick_index = get_joystick_index(which);\n      if(joystick_index < 0)\n        break;\n\n#ifdef CONFIG_SWITCH\n      // Ignore fake axis \"buttons\".\n      if((button >= 16) && (button <= 23))\n        break;\n#endif\n\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_JOYSTICK_BUTTON_UP: j%d b%d\\n\",\n        joystick_index, button\n      );\n\n      joystick_button_release(status, joystick_index, button);\n      break;\n    }\n\n    case SDL_EVENT_JOYSTICK_HAT_MOTION:\n    {\n      int which = event->jhat.which;\n      int dir = event->jhat.value;\n      boolean hat_u = (dir & SDL_HAT_UP) ? true : false;\n      boolean hat_d = (dir & SDL_HAT_DOWN) ? true : false;\n      boolean hat_l = (dir & SDL_HAT_LEFT) ? true : false;\n      boolean hat_r = (dir & SDL_HAT_RIGHT) ? true : false;\n\n      // Get the real joystick index from the SDL instance ID\n      int joystick_index = get_joystick_index(which);\n      if(joystick_index < 0)\n        break;\n\n      trace(\n        \"--EVENT_SDL-- SDL_EVENT_JOYSTICK_HAT_MOTION: \"\n        \"j%d up:%u down:%u left:%u right:%u\\n\",\n        joystick_index, hat_u, hat_d, hat_l, hat_r\n      );\n\n      joystick_hat_update(status, joystick_index, JOYHAT_UP, hat_u);\n      joystick_hat_update(status, joystick_index, JOYHAT_DOWN, hat_d);\n      joystick_hat_update(status, joystick_index, JOYHAT_LEFT, hat_l);\n      joystick_hat_update(status, joystick_index, JOYHAT_RIGHT, hat_r);\n      break;\n    }\n\n    default:\n      trace(\"--EVENT_SDL-- unhandled event %u\\n\", event->type);\n      return false;\n  }\n\n  return true;\n}\n\nboolean __update_event_status(void)\n{\n  boolean rval = false;\n  SDL_Event event;\n\n#ifdef CONFIG_WIIU\n  WHBProcIsRunning();\n#endif\n\n  while(SDL_PollEvent(&event))\n    rval |= process_event(&event);\n\n#if 0\n  // This one is a little annoying even for trace logging...\n  trace(\"--EVENT_SDL-- __update_event_status -> %u\\n\", rval);\n#endif\n\n#if !SDL_VERSION_ATLEAST(2,0,0)\n  {\n    // ALT+F4 - will not trigger an exit event, so set the variable manually.\n    struct buffered_status *status = store_status();\n\n    if(status->key_repeat == IKEY_F4 && get_alt_status(keycode_internal))\n    {\n      status->key = IKEY_UNKNOWN;\n      status->key_repeat = IKEY_UNKNOWN;\n      status->unicode_repeat = 0;\n      status->unicode_length = 0;\n      status->exit_status = true;\n      return true;\n    }\n  }\n#endif /*SDL_VERSION_ATLEAST*/\n\n  return rval;\n}\n\n// This returns whether the input buffer _may_ contain a request to quit.\n// Proper polling should be performed if the answer is yes.\nboolean __peek_exit_input(void)\n{\n  SDL_Event events[256];\n  int num_events;\n  int key, mod, scancode;\n  int i;\n\n  SDL_PumpEvents();\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n  num_events =\n   SDL_PeepEvents(events, 256, SDL_PEEKEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST);\n#else /* !SDL_VERSION_ATLEAST(2,0,0) */\n  num_events = SDL_PeepEvents(events, 256, SDL_PEEKEVENT, SDL_ALLEVENTS);\n#endif /* SDL_VERSION_ATLEAST(2,0,0) */\n\n  for(i = 0; i < num_events; i++)\n  {\n    if(events[i].type == SDL_EVENT_QUIT)\n      return true;\n\n    if(events[i].type == SDL_EVENT_KEY_DOWN)\n    {\n      read_key_event(&key, &mod, &scancode, &(events[i].key));\n\n      if(key == SDLK_ESCAPE)\n        return true;\n\n      if(key == SDLK_C && (mod & SDL_KMOD_CTRL))\n        return true;\n\n      if(key == SDLK_F4 && (mod & SDL_KMOD_ALT))\n        return true;\n    }\n  }\n  return false;\n}\n\nvoid __wait_event(void)\n{\n  /**\n   * This function used to take a timeout param, but we really don't want to\n   * implement that on a per-platform basis. SDL_WaitEventTimeout also does a\n   * very cool SDL_Delay(10) which we can probably live without.\n   */\n  SDL_Event event;\n  boolean any_event;\n\n  // FIXME: WaitEvent with MSVC hangs the render cycle, so this is, hopefully,\n  //        a short-term fix.\n#ifdef MSVC_H\n  any_event = SDL_PollEvent(&event);\n#else\n  any_event = SDL_WaitEvent(&event);\n#endif\n\n  if(any_event)\n    process_event(&event);\n}\n\nvoid __warp_mouse(int x, int y)\n{\n  SDL_Window *window = sdl_get_current_window();\n\n  if((x < 0) || (y < 0))\n  {\n#if SDL_VERSION_ATLEAST(3,0,0)\n    float current_x, current_y;\n#else\n    int current_x, current_y;\n#endif\n    SDL_GetMouseState(&current_x, &current_y);\n\n    if(x < 0)\n      x = current_x;\n\n    if(y < 0)\n      y = current_y;\n  }\n\n  SDL_WarpMouseInWindow(window, x, y);\n}\n\n/**\n * Enable text events for the provided window.\n * Prior to SDL3, this was global, so the window ID isn't used.\n */\nvoid sdl_init_window_text_events(unsigned sdl_window_id)\n{\n#if SDL_VERSION_ATLEAST(2,28,0)\n  /* This SDL version supports hiding the screen keyboard, so it's safe to\n   * enable text events and defer to MegaZeux's screen keyboard toggle. */\n  SDL_Window *window = SDL_GetWindowFromID(sdl_window_id);\n  if(window)\n    last_text_window_id = sdl_window_id;\n\n  SDL_StopTextInput(window);\n  SDL_SetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD,\n   input.showing_screen_keyboard ? \"1\" : \"0\");\n  SDL_StartTextInput(window);\n\n#elif SDL_VERSION_ATLEAST(2,0,0)\n  /* Hack: since this version of SDL doesn't support forcibly hiding the\n   * screen keyboard, if a screen keyboard is supported, do not enable text\n   * events and use the fallback instead. This is required on Android. */\n  if(!SDL_HasScreenKeyboardSupport())\n  {\n    SDL_StartTextInput();\n  }\n  else\n  {\n    SDL_StopTextInput();\n    unicode_fallback = true;\n  }\n#else\n  SDL_EnableUNICODE(1);\n#endif\n}\n\nboolean platform_has_screen_keyboard(void)\n{\n  return SDL_HasScreenKeyboardSupport();\n}\n\nboolean platform_show_screen_keyboard(void)\n{\n  trace(\"--EVENT_SDL-- requesting screen keyboard\\n\");\n  input.showing_screen_keyboard = true;\n  sdl_init_window_text_events(last_text_window_id);\n  return true;\n}\n\nboolean platform_hide_screen_keyboard(void)\n{\n  trace(\"--EVENT_SDL-- hiding screen keyboard\\n\");\n  input.showing_screen_keyboard = false;\n  sdl_init_window_text_events(last_text_window_id);\n  return true;\n}\n\nboolean platform_is_screen_keyboard_active(void)\n{\n  SDL_Window *window = SDL_GetWindowFromID(last_text_window_id);\n  (void)window; /* SDL2 */\n  return SDL_ScreenKeyboardShown(window);\n}\n\nvoid platform_init_event(void)\n{\n#ifdef NO_JOYSTICK_ADDED_EVENTS\n  int i, count;\n\n#if SDL_VERSION_ATLEAST(3,0,0)\n  SDL_JoystickID *instance_ids = SDL_GetJoysticks(&count);\n  if(instance_ids)\n  {\n    if(count > MAX_JOYSTICKS)\n      count = MAX_JOYSTICKS;\n\n    for(i = 0; i < count; i++)\n      init_joystick(instance_ids[i]);\n\n    SDL_free(instance_ids);\n  }\n#else\n  count = SDL_NumJoysticks();\n\n  if(count > MAX_JOYSTICKS)\n    count = MAX_JOYSTICKS;\n\n  for(i = 0; i < count; i++)\n    init_joystick(i);\n#endif\n#endif /* NO_JOYSTICK_ADDED_EVENTS */\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n  SDL_SetGamepadEventsEnabled(true);\n  load_gamecontrollerdb();\n#endif\n\n  SDL_SetJoystickEventsEnabled(true);\n\n#if SDL_VERSION_ATLEAST(3,0,0)\n  /* Disable a couple of events that are unused by MZX. */\n  SDL_SetEventEnabled(SDL_EVENT_JOYSTICK_UPDATE_COMPLETE, false);\n  SDL_SetEventEnabled(SDL_EVENT_GAMEPAD_UPDATE_COMPLETE, false);\n#endif\n\n  /* It's not clear which ports do and don't implement SDL text events, so\n   * enable the unicode fallback at startup until proven otherwise.\n   * SDL 1.2 might also need this (Pandora? doesn't generate unicode presses).\n   * If it isn't required, real unicode events will turn this off. */\n  unicode_fallback = true;\n}\n"
  },
  {
    "path": "src/expr.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson (original tr_msg)\n * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <ctype.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"expr.h\"\n\n#include \"counter.h\"\n#include \"memcasecmp.h\"\n#include \"rasm.h\"\n#include \"robot.h\"\n#include \"str.h\"\n#include \"util.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n\nenum op\n{\n  OP_ADDITION,\n  OP_SUBTRACTION,\n  OP_MULTIPLICATION,\n  OP_DIVISION,\n  OP_MODULUS,\n  OP_EXPONENTIATION,\n  OP_AND,\n  OP_OR,\n  OP_XOR,\n  OP_BITSHIFT_LEFT,\n  OP_BITSHIFT_RIGHT,\n  OP_ARITHMETIC_BITSHIFT_RIGHT,\n  OP_EQUAL,\n  OP_LESS_THAN,\n  OP_GREATER_THAN,\n  OP_GREATER_THAN_OR_EQUAL,\n  OP_LESS_THAN_OR_EQUAL,\n  OP_NOT_EQUAL,\n  OP_TERNARY\n};\n\n#ifdef CONFIG_DEBYTECODE\n// Forward declaration for recursion...\nstatic inline char *skip_identifier(char *expression, char terminator);\n\n/**\n * Skip string interpolation brackets.\n */\nstatic inline char *skip_string_interpolation(char *expression)\n{\n  char current_char;\n\n  while(1)\n  {\n    current_char = *expression;\n    expression++;\n\n    if(current_char == '\\0')\n    {\n      return expression - 1;\n    }\n    else\n\n    if(current_char == '}')\n    {\n      break;\n    }\n    else\n\n    if(current_char == '`')\n    {\n      expression = skip_identifier(expression, current_char);\n    }\n  }\n  return expression;\n}\n#endif\n\n/**\n * Skip an expression wrapped in parentheses.\n */\nstatic inline char *skip_expression(char *expression)\n{\n  char current_char;\n  int level = 0;\n\n  while(1)\n  {\n    current_char = *expression;\n    expression++;\n\n    if(current_char == '\\0')\n    {\n      return expression - 1;\n    }\n    else\n\n    if(current_char == '(')\n    {\n      level++;\n    }\n    else\n\n    if(current_char == ')')\n    {\n      if(level <= 0)\n        break;\n\n      level--;\n    }\n  }\n  return expression;\n}\n\n/**\n * Skip an identifier.\n */\nstatic inline char *skip_identifier(char *expression, char terminator)\n{\n  char current_char;\n\n  while(1)\n  {\n    current_char = *expression;\n    expression++;\n\n    if(current_char == '\\0')\n    {\n      return expression - 1;\n    }\n    else\n\n#ifndef CONFIG_DEBYTECODE\n    if(current_char == '(')\n    {\n      expression = skip_expression(expression);\n    }\n    else\n#else\n    if(current_char == '{')\n    {\n      expression = skip_string_interpolation(expression);\n    }\n    else\n#endif\n\n    if(current_char == terminator)\n      break;\n  }\n  return expression;\n}\n\n/**\n * Parse the current expression level until the terminator is found, skipping\n * the contents of identifiers and nested expressions. If the terminator char\n * is not found, returns false. This is pretty dumb, but still faster than\n * actually running the expression.\n */\nstatic inline boolean ternary_short_circuit(char **_expression, char terminator,\n int *error)\n{\n  char *expression = *_expression;\n  char current_char;\n  int ternary_level = 0;\n\n  while(1)\n  {\n    current_char = *expression;\n    expression++;\n\n    // Only allow termination if a nested ternary expression hasn't started...\n    if(current_char == terminator && !ternary_level)\n    {\n      *_expression = expression;\n      return true;\n    }\n\n    if(current_char == '\\0')\n    {\n      break;\n    }\n    else\n\n    if(current_char == '(')\n    {\n      expression = skip_expression(expression);\n    }\n    else\n\n    if(current_char == ')')\n    {\n      break;\n    }\n    else\n\n    if(current_char == '?')\n    {\n      ternary_level++;\n    }\n    else\n\n    if(current_char == ':')\n    {\n      if(ternary_level <= 0)\n        break;\n\n      ternary_level--;\n    }\n    else\n\n    // The main concern here is reserved chars being found in identifiers.\n    // The affected identifiers should always have quotes, though.\n#ifdef CONFIG_DEBYTECODE\n    if(current_char == '`')\n#else\n    if(current_char == '&' || current_char == '\\'')\n#endif\n    {\n      expression = skip_identifier(expression, current_char);\n    }\n  }\n  if(error)\n    *error = 2;\n  return false;\n}\n\n#ifndef CONFIG_DEBYTECODE\n\n/* This new expression parser for legacy Robotic is faster than the older\n * parser. It's not suited to the more complex debytecode (mainly thanks to\n * interpolation), so I've left the old parser intact below for debytecode.\n *\n * Error codes:\n * -1 - unknown\n * 0  - success\n * 1  - invalid operand\n * 2  - invalid operator\n * 3  - stack overflow\n * 4  - stack underflow\n */\n\n#define EXPR_BUFFER_SIZE 512\n#define EXPR_STACK_SIZE 32\n\n#define EXPR_STATE_START_OPERAND 1\n#define EXPR_STATE_PARSE_OPERAND 2\n#define EXPR_STATE_AMP 4\n#define EXPR_STATE_PUSH 8\n#define EXPR_STATE_PUSH_INTERPOLATION 16\n#define EXPR_STATE_INTERPOLATING 32\n#define EXPR_STATE_TERNARY_MIDDLE 64\n\nstruct expr_stack {\n  int buf_start;\n  int operand;\n  char operator;\n  char state;\n};\n\nstatic char buffer[EXPR_BUFFER_SIZE];\nstatic struct expr_stack stack[EXPR_STACK_SIZE];\nstatic int stack_alloc = EXPR_STACK_SIZE;\nstatic char *buf_alloc = buffer + EXPR_BUFFER_SIZE;\n\n// Push a null onto the buffer to help with unary operator processing.\n\n#define PUSH_STACK(st)                \\\n{                                     \\\n  if(pos >= stack_alloc)              \\\n  {                                   \\\n    *error = 3;                       \\\n    goto err_out;                     \\\n  }                                   \\\n                                      \\\n  stack[pos].buf_start = buf_start;   \\\n  stack[pos].operator = operator;     \\\n  stack[pos].operand = operand_a;     \\\n  stack[pos].state = st;              \\\n  pos++;                              \\\n                                      \\\n  *buf_pos = '\\0';                    \\\n  buf_pos++;                          \\\n                                      \\\n  buf_start = buf_pos - buffer;       \\\n  operator = OP_ADDITION;             \\\n  operand_a = 0;                      \\\n}\n\n// if err is true, error out at the bottom of the stack\n// if err is false, exit successfully at the bottom of the stack\n\n#define POP_STACK(err)                \\\n{                                     \\\n  buf_pos--;                          \\\n  pos--;                              \\\n  if(pos >= 0)                        \\\n  {                                   \\\n    buf_start = stack[pos].buf_start; \\\n    operator = stack[pos].operator;   \\\n    operand_a = stack[pos].operand;   \\\n    state = stack[pos].state;         \\\n  }                                   \\\n  else if(err)                        \\\n  {                                   \\\n    *error = 4;                       \\\n    goto err_out;                     \\\n  }                                   \\\n  else                                \\\n  {                                   \\\n    continue;                         \\\n  }                                   \\\n}\n\nstatic inline void skip_spaces(char **_expression)\n{\n  char *expression = *_expression;\n\n  while(isspace((int)*expression))\n    expression++;\n\n  *_expression = expression;\n}\n\nint parse_expression(struct world *mzx_world, char **_expression, int *error,\n int id)\n{\n  char number_buffer[16];\n\n  // Position in stack\n  int pos = 0;\n  char current_char;\n\n  // Current positions in the buffer and in the original expression\n  char *buf_pos = buffer;\n  char *expression = *_expression;\n\n  // For the current level of expression:\n  // Offset in the buffer where operand B's name starts\n  int buf_start = 0;\n\n  // Operator ID, values for operands\n  enum op operator = OP_ADDITION;\n  int operand_a = 0;\n  int operand_b = 0;\n\n  // Parsing state\n  char state = 0;\n\n  *error = 0;\n\n  // Note: initial open paren already skipped.\n  do\n  {\n    // Get next operand\n\n\n    // Figure out what our operand actually is.\n    // Also handle the prefixed unary operators.\n\n    if(!(state & EXPR_STATE_START_OPERAND))\n    {\n      // We need to skip spaces, first\n      skip_spaces(&expression);\n\n      current_char = *expression;\n      expression++;\n\n      switch(current_char)\n      {\n        // One's compliment\n        case '~':\n        case '!':\n          // Put it on the name buffer and handle it once we have a value.\n          *buf_pos = '~';\n          buf_pos++;\n          continue;\n\n        // Unary negation\n        case '-':\n          // Put it on the name buffer and handle it once we have a value.\n          *buf_pos = '-';\n          buf_pos++;\n          continue;\n\n        // Nested expression\n        case '(':\n          state |= EXPR_STATE_PUSH;\n          break;\n\n        // Counter\n        case '\\'':\n          state |= EXPR_STATE_PARSE_OPERAND;\n          break;\n\n        // Also a counter\n        case '&':\n          state |= EXPR_STATE_PARSE_OPERAND | EXPR_STATE_AMP;\n          break;\n\n        // Otherwise, it must be an integer, or just something invalid.\n        default:\n        {\n          if((current_char >= '0') && (current_char <= '9'))\n          {\n            // Integer\n            char *end_p;\n            operand_b = (int)strtol(expression - 1, &end_p, 0);\n            expression = end_p;\n            break;\n          }\n          else\n          {\n            // Invalid operand\n            *error = 1;\n            goto err_out;\n          }\n        }\n      }\n      state |= EXPR_STATE_START_OPERAND;\n      buf_start = buf_pos - buffer;\n    }\n\n\n    // Parse a named operand.\n    if(state & EXPR_STATE_PARSE_OPERAND)\n    {\n      do\n      {\n        current_char = *expression;\n        expression++;\n\n        if(buf_pos >= buf_alloc)\n        {\n          // Truncate the counter name and continue.\n          buf_pos = buf_alloc - 1;\n          *buf_pos = '\\0';\n          break;\n        }\n\n        if(current_char == '\\'')\n        {\n          if(state & EXPR_STATE_INTERPOLATING)\n          {\n            // Unexpected end of interpolation!\n            // Back up expression so it finds ' the next level down.\n            expression--;\n          }\n          *buf_pos = '\\0';\n          break;\n        }\n        else\n\n        if(current_char == '&')\n        {\n          if(state & EXPR_STATE_AMP)\n          {\n            // Already reading a &counter&? This is the end\n            *buf_pos = '\\0';\n          }\n          else\n\n          if(*expression == '&')\n          {\n            // Two &&s in a counter name -> one & char\n            expression++;\n            *buf_pos = '&';\n            buf_pos++;\n          }\n\n          else\n          {\n            // Interpolation\n            state |= EXPR_STATE_PUSH_INTERPOLATION;\n          }\n          break;\n        }\n        else\n\n        if(current_char == '\\0')\n        {\n          // Invalid -- truncated operand\n          *error = 1;\n          goto err_out;\n        }\n        else\n\n        if(current_char == '(')\n        {\n          state |= EXPR_STATE_PUSH;\n          break;\n        }\n\n        else\n        {\n          *buf_pos = current_char;\n          buf_pos++;\n        }\n      }\n      while(1);\n    }\n\n\n    // Push state onto stack for expressions and interpolation\n    if(state & (EXPR_STATE_PUSH|EXPR_STATE_PUSH_INTERPOLATION))\n    {\n      char push_state = state & ~EXPR_STATE_PUSH &\n       ~EXPR_STATE_PUSH_INTERPOLATION;\n\n      PUSH_STACK(push_state);\n\n      // Set up for interpolating a counter/string\n      if(state & EXPR_STATE_PUSH_INTERPOLATION)\n      {\n        state = EXPR_STATE_START_OPERAND | EXPR_STATE_PARSE_OPERAND\n         | EXPR_STATE_AMP | EXPR_STATE_INTERPOLATING;\n      }\n      else\n      {\n        state = 0;\n      }\n\n      continue;\n    }\n\n\n    // Interpolate a counter or string\n    if(state & EXPR_STATE_INTERPOLATING)\n    {\n      const char *src;\n      size_t len;\n      buf_pos = buffer + buf_start;\n\n      // Input\n      if(!memcasecmp(buf_pos, \"INPUT\", 6))\n      {\n        src = mzx_world->current_board->input_string;\n        src = src ? src : \"\";\n        len = strlen(src);\n      }\n      else\n\n      // Counter or string\n      if(is_string(buf_pos))\n      {\n        // Write the value of the counter name\n        struct string str_src;\n\n        get_string(mzx_world, buf_pos, &str_src, 0);\n\n        src = str_src.value;\n        len = str_src.length;\n      }\n      else\n\n      // #(counter) is a hex representation.\n      if(*buf_pos == '+')\n      {\n        src = tr_int_to_hex_string(number_buffer,\n         get_counter(mzx_world, buf_pos + 1, id), &len);\n      }\n      else\n\n      if(*buf_pos == '#')\n      {\n        sprintf(number_buffer, \"%02x\",\n         get_counter(mzx_world, buf_pos + 1, id));\n\n        src = number_buffer;\n        len = 2;\n      }\n      else\n      {\n        src = tr_int_to_string(number_buffer,\n         get_counter(mzx_world, buf_pos, id), &len);\n      }\n\n      // Pop before writing to remove the unary null terminator\n      POP_STACK(true);\n\n      if((ptrdiff_t)len > (buf_alloc - buf_pos))\n      {\n        // Truncate the interpolated value and continue\n        len = buf_alloc - buf_pos;\n      }\n\n      if(len)\n      {\n        memcpy(buf_pos, src, len);\n        buf_pos += len;\n      }\n      continue;\n    }\n\n\n    // Translate operand B\n    buf_pos = buffer + buf_start;\n    if(state & EXPR_STATE_PARSE_OPERAND)\n    {\n      operand_b = get_counter(mzx_world, buf_pos, id);\n    }\n\n    // Clear states for next operand\n    state &= EXPR_STATE_TERNARY_MIDDLE;\n\n\n    // Apply unary operators\n    while((--buf_pos) >= buffer)\n    {\n      switch(*buf_pos)\n      {\n        case '~':\n          operand_b = ~operand_b;\n          continue;\n\n        case '-':\n          operand_b = -operand_b;\n          continue;\n      }\n      break;\n    }\n\n    // Shift the buffer pos in front of the unary null, or to the beginning\n    buf_pos++;\n\n\n    // Perform operation\n    switch(operator)\n    {\n      case OP_ADDITION:\n        operand_a += operand_b;\n        break;\n\n      case OP_SUBTRACTION:\n        operand_a -= operand_b;\n        break;\n\n      case OP_MULTIPLICATION:\n        operand_a *= operand_b;\n        break;\n\n      case OP_DIVISION:\n        operand_a = safe_divide_32(operand_a, operand_b);\n        break;\n\n      case OP_MODULUS:\n      {\n        int val = safe_modulo_32(operand_a, operand_b);\n\n        // Converted C99 regulated truncated modulus to\n        // the more useful (for us) floored modulus\n        // Source:\n        // Division and Modulus for Computer Scientists\n        // DAAN LEIJEN\n        // University of Utrecht\n\n        if((val < 0) ^ (operand_b < 0))\n          val += operand_b;\n\n        operand_a = val;\n        break;\n      }\n\n      case OP_EXPONENTIATION:\n      {\n        int i;\n        int val = 1;\n\n        // a==0 -> result = 0\n        if(operand_a == 0)\n          break;\n\n        // a==1 -> result = 1\n        if(operand_a == 1)\n          break;\n\n        // a==-1 -> result = 1 if b is even, -1 if b is odd\n        if(operand_a == -1)\n          operand_b &= 1;\n\n        // no floating point support :(\n        if(operand_b < 0)\n        {\n          operand_a = 0;\n          break;\n        }\n\n        for(i = 0; i < operand_b; i++)\n          val *= operand_a;\n\n        operand_a = val;\n        break;\n      }\n\n      case OP_AND:\n        operand_a &= operand_b;\n        break;\n\n      case OP_OR:\n        operand_a |= operand_b;\n        break;\n\n      case OP_XOR:\n        operand_a ^= operand_b;\n        break;\n\n      case OP_BITSHIFT_LEFT:\n        operand_a = safe_left_shift_32(operand_a, operand_b);\n        break;\n\n      case OP_BITSHIFT_RIGHT:\n        operand_a = safe_logical_right_shift_32(operand_a, operand_b);\n        break;\n\n      case OP_ARITHMETIC_BITSHIFT_RIGHT:\n        operand_a = safe_arithmetic_right_shift_32(operand_a, operand_b);\n        break;\n\n      case OP_EQUAL:\n        operand_a = (operand_a == operand_b);\n        break;\n\n      case OP_LESS_THAN:\n        operand_a = (operand_a < operand_b);\n        break;\n\n      case OP_LESS_THAN_OR_EQUAL:\n        operand_a = (operand_a <= operand_b);\n        break;\n\n      case OP_GREATER_THAN:\n        operand_a = (operand_a > operand_b);\n        break;\n\n      case OP_GREATER_THAN_OR_EQUAL:\n        operand_a = (operand_a >= operand_b);\n        break;\n\n      case OP_NOT_EQUAL:\n        operand_a = (operand_a != operand_b);\n        break;\n\n      default:\n        break;\n    }\n\n\n    // Get next operator -- we need to skip any spaces first\n    // Prefix operators are handled during the operand search, not here.\n    skip_spaces(&expression);\n\n    current_char = *expression;\n    expression++;\n\n    switch(current_char)\n    {\n      // Ternary operator left (2.90+)\n      case '?':\n      {\n        if(mzx_world->version < V290)\n          goto err_out;\n\n        // True\n        if(operand_a)\n        {\n          PUSH_STACK(state);\n          state = EXPR_STATE_TERNARY_MIDDLE;\n        }\n\n        // False - seek next ':'\n        else\n        {\n          if(!ternary_short_circuit(&expression, ':', error))\n            goto err_out;\n\n          // Preserve ternary middle state in case these are nested\n          state = state & EXPR_STATE_TERNARY_MIDDLE;\n        }\n        operator = OP_ADDITION;\n        operand_a = 0;\n        break;\n      }\n\n      // Ternary operator right\n      case ':':\n      {\n        int value = operand_a;\n        int ternary_level = 0;\n\n        if(mzx_world->version < V290)\n          goto err_out;\n\n        if(!(state & EXPR_STATE_TERNARY_MIDDLE))\n        {\n          *error = 2;\n          goto err_out;\n        }\n\n        POP_STACK(true);\n\n        // Seek next ')'\n        while(1)\n        {\n          current_char = *expression;\n          expression++;\n\n          if(current_char == '\\0')\n          {\n            *error = 2;\n            goto err_out;\n          }\n          else\n\n          // Validate ternary operators while we're at it (actually necessary)\n          if(current_char == '?')\n          {\n            ternary_level++;\n          }\n          else\n\n          if(current_char == ':')\n          {\n            if(ternary_level == 0)\n            {\n              // Unroll ternary stack levels as-needed\n              if(state & EXPR_STATE_TERNARY_MIDDLE)\n              {\n                POP_STACK(true);\n              }\n              else\n              {\n                *error = 2;\n                goto err_out;\n              }\n            }\n            else\n            {\n              ternary_level--;\n            }\n          }\n          else\n\n          if(current_char == '\\'' || current_char == '&')\n          {\n            expression = skip_identifier(expression, current_char);\n          }\n          else\n\n          if(current_char == '(')\n          {\n            expression = skip_expression(expression);\n          }\n          else\n\n          if(current_char == ')')\n          {\n            break;\n          }\n        }\n\n        operand_a = value;\n\n        // Proceed into the ')' handler:\n      }\n\n      /* fallthrough */\n\n      // End of expression\n      case ')':\n      {\n        int value = operand_a;\n        size_t len;\n\n        // Invalid end of expression where : should exist\n        if(state & EXPR_STATE_TERNARY_MIDDLE)\n        {\n          *error = 2;\n          goto err_out;\n        }\n\n        // Pop before writing to (if necessary) overwrite the unary null\n        POP_STACK(false);\n\n        // If we're in the middle of an operand, print to the buffer\n        if(state & EXPR_STATE_PARSE_OPERAND)\n        {\n          char *src = tr_int_to_string(number_buffer, value, &len);\n\n          if((ptrdiff_t)(len + 1) > (buf_alloc - buf_pos))\n          {\n            // Truncate the interpolated value and continue.\n            len = buf_alloc - buf_pos - 1;\n          }\n\n          memcpy(buf_pos, src, len);\n          buf_pos += len;\n        }\n\n        // Otherwise, this is the new operand\n        else\n        {\n          operand_b = value;\n        }\n        continue;\n      }\n\n      // Addition operator\n      case '+':\n      {\n        operator = OP_ADDITION;\n        break;\n      }\n\n      // Subtraction operator\n      case '-':\n      {\n        operator = OP_SUBTRACTION;\n        break;\n      }\n\n      // Multiplication operator\n      case '*':\n      {\n        operator = OP_MULTIPLICATION;\n        break;\n      }\n\n      // Division operator\n      case '/':\n      {\n        operator = OP_DIVISION;\n        break;\n      }\n\n      // Modulus operator\n      case '%':\n      {\n        operator = OP_MODULUS;\n        break;\n      }\n\n      // Exponent operator\n      case '^':\n      {\n        operator = OP_EXPONENTIATION;\n        break;\n      }\n\n      // Bitwise AND operator\n      case 'a':\n      {\n        operator = OP_AND;\n        break;\n      }\n\n      // Bitwise OR operator\n      case 'o':\n      {\n        operator = OP_OR;\n        break;\n      }\n\n      // Bitwise XOR operator\n      case 'x':\n      {\n        operator = OP_XOR;\n        break;\n      }\n\n      // Less than/bitshift left\n      case '<':\n      {\n        if(*expression == '<')\n        {\n          expression++;\n          operator = OP_BITSHIFT_LEFT;\n          break;\n        }\n        if(*expression == '=')\n        {\n          expression++;\n          operator = OP_LESS_THAN_OR_EQUAL;\n          break;\n        }\n        operator = OP_LESS_THAN;\n        break;\n      }\n\n      // Greater than/bitshift right\n      case '>':\n      {\n        if(*expression == '>')\n        {\n          expression++;\n          if(*expression == '>')\n          {\n            expression++;\n            operator = OP_ARITHMETIC_BITSHIFT_RIGHT;\n            break;\n          }\n          operator = OP_BITSHIFT_RIGHT;\n          break;\n        }\n        if(*expression == '=')\n        {\n          expression++;\n          operator = OP_GREATER_THAN_OR_EQUAL;\n          break;\n        }\n        operator = OP_GREATER_THAN;\n        break;\n      }\n\n      // Equality\n      case '=':\n      {\n        operator = OP_EQUAL;\n        break;\n      }\n\n      // Inequality\n      case '!':\n      {\n        if(*expression == '=')\n        {\n          expression++;\n          operator = OP_NOT_EQUAL;\n          break;\n        }\n        // Invalid operator\n        goto err_out;\n      }\n\n      // Null terminator or unrecognized symbol\n      case '\\0':\n      default:\n        // Invalid operator\n        *error = 2;\n        goto err_out;\n    }\n  }\n  while(pos >= 0);\n\n  *_expression = expression;\n  return operand_a;\n\nerr_out:\n  if(!*error)\n    *error = -1;\n\n  *_expression = expression;\n  return 0;\n}\n\n\n#else /* CONFIG_DEBYTECODE */\n\n\n/* This is the old expression parser in its full glory.\n *\n * This parser is slower than the new one, but generally easier to understand.\n * Rather than adapt the new parser to the more complex debytecode--so it could\n * be rewritten for compiled expressions later anyway--I'm just leaving the old\n * one intact for now.\n */\n\n\n// Only pass these evaluators valid expressions. The compiler should take care\n// of that part, so don't call it outside of evaluating robot code.\n\nstatic int last_val;\n\nstatic char *expr_skip_whitespace(char *expression)\n{\n  while(isspace((int)*expression))\n    expression++;\n  return expression;\n}\n\nstatic int parse_argument(struct world *mzx_world, char **_argument,\n int *type, int operand_a, int id, const char terminator1, const char terminator2)\n{\n  char *argument = *_argument;\n  int first_char = *argument;\n\n  // Test the first one.\n\n  switch(first_char)\n  {\n    // Addition operator\n    case '+':\n    {\n      *type = 1;\n      *_argument = argument + 1;\n      return OP_ADDITION;\n    }\n\n    // Subtraction operator\n    case '-':\n    {\n      argument++;\n      if(!isspace((int)*argument))\n      {\n        int t2;\n        int val = parse_argument(mzx_world, &argument, &t2, 0, id,\n         terminator1, terminator2);\n#ifndef CONFIG_DEBYTECODE\n        if((t2 != 0) && (t2 != 2))\n        {\n          *type = -1;\n          *_argument = argument;\n          return -1;\n        }\n#endif\n        val = -val;\n        *type = 2;\n        *_argument = argument;\n        return val;\n      }\n\n      *type = 1;\n      *_argument = argument;\n      return OP_SUBTRACTION;\n    }\n\n    // Multiplication operator\n    case '*':\n    {\n      *type = 1;\n      argument++;\n#ifdef CONFIG_DEBYTECODE\n      if(*argument == '*')\n      {\n        *_argument = argument + 1;\n        return OP_EXPONENTIATION;\n      }\n      else\n#endif\n      {\n        *_argument = argument;\n        return OP_MULTIPLICATION;\n      }\n    }\n\n    // Division operator\n    case '/':\n    {\n      *type = 1;\n      *_argument = argument + 1;\n      return OP_DIVISION;\n    }\n\n    // Modulus operator\n    case '%':\n    {\n      *type = 1;\n      *_argument = argument + 1;\n      return OP_MODULUS;\n    }\n\n#ifndef CONFIG_DEBYTECODE\n    // Exponent operator\n    case '^':\n    {\n      *type = 1;\n      *_argument = argument + 1;\n      return OP_EXPONENTIATION;\n    }\n#endif\n\n    // Bitwise AND operator\n#ifdef CONFIG_DEBYTECODE\n    case '&':\n#else\n    case 'a':\n#endif\n    {\n      *type = 1;\n      *_argument = argument + 1;\n      return OP_AND;\n    }\n\n    // Bitwise OR operator\n#ifdef CONFIG_DEBYTECODE\n    case '|':\n#else\n    case 'o':\n#endif\n    {\n      *type = 1;\n      *_argument = argument + 1;\n      return OP_OR;\n    }\n\n    // Bitwise XOR operator\n#ifdef CONFIG_DEBYTECODE\n    case '^':\n#else\n    case 'x':\n#endif\n    {\n      *type = 1;\n      *_argument = argument + 1;\n      return OP_XOR;\n    }\n\n    // Less than/bitshift left\n    case '<':\n    {\n      *type = 1;\n      argument++;\n      if(*argument == '<')\n      {\n        *_argument = argument + 1;\n        return OP_BITSHIFT_LEFT;\n      }\n      if(*argument == '=')\n      {\n        *_argument = argument + 1;\n        return OP_LESS_THAN_OR_EQUAL;\n      }\n      *_argument = argument;\n      return OP_LESS_THAN;\n    }\n\n    // Greater than/bitshift right\n    case '>':\n    {\n      *type = 1;\n      argument++;\n      if(*argument == '>')\n      {\n        if(argument[1] == '>')\n        {\n          *_argument = argument + 2;\n          return OP_ARITHMETIC_BITSHIFT_RIGHT;\n        }\n        *_argument = argument + 1;\n        return OP_BITSHIFT_RIGHT;\n      }\n      if(*argument == '=')\n      {\n        *_argument = argument + 1;\n        return OP_GREATER_THAN_OR_EQUAL;\n      }\n      *_argument = argument;\n      return OP_GREATER_THAN;\n    }\n\n    // Equality\n    case '=':\n    {\n      *type = 1;\n      *_argument = argument + 1;\n      return OP_EQUAL;\n    }\n\n    // Inequality (if there is no =, we do one's compliment)\n    case '!':\n    {\n      if(*(argument + 1) == '=')\n      {\n        *type = 1;\n        *_argument = argument + 2;\n        return OP_NOT_EQUAL;\n      }\n    }\n\n    /* fallthrough */\n\n    // One's complement\n    case '~':\n    {\n      int t2, val;\n\n      argument++;\n      val = parse_argument(mzx_world, &argument, &t2, 0, id,\n       terminator1, terminator2);\n\n#ifndef CONFIG_DEBYTECODE\n      if((t2 != 0) && (t2 != 2))\n      {\n        *type = -1;\n        *_argument = argument;\n        return -1;\n      }\n#endif\n\n      val = ~val;\n      *type = 2;\n      *_argument = argument;\n      return val;\n    }\n\n#ifndef CONFIG_DEBYTECODE\n    // # is the last expression value, 32bit.\n    case '#':\n    {\n      *type = 0;\n      *_argument = argument + 1;\n      return last_val;\n    }\n\n    // The evil null terminator...\n    case '\\0':\n    {\n      *type = -1;\n      return -1;\n    }\n#endif\n\n    // Parentheses! Recursive goodness...\n    // Treat a valid return as an argument.\n    case '(':\n    {\n      int val, error;\n      argument++;\n      val = parse_expression(mzx_world, &argument, &error, id);\n#ifndef CONFIG_DEBYTECODE\n      if(error)\n      {\n        *type = -1;\n        *_argument = argument;\n        return -1;\n      }\n#endif\n      *type = 0;\n      *_argument = argument;\n      return val;\n    }\n\n    // Ternary operator left (2.90+)\n    case '?':\n    {\n      if(mzx_world->version < V290)\n      {\n        *type = -1;\n        *_argument = argument;\n        return -1;\n      }\n\n      // True - nothing to be done here\n      // False - seek next ':'\n      argument++;\n      if(!operand_a)\n      {\n        if(!ternary_short_circuit(&argument, ':', NULL))\n        {\n          *type = -1;\n          *_argument = argument;\n          return -1;\n        }\n      }\n\n      *type = 1;\n      *_argument = argument;\n      return OP_TERNARY;\n    }\n\n    // Ternary operator right\n    case ':':\n    {\n      // We're only here because we finished execution of the inner argument.\n      if(mzx_world->version < V290)\n      {\n        *type = -1;\n        *_argument = argument;\n        return -1;\n      }\n\n      // Seek next terminator\n      while(1)\n      {\n        argument++;\n        first_char = *argument;\n\n#ifndef CONFIG_DEBYTECODE\n        if(first_char == '\\0')\n        {\n            *type = -1;\n            *_argument = argument;\n            return -1;\n        }\n        else\n#endif // !CONFIG_DEBYTECODE\n\n        if(first_char == '`')\n        {\n          argument = skip_identifier(argument + 1, first_char) - 1;\n        }\n        else\n\n        if(first_char == '(')\n        {\n          argument = skip_expression(argument + 1) - 1;\n        }\n        else\n\n        if(first_char == terminator1 || first_char == terminator2)\n        {\n          break;\n        }\n      }\n\n      // This performs an addition (in this case, of zero) without requiring\n      // us to seek a new operand. The parser will then find the ')' and exit.\n      *type = 2;\n      *_argument = argument;\n      return 0;\n    }\n\n#ifdef CONFIG_DEBYTECODE\n\n    // Begins with a ` makes it a counter name\n    case '`':\n    {\n      char name_translated[ROBOT_MAX_TR];\n      argument++;\n\n      *type = 0;\n      *_argument = tr_msg_ext(mzx_world, argument, id, name_translated, '`');\n      return get_counter(mzx_world, name_translated, id);\n    }\n\n#else // !CONFIG_DEBYTECODE\n\n    // Begins with a ' or a & makes it a message ('counter')\n    case '&':\n    case '\\'':\n    {\n      // Find where the next ' or & is\n      char t_char = first_char;\n      char temp[256];\n      char temp2[256];\n      int count = 0;\n\n      argument++;\n\n      // Remember, null terminator is evil; if it's hit exit completely.\n      while(((*argument) != t_char) && (count < 256))\n      {\n        // If a nested expression is hit closing 's should be ignored\n        if((*argument) == '(')\n        {\n          int close_paren_count = 1;\n          // The number of )'s to expect.. finding one decreases it..\n          // And finding a ( increases it.\n\n          while((close_paren_count) && (count < 256))\n          {\n            temp[count] = *argument;\n            argument++;\n            count++;\n\n            switch(*argument)\n            {\n              case '\\0':\n              {\n                *type = -1;\n                *_argument = argument;\n                return -1;\n              }\n              case ')':\n              {\n                close_paren_count--;\n                break;\n              }\n              case '(':\n              {\n                close_paren_count++;\n                break;\n              }\n            }\n          }\n        }\n        else\n        {\n          if(*argument == '\\0')\n          {\n            *type = -1;\n            *_argument = argument;\n            return -1;\n          }\n\n          temp[count] = *argument;\n          argument++;\n          count++;\n        }\n      }\n\n      *type = 0;\n      *_argument = argument + 1;\n\n      temp[count] = '\\0';\n      tr_msg(mzx_world, temp, id, temp2);\n      return get_counter(mzx_world, temp2, id);\n    }\n\n#endif // !CONFIG_DEBYTECODE\n\n    // Otherwise, it must be an integer, or just something invalid.\n    default:\n    {\n      if((first_char >= '0') && (first_char <= '9'))\n      {\n        char *end_p;\n        int val = (int)strtol(argument, &end_p, 0);\n        *type = 0;\n        *_argument = end_p;\n        return val;\n      }\n      else\n      {\n#ifdef CONFIG_DEBYTECODE\n        char *end_p = find_non_identifier_char(argument);\n        char temp = *end_p;\n        int value;\n\n        *end_p = 0;\n        value = get_counter(mzx_world, argument, id);\n        *end_p = temp;\n\n        *type = 0;\n        *_argument = end_p;\n        return value;\n#else\n        // It's not so good..\n        *type = -1;\n        *_argument = argument;\n        return -1;\n#endif\n      }\n    }\n  }\n}\n\nstatic int evaluate_operation(int operand_a, enum op c_operator, int operand_b)\n{\n  switch(c_operator)\n  {\n    case OP_ADDITION:\n      return operand_a + operand_b;\n\n    case OP_SUBTRACTION:\n      return operand_a - operand_b;\n\n    case OP_MULTIPLICATION:\n      return operand_a * operand_b;\n\n    case OP_DIVISION:\n      return safe_divide_32(operand_a, operand_b);\n\n    case OP_MODULUS:\n    {\n      int val = safe_modulo_32(operand_a, operand_b);\n\n      // Converted C99 regulated truncated modulus to\n      // the more useful (for us) floored modulus\n      // Source:\n      // Division and Modulus for Computer Scientists\n      // DAAN LEIJEN\n      // University of Utrecht\n\n      if((val < 0) ^ (operand_b < 0))\n        return val + operand_b;\n\n      return val;\n    }\n\n    case OP_EXPONENTIATION:\n    {\n      int i;\n      int val = 1;\n\n      if(operand_a == 0)\n        return 0;\n\n      if(operand_a == 1)\n        return 1;\n\n      if(operand_b < 0)\n      {\n        if(operand_a == -1)\n          operand_b *= -1;\n        else\n          return 0;\n      }\n\n      for(i = 0; i < operand_b; i++)\n        val *= operand_a;\n\n      return val;\n    }\n\n    case OP_AND:\n      return operand_a & operand_b;\n\n    case OP_OR:\n      return operand_a | operand_b;\n\n    case OP_XOR:\n      return (operand_a ^ operand_b);\n\n    case OP_BITSHIFT_LEFT:\n      return safe_left_shift_32(operand_a, operand_b);\n\n    case OP_BITSHIFT_RIGHT:\n      return safe_logical_right_shift_32(operand_a, operand_b);\n\n    case OP_ARITHMETIC_BITSHIFT_RIGHT:\n      return safe_arithmetic_right_shift_32(operand_a, operand_b);\n\n    case OP_EQUAL:\n      return operand_a == operand_b;\n\n    case OP_LESS_THAN:\n      return operand_a < operand_b;\n\n    case OP_LESS_THAN_OR_EQUAL:\n      return operand_a <= operand_b;\n\n    case OP_GREATER_THAN:\n      return operand_a > operand_b;\n\n    case OP_GREATER_THAN_OR_EQUAL:\n      return operand_a >= operand_b;\n\n    case OP_NOT_EQUAL:\n      return operand_a != operand_b;\n\n    case OP_TERNARY:\n      return operand_b;\n\n    default:\n      return operand_a;\n  }\n}\n\nstatic int _parse_expression(struct world *mzx_world, char **_expression,\n int *error, int id, const char *operand_a, const char terminator1, const char terminator2)\n{\n  char *expression = *_expression;\n  int operand_val;\n  int current_arg;\n  int c_operator;\n  int value;\n\n  *error = 0;\n\n  if(!operand_a)\n  {\n    // Skip initial whitespace..\n    expression = expr_skip_whitespace(expression);\n    value = parse_argument(mzx_world, &expression, &current_arg, 0, id,\n     terminator1, terminator2);\n  }\n  else\n  {\n    // Use a pre-evaluated counter name as the initial operand.\n    value = get_counter(mzx_world, operand_a, id);\n    current_arg = 0;\n  }\n\n#ifndef CONFIG_DEBYTECODE\n  if((current_arg != 0) && (current_arg != 2))\n  {\n    // First argument must be a value type.\n    *error = 1;\n    value = -99;\n    goto err_out;\n  }\n#endif\n\n  expression = expr_skip_whitespace(expression);\n\n  while(1)\n  {\n    if(*expression == terminator1 || *expression == terminator2)\n      break;\n\n    c_operator = parse_argument(mzx_world, &expression, &current_arg, value, id,\n     terminator1, terminator2);\n    // Next arg must be an operator, unless it's a negative number,\n    // in which case it's considered + num\n    if(current_arg == 2)\n    {\n      value = evaluate_operation(value, OP_ADDITION, c_operator);\n    }\n    else\n    {\n#ifndef CONFIG_DEBYTECODE\n      if(current_arg != 1)\n      {\n        *error = 2;\n        value = -100;\n        goto err_out;\n      }\n#endif\n\n      expression = expr_skip_whitespace(expression);\n      operand_val = parse_argument(mzx_world, &expression, &current_arg, value, id,\n       terminator1, terminator2);\n\n#ifndef CONFIG_DEBYTECODE\n      // And now it must be an integer.\n      if((current_arg != 0) && (current_arg != 2))\n      {\n        *error = 3;\n        value = -102;\n        goto err_out;\n      }\n#endif\n\n      value = evaluate_operation(value, (enum op)c_operator, operand_val);\n    }\n    expression = expr_skip_whitespace(expression);\n  }\n\n  last_val = value;\n\n#ifndef CONFIG_DEBYTECODE\nerr_out:\n#endif\n  *_expression = expression;\n  return value;\n}\n\nint parse_expression(struct world *mzx_world, char **_expression, int *error,\n int id)\n{\n  int ret = _parse_expression(mzx_world, _expression, error, id, NULL, ')', ')');\n  // Skip trailing ).\n  (*_expression)++;\n  return ret;\n}\n\n#ifdef CONFIG_DEBYTECODE\n\nstatic void output_string(char * RESTRICT * RESTRICT dest, size_t *remaining,\n const char *src, size_t src_length)\n{\n  if(src_length > *remaining)\n    src_length = *remaining;\n\n  memcpy(*dest, src, src_length);\n  *dest += src_length;\n  *remaining -= src_length;\n}\n\nstatic void output_input_string(struct world *mzx_world,\n char * RESTRICT * RESTRICT dest, size_t *remaining)\n{\n  const char *str = mzx_world->current_board->input_string;\n  if(!str)\n    str = \"\";\n\n  output_string(dest, remaining, str, strlen(str));\n}\n\nstatic void output_number(char * RESTRICT *dest, size_t *remaining,\n int32_t value, boolean hex_byte, boolean hex_short)\n{\n  char number_buffer[16];\n  size_t len;\n\n  if(hex_byte)\n  {\n    snprintf(number_buffer, sizeof(number_buffer), \"%02\" PRIx32, value);\n    output_string(dest, remaining, number_buffer, 2);\n  }\n  else\n\n  if(hex_short)\n  {\n    char *src = tr_int_to_hex_string(number_buffer, value, &len);\n    output_string(dest, remaining, src, len);\n  }\n  else\n  {\n    len = snprintf(number_buffer, sizeof(number_buffer), \"%\" PRId32, value);\n    output_string(dest, remaining, number_buffer, len);\n  }\n}\n\nint parse_string_expression(struct world *mzx_world, char **_expression,\n int id, char *output, size_t output_left)\n{\n  char name_translated[ROBOT_MAX_TR];\n  char *expression = *_expression;\n  size_t expression_length = output_left;\n  int error;\n  int32_t value;\n  // TODO: real formatting syntax\n  boolean hex_byte = false;\n  boolean hex_short = false;\n\n  while(1)\n  {\n    expression = expr_skip_whitespace(expression);\n\n    hex_byte = hex_short = false;\n    if(*expression == '#')\n    {\n      hex_byte = true;\n      expression = expr_skip_whitespace(expression + 1);\n    }\n    else\n\n    if(*expression == '+')\n    {\n      hex_short = true;\n      expression = expr_skip_whitespace(expression + 1);\n    }\n\n    switch(*expression)\n    {\n      case '(':\n      {\n        // Numeric expression.\n        expression++;\n        value = parse_expression(mzx_world, &expression, &error, id);\n        output_number(&output, &output_left, value, hex_byte, hex_short);\n        break;\n      }\n\n      case '\"':\n      {\n        // String literal.\n        expression++;\n\n        expression = tr_msg_ext(mzx_world, expression, id, name_translated, '\"');\n        output_string(&output, &output_left, name_translated, strlen(name_translated));\n        break;\n      }\n\n      case '}':\n        expression++;\n        *_expression = expression;\n        expression_length -= output_left;\n        return expression_length;\n\n      case '`':\n      {\n        // String identifier (complex) or no-parentheses expression.\n        struct string string;\n        expression++;\n\n        expression =\n         tr_msg_ext(mzx_world, expression, id, name_translated, '`');\n\n        if(is_string(name_translated))\n        {\n          if(get_string(mzx_world, name_translated, &string, id))\n            output_string(&output, &output_left, string.value, string.length);\n        }\n        else\n        {\n          // Pass the pre-evaluated counter name through...\n          value = _parse_expression(mzx_world, &expression, &error, id,\n           name_translated, ',', '}');\n          output_number(&output, &output_left, value, hex_byte, hex_short);\n        }\n        break;\n      }\n\n      default:\n      {\n        // String identifier (simple) or no-parentheses expression.\n        char *next;\n        struct string string;\n        size_t len;\n\n        next = find_non_identifier_char(expression);\n        len = MIN(ROBOT_MAX_TR - 1, next - expression);\n\n        memcpy(name_translated, expression, len);\n        name_translated[len] = '\\0';\n\n        if(!strcasecmp(name_translated, \"INPUT\"))\n        {\n          output_input_string(mzx_world, &output, &output_left);\n          expression = next;\n        }\n        else\n\n        if(is_string(name_translated))\n        {\n          if(get_string(mzx_world, name_translated, &string, id))\n            output_string(&output, &output_left, string.value, string.length);\n          expression = next;\n        }\n        else\n        {\n          value = _parse_expression(mzx_world, &expression, &error, id,\n           0, ',', '}');\n          output_number(&output, &output_left, value, hex_byte, hex_short);\n        }\n        break;\n      }\n    }\n    // Skip comma, if present\n    expression = expr_skip_whitespace(expression);\n    if(*expression == ',')\n      expression++;\n  }\n}\n\n#endif // CONFIG_DEBYTECODE\n\n#endif // outer CONFIG_DEBYTECODE\n\n// Translates message at target to the given buffer, returning location\n// of this buffer. && becomes &, &INPUT& becomes the last input string,\n// and &COUNTER& becomes the value of COUNTER. The size of the string is\n// clipped to 512 chars.\n\n#ifdef CONFIG_DEBYTECODE\n\nchar *tr_msg_ext(struct world *mzx_world, char *mesg, int id, char *buffer,\n char terminating_char)\n{\n  char *src_ptr = mesg;\n  char current_char = *src_ptr;\n\n  int dest_pos = 0;\n  int expr_length;\n\n  while((current_char != terminating_char) && (dest_pos < ROBOT_MAX_TR - 1))\n  {\n    switch(current_char)\n    {\n      case '\\\\':\n        if(src_ptr[1] == '{' || src_ptr[1] == '}' ||\n         src_ptr[1] == '\\\\' || src_ptr[1] == terminating_char)\n        {\n          buffer[dest_pos] = src_ptr[1];\n          src_ptr += 2;\n        }\n        else\n        {\n          buffer[dest_pos] = current_char;\n          src_ptr++;\n        }\n        dest_pos++;\n        break;\n\n      case '{':\n      {\n        src_ptr++;\n        expr_length = parse_string_expression(mzx_world, &src_ptr, id,\n         buffer + dest_pos, ROBOT_MAX_TR - dest_pos - 1);\n        dest_pos += expr_length;\n        break;\n      }\n\n      default:\n        buffer[dest_pos] = current_char;\n        src_ptr++;\n        dest_pos++;\n    }\n\n    current_char = *src_ptr;\n  }\n\n  buffer[dest_pos] = 0;\n  return src_ptr + 1;\n}\n\n#else /* !CONFIG_DEBYTECODE */\n\nchar *tr_msg_ext(struct world *mzx_world, char *mesg, int id, char *buffer,\n char terminating_char)\n{\n  struct board *src_board = mzx_world->current_board;\n  char name_buffer[256];\n  char number_buffer[16];\n  char *name_ptr;\n  char current_char;\n  char *src_ptr = mesg;\n  char *old_ptr;\n\n  size_t dest_pos = 0;\n  size_t name_length;\n  int error;\n  int val;\n\n  do\n  {\n    current_char = *src_ptr;\n\n    if((current_char == '(') && (mzx_world->version >= V268))\n    {\n      src_ptr++;\n      old_ptr = src_ptr;\n\n      val = parse_expression(mzx_world, &src_ptr, &error, id);\n      if(!error)\n      {\n        sprintf(number_buffer, \"%d\", val);\n        strcpy(buffer + dest_pos, number_buffer);\n        dest_pos += strlen(number_buffer);\n      }\n      else\n      {\n        buffer[dest_pos] = '(';\n        dest_pos++;\n        src_ptr = old_ptr;\n      }\n\n      current_char = *src_ptr;\n    }\n    else\n\n    if(current_char == '&')\n    {\n      src_ptr++;\n      current_char = *src_ptr;\n\n      if(current_char == '&')\n      {\n        src_ptr++;\n        buffer[dest_pos] = '&';\n        dest_pos++;\n      }\n      else\n      {\n        // Input or Counter?\n        name_ptr = name_buffer;\n\n        while(current_char)\n        {\n          if(current_char == '(' && (mzx_world->version >= V268))\n          {\n            src_ptr++;\n            val = parse_expression(mzx_world, &src_ptr, &error, id);\n            if(!error)\n            {\n              sprintf(number_buffer, \"%d\", val);\n              strcpy(name_ptr, number_buffer);\n              name_ptr += strlen(number_buffer);\n            }\n          }\n          else\n          {\n            *name_ptr = *src_ptr;\n            name_ptr++;\n            src_ptr++;\n          }\n\n          current_char = *src_ptr;\n\n          if(current_char == '&')\n          {\n            src_ptr++;\n            current_char = *src_ptr;\n            break;\n          }\n        }\n\n        *name_ptr = 0;\n\n        if(!memcasecmp(name_buffer, \"INPUT\", 6))\n        {\n          // Input\n          const char *input_string = src_board->input_string ? src_board->input_string : \"\";\n          name_length = strlen(input_string);\n          if(dest_pos + name_length >= ROBOT_MAX_TR)\n            name_length = ROBOT_MAX_TR - dest_pos - 1;\n\n          memcpy(buffer + dest_pos, input_string, name_length);\n          dest_pos += name_length;\n        }\n        else\n\n        // Counter or string\n        if(is_string(name_buffer))\n        {\n          // Write the value of the counter name\n          struct string str_src;\n\n          if(get_string(mzx_world, name_buffer, &str_src, 0))\n          {\n            name_length = str_src.length;\n\n            if(dest_pos + name_length >= ROBOT_MAX_TR)\n              name_length = ROBOT_MAX_TR - dest_pos - 1;\n\n            memcpy(buffer + dest_pos, str_src.value, name_length);\n            dest_pos += name_length;\n          }\n        }\n        else\n\n        // #(counter) is a hex representation.\n        if(name_buffer[0] == '+')\n        {\n          char *src = tr_int_to_hex_string(number_buffer,\n           get_counter(mzx_world, name_buffer + 1, id), &name_length);\n\n          if(dest_pos + name_length >= ROBOT_MAX_TR)\n            name_length = ROBOT_MAX_TR - dest_pos - 1;\n\n          memcpy(buffer + dest_pos, src, name_length);\n          dest_pos += name_length;\n        }\n        else\n\n        if(name_buffer[0] == '#')\n        {\n          name_length = 2;\n          if(dest_pos + name_length >= ROBOT_MAX_TR)\n            name_length = ROBOT_MAX_TR - dest_pos - 1;\n\n          sprintf(number_buffer, \"%02x\",\n           get_counter(mzx_world, name_buffer + 1, id));\n\n          memcpy(buffer + dest_pos, number_buffer, name_length);\n          dest_pos += name_length;\n        }\n        else\n        {\n          char *src = tr_int_to_string(number_buffer,\n           get_counter(mzx_world, name_buffer, id), &name_length);\n\n          if(dest_pos + name_length >= ROBOT_MAX_TR)\n            name_length = ROBOT_MAX_TR - dest_pos - 1;\n\n          memcpy(buffer + dest_pos, src, name_length);\n          dest_pos += name_length;\n        }\n      }\n    }\n    else\n    {\n      buffer[dest_pos] = current_char;\n      src_ptr++;\n      dest_pos++;\n    }\n  } while(current_char && (dest_pos < ROBOT_MAX_TR - 1));\n\n  buffer[dest_pos] = 0;\n  return buffer;\n}\n\n#endif /* !CONFIG_DEBYTECODE */\n"
  },
  {
    "path": "src/expr.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EXPR_H\n#define __EXPR_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <limits.h>\n#include <stddef.h>\n#include \"world_struct.h\"\n\nint parse_expression(struct world *mzx_world, char **expression, int *error,\n int id);\n\n#ifdef CONFIG_DEBYTECODE\nint parse_string_expression(struct world *mzx_world, char **_expression,\n int id, char *output, size_t output_left);\n#endif\n\nCORE_LIBSPEC char *tr_msg_ext(struct world *mzx_world, char *mesg, int id,\n char *buffer, char terminating_char);\n\nstatic inline char *tr_msg(struct world *mzx_world, char *mesg, int id,\n char *buffer)\n{\n  return tr_msg_ext(mzx_world, mesg, id, buffer, 0);\n}\n\n/* Guard against division by 0 and dividing INT_MIN by -1. */\nstatic inline int safe_divide_32(int a, int b)\n{\n  if(b == 0 || (a == INT_MIN && b == -1))\n    return 0;\n\n  return a / b;\n}\n\n/* Guard against division by 0 and dividing INT_MIN by -1. */\nstatic inline int safe_modulo_32(int a, int b)\n{\n  if(b == 0 || (a == INT_MIN && b == -1))\n    return 0;\n\n  return a % b;\n}\n\n/* Guard against shift by values under 0 and over 31. */\nstatic inline int safe_left_shift_32(int a, int b)\n{\n  return ((unsigned int)b < 32) ? a << b : 0;\n}\n\n/* Guard against shift by values under 0 and over 31. */\nstatic inline int safe_logical_right_shift_32(int a, int b)\n{\n  return ((unsigned int)b < 32) ? (unsigned int)a >> b : 0;\n}\n\n/* Guard against shift by values under 0 and over 31. */\nstatic inline int safe_arithmetic_right_shift_32(int a, int b)\n{\n  // Preserve sign for invalid exponents.\n  return ((unsigned int)b < 32) ? (signed int)a >> b : (signed int)a >> 31;\n}\n\nstatic inline char *tr_int_to_string(char dest[12], int value, size_t *len)\n{\n  char *pos = dest + 11;\n\n  *(pos--) = '\\0';\n\n  if(value >= 0)\n  {\n    while(value >= 10)\n    {\n      *(pos--) = '0' + value % 10;\n      value /= 10;\n    }\n    *pos = '0' + value;\n  }\n  else\n  {\n    while(value <= -10)\n    {\n      *(pos--) = '0' - value % 10;\n      value /= 10;\n    }\n    *(pos--) = '0' - value;\n    *pos = '-';\n  }\n\n  *len = dest + 11 - pos;\n  return pos;\n}\n\nstatic inline char *tr_int_to_hex_string(char dest[9], int value, size_t *len)\n{\n  static const char hex[] = \"0123456789abcdef\";\n  char *pos = dest + 8;\n\n  *(pos--) = '\\0';\n\n  if(value >= 0)\n  {\n    while(value >= 0x10)\n    {\n      *(pos--) = hex[value & 0x0F];\n      value >>= 4;\n    }\n    *pos = hex[value & 0x0F];\n  }\n  else\n  {\n    while(dest < pos)\n    {\n      *(pos--) = hex[value & 0x0F];\n      value >>= 4;\n    }\n    *pos = hex[value & 0x0F];\n  }\n\n  *len = dest + 8 - pos;\n  return pos;\n}\n\n__M_END_DECLS\n\n#endif // __EXPR_H\n"
  },
  {
    "path": "src/extmem.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2021-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"error.h\"\n#include \"extmem.h\"\n#include \"platform_endian.h\"\n#include \"robot.h\"\n#include \"util.h\"\n#include \"world_struct.h\"\n\n#include <limits.h>\n#include <stdint.h>\n#include <zlib.h>\n\n#ifdef CONFIG_NDS\n#include \"../arch/nds/extmem.h\"\n#define USE_PLATFORM_EXTRAM_ALLOC\n#define EXTRAM_BUFFER_SIZE\n#define EXTRAM_BUFFER_DECL\n// Use a static buffer in fast RAM instead of the default stack buffer.\n// The benefits of this are questionable but at least it isn't on the stack.\nDTCM_BSS static uint32_t extram_deflate_buffer[4096 / sizeof(uint32_t)];\n#endif\n\n/**\n * NOTE: platform allocation and platform store/retrieve are mutually exclusive.\n * Only implement at most one variant for a particular platform and use the\n * dummy implementations for the other.\n */\n#if defined(USE_PLATFORM_EXTRAM_ALLOC) && defined(USE_PLATFORM_EXTRAM_STORE)\n#error Only implement one of platform_extram_alloc and platform_extram_store!\n#endif\n\n#ifndef USE_PLATFORM_EXTRAM_ALLOC\n/* Acquire permissions to read from/write to platform extra RAM. */\nstatic void platform_extram_lock(void) {}\n/* Release permissions to read from/write to platform extra RAM. */\nstatic void platform_extram_unlock(void) {}\n/* Attempt to allocate a memory mapped buffer in extra RAM. */\nstatic uint32_t *platform_extram_alloc(size_t len) { return NULL; }\n/* Attempt to reallocate a memory mapped buffer in extra RAM. */\nstatic uint32_t *platform_extram_resize(void *buffer, size_t len) { return NULL; }\n/* Free a memory mapped buffer in extra RAM. */\nstatic void platform_extram_free(void *buffer) {}\n#endif\n\n#ifndef USE_PLATFORM_EXTRAM_STORE\n/* Send a locally allocated buffer to non-mapped extra RAM. */\nstatic void *platform_extram_store(void *buffer, size_t len) { return buffer; }\n/* Retrieve a buffer from non-mapped extra RAM. */\nstatic void *platform_extram_retrieve(void *buffer, size_t len) { return buffer; }\n#endif\n\n#define EXTRAM_ID ((uint32_t)(('E') | ('X' << 8) | ('T' << 16) | (0xfe << 24)))\n\n#ifndef EXTRAM_COMPRESS_BOARDS_THRESHOLD\n/* Minimum size (in bytes) for board planes to be deflated. Blocks less than\n * this don't save much RAM in the long run and mostly slow down loading. */\n#define EXTRAM_COMPRESS_BOARDS_THRESHOLD 1024\n#endif\n\n#ifndef EXTRAM_COMPRESS_ROBOTS_THRESHOLD\n/* Minimum size (in bytes) for robots to be deflated.\n * Robots don't compress as well as boards and aren't worth as much effort. */\n#define EXTRAM_COMPRESS_ROBOTS_THRESHOLD 16384\n#endif\n\n#ifndef EXTRAM_BUFFER_SIZE\n/* Size (in uint32_t) of extra memory deflate buffer. */\n#define EXTRAM_BUFFER_SIZE (4096 / sizeof(uint32_t))\n#endif\n\n#ifndef EXTRAM_BUFFER_DECL\n#define EXTRAM_BUFFER_DECL uint32_t extram_deflate_buffer[EXTRAM_BUFFER_SIZE]\n#endif\n\nenum extram_flags\n{\n  EXTRAM_PLATFORM_ALLOC   = (1 << 0), /* Buffer created by platform_extram. */\n  EXTRAM_DEFLATE          = (1 << 1), /* Buffer uses DEFLATE compression. */\n  EXTRAM_PAGED            = (1 << 2), /* Buffer was paged to disk. (TODO?) */\n  EXTRAM_RLE3             = (1 << 3), /* Buffer uses MZX RLE3 compression. */\n};\n\nstruct extram_block\n{\n  uint32_t id;\n  uint32_t flags;\n  uint32_t uncompressed_size;\n  uint32_t compressed_size;\n  uint32_t checksum;\n  uint32_t data[];\n};\n\nstruct extram_data\n{\n  z_stream z;\n  size_t compression_threshold;\n  int status;\n  boolean initialized;\n  boolean free_data;\n};\n\n#ifdef EXTRAM_STATS\nstruct method_stats\n{\n  size_t total;\n  size_t total_compressed;\n  size_t total_uncompressed;\n  double best_ratio;\n  double worst_ratio;\n};\n\nstatic struct method_stats deflate_stats;\nstatic struct method_stats rle3_stats;\n\nstatic void tick_method_stats(struct method_stats *stats, size_t compressed,\n size_t uncompressed)\n{\n  double ratio = (double)compressed / (double)uncompressed;\n\n  if(!stats->total)\n    stats->best_ratio = INFINITY;\n  stats->total++;\n  stats->total_compressed += compressed;\n  stats->total_uncompressed += uncompressed;\n  if(ratio < stats->best_ratio)\n    stats->best_ratio = ratio;\n  if(ratio > stats->worst_ratio)\n    stats->worst_ratio = ratio;\n}\n\nstatic void tick_stats(struct extram_block *block)\n{\n  if(block->flags & EXTRAM_RLE3)\n  {\n    tick_method_stats(&rle3_stats, block->compressed_size, block->uncompressed_size);\n  }\n  else\n\n  if(block->flags & EXTRAM_DEFLATE)\n    tick_method_stats(&deflate_stats, block->compressed_size, block->uncompressed_size);\n}\n\nstatic void print_method_stats(struct method_stats *stats)\n{\n  double avg = (double)stats->total_compressed / (double)stats->total_uncompressed;\n  trace(\"--EXTRAM--     TOTAL:%zu  IN:%zu  OUT:%zu  AVG:%.5f  BEST:%.5f  WORST:%.5f\\n\",\n   stats->total, stats->total_uncompressed, stats->total_compressed, avg,\n   stats->best_ratio, stats->worst_ratio);\n}\n\nstatic void print_stats(void)\n{\n  if(rle3_stats.total)\n  {\n    trace(\"--EXTRAM--   RLE3 stats:\\n\");\n    print_method_stats(&rle3_stats);\n  }\n  if(deflate_stats.total)\n  {\n    trace(\"--EXTRAM--   DEFLATE stats:\\n\");\n    print_method_stats(&deflate_stats);\n  }\n}\n#endif\n\n#if 0\nstatic size_t RLE2_pack(uint8_t * RESTRICT data, size_t data_len,\n const uint8_t *src, size_t src_len)\n{\n  size_t i, j, runsize;\n  uint8_t current_char;\n\n  for(i = 0, j = 0; i < src_len; i++)\n  {\n    current_char = src[i];\n    runsize = 1;\n\n    while((i < (src_len - 1)) && (src[i + 1] == current_char) &&\n     (runsize < 127))\n    {\n      i++;\n      runsize++;\n    }\n\n    if((runsize > 1) || current_char & 0x80)\n    {\n      if(j + 1 >= data_len)\n        return 0;\n\n      data[j++] = runsize | 0x80;\n      data[j++] = current_char;\n    }\n    else\n    {\n      if(j >= data_len)\n        return 0;\n\n      data[j++] = current_char;\n    }\n  }\n  return j;\n}\n\nstatic boolean RLE2_unpack(uint8_t * RESTRICT dest, size_t dest_len,\n const uint8_t *data, size_t data_len)\n{\n  size_t i, j, runsize;\n  uint8_t current_char;\n\n  for(i = 0, j = 0; j < data_len; j++)\n  {\n    current_char = data[j];\n\n    if(!(current_char & 0x80))\n    {\n      if(i >= dest_len)\n        return false;\n\n      dest[i++] = current_char;\n    }\n    else\n    {\n      j++;\n      runsize = current_char & 0x7F;\n      if(j >= data_len || (i + runsize) > dest_len)\n        return false;\n\n      current_char = data[j];\n\n      memset(dest + i, current_char, runsize);\n      i += runsize;\n    }\n  }\n  return true;\n}\n#endif\n\n#define RLE3_MAX_CHAR   (0x3F)\n#define RLE3_MAX_BLOCK  (0x2000)\n\n#define RLE3_PACK_OVERHEAD(sz) (((sz) - 1 >= 0x20) ? 2 : 1)\n\n#define RLE3_PACK_SIZE(code, sz) do { \\\n  size_t tmp = (sz) - 1; \\\n  if(j >= data_len || !(sz)) \\\n    return 0; \\\n  if(tmp >= 0x20) \\\n  { \\\n    if(j + 1 >= data_len) \\\n      return 0; \\\n    data[j++] = code | 0x20 | (tmp >> 8); \\\n    data[j++] = tmp & 0xFF; \\\n  } \\\n  else \\\n    data[j++] = code | tmp; \\\n} while(0)\n\n#define RLE3_PACK_CHAR(ch)      do{ data[j++] = (ch) & RLE3_MAX_CHAR; }while(0)\n#define RLE3_PACK_BLOCK(sz)     RLE3_PACK_SIZE(0x40, sz)\n#define RLE3_PACK_RUN_CHAR(sz)  RLE3_PACK_SIZE(0x80, sz)\n#define RLE3_PACK_RUN_BLOCK(sz) RLE3_PACK_SIZE(0xC0, sz)\n\n#define RLE3_UNPACK_SIZE(ch) \\\n (((ch) & 0x20) && j < data_len ? ((((ch) & 0x1F) << 8) | data[j++]) + 1 : (ch & 0x1F) + 1)\n\n/**\n * Bound the maximum RLE3 stream size for an input of a given length. This will\n * always be slightly larger than the given input (regardless of how well the\n * input compresses).\n *\n * @param  src_len  length of the source data to be compressed.\n * @return          upper bound size of the corresponding RLE3 stream.\n */\nstatic inline size_t RLE3_bound(size_t src_len)\n{\n  return src_len + ((src_len + 8191u) & ~8192u) / 4096u;\n}\n\n/**\n * Pack a buffer with RLE3 compression, a run length encoding scheme designed\n * to address some of the major flaws of Alexis' RLE2 without losing much speed.\n * This is much faster than zlib DEFLATE and compresses board data fairly well.\n *\n * @param   data      buffer to output RLE3 stream to.\n * @param   data_len  size of RLE3 buffer.\n * @param   src       uncompressed source data.\n * @param   src_len   length of uncompressed source.\n * @return            final RLE3 stream length, or 0 on failure.\n */\nstatic size_t RLE3_pack(uint8_t * RESTRICT data, size_t data_len,\n const uint8_t *src, size_t src_len)\n{\n  ssize_t last_index[256];\n  uint8_t prev_chr = 0;\n  size_t i = 0;\n  size_t j = 0;\n\n  memset(last_index, 0xFF, sizeof(last_index));\n\n  while(i < src_len)\n  {\n    const uint8_t *block = src + i;\n    ssize_t block_start = i;\n    size_t block_len = 0;\n    size_t block_repeats = 0;\n    size_t block_repeat_len = 0;\n    size_t prev_repeats = 0;\n    boolean block_split = false;\n\n    while(i < src_len && block_len < RLE3_MAX_BLOCK)\n    {\n      if(src[i] == prev_chr)\n      {\n        // Is this a char run big enough to justify ending a block?\n        // A char run needs to be 3 or longer to break even in the worst case\n        // where the following block requires a 2 byte length (uncommon).\n        if(i + 2 < src_len &&\n         src[i + 1] == prev_chr &&\n         src[i + 2] == prev_chr)\n        {\n          prev_repeats = 3;\n          i += 3;\n          while(i < src_len && src[i] == prev_chr)\n            i++, prev_repeats++;\n\n          break;\n        }\n      }\n\n      // Is this the start of a block repeat?\n      if(last_index[src[i]] >= block_start && (size_t)last_index[src[i]] + 1 < i)\n      {\n        ssize_t block_break = last_index[src[i]];\n        size_t block_break_len = block_break - block_start;\n        const uint8_t *prev_pos = src + block_break;\n        const uint8_t *pos = src + i;\n        size_t k = i;\n        size_t x;\n        size_t savings;\n        size_t overhead;\n\n        block_repeat_len = i - block_break;\n\n        while(k < src_len)\n        {\n          for(x = 0; k + x < src_len && x < block_repeat_len; x++)\n            if(pos[x] != prev_pos[x])\n              break;\n\n          if(x < block_repeat_len)\n            break;\n\n          block_repeats++;\n          pos += block_repeat_len;\n          k += block_repeat_len;\n        }\n\n        savings = block_repeat_len * block_repeats;\n        /* Max overhead of following block + size of repeat code + overhead of\n         * splitting the current block (if applicable). */\n        overhead = 2 + RLE3_PACK_OVERHEAD(block_repeats) +\n         ((block_break_len) ? RLE3_PACK_OVERHEAD(block_break_len) : 0);\n\n        // Is this worth ending the block?\n        if(savings >= overhead)\n        {\n          if(block_break > block_start)\n          {\n            block_split = true;\n            block_len = block_break_len;\n          }\n          i += block_repeat_len * block_repeats;\n          break;\n        }\n        block_repeats = 0;\n      }\n\n      if(last_index[src[i]] < block_start)\n        last_index[src[i]] = i;\n\n      // Continue the block...\n      prev_chr = src[i++];\n      block_len++;\n    }\n\n    // Emit block(s) (never allow an individual block to go over RLE3_MAX_BLOCK).\n    while(block_len)\n    {\n      size_t pos_start = 0;\n      size_t pos_end = block_len;\n      boolean trim = false;\n\n      if(!block_repeats || block_split)\n      {\n        // If this block isn't required to be encoded as a single block it\n        // may be possible to replace part (or all) of it with literal chars,\n        // reducing the block length encoding overhead.\n        size_t old_overhead = RLE3_PACK_OVERHEAD(block_len);\n        size_t new_overhead;\n\n        while(pos_start < block_len && block[pos_start] <= RLE3_MAX_CHAR)\n          pos_start++;\n\n        if(pos_start < block_len)\n          while(pos_end > pos_start && block[pos_end - 1] <= RLE3_MAX_CHAR)\n            pos_end--;\n\n        new_overhead = pos_start < block_len ? RLE3_PACK_OVERHEAD(pos_end - pos_start) : 0;\n        if(new_overhead < old_overhead)\n          trim = true;\n      }\n\n      if(trim)\n      {\n        // Emit the head and tail of the block as literal chars.\n        size_t k = 0;\n        if(j + block_len > data_len)\n          return 0;\n\n        while(block[k] <= RLE3_MAX_CHAR && k < block_len)\n        {\n          RLE3_PACK_CHAR(block[k]);\n          k++;\n        }\n        if(pos_start < pos_end)\n        {\n          size_t middle_len = pos_end - pos_start;\n          RLE3_PACK_BLOCK(middle_len);\n          if(j + middle_len > data_len)\n            return 0;\n\n          memcpy(data + j, block + k, middle_len);\n          j += middle_len;\n          k += middle_len;\n        }\n\n        if(j + block_len > data_len)\n          return 0;\n\n        while(k < block_len)\n        {\n          RLE3_PACK_CHAR(block[k]);\n          k++;\n        }\n        //trace(\"--RLE3--     %s %zu\\n\", (pos_start < pos_end) ? \"mixed\" : \"chars\", block_len);\n      }\n      else\n      {\n        RLE3_PACK_BLOCK(block_len);\n        if(j + block_len > data_len)\n          return 0;\n\n        memcpy(data + j, block, block_len);\n        j += block_len;\n        //trace(\"--RLE3--     block %zu\\n\", block_len);\n      }\n\n      if(block_split)\n      {\n        block += block_len;\n        block_len = block_repeat_len;\n        block_split = false;\n      }\n      else\n        break;\n    }\n\n    // Emit block run.\n    while(block_repeats)\n    {\n      size_t pack_count = MIN(RLE3_MAX_BLOCK, block_repeats);\n      block_repeats -= pack_count;\n      RLE3_PACK_RUN_BLOCK(pack_count);\n      //trace(\"--RLE3--     repeat block x %zu\\n\", pack_count);\n    }\n\n    // Emit char run.\n    while(prev_repeats)\n    {\n      size_t pack_count = MIN(RLE3_MAX_BLOCK, prev_repeats);\n      prev_repeats -= pack_count;\n      RLE3_PACK_RUN_CHAR(pack_count);\n      //trace(\"--RLE3--     repeat %02Xh x %zu\\n\", (int)prev_chr, pack_count);\n    }\n  }\n  return j;\n}\n\n/**\n * Unpack an RLE3 stream.\n *\n * @param   dest      destination buffer for the unpacked stream.\n * @param   dest_len  size of destination buffer.\n * @param   data      source RLE3 stream to unpack.\n * @param   data_len  length of source RLE3 stream.\n * @return            `true` on success, otherwise `false`. This function will\n *                    fail if the unpacked stream size doesn't match `dest_len`.\n */\nstatic boolean RLE3_unpack(uint8_t * RESTRICT dest, size_t dest_len,\n const uint8_t *data, size_t data_len)\n{\n  const uint8_t *prev_block = NULL;\n  size_t prev_len = 0;\n  uint8_t prev_chr = 0;\n  size_t count;\n  size_t i = 0;\n  size_t j = 0;\n  uint8_t chr;\n\n  while(j < data_len)\n  {\n    if(i >= dest_len)\n      break;\n\n    chr = data[j++];\n    if(chr >> 6)\n      count = RLE3_UNPACK_SIZE(chr);\n\n    switch(chr >> 6)\n    {\n      case 0:\n      {\n        // Char. Usually there are several of these in a row.\n        dest[i++] = chr;\n        while(i < dest_len && j < data_len && data[j] <= RLE3_MAX_CHAR)\n          dest[i++] = data[j++];\n\n        prev_chr = data[j - 1];\n        prev_block = data + j - 1;\n        prev_len = 1;\n        break;\n      }\n\n      case 1:\n      {\n        // Block.\n        if(i + count > dest_len || j + count > data_len)\n          return false;\n\n        prev_block = data + j;\n        prev_len = count;\n\n        memcpy(dest + i, data + j, count);\n        i += count;\n        j += count;\n\n        prev_chr = data[j - 1];\n        break;\n      }\n\n      case 2:\n      {\n        // Repeat last char.\n        if(i + count > dest_len)\n          return false;\n\n        memset(dest + i, prev_chr, count);\n        i += count;\n        break;\n      }\n\n      case 3:\n      {\n        // Repeat last block.\n        if(!prev_block || i + count * prev_len > dest_len)\n          return false;\n\n        while(count--)\n        {\n          memcpy(dest + i, prev_block, prev_len);\n          i += prev_len;\n        }\n        break;\n      }\n    }\n  }\n  return (i == dest_len);\n}\n\n/**\n * Initialize a deflate stream.\n */\nstatic boolean extram_deflate_init(struct extram_data *data)\n{\n  int window_bits = -MAX_WBITS;\n  int mem_level = 8;\n\n  if(data->status && data->status != Z_OK)\n    return false;\n\n  if(data->initialized)\n  {\n    data->status = deflateReset(&data->z);\n    return (data->status == Z_OK);\n  }\n\n  while(mem_level > 4 || window_bits < -8)\n  {\n    /* Z_DEFAULT_COMPRESSION doesn't compress board data much better than\n     * Z_BEST_SPEED and it takes over twice as long. */\n    int res = deflateInit2(&data->z, Z_BEST_SPEED, Z_DEFLATED,\n     window_bits, mem_level, Z_DEFAULT_STRATEGY);\n\n    data->status = res;\n    if(res == Z_OK)\n    {\n      data->initialized = true;\n      return true;\n    }\n\n    /* Try progressively worse settings to see if one will stick... */\n    /* This works out to (1 << (mem_level + 9)) = 8192 minimum. */\n    if(mem_level > 4)\n      mem_level--;\n    /* This works out to (1 << (window_bits + 2)) = 2048 minimum. */\n    if(window_bits < -8)\n      window_bits++;\n  }\n  return false;\n}\n\n/**\n * Destroy a deflate stream.\n */\nstatic void extram_deflate_destroy(struct extram_data *data)\n{\n  if(data->initialized)\n    deflateEnd(&data->z);\n}\n\n/**\n * Initialize an inflate stream.\n */\nstatic boolean extram_inflate_init(struct extram_data *data)\n{\n  if(data->status && data->status != Z_OK)\n    return false;\n\n  if(data->initialized)\n  {\n    data->status = inflateReset(&data->z);\n    return (data->status == Z_OK);\n  }\n\n  /* This needs to be -MAX_WBITS (32k buffer). */\n  data->status = inflateInit2(&data->z, -MAX_WBITS);\n  if(data->status == Z_OK)\n  {\n    data->initialized = true;\n    return true;\n  }\n  return false;\n}\n\n/**\n * Destroy an inflate stream.\n */\nstatic void extram_inflate_destroy(struct extram_data *data)\n{\n  if(data->initialized)\n    inflateEnd(&data->z);\n}\n\n/**\n * Calculate a checksum for a block of memory.\n */\nstatic uint32_t extram_checksum(const void *src, size_t len)\n{\n  uint32_t checksum = adler32(0L, NULL, 0);\n  while(len > 0)\n  {\n    unsigned int blocklen = MIN(len, UINT_MAX);\n    checksum = adler32(checksum, src, blocklen);\n    len -= blocklen;\n  }\n  return checksum;\n}\n\n#if defined(__GNUC__) && defined(__arm__)\n\n/* Fast 32-bit aligned ARM copy. */\nstatic void extram_copy32(uint32_t * RESTRICT d32, const uint32_t *s32, size_t len32)\n{\n  /* Code for handling the tail was inspired by musl.\n   * a) bit 2 is shifted off (carry flag means >=4 words);\n   * b) bit 1 is the sign bit (sign flag means >=2 words). */\n  asm(\n    \"MOVS    r12, %2, lsr #3  \\n\\t\"\n    \"BEQ     .Ltail           \\n\\t\"\n    \".Lbody:                  \\n\\t\"\n    \"LDMIA   %1!, {r3-r10}    \\n\\t\"\n    \"STMIA   %0!, {r3-r10}    \\n\\t\"\n    \"SUBS    r12, r12, #1     \\n\\t\"\n    \"BNE     .Lbody           \\n\\t\"\n    \".Ltail:                  \\n\\t\"\n    \"MOVS    r12, %2, lsl #30 \\n\\t\"\n    \"LDMCSIA %1!, {r3-r6}     \\n\\t\"\n    \"LDMMIIA %1!, {r7-r8}     \\n\\t\"\n    \"STMCSIA %0!, {r3-r6}     \\n\\t\"\n    \"STMMIIA %0!, {r7-r8}     \\n\\t\"\n    \"TST     %2, #1           \\n\\t\"\n    \"LDMNEIA %1!, {r3}        \\n\\t\"\n    \"STMNEIA %0!, {r3}        \\n\\t\"\n    : \"+r\" (d32), \"+r\" (s32), \"+r\" (len32), \"=m\" (*d32)\n    : \"m\" (*s32)\n    : \"r3\", \"r4\", \"r5\", \"r6\", \"r7\", \"r8\", \"r9\", \"r10\", \"r12\", \"cc\"\n  );\n}\n\n#else\n\n#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)\n/* Prevent GCC from replacing the copy loop with memcpy. */\nstatic void extram_copy32(uint32_t * RESTRICT, const uint32_t *, size_t)\n__attribute__((optimize(\"-fno-tree-loop-distribute-patterns\")));\n#endif\n\nstatic void extram_copy32(uint32_t * RESTRICT d32, const uint32_t *s32, size_t len32)\n{\n  size_t i;\n  for(i = 0; i < len32; i++)\n    d32[i] = s32[i];\n}\n\n#endif /* extram_copy32 */\n\n/**\n * Perform a 32-bit aligned copy. Both the source and the dest buffers must be\n * 32-bit aligned. The destination buffer additionally should be large enough\n * to fit `len` rounded up to a multiple of 4 bytes (use `extram_alloc_size`).\n */\nstatic boolean extram_copy(void * RESTRICT dest, const void *src, size_t len)\n{\n  const uint32_t *s32 = (const uint32_t *)src;\n  uint32_t *d32 = (uint32_t *)dest;\n  size_t len32;\n\n  if(((size_t)src & 3) || ((size_t)dest & 3))\n  {\n    debug(\"--EXTRAM-- bad copy parameters: %p %p %zu!\\n\", dest, src, len);\n    return false;\n  }\n\n  len32 = len >> 2;\n  extram_copy32(d32, s32, len32);\n\n  if(len & 3)\n  {\n    const uint8_t *src8 = (const uint8_t *)src;\n    uint32_t buffer = 0;\n    uint32_t pos;\n    size_t i = len & ~3;\n\n#if PLATFORM_BYTE_ORDER == PLATFORM_LIL_ENDIAN\n    for(pos = 0; i < len; i++, pos += 8)\n      buffer |= src8[i] << pos;\n#else\n    for(pos = 24; i < len; i++, pos -= 8)\n      buffer |= src8[i] << pos;\n#endif\n    d32[len32] = buffer;\n  }\n  return true;\n}\n\n/**\n * Get the rounded allocation size for a buffer.\n */\nstatic size_t extram_alloc_size(size_t len)\n{\n  return (len + 3) & ~3;\n}\n\n/**\n * Get the extra memory block size for an input buffer.\n */\nstatic size_t extram_block_size(size_t len)\n{\n  return sizeof(struct extram_block) + extram_alloc_size(len);\n}\n\n/**\n * Send a buffer to extra memory.\n *\n * @param  data structure including z_stream information.\n * @param  _src pointer to buffer to store to extra RAM.\n * @param  len  size of buffer to store to extra RAM.\n * @return      `true` on success, otherwise `false`.\n */\nstatic boolean store_buffer_to_extram(struct extram_data *data,\n char **_src, size_t len)\n{\n  struct extram_block *block;\n  uint8_t *src = (uint8_t *)*_src;\n  void *ptr;\n  uint32_t flags = EXTRAM_PLATFORM_ALLOC;\n  size_t projected_size = len;\n  size_t alloc_size;\n  EXTRAM_BUFFER_DECL;\n\n  trace(\"--EXTRAM-- store_buffer_to_extram %p %zu\\n\", src, len);\n\n  if(len >= data->compression_threshold)\n  {\n    // Before trying zlib (slow), how well does this RLE?\n    size_t rle3_size = RLE3_pack((void *)extram_deflate_buffer, sizeof(extram_deflate_buffer), src, len);\n    if(rle3_size > 0)\n    {\n      projected_size = rle3_size;\n      flags |= EXTRAM_RLE3;\n    }\n    else\n    {\n      // Project compressed size...\n      data->z.next_in = (Bytef *)src;\n      data->z.avail_in = len;\n\n      if(extram_deflate_init(data))\n      {\n        projected_size = deflateBound(&data->z, len);\n        flags |= EXTRAM_DEFLATE;\n      }\n    }\n  }\n\n  platform_extram_lock();\n\n  alloc_size = extram_block_size(projected_size);\n  block = (struct extram_block *)platform_extram_alloc(alloc_size);\n  if(!block)\n  {\n    flags &= ~EXTRAM_PLATFORM_ALLOC;\n    block = cmalloc(alloc_size);\n  }\n\n  block->id = EXTRAM_ID;\n  block->flags = flags;\n  block->uncompressed_size = len;\n  block->checksum = extram_checksum(src, len);\n\n  if(flags & EXTRAM_DEFLATE)\n  {\n    // Compress.\n    uint32_t *pos = block->data;\n    size_t sz;\n    size_t new_alloc_size;\n    int res = Z_OK;\n\n    if(~flags & EXTRAM_PLATFORM_ALLOC)\n    {\n      /* DEFLATE directly into the extram block. */\n      data->z.next_out = (Bytef *)block->data;\n      data->z.avail_out = extram_alloc_size(projected_size);\n\n      res = deflate(&data->z, Z_FINISH);\n    }\n    else\n\n    while(res != Z_STREAM_END)\n    {\n      data->z.next_out = (Bytef *)extram_deflate_buffer;\n      data->z.avail_out = sizeof(extram_deflate_buffer);\n\n      res = deflate(&data->z, Z_FINISH);\n\n      if(res != Z_OK && res != Z_BUF_ERROR && res != Z_STREAM_END)\n        break;\n\n      sz = sizeof(extram_deflate_buffer) - data->z.avail_out;\n      if(!extram_copy(pos, extram_deflate_buffer, sz))\n        goto err;\n\n      pos += sz >> 2;\n    }\n\n    if(res != Z_STREAM_END)\n    {\n      debug(\"--EXTRAM-- deflate failed with code %d\\n\", res);\n      goto err;\n    }\n    trace(\"--EXTRAM--   DEFLATE size=%zu\\n\", (size_t)data->z.total_out);\n\n    block->compressed_size = data->z.total_out;\n\n    /* Shrink the allocation to the real compressed size. */\n    new_alloc_size = extram_block_size(block->compressed_size);\n    if(new_alloc_size < alloc_size)\n    {\n      alloc_size = new_alloc_size;\n      if(flags & EXTRAM_PLATFORM_ALLOC)\n      {\n        ptr = platform_extram_resize(block, new_alloc_size);\n      }\n      else\n        ptr = crealloc(block, new_alloc_size);\n\n      if(!ptr)\n        goto err;\n      block = (struct extram_block *)ptr;\n    }\n  }\n  else\n\n  if(flags & EXTRAM_RLE3)\n  {\n    // RLE3.\n    block->compressed_size = projected_size;\n\n    if(!extram_copy(block->data, extram_deflate_buffer, projected_size))\n      goto err;\n\n    trace(\"--EXTRAM--   RLE3 size=%zu\\n\", projected_size);\n  }\n  else\n  {\n    // Store.\n    block->compressed_size = len;\n\n    if(!extram_copy(block->data, src, len))\n      goto err;\n  }\n\n#ifdef EXTRAM_STATS\n  tick_stats(block);\n#endif\n\n  platform_extram_unlock();\n\n  ptr = platform_extram_store(block, alloc_size);\n  if(!ptr)\n  {\n    debug(\"--EXTRAM-- failed to send buffer to non-mapped platform RAM.\\n\");\n    goto err;\n  }\n\n  free(src);\n  *_src = ptr;\n  return true;\n\nerr:\n  if(flags & EXTRAM_PLATFORM_ALLOC)\n  {\n    platform_extram_free(block);\n  }\n  else\n    free(block);\n\n  platform_extram_unlock();\n  return false;\n}\n\n/**\n * Retrieve a buffer from extra memory.\n *\n * @param  data structure including z_stream information.\n * @param  src  pointer to buffer to retrieve from extra RAM.\n * @param  len  length of buffer to retreieve from extra RAM.\n * @return      `true` on success, otherwise `false`.\n */\nstatic boolean retrieve_buffer_from_extram(struct extram_data *data,\n char **src, size_t len)\n{\n  struct extram_block *block = (struct extram_block *)(*src);\n  void *ptr;\n  uint32_t checksum;\n  size_t alloc_size;\n  uint8_t *buffer = NULL;\n  EXTRAM_BUFFER_DECL;\n\n  trace(\"--EXTRAM-- retrieve_buffer_from_extram %p %zu\\n\", *src, len);\n\n  ptr = platform_extram_retrieve(block, len);\n  if(!ptr)\n  {\n    debug(\"--EXTRAM-- failed to retrieve buffer %p from non-mapped platform RAM.\\n\",\n     (void *)*src);\n    goto err;\n  }\n  block = ptr;\n\n  platform_extram_lock();\n\n  /* Try to eliminate false positives arriving here... */\n  if(block->id != EXTRAM_ID)\n  {\n    debug(\"--EXTRAM-- ID mismatch @ %p (expected %08zx, got %08zx)\\n\",\n     (void *)block, (size_t)EXTRAM_ID, (size_t)block->id);\n    goto err;\n  }\n\n  if(len != block->uncompressed_size)\n  {\n    debug(\"--EXTRAM-- size mismatch @ %p (expected %zu, got %zu)\\n\",\n      (void *)block, len, (size_t)block->uncompressed_size\n    );\n    goto err;\n  }\n\n  /* Destroy the block and clear *src. */\n  if(data->free_data)\n    goto clear;\n\n  alloc_size = extram_alloc_size(len);\n  buffer = cmalloc(alloc_size);\n\n  if(block->flags & EXTRAM_DEFLATE)\n  {\n    // Compressed.\n    uint32_t *pos = block->data;\n    size_t left = block->compressed_size;\n    int res = Z_OK;\n\n    data->z.next_in = (Bytef *)extram_deflate_buffer;\n    data->z.avail_in = sizeof(extram_deflate_buffer);\n\n    if(!extram_inflate_init(data))\n    {\n      debug(\"--EXTRAM-- inflateInit failed @ %p\\n\", (void *)block);\n      goto err;\n    }\n\n    data->z.next_out = buffer;\n    data->z.avail_out = len;\n\n    if(~block->flags & EXTRAM_PLATFORM_ALLOC)\n    {\n      /* Inflate directly from the extram block. */\n      data->z.next_in = (Bytef *)block->data;\n      data->z.avail_in = block->compressed_size;\n\n      res = inflate(&data->z, Z_FINISH);\n    }\n    else\n\n    while((res == Z_OK || res == Z_BUF_ERROR) && left)\n    {\n      size_t sz = MIN(left, sizeof(extram_deflate_buffer));\n      size_t sz_ext = extram_alloc_size(sz);\n      left -= sz;\n      if(!extram_copy(extram_deflate_buffer, pos, sz_ext))\n        goto err;\n\n      pos += sz_ext >> 2;\n\n      data->z.next_in = (Bytef *)extram_deflate_buffer;\n      data->z.avail_in = sz;\n      res = inflate(&data->z, 0);\n    }\n\n    if(res != Z_STREAM_END || data->z.total_out != len)\n    {\n      debug(\"--EXTRAM-- inflate failed @ %p (total_out=%zu, len=%zu) with code %d (%s)\\n\",\n        (void *)block, (size_t)data->z.total_out, len, res,\n        (data->z.msg ? data->z.msg : \"no message\")\n      );\n      goto err;\n    }\n    trace(\"--EXTRAM--   inflated block of size %zu to %zu\\n\",\n     (size_t)data->z.total_in, (size_t)data->z.total_out);\n  }\n  else\n\n  if(block->flags & EXTRAM_RLE3)\n  {\n    // RLE3.\n    size_t sz = extram_alloc_size(block->compressed_size);\n    if(sz > sizeof(extram_deflate_buffer) ||\n     !extram_copy(extram_deflate_buffer, block->data, sz))\n    {\n      debug(\"--EXTRAM-- failed to copy RLE3 @ %p: uncompressed=%zu, compressed=%zu\\n\",\n        (void *)block, (size_t)block->uncompressed_size, (size_t)block->compressed_size\n      );\n      goto err;\n    }\n\n    if(!RLE3_unpack(buffer, len, (void *)extram_deflate_buffer,\n     block->compressed_size))\n    {\n      debug(\"--EXTRAM-- failed to unpack RLE3 @ %p\\n\", (void *)block);\n      goto err;\n    }\n    trace(\"--EXTRAM--   RLE3 unpacked block of size %zu to %zu\\n\",\n     (size_t)block->compressed_size, (size_t)block->uncompressed_size);\n  }\n  else\n  {\n    // Stored.\n    if(block->compressed_size != block->uncompressed_size)\n    {\n      debug(\"--EXTRAM-- stored size inconsistent @ %p: uncompressed=%zu, compressed=%zu\\n\",\n        (void *)block, (size_t)block->uncompressed_size, (size_t)block->compressed_size\n      );\n      goto err;\n    }\n    if(!extram_copy(buffer, block->data, alloc_size))\n      goto err;\n  }\n\n  checksum = extram_checksum(buffer, len);\n  if(checksum != block->checksum)\n  {\n    debug(\"--EXTRAM-- checksum fail @ %p (expected %08zx, got %08zx)\\n\",\n      (void *)block, (size_t)block->checksum, (size_t)checksum\n    );\n    goto err;\n  }\n\nclear:\n  if(block->flags & EXTRAM_PLATFORM_ALLOC)\n  {\n    platform_extram_free(block);\n  }\n  else\n    free(block);\n\n  platform_extram_unlock();\n  *src = (void *)buffer;\n  return true;\n\nerr:\n  platform_extram_unlock();\n  free(buffer);\n  return false;\n}\n\n/**\n * Move the board's memory from normal RAM to extra RAM.\n *\n * @param  board  board to send to extra RAM.\n * @param  file   __FILE__ of invoking call (via macro).\n * @param  line   __LINE__ of invoking call (via macro).\n */\nvoid real_store_board_to_extram(struct board *board, const char *file, int line)\n{\n  size_t board_size = board->board_width * board->board_height;\n  struct robot **robot_list = board->robot_list;\n  struct extram_data data;\n  int i;\n\n  if(board->is_extram)\n  {\n    warn(\"--EXTRAM-- board %p is already in extram! (%s:%d)\\n\", (void *)board, file, line);\n    return;\n  }\n\n  trace(\"--EXTRAM-- storing board %p (%s:%d)\\n\", (void *)board, file, line);\n  board->is_extram = true;\n\n  memset(&data, 0, sizeof(struct extram_data));\n\n  // Layer data.\n  data.compression_threshold = EXTRAM_COMPRESS_BOARDS_THRESHOLD;\n  if(!store_buffer_to_extram(&data, &board->level_id, board_size))\n    goto err;\n  if(!store_buffer_to_extram(&data, &board->level_param, board_size))\n    goto err;\n  if(!store_buffer_to_extram(&data, &board->level_color, board_size))\n    goto err;\n  if(!store_buffer_to_extram(&data, &board->level_under_id, board_size))\n    goto err;\n  if(!store_buffer_to_extram(&data, &board->level_under_param, board_size))\n    goto err;\n  if(!store_buffer_to_extram(&data, &board->level_under_color, board_size))\n    goto err;\n\n  // Overlay.\n  if(board->overlay_mode)\n  {\n    if(!store_buffer_to_extram(&data, &board->overlay, board_size))\n      goto err;\n    if(!store_buffer_to_extram(&data, &board->overlay_color, board_size))\n      goto err;\n  }\n\n  // Robot programs and source.\n  data.compression_threshold = EXTRAM_COMPRESS_ROBOTS_THRESHOLD;\n  for(i = 1; robot_list && i <= board->num_robots; i++)\n  {\n    struct robot *cur_robot = robot_list[i];\n    if(cur_robot)\n    {\n      if(cur_robot->program_bytecode)\n      {\n        if(!store_buffer_to_extram(&data, &cur_robot->program_bytecode,\n         cur_robot->program_bytecode_length))\n          goto err;\n      }\n\n#if defined(CONFIG_DEBYTECODE) || defined(CONFIG_EDITOR)\n      if(cur_robot->program_source)\n      {\n        if(!store_buffer_to_extram(&data, &cur_robot->program_source,\n         cur_robot->program_source_length))\n          goto err;\n      }\n#endif\n\n#ifdef CONFIG_EDITOR\n      if(cur_robot->command_map)\n      {\n        if(!store_buffer_to_extram(&data, (char **)&cur_robot->command_map,\n         cur_robot->command_map_length * sizeof(struct command_mapping)))\n          goto err;\n      }\n#endif\n      // TODO: it would eventually be better to pack and send these to extra\n      // memory instead of doing this.\n      clear_label_cache(cur_robot);\n    }\n  }\n\n#ifdef EXTRAM_STATS\n  print_stats();\n#endif\n\n  extram_deflate_destroy(&data);\n  return;\n\nerr:\n  {\n    char msg[81];\n    snprintf(msg, ARRAY_SIZE(msg), \"Failed to store board to extram at %s:%d\", file, line);\n    error(msg, ERROR_T_FATAL, ERROR_OPT_EXIT | ERROR_OPT_NO_HELP, 0);\n  }\n}\n\n/**\n * Move the board's memory from extra RAM to normal RAM.\n *\n * @param  board      board to retrieve from extra RAM.\n * @param  free_data  free all buffers and NULL their respective pointers. This\n *                    is faster than retrieving and freeing from normal RAM.\n * @param  file       __FILE__ of invoking call (via macro).\n * @param  line       __LINE__ of invoking call (via macro).\n */\nvoid real_retrieve_board_from_extram(struct board *board, boolean free_data,\n const char *file, int line)\n{\n  size_t board_size = board->board_width * board->board_height;\n  struct robot **robot_list = board->robot_list;\n  struct extram_data data;\n  int i;\n\n  if(!board->is_extram)\n  {\n    warn(\"--EXTRAM-- board %p isn't in extram! (%s:%d)\\n\", (void *)board, file, line);\n    return;\n  }\n\n  trace(\"--EXTRAM-- %s board %p (%s:%d)\\n\",\n   (free_data ? \"freeing\" : \"retrieving\"), (void *)board, file, line);\n  board->is_extram = false;\n\n  memset(&data, 0, sizeof(struct extram_data));\n  data.free_data = free_data;\n\n  // Layer data.\n  if(!retrieve_buffer_from_extram(&data, &board->level_id, board_size))\n    goto err;\n  if(!retrieve_buffer_from_extram(&data, &board->level_param, board_size))\n    goto err;\n  if(!retrieve_buffer_from_extram(&data, &board->level_color, board_size))\n    goto err;\n  if(!retrieve_buffer_from_extram(&data, &board->level_under_id, board_size))\n    goto err;\n  if(!retrieve_buffer_from_extram(&data, &board->level_under_param, board_size))\n    goto err;\n  if(!retrieve_buffer_from_extram(&data, &board->level_under_color, board_size))\n    goto err;\n\n  // Overlay.\n  if(board->overlay_mode)\n  {\n    if(!retrieve_buffer_from_extram(&data, &board->overlay, board_size))\n      goto err;\n    if(!retrieve_buffer_from_extram(&data, &board->overlay_color, board_size))\n      goto err;\n  }\n\n  // Robot programs and source.\n  for(i = 1; robot_list && i <= board->num_robots; i++)\n  {\n    struct robot *cur_robot = robot_list[i];\n    if(cur_robot)\n    {\n      if(cur_robot->program_bytecode)\n      {\n        if(!retrieve_buffer_from_extram(&data, &cur_robot->program_bytecode,\n         cur_robot->program_bytecode_length))\n          goto err;\n      }\n\n#if defined(CONFIG_DEBYTECODE) || defined(CONFIG_EDITOR)\n      if(cur_robot->program_source)\n      {\n        if(!retrieve_buffer_from_extram(&data, &cur_robot->program_source,\n         cur_robot->program_source_length))\n          goto err;\n      }\n#endif\n\n#ifdef CONFIG_EDITOR\n      if(cur_robot->command_map)\n      {\n        if(!retrieve_buffer_from_extram(&data, (char **)&cur_robot->command_map,\n         cur_robot->command_map_length * sizeof(struct command_mapping)))\n          goto err;\n      }\n#endif\n      // TODO: it would eventually be better to pack and send these to extra\n      // memory instead of doing this.\n      clear_label_cache(cur_robot);\n      cache_robot_labels(cur_robot);\n    }\n  }\n  extram_inflate_destroy(&data);\n  return;\n\nerr:\n  {\n    char msg[81];\n    snprintf(msg, ARRAY_SIZE(msg), \"Failed to load board from extram at %s:%d\", file, line);\n    error(msg, ERROR_T_FATAL, ERROR_OPT_EXIT | ERROR_OPT_NO_HELP, 0);\n  }\n}\n\n#ifdef CONFIG_EDITOR\n\nstatic boolean get_extram_buffer_usage(const void *buffer, size_t *compressed,\n size_t *uncompressed)\n{\n  const struct extram_block *block = (const struct extram_block *)buffer;\n  if(block->id != EXTRAM_ID)\n    return false;\n\n  *compressed += extram_block_size(block->compressed_size);\n  *uncompressed += block->uncompressed_size;\n  return true;\n}\n\n/**\n * Get total actual extra RAM usage (compressed) and total uncompressed\n * extra RAM size data for a board.\n *\n * @param  board          board to get extra RAM statistics for.\n * @param  compressed     pointer to write total actual RAM usage to.\n * @param  uncompressed   pointer to write total stored data size to.\n * @return                `true` on success, otherwise `false`.\n */\nboolean board_extram_usage(struct board *board, size_t *compressed,\n size_t *uncompressed)\n{\n  struct robot **robot_list;\n  int i;\n\n#ifdef USE_PLATFORM_EXTRAM_STORE\n  /* The headers are stored in non-mapped RAM and not really accessible. */\n  return false;\n#endif\n\n  if(!board || !compressed | !uncompressed)\n    return false;\n\n  *compressed = 0;\n  *uncompressed = 0;\n\n  if(!board->is_extram)\n    return true;\n\n  platform_extram_lock();\n\n  if(!get_extram_buffer_usage(board->level_id, compressed, uncompressed))\n    goto err;\n  if(!get_extram_buffer_usage(board->level_param, compressed, uncompressed))\n    goto err;\n  if(!get_extram_buffer_usage(board->level_color, compressed, uncompressed))\n    goto err;\n  if(!get_extram_buffer_usage(board->level_under_id, compressed, uncompressed))\n    goto err;\n  if(!get_extram_buffer_usage(board->level_under_param, compressed, uncompressed))\n    goto err;\n  if(!get_extram_buffer_usage(board->level_under_color, compressed, uncompressed))\n    goto err;\n\n  if(board->overlay_mode)\n  {\n    if(!get_extram_buffer_usage(board->overlay, compressed, uncompressed))\n      goto err;\n    if(!get_extram_buffer_usage(board->overlay_color, compressed, uncompressed))\n      goto err;\n  }\n\n  robot_list = board->robot_list;\n  for(i = 1; i <= board->num_robots; i++)\n  {\n    struct robot *cur_robot = robot_list[i];\n    if(cur_robot)\n    {\n      if(cur_robot->program_bytecode)\n        if(!get_extram_buffer_usage(cur_robot->program_bytecode,\n         compressed, uncompressed))\n          goto err;\n\n#if defined(CONFIG_DEBYTECODE) || defined(CONFIG_EDITOR)\n      if(cur_robot->program_source)\n        if(!get_extram_buffer_usage(cur_robot->program_source,\n         compressed, uncompressed))\n          goto err;\n\n      if(cur_robot->command_map)\n        if(!get_extram_buffer_usage(cur_robot->command_map,\n         compressed, uncompressed))\n          goto err;\n#endif\n    }\n  }\n  platform_extram_unlock();\n  return true;\n\nerr:\n  platform_extram_unlock();\n  return false;\n}\n\n#endif /* CONFIG_EDITOR */\n"
  },
  {
    "path": "src/extmem.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Kevin Vance <kvance@kvance.com>\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __EXTMEM_H\n#define __EXTMEM_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"board_struct.h\"\n#include \"world_struct.h\"\n\n#define store_board_to_extram(b) \\\n real_store_board_to_extram(b, __FILE__, __LINE__)\n#define retrieve_board_from_extram(b) \\\n real_retrieve_board_from_extram(b, false, __FILE__, __LINE__)\n#define clear_board_from_extram(b) \\\n real_retrieve_board_from_extram(b, true, __FILE__, __LINE__)\n\n#define set_current_board(mzx_world, b) \\\n real_set_current_board(mzx_world, b, __FILE__, __LINE__)\n#define set_current_board_ext(mzx_world, b) \\\n real_set_current_board_ext(mzx_world, b, __FILE__, __LINE__)\n\n#ifdef CONFIG_EXTRAM\n\n// Move the board's memory from normal RAM to extra RAM.\nCORE_LIBSPEC void real_store_board_to_extram(struct board *board,\n const char *file, int line);\n\n// Move the board's memory from extra RAM to normal RAM. If free_data is set,\n// clear all buffers and NULL their respective pointers instead.\nCORE_LIBSPEC void real_retrieve_board_from_extram(struct board *board,\n boolean free_data, const char *file, int line);\n\n#ifdef CONFIG_EDITOR\nCORE_LIBSPEC boolean board_extram_usage(struct board *board, size_t *compressed,\n size_t *uncompressed);\n#endif /* CONFIG_EDITOR */\n\n#else /* !CONFIG_EXTRAM */\n\n#ifdef DEBUG\n#include \"util.h\"\n#endif\n\nstatic inline void real_store_board_to_extram(struct board *board,\n const char *file, int line)\n{\n#ifdef DEBUG\n  if(!board->is_extram)\n    board->is_extram = true;\n  else\n    warn(\"board %p is already in extram! (%s:%d)\\n\", (void *)board, file, line);\n#endif\n}\n\nstatic inline void real_retrieve_board_from_extram(struct board *board,\n boolean free_data, const char *file, int line)\n{\n#ifdef DEBUG\n  if(board->is_extram)\n    board->is_extram = false;\n  else\n    warn(\"board %p isn't in extram! (%s:%d)\\n\", (void *)board, file, line);\n#endif\n}\n\n#endif /* !CONFIG_EXTRAM */\n\nstatic inline void real_set_current_board(struct world *mzx_world,\n struct board *cur_board, const char *file, int line)\n{\n  if(mzx_world->current_board && mzx_world->current_board != cur_board)\n    real_store_board_to_extram(mzx_world->current_board, file, line);\n  mzx_world->current_board = cur_board;\n}\n\nstatic inline void real_set_current_board_ext(struct world *mzx_world,\n struct board *cur_board, const char *file, int line)\n{\n  if(mzx_world->current_board != cur_board)\n  {\n    real_set_current_board(mzx_world, cur_board, file, line);\n    real_retrieve_board_from_extram(cur_board, false, file, line);\n  }\n}\n\n__M_END_DECLS\n\n#endif // __BOARD_H\n"
  },
  {
    "path": "src/game.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2018-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Main title screen/gaming code\n\n#include <ctype.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/stat.h>\n\n#include \"caption.h\"\n#include \"configure.h\"\n#include \"const.h\"\n#include \"core.h\"\n#include \"data.h\"\n#include \"error.h\"\n#include \"event.h\"\n#include \"game.h\"\n#include \"game_menu.h\"\n#include \"game_player.h\"\n#include \"game_update.h\"\n#include \"graphics.h\"\n#include \"robot.h\"\n#include \"window.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n#include \"io/fsafeopen.h\"\n#include \"io/vio.h\"\n\n#include \"audio/audio.h\"\n#include \"audio/sfx.h\"\n\nstatic const char *const world_ext[] = { \".MZX\", NULL };\nstatic const char *const save_ext[] = { \".SAV\", NULL };\n\nstruct game_context\n{\n  context ctx;\n  boolean fade_in;\n  boolean need_reload;\n  boolean load_dialog_on_failed_load;\n  boolean is_title;\n  boolean allow_cheats;\n\n  // Menu return values.\n  enum keycode menu_key;\n  boolean menu_alt;\n};\n\n// As nice as this would be to put in the title context, it's annoying\n// to get the context to clear_intro_mesg right now.\nstatic unsigned int intro_mesg_timer = MESG_TIMEOUT;\n\n/**\n * Activate the title screen intro message.\n */\n\nstatic void enable_intro_mesg(void)\n{\n  intro_mesg_timer = MESG_TIMEOUT;\n}\n\n/**\n * Disable the title screen intro message.\n */\n\nvoid clear_intro_mesg(void)\n{\n  intro_mesg_timer = 0;\n}\n\n/**\n * Draw the title screen intro message.\n */\n\nvoid draw_intro_mesg(struct world *mzx_world)\n{\n  static const char mesg1[] = \"F1: Help   \";\n  static const char mesg2[] = \"Enter: Menu   Ctrl-Alt-Enter: Fullscreen\";\n\n  if(intro_mesg_timer == 0)\n    return;\n\n  intro_mesg_timer--;\n\n  if(mzx_world->help_file)\n  {\n    write_string(mesg1, 14, 24, scroll_color, 0);\n    write_string(mesg2, 25, 24, scroll_color, 0);\n  }\n  else\n  {\n    write_string(mesg2, 20, 24, scroll_color, 0);\n  }\n}\n\n/**\n * Load a module for gameplay.\n */\n\nboolean load_game_module(struct world *mzx_world, char *filename,\n boolean fail_if_same)\n{\n  struct board *cur_board = mzx_world->current_board;\n  char translated_name[MAX_PATH];\n  size_t mod_name_size = strlen(filename);\n  boolean mod_star = false;\n  int n_result;\n\n  // Special case: mod \"\" ends the module.\n  if(!filename[0])\n  {\n    mzx_world->real_mod_playing[0] = 0;\n    audio_end_module();\n    return false;\n  }\n\n  // Do nothing.\n  if(!strcmp(filename, \"*\"))\n    return false;\n\n  // Temporarily get rid of the star.\n  if(mod_name_size && filename[mod_name_size - 1] == '*')\n  {\n    filename[mod_name_size - 1] = 0;\n    mod_star = true;\n  }\n\n  // Get the translated name (the one we want to compare against later)\n  n_result = fsafetranslate(filename, translated_name, MAX_PATH);\n  if(n_result != FSAFE_SUCCESS)\n    n_result = audio_legacy_translate(filename, translated_name, MAX_PATH);\n\n  // Add * back\n  if(mod_star)\n    filename[mod_name_size - 1] = '*';\n\n  if(n_result == FSAFE_SUCCESS)\n  {\n    // In certain situations, play the mod only if the names are different.\n    if((mod_star || fail_if_same) &&\n     !strcasecmp(translated_name, mzx_world->real_mod_playing))\n      return false;\n\n    // If the mod actually changes, update real mod playing.\n    // The safety check has already been done, so don't do it again ('false').\n    if(audio_play_module(translated_name, false, cur_board->volume))\n    {\n      strcpy(mzx_world->real_mod_playing, translated_name);\n      return true;\n    }\n  }\n  return false;\n}\n\n/**\n * Load the current board's module, even if it's already playing.\n */\n\nvoid load_board_module(struct world *mzx_world)\n{\n  load_game_module(mzx_world, mzx_world->current_board->mod_playing, false);\n}\n\n/**\n * Fade out before the world load and clear the screen. The Emscripten port and\n * anything using the meter without a protected palette need special handling.\n */\nstatic inline void load_vquick_fadeout_and_clear(void)\n{\n#ifdef __EMSCRIPTEN__\n  // HACK: avoid putting load_world_*/load_savegame on the asyncify whitelist\n  insta_fadeout();\n#else\n  vquick_fadeout();\n#endif\n\n  clear_screen();\n\n#ifdef CONFIG_LOADSAVE_METER\n  // Fade back in to display the meter if, for whatever reason, it can't use\n  // the protected palette.\n  insta_fadein();\n#endif\n}\n\n/**\n * Load a world and prepare it for gameplay.\n * On failure, make sure the title's fade setting is restored (if applicable).\n * If the provided start board parameter is a valid board in the loaded world,\n * gameplay will start on the given board.\n */\nstatic boolean load_world_gameplay_ext(struct game_context *game, char *name,\n int start_board)\n{\n  struct world *mzx_world = ((context *)game)->world;\n  boolean was_faded = get_fade_status();\n  boolean ignore;\n\n  struct board *cur_board;\n\n  // Save the name of the current playing mod; we'll want it to continue playing\n  // into gameplay if the first board has the same mod or mod *.\n  char old_mod_playing[MAX_PATH];\n  strcpy(old_mod_playing, mzx_world->real_mod_playing);\n\n  load_vquick_fadeout_and_clear();\n\n  game->fade_in = true;\n\n  // Reset the joystick mappings to the defaults before loading a game config.\n  joystick_reset_game_map();\n\n  if(reload_world(mzx_world, name, &ignore))\n  {\n    if((start_board < 0) || (start_board >= mzx_world->num_boards) ||\n     !(mzx_world->board_list[start_board]))\n    {\n      start_board = mzx_world->first_board;\n    }\n\n    // Do this even if it's the current board--this is necessary to set up\n    // the temporary board if the title has Reset Board on Entry set.\n    change_board(mzx_world, start_board);\n    cur_board = mzx_world->current_board;\n\n    // Send both JUSTENTERED and JUSTLOADED; the JUSTLOADED label will\n    // take priority if a robot defines it.\n    send_robot_def(mzx_world, 0, LABEL_JUSTENTERED);\n    send_robot_def(mzx_world, 0, LABEL_JUSTLOADED);\n\n    change_board_set_values(mzx_world);\n    change_board_load_assets(mzx_world);\n\n    // Load the mod unless it's the mod from the title screen or *.\n    strcpy(mzx_world->real_mod_playing, old_mod_playing);\n    load_game_module(mzx_world, cur_board->mod_playing, true);\n    sfx_clear_queue();\n\n    caption_set_world(mzx_world);\n    return true;\n  }\n\n  if(was_faded)\n    game->fade_in = false;\n\n  return false;\n}\n\n/**\n * Load a world and prepare it for gameplay.\n * On failure, make sure the title's fade setting is restored (if applicable).\n */\nstatic boolean load_world_gameplay(struct game_context *game, char *name)\n{\n  return load_world_gameplay_ext(game, name, -1);\n}\n\n/**\n * Load a world and prepare it for the title screen.\n * On failure, display a blank screen.\n */\n\nstatic boolean load_world_title(struct game_context *game, char *name)\n{\n  struct world *mzx_world = ((context *)game)->world;\n  boolean ignore;\n\n  load_vquick_fadeout_and_clear();\n  enable_intro_mesg();\n  game->fade_in = true;\n\n  // Reset the joystick mappings to the defaults before loading a game config.\n  joystick_reset_game_map();\n\n  if(reload_world(mzx_world, name, &ignore))\n  {\n    // Load was successful, so set curr_file\n    if(curr_file != name)\n      strcpy(curr_file, name);\n\n    // Only send JUSTLOADED on the title screen.\n    send_robot_def(mzx_world, 0, LABEL_JUSTLOADED);\n\n    // Do this even though it should be the initial board--this is necessary to\n    // set up the temporary board if the title has Reset Board on Entry set.\n    change_board(mzx_world, 0);\n    change_board_set_values(mzx_world);\n    change_board_load_assets(mzx_world);\n\n    // Music may be playing from the previous world or from editing. End the\n    // module explicitly first in case the title module fails to load.\n    audio_end_module();\n    load_board_module(mzx_world);\n    sfx_clear_queue();\n\n    caption_set_world(mzx_world);\n    return true;\n  }\n  else\n\n  if(mzx_world->active)\n  {\n    clear_world(mzx_world);\n    clear_global_data(mzx_world);\n  }\n\n  return false;\n}\n\n/**\n * Load a saved game and prepare it for gameplay.\n * On failure, make sure the title world's fade setting is restored.\n */\n\nstatic boolean load_savegame(struct game_context *game, char *name)\n{\n  struct world *mzx_world = ((context *)game)->world;\n  boolean was_faded = get_fade_status();\n  boolean save_is_faded;\n\n  load_vquick_fadeout_and_clear();\n  game->fade_in = true;\n\n  if(reload_savegame(mzx_world, name, &save_is_faded))\n  {\n    if(curr_sav != name)\n      strcpy(curr_sav, name);\n\n    // Only send JUSTLOADED for savegames.\n    send_robot_def(mzx_world, 0, LABEL_JUSTLOADED);\n    find_player(mzx_world);\n\n    load_game_module(mzx_world, mzx_world->real_mod_playing, false);\n    sfx_clear_queue();\n\n    if(save_is_faded)\n      game->fade_in = false;\n\n    caption_set_world(mzx_world);\n    return true;\n  }\n\n  if(was_faded)\n    game->fade_in = false;\n\n  return false;\n}\n\n/**\n * Open a user interface to select a world to load on the title.\n */\n\nstatic boolean load_world_title_selection(struct game_context *game)\n{\n  struct world *mzx_world = ((context *)game)->world;\n  char world_name[MAX_PATH] = { 0 };\n\n  if(!choose_file_ch(mzx_world, world_ext, world_name, \"Load World\",\n   ALLOW_ALL_DIRS))\n  {\n    return load_world_title(game, world_name);\n  }\n\n  return false;\n}\n\n/**\n * Open a user interface to select a save to load.\n */\n\nstatic boolean load_savegame_selection(struct game_context *game)\n{\n  struct world *mzx_world = ((context *)game)->world;\n  char save_file_name[MAX_PATH] = { 0 };\n  int slot_result = SLOTSEL_FILE_MANAGER_RESULT;\n\n  if(get_config()->save_slots)\n  {\n    slot_result = slot_manager(mzx_world, save_file_name,\n     \"Choose game to restore\", false);\n\n    if(slot_result == SLOTSEL_CANCEL_RESULT)\n    {\n      return false;\n    }\n  }\n\n  if(slot_result == SLOTSEL_OK_RESULT ||\n   !choose_file_ch(mzx_world, save_ext, save_file_name,\n    \"Choose game to restore\", ALLOW_ALL_DIRS))\n  {\n    return load_savegame(game, save_file_name);\n  }\n\n  return false;\n}\n\n/**\n * Draw function for the title and gameplay. This actually includes most of the\n * game update cycle, including player input and updating the board.\n */\n\nstatic boolean game_draw(context *ctx)\n{\n  struct game_context *game = (struct game_context *)ctx;\n  const struct config_info *conf = get_config();\n  struct world *mzx_world = ctx->world;\n\n  // No game state change has happened (yet)\n  mzx_world->change_game_state = CHANGE_STATE_NONE;\n\n  if(game->is_title && game->fade_in)\n  {\n    // Focus on center\n    int x, y;\n    set_screen_coords(640/2, 350/2, &x, &y);\n    focus_pixel(x, y);\n  }\n\n  if(!mzx_world->active)\n  {\n    // There is no MZX_SPEED to derive a framerate from, so use the UI rate.\n    set_context_framerate_mode(ctx, FRAMERATE_UI);\n    if(!conf->standalone_mode)\n    {\n      // Animate the intro message and periodically reset the timer so it's\n      // obvious that MZX is still working and hasn't e.g. frozen. Do this at\n      // (effectively) MZX speed 4 so it doesn't hurt to look at.\n      if(!(intro_mesg_timer % 3))\n      {\n        if(intro_mesg_timer < 10)\n          enable_intro_mesg();\n        update_scroll_color();\n      }\n      draw_intro_mesg(mzx_world);\n    }\n    m_show();\n    return true;\n  }\n\n  set_context_framerate_mode(ctx, FRAMERATE_MZX_SPEED);\n  update_world(ctx, game->is_title);\n  return draw_world(ctx, game->is_title);\n}\n\n// Forward declaration since this is used for both game and title screen.\n__editor_maybe_static\nvoid play_game(context *ctx, boolean *_fade_in);\n\n/**\n * Idle function for the title and gameplay. Executes parts of the update\n * cycle that need to occur immediately after the delay.\n */\n\nstatic boolean game_idle(context *ctx)\n{\n  struct game_context *game = (struct game_context *)ctx;\n  const struct config_info *conf = get_config();\n  struct world *mzx_world = ctx->world;\n\n  if(!mzx_world->active)\n    return false;\n\n  if(game->fade_in)\n  {\n    vquick_fadein();\n    game->fade_in = false;\n  }\n\n  switch(mzx_world->change_game_state)\n  {\n    case CHANGE_STATE_NONE:\n    case CHANGE_STATE_INTERRUPT_CYCLE:\n      break;\n\n    case CHANGE_STATE_SWAP_WORLD:\n    {\n      // The SWAP WORLD command was used by a robot.\n      // TODO: the game has already been loaded at this point, but maybe\n      // should be loaded here instead of in run_robot.c?\n      caption_set_world(mzx_world);\n\n      // Load the new board's mod\n      load_board_module(mzx_world);\n\n      // Send both JUSTLOADED and JUSTENTERED; the JUSTENTERED label will take\n      // priority if a robot defines it (instead of JUSTLOADED like on the title\n      // screen).\n      send_robot_def(mzx_world, 0, LABEL_JUSTLOADED);\n      send_robot_def(mzx_world, 0, LABEL_JUSTENTERED);\n\n      return true;\n    }\n\n    case CHANGE_STATE_LOAD_GAME_ROBOTIC:\n    {\n      // The LOAD_GAME counter was used by a robot.\n      // TODO: the game has already been loaded at this point, but maybe\n      // should be loaded here instead of in counter.c?\n\n      // real_mod_playing was set during the savegame load but the mod hasn't\n      // started playing yet.\n      load_game_module(mzx_world, mzx_world->real_mod_playing, false);\n\n      // Only send JUSTLOADED for savegames.\n      send_robot_def(mzx_world, 0, LABEL_JUSTLOADED);\n\n      return true;\n    }\n\n    case CHANGE_STATE_PLAY_GAME_ROBOTIC:\n    {\n      if(!game->is_title)\n        break;\n\n      if(load_world_gameplay(game, curr_file))\n      {\n        play_game(ctx, NULL);\n        game->need_reload = true;\n        return true;\n      }\n      break;\n    }\n\n    case CHANGE_STATE_EXIT_GAME_ROBOTIC:\n    {\n      // The EXIT_GAME counter was used by a robot. This works during gameplay,\n      // but also on the titlescreen if standalone mode is active.\n      if(!game->is_title || conf->standalone_mode)\n      {\n        destroy_context(ctx);\n        return true;\n      }\n      break;\n    }\n\n    case CHANGE_STATE_REQUEST_EXIT:\n    {\n      // The user halted the program while a robot was executing.\n      destroy_context(ctx);\n      return true;\n    }\n  }\n\n  // A board change or other form of teleport may need to occur.\n  // This may require a fade in the next time this function is run (next cycle).\n  if(update_resolve_target(mzx_world, &(game->fade_in)))\n    return true;\n\n  // The SAVE_GAME counter might have been used this cycle.\n  if(!game->is_title && mzx_world->robotic_save_type == SAVE_GAME)\n  {\n    save_world(mzx_world, mzx_world->robotic_save_path, true, MZX_VERSION);\n    mzx_world->robotic_save_type = SAVE_NONE;\n  }\n\n  return false;\n}\n\nstatic boolean game_key(context *ctx, int *key); // Forward declaration.\n\n/**\n * Callback for handling the game menu key.\n */\nstatic void game_menu_callback(context *ctx, context_callback_param *ignore)\n{\n  struct game_context *game = (struct game_context *)ctx;\n  int key = game->menu_key;\n\n  game->menu_key = 0;\n\n  if(key)\n    game_key(ctx, &(key));\n}\n\n/**\n * Joystick function for the gameplay context. Gameplay only detects UI\n * escape presses and ignores everything else.\n */\nstatic boolean game_joystick(context *ctx, int *key, int action)\n{\n  struct game_context *game = (struct game_context *)ctx;\n\n  switch(action)\n  {\n    case JOY_START:\n    case JOY_SELECT:\n    {\n      // Special: open the game menu if the enter or escape menu is allowed.\n      if(allow_enter_menu(ctx->world, false) ||\n       allow_exit_menu(ctx->world, false))\n      {\n        game_menu(ctx, true, &(game->menu_key), &(game->menu_alt));\n        context_callback(ctx, NULL, game_menu_callback);\n      }\n      return true;\n    }\n  }\n  return false;\n}\n\n/**\n * Key function for the gameplay context. This handles interface keys and some\n * key labels; some other keys are handled in the update function.\n */\nstatic boolean game_key(context *ctx, int *key)\n{\n  struct game_context *game = (struct game_context *)ctx;\n  const struct config_info *conf = get_config();\n  struct world *mzx_world = ctx->world;\n  struct board *cur_board = mzx_world->current_board;\n  char keylbl[] = \"KEY?\";\n\n  int key_status = get_key_status(keycode_internal_wrt_numlock, *key);\n  boolean exit_status = get_exit_status();\n  boolean confirm_exit = false;\n\n  if(*key && !exit_status)\n  {\n    // Get the char for the KEY? labels. If there is no relevant unicode\n    // keypress, we want to use the regular code instead.\n    int key_unicode = get_key(keycode_text_ascii);\n    int key_char = *key;\n\n    if(key_unicode > 0 && key_unicode < 256)\n      key_char = key_unicode;\n\n    if(key_char > 0 && key_char < 256)\n    {\n      // KEY? label and pre-2.80 KEY counter.\n      // Values over 256 have no meaning here.\n      key_char = toupper(key_char);\n\n      // 1.xx only supported A-Z. <2.60 only supported 1-9 and A-Z.\n      // Since 2.60 magic overlaps with a lot of older games, 2.62 came out\n      // a week after it, and this counter frequently broke old games,\n      // apply the new behavior conservatively to >=2.62 instead.\n      if(mzx_world->version >= V262 ||\n       (key_char >= 'A' && key_char <= 'Z') ||\n       (key_char >= '1' && key_char <= '9' && mzx_world->version >= V200))\n      {\n        // Send the KEY? label.\n        keylbl[3] = key_char;\n        send_robot_all_def(mzx_world, keylbl);\n\n        // In pre-2.80 MZX versions, KEY was a board counter.\n        if(mzx_world->version < VERSION_PORT)\n          cur_board->last_key = key_char;\n      }\n    }\n\n    switch(*key)\n    {\n      case IKEY_F3:\n      {\n        // Save game\n        if(allow_save_menu(mzx_world))\n        {\n          char save_game[MAX_PATH];\n          int slot_result = SLOTSEL_FILE_MANAGER_RESULT;\n          strcpy(save_game, curr_sav);\n\n          if(get_config()->save_slots)\n          {\n            slot_result = slot_manager(mzx_world, save_game, \"Save game\", true);\n\n            if(slot_result == SLOTSEL_CANCEL_RESULT)\n            {\n              return true;\n            }\n          }\n\n          if(slot_result == SLOTSEL_OK_RESULT ||\n           !new_file(mzx_world, save_ext, \".sav\", save_game, \"Save game\",\n            ALLOW_ALL_DIRS))\n          {\n            strcpy(curr_sav, save_game);\n            save_world(mzx_world, curr_sav, true, MZX_VERSION);\n          }\n        }\n        return true;\n      }\n\n      case IKEY_F4:\n      {\n        // ALT+F4 - do nothing.\n        if(get_alt_status(keycode_internal))\n          break;\n\n        // Restore saved game\n        if(allow_load_menu(mzx_world, false))\n          load_savegame_selection(game);\n\n        return true;\n      }\n\n      case IKEY_F5:\n      case IKEY_INSERT:\n      {\n        // Change bomb type\n        if(!mzx_world->dead)\n          player_switch_bomb_type(mzx_world);\n\n        return true;\n      }\n\n      // Toggle debug mode\n      case IKEY_F6:\n      {\n        if(edit_world && allow_debug_menu(mzx_world))\n          mzx_world->debug_mode = !(mzx_world->debug_mode);\n\n        return true;\n      }\n\n      // Cheat\n      case IKEY_F7:\n      {\n        if(game->allow_cheats || mzx_world->editing)\n          player_cheat_give_all(mzx_world);\n\n        return true;\n      }\n\n      // Cheat More\n      case IKEY_F8:\n      {\n        if(game->allow_cheats || mzx_world->editing)\n          player_cheat_zap(mzx_world);\n\n        return true;\n      }\n\n      // Quick save\n      case IKEY_F9:\n      {\n        if(allow_save_menu(mzx_world))\n          save_world(mzx_world, curr_sav, true, MZX_VERSION);\n\n        return true;\n      }\n\n      // Quick load saved game\n      case IKEY_F10:\n      {\n        if(allow_load_menu(mzx_world, false))\n        {\n          struct stat file_info;\n\n          if(!vstat(curr_sav, &file_info))\n            load_savegame(game, curr_sav);\n        }\n        return true;\n      }\n\n      case IKEY_F11:\n      {\n        if(allow_debug_menu(mzx_world))\n        {\n          // Breakpoint editor\n          if(get_alt_status(keycode_internal) || game->menu_alt)\n          {\n            if(debug_robot_config)\n              debug_robot_config(mzx_world);\n          }\n          // Counter debugger\n          else\n          {\n            if(debug_counters)\n              debug_counters(ctx);\n          }\n        }\n        game->menu_alt = false;\n        return true;\n      }\n\n      case IKEY_RETURN:\n      {\n        // KeyEnter label was added in 2.60. See the KEY? comments above for\n        // why this is being applied to >=2.62 instead of 2.60.\n        if(mzx_world->version >= V262)\n          send_robot_all_def(mzx_world, \"KeyEnter\");\n\n        // Ignore if this isn't a fresh press\n        if(key_status != 1)\n          return true;\n\n        if(allow_enter_menu(mzx_world, false))\n        {\n          game_menu(ctx, false, &(game->menu_key), &(game->menu_alt));\n          context_callback(ctx, NULL, game_menu_callback);\n        }\n        return true;\n      }\n\n      case IKEY_ESCAPE:\n      {\n        // Ignore if this isn't a fresh press\n        // NOTE: disabled because it breaks the joystick action.\n        //if(key_status != 1)\n          //return true;\n\n        // ESCAPE_MENU (2.90+)\n        if(allow_exit_menu(mzx_world, false))\n          confirm_exit = true;\n\n        break;\n      }\n    }\n  }\n\n  // Quit\n  if(exit_status || confirm_exit)\n  {\n    // Special behaviour in standalone- only escape exits\n    // ask for confirmation. Exit events instead terminate MegaZeux.\n    if(conf->standalone_mode && !confirm_exit)\n    {\n      core_full_exit(ctx);\n    }\n    else\n    {\n      if(!confirm(mzx_world, \"Quit playing- Are you sure?\"))\n        destroy_context(ctx);\n    }\n    return true;\n  }\n\n  return false;\n}\n\n/**\n * Destroy a game context.\n */\n\nstatic void game_destroy(context *ctx)\n{\n  vquick_fadeout();\n  clear_screen();\n  audio_end_module();\n  sfx_clear_queue();\n}\n\n/**\n * Create and run the gameplay context.\n */\n\n__editor_maybe_static\nvoid play_game(context *parent, boolean *_fade_in)\n{\n  const struct config_info *conf = get_config();\n  struct game_context *game;\n  struct context_spec spec;\n\n  game = ccalloc(1, sizeof(struct game_context));\n  game->fade_in = _fade_in ? * _fade_in : true;\n  game->is_title = false;\n  game->allow_cheats = false;\n\n  // Not used for gameplay...\n  game->need_reload = false;\n  game->load_dialog_on_failed_load = false;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw     = game_draw;\n  spec.idle     = game_idle;\n  spec.key      = game_key;\n  spec.joystick = game_joystick;\n  spec.destroy  = game_destroy;\n\n  spec.framerate_mode = FRAMERATE_MZX_SPEED;\n\n  create_context((context *)game, parent, &spec, CTX_PLAY_GAME);\n\n  if(conf->allow_cheats == ALLOW_CHEATS_ALWAYS ||\n   (conf->allow_cheats == ALLOW_CHEATS_MZXRUN && !edit_world))\n  {\n    // Enable cheating outside of the editor.\n    game->allow_cheats = true;\n  }\n\n  clear_intro_mesg();\n}\n\n/**\n * When control returns to the title screen from gameplay or the editor, the\n * current world needs to be reloaded. When starting MegaZeux, if the startup\n * world can't be loaded, the load menu needs to be displayed instead.\n */\n\nstatic void title_resume(context *ctx)\n{\n  struct game_context *title = (struct game_context *)ctx;\n  struct config_info *conf = get_config();\n\n  if(title->need_reload)\n  {\n    if(!load_world_title(title, curr_file))\n    {\n      // World failed to load and no video? Hard exit so MZX doesn't stay\n      // stuck on a dialog or the blank title screen...\n      if(!has_video_initialized())\n      {\n        destroy_context(ctx);\n        return;\n      }\n\n      conf->standalone_mode = false;\n\n      // Do this to avoid some UI fade bugs...\n      insta_fadein();\n\n      if(title->load_dialog_on_failed_load)\n      {\n        load_world_title_selection(title);\n      }\n    }\n  }\n\n  title->load_dialog_on_failed_load = false;\n  title->need_reload = false;\n}\n\nstatic boolean title_key(context *ctx, int *key); // Forward declaration.\n\n/**\n * Callback for handling the main menu key.\n */\nstatic void main_menu_callback(context *ctx, context_callback_param *ignore)\n{\n  struct game_context *title = (struct game_context *)ctx;\n  int key = title->menu_key;\n\n  title->menu_key = 0;\n  title->menu_alt = false;\n\n  if(key)\n    title_key(ctx, &(key));\n}\n\n/**\n * Joystick handler for the title screen.\n */\nstatic boolean title_joystick(context *ctx, int *key, int action)\n{\n  struct game_context *title = (struct game_context *)ctx;\n  struct world *mzx_world = ctx->world;\n\n  switch(action)\n  {\n    case JOY_B:\n    case JOY_SELECT:\n    {\n      // Special: open the main menu if the enter or escape menu is allowed.\n      // The B button is the alternate for select instead of start because the\n      // start button makes much more sense playing the game on the title.\n      if(allow_enter_menu(mzx_world, true) || allow_exit_menu(mzx_world, true))\n      {\n        main_menu(ctx, true, &(title->menu_key));\n        context_callback(ctx, NULL, main_menu_callback);\n      }\n      return true;\n    }\n\n    case JOY_A:         *key = IKEY_F5; return true;\n    case JOY_X:         *key = IKEY_F3; return true;\n    case JOY_Y:         *key = IKEY_F4; return true;\n    case JOY_START:     *key = IKEY_F5; return true;\n    case JOY_LSHOULDER: *key = IKEY_F2; return true;\n    case JOY_RSHOULDER: *key = IKEY_F2; return true;\n    case JOY_LTRIGGER:  *key = IKEY_F3; return true;\n    case JOY_RTRIGGER:  *key = IKEY_F4; return true;\n  }\n  return false;\n}\n\n/**\n * Key handler for the title screen.\n */\nstatic boolean title_key(context *ctx, int *key)\n{\n  const struct config_info *conf = get_config();\n  struct game_context *title = (struct game_context *)ctx;\n  struct world *mzx_world = ctx->world;\n\n  // NOTE: disabled due to joystick support. See IKEY_RETURN and IKEY_ESCAPE.\n  //int key_status = get_key_status(keycode_internal_wrt_numlock, *key);\n  boolean exit_status = get_exit_status();\n\n  boolean reload_curr_file_in_editor = true;\n  boolean confirm_exit = false;\n\n  switch(*key)\n  {\n#ifdef CONFIG_HELPSYS\n    case IKEY_h:\n    {\n      // Help system alternate binding.\n      *key = IKEY_F1;\n      break;\n    }\n#endif\n\n    case IKEY_s:\n    {\n      // Configure alternate binding.\n      *key = IKEY_F2;\n      break;\n    }\n\n    case IKEY_F3:\n    case IKEY_l:\n    {\n      if(allow_load_world_menu(mzx_world))\n        load_world_title_selection(title);\n\n      return true;\n    }\n\n    case IKEY_F4:\n    {\n      // ALT+F4 - do nothing.\n      if(get_alt_status(keycode_internal))\n        break;\n    }\n\n    /* fallthrough */\n\n    case IKEY_r:\n    {\n      // Restore saved game\n      if(allow_load_menu(mzx_world, true))\n      {\n        if(load_savegame_selection(title))\n        {\n          play_game(ctx, &(title->fade_in));\n          title->need_reload = true;\n        }\n      }\n      return true;\n    }\n\n    case IKEY_F5:\n    case IKEY_p:\n    {\n      // Play game\n      if(mzx_world->active)\n      {\n        if(mzx_world->only_from_swap)\n        {\n          error(\"You can only play this game via a swap from another game\",\n           ERROR_T_WARNING, ERROR_OPT_OK, 0x3101);\n          return true;\n        }\n\n        if(load_world_gameplay(title, curr_file))\n        {\n          play_game(ctx, NULL);\n          title->need_reload = true;\n        }\n      }\n      return true;\n    }\n\n    case IKEY_F7:\n    case IKEY_u:\n    {\n      if(check_for_updates)\n      {\n        // FIXME this is garbage\n        int current_music_vol = audio_get_music_volume();\n        int current_pcs_vol = audio_get_pcs_volume();\n        audio_set_music_volume(0);\n        audio_set_pcs_volume(0);\n        if(mzx_world->active)\n          audio_set_module_volume(0);\n\n        check_for_updates(ctx, false);\n\n        audio_set_pcs_volume(current_pcs_vol);\n        audio_set_music_volume(current_music_vol);\n        if(mzx_world->active)\n          audio_set_module_volume(mzx_world->current_board->volume);\n      }\n      return true;\n    }\n\n    case IKEY_F8:\n    case IKEY_n:\n    {\n      reload_curr_file_in_editor = false;\n    }\n\n    /* fallthrough */\n\n    case IKEY_F9:\n    case IKEY_e:\n    {\n      if(edit_world)\n      {\n        // Editor\n        sfx_clear_queue();\n        vquick_fadeout();\n        title->need_reload = true;\n        title->fade_in = true;\n\n        edit_world(ctx, reload_curr_file_in_editor);\n      }\n      return true;\n    }\n\n    // Quickload saved game\n    case IKEY_F10:\n    {\n      if(allow_load_menu(mzx_world, true))\n      {\n        struct stat file_info;\n\n        if(!vstat(curr_sav, &file_info) && load_savegame(title, curr_sav))\n        {\n          play_game(ctx, &(title->fade_in));\n          title->need_reload = true;\n        }\n      }\n      return true;\n    }\n\n    case IKEY_RETURN: // Enter\n    {\n      // Ignore if this isn't a fresh press\n      // NOTE: disabled because it breaks the joystick actions.\n      //if(key_status != 1)\n        //return true;\n\n      if(allow_enter_menu(mzx_world, true))\n      {\n        main_menu(ctx, false, &(title->menu_key));\n        context_callback(ctx, NULL, main_menu_callback);\n      }\n      return true;\n    }\n\n    case IKEY_ESCAPE:\n    {\n      // Ignore if this isn't a fresh press\n      // NOTE: disabled because it breaks the joystick actions.\n      //if(key_status != 1)\n        //return true;\n\n      if(allow_exit_menu(mzx_world, true))\n        confirm_exit = true;\n\n      break;\n    }\n  }\n\n  // Quit\n  if(exit_status || confirm_exit)\n  {\n    // Special behaviour in standalone- only escape exits\n    // ask for confirmation. Exit events instead terminate MegaZeux.\n    if(conf->standalone_mode && !confirm_exit)\n    {\n      core_full_exit(ctx);\n    }\n    else\n    {\n      if(!confirm(mzx_world, \"Exit MegaZeux - Are you sure?\"))\n        destroy_context(ctx);\n    }\n    return true;\n  }\n\n  return false;\n}\n\n/**\n * Create and run the title screen context. This context should only be started\n * when MegaZeux is opened, and MegaZeux should terminate if it is destroyed.\n * If startup_editor is enabled, also start the editor. If standalone_mode and\n * no_titlescreen mode are enabled, only attempt to start gameplay instead.\n */\n\nvoid title_screen(context *parent)\n{\n  struct config_info *conf = get_config();\n  struct game_context *title;\n  struct game_context tmp;\n  struct context_spec spec;\n\n  tmp.ctx.world = parent->world;\n\n  if(edit_world)\n  {\n    conf->standalone_mode = false;\n\n    if(conf->test_mode)\n    {\n      if(load_world_gameplay_ext(&tmp, curr_file, conf->test_mode_start_board))\n      {\n        parent->world->editing = true;\n        play_game(parent, NULL);\n      }\n      return;\n    }\n  }\n\n  if(conf->standalone_mode && conf->no_titlescreen)\n  {\n    if(load_world_gameplay(&tmp, curr_file))\n    {\n      play_game(parent, NULL);\n      return;\n    }\n\n    conf->standalone_mode = false;\n  }\n\n  title = ccalloc(1, sizeof(struct game_context));\n  title->fade_in = true;\n  title->need_reload = true;\n  title->load_dialog_on_failed_load = true;\n  title->is_title = true;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.resume   = title_resume;\n  spec.draw     = game_draw;\n  spec.idle     = game_idle;\n  spec.key      = title_key;\n  spec.joystick = title_joystick;\n  spec.destroy  = game_destroy;\n\n  create_context((context *)title, parent, &spec, CTX_TITLE_SCREEN);\n  default_palette();\n\n  if(edit_world && conf->startup_editor)\n  {\n    title->load_dialog_on_failed_load = false;\n    edit_world((context *)title, true);\n  }\n\n#ifdef VERSION_PRERELEASE\n  if(has_video_initialized())\n  {\n    error(\"Pre-release: created saves/worlds may be incompatible with future versions.\",\n     ERROR_T_WARNING, ERROR_OPT_OK, 0);\n  }\n#endif\n\n  clear_screen();\n}\n"
  },
  {
    "path": "src/game.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declarations for GAME.CPP */\n\n#ifndef __GAME_H\n#define __GAME_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"core.h\"\n#include \"world_struct.h\"\n\nCORE_LIBSPEC void title_screen(context *parent);\nCORE_LIBSPEC void load_board_module(struct world *mzx_world);\nCORE_LIBSPEC boolean load_game_module(struct world *mzx_world, char *filename,\n boolean fail_if_same);\n\nvoid clear_intro_mesg(void);\nvoid draw_intro_mesg(struct world *mzx_world);\n\n#ifdef CONFIG_EDITOR\nCORE_LIBSPEC void play_game(context *parent, boolean *_fade_in);\n#endif\n\n__M_END_DECLS\n\n#endif // __GAME_H\n"
  },
  {
    "path": "src/game_menu.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2018-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"about.h\"\n#include \"configure.h\"\n#include \"const.h\"\n#include \"core.h\"\n#include \"counter.h\"\n#include \"event.h\"\n#include \"game_menu.h\"\n#include \"game_player.h\"\n#include \"graphics.h\"\n#include \"settings.h\"\n#include \"util.h\"\n#include \"window.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n\n#include <string.h>\n\n#define MENU_COL            0x19\n#define MENU_COL_DARK       0x10\n#define MENU_COL_CORNER     0x18\n#define MENU_COL_TITLE      0x1E\n#define MENU_COL_TEXT       0x1F\n#define MENU_COL_NO_SELECT  0x19\n#define MENU_COL_SELECTED   0xFC\n#define MENU_COL_COUNTER    0x1B\n\n#define GAME_MENU_X         8\n#define GAME_MENU_Y         4\n#define GAME_MENU_WIDTH     29\n\n#define GAME_STATUS_X       (GAME_MENU_X + GAME_MENU_WIDTH + 1)\n#define GAME_STATUS_Y       GAME_MENU_Y\n#define GAME_STATUS_WIDTH   30\n#define GAME_STATUS_HEIGHT  18\n#define GAME_STATUS_INDENT  16\n\n#define MAIN_MENU_X         28\n#define MAIN_MENU_Y         4\n#define MAIN_MENU_WIDTH     24\n\n#define KEY_CHAR            '\\x0C'\n\nstatic const char main_menu_title[] = \"MegaZeux \" VERSION;\n\nenum main_menu_opts\n{\n  M_NONE,\n  M_RETURN,\n  M_EXIT,\n  M_HELP,\n  M_SETTINGS,\n  M_LOAD_WORLD,\n  M_RESTORE_SAVE,\n  M_PLAY_WORLD,\n  M_UPDATER,\n  M_NEW_WORLD,\n  M_EDIT_WORLD,\n  M_QUICKLOAD,\n  M_ABOUT,\n  M_MAX_OPTS\n};\n\nconst char *main_menu_labels[] =\n{\n  [M_RETURN]        = \"Enter- Close menu\",\n  [M_EXIT]          = \"Esc-   Exit MegaZeux\",\n  [M_HELP]          = \"F1/H - Help\",\n  [M_SETTINGS]      = \"F2/S - Settings\",\n  [M_LOAD_WORLD]    = \"F3/L - Load world\",\n  [M_RESTORE_SAVE]  = \"F4/R - Restore game\",\n  [M_PLAY_WORLD]    = \"F5/P - Play world\",\n  [M_UPDATER]       = \"F7/U - Updater\",\n  [M_NEW_WORLD]     = \"F8/N - New world\",\n  [M_EDIT_WORLD]    = \"F9/E - Edit world\",\n  [M_QUICKLOAD]     = \"F10  - Quickload\",\n  [M_ABOUT]         = \"-= About MegaZeux =-\",\n};\n\n// Keys to pass to the parent key handler, or 0 if none.\nconst enum keycode main_menu_keys[] =\n{\n  [M_RETURN]        = 0,\n  [M_EXIT]          = IKEY_ESCAPE,\n  [M_HELP]          = IKEY_F1,\n  [M_SETTINGS]      = IKEY_F2,\n  [M_LOAD_WORLD]    = IKEY_F3,\n  [M_RESTORE_SAVE]  = IKEY_F4,\n  [M_PLAY_WORLD]    = IKEY_F5,\n  [M_UPDATER]       = IKEY_F7,\n  [M_NEW_WORLD]     = IKEY_F8,\n  [M_EDIT_WORLD]    = IKEY_F9,\n  [M_QUICKLOAD]     = IKEY_F10,\n  [M_ABOUT]         = 0,\n};\n\nstatic const char game_menu_title[] = \"Game Menu\";\n\nenum game_menu_opts\n{\n  G_NONE,\n  G_RETURN,\n  G_EXIT,\n  G_HELP,\n  G_SETTINGS,\n  G_SAVE,\n  G_LOAD,\n  G_TOGGLE_BOMBS,\n  G_QUICKSAVE,\n  G_QUICKLOAD,\n  G_MOVE,\n  G_SHOOT,\n  G_BOMB,\n  G_BLANK,\n  G_DEBUG_WINDOW,\n  G_DEBUG_VARIABLES,\n  G_DEBUG_ROBOTS,\n  G_MAX_OPTS\n};\n\nconst char *game_menu_labels[] =\n{\n  [G_RETURN]          = \"Enter  - Return to game\",\n  [G_EXIT]            = \"Esc    - Exit game\",\n  [G_HELP]            = \"F1     - Help\",\n  [G_SETTINGS]        = \"F2     - Settings\",\n  [G_SAVE]            = \"F3     - Save\",\n  [G_LOAD]            = \"F4     - Load\",\n  [G_TOGGLE_BOMBS]    = \"F5/Ins - Toggle bomb type\",\n  [G_QUICKSAVE]       = \"F9     - Quicksave\",\n  [G_QUICKLOAD]       = \"F10    - Quickload\",\n  [G_MOVE]            = \"Arrows - Move\",\n  [G_SHOOT]           = \"Space  - Shoot\",\n  [G_BOMB]            = \"Delete - Bomb\",\n  [G_BLANK]           = \"\",\n  [G_DEBUG_WINDOW]    = \"F6     - Debug Window\",\n  [G_DEBUG_VARIABLES] = \"F11    - Debug Variables\",\n  [G_DEBUG_ROBOTS]    = \"Alt+F11- Debug Robots\",\n};\n\n// Keys to pass to the parent key handler, or 0 if none.\nconst enum keycode game_menu_keys[] =\n{\n  [G_RETURN]          = 0,\n  [G_EXIT]            = IKEY_ESCAPE,\n  [G_HELP]            = IKEY_F1,\n  [G_SETTINGS]        = IKEY_F2,\n  [G_SAVE]            = IKEY_F3,\n  [G_LOAD]            = IKEY_F4,\n  [G_TOGGLE_BOMBS]    = IKEY_F5,\n  [G_QUICKSAVE]       = IKEY_F9,\n  [G_QUICKLOAD]       = IKEY_F10,\n  [G_MOVE]            = 0,\n  [G_SHOOT]           = 0,\n  [G_BOMB]            = 0,\n  [G_BLANK]           = 0,\n  [G_DEBUG_WINDOW]    = IKEY_F6,\n  [G_DEBUG_VARIABLES] = IKEY_F11,\n  [G_DEBUG_ROBOTS]    = IKEY_F11,\n};\n\nstatic enum main_menu_opts main_menu_last_selected = M_NONE;\nstatic enum game_menu_opts game_menu_last_selected = G_NONE;\n\nstruct menu_opt\n{\n  const char *label;\n  enum keycode return_key;\n  int which;\n  boolean is_selectable;\n};\n\nstruct game_menu_context\n{\n  context ctx;\n  boolean *return_alt;\n  enum keycode *return_key;\n  const char *title;\n  struct menu_opt options[MAX((int)M_MAX_OPTS, (int)G_MAX_OPTS)];\n  int num_options;\n  int x;\n  int y;\n  int width;\n  int height;\n  int current_option;\n  int last_opened_opt_type;\n  boolean is_game_menu;\n  boolean show_status;\n};\n\n/**\n * Is the exit menu currently allowed?\n */\nboolean allow_exit_menu(struct world *mzx_world, boolean is_titlescreen)\n{\n  // ESCAPE_MENU (2.90+) only works on the titlescreen if standalone_mode is\n  // enabled.\n  if(is_titlescreen && !get_config()->standalone_mode)\n    return true;\n\n  if(mzx_world->version < V290 || get_counter(mzx_world, \"ESCAPE_MENU\", 0))\n    return true;\n\n  return false;\n}\n\n/**\n * Are the main or game menus currently allowed?\n */\nboolean allow_enter_menu(struct world *mzx_world, boolean is_titlescreen)\n{\n  // ENTER_MENU only works on the titlescreen if standalone_mode is enabled.\n  if(is_titlescreen && !get_config()->standalone_mode)\n    return true;\n\n  if(mzx_world->version < V260 || get_counter(mzx_world, \"ENTER_MENU\", 0))\n    return true;\n\n  return false;\n}\n\n/**\n * Is the help system currently allowed by the running world?\n * Additional restrictions also apply to when this can open in the main loop.\n */\nboolean allow_help_system(struct world *mzx_world, boolean is_titlescreen)\n{\n  // If the help file doesn't exist, don't allow this...\n  if(!mzx_world->help_file)\n    return false;\n\n  // HELP_MENU only works on the titlescreen if standalone_mode is enabled.\n  if(is_titlescreen && !get_config()->standalone_mode)\n    return true;\n\n  if(mzx_world->version < V260 || get_counter(mzx_world, \"HELP_MENU\", 0))\n    return true;\n\n  return false;\n}\n\n/**\n * Is the settings menu currently allowed?\n * Additional restrictions also apply to when this can open in the main loop.\n */\nboolean allow_settings_menu(struct world *mzx_world, boolean is_titlescreen,\n boolean is_override)\n{\n  // F2_MENU only works on the titlescreen if standalone_mode is enabled.\n  // Also, if certain modifiers are pressed the F2_MENU counter is ignored\n  // altogether outside of standalone_mode.\n  if((is_override || is_titlescreen) && !get_config()->standalone_mode)\n    return true;\n\n  if(mzx_world->version < V260 || get_counter(mzx_world, \"F2_MENU\", 0))\n    return true;\n\n  return false;\n}\n\n/**\n * Is the load world menu on the titlescreen currently allowed?\n */\nboolean allow_load_world_menu(struct world *mzx_world)\n{\n  if(!get_config()->standalone_mode)\n    return true;\n\n  return false;\n}\n\n/**\n * Are the save menu and quicksave currently allowed?\n */\nboolean allow_save_menu(struct world *mzx_world)\n{\n  // The save enabled settings aren't actually available on the titlescreen...\n  if(!mzx_world->dead && player_can_save(mzx_world))\n    return true;\n\n  return false;\n}\n\n/**\n * Are the load menu and quickload currently allowed?\n */\nboolean allow_load_menu(struct world *mzx_world, boolean is_titlescreen)\n{\n  // LOAD_MENU only works on the titlescreen if standalone_mode is enabled.\n  if(is_titlescreen && !get_config()->standalone_mode)\n    return true;\n\n  if(mzx_world->version < V282 || get_counter(mzx_world, \"LOAD_MENU\", 0))\n    return true;\n\n  return false;\n}\n\n/**\n * Is player movement currently allowed?\n * This only affects the shortcut display for movement.\n */\nstatic boolean allow_movement(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n\n  if(mzx_world->active && !mzx_world->dead && cur_board &&\n   !(cur_board->player_ns_locked && cur_board->player_ew_locked))\n    return true;\n\n  return false;\n}\n\n/**\n * Is attacking and bomb switching allowed from the menu allowed?\n * This only affects the shortcut display for attacking and bomb switching\n * from the game menu.\n */\nstatic boolean allow_attacking(struct world *mzx_world)\n{\n  if(mzx_world->active && !mzx_world->dead && mzx_world->current_board &&\n   !(mzx_world->current_board->player_attack_locked))\n    return true;\n\n  return false;\n}\n\n/**\n * Are the debug menus currently allowed?\n */\nboolean allow_debug_menu(struct world *mzx_world)\n{\n  // The only thing that can enable these is the editing flag.\n  // Note these also check to see if their hooks are available individually.\n  if(mzx_world->editing)\n    return true;\n\n  return false;\n}\n\nstatic void set_main_menu_opt(struct menu_opt *opt, enum main_menu_opts which)\n{\n  opt->label = main_menu_labels[which];\n  opt->return_key = main_menu_keys[which];\n  opt->which = which;\n  opt->is_selectable = true;\n}\n\n/**\n * Build the main menu. The menu passed here should be at least M_MAX_OPTS long.\n */\nstatic void construct_main_menu(struct world *mzx_world, struct menu_opt *menu,\n int *_menu_length)\n{\n  boolean show_load_options;\n  int menu_length = 0;\n\n  set_main_menu_opt(menu++, M_RETURN);\n  menu_length++;\n\n  set_main_menu_opt(menu++, M_EXIT);\n  menu_length++;\n\n  if(allow_help_system(mzx_world, true))\n  {\n    set_main_menu_opt(menu++, M_HELP);\n    menu_length++;\n  }\n\n  if(allow_settings_menu(mzx_world, true, true))\n  {\n    set_main_menu_opt(menu++, M_SETTINGS);\n    menu_length++;\n  }\n\n  if(allow_load_world_menu(mzx_world))\n  {\n    set_main_menu_opt(menu++, M_LOAD_WORLD);\n    menu_length++;\n  }\n\n  show_load_options = allow_load_menu(mzx_world, true);\n  if(show_load_options)\n  {\n    set_main_menu_opt(menu++, M_RESTORE_SAVE);\n    menu_length++;\n  }\n\n  set_main_menu_opt(menu++, M_PLAY_WORLD);\n  menu_length++;\n\n  if(check_for_updates)\n  {\n    set_main_menu_opt(menu++, M_UPDATER);\n    menu_length++;\n  }\n\n  if(edit_world)\n  {\n    set_main_menu_opt(menu++, M_NEW_WORLD);\n    set_main_menu_opt(menu++, M_EDIT_WORLD);\n    menu_length += 2;\n  }\n\n  if(show_load_options)\n  {\n    set_main_menu_opt(menu++, M_QUICKLOAD);\n    menu_length++;\n  }\n\n  set_main_menu_opt(menu++, M_ABOUT);\n  menu_length++;\n\n  *_menu_length = menu_length;\n}\n\nstatic void set_game_menu_opt(struct menu_opt *opt, enum game_menu_opts which,\n boolean is_selectable)\n{\n  opt->label = game_menu_labels[which];\n  opt->return_key = game_menu_keys[which];\n  opt->which = which;\n  opt->is_selectable = is_selectable;\n}\n\n/**\n * Build the game menu. The menu passed here should be at least G_MAX_OPTS long.\n */\nstatic void construct_game_menu(struct world *mzx_world, struct menu_opt *menu,\n int *_menu_length)\n{\n  boolean show_save_options = allow_save_menu(mzx_world);\n  boolean show_load_options = allow_load_menu(mzx_world, false);\n  int menu_length = 0;\n\n  set_game_menu_opt(menu++, G_RETURN, true);\n  menu_length++;\n\n  set_game_menu_opt(menu++, G_EXIT, true);\n  menu_length++;\n\n  if(allow_help_system(mzx_world, false))\n  {\n    set_game_menu_opt(menu++, G_HELP, true);\n    menu_length++;\n  }\n\n  if(allow_settings_menu(mzx_world, false, true))\n  {\n    set_game_menu_opt(menu++, G_SETTINGS, true);\n    menu_length++;\n  }\n\n  if(show_save_options)\n  {\n    set_game_menu_opt(menu++, G_SAVE, true);\n    menu_length++;\n  }\n\n  if(show_load_options)\n  {\n    set_game_menu_opt(menu++, G_LOAD, true);\n    menu_length++;\n  }\n\n  if(allow_attacking(mzx_world))\n  {\n    set_game_menu_opt(menu++, G_TOGGLE_BOMBS, true);\n    menu_length++;\n  }\n\n  if(show_save_options)\n  {\n    set_game_menu_opt(menu++, G_QUICKSAVE, true);\n    menu_length++;\n  }\n\n  if(show_load_options)\n  {\n    set_game_menu_opt(menu++, G_QUICKLOAD, true);\n    menu_length++;\n  }\n\n  if(allow_movement(mzx_world))\n  {\n    set_game_menu_opt(menu++, G_MOVE, false);\n    menu_length++;\n  }\n\n  if(allow_attacking(mzx_world))\n  {\n    set_game_menu_opt(menu++, G_SHOOT, false);\n    set_game_menu_opt(menu++, G_BOMB, false);\n    menu_length += 2;\n  }\n\n  if(allow_debug_menu(mzx_world))\n  {\n    set_game_menu_opt(menu++, G_BLANK, false);\n    set_game_menu_opt(menu++, G_DEBUG_WINDOW, true);\n    set_game_menu_opt(menu++, G_DEBUG_VARIABLES, true);\n    set_game_menu_opt(menu++, G_DEBUG_ROBOTS, true);\n    menu_length += 4;\n  }\n\n  *_menu_length = menu_length;\n}\n\n/**\n * Draw a menu border and title to the screen.\n */\nstatic void draw_menu_box(int x, int y, int width, int height,\n const char *title)\n{\n  draw_window_box(x, y, width + x - 1, height + y - 1,\n   MENU_COL, MENU_COL_DARK, MENU_COL_CORNER, true, true);\n\n  if(title)\n  {\n    int title_width = strlen(title);\n    int title_x = (width - title_width)/2 + x;\n\n    write_string(title, title_x, y, MENU_COL_TITLE, WR_NONE);\n    draw_char(' ', MENU_COL_TITLE, title_x - 1, y);\n    draw_char(' ', MENU_COL_TITLE, title_x + title_width, y);\n  }\n}\n\n/**\n * Write a single counter to the status screen.\n */\n\nstatic void show_counter(struct world *mzx_world, const char *counter_name,\n int x, int y, boolean skip_if_zero)\n{\n  int counter_value = get_counter(mzx_world, counter_name, 0);\n\n  if((skip_if_zero) && (!counter_value))\n    return;\n\n  write_string(counter_name, x, y, MENU_COL_COUNTER, false);\n  write_number(counter_value, MENU_COL_TEXT, x + GAME_STATUS_INDENT, y,\n   1, false, 10);\n}\n\n/**\n * Draw the game status window. This window displays information to the player\n * such as built-in counters, keys, custom status counters, and active effects.\n */\nstatic void draw_game_status(struct world *mzx_world)\n{\n  char *keys = mzx_world->keys;\n  int x;\n  int y;\n  int i;\n\n  draw_menu_box(\n   GAME_STATUS_X, GAME_STATUS_Y, GAME_STATUS_WIDTH, GAME_STATUS_HEIGHT,\n   NULL\n  );\n\n  x = GAME_STATUS_X + 2;\n  y = GAME_STATUS_Y + 1;\n\n  show_counter(mzx_world, \"Gems\",     x, y++, false);\n  show_counter(mzx_world, \"Ammo\",     x, y++, false);\n  show_counter(mzx_world, \"Health\",   x, y++, false);\n  show_counter(mzx_world, \"Lives\",    x, y++, false);\n  show_counter(mzx_world, \"Lobombs\",  x, y++, false);\n  show_counter(mzx_world, \"Hibombs\",  x, y++, false);\n  show_counter(mzx_world, \"Coins\",    x, y++, false);\n  show_counter(mzx_world, \"Score\",    x, y++, false);\n\n  // Bomb selection\n  write_string(\"(cur.)\", (x + 9), y - (mzx_world->bomb_type ? 3 : 4),\n   MENU_COL, false);\n\n  // Keys\n  write_string(\"Keys\", x, y, MENU_COL_COUNTER, false);\n\n  for(i = 0; i < 8; i++)\n  {\n    if(keys[i] != NO_KEY)\n      draw_char(KEY_CHAR, 0x10 + keys[i], (x + i + GAME_STATUS_INDENT), y);\n\n    if(keys[i+8] != NO_KEY)\n      draw_char(KEY_CHAR, 0x10 + keys[i+8], (x + i + GAME_STATUS_INDENT), y+1);\n  }\n\n  y += 2;\n\n  // Custom status counters\n  for(i = 0; i < NUM_STATUS_COUNTERS; i++)\n  {\n    char *name = mzx_world->status_counters_shown[i];\n    if(name[0])\n      show_counter(mzx_world, name, x, y, true);\n\n    y++;\n  }\n\n  // Active effects\n  y = GAME_STATUS_Y + GAME_STATUS_HEIGHT - 1;\n\n  if(mzx_world->firewalker_dur > 0)\n    write_string(\"-W-\", x+4, y, 0x1C, false);\n\n  if(mzx_world->freeze_time_dur > 0)\n    write_string(\"-F-\", x+8, y, 0x1B, false);\n\n  if(mzx_world->slow_time_dur > 0)\n    write_string(\"-S-\", x+12, y, 0x1E, false);\n\n  if(mzx_world->wind_dur > 0)\n    write_string(\"-W-\", x+16, y, 0x1F, false);\n\n  if(mzx_world->blind_dur > 0)\n    write_string(\"-B-\", x+20, y, 0x19, false);\n}\n\n/**\n * Draw the main or game menu.\n */\nstatic boolean menu_draw(context *ctx)\n{\n  struct game_menu_context *game_menu = (struct game_menu_context *)ctx;\n  unsigned int color;\n  int x;\n  int y;\n  int i;\n\n  draw_menu_box(\n    game_menu->x, game_menu->y, game_menu->width, game_menu->height,\n    game_menu->title\n  );\n\n  x = game_menu->x + 2;\n  y = game_menu->y + 1;\n\n  for(i = 0; i < game_menu->num_options; i++)\n  {\n    color = MENU_COL_TEXT;\n    if(game_menu->current_option >= 0)\n      if(!game_menu->options[i].is_selectable)\n        color = MENU_COL_NO_SELECT;\n\n    write_string(game_menu->options[i].label, x, y, color, false);\n    y++;\n  }\n\n  // Selected option\n  i = game_menu->current_option;\n  if(i >= 0 && i < game_menu->num_options)\n  {\n    x = game_menu->x + 1;\n    y = + game_menu->y + 1 + i;\n    color_line(game_menu->width - 2, x, y, MENU_COL_SELECTED);\n    cursor_hint(x + 1, y);\n    //write_string(game_menu->options[i].label, x, y, MENU_COL_SELECTED, false);\n\n    x = game_menu->x + 1;\n    //draw_char(' ', MENU_COL_SELECTED, x, y);\n\n    x = game_menu->width + game_menu->x - 1;\n    //draw_char(' ', MENU_COL_SELECTED, x, y);\n  }\n\n  if(game_menu->show_status)\n    draw_game_status(ctx->world);\n\n  return true;\n}\n\nstatic void focus_status(void)\n{\n  int fx = (GAME_STATUS_WIDTH / 2) + GAME_STATUS_X;\n  int fy = (GAME_STATUS_HEIGHT / 2) + GAME_STATUS_Y;\n  focus_screen(fx, fy);\n}\n\nstatic void focus_menu(struct game_menu_context *game_menu)\n{\n  int fx = (game_menu->width / 2) + game_menu->x;\n  int fy = (game_menu->height / 2) + game_menu->y;\n\n  if(game_menu->current_option >= 0)\n  {\n    fy += game_menu->current_option - (game_menu->num_options / 2);\n  }\n  else\n\n  if(game_menu->show_status)\n  {\n    focus_status();\n    return;\n  }\n\n  focus_screen(fx, fy);\n}\n\n/**\n * Find the last selected option in the menu, if it exists. Otherwise, this\n * selects the first option.\n */\nstatic int find_last_option(struct game_menu_context *game_menu, int which)\n{\n  struct menu_opt *current;\n  int i;\n\n  for(i = 0; i < game_menu->num_options; i++)\n  {\n    current = &(game_menu->options[i]);\n\n    if(current->is_selectable && current->which == which)\n      return i;\n  }\n  return 0;\n}\n\n/**\n * Select the previous menu option.\n */\nstatic void menu_select_prev(struct game_menu_context *game_menu)\n{\n  int i = 0;\n\n  if(game_menu->current_option >= 0)\n  {\n    int current = game_menu->current_option;\n    i = current;\n\n    do\n    {\n      i--;\n      if(i < 0)\n        i = game_menu->num_options - 1;\n\n      if(game_menu->options[i].is_selectable)\n        break;\n    }\n    while(i != current);\n\n    game_menu->current_option = i;\n  }\n  else\n    game_menu->current_option =\n     find_last_option(game_menu, game_menu->last_opened_opt_type);\n\n  focus_menu(game_menu);\n}\n\n/**\n * Select the next menu option.\n */\nstatic void menu_select_next(struct game_menu_context *game_menu)\n{\n  int i = 0;\n\n  if(game_menu->current_option >= 0)\n  {\n    int current = game_menu->current_option;\n    i = current;\n\n    do\n    {\n      i++;\n      if(i >= game_menu->num_options)\n        i = 0;\n\n      if(game_menu->options[i].is_selectable)\n        break;\n    }\n    while(i != current);\n\n    game_menu->current_option = i;\n  }\n  else\n    game_menu->current_option =\n     find_last_option(game_menu, game_menu->last_opened_opt_type);\n\n  focus_menu(game_menu);\n}\n\n/**\n * Set the return key for the parent.\n */\nstatic void set_return_key(struct game_menu_context *game_menu,\n enum keycode return_key, boolean return_alt)\n{\n  if(game_menu->return_alt)\n    *(game_menu->return_alt) = return_alt;\n\n  if(game_menu->return_key)\n    *(game_menu->return_key) = return_key;\n}\n\n/**\n * Activate the main menu. Return true if the menu should exit.\n */\nstatic boolean main_menu_activate(struct game_menu_context *game_menu, int *key)\n{\n  int current = game_menu->current_option;\n  enum main_menu_opts which;\n  enum keycode return_key;\n\n  if(current < 0 || current >= game_menu->num_options)\n    return false;\n\n  which = (enum main_menu_opts)game_menu->options[current].which;\n  return_key = game_menu->options[current].return_key;\n\n  switch(which)\n  {\n    case M_HELP:\n    case M_SETTINGS:\n    {\n      // These go to the main loop instead.\n      *key = return_key;\n      return false;\n    }\n\n    case M_ABOUT:\n    {\n      // Special: display \"About MegaZeux\".\n      about_megazeux(&game_menu->ctx);\n      return true;\n    }\n\n    case M_EXIT:\n    {\n      // Special: if the exit menu isn't available, set the exit status.\n      if(!allow_exit_menu(game_menu->ctx.world, false))\n        set_exit_status(true);\n    }\n\n    /* fall-through */\n\n    default:\n    {\n      set_return_key(game_menu, return_key, false);\n      return true;\n    }\n  }\n}\n\n/**\n * Activate the game menu. Return true if the menu should exit.\n */\nstatic boolean game_menu_activate(struct game_menu_context *game_menu, int *key)\n{\n  struct world *mzx_world = game_menu->ctx.world;\n  int current = game_menu->current_option;\n  enum game_menu_opts which;\n  enum keycode return_key;\n  boolean return_alt = false;\n\n  if(current < 0 || current >= game_menu->num_options)\n    return false;\n\n  which = (enum game_menu_opts)game_menu->options[current].which;\n  return_key = game_menu->options[current].return_key;\n\n  switch(which)\n  {\n    case G_HELP:\n    {\n      // This always go to the main loop instead.\n      *key = return_key;\n      return false;\n    }\n\n    case G_SETTINGS:\n    {\n      // TODO: right now this can't really tell the main loop to force an\n      // override so whatever, just open it right here\n      game_settings(mzx_world);\n      return false;\n    }\n\n    case G_EXIT:\n    {\n      if(!allow_exit_menu(mzx_world, false))\n        set_exit_status(true);\n\n      set_return_key(game_menu, return_key, return_alt);\n      return true;\n    }\n\n    case G_TOGGLE_BOMBS:\n    {\n      // Pretty much no point in this one closing the menu...\n      if(!mzx_world->dead)\n        player_switch_bomb_type(mzx_world);\n\n      return false;\n    }\n\n    case G_DEBUG_ROBOTS:\n    {\n      return_alt = true;\n    }\n\n    /* fall-through */\n\n    default:\n    {\n      set_return_key(game_menu, return_key, return_alt);\n      return true;\n    }\n  }\n}\n\n/**\n * Activate the current menu. Return true if the menu should exit.\n */\nstatic boolean menu_activate(struct game_menu_context *game_menu, int *key)\n{\n  if(game_menu->is_game_menu)\n    return game_menu_activate(game_menu, key);\n  else\n    return main_menu_activate(game_menu, key);\n}\n\n/**\n * Joystick input for the game menu. Use the default UI joystick mapping except\n * with an exception: JOY_LSHOULDER should open F2 since it will be available\n * more often than JOY_RSHOULDER on consoles.\n */\nstatic boolean menu_joystick(context *ctx, int *key, int action)\n{\n  int default_key = get_joystick_ui_key();\n\n  switch(action)\n  {\n    case JOY_START:\n    case JOY_X:\n    case JOY_Y:\n    {\n      // Since start opens the menu, it should also close it instead of\n      // selecting things. Also let X and Y close the menu, just because.\n      *key = IKEY_ESCAPE;\n      return true;\n    }\n\n    case JOY_LSHOULDER:\n    {\n      *key = IKEY_F2;\n      return true;\n    }\n  }\n\n  if(default_key)\n  {\n    *key = default_key;\n    return true;\n  }\n  return false;\n}\n\n/**\n * Close the menu on an exit event or an return/escape press.\n * NOTE: In DOS versions of MZX, clicking the menus would close the\n * menu and trigger the clicked function. This feature was removed in\n * the port and has not yet been restored.\n */\nstatic boolean menu_key(context *ctx, int *key)\n{\n  struct game_menu_context *game_menu = (struct game_menu_context *)ctx;\n  struct world *mzx_world = ctx->world;\n  boolean activate_option = false;\n  boolean exit_menu = false;\n\n  if(get_exit_status())\n    *key = IKEY_ESCAPE;\n\n  switch(*key)\n  {\n    case IKEY_UP:\n    case IKEY_LEFT:\n    {\n      // Previous option\n      menu_select_prev(game_menu);\n      return true;\n    }\n\n    case IKEY_DOWN:\n    case IKEY_RIGHT:\n    {\n      // Next option\n      menu_select_next(game_menu);\n      return true;\n    }\n\n    case IKEY_HOME:\n    case IKEY_PAGEUP:\n    {\n      // First option\n      game_menu->current_option = 0;\n      return true;\n    }\n\n    case IKEY_END:\n    case IKEY_PAGEDOWN:\n    {\n      // Last option\n      game_menu->current_option = 0;\n      menu_select_prev(game_menu);\n      return true;\n    }\n\n    case IKEY_SPACE:\n    {\n      // Activate the current option if there is a current option, otherwise\n      // do nothing.\n      if(game_menu->current_option >= 0)\n        activate_option = true;\n\n      break;\n    }\n\n    case IKEY_RETURN:\n    {\n      // Activate the current option if there is a current option, otherwise\n      // exit the menu.\n      if(game_menu->current_option >= 0)\n      {\n        activate_option = true;\n      }\n      else\n        exit_menu = true;\n\n      break;\n    }\n\n    case IKEY_ESCAPE:\n    {\n      // Exit the menu.\n      exit_menu = true;\n      break;\n    }\n\n    case IKEY_INSERT:\n    case IKEY_F5:\n    {\n      if(game_menu->is_game_menu)\n      {\n        // Pretty much no point in closing the menu for this one...\n        if(allow_attacking(mzx_world))\n          player_switch_bomb_type(mzx_world);\n\n        return true;\n      }\n    }\n\n    /* fall-through */\n\n    case IKEY_F3:\n    case IKEY_F4:\n    case IKEY_F6:\n    case IKEY_F7:\n    case IKEY_F8:\n    case IKEY_F9:\n    case IKEY_F10:\n    case IKEY_F11:\n    {\n      // Return the pressed key...\n      set_return_key(game_menu, *key, get_alt_status(keycode_internal));\n      exit_menu = true;\n      break;\n    }\n  }\n\n  if(activate_option)\n  {\n    if(menu_activate(game_menu, key))\n    {\n      destroy_context(ctx);\n      return true;\n    }\n  }\n\n  if(exit_menu)\n  {\n    destroy_context(ctx);\n    return true;\n  }\n  return false;\n}\n\n/**\n * Handle menu clicks.\n */\nstatic boolean menu_click(context *ctx, int *key, int button, int x, int y)\n{\n  struct game_menu_context *game_menu = (struct game_menu_context *)ctx;\n\n  if((x >= game_menu->x + 1) && (x < game_menu->x + game_menu->width - 1) &&\n   (y >= game_menu->y + 1) && (y < game_menu->y + game_menu->height - 1))\n  {\n    int selected = y - game_menu->y - 1;\n\n    if(selected < game_menu->num_options &&\n     game_menu->options[selected].is_selectable)\n    {\n      if(selected == game_menu->current_option)\n      {\n        if(menu_activate(game_menu, key))\n          destroy_context(ctx);\n      }\n      else\n      {\n        game_menu->current_option = selected;\n        focus_menu(game_menu);\n      }\n    }\n    return true;\n  }\n  return false;\n}\n\n/**\n * Block mouse drag activation...\n */\nstatic boolean menu_drag(context *ctx, int *key, int button, int x, int y)\n{\n  return false;\n}\n\n/**\n * Restore the screen when the menu is closed and save the last option.\n */\nstatic void menu_destroy(context *ctx)\n{\n  struct game_menu_context *game_menu = (struct game_menu_context *)ctx;\n  int current = game_menu->current_option;\n  int which;\n\n  if(current >= 0)\n  {\n    which = game_menu->options[current].which;\n\n    if(game_menu->is_game_menu)\n      game_menu_last_selected = (enum game_menu_opts)which;\n    else\n      main_menu_last_selected = (enum main_menu_opts)which;\n  }\n  cursor_off();\n  restore_screen();\n}\n\n/**\n * Create and run the game menu context for gameplay. This menu also displays\n * a status screen with the player's current stats.\n */\nvoid game_menu(context *parent, boolean start_selected, enum keycode *retval,\n boolean *retval_alt)\n{\n  struct game_menu_context *game_menu =\n   cmalloc(sizeof(struct game_menu_context));\n  struct context_spec spec;\n\n  save_screen();\n  m_show();\n\n  construct_game_menu(parent->world,\n   game_menu->options, &game_menu->num_options);\n\n  game_menu->title = game_menu_title;\n  game_menu->x = GAME_MENU_X;\n  game_menu->y = GAME_MENU_Y;\n  game_menu->width = GAME_MENU_WIDTH;\n  game_menu->height = game_menu->num_options + 2;\n  game_menu->current_option = -1;\n  game_menu->last_opened_opt_type = game_menu_last_selected;\n  game_menu->is_game_menu = true;\n  game_menu->show_status = allow_enter_menu(parent->world, false);\n  game_menu->return_key = retval;\n  game_menu->return_alt = retval_alt;\n\n  if(start_selected)\n  {\n    game_menu->current_option =\n     find_last_option(game_menu, game_menu_last_selected);\n  }\n\n  if(!game_menu->show_status)\n  {\n    // Center menu...\n    game_menu->x = (SCREEN_W - GAME_MENU_WIDTH) / 2;\n  }\n\n  focus_menu(game_menu);\n  if(retval)\n    *retval = 0;\n  if(retval_alt)\n    *retval_alt = false;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw     = menu_draw;\n  spec.key      = menu_key;\n  spec.joystick = menu_joystick;\n  spec.click    = menu_click;\n  spec.drag     = menu_drag;\n  spec.destroy  = menu_destroy;\n\n  create_context((context *)game_menu, parent, &spec, CTX_GAME_MENU);\n}\n\n/**\n * Create and run the main menu context for the title screen.\n */\nvoid main_menu(context *parent, boolean start_selected, enum keycode *retval)\n{\n  struct game_menu_context *game_menu =\n   cmalloc(sizeof(struct game_menu_context));\n  struct context_spec spec;\n\n  save_screen();\n  m_show();\n\n  construct_main_menu(parent->world,\n   game_menu->options, &game_menu->num_options);\n\n  game_menu->title = main_menu_title;\n  game_menu->x = MAIN_MENU_X;\n  game_menu->y = MAIN_MENU_Y;\n  game_menu->width = MAIN_MENU_WIDTH;\n  game_menu->height = game_menu->num_options + 2;\n  game_menu->current_option = -1;\n  game_menu->last_opened_opt_type = main_menu_last_selected;\n  game_menu->is_game_menu = false;\n  game_menu->show_status = false;\n  game_menu->return_key = retval;\n  game_menu->return_alt = NULL;\n\n  if(start_selected)\n  {\n    game_menu->current_option =\n     find_last_option(game_menu, main_menu_last_selected);\n  }\n\n  focus_menu(game_menu);\n  if(retval)\n    *retval = 0;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw     = menu_draw;\n  spec.key      = menu_key;\n  spec.joystick = menu_joystick;\n  spec.click    = menu_click;\n  spec.drag     = menu_drag;\n  spec.destroy  = menu_destroy;\n\n  create_context((context *)game_menu, parent, &spec, CTX_MAIN_MENU);\n}\n"
  },
  {
    "path": "src/game_menu.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018-2019 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __GAME_MENU_H\n#define __GAME_MENU_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"core.h\"\n#include \"keysym.h\"\n\nboolean allow_exit_menu(struct world *mzx_world, boolean is_titlescreen);\nboolean allow_enter_menu(struct world *mzx_world, boolean is_titlescreen);\nboolean allow_help_system(struct world *mzx_world, boolean is_titlescreen);\nboolean allow_settings_menu(struct world *mzx_world, boolean is_titlescreen,\n boolean is_override);\nboolean allow_load_world_menu(struct world *mzx_world);\nboolean allow_save_menu(struct world *mzx_world);\nboolean allow_load_menu(struct world *mzx_world, boolean is_titlescreen);\nboolean allow_debug_menu(struct world *mzx_world);\n\nvoid game_menu(context *parent, boolean start_selected, enum keycode *retval,\n boolean *retval_alt);\nvoid main_menu(context *parent, boolean start_selected, enum keycode *retval);\n\n__M_END_DECLS\n\n#endif // __GAME_MENU_H\n"
  },
  {
    "path": "src/game_ops.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"const.h\"\n#include \"counter.h\"\n#include \"game_ops.h\"\n#include \"game_player.h\"\n#include \"graphics.h\"\n#include \"idarray.h\"\n#include \"idput.h\"\n#include \"robot.h\"\n#include \"util.h\"\n#include \"window.h\"\n#include \"world_struct.h\"\n\n#include \"audio/sfx.h\"\n\n/**\n * Utility functions for gameplay.\n */\n\n/**\n * Update a robot's position after moving it. This was never done by older\n * versions, so don't update the robot's compatibility position values.\n */\nstatic void fix_robot_pos(struct board *cur_board, int id, int x, int y)\n{\n  struct robot *cur_robot = cur_board->robot_list[id];\n  cur_robot->xpos = x;\n  cur_robot->ypos = y;\n}\n\n/**\n * Update a robot's position after moving it. This was never done by older\n * versions, so don't update the robot's compatibility position values.\n */\nstatic void fix_robot_pos_offs(struct board *cur_board, int id, int offset)\n{\n  struct robot *cur_robot = cur_board->robot_list[id];\n  cur_robot->xpos = offset % cur_board->board_width;\n  cur_robot->ypos = offset / cur_board->board_width;\n}\n\n//Bit 1- +1\n//Bit 2- -1\n//Bit 4- +width\n//Bit 8- -width\nstatic const char cw_offs[8] = { 10, 2, 6, 4, 5, 1, 9, 8 };\nstatic const char ccw_offs[8] = { 10, 8, 9, 1, 5, 4, 6, 2 };\n\n// Rotate an area\nvoid rotate(struct world *mzx_world, int x, int y, int dir)\n{\n  struct board *src_board = mzx_world->current_board;\n  const char *offsp = cw_offs;\n  int offs[8];\n  int offset, i;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  int cw, ccw;\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n  char *level_color = src_board->level_color;\n  enum thing id;\n  char param, color;\n  enum thing cur_id;\n  char cur_param, cur_color;\n  int d_flag;\n  int cur_offset, next_offset;\n\n  offset = x + (y * board_width);\n  if((x == 0) || (y == 0) || (x == (board_width - 1)) ||\n   (y == (board_height - 1))) return;\n\n  if(dir)\n    offsp = ccw_offs;\n\n  // Fix offsets\n  for(i = 0; i < 8; i++)\n  {\n    int offsval = offsp[i];\n    if(offsval & 1)\n      offs[i] = 1;\n    else\n      offs[i] = 0;\n\n    if(offsval & 2)\n      offs[i]--;\n\n    if(offsval & 4)\n      offs[i] += board_width;\n\n    if(offsval & 8)\n      offs[i] -= board_width;\n  }\n\n  for(i = 0; i < 8; i++)\n  {\n    cur_id = (enum thing)level_id[offset + offs[i]];\n    if((flags[(int)cur_id] & A_UNDER) && (cur_id != GOOP))\n      break;\n  }\n\n  if(i == 8)\n  {\n    // No surrounding floors were found, so swap temporarily off of the board.\n    // For some reason this case doesn't track rotation state with update_done.\n    for(i = 0; i < 8; i++)\n    {\n      cur_id = (enum thing)level_id[offset + offs[i]];\n      d_flag = flags[(int)cur_id];\n\n      if(!((d_flag & A_PUSHABLE) || (d_flag & A_SPEC_PUSH)))\n      {\n        /**\n         * From 2.80X through 2.91X, this would also exit for anything with\n         * A_SPEC_PUSH. This regression was most likely introduced because\n         * of a comment about transports not being pushable. If something\n         * seriously relies on A_SPEC_PUSH objects not rotating in this edge\n         * case, add a compatibility check.\n         *\n         * This also originally checked for the GATE thing, but GATE never had\n         * either pushable flag set. MZX 1.xx appears to have blacklisted\n         * A_SPEC_PUSH and this may have been a (broken) attempt to prevent\n         * transports from rotating after that bug was fixed in 2.00.\n         */\n        break;\n      }\n    }\n\n    if(i == 8)\n    {\n      // All surrounding objects are pushable and can be rotated.\n      cur_offset = offset + offs[0];\n      id = (enum thing)level_id[cur_offset];\n      color = level_color[cur_offset];\n      param = level_param[cur_offset];\n\n      for(i = 0; i < 7; i++)\n      {\n        cur_offset = offset + offs[i];\n        next_offset = offset + offs[i + 1];\n        level_id[cur_offset] = level_id[next_offset];\n        level_color[cur_offset] = level_color[next_offset];\n        level_param[cur_offset] = level_param[next_offset];\n\n        if(level_id[cur_offset] == ROBOT_PUSHABLE)\n          fix_robot_pos_offs(src_board, level_param[cur_offset], cur_offset);\n      }\n\n      cur_offset = offset + offs[7];\n      level_id[cur_offset] = (char)id;\n      level_color[cur_offset] = color;\n      level_param[cur_offset] = param;\n\n      if(id == ROBOT_PUSHABLE)\n        fix_robot_pos_offs(src_board, param, cur_offset);\n    }\n  }\n  else\n  {\n    // A floor was found, so start the rotation from the floor.\n    cw = i - 1;\n\n    if(cw == -1)\n      cw = 7;\n\n    do\n    {\n      ccw = i + 1;\n      if(ccw == 8)\n        ccw = 0;\n\n      cur_offset = offset + offs[ccw];\n      next_offset = offset + offs[i];\n      cur_id = (enum thing)level_id[cur_offset];\n      d_flag = flags[(int)cur_id];\n\n      /**\n       * This originally checked for the GATE thing, but GATE never had\n       * either pushable flag set. MZX 1.xx appears to have blacklisted\n       * A_SPEC_PUSH and this may have been a (broken) attempt to prevent\n       * transports from rotating after that bug was fixed in 2.00.\n       */\n      if(((d_flag & A_PUSHABLE) || (d_flag & A_SPEC_PUSH)) &&\n       (!(mzx_world->update_done[cur_offset] & 2)))\n      {\n        cur_param = level_param[cur_offset];\n        cur_color = level_color[cur_offset];\n        offs_place_id(mzx_world, next_offset, cur_id, cur_color, cur_param);\n        offs_remove_id(mzx_world, cur_offset);\n        mzx_world->update_done[next_offset] |= 2;\n        i = ccw;\n\n        if(cur_id == ROBOT_PUSHABLE)\n          fix_robot_pos_offs(src_board, cur_param, next_offset);\n      }\n      else\n      {\n        i = ccw;\n        while(i != cw)\n        {\n          cur_id = (enum thing)level_id[offset + offs[i]];\n          if((flags[(int)cur_id] & A_UNDER) && (cur_id != GOOP))\n            break;\n\n          i++;\n          if(i == 8)\n            i = 0;\n        }\n      }\n    } while(i != cw);\n  }\n}\n\nvoid calculate_xytop(struct world *mzx_world, int *x, int *y)\n{\n  struct board *src_board = mzx_world->current_board;\n  int nx, ny;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  int viewport_width = src_board->viewport_width;\n  int viewport_height = src_board->viewport_height;\n  int locked_y = src_board->locked_y;\n\n  // Calculate xy top from player position and scroll view pos, or\n  // as static position if set.\n  if(locked_y != -1)\n  {\n    nx = src_board->locked_x + src_board->scroll_x;\n    ny = locked_y + src_board->scroll_y;\n  }\n  else\n  {\n    // Calculate from player position\n    // Center screen around player, add scroll factor\n    nx = mzx_world->player_x - (viewport_width / 2);\n    ny = mzx_world->player_y - (viewport_height / 2);\n\n    if(nx < 0)\n      nx = 0;\n\n    if(ny < 0)\n      ny = 0;\n\n    if(nx > (board_width - viewport_width))\n     nx = board_width - viewport_width;\n\n    if(ny > (board_height - viewport_height))\n     ny = board_height - viewport_height;\n\n    nx += src_board->scroll_x;\n    ny += src_board->scroll_y;\n  }\n  // Prevent from going offscreen\n  if(nx < 0)\n    nx = 0;\n\n  if(ny < 0)\n    ny = 0;\n\n  if(nx > (board_width - viewport_width))\n    nx = board_width - viewport_width;\n\n  if(ny > (board_height - viewport_height))\n    ny = board_height - viewport_height;\n\n  *x = nx;\n  *y = ny;\n}\n\n// Return the seek dir relative to the player.\n\nint find_seek(struct world *mzx_world, int x, int y)\n{\n  int dir;\n  int player_x = mzx_world->player_x;\n  int player_y = mzx_world->player_y;\n\n  if(y == player_y)\n  {\n    dir = 0;                // Go horizontally\n  }\n  else\n  if(x == player_x)\n  {\n    dir = 1;                // Go vertically\n  }\n  else\n  {\n    dir = Random(2);        // Go random horizontal or vertical\n  }\n\n  if(dir)\n  {\n    // Dir becomes up or down.\n    if(player_y < y)\n    {\n      return 0;\n    }\n    else\n    {\n      return 1;\n    }\n  }\n  else\n  {\n    // Dir becomes left or right.\n    if(player_x < x)\n    {\n      return 3;\n    }\n    else\n    {\n      return 2;\n    }\n  }\n}\n\nint flip_dir(int dir)\n{\n  dir ^= 1;                 // Toggle the lsb to change direction\n  return dir;\n}\n\n// Push/transport currently have no recursion protection. As far as\n// I'm aware, there's only so large you can make a push/transport chain\n// (at most around 400). This is probably too much for an MS-DOS stack,\n// but any decent 32bit OS should be able to tolerate it.\n\n// See if a transport can be made to that x/y position\n// This is an additional helper function to try to sanitize transport\n// Returns 1 if successful, 0 if not\n\nstatic int try_transport(struct world *mzx_world, int x, int y, int push_status,\n int can_push, enum thing id, int dir)\n{\n  struct board *src_board = mzx_world->current_board;\n  int d_offset = xy_to_offset(src_board, x, y);\n  enum thing d_id = (enum thing)src_board->level_id[d_offset];\n\n  // Not gonna be happening with goop or a transport\n  if((d_id != GOOP) && (d_id != TRANSPORT))\n  {\n    // Get flags for the ID\n    int d_flag = flags[d_id];\n\n    // Can it be moved under? It's a good destination then\n    if(d_flag & A_UNDER)\n      return 1;\n\n    // Is it pushable?\n    if(d_flag & (push_status | A_SPEC_PUSH))\n    {\n      // Player can move onto sensor.. sensor is not under\n      // but pushable.\n      if((d_id == SENSOR) && (id == PLAYER))\n      {\n        return 1;\n      }\n      else\n      {\n        // Okay, try pushing it.. if it can be pushed\n        if(can_push == 1)\n        {\n          int px = 0, py = 0;\n          xy_shift_dir(src_board, x, y, &px, &py, flip_dir(dir));\n          // Try to push it\n          if(push(mzx_world, px, py, dir, 0) == 0)\n            return 1;\n        }\n      }\n    }\n  }\n\n  return 0;\n}\n\nint transport(struct world *mzx_world, int x, int y, int dir, enum thing id,\n int param, int color, int can_push)\n{\n  struct board *src_board = mzx_world->current_board;\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n  // East/west or north/south\n  int push_status = A_PUSHEW;\n  int offset = xy_to_offset(src_board, x, y);\n  int dx, dy, d_offset;\n  char t_param = level_param[offset];\n  int found = 0;\n\n  // Get the direction of the transporter\n  int t_dir = t_param & 0x07;\n\n  // The direction must either be the given one or anydir\n  if((t_dir != dir) && (t_dir != 4))\n    return 1;\n\n  if(dir < 2)\n  {\n    push_status = A_PUSHNS;\n  }\n\n  if(xy_shift_dir(src_board, x, y, &dx, &dy, dir))\n    return 1;\n\n  // See if the first one will work\n\n  found = try_transport(mzx_world, dx, dy, push_status,\n   can_push, id, dir);\n\n  // Until found or exited, loop on\n  while(!found)\n  {\n    // The next one must be a transporter\n    d_offset = xy_to_offset(src_board, dx, dy);\n    // Get next dir\n\n    if(xy_shift_dir(src_board, dx, dy, &dx, &dy, dir))\n      return 1;\n\n    if(level_id[d_offset] == TRANSPORT)\n    {\n      t_dir = level_param[d_offset] & 0x07;\n      if((t_dir == 4) || (t_dir == flip_dir(dir)))\n      {\n        // Try the new location, after the transport\n        found = try_transport(mzx_world, dx, dy,\n         push_status, can_push, id, dir);\n      }\n    }\n  }\n\n  // Okay, can put it at dx, dy.. if we're not just checking\n  if(id != SPACE)\n  {\n    // Place it\n    play_sfx(mzx_world, SFX_TRANSPORT);\n    id_place(mzx_world, dx, dy, id, color, param);\n\n    if(is_robot(id))\n      fix_robot_pos(src_board, param, dx, dy);\n  }\n\n  // Successful return..\n  return 0;\n}\n\nstatic void push_player_sensor(struct world *mzx_world, int p_offset,\n int d_offset, char param, char color)\n{\n  struct board *src_board = mzx_world->current_board;\n  char *level_under_id = src_board->level_under_id;\n  char *level_under_color = src_board->level_under_color;\n  char *level_under_param = src_board->level_under_param;\n\n  // Record the old sensor's details\n  color = src_board->level_color[p_offset];\n  param = src_board->level_param[p_offset];\n\n  // Move the bottom layer under the sensor to the top,\n  // eliminating the sensor\n  src_board->level_id[p_offset] = level_under_id[p_offset];\n  src_board->level_color[p_offset] = level_under_color[p_offset];\n  src_board->level_param[p_offset] = level_under_param[p_offset];\n\n  // Do we now have a sensor? If so, swap it with our current sensor\n  if (src_board->level_id[p_offset] == SENSOR)\n  {\n    char tmp_color = src_board->level_color[p_offset];\n    char tmp_param = src_board->level_param[p_offset];\n    src_board->level_color[p_offset] = color;\n    src_board->level_param[p_offset] = param;\n    color = tmp_color;\n    param = tmp_param;\n  }\n\n  // Restore the previous under with this stuff\n  level_under_id[p_offset] = mzx_world->under_player_id;\n  level_under_color[p_offset] = mzx_world->under_player_color;\n  level_under_param[p_offset] = mzx_world->under_player_param;\n\n  // Get new under stuff\n  mzx_world->under_player_id = level_under_id[d_offset];\n  mzx_world->under_player_color = level_under_color[d_offset];\n  mzx_world->under_player_param = level_under_param[d_offset];\n\n  // Move the sensor under the player\n  level_under_id[d_offset] = (char)SENSOR;\n  level_under_color[d_offset] = color;\n  level_under_param[d_offset] = param;\n\n  push_sensor(mzx_world, level_under_param[d_offset]);\n}\n\nint push(struct world *mzx_world, int x, int y, int dir, int checking)\n{\n  struct board *src_board = mzx_world->current_board;\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n  char *level_color = src_board->level_color;\n  char *level_under_id = src_board->level_under_id;\n  char *level_under_param = src_board->level_under_param;\n  char *level_under_color = src_board->level_under_color;\n\n  int push_status = A_PUSHEW;\n  int dx = x, dy = y, d_offset, d_flag;\n  enum thing d_id = NO_ID;\n  char d_param, d_color;\n  enum thing d_under_id = NO_ID;\n  enum thing p_id = NO_ID;\n  char p_param = 0xFF, p_color = 0xFF;\n  enum thing p_under_id = NO_ID;\n  char sensor_param = 0xFF, sensor_color = 0xFF;\n  int p_offset = -1;\n\n  if(dir < 2)\n  {\n    push_status = A_PUSHNS;\n  }\n\n  // See if the block can be pushed; if not, exit.\n  while(1)\n  {\n    // Previous ID\n    p_id = d_id;\n    p_under_id = d_under_id;\n    // Get the next in the dir, out of bounds returns 1\n    if(xy_shift_dir(src_board, dx, dy, &dx, &dy, dir))\n      return 1;\n    d_offset = xy_to_offset(src_board, dx, dy);\n    d_id = (enum thing)level_id[d_offset];\n    d_under_id = (enum thing)level_under_id[d_offset];\n    d_flag = flags[(int)d_id];\n\n    // If a push can be made here, the destination has been found\n\n    if(d_flag & A_UNDER)\n    {\n      // FIXME - In game2.asm you can't push things over water, even though\n      // it still works somehow. Look into this.\n      // Lava and goop are automatic failures even though it's \"underable\"\n      if((d_id == LAVA) || (d_id == GOOP))\n        return 1;\n      // Otherwise, we're good\n      break;\n    }\n    else\n    {\n      // The search may continue... if it's pushable in that dir, continue\n      if(!(d_flag & push_status))\n      {\n        // \"spec push\" can be pushed too, otherwise no push\n        if(!(d_flag & A_SPEC_PUSH))\n          return 1;\n        // Player can be pushed onto sensor, otherwise it's pushable\n        if((d_id == SENSOR) && (p_id == PLAYER) && (p_under_id != SENSOR))\n          break;\n        else\n\n        // Can be pushed onto a transport if transport checks out\n        if(d_id == TRANSPORT)\n        {\n          if(transport(mzx_world, dx, dy, dir, SPACE, 0, 0, 1))\n            return 1;\n          else\n            break;\n        }\n      }\n    }\n  }\n\n  // The push can be made now, make sure we're not just checking though\n  if(!checking)\n  {\n    // Start back at the beginning...\n    dx = x;\n    dy = y;\n    p_id = NO_ID;\n\n    while(1)\n    {\n      // Get thing at next location\n      xy_shift_dir(src_board, dx, dy, &dx, &dy, dir);\n      d_offset = xy_to_offset(src_board, dx, dy);\n      d_id = (enum thing)level_id[d_offset];\n      d_param = level_param[d_offset];\n      d_color = level_color[d_offset];\n      d_under_id = (enum thing)level_under_id[d_offset];\n      d_flag = flags[(int)d_id];\n\n      // Sensor? Mark it\n      if(d_under_id == SENSOR)\n      {\n        sensor_color = level_under_color[d_offset];\n        sensor_param = level_under_param[d_offset];\n      }\n\n      // Can the destination be moved under and thus pushed onto?\n      // A sensor will also work if the player is what's being pushed onto it.\n      // (but only if what's under the player isn't another sensor)\n      if((d_flag & A_UNDER) ||\n       ((p_id == PLAYER) && (d_id == SENSOR) && (p_under_id != SENSOR)))\n      {\n        // Place the previous thing here\n        id_place(mzx_world, dx, dy, p_id, p_color, p_param);\n\n        if((p_id == PLAYER) && (p_under_id == SENSOR))\n        {\n          push_player_sensor(mzx_world, p_offset, d_offset, sensor_param,\n           sensor_color);\n        }\n        else\n\n        if(p_id == ROBOT_PUSHABLE)\n          fix_robot_pos(src_board, p_param, dx, dy);\n\n        // If this is a sensor, the player was pushed onto it\n        if(d_id == SENSOR)\n        {\n          // The player is just stepping on it.. alert sensor\n          step_sensor(mzx_world, d_param);\n        }\n        // All under can stop\n        break;\n      }\n\n      // Is it pushable?\n      if(d_flag & (push_status | A_SPEC_PUSH))\n      {\n        if(d_id == TRANSPORT)\n        {\n          // If it's a transport, transport it\n          transport(mzx_world, dx, dy, dir, p_id, p_param, p_color, 1);\n          break;\n        }\n\n        // No previous ID yet?\n        if(p_id == NO_ID)\n        {\n          // Remove what was there\n          id_remove_top(mzx_world, dx, dy);\n        }\n        else\n        {\n          // Otherwise, put the last one in the new one, and make\n          // the current one the new last one.\n          level_id[d_offset] = p_id;\n          level_param[d_offset] = p_param;\n          level_color[d_offset] = p_color;\n\n          // FIXME: this is bugged, see issue 632\n          // Was a player/sensor sandwich pushed?\n          if((p_id == PLAYER) && (p_under_id == SENSOR))\n          {\n            push_player_sensor(mzx_world, p_offset, d_offset,\n             sensor_param, sensor_color);\n          }\n\n          if(p_id == ROBOT_PUSHABLE)\n            fix_robot_pos(src_board, p_param, dx, dy);\n        }\n\n        // How about a pushable robot?\n        if(d_id == ROBOT_PUSHABLE)\n        {\n          // Send the default label for pushing\n          send_robot_def(mzx_world, d_param, LABEL_PUSHED);\n        }\n\n        // Load current into previous\n        p_id = d_id;\n        p_under_id = d_under_id;\n        p_param = d_param;\n        p_color = d_color;\n        p_offset = d_offset;\n\n        // Is it a sensor that was pushed? Flag it.\n        if(d_id == SENSOR)\n        {\n          push_sensor(mzx_world, d_param);\n        }\n      }\n    }\n  }\n\n  play_sfx(mzx_world, SFX_PUSH);\n\n  return 0;\n}\n\nvoid shoot(struct world *mzx_world, int x, int y, int dir, int type)\n{\n  int dx, dy, d_offset, d_flag;\n  char d_id, d_param;\n  struct board *src_board = mzx_world->current_board;\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n\n  // If enemy hurts enemy then enemy bullet becomes neutral\n  if((mzx_world->enemy_hurt_enemy == 1) && (type == ENEMY_BULLET))\n  {\n    type = NEUTRAL_BULLET;\n  }\n\n  if(xy_shift_dir(src_board, x, y, &dx, &dy, dir))\n  {\n    return;\n  }\n\n  d_offset = xy_to_offset(src_board, dx, dy);\n  d_id = src_board->level_id[d_offset];\n  d_param = src_board->level_param[d_offset];\n  d_flag = flags[(int)d_id];\n\n  // If it's a player bullet maybe give some points\n  // Has to be an enemy ID 80-95 but not 83, 85, 92, or 93\n  if((type == PLAYER_BULLET) && (d_id >= SNAKE) &&\n   (d_id <= BEAR_CUB) && (d_id != SLIMEBLOB) && (d_id != GHOST) &&\n   (d_id != BULLET_GUN) && (d_id != SPINNING_GUN))\n  {\n    // Score isn't supposed to overflow.. but the overflow\n    // detection in game2.asm is broken anyway....\n    // Just let it wrap around for now.\n    inc_counter(mzx_world, \"SCORE\", 3, 0);\n  }\n\n  // If it's underable the bullet can move here\n  if(d_flag & A_UNDER)\n  {\n    char n_param = (type << 2) | dir;\n    id_place(mzx_world, dx, dy, BULLET, bullet_color[type], n_param);\n    return;\n  }\n\n  // Is it shootable?\n  if(d_flag & A_SHOOTABLE)\n  {\n    // However, enemies can only shoot these things:\n    if((type != ENEMY_BULLET) || (d_id == BULLET) || (d_id == GEM) ||\n     (d_id == MAGIC_GEM) || (d_id == BREAKAWAY) ||\n     (d_id == CUSTOM_BREAK))\n    {\n      // Can be removed\n      id_remove_top(mzx_world, dx, dy);\n      play_sfx(mzx_world, SFX_BREAK);\n    }\n    return;\n  }\n\n  // Must be a special shot for anything to happen now\n  if(d_flag & A_SPEC_SHOT)\n  {\n    // And these are the ones\n    switch(d_id)\n    {\n      case RICOCHET_PANEL:\n      {\n        play_sfx(mzx_world, SFX_RICOCHET);\n        // Fanangle the panel orientation into a shoot direction\n        if(d_param == 1)\n        {\n          dir ^= 2;\n        }\n        else\n        {\n          dir ^= 3;\n        }\n        shoot(mzx_world, dx, dy, dir, NEUTRAL_BULLET);\n        break;\n      }\n\n      case RICOCHET:\n      {\n        int pdir = flip_dir(dir);\n        play_sfx(mzx_world, SFX_RICOCHET);\n        // Shoot backwards\n        shoot(mzx_world, dx, dy, pdir, NEUTRAL_BULLET);\n        break;\n      }\n\n      case MINE:\n      {\n        // Turn into explosion\n        level_id[d_offset] = EXPLOSION;\n        // Get rid of count and anim fields in param\n        level_param[d_offset] = d_param & 0xF0;\n        play_sfx(mzx_world, SFX_EXPLOSION);\n        break;\n      }\n\n      case PLAYER:\n      {\n        // Player bullets don't hurt player\n        if(type == PLAYER_BULLET) break;\n        hurt_player(mzx_world, BULLET);\n        send_robot_def(mzx_world, 0, LABEL_PLAYERHIT);\n        break;\n      }\n\n      case ROBOT:\n      case ROBOT_PUSHABLE:\n      {\n        int idx = d_param;\n        // Send the current shot label to the robot\n        // 4 is PLAYERSHOT, 5 is NEUTRAL, 6 is ENEMY\n        send_robot_def(mzx_world, d_param, type + 4);\n        // Set its last shot dir..\n        (src_board->robot_list[idx])->last_shot_dir =\n         int_to_dir(flip_dir(dir));\n        break;\n      }\n\n      case EYE:\n      {\n        // Can't be shot by enemy bullet\n        if(type == ENEMY_BULLET) break;\n        // Turn into explosion\n        level_id[d_offset] = (char)EXPLOSION;\n        level_param[d_offset] = (d_param & 0x38) << 1;\n        play_sfx(mzx_world, SFX_EXPLOSION);\n        break;\n      }\n\n      case SLIMEBLOB:\n      {\n        // Can't be shot by enemy bullet\n        if(type != ENEMY_BULLET)\n        {\n          // Check if not invincible\n          if(!(d_param & 0x80))\n          {\n            // Kill it\n            id_remove_top(mzx_world, dx, dy);\n            play_sfx(mzx_world, SFX_BREAK);\n          }\n        }\n        break;\n      }\n\n      case RUNNER:\n      {\n        // Can't be shot by enemy bullet\n        if(type != ENEMY_BULLET)\n        {\n          // Check if it has 0 HP\n          if(d_param & 0xC0)\n          {\n            // Otherwise, take an HP\n            level_param[d_offset] = d_param - 0x40;\n            play_sfx(mzx_world, SFX_ENEMY_HP_DOWN);\n          }\n          else\n          {\n            // Kill it\n            id_remove_top(mzx_world, dx, dy);\n            play_sfx(mzx_world, SFX_BREAK);\n          }\n        }\n\n        break;\n      }\n\n      case GHOST:\n      {\n        // Can't be shot by enemy bullet\n        if(type != ENEMY_BULLET)\n        {\n          // Is it not invincible?\n          if(!(d_param & 0x08))\n          {\n            // Kill it\n            id_remove_top(mzx_world, dx, dy);\n            play_sfx(mzx_world, SFX_BREAK);\n          }\n        }\n\n        break;\n      }\n\n      case DRAGON:\n      {\n        // Can't be shot by enemy bullet\n        if(type != ENEMY_BULLET)\n        {\n          // Out of HP?\n          if(!(d_param & 0xE0))\n          {\n            // Kill it\n            id_remove_top(mzx_world, dx, dy);\n            play_sfx(mzx_world, SFX_BREAK);\n          }\n          else\n          {\n            // Take away a hit point\n            level_param[d_offset] = d_param - 0x20;\n            play_sfx(mzx_world, SFX_ENEMY_HP_DOWN);\n          }\n        }\n\n        break;\n      }\n\n      case FISH:\n      case SPIDER:\n      case BEAR:\n      {\n        // Zero HP?\n        if(!(d_param & 0x80))\n        {\n          // Kill it\n          id_remove_top(mzx_world, dx, dy);\n          play_sfx(mzx_world, SFX_BREAK);\n        }\n        else\n        {\n          // Remove HP\n          level_param[d_offset] = d_param ^ 0x80;\n          play_sfx(mzx_world, SFX_ENEMY_HP_DOWN);\n        }\n        break;\n      }\n    }\n    return;\n  }\n}\n\nvoid shoot_fire(struct world *mzx_world, int x, int y, int dir)\n{\n  struct board *src_board = mzx_world->current_board;\n  int dx, dy, d_offset;\n  enum thing d_id;\n  char d_param, d_flag;\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n\n  if(xy_shift_dir(src_board, x, y, &dx, &dy, dir))\n    return;\n\n  d_offset = xy_to_offset(src_board, dx, dy);\n  d_id = (enum thing)level_id[d_offset];\n  d_param = level_param[d_offset];\n  d_flag = flags[(int)d_id];\n\n  // If it can be moved under, place it\n  if(d_flag & A_UNDER)\n  {\n    id_place(mzx_world, dx, dy, SHOOTING_FIRE, 0, dir << 1);\n  }\n  else\n\n  // Did it hit a robot?\n  if(is_robot(d_id))\n  {\n    // Send the spitfire label\n    send_robot_def(mzx_world, d_param, LABEL_SPITFIRE);\n  }\n  else\n\n  // Did it hit the player?\n  if(d_id == PLAYER)\n  {\n    hurt_player(mzx_world, SHOOTING_FIRE);\n  }\n}\n\nvoid shoot_seeker(struct world *mzx_world, int x, int y, int dir)\n{\n  struct board *src_board = mzx_world->current_board;\n  int dx, dy, d_offset;\n  enum thing d_id;\n  char d_flag;\n  char *level_id = src_board->level_id;\n\n  if(xy_shift_dir(src_board, x, y, &dx, &dy, dir))\n    return;\n\n  d_offset = xy_to_offset(src_board, dx, dy);\n  d_id = (enum thing)level_id[d_offset];\n  d_flag = flags[(int)d_id];\n\n  // If it can be moved under, place it\n  if(d_flag & A_UNDER)\n  {\n    id_place(mzx_world, dx, dy, SEEKER, 0, 127);\n  }\n  else\n\n  // Did it hit the player?\n  if(d_id == PLAYER)\n  {\n    hurt_player(mzx_world, SEEKER);\n  }\n}\n\nvoid shoot_missile(struct world *mzx_world, int x, int y, int dir)\n{\n  struct board *src_board = mzx_world->current_board;\n  int dx, dy, d_offset;\n  enum thing d_id;\n  char d_flag;\n  char *level_id = src_board->level_id;\n\n  if(xy_shift_dir(src_board, x, y, &dx, &dy, dir))\n    return;\n\n  d_offset = xy_to_offset(src_board, dx, dy);\n  d_id = (enum thing)level_id[d_offset];\n  d_flag = flags[(int)d_id];\n\n  // If it can be moved under, place it\n  if(d_flag & A_UNDER)\n  {\n    id_place(mzx_world, dx, dy, MISSILE, missile_color, dir);\n  }\n\n  // Did it hit the player?\n  if(d_id == PLAYER)\n  {\n    hurt_player(mzx_world, MISSILE);\n  }\n}\n\nvoid shoot_lazer(struct world *mzx_world, int x, int y, int dir, int length,\n int color)\n{\n  struct board *src_board = mzx_world->current_board;\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n\n  int lx = x;\n  int ly = y;\n  int offset;\n  enum thing id;\n  char param;\n\n  while(1)\n  {\n    if(xy_shift_dir(src_board, lx, ly, &lx, &ly, dir))\n    {\n      // Out of bounds\n      return;\n    }\n\n    offset = xy_to_offset(src_board, lx, ly);\n    id = (enum thing)level_id[offset];\n\n    if(id == PLAYER)\n    {\n      hurt_player(mzx_world, LAZER);\n\n      // Restart if zapped?\n      if(src_board->restart_if_zapped != 1)\n      {\n        int p_dir = (src_board->player_last_dir) & 0x0F;\n        if(p_dir != 0)\n        {\n          // Move the player in the opposite direction\n          move(mzx_world, lx, ly, flip_dir((p_dir - 1)),\n           CAN_PUSH | CAN_TRANSPORT | CAN_WATERWALK);\n        }\n      }\n\n      break;\n    }\n    else\n\n    // Robot\n    if(is_robot(id))\n    {\n      param = level_param[offset];\n      // Send the robot the lazer label\n      send_robot_def(mzx_world, param, LABEL_LAZER);\n\n      break;\n    }\n    else\n    // Otherwise...\n    {\n      int flag = flags[(int)id];\n      char n_param;\n      // Blocked. Get out of here\n      if(!(flag & A_UNDER)) return;\n\n      // Combine length and dir in lazer param\n      n_param = length << 3;\n\n      // Check vertical/horizontal\n      if(dir <= 1)\n      {\n        n_param |= 1;\n      }\n\n      id_place(mzx_world, lx, ly, LAZER, color, n_param);\n    }\n  }\n}\n\nenum move_status move(struct world *mzx_world, int x, int y, int dir,\n int move_flags)\n{\n  struct board *src_board = mzx_world->current_board;\n  int dx, dy, d_offset, d_flag;\n  enum thing d_id;\n  char d_param;\n  int p_offset = xy_to_offset(src_board, x, y);\n  enum thing p_id;\n  char p_param, p_color;\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n  char *level_color = src_board->level_color;\n\n  /* Can't move to invalid directions. This was implicit prior to\n   * 2.93d for all things capable of moving in invalid directions\n   * (ShootingFire, Missile, Pusher, and SpittingTiger) */\n  if((unsigned)dir > 3)\n    return HIT;\n\n  if(xy_shift_dir(src_board, x, y, &dx, &dy, dir))\n  {\n    // Hit edge\n    return HIT_EDGE;\n  }\n\n  d_offset = xy_to_offset(src_board, dx, dy);\n  d_id = (enum thing)level_id[d_offset];\n  d_param = level_param[d_offset];\n  d_flag = flags[(int)d_id];\n\n  p_id = (enum thing)level_id[p_offset];\n  p_param = level_param[p_offset];\n  p_color = level_color[p_offset];\n\n  // Only if the player shouldn't be checked yet\n  if((move_flags & REACT_PLAYER) && (d_id == PLAYER))\n  {\n    // Hit the player\n    return HIT_PLAYER;\n  }\n\n  // Is it a spitfire? Is it moving onto a robot?\n  if((move_flags & SPITFIRE) && is_robot(d_id))\n  {\n    // Send the label\n    send_robot_def(mzx_world, d_param, LABEL_SPITFIRE);\n    return HIT;\n  }\n\n  // Can move? However, flags may prohibit the move.\n  if(d_flag & A_UNDER)\n  {\n    int web_flags = (move_flags & (MUST_WEB | MUST_THICKWEB)) >> 5;\n    int web = (d_id == WEB) | ((d_id == THICK_WEB) << 1);\n\n    // Web and thick web may be flagged separately, or together.\n    // If they're both on, we must allow either WEB or THICK_WEB ids.\n    if(web_flags && !(web_flags & web))\n      return HIT;\n\n    // Must be water?\n    if((move_flags & MUST_WATER) && !(is_water(d_id)))\n      return HIT;\n\n    // Must be lava or goop?\n    if((move_flags & MUST_LAVAGOOP) &&\n     (d_id != LAVA) && (d_id != GOOP))\n    {\n      return HIT;\n    }\n\n    // Now CAN it move to lava if there's lava?\n    if((d_id == LAVA) && !(move_flags & CAN_LAVAWALK))\n      return HIT;\n\n    // Same for fire\n    if((d_id == FIRE) && !(move_flags & CAN_FIREWALK))\n      return HIT;\n\n    // Same for water\n    if(is_water(d_id) && !(move_flags & CAN_WATERWALK))\n      return HIT;\n\n    // Only must goop can go on goop\n    if((d_id == GOOP) && !(move_flags & CAN_GOOPWALK))\n      return HIT;\n  }\n  else\n\n  // Moving onto a transporter?\n  if(d_id == TRANSPORT)\n  {\n    // Can it transport?\n    if(move_flags & CAN_TRANSPORT)\n    {\n      int can_push = 0;\n\n      // Transport it\n      if(move_flags & CAN_PUSH)\n        can_push = 1;\n\n      // If it can transport, do it\n      if(!transport(mzx_world, dx, dy, dir, p_id, p_param, p_color, can_push))\n      {\n        id_remove_top(mzx_world, x, y);\n        return NO_HIT;\n      }\n    }\n    // Otherwise blocked\n    return HIT;\n  }\n  else\n\n  // Is it pushable and can it be pushed?\n  if((d_flag & (A_PUSHNS | A_PUSHEW | A_SPEC_PUSH)) &&\n   (move_flags & CAN_PUSH))\n  {\n    // If it can't push, exit\n      if(push(mzx_world, x, y, dir, 0))\n        return HIT;\n  }\n  else\n  {\n    return HIT;\n  }\n\n  // Made it this far - actually move the thing.\n  // Remove what was there\n  id_remove_top(mzx_world, x, y);\n  // Place a new one at the destination\n  id_place(mzx_world, dx, dy, p_id, p_color, p_param);\n\n  if(is_robot(p_id))\n    fix_robot_pos(src_board, p_param, dx, dy);\n\n  // Successfully return\n  return NO_HIT;\n}\n\nenum dir parsedir(struct world *mzx_world, enum dir old_dir, int x, int y,\n enum dir flow_dir, int bln, int bls, int ble, int blw)\n{\n  int n_dir = (int)old_dir & 0x0F;\n\n  switch(n_dir)\n  {\n    // idle, beneath, anydir, nodir don't get modified\n    case IDLE:\n    case BENEATH:\n    case ANYDIR:\n    case NODIR:\n      return (enum dir)n_dir;\n\n    case NORTH:\n    case SOUTH:\n    case EAST:\n    case WEST:\n      n_dir--;\n      break;\n\n    case RANDNS:\n      n_dir = Random(2);\n      break;\n\n    case RANDEW:\n      n_dir = (Random(2)) + 2;\n      break;\n\n    case RANDNE:\n      n_dir = Random(2) << 1;\n      break;\n\n    case RANDNB:\n      bln ^= 1;\n      bls ^= 1;\n      ble ^= 1;\n      blw ^= 1;\n\n      /* fallthrough */\n\n    case RANDB:\n    {\n      int bl_sum = bln + bls + ble + blw;\n      if(bl_sum)\n      {\n        n_dir = Random(bl_sum);\n        if(!bln)\n          n_dir++;\n        if(!bls && n_dir)\n          n_dir++;\n        if(!ble && (n_dir > 1))\n          n_dir++;\n      }\n      else\n      {\n        return NODIR;\n      }\n      break;\n    }\n\n    case SEEK:\n      n_dir = find_seek(mzx_world, x, y);\n      break;\n\n    case RANDANY:\n      n_dir = Random(4);\n      break;\n\n    case FLOW:\n      if(flow_dir)\n        n_dir = flow_dir - 1;\n      else\n        return IDLE;\n      break;\n  }\n\n  // Rotate clockwise\n  if(old_dir & CW)\n  {\n    if(n_dir >= SOUTH)\n      n_dir ^= 1;\n\n    n_dir ^= 2;\n  }\n\n  // Move opposite\n  if(old_dir & OPP)\n  {\n    n_dir = (n_dir ^ 1);\n  }\n\n  // Randp\n  if(old_dir & RANDP)\n  {\n    n_dir ^= 2;\n    if(Random(2) == 1)\n    {\n      n_dir ^= 1;\n    }\n  }\n\n  if(old_dir & RANDNOT)\n  {\n    int rval = Random(4);\n    if(n_dir != rval)\n    {\n      n_dir = rval;\n    }\n    else\n    {\n      n_dir = 4;\n    }\n  }\n\n  return (enum dir)(n_dir + 1);\n}\n"
  },
  {
    "path": "src/game_ops.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __GAME_OPS_H\n#define __GAME_OPS_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"data.h\"\n#include \"world_struct.h\"\n\nvoid rotate(struct world *mzx_world, int x, int y, int dir);\n\nvoid calculate_xytop(struct world *mzx_world, int *x, int *y);\n\nint flip_dir(int dir);\nint find_seek(struct world *mzx_world, int x, int y);\n\nint transport(struct world *mzx_world, int x, int y, int dir, enum thing id,\n int param, int color, int can_push);\nint push(struct world *mzx_world, int x, int y, int dir, int checking);\n\nvoid shoot(struct world *mzx_world, int x, int y, int dir, int type);\nvoid shoot_fire(struct world *mzx_world, int x, int y, int dir);\nvoid shoot_seeker(struct world *mzx_world, int x, int y, int dir);\nvoid shoot_missile(struct world *mzx_world, int x, int y, int dir);\nvoid shoot_lazer(struct world *mzx_world, int x, int y, int dir, int length,\n int color);\n\nenum move_status move(struct world *mzx_world, int x, int y, int dir,\n int flags);\nenum dir parsedir(struct world *mzx_world, enum dir old_dir, int x, int y,\n enum dir flow_dir, int bln, int bls, int ble, int blw);\n\n// Function to take an x/y position and return an array offset\n// (within the board)\n\nstatic inline int xy_to_offset(struct board *src_board, int x, int y)\n{\n  return (y * src_board->board_width) + x;\n}\n\n// Take an x/y position and a direction and return a new x/y position (in\n// what ret_x/ret_y point to)\n// Returns -1 if offscreen, 0 otherwise.\n\nstatic inline int xy_shift_dir(struct board *src_board, int x, int y,\n int *ret_x, int *ret_y, int direction)\n{\n  switch(direction)\n  {\n    case 0: y--; break; // North\n    case 1: y++; break; // South\n    case 2: x++; break; // East\n    case 3: x--; break; // West\n  }\n  if((x == -1) || (x == src_board->board_width) ||\n   (y == -1) || (y == src_board->board_height))\n    return -1;\n\n  *ret_x = x;\n  *ret_y = y;\n  return 0;\n}\n\n__M_END_DECLS\n\n#endif // __GAME_OPS_H\n"
  },
  {
    "path": "src/game_player.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2018-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <string.h>\n\n#include \"counter.h\"\n#include \"game.h\"\n#include \"game_ops.h\"\n#include \"game_player.h\"\n#include \"idarray.h\"\n#include \"idput.h\"\n#include \"robot.h\"\n#include \"scrdisp.h\"\n#include \"util.h\"\n#include \"window.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n\n#include \"audio/sfx.h\"\n\n/**\n * Player functions for gameplay.\n */\n\nvoid set_mesg(struct world *mzx_world, const char *str)\n{\n  if(mzx_world->bi_mesg_status)\n  {\n    set_mesg_direct(mzx_world->current_board, str);\n  }\n}\n\nvoid set_mesg_direct(struct board *src_board, const char *str)\n{\n  snprintf(src_board->bottom_mesg, ROBOT_MAX_TR, \"%s\", str);\n  src_board->bottom_mesg[ROBOT_MAX_TR - 1] = 0;\n  src_board->b_mesg_timer = MESG_TIMEOUT;\n  clear_intro_mesg();\n}\n\nstatic void set_3_mesg(struct world *mzx_world, const char *str1, int num,\n const char *str2)\n{\n  if(mzx_world->bi_mesg_status)\n  {\n    struct board *src_board = mzx_world->current_board;\n    sprintf(src_board->bottom_mesg, \"%s%d%s\", str1, num, str2);\n    src_board->b_mesg_timer = MESG_TIMEOUT;\n  }\n}\n\nboolean player_can_save(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int offset;\n\n  if(cur_board->save_mode == CANT_SAVE)\n    return false;\n\n  if(cur_board->save_mode == CAN_SAVE_ON_SENSOR)\n  {\n    offset = xy_to_offset(cur_board, mzx_world->player_x, mzx_world->player_y);\n\n    if(cur_board->level_under_id[offset] != SENSOR)\n      return false;\n  }\n\n  return true;\n}\n\nvoid player_switch_bomb_type(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n\n  if(!mzx_world->active || !cur_board)\n    return;\n\n  mzx_world->bomb_type ^= 1;\n  if(!cur_board->player_attack_locked && cur_board->can_bomb)\n  {\n    play_sfx(mzx_world, SFX_SWITCH_BOMB_TYPE);\n    if(mzx_world->bomb_type)\n    {\n      set_mesg(mzx_world, \"You switch to high strength bombs.\");\n    }\n    else\n    {\n      set_mesg(mzx_world, \"You switch to low strength bombs.\");\n    }\n  }\n}\n\nvoid player_cheat_give_all(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int i;\n\n  set_counter(mzx_world, \"AMMO\", 32767, 1);\n  set_counter(mzx_world, \"COINS\", 32767, 1);\n  set_counter(mzx_world, \"GEMS\", 32767, 1);\n  set_counter(mzx_world, \"HEALTH\", 32767, 1);\n  set_counter(mzx_world, \"HIBOMBS\", 32767, 1);\n  set_counter(mzx_world, \"LIVES\", 32767, 1);\n  set_counter(mzx_world, \"LOBOMBS\", 32767, 1);\n  set_counter(mzx_world, \"SCORE\", 0, 1);\n  set_counter(mzx_world, \"TIME\", cur_board->time_limit, 1);\n\n  mzx_world->dead = false;\n\n  for(i = 0; i < 16; i++)\n  {\n    mzx_world->keys[i] = i;\n  }\n\n  cur_board->player_ns_locked = 0;\n  cur_board->player_ew_locked = 0;\n  cur_board->player_attack_locked = 0;\n}\n\nvoid player_cheat_zap(struct world *mzx_world)\n{\n  int player_x = mzx_world->player_x;\n  int player_y = mzx_world->player_y;\n  int board_width = mzx_world->current_board->board_width;\n  int board_height = mzx_world->current_board->board_height;\n\n  if(player_x > 0)\n  {\n    place_at_xy(mzx_world, SPACE, 7, 0, player_x - 1,\n      player_y);\n\n    if(player_y > 0)\n    {\n      place_at_xy(mzx_world, SPACE, 7, 0, player_x - 1,\n        player_y - 1);\n    }\n\n    if(player_y < (board_height - 1))\n    {\n      place_at_xy(mzx_world, SPACE, 7, 0, player_x - 1,\n        player_y + 1);\n    }\n  }\n\n  if(player_x < (board_width - 1))\n  {\n    place_at_xy(mzx_world, SPACE, 7, 0, player_x + 1,\n      player_y);\n\n    if(player_y > 0)\n    {\n      place_at_xy(mzx_world, SPACE, 7, 0,\n        player_x + 1, player_y - 1);\n    }\n\n    if(player_y < (board_height - 1))\n    {\n      place_at_xy(mzx_world, SPACE, 7, 0,\n        player_x + 1, player_y + 1);\n    }\n  }\n\n  if(player_y > 0)\n  {\n    place_at_xy(mzx_world, SPACE, 7, 0,\n      player_x, player_y - 1);\n  }\n\n  if(player_y < (board_height - 1))\n  {\n    place_at_xy(mzx_world, SPACE, 7, 0,\n      player_x, player_y + 1);\n  }\n}\n\nvoid hurt_player(struct world *mzx_world, enum thing damage_src)\n{\n  int amount = id_dmg[damage_src];\n  dec_counter(mzx_world, \"health\", amount, 0);\n  play_sfx(mzx_world, SFX_HURT);\n  set_mesg(mzx_world, \"Ouch!\");\n}\n\nint take_key(struct world *mzx_world, int color)\n{\n  int i;\n  char *keys = mzx_world->keys;\n\n  color &= 15;\n\n  for(i = 0; i < NUM_KEYS; i++)\n  {\n    if(keys[i] == color) break;\n  }\n\n  if(i < NUM_KEYS)\n  {\n    keys[i] = NO_KEY;\n    return 0;\n  }\n\n  return 1;\n}\n\n// Give a key. Returns non-0 if no room.\nint give_key(struct world *mzx_world, int color)\n{\n  int i;\n  char *keys = mzx_world->keys;\n\n  color &= 15;\n\n  for(i = 0; i < NUM_KEYS; i++)\n    if(keys[i] == NO_KEY) break;\n\n  if(i < NUM_KEYS)\n  {\n    keys[i] = color;\n    return 0;\n  }\n\n  return 1;\n}\n\nstatic void give_potion(struct world *mzx_world, enum potion type)\n{\n  struct board *src_board = mzx_world->current_board;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n  char *level_color = src_board->level_color;\n\n  play_sfx(mzx_world, SFX_RING_POTION);\n  inc_counter(mzx_world, \"SCORE\", 5, 0);\n\n  switch(type)\n  {\n    case POTION_DUD:\n    {\n      set_mesg(mzx_world, \"* No effect *\");\n      break;\n    }\n\n    case POTION_INVINCO:\n    {\n      set_mesg(mzx_world, \"* Invinco *\");\n      send_robot_def(mzx_world, 0, LABEL_INVINCO);\n      set_counter(mzx_world, \"INVINCO\", 113, 0);\n      break;\n    }\n\n    case POTION_BLAST:\n    {\n      int placement_period = 18;\n      int x, y, offset, d_flag;\n      enum thing d_id;\n\n      for(y = 0, offset = 0; y < board_height; y++)\n      {\n        for(x = 0; x < board_width; x++, offset++)\n        {\n          d_id = (enum thing)level_id[offset];\n          d_flag = flags[d_id];\n\n          if((d_flag & A_UNDER) && !(d_flag & A_ENTRANCE))\n          {\n            // Adjust the ratio for board size\n\n            if(Random(placement_period) == 0)\n            {\n              id_place(mzx_world, x, y, EXPLOSION, 0,\n               16 * ((Random(5)) + 2));\n            }\n          }\n        }\n      }\n\n      set_mesg(mzx_world, \"* Blast *\");\n      break;\n    }\n\n    case POTION_SMALL_HEALTH:\n    {\n      inc_counter(mzx_world, \"HEALTH\", 10, 0);\n      set_mesg(mzx_world, \"* Healing *\");\n      break;\n    }\n\n    case POTION_BIG_HEALTH:\n    {\n      inc_counter(mzx_world, \"HEALTH\", 50, 0);\n      set_mesg(mzx_world, \"* Healing *\");\n      break;\n    }\n\n    case POTION_POISON:\n    {\n      dec_counter(mzx_world, \"HEALTH\", 10, 0);\n      set_mesg(mzx_world, \"* Poison *\");\n      break;\n    }\n\n    case POTION_BLIND:\n    {\n      mzx_world->blind_dur = 200;\n      set_mesg(mzx_world, \"* Blind *\");\n      break;\n    }\n\n    case POTION_KILL:\n    {\n      int x, y, offset;\n      enum thing d_id;\n\n      for(y = 0, offset = 0; y < board_height; y++)\n      {\n        for(x = 0; x < board_width; x++, offset++)\n        {\n          d_id = (enum thing)level_id[offset];\n\n          if(is_enemy(d_id))\n            id_remove_top(mzx_world, x, y);\n        }\n      }\n      set_mesg(mzx_world, \"* Kill enemies *\");\n      break;\n    }\n\n    case POTION_FIREWALK:\n    {\n      mzx_world->firewalker_dur = 200;\n      set_mesg(mzx_world, \"* Firewalking *\");\n      break;\n    }\n\n    case POTION_DETONATE:\n    {\n      int x, y, offset;\n      for(y = 0, offset = 0; y < board_height; y++)\n      {\n        for(x = 0; x < board_width; x++, offset++)\n        {\n          if(level_id[offset] == BOMB)\n          {\n            level_id[offset] = EXPLOSION;\n            if(level_param[offset] == 0)\n              level_param[offset] = 32;\n            else\n              level_param[offset] = 64;\n          }\n        }\n      }\n      set_mesg(mzx_world, \"* Detonate *\");\n      break;\n    }\n\n    case POTION_BANISH:\n    {\n      int x, y, offset;\n      for(y = 0, offset = 0; y < board_height; y++)\n      {\n        for(x = 0; x < board_width; x++, offset++)\n        {\n          if(level_id[offset] == (char)DRAGON)\n          {\n            level_id[offset] = GHOST;\n            level_param[offset] = 51;\n          }\n        }\n      }\n      set_mesg(mzx_world, \"* Banish dragons *\");\n      break;\n    }\n\n    case POTION_SUMMON:\n    {\n      enum thing d_id;\n      int x, y, offset;\n\n      for(y = 0, offset = 0; y < board_height; y++)\n      {\n        for(x = 0; x < board_width; x++, offset++)\n        {\n          d_id = (enum thing)level_id[offset];\n          if(is_enemy(d_id))\n          {\n            level_id[offset] = (char)DRAGON;\n            level_color[offset] = 4;\n            level_param[offset] = 102;\n          }\n        }\n      }\n      set_mesg(mzx_world, \"* Summon dragons *\");\n      break;\n    }\n\n    case POTION_AVALANCHE:\n    {\n      int placement_period = 18;\n      int x, y, offset;\n\n      for(y = 0, offset = 0; y < board_height; y++)\n      {\n        for(x = 0; x < board_width; x++, offset++)\n        {\n          int d_flag = flags[(int)level_id[offset]];\n\n          if((d_flag & A_UNDER) && !(d_flag & A_ENTRANCE))\n          {\n            if((Random(placement_period)) == 0)\n            {\n              id_place(mzx_world, x, y, BOULDER, 7, 0);\n            }\n          }\n        }\n      }\n      set_mesg(mzx_world, \"* Avalanche *\");\n      break;\n    }\n\n    case POTION_FREEZE:\n    {\n      mzx_world->freeze_time_dur = 200;\n      set_mesg(mzx_world, \"* Freeze time *\");\n      break;\n    }\n\n    case POTION_WIND:\n    {\n      mzx_world->wind_dur = 200;\n      set_mesg(mzx_world, \"* Wind *\");\n      break;\n    }\n\n    case POTION_SLOW:\n    {\n      mzx_world->slow_time_dur = 200;\n      set_mesg(mzx_world, \"* Slow time *\");\n      break;\n    }\n  }\n}\n\nstatic void open_chest(struct world *mzx_world, int chest_x, int chest_y)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int offset = xy_to_offset(cur_board, chest_x, chest_y);\n  char param = cur_board->level_param[offset];\n\n  enum chest_contents item_type = (param & 15);\n  int item_value = (param & 240) >> 4;\n\n  if(item_type == CHEST_EMPTY)\n  {\n    play_sfx(mzx_world, SFX_EMPTY_CHEST);\n    return;\n  }\n\n  // Act upon contents\n  play_sfx(mzx_world, SFX_CHEST);\n\n  switch(item_type)\n  {\n    case CHEST_EMPTY:\n      break;\n\n    case CHEST_KEY:\n    {\n      if(give_key(mzx_world, item_value))\n      {\n        set_mesg(mzx_world,\n         \"Inside the chest is a key, but you can't carry any more keys!\");\n        return;\n      }\n      set_mesg(mzx_world, \"Inside the chest you find a key.\");\n      break;\n    }\n\n    case CHEST_COINS:\n    {\n      item_value *= 5;\n      set_3_mesg(mzx_world, \"Inside the chest you find \",\n       item_value, \" coins.\");\n      inc_counter(mzx_world, \"COINS\", item_value, 0);\n      inc_counter(mzx_world, \"SCORE\", item_value, 0);\n      break;\n    }\n\n    case CHEST_LIVES:\n    {\n      if(item_value > 1)\n      {\n        set_3_mesg(mzx_world, \"Inside the chest you find \",\n         item_value, \" free lives.\");\n      }\n      else\n      {\n        set_mesg(mzx_world, \"Inside the chest you find 1 free life.\");\n      }\n      inc_counter(mzx_world, \"LIVES\", item_value, 0);\n      break;\n    }\n\n    case CHEST_AMMO:\n    {\n      item_value *= 5;\n      set_3_mesg(mzx_world, \"Inside the chest you find \",\n       item_value, \" rounds of ammo.\");\n      inc_counter(mzx_world, \"AMMO\", item_value, 0);\n      break;\n    }\n\n    case CHEST_GEMS:\n    {\n      item_value *= 5;\n      set_3_mesg(mzx_world, \"Inside the chest you find \", item_value, \" gems.\");\n      inc_counter(mzx_world, \"GEMS\", item_value, 0);\n      inc_counter(mzx_world, \"SCORE\", item_value, 0);\n      break;\n    }\n\n    case CHEST_HEALTH:\n    {\n      item_value *= 5;\n      set_3_mesg(mzx_world, \"Inside the chest you find \",\n       item_value, \" health.\");\n      inc_counter(mzx_world, \"HEALTH\", item_value, 0);\n      break;\n    }\n\n    case CHEST_POTION:\n    {\n      int answer;\n      answer = confirm(mzx_world,\n       \"Inside the chest you find a potion. Drink it?\");\n\n      if(answer)\n        return;\n\n      give_potion(mzx_world, (enum potion)item_value);\n      break;\n    }\n\n    case CHEST_RING:\n    {\n      int answer;\n      answer = confirm(mzx_world,\n       \"Inside the chest you find a ring. Wear it?\");\n\n      if(answer)\n        return;\n\n      give_potion(mzx_world, (enum potion)item_value);\n      break;\n    }\n\n    case CHEST_LOBOMBS:\n    {\n      item_value *= 5;\n      set_3_mesg(mzx_world, \"Inside the chest you find \",\n       item_value, \" low strength bombs.\");\n      inc_counter(mzx_world, \"LOBOMBS\", item_value, 0);\n      break;\n    }\n\n    case CHEST_HIBOMBS:\n    {\n      item_value *= 5;\n      set_3_mesg(mzx_world, \"Inside the chest you find \", item_value,\n       \" high strength bombs.\");\n      inc_counter(mzx_world, \"HIBOMBS\", item_value, 0);\n      break;\n    }\n  }\n  // Empty chest\n  cur_board->level_param[offset] = CHEST_EMPTY;\n}\n\nstatic void place_player(struct world *mzx_world, int x, int y, int dir)\n{\n  struct board *src_board = mzx_world->current_board;\n  if((mzx_world->player_x != x) || (mzx_world->player_y != y))\n  {\n    id_remove_top(mzx_world, mzx_world->player_x, mzx_world->player_y);\n  }\n  id_place(mzx_world, x, y, PLAYER, 0, 0);\n  mzx_world->player_x = x;\n  mzx_world->player_y = y;\n  src_board->player_last_dir =\n   (src_board->player_last_dir & 240) | (dir + 1);\n  mzx_world->player_moved = true;\n}\n\n// mzx_world->player_moved will be true if the player moved, otherwise false.\n// This function is guaranteed to keep the player position correctly updated;\n// using find_player after using this function is not necessary.\nvoid grab_item(struct world *mzx_world, int item_x, int item_y, int src_dir)\n{\n  // \"src_dir\" is the direction from the player to this object.\n  struct board *cur_board = mzx_world->current_board;\n  int offset = xy_to_offset(cur_board, item_x, item_y);\n  enum thing id = (enum thing)cur_board->level_id[offset];\n  char param = cur_board->level_param[offset];\n  char color = cur_board->level_color[offset];\n  boolean remove_item = false;\n\n  char tmp[81];\n\n  mzx_world->player_moved = false;\n\n  switch(id)\n  {\n    case CHEST:\n    {\n      // Chest\n      open_chest(mzx_world, item_x, item_y);\n      break;\n    }\n\n    case GEM:\n    case MAGIC_GEM:\n    {\n      if(id == GEM)\n        play_sfx(mzx_world, SFX_GEM);\n      else\n        play_sfx(mzx_world, SFX_MAGIC_GEM);\n\n      inc_counter(mzx_world, \"GEMS\", 1, 0);\n\n      if(id == MAGIC_GEM)\n        inc_counter(mzx_world, \"HEALTH\", 1, 0);\n\n      inc_counter(mzx_world, \"SCORE\", 1, 0);\n      remove_item = true;\n      break;\n    }\n\n    case HEALTH:\n    {\n      play_sfx(mzx_world, SFX_HEALTH);\n      inc_counter(mzx_world, \"HEALTH\", param, 0);\n      remove_item = true;\n      break;\n    }\n\n    case RING:\n    case POTION:\n    {\n      give_potion(mzx_world, (enum potion)param);\n      remove_item = true;\n      break;\n    }\n\n    case GOOP:\n    {\n      play_sfx(mzx_world, SFX_GOOP);\n      set_mesg(mzx_world, \"There is goop in your way!\");\n      send_robot_def(mzx_world, 0, LABEL_GOOPTOUCHED);\n      break;\n    }\n\n    case ENERGIZER:\n    {\n      play_sfx(mzx_world, SFX_INVINCO_START);\n      set_mesg(mzx_world, \"Energize!\");\n      send_robot_def(mzx_world, 0, LABEL_INVINCO);\n      set_counter(mzx_world, \"INVINCO\", 113, 0);\n      remove_item = true;\n      break;\n    }\n\n    case AMMO:\n    {\n      play_sfx(mzx_world, SFX_AMMO);\n      inc_counter(mzx_world, \"AMMO\", param, 0);\n      remove_item = true;\n      break;\n    }\n\n    case BOMB:\n    {\n      if(cur_board->collect_bombs)\n      {\n        if(param)\n        {\n          play_sfx(mzx_world, SFX_HI_BOMB);\n          inc_counter(mzx_world, \"HIBOMBS\", 1, 0);\n        }\n        else\n        {\n          play_sfx(mzx_world, SFX_LO_BOMB);\n          inc_counter(mzx_world, \"LOBOMBS\", 1, 0);\n        }\n        remove_item = true;\n      }\n      else\n      {\n        // Light bomb\n        play_sfx(mzx_world, SFX_PLACE_LO_BOMB);\n        cur_board->level_id[offset] = LIT_BOMB;\n        cur_board->level_param[offset] = param << 7;\n      }\n      break;\n    }\n\n    case KEY:\n    {\n      if(give_key(mzx_world, color))\n      {\n        play_sfx(mzx_world, SFX_FULL_KEYS);\n        set_mesg(mzx_world, \"You can't carry any more keys!\");\n      }\n      else\n      {\n        play_sfx(mzx_world, SFX_KEY);\n        set_mesg(mzx_world, \"You grab the key.\");\n        remove_item = true;\n      }\n      break;\n    }\n\n    case LOCK:\n    {\n      if(take_key(mzx_world, color))\n      {\n        play_sfx(mzx_world, SFX_CANT_UNLOCK);\n        set_mesg(mzx_world, \"You need an appropriate key!\");\n      }\n      else\n      {\n        play_sfx(mzx_world, SFX_UNLOCK);\n        set_mesg(mzx_world, \"You open the lock.\");\n        remove_item = true;\n      }\n      break;\n    }\n\n    case DOOR:\n    {\n      int board_width = cur_board->board_width;\n      char *level_id = cur_board->level_id;\n      char *level_param = cur_board->level_param;\n      int x = offset % board_width;\n      int y = offset / board_width;\n      char door_first_movement[8] = { 0, 3, 0, 2, 1, 3, 1, 2 };\n\n      if(param & 8)\n      {\n        // Locked\n        if(take_key(mzx_world, color))\n        {\n          // Need key\n          play_sfx(mzx_world, SFX_DOOR_LOCKED);\n          set_mesg(mzx_world, \"The door is locked!\");\n          break;\n        }\n\n        // Unlocked\n        set_mesg(mzx_world, \"You unlock and open the door.\");\n      }\n      else\n      {\n        set_mesg(mzx_world, \"You open the door.\");\n      }\n\n      cur_board->level_id[offset] = OPEN_DOOR;\n      cur_board->level_param[offset] = (param & 7);\n\n      if(move(mzx_world, x, y, door_first_movement[param & 7],\n       CAN_PUSH | CAN_LAVAWALK | CAN_FIREWALK | CAN_WATERWALK))\n      {\n        set_mesg(mzx_world, \"The door is blocked from opening!\");\n        play_sfx(mzx_world, SFX_DOOR_LOCKED);\n        level_id[offset] = DOOR;\n        level_param[offset] = param & 7;\n      }\n      else\n      {\n        play_sfx(mzx_world, SFX_OPEN_DOOR);\n        // The player might have been pushed by the door.\n        find_player(mzx_world);\n      }\n      break;\n    }\n\n    case GATE:\n    {\n      if(param)\n      {\n        // Locked\n        if(take_key(mzx_world, color))\n        {\n          // Need key\n          play_sfx(mzx_world, SFX_GATE_LOCKED);\n          set_mesg(mzx_world, \"The gate is locked!\");\n          break;\n        }\n        // Unlocked\n        set_mesg(mzx_world, \"You unlock and open the gate.\");\n      }\n      else\n      {\n        set_mesg(mzx_world, \"You open the gate.\");\n      }\n\n      cur_board->level_id[offset] = (char)OPEN_GATE;\n      cur_board->level_param[offset] = 22;\n      play_sfx(mzx_world, SFX_OPEN_GATE);\n      break;\n    }\n\n    case TRANSPORT:\n    {\n      if(!transport(mzx_world, item_x, item_y, src_dir, PLAYER, 0, 0, 1))\n      {\n        // The player moved, so we need to erase the old player and find the new\n        // one. The under player vars were updated as part of the transport, so\n        // we need to preserve them after the id_remove_top.\n        int under_player_id = mzx_world->under_player_id;\n        int under_player_color = mzx_world->under_player_color;\n        int under_player_param = mzx_world->under_player_param;\n        int player_last_dir = cur_board->player_last_dir;\n\n        id_remove_top(mzx_world, mzx_world->player_x, mzx_world->player_y);\n        mzx_world->under_player_id = under_player_id;\n        mzx_world->under_player_color = under_player_color;\n        mzx_world->under_player_param = under_player_param;\n        cur_board->player_last_dir = (player_last_dir & 240) + src_dir + 1;\n        mzx_world->player_moved = true;\n\n        // Figure out the player's new position so we don't get player clones.\n        find_player(mzx_world);\n      }\n      break;\n    }\n\n    case COIN:\n    {\n      play_sfx(mzx_world, SFX_COIN);\n      inc_counter(mzx_world, \"COINS\", 1, 0);\n      inc_counter(mzx_world, \"SCORE\", 1, 0);\n      remove_item = true;\n      break;\n    }\n\n    case POUCH:\n    {\n      play_sfx(mzx_world, SFX_POUCH);\n      inc_counter(mzx_world, \"GEMS\", (param & 15) * 5, 0);\n      inc_counter(mzx_world, \"COINS\", (param >> 4) * 5, 0);\n      inc_counter(mzx_world, \"SCORE\", ((param & 15) + (param >> 4)) * 5, 1);\n      sprintf(tmp, \"The pouch contains %d gems and %d coins.\",\n       (param & 15) * 5, (param >> 4) * 5);\n      set_mesg(mzx_world, tmp);\n      remove_item = true;\n      break;\n    }\n\n    case FOREST:\n    {\n      play_sfx(mzx_world, SFX_FOREST);\n      if(cur_board->forest_becomes == FOREST_TO_EMPTY)\n      {\n        remove_item = true;\n      }\n      else\n      {\n        cur_board->level_id[offset] = (char)FLOOR;\n        place_player(mzx_world, item_x, item_y, src_dir);\n      }\n      break;\n    }\n\n    case LIFE:\n    {\n      play_sfx(mzx_world, SFX_LIFE);\n      inc_counter(mzx_world, \"LIVES\", 1, 0);\n      set_mesg(mzx_world, \"You find a free life!\");\n      remove_item = true;\n      break;\n    }\n\n    case INVIS_WALL:\n    {\n      cur_board->level_id[offset] = (char)NORMAL;\n      set_mesg(mzx_world, \"Oof! You ran into an invisible wall.\");\n      play_sfx(mzx_world, SFX_INVIS_WALL);\n      break;\n    }\n\n    case MINE:\n    {\n      cur_board->level_id[offset] = (char)EXPLOSION;\n      cur_board->level_param[offset] = param & 240;\n      play_sfx(mzx_world, SFX_EXPLOSION);\n      break;\n    }\n\n    case EYE:\n    {\n      cur_board->level_id[offset] = (char)EXPLOSION;\n      cur_board->level_param[offset] = (param << 1) & 112;\n      break;\n    }\n\n    case THIEF:\n    {\n      dec_counter(mzx_world, \"GEMS\", (param & 128) >> 7, 0);\n      play_sfx(mzx_world, SFX_STOLEN_GEM);\n      break;\n    }\n\n    case SLIMEBLOB:\n    {\n      if(param & 64)\n        hurt_player(mzx_world, SLIMEBLOB);\n\n      if(param & 128)\n        break;\n\n      cur_board->level_id[offset] = (char)BREAKAWAY;\n      break;\n    }\n\n    case GHOST:\n    {\n      hurt_player(mzx_world, GHOST);\n\n      // Die !?\n      if(!(param & 8))\n        remove_item = true;\n\n      break;\n    }\n\n    case DRAGON:\n    {\n      hurt_player(mzx_world, DRAGON);\n      break;\n    }\n\n    case FISH:\n    {\n      if(param & 64)\n      {\n        hurt_player(mzx_world, FISH);\n        remove_item = true;\n      }\n      break;\n    }\n\n    case ROBOT:\n    {\n      int idx = param;\n\n      // update last touched direction\n      cur_board->robot_list[idx]->last_touch_dir =\n       int_to_dir(flip_dir(src_dir));\n\n      send_robot_def(mzx_world, param, LABEL_TOUCH);\n      break;\n    }\n\n    case SIGN:\n    case SCROLL:\n    {\n      int idx = param;\n      play_sfx(mzx_world, SFX_SCROLL_SIGN);\n\n      scroll_edit(mzx_world, cur_board->scroll_list[idx], id & 1);\n\n      if(id == SCROLL)\n        remove_item = true;\n\n      break;\n    }\n\n    default:\n      break;\n  }\n\n  if(remove_item)\n  {\n    // If this is a consumable item, remove it and put the player where it was.\n    // FIXME has_context_changed\n    offs_remove_id(mzx_world, offset);\n    place_player(mzx_world, item_x, item_y, src_dir);\n  }\n\n  return;\n}\n\n// mzx_world->player_moved will be true if the player moved, otherwise false.\n// This function is guaranteed to keep the player position correctly updated;\n// using find_player after using this function is not necessary.\nvoid move_player(struct world *mzx_world, int dir)\n{\n  struct board *src_board = mzx_world->current_board;\n  // Dir is from 0 to 3\n  int player_x = mzx_world->player_x;\n  int player_y = mzx_world->player_y;\n  int new_x = player_x;\n  int new_y = player_y;\n  int edge = 0;\n\n  mzx_world->player_moved = false;\n\n  switch(dir)\n  {\n    case 0:\n      if(--new_y < 0)\n        edge = 1;\n      break;\n    case 1:\n      if(++new_y >= src_board->board_height)\n        edge = 1;\n      break;\n    case 2:\n      if(++new_x >= src_board->board_width)\n        edge = 1;\n      break;\n    case 3:\n      if(--new_x < 0)\n        edge = 1;\n      break;\n  }\n\n  if(edge)\n  {\n    // Hit an edge, teleport to another board?\n    int board_dir = src_board->board_dir[dir];\n    // Board must be valid\n    if((board_dir == NO_BOARD) ||\n     (board_dir >= mzx_world->num_boards) ||\n     (!mzx_world->board_list[board_dir]))\n    {\n      return;\n    }\n\n    mzx_world->target_board = board_dir;\n    mzx_world->target_where = TARGET_POSITION;\n    mzx_world->target_x = player_x;\n    mzx_world->target_y = player_y;\n\n    switch(dir)\n    {\n      case 0: // North- Enter south side\n      {\n        mzx_world->target_y =\n         (mzx_world->board_list[board_dir])->board_height - 1;\n        break;\n      }\n\n      case 1: // South- Enter north side\n      {\n        mzx_world->target_y = 0;\n        break;\n      }\n\n      case 2: // East- Enter west side\n      {\n        mzx_world->target_x = 0;\n        break;\n      }\n\n      case 3: // West- Enter east side\n      {\n        mzx_world->target_x =\n         (mzx_world->board_list[board_dir])->board_width - 1;\n        break;\n      }\n    }\n    src_board->player_last_dir =\n     (src_board->player_last_dir & 240) + dir + 1;\n\n    mzx_world->player_moved = true;\n    return;\n  }\n  else\n  {\n    // Not edge\n    int d_offset = new_x + (new_y * src_board->board_width);\n    enum thing d_id = (enum thing)src_board->level_id[d_offset];\n    enum thing u_id = (enum thing)src_board->level_under_id[d_offset];\n    int d_flag = flags[(int)d_id];\n\n    if(d_flag & A_SPEC_STOOD)\n    {\n      // Sensor\n      // Activate label and then move player\n      step_sensor(mzx_world, src_board->level_param[d_offset]);\n      place_player(mzx_world, new_x, new_y, dir);\n      return;\n    }\n    else\n\n    if(d_flag & A_ENTRANCE)\n    {\n      // Entrance\n      entrance(mzx_world, new_x, new_y);\n      place_player(mzx_world, new_x, new_y, dir);\n      return;\n    }\n    else\n\n    if((d_flag & A_ITEM) && (d_id != ROBOT_PUSHABLE))\n    {\n      // Item (handles player movement separately)\n      grab_item(mzx_world, new_x, new_y, dir);\n      return;\n    }\n    else\n\n    if(d_flag & A_UNDER)\n    {\n      // Under\n      place_player(mzx_world, new_x, new_y, dir);\n      return;\n    }\n    else\n\n    if((d_flag & A_ENEMY) || (d_flag & A_HURTS))\n    {\n      if(d_id == BULLET)\n      {\n        // Bullet\n        if((src_board->level_param[d_offset] >> 2) == PLAYER_BULLET)\n        {\n          // Player bullet no hurty\n          id_remove_top(mzx_world, new_x, new_y);\n          place_player(mzx_world, new_x, new_y, dir);\n          return;\n        }\n        else\n        {\n          // Enemy or hurtsy\n          dec_counter(mzx_world, \"HEALTH\", id_dmg[61], 0);\n          play_sfx(mzx_world, SFX_HURT);\n          set_mesg(mzx_world, \"Ouch!\");\n        }\n      }\n      else\n      {\n        dec_counter(mzx_world, \"HEALTH\", id_dmg[(int)d_id], 0);\n        play_sfx(mzx_world, SFX_HURT);\n        set_mesg(mzx_world, \"Ouch!\");\n\n        if(d_flag & A_ENEMY)\n        {\n          // Kill/move\n          id_remove_top(mzx_world, new_x, new_y);\n\n          // Not onto goop.. (under is now top)\n          if(u_id != GOOP && !src_board->restart_if_zapped)\n          {\n            place_player(mzx_world, new_x, new_y, dir);\n            return;\n          }\n        }\n      }\n    }\n    else\n    {\n      int dir_mask;\n      // Check for push\n      if(dir > 1)\n        dir_mask = d_flag & A_PUSHEW;\n      else\n        dir_mask = d_flag & A_PUSHNS;\n\n      if(dir_mask || (d_flag & A_SPEC_PUSH))\n      {\n        // Push\n        // Pushable robot needs to be sent the touch label\n        if(d_id == ROBOT_PUSHABLE)\n          send_robot_def(mzx_world,\n           src_board->level_param[d_offset], LABEL_TOUCH);\n\n        if(!push(mzx_world, player_x, player_y, dir, 0))\n        {\n          place_player(mzx_world, new_x, new_y, dir);\n          return;\n        }\n      }\n    }\n  }\n}\n\n// Attempt to use an entrance located at the given position.\nvoid entrance(struct world *mzx_world, int x, int y)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int offset = xy_to_offset(cur_board, x, y);\n  enum thing id;\n  int color;\n  int board;\n\n  if(cur_board->level_id[offset] == PLAYER)\n  {\n    id = cur_board->level_under_id[offset];\n    color = cur_board->level_under_color[offset];\n    board = cur_board->level_under_param[offset];\n  }\n  else\n  {\n    id = cur_board->level_id[offset];\n    color = cur_board->level_color[offset];\n    board = cur_board->level_param[offset];\n  }\n\n  if(!(flags[id] & A_ENTRANCE))\n    return;\n\n  play_sfx(mzx_world, SFX_ENTRANCE);\n\n  // MegaZeux 1.x allows entrances on a board to link to other entrances\n  // on the same board. This was removed in MegaZeux 2.00.\n  if((board != mzx_world->current_board_id || mzx_world->version < V200) &&\n   (board < mzx_world->num_boards) && mzx_world->board_list[board])\n  {\n    mzx_world->target_board = board;\n    mzx_world->target_where = TARGET_ENTRANCE;\n    mzx_world->target_color = color;\n    mzx_world->target_id = id;\n  }\n}\n\nvoid find_player(struct world *mzx_world)\n{\n  struct board *src_board = mzx_world->current_board;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  char *level_id = src_board->level_id;\n  int dx, dy, offset;\n\n  if(mzx_world->player_x >= board_width)\n    mzx_world->player_x = 0;\n\n  if(mzx_world->player_y >= board_height)\n    mzx_world->player_y = 0;\n\n  if((enum thing)level_id[mzx_world->player_x +\n   (mzx_world->player_y * board_width)] != PLAYER)\n  {\n    for(dy = 0, offset = 0; dy < board_height; dy++)\n    {\n      for(dx = 0; dx < board_width; dx++, offset++)\n      {\n        if((enum thing)level_id[offset] == PLAYER)\n        {\n          mzx_world->player_x = dx;\n          mzx_world->player_y = dy;\n          return;\n        }\n      }\n    }\n\n    replace_player(mzx_world);\n  }\n}\n"
  },
  {
    "path": "src/game_player.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __GAME_PLAYER_H\n#define __GAME_PLAYER_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"world_struct.h\"\n\nCORE_LIBSPEC void find_player(struct world *mzx_world);\n\nvoid set_mesg(struct world *mzx_world, const char *str);\nvoid set_mesg_direct(struct board *src_board, const char *str);\n\nboolean player_can_save(struct world *mzx_world);\nvoid player_switch_bomb_type(struct world *mzx_world);\nvoid player_cheat_give_all(struct world *mzx_world);\nvoid player_cheat_zap(struct world *mzx_world);\n\nvoid hurt_player(struct world *mzx_world, enum thing damage_src);\nint take_key(struct world *mzx_world, int color);\nint give_key(struct world *mzx_world, int color);\nvoid grab_item(struct world *mzx_world, int item_x, int item_y, int src_dir);\nvoid move_player(struct world *mzx_world, int dir);\nvoid entrance(struct world *mzx_world, int x, int y);\n\n__M_END_DECLS\n\n#endif // __GAME_PLAYER_H\n"
  },
  {
    "path": "src/game_update.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2018-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"caption.h\"\n#include \"core.h\"\n#include \"counter.h\"\n#include \"event.h\"\n#include \"game.h\"\n#include \"game_ops.h\"\n#include \"game_player.h\"\n#include \"game_update.h\"\n#include \"graphics.h\"\n#include \"idarray.h\"\n#include \"idput.h\"\n#include \"sprite.h\"\n#include \"robot.h\"\n#include \"util.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n\n#include \"audio/audio.h\"\n#include \"audio/sfx.h\"\n\n#include <string.h>\n\n// Number of cycles to make player idle before repeating a\n// directional move\n#define REPEAT_WAIT 2\n\n// Length of cooldown between player shots, including current frame.\n#define MAX_PLAYER_SHOT_COOLDOWN 2\n\n// Updates game variables\n// Slowed = 1 to not update lazwall or time\n// due to slowtime or freezetime\n\nvoid update_scroll_color(void)\n{\n  static boolean scroll_color_flip_flop;\n  scroll_color_flip_flop = !scroll_color_flip_flop;\n\n  // Change scroll color\n  if(!scroll_color_flip_flop)\n  {\n    scroll_color++;\n\n    if(scroll_color > 15)\n      scroll_color = 7;\n  }\n}\n\nstatic void update_variables(struct world *mzx_world)\n{\n  struct board *src_board = mzx_world->current_board;\n  int blind_dur = mzx_world->blind_dur;\n  int firewalker_dur = mzx_world->firewalker_dur;\n  int freeze_time_dur = mzx_world->freeze_time_dur;\n  int slow_time_dur = mzx_world->slow_time_dur;\n  int wind_dur = mzx_world->wind_dur;\n  int b_mesg_timer = src_board->b_mesg_timer;\n  int invinco;\n  int lazwall_start = src_board->lazwall_start;\n\n  // Determine whether the current cycle is frozen\n  if(mzx_world->freeze_time_dur)\n  {\n    mzx_world->current_cycle_frozen = true;\n  }\n  else\n  if(mzx_world->slow_time_dur)\n  {\n    mzx_world->current_cycle_frozen = !mzx_world->current_cycle_frozen;\n  }\n  else\n  {\n    mzx_world->current_cycle_frozen = false;\n  }\n\n  // Toggle board cycle flip flop\n  if(!mzx_world->current_cycle_frozen)\n    mzx_world->current_cycle_odd = !mzx_world->current_cycle_odd;\n\n  // Also update the scroll color.\n  update_scroll_color();\n\n  // Decrease time limit (unless this is a frozen cycle)\n  if(!mzx_world->current_cycle_odd && !mzx_world->current_cycle_frozen)\n  {\n    int time_left = get_counter(mzx_world, \"TIME\", 0);\n    if(time_left > 0)\n    {\n      if(time_left == 1)\n      {\n        // Out of time\n        dec_counter(mzx_world, \"HEALTH\", 10, 0);\n        set_mesg(mzx_world, \"Out of time!\");\n        play_sfx(mzx_world, SFX_OUT_OF_TIME);\n        // Reset time\n        set_counter(mzx_world, \"TIME\", src_board->time_limit, 0);\n      }\n      else\n      {\n        dec_counter(mzx_world, \"TIME\", 1, 0);\n      }\n    }\n  }\n\n  // Decrease effect durations\n  if(blind_dur > 0)\n    mzx_world->blind_dur = blind_dur - 1;\n\n  if(firewalker_dur > 0)\n    mzx_world->firewalker_dur = firewalker_dur - 1;\n\n  if(freeze_time_dur > 0)\n    mzx_world->freeze_time_dur = freeze_time_dur - 1;\n\n  if(slow_time_dur > 0)\n    mzx_world->slow_time_dur = slow_time_dur - 1;\n\n  if(wind_dur > 0)\n    mzx_world->wind_dur = wind_dur - 1;\n\n  // Decrease message timer\n  if(b_mesg_timer > 0)\n    src_board->b_mesg_timer = b_mesg_timer - 1;\n\n  // Invinco\n  invinco = get_counter(mzx_world, \"INVINCO\", 0);\n  if(invinco > 0)\n  {\n    if(invinco == 1)\n    {\n      // Just finished-\n      set_counter(mzx_world, \"INVINCO\", 0, 0);\n    }\n    else\n    {\n      // Decrease\n      dec_counter(mzx_world, \"INVINCO\", 1, 0);\n      play_sfx(mzx_world, SFX_INVINCO_BEAT);\n      id_chars[player_color] = Random(256);\n    }\n  }\n\n  // Lazerwall start- cycle 0 to 7 then -7 to -1\n  if(!mzx_world->current_cycle_frozen)\n  {\n    if(((signed char)lazwall_start) < 7)\n      src_board->lazwall_start = lazwall_start + 1;\n    else\n      src_board->lazwall_start = -7;\n  }\n}\n\n/**\n * Update the current mod's volume if a mod fade is active.\n */\n\nstatic void update_mod_volume(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int volume_target = cur_board->volume_target;\n  int volume_inc = cur_board->volume_inc;\n  int volume = cur_board->volume;\n\n  if(volume_inc)\n  {\n    volume += volume_inc;\n\n    if(volume_inc < 0)\n    {\n      if(volume <= volume_target)\n      {\n        volume = volume_target;\n        cur_board->volume_inc = 0;\n      }\n    }\n    else\n    {\n      if(volume >= volume_target)\n      {\n        volume = volume_target;\n        cur_board->volume_inc = 0;\n      }\n    }\n    cur_board->volume = volume;\n    audio_set_module_volume(volume);\n  }\n}\n\n/**\n * Handle all player effects caused by things beneath the player.\n * May result in a context change.\n */\n\nstatic void update_player_under(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int player_x = mzx_world->player_x;\n  int player_y = mzx_world->player_y;\n  int offset = xy_to_offset(cur_board, player_x, player_y);\n  enum thing under_id = (enum thing)cur_board->level_under_id[offset];\n\n  // NOTE: flags[under_id] & A_AFFECT_IF_STOOD\n  // All types handled here use this flag, however, checking it here isn't\n  // really necessary anymore. However, this flag might be useful in the\n  // future if user-defined types are implemented.\n\n  switch(under_id)\n  {\n    case N_WATER:\n    {\n      move_player(mzx_world, 0);\n      return;\n    }\n\n    case S_WATER:\n    {\n      move_player(mzx_world, 1);\n      return;\n    }\n\n    case E_WATER:\n    {\n      move_player(mzx_world, 2);\n      return;\n    }\n\n    case W_WATER:\n    {\n      move_player(mzx_world, 3);\n      return;\n    }\n\n    case ICE:\n    {\n      int player_last_dir = cur_board->player_last_dir;\n      if(player_last_dir & 0x0F)\n      {\n        move_player(mzx_world, (player_last_dir & 0x0F) - 1);\n\n        // FIXME has_context_changed\n        if(!mzx_world->player_moved)\n          cur_board->player_last_dir = player_last_dir & 0xF0;\n      }\n      return;\n    }\n\n    case LAVA:\n    {\n      if(mzx_world->firewalker_dur > 0)\n        break;\n\n      play_sfx(mzx_world, SFX_LAVA);\n      set_mesg(mzx_world, \"Augh!\");\n      dec_counter(mzx_world, \"HEALTH\", id_dmg[26], 0);\n      return;\n    }\n\n    case FIRE:\n    {\n      if(mzx_world->firewalker_dur > 0)\n        break;\n\n      play_sfx(mzx_world, SFX_FIRE_HURT);\n      set_mesg(mzx_world, \"Ouch!\");\n      dec_counter(mzx_world, \"HEALTH\", id_dmg[63], 0);\n      return;\n    }\n\n    default:\n    {\n      return;\n    }\n  }\n}\n\n/**\n * Apply wind to the player if wind is currently active.\n * May result in a context change.\n */\n\nstatic void update_player_wind(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n\n  if(mzx_world->wind_dur > 0)\n  {\n    int wind_dir = Random(9);\n    if(wind_dir < 4)\n    {\n      // No wind this turn if above 3\n      cur_board->player_last_dir =\n       (cur_board->player_last_dir & 0xF0) + wind_dir;\n      move_player(mzx_world, wind_dir);\n    }\n  }\n}\n\n/**\n * Handle all user player input.\n * May result in a context change.\n */\n\nstatic void update_player_input(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int space_pressed = get_key_status(keycode_internal_wrt_numlock, IKEY_SPACE);\n  int up_pressed = get_key_status(keycode_internal_wrt_numlock, IKEY_UP);\n  int down_pressed = get_key_status(keycode_internal_wrt_numlock, IKEY_DOWN);\n  int right_pressed = get_key_status(keycode_internal_wrt_numlock, IKEY_RIGHT);\n  int left_pressed = get_key_status(keycode_internal_wrt_numlock, IKEY_LEFT);\n  int del_pressed = get_key_status(keycode_internal_wrt_numlock, IKEY_DELETE);\n\n  // Shoot\n  if(space_pressed && mzx_world->bi_shoot_status)\n  {\n    if(!mzx_world->player_shoot_cooldown && !cur_board->player_attack_locked)\n    {\n      int move_dir = -1;\n\n      if(up_pressed)\n        move_dir = 0;\n      else\n\n      if(down_pressed)\n        move_dir = 1;\n      else\n\n      if(right_pressed)\n        move_dir = 2;\n      else\n\n      if(left_pressed)\n        move_dir = 3;\n\n      if(move_dir != -1)\n      {\n        if(!cur_board->can_shoot)\n        {\n          set_mesg(mzx_world, \"Can't shoot here!\");\n        }\n        else\n\n        if(!get_counter(mzx_world, \"AMMO\", 0))\n        {\n          set_mesg(mzx_world, \"You are out of ammo!\");\n          play_sfx(mzx_world, SFX_OUT_OF_AMMO);\n        }\n\n        else\n        {\n          dec_counter(mzx_world, \"AMMO\", 1, 0);\n          play_sfx(mzx_world, SFX_SHOOT);\n          shoot(mzx_world, mzx_world->player_x, mzx_world->player_y,\n           move_dir, PLAYER_BULLET);\n          mzx_world->player_shoot_cooldown = MAX_PLAYER_SHOT_COOLDOWN;\n          cur_board->player_last_dir =\n           (cur_board->player_last_dir & 0x0F) | (move_dir << 4);\n        }\n      }\n    }\n  }\n  else\n\n  // Player movement\n  if(up_pressed && !cur_board->player_ns_locked)\n  {\n    int key_up_delay = mzx_world->key_up_delay;\n    if((key_up_delay == 0) || (key_up_delay > REPEAT_WAIT))\n    {\n      move_player(mzx_world, 0);\n      cur_board->player_last_dir = (cur_board->player_last_dir & 0x0F);\n    }\n    if(key_up_delay <= REPEAT_WAIT)\n      mzx_world->key_up_delay = key_up_delay + 1;\n  }\n  else\n\n  if(down_pressed && !cur_board->player_ns_locked)\n  {\n    int key_down_delay = mzx_world->key_down_delay;\n    if((key_down_delay == 0) || (key_down_delay > REPEAT_WAIT))\n    {\n      move_player(mzx_world, 1);\n      cur_board->player_last_dir =\n       (cur_board->player_last_dir & 0x0F) + 0x10;\n    }\n    if(key_down_delay <= REPEAT_WAIT)\n      mzx_world->key_down_delay = key_down_delay + 1;\n  }\n  else\n\n  if(right_pressed && !cur_board->player_ew_locked)\n  {\n    int key_right_delay = mzx_world->key_right_delay;\n    if((key_right_delay == 0) || (key_right_delay > REPEAT_WAIT))\n    {\n      move_player(mzx_world, 2);\n      cur_board->player_last_dir =\n       (cur_board->player_last_dir & 0x0F) + 0x20;\n    }\n    if(key_right_delay <= REPEAT_WAIT)\n      mzx_world->key_right_delay = key_right_delay + 1;\n  }\n  else\n\n  if(left_pressed && !cur_board->player_ew_locked)\n  {\n    int key_left_delay = mzx_world->key_left_delay;\n    if((key_left_delay == 0) || (key_left_delay > REPEAT_WAIT))\n    {\n      move_player(mzx_world, 3);\n      cur_board->player_last_dir =\n       (cur_board->player_last_dir & 0x0F) + 0x30;\n    }\n    if(key_left_delay <= REPEAT_WAIT)\n      mzx_world->key_left_delay = key_left_delay + 1;\n  }\n\n  // Reset timers when all of the movement keys are released. Some games rely\n  // on this persisting when locking the player, e.g. Brotherhood periodically\n  // locks the player when moving in water.\n  // NOTE: the pre-port behavior was to reset these on individual key releases.\n  // From user feedback, this behavior generally seems preferred.\n  if(!up_pressed && !down_pressed && !right_pressed && !left_pressed)\n  {\n    mzx_world->key_up_delay = 0;\n    mzx_world->key_down_delay = 0;\n    mzx_world->key_right_delay = 0;\n    mzx_world->key_left_delay = 0;\n  }\n\n  // Bomb\n  if(del_pressed && !cur_board->player_attack_locked)\n  {\n    int offset =\n     xy_to_offset(cur_board, mzx_world->player_x, mzx_world->player_y);\n    enum thing under_id = (enum thing)cur_board->level_under_id[offset];\n    char under_param = cur_board->level_under_param[offset];\n    char under_color = cur_board->level_under_color[offset];\n\n    if(under_id != LIT_BOMB)\n    {\n      if(!cur_board->can_bomb)\n      {\n        set_mesg(mzx_world, \"Can't bomb here!\");\n      }\n      else\n\n      if((mzx_world->bomb_type == 0) && !get_counter(mzx_world, \"LOBOMBS\", 0))\n      {\n        set_mesg(mzx_world, \"You are out of low strength bombs!\");\n        play_sfx(mzx_world, SFX_OUT_OF_BOMBS);\n      }\n      else\n\n      if((mzx_world->bomb_type == 1) && !get_counter(mzx_world, \"HIBOMBS\", 0))\n      {\n        set_mesg(mzx_world, \"You are out of high strength bombs!\");\n        play_sfx(mzx_world, SFX_OUT_OF_BOMBS);\n      }\n      else\n\n      if(!(flags[under_id] & A_ENTRANCE))\n      {\n        // Bomb!\n        mzx_world->under_player_id = under_id;\n        mzx_world->under_player_color = under_color;\n        mzx_world->under_player_param = under_param;\n        cur_board->level_under_id[offset] = LIT_BOMB;\n        cur_board->level_under_color[offset] = 8;\n        cur_board->level_under_param[offset] = mzx_world->bomb_type << 7;\n\n        if(mzx_world->bomb_type)\n        {\n          play_sfx(mzx_world, SFX_PLACE_HI_BOMB);\n          dec_counter(mzx_world, \"HIBOMBS\", 1, 0);\n        }\n        else\n        {\n          play_sfx(mzx_world, SFX_PLACE_LO_BOMB);\n          dec_counter(mzx_world, \"LOBOMBS\", 1, 0);\n        }\n      }\n    }\n  }\n\n  if(mzx_world->player_shoot_cooldown)\n    mzx_world->player_shoot_cooldown--;\n}\n\n/**\n * Is the player on an entrance?\n */\n\nstatic boolean player_on_entrance(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int player_x = mzx_world->player_x;\n  int player_y = mzx_world->player_y;\n  int offset = xy_to_offset(cur_board, player_x, player_y);\n\n  int under_id = cur_board->level_under_id[offset];\n\n  if(flags[under_id] & A_ENTRANCE)\n    return true;\n\n  return false;\n}\n\n/**\n * Alter char IDs to hide the player from view. Used only on the title screen.\n */\n\nstatic void hide_player(struct world *mzx_world)\n{\n  id_chars[player_color] = 0;\n  id_chars[player_char + 0] = 32;\n  id_chars[player_char + 1] = 32;\n  id_chars[player_char + 2] = 32;\n  id_chars[player_char + 3] = 32;\n}\n\n/**\n * Focus the screen on the player.\n * Mainly for platforms with screens too small to display the whole world.\n */\n\nstatic void focus_on_player(struct world *mzx_world)\n{\n  int player_x   = mzx_world->player_x;\n  int player_y   = mzx_world->player_y;\n  int viewport_x = mzx_world->current_board->viewport_x;\n  int viewport_y = mzx_world->current_board->viewport_y;\n  int top_x, top_y;\n\n  calculate_xytop(mzx_world, &top_x, &top_y);\n\n  // Focus on the player\n  focus_screen(player_x - top_x + viewport_x, player_y - top_y + viewport_y);\n}\n\n/**\n * End the game (player lives reached zero).\n */\n\nstatic void end_game(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int endgame_board = mzx_world->endgame_board;\n  int endgame_x = mzx_world->endgame_x;\n  int endgame_y = mzx_world->endgame_y;\n\n  // Game over\n  if(endgame_board != NO_ENDGAME_BOARD)\n  {\n    // Jump to given board\n    if(mzx_world->current_board_id == endgame_board)\n    {\n      id_remove_top(mzx_world, mzx_world->player_x,\n        mzx_world->player_y);\n      id_place(mzx_world, endgame_x, endgame_y, PLAYER, 0, 0);\n      mzx_world->player_x = endgame_x;\n      mzx_world->player_y = endgame_y;\n    }\n    else\n    {\n      mzx_world->target_board = endgame_board;\n      mzx_world->target_where = TARGET_POSITION;\n      mzx_world->target_x = endgame_x;\n      mzx_world->target_y = endgame_y;\n    }\n\n    // In pre-port versions, the endgame board would be disabled after this\n    // teleport. This was changed in 2.80: due to a bug, the endgame board\n    // would never be disabled, in effect preventing the game over state from\n    // ever occurring. We like the new behavior better, but DOS worlds may not.\n    if(mzx_world->version < VERSION_PORT)\n      mzx_world->endgame_board = NO_ENDGAME_BOARD;\n\n    // Give one more life with minimal health\n    set_counter(mzx_world, \"LIVES\", 1, 0);\n    set_counter(mzx_world, \"HEALTH\", 1, 0);\n  }\n  else\n  {\n    set_mesg(mzx_world, \"Game over\");\n    /* I can't imagine anything actually relied on this obtuse misbehavior\n     * but it's good to version lock anyhow.\n     */\n    if((mzx_world->version <= V283) || mzx_world->bi_mesg_status)\n    {\n      cur_board->b_mesg_row = 24;\n      cur_board->b_mesg_col = -1;\n    }\n\n    if(mzx_world->game_over_sfx)\n      play_sfx(mzx_world, SFX_GAME_OVER);\n\n    mzx_world->dead = true;\n  }\n}\n\n/**\n * End the player's current life.\n */\n\nstatic void end_life(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int death_board = mzx_world->death_board;\n\n  // Death\n  set_counter(mzx_world, \"HEALTH\", mzx_world->starting_health, 0);\n  dec_counter(mzx_world, \"Lives\", 1, 0);\n  set_mesg(mzx_world, \"You have died...\");\n  sfx_clear_queue();\n  play_sfx(mzx_world, SFX_DEATH);\n\n  if(mzx_world->version >= V293)\n    send_robot_def(mzx_world, 0, LABEL_PLAYERDIED);\n\n  // Go somewhere else?\n  if(death_board != DEATH_SAME_POS)\n  {\n    if(death_board == NO_DEATH_BOARD)\n    {\n      int player_restart_x = mzx_world->player_restart_x;\n      int player_restart_y = mzx_world->player_restart_y;\n\n      if(player_restart_x >= cur_board->board_width)\n        player_restart_x = cur_board->board_width - 1;\n\n      if(player_restart_y >= cur_board->board_height)\n        player_restart_y = cur_board->board_height - 1;\n\n      // Return to entry x/y\n      id_remove_top(mzx_world, mzx_world->player_x, mzx_world->player_y);\n      id_place(mzx_world, player_restart_x, player_restart_y, PLAYER, 0, 0);\n      mzx_world->player_x = player_restart_x;\n      mzx_world->player_y = player_restart_y;\n    }\n    else\n    {\n      // Jump to given board\n      if(mzx_world->current_board_id == death_board)\n      {\n        int death_x = mzx_world->death_x;\n        int death_y = mzx_world->death_y;\n\n        id_remove_top(mzx_world, mzx_world->player_x, mzx_world->player_y);\n        id_place(mzx_world, death_x, death_y, PLAYER, 0, 0);\n        mzx_world->player_x = death_x;\n        mzx_world->player_y = death_y;\n      }\n      else\n      {\n        mzx_world->target_board = death_board;\n        mzx_world->target_where = TARGET_POSITION;\n        mzx_world->target_x = mzx_world->death_x;\n        mzx_world->target_y = mzx_world->death_y;\n      }\n    }\n  }\n}\n\n/**\n * Update the world, including the player, board, and robots.\n * Draw the world/sprites/etc too.\n */\nvoid update_world(context *ctx, boolean is_title)\n{\n  struct world *mzx_world = ctx->world;\n\n  if(!is_title && mzx_world->version >= V251s1 &&\n   get_counter(mzx_world, \"CURSORSTATE\", 0))\n  {\n    // Turn on mouse\n    m_show();\n  }\n  else\n  {\n    // Turn off mouse\n    m_hide();\n  }\n\n  // Update\n  update_variables(mzx_world);\n  update_mod_volume(mzx_world);\n  update_player_under(mzx_world); // Ice, fire, water, lava\n  update_player_wind(mzx_world);\n\n  if(!is_title && (!mzx_world->dead))\n    update_player_input(mzx_world);\n\n  // Global robot\n  if(mzx_world->current_board->robot_list[0])\n    if(mzx_world->current_board->robot_list[0]->used)\n      run_robot(ctx, 0, -1, -1);\n\n  if(!mzx_world->current_cycle_frozen)\n  {\n    mzx_world->player_was_on_entrance = player_on_entrance(mzx_world);\n    mzx_world->was_zapped = false;\n\n    update_board(ctx);\n\n    if(player_on_entrance(mzx_world) && !mzx_world->player_was_on_entrance &&\n     !mzx_world->was_zapped && mzx_world->version >= V200)\n    {\n      // Pushed onto an entrance (2.00+)\n      // There's often a pushed sound in this case, so clear the current SFX\n      sfx_clear_queue();\n      entrance(mzx_world, mzx_world->player_x, mzx_world->player_y);\n    }\n  }\n  else\n  {\n    // Find the player in case the player was moved by the global robot\n    find_player(mzx_world);\n  }\n\n  // The player is done moving now, so focus the screen on it if needed.\n  if(!is_title)\n    focus_on_player(mzx_world);\n\n  // On the title screen, the player needs to be hidden (and a robot might have\n  // tried to change this), so hide it.\n  if(is_title)\n    hide_player(mzx_world);\n\n  // Death and game over\n  if(get_counter(mzx_world, \"LIVES\", 0) == 0)\n  {\n    end_game(mzx_world);\n  }\n  else\n\n  if(get_counter(mzx_world, \"HEALTH\", 0) == 0)\n  {\n    end_life(mzx_world);\n  }\n}\n\n/**\n * Draw the built-in message to the screen.\n */\n\nstatic void draw_message(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int mesg_y = cur_board->b_mesg_row;\n  unsigned char tmp_color = scroll_color;\n  char *lines[25];\n  int i = 1;\n  int j;\n\n  /* Safety check: bad values can get into these vars from\n   * old worlds/saves and cause out-of-bounds draws. */\n  if(mesg_y < 0 || mesg_y >= SCREEN_H ||\n   cur_board->b_mesg_col < -1 || cur_board->b_mesg_col >= SCREEN_W)\n    return;\n\n  /* Always at least one line.. */\n  lines[0] = cur_board->bottom_mesg;\n\n  /* Find pointers to each \"line\" terminated by \\n */\n  while(1)\n  {\n    char *pos = strchr(lines[i - 1], '\\n');\n    if(!pos)\n      break;\n    *pos = 0;\n    if(i >= 25 - mesg_y)\n      break;\n    lines[i] = pos + 1;\n    i++;\n  }\n\n  for(j = 0; j < i; j++)\n  {\n    int mesg_length = color_string_length(lines[j], ROBOT_MAX_TR);\n    int mesg_edges = mzx_world->mesg_edges;\n    int mesg_x = cur_board->b_mesg_col;\n    int clip_x = (mesg_x >= 0) ? mesg_x : 0;\n    int clip_pos = 80;\n    char backup = 0;\n\n    if(mzx_world->version >= V294 && mesg_length > (80 - clip_x))\n    {\n      clip_pos = color_string_index_of(lines[j], ROBOT_MAX_TR, 80 - clip_x, '\\0');\n      backup = lines[j][clip_pos];\n      lines[j][clip_pos] = 0;\n      mesg_length = 80 - clip_x;\n    }\n    else\n\n    if(mzx_world->version < V294 && mesg_length > 80)\n    {\n      // Compat for 2.83 through 2.93's buggy clipping behavior...\n      backup = lines[j][80];\n      lines[j][80] = 0;\n      mesg_length = 80;\n    }\n\n    if(mesg_x == -1)\n      mesg_x = 40 - (mesg_length / 2);\n\n    color_string_ext_special(lines[j], mesg_x, mesg_y, &tmp_color, false, 0, 0);\n\n    if((mesg_x > 0) && (mesg_edges))\n      draw_char_ext(' ', scroll_color, mesg_x - 1, mesg_y, 0, 0);\n\n    mesg_x += mesg_length;\n    if((mesg_x < 80) && (mesg_edges))\n      draw_char_ext(' ', scroll_color, mesg_x, mesg_y, 0, 0);\n\n    if(backup)\n    {\n      lines[j][clip_pos] = backup;\n\n      // Versions until 2.94 didn't parse color codes after the clip.\n      if(mzx_world->version >= V294)\n      {\n        tmp_color = color_string_get_final_color(lines[j] + clip_pos,\n         ROBOT_MAX_TR, tmp_color);\n      }\n    }\n\n    mesg_y++;\n  }\n\n  /* Restore original bottom mesg for next iteration */\n  for(j = 1; j < i; j++)\n    *(lines[j] - 1) = '\\n';\n}\n\n/**\n * Draw the active world.\n */\n\nboolean draw_world(context *ctx, boolean is_title)\n{\n  struct world *mzx_world = ctx->world;\n  struct board *cur_board = mzx_world->current_board;\n  const struct config_info *conf = get_config();\n  int time_remaining;\n  int top_x;\n  int top_y;\n\n  char tmp_str[10];\n\n  // If a teleport command is used, don't draw the world (i.e leave the\n  // previous frame visible). This is a hacky way of preventing a board\n  // from appearing if a game teleports away from it on the first cycle\n  // the board is run. This is relied on by title screens (like Bernard\n  // the Bard) and probably in other places so we have to keep it around\n  // for compatibility.\n  if(mzx_world->target_where == TARGET_TELEPORT)\n  {\n    // This is passed to the main loop and will prevent update_screen() from\n    // being executed. Some games, such as Thanatos Insignia, set the color\n    // intensity before a teleport and rely on it not taking immediately.\n    return false;\n  }\n\n  blank_layers();\n\n  // Draw border\n  select_layer(GAME_UI_LAYER);\n  draw_viewport(cur_board, mzx_world->edge_color);\n\n  // Figure out x/y of top\n  calculate_xytop(mzx_world, &top_x, &top_y);\n\n  // Draw screen\n  if(mzx_world->blind_dur > 0)\n  {\n    int player_x = mzx_world->player_x;\n    int player_y = mzx_world->player_y;\n\n    // Only draw the player during gameplay\n    if(is_title)\n      player_x = -1;\n\n    draw_game_window_blind(cur_board, top_x, top_y, player_x, player_y);\n  }\n  else\n  {\n    draw_game_window(cur_board, top_x, top_y);\n  }\n\n  // Add sprites\n  select_layer(OVERLAY_LAYER);\n  draw_sprites(mzx_world);\n\n  // Add time limit\n  time_remaining = get_counter(mzx_world, \"TIME\", 0);\n  if(time_remaining)\n  {\n    int edge_color = mzx_world->edge_color;\n    int timer_color;\n    if(edge_color == 15)\n      timer_color = 0xF0; // Prevent white on white for timer\n    else\n      timer_color = (edge_color << 4) + 15;\n\n    select_layer(UI_LAYER);\n\n    sprintf(tmp_str, \"%d:%02d\",\n     (unsigned short)(time_remaining / 60), (time_remaining % 60));\n    write_string(tmp_str, 1, 24, timer_color, 0);\n\n    // Border with spaces\n    draw_char(' ', edge_color, (unsigned int)strlen(tmp_str) + 1, 24);\n    draw_char(' ', edge_color, 0, 24);\n  }\n\n  if(get_screen_mode() && !mzx_world->smzx_message)\n    select_layer(UI_LAYER);\n  else\n    select_layer(GAME_UI_LAYER);\n\n  // Add message\n  if(cur_board->b_mesg_timer > 0)\n    draw_message(mzx_world);\n\n  select_layer(UI_LAYER);\n  if(!conf->standalone_mode)\n    draw_intro_mesg(mzx_world);\n\n  // Add debug box\n  if(draw_debug_box && mzx_world->debug_mode)\n  {\n    draw_debug_box(mzx_world, 60, 19, mzx_world->player_x,\n     mzx_world->player_y, 1);\n  }\n  return true;\n}\n\n// In order of precedence, from highest to least. 1.x lacks some match types.\nenum target_match\n{\n  MATCH_COLOR_AND_THING,\n  MATCH_COLOR,\n  MATCH_FG_AND_THING,\n  MATCH_FG,\n  MATCH_THING,\n  NUM_TARGET_MATCHES\n};\n\n/**\n * If an entrance or the TELEPORT command was used, this state\n * needs to be resolved at the start of cycle execution. After a\n * target is resolved, keypresses for certain control features\n * such as the exit dialog are suppressed until the next cycle.\n */\nboolean update_resolve_target(struct world *mzx_world,\n boolean *fade_in_next_cycle)\n{\n  struct board *src_board = mzx_world->current_board;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  char *level_id = src_board->level_id;\n  char *level_color = src_board->level_color;\n  char *level_under_id = src_board->level_under_id;\n  int i;\n\n  if(mzx_world->target_where != TARGET_NONE)\n  {\n    int saved_player_last_dir = src_board->player_last_dir;\n    int target_board = mzx_world->target_board;\n    boolean is_different_board = false;\n    boolean load_assets = false;\n\n    // TELEPORT or ENTRANCE.\n\n    // Destroy message, bullets, spitfire?\n    if(mzx_world->clear_on_exit)\n    {\n      int offset;\n      enum thing d_id;\n\n      src_board->b_mesg_timer = 0;\n      for(offset = 0; offset < (board_width * board_height); offset++)\n      {\n        d_id = (enum thing)level_id[offset];\n        if((d_id == SHOOTING_FIRE) || (d_id == BULLET))\n          offs_remove_id(mzx_world, offset);\n      }\n    }\n\n    // Sprites can also optionally be disabled on exit.\n    for(i = 0; i < MAX_SPRITES; i++)\n    {\n      struct sprite *spr = mzx_world->sprite_list[i];\n\n      if((spr->flags & SPRITE_INITIALIZED) && (spr->flags & SPRITE_OFF_ON_EXIT))\n      {\n        spr->flags &= ~SPRITE_INITIALIZED;\n        mzx_world->active_sprites--;\n      }\n    }\n\n    // Load board\n    mzx_world->under_player_id = (char)SPACE;\n    mzx_world->under_player_param = 0;\n    mzx_world->under_player_color = 7;\n    mzx_world->current_cycle_odd = false;\n\n    if(mzx_world->current_board_id != target_board)\n      is_different_board = true;\n\n    if(is_different_board || src_board->reset_on_entry_same_board)\n    {\n      change_board(mzx_world, target_board);\n      load_assets = true;\n    }\n\n    src_board = mzx_world->current_board;\n\n    level_id = src_board->level_id;\n    level_color = src_board->level_color;\n    level_under_id = src_board->level_under_id;\n    board_width = src_board->board_width;\n    board_height = src_board->board_height;\n\n    // Find target x/y\n    if(mzx_world->target_where == TARGET_ENTRANCE)\n    {\n      int tmp_x[NUM_TARGET_MATCHES];\n      int tmp_y[NUM_TARGET_MATCHES];\n      int x, y, offset;\n      enum thing d_id;\n      enum thing target_id = mzx_world->target_id;\n      int target_color = mzx_world->target_color;\n\n      // Entrance\n      // First, set searched for id to the first in the whirlpool\n      // series if it is part of the whirlpool series\n      if(is_whirlpool(target_id))\n        target_id = WHIRLPOOL_1;\n\n      // Now scan. Order-\n      // 1) Same type & color\n      // 2) Same color\n      // 3) Same type & foreground\n      // 4) Same foreground\n      // 5) Same type\n      // Search for first of all 5 at once\n\n      for(i = 0; i < NUM_TARGET_MATCHES; i++)\n      {\n        // None found\n        tmp_x[i] = -1;\n        tmp_y[i] = -1;\n      }\n\n      for(y = 0, offset = 0; y < board_height; y++)\n      {\n        for(x = 0; x < board_width; x++, offset++)\n        {\n          d_id = (enum thing)level_id[offset];\n\n          if(d_id == PLAYER)\n          {\n            // Remove the player, maybe readd\n            mzx_world->player_x = x;\n            mzx_world->player_y = y;\n            id_remove_top(mzx_world, x, y);\n            // Grab again - might have revealed an entrance\n            d_id = (enum thing)level_id[offset];\n          }\n\n          if(is_whirlpool(d_id))\n            d_id = WHIRLPOOL_1;\n\n          if(d_id == target_id)\n          {\n            // Same type\n            // Color match?\n            if(level_color[offset] == target_color)\n            {\n              tmp_x[MATCH_COLOR_AND_THING] = x;\n              tmp_y[MATCH_COLOR_AND_THING] = y;\n              // 1.x doesn't support color+thing match, so set this too.\n              tmp_x[MATCH_COLOR] = x;\n              tmp_y[MATCH_COLOR] = y;\n            }\n            else\n\n            // Fg?\n            if((level_color[offset] & 0x0F) == (target_color & 0x0F))\n            {\n              tmp_x[MATCH_FG_AND_THING] = x;\n              tmp_y[MATCH_FG_AND_THING] = y;\n            }\n\n            tmp_x[MATCH_THING] = x;\n            tmp_y[MATCH_THING] = y;\n          }\n          else\n\n          if(flags[(int)d_id] & A_ENTRANCE)\n          {\n            // Not same type, but an entrance\n            // Color match?\n            if(level_color[offset] == target_color)\n            {\n              tmp_x[MATCH_COLOR] = x;\n              tmp_y[MATCH_COLOR] = y;\n            }\n            else\n\n            // Fg?\n            if((level_color[offset] & 0x0F) == (target_color & 0x0F))\n            {\n              tmp_x[MATCH_FG] = x;\n              tmp_y[MATCH_FG] = y;\n            }\n          }\n        }\n      }\n\n      // We've got it... maybe.\n      if(mzx_world->version >= V200)\n      {\n        for(i = 0; i < NUM_TARGET_MATCHES; i++)\n        {\n          if(tmp_x[i] >= 0)\n            break;\n        }\n      }\n      else\n      {\n        // MegaZeux 1.x only checks for exact color and exact thing matches.\n        if(tmp_x[MATCH_COLOR] >= 0)\n        {\n          i = MATCH_COLOR;\n        }\n        else\n\n        if(tmp_x[MATCH_THING] >= 0)\n        {\n          i = MATCH_THING;\n        }\n        else\n          i = NUM_TARGET_MATCHES;\n      }\n\n      if(i < NUM_TARGET_MATCHES)\n      {\n        mzx_world->player_x = tmp_x[i];\n        mzx_world->player_y = tmp_y[i];\n      }\n\n      id_place(mzx_world, mzx_world->player_x,\n       mzx_world->player_y, PLAYER, 0, 0);\n    }\n    else\n    {\n      int target_x = mzx_world->target_x;\n      int target_y = mzx_world->target_y;\n\n      // Specified x/y\n      if(target_x < 0)\n        target_x = 0;\n\n      if(target_y < 0)\n        target_y = 0;\n\n      if(target_x >= board_width)\n        target_x = board_width - 1;\n\n      if(target_y >= board_height)\n        target_y = board_height - 1;\n\n      find_player(mzx_world);\n      place_player_xy(mzx_world, target_x, target_y);\n    }\n\n    send_robot_def(mzx_world, 0, LABEL_JUSTENTERED);\n\n    // Set the time/restart position/etc even if the board hasn't changed.\n    change_board_set_values(mzx_world);\n\n    // Now... Set player_last_dir for direction FACED\n    src_board->player_last_dir = (src_board->player_last_dir & 0x0F) |\n     (saved_player_last_dir & 0xF0);\n\n    // ...and if player ended up on ICE, set last dir pressed as well\n    if((enum thing)level_under_id[mzx_world->player_x +\n     (mzx_world->player_y * board_width)] == ICE)\n    {\n      src_board->player_last_dir = saved_player_last_dir;\n    }\n\n    if(mzx_world->target_where != TARGET_TELEPORT)\n    {\n      // Prepare for fadein\n      if(!get_fade_status())\n        *fade_in_next_cycle = true;\n      vquick_fadeout();\n    }\n\n    // Load the new board's charset and palette, if necessary.\n    // If reset/load on same board is set, this should always be done.\n    if(load_assets)\n      change_board_load_assets(mzx_world);\n\n    if(is_different_board)\n    {\n      /* Load board's mod unless it's the same mod playing.\n       * Note: this was performed at the start of the NEXT cycle prior to 2.91g.\n       * Note: prior to 2.91g this wasn't conditional on is_different_board,\n       * but the difference should be irrelevant.\n       */\n      load_game_module(mzx_world, src_board->mod_playing, true);\n\n#ifdef CONFIG_EDITOR\n      // Also, update the caption to indicate the current board.\n      if(mzx_world->editing)\n        caption_set_board(mzx_world, mzx_world->current_board);\n#endif\n    }\n\n    mzx_world->target_where = TARGET_NONE;\n\n    // Disallow any keypresses this cycle\n    return true;\n  }\n\n  return false;\n}\n"
  },
  {
    "path": "src/game_update.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018-2020 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __GAME_UPDATE_H\n#define __GAME_UPDATE_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"core.h\"\n\nvoid update_world(context *ctx, boolean is_title);\nvoid update_board(context *ctx);\n\nvoid update_scroll_color(void);\n\nboolean draw_world(context *ctx, boolean is_title);\n\nboolean update_resolve_target(struct world *mzx_world,\n boolean *fade_in_next_cycle);\n\n__M_END_DECLS\n\n#endif // __GAME_UPDATE_H\n"
  },
  {
    "path": "src/game_update_board.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"const.h\"\n#include \"core.h\"\n#include \"counter.h\"\n#include \"data.h\"\n#include \"game_ops.h\"\n#include \"game_player.h\"\n#include \"game_update.h\"\n#include \"idarray.h\"\n#include \"idput.h\"\n#include \"robot.h\"\n#include \"world.h\"\n#include \"util.h\"\n\n#include \"audio/sfx.h\"\n\n// For missile turning (directions)\n\nstatic const int cwturndir[4] = { 2, 3, 1, 0 };\nstatic const int ccwturndir[4] = { 3, 2, 0, 1 };\n\n// OPEN DOOR movement directions, use bits 1,2,4,8,16 to index it.\n// 0ffh=no movement.\n\nstatic const char open_door_movement[] =\n{\n  3   , 0   , 2   , 0   , 3   , 1   , 2   , 1   ,\n  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n  2   , 1   , 3   , 1   , 2   , 0   , 3   , 0   ,\n  1   , 2   , 1   , 3   , 0   , 2   , 0   , 3\n};\n\n// Bits for WAIT, in proper bit form, for opening doors. Use bits 1-16.\n\nstatic const char open_door_max_wait[] =\n{\n  32 , 32 , 32 , 32 , 32 , 32 , 32 , 32 ,\n  224, 224, 224, 224, 224, 224, 224, 224,\n  224, 224, 224, 224, 224, 224, 224, 224,\n  32 , 32 , 32 , 32 , 32 , 32 , 32 , 32\n};\n\n// Increases current parameter and stores\n\nstatic int inc_param(int param, int max)\n{\n  if(param == max)\n  {\n    return 0;\n  }\n  else\n  {\n    return param + 1;\n  }\n}\n\n// This is the big one. Update all of the stuff on the screen..\n\nvoid update_board(context *ctx)\n{\n  struct world *mzx_world = ctx->world;\n  int i;\n  int x, y;\n  int level_offset;\n  struct board *src_board = mzx_world->current_board;\n  struct robot *cur_robot;\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n  char *level_color = src_board->level_color;\n  char *level_under_id = src_board->level_under_id;\n  char *level_under_color = src_board->level_under_color;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  boolean slow_down;\n  enum thing current_id;\n  char current_param;\n  char current_color;\n  enum thing current_under_id;\n  char *update_done = mzx_world->update_done;\n\n  // NOTE: already toggled.\n  slow_down = mzx_world->current_cycle_odd;\n\n  // Clear the status code of all robots\n  for(i = 0; i < src_board->num_robots_active; i++)\n  {\n    cur_robot = src_board->robot_list_name_sorted[i];\n    cur_robot->status = 0;\n  }\n\n  memset(update_done, 0, board_width * board_height);\n\n  // The big update loop\n  for(y = 0, level_offset = 0; y < board_height; y++)\n  {\n    for(x = 0; x < board_width; x++, level_offset++)\n    {\n      current_id = (enum thing)level_id[level_offset];\n\n      // If the char's update done value is set or the id is < 25\n      // (space trough W water) then there's nothing to do here;\n      // go to the next one.\n\n      if((update_done[level_offset] & 1) || (current_id < 25) ||\n       !(flags[(int)current_id] & A_UPDATE))\n      {\n        continue;\n      }\n\n      current_param = level_param[level_offset];\n\n      switch(current_id)\n      {\n        case ROBOT:\n        case ROBOT_PUSHABLE:\n        {\n          run_robot(ctx, current_param, x, y);\n\n          // On a game state change, we need to return to the main game loop.\n          if(mzx_world->change_game_state)\n            return;\n\n          break;\n        }\n\n        case ICE:\n        {\n          // Start ice animation\n          if(current_param == 0)\n          {\n            int rval = Random(256);\n            if(rval == 0)\n            {\n              // Start anim at 1\n              level_param[level_offset] = 1;\n            }\n          }\n          else\n\n          // End ice animation\n          if(current_param == 3)\n          {\n            // Reset animation\n            level_param[level_offset] = 0;\n          }\n          else\n          {\n            // Increase animation\n            level_param[level_offset] = current_param + 1;\n          }\n\n          break;\n        }\n\n        case LAVA:\n        {\n          int r_val;\n\n          if(slow_down) break;\n\n          r_val = Random(256);\n\n          if(r_val >= 20)\n          {\n            if(current_param == 2)\n            {\n              level_param[level_offset] = 0;\n            }\n            else\n            {\n              level_param[level_offset] = current_param + 1;\n            }\n          }\n          break;\n        }\n\n        case FIRE:\n        {\n          // Get a random number\n          int rval = Random(256);\n          int new_x, new_y;\n          // If it's less than 20, don't animate\n          if(rval >= 20)\n          {\n            // If not at the end of the animation, increment it\n            if(current_param != 5)\n            {\n              level_param[level_offset] = current_param + 1;\n            }\n            else\n            {\n              // Reset the animation\n              level_param[level_offset] = 0;\n            }\n          }\n\n          // Check under for water, ice, or lava; these all kill the fire.\n\n          current_under_id = (enum thing)level_under_id[level_offset];\n          if((current_under_id >= STILL_WATER) &&\n           (current_under_id <= LAVA))\n          {\n            // Ice also melts into stillwater\n            if(current_under_id == ICE)\n            {\n              level_under_id[level_offset] = (char)STILL_WATER;\n              level_under_color[level_offset] = 25;\n            }\n            id_remove_top(mzx_world, x, y);\n            break;\n          }\n\n          // Otherwise the fire hurts and spreads and stuff, maybe dies\n          // Random number says it might die or spread\n          rval = Random(256);\n          // No spread here\n          if(rval > 8) break;\n          // If it's 1, it should die, if fire doesn't burn forever\n          // You would think dead fire can't spread, but in MZX it can!\n          // Makes you wonder what the point of \"fire burns forever\" is.\n          // This should possibly be changed later.\n          if((rval == 1) && (src_board->fire_burns != FIRE_BURNS_FOREVER))\n          {\n            // Fire turns into dark grey ash\n            level_id[level_offset] = (char)FLOOR;\n            level_color[level_offset] = 8;\n          }\n\n          // Put fire all around\n          for(i = 3; i >= 0; i--)\n          {\n            if(!xy_shift_dir(src_board, x, y, &new_x, &new_y, i))\n            {\n              // Get offset\n              int offset = xy_to_offset(src_board, new_x, new_y);\n              int place = 0;\n              // Save ID and param there\n              enum thing new_id = (enum thing)level_id[offset];\n              char new_color = level_color[offset];\n\n              // Fire does things to certain ID's\n              if((new_id == SPACE) &&\n               (src_board->fire_burn_space == 1))\n              {\n                place = 1;\n              }\n\n              // Fake\n              if((new_id >= FAKE) && (new_id <= THICK_WEB) &&\n               (src_board->fire_burn_fakes == 1))\n              {\n                place = 1;\n              }\n\n              if((new_id == TREE) &&\n               (src_board->fire_burn_trees == 1))\n              {\n                place = 1;\n              }\n\n              // Brown stuff\n              // Don't burn scrolls, signs, sensors, robots, or the player\n              if((new_color == 6) &&\n               (src_board->fire_burn_brown == 1) && (new_id < SENSOR))\n              {\n                place = 1;\n              }\n\n              // Player\n              if(new_id == PLAYER)\n              {\n                // Make sure player can't.. walk on fire.\n                if(mzx_world->firewalker_dur == 0)\n                {\n                  hurt_player(mzx_world, FIRE);\n                }\n              }\n\n              if(place == 1)\n              {\n                // Place a new fire\n                id_place(mzx_world, new_x, new_y, FIRE, 0, 0);\n              }\n            }\n          }\n\n          break;\n        }\n\n        case BULLET:\n        {\n          int bullettype = current_param >> 2;\n          int direction = current_param & 0x03;\n          // Erase at the current position\n          id_remove_top(mzx_world, x, y);\n          // Place a new one\n          shoot(mzx_world, x, y, direction, bullettype);\n\n          break;\n        }\n\n        case EXPLOSION:\n        {\n          int stage = current_param & 0x0F;\n          // Stage zero means climb outwards\n          if(!stage)\n          {\n            // Move the explosion outward if it has any more to go.\n            if(current_param & 0xF0)\n            {\n              int new_x, new_y;\n              // Decrease stage\n              current_param -= 0x10;\n              level_param[level_offset] = current_param;\n\n              // Put explosion at each direction\n              for(i = 3; i >= 0; i--)\n              {\n                if(!xy_shift_dir(src_board, x, y, &new_x, &new_y, i))\n                {\n                  // Get offset\n                  int offset = xy_to_offset(src_board, new_x, new_y);\n                  // Save ID and param there\n                  enum thing new_id = (enum thing)level_id[offset];\n                  char new_param = level_param[offset];\n                  // Get the flags for that ID\n                  int flag = flags[(int)new_id];\n\n                  // Should a new explosion be placed here?\n                  // Also, some further things can happen.\n                  // Does what's there..\n                  // Blow up (removed by explosion)?\n                  // Slimes, ghosts, and dragons might also die if the\n                  // bit isn't set\n                  if((flag & A_BLOW_UP) ||\n                   ((new_id == SLIMEBLOB) && !(new_param & 0x80)) ||\n                   ((new_id == GHOST) && !(new_param & 0x08)) ||\n                   ((new_id == DRAGON) && !(new_param & 0xE0)))\n                  {\n                    // Remove what's there\n                    id_remove_top(mzx_world, new_x, new_y);\n                    // Place a new explosion (one level down)\n                    id_place(mzx_world, new_x, new_y, EXPLOSION,\n                     0, current_param);\n                    continue;\n                  }\n\n                  // Explode? (create a new explosion)\n                  if(flag & A_EXPLODE)\n                  {\n                    // Remove what's there\n                    id_remove_top(mzx_world, new_x, new_y);\n                    // Place a fresh new explosion\n                    id_place(mzx_world, new_x, new_y, EXPLOSION, 0, 64);\n                    continue;\n                  }\n\n                  // If it can be pushed under, place an explosion over it\n                  if(flag & A_UNDER)\n                  {\n                    // Place a new explosion over it.\n                    id_place(mzx_world, new_x, new_y, EXPLOSION,\n                     0, current_param);\n                    continue;\n                  }\n\n                  // Now, see if it's something that reacts to explosions\n                  // Player takes damage\n                  if(new_id == PLAYER)\n                  {\n                    hurt_player(mzx_world, EXPLOSION);\n                    continue;\n                  }\n\n                  // Mine explodes\n                  if(new_id == MINE)\n                  {\n                    // Take explosion radius from param\n                    // Remove what's there\n                    id_remove_top(mzx_world, new_x, new_y);\n                    // Place a fresh new explosion\n                    id_place(mzx_world, new_x, new_y, EXPLOSION,\n                     0, new_param & 0xF0);\n                    continue;\n                  }\n\n                  // Robot gets sent a label\n                  if(is_robot(new_id))\n                  {\n                    // Send bombed label\n                    send_robot_def(mzx_world, new_param, LABEL_BOMBED);\n                    continue;\n                  }\n\n                  // Dragon takes damage, if it wasn't already killed\n                  if(new_id == DRAGON)\n                  {\n                    // Decrease HP\n                    level_param[offset] = new_param - 0x20;\n                    continue;\n                  }\n                }\n              }\n            }\n          }\n\n          // See if it's at stage 3, if so it's done\n          if(stage == 3)\n          {\n            // Get what's underneath it\n            current_under_id =\n             (enum thing)level_under_id[level_offset];\n            // Leave space if over goop, water, lava, or ice\n            if((current_under_id == GOOP) ||\n             ((current_under_id >= STILL_WATER) &&\n             (current_under_id <= LAVA)))\n            {\n              // Leave space\n              id_remove_top(mzx_world, x, y);\n              break;\n            }\n\n            // Is under an entrance? Explosion leaves space? Leave space.\n            if((flags[(int)current_under_id] & A_ENTRANCE)\n             || src_board->explosions_leave == EXPL_LEAVE_SPACE)\n            {\n              // Leave space\n              id_remove_top(mzx_world, x, y);\n              break;\n            }\n            // Leave ash?\n            if(src_board->explosions_leave == EXPL_LEAVE_ASH)\n            {\n              // Leave ash if the params say so\n              level_id[level_offset] = (char)FLOOR;\n              level_color[level_offset] = 8;\n              break;\n            }\n            // Otherwise leave fire\n            level_id[level_offset] = (char)FIRE;\n            level_param[level_offset] = 0;\n          }\n          else\n          {\n            // Otherwise go to the next stage\n            level_param[level_offset] = current_param + 1;\n          }\n          break;\n        }\n\n        case CW_ROTATE:\n        case CCW_ROTATE:\n        {\n          if(slow_down)\n            break;\n\n          level_param[level_offset] = inc_param(current_param, 3);\n          rotate(mzx_world, x, y, current_id - CW_ROTATE);\n          break;\n        }\n\n        case TRANSPORT:\n        {\n          if(slow_down)\n            break;\n\n          // Is it still animating?\n          if((current_param & 0x18) != 0x18)\n          {\n            // Increase animation\n            level_param[level_offset] = current_param + 0x08;\n          }\n          else\n          {\n            // Otherwise, erase anim bits\n            level_param[level_offset] = current_param & 0xE7;\n          }\n          break;\n        }\n\n        case SHOOTING_FIRE:\n        {\n          enum move_status status;\n          // Get direction\n          int m_dir = current_param >> 1;\n          // Flip animation and store\n          current_param ^= 1;\n          level_param[level_offset] = current_param;\n          // Try moving\n\n          /* Note: directions >3 always result in a self-collision. */\n          status = move(mzx_world, x, y, m_dir,\n           CAN_LAVAWALK | CAN_FIREWALK | CAN_WATERWALK |\n           REACT_PLAYER | CAN_GOOPWALK | SPITFIRE);\n\n          if(status == HIT_PLAYER)\n          {\n            // Hit player; hurt the player and die\n            hurt_player(mzx_world, SHOOTING_FIRE);\n            id_remove_top(mzx_world, x, y);\n          }\n          else\n          {\n            if(status != NO_HIT)\n            {\n              // Didn't hit the player.. check stuff\n              current_under_id =\n               (enum thing)level_under_id[level_offset];\n              // See if it hit an entrance\n              if((current_under_id == STAIRS) ||\n               (current_under_id == CAVE) ||\n               ((current_under_id >= WHIRLPOOL_1) &&\n               (current_under_id <= WHIRLPOOL_4)))\n              {\n                id_remove_top(mzx_world, x, y);\n              }\n              else\n              {\n                // Put fire\n                level_id[level_offset] = (char)FIRE;\n                level_param[level_offset] = 0;\n              }\n            }\n          }\n\n          break;\n        }\n\n        case MISSILE:\n        {\n          enum move_status status = HIT_PLAYER;\n          int move_params =\n           CAN_LAVAWALK | CAN_FIREWALK | CAN_WATERWALK |\n           REACT_PLAYER | CAN_GOOPWALK;\n\n          // Param is the direction\n          /* Note: directions >3 always result in a self collision.\n           * Rotating an invalid direction is also an invalid direction,\n           * so skip all of the checks and just explode if this happens.\n           * Rotating an invalid direction would read junk prior to 2.93d */\n          if((unsigned)current_param <= 3)\n            status = move(mzx_world, x, y, current_param, move_params);\n\n          // Did it hit something that's not the player?\n          if((status == HIT) || (status == HIT_EDGE))\n          {\n            // Otherwise change direction; try cw then ccw\n            int new_direction = cwturndir[(int)current_param];\n            level_param[level_offset] = new_direction;\n            status = move(mzx_world, x, y, new_direction, move_params);\n            // Did it hit something that's not the player? Try ccw.\n            if((status == HIT) || (status == HIT_EDGE))\n            {\n              new_direction = ccwturndir[(int)current_param];\n              level_param[level_offset] = new_direction;\n              status = move(mzx_world, x, y, new_direction, move_params);\n              if(status)\n                status = HIT_PLAYER;\n            }\n          }\n\n          // Did it hit the player?\n          if(status == HIT_PLAYER)\n          {\n            // If so, leave explosion\n            level_id[level_offset] = (char)EXPLOSION;\n            level_param[level_offset] = 48;\n            play_sfx(mzx_world, SFX_EXPLOSION);\n          }\n\n          break;\n        }\n\n        case SEEKER:\n        {\n          int seek_dir;\n\n          if(slow_down) break;\n          if(current_param == 0)\n          {\n            id_remove_top(mzx_world, x, y);\n          }\n          else\n          {\n            level_param[level_offset] = current_param - 1;\n            seek_dir = find_seek(mzx_world, x, y);\n            if(move(mzx_world, x, y, seek_dir,\n             CAN_PUSH | CAN_LAVAWALK | CAN_FIREWALK |\n             CAN_WATERWALK | REACT_PLAYER | CAN_GOOPWALK) ==\n             HIT_PLAYER)\n            {\n              hurt_player(mzx_world, SEEKER);\n              id_remove_top(mzx_world, x, y);\n            }\n          }\n          break;\n        }\n\n        case LAZER:\n        {\n          // Decrease time until death\n          // If it's less than 8, kill it\n          current_param -= 8;\n          if(current_param < 8)\n          {\n            id_remove_top(mzx_world, x, y);\n          }\n          else\n          {\n            if(slow_down) break;\n            // Animate every other cycle\n            if((current_param & 0x06) == 0x06)\n            {\n              // Otherwise, erase anim bits\n              level_param[level_offset] = current_param & 0xF9;\n            }\n            else\n            {\n              // Increase animation\n              level_param[level_offset] = current_param + 2;\n            }\n          }\n          break;\n        }\n\n        case PUSHER:\n        {\n          /* Note: directions >3 always result in a self-collision. */\n          if(!slow_down)\n            move(mzx_world, x, y, current_param, CAN_PUSH);\n\n          break;\n        }\n\n        case SNAKE:\n        {\n          enum move_status status;\n\n          // Flip count and store\n          current_param ^= 8;\n          level_param[level_offset] = current_param;\n\n          // If the flipflop or fast-movement is set, move\n          if((current_param & 0x08) || !(current_param & 0x04))\n          {\n            // Try move\n            status = move(mzx_world, x, y, current_param & 0x03,\n             REACT_PLAYER);\n            // See if it hit the player\n            if(status == HIT_PLAYER)\n            {\n              // If so, take damage and die\n              hurt_player(mzx_world, SNAKE);\n              id_remove_top(mzx_world, x, y);\n              break;\n            }\n\n            // Otherwise, if it didn't move, get a new direction\n            if(status != NO_HIT)\n            {\n              int intelligence = (current_param & 0x70) >> 4;\n              int rval = Random(8);\n              int m_dir;\n\n              // Mask out direction\n              current_param &= 0xFC;\n\n              // If it's less than intelligence, make a \"smart\" move\n              if(rval < intelligence)\n              {\n                m_dir = find_seek(mzx_world, x, y);\n              }\n              else\n              {\n                // Get a number between 0 and 3\n                m_dir = Random(4);\n              }\n\n              // Set direction\n              current_param |= m_dir;\n              level_param[level_offset] = current_param;\n            }\n          }\n          break;\n        }\n\n        case EYE:\n        {\n          // Flip count and store\n          current_param ^= 0x80;\n          level_param[level_offset] = current_param;\n\n          // If the flipflop or fast-movement is set, move\n          if((current_param & 0x80) || !(current_param & 0x40))\n          {\n            int intelligence = current_param & 7;\n            int rval = Random(8);\n            int m_dir;\n\n            if(rval < intelligence)\n            {\n              m_dir = find_seek(mzx_world, x, y);\n            }\n            else\n            {\n              // Get a number between 0 and 3\n              m_dir = Random(4);\n            }\n\n            if(move(mzx_world, x, y, m_dir,\n             CAN_LAVAWALK | CAN_FIREWALK | CAN_WATERWALK | REACT_PLAYER |\n             CAN_GOOPWALK) == HIT_PLAYER)\n            {\n              // Hit the player. Get the blast radius.\n              int radius = (current_param & 0x38) << 1;\n              // Explode (place explosion)\n              level_id[level_offset] = EXPLOSION;\n              level_param[level_offset] = radius;\n              play_sfx(mzx_world, SFX_EXPLOSION);\n            }\n          }\n          break;\n        }\n\n        case THIEF:\n        {\n          // Movement rate, 1-4\n          int move_speed = current_param & 0x18;\n          // Movement counter\n          int move_cycle = (current_param & 0x60) >> 2;\n\n          // Should the thief move?\n          if(move_cycle == move_speed)\n          {\n            int intelligence = current_param & 0x07;\n            int rval = Random(8);\n            int m_dir;\n\n            // Zero out move cycle\n            level_param[level_offset] = current_param & 0x9F;\n\n            // See if a \"smart move\" should be made\n            if(rval < intelligence)\n            {\n              m_dir = find_seek(mzx_world, x, y);\n            }\n            else\n            {\n              m_dir = Random(4);\n            }\n\n            // Move and see if it hit the player\n            if(move(mzx_world, x, y, m_dir, REACT_PLAYER) == HIT_PLAYER)\n            {\n              // Get amount of gems to take\n              int gems_take = ((current_param & 0x80) >> 7) + 1;\n              // Take them\n              dec_counter(mzx_world, \"GEMS\", gems_take, 0);\n              play_sfx(mzx_world, SFX_STOLEN_GEM);\n            }\n          }\n          else\n          {\n            // Increment move cycle\n            level_param[level_offset] = current_param + 0x20;\n          }\n\n          break;\n        }\n\n        case SLIMEBLOB:\n        {\n          if(!slow_down)\n          {\n            int spread_speed = current_param & 0x03;\n            int current_cycle = (current_param & 0x3C) >> 2;\n\n            // This was changed from speed*5 some time between 2.04 and 2.07.\n            // Despite MegaZeux 2.x having changed the speed setting range\n            // from 1-4 to 1-8, this setting has always been two bits.\n            // BUG: The newer behavior seems to be a broken attempt at speed*3.\n            if(mzx_world->version >= V251)\n              spread_speed |= spread_speed << 1;\n            else\n              spread_speed |= spread_speed << 2;\n\n            if(spread_speed == current_cycle)\n            {\n              int new_x, new_y;\n              current_color = level_color[level_offset];\n              // Clear cycle\n              // BUG: This leaves the lowest cycle count bit set.\n              // This bug exists even in MegaZeux 1.x.\n              current_param &= 0xC7;\n\n              // Put slimes all around\n              for(i = 3; i >= 0; i--)\n              {\n                if(!xy_shift_dir(src_board, x, y, &new_x, &new_y, i))\n                {\n                  // Get offset\n                  int offset = xy_to_offset(src_board, new_x, new_y);\n                  // Save ID and param there\n                  enum thing new_id = (enum thing)level_id[offset];\n\n                  // See if it hits a fake\n                  if(is_fake(new_id))\n                  {\n                    // Put a slime\n                    id_place(mzx_world, new_x, new_y, SLIMEBLOB,\n                     current_color, current_param);\n                  }\n                  else\n                  {\n                    // See if it hits the player and hurts the player\n                    if((new_id == PLAYER) && (current_param & 0x40))\n                    {\n                      // Take damage\n                      hurt_player(mzx_world, SLIMEBLOB);\n                    }\n                  }\n                  // Put a breakaway\n                  id_place(mzx_world, x, y, BREAKAWAY, current_color, 0);\n                }\n              }\n            }\n            else\n            {\n              // Increase cycle\n              // BUG: corrupting overflow.\n              level_param[level_offset] = current_param + 4;\n            }\n          }\n          break;\n        }\n\n        case RUNNER:\n        {\n          int speed = current_param & 0x0C;\n          int cycle = (current_param & 0x30) >> 2;\n\n          if(cycle == speed)\n          {\n            // Get direction\n            int direction = current_param & 0x03;\n            enum move_status status;\n            // Clear cycle\n            level_param[level_offset] = current_param & 0xCF;\n\n            status = move(mzx_world, x, y, direction,\n             CAN_PUSH | CAN_TRANSPORT | REACT_PLAYER);\n            // Did the move not go through?\n            if(status != NO_HIT)\n            {\n              // Did it hit the player?\n              if(status == HIT_PLAYER)\n              {\n                // Hurt the player and die\n                hurt_player(mzx_world, RUNNER);\n                id_remove_top(mzx_world, x, y);\n                break;\n              }\n              else\n              {\n                // Change direction\n                level_param[level_offset] = current_param ^ 1;\n              }\n            }\n          }\n          else\n          {\n            // Increase cycle\n            level_param[level_offset] = current_param + 0x10;\n          }\n\n          break;\n        }\n\n        case GHOST:\n        {\n          int speed = current_param & 0x30;\n          int cycle = (current_param & 0xC0) >> 2;\n\n          if(speed == cycle)\n          {\n            // Get intelligence\n            int intelligence = current_param & 0x07;\n            int rval = Random(8);\n            int m_dir;\n            // Clear cycle\n            level_param[level_offset] = current_param & 0x3F;\n\n            if(rval < intelligence)\n            {\n              // Smart move\n              m_dir = find_seek(mzx_world, x, y);\n            }\n            else\n            {\n              m_dir = Random(4);\n            }\n\n            // Try move, did it hit the player?\n            if(move(mzx_world, x, y, m_dir,\n             CAN_LAVAWALK | CAN_FIREWALK | CAN_WATERWALK |\n             REACT_PLAYER | CAN_GOOPWALK) == HIT_PLAYER)\n            {\n              // Take damage\n              hurt_player(mzx_world, GHOST);\n              // Only die if not invincible\n              if(!(current_param & 0x08))\n              {\n                id_remove_top(mzx_world, x, y);\n                break;\n              }\n            }\n          }\n          else\n          {\n            // Increase cycle\n            level_param[level_offset] = current_param + 0x40;\n          }\n\n          break;\n        }\n\n        case DRAGON:\n        {\n          int fire_rate;\n          int rval = -1;\n\n          // Can it move?\n          if(current_param & 0x04)\n          {\n            int cycle = current_param & 0x18;\n            if(cycle == 0x18)\n            {\n              int m_dir;\n              enum move_status status;\n              // Zero out movement\n              level_param[level_offset] = current_param & 0xE7;\n\n              // One out of 8 moves is random\n              if(src_board->dragons_can_randomly_move)\n                rval = Random(8);\n\n              if(!(rval & 0x07))\n              {\n                m_dir = Random(4);\n              }\n              else\n              {\n                // Otherwise make a smart move\n                m_dir = find_seek(mzx_world, x, y);\n              }\n\n              status = move(mzx_world, x, y, m_dir,\n               CAN_LAVAWALK | CAN_FIREWALK | REACT_PLAYER);\n\n              // Is it blocked?\n              if(status != NO_HIT)\n              {\n                // Did it hit the player?\n                if(status == HIT_PLAYER)\n                {\n                  // Dragons don't die when hit by the player\n                  hurt_player(mzx_world, DRAGON);\n                }\n\n                // Can't shoot, so get out of here\n                break;\n              }\n            }\n            else\n            {\n              level_param[level_offset] = current_param + 8;\n            }\n          }\n\n          // Shoot; get the fire rate\n          fire_rate = current_param & 0x03;\n          rval = Random(16);\n\n          // Should it fire?\n          if(rval < fire_rate)\n          {\n            // Shoot in the direction of the player\n            int fire_dir = find_seek(mzx_world, x, y);\n            shoot_fire(mzx_world, x, y, fire_dir);\n            play_sfx(mzx_world, SFX_DRAGON_FIRE);\n          }\n\n          break;\n        }\n\n        case FISH:\n        {\n          // Is the cycle count ready or is fast movement on?\n          if((current_param & 0x10) || !(current_param & 0x08))\n          {\n            unsigned m_dir = (unsigned)(level_under_id[level_offset] - N_WATER);\n\n            // Toggle cycle flipflop off\n            level_param[level_offset] = current_param & 0xEF;\n\n            // Is there not water there or is it not affected by it?\n            if(!(current_param & 0x20) || (m_dir > 3))\n            {\n              int intelligence = current_param & 0x07;\n              int rval = Random(8);\n              if(rval < intelligence)\n              {\n                m_dir = find_seek(mzx_world, x, y);\n              }\n              else\n              {\n                m_dir = Random(4);\n              }\n            }\n\n            // Move. Did it hit the player and does the\n            // player hurt fish?\n            if((move(mzx_world, x, y, m_dir, CAN_WATERWALK |\n             REACT_PLAYER | MUST_WATER) == HIT_PLAYER) &&\n             (current_param & 0x40))\n            {\n              hurt_player(mzx_world, FISH);\n              id_remove_top(mzx_world, x, y);\n            }\n          }\n          else\n          {\n            // Toggle cycle flipflop on\n            level_param[level_offset] = current_param | 0x10;\n          }\n\n          break;\n        }\n\n        case SHARK:\n        {\n          int intelligence;\n          int rval;\n          int m_dir;\n          enum move_status status;\n          int new_x = 0, new_y = 0;\n          int fire_rate;\n          int shoot_type;\n\n          if(slow_down) break;\n\n          intelligence = current_param & 0x07;\n          rval = Random(8);\n\n          if(rval < intelligence)\n          {\n            m_dir = find_seek(mzx_world, x, y);\n          }\n          else\n          {\n            m_dir = Random(4);\n          }\n\n          // Hit player and die\n          status = move(mzx_world, x, y, m_dir,\n           MUST_LAVAGOOP | REACT_PLAYER | CAN_GOOPWALK | CAN_LAVAWALK);\n\n          if(status == HIT_PLAYER)\n          {\n            hurt_player(mzx_world, SHARK);\n            id_remove_top(mzx_world, x, y);\n            break;\n          }\n\n          // Should shoot in the direction it moved.\n\n          // If blocked, keep old x/y, otherwise update\n          if(status != NO_HIT)\n          {\n            new_x = x;\n            new_y = y;\n          }\n          else\n          {\n            xy_shift_dir(src_board, x, y, &new_x, &new_y, m_dir);\n          }\n\n          // Get shark fire rate\n          fire_rate = (current_param & 0xE0) >> 5;\n          // Get the type of thing it shoots\n          shoot_type = current_param & 0x18;\n          // Get a number between 0 and 31\n          rval = Random(32);\n\n          // Is the random number <= the rate? Also, does it shoot?\n          if((rval <= fire_rate) && (shoot_type != 24))\n          {\n            // Shoot seeker\n            if(shoot_type == 8)\n            {\n              shoot_seeker(mzx_world, new_x, new_y, m_dir);\n            }\n            else\n\n            // Shoot fire\n            if(shoot_type == 16)\n            {\n              shoot_fire(mzx_world, new_x, new_y, m_dir);\n              play_sfx(mzx_world, SFX_DRAGON_FIRE);\n            }\n            else\n            {\n              // Shoot bullet\n              shoot(mzx_world, new_x, new_y, m_dir, ENEMY_BULLET);\n            }\n          }\n\n          break;\n        }\n\n        case SPIDER:\n        {\n          // Toggle cycle flipflop\n          current_param ^= 0x40;\n          level_param[level_offset] = current_param;\n\n          // Is the cycle count ready or is fast movement on?\n          if((current_param & 0x40) || !(current_param & 0x20))\n          {\n            int intelligence = current_param & 0x07;\n            int rval = Random(8);\n            int m_dir;\n            // Set these flags\n            int flags = 128;\n\n            if(rval < intelligence)\n            {\n              m_dir = find_seek(mzx_world, x, y);\n            }\n            else\n            {\n              m_dir = Random(4);\n            }\n            flags |= ((current_param & 0x18) << 2) + 32;\n\n            if(move(mzx_world, x, y, m_dir, flags) == HIT_PLAYER)\n            {\n              hurt_player(mzx_world, SPIDER);\n              id_remove_top(mzx_world, x, y);\n            }\n          }\n\n          break;\n        }\n\n        case GOBLIN:\n        {\n          int move_rate;\n          int move_cycle;\n\n          if(slow_down) break;\n\n          // Should it pause?\n          if(current_param & 0x20)\n          {\n            move_rate = ((current_param & 0x03) << 1) + 1;\n            move_cycle = ((current_param & 0x1C) >> 2);\n\n            if(move_rate == move_cycle)\n            {\n              // Zero out move cycle and move bit\n              level_param[level_offset] = current_param & 0xC3;\n            }\n            else\n            {\n              // Increase move cycle\n              level_param[level_offset] = current_param + 4;\n            }\n          }\n          else\n          {\n            int intelligence = (current_param & 0xC0) >> 6;\n            int rval = Random(4);\n            int m_dir;\n\n            // Otherwise, move\n            current_param += 4;\n            // If the non-pause cycle is maxed out\n            if((current_param & 0x1C) == 0x14)\n            {\n              // Pause next time\n              current_param = (current_param & 0xE3) | 0x20;\n            }\n\n            level_param[level_offset] = current_param;\n\n            if(rval < intelligence)\n            {\n              m_dir = find_seek(mzx_world, x, y);\n            }\n            else\n            {\n              m_dir = Random(4);\n            }\n\n            // Try to move, does it hit the player, etc.\n            if(move(mzx_world, x, y, m_dir, CAN_WATERWALK |\n             REACT_PLAYER) == HIT_PLAYER)\n            {\n              hurt_player(mzx_world, GOBLIN);\n              id_remove_top(mzx_world, x, y);\n            }\n          }\n\n          break;\n        }\n\n        case SPITTING_TIGER:\n        {\n          int intelligence;\n          int rval;\n          int m_dir;\n          enum move_status status;\n          int new_x = 0, new_y = 0;\n          int fire_rate;\n          int shoot_type;\n          boolean no_shoot = false;\n\n          if(slow_down) break;\n\n          intelligence = current_param & 0x07;\n          rval = Random(8);\n\n          if(rval < intelligence)\n          {\n            m_dir = find_seek(mzx_world, x, y);\n          }\n          else\n          {\n            // From 2.80 until 2.94, SpittingTigers used the wrong value here,\n            // resulting in directions 4-7. Invalid dirs result in no movement\n            // and self-destruction with bullets when Enemies Hurt Enemies is on.\n            if(src_board->spittingtiger_moves >= SPITTINGTIGER_MOVES_SLOWLY)\n            {\n              m_dir = Random(8);\n              // 2.94+: allow enabling slow tigers without the self-destruct bug.\n              if(m_dir > 3 && src_board->spittingtiger_moves <\n               SPITTINGTIGER_MOVES_SLOWLY_SELF_DESTRUCTS)\n                no_shoot = true;\n            }\n            else\n              m_dir = Random(4);\n          }\n\n          // Hit player and die\n          status = move(mzx_world, x, y, m_dir,\n           CAN_WATERWALK | REACT_PLAYER);\n\n          if(status == HIT_PLAYER)\n          {\n            hurt_player(mzx_world, SPITTING_TIGER);\n            id_remove_top(mzx_world, x, y);\n            break;\n          }\n\n          // Should shoot in the direction it moved.\n\n          // If blocked, keep old x/y, otherwise update\n          if(status)\n          {\n            new_x = x;\n            new_y = y;\n          }\n          else\n          {\n            xy_shift_dir(src_board, x, y, &new_x, &new_y, m_dir);\n          }\n\n          // Get tiger fire rate\n          fire_rate = (current_param & 0xE0) >> 5;\n          // Get the type of thing it shoots\n          shoot_type = current_param & 0x18;\n          // Get a number between 0 and 31\n          rval = Random(32);\n\n          // Is the random number <= the rate? Also, does it shoot?\n          if((rval <= fire_rate) && (shoot_type != 24) && !no_shoot)\n          {\n            // Shoot seeker\n            if(shoot_type == 8)\n            {\n              shoot_seeker(mzx_world, new_x, new_y, m_dir);\n            }\n            else\n\n            // Shoot fire\n            if(shoot_type == 16)\n            {\n              shoot_fire(mzx_world, new_x, new_y, m_dir);\n              play_sfx(mzx_world, SFX_DRAGON_FIRE);\n            }\n            else\n            {\n              // Shoot bullet\n              shoot(mzx_world, new_x, new_y, m_dir, ENEMY_BULLET);\n            }\n          }\n\n          break;\n        }\n\n        case SPINNING_GUN:\n        {\n          // A spinnning gun is a bulletgun that spins. So it can fall\n          // through for now, but this might get changed with refactoring..\n          if(slow_down)\n            break;\n\n          // Update animation and direction\n\n          if((current_param & 0x18) == 0x18)\n          {\n            current_param &= 0xE7;\n          }\n          else\n          {\n            current_param += 8;\n          }\n\n          level_param[level_offset] = current_param;\n        }\n\n        /* fallthrough */\n\n        case BULLET_GUN:\n        {\n          int shoot_rate;\n          int rval;\n\n          if(slow_down)\n            break;\n\n          shoot_rate = current_param & 0x07;\n          rval = Random(16);\n\n          // Shoot now.. maybe\n          if(rval <= shoot_rate)\n          {\n            int intelligence = current_param & 0x60;\n            int should_shoot = 0;\n            int direction = (current_param & 0x18) >> 3;\n            rval = Random(4) << 5;\n\n            // Is the player aligned with the gun?\n            if(find_seek(mzx_world, x, y) == direction)\n            {\n              // If so, should shoot\n              should_shoot = 1;\n            }\n\n            // Unless the gun is stupid, then do the opposite\n            if(rval > intelligence)\n            {\n              should_shoot ^= 1;\n            }\n\n            if(should_shoot)\n            {\n              // See what to shoot\n              if(current_param & 0x80)\n              {\n                // Shoot fire\n                shoot_fire(mzx_world, x, y, direction);\n              }\n              else\n              {\n                // Shoot bullet\n                shoot(mzx_world, x, y, direction, ENEMY_BULLET);\n              }\n            }\n          }\n\n          break;\n        }\n\n        case BEAR:\n        {\n          int move_rate = current_param & 0x18;\n          int move_cycle = (current_param & 0x60) >> 2;\n\n          if(move_rate == move_cycle)\n          {\n            int sensitivity = ((current_param & 0x07) + 1) << 1;\n            int player_distance;\n            int player_dist_x = mzx_world->player_x - x;\n            int player_dist_y = mzx_world->player_y - y;\n\n            // Zero out move cycle\n            level_param[level_offset] &= 0x9F;\n\n            if(player_dist_x < 0) player_dist_x = -player_dist_x;\n            if(player_dist_y < 0) player_dist_y = -player_dist_y;\n            player_distance = player_dist_x + player_dist_y;\n\n            if(player_distance <= sensitivity)\n            {\n              // Move in the player's direction, always\n              if(move(mzx_world, x, y, find_seek(mzx_world, x, y),\n               CAN_WATERWALK | REACT_PLAYER) == HIT_PLAYER)\n              {\n                hurt_player(mzx_world, BEAR);\n                id_remove_top(mzx_world, x, y);\n              }\n            }\n          }\n          else\n          {\n            level_param[level_offset] = current_param + 0x20;\n          }\n\n          break;\n        }\n\n        case BEAR_CUB:\n        {\n          int switch_rate;\n          int switch_cycle;\n          int intelligence;\n          int rval;\n          int m_dir;\n\n          if(slow_down) break;\n\n          switch_rate = ((current_param & 0x0C) >> 1) + 1;\n          switch_cycle = (current_param & 0x70) >> 4;\n\n          // Should it switch from chasing/running away from the player?\n          if(switch_cycle == switch_rate)\n          {\n            // Zero out the switch cycle\n            current_param &= 0x8F;\n            // Toggle the switch\n            current_param ^= 0x80;\n          }\n          else\n          {\n            // Increase switch time\n            current_param += 16;\n          }\n\n          level_param[level_offset] = current_param;\n\n          intelligence = current_param & 0x03;\n          rval = Random(4);\n\n          if(rval < intelligence)\n          {\n            m_dir = find_seek(mzx_world, x, y);\n          }\n          else\n          {\n            m_dir = Random(4);\n          }\n\n          // Should it run in that direction or the opposite one?\n          if(current_param & 0x80)\n          {\n            m_dir = flip_dir(m_dir);\n          }\n\n          if(move(mzx_world, x, y, m_dir, CAN_WATERWALK |\n           REACT_PLAYER) == HIT_PLAYER)\n          {\n            hurt_player(mzx_world, BEAR_CUB);\n            id_remove_top(mzx_world, x, y);\n          }\n\n          break;\n        }\n\n        case ENERGIZER:\n        {\n          level_param[level_offset] = inc_param(current_param, 7);\n          break;\n        }\n\n        case LIT_BOMB:\n        {\n          if(!slow_down)\n          {\n            if((current_param & 6) == 6)\n            {\n              // Is it a highbomb?\n              if(current_param & 0x80)\n              {\n                level_param[level_offset] = 0x40;\n              }\n              else\n              {\n                level_param[level_offset] = 0x20;\n              }\n\n              level_id[level_offset] = (char)EXPLOSION;\n              play_sfx(mzx_world, SFX_EXPLOSION);\n            }\n            else\n            {\n              level_param[level_offset] = current_param + 1;\n            }\n          }\n          break;\n        }\n\n        case OPEN_DOOR:\n        {\n          int curr_wait = current_param & 0xE0;\n          // Get the door stage\n          int stage = current_param & 0x1F;\n          int door_wait = open_door_max_wait[stage];\n          int door_move = open_door_movement[stage];\n\n          if(curr_wait == door_wait)\n          {\n            // Is it at stage 3?\n            if((current_param & 0x18) == 0x18)\n            {\n              // Turn into a regular door\n              level_param[level_offset] = current_param & 0x07;\n              level_id[level_offset] = (char)DOOR;\n            }\n            else\n            {\n              // Otherwise, add to, reset the wait the stage\n              level_param[level_offset] = stage + 8;\n            }\n\n            // Only do this if movement is possible\n            if(door_move != 0xFF)\n            {\n              if(move(mzx_world, x, y, door_move, CAN_PUSH |\n               CAN_LAVAWALK | CAN_FIREWALK | CAN_WATERWALK) != NO_HIT)\n              {\n                // Reset the param and make the door open\n                level_id[level_offset] = OPEN_DOOR;\n                level_param[level_offset] = current_param;\n              }\n            }\n          }\n          else\n          {\n            level_param[level_offset] = current_param + 0x20;\n          }\n          break;\n        }\n\n        case OPEN_GATE:\n        {\n          // End of wait? Close it.\n          if(current_param == 0)\n          {\n            // Make it closed gate\n            level_id[level_offset] = (char)GATE;\n            play_sfx(mzx_world, SFX_GATE_CLOSE);\n          }\n          else\n          {\n            // Decrease wait\n            level_param[level_offset] = current_param - 1;\n          }\n\n          break;\n        }\n\n        case N_MOVING_WALL:\n        case S_MOVING_WALL:\n        case E_MOVING_WALL:\n        case W_MOVING_WALL:\n        {\n          // Get direction\n          int m_dir = (int)current_id - N_MOVING_WALL;\n          // Try the move\n          if(move(mzx_world, x, y, m_dir, CAN_PUSH |\n           CAN_TRANSPORT | CAN_LAVAWALK | CAN_FIREWALK |\n           CAN_WATERWALK) != NO_HIT)\n          {\n            // Can't move; try other direction\n            level_id[level_offset] = flip_dir(m_dir) + N_MOVING_WALL;\n          }\n          break;\n        }\n\n        case LAZER_GUN:\n        {\n          // Look at the param\n          int start_time = (current_param & 0x1C) >> 2;\n          // Must be the correct start time\n          if(start_time == src_board->lazwall_start)\n          {\n            char length = ((current_param & 0xE0) >> 5) + 1;\n            char direction = (current_param & 0x03);\n            current_color = level_color[level_offset];\n            shoot_lazer(mzx_world, x, y, direction, length, current_color);\n          }\n          break;\n        }\n\n        case LIFE:\n        {\n          if(!slow_down)\n            level_param[level_offset] = inc_param(current_param, 3);\n\n          break;\n        }\n\n        // Whirlpool\n        case WHIRLPOOL_1:\n        case WHIRLPOOL_2:\n        case WHIRLPOOL_3:\n        case WHIRLPOOL_4:\n        {\n          if(!slow_down)\n          {\n            // Is it the last phase?\n            if(current_id == WHIRLPOOL_4)\n            {\n              // Loop back\n              level_id[level_offset] = WHIRLPOOL_1;\n            }\n            else\n            {\n              // Increase \"frame\"\n              level_id[level_offset] = current_id + 1;\n            }\n          }\n\n          break;\n        }\n\n        case MINE:\n        {\n          // Should it go to the next phase?\n          if((current_param & 0x0E) != 0x0E)\n          {\n            level_param[level_offset] = current_param + 2;\n          }\n          else\n          {\n            // Animate\n            level_param[level_offset] = (current_param & 0xF1) ^ 1;\n          }\n          break;\n        }\n\n        case MISSILE_GUN:\n        {\n          // Apparently, missile guns have intelligence, but this\n          // doesn't even affect anything. Hrm...\n\n          int fire_rate;\n          int m_dir;\n          int rval;\n\n          if(!slow_down)\n          {\n            fire_rate = current_param & 0x07;\n            rval = Random(2) << 5;\n            m_dir = find_seek(mzx_world, x, y);\n\n            // Fire rate of seven means always shoot\n            // Only shoot if the gun is facing the player\n            if(((rval < fire_rate) || (fire_rate == 7)) &&\n             (m_dir == ((current_param >> 3) & 0x03)))\n            {\n              shoot_missile(mzx_world, x, y, m_dir);\n              // Should it die now?\n              if(!(current_param & 0x80))\n              {\n                id_remove_top(mzx_world, x, y);\n              }\n            }\n          }\n\n          break;\n        }\n\n        default:\n        {\n          break;\n        }\n      }\n    }\n  }\n\n  // Run all of the robots _again_, this time in reverse order.\n  level_offset = (board_width * board_height) - 1;\n\n  for(y = board_height - 1; y >= 0; y--)\n  {\n    for(x = board_width - 1; x >= 0; x--)\n    {\n      current_id = (enum thing)level_id[level_offset];\n      if(is_robot(current_id))\n      {\n        current_param = level_param[level_offset];\n\n        // May change the source board (with swap world or load game)\n        run_robot(ctx, -current_param, x, y);\n\n        // On a game state change, we need to return to the main game loop.\n        if(mzx_world->change_game_state)\n          return;\n      }\n      level_offset--;\n    }\n  }\n\n  find_player(mzx_world);\n}\n"
  },
  {
    "path": "src/graphics.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n * Copyright (C) 2017-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * YUV Renderers:\n *   Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * OpenGL #2 Renderer:\n *   Copyright (C) 2007 Joel Bouchard Lamontagne <logicow@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <assert.h>\n#include <ctype.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/stat.h>\n\n#include \"configure.h\"\n#include \"error.h\"\n#include \"event.h\"\n#include \"graphics.h\"\n#include \"platform.h\"\n#include \"pngops.h\"\n#include \"render.h\"\n#include \"render_layer.h\"\n#include \"renderers.h\"\n#include \"world.h\"\n#include \"io/vio.h\"\n\n#ifdef CONFIG_SDL\n#include \"SDLmzx.h\"\n#endif // CONFIG_SDL\n\n#include \"util.h\"\n\n#define CURSOR_BLINK_RATE 115\n\n__editor_maybe_static struct graphics_data graphics;\n\nstatic const struct renderer_data renderers[] =\n{\n#if defined(CONFIG_RENDER_SOFT)\n  { \"software\", render_soft_register },\n#endif\n#if defined(CONFIG_RENDER_SOFTSCALE)\n  { \"softscale\", render_softscale_register },\n#endif\n#if defined(CONFIG_RENDER_SDLACCEL)\n  { \"sdlaccel\", render_sdlaccel_register },\n#endif\n#if defined(CONFIG_RENDER_GL_FIXED)\n  { \"opengl1\", render_gl1_register },\n  { \"opengl2\", render_gl2_register },\n#endif\n#if defined(CONFIG_RENDER_GL_PROGRAM)\n  { \"glsl\", render_glsl_register },\n  { \"glslscale\", render_glsl_software_register },\n  { \"auto_glsl\", render_auto_glsl_register },\n#endif\n#if defined(CONFIG_RENDER_YUV)\n  { \"overlay1\", render_yuv1_register },\n  { \"overlay2\", render_yuv2_register },\n#endif\n#if defined(CONFIG_RENDER_GP2X)\n  { \"gp2x\", render_gp2x_register },\n#endif\n#if defined(CONFIG_NDS)\n  { \"nds\", render_nds_register },\n#endif\n#if defined(CONFIG_3DS)\n#if defined(CONFIG_RENDER_CTR)\n  { \"3ds\", render_ctr_register },\n#endif\n#endif\n#if defined(CONFIG_WII)\n#if defined(CONFIG_RENDER_GX)\n  { \"gx\", render_gx_register },\n#endif\n#if !defined(CONFIG_SDL)\n  { \"xfb\", render_xfb_register },\n#endif\n#endif\n#if defined(CONFIG_DJGPP)\n  { \"ega\", render_ega_register },\n#if defined(CONFIG_DOS_SVGA)\n  { \"svga\", render_svga_register },\n#endif\n#endif\n#if defined(CONFIG_DREAMCAST)\n  { \"dreamcast\", render_dc_register },\n  { \"dreamcast_fb\", render_dc_fb_register },\n#endif\n  { NULL, NULL }\n};\n\nstruct renderer_alias\n{\n  const char *alias;\n  const char *name;\n};\n\nstatic const struct renderer_alias renderer_aliases[] =\n{\n#if defined(CONFIG_RENDER_SOFTSCALE)\n  { \"overlay1\", \"softscale\" },\n  { \"overlay2\", \"softscale\" },\n#endif\n  { NULL, NULL },\n};\n\nstatic const struct rgb_color default_pal[16] =\n{\n  /* r, g, b, unused */\n  { 0, 0, 0, 0 },\n  { 0, 0, 170, 0 },\n  { 0, 170, 0, 0 },\n  { 0, 170, 170, 0 },\n  { 170, 0, 0, 0 },\n  { 170, 0, 170, 0 },\n  { 170, 85, 0, 0 },\n  { 170, 170, 170, 0 },\n  { 85, 85, 85, 0 },\n  { 85, 85, 255, 0 },\n  { 85, 255, 85, 0 },\n  { 85, 255, 255, 0 },\n  { 255, 85, 85, 0 },\n  { 255, 85, 255, 0 },\n  { 255, 255, 85, 0 },\n  { 255, 255, 255, 0 }\n};\n\n#if (NUM_CHARSETS < 16)\nstatic boolean extended_charsets_check(boolean show_error, int pos, int count);\n#else\nstatic inline boolean extended_charsets_check(boolean s, int p, int c)\n{\n  return true;\n}\n#endif\n\nstatic void remap_charbyte(struct graphics_data *graphics, uint16_t chr,\n uint8_t byte)\n{\n  if(graphics->renderer.remap_charbyte)\n    graphics->renderer.remap_charbyte(graphics, chr, byte);\n}\n\nstatic void remap_char(struct graphics_data *graphics, uint16_t chr)\n{\n  if(graphics->renderer.remap_char)\n    graphics->renderer.remap_char(graphics, chr);\n}\n\nstatic void remap_char_range(struct graphics_data *graphics, uint16_t first,\n uint16_t len)\n{\n  if(graphics->renderer.remap_char_range)\n    graphics->renderer.remap_char_range(graphics, first, len);\n}\n\nvoid ec_change_byte(uint16_t chr, uint8_t byte, uint8_t new_value)\n{\n  extended_charsets_check(true, chr, 1);\n\n  chr = chr % PROTECTED_CHARSET_POSITION;\n  graphics.charset[(chr * CHAR_SIZE) + byte] = new_value;\n\n  remap_charbyte(&graphics, chr, byte);\n}\n\nvoid ec_change_char(uint16_t chr, const char matrix[CHAR_SIZE])\n{\n  extended_charsets_check(true, chr, 1);\n\n  chr = chr % PROTECTED_CHARSET_POSITION;\n  memcpy(graphics.charset + (chr * CHAR_SIZE), matrix, CHAR_SIZE);\n\n  remap_char(&graphics, chr);\n}\n\nuint8_t ec_read_byte(uint16_t chr, uint8_t byte)\n{\n  extended_charsets_check(true, chr, 1);\n\n  chr = chr % PROTECTED_CHARSET_POSITION;\n  return graphics.charset[(chr * CHAR_SIZE) + byte];\n}\n\nvoid ec_read_char(uint16_t chr, char matrix[CHAR_SIZE])\n{\n  extended_charsets_check(true, chr, 1);\n\n  chr = chr % PROTECTED_CHARSET_POSITION;\n  memcpy(matrix, graphics.charset + (chr * CHAR_SIZE), CHAR_SIZE);\n}\n\nvoid ec_clear_set(void)\n{\n  memset(graphics.charset, 0, PROTECTED_CHARSET_POSITION * CHAR_SIZE);\n  remap_char_range(&graphics, 0, FULL_CHARSET_SIZE);\n}\n\nboolean ec_load_set(const char *filename)\n{\n  vfile *vf = vfopen_unsafe(filename, \"rb\");\n  if(vf)\n  {\n    int count = vfread(graphics.charset, CHAR_SIZE, PROTECTED_CHARSET_POSITION, vf);\n    vfclose(vf);\n\n    if(count > 0)\n    {\n      // some renderers may want to map charsets to textures\n      remap_char_range(&graphics, 0, count);\n      return true;\n    }\n  }\n  return false;\n}\n\n__editor_maybe_static void ec_load_set_secondary(const char *filename,\n uint8_t dest[CHAR_SIZE * CHARSET_SIZE])\n{\n  vfile *vf = vfopen_unsafe(filename, \"rb\");\n  if(vf)\n  {\n    int count = vfread(dest, CHAR_SIZE, CHARSET_SIZE, vf);\n    vfclose(vf);\n\n    // This might have been somewhere in the charset, so\n    // some renderers may want to map charsets to textures\n    if(count > 0)\n      remap_char_range(&graphics, 0, FULL_CHARSET_SIZE);\n  }\n}\n\nint ec_load_set_var(const char *filename, uint16_t first_chr, int version)\n{\n  vfile *vf = vfopen_unsafe(filename, \"rb\");\n  if(vf)\n  {\n    int maxchars = PROTECTED_CHARSET_POSITION;\n    int count;\n\n    int size = vfilelength(vf, false) / CHAR_SIZE;\n\n    if(version >= V290)\n    {\n      extended_charsets_check(true, first_chr, size);\n    }\n    else\n      maxchars = 256;\n\n    if(first_chr > maxchars)\n    {\n      vfclose(vf);\n      return -1;\n    }\n\n    if(size + first_chr > maxchars)\n      size = maxchars - first_chr;\n\n    count = vfread(graphics.charset + (first_chr * CHAR_SIZE), CHAR_SIZE, size, vf);\n    vfclose(vf);\n\n    // some renderers may want to map charsets to textures\n    if(count > 0)\n      remap_char_range(&graphics, first_chr, count);\n\n    return count;\n  }\n  return -1;\n}\n\nvoid ec_mem_load_set(const void *buffer, size_t len)\n{\n  // This is used only for legacy and ZIP world loading and the default charsets\n  // Use ec_clear_set() in conjunction with this for world loads.\n  size_t count;\n\n  if(len > CHAR_SIZE * PROTECTED_CHARSET_POSITION)\n    len = CHAR_SIZE * PROTECTED_CHARSET_POSITION;\n\n  memcpy(graphics.charset, buffer, len);\n\n  // some renderers may want to map charsets to textures\n  count = len / CHAR_SIZE;\n  if(count > 0)\n    remap_char_range(&graphics, 0, count);\n}\n\nvoid ec_mem_load_set_var(const void *buffer, size_t len, uint16_t first_chr,\n int version)\n{\n  size_t maxchars = PROTECTED_CHARSET_POSITION;\n  size_t offset = first_chr * CHAR_SIZE;\n  size_t count = (len + CHAR_SIZE - 1) / CHAR_SIZE;\n\n  if(version >= V290)\n  {\n    extended_charsets_check(true, first_chr, count);\n  }\n  else\n    maxchars = 256;\n\n  if(first_chr > maxchars)\n    return;\n\n  if(count > maxchars - first_chr)\n  {\n    count = maxchars - first_chr;\n    len = count * CHAR_SIZE;\n  }\n\n  memcpy(graphics.charset + offset, buffer, len);\n\n  // some renderers may want to map charsets to textures\n  if(count > 0)\n    remap_char_range(&graphics, first_chr, count);\n}\n\nvoid ec_mem_save_set_var(void *buffer, size_t len, uint16_t first_chr)\n{\n  size_t offset = first_chr * CHAR_SIZE;\n  size_t size = MIN(PROTECTED_CHARSET_POSITION * CHAR_SIZE - offset, len);\n\n  if(first_chr < PROTECTED_CHARSET_POSITION)\n    memcpy(buffer, graphics.charset + offset, size);\n}\n\n__editor_maybe_static void ec_load_mzx(void)\n{\n  ec_mem_load_set(graphics.default_charset, CHAR_SIZE * CHARSET_SIZE);\n}\n\nstatic void update_colors(struct rgb_color *palette, unsigned int count)\n{\n  graphics.renderer.update_colors(&graphics, palette, count);\n}\n\nstatic unsigned int make_palette(struct rgb_color *palette)\n{\n  unsigned int paletteSize;\n  int i;\n\n  // Is SMZX mode set?\n  if(graphics.screen_mode)\n  {\n    // SMZX mode 1: set all colors to diagonals\n    if(graphics.screen_mode == 1)\n    {\n      for(i = 0; i < SMZX_PAL_SIZE; i++)\n      {\n        palette[i].r =\n         ((graphics.intensity_palette[i & 15].r << 1) +\n          graphics.intensity_palette[i >> 4].r) / 3;\n        palette[i].g =\n         ((graphics.intensity_palette[i & 15].g << 1) +\n          graphics.intensity_palette[i >> 4].g) / 3;\n        palette[i].b =\n         ((graphics.intensity_palette[i & 15].b << 1) +\n          graphics.intensity_palette[i >> 4].b) / 3;\n      }\n    }\n    else\n    {\n      memcpy(palette, graphics.intensity_palette,\n       sizeof(struct rgb_color) * SMZX_PAL_SIZE);\n    }\n    paletteSize = SMZX_PAL_SIZE;\n  }\n  else\n  {\n    memcpy(palette, graphics.intensity_palette,\n     sizeof(struct rgb_color) * PAL_SIZE);\n    paletteSize = PAL_SIZE;\n  }\n  memcpy(palette + paletteSize, graphics.protected_palette,\n   sizeof(struct rgb_color) * PROTECTED_PAL_SIZE);\n  graphics.protected_pal_position = paletteSize;\n\n  paletteSize += PROTECTED_PAL_SIZE;\n  if(paletteSize > 256 && !layer_renderer_check(false))\n  {\n    // Renderers without layer support often have poor upport for large\n    // palettes, so to be safe we restrict the palette size\n    paletteSize = 256;\n  }\n  return paletteSize;\n}\n\nvoid update_palette(void)\n{\n  struct rgb_color new_palette[FULL_PAL_SIZE];\n  update_colors(new_palette, make_palette(new_palette));\n}\n\nvoid default_palette(void)\n{\n  memcpy(graphics.palette, default_pal,\n   sizeof(struct rgb_color) * PAL_SIZE);\n\n  if(!graphics.fade_status)\n  {\n    memcpy(graphics.intensity_palette, default_pal,\n     sizeof(struct rgb_color) * PAL_SIZE);\n  }\n  graphics.palette_dirty = true;\n}\n\nvoid default_protected_palette(void)\n{\n  memcpy(graphics.protected_palette, default_pal,\n   sizeof(struct rgb_color) * PAL_SIZE);\n  graphics.palette_dirty = true;\n}\n\nstatic void init_palette(void)\n{\n  int i;\n\n  memcpy(graphics.palette, default_pal,\n   sizeof(struct rgb_color) * PAL_SIZE);\n  memcpy(graphics.protected_palette, default_pal,\n   sizeof(struct rgb_color) * PAL_SIZE);\n  memcpy(graphics.intensity_palette, default_pal,\n   sizeof(struct rgb_color) * PAL_SIZE);\n  memset(graphics.current_intensity, 0,\n   sizeof(uint32_t) * PAL_SIZE);\n\n  for(i = 0; i < SMZX_PAL_SIZE; i++)\n    graphics.saved_intensity[i] = 100;\n\n  graphics.fade_status = true;\n  graphics.palette_dirty = true;\n}\n\nstatic int intensity(unsigned int component, unsigned int percent)\n{\n  component = (component * percent) / 100;\n\n  if(component > 255)\n    component = 255;\n\n  return component;\n}\n\nvoid set_color_intensity(uint8_t color, unsigned int percent)\n{\n  if(graphics.fade_status)\n  {\n    graphics.saved_intensity[color] = percent;\n  }\n  else\n  {\n    int r = intensity(graphics.palette[color].r, percent);\n    int g = intensity(graphics.palette[color].g, percent);\n    int b = intensity(graphics.palette[color].b, percent);\n\n    graphics.intensity_palette[color].r = r;\n    graphics.intensity_palette[color].g = g;\n    graphics.intensity_palette[color].b = b;\n\n    graphics.current_intensity[color] = percent;\n    graphics.palette_dirty = true;\n  }\n}\n\n/** Set a color intensity in the MZX palette, even if SMZX is enabled. */\nvoid set_color_intensity_mzx(uint8_t color, unsigned int percent)\n{\n  if(graphics.screen_mode >= 2)\n  {\n    graphics.backup_intensity[color] = percent;\n  }\n  else\n    set_color_intensity(color, percent);\n}\n\nvoid set_palette_intensity(unsigned int percent)\n{\n  int i, num_colors;\n\n  if(graphics.screen_mode >= 2)\n    num_colors = SMZX_PAL_SIZE;\n  else\n    num_colors = PAL_SIZE;\n\n  for(i = 0; i < num_colors; i++)\n  {\n    set_color_intensity(i, percent);\n  }\n  graphics.palette_dirty = true;\n}\n\nvoid set_rgb(uint8_t color, unsigned int r, unsigned int g, unsigned int b)\n{\n  uint32_t percent = graphics.current_intensity[color];\n  r = r * 255 / 63;\n  g = g * 255 / 63;\n  b = b * 255 / 63;\n\n  graphics.palette[color].r = r;\n  graphics.intensity_palette[color].r = intensity(r, percent);\n\n  graphics.palette[color].g = g;\n  graphics.intensity_palette[color].g = intensity(g, percent);\n\n  graphics.palette[color].b = b;\n  graphics.intensity_palette[color].b = intensity(b, percent);\n  graphics.palette_dirty = true;\n}\n\n/** Set a color in the MZX palette, even if SMZX is enabled. */\nvoid set_rgb_mzx(uint8_t color, unsigned int r, unsigned int g, unsigned int b)\n{\n  if(graphics.screen_mode >= 2)\n  {\n    r = r * 255 / 63;\n    g = g * 255 / 63;\n    b = b * 255 / 63;\n    graphics.backup_palette[color].r = r;\n    graphics.backup_palette[color].g = g;\n    graphics.backup_palette[color].b = b;\n  }\n  else\n    set_rgb(color, r, g, b);\n}\n\nvoid set_protected_rgb(uint8_t color, unsigned int r, unsigned int g,\n unsigned int b)\n{\n  r = r * 255 / 63;\n  g = g * 255 / 63;\n  b = b * 255 / 63;\n  graphics.protected_palette[color].r = r;\n  graphics.protected_palette[color].g = g;\n  graphics.protected_palette[color].b = b;\n  graphics.palette_dirty = true;\n}\n\nvoid set_red_component(uint8_t color, unsigned int r)\n{\n  uint32_t percent = graphics.current_intensity[color];\n  r = r * 255 / 63;\n\n  graphics.palette[color].r = r;\n  graphics.intensity_palette[color].r = intensity(r, percent);\n  graphics.palette_dirty = true;\n}\n\nvoid set_green_component(uint8_t color, unsigned int g)\n{\n  uint32_t percent = graphics.current_intensity[color];\n  g = g * 255 / 63;\n\n  graphics.palette[color].g = g;\n  graphics.intensity_palette[color].g = intensity(g, percent);\n  graphics.palette_dirty = true;\n}\n\nvoid set_blue_component(uint8_t color, unsigned int b)\n{\n  uint32_t percent = graphics.current_intensity[color];\n  b = b * 255 / 63;\n\n  graphics.palette[color].b = b;\n  graphics.intensity_palette[color].b = intensity(b, percent);\n  graphics.palette_dirty = true;\n}\n\nunsigned int get_color_intensity(uint8_t color)\n{\n  if(graphics.fade_status)\n    return graphics.saved_intensity[color];\n  return graphics.current_intensity[color];\n}\n\n/** Get a color intensity from the MZX palette, even if SMZX is enabled. */\nunsigned int get_color_intensity_mzx(uint8_t color)\n{\n  if(graphics.screen_mode >= 2)\n  {\n    return graphics.backup_intensity[color];\n  }\n  else\n    return get_color_intensity(color);\n}\n\nvoid get_rgb(uint8_t color, uint8_t *r, uint8_t *g, uint8_t *b)\n{\n  *r = ((graphics.palette[color].r * 126) + 255) / 510;\n  *g = ((graphics.palette[color].g * 126) + 255) / 510;\n  *b = ((graphics.palette[color].b * 126) + 255) / 510;\n}\n\n/** Get a color from the MZX palette, even if SMZX is enabled. */\nvoid get_rgb_mzx(uint8_t color, uint8_t *r, uint8_t *g, uint8_t *b)\n{\n  if(graphics.screen_mode >= 2)\n  {\n    *r = ((graphics.backup_palette[color].r * 126) + 255) / 510;\n    *g = ((graphics.backup_palette[color].g * 126) + 255) / 510;\n    *b = ((graphics.backup_palette[color].b * 126) + 255) / 510;\n  }\n  else\n    get_rgb(color, r, g, b);\n}\n\nunsigned int get_red_component(uint8_t color)\n{\n  return ((graphics.palette[color].r * 126) + 255) / 510;\n}\n\nunsigned int get_green_component(uint8_t color)\n{\n  return ((graphics.palette[color].g * 126) + 255) / 510;\n}\n\nunsigned int get_blue_component(uint8_t color)\n{\n  return ((graphics.palette[color].b * 126) + 255) / 510;\n}\n\nstatic unsigned int get_smzx_index_offset(uint8_t palette, unsigned int index)\n{\n  index %= 4;\n\n  if(index == 1)\n    index = 2;\n  else\n\n  if(index == 2)\n    index = 1;\n\n  return (unsigned int)palette * 4 + index;\n}\n\nuint8_t get_smzx_index(uint8_t palette, unsigned int offset)\n{\n  offset = get_smzx_index_offset(palette, offset);\n\n  return graphics.smzx_indices[offset];\n}\n\nvoid set_smzx_index(uint8_t palette, unsigned int offset, uint8_t color)\n{\n  // Setting the SMZX index is only supported for mode 3\n  if(graphics.screen_mode != 3)\n    return;\n\n  offset = get_smzx_index_offset(palette, offset);\n\n  graphics.smzx_indices[offset] = color % SMZX_PAL_SIZE;\n  graphics.palette_dirty = true;\n}\n\n/**\n * Returns the effective luma in the range of [0,255] of a given color in the\n * game palette (0-255) or protected palette (256-271).\n */\n__editor_maybe_static\nint get_color_luma(unsigned int color)\n{\n  struct rgb_color rgb;\n  unsigned int sum;\n\n  if(color < 256)\n    rgb = graphics.palette[color];\n  else\n    rgb = graphics.protected_palette[color % 16];\n\n  sum = (unsigned int)rgb.r * 306u + /* 1024 * .299 */\n        (unsigned int)rgb.g * 601u + /* 1024 * .587 */\n        (unsigned int)rgb.b * 117u;  /* 1024 * .114 */\n\n  return (int)((sum + 512u) / 1024u);\n}\n\n/**\n * Returns the average effective luma in the range of [0,255] of a given char\n * using a given game palette.\n */\n__editor_maybe_static\nint get_char_average_luma(uint16_t chr, uint8_t palette, int mode, int mask_chr)\n{\n  const uint8_t *char_data = graphics.charset + chr * CHAR_SIZE;\n  const uint8_t *mask_values = NULL;\n  boolean use_mask = false;\n  uint8_t mask;\n  int count = 0;\n  int sum = 0;\n  int x;\n  int y;\n\n  if(chr >= FULL_CHARSET_SIZE)\n    return 0;\n\n  if(mask_chr >= 0 && mask_chr < FULL_CHARSET_SIZE)\n  {\n    mask_values = graphics.charset + mask_chr * CHAR_SIZE;\n    use_mask = true;\n  }\n\n  if(mode < 0)\n    mode = graphics.screen_mode;\n\n  if(mode)\n  {\n    int lumas[4];\n\n    if(mode == 1)\n    {\n      // Due to quirks in mode 1, the interpolated colors 1 and 2 are not\n      // actually stored in the palette. Conveniently, the stronger-weighted\n      // user color can be derived from the lower bit (1->3, 2->0).\n      lumas[0] = get_color_luma((palette & 0xF0) >> 4);\n      lumas[3] = get_color_luma(palette & 0xF);\n      lumas[1] = lumas[3];\n      lumas[2] = lumas[0];\n    }\n    else\n    {\n      lumas[0] = get_color_luma(graphics.smzx_indices[palette * 4 + 0]);\n      lumas[1] = get_color_luma(graphics.smzx_indices[palette * 4 + 1]);\n      lumas[2] = get_color_luma(graphics.smzx_indices[palette * 4 + 2]);\n      lumas[3] = get_color_luma(graphics.smzx_indices[palette * 4 + 3]);\n    }\n\n    for(y = 0; y < CHAR_H; y++)\n    {\n      for(x = 0, mask = 0xC0; x < CHAR_W; x += 2, mask >>= 2)\n      {\n        if(!use_mask || (mask_values[y] & mask))\n        {\n          sum += lumas[(char_data[y] & mask) >> (6 - x)];\n          count++;\n        }\n      }\n    }\n  }\n  else\n  {\n    int lumas[2] =\n    {\n      get_color_luma((palette & 0xF0) >> 4),\n      get_color_luma(palette & 0xF),\n    };\n\n    for(y = 0; y < CHAR_H; y++)\n    {\n      for(x = 0, mask = 0x80; x < CHAR_W; x++, mask >>= 1)\n      {\n        if(!use_mask || (mask_values[y] & mask))\n        {\n          sum += lumas[(char_data[y] & mask) >> (7 - x)];\n          count++;\n        }\n      }\n    }\n  }\n  return (sum + count / 2) / count;\n}\n\nvoid load_palette(const char *filename)\n{\n  int file_size, i, r, g, b;\n  vfile *pal_file;\n\n  pal_file = vfopen_unsafe(filename, \"rb\");\n  if(!pal_file)\n    return;\n\n  file_size = vfilelength(pal_file, false);\n\n  switch(graphics.screen_mode)\n  {\n    // Regular text mode, 16 colors\n    case 0:\n      file_size = MIN(file_size, 16 * 3);\n      break;\n\n    // SMZX modes, up to 256 colors\n    default:\n      file_size = MIN(file_size, 256 * 3);\n      break;\n  }\n\n  for(i = 0; i < file_size / 3; i++)\n  {\n    r = vfgetc(pal_file);\n    g = vfgetc(pal_file);\n    b = vfgetc(pal_file);\n    set_rgb(i, r, g, b);\n  }\n\n  vfclose(pal_file);\n}\n\nvoid load_palette_mem(const void *buffer, size_t len)\n{\n  const uint8_t *pal = buffer;\n  int size, r, g, b, i, j;\n\n  switch(graphics.screen_mode)\n  {\n    // Regular text mode\n    case 0:\n      size = MIN(len, 16*3);\n      break;\n\n    // SMZX\n    default:\n      size = MIN(len, 256*3);\n      break;\n  }\n\n  for(i = 0, j = 0; j+2 < size; i++)\n  {\n    r = pal[j++];\n    g = pal[j++];\n    b = pal[j++];\n    set_rgb(i, r, g, b);\n  }\n}\n\nvoid load_index_file(const char *filename)\n{\n  vfile *idx_file;\n  int i;\n\n  if(get_screen_mode() != 3)\n    return;\n\n  idx_file = vfopen_unsafe(filename, \"rb\");\n  if(idx_file)\n  {\n    for(i = 0; i < SMZX_PAL_SIZE; i++)\n    {\n      set_smzx_index(i, 0, vfgetc(idx_file));\n      set_smzx_index(i, 1, vfgetc(idx_file));\n      set_smzx_index(i, 2, vfgetc(idx_file));\n      set_smzx_index(i, 3, vfgetc(idx_file));\n    }\n\n    vfclose(idx_file);\n  }\n}\n\nvoid save_indices(void *buffer)\n{\n  uint8_t *copy_buffer = buffer;\n  int i;\n\n  if(get_screen_mode() != 3)\n    return;\n\n  for(i = 0; i < SMZX_PAL_SIZE; i++)\n  {\n    *(copy_buffer++) = get_smzx_index(i, 0);\n    *(copy_buffer++) = get_smzx_index(i, 1);\n    *(copy_buffer++) = get_smzx_index(i, 2);\n    *(copy_buffer++) = get_smzx_index(i, 3);\n  }\n}\n\nvoid load_indices(const void *buffer, size_t size)\n{\n  const uint8_t *copy_buffer = buffer;\n  unsigned int i;\n\n  if(get_screen_mode() != 3)\n    return;\n\n  if(size > SMZX_PAL_SIZE * 4)\n    size = SMZX_PAL_SIZE * 4;\n\n  // Truncate incomplete colors\n  size /= 4;\n\n  for(i = 0; i < size; i++)\n  {\n    set_smzx_index(i, 0, *(copy_buffer++));\n    set_smzx_index(i, 1, *(copy_buffer++));\n    set_smzx_index(i, 2, *(copy_buffer++));\n    set_smzx_index(i, 3, *(copy_buffer++));\n  }\n}\n\nvoid load_indices_direct(const void *buffer, size_t size)\n{\n  if(size > SMZX_PAL_SIZE * 4)\n    size = SMZX_PAL_SIZE * 4;\n\n  memcpy(graphics.smzx_indices, buffer, size);\n  graphics.palette_dirty = true;\n}\n\nvoid smzx_palette_loaded(boolean is_loaded)\n{\n  graphics.default_smzx_loaded = is_loaded;\n}\n\nstatic void update_intensity_palette(void)\n{\n  int i;\n  for(i = 0; i < SMZX_PAL_SIZE; i++)\n  {\n    set_color_intensity(i, graphics.current_intensity[i]);\n  }\n}\n\nstatic void swap_palettes(void)\n{\n  struct rgb_color temp_colors[SMZX_PAL_SIZE];\n  uint32_t temp_intensities[SMZX_PAL_SIZE];\n  memcpy(temp_colors, graphics.backup_palette,\n   sizeof(struct rgb_color) * SMZX_PAL_SIZE);\n  memcpy(graphics.backup_palette, graphics.palette,\n   sizeof(struct rgb_color) * SMZX_PAL_SIZE);\n  memcpy(graphics.palette, temp_colors,\n   sizeof(struct rgb_color) * SMZX_PAL_SIZE);\n  memcpy(temp_intensities, graphics.backup_intensity,\n   sizeof(uint32_t) * SMZX_PAL_SIZE);\n  if(graphics.fade_status)\n  {\n    memcpy(graphics.backup_intensity,\n     graphics.saved_intensity, sizeof(uint32_t) * SMZX_PAL_SIZE);\n    memcpy(graphics.saved_intensity, temp_intensities,\n     sizeof(uint32_t) * SMZX_PAL_SIZE);\n  }\n  else\n  {\n    memcpy(graphics.backup_intensity,\n     graphics.current_intensity, sizeof(uint32_t) * SMZX_PAL_SIZE);\n    memcpy(graphics.current_intensity, temp_intensities,\n     sizeof(uint32_t) * SMZX_PAL_SIZE);\n    update_intensity_palette();\n  }\n}\n\nstatic void fix_layer_screen_mode(void)\n{\n  // Fix the screen mode for all active layers except the UI_LAYER.\n  unsigned int i;\n  for(i = 0; i < graphics.layer_count; i++)\n    graphics.video_layers[i].mode = graphics.screen_mode;\n\n  graphics.video_layers[UI_LAYER].mode = 0;\n}\n\nvoid set_screen_mode(unsigned int mode)\n{\n  int i;\n  uint8_t *pal_idx;\n  char bg, fg;\n  mode %= 4;\n\n  if((mode >= 2) && (graphics.screen_mode < 2))\n  {\n    swap_palettes();\n    graphics.screen_mode = mode;\n    if(!graphics.default_smzx_loaded)\n    {\n      if(graphics.fade_status)\n      {\n        for(i = 0; i < SMZX_PAL_SIZE; i++)\n        {\n          graphics.current_intensity[i] = 0;\n        }\n      }\n      set_palette_intensity(100);\n      load_palette(mzx_res_get_by_id(SMZX_PAL));\n      graphics.default_smzx_loaded = true;\n    }\n  }\n  else\n\n  if((graphics.screen_mode >= 2) && (mode < 2))\n  {\n    swap_palettes();\n\n    graphics.screen_mode = mode;\n  }\n  else\n  {\n    graphics.screen_mode = mode;\n  }\n\n  pal_idx = graphics.smzx_indices;\n  if(mode == 1 || mode == 2)\n  {\n    for(i = 0; i < 256; i++)\n    {\n      bg = (i & 0xF0) >> 4;\n      fg = i & 0x0F;\n      pal_idx[0] = (bg << 4) | bg;\n      pal_idx[1] = (bg << 4) | fg;\n      pal_idx[2] = (fg << 4) | bg;\n      pal_idx[3] = (fg << 4) | fg;\n      pal_idx += 4;\n    }\n  }\n  else\n\n  if(mode == 3)\n  {\n    for(i = 0; i < 256; i++)\n    {\n      pal_idx[0] = (i + 0) & 0xFF;\n      pal_idx[1] = (i + 2) & 0xFF;\n      pal_idx[2] = (i + 1) & 0xFF;\n      pal_idx[3] = (i + 3) & 0xFF;\n      pal_idx += 4;\n    }\n  }\n\n  fix_layer_screen_mode();\n  graphics.palette_dirty = true;\n  graphics.smzx_dirty = true;\n}\n\nunsigned int get_screen_mode(void)\n{\n  return graphics.screen_mode;\n}\n\n/**\n * The cursor needs to be reasonably visible whenever possible. A good deal of\n * the time this is possible with the classic cursor behavior of using the\n * foreground color of whatever is beneath it. For completely solid chars, the\n * background color is instead used.\n *\n * This behavior doesn't work very well when the foreground and background\n * colors are very close (modes 0 and 1), so instead we switch the cursor to\n * protected white or black. Foreground and background have no meaning in modes\n * 2 and 3, so always use protected white or black in these modes.\n */\n\nstatic unsigned int get_cursor_color(void)\n{\n  struct char_element *cursor_element =\n   graphics.text_video + graphics.cursor_x + (graphics.cursor_y * SCREEN_W);\n  unsigned int cursor_char = cursor_element->char_value;\n  unsigned int fg_color = cursor_element->fg_color;\n  unsigned int bg_color = cursor_element->bg_color;\n  unsigned int cursor_color;\n  int i;\n\n  if(bg_color >= 0x10)\n  {\n    // UI- use protected palette white or black.\n    if(bg_color <= 0x19)\n      cursor_color = graphics.protected_pal_position + 0x0F;\n\n    else\n      cursor_color = graphics.protected_pal_position;\n  }\n  else\n\n  if(graphics.screen_mode <= 1)\n  {\n    // Modes 0 and 1- use the (modified) classic cursor color logic.\n    // NOTE: there was a trick using uint32_t * here before that caused\n    // misalignment crashes on some platforms.\n    uint8_t *offset = graphics.charset + cursor_char * CHAR_SIZE;\n    int cursor_solid = 0xFF;\n\n    // Choose FG by default.\n    cursor_color = fg_color;\n\n    // If the char under the cursor is completely solid, use the BG instead.\n    for(i = 0; i < CHAR_SIZE; i++)\n    {\n      cursor_solid &= *offset;\n      offset++;\n    }\n\n    if(cursor_solid == 0xFF)\n      cursor_color = bg_color;\n\n    if(fg_color < 0x10 && bg_color < 0x10)\n    {\n      // If the fg and bg colors are game colors and close in brightness/the\n      // same, neither color fits well, so choose a protected palette color.\n      int fg_luma = get_color_luma(fg_color);\n      int bg_luma = get_color_luma(bg_color);\n\n      if(abs(fg_luma - bg_luma) < 32)\n      {\n        cursor_color = graphics.protected_pal_position;\n\n        if(fg_luma + bg_luma < 256)\n          cursor_color |= 0x0F;\n      }\n    }\n\n    // Offset adjust protected colors if necessary.\n    if(cursor_color >= 0x10)\n    {\n      cursor_color = graphics.protected_pal_position + (cursor_color & 0x0F);\n    }\n    else\n\n    // Offset adjust mode 1 colors if necessary.\n    if(graphics.screen_mode == 1)\n      cursor_color = (cursor_color << 4) | (cursor_color & 0x0F);\n  }\n\n  else\n  {\n    // Modes 2 and 3- pick protected white or black based on the average luma.\n    unsigned int pal;\n    int avg;\n\n    bg_color &= 0x0F;\n    fg_color &= 0x0F;\n    pal = (bg_color << 4) | fg_color;\n\n    avg = get_char_average_luma(cursor_char, pal, graphics.screen_mode, -1);\n\n    cursor_color = graphics.protected_pal_position;\n\n    if(avg < 128)\n      cursor_color |= 0x0F;\n  }\n\n  return cursor_color;\n}\n\n#ifndef CONFIG_NO_LAYER_RENDERING\nstatic int compare_layers(const void *a, const void *b)\n{\n  return (*(struct video_layer * const *)a)->draw_order -\n    (*(struct video_layer * const *)b)->draw_order;\n}\n#endif\n\nvoid update_screen(void)\n{\n  uint32_t ticks = get_ticks();\n\n  if((ticks - graphics.cursor_timestamp) > CURSOR_BLINK_RATE)\n  {\n    graphics.cursor_flipflop ^= 1;\n    graphics.cursor_timestamp = ticks;\n  }\n\n  if(graphics.smzx_dirty)\n  {\n    /* Request an SMZX mode change from the renderer, if applicable (EGA).\n     * Hardware SMZX may reset various text mode settings, so do it first.\n     */\n    graphics.smzx_dirty = false;\n    if(graphics.renderer.set_screen_mode)\n      graphics.renderer.set_screen_mode(&graphics, graphics.screen_mode);\n  }\n\n  if(graphics.palette_dirty)\n  {\n    update_palette();\n    graphics.palette_dirty = false;\n  }\n\n#ifndef CONFIG_NO_LAYER_RENDERING\n  if(graphics.requires_extended && graphics.renderer.render_layer)\n  {\n    uint32_t layer;\n    for(layer = 0; layer < graphics.layer_count; layer++)\n    {\n      graphics.sorted_video_layers[layer] = &graphics.video_layers[layer];\n    }\n\n    qsort(graphics.sorted_video_layers, graphics.layer_count,\n     sizeof(struct video_layer *), compare_layers);\n\n    for(layer = 0; layer < graphics.layer_count; layer++)\n    {\n      if(graphics.sorted_video_layers[layer]->data &&\n       !graphics.sorted_video_layers[layer]->empty)\n        graphics.renderer.render_layer(&graphics,\n         graphics.sorted_video_layers[layer]);\n    }\n  }\n  else\n#endif\n  if(graphics.renderer.render_graph)\n  {\n    // Fallback if the layer renderer is unavailable or unnecessary\n    graphics.renderer.render_graph(&graphics);\n  }\n#ifndef CONFIG_NO_LAYER_RENDERING\n  else\n\n  if(graphics.renderer.render_layer)\n  {\n    // Fallback using the layer renderer\n    graphics.text_video_layer.mode = graphics.screen_mode;\n    graphics.text_video_layer.data = graphics.text_video;\n\n    graphics.renderer.render_layer(&graphics, &(graphics.text_video_layer));\n  }\n#endif\n\n  if(graphics.renderer.render_cursor || graphics.renderer.hardware_cursor)\n  {\n    unsigned int cursor_color = get_cursor_color();\n    unsigned int offset = 0;\n    unsigned int lines = 0;\n    boolean enabled = true;\n\n    switch(graphics.cursor_mode)\n    {\n      case CURSOR_MODE_UNDERLINE:\n        lines = 2;\n        offset = 12;\n        break;\n      case CURSOR_MODE_SOLID:\n        lines = 14;\n        offset = 0;\n        break;\n      case CURSOR_MODE_HINT:\n        break;\n      case CURSOR_MODE_INVISIBLE:\n      default:\n        enabled = false;\n        break;\n    }\n\n    // Try to render the standard software cursor first.\n    if(graphics.renderer.render_cursor && enabled && graphics.cursor_flipflop)\n    {\n      graphics.renderer.render_cursor(&graphics,\n       graphics.cursor_x, graphics.cursor_y, cursor_color, lines, offset);\n    }\n    else\n\n    // Other platforms may use a hardware cursor instead that may need to be\n    // updated any frame regardless of the cursor state and blinking.\n    if(graphics.renderer.hardware_cursor)\n    {\n      graphics.renderer.hardware_cursor(&graphics,\n       graphics.cursor_x, graphics.cursor_y, cursor_color, lines, offset, enabled);\n    }\n  }\n\n  if(graphics.mouse_status &&\n   graphics.system_mouse != SYSTEM_MOUSE_HIDE_SOFTWARE_MOUSE)\n  {\n    int mouse_x, mouse_y;\n    get_mouse_pixel_position(&mouse_x, &mouse_y);\n\n    mouse_x = (mouse_x / graphics.mouse_width) * graphics.mouse_width;\n    mouse_y = (mouse_y / graphics.mouse_height) * graphics.mouse_height;\n\n    graphics.renderer.render_mouse(&graphics, mouse_x, mouse_y,\n     graphics.mouse_width, graphics.mouse_height);\n  }\n\n  graphics.renderer.sync_screen(&graphics, &(graphics.window));\n}\n\nboolean get_fade_status(void)\n{\n  return graphics.fade_status;\n}\n\nvoid dialog_fadein(void)\n{\n  graphics.dialog_fade_status = get_fade_status();\n  if(graphics.dialog_fade_status)\n  {\n    clear_screen();\n    insta_fadein();\n  }\n}\n\nvoid dialog_fadeout(void)\n{\n  if(graphics.dialog_fade_status)\n  {\n    insta_fadeout();\n  }\n}\n\n// Very quick fade out. Saves intensity table for fade in. Be sure\n// to use in conjuction with the next function.\nvoid vquick_fadeout(void)\n{\n  if(!has_video_initialized())\n  {\n    // If we're running without video there's no point waiting 11 frames.\n    insta_fadeout();\n    return;\n  }\n\n  if(!graphics.fade_status)\n  {\n    int i, i2, num_colors;\n\n    if(graphics.screen_mode >= 2)\n      num_colors = SMZX_PAL_SIZE;\n    else\n      num_colors = PAL_SIZE;\n\n    memcpy(graphics.saved_intensity, graphics.current_intensity,\n     sizeof(uint32_t) * num_colors);\n\n    for(i = 10; i >= 0; i--)\n    {\n      uint32_t ticks = get_ticks();\n\n      for(i2 = 0; i2 < num_colors; i2++)\n        set_color_intensity(i2, (graphics.saved_intensity[i2] * i / 10));\n\n      graphics.palette_dirty = true;\n      update_screen();\n\n      ticks = get_ticks() - ticks;\n      if(ticks <= 16)\n        delay(16 - ticks);\n    }\n    graphics.fade_status = true;\n  }\n}\n\n// Very quick fade in. Uses intensity table saved from fade out. For\n// use in conjuction with the previous function.\nvoid vquick_fadein(void)\n{\n  if(!has_video_initialized())\n  {\n    // If we're running without video there's no point waiting 11 frames.\n    insta_fadein();\n    return;\n  }\n\n  if(graphics.fade_status)\n  {\n    unsigned int i, i2, num_colors;\n\n    graphics.fade_status = false;\n\n    if(graphics.screen_mode >= 2)\n      num_colors = SMZX_PAL_SIZE;\n    else\n      num_colors = PAL_SIZE;\n\n    for(i = 0; i <= 10; i++)\n    {\n      uint32_t ticks = get_ticks();\n\n      for(i2 = 0; i2 < num_colors; i2++)\n        set_color_intensity(i2, (graphics.saved_intensity[i2] * i / 10));\n\n      graphics.palette_dirty = true;\n      update_screen();\n\n      ticks = get_ticks() - ticks;\n      if(ticks <= 16)\n        delay(16 - ticks);\n    }\n  }\n}\n\n// Instant fade out\nvoid insta_fadeout(void)\n{\n  unsigned int i, num_colors;\n\n  if(graphics.fade_status)\n    return;\n\n  if(graphics.screen_mode >= 2)\n    num_colors = SMZX_PAL_SIZE;\n  else\n    num_colors = PAL_SIZE;\n\n  memcpy(graphics.saved_intensity, graphics.current_intensity,\n   sizeof(uint32_t) * num_colors);\n\n  for(i = 0; i < num_colors; i++)\n    set_color_intensity(i, 0);\n\n  graphics.palette_dirty = true;\n  update_screen(); // NOTE: this was called conditionally in 2.81e\n\n  graphics.fade_status = true;\n}\n\n// Instant fade in\nvoid insta_fadein(void)\n{\n  unsigned int i, num_colors;\n\n  if(!graphics.fade_status)\n    return;\n\n  graphics.fade_status = false;\n\n  if(graphics.screen_mode >= 2)\n    num_colors = SMZX_PAL_SIZE;\n  else\n    num_colors = PAL_SIZE;\n\n  for(i = 0; i < num_colors; i++)\n    set_color_intensity(i, graphics.saved_intensity[i]);\n\n  graphics.palette_dirty = true;\n  update_screen(); // NOTE: this was called conditionally in 2.81e\n}\n\nstatic boolean set_graphics_output(struct config_info *conf)\n{\n  char video_output[sizeof(conf->video_output)];\n  const struct renderer_data *renderer = renderers;\n  const struct renderer_alias *alias = renderer_aliases;\n  int i = 0;\n\n  // The first renderer was NULL, this shouldn't happen\n  if(!renderer->name)\n  {\n    warn(\"No renderers built, please provide a valid config.h!\\n\");\n    return false;\n  }\n\n  // Some \"renderers\" are aliases for other renderers that are kept around for\n  // compatibility reasons only. These are kept separate from the main list.\n  memcpy(video_output, conf->video_output, sizeof(video_output));\n  while(alias->alias)\n  {\n    if(!strcasecmp(video_output, alias->alias))\n    {\n      snprintf(video_output, sizeof(video_output), \"%s\", alias->name);\n      break;\n    }\n    alias++;\n  }\n\n  while(renderer->name)\n  {\n    if(!strcasecmp(video_output, renderer->name))\n      break;\n    renderer++;\n    i++;\n  }\n\n  // If no match found, use first renderer in the renderer list\n  if(!renderer->name)\n  {\n    renderer = renderers;\n    i = 0;\n  }\n\n  renderer->reg(&graphics.renderer);\n  graphics.renderer_num = i;\n  graphics.window.is_init = false;\n\n  debug(\"Video: using '%s' renderer.\\n\", renderer->name);\n  return true;\n}\n\nconst char *video_get_default_caption(void)\n{\n  return graphics.default_caption;\n}\n\nstatic void new_empty_layer(struct video_layer *layer, int x, int y,\n unsigned int w, unsigned int h, int draw_order)\n{\n  // Layers are persistent and static\n  if(!layer->data || layer->w != w || layer->h != h)\n    layer->data = crealloc(layer->data, sizeof(struct char_element) * w * h);\n\n  layer->w = w;\n  layer->h = h;\n  layer->x = x;\n  layer->y = y;\n  layer->mode = graphics.screen_mode;\n  layer->draw_order = draw_order;\n  layer->transparent_col = -1;\n  layer->offset = 0;\n}\n\nuint32_t create_layer(int x, int y, unsigned int w, unsigned int h,\n int draw_order, int t_col, int offset, boolean unbound)\n{\n  uint32_t layer_idx = graphics.layer_count;\n  struct video_layer *layer = &graphics.video_layers[layer_idx];\n\n  new_empty_layer(layer, x, y, w, h, draw_order);\n  memset(layer->data, 0xFF, sizeof(struct char_element) * w * h);\n  layer->empty = true;\n  layer->transparent_col = t_col;\n  layer->offset = offset;\n  graphics.layer_count++;\n\n  // This shouldn't ever happen, but just in case...\n  if(graphics.current_layer == layer_idx)\n    select_layer(layer_idx);\n\n  if(!graphics.requires_extended && unbound)\n    graphics.requires_extended = true;\n\n  return layer_idx;\n}\n\nvoid set_layer_offset(uint32_t layer, int offset)\n{\n  graphics.video_layers[layer].offset = offset;\n}\n\nvoid set_layer_mode(uint32_t layer, int mode)\n{\n  // In general, we want the layer to use the screen mode, but some\n  // UI elements need to be able to change this.\n  graphics.video_layers[layer].mode = mode;\n}\n\nvoid move_layer(uint32_t layer, int x, int y)\n{\n  graphics.video_layers[layer].x = x;\n  graphics.video_layers[layer].y = y;\n  if(!graphics.requires_extended && (x % CHAR_W != 0 || y % CHAR_H != 0))\n    graphics.requires_extended = true;\n}\n\nstatic void init_layers(void)\n{\n  new_empty_layer(&graphics.video_layers[BOARD_LAYER],\n   0, 0, SCREEN_W, SCREEN_H, LAYER_DRAWORDER_BOARD);\n  new_empty_layer(&graphics.video_layers[OVERLAY_LAYER],\n   0, 0, SCREEN_W, SCREEN_H, LAYER_DRAWORDER_OVERLAY);\n  new_empty_layer(&graphics.video_layers[GAME_UI_LAYER],\n   0, 0, SCREEN_W, SCREEN_H, LAYER_DRAWORDER_GAME_UI);\n  new_empty_layer(&graphics.video_layers[UI_LAYER],\n   0, 0, SCREEN_W, SCREEN_H, LAYER_DRAWORDER_UI);\n\n  select_layer(UI_LAYER);\n\n  graphics.layer_count = NUM_DEFAULT_LAYERS;\n  graphics.layer_count_prev = graphics.layer_count;\n  blank_layers();\n}\n\nvoid select_layer(uint32_t layer_id)\n{\n  struct video_layer *layer = &graphics.video_layers[layer_id];\n  graphics.current_layer = layer_id;\n  graphics.current_video = layer->data;\n  graphics.current_video_end = layer->data + (layer->w * layer->h);\n}\n\nvoid blank_layers(void)\n{\n  // This clears the default layers and deletes all other layers\n\n  memset(graphics.video_layers[BOARD_LAYER].data, 0x00,\n   sizeof(struct char_element) * SCREEN_W * SCREEN_H);\n  memset(graphics.video_layers[OVERLAY_LAYER].data, 0xFF,\n   sizeof(struct char_element) * SCREEN_W * SCREEN_H);\n  memset(graphics.video_layers[GAME_UI_LAYER].data, 0xFF,\n   sizeof(struct char_element) * SCREEN_W * SCREEN_H);\n  memset(graphics.video_layers[UI_LAYER].data, 0xFF,\n   sizeof(struct char_element) * SCREEN_W * SCREEN_H);\n\n  graphics.video_layers[BOARD_LAYER].empty = false;\n  graphics.video_layers[OVERLAY_LAYER].empty = true;\n  graphics.video_layers[GAME_UI_LAYER].empty = true;\n  graphics.video_layers[UI_LAYER].empty = true;\n\n  // Delete the rest of the layers\n  destruct_extra_layers(0);\n\n  // Since the layers were cleared, their screen mode values need to be reset.\n  fix_layer_screen_mode();\n}\n\nvoid destruct_extra_layers(uint32_t first)\n{\n  // Delete layers that have not persisted since the previous frame and\n  // make all extra layers available for use.\n  uint32_t i;\n\n  if(first < NUM_DEFAULT_LAYERS)\n    first = NUM_DEFAULT_LAYERS;\n\n  if(graphics.layer_count_prev > graphics.layer_count)\n  {\n    for(i = graphics.layer_count; i < graphics.layer_count_prev; i++)\n    {\n      free(graphics.video_layers[i].data);\n      graphics.video_layers[i].data = NULL;\n    }\n  }\n  graphics.layer_count_prev = graphics.layer_count;\n\n  if(graphics.layer_count > first)\n    graphics.layer_count = first;\n\n  // Extended graphics may be no longer needed after destroying layers\n  if(graphics.layer_count == NUM_DEFAULT_LAYERS)\n    graphics.requires_extended = false;\n}\n\nvoid destruct_layers(void)\n{\n  uint32_t i;\n  for(i = 0; i < TEXTVIDEO_LAYERS; i++)\n  {\n    if(graphics.video_layers[i].data)\n    {\n      free(graphics.video_layers[i].data);\n      graphics.video_layers[i].data = NULL;\n    }\n  }\n  graphics.layer_count_prev = 0;\n  graphics.layer_count = 0;\n}\n\nstatic boolean try_init_video(struct config_info *conf)\n{\n  if(graphics.renderer.init_video(&graphics, conf))\n  {\n    if(video_create_window())\n      return true;\n\n    if(graphics.renderer.free_video)\n      graphics.renderer.free_video(&graphics);\n  }\n  return false;\n}\n\nboolean init_video(struct config_info *conf, const char *caption)\n{\n  graphics.screen_mode = 0;\n  graphics.fullscreen = conf->fullscreen;\n  graphics.fullscreen_windowed = conf->fullscreen_windowed;\n  graphics.resolution_width = conf->resolution_width;\n  graphics.resolution_height = conf->resolution_height;\n  graphics.window_width = conf->window_width;\n  graphics.window_height = conf->window_height;\n  graphics.mouse_status = false;\n  graphics.cursor_hint_mode = conf->cursor_hint_mode;\n  graphics.cursor_timestamp = get_ticks();\n  graphics.cursor_flipflop = 1;\n  graphics.system_mouse = conf->system_mouse;\n  graphics.grab_mouse = conf->grab_mouse;\n  graphics.disable_screensaver = conf->disable_screensaver;\n\n  memset(&(graphics.text_video_layer), 0, sizeof(struct video_layer));\n  graphics.text_video_layer.w = SCREEN_W;\n  graphics.text_video_layer.h = SCREEN_H;\n  graphics.text_video_layer.transparent_col = -1;\n\n  init_layers();\n\n  if(!set_graphics_output(conf))\n    return false;\n\n  if(conf->resolution_width <= 0 || conf->resolution_height <= 0)\n  {\n    // The driver should attempt to pick the best resolution automatically.\n    // Not all create_window implementations support this.\n    graphics.resolution_width = 0;\n    graphics.resolution_height = 0;\n  }\n\n  if(conf->window_width <= 0 || conf->window_height <= 0)\n  {\n    graphics.window_width = 640;\n    graphics.window_height = 350;\n  }\n\n  snprintf(graphics.default_caption, 32, \"%s\", caption);\n\n  if(!try_init_video(conf))\n  {\n    // Try falling back to the first registered renderer\n    debug(\"Failed to initialize '%s', attempting fallback.\\n\", conf->video_output);\n    strcpy(conf->video_output, \"\");\n    if(!set_graphics_output(conf))\n      return false;\n\n    if(!try_init_video(conf))\n    {\n      // One last attempt with the \"safest\" settings.\n      // NOTE: this was originally done in set_video_mode.\n      debug(\"Failed to initialize fallback, trying 640x350.\\n\");\n      graphics.window_width = 640;\n      graphics.window_height = 350;\n      graphics.fullscreen = false;\n      graphics.allow_resize = false;\n      conf->force_bpp = BPP_AUTO;\n\n      if(!try_init_video(conf))\n      {\n        warn(\"Failed to initialize video.\\n\");\n        return false;\n      }\n    }\n  }\n\n  ec_load_set_secondary(mzx_res_get_by_id(MZX_DEFAULT_CHR),\n   graphics.default_charset);\n  ec_load_set_secondary(mzx_res_get_by_id(MZX_EDIT_CHR),\n   graphics.charset + (PROTECTED_CHARSET_POSITION * CHAR_SIZE));\n\n  ec_clear_set();\n  ec_load_mzx();\n  init_palette();\n  graphics.is_initialized = true;\n  return true;\n}\n\nvoid quit_video(void)\n{\n  if(graphics.renderer.free_video)\n    graphics.renderer.free_video(&graphics);\n\n  destruct_layers();\n  graphics.window.is_init = false;\n}\n\nboolean has_video_initialized(void)\n{\n#ifdef CONFIG_SDL\n  // Dummy SDL driver should act as headless.\n  const char *sdl_driver = SDL_GetCurrentVideoDriver();\n  if(sdl_driver && !strcmp(sdl_driver, \"dummy\")) return false;\n#endif /* CONFIG_SDL */\n\n  // Renderers can also report as headless.\n  if(graphics.window.is_init && graphics.window.is_headless)\n    return false;\n\n  return graphics.is_initialized;\n}\n\nstatic void set_window_ratio(struct video_window *window, enum ratio_type ratio)\n{\n  switch(ratio)\n  {\n    case RATIO_MODERN_64_35:\n      window->ratio_numerator = 64;\n      window->ratio_denominator = 35;\n      break;\n    case RATIO_CLASSIC_4_3:\n      window->ratio_numerator = 4;\n      window->ratio_denominator = 3;\n      break;\n    default:\n    case RATIO_STRETCH:\n      window->ratio_numerator = 0;\n      window->ratio_denominator = 0;\n      break;\n  }\n}\n\nunsigned video_create_window(void)\n{\n  struct video_window *window = &(graphics.window);\n\n  if(window->is_init)\n    return false;\n\n  window->bits_per_pixel = graphics.bits_per_pixel;\n  window->is_fullscreen = graphics.fullscreen;\n  window->is_fullscreen_windowed = graphics.fullscreen_windowed;\n  window->is_headless = false;\n  window->allow_resize = graphics.allow_resize;\n  set_window_ratio(window, graphics.ratio);\n\n  if(graphics.fullscreen)\n  {\n    window->width_px = graphics.resolution_width;\n    window->height_px = graphics.resolution_height;\n  }\n  else\n  {\n    window->width_px = graphics.window_width;\n    window->height_px = graphics.window_height;\n  }\n  video_window_update_viewport(window);\n\n  if(graphics.renderer.create_window(&graphics, window))\n  {\n    window->is_init = true;\n    video_set_window_caption(window, graphics.default_caption);\n    video_set_window_icon(window, mzx_res_get_by_id(MZX_ICON_PNG));\n\n    // Make sure a BPP was selected by the renderer (if applicable).\n    if(window->bits_per_pixel == BPP_AUTO)\n      warn(\"renderer.create_window must auto-select BPP! Report this!\\n\");\n\n    return 1;\n  }\n  return 0;\n}\n\nstatic boolean valid_window_id(unsigned window_id)\n{\n  return window_id == 1;\n}\n\nconst struct video_window *video_get_window(unsigned window_id)\n{\n  if(valid_window_id(window_id))\n    return &graphics.window;\n\n  return NULL;\n}\n\nunsigned video_window_by_platform_id(unsigned platform_id)\n{\n  if(graphics.window.platform_id == platform_id)\n    return 1;\n\n  return 0;\n}\n\n/**\n * Update the scaled viewport coordinates within the window according to\n * the window's scaling ratio, for use in SDL update rectangles or glViewport.\n * The viewport is also used to translate mouse coordinates between screen\n * and window space.\n *\n * This should be called by the renderer any time create_window forces the\n * window size (SDL window creation functions already handle this).\n * This is called automatically between resize_window and resize_callback.\n *\n * For best results, call glViewport (or equivalent) in either\n * resize_callback or sync_screen.\n */\nvoid video_window_update_viewport(struct video_window *window)\n{\n  if(graphics.renderer.set_viewport)\n  {\n    graphics.renderer.set_viewport(&graphics, window);\n  }\n  else\n  {\n    debug(\"Implement set_viewport!\");\n    window->viewport_x = 0;\n    window->viewport_y = 0;\n    window->viewport_width = window->width_px;\n    window->viewport_height = window->height_px;\n    window->is_integer_scaled = false;\n  }\n}\n\n/* Sync the current window size after it has ALREADY been changed by the\n * platform or by a call to a graphics.c window resize function.\n * SDL2/3 reports a window resize event after it is resized;\n * this is the only situation in which this should be called anywhere else. */\nvoid video_sync_window_size(unsigned window_id,\n unsigned new_width_px, unsigned new_height_px)\n{\n  if(valid_window_id(window_id))\n  {\n    graphics.window.width_px = new_width_px;\n    graphics.window.height_px = new_height_px;\n\n    if(graphics.window.is_fullscreen)\n    {\n      graphics.resolution_width = new_width_px;\n      graphics.resolution_height = new_height_px;\n    }\n    else\n    {\n      graphics.window_width = new_width_px;\n      graphics.window_height = new_height_px;\n    }\n    video_window_update_viewport(&(graphics.window));\n\n    if(graphics.renderer.resize_callback)\n      graphics.renderer.resize_callback(&graphics, &graphics.window);\n\n    graphics.palette_dirty = true;\n    update_screen();\n  }\n}\n\nstatic void resize_window(unsigned window_id, boolean fullscreen)\n{\n  if(graphics.renderer.resize_window)\n  {\n    if(fullscreen)\n    {\n      graphics.window.width_px = graphics.resolution_width;\n      graphics.window.height_px = graphics.resolution_height;\n    }\n    else\n    {\n      graphics.window.width_px = graphics.window_width;\n      graphics.window.height_px = graphics.window_height;\n    }\n    graphics.window.is_fullscreen = fullscreen;\n    graphics.fullscreen = fullscreen;\n    set_window_ratio(&graphics.window, graphics.ratio);\n\n    graphics.renderer.resize_window(&graphics, &graphics.window);\n\n    // Renderer resize_window may have modified the requested dimensions.\n    video_sync_window_size(window_id,\n     graphics.window.width_px, graphics.window.height_px);\n  }\n}\n\n/* Change the current window size. This should NOT be called in response to\n * SDL2/3 window resize events, but when something in MegaZeux explicitly\n * wants the window size to change. If currently in fullscreen, this switches\n * to windowed mode. */\nvoid video_resize_window(unsigned window_id,\n unsigned new_width_px, unsigned new_height_px)\n{\n  if(graphics.renderer.resize_window && valid_window_id(window_id))\n  {\n    graphics.window_width = new_width_px;\n    graphics.window_height = new_height_px;\n    resize_window(window_id, false);\n  }\n}\n\n/* Set the current fullscreen size and switch to fullscreen mode. */\nvoid video_resize_fullscreen(unsigned window_id,\n unsigned new_width_px, unsigned new_height_px)\n{\n  if(graphics.renderer.resize_window && valid_window_id(window_id))\n  {\n    graphics.resolution_width = new_width_px;\n    graphics.resolution_height = new_height_px;\n    resize_window(window_id, true);\n  }\n}\n\nvoid video_toggle_fullscreen(void)\n{\n  unsigned current_fullscreen = video_get_fullscreen_window();\n  if(current_fullscreen)\n    resize_window(current_fullscreen, false);\n  else\n    resize_window(1, true);\n}\n\nunsigned video_get_fullscreen_window(void)\n{\n  if(graphics.window.is_fullscreen)\n    return 1;\n  return 0;\n}\n\nboolean video_is_fullscreen(void)\n{\n  return video_get_fullscreen_window() > 0;\n}\n\nboolean video_set_window_caption(struct video_window *window, const char *caption)\n{\n  if(!window)\n    window = &(graphics.window);\n\n  if(graphics.renderer.set_window_caption)\n  {\n    if(!graphics.renderer.set_window_caption(&graphics, window, caption))\n    {\n      debug(\"--VIDEO-- video_set_window_caption failed: %s\\n\",\n       caption ? caption : \"NULL\");\n      return false;\n    }\n    trace(\"--VIDEO-- video_set_window_caption: %s\\n\", caption ? caption : \"NULL\");\n  }\n  return true;\n}\n\nboolean video_set_window_icon(struct video_window *window, const char *icon_path)\n{\n  if(!window)\n    window = &(graphics.window);\n\n  if(graphics.renderer.set_window_icon)\n  {\n    if(!graphics.renderer.set_window_icon(&graphics, window, icon_path))\n    {\n      debug(\"--VIDEO-- video_set_window_icon failed: %s\\n\",\n       icon_path ? icon_path : \"NULL\");\n      return false;\n    }\n    trace(\"--VIDEO-- video_set_window_icon: %s\\n\", icon_path ? icon_path : \"NULL\");\n  }\n  return true;\n}\n\nboolean change_video_output(struct config_info *conf, const char *output)\n{\n  char old_video_output[sizeof(conf->video_output)];\n  size_t len = sizeof(conf->video_output);\n  boolean fallback = false;\n  boolean retval = true;\n\n  memcpy(old_video_output, conf->video_output, len);\n  old_video_output[len - 1] = 0;\n\n  snprintf(conf->video_output, len, \"%s\", output);\n  conf->video_output[len - 1] = 0;\n\n  if(graphics.renderer.free_video)\n    graphics.renderer.free_video(&graphics);\n\n  set_graphics_output(conf);\n\n  if(!try_init_video(conf))\n  {\n    retval = false;\n\n    memcpy(conf->video_output, old_video_output, len);\n    if(!set_graphics_output(conf))\n    {\n      warn(\"Failed to roll back renderer!\\n\");\n      fallback = true;\n    }\n    else\n\n    if(!try_init_video(conf))\n    {\n      warn(\"Failed to roll back video mode!\\n\");\n      fallback = true;\n    }\n  }\n\n  if(fallback)\n  {\n    // Attempt the first renderer in the list (unless that just failed).\n    if(!strcmp(conf->video_output, renderers->name))\n    {\n      warn(\"Aborting!\\n\");\n      exit(1);\n    }\n\n    snprintf(conf->video_output, len, \"%s\", renderers->name);\n    if(!set_graphics_output(conf))\n    {\n      warn(\"Failed to load fallback renderer, aborting!\\n\");\n      exit(1);\n    }\n\n    if(!try_init_video(conf))\n    {\n      warn(\"Failed to set fallback video mode, aborting!\\n\");\n      exit(1);\n    }\n  }\n\n  update_palette();\n  return retval;\n}\n\nint get_available_video_output_list(const char **buffer, int buffer_len)\n{\n  const struct renderer_data *renderer = renderers;\n  int i;\n\n  for(i = 0; i < buffer_len; i++, renderer++)\n  {\n    if(!renderer->name)\n      break;\n\n    buffer[i] = renderer->name;\n  }\n  return i;\n}\n\nint get_current_video_output(void)\n{\n  return graphics.renderer_num;\n}\n\nstatic void dirty_ui(void)\n{\n  if(graphics.requires_extended) return;\n  if(graphics.current_layer != UI_LAYER) return;\n  if(graphics.screen_mode == 0) return;\n  graphics.requires_extended = true;\n}\n\nstatic void dirty_current(void)\n{\n  graphics.video_layers[graphics.current_layer].empty = false;\n}\n\nstatic int offset_adjust(int offset, unsigned int x, unsigned int y)\n{\n  // Transform the given offset from screen space to layer space\n  struct video_layer *layer = &graphics.video_layers[graphics.current_layer];\n\n  if(layer->w != SCREEN_W || layer->x != 0 || layer->y != 0)\n  {\n    // NOTE: using the UI drawing functions on non-aligned layers is undefined\n    // behavior. Use draw_char_to_layer instead.\n    assert((int)x >= (layer->x / CHAR_W));\n    assert((int)y >= (layer->y / CHAR_H));\n    assert(x < (layer->x / CHAR_W) + layer->w);\n    assert(y < (layer->y / CHAR_H) + layer->h);\n\n    x -= layer->x / CHAR_W;\n    y -= layer->y / CHAR_H;\n    return y * layer->w + x;\n  }\n  return offset;\n}\n\nstatic int hexdigit(uint8_t hex)\n{\n  if(hex >= '0' && hex <= '9')\n    return hex - '0';\n  if(hex >= 'A' && hex <= 'F')\n    return hex - 'A' + 10;\n  if(hex >= 'a' && hex <= 'f')\n    return hex - 'a' + 10;\n  return -1;\n}\n\n/**\n * Helper function for handling `color_string` strings.\n * Return the total display length of the nul terminated color string `string`.\n * Will stop after `max_size` bytes are read from `string`, if applicable.\n */\nsize_t color_string_length(const char *string, size_t max_size)\n{\n  size_t pos = 0;\n  size_t i;\n  for(i = 0; i < max_size; i++)\n  {\n    if(!string[i])\n      break;\n\n    if(string[i] == '~' || string[i] == '@')\n    {\n      // All ~/@ values either escape the next char or display a color code.\n      i++;\n      if(i >= max_size && !string[i])\n        break;\n\n      if(isxdigit((uint8_t)string[i]))\n      {\n        // Color code--no display chars.\n        continue;\n      }\n    }\n    // Display character.\n    pos++;\n  }\n  return pos;\n}\n\n/**\n * Helper function for handling `color_string` strings.\n * Find the byte index of the character at display position `offset` in a color\n * string. If the string displays as fewer than `offset` characters, returns\n * the offset of the terminator or nul instead (whichever is found first).\n * Will stop after `max_size` bytes are read from `string`, if applicable.\n * This is useful for clipping a color string for display.\n */\nsize_t color_string_index_of(const char *string, size_t max_size, size_t offset,\n int terminator)\n{\n  size_t pos = 0;\n  size_t i;\n  for(i = 0; i < max_size && pos < offset; i++)\n  {\n    if(!string[i] || string[i] == terminator)\n      break;\n\n    if(string[i] == '~' || string[i] == '@')\n    {\n      // All ~/@ values either escape the next char or display a color code.\n      i++;\n      if(i >= max_size && (!string[i] || string[i] == terminator))\n        break;\n\n      if(isxdigit((uint8_t)string[i]))\n      {\n        // Color code--no display chars.\n        continue;\n      }\n    }\n    // Display character.\n    pos++;\n  }\n  return i;\n}\n\n/**\n * Helper function for handling `color_string` strings.\n * Returns the final color code of a color string, same as the return value\n * of `color_string_ext_special`, without actually displaying the string.\n */\nuint8_t color_string_get_final_color(const char *string, size_t max_size,\n uint8_t initial_color)\n{\n  int fg_color = initial_color & 0x0f;\n  int bg_color = initial_color >> 4;\n  size_t i;\n  for(i = 0; i < max_size; i++)\n  {\n    if(!string[i])\n      break;\n\n    if(string[i] == '~' || string[i] == '@')\n    {\n      // All ~/@ values either escape the next char or display a color code.\n      i++;\n      if(i >= max_size && !string[i])\n        break;\n\n      if(isxdigit((uint8_t)string[i]))\n      {\n        // Color code--no display chars.\n        int num = hexdigit(string[i]);\n        if(string[i - 1] == '~')\n          fg_color = num;\n        else\n          bg_color = num;\n\n        continue;\n      }\n    }\n    // Display character.\n  }\n  return (bg_color << 4) | fg_color;\n}\n\n// Note: 2.93 erroneously had sane color escaping behavior here.\n// ~ and @ should always escape the next char regardless of whether it's ~/@.\nstatic int write_string_intl(const char *str, unsigned int x, unsigned int y,\n uint8_t color, boolean allow_tabs, boolean allow_newline, boolean end_newline,\n boolean allow_colors, boolean mask_midchars, int chr_offset, int color_offset)\n{\n  int scr_off = (y * SCREEN_W) + x;\n  struct char_element *dest = graphics.current_video + offset_adjust(scr_off, x, y);\n  struct char_element *dest_copy = graphics.text_video + scr_off;\n\n  int bg_color = (color >> 4) + color_offset;\n  int fg_color = (color & 0x0F) + color_offset;\n  int code;\n\n  dirty_ui();\n  dirty_current();\n\n  while(*str && dest < graphics.current_video_end)\n  {\n    int cur_char = *str++;\n\n    if(cur_char == '\\n')\n    {\n      if(end_newline)\n        break;\n\n      if(allow_newline)\n      {\n        y++;\n        dest = graphics.current_video + offset_adjust((y * SCREEN_W) + x, x, y);\n        dest_copy = graphics.text_video + (y * SCREEN_W) + x;\n        continue;\n      }\n    }\n    else\n\n    if(cur_char == '\\t')\n    {\n      if(allow_tabs)\n      {\n        // Note: the write_line functions used 10 here, but they were used\n        // only for scrolls, which don't allow control codes. 1.x allowed\n        // char 9 but displayed them as char 9.\n        dest += 5;\n        dest_copy += 5;\n        continue;\n      }\n    }\n    else\n\n    if(allow_colors)\n    {\n      if(cur_char == '@')\n      {\n        if(!*str)\n          break;\n\n        code = hexdigit(*str);\n        if(code >= 0)\n        {\n          bg_color = code + color_offset;\n          str++;\n          continue;\n        }\n        else\n          cur_char = *str++;\n      }\n      else\n\n      if(cur_char == '~')\n      {\n        if(!*str)\n          break;\n\n        code = hexdigit(*str);\n        if(code >= 0)\n        {\n          fg_color = code + color_offset;\n          str++;\n          continue;\n        }\n        else\n          cur_char = *str++;\n      }\n    }\n\n    if(mask_midchars)\n    {\n      if(cur_char >= 32 && cur_char <= 126)\n        chr_offset = PRO_CH;\n      else\n        chr_offset = 0;\n    }\n\n    /* Draw char */\n    dest->char_value = cur_char + chr_offset;\n    dest->bg_color = bg_color;\n    dest->fg_color = fg_color;\n    *(dest_copy++) = *dest;\n    dest++;\n  }\n\n  return ((bg_color & 0xf) << 4) | (fg_color & 0xf);\n}\n\nvoid write_string_ext(const char *str, unsigned int x, unsigned int y,\n uint8_t color, int flags, unsigned int chr_offset, unsigned int color_offset)\n{\n  boolean allow_tabs      = (flags & WR_TAB) != 0;\n  boolean allow_newlines  = (flags & WR_NEWLINE) != 0;\n  boolean end_newline     = (flags & WR_LINE) != 0;\n  boolean allow_colors    = (flags & WR_COLOR) != 0;\n  boolean mask_midchars   = (flags & WR_MASK) != 0;\n\n  write_string_intl(str, x, y, color, allow_tabs, allow_newlines,\n   end_newline, allow_colors, mask_midchars, chr_offset, color_offset);\n}\n\nvoid color_string_ext_special(const char *str, unsigned int x, unsigned int y,\n uint8_t *color, boolean allow_newline, unsigned int chr_offset, unsigned int color_offset)\n{\n  *color = write_string_intl(str, x, y, *color,\n   false, allow_newline, false, true, false, chr_offset, color_offset);\n}\n\nvoid color_string_ext(const char *str, unsigned int x, unsigned int y,\n uint8_t color, boolean allow_newline, unsigned int chr_offset, unsigned int color_offset)\n{\n  color_string_ext_special(str, x, y, &color, allow_newline, chr_offset, color_offset);\n}\n\n// Set rightalign to print the rightmost char at xy and proceed to the left\n// minlen is the minimum length to print. Pad with 0.\n\nvoid write_number(int number, uint8_t color, unsigned int x, unsigned int y,\n int minlen, boolean rightalign, int base)\n{\n  char temp[12];\n  minlen = CLAMP(minlen, 0, 11);\n\n  if(base == 10)\n    snprintf(temp, 12, \"%0*d\", minlen, number);\n  else\n    snprintf(temp, 12, \"%0*x\", minlen, number);\n\n  temp[11] = 0;\n\n  if(rightalign)\n  {\n    unsigned int shift = strlen(temp) - 1;\n    if(shift < x)\n      x -= shift;\n    else\n      x = 0;\n  }\n\n  write_string_intl(temp, x, y, color,\n   false, false, false, false, false, PRO_CH, 16);\n}\n\nstatic void color_line_ext(unsigned int length, unsigned int x, unsigned int y,\n uint8_t color, unsigned int color_offset)\n{\n  int scr_off = (y * SCREEN_W) + x;\n  struct char_element *dest = graphics.current_video + offset_adjust(scr_off, x, y);\n  struct char_element *dest_copy = graphics.text_video + scr_off;\n  uint8_t bg_color = (color >> 4) + color_offset;\n  uint8_t fg_color = (color & 0x0F) + color_offset;\n  unsigned int i;\n\n  dirty_ui();\n  dirty_current();\n\n  for(i = 0; i < length && dest < graphics.current_video_end; i++)\n  {\n    dest->char_value = dest_copy->char_value;\n    dest->bg_color = bg_color;\n    dest->fg_color = fg_color;\n    *(dest_copy++) = *dest;\n    dest++;\n  }\n}\n\nvoid fill_line_ext(unsigned int length, unsigned int x, unsigned int y,\n uint8_t chr, uint8_t color, unsigned int chr_offset, unsigned int color_offset)\n{\n  int scr_off = (y * SCREEN_W) + x;\n  struct char_element *dest = graphics.current_video + offset_adjust(scr_off, x, y);\n  struct char_element *dest_copy = graphics.text_video + scr_off;\n  uint8_t bg_color = (color >> 4) + color_offset;\n  uint8_t fg_color = (color & 0x0F) + color_offset;\n  unsigned int i;\n\n  dirty_ui();\n  dirty_current();\n\n  for(i = 0; i < length && dest < graphics.current_video_end; i++)\n  {\n    dest->char_value = chr + chr_offset;\n    dest->bg_color = bg_color;\n    dest->fg_color = fg_color;\n    *(dest_copy++) = *dest;\n    dest++;\n  }\n}\n\n#ifdef CONFIG_EDITOR\nvoid draw_char_mixed_pal_ext(uint8_t chr, uint8_t bg_color,\n uint8_t fg_color, unsigned int x, unsigned int y, unsigned int chr_offset)\n{\n  int scr_off = (y * SCREEN_W) + x;\n  struct char_element *dest = graphics.current_video + offset_adjust(scr_off, x, y);\n  struct char_element *dest_copy = graphics.text_video + scr_off;\n  dest->char_value = chr + chr_offset;\n\n  dest->bg_color = bg_color & 31;\n  dest->fg_color = fg_color & 31;\n\n  *(dest_copy++) = *dest;\n\n  dirty_ui();\n  dirty_current();\n}\n#endif /* CONFIG_EDITOR */\n\nvoid draw_char_ext(uint8_t chr, uint8_t color, unsigned int x, unsigned int y,\n unsigned int chr_offset, unsigned int color_offset)\n{\n  int scr_off = (y * SCREEN_W) + x;\n  struct char_element *dest = graphics.current_video + offset_adjust(scr_off, x, y);\n  struct char_element *dest_copy = graphics.text_video + scr_off;\n  dest->char_value = chr + chr_offset;\n  dest->bg_color = (color >> 4) + color_offset;\n  dest->fg_color = (color & 0x0F) + color_offset;\n  *(dest_copy++) = *dest;\n\n  dirty_ui();\n  dirty_current();\n}\n\n/**\n * draw_char_ext, except if the color being drawn has a background color of 0,\n * the background color from text_video will bleed through to the new character.\n * This effect is used by legacy sprites to simulate transparency.\n */\nvoid draw_char_bleedthru_ext(uint8_t chr, uint8_t color,\n unsigned int x, unsigned int y, unsigned int chr_offset, unsigned int color_offset)\n{\n  int offset = (y * SCREEN_W) + x;\n  struct char_element *dest = graphics.current_video + offset_adjust(offset, x, y);\n  struct char_element *dest_copy = graphics.text_video + offset;\n\n  if(!(color & 0xF0))\n  {\n    // Bleed-through background color from text_video.\n    color |= (dest_copy->bg_color & 0x0F) << 4;\n  }\n\n  dest->char_value = chr + chr_offset;\n  dest->bg_color = (color >> 4) + color_offset;\n  dest->fg_color = (color & 0x0F) + color_offset;\n  *(dest_copy++) = *dest;\n\n  dirty_ui();\n  dirty_current();\n}\n\nvoid draw_char_to_layer(uint8_t chr, uint8_t color,\n unsigned int x, unsigned int y, unsigned int chr_offset, unsigned int color_offset)\n{\n  int w = graphics.video_layers[graphics.current_layer].w;\n  struct char_element *dest = graphics.current_video + (y * w) + x;\n\n  assert(dest < graphics.current_video_end);\n\n  dest->char_value = chr + chr_offset;\n  dest->bg_color = (color >> 4) + color_offset;\n  dest->fg_color = (color & 0x0F) + color_offset;\n  dirty_current();\n}\n\nvoid color_string(const char *string, unsigned int x, unsigned int y, uint8_t color)\n{\n  color_string_ext(string, x, y, color, false, PRO_CH, 16);\n}\n\nvoid write_string(const char *string, unsigned int x, unsigned int y,\n uint8_t color, int flags)\n{\n  write_string_ext(string, x, y, color, flags, PRO_CH, 16);\n}\n\nvoid color_line(unsigned int length, unsigned int x, unsigned int y, uint8_t color)\n{\n  color_line_ext(length, x, y, color, 16);\n}\n\nvoid fill_line(unsigned int length, unsigned int x, unsigned int y,\n uint8_t chr, uint8_t color)\n{\n  fill_line_ext(length, x, y, chr, color, PRO_CH, 16);\n}\n\nvoid draw_char(uint8_t chr, uint8_t color, unsigned int x, unsigned int y)\n{\n  draw_char_ext(chr, color, x, y, PRO_CH, 16);\n}\n\nvoid erase_char(unsigned int x, unsigned int y)\n{\n  int scr_off = (y * SCREEN_W) + x;\n  struct char_element *dest = graphics.current_video + offset_adjust(scr_off, x, y);\n  dest->char_value = INVISIBLE_CHAR;\n}\n\nvoid erase_area(unsigned int x, unsigned int y, unsigned int x2, unsigned int y2)\n{\n  unsigned int i, j;\n\n  for(i = y; i <= y2; i++)\n    for(j = x; j <= x2; j++)\n      erase_char(j, i);\n}\n\nvoid clear_screen(void)\n{\n  // Hide the game screen by drawing blank chars over the UI.\n  struct char_element *dest;\n  struct char_element *dest_copy = graphics.text_video;\n  uint32_t current_layer = graphics.current_layer;\n  int i;\n\n  select_layer(UI_LAYER);\n  dirty_current();\n\n  dest = graphics.current_video;\n  for(i = 0; i < (SCREEN_W * SCREEN_H); i++)\n  {\n    dest->char_value = 0;\n    dest->fg_color = 16; // Protected black\n    dest->bg_color = 16; // Protected black\n    *(dest_copy++) = *dest;\n    dest++;\n  }\n\n  select_layer(current_layer);\n  update_screen();\n}\n\nvoid set_screen(struct char_element *src)\n{\n  int offset = SCREEN_W * SCREEN_H;\n  int size = offset * sizeof(struct char_element);\n\n#ifndef CONFIG_NO_LAYER_RENDERING\n  memcpy(graphics.video_layers[UI_LAYER].data, src, size);\n  src += offset;\n\n  memcpy(graphics.video_layers[GAME_UI_LAYER].data, src, size);\n  src += offset;\n#endif\n\n  memcpy(graphics.text_video, src, size);\n}\n\nvoid get_screen(struct char_element *dest)\n{\n  int offset = SCREEN_W * SCREEN_H;\n  int size = offset * sizeof(struct char_element);\n\n#ifndef CONFIG_NO_LAYER_RENDERING\n  memcpy(dest, graphics.video_layers[UI_LAYER].data, size);\n  dest += offset;\n\n  memcpy(dest, graphics.video_layers[GAME_UI_LAYER].data, size);\n  dest += offset;\n#endif\n\n  memcpy(dest, graphics.text_video, size);\n}\n\nvoid cursor_underline(unsigned int x, unsigned int y)\n{\n  graphics.cursor_mode = CURSOR_MODE_UNDERLINE;\n  graphics.cursor_x = x;\n  graphics.cursor_y = y;\n}\n\nvoid cursor_solid(unsigned int x, unsigned int y)\n{\n  graphics.cursor_mode = CURSOR_MODE_SOLID;\n  graphics.cursor_x = x;\n  graphics.cursor_y = y;\n}\n\nvoid cursor_hint(unsigned int x, unsigned int y)\n{\n  graphics.cursor_mode = graphics.cursor_hint_mode;\n  graphics.cursor_x = x;\n  graphics.cursor_y = y;\n}\n\nvoid cursor_off(void)\n{\n  graphics.cursor_mode = CURSOR_MODE_INVISIBLE;\n}\n\nvoid m_hide(void)\n{\n  graphics.mouse_status = false;\n}\n\nvoid m_show(void)\n{\n  graphics.mouse_status = true;\n}\n\nvoid mouse_size(unsigned int width, unsigned int height)\n{\n  graphics.mouse_width = width;\n  graphics.mouse_height = height;\n}\n\n#if defined(CONFIG_ENABLE_SCREENSHOTS) || defined(CONFIG_EDITOR)\n\nstatic void screenshot_init_palette(struct graphics_data *graphics,\n struct rgb_color palette[FULL_PAL_SIZE], uint32_t backup_palette[FULL_PAL_SIZE])\n{\n  int palette_size;\n  int i;\n\n  if(backup_palette)\n  {\n    memcpy(backup_palette, graphics->flat_intensity_palette,\n     FULL_PAL_SIZE * sizeof(uint32_t));\n  }\n\n  palette_size = make_palette(palette);\n  for(i = 0; i < palette_size; i++)\n  {\n    // PNG byte order: R, G, B, A\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n    graphics->flat_intensity_palette[i] = 0xff |\n     (palette[i].b << 8) | (palette[i].g << 16) | (palette[i].r << 24);\n#else\n    graphics->flat_intensity_palette[i] = 0xff000000u |\n     (palette[i].b << 16) | (palette[i].g << 8) | (palette[i].r << 0);\n#endif /* PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN */\n  }\n}\n\n#endif /* CONFIG_ENABLE_SCREENSHOTS || CONFIG_EDITOR */\n\n#ifdef CONFIG_ENABLE_SCREENSHOTS\n#if 1 // CONFIG_PNG\n\n#define DUMP_FMT_EXT \"png\"\n\nstruct dump_screen_priv\n{\n  const uint8_t *pix8;\n  const uint32_t *pix32;\n};\n\n/*\nstatic const uint8_t *dump_screen_callback(size_t num_pixels, void *priv)\n{\n  struct dump_screen_priv *data = (struct dump_screen_priv *)priv;\n  const uint8_t *ret = data->pix8;\n  data->pix8 += num_pixels;\n  return ret;\n}\n\nstatic void dump_screen_real(const uint8_t *pix,\n const struct rgb_color *pal, int count, const char *name)\n{\n  struct dump_screen_priv priv = { pix, NULL };\n  png_write_image_8bpp(name, 640, 350, pal, count, &priv, dump_screen_callback);\n}\n*/\n\nstatic const uint32_t *dump_screen_callback_32bpp(size_t num_pixels, void *priv)\n{\n  struct dump_screen_priv *data = (struct dump_screen_priv *)priv;\n  const uint32_t *ret = data->pix32;\n  data->pix32 += num_pixels;\n  return ret;\n}\n\nstatic void dump_screen_real_32bpp(const uint32_t *pix, const char *name)\n{\n  struct dump_screen_priv priv = { NULL, pix };\n  png_write_image_32bpp(name, 640, 350, &priv, dump_screen_callback_32bpp);\n}\n\n#else\n\n#define DUMP_FMT_EXT \"bmp\"\n\n/*\nstatic void dump_screen_real(const uint8_t *pix, const struct rgb_color *pal,\n int count, const char *name)\n{\n  vfile *file;\n  int i;\n\n  file = vfopen_unsafe(name, \"wb\");\n  if(!file)\n    return;\n\n  // BMP header\n  vfputw(0x4D42, file); // BM\n  vfputd(14 + 40 + count * 4 + 640 * 350, file); // BMP + DIB + palette + image\n  vfputd(0, file); // Reserved\n  vfputd(14, file); // DIB header offset\n\n  // DIB header\n  vfputd(40, file); // DIB header size (Windows 3/BITMAPINFOHEADER)\n  vfputd(640, file); // Width in pixels\n  vfputd(350, file); // Height in pixels\n  vfputw(1, file); // Number of color planes\n  vfputw(8, file); // Bits per pixel\n  vfputd(0, file); // Compression method (none)\n  vfputd(640 * 350, file); // Image data size\n  vfputd(3780, file); // Horizontal dots per meter\n  vfputd(3780, file); // Vertical dots per meter\n  vfputd(count, file); // Number of colors in palette\n  vfputd(0, file); // Number of important colors\n\n  // Color palette\n  for(i = 0; i < count; i++)\n  {\n    vfputc(pal[i].b, file);\n    vfputc(pal[i].g, file);\n    vfputc(pal[i].r, file);\n    vfputc(0, file);\n  }\n\n  // Image data\n  for(i = 349; i >= 0; i--)\n  {\n    vfwrite(pix + i * 640, sizeof(uint8_t), 640, file);\n  }\n\n  vfclose(file);\n}\n*/\n\nstatic void dump_screen_real_32bpp(const uint32_t *pix, const char *name)\n{\n  vfile *file;\n  int i, x;\n  uint8_t rowbuffer[SCREEN_PIX_W * 3]; // 24bpp\n  uint8_t *rowbuffer_ptr;\n  const uint8_t *pix_ptr;\n\n  file = vfopen_unsafe(name, \"wb\");\n  if(!file)\n    return;\n\n  // BMP header\n  vfputw(0x4D42, file); // BM\n  // BMP + DIB + image\n  vfputd(14 + 40 + SCREEN_PIX_W * SCREEN_PIX_H * 3, file);\n  vfputd(0, file); // Reserved\n  vfputd(14, file); // DIB header offset\n\n  // DIB header\n  vfputd(40, file); // DIB header size (Windows 3/BITMAPINFOHEADER)\n  vfputd(SCREEN_PIX_W, file); // Width in pixels\n  vfputd(SCREEN_PIX_H, file); // Height in pixels\n  vfputw(1, file); // Number of color planes\n  vfputw(24, file); // Bits per pixel\n  vfputd(0, file); // Compression method (none)\n  vfputd(SCREEN_PIX_W * SCREEN_PIX_H * 3, file); // Image data size\n  vfputd(3780, file); // Horizontal dots per meter\n  vfputd(3780, file); // Vertical dots per meter\n  vfputd(0, file); // Number of colors in palette\n  vfputd(0, file); // Number of important colors\n\n  // Image data\n  for(i = SCREEN_PIX_H - 1; i >= 0; i--)\n  {\n    pix_ptr = (const uint8_t *)(pix + i * SCREEN_PIX_W);\n    rowbuffer_ptr = rowbuffer;\n    for(x = 0; x < SCREEN_PIX_W; x++)\n    {\n      rowbuffer_ptr[0] = pix_ptr[2];\n      rowbuffer_ptr[1] = pix_ptr[1];\n      rowbuffer_ptr[2] = pix_ptr[0];\n      rowbuffer_ptr += 3;\n      pix_ptr += 4;\n    }\n    vfwrite(rowbuffer, SCREEN_PIX_W * 3, 1, file);\n  }\n\n  vfclose(file);\n}\n\n#endif // CONFIG_PNG\n\n#define MAX_NAME_SIZE 20\n\nstatic void screenshot_cleanup_palette(const uint32_t backup_palette[FULL_PAL_SIZE])\n{\n  memcpy(graphics.flat_intensity_palette, backup_palette,\n   FULL_PAL_SIZE * sizeof(uint32_t));\n}\n\nvoid dump_screen(void)\n{\n  struct rgb_color palette[FULL_PAL_SIZE];\n  uint32_t backup_palette[FULL_PAL_SIZE];\n  char name[MAX_NAME_SIZE];\n  struct stat file_info;\n  uint32_t *ss;\n  int i;\n  uint32_t layer;\n\n  for(i = 0; i < 99999; i++)\n  {\n    snprintf(name, MAX_NAME_SIZE - 1, \"screen%d.%s\", i, DUMP_FMT_EXT);\n    name[MAX_NAME_SIZE - 1] = '\\0';\n    if(vstat(name, &file_info))\n      break;\n  }\n\n  ss = (uint32_t *)cmalloc(sizeof(uint32_t) * 640 * 350);\n  if(!ss)\n    return;\n\n  screenshot_init_palette(&graphics, palette, backup_palette);\n\n  for(layer = 0; layer < graphics.layer_count; layer++)\n    graphics.sorted_video_layers[layer] = &graphics.video_layers[layer];\n\n  qsort(graphics.sorted_video_layers, graphics.layer_count,\n   sizeof(struct video_layer *), compare_layers);\n\n  for(layer = 0; layer < graphics.layer_count; layer++)\n  {\n    render_layer(ss, SCREEN_PIX_W, SCREEN_PIX_H,\n     SCREEN_PIX_W * sizeof(uint32_t), 32, &graphics,\n     graphics.sorted_video_layers[layer]);\n  }\n\n  screenshot_cleanup_palette(backup_palette);\n\n  //dump_screen_real(ss, palette, make_palette(palette), name);\n  dump_screen_real_32bpp(ss, name);\n  free(ss);\n}\n#endif /* CONFIG_ENABLE_SCREENSHOTS */\n\n#ifdef CONFIG_EDITOR\nstruct dump_layer_priv\n{\n  boolean (*status_callback)(void *, size_t, size_t);\n  void *status_priv;\n  struct graphics_data *graphics_copy;\n  uint32_t *flat_array;\n  size_t pitch;\n  size_t width_px;\n  size_t height_px;\n  size_t width_ch;\n  size_t height_ch;\n  size_t pos_in_layer;\n  size_t pos_in_array;\n  struct video_layer layer;\n};\n\n/* Render the layer one row at a time as it may be LARGE. */\nstatic const uint32_t *dump_layer_callback(size_t num_pixels, void *priv)\n{\n  struct dump_layer_priv *data = (struct dump_layer_priv *)priv;\n  uint32_t *pos;\n\n  if(data->pos_in_array >= CHAR_H)\n  {\n    if(data->status_callback)\n    {\n      if(!data->status_callback(data->status_priv,\n       data->pos_in_layer, data->height_ch))\n        return NULL; // cancel requested\n    }\n\n    if(data->pos_in_layer >= data->height_ch)\n      return NULL;\n\n    render_layer(data->flat_array, data->width_px, CHAR_H,\n     data->pitch, 32, data->graphics_copy, &data->layer);\n    data->layer.data += data->width_ch;\n    data->pos_in_layer++;\n    data->pos_in_array = 0;\n  }\n\n  pos = data->flat_array + (data->pitch >> 2) * data->pos_in_array;\n  data->pos_in_array++;\n  return pos;\n}\n\n/* Render an arbitrary layer to an image.\n * TODO: The caller is responsible for constructing the layer char_element\n * array since the graphics API functions are very much intended for operating\n * on real graphical layers rather than arbitrary layers. */\nboolean dump_layer_to_image(const char *filename,\n size_t width_ch, size_t height_ch, const struct char_element *layer,\n boolean (*status_callback)(void *priv, size_t p, size_t m), void *priv)\n{\n  struct graphics_data *graphics_copy;\n  struct rgb_color palette[FULL_PAL_SIZE];\n  struct dump_layer_priv data;\n  uint32_t *flat_array;\n  boolean ret;\n\n  if(width_ch >= 32768 || height_ch >= 32768)\n    return false;\n\n  /* This may be run as an async task, which results in palette conflicts.\n   * The easiest thing to do is duplicate graphics entirely. */\n  graphics_copy = (struct graphics_data *)cmalloc(sizeof(struct graphics_data));\n  if(!graphics_copy)\n    return false;\n\n  flat_array = (uint32_t *)cmalloc(width_ch * CHAR_W * CHAR_H * sizeof(uint32_t));\n  if(!flat_array)\n  {\n    free(graphics_copy);\n    return false;\n  }\n\n  memcpy(graphics_copy, &graphics, sizeof(struct graphics_data));\n  memset(&data, 0, sizeof(data));\n  data.status_callback = status_callback;\n  data.status_priv = priv;\n  data.graphics_copy = graphics_copy;\n  data.flat_array = flat_array;\n  data.pitch = width_ch * CHAR_W * sizeof(uint32_t);\n  data.width_px = width_ch * CHAR_W;\n  data.height_px = height_ch * CHAR_H;\n  data.width_ch = width_ch;\n  data.height_ch = height_ch;\n  data.pos_in_array = CHAR_H; // redraw\n  data.pos_in_layer = 0;\n\n  data.layer.data = (struct char_element *)layer;\n  data.layer.x = 0;\n  data.layer.y = 0;\n  data.layer.w = width_ch;\n  data.layer.h = 1;\n  data.layer.transparent_col = -1;\n  data.layer.offset = 0;\n  data.layer.mode = get_screen_mode();\n  data.layer.empty = false;\n\n  screenshot_init_palette(graphics_copy, palette, NULL);\n\n  ret = png_write_image_32bpp(filename,\n   width_ch * CHAR_W, height_ch * CHAR_H, &data, dump_layer_callback);\n\n  free(graphics_copy);\n  free(flat_array);\n  return ret;\n}\n#endif /* CONFIG_EDITOR */\n\n/**\n * Generate a bitmask of visible pixels for a character/palette pair using the\n * current screen mode and a given transparent color index. The provided buffer\n * must be CHAR_SIZE bytes in length. Returns `false` if there are no visible\n * pixels, otherwise `true`.\n */\nboolean get_char_visible_bitmask(uint16_t char_idx, uint8_t palette,\n int transparent_color, uint8_t * RESTRICT buffer)\n{\n  const uint8_t *chrdata = graphics.charset + char_idx * CHAR_SIZE;\n  const uint8_t HI = 0xAA;\n  const uint8_t LO = 0x55;\n  int is_transparent[4];\n  int ret = 0x00;\n  int y;\n\n  if(graphics.screen_mode == 0)\n  {\n    int bg = (palette & 0xF0) >> 4;\n    int fg = (palette & 0x0F);\n    is_transparent[0] = (bg == transparent_color);\n    is_transparent[1] = (fg == transparent_color);\n\n    for(y = 0; y < CHAR_SIZE; y++)\n    {\n      uint8_t base = chrdata[y];\n      uint8_t mask = 0xFF;\n\n      if(is_transparent[0])\n        mask &= base;\n\n      if(is_transparent[1])\n        mask &= ~base;\n\n      buffer[y] = mask;\n      ret |= mask;\n    }\n  }\n  else\n  {\n    is_transparent[0] = graphics.smzx_indices[palette * 4 + 0] == transparent_color;\n    is_transparent[1] = graphics.smzx_indices[palette * 4 + 1] == transparent_color;\n    is_transparent[2] = graphics.smzx_indices[palette * 4 + 2] == transparent_color;\n    is_transparent[3] = graphics.smzx_indices[palette * 4 + 3] == transparent_color;\n\n    for(y = 0; y < CHAR_SIZE; y++)\n    {\n      uint8_t base = chrdata[y];\n      uint8_t mask = 0xFF;\n\n      if(is_transparent[0])\n      {\n        uint8_t colmask = (~base & HI) & ((~base & LO) << 1);\n        mask &= ~(colmask | (colmask >> 1));\n      }\n\n      if(is_transparent[1])\n      {\n        uint8_t colmask = (~base & HI) & ((base & LO) << 1);\n        mask &= ~(colmask | (colmask >> 1));\n      }\n\n      if(is_transparent[2])\n      {\n        uint8_t colmask = (base & HI) & ((~base & LO) << 1);\n        mask &= ~(colmask | (colmask >> 1));\n      }\n\n      if(is_transparent[3])\n      {\n        uint8_t colmask = (base & HI) & ((base & LO) << 1);\n        mask &= ~(colmask | (colmask >> 1));\n      }\n\n      buffer[y] = mask;\n      ret |= mask;\n    }\n  }\n  return (ret != 0x00);\n}\n\nvoid get_screen_coords(int screen_x, int screen_y, int *x, int *y,\n int *min_x, int *min_y, int *max_x, int *max_y)\n{\n#if 0\n  if(graphics.renderer.get_screen_coords)\n  {\n    graphics.renderer.get_screen_coords(&graphics, &(graphics.window),\n     screen_x, screen_y, x, y, min_x, min_y, max_x, max_y);\n  }\n  else\n#endif\n  {\n    get_screen_coords_viewport(&graphics, &(graphics.window),\n     screen_x, screen_y, x, y, min_x, min_y, max_x, max_y);\n  }\n}\n\nvoid set_screen_coords(int x, int y, int *screen_x, int *screen_y)\n{\n#if 0\n  if(graphics.renderer.set_screen_coords)\n  {\n    graphics.renderer.set_screen_coords(&graphics, &(graphics.window),\n     x, y, screen_x, screen_y);\n  }\n  else\n#endif\n  {\n    set_screen_coords_viewport(&graphics, &(graphics.window),\n     x, y, screen_x, screen_y);\n  }\n}\n\nvoid focus_screen(int x, int y)\n{\n  int pixel_x = x * graphics.resolution_width  / SCREEN_W +\n                    graphics.resolution_width  / SCREEN_W / 2;\n  int pixel_y = y * graphics.resolution_height / SCREEN_H +\n                    graphics.resolution_height / SCREEN_H / 2;\n  focus_pixel(pixel_x, pixel_y);\n}\n\nvoid focus_pixel(int x, int y)\n{\n  if(graphics.renderer.focus_pixel)\n    graphics.renderer.focus_pixel(&graphics, x, y);\n}\n\nboolean switch_shader(const char *name)\n{\n  if(graphics.renderer.switch_shader)\n    return graphics.renderer.switch_shader(&graphics, name);\n\n  return false;\n}\n\nboolean layer_renderer_check(boolean show_error)\n{\n#ifdef CONFIG_NO_LAYER_RENDERING\n  if(1)\n#else\n  if(!graphics.renderer.render_layer)\n#endif\n  {\n    if(show_error)\n    {\n      error_message(E_NO_LAYER_RENDERER, 0, NULL);\n      set_error_suppression(E_NO_LAYER_RENDERER, 1);\n    }\n    return false;\n  }\n  return true;\n}\n\n#if (NUM_CHARSETS < 16)\n\n/**\n * Determine if an extended charsets write would be valid on platforms that\n * don't have a full-sized charset buffer. While a renderer capable of layer\n * renderering is required to display from extended charsets, the extended\n * charset buffer space is available to all 2.90+ games and it may be used to\n * copy char data into the main charset. Thus, attempts to access extended\n * charsets do not necessarily have a direct relation to the renderer.\n *\n * This should only ever be the Nintendo DS. Do not add any other platforms\n * that rely on this horrible hack to save (only 50k) RAM unless you also plan\n * to add support for the extended charsets being allocated to the heap so\n * games using them as a buffer don't break.\n */\nstatic boolean extended_charsets_check(boolean show_error, int pos, int count)\n{\n  // Simulate the bounding that'd be used with the normal charset count...\n  pos %= (15 * CHARSET_SIZE);\n\n  if((pos >= PROTECTED_CHARSET_POSITION) ||\n   (pos + count > PROTECTED_CHARSET_POSITION))\n  {\n    if(show_error)\n    {\n      error_message(E_NO_EXTENDED_CHARSETS, 0, NULL);\n      set_error_suppression(E_NO_EXTENDED_CHARSETS, true);\n    }\n    return false;\n  }\n  return true;\n}\n\n#endif\n"
  },
  {
    "path": "src/graphics.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __GRAPHICS_H\n#define __GRAPHICS_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stdint.h>\n\n#include \"configure.h\"\n\nstruct rgb_color\n{\n  uint8_t r;\n  uint8_t g;\n  uint8_t b;\n  uint8_t unused;\n};\n\n#define INVISIBLE_CHAR 65535\n\nstruct char_element\n{\n  uint16_t char_value;\n  uint8_t bg_color;\n  uint8_t fg_color;\n};\n\n#define SCREEN_W 80\n#define SCREEN_H 25\n\n#define CHAR_W 8\n#define CHAR_H 14\n\n#define SCREEN_PIX_W (SCREEN_W * CHAR_W)\n#define SCREEN_PIX_H (SCREEN_H * CHAR_H)\n\n/**\n * The Nintendo DS is a bit of a special case in that we support it despite it\n * not currently handling either the protected charset or the protected palette.\n */\n#if defined(CONFIG_NDS)\n#define NUM_CHARSETS 2\n#endif\n\n#define CHAR_SIZE 14\n#define CHARSET_SIZE 256\n#ifndef NUM_CHARSETS\n#define NUM_CHARSETS 16\n#endif\n#define PROTECTED_CHARSET_POSITION ((NUM_CHARSETS - 1) * CHARSET_SIZE)\n#define PRO_CH  PROTECTED_CHARSET_POSITION\n#define FULL_CHARSET_SIZE (CHARSET_SIZE * NUM_CHARSETS)\n\n#define PAL_SIZE 16\n#define SMZX_PAL_SIZE 256\n#define PROTECTED_PAL_SIZE 16\n#define FULL_PAL_SIZE (SMZX_PAL_SIZE + PROTECTED_PAL_SIZE)\n\n#ifdef CONFIG_NO_LAYER_RENDERING\n#define SET_SCREEN_SIZE (SCREEN_W * SCREEN_H)\n#else\n#define SET_SCREEN_SIZE (SCREEN_W * SCREEN_H * 3)\n#endif\n\n#define TEXTVIDEO_LAYERS 512\n\n#define LAYER_DRAWORDER_BOARD 0\n#define LAYER_DRAWORDER_OVERLAY 1000\n#define LAYER_DRAWORDER_GAME_UI 2000\n#define LAYER_DRAWORDER_UI 3000\n#define LAYER_DRAWORDER_MAX (LAYER_DRAWORDER_UI + 1000)\n\nenum default_video_layers\n{\n  BOARD_LAYER         = 0,\n  OVERLAY_LAYER       = 1,\n  GAME_UI_LAYER       = 2,\n  UI_LAYER            = 3,\n  NUM_DEFAULT_LAYERS  = 4\n};\n\nenum write_string_flags\n{\n  WR_NONE     = 0,\n  WR_TAB      = (1 << 0), /* If set, tab will draw 5 spaces. */\n  WR_NEWLINE  = (1 << 1), /* If set, \\n will act line a newline. */\n  WR_LINE     = (1 << 2), /* If set, stop if \\n is encountered. */\n  WR_COLOR    = (1 << 3), /* If set, allow ~@ color codes. */\n  WR_MASK     = (1 << 4)  /* If set, use the protected charset for 32-126\n                           * and use the user charset for all other chars.\n                           * Overrides the provided chars offset. */\n};\n\nstruct graphics_data;\nstruct video_layer;\nstruct video_window;\n\nstruct renderer\n{\n  boolean (*init_video)       (struct graphics_data *, struct config_info *);\n  void    (*free_video)       (struct graphics_data *);\n  boolean (*create_window)    (struct graphics_data *, struct video_window *);\n  boolean (*resize_window)    (struct graphics_data *, struct video_window *);\n  boolean (*resize_callback)  (struct graphics_data *, struct video_window *);\n  void    (*set_viewport)     (struct graphics_data *, struct video_window *);\n#if 0\n  void    (*get_screen_coords)(struct graphics_data *, struct video_window *,\n                                int screen_x, int screen_y, int *x, int *y,\n                                int *min_x, int *min_y, int *max_x, int *max_y);\n  void    (*set_screen_coords)(struct graphics_data *, struct video_window *,\n                                int x, int y, int *screen_x, int *screen_y);\n#endif\n  boolean (*set_window_caption)(struct graphics_data *, struct video_window *,\n                                const char *caption);\n  boolean (*set_window_icon)  (struct graphics_data *, struct video_window *,\n                                const char *icon_path);\n  boolean (*set_screen_mode)  (struct graphics_data *, unsigned mode);\n  void    (*update_colors)    (struct graphics_data *, struct rgb_color *palette,\n                                unsigned int count);\n  void    (*remap_char_range) (struct graphics_data *, uint16_t first, uint16_t count);\n  void    (*remap_char)       (struct graphics_data *, uint16_t chr);\n  void    (*remap_charbyte)   (struct graphics_data *, uint16_t chr, uint8_t byte);\n  boolean (*switch_shader)    (struct graphics_data *, const char *name);\n  void    (*render_graph)     (struct graphics_data *);\n  void    (*render_layer)     (struct graphics_data *, struct video_layer *);\n  void    (*render_cursor)    (struct graphics_data *, unsigned x, unsigned y,\n                                uint16_t color, unsigned lines, unsigned offset);\n  void    (*hardware_cursor)  (struct graphics_data *, unsigned x, unsigned y,\n                                uint16_t color, unsigned lines, unsigned offset,\n                                boolean enable);\n  void    (*render_mouse)     (struct graphics_data *, unsigned x, unsigned y,\n                                unsigned w, unsigned h);\n  void    (*sync_screen)      (struct graphics_data *, struct video_window *);\n  void    (*focus_pixel)      (struct graphics_data *, unsigned x, unsigned y);\n};\n\nstruct video_layer\n{\n  unsigned int w, h;\n  int x, y;\n  struct char_element *data;\n#if defined(CONFIG_3DS)\n  void *platform_layer_data;\n#endif /* CONFIG_3DS */\n  int draw_order;\n  int transparent_col;\n  int offset;\n  uint8_t mode;\n  boolean empty;\n};\n\nstruct video_window\n{\n  unsigned platform_id;\n  // Real size of the window.\n  unsigned width_px;\n  unsigned height_px;\n  // Scaled viewport size within the window (video_window_update_viewport).\n  // These variables are used to convert from screen space to real window space.\n  int viewport_x;\n  int viewport_y;\n  int viewport_width;\n  int viewport_height;\n  int ratio_numerator;\n  int ratio_denominator;\n\n  unsigned bits_per_pixel;\n  boolean is_init;\n  boolean is_headless;\n  boolean is_integer_scaled;\n  boolean is_fullscreen;\n  boolean is_fullscreen_windowed;\n  boolean allow_resize;\n  boolean grab_mouse;\n};\n\nstruct graphics_data\n{\n  struct video_window window;\n  unsigned int screen_mode;\n  char default_caption[32];\n  struct char_element text_video[SCREEN_W * SCREEN_H];\n  uint8_t charset[CHAR_SIZE * CHARSET_SIZE * NUM_CHARSETS];\n  struct rgb_color palette[SMZX_PAL_SIZE];\n  struct rgb_color protected_palette[PAL_SIZE];\n  struct rgb_color intensity_palette[SMZX_PAL_SIZE];\n  struct rgb_color backup_palette[SMZX_PAL_SIZE];\n  uint8_t smzx_indices[SMZX_PAL_SIZE * 4];\n  uint32_t current_intensity[SMZX_PAL_SIZE];\n  uint32_t saved_intensity[SMZX_PAL_SIZE];\n  uint32_t backup_intensity[SMZX_PAL_SIZE];\n  boolean is_initialized;\n  boolean default_smzx_loaded;\n  boolean palette_dirty;\n  boolean smzx_dirty;\n  boolean fade_status;\n  boolean dialog_fade_status;\n  boolean requires_extended;\n\n  uint32_t layer_count;\n  uint32_t layer_count_prev;\n  struct video_layer text_video_layer;\n  struct video_layer video_layers[TEXTVIDEO_LAYERS];\n  uint32_t current_layer;\n  struct char_element *current_video;\n  struct char_element *current_video_end;\n  struct video_layer *sorted_video_layers[TEXTVIDEO_LAYERS];\n\n  enum cursor_mode_types cursor_mode;\n  enum cursor_mode_types cursor_hint_mode;\n  unsigned int cursor_x;\n  unsigned int cursor_y;\n  unsigned int cursor_flipflop;\n  uint32_t cursor_timestamp;\n  unsigned int mouse_width;\n  unsigned int mouse_height;\n  enum system_mouse_type system_mouse;\n  boolean mouse_status;\n  boolean grab_mouse;\n  boolean fullscreen;\n  boolean fullscreen_windowed;\n  boolean allow_resize;\n  unsigned int resolution_width;\n  unsigned int resolution_height;\n  unsigned int window_width;\n  unsigned int window_height;\n  unsigned int bits_per_pixel;\n  enum ratio_type ratio;\n  enum gl_filter_type gl_filter_method;\n  int gl_vsync;\n  char gl_scaling_shader[32];\n  char sdl_render_driver[16];\n  enum screensaver_disable_mode disable_screensaver;\n\n  uint8_t default_charset[CHAR_SIZE * CHARSET_SIZE];\n\n  uint32_t flat_intensity_palette[FULL_PAL_SIZE];\n  uint32_t protected_pal_position;\n  struct renderer renderer;\n  void *render_data;\n  int renderer_num;\n};\n\nCORE_LIBSPEC void color_string(const char *string, unsigned int x,\n unsigned int y, uint8_t color);\nCORE_LIBSPEC void write_string(const char *string, unsigned int x,\n unsigned int y, uint8_t color, int flags);\nCORE_LIBSPEC void write_number(int number, uint8_t color, unsigned int x,\n unsigned int y, int minlen, boolean rightalign, int base);\nCORE_LIBSPEC void color_line(unsigned int length, unsigned int x,\n unsigned int y, uint8_t color);\nCORE_LIBSPEC void fill_line(unsigned int length, unsigned int x, unsigned int y,\n uint8_t chr, uint8_t color);\nCORE_LIBSPEC void fill_line_ext(unsigned int length, unsigned int x, unsigned int y,\n uint8_t chr, uint8_t color, unsigned int chr_offset, unsigned int color_offset);\nCORE_LIBSPEC void draw_char(uint8_t chr, uint8_t color, unsigned int x, unsigned int y);\nCORE_LIBSPEC void erase_char(unsigned int x, unsigned int y);\nCORE_LIBSPEC void erase_area(unsigned int x, unsigned int y,\n unsigned int x2, unsigned int y2);\n\nCORE_LIBSPEC void write_string_ext(const char *str, unsigned int x, unsigned int y,\n uint8_t color, int flags, unsigned int chr_offset, unsigned int color_offset);\nCORE_LIBSPEC void draw_char_ext(uint8_t chr, uint8_t color, unsigned int x,\n unsigned int y, unsigned int chr_offset, unsigned int color_offset);\nCORE_LIBSPEC void draw_char_bleedthru_ext(uint8_t chr, uint8_t color,\n unsigned int x, unsigned int y, unsigned int chr_offset, unsigned int color_offset);\nCORE_LIBSPEC void draw_char_to_layer(uint8_t chr, uint8_t color,\n unsigned int x, unsigned int y, unsigned int chr_offset, unsigned int color_offset);\n\nCORE_LIBSPEC void clear_screen(void);\n\nCORE_LIBSPEC void cursor_underline(unsigned int x, unsigned int y);\nCORE_LIBSPEC void cursor_solid(unsigned int x, unsigned int y);\nCORE_LIBSPEC void cursor_hint(unsigned int x, unsigned int y);\nCORE_LIBSPEC void cursor_off(void);\n\nCORE_LIBSPEC boolean init_video(struct config_info *conf, const char *caption);\nCORE_LIBSPEC void quit_video(void);\nCORE_LIBSPEC void destruct_layers(void);\nCORE_LIBSPEC void destruct_extra_layers(uint32_t first);\nCORE_LIBSPEC uint32_t create_layer(int x, int y, unsigned int w, unsigned int h,\n int draw_order, int t_col, int offset, boolean unbound);\nCORE_LIBSPEC void set_layer_offset(uint32_t layer, int offset);\nCORE_LIBSPEC void set_layer_mode(uint32_t layer, int mode);\nCORE_LIBSPEC void move_layer(uint32_t layer, int x, int y);\nCORE_LIBSPEC void select_layer(uint32_t layer);\nCORE_LIBSPEC void blank_layers(void);\nCORE_LIBSPEC boolean has_video_initialized(void);\nCORE_LIBSPEC void update_screen(void);\nCORE_LIBSPEC void set_window_caption(const char *caption);\n\nCORE_LIBSPEC void ec_read_char(uint16_t chr, char matrix[CHAR_SIZE]);\nCORE_LIBSPEC void ec_change_char(uint16_t chr, const char matrix[CHAR_SIZE]);\nCORE_LIBSPEC int ec_load_set_var(const char *filename, uint16_t first_chr, int ver);\nCORE_LIBSPEC void ec_mem_load_set(const void *buffer, size_t len);\nCORE_LIBSPEC void ec_mem_load_set_var(const void *buffer, size_t len,\n uint16_t first_chr, int ver);\nCORE_LIBSPEC void ec_mem_save_set_var(void *buffer, size_t len, uint16_t first_chr);\nCORE_LIBSPEC void ec_clear_set(void);\n\nCORE_LIBSPEC void update_palette(void);\nCORE_LIBSPEC void load_palette(const char *filename);\nCORE_LIBSPEC void load_palette_mem(const void *buffer, size_t len);\nCORE_LIBSPEC void load_index_file(const char *filename);\nCORE_LIBSPEC void smzx_palette_loaded(boolean is_loaded);\nCORE_LIBSPEC void set_screen_mode(unsigned int mode);\nCORE_LIBSPEC unsigned int get_screen_mode(void);\nCORE_LIBSPEC void set_palette_intensity(unsigned int percent);\nCORE_LIBSPEC void set_rgb(uint8_t color, unsigned int r,\n unsigned int g, unsigned int b);\nCORE_LIBSPEC void set_protected_rgb(uint8_t color, unsigned int r,\n unsigned int g, unsigned int b);\nCORE_LIBSPEC void set_red_component(uint8_t color, unsigned int r);\nCORE_LIBSPEC void set_green_component(uint8_t color, unsigned int g);\nCORE_LIBSPEC void set_blue_component(uint8_t color, unsigned int b);\nCORE_LIBSPEC void get_rgb(uint8_t color, uint8_t *r, uint8_t *g, uint8_t *b);\nCORE_LIBSPEC unsigned int get_red_component(uint8_t color);\nCORE_LIBSPEC unsigned int get_green_component(uint8_t color);\nCORE_LIBSPEC unsigned int get_blue_component(uint8_t color);\nCORE_LIBSPEC uint8_t get_smzx_index(uint8_t palette, unsigned int offset);\nCORE_LIBSPEC void set_smzx_index(uint8_t palette, unsigned int offset, uint8_t color);\nCORE_LIBSPEC boolean get_fade_status(void);\nCORE_LIBSPEC void vquick_fadeout(void);\nCORE_LIBSPEC void insta_fadein(void);\nCORE_LIBSPEC void insta_fadeout(void);\nCORE_LIBSPEC void dialog_fadein(void);\nCORE_LIBSPEC void dialog_fadeout(void);\nCORE_LIBSPEC void default_palette(void);\nCORE_LIBSPEC void default_protected_palette(void);\n\nCORE_LIBSPEC void m_hide(void);\nCORE_LIBSPEC void m_show(void);\nCORE_LIBSPEC void mouse_size(unsigned int width, unsigned int height);\n\nconst char *video_get_default_caption(void);\n\nvoid color_string_ext(const char *string, unsigned int x, unsigned int y,\n uint8_t color, boolean allow_newline, unsigned int chr_offset, unsigned int color_offset);\nvoid color_string_ext_special(const char *string, unsigned int x, unsigned int y,\n uint8_t *color, boolean allow_newline, unsigned int chr_offset, unsigned int color_offset);\n\nsize_t color_string_length(const char *string, size_t max_size);\nsize_t color_string_index_of(const char *string, size_t max_size, size_t offset,\n int terminator);\nuint8_t color_string_get_final_color(const char *string, size_t max_size,\n uint8_t initial_color);\n\nboolean change_video_output(struct config_info *conf, const char *output);\nint get_available_video_output_list(const char **buffer, int buffer_len);\nint get_current_video_output(void);\n\nunsigned video_create_window(void);\nCORE_LIBSPEC const struct video_window *video_get_window(unsigned window_id);\nunsigned video_window_by_platform_id(unsigned platform_id);\nvoid video_window_update_viewport(struct video_window *window);\nvoid video_sync_window_size(unsigned window_id,\n unsigned new_width_px, unsigned new_height_px);\nvoid video_resize_window(unsigned window_id,\n unsigned new_width_px, unsigned new_height_px);\nvoid video_resize_fullscreen(unsigned window_id,\n unsigned new_width_px, unsigned new_height_px);\nvoid video_toggle_fullscreen(void);\nunsigned video_get_fullscreen_window(void);\nboolean video_is_fullscreen(void);\nboolean video_set_window_caption(struct video_window *window, const char *caption);\nboolean video_set_window_icon(struct video_window *window, const char *icon_file);\n\nvoid set_screen(struct char_element *src);\nvoid get_screen(struct char_element *dest);\n\nvoid ec_change_byte(uint16_t chr, uint8_t byte, uint8_t new_value);\nuint8_t ec_read_byte(uint16_t chr, uint8_t byte);\nboolean ec_load_set(const char *filename);\n\nvoid set_color_intensity(uint8_t color, unsigned int percent);\nvoid set_color_intensity_mzx(uint8_t color, unsigned int percent);\nunsigned int get_color_intensity(uint8_t color);\nunsigned int get_color_intensity_mzx(uint8_t color);\nvoid save_indices(void *buffer);\nvoid load_indices(const void *buffer, size_t size);\nvoid load_indices_direct(const void *buffer, size_t size);\nvoid set_rgb_mzx(uint8_t color, unsigned int r, unsigned int g, unsigned int b);\nvoid get_rgb_mzx(uint8_t color, uint8_t *r, uint8_t *g, uint8_t *b);\nvoid vquick_fadein(void);\nboolean get_char_visible_bitmask(uint16_t char_idx, uint8_t palette,\n int transparent_color, uint8_t * RESTRICT buffer);\n\nvoid get_screen_coords(int screen_x, int screen_y, int *x, int *y,\n int *min_x, int *min_y, int *max_x, int *max_y);\nvoid set_screen_coords(int x, int y, int *screen_x, int *screen_y);\n\nCORE_LIBSPEC boolean switch_shader(const char *name);\nCORE_LIBSPEC boolean layer_renderer_check(boolean show_error);\n\n/* Renderers might have the facility to center a screen on a given\n * region of the window. Currently only the NDS renderer implements\n * this feature.\n */\nvoid focus_screen(int x, int y); // Board coordinates\nvoid focus_pixel(int x, int y);  // Pixel coordinates\n\n#ifdef CONFIG_EDITOR\nCORE_LIBSPEC extern struct graphics_data graphics;\nCORE_LIBSPEC int get_color_luma(unsigned int color);\nCORE_LIBSPEC int get_char_average_luma(uint16_t chr, uint8_t palette, int mode,\n int mask_chr);\nCORE_LIBSPEC void ec_load_mzx(void);\nCORE_LIBSPEC void ec_load_set_secondary(const char *filename,\n uint8_t dest[CHAR_SIZE * CHARSET_SIZE]);\nCORE_LIBSPEC void draw_char_mixed_pal_ext(uint8_t chr, uint8_t bg_color,\n uint8_t fg_color, unsigned int x, unsigned int y, unsigned int chr_offset);\nCORE_LIBSPEC boolean dump_layer_to_image(const char *filename,\n size_t width_ch, size_t height_ch, const struct char_element *layer,\n boolean (*status_callback)(void *priv, size_t p, size_t m), void *priv);\n#endif // CONFIG_EDITOR\n\n#ifdef CONFIG_ENABLE_SCREENSHOTS\nvoid dump_screen(void);\n#endif\n\n__M_END_DECLS\n\n#endif // __GRAPHICS_H\n"
  },
  {
    "path": "src/hashtable.h",
    "content": "/* The MIT License\n\n   Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>\n   Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n\n   Permission is hereby granted, free of charge, to any person obtaining\n   a copy of this software and associated documentation files (the\n   \"Software\"), to deal in the Software without restriction, including\n   without limitation the rights to use, copy, modify, merge, publish,\n   distribute, sublicense, and/or sell copies of the Software, and to\n   permit persons to whom the Software is furnished to do so, subject to\n   the following conditions:\n\n   The above copyright notice and this permission notice shall be\n   included in all copies or substantial portions of the Software.\n\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n   SOFTWARE.\n*/\n\n/**\n * NOTE: this is a significantly altered khash specifically for MegaZeux.\n *\n * Changes from the original khash:\n * - Tab removal and style update to more closely match MZX style.\n * - The variable scopes have been replaced with C99 \"static inline\".\n * - The custom int types have been replaced by C99 stdint.h types and size_t.\n * - The allocator macros have been replaced with MZX's check alloc functions.\n * - The default key types have been replaced with a single struct-based key\n *   type expected to contain a key field and a key length field. The names of\n *   these fields can be specified. A uint32_t field named \"hash\" is also\n *   expected to exist in this struct for the full hash value to be stored.\n * - The kh_get function has also been altered to optionally take a separate\n *   key pointer and key length parameters instead of a key struct.\n * - Alternate macros have been added to provide a uthash-like interface.\n *\n * All changes to this file for MegaZeux are also licensed under the MIT License.\n * However, certain functions used here (check alloc, memcasecmp) are not.\n *\n * This header is not currently compatible with the standard khash header.\n * See contrib/khash.h for the original example and changelog from this file.\n */\n\n#ifndef __HASHTABLE_H\n#define __HASHTABLE_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stddef.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <limits.h>\n\n#include \"memcasecmp.h\"\n#include \"platform_endian.h\"\n\n#ifndef klib_unused\n#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3)\n#define klib_unused __attribute__ ((__unused__))\n#else\n#define klib_unused\n#endif\n#endif /* klib_unused */\n\n#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)\n#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)\n#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)\n#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))\n#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))\n#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))\n#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))\n\n#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)\n\n#ifndef kroundup32\n#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))\n#endif\n\nstatic const double __ac_HASH_UPPER = 0.77;\n\n/**\n * Keep table sizes in the uint32_t range since the hashes are uint32_t.\n * In practice, tables over this size will probably never be used, since the\n * counters and strings lists are bounded to 2^31.\n */\n#if ARCHITECTURE_BITS >= 64\nstatic const uint64_t __ac_HASH_MAXIMUM = ((uint64_t)UINT32_MAX) + 1;\n#else\nstatic const size_t __ac_HASH_MAXIMUM = ((size_t)INT32_MAX) + 1;\n#endif\n\n#define __KHASH_TYPE(name, khkey_t, khval_t) \\\n  typedef struct kh_##name##_s \\\n  { \\\n    size_t n_buckets;   \\\n    size_t size;        \\\n    size_t n_occupied;  \\\n    size_t upper_bound; \\\n    uint32_t *flags;    \\\n    khkey_t *keys;      \\\n    khval_t *vals;      \\\n  } kh_##name##_t;\n\n#define __KHASH_IMPL(name, khkey_t, khval_t, kh_is_map, ptr_field, len_field, \\\n __hash_func, __hash_equal) \\\n  \\\n  static inline klib_unused kh_##name##_t *kh_init_##name(void) \\\n  { \\\n    return (kh_##name##_t*)ccalloc(1, sizeof(kh_##name##_t));             \\\n  } \\\n  static inline klib_unused void kh_destroy_##name(kh_##name##_t *h) \\\n  { \\\n    if(h)                                                                 \\\n    {                                                                     \\\n      free(h->flags);                                                     \\\n      free((void *)h->keys);                                              \\\n      free((void *)h->vals);                                              \\\n      free(h);                                                            \\\n    }                                                                     \\\n  } \\\n  \\\n  static inline klib_unused void kh_clear_##name(kh_##name##_t *h) \\\n  { \\\n    if(h && h->flags)                                                     \\\n    {                                                                     \\\n      size_t flags_size = __ac_fsize(h->n_buckets);                       \\\n      memset(h->flags, 0xaa, flags_size * sizeof(uint32_t));              \\\n      h->size = h->n_occupied = 0;                                        \\\n    }                                                                     \\\n  } \\\n  \\\n  static inline klib_unused size_t kh_get_no_obj_##name(const kh_##name##_t *h, \\\n   const void *key_ptr, uint32_t key_len, uint32_t hash, int has_hash) \\\n  { \\\n    if(h->n_buckets)                                                      \\\n    {                                                                     \\\n      size_t i, last, mask, step = 0;                                     \\\n      mask = h->n_buckets - 1;                                            \\\n      if(!has_hash)                                                       \\\n        hash = __hash_func(key_ptr, key_len);                             \\\n      i = hash & mask;                                                    \\\n      last = i;                                                           \\\n      while(!__ac_isempty(h->flags, i) &&                                 \\\n       (__ac_isdel(h->flags, i) ||                                        \\\n        hash != h->keys[i]->hash || \\\n        !__hash_equal(h->keys[i]->ptr_field, key_ptr,                     \\\n         h->keys[i]->len_field, key_len)))                                \\\n      {                                                                   \\\n        i = (i + (++step)) & mask;                                        \\\n        if(i == last)                                                     \\\n          return h->n_buckets;                                            \\\n      }                                                                   \\\n      if(!__ac_iseither(h->flags, i))                                     \\\n        return i;                                                         \\\n    }                                                                     \\\n    return h->n_buckets;                                                  \\\n  } \\\n  \\\n  static inline klib_unused size_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \\\n  { \\\n    return kh_get_no_obj_##name(h, key->ptr_field, key->len_field,        \\\n     key->hash, 1);                                                       \\\n  } \\\n  \\\n  static inline klib_unused int kh_resize_##name(kh_##name##_t *h, size_t new_n_buckets) \\\n  { \\\n    /* This function uses 0.25*n_buckets bytes of working space instead of \\\n     * [sizeof(key_t+val_t)+.25]*n_buckets. \\\n     */ \\\n    uint32_t *new_flags = 0;                                              \\\n    size_t j = 1;                                                         \\\n    size_t new_upper_bound;                                               \\\n  \\\n    kroundup32(new_n_buckets);                                            \\\n    if(new_n_buckets > __ac_HASH_MAXIMUM)                                 \\\n      return -1;                                                          \\\n    if(new_n_buckets < 4)                                                 \\\n      new_n_buckets = 4;                                                  \\\n  \\\n    new_upper_bound = (size_t)(new_n_buckets * __ac_HASH_UPPER + 0.5);    \\\n    if(h->size >= new_upper_bound)                                        \\\n    {                                                                     \\\n      j = 0;  /* requested size is too small */                           \\\n    }                                                                     \\\n    else                                                                  \\\n    {                                                                     \\\n      /* hash table size to be changed (shrink or expand); rehash */      \\\n      size_t flags_size = __ac_fsize(new_n_buckets) * sizeof(uint32_t);   \\\n      new_flags = (uint32_t *)cmalloc(flags_size);                        \\\n      if(!new_flags)                                                      \\\n        return -1;                                                        \\\n      memset(new_flags, 0xaa, flags_size);                                \\\n      if(h->n_buckets < new_n_buckets)                                    \\\n      {                                                                   \\\n        /* expand */                                                      \\\n        size_t size = new_n_buckets * sizeof(khkey_t);                    \\\n        khkey_t *new_keys = (khkey_t *)crealloc((void *)h->keys, size);   \\\n        if(!new_keys)                                                     \\\n        {                                                                 \\\n          free(new_flags);                                                \\\n          return -1;                                                      \\\n        }                                                                 \\\n        h->keys = new_keys;                                               \\\n        if(kh_is_map)                                                     \\\n        {                                                                 \\\n          size_t size = new_n_buckets * sizeof(khval_t);                  \\\n          khval_t *new_vals = (khval_t *)crealloc((void *)h->vals, size); \\\n          if(!new_vals)                                                   \\\n          {                                                               \\\n            free(new_flags);                                              \\\n            return -1;                                                    \\\n          }                                                               \\\n          h->vals = new_vals;                                             \\\n        }                                                                 \\\n      } /* otherwise shrink */                                            \\\n    } \\\n  \\\n    if(j) \\\n    { \\\n      /* rehashing is needed */                                           \\\n      size_t new_mask = new_n_buckets - 1;                                \\\n      for(j = 0; j != h->n_buckets; ++j)                                  \\\n      {                                                                   \\\n        if(__ac_iseither(h->flags, j) == 0)                               \\\n        {                                                                 \\\n          khkey_t key = h->keys[j];                                       \\\n          khval_t val;                                                    \\\n          h->keys[j] = 0;                                                 \\\n          if(kh_is_map)                                                   \\\n          {                                                               \\\n            val = h->vals[j];                                             \\\n            h->vals[j] = 0;                                               \\\n          }                                                               \\\n          __ac_set_isdel_true(h->flags, j);                               \\\n          while(1)                                                        \\\n          {                                                               \\\n            /* kick-out process; sort of like in Cuckoo hashing */        \\\n            size_t k = key->hash;                                         \\\n            size_t i, step = 0;                                           \\\n            i = k & new_mask;                                             \\\n            while(!__ac_isempty(new_flags, i))                            \\\n              i = (i + (++step)) & new_mask;                              \\\n            __ac_set_isempty_false(new_flags, i);                         \\\n            if(i < h->n_buckets && __ac_iseither(h->flags, i) == 0)       \\\n            {                                                             \\\n              /* kick out the existing element */                         \\\n              { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; }  \\\n              if(kh_is_map)                                               \\\n              { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; }  \\\n              /* mark it as deleted in the old hash table */              \\\n              __ac_set_isdel_true(h->flags, i);                           \\\n            }                                                             \\\n            else                                                          \\\n            {                                                             \\\n              /* write the element and jump out of the loop */            \\\n              h->keys[i] = key;                                           \\\n              if(kh_is_map)                                               \\\n                h->vals[i] = val;                                         \\\n              break;                                                      \\\n            }                                                             \\\n          }                                                               \\\n        }                                                                 \\\n      }                                                                   \\\n      if(h->n_buckets > new_n_buckets)                                    \\\n      {                                                                   \\\n        /* shrink the hash table */                                       \\\n        size_t size = new_n_buckets * sizeof(khkey_t);                    \\\n        h->keys = (khkey_t*)crealloc((void *)h->keys, size);              \\\n        if(kh_is_map)                                                     \\\n        {                                                                 \\\n          size = new_n_buckets * sizeof(khval_t);                         \\\n          h->vals = (khval_t*)crealloc((void *)h->vals, size);            \\\n        }                                                                 \\\n      }                                                                   \\\n      free(h->flags); /* free the working space */                        \\\n      h->flags = new_flags;                                               \\\n      h->n_buckets = new_n_buckets;                                       \\\n      h->n_occupied = h->size;                                            \\\n      h->upper_bound = new_upper_bound;                                   \\\n    }                                                                     \\\n    return 0;                                                             \\\n  } \\\n  \\\n  static inline klib_unused size_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \\\n  { \\\n    size_t x;                                                             \\\n    if(h->n_occupied >= h->upper_bound)                                   \\\n    {                                                                     \\\n      /* update the hash table */                                         \\\n      if(h->n_buckets > (h->size << 1))                                   \\\n      {                                                                   \\\n        /* clear \"deleted\" elements */                                    \\\n        if(kh_resize_##name(h, h->n_buckets - 1) < 0)                     \\\n        {                                                                 \\\n          *ret = -1;                                                      \\\n          return h->n_buckets;                                            \\\n        }                                                                 \\\n      }                                                                   \\\n      else                                                                \\\n  \\\n      if(kh_resize_##name(h, h->n_buckets + 1) < 0)                       \\\n      {                                                                   \\\n        /* expand the hash table */                                       \\\n        *ret = -1;                                                        \\\n        return h->n_buckets;                                              \\\n      }                                                                   \\\n    }                                                                     \\\n    /* TODO: to implement automatically shrinking;                        \\\n     * resize() already support shrinking */                              \\\n    {                                                                     \\\n      size_t i, site, last, mask = h->n_buckets - 1, step = 0;            \\\n      uint32_t hash = __hash_func(key->ptr_field, key->len_field);        \\\n      key->hash = hash;                                                   \\\n      x = site = h->n_buckets;                                            \\\n      i = hash & mask;                                                    \\\n      if(__ac_isempty(h->flags, i))                                       \\\n      {                                                                   \\\n        /* for speed up */                                                \\\n        x = i;                                                            \\\n      }                                                                   \\\n      else                                                                \\\n      {                                                                   \\\n        last = i;                                                         \\\n        while(!__ac_isempty(h->flags, i) &&                               \\\n         (__ac_isdel(h->flags, i) ||                                      \\\n          hash != h->keys[i]->hash || \\\n          !__hash_equal(h->keys[i]->ptr_field, key->ptr_field,            \\\n           h->keys[i]->len_field, key->len_field)))                       \\\n        {                                                                 \\\n          if(__ac_isdel(h->flags, i))                                     \\\n            site = i;                                                     \\\n          i = (i + (++step)) & mask;                                      \\\n          if(i == last)                                                   \\\n          {                                                               \\\n            x = site;                                                     \\\n            break;                                                        \\\n          }                                                               \\\n        }                                                                 \\\n        if(x == h->n_buckets)                                             \\\n        {                                                                 \\\n          if(__ac_isempty(h->flags, i) && site != h->n_buckets)           \\\n            x = site;                                                     \\\n          else                                                            \\\n            x = i;                                                        \\\n        }                                                                 \\\n      }                                                                   \\\n    }                                                                     \\\n    if(__ac_isempty(h->flags, x))                                         \\\n    {                                                                     \\\n      /* not present at all */                                            \\\n      h->keys[x] = key;                                                   \\\n      __ac_set_isboth_false(h->flags, x);                                 \\\n      ++h->size;                                                          \\\n      ++h->n_occupied;                                                    \\\n      *ret = 1;                                                           \\\n    }                                                                     \\\n    else                                                                  \\\n  \\\n    if(__ac_isdel(h->flags, x))                                           \\\n    {                                                                     \\\n      /* deleted */                                                       \\\n      h->keys[x] = key;                                                   \\\n      __ac_set_isboth_false(h->flags, x);                                 \\\n      ++h->size;                                                          \\\n      *ret = 2;                                                           \\\n    }                                                                     \\\n    /* Don't touch h->keys[x] if present and not deleted */               \\\n    else                                                                  \\\n      *ret = 0;                                                           \\\n    return x;                                                             \\\n  }                                                                       \\\n  \\\n  static inline klib_unused void kh_del_##name(kh_##name##_t *h, size_t x) \\\n  { \\\n    if(x != h->n_buckets && !__ac_iseither(h->flags, x))                  \\\n    {                                                                     \\\n      __ac_set_isdel_true(h->flags, x);                                   \\\n      --h->size;                                                          \\\n      h->keys[x] = 0;                                                     \\\n      if(kh_is_map)                                                       \\\n        h->vals[x] = 0;                                                   \\\n    }                                                                     \\\n  }\n\n#define KHASH_INIT2(name, khkey_t, khval_t, kh_is_map,    \\\n ptr_field, len_field, __hash_func, __hash_equal)         \\\n  __KHASH_TYPE(name, khkey_t, khval_t)                    \\\n  __KHASH_IMPL(name, khkey_t, khval_t, kh_is_map,         \\\n   ptr_field, len_field, __hash_func, __hash_equal)\n\n#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map,     \\\n ptr_field, len_field, __hash_func, __hash_equal)         \\\n  KHASH_INIT2(name, khkey_t, khval_t, kh_is_map,          \\\n   ptr_field, len_field, __hash_func, __hash_equal)\n\n/* Other convenient macros... */\n\n/*!\n  @abstract Type of the hash table.\n  @param  name  Name of the hash table [symbol]\n */\n#define khash_t(name) kh_##name##_t\n\n/*! @function\n  @abstract     Initiate a hash table.\n  @param  name  Name of the hash table [symbol]\n  @return       Pointer to the hash table [khash_t(name)*]\n */\n#define kh_init(name) kh_init_##name()\n\n/*! @function\n  @abstract     Destroy a hash table.\n  @param  name  Name of the hash table [symbol]\n  @param  h     Pointer to the hash table [khash_t(name)*]\n */\n#define kh_destroy(name, h) kh_destroy_##name(h)\n\n/*! @function\n  @abstract     Reset a hash table without deallocating memory.\n  @param  name  Name of the hash table [symbol]\n  @param  h     Pointer to the hash table [khash_t(name)*]\n */\n#define kh_clear(name, h) kh_clear_##name(h)\n\n/*! @function\n  @abstract     Resize a hash table.\n  @param  name  Name of the hash table [symbol]\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  s     New size [size_t]\n */\n#define kh_resize(name, h, s) kh_resize_##name(h, s)\n\n/*! @function\n  @abstract     Insert a key to the hash table.\n  @param  name  Name of the hash table [symbol]\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  k     Key [type of keys]\n  @param  r     Extra return code: -1 if the operation failed;\n                0 if the key is present in the hash table;\n                1 if the bucket is empty (never used); 2 if the element in\n        the bucket has been deleted [int*]\n  @return       Iterator to the inserted element [size_t]\n */\n#define kh_put(name, h, k, r) kh_put_##name(h, k, r)\n\n/*! @function\n  @abstract     Retrieve a key from the hash table.\n  @param  name  Name of the hash table [symbol]\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  k     Key [type of keys]\n  @return       Iterator to the found element, or kh_end(h) if the element is\n                absent [size_t]\n */\n#define kh_get(name, h, k) kh_get_##name(h, k)\n#define kh_get_no_obj(name, h, kp, kl) kh_get_no_obj_##name(h, kp, kl, 0, 0)\n\n/*! @function\n  @abstract     Remove a key from the hash table.\n  @param  name  Name of the hash table [symbol]\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  k     Iterator to the element to be deleted [size_t]\n */\n#define kh_del(name, h, k) kh_del_##name(h, k)\n\n/*! @function\n  @abstract     Test whether a bucket contains data.\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  x     Iterator to the bucket [size_t]\n  @return       1 if containing data; 0 otherwise [int]\n */\n#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))\n\n/*! @function\n  @abstract     Get key given an iterator\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  x     Iterator to the bucket [size_t]\n  @return       Key [type of keys]\n */\n#define kh_key(h, x) ((h)->keys[x])\n\n/*! @function\n  @abstract     Get value given an iterator\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  x     Iterator to the bucket [size_t]\n  @return       Value [type of values]\n  @discussion   For hash sets, calling this results in segfault.\n */\n#define kh_val(h, x) ((h)->vals[x])\n\n/*! @function\n  @abstract     Alias of kh_val()\n */\n#define kh_value(h, x) ((h)->vals[x])\n\n/*! @function\n  @abstract     Get the start iterator\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @return       The start iterator [size_t]\n */\n#define kh_begin(h) (size_t)(0)\n\n/*! @function\n  @abstract     Get the end iterator\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @return       The end iterator [size_t]\n */\n#define kh_end(h) ((h)->n_buckets)\n\n/*! @function\n  @abstract     Get the number of elements in the hash table\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @return       Number of elements in the hash table [size_t]\n */\n#define kh_size(h) ((h)->size)\n\n/*! @function\n  @abstract     Get the number of buckets in the hash table\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @return       Number of buckets in the hash table [size_t]\n */\n#define kh_n_buckets(h) ((h)->n_buckets)\n\n/*! @function\n  @abstract     Iterate over the entries in the hash table\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  kvar  Variable to which key will be assigned\n  @param  vvar  Variable to which value will be assigned\n  @param  code  Block of code to execute\n */\n#define kh_foreach(h, kvar, vvar, code) { size_t __i;    \\\n  for (__i = kh_begin(h); __i != kh_end(h); ++__i) {    \\\n    if (!kh_exist(h,__i)) continue;            \\\n    (kvar) = kh_key(h,__i);                \\\n    (vvar) = kh_val(h,__i);                \\\n    code;                        \\\n  } }\n\n/*! @function\n  @abstract     Iterate over the values in the hash table\n  @param  h     Pointer to the hash table [khash_t(name)*]\n  @param  vvar  Variable to which value will be assigned\n  @param  code  Block of code to execute\n */\n#define kh_foreach_value(h, vvar, code) { size_t __i;    \\\n  for (__i = kh_begin(h); __i != kh_end(h); ++__i) {    \\\n    if (!kh_exist(h,__i)) continue;            \\\n    (vvar) = kh_val(h,__i);                \\\n    code;                        \\\n  } }\n\n/* --- BEGIN OF HASH FUNCTIONS --- */\n\n// TODO make case sensitive versions?\n\n/**\n * This hash function results in far better distribution than the default X31\n * function and isn't significantly more expensive.\n *\n * http://isthe.com/chongo/tech/comp/fnv/#FNV-1a\n */\nstatic inline uint32_t fnv_1a_hash_string_len(const void *_str, uint32_t len)\n{\n  const char *str = _str;\n  uint32_t h = 0;\n  for(; len; len--)\n    h = (h ^ (uint32_t)memtolower((int)*(str++))) * 16777619;\n  return h;\n}\n\n#define kh_mem_hash_func(keyptr, keylen) \\\n  fnv_1a_hash_string_len(keyptr, keylen)\n\n#define kh_mem_hash_equal(aptr, bptr, alen, blen) \\\n  (((uint32_t)alen == (uint32_t)blen) && !memcasecmp32(aptr, bptr, blen))\n\n/* --- END OF HASH FUNCTIONS --- */\n\n#define HASH_SET_INIT(name, khkey_t, key_ptr_field, key_len_field) \\\n  KHASH_INIT(name, khkey_t, char, 0, \\\n   key_ptr_field, key_len_field, kh_mem_hash_func, kh_mem_hash_equal)\n\n#define HASH_MAP_INIT(name, khkey_t, khval_t, key_ptr_field, key_len_field) \\\n  KHASH_INIT(name, khkey_t, khval_t, 1, \\\n   key_ptr_field, key_len_field, kh_mem_hash_func, kh_mem_hash_equal)\n\n// khash_t(n) abstraction for the unlikely event this library gets replaced.\n#define hash_t(n) khash_t(n)\n\n// uthash-like macros.\n\n/**\n * Add an object to the hash table.\n * If the hash table hasn't been initialized, this function will initialize it.\n *\n * @param name    The unique identifier of the hash table type (e.g. COUNTER).\n * @param h       Variable containing hash table pointer. If this variable is\n *                set to null, the hash table is considered uninitialized.\n * @param keyobj  A new key struct to add to the table. Its key and key length\n *                fields must be initialized. Its hash field will be\n *                initialized during this operation.\n */\n#define HASH_ADD(n, h, keyobj) do                                 \\\n{                                                                 \\\n  int _res;                                                       \\\n  if(!h) h = kh_init(n);                                          \\\n  kh_put(n, (khash_t(n) *)h, keyobj, &_res);                      \\\n} while(0)\n\n/**\n * Find an object in the hash table.\n * If the hash table hasn't been initialized, this function will do nothing.\n *\n * @param name    The unique identifier of the hash table type (e.g. COUNTER).\n * @param h       Variable containing the hash table pointer.\n * @param keyptr  Pointer to the key variable (usually a string).\n * @param keylen  Length of the key variable in bytes.\n * @param destobj Pointer variable that will be set to the object if found\n *                or NULL if not found.\n */\n#define HASH_FIND(n, _h, keyptr, keylen, destobj) do              \\\n{                                                                 \\\n  destobj = NULL;                                                 \\\n  if(_h)                                                          \\\n  {                                                               \\\n    khash_t(n) *h = _h;                                           \\\n    size_t iter = kh_get_no_obj(n, h, keyptr, (uint32_t)keylen);  \\\n                                                                  \\\n    if(iter < kh_end(h)) destobj = h->keys[iter];                 \\\n  }                                                               \\\n} while(0)\n\n/**\n * Delete an object from the hash table.\n * If the hash table hasn't been initialized, this function will do nothing.\n *\n * @param name    The unique identifier of the hash table type (e.g. COUNTER).\n * @param h       Variable containing the hash table pointer.\n * @param keyobj  Object to remove from the hash table. Its key, key length,\n *                and hash fields should be initialized (which will be the\n *                case if this object was passed to HASH_ADD and/or was\n *                returned from HASH_FIND).\n */\n#define HASH_DELETE(n, _h, keyobj) do                             \\\n{                                                                 \\\n  if(_h)                                                          \\\n  {                                                               \\\n    khash_t(n) *h = _h;                                           \\\n    size_t iter = kh_get(n, h, keyobj);                           \\\n                                                                  \\\n    if(iter < kh_end(h)) kh_del(n, h, iter);                      \\\n  }                                                               \\\n} while(0)\n\n/**\n * Destroy the hash table.\n * If the hash table hasn't been initialized, this function will do nothing.\n *\n * @param name    The unique identifier of the hash table type (e.g. COUNTER)\n * @param h       Variable containing the hash table pointer. This function\n *                will set it to NULL after destroying the hash table.\n */\n#define HASH_CLEAR(n, _h) do                                      \\\n{                                                                 \\\n  if(_h)                                                          \\\n  {                                                               \\\n    khash_t(n) *h = _h;                                           \\\n    kh_destroy(n, h);                                             \\\n    _h = NULL;                                                    \\\n  }                                                               \\\n} while(0)\n\n/**\n * Iterate the hash table.\n * If the hash table hasn't been initialized, this function will do nothing.\n *\n * @param name    The unique identifier of the hash table type (e.g. COUNTER)\n * @param h       Variable containing the hash table pointer.\n * @param element The variable the current element will be stored to.\n * @param code    A block of code to execute for every element.\n */\n#define HASH_ITER(n, _h, element, code) do                        \\\n{                                                                 \\\n  khash_t(n) *__h = _h;                                           \\\n  size_t __i;                                                     \\\n  if(_h) for(__i = kh_begin(__h); __i != kh_end(__h); __i++)      \\\n  {                                                               \\\n    if(!kh_exist(__h, __i)) continue;                             \\\n    (element) = kh_key(__h, __i);                                 \\\n    code;                                                         \\\n  }                                                               \\\n} while(0)\n\n/**\n * Get the total memory usage of a hash table.\n *\n * @param name    The unique identifier of the hash table type (e.g. COUNTER)\n * @param h       Variable containing the hash table pointer.\n * @param size    Variable to store total size (should be size_t).\n */\n#define HASH_MEMORY_USAGE(n, _h, size) do                         \\\n{                                                                 \\\n  khash_t(n) *__h = _h;                                           \\\n  if(__h && __h->keys)                                            \\\n  {                                                               \\\n    (size) = sizeof(*__h);                                        \\\n    (size) += __h->n_buckets * sizeof(__h->keys[0]);              \\\n    (size) += __ac_fsize(__h->n_buckets);                         \\\n    if(__h->vals)                                                 \\\n      (size) += __h->n_buckets * sizeof(__h->vals[0]);            \\\n  }                                                               \\\n  else                                                            \\\n    (size) = 0;                                                   \\\n} while(0)\n\n__M_END_DECLS\n\n#endif /* __HASHTABLE_H */\n"
  },
  {
    "path": "src/helpsys.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// internal help system\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"core.h\"\n#include \"data.h\"\n#include \"graphics.h\"\n#include \"helpsys.h\"\n#include \"scrdisp.h\"\n#include \"util.h\"\n#include \"window.h\"\n#include \"world.h\"\n#include \"io/vio.h\"\n\nstatic char *help;\n\nvoid help_open(struct world *mzx_world, const char *file_name)\n{\n  if(file_name)\n  {\n    mzx_world->help_file = vfopen_unsafe(file_name, \"rb\");\n    if(!mzx_world->help_file)\n      return;\n\n    help = cmalloc(1024 * 64);\n  }\n}\n\nvoid help_close(struct world *mzx_world)\n{\n  if(mzx_world->help_file)\n  {\n    vfclose(mzx_world->help_file);\n    mzx_world->help_file = NULL;\n  }\n\n  free(help);\n  help = NULL;\n}\n\n/**\n * This really ought to only take the context, but this function is still used\n * outside of core.c in the following places:\n *\n * src/editor/char_ed.c\n * src/editor/robo_ed.c\n * src/window.c (dialogs)\n */\nvoid help_system(context *ctx, struct world *mzx_world)\n{\n  char file[13], file2[13], label[13];\n  int where, offs, size, t1, t2;\n  vfile *vf;\n\n  vf = mzx_world->help_file;\n  if(!vf)\n    return;\n\n  vrewind(vf);\n  t1 = vfgetw(vf);\n  vfseek(vf, t1 * 21 + 4 + get_context(ctx) * 12, SEEK_SET);\n\n  // At proper context info\n  where = vfgetd(vf);    // Where file to load is\n  size = vfgetd(vf);     // Size of file to load\n  offs = vfgetd(vf);     // Offset within file of link\n\n  // Jump to file\n  vfseek(vf, where, SEEK_SET);\n  // Read it in\n  size = vfread(help, 1, size, vf);\n  // Display it\n  cursor_off();\n\nlabelled:\n  help_display(mzx_world, help, offs, file, label);\n\n  // File?\n  if(file[0])\n  {\n    // Yep. Search for file.\n    vfseek(vf, 2, SEEK_SET);\n    for(t2 = 0; t2 < t1; t2++)\n    {\n      if(!vfread(file2, 13, 1, vf))\n        return;\n      if(!strcmp(file, file2))\n        break;\n      vfseek(vf, 8, SEEK_CUR);\n    }\n\n    if(t2 < t1)\n    {\n      // Found file.\n      where = vfgetd(vf);\n      size = vfgetd(vf);\n      vfseek(vf, where, SEEK_SET);\n      size = vfread(help, 1, size, vf);\n\n      // Search for label\n      for(t2 = 0; t2 < size; t2++)\n      {\n        if(help[t2] != 255)\n          continue;\n        if(help[t2 + 1] != ':')\n          continue;\n        if(!strcmp(help + t2 + 3, label))\n          break; // Found label!\n      }\n\n      if(t2 < size)\n      {\n        // Found label. t2 is offset.\n        offs = t2;\n        goto labelled;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/helpsys.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __HELPSYS_H\n#define __HELPSYS_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#ifdef CONFIG_HELPSYS\n\n#include \"core.h\"\n#include \"world_struct.h\"\n\nCORE_LIBSPEC void help_open(struct world *mzx_world, const char *file_name);\nCORE_LIBSPEC void help_close(struct world *mzx_world);\nCORE_LIBSPEC void help_system(context *ctx, struct world *mzx_world);\n\n#endif\n\n__M_END_DECLS\n\n#endif // __HELPSYS_H\n"
  },
  {
    "path": "src/idarray.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"idarray.h\"\n#include \"data.h\"\n#include \"const.h\"\n#include \"util.h\"\n#include \"world_struct.h\"\n\n// Place an id/color/param combo in an array position, moving current to\n// \"under\" status if possible, and clearing original \"under\". If placing an\n// \"under\", then automatically clear lower no matter what. Also mark as\n// updated.\n\nvoid id_place(struct world *mzx_world, int array_x, int array_y,\n enum thing id, char color, char param)\n{\n  struct board *src_board = mzx_world->current_board;\n  int offset;\n\n  array_x = CLAMP(array_x, 0, src_board->board_width - 1);\n  array_y = CLAMP(array_y, 0, src_board->board_height - 1);\n\n  offset = (array_y * src_board->board_width) + array_x;\n  offs_place_id(mzx_world, offset, id, color, param);\n}\n\n// Place ID using an offset instead of coordinate\nvoid offs_place_id(struct world *mzx_world, unsigned int offset,\n enum thing id, char color, char param)\n{\n  struct board *src_board = mzx_world->current_board;\n  enum thing p_id = (enum thing)src_board->level_id[offset];\n  int p_flag = flags[p_id];\n  int d_flag = flags[(int)id];\n\n  // Mark as updated\n  mzx_world->update_done[offset] = 1;\n\n  // Is it a sensor and is the player being put on it?\n  // Or, can it be moved under and can the new item not be moved under?\n  if(((p_flag & A_SPEC_STOOD) && (id == PLAYER)) ||\n   ((p_flag & A_UNDER) && !(d_flag & A_UNDER)))\n  {\n    // If moving the player down, move what's there underneath the player\n    // into the under_player variables\n    if(id == PLAYER)\n    {\n      mzx_world->under_player_id = src_board->level_under_id[offset];\n      mzx_world->under_player_param = src_board->level_under_param[offset];\n      mzx_world->under_player_color = src_board->level_under_color[offset];\n    }\n\n    // Then move what's currently on top under\n    src_board->level_under_id[offset] = p_id;\n    src_board->level_under_param[offset] = src_board->level_param[offset];\n    src_board->level_under_color[offset] = src_board->level_color[offset];\n  }\n  else\n  {\n    // Clear anything that might be underneath\n    src_board->level_under_id[offset] = 0;\n    src_board->level_under_param[offset] = 0;\n    src_board->level_under_color[offset] = 7;\n  }\n\n  // Put new combo\n  src_board->level_id[offset] = (char)id;\n  src_board->level_param[offset] = param;\n  src_board->level_color[offset] = color;\n}\n\n// Remove the top thing at a position\nvoid id_remove_top(struct world *mzx_world, int array_x, int array_y)\n{\n  struct board *current_board = mzx_world->current_board;\n  int offset = (array_y * current_board->board_width) + array_x;\n\n  offs_remove_id(mzx_world, offset);\n}\n\n// Remove the top thing at an offset\nvoid offs_remove_id(struct world *mzx_world, unsigned int offset)\n{\n  struct board *src_board = mzx_world->current_board;\n  enum thing id = (enum thing)src_board->level_id[offset];\n\n  src_board->level_id[offset] = src_board->level_under_id[offset];\n  src_board->level_param[offset] = src_board->level_under_param[offset];\n  src_board->level_color[offset] = src_board->level_under_color[offset];\n\n  if(id == PLAYER)\n  {\n    // Removing the player? Then put the under_player stuff under\n    src_board->level_under_id[offset] = mzx_world->under_player_id;\n    src_board->level_under_param[offset] = mzx_world->under_player_param;\n    src_board->level_under_color[offset] = mzx_world->under_player_color;\n\n    mzx_world->under_player_id = (char)SPACE;\n    mzx_world->under_player_param = 0;\n    mzx_world->under_player_color = 7;\n  }\n  else\n  {\n    src_board->level_under_id[offset] = (char)SPACE;\n    src_board->level_under_param[offset] = 0;\n    src_board->level_under_color[offset] = 7;\n  }\n}\n\nvoid id_remove_under(struct world *mzx_world, int array_x, int array_y)\n{\n  struct board *src_board = mzx_world->current_board;\n  int offset = (array_y * src_board->board_width) + array_x;\n\n  // If removing something under the player place this stuff instead\n  if((enum thing)src_board->level_id[offset] == PLAYER)\n  {\n    src_board->level_under_id[offset] = mzx_world->under_player_id;\n    src_board->level_under_param[offset] = mzx_world->under_player_param;\n    src_board->level_under_color[offset] = mzx_world->under_player_color;\n    mzx_world->under_player_id = (char)SPACE;\n    mzx_world->under_player_param = 0;\n    mzx_world->under_player_color = 0;\n  }\n  else\n  {\n    src_board->level_under_id[offset] = (char)SPACE;\n    src_board->level_under_param[offset] = 0;\n    src_board->level_under_color[offset] = 7;\n  }\n\n  mzx_world->update_done[offset] = 1;\n}\n"
  },
  {
    "path": "src/idarray.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declarations for IDARRAY.ASM */\n\n#ifndef __IDARRAY_H\n#define __IDARRAY_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"data.h\"\n#include \"world_struct.h\"\n\nCORE_LIBSPEC void id_remove_top(struct world *mzx_world,\n int array_x, int array_y);\n\nvoid id_place(struct world *mzx_world, int array_x, int array_y,\n enum thing id, char color, char param);\nvoid offs_place_id(struct world *mzx_world, unsigned int offset,\n enum thing id, char color, char param);\nvoid offs_remove_id(struct world *mzx_world, unsigned int offset);\nvoid id_remove_under(struct world *mzx_world, int array_x, int array_y);\n\n__M_END_DECLS\n\n#endif // __IDARRAY_H\n"
  },
  {
    "path": "src/idput.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1998 MenTaLguY - mentalg@geocities.com\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n\n#include \"board.h\"\n#include \"const.h\"\n#include \"data.h\"\n#include \"graphics.h\"\n#include \"idput.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n\n#define thin_line            128\n#define thick_line           144\n#define ice_anim             160\n#define lava_anim            164\n#define low_ammo             167\n#define hi_ammo              168\n#define lit_bomb             169\n#define energizer_glow       176\n#define explosion_colors     184\n#define horiz_door           188\n#define vert_door            189\n#define cw_anim              190\n#define ccw_anim             194\n#define open_door            198\n#define trans_north          230\n#define trans_south          234\n#define trans_east           238\n#define trans_west           242\n#define trans_all            246\n#define thick_arrow          250\n#define thin_arrow           254\n#define horiz_lazer          258\n#define vert_lazer           262\n#define fire_anim            266\n#define fire_colors          272\n#define life_anim            278\n#define life_colors          282\n#define ricochet_panels      286\n#define mine_anim            288\n#define shooting_fire_anim   290\n#define shooting_fire_colors 292\n#define seeker_anim          294\n#define seeker_colors        298\n#define whirlpool_glow       302\n\nunsigned char id_chars[ID_CHARS_SIZE];\nunsigned char id_dmg[ID_DMG_SIZE];\nunsigned char bullet_color[ID_BULLET_COLOR_SIZE] = { 15, 15, 15 };\nunsigned char missile_color = 8;\n\n/* Simulate DOS-era reads of id_chars, which was a single 455 byte array.\n * This is only accurate for out-of-bounds reads when drawing unusual thing\n * parameters until 2.51s3.\n *\n * 0-127:   ID chars (0-127)\n * 128-322: ID animations (except bullet colors and missile color)\n * 323:     missile color (until 2.51s3, unused after)\n * 324-326: bullet colors (until 2.51s3.1, unused after)\n * 327-454: ID damages 0-127 (until 2.51s3.1, unused after)\n *\n * 455-909: out of bounds default ID chars (until 2.51s3).\n * 455-:    out of bounds misc pointers and various other things (2.51s3+)\n */\nstatic unsigned char get_id_char_by_legacy_index(unsigned index)\n{\n  if(index < ID_CHARS_SIZE)\n    return id_chars[index];\n\n  if(index == ID_MISSILE_COLOR_POS)\n    return missile_color;\n\n  if(index < ID_DMG_POS)\n    return bullet_color[index - ID_BULLET_COLOR_POS];\n\n  if(index < ID_CHARS_TOTAL_SIZE)\n    return id_dmg[index - ID_DMG_POS];\n\n  return 255;\n}\n\nvoid set_id_char_by_legacy_index(unsigned index, unsigned char value)\n{\n  if(index < ID_CHARS_SIZE)\n    id_chars[index] = value;\n  else\n\n  if(index == ID_MISSILE_COLOR_POS)\n    missile_color = value;\n  else\n\n  if(index < ID_DMG_POS)\n    bullet_color[index - ID_BULLET_COLOR_POS] = value;\n  else\n\n  if(index < ID_CHARS_TOTAL_SIZE)\n    id_dmg[index - ID_DMG_POS] = value;\n}\n\nstatic unsigned char get_special_id_char(struct board *src_board,\n enum thing cell_id, char param, int offset)\n{\n  char *level_id = src_board->level_id;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n\n  switch(cell_id)\n  {\n    case LINE:\n    {\n      int array_x = offset % board_width;\n      int array_y = offset / board_width;\n\n      int bits = 0;\n      char *ptr = level_id + offset;\n\n      if(array_y > 0)\n      {\n        if(*(ptr - board_width) == LINE)\n          bits = 1;\n      }\n      else\n        bits = 1;\n\n      if(array_y < (board_height - 1))\n      {\n        if(*(ptr + board_width) == LINE)\n          bits |= 2;\n      }\n      else\n        bits |= 2;\n\n      if(array_x > 0)\n      {\n        if(*(ptr - 1) == LINE)\n          bits |= 8;\n      }\n      else\n        bits |= 8;\n\n      if(array_x < (board_width - 1))\n      {\n        if(*(ptr + 1) == LINE)\n          bits |= 4;\n      }\n      else\n        bits |= 4;\n\n      return id_chars[thick_line + bits];\n    }\n\n    case WEB:\n    case THICK_WEB:\n    {\n      int array_x = offset % board_width;\n      int array_y = offset / board_width;\n      int bits = 0;\n\n      char *ptr;\n      ptr = level_id + offset;\n\n      if(array_y > 0)\n      {\n        if(*(ptr - board_width) != SPACE)\n          bits = 1;\n      }\n      else\n        bits = 1;\n\n      if(array_y < (board_height - 1))\n      {\n        if(*(ptr + board_width) != SPACE)\n          bits |= 2;\n      }\n      else\n        bits |= 2;\n\n      if(array_x > 0)\n      {\n        if(*(ptr - 1) != SPACE)\n          bits |= 8;\n      }\n      else\n        bits |= 8;\n\n      if(array_x < (board_width - 1))\n      {\n        if(*(ptr + 1) != SPACE)\n          bits |= 4;\n      }\n      else\n        bits |= 4;\n\n      if(cell_id == WEB)\n        return id_chars[thin_line + bits];\n      return id_chars[thick_line + bits];\n    }\n\n    case ICE:\n    {\n      /* Allowed OOB until 2.93d */\n      return get_id_char_by_legacy_index(ice_anim + param);\n    }\n\n    case LAVA:\n    {\n      /* Allowed OOB until 2.93d */\n      return get_id_char_by_legacy_index(lava_anim + param);\n    }\n\n    case AMMO:\n    {\n      if(param < 10)\n        return id_chars[low_ammo];\n      else\n        return id_chars[hi_ammo];\n    }\n\n    case LIT_BOMB:\n    {\n      return id_chars[lit_bomb + (param & 0x0F)];\n    }\n\n    case DOOR:\n    {\n      if(param & 1)\n        return id_chars[vert_door];\n      else\n        return id_chars[horiz_door];\n    }\n\n    case OPEN_DOOR:\n    {\n      return id_chars[open_door + (param & 0x1F)];\n    }\n\n    case CW_ROTATE:\n    {\n      /* Allowed OOB until 2.93d */\n      return get_id_char_by_legacy_index(cw_anim + param);\n    }\n\n    case CCW_ROTATE:\n    {\n      /* Allowed OOB until 2.93d */\n      return get_id_char_by_legacy_index(ccw_anim + param);\n    }\n\n    case TRANSPORT:\n    {\n      switch(param & 0x07)\n      {\n        case 0:\n          return id_chars[trans_north + ((param >> 3) & 0x03)];\n        case 1:\n          return id_chars[trans_south + ((param >> 3) & 0x03)];\n        case 2:\n          return id_chars[trans_east  + ((param >> 3) & 0x03)];\n        case 3:\n          return id_chars[trans_west  + ((param >> 3) & 0x03)];\n        default:\n          return id_chars[trans_all   + ((param >> 3) & 0x03)];\n      }\n    }\n\n    case PUSHER:\n    case MISSILE:\n    case SPIKE:\n    {\n      /* Allowed OOB until 2.93d */\n      return get_id_char_by_legacy_index(thick_arrow + param);\n    }\n\n    case LAZER:\n    {\n      if(param & 1)\n        return id_chars[vert_lazer + ((param >> 1) & 0x03)];\n      else\n        return id_chars[horiz_lazer + ((param >> 1) & 0x03)];\n    }\n\n    case BULLET:\n    {\n      /* Allowed OOB until 2.93d */\n      return get_id_char_by_legacy_index(bullet_char + param);\n    }\n\n    case FIRE:\n    {\n      /* Allowed OOB until 2.93d */\n      return get_id_char_by_legacy_index(fire_anim + param);\n    }\n\n    case LIFE:\n    {\n      /* Allowed OOB until 2.93d */\n      return get_id_char_by_legacy_index(life_anim + param);\n    }\n\n    case RICOCHET_PANEL:\n    {\n      /* Allowed OOB until 2.93d */\n      return get_id_char_by_legacy_index(ricochet_panels + param);\n    }\n\n    case MINE:\n    {\n      return id_chars[mine_anim + (param & 0x01)];\n    }\n\n    case SHOOTING_FIRE:\n    {\n      return id_chars[shooting_fire_anim + (param & 0x01)];\n    }\n\n    case SEEKER:\n    {\n      return id_chars[seeker_anim + (param & 0x03)];\n    }\n\n    case BULLET_GUN:\n    case SPINNING_GUN:\n    {\n      return id_chars[thin_arrow + ((param >> 3) & 0x03)];\n    }\n\n    case MISSILE_GUN:\n    {\n      return id_chars[thick_arrow + ((param >> 3) & 0x03)];\n    }\n\n    case SENSOR:\n    {\n      int idx = param;\n      return (src_board->sensor_list[idx])->sensor_char;\n    }\n\n    case ROBOT:\n    case ROBOT_PUSHABLE:\n    {\n      int idx = param;\n      return (src_board->robot_list[idx])->robot_char;\n    }\n\n    case PLAYER:\n    {\n      return id_chars[player_char + (src_board->player_last_dir >> 4)];\n    }\n\n    // everything else\n    default:\n    {\n      return id_chars[cell_id];\n    }\n  }\n}\n\nstatic unsigned char get_special_id_color(struct board *src_board,\n enum thing cell_id, char color, char param)\n{\n  char normal_color = color, spec_color = 0;\n  switch(cell_id)\n  {\n    case ENERGIZER:\n      /* Allowed OOB until 2.93d */\n      spec_color = get_id_char_by_legacy_index(energizer_glow + param);\n      break;\n\n    case EXPLOSION:\n      spec_color = id_chars[explosion_colors + (param & 0x0F)];\n      break;\n\n    case FIRE:\n      /* Allowed OOB until 2.93d */\n      spec_color = get_id_char_by_legacy_index(fire_colors + param);\n      break;\n\n    case LIFE:\n      /* Allowed OOB until 2.93d */\n      spec_color = get_id_char_by_legacy_index(life_colors + param);\n      break;\n\n    case WHIRLPOOL_1:\n    case WHIRLPOOL_2:\n    case WHIRLPOOL_3:\n    case WHIRLPOOL_4:\n      spec_color = id_chars[whirlpool_glow + (int)(cell_id - WHIRLPOOL_1)];\n      break;\n\n    case SHOOTING_FIRE:\n      spec_color = id_chars[shooting_fire_colors + (param & 1)];\n      break;\n\n    case SEEKER:\n      spec_color = id_chars[seeker_colors + (param & 0x03)];\n      break;\n\n    case SCROLL:\n      spec_color = scroll_color;\n      break;\n\n    case PLAYER: /* player */\n      return id_chars[player_color];\n\n    default:\n      return color;\n  }\n  if(!(spec_color & 0xF0))\n  {\n    normal_color &= 0xF0;\n    normal_color |= (spec_color & 0x0F);\n  }\n  else\n  {\n    normal_color = spec_color;\n  }\n  return normal_color;\n}\n\nunsigned char get_id_char(struct board *src_board, int id_offset)\n{\n  unsigned char cell_id, cell_char;\n  cell_id = src_board->level_id[id_offset];\n  cell_char = id_chars[cell_id];\n\n  switch(cell_char)\n  {\n    case 255: return src_board->level_param[id_offset];\n    case 0: return\n      get_special_id_char(src_board, (enum thing)cell_id,\n       src_board->level_param[id_offset], id_offset);\n    default: return cell_char;\n  }\n}\n\nunsigned char get_id_board_color(struct board *src_board, int id_offset, int ignore_under)\n{\n  unsigned char color = get_special_id_color(src_board,\n   (enum thing)src_board->level_id[id_offset],\n   src_board->level_color[id_offset],\n   src_board->level_param[id_offset]\n  );\n\n  if(!(color & 0xF0) && !(ignore_under))\n  {\n    color |=\n      (src_board->level_under_color[id_offset] & 0xF0);\n  }\n  return color;\n}\n\nunsigned char get_id_color(struct board *src_board, int id_offset)\n{\n  return get_id_board_color(src_board, id_offset, 0);\n}\n\nunsigned char get_id_under_char(struct board *src_board, int id_offset)\n{\n  unsigned char cell_id, cell_char;\n  cell_id = src_board->level_under_id[id_offset];\n  cell_char = id_chars[cell_id];\n\n  switch(cell_char)\n  {\n    case 255: return src_board->level_under_param[id_offset];\n    case 0: return\n      get_special_id_char(src_board, (enum thing)cell_id,\n       src_board->level_under_param[id_offset], id_offset);\n    default: return cell_char;\n  }\n}\n\nunsigned char get_id_under_color(struct board *src_board, int id_offset)\n{\n  return get_special_id_color(src_board,\n   (enum thing)src_board->level_under_id[id_offset],\n   src_board->level_under_color[id_offset],\n   src_board->level_under_param[id_offset]\n  );\n}\n\nvoid id_put(struct board *src_board, unsigned char x_pos, unsigned char y_pos,\n int array_x, int array_y, int ovr_x, int ovr_y)\n{\n  int array_offset, overlay_offset;\n  int overlay_mode = src_board->overlay_mode;\n  int board_width = src_board->board_width;\n\n  unsigned char c, color;\n\n  array_offset = (array_y * board_width) + array_x;\n\n  // Note: old worlds unfortunately can contain the hide overlay and hide board\n  // flags, so their checks have been kept functionally intact unless noted.\n  if(!(overlay_mode & OVERLAY_FLAG_HIDE_OVERLAY) &&\n   (overlay_mode & OVERLAY_MODE_MASK) != OVERLAY_OFF &&\n   (overlay_mode & OVERLAY_MODE_MASK) != OVERLAY_TRANSPARENT)\n  {\n    if((overlay_mode & OVERLAY_MODE_MASK) == OVERLAY_STATIC)\n    {\n      overlay_offset = (ovr_y * board_width) + ovr_x;\n    }\n    else\n    {\n      overlay_offset = array_offset;\n    }\n\n    select_layer(OVERLAY_LAYER);\n    c = src_board->overlay[overlay_offset];\n    color = src_board->overlay_color[overlay_offset];\n\n    // NOTE: <2.51s3.1 won't display 32 even with OVERLAY_FLAG_HIDE_BOARD set.\n    if(!(overlay_mode & OVERLAY_FLAG_HIDE_BOARD))\n    {\n      if(c == 32)\n      {\n        select_layer(BOARD_LAYER);\n        c = get_id_char(src_board, array_offset);\n        color = get_id_color(src_board, array_offset);\n      }\n      else\n\n      if(!(color & 0xF0))\n      {\n        /*\n        // Undocumented and inaccessible MZXak overlay mode 4.\n        if(overlay_mode == 4)\n        {\n          select_layer(BOARD_LAYER);\n          c = get_id_char(src_board, array_offset);\n        }\n        */\n        color = (color & 0x0F) | (get_id_color(src_board, array_offset) & 0xF0);\n      }\n    }\n  }\n  else\n  // NOTE: <2.51s3 checked (overlay_mode & OVERLAY_FLAG_HIDE_BOARD) here.\n  {\n    select_layer(BOARD_LAYER);\n    c = get_id_char(src_board, array_offset);\n    color = get_id_color(src_board, array_offset);\n  }\n\n  draw_char_ext(c, color, x_pos, y_pos, 0, 0);\n}\n\nvoid draw_game_window(struct board *src_board, int array_x, int array_y)\n{\n  int x_limit, y_limit;\n  int x, y;\n  int a_x, a_y;\n  int o_x, o_y;\n\n  x_limit = src_board->viewport_width;\n  y_limit = src_board->viewport_height;\n\n  for(y = src_board->viewport_y, a_y = array_y, o_y = 0; o_y < y_limit;\n   y++, a_y++, o_y++)\n  {\n    for(x = src_board->viewport_x, a_x = array_x, o_x = 0;\n     o_x < x_limit; x++, a_x++, o_x++)\n    {\n      id_put(src_board, x, y, a_x, a_y, o_x, o_y);\n    }\n  }\n}\n\nvoid draw_game_window_blind(struct board *src_board, int array_x, int array_y,\n int player_x, int player_y)\n{\n  int viewport_x = src_board->viewport_x;\n  int viewport_y = src_board->viewport_y;\n  int viewport_width = src_board->viewport_width;\n  int viewport_height = src_board->viewport_height;\n  int i;\n\n  select_layer(BOARD_LAYER);\n\n  for(i = viewport_y; i < viewport_y + viewport_height; i++)\n    fill_line(viewport_width, viewport_x, i, 176, 8);\n\n  // Find where player would be and draw.\n  if(player_x >= 0 && player_y >= 0)\n  {\n    id_put(src_board, player_x - array_x + viewport_x,\n     player_y - array_y + viewport_y, player_x,\n     player_y, player_x, player_y);\n  }\n}\n\nvoid draw_viewport(struct board *src_board, int edge_color)\n{\n  int i, i2;\n  int viewport_x = src_board->viewport_x;\n  int viewport_y = src_board->viewport_y;\n  int viewport_width = src_board->viewport_width;\n  int viewport_height = src_board->viewport_height;\n\n  // Draw the current viewport\n  if(viewport_y > 1)\n  {\n    // Top\n    for(i = 0; i < viewport_y; i++)\n      fill_line_ext(80, 0, i, 177, edge_color, 0, 0);\n  }\n\n  if((viewport_y + viewport_height) < 24)\n  {\n    // Bottom\n    for(i = viewport_y + viewport_height + 1; i < 25; i++)\n      fill_line_ext(80, 0, i, 177, edge_color, 0, 0);\n  }\n\n  if(viewport_x > 1)\n  {\n    // Left\n    for(i = 0; i < 25; i++)\n      fill_line_ext(viewport_x, 0, i, 177, edge_color, 0, 0);\n  }\n\n  if((viewport_x + viewport_width) < 79)\n  {\n    // Right\n    i2 = viewport_x + viewport_width + 1;\n    for(i = 0; i < 25; i++)\n    {\n      fill_line_ext(80 - i2, i2, i, 177, edge_color, 0, 0);\n    }\n  }\n\n  // Draw the box\n  if(viewport_x > 0)\n  {\n    // left\n    for(i = 0; i < viewport_height; i++)\n    {\n      draw_char_ext('\\xba', edge_color, viewport_x - 1,\n       i + viewport_y, 0, 0);\n    }\n\n    if(viewport_y > 0)\n    {\n      draw_char_ext('\\xc9', edge_color, viewport_x - 1,\n       viewport_y - 1, 0, 0);\n    }\n  }\n  if((viewport_x + viewport_width) < 80)\n  {\n    // right\n    for(i = 0; i < viewport_height; i++)\n    {\n      draw_char_ext('\\xba', edge_color,\n       viewport_x + viewport_width, i + viewport_y, 0, 0);\n    }\n\n    if(viewport_y > 0)\n    {\n      draw_char_ext('\\xbb', edge_color,\n       viewport_x + viewport_width, viewport_y - 1, 0, 0);\n    }\n  }\n\n  if(viewport_y > 0)\n  {\n    // top\n    for(i = 0; i < viewport_width; i++)\n    {\n      draw_char_ext('\\xcd', edge_color, viewport_x + i,\n       viewport_y - 1, 0, 0);\n    }\n  }\n\n  if((viewport_y + viewport_height) < 25)\n  {\n    // bottom\n    for(i = 0; i < viewport_width; i++)\n    {\n      draw_char_ext('\\xcd', edge_color, viewport_x + i,\n       viewport_y + viewport_height, 0, 0);\n    }\n\n    if(viewport_x > 0)\n    {\n      draw_char_ext('\\xc8', edge_color, viewport_x - 1,\n       viewport_y + viewport_height, 0, 0);\n    }\n\n    if((viewport_x + viewport_width) < 80)\n    {\n      draw_char_ext('\\xbc', edge_color, viewport_x + viewport_width,\n       viewport_y + viewport_height, 0, 0);\n    }\n  }\n}\n"
  },
  {
    "path": "src/idput.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Declarations for IDPUT.ASM */\n\n#ifndef __IDPUT_H\n#define __IDPUT_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"world_struct.h\"\n#include \"data.h\"\n\nvoid set_id_char_by_legacy_index(unsigned index, unsigned char value);\n\nCORE_LIBSPEC void id_put(struct board *src_board, unsigned char x_pos,\n unsigned char y_pos, int array_x, int array_y, int ovr_x, int ovr_y);\nCORE_LIBSPEC void draw_game_window(struct board *src_board,\n int array_x, int array_y);\nCORE_LIBSPEC void draw_game_window_blind(struct board *src_board,\n int array_x, int array_y, int player_x, int player_y);\nCORE_LIBSPEC void draw_viewport(struct board *src_board, int edge_color);\n\nCORE_LIBSPEC unsigned char get_id_char(struct board *src_board, int id_offset);\nCORE_LIBSPEC unsigned char get_id_color(struct board *src_board, int id_offset);\nCORE_LIBSPEC unsigned char get_id_board_color(struct board *src_board, int id_offset, int ignore_under);\nCORE_LIBSPEC unsigned char get_id_under_char(struct board *src_board, int id_offset);\nCORE_LIBSPEC unsigned char get_id_under_color(struct board *src_board, int id_offset);\n\n#define ID_CHARS_SIZE               323\n#define ID_DMG_SIZE                 128\n#define ID_BULLET_COLOR_SIZE        3\n\n// Pre-2.90 world format sizes for these arrays.\n#define LEGACY_ID_CHARS_SIZE        ID_CHARS_SIZE\n#define LEGACY_ID_DMG_SIZE          ID_DMG_SIZE\n#define LEGACY_ID_BULLET_COLOR_SIZE ID_BULLET_COLOR_SIZE\n\n// Constants for the CHANGE CHAR ID command. If any of the ID chars arrays are\n// extended, their indexable space for the CHANGE CHAR ID command needs to be\n// placed after the existing tables.\n#define ID_CHARS_POS          0\n#define ID_MISSILE_COLOR_POS  LEGACY_ID_CHARS_SIZE\n#define ID_BULLET_COLOR_POS   (ID_MISSILE_COLOR_POS + 1)\n#define ID_DMG_POS            (ID_BULLET_COLOR_POS + LEGACY_ID_BULLET_COLOR_SIZE)\n#define ID_CHARS_TOTAL_SIZE   (ID_DMG_POS + LEGACY_ID_DMG_SIZE)\n\n#define bullet_char  306\n#define player_char  318\n#define player_color 322\n\nCORE_LIBSPEC extern unsigned char id_chars[ID_CHARS_SIZE];\nCORE_LIBSPEC extern unsigned char id_dmg[ID_DMG_SIZE];\nCORE_LIBSPEC extern unsigned char bullet_color[ID_BULLET_COLOR_SIZE];\nCORE_LIBSPEC extern unsigned char missile_color;\n\n__M_END_DECLS\n\n#endif // __IDPUT_H\n"
  },
  {
    "path": "src/intake.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2018-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdint.h>\n#include <string.h>\n#include <ctype.h>\n\n#include \"configure.h\"\n#include \"core.h\"\n#include \"event.h\"\n#include \"intake.h\"\n#include \"graphics.h\"\n#include \"window.h\"\n#include \"world_struct.h\"\n\n// Global status of insert\nstatic boolean insert_on = true;\n\nstatic char last_char = 0;\n\n/**\n * Get the current intake insert state.\n */\nboolean intake_get_insert(void)\n{\n  return insert_on;\n}\n\n/**\n * Set the current intake insert state.\n */\nvoid intake_set_insert(boolean new_insert_state)\n{\n  insert_on = new_insert_state ? true : false;\n}\n\nstatic inline void intake_old_place_char(char *string, int *currx,\n int *curr_len, uint32_t chr)\n{\n  // Overwrite or insert?\n  if(insert_on || (*currx == *curr_len))\n  {\n    // Insert- Move all ahead 1, increasing string length\n    (*curr_len)++;\n    memmove(string + *currx + 1, string + *currx, *curr_len - *currx);\n  }\n  // Add character and move forward one\n  string[(*currx)++] = chr;\n}\n\n// (returns the key used to exit) String points to your memory for storing\n// the new string. The current \"value\" is used- clear the string before\n// calling intake if you need a blank string. Max_len is the maximum length\n// of the string. X, y, and color are self-explanitory.\n\n// The editing keys supported are as\n// follows- Keys to enter characters, Enter/ESC to exit, Home/End to move to\n// the front/back of the line, Insert to toggle insert/overwrite, Left/Right\n// to move within the line, Bkspace/Delete to delete as usual, and Alt-Bksp\n// to clear the entire line. No screen saving is performed. After this\n// function, the cursor is automatically off.\n\n// Mouse support- Clicking inside string sends cursor there. Clicking\n// outside string returns a MOUSE_EVENT to caller without acknowledging\n// the event.\n\n// Returns a backspace if attempted at start of line. (exit_type==INTK_EXIT_ANY)\n// Returns a delete if attempted at end of line. (exit_type==INTK_EXIT_ANY)\n\nint intake(struct world *mzx_world, char *string, int max_len, int display_len,\n int x, int y, char color, enum intake_exit_type exit_type, boolean mask_colors,\n int *return_x_pos)\n{\n  int use_mask = get_config()->mask_midchars && mask_colors;\n  int offset = mask_colors ? PRO_CH : 0;\n  int c_offset = mask_colors ? 16 : 0;\n  int currx, curr_len;\n  int scrolledx, tmpx;\n  int done = 0, place = 0;\n  char tmp_char;\n  boolean select_char = false;\n  int mouse_press;\n  int action;\n  int key;\n\n  // Put cursor at the end of the string...\n  currx = curr_len = (int)strlen(string);\n\n  // ...unless return_x_pos says not to.\n  if((return_x_pos) && (*return_x_pos < currx))\n    currx = *return_x_pos;\n\n  do\n  {\n    int flags = use_mask ? WR_MASK : 0;\n\n    if(max_len > display_len && currx > display_len)\n    {\n      scrolledx = currx - display_len;\n      tmpx = currx;\n    }\n    else\n    {\n      scrolledx = 0;\n      tmpx = display_len;\n    }\n\n    tmp_char = string[tmpx];\n    string[tmpx] = '\\0';\n    write_string_ext(string + scrolledx, x, y, color, flags, 0, c_offset);\n    string[tmpx] = tmp_char;\n\n    if(curr_len < display_len)\n    {\n      fill_line_ext(display_len + 1 - curr_len, x + curr_len, y, 32, color,\n       offset, c_offset);\n    }\n\n    if(insert_on)\n      cursor_underline(x + currx - scrolledx, y);\n    else\n      cursor_solid(x + currx - scrolledx, y);\n\n    update_screen();\n    update_event_status_delay();\n    key = get_key(keycode_internal_wrt_numlock);\n    place = 0;\n\n    if(!key && has_unicode_input())\n      key = IKEY_UNICODE;\n\n    action = get_joystick_ui_action();\n    if(action)\n    {\n      switch(action)\n      {\n        case JOY_X:\n          // Select character\n          select_char = true;\n          key = IKEY_c;\n          break;\n\n        case JOY_Y:\n          key = IKEY_BACKSPACE;\n          break;\n\n        default:\n          key = get_joystick_ui_key();;\n          break;\n      }\n    }\n\n    // Exit event mimics escape\n    if(get_exit_status() && exit_type != INTK_EXIT_ENTER)\n    {\n      key = 0;\n      done = 1;\n    }\n\n    mouse_press = get_mouse_press_ext();\n\n    if(mouse_press)\n    {\n      int mouse_x, mouse_y;\n      get_mouse_position(&mouse_x, &mouse_y);\n      if((mouse_y == y) && (mouse_x >= x) &&\n       (mouse_x <= (x + display_len)) && (mouse_press <= MOUSE_BUTTON_RIGHT))\n      {\n        // Yep, reposition cursor.\n        currx = mouse_x - x;\n        if(currx > curr_len)\n          currx = curr_len;\n      }\n      else\n      {\n        key = -1;\n        done = 1;\n      }\n    }\n\n    // Handle key cases\n    switch(key)\n    {\n      case IKEY_ESCAPE:\n      {\n        // ESC\n        if(exit_type != INTK_EXIT_ENTER)\n        {\n          done = 1;\n        }\n        break;\n      }\n\n      case IKEY_RETURN:\n      {\n        // Enter\n        done = 1;\n        break;\n      }\n\n      case IKEY_HOME:\n      {\n        // Home\n        currx = 0;\n        break;\n      }\n\n      case IKEY_END:\n      {\n        // End\n        currx = curr_len;\n        break;\n      }\n\n      case IKEY_LEFT:\n      {\n        if(get_ctrl_status(keycode_internal))\n        {\n          // Find nearest space to the left\n          if(currx)\n          {\n            char *next_position = string + currx - 1;\n\n            while(currx && !isalnum((int)*next_position))\n            {\n              next_position--;\n              currx--;\n            }\n\n            while(currx && isalnum((int)*next_position))\n            {\n              next_position--;\n              currx--;\n            }\n          }\n        }\n        else\n        {\n          // Left\n          if(currx > 0)\n            currx--;\n        }\n\n        break;\n      }\n\n      case IKEY_RIGHT:\n      {\n        if(get_ctrl_status(keycode_internal))\n        {\n          // Find nearest space to the right\n          if(currx < curr_len)\n          {\n            char *current_position = string + currx;\n            char current_char = *current_position;\n            if(!isalnum((int)current_char))\n            {\n              do\n              {\n                current_position++;\n                currx++;\n                current_char = *current_position;\n              } while(current_char && !isalnum((int)current_char));\n            }\n\n            while(current_char && isalnum((int)current_char))\n            {\n              current_position++;\n              currx++;\n              current_char = *current_position;\n            }\n          }\n        }\n        else\n        {\n          // Right\n          if(currx < curr_len)\n            currx++;\n        }\n\n        break;\n      }\n\n      case IKEY_F1:\n      case IKEY_F2:\n      case IKEY_F3:\n      case IKEY_F4:\n      case IKEY_F5:\n      case IKEY_F6:\n      case IKEY_F7:\n      case IKEY_F8:\n      case IKEY_F9:\n      case IKEY_F10:\n      case IKEY_F11:\n      case IKEY_F12:\n      case IKEY_UP:\n      case IKEY_DOWN:\n      case IKEY_TAB:\n      case IKEY_PAGEUP:\n      case IKEY_PAGEDOWN:\n      {\n        done = 1;\n        break;\n      }\n\n      case IKEY_INSERT:\n      {\n        // Insert\n        insert_on ^= 1;\n        break;\n      }\n\n      case IKEY_BACKSPACE:\n      {\n        // Backspace, at 0 it might exit\n        if(get_alt_status(keycode_internal))\n        {\n          // Alt-backspace, erase input\n          curr_len = currx = 0;\n          string[0] = 0;\n        }\n        else\n\n        if(get_ctrl_status(keycode_internal))\n        {\n          // Find nearest space to the left\n          if(currx)\n          {\n            int old_position = currx;\n\n            while(currx && !isalnum((int)string[currx]))\n              currx--;\n\n            while(currx && isalnum((int)string[currx]))\n              currx--;\n\n            curr_len -= old_position - currx;\n\n            memmove(string + currx, string + old_position,\n             strlen(string + old_position) + 1);\n          }\n        }\n        else\n\n        if(currx == 0)\n        {\n          if(exit_type == INTK_EXIT_ANY)\n          {\n            done = 1;\n          }\n        }\n        else\n        {\n          // Move all back 1, decreasing string length\n          memmove(string + currx - 1, string + currx, curr_len - currx + 1);\n          curr_len--;\n          // Cursor back one\n          currx--;\n        }\n        break;\n      }\n\n      case IKEY_DELETE:\n      {\n        // Delete, at the end might exit\n        if(currx == curr_len)\n        {\n          if(exit_type == INTK_EXIT_ANY)\n            done = 1;\n        }\n        else\n        {\n          if(curr_len)\n          {\n            // Move all back 1, decreasing string length\n            memmove(string + currx, string + currx + 1, curr_len - currx);\n            curr_len--;\n          }\n        }\n        break;\n      }\n\n      case IKEY_c:\n      {\n        if(select_char || (get_alt_status(keycode_internal) &&\n         !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal)))\n        {\n          // If alt - C is pressed, choose character\n          int new_char = char_selection(last_char);\n          select_char = false;\n\n          if(new_char >= 32)\n          {\n            intake_old_place_char(string, &currx, &curr_len, new_char);\n            last_char = new_char;\n          }\n        }\n        else\n        {\n          place = 1;\n        }\n\n        break;\n      }\n\n      case IKEY_t:\n      {\n        // Hack for SFX editor dialog\n        if(get_alt_status(keycode_internal) &&\n         !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n        {\n          done = 1;\n        }\n        else\n        {\n          place = 1;\n        }\n        break;\n      }\n\n      case IKEY_v:\n      {\n        // Hack for scroll editor\n        if(get_alt_status(keycode_internal) &&\n         !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n        {\n          done = 1;\n        }\n        else\n        {\n          place = 1;\n        }\n        break;\n      }\n\n      case IKEY_LSHIFT:\n      case IKEY_RSHIFT:\n      case 0:\n      {\n        place = 0;\n        break;\n      }\n\n      default:\n      {\n        // Place the char\n        place = 1;\n        break;\n      }\n\n      case -1:\n      {\n        break;\n      }\n    }\n\n    if(place)\n    {\n      int num_placed = 0;\n\n      while((curr_len < max_len) && (!done) && num_placed < KEY_UNICODE_MAX)\n      {\n        uint32_t cur_char = get_key(keycode_text_ascii);\n        if(cur_char)\n        {\n          intake_old_place_char(string, &currx, &curr_len, cur_char);\n          num_placed++;\n        }\n        else\n          break;\n      }\n    }\n\n    // Loop\n  } while(!done);\n\n  cursor_off();\n  if(return_x_pos)\n    *return_x_pos = currx;\n\n  return key;\n}\n\nstruct intake_subcontext\n{\n  subcontext ctx;\n  boolean (*event_cb)(void *priv, subcontext *sub, enum intake_event_type type,\n   int old_pos, int new_pos, int value, const char *data);\n  void *event_priv;\n  char *dest;\n  int current_length;\n  int max_length;\n  int pos;\n  int *pos_external;\n  int *length_external;\n  boolean select_char;\n};\n\n/**\n * Set the current cursor position within the editing string.\n * This should be used mainly by intake_sync and intake_apply_event_fixed.\n */\nstatic void intake_set_pos(struct intake_subcontext *intk, int new_pos)\n{\n  if(new_pos < 0)\n    new_pos = 0;\n  if(new_pos > intk->current_length)\n    new_pos = intk->current_length;\n\n  intk->pos = new_pos;\n  if(intk->pos_external)\n    *(intk->pos_external) = new_pos;\n}\n\n/**\n * Set the editing string length.\n * This should be used mainly by intake_sync and intake_apply_event_fixed.\n */\nstatic void intake_set_length(struct intake_subcontext *intk, int new_length)\n{\n  if(new_length < 0)\n    new_length = 0;\n  if(new_length > intk->max_length)\n    new_length = intk->max_length;\n\n  if(intk->dest)\n    intk->dest[new_length] = '\\0';\n\n  intk->current_length = new_length;\n  if(intk->length_external)\n    *(intk->length_external) = new_length;\n}\n\n/**\n * Sync the current pos/length values with their external duplicates.\n */\nvoid intake_sync(subcontext *sub)\n{\n  struct intake_subcontext *intk = (struct intake_subcontext *)sub;\n  if(!intk)\n    return;\n\n  // If using a fixed buffer, calculate from it instead of the external length.\n  if(intk->dest)\n  {\n    intake_set_length(intk, strlen(intk->dest));\n  }\n  else\n\n  if(intk->length_external)\n    intake_set_length(intk, *(intk->length_external));\n\n  if(intk->pos_external)\n    intake_set_pos(intk, *(intk->pos_external));\n}\n\n/**\n * Find the start of the word preceding the cursor and place the cursor there.\n */\nstatic void intake_skip_back(struct intake_subcontext *intk)\n{\n  char *next_position;\n  int pos = intk->pos;\n\n  if(pos)\n  {\n    next_position = intk->dest + pos - 1;\n\n    while(pos && !isalnum((int)*next_position))\n    {\n      next_position--;\n      pos--;\n    }\n\n    while(pos && isalnum((int)*next_position))\n    {\n      next_position--;\n      pos--;\n    }\n\n    intake_set_pos(intk, pos);\n  }\n}\n\n/**\n * Find the end of the word following the cursor and place the cursor there.\n */\nstatic void intake_skip_forward(struct intake_subcontext *intk)\n{\n  char *current_position;\n  int pos = intk->pos;\n\n  if(pos < intk->current_length)\n  {\n    current_position = intk->dest + pos;\n\n    if(!isalnum((int)*current_position))\n    {\n      do\n      {\n        current_position++;\n        pos++;\n      } while(*current_position && !isalnum((int)*current_position));\n    }\n\n    while(*current_position && isalnum((int)*current_position))\n    {\n      current_position++;\n      pos++;\n    }\n\n    intake_set_pos(intk, pos);\n  }\n}\n\n/**\n * Apply intake events to the fixed-size buffer supplied with the original\n * intake2() call. If no event callback is provided, this function will always\n * be used as the event callback. If an event callback is provided, the event\n * callback must call this function manually if it is required.\n */\nboolean intake_apply_event_fixed(subcontext *sub, enum intake_event_type type,\n int new_pos, int value, const char *data)\n{\n  struct intake_subcontext *intk = (struct intake_subcontext *)sub;\n\n  if(!intk || !intk->dest || intk->pos < 0 || intk->pos > intk->current_length)\n    return false;\n\n  switch(type)\n  {\n    case INTK_NO_EVENT:\n      return false;\n\n    case INTK_MOVE:\n      break;\n\n    case INTK_MOVE_WORDS:\n    {\n      while(value < 0)\n        intake_skip_back(intk), value++;\n      while(value > 0)\n        intake_skip_forward(intk), value--;\n      new_pos = intk->pos;\n      break;\n    }\n\n    case INTK_INSERT:\n    {\n      if(intk->current_length >= intk->max_length)\n        return false;\n      intake_set_length(intk, intk->current_length + 1);\n\n      if(intk->pos < intk->current_length)\n        memmove(intk->dest + intk->pos + 1, intk->dest + intk->pos,\n         intk->current_length - intk->pos);\n\n      if(intk->pos <= intk->current_length)\n        intk->dest[intk->pos] = value;\n      break;\n    }\n\n    case INTK_OVERWRITE:\n    {\n      if(intk->pos == intk->current_length)\n      {\n        if(intk->current_length >= intk->max_length)\n          return false;\n\n        intake_set_length(intk, intk->current_length + 1);\n      }\n\n      if(intk->pos <= intk->current_length)\n        intk->dest[intk->pos] = value;\n      break;\n    }\n\n    case INTK_DELETE:\n    {\n      if(intk->pos < intk->current_length)\n      {\n        memmove(intk->dest + intk->pos, intk->dest + intk->pos + 1,\n         intk->current_length - intk->pos);\n        intake_set_length(intk, intk->current_length - 1);\n      }\n      break;\n    }\n\n    case INTK_BACKSPACE:\n    {\n      if(intk->pos > 0)\n      {\n        memmove(intk->dest + intk->pos - 1, intk->dest + intk->pos,\n         intk->current_length - intk->pos + 1);\n        intake_set_length(intk, intk->current_length - 1);\n      }\n      break;\n    }\n\n    case INTK_BACKSPACE_WORDS:\n    {\n      if(intk->pos > 0)\n      {\n        int old_pos = intk->pos;\n        while(value > 0 && intk->pos > 0)\n        {\n          intake_skip_back(intk);\n          value--;\n        }\n        new_pos = intk->pos;\n        if(intk->pos < old_pos)\n        {\n          memmove(intk->dest + intk->pos, intk->dest + old_pos,\n           intk->current_length - old_pos + 1);\n          intake_set_length(intk, intk->current_length - (old_pos - intk->pos));\n        }\n      }\n      break;\n    }\n\n    case INTK_CLEAR:\n    {\n      intk->dest[0] = 0;\n      intake_set_length(intk, 0);\n      break;\n    }\n\n    case INTK_INSERT_BLOCK:\n    {\n      if(!data)\n        return false;\n\n      if(intk->current_length + value > intk->max_length)\n      {\n        value = intk->max_length - intk->current_length;\n        new_pos = intk->pos + value;\n        if(!value)\n          return false;\n      }\n\n      if(intk->pos < intk->current_length)\n        memmove(intk->dest + intk->pos + value, intk->dest + intk->pos,\n         intk->current_length - intk->pos + 1);\n\n      memcpy(intk->dest + intk->pos, data, value);\n      intake_set_length(intk, intk->current_length + value);\n      break;\n    }\n\n    case INTK_OVERWRITE_BLOCK:\n    {\n      if(!data)\n        return false;\n\n      if(intk->pos + value > intk->max_length)\n      {\n        value = intk->max_length - intk->pos;\n        new_pos = intk->pos + value;\n        if(!value)\n          return false;\n      }\n\n      memcpy(intk->dest + intk->pos, data, value);\n\n      if(intk->pos + value > intk->current_length)\n        intake_set_length(intk, intk->pos + value);\n      break;\n    }\n  }\n  intake_set_pos(intk, new_pos);\n  return true;\n}\n\n/**\n * Send an intake event to the parent context, if applicable.\n */\nstatic void intake_event_ext(struct intake_subcontext *intk,\n enum intake_event_type type, int old_pos, int new_pos, int value, const char *data)\n{\n  if(intk->event_cb)\n  {\n    if(intk->event_cb(intk->event_priv, (subcontext *)intk, type, old_pos,\n     new_pos, value, data))\n    {\n      intake_sync((subcontext *)intk);\n    }\n  }\n  else\n    intake_apply_event_fixed((subcontext *)intk, type, new_pos, value, data);\n}\n\nstatic void intake_event(struct intake_subcontext *intk,\n enum intake_event_type type, int old_pos, int new_pos)\n{\n  intake_event_ext(intk, type, old_pos, new_pos, 0, NULL);\n}\n\n/**\n * Place a new char inside of the string.\n */\nstatic boolean intake_place_char(struct intake_subcontext *intk, char chr)\n{\n  if(chr && (intk->current_length != intk->max_length))\n  {\n    enum intake_event_type type = insert_on ? INTK_INSERT : INTK_OVERWRITE;\n    intake_event_ext(intk, type, intk->pos, intk->pos + 1, chr, NULL);\n    return true;\n  }\n  return false;\n}\n\n/**\n * Make sure the intake values are synchronized before doing anything.\n */\nstatic boolean intake_idle(subcontext *sub)\n{\n  intake_sync(sub);\n  return false;\n}\n\n/**\n * Joystick input. Can't do much here; pass most input through to the parent.\n */\nstatic boolean intake_joystick(subcontext *sub, int *key, int action)\n{\n  switch(action)\n  {\n    case JOY_LEFT:\n      *key = IKEY_LEFT;\n      return true;\n\n    case JOY_RIGHT:\n      *key = IKEY_RIGHT;\n      return true;\n\n    case JOY_X:\n      // Select character\n      ((struct intake_subcontext *)sub)->select_char = true;\n      *key = IKEY_c;\n      return true;\n\n    case JOY_Y:\n      *key = IKEY_BACKSPACE;\n      return true;\n  }\n  return false;\n}\n\n/**\n * Input/delete text, move the cursor, or pass input on to the parent context.\n */\nstatic boolean intake_key(subcontext *sub, int *key)\n{\n  struct intake_subcontext *intk = (struct intake_subcontext *)sub;\n  boolean alt_status = get_alt_status(keycode_internal);\n  boolean ctrl_status = get_ctrl_status(keycode_internal);\n  boolean shift_status = get_shift_status(keycode_internal);\n  boolean any_mod = (alt_status || ctrl_status || shift_status);\n  boolean place = false;\n  int num_placed = 0;\n\n  // Exit-- let the parent context handle.\n  if(get_exit_status())\n    return false;\n\n  switch(*key)\n  {\n    case IKEY_HOME:\n    {\n      if(!any_mod)\n      {\n        // Home\n        intake_event(intk, INTK_MOVE, intk->pos, 0);\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_END:\n    {\n      if(!any_mod)\n      {\n        // End\n        intake_event(intk, INTK_MOVE, intk->pos, intk->current_length);\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_LEFT:\n    {\n      if(get_ctrl_status(keycode_internal))\n      {\n        // Move one word backward.\n        intake_event_ext(intk, INTK_MOVE_WORDS, intk->pos, intk->pos, -1, NULL);\n        return true;\n      }\n      else\n\n      if(!any_mod)\n      {\n        // Left\n        if(intk->pos > 0)\n          intake_event(intk, INTK_MOVE, intk->pos, intk->pos - 1);\n\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_RIGHT:\n    {\n      if(get_ctrl_status(keycode_internal))\n      {\n        // Move one word forward.\n        intake_event_ext(intk, INTK_MOVE_WORDS, intk->pos, intk->pos, 1, NULL);\n        return true;\n      }\n      else\n\n      if(!any_mod)\n      {\n        // Right\n        if(intk->pos < intk->current_length)\n          intake_event(intk, INTK_MOVE, intk->pos, intk->pos + 1);\n\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_INSERT:\n    {\n      if(!any_mod)\n      {\n        // Insert\n        insert_on = !insert_on;\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_BACKSPACE:\n    {\n      if(get_alt_status(keycode_internal))\n      {\n        // Alt-backspace, erase input\n        intake_event(intk, INTK_CLEAR, intk->pos, 0);\n        return true;\n      }\n      else\n\n      if(get_ctrl_status(keycode_internal))\n      {\n        // Delete word\n        if(intk->pos)\n          intake_event_ext(intk, INTK_BACKSPACE_WORDS, intk->pos, intk->pos, 1, NULL);\n        return true;\n      }\n      else\n\n      if(intk->pos > 0)\n      {\n        // Backspace previous char.\n        intake_event(intk, INTK_BACKSPACE, intk->pos, intk->pos - 1);\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_DELETE:\n    {\n      // Delete current char.\n      // If at the end of the string, let the parent handle this instead.\n      if(intk->current_length && intk->pos < intk->current_length)\n      {\n        intake_event(intk, INTK_DELETE, intk->pos, intk->pos);\n        return true;\n      }\n      break;\n    }\n\n    case IKEY_c:\n    {\n      if(intk->select_char || (get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal)))\n      {\n        // If alt - C is pressed, choose character\n        int new_char = char_selection(last_char);\n        intk->select_char = false;\n\n        if(new_char >= 32)\n        {\n          last_char = new_char;\n          intake_place_char(intk, new_char);\n        }\n        return true;\n      }\n      else\n      {\n        place = true;\n      }\n      break;\n    }\n\n    default:\n    {\n      place = true;\n      break;\n    }\n  }\n\n  // Attempt to add char(s) to the string.\n  while(place && num_placed < KEY_UNICODE_MAX)\n  {\n    char cur_char = get_key(keycode_text_ascii);\n    if(cur_char)\n    {\n      intake_place_char(intk, cur_char);\n      num_placed++;\n    }\n    else\n      break;\n  }\n  return !!(num_placed);\n}\n\n/**\n * Create a text entry subcontext on top of the current context. Will pass all\n * inputs it can't/won't handle through to its parent. An optional external\n * cursor position pointer can be provided to allow intake to return the cursor\n * position and receive outside updates to the cursor position.\n *\n * Unlike the original intake(), this intake implementation takes no screen\n * positioning information, doesn't draw the string being edited, and doesn't\n * handle mouse clicks. Doing these is the responsibility of the parent context.\n */\nsubcontext *intake2(context *parent, char *dest, int max_length,\n int *pos_external, int *length_external)\n{\n  struct intake_subcontext *intk =\n   (struct intake_subcontext *)ccalloc(1, sizeof(struct intake_subcontext));\n  struct context_spec spec;\n\n  intk->dest = dest;\n  intk->max_length = max_length;\n  intk->pos_external = pos_external;\n  intk->length_external = length_external;\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.idle     = intake_idle;\n  spec.key      = intake_key;\n  spec.joystick = intake_joystick;\n\n  intake_sync((subcontext *)intk);\n  if(!pos_external)\n    intake_set_pos(intk, intk->current_length);\n\n  create_subcontext((subcontext *)intk, parent, &spec);\n  return (subcontext *)intk;\n}\n\n/**\n * Insert a string of text into the intake buffer. A linebreak char can be\n * provided which will cause this loop to terminate and return the next char\n * position. Otherwise, return NULL when the input string has been exhausted.\n */\nconst char *intake_input_string(subcontext *sub, const char *src,\n int linebreak_char)\n{\n  struct intake_subcontext *intk = (struct intake_subcontext *)sub;\n  const char *pos = src;\n  int cur_char;\n  int length = 0;\n  enum intake_event_type type = insert_on ? INTK_INSERT_BLOCK : INTK_OVERWRITE_BLOCK;\n\n  intake_sync(sub);\n\n  while(*pos)\n  {\n    cur_char = *pos;\n\n    // Linebreak char? Skip and request a line break.\n    if(cur_char == linebreak_char)\n    {\n      if(length > 0)\n        intake_event_ext(intk, type, intk->pos, intk->pos + length, length, src);\n\n      return (pos + 1);\n    }\n\n    // Otherwise, attempt to place until no more space is left.\n    length++;\n    if(intk->current_length + length >= intk->max_length)\n      break;\n\n    pos++;\n  }\n\n  if(length > 0)\n    intake_event_ext(intk, type, intk->pos, intk->pos + length, length, src);\n\n  return NULL;\n}\n\n/**\n * Set the intake event callback function. This feature is used to report\n * individual intake events immediately to the parent context as they occur.\n * The callback return value should indicate success (`true`) or failure\n * (`false`). If the callback returns `true`, `intake_sync` will be called.\n */\nvoid intake_event_callback(subcontext *sub, void *priv,\n boolean (*event_cb)(void *priv, subcontext *sub, enum intake_event_type type,\n int old_pos, int new_pos, int value, const char *data))\n{\n  struct intake_subcontext *intk = (struct intake_subcontext *)sub;\n  if(intk)\n  {\n    intk->event_cb = event_cb;\n    intk->event_priv = priv;\n  }\n}\n"
  },
  {
    "path": "src/intake.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2018-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __INTAKE_H\n#define __INTAKE_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"core.h\"\n\nenum intake_exit_type\n{\n  INTK_EXIT_ENTER,\n  INTK_EXIT_ENTER_ESC,\n  INTK_EXIT_ANY\n};\n\nenum intake_event_type\n{\n  INTK_NO_EVENT,\n  INTK_MOVE,            /* Cursor moved within the intake line. */\n  INTK_MOVE_WORDS,      /* Cursor moved a number of words within the intake line. */\n  INTK_INSERT,          /* Text inserted. */\n  INTK_OVERWRITE,       /* Text overwritten. */\n  INTK_DELETE,          /* Text deleted with Delete. */\n  INTK_BACKSPACE,       /* Text deleted with Backspace. */\n  INTK_BACKSPACE_WORDS, /* Text deleted with Ctrl+Backspace (value=#words). */\n  INTK_CLEAR,           /* Text deleted with Alt+Backspace. */\n  INTK_INSERT_BLOCK,    /* Text block inserted via intake_input_string. */\n  INTK_OVERWRITE_BLOCK, /* Text block overwritten via intake_input_string. */\n};\n\nCORE_LIBSPEC boolean intake_get_insert(void);\nCORE_LIBSPEC void intake_set_insert(boolean new_insert_state);\n\nCORE_LIBSPEC int intake(struct world *mzx_world, char *string, int max_len, int display_len,\n int x, int y, char color, enum intake_exit_type exit_type, boolean mask_colors,\n int *return_x_pos);\n\nCORE_LIBSPEC subcontext *intake2(context *parent, char *dest, int max_length,\n int *pos_external, int *length_external);\n\nCORE_LIBSPEC void intake_sync(subcontext *intk);\nCORE_LIBSPEC const char *intake_input_string(subcontext *intk, const char *src,\n int linebreak_char);\nCORE_LIBSPEC void intake_event_callback(subcontext *intk, void *priv,\n boolean (*event_cb)(void *priv, subcontext *sub, enum intake_event_type type,\n int old_pos, int new_pos, int value, const char *data));\n\nCORE_LIBSPEC boolean intake_apply_event_fixed(subcontext *sub,\n enum intake_event_type type, int new_pos, int value, const char *data);\n\n__M_END_DECLS\n\n#endif // __INTAKE_H\n\n"
  },
  {
    "path": "src/intake_num.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <math.h>\n#include <stdint.h>\n#include <string.h>\n\n#include \"core.h\"\n#include \"event.h\"\n#include \"graphics.h\"\n#include \"intake_num.h\"\n#include \"util.h\"\n#include \"window.h\"\n\n\nstruct intake_num_context\n{\n  context ctx;\n  int x;\n  int y;\n  int w;\n  int color;\n  int value;\n  int min_val;\n  int max_val;\n  boolean leading_minus;\n  boolean empty;\n  boolean save;\n  context *callback_ctx;\n  void (*callback_fn)(context *, int);\n};\n\n/**\n * Change the input number and bound it.\n */\n\nstatic void change_value(struct intake_num_context *intk, int value)\n{\n  if(intk->leading_minus && value)\n    value *= -1;\n\n  intk->value = CLAMP(value, intk->min_val, intk->max_val);\n  intk->empty = false;\n  intk->leading_minus = false;\n}\n\n/**\n * Expand draw area to encompass maximum/minimum value if necessary.\n * The minus is drawn on the left border char, so abs() is used to ignore it.\n */\n\nstatic void fix_draw_area(struct intake_num_context *intk)\n{\n  char buffer[12];\n  int buf_len;\n\n  snprintf(buffer, 12, \"%d\", abs(intk->max_val));\n  buf_len = strlen(buffer);\n  if(buf_len > intk->w)\n    intk->w = buf_len;\n\n  snprintf(buffer, 12, \"%d\", abs(intk->min_val));\n  buf_len = strlen(buffer);\n  if(buf_len > intk->w)\n    intk->w = buf_len;\n}\n\n/**\n * Draw the number input to the screen.\n */\n\nstatic boolean intake_num_draw(context *ctx)\n{\n  struct intake_num_context *intk = (struct intake_num_context *)ctx;\n  int write_pos = intk->x + intk->w + 1;\n  char buffer[12] = { 0 };\n  int i;\n\n  snprintf(buffer, 12, \"%d\", intk->value);\n  write_pos -= strlen(buffer);\n\n  for(i = 0; i < intk->w + 2; i++)\n    draw_char(0, intk->color, intk->x + i, intk->y);\n\n  if(intk->leading_minus)\n    draw_char('-', intk->color, write_pos, intk->y);\n\n  if(!intk->empty)\n    write_string(buffer, write_pos, intk->y, intk->color, false);\n\n  return true;\n}\n\n/**\n * Keyhandler for number input.\n */\n\nstatic boolean intake_num_key(context *ctx, int *key)\n{\n  struct intake_num_context *intk = (struct intake_num_context *)ctx;\n  int increment_value = 0;\n\n  if(get_exit_status())\n  {\n    *key = IKEY_ESCAPE;\n  }\n  else\n\n  if(has_unicode_input())\n  {\n    boolean modified = false;\n    int num_read = 0;\n\n    while(num_read < KEY_UNICODE_MAX)\n    {\n      uint32_t unicode = get_key(keycode_text_ascii);\n      num_read++;\n      if(unicode < '0' || unicode > '9')\n        continue;\n\n      modified = true;\n\n      // At exactly maximum/minimum, typing a number should wrap\n      if((intk->value == intk->min_val && intk->min_val < 0)\n       || (intk->value == intk->max_val && intk->max_val > 0))\n      {\n        change_value(intk, 0);\n        break;\n      }\n\n      else\n      {\n        int add_value = (unicode - '0');\n\n        if(intk->value < 0)\n          add_value *= -1;\n\n        change_value(intk, intk->value * 10 + add_value);\n      }\n    }\n    if(modified)\n      return true;\n  }\n\n  switch(*key)\n  {\n    case IKEY_ESCAPE:\n    {\n      // Exit\n      intk->save = false;\n      destroy_context(ctx);\n      return true;\n    }\n\n    case IKEY_RETURN:\n    case IKEY_TAB:\n    {\n      // Set component and exit\n      destroy_context(ctx);\n      return true;\n    }\n\n    case IKEY_DELETE:\n    case IKEY_PERIOD:\n    {\n      // Set to zero and exit\n      change_value(intk, 0);\n      destroy_context(ctx);\n      return true;\n    }\n\n    case IKEY_BACKSPACE:\n    {\n      int new_value = intk->value / 10;\n\n      if(intk->leading_minus)\n      {\n        intk->leading_minus = false;\n      }\n      else\n\n      if(new_value != 0)\n      {\n        change_value(intk, new_value);\n      }\n\n      else\n      {\n        if(intk->value < 0)\n          intk->leading_minus = true;\n\n        intk->value = 0;\n        intk->empty = true;\n      }\n\n      return true;\n    }\n\n    case IKEY_MINUS:\n    {\n      if(intk->min_val < 0)\n      {\n        // Add/remove leading '-'\n        if(intk->empty || intk->value == 0)\n        {\n          intk->empty = 1;\n          intk->leading_minus ^= 1;\n        }\n        else\n\n        // Negate the existing number\n        if(-intk->value >= intk->min_val && -intk->value <= intk->max_val)\n        {\n          change_value(intk, intk->value * -1);\n        }\n      }\n      return true;\n    }\n\n    case IKEY_HOME:\n    {\n      intk->value = intk->min_val;\n      return true;\n    }\n\n    case IKEY_END:\n    {\n      intk->value = intk->max_val;\n      return true;\n    }\n\n    case IKEY_RIGHT:\n    case IKEY_UP:\n    {\n      if(get_alt_status(keycode_internal) || get_ctrl_status(keycode_internal))\n      {\n        increment_value = 10;\n      }\n      else\n      {\n        increment_value = 1;\n      }\n\n      break;\n    }\n\n    case IKEY_PAGEUP:\n    {\n      increment_value = 100;\n      break;\n    }\n\n    case IKEY_LEFT:\n    case IKEY_DOWN:\n    {\n      if(get_alt_status(keycode_internal) || get_ctrl_status(keycode_internal))\n      {\n        increment_value = -10;\n      }\n      else\n      {\n        increment_value = -1;\n      }\n\n      break;\n    }\n\n    case IKEY_PAGEDOWN:\n    {\n      increment_value = -100;\n      break;\n    }\n  }\n\n  if(increment_value)\n  {\n    change_value(intk, intk->value + increment_value);\n    return true;\n  }\n\n  return false;\n}\n\n/**\n * New mouse clicks exit and save the new value.\n */\n\nstatic boolean intake_num_click(context *ctx, int *key, int button,\n int x, int y)\n{\n  if(button && !get_mouse_drag())\n  {\n    destroy_context(ctx);\n    return true;\n  }\n  return false;\n}\n\n/**\n * On exit, get the final value and execute the callback if necessary.\n */\n\nstatic void intake_num_destroy(context *ctx)\n{\n  struct intake_num_context *intk = (struct intake_num_context *)ctx;\n\n  if(intk->save)\n  {\n    int value = CLAMP(intk->value, intk->min_val, intk->max_val);\n\n    if(intk->callback_fn)\n      intk->callback_fn(intk->callback_ctx, value);\n  }\n}\n\n/**\n * Create a context for number entry. On exit if the number was changed, the\n * callback will be executed using the parent context and the final value as\n * inputs.\n */\n\ncontext *intake_num(context *parent, int value, int min_val, int max_val,\n int x, int y, int min_width, int color, void (*callback)(context *, int))\n{\n  struct intake_num_context *intk = cmalloc(sizeof(struct intake_num_context));\n  struct context_spec spec;\n\n  intk->x = x;\n  intk->y = y;\n  intk->w = min_width;\n  intk->color = color;\n\n  intk->value = value;\n  intk->min_val = min_val;\n  intk->max_val = max_val;\n  intk->leading_minus = false;\n  intk->empty = false;\n  intk->save = true;\n\n  intk->callback_ctx = parent;\n  intk->callback_fn = callback;\n\n  fix_draw_area(intk);\n\n  memset(&spec, 0, sizeof(struct context_spec));\n  spec.draw     = intake_num_draw;\n  spec.key      = intake_num_key;\n  spec.click    = intake_num_click;\n  spec.destroy  = intake_num_destroy;\n\n  create_context((context *)intk, parent, &spec, CTX_INTAKE_NUM);\n  return (context *)intk;\n}\n"
  },
  {
    "path": "src/intake_num.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __INTAKE_NUM_H\n#define __INTAKE_NUM_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"core.h\"\n\n/**\n * Create a context for number entry. On exit if the number was changed, the\n * callback will be executed using the parent context and the final value as\n * inputs.\n *\n * @param parent    Calling context/context to receive result.\n * @param value     Initial number value.\n * @param min_val   Minimum possible value.\n * @param max_val   Maximum possible value.\n * @param x         X position to display the number onscreen.\n * @param y         Y position to display the number onscreen.\n * @param min_width Minimum width of the number display.\n * @param color     Color of the number display.\n * @param callback  Function to be called after a number is entered.\n * @return          The new intake_num context.\n */\n\nCORE_LIBSPEC\ncontext *intake_num(context *parent, int value, int min_val, int max_val,\n int x, int y, int min_width, int color, void (*callback)(context *, int));\n\n__M_END_DECLS\n\n#endif // __INTAKE_NUM_H\n"
  },
  {
    "path": "src/io/bitstream.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __IO_BITSTREAM_H\n#define __IO_BITSTREAM_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <inttypes.h>\n#include <stdio.h>\n\n#include \"../platform_endian.h\"\n\n/**\n * Most zip compression methods require treating the input as a stream of\n * bits rather than bytes.\n */\n\n#if ARCHITECTURE_BITS >= 64\n#define BS_CAPACITY 64\ntypedef uint64_t BS_BUFTYPE;\n#else\n#define BS_CAPACITY 32\ntypedef uint32_t BS_BUFTYPE;\n#endif\n\nstruct bitstream\n{\n  BS_BUFTYPE buf;\n  BS_BUFTYPE buf_left;\n  const uint8_t *input;\n  size_t input_left;\n};\n\nstatic inline boolean bs_fill(struct bitstream *b)\n{\n  if(b->input_left)\n  {\n#if PLATFORM_BYTE_ORDER == PLATFORM_LIL_ENDIAN\n    size_t align = (size_t)b->input % ALIGN_64_MODULO;\n\n#if ARCHITECTURE_BITS >= 64\n    if(!align && b->buf_left == 0 && b->input_left >= 8)\n    {\n      b->buf = ((BS_BUFTYPE)*((uint64_t *)b->input));\n      b->buf_left += 64;\n      b->input += 8;\n      b->input_left -= 8;\n    }\n    else\n#endif\n    if(!(align % ALIGN_32_MODULO) && b->buf_left <= (BS_CAPACITY - 32) && b->input_left >= 4)\n    {\n      b->buf |= ((BS_BUFTYPE)*((uint32_t *)b->input)) << b->buf_left;\n      b->buf_left += 32;\n      b->input += 4;\n      b->input_left -= 4;\n    }\n    else\n\n    if(!(align % ALIGN_16_MODULO) && b->buf_left <= (BS_CAPACITY - 16) && b->input_left >= 2)\n    {\n      b->buf |= ((BS_BUFTYPE)*((uint16_t *)b->input)) << b->buf_left;\n      b->buf_left += 16;\n      b->input += 2;\n      b->input_left -= 2;\n    }\n    else\n#endif\n    while(b->buf_left <= (BS_CAPACITY - 8) && b->input_left)\n    {\n      b->buf |= ((BS_BUFTYPE)*(b->input)) << b->buf_left;\n      b->buf_left += 8;\n      b->input++;\n      b->input_left--;\n    }\n\n    if(!b->input_left)\n      b->input = NULL;\n\n    return true;\n  }\n  return false;\n}\n\nstatic inline int bs_read(struct bitstream *b, BS_BUFTYPE mask, BS_BUFTYPE bits)\n{\n  BS_BUFTYPE tmp;\n  if(b->buf_left < bits)\n    if(!bs_fill(b) || b->buf_left < bits)\n      return EOF;\n\n  tmp = (b->buf & mask);\n  b->buf >>= bits;\n  b->buf_left -= bits;\n  return tmp;\n}\n\n#define BS_READ(b,bits) bs_read(b, (0xFFFF >> (16 - bits)), bits)\n\n__M_END_DECLS\n\n#endif /* __IO_BITSTREAM_H */\n"
  },
  {
    "path": "src/io/fsafeopen.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2005 Alistair Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2020-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n#include <ctype.h>\n#include <sys/stat.h>\n\n#include \"fsafeopen.h\"\n#include \"path.h\"\n#include \"vio.h\"\n\n#include \"../util.h\"\n\n#if defined(CONFIG_3DS)\n// Enable hacks to reduce the number of filesystem operations on platforms\n// with extremely slow filesystem access.\n#define SLOW_FILESYSTEM_HACKS\n#endif\n\n/* As of Windows 7, short filenames can be disabled per-volume.\n * In Windows 8+ they are disabled by default except on the boot volume.\n * Github CI Windows runners now disable them too, so case5 must be enabled.\n */\n//#ifndef _WIN32\n#define ENABLE_DOS_COMPAT_TRANSLATIONS\n//#endif\n\n#ifdef ENABLE_DOS_COMPAT_TRANSLATIONS\n#if defined(SLOW_FILESYSTEM_HACKS) || defined(_WIN32) || defined(CONFIG_DJGPP)\n// Skip the extra all caps/lower case checks, either because the current\n// platform will always be case-insensitive, or because they are slower than\n// just scanning the directory.\n#define NO_EXTRA_CASE_CHECKS\n#endif\n\nenum sfn_type\n{\n  NOT_AN_SFN,\n  SFN,\n  SFN_TRUNCATED,\n};\n\n#define SFN_BUFFER_LEN 13\n\n/**\n * Determine if a given character is a valid SFN character.\n * NOTE: ~ will return false for this for the purposes of detecting truncated\n * SFNs. Spaces also cause this function to return false, as though they were\n * technically valid in the SFN, they were stripped in practice. Conversely,\n * lowercase letters will be treated as valid even though they technically\n * are not as these strings are going to be compared case-insensitively anyway.\n *\n * Valid SFN chars:     Space ! # $ % & ' ( ) - 0-9 @ A-Z ^ _ ` { } ~ \\x80-\\xFF\n * Invalid SFN chars:   \\x00-\\x1F a-z \" * + , . / : ; < = > ? [ \\ ] | \\x7F\n *\n * https://en.wikipedia.org/wiki/8.3_filename\n *\n * @param  chr  Character to test.\n * @return      True if the character is a valid SFN character, otherwise false.\n */\nstatic boolean is_sfn_char(unsigned char chr)\n{\n  static const unsigned char sfn_chars[256] =\n  {\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x1\n    0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, // 0x2\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 0x3\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x4\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 0x5\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x6\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, // 0x7\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x8\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x9\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xA\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xB\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xC\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xD\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xE\n    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xF\n  };\n  return sfn_chars[chr];\n}\n\n/**\n * Determine if an input filename is a valid SFN.\n *\n * @param  filename   Filename to check.\n * @param  check_len  Length of filename to check.\n * @return            \"NOT_AN_SFN\" if not an SFN; \"SFN\" if an SFN;\n *                    or \"SFN_TRUNCATED\" if an SFN in the format \"NAME~1.EXT\".\n */\nstatic enum sfn_type is_sfn(const char *filename, size_t check_len)\n{\n  boolean is_truncated = false;\n  size_t i;\n\n  if(check_len < SFN_BUFFER_LEN)\n  {\n    for(i = 0; i < check_len; i++)\n    {\n      if(!is_sfn_char(filename[i]))\n      {\n        if(i <= 6 && filename[i] == '~')\n        {\n          size_t tilde_pos = i;\n          i++;\n          while(isdigit((unsigned char)filename[i]))\n            i++;\n\n          if(i > tilde_pos + 1 && (filename[i] == '.' || filename[i] == '\\0'))\n            is_truncated = true;\n        }\n        break;\n      }\n    }\n\n    // The filename portion must be no longer than 8 chars...\n    if(i > 8)\n      return NOT_AN_SFN;\n\n    if(i < check_len)\n    {\n      // These are the only valid terminators for the filename portion.\n      if(filename[i] != '.' && filename[i] != '\\0')\n        return NOT_AN_SFN;\n\n      if(filename[i++] == '.')\n      {\n        size_t j;\n        for(j = i; j < check_len; j++)\n          if(!is_sfn_char(filename[j]))\n            break;\n\n        // The extension portion must be no longer than 3 chars and must not\n        // be terminated before the end of the string...\n        if(j < check_len || (j - i) > 3)\n          return NOT_AN_SFN;\n      }\n    }\n    return is_truncated ? SFN_TRUNCATED : SFN;\n  }\n  return NOT_AN_SFN;\n}\n\n/**\n * Translate a given filename to an SFN. The provided buffer must be long\n * enough to hold an SFN. If the given filename is already an SFN, the filename\n * pointer will be returned; otherwise, the buffer pointer will be returned.\n *\n * @param  dest         Destination buffer.\n * @param  buffer_len   Length of destination buffer.\n * @param  filename     Filename to translate to an SFN.\n * @return              filename if already an SFN; dest on a successful\n *                      translation; NULL if an error occured.\n */\nstatic const char *get_sfn(char *dest, size_t buffer_len, const char *filename)\n{\n  size_t len = strlen(filename);\n  ssize_t _ext_pos = path_get_ext_offset(filename);\n  size_t ext_pos = _ext_pos >= 0 ? (size_t)_ext_pos : len;\n  size_t i;\n  size_t j;\n\n  if(is_sfn(filename, len))\n    return filename;\n\n  if(buffer_len < SFN_BUFFER_LEN)\n    return NULL;\n\n  // 1) Copy the first 6 valid chars of the filename.\n  // Spaces and periods before the extension position should be stripped.\n  // Other invalid characters should be replaced with an underscore.\n  for(i = 0, j = 0; i < ext_pos && j < 6; i++)\n  {\n    char c = filename[i];\n\n    if(!is_sfn_char(c))\n    {\n      if(c == ' ' || c == '.')\n        continue;\n\n      dest[j++] = '_';\n    }\n    else\n      dest[j++] = c;\n  }\n\n  // 2) Append ~1.\n  dest[j++] = '~';\n  dest[j++] = '1';\n\n  // 3) If there's an extension, place one period and the first 3 valid\n  // characters of the extension. In the case of a trailing period, just\n  // skip it.\n  if(ext_pos + 1 < len)\n  {\n    size_t ext_max = j + 4;\n    dest[j++] = '.';\n\n    for(i = ext_pos + 1; i < len && j < ext_max; i++)\n    {\n      char c = filename[i];\n\n      if(!is_sfn_char(c))\n      {\n        if(c == ' ')\n          continue;\n\n        dest[j++] = '_';\n      }\n      else\n        dest[j++] = c;\n    }\n  }\n  dest[j] = '\\0';\n  return dest;\n}\n\n// convert to lowercase\n\nstatic void case1(char *string)\n{\n  int i, len = strlen(string);\n\n  // lowercase it\n  for(i = 0; i < len; i++)\n    string[i] = tolower((int)string[i]);\n}\n\n// convert to uppercase\n\nstatic void case2(char *string)\n{\n  int i, len = strlen(string);\n\n  // uppercase it\n  for(i = 0; i < len; i++)\n    string[i] = toupper((int)string[i]);\n}\n\n// convert from anything to filename.EXT\n\nstatic void case3(char *string)\n{\n  int i, len = strlen(string);\n\n  // upper case extension\n  for(i = len; i > 0; i--)\n  {\n    string[i] = toupper((int)string[i]);\n\n    // last separator\n    if(string[i] == '.')\n      break;\n  }\n\n  // lowercase rest\n  for(i--; i >= 0; i--)\n    string[i] = tolower((int)string[i]);\n}\n\n// convert from anything to FILENAME.ext\n\nstatic void case4(char *string)\n{\n  int i, len = strlen(string);\n\n  // lowercase extension\n  for(i = len; i > 0; i--)\n  {\n    string[i] = tolower((int)string[i]);\n\n    // last separator\n    if(string[i] == '.')\n      break;\n  }\n\n  // uppercase rest\n  for(i--; i >= 0; i--)\n    string[i] = toupper((int)string[i]);\n}\n\n// brute force method; returns -1 if no permutation can be found to work\n\nstatic int case5(char *path, size_t buffer_len, char *string, boolean check_sfn)\n{\n  int ret = -FSAFE_BRUTE_FORCE_FAILED;\n  int dirlen = string - path;\n  vdir *wd;\n  char *newpath;\n\n  newpath = cmalloc(MAX_PATH);\n\n  // prepend the working directory\n  snprintf(newpath, MAX_PATH, \"./\");\n\n  // copy everything sans last token\n  if(dirlen > 0)\n  {\n    if(dirlen + 2 >= MAX_PATH)\n      dirlen = MAX_PATH - 2;\n    memcpy(newpath + 2, path, dirlen - 1);\n    newpath[dirlen + 2 - 1] = 0;\n  }\n\n  wd = vdir_open_ext(newpath, VDIR_FAST);\n  if(wd)\n  {\n    const char *string_cmp = string;\n    char string_sfn[SFN_BUFFER_LEN];\n    char newpath_sfn[SFN_BUFFER_LEN];\n    boolean has_sfn_match = false;\n    enum sfn_type type;\n\n    // If the input path is a truncated SFN, it may need to be aggressively\n    // checked against generated SFNs from files in the directory.\n    type = is_sfn(string, strlen(string));\n    if(check_sfn && type == SFN_TRUNCATED)\n    {\n      memcpy(string_sfn, string, strlen(string) + 1);\n      string_cmp = string_sfn;\n    }\n\n#ifdef CONFIG_DJGPP\n    if(type == NOT_AN_SFN)\n    {\n      get_sfn(string_sfn, SFN_BUFFER_LEN, string);\n    }\n#endif\n\n    while(vdir_read(wd, newpath, MAX_PATH, NULL))\n    {\n      // okay, we got something, but does it match?\n      if(strcasecmp(string_cmp, newpath) == 0)\n      {\n        memcpy(string, newpath, strlen(newpath) + 1);\n        ret = FSAFE_SUCCESS;\n        break;\n      }\n      else\n\n#ifdef CONFIG_DJGPP\n      // Is there a file matching the generated SFN for the requested LFN?\n      if(type == NOT_AN_SFN)\n      {\n        if(!strcasecmp(string_sfn, newpath))\n        {\n          trace(\"%s:%d: truncated LFN '%s' to '%s'\\n\",\n           __FILE__, __LINE__, string, newpath);\n          memcpy(string, newpath, strlen(newpath) + 1);\n          ret = FSAFE_SUCCESS;\n          break;\n        }\n      }\n      else\n#endif\n\n      if(type == SFN_TRUNCATED)\n      {\n        const char *newpath_cmp = get_sfn(newpath_sfn, SFN_BUFFER_LEN, newpath);\n        if(!strcasecmp(string_cmp, newpath_cmp))\n        {\n          size_t newpath_len = strlen(newpath);\n\n          // If there are duplicate SFN matches, there is no unambiguous\n          // result and thus it is not possible to guarantee a correct match.\n          if(has_sfn_match)\n          {\n            trace(\"%s:%d: ambiguous match for SFN '%s' to '%s', aborting.\\n\",\n             __FILE__, __LINE__, string_sfn, newpath);\n            memcpy(string, string_sfn, strlen(string_sfn) + 1);\n            ret = -FSAFE_BRUTE_FORCE_SFN_AMBIGUOUS;\n            break;\n          }\n          else\n\n          // Make sure the SFN expansion won't overflow the buffer.\n          if(newpath_len + dirlen + 1 > buffer_len)\n          {\n            trace(\"%s:%d: expansion for SFN '%s' to '%s' would overflow buffer,\"\n             \" aborting.\\n\", __FILE__, __LINE__, string_sfn, newpath);\n            ret = -FSAFE_BRUTE_FORCE_SFN_OVERFLOW;\n            break;\n          }\n          else\n          {\n            // Overwrite the old path with the expanded match, then continue\n            // searching the directory for duplicate matches or an exact match.\n            memcpy(string, newpath, newpath_len + 1);\n            trace(\"%s:%d: expanded SFN '%s' to '%s'\\n\",\n             __FILE__, __LINE__, string_sfn, newpath);\n            has_sfn_match = true;\n            ret = FSAFE_SUCCESS;\n          }\n        }\n      }\n    }\n\n    vdir_close(wd);\n  }\n\n  free(newpath);\n  return ret;\n}\n\nstatic int match(char *path, size_t buffer_len)\n{\n  char *nexttoken = path;\n  char *token = NULL;\n  struct stat inode;\n  int i;\n\n  if(path == NULL)\n    return -FSAFE_MATCH_FAILED;\n\n  /* FOUR likely permutations on files, and two likely permutations on\n   * directories before we start having to do anything fancy:\n   *\n   * filename.ext FILENAME.EXT filename.EXT FILENAME.ext\n   * directory    DIRECTORY\n   */\n\n  while(1)\n  {\n    {\n      token = path_tokenize(&nexttoken);\n\n      // this token is the file\n      if(nexttoken == NULL)\n      {\n        for(i = 0; i < 5; i++)\n        {\n          // check file\n          if(vstat(path, &inode) == 0)\n            break;\n\n#ifdef NO_EXTRA_CASE_CHECKS\n          // Skip the \"normal\" cases.\n          i = 4;\n#endif\n\n          // try normal cases, then try brute force\n          switch(i)\n          {\n            case 0:\n              case1(token);\n              break;\n\n            case 1:\n              case2(token);\n              break;\n\n            case 2:\n              case3(token);\n              break;\n\n            case 3:\n              case4(token);\n              break;\n\n            default:\n              // try brute force\n              if(case5(path, buffer_len, token, true) < 0)\n              {\n                trace(\"%s:%d: file matches for %s failed.\\n\",\n                 __FILE__, __LINE__, path);\n                return -FSAFE_MATCH_FAILED;\n              }\n          }\n        }\n        break;\n      }\n\n      for(i = 0; i < 3; i++)\n      {\n        // check directory\n        if(vstat(path, &inode) == 0)\n          break;\n\n#ifdef NO_EXTRA_CASE_CHECKS\n        // Skip the \"normal\" cases.\n        i = 2;\n#endif\n\n        // try normal cases, then try brute force\n        switch(i)\n        {\n          case 0:\n            case1(token);\n            break;\n\n          case 1:\n            case2(token);\n            break;\n\n          default:\n            // try brute force\n            if(case5(path, buffer_len, token, false) < 0)\n            {\n              trace(\"%s:%d: directory matches for %s failed.\\n\",\n               __FILE__, __LINE__, path);\n              return -FSAFE_MATCH_FAILED;\n            }\n        }\n      }\n\n      /* this \"hack\" overwrites the token's \\0 to re-formulate\n       * the string versus path_tokenize(); it has the nice side-effect of\n       * also converting windows style path to UNIX ones, so they'll\n       * work on everything.\n       */\n      token[strlen(token)] = '/';\n    }\n  }\n\n  return FSAFE_SUCCESS;\n}\n#endif /* ENABLE_DOS_COMPAT_TRANSLATIONS */\n\n/* OK before we do anything, we need to make some security checks. MZX games\n * shouldn't be able to open C:\\Windows\\Explorer.exe and overwrite it, so\n * we need to filter out any absolute pathnames, or relative pathnames\n * including \"..\". Do so here.\n */\n\nstatic int fsafetest(const char *path, char *newpath, size_t buffer_len,\n boolean safety_checks)\n{\n  enum path_safe_mask ret;\n\n  // we don't accept it if it's NULL or too short\n  if((path == NULL) || (path[0] == 0))\n    return -FSAFE_INVALID_ARGUMENT;\n\n  path_clean_copy_posixdos(newpath, buffer_len, path);\n#ifdef CONFIG_AMIGA\n  /* TODO: this should be done in the clean functions, but for compatibility,\n   * right now only do it on the platforms where it is required (Amiga). */\n  path_clean_current_tokens(newpath, buffer_len);\n#endif\n\n  if(!safety_checks)\n    return FSAFE_SUCCESS;\n\n  ret = path_safety_check(newpath, PATH_SAFE_ANY_ROOT | PATH_SAFE_UNIX_PARENT |\n                                   PATH_SAFE_DOS_CHARACTER | PATH_SAFE_DOS_DEVICE);\n\n  if(ret == PATH_SAFE_UNIX_ROOT)\n    return -FSAFE_ABSOLUTE_PATH_ERROR;\n\n  if(ret == PATH_SAFE_DOS_ROOT)\n    return -FSAFE_WINDOWS_DRIVE_LETTER_ERROR;\n\n  if(ret == PATH_SAFE_UNIX_PARENT)\n    return -FSAFE_PARENT_DIRECTORY_ERROR;\n\n  if(ret == PATH_SAFE_DOS_CHARACTER)\n    return -FSAFE_DOS_RESERVED_CHARACTER_ERROR;\n\n  if(ret == PATH_SAFE_DOS_DEVICE)\n    return -FSAFE_DOS_DEVICE_NAME_ERROR;\n\n  return FSAFE_SUCCESS;\n}\n\nint fsafetranslate(const char *path, char *newpath, size_t buffer_len)\n{\n  struct stat file_info;\n  int ret;\n\n  // try to pass the basic security tests\n  ret = fsafetest(path, newpath, buffer_len, true);\n  if(ret == FSAFE_SUCCESS)\n  {\n    // see if file is already there\n    if(vstat(newpath, &file_info) != 0)\n    {\n#ifdef ENABLE_DOS_COMPAT_TRANSLATIONS\n      // it isn't, so try harder..\n      ret = match(newpath, buffer_len);\n      if(ret == FSAFE_SUCCESS)\n      {\n        // ..and update the stat information for the new path\n        if(vstat(newpath, &file_info) != 0)\n          ret = -FSAFE_MATCH_FAILED;\n      }\n      else\n      {\n        // Replace the failed match with the original user-supplied path\n        fsafetest(path, newpath, buffer_len, false);\n      }\n#else\n      // on WIN32 we can't, so fail hard\n      ret = -FSAFE_MATCH_FAILED;\n#endif\n    }\n\n    if(ret == FSAFE_SUCCESS)\n    {\n      // most callers don't want directories\n      if(S_ISDIR(file_info.st_mode))\n        ret = -FSAFE_MATCHED_DIRECTORY;\n    }\n  }\n\n#ifdef ENABLE_DOS_COMPAT_TRANSLATIONS\n  if(ret == -FSAFE_SUCCESS || ret == -FSAFE_MATCHED_DIRECTORY)\n  {\n    trace(\"%s:%d: translated %s to %s%s.\\n\", __FILE__, __LINE__,\n     path, newpath, (ret == -FSAFE_MATCHED_DIRECTORY) ? \"/\" : \"\");\n  }\n  else\n  {\n    trace(\"%s:%d: failed to translate %s (err %d).\\n\",\n     __FILE__, __LINE__, path, ret);\n  }\n#endif\n\n  return ret;\n}\n\nvfile *fsafeopen(const char *path, const char *mode)\n{\n  char *newpath;\n  int i, ret;\n  vfile *f;\n\n  newpath = cmalloc(MAX_PATH);\n\n  // validate pathname, and optionally retrieve a better name\n  ret = fsafetranslate(path, newpath, MAX_PATH);\n\n  // filename couldn't be \"found\", but there were no security issues\n  if(ret == -FSAFE_MATCH_FAILED)\n  {\n    // if we're reading, any failure to translate the name means we back out\n    for(i = 0; i < (int)strlen(mode); i++)\n    {\n      if(mode[i] == 'r' || mode[i] == '+')\n      {\n        free(newpath);\n        return NULL;\n      }\n    }\n  }\n  else\n\n  if(ret < 0)\n  {\n    // bad name, or security checks failed\n    free(newpath);\n    return NULL;\n  }\n\n  // _TRY_ opening the file\n  f = vfopen_unsafe(newpath, mode);\n  free(newpath);\n  return f;\n}\n\n/* It's conceivable that on some platforms (like Linux, or Macintosh classic),\n * fgets may return a string that still contains \"EOL\" characters considered\n * by another platform. For example, if a file is written out by Windows,\n * and a Linux user reads it, the buffers will not remove the \\r character.\n *\n * This function provides a \"safe\" wrapper that removes all kinds of line\n * endings from the buffer, and should work at least until somebody invents\n * a new three byte string terminator ;-(\n */\n/*\nchar *fsafegets(char *s, int size, FILE *stream)\n{\n  char *ret = fgets(s, size, stream);\n\n  if(ret)\n  {\n    size_t len = strlen(ret);\n\n    if(len > 0)\n      if(s[len - 1] == '\\r' || s[len - 1] == '\\n')\n        s[len - 1] = '\\0';\n\n    if(len > 1)\n      if(s[len - 2] == '\\r' || s[len - 2] == '\\n')\n        s[len - 2] = '\\0';\n  }\n\n  return ret;\n}\n*/\n"
  },
  {
    "path": "src/io/fsafeopen.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Alistair Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __IO_FSAFEOPEN_H\n#define __IO_FSAFEOPEN_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"vfile.h\"\n\nenum\n{\n  FSAFE_SUCCESS = 0,\n  FSAFE_MATCHED_DIRECTORY,\n  FSAFE_MATCH_FAILED,\n  FSAFE_BRUTE_FORCE_FAILED,\n  FSAFE_BRUTE_FORCE_SFN_AMBIGUOUS,\n  FSAFE_BRUTE_FORCE_SFN_OVERFLOW,\n  FSAFE_INVALID_ARGUMENT,\n  FSAFE_ABSOLUTE_PATH_ERROR,\n  FSAFE_WINDOWS_DRIVE_LETTER_ERROR,\n  FSAFE_PARENT_DIRECTORY_ERROR,\n  FSAFE_DOS_RESERVED_CHARACTER_ERROR,\n  FSAFE_DOS_DEVICE_NAME_ERROR\n};\n\nint fsafetranslate(const char *path, char *newpath, size_t buffer_len);\nvfile *fsafeopen(const char *path, const char *mode);\n\n__M_END_DECLS\n\n#endif // __IO_FSAFEOPEN_H\n"
  },
  {
    "path": "src/io/memfile.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __IO_MEMFILE_H\n#define __IO_MEMFILE_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <assert.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nstruct memfile\n{\n  unsigned char *current;\n  unsigned char *start;\n  unsigned char *end;\n  boolean alloc;\n  /* Generally an error on seeking past the end of memfiles is desired but for\n   * wrappers with safety checks (vfile.c), it is useful to seek past the end.\n   */\n  boolean seek_past_end;\n  boolean is_write;\n};\n\n/**\n * Open a memory buffer for reading.\n */\nstatic inline void mfopen(const void *src, size_t len, struct memfile *mf)\n{\n  mf->start = (unsigned char *)src;\n  mf->current = (unsigned char *)src;\n  mf->end = (unsigned char *)src + len;\n  mf->alloc = false;\n  mf->seek_past_end = false;\n  mf->is_write = false;\n}\n\n/**\n * Open a memory buffer for writing.\n */\nstatic inline void mfopen_wr(void *dest, size_t len, struct memfile *mf)\n{\n  mf->start = (unsigned char *)dest;\n  mf->current = mf->start;\n  mf->end = mf->start + len;\n  mf->alloc = false;\n  mf->seek_past_end = false;\n  mf->is_write = true;\n}\n\n/**\n * This version exists to be a drop-in alternative for fopen().\n * For general purposes, use mfopen() instead.\n */\nstatic inline struct memfile *mfopen_alloc(const void *src, size_t len)\n{\n  struct memfile *mf = (struct memfile *)cmalloc(sizeof(struct memfile));\n\n  mf->start = (unsigned char *)src;\n  mf->current = (unsigned char *)src;\n  mf->end = (unsigned char *)src + len;\n  mf->alloc = true;\n  mf->seek_past_end = false;\n  mf->is_write = false;\n  return mf;\n}\n\n/**\n * This function is intended to be a drop-in fclose() alternative for\n * use with mfopen_alloc().\n */\nstatic inline int mf_alloc_free(struct memfile *mf)\n{\n  if(mf && mf->alloc)\n  {\n    free(mf);\n    return 0;\n  }\n  return -1;\n}\n\n/**\n * Copy the buffer position and length to external variables.\n */\nstatic inline void mfsync(void **buf, size_t *len, struct memfile *mf)\n{\n  if(buf) *buf = mf->start;\n  if(len) *len = mf->end - mf->start;\n}\n\n/**\n * Determine if the memfile has at least len space remaining.\n */\nstatic inline boolean mfhasspace(size_t len, struct memfile *mf)\n{\n  return mf->current && mf->current <= mf->end ? (size_t)(mf->end - mf->current) >= len : false;\n}\n\n/**\n * Move the buffer of a memfile while preserving its current position.\n */\nstatic inline void mfmove(void *new_buf, size_t new_len, struct memfile *mf)\n{\n  size_t pos = mf->current - mf->start;\n\n  mf->start = (unsigned char *)new_buf;\n  mf->current = mf->start + pos;\n  mf->end = mf->start + new_len;\n\n  if(mf->current > mf->end)\n    mf->current = mf->end;\n}\n\n/**\n * Resize the memfile buffer and preserve the current position.\n * Do not use this function unless the memfile buffer is on the heap.\n */\nstatic inline void mfresize(size_t new_len, struct memfile *mf)\n{\n  void *new_buf = realloc(mf->start, new_len);\n  if(new_buf)\n    mfmove(new_buf, new_len, mf);\n}\n\n/**\n * The mfget/mfput functions assume bounding has already been performed by\n * the caller to reduce the amount of code inlined and to improve performance.\n * If full bounds checks on arbitrary data are needed, use vio instead.\n * Bounds check asserts are provided to help debug for debug builds.\n */\nstatic inline int mfgetc(struct memfile *mf)\n{\n  int v;\n  assert(mf->end - mf->current >= 1);\n  v =  mf->current[0];\n  mf->current += 1;\n  return v;\n}\n\nstatic inline int mfgetw(struct memfile *mf)\n{\n  int v;\n  assert(mf->end - mf->current >= 2);\n  v =  mf->current[0];\n  v |= mf->current[1] << 8;\n  mf->current += 2;\n  return v;\n}\n\nstatic inline int mfgetd(struct memfile *mf)\n{\n  int v;\n  assert(mf->end - mf->current >= 4);\n  v =  mf->current[0];\n  v |= mf->current[1] << 8;\n  v |= mf->current[2] << 16;\n  v |= (unsigned int)mf->current[3] << 24;\n  mf->current += 4;\n  return v;\n}\n\nstatic inline unsigned int mfgetud(struct memfile *mf)\n{\n  return (unsigned int)mfgetd(mf);\n}\n\nstatic inline int64_t mfgetq(struct memfile *mf)\n{\n  int64_t v;\n  assert(mf->end - mf->current >= 8);\n  v =  (int64_t)mf->current[0];\n  v |= (int64_t)mf->current[1] << 8;\n  v |= (int64_t)mf->current[2] << 16;\n  v |= (int64_t)mf->current[3] << 24;\n  v |= (int64_t)mf->current[4] << 32;\n  v |= (int64_t)mf->current[5] << 40;\n  v |= (int64_t)mf->current[6] << 48;\n  v |= (int64_t)mf->current[7] << 56;\n  mf->current += 8;\n  return v;\n}\n\nstatic inline uint64_t mfgetuq(struct memfile *mf)\n{\n  return (uint64_t)mfgetq(mf);\n}\n\nstatic inline int mfputc(int ch, struct memfile *mf)\n{\n  assert(mf->is_write && mf->end - mf->current >= 1);\n  mf->current[0] = ch & 0xFF;\n  mf->current += 1;\n  return ch & 0xFF;\n}\n\nstatic inline void mfputw(int ch, struct memfile *mf)\n{\n  assert(mf->is_write && mf->end - mf->current >= 2);\n  mf->current[0] = ch & 0xFF;\n  mf->current[1] = (ch >> 8) & 0xFF;\n  mf->current += 2;\n}\n\nstatic inline void mfputd(int ch, struct memfile *mf)\n{\n  assert(mf->is_write && mf->end - mf->current >= 4);\n  mf->current[0] = ch & 0xFF;\n  mf->current[1] = (ch >> 8) & 0xFF;\n  mf->current[2] = (ch >> 16) & 0xFF;\n  mf->current[3] = (ch >> 24) & 0xFF;\n  mf->current += 4;\n}\n\nstatic inline void mfputud(size_t ch, struct memfile *mf)\n{\n  mfputd((unsigned int)ch, mf);\n}\n\nstatic inline void mfputq(int64_t v, struct memfile *mf)\n{\n  assert(mf->is_write && mf->end - mf->current >= 8);\n  mf->current[0] = v & 0xFF;\n  mf->current[1] = (v >> 8) & 0xFF;\n  mf->current[2] = (v >> 16) & 0xFF;\n  mf->current[3] = (v >> 24) & 0xFF;\n  mf->current[4] = (v >> 32) & 0xFF;\n  mf->current[5] = (v >> 40) & 0xFF;\n  mf->current[6] = (v >> 48) & 0xFF;\n  mf->current[7] = (v >> 56) & 0xFF;\n  mf->current += 8;\n}\n\nstatic inline void mfputuq(uint64_t v, struct memfile *mf)\n{\n  mfputq((int64_t)v, mf);\n}\n\nstatic inline size_t mfread(void *dest, size_t len, size_t count,\n struct memfile *mf)\n{\n  unsigned char *pos = (unsigned char *)dest;\n  size_t total = len * count;\n\n  if(!mf->current || len == 0 || count == 0)\n    return 0;\n\n  if(!mfhasspace(total, mf))\n  {\n    if(mf->current >= mf->end)\n      return 0;\n\n    count = (mf->end - mf->current) / len;\n    total = len * count;\n  }\n\n  memcpy(pos, mf->current, total);\n  mf->current += total;\n  return count;\n}\n\nstatic inline size_t mfwrite(const void *src, size_t len, size_t count,\n struct memfile *mf)\n{\n  unsigned char *pos = (unsigned char *)src;\n  size_t total = len * count;\n\n  if(!mf->current || len == 0 || count == 0 || !mf->is_write)\n    return 0;\n\n  if(!mfhasspace(total, mf))\n  {\n    if(mf->current >= mf->end)\n      return 0;\n\n    count = (mf->end - mf->current) / len;\n    total = len * count;\n  }\n\n  memcpy(mf->current, pos, total);\n  mf->current += total;\n  return count;\n}\n\n/**\n * Read a line from memory, safely trimming platform-specific EOL chars\n * as-needed.\n *\n * NOTE: this only works with Unix and Windows line ends right now. If support\n * for Mac OS <=9 is ever added the loop should terminate at \\r instead of \\n.\n */\nstatic inline char *mfsafegets(char *dest, int len, struct memfile *mf)\n{\n  unsigned char *stop = mf->current + len - 1;\n  unsigned char *in = mf->current;\n  unsigned char ch;\n  char *out = dest;\n\n  if(mf->end < stop)\n    stop = mf->end;\n\n  // Return NULL if this is the end of the file.\n  if(in >= stop)\n    return NULL;\n\n  // Copy until the end/bound or a newline.\n  while(in < stop && (ch = *(in++)) != '\\n')\n    *(out++) = ch;\n\n  *out = 0;\n\n  // Seek to the next line (or the end of the file).\n  mf->current = in;\n\n  // Length at least 1 -- get rid of \\r and \\n\n  if(out > dest)\n    if(out[-1] == '\\r' || out[-1] == '\\n')\n      out[-1] = 0;\n\n  // Length at least 2 -- get rid of \\r and \\n\n  if(out - 1 > dest)\n    if(out[-2] == '\\r' || out[-2] == '\\n')\n      out[-2] = 0;\n\n  return dest;\n}\n\nstatic inline int mfseek(struct memfile *mf, ptrdiff_t offs, int code)\n{\n  unsigned char *ptr;\n  ptrdiff_t pos;\n\n  switch(code)\n  {\n    case SEEK_SET:\n      pos = offs;\n      break;\n\n    case SEEK_CUR:\n      pos = (mf->current - mf->start) + offs;\n      break;\n\n    case SEEK_END:\n      pos = (mf->end - mf->start) + offs;\n      break;\n\n    default:\n      pos = -1;\n      break;\n  }\n\n  // pos >= 0 doesn't necessarily imply ptr >= start due to overflow.\n  ptr = mf->start + pos;\n  if(pos >= 0 && ptr >= mf->start && (mf->seek_past_end || ptr <= mf->end))\n  {\n    mf->current = mf->start + pos;\n    return 0;\n  }\n\n  return -1;\n}\n\nstatic inline ptrdiff_t mftell(struct memfile *mf)\n{\n  assert(mf->current >= mf->start);\n  return mf->current - mf->start;\n}\n\n__M_END_DECLS\n\n#endif // __IO_MEMFILE_H\n"
  },
  {
    "path": "src/io/path.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2012, 2020-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <errno.h>\n#include <string.h>\n#include <sys/stat.h>\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#include \"../util.h\"\n#include \"path.h\"\n#include \"vio.h\"\n\n/**\n * Tokenize a null terminated path string. This is a special implementation\n * of `strsep` for paths.\n *\n * Note this function currently isn't aware of absolute paths, so \"C:/a\" will\n * tokenize to \"C:\" followed by \"a\". If this behavior isn't desired, use the\n * return value of `path_is_absolute` before tokenizing to skip the root. This\n * function also does not skip duplicate slashes or trailing slashes\n * (see `path_clean`).\n *\n * @param next  pointer to the current position in the path string.\n *              The value will be updated to the location of the next token or\n *              to `NULL` if there are no further tokens.\n * @return      the initial value of `next` if it represents a token (i.e. is\n *              not `NULL`), otherwise `NULL`.\n */\nchar *path_tokenize(char **next)\n{\n  char *src = *next;\n  if(src)\n  {\n    char *current = strpbrk(src, \"\\\\/\");\n    if(current)\n    {\n      *current = '\\0';\n      *next = current + 1;\n    }\n    else\n      *next = NULL;\n  }\n  return src;\n}\n\n/**\n * Similar to `path_tokenize`, but strips one path token off of the end of the\n * provided path.\n *\n * This function is not aware of absolute paths; if the token returned is a\n * root, the directory separator portion of the root will be replaced with nul.\n *\n * @param  base       Pointer to the path to reverse tokenize. If there is only\n *                    one token in this path, NULL will be stored to *`base`.\n *                    Otherwise, this value will not be modified.\n * @param  base_len   Pointer to the current length of the path pointed to by\n *                    `base`. If there is only one token in the path, 0 will be\n *                    stored to *`base_len`. This field MUST be initialized\n *                    properly if provided. If not provided, each call will\n *                    perform one `strlen` call.\n * @return            A pointer to the token removed from *`base`, or NULL if\n *                    *`base` is NULL or `base` is NULL.\n */\nchar *path_reverse_tokenize(char **_base, size_t *_base_len)\n{\n  size_t base_len;\n  char *base;\n  char *pos;\n  if(!_base || !*_base)\n    return NULL;\n\n  base = *_base;\n  base_len = _base_len ? *_base_len : strlen(base);\n  pos = base + base_len;\n\n  while(pos >= base)\n  {\n    if(isslash(*pos))\n    {\n      if(_base_len)\n        *_base_len = (pos > base) ? pos - base - 1 : 0;\n\n      *pos = '\\0';\n      return pos + 1;\n    }\n    pos--;\n  }\n\n  *_base = NULL;\n  if(_base_len)\n    *_base_len = 0;\n  return base;\n}\n\n/**\n * Force a given filename path to use the provided file extension. If the\n * filename already has the given extension, the string will not be modified.\n *\n * @param  path         Path to force the extension of.\n * @param  buffer_len   Size of the buffer of the path.\n * @param  ext          Extension to add to the path.\n * @return              true on success, otherwise false.\n */\nboolean path_force_ext(char *path, size_t buffer_len, const char *ext)\n{\n  size_t path_len = strlen(path);\n  size_t ext_len = strlen(ext);\n\n  if((path_len < ext_len) || (path[path_len - ext_len] != '.') ||\n   strcasecmp(path + path_len - ext_len, ext))\n  {\n    if(path_len + ext_len >= buffer_len)\n      return false;\n\n    snprintf(path + path_len, MAX_PATH - path_len, \"%s\", ext);\n    path[buffer_len - 1] = '\\0';\n  }\n  return true;\n}\n\n/**\n * Get the position of the extension in a filename path.\n *\n * @param  path   Path to get the extension position of.\n * @return        The index of the start of the extension or -1 if not found.\n */\nssize_t path_get_ext_offset(const char *path)\n{\n  ssize_t path_len = strlen(path);\n  ssize_t root_len = path_is_absolute(path);\n  ssize_t ext_pos;\n\n  for(ext_pos = path_len - 1; ext_pos >= root_len; ext_pos--)\n  {\n    // Don't let this detect an \"extension\" of a directory!\n    if(isslash(path[ext_pos]))\n      break;\n\n    if(path[ext_pos] == '.')\n      return ext_pos;\n  }\n  return -1;\n}\n\n/**\n * Get the location of the first char of the filename in a filename path.\n * If there is no filename, the returned index will be the location of the\n * null terminator. If the string is empty, -1 will be returned.\n */\nstatic ssize_t path_get_filename_offset(const char *path)\n{\n  struct stat stat_info;\n  ssize_t root_len;\n  ssize_t pos;\n  if(!path || !path[0])\n    return -1;\n\n  // If this path stats and happens to be a directory without a terminating\n  // slash, the filename offset is the end of the string...\n  if(vstat(path, &stat_info) >= 0 && S_ISDIR(stat_info.st_mode))\n    return strlen(path);\n\n  // Otherwise, find the last directory separator (if any).\n  pos = (ssize_t)strlen(path) - 1;\n  root_len = path_is_absolute(path);\n  while(pos >= root_len)\n  {\n    if(isslash(path[pos]))\n      return pos + 1;\n\n    pos--;\n  }\n  return root_len;\n}\n\n/**\n * Determine if the given path is an absolute path.\n *\n * @param  path   Path to test.\n * @return        length of root token if this is an absolute path, otherwise 0.\n */\nssize_t path_is_absolute(const char *path)\n{\n  size_t len;\n  size_t i;\n\n#ifdef PATH_UNC_ROOTS\n  // Windows unified DOS and UNC roots.\n  len = strlen(path);\n  if(len > 2 && isslash(path[0]) && isslash(path[1]) && !isslash(path[2]))\n  {\n    i = 3;\n    // UNC roots may be prefixed with \\\\.\\UNC\\, try to catch that.\n    if((path[2] == '.'|| path[2] == '?') && isslash(path[3])\n     && !strncasecmp(path + 4, \"unc\", 3) && isslash(path[7]))\n    {\n      i = 8;\n    }\n\n    // Host name or . or ?\n    for(; i < len; i++)\n      if(isslash(path[i]))\n        break;\n\n    if(i + 1 < len && !isslash(path[i + 1]))\n    {\n      // Share or root name.\n      for(i += 1; i < len; i++)\n        if(isslash(path[i]))\n          break;\n\n      if(isslash(path[i]))\n        return i + 1;\n\n      return i;\n    }\n  }\n#endif /* UNC roots */\n\n#ifdef PATH_UNIX_STYLE_ROOTS\n  // Unix-style root.\n  if(isslash(path[0]))\n    return 1;\n#endif\n\n  // DOS-style or Amiga-style root.\n#ifndef PATH_UNC_ROOTS\n  len = strlen(path);\n#endif\n  for(i = 0; i < len; i++)\n  {\n#ifndef PATH_AMIGA_STYLE_ROOTS\n\n    if(isslash(path[i]))\n      break;\n\n    if(path[i] == ':')\n    {\n      if(i == 0)\n        break;\n\n      i++;\n#ifdef PATH_DOS_STYLE_ROOTS\n      // True DOS-style roots do not require a trailing slash.\n      if(path[i] == '\\0')\n        return i;\n#endif\n\n      if(isslash(path[i]))\n      {\n        while(isslash(path[i]))\n          i++;\n#ifndef PATH_DOS_STYLE_ROOTS\n        // Allow DOS-style roots in Linux only with >=2 slashes.\n        if(i >= 2 && isslash(path[i - 2]))\n#endif\n        return i;\n      }\n      break;\n    }\n\n#else /* PATH_AMIGA_STYLE_ROOTS */\n\n    /* Amiga: root ends at the first ':'.\n     * If ':' is the first character, it's an absolute path on the current\n     * disk. The root can include slashes! :(\n     */\n    if(path[i] == ':')\n    {\n      i++;\n\n      /* Include any following slashes (they will get cleaned). */\n      while(isslash(path[i]))\n        i++;\n\n      return i;\n    }\n\n#endif\n  }\n  return 0;\n}\n\n/**\n * Determine if the given path is an absolute path, using DOS-style roots rules.\n *\n * @param  path   Path to test.\n * @return        length of root token if this is an absolute path, otherwise 0.\n */\nssize_t path_is_absolute_dos(const char *path)\n{\n  size_t len;\n  size_t i;\n\n  // Unix-style root.\n  if(isslash(path[0]))\n    return 1;\n\n  // DOS-style root.\n  len = strlen(path);\n  for(i = 0; i < len; i++)\n  {\n    if(isslash(path[i]))\n      break;\n\n    if(path[i] == ':')\n    {\n      if(i == 0)\n        break;\n\n      i++;\n      // True DOS-style roots do not require a trailing slash.\n      if(path[i] == '\\0')\n        return i;\n\n      if(isslash(path[i]))\n      {\n        while(isslash(path[i]))\n          i++;\n        return i;\n      }\n      break;\n    }\n  }\n  return 0;\n}\n\n/**\n * Determine if the given path is a root path.\n *\n * @param  path   Path to test.\n * @return        `true` if the path is a root path, otherwise `false`.\n */\nboolean path_is_root(const char *path)\n{\n  ssize_t root_len = path_is_absolute(path);\n  return root_len && !path[root_len];\n}\n\n/**\n * Determine if the given path contains a directory.\n *\n * @param  path   Path to test.\n * @return        True if the path contains a directory, otherwise false.\n */\nboolean path_has_directory(const char *path)\n{\n  struct stat stat_info;\n  size_t len;\n  size_t i;\n\n  if(!path || !path[0])\n    return false;\n\n  // Check for slashes first to avoid a stat call...\n  len = strlen(path);\n  for(i = 0; i < len; i++)\n    if(isslash(path[i]))\n      return true;\n\n  return vstat(path, &stat_info) >= 0 && S_ISDIR(stat_info.st_mode);\n}\n\n/**\n * Truncate a path to its directory portion, if any.\n *\n * @param  path         Path to truncate.\n * @param  buffer_len   Size of the path buffer.\n * @return              The length of the directory string, or -1 on error.\n */\nssize_t path_to_directory(char *path, size_t buffer_len)\n{\n  ssize_t filename_pos = path_get_filename_offset(path);\n\n  // Invalid path.\n  if(filename_pos < 0 || (size_t)filename_pos >= buffer_len)\n  {\n    if(buffer_len > 0)\n      path[0] = '\\0';\n\n    return -1;\n  }\n\n  path[filename_pos] = '\\0';\n  if(filename_pos > 0)\n    filename_pos = path_clean(path, buffer_len);\n\n  return filename_pos;\n}\n\n/**\n * Remove the directory portion of a filename path, if any.\n *\n * @param  path         Filename path to remove the directory portion of.\n * @param  buffer_len   Size of the path buffer.\n * @return              The length of the filename string, or -1 on error.\n */\nssize_t path_to_filename(char *path, size_t buffer_len)\n{\n  ssize_t filename_pos = path_get_filename_offset(path);\n  size_t path_len = strlen(path);\n  size_t filename_len;\n\n  // Invalid path.\n  if(filename_pos < 0 || path_len - (size_t)filename_pos >= buffer_len)\n  {\n    if(buffer_len > 0)\n      path[0] = '\\0';\n\n    return -1;\n  }\n\n  // If there isn't a directory prefix, don't modify the buffer...\n  if(filename_pos == 0)\n    return path_len;\n\n  filename_len = path_len - filename_pos;\n  if(filename_len > 0)\n    memmove(path, path + filename_pos, filename_len);\n  path[filename_len] = '\\0';\n\n  return filename_len;\n}\n\n/**\n * Copy the directory portion of a filename or directory path to the\n * destination buffer.\n *\n * @param  dest       Destination buffer for the directory.\n * @param  dest_len   Size of the destination buffer.\n * @param  path       Path to get the directory of.\n * @return            The length of the directory string, or -1 on error.\n */\nssize_t path_get_directory(char *dest, size_t dest_len, const char *path)\n{\n  ssize_t filename_pos = path_get_filename_offset(path);\n\n  // Invalid path, or it's too long to store.\n  if(filename_pos < 0 || (size_t)filename_pos >= dest_len)\n  {\n    if(dest_len > 0)\n      dest[0] = '\\0';\n\n    return -1;\n  }\n\n  dest[filename_pos] = '\\0';\n  if(filename_pos > 0)\n  {\n    memcpy(dest, path, filename_pos);\n    filename_pos = path_clean(dest, dest_len);\n  }\n  return filename_pos;\n}\n\n/**\n * Copy the filename portion of a filename path to the destination buffer.\n *\n * @param  dest       Destination buffer for the filename.\n * @param  dest_len   Size of the destination buffer.\n * @param  path       Path to get the filename of.\n * @return            The length of the filename string, or -1 on error.\n */\nssize_t path_get_filename(char *dest, size_t dest_len, const char *path)\n{\n  ssize_t filename_pos = path_get_filename_offset(path);\n  size_t path_len = strlen(path);\n  size_t filename_len;\n\n  // Invalid path, or it's too long to store.\n  if(filename_pos < 0 || path_len - (size_t)filename_pos >= dest_len)\n  {\n    if(dest_len > 0)\n      dest[0] = '\\0';\n\n    return -1;\n  }\n\n  filename_len = path_len - filename_pos;\n  dest[filename_len] = '\\0';\n  if(filename_len > 0)\n    memcpy(dest, path + filename_pos, filename_len);\n\n  return filename_len;\n}\n\n/**\n * Copy both the directory and the filename portions of a filename path to\n * individual destination buffers.\n *\n * @param  d_dest   Destination buffer for the directory.\n * @param  d_len    Size of the directory destination buffer.\n * @param  f_dest   Destination buffer for the filename.\n * @param  f_len    Size of the filename destination buffer.\n * @param  path     Path to get the directory and filename of.\n * @return          true on success, otherwise false.\n */\nboolean path_get_directory_and_filename(char *d_dest, size_t d_len,\n char *f_dest, size_t f_len, const char *path)\n{\n  ssize_t filename_pos = path_get_filename_offset(path);\n  size_t path_len = strlen(path);\n  size_t filename_len;\n\n  // Invalid path, or one of the components is too long to store.\n  if(filename_pos < 0 || (size_t)filename_pos >= d_len ||\n   path_len - (size_t)filename_pos >= f_len)\n  {\n    if(d_len > 0)\n      d_dest[0] = '\\0';\n    if(f_len > 0)\n      f_dest[0] = '\\0';\n\n    return false;\n  }\n\n  d_dest[filename_pos] = '\\0';\n  if(filename_pos > 0)\n  {\n    memcpy(d_dest, path, filename_pos);\n    path_clean(d_dest, d_len);\n  }\n\n  filename_len = path_len - filename_pos;\n  f_dest[filename_len] = '\\0';\n  if(filename_len > 0)\n    memcpy(f_dest, path + filename_pos, filename_len);\n\n  return true;\n}\n\n/**\n * Get the parent directory of a provided path string if the path string\n * contains a parent component.\n *\n * @param  dest       Destination buffer for the parent.\n * @param  dest_len   Size of the destination buffer.\n * @return            Length of the parent string, -1 if the path string is\n *                    invalid or doesn't fit in the provided buffer, or 0\n *                    if the path contains no parent directory.\n */\nssize_t path_get_parent(char *dest, size_t dest_len, const char *path)\n{\n  ssize_t path_len;\n  ssize_t root_len;\n  ssize_t i;\n  if(!path || !path[0])\n    return -1;\n\n  // Find last slash without respect to vstat.\n  root_len = path_is_absolute(path);\n  path_len = strlen(path);\n  for(i = path_len - 1; i >= root_len; i--)\n    if(isslash(path[i]))\n      break;\n  // Include the separator.\n  i++;\n\n  if((size_t)i >= dest_len)\n    return -1;\n\n  dest[i] = '\\0';\n  if(i > 0)\n  {\n    memcpy(dest, path, i);\n    i = path_clean(dest, dest_len);\n  }\n  return i;\n}\n\n/**\n * Clean a path according to the current platform, namely:\n * - All: normalize slashes to this platform's directory separator.\n * - POSIX, DOS/Win32: remove extra directory separators and ending separators.\n * - Amiga: remove end-of-root directory separators, single ending separator.\n *\n * @param  path       Path to clean slashes of.\n * @param  path_len   Size of the path buffer.\n * @return            The length of the destination path.\n */\nsize_t path_clean(char *path, size_t path_len)\n{\n  boolean need_copy = false;\n  size_t root_len;\n  size_t i = 0;\n  size_t j = 0;\n\n  root_len = path_is_absolute(path);\n\n#ifdef PATH_UNC_ROOTS\n  // UNC roots should retain two slashes at the start.\n  if(root_len >= 2 && isslash(path[0]) && isslash(path[1]))\n  {\n    path[0] = path[1] = DIR_SEPARATOR_CHAR;\n    i = j = 1; // Merge >2 slashes into the second one.\n  }\n#endif\n\n#ifdef PATH_AMIGA_STYLE_ROOTS\n  /* Strip root trailing slashes, do not normalize internal root slashes. */\n  i = j = root_len;\n  if(root_len && isslash(path[j - 1]))\n  {\n    while(j > 0 && isslash(path[j - 1]))\n      j--;\n\n    need_copy = true;\n  }\n#elif !defined(PATH_DOS_STYLE_ROOTS)\n  // Non-native DOS-style double slash roots should retain two slashes.\n  if(root_len >= 4 && !isslash(path[0]))\n  {\n    while(root_len >= 3 && path[root_len - 3] != ':')\n      root_len--;\n    path[root_len - 2] = path[root_len - 1] = DIR_SEPARATOR_CHAR;\n    i = j = root_len - 1; // Merge >2 slashes into the second slash.\n  }\n#endif\n\n  while((i < path_len) && path[i])\n  {\n    if(isslash(path[i]))\n    {\n#ifndef PATH_AMIGA_STYLE_ROOTS\n      while(isslash(path[i]))\n        i++;\n\n      if(i > j + 1)\n        need_copy = true;\n#else\n      /* Can't merge slashes on Amiga. */\n      i++;\n#endif\n\n      path[j++] = DIR_SEPARATOR_CHAR;\n    }\n    else\n    {\n      if(need_copy)\n        path[j] = path[i];\n\n      i++;\n      j++;\n    }\n  }\n  path[j] = '\\0';\n\n  /* Trim trailing slashes unless they are a component of the root.\n   * Amiga can only do this if there is exactly one trailing slash. */\n  if(j >= 2 && j > root_len && path[j - 1] == DIR_SEPARATOR_CHAR)\n#ifdef PATH_AMIGA_STYLE_ROOTS\n    if(path[j - 2] != DIR_SEPARATOR_CHAR)\n#endif\n    path[--j] = '\\0';\n\n  return j;\n}\n\n/**\n * Create a duplicate of a path according to the current platform, namely:\n * - All: normalize slashes to this platform's directory separator.\n * - POSIX, DOS/Win32: remove extra directory separators and ending separators.\n * - Amiga: remove end-of-root directory separators, single ending separator.\n *\n * @param  dest       Destination buffer for the cleaned path.\n * @param  dest_len   Size of the destination buffer.\n * @param  path       Path to clean slashes of.\n * @return            The length of the destination path.\n */\nsize_t path_clean_copy(char *dest, size_t dest_len, const char *path)\n{\n  size_t path_len = strlen(path);\n  size_t root_len;\n  size_t i = 0;\n  size_t j = 0;\n\n  root_len = path_is_absolute(path);\n\n#ifdef PATH_UNC_ROOTS\n  // UNC roots should retain two slashes at the start.\n  if(root_len >= 2 && isslash(path[0]) && isslash(path[1]))\n  {\n    if(dest_len >= 2)\n      dest[j++] = DIR_SEPARATOR_CHAR;\n    i = 1; // Merge >2 slashes into the second one.\n  }\n#endif\n\n#ifdef PATH_AMIGA_STYLE_ROOTS\n  /* Strip root trailing slashes, do not normalize internal root slashes. */\n  if(root_len)\n  {\n    while(j < dest_len - 1 && i < root_len && path[i] != ':')\n      dest[j++] = path[i++];\n\n    if(j < dest_len - 1)\n      dest[j++] = ':';\n\n    i = root_len;\n  }\n#elif !defined(PATH_DOS_STYLE_ROOTS)\n  // Non-native DOS-style double slash roots should retain two slashes.\n  if(root_len >= 4 && !isslash(path[0]))\n  {\n    while(j < dest_len - 1 && i < root_len && !isslash(path[i]))\n      dest[j++] = path[i++];\n\n    i++;\n    if(j < dest_len - 1)\n      dest[j++] = DIR_SEPARATOR_CHAR;\n    // Merge >2 slashes into the second slash.\n    root_len = j + 1;\n  }\n#endif\n\n  while((i < path_len) && (j < dest_len - 1))\n  {\n    if(isslash(path[i]))\n    {\n#ifndef PATH_AMIGA_STYLE_ROOTS\n      /* Can't merge slashes on Amiga. */\n      while(isslash(path[i]))\n#endif\n        i++;\n\n      dest[j++] = DIR_SEPARATOR_CHAR;\n    }\n    else\n      dest[j++] = path[i++];\n  }\n  dest[j] = '\\0';\n\n  /* Trim trailing slashes unless they are a component of the root.\n   * Amiga can only do this if there is exactly one trailing slash. */\n  if(j >= 2 && j > root_len && dest[j - 1] == DIR_SEPARATOR_CHAR)\n#ifdef PATH_AMIGA_STYLE_ROOTS\n    if(dest[j - 2] != DIR_SEPARATOR_CHAR)\n#endif\n    dest[--j] = '\\0';\n\n  return j;\n}\n\n/**\n * Create a duplicate of a path, cleaning specifically using POSIX with DOS\n * single slash root rules:\n * - Normalize all slashes to the POSIX directory separator /.\n * - Merge duplicate slashes and trim any final trailing slashes.\n * - Do not preserve double root slashes.\n *\n * @param  dest       Destination buffer for the cleaned path.\n * @param  dest_len   Size of the destination buffer.\n * @param  path       Path to clean slashes of.\n * @return            The length of the destination path.\n */\nsize_t path_clean_copy_posixdos(char *dest, size_t dest_len, const char *path)\n{\n  size_t path_len = strlen(path);\n  size_t root_len;\n  size_t i = 0;\n  size_t j = 0;\n\n  root_len = path_is_absolute_dos(path);\n\n  while((i < path_len) && (j < dest_len - 1))\n  {\n    if(isslash(path[i]))\n    {\n      while(isslash(path[i]))\n        i++;\n\n      dest[j++] = '/';\n    }\n    else\n      dest[j++] = path[i++];\n  }\n  dest[j] = '\\0';\n\n  /* Trim trailing slashes unless they are a component of the root. */\n  if(j >= 2 && j > root_len && dest[j - 1] == '/')\n    dest[--j] = '\\0';\n\n  return j;\n}\n\n/**\n * Strip redundant POSIX/DOS/Win32 current directory tokens (\".\") out of\n * a path. TODO: this function should be merged into the path clean functions\n * in 2.94, but for now it strictly uses DOS roots rules, because it's needed\n * by Amiga to fix DOS paths provided by games.\n */\nssize_t path_clean_current_tokens(char *path, size_t path_len)\n{\n  size_t i = path_is_absolute(path);\n  size_t j = i;\n\n  while(i < path_len && path[i])\n  {\n    if(path[i] == '.' && /* TODO: i >= root_len && */\n     (j == 0 || isslash(path[j - 1])) &&\n     (path[i + 1] == '\\0' || isslash(path[i + 1])))\n    {\n      i++;\n      while(isslash(path[i]))\n        i++;\n      if(j == 0 && path[i] == '\\0')\n        path[j++] = '.';\n    }\n    else\n    {\n      if(j < i)\n        path[j] = path[i];\n      j++;\n      i++;\n    }\n  }\n  path[j] = '\\0';\n\n  return j;\n}\n\n/**\n * Append a relative directory or filename path to an existing base path.\n * This function does not handle ./, ../, etc; to resolve relative paths\n * containing those, use path_navigate() instead. On success, the destination\n * is guaranteed to have cleaned  by `path_clean`/`path_clean_copy`.\n *\n * @param  path         Existing base path.\n * @param  buffer_len   Size of the base path buffer.\n * @param  rel          Relative path to be joined to the end of the base path.\n * @return              The new length of the base path, or -1 on error.\n */\nssize_t path_append(char *path, size_t buffer_len, const char *rel)\n{\n  size_t path_len = strlen(path);\n  size_t rel_len = strlen(rel);\n\n  // Needs to be able to fit the worst case size: path + separator + rel + \\0.\n  if(path_len && rel_len && path_len + rel_len + 2 <= buffer_len)\n  {\n    path_len = path_clean(path, buffer_len);\n    if(path[path_len - 1] != DIR_SEPARATOR_CHAR)\n      path[path_len++] = DIR_SEPARATOR_CHAR;\n\n    rel_len = path_clean_copy(path + path_len, buffer_len - path_len, rel);\n    return path_len + rel_len;\n  }\n  return -1;\n}\n\n/**\n * Join a base directory path to a relative directory or filename path.\n * This function does not handle ./, ../, etc; to resolve relative paths\n * containing those, use path_navigate() instead. On success, the destination\n * is guaranteed to have cleaned  by `path_clean`/`path_clean_copy`.\n *\n * @param  dest       Destination buffer for the joined path.\n * @param  dest_len   Size of the destination buffer.\n * @param  base       Base directory path.\n * @param  rel        Relative path to be joined to the end of the base path.\n * @return            The length of the resulting path, or -1 on error.\n */\nssize_t path_join(char *dest, size_t dest_len, const char *base, const char *rel)\n{\n  size_t base_len = strlen(base);\n  size_t rel_len = strlen(rel);\n\n  // Needs to be able to fit the worst case size: base + separator + rel + \\0.\n  if(base_len && rel_len && base_len + rel_len + 2 <= dest_len)\n  {\n    base_len = path_clean_copy(dest, dest_len, base);\n    if(dest[base_len - 1] != DIR_SEPARATOR_CHAR)\n      dest[base_len++] = DIR_SEPARATOR_CHAR;\n\n    rel_len = path_clean_copy(dest + base_len, dest_len - base_len, rel);\n    return base_len + rel_len;\n  }\n  return -1;\n}\n\n/**\n * Determine if `path` is prefixed by `prefix`. Returns the index of the first\n * non-prefix and non-slash char of `path` if `path` is prefixed by `prefix`,\n * otherwise -1.\n */\nstatic ssize_t path_has_prefix(const char *path, size_t buffer_len,\n const char *prefix, size_t prefix_len)\n{\n  // Normal string compare, but allow different kinds of slashes.\n  size_t i = 0;\n  size_t j = 0;\n  while(i < prefix_len && prefix[i])\n  {\n    if(j >= buffer_len || !path[j])\n      return -1;\n\n    if(isslash(prefix[i]))\n    {\n      if(!isslash(path[j]))\n        return -1;\n\n      // Skip duplicate slashes.\n      while(isslash(prefix[i]))\n        i++;\n      while(isslash(path[j]))\n        j++;\n    }\n    else\n    {\n      if(prefix[i++] != path[j++])\n        return -1;\n    }\n  }\n\n  // Make sure this was actually a valid prefix--the prefix should either have\n  // a trailing slash or the next character of the path should be a slash.\n  if(!isslash(prefix[i - 1]) && !isslash(path[j]))\n    return -1;\n\n  // The prefix likely does not have trailing slashes, so skip them.\n  while(isslash(path[j]))\n    j++;\n\n  return j;\n}\n\n/**\n * Remove a directory prefix from a path if it exists. The prefix does not\n * need trailing slashes, but it must represent a complete directory name.\n *\n * @param  path         Path to remove a prefix from.\n * @param  buffer_len   Size of the path buffer.\n * @param  prefix       Prefixed directory to remove from path if found.\n * @param  prefix_len   Length of prefix to test or 0 to test the entire prefix.\n * @return              New length of path, or -1 if the prefix was not found.\n */\nssize_t path_remove_prefix(char *path, size_t buffer_len,\n const char *prefix, size_t prefix_len)\n{\n  prefix_len = prefix_len ? prefix_len : strlen(prefix);\n\n  if(prefix_len)\n  {\n    ssize_t offset = path_has_prefix(path, buffer_len, prefix, prefix_len);\n    if(offset < 0)\n      return -1;\n\n    return path_clean_copy(path, buffer_len, path + offset);\n  }\n  return -1;\n}\n\n/* Check a single path token for a reserved DOS device. */\nstatic boolean path_safety_check_dos_device(const char *token, size_t token_len)\n{\n  /* These names can not exist alone or followed by any extension,\n   * including multiple extensions like \".tar.gz\". */\n  static const char * const devices_num[] =\n  {\n    \"com\",\n    \"lpt\",\n  };\n  static const char * const devices[] =\n  {\n    \"aux\",\n    \"con\",\n    \"nul\",\n    \"prn\",\n  };\n  /* Numbered devices are reserved 1-9 and for Latin-1 superscripts 1-3. */\n  static const char reserved_numbers[] = \"123456789\\xb9\\xb2\\xb3\";\n\n  size_t i;\n\n  /* path_get_ext_offset gets the last extension, this needs to exclude all */\n  for(i = 0; i < token_len; i++)\n    if(token[i] == '.')\n      break;\n\n  token_len = i;\n\n  if(token_len == 3)\n  {\n    for(i = 0; i < ARRAY_SIZE(devices); i++)\n      if(!strncasecmp(token, devices[i], 3))\n        return true;\n  }\n  else\n\n  if(token_len == 4)\n  {\n    for(i = 0; i < ARRAY_SIZE(devices_num); i++)\n    {\n      if(strncasecmp(token, devices_num[i], 3))\n        continue;\n\n      if(strchr(reserved_numbers, token[3]))\n        return true;\n\n      /* Matched one device prefix, won't match another... */\n      break;\n    }\n  }\n  return false;\n}\n\n/**\n * Perform safety checks on a provided path, optionally including any of the\n * following checks. Use following a path_clean function for best results.\n * Many of these checks are deliberately overly aggressive, since the specifics\n * of what counts as a root may vary between systems.\n *\n * PATH_SAFE_UNIX_ROOT:     detect paths beginning with any slash, including\n *                          Unix and UNC roots.\n * PATH_SAFE_DOS_ROOT:      detect any path that may be mistaken for containing\n *                          a DOS or Amiga root (any path containing a colon).\n * PATH_SAFE_UNIX_PARENT:   detect paths containing Unix parent directory\n *                          tokens (..), which may break sandboxing.\n * PATH_SAFE_AMIGA_PARENT:  detect path containing adjacent slashes, which\n *                          may break sandboxing.\n * PATH_SAFE_DOS_CHARACTER: detect characters forbidden by NTFS/VFAT/exFAT,\n *                          including 1-31 and any of these: \"*:<>?|. Doesn't\n *                          exclude \\0, /, or \\ (meaningful in C-string paths),\n *                          or several characters only forbidden pre-VFAT\n *                          (existing Windows-era games rely on these).\n * PATH_SAFE_DOS_DEVICE:    detect patch containing deserved DOS/Windows\n *                          device names, which can cause hangs or worse in\n *                          DOS and Windows.\n *\n * @param path        the path to safety check.\n * @param check_mask  a mask of the above enum values for checks to perform.\n * @return            the first check in the provided check_mask that fails,\n *                    otherwise PATH_SAFE_OK.\n */\nenum path_safe_mask path_safety_check(const char *path, int check_mask)\n{\n  size_t len = strlen(path);\n  const char *pos;\n\n  if((check_mask & PATH_SAFE_UNIX_ROOT) && isslash(path[0]))\n    return PATH_SAFE_UNIX_ROOT;\n\n  if((check_mask & PATH_SAFE_DOS_ROOT) && strchr(path, ':'))\n    return PATH_SAFE_DOS_ROOT;\n\n  if(check_mask & PATH_SAFE_UNIX_PARENT)\n  {\n    pos = path;\n    while((pos = strstr(pos, \"..\")))\n    {\n      if((pos == path || isslash(pos[-1])) &&\n       (pos[2] == '\\0' || isslash(pos[2])))\n        return PATH_SAFE_UNIX_PARENT;\n\n      pos += 2;\n    }\n  }\n\n  if(check_mask & PATH_SAFE_AMIGA_PARENT)\n  {\n    pos = path;\n    while((pos = strpbrk(pos, \"/\\\\\")))\n    {\n      if(isslash(pos[1]))\n        return PATH_SAFE_AMIGA_PARENT;\n\n      pos++;\n    }\n  }\n\n  if(check_mask & PATH_SAFE_DOS_CHARACTER)\n  {\n    if(strpbrk(path,\n          \"\\x01\\x02\\x03\\x04\\x05\\x06\\x07\"\n      \"\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\"\n      \"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\"\n      \"\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\"\n      \"\\\"*:<>?|\"))\n      return PATH_SAFE_DOS_CHARACTER;\n  }\n\n  if(check_mask & PATH_SAFE_DOS_DEVICE)\n  {\n    size_t root_len = path_is_absolute(path);\n    const char *end = path + len;\n\n    /* path tokenize functions are currently destructive, so do this instead. */\n    pos = path + root_len;\n    for(;;)\n    {\n      size_t token_len;\n      const char *next = strpbrk(pos, \"/\\\\\");\n      token_len = (next ? next : end) - pos;\n\n      if(path_safety_check_dos_device(pos, token_len))\n        return PATH_SAFE_DOS_DEVICE;\n      if(!next)\n        break;\n\n      pos = next + 1;\n    }\n  }\n  return PATH_SAFE_OK;\n}\n\n/**\n * Get a descriptive error string for a path_safety_check result.\n *\n * @param   value   the return value from path_safety_check. If a mask of\n *                  multiple values is somehow passed here, the lowest value\n *                  in the set will be used.\n * @return          a statically allocated constant string describing the error.\n */\nconst char *path_safety_strerror(enum path_safe_mask value)\n{\n  int x = (int)value;\n\n  switch(x & -x)\n  {\n    case PATH_SAFE_OK:            return \"no error\";\n    case PATH_SAFE_UNIX_ROOT:     return \"POSIX/UNC absolute path\";\n    case PATH_SAFE_DOS_ROOT:      return \"DOS/Amiga absolute path\";\n    case PATH_SAFE_UNIX_PARENT:   return \"'..' parent directory token\";\n    case PATH_SAFE_AMIGA_PARENT:  return \"Amiga parent directory token\";\n    case PATH_SAFE_DOS_CHARACTER: return \"DOS/Windows reserved character\";\n    case PATH_SAFE_DOS_DEVICE:    return \"DOS/Windows reserved device\";\n    default:                      return \"unknown\";\n  }\n}\n\n/**\n * Internal common implementation for `path_navigate` and `path_navigate_no_check`.\n */\nstatic ssize_t path_navigate_internal(char *path, size_t path_len, const char *target,\n boolean allow_checks)\n{\n  struct stat stat_info;\n  char buffer[MAX_PATH];\n  const char *current;\n  const char *next;\n  const char *end;\n  char current_char;\n  size_t root_len;\n  size_t len;\n\n  if(!path || !target || !target[0])\n    return -1;\n\n  current = target;\n  end = target + strlen(target);\n\n  len = path_is_absolute(target);\n  if(len)\n  {\n    /**\n     * Destination starts with any type of root, including:\n     *\n     * 1) a Unix-style root directory e.g. / or \\.\n     * Aside from Unix-likes, these are also supported by console platforms.\n     * Even Windows (back through XP at least) doesn't seem to mind them.\n     *\n     * 2) a DOS-style root directory e.g. C: or sdcard:/.\n     * Aside from Windows, these are often used by console SDKs (albeit with /\n     * instead of \\) to distinguish SD cards and the like.\n     * Linux and macOS also allow them for VFS roots (with double slashes).\n     *\n     * 3) a Windows-style UNC root (Windows only) e.g. \\\\localhost\\share\\\n     * These are used for network folders and long paths.\n     */\n    next = target + len;\n    snprintf(buffer, MAX_PATH, \"%.*s\" DIR_SEPARATOR, (int)len, target);\n    path_clean(buffer, MAX_PATH);\n\n    if(allow_checks && vstat(buffer, &stat_info) < 0)\n    {\n      trace(\"--PATH-- path_navigate_internal: stat of root '%s' failed (%d)\\n\",\n       buffer, errno);\n      return -1;\n    }\n\n    current = next;\n    while(isslash(current[0]))\n      current++;\n  }\n  else\n  {\n    /**\n     * Destination is relative--start from the current path. Make sure there's\n     * a trailing separator.\n     */\n    len = path_clean_copy(buffer, MAX_PATH, path);\n    if(!len)\n      return -1;\n\n    if(!isslash(buffer[len - 1]) && len + 1 < MAX_PATH)\n    {\n      buffer[len++] = DIR_SEPARATOR_CHAR;\n      buffer[len] = '\\0';\n    }\n  }\n\n#ifdef PATH_DOS_STYLE_ROOTS\n  // Any attempted DOS-style root after the start is a broken path.\n  if(strchr(current, ':'))\n    return -1;\n#endif\n\n  current_char = current[0];\n  len = strlen(buffer);\n  root_len = path_is_absolute(buffer);\n\n  // Apply directory fragments to the path.\n  while(current_char != '\\0')\n  {\n    // Increment next to skip the separator so it will be copied over.\n    next = strpbrk(current, \"/\\\\\");\n    if(!next) next = end;\n    else      next++;\n\n#ifndef PATH_AMIGA_STYLE_ROOTS\n    // . does nothing, .. goes back one level\n    if(current_char == '.' && (current[1] == '\\0' || isslash(current[1])))\n    {\n      // current directory, don't do anything.\n    }\n    else\n\n    if(current_char == '.' && current[1] == '.' &&\n     (current[2] == '\\0' || isslash(current[2])))\n#else /* PATH_AMIGA_STYLE_ROOTS */\n    /* Zero-length path token -> parent directory (as Unix \"..\"). */\n    if((next - current) == 1 && isslash(current[0]))\n#endif\n    {\n      // Skip the rightmost separator (current level) and look for the\n      // previous separator. If found, truncate the path to it.\n      // Do not attempt to consume portions of the root.\n      if(len > root_len)\n      {\n        char *pos = buffer + len - 1;\n        char *end = buffer + root_len;\n        do\n        {\n          pos--;\n        }\n        while(pos >= end && !isslash(*pos));\n\n        if(pos >= buffer)\n        {\n          pos[1] = '\\0';\n          len = strlen(buffer);\n        }\n      }\n    }\n    else\n    {\n      snprintf(buffer + len, MAX_PATH - len, \"%.*s\", (int)(next - current),\n       current);\n      buffer[MAX_PATH - 1] = '\\0';\n      len = strlen(buffer);\n    }\n\n    current = next;\n    current_char = current[0];\n  }\n\n  // This needs to be done before the stat for some platforms (e.g. 3DS)\n  len = path_clean(buffer, MAX_PATH);\n  if(len < path_len)\n  {\n    trace(\"--PATH-- path_navigate_internal: '%s'\\n\", buffer);\n    if(allow_checks)\n    {\n      if(vstat(buffer, &stat_info) < 0)\n      {\n        trace(\"--PATH-- path_navigate_internal: failed stat (%d)\\n\", errno);\n        return -1;\n      }\n      if(!S_ISDIR(stat_info.st_mode))\n      {\n        trace(\"--PATH-- path_navigate_internal: not dir (%d)\\n\", stat_info.st_mode);\n        return -1;\n      }\n      if(vaccess(buffer, R_OK|X_OK) < 0)\n      {\n        trace(\"--PATH-- path_navigate_internal: failed access (%d)\\n\", errno);\n        return -1;\n      }\n    }\n    memcpy(path, buffer, len + 1);\n    path[path_len - 1] = '\\0';\n    return len;\n  }\n\n  return -1;\n}\n\n/**\n * Navigate a directory path to a target like chdir. The provided directory\n * path must be a valid directory. The target may be a relative path or an\n * absolute path in either Unix or Windows style. If \".\" or \"..\" is found in\n * the target, it will be handled appropriately. If the final resulting path\n * successfully stats, the provided path will be overwritten with the\n * destination. If the final path is not valid or if an error occurs, the\n * provided path will not be modified.\n *\n * @param  path       Directory path to navigate from.\n * @param  path_len   Size of path buffer.\n * @param  target     Target to navigate to.\n * @return            The new length of the path, or -1 on error.\n */\nssize_t path_navigate(char *path, size_t path_len, const char *target)\n{\n  return path_navigate_internal(path, path_len, target, true);\n}\n\n/**\n * Like `path_navigate`, but no `vstat` or `vaccess` call will be performed to\n * check the resulting path. See `path_navigate` for more info.\n *\n * @param  path       Directory path to navigate from.\n * @param  path_len   Size of path buffer.\n * @param  target     Target to navigate to.\n * @return            The new length of the path, or -1 on error.\n */\nssize_t path_navigate_no_check(char *path, size_t path_len, const char *target)\n{\n  return path_navigate_internal(path, path_len, target, false);\n}\n\n/**\n * Create the parent directory of a given filename if it doesn't exist\n * (similar to mkdir -p). This function will call vstat multiple times and\n * will call vmkdir to create directories as needed. This function will not\n * make recursive calls.\n *\n * @param filename    Filename to create the parent directory of.\n * @return            `0` on success or a non-zero value on error.\n *                    See `enum path_create_error`.\n */\nenum path_create_error path_create_parent_recursively(const char *filename)\n{\n  struct stat stat_info;\n  char parent_directory[MAX_PATH];\n  ssize_t pos;\n\n  ssize_t parent_len = path_get_directory(parent_directory, MAX_PATH, filename);\n  if(parent_len < 0)\n    return PATH_CREATE_ERR_BUFFER;\n\n  // No parent directory? Don't need to do anything...\n  if(parent_len == 0)\n    return PATH_CREATE_SUCCESS;\n\n  /**\n   * Step 1: walk the parent directory backwards and stat it successively until\n   * it finds something that exists. Replace slashes with nuls; they can be\n   * added back when needed again.\n   */\n  pos = parent_len;\n  do\n  {\n    parent_directory[pos] = '\\0';\n    if(vstat(parent_directory, &stat_info))\n    {\n      // Make sure the error is that the dir is missing and not something else.\n      if(errno != ENOENT)\n        return PATH_CREATE_ERR_STAT_ERROR;\n    }\n    else\n\n    /**\n     * If a file exists where a directory needs to be placed there isn't\n     * really anything else that can be done.\n     */\n    if(!S_ISDIR(stat_info.st_mode))\n    {\n      return PATH_CREATE_ERR_FILE_EXISTS;\n    }\n    else\n      break;\n\n    // Find the next slash.\n    while(pos > 0 && !isslash(parent_directory[pos]))\n      pos--;\n  }\n  while(pos > 0);\n\n  // The entire path already exists? Nothing else needs to be done...\n  if(pos == parent_len)\n    return PATH_CREATE_SUCCESS;\n\n  /**\n   * Step 2: restore slashes and mkdir until the original end of the parent\n   * directory is found.\n   */\n  while(pos < parent_len)\n  {\n    /**\n     * If pos==0, the base of the path didn't exist and needs to be created.\n     * Otherwise, look for slashes that were removed, fix them, and create\n     * the next directory.\n     */\n    if(!pos || !parent_directory[pos])\n    {\n      if(!parent_directory[pos])\n        parent_directory[pos] = DIR_SEPARATOR_CHAR;\n\n      if(vmkdir(parent_directory, 0755))\n        return PATH_CREATE_ERR_MKDIR_FAILED;\n    }\n\n    while(pos < parent_len && parent_directory[pos])\n      pos++;\n  }\n  return PATH_CREATE_SUCCESS;\n}\n"
  },
  {
    "path": "src/io/path.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2012, 2020-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __IO_PATH_H\n#define __IO_PATH_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stddef.h>\n\n// Most platforms allow / as a root.\n#define PATH_UNIX_STYLE_ROOTS\n\n#if defined(_WIN32) || defined(CONFIG_DJGPP) || defined(CONFIG_DOS_STYLE_ROOTS)\n// DOS-style roots with one slash (all platforms allow two).\n#define PATH_DOS_STYLE_ROOTS\n#endif\n\n#if defined(CONFIG_AMIGA)\n/* Amiga exists in an alternate universe where roots are delimited only\n * by the colon, and any 0-length dir component is equivalent to \"..\"\n * on everything else. This requires some pretty different handling. */\n#define PATH_AMIGA_STYLE_ROOTS\n#undef PATH_UNIX_STYLE_ROOTS\n#endif\n\n#if defined(_WIN32)\n// Windows-style UNC roots.\n#define PATH_UNC_ROOTS\n#endif\n\n#ifndef DIR_SEPARATOR\n#if defined(_WIN32) || defined(CONFIG_DJGPP)\n#define DIR_SEPARATOR \"\\\\\"\n#define DIR_SEPARATOR_CHAR '\\\\'\n#else /* !_WIN32 && !CONFIG_DJGPP */\n#define DIR_SEPARATOR \"/\"\n#define DIR_SEPARATOR_CHAR '/'\n#endif\n#endif /* DIR_SEPARATOR */\n\n/* Standalone current/parent directory string for file manager usage.\n * Amiga has a parent directory token, but not a current directory token. */\n#ifndef PATH_PARENT_DIR\n#if defined(CONFIG_AMIGA)\n#define PATH_CURRENT_DIR \"\"\n#define PATH_PARENT_DIR \"/\"\n#else\n#define PATH_CURRENT_DIR \".\"\n#define PATH_PARENT_DIR \"..\"\n#endif\n#endif /* PATH_PARENT_DIR */\n\nenum path_safe_mask\n{\n  PATH_SAFE_OK            = 0,\n  PATH_SAFE_UNIX_ROOT     = (1 << 0),       /* Path begins with a slash */\n  PATH_SAFE_DOS_ROOT      = (1 << 1),       /* Path contains : anywhere */\n  PATH_SAFE_UNIX_PARENT   = (1 << 2),       /* Path contains a . or .. token */\n  PATH_SAFE_AMIGA_PARENT  = (1 << 3),       /* Path contains adjacent slashes */\n  PATH_SAFE_DOS_CHARACTER = (1 << 4),       /* Path contains reserved char(s):\n                                             * 1-31, or any of \"*:<>?| */\n  PATH_SAFE_DOS_DEVICE    = (1 << 5),       /* Path is a reserved DOS/Windows\n                                             * device that can cause hangs. */\n\n  PATH_SAFE_ANY_ROOT      = (PATH_SAFE_UNIX_ROOT | PATH_SAFE_DOS_ROOT),\n  PATH_SAFE_ANY           = (PATH_SAFE_ANY_ROOT | PATH_SAFE_UNIX_PARENT |\n                             PATH_SAFE_AMIGA_PARENT | PATH_SAFE_DOS_CHARACTER |\n                             PATH_SAFE_DOS_DEVICE),\n};\n\nenum path_create_error\n{\n  PATH_CREATE_SUCCESS,\n  PATH_CREATE_ERR_BUFFER,\n  PATH_CREATE_ERR_STAT_ERROR,\n  PATH_CREATE_ERR_MKDIR_FAILED,\n  PATH_CREATE_ERR_FILE_EXISTS\n};\n\n/**\n * Determine if a character is a path separating slash.\n * @param  chr  Character to check.\n * @return      True if the character is a slash, otherwise false.\n */\nstatic inline boolean isslash(const char chr)\n{\n  return (chr == '\\\\') || (chr == '/');\n}\n\nUTILS_LIBSPEC char *path_tokenize(char **next);\nUTILS_LIBSPEC char *path_reverse_tokenize(char **base, size_t *base_len);\n\nUTILS_LIBSPEC boolean path_force_ext(char *path, size_t buffer_len, const char *ext);\nUTILS_LIBSPEC ssize_t path_get_ext_offset(const char *path);\n\nUTILS_LIBSPEC ssize_t path_is_absolute(const char *path);\nUTILS_LIBSPEC ssize_t path_is_absolute_dos(const char *path);\nUTILS_LIBSPEC boolean path_is_root(const char *path);\nUTILS_LIBSPEC boolean path_has_directory(const char *path);\nUTILS_LIBSPEC ssize_t path_to_directory(char *path, size_t buffer_len);\nUTILS_LIBSPEC ssize_t path_to_filename(char *path, size_t buffer_len);\nUTILS_LIBSPEC ssize_t path_get_directory(char *dest, size_t dest_len,\n const char *path);\nUTILS_LIBSPEC ssize_t path_get_filename(char *dest, size_t dest_len,\n const char *path);\nUTILS_LIBSPEC boolean path_get_directory_and_filename(char *d_dest, size_t d_len,\n char *f_dest, size_t f_len, const char *path);\nUTILS_LIBSPEC ssize_t path_get_parent(char *dest, size_t dest_len,\n const char *path);\n\nUTILS_LIBSPEC size_t path_clean(char *path, size_t path_len);\nUTILS_LIBSPEC size_t path_clean_copy(char *dest, size_t dest_len, const char *path);\nUTILS_LIBSPEC size_t path_clean_copy_posixdos(char *dest, size_t dest_len,\n const char *path);\nUTILS_LIBSPEC ssize_t path_clean_current_tokens(char *path, size_t path_len);\nUTILS_LIBSPEC ssize_t path_append(char *path, size_t buffer_len, const char *rel);\nUTILS_LIBSPEC ssize_t path_join(char *dest, size_t dest_len, const char *base,\n const char *rel);\nUTILS_LIBSPEC ssize_t path_remove_prefix(char *path, size_t buffer_len,\n const char *prefix, size_t prefix_len);\n\nUTILS_LIBSPEC enum path_safe_mask path_safety_check(const char *path, int flags);\nUTILS_LIBSPEC const char *path_safety_strerror(enum path_safe_mask value);\n\nUTILS_LIBSPEC ssize_t path_navigate(char *path, size_t path_len,\n const char *target);\nUTILS_LIBSPEC ssize_t path_navigate_no_check(char *path, size_t path_len,\n const char *target);\n\nUTILS_LIBSPEC enum path_create_error path_create_parent_recursively(\n const char *filename);\n\n__M_END_DECLS\n\n#endif /* __IO_PATH_H */\n"
  },
  {
    "path": "src/io/vfile.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Types for vio.h and memfile.h so they don't have to be included in\n * world_struct.h and other high-traffic headers.\n */\n\n#ifndef __IO_VFILE_H\n#define __IO_VFILE_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\ntypedef struct vfile vfile;\ntypedef struct vdir vdir;\ntypedef struct vvolumelist vvolumelist;\ntypedef struct vfilesystem vfilesystem;\nstruct memfile;\nstruct stat;\n\n/* Dummy device for stat on a virtual file. */\n#define VFS_MZX_DEVICE (dev_t)(('M'<<24u) | ('Z'<<16u) | ('X'<<8u) | ('V'))\n\n__M_END_DECLS\n\n#endif /* __IO_VFILE_H */\n"
  },
  {
    "path": "src/io/vfs.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2021-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <time.h>\n#include <sys/stat.h>\n\n#include \"vfs.h\"\n\n#ifdef VIRTUAL_FILESYSTEM\n\n#ifdef VIRTUAL_FILESYSTEM_PARALLEL\n#include \"../platform.h\"\n#endif\n\n#include \"../util.h\"\n#include \"path.h\"\n#include \"vio.h\"\n\n// Maximum reference count for a given VFS inode.\n#define VFS_MAX_REFCOUNT 255\n// Maximum name length.\n#define VFS_MAX_NAME UINT16_MAX\n\n#define VFS_DEFAULT_FILE_SIZE 32\n#define VFS_DEFAULT_DIR_SIZE 4\n\n#define VFS_NO_INODE 0\n#define VFS_ROOT_INODE 1\n\n#define VFS_IDX_SELF   0\n#define VFS_IDX_PARENT 1\n\n#define VFS_INODE_TYPE(n) ((n)->flags & VFS_INODE_TYPEMASK)\n#define VFS_IS_CACHED(n) ((n)->timestamp != 0)\n#define VFS_IS_INVALIDATED(n) ((n)->parent == VFS_NO_INODE)\n\nenum vfs_inode_flags\n{\n  VFS_INODE_FILE       = (1<<0),\n  VFS_INODE_DIR        = (1<<1),\n  VFS_INODE_TYPEMASK   = VFS_INODE_FILE | VFS_INODE_DIR,\n  VFS_INODE_IS_REAL    = (1<<2), // Cache of a real location in the filesystem.\n  VFS_INODE_NAME_ALLOC = (1<<7),\n};\n\nunion vfs_inode_contents\n{\n  void *has_contents;\n  uint32_t *inodes;\n  unsigned char *data;\n};\n\n/* `name` buffer is long enough to confortably fit null terminated 8.3 names. */\nstruct vfs_inode\n{\n  union vfs_inode_contents contents;\n  size_t length;\n  size_t length_alloc;\n  uint32_t timestamp;\n  int64_t create_time;\n  int64_t modify_time;\n  uint8_t flags; // 0=deleted\n  uint8_t refcount; // If 255, refuse creation of new refs.\n  uint16_t name_length;\n  uint32_t parent;\n  char name[16];\n};\n\n/* If `flags` & VFS_INODE_NAME_ALLOC, `name` is a pointer instead of a buffer.\n * `name` is still null terminated in this case. Alloc size is strlen(name)+1. */\nstruct vfs_inode_name_alloc\n{\n  union vfs_inode_contents contents;\n  size_t length;\n  size_t length_alloc;\n  uint32_t timestamp;\n  int64_t create_time;\n  int64_t modify_time;\n  uint8_t flags; // 0=deleted\n  uint8_t refcount; // If 255, refuse creation of new refs.\n  uint16_t name_length;\n  uint32_t parent;\n  char *name;\n};\n\nstruct vfilesystem\n{\n  struct vfs_inode **table;\n  uint32_t table_length;\n  uint32_t table_alloc;\n  uint32_t table_next;\n  uint32_t current;\n  uint32_t current_root;\n#ifdef VIRTUAL_FILESYSTEM_PARALLEL\n  platform_thread_id origin;\n  platform_mutex lock;\n  platform_cond cond;\n  int num_writers;\n  int num_readers;\n  int num_promotions;\n#endif\n  size_t cache_total;\n  boolean is_writer;\n  boolean disable_timestamp;\n  enum vfs_error error;\n  char current_path[MAX_PATH];\n  size_t current_path_len;\n};\n\nstatic time_t vfs_get_date(void)\n{\n  return time(NULL);\n}\n\nstatic uint32_t vfs_get_timestamp(void)\n{\n  uint32_t t = (uint32_t)vfs_get_date();\n  return t ? t : 1;\n}\n\nstatic int vfs_name_cmp(const char *a, const char *b)\n{\n#ifdef VIRTUAL_FILESYSTEM_CASE_INSENSITIVE\n  return strcasecmp(a, b);\n#else\n  return strcmp(a, b);\n#endif\n}\n\n\n/**\n * vfs_inode functions.\n */\n\nstatic boolean vfs_inode_init_name(struct vfs_inode *n, const char *name)\n{\n  size_t name_len = strlen(name);\n  if(name_len > VFS_MAX_NAME)\n    return false;\n\n  if(name_len >= sizeof(n->name))\n  {\n    struct vfs_inode_name_alloc *_n = (struct vfs_inode_name_alloc *)n;\n    char *inode_name = (char *)malloc(name_len + 1);\n    if(!inode_name)\n      return false;\n\n    _n->flags |= VFS_INODE_NAME_ALLOC;\n    _n->name = inode_name;\n    memcpy(_n->name, name, name_len + 1);\n  }\n  else\n  {\n    n->flags &= ~VFS_INODE_NAME_ALLOC;\n    memcpy(n->name, name, name_len + 1);\n  }\n\n  n->name_length = name_len;\n  return true;\n}\n\nstatic boolean vfs_inode_rename(struct vfs_inode *n, const char *name)\n{\n  if(n->flags & VFS_INODE_NAME_ALLOC)\n  {\n    struct vfs_inode_name_alloc *_n = (struct vfs_inode_name_alloc *)n;\n    char *prev_name = _n->name;\n\n    if(!vfs_inode_init_name(n, name))\n    {\n      _n->name = prev_name;\n      return false;\n    }\n    free(prev_name);\n    return true;\n  }\n  return vfs_inode_init_name(n, name);\n}\n\nstatic inline const char *vfs_inode_name(struct vfs_inode *n)\n{\n  if(n->flags & VFS_INODE_NAME_ALLOC)\n  {\n    struct vfs_inode_name_alloc *_n = (struct vfs_inode_name_alloc *)n;\n    return _n->name;\n  }\n  return n->name;\n}\n\nstatic boolean vfs_inode_init_file(struct vfs_inode *n, const char *name,\n size_t init_alloc, boolean is_real)\n{\n  if(init_alloc < VFS_DEFAULT_FILE_SIZE)\n    init_alloc = VFS_DEFAULT_FILE_SIZE;\n\n  n->contents.data = (unsigned char *)malloc(init_alloc);\n  if(!n->contents.data)\n    return false;\n\n  n->length = 0;\n  n->length_alloc = init_alloc;\n  n->timestamp = is_real ? 1 : 0;\n  n->create_time = 0;\n  n->modify_time = 0;\n  n->refcount = 0;\n  n->flags = VFS_INODE_FILE;\n  n->parent = VFS_NO_INODE;\n  if(is_real)\n    n->flags |= VFS_INODE_IS_REAL;\n\n  if(!vfs_inode_init_name(n, name))\n  {\n    free(n->contents.data);\n    n->flags = 0;\n    return false;\n  }\n  return true;\n}\n\nstatic boolean vfs_inode_init_directory(struct vfs_inode *n, const char *name,\n size_t init_alloc, boolean is_real)\n{\n  if(init_alloc < VFS_DEFAULT_DIR_SIZE)\n    init_alloc = VFS_DEFAULT_DIR_SIZE;\n\n  n->contents.inodes = (uint32_t *)malloc(init_alloc * sizeof(uint32_t));\n  if(!n->contents.inodes)\n    return false;\n\n  n->length = 2;\n  n->length_alloc = init_alloc;\n  n->timestamp = is_real ? 1 : 0;\n  n->create_time = 0;\n  n->modify_time = 0;\n  n->refcount = 0;\n  n->flags = VFS_INODE_DIR;\n  n->parent = VFS_NO_INODE;\n  if(is_real)\n    n->flags |= VFS_INODE_IS_REAL;\n\n  if(!vfs_inode_init_name(n, name))\n  {\n    free(n->contents.inodes);\n    n->flags = 0;\n    return false;\n  }\n  return true;\n}\n\nstatic void vfs_inode_clear(struct vfs_inode *n)\n{\n  if(n->flags)\n  {\n    struct vfs_inode_name_alloc *_n = (struct vfs_inode_name_alloc *)n;\n\n    if(n->contents.has_contents)\n    {\n      free(n->contents.has_contents);\n      n->contents.has_contents = NULL;\n    }\n\n    if(n->flags & VFS_INODE_NAME_ALLOC)\n    {\n      free(_n->name);\n      _n->name = NULL;\n    }\n\n    n->flags = 0;\n  }\n}\n\nstatic boolean vfs_inode_expand_directory(struct vfs_inode *n, size_t count)\n{\n  uint32_t *inodes;\n  uint64_t new_length = n->length + count;\n  uint64_t new_alloc = n->length_alloc;\n\n  if(new_length > UINT32_MAX)\n    return false;\n\n  assert(VFS_INODE_TYPE(n) == VFS_INODE_DIR);\n\n  if(n->length_alloc >= new_length)\n    return true;\n\n  while(new_length > new_alloc)\n    new_alloc <<= 1;\n\n  if(new_alloc > UINT32_MAX)\n    return false;\n\n  inodes = (uint32_t *)realloc(n->contents.inodes, new_alloc * 2 * sizeof(uint32_t));\n  if(!inodes)\n    return false;\n\n  n->contents.inodes = inodes;\n  n->length_alloc = new_alloc;\n  return true;\n}\n\nstatic boolean vfs_inode_insert_directory(struct vfs_inode *n, uint32_t inode, uint32_t pos)\n{\n  uint32_t *inodes;\n\n  assert(VFS_INODE_TYPE(n) == VFS_INODE_DIR);\n  assert(pos >= 2 && pos <= n->length);\n\n  if(n->length == UINT32_MAX)\n    return false;\n\n  inodes = n->contents.inodes;\n\n  if(pos < n->length)\n    memmove(inodes + pos + 1, inodes + pos, sizeof(uint32_t) * (n->length - pos));\n\n  inodes[pos] = inode;\n  n->length++;\n  return true;\n}\n\nstatic boolean vfs_inode_delete_directory(struct vfs_inode *n, uint32_t pos)\n{\n  uint32_t *inodes;\n\n  assert(VFS_INODE_TYPE(n) == VFS_INODE_DIR);\n  assert(pos >= 2 && pos < n->length);\n\n  inodes = n->contents.inodes;\n\n  if(pos < n->length - 1)\n    memmove(inodes + pos, inodes + pos + 1, sizeof(uint32_t) * (n->length - pos - 1));\n  n->length--;\n  return true;\n}\n\nstatic boolean vfs_inode_move_directory(struct vfs_inode *n, uint32_t old_pos,\n uint32_t new_pos)\n{\n  uint32_t *inodes;\n  uint32_t inode;\n\n  assert(VFS_INODE_TYPE(n) == VFS_INODE_DIR);\n  assert(old_pos >= 2 && old_pos < n->length);\n  assert(new_pos >= 2 && new_pos < n->length);\n\n  inodes = n->contents.inodes;\n  inode = inodes[old_pos];\n\n  if(old_pos < new_pos)\n  {\n    memmove(inodes + old_pos, inodes + old_pos + 1, sizeof(uint32_t) * (new_pos - old_pos));\n    inodes[new_pos] = inode;\n  }\n  else\n\n  if(old_pos > new_pos)\n  {\n    memmove(inodes + new_pos + 1, inodes + new_pos, sizeof(uint32_t) * (old_pos - new_pos));\n    inodes[new_pos] = inode;\n  }\n  return true;\n}\n\n\n/**\n * vfilesystem functions.\n */\n\nstatic uint32_t vfs_seterror(vfilesystem *vfs, enum vfs_error e)\n{\n  vfs->error = e;\n  // Most functions setting the error return VFS_NO_INODE,\n  // so doing so here makes things slightly cleaner.\n  return VFS_NO_INODE;\n}\n\nstatic enum vfs_error vfs_geterror(vfilesystem *vfs)\n{\n  int error = vfs->error;\n  vfs->error = VFS_ERR_UNKNOWN;\n  return error;\n}\n\nstatic boolean vfs_init_lock(vfilesystem *vfs)\n{\n#ifdef VIRTUAL_FILESYSTEM_PARALLEL\n  platform_mutex_init(&(vfs->lock));\n  platform_cond_init(&(vfs->cond));\n  vfs->origin = platform_get_thread_id();\n  vfs->num_readers = 0;\n  vfs->num_writers = 0;\n  vfs->num_promotions = 0;\n  vfs->is_writer = false;\n  return true;\n#endif\n  vfs->is_writer = true;\n  return true;\n}\n\nstatic boolean vfs_clear_lock(vfilesystem *vfs)\n{\n#ifdef VIRTUAL_FILESYSTEM_PARALLEL\n  platform_mutex_destroy(&(vfs->lock));\n  platform_cond_destroy(&(vfs->cond));\n#endif\n  return true;\n}\n\nstatic boolean vfs_read_lock(vfilesystem *vfs)\n{\n#ifdef VIRTUAL_FILESYSTEM_PARALLEL\n  if(!platform_mutex_lock(&(vfs->lock)))\n  {\n    vfs_seterror(vfs, VFS_ERR_UNKNOWN);\n    return false;\n  }\n\n  while(vfs->num_writers || vfs->is_writer)\n    platform_cond_wait(&(vfs->cond), &(vfs->lock));\n\n  vfs->num_readers++;\n\n  platform_mutex_unlock(&(vfs->lock));\n#endif\n  return true;\n}\n\nstatic boolean vfs_read_unlock(vfilesystem *vfs)\n{\n#ifdef VIRTUAL_FILESYSTEM_PARALLEL\n  if(!platform_mutex_lock(&(vfs->lock)))\n  {\n    vfs_seterror(vfs, VFS_ERR_UNKNOWN);\n    return false;\n  }\n\n  assert(vfs->num_readers > 0);\n  vfs->num_readers--;\n  if(!vfs->num_readers)\n    platform_cond_broadcast(&(vfs->cond));\n\n  platform_mutex_unlock(&(vfs->lock));\n#endif\n  return true;\n}\n\nstatic boolean vfs_write_lock(vfilesystem *vfs)\n{\n#ifdef VIRTUAL_FILESYSTEM_PARALLEL\n  if(!platform_mutex_lock(&(vfs->lock)))\n  {\n    vfs_seterror(vfs, VFS_ERR_UNKNOWN);\n    return false;\n  }\n\n  vfs->num_writers++;\n\n  while(vfs->num_readers || vfs->is_writer)\n    platform_cond_wait(&(vfs->cond), &(vfs->lock));\n\n  vfs->num_writers--;\n  vfs->is_writer = true;\n\n  platform_mutex_unlock(&(vfs->lock));\n#endif\n  return true;\n}\n\nstatic boolean vfs_write_unlock(vfilesystem *vfs)\n{\n#ifdef VIRTUAL_FILESYSTEM_PARALLEL\n  if(!platform_mutex_lock(&(vfs->lock)))\n  {\n    vfs_seterror(vfs, VFS_ERR_UNKNOWN);\n    return false;\n  }\n\n  assert(vfs->is_writer);\n  vfs->is_writer = false;\n  platform_cond_broadcast(&(vfs->cond));\n\n  platform_mutex_unlock(&(vfs->lock));\n#endif\n  return true;\n}\n\n/**\n * Promote the current thread's reader lock to a writer lock. On failure,\n * returns `false`, and the current thread will still have a reader lock.\n */\nstatic boolean vfs_elevate_lock(vfilesystem *vfs)\n{\n#ifdef VIRTUAL_FILESYSTEM_PARALLEL\n  if(!platform_mutex_lock(&(vfs->lock)))\n  {\n    vfs_seterror(vfs, VFS_ERR_UNKNOWN);\n    return false;\n  }\n\n  vfs->num_writers++;\n  vfs->num_promotions++;\n\n  while(vfs->num_readers > vfs->num_promotions || vfs->is_writer)\n    platform_cond_wait(&(vfs->cond), &(vfs->lock));\n\n  vfs->num_readers--;\n  vfs->num_writers--;\n  vfs->num_promotions--;\n  vfs->is_writer = true;\n\n  platform_mutex_unlock(&(vfs->lock));\n#endif\n  return true;\n}\n\n/**\n * Initialize the given VFS. The initialized VFS will contain one root inode\n * corresponding to C: (Windows) or / (everything else).\n */\nstatic boolean vfs_setup(vfilesystem *vfs)\n{\n  struct vfs_inode *n;\n\n  memset(vfs, 0, sizeof(vfilesystem));\n\n  vfs->table = (struct vfs_inode **)calloc(4, sizeof(struct vfs_inode *));\n  if(!vfs->table)\n    return false;\n\n  vfs->table[VFS_NO_INODE] = (struct vfs_inode *)calloc(1, sizeof(struct vfs_inode));\n  vfs->table[VFS_ROOT_INODE] = (struct vfs_inode *)calloc(1, sizeof(struct vfs_inode));\n  if(!vfs->table[VFS_NO_INODE] || !vfs->table[VFS_ROOT_INODE])\n    return false;\n\n  if(!vfs_init_lock(vfs))\n    return false;\n\n  vfs->table_length = 2;\n  vfs->table_alloc = 4;\n  vfs->table_next = 2;\n  vfs->current = VFS_ROOT_INODE;\n  vfs->current_root = VFS_ROOT_INODE;\n  vfs->disable_timestamp = false;\n  vfs_seterror(vfs, VFS_ERR_UNKNOWN);\n\n  /* 0: list of roots. */\n  n = vfs->table[VFS_NO_INODE];\n  n->contents.inodes = (uint32_t *)malloc(3 * sizeof(uint32_t));\n  if(!n->contents.inodes)\n    return false;\n\n  n->contents.inodes[VFS_IDX_SELF]   = VFS_NO_INODE;\n  n->contents.inodes[VFS_IDX_PARENT] = VFS_NO_INODE;\n  n->contents.inodes[2] = VFS_ROOT_INODE;\n  n->length = 3;\n  n->length_alloc = 3;\n  n->timestamp = 0;\n  n->flags = VFS_INODE_DIR;\n  n->refcount = 255;\n  n->parent = VFS_NO_INODE;\n  vfs_inode_init_name(n, \"\");\n\n  /* 1: / or C:\\ */\n  n = vfs->table[VFS_ROOT_INODE];\n  n->contents.inodes = (uint32_t *)malloc(4 * sizeof(uint32_t));\n  if(!n->contents.inodes)\n    return false;\n\n  n->contents.inodes[VFS_IDX_SELF]   = VFS_ROOT_INODE;\n  n->contents.inodes[VFS_IDX_PARENT] = VFS_ROOT_INODE;\n  n->length = 2;\n  n->length_alloc = 4;\n  n->timestamp = 0;\n  n->create_time = vfs_get_date();\n  n->modify_time = n->create_time;\n  n->flags = VFS_INODE_DIR;\n  n->refcount = 0;\n  n->parent = VFS_ROOT_INODE;\n#ifdef VIRTUAL_FILESYSTEM_DOS_DRIVE\n  vfs_inode_init_name(n, \"C:\\\\\");\n#else\n  vfs_inode_init_name(n, \"/\");\n#endif\n\n  strcpy(vfs->current_path, n->name);\n  vfs->current_path_len = strlen(n->name);\n  return true;\n}\n\n/**\n * Release all allocated memory for a given VFS.\n */\nstatic void vfs_clear(vfilesystem *vfs)\n{\n  size_t i;\n  for(i = 0; i < vfs->table_length; i++)\n  {\n    if(vfs->table[i])\n    {\n      vfs_inode_clear(vfs->table[i]);\n      free(vfs->table[i]);\n    }\n  }\n\n  vfs_clear_lock(vfs);\n\n  free(vfs->table);\n  vfs->table = NULL;\n}\n\n/**\n * Allocate a given inode.\n */\nstatic struct vfs_inode *vfs_allocate_inode(vfilesystem *vfs, uint32_t inode)\n{\n  vfs->table[inode] = (struct vfs_inode *)malloc(sizeof(struct vfs_inode));\n  if(vfs->table[inode])\n    vfs->table[inode]->flags = 0;\n\n  return vfs->table[inode];\n}\n\n/**\n * Get a pointer to an inode data structure from its index.\n * This will return a pointer even for cleared inodes.\n */\nstatic struct vfs_inode *vfs_get_inode_ptr(vfilesystem *vfs, uint32_t inode)\n{\n  assert(inode < vfs->table_length);\n  return vfs->table[inode];\n}\n\n/**\n * Get the next unused inode in the VFS. table_next will be advanced to the\n * position of the returned inode. This will allocate more inode space in the\n * VFS if needed.\n */\nstatic uint32_t vfs_get_next_free_inode(vfilesystem *vfs)\n{\n  while(vfs->table_next < vfs->table_length)\n  {\n    if(!vfs->table[vfs->table_next])\n    {\n      if(!vfs_allocate_inode(vfs, vfs->table_next))\n        return vfs_seterror(vfs, VFS_ENOSPC);\n\n      return vfs->table_next;\n    }\n\n    if(!vfs->table[vfs->table_next]->flags)\n      return vfs->table_next;\n\n    vfs->table_next++;\n  }\n\n  if(vfs->table_length >= vfs->table_alloc)\n  {\n    struct vfs_inode **ptr;\n    if(!vfs->table_alloc)\n      vfs->table_alloc = 4;\n\n    vfs->table_alloc <<= 1;\n    ptr = (struct vfs_inode **)realloc(vfs->table,\n     vfs->table_alloc * sizeof(struct vfs_inode *));\n    if(!ptr)\n      return vfs_seterror(vfs, VFS_ENOSPC);\n\n    vfs->table = ptr;\n  }\n\n  if(!vfs_allocate_inode(vfs, vfs->table_length))\n    return vfs_seterror(vfs, VFS_ENOSPC);\n\n  return (vfs->table_length++);\n}\n\n/**\n * Returns the inode of a given name within parent. This name should not include\n * path separators. The value of `index` will be set to the index of the inode\n * if it exists, otherwise the index where that inode should be placed.\n */\nstatic uint32_t vfs_get_inode_in_parent_by_name(vfilesystem *vfs,\n struct vfs_inode *parent, const char *name, uint32_t *index)\n{\n  uint32_t a = 2;\n  uint32_t b = parent->length - 1;\n  uint32_t current;\n  uint32_t inode;\n  int cmp;\n\n  assert(parent->length >= 2);\n\n  if(!name[0])\n    return VFS_NO_INODE;\n\n  /* Special case--current and parent dir. */\n  if(name[0] == '.')\n  {\n    if(name[1] == '.' && name[2] == '\\0')\n    {\n      if(index)\n        *index = VFS_IDX_PARENT;\n      return parent->contents.inodes[VFS_IDX_PARENT];\n    }\n    else\n\n    if(name[1] == '\\0')\n    {\n      if(index)\n        *index = VFS_IDX_SELF;\n      return parent->contents.inodes[VFS_IDX_SELF];\n    }\n  }\n\n  while(a <= b)\n  {\n    current = (b - a) / 2 + a;\n    inode = parent->contents.inodes[current];\n\n    cmp = vfs_name_cmp(name, vfs_inode_name(vfs_get_inode_ptr(vfs, inode)));\n    if(cmp > 0)\n    {\n      a = current + 1;\n    }\n    else\n\n    if(cmp < 0)\n    {\n      b = current - 1;\n    }\n    else\n    {\n      if(index)\n        *index = current;\n      return inode;\n    }\n  }\n\n  if(index)\n    *index = a;\n  return VFS_NO_INODE;\n}\n\n/**\n * Get the first inode for a given path string.\n * This is either the \"working directory\" inode or a root inode.\n * The string pointed to by `path` will be advanced past the base token if it\n * exists.\n */\nstatic uint32_t vfs_get_path_base_inode(vfilesystem *vfs, const char **path)\n{\n  ssize_t len = path_is_absolute(*path);\n  uint32_t inode;\n  char buffer[32];\n\n  if(!*path[0])\n    return vfs_seterror(vfs, VFS_ENOENT);\n\n  if(len >= (ssize_t)sizeof(buffer))\n    return vfs_seterror(vfs, VFS_ENOENT);\n\n  if(len > 0)\n  {\n    struct vfs_inode *roots = vfs_get_inode_ptr(vfs, 0);\n#ifdef PATH_UNC_ROOTS\n    // These can be passed from pretty much anywhere and need to be\n    // stripped out here.\n    if(len >= 4 && (!memcmp(*path, \"\\\\\\\\.\\\\\", 4) || !memcmp(*path, \"\\\\\\\\?\\\\\", 4)))\n    {\n      *path += 4;\n      len -= 4;\n    }\n#endif\n    memcpy(buffer, *path, len);\n    buffer[len] = '\\0';\n    *path += len;\n\n    path_clean(buffer, sizeof(buffer));\n\n    inode = vfs_get_inode_in_parent_by_name(vfs, roots, buffer, NULL);\n    if(inode == VFS_NO_INODE)\n    {\n#ifdef VIRTUAL_FILESYSTEM_DOS_DRIVE\n      // Windows and DJGPP: / behaves as the root of the current active drive.\n      if(!strcmp(buffer, DIR_SEPARATOR))\n        return vfs->current_root;\n#endif\n\n      return vfs_seterror(vfs, VFS_ENOENT);\n    }\n\n    return inode;\n  }\n  return vfs->current;\n}\n\n/**\n * Find an inode in the VFS located at the given relative path within a known\n * base inode. `relative_path` should be cleaned for slashes prior to calling\n * this function and should be writeable.\n */\nstatic uint32_t vfs_get_inode_by_relative_path(vfilesystem *vfs, uint32_t inode,\n char *relative_path)\n{\n  struct vfs_inode *parent;\n  char *current;\n  char *next;\n\n  next = relative_path;\n  while((current = path_tokenize(&next)))\n  {\n    if(!current[0])\n      continue;\n\n    parent = vfs_get_inode_ptr(vfs, inode);\n    if(VFS_INODE_TYPE(parent) != VFS_INODE_DIR)\n      return vfs_seterror(vfs, VFS_ENOTDIR);\n\n    inode = vfs_get_inode_in_parent_by_name(vfs, parent, current, NULL);\n    if(!inode)\n      return vfs_seterror(vfs, VFS_ENOENT);\n  }\n\n  return inode;\n}\n\n/**\n * Find an inode in the VFS located at the given path.\n */\nstatic uint32_t vfs_get_inode_by_path(vfilesystem *vfs, const char *path)\n{\n  char buffer[MAX_PATH];\n  uint32_t inode;\n\n  inode = vfs_get_path_base_inode(vfs, &path);\n  if(!inode)\n    return VFS_NO_INODE;\n\n  path_clean_copy(buffer, sizeof(buffer), path);\n\n  return vfs_get_inode_by_relative_path(vfs, inode, buffer);\n}\n\n/**\n * Find an inode and its parent inode in the VFS from a given path, if either\n * exists. The name of the child inode will be copied to the provided buffer.\n */\nstatic boolean vfs_get_inode_and_parent_by_path(vfilesystem *vfs, const char *path,\n uint32_t *_parent, uint32_t *_inode, char *name, size_t name_length)\n{\n  struct vfs_inode *p;\n  char buffer[MAX_PATH];\n  char *child;\n  uint32_t inode = VFS_NO_INODE;\n  uint32_t parent = VFS_NO_INODE;\n  uint32_t base;\n\n  base = vfs_get_path_base_inode(vfs, &path);\n  if(!base)\n    return false;\n\n  path_clean_copy(buffer, sizeof(buffer), path);\n\n  // If the buffer is empty, the entire path is a root.\n  // The target inode is the root and its parent is itself in this situation.\n  if(!buffer[0])\n  {\n    child = buffer;\n    parent = base;\n    inode = base;\n  }\n  else\n  {\n    child = strrchr(buffer, DIR_SEPARATOR_CHAR);\n    if(child)\n    {\n      *(child++) = '\\0';\n      parent = vfs_get_inode_by_relative_path(vfs, base, buffer);\n    }\n    else\n    {\n      child = buffer;\n      parent = base;\n    }\n\n    if(parent)\n    {\n      p = vfs_get_inode_ptr(vfs, parent);\n      if(VFS_INODE_TYPE(p) != VFS_INODE_DIR)\n      {\n        vfs_seterror(vfs, VFS_ENOTDIR);\n        return false;\n      }\n      inode = vfs_get_inode_in_parent_by_name(vfs, p, child, NULL);\n    }\n  }\n\n  if(_parent)\n    *_parent = parent;\n  if(_inode)\n    *_inode = inode;\n  if(name)\n    snprintf(name, name_length, \"%s\", child);\n\n  return true;\n}\n\n/**\n * Get the path for a given directory inode.\n */\nstatic boolean vfs_get_inode_path(vfilesystem *vfs, uint32_t inode,\n char *buffer, size_t buffer_length)\n{\n  struct vfs_inode *n;\n  uint32_t current;\n  uint32_t next;\n  size_t needed = 0;\n\n  // 1. Get the required size for this string.\n  current = inode;\n  while(true)\n  {\n    n = vfs_get_inode_ptr(vfs, current);\n    if(!n || !n->contents.inodes || VFS_INODE_TYPE(n) != VFS_INODE_DIR)\n      return false;\n\n    next = n->contents.inodes[VFS_IDX_PARENT];\n\n    // Dir separator, except for the root since it already has one.\n    if(needed && next != current)\n      needed++;\n\n    needed += n->name_length;\n\n    if(next == current)\n      break;\n\n    current = next;\n  }\n\n  if(needed + 1 >= buffer_length)\n    return false;\n\n  // 2. Copy name into the buffer.\n  buffer[needed] = '\\0';\n  current = inode;\n  while(true)\n  {\n    n = vfs_get_inode_ptr(vfs, current);\n\n    next = n->contents.inodes[VFS_IDX_PARENT];\n    if(next == current)\n      break;\n\n    needed -= n->name_length;\n    memcpy(buffer + needed, vfs_inode_name(n), n->name_length);\n    buffer[--needed] = DIR_SEPARATOR_CHAR;\n    current = next;\n  }\n  // Add root name into buffer separately.\n  memcpy(buffer, vfs_inode_name(n), n->name_length);\n  return true;\n}\n\n/**\n * Generate an inode in the VFS with a given name. This name should not include\n * path separators.\n */\nstatic uint32_t vfs_make_inode(vfilesystem *vfs, uint32_t parent,\n const char *name, size_t init_alloc, int flags)\n{\n  struct vfs_inode *p;\n  struct vfs_inode *n;\n  uint32_t pos;\n  uint32_t pos_in_parent;\n  boolean is_real;\n\n  p = vfs_get_inode_ptr(vfs, parent);\n\n  assert(VFS_INODE_TYPE(p) == VFS_INODE_DIR);\n  assert(flags & VFS_INODE_TYPEMASK);\n\n  if(!name[0])\n    return vfs_seterror(vfs, VFS_ENOENT);\n\n  pos = vfs_get_inode_in_parent_by_name(vfs, p, name, &pos_in_parent);\n  if(pos != VFS_NO_INODE)\n    return vfs_seterror(vfs, VFS_EEXIST);\n\n  pos = vfs_get_next_free_inode(vfs);\n  if(!pos)\n    return VFS_NO_INODE;\n\n  if(!vfs_inode_expand_directory(p, 1))\n    return vfs_seterror(vfs, VFS_ENOSPC);\n\n  n = vfs_get_inode_ptr(vfs, pos);\n  is_real = !!(flags & VFS_INODE_IS_REAL);\n\n  if((flags & VFS_INODE_TYPEMASK) == VFS_INODE_DIR)\n  {\n    if(!vfs_inode_init_directory(n, name, init_alloc, is_real))\n      return vfs_seterror(vfs, VFS_ENOSPC);\n\n    // Init self and parent inodes.\n    n->contents.inodes[VFS_IDX_SELF] = pos;\n    n->contents.inodes[VFS_IDX_PARENT] = p->contents.inodes[VFS_IDX_SELF];\n  }\n  else\n  {\n    if(!vfs_inode_init_file(n, name, init_alloc, is_real))\n      return vfs_seterror(vfs, VFS_ENOSPC);\n\n    // Update tracking for the total size of cached files.\n    if(is_real)\n      vfs->cache_total += n->length_alloc;\n  }\n  // Replace the placeholder timestamp with a real one...\n  if(is_real && !vfs->disable_timestamp)\n    n->timestamp = vfs_get_timestamp();\n\n  vfs_inode_insert_directory(p, pos, pos_in_parent);\n  n->parent = parent;\n  return pos;\n}\n\n/**\n * Delete an inode in the VFS. It will be removed from its parent directory.\n * The inode will be cleared and marked for reuse immediately if there are no\n * open file references. Otherwise, it will be cleared and marked for reuse\n * when its reference count reaches 0.\n */\nstatic boolean vfs_delete_inode(vfilesystem *vfs, uint32_t inode)\n{\n  struct vfs_inode *p;\n  struct vfs_inode *n;\n  uint32_t pos_in_parent;\n  uint32_t pos;\n\n  if(inode == VFS_NO_INODE)\n    return vfs_seterror(vfs, VFS_ENOENT);\n\n  n = vfs_get_inode_ptr(vfs, inode);\n\n  if(n->parent != VFS_NO_INODE)\n  {\n    if(n->parent == inode)\n      return vfs_seterror(vfs, VFS_EBUSY);\n\n    p = vfs_get_inode_ptr(vfs, n->parent);\n    assert(VFS_INODE_TYPE(p) == VFS_INODE_DIR);\n\n    // Find in parent.\n    pos = vfs_get_inode_in_parent_by_name(vfs, p, vfs_inode_name(n),\n     &pos_in_parent);\n    if(pos != inode)\n      return vfs_seterror(vfs, VFS_ENOENT);\n\n    // Remove from parent.\n    vfs_inode_delete_directory(p, pos_in_parent);\n    n->parent = VFS_NO_INODE;\n  }\n\n  // Clear the inode and mark it for reuse if it's not currently in-use.\n  if(n->refcount == 0)\n  {\n    // Update tracking for the total size of cached files.\n    if(VFS_INODE_TYPE(n) == VFS_INODE_FILE && VFS_IS_CACHED(n))\n    {\n      assert(vfs->cache_total >= n->length_alloc);\n      vfs->cache_total -= n->length_alloc;\n    }\n\n    vfs_inode_clear(n);\n    if(inode < vfs->table_next)\n      vfs->table_next = inode;\n  }\n  return true;\n}\n\n/**\n * Move an inode in the VFS from one directory to another. The old and new\n * parent directories may be the same directory.\n */\nstatic boolean vfs_move_inode(vfilesystem *vfs, uint32_t old_parent,\n uint32_t new_parent, uint32_t inode, const char *old_name, const char *new_name)\n{\n  struct vfs_inode *old_p;\n  struct vfs_inode *new_p;\n  struct vfs_inode *n;\n  uint32_t old_pos_in_parent;\n  uint32_t new_pos_in_parent = 0;\n  uint32_t pos;\n\n  if(inode == VFS_NO_INODE)\n    return vfs_seterror(vfs, VFS_ENOENT);\n\n  n = vfs_get_inode_ptr(vfs, inode);\n  old_p = vfs_get_inode_ptr(vfs, old_parent);\n  new_p = vfs_get_inode_ptr(vfs, new_parent);\n  assert(VFS_INODE_TYPE(old_p) == VFS_INODE_DIR);\n  assert(VFS_INODE_TYPE(new_p) == VFS_INODE_DIR);\n\n  if(old_p != new_p && !vfs_inode_expand_directory(new_p, 1))\n    return vfs_seterror(vfs, VFS_ENOSPC);\n\n  // Find in parents.\n  pos = vfs_get_inode_in_parent_by_name(vfs, old_p, old_name, &old_pos_in_parent);\n  if(pos != inode)\n    return vfs_seterror(vfs, VFS_ENOENT);\n\n  pos = vfs_get_inode_in_parent_by_name(vfs, new_p, new_name, &new_pos_in_parent);\n  if(pos != VFS_NO_INODE)\n    return vfs_seterror(vfs, VFS_EEXIST);\n\n  // Rename.\n  if(!vfs_inode_rename(n, new_name))\n    return vfs_seterror(vfs, VFS_ENOSPC);\n\n  // Move from old position to new position.\n  if(old_p != new_p)\n  {\n    vfs_inode_delete_directory(old_p, old_pos_in_parent);\n    vfs_inode_insert_directory(new_p, inode, new_pos_in_parent);\n  }\n  else\n  {\n    // The position determined above assumes this will keep its old position,\n    // so adjust it to account for its own removal.\n    if(new_pos_in_parent > old_pos_in_parent)\n      new_pos_in_parent--;\n\n    vfs_inode_move_directory(old_p, old_pos_in_parent, new_pos_in_parent);\n  }\n\n  n->parent = new_parent;\n  return true;\n}\n\n/**\n * Determine if inode `A` is the same as or is an ancestor of inode `B`.\n * Both inodes `A` and `B` MUST be directories currently.\n */\nstatic boolean vfs_is_ancestor_inode(vfilesystem *vfs, uint32_t A, uint32_t B)\n{\n  struct vfs_inode *a = vfs_get_inode_ptr(vfs, A);\n  struct vfs_inode *b = vfs_get_inode_ptr(vfs, B);\n  if(!a || !b)\n    return false;\n\n  assert(VFS_INODE_TYPE(a) == VFS_INODE_DIR);\n  assert(VFS_INODE_TYPE(b) == VFS_INODE_DIR);\n\n  // Walk B back to its root.\n  while(true)\n  {\n    if(a == b)\n      return true;\n\n    if(!b->contents.inodes || b->contents.inodes[VFS_IDX_PARENT] == B)\n      return false;\n\n    B = b->contents.inodes[VFS_IDX_PARENT];\n    b = vfs_get_inode_ptr(vfs, B);\n    assert(b && VFS_INODE_TYPE(b) == VFS_INODE_DIR);\n  }\n}\n\n/**\n * Invalidate a cached inode and all children, recursively.\n * If all of a directory's children are invalidated, it will be\n * marked invalid as well. Invalidated inodes are unlinked from their\n * parent directory and are destroyed if not currently in use.\n * If currently in use, they will remain allocated until closed.\n *\n * Returns true if all children are invalidated, otherwise false.\n */\nstatic boolean vfs_invalidate_inode(vfilesystem *vfs, uint32_t inode)\n{\n  struct vfs_inode *n = vfs_get_inode_ptr(vfs, inode);\n  boolean is_empty_or_file = true;\n  size_t i;\n  if(!n)\n    return true;\n\n  vfs_seterror(vfs, 0);\n  if(VFS_INODE_TYPE(n) == VFS_INODE_DIR)\n  {\n    for(i = 2; i < n->length; i++)\n    {\n      uint32_t child = n->contents.inodes[i];\n      struct vfs_inode *c = vfs_get_inode_ptr(vfs, child);\n      if(!c)\n        continue;\n\n      if(VFS_INODE_TYPE(c) == VFS_INODE_DIR)\n      {\n        if(vfs_invalidate_inode(vfs, child))\n        {\n          i--;\n        }\n        else\n          is_empty_or_file = false;\n      }\n      else\n      {\n        if(VFS_IS_CACHED(c))\n        {\n          vfs_delete_inode(vfs, child);\n          i--;\n        }\n        else\n          is_empty_or_file = false;\n      }\n    }\n  }\n\n  if(is_empty_or_file && VFS_IS_CACHED(n) && inode != vfs->current)\n  {\n    vfs_delete_inode(vfs, inode);\n    return true;\n  }\n  return false;\n}\n\n#if 0\nstatic void _vfs_print_str(vfilesystem *vfs, const char *str, int level)\n{\n  fprintf(mzxerr, \"%*.*s%s\\n\", level*2, level*2, \"\", str);\n  fflush(mzxerr);\n}\n\nstatic void _vfs_print_len(vfilesystem *vfs, size_t len, int level)\n{\n  fprintf(mzxerr, \"%*.*slen: %zu\\n\", level*2, level*2, \"\", len);\n  fflush(mzxerr);\n}\n\nstatic void _vfs_print_name(vfilesystem *vfs, struct vfs_inode *n, int level)\n{\n  _vfs_print_str(vfs, vfs_inode_name(n), level);\n}\n\nstatic void _vfs_print_dir(vfilesystem *vfs, struct vfs_inode *dir, int level)\n{\n  size_t i;\n  assert(VFS_INODE_TYPE(dir) == VFS_INODE_DIR);\n\n  level++;\n  if(level > 0)\n    _vfs_print_len(vfs, dir->length, level);\n\n  for(i = 0; i < dir->length; i++)\n  {\n    uint32_t inode = dir->contents.inodes[i];\n    struct vfs_inode *n = vfs_get_inode_ptr(vfs, inode);\n\n    if(i == VFS_IDX_SELF)\n    {\n      assert(n == dir);\n      if(level > 0)\n        _vfs_print_str(vfs, \".\", level);\n    }\n    else\n\n    if(i == VFS_IDX_PARENT)\n    {\n      if(level > 0)\n        _vfs_print_str(vfs, \"..\", level);\n    }\n    else\n\n    if(VFS_INODE_TYPE(n) == VFS_INODE_DIR)\n    {\n      _vfs_print_name(vfs, n, level);\n      _vfs_print_str(vfs, \"{\", level);\n      _vfs_print_dir(vfs, n, level);\n      _vfs_print_str(vfs, \"}\", level);\n    }\n    else\n      _vfs_print_name(vfs, n, level);\n  }\n}\n\nstatic inline void vfs_print(vfilesystem *vfs)\n{\n  struct vfs_inode *roots = vfs_get_inode_ptr(vfs, 0);\n  _vfs_print_dir(vfs, roots, -1);\n}\n#endif /* DEBUG */\n\n\n/**\n * External functions.\n */\n\n/**\n * Create a root with the given name if it does not exist.\n *\n * @param vfs     VFS handle.\n * @param name    name of the root to create. It must contain alphanumeric\n *                characters only.\n * @return        0 on success, `VFS_EEXIST` if the root already exists,\n *                `VFS_EINVAL` if the name is invalid, or other `vfs_error` codes.\n *                This function does not set `errno`.\n */\nenum vfs_error vfs_make_root(vfilesystem *vfs, const char *name)\n{\n  struct vfs_inode *n;\n  char buffer[MAX_PATH];\n  uint32_t inode;\n  size_t sz;\n  size_t i;\n\n  if(!name)\n    return VFS_EINVAL;\n  if(name[0] == '/' && name[1] == '\\0')\n    return VFS_EEXIST;\n\n  sz = strlen(name);\n  if(!sz || sz + 2 >= MAX_PATH)\n    return VFS_EINVAL;\n  for(i = 0; i < sz; i++)\n    if(!isalnum((unsigned char)name[i]))\n      return VFS_EINVAL;\n\n#ifdef PATH_DOS_STYLE_ROOTS\n  snprintf(buffer, MAX_PATH, \"%s:\" DIR_SEPARATOR, name);\n#else\n  snprintf(buffer, MAX_PATH, \"%s:\" DIR_SEPARATOR DIR_SEPARATOR, name);\n#endif\n\n  if(!vfs_write_lock(vfs))\n    return vfs_geterror(vfs);\n\n  // This will check for existence and insert the new root into the roots list.\n  inode = vfs_make_inode(vfs, VFS_NO_INODE, buffer, 0, VFS_INODE_DIR);\n  if(!inode)\n  {\n    enum vfs_error code = vfs_geterror(vfs);\n    vfs_write_unlock(vfs);\n    return code;\n  }\n\n  n = vfs_get_inode_ptr(vfs, inode);\n  n->create_time = vfs_get_date();\n  n->modify_time = n->create_time;\n  // The new root is its own parent.\n  n->parent = inode;\n  n->contents.inodes[VFS_IDX_PARENT] = inode;\n\n  vfs_write_unlock(vfs);\n  return 0;\n}\n\n/**\n * Create a file in the VFS at a given path if it doesn't already exist.\n * If the file does exist, this function will return `VFS_EEXIST`, similar\n * to an `open` call with O_CREAT|O_EXCL set.\n *\n * @param vfs   VFS handle.\n * @param path  path of file within `vfs` to create.\n * @return      0 on success, otherwise a relevant `vfs_error` code.\n *              This function does not set `errno`.\n */\nenum vfs_error vfs_create_file_at_path(vfilesystem *vfs, const char *path)\n{\n  struct vfs_inode *p;\n  char buffer[MAX_PATH];\n  uint32_t parent;\n  uint32_t inode;\n  boolean need_unlink = false;\n  enum vfs_error code = 0;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  if(!vfs_get_inode_and_parent_by_path(vfs, path, &parent, &inode, buffer, sizeof(buffer)))\n    goto err;\n\n  // Parent must exist, but target must not exist.\n  if(inode)\n  {\n    // open() sets a different error depending on the type of the existing file.\n    p = vfs_get_inode_ptr(vfs, inode);\n    if(p && VFS_INODE_TYPE(p) == VFS_INODE_FILE)\n    {\n      if(!VFS_IS_CACHED(p))\n      {\n        vfs_seterror(vfs, VFS_EEXIST);\n        goto err;\n      }\n      // Special: creating a virtual file can mask a real filesystem file.\n      need_unlink = true;\n    }\n    else\n    {\n      vfs_seterror(vfs, VFS_EISDIR);\n      goto err;\n    }\n  }\n  if(!parent) // Error is set by vfs_get_inode_by_path.\n    goto err;\n\n  // If the parent is cached and times out, ignore this call.\n  p = vfs_get_inode_ptr(vfs, parent);\n  if(!p)\n    goto err;\n\n  if(!vfs_elevate_lock(vfs))\n    goto err;\n\n  // If there's a cached file that needs to be removed, do that first.\n  if(need_unlink)\n  {\n    if(!vfs_delete_inode(vfs, inode))\n      goto err;\n  }\n\n  // Create a virtual (i.e. not real) file.\n  inode = vfs_make_inode(vfs, parent, buffer, 0, VFS_INODE_FILE);\n  if(inode)\n  {\n    struct vfs_inode *n = vfs_get_inode_ptr(vfs, inode);\n    n->create_time = vfs_get_date();\n    n->modify_time = n->create_time;\n    p->modify_time = n->create_time;\n  }\n  else\n    code = vfs_geterror(vfs);\n\n  vfs_write_unlock(vfs);\n  return code;\n\nerr:\n  code = vfs_geterror(vfs);\n  vfs_read_unlock(vfs);\n  return code;\n}\n\n/**\n * \"Open\" a file in a VFS, obtaining a file descriptor for the file and\n * incrementing its reference count by one. This is meaningless for directories,\n * and calls where the path is a directory will return `VFS_EISDIR`\n * (use `vfs_readdir` instead).\n *\n * @param vfs       VFS handle.\n * @param path      path of file within `vfs` to \"open\".\n * @param is_write  should be `true` if write operations are to be performed.\n * @param inode     a pointer for the file descriptor to be written to.\n * @return          0 on success, otherwise a relevant `vfs_error` code.\n *                  This function does not set `errno`.\n *                  If the file is cached, this function will behave as\n *                  normal but instead return `VFS_ERR_IS_CACHED`.\n */\nenum vfs_error vfs_open_if_exists(vfilesystem *vfs,\n const char *path, boolean is_write, uint32_t *_inode)\n{\n  struct vfs_inode *n;\n  uint32_t inode;\n  enum vfs_error code = 0;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  inode = vfs_get_inode_by_path(vfs, path);\n  if(!inode)\n    goto err;\n\n  n = vfs_get_inode_ptr(vfs, inode);\n  if(!n || n->refcount >= VFS_MAX_REFCOUNT)\n    goto err;\n\n  // Opening directories is nonsensical.\n  if(VFS_INODE_TYPE(n) == VFS_INODE_DIR)\n  {\n    vfs_seterror(vfs, VFS_EISDIR);\n    goto err;\n  }\n\n  n->refcount++;\n  if(VFS_IS_CACHED(n))\n    code = VFS_ERR_IS_CACHED;\n\n  vfs_read_unlock(vfs);\n  *_inode = inode;\n  return code;\n\nerr:\n  code = vfs_geterror(vfs);\n  vfs_read_unlock(vfs);\n  return code;\n}\n\n/**\n * \"Close\" a file in a VFS, decrementing its reference count by one.\n *\n * @param vfs   VFS handle.\n * @param inode file descriptor to \"close\".\n * @return      0 on success, otherwise a relevant `vfs_error` code.\n *              This function does not set `errno`.\n */\nenum vfs_error vfs_close(vfilesystem *vfs, uint32_t inode)\n{\n  struct vfs_inode *n;\n  if(inode >= vfs->table_length)\n    return VFS_EBADF;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  n = vfs_get_inode_ptr(vfs, inode);\n  assert(n->refcount > 0);\n  n->refcount--;\n\n  n->modify_time = vfs_get_date();\n  if(VFS_IS_CACHED(n))\n  {\n    if(n->refcount == 0 && VFS_IS_INVALIDATED(n))\n    {\n      if(vfs_elevate_lock(vfs))\n      {\n        vfs_delete_inode(vfs, inode);\n        vfs_write_unlock(vfs);\n        return 0;\n      }\n    }\n    if(!vfs->disable_timestamp)\n      n->timestamp = vfs_get_timestamp();\n  }\n\n  vfs_read_unlock(vfs);\n  return 0;\n}\n\n/**\n * Truncate an \"open\" VFS file to length 0.\n *\n * @param vfs   VFS handle.\n * @param inode file descriptor to the file to truncate.\n * @return      0 on success, otherwise a relevant `vfs_error` code.\n *              This function does not set `errno`.\n */\nenum vfs_error vfs_truncate(vfilesystem *vfs, uint32_t inode)\n{\n  struct vfs_inode *n;\n  if(inode >= vfs->table_length)\n    return VFS_EBADF;\n\n  if(!vfs_write_lock(vfs))\n    return vfs_geterror(vfs);\n\n  n = vfs_get_inode_ptr(vfs, inode);\n  if(!n || !n->refcount)\n  {\n    vfs_write_unlock(vfs);\n    return VFS_EBADF;\n  }\n\n  if(n->length_alloc > VFS_DEFAULT_FILE_SIZE)\n  {\n    unsigned char *tmp = (unsigned char *)realloc(n->contents.data, VFS_DEFAULT_FILE_SIZE);\n    if(tmp)\n    {\n      // Update cache size tracking.\n      if(VFS_IS_CACHED(n))\n      {\n        assert(vfs->cache_total >= n->length_alloc);\n        vfs->cache_total -= n->length_alloc - VFS_DEFAULT_FILE_SIZE;\n      }\n      n->contents.data = tmp;\n      n->length_alloc = VFS_DEFAULT_FILE_SIZE;\n    }\n\n  }\n  n->length = 0;\n\n  vfs_write_unlock(vfs);\n  return 0;\n}\n\n/**\n * Get the length of an open VFS inode.\n * This function should NOT be called while a file is locked for read/write.\n *\n * @param vfs   VFS handle.\n * @param inode file descriptor to the file to write.\n * @return      the length of the file, or a negative number corresponding to a\n *              relevant `vfs_error` code. This function does not set `errno`.\n */\nssize_t vfs_filelength(vfilesystem *vfs, uint32_t inode)\n{\n  struct vfs_inode *n;\n  ssize_t ret;\n\n  if(!vfs_read_lock(vfs))\n    return -(ssize_t)vfs_geterror(vfs);\n\n  if(inode >= vfs->table_length)\n    goto err;\n\n  n = vfs_get_inode_ptr(vfs, inode);\n  if(!n || !n->refcount)\n    goto err;\n\n  ret = n->length;\n  if(ret < 0)\n    goto err;\n\n  vfs_read_unlock(vfs);\n  return ret;\n\nerr:\n  vfs_read_unlock(vfs);\n  return -(ssize_t)VFS_EBADF;\n}\n\n/**\n * Lock an \"open\" VFS file for reading and retrieve its underlying data buffer.\n * Multiple reading locks can be acquired on a VFS handle simultaneously, but\n * a reading lock will block if a writing lock is held. To avoid deadlocks,\n * only one lock should ever be acquired per thread simultaneously.\n *\n * @param vfs         VFS handle.\n * @param inode       file descriptor to the file to read.\n * @param data        pointer to store a pointer to the underlying data to.\n * @param data_length pointer to store the length of the underlying data to.\n * @return            0 on success, otherwise a relevant `vfs_error` code.\n *                    This function does not set `errno`.\n */\nenum vfs_error vfs_lock_file_read(vfilesystem *vfs, uint32_t inode,\n const unsigned char **data, size_t *data_length)\n{\n  struct vfs_inode *n;\n  if(inode < vfs->table_length)\n  {\n    if(!vfs_read_lock(vfs))\n      return vfs_geterror(vfs);\n\n    n = vfs_get_inode_ptr(vfs, inode);\n    if(!n || !n->refcount)\n    {\n      vfs_read_unlock(vfs);\n      return VFS_EBADF;\n    }\n\n    *data = n->contents.data;\n    *data_length = n->length;\n    return 0;\n  }\n  return VFS_EBADF;\n}\n\n/**\n * Release a held reading lock on a VFS handle.\n *\n * @param vfs   VFS handle.\n * @param inode file descriptor to the file to read.\n * @return      0 on success, otherwise a relevant `vfs_error` code.\n *              This function does not set `errno`.\n */\nenum vfs_error vfs_unlock_file_read(vfilesystem *vfs, uint32_t inode)\n{\n  if(inode < vfs->table_length)\n  {\n    struct vfs_inode *n = vfs_get_inode_ptr(vfs, inode);\n    if(!n || !n->refcount)\n      return VFS_EBADF;\n\n    if(vfs_read_unlock(vfs))\n      return 0;\n  }\n  return VFS_EBADF;\n}\n\n/**\n * Lock an \"open\" VFS file for writing and retrieve its underlying data buffer.\n * Only one writing lock can be acquired on a VFS handle at any given time.\n * To avoid deadlocks, only one lock should ever be acquired per thread\n * simultaneously.\n *\n * @param vfs         VFS handle.\n * @param inode       file descriptor to the file to write.\n * @param data        pointer to store a pointer to the pointer to the underlying data to.\n * @param data_length pointer to store a pointer to the the length of the underlying data to.\n * @param data_alloc  pointer to store a pointer to the the allocation size of the underlying data to.\n * @return            0 on success, otherwise a relevant `vfs_error` code.\n *                    This function does not set `errno`.\n */\nenum vfs_error vfs_lock_file_write(vfilesystem *vfs, uint32_t inode,\n unsigned char ***data, size_t **data_length, size_t **data_alloc)\n{\n  struct vfs_inode *n;\n  if(inode < vfs->table_length)\n  {\n    if(!vfs_write_lock(vfs))\n      return vfs_geterror(vfs);\n\n    n = vfs_get_inode_ptr(vfs, inode);\n    if(!n || !n->refcount)\n    {\n      vfs_write_unlock(vfs);\n      return VFS_EBADF;\n    }\n\n    // Since the buffer may be reallocated, remove the old alloc size.\n    if(VFS_IS_CACHED(n))\n    {\n      assert(vfs->cache_total >= n->length_alloc);\n      vfs->cache_total -= n->length_alloc;\n    }\n\n    *data = &(n->contents.data);\n    *data_length = &(n->length);\n    *data_alloc = &(n->length_alloc);\n    return 0;\n  }\n  return VFS_EBADF;\n}\n\n/**\n * Release a held writing lock on a VFS handle.\n *\n * @param vfs   VFS handle.\n * @param inode file descriptor to the file to write.\n * @return      0 on success, otherwise a relevant `vfs_error` code.\n *              This function does not set `errno`.\n */\nenum vfs_error vfs_unlock_file_write(vfilesystem *vfs, uint32_t inode)\n{\n  if(inode < vfs->table_length)\n  {\n    struct vfs_inode *n = vfs_get_inode_ptr(vfs, inode);\n    if(!n || !n->refcount)\n      return VFS_EBADF;\n\n    // The size of this file may have changed, add the new size back.\n    if(VFS_IS_CACHED(n))\n      vfs->cache_total += n->length_alloc;\n\n    if(vfs_write_unlock(vfs))\n      return 0;\n  }\n  return VFS_EBADF;\n}\n\n/**\n * Set the current working directory of a VFS. This operation currently can\n * only be performed by the thread that created this VFS.\n *\n * @param vfs   VFS handle.\n * @param path  path of directory to set as the current working directory.\n * @return      0 on success, otherwise a relevant `vfs_error` code.\n *              This function does not set `errno`.\n *              If the directory is cached, this function will behave as\n *              normal but instead return `VFS_ERR_IS_CACHED`.\n */\nenum vfs_error vfs_chdir(vfilesystem *vfs, const char *path)\n{\n  struct vfs_inode *n;\n  uint32_t inode;\n  uint32_t root;\n  char buffer[MAX_PATH * 2];\n  size_t len;\n  enum vfs_error code = 0;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n#ifdef VIRTUAL_FILESYSTEM_PARALLEL\n  // TODO: for now, only allow chdir from the thread that created this VFS.\n  if(!platform_is_same_thread(vfs->origin, platform_get_thread_id()))\n  {\n    vfs_seterror(vfs, VFS_EACCES);\n    goto err;\n  }\n#endif\n\n  // Navigate the data structure first to get better errors than path_navigate.\n  inode = vfs_get_inode_by_path(vfs, path);\n  if(!inode)\n    goto err;\n\n  n = vfs_get_inode_ptr(vfs, inode);\n  if(!n)\n    goto err;\n\n  if(VFS_IS_CACHED(n))\n    code = VFS_ERR_IS_CACHED;\n\n  // If this is the same as the current inode, exit early.\n  if(inode == vfs->current)\n  {\n    vfs_read_unlock(vfs);\n    return code;\n  }\n\n  if(VFS_INODE_TYPE(n) != VFS_INODE_DIR)\n  {\n    vfs_seterror(vfs, VFS_ENOTDIR);\n    goto err;\n  }\n\n  // Get the new working directory path string.\n  if(!vfs_get_inode_path(vfs, inode, buffer, sizeof(buffer)))\n  {\n    vfs_seterror(vfs, VFS_ENAMETOOLONG);\n    goto err;\n  }\n  len = strlen(buffer);\n  if(len >= sizeof(vfs->current_path))\n  {\n    vfs_seterror(vfs, VFS_ENAMETOOLONG);\n    goto err;\n  }\n\n  // Find the root this directory exists in.\n  root = inode;\n  while(true)\n  {\n    uint32_t next;\n    if(!root || !n || !n->contents.inodes)\n      goto err;\n\n    next = n->contents.inodes[VFS_IDX_PARENT];\n    if(next == root)\n      break;\n\n    root = next;\n    n = vfs_get_inode_ptr(vfs, root);\n  }\n\n  if(!vfs_elevate_lock(vfs))\n    goto err;\n\n  memcpy(vfs->current_path, buffer, len + 1);\n  vfs->current_path_len = len;\n  vfs->current = inode;\n  vfs->current_root = root;\n  vfs_write_unlock(vfs);\n  return code;\n\nerr:\n  code = vfs_geterror(vfs);\n  vfs_read_unlock(vfs);\n  return code;\n}\n\n/**\n * Get the current working directory of a VFS as a path string.\n *\n * @param vfs       VFS handle.\n * @param dest      buffer to store the current working directory path to.\n * @param dest_len  size of `dest` in bytes.\n * @return          0 on success, otherwise a relevant `vfs_error` code.\n *                  This function does not set `errno`.\n *                  If the directory is cached, this function will behave as\n *                  normal but instead return `VFS_ERR_IS_CACHED`.\n */\nenum vfs_error vfs_getcwd(vfilesystem *vfs, char *dest, size_t dest_len)\n{\n  struct vfs_inode *n;\n  enum vfs_error code = 0;\n\n  if(!dest || !dest_len)\n    return VFS_EINVAL;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  if(vfs->current_path_len >= dest_len)\n  {\n    vfs_read_unlock(vfs);\n    return VFS_ERANGE;\n  }\n  // This should already have been cleaned by vfs_chdir, so copy directly.\n  memcpy(dest, vfs->current_path, vfs->current_path_len + 1);\n\n  n = vfs_get_inode_ptr(vfs, vfs->current);\n  if(n && VFS_IS_CACHED(n))\n    code = VFS_ERR_IS_CACHED;\n\n  vfs_read_unlock(vfs);\n  return code;\n}\n\n/**\n * Create a virtual directory (i.e. not a cached copy of a real directory).\n * If this virtual directory is being created on a real path, that path must\n * be cached via `vfs_cache_directory` prior to calling this function.\n *\n * @param vfs   VFS handle.\n * @param path  path to create a directory at within `vfs`.\n * @param mode  permission bits for the created directory (ignored).\n * @return      0 on success, otherwise a relevant `vfs_error` code.\n *              This function does not set `errno`.\n */\nenum vfs_error vfs_mkdir(vfilesystem *vfs, const char *path, int mode)\n{\n  struct vfs_inode *p;\n  char buffer[MAX_PATH];\n  uint32_t parent;\n  uint32_t inode;\n  enum vfs_error code = 0;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  if(!vfs_get_inode_and_parent_by_path(vfs, path, &parent, &inode, buffer, sizeof(buffer)))\n    goto err;\n\n  // Parent must exist, but target must not exist.\n  if(inode)\n  {\n    vfs_seterror(vfs, VFS_EEXIST);\n    goto err;\n  }\n  if(!parent) // Error is set by vfs_get_inode_by_path.\n    goto err;\n\n  p = vfs_get_inode_ptr(vfs, parent);\n  if(!p)\n    goto err;\n\n  if(!vfs_elevate_lock(vfs))\n    goto err;\n\n  // Create a virtual (i.e. not real) directory.\n  inode = vfs_make_inode(vfs, parent, buffer, 0, VFS_INODE_DIR);\n  if(inode)\n  {\n    struct vfs_inode *n = vfs_get_inode_ptr(vfs, inode);\n    n->create_time = vfs_get_date();\n    n->modify_time = n->create_time;\n    p->modify_time = n->create_time;\n  }\n  else\n    code = vfs_geterror(vfs);\n\n  vfs_write_unlock(vfs);\n  return code;\n\nerr:\n  code = vfs_geterror(vfs);\n  vfs_read_unlock(vfs);\n  return code;\n}\n\n/**\n * Rename a virtual or cached file or directory. If a file is moved, the\n * destination either must not exist or must be a file. If a directory is moved,\n * the destination either must not exist or must be an empty directory.\n *\n * @param vfs     VFS handle.\n * @param oldpath path of a file or directory within `vfs` to rename.\n * @param newpath new path to rename the file or directory to.\n * @return        0 on success, otherwise a relevant `vfs_error` code.\n *                This function does not set `errno`.\n *                Returns `VFS_ERR_IS_CACHED` if a cached file was renamed.\n */\nenum vfs_error vfs_rename(vfilesystem *vfs, const char *oldpath, const char *newpath)\n{\n  char buffer[MAX_PATH];\n  char buffer2[MAX_PATH];\n  struct vfs_inode *old_p;\n  struct vfs_inode *old_n;\n  struct vfs_inode *new_p;\n  struct vfs_inode *new_n;\n  uint32_t old_parent;\n  uint32_t new_parent;\n  uint32_t old_inode;\n  uint32_t new_inode;\n  enum vfs_error code = 0;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  if(!vfs_get_inode_and_parent_by_path(vfs, oldpath, &old_parent, &old_inode, buffer, sizeof(buffer)))\n    goto err;\n\n  // old must have both a parent and an inode and they should be different.\n  if(!old_parent || !old_inode)\n  {\n    vfs_seterror(vfs, VFS_ENOENT);\n    goto err;\n  }\n  if(old_parent == old_inode)\n  {\n    vfs_seterror(vfs, VFS_EBUSY);\n    goto err;\n  }\n  old_p = vfs_get_inode_ptr(vfs, old_parent);\n  old_n = vfs_get_inode_ptr(vfs, old_inode);\n  if(VFS_INODE_TYPE(old_p) != VFS_INODE_DIR)\n    goto err;\n\n  // If the file being moved is cached, let the caller know.\n  if(VFS_IS_CACHED(old_n))\n    code = VFS_ERR_IS_CACHED;\n\n  if(!vfs_get_inode_and_parent_by_path(vfs, newpath, &new_parent, &new_inode, buffer2, sizeof(buffer2)))\n    goto err;\n\n  // new must have a parent, and its inode should be different.\n  if(!new_parent)\n  {\n    vfs_seterror(vfs, VFS_ENOENT);\n    goto err;\n  }\n  if(new_parent == new_inode)\n  {\n    vfs_seterror(vfs, VFS_EBUSY);\n    goto err;\n  }\n  new_p = vfs_get_inode_ptr(vfs, new_parent);\n  new_n = new_inode ? vfs_get_inode_ptr(vfs, new_inode) : NULL;\n  if(VFS_INODE_TYPE(new_p) != VFS_INODE_DIR)\n    goto err;\n\n  // Special case: if old and new are the same, return early with success.\n  if(old_n == new_n)\n  {\n    vfs_read_unlock(vfs);\n    return code;\n  }\n\n  if(VFS_INODE_TYPE(old_n) == VFS_INODE_DIR)\n  {\n    // If old is a dir, the new parent should not be prefixed by old.\n    if(vfs_is_ancestor_inode(vfs, old_inode, new_parent))\n    {\n      vfs_seterror(vfs, VFS_EINVAL);\n      goto err;\n    }\n\n    // If old is a dir, new must be an empty dir if it exists.\n    if(new_n)\n    {\n      if(VFS_INODE_TYPE(new_n) != VFS_INODE_DIR)\n      {\n        vfs_seterror(vfs, VFS_ENOTDIR);\n        goto err;\n      }\n      if(new_n->length > 2)\n      {\n        vfs_seterror(vfs, VFS_ENOTEMPTY);\n        goto err;\n      }\n    }\n\n    // Also, neither of them can be the current directory or an ancestor.\n    if(vfs_is_ancestor_inode(vfs, old_inode, vfs->current) ||\n       vfs_is_ancestor_inode(vfs, new_inode, vfs->current))\n    {\n      vfs_seterror(vfs, VFS_EBUSY);\n      goto err;\n    }\n  }\n  else\n  {\n    if(new_n)\n    {\n      // If old is a file, new must be a file if it exists.\n      if(VFS_INODE_TYPE(new_n) == VFS_INODE_DIR)\n      {\n        vfs_seterror(vfs, VFS_EISDIR);\n        goto err;\n      }\n      // New must not have any references if it exists.\n      if(new_n->refcount)\n      {\n        // Might be incorrect, POSIX only mentions dirs.\n        vfs_seterror(vfs, VFS_EBUSY);\n        goto err;\n      }\n    }\n  }\n\n  if(!vfs_elevate_lock(vfs))\n    goto err;\n\n  if(new_inode && !vfs_delete_inode(vfs, new_inode))\n    goto err_write;\n\n  if(!vfs_move_inode(vfs, old_parent, new_parent, old_inode, buffer, buffer2))\n    goto err_write;\n\n  old_p->modify_time = vfs_get_date();\n  new_p->modify_time = old_p->modify_time;\n  vfs_write_unlock(vfs);\n  return code;\n\nerr_write:\n  code = vfs_geterror(vfs);\n  vfs_write_unlock(vfs);\n  return code;\n\nerr:\n  code = vfs_geterror(vfs);\n  vfs_read_unlock(vfs);\n  return code;\n}\n\n/**\n * Remove a virtual file (i.e. not a cached copy of a real file).\n * If this is a cached real file, it should be unlinked separately and\n * invalidated with `vfs_invalidate_at_path`. This function does not work on\n * directories (use `vfs_rmdir` instead).\n *\n * @param vfs   VFS handle.\n * @param path  path of file within `vfs` to remove.\n * @return      0 on success, otherwise a relevant `vfs_error` code.\n *              This function does not set `errno`.\n *              If the file/directory exists and is cached, `VFS_ERR_IS_CACHED`.\n */\nenum vfs_error vfs_unlink(vfilesystem *vfs, const char *path)\n{\n  char buffer[MAX_PATH];\n  struct vfs_inode *n;\n  uint32_t parent;\n  uint32_t inode;\n  enum vfs_error code = 0;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  if(!vfs_get_inode_and_parent_by_path(vfs, path, &parent, &inode, buffer, sizeof(buffer)))\n    goto err;\n\n  // Both must exist and be different.\n  if(!parent || !inode)\n  {\n    vfs_seterror(vfs, VFS_ENOENT);\n    goto err;\n  }\n  if(parent == inode)\n  {\n    vfs_seterror(vfs, VFS_EBUSY);\n    goto err;\n  }\n\n  n = vfs_get_inode_ptr(vfs, inode);\n  if(!n)\n    goto err;\n\n  // This function is not applicable to cached files.\n  if(VFS_IS_CACHED(n))\n  {\n    vfs_seterror(vfs, VFS_ERR_IS_CACHED);\n    goto err;\n  }\n\n  // If this is a directory, this call should be ignored.\n  if(VFS_INODE_TYPE(n) == VFS_INODE_DIR)\n  {\n    vfs_seterror(vfs, VFS_EPERM);\n    goto err;\n  }\n\n  // If the inode is currently in use, this call should be ignored.\n  if(n->refcount)\n  {\n    vfs_seterror(vfs, VFS_EBUSY);\n    goto err;\n  }\n\n  if(!vfs_elevate_lock(vfs))\n    goto err;\n\n  if(vfs_delete_inode(vfs, inode))\n  {\n    struct vfs_inode *p = vfs_get_inode_ptr(vfs, parent);\n    p->modify_time = vfs_get_date();\n  }\n  else\n    code = vfs_geterror(vfs);\n\n  vfs_write_unlock(vfs);\n  return code;\n\nerr:\n  code = vfs_geterror(vfs);\n  vfs_read_unlock(vfs);\n  return code;\n}\n\n/**\n * Remove an empty virtual directory (i.e. not a cached directory).\n * If this is a cached directory, it should be removed separately and\n * invalidated with `vfs_invalidate_at_path`.\n *\n * @param vfs   VFS handle.\n * @param path  path of directory within `vfs` to remove.\n * @return      0 on success, otherwise a relevant `vfs_error` code.\n *              This function does not set `errno`.\n *              If the file/directory exists and is cached, `VFS_ERR_IS_CACHED`.\n */\nenum vfs_error vfs_rmdir(vfilesystem *vfs, const char *path)\n{\n  char buffer[MAX_PATH];\n  struct vfs_inode *n;\n  uint32_t parent;\n  uint32_t inode;\n  enum vfs_error code = 0;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  if(!vfs_get_inode_and_parent_by_path(vfs, path, &parent, &inode, buffer, sizeof(buffer)))\n    goto err;\n\n  // Both must exist and be different.\n  if(!parent || !inode)\n  {\n    vfs_seterror(vfs, VFS_ENOENT);\n    goto err;\n  }\n  if(parent == inode)\n  {\n    vfs_seterror(vfs, VFS_EBUSY);\n    goto err;\n  }\n\n  n = vfs_get_inode_ptr(vfs, inode);\n  if(!n)\n    goto err;\n\n  // This function is not applicable to cached files.\n  if(VFS_IS_CACHED(n))\n  {\n    // ...unless there are virtual children somewhere. A full check could\n    // be slow so just check for any children and hope cached files are correct.\n    if(VFS_INODE_TYPE(n) == VFS_INODE_DIR && n->length > 2)\n      vfs_seterror(vfs, VFS_ENOTEMPTY);\n    else\n      vfs_seterror(vfs, VFS_ERR_IS_CACHED);\n    goto err;\n  }\n\n  // If this is a file, this call should be ignored.\n  if(VFS_INODE_TYPE(n) != VFS_INODE_DIR)\n  {\n    vfs_seterror(vfs, VFS_ENOTDIR);\n    goto err;\n  }\n\n  // If the inode is not empty, this call should be ignored.\n  if(n->length > 2)\n  {\n    vfs_seterror(vfs, VFS_ENOTEMPTY);\n    goto err;\n  }\n\n  // If the inode is the CWD, this call should be ignored.\n  if(inode == vfs->current)\n  {\n    vfs_seterror(vfs, VFS_EBUSY);\n    goto err;\n  }\n\n  if(!vfs_elevate_lock(vfs))\n    goto err;\n\n  if(vfs_delete_inode(vfs, inode))\n  {\n    struct vfs_inode *p = vfs_get_inode_ptr(vfs, parent);\n    p->modify_time = vfs_get_date();\n  }\n  else\n    code = vfs_geterror(vfs);\n\n  vfs_write_unlock(vfs);\n  return code;\n\nerr:\n  code = vfs_geterror(vfs);\n  vfs_read_unlock(vfs);\n  return code;\n}\n\n/**\n * Get access permissions for a virtual file or directory (i.e. not cached).\n * If this is a cached file or directory, it should be checked with `access`.\n * Currently, the VFS system doesn't bother with permissions, so this works\n * as long as the file exists and isn't cached.\n *\n * @param vfs   VFS handle.\n * @param path  path within `vfs` to query.\n * @param mode  permissions to query (ignored; always treated as `F_OK`).\n * @return      0 on success, otherwise a relevant `vfs_error` code.\n *              This function does not set `errno`.\n */\nenum vfs_error vfs_access(vfilesystem *vfs, const char *path, int mode)\n{\n  struct vfs_inode *n;\n  uint32_t inode;\n  enum vfs_error code;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  inode = vfs_get_inode_by_path(vfs, path);\n  if(!inode)\n    goto err;\n\n  n = vfs_get_inode_ptr(vfs, inode);\n  if(!n)\n    goto err;\n\n  // This function is not applicable to cached files.\n  if(VFS_IS_CACHED(n))\n  {\n    vfs_seterror(vfs, VFS_ERR_IS_CACHED);\n    goto err;\n  }\n\n  // All operations are allowed for virtual files.\n  vfs_read_unlock(vfs);\n  return 0;\n\nerr:\n  code = vfs_geterror(vfs);\n  vfs_read_unlock(vfs);\n  return code;\n}\n\n/**\n * Get extended information about any file or directory (i.e. virtual or cached).\n * This function allows usage on cached files since `stat` calls can be very\n * slow and do not modify the filesystem.\n *\n * @param vfs   VFS handle.\n * @param path  path within `vfs` to query.\n * @param st    destination to store queried `stat` data to. If the call is\n *              successful, every field of this struct will be initialized.\n * @return      0 on success, otherwise a relevant `vfs_error` code.\n *              This function does not set `errno`.\n *              If the file/directory exists and is cached, `VFS_ERR_IS_CACHED`.\n */\nenum vfs_error vfs_stat(vfilesystem *vfs, const char *path, struct stat *st)\n{\n  struct vfs_inode *n;\n  uint32_t inode;\n  int mode = S_IRWXU|S_IRWXG|S_IRWXO;\n  enum vfs_error code;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  inode = vfs_get_inode_by_path(vfs, path);\n  if(!inode)\n    goto err;\n\n  n = vfs_get_inode_ptr(vfs, inode);\n  if(!n)\n    goto err;\n\n  memset(st, 0, sizeof(struct stat));\n\n  if(VFS_INODE_TYPE(n) == VFS_INODE_FILE)\n  {\n    st->st_mode = S_IFREG | mode;\n    st->st_size = n->length;\n  }\n  else\n    st->st_mode = S_IFDIR | mode;\n\n  // Dummy device value to indicate this is an inode from an MZX VFS.\n  st->st_dev = (dev_t)VFS_MZX_DEVICE;\n  st->st_ino = inode;\n  st->st_nlink = 1;\n  // This is cheating a little--atime is the access time but noatime can\n  // cause it to never be updated in real filesystems. ctime is for status\n  // changes but this VFS treats modifications and status changes as the same.\n  st->st_atime = n->create_time;\n  st->st_mtime = n->modify_time;\n  st->st_ctime = n->modify_time;\n  code = VFS_IS_CACHED(n) ? VFS_ERR_IS_CACHED : 0;\n  vfs_read_unlock(vfs);\n  return code;\n\nerr:\n  code = vfs_geterror(vfs);\n  vfs_read_unlock(vfs);\n  return code;\n}\n\n/**\n * Get a list of virtual files present in a directory.\n * The list of virtual files will be allocated to `struct vfs_dir *d`.\n *\n * @param vfs   VFS handle.\n * @param path  path within `vfs` to query.\n * @param d     destination to store queried directory data to.\n * @return      0 on success, otherwise a relevant `vfs_error` code.\n *              This function does not set `errno`.\n */\nenum vfs_error vfs_readdir(vfilesystem *vfs, const char *path, struct vfs_dir *d)\n{\n  struct vfs_dir_file **files;\n  struct vfs_inode *p;\n  uint32_t *inodes;\n  uint32_t inode;\n  size_t num_alloc;\n  size_t num_files;\n  size_t i;\n  enum vfs_error code;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  inode = vfs_get_inode_by_path(vfs, path);\n  if(!inode)\n    goto err;\n\n  p = vfs_get_inode_ptr(vfs, inode);\n  if(!p)\n    goto err;\n\n  // This function is only applicable to files.\n  if(VFS_INODE_TYPE(p) != VFS_INODE_DIR)\n  {\n    vfs_seterror(vfs, VFS_ENOTDIR);\n    goto err;\n  }\n\n  files = NULL;\n  num_files = 0;\n  num_alloc = 0;\n  inodes = p->contents.inodes;\n\n  for(i = 2; i < p->length; i++)\n  {\n    struct vfs_dir_file *f;\n    struct vfs_inode *n;\n    const char *name;\n    size_t len;\n    int type;\n    inode = inodes[i];\n\n    // Ignore cached files; the caller should be using dirent for them.\n    n = vfs_get_inode_ptr(vfs, inode);\n    if(!n || VFS_IS_CACHED(n))\n      continue;\n\n    if(num_files >= num_alloc)\n    {\n      struct vfs_dir_file **t;\n      num_alloc = num_alloc ? num_alloc << 1 : 4;\n\n      t = (struct vfs_dir_file **)realloc(files, num_alloc * sizeof(struct vfs_dir_file *));\n      if(!t)\n      {\n        vfs_seterror(vfs, VFS_ENOMEM);\n        goto err_free;\n      }\n      files = t;\n    }\n\n    name = vfs_inode_name(n);\n    len = n->name_length;\n    switch(VFS_INODE_TYPE(n))\n    {\n      case VFS_INODE_DIR:\n        type = DIR_TYPE_DIR;\n        break;\n      case VFS_INODE_FILE:\n        type = DIR_TYPE_FILE;\n        break;\n      default:\n        type = DIR_TYPE_UNKNOWN;\n        break;\n    }\n\n    f = (struct vfs_dir_file *)malloc(sizeof(struct vfs_dir_file) + len);\n    if(!f)\n    {\n      vfs_seterror(vfs, VFS_ENOMEM);\n      goto err_free;\n    }\n\n    f->type = type;\n    memcpy(f->name, name, len + 1);\n\n    files[num_files++] = f;\n  }\n\n  if(num_files < num_alloc)\n  {\n    struct vfs_dir_file **t = (struct vfs_dir_file **)realloc(files,\n     num_files * sizeof(struct vfs_dir_file *));\n    if(t)\n      files = t;\n  }\n\n  d->files = files;\n  d->num_files = num_files;\n\n  vfs_read_unlock(vfs);\n  return 0;\n\nerr_free:\n  if(files)\n  {\n    for(i = 0; i < num_files; i++)\n      free(files[i]);\n    free(files);\n  }\nerr:\n  code = vfs_geterror(vfs);\n  vfs_read_unlock(vfs);\n  return code;\n}\n\n/**\n * Free memory allocated to a `struct vfs_dir` by `vfs_readdir`.\n *\n * @param d   `vfs_dir` struct to free data from.\n * @return    0 on success, otherwise a negative value.\n */\nenum vfs_error vfs_readdir_free(struct vfs_dir *d)\n{\n  size_t i;\n  if(d->files)\n  {\n    for(i = 0; i < d->num_files; i++)\n      free(d->files[i]);\n\n    free(d->files);\n    d->files = NULL;\n  }\n  return 0;\n}\n\n/**\n * Recursively delete all cached entries at the given path.\n * Unlinks cached entries that are currently opened, but they will\n * not be destroyed until they are closed.\n *\n * @param vfs       VFS handle.\n * @param path      path of file or directory within `vfs` to invalidate.\n * @return          0 on success, otherwise a relevant `vfs_error` code.\n *                  This function does not set `errno`.\n */\nenum vfs_error vfs_invalidate_at_path(vfilesystem *vfs, const char *path)\n{\n  uint32_t inode;\n  enum vfs_error code = 0;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  inode = vfs_get_inode_by_path(vfs, path);\n  if(!inode)\n  {\n    code = vfs_geterror(vfs);\n    vfs_read_unlock(vfs);\n    return code;\n  }\n\n  if(!vfs_elevate_lock(vfs))\n  {\n    vfs_read_unlock(vfs);\n    return VFS_ERR_UNKNOWN;\n  }\n\n  vfs_invalidate_inode(vfs, inode);\n  code = vfs_geterror(vfs);\n  vfs_write_unlock(vfs);\n  return code;\n}\n\nstruct sort_data\n{\n  struct vfs_inode *n;\n  uint32_t inode;\n};\n\nstatic int invalidate_sort_fn(const void *A, const void *B)\n{\n  const struct sort_data *a = (const struct sort_data *)A;\n  const struct sort_data *b = (const struct sort_data *)B;\n\n  // Sort by timestamp ascending (invalidate older files first).\n  // Intentionally underflow since these are 32-bit truncated timestamps.\n  if(a->n->timestamp != b->n->timestamp)\n    return (int32_t)b->n->timestamp - (int32_t)a->n->timestamp;\n\n  // Sort by size descending.\n  if(a->n->length_alloc != b->n->length_alloc)\n    return a->n->length_alloc > b->n->length_alloc ? -1 : 1;\n\n  // Stabilize using the unique inode number.\n  return a->inode < b->inode ? -1 : a->inode > b->inode ? 1 : 0;\n}\n\n/**\n * Free cached entries until the amount of memory pointed to by\n * `amount_to_free` has been invalidated. This function ignores cached\n * entries that have active references and directories. This may be an\n * expensive operation.\n *\n * @param vfs       VFS handle\n * @param amount_to_free  a pointer to the amount of memory to be freed\n *                  by this function. If at least this amount was freed,\n *                  0 will be stored to this value; otherwise, the amount of\n *                  memory that count not be freed will be stored here.\n * @return          0 on success, otherwise a relevant `vfs_error` code.\n *                  This function does not set `errno`.\n *                  A successful return value does not guarantee any entries were freed.\n */\nenum vfs_error vfs_invalidate_at_least(vfilesystem *vfs, size_t *_amount_to_free)\n{\n  struct sort_data fallback[64];\n  struct sort_data *targets;\n  size_t amount_to_free;\n  size_t total_free;\n  size_t max_targets;\n  size_t num_targets;\n  size_t i;\n\n  if(!_amount_to_free)\n    return VFS_EINVAL;\n  if(*_amount_to_free == 0)\n    return 0;\n\n  amount_to_free = *_amount_to_free;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  // This is likely too large, but that's why there's a fallback table.\n  max_targets = vfs->table_length;\n  targets = (struct sort_data *)malloc(max_targets * sizeof(struct sort_data));\n  if(!targets)\n  {\n    targets = fallback;\n    max_targets = ARRAY_SIZE(fallback);\n  }\n\n  num_targets = 0;\n  for(i = 0; i < vfs->table_length; i++)\n  {\n    struct vfs_inode *n = vfs->table[i];\n\n    if(n && VFS_INODE_TYPE(n) == VFS_INODE_FILE && n->refcount == 0 &&\n     VFS_IS_CACHED(n) && !VFS_IS_INVALIDATED(n))\n    {\n      struct sort_data tmp = { n, i };\n      targets[num_targets++] = tmp;\n\n      if(num_targets >= max_targets)\n        break;\n    }\n  }\n\n  if(num_targets)\n    qsort(targets, num_targets, sizeof(struct sort_data), invalidate_sort_fn);\n\n  // Can't do anything, just exit.\n  if(!num_targets || !vfs_elevate_lock(vfs))\n  {\n    vfs_read_unlock(vfs);\n    if(targets != fallback)\n      free(targets);\n    return 0;\n  }\n\n  // If there is an oldest accessed file of at least the requested size and no\n  // open references, deleting it should be good enough.\n  for(i = 0; i < num_targets; i++)\n  {\n    if(targets[i].n->length_alloc >= amount_to_free)\n    {\n      vfs_delete_inode(vfs, targets[i].inode);\n      amount_to_free = 0;\n      goto success;\n    }\n  }\n\n  // Delete entries from oldest to newest until the threshold is reached.\n  total_free = 0;\n  for(i = 0; i < num_targets && total_free < amount_to_free; i++)\n  {\n    total_free += targets[i].n->length_alloc;\n    vfs_delete_inode(vfs, targets[i].inode);\n    if(total_free >= amount_to_free)\n      break;\n  }\n  amount_to_free = amount_to_free > total_free ? amount_to_free - total_free : 0;\n\nsuccess:\n  vfs_write_unlock(vfs);\n\n  *_amount_to_free = amount_to_free;\n  if(targets != fallback)\n    free(targets);\n  return 0;\n}\n\n/**\n * Free ALL cached entries in the entire VFS. Opened cached entries will be\n * unlinked by this function, but not freed until they are closed.\n *\n * @param vfs       VFS handle.\n * @return          0 on success, otherwise a relevant `vfs_error` code.\n *                  This function does not set `errno`.\n */\nenum vfs_error vfs_invalidate_all(vfilesystem *vfs)\n{\n  if(!vfs_write_lock(vfs))\n    return vfs_geterror(vfs);\n\n  // VFS_NO_INODE = the list of all roots.\n  vfs_invalidate_inode(vfs, VFS_NO_INODE);\n\n  vfs_write_unlock(vfs);\n  return 0;\n}\n\n/**\n * Create a cached directory entry at the given path.\n *\n * @param vfs       VFS handle.\n * @param path      path of directory to create a cache entry at within `vfs`.\n * @param st        stat information to create cached directory from.\n * @return          0 on success, `VFS_ENOINT` if the parent doesn't exist, or\n *                  potentially another relevant `vfs_error` code.\n *                  This function does not set `errno`.\n */\nenum vfs_error vfs_cache_directory(vfilesystem *vfs, const char *path, const struct stat *st)\n{\n  struct vfs_inode *p;\n  char buffer[MAX_PATH];\n  uint32_t parent;\n  uint32_t inode;\n  enum vfs_error code = 0;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  if(!vfs_get_inode_and_parent_by_path(vfs, path, &parent, &inode, buffer, sizeof(buffer)))\n    goto err;\n\n  // Parent must exist, but target must not exist.\n  if(inode)\n  {\n    vfs_seterror(vfs, VFS_EEXIST);\n    goto err;\n  }\n  if(!parent) // Error is set by vfs_get_inode_by_path.\n    goto err;\n\n  p = vfs_get_inode_ptr(vfs, parent);\n  if(!p)\n    goto err;\n\n  if(!vfs_elevate_lock(vfs))\n    goto err;\n\n  // Create a cache directory.\n  inode = vfs_make_inode(vfs, parent, buffer, 0,  VFS_INODE_DIR | VFS_INODE_IS_REAL);\n  if(inode)\n  {\n    struct vfs_inode *n = vfs_get_inode_ptr(vfs, inode);\n    n->create_time = vfs_get_date();\n    n->modify_time = st ? st->st_mtime : n->create_time;\n  }\n  else\n    code = vfs_geterror(vfs);\n\n  vfs_write_unlock(vfs);\n  return code;\n\nerr:\n  code = vfs_geterror(vfs);\n  vfs_read_unlock(vfs);\n  return code;\n}\n\n/**\n * Create a cached file entry at the given path.\n * An internal buffer of at least `data_length` bytes will be allocated\n * and initialized using the provided callback.\n *\n * @param vfs         VFS handle.\n * @param path        path of file to create a cache entry at within `vfs`.\n * @param readfn      function to read bytes into a buffer, or `NULL`.\n *                    Arg 1: destination buffer to read to.\n *                    Arg 2: number of bytes to attempt to read.\n *                    Arg 3: the value provided to `priv`.\n *                    Return value: the number of bytes successfully read, which\n *                    may be less than the requested number. The total number of\n *                    bytes successfully read determines the actual file length.\n * @param priv        private data for `readfn`.\n * @param data_length initial allocated size of the cached file. This function\n *                    will attempt to read `data_length` bytes using `readfn`.\n * @return            0 on success, `VFS_ENOINT` if the parent doesn't exist, or\n *                    potentially another relevant `vfs_error` code.\n *                    This function does not set `errno`.\n */\nenum vfs_error vfs_cache_file_callback(vfilesystem *vfs, const char *path,\n size_t (*readfn)(void * RESTRICT, size_t, void * RESTRICT),\n void *priv, size_t data_length)\n{\n  struct vfs_inode *p;\n  char buffer[MAX_PATH];\n  uint32_t parent;\n  uint32_t inode;\n  enum vfs_error code = 0;\n\n  if(!vfs_read_lock(vfs))\n    return vfs_geterror(vfs);\n\n  if(!vfs_get_inode_and_parent_by_path(vfs, path, &parent, &inode, buffer, sizeof(buffer)))\n    goto err;\n\n  // Parent must exist, but target must not exist.\n  if(inode)\n  {\n    p = vfs_get_inode_ptr(vfs, inode);\n    if(p && VFS_INODE_TYPE(p) == VFS_INODE_FILE)\n      vfs_seterror(vfs, VFS_EEXIST);\n    else\n      vfs_seterror(vfs, VFS_EISDIR);\n    goto err;\n  }\n  if(!parent) // Error is set by vfs_get_inode_by_path.\n    goto err;\n\n  p = vfs_get_inode_ptr(vfs, parent);\n  if(!p)\n    goto err;\n\n  if(!vfs_elevate_lock(vfs))\n    goto err;\n\n  // Create a cache file.\n  inode = vfs_make_inode(vfs, parent, buffer, data_length,\n   VFS_INODE_FILE | VFS_INODE_IS_REAL);\n  if(inode)\n  {\n    struct vfs_inode *n = vfs_get_inode_ptr(vfs, inode);\n    n->create_time = vfs_get_date();\n    n->modify_time = n->create_time;\n\n    if(readfn)\n      n->length = readfn(n->contents.data, n->length_alloc, priv);\n    else\n      n->length = 0;\n  }\n  else\n    code = vfs_geterror(vfs);\n\n  vfs_write_unlock(vfs);\n  return code;\n\nerr:\n  code = vfs_geterror(vfs);\n  vfs_read_unlock(vfs);\n  return code;\n}\n\nstruct cache_file_mem_fn_data\n{\n  const unsigned char *data;\n  size_t left;\n};\n\nstatic size_t cache_file_mem_fn(void * RESTRICT dest, size_t nbytes,\n void * RESTRICT priv)\n{\n  struct cache_file_mem_fn_data *d = (struct cache_file_mem_fn_data *)priv;\n  if(!nbytes || !d || !d->data)\n    return 0;\n\n  if(nbytes > d->left)\n    nbytes = d->left;\n\n  if(nbytes)\n  {\n    memcpy(dest, d->data, nbytes);\n    d->data += nbytes;\n    d->left -= nbytes;\n  }\n  return nbytes;\n}\n\n/**\n * Create a cached file entry at the given path.\n * The contents of the file will be duplicated from the provided buffer.\n *\n * @param vfs         VFS handle.\n * @param path        path of file to create a cache entry at within `vfs`.\n * @param data        initial contents of the cached file.\n * @param data_length initial length and allocated size of the cached file.\n * @return            0 on success, `VFS_ENOINT` if the parent doesn't exist, or\n *                    potentially another relevant `vfs_error` code.\n *                    This function does not set `errno`.\n */\nenum vfs_error vfs_cache_file(vfilesystem *vfs, const char *path, const void *data,\n size_t data_length)\n{\n  struct cache_file_mem_fn_data d =\n  {\n    (const unsigned char *)data,\n    data_length\n  };\n  return vfs_cache_file_callback(vfs, path, cache_file_mem_fn, &d, data_length);\n}\n\n/**\n * Get the current total amount of RAM used by cached files.\n * This does not include structure overhead.\n *\n * @param vfs         VFS handle.\n * @return            the total amount of RAM used.\n */\nsize_t vfs_get_cache_total_size(vfilesystem *vfs)\n{\n  size_t sz = 0;\n\n  if(vfs_read_lock(vfs))\n  {\n    sz = vfs->cache_total;\n    vfs_read_unlock(vfs);\n  }\n  return sz;\n}\n\n/**\n * Get the total memory usage for the entire VFS.\n * This may be a slow operation.\n *\n * @param vfs         VFS handle.\n * @return            the total amount of RAM used.\n */\nsize_t vfs_get_total_memory_usage(vfilesystem *vfs)\n{\n  size_t sz = 0;\n  size_t i;\n\n  if(vfs_read_lock(vfs))\n  {\n    sz += sizeof(vfilesystem);\n    sz += vfs->table_alloc * sizeof(struct vfs_inode *);\n\n    for(i = 0; i < vfs->table_length; i++)\n    {\n      struct vfs_inode *n = vfs->table[i];\n      if(!n)\n        continue;\n\n      sz += MAX(sizeof(struct vfs_inode), sizeof(struct vfs_inode_name_alloc));\n      if(n->flags & VFS_INODE_NAME_ALLOC)\n        sz += n->name_length + 1;\n\n      if(n->flags & VFS_INODE_DIR)\n        sz += n->length_alloc * sizeof(uint32_t);\n      else\n        sz += n->length_alloc;\n    }\n    vfs_read_unlock(vfs);\n  }\n  return sz;\n}\n\n/**\n * Sets whether or not the VFS should track timestamps for files\n * created or closed AFTER this function is called. This function exists\n * mainly for the unit tests.\n *\n * @param vfs         VFS handle.\n * @param enable      if true, track timestamps.\n */\nvoid vfs_set_timestamps_enabled(vfilesystem *vfs, boolean enable)\n{\n  vfs->disable_timestamp = !enable;\n}\n\n/**\n * Convert vfs_error codes to their errno equivalents.\n *\n * @param err         `vfs_error` to convert.\n * @return            equivalent `errno` value, or `EINVAL` if there isn't one.\n */\nint vfs_error_to_errno(enum vfs_error err)\n{\n  switch(err)\n  {\n    case VFS_EPERM:         return EPERM;\n    case VFS_ENOENT:        return ENOENT;\n    case VFS_EBADF:         return EBADF;\n    case VFS_ENOMEM:        return ENOMEM;\n    case VFS_EACCES:        return EACCES;\n    case VFS_EBUSY:         return EBUSY;\n    case VFS_EEXIST:        return EEXIST;\n    case VFS_ENOTDIR:       return ENOTDIR;\n    case VFS_EISDIR:        return EISDIR;\n    case VFS_EINVAL:        return EINVAL;\n    case VFS_ENOSPC:        return ENOSPC;\n    case VFS_ERANGE:        return ERANGE;\n    case VFS_ENAMETOOLONG:  return ENAMETOOLONG;\n    case VFS_ENOTEMPTY:     return ENOTEMPTY;\n    case VFS_ERR_UNKNOWN:   return 0;\n    case VFS_ERR_IS_CACHED: return 0;\n  }\n  if(err == 0)\n    return 0;\n\n  return EINVAL;\n}\n\n#endif /* VIRTUAL_FILESYSTEM */\n\n/**\n * Allocate and initialize a VFS.\n * (This function is enabled even when VIRTUAL_FILESYSTEM is not defined so\n * -pedantic doesn't complain about ISO C not allowing empty compilation units.)\n *\n * @return a `vfilesystem` handle on success, otherwise `NULL`. If VFS support\n *         is disabled, this function will always return `NULL`.\n */\nvfilesystem *vfs_init(void)\n{\n#ifdef VIRTUAL_FILESYSTEM\n  vfilesystem *vfs = (vfilesystem *)malloc(sizeof(vfilesystem));\n  if(vfs)\n  {\n    if(vfs_setup(vfs))\n      return vfs;\n\n    vfs_clear(vfs);\n    free(vfs);\n  }\n#endif\n  return NULL;\n}\n\n/**\n * Free a VFS handle created by `vfs_init`.\n *\n * @param vfs   VFS handle to free.\n */\nvoid vfs_free(vfilesystem *vfs)\n{\n#ifdef VIRTUAL_FILESYSTEM\n  vfs_clear(vfs);\n  free(vfs);\n#endif\n}\n"
  },
  {
    "path": "src/io/vfs.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2021-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * MZX virtual filesystem. This is intended mainly as a support layer\n * to be used internally by vio.c, but can also be used by itself.\n */\n\n#ifndef __IO_VFS_H\n#define __IO_VFS_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stdint.h>\n#include <stdlib.h>\n#include \"vfile.h\"\n\n#if defined(CONFIG_VFS) && !defined(NO_VIRTUAL_FILESYSTEM)\n#define VIRTUAL_FILESYSTEM\n#endif\n\n#if defined(_WIN32) || defined(CONFIG_DJGPP) || defined(__APPLE__)\n#define VIRTUAL_FILESYSTEM_CASE_INSENSITIVE\n#endif\n\n#if defined(_WIN32) || defined(CONFIG_DJGPP)\n#define VIRTUAL_FILESYSTEM_DOS_DRIVE\n#endif\n\n#if !defined(CONFIG_DJGPP)\n#define VIRTUAL_FILESYSTEM_PARALLEL\n#endif\n\n/* errno-equivalent codes since some platforms have non-POSIX conformant\n * values that broke things before (seen with Haiku ENOENT).\n * Use `vfs_error_to_errno` to convert to real errno values.\n */\nenum vfs_error\n{\n  VFS_EPERM         = 1,\n  VFS_ENOENT        = 2,\n  VFS_EBADF         = 9,\n  VFS_ENOMEM        = 12,\n  VFS_EACCES        = 13,\n  VFS_EBUSY         = 16,\n  VFS_EEXIST        = 17,\n  VFS_ENOTDIR       = 20,\n  VFS_EISDIR        = 21,\n  VFS_EINVAL        = 22,\n  VFS_ENOSPC        = 28,\n  VFS_ERANGE        = 34,\n  VFS_ENAMETOOLONG  = 36,\n  VFS_ENOTEMPTY     = 39,\n  /* Unknown internal error. */\n  VFS_ERR_UNKNOWN   = 65536,\n  /* An operation failed because it does not work on cached files. */\n  VFS_ERR_IS_CACHED = 65537\n};\n\nstruct vfs_dir_file\n{\n  char type;\n  char name[1];\n};\n\nstruct vfs_dir\n{\n  struct vfs_dir_file **files;\n  size_t num_files;\n};\n\nUTILS_LIBSPEC vfilesystem *vfs_init(void);\nUTILS_LIBSPEC void vfs_free(vfilesystem *vfs);\n\n#ifdef VIRTUAL_FILESYSTEM\n\nUTILS_LIBSPEC enum vfs_error vfs_make_root(vfilesystem *vfs, const char *name);\nUTILS_LIBSPEC enum vfs_error vfs_create_file_at_path(vfilesystem *vfs, const char *path);\n\nUTILS_LIBSPEC enum vfs_error vfs_open_if_exists(vfilesystem *vfs,\n const char *path, boolean is_write, uint32_t *inode);\nUTILS_LIBSPEC enum vfs_error vfs_close(vfilesystem *vfs, uint32_t inode);\nUTILS_LIBSPEC enum vfs_error vfs_truncate(vfilesystem *vfs, uint32_t inode);\nUTILS_LIBSPEC ssize_t vfs_filelength(vfilesystem *vfs, uint32_t inode);\nUTILS_LIBSPEC enum vfs_error vfs_lock_file_read(vfilesystem *vfs, uint32_t inode,\n const unsigned char **data, size_t *data_length);\nUTILS_LIBSPEC enum vfs_error vfs_unlock_file_read(vfilesystem *vfs, uint32_t inode);\nUTILS_LIBSPEC enum vfs_error vfs_lock_file_write(vfilesystem *vfs, uint32_t inode,\n unsigned char ***data, size_t **data_length, size_t **data_alloc);\nUTILS_LIBSPEC enum vfs_error vfs_unlock_file_write(vfilesystem *vfs, uint32_t inode);\n\nUTILS_LIBSPEC enum vfs_error vfs_chdir(vfilesystem *vfs, const char *path);\nUTILS_LIBSPEC enum vfs_error vfs_getcwd(vfilesystem *vfs, char *dest, size_t dest_len);\nUTILS_LIBSPEC enum vfs_error vfs_mkdir(vfilesystem *vfs, const char *path, int mode);\nUTILS_LIBSPEC enum vfs_error vfs_rename(vfilesystem *vfs, const char *oldpath,\n const char *newpath);\nUTILS_LIBSPEC enum vfs_error vfs_unlink(vfilesystem *vfs, const char *path);\nUTILS_LIBSPEC enum vfs_error vfs_rmdir(vfilesystem *vfs, const char *path);\nUTILS_LIBSPEC enum vfs_error vfs_access(vfilesystem *vfs, const char *path, int mode);\nUTILS_LIBSPEC enum vfs_error vfs_stat(vfilesystem *vfs, const char *path, struct stat *st);\n\nUTILS_LIBSPEC enum vfs_error vfs_readdir(vfilesystem *vfs, const char *path, struct vfs_dir *d);\nUTILS_LIBSPEC enum vfs_error vfs_readdir_free(struct vfs_dir *d);\n\nUTILS_LIBSPEC enum vfs_error vfs_invalidate_at_path(vfilesystem *vfs, const char *path);\nUTILS_LIBSPEC enum vfs_error vfs_invalidate_at_least(vfilesystem *vfs, size_t *amount_to_free);\nUTILS_LIBSPEC enum vfs_error vfs_invalidate_all(vfilesystem *vfs);\nUTILS_LIBSPEC enum vfs_error vfs_cache_directory(vfilesystem *vfs,\n const char *path, const struct stat *st);\nUTILS_LIBSPEC enum vfs_error vfs_cache_file(vfilesystem *vfs, const char *path,\n const void *data, size_t data_length);\nUTILS_LIBSPEC enum vfs_error vfs_cache_file_callback(vfilesystem *vfs,\n const char *path, size_t (*readfn)(void * RESTRICT, size_t, void * RESTRICT),\n void *priv, size_t data_length);\nUTILS_LIBSPEC size_t vfs_get_cache_total_size(vfilesystem *vfs);\nUTILS_LIBSPEC size_t vfs_get_total_memory_usage(vfilesystem *vfs);\nUTILS_LIBSPEC void vfs_set_timestamps_enabled(vfilesystem *vfs, boolean enable);\n\nUTILS_LIBSPEC int vfs_error_to_errno(enum vfs_error err);\n\n#else /* !VIRTUAL_FILESYSTEM */\n\nstatic inline enum vfs_error vfs_make_root(vfilesystem *v, const char *n)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_create_file_at_path(vfilesystem *v, const char *p)\n{ return VFS_EINVAL; }\n\nstatic inline enum vfs_error  vfs_open_if_exists(vfilesystem *v,\n const char *p, boolean w, uint32_t *i) { return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_close(vfilesystem *v, uint32_t i)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_truncate(vfilesystem *v, uint32_t i)\n{ return VFS_EINVAL; }\nstatic inline ssize_t vfs_filelength(vfilesystem *v, uint32_t i)\n{ return -VFS_EINVAL; }\nstatic inline enum vfs_error vfs_lock_file_read(vfilesystem *v, uint32_t i,\n const unsigned char **d, size_t *l) { return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_unlock_file_read(vfilesystem *v, uint32_t i)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_lock_file_write(vfilesystem * v, uint32_t i,\n unsigned char ***d, size_t **l, size_t **a) { return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_unlock_file_write(vfilesystem *v, uint32_t i)\n{ return VFS_EINVAL; }\n\nstatic inline enum vfs_error vfs_chdir(vfilesystem *v, const char *p)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_getcwd(vfilesystem *v, char *d, size_t l)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_mkdir(vfilesystem *v, const char *p, int m)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_rename(vfilesystem *v, const char *o, const char *n)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_unlink(vfilesystem *v, const char *p)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_rmdir(vfilesystem *v, const char *p)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_access(vfilesystem *v, const char *p, int m)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_stat(vfilesystem *v, const char *p, struct stat *s)\n{ return VFS_EINVAL; }\n\nstatic inline enum vfs_error vfs_readdir(vfilesystem *v, const char *p,\n struct vfs_dir *d) { return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_readdir_free(struct vfs_dir *d)\n{ return VFS_EINVAL; }\n\nstatic inline enum vfs_error vfs_invalidate_at_path(vfilesystem *v, const char *p)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_invalidate_at_least(vfilesystem *v, size_t *a)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_invalidate_all(vfilesystem *v)\n{ return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_cache_directory(vfilesystem *v, const char *p,\n const struct stat *st) { return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_cache_file(vfilesystem *v, const char *p,\n const void *d, size_t l) { return VFS_EINVAL; }\nstatic inline enum vfs_error vfs_cache_file_callback(vfilesystem *v,\n const char *p, size_t (*r)(void * RESTRICT, size_t, void * RESTRICT),\n void *pr, size_t l) { return VFS_EINVAL; }\nstatic inline size_t vfs_get_cache_total_size(vfilesystem *v) { return 0; }\nstatic inline size_t vfs_get_total_memory_usage(vfilesystem *vfs) { return 0; }\nstatic inline void vfs_set_timestamps_enabled(vfilesystem *v, boolean e) { }\n\nstatic inline int vfs_error_to_errno(enum vfs_error err) { return 0; }\n\n#endif /* !VIRTUAL_FILESYSTEM */\n\n__M_END_DECLS\n\n#endif /* __IO_VFS_H */\n"
  },
  {
    "path": "src/io/vio.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <assert.h>\n#include <dirent.h>\n#include <errno.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <sys/stat.h>\n\n#include \"../util.h\"\n#include \"memfile.h\"\n#include \"path.h\"\n#include \"vio.h\"\n#include \"vfs.h\"\n//#include \"zip.h\"\n\n#ifdef _WIN32\n#include \"vio_win32.h\"\n#else\n#include \"vio_posix.h\"\n#endif\n\n#ifndef VFILE_SMALL_BUFFER_SIZE\n#define VFILE_SMALL_BUFFER_SIZE 256\n#endif\n\n#ifndef VFILE_LARGE_BUFFER_SIZE\n#define VFILE_LARGE_BUFFER_SIZE 32768\n#endif\n\n// If 0 is provided as a cache maximum size, use this value instead.\n#define DEFAULT_MAX_SIZE (1<<22)\n\nstruct vfile\n{\n  FILE *fp;\n\n  struct memfile mf;\n  // Local copy of pointer/size of a memory vfile buffer (expandable or not).\n  void *local_buffer;\n  size_t local_buffer_size;\n  // External copy of pointer/size of expandable vfile buffer.\n  void **external_buffer;\n  size_t *external_buffer_size;\n  // Virtual file--inode and position.\n  size_t *v_length;\n  size_t position;\n  size_t position_writeback;\n  uint32_t inode;\n\n  // vungetc buffer for memory files.\n  int tmp_chr;\n  int flags;\n  // To avoid arithmetic on NULL...\n  char dummy[1];\n};\n\n\n/************************************************************************\n * Virtual filesystem support functions.\n */\n\n// Letting the optimizer deal with this is cleaner than macros everywhere...\n#ifdef VIRTUAL_FILESYSTEM\nstatic vfilesystem *vfs_base = NULL;\n#else\n#define vfs_base ((vfilesystem *)NULL)\n#endif\nstatic size_t vfs_max_size = 0;\nstatic size_t vfs_max_auto_cache_file_size = 0;\nstatic boolean vfs_enable_auto_cache = false;\n\n/**\n * Recursively cache the provided directory path if it doesn't exist in\n * the cache, including the root. This function will cache as much of `path`\n * as possible, starting from the root and working down the directory tree.\n * Returns true if the directory is already in the VFS or if the entire\n * path is successfully cached.\n */\nstatic boolean vio_cache_directory_recursively(vfilesystem *vfs, const char *path)\n{\n  char tmp[MAX_PATH];\n  char *next;\n  struct stat st;\n  ssize_t len;\n  enum vfs_error code;\n  int ret;\n\n  // Does it already exist in the VFS?\n  code = vfs_stat(vfs, path, &st);\n  if(code == 0 || code == VFS_ERR_IS_CACHED)\n  {\n    trace(\"--VIO-- vio_cache_directory_recursively: '%s' exists\\n\", path);\n    return true;\n  }\n\n/*\n  // Is it a real directory?\n  ret = platform_stat(path, &st);\n  if(ret < 0 || !S_ISDIR(st.st_mode))\n    return false;\n*/\n\n  // Initialize root.\n  path_clean_copy(tmp, MAX_PATH, path);\n  next = tmp;\n  len = path_is_absolute(tmp);\n  if(len > 1)\n  {\n#ifdef PATH_DOS_STYLE_ROOTS\n    int skip = 0;\n    int colon_pos = -1;\n\n    // Root should end in :/, unless CWD is the root, in which\n    // case it may end with just a :.\n    if(tmp[len - 1] == ':')\n      colon_pos = len - 1;\n    if(tmp[len - 2] == ':')\n      colon_pos = len - 2;\n    if(colon_pos < 0)\n    {\n      trace(\"--VIO-- vio_cache_directory_recursively: missing colon (%s)\\n\", path);\n      return false;\n    }\n\n#ifdef PATH_UNC_ROOTS\n    // Reject UNC host/share paths as network files should be assumed volatile.\n    // Allow local file UNC prefixes.\n    if(isslash(tmp[0]))\n    {\n      if(len > 4 && (!memcmp(tmp, \"\\\\\\\\.\\\\\", 4) || !memcmp(tmp, \"\\\\\\\\?\\\\\", 4)) &&\n       strncasecmp(tmp + 4, \"unc\\\\\", 4))\n      {\n        next += 4;\n        skip = 4;\n      }\n      else\n      {\n        trace(\"--VIO-- vio_cache_directory_recursively: rejecting UNC (%s)\\n\", path);\n        return false;\n      }\n    }\n#endif\n\n    path_tokenize(&next);\n    tmp[colon_pos] = '\\0';\n\n    code = vfs_make_root(vfs, tmp + skip);\n    if(code != 0 && code != VFS_EEXIST)\n    {\n      trace(\"--VIO-- vio_cache_directory_recursively: failed vfs_make_root '%s' (%s)\\n\",\n       tmp + skip, path);\n      return false;\n    }\n\n    // Repair current fragment.\n    tmp[colon_pos] = ':';\n    if(colon_pos < len - 1)\n      tmp[len - 1] = DIR_SEPARATOR_CHAR;\n#else\n    // DOS-style roots are only supported as VFS roots and should never\n    // make it here!\n    trace(\"--VIO-- vio_cache_directory_recursively: unsupported DOS-style root (%s)\\n\", path);\n    return false;\n#endif\n  }\n  else\n\n  if(tmp[0] == '/')\n    next++;\n\n  // Initialize directories.\n  while(next)\n  {\n    path_tokenize(&next);\n\n    ret = platform_stat(tmp, &st);\n    if(ret < 0 || !S_ISDIR(st.st_mode))\n    {\n      trace(\"--VIO-- vio_cache_directory_recursively: stat failed or is not dir '%s' (%s)\\n\",\n       tmp, path);\n      return false;\n    }\n\n    code = vfs_cache_directory(vfs, tmp, &st);\n    if(code != 0 && code != VFS_EEXIST)\n    {\n      trace(\"--VIO-- vio_cache_directory_recursively: failed vfs_cache_directory '%s' (%s)\\n\",\n       tmp, path);\n      return false;\n    }\n\n    // Repair current fragment.\n    if(next)\n      next[-1] = DIR_SEPARATOR_CHAR;\n  }\n  trace(\"--VIO-- vio_cache_directory_recursively: '%s' OK\\n\", path);\n  return true;\n}\n\n/**\n * Get the parent directory from the provided path and cache it recursively,\n * including the root. The parent must successfully stat. If there is no parent\n * component of the path, this function automatically succeeds.\n */\nstatic boolean vio_cache_parent_recursively(vfilesystem *vfs, const char *path)\n{\n  char parent[MAX_PATH];\n\n  // If there is no parent component, there's nothing else to do here.\n  if(path_get_parent(parent, MAX_PATH, path) <= 0)\n    return true;\n\n  return vio_cache_directory_recursively(vfs, parent);\n}\n\nstatic size_t cache_file_read_fn(void * RESTRICT dest, size_t nbytes,\n void * RESTRICT priv)\n{\n  FILE *fp = (FILE *)priv;\n  return fread(dest, 1, nbytes, fp);\n}\n\n/**\n * Cache the file at the provided path and its parent directory (recursively).\n * The file should already be opened with stdio.\n */\nstatic boolean vio_cache_file_and_parent(vfilesystem *vfs, const char *path,\n FILE *fp, int flags)\n{\n  struct stat st;\n  int64_t len;\n  int64_t pos;\n  enum vfs_error code;\n\n  // Already cached...\n  if(vfs_stat(vfs, path, &st) == 0 && S_ISREG(st.st_mode))\n    return true;\n\n  len = platform_filelength(fp);\n  if(len < 0 || (size_t)len >= SIZE_MAX)\n    return false;\n\n  // Can't cache files over the individual file size limit.\n  if((size_t)len >= vfs_max_auto_cache_file_size && (~flags & V_FORCE_CACHE))\n    return false;\n\n  // If there isn't enough space, try to free some.\n  if(vfs_max_size)\n  {\n    size_t current = vfs_get_cache_total_size(vfs);\n    size_t avail = vfs_max_size > current ? vfs_max_size - current : 0;\n    size_t to_free = (size_t)len > avail ? len - avail : 0;\n\n    if(to_free)\n      vfs_invalidate_at_least(vfs, &to_free);\n\n    // If it wasn't able to free enough space, just fail.\n    if(to_free && (~flags & V_FORCE_CACHE))\n      return false;\n  }\n\n  if(!vio_cache_parent_recursively(vfs, path))\n    return false;\n\n  pos = platform_ftell(fp);\n  code = vfs_cache_file_callback(vfs, path, cache_file_read_fn, fp, len);\n  platform_fseek(fp, pos, SEEK_SET);\n  return code == 0;\n}\n\n/**\n * Given a relative or absolute path, convert it to an absolute path if the\n * current working directory is virtual. This is necessary because the current\n * real directory is desyncronized from the current VFS directory (the current\n * VFS directory does not exist in the real filesystem). This function will\n * always clobber the contents of the provided buffer.\n *\n * TODO: if a platform needs chdir/getcwd emulation, this would be a good\n * place to insert relative path conversion for that too, since all functions\n * which take paths need to use this when the VFS is enabled.\n */\nstatic const char *vio_normalize_virtual_path(vfilesystem *vfs,\n char *buffer, size_t buffer_len, const char *path)\n{\n  // Cached current directory will return VFS_ERR_IS_CACHED instead.\n  if(vfs_getcwd(vfs, buffer, buffer_len) == 0)\n  {\n    path_navigate_no_check(buffer, buffer_len, path);\n    return buffer;\n  }\n  return path;\n}\n\n/**\n * Enables the virtual filesystem for all vio.c functions. The virtual\n * filesystem provides virtual files and file caching. This function\n * is not thread-safe. If the virtual filesystem is already initialized,\n * this function will update the cache settings.\n *\n * @param max_size            soft limit for the maximum total size of cached\n *                            files in the VFS. In some cases, the total size\n *                            may be allowed exceed this value. If 0 is provided\n *                            a default maximum size will be used instead.\n * @param max_file_size       maximum size of files allowed to be automatically\n *                            cached, if enabled. If 0 is provided, this will\n *                            default to `max_size`.\n * @param enable_auto_cache   if `true`, files will be automatically cached.\n * @return                    `true` on success, otherwise `false`.\n */\nboolean vio_filesystem_init(size_t max_size, size_t max_file_size,\n boolean enable_auto_cache)\n{\n  char tmp[MAX_PATH];\n  enum vfs_error code;\n\n  trace(\"--VIO-- vio_filesystem_init\\n\");\n  if(!vfs_base)\n  {\n#ifdef VIRTUAL_FILESYSTEM\n    vfs_base = vfs_init();\n#endif\n    if(!vfs_base)\n    {\n      trace(\"--VIO-- vio_filesystem_init: failed vfs_init\\n\");\n      return false;\n    }\n  }\n\n  // Synchronize the current working directory.\n  if(!getcwd(tmp, MAX_PATH))\n  {\n    trace(\"--VIO-- vio_filesystem_init: failed getcwd\\n\");\n    goto err;\n  }\n  if(!vio_cache_directory_recursively(vfs_base, tmp))\n  {\n    trace(\"--VIO-- vio_filesystem_init: \"\n     \"failed vio_cache_directory_recursively: %s\\n\", tmp);\n    goto err;\n  }\n  code = vfs_chdir(vfs_base, tmp);\n  if(code != VFS_ERR_IS_CACHED)\n  {\n    trace(\"--VIO-- vio_filesystem_init: failed vfs_chdir with code %u: %s\\n\",\n     code, strerror(vfs_error_to_errno(code)));\n    goto err;\n  }\n\n  // Immediately purge the cache if it has been disabled.\n  if(vfs_enable_auto_cache && !enable_auto_cache)\n    vfs_invalidate_all(vfs_base);\n\n  vfs_max_size = max_size ? max_size : DEFAULT_MAX_SIZE;\n  vfs_max_auto_cache_file_size = max_file_size ? max_file_size : vfs_max_size;\n  vfs_enable_auto_cache = enable_auto_cache;\n\n  trace(\"--VIO-- vio_filesystem_init: success\\n\");\n  return true;\n\nerr:\n#ifdef VIRTUAL_FILESYSTEM\n  vfs_free(vfs_base);\n  vfs_base = NULL;\n#endif\n  return false;\n}\n\n/**\n * Disable the virtual filesystem for all vio.c functions and free all memory\n * associated with it. This function is not thread-safe and should be called\n * only when all other threads are finished using vio.c functions.\n *\n * @return                    `true` on success.\n */\nboolean vio_filesystem_exit(void)\n{\n#ifdef VIRTUAL_FILESYSTEM\n  if(vfs_base)\n    vfs_free(vfs_base);\n\n  vfs_base = NULL;\n#endif\n  return true;\n}\n\n/**\n * Get the total memory usage for the contents of cached files ONLY\n * the vio.c virtual filesystem.\n *\n * @return                    the total amount of memory used.\n */\nsize_t vio_filesystem_total_cached_usage(void)\n{\n  if(vfs_base)\n    return vfs_get_cache_total_size(vfs_base);\n  return 0;\n}\n\n/**\n * Get the total memory usage of the vio.c virtual filesystem,\n * INCLUDING cached files.\n *\n * @return                    the total amount of memory used.\n */\nsize_t vio_filesystem_total_memory_usage(void)\n{\n  if(vfs_base)\n    return vfs_get_total_memory_usage(vfs_base);\n  return 0;\n}\n\n/**\n * Create a virtual file in the VFS which is not backed by a physical file.\n * If a physical file exists at the provided path, it will be masked.\n * If the file already exists in the virtual filesystem, this function will\n * return true.\n *\n * The parent directory of the path must be a real directory or an existing\n * virtual directory.\n *\n * @param                     path to create a new file at.\n * @return                    `true` on success, otherwise `false`.\n */\nboolean vio_virtual_file(const char *path)\n{\n  enum vfs_error code;\n  if(!vfs_base)\n    return false;\n\n  if(!vio_cache_parent_recursively(vfs_base, path))\n    return false;\n\n  code = vfs_create_file_at_path(vfs_base, path);\n  if(code != 0 && code != VFS_EEXIST)\n    return false;\n\n  return true;\n}\n\n/**\n * Create a virtual directory in the VFS which is not backed by a physical file.\n * If a physical file exists at the provided path, it will be masked.\n * If the directory already exists in the virtual filesystem, this function\n * will return true.\n *\n * The parent directory of the path must be a real directory or an existing\n * virtual directory.\n *\n * @param                     path to create a new directory at.\n * @return                    `true` on success, otherwise `false`.\n */\nboolean vio_virtual_directory(const char *path)\n{\n  enum vfs_error code;\n  if(!vfs_base)\n    return false;\n\n  if(!vio_cache_parent_recursively(vfs_base, path))\n    return false;\n\n  code = vfs_mkdir(vfs_base, path, 0755);\n  if(code != 0 && code != VFS_EEXIST)\n    return false;\n\n  return true;\n}\n\n/**\n * Invalidate and purge cached files in the VFS until at least\n * the amount of memory pointed to by `amount_to_free` has been invalided.\n */\nboolean vio_invalidate_at_least(size_t *amount_to_free)\n{\n#ifdef DEBUG\n  size_t init = amount_to_free ? *amount_to_free : 0;\n#endif\n  enum vfs_error code;\n  if(!vfs_base || !amount_to_free)\n    return false;\n\n  code = vfs_invalidate_at_least(vfs_base, amount_to_free);\n  if(code != 0)\n    return false;\n\n  debug(\"vio_invalidate_at_least freed >= %zu buffered\\n\", init - *amount_to_free);\n  if(*amount_to_free > 0)\n    return false;\n\n  return true;\n}\n\n/**\n * Invalidate and purge ALL cached files in the VFS.\n */\nboolean vio_invalidate_all(void)\n{\n  if(!vfs_base)\n    return false;\n\n  debug(\"vio_invalidate_all\\n\");\n  if(vfs_invalidate_all(vfs_base) != 0)\n    return false;\n\n  return true;\n}\n\n\n/************************************************************************\n * vfile functions and stdio/unistd wrappers.\n */\n\n/**\n * Parse vfile mode flags from a standard fopen mode string.\n */\nint vfile_get_mode_flags(const char *mode)\n{\n  int flags = 0;\n\n  assert(mode);\n\n  switch(*(mode++))\n  {\n    case 'r':\n      flags |= VF_READ;\n      break;\n\n    case 'w':\n      flags |= VF_WRITE | VF_TRUNCATE;\n      break;\n\n    case 'a':\n      flags |= VF_WRITE | VF_APPEND;\n      break;\n\n    default:\n      return 0;\n  }\n\n  while(*mode)\n  {\n    switch(*(mode++))\n    {\n      case 'b':\n        flags |= VF_BINARY;\n        break;\n\n      // Explicitly \"text\" mode. Does nothing, but some libcs support it.\n      case 't':\n        break;\n\n      case '+':\n        flags |= (VF_READ | VF_WRITE);\n        break;\n\n      default:\n        return 0;\n    }\n  }\n  return flags;\n}\n\n/**\n * Diagnostic function to get the internal flags of a vfile.\n * Doesn't have much use outside of the unit tests.\n *\n * @param  vf     vfile handle.\n * @return        the internal flags of the provided vfile.\n */\nint vfile_get_flags(vfile *vf)\n{\n  return vf ? vf->flags : 0;\n}\n\n/**\n * Internal function for opening a file from the VFS.\n * filename should already have been normalized by the caller.\n */\nstatic enum vfs_error vfopen_virtual(vfilesystem *vfs, vfile *vf,\n const char *filename, int flags)\n{\n  uint32_t inode;\n  enum vfs_error code;\n\n  code = vfs_open_if_exists(vfs_base, filename, !!(flags & VF_WRITE), &inode);\n  if(code != 0 && code != VFS_ERR_IS_CACHED)\n    return code;\n\n  if(flags & VF_TRUNCATE)\n    vfs_truncate(vfs_base, inode);\n\n  vf->flags |= VF_MEMORY | VF_MEMORY_EXPANDABLE | VF_VIRTUAL;\n  vf->inode = inode;\n  vf->position = (flags & VF_APPEND) ? vfs_filelength(vfs, inode) : 0;\n  vf->position_writeback = vf->position;\n  return code;\n}\n\n/**\n * Open a file for input or output with user-defined flags.\n */\nvfile *vfopen_unsafe_ext(const char *filename, const char *mode,\n int user_flags)\n{\n  char buffer[MAX_PATH];\n  int flags = vfile_get_mode_flags(mode);\n  enum vfs_error code;\n  vfile *vf;\n  FILE *fp;\n\n  assert(filename && flags);\n  flags |= (user_flags & VF_PUBLIC_MASK);\n\n  vf = (vfile *)calloc(1, sizeof(vfile));\n  vf->tmp_chr = EOF;\n  vf->flags = flags;\n\n  if(vfs_base)\n  {\n    filename = vio_normalize_virtual_path(vfs_base, buffer, MAX_PATH, filename);\n    code = vfopen_virtual(vfs_base, vf, filename, flags);\n    // File is 100% virtual? Can exit now.\n    if(code == 0)\n      return vf;\n    // Non-write and cached? Don't need a FILE handle for that either...\n    if(code == VFS_ERR_IS_CACHED && (~flags & VF_WRITE))\n      return vf;\n  }\n\n  // Open the vfile as a normal file.\n  fp = platform_fopen_unsafe(filename, mode);\n  if(!fp)\n  {\n    if(vfs_base && vf->inode)\n    {\n      vfs_close(vfs_base, vf->inode);\n      vfs_invalidate_at_path(vfs_base, filename);\n    }\n    free(vf);\n    return NULL;\n  }\n\n#ifndef VFILE_NO_SETVBUF\n  if((flags & VF_BINARY) && (!(flags & VF_READ) || !(flags & VF_WRITE)))\n  {\n    if(flags & V_LARGE_BUFFER)\n    {\n      setvbuf(fp, NULL, _IOFBF, VFILE_LARGE_BUFFER_SIZE);\n      flags &= ~V_SMALL_BUFFER;\n    }\n    else\n\n    if(flags & V_SMALL_BUFFER)\n      setvbuf(fp, NULL, _IOFBF, VFILE_SMALL_BUFFER_SIZE);\n  }\n  else\n#endif\n    flags &= ~(V_SMALL_BUFFER | V_LARGE_BUFFER);\n\n  vf->fp = fp;\n  vf->flags |= VF_FILE;\n\n  if(vfs_base && !vf->inode && (~flags & V_DONT_CACHE))\n  {\n    if(vfs_enable_auto_cache || (flags & V_FORCE_CACHE))\n    {\n      if(vio_cache_file_and_parent(vfs_base, filename, fp, flags))\n      {\n        // In the rare case this fails, the vfile will just fall back to stdio.\n        vfopen_virtual(vfs_base, vf, filename, flags);\n      }\n    }\n  }\n  return vf;\n}\n\n/**\n * Open a file for input or output.\n */\nvfile *vfopen_unsafe(const char *filename, const char *mode)\n{\n  return vfopen_unsafe_ext(filename, mode, 0);\n}\n\n/**\n * Create a vfile from an existing fp.\n */\nvfile *vfile_init_fp(FILE *fp, const char *mode)\n{\n  int flags = vfile_get_mode_flags(mode);\n  vfile *vf = NULL;\n\n  assert(fp && flags);\n\n  vf = (vfile *)ccalloc(1, sizeof(vfile));\n  vf->fp = fp;\n  vf->tmp_chr = EOF;\n  vf->flags = flags | VF_FILE;\n  return vf;\n}\n\n/**\n * Create a vfile from an existing memory buffer.\n */\nvfile *vfile_init_mem(void *buffer, size_t size, const char *mode)\n{\n  int flags = vfile_get_mode_flags(mode);\n  vfile *vf = NULL;\n  size_t filesize = size;\n\n  assert((buffer && size) || (!buffer && !size));\n  assert(flags);\n\n  // \"w\"-based modes should start at size 0 and expand as-needed, either in\n  // their fixed buffer or with an extendable buffer (see vfile_init_mem_ext).\n  if(flags & VF_TRUNCATE)\n    filesize = 0;\n\n  vf = (vfile *)ccalloc(1, sizeof(vfile));\n  mfopen_wr(buffer ? buffer : vf->dummy, filesize, &(vf->mf));\n  vf->mf.seek_past_end = true;\n  vf->tmp_chr = EOF;\n  vf->flags = flags | VF_MEMORY;\n  vf->local_buffer = buffer;\n  vf->local_buffer_size = size;\n  return vf;\n}\n\n/**\n * Create a vfile from an existing memory buffer.\n * This vfile will be resizable and, when resized, the source pointer and size\n * will be updated to match.\n *\n  // vfs_init_mem_ext needs to be modified to allow readable files from\n  // external buffers. attempt to sync vfile to external buffer for each op\n  // also need a way to insert the inode into the vfile from here.\n */\nvfile *vfile_init_mem_ext(void **external_buffer, size_t *external_buffer_size,\n const char *mode)\n{\n  vfile *vf = vfile_init_mem(*external_buffer, *external_buffer_size, mode);\n\n  assert(vf->flags & VF_WRITE);\n\n  vf->flags |= VF_MEMORY_EXPANDABLE;\n  vf->external_buffer = external_buffer;\n  vf->external_buffer_size = external_buffer_size;\n  return vf;\n}\n\n/**\n * Create a temporary vfile.\n * If `initial_size` is non-zero, this function will attempt to allocate an\n * expandable temporary file in RAM with this size. If allocation fails or if\n * `initial_size` is zero, tmpfile() will be called instead.\n */\nvfile *vtempfile(size_t initial_size)\n{\n  FILE *fp;\n  vfile *vf;\n\n  if(initial_size)\n  {\n    void *buffer = malloc(initial_size);\n    if(buffer)\n    {\n      vf = vfile_init_mem(buffer, initial_size, \"wb+\");\n      vf->flags |= VF_MEMORY_EXPANDABLE | VF_MEMORY_FREE;\n      vf->local_buffer = buffer;\n      vf->local_buffer_size = initial_size;\n      return vf;\n    }\n  }\n\n  fp = platform_tmpfile();\n  if(fp)\n  {\n    setvbuf(fp, NULL, _IOFBF, VFILE_SMALL_BUFFER_SIZE);\n    return vfile_init_fp(fp, \"wb+\");\n  }\n\n  return NULL;\n}\n\n/**\n * Force an open vfile entirely into RAM. This function will ALWAYS rewind.\n * If this is a regular vfile in READ mode, attempt to copy it into RAM.\n * If this is a cached vfile in READ mode, just close the FILE if it's open.\n * If this is a memory or virtual vfile, nothing needs to be done.\n * Returns false if the vfile can not be moved to memory or is in write mode.\n */\nboolean vfile_force_to_memory(vfile *vf)\n{\n  assert(vf);\n  vrewind(vf);\n  // This only works in read mode--write mode needs to be able to write\n  // back, unless it's a virtual file, in which case\n  if(~vf->flags & VF_FILE)\n  {\n    // Already entirely in memory.\n    if(vf->flags & VF_MEMORY)\n      return true;\n    // Broken handle :(\n    return false;\n  }\n  // If there's an underlying FILE, it can only be closed in read mode.\n  if(vf->flags & VF_WRITE)\n    return false;\n\n  // FILE and memory -> cached, close the underlying FILE.\n  // If there is no memory, it needs to be loaded to memory first.\n  if(~vf->flags & VF_MEMORY)\n  {\n    int64_t len = vfilelength(vf, false);\n    void *buffer;\n\n    if(len < 0 || (size_t)len >= SIZE_MAX)\n      return false;\n\n    buffer = malloc(len);\n    if(!buffer)\n      return false;\n\n    if(!vfread(buffer, len, 1, vf))\n    {\n      free(buffer);\n      return false;\n    }\n\n    mfopen(buffer, len, &(vf->mf));\n    vf->mf.seek_past_end = true;\n    vf->tmp_chr = EOF;\n    vf->flags |= VF_MEMORY | VF_MEMORY_FREE;\n    vf->local_buffer = buffer;\n    vf->local_buffer_size = len;\n  }\n\n  fclose(vf->fp);\n  vf->flags &= ~VF_FILE;\n  vf->fp = NULL;\n  return true;\n}\n\n/**\n * Close a file. The file pointer should not be used after using this function.\n */\nint vfclose(vfile *vf)\n{\n  int retval = 0;\n\n  assert(vf);\n\n  if(vfs_base && vf->inode)\n  {\n    // Rewind to force writeback.\n    // TODO: kind of a crap way to do this, might need to add vfflush.\n    vrewind(vf);\n    vfs_close(vfs_base, vf->inode);\n  }\n\n  if((vf->flags & VF_MEMORY) && (vf->flags & VF_MEMORY_FREE))\n  {\n    free(vf->local_buffer);\n    if(vf->external_buffer)\n    {\n      // Make sure these didn't desync somehow...\n      assert(vf->local_buffer == *(vf->external_buffer));\n      *(vf->external_buffer) = NULL;\n    }\n  }\n\n  if(vf->flags & VF_FILE)\n    retval = fclose(vf->fp);\n\n  free(vf);\n  return retval;\n}\n\n\n/************************************************************************\n * Filesystem function wrappers.\n */\n\n/**\n * Change the current working directory to path.\n */\nint vchdir(const char *path)\n{\n  if(vfs_base)\n  {\n    char buffer[MAX_PATH];\n    enum vfs_error code;\n    int ret;\n\n    path = vio_normalize_virtual_path(vfs_base, buffer, MAX_PATH, path);\n    // If this is also a virtual path, this will succeed. The vfs_access\n    // call filters out cached directories, which vfs_chdir DOES work on.\n    if(vfs_access(vfs_base, path, R_OK) == 0 && vfs_chdir(vfs_base, path) == 0)\n      return 0;\n\n    // Special: the current directory must exist in the VFS.\n    vio_cache_directory_recursively(vfs_base, path);\n\n    // If this is a real path, this function will succeed, in which case\n    // the VFS current directory should also be updated.\n    ret = platform_chdir(path);\n    if(ret == 0)\n    {\n      code = vfs_chdir(vfs_base, path);\n      assert(code == VFS_ERR_IS_CACHED);\n      (void)code; // silence -Wunused-but-set-variable\n    }\n\n    return ret;\n  }\n  return platform_chdir(path);\n}\n\n/**\n * Print the name of the current working directory to buffer 'buf' of\n * length 'size'.\n */\nchar *vgetcwd(char *buf, size_t size)\n{\n  if(vfs_base)\n  {\n    // If cwd is a virtual path, return the VFS cwd.\n    // Cached real cwds will return VFS_ERR_IS_CACHED.\n    if(vfs_getcwd(vfs_base, buf, size) == 0)\n      return buf;\n  }\n  return platform_getcwd(buf, size);\n}\n\n/**\n * Make a directory with a given name.\n */\nint vmkdir(const char *path, int mode)\n{\n  if(vfs_base)\n  {\n    char buffer[MAX_PATH];\n    enum vfs_error code;\n    int ret;\n\n    path = vio_normalize_virtual_path(vfs_base, buffer, MAX_PATH, path);\n\n    // Special: this function prefers real mkdir over vfs_mkdir.\n    // Use vio_virtual_directory to force creation of virtual directories.\n    ret = platform_mkdir(path, mode);\n    if(ret == 0 || errno != ENOENT)\n      return ret;\n\n    code = vfs_mkdir(vfs_base, path, mode);\n    if(code != 0)\n    {\n      errno = vfs_error_to_errno(code);\n      return -1;\n    }\n    return 0;\n  }\n  return platform_mkdir(path, mode);\n}\n\n/**\n * Rename a file or directory.\n */\nint vrename(const char *oldpath, const char *newpath)\n{\n  if(vfs_base)\n  {\n    char buffer1[MAX_PATH];\n    char buffer2[MAX_PATH];\n    enum vfs_error code;\n    int ret;\n\n    oldpath = vio_normalize_virtual_path(vfs_base, buffer1, MAX_PATH, oldpath);\n    newpath = vio_normalize_virtual_path(vfs_base, buffer2, MAX_PATH, newpath);\n\n    // Preemptively cache the destination parent.\n    vio_cache_parent_recursively(vfs_base, newpath);\n\n    // Attempt vfs_rename, which will handle virtual source files\n    // moving, replacing cached files, or replacing virtual files.\n    // No filesystem operation needs to be done in this case.\n    code = vfs_rename(vfs_base, oldpath, newpath);\n    if(code == 0)\n      return 0;\n\n    if(code == VFS_EBUSY || code == VFS_ENOTDIR ||\n     code == VFS_EISDIR || code == VFS_EEXIST)\n    {\n      errno = vfs_error_to_errno(code);\n      return -1;\n    }\n\n    // Try a real rename...\n    ret = platform_rename(oldpath, newpath);\n    if(ret != 0)\n    {\n      // (Try to) roll back the vfs_rename.\n      // This unfortunately won't bring back the old dir/file at newpath.\n      vfs_rename(vfs_base, newpath, oldpath);\n    }\n    return ret;\n  }\n  return platform_rename(oldpath, newpath);\n}\n\n/**\n * Unlink a name (and delete a file if it was the last link to the file) if\n * the file is a real file.\n */\nint vunlink(const char *path)\n{\n  if(vfs_base)\n  {\n    char buffer[MAX_PATH];\n    enum vfs_error code;\n    int ret;\n\n    path = vio_normalize_virtual_path(vfs_base, buffer, MAX_PATH, path);\n    code = vfs_unlink(vfs_base, path);\n    if(code == 0)\n      return 0;\n\n    // VFS inode exists and isn't cached, but there was an error.\n    if(code == VFS_EBUSY || code == VFS_EPERM)\n    {\n      errno = vfs_error_to_errno(code);\n      return -1;\n    }\n\n    ret = platform_unlink(path);\n    if(ret == 0)\n      vfs_invalidate_at_path(vfs_base, path);\n    return ret;\n  }\n  return platform_unlink(path);\n}\n\n/**\n * Remove an empty directory at a given path.\n */\nint vrmdir(const char *path)\n{\n  if(vfs_base)\n  {\n    char buffer[MAX_PATH];\n    enum vfs_error code;\n    int ret;\n\n    path = vio_normalize_virtual_path(vfs_base, buffer, MAX_PATH, path);\n    code = vfs_rmdir(vfs_base, path);\n    if(code == 0)\n      return 0;\n\n    // VFS inode exists and isn't cached, but there was an error.\n    if(code == VFS_EBUSY || code == VFS_ENOTDIR || code == VFS_ENOTEMPTY)\n    {\n      errno = vfs_error_to_errno(code);\n      return -1;\n    }\n\n    ret = platform_rmdir(path);\n    if(ret == 0)\n      vfs_invalidate_at_path(vfs_base, path);\n    return ret;\n  }\n  return platform_rmdir(path);\n}\n\n/**\n * Check an access permission for a path for the current process.\n *\n * F_OK   (checks existence only)\n * R_OK   Read permission.\n * W_OK   Write permission.\n * X_OK   Execute permission.\n */\nint vaccess(const char *path, int mode)\n{\n  if(vfs_base)\n  {\n    char buffer[MAX_PATH];\n    enum vfs_error code;\n\n    path = vio_normalize_virtual_path(vfs_base, buffer, MAX_PATH, path);\n    code = vfs_access(vfs_base, path, mode);\n    if(code == 0)\n      return 0;\n\n    // File exists and is virtual but permission was denied.\n    if(code == VFS_EACCES)\n    {\n      errno = vfs_error_to_errno(code);\n      return -1;\n    }\n  }\n  return platform_access(path, mode);\n}\n\n/**\n * Get file information for a file at a given path.\n */\nint vstat(const char *path, struct stat *buf)\n{\n  if(vfs_base)\n  {\n    char buffer[MAX_PATH];\n    struct stat tmp;\n    enum vfs_error code;\n\n    path = vio_normalize_virtual_path(vfs_base, buffer, MAX_PATH, path);\n    // Pass through success only.\n    code = vfs_stat(vfs_base, path, &tmp);\n    if(code == 0 || code == VFS_ERR_IS_CACHED)\n    {\n      memcpy(buf, &tmp, sizeof(struct stat));\n      return 0;\n    }\n  }\n  return platform_stat(path, buf);\n}\n\n\n/************************************************************************\n * stdio function wrappers.\n */\n\n/* If virtual IO fails, close the VFS file (if applicable)\n * and fall back to stdio. */\nstatic inline void vfile_cleanup_virtual(vfile *vf)\n{\n  if(vfs_base)\n    vfs_close(vfs_base, vf->inode);\n\n  vf->flags &= ~(VF_MEMORY | VF_VIRTUAL);\n  vf->inode = 0;\n\n  if(vf->fp)\n  {\n    size_t pos = vf->position;\n    int buf = vf->tmp_chr;\n    vrewind(vf);\n    vfseek(vf, pos, SEEK_SET);\n    if(buf >= 0)\n      vungetc(buf, vf);\n  }\n  else\n    vf->flags &= ~VF_FILE;\n}\n\n/* Perform a writeback for cached vfiles. This should be performed\n * after the VFS file is read locked and the memfile is synchronized.\n * Returns true on success or if no writeback was required. */\nstatic inline boolean virt_writeback(vfile *vf)\n{\n  size_t count;\n  size_t avail;\n  size_t out;\n  const unsigned char *src;\n\n  // Detached vfiles and read-only vfiles can't perform a writeback operation.\n  if(!vf->inode || !vf->fp || (~vf->flags & VF_WRITE))\n    return true;\n\n  if(vf->position_writeback == vf->position)\n    return true;\n\n  // fseek to write position (also allows switching to write mode).\n  if(platform_fseek(vf->fp, vf->position_writeback, SEEK_SET) != 0)\n    return false;\n\n  // This shouldn't be able to happen--all backwards seeks call here.\n  assert(vf->position_writeback < vf->position);\n\n  // Attempt write...\n  src = vf->mf.start + vf->position_writeback;\n  if(src >= vf->mf.end)\n    return false;\n\n  avail = vf->mf.end - src;\n  count = vf->position - vf->position_writeback;\n  avail = MIN(avail, count);\n  out = fwrite(src, 1, avail, vf->fp);\n  fflush(vf->fp);\n\n  if(out < count)\n    return false;\n\n  return true;\n}\n\n/**\n * Begin a read or write operation on a VFS vfile.\n * This synchronizes the internal memfile and, if the VFS is no longer\n * available, attempts to fall back to FILE io.\n */\nstatic inline boolean virt_read(vfile *vf)\n{\n  const unsigned char *tmp = NULL;\n  size_t len = 0;\n  enum vfs_error code = VFS_EINVAL;\n  if(!vf->inode)\n    return false;\n\n  assert(vf->flags & VF_MEMORY);\n  if(vfs_base)\n    code = vfs_lock_file_read(vfs_base, vf->inode, &tmp, &len);\n  if(code != 0)\n  {\n    vfile_cleanup_virtual(vf);\n    return false;\n  }\n  mfopen(tmp, len, &(vf->mf));\n  vf->mf.seek_past_end = true;\n  mfseek(&(vf->mf), vf->position, SEEK_SET);\n  virt_writeback(vf);\n  return true;\n}\n\nstatic inline void virt_read_end(vfile *vf)\n{\n  if(!vfs_base || !vf->inode)\n    return;\n\n  assert(vf->flags & VF_MEMORY);\n  vfs_unlock_file_read(vfs_base, vf->inode);\n  vf->position = mftell(&(vf->mf));\n  vf->position_writeback = vf->position;\n}\n\nstatic inline boolean virt_write(vfile *vf)\n{\n  enum vfs_error code = VFS_EINVAL;\n  if(!vf->inode)\n    return false;\n\n  assert(vf->flags & VF_MEMORY);\n  if(vfs_base)\n  {\n    code = vfs_lock_file_write(vfs_base, vf->inode,\n     (unsigned char ***)&vf->external_buffer,\n     &vf->v_length, &vf->external_buffer_size);\n  }\n  if(code != 0)\n  {\n    vfile_cleanup_virtual(vf);\n    return false;\n  }\n  vf->local_buffer = *vf->external_buffer;\n  vf->local_buffer_size = *vf->external_buffer_size;\n  mfopen_wr(vf->local_buffer, *vf->v_length, &(vf->mf));\n  vf->mf.seek_past_end = true;\n  mfseek(&(vf->mf), vf->position, SEEK_SET);\n  return true;\n}\n\nstatic inline void virt_write_end(vfile *vf)\n{\n  if(!vfs_base || !vf->inode)\n    return;\n\n  assert(vf->flags & VF_MEMORY);\n\n  *(vf->v_length) = vf->mf.end - vf->mf.start;\n  vfs_unlock_file_write(vfs_base, vf->inode);\n  vf->position = mftell(&(vf->mf));\n}\n\n/**\n * Ensure an amount of space exists in a memory vfile or expand the vfile\n * (if possible). This should be used for writing only.\n */\nstatic inline boolean vfile_ensure_space(size_t amount_to_write, vfile *vf)\n{\n  struct memfile *mf = &(vf->mf);\n  size_t new_size;\n\n  assert(vf->flags & VF_MEMORY);\n\n  /* In append mode, all writes should occur at the end. */\n  if(vf->flags & VF_APPEND)\n    mf->current = mf->end;\n\n  if(!mfhasspace(amount_to_write, mf))\n  {\n    new_size = (mf->start ? (mf->current - mf->start) : 0) + amount_to_write;\n    if(new_size > vf->local_buffer_size)\n    {\n      size_t new_size_alloc = 32;\n      void *t;\n      int i;\n\n      if(!(vf->flags & VF_MEMORY_EXPANDABLE))\n        return false;\n\n      for(i = 0; i < 64 && new_size_alloc < new_size; i++)\n        new_size_alloc <<= 1;\n\n      if(i >= 64)\n        return false;\n\n      t = realloc(vf->local_buffer, new_size_alloc);\n      if(!t)\n        return false;\n\n      vf->local_buffer = t;\n      vf->local_buffer_size = new_size_alloc;\n\n      if(vf->external_buffer)\n        *(vf->external_buffer) = t;\n      if(vf->external_buffer_size)\n        *(vf->external_buffer_size) = new_size_alloc;\n    }\n    mfmove(vf->local_buffer, new_size, mf);\n\n    return (mf->end - mf->start) >= (ptrdiff_t)new_size;\n  }\n  return true;\n}\n\n/**\n * Get a direct memory access memfile of a given length starting at the current\n * position of a memory vfile. This should pretty much not be used, but some\n * things in the zip code already relied on it. If dest is NULL, a size check\n * (and potential expansion) will still be performed.\n */\nboolean vfile_get_memfile_block(vfile *vf, size_t length, struct memfile *dest)\n{\n  assert(vf);\n  assert(vf->flags & VF_MEMORY);\n  if(vf->flags & VF_WRITE)\n  {\n    if(!vfile_ensure_space(length, vf))\n      return false;\n  }\n  if(!mfhasspace(length, &(vf->mf)))\n    return false;\n\n  if(dest)\n    mfopen_wr(vf->mf.current, length, dest);\n  return true;\n}\n\n/**\n * Read a single byte from a file.\n */\nint vfgetc(vfile *vf)\n{\n  assert(vf);\n  assert(vf->flags & VF_READ);\n\n  if(virt_read(vf) || (vf->flags & VF_MEMORY))\n  {\n    int character = EOF;\n    if(vf->tmp_chr != EOF)\n    {\n      character = vf->tmp_chr;\n      vf->tmp_chr = EOF;\n    }\n    else\n\n    if(mfhasspace(1, &(vf->mf)))\n      character = mfgetc(&(vf->mf));\n\n    virt_read_end(vf);\n    return character;\n  }\n\n  if(vf->flags & VF_FILE)\n    return fgetc(vf->fp);\n\n  return EOF;\n}\n\n/**\n * Read two bytes from a file as an unsigned integer (little endian).\n */\nint vfgetw(vfile *vf)\n{\n  assert(vf);\n  assert(vf->flags & VF_READ);\n\n  if(virt_read(vf) || (vf->flags & VF_MEMORY))\n  {\n    int character = EOF;\n    if(vf->tmp_chr != EOF)\n    {\n      if(mfhasspace(1, &(vf->mf)))\n      {\n        character = vf->tmp_chr | (mfgetc(&(vf->mf)) << 8);\n        vf->tmp_chr = EOF;\n      }\n    }\n    else\n\n    if(mfhasspace(2, &(vf->mf)))\n      character = mfgetw(&(vf->mf));\n\n    virt_read_end(vf);\n    return character;\n  }\n\n  if(vf->flags & VF_FILE)\n  {\n    FILE *fp = vf->fp;\n    int a = fgetc(fp);\n    int b = fgetc(fp);\n\n    return (a != EOF) && (b != EOF) ? ((b << 8) | a) : EOF;\n  }\n\n  return EOF;\n}\n\n/**\n * Read four bytes from a file as a signed integer (little endian).\n */\nint vfgetd(vfile *vf)\n{\n  assert(vf);\n  assert(vf->flags & VF_READ);\n\n  if(virt_read(vf) || (vf->flags & VF_MEMORY))\n  {\n    int character = EOF;\n    if(vf->tmp_chr != EOF)\n    {\n      if(mfhasspace(3, &(vf->mf)))\n      {\n        character = vf->tmp_chr |\n         (mfgetc(&(vf->mf)) << 8) |\n         (mfgetw(&(vf->mf)) << 16);\n        vf->tmp_chr = EOF;\n      }\n    }\n    else\n\n    if(mfhasspace(4, &(vf->mf)))\n      character = mfgetd(&(vf->mf));\n\n    virt_read_end(vf);\n    return character;\n  }\n\n  if(vf->flags & VF_FILE)\n  {\n    FILE *fp = vf->fp;\n    int a = fgetc(fp);\n    int b = fgetc(fp);\n    int c = fgetc(fp);\n    int d = fgetc(fp);\n\n    if((a == EOF) || (b == EOF) || (c == EOF) || (d == EOF))\n      return EOF;\n\n    return ((uint32_t)d << 24) | (c << 16) | (b << 8) | a;\n  }\n\n  return EOF;\n}\n\n/**\n * Read eight bytes from a file as a signed integer (little endian).\n */\nint64_t vfgetq(vfile *vf)\n{\n  assert(vf);\n  assert(vf->flags & VF_READ);\n\n  if(virt_read(vf) || (vf->flags & VF_MEMORY))\n  {\n    int64_t character = EOF;\n    if(vf->tmp_chr != EOF)\n    {\n      if(mfhasspace(7, &(vf->mf)))\n      {\n        character = vf->tmp_chr |\n         ((int64_t)mfgetc(&(vf->mf)) << 8) |\n         ((int64_t)mfgetw(&(vf->mf)) << 16) |\n         ((int64_t)mfgetud(&(vf->mf)) << 32);\n        vf->tmp_chr = EOF;\n      }\n    }\n    else\n\n    if(mfhasspace(8, &(vf->mf)))\n      character = mfgetq(&(vf->mf));\n\n    virt_read_end(vf);\n    return character;\n  }\n\n  if(vf->flags & VF_FILE)\n  {\n    FILE *fp = vf->fp;\n    int a = fgetc(fp);\n    int b = fgetc(fp);\n    int c = fgetc(fp);\n    int d = fgetc(fp);\n    int e = fgetc(fp);\n    int f = fgetc(fp);\n    int g = fgetc(fp);\n    int h = fgetc(fp);\n\n    if((a == EOF) || (b == EOF) || (c == EOF) || (d == EOF) ||\n     (e == EOF) || (f == EOF) || (g == EOF) || (h == EOF))\n      return EOF;\n\n    return ((uint64_t)h << 56) | ((uint64_t)g << 48) | ((uint64_t)f << 40) |\n     ((uint64_t)e << 32) | ((uint32_t)d << 24) | (c << 16) | (b << 8) | a;\n  }\n\n  return EOF;\n}\n\n/**\n * Write a single byte to a file.\n */\nint vfputc(int character, vfile *vf)\n{\n  assert(vf);\n  assert(vf->flags & VF_WRITE);\n\n  if(virt_write(vf) || (vf->flags & VF_MEMORY))\n  {\n    if(vfile_ensure_space(1, vf))\n      character = mfputc(character, &(vf->mf));\n    else\n      character = EOF;\n\n    virt_write_end(vf);\n    return character;\n  }\n\n  if(vf->flags & VF_FILE)\n    return fputc(character, vf->fp);\n\n  return EOF;\n}\n\n/**\n * Write an unsigned short integer to a file (little endian).\n */\nint vfputw(int character, vfile *vf)\n{\n  assert(vf);\n  assert(vf->flags & VF_WRITE);\n\n  if(virt_write(vf) || (vf->flags & VF_MEMORY))\n  {\n    if(vfile_ensure_space(2, vf))\n      mfputw(character, &(vf->mf));\n    else\n      character = EOF;\n\n    virt_write_end(vf);\n    return character;\n  }\n\n  if(vf->flags & VF_FILE)\n  {\n    FILE *fp = vf->fp;\n    int a = fputc(character & 0xFF, fp);\n    int b = fputc((character >> 8) & 0xFF, fp);\n    return (a != EOF) && (b != EOF) ? character : EOF;\n  }\n\n  return EOF;\n}\n\n/**\n * Write a signed long integer to a file (little endian).\n */\nint vfputd(int character, vfile *vf)\n{\n  assert(vf);\n  assert(vf->flags & VF_WRITE);\n\n  if(virt_write(vf) || (vf->flags & VF_MEMORY))\n  {\n    if(vfile_ensure_space(4, vf))\n      mfputd(character, &(vf->mf));\n    else\n      character = EOF;\n\n    virt_write_end(vf);\n    return character;\n  }\n\n  if(vf->flags & VF_FILE)\n  {\n    FILE *fp = vf->fp;\n    int a = fputc(character & 0xFF, fp);\n    int b = fputc((character >> 8) & 0xFF, fp);\n    int c = fputc((character >> 16) & 0xFF, fp);\n    int d = fputc((character >> 24) & 0xFF, fp);\n\n    if((a == EOF) || (b == EOF) || (c == EOF) || (d == EOF))\n      return EOF;\n\n    return character;\n  }\n\n  return EOF;\n}\n\n/**\n * Write a signed 64-bit integer to a file (little endian).\n */\nint64_t vfputq(int64_t character, vfile *vf)\n{\n  assert(vf);\n  assert(vf->flags & VF_WRITE);\n\n  if(virt_write(vf) || (vf->flags & VF_MEMORY))\n  {\n    if(vfile_ensure_space(8, vf))\n      mfputq(character, &(vf->mf));\n    else\n      character = EOF;\n\n    virt_write_end(vf);\n    return character;\n  }\n\n  if(vf->flags & VF_FILE)\n  {\n    FILE *fp = vf->fp;\n    int a = fputc(character & 0xFF, fp);\n    int b = fputc((character >> 8) & 0xFF, fp);\n    int c = fputc((character >> 16) & 0xFF, fp);\n    int d = fputc((character >> 24) & 0xFF, fp);\n    int e = fputc((character >> 32) & 0xFF, fp);\n    int f = fputc((character >> 40) & 0xFF, fp);\n    int g = fputc((character >> 48) & 0xFF, fp);\n    int h = fputc((character >> 56) & 0xFF, fp);\n\n    if((a == EOF) || (b == EOF) || (c == EOF) || (d == EOF) ||\n     (e == EOF) || (f == EOF) || (g == EOF) || (h == EOF))\n      return EOF;\n\n    return character;\n  }\n\n  return EOF;\n}\n\n/**\n * Read an array from a file.\n */\nsize_t vfread(void *dest, size_t size, size_t count, vfile *vf)\n{\n  assert(vf);\n  assert(dest);\n  assert(vf->flags & VF_READ);\n\n  if(virt_read(vf) || (vf->flags & VF_MEMORY))\n  {\n    if(size && count && vf->tmp_chr != EOF)\n    {\n      char *d = (char *)dest;\n      *(d++) = vf->tmp_chr;\n      if(size > 1)\n      {\n        if(!mfread(d, size - 1, 1, &(vf->mf)))\n        {\n          virt_read_end(vf);\n          return 0;\n        }\n\n        d += size - 1;\n      }\n      vf->tmp_chr = EOF;\n      if(count > 1)\n        count = mfread(d, size, count - 1, &(vf->mf)) + 1;\n    }\n    else\n      count = mfread(dest, size, count, &(vf->mf));\n\n    virt_read_end(vf);\n    return count;\n  }\n\n  if(vf->flags & VF_FILE)\n    return fread(dest, size, count, vf->fp);\n\n  return 0;\n}\n\n/**\n * Write an array to a file.\n */\nsize_t vfwrite(const void *src, size_t size, size_t count, vfile *vf)\n{\n  assert(vf);\n  assert(src);\n  assert(vf->flags & VF_WRITE);\n\n  if(virt_write(vf) || (vf->flags & VF_MEMORY))\n  {\n    if(vfile_ensure_space(size * count, vf))\n      count = mfwrite(src, size, count, &(vf->mf));\n    else\n      count = 0;\n\n    virt_write_end(vf);\n    return count;\n  }\n\n  if(vf->flags & VF_FILE)\n  {\n#ifdef __DragonFly__\n    /* DragonFly BSD ignored the C standard on this: */\n    if(!size)\n      return 0;\n#endif\n    return fwrite(src, size, count, vf->fp);\n  }\n\n  return 0;\n}\n\n/**\n * Read a line from a file, safely trimming platform-specific line end chars\n * as-needed.\n */\nchar *vfsafegets(char *dest, int size, vfile *vf)\n{\n  assert(vf);\n  assert(dest);\n  assert(size > 1);\n  assert(vf->flags & VF_READ);\n\n  if(virt_read(vf) || (vf->flags & VF_MEMORY))\n  {\n    if(size && vf->tmp_chr != EOF)\n    {\n      int tmp = vf->tmp_chr;\n      vf->tmp_chr = EOF;\n      if(tmp == '\\r' || tmp == '\\n')\n      {\n        // If this \\r is part of a DOS line end, consume the corresponding \\n.\n        if(tmp == '\\r' && (mfhasspace(1, &(vf->mf)) && vf->mf.current[0] == '\\n'))\n          vf->mf.current++;\n        dest[0] = '\\0';\n      }\n      else\n      {\n        dest[0] = tmp;\n        dest[1] = '\\0';\n        mfsafegets(dest + 1, size - 1, &(vf->mf));\n      }\n    }\n    else\n      dest = mfsafegets(dest, size, &(vf->mf));\n\n    virt_read_end(vf);\n    return dest;\n  }\n\n  if(vf->flags & VF_FILE)\n  {\n    if(fgets(dest, size, vf->fp))\n    {\n      size_t len = strlen(dest);\n      while(len >= 1 && (dest[len - 1] == '\\r' || dest[len - 1] == '\\n'))\n      {\n        len--;\n        dest[len] = '\\0';\n      }\n      return dest;\n    }\n    return NULL;\n  }\n\n  return NULL;\n}\n\n/**\n * Write a null-terminated string to a file. The null terminator will not be\n * written to the file.\n */\nint vfputs(const char *src, vfile *vf)\n{\n  size_t len;\n  int ret;\n\n  assert(vf);\n  assert(src);\n  assert(vf->flags & VF_WRITE);\n\n  len = strlen(src);\n  if(!len)\n    return 0;\n\n  ret = vfwrite(src, len, 1, vf);\n  return (ret == 1) ? 0 : EOF;\n}\n\n/**\n * Print a formatted string with a variable number of parameters to a file.\n * This function internally uses vfprintf and vsnprintf.\n */\nint vf_printf(vfile *vf, const char *fmt, ...)\n{\n  va_list args;\n  int ret;\n\n  va_start(args, fmt);\n  ret = vf_vprintf(vf, fmt, args);\n  va_end(args);\n\n  return ret;\n}\n\n/**\n * Print a formatted string with a variable number of parameters in a va_list\n * to a file. This function internally uses vfprintf and vsnprintf.\n */\nint vf_vprintf(vfile *vf, const char *fmt, va_list args)\n{\n  assert(vf);\n  assert(fmt);\n  assert(vf->flags & VF_WRITE);\n\n  if(virt_write(vf) || (vf->flags & VF_MEMORY))\n  {\n    // Get the expected output length from the format string and args.\n    // This requires a duplicate arglist so vsnprintf can be called twice.\n    // va_copy is supported by MSVC 2015+.\n    va_list tmp_args;\n    int length;\n    char _buffer[512];\n    void *buffer;\n\n    va_copy(tmp_args, args);\n    length = vsnprintf(NULL, 0, fmt, tmp_args);\n    va_end(tmp_args);\n\n    // Note: vsnprintf will fail if this is actually MSVC's broken _vsnprintf.\n    // This shouldn't happen since only MinGW and MSVC 2015+ are supported.\n    // Alternatively, some errors cause it to return -1.\n    if(length < 0)\n    {\n      length = -1;\n      goto err;\n    }\n\n    // Write to a temporary buffer to avoid null terminators overwriting data\n    // and potential side effects of allocating extra vfile space. This is a\n    // little slower but less messy than the alternative.\n    if((size_t)length >= sizeof(_buffer))\n    {\n      buffer = malloc(length + 1);\n      if(!buffer)\n      {\n        length = -1;\n        goto err;\n      }\n    }\n    else\n      buffer = _buffer;\n\n    length = vsnprintf((char *)buffer, length + 1, fmt, args);\n\n    if(vfile_ensure_space(length, vf))\n    {\n      mfwrite(buffer, length, 1, &(vf->mf));\n    }\n    else\n      length = -1;\n\n    if(buffer != _buffer)\n      free(buffer);\n\n  err:\n    virt_write_end(vf);\n    return length;\n  }\n\n  if(vf->flags & VF_FILE)\n    return vfprintf(vf->fp, fmt, args);\n\n  return -1;\n}\n\n/**\n * Place a character back into the stream. This can be used once only for\n * memory streams and is only guaranteed to be usable once for files.\n * If chr is EOF or otherwise does not represent a valid char, this function\n * will fail and the stream will be unmodified.\n */\nint vungetc(int chr, vfile *vf)\n{\n  assert(vf);\n\n  // Note: MSVCRT &255s non-EOF values so this may not be 100% accurate to stdio.\n  // If this somehow breaks something it can be reverted later.\n  if(chr < 0 || chr >= 256)\n    return EOF;\n\n  if(vf->flags & VF_MEMORY)\n  {\n    if(vf->tmp_chr != EOF)\n      return EOF;\n\n    vf->tmp_chr = chr;\n    return chr;\n  }\n\n  if(vf->flags & VF_FILE)\n    return ungetc(chr, vf->fp);\n\n  return EOF;\n}\n\n/**\n * Seek to a position in a file.\n */\nint vfseek(vfile *vf, int64_t offset, int whence)\n{\n  assert(vf);\n  vf->tmp_chr = EOF;\n\n  if(virt_read(vf) || (vf->flags & VF_MEMORY))\n  {\n    int ret = mfseek(&(vf->mf), offset, whence);\n    virt_read_end(vf);\n    return ret;\n  }\n\n  if(vf->flags & VF_FILE)\n    return platform_fseek(vf->fp, offset, whence);\n\n  return -1;\n}\n\n/**\n * Return the current position in a file.\n */\nint64_t vftell(vfile *vf)\n{\n  assert(vf);\n\n  if(virt_read(vf) || (vf->flags & VF_MEMORY))\n  {\n    long res = mftell(&(vf->mf));\n    /**\n     * The number of buffered chars should be subtracted from the cursor for\n     * binary mode files (in text mode the behavior is unspecified.). If the\n     * cursor is at position 0 the return value of this operation is undefined.\n     */\n    if((vf->tmp_chr != EOF) && (vf->flags & VF_BINARY) && res > 0)\n      res--;\n\n    virt_read_end(vf);\n    return res;\n  }\n\n  if(vf->flags & VF_FILE)\n    return platform_ftell(vf->fp);\n\n  return -1;\n}\n\n/**\n * Rewind the file to the start (and clear EOF/errors if applicable).\n */\nvoid vrewind(vfile *vf)\n{\n  assert(vf);\n  vf->tmp_chr = EOF;\n\n  if(virt_read(vf) || (vf->flags & VF_MEMORY))\n  {\n    mfseek(&(vf->mf), 0, SEEK_SET);\n    virt_read_end(vf);\n    return;\n  }\n\n  if(vf->flags & VF_FILE)\n  {\n    rewind(vf->fp);\n    return;\n  }\n}\n\n/**\n * Return the length of the file and optionally rewind to the start of it.\n * If rewind is false, this function is guaranteed to either not modify the\n * current file position or to restore it to its position prior to calling this.\n * This function is not guaranteed to work correctly on streams that have been\n * written to or on memory write streams with fixed buffers, but is probably\n * safe to use for these cases.\n */\nint64_t vfilelength(vfile *vf, boolean rewind)\n{\n  int64_t size = -1;\n\n  assert(vf);\n\n  if(vfs_base && vf->inode)\n  {\n    size = vfs_filelength(vfs_base, vf->inode);\n  }\n\n  if((vf->flags & VF_MEMORY) && size < 0)\n  {\n    size = vf->mf.end - vf->mf.start;\n  }\n\n  if((vf->flags & VF_FILE) && size < 0)\n  {\n    // fstat (and maybe _filelength) rely on the copy on-disk being up to date.\n    // The SEEK_END hack works without this, since fseek also flushes the file.\n    if(vf->flags & VF_WRITE)\n      fflush(vf->fp);\n\n    size = platform_filelength(vf->fp);\n    if(size < 0)\n    {\n      int64_t current_pos = vftell(vf);\n\n      vfseek(vf, 0, SEEK_END);\n      size = vftell(vf);\n\n      if(!rewind)\n        vfseek(vf, current_pos, SEEK_SET);\n    }\n  }\n\n  if(rewind)\n    vrewind(vf);\n\n  return size;\n}\n\n\n/************************************************************************\n * dirent function wrappers.\n */\n\nstruct vdir\n{\n  struct dir_handle dh;\n  struct vfs_dir virt;\n  long position;\n  long num_total;\n  long num_real;\n  int flags;\n  boolean has_real;\n#ifdef PLATFORM_NO_REWINDDIR\n  // Path for dirent implementations with no working rewinddir.\n  char path[1];\n#endif\n};\n\n/**\n * Open a directory for reading.\n *\n * @param path        path to directory to open.\n * @param skip_scan   if `true`, skip the inital directory read to count\n *                    files. This breaks the read, seek, and tell functions.\n * @return            `vdir` handle on success, otherwise NULL.\n */\nvdir *vdir_open_ext(const char *path, int flags)\n{\n  char buffer[MAX_PATH];\n  boolean virt_okay = false;\n  vdir *dir;\n\n#ifdef PLATFORM_NO_REWINDDIR\n\n  ssize_t pathlen = -1;\n\n  // Make path absolute so this directory can be located more reliably.\n  if(vgetcwd(buffer, MAX_PATH))\n  {\n    pathlen = path_navigate_no_check(buffer, MAX_PATH, path);\n    if(pathlen >= 0)\n      path = buffer;\n  }\n  if(pathlen < 0)\n    pathlen = strlen(path);\n\n  dir = (vdir *)calloc(1, sizeof(vdir) + pathlen);\n  if(dir)\n    memcpy(dir->path, path, pathlen + 1);\n\n#else\n  dir = (vdir *)calloc(1, sizeof(vdir));\n\n  // Path needs to be made absolute only for VFS operations.\n  if(vfs_base)\n    path = vio_normalize_virtual_path(vfs_base, buffer, MAX_PATH, path);\n#endif\n\n  if(dir)\n  {\n    flags &= VDIR_PUBLIC_MASK;\n    dir->flags = flags;\n\n    // There may be virtual files in this directory, or this directory\n    // may also be virtual. Both real and virtual files need to be listed.\n    // TODO: overlaid files should replace the real file somehow? ugh\n    if(vfs_base && vfs_readdir(vfs_base, path, &dir->virt) == 0)\n    {\n      virt_okay = true;\n      dir->num_total += dir->virt.num_files;\n    }\n\n    if(platform_opendir(&(dir->dh), path))\n    {\n      // Get total real files.\n      dir->has_real = true;\n      if(~flags & VDIR_NO_SCAN)\n      {\n        while(platform_readdir(dir->dh, NULL, 0, NULL))\n          dir->num_real++;\n\n        dir->num_total += dir->num_real;\n        vdir_rewind(dir);\n      }\n    }\n    else\n\n    if(!virt_okay)\n      goto err;\n\n    return dir;\n  }\n\nerr:\n  free(dir);\n  return NULL;\n}\n\n/**\n * Open a directory for reading.\n */\nvdir *vdir_open(const char *path)\n{\n  return vdir_open_ext(path, false);\n}\n\n/**\n * Close a directory.\n */\nint vdir_close(vdir *dir)\n{\n  int ret = 0;\n  if(dir->has_real)\n    ret = platform_closedir(dir->dh);\n\n  vfs_readdir_free(&dir->virt);\n  free(dir);\n  return ret;\n}\n\n/**\n * Read the next file from a directory.\n * Returns `true` if a file was read, otherwise `false`.\n */\nboolean vdir_read(vdir *dir, char *buffer, size_t len, enum vdir_type *type)\n{\n  int dirent_type = -1;\n\n  if(dir->position >= dir->num_total && (~dir->flags & VDIR_NO_SCAN))\n    return false;\n\n  /* Return a virtual file. */\n  if(dir->position >= dir->num_real && dir->position < dir->num_total)\n  {\n    size_t i = dir->position - dir->num_real;\n    struct vfs_dir_file *f = dir->virt.files[i];\n    size_t sz;\n    // Shouldn't happen.\n    if(!f)\n      return false;\n\n    if(buffer)\n    {\n      sz = snprintf(buffer, len, \"%s\", f->name);\n      if(sz >= len)\n        return false;\n    }\n    if(type)\n      *type = (enum vdir_type)f->type;\n\n    dir->position++;\n    return true;\n  }\n\n  if(!dir->has_real)\n    return false;\n\n  if(!platform_readdir(dir->dh, buffer, len, &dirent_type))\n    return false;\n\n  if(type)\n  {\n    switch(dirent_type)\n    {\n  #ifdef DT_UNKNOWN\n      case DT_REG:\n        *type = DIR_TYPE_FILE;\n        break;\n\n      case DT_DIR:\n        *type = DIR_TYPE_DIR;\n        break;\n  #endif\n\n      default:\n        *type = DIR_TYPE_UNKNOWN;\n        break;\n    }\n  }\n\n  dir->position++;\n  return true;\n}\n\n/**\n * Seek to a position in a directory.\n */\nboolean vdir_seek(vdir *dir, long position)\n{\n  if(position < 0)\n    return false;\n\n  // If position is before current position, rewind.\n  if(position < dir->position)\n  {\n    if(!vdir_rewind(dir))\n      return false;\n  }\n\n  // Skip files until the current position is the requested position.\n  while(dir->position < position)\n    if(!vdir_read(dir, NULL, 0, NULL))\n      break;\n\n  return true;\n}\n\n/**\n * Rewind the directory to the first file.\n * Some platforms don't have a rewinddir implementation and need special\n * handling, hence both vdir_open and vdir_seek call this instead of rewinddir.\n */\nboolean vdir_rewind(vdir *dir)\n{\n  dir->position = 0;\n  if(!dir->num_real)\n    return true;\n\n#ifdef PLATFORM_NO_REWINDDIR\n  platform_closedir(dir->dh);\n\n  // Virtual files are still valid, only the real dir needs to be reopened.\n  if(!platform_opendir(&(dir->dh), dir->path))\n  {\n    dir->num_total -= dir->num_real;\n    dir->num_real = 0;\n    dir->flags &= ~VDIR_NO_SCAN;\n    return false;\n  }\n  return true;\n#else\n  return platform_rewinddir(dir->dh);\n#endif\n}\n\n/**\n * Report the current position in a directory.\n */\nlong vdir_tell(vdir *dir)\n{\n  return dir->position;\n}\n\n/**\n * Report the length of a directory.\n */\nlong vdir_length(vdir *dir)\n{\n  return dir->num_total;\n}\n\n\n/************************************************************************\n * Volume listing function wrappers.\n * For DOS/Amiga-style volumes, this will append ':' (but no slash).\n */\n\nstruct vvolumelist\n{\n  char **names;\n  size_t num_total;\n  size_t num_alloc;\n  size_t pos;\n};\n\nstatic vvolumelist *vvolumelist_alloc(void)\n{\n  vvolumelist *volumes = (vvolumelist *)malloc(sizeof(vvolumelist));\n  if(!volumes)\n    return NULL;\n\n  volumes->pos = 0;\n  volumes->num_total = 0;\n  volumes->num_alloc = 16;\n  volumes->names = (char **)malloc(volumes->num_alloc * sizeof(char *));\n  if(!volumes->names)\n  {\n    free(volumes);\n    return NULL;\n  }\n  return volumes;\n}\n\nstatic boolean vvolumelist_append(vvolumelist *volumes,\n const char *name, size_t name_len)\n{\n  char *n;\n\n  trace(\"--VIO-- vvolumelist_append: %s\\n\", name);\n\n  if(volumes->num_total == volumes->num_alloc)\n  {\n    char **tmp;\n    size_t new_num = volumes->num_alloc << 1u;\n\n    if(new_num <= volumes->num_alloc)\n      return false;\n\n    tmp = (char **)realloc(volumes->names, new_num * sizeof(char *));\n    if(!tmp)\n      return false;\n\n    volumes->names = tmp;\n    volumes->num_alloc = new_num;\n  }\n\n  n = (char *)malloc(name_len + 1);\n  if(!n)\n    return false;\n\n  memcpy(n, name, name_len);\n  n[name_len] = '\\0';\n  volumes->names[volumes->num_total++] = n;\n  return true;\n}\n\nstatic void vvolumelist_free(vvolumelist *volumes)\n{\n  size_t i;\n  for(i = 0; i < volumes->num_total; i++)\n    free(volumes->names[i]);\n\n  free(volumes->names);\n  free(volumes);\n}\n\n/**\n * Open the system volume list for reading. This function will read the volume\n * list in one go and allocate a copy to memory, as some platforms require\n * locking the volume list (Amiga) or temporarily changing the disk (DOS).\n * For most POSIX platforms, this list will consist of \"/\" and nothing else.\n *\n * @return an opaque volume list on success, otherwise NULL.\n */\nvvolumelist *vvolumelist_open(void)\n{\n  struct vvolumelist_handle vh;\n  vvolumelist *ret;\n  int sz;\n  char buf[256]; /* Amiga BCPL strings max at 255, other platforms are less. */\n\n  trace(\"--VIO-- vvolumelist_open\\n\");\n\n  if(!platform_vvolumelist_open(&vh))\n  {\n    trace(\"--VIO-- vvolumelist_open: failed to open platform volume list\\n\");\n    return NULL;\n  }\n\n  ret = vvolumelist_alloc();\n  if(!ret)\n    goto err;\n\n  while((sz = platform_vvolumelist_read(&vh, buf, sizeof(buf))))\n    if((size_t)sz < sizeof(buf) && !vvolumelist_append(ret, buf, sz))\n      goto err2;\n\n  platform_vvolumelist_close(&vh);\n\n  /* TODO: virtual roots */\n  return ret;\n\nerr2:\n  vvolumelist_free(ret);\nerr:\n  platform_vvolumelist_close(&vh);\n  return NULL;\n}\n\n/**\n * Release resources associated with an opened system volume list and free it.\n *\n * @param volumes   system volume list to destroy.\n * @return          0 on success.\n */\nint vvolumelist_close(vvolumelist *volumes)\n{\n  vvolumelist_free(volumes);\n  return 0;\n}\n\n/**\n * Get the next volume from an opened system volume list.\n *\n * @param volumes   system volume list to read.\n * @return          the name of the next volume, or NULL (end of list).\n */\nconst char *vvolumelist_read(vvolumelist *volumes)\n{\n  if(volumes->pos >= volumes->num_total)\n    return NULL;\n\n  return volumes->names[volumes->pos++];\n}\n"
  },
  {
    "path": "src/io/vio.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __IO_VIO_H\n#define __IO_VIO_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"vfile.h\"\n#include \"../platform_attribute.h\" /* ATTRIBUTE_PRINTF */\n\n#include <stdarg.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <sys/stat.h>\n\nenum vfileflags\n{\n  /* Internal flags. */\n  VF_FILE               = (1<<0),\n  VF_MEMORY             = (1<<1),\n  VF_MEMORY_EXPANDABLE  = (1<<2),\n  VF_MEMORY_FREE        = (1<<3), // Free memory buffer on vfclose.\n  VF_READ               = (1<<4),\n  VF_WRITE              = (1<<5),\n  VF_APPEND             = (1<<6),\n  VF_BINARY             = (1<<7),\n  VF_TRUNCATE           = (1<<8),\n  VF_VIRTUAL            = (1<<9), // Virtual or cached file.\n\n  /* Public flags. */\n  V_DONT_CACHE   = (1<<27), // do not add this file to the cache.\n  V_FORCE_CACHE  = (1<<28), // ignore the auto cache settings, always cache.\n  V_SMALL_BUFFER = (1<<29), // setvbuf <= 256 for real files in binary mode.\n  V_LARGE_BUFFER = (1<<30), // setvbuf >= 8192 for real files in binary mode.\n\n  VF_STORAGE_MASK       = (VF_FILE | VF_MEMORY),\n  VF_PUBLIC_MASK        = (V_DONT_CACHE | V_FORCE_CACHE |\n                           V_SMALL_BUFFER | V_LARGE_BUFFER)\n};\n\nenum vdir_type\n{\n  DIR_TYPE_UNKNOWN,\n  DIR_TYPE_FILE,\n  DIR_TYPE_DIR,\n  NUM_DIR_TYPES\n};\n\nenum vdirflags\n{\n  VDIR_NO_SCAN          = (1<<0), // Don't scan the directory to get its length.\n                                  // This will break seek, tell, and length.\n  VDIR_FAST             = VDIR_NO_SCAN, // Enable all speed hacks.\n  VDIR_PUBLIC_MASK      = VDIR_NO_SCAN\n};\n\nUTILS_LIBSPEC boolean vio_filesystem_init(size_t max_size, size_t max_file_size,\n boolean enable_auto_cache);\nUTILS_LIBSPEC boolean vio_filesystem_exit(void);\nUTILS_LIBSPEC size_t vio_filesystem_total_cached_usage(void);\nUTILS_LIBSPEC size_t vio_filesystem_total_memory_usage(void);\nUTILS_LIBSPEC boolean vio_virtual_file(const char *path);\nUTILS_LIBSPEC boolean vio_virtual_directory(const char *path);\nUTILS_LIBSPEC boolean vio_invalidate_at_least(size_t *amount_to_free);\nUTILS_LIBSPEC boolean vio_invalidate_all(void);\n\nUTILS_LIBSPEC vfile *vfopen_unsafe_ext(const char *filename, const char *mode,\n int user_flags);\nUTILS_LIBSPEC vfile *vfopen_unsafe(const char *filename, const char *mode);\nUTILS_LIBSPEC vfile *vfile_init_fp(FILE *fp, const char *mode);\nUTILS_LIBSPEC vfile *vfile_init_mem(void *buffer, size_t size, const char *mode);\nUTILS_LIBSPEC vfile *vfile_init_mem_ext(void **external_buffer,\n size_t *external_buffer_size, const char *mode);\nUTILS_LIBSPEC vfile *vtempfile(size_t initial_size);\nUTILS_LIBSPEC boolean vfile_force_to_memory(vfile *vf);\nUTILS_LIBSPEC int vfclose(vfile *vf);\n\nUTILS_LIBSPEC int vfile_get_mode_flags(const char *mode);\nUTILS_LIBSPEC int vfile_get_flags(vfile *vf);\nboolean vfile_get_memfile_block(vfile *vf, size_t length, struct memfile *dest);\n\nUTILS_LIBSPEC int vchdir(const char *path);\nUTILS_LIBSPEC char *vgetcwd(char *buf, size_t size);\nUTILS_LIBSPEC int vmkdir(const char *path, int mode);\nUTILS_LIBSPEC int vrename(const char *oldpath, const char *newpath);\nUTILS_LIBSPEC int vunlink(const char *path);\nUTILS_LIBSPEC int vrmdir(const char *path);\nUTILS_LIBSPEC int vaccess(const char *path, int mode);\nUTILS_LIBSPEC int vstat(const char *path, struct stat *buf);\n\nUTILS_LIBSPEC int vfgetc(vfile *vf);\nUTILS_LIBSPEC int vfgetw(vfile *vf);\nUTILS_LIBSPEC int vfgetd(vfile *vf);\nUTILS_LIBSPEC int64_t vfgetq(vfile *vf);\nUTILS_LIBSPEC int vfputc(int character, vfile *vf);\nUTILS_LIBSPEC int vfputw(int character, vfile *vf);\nUTILS_LIBSPEC int vfputd(int character, vfile *vf);\nUTILS_LIBSPEC int64_t vfputq(int64_t character, vfile *vf);\nUTILS_LIBSPEC size_t vfread(void *dest, size_t size, size_t count, vfile *vf);\nUTILS_LIBSPEC size_t vfwrite(const void *src, size_t size, size_t count, vfile *vf);\nUTILS_LIBSPEC char *vfsafegets(char *dest, int size, vfile *vf);\nUTILS_LIBSPEC int vfputs(const char *src, vfile *vf);\nUTILS_LIBSPEC int vf_printf(vfile *vf, const char *fmt, ...)\n ATTRIBUTE_PRINTF(2, 3);\nUTILS_LIBSPEC int vf_vprintf(vfile *vf, const char *fmt, va_list args)\n ATTRIBUTE_PRINTF(2, 0);\nUTILS_LIBSPEC int vungetc(int ch, vfile *vf);\nUTILS_LIBSPEC int vfseek(vfile *vf, int64_t offset, int whence);\nUTILS_LIBSPEC int64_t vftell(vfile *vf);\nUTILS_LIBSPEC void vrewind(vfile *vf);\nUTILS_LIBSPEC int64_t vfilelength(vfile *vf, boolean rewind);\n\nUTILS_LIBSPEC vdir *vdir_open(const char *path);\nUTILS_LIBSPEC vdir *vdir_open_ext(const char *path, int flags);\nUTILS_LIBSPEC int vdir_close(vdir *dir);\nUTILS_LIBSPEC boolean vdir_read(vdir *dir, char *buffer, size_t len, enum vdir_type *type);\nUTILS_LIBSPEC boolean vdir_seek(vdir *dir, long position);\nUTILS_LIBSPEC boolean vdir_rewind(vdir *dir);\nUTILS_LIBSPEC long vdir_tell(vdir *dir);\nUTILS_LIBSPEC long vdir_length(vdir *dir);\n\nUTILS_LIBSPEC vvolumelist *vvolumelist_open(void);\nUTILS_LIBSPEC int vvolumelist_close(vvolumelist *volumes);\nUTILS_LIBSPEC const char *vvolumelist_read(vvolumelist *volumes);\n\n__M_END_DECLS\n\n#endif /* __IO_VIO_H */\n"
  },
  {
    "path": "src/io/vio_no_vfs.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Hack so the utilities can build without SDL threading functions. */\n#define NO_VIRTUAL_FILESYSTEM\n#include \"vio.c\"\n"
  },
  {
    "path": "src/io/vio_posix.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __IO_VIO_POSIX_H\n#define __IO_VIO_POSIX_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n/**\n * Standard path function and dirent wrappers.\n * On most relevant platforms these already support UTF-8.\n */\n\n#include <dirent.h>\n#include <limits.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\n#include \"vio_volume.h\"\n\n// pspdev/devkitPSP historically does not have a rewinddir implementation.\n// libctru (3DS) and libnx (Switch) have rewinddir but it doesn't work.\n#if defined(CONFIG_3DS) || defined(CONFIG_PSP) || defined(CONFIG_SWITCH)\n#define PLATFORM_NO_REWINDDIR\n#endif\n\n/* clang has broken MemorySanitizer instrumentation for *stat, leading to false\n * positives. Use this define to enable memset() in the functions that use them.\n */\n#if defined(__has_feature)\n#if __has_feature(memory_sanitizer)\n#define STAT_NEEDS_MEMSET 1\n#endif\n#endif\n\n/**\n * Regular readdir/dirent can cause issues for 32-bit executables running\n * on large filesystems. This is caused by values for d_ino or d_off\n * overflowing their respective fields (even though this code doesn't\n * care about them). An example of this is running 32-bit ARM executables\n * using qemu-arm-static on an AMD64 host. For POSIX systems, please define\n * _FILE_OFFSET_BITS=64 if it is available (see the *nix Makefile fragment).\n */\n\nstatic inline FILE *platform_fopen_unsafe(const char *path, const char *mode)\n{\n  return fopen_unsafe(path, mode);\n}\n\nstatic inline FILE *platform_tmpfile(void)\n{\n#ifdef CONFIG_NDS\n  // tmpfile() pulls in the non-integer-only variant of sprintf from newlib,\n  // while the only functions currently using tmpfile() have a fallback.\n  return NULL;\n#else\n  return tmpfile();\n#endif /* CONFIG_NDS */\n}\n\nstatic inline char *platform_getcwd(char *buf, size_t size)\n{\n  return getcwd(buf, size);\n}\n\nstatic inline int platform_chdir(const char *path)\n{\n  return chdir(path);\n}\n\nstatic inline int platform_mkdir(const char *path, int mode)\n{\n  return mkdir(path, mode);\n}\n\nstatic inline int platform_rename(const char *oldpath, const char *newpath)\n{\n  return rename(oldpath, newpath);\n}\n\nstatic inline int platform_unlink(const char *path)\n{\n  return unlink(path);\n}\n\nstatic inline int platform_rmdir(const char *path)\n{\n  return rmdir(path);\n}\n\nstatic inline int platform_access(const char *path, int mode)\n{\n#if defined(CONFIG_AMIGA)\n  /* X_OK seems to have non-POSIX semantics here, replace with R_OK. */\n  if(mode & X_OK)\n    mode = (mode & ~X_OK) | R_OK;\n#endif\n#if defined(CONFIG_DREAMCAST)\n  // KallistiOS doesn't have access() :(\n  return 0;\n#else\n  return access(path, mode);\n#endif\n}\n\nstatic inline int platform_stat(const char *path, struct stat *buf)\n{\n#ifdef STAT_NEEDS_MEMSET\n  memset(buf, 0, sizeof(struct stat));\n#endif\n  return stat(path, buf);\n}\n\nstruct dir_handle\n{\n  DIR *dir;\n};\n\nstatic inline boolean platform_opendir(struct dir_handle *dh, const char *path)\n{\n  dh->dir = opendir(path);\n  return dh->dir != NULL;\n}\n\nstatic inline int platform_closedir(struct dir_handle dh)\n{\n  return closedir(dh.dir);\n}\n\nstatic inline boolean platform_readdir(struct dir_handle dh, char *buffer,\n size_t buffer_len, int *d_type)\n{\n  // Note: ino_t may cause issues unless _FILE_OFFSET_BITS=64 is defined.\n  struct dirent *d = readdir(dh.dir);\n  if(!d)\n    return false;\n\n#ifdef CONFIG_PSP\n  // SCE IO does this instead for some reason. This issue is only present in\n  // post-devkitPSP builds for some reason. FIXME: bandaid, upstream fix needed?\n  if(d->d_name[0] == '\\0')\n    return false;\n#endif\n\n  if(buffer && buffer_len)\n    snprintf(buffer, buffer_len, \"%s\", d->d_name);\n\n#ifdef DT_UNKNOWN\n  if(d_type)\n    *d_type = d->d_type;\n#endif\n\n  return true;\n}\n\nstatic inline boolean platform_rewinddir(struct dir_handle dh)\n{\n#ifndef PLATFORM_NO_REWINDDIR\n  rewinddir(dh.dir);\n  return true;\n#endif\n\n  return false;\n}\n\nstatic inline int platform_fseek(FILE *fp, int64_t offset, int whence)\n{\n#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64\n  return fseeko(fp, offset, whence);\n#else\n  if(offset >= LONG_MAX)\n    return -1;\n  return fseek(fp, offset, whence);\n#endif\n}\n\nstatic inline int64_t platform_ftell(FILE *fp)\n{\n#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64\n  return ftello(fp);\n#else\n  return ftell(fp);\n#endif\n}\n\n#if defined(__EMSCRIPTEN__)\n\n/**\n * For unknown reasons, Emscripten fstat is much slower than the fallback.\n */\nstatic inline int64_t platform_filelength(FILE *fp)\n{\n  return -1;\n}\n\n#else /* fstat */\n\nstatic inline int64_t platform_filelength(FILE *fp)\n{\n  // Note: off_t, ino_t may cause issues unless _FILE_OFFSET_BITS=64 is defined.\n  struct stat st;\n  int fd = fileno(fp);\n\n#ifdef STAT_NEEDS_MEMSET\n  memset(&st, 0, sizeof(struct stat));\n#endif\n\n  if(fd < 0)\n    return -1;\n  if(fstat(fd, &st) < 0)\n    return -1;\n\n  return st.st_size;\n}\n\n#endif /* fstat */\n\n__M_END_DECLS\n\n#endif /* __IO_VIO_POSIX_H */\n"
  },
  {
    "path": "src/io/vio_volume.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __IO_VIO_VOLUME_H\n#define __IO_VIO_VOLUME_H\n\n#include \"../compat.h\"\n#include \"../util.h\"\n#include \"path.h\"\n\n__M_BEGIN_DECLS\n\n/* Volume list reading implementations for vio_posix.h\n * These are more involved than the rest of vio_posix.h\n * but generally not worth their own compilation units. */\n\n#if defined(CONFIG_DJGPP)\n\n#include <dir.h>\n\nstruct vvolumelist_handle\n{\n  int current_disk;\n  int max_disk;\n  int pos;\n};\n\nstatic inline boolean platform_vvolumelist_open(struct vvolumelist_handle *vh)\n{\n  vh->current_disk = getdisk();\n  vh->max_disk = setdisk(vh->current_disk);\n  vh->pos = 0;\n  return true;\n}\n\nstatic inline void platform_vvolumelist_close(struct vvolumelist_handle *vh)\n{\n  setdisk(vh->current_disk);\n}\n\nstatic inline int platform_vvolumelist_read(struct vvolumelist_handle *vh,\n char *buf, size_t buf_sz)\n{\n  while(vh->pos < vh->max_disk)\n  {\n    int num = (vh->pos++);\n\n    setdisk(num);\n    if(getdisk() != num)\n      continue;\n\n    return snprintf(buf, buf_sz, \"%c:\", 'A' + (char)num);\n  }\n  return 0;\n}\n\n\n#elif defined(CONFIG_AMIGA)\n\n#include <dos/dos.h>\n#include <dos/dosextens.h>\n#include <proto/dos.h>\n\nstruct vvolumelist_handle\n{\n  struct DosList *dl;\n  size_t pos;\n};\n\nstatic inline boolean platform_vvolumelist_open(struct vvolumelist_handle *vh)\n{\n  vh->dl = LockDosList(LDF_VOLUMES | LDF_READ);\n  vh->pos = 0;\n  return true;\n}\n\nstatic inline void platform_vvolumelist_close(struct vvolumelist_handle *vh)\n{\n  UnLockDosList(LDF_VOLUMES | LDF_READ);\n}\n\nstatic inline int platform_vvolumelist_read(struct vvolumelist_handle *vh,\n char *buf, size_t buf_sz)\n{\n  /* Patch in SYS: after the reported list. */\n  static const char * const extras[] =\n  {\n    \"SYS\"\n  };\n\n  while((vh->dl = NextDosEntry(vh->dl, LDF_VOLUMES)))\n  {\n    uint8_t *bname = (uint8_t *)BADDR(vh->dl->dol_Name);\n    int len = bname[0];\n\n    return snprintf(buf, buf_sz, \"%*.*s:\", len, len, (char *)bname + 1);\n  }\n\n  while(vh->pos < ARRAY_SIZE(extras))\n  {\n    size_t num = (vh->pos++);\n    return snprintf(buf, buf_sz, \"%s:\", extras[num]);\n  }\n  return 0;\n}\n\n\n#elif defined(CONFIG_WII)\n\n#include <sys/iosupport.h>\n\nstruct vvolumelist_handle\n{\n  int pos;\n};\n\nstatic inline boolean platform_vvolumelist_open(struct vvolumelist_handle *vh)\n{\n  vh->pos = 0;\n  return true;\n}\n\nstatic inline void platform_vvolumelist_close(struct vvolumelist_handle *vh)\n{\n  /* nop */\n  (void)vh;\n}\n\nstatic inline int platform_vvolumelist_read(struct vvolumelist_handle *vh,\n char *buf, size_t buf_sz)\n{\n  while(vh->pos < STD_MAX)\n  {\n    int num = (vh->pos++);\n\n    if(devoptab_list[num] && devoptab_list[num]->chdir_r)\n      return snprintf(buf, buf_sz, \"%s:\", devoptab_list[num]->name);\n  }\n  return 0;\n}\n\n\n#else /* Use a list of hardcoded roots. */\n\nstatic const char * const volume_list[] =\n{\n#if defined(CONFIG_PSVITA)\n  \"app0:\",\n  \"imc0:\",\n  \"uma0:\",\n  \"ux0:\"\n#else /* Normal POSIX--patch in / and nothing else. */\n  DIR_SEPARATOR\n#endif\n};\n\nstruct vvolumelist_handle\n{\n  unsigned pos;\n};\n\nstatic inline boolean platform_vvolumelist_open(struct vvolumelist_handle *vh)\n{\n  vh->pos = 0;\n  return true;\n}\n\nstatic inline void platform_vvolumelist_close(struct vvolumelist_handle *vh)\n{\n  /* nop */\n  (void)vh;\n}\n\nstatic inline int platform_vvolumelist_read(struct vvolumelist_handle *vh,\n char *buf, size_t buf_sz)\n{\n  while(vh->pos < ARRAY_SIZE(volume_list))\n  {\n    unsigned num = (vh->pos++);\n\n    return snprintf(buf, buf_sz, \"%s\", volume_list[num]);\n  }\n  return 0;\n}\n\n#endif\n\n__M_END_DECLS\n\n#endif /* __IO_VIO_VOLUME_H */\n"
  },
  {
    "path": "src/io/vio_win32.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __IO_VIO_WIN32_H\n#define __IO_VIO_WIN32_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n/**\n * Win32 UTF-8 stdio, unistd, and dirent path function wrappers.\n */\n\n#include <dirent.h>\n#include <stdio.h>\n#include <sys/stat.h>\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n#include <wchar.h>\n\n#ifndef _WIN32_WINNT_WIN2K\n#define _WIN32_WINNT_WIN2K 0x500\n#endif\n#ifndef _WIN32_WINNT_WINXP\n#define _WIN32_WINNT_WINXP 0x501\n#endif\n\n/**\n * The wide char conversion functions were added in Windows 2000 and generally\n * the wide versions of the stdio functions just don't appear to work in older\n * OSes (tested Win98 with KernelEx using tdm-gcc 5.1.0).\n */\n#if WINVER >= _WIN32_WINNT_WIN2K\n#define WIDE_PATHS 1\n#endif\n\n#ifdef WIDE_PATHS\n/**\n * Convert a UTF-8 char string into a UTF-16 wide char string for use with\n * Win32 wide char functions. Returns the length of the output (including the\n * null terminator) or 0 on failure.\n */\nstatic inline int utf8_to_utf16(const char *src, wchar_t *dest, int dest_size)\n{\n  return MultiByteToWideChar(\n    CP_UTF8,\n    0,\n    (LPCCH)src,\n    -1, // Null terminated.\n    (LPWSTR)dest,\n    dest_size\n  );\n}\n\n/**\n * Convert a UTF-16 wide char string into a UTF-8 char string for general\n * usage. Returns the length of the output (including the null terminator)\n * or 0 on failure.\n */\nstatic inline int utf16_to_utf8(const wchar_t *src, char *dest, int dest_size)\n{\n  return WideCharToMultiByte(\n    CP_UTF8,\n    0,\n    (LPCWCH)src,\n    -1, // Null terminated.\n    (LPSTR)dest,\n    dest_size,\n    NULL,\n    NULL\n  );\n}\n#endif\n\nstatic inline FILE *platform_fopen_unsafe(const char *path, const char *mode)\n{\n#ifdef WIDE_PATHS\n  wchar_t wpath[MAX_PATH];\n  wchar_t wmode[64];\n\n  if(utf8_to_utf16(path, wpath, MAX_PATH))\n    if(utf8_to_utf16(mode, wmode, 64))\n      return _wfopen(wpath, wmode);\n#endif\n  return fopen_unsafe(path, mode);\n}\n\nstatic inline FILE *platform_tmpfile(void)\n{\n#ifdef WIDE_PATHS\n  /**\n   * The non-terrible solution to doing this was introduced around roughly the\n   * same time wide path support was so just tie them together. This also\n   * hopefully means that user dirs with unicode characters won't break.\n   */\n  wchar_t wpath[MAX_PATH];\n  wchar_t wfile[MAX_PATH];\n\n  if(GetTempPathW(MAX_PATH, wpath))\n  {\n    if(GetTempFileNameW(wpath, L\"MZX\", 0, wfile))\n      return _wfopen(wfile, L\"wb+\");\n  }\n#endif\n  /* This attempts to put a file in C:\\ and generally is terrible! */\n  return tmpfile();\n}\n\nstatic inline char *platform_getcwd(char *buf, size_t size)\n{\n#ifdef WIDE_PATHS\n  wchar_t wpath[MAX_PATH];\n\n  if(!_wgetcwd(wpath, MAX_PATH))\n    return NULL;\n\n  if(utf16_to_utf8(wpath, buf, size))\n    return buf;\n#endif\n  return getcwd(buf, size);\n}\n\nstatic inline int platform_chdir(const char *path)\n{\n#ifdef WIDE_PATHS\n  wchar_t wpath[MAX_PATH];\n\n  if(utf8_to_utf16(path, wpath, MAX_PATH))\n    return _wchdir(wpath);\n#endif\n  return chdir(path);\n}\n\nstatic inline int platform_mkdir(const char *path, int mode)\n{\n#ifdef WIDE_PATHS\n  wchar_t wpath[MAX_PATH];\n\n  if(utf8_to_utf16(path, wpath, MAX_PATH))\n    return _wmkdir(wpath);\n#endif\n  return mkdir(path);\n}\n\nstatic inline int platform_rename(const char *oldpath, const char *newpath)\n{\n#ifdef WIDE_PATHS\n  wchar_t woldpath[MAX_PATH];\n  wchar_t wnewpath[MAX_PATH];\n\n  if(utf8_to_utf16(oldpath, woldpath, MAX_PATH))\n    if(utf8_to_utf16(newpath, wnewpath, MAX_PATH))\n      return _wrename(woldpath, wnewpath);\n#endif\n  return rename(oldpath, newpath);\n}\n\nstatic inline int platform_unlink(const char *path)\n{\n#ifdef WIDE_PATHS\n  wchar_t wpath[MAX_PATH];\n\n  if(utf8_to_utf16(path, wpath, MAX_PATH))\n    return _wunlink(wpath);\n#endif\n  return _unlink(path);\n}\n\nstatic inline int platform_rmdir(const char *path)\n{\n#ifdef WIDE_PATHS\n  wchar_t wpath[MAX_PATH];\n\n  if(utf8_to_utf16(path, wpath, MAX_PATH))\n    return _wrmdir(wpath);\n#endif\n  return rmdir(path);\n}\n\nstatic inline int platform_access(const char *path, int mode)\n{\n  // X_OK isn't supported on Windows. Use R_OK instead...\n  if(mode & X_OK)\n    mode = (mode & ~X_OK) | R_OK;\n\n#ifdef WIDE_PATHS\n  {\n    wchar_t wpath[MAX_PATH];\n\n    if(utf8_to_utf16(path, wpath, MAX_PATH))\n      return _waccess(wpath, mode);\n  }\n#endif\n  return access(path, mode);\n}\n\nstatic inline int platform_stat(const char *path, struct stat *buf)\n{\n#ifdef WIDE_PATHS\n  wchar_t wpath[MAX_PATH];\n  struct _stat stat_info;\n  int ret;\n\n  if(utf8_to_utf16(path, wpath, MAX_PATH))\n  {\n    ret = _wstat(wpath, &stat_info);\n\n    buf->st_gid = stat_info.st_gid;\n    buf->st_atime = (time_t)stat_info.st_atime;\n    buf->st_ctime = (time_t)stat_info.st_ctime;\n    buf->st_dev = stat_info.st_dev;\n    buf->st_ino = stat_info.st_ino;\n    buf->st_mode = stat_info.st_mode;\n    buf->st_mtime = (time_t)stat_info.st_mtime;\n    buf->st_nlink = stat_info.st_nlink;\n    buf->st_rdev = stat_info.st_rdev;\n    buf->st_size = (_off_t)stat_info.st_size;\n    buf->st_uid = stat_info.st_uid;\n    return ret;\n  }\n#endif\n  return stat(path, buf);\n}\n\nstruct dir_handle\n{\n  DIR *dir;\n#ifdef WIDE_PATHS\n  _WDIR *wdir;\n#endif\n};\n\nstatic inline boolean platform_opendir(struct dir_handle *dh, const char *path)\n{\n#ifdef WIDE_PATHS\n  wchar_t wpath[MAX_PATH];\n\n  dh->dir = NULL;\n  dh->wdir = NULL;\n\n  if(utf8_to_utf16(path, wpath, MAX_PATH))\n  {\n    dh->wdir = _wopendir(wpath);\n    if(dh->wdir)\n      return true;\n  }\n#endif\n\n  dh->dir = opendir(path);\n  return dh->dir != NULL;\n}\n\nstatic inline int platform_closedir(struct dir_handle dh)\n{\n#ifdef WIDE_PATHS\n  if(dh.wdir)\n    return _wclosedir(dh.wdir);\n#endif\n  return closedir(dh.dir);\n}\n\nstatic inline boolean platform_readdir(struct dir_handle dh, char *buffer,\n size_t buffer_len, int *d_type)\n{\n  struct dirent *d;\n\n#ifdef WIDE_PATHS\n  if(dh.wdir)\n  {\n    struct _wdirent *wd = _wreaddir(dh.wdir);\n    if(!wd)\n      return false;\n\n    if(buffer && buffer_len)\n      if(!utf16_to_utf8(wd->d_name, buffer, buffer_len))\n        buffer[0] = '\\0';\n\n#ifdef DT_UNKNOWN\n    if(d_type)\n      *d_type = wd->d_type;\n#endif\n\n    return true;\n  }\n#endif\n\n  d = readdir(dh.dir);\n  if(!d)\n    return false;\n\n  if(buffer && buffer_len)\n  {\n    snprintf(buffer, buffer_len, \"%s\", d->d_name);\n    buffer[buffer_len - 1] = '\\0';\n  }\n\n#ifdef DT_UNKNOWN\n  if(d_type)\n    *d_type = d->d_type;\n#endif\n\n  return true;\n}\n\nstatic inline boolean platform_rewinddir(struct dir_handle dh)\n{\n#ifdef WIDE_PATHS\n  if(dh.wdir)\n  {\n    _wrewinddir(dh.wdir);\n    return true;\n  }\n#endif\n\n  rewinddir(dh.dir);\n  return true;\n}\n\nstruct vvolumelist_handle\n{\n  DWORD drives;\n  DWORD pos;\n};\n\nstatic inline boolean platform_vvolumelist_open(struct vvolumelist_handle *vh)\n{\n  vh->drives = GetLogicalDrives();\n  vh->pos = 0;\n  return true;\n}\n\nstatic inline void platform_vvolumelist_close(struct vvolumelist_handle *vh)\n{\n  /* nop */\n  (void)vh;\n}\n\nstatic inline int platform_vvolumelist_read(struct vvolumelist_handle *vh,\n char *buf, size_t buf_sz)\n{\n  while(vh->pos < 32)\n  {\n    DWORD num = (vh->pos++);\n\n    if(vh->drives & (1u << num))\n      return snprintf(buf, buf_sz, \"%c:\", 'A' + (char)num);\n  }\n  return 0;\n}\n\nstatic inline int platform_fseek(FILE *fp, int64_t offset, int whence)\n{\n#if WINVER >= _WIN32_WINNT_WINXP\n  return _fseeki64(fp, offset, whence);\n#else\n  fpos_t pos;\n  int fd = _fileno(fp);\n  if(fd < 0)\n    return -1;\n\n  // _lseeki64 works at a lower level than stdio functions.\n  // This forces MSVCRT to synchronize any buffered data.\n  if(fgetpos(fp, &pos) < 0 || fsetpos(fp, &pos) < 0)\n    return -1;\n\n  return _lseeki64(fd, offset, whence) >= 0 ? 0 : -1;\n#endif\n}\n\nstatic inline int64_t platform_ftell(FILE *fp)\n{\n#if WINVER >= _WIN32_WINNT_WINXP\n  return _ftelli64(fp);\n#else\n  fpos_t pos;\n  int fd = _fileno(fp);\n  if(fd < 0)\n    return -1;\n\n  // _telli64 works at a lower level than stdio functions.\n  // This forces MSVCRT to synchronize any buffered data.\n  if(fgetpos(fp, &pos) < 0 || fsetpos(fp, &pos) < 0)\n    return -1;\n\n  return _telli64(fd);\n#endif\n}\n\nstatic inline int64_t platform_filelength(FILE *fp)\n{\n  int fd = _fileno(fp);\n  return fd >= 0 ? _filelengthi64(fd) : -1;\n}\n\n__M_END_DECLS\n\n#endif /* __IO_VIO_WIN32_H */\n"
  },
  {
    "path": "src/io/zip.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <assert.h>\n#include <inttypes.h>\n#include <limits.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/stat.h>\n#include <time.h>\n\n#include <zlib.h>\n\n// This needs to stay self-sufficient - don't use core functions.\n// Including util.h for the macros only...\n\n#include \"../util.h\"\n\n#include \"memfile.h\"\n#include \"vio.h\"\n#include \"zip.h\"\n#include \"zip_stream.h\"\n\n// Data descriptors are short areas after the compressed file containing the\n// uncompressed size, compressed size, and CRC-32 checksum of the file, and\n// are useful for platforms where seeking backward is impossible/infeasible.\n\n// The NDS libfat library may have a bug with seeking backwards from the end of\n// the file. Enable data descriptors so the save can be handled in one pass.\n// The 3DS has incredibly slow file access that seems to be negatively impacted\n// by backwards seeks in particular, so enable data descriptors for it too.\n// The Switch may similarly benefit with this.\n// DJGPP may be running on exceptionally slow hardware.\n\n#if defined(CONFIG_NDS) || defined(CONFIG_3DS) || defined(CONFIG_SWITCH) || \\\n defined(CONFIG_PSVITA) || defined(CONFIG_DJGPP)\n#define ZIP_WRITE_DATA_DESCRIPTOR\n#endif\n\n// Similarly, some platforms are slow and need faster compression.\n// TODO: these should probably be runtime configurable instead.\n#if defined(CONFIG_NDS) || defined(CONFIG_PSP) || defined(CONFIG_DJGPP)\n#define ZIP_WRITE_DEFLATE_FAST\n#endif\n\n#define ZIP_VERSION_MINIMUM 20\n#define ZIP64_VERSION_MINIMUM 45\n#define ZIP_VERSION(x) ((x) & 0x00ff)\n\n#define DATA_DESCRIPTOR_LEN 12\n#define ZIP64_DATA_DESCRIPTOR_LEN 20\n\n#define ZIP_DEFAULT_NUM_FILES 4\n#define ZIP_MAX_NUM_FILES MIN(1u << 24u, SIZE_MAX)\n\n#define ZIP_STREAM_BUFFER_SIZE 65536\n#define ZIP_STREAM_BUFFER_U_SIZE (ZIP_STREAM_BUFFER_SIZE * 3 / 4)\n#define ZIP_STREAM_BUFFER_C_SIZE (ZIP_STREAM_BUFFER_SIZE / 4)\n\n#define LOCAL_FILE_HEADER_LEN 30\n#define CENTRAL_FILE_HEADER_LEN 46\n#define EOCD_RECORD_LEN 22\n\n#define ZIP64_EOCD_RECORD_LEN 56\n#define ZIP64_LOCATOR_LEN 20\n#define ZIP64_LOCAL_EXTRA_LEN 20 /* Fixed size for LOCAL only. */\n#define ZIP64_MAX_EXTRA_LEN 32 /* Variable size in CENTRAL only. */\n\n#define ZIP_DEFAULT_HEADER_BUFFER \\\n (CENTRAL_FILE_HEADER_LEN + ZIP64_MAX_EXTRA_LEN + 8)\n\n/**\n * This zip reader/writer was designed:\n *\n * 1) Around the needs of our world/board/MZM files. These files need\n * binary headers (or at least it's nice to have them). The way they're\n * split up also requires fairly fast zip reading and rewriting that\n * minimizes file IO function usage.\n *\n * 2) With the ability to use a memory buffer instead of a file. This is\n * mainly for MZMs, which need to be writable to memory.\n *\n * While not copied from or even based on ajs's zipio, a few pointers were\n * taken from it. While zips can't currently be read and modified at the\n * same time, this would be a potentially useful future addition with\n * implications for downver or accessing files/worlds from inside of a zip.\n */\n\nstatic uint32_t zip_get_dos_date_time(void)\n{\n  time_t current_time = time(NULL);\n  struct tm *tm = localtime(&current_time);\n\n  uint16_t time;\n  uint16_t date;\n\n  time =  (tm->tm_hour << 11);\n  time |= (tm->tm_min << 5);\n  time |= (tm->tm_sec >> 1);\n\n  date =  ((tm->tm_year - 80) << 9);\n  date |= ((tm->tm_mon + 1) << 5);\n  date |= (tm->tm_mday);\n\n  return (date << 16) | time;\n}\n\nstatic const char *zip_error_string(enum zip_error code)\n{\n  switch(code)\n  {\n    case ZIP_SUCCESS:\n      return \"no error\";\n    case ZIP_IGNORE_FILE:\n      return \"no error; file in archive was ignored\";\n    case ZIP_EOF:\n      return \"reached end of file\";\n    case ZIP_NULL:\n      return \"function received null archive\";\n    case ZIP_NULL_BUF:\n      return \"function received null buffer\";\n    case ZIP_ALLOC_ERROR:\n      return \"out of memory\";\n    case ZIP_STAT_ERROR:\n      return \"fstat failed for input file\";\n    case ZIP_SEEK_ERROR:\n      return \"could not seek to position\";\n    case ZIP_READ_ERROR:\n      return \"could not read from position\";\n    case ZIP_WRITE_ERROR:\n      return \"could not write to position\";\n    case ZIP_BOUND_ERROR:\n      return \"value exceeds bound of provided field\";\n    case ZIP_INVALID_READ_IN_WRITE_MODE:\n      return \"can't read in write mode\";\n    case ZIP_INVALID_WRITE_IN_READ_MODE:\n      return \"can't write in read mode\";\n    case ZIP_INVALID_FILE_READ_UNINITIALIZED:\n      return \"directory has not been read\";\n    case ZIP_INVALID_FILE_READ_IN_STREAM_MODE:\n      return \"can't read file in stream mode\";\n    case ZIP_INVALID_FILE_WRITE_IN_STREAM_MODE:\n      return \"can't write file in stream mode\";\n    case ZIP_INVALID_STREAM_READ:\n      return \"can't read/close; not streaming\";\n    case ZIP_INVALID_STREAM_WRITE:\n      return \"can't write/close; not streaming\";\n    case ZIP_NOT_MEMORY_ARCHIVE:\n      return \"archive isn't a memory archive\";\n    case ZIP_NO_EOCD:\n      return \"file is not a zip archive\";\n    case ZIP_NO_EOCD_ZIP64:\n      return \"Zip64 EOCD locator or Zip64 EOCD not found or invalid\";\n    case ZIP_NO_CENTRAL_DIRECTORY:\n      return \"could not find or read central directory\";\n    case ZIP_INCOMPLETE_CENTRAL_DIRECTORY:\n      return \"central directory is missing records\";\n    case ZIP_UNSUPPORTED_VERSION:\n      return \"unsupported minimum version to extract\";\n    case ZIP_UNSUPPORTED_NUMBER_OF_ENTRIES:\n      return \"unsupported number of files in archive\";\n    case ZIP_UNSUPPORTED_MULTIPLE_DISKS:\n      return \"unsupported multiple volume archive\";\n    case ZIP_UNSUPPORTED_FLAGS:\n      return \"unsupported flags\";\n    case ZIP_UNSUPPORTED_DECOMPRESSION:\n      return \"unsupported method for decompression\";\n    case ZIP_UNSUPPORTED_COMPRESSION:\n      return \"unsupported method; use DEFLATE or none\";\n    case ZIP_UNSUPPORTED_DECOMPRESSION_STREAM:\n      return \"method does not support partial decompression\";\n    case ZIP_UNSUPPORTED_COMPRESSION_STREAM:\n      return \"method does not support partial compression\";\n    case ZIP_UNSUPPORTED_METHOD_MEMORY_STREAM:\n      return \"can not open compressed files for direct memory read/write\";\n    case ZIP_NO_ZIP64_EXTRA_DATA:\n      return \"missing  extra data field\";\n    case ZIP_INVALID_ZIP64:\n      return \"invalid Zip64 extra data field\";\n    case ZIP_MISSING_LOCAL_HEADER:\n      return \"could not find file header\";\n    case ZIP_HEADER_MISMATCH:\n      return \"local header mismatch\";\n    case ZIP_CRC32_MISMATCH:\n      return \"CRC-32 mismatch; validation failed\";\n    case ZIP_DECOMPRESS_FAILED:\n      return \"decompression failed\";\n    case ZIP_COMPRESS_FAILED:\n      return \"compression failed\";\n    case ZIP_INPUT_EMPTY:\n      return \"stream input buffer exhausted\";\n    case ZIP_OUTPUT_FULL:\n      return \"stream output buffer full\";\n    case ZIP_STREAM_FINISHED:\n      return \"end of stream reached\";\n  }\n  warn(\"zip_error_string: received unknown error code %d!\\n\", code);\n  return \"UNKNOWN ERROR\";\n}\n\nstatic void zip_error(const char *func, enum zip_error code)\n{\n  warn(\"%s: %s\\n\", func, zip_error_string(code));\n}\n\n/**\n * Mac OS X likes to pollute ZIPs with metadata files. Try to detect these...\n * Also check for Thumbs.db because Windows :(\n *\n * If these are ever needed for some reason, it should be trivial to add an\n * option to disable this.\n */\nstatic inline boolean zip_is_ignore_file(const char *filename, size_t len)\n{\n  if(len >= 9)\n  {\n    if(!strncasecmp(filename, \"__MACOSX/\", 9))\n      return true;\n\n    if(!strcasecmp(filename + len - 9, \".DS_Store\") &&\n     (len == 9 || filename[len - 10] == '/'))\n      return true;\n\n    if(!strcasecmp(filename + len - 9, \"Thumbs.db\") &&\n     (len == 9 || filename[len - 10] == '/'))\n      return true;\n  }\n  return false;\n}\n\n/**\n * Return true if a method is supported for decompression.\n */\nstatic boolean zip_method_is_supported(uint8_t method)\n{\n  if(method > ZIP_M_NONE && method <= ZIP_M_MAX_SUPPORTED)\n    return !!zip_method_handlers[method];\n\n  return (method == ZIP_M_NONE);\n}\n\n/**\n * Set a zip archive's zip stream functions for a given method, or to NULL\n * if the specified method isn't supported.\n */\nstatic enum zip_error zip_get_stream(struct zip_archive *zp, uint8_t method,\n enum zip_internal_state new_mode)\n{\n  zp->stream = NULL;\n  zp->stream_data = NULL;\n\n  if(method == ZIP_M_NONE)\n  {\n    // Special handling for store.\n    return ZIP_SUCCESS;\n  }\n\n  if(method <= ZIP_M_MAX_SUPPORTED)\n  {\n    const struct zip_method_handler *result = zip_method_handlers[method];\n\n    switch(new_mode)\n    {\n      // These should never reach here!\n      case ZIP_S_READ_UNINITIALIZED:\n      case ZIP_S_READ_FILES:\n      case ZIP_S_READ_MEMSTREAM:\n      case ZIP_S_WRITE_UNINITIALIZED:\n      case ZIP_S_WRITE_FILES:\n      case ZIP_S_WRITE_MEMSTREAM:\n      case ZIP_S_ERROR:\n        return -1;\n\n      case ZIP_S_READ_STREAM:\n      {\n        if(!result || !result->decompress_open)\n          return ZIP_UNSUPPORTED_DECOMPRESSION;\n\n        break;\n      }\n\n      case ZIP_S_WRITE_STREAM:\n      {\n        if(!result || !result->compress_open)\n          return ZIP_UNSUPPORTED_COMPRESSION;\n\n        break;\n      }\n    }\n\n    if(!zp->stream_data_ptrs[method])\n    {\n      struct zip_stream_data *tmp = result->create();\n      if(!tmp)\n        return ZIP_ALLOC_ERROR;\n\n      zp->stream_data_ptrs[method] = tmp;\n    }\n\n    zp->stream = result;\n    zp->stream_data = zp->stream_data_ptrs[method];\n    return ZIP_SUCCESS;\n  }\n  return -1;\n}\n\n/**\n * Calculate an upper bound for the total compressed size of a memory block\n * of a given length with deflate.\n */\nint zip_bound_deflate_usage(size_t length)\n{\n  z_stream stream;\n  int bound;\n\n  memset(&stream, 0, sizeof(z_stream));\n\n  // Note: aside from the windowbits, these are all defaults\n  deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS,\n   8, Z_DEFAULT_STRATEGY);\n\n  bound = deflateBound(&stream, length);\n\n  deflateEnd(&stream);\n\n  return bound;\n}\n\n/**\n * Calculate an upper bound for the total size of headers for an archive.\n */\nint zip_bound_total_header_usage(int num_files, int max_name_size)\n{\n  // Expected:\n  // max_name_size = 8 for MZX world data\n  //               = 5 for MZM data\n  int extra = 0;\n\n#ifdef ZIP_WRITE_DATA_DESCRIPTOR\n  extra = num_files * ZIP64_DATA_DESCRIPTOR_LEN;    // data descriptor size\n#endif\n\n  return num_files *\n   // base + file name\n   (LOCAL_FILE_HEADER_LEN + max_name_size +         // Local\n    CENTRAL_FILE_HEADER_LEN + max_name_size +       // Central directory\n    ZIP64_LOCAL_EXTRA_LEN + ZIP64_MAX_EXTRA_LEN) +  // Zip64 extra field\n    ZIP64_EOCD_RECORD_LEN + ZIP64_LOCATOR_LEN +     // Zip64 EOCD record\n    EOCD_RECORD_LEN + extra;                        // EOCD record\n}\n\n\n/* Basic checks to make sure various functions can actually be used. */\n\nstatic inline enum zip_error zip_read_file_mode_check(struct zip_archive *zp)\n{\n  switch(zp->mode)\n  {\n    case ZIP_S_READ_FILES:          return ZIP_SUCCESS;\n    case ZIP_S_READ_STREAM:         return ZIP_INVALID_FILE_READ_IN_STREAM_MODE;\n    case ZIP_S_READ_MEMSTREAM:      return ZIP_INVALID_FILE_READ_IN_STREAM_MODE;\n    case ZIP_S_READ_UNINITIALIZED:  return ZIP_INVALID_FILE_READ_UNINITIALIZED;\n    default:                        return ZIP_INVALID_READ_IN_WRITE_MODE;\n  }\n}\n\nstatic inline enum zip_error zip_read_stream_mode_check(struct zip_archive *zp)\n{\n  switch(zp->mode)\n  {\n    case ZIP_S_READ_STREAM:         return ZIP_SUCCESS;\n    case ZIP_S_READ_FILES:          return ZIP_INVALID_STREAM_READ;\n    case ZIP_S_READ_MEMSTREAM:      return ZIP_INVALID_STREAM_READ;\n    case ZIP_S_READ_UNINITIALIZED:  return ZIP_INVALID_FILE_READ_UNINITIALIZED;\n    default:                        return ZIP_INVALID_READ_IN_WRITE_MODE;\n  }\n}\n\nstatic inline enum zip_error zip_write_file_mode_check(struct zip_archive *zp)\n{\n  switch(zp->mode)\n  {\n    case ZIP_S_WRITE_FILES:         return ZIP_SUCCESS;\n    case ZIP_S_WRITE_STREAM:        return ZIP_INVALID_FILE_WRITE_IN_STREAM_MODE;\n    case ZIP_S_WRITE_MEMSTREAM:     return ZIP_INVALID_FILE_WRITE_IN_STREAM_MODE;\n    case ZIP_S_WRITE_UNINITIALIZED: return ZIP_SUCCESS;\n    default:                        return ZIP_INVALID_WRITE_IN_READ_MODE;\n  }\n}\n\nstatic inline enum zip_error zip_write_stream_mode_check(struct zip_archive *zp)\n{\n  switch(zp->mode)\n  {\n    case ZIP_S_WRITE_STREAM:        return ZIP_SUCCESS;\n    case ZIP_S_WRITE_FILES:         return ZIP_INVALID_STREAM_WRITE;\n    case ZIP_S_WRITE_MEMSTREAM:     return ZIP_INVALID_STREAM_WRITE;\n    case ZIP_S_WRITE_UNINITIALIZED: return ZIP_INVALID_STREAM_WRITE;\n    default:                        return ZIP_INVALID_WRITE_IN_READ_MODE;\n  }\n}\n\nstatic inline void precalculate_read_errors(struct zip_archive *zp)\n{\n  zp->read_file_error = zip_read_file_mode_check(zp);\n  zp->read_stream_error = zip_read_stream_mode_check(zp);\n}\n\nstatic inline void precalculate_write_errors(struct zip_archive *zp)\n{\n  zp->write_file_error = zip_write_file_mode_check(zp);\n  zp->write_stream_error = zip_write_stream_mode_check(zp);\n}\n\n/**\n * Allocate a zip file header struct, including any necessary extra data.\n */\nstatic struct zip_file_header *zip_allocate_file_header(uint16_t filename_len)\n{\n  // Attempt to reclaim any alignment padding to fit the filename...\n  size_t size = MAX(sizeof(struct zip_file_header),\n   offsetof(struct zip_file_header, file_name) + filename_len + 1);\n\n  return (struct zip_file_header *)malloc(size);\n}\n\n/**\n * Free a zip file header struct, including any necessary extra data.\n * If the provided file header pointer is NULL, this function does nothing.\n */\nstatic void zip_free_file_header(struct zip_file_header *fh)\n{\n  // Filename is now allocated as part of the base struct, so no need to\n  // explicitly free it...\n  free(fh);\n}\n\n/**\n * @return `true` if the current file header is Zip64, otherwise `false`.\n *         When writing, this must be called AFTER a file is written.\n */\nstatic boolean zip_file_is_zip64(struct zip_file_header *fh)\n{\n  if(fh->compressed_size >= 0xfffffffful ||\n   fh->uncompressed_size >= 0xfffffffful ||\n   fh->offset >= 0xfffffffful)\n    return true;\n  return false;\n}\n\n/**\n * Ensure the size of the zip archive header buffer is at least\n * the provided value.\n */\nstatic boolean zip_ensure_header_buffer_size(struct zip_archive *zp,\n size_t new_size)\n{\n  if(zp->header_buffer_alloc >= new_size)\n    return true;\n\n  if(zp->header_buffer)\n  {\n    uint8_t *tmp = (uint8_t *)realloc(zp->header_buffer, new_size);\n    if(!tmp)\n      return false;\n\n    zp->header_buffer = tmp;\n  }\n  else\n  {\n    zp->header_buffer = (uint8_t *)malloc(new_size);\n    if(!zp->header_buffer)\n      return false;\n  }\n  zp->header_buffer_alloc = new_size;\n  return true;\n}\n\nstatic char file_sig_local[] =\n{\n  0x50,\n  0x4b,\n  0x03,\n  0x04\n};\n\nstatic char file_sig_central[] =\n{\n  0x50,\n  0x4b,\n  0x01,\n  0x02\n};\n\nstatic uint32_t data_descriptor_sig = 0x08074b50;\n\nstatic enum zip_error zip_read_file_header_signature(struct zip_archive *zp,\n boolean is_central)\n{\n  vfile *vf = zp->vf;\n  int n, i;\n\n  char *magic = is_central ? file_sig_central : file_sig_local;\n\n  // Find the next file header\n  i = 0;\n  while(1)\n  {\n    n = vfgetc(vf);\n    if(n < 0)\n      return ZIP_MISSING_LOCAL_HEADER;\n\n    // Match the signature\n    if(n == magic[i])\n    {\n      i++;\n      if(i == 4)\n        // We've found the file header signature\n        break;\n    }\n    else\n\n    // Even if it's not a match, it could be the start of the signature.\n    if(n == 'P')\n      i = 1;\n\n    else\n      i = 0;\n  }\n\n  return ZIP_SUCCESS;\n}\n\n// Search extra fields for Zip64 info.\nstatic enum zip_error zip_read_file_header_extra_fields(struct zip_archive *zp,\n struct zip_file_header *fh, int extra_length, boolean is_central)\n{\n  struct memfile mf;\n  int tag;\n  int sz;\n\n  if(!zip_ensure_header_buffer_size(zp, extra_length))\n    return ZIP_ALLOC_ERROR;\n\n  if(!vfread(zp->header_buffer, extra_length, 1, zp->vf))\n    return ZIP_READ_ERROR;\n\n  mfopen(zp->header_buffer, extra_length, &mf);\n\n  while(extra_length >= 4)\n  {\n    extra_length -= 4;\n    tag = mfgetw(&mf);\n    sz = mfgetw(&mf);\n    if(sz > extra_length)\n      return ZIP_NO_ZIP64_EXTRA_DATA;\n\n    if(tag == 0x0001) /* Zip64 extra data field. */\n    {\n      if(is_central)\n      {\n        // Central headers only contain the used fields (fixed order).\n        if(fh->uncompressed_size >= 0xfffffffful)\n        {\n          if(sz < 8)\n            return ZIP_INVALID_ZIP64;\n\n          fh->uncompressed_size = mfgetuq(&mf);\n        }\n        if(fh->compressed_size >= 0xfffffffful)\n        {\n          if(sz < 8)\n            return ZIP_INVALID_ZIP64;\n\n          fh->compressed_size = mfgetuq(&mf);\n        }\n        if(fh->offset >= 0xfffffffful)\n        {\n          if(sz < 8)\n            return ZIP_INVALID_ZIP64;\n\n          fh->offset = mfgetuq(&mf);\n        }\n        // Ignore start disk.\n      }\n      else\n      {\n        // Local headers always contain both length fields and nothing else.\n        if(sz < ZIP64_LOCAL_EXTRA_LEN - 4)\n          return ZIP_INVALID_ZIP64;\n\n        if(fh->uncompressed_size >= 0xfffffffful)\n          fh->uncompressed_size = mfgetuq(&mf);\n        else\n          mf.current += 8;\n\n        if(fh->compressed_size >= 0xfffffffful)\n          fh->compressed_size = mfgetuq(&mf);\n        else\n          mf.current += 8;\n      }\n      return ZIP_SUCCESS;\n    }\n\n    extra_length -= sz;\n    mfseek(&mf, sz, SEEK_CUR);\n  }\n  return ZIP_NO_ZIP64_EXTRA_DATA;\n}\n\n/**\n * Read a zip file header from the central directory. This function will\n * allocate a new zip_file_header struct at the provided destination.\n */\nstatic enum zip_error zip_read_central_file_header(struct zip_archive *zp,\n struct zip_file_header **_central_fh)\n{\n  struct zip_file_header *central_fh;\n  enum zip_error result;\n  char buffer[CENTRAL_FILE_HEADER_LEN];\n  struct memfile mf;\n\n  int extract_version;\n  int file_name_length;\n  int extra_length;\n  int skip_length;\n  int method;\n  int flags;\n\n  result = zip_read_file_header_signature(zp, true);\n  if(result)\n    return result;\n\n  // We already read four\n  if(!vfread(buffer, CENTRAL_FILE_HEADER_LEN - 4, 1, zp->vf))\n    return ZIP_READ_ERROR;\n\n  mfopen(buffer, CENTRAL_FILE_HEADER_LEN - 4, &mf);\n\n  // Jump ahead to get the file name length since it's needed to allocate the\n  // file header struct now.\n  mfseek(&mf, 24, SEEK_SET);\n  file_name_length = mfgetw(&mf);\n  mfseek(&mf, 0, SEEK_SET);\n\n  central_fh = zip_allocate_file_header(file_name_length);\n  if(!central_fh)\n    return ZIP_ALLOC_ERROR;\n\n  *_central_fh = central_fh;\n\n  // Version made by              2\n  // Version needed to extract    2\n  mf.current += 2;\n  extract_version = mfgetw(&mf);\n  if(ZIP_VERSION(extract_version) > ZIP64_VERSION_MINIMUM)\n    return ZIP_UNSUPPORTED_VERSION;\n\n  // General purpose bit flag     2\n\n  flags = mfgetw(&mf);\n\n  if((flags & ~(ZIP_F_ALLOWED | ZIP_F_UNUSED)) != 0)\n  {\n    warn(\n      \"Zip using unsupported options \"\n      \"(allowing %d, found %d -- unsupported: %d).\\n\",\n      ZIP_F_ALLOWED,\n      flags,\n      flags & ~ZIP_F_ALLOWED\n    );\n    return ZIP_UNSUPPORTED_FLAGS;\n  }\n  central_fh->flags = flags;\n\n  // Compression method           2\n\n  method = mfgetw(&mf);\n\n  if(method < 0)\n    return ZIP_READ_ERROR;\n\n  if(!zip_method_is_supported(method))\n    return ZIP_UNSUPPORTED_DECOMPRESSION;\n\n  central_fh->method = method;\n\n  // File last modification time  2\n  // File last modification date  2\n  mf.current += 4;\n\n  // CRC-32, sizes                12\n\n  central_fh->crc32 = mfgetud(&mf);\n  central_fh->compressed_size = mfgetud(&mf);\n  central_fh->uncompressed_size = mfgetud(&mf);\n\n  // File name length             2\n  // Extra field length           2\n  // File comment length          2\n\n  file_name_length = mfgetw(&mf);\n  central_fh->file_name_length = file_name_length;\n  extra_length = mfgetw(&mf);\n  skip_length =  mfgetw(&mf);\n\n  // Disk number of file start    2\n  // Internal file attributes     2\n  // External file attributes     4\n  mf.current += 8;\n\n  // Offset to local header       4 (from start of file)\n  central_fh->offset = mfgetud(&mf);\n\n  // File name (n)\n  vfread(central_fh->file_name, file_name_length, 1, zp->vf);\n  central_fh->file_name[file_name_length] = 0;\n\n  // Version 4.5 and up: if one of several fields is maximum value, it's Zip64.\n  if(ZIP_VERSION(extract_version) >= ZIP64_VERSION_MINIMUM &&\n   zip_file_is_zip64(central_fh))\n  {\n    enum zip_error r = zip_read_file_header_extra_fields(zp, central_fh,\n     extra_length, true);\n    if(r != ZIP_SUCCESS)\n      return r;\n  }\n  else\n    skip_length += extra_length;\n\n  // Done. Skip to the position where the next header should be.\n  if(skip_length && vfseek(zp->vf, skip_length, SEEK_CUR))\n    return ZIP_SEEK_ERROR;\n\n  if(zip_is_ignore_file(central_fh->file_name, file_name_length))\n    return ZIP_IGNORE_FILE;\n\n  return ZIP_SUCCESS;\n}\n\n/**\n * Verify a local zip file header against the header read from the central\n * directory. Any serious discrepancies mean this file may be invalid.\n */\nstatic enum zip_error zip_verify_local_file_header(struct zip_archive *zp,\n struct zip_file_header *central_fh)\n{\n  enum zip_error result;\n  char buffer[LOCAL_FILE_HEADER_LEN];\n  struct memfile mf;\n  boolean local_is_zip64 = false;\n\n  uint32_t crc32;\n  uint64_t compressed_size;\n  uint64_t uncompressed_size;\n  int file_name_length;\n  int extra_length;\n  int data_position;\n  int version;\n  int flags;\n\n  result = zip_read_file_header_signature(zp, false);\n  if(result)\n    return result;\n\n  // We already read four\n  if(!vfread(buffer, LOCAL_FILE_HEADER_LEN - 4, 1, zp->vf))\n    return ZIP_READ_ERROR;\n\n  mfopen(buffer, LOCAL_FILE_HEADER_LEN - 4, &mf);\n\n  // Version required to extract  2\n  version = mfgetw(&mf);\n  if(ZIP_VERSION(version) > ZIP64_VERSION_MINIMUM)\n    return ZIP_UNSUPPORTED_VERSION;\n\n  // General purpose bit flag     2\n  flags = mfgetw(&mf);\n\n  // Compression method           2\n  // File last modification time  2\n  // File last modification date  2\n  mf.current += 6;\n\n  // CRC-32, sizes              12\n  crc32 = mfgetud(&mf);\n  compressed_size = mfgetud(&mf);\n  uncompressed_size = mfgetud(&mf);\n\n  // File name length           2\n  // Extra field length         2\n  file_name_length = mfgetw(&mf);\n  extra_length = mfgetw(&mf);\n\n  data_position = vftell(zp->vf) + file_name_length + extra_length;\n\n  // If there is a data descriptor, or if one of the size fields suggests\n  // there is a Zip64 extra field, then search for the Zip64 extra field.\n  if(ZIP_VERSION(version) >= ZIP64_VERSION_MINIMUM &&\n   (compressed_size >= 0xfffffffful || uncompressed_size >= 0xfffffffful ||\n   (flags & ZIP_F_DATA_DESCRIPTOR)))\n  {\n    struct zip_file_header tmp;\n    enum zip_error res;\n\n    tmp.compressed_size = compressed_size;\n    tmp.uncompressed_size = uncompressed_size;\n\n    // Skip filename\n    if(vfseek(zp->vf, file_name_length, SEEK_CUR))\n      return ZIP_SEEK_ERROR;\n\n    res = zip_read_file_header_extra_fields(zp, &tmp, extra_length, false);\n    if(res == ZIP_SUCCESS)\n    {\n      local_is_zip64 = true;\n      compressed_size = tmp.compressed_size;\n      uncompressed_size = tmp.uncompressed_size;\n    }\n    else\n\n    if(res != ZIP_NO_ZIP64_EXTRA_DATA)\n      return res;\n  }\n\n  if(flags & ZIP_F_DATA_DESCRIPTOR)\n  {\n    // With data descriptor.\n    char dd_buffer[24];\n    struct memfile dd_mf;\n    uint32_t peek;\n    int sz = (local_is_zip64 ? ZIP64_DATA_DESCRIPTOR_LEN : DATA_DESCRIPTOR_LEN) + 4;\n\n    // Signature (optional)       4\n    // CRC-32, sizes              12 (regular) or 20 (Zip64)\n\n    vfseek(zp->vf, data_position + central_fh->compressed_size, SEEK_SET);\n    vfread(dd_buffer, sz, 1, zp->vf);\n    mfopen(dd_buffer, sz, &dd_mf);\n\n    // The data descriptor may or may not have an optional signature field,\n    // meaning it may be either 12 or 16 bytes long (20 or 24 for Zip64).\n    // If the CRC-32 is equal to the magic and the next four bytes are equal\n    // to the CRC-32, this is probably why.\n    crc32 = mfgetud(&dd_mf);\n    peek = mfgetud(&dd_mf);\n    if(crc32 == data_descriptor_sig && peek == central_fh->crc32)\n      crc32 = peek;\n    else\n      dd_mf.current -= 4;\n\n    if(local_is_zip64)\n    {\n      compressed_size = mfgetuq(&dd_mf);\n      uncompressed_size = mfgetuq(&dd_mf);\n    }\n    else\n    {\n      compressed_size = mfgetud(&dd_mf);\n      uncompressed_size = mfgetud(&dd_mf);\n    }\n  }\n\n  // Verify values are correct.\n  if((flags & ~ZIP_F_UNUSED) != (central_fh->flags & ~ZIP_F_UNUSED) ||\n   crc32 != central_fh->crc32 ||\n   compressed_size != central_fh->compressed_size ||\n   uncompressed_size != central_fh->uncompressed_size)\n    return ZIP_HEADER_MISMATCH;\n\n  if(vfseek(zp->vf, data_position, SEEK_SET))\n    return ZIP_SEEK_ERROR;\n\n  return ZIP_SUCCESS;\n}\n\n/**\n * Write a local or central zip file header to the output file.\n * The provided zip file header struct's data should be fully initialized.\n */\nstatic enum zip_error zip_write_file_header(struct zip_archive *zp,\n struct zip_file_header *fh, boolean is_central)\n{\n  enum zip_error result = ZIP_SUCCESS;\n  boolean zip64_uncompressed = (fh->uncompressed_size >= 0xfffffffful);\n  boolean zip64_compressed = (fh->compressed_size >= 0xfffffffful);\n  boolean zip64_offset = (fh->offset >= 0xfffffffful);\n  boolean zip64_extra = false;\n\n  char *magic;\n  struct memfile mf;\n  size_t header_size;\n  size_t extra_size = 0;\n  uint16_t ver;\n  int i;\n\n  if(is_central)\n  {\n    zip64_extra = zip_file_is_zip64(fh);\n    if(zip64_extra)\n    {\n      extra_size = 4;\n      if(zip64_uncompressed)\n        extra_size += 8;\n      if(zip64_compressed)\n        extra_size += 8;\n      if(zip64_offset)\n        extra_size += 8;\n      // Multi-disk not supported; extra disk field won't be present.\n      assert(extra_size < ZIP64_MAX_EXTRA_LEN);\n    }\n    header_size = fh->file_name_length + CENTRAL_FILE_HEADER_LEN + extra_size;\n    magic = file_sig_central;\n  }\n  else\n  {\n#ifndef ZIP_WRITE_DATA_DESCRIPTOR\n    // Position to write CRC, sizes after file write\n    int64_t header_start = vftell(zp->vf);\n    zp->stream_crc_position = header_start + 14;\n    zp->stream_zip64_position = header_start + LOCAL_FILE_HEADER_LEN +\n     fh->file_name_length + 4;\n#endif\n    zip64_extra = zp->zip64_current;\n    if(zip64_extra)\n      extra_size = ZIP64_LOCAL_EXTRA_LEN;\n\n    header_size = fh->file_name_length + LOCAL_FILE_HEADER_LEN + extra_size;\n    magic = file_sig_local;\n  }\n\n  if(header_size > zp->header_buffer_alloc)\n  {\n    uint8_t *tmp = (uint8_t *)realloc(zp->header_buffer, header_size);\n    if(!tmp)\n      return ZIP_ALLOC_ERROR;\n\n    zp->header_buffer = tmp;\n    zp->header_buffer_alloc = header_size;\n  }\n\n  mfopen_wr(zp->header_buffer, header_size, &mf);\n\n  // Signature\n  for(i = 0; i<4; i++)\n    mfputc(magic[i], &mf);\n\n  // Version made by (central directory only)\n  // Version needed to extract\n  ver = zip64_extra ? ZIP64_VERSION_MINIMUM : ZIP_VERSION_MINIMUM;\n  mfputw(ver, &mf);\n  if(is_central)\n    mfputw(ver, &mf);\n\n  // General purpose bit flag\n  mfputw(fh->flags, &mf);\n\n  // Compression method\n  mfputw(fh->method, &mf);\n\n  // File last modification time\n  // File last modification date\n  mfputud(zp->header_timestamp, &mf);\n\n  // note: zp->stream_crc_position should be here.\n\n  // CRC-32\n  mfputud(fh->crc32, &mf);\n\n  // Compressed size\n  mfputud(zip64_compressed ? 0xfffffffful : fh->compressed_size, &mf);\n\n  // Uncompressed size\n  mfputud(zip64_uncompressed ? 0xfffffffful : fh->uncompressed_size, &mf);\n\n  // File name length\n  mfputw(fh->file_name_length, &mf);\n\n  // Extra field length\n  mfputw(extra_size, &mf);\n\n  // (central directory only fields)\n  if(is_central)\n  {\n    // File comment length\n    mfputw(0, &mf);\n\n    // Disk number where file starts\n    mfputw(0, &mf);\n\n    // Internal file attributes\n    mfputw(0, &mf);\n\n    // External file attributes\n    mfputd(0, &mf);\n\n    // Relative offset of local file header\n    mfputd(zip64_offset ? 0xfffffffful : fh->offset, &mf);\n  }\n\n  // File name\n  mfwrite(fh->file_name, fh->file_name_length, 1, &mf);\n\n  // Extra field (zero bytes or 32 bytes if Zip64)\n  if(zip64_extra)\n  {\n    // Tag: Zip64 Extra field\n    mfputw(0x0001, &mf);\n    // Length of extra field\n    mfputw(extra_size - 4, &mf);\n    // Uncompressed size\n    if(!is_central || zip64_uncompressed)\n      mfputuq(fh->uncompressed_size, &mf);\n    // Compressed size\n    if(!is_central || zip64_compressed)\n      mfputuq(fh->compressed_size, &mf);\n    // Local header offset\n    if(is_central && zip64_offset)\n      mfputuq(fh->offset, &mf);\n  }\n\n  // File comment (zero bytes)\n\n  if(!vfwrite(zp->header_buffer, header_size, 1, zp->vf))\n    result = ZIP_WRITE_ERROR;\n\n  return result;\n}\n\n/***********/\n/* Reading */\n/***********/\n\n/**\n * Allocate or resize the stream buffer. A single buffer is used across the\n * entire zip read/write session to reduce the number of allocations/frees.\n */\nstatic enum zip_error zip_set_stream_buffer_size(struct zip_archive *zp, size_t size)\n{\n  size = MAX(size, ZIP_STREAM_BUFFER_SIZE);\n\n  if(!zp->stream_buffer || zp->stream_buffer_alloc < size)\n  {\n    uint8_t *tmp = (uint8_t *)realloc(zp->stream_buffer, size);\n    if(!tmp)\n      return ZIP_ALLOC_ERROR;\n\n    zp->stream_buffer = tmp;\n    zp->stream_buffer_alloc = size;\n  }\n  return ZIP_SUCCESS;\n}\n\n/**\n * Decompress data from a stream into the destination or into the stream\n * buffer.\n */\nstatic enum zip_error zread_stream(uint8_t *destBuf, size_t readLen,\n size_t *consumed, struct zip_archive *zp)\n{\n  struct zip_stream_data *stream_data = zp->stream_data;\n  boolean direct_write = (readLen == zp->stream_u_left);\n  uint8_t *in;\n  size_t in_size;\n  size_t out_size;\n  enum zip_error (*decompress_fn)(struct zip_stream_data *);\n  enum zip_error result;\n\n  while(readLen)\n  {\n    if(zp->stream_buffer_pos < zp->stream_buffer_end)\n    {\n      size_t amount = zp->stream_buffer_end - zp->stream_buffer_pos;\n      amount = MIN(readLen, amount);\n\n      memcpy(destBuf, zp->stream_buffer + zp->stream_buffer_pos, amount);\n      zp->stream_buffer_pos += amount;\n      destBuf += amount;\n      readLen -= amount;\n      if(!readLen)\n        break;\n    }\n\n    // Fetch more decompressed data. If direct_write is true, this can be\n    // fetched directly into the output. Otherwise, it needs to stick around\n    // so it can be copied for future reads.\n\n    if(zp->stream->decompress_block)\n    {\n      result = zip_set_stream_buffer_size(zp, ZIP_STREAM_BUFFER_SIZE);\n      if(result != ZIP_SUCCESS)\n        return result;\n\n      decompress_fn = zp->stream->decompress_block;\n\n      in = zp->stream_buffer;\n      in_size = ZIP_STREAM_BUFFER_SIZE;\n      out_size = 0;\n\n      if(!direct_write)\n      {\n        in = zp->stream_buffer + ZIP_STREAM_BUFFER_U_SIZE;\n        in_size = ZIP_STREAM_BUFFER_C_SIZE;\n        out_size = ZIP_STREAM_BUFFER_U_SIZE;\n      }\n    }\n    else\n\n    if(zp->stream_left)\n    {\n      size_t size = zp->streaming_file->compressed_size;\n      out_size = zp->streaming_file->uncompressed_size;\n      in_size = size;\n\n      if(!direct_write)\n        size += out_size;\n\n      result = zip_set_stream_buffer_size(zp, size);\n      if(result != ZIP_SUCCESS)\n        return result;\n\n      decompress_fn = zp->stream->decompress_file;\n\n      in = zp->stream_buffer;\n      if(!direct_write)\n        in += out_size;\n    }\n    // If block decompression isn't available, the entire file should have\n    // already been decompressed. Requesting any more data is an instant EOF.\n    else\n      return ZIP_EOF;\n\n    if(direct_write)\n    {\n      zp->stream->output(stream_data, destBuf, readLen);\n      zp->stream_buffer_end = 0;\n      readLen = 0;\n    }\n    else\n    {\n      zp->stream->output(stream_data, zp->stream_buffer, out_size);\n      zp->stream_buffer_end = out_size;\n    }\n\n    while((result = decompress_fn(stream_data)) == ZIP_INPUT_EMPTY)\n    {\n      in_size = MIN(in_size, zp->stream_left - *consumed);\n      if(!in_size)\n        return ZIP_EOF;\n\n      zp->stream->input(stream_data, in, in_size);\n      if(!vfread(in, in_size, 1, zp->vf))\n        return ZIP_READ_ERROR;\n\n      *consumed += in_size;\n    }\n    if(result != ZIP_OUTPUT_FULL && result != ZIP_STREAM_FINISHED)\n      return result;\n\n    zp->stream_buffer_pos = 0;\n  }\n  return ZIP_SUCCESS;\n}\n\n/**\n * Read data from a zip archive. Only works while streaming a file.\n */\nenum zip_error zread(void *destBuf, size_t readLen, struct zip_archive *zp)\n{\n  struct zip_file_header *fh;\n  size_t consumed = 0;\n\n  enum zip_error result;\n\n  result = zp->read_stream_error;\n  if(result)\n    goto err_out;\n\n  if(!readLen)\n    return ZIP_SUCCESS;\n\n  if(!zp->stream_u_left)\n    return ZIP_EOF;\n\n  // Can't read past the length of the file...\n  readLen = MIN(readLen, zp->stream_u_left);\n\n  fh = zp->streaming_file;\n\n  // No compression\n  if(fh->method == ZIP_M_NONE)\n  {\n    consumed = readLen;\n    if(!vfread(destBuf, readLen, 1, zp->vf))\n    {\n      result = ZIP_EOF;\n      goto err_out;\n    }\n  }\n  else\n\n  // Decompression via stream\n  if(zp->stream)\n  {\n    result = zread_stream(destBuf, readLen, &consumed, zp);\n    if(result)\n      goto err_out;\n  }\n  else\n  {\n    result = ZIP_UNSUPPORTED_DECOMPRESSION;\n    goto err_out;\n  }\n\n  // Update the crc32 and streamed amount\n  zp->stream_crc32 = crc32(zp->stream_crc32, destBuf, readLen);\n  zp->stream_u_left -= readLen;\n  zp->stream_left -= consumed;\n  return ZIP_SUCCESS;\n\nerr_out:\n  if(result != ZIP_EOF)\n    zip_error(\"zread\", result);\n\n  return result;\n}\n\n/**\n * Get the name of the next file in the archive.\n */\nenum zip_error zip_get_next_name(struct zip_archive *zp,\n char *name, int name_buffer_size)\n{\n  struct zip_file_header *fh;\n  enum zip_error result;\n\n  result = zp->read_file_error;\n  if(result)\n    goto err_out;\n\n  if(zp->pos >= zp->num_files)\n  {\n    return ZIP_EOF;\n  }\n\n  fh = zp->files[zp->pos];\n\n  // Copy the file name, if requested\n  if(name && name_buffer_size)\n  {\n    name_buffer_size = MIN(name_buffer_size, fh->file_name_length);\n    memcpy(name, fh->file_name, name_buffer_size);\n    name[name_buffer_size] = 0;\n  }\n\n  return ZIP_SUCCESS;\n\nerr_out:\n  if(result != ZIP_EOF)\n    zip_error(\"zip_get_next_name\", result);\n  return result;\n}\n\n/**\n * Get the MZX properties of the next file in the archive.\n * These fields need to be initialized externally; see world_format.h.\n *\n * @param zp        zip archive structure.\n * @param file_id   pointer to variable to store the file type ID to, or null.\n * @param board_id  pointer to variable to store the board number to, or null.\n * @param robot_id  pointer to variable to store the object number to, or null.\n * @return          `ZIP_SUCCESS` on success;\n *                  `ZIP_EOF` if the end of archive has been reached;\n *                  `ZIP_NULL` if `zp` is a null pointer;\n *                  `ZIP_INVALID_READ_IN_WRITE_MODE` if `zp` is a write archive.\n *                  `ZIP_INVALID_FILE_READ_UNINITIALIZED` if the central\n *                  directory has not been read yet;\n *                  `ZIP_INVALID_FILE_READ_IN_STREAM_MODE` if `zp` has opened\n *                  the current file for reading.\n */\nenum zip_error zip_get_next_mzx_file_id(struct zip_archive *zp,\n unsigned int *file_id, unsigned int *board_id, unsigned int *robot_id)\n{\n  struct zip_file_header *fh;\n  enum zip_error result = ZIP_NULL;\n\n  if(!zp)\n    goto err_out;\n\n  result = zp->read_file_error;\n  if(result)\n    goto err_out;\n\n  if(zp->pos >= zp->num_files)\n    return ZIP_EOF;\n\n  fh = zp->files[zp->pos];\n\n  if(file_id)\n    *file_id = fh->mzx_file_id;\n\n  if(board_id)\n    *board_id = fh->mzx_board_id;\n\n  if(robot_id)\n    *robot_id = fh->mzx_robot_id;\n\n  return ZIP_SUCCESS;\n\nerr_out:\n  if(result != ZIP_EOF)\n    zip_error(\"zip_get_next_file_id\", result);\n  return result;\n}\n\n/**\n * Get the uncompressed length of the next file in the archive within the\n * range of `size_t`. This should be used when reading whole files to memory.\n *\n * @param zp      zip archive structure.\n * @param u_size  pointer to `size_t` variable to store uncompressed size to.\n *                If null, no size will be stored.\n * @return        `ZIP_SUCCESS` on success;\n *                `ZIP_EOF` if the end of the archive has been reached;\n *                `ZIP_BOUND_ERROR` if the uncompressed size is greater than\n *                or equal to `SIZE_MAX`;\n *                `ZIP_NULL` if `zp` is a null pointer;\n *                `ZIP_INVALID_READ_IN_WRITE_MODE` if `zp` is a write archive.\n *                `ZIP_INVALID_FILE_READ_UNINITIALIZED` if the central\n *                directory has not been read yet;\n *                `ZIP_INVALID_FILE_READ_IN_STREAM_MODE` if `zp` has opened\n *                the current file for reading.\n */\nenum zip_error zip_get_next_uncompressed_size(struct zip_archive *zp,\n size_t *u_size)\n{\n  uint64_t tmp = 0;\n  enum zip_error result = zip_get_next_uncompressed_size64(zp, &tmp);\n  if(result)\n    return result;\n\n  if(tmp >= SIZE_MAX)\n    return ZIP_BOUND_ERROR;\n\n  if(u_size)\n    *u_size = tmp;\n\n  return ZIP_SUCCESS;\n}\n\n/**\n * Get the uncompressed length of the next file in the archive. This should\n * be used when the entire file does not need to fit into memory.\n *\n * @param zp      zip archive structure.\n * @param u_size  pointer to `uint64_t` variable to store uncompressed size to.\n *                If null, no size will be stored.\n * @return        `ZIP_SUCCESS` on success;\n *                `ZIP_EOF` if the end of the archive has been reached;\n *                `ZIP_NULL` if `zp` is a null pointer;\n *                `ZIP_INVALID_READ_IN_WRITE_MODE` if `zp` is a write archive.\n *                `ZIP_INVALID_FILE_READ_UNINITIALIZED` if the central\n *                directory has not been read yet;\n *                `ZIP_INVALID_FILE_READ_IN_STREAM_MODE` if `zp` has opened\n *                the current file for reading.\n */\nenum zip_error zip_get_next_uncompressed_size64(struct zip_archive *zp,\n uint64_t *u_size)\n{\n  enum zip_error result = ZIP_NULL;\n\n  if(!zp)\n    goto err_out;\n\n  result = zp->read_file_error;\n  if(result)\n    goto err_out;\n\n  if(zp->pos >= zp->num_files)\n    return ZIP_EOF;\n\n  if(u_size)\n    *u_size = zp->files[zp->pos]->uncompressed_size;\n\n  return ZIP_SUCCESS;\n\nerr_out:\n  if(result != ZIP_EOF)\n    zip_error(\"zip_get_next_u_size\", result);\n  return result;\n}\n\n/**\n * Get the compression method of the next file in the archive.\n */\nenum zip_error zip_get_next_method(struct zip_archive *zp, unsigned int *method)\n{\n  enum zip_error result;\n\n  result = zp->read_file_error;\n  if(result)\n    goto err_out;\n\n  if(zp->pos >= zp->num_files)\n  {\n    return ZIP_EOF;\n  }\n\n  if(method)\n    *method = zp->files[zp->pos]->method;\n\n  return ZIP_SUCCESS;\n\nerr_out:\n  if(result != ZIP_EOF)\n    zip_error(\"zip_get_next_method\", result);\n  return result;\n}\n\n/**\n * Common function for zip stream opening.\n * mode should be either ZIP_S_READ_STREAM or ZIP_S_READ_MEMSTREAM.\n */\nstatic enum zip_error zip_read_stream_open(struct zip_archive *zp, uint8_t mode)\n{\n  struct zip_file_header *central_fh;\n\n  uint64_t c_size;\n  uint64_t u_size;\n  uint16_t method;\n\n  uint64_t read_pos;\n  enum zip_error result;\n\n  result = (zp ? zp->read_file_error : ZIP_NULL);\n  if(result)\n    return result;\n\n  if(zp->pos >= zp->num_files)\n    return ZIP_EOF;\n\n  central_fh = zp->files[zp->pos];\n\n  c_size = central_fh->compressed_size;\n  u_size = central_fh->uncompressed_size;\n  method = central_fh->method;\n\n  // Special mem stream checks.\n  if(mode == ZIP_S_READ_MEMSTREAM)\n  {\n    if(!zp->is_memory)\n      return ZIP_NOT_MEMORY_ARCHIVE;\n\n    if(method != ZIP_M_NONE)\n      return ZIP_UNSUPPORTED_METHOD_MEMORY_STREAM;\n  }\n\n  // Attempt to get stream functions for this compression method. This will\n  // return an error if the method is not supported for decompression.\n  result = zip_get_stream(zp, method, mode);\n  if(result)\n    return result;\n\n  // Seek to the start of the record\n  read_pos = vftell(zp->vf);\n  if(read_pos != central_fh->offset)\n  {\n    if(vfseek(zp->vf, central_fh->offset, SEEK_SET))\n      return ZIP_SEEK_ERROR;\n  }\n\n  // Verify the local header matches the central directory header.\n  result = zip_verify_local_file_header(zp, central_fh);\n  if(result)\n    return result;\n\n  // Everything looks good. Set up stream mode.\n  zp->mode = mode;\n  zp->stream_buffer_pos = 0;\n  zp->stream_buffer_end = 0;\n  zp->streaming_file = central_fh;\n  zp->stream_u_left = u_size;\n  zp->stream_left = c_size;\n  zp->stream_crc32 = 0;\n\n  if(zp->stream)\n    zp->stream->decompress_open(zp->stream_data, method, central_fh->flags);\n\n  precalculate_read_errors(zp);\n  return ZIP_SUCCESS;\n}\n\n/**\n * Open a stream to read the next file from a zip archive. If provided, destLen\n * will be the uncompressed size of the file on return (or 0 upon error).\n */\nenum zip_error zip_read_open_file_stream(struct zip_archive *zp,\n uint64_t *destLen)\n{\n  enum zip_error result = zip_read_stream_open(zp, ZIP_S_READ_STREAM);\n  if(result)\n    goto err_out;\n\n  if(destLen)\n    *destLen = zp->streaming_file->uncompressed_size;\n\n  return ZIP_SUCCESS;\n\nerr_out:\n  if(result != ZIP_EOF)\n    zip_error(\"zip_read_open_file_stream\", result);\n  if(destLen)\n    *destLen = 0;\n  return result;\n}\n\n/**\n * Like zip_read_open_file_stream, but allows the direct reading of\n * uncompressed files. This is abusable, but quicker than the alternatives.\n *\n * ZIP_NOT_MEMORY_ARCHIVE or ZIP_UNSUPPORTED_METHOD_MEMORY_STREAM will be\n * silently returned if the current archive or file doesn't support this; use\n * regular functions instead in this case.\n */\nenum zip_error zip_read_open_mem_stream(struct zip_archive *zp,\n struct memfile *mf)\n{\n  enum zip_error result = zip_read_stream_open(zp, ZIP_S_READ_MEMSTREAM);\n  if(result)\n    goto err_out;\n\n  if(!vfile_get_memfile_block(zp->vf, zp->streaming_file->compressed_size, mf))\n  {\n    result = ZIP_EOF;\n    goto err_out;\n  }\n\n  return ZIP_SUCCESS;\n\nerr_out:\n  if(result != ZIP_EOF &&\n   result != ZIP_NOT_MEMORY_ARCHIVE &&\n   result != ZIP_UNSUPPORTED_METHOD_MEMORY_STREAM)\n    zip_error(\"zip_read_open_mem_stream\", result);\n  memset(mf, 0, sizeof(struct memfile));\n  return result;\n}\n\n/**\n * Shared function for closing both types of zip read streams.\n */\nenum zip_error zip_read_close_stream(struct zip_archive *zp)\n{\n  uint32_t expected_crc32;\n  uint32_t stream_crc32;\n  uint8_t buffer[512];\n  int size;\n\n  enum zip_error result;\n\n  // mem streams need special cleanup but are mostly the same.\n  if(zp && zp->mode == ZIP_S_READ_MEMSTREAM)\n  {\n    struct memfile mf;\n    size_t c_size = zp->streaming_file->compressed_size;\n\n    // If this doesn't work, something modified the zip archive structures.\n    if(!vfile_get_memfile_block(zp->vf, c_size, &mf))\n      assert(0);\n\n    zp->read_stream_error = ZIP_SUCCESS;\n    zp->stream_u_left = 0;\n    zp->stream_crc32 = crc32(0, mf.current, c_size);\n  }\n\n  result = (zp ? zp->read_stream_error : ZIP_NULL);\n  if(result)\n    goto err_out;\n\n  // If the stream was incomplete, finish the crc32\n  while(zp->stream_u_left)\n  {\n    size = MIN(sizeof(buffer), zp->stream_u_left);\n    result = zread(buffer, size, zp);\n    if(result)\n      break;\n  }\n\n  // TODO maybe check final in/out...\n  if(zp->stream)\n    zp->stream->close(zp->stream_data, NULL, NULL);\n\n  expected_crc32 = zp->streaming_file->crc32;\n  stream_crc32 = zp->stream_crc32;\n\n  // Increment the position and clear the streaming vars\n  zp->mode = ZIP_S_READ_FILES;\n  zp->streaming_file = NULL;\n  zp->stream = NULL;\n  zp->stream_left = 0;\n  zp->stream_u_left = 0;\n  zp->stream_crc32 = 0;\n  zp->pos++;\n\n  precalculate_read_errors(zp);\n\n  // Check for an error from zread...\n  if(result)\n    goto err_out;\n\n  // Check the CRC-32 of the stream\n  if(expected_crc32 != stream_crc32)\n  {\n    warn(\"crc check: expected %\"PRIx32\", got %\"PRIx32\"\\n\",\n     expected_crc32, stream_crc32);\n    result = ZIP_CRC32_MISMATCH;\n    goto err_out;\n  }\n\n  return ZIP_SUCCESS;\n\nerr_out:\n  zip_error(\"zip_read_close_stream\", result);\n  return result;\n}\n\n/**\n * Rewind to the start of the zip archive.\n */\nenum zip_error zip_rewind(struct zip_archive *zp)\n{\n  enum zip_error result;\n\n  result = zp->read_file_error;\n  if(result)\n    goto err_out;\n\n  if(zp->num_files == 0)\n  {\n    return ZIP_EOF;\n  }\n\n  zp->pos = 0;\n\n  return ZIP_SUCCESS;\n\nerr_out:\n  zip_error(\"zip_rewind\", result);\n  return result;\n}\n\n/**\n * Skip the current file in the zip archive.\n */\nenum zip_error zip_skip_file(struct zip_archive *zp)\n{\n  enum zip_error result;\n\n  result = zp->read_file_error;\n  if(result)\n    goto err_out;\n\n  if(zp->pos >= zp->num_files)\n  {\n    return ZIP_EOF;\n  }\n\n  zp->pos++;\n\n  return ZIP_SUCCESS;\n\nerr_out:\n  zip_error(\"zip_skip_file\", result);\n  return result;\n}\n\n/**\n * Read a file from the a zip archive. If provided, the value of readLen will\n * be set to the number of bytes read into the buffer.\n */\nenum zip_error zip_read_file(struct zip_archive *zp,\n void *destBuf, size_t destLen, size_t *readLen)\n{\n  uint64_t u_size;\n  enum zip_error result;\n\n  // No need to check mode; the functions used here will\n\n  // We can't accept a NULL pointer here, though\n  if(!destBuf)\n  {\n    result = ZIP_NULL_BUF;\n    goto err_out;\n  }\n\n  result = zip_read_open_file_stream(zp, &u_size);\n  if(result)\n    goto err_out;\n\n  u_size = MIN(destLen, u_size);\n\n  result = zread(destBuf, u_size, zp);\n  if(result && result != ZIP_EOF)\n    goto err_close;\n\n  result = zip_read_close_stream(zp);\n  if(result)\n    goto err_out;\n\n  if(readLen)\n    *readLen = u_size;\n\n  return ZIP_SUCCESS;\n\nerr_close:\n  zip_read_close_stream(zp);\n\nerr_out:\n  if(result != ZIP_EOF)\n    zip_error(\"zip_read_file\", result);\n\n  if(readLen)\n    *readLen = 0;\n\n  return result;\n}\n\n/***********/\n/* Writing */\n/***********/\n\n/**\n * Check if there's enough room in a memory archive to write a number of bytes\n * at the current position in the file. If there isn't, and if the buffer is\n * expandable, vfile_get_memfile_block will ensure that the buffer is expanded\n * to allow the requested size. Otherwise, this function will return ZIP_EOF.\n */\nstatic enum zip_error zip_ensure_capacity(size_t len, struct zip_archive *zp)\n{\n  if(!vfile_get_memfile_block(zp->vf, len, NULL))\n    return ZIP_EOF;\n\n  return ZIP_SUCCESS;\n}\n\n/**\n * Flush data out to the file.\n */\nstatic enum zip_error zwrite_out(const void *buffer, size_t len,\n struct zip_archive *zp)\n{\n  if(zp->is_memory && zip_ensure_capacity(len, zp))\n    return ZIP_EOF;\n\n  if(!vfwrite(buffer, len, 1, zp->vf))\n    return ZIP_WRITE_ERROR;\n\n  return ZIP_SUCCESS;\n}\n\n/**\n * Compress an input buffer and output it as-needed until the entire input has\n * been consumed. A null buffer can be provided to end the stream.\n */\nstatic enum zip_error zwrite_stream_compress(const void *buffer, size_t len,\n size_t *write_len, struct zip_archive *zp)\n{\n  struct zip_stream_data *stream_data = zp->stream_data;\n  uint8_t *out = zp->stream_buffer + ZIP_STREAM_BUFFER_U_SIZE;\n  size_t out_len = ZIP_STREAM_BUFFER_C_SIZE;\n  enum zip_error result;\n  boolean finish = !buffer;\n\n  if(!finish)\n    zp->stream->input(stream_data, buffer, len);\n\n  assert(zp->stream->compress_block);\n  while((result = zp->stream->compress_block(stream_data, finish)) == ZIP_OUTPUT_FULL)\n  {\n    result = zwrite_out(out, out_len, zp);\n    if(result)\n      return result;\n\n    *write_len += out_len;\n    zp->stream->output(stream_data, out, out_len);\n  }\n  if(result != ZIP_INPUT_EMPTY && result != ZIP_STREAM_FINISHED)\n    return result;\n\n  if(finish)\n  {\n    uint64_t total_written = zp->stream_left + *write_len;\n    if(result != ZIP_STREAM_FINISHED)\n      return ZIP_COMPRESS_FAILED;\n\n    // Flush any leftover data.\n    if(stream_data->final_output_length > total_written)\n    {\n      out_len = stream_data->final_output_length - total_written;\n      result = zwrite_out(out, out_len, zp);\n      if(result)\n        return result;\n\n      *write_len += out_len;\n    }\n  }\n  return ZIP_SUCCESS;\n}\n\n/**\n * Buffer stream input data, compressing and flushing it to file as-needed.\n */\nstatic enum zip_error zwrite_stream(const void *src, size_t srcLen,\n size_t *write_len, struct zip_archive *zp)\n{\n  enum zip_error result;\n\n  assert(src);\n  if(zp->stream_buffer_pos + srcLen <= zp->stream_buffer_end)\n  {\n    memcpy(zp->stream_buffer + zp->stream_buffer_pos, src, srcLen);\n    zp->stream_buffer_pos += srcLen;\n    return ZIP_SUCCESS;\n  }\n\n  if(zp->stream_buffer_pos)\n  {\n    result = zwrite_stream_compress(zp->stream_buffer, zp->stream_buffer_pos,\n     write_len, zp);\n    zp->stream_buffer_pos = 0;\n    if(result)\n      return result;\n\n    if(srcLen <= zp->stream_buffer_end / 2)\n    {\n      memcpy(zp->stream_buffer, src, srcLen);\n      zp->stream_buffer_pos = srcLen;\n      return ZIP_SUCCESS;\n    }\n  }\n  return zwrite_stream_compress(src, srcLen, write_len, zp);\n}\n\n/**\n * Finish the stream; write anything left on the buffer, then write a null\n * buffer to signal the stream end.\n */\nstatic enum zip_error zwrite_finish(size_t *write_len, struct zip_archive *zp)\n{\n  enum zip_error result;\n\n  if(zp->stream_buffer_pos)\n  {\n    result = zwrite_stream_compress(zp->stream_buffer, zp->stream_buffer_pos,\n     write_len, zp);\n    if(result)\n      return result;\n\n    zp->stream_buffer_pos = 0;\n  }\n  return zwrite_stream_compress(NULL, 0, write_len, zp);\n}\n\n/**\n * Write data to a zip archive. Only works in stream mode.\n */\nenum zip_error zwrite(const void *src, size_t srcLen, struct zip_archive *zp)\n{\n  struct zip_file_header *fh;\n  size_t writeLen = 0;\n\n  enum zip_error result;\n\n  result = (zp ? zp->write_stream_error : ZIP_NULL);\n  if(result)\n    goto err_out;\n\n  if(!srcLen)\n    return ZIP_SUCCESS;\n\n  fh = zp->streaming_file;\n\n  // No compression\n  if(fh->method == ZIP_M_NONE)\n  {\n    writeLen = srcLen;\n    result = zwrite_out(src, srcLen, zp);\n    if(result)\n      goto err_out;\n  }\n  else\n\n  // Compression via stream\n  if(zp->stream)\n  {\n    result = zwrite_stream(src, srcLen, &writeLen, zp);\n    if(result)\n      goto err_out;\n  }\n  else\n  {\n    result = ZIP_UNSUPPORTED_COMPRESSION;\n    goto err_out;\n  }\n\n  // Update the stream\n  fh->uncompressed_size += srcLen;\n  zp->stream_crc32 = crc32(zp->stream_crc32, src, srcLen);\n  zp->stream_left += writeLen;\n  return ZIP_SUCCESS;\n\nerr_out:\n  zip_error(\"zwrite\", result);\n  return result;\n}\n\n/**\n * Write data to a zip archive. Only works in stream mode. This bypasses the\n * input buffer for streams known to only need to write a single block and\n * should only be used internally.\n */\nstatic enum zip_error zwrite_direct(const void *src, size_t srcLen,\n struct zip_archive *zp)\n{\n  size_t writeLen = 0;\n  enum zip_error result;\n\n  if(!zp->stream || zp->stream_buffer_pos)\n    return zwrite(src, srcLen, zp);\n\n  result = zwrite_stream_compress(src, srcLen, &writeLen, zp);\n  if(result)\n    return result;\n\n  // Update the stream\n  zp->streaming_file->uncompressed_size += srcLen;\n  zp->stream_crc32 = crc32(zp->stream_crc32, src, srcLen);\n  zp->stream_left += writeLen;\n  return ZIP_SUCCESS;\n}\n\n/**\n * Enable/disable Zip64 for the next file to be written.\n */\nenum zip_error zip_set_zip64_enabled(struct zip_archive *zp,\n boolean enable_zip64)\n{\n  if(!zp)\n    return ZIP_NULL;\n\n  zp->zip64_enabled = enable_zip64;\n  return ZIP_SUCCESS;\n}\n\n/**\n * Writes the data descriptor for a file. If data descriptors are turned\n * off, this function will seek back and add the data into the local header.\n */\nstatic inline enum zip_error zip_write_data_descriptor(struct zip_archive *zp,\n struct zip_file_header *fh)\n{\n  char buffer[ZIP64_DATA_DESCRIPTOR_LEN];\n  struct memfile mf;\n  uint32_t c_size = MIN(fh->compressed_size, 0xfffffffful);\n  uint32_t u_size = MIN(fh->uncompressed_size, 0xfffffffful);\n\n  mfopen_wr(buffer, DATA_DESCRIPTOR_LEN, &mf);\n  mfputud(fh->crc32, &mf);\n  mfputud(c_size, &mf);\n  mfputud(u_size, &mf);\n\n#ifdef ZIP_WRITE_DATA_DESCRIPTOR\n  {\n    if(zp->zip64_current)\n    {\n      if(zp->is_memory && zip_ensure_capacity(ZIP64_DATA_DESCRIPTOR_LEN, zp))\n        return ZIP_EOF;\n\n      mfopen_wr(buffer, ZIP64_DATA_DESCRIPTOR_LEN, &mf);\n      mfseek(&mf, 4, SEEK_SET);\n      mfputuq(fh->compressed_size, &mf);\n      mfputuq(fh->uncompressed_size, &mf);\n      vfwrite(buffer, ZIP64_DATA_DESCRIPTOR_LEN, 1, zp->vf);\n    }\n    else\n    {\n      // Write data descriptor\n      if(zp->is_memory && zip_ensure_capacity(DATA_DESCRIPTOR_LEN, zp))\n        return ZIP_EOF;\n\n      vfwrite(buffer, 12, 1, zp->vf);\n    }\n  }\n#else\n  {\n    // Go back and write sizes and CRC32\n    int64_t return_position = vftell(zp->vf);\n\n    if(vfseek(zp->vf, zp->stream_crc_position, SEEK_SET))\n      return ZIP_SEEK_ERROR;\n\n    if(!vfwrite(buffer, DATA_DESCRIPTOR_LEN, 1, zp->vf))\n      return ZIP_WRITE_ERROR;\n\n    if(zp->zip64_current)\n    {\n      // Write the Zip64 copies of the sizes.\n      // Both are always present in the local header.\n      if(vfseek(zp->vf, zp->stream_zip64_position, SEEK_SET))\n        return ZIP_SEEK_ERROR;\n\n      vfputq(fh->uncompressed_size, zp->vf);\n      vfputq(fh->compressed_size, zp->vf);\n    }\n\n    if(vfseek(zp->vf, return_position, SEEK_SET))\n      return ZIP_SEEK_ERROR;\n  }\n#endif // !ZIP_WRITE_DATA_DESCRIPTOR\n\n  return ZIP_SUCCESS;\n}\n\n/**\n * Common function for zip write stream opening.\n * mode should be either ZIP_S_WRITE_STREAM or ZIP_S_WRITE_MEMSTREAM.\n */\nstatic enum zip_error zip_write_open_stream(struct zip_archive *zp,\n const char *name, int method, uint8_t mode)\n{\n  struct zip_file_header *fh;\n  uint16_t file_name_len;\n\n  enum zip_error result;\n\n  result = (zp ? zp->write_file_error : ZIP_NULL);\n  if(result)\n    return result;\n\n  // Special mem stream checks.\n  if(mode == ZIP_S_WRITE_MEMSTREAM)\n  {\n    if(!zp->is_memory)\n      return ZIP_NOT_MEMORY_ARCHIVE;\n\n    // Sanity check (this shouldn't actually happen ever).\n    if(method == ZIP_M_DEFLATE)\n      return ZIP_UNSUPPORTED_METHOD_MEMORY_STREAM;\n  }\n\n  // memfiles: make sure there's enough space for the header\n  if(zp->is_memory && zip_ensure_capacity(strlen(name) + 30, zp))\n    return ZIP_EOF;\n\n  // Attempt to get stream functions for this compression method. This will\n  // return an error if the method is not supported for compression.\n  result = zip_get_stream(zp, method, mode);\n  if(result)\n    return result;\n\n  // If needed, expand the files list so the file can be added to it.\n  if(zp->pos == zp->files_alloc)\n  {\n    size_t count = zp->files_alloc * 2;\n    struct zip_file_header **tmp = (struct zip_file_header **)realloc(zp->files,\n     count * sizeof(struct zip_file_header *));\n    if(!tmp)\n      return ZIP_ALLOC_ERROR;\n\n    zp->files = tmp;\n    zp->files_alloc = count;\n  }\n\n  file_name_len = strlen(name);\n  fh = zip_allocate_file_header(file_name_len);\n  if(!fh)\n    return ZIP_ALLOC_ERROR;\n\n  // Set up the header\n  fh->flags = 0;\n#ifdef ZIP_WRITE_DATA_DESCRIPTOR\n  fh->flags |= ZIP_F_DATA_DESCRIPTOR;\n#endif\n#ifdef ZIP_WRITE_DEFLATE_FAST\n  if(method == ZIP_M_DEFLATE)\n    fh->flags |= ZIP_F_DEFLATE_FAST;\n#endif\n  fh->method = method;\n  fh->crc32 = 0;\n  fh->compressed_size = 0;\n  fh->uncompressed_size = 0;\n  fh->offset = vftell(zp->vf);\n  fh->file_name_length = file_name_len;\n  memcpy(fh->file_name, name, file_name_len + 1);\n\n  // Write the header\n  result = zip_write_file_header(zp, fh, 0);\n  if(result)\n  {\n    zip_free_file_header(fh);\n    zp->streaming_file = NULL;\n    return result;\n  }\n\n  // Now that the header is written, it can be added to the files list.\n  zp->running_file_name_length += file_name_len;\n  zp->files[zp->pos] = fh;\n  zp->num_files++;\n\n  // Set up the stream\n  zp->mode = mode;\n  zp->streaming_file = fh;\n  zp->stream_buffer_pos = 0;\n  zp->stream_buffer_end = ZIP_STREAM_BUFFER_U_SIZE;\n  zp->stream_left = 0;\n  zp->stream_crc32 = 0;\n\n  if(zp->stream)\n  {\n    struct zip_stream_data *stream_data = zp->stream_data;\n    zp->stream->compress_open(stream_data, method, fh->flags);\n\n    result = zip_set_stream_buffer_size(zp, ZIP_STREAM_BUFFER_SIZE);\n    if(result != ZIP_SUCCESS)\n      return result;\n\n    zp->stream->output(stream_data, zp->stream_buffer + ZIP_STREAM_BUFFER_U_SIZE,\n     ZIP_STREAM_BUFFER_C_SIZE);\n  }\n\n  precalculate_write_errors(zp);\n  return ZIP_SUCCESS;\n}\n\n/**\n * Enable or disable Zip64 based on a fixed input file size and known\n * compression mode.\n */\nstatic enum zip_error zip_write_autodetect_zip64(struct zip_archive *zp,\n int method, uint8_t mode, size_t src_len)\n{\n  enum zip_error result;\n\n  zp->zip64_current = false;\n  if(!zp->zip64_enabled)\n    return ZIP_SUCCESS;\n\n  if(src_len >= 0xfffffffful)\n  {\n    zp->zip64_current = true;\n    return ZIP_SUCCESS;\n  }\n\n  // Hack--set up the stream early so its compress_bound function can be used.\n  result = zip_get_stream(zp, method, mode);\n  if(result)\n    return result;\n\n  if(zp->stream != NULL && zp->stream->compress_bound != NULL)\n  {\n    size_t bound_len = 0;\n    int flags = 0;\n\n    // Bounding requires initializing the zstream, so initialize first\n    // using the correct compression level.\n#ifdef ZIP_WRITE_DEFLATE_FAST\n    if(method == ZIP_M_DEFLATE)\n      flags |= ZIP_F_DEFLATE_FAST;\n#endif\n\n    zp->stream->compress_open(zp->stream_data, method, flags);\n    result = zp->stream->compress_bound(zp->stream_data, src_len, &bound_len);\n    if(result)\n      return result;\n\n    if(bound_len >= 0xfffffffful)\n      zp->zip64_current = true;\n  }\n  return ZIP_SUCCESS;\n}\n\n/**\n * Open a file writing stream to write the next file in the zip archive.\n * If Zip64 is enabled, this file will always be written with Zip64 data.\n */\nenum zip_error zip_write_open_file_stream(struct zip_archive *zp,\n const char *name, int method)\n{\n  enum zip_error result;\n\n  zp->zip64_current = zp->zip64_enabled;\n  result = zip_write_open_stream(zp, name, method, ZIP_S_WRITE_STREAM);\n  if(result)\n    goto err_out;\n\n  return ZIP_SUCCESS;\n\nerr_out:\n  zip_error(\"zip_write_open_file_stream\", result);\n  return result;\n}\n\n/**\n * Like zip_write_open_file_stream, but allows the direct writing of\n * uncompressed files to memory (deflate is not supported by this method).\n * This is abusable, but quicker than the alternatives. The exact filesize\n * of the file to be written must be provided to this function.\n *\n * ZIP_NOT_MEMORY_ARCHIVE will be silently returned if the current archive\n * doesn't support this; use regular functions instead in this case.\n */\nenum zip_error zip_write_open_mem_stream(struct zip_archive *zp,\n struct memfile *mf, const char *name, size_t length)\n{\n  enum zip_error result = zip_write_autodetect_zip64(zp, ZIP_M_NONE,\n   ZIP_S_WRITE_MEMSTREAM, length);\n  if(result)\n    goto err_out;\n\n  result = zip_write_open_stream(zp, name, ZIP_M_NONE, ZIP_S_WRITE_MEMSTREAM);\n  if(result)\n    goto err_out;\n\n  if(!vfile_get_memfile_block(zp->vf, length, mf))\n  {\n    result = ZIP_EOF;\n    goto err_out;\n  }\n  return ZIP_SUCCESS;\n\nerr_out:\n  if(result != ZIP_NOT_MEMORY_ARCHIVE &&\n   result != ZIP_UNSUPPORTED_METHOD_MEMORY_STREAM)\n    zip_error(\"zip_write_open_file_stream\", result);\n  mfopen(NULL, 0, mf);\n  return result;\n}\n\n/**\n * Close a file writing stream.\n */\nenum zip_error zip_write_close_stream(struct zip_archive *zp)\n{\n  struct zip_file_header *fh;\n  enum zip_error result;\n\n  result = (zp ? zp->write_stream_error : ZIP_NULL);\n  if(result)\n    goto err_out;\n\n  if(zp->stream)\n  {\n    struct zip_stream_data *stream_data = zp->stream_data;\n    size_t final_in = 0;\n    size_t final_out = 0;\n    size_t write_len = 0;\n\n    result = zwrite_finish(&write_len, zp);\n    if(result)\n      goto err_out;\n\n    zp->stream_left += write_len;\n    zp->stream->close(stream_data, &final_in, &final_out);\n\n    if(final_in != zp->streaming_file->uncompressed_size)\n      warn(\"uncompressed size mismatch: %zu != %zu (THIS IS AN MZX BUG)\\n\",\n       final_in, (size_t)zp->streaming_file->uncompressed_size);\n    if(final_out != zp->stream_left)\n      warn(\"compressed size mismatch: %zu != %zu (THIS IS AN MZX BUG)\\n\",\n       final_out, (size_t)zp->stream_left);\n  }\n\n  // Add missing fields to the header.\n  fh = zp->streaming_file;\n  fh->crc32 = zp->stream_crc32;\n  fh->compressed_size = zp->stream_left;\n\n  // Write the missing fields to the local header.\n  result = zip_write_data_descriptor(zp, fh);\n  if(result)\n    goto err_out;\n\n  zp->pos++;\n\n  // Clean up the stream\n  zp->mode = ZIP_S_WRITE_FILES;\n  zp->streaming_file = NULL;\n  zp->stream_buffer_end = 0;\n  zp->stream_left = 0;\n  zp->stream_crc32 = 0;\n\n  precalculate_write_errors(zp);\n  return ZIP_SUCCESS;\n\nerr_out:\n  zip_error(\"zip_write_close_stream\", result);\n  return result;\n}\n\n/**\n * Close a memory writing stream. Unlike with reading, this currently needs its\n * own function still (to receive the memfile pointer indicating the write).\n */\nenum zip_error zip_write_close_mem_stream(struct zip_archive *zp,\n struct memfile *mf)\n{\n  unsigned char *start = mf->start;\n  unsigned char *end = mf->current;\n  size_t length = end - start;\n\n  enum zip_error result =\n   !zp ? ZIP_NULL :\n   zp->mode != ZIP_S_WRITE_MEMSTREAM ? ZIP_INVALID_STREAM_WRITE :\n   ZIP_SUCCESS;\n\n  if(result)\n    goto err_out;\n\n  // Compute some things that a regular stream would have here but are missing\n  // from mem streams.\n  zp->stream_crc32 = crc32(0, start, length);\n  zp->stream_left = length;\n  zp->streaming_file->uncompressed_size = length;\n\n  // Increment the program position (since direct writing was used)\n  vfseek(zp->vf, mf->current - mf->start, SEEK_CUR);\n\n  // Now the regular function can be used...\n  zp->write_stream_error = ZIP_SUCCESS;\n  return zip_write_close_stream(zp);\n\nerr_out:\n  zip_error(\"zip_write_close_mem_stream\", result);\n  return result;\n}\n\n/**\n * Write a file to a zip archive.\n * Currently handled methods: ZIP_M_NONE, ZIP_M_DEFLATE\n *\n * If Zip64 is enabled, then Zip64 will be automatically selected for this\n * file based on its total length and its compressed size bound (if applicable).\n */\nenum zip_error zip_write_file(struct zip_archive *zp, const char *name,\n const void *src, size_t srcLen, int method)\n{\n  enum zip_error result;\n\n  // No need to check mode; the functions used here will\n\n  // Attempting to DEFLATE a small file? Store instead...\n  if(srcLen < 256 && method == ZIP_M_DEFLATE)\n    method = ZIP_M_NONE;\n\n  result = zip_write_autodetect_zip64(zp, method, ZIP_S_WRITE_STREAM, srcLen);\n  if(result)\n    goto err_out;\n\n  result = zip_write_open_stream(zp, name, method, ZIP_S_WRITE_STREAM);\n  if(result)\n    goto err_out;\n\n  result = zwrite_direct(src, srcLen, zp);\n  if(result && result != ZIP_EOF)\n    goto err_close;\n\n  result = zip_write_close_stream(zp);\n  if(result)\n    goto err_out;\n\n  return ZIP_SUCCESS;\n\nerr_close:\n  zip_write_close_stream(zp);\n\nerr_out:\n  zip_error(\"zip_write_file\", result);\n  return result;\n}\n\n/**\n * Reads the central directory of a zip archive. This places the archive into\n * file read mode; read files using zip_read_file(). If this fails, the input\n * is probably not actually a zip archive or uses features we don't support.\n */\n\nstruct zip_eocd\n{\n  uint32_t disk_start_of_central_directory;\n  uint32_t disk_eocd;\n  uint64_t records_on_disk;\n  uint64_t records_total;\n  uint64_t size_central_directory;\n  uint64_t offset_central_directory;\n  uint64_t zip64_eocd_offset;\n};\n\nstatic char eocd_sig[] =\n{\n  0x50,\n  0x4b,\n  0x05,\n  0x06\n};\n\nstatic char zip64_eocd_sig[] =\n{\n  0x50,\n  0x4b,\n  0x06,\n  0x06,\n};\n\nstatic char zip64_locator_sig[] =\n{\n  0x50,\n  0x4b,\n  0x06,\n  0x07\n};\n\nstatic enum zip_error zip_find_eocd(struct zip_archive *zp)\n{\n  vfile *vf = zp->vf;\n  int i;\n  int j;\n  int n;\n\n  // Go to the latest position the EOCD might be.\n  if(vfseek(vf, (int64_t)(zp->end_in_file - EOCD_RECORD_LEN), SEEK_SET))\n  {\n    return ZIP_NO_EOCD;\n  }\n\n  // Find the end of central directory signature.\n  i = 0;\n  j = -EOCD_RECORD_LEN;\n  do\n  {\n    n = vfgetc(vf);\n    j++;\n\n    if(n == eocd_sig[i])\n    {\n      i++;\n      if(i == 4)\n        // We've found the EOCD signature\n        break;\n    }\n\n    else\n    {\n      if(n < 0)\n        break;\n\n      i = i ? i + 5 :\n       (n == 0x06 ? 4 :\n        n == 0x05 ? 3 :\n        n == 0x4b ? 2 : 5);\n      j -= i;\n\n      if(vfseek(vf, -i, SEEK_CUR))\n      {\n        i = 0;\n        break;\n      }\n\n      i = 0;\n    }\n  }\n  // Max length of signature + comment.\n  while(j >= -65557);\n\n  if(i < 4)\n  {\n    return ZIP_NO_EOCD;\n  }\n\n  // Found the EOCD record signature                    4\n  return ZIP_SUCCESS;\n}\n\n/**\n * Read the standard 22 byte EOCD record, minus the signature bytes.\n */\nstatic enum zip_error zip_read_eocd(struct zip_archive *zp,\n struct zip_eocd *eocd)\n{\n  char buffer[EOCD_RECORD_LEN - 4];\n  struct memfile mf;\n\n  // Already read the first 4 signature bytes.\n  if(!vfread(buffer, EOCD_RECORD_LEN - 4, 1, zp->vf))\n    return ZIP_READ_ERROR;\n\n  mfopen(buffer, EOCD_RECORD_LEN - 4, &mf);\n\n  // Number of this disk                                2\n  // Disk where central directory starts                2\n  // Number of central directory records on this disk   2\n  // Total number of central directory records          2\n  eocd->disk_eocd                       = mfgetw(&mf);\n  eocd->disk_start_of_central_directory = mfgetw(&mf);\n  eocd->records_on_disk                 = mfgetw(&mf);\n  eocd->records_total                   = mfgetw(&mf);\n\n  // Size of central directory (bytes)                  4\n  eocd->size_central_directory = mfgetud(&mf);\n\n  // Offset to central directory from start of file     4\n  eocd->offset_central_directory = mfgetud(&mf);\n\n  // Comment length (ignore)                            2\n  return ZIP_SUCCESS;\n}\n\n/**\n * Read the Zip64 EOCD record locator from the current position in the\n * stream, if it exists.\n */\nstatic enum zip_error zip_read_zip64_eocd_locator(struct zip_archive *zp,\n struct zip_eocd *eocd)\n{\n  uint8_t buffer[ZIP64_LOCATOR_LEN];\n  uint32_t disk_start_of_zip64_eocd;\n  uint32_t disk_total;\n  struct memfile mf;\n\n  if(!vfread(buffer, ZIP64_LOCATOR_LEN, 1, zp->vf))\n    return ZIP_READ_ERROR;\n\n  if(memcmp(buffer, zip64_locator_sig, 4))\n    return ZIP_NO_EOCD_ZIP64;\n\n  mfopen(buffer + 4, ZIP64_LOCATOR_LEN - 4, &mf);\n\n  // Disk where central directory starts                4\n  disk_start_of_zip64_eocd = mfgetud(&mf);\n  // Relative offset of the zip64 EOCD                  8\n  eocd->zip64_eocd_offset = mfgetuq(&mf);\n  // Total number of disks                              4\n  disk_total = mfgetud(&mf);\n\n  if(disk_start_of_zip64_eocd != 0 || disk_total != 1)\n    return ZIP_UNSUPPORTED_MULTIPLE_DISKS;\n\n  // Impossible to support with 64-bit stdio.\n  if(eocd->zip64_eocd_offset > INT64_MAX)\n    return ZIP_INVALID_ZIP64;\n\n  return ZIP_SUCCESS;\n}\n\n/**\n * Read the Zip64 EOCD record. Despite APPNOTE.TXT implying that only the\n * fields dummied in the EOCD should be used, PKZIP 6.0 unconditionally\n * prefers ALL fields from this structure over the old fields when present.\n */\nstatic enum zip_error zip_read_zip64_eocd(struct zip_archive *zp,\n struct zip_eocd *eocd)\n{\n  uint8_t buffer[ZIP64_EOCD_RECORD_LEN];\n  struct memfile mf;\n  uint64_t sz;\n  uint16_t ver;\n\n  if(!vfread(buffer, ZIP64_EOCD_RECORD_LEN, 1, zp->vf))\n    return ZIP_READ_ERROR;\n\n  if(memcmp(buffer, zip64_eocd_sig, 4))\n    return ZIP_NO_EOCD_ZIP64;\n\n  mfopen(buffer + 4, ZIP64_EOCD_RECORD_LEN - 4, &mf);\n  // Size of Zip64 EOCD record                          8\n  sz = mfgetuq(&mf);\n  if(sz < ZIP64_EOCD_RECORD_LEN - 12)\n    return ZIP_INVALID_ZIP64;\n\n  // Version made by                                    2\n  // Version needed to extract                          2\n  mf.current += 2;\n  ver = mfgetw(&mf);\n  if(ZIP_VERSION(ver) != ZIP64_VERSION_MINIMUM)\n    return ZIP_UNSUPPORTED_VERSION;\n\n  // Number of this disk                                4\n  eocd->disk_eocd = mfgetud(&mf);\n\n  // Disk where central directory starts                4\n  eocd->disk_start_of_central_directory = mfgetud(&mf);\n\n  // Central directory entries on this disk             8\n  // Total number of central directory entries          8\n  eocd->records_on_disk = mfgetuq(&mf);\n  eocd->records_total = mfgetuq(&mf);\n\n  // Size of the central directory                      8\n  eocd->size_central_directory = mfgetuq(&mf);\n\n  // Offset of the central directory                    8\n  eocd->offset_central_directory = mfgetuq(&mf);\n\n  // Zip64 extensible data sector (ignore)\n  return ZIP_SUCCESS;\n}\n\n/**\n * Read the EOCD record and central directory of the zip to memory.\n * This is required before the zip can be properly read.\n */\nstatic enum zip_error zip_read_directory(struct zip_archive *zp)\n{\n  struct zip_eocd eocd;\n  int64_t eocd_pos;\n  size_t i, j;\n  int result;\n\n  result = zip_find_eocd(zp);\n  if(result)\n    goto err_out;\n\n  eocd_pos = vftell(zp->vf) - 4;\n  if(eocd_pos < 0)\n  {\n    result = ZIP_SEEK_ERROR;\n    goto err_out;\n  }\n\n  // Fix bogus old GCC warnings\n  memset(&eocd, 0, sizeof(eocd));\n\n  result = zip_read_eocd(zp, &eocd);\n  if(result != ZIP_SUCCESS)\n    goto err_out;\n\n  // Check for zip64 locator.\n  if(eocd.records_total && eocd_pos >= ZIP64_LOCATOR_LEN)\n  {\n    if(vfseek(zp->vf, eocd_pos - ZIP64_LOCATOR_LEN, SEEK_SET))\n    {\n      result = ZIP_SEEK_ERROR;\n      goto err_out;\n    }\n\n    result = zip_read_zip64_eocd_locator(zp, &eocd);\n    if(result == ZIP_SUCCESS && (uint64_t)eocd_pos >= eocd.zip64_eocd_offset)\n    {\n      if(vfseek(zp->vf, eocd.zip64_eocd_offset, SEEK_SET))\n      {\n        result = ZIP_SEEK_ERROR;\n        goto err_out;\n      }\n\n      result = zip_read_zip64_eocd(zp, &eocd);\n      if(result != ZIP_SUCCESS)\n        goto err_out;\n    }\n    else\n\n    if(result != ZIP_NO_EOCD_ZIP64)\n      goto err_out;\n  }\n\n  // Test for unsupported features like multiple disks.\n  if(eocd.disk_eocd != 0 || eocd.disk_start_of_central_directory != 0 ||\n   eocd.records_on_disk != eocd.records_total)\n  {\n    result = ZIP_UNSUPPORTED_MULTIPLE_DISKS;\n    goto err_out;\n  }\n\n  if(eocd.records_total >= ZIP_MAX_NUM_FILES)\n  {\n    result = ZIP_UNSUPPORTED_NUMBER_OF_ENTRIES;\n    goto err_out;\n  }\n\n  if((uint64_t)eocd_pos < eocd.offset_central_directory)\n  {\n    result = ZIP_NO_CENTRAL_DIRECTORY;\n    goto err_out;\n  }\n\n  zp->num_files = eocd.records_total;\n  zp->size_central_directory = eocd.size_central_directory;\n  zp->offset_central_directory = eocd.offset_central_directory;\n\n  // Load central directory records\n  if(zp->num_files)\n  {\n    size_t n = zp->num_files;\n    struct zip_file_header **f =\n     (struct zip_file_header **)calloc(n, sizeof(struct zip_file_header *));\n    zp->files = f;\n    if(!f)\n    {\n      result = ZIP_ALLOC_ERROR;\n      goto err_out;\n    }\n\n    // Go to the start of the central directory.\n    if(vfseek(zp->vf, zp->offset_central_directory, SEEK_SET))\n    {\n      result = ZIP_SEEK_ERROR;\n      goto err_realloc;\n    }\n\n    for(i = 0, j = 0; i < n; i++)\n    {\n      result = zip_read_central_file_header(zp, &(f[j]));\n      if(result)\n      {\n        zip_free_file_header(f[j]);\n        f[j] = NULL;\n        if(result == ZIP_IGNORE_FILE || result == ZIP_UNSUPPORTED_VERSION)\n        {\n          zp->num_files--;\n          continue;\n        }\n\n        zip_error(\"error reading central directory record\", result);\n        break;\n      }\n      zp->files_alloc++;\n      j++;\n    }\n\n    if(zp->files_alloc == 0)\n    {\n      result = ZIP_NO_CENTRAL_DIRECTORY;\n      goto err_realloc;\n    }\n\n    if(zp->files_alloc < zp->num_files)\n    {\n      warn(\"expected %zu central directory records but only found %zu\\n\",\n       n, zp->files_alloc);\n      result = ZIP_INCOMPLETE_CENTRAL_DIRECTORY;\n      goto err_realloc;\n    }\n\n    // Shrink file header array.\n    if(zp->files_alloc < n)\n    {\n      f = (struct zip_file_header **)realloc(zp->files,\n       zp->files_alloc * sizeof(struct zip_file_header *));\n      if(f)\n        zp->files = f;\n    }\n  }\n\n  // We're in file read mode now.\n  zp->mode = ZIP_S_READ_FILES;\n  precalculate_read_errors(zp);\n\n  // At this point, we're probably at the EOCD. Reading files will seek\n  // to the start of their respective entries, so just leave it alone.\n  return ZIP_SUCCESS;\n\nerr_realloc:\n  zp->num_files = zp->files_alloc;\n\n  // Shrink file header array.\n  if(zp->files_alloc)\n  {\n    struct zip_file_header **f = (struct zip_file_header **)realloc(zp->files,\n     zp->files_alloc * sizeof(struct zip_file_header *));\n    if(f)\n      zp->files = f;\n  }\n  else\n  {\n    free(zp->files);\n    zp->files = NULL;\n  }\n\n  // We're in file read mode now.\n  zp->mode = ZIP_S_READ_FILES;\n  precalculate_read_errors(zp);\n\nerr_out:\n  zip_error(\"zip_read_directory\", result);\n  return result;\n}\n\n/**\n * Write the EOCD during the archive close.\n */\nstatic enum zip_error zip_write_eocd_record(struct zip_archive *zp)\n{\n  char buffer[EOCD_RECORD_LEN];\n  struct memfile mf;\n  uint32_t size;\n  uint32_t offset;\n  uint16_t num_files;\n  int i;\n\n  // Memfiles: make sure there's enough space\n  if(zp->is_memory && zip_ensure_capacity(EOCD_RECORD_LEN, zp))\n    return ZIP_EOF;\n\n  // Clamp these values for Zip64.\n  num_files = MIN(zp->num_files, 0xffff);\n  size = MIN(zp->size_central_directory, 0xfffffffful);\n  offset = MIN(zp->offset_central_directory, 0xfffffffful);\n\n  mfopen_wr(buffer, EOCD_RECORD_LEN, &mf);\n\n  // Signature                                          4\n  for(i = 0; i<4; i++)\n    mfputc(eocd_sig[i], &mf);\n\n  // Number of this disk                                2\n  mfputw(0, &mf);\n\n  // Disk where central directory starts                2\n  mfputw(0, &mf);\n\n  // Number of central directory records on this disk   2\n  mfputw(num_files, &mf);\n\n  // Total number of central directory records          2\n  mfputw(num_files, &mf);\n\n  // Size of central directory                          4\n  mfputud(size, &mf);\n\n  // Offset of central directory                        4\n  mfputud(offset, &mf);\n\n  // Comment length                                     2\n  mfputw(0, &mf);\n\n  // Comment (length is zero)\n\n  if(!vfwrite(buffer, EOCD_RECORD_LEN, 1, zp->vf))\n    return ZIP_WRITE_ERROR;\n\n  return ZIP_SUCCESS;\n}\n\n/**\n * Test if a Zip64 EOCD needs to be emitted.\n * An archive can use Zip64 extra data features without needing a Zip64 EOCD.\n */\nstatic boolean zip_write_zip64_is_required(struct zip_archive *zp)\n{\n  if(zp->num_files >= 0xffff)\n    return true;\n  if(zp->size_central_directory >= 0xfffffffful)\n    return true;\n  if(zp->offset_central_directory >= 0xfffffffful)\n    return true;\n\n  return false;\n}\n\n/**\n * Write the Zip64 EOCD and locator during the archive close.\n */\nstatic enum zip_error zip_write_zip64_eocd_record(struct zip_archive *zp)\n{\n  char buffer[ZIP64_EOCD_RECORD_LEN + ZIP64_LOCATOR_LEN];\n  struct memfile mf;\n  int64_t zip64_eocd_offset = vftell(zp->vf);\n\n  if(zp->is_memory && zip_ensure_capacity(sizeof(buffer), zp))\n    return ZIP_EOF;\n\n  if(zip64_eocd_offset < 0)\n    return ZIP_SEEK_ERROR;\n\n  mfopen_wr(buffer, sizeof(buffer), &mf);\n\n  // Signature                                          4\n  mfwrite(zip64_eocd_sig, 4, 1, &mf);\n\n  // Size of Zip64 end of central directory record      8\n  mfputuq(ZIP64_EOCD_RECORD_LEN - 12, &mf);\n\n  // Version made by                                    2\n  // Version needed to extract                          2\n  mfputw(ZIP64_VERSION_MINIMUM, &mf);\n  mfputw(ZIP64_VERSION_MINIMUM, &mf);\n\n  // Number of this disk                                4\n  // Disk where central directory starts                4\n  mfputud(0, &mf);\n  mfputud(0, &mf);\n\n  // Number of central directory records on this disk   8\n  // Total number of central directory records          8\n  mfputuq(zp->num_files, &mf);\n  mfputuq(zp->num_files, &mf);\n\n  // Size of the central directory                      8\n  // Offset of start of central directory               8\n  mfputuq(zp->size_central_directory, &mf);\n  mfputuq(zp->offset_central_directory, &mf);\n\n  // Extensible data sector                             0\n\n  // Signature                                          4\n  mfwrite(zip64_locator_sig, 4, 1, &mf);\n\n  // Disk where the Zip64 EOCD is located               4\n  mfputud(0, &mf);\n\n  // Offset of the Zip64 EOCD record                    8\n  mfputq(zip64_eocd_offset, &mf);\n\n  // Total number of disks                              4\n  mfputud(1, &mf);\n\n  assert(mftell(&mf) == sizeof(buffer));\n  if(!vfwrite(buffer, sizeof(buffer), 1, zp->vf))\n    return ZIP_WRITE_ERROR;\n\n  return ZIP_SUCCESS;\n}\n\n/**\n * Attempts to close the zip archive, and when writing, constructs the central\n * directory and EOCD record. Upon returning ZIP_SUCCESS, *final_length will be\n * set to the final length of the file.\n */\nenum zip_error zip_close(struct zip_archive *zp, uint64_t *final_length)\n{\n  int result = ZIP_SUCCESS;\n  int mode;\n  size_t i;\n\n  if(!zp)\n    return ZIP_NULL;\n\n  // On the off chance someone actually tries this...\n  if(zp->is_memory && final_length &&\n   (void *)final_length == (void *)zp->external_buffer_size)\n  {\n    warn(\n      \"zip_close: Detected use of external buffer size pointer as final_length \"\n      \"(should provide NULL instead). Report this!\\n\"\n    );\n    final_length = NULL;\n  }\n\n  mode = zp->mode;\n\n  // Before initiating the close, make sure there wasn't an open write stream!\n  if(zp->streaming_file && mode == ZIP_S_WRITE_STREAM)\n  {\n    warn(\"zip_close called while writing file stream!\\n\");\n    zip_write_close_stream(zp);\n    mode = ZIP_S_WRITE_FILES;\n  }\n\n  if(mode == ZIP_S_WRITE_FILES)\n  {\n    uint64_t size_cd = zp->running_file_name_length;\n    size_t size_eocd = EOCD_RECORD_LEN;\n\n    for(i = 0; i < zp->num_files; i++)\n    {\n      struct zip_file_header *fh = zp->files[i];\n      boolean is_zip64 = false;\n      if(fh->uncompressed_size >= 0xfffffffful)\n      {\n        is_zip64 = true;\n        size_cd += 8;\n      }\n      if(fh->compressed_size >= 0xfffffffful)\n      {\n        is_zip64 = true;\n        size_cd += 8;\n      }\n      if(fh->offset >= 0xfffffffful)\n      {\n        is_zip64 = true;\n        size_cd += 8;\n      }\n      size_cd += CENTRAL_FILE_HEADER_LEN + (is_zip64 ? 4 : 0);\n    }\n\n    zp->offset_central_directory = vftell(zp->vf);\n    zp->size_central_directory = size_cd;\n\n    if(zip_write_zip64_is_required(zp))\n      size_eocd += ZIP64_EOCD_RECORD_LEN + ZIP64_LOCATOR_LEN;\n\n    // Calculate projected file size in case more space is needed\n    if(final_length)\n      *final_length = zp->offset_central_directory + size_cd + size_eocd;\n\n    // Ensure there's enough space to close the file\n    if(zp->is_memory && zip_ensure_capacity(size_cd + size_eocd, zp))\n    {\n      result = ZIP_EOF;\n      mode = ZIP_S_ERROR;\n    }\n  }\n\n  zp->pos = 0;\n  for(i = zp->pos; i < zp->num_files; i++)\n  {\n    struct zip_file_header *fh = zp->files[i];\n\n    if(fh)\n    {\n      // Write the central directory\n      if(mode == ZIP_S_WRITE_FILES)\n      {\n        result = zip_write_file_header(zp, fh, true);\n        if(result)\n        {\n          // Not much that can be done at this point.\n          mode = ZIP_S_ERROR;\n        }\n      }\n      zip_free_file_header(fh);\n    }\n  }\n\n  // Write the end of central directory record\n  if(mode == ZIP_S_WRITE_FILES || mode == ZIP_S_WRITE_UNINITIALIZED)\n  {\n    size_t end_pos;\n    uint64_t actual_size_cd = vftell(zp->vf) - zp->offset_central_directory;\n    if(mode == ZIP_S_WRITE_FILES)\n      assert(actual_size_cd == zp->size_central_directory);\n    zp->size_central_directory = actual_size_cd;\n\n    if(zip_write_zip64_is_required(zp))\n      zip_write_zip64_eocd_record(zp);\n\n    result = zip_write_eocd_record(zp);\n    end_pos = vftell(zp->vf);\n\n    if(final_length)\n      *final_length = end_pos;\n\n    // Just to be sure, close the vfile before attempting any changes to the\n    // external buffer if this is an expandable memory ZIP.\n    vfclose(zp->vf);\n\n    // Reduce the size of the buffer if this is an expandable memory zip.\n    if(zp->is_memory && zp->external_buffer && zp->external_buffer_size &&\n     *(zp->external_buffer_size) > end_pos)\n    {\n      void *tmp = realloc(*(zp->external_buffer), end_pos);\n      if(tmp)\n      {\n        *(zp->external_buffer) = tmp;\n        *(zp->external_buffer_size) = end_pos;\n      }\n    }\n  }\n  else\n  {\n    vfclose(zp->vf);\n    if(final_length)\n      *final_length = zp->end_in_file;\n  }\n\n  for(i = 0; i < ARRAY_SIZE(zp->stream_data_ptrs); i++)\n    if(zip_method_handlers[i] && zp->stream_data_ptrs[i])\n      zip_method_handlers[i]->destroy(zp->stream_data_ptrs[i]);\n\n  free(zp->header_buffer);\n  free(zp->stream_buffer);\n  free(zp->files);\n  free(zp);\n\n  if(result != ZIP_SUCCESS)\n    zip_error(\"zip_close\", result);\n\n  return result;\n}\n\n/**\n * Perform additional setup for write mode.\n */\nstatic boolean zip_init_for_write(struct zip_archive *zp, int num_files)\n{\n  zp->files_alloc = num_files;\n  zp->files = (struct zip_file_header **)malloc(num_files *\n   sizeof(struct zip_file_header *));\n\n  if(!zp->files)\n    return false;\n\n  zp->header_buffer = (uint8_t *)malloc(ZIP_DEFAULT_HEADER_BUFFER);\n  zp->header_buffer_alloc = ZIP_DEFAULT_HEADER_BUFFER;\n  zp->header_timestamp = zip_get_dos_date_time();\n  zp->running_file_name_length = 0;\n  zp->zip64_enabled = true;\n\n  if(!zp->header_buffer)\n  {\n    free(zp->files);\n    return false;\n  }\n\n  zp->mode = ZIP_S_WRITE_UNINITIALIZED;\n  return true;\n}\n\n/**\n * Set up a new zip archive struct.\n */\nstatic struct zip_archive *zip_new_archive(void)\n{\n  struct zip_archive *zp =\n   (struct zip_archive *)calloc(1, sizeof(struct zip_archive));\n\n  zp->mode = ZIP_S_READ_UNINITIALIZED;\n  return zp;\n}\n\n/**\n * Open a zip archive located in a file for reading. Returns a zip_archive\n * pointer if this archive is ready for file reading; otherwise, returns\n * NULL. On failure, this will also close the file pointer.\n */\nstruct zip_archive *zip_open_vf_read(vfile *vf)\n{\n  if(vf)\n  {\n    struct zip_archive *zp = zip_new_archive();\n    int64_t file_len;\n\n    if(!zp)\n      return NULL;\n\n    zp->vf = vf;\n    file_len = vfilelength(zp->vf, false);\n\n    if(file_len < 0)\n    {\n      zip_error(\"zip_open_vf_read\", ZIP_STAT_ERROR);\n      vfclose(vf);\n      free(zp);\n      return NULL;\n    }\n\n    zp->end_in_file = file_len;\n\n    if(ZIP_SUCCESS != zip_read_directory(zp))\n    {\n      zip_close(zp, NULL);\n      return NULL;\n    }\n\n    precalculate_read_errors(zp);\n    precalculate_write_errors(zp);\n    return zp;\n  }\n  return NULL;\n}\n\nstruct zip_archive *zip_open_file_read(const char *file_name)\n{\n  vfile *vf = vfopen_unsafe(file_name, \"rb\");\n\n  return zip_open_vf_read(vf);\n}\n\n/**\n * Open a zip archive located in a file for writing. The archive will be in\n * raw write mode, for use with zip_write(), until zip_write_file() is called.\n * Afterward, the archive will be in file write mode.\n */\nstruct zip_archive *zip_open_vf_write(vfile *vf)\n{\n  if(vf)\n  {\n    struct zip_archive *zp = zip_new_archive();\n    if(!zp)\n      return NULL;\n\n    zp->vf = vf;\n\n    if(!zip_init_for_write(zp, ZIP_DEFAULT_NUM_FILES))\n    {\n      free(zp);\n      return NULL;\n    }\n\n    precalculate_read_errors(zp);\n    precalculate_write_errors(zp);\n    return zp;\n  }\n  return NULL;\n}\n\nstruct zip_archive *zip_open_file_write(const char *file_name)\n{\n  vfile *vf = vfopen_unsafe(file_name, \"wb\");\n\n  return zip_open_vf_write(vf);\n}\n\n/**\n * Open a zip archive located in memory for reading. Returns a zip_archive\n * pointer if this archive is ready for file reading; otherwise, returns\n * NULL.\n */\nstruct zip_archive *zip_open_mem_read(const void *src, size_t len)\n{\n  if(src && len > 0)\n  {\n    struct zip_archive *zp = zip_new_archive();\n    if(!zp)\n      return NULL;\n\n    zp->vf = vfile_init_mem((void *)src, len, \"rb\");\n    zp->is_memory = true;\n    zp->end_in_file = len;\n\n    if(ZIP_SUCCESS != zip_read_directory(zp))\n    {\n      zip_close(zp, NULL);\n      return NULL;\n    }\n\n    precalculate_read_errors(zp);\n    precalculate_write_errors(zp);\n    return zp;\n  }\n  return NULL;\n}\n\n/**\n * Open a zip archive for writing to a block of memory. Returns a zip_archive\n * upon success; otherwise, returns NULL. An optional offset can be specified\n * indicated the position in the file to begin writing ZIP data.\n */\nstruct zip_archive *zip_open_mem_write(void *src, size_t len, size_t start_pos)\n{\n  if(src && len > 0 && start_pos < len)\n  {\n    struct zip_archive *zp = zip_new_archive();\n    if(!zp)\n      return NULL;\n\n    zp->vf = vfile_init_mem(src, len, \"wb\");\n    zp->is_memory = true;\n    if(!zp->vf)\n    {\n      free(zp);\n      return NULL;\n    }\n\n    if(!zip_init_for_write(zp, ZIP_DEFAULT_NUM_FILES))\n    {\n      vfclose(zp->vf);\n      free(zp);\n      return NULL;\n    }\n    vfseek(zp->vf, start_pos, SEEK_SET);\n\n    precalculate_read_errors(zp);\n    precalculate_write_errors(zp);\n    return zp;\n  }\n  return NULL;\n}\n\n/**\n * Open a zip archive for writing to a block of memory. See the above function.\n *\n * The locations described by external_buffer and external_buffer_size must be\n * initialized before this function is called. If this archive exceeds the size\n * of its buffer, it will automatically attempt to reallocate the buffer.\n */\nstruct zip_archive *zip_open_mem_write_ext(void **external_buffer,\n size_t *external_buffer_size, size_t start_pos)\n{\n  if(external_buffer && external_buffer_size)\n  {\n    struct zip_archive *zp = zip_new_archive();\n    if(!zp)\n      return NULL;\n\n    // Allow expansion to be managed by vio.c. The external buffer pointers are\n    // stored here as well, so zip_close can shrink the buffer.\n    zp->vf = vfile_init_mem_ext(external_buffer, external_buffer_size, \"wb\");\n    zp->external_buffer = external_buffer;\n    zp->external_buffer_size = external_buffer_size;\n    zp->is_memory = true;\n\n    if(!zp->vf)\n    {\n      free(zp);\n      return NULL;\n    }\n\n    if(!zip_init_for_write(zp, ZIP_DEFAULT_NUM_FILES))\n    {\n      vfclose(zp->vf);\n      free(zp);\n      return NULL;\n    }\n    vfseek(zp->vf, start_pos, SEEK_SET);\n\n    precalculate_read_errors(zp);\n    precalculate_write_errors(zp);\n    return zp;\n  }\n  return NULL;\n}\n"
  },
  {
    "path": "src/io/zip.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __ZIP_H\n#define __ZIP_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stdint.h>\n\n// This needs to stay self-sufficient.\n// Don't use core features, don't extern anything.\n\n#include \"vfile.h\"\n\n/**\n * Methods universally supported by MZX are 0 (store) and 8 (Deflate).\n * Emscripten and utils builds also enable decompressors for 1 (Shrink),\n * 2-5 (Reduce), 6 (Implode), and 9 (Deflate64).\n */\nenum zip_compression_method\n{\n  ZIP_M_NONE                    = 0, // Store\n  ZIP_M_SHRUNK                  = 1,\n  ZIP_M_REDUCED_1               = 2, // Reduced with compression factor 1\n  ZIP_M_REDUCED_2               = 3, // Reduced with compression factor 2\n  ZIP_M_REDUCED_3               = 4, // Reduced with compression factor 3\n  ZIP_M_REDUCED_4               = 5, // Reduced with compression factor 4\n  ZIP_M_IMPLODED                = 6,\n  ZIP_M_DEFLATE                 = 8,\n  ZIP_M_DEFLATE64               = 9,\n  ZIP_M_DCL_IMPLODING           = 10, // Old IBM TERSE\n  ZIP_M_BZIP2                   = 12,\n  ZIP_M_LZMA                    = 14,\n  ZIP_M_IBM_TERSE               = 18,\n  ZIP_M_LZ77                    = 19,\n  ZIP_M_WAVPACK                 = 97,\n  ZIP_M_PPMD                    = 98,\n};\n\n#define ZIP_M_MAX_SUPPORTED ZIP_M_DEFLATE64\n\nenum zip_general_purpose_flag\n{\n  ZIP_F_ENCRYPTED           = (1<<0), // Indicates that a file is encrypted.\n  ZIP_F_COMPRESSION_1       = (1<<1), // Varies.\n  ZIP_F_COMPRESSION_2       = (1<<2), // Varies.\n  ZIP_F_DATA_DESCRIPTOR     = (1<<3), // Sizes and CRC32 stored after file data.\n  ZIP_F_ENHANCED_DEFLATE    = (1<<4), // Reserved for Deflate64.\n  ZIP_F_COMPRESSED_PATCHED  = (1<<5), // ?????\n  ZIP_F_STRONG_ENCRYPTION   = (1<<6), // Indicates strong encryption is used.\n  ZIP_F_UNUSED_7            = (1<<7),\n  ZIP_F_UNUSED_8            = (1<<8),\n  ZIP_F_UNUSED_9            = (1<<9),\n  ZIP_F_UNUSED_10           = (1<<10),\n  ZIP_F_LANGUAGE_ENCODING   = (1<<11), // Indicates UTF-8 filename/comments.\n  ZIP_F_UNUSED_12           = (1<<12),\n  ZIP_F_MASKED_HEADER_DATA  = (1<<13), // See: strong encryption.\n  ZIP_F_UNUSED_14           = (1<<14),\n  ZIP_F_UNUSED_15           = (1<<15)\n};\n\n// These flags are allowed for all DEFLATE and stored files we care about.\n#define ZIP_F_ALLOWED     (\\\n  ZIP_F_DATA_DESCRIPTOR   |\\\n  ZIP_F_COMPRESSION_1     |\\\n  ZIP_F_COMPRESSION_2     |\\\n  ZIP_F_LANGUAGE_ENCODING )\n\n// Some ancient archives from unknown sources seem to have set the unused\n// flags in places. These should be safe to ignore.\n#define ZIP_F_UNUSED (ZIP_F_UNUSED_7 | ZIP_F_UNUSED_8 | ZIP_F_UNUSED_9 |\\\n ZIP_F_UNUSED_10 | ZIP_F_UNUSED_12 | ZIP_F_UNUSED_14 | ZIP_F_UNUSED_15)\n\n// DEFLATE-specific compression flags.\n#define ZIP_F_DEFLATE_MAXIMUM   ZIP_F_COMPRESSION_1\n#define ZIP_F_DEFLATE_FAST      ZIP_F_COMPRESSION_2\n\nenum zip_internal_state\n{\n  ZIP_S_READ_UNINITIALIZED,\n  ZIP_S_READ_FILES,\n  ZIP_S_READ_STREAM,\n  ZIP_S_READ_MEMSTREAM,\n  ZIP_S_WRITE_UNINITIALIZED,\n  ZIP_S_WRITE_FILES,\n  ZIP_S_WRITE_STREAM,\n  ZIP_S_WRITE_MEMSTREAM,\n  ZIP_S_ERROR,\n};\n\nenum zip_error\n{\n  ZIP_SUCCESS = 0,\n  ZIP_IGNORE_FILE,\n  ZIP_EOF,\n  ZIP_NULL,\n  ZIP_NULL_BUF,\n  ZIP_ALLOC_ERROR,\n  ZIP_STAT_ERROR,\n  ZIP_SEEK_ERROR,\n  ZIP_READ_ERROR,\n  ZIP_WRITE_ERROR,\n  ZIP_BOUND_ERROR,\n  ZIP_INVALID_READ_IN_WRITE_MODE,\n  ZIP_INVALID_WRITE_IN_READ_MODE,\n  ZIP_INVALID_FILE_READ_UNINITIALIZED,\n  ZIP_INVALID_FILE_READ_IN_STREAM_MODE,\n  ZIP_INVALID_FILE_WRITE_IN_STREAM_MODE,\n  ZIP_INVALID_STREAM_READ,\n  ZIP_INVALID_STREAM_WRITE,\n  ZIP_NOT_MEMORY_ARCHIVE,\n  ZIP_NO_EOCD,\n  ZIP_NO_EOCD_ZIP64,\n  ZIP_NO_CENTRAL_DIRECTORY,\n  ZIP_INCOMPLETE_CENTRAL_DIRECTORY,\n  ZIP_UNSUPPORTED_VERSION,\n  ZIP_UNSUPPORTED_NUMBER_OF_ENTRIES,\n  ZIP_UNSUPPORTED_MULTIPLE_DISKS,\n  ZIP_UNSUPPORTED_FLAGS,\n  ZIP_UNSUPPORTED_DECOMPRESSION,\n  ZIP_UNSUPPORTED_COMPRESSION,\n  ZIP_UNSUPPORTED_DECOMPRESSION_STREAM,\n  ZIP_UNSUPPORTED_COMPRESSION_STREAM,\n  ZIP_UNSUPPORTED_METHOD_MEMORY_STREAM,\n  ZIP_NO_ZIP64_EXTRA_DATA,\n  ZIP_INVALID_ZIP64,\n  ZIP_MISSING_LOCAL_HEADER,\n  ZIP_HEADER_MISMATCH,\n  ZIP_CRC32_MISMATCH,\n  ZIP_DECOMPRESS_FAILED,\n  ZIP_COMPRESS_FAILED,\n  ZIP_INPUT_EMPTY,\n  ZIP_OUTPUT_FULL,\n  ZIP_STREAM_FINISHED,\n};\n\nstruct zip_file_header\n{\n  uint16_t flags;\n  uint16_t method;\n  uint32_t crc32;\n  uint64_t compressed_size;\n  uint64_t uncompressed_size;\n  uint64_t offset;\n  uint32_t mzx_file_id;\n  uint8_t mzx_board_id;\n  uint8_t mzx_robot_id;\n  uint16_t file_name_length;\n  // This struct is allocated with an extended area for the filename.\n  // This field MUST be at least 4-aligned and it must be the last field in the\n  // struct (any extra padding after will be used).\n  char file_name[1];\n};\n\n/**\n * Data for an active ZIP (de)compression stream.\n */\nstruct zip_stream_data\n{\n  // Current position and remaining size of the input buffer.\n  const uint8_t *input_buffer;\n  size_t input_length;\n\n  // Current position and remaining size of the output buffer.\n  uint8_t *output_buffer;\n  size_t output_length;\n\n  // Final input and output size. zlib only supports up to 32-bit\n  // sizes on 32-bit systems and the legacy methods shouldn't be big\n  // enough to matter, so size_t is fine here for now.\n  size_t final_input_length;\n  size_t final_output_length;\n\n  // Has this stream been initialized?\n  boolean is_initialized;\n\n  // Is this stream finished?\n  boolean finished;\n\n  // Is this a compression stream? (false for a decompression stream)\n  boolean is_compression_stream;\n};\n\nstruct zip_method_handler;\n\nstruct zip_archive\n{\n  uint8_t mode;\n\n  size_t pos;\n  size_t num_files; // ZIP supports 2^64-1 files, but this needs to fit in RAM.\n  size_t files_alloc;\n  uint64_t size_central_directory;\n  uint64_t offset_central_directory;\n\n  uint8_t *header_buffer;\n  uint32_t header_buffer_alloc;\n  uint32_t header_timestamp;\n  uint32_t running_file_name_length;\n\n  struct zip_file_header **files;\n  struct zip_file_header *streaming_file;\n  uint8_t *stream_buffer;\n  uint32_t stream_buffer_pos;\n  uint32_t stream_buffer_end;\n  uint32_t stream_buffer_alloc;\n  uint64_t stream_crc_position;\n  uint64_t stream_zip64_position;\n  uint64_t stream_u_left;\n  uint64_t stream_left;\n  uint32_t stream_crc32;\n\n  uint32_t start_in_file;\n  uint64_t end_in_file;\n\n  enum zip_error read_file_error;\n  enum zip_error read_stream_error;\n  enum zip_error write_file_error;\n  enum zip_error write_stream_error;\n\n  vfile *vf;\n\n  boolean is_memory;\n  boolean zip64_enabled; // Zip64 should be used as-needed.\n  boolean zip64_current; // Zip64 is active for current file.\n  void **external_buffer;\n  size_t *external_buffer_size;\n\n  const struct zip_method_handler *stream;\n  struct zip_stream_data *stream_data;\n  struct zip_stream_data *stream_data_ptrs[ZIP_M_MAX_SUPPORTED + 1];\n};\n\nUTILS_LIBSPEC int zip_bound_deflate_usage(size_t length);\nUTILS_LIBSPEC int zip_bound_total_header_usage(int num_files, int max_name_size);\n\nUTILS_LIBSPEC enum zip_error zread(void *destBuf, size_t readLen,\n struct zip_archive *zp);\n\nUTILS_LIBSPEC enum zip_error zip_get_next_name(struct zip_archive *zp,\n char *name, int name_buffer_size);\n\nUTILS_LIBSPEC enum zip_error zip_get_next_mzx_file_id(struct zip_archive *zp,\n unsigned int *prop_id, unsigned int *board_id, unsigned int *robot_id);\n\nUTILS_LIBSPEC enum zip_error zip_get_next_method(struct zip_archive *zp,\n unsigned int *method);\n\nUTILS_LIBSPEC enum zip_error zip_get_next_uncompressed_size(\n struct zip_archive *zp, size_t *u_size);\nUTILS_LIBSPEC enum zip_error zip_get_next_uncompressed_size64(\n struct zip_archive *zp, uint64_t *u_size);\n\nUTILS_LIBSPEC enum zip_error zip_read_open_file_stream(struct zip_archive *zp,\n uint64_t *destLen);\n\nUTILS_LIBSPEC enum zip_error zip_read_close_stream(struct zip_archive *zp);\n\nUTILS_LIBSPEC enum zip_error zip_read_open_mem_stream(struct zip_archive *zp,\n struct memfile *mf);\n\nUTILS_LIBSPEC enum zip_error zip_rewind(struct zip_archive *zp);\n\nUTILS_LIBSPEC enum zip_error zip_skip_file(struct zip_archive *zp);\n\nUTILS_LIBSPEC enum zip_error zip_read_file(struct zip_archive *zp,\n void *destBuf, size_t destLen, size_t *readLen);\n\nUTILS_LIBSPEC enum zip_error zwrite(const void *src, size_t srcLen,\n struct zip_archive *zp);\n\nUTILS_LIBSPEC enum zip_error zip_set_zip64_enabled(struct zip_archive *zp,\n boolean enable_zip64);\n\nUTILS_LIBSPEC enum zip_error zip_write_open_file_stream(struct zip_archive *zp,\n const char *name, int method);\n\nUTILS_LIBSPEC enum zip_error zip_write_close_stream(struct zip_archive *zp);\n\nUTILS_LIBSPEC enum zip_error zip_write_open_mem_stream(struct zip_archive *zp,\n struct memfile *mf, const char *name, size_t length);\n\nUTILS_LIBSPEC enum zip_error zip_write_close_mem_stream(struct zip_archive *zp,\n struct memfile *mf);\n\nUTILS_LIBSPEC enum zip_error zip_write_file(struct zip_archive *zp,\n const char *name, const void *src, size_t srcLen, int method);\n\nUTILS_LIBSPEC enum zip_error zip_close(struct zip_archive *zp,\n uint64_t *final_length);\n\nUTILS_LIBSPEC struct zip_archive *zip_open_vf_read(vfile *vf);\nUTILS_LIBSPEC struct zip_archive *zip_open_vf_write(vfile *vf);\nUTILS_LIBSPEC struct zip_archive *zip_open_file_read(const char *file_name);\nUTILS_LIBSPEC struct zip_archive *zip_open_file_write(const char *file_name);\nUTILS_LIBSPEC struct zip_archive *zip_open_mem_read(const void *src, size_t len);\nUTILS_LIBSPEC struct zip_archive *zip_open_mem_write(void *src, size_t len,\n size_t start_pos);\nUTILS_LIBSPEC struct zip_archive *zip_open_mem_write_ext(void **external_buffer,\n size_t *external_buffer_size, size_t start_pos);\n\n__M_END_DECLS\n\n#endif //__ZIP_H\n"
  },
  {
    "path": "src/io/zip_deflate.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Deflate (zip compression method 8) compressor and decompressor via zlib.\n */\n\n#ifndef __ZIP_DEFLATE_H\n#define __ZIP_DEFLATE_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n#include <zlib.h>\n\n#include \"zip.h\"\n\nstruct deflate_stream_data\n{\n  struct zip_stream_data zs;\n  z_stream z;\n  boolean is_inflate;\n  boolean is_deflate;\n  boolean should_finish;\n  uint16_t flags;\n};\n\nstatic inline struct zip_stream_data *deflate_create(void)\n{\n  return (struct zip_stream_data *)calloc(1, sizeof(struct deflate_stream_data));\n}\n\nstatic inline void deflate_destroy(struct zip_stream_data *zs)\n{\n  struct deflate_stream_data *ds = ((struct deflate_stream_data *)zs);\n\n  if(ds->is_inflate)\n    inflateEnd(&(ds->z));\n\n  if(ds->is_deflate)\n    deflateEnd(&(ds->z));\n\n  free(zs);\n}\n\nstatic inline void inflate_open(struct zip_stream_data *zs, uint16_t method,\n uint16_t flags)\n{\n  // Only clear the common portion of the stream data.\n  memset(zs, 0, sizeof(struct zip_stream_data));\n}\n\nstatic inline void deflate_open(struct zip_stream_data *zs, uint16_t method,\n uint16_t flags)\n{\n  struct deflate_stream_data *ds = ((struct deflate_stream_data *)zs);\n  int prev_flags = ds->flags;\n  // Only clear the common portion of the stream data.\n  memset(zs, 0, sizeof(struct zip_stream_data));\n  zs->is_compression_stream = true;\n  ds->should_finish = false;\n  ds->flags = flags & (ZIP_F_DEFLATE_MAXIMUM | ZIP_F_DEFLATE_FAST);\n\n  if(ds->is_deflate && prev_flags != ds->flags)\n  {\n    deflateEnd(&(ds->z));\n    ds->is_deflate = false;\n  }\n}\n\nstatic inline void deflate_close(struct zip_stream_data *zs,\n size_t *final_input_length, size_t *final_output_length)\n{\n  struct deflate_stream_data *ds = ((struct deflate_stream_data *)zs);\n  if(!zs->finished)\n  {\n    // Reset the stream to be used again.\n    if(ds->is_inflate)\n      inflateReset(&(ds->z));\n\n    if(ds->is_deflate)\n      deflateReset(&(ds->z));\n\n    zs->finished = true;\n  }\n\n  if(final_input_length)\n    *final_input_length = zs->final_input_length;\n\n  if(final_output_length)\n    *final_output_length = zs->final_output_length;\n}\n\nstatic inline boolean deflate_input(struct zip_stream_data *zs,\n const void *src, size_t src_len)\n{\n  struct deflate_stream_data *ds = ((struct deflate_stream_data *)zs);\n  ds->z.next_in = (Bytef *)src;\n  ds->z.avail_in = (uInt)src_len;\n  return true;\n}\n\nstatic inline boolean deflate_output(struct zip_stream_data *zs, void *dest,\n size_t dest_len)\n{\n  struct deflate_stream_data *ds = ((struct deflate_stream_data *)zs);\n  ds->z.next_out = (Bytef *)dest;\n  ds->z.avail_out = (uInt)dest_len;\n  return true;\n}\n\nstatic inline enum zip_error inflate_init(struct zip_stream_data *zs)\n{\n  struct deflate_stream_data *ds = ((struct deflate_stream_data *)zs);\n\n  if(zs->is_initialized)\n    return ZIP_SUCCESS;\n\n  if(ds->is_deflate)\n  {\n    deflateEnd(&(ds->z));\n    ds->is_deflate = false;\n  }\n\n  if(ds->z.avail_in)\n  {\n    // This is a raw deflate stream, so use -MAX_WBITS.\n    if(!ds->is_inflate)\n    {\n      inflateInit2(&(ds->z), -MAX_WBITS);\n      ds->is_inflate = true;\n    }\n    zs->is_initialized = true;\n    return ZIP_SUCCESS;\n  }\n  return ZIP_INPUT_EMPTY;\n}\n\nstatic inline enum zip_error inflate_block(struct zip_stream_data *zs)\n{\n  struct deflate_stream_data *ds = ((struct deflate_stream_data *)zs);\n\n  enum zip_error result = inflate_init(zs);\n  int err;\n\n  if(result)\n    return result;\n\n  if(zs->finished)\n    return ZIP_STREAM_FINISHED;\n\n  if(!ds->z.avail_out)\n    return ZIP_OUTPUT_FULL;\n\n  if(!ds->z.avail_in)\n    return ZIP_INPUT_EMPTY;\n\n  err = inflate(&(ds->z), Z_FINISH);\n  if(err == Z_STREAM_END)\n  {\n    zs->final_input_length = ds->z.total_in;\n    zs->final_output_length = ds->z.total_out;\n    zs->finished = true;\n\n    inflateReset(&(ds->z));\n    return ZIP_STREAM_FINISHED;\n  }\n\n  if(err == Z_OK || err == Z_BUF_ERROR)\n  {\n    if(!ds->z.avail_out)\n      return ZIP_OUTPUT_FULL;\n\n    if(!ds->z.avail_in)\n      return ZIP_INPUT_EMPTY;\n  }\n  return ZIP_DECOMPRESS_FAILED;\n}\n\nstatic inline enum zip_error deflate_init(struct zip_stream_data *zs)\n{\n  struct deflate_stream_data *ds = ((struct deflate_stream_data *)zs);\n\n  if(ds->is_inflate)\n  {\n    inflateEnd(&(ds->z));\n    ds->is_inflate = false;\n  }\n\n  if(!ds->is_deflate)\n  {\n    int level = Z_DEFAULT_COMPRESSION;\n    if(ds->flags & ZIP_F_DEFLATE_MAXIMUM)\n      level = Z_BEST_COMPRESSION;\n    if(ds->flags & ZIP_F_DEFLATE_FAST)\n      level = Z_BEST_SPEED;\n\n    // This is a raw deflate stream, so use -MAX_WBITS.\n    // Note: aside from the compression and windowbits, these are all defaults.\n    deflateInit2(&(ds->z), level, Z_DEFLATED, -MAX_WBITS,\n     8, Z_DEFAULT_STRATEGY);\n    ds->is_deflate = true;\n  }\n  zs->is_initialized = true;\n  return ZIP_SUCCESS;\n}\n\nstatic inline enum zip_error deflate_block(struct zip_stream_data *zs,\n boolean should_finish)\n{\n  struct deflate_stream_data *ds = ((struct deflate_stream_data *)zs);\n\n  enum zip_error result = deflate_init(zs);\n  int flush = Z_NO_FLUSH;\n  int err;\n\n  if(result)\n    return result;\n\n  if(zs->finished)\n    return ZIP_STREAM_FINISHED;\n\n  if(!ds->z.avail_out)\n    return ZIP_OUTPUT_FULL;\n\n  should_finish |= ds->should_finish;\n  if(!should_finish && !ds->z.avail_in)\n    return ZIP_INPUT_EMPTY;\n\n  if(should_finish)\n  {\n    ds->should_finish = true;\n    flush = Z_FINISH;\n  }\n\n  err = deflate(&(ds->z), flush);\n  if(err == Z_STREAM_END)\n  {\n    zs->final_input_length = ds->z.total_in;\n    zs->final_output_length = ds->z.total_out;\n    zs->finished = true;\n\n    deflateReset(&(ds->z));\n    return ZIP_STREAM_FINISHED;\n  }\n\n  if(err == Z_OK || err == Z_BUF_ERROR)\n  {\n    if(!ds->z.avail_out)\n      return ZIP_OUTPUT_FULL;\n\n    if(flush != Z_FINISH && !ds->z.avail_in)\n      return ZIP_INPUT_EMPTY;\n  }\n  return ZIP_COMPRESS_FAILED;\n}\n\nstatic inline enum zip_error deflate_bound(struct zip_stream_data *zs,\n size_t src_len, size_t *bound_len)\n{\n  struct deflate_stream_data *ds = (struct deflate_stream_data *)zs;\n  enum zip_error result = deflate_init(zs);\n\n  if(!bound_len)\n    return ZIP_NULL;\n  if(result)\n    return result;\n\n  *bound_len = deflateBound(&(ds->z), src_len);\n  return ZIP_SUCCESS;\n}\n\n__M_END_DECLS\n\n#endif /* __ZIP_DEFLATE_H */\n"
  },
  {
    "path": "src/io/zip_deflate64.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __ZIP_DEFLATE64_H\n#define __ZIP_DEFLATE64_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <assert.h>\n#include <stdlib.h>\n#include <zlib.h>\n\n// Not really how you're supposed to do this, oh well... :(\n#include \"../../contrib/infback9/infback9.h\"\n#include \"../../contrib/infback9/infback9.c\"\n#include \"../../contrib/infback9/inffix9.h\"\n#include \"../../contrib/infback9/inflate9.h\"\n#include \"../../contrib/infback9/inftree9.h\"\n#include \"../../contrib/infback9/inftree9.c\"\n\n#include \"zip.h\"\n\nstruct deflate64_stream_data\n{\n  struct zip_stream_data zs;\n  z_stream z;\n  uint8_t window[1 << 16];\n};\n\n/**\n * Allocate an inflate64 stream.\n */\nstatic inline struct zip_stream_data *inflate64_create(void)\n{\n  return (struct zip_stream_data *)malloc(sizeof(struct deflate64_stream_data));\n}\n\nstatic inline void inflate64_open(struct zip_stream_data *zs, uint16_t method,\n uint16_t flags)\n{\n  struct deflate64_stream_data *d64s = ((struct deflate64_stream_data *)zs);\n  memset(zs, 0, sizeof(struct zip_stream_data));\n  memset(&(d64s->z), 0, sizeof(z_stream));\n}\n\nstatic inline void inflate64_close(struct zip_stream_data *zs,\n size_t *final_input_length, size_t *final_output_length)\n{\n  if(final_input_length)\n    *final_input_length = zs->final_input_length;\n\n  if(final_output_length)\n    *final_output_length = zs->final_output_length;\n}\n\nstatic inline unsigned inflate64_in_callback(void *data,\n z_const unsigned char **in)\n{\n  struct zip_stream_data *zs = (struct zip_stream_data *)data;\n  size_t length = zs->input_length;\n  *in = (void *)zs->input_buffer;\n  zs->input_buffer = NULL;\n  zs->input_length = 0;\n  zs->final_input_length += length;\n  return length;\n}\n\nstatic inline int inflate64_out_callback(void *data, unsigned char *buffer,\n unsigned buffer_len)\n{\n  struct zip_stream_data *zs = (struct zip_stream_data *)data;\n  int err = 0;\n\n  if(buffer_len > zs->output_length)\n  {\n    buffer_len = zs->output_length;\n    err = -1;\n  }\n\n  if(buffer_len)\n  {\n    memcpy(zs->output_buffer, buffer, buffer_len);\n    zs->output_buffer += buffer_len;\n    zs->output_length -= buffer_len;\n    zs->final_output_length += buffer_len;\n  }\n  return err;\n}\n\nstatic inline enum zip_error inflate64_file(struct zip_stream_data *zs)\n{\n  struct deflate64_stream_data *d64s = ((struct deflate64_stream_data *)zs);\n  int err;\n\n  if(zs->finished)\n    return ZIP_STREAM_FINISHED;\n\n  if(!zs->output_buffer)\n    return ZIP_OUTPUT_FULL;\n\n  if(!zs->input_buffer)\n    return ZIP_INPUT_EMPTY;\n\n  err = inflateBack9Init(&(d64s->z), d64s->window);\n  if(err != Z_OK)\n    return ZIP_DECOMPRESS_FAILED;\n\n  err = inflateBack9(\n    &(d64s->z),\n    inflate64_in_callback, zs,\n    inflate64_out_callback, zs\n  );\n\n  zs->finished = true;\n\n  inflateBack9End(&(d64s->z));\n\n  if(err == Z_STREAM_END)\n    return ZIP_STREAM_FINISHED;\n\n  return ZIP_DECOMPRESS_FAILED;\n}\n\n__M_END_DECLS\n\n#endif\n"
  },
  {
    "path": "src/io/zip_dict.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __ZIP_DICT_H\n#define __ZIP_DICT_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../util.h\"\n\n/**\n * Copy a block of memory from an earlier portion of an output stream to the\n * current position in the output stream. If the specified distance would\n * result in a negative offset relative to the start pointer, the bytes at\n * negative offsets are treated as 0. No other bounds checks are performed here.\n */\nstatic inline void sliding_dictionary_copy(uint8_t *start, uint8_t **_pos,\n uint32_t distance, uint32_t length)\n{\n  uint8_t *pos = *_pos;\n  ptrdiff_t copy_offset = (pos - distance) - start;\n  uint32_t i;\n\n  if(copy_offset < 0)\n  {\n    // Treat bytes at negative offsets as having value 0...\n    uint32_t fill_amount = MIN((uint32_t)(-copy_offset), length);\n\n    for(i = 0; i < fill_amount; i++)\n      pos[i] = 0;\n\n    pos += fill_amount;\n    length -= fill_amount;\n    copy_offset = 0;\n  }\n\n  if(length > 0)\n  {\n    // NOTE: These algorithms rely on moving dictionary values that might not\n    // exist until mid-copy, so memcpy is not safe to use here.\n    for(i = 0; i < length; i++)\n      pos[i] = start[copy_offset + i];\n\n    pos += length;\n  }\n  *_pos = pos;\n}\n\n__M_END_DECLS\n\n#endif /* __ZIP_DICT_H */\n"
  },
  {
    "path": "src/io/zip_implode.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Implode (zip compression method 6) decompressor.\n */\n\n#ifndef __ZIP_IMPLODE_H\n#define __ZIP_IMPLODE_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <assert.h>\n#include <inttypes.h>\n\n#include \"bitstream.h\"\n#include \"zip.h\"\n#include \"zip_dict.h\"\n#include \"zip_stream.h\"\n\nstruct explode_stream_data\n{\n  struct zip_stream_data zs;\n  struct bitstream b;\n  struct SF_tree *literal_tree;\n  struct SF_tree *length_tree;\n  struct SF_tree *distance_tree;\n  uint8_t minimum_match_length;\n  boolean has_8k_dictionary;\n  boolean has_literal_tree;\n};\n\n#define REVERSE_BITS(u_16) \\\n( \\\n  ((u_16 & 0x8000) >> 15) | \\\n  ((u_16 & 0x4000) >> 13) | \\\n  ((u_16 & 0x2000) >> 11) | \\\n  ((u_16 & 0x1000) >>  9) | \\\n  ((u_16 & 0x0800) >>  7) | \\\n  ((u_16 & 0x0400) >>  5) | \\\n  ((u_16 & 0x0200) >>  3) | \\\n  ((u_16 & 0x0100) >>  1) | \\\n  ((u_16 & 0x0080) <<  1) | \\\n  ((u_16 & 0x0040) <<  3) | \\\n  ((u_16 & 0x0020) <<  5) | \\\n  ((u_16 & 0x0010) <<  7) | \\\n  ((u_16 & 0x0008) <<  9) | \\\n  ((u_16 & 0x0004) << 11) | \\\n  ((u_16 & 0x0002) << 13) | \\\n  ((u_16 & 0x0001) << 15)   \\\n)\n\n// Struct for Shannon-Fano compressed tree pairs.\nstruct SF_pair\n{\n  uint8_t length;\n  uint8_t count;\n};\n\n// Struct for expanded Shannon-Fano tree leaves when constructing the table.\nstruct SF_value\n{\n  uint8_t length;\n  uint16_t value;\n};\n\n// Final tree struct for the Shannon-Fano tree.\nstruct SF_tree\n{\n  union\n  {\n    uint16_t next[2];\n    struct\n    {\n      uint16_t is_value;\n      uint16_t value;\n    } v;\n  } data;\n};\n\n#define SF_TREE_VALUE 0xFFFF\n#define IS_VALUE(sft) (sft.data.v.is_value == SF_TREE_VALUE)\n\nstatic inline int sort_sf_list(const void *A, const void *B)\n{\n  const struct SF_value *a = (const struct SF_value *)A;\n  const struct SF_value *b = (const struct SF_value *)B;\n  return (a->length != b->length) ? a->length - b->length : a->value - b->value;\n}\n\n/**\n * Read an imploded Shannon-Fano tree from a bitstream.\n * NOTE: this will fail unless the entire tree is buffered in the bitstream.\n */\nstatic inline enum zip_error expl_SF_read_tree(struct bitstream *b,\n struct SF_tree **output)\n{\n  struct SF_pair *data = NULL;\n  struct SF_value *list = NULL;\n  struct SF_tree *tree = NULL;\n  uint16_t *codes = NULL;\n\n  int tree_data_len = BS_READ(b, 8) + 1;\n  int tree_list_len = 0;\n  int i, j;\n\n  if(tree_data_len == 0 || tree_data_len > 256)\n    goto err_free;\n\n  // Read the raw tree data.\n  data = cmalloc(sizeof(struct SF_pair) * tree_data_len);\n  for(i = 0; i < tree_data_len; i++)\n  {\n    int value = BS_READ(b, 8);\n    if(value == EOF)\n      goto err_free;\n\n    data[i].length = (value & 0x0F) + 1;\n    data[i].count  = ((value & 0xF0) >> 4) + 1;\n    tree_list_len += data[i].count;\n  }\n\n  list = cmalloc(sizeof(struct SF_value) * tree_list_len);\n\n  // Build the initial node list.\n  {\n    uint16_t value = 0;\n    uint16_t pos = 0;\n    for(i = 0; i < tree_data_len; i++)\n    {\n      for(j = 0; j < (int)data[i].count; j++)\n      {\n        list[pos].length = data[i].length;\n        list[pos].value = value++;\n        pos++;\n      }\n    }\n  }\n  free(data);\n  data = NULL;\n\n  qsort(list, tree_list_len, sizeof(struct SF_value), sort_sf_list);\n\n  codes = cmalloc(sizeof(uint16_t) * tree_list_len);\n\n  // Calculate the SF codes.\n  {\n    uint16_t code = 0;\n    uint16_t code_inc = 0;\n    uint8_t last_length = 0;\n\n    for(i = tree_list_len - 1; i >= 0; i--)\n    {\n      code += code_inc;\n      if(list[i].length != last_length)\n      {\n        last_length = list[i].length;\n        code_inc = 1 << (16 - last_length);\n      }\n      codes[i] = REVERSE_BITS(code);\n    }\n  }\n\n  // Allocate twice as many nodes as there are leaves for now...\n  tree = ccalloc(tree_list_len * 2, sizeof(struct SF_tree));\n\n  // Construct the searchable tree using the list.\n  {\n    uint16_t count = 1;\n\n    for(i = 0; i < tree_list_len; i++)\n    {\n      uint16_t pos = 0;\n      uint16_t next_pos;\n\n      for(j = 0; j < list[i].length; j++)\n      {\n        uint8_t bit = (codes[i] >> j) & 1;\n\n        if(IS_VALUE(tree[pos]))\n          goto err_free;\n\n        next_pos = tree[pos].data.next[bit];\n\n        if(!next_pos)\n        {\n          if(count >= tree_list_len * 2)\n            goto err_free;\n\n          tree[pos].data.next[bit] = count;\n          next_pos = count;\n          count++;\n        }\n        pos = next_pos;\n      }\n\n      if(IS_VALUE(tree[pos]))\n        goto err_free;\n\n      tree[pos].data.v.is_value = SF_TREE_VALUE;\n      tree[pos].data.v.value = list[i].value;\n    }\n\n    // Validate the tree -- make sure all child indexes are set for non-leaves.\n    for(i = 0; i < count; i++)\n    {\n      if(!IS_VALUE(tree[i]))\n        if(!tree[i].data.next[0] || !tree[i].data.next[1])\n          goto err_free;\n    }\n\n    // Shrink the tree...\n    tree = crealloc(tree, count * sizeof(struct SF_tree));\n  }\n  free(list);\n  free(codes);\n  list = NULL;\n  codes = NULL;\n\n  *output = tree;\n  return ZIP_SUCCESS;\n\nerr_free:\n  free(data);\n  free(list);\n  free(tree);\n  free(codes);\n  *output = NULL;\n  return ZIP_DECOMPRESS_FAILED;\n}\n\n/**\n * Read a variable length encoded Shannon-Fano tree value from a bitstream and\n * return the decoded value. Returns -1 on failure.\n */\nstatic inline int expl_SF_decode(struct bitstream *b, struct SF_tree *tree)\n{\n  uint16_t pos = 0;\n  int bit;\n\n  while(true)\n  {\n    if(IS_VALUE(tree[pos]))\n      return tree[pos].data.v.value;\n\n    bit = BS_READ(b, 1);\n    if(bit < 0 || bit > 1)\n      return -1;\n\n    pos = tree[pos].data.next[bit];\n  }\n}\n\n/**\n * Free a Shannon-Fano tree. Currently, this just only needs one free()...\n */\nstatic inline void expl_SF_free(struct SF_tree *tree)\n{\n  free(tree);\n}\n\n/**\n * Allocate an explode stream.\n */\nstatic inline struct zip_stream_data *expl_create(void)\n{\n  return cmalloc(sizeof(struct explode_stream_data));\n}\n\n/**\n * Open an explode stream.\n */\nstatic inline void expl_open(struct zip_stream_data *zs, uint16_t method,\n uint16_t flags)\n{\n  struct explode_stream_data *xs = ((struct explode_stream_data *)zs);\n\n  memset(zs, 0, sizeof(struct explode_stream_data));\n  xs->has_8k_dictionary = !!(flags & ZIP_F_COMPRESSION_1);\n  xs->has_literal_tree = !!(flags & ZIP_F_COMPRESSION_2);\n  xs->minimum_match_length = xs->has_literal_tree ? 3 : 2;\n}\n\n/**\n * Close an explode stream.\n */\nstatic inline void expl_close(struct zip_stream_data *zs,\n size_t *final_input_length, size_t *final_output_length)\n{\n  struct explode_stream_data *xs = ((struct explode_stream_data *)zs);\n  expl_SF_free(xs->literal_tree);\n  expl_SF_free(xs->length_tree);\n  expl_SF_free(xs->distance_tree);\n\n  if(final_input_length)\n    *final_input_length = zs->final_input_length;\n\n  if(final_output_length)\n    *final_output_length = zs->final_output_length;\n}\n\n/**\n * Explode the input stream into the output buffer as a single file.\n */\nstatic inline enum zip_error expl_file(struct zip_stream_data *zs)\n{\n  struct explode_stream_data *xs = ((struct explode_stream_data *)zs);\n  struct bitstream *b = &(xs->b);\n\n  uint8_t *start = zs->output_buffer;\n  uint8_t *pos = zs->output_buffer;\n  uint8_t *end = zs->output_buffer + zs->output_length;\n\n  enum zip_error result;\n\n  if(zs->finished)\n    return ZIP_STREAM_FINISHED;\n\n  if(!start)\n    return ZIP_OUTPUT_FULL;\n\n  if(!b->input)\n  {\n    if(!zs->input_buffer)\n      return ZIP_INPUT_EMPTY;\n\n    b->input = zs->input_buffer;\n    b->input_left = zs->input_length;\n    zs->final_input_length = zs->input_length;\n    zs->input_buffer = NULL;\n    zs->input_length = 0;\n  }\n\n  zs->output_buffer = NULL;\n  zs->output_length = 0;\n\n  if(!xs->length_tree)\n  {\n    if(!xs->literal_tree && xs->has_literal_tree)\n    {\n      result = expl_SF_read_tree(b, &(xs->literal_tree));\n      if(result != ZIP_SUCCESS)\n        return result;\n    }\n\n    result = expl_SF_read_tree(b, &(xs->length_tree));\n    if(result != ZIP_SUCCESS)\n      return result;\n  }\n\n  if(!xs->distance_tree)\n  {\n    result = expl_SF_read_tree(b, &(xs->distance_tree));\n    if(result != ZIP_SUCCESS)\n      return result;\n  }\n\n  while(pos < end)\n  {\n    int value = BS_READ(b, 1);\n\n    if(value < 0)\n      break;\n\n    if(value)\n    {\n      // Literal data.\n      if(xs->literal_tree)\n      {\n        value = expl_SF_decode(b, xs->literal_tree);\n      }\n      else\n        value = BS_READ(b, 8);\n\n      if(value < 0)\n        break;\n\n      *(pos++) = value;\n    }\n    else\n    {\n      // Sliding dictionary match.\n      uint16_t distance;\n      uint16_t length;\n\n      // Distance\n      if(xs->has_8k_dictionary)\n      {\n        distance = BS_READ(b, 7);\n        value = expl_SF_decode(b, xs->distance_tree);\n        if(value < 0)\n          break;\n\n        distance |= value << 7;\n      }\n      else\n      {\n        distance = BS_READ(b, 6);\n        value = expl_SF_decode(b, xs->distance_tree);\n        if(value < 0)\n          break;\n\n        distance |= value << 6;\n      }\n\n      // Length\n      value = expl_SF_decode(b, xs->length_tree);\n      if(value < 0)\n        break;\n\n      length = value + xs->minimum_match_length;\n      if(value == 63)\n      {\n        value = BS_READ(b, 8);\n        if(value < 0)\n          break;\n\n        length += value;\n      }\n\n      // Must be able to write this amount to the buffer.\n      if(pos + length > end)\n        return ZIP_DECOMPRESS_FAILED;\n\n      sliding_dictionary_copy(start, &pos, distance + 1, length);\n    }\n  }\n  zs->final_output_length = pos - start;\n  zs->finished = true;\n  return ZIP_STREAM_FINISHED;\n}\n\n#endif /* __ZIP_IMPLODE_H */\n"
  },
  {
    "path": "src/io/zip_reduce.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Reduce (zip compression methods 2-5) decompressor.\n */\n\n#ifndef __ZIP_REDUCE_H\n#define __ZIP_REDUCE_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <assert.h>\n#include <inttypes.h>\n\n#include \"bitstream.h\"\n#include \"zip_dict.h\"\n#include \"zip_stream.h\"\n\n#include \"../util.h\"\n\nstruct reduce_stream_data\n{\n  struct zip_stream_data zs;\n  struct bitstream b;\n  uint8_t factor;\n};\n\n/**\n * Allocate an expanding stream.\n */\nstatic inline struct zip_stream_data *reduce_ex_create(void)\n{\n  return cmalloc(sizeof(struct reduce_stream_data));\n}\n\n/**\n * Open an expanding stream.\n */\nstatic inline void reduce_ex_open(struct zip_stream_data *zs, uint16_t method,\n uint16_t flags)\n{\n  struct reduce_stream_data *rs = ((struct reduce_stream_data *)zs);\n  memset(zs, 0, sizeof(struct reduce_stream_data));\n  rs->factor = CLAMP((method - 2), 0, 3);\n}\n\n/**\n * Return the number of bits required to store an index for a follower set of\n * a given length, or 0 if the follower set is of length 0.\n */\nstatic inline uint8_t follower_set_bits_required(int length)\n{\n  int i;\n  if(length < 1)\n    return 0;\n\n  length--;\n  for(i = 1; i < 8; i++)\n    if(length < (1 << i))\n      return i;\n  return i;\n}\n\n#define REDUCE_DLE (144)\n#define REDUCE_BUFFER_SIZE (8192)\n#define SET(ch) (sets + (ch * 32))\n\n/**\n * Expand the entire input buffer into the output buffer as a complete file.\n */\nstatic inline enum zip_error reduce_ex_file(struct zip_stream_data *zs)\n{\n  struct reduce_stream_data *rs = ((struct reduce_stream_data *)zs);\n  struct bitstream *b = &(rs->b);\n  uint8_t factor = rs->factor;\n  uint8_t n[256];\n  uint8_t *buffer = cmalloc((REDUCE_BUFFER_SIZE + 32 * 256) * sizeof(uint8_t));\n  uint8_t *sets = buffer + REDUCE_BUFFER_SIZE;\n  uint8_t *set;\n\n  uint8_t *start = zs->output_buffer;\n  uint8_t *pos = zs->output_buffer;\n  uint8_t *end = zs->output_buffer + zs->output_length;\n\n  uint32_t buffer_pos;\n  uint32_t buffer_stop;\n  uint32_t length = 0;\n  uint32_t distance = 0;\n  uint8_t last_character;\n  uint8_t state;\n  boolean eof = false;\n  int value;\n  int i;\n  int j;\n\n  if(zs->finished)\n    return ZIP_STREAM_FINISHED;\n\n  if(!zs->output_buffer)\n    return ZIP_OUTPUT_FULL;\n\n  if(!zs->input_buffer)\n    return ZIP_INPUT_EMPTY;\n\n  b->input = zs->input_buffer;\n  b->input_left = zs->input_length;\n  zs->final_input_length = zs->input_length;\n  zs->input_buffer = zs->output_buffer = NULL;\n  zs->input_length = zs->output_length = 0;\n\n  // Read the follower sets from the input buffer.\n  for(i = 255; i >= 0; i--)\n  {\n    value = BS_READ(b, 6);\n    if(value < 0)\n      goto err_free;\n\n    n[i] = follower_set_bits_required(value);\n    set = SET(i);\n    for(j = 0; j < value; j++)\n      set[j] = BS_READ(b, 8);\n    for(j = value; j < 32; j++)\n      set[j] = 0;\n  }\n\n  last_character = 0;\n  state = 0;\n  while(pos < end)\n  {\n    // Abort if this is the end of the stream...\n    if(eof)\n      break;\n\n    // Stage 1: probabilistic decompression using follower sets.\n    buffer_pos = 0;\n    while(buffer_pos < REDUCE_BUFFER_SIZE)\n    {\n      if(!n[last_character])\n      {\n        value = BS_READ(b, 8);\n        if(value < 0)\n        {\n          eof = true;\n          break;\n        }\n\n        last_character = value;\n      }\n      else\n      {\n        value = BS_READ(b, 1);\n        if(value < 0)\n        {\n          eof = true;\n          break;\n        }\n\n        if(value)\n        {\n          value = BS_READ(b, 8);\n          if(value < 0)\n          {\n            eof = true;\n            break;\n          }\n\n          last_character = value;\n        }\n        else\n        {\n          value = BS_READ(b, n[last_character]);\n          if(value < 0)\n          {\n            eof = true;\n            break;\n          }\n\n          last_character = SET(last_character)[value];\n        }\n      }\n      buffer[buffer_pos++] = last_character;\n    }\n\n    // Stage 2: expand intermediate buffer.\n    buffer_stop = buffer_pos;\n    buffer_pos = 0;\n    while(pos < end && buffer_pos < buffer_stop)\n    {\n      uint8_t ch = buffer[buffer_pos++];\n      switch(state)\n      {\n        case 0:\n        {\n          if(ch != REDUCE_DLE)\n          {\n            *(pos++) = ch;\n          }\n          else\n            state = 1;\n          break;\n        }\n\n        case 1:\n        {\n          if(ch)\n          {\n            uint8_t lower_mask = 0x7F >> factor;\n            uint8_t upper_mask = 0xFF ^ lower_mask;\n\n            distance = (uint32_t)(ch & upper_mask) << (factor + 1);\n            length = (ch & lower_mask);\n            state = (length == lower_mask) ? 2 : 3;\n          }\n          else\n          {\n            *(pos++) = REDUCE_DLE;\n            state = 0;\n          }\n          break;\n        }\n\n        case 2:\n        {\n          length += ch;\n          state = 3;\n          break;\n        }\n\n        case 3:\n        {\n          distance += ch + 1;\n          length += 3;\n\n          if(pos + length > end)\n            goto err_free;\n\n          sliding_dictionary_copy(start, &pos, distance, length);\n          state = 0;\n          break;\n        }\n      }\n    }\n  }\n  free(buffer);\n\n  zs->final_output_length = pos - start;\n  zs->finished = true;\n  return ZIP_STREAM_FINISHED;\n\nerr_free:\n  free(buffer);\n  return ZIP_DECOMPRESS_FAILED;\n}\n\n__M_END_DECLS\n\n#endif /* __ZIP_REDUCE_H */\n"
  },
  {
    "path": "src/io/zip_shrink.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Shrink (zip compression method 1) decompressor.\n */\n\n#ifndef __ZIP_SHRINK_H\n#define __ZIP_SHRINK_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <assert.h>\n#include <inttypes.h>\n\n#include \"bitstream.h\"\n#include \"zip.h\"\n\n#include \"../util.h\"\n\nstruct LZW_node\n{\n  uint16_t pr_st; // State (0x6000) and previous node index (0x1FFF)\n  uint8_t length;\n  uint8_t value;\n};\n\nstruct LZW_tree\n{\n  struct LZW_node *nodes;\n  uint16_t next;\n  uint16_t length;\n  uint16_t alloc;\n  uint16_t previous_code;\n  uint8_t previous_first_char;\n};\n\nstruct shrink_stream_data\n{\n  struct zip_stream_data zs;\n  struct bitstream b;\n  struct LZW_tree tree;\n  uint8_t bit_width;\n};\n\n#define LZW_NO_CODE 0xFFFF\n#define LZW_COMMAND_CODE 0x100\n#define LZW_NUM_CODES_DEFAULT 257\n#define LZW_TREE_ALLOC_DEFAULT 1024\n#define LZW_TREE_ALLOC_MAX 8192\n#define LZW_BIT_WIDTH_DEFAULT 9\n#define LZW_BIT_WIDTH_MAX     13\n\n#define LZW_AVAILABLE   0\n#define LZW_LEAF        1\n#define LZW_BRANCH      2\n\n#define LZW_GET_PREV(n)    ((n)->pr_st & 0x1FFF)\n#define LZW_SET_PREV(n,p)  ((n)->pr_st = ((n)->pr_st & ~0x1FFF)|(p & 0x1FFF))\n#define LZW_GET_STATE(n)   (((n)->pr_st >> 13) & 3)\n#define LZW_SET_STATE(n,s) ((n)->pr_st = ((n)->pr_st & 0x1FFF)|((s & 3)<<13));\n\n/**\n * Add an LZW code.\n */\nstatic inline void LZW_add(struct LZW_tree *tree)\n{\n  struct LZW_node *current = tree->nodes + tree->next;\n  uint8_t prev_length;\n\n  if(tree->next == tree->length)\n  {\n    if(tree->length == tree->alloc)\n    {\n      if(tree->alloc >= LZW_TREE_ALLOC_MAX)\n        return;\n\n      tree->alloc *= 2;\n      tree->nodes = crealloc(tree->nodes, tree->alloc * sizeof(struct LZW_node));\n      current = tree->nodes + tree->next;\n    }\n    tree->length++;\n    tree->next++;\n  }\n  else\n  {\n    // Find the next available code for the next time this function is called.\n    uint16_t i;\n    //assert(tree->next != tree->previous_code);\n    assert(LZW_GET_STATE(current) == LZW_AVAILABLE);\n    for(i = tree->next + 1; i < tree->length; i++)\n      if(LZW_GET_STATE(tree->nodes + i) == LZW_AVAILABLE)\n        break;\n    tree->next = i;\n  }\n\n  LZW_SET_PREV(current, tree->previous_code);\n  LZW_SET_STATE(current, LZW_LEAF);\n  current->value = tree->previous_first_char;\n\n  // NOTE: may intentionally overflow to 0, in which case the length will be\n  // computed as-needed by iterating the tree. The other case this might be 0\n  // is with nodes marked \"available\", as they may be spontaneously overwritten,\n  // invalidating the lengths of their children as well (see below).\n  prev_length = tree->nodes[tree->previous_code].length;\n  current->length = prev_length ? prev_length + 1 : 0;\n}\n\n/**\n * Partially clear the LZW tree.\n * In practice what this means is marking leaf nodes as available and resetting\n * the next available code. NOTE: available codes can still be referenced,\n * branched from, and then overwritten, altering their child strings too.\n */\nstatic inline void LZW_partial_clear(struct LZW_tree *tree)\n{\n  struct LZW_node *current;\n  uint16_t first_available = tree->length;\n  uint16_t i;\n\n  // Pass 1: all nodes should be marked as \"leaves\" or \"available\".\n  // Either of these could actually be branches...\n  for(i = LZW_NUM_CODES_DEFAULT; i < tree->length; i++)\n  {\n    current = tree->nodes + i;\n    LZW_SET_STATE(tree->nodes + LZW_GET_PREV(current), LZW_BRANCH);\n  }\n\n  // Pass 2: chain leaves/available and mark available, mark others as leaves.\n  for(i = tree->length - 1; i >= LZW_NUM_CODES_DEFAULT; i--)\n  {\n    current = tree->nodes + i;\n\n    switch(LZW_GET_STATE(current))\n    {\n      case LZW_AVAILABLE:\n      case LZW_LEAF:\n        LZW_SET_STATE(current, LZW_AVAILABLE);\n        current->length = 0;\n        first_available = i;\n        break;\n\n      case LZW_BRANCH:\n        LZW_SET_STATE(current, LZW_LEAF);\n        break;\n    }\n  }\n  tree->next = first_available;\n}\n\n/**\n * Get the length of an LZW code, or compute it if it isn't currently stored.\n * This happens when one or mode codes in the sequence are marked for reuse.\n */\nstatic inline uint16_t LZW_get_length(struct LZW_tree *tree, struct LZW_node *c)\n{\n  uint16_t code;\n  uint16_t length = 1;\n\n  if(c->length)\n    return c->length;\n\n  do\n  {\n    // Shouldn't happen, but...\n    if(length >= (1<<13))\n      return 0;\n\n    length++;\n    code = LZW_GET_PREV(c);\n    c = tree->nodes + code;\n  }\n  while(code >= LZW_NUM_CODES_DEFAULT);\n  return length;\n}\n\n/**\n * Output an LZW code.\n */\nstatic inline enum zip_error LZW_output(struct LZW_tree *tree, uint16_t code,\n uint8_t **_pos, uint8_t *end)\n{\n  uint8_t *pos = *_pos;\n\n  if(code >= LZW_NUM_CODES_DEFAULT)\n  {\n    struct LZW_node *nodes = tree->nodes;\n    struct LZW_node *current = nodes + code;\n    uint16_t length = LZW_get_length(tree, current);\n    uint16_t i;\n\n    if(pos + length > end)\n    {\n      trace(\"--UNSHRINK-- out of buffer in LZW_output!\\n\");\n      return ZIP_OUTPUT_FULL;\n    }\n\n    if(length == 0)\n    {\n      trace(\"--UNSHRINK-- encountered infinite cycle at %u! aborting\\n\", code);\n      return ZIP_DECOMPRESS_FAILED;\n    }\n\n    for(i = length - 1; i > 0; i--)\n    {\n      pos[i] = current->value;\n      code = LZW_GET_PREV(current);\n      current = nodes + code;\n    }\n    *pos = code;\n    pos += length;\n  }\n  else\n\n  if(pos < end)\n  {\n    *(pos++) = code;\n  }\n  else\n  {\n    trace(\"--UNSHRINK-- out of buffer in LZW_output!\\n\");\n    return ZIP_OUTPUT_FULL;\n  }\n\n  *_pos = pos;\n  tree->previous_first_char = code;\n  return ZIP_SUCCESS;\n}\n\n/**\n * Decode an LZW code and create the next code from known data.\n */\nstatic inline enum zip_error LZW_decode(struct LZW_tree *tree, uint16_t code,\n uint8_t **_pos, uint8_t *end)\n{\n  enum zip_error result;\n\n  if(code > tree->length)\n  {\n    trace(\"--UNSHRINK-- invalid code %u\\n\", code);\n    return ZIP_DECOMPRESS_FAILED;\n  }\n\n  if(code == tree->next)\n  {\n    // This is a special case--the current code is the previous code with the\n    // first character of the previous code appended, and needs to be added\n    // before the output occurs (instead of after).\n    if(tree->previous_code == LZW_NO_CODE)\n    {\n      trace(\"--UNSHRINK-- invalid code %u (in special case)\\n\", code);\n      return ZIP_DECOMPRESS_FAILED;\n    }\n\n    LZW_add(tree);\n    result = LZW_output(tree, code, _pos, end);\n    tree->previous_code = code;\n    return result;\n  }\n\n  // Otherwise, output first, and then add a new code, which is the previous\n  // code with the first character of the current code appended.\n  result = LZW_output(tree, code, _pos, end);\n  if(result == ZIP_SUCCESS)\n  {\n    if(tree->previous_code != LZW_NO_CODE)\n      LZW_add(tree);\n\n    tree->previous_code = code;\n  }\n  return result;\n}\n\n/**\n * Allocate an unshrink stream.\n */\nstatic inline struct zip_stream_data *unshrink_create(void)\n{\n  return cmalloc(sizeof(struct shrink_stream_data));\n}\n\n/**\n * Open a unshrink stream.\n */\nstatic inline void unshrink_open(struct zip_stream_data *zs, uint16_t method,\n uint16_t flags)\n{\n  struct shrink_stream_data *ss = ((struct shrink_stream_data *)zs);\n  struct LZW_tree *tree = &(ss->tree);\n  int i;\n\n  memset(zs, 0, sizeof(struct shrink_stream_data));\n\n  tree->nodes = cmalloc(LZW_TREE_ALLOC_DEFAULT * sizeof(struct LZW_node));\n  tree->alloc = LZW_TREE_ALLOC_DEFAULT;\n  tree->next = LZW_NUM_CODES_DEFAULT;\n  tree->length = LZW_NUM_CODES_DEFAULT;\n  tree->previous_code = LZW_NO_CODE;\n  tree->previous_first_char = 0;\n\n  for(i = 0; i < LZW_NUM_CODES_DEFAULT; i++)\n  {\n    tree->nodes[i].length = 1;\n    LZW_SET_STATE(tree->nodes + i, LZW_BRANCH);\n  }\n\n  ss->bit_width = LZW_BIT_WIDTH_DEFAULT;\n}\n\n/**\n * Free extra allocated data for a (un)shrink stream.\n */\nstatic inline void unshrink_close(struct zip_stream_data *zs,\n size_t *final_input_length, size_t *final_output_length)\n{\n  struct shrink_stream_data *ss = ((struct shrink_stream_data *)zs);\n  free(ss->tree.nodes);\n\n  if(final_input_length)\n    *final_input_length = zs->final_input_length;\n\n  if(final_output_length)\n    *final_output_length = zs->final_output_length;\n}\n\n/**\n * Unshrink the input buffer into the output buffer, treating it as a file.\n */\nstatic inline enum zip_error unshrink_file(struct zip_stream_data *zs)\n{\n  struct shrink_stream_data *ss = ((struct shrink_stream_data *)zs);\n  struct LZW_tree *tree = &(ss->tree);\n  struct bitstream *b = &(ss->b);\n  enum zip_error result;\n\n  uint8_t *start = zs->output_buffer;\n  uint8_t *pos = zs->output_buffer;\n  uint8_t *end = zs->output_buffer + zs->output_length;\n\n  uint8_t bit_width = ss->bit_width;\n\n  if(zs->finished)\n    return ZIP_STREAM_FINISHED;\n\n  if(!zs->output_buffer)\n    return ZIP_OUTPUT_FULL;\n\n  if(!zs->input_buffer)\n    return ZIP_INPUT_EMPTY;\n\n  b->input = zs->input_buffer;\n  b->input_left = zs->input_length;\n  zs->final_input_length = zs->input_length;\n  zs->input_buffer = zs->output_buffer = NULL;\n  zs->input_length = zs->output_length = 0;\n\n  while(pos < end)\n  {\n    int code = BS_READ(b, bit_width);\n    if(code < 0)\n      break;\n\n    if(code == LZW_COMMAND_CODE)\n    {\n      code = BS_READ(b, bit_width);\n      if(code < 0)\n        break;\n\n      if(code == 1)\n      {\n        if(bit_width >= LZW_BIT_WIDTH_MAX)\n        {\n          trace(\"--UNSHRINK-- can't expand bit width beyond 13!\\n\");\n          return ZIP_DECOMPRESS_FAILED;\n        }\n\n        bit_width++;\n        ss->bit_width = bit_width;\n      }\n      else\n\n      if(code == 2)\n      {\n        LZW_partial_clear(tree);\n      }\n      else\n      {\n        trace(\"--UNSHRINK-- invalid shrink command code %u!\\n\", code);\n        return ZIP_DECOMPRESS_FAILED;\n      }\n      continue;\n    }\n\n    result = LZW_decode(tree, code, &pos, end);\n    if(result)\n      return result;\n  }\n  zs->final_output_length = pos - start;\n  zs->finished = true;\n  return ZIP_STREAM_FINISHED;\n}\n\n__M_END_DECLS\n\n#endif /* __ZIP_SHRINK_H */\n"
  },
  {
    "path": "src/io/zip_stream.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <inttypes.h>\n\n#include \"zip.h\"\n#include \"zip_stream.h\"\n#include \"zip_deflate.h\"\n\n/**\n * The extra decompressors (shrink, reduce, implode, deflate64) are useful for\n * utils that might need to scan an arbitrary archive (checkres).\n */\n#if defined(CONFIG_UTILS) || defined(__EMSCRIPTEN__)\n#define ZIP_EXTRA_DECOMPRESSORS\n#include \"zip_deflate64.h\"\n#include \"zip_implode.h\"\n#include \"zip_reduce.h\"\n#include \"zip_shrink.h\"\n#endif\n\n#ifdef ZIP_EXTRA_DECOMPRESSORS\nstatic void zip_stream_destroy(struct zip_stream_data *zs)\n{\n  free(zs);\n}\n\nstatic void zip_stream_close(struct zip_stream_data *zs,\n size_t *final_input_length, size_t *final_output_length)\n{\n  if(final_input_length)\n    *final_input_length = zs->final_input_length;\n\n  if(final_output_length)\n    *final_output_length = zs->final_output_length;\n}\n\nstatic boolean zip_stream_input(struct zip_stream_data *zs, const void *src,\n size_t src_len)\n{\n  if(!zs->input_buffer)\n  {\n    zs->input_buffer = (uint8_t *)src;\n    zs->input_length = src_len;\n    return true;\n  }\n  return false;\n}\n\nstatic boolean zip_stream_output(struct zip_stream_data *zs, void *dest,\n size_t dest_len)\n{\n  zs->output_buffer = (uint8_t *)dest;\n  zs->output_length = dest_len;\n  return true;\n}\n\nstatic const struct zip_method_handler shrink_spec =\n{\n  unshrink_create,\n  zip_stream_destroy,\n  unshrink_open,\n  NULL,\n  unshrink_close,\n  zip_stream_input,\n  zip_stream_output,\n  unshrink_file,\n  NULL,\n  NULL,\n  NULL\n};\n\nstatic const struct zip_method_handler reduce_spec =\n{\n  reduce_ex_create,\n  zip_stream_destroy,\n  reduce_ex_open,\n  NULL,\n  zip_stream_close,\n  zip_stream_input,\n  zip_stream_output,\n  reduce_ex_file,\n  NULL,\n  NULL,\n  NULL\n};\n\nstatic const struct zip_method_handler implode_spec =\n{\n  expl_create,\n  zip_stream_destroy,\n  expl_open,\n  NULL,\n  expl_close,\n  zip_stream_input,\n  zip_stream_output,\n  expl_file,\n  NULL,\n  NULL,\n  NULL\n};\n\nstatic const struct zip_method_handler deflate64_spec =\n{\n  inflate64_create,\n  zip_stream_destroy,\n  inflate64_open,\n  NULL,\n  inflate64_close,\n  zip_stream_input,\n  zip_stream_output,\n  inflate64_file,\n  NULL,\n  NULL,\n  NULL\n};\n#endif\n\nstatic const struct zip_method_handler deflate_spec =\n{\n  deflate_create,\n  deflate_destroy,\n  inflate_open,\n  deflate_open,\n  deflate_close,\n  deflate_input,\n  deflate_output,\n  NULL,\n  inflate_block,\n  deflate_block,\n  deflate_bound\n};\n\n#ifdef ZIP_EXTRA_DECOMPRESSORS\n#define ZIP_EXTRA(ds) (ds)\n#else\n#define ZIP_EXTRA(ds) NULL\n#endif\n\nconst struct zip_method_handler * const zip_method_handlers[] =\n{\n  NULL,\n  ZIP_EXTRA(&shrink_spec),\n  ZIP_EXTRA(&reduce_spec),\n  ZIP_EXTRA(&reduce_spec),\n  ZIP_EXTRA(&reduce_spec),\n  ZIP_EXTRA(&reduce_spec),\n  ZIP_EXTRA(&implode_spec),\n  NULL,\n  &deflate_spec,\n  ZIP_EXTRA(&deflate64_spec),\n};\n"
  },
  {
    "path": "src/io/zip_stream.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __ZIP_STREAM_H\n#define __ZIP_STREAM_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <inttypes.h>\n#include \"zip.h\"\n\n/**\n * Describes ZIP stream functions for a particular (de)compresson method.\n */\nstruct zip_method_handler\n{\n  // Create a stream data structure for this method handler.\n  struct zip_stream_data *(*create)(void);\n\n  // Destroy a stream data structure for this method handler.\n  // This function will free the provided zip_stream_data pointer.\n  void (*destroy)(struct zip_stream_data *);\n\n  // Open a decompression stream.\n  void (*decompress_open)(struct zip_stream_data *, uint16_t method,\n   uint16_t flags);\n\n  // Open a compression stream.\n  void (*compress_open)(struct zip_stream_data *, uint16_t method,\n   uint16_t flags);\n\n  // Frees any memory allocated by the stream and does any work necessary to\n  // reset the zip_stream_data struct for the next file, if applicable.\n  void (*close)(struct zip_stream_data *, size_t *final_input_length,\n   size_t *final_output_length);\n\n  // Provide the next input buffer for a stream.\n  boolean (*input)(struct zip_stream_data *, const void *src, size_t src_len);\n\n  // Provide the next output buffer for a stream.\n  boolean (*output)(struct zip_stream_data *, void *dest, size_t dest_len);\n\n  // Decompress, treating the input and output as an entire file.\n  enum zip_error (*decompress_file)(struct zip_stream_data *);\n\n  // Decompress until the input is exhausted or the output is full.\n  // Can be called multiple times.\n  enum zip_error (*decompress_block)(struct zip_stream_data *);\n\n  // Compress until the input is exhausted or the output is full.\n  // Can be called multiple times. Provide \"true\" to the second parameter\n  // to finish the stream.\n  enum zip_error (*compress_block)(struct zip_stream_data *, boolean);\n\n  // Bound final compressed stream length based on input stream length.\n  enum zip_error (*compress_bound)(struct zip_stream_data *, size_t src_len,\n   size_t *bound_len);\n};\n\n/**\n * ZIP stream function registry for supported (de)compression methods.\n */\nUTILS_LIBSPEC\nextern const struct zip_method_handler * const zip_method_handlers[ZIP_M_MAX_SUPPORTED + 1];\n\n__M_END_DECLS\n\n#endif /* __ZIP_STREAM_H */\n"
  },
  {
    "path": "src/keysym.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __KEYSYM_H\n#define __KEYSYM_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\nenum keycode\n{\n  IKEY_UNKNOWN      = 0,\n  IKEY_FIRST        = 0,\n  IKEY_UNICODE      = 1,\n  IKEY_BACKSPACE    = 8,\n  IKEY_TAB          = 9,\n  IKEY_RETURN       = 13,\n  IKEY_ESCAPE       = 27,\n  IKEY_SPACE        = 32,\n  IKEY_QUOTE        = 39,\n//IKEY_PLUS         = 43,\n  IKEY_COMMA        = 44,\n  IKEY_MINUS        = 45,\n  IKEY_PERIOD       = 46,\n  IKEY_SLASH        = 47,\n  IKEY_0            = 48,\n  IKEY_1            = 49,\n  IKEY_2            = 50,\n  IKEY_3            = 51,\n  IKEY_4            = 52,\n  IKEY_5            = 53,\n  IKEY_6            = 54,\n  IKEY_7            = 55,\n  IKEY_8            = 56,\n  IKEY_9            = 57,\n  IKEY_SEMICOLON    = 59,\n  IKEY_EQUALS       = 61,\n  IKEY_LEFTBRACKET  = 91,\n  IKEY_BACKSLASH    = 92,\n  IKEY_RIGHTBRACKET = 93,\n  IKEY_BACKQUOTE    = 96,\n  IKEY_a            = 97,\n  IKEY_b            = 98,\n  IKEY_c            = 99,\n  IKEY_d            = 100,\n  IKEY_e            = 101,\n  IKEY_f            = 102,\n  IKEY_g            = 103,\n  IKEY_h            = 104,\n  IKEY_i            = 105,\n  IKEY_j            = 106,\n  IKEY_k            = 107,\n  IKEY_l            = 108,\n  IKEY_m            = 109,\n  IKEY_n            = 110,\n  IKEY_o            = 111,\n  IKEY_p            = 112,\n  IKEY_q            = 113,\n  IKEY_r            = 114,\n  IKEY_s            = 115,\n  IKEY_t            = 116,\n  IKEY_u            = 117,\n  IKEY_v            = 118,\n  IKEY_w            = 119,\n  IKEY_x            = 120,\n  IKEY_y            = 121,\n  IKEY_z            = 122,\n  IKEY_DELETE       = 127,\n  IKEY_KP0          = 256,\n  IKEY_KP1          = 257,\n  IKEY_KP2          = 258,\n  IKEY_KP3          = 259,\n  IKEY_KP4          = 260,\n  IKEY_KP5          = 261,\n  IKEY_KP6          = 262,\n  IKEY_KP7          = 263,\n  IKEY_KP8          = 264,\n  IKEY_KP9          = 265,\n  IKEY_KP_PERIOD    = 266,\n  IKEY_KP_DIVIDE    = 267,\n  IKEY_KP_MULTIPLY  = 268,\n  IKEY_KP_MINUS     = 269,\n  IKEY_KP_PLUS      = 270,\n  IKEY_KP_ENTER     = 271,\n  IKEY_UP           = 273,\n  IKEY_DOWN         = 274,\n  IKEY_RIGHT        = 275,\n  IKEY_LEFT         = 276,\n  IKEY_INSERT       = 277,\n  IKEY_HOME         = 278,\n  IKEY_END          = 279,\n  IKEY_PAGEUP       = 280,\n  IKEY_PAGEDOWN     = 281,\n  IKEY_F1           = 282,\n  IKEY_F2           = 283,\n  IKEY_F3           = 284,\n  IKEY_F4           = 285,\n  IKEY_F5           = 286,\n  IKEY_F6           = 287,\n  IKEY_F7           = 288,\n  IKEY_F8           = 289,\n  IKEY_F9           = 290,\n  IKEY_F10          = 291,\n  IKEY_F11          = 292,\n  IKEY_F12          = 293,\n  IKEY_NUMLOCK      = 300,\n  IKEY_CAPSLOCK     = 301,\n  IKEY_SCROLLOCK    = 302,\n  IKEY_RSHIFT       = 303,\n  IKEY_LSHIFT       = 304,\n  IKEY_RCTRL        = 305,\n  IKEY_LCTRL        = 306,\n  IKEY_RALT         = 307,\n  IKEY_LALT         = 308,\n  IKEY_LSUPER       = 311,\n  IKEY_RSUPER       = 312,\n  IKEY_ALTGR        = 313,\n  IKEY_SYSREQ       = 317,\n  IKEY_BREAK        = 318,\n  IKEY_MENU         = 319,\n  IKEY_LAST\n};\n\n#define MOUSE_BUTTON(x) (1 << ((x) - 1))\n\nenum mouse_button\n{\n  MOUSE_NO_BUTTON,\n  MOUSE_BUTTON_LEFT,\n  MOUSE_BUTTON_MIDDLE,\n  MOUSE_BUTTON_RIGHT,\n  MOUSE_BUTTON_X1,\n  MOUSE_BUTTON_X2,\n  MOUSE_BUTTON_WHEELUP,\n  MOUSE_BUTTON_WHEELDOWN,\n  MOUSE_BUTTON_WHEELLEFT,\n  MOUSE_BUTTON_WHEELRIGHT,\n  NUM_MOUSE_BUTTONS\n};\n\nenum joystick_action\n{\n  JOY_NO_ACTION,\n  JOY_A,\n  JOY_B,\n  JOY_X,\n  JOY_Y,\n  JOY_SELECT,\n  //JOY_GUIDE,\n  JOY_START,\n  JOY_LSTICK,\n  JOY_RSTICK,\n  JOY_LSHOULDER,\n  JOY_RSHOULDER,\n  JOY_LTRIGGER,\n  JOY_RTRIGGER,\n  JOY_UP,\n  JOY_DOWN,\n  JOY_LEFT,\n  JOY_RIGHT,\n  JOY_L_UP,\n  JOY_L_DOWN,\n  JOY_L_LEFT,\n  JOY_L_RIGHT,\n  JOY_R_UP,\n  JOY_R_DOWN,\n  JOY_R_LEFT,\n  JOY_R_RIGHT,\n  NUM_JOYSTICK_ACTIONS\n};\n\nenum joystick_special_axis\n{\n  JOY_NO_AXIS,\n  JOY_AXIS_LEFT_X,\n  JOY_AXIS_LEFT_Y,\n  JOY_AXIS_RIGHT_X,\n  JOY_AXIS_RIGHT_Y,\n  JOY_AXIS_LEFT_TRIGGER,\n  JOY_AXIS_RIGHT_TRIGGER,\n  NUM_JOYSTICK_SPECIAL_AXES\n};\n\nenum joystick_hat\n{\n  JOYHAT_UP,\n  JOYHAT_DOWN,\n  JOYHAT_LEFT,\n  JOYHAT_RIGHT,\n  NUM_JOYSTICK_HAT_DIRS\n};\n\n__M_END_DECLS\n\n#endif // __KEYSYM_H\n"
  },
  {
    "path": "src/legacy_board.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <inttypes.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"legacy_board.h\"\n#include \"legacy_robot.h\"\n\n#include \"board.h\"\n#include \"const.h\"\n#include \"error.h\"\n#include \"robot.h\"\n#include \"util.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n#include \"io/vio.h\"\n\n/* 13 (not NULL terminated in format) */\n#define LEGACY_MOD_FILENAME_MAX 13\n\n/* 80 (w/o saved NULL terminator) */\n#define LEGACY_BOTTOM_MESG_MAX  80\n\n/* 80 (w/o saved NULL terminator) */\n#define LEGACY_INPUT_STRING_MAX 80\n\n\nstatic int cmp_robots(const void *dest, const void *src)\n{\n  struct robot *rsrc = *((struct robot **)src);\n  struct robot *rdest = *((struct robot **)dest);\n  return strcasecmp(rdest->robot_name, rsrc->robot_name);\n}\n\n/* Due to some 1.x RLE jank the dimensions need special handling. */\nstatic int load_RLE_dimensions(vfile *vf, int *w, int *h)\n{\n  /* Some streams may have a run of 0 appended, but not all of them?\n   * No known 1.xx worlds actually contain these; this comes from VER1TO2. */\n  int tmp = vfgetc(vf);\n  if(!tmp)\n  {\n    debug(\"Board plane ending in run of length 0 @ %\" PRId64 \"\\n\", vftell(vf) - 1);\n    vfgetc(vf);\n    *w = vfgetc(vf);\n  }\n  else\n    *w = tmp;\n\n  *h = vfgetc(vf);\n  return (*h < 0) ? -1 : 0;\n}\n\n/* Stream position should be after the dimensions. */\nstatic int load_RLE_plane(struct board *cur_board, char *plane, vfile *vf,\n int size, boolean check_size)\n{\n  int i, runsize, byte;\n\n  if(check_size)\n  {\n    int w, h;\n    if(load_RLE_dimensions(vf, &w, &h))\n      return -1;\n    if(w != cur_board->board_width || h != cur_board->board_height)\n      return -1;\n  }\n\n  for(i = 0; i < size;)\n  {\n    runsize = vfgetc(vf);\n    byte = vfgetc(vf);\n    if(byte < 0)\n      return -1;\n\n    if(i + runsize > size)\n      return -2;\n\n    memset(plane + i, byte, runsize);\n    i += runsize;\n  }\n  return 0;\n}\n\nstatic int load_RLE2_plane(struct board *cur_board, char *plane, vfile *vf,\n int size, boolean check_size)\n{\n  int i, runsize;\n  int current_char;\n\n  if(check_size)\n  {\n    int w = vfgetw(vf);\n    int h = vfgetw(vf);\n    if(h < 0 || w != cur_board->board_width || h != cur_board->board_height)\n      return -1;\n  }\n\n  for(i = 0; i < size;)\n  {\n    current_char = vfgetc(vf);\n    if(current_char < 0)\n      return -1;\n\n    if(~current_char & 0x80)\n    {\n      // Regular character\n      plane[i++] = current_char;\n    }\n    else\n    {\n      // A run\n      runsize = current_char & 0x7F;\n      if(i + runsize > size)\n        return -2;\n\n      current_char = vfgetc(vf);\n      if(current_char < 0)\n        return -1;\n\n      memset(plane + i, current_char, runsize);\n      i += runsize;\n    }\n  }\n\n  return 0;\n}\n\nint legacy_load_board_direct(struct world *mzx_world, struct board *cur_board,\n vfile *vf, int data_size, int savegame, int file_version)\n{\n  char input_string[ROBOT_MAX_TR];\n  int num_robots, num_scrolls, num_sensors, num_robots_active;\n  int overlay_mode, size, board_width, board_height, i;\n  int viewport_x, viewport_y, viewport_width, viewport_height;\n  int overlay_width = 0;\n  int overlay_height = 0;\n  boolean truncated = false;\n\n  struct robot *cur_robot;\n  struct scroll *cur_scroll;\n  struct sensor *cur_sensor;\n\n  int board_location = vftell(vf);\n\n  cur_board->num_robots = 0;\n  cur_board->num_robots_allocated = 0;\n  cur_board->num_robots_active = 0;\n  cur_board->num_scrolls = 0;\n  cur_board->num_scrolls_allocated = 0;\n  cur_board->num_sensors = 0;\n  cur_board->num_sensors_allocated = 0;\n  cur_board->robot_list = NULL;\n  cur_board->robot_list_name_sorted = NULL;\n  cur_board->sensor_list = NULL;\n  cur_board->scroll_list = NULL;\n\n  default_board_settings(mzx_world, cur_board);\n\n  if(file_version >= V200)\n  {\n    // board_mode, unused\n    if(vfgetc(vf) < 0)\n    {\n      error_message(E_WORLD_BOARD_MISSING, board_location, NULL);\n      return VAL_MISSING;\n    }\n\n    overlay_mode = vfgetc(vf);\n\n    if(!overlay_mode)\n    {\n      overlay_mode = vfgetc(vf);\n      overlay_width = vfgetw(vf);\n      overlay_height = vfgetw(vf);\n      cur_board->board_width = overlay_width;\n      cur_board->board_height = overlay_height;\n\n      size = overlay_width * overlay_height;\n\n      if(size < 1 || size > MAX_BOARD_SIZE)\n        goto err_invalid;\n\n      cur_board->overlay = (char *)cmalloc(size);\n      cur_board->overlay_color = (char *)cmalloc(size);\n\n      if(!cur_board->overlay || !cur_board->overlay_color)\n        goto err_freeoverlay;\n\n      if(load_RLE2_plane(cur_board, cur_board->overlay, vf, size, false))\n        goto err_freeoverlay;\n      if(load_RLE2_plane(cur_board, cur_board->overlay_color, vf, size, true))\n        goto err_freeoverlay;\n    }\n    else\n    {\n      overlay_mode = OVERLAY_OFF;\n      // Undo that last get\n      vfseek(vf, -1, SEEK_CUR);\n    }\n\n    cur_board->overlay_mode = overlay_mode;\n\n    board_width = vfgetw(vf);\n    board_height = vfgetw(vf);\n    cur_board->board_width = board_width;\n    cur_board->board_height = board_height;\n\n    size = board_width * board_height;\n\n    if(size < 1 || size > MAX_BOARD_SIZE)\n      goto err_freeoverlay;\n    if(overlay_mode && (board_width != overlay_width || board_height != overlay_height))\n      goto err_freeoverlay;\n\n    cur_board->level_id = (char *)cmalloc(size);\n    cur_board->level_color = (char *)cmalloc(size);\n    cur_board->level_param = (char *)cmalloc(size);\n    cur_board->level_under_id = (char *)cmalloc(size);\n    cur_board->level_under_color = (char *)cmalloc(size);\n    cur_board->level_under_param = (char *)cmalloc(size);\n\n    if(!cur_board->level_id || !cur_board->level_color ||\n     !cur_board->level_param || !cur_board->level_under_id ||\n     !cur_board->level_under_color || !cur_board->level_under_param)\n      goto err_freeboard;\n\n    if(load_RLE2_plane(cur_board, cur_board->level_id, vf, size, false))\n      goto err_freeboard;\n    if(load_RLE2_plane(cur_board, cur_board->level_color, vf, size, true))\n      goto err_freeboard;\n    if(load_RLE2_plane(cur_board, cur_board->level_param, vf, size, true))\n      goto err_freeboard;\n    if(load_RLE2_plane(cur_board, cur_board->level_under_id, vf, size, true))\n      goto err_freeboard;\n    if(load_RLE2_plane(cur_board, cur_board->level_under_color, vf, size, true))\n      goto err_freeboard;\n    if(load_RLE2_plane(cur_board, cur_board->level_under_param, vf, size, true))\n      goto err_freeboard;\n  }\n  else\n  {\n    /* 1.x has no overlay and a different packing format. */\n    overlay_mode = OVERLAY_OFF;\n    cur_board->overlay_mode = overlay_mode;\n\n    if(load_RLE_dimensions(vf, &board_width, &board_height))\n      goto err_invalid;\n\n    cur_board->board_width = board_width;\n    cur_board->board_height = board_height;\n\n    size = board_width * board_height;\n\n    if(size < 1 || size > MAX_BOARD_SIZE)\n      goto err_invalid;\n\n    cur_board->level_id = (char *)cmalloc(size);\n    cur_board->level_color = (char *)cmalloc(size);\n    cur_board->level_param = (char *)cmalloc(size);\n    cur_board->level_under_id = (char *)cmalloc(size);\n    cur_board->level_under_color = (char *)cmalloc(size);\n    cur_board->level_under_param = (char *)cmalloc(size);\n\n    if(!cur_board->level_id || !cur_board->level_color ||\n     !cur_board->level_param || !cur_board->level_under_id ||\n     !cur_board->level_under_color || !cur_board->level_under_param)\n      goto err_freeboard;\n\n    if(load_RLE_plane(cur_board, cur_board->level_id, vf, size, false))\n      goto err_freeboard;\n    if(load_RLE_plane(cur_board, cur_board->level_color, vf, size, true))\n      goto err_freeboard;\n    if(load_RLE_plane(cur_board, cur_board->level_param, vf, size, true))\n      goto err_freeboard;\n    if(load_RLE_plane(cur_board, cur_board->level_under_id, vf, size, true))\n      goto err_freeboard;\n    if(load_RLE_plane(cur_board, cur_board->level_under_color, vf, size, true))\n      goto err_freeboard;\n    if(load_RLE_plane(cur_board, cur_board->level_under_param, vf, size, true))\n      goto err_freeboard;\n  }\n\n  // Load board parameters\n\n  if(file_version < V283)\n  {\n    if(!vfread(cur_board->mod_playing, LEGACY_MOD_FILENAME_MAX, 1, vf))\n      cur_board->mod_playing[0] = 0;\n\n    cur_board->mod_playing[LEGACY_MOD_FILENAME_MAX] = 0;\n  }\n  else\n  {\n    size_t len = vfgetw(vf);\n    if(len >= MAX_PATH)\n      len = MAX_PATH - 1;\n\n    if(!vfread(cur_board->mod_playing, len, 1, vf))\n      len = 0;\n\n    cur_board->mod_playing[len] = 0;\n  }\n\n  viewport_x = vfgetc(vf);\n  viewport_y = vfgetc(vf);\n  viewport_width = vfgetc(vf);\n  viewport_height = vfgetc(vf);\n\n  if(\n   (viewport_x < 0) || (viewport_x > 79) ||\n   (viewport_y < 0) || (viewport_y > 24) ||\n   (viewport_width < 1) || (viewport_width > 80) ||\n   (viewport_height < 1) || (viewport_height > 25))\n    goto err_freeboard;\n\n  cur_board->viewport_x = viewport_x;\n  cur_board->viewport_y = viewport_y;\n  cur_board->viewport_width = viewport_width;\n  cur_board->viewport_height = viewport_height;\n  cur_board->can_shoot = vfgetc(vf);\n  cur_board->can_bomb = vfgetc(vf);\n  cur_board->fire_burn_brown = vfgetc(vf);\n  cur_board->fire_burn_space = vfgetc(vf);\n  cur_board->fire_burn_fakes = vfgetc(vf);\n  cur_board->fire_burn_trees = vfgetc(vf);\n  cur_board->explosions_leave = vfgetc(vf);\n  cur_board->save_mode = vfgetc(vf);\n  cur_board->forest_becomes = vfgetc(vf);\n  cur_board->collect_bombs = vfgetc(vf);\n  cur_board->fire_burns = vfgetc(vf);\n\n  for(i = 0; i < 4; i++)\n    cur_board->board_dir[i] = vfgetc(vf);\n\n  cur_board->restart_if_zapped = vfgetc(vf);\n  cur_board->time_limit = vfgetw(vf);\n\n  if(file_version < V283)\n  {\n    cur_board->last_key = vfgetc(vf);\n    cur_board->num_input = vfgetw(vf);\n    cur_board->input_size = vfgetc(vf);\n\n    if(file_version < V200)\n    {\n      cur_board->volume = vfgetc(vf);\n      cur_board->player_ns_locked = vfgetc(vf);\n      cur_board->player_ew_locked = vfgetc(vf);\n      cur_board->player_attack_locked = vfgetc(vf);\n    }\n\n    if(!vfread(input_string, LEGACY_INPUT_STRING_MAX + 1, 1, vf))\n      input_string[0] = 0;\n    input_string[LEGACY_INPUT_STRING_MAX] = 0;\n    board_set_input_string(cur_board, input_string, LEGACY_INPUT_STRING_MAX);\n\n    if(file_version < V200)\n    {\n      // Effects and unused junk in 1.x :(\n      unsigned char tmp[9];\n      memset(tmp, 0, sizeof(tmp));\n      vfread(tmp, 1, 9, vf);\n\n      cur_board->blind_dur_v1 = tmp[0];\n      cur_board->firewalker_dur_v1 = tmp[1];\n      cur_board->freeze_time_dur_v1 = tmp[3];\n      cur_board->slow_time_dur_v1 = tmp[4];\n      cur_board->wind_dur_v1 = tmp[8];\n    }\n\n    cur_board->player_last_dir = vfgetc(vf);\n\n    if(!vfread(cur_board->bottom_mesg, LEGACY_BOTTOM_MESG_MAX + 1, 1, vf))\n      cur_board->bottom_mesg[0] = 0;\n    cur_board->bottom_mesg[LEGACY_BOTTOM_MESG_MAX] = 0;\n\n    cur_board->b_mesg_timer = vfgetc(vf);\n    cur_board->lazwall_start = vfgetc(vf);\n    cur_board->b_mesg_row = vfgetc(vf);\n    cur_board->b_mesg_col = (signed char)vfgetc(vf);\n\n    if(file_version >= V200)\n    {\n      cur_board->scroll_x = (signed short)vfgetw(vf);\n      cur_board->scroll_y = (signed short)vfgetw(vf);\n      cur_board->locked_x = (signed short)vfgetw(vf);\n      cur_board->locked_y = (signed short)vfgetw(vf);\n    }\n    else\n    {\n      cur_board->scroll_x = (signed char)vfgetc(vf);\n      cur_board->scroll_y = (signed char)vfgetc(vf);\n\n      // World files have different values to indicate centered messages,\n      // typically -128. VER1TO2 fixes values <=0 and >=80. However, in save\n      // files 0 corresponds to column 0 (as with later versions).\n      if(cur_board->b_mesg_col < 0 || cur_board->b_mesg_col >= 80 ||\n       (!savegame && cur_board->b_mesg_col == 0))\n        cur_board->b_mesg_col = -1;\n    }\n  }\n  else\n\n  if(savegame)\n  {\n    size_t len;\n\n    cur_board->last_key = vfgetc(vf);\n    cur_board->num_input = vfgetw(vf);\n    cur_board->input_size = vfgetw(vf);\n\n    len = vfgetw(vf);\n    if(len >= ROBOT_MAX_TR)\n      len = ROBOT_MAX_TR - 1;\n\n    if(!vfread(input_string, len, 1, vf))\n      len = 0;\n    input_string[len] = 0;\n    board_set_input_string(cur_board, input_string, len);\n\n    cur_board->player_last_dir = vfgetc(vf);\n\n    len = vfgetw(vf);\n    if(len >= ROBOT_MAX_TR)\n      len = ROBOT_MAX_TR - 1;\n\n    if(!vfread(cur_board->bottom_mesg, len, 1, vf))\n      len = 0;\n    cur_board->bottom_mesg[len] = 0;\n\n    cur_board->b_mesg_timer = vfgetc(vf);\n    cur_board->lazwall_start = vfgetc(vf);\n    cur_board->b_mesg_row = vfgetc(vf);\n    cur_board->b_mesg_col = (signed char)vfgetc(vf);\n    cur_board->scroll_x = (signed short)vfgetw(vf);\n    cur_board->scroll_y = (signed short)vfgetw(vf);\n    cur_board->locked_x = (signed short)vfgetw(vf);\n    cur_board->locked_y = (signed short)vfgetw(vf);\n  }\n\n  if(file_version >= V200)\n  {\n    cur_board->player_ns_locked = vfgetc(vf);\n    cur_board->player_ew_locked = vfgetc(vf);\n    cur_board->player_attack_locked = vfgetc(vf);\n\n    if(file_version < V283 || savegame)\n    {\n      cur_board->volume = vfgetc(vf);\n      cur_board->volume_inc = vfgetc(vf);\n      cur_board->volume_target = vfgetc(vf);\n    }\n  }\n\n\n  /***************/\n  /* Load robots */\n  /***************/\n  num_robots = vfgetc(vf);\n  num_robots_active = 0;\n\n  if(num_robots == EOF)\n    truncated = true;\n\n  // EOF/crazy value check\n  if((num_robots < 0) || (num_robots > 255) || (num_robots > size))\n    goto board_scan;\n\n  cur_board->robot_list = ccalloc(num_robots + 1, sizeof(struct robot *));\n  // Also allocate for name sorted list\n  cur_board->robot_list_name_sorted =\n   ccalloc(num_robots, sizeof(struct robot *));\n\n  // Any null objects being placed will later be optimized out\n  set_error_suppression(E_WORLD_ROBOT_MISSING, 0);\n\n  if(num_robots)\n  {\n    for(i = 1; i <= num_robots; i++)\n    {\n      // Make sure there's robots to load here\n      if(truncated)\n        break;\n\n      cur_robot = legacy_load_robot_allocate(mzx_world, vf, savegame,\n       file_version, &truncated);\n\n      if(cur_robot->used)\n      {\n        cur_board->robot_list[i] = cur_robot;\n        cur_board->robot_list_name_sorted[num_robots_active] = cur_robot;\n        num_robots_active++;\n      }\n      else\n      {\n        clear_robot(cur_robot);\n        cur_board->robot_list[i] = NULL;\n      }\n    }\n  }\n\n  if(num_robots_active > 0)\n  {\n    if(num_robots_active != num_robots)\n    {\n      cur_board->robot_list_name_sorted =\n       crealloc(cur_board->robot_list_name_sorted,\n       sizeof(struct robot *) * num_robots_active);\n    }\n    qsort(cur_board->robot_list_name_sorted, num_robots_active,\n     sizeof(struct robot *), cmp_robots);\n  }\n  else\n  {\n    free(cur_board->robot_list_name_sorted);\n    cur_board->robot_list_name_sorted = NULL;\n  }\n\n  cur_board->num_robots = num_robots;\n  cur_board->num_robots_allocated = num_robots;\n  cur_board->num_robots_active = num_robots_active;\n\n\n  /****************/\n  /* Load scrolls */\n  /****************/\n  num_scrolls = vfgetc(vf);\n\n  if(num_scrolls == EOF)\n    truncated = 1;\n\n  if((num_scrolls < 0) || (num_scrolls > 255) || (num_robots + num_scrolls > size))\n    goto board_scan;\n\n  cur_board->scroll_list = ccalloc(num_scrolls + 1, sizeof(struct scroll *));\n\n  if(num_scrolls)\n  {\n    for(i = 1; i <= num_scrolls; i++)\n    {\n      cur_scroll = legacy_load_scroll_allocate(vf, file_version);\n      if(cur_scroll->used)\n        cur_board->scroll_list[i] = cur_scroll;\n      else\n        clear_scroll(cur_scroll);\n    }\n  }\n\n  cur_board->num_scrolls = num_scrolls;\n  cur_board->num_scrolls_allocated = num_scrolls;\n\n\n  /****************/\n  /* Load sensors */\n  /****************/\n  num_sensors = vfgetc(vf);\n\n  if(num_sensors == EOF)\n    truncated = 1;\n\n  if((num_sensors < 0) || (num_sensors > 255) ||\n   (num_scrolls + num_sensors + num_robots > size))\n    goto board_scan;\n\n  cur_board->sensor_list = ccalloc(num_sensors + 1, sizeof(struct sensor *));\n\n  if(num_sensors)\n  {\n    for(i = 1; i <= num_sensors; i++)\n    {\n      cur_sensor = legacy_load_sensor_allocate(vf, file_version);\n      if(cur_sensor->used)\n        cur_board->sensor_list[i] = cur_sensor;\n      else\n        clear_sensor(cur_sensor);\n    }\n  }\n\n  cur_board->num_sensors = num_sensors;\n  cur_board->num_sensors_allocated = num_sensors;\n\n\nboard_scan:\n  // Now do a board scan to make sure there aren't more than the data told us.\n  {\n    char *level_id = cur_board->level_id;\n    char *level_param = cur_board->level_param;\n    char *level_under_id = cur_board->level_under_id;\n    struct robot **robot_list = cur_board->robot_list;\n\n    int robot_count = 0, scroll_count = 0, sensor_count = 0;\n    char err_mesg[80] = { 0 };\n    unsigned char id;\n    unsigned char pr;\n\n    char found_robots[256] = {0};\n\n    for(i = 0; i < (board_width * board_height); i++)\n    {\n      id = level_id[i];\n      pr = level_param[i];\n\n      if(level_under_id[i] > 127)\n        level_under_id[i] = CUSTOM_FLOOR;\n\n      switch(id)\n      {\n        case ROBOT:\n        case ROBOT_PUSHABLE:\n        {\n          robot_count++;\n          if(!found_robots[pr] && pr <= num_robots && robot_list[pr])\n          {\n            found_robots[pr] = 1;\n            // Also fix the xpos/ypos values, which may have been saved\n            // incorrectly in ver1to2 worlds (see: Caverns of Zeux).\n            cur_robot = robot_list[pr];\n            cur_robot->xpos = i % board_width;\n            cur_robot->ypos = i / board_width;\n            cur_robot->compat_xpos = cur_robot->xpos;\n            cur_robot->compat_ypos = cur_robot->ypos;\n          }\n\n          else\n          {\n            cur_board->level_id[i] = CUSTOM_BLOCK;\n            cur_board->level_param[i] = 'R';\n            cur_board->level_color[i] = 0xCF;\n          }\n          break;\n        }\n        case SIGN:\n        case SCROLL:\n        {\n          scroll_count++;\n          if(scroll_count > cur_board->num_scrolls)\n          {\n            cur_board->level_id[i] = CUSTOM_BLOCK;\n            cur_board->level_param[i] = 'S';\n            cur_board->level_color[i] = 0xCF;\n          }\n          break;\n        }\n        case SENSOR:\n        {\n          // Wait, I forgot.  Nobody cares about sensors.\n          //sensor_count++;\n          if(sensor_count > cur_board->num_sensors)\n          {\n            cur_board->level_id[i] = CUSTOM_FLOOR;\n            cur_board->level_param[i] = 'S';\n            cur_board->level_color[i] = 0xDF;\n          }\n          break;\n        }\n        default:\n        {\n          if(id > 127)\n            level_id[i] = CUSTOM_BLOCK;\n          break;\n        }\n      }\n    }\n\n    // Perform a robot scan to make sure every robot is actually on the board.\n    // Some old worlds (e.g. Catacombs of Zeux, Demon Earth) contain useless\n    // robots that aren't. Silently mark the missing ones to not be saved.\n    for(i = 1; i <= num_robots; i++)\n    {\n      cur_robot = robot_list[i];\n\n      if(cur_robot && cur_robot->used)\n      {\n        if(!found_robots[i])\n        {\n          debug(\"Robot # %d was loaded but is missing on board @ %d; marked unused\\n\",\n           i, board_location);\n\n          cur_robot->used = 0;\n        }\n      }\n    }\n\n    if(robot_count > cur_board->num_robots)\n    {\n      snprintf(err_mesg, 80, \"found %i robots; expected %i\",\n       robot_count, cur_board->num_robots);\n\n      error_message(E_BOARD_SUMMARY, board_location, err_mesg);\n    }\n    if(scroll_count > cur_board->num_scrolls)\n    {\n      snprintf(err_mesg, 80, \"found %i scrolls/signs; expected %i\",\n       scroll_count, cur_board->num_scrolls);\n\n      error_message(E_BOARD_SUMMARY, board_location, err_mesg);\n    }\n    // This won't be reached but I'll leave it anyway.\n    if(sensor_count > cur_board->num_sensors)\n    {\n      snprintf(err_mesg, 80, \"found %i sensors; expected %i\",\n       sensor_count, cur_board->num_sensors);\n\n      error_message(E_BOARD_SUMMARY, board_location, err_mesg);\n    }\n    if(err_mesg[0])\n    {\n      error_message(E_BOARD_SUMMARY, board_location,\n       \"Any extra robots/scrolls/signs were replaced\");\n    }\n\n  }\n\n  if(truncated == 1)\n    error_message(E_WORLD_BOARD_TRUNCATED_SAFE, board_location, NULL);\n\n  return VAL_SUCCESS;\n\nerr_freeboard:\n  free(cur_board->level_id);\n  free(cur_board->level_color);\n  free(cur_board->level_param);\n  free(cur_board->level_under_id);\n  free(cur_board->level_under_color);\n  free(cur_board->level_under_param);\n\nerr_freeoverlay:\n  if(overlay_mode)\n  {\n    free(cur_board->overlay);\n    free(cur_board->overlay_color);\n  }\n\nerr_invalid:\n  error_message(E_WORLD_BOARD_CORRUPT, board_location, NULL);\n  return VAL_INVALID;\n}\n\n\nstruct board *legacy_load_board_allocate(struct world *mzx_world, vfile *vf,\n int data_offset, int data_size, int savegame, int file_version)\n{\n  struct board *cur_board;\n  enum val_result result;\n\n  // Skip deleted boards\n  if(!data_size)\n    return NULL;\n\n  // Should generally be at this position after reading the previous\n  // board, but don't count on that being true...\n  if(vftell(vf) != data_offset)\n  {\n    if(vfseek(vf, data_offset, SEEK_SET))\n    {\n      error_message(E_WORLD_BOARD_MISSING, data_offset, NULL);\n      return NULL;\n    }\n  }\n\n  cur_board = cmalloc(sizeof(struct board));\n  result = legacy_load_board_direct(mzx_world, cur_board, vf, data_size,\n   savegame, file_version);\n\n  if(result != VAL_SUCCESS)\n    dummy_board(mzx_world, cur_board);\n\n  return cur_board;\n}\n"
  },
  {
    "path": "src/legacy_board.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __LEGACY_BOARD_H\n#define __LEGACY_BOARD_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"const.h\"\n#include \"world_struct.h\"\n#include \"io/vfile.h\"\n\nCORE_LIBSPEC int legacy_load_board_direct(struct world *mzx_world,\n struct board *cur_board, vfile *vf, int data_size, int savegame, int version);\n\nCORE_LIBSPEC struct board *legacy_load_board_allocate(struct world *mzx_world,\n vfile *vf, int data_offset, int data_size, int savegame, int file_version);\n\n__M_END_DECLS\n\n#endif // __LEGACY_BOARD_H\n"
  },
  {
    "path": "src/legacy_rasm.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2012-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <ctype.h>\n#include <limits.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"rasm.h\"\n#include \"data.h\"\n#include \"util.h\"\n#include \"io/fsafeopen.h\"\n#include \"io/memfile.h\"\n#include \"io/vio.h\"\n\n#define IMM_U16            (1 << 0)\n#define IMM_S16            (1 << 0)\n#define CHARACTER          (1 << 2)\n#define COLOR              (1 << 3)\n#define DIRECTION          (1 << 4)\n#define THING              (1 << 5)\n#define PARAM              (1 << 6)\n#define STRING             (1 << 7)\n#define EQUALITY           (1 << 8)\n#define CONDITION          (1 << 9)\n#define ITEM               (1 << 10)\n#define EXTRA              (1 << 11)\n#define UNDEFINED          (1 << 13)\n#define EXTENDED           (1 << 14) /* Allow disassembly of char -> imm */\n\n#define ERR_BADCHARACTER   2\n#define ERR_INVALID        3\n\n// 1 << 31 generates compiler warnings.\n\n#define CMD                (1 << 30)\n#define CMD_NOT            CMD | 0\n#define CMD_ANY            CMD | 1\n#define CMD_PLAYER         CMD | 2\n#define CMD_NS             CMD | 3\n#define CMD_EW             CMD | 4\n#define CMD_ATTACK         CMD | 5\n#define CMD_ITEM           CMD | 6\n#define CMD_SELF           CMD | 7\n#define CMD_RANDOM         CMD | 8\n#define CMD_STRING         CMD | 9\n#define CMD_CHAR           CMD | 10\n#define CMD_ALL            CMD | 11\n#define CMD_EDIT           CMD | 12\n#define CMD_PUSHABLE       CMD | 13\n#define CMD_NONPUSHABLE    CMD | 14\n#define CMD_LAVAWALKER     CMD | 15\n#define CMD_NONLAVAWALKER  CMD | 16\n#define CMD_ROW            CMD | 17\n#define CMD_COUNTERS       CMD | 18\n#define CMD_ID             CMD | 19\n#define CMD_MOD            CMD | 20\n#define CMD_ORDER          CMD | 21\n#define CMD_THICK          CMD | 22\n#define CMD_ARROW          CMD | 23\n#define CMD_THIN           CMD | 24\n#define CMD_MAXHEALTH      CMD | 25\n#define CMD_POSITION       CMD | 26\n#define CMD_MESG           CMD | 27\n#define CMD_COLUMN         CMD | 28\n#define CMD_COLOR          CMD | 29\n#define CMD_SIZE           CMD | 30\n#define CMD_BULLETN        CMD | 31\n#define CMD_BULLETS        CMD | 32\n#define CMD_BULLETE        CMD | 33\n#define CMD_BULLETW        CMD | 34\n#define CMD_BULLETCOLOR    CMD | 35\n#define CMD_FIRST          CMD | 36\n#define CMD_LAST           CMD | 37\n#define CMD_FADE           CMD | 38\n#define CMD_OUT            CMD | 39\n#define CMD_IN             CMD | 40\n#define CMD_BLOCK          CMD | 41\n#define CMD_SFX            CMD | 42\n#define CMD_INTENSITY      CMD | 43\n#define CMD_SET            CMD | 44\n#define CMD_PALETTE        CMD | 45\n#define CMD_WORLD          CMD | 46\n#define CMD_ALIGNEDROBOT   CMD | 47\n#define CMD_GO             CMD | 48\n#define CMD_SAVING         CMD | 49\n#define CMD_SENSORONLY     CMD | 50\n#define CMD_ON             CMD | 51\n#define CMD_STATIC         CMD | 52\n#define CMD_TRANSPARENT    CMD | 53\n#define CMD_OVERLAY        CMD | 54\n#define CMD_START          CMD | 55\n#define CMD_LOOP           CMD | 56\n#define CMD_EDGE           CMD | 57\n#define CMD_SAM            CMD | 58\n#define CMD_PLAY           CMD | 59\n#define CMD_PERCENT        CMD | 60\n#define CMD_HIGH           CMD | 61\n#define CMD_MATCHES        CMD | 62\n#define CMD_NONE           CMD | 63\n#define CMD_INPUT          CMD | 64\n#define CMD_COUNTER        CMD | 66\n#define CMD_DUPLICATE      CMD | 67\n#define CMD_NO             CMD | 68\n\n#define IGNORE_TYPE             (1 << 29)\n#define IGNORE_TYPE_A           IGNORE_TYPE | 2\n#define IGNORE_TYPE_AN          IGNORE_TYPE | 3\n#define IGNORE_TYPE_AND         IGNORE_TYPE | 4\n#define IGNORE_TYPE_AS          IGNORE_TYPE | 5\n#define IGNORE_TYPE_AT          IGNORE_TYPE | 6\n#define IGNORE_TYPE_BY          IGNORE_TYPE | 7\n#define IGNORE_TYPE_ELSE        IGNORE_TYPE | 8\n#define IGNORE_TYPE_FOR         IGNORE_TYPE | 9\n#define IGNORE_TYPE_FROM        IGNORE_TYPE | 10\n#define IGNORE_TYPE_IS          IGNORE_TYPE | 12\n#define IGNORE_TYPE_OF          IGNORE_TYPE | 13\n#define IGNORE_TYPE_THE         IGNORE_TYPE | 14\n#define IGNORE_TYPE_THEN        IGNORE_TYPE | 15\n#define IGNORE_TYPE_TO          IGNORE_TYPE | 19\n#define IGNORE_TYPE_WITH        IGNORE_TYPE | 20\n\nstruct mzx_command\n{\n  const char *const name;\n  int parameters;\n  const int *const param_types;\n};\n\nstruct mzx_command_rw\n{\n  char *name;\n  int parameters;\n  int *param_types;\n};\n\nstatic const int cm2[]   = { IGNORE_TYPE_FOR, IMM_U16 | STRING };\nstatic const int cm3[]   = { IMM_U16 | STRING };\nstatic const int cm4[]   = { DIRECTION, IGNORE_TYPE_FOR, IMM_U16 | STRING };\nstatic const int cm5[]   = { DIRECTION };\nstatic const int cm6[]   = { COLOR | STRING, THING, PARAM | STRING };\nstatic const int cm7[]   = { CHARACTER | STRING | IMM_U16 };\nstatic const int cm8[]   = { COLOR | STRING };\nstatic const int cm9[]   = { IMM_S16 | STRING, IMM_S16 | STRING };\nstatic const int cm10[]  = { STRING, IGNORE_TYPE_TO, IMM_U16 | STRING };\nstatic const int cm11[]  = { STRING, IGNORE_TYPE_BY, IMM_U16 | STRING };\nstatic const int cm12[]  = { STRING, IGNORE_TYPE_BY, IMM_U16 | STRING };\nstatic const int cm13[]  = { STRING, IGNORE_TYPE_TO, STRING };\nstatic const int cm14[]  = { STRING, IGNORE_TYPE_BY, STRING };\nstatic const int cm15[]  = { STRING, IGNORE_TYPE_BY, STRING };\nstatic const int cm16[]  = { STRING, EQUALITY, IMM_U16 | STRING,\n IGNORE_TYPE_THEN, STRING };\nstatic const int cm17[]  = { STRING, EQUALITY, STRING, IGNORE_TYPE_THEN,\n STRING };\nstatic const int cm18[]  = { CONDITION, IGNORE_TYPE_THEN, STRING };\nstatic const int cm19[]  = { CMD_NOT, CONDITION, IGNORE_TYPE_THEN, STRING };\nstatic const int cm20[]  = { CMD_ANY, COLOR | STRING, THING, PARAM | STRING,\n IGNORE_TYPE_THEN, STRING };\nstatic const int cm21[]  = { CMD_NO, COLOR | STRING, THING, PARAM | STRING,\n IGNORE_TYPE_THEN, STRING };\nstatic const int cm22[]  = { COLOR | STRING, THING, PARAM | STRING,\n IGNORE_TYPE_AT, DIRECTION, IGNORE_TYPE_THEN, STRING };\nstatic const int cm23[]  = { CMD_NOT, COLOR | STRING, THING, PARAM | STRING,\n IGNORE_TYPE_AT, DIRECTION, IGNORE_TYPE_THEN, STRING };\nstatic const int cm24[]  = { COLOR | STRING, THING, PARAM | STRING,\n IGNORE_TYPE_AT, IMM_S16 | STRING, IMM_S16 | STRING, IGNORE_TYPE_THEN, STRING };\nstatic const int cm25[]  = { IGNORE_TYPE_AT, IMM_S16 | STRING, IMM_S16 | STRING,\n IGNORE_TYPE_THEN, STRING };\nstatic const int cm26[]  = { IGNORE_TYPE_AT, DIRECTION, IGNORE_TYPE_OF,\n CMD_PLAYER, IGNORE_TYPE_IS, COLOR | STRING, THING, PARAM | STRING,\n IGNORE_TYPE_THEN, STRING };\nstatic const int cm27[]  = { STRING };\nstatic const int cm28[]  = { STRING };\nstatic const int cm29[]  = { STRING };\nstatic const int cm30[]  = { STRING, IGNORE_TYPE_TO, STRING };\nstatic const int cm31[]  = { IMM_U16 | STRING };\nstatic const int cm32[]  = { COLOR | STRING, THING, PARAM | STRING,\n IGNORE_TYPE_TO, DIRECTION };\nstatic const int cm33[]  = { IMM_U16 | STRING, ITEM };\nstatic const int cm34[]  = { IMM_U16 | STRING, ITEM };\nstatic const int cm35[]  = { IMM_U16 | STRING, ITEM, IGNORE_TYPE_ELSE, STRING };\nstatic const int cm38[]  = { STRING };\nstatic const int cm39[]  = { IMM_U16 | STRING, STRING };\nstatic const int cm40[]  = { IMM_U16 | STRING };\nstatic const int cm41[]  = { CMD_MOD };\nstatic const int cm42[]  = { CMD_SAM };\nstatic const int cm43[]  = { STRING };\nstatic const int cm44[]  = { CMD_PLAY };\nstatic const int cm45[]  = { CMD_PLAY, STRING };\nstatic const int cm46[]  = { CMD_PLAY };\nstatic const int cm48[]  = { IMM_U16 | STRING };\nstatic const int cm49[]  = { CMD_SFX, STRING };\nstatic const int cm50[]  = { IGNORE_TYPE_AT, DIRECTION };\nstatic const int cm53[]  = { IGNORE_TYPE_AT, DIRECTION, IGNORE_TYPE_TO,\n STRING };\nstatic const int cm54[]  = { STRING, IMM_U16 | STRING };\nstatic const int cm55[]  = { STRING, IMM_U16 | STRING };\nstatic const int cm58[]  = { CMD_NS };\nstatic const int cm59[]  = { CMD_EW };\nstatic const int cm60[]  = { CMD_ATTACK };\nstatic const int cm61[]  = { CMD_PLAYER, IGNORE_TYPE_TO, DIRECTION };\nstatic const int cm62[]  = { CMD_PLAYER, IGNORE_TYPE_TO, DIRECTION,\n IGNORE_TYPE_ELSE, STRING };\nstatic const int cm63[]  = { CMD_PLAYER, IGNORE_TYPE_AT, IMM_S16 | STRING,\n IMM_S16 | STRING };\nstatic const int cm66[]  = { CMD_PLAYER, IGNORE_TYPE_AT, IMM_S16 | STRING,\n IMM_S16 | STRING, STRING };\nstatic const int cm67[]  = { CMD_PLAYER, DIRECTION };\nstatic const int cm68[]  = { DIRECTION, IGNORE_TYPE_ELSE, STRING };\nstatic const int cm71[]  = { DIRECTION, IGNORE_TYPE_WITH, DIRECTION };\nstatic const int cm72[]  = { IGNORE_TYPE_TO, DIRECTION };\nstatic const int cm73[]  = { IGNORE_TYPE_TO, DIRECTION };\nstatic const int cm74[]  = { CMD_HIGH, IGNORE_TYPE_TO, DIRECTION };\nstatic const int cm75[]  = { IGNORE_TYPE_TO, DIRECTION };\nstatic const int cm76[]  = { IGNORE_TYPE_TO, DIRECTION };\nstatic const int cm77[]  = { IGNORE_TYPE_TO, DIRECTION };\nstatic const int cm78[]  = { IGNORE_TYPE_TO, DIRECTION, IGNORE_TYPE_FOR,\n IMM_U16 | STRING };\nstatic const int cm79[]  = { COLOR | STRING, THING, PARAM | STRING,\n IGNORE_TYPE_AT, IMM_S16 | STRING, IMM_S16 | STRING };\nstatic const int cm80[]  = { IGNORE_TYPE_AS, IGNORE_TYPE_AN, CMD_ITEM };\nstatic const int cm81[]  = { IGNORE_TYPE_AT, IMM_S16 | STRING, IMM_S16 | STRING,\n IGNORE_TYPE_TO, STRING };\nstatic const int cm82[]  = { STRING };\nstatic const int cm83[]  = { IGNORE_TYPE_AT, IMM_S16 | STRING,\n IMM_S16 | STRING };\nstatic const int cm84[]  = { IGNORE_TYPE_FROM, DIRECTION };\nstatic const int cm85[]  = { CMD_SELF, IGNORE_TYPE_TO, DIRECTION };\nstatic const int cm86[]  = { CMD_SELF, IGNORE_TYPE_AT, IMM_S16 | STRING,\n IMM_S16 | STRING };\nstatic const int cm87[]  = { IGNORE_TYPE_IS, CHARACTER | STRING | IMM_U16 };\nstatic const int cm88[]  = { IGNORE_TYPE_IS, CHARACTER | STRING | IMM_U16 };\nstatic const int cm89[]  = { IGNORE_TYPE_IS, CHARACTER | STRING | IMM_U16 };\nstatic const int cm90[]  = { IGNORE_TYPE_IS, CHARACTER | STRING | IMM_U16 };\nstatic const int cm91[]  = { COLOR | STRING };\nstatic const int cm92[]  = { COLOR | STRING, IGNORE_TYPE_ELSE, STRING };\nstatic const int cm93[]  = { COLOR | STRING };\nstatic const int cm94[]  = { COLOR | STRING, IGNORE_TYPE_ELSE, STRING };\nstatic const int cm95[]  = { STRING, IGNORE_TYPE_BY, CMD_RANDOM,\n IMM_U16 | STRING, IGNORE_TYPE_TO, IMM_U16 | STRING };\nstatic const int cm96[]  = { STRING, IGNORE_TYPE_BY, CMD_RANDOM,\n IMM_U16 | STRING, IGNORE_TYPE_TO, IMM_U16 | STRING };\nstatic const int cm97[]  = { STRING, IGNORE_TYPE_TO, CMD_RANDOM,\n IMM_U16 | STRING, IGNORE_TYPE_TO, IMM_U16 | STRING };\nstatic const int cm98[]  = { IMM_U16 | STRING, ITEM, IGNORE_TYPE_FOR,\n IMM_U16 | STRING, ITEM, IGNORE_TYPE_ELSE, STRING };\nstatic const int cm99[]  = { IGNORE_TYPE_AT, DIRECTION, IGNORE_TYPE_OF,\n CMD_PLAYER, IGNORE_TYPE_TO, STRING };\nstatic const int cm100[] = { COLOR | STRING, THING, PARAM | STRING,\n IGNORE_TYPE_TO, DIRECTION, IGNORE_TYPE_OF, CMD_PLAYER };\nstatic const int cm101[] = { STRING };\nstatic const int cm102[] = { STRING };\nstatic const int cm103[] = { STRING };\nstatic const int cm104[] = { STRING, STRING };\nstatic const int cm105[] = { STRING, STRING, STRING };\nstatic const int cm106[] = { STRING };\nstatic const int cm107[] = { STRING };\nstatic const int cm108[] = { STRING };\nstatic const int cm109[] = { CMD_PLAYER, IGNORE_TYPE_TO, STRING,\n IGNORE_TYPE_AT, IMM_S16 | STRING, IMM_S16 | STRING };\nstatic const int cm110[] = { IGNORE_TYPE_TO, DIRECTION, IGNORE_TYPE_FOR,\n IMM_U16 | STRING };\nstatic const int cm111[] = { CMD_STRING, STRING };\nstatic const int cm112[] = { CMD_STRING, IGNORE_TYPE_IS, STRING,\n IGNORE_TYPE_THEN, STRING };\nstatic const int cm113[] = { CMD_STRING, IGNORE_TYPE_IS, CMD_NOT, STRING,\n IGNORE_TYPE_THEN, STRING };\nstatic const int cm114[] = { CMD_STRING, CMD_MATCHES, STRING, IGNORE_TYPE_THEN,\n STRING };\nstatic const int cm115[] = { CMD_CHAR, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm116[] = { STRING };\nstatic const int cm117[] = { STRING };\nstatic const int cm118[] = { CMD_ALL, COLOR | STRING, THING, PARAM | STRING,\n IGNORE_TYPE_TO, DIRECTION };\nstatic const int cm119[] = { IGNORE_TYPE_AT, IMM_S16 | STRING, IMM_S16 | STRING,\n IGNORE_TYPE_TO, IMM_S16 | STRING, IMM_S16 | STRING };\nstatic const int cm120[] = { CMD_EDGE, CMD_COLOR, IGNORE_TYPE_TO, COLOR | STRING };\nstatic const int cm121[] = { IGNORE_TYPE_TO, IGNORE_TYPE_THE, DIRECTION,\n IGNORE_TYPE_IS, STRING };\nstatic const int cm122[] = { IGNORE_TYPE_TO, IGNORE_TYPE_THE, DIRECTION,\n CMD_NONE };\nstatic const int cm123[] = { CMD_EDIT, CHARACTER | STRING | IMM_U16 | EXTENDED,\n IGNORE_TYPE_TO,\n IMM_U16 | STRING, IMM_U16 | STRING, IMM_U16 | STRING, IMM_U16 | STRING,\n IMM_U16 | STRING, IMM_U16 | STRING, IMM_U16 | STRING, IMM_U16 | STRING,\n IMM_U16 | STRING, IMM_U16 | STRING, IMM_U16 | STRING, IMM_U16 | STRING,\n IMM_U16 | STRING, IMM_U16 | STRING };\nstatic const int cm124[] = { CMD_PUSHABLE };\nstatic const int cm125[] = { CMD_NONPUSHABLE };\nstatic const int cm126[] = { IGNORE_TYPE_FOR, IMM_U16 | STRING };\nstatic const int cm127[] = { IGNORE_TYPE_FOR, IMM_U16 | STRING };\nstatic const int cm128[] = { IGNORE_TYPE_FOR, IMM_U16 | STRING };\nstatic const int cm129[] = { IGNORE_TYPE_FOR, IMM_U16 | STRING };\nstatic const int cm130[] = { IGNORE_TYPE_FOR, IMM_U16 | STRING };\nstatic const int cm132[] = { IGNORE_TYPE_FROM, DIRECTION, IGNORE_TYPE_TO,\n DIRECTION };\nstatic const int cm133[] = { IGNORE_TYPE_A, CMD_LAVAWALKER };\nstatic const int cm134[] = { IGNORE_TYPE_A, CMD_NONLAVAWALKER };\nstatic const int cm135[] = { IGNORE_TYPE_FROM, COLOR | STRING, THING,\n PARAM | STRING, IGNORE_TYPE_TO, COLOR | STRING, THING, PARAM | STRING};\nstatic const int cm136[] = { IGNORE_TYPE_IS, COLOR | STRING};\nstatic const int cm137[] = { IGNORE_TYPE_IS, COLOR | STRING };\nstatic const int cm138[] = { IGNORE_TYPE_IS, COLOR | STRING };\nstatic const int cm139[] = { CMD_ROW, IGNORE_TYPE_IS, IMM_U16 | STRING };\nstatic const int cm140[] = { IGNORE_TYPE_TO, CMD_SELF };\nstatic const int cm141[] = { IGNORE_TYPE_TO, CMD_PLAYER };\nstatic const int cm142[] = { IGNORE_TYPE_TO, CMD_COUNTERS };\nstatic const int cm143[] = { CMD_CHAR, CMD_ID, IMM_U16 | STRING,\n IGNORE_TYPE_TO, CHARACTER | STRING | IMM_U16 };\nstatic const int cm144[] = { IGNORE_TYPE_TO, CMD_MOD, CMD_ORDER,\n IMM_U16 | STRING };\nstatic const int cm145[] = { STRING };\nstatic const int cm147[] = { CMD_THICK, CMD_ARROW, CMD_CHAR, DIRECTION,\n IGNORE_TYPE_TO, CHARACTER | STRING | IMM_U16 };\nstatic const int cm148[] = { CMD_THIN, CMD_ARROW, CMD_CHAR, DIRECTION,\n IGNORE_TYPE_TO, CHARACTER | STRING | IMM_U16 };\nstatic const int cm149[] = { CMD_MAXHEALTH, IMM_U16 | STRING };\nstatic const int cm150[] = { CMD_PLAYER, CMD_POSITION };\nstatic const int cm151[] = { CMD_PLAYER, CMD_POSITION };\nstatic const int cm152[] = { CMD_PLAYER, CMD_POSITION };\nstatic const int cm153[] = { CMD_MESG, CMD_COLUMN, IGNORE_TYPE_TO,\n IMM_U16 | STRING };\nstatic const int cm154[] = { CMD_MESG };\nstatic const int cm155[] = { CMD_MESG };\nstatic const int cm157[] = { CMD_SAM, IMM_U16 | STRING, IMM_U16 | STRING };\nstatic const int cm158[] = { STRING };\nstatic const int cm159[] = { CMD_COLOR, IGNORE_TYPE_IS, COLOR | STRING };\nstatic const int cm160[] = { CMD_COLOR, IGNORE_TYPE_IS, COLOR | STRING };\nstatic const int cm161[] = { CMD_COLOR, IGNORE_TYPE_IS, COLOR | STRING };\nstatic const int cm162[] = { CMD_COLOR, IGNORE_TYPE_IS, COLOR | STRING };\nstatic const int cm163[] = { CMD_COLOR, IGNORE_TYPE_IS, COLOR | STRING };\nstatic const int cm164[] = { IGNORE_TYPE_IS, IGNORE_TYPE_AT, IMM_U16 | STRING,\n IMM_U16 | STRING };\nstatic const int cm165[] = { CMD_SIZE, IGNORE_TYPE_IS, IMM_U16 | STRING,\n IGNORE_TYPE_BY, IMM_U16 | STRING };\nstatic const int cm166[] = { CMD_MESG, CMD_COLUMN, IGNORE_TYPE_TO, STRING };\nstatic const int cm167[] = { CMD_ROW, IGNORE_TYPE_IS, STRING };\nstatic const int cm168[] = { CMD_PLAYER, CMD_POSITION, IGNORE_TYPE_TO,\n IMM_U16 | STRING };\nstatic const int cm169[] = { CMD_PLAYER, CMD_POSITION, IGNORE_TYPE_FROM,\n IMM_U16 | STRING };\nstatic const int cm170[] = { CMD_PLAYER, CMD_POSITION, IGNORE_TYPE_WITH,\n IMM_U16 | STRING };\nstatic const int cm171[] = { CMD_PLAYER, CMD_POSITION, IGNORE_TYPE_FROM,\n IMM_U16 | STRING, IGNORE_TYPE_AND, CMD_DUPLICATE, CMD_SELF };\nstatic const int cm172[] = { CMD_PLAYER, CMD_POSITION, IGNORE_TYPE_WITH,\n IMM_U16 | STRING, IGNORE_TYPE_AND, CMD_DUPLICATE, CMD_SELF };\nstatic const int cm173[] = { CMD_BULLETN, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm174[] = { CMD_BULLETS, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm175[] = { CMD_BULLETE, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm176[] = { CMD_BULLETW, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm177[] = { CMD_BULLETN, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm178[] = { CMD_BULLETS, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm179[] = { CMD_BULLETE, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm180[] = { CMD_BULLETW, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm181[] = { CMD_BULLETN, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm182[] = { CMD_BULLETS, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm183[] = { CMD_BULLETE, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm184[] = { CMD_BULLETW, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm185[] = { CMD_BULLETCOLOR, IGNORE_TYPE_IS,\n COLOR | STRING };\nstatic const int cm186[] = { CMD_BULLETCOLOR, IGNORE_TYPE_IS,\n COLOR | STRING };\nstatic const int cm187[] = { CMD_BULLETCOLOR, IGNORE_TYPE_IS,\n COLOR | STRING };\nstatic const int cm193[] = { CMD_SELF, CMD_FIRST };\nstatic const int cm194[] = { CMD_SELF, CMD_LAST };\nstatic const int cm195[] = { CMD_PLAYER, CMD_FIRST };\nstatic const int cm196[] = { CMD_PLAYER, CMD_LAST };\nstatic const int cm197[] = { CMD_COUNTERS, CMD_FIRST };\nstatic const int cm198[] = { CMD_COUNTERS, CMD_LAST };\nstatic const int cm199[] = { CMD_FADE, CMD_OUT };\nstatic const int cm200[] = { CMD_FADE, CMD_IN, STRING };\nstatic const int cm201[] = { CMD_BLOCK, IGNORE_TYPE_AT, IMM_S16 | STRING,\n IMM_S16 | STRING, IGNORE_TYPE_FOR, IMM_U16 | STRING, IGNORE_TYPE_BY,\n IMM_U16 | STRING, IGNORE_TYPE_TO, IMM_S16 | STRING, IMM_S16 | STRING };\nstatic const int cm202[] = { CMD_INPUT };\nstatic const int cm203[] = { IGNORE_TYPE_TO, DIRECTION };\nstatic const int cm204[] = { CMD_CHAR, CHARACTER | STRING | IMM_U16 | EXTENDED,\n DIRECTION };\nstatic const int cm205[] = { CMD_CHAR, CHARACTER | STRING | IMM_U16 | EXTENDED,\n DIRECTION };\nstatic const int cm206[] = { CMD_CHAR, CHARACTER | STRING | IMM_U16 | EXTENDED,\n IGNORE_TYPE_TO, CHARACTER | STRING | IMM_U16 | EXTENDED };\nstatic const int cm210[] = { CMD_SFX, IMM_U16 | STRING, IGNORE_TYPE_TO,\n STRING };\nstatic const int cm211[] = { CMD_INTENSITY, IGNORE_TYPE_IS, IGNORE_TYPE_AT,\n IMM_U16 | STRING, CMD_PERCENT };\nstatic const int cm212[] = { CMD_INTENSITY, IMM_U16 | STRING, IGNORE_TYPE_IS,\n IGNORE_TYPE_AT, IMM_U16 | STRING, CMD_PERCENT };\nstatic const int cm213[] = { CMD_FADE, CMD_OUT };\nstatic const int cm214[] = { CMD_FADE, CMD_IN };\nstatic const int cm215[] = { CMD_COLOR, IMM_U16 | STRING, IGNORE_TYPE_TO,\n IMM_U16 | STRING, IMM_U16 | STRING, IMM_U16 | STRING };\nstatic const int cm216[] = { CMD_CHAR, CMD_SET, STRING };\nstatic const int cm217[] = { STRING, IGNORE_TYPE_BY, IMM_U16 | STRING };\nstatic const int cm218[] = { STRING, IGNORE_TYPE_BY, IMM_U16 | STRING };\nstatic const int cm219[] = { STRING, IGNORE_TYPE_BY, IMM_U16 | STRING };\nstatic const int cm220[] = { CMD_CHAR, DIRECTION, IGNORE_TYPE_IS,\n CHARACTER | STRING | IMM_U16 };\nstatic const int cm222[] = { CMD_PALETTE, STRING };\nstatic const int cm224[] = { CMD_FADE, IGNORE_TYPE_TO, IMM_U16 | STRING,\n IGNORE_TYPE_BY, IMM_U16 | STRING };\nstatic const int cm225[] = { CMD_POSITION, IMM_S16 | STRING,\n IMM_S16 | STRING };\nstatic const int cm226[] = { CMD_WORLD, STRING };\nstatic const int cm227[] = { CMD_ALIGNEDROBOT, IGNORE_TYPE_WITH, STRING,\n IGNORE_TYPE_THEN, STRING };\nstatic const int cm231[] = { CMD_FIRST, CMD_STRING, IGNORE_TYPE_IS, STRING,\n IGNORE_TYPE_THEN, STRING };\nstatic const int cm232[] = { CMD_GO, STRING };\nstatic const int cm233[] = { IGNORE_TYPE_FOR, CMD_MOD, CMD_FADE };\nstatic const int cm235[] = { CMD_SAVING };\nstatic const int cm236[] = { CMD_SAVING };\nstatic const int cm237[] = { CMD_SENSORONLY, CMD_SAVING };\nstatic const int cm238[] = { CMD_COUNTER, IMM_U16 | STRING, IGNORE_TYPE_IS,\n STRING };\nstatic const int cm239[] = { IGNORE_TYPE_IS, CMD_ON };\nstatic const int cm240[] = { IGNORE_TYPE_IS, CMD_STATIC };\nstatic const int cm241[] = { IGNORE_TYPE_IS, CMD_TRANSPARENT };\nstatic const int cm242[] = { COLOR | STRING, CHARACTER | STRING | IMM_U16,\n CMD_OVERLAY, IGNORE_TYPE_TO, IMM_S16 | STRING, IMM_S16 | STRING };\nstatic const int cm243[] = { CMD_OVERLAY, CMD_BLOCK, IGNORE_TYPE_AT,\n IMM_S16 | STRING, IMM_S16 | STRING, IGNORE_TYPE_FOR, IMM_U16 | STRING,\n IGNORE_TYPE_BY, IMM_U16 | STRING, IGNORE_TYPE_TO, IMM_S16 | STRING,\n IMM_S16 | STRING };\nstatic const int cm245[] = { CMD_OVERLAY, COLOR | STRING,\n CHARACTER | STRING | IMM_U16, IGNORE_TYPE_TO,\n COLOR | STRING, CHARACTER | STRING | IMM_U16 };\nstatic const int cm246[] = { CMD_OVERLAY, COLOR | STRING, IGNORE_TYPE_TO,\n COLOR | STRING };\nstatic const int cm247[] = { CMD_OVERLAY, COLOR | STRING, STRING,\n IGNORE_TYPE_AT, IMM_S16 | STRING, IMM_S16 | STRING };\nstatic const int cm251[] = { CMD_START };\nstatic const int cm252[] = { IGNORE_TYPE_FOR, IMM_U16 | STRING };\nstatic const int cm253[] = { CMD_LOOP };\nstatic const int cm254[] = { CMD_MESG, CMD_EDGE };\nstatic const int cm255[] = { CMD_MESG, CMD_EDGE };\n\nstatic const struct mzx_command command_list[] =\n{\n  { \"end\",            0, NULL },\n  { \"die\",            0, NULL },\n  { \"wait\",           1, cm2 },\n  { \"cycle\",          1, cm3 },\n  { \"go\",             2, cm4 },\n  { \"walk\",           1, cm5 },\n  { \"become\",         3, cm6 },\n  { \"char\",           1, cm7 },\n  { \"color\",          1, cm8 },\n  { \"gotoxy\",         2, cm9 },\n  { \"set\",            2, cm10 },\n  { \"inc\",            2, cm11 },\n  { \"dec\",            2, cm12 },\n  { \"set\",            2, cm13 },\n  { \"inc\",            2, cm14 },\n  { \"dec\",            2, cm15 },\n  { \"if\",             4, cm16 },\n  { \"if\",             4, cm17 },\n  { \"if\",             2, cm18 },\n  { \"if\",             3, cm19 },\n  { \"if\",             5, cm20 },\n  { \"if\",             5, cm21 },\n  { \"if\",             5, cm22 },\n  { \"if\",             6, cm23 },\n  { \"if\",             6, cm24 },\n  { \"if\",             3, cm25 },\n  { \"if\",             6, cm26 },\n  { \"double\",         1, cm27 },\n  { \"half\",           1, cm28 },\n  { \"goto\",           1, cm29 },\n  { \"send\",           2, cm30 },\n  { \"explode\",        1, cm31 },\n  { \"put\",            4, cm32 },\n  { \"give\",           2, cm33 },\n  { \"take\",           2, cm34 },\n  { \"take\",           3, cm35 },\n  { \"endgame\",        0, NULL },\n  { \"endlife\",        0, NULL },\n  { \"mod\",            1, cm38 },\n  { \"sam\",            2, cm39 },\n  { \"volume\",         1, cm40 },\n  { \"end\",            1, cm41 },\n  { \"end\",            1, cm42 },\n  { \"play\",           1, cm43 },\n  { \"end\",            1, cm44 },\n  { \"wait\",           2, cm45 },\n  { \"wait\",           1, cm46 },\n  { \"_blank_line\",    0, NULL },\n  { \"sfx\",            1, cm48 },\n  { \"play\",           2, cm49 },\n  { \"open\",           1, cm50 },\n  { \"lockself\",       0, NULL },\n  { \"unlockself\",     0, NULL },\n  { \"send\",           2, cm53 },\n  { \"zap\",            2, cm54 },\n  { \"restore\",        2, cm55 },\n  { \"lockplayer\",     0, NULL },\n  { \"unlockplayer\",   0, NULL },\n  { \"lockplayer\",     1, cm58 },\n  { \"lockplayer\",     1, cm59 },\n  { \"lockplayer\",     1, cm60 },\n  { \"move\",           2, cm61 },\n  { \"move\",           3, cm62 },\n  { \"put\",            3, cm63 },\n  { \"__unused\",       0, NULL },\n  { \"__unused\",       0, NULL },\n  { \"if\",             4, cm66 },\n  { \"put\",            2, cm67 },\n  { \"try\",            2, cm68 },\n  { \"rotatecw\",       0, NULL },\n  { \"rotateccw\",      0, NULL },\n  { \"switch\",         2, cm71 },\n  { \"shoot\",          1, cm72 },\n  { \"laybomb\",        1, cm73 },\n  { \"laybomb\",        2, cm74 },\n  { \"shootmissile\",   1, cm75 },\n  { \"shootseeker\",    1, cm76 },\n  { \"spitfire\",       1, cm77 },\n  { \"lazerwall\",      2, cm78 },\n  { \"put\",            5, cm79 },\n  { \"die\",            1, cm80 },\n  { \"send\",           3, cm81 },\n  { \"copyrobot\",      1, cm82 },\n  { \"copyrobot\",      2, cm83 },\n  { \"copyrobot\",      1, cm84 },\n  { \"duplicate\",      2, cm85 },\n  { \"duplicate\",      3, cm86 },\n  { \"bulletn\",        1, cm87 },\n  { \"bullets\",        1, cm88 },\n  { \"bullete\",        1, cm89 },\n  { \"bulletw\",        1, cm90 },\n  { \"givekey\",        1, cm91 },\n  { \"givekey\",        2, cm92 },\n  { \"takekey\",        1, cm93 },\n  { \"takekey\",        2, cm94 },\n  { \"inc\",            4, cm95 },\n  { \"dec\",            4, cm96 },\n  { \"set\",            4, cm97 },\n  { \"trade\",          5, cm98 },\n  { \"send\",           3, cm99 },\n  { \"put\",            5, cm100 },\n  { \"/\",              1, cm101 },\n  { \"*\",              1, cm102 },\n  { \"[\",              1, cm103 },\n  { \"?\",              2, cm104 },\n  { \"?\",              3, cm105 },\n  { \":\",              1, cm106 },\n  { \".\",              1, cm107 },\n  { \"|\",              1, cm108 },\n  { \"teleport\",       4, cm109 },\n  { \"scrollview\",     2, cm110 },\n  { \"input\",          2, cm111 },\n  { \"if\",             3, cm112 },\n  { \"if\",             4, cm113 },\n  { \"if\",             4, cm114 },\n  { \"player\",         2, cm115 },\n  { \"%\",              1, cm116 },\n  { \"&\",              1, cm117 },\n  { \"move\",           5, cm118 },\n  { \"copy\",           4, cm119 },\n  { \"set\",            3, cm120 },\n  { \"board\",          2, cm121 },\n  { \"board\",          2, cm122 },\n  { \"char\",           16, cm123 },\n  { \"become\",         1, cm124 },\n  { \"become\",         1, cm125 },\n  { \"blind\",          1, cm126 },\n  { \"firewalker\",     1, cm127 },\n  { \"freezetime\",     1, cm128 },\n  { \"slowtime\",       1, cm129 },\n  { \"wind\",           1, cm130 },\n  { \"avalanche\",      0, NULL },\n  { \"copy\",           2, cm132 },\n  { \"become\",         1, cm133 },\n  { \"become\",         1, cm134 },\n  { \"change\",         6, cm135 },\n  { \"playercolor\",    1, cm136 },\n  { \"bulletcolor\",    1, cm137 },\n  { \"missilecolor\",   1, cm138 },\n  { \"message\",        2, cm139 },\n  { \"rel\",            1, cm140 },\n  { \"rel\",            1, cm141 },\n  { \"rel\",            1, cm142 },\n  { \"change\",         4, cm143 },\n  { \"jump\",           3, cm144 },\n  { \"ask\",            1, cm145 },\n  { \"fillhealth\",     0, NULL },\n  { \"change\",         5, cm147 },\n  { \"change\",         5, cm148 },\n  { \"set\",            2, cm149 },\n  { \"save\",           2, cm150 },\n  { \"restore\",        2, cm151 },\n  { \"exchange\",       2, cm152 },\n  { \"set\",            3, cm153 },\n  { \"center\",         1, cm154 },\n  { \"clear\",          1, cm155 },\n  { \"resetview\",      0, NULL },\n  { \"mod\",            3, cm157 },\n  { \"volume\",         1, cm158 },\n  { \"scrollbase\",     2, cm159 },\n  { \"scrollcorner\",   2, cm160 },\n  { \"scrolltitle\",    2, cm161 },\n  { \"scrollpointer\",  2, cm162 },\n  { \"scrollarrow\",    2, cm163 },\n  { \"viewport\",       2, cm164 },\n  { \"viewport\",       3, cm165 },\n  { \"set\",            3, cm166 },\n  { \"message\",        2, cm167 },\n  { \"save\",           3, cm168 },\n  { \"restore\",        3, cm169 },\n  { \"exchange\",       3, cm170 },\n  { \"restore\",        5, cm171 },\n  { \"exchange\",       5, cm172 },\n  { \"player\",         2, cm173 },\n  { \"player\",         2, cm174 },\n  { \"player\",         2, cm175 },\n  { \"player\",         2, cm176 },\n  { \"neutral\",        2, cm177 },\n  { \"neutral\",        2, cm178 },\n  { \"neutral\",        2, cm179 },\n  { \"neutral\",        2, cm180 },\n  { \"enemy\",          2, cm181 },\n  { \"enemy\",          2, cm182 },\n  { \"enemy\",          2, cm183 },\n  { \"enemy\",          2, cm184 },\n  { \"player\",         2, cm185 },\n  { \"neutral\",        2, cm186 },\n  { \"enemy\",          2, cm187 },\n  { \"__unused\",       0, NULL },\n  { \"__unused\",       0, NULL },\n  { \"__unused\",       0, NULL },\n  { \"__unused\",       0, NULL },\n  { \"__unused\",       0, NULL },\n  { \"rel\",            2, cm193 },\n  { \"rel\",            2, cm194 },\n  { \"rel\",            2, cm195 },\n  { \"rel\",            2, cm196 },\n  { \"rel\",            2, cm197 },\n  { \"rel\",            2, cm198 },\n  { \"mod\",            2, cm199 },\n  { \"mod\",            3, cm200 },\n  { \"copy\",           7, cm201 },\n  { \"clip\",           1, cm202 },\n  { \"push\",           1, cm203 },\n  { \"scroll\",         3, cm204 },\n  { \"flip\",           3, cm205 },\n  { \"copy\",           3, cm206 },\n  { \"__unused\",       0, NULL },\n  { \"__unused\",       0, NULL },\n  { \"__unused\",       0, NULL },\n  { \"change\",         3, cm210 },\n  { \"color\",          3, cm211 },\n  { \"color\",          4, cm212 },\n  { \"color\",          2, cm213 },\n  { \"color\",          2, cm214 },\n  { \"set\",            5, cm215 },\n  { \"load\",           3, cm216 },\n  { \"multiply\",       2, cm217 },\n  { \"divide\",         2, cm218 },\n  { \"modulo\",         2, cm219 },\n  { \"player\",         3, cm220 },\n  { \"__unused\",       0, NULL },\n  { \"load\",           2, cm222 },\n  { \"__unused\",       0, NULL },\n  { \"mod\",            3, cm224 },\n  { \"scrollview\",     3, cm225 },\n  { \"swap\",           2, cm226 },\n  { \"if\",             3, cm227 },\n  { \"__unused\",       0, NULL },\n  { \"lockscroll\",     0, NULL },\n  { \"unlockscroll\",   0, NULL },\n  { \"if\",             4, cm231 },\n  { \"persistent\",     2, cm232 },\n  { \"wait\",           2, cm233 },\n  { \"__unused\",       0, NULL },\n  { \"enable\",         1, cm235 },\n  { \"disable\",        1, cm236 },\n  { \"enable\",         2, cm237 },\n  { \"status\",         3, cm238 },\n  { \"overlay\",        1, cm239 },\n  { \"overlay\",        1, cm240 },\n  { \"overlay\",        1, cm241 },\n  { \"put\",            5, cm242 },\n  { \"copy\",           8, cm243 },\n  { \"__unused\",       0, NULL },\n  { \"change\",         5, cm245 },\n  { \"change\",         3, cm246 },\n  { \"write\",          5, cm247 },\n  { \"__unused\",       0, NULL },\n  { \"__unused\",       0, NULL },\n  { \"__unused\",       0, NULL },\n  { \"loop\",           1, cm251 },\n  { \"loop\",           1, cm252 },\n  { \"abort\",          1, cm253 },\n  { \"disable\",        2, cm254 },\n  { \"enable\",         2, cm255 }\n};\n\nstatic const char *const command_fragments[69] =\n{\n  \"not\",\n  \"any\",\n  \"player\",\n  \"ns\",\n  \"ew\",\n  \"attack\",\n  \"item\",\n  \"self\",\n  \"random\",\n  \"string\",\n  \"char\",\n  \"all\",\n  \"edit\",\n  \"pushable\",\n  \"nonpushable\",\n  \"lavawalker\",\n  \"nonlavawalker\",\n  \"row\",\n  \"counters\",\n  \"id\",\n  \"mod\",\n  \"order\",\n  \"thick\",\n  \"arrow\",\n  \"thin\",\n  \"maxhealth\",\n  \"position\",\n  \"mesg\",\n  \"column\",\n  \"color\",\n  \"size\",\n  \"bulletn\",\n  \"bullets\",\n  \"bullete\",\n  \"bulletw\",\n  \"bulletcolor\",\n  \"first\",\n  \"last\",\n  \"fade\",\n  \"out\",\n  \"in\",\n  \"block\",\n  \"sfx\",\n  \"intensity\",\n  \"set\",\n  \"palette\",\n  \"world\",\n  \"alignedrobot\",\n  \"go\",\n  \"saving\",\n  \"sensoronly\",\n  \"on\",\n  \"static\",\n  \"transparent\",\n  \"overlay\",\n  \"start\",\n  \"loop\",\n  \"edge\",\n  \"sam\",\n  \"play\",\n  \"percent\",\n  \"high\",\n  \"matches\",\n  \"none\",\n  \"input\",\n  \"dir\",\n  \"counter\",\n  \"duplicate\",\n  \"no\"\n};\n\n/*\n  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F\n*/\nconst char special_first_char[256] =\n{\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 0x00-0x0F\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 0x10-0x1F\n  0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1,   // 0x20-0x2F\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,   // 0x30-0x3F\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 0x40-0x4F\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,   // 0x50-0x5F\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 0x60-0x6F\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,   // 0x70-0x7F\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 0x80-0x8F\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 0x90-0x9F\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 0xA0-0xAF\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 0xB0-0xBF\n  0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,   // 0xC0-0xCF\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 0xD0-0xDF\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 0xE0-0xEF\n  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0    // 0xF0-0xFF\n};\n\nstatic const struct search_entry_short sorted_argument_list[] =\n{\n  { \"!=\",               5,   S_EQUALITY  },\n  { \",\",                0,   S_EXTRA     },\n  { \";\",                1,   S_EXTRA     },\n  { \"<\",                1,   S_EQUALITY  },\n  { \"<=\",               4,   S_EQUALITY  },\n  { \"<>\",               5,   S_EQUALITY  },\n  { \"=\",                0,   S_EQUALITY  },\n  { \"=<\",               4,   S_EQUALITY  },\n  { \"==\",               0,   S_EQUALITY  },\n  { \"===\",              6,   S_EQUALITY  },\n  { \"=>\",               3,   S_EQUALITY  },\n  { \">\",                2,   S_EQUALITY  },\n  { \"><\",               5,   S_EQUALITY  },\n  { \">=\",               3,   S_EQUALITY  },\n  { \"?=\",               7,   S_EQUALITY  },\n  { \"?==\",              8,   S_EQUALITY  },\n  { \"a\",                2,   S_EXTRA     },\n  { \"aligned\",          5,   S_CONDITION },\n  { \"alignedew\",        7,   S_CONDITION },\n  { \"alignedns\",        6,   S_CONDITION },\n  { \"alignedrobot\",     47,  S_CMD       },\n  { \"all\",              11,  S_CMD       },\n  { \"ammo\",             35,  S_THING     },\n  { \"ammos\",            1,   S_ITEM      },\n  { \"an\",               3,   S_EXTRA     },\n  { \"and\",              4,   S_EXTRA     },\n  { \"any\",              1,   S_CMD       },\n  { \"anydir\",           12,  S_DIR       },\n  { \"arrow\",            23,  S_CMD       },\n  { \"as\",               5,   S_EXTRA     },\n  { \"at\",               6,   S_EXTRA     },\n  { \"attack\",           5,   S_CMD       },\n  { \"bear\",             94,  S_THING     },\n  { \"bearcub\",          95,  S_THING     },\n  { \"beneath\",          11,  S_DIR       },\n  { \"block\",            41,  S_CMD       },\n  { \"blocked\",          4,   S_CONDITION },\n  { \"bomb\",             36,  S_THING     },\n  { \"boulder\",          8,   S_THING     },\n  { \"box\",              11,  S_THING     },\n  { \"breakaway\",        6,   S_THING     },\n  { \"bullet\",           61,  S_THING     },\n  { \"bulletcolor\",      35,  S_CMD       },\n  { \"bullete\",          33,  S_CMD       },\n  { \"bulletgun\",        92,  S_THING     },\n  { \"bulletn\",          31,  S_CMD       },\n  { \"bullets\",          32,  S_CMD       },\n  { \"bulletw\",          34,  S_CMD       },\n  { \"by\",               7,   S_EXTRA     },\n  { \"carpet\",           14,  S_THING     },\n  { \"cave\",             44,  S_THING     },\n  { \"ccwrotate\",        46,  S_THING     },\n  { \"char\",             10,  S_CMD       },\n  { \"chest\",            27,  S_THING     },\n  { \"coin\",             50,  S_THING     },\n  { \"coins\",            8,   S_ITEM      },\n  { \"color\",            29,  S_CMD       },\n  { \"column\",           28,  S_CMD       },\n  { \"counter\",          66,  S_CMD       },\n  { \"counters\",         18,  S_CMD       },\n  { \"crate\",            9,   S_THING     },\n  { \"customblock\",      5,   S_THING     },\n  { \"custombox\",        12,  S_THING     },\n  { \"custombreak\",      7,   S_THING     },\n  { \"customfloor\",      17,  S_THING     },\n  { \"customhurt\",       76,  S_THING     },\n  { \"custompush\",       10,  S_THING     },\n  { \"cw\",               17,  S_DIR       },\n  { \"cwrotate\",         45,  S_THING     },\n  { \"delpressed\",       15,  S_CONDITION },\n  { \"dir\",              65,  S_CMD       },\n  { \"door\",             41,  S_THING     },\n  { \"down\",             2,   S_DIR       },\n  { \"downpressed\",      13,  S_CONDITION },\n  { \"dragon\",           86,  S_THING     },\n  { \"duplicate\",        67,  S_CMD       },\n  { \"e\",                3,   S_DIR       },\n  { \"east\",             3,   S_DIR       },\n  { \"edge\",             57,  S_CMD       },\n  { \"edit\",             12,  S_CMD       },\n  { \"else\",             8,   S_EXTRA     },\n  { \"emovingwall\",      53,  S_THING     },\n  { \"energizer\",        33,  S_THING     },\n  { \"ew\",               4,   S_CMD       },\n  { \"ewater\",           23,  S_THING     },\n  { \"explosion\",        38,  S_THING     },\n  { \"eye\",              81,  S_THING     },\n  { \"fade\",             38,  S_CMD       },\n  { \"fake\",             13,  S_THING     },\n  { \"fire\",             63,  S_THING     },\n  { \"firewalking\",      2,   S_CONDITION },\n  { \"first\",            36,  S_CMD       },\n  { \"fish\",             87,  S_THING     },\n  { \"floor\",            15,  S_THING     },\n  { \"flow\",             13,  S_DIR       },\n  { \"for\",              9,   S_EXTRA     },\n  { \"forest\",           65,  S_THING     },\n  { \"from\",             10,  S_EXTRA     },\n  { \"gate\",             47,  S_THING     },\n  { \"gem\",              28,  S_THING     },\n  { \"gems\",             0,   S_ITEM      },\n  { \"ghost\",            85,  S_THING     },\n  { \"go\",               48,  S_CMD       },\n  { \"goblin\",           90,  S_THING     },\n  { \"goop\",             34,  S_THING     },\n  { \"health\",           30,  S_THING     },\n  { \"healths\",          4,   S_ITEM      },\n  { \"hibombs\",          7,   S_ITEM      },\n  { \"high\",             61,  S_CMD       },\n  { \"ice\",              25,  S_THING     },\n  { \"id\",               19,  S_CMD       },\n  { \"idle\",             0,   S_DIR       },\n  { \"image_file\",       100, S_THING     },\n  { \"in\",               40,  S_CMD       },\n  { \"input\",            64,  S_CMD       },\n  { \"intensity\",        43,  S_CMD       },\n  { \"into\",             11,  S_EXTRA     },\n  { \"inviswall\",        71,  S_THING     },\n  { \"is\",               12,  S_EXTRA     },\n  { \"item\",             6,   S_CMD       },\n  { \"key\",              39,  S_THING     },\n  { \"last\",             37,  S_CMD       },\n  { \"lastshot\",         8,   S_CONDITION },\n  { \"lasttouch\",        9,   S_CONDITION },\n  { \"lava\",             26,  S_THING     },\n  { \"lavawalker\",       15,  S_CMD       },\n  { \"lazer\",            59,  S_THING     },\n  { \"lazergun\",         60,  S_THING     },\n  { \"left\",             4,   S_DIR       },\n  { \"leftpressed\",      11,  S_CONDITION },\n  { \"life\",             66,  S_THING     },\n  { \"line\",             4,   S_THING     },\n  { \"litbomb\",          37,  S_THING     },\n  { \"lives\",            5,   S_ITEM      },\n  { \"lobombs\",          6,   S_ITEM      },\n  { \"lock\",             40,  S_THING     },\n  { \"loop\",             56,  S_CMD       },\n  { \"magicgem\",         29,  S_THING     },\n  { \"matches\",          62,  S_CMD       },\n  { \"maxhealth\",        25,  S_CMD       },\n  { \"mesg\",             27,  S_CMD       },\n  { \"mine\",             74,  S_THING     },\n  { \"missile\",          62,  S_THING     },\n  { \"missilegun\",       97,  S_THING     },\n  { \"mod\",              20,  S_CMD       },\n  { \"musicon\",          16,  S_CONDITION },\n  { \"n\",                1,   S_DIR       },\n  { \"nmovingwall\",      51,  S_THING     },\n  { \"no\",               68,  S_CMD       },\n  { \"nodir\",            14,  S_DIR       },\n  { \"none\",             63,  S_CMD       },\n  { \"nonlavawalker\",    16,  S_CMD       },\n  { \"nonpushable\",      14,  S_CMD       },\n  { \"normal\",           1,   S_THING     },\n  { \"north\",            1,   S_DIR       },\n  { \"not\",              0,   S_CMD       },\n  { \"ns\",               3,   S_CMD       },\n  { \"nwater\",           21,  S_THING     },\n  { \"of\",               13,  S_EXTRA     },\n  { \"on\",               51,  S_CMD       },\n  { \"opendoor\",         42,  S_THING     },\n  { \"opengate\",         48,  S_THING     },\n  { \"opp\",              18,  S_DIR       },\n  { \"order\",            21,  S_CMD       },\n  { \"out\",              39,  S_CMD       },\n  { \"overlay\",          54,  S_CMD       },\n  { \"palette\",          45,  S_CMD       },\n  { \"pcsfxon\",          17,  S_CONDITION },\n  { \"percent\",          60,  S_CMD       },\n  { \"play\",             59,  S_CMD       },\n  { \"player\",           2,   S_CMD       },\n  { \"position\",         26,  S_CMD       },\n  { \"potion\",           32,  S_THING     },\n  { \"pouch\",            55,  S_THING     },\n  { \"pushable\",         13,  S_CMD       },\n  { \"pushablerobot\",    123, S_THING     },\n  { \"pusher\",           56,  S_THING     },\n  { \"randany\",          10,  S_DIR       },\n  { \"randb\",            15,  S_DIR       },\n  { \"randew\",           6,   S_DIR       },\n  { \"randnb\",           8,   S_DIR       },\n  { \"randne\",           7,   S_DIR       },\n  { \"randnot\",          19,  S_DIR       },\n  { \"randns\",           5,   S_DIR       },\n  { \"random\",           8,   S_CMD       },\n  { \"randp\",            16,  S_DIR       },\n  { \"ricochet\",         73,  S_THING     },\n  { \"ricochetpanel\",    72,  S_THING     },\n  { \"right\",            3,   S_DIR       },\n  { \"rightpressed\",     10,  S_CONDITION },\n  { \"ring\",             31,  S_THING     },\n  { \"robot\",            124, S_THING     },\n  { \"row\",              17,  S_CMD       },\n  { \"runner\",           84,  S_THING     },\n  { \"s\",                2,   S_DIR       },\n  { \"sam\",              58,  S_CMD       },\n  { \"saving\",           49,  S_CMD       },\n  { \"score\",            3,   S_ITEM      },\n  { \"scroll\",           126, S_THING     },\n  { \"seek\",             9,   S_DIR       },\n  { \"seeker\",           79,  S_THING     },\n  { \"self\",             7,   S_CMD       },\n  { \"sensor\",           122, S_THING     },\n  { \"sensoronly\",       50,  S_CMD       },\n  { \"set\",              44,  S_CMD       },\n  { \"sfx\",              42,  S_CMD       },\n  { \"shark\",            88,  S_THING     },\n  { \"shootingfire\",     78,  S_THING     },\n  { \"sign\",             125, S_THING     },\n  { \"size\",             30,  S_CMD       },\n  { \"sliderew\",         58,  S_THING     },\n  { \"sliderns\",         57,  S_THING     },\n  { \"slimeblob\",        83,  S_THING     },\n  { \"smovingwall\",      52,  S_THING     },\n  { \"snake\",            80,  S_THING     },\n  { \"solid\",            2,   S_THING     },\n  { \"south\",            2,   S_DIR       },\n  { \"space\",            0,   S_THING     },\n  { \"spacepressed\",     14,  S_CONDITION },\n  { \"spider\",           89,  S_THING     },\n  { \"spike\",            75,  S_THING     },\n  { \"spinninggun\",      93,  S_THING     },\n  { \"spittingtiger\",    91,  S_THING     },\n  { \"sprite\",           98,  S_THING     },\n  { \"sprite_colliding\", 99,  S_THING     },\n  { \"stairs\",           43,  S_THING     },\n  { \"start\",            55,  S_CMD       },\n  { \"static\",           52,  S_CMD       },\n  { \"stillwater\",       20,  S_THING     },\n  { \"string\",           9,   S_CMD       },\n  { \"swater\",           22,  S_THING     },\n  { \"swimming\",         1,   S_CONDITION },\n  { \"text\",             77,  S_THING     },\n  { \"the\",              14,  S_EXTRA     },\n  { \"then\",             15,  S_EXTRA     },\n  { \"there\",            16,  S_EXTRA     },\n  { \"thick\",            22,  S_CMD       },\n  { \"thickweb\",         19,  S_THING     },\n  { \"thief\",            82,  S_THING     },\n  { \"thin\",             24,  S_CMD       },\n  { \"through\",          17,  S_EXTRA     },\n  { \"thru\",             18,  S_EXTRA     },\n  { \"tiles\",            16,  S_THING     },\n  { \"time\",             2,   S_ITEM      },\n  { \"to\",               19,  S_EXTRA     },\n  { \"touching\",         3,   S_CONDITION },\n  { \"transparent\",      53,  S_CMD       },\n  { \"transport\",        49,  S_THING     },\n  { \"tree\",             3,   S_THING     },\n  { \"under\",            11,  S_DIR       },\n  { \"up\",               1,   S_DIR       },\n  { \"uppressed\",        12,  S_CONDITION },\n  { \"w\",                4,   S_DIR       },\n  { \"walking\",          0,   S_CONDITION },\n  { \"web\",              18,  S_THING     },\n  { \"west\",             4,   S_DIR       },\n  { \"whirlpool\",        67,  S_THING     },\n  { \"whirlpool2\",       68,  S_THING     },\n  { \"whirlpool3\",       69,  S_THING     },\n  { \"whirlpool4\",       70,  S_THING     },\n  { \"with\",             20,  S_EXTRA     },\n  { \"wmovingwall\",      54,  S_THING     },\n  { \"world\",            46,  S_CMD       },\n  { \"wwater\",           24,  S_THING     }\n};\n\nstatic const int num_argument_names =\n sizeof(sorted_argument_list) / sizeof(struct search_entry_short);\n\nstatic int escape_chars(char *dest, char *src)\n{\n  if(src[0] == '\\\\')\n  {\n    switch(src[1])\n    {\n      case '\"':\n        *dest = '\"';\n        return 2;\n\n      case '0':\n        *dest = 0;\n        return 2;\n\n      case 'n':\n        *dest = '\\n';\n        return 2;\n\n      case 'r':\n        *dest = '\\r';\n        return 2;\n\n      case 't':\n        *dest = '\\t';\n        return 2;\n\n      case '\\\\':\n        *dest = '\\\\';\n        return 2;\n    }\n  }\n\n  *dest = *src;\n  return 1;\n}\n\nstatic int get_param(char *cmd_line)\n{\n  if((cmd_line[1] == '?') && (cmd_line[2] == '?'))\n  {\n    return 256;\n  }\n\n  return strtol(cmd_line + 1, NULL, 16);\n}\n\nstatic int is_color(char *cmd_line)\n{\n  if( (cmd_line[0] == 'c') &&\n  ( (isxdigit((int)cmd_line[1]) && (cmd_line[2] == '?'))  ||\n    (isxdigit((int)cmd_line[2]) && (cmd_line[1] == '?'))  ||\n    (isxdigit((int)cmd_line[1]) && isxdigit((int)cmd_line[2])) ||\n    ((cmd_line[1] == '?') && (cmd_line[2] == '?')) ) )\n  {\n    return 1;\n  }\n  else\n  {\n    return 0;\n  }\n}\n\nstatic int is_param(char *cmd_line)\n{\n  if((cmd_line[0] == 'p') &&\n   ((isxdigit((int)cmd_line[1]) && isxdigit((int)cmd_line[2])) ||\n   ((cmd_line[1] == '?') && (cmd_line[2] == '?'))))\n  {\n    return 1;\n  }\n  else\n  {\n    return 0;\n  }\n}\n\n#ifdef CONFIG_DEBYTECODE\nstatic\n#else\n__editor_maybe_static\n#endif\nconst struct search_entry_short *find_argument(char *name)\n{\n  const struct search_entry_short *base = sorted_argument_list;\n  int bottom = 0, top = num_argument_names - 1, middle;\n  int cmpval;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    cmpval = strcasecmp(name, (sorted_argument_list[middle]).name);\n\n    if(cmpval > 0)\n      bottom = middle + 1;\n    else\n\n    if(cmpval < 0)\n      top = middle - 1;\n    else\n      return base + middle;\n  }\n\n  return NULL;\n}\n\nint get_color(char *cmd_line)\n{\n  if(cmd_line[1] == '?')\n  {\n    if(cmd_line[2] == '?')\n    {\n      return 0x100 + 0x10 + 0x10;\n    }\n    else\n    {\n      return strtol(cmd_line + 2, NULL, 16) | 0x100;\n    }\n  }\n\n  if(cmd_line[2] == '?')\n  {\n    char temp[2];\n    temp[0] = cmd_line[1];\n    temp[1] = 0;\n    return (strtol(temp, NULL, 16) + 0x10) | 0x100;\n  }\n\n  return strtol(cmd_line + 1, NULL, 16);\n}\n\nstatic int rasm_parse_argument(char *cmd_line, char **next,\n int *arg_translated, int *error, int *arg_short)\n{\n  char current = *cmd_line;\n  char *space_position;\n  const struct search_entry_short *matched_argument;\n\n  *error = 0;\n\n  switch(current)\n  {\n    case '\"':\n    {\n      // If the first character is a quote it'll be a string.\n      // It either has to be terminated with a \" or a null terminator.\n      // \\'s can escape quotes.\n      do\n      {\n        cmd_line++;\n        current = *cmd_line;\n        if((current == '\\\\') && ((cmd_line[1] == '\"') ||\n         (cmd_line[1] == '\\\\')))\n        {\n          cmd_line++;\n        }\n\n      } while((current != '\"') && current);\n\n      *arg_translated = 0;\n      if(current)\n        *next = cmd_line + 1;\n      else\n        *next = cmd_line;\n\n      *arg_short = S_STRING;\n      return STRING;\n    }\n\n    case '-':\n    case '0':\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9':\n    {\n      // If the first character is a negative sign or a numerical constant,\n      // it's an immediate number. I can't figure out the difference between\n      // IMM_U16 and IMM_S16 to Robotic so I'm giving IMM_U16 for now.\n\n      *arg_translated = strtol(cmd_line, next, 10);\n      *arg_short = IMM_U16;\n      return IMM_U16;\n    }\n\n    case '\\'':\n    {\n      // If the first letter is a single quote then it's a character;\n      // make sure there's only one constant there and a close quote.\n      char c;\n      int n = escape_chars(&c, cmd_line + 1);\n\n      if(cmd_line[n + 1] != '\\'')\n      {\n        *error = ERR_BADCHARACTER;\n        return -1;\n      }\n\n      *arg_translated = (int)c;\n      *next = cmd_line + n + 2;\n      *arg_short = S_CHARACTER;\n      return CHARACTER;\n    }\n\n    // Is it a color?\n    case 'c':\n    {\n      if(is_color(cmd_line))\n      {\n        *arg_translated = (int)get_color(cmd_line);\n        *next = cmd_line + 3;\n        *arg_short = S_COLOR;\n        return COLOR;\n      }\n      break;\n    }\n\n    case 'p':\n    {\n      // Is it a parameter?\n      if(is_param(cmd_line))\n      {\n        *arg_translated = (int)get_param(cmd_line);\n        *next = cmd_line + 3;\n        *arg_short = S_PARAM;\n        return PARAM;\n      }\n      break;\n    }\n\n\n    case '$':\n    {\n      // This is a hex immediate\n      *arg_translated = strtol(cmd_line + 1, next, 16);\n      *arg_short = IMM_U16;\n      return IMM_U16;\n    }\n  }\n\n  // It's most likely an unquoted character\n  if(!isprint((int)current))\n  {\n    //Seriously, who would name a counter a smiley face, anyways?\n    if(isspace((int)cmd_line[1]) || !cmd_line[1])\n    {\n      *arg_translated = current;\n      *next = cmd_line + 1;\n      *arg_short = S_CHARACTER;\n      return CHARACTER;\n    }\n  }\n\n  // Otherwise, it's a word matched parameter\n\n  space_position = cmd_line;\n\n  do\n  {\n    space_position++;\n    current = *space_position;\n  } while((current != ' ') && (current) && (current != '\\n'));\n\n  *space_position = 0;\n  matched_argument = find_argument(cmd_line);\n  *space_position = current;\n\n  *next = space_position;\n\n  if(matched_argument)\n  {\n    int arg_type = matched_argument->type;\n    *arg_short = arg_type;\n    *arg_translated = matched_argument->offset;\n\n    if(arg_type == S_CMD)\n    {\n      return (CMD | matched_argument->offset);\n    }\n    else\n    {\n      return (1 << matched_argument->type);\n    }\n  }\n\n  *arg_short = S_UNDEFINED;\n  *arg_translated = 0;\n  *error = ERR_INVALID;\n  return UNDEFINED;\n}\n\nstatic int get_word(char *dest, size_t left, char *source, char t)\n{\n  size_t i = 0;\n  char current;\n\n  current = *source;\n\n  while((current != t) && (current != 0) && (current != '\\n') && (i + 1 < left))\n  {\n    source += escape_chars(dest + i, source);\n    current = *source;\n\n    i++;\n  }\n\n  dest[i] = 0;\n\n  return i;\n}\n\nstatic const struct search_entry sorted_command_list[] =\n{\n  { \"%\",              1, { 116 } },\n  { \"&\",              1, { 117 } },\n  { \"*\",              1, { 102 } },\n  { \".\",              1, { 107 } },\n  { \"/\",              1, { 101 } },\n  { \":\",              1, { 106 } },\n  { \"?\",              2, { 104, 105 } },\n  { \"[\",              1, { 103 } },\n  { \"abort\",          1, { 253 } },\n  { \"ask\",            1, { 145 } },\n  { \"avalanche\",      1, { 131 } },\n  { \"become\",         5, { 124, 125, 133, 134, 6 } },\n  { \"blind\",          1, { 126 } },\n  { \"board\",          2, { 121, 122 } },\n  { \"bulletcolor\",    1, { 137 } },\n  { \"bullete\",        1, { 89 } },\n  { \"bulletn\",        1, { 87 } },\n  { \"bullets\",        1, { 88 } },\n  { \"bulletw\",        1, { 90 } },\n  { \"center\",         1, { 154 } },\n  { \"change\",         7, { 135, 143, 147, 148, 210, 245, 246 } },\n  { \"char\",           2, { 123, 7 } },\n  { \"clear\",          1, { 155 } },\n  { \"clip\",           1, { 202 } },\n  { \"color\",          5, { 211, 212, 213, 214, 8 } },\n  { \"copy\",           5, { 119, 132, 201, 206, 243 } },\n  { \"copyrobot\",      3, { 82, 83, 84 } },\n  { \"cycle\",          1, { 3 } },\n  { \"dec\",            3, { 12, 15, 96 } },\n  { \"die\",            2, { 1, 80 } },\n  { \"disable\",        2, { 236, 254 } },\n  { \"divide\",         1, { 218 } },\n  { \"double\",         1, { 27 } },\n  { \"duplicate\",      2, { 85, 86 } },\n  { \"enable\",         3, { 235, 237, 255 } },\n  { \"end\",            4, { 0, 41, 42, 44 } },\n  { \"endgame\",        1, { 36 } },\n  { \"endlife\",        1, { 37 } },\n  { \"enemy\",          5, { 181, 182, 183, 184, 187 } },\n  { \"exchange\",       3, { 152, 170, 172 } },\n  { \"explode\",        1, { 31 } },\n  { \"fillhealth\",     1, { 146 } },\n  { \"firewalker\",     1, { 127 } },\n  { \"flip\",           1, { 205 } },\n  { \"freezetime\",     1, { 128 } },\n  { \"give\",           1, { 33 } },\n  { \"givekey\",        2, { 91, 92 } },\n  { \"go\",             1, { 4 } },\n  { \"goto\",           1, { 29 } },\n  { \"gotoxy\",         1, { 9 } },\n  { \"half\",           1, { 28 } },\n  { \"if\",             19, { 112, 113, 114, 16, 17, 18, 19, 20, 21, 22,\n   227, 23, 231, 24, 25, 26, 64, 65, 66 } },\n  { \"inc\",            3, { 11, 14, 95 } },\n  { \"input\",          1, { 111 } },\n  { \"jump\",           1, { 144 } },\n  { \"laybomb\",        2, { 73, 74 } },\n  { \"lazerwall\",      1, { 78 } },\n  { \"load\",           2, { 216, 222 } },\n  { \"lockplayer\",     4, { 56, 58, 59, 60 } },\n  { \"lockscroll\",     1, { 229 } },\n  { \"lockself\",       1, { 51 } },\n  { \"loop\",           2, { 251, 252 } },\n  { \"message\",        2, { 139, 167 } },\n  { \"missilecolor\",   1, { 138 } },\n  { \"mod\",            5, { 199, 200, 224, 38, 157 } },\n  { \"modulo\",         1, { 219 } },\n  { \"move\",           3, { 118, 61, 62 } },\n  { \"multiply\",       1, { 217 } },\n  { \"neutral\",        5, { 177, 178, 179, 180, 186 } },\n  { \"open\",           1, { 50 } },\n  { \"overlay\",        3, { 239, 240, 241 } },\n  { \"persistent\",     1, { 232 } },\n  { \"play\",           2, { 43, 49 } },\n  { \"player\",         7, { 115, 173, 174, 175, 176, 185, 220 } },\n  { \"playercolor\",    1, { 136 } },\n  { \"push\",           1, { 203 } },\n  { \"put\",            6, { 100, 242, 32, 63, 67, 79 } },\n  { \"rel\",            9, { 140, 141, 142, 193, 194, 195, 196, 197, 198 } },\n  { \"resetview\",      1, { 156 } },\n  { \"restore\",        4, { 151, 169, 171, 55 } },\n  { \"rotateccw\",      1, { 70 } },\n  { \"rotatecw\",       1, { 69 } },\n  { \"sam\",            1, { 39 } },\n  { \"save\",           2, { 150, 168 } },\n  { \"scroll\",         1, { 204 } },\n  { \"scrollarrow\",    1, { 163 } },\n  { \"scrollbase\",     1, { 159 } },\n  { \"scrollcorner\",   1, { 160 } },\n  { \"scrollpointer\",  1, { 162 } },\n  { \"scrolltitle\",    1, { 161 } },\n  { \"scrollview\",     2, { 110, 225 } },\n  { \"send\",           4, { 30, 53, 81, 99 } },\n  { \"set\",            8, { 10, 120, 13, 149, 153, 166, 215, 97 } },\n  { \"sfx\",            1, { 48 } },\n  { \"shoot\",          1, { 72 } },\n  { \"shootmissile\",   1, { 75 } },\n  { \"shootseeker\",    1, { 76 } },\n  { \"slowtime\",       1, { 129 } },\n  { \"spitfire\",       1, { 77 } },\n  { \"status\",         1, { 238 } },\n  { \"swap\",           1, { 226 } },\n  { \"switch\",         1, { 71 } },\n  { \"take\",           2, { 34, 35 } },\n  { \"takekey\",        2, { 93, 94 } },\n  { \"teleport\",       1, { 109 } },\n  { \"trade\",          1, { 98 } },\n  { \"try\",            1, { 68 } },\n  { \"unlockplayer\",   1, { 57 } },\n  { \"unlockscroll\",   1, { 230 } },\n  { \"unlockself\",     1, { 52 } },\n  { \"viewport\",       2, { 164, 165 } },\n  { \"volume\",         2, { 158, 40 } },\n  { \"wait\",           4, { 2, 233, 45, 46 } },\n  { \"walk\",           1, { 5 } },\n  { \"wind\",           1, { 130 } },\n  { \"write\",          1, { 247 } },\n  { \"zap\",            1, { 54 } },\n  { \"|\",              1, { 108 } },\n};\n\nstatic const int num_command_names =\n sizeof(sorted_command_list) / sizeof(struct search_entry);\n\nstatic const struct search_entry *find_command(const char *name)\n{\n  int bottom = 0, top = num_command_names - 1, middle;\n  const struct search_entry *base = sorted_command_list;\n  int cmpval;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    cmpval = strcasecmp(name, (sorted_command_list[middle]).name);\n\n    if(cmpval > 0)\n      bottom = middle + 1;\n    else\n\n    if(cmpval < 0)\n      top = middle - 1;\n    else\n      return base + middle;\n  }\n\n  return NULL;\n}\n\nstatic const char *const type_names[] =\n{\n  \"imm\",\n  \"imm\",\n  \"char\",\n  \"color\",\n  \"dir\",\n  \"thing\",\n  \"param\",\n  \"str\",\n  \"equ\",\n  \"cond\",\n  \"item\",\n  \"extra\",\n  \"undef\",\n  \"undef\"\n};\n\nstatic void get_wanted_arg(char *buffer, int arg)\n{\n  char *str_position = buffer;\n  int multi = 0;\n  int i;\n\n  if(arg & CMD)\n  {\n    sprintf(buffer, \"\\'%s\\'\", command_fragments[arg & ~CMD]);\n  }\n  else\n\n  for(i = 0; i < 14; i++)\n  {\n    if(arg & (1 << i))\n    {\n      if(multi)\n      {\n        *str_position = '/';\n        str_position++;\n      }\n      else\n      {\n        multi = 1;\n      }\n\n      strcpy(str_position, type_names[i]);\n      str_position += strlen(type_names[i]);\n    }\n  }\n}\n\nstatic void print_error(int arg_number, char *error_buffer, int bad_arg,\n int correct_arg)\n{\n  char bad_arg_err[64];\n  char correct_arg_err[64];\n\n  get_wanted_arg(bad_arg_err, bad_arg);\n  get_wanted_arg(correct_arg_err, correct_arg);\n\n  sprintf(error_buffer, \"a%d: expected %s, got %s\", arg_number + 1,\n   correct_arg_err, bad_arg_err);\n}\n\nstatic int match_command(struct mzx_command_rw *cmd, char *error_buffer)\n{\n  int i, i2, i3;\n  int num_params;\n  const struct search_entry *matched_command;\n  const struct mzx_command *current_command;\n\n  error_buffer[0] = 0;\n\n  matched_command = find_command(cmd->name);\n\n  if(matched_command)\n  {\n    for(i = 0; i < matched_command->count; i++)\n    {\n      current_command = command_list + matched_command->offsets[i];\n\n      if(strcasecmp(cmd->name, current_command->name))\n        continue;\n\n      if(cmd->parameters != current_command->parameters)\n      {\n        if(!error_buffer[0])\n        {\n          switch(current_command->parameters)\n          {\n            case 0:\n            {\n              sprintf(error_buffer, \"expected no arguments, got %d\",\n               cmd->parameters);\n              break;\n            }\n\n            case 1:\n            {\n              sprintf(error_buffer, \"expected 1 argument, got %d\",\n               cmd->parameters);\n              break;\n            }\n\n            default:\n            {\n              sprintf(error_buffer, \"expected %d arguments, got %d\",\n               current_command->parameters, cmd->parameters);\n              break;\n            }\n          }\n        }\n        continue;\n      }\n\n      num_params = cmd->parameters;\n\n      for(i2 = 0, i3 = 0; i2 < num_params; i2++)\n      {\n        if(!(current_command->param_types[i2] & IGNORE_TYPE))\n        {\n          if(cmd->param_types[i3] & UNDEFINED)\n          {\n            // Dest must be string\n            if(!(current_command->param_types[i2] & STRING))\n            {\n              print_error(i3, error_buffer, cmd->param_types[i3],\n               current_command->param_types[i2]);\n              break;\n            }\n          }\n          else\n\n          if(current_command->param_types[i2] & CMD)\n          {\n            if(cmd->param_types[i3] != current_command->param_types[i2])\n            {\n              if(!error_buffer[0])\n              {\n                print_error(i3, error_buffer, cmd->param_types[i3],\n                 current_command->param_types[i2]);\n              }\n\n              break;\n            }\n          }\n          else\n\n          if((cmd->param_types[i3] & current_command->param_types[i2]) !=\n           cmd->param_types[i3])\n          {\n            // Can allow char if imm is allowed, and can allow imm if\n            // param is allowed.\n            // We must also ignore CMD and IGNORE_TYPE because these\n            // cause the meanings of the bits to become overloaded.\n            if(!(((cmd->param_types[i3] & CHARACTER) &&\n             (current_command->param_types[i2] & (IMM_S16 | IMM_U16))) ||\n             (((cmd->param_types[i3] & (IMM_S16 | IMM_U16)) &&\n             (current_command->param_types[i2] & PARAM)))) ||\n             (cmd->param_types[i3] & (CMD | IGNORE_TYPE)))\n            {\n              print_error(i3, error_buffer, cmd->param_types[i3],\n               current_command->param_types[i2]);\n              break;\n            }\n          }\n\n          i3++;\n        }\n        else\n        {\n          // Don't count this one\n          num_params++;\n        }\n      }\n\n      if(i2 == num_params)\n        return matched_command->offsets[i];\n    }\n  }\n  else\n  {\n    sprintf(error_buffer, \"command \\'%s\\' does not exist\", cmd->name);\n  }\n\n  return -1;\n}\n\nstatic void rasm_skip_whitespace(char *cpos, char **next)\n{\n  while(*cpos == ' ')\n  {\n    cpos++;\n  }\n  *next = cpos;\n}\n\nstatic int assemble_command(int command_number, struct mzx_command_rw *cmd,\n void *params[32], char *obj_pos, char **next_obj_pos)\n{\n  int i;\n  int size;\n  char *c_obj_pos = obj_pos + 2;\n\n  obj_pos[1] = (char)command_number;\n\n  for(i = 0; i < cmd->parameters; i++)\n  {\n    if(cmd->param_types[i] & (STRING | UNDEFINED))\n    {\n      size_t str_size = strlen((char *)params[i]);\n      *(c_obj_pos) = (char)str_size + 1;\n      strcpy(c_obj_pos + 1, (char *)params[i]);\n      c_obj_pos += str_size + 2;\n    }\n    else\n    {\n      if(!(cmd->param_types[i] & CMD))\n      {\n        // It's not a command fragment\n        *c_obj_pos = 0;\n        memcpy(c_obj_pos + 1, (char *)params[i], 2);\n        c_obj_pos += 3;\n      }\n    }\n  }\n\n  size = (int)(c_obj_pos - obj_pos - 1);\n  *obj_pos = size;\n  *c_obj_pos = size;\n\n  *next_obj_pos = c_obj_pos + 1;\n\n  return size + 2;\n}\n\n#ifndef CONFIG_DEBYTECODE\n__editor_maybe_static\n#endif\nint legacy_assemble_line(char *cpos, char *output_buffer, char *error_buffer,\n char *param_listing, int *arg_count_ext)\n{\n  char *current_line_position, *next_line_position;\n  char *next;\n  int translated_command = 0;\n  int arg_count = 0;\n  int current_arg_short = 0;\n  int current_arg_type, last_arg_type;\n  int current_arg_translation = 0, last_arg_translation = -1;\n  int error;\n  char command_name[256];\n  int command_params[32];\n  char temp[ROBOT_MAX_TR];\n  char *first_non_space = NULL;\n  void *param_list[32];\n  int advance;\n  int dir_modifier_buffer = 0;\n  int words = 0;\n  char *temp_pos = temp;\n  size_t temp_left = sizeof(temp);\n\n  struct mzx_command_rw current_command;\n\n  current_command.name = command_name;\n  current_command.param_types = command_params;\n\n  if(cpos[0])\n  {\n    rasm_skip_whitespace(cpos + 1, &first_non_space);\n    if(cpos[0] == ' ')\n    {\n      cpos = first_non_space;\n      rasm_skip_whitespace(cpos + 1, &first_non_space);\n    }\n  }\n\n  if(!cpos[0] || (cpos[0] == '\\n'))\n  {\n    translated_command = 47;\n    strcpy(command_name, command_list[47].name);\n    current_command.parameters = 0;\n    arg_count = 0;\n  }\n  else\n\n  if(special_first_char[(int)cpos[0]] && (first_non_space[0] != '\\\"'))\n  {\n    size_t str_size;\n\n    command_name[0] = cpos[0];\n    command_name[1] = 0;\n    current_command.parameters = 1;\n\n    str_size = strlen(first_non_space) + 1;\n    if(temp_left < str_size)\n      goto err_buffer;\n\n    memcpy(temp_pos, first_non_space, str_size);\n    current_command.param_types[0] = STRING;\n\n    param_list[0] = temp_pos;\n    temp_pos += str_size;\n    temp_left -= str_size;\n    arg_count = 1;\n\n    if(param_listing)\n    {\n      param_listing[0] = S_STRING;\n      words = 1;\n    }\n\n    translated_command = match_command(&current_command, error_buffer);\n  }\n  else\n  {\n    current_line_position = cpos;\n\n    current_line_position +=\n     get_word(command_name, 256, current_line_position, ' ');\n    words++;\n    last_arg_type = 0;\n\n    while((*current_line_position != 0) && (*current_line_position != '\\n'))\n    {\n      rasm_skip_whitespace(current_line_position, &current_line_position);\n\n      if(*current_line_position == 0)\n        break;\n\n      current_arg_type = rasm_parse_argument(current_line_position,\n       &next_line_position, &current_arg_translation, &error,\n       &current_arg_short);\n\n      if(param_listing)\n        param_listing[words] = current_arg_short;\n\n      current_command.param_types[arg_count] = current_arg_type;\n\n      if(error == ERR_BADCHARACTER)\n      {\n        sprintf(error_buffer, \"Unterminated char constant.\");\n        return -1;\n      }\n\n      if(current_arg_type != EXTRA)\n      {\n        if(current_arg_type == UNDEFINED)\n        {\n          // Grab the string off the command list.\n          size_t str_size =\n           get_word(temp_pos, temp_left, current_line_position, ' ');\n\n          if(temp_left < str_size + 1)\n            goto err_buffer;\n\n          param_list[arg_count] = temp_pos;\n          temp_pos += str_size + 1;\n          temp_left -= str_size + 1;\n          advance = 1;\n          dir_modifier_buffer = 0;\n        }\n        else\n\n        if(current_arg_type == STRING)\n        {\n          // Grab the string off the command list.\n          size_t str_size =\n           get_word(temp_pos, temp_left, current_line_position + 1, '\"');\n\n          if(temp_left < str_size + 1)\n            goto err_buffer;\n\n          param_list[arg_count] = temp_pos;\n          temp_pos += str_size + 1;\n          temp_left -= str_size + 1;\n          advance = 1;\n          dir_modifier_buffer = 0;\n        }\n        else\n\n        if(current_arg_type == DIRECTION)\n        {\n          if(current_arg_translation >= 16)\n          {\n            dir_modifier_buffer |= 1 << (current_arg_translation - 12);\n            advance = 0;\n          }\n          else\n\n          if((last_arg_type == CONDITION) &&\n           ((last_arg_translation == 0) || (last_arg_translation == 3) ||\n           (last_arg_translation == 4) || (last_arg_translation == 8) ||\n           (last_arg_translation == 9)))\n          {\n            ((char *)param_list[arg_count - 1])[1] |=\n             current_arg_translation | dir_modifier_buffer;\n            advance = 0;\n          }\n          else\n          {\n            if(temp_left < 2)\n              goto err_buffer;\n\n            // Store the translation into the command list.\n            temp_pos[0] = current_arg_translation | dir_modifier_buffer;\n            temp_pos[1] = 0;\n\n            param_list[arg_count] = temp_pos;\n            temp_pos += 2;\n            temp_left -= 2;\n            advance = 1;\n            dir_modifier_buffer = 0;\n          }\n        }\n        else\n        {\n          if(temp_left < 2)\n            goto err_buffer;\n\n          // Store the translation into the command list.\n          temp_pos[0] = current_arg_translation;\n          temp_pos[1] = current_arg_translation >> 8;\n\n          param_list[arg_count] = temp_pos;\n          temp_pos += 2;\n          temp_left -= 2;\n          advance = 1;\n          dir_modifier_buffer = 0;\n        }\n\n        if(advance)\n        {\n          last_arg_type = current_arg_type;\n          last_arg_translation = current_arg_translation;\n          arg_count++;\n        }\n\n        if(arg_count == 32)\n        {\n          sprintf(error_buffer, \"Too many arguments to parse.\");\n          return -1;\n        }\n      }\n      else\n      {\n        dir_modifier_buffer = 0;\n      }\n\n      current_line_position = next_line_position;\n    }\n\n    current_command.parameters = arg_count;\n    translated_command = match_command(&current_command, error_buffer);\n\n    if(translated_command == -1)\n      return -1;\n  }\n\n  if(param_listing)\n    *arg_count_ext = words;\n\n  return assemble_command(translated_command,\n   &current_command, param_list, output_buffer, &next);\n\nerr_buffer:\n  sprintf(error_buffer, \"Token %d exceeded command buffer.\", arg_count);\n  return -1;\n}\n\n#ifndef CONFIG_DEBYTECODE\n\nchar *assemble_file(char *name, int *size)\n{\n  vfile *input_file = fsafeopen(name, \"rb\");\n  char line_buffer[256];\n  char bytecode_buffer[256];\n  char error_buffer[256];\n  int line_bytecode_length;\n  int allocated_size = 1024;\n  char *buffer;\n  int output_position = 1;\n  int current_size = 1;\n\n  if(!input_file)\n    return NULL;\n\n  buffer = cmalloc(1024);\n  buffer[0] = 0xFF;\n\n  // fsafegets ensures no line terminators are present\n  while(vfsafegets(line_buffer, 255, input_file))\n  {\n    line_bytecode_length =\n     legacy_assemble_line(line_buffer, bytecode_buffer, error_buffer, NULL, NULL);\n\n    if((line_bytecode_length != -1) &&\n     ((current_size + line_bytecode_length) < MAX_OBJ_SIZE))\n    {\n      if((current_size + line_bytecode_length) > allocated_size)\n      {\n        allocated_size *= 2;\n        buffer = crealloc(buffer, allocated_size);\n      }\n      memcpy(buffer + output_position, bytecode_buffer, line_bytecode_length);\n      output_position += line_bytecode_length;\n      current_size += line_bytecode_length;\n    }\n    else\n    {\n      free(buffer);\n      buffer = NULL;\n      goto exit_out;\n    }\n  }\n\n  // trim the buffer to match the output size\n  buffer = crealloc(buffer, current_size + 1);\n  buffer[current_size] = 0;\n  *size = current_size + 1;\n\nexit_out:\n  vfclose(input_file);\n  return buffer;\n}\n\nchar *assemble_program(char *src, int len, int *size)\n{\n  char line_buffer[256];\n  char bytecode_buffer[256];\n  char error_buffer[256];\n  int line_bytecode_length;\n  int allocated_size = 1024;\n  char *buffer;\n  struct memfile mf;\n\n  int output_position = 1;\n  int current_size = 1;\n\n  buffer = cmalloc(1024);\n  buffer[0] = 0xFF;\n\n  mfopen(src, len, &mf);\n\n  // It's wasteful to copy each line, but the alternative is worse...\n  while(mfsafegets(line_buffer, 256, &mf))\n  {\n    line_bytecode_length =\n     legacy_assemble_line(line_buffer, bytecode_buffer, error_buffer, NULL, NULL);\n\n    if((line_bytecode_length != -1) &&\n     ((current_size + line_bytecode_length) < MAX_OBJ_SIZE))\n    {\n      if((current_size + line_bytecode_length) > allocated_size)\n      {\n        allocated_size *= 2;\n        buffer = crealloc(buffer, allocated_size);\n      }\n      memcpy(buffer + output_position, bytecode_buffer, line_bytecode_length);\n      output_position += line_bytecode_length;\n      current_size += line_bytecode_length;\n    }\n    else\n    {\n      free(buffer);\n      return NULL;\n    }\n  }\n\n  // trim the buffer to match the output size\n  buffer = crealloc(buffer, current_size + 1);\n  buffer[current_size] = 0;\n  *size = current_size + 1;\n\n  return buffer;\n}\n\n__editor_maybe_static void print_color(int color, char *color_buffer)\n{\n  /* Simulate the 2.x interpreter function fix_color here to guarantee\n   * that invalid values are accurately disassembled as they are interpreted.\n   * Invalid values below 0 (signed 16-bit) are ultimately handled mod 256.\n   * Invalid values above 0x120 are treated identically to c??.\n   *\n   * This interpreter behavior dates back to 2.51 or older, but the DOS\n   * disassembler would treat all invalid values as c??, and port versions\n   * prior to 2.93c would print one of two forms of invalid junk instead.\n   */\n  if((int16_t)color < 0x100)\n  {\n    sprintf(color_buffer, \"c%02x\", color & 0xff);\n  }\n  else\n\n  if(color < 0x110)\n  {\n    sprintf(color_buffer, \"c?%1x\", color & 0x0f);\n  }\n  else\n\n  if(color < 0x120)\n  {\n    sprintf(color_buffer, \"c%1x?\", color & 0x0f);\n  }\n  else\n  {\n    sprintf(color_buffer, \"c??\");\n  }\n}\n\nstatic int print_dir(int dir, char *dir_buffer, char *arg_types,\n int arg_place)\n{\n  static const char *const dir_types[20] =\n  {\n    \"IDLE\", \"NORTH\", \"SOUTH\", \"EAST\", \"WEST\", \"RANDNS\", \"RANDEW\", \"RANDNE\",\n    \"RANDNB\", \"SEEK\", \"RANDANY\", \"BENEATH\", \"ANYDIR\", \"FLOW\", \"NODIR\",\n    \"RANDB\", \"RANDP\", \"CW\", \"OPP\", \"RANDNOT\"\n  };\n\n  char *dir_write = dir_buffer;\n\n  if(dir & 0x10)\n  {\n    strcpy(dir_write, \"RANDP \");\n    if(arg_types)\n    {\n      arg_types[arg_place] = S_DIR;\n      arg_place++;\n    }\n    dir_write += 6;\n  }\n\n  if(dir & 0x20)\n  {\n    strcpy(dir_write, \"CW \");\n    if(arg_types)\n    {\n      arg_types[arg_place] = S_DIR;\n      arg_place++;\n    }\n    dir_write += 3;\n  }\n\n  if(dir & 0x40)\n  {\n    strcpy(dir_write, \"OPP \");\n    if(arg_types)\n    {\n      arg_types[arg_place] = S_DIR;\n      arg_place++;\n    }\n    dir_write += 4;\n  }\n\n  if(dir & 0x80)\n  {\n    strcpy(dir_write, \"RANDNOT \");\n    if(arg_types)\n    {\n      arg_types[arg_place] = S_DIR;\n      arg_place++;\n    }\n    dir_write += 8;\n  }\n\n  if(arg_types)\n    arg_types[arg_place] = S_DIR;\n\n  sprintf(dir_write, \"%s\", dir_types[dir & 0x0F]);\n\n  return arg_place;\n}\n\n__editor_maybe_static int unescape_char(char *dest, char c)\n{\n  switch(c)\n  {\n    case '\"':\n      dest[0] = '\\\\';\n      dest[1] = '\"';\n      return 2;\n\n    case 0:\n      dest[0] = '\\\\';\n      dest[1] = '0';\n      return 2;\n\n    case '\\n':\n      dest[0] = '\\\\';\n      dest[1] = 'n';\n      return 2;\n\n    case '\\r':\n      dest[0] = '\\\\';\n      dest[1] = 'r';\n      return 2;\n\n    case '\\t':\n      dest[0] = '\\\\';\n      dest[1] = 't';\n      return 2;\n\n    case '\\\\':\n      dest[0] = '\\\\';\n      dest[1] = '\\\\';\n      return 2;\n\n    default:\n      dest[0] = c;\n      return 1;\n  }\n}\n\n__editor_maybe_static int disassemble_line(char *cpos, char **next,\n char *output_buffer, char *error_buffer, int *total_bytes,\n int print_ignores, char *arg_types, int *arg_count, int base)\n{\n  static const char *const equality_types[9] =\n  {\n    \"=\", \"<\", \">\", \">=\", \"<=\", \"!=\", \"===\", \"?=\", \"?==\"\n  };\n\n  static const char *const condition_types[18] =\n  {\n    \"walking\", \"swimming\", \"firewalking\", \"touching\", \"blocked\", \"aligned\",\n    \"alignedns\", \"alignedew\", \"lastshot\", \"lasttouch\", \"rightpressed\",\n    \"leftpressed\", \"uppressed\", \"downpressed\", \"spacepressed\", \"delpressed\",\n    \"musicon\", \"pcsfxon\"\n  };\n\n  static const char *const item_types[9] =\n  {\n    \"GEMS\", \"AMMOS\", \"TIME\", \"SCORE\", \"HEALTHS\", \"LIVES\",\n    \"LOBOMBS\", \"HIBOMBS\", \"COINS\"\n  };\n\n  static const char *const ignore_list[21] =\n  {\n    \",\", \";\", \"a\", \"an\", \"and\", \"as\", \"at\", \"by\", \"else\", \"for\", \"from\",\n    \"into\", \"is\", \"of\", \"the\", \"then\", \"there\", \"through\", \"thru\", \"to\",\n    \"with\"\n  };\n\n  int length = *cpos;\n  int i;\n  char *input_position = cpos + 2;\n  char *output_position = output_buffer;\n  const struct mzx_command *current_command;\n\n  if(length)\n  {\n    int command_number = *(cpos + 1);\n    int current_param;\n    int total_params;\n    int words = 0;\n\n    current_command = &command_list[command_number];\n\n    if(command_number != ROBOTIC_CMD_BLANK_LINE)\n    {\n      strcpy(output_position, current_command->name);\n      output_position += strlen(current_command->name);\n    }\n\n    total_params = current_command->parameters;\n\n    for(i = 0; i < total_params; i++, words++)\n    {\n      *output_position = ' ';\n      output_position++;\n\n      current_param = current_command->param_types[i];\n\n      if(current_param & CMD)\n      {\n        // Command fragment\n        int fragment_type = current_param & ~CMD;\n        strcpy(output_position, command_fragments[fragment_type]);\n        output_position += strlen(command_fragments[fragment_type]);\n        if(arg_types)\n          arg_types[words] = S_CMD;\n      }\n      else\n\n      if(current_param & IGNORE_TYPE)\n      {\n        if(print_ignores)\n        {\n          // Ignore fragment\n          int ignore_type = current_param & ~IGNORE_TYPE;\n          if(arg_types)\n            arg_types[words] = S_EXTRA;\n          strcpy(output_position, ignore_list[ignore_type]);\n          output_position += strlen(ignore_list[ignore_type]);\n        }\n        else\n        {\n          words--;\n          output_position--;\n        }\n\n        total_params++;\n      }\n      else\n      {\n        int param_type = current_param;\n\n        // If it can be a string, if it's NOT a string check if it can\n        // be anything else too.\n        if(param_type & STRING)\n        {\n          if(*input_position)\n          {\n            // It is a string, so make it that\n            param_type &= STRING;\n          }\n          else\n          {\n            // Otherwise use the non-string part\n            param_type &= ~STRING;\n          }\n        }\n\n        // Give char representation, not immediate\n        if(param_type & CHARACTER)\n        {\n          // ...unless this is an extended char in a command that supports it.\n          if(input_position[2] && (param_type & EXTENDED))\n            param_type = IMM_U16;\n          else\n            param_type &= ~IMM_U16;\n        }\n        param_type &= ~EXTENDED;\n\n        switch(param_type)\n        {\n          case IMM_U16:\n          {\n            int imm = (short)(*(input_position + 1) |\n             (*(input_position + 2) << 8));\n            char imm_buffer[16];\n\n            if(arg_types)\n              arg_types[words] = S_IMM_U16;\n\n            input_position += 3;\n            if(base == 10)\n              sprintf(imm_buffer, \"%d\", imm);\n            else\n              sprintf(imm_buffer, \"$%x\", imm);\n\n            strcpy(output_position, imm_buffer);\n            output_position += strlen(imm_buffer);\n            break;\n          }\n\n          case CHARACTER:\n          {\n            int character = *(input_position + 1);\n\n            if(arg_types)\n              arg_types[words] = S_CHARACTER;\n\n            input_position += 3;\n            output_position[0] = '\\'';\n            output_position++;\n\n            output_position += unescape_char(output_position, character);\n\n            output_position[0] = '\\'';\n            output_position++;\n\n            break;\n          }\n\n          case COLOR:\n          {\n            int color = *(input_position + 1) | (*(input_position + 2) << 8);\n            char color_buffer[16];\n\n            if(arg_types)\n              arg_types[words] = S_COLOR;\n\n            input_position += 3;\n            print_color(color, color_buffer);\n            strcpy(output_position, color_buffer);\n            output_position += strlen(color_buffer);\n            break;\n          }\n\n          case DIRECTION:\n          {\n            int dir = *(input_position + 1);\n            char dir_buffer[64];\n            words = print_dir(dir, dir_buffer, arg_types, words);\n\n            strcpy(output_position, dir_buffer);\n            output_position += strlen(dir_buffer);\n            input_position += 3;\n            break;\n          }\n\n          case THING:\n          {\n            int thing = *(input_position + 1);\n            input_position += 3;\n\n            if(arg_types)\n              arg_types[words] = S_THING;\n\n            if(thing >= (int)ARRAY_SIZE(thing_names))\n            {\n              strcpy(output_position, \"??\");\n              output_position += 2;\n              break;\n            }\n\n            strcpy(output_position, thing_names[thing]);\n            output_position += strlen(thing_names[thing]);\n            break;\n          }\n\n          case PARAM:\n          {\n            int param = *(input_position + 1) | (*(input_position + 2) << 8);\n            char param_buffer[16];\n\n            if(arg_types)\n              arg_types[words] = S_PARAM;\n\n            /* Note: disassembling invalid values modulo 256 to match how\n             * they are interpreted. The DOS disassembler would disassemble\n             * all invalid values to p?? and the port disassembler would\n             * produce either p?? or junk prior to 2.93c.\n             */\n            input_position += 3;\n            if(param == 0x100)\n            {\n              sprintf(param_buffer, \"p??\");\n            }\n            else\n            {\n              sprintf(param_buffer, \"p%02x\", param & 0xff);\n            }\n\n            strcpy(output_position, param_buffer);\n            output_position += strlen(param_buffer);\n            break;\n          }\n\n          case STRING:\n          {\n            int num_chars = *input_position - 1;\n            int i;\n\n            if(arg_types)\n              arg_types[words] = S_STRING;\n\n            input_position++;\n\n            *output_position = '\"';\n            output_position++;\n\n            for(i = 0; i < num_chars; i++)\n            {\n              output_position +=\n               unescape_char(output_position, *input_position);\n\n              input_position++;\n            }\n\n            *output_position = '\"';\n            input_position++;\n            output_position++;\n\n            break;\n          }\n\n          case EQUALITY:\n          {\n            int equality = *(input_position + 1);\n            input_position += 3;\n\n            if(arg_types)\n              arg_types[words] = S_EQUALITY;\n\n            // Is this actually a valid operator?\n            if(equality < (int)ARRAY_SIZE(equality_types))\n            {\n              strcpy(output_position, equality_types[equality]);\n              output_position += strlen(equality_types[equality]);\n            }\n            else\n            {\n              strcpy(output_position, \"??\");\n              output_position += 2;\n            }\n            break;\n          }\n\n          case CONDITION:\n          {\n            int condition = *(input_position + 1);\n            int direction = *(input_position + 2);\n            input_position += 3;\n\n            if(arg_types)\n              arg_types[words] = S_CONDITION;\n\n            if(condition >= (int)ARRAY_SIZE(condition_types))\n            {\n              strcpy(output_position, \"??\");\n              output_position += 2;\n              break;\n            }\n\n            strcpy(output_position, condition_types[condition]);\n            output_position += strlen(condition_types[condition]);\n\n            if((condition == 0) || (condition == 3) || (condition == 4) ||\n             (condition == 8) || (condition == 9))\n            {\n              char dir_buffer[64];\n              dir_buffer[0] = ' ';\n              words = print_dir(direction, dir_buffer + 1, arg_types,\n               words + 1);\n              strcpy(output_position, dir_buffer);\n              output_position += strlen(dir_buffer);\n            }\n\n            break;\n          }\n\n          case ITEM:\n          {\n            int item = *(input_position + 1);\n            input_position += 3;\n\n            if(arg_types)\n              arg_types[words] = S_ITEM;\n\n            if(item >= (int)ARRAY_SIZE(item_types))\n            {\n              strcpy(output_position, \"??\");\n              output_position += 2;\n              break;\n            }\n\n            strcpy(output_position, item_types[item]);\n            output_position += strlen(item_types[item]);\n            break;\n          }\n        }\n      }\n    }\n\n    *output_position = 0;\n\n    *next = cpos + (*cpos) + 2;\n    *total_bytes = (int)(output_position - output_buffer);\n\n    if(arg_types)\n      *arg_count = words;\n\n    return 1;\n  }\n\n  return 0;\n}\n\nvoid disassemble_file(char *name, char *program, int program_length,\n int allow_ignores, int base)\n{\n  vfile *output_file = fsafeopen(name, \"wb\");\n  char command_buffer[256];\n  char error_buffer[256];\n  char *current_robot_pos = program + 1;\n  char *next;\n  int new_line;\n  int line_text_length;\n\n  if(!output_file)\n    return;\n\n  do\n  {\n    new_line = disassemble_line(current_robot_pos,\n     &next, command_buffer, error_buffer,\n     &line_text_length, allow_ignores, NULL, NULL, base);\n\n    if(new_line)\n    {\n      vfwrite(command_buffer, line_text_length, 1, output_file);\n      vfputc('\\n', output_file);\n    }\n\n    current_robot_pos = next;\n  } while(new_line);\n\n  vfclose(output_file);\n}\n\nstatic inline int get_program_line_count(char *program, int program_length)\n{\n  char *end = program + program_length;\n  int line_num = 0;\n\n  // Skip 0xFF\n  program++;\n\n  while(program < end)\n  {\n    program += *program + 2;\n    line_num++;\n  }\n\n  return line_num;\n}\n\nvoid disassemble_program(char *program, int program_length,\n char **_source, int *_source_length, struct command_mapping **_command_map,\n int *_command_map_length)\n{\n  // Take a program and turn in into source code plus a mapping from the\n  // bytecode to sourcecode. Required for the robot debugger.\n\n  char line_buffer[256] = { 0 };\n  int line_size = 0;\n\n  // Better to overshoot than undershoot here.\n  int source_length = program_length * 2;\n  char *source = cmalloc(source_length);\n  int offset = 0;\n\n  struct command_mapping *cmd_map = NULL;\n  int cmd_map_length = 0;\n  int i = 1;\n\n  char *start = program;\n  char *next = program + 1;\n\n  int ret;\n\n  if(_command_map && _command_map_length)\n  {\n    cmd_map_length = get_program_line_count(program, program_length);\n    cmd_map = cmalloc(cmd_map_length * sizeof(struct command_mapping));\n\n    cmd_map[0].real_line = 0;\n    cmd_map[0].bc_pos = 0;\n    cmd_map[0].src_pos = 0;\n  }\n\n  while(*next)\n  {\n    line_size = 0;\n\n    if(cmd_map && i < cmd_map_length)\n    {\n      cmd_map[i].real_line = i;\n      cmd_map[i].bc_pos = next - start;\n      cmd_map[i].src_pos = offset;\n      i++;\n    }\n\n    ret = disassemble_line(next, &next,\n     line_buffer,\n     NULL,        // Error buffer\n     &line_size,\n     1,           // Print ignored words\n     NULL,        // Arg types\n     NULL,        // Arg count\n     10           // Numeric base\n    );\n\n    // +2 for line break and potentially the terminator\n    while(source_length - offset < line_size + 2)\n    {\n      source_length *= 2;\n      source = crealloc(source, source_length);\n    }\n\n    if(ret)\n    {\n      memcpy(source + offset, line_buffer, line_size);\n      offset += line_size;\n\n      source[offset] = '\\n';\n      offset++;\n    }\n  }\n\n  source[offset] = '\\0';\n\n  // Shrink program alloc\n  source_length = offset;\n  source = crealloc(source, source_length + 1);\n\n  *_source = source;\n  *_source_length = source_length;\n\n  if(_command_map && _command_map_length)\n  {\n    *_command_map = cmd_map;\n    *_command_map_length = cmd_map_length;\n  }\n}\n#endif // !CONFIG_DEBYTECODE\n\nenum v1_params\n{\n  V1_NO_PARAM,\n  V1_16,              // unsigned\n  V1_U8,              // unsigned\n  V1_STRING,\n  V1_CHAR,\n  V1_COLOR_WILDCARD,  // bit 8 is stored as bit 7 of the next byte(!).\n  V1_COLOR,           // unused in ver1to2; colors with no wildcard support.\n  V1_DIRECTION,\n  V1_ITEM,\n  V1_EQUALITY,\n  V1_CONDITION,\n  V1_THING,\n  V1_PARAM,\n  V1_PARAM_WILDCARD,  // p0 -> p??\n  V1_ADD_PARAM,       // param was not present in the original command, use 256.\n  V1_8,               // signed\n\n  V1_SPECIAL_CHAR_EDIT,     // translate 14 type-2 parameters\n  V1_SPECIAL_TOUCHING_DIR,  // convert direction to condition: touching [dir]\n};\n\nstatic const uint8_t v1_command_translation[256][8] =\n{\n  // cvt=convert to a different command.\n  { ROBOTIC_CMD_END },\n  { ROBOTIC_CMD_DIE },\n  { ROBOTIC_CMD_WAIT,               V1_U8 },\n  { ROBOTIC_CMD_CYCLE,              V1_U8 },\n  { ROBOTIC_CMD_GO,                 V1_DIRECTION, V1_U8 },\n  { ROBOTIC_CMD_WALK,               V1_DIRECTION },\n  { ROBOTIC_CMD_BECOME,             V1_COLOR_WILDCARD, V1_THING, V1_PARAM },\n  { ROBOTIC_CMD_CHAR,               V1_CHAR },\n  { ROBOTIC_CMD_COLOR,              V1_COLOR },\n  { ROBOTIC_CMD_GOTOXY,             V1_8, V1_8 },\n  { ROBOTIC_CMD_SET,                V1_STRING, V1_16 },\n  { ROBOTIC_CMD_INC,                V1_STRING, V1_16 },\n  { ROBOTIC_CMD_DEC,                V1_STRING, V1_16 },\n  { ROBOTIC_CMD_SET,                V1_STRING, V1_STRING }, // cvt\n  { ROBOTIC_CMD_INC,                V1_STRING, V1_STRING }, // cvt\n  { ROBOTIC_CMD_DEC,                V1_STRING, V1_STRING }, // cvt\n  { ROBOTIC_CMD_IF,                 V1_STRING, V1_EQUALITY, V1_16, V1_STRING },\n  { ROBOTIC_CMD_IF,                 V1_STRING, V1_EQUALITY, V1_STRING, V1_STRING }, // cvt\n  { ROBOTIC_CMD_IF_CONDITION,       V1_CONDITION, V1_STRING },\n  { ROBOTIC_CMD_IF_NOT_CONDITION,   V1_CONDITION, V1_STRING },\n  { ROBOTIC_CMD_IF_ANY,             V1_COLOR_WILDCARD, V1_THING, V1_ADD_PARAM, V1_STRING },\n  { ROBOTIC_CMD_IF_NO,              V1_COLOR_WILDCARD, V1_THING, V1_ADD_PARAM, V1_STRING },\n  { ROBOTIC_CMD_IF_THING_DIR,       V1_COLOR_WILDCARD, V1_THING, V1_ADD_PARAM, V1_DIRECTION, V1_STRING },\n  { ROBOTIC_CMD_IF_NOT_THING_DIR,   V1_COLOR_WILDCARD, V1_THING, V1_ADD_PARAM, V1_DIRECTION, V1_STRING },\n  { ROBOTIC_CMD_IF_THING_XY,        V1_COLOR_WILDCARD, V1_THING, V1_ADD_PARAM, V1_8, V1_8, V1_STRING },\n  { ROBOTIC_CMD_IF_AT,              V1_8, V1_8, V1_STRING },\n  { ROBOTIC_CMD_IF_DIR_OF_PLAYER,   V1_DIRECTION, V1_COLOR_WILDCARD, V1_THING, V1_ADD_PARAM, V1_STRING },\n  { ROBOTIC_CMD_DOUBLE,             V1_STRING },\n  { ROBOTIC_CMD_HALF,               V1_STRING },\n  { ROBOTIC_CMD_GOTO,               V1_STRING },\n  { ROBOTIC_CMD_SEND,               V1_STRING, V1_STRING },\n  { ROBOTIC_CMD_EXPLODE,            V1_U8 },\n  { ROBOTIC_CMD_PUT_DIR,            V1_COLOR, V1_THING, V1_PARAM, V1_DIRECTION },\n  { ROBOTIC_CMD_GIVE,               V1_16, V1_ITEM },\n  { ROBOTIC_CMD_TAKE,               V1_16, V1_ITEM },\n  { ROBOTIC_CMD_TAKE_OR,            V1_16, V1_ITEM, V1_STRING },\n  { ROBOTIC_CMD_ENDGAME },\n  { ROBOTIC_CMD_ENDLIFE },\n  { ROBOTIC_CMD_MOD,                V1_STRING },\n  { ROBOTIC_CMD_SAM,                V1_16, V1_STRING },\n  { ROBOTIC_CMD_VOLUME,             V1_U8 },\n  { ROBOTIC_CMD_END_MOD },\n  { ROBOTIC_CMD_END_SAM },\n  { ROBOTIC_CMD_PLAY,               V1_STRING },\n  { ROBOTIC_CMD_END_PLAY },\n  { ROBOTIC_CMD_WAIT_THEN_PLAY,     V1_STRING },\n  { ROBOTIC_CMD_WAIT_PLAY },\n  { ROBOTIC_CMD_BLANK_LINE },\n  { ROBOTIC_CMD_SFX,                V1_U8 },\n  { ROBOTIC_CMD_PLAY_IF_SILENT,     V1_STRING },\n  { ROBOTIC_CMD_OPEN,               V1_DIRECTION },\n  { ROBOTIC_CMD_LOCKSELF },\n  { ROBOTIC_CMD_UNLOCKSELF },\n  { ROBOTIC_CMD_SEND_DIR,           V1_DIRECTION, V1_STRING },\n  { ROBOTIC_CMD_ZAP,                V1_STRING, V1_U8 },\n  { ROBOTIC_CMD_RESTORE,            V1_STRING, V1_U8 },\n  { ROBOTIC_CMD_LOCKPLAYER },\n  { ROBOTIC_CMD_UNLOCKPLAYER },\n  { ROBOTIC_CMD_LOCKPLAYER_NS },\n  { ROBOTIC_CMD_LOCKPLAYER_EW },\n  { ROBOTIC_CMD_LOCKPLAYER_ATTACK },\n  { ROBOTIC_CMD_MOVE_PLAYER_DIR,    V1_DIRECTION },\n  { ROBOTIC_CMD_MOVE_PLAYER_DIR_OR, V1_DIRECTION, V1_STRING },\n  { ROBOTIC_CMD_PUT_PLAYER_XY,      V1_8, V1_8, },\n  { ROBOTIC_CMD_IF_CONDITION,       V1_SPECIAL_TOUCHING_DIR, V1_STRING }, // cvt\n  { ROBOTIC_CMD_IF_NOT_CONDITION,   V1_SPECIAL_TOUCHING_DIR, V1_STRING }, // cvt\n  { ROBOTIC_CMD_IF_PLAYER_XY,       V1_8, V1_8, V1_STRING },\n  { ROBOTIC_CMD_PUT_PLAYER_DIR,     V1_DIRECTION },\n  { ROBOTIC_CMD_TRY_DIR,            V1_DIRECTION, V1_STRING },\n  { ROBOTIC_CMD_ROTATECW },\n  { ROBOTIC_CMD_ROTATECCW },\n  { ROBOTIC_CMD_SWITCH,             V1_DIRECTION, V1_DIRECTION },\n  { ROBOTIC_CMD_SHOOT,              V1_DIRECTION },\n  { ROBOTIC_CMD_LAYBOMB,            V1_DIRECTION },\n  { ROBOTIC_CMD_LAYBOMB_HIGH,       V1_DIRECTION },\n  { ROBOTIC_CMD_SHOOTMISSILE,       V1_DIRECTION },\n  { ROBOTIC_CMD_SHOOTSEEKER,        V1_DIRECTION },\n  { ROBOTIC_CMD_SPITFIRE,           V1_DIRECTION },\n  { ROBOTIC_CMD_LAZERWALL,          V1_DIRECTION, V1_U8 },\n  { ROBOTIC_CMD_PUT_XY,             V1_COLOR, V1_THING, V1_PARAM, V1_8, V1_8 },\n  { ROBOTIC_CMD_DIE_ITEM },\n  { ROBOTIC_CMD_SEND_XY,            V1_8, V1_8, V1_STRING },\n  { ROBOTIC_CMD_COPYROBOT_NAMED,    V1_STRING },\n  { ROBOTIC_CMD_COPYROBOT_XY,       V1_8, V1_8 },\n  { ROBOTIC_CMD_COPYROBOT_DIR,      V1_DIRECTION },\n  { ROBOTIC_CMD_DUPLICATE_SELF_DIR, V1_DIRECTION },\n  { ROBOTIC_CMD_DUPLICATE_SELF_XY,  V1_8, V1_8 },\n  { ROBOTIC_CMD_BULLETN,            V1_CHAR },\n  { ROBOTIC_CMD_BULLETS,            V1_CHAR },\n  { ROBOTIC_CMD_BULLETE,            V1_CHAR },\n  { ROBOTIC_CMD_BULLETW,            V1_CHAR },\n  { ROBOTIC_CMD_GIVEKEY,            V1_COLOR },\n  { ROBOTIC_CMD_GIVEKEY_OR,         V1_COLOR, V1_STRING },\n  { ROBOTIC_CMD_TAKEKEY,            V1_COLOR },\n  { ROBOTIC_CMD_TAKEKEY_OR,         V1_COLOR, V1_STRING },\n  { ROBOTIC_CMD_INC_RANDOM,         V1_STRING, V1_16, V1_16 },\n  { ROBOTIC_CMD_DEC_RANDOM,         V1_STRING, V1_16, V1_16 },\n  { ROBOTIC_CMD_SET_RANDOM,         V1_STRING, V1_16, V1_16 },\n  { ROBOTIC_CMD_TRADE,              V1_16, V1_ITEM, V1_16, V1_ITEM, V1_STRING },\n  { ROBOTIC_CMD_SEND_DIR_PLAYER,    V1_DIRECTION, V1_STRING },\n  { ROBOTIC_CMD_PUT_DIR_PLAYER,     V1_COLOR, V1_THING, V1_PARAM, V1_DIRECTION },\n  { ROBOTIC_CMD_SLASH,              V1_STRING },\n  { ROBOTIC_CMD_MESSAGE_LINE,       V1_STRING },\n  { ROBOTIC_CMD_MESSAGE_BOX_LINE,   V1_STRING },\n  { ROBOTIC_CMD_MESSAGE_BOX_OPTION, V1_STRING, V1_STRING },\n  { ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION, V1_STRING, V1_STRING, V1_STRING },\n  { ROBOTIC_CMD_LABEL,              V1_STRING },\n  { ROBOTIC_CMD_COMMENT,            V1_STRING },\n  { ROBOTIC_CMD_ZAPPED_LABEL,       V1_STRING },\n  { ROBOTIC_CMD_TELEPORT,           V1_STRING, V1_8, V1_8 },\n  { ROBOTIC_CMD_SCROLLVIEW,         V1_DIRECTION, V1_U8 },\n  { ROBOTIC_CMD_INPUT,              V1_STRING },\n  { ROBOTIC_CMD_IF_INPUT,           V1_STRING, V1_STRING },\n  { ROBOTIC_CMD_IF_INPUT_NOT,       V1_STRING, V1_STRING },\n  { ROBOTIC_CMD_IF_INPUT_MATCHES,   V1_STRING, V1_STRING },\n  { ROBOTIC_CMD_PLAYER_CHAR,        V1_CHAR },\n  { ROBOTIC_CMD_MESSAGE_BOX_COLOR_LINE, V1_STRING },\n  { ROBOTIC_CMD_MESSAGE_BOX_CENTER_LINE, V1_STRING },\n  { ROBOTIC_CMD_MOVE_ALL,           V1_COLOR_WILDCARD, V1_THING, V1_ADD_PARAM, V1_DIRECTION },\n  { ROBOTIC_CMD_COPY,               V1_8, V1_8, V1_8, V1_8 },\n  { ROBOTIC_CMD_SET_EDGE_COLOR,     V1_COLOR },\n  { ROBOTIC_CMD_BOARD,              V1_DIRECTION, V1_STRING },\n  { ROBOTIC_CMD_BOARD_IS_NONE,      V1_DIRECTION },\n  { ROBOTIC_CMD_CHAR_EDIT,          V1_CHAR, V1_SPECIAL_CHAR_EDIT },\n  { ROBOTIC_CMD_BECOME_PUSHABLE },\n  { ROBOTIC_CMD_BECOME_NONPUSHABLE },\n  { ROBOTIC_CMD_BLIND,              V1_U8 },\n  { ROBOTIC_CMD_FIREWALKER,         V1_U8 },\n  { ROBOTIC_CMD_FREEZETIME,         V1_U8 },\n  { ROBOTIC_CMD_SLOWTIME,           V1_U8 },\n  { ROBOTIC_CMD_WIND,               V1_U8 },\n  { ROBOTIC_CMD_AVALANCHE },\n  { ROBOTIC_CMD_COPY_DIR,           V1_DIRECTION, V1_DIRECTION },\n  { ROBOTIC_CMD_BECOME_LAVAWALKER },\n  { ROBOTIC_CMD_BECOME_NONLAVAWALKER },\n  { ROBOTIC_CMD_CHANGE,             V1_COLOR_WILDCARD, V1_THING, V1_ADD_PARAM,\n                                    V1_COLOR_WILDCARD, V1_THING, V1_PARAM_WILDCARD },\n  { ROBOTIC_CMD_PLAYERCOLOR,        V1_COLOR },\n  { ROBOTIC_CMD_BULLETCOLOR,        V1_COLOR },\n  { ROBOTIC_CMD_MISSILECOLOR,       V1_COLOR },\n  { ROBOTIC_CMD_MESSAGE_ROW,        V1_U8 },\n  { ROBOTIC_CMD_REL_SELF },\n  { ROBOTIC_CMD_REL_PLAYER },\n  { ROBOTIC_CMD_REL_COUNTERS },\n  { ROBOTIC_CMD_SET_ID_CHAR,        V1_16, V1_CHAR },\n  { ROBOTIC_CMD_JUMP_MOD_ORDER,     V1_U8 },\n  { ROBOTIC_CMD_ASK,                V1_STRING },\n  { ROBOTIC_CMD_FILLHEALTH },\n  { ROBOTIC_CMD_THICK_ARROW,        V1_DIRECTION, V1_CHAR },\n  { ROBOTIC_CMD_THIN_ARROW,         V1_DIRECTION, V1_CHAR },\n  { ROBOTIC_CMD_SET_MAX_HEALTH,     V1_16 },\n  { ROBOTIC_CMD_SAVE_PLAYER_POSITION },\n  { ROBOTIC_CMD_RESTORE_PLAYER_POSITION },\n  { ROBOTIC_CMD_EXCHANGE_PLAYER_POSITION },\n  { ROBOTIC_CMD_MESSAGE_COLUMN,     V1_U8 },\n  { ROBOTIC_CMD_CENTER_MESSAGE },\n  { ROBOTIC_CMD_CLEAR_MESSAGE },\n  { ROBOTIC_CMD_RESETVIEW },\n  { ROBOTIC_CMD_MOD_SAM,            V1_16, V1_U8 },\n  { ROBOTIC_CMD_VOLUME,             V1_STRING }, // cvt\n  { ROBOTIC_CMD_SCROLLBASE,         V1_COLOR },\n  { ROBOTIC_CMD_SCROLLCORNER,       V1_COLOR },\n  { ROBOTIC_CMD_SCROLLTITLE,        V1_COLOR },\n  { ROBOTIC_CMD_SCROLLPOINTER,      V1_COLOR },\n  { ROBOTIC_CMD_SCROLLARROW,        V1_COLOR },\n  { ROBOTIC_CMD_VIEWPORT,           V1_U8, V1_U8 },\n  { ROBOTIC_CMD_VIEWPORT_WIDTH,     V1_U8, V1_U8 },\n  { ROBOTIC_CMD_MESSAGE_COLUMN,     V1_STRING }, // cvt\n  { ROBOTIC_CMD_MESSAGE_ROW,        V1_STRING }, // cvt\n\n  /* The following future commands aren't supported by MegaZeux 1.x, but\n   * are allowed to make automated testing of 1.x worlds possible. */\n  [ROBOTIC_CMD_SWAP_WORLD] = { ROBOTIC_CMD_SWAP_WORLD, V1_STRING },\n};\n\nboolean legacy_convert_v1_program(char **_dest, int *_dest_len,\n int *_cur_prog_line, const char *src, int src_len)\n{\n  uint8_t buf[257];\n  size_t dest_len;\n  size_t dest_alloc;\n  int cur_prog_line = *_cur_prog_line;\n  int i;\n  char *dest;\n  char *tmp;\n\n  // Special case- replace very short programs with FF 00.\n  if(src_len <= 2)\n  {\n    dest = (char *)cmalloc(2);\n    if(!dest)\n      return false;\n\n    dest[0] = 0xff;\n    dest[1] = 0;\n    *_dest = dest;\n    *_dest_len = 2;\n    *_cur_prog_line = 0;\n    return true;\n  }\n\n  // Fix out-of-bounds cur_prog_line.\n  if(cur_prog_line < 0 || cur_prog_line >= src_len)\n  {\n    debug(\"Robot has out-of-bounds v1 cur_prog_line %d (len: %d)\\n\",\n     cur_prog_line, src_len);\n    cur_prog_line = 0;\n  }\n\n  dest_alloc = src_len * 2;\n  dest = (char *)cmalloc(dest_alloc);\n  if(!dest)\n    return false;\n\n  dest[0] = 0xff;\n  dest_len = 1;\n\n  for(i = 1; i + 3 < src_len && *src;)\n  {\n    int cmd_off = i;\n    int cmd_len = src[i++];\n    int cmd = src[i++];\n    int next_cmd = i + cmd_len;\n    int len = 1;\n    int j;\n\n    if(next_cmd >= src_len)\n    {\n      trace(\"invalid 1.x command: length extends past program, truncating\\n\");\n      break;\n    }\n\n    // All Robo-P commands past MESSAGE ROW \"str\" (167) should be treated as\n    // NOPs, unless some special forward compatibilty is needed (SWAP WORLD).\n    if(cmd > ROBOTIC_CMD_UNUSED_167 && cmd != ROBOTIC_CMD_SWAP_WORLD)\n      cmd = ROBOTIC_CMD_BLANK_LINE;\n\n    buf[len++] = v1_command_translation[cmd][0];\n\n    for(j = 1; v1_command_translation[cmd][j]; j++)\n    {\n      enum v1_params type = v1_command_translation[cmd][j];\n      switch(type)\n      {\n        case V1_16:\n        case V1_CONDITION:\n        {\n          if(i + 2 > src_len || len + 3 > 255)\n            goto err;\n\n          buf[len++] = 0;\n          buf[len++] = src[i++];\n          buf[len++] = src[i++];\n          break;\n        }\n\n        case V1_U8:\n        case V1_CHAR:\n        case V1_COLOR: // no thing for wildcard, or wildcard is ignored\n        case V1_DIRECTION:\n        case V1_ITEM:\n        case V1_EQUALITY:\n        {\n          if(i >= src_len || len + 3 > 255)\n            goto err;\n\n          buf[len++] = 0;\n          buf[len++] = src[i++];\n          buf[len++] = 0;\n          break;\n        }\n\n        case V1_STRING:\n        {\n          int start = len;\n          int str_len = 0;\n          // Reserve length byte.\n          len++;\n\n          while(len < 255 && i < src_len && src[i])\n          {\n            if(src[i] == '&')\n            {\n              // Convert & to &&. ver1to2 did this conditionally for (mainly)\n              // text displaying commands, but it really ought to be applied\n              // unconditionally.\n              buf[len++] = '&';\n              str_len++;\n\n              if(len >= 255)\n                goto err;\n            }\n            buf[len++] = src[i++];\n            str_len++;\n          }\n          if(len >= 255 || i >= src_len)\n            goto err;\n\n          buf[start] = str_len + 1;\n          buf[len++] = '\\0';\n          i++;\n          break;\n        }\n\n        case V1_COLOR_WILDCARD:\n        {\n          if(i >= src_len || len + 3 > 255)\n            goto err;\n\n          buf[len++] = 0;\n          buf[len++] = src[i++];\n\n          // bit 8 is stored as bit 7 of the thing\n          if(i + 1 <= src_len && v1_command_translation[cmd][j + 1] == V1_THING)\n            buf[len++] = src[i] >> 7;\n          else\n            buf[len++] = 0;\n          break;\n        }\n\n        case V1_THING:\n        {\n          if(i >= src_len || len + 3 > 255)\n            goto err;\n\n          // Bit 7 is used to store color bit 8 in some cases.\n          buf[len++] = 0;\n          buf[len++] = src[i++] & 0x7f;\n          buf[len++] = 0;\n          break;\n        }\n\n        case V1_PARAM:\n        case V1_PARAM_WILDCARD:\n        {\n          int param;\n          if(i >= src_len || len + 3 > 255)\n            goto err;\n          param = src[i++];\n\n          // p?? encodes to a value of 0(!), convert to 256.\n          // VER1TO2 did this unconditionally, which only worked because\n          // non-CHANGE replacement commands treated p?? like 0 until 2.80d.\n          if(type == V1_PARAM_WILDCARD && param == 0)\n            param = 256;\n\n          buf[len++] = 0;\n          buf[len++] = param & 0xff;\n          buf[len++] = param >> 8;\n          break;\n        }\n\n        case V1_ADD_PARAM:\n        {\n          if(len + 3 > 255)\n            goto err;\n\n          buf[len++] = 0;\n          buf[len++] = 0;\n          buf[len++] = (256 >> 8);\n          break;\n        }\n\n        case V1_8:\n        {\n          int16_t param;\n          if(i >= src_len || len + 3 > 255)\n            goto err;\n\n          param = (int8_t)src[i++];\n\n          buf[len++] = 0;\n          buf[len++] = param & 0xff;\n          buf[len++] = param >> 8;\n          break;\n        }\n\n        case V1_SPECIAL_CHAR_EDIT:\n        {\n          // Hack so the parameter type mapping table doesn't need to be huge.\n          int k;\n          for(k = 0; k < 14; k++)\n          {\n            if(i >= src_len || len + 3 > 255)\n              goto err;\n\n            buf[len++] = 0;\n            buf[len++] = src[i++];\n            buf[len++] = 0;\n          }\n          break;\n        }\n\n        case V1_SPECIAL_TOUCHING_DIR:\n        {\n          // Input is the DIR parameter of IF [NOT] PLAYER DIR \"label\".\n          // This gets converted to IF [NOT] TOUCHING DIR \"label\".\n          if(i >= src_len || len + 3 > 255)\n            goto err;\n\n          buf[len++] = 0;\n          buf[len++] = TOUCHING;\n          buf[len++] = src[i++];\n          break;\n        }\n\n        default:\n        {\n          warn(\"unhandled 1.x parameter type! FIXME!!!\\n\");\n          break;\n        }\n      }\n    }\n\n    if(i >= src_len || i >= next_cmd)\n    {\n      trace(\"invalid 1.x command: params too long, truncating.\\n\");\n      break;\n    }\n    // Chronos Stasis 1.01 has some unusual corrupted robots with broken\n    // trailing length bytes. This is safe to quietly fix.\n    if(src[next_cmd - 1] != cmd_len)\n      trace(\"invalid 1.x command: trailing length != starting length.\\n\");\n\n    // Convert v1 cur_prog_line to v2 cur_prog_line.\n    // Any value in the middle of a command is junk and should be filtered.\n    if(cur_prog_line > cmd_off && cur_prog_line < next_cmd)\n    {\n      debug(\"Robot has invalid v1 cur_prog_line %d @ offset %d (len: %d)\\n\",\n       cur_prog_line, cmd_off, src_len);\n      cur_prog_line = 0;\n    }\n    if(cur_prog_line == cmd_off)\n      cur_prog_line = dest_len;\n\n    i = next_cmd;\n\n    buf[0] = len - 1;\n    buf[len] = len - 1;\n    len++;\n\n    // +1 for program terminating 0.\n    if(dest_len + len + 1 > dest_alloc)\n    {\n      while(dest_len + len + 1 > dest_alloc)\n      {\n        if(dest_alloc > INT_MAX)\n          goto err;\n\n        dest_alloc *= 2;\n      }\n      tmp = (char *)crealloc(dest, dest_alloc);\n      if(!tmp)\n        goto err;\n\n      dest = tmp;\n    }\n    memcpy(dest + dest_len, buf, len);\n    dest_len += len;\n  }\n\n  dest[dest_len++] = '\\0';\n\n  tmp = (char *)crealloc(dest, dest_len);\n  if(tmp)\n    dest = tmp;\n\n  *_dest = dest;\n  *_dest_len = dest_len;\n  *_cur_prog_line = cur_prog_line;\n  return true;\n\nerr:\n  free(dest);\n  return false;\n}\n\nenum bytecode_fix_status\n{\n  BC_NO_ERROR,\n  BC_COULDNT_FIX,\n  BC_MISSING_TERMINATOR,\n  BC_WRONG_TERMINATOR,\n  BC_APPLIED_PATCH,\n  BC_REPLACED_WITH_NOP\n};\n\nstruct program_patch\n{\n  const char *bad_command;\n  const int bad_command_length;\n  const char *patch;\n  const int patch_length;\n  const boolean require_exact_command; // if false, the match can be a prefix.\n  const boolean is_end_of_program;\n};\n\nstatic const struct program_patch program_patches[] =\n{\n  // Fix the global robot from worlds produced by Mikawo's zzt2mzx generator.\n  // The correct end of the robot exists in the template board supplied with\n  // the converter, so it's known exactly what should be there.\n  {\n    \"\\x04\\x30\\x03\\x00\\x01\\x6E\", 6,\n    \"\\x04\\x30\\x00\\x26\\x00\\x04\"\n    \"\\x04\\x5B\\x00\\x0E\\x00\\x04\"\n    \"\\x01\\x00\\x01\"\n    \"\\x07\\x6A\\x05!^wk\\x00\\x07\"\n    \"\\x01\\x2C\\x01\"\n    \"\\x04\\x30\\x00\\x26\\x00\\x04\"\n    \"\\x04\\x5B\\x00\\x0F\\x00\\x04\"\n    \"\\x01\\x00\\x01\\x00\", 43,\n    true,\n    true\n  },\n  // Loco, board 2, 45 19\n  {\n    \"\\x04\\x1D\\x02\\x61\\x00\\x7E\", 6,\n    \"\\x04\\x1D\\x02\\x61\\x00\\x04\"\n    \"\\x04\\x6A\\x02\\x62\\x00\\x04\"\n    \"\\x19\\x4F\\x00\\x20\\x01\\x00\\x05\\x00\", 20,\n    true,\n    false\n  },\n  // Manuel the Manx, board 6, 3 25\n  // There's no recoverable data here, but this board is inaccessible anyway.\n  {\n    \"\\x0F\\x6E\\x00\\x04\\x00\\x00Aloopcoun\\x84\\x84\\x84\", 17,\n    \"\\x00\", 1,\n    true,\n    true\n  },\n  // Slave Pit global robot (no recoverable data)\n  {\n    \"\\xFF\\xFF\\xFF\\x00\\x00\\x00\\xFF\\xFF\\xFF\\x88\\x01\\x00\", 12,\n    \"\\x00\", 1,\n    false,\n    true\n  },\n};\n\nstatic void validate_legacy_bytecode_print(char *bc, int program_length,\n int offset)\n{\n#ifdef DEBUG\n  char *err_mesg;\n  char *pos;\n  int command_length;\n  int print_length;\n  int i;\n\n  if(offset > program_length)\n  {\n    fprintf(mzxerr, \"\\n\");\n    debug(\"Offset exceeded program length\\n\");\n    debug(\"Prog len: %d    Offset: %d\\n\", program_length, offset);\n    return;\n  }\n  else\n\n  if(offset == program_length)\n  {\n    debug(\"Offset reached end of program (length %d)\\n\", program_length);\n    return;\n  }\n\n  command_length = bc[offset];\n  print_length = MIN(command_length + 2, program_length - offset);\n\n  err_mesg = cmalloc(sizeof(char) * (print_length * 3 + 2));\n  err_mesg[0] = 0;\n  pos = err_mesg;\n\n  for(i = 0; i < print_length; i++)\n  {\n    snprintf(pos, 4, \"%X \", bc[offset + i]);\n    pos += strlen(pos);\n  }\n\n  debug(\"Prog len: %i    Offset: %i\\n\", program_length, offset);\n  debug(\"Bytecode: %s\\n\", err_mesg);\n  free(err_mesg);\n#endif\n}\n\nstatic enum bytecode_fix_status validate_legacy_bytecode_attempt_fix(char **_bc,\n int *_len, int offset)\n{\n  const struct program_patch *p;\n  char *bc = *_bc;\n  int program_length = *_len;\n  int command_length;\n  int left = program_length - offset;\n  size_t i;\n\n  // Can't do anything in this case.\n  if(offset > program_length)\n    return BC_COULDNT_FIX;\n\n  if(left == 0)\n  {\n    // Might just be a truncated last byte, which is an easy fix.\n    bc = crealloc(bc, program_length + 1);\n    bc[program_length] = '\\x00';\n    *_len = program_length + 1;\n    *_bc = bc;\n    debug(\"Added missing terminator to truncated program\\n\\n\");\n    return BC_MISSING_TERMINATOR;\n  }\n  else\n\n  if(left == 1)\n  {\n    // Could also be a wrong last byte (Sidewinder's Engines).\n    debug(\"Corrected program terminator %X\\n\", bc[offset]);\n    bc[offset] = '\\x00';\n    return BC_WRONG_TERMINATOR;\n  }\n  else\n\n  if(left == 2 && bc[offset + 1] == '\\x00')\n  {\n    // Due to Eternal Eclipse Taoyarin 1.0 consistently having these malformed\n    // bytecode endings, this seems to have been a general MZX bug. Just zero\n    // out the first byte and decrease the length by one.\n    debug(\"Corrected program terminator %X %X\\n\", bc[offset], bc[offset+1]);\n    bc[offset] = '\\x00';\n    *_len = program_length - 1;\n    return BC_WRONG_TERMINATOR;\n  }\n\n  // The rest of the fixes are more technical, so print detailed debug output.\n  validate_legacy_bytecode_print(bc, program_length, offset);\n\n  command_length = MIN(bc[offset] + 2, left);\n\n  // Check for specific patches. The bad command has to exactly match what's\n  // in the list for a patch to be applied.\n  for(i = 0; i < ARRAY_SIZE(program_patches); i++)\n  {\n    p = &program_patches[i];\n    if(left < p->bad_command_length || command_length < p->bad_command_length)\n      continue;\n\n    // If this is set, the match string can't be a prefix of the command.\n    if(p->require_exact_command && command_length > p->bad_command_length)\n      continue;\n\n    if(!memcmp(bc + offset, p->bad_command, p->bad_command_length))\n    {\n      int new_length = program_length;\n      boolean add_terminator = false;\n\n      if(p->is_end_of_program)\n        new_length += p->patch_length - left;\n\n      if(left <= p->patch_length && !p->is_end_of_program)\n      {\n        new_length++;\n        add_terminator = true;\n      }\n\n      if(new_length != program_length)\n        bc = crealloc(bc, new_length);\n\n      memcpy(bc + offset, p->patch, p->patch_length);\n\n      if(add_terminator)\n        bc[offset + p->patch_length] = '\\x00';\n\n      *_len = new_length;\n      *_bc = bc;\n      debug(\"Applied command patch %zu\\n\\n\", i);\n      return BC_APPLIED_PATCH;\n    }\n  }\n\n  // If the bad command is consistent with itself, NOP it.\n  if(bc[offset] + offset + 2 <= program_length)\n  {\n    int cmd_len = bc[offset];\n    int end_len = bc[cmd_len + offset + 1];\n\n    if(cmd_len > 0 && cmd_len == end_len)\n    {\n      bc[offset + 1] = ROBOTIC_CMD_BLANK_LINE;\n      debug(\"Replaced invalid command with NOP\\n\\n\");\n      return BC_REPLACED_WITH_NOP;\n    }\n  }\n\n  // Truncate the program and display an error.\n  // NOTE: returning this probably means this will get dummied out right now,\n  // so don't bother with reallocation.\n  //bc = crealloc(bc, offset + 1);\n  bc[offset] = '\\x00';\n  //*_bc = bc;\n  //*_len = offset + 1;\n  debug(\"Truncated program at invalid command\\n\\n\");\n  return BC_COULDNT_FIX;\n}\n\nstatic inline int check_numeric_param(const char *bc, int min, int max)\n{\n  if(bc[0] == 0)\n  {\n    int val = bc[1] | ((signed char)bc[2] << 8);\n    if(val >= min && val <= max)\n      return val;\n  }\n  return INT_MIN;\n}\n\nboolean validate_legacy_bytecode(char **_bc, int *_program_length,\n int *_cur_prog_line)\n{\n  char *bc = *_bc;\n  int program_length = *_program_length;\n  int cur_prog_line = _cur_prog_line ? *_cur_prog_line : 0;\n  int cur_command_start = 0, cur_command_length = 0, cur_param_length = 0;\n  int cur_command, p;\n  int i = 1;\n  enum bytecode_fix_status status = BC_NO_ERROR;\n\n  if(!bc)\n    return false;\n\n  // First -- fix the odd robots that appear in old MZX games,\n  // such as Forest of Ruin and Catacombs of Zeux.\n  if(program_length <= 2)\n  {\n    if(program_length < 2)\n    {\n      bc = crealloc(bc, 2);\n      program_length = 2;\n    }\n    bc[0] = 0xFF;\n    bc[1] = 0x0;\n  }\n\n  // Error out if the start of the program is invalid.\n  if(bc[0] != 0xFF)\n    status = BC_COULDNT_FIX;\n\n  // One iteration should be a single command.\n  while(status != BC_COULDNT_FIX)\n  {\n    cur_command_length = bc[i];\n    i++;\n    if(cur_command_length == 0)\n      break;\n\n    cur_command_start = i;\n\n    if(i + cur_command_length >= program_length ||\n     bc[i + cur_command_length] != cur_command_length)\n    {\n      i--;\n      status = validate_legacy_bytecode_attempt_fix(&bc, &program_length, i);\n      continue;\n    }\n\n    cur_command = bc[i];\n    i++;\n\n    for(p = 0; p < command_list[cur_command].parameters; p++)\n    {\n      int param_type = command_list[cur_command].param_types[p];\n\n      if((param_type & IGNORE_TYPE) || (param_type & CMD))\n        continue;\n\n      cur_param_length = bc[i];\n      if(cur_param_length == 0)\n        cur_param_length = 2;\n\n      // THINGs must be length 0 and smaller than 127\n      if((param_type & THING) && check_numeric_param(bc + i, 0, 127) < 0)\n      {\n        i = cur_command_start - 1;\n        status = validate_legacy_bytecode_attempt_fix(&bc, &program_length, i);\n        break;\n      }\n\n      // ITEMs must be length 0 and smaller than the number of ITEM types.\n      if((param_type & ITEM) && check_numeric_param(bc + i, 0, 8) < 0)\n      {\n        i = cur_command_start - 1;\n        status = validate_legacy_bytecode_attempt_fix(&bc, &program_length, i);\n        break;\n      }\n\n      i += cur_param_length + 1;\n    }\n\n    // Retry patched command...\n    if(i < cur_command_start)\n      continue;\n\n    if((i > cur_command_start + cur_command_length + 1) || (i > program_length))\n    {\n      i = cur_command_start - 1;\n      status = validate_legacy_bytecode_attempt_fix(&bc, &program_length, i);\n      continue;\n    }\n\n    // Now that the command is known to be roughly valid: cur_prog_line anywhere\n    // between the command byte and the second length byte is invalid.\n    if(cur_prog_line >= cur_command_start &&\n     cur_prog_line <= cur_command_start + cur_command_length)\n    {\n      debug(\"Robot has invalid cur_prog_line %d @ offset %d (len: %d)\\n\",\n       cur_prog_line, cur_command_start - 1, *_program_length);\n      cur_prog_line = 0;\n    }\n\n    i = cur_command_start + cur_command_length + 1;\n  }\n\n  if((status != BC_COULDNT_FIX) && (i < program_length))\n  {\n    debug(\"Robot checked for %i but program length is %i; extra removed\\n\",\n     program_length, i);\n\n    bc = crealloc(bc, i);\n    program_length = i;\n  }\n\n  *_bc = bc;\n  *_program_length = program_length;\n\n  if((status == BC_COULDNT_FIX) || (i > program_length))\n    return false;\n\n  // Fix out-of-bounds cur_prog_line too.\n  if(cur_prog_line < 0 || cur_prog_line >= program_length)\n  {\n    debug(\"Robot has out-of-bounds cur_prog_line %d (old len:%d new len:%d)\\n\",\n     cur_prog_line, *_program_length, program_length);\n    cur_prog_line = 0;\n  }\n\n  if(_cur_prog_line)\n    *_cur_prog_line = cur_prog_line;\n\n  return true;\n}\n"
  },
  {
    "path": "src/legacy_rasm.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __LEGACY_RASM_H\n#define __LEGACY_RASM_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stdio.h>\n\n#define MAX_OBJ_SIZE       2097152\n\n#define S_IMM_U16          0\n#define S_IMM_S16          0\n#define S_CHARACTER        2\n#define S_COLOR            3\n#define S_DIR              4\n#define S_THING            5\n#define S_PARAM            6\n#define S_STRING           7\n#define S_EQUALITY         8\n#define S_CONDITION        9\n#define S_ITEM             10\n#define S_EXTRA            11\n#define S_CMD              12\n#define S_UNDEFINED        13\n\nstruct search_entry\n{\n  const char *const name;\n  int count;\n  const int offsets[19];\n};\n\nstruct search_entry_short\n{\n  const char *const name;\n  int offset;\n  int type;\n};\n\n// Maps bytecode positions to source code positions.\nstruct command_mapping\n{\n  int real_line;\n  int bc_pos;\n  int src_pos;\n};\n\nCORE_LIBSPEC int get_color(char *cmd_line);\n\nextern const char special_first_char[256];\n\n// This command will reallocate the program as-needed, but it won't free it.\nboolean validate_legacy_bytecode(char **_bc, int *_program_length,\n int *_cur_prog_line);\nboolean legacy_convert_v1_program(char **_dest, int *_dest_len,\n int *_cur_prog_line, const char *src, int src_len);\n\n#ifdef CONFIG_DEBYTECODE\n\nCORE_LIBSPEC int legacy_assemble_line(char *cpos, char *output_buffer, char *error_buffer,\n char *param_listing, int *arg_count_ext);\n\n#else // !CONFIG_DEBYTECODE\n\nchar *assemble_file(char *name, int *size);\nchar *assemble_program(char *src, int len, int *size);\n\nvoid disassemble_file(char *name, char *program, int program_length,\n int allow_ignores, int base);\nCORE_LIBSPEC void disassemble_program(char *program, int program_length,\n char **_source, int *_source_length, struct command_mapping **_command_map,\n int *_command_map_length);\n\n#ifdef CONFIG_EDITOR\nCORE_LIBSPEC int legacy_assemble_line(char *cpos, char *output_buffer,\n char *error_buffer, char *param_listing, int *arg_count_ext);\nCORE_LIBSPEC int disassemble_line(char *cpos, char **next, char *output_buffer,\n char *error_buffer, int *total_bytes, int print_ignores, char *arg_types,\n int *arg_count, int base);\nCORE_LIBSPEC const struct search_entry_short *find_argument(char *name);\nCORE_LIBSPEC void print_color(int color, char *color_buffer);\nCORE_LIBSPEC int unescape_char(char *dest, char c);\n#endif // CONFIG_EDITOR\n\n#endif // !CONFIG_DEBYTECODE\n\n__M_END_DECLS\n\n#endif // __LEGACY_RASM_H\n"
  },
  {
    "path": "src/legacy_robot.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"legacy_robot.h\"\n\n#include \"const.h\"\n#include \"error.h\"\n#include \"legacy_rasm.h\"\n#include \"rasm.h\"\n#include \"robot.h\"\n#include \"util.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n#include \"io/memfile.h\"\n#include \"io/vio.h\"\n\n/* This is the largest allowed stack size in MZX 2.84X, in case the maximum\n * stack size ever gets increased for some reason. */\n#define LEGACY_ROBOT_MAX_STACK 65536\n\nstatic boolean legacy_load_robot_v1(struct world *mzx_world, struct robot *cur_robot,\n vfile *vf, int savegame, int file_version)\n{\n  int robot_location = vftell(vf);\n  char *program_v1 = NULL;\n  char *program_v2 = NULL;\n  int program_v1_length;\n  int program_v2_length;\n\n  create_blank_robot(cur_robot);\n  cur_robot->world_version = mzx_world->version;\n\n  program_v1_length = vfgetw(vf);\n  vfgetw(vf); // Unused high bytes\n  vfgetd(vf); // Junk\n\n  if(!vfread(cur_robot->robot_name, LEGACY_ROBOT_NAME_SIZE, 1, vf))\n    return false;\n\n  cur_robot->robot_name[LEGACY_ROBOT_NAME_SIZE - 1] = '\\0';\n\n  cur_robot->robot_char = vfgetc(vf);\n  cur_robot->cur_prog_line = vfgetw(vf);\n  vfgetw(vf); // Unused high bytes\n  cur_robot->pos_within_line = vfgetc(vf);\n  cur_robot->robot_cycle = vfgetc(vf);\n  cur_robot->cycle_count = vfgetc(vf);\n  cur_robot->bullet_type = vfgetc(vf);\n  cur_robot->is_locked = vfgetc(vf);\n  cur_robot->can_lavawalk = vfgetc(vf);\n  cur_robot->walk_dir = (enum dir)vfgetc(vf);\n  cur_robot->last_touch_dir = (enum dir)vfgetc(vf);\n  cur_robot->last_shot_dir = (enum dir)vfgetc(vf);\n\n  if(!program_v1_length)\n  {\n    create_blank_robot_program(cur_robot);\n    return true;\n  }\n\n  program_v1 = (char *)cmalloc(program_v1_length);\n  if(!vfread(program_v1, program_v1_length, 1, vf))\n  {\n    trace(\"failed to read v1 program (len %d) @ %d\\n\", program_v1_length, robot_location);\n    goto err;\n  }\n\n  // Convert v1 program to v2.\n  if(!legacy_convert_v1_program(&program_v2, &program_v2_length,\n   &cur_robot->cur_prog_line, program_v1, program_v1_length))\n  {\n    trace(\"legacy convert v1 program failed @ %d\\n\", robot_location);\n    goto err;\n  }\n\n  free(program_v1);\n  program_v1 = NULL;\n\n  if(!validate_legacy_bytecode(&program_v2, &program_v2_length,\n   &cur_robot->cur_prog_line))\n  {\n    trace(\"validate legacy bytecode failed @ %d\\n\", robot_location);\n    goto err;\n  }\n\n#ifdef CONFIG_DEBYTECODE\n\n  // TODO: now convert v2 to source. This multi-step conversion\n  // mess should be replaced with a v1 bytecode frontend.\n  cur_robot->program_source =\n   legacy_disassemble_program(program_v2, program_v2_length,\n   &(cur_robot->program_source_length), true, 10);\n\n  if(cur_robot->cur_prog_line)\n  {\n    // Translate the legacy current bytecode offset and stack bytecode offsets\n    // into usable new bytecode offsets. This may compile the robot program.\n    translate_robot_bytecode_offsets(mzx_world, cur_robot, file_version);\n  }\n  free(program_v2);\n  program_v2 = NULL;\n\n#else\n\n  cur_robot->program_bytecode = program_v2;\n  cur_robot->program_bytecode_length = program_v2_length;\n  cache_robot_labels(cur_robot);\n\n#endif\n\n  return true;\n\nerr:\n  error_message(E_BOARD_ROBOT_CORRUPT, robot_location, NULL);\n  free(program_v1);\n  free(program_v2);\n  cur_robot->used = 0;\n  return false;\n}\n\nstruct robot *legacy_load_robot_allocate(struct world *mzx_world, vfile *vf,\n int savegame, int file_version, boolean *truncated)\n{\n  struct robot *cur_robot = (struct robot *)cmalloc(sizeof(struct robot));\n\n  cur_robot->stack = NULL;\n  cur_robot->label_list = NULL;\n  cur_robot->program_bytecode = NULL;\n  cur_robot->program_source = NULL;\n  cur_robot->num_labels = 0;\n\n  if(file_version < V200)\n  {\n    if(!legacy_load_robot_v1(mzx_world, cur_robot, vf, savegame, file_version))\n      *truncated = true;\n  }\n  else\n\n  if(!legacy_load_robot(mzx_world, cur_robot, vf, savegame, file_version))\n    *truncated = true;\n\n  return cur_robot;\n}\n\n// Most of this stuff does not have to be loaded unless savegame\n// is set.\nvoid legacy_load_robot_from_memory(struct world *mzx_world,\n struct robot *cur_robot, struct memfile *mf, int savegame, int version,\n int robot_location)\n{\n  int program_length;\n  int xpos, ypos;\n  int i;\n\n  cur_robot->world_version = mzx_world->version;\n\n  cur_robot->stack = NULL;\n  cur_robot->label_list = NULL;\n  cur_robot->program_bytecode = NULL;\n  cur_robot->program_source = NULL;\n  cur_robot->num_labels = 0;\n\n  program_length = mfgetw(mf);\n  // Skip DOS program pointer field.\n  mf->current += 2;\n\n#ifdef CONFIG_EDITOR\n  cur_robot->command_map = NULL;\n  cur_robot->command_map_length = 0;\n\n  cur_robot->commands_total = 0;\n  cur_robot->commands_cycle = 0;\n  cur_robot->commands_caught = 0;\n#endif\n\n  mfread(cur_robot->robot_name, LEGACY_ROBOT_NAME_SIZE, 1, mf);\n  cur_robot->robot_name[LEGACY_ROBOT_NAME_SIZE - 1] = '\\0';\n\n  cur_robot->robot_char = mfgetc(mf);\n  cur_robot->cur_prog_line = mfgetw(mf);\n  cur_robot->pos_within_line = mfgetc(mf);\n  cur_robot->robot_cycle = mfgetc(mf);\n  cur_robot->cycle_count = mfgetc(mf);\n  cur_robot->bullet_type = mfgetc(mf);\n  cur_robot->is_locked = mfgetc(mf);\n  cur_robot->can_lavawalk = mfgetc(mf);\n  cur_robot->walk_dir = (enum dir)mfgetc(mf);\n  cur_robot->last_touch_dir = (enum dir)mfgetc(mf);\n  cur_robot->last_shot_dir = (enum dir)mfgetc(mf);\n  xpos = mfgetw(mf);\n  ypos = mfgetw(mf);\n  cur_robot->status = mfgetc(mf);\n  // Skip local - these are in the save files now\n  mf->current += 2;\n  cur_robot->used = mfgetc(mf);\n  if(version <= V283)\n    cur_robot->loop_count = mfgetw(mf);\n  else\n    mf->current += 2;\n\n  // Fix xpos and ypos for global robot\n  xpos = (xpos >= 32768) ? -1 : xpos;\n  ypos = (ypos >= 32768) ? -1 : ypos;\n  cur_robot->xpos = xpos;\n  cur_robot->ypos = ypos;\n  cur_robot->compat_xpos = xpos;\n  cur_robot->compat_ypos = ypos;\n\n  // If savegame, there's some additional information to get\n  if(savegame)\n  {\n    int stack_skip = 0;\n    int stack_size;\n\n    if(version >= V284)\n      cur_robot->loop_count = mfgetd(mf);\n\n    for(i = 0; i < 32; i++)\n      cur_robot->local[i] = mfgetd(mf);\n\n    // Double so we can fit pos_within_line values in.\n    stack_size = mfgetd(mf) * 2;\n    cur_robot->stack_pointer = mfgetd(mf) * 2;\n\n    // Don't allow the stack size to be loaded over the maximum.\n    if(stack_size > LEGACY_ROBOT_MAX_STACK)\n    {\n      stack_skip = LEGACY_ROBOT_MAX_STACK - stack_size;\n      stack_size = LEGACY_ROBOT_MAX_STACK;\n    }\n    else\n\n    if(stack_size < 0)\n      stack_size = 0;\n\n    // Also don't allow stack positions beyond the size of the stack.\n    cur_robot->stack_pointer = CLAMP(cur_robot->stack_pointer, 0, stack_size);\n\n    cur_robot->stack = (int *)ccalloc(stack_size, sizeof(int));\n    for(i = 0; i < stack_size; i += 2)\n    {\n      cur_robot->stack[i] = mfgetd(mf);\n      cur_robot->stack[i+1] = 0;\n    }\n\n    // In the (unlikely) event the saved stack size was larger than the maximum,\n    // the unused stack values need to be skipped...\n    mf->current += 4 * (stack_skip / 2);\n\n    cur_robot->stack_size = stack_size;\n  }\n  else\n  {\n    // Otherwise, allocate some stuff; local counters are 0\n    cur_robot->loop_count = 0;\n    memset(cur_robot->local, 0, sizeof(int) * 32);\n\n    // Give an empty stack.\n    cur_robot->stack_size = 0;\n    cur_robot->stack = NULL;\n\n    // Initialize the stack pointer to the bottom\n    cur_robot->stack_pointer = 0;\n  }\n\n#ifdef CONFIG_DEBYTECODE\n\n  // The program is legacy bytecode and we have to convert it to source.\n  cur_robot->program_bytecode = NULL;\n  cur_robot->program_bytecode_length = 0;\n\n  if((cur_robot->used) || (program_length >= 2))\n  {\n    char *program_legacy_bytecode = (char *)cmalloc(program_length);\n    mfread(program_legacy_bytecode, program_length, 1, mf);\n\n    if(!validate_legacy_bytecode(&program_legacy_bytecode, &program_length,\n     &(cur_robot->cur_prog_line)))\n    {\n      free(program_legacy_bytecode);\n      goto err_invalid;\n    }\n\n    cur_robot->program_source =\n     legacy_disassemble_program(program_legacy_bytecode, program_length,\n     &(cur_robot->program_source_length), true, 10);\n\n    free(program_legacy_bytecode);\n\n    // Translate the legacy current bytecode offset and stack bytecode offsets\n    // into usable new bytecode offsets. This may compile the robot program.\n    translate_robot_bytecode_offsets(mzx_world, cur_robot, version);\n  }\n  else\n  {\n    // Skip over program, we're not going to use it.\n    mf->current += program_length;\n\n    create_blank_robot_program(cur_robot);\n    cur_robot->cur_prog_line = 0;\n  }\n\n#else /* !CONFIG_DEBYTECODE */\n\n  cur_robot->program_bytecode_length = program_length;\n  if(program_length > 0)\n  {\n    cur_robot->program_bytecode = (char *)cmalloc(program_length);\n    mfread(cur_robot->program_bytecode, program_length, 1, mf);\n\n    if(!validate_legacy_bytecode(&cur_robot->program_bytecode, &program_length,\n     &(cur_robot->cur_prog_line)))\n    {\n      // Only error for used robots; unused robots don't really matter and\n      // in some games (Slave Pit, Wes) may have garbage programs.\n      if(cur_robot->used)\n        goto err_invalid;\n\n      debug(\"silently ignoring unused corrupt robot @ %4.4X\\n\", robot_location);\n      clear_robot_contents(cur_robot);\n      create_blank_robot(cur_robot);\n      create_blank_robot_program(cur_robot);\n      cur_robot->cur_prog_line = 0;\n    }\n    else\n      cur_robot->program_bytecode_length = program_length;\n\n    // The stack must also be checked for invalid offsets.\n    fix_robot_stack_offsets(cur_robot);\n\n    // Now create a label cache IF the robot is in use\n    if(cur_robot->used)\n      cache_robot_labels(cur_robot);\n  }\n#endif /* !CONFIG_DEBYTECODE */\n\n  return;\n\nerr_invalid:\n  // Don't worry about the unused source and bytecode\n  // that were allocated, they'll be cleared later.\n  error_message(E_BOARD_ROBOT_CORRUPT, robot_location, NULL);\n\n  clear_robot_contents(cur_robot);\n  create_blank_robot(cur_robot);\n  create_blank_robot_program(cur_robot);\n  strcpy(cur_robot->robot_name, \"<<error>>\");\n  cur_robot->cur_prog_line = 0;\n}\n\n/**\n * Calculate the amount of a robot that needs to be read in order to\n * calculate its size.\n */\nsize_t legacy_calculate_partial_robot_size(int savegame, int version)\n{\n  size_t partial_robot_size = 41;\n\n  if(savegame)\n  {\n    if(version >= V284)\n      partial_robot_size += 4; // loopcount\n    partial_robot_size += 32 * 4; // 32 local counters\n    partial_robot_size += 2 * 4; // stack size and position\n  }\n  return partial_robot_size;\n}\n\n/**\n * This function is used to calculate a robot's total size based on an\n * incomplete load of that robot. This allows the robot size to be\n * calculated before the entire thing is loaded.\n */\nsize_t legacy_load_robot_calculate_size(const void *buffer, int savegame,\n int version)\n{\n  struct memfile mf;\n  int program_length;\n  int stack_size;\n\n  // Base robot size\n  size_t robot_size = legacy_calculate_partial_robot_size(savegame, version);\n\n  // First, read the program length (0 bytes in)\n  mfopen(buffer, robot_size, &mf);\n  program_length = mfgetw(&mf);\n  mfgetw(&mf);\n\n  // Next, if this is a savegame robot, read the stack size\n  if(savegame)\n  {\n    mfseek(&mf, robot_size - 8, SEEK_SET);\n    stack_size = mfgetd(&mf);\n\n    // Don't allow the stack size to be loaded over the maximum.\n    // Nothing valid should ever be higher than this and since the full robot\n    // is being allocated based on this, invalid robots need to be truncated.\n    if(stack_size > LEGACY_ROBOT_MAX_STACK)\n    {\n      return robot_size;\n    }\n    else\n\n    if(stack_size < 0)\n      stack_size = 0;\n\n    robot_size += stack_size * 4;\n  }\n\n  robot_size += program_length;\n  return robot_size;\n}\n\nboolean legacy_load_robot(struct world *mzx_world, struct robot *cur_robot,\n vfile *vf, int savegame, int version)\n{\n  int robot_location = vftell(vf);\n  size_t partial_size = legacy_calculate_partial_robot_size(savegame, version);\n  char *buffer = (char *)cmalloc(partial_size);\n  boolean truncated = true;\n  size_t full_size;\n\n  if(vfread(buffer, partial_size, 1, vf))\n  {\n    full_size = legacy_load_robot_calculate_size(buffer, savegame, version);\n    buffer = crealloc(buffer, full_size);\n\n    if((partial_size == full_size) ||\n     vfread(buffer + partial_size, full_size - partial_size, 1, vf))\n      truncated = false;\n  }\n\n  create_blank_robot(cur_robot);\n\n  if(truncated)\n  {\n    error_message(E_BOARD_ROBOT_CORRUPT, robot_location, NULL);\n    create_blank_robot_program(cur_robot);\n    strcpy(cur_robot->robot_name, \"<<error>>\");\n    vfseek(vf, 0, SEEK_END);\n  }\n  else\n  {\n    struct memfile mf;\n    mfopen(buffer, full_size, &mf);\n    legacy_load_robot_from_memory(mzx_world, cur_robot, &mf, savegame,\n     version, robot_location);\n  }\n  free(buffer);\n  return !truncated;\n}\n\n/**\n * Convert v1 scroll text to scroll text with color code support.\n *\n * 1.x uses a bizarre color code hack where lines can start with ![byte].\n * The entire line will be colored [byte] (including non-text areas) and the\n * color code will be displayed as two spaces.\n *\n * Convert these to Robotic ~@-style color codes, pad their affected lines with\n * spaces so they display correctly, and escape existing ~@s.\n */\nstatic boolean legacy_convert_scroll_v1(struct scroll *cur_scroll)\n{\n  char *old_text = cur_scroll->mesg;\n  char *new_text;\n  size_t old_size = cur_scroll->mesg_size;\n  size_t new_size;\n  size_t new_pos;\n  size_t i;\n\n  new_text = (char *)cmalloc(old_size * 2);\n  new_size = old_size * 2;\n  if(!new_text)\n    goto err;\n\n  new_text[0] = 0x01;\n  new_pos = 1;\n\n  for(i = 1; i < old_size && old_text[i];)\n  {\n    // Start of line.\n    char linebuf[256];\n    int pad = 0;\n    size_t j = 0;\n\n    if(old_text[i] == '!')\n    {\n      // Legacy v1 color code.\n      static const char hex[] = \"0123456789abcdef\";\n      uint8_t color = old_text[i + 1];\n      if(!color)\n        goto err;\n\n      linebuf[j++] = '~';\n      linebuf[j++] = hex[color & 0xf];\n      linebuf[j++] = '@';\n      linebuf[j++] = hex[color >> 4];\n      linebuf[j++] = ' ';\n      linebuf[j++] = ' ';\n      i += 2;\n      pad = 62;\n    }\n\n    // Direct line copy with escapes for ~ and @.\n    while(old_text[i])\n    {\n      if(j >= sizeof(linebuf) - 3)\n        goto err;\n\n      if(old_text[i] == '\\n')\n      {\n        i++;\n        break;\n      }\n\n      if(old_text[i] == '~')\n        linebuf[j++] = '~';\n\n      if(old_text[i] == '@')\n        linebuf[j++] = '@';\n\n      linebuf[j++] = old_text[i++];\n      pad--;\n    }\n    for(; pad > 0; pad--)\n      linebuf[j++] = ' ';\n\n    linebuf[j++] = '\\n';\n\n    // Append line (+1 for scroll terminator).\n    if(j + new_pos + 1 > new_size)\n    {\n      char *t;\n      while(j + new_pos + 1 > new_size)\n      {\n        if(new_size > INT_MAX)\n          goto err;\n\n        new_size *= 2;\n      }\n      t = (char *)crealloc(new_text, new_size);\n      if(!t)\n        goto err;\n\n      new_text = t;\n    }\n    memcpy(new_text + new_pos, linebuf, j);\n    new_pos += j;\n  }\n  new_text[new_pos++] = '\\0';\n\n  cur_scroll->mesg = (char *)crealloc(new_text, new_pos);\n  cur_scroll->mesg_size = new_pos;\n  if(!cur_scroll->mesg)\n    goto err;\n\n  free(old_text);\n  return true;\n\nerr:\n  free(old_text);\n  free(new_text);\n  cur_scroll->mesg = NULL;\n  return false;\n}\n\nstatic void legacy_load_scroll(struct scroll *cur_scroll, vfile *vf, int file_version)\n{\n  int scroll_size;\n\n  cur_scroll->mesg = NULL;\n  cur_scroll->num_lines = vfgetw(vf);\n\n  if(file_version >= V200)\n  {\n    vfgetw(vf); // Skip junk\n    scroll_size = vfgetw(vf);\n    cur_scroll->used = vfgetc(vf);\n  }\n  else\n  {\n    vfgetd(vf); // Skip junk\n    scroll_size = vfgetd(vf);\n    cur_scroll->used = 1;\n  }\n\n  if(scroll_size < 0 || scroll_size > 65535)\n    goto scroll_err;\n\n  cur_scroll->mesg_size = scroll_size;\n  cur_scroll->mesg = (char *)cmalloc(scroll_size);\n  if(!vfread(cur_scroll->mesg, scroll_size, 1, vf))\n    goto scroll_err;\n\n  if(scroll_size < 3)\n    goto scroll_err;\n\n  if(file_version < V200)\n  {\n    if(!legacy_convert_scroll_v1(cur_scroll))\n      goto scroll_err;\n  }\n  else\n    cur_scroll->mesg[scroll_size - 1] = '\\0';\n\n  return;\n\nscroll_err:\n  // Something screwed up, slip in an empty scroll.\n  cur_scroll->num_lines = 1;\n  cur_scroll->mesg_size = 3;\n  cur_scroll->used = 1;\n\n  free(cur_scroll->mesg);\n  cur_scroll->mesg = (char *)cmalloc(3);\n  strcpy(cur_scroll->mesg, \"\\x01\\x0A\");\n}\n\nstruct scroll *legacy_load_scroll_allocate(vfile *vf, int file_version)\n{\n  struct scroll *cur_scroll = (struct scroll *)cmalloc(sizeof(struct scroll));\n  legacy_load_scroll(cur_scroll, vf, file_version);\n  return cur_scroll;\n}\n\nstatic void legacy_load_sensor(struct sensor *cur_sensor, vfile *vf, int file_version)\n{\n  if(!vfread(cur_sensor->sensor_name, LEGACY_ROBOT_NAME_SIZE, 1, vf))\n    goto sensor_err;\n\n  cur_sensor->sensor_char = vfgetc(vf);\n\n  if(!vfread(cur_sensor->robot_to_mesg, LEGACY_ROBOT_NAME_SIZE, 1, vf))\n    goto sensor_err;\n\n  cur_sensor->sensor_name[LEGACY_ROBOT_NAME_SIZE - 1] = '\\0';\n  cur_sensor->robot_to_mesg[LEGACY_ROBOT_NAME_SIZE - 1] = '\\0';\n\n  if(file_version >= V200)\n    cur_sensor->used = vfgetc(vf);\n  else\n    cur_sensor->used = 1;\n\n  return;\n\nsensor_err:\n  // Something screwed up, insert filler\n  strcpy(cur_sensor->sensor_name, \"\");\n  strcpy(cur_sensor->robot_to_mesg, \"\");\n  cur_sensor->sensor_char = 'S';\n  cur_sensor->used = 1;\n}\n\nstruct sensor *legacy_load_sensor_allocate(vfile *vf, int file_version)\n{\n  struct sensor *cur_sensor = (struct sensor *)cmalloc(sizeof(struct sensor));\n  legacy_load_sensor(cur_sensor, vf, file_version);\n  return cur_sensor;\n}\n"
  },
  {
    "path": "src/legacy_robot.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __LEGACY_ROBOT_H\n#define __LEGACY_ROBOT_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"const.h\"\n#include \"world_struct.h\"\n#include \"io/vfile.h\"\n\nstruct robot *legacy_load_robot_allocate(struct world *mzx_world, vfile *vf,\n int savegame, int file_version, boolean *truncated);\n\nvoid legacy_load_robot_from_memory(struct world *mzx_world,\n struct robot *cur_robot, struct memfile *mf, int savegame, int version,\n int robot_location);\n\nsize_t legacy_calculate_partial_robot_size(int savegame, int version);\n\nsize_t legacy_load_robot_calculate_size(const void *buffer, int savegame,\n int version);\n\nboolean legacy_load_robot(struct world *mzx_world, struct robot *cur_robot,\n vfile *vf, int savegame, int version);\n\nstruct scroll *legacy_load_scroll_allocate(vfile *vf, int file_version);\n\nstruct sensor *legacy_load_sensor_allocate(vfile *vf, int file_version);\n\n__M_END_DECLS\n\n#endif // __LEGACY_ROBOT_H\n"
  },
  {
    "path": "src/legacy_world.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/stat.h>\n\n#include \"legacy_world.h\"\n#include \"legacy_board.h\"\n#include \"legacy_robot.h\"\n\n#include \"configure.h\"\n#include \"const.h\"\n#include \"counter.h\"\n#include \"error.h\"\n#include \"extmem.h\"\n#include \"graphics.h\"\n#include \"idput.h\"\n#include \"robot.h\"\n#include \"sprite.h\"\n#include \"str.h\"\n#include \"window.h\"\n#include \"world.h\"\n#include \"util.h\"\n#include \"io/path.h\"\n#include \"io/vio.h\"\n\n#include \"audio/sfx.h\"\n\n#define WORLD_GLOBAL_OFFSET_OFFSET 4230\n#define WORLD_V1_BOARD_NUM_OFFSET 4009\n#define WORLD_BLOCK_1_SIZE 4129\n#define WORLD_BLOCK_2_SIZE 72\n#define DECRYPT_BUFFER_SIZE 8192\n\n#ifndef CONFIG_LOADSAVE_METER\n\nstatic inline void meter_update_screen(int *curr, int target) {}\nstatic inline void meter_restore_screen(void) {}\nstatic inline void meter_initial_draw(int curr, int target, const char *title) {}\n\n#endif //!CONFIG_LOADSAVE_METER\n\nstatic inline boolean legacy_load_counter(struct world *mzx_world,\n vfile *vf, struct counter_list *counter_list, int index, int file_version)\n{\n  char name_buffer[ROBOT_MAX_TR];\n  int name_length;\n  int value;\n\n  if(file_version >= V281)\n  {\n    value = vfgetd(vf);\n    name_length = vfgetd(vf);\n\n    name_buffer[0] = 0;\n    if(name_length && !vfread(name_buffer, name_length, 1, vf))\n      return false;\n\n    if(file_version >= V284)\n    {\n      // Stupid legacy hacks\n      if(!strncasecmp(name_buffer, \"mzx_speed\", name_length))\n      {\n        mzx_world->mzx_speed = value;\n        return false;\n      }\n\n      if(!strncasecmp(name_buffer, \"_____lock_speed\", name_length))\n      {\n        mzx_world->lock_speed = value;\n        return false;\n      }\n    }\n  }\n  else\n  {\n    name_length = vfread(name_buffer, 1, 15, vf);\n    name_buffer[name_length] = '\\0';\n\n    if(file_version == V280)\n    {\n      // MegaZeux 2.80 used a hacked DOS counter list with dwords.\n      value = vfgetd(vf);\n    }\n    else\n\n    if(file_version >= V200)\n    {\n      // Counters are signed words in DOS 2.x.\n      value = (signed short)vfgetw(vf);\n    }\n    else\n    {\n      // Counters are unsigned words in 1.x.\n      value = vfgetw(vf);\n    }\n  }\n\n  load_new_counter(counter_list, index, name_buffer, name_length, value);\n  return true;\n}\n\nstatic void legacy_load_counter_list(struct world *mzx_world, vfile *vf,\n int file_version, int extra)\n{\n  struct counter_list *counter_list;\n  int num_counters;\n  int i;\n  int j;\n\n  num_counters = vfgetd(vf);\n  counter_list = &(mzx_world->counter_list);\n  counter_list->num_counters = num_counters;\n  counter_list->num_counters_allocated = num_counters + extra;\n  counter_list->counters = ccalloc(num_counters + extra, sizeof(struct counter *));\n\n  for(i = 0, j = 0; i < num_counters; i++)\n  {\n    if(!legacy_load_counter(mzx_world, vf, counter_list, j, file_version))\n    {\n      // Remove 2.84 special counters and counters that failed to load.\n      counter_list->num_counters--;\n      continue;\n    }\n    j++;\n  }\n#ifndef CONFIG_COUNTER_HASH_TABLES\n  // Versions without the hash table require these to be sorted at all times.\n  sort_counter_list(counter_list);\n#endif\n}\n\nstatic inline void legacy_load_string(vfile *vf,\n struct string_list *string_list, int index)\n{\n  char name_buffer[ROBOT_MAX_TR];\n  int name_length = vfgetd(vf);\n  int str_length = vfgetd(vf);\n\n  struct string *src_string;\n\n  name_buffer[0] = 0;\n  if(name_length && !vfread(name_buffer, name_length, 1, vf))\n    return;\n\n  src_string = load_new_string(string_list, index,\n   name_buffer, name_length, str_length);\n\n  if(str_length && !vfread(src_string->value, str_length, 1, vf))\n    return;\n}\n\nstatic void legacy_load_string_list(struct world *mzx_world, vfile *vf,\n int file_version)\n{\n  struct string_list *string_list;\n  int num_strings;\n  int i;\n\n  num_strings = vfgetd(vf);\n  string_list = &(mzx_world->string_list);\n  string_list->num_strings = num_strings;\n  string_list->num_strings_allocated = num_strings;\n  string_list->strings = ccalloc(num_strings, sizeof(struct string *));\n\n  for(i = 0; i < num_strings; i++)\n    legacy_load_string(vf, string_list, i);\n\n#ifndef CONFIG_COUNTER_HASH_TABLES\n  // Versions without the hash table require these to be sorted at all times.\n  sort_string_list(string_list);\n#endif\n}\n\nstatic const char magic_code[16] =\n \"\\xE6\\x52\\xEB\\xF2\\x6D\\x4D\\x4A\\xB7\\x87\\xB2\\x92\\x88\\xDE\\x91\\x24\";\n\n#define MAX_PASSWORD_LENGTH 15\n\nstatic int get_pw_xor_code(char *password, int pro_method)\n{\n  int work = 85; // Start with 85... (01010101)\n  size_t i;\n  // Clear pw after first null\n\n  for(i = strlen(password); i < MAX_PASSWORD_LENGTH; i++)\n  {\n    password[i] = 0;\n  }\n\n  for(i = 0; i < MAX_PASSWORD_LENGTH; i++)\n  {\n    //For each byte, roll once to the left and xor in pw byte if it\n    //is an odd character, or add in pw byte if it is an even character.\n    work <<= 1;\n    if(work > 255)\n      work ^= 257; // Wraparound from roll\n\n    if(i & 1)\n    {\n      work += (signed char)password[i]; // Add (even byte)\n      if(work > 255)\n        work ^= 257; // Wraparound from add\n    }\n    else\n    {\n      work ^= (signed char)password[i]; // XOR (odd byte);\n    }\n  }\n  // To factor in protection method, add it in and roll one last time\n  work += pro_method;\n  if(work > 255)\n    work ^= 257;\n\n  work <<= 1;\n  if(work > 255)\n    work ^= 257;\n\n  // Can't be 0-\n  if(work == 0)\n    work = 86; // (01010110)\n  // Done!\n  return work;\n}\n\nstruct decrypt_data\n{\n  vfile *source;\n  vfile *dest;\n  const char *file_name;\n  size_t source_length;\n  unsigned int xor_val;\n  int xor_w;\n  int xor_d;\n  char password[MAX_PASSWORD_LENGTH + 1];\n  char backup_name[MAX_PATH];\n  char buffer[DECRYPT_BUFFER_SIZE];\n  int meter_curr;\n  int meter_target;\n};\n\nstatic long decrypt_backup(struct decrypt_data *data)\n{\n  vfile *source = NULL;\n  vfile *dest = NULL;\n  long ret = -1;\n  long file_length;\n  long left;\n  int meter_target = 1;\n  int meter_curr = 0;\n  int len;\n\n  meter_initial_draw(meter_curr, meter_target, \"Writing backup world...\");\n\n  source = vfopen_unsafe(data->file_name, \"rb\");\n  if(!source)\n    goto err;\n\n  dest = vfopen_unsafe(data->backup_name, \"wb\");\n  if(!dest)\n    goto err;\n\n  file_length = vfilelength(source, false);\n  meter_target = file_length;\n\n  left = file_length;\n  while(left > 0)\n  {\n    len = MIN(left, DECRYPT_BUFFER_SIZE);\n    left -= len;\n\n    if(!vfread(data->buffer, len, 1, source))\n      goto err;\n\n    if(!vfwrite(data->buffer, len, 1, dest))\n      goto err;\n\n    meter_curr += len - 1;\n    meter_update_screen(&meter_curr, meter_target);\n  }\n\n  ret = file_length;\n\nerr:\n  if(source)\n    vfclose(source);\n  if(dest)\n    vfclose(dest);\n\n  meter_restore_screen();\n  return ret;\n}\n\nstatic boolean decrypt_block(struct decrypt_data *data, int block_len)\n{\n  int len;\n  int i;\n\n  while(block_len > 0)\n  {\n    len = MIN(block_len, DECRYPT_BUFFER_SIZE);\n    block_len -= len;\n\n    if(!vfread(data->buffer, len, 1, data->source))\n      return false;\n\n    for(i = 0; i < len; i++)\n      data->buffer[i] ^= data->xor_val;\n\n    if(!vfwrite(data->buffer, len, 1, data->dest))\n      return false;\n\n    data->meter_curr += len - 1;\n    meter_update_screen(&data->meter_curr, data->meter_target);\n  }\n  return true;\n}\n\nstatic boolean decrypt_and_fix_offset(struct decrypt_data *data)\n{\n  long offset = vfgetd(data->source);\n  if(offset == EOF)\n    return false;\n\n  offset ^= data->xor_d;\n\n  // Adjust the offset to account for removing the password...\n  offset -= MAX_PASSWORD_LENGTH;\n\n  vfputd(offset, data->dest);\n\n  data->meter_curr += 4 - 1;\n  meter_update_screen(&data->meter_curr, data->meter_target);\n  return true;\n}\n\nstatic boolean decrypt(struct decrypt_data *data)\n{\n  int pro_method;\n  int i;\n  int num_boards;\n  char *src_ptr;\n  char *magic_pos;\n\n  data->meter_target = (data->source_length - MAX_PASSWORD_LENGTH);\n  data->meter_curr = 0;\n  meter_initial_draw(data->meter_curr, data->meter_target, \"Decrypting...\");\n\n  if(!vfread(data->buffer, 44, 1, data->source))\n    goto err_meter;\n\n  src_ptr = data->buffer + LEGACY_BOARD_NAME_SIZE;\n  pro_method = *src_ptr;\n  src_ptr++;\n\n  // Get password\n  memcpy(data->password, src_ptr, MAX_PASSWORD_LENGTH);\n  data->password[MAX_PASSWORD_LENGTH] = '\\0';\n  // First, normalize password...\n  for(i = 0; i < MAX_PASSWORD_LENGTH; i++)\n  {\n    data->password[i] ^= magic_code[i];\n    data->password[i] -= 0x12 + pro_method;\n    data->password[i] ^= 0x8D;\n  }\n\n  // Xor code\n  data->xor_val = get_pw_xor_code(data->password, pro_method);\n  data->xor_w = data->xor_val | (data->xor_val << 8);\n  data->xor_d = data->xor_val | (data->xor_val << 8) |\n   (data->xor_val << 16) | (data->xor_val << 24);\n\n  // Copy title\n  if(!vfwrite(data->buffer, LEGACY_BOARD_NAME_SIZE, 1, data->dest))\n    goto err_meter;\n\n  // Protection method.\n  vfputc(0, data->dest);\n\n  // Copy magic.\n  magic_pos = data->buffer + LEGACY_BOARD_NAME_SIZE + 1 + MAX_PASSWORD_LENGTH;\n  if(!vfwrite(magic_pos, 3, 1, data->dest))\n    goto err_meter;\n\n  data->meter_curr += LEGACY_BOARD_NAME_SIZE + 1 + 3 - 1;\n  meter_update_screen(&data->meter_curr, data->meter_target);\n\n  // Decrypt world data.\n  if(!decrypt_block(data, WORLD_BLOCK_1_SIZE + WORLD_BLOCK_2_SIZE))\n    goto err_meter;\n\n  // Decrypt and fix global robot offset.\n  if(!decrypt_and_fix_offset(data))\n    goto err_meter;\n\n  // Decrypt SFX table (if present).\n  num_boards = vfgetc(data->source) ^ data->xor_val;\n  vfputc(num_boards, data->dest);\n  data->meter_curr++;\n  if(!num_boards)\n  {\n    int sfx_length = vfgetw(data->source) ^ data->xor_w;\n    vfputw(sfx_length, data->dest);\n    data->meter_curr += 2;\n\n    if(!decrypt_block(data, sfx_length))\n      goto err_meter;\n\n    num_boards = vfgetc(data->source) ^ data->xor_val;\n    vfputc(num_boards, data->dest);\n    data->meter_curr++;\n  }\n\n  // Decrypt board titles.\n  if(!decrypt_block(data, LEGACY_BOARD_NAME_SIZE * num_boards))\n    goto err_meter;\n\n  // Decrypt and fix board table.\n  for(i = 0; i < num_boards; i++)\n  {\n    // Board length.\n    int board_length = vfgetd(data->source) ^ data->xor_d;\n    vfputd(board_length, data->dest);\n    data->meter_curr += 4;\n\n    // Board offset.\n    if(!decrypt_and_fix_offset(data))\n      goto err_meter;\n  }\n\n  // Decrypt the rest of the file.\n  i = vftell(data->source);\n  if(!decrypt_block(data, data->source_length - i))\n    goto err_meter;\n\n  meter_restore_screen();\n  return true;\n\nerr_meter:\n  meter_restore_screen();\n  return false;\n}\n\nstatic vfile *legacy_decrypt_world_to_temp_file(vfile *source)\n{\n  struct decrypt_data *data;\n  vfile *dest;\n  size_t source_length = vfilelength(source, true);\n\n  debug(\"Attempting decrypt: to temporary file.\\n\");\n  dest = vtempfile(source_length - MAX_PASSWORD_LENGTH);\n  if(!dest)\n  {\n    debug(\"Attempting decrypt: to _DECRYPT.TMP\\n\");\n    dest = vfopen_unsafe(\"_DECRYPT.TMP\", \"wb+\");\n    if(!dest)\n      return NULL;\n  }\n\n  data = ccalloc(1, sizeof(struct decrypt_data));\n\n  data->source_length = source_length;\n  data->source = source;\n  data->dest = dest;\n\n  if(!decrypt(data))\n  {\n    vfclose(data->dest);\n    free(data);\n    return NULL;\n  }\n  free(data);\n  return dest;\n}\n\nstatic vfile *legacy_decrypt_world(const char *file_name)\n{\n  struct decrypt_data *data;\n  vfile *dest;\n  int file_length;\n\n  data = ccalloc(1, sizeof(struct decrypt_data));\n\n  data->file_name = file_name;\n  snprintf(data->backup_name, MAX_PATH, \"%.*s.locked\", MAX_PATH - 8, file_name);\n  debug(\"Attempting decrypt: to '%s'.\\n\", data->backup_name);\n\n  file_length = decrypt_backup(data);\n  if(file_length < 0)\n  {\n    // Try a shorter name...\n    ptrdiff_t pos = strrchr(file_name, '.') - file_name;\n    if(pos >= 0 && pos < MAX_PATH)\n    {\n      data->backup_name[pos] = '\\0';\n\n      if(path_force_ext(data->backup_name, MAX_PATH, \".LCK\"))\n      {\n        debug(\"Attempting decrypt: to '%s'.\\n\", data->backup_name);\n        file_length = decrypt_backup(data);\n      }\n    }\n\n    if(file_length < 0)\n    {\n      free(data);\n      return NULL;\n    }\n  }\n\n  data->source_length = file_length;\n  data->source = vfopen_unsafe(data->backup_name, \"rb\");\n  if(!data->source)\n    goto err_free;\n\n  data->dest = vfopen_unsafe_ext(file_name, \"wb\", V_SMALL_BUFFER);\n  if(!data->dest)\n  {\n    error_message(E_WORLD_DECRYPT_WRITE_PROTECTED, 0, NULL);\n    goto err_free;\n  }\n\n  if(!decrypt(data))\n    goto err_free;\n\n  dest = data->dest;\n  vfclose(data->source);\n  free(data);\n  return dest;\n\nerr_free:\n  if(data->source)\n    vfclose(data->source);\n  if(data->dest)\n    vfclose(data->dest);\n\n  free(data);\n  return NULL;\n}\n\n\n/* Validate that this file is a world or save file within reasonable doubt.\n * This needs to be done before any data is ever loaded so that Megazeux can\n * cleanly abort if there is an issue.\n *\n * There are a few redundant checks here with try_load_world, but that's ok.\n */\nstatic enum val_result __validate_legacy_world_file(vfile *vf, boolean savegame)\n{\n  char magic[15];\n  int num_boards;\n  int board_name_offset;\n  int v, i;\n\n  /* TEST 2:  Is it a save file? */\n  if(savegame)\n  {\n    int screen_mode, num_counters, num_strings, len;\n\n    if(vfread(magic, 5, 1, vf) != 1)\n      goto err_invalid;\n\n    v = save_magic(magic);\n    if(!v)\n    {\n      goto err_invalid;\n    }\n    else\n\n    if(v > MZX_LEGACY_FORMAT_VERSION)\n    {\n      error_message(E_SAVE_VERSION_TOO_RECENT, v, NULL);\n      return VAL_VERSION;\n    }\n    else\n\n    // This enables 2.84 save loading.\n    // This can be expanded to even older save files in the future:\n    // 2.00-2.83 saves require various types of surgery to the load functions.\n    // TODO: 1.xx saves are implemented but this function needs an overhaul.\n    if(v < MZX_LEGACY_FORMAT_VERSION)\n    {\n      error_message(E_SAVE_VERSION_OLD, v, NULL);\n      return VAL_VERSION;\n    }\n\n    /* TEST 3:  Check for truncation, savegame style. */\n    if(vfseek(vf, 8 + WORLD_BLOCK_1_SIZE + 71, SEEK_SET))\n    {\n      debug(\"save block 1\\n\");\n      goto err_invalid;\n    }\n    len = vfgetw(vf);\n    if(len < 0 || vfseek(vf, len + WORLD_BLOCK_2_SIZE + 24, SEEK_CUR))\n    {\n      debug(\"save block 2\\n\");\n      goto err_invalid;\n    }\n\n    //do counters - vvvvnnnn(name)\n    num_counters = vfgetd(vf);\n    if(num_counters < 0)\n    {\n      debug(\"counter num\\n\");\n      goto err_invalid;\n    }\n\n    for(i = 0; i < num_counters; i++)\n    {\n      vfgetd(vf);\n      len = vfgetd(vf);\n\n      if(len < 0 || len >= ROBOT_MAX_TR)\n      {\n        debug(\"counter %d: name length=%d\\n\", i, len);\n        goto err_invalid;\n      }\n      if(vfseek(vf, len, SEEK_CUR))\n      {\n        debug(\"counter %d name\\n\", i);\n        goto err_invalid;\n      }\n    }\n\n    //do strings-   nnnnllll(name)(value)\n    num_strings = vfgetd(vf);\n    if(num_strings < 0)\n    {\n      debug(\"string num\\n\");\n      goto err_invalid;\n    }\n\n    for(i = 0; i < num_strings; i++)\n    {\n      int name_length = vfgetd(vf);\n      int value_length = vfgetd(vf);\n      if(value_length < 0 || name_length < 0 || name_length >= ROBOT_MAX_TR)\n      {\n        debug(\"string %d: value length=%d, name length=%d\\n\",\n         i, value_length, name_length);\n        goto err_invalid;\n      }\n      if(vfseek(vf, name_length + value_length, SEEK_CUR))\n      {\n        debug(\"string %d name+value\\n\", i);\n        goto err_invalid;\n      }\n    }\n\n    if(\n     vfseek(vf, 4612, SEEK_CUR) || //sprites\n     vfseek(vf, 12, SEEK_CUR) || //misc\n     vfseek(vf, vfgetw(vf), SEEK_CUR) || //fread_open\n     vfseek(vf, 4, SEEK_CUR) || //fread_pos\n     vfseek(vf, vfgetw(vf), SEEK_CUR) || //fwrite_open\n     vfseek(vf, 4, SEEK_CUR)) //fwrite_pos\n    {\n      debug(\"post strings\\n\");\n      goto err_invalid;\n    }\n\n    screen_mode = vfgetw(vf);\n    if((screen_mode < 0) || (screen_mode > 3) || (screen_mode > 1 &&\n     vfseek(vf, 768, SEEK_CUR))) //smzx palette\n    {\n      debug(\"smzx palette\\n\");\n      goto err_invalid;\n    }\n\n    if(\n     vfseek(vf, 4, SEEK_CUR) || //commands\n     ((len = vfgetd(vf)) < 0) || //vlayer size\n     vfseek(vf, 4, SEEK_CUR) || // width & height\n     vfseek(vf, len, SEEK_CUR) || //chars\n     vfseek(vf, len, SEEK_CUR)) //colors\n    {\n      debug(\"vlayer\\n\");\n      goto err_invalid;\n    }\n\n    /* Global robot pointer */\n    if(vfgetd(vf) < 0)\n      goto err_invalid;\n  }\n\n  else /* !savegame */\n  {\n    int protection_method;\n\n    if(vfseek(vf, LEGACY_BOARD_NAME_SIZE, SEEK_SET))\n    {\n      debug(\"world name\\n\");\n      goto err_invalid;\n    }\n\n    /* TEST 4:  If it's locked, try to decrypt it. */\n    protection_method = vfgetc(vf);\n    if(protection_method != 0)\n    {\n      if(protection_method < 0 || protection_method > 3)\n        goto err_invalid;\n\n      /**\n       * Check the version to make sure this file is a non-corrupted MZX file.\n       * The MZX 2.x format stores a 15 byte password followed by the version.\n       * The MZX 1.x format stores a 1 byte password length, a fixed 15 byte\n       * password buffer, and then the version. In either case, the version is\n       * not encrypted.\n       */\n      vfseek(vf, LEGACY_BOARD_NAME_SIZE + 1 + MAX_PASSWORD_LENGTH, SEEK_SET);\n      if(!vfread(magic, 4, 1, vf))\n        goto err_invalid;\n\n      v = world_magic(magic);\n      if(v < V200)\n      {\n        v = world_magic(magic + 1);\n        if(v != V100)\n          goto err_invalid;\n\n        vfseek(vf, -3, SEEK_CUR);\n      }\n      /* 2.x encrypts locked worlds. */\n      else\n        return VAL_PROTECTED;\n    }\n\n    /* TEST 5:  Test the magic */\n    if(!vfread(magic, 3, 1, vf))\n      goto err_invalid;\n\n    v = world_magic(magic);\n    if(v == 0)\n    {\n      goto err_invalid;\n    }\n    else\n\n    if(v < V100)\n    {\n      error_message(E_WORLD_FILE_VERSION_OLD, v, NULL);\n      return VAL_VERSION;\n    }\n    else\n\n    if(v > MZX_LEGACY_FORMAT_VERSION)\n    {\n      error_message(E_WORLD_FILE_VERSION_TOO_RECENT, v, NULL);\n      return VAL_VERSION;\n    }\n\n    if(v >= V200)\n    {\n      /* TEST 6:  Attempt to eliminate invalid files by\n       * testing the palette for impossible values.\n       */\n      vfseek(vf, WORLD_GLOBAL_OFFSET_OFFSET - 48, SEEK_SET);\n      for(i = 0; i < 48; i++)\n      {\n        int val = vfgetc(vf);\n        if(val < 0 || val > 63)\n          goto err_invalid;\n      }\n\n      /* Global robot pointer */\n      if(vfgetd(vf) < 0)\n        goto err_invalid;\n    }\n    else\n    {\n      /* 1.x doesn't have a palette or global robot pointer to check. */\n      long offset = WORLD_V1_BOARD_NUM_OFFSET;\n      if(protection_method)\n        offset += MAX_PASSWORD_LENGTH + 1;\n\n      if(vfseek(vf, offset, SEEK_SET))\n        goto err_invalid;\n    }\n  }\n\n  /* TEST 7:  All branches should be at the board count now.\n   * Test for valid SFX structure, if applicable, and board information.\n   */\n  num_boards = vfgetc(vf);\n  if(num_boards == 0 && v >= V200)\n  {\n    int sfx_size = vfgetw(vf);\n    int sfx_read = 0;\n    int cur_sfx_size;\n\n    for(i = 0; i < NUM_BUILTIN_SFX; i++)\n    {\n      cur_sfx_size = vfgetc(vf);\n      if(cur_sfx_size < 0 || cur_sfx_size > LEGACY_SFX_SIZE)\n        goto err_invalid;\n\n      if(vfseek(vf, cur_sfx_size, SEEK_CUR))\n        break;\n\n      sfx_read += cur_sfx_size + 1;\n    }\n\n    if(i != NUM_BUILTIN_SFX || sfx_read != sfx_size)\n      goto err_invalid;\n\n    num_boards = vfgetc(vf);\n  }\n  if(num_boards <= 0)\n    goto err_invalid;\n\n  // Read the last board size and pointer to make sure the board table exists.\n  board_name_offset = num_boards * (LEGACY_BOARD_NAME_SIZE + 8) - 8;\n\n  if(vfseek(vf, board_name_offset, SEEK_CUR))\n    goto err_invalid;\n\n  if(vfgetd(vf) == EOF || vfgetd(vf) == EOF)\n    goto err_invalid;\n\n  return VAL_SUCCESS;\n\nerr_invalid:\n  if(savegame)\n    error_message(E_SAVE_FILE_INVALID, 0, NULL);\n  else\n    error_message(E_WORLD_FILE_INVALID, 0, NULL);\n  return VAL_INVALID;\n}\n\nvfile *validate_legacy_world_file(struct world *mzx_world,\n const char *file, boolean savegame)\n{\n  struct stat stat_result;\n  enum val_result res;\n  vfile *vf;\n\n  /* TEST 1: make sure it's even a file. */\n  if(vstat(file, &stat_result) || !S_ISREG(stat_result.st_mode))\n  {\n    error_message(E_FILE_DOES_NOT_EXIST, 0, NULL);\n    return NULL;\n  }\n\n  vf = vfopen_unsafe_ext(file, \"rb\", V_LARGE_BUFFER);\n  if(!vf)\n  {\n    error_message(E_IO_READ, 0, NULL);\n    return NULL;\n  }\n\n  res = __validate_legacy_world_file(vf, savegame);\n  if(res == VAL_SUCCESS)\n    return vf;\n\n  if(res == VAL_PROTECTED)\n  {\n    if(get_config()->auto_decrypt_worlds || !has_video_initialized())\n    {\n      // Attempt to decrypt to a temporary file.\n      vfile *tmp = legacy_decrypt_world_to_temp_file(vf);\n      if(tmp)\n      {\n        vfclose(vf);\n        return tmp;\n      }\n      error_message(E_IO_WRITE, 0, NULL);\n      return NULL;\n    }\n\n    if(!confirm(mzx_world, \"This world may be password protected. Decrypt it?\"))\n    {\n      vfclose(vf);\n\n      // Decrypt and try again\n      vf = legacy_decrypt_world(file);\n      if(!vf)\n      {\n        error_message(E_IO_READ, 0, NULL);\n        return NULL;\n      }\n\n      res = __validate_legacy_world_file(vf, savegame);\n      if(res == VAL_SUCCESS)\n        return vf;\n    }\n    if(res == VAL_PROTECTED)\n      error_message(E_WORLD_LOCKED, 0, NULL);\n  }\n  vfclose(vf);\n  return NULL;\n}\n\n/**\n * Common v1 and v2 board list loader.\n */\nstatic void legacy_load_world_boards(struct world *mzx_world, vfile *vf,\n boolean savegame, int file_version, int num_boards)\n{\n  unsigned int *board_offsets;\n  unsigned int *board_sizes;\n  long board_names_pos;\n  int i;\n\n  int meter_target = 2 + num_boards;\n  int meter_curr = 0;\n\n  meter_update_screen(&meter_curr, meter_target);\n\n  mzx_world->num_boards = num_boards;\n  mzx_world->num_boards_allocated = num_boards;\n  mzx_world->board_list = cmalloc(sizeof(struct board *) * num_boards);\n\n  // Skip the names for now\n  board_names_pos = vftell(vf);\n  vfseek(vf, num_boards * LEGACY_BOARD_NAME_SIZE, SEEK_CUR);\n\n  // Read the board offsets/sizes preemptively to reduce the amount of seeking.\n  board_offsets = cmalloc(sizeof(int) * num_boards);\n  board_sizes = cmalloc(sizeof(int) * num_boards);\n  for(i = 0; i < num_boards; i++)\n  {\n    board_sizes[i] = vfgetd(vf);\n    board_offsets[i] = vfgetd(vf);\n  }\n\n  for(i = 0; i < num_boards; i++)\n  {\n    mzx_world->board_list[i] =\n     legacy_load_board_allocate(mzx_world, vf, board_offsets[i], board_sizes[i],\n      savegame, file_version);\n\n    if(mzx_world->board_list[i])\n    {\n      // Also patch a pointer to the global robot\n      if(mzx_world->board_list[i]->robot_list)\n        (mzx_world->board_list[i])->robot_list[0] = &mzx_world->global_robot;\n\n      // Also optimize out null objects\n      optimize_null_objects(mzx_world->board_list[i]);\n\n      store_board_to_extram(mzx_world->board_list[i]);\n    }\n\n    meter_update_screen(&meter_curr, meter_target);\n  }\n\n  free(board_offsets);\n  free(board_sizes);\n\n  // Go back to where the names are\n  vfseek(vf, board_names_pos, SEEK_SET);\n  for(i = 0; i < num_boards; i++)\n  {\n    char ignore[LEGACY_BOARD_NAME_SIZE];\n    char *board_name = ignore;\n\n    if(mzx_world->board_list[i])\n      board_name = mzx_world->board_list[i]->board_name;\n\n    if(!vfread(board_name, LEGACY_BOARD_NAME_SIZE, 1, vf))\n      board_name[0] = 0;\n\n    board_name[LEGACY_BOARD_NAME_SIZE - 1] = '\\0';\n  }\n}\n\n/**\n * Load a world in the extremely legacy 1.x world format.\n */\nstatic void legacy_load_world_v1(struct world *mzx_world, vfile *vf, const char *file,\n boolean savegame, int file_version, char *name, boolean *faded)\n{\n  static const uint8_t v1_id_dmg[ID_DMG_SIZE] =\n  {\n    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, /* 0x0? */\n    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,100,  0,  0,  0,  0,  0, /* 0x1? */\n    0,  0,  0,  0,  0,  0,  5,  0,  0,  0,  0,  0,  0,  0,  0,  0, /* 0x2? */\n    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10,  0, 10,  5,  5, /* 0x3? */\n    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10, 10,  0, 10, 10, /* 0x4? */\n    10, 0,  0, 10, 10, 10, 10, 10, 10, 10, 10, 10,  0,  0, 10, 10, /* 0x5? */\n  };\n\n  unsigned char *charset_mem;\n  int protection_method;\n  int num_boards;\n  long offset;\n  int score = 0;\n  int i;\n  char buf[4];\n\n  int meter_target = 2, meter_curr = 0;\n\n  mzx_world->version = file_version;\n  mzx_world->current_board_id = 0;\n\n  if(savegame)\n  {\n    vfseek(vf, 5, SEEK_SET);\n    mzx_world->version = file_version;\n    mzx_world->current_board_id = vfgetc(vf);\n  }\n  else\n  {\n    strcpy(mzx_world->name, name);\n\n    vfseek(vf, BOARD_NAME_SIZE, SEEK_SET);\n    protection_method = vfgetc(vf);\n\n    offset = BOARD_NAME_SIZE + 1 + 3;\n    if(protection_method)\n      offset += MAX_PASSWORD_LENGTH + 1;\n    vfseek(vf, offset, SEEK_SET);\n  }\n\n  meter_initial_draw(meter_curr, meter_target, \"Loading...\");\n\n  charset_mem = cmalloc(CHAR_SIZE * CHARSET_SIZE);\n  if(!vfread(charset_mem, CHAR_SIZE * CHARSET_SIZE, 1, vf))\n  {\n    free(charset_mem);\n    goto err_close;\n  }\n  ec_clear_set();\n  ec_mem_load_set(charset_mem, CHAR_SIZE * CHARSET_SIZE);\n  free(charset_mem);\n\n  // id_chars.\n  memset(id_chars, 0, ID_CHARS_SIZE);\n  memcpy(id_dmg, v1_id_dmg, ID_DMG_SIZE);\n  memset(bullet_color, 0, ID_BULLET_COLOR_SIZE);\n\n  if(!vfread(id_chars, 306, 1, vf))\n    goto err_close;\n  // Goop didn't exist in 1.x.\n  id_chars[GOOP] = 176;\n\n  if(savegame)\n  {\n    if(!vfread(mzx_world->keys, NUM_KEYS, 1, vf))\n      goto err_close;\n  }\n\n  // Status counters.\n  if(vfread((char *)mzx_world->status_counters_shown, COUNTER_NAME_SIZE, 4, vf) != 4)\n    goto err_close;\n\n  for(i = 0; i < 4; i++)\n    mzx_world->status_counters_shown[i][COUNTER_NAME_SIZE - 1] = '\\0';\n  for(; i < NUM_STATUS_COUNTERS; i++)\n    mzx_world->status_counters_shown[i][0] = '\\0';\n\n  // Junk that was later moved to id_chars.\n  if(!vfread(buf, 4, 1, vf))\n    goto err_close;\n  memcpy(id_chars + bullet_char, buf, 4);\n  memcpy(id_chars + bullet_char + 4, buf, 4);\n  memcpy(id_chars + bullet_char + 8, buf, 4);\n  buf[0] = vfgetc(vf);\n  memset(id_chars + player_char, buf[0], 4);\n\n  // Saved position.\n  memset(mzx_world->pl_saved_x, 0, sizeof(mzx_world->pl_saved_x));\n  memset(mzx_world->pl_saved_y, 0, sizeof(mzx_world->pl_saved_y));\n  memset(mzx_world->pl_saved_board, 0, sizeof(mzx_world->pl_saved_board));\n  mzx_world->pl_saved_x[0] = vfgetc(vf);\n  mzx_world->pl_saved_y[0] = vfgetc(vf);\n  mzx_world->pl_saved_board[0] = vfgetc(vf);\n\n  // Global info.\n  mzx_world->edge_color = vfgetc(vf);\n\n  // More junk that was later moved to id_chars.\n  id_chars[player_color] = vfgetc(vf);\n  i = vfgetc(vf);\n  memset(bullet_color, i, sizeof(bullet_color));\n  missile_color = vfgetc(vf);\n\n  if(savegame)\n  {\n    mzx_world->scroll_base_color = vfgetc(vf);\n    mzx_world->scroll_corner_color = vfgetc(vf);\n    mzx_world->scroll_pointer_color = vfgetc(vf);\n    mzx_world->scroll_title_color = vfgetc(vf);\n    mzx_world->scroll_arrow_color = vfgetc(vf);\n    score = vfgetd(vf);\n  }\n\n  // Global info.\n  mzx_world->first_board = vfgetc(vf);\n  mzx_world->endgame_board = vfgetc(vf);\n  mzx_world->death_board = vfgetc(vf);\n  mzx_world->endgame_x = vfgetc(vf);\n  mzx_world->endgame_y = vfgetc(vf);\n  mzx_world->death_x = vfgetc(vf);\n  mzx_world->death_y = vfgetc(vf);\n  mzx_world->starting_lives = vfgetw(vf);\n  mzx_world->lives_limit = vfgetw(vf);\n  mzx_world->starting_health = vfgetw(vf);\n  mzx_world->health_limit = vfgetw(vf);\n  mzx_world->enemy_hurt_enemy = vfgetc(vf);\n  mzx_world->clear_on_exit = 0;\n  mzx_world->only_from_swap = 0;\n  mzx_world->game_over_sfx = vfgetc(vf);\n  vfgetc(vf); // unused\n\n  // Fix special values for these.\n  if(mzx_world->endgame_board >= 128)\n    mzx_world->endgame_board = NO_ENDGAME_BOARD;\n\n  if(mzx_world->death_board >= 129)\n    mzx_world->death_board = DEATH_SAME_POS;\n  else\n  if(mzx_world->death_board == 128)\n    mzx_world->death_board = NO_DEATH_BOARD;\n\n  if(savegame)\n  {\n    // Counter list.\n    // Allocate 1 extra for the score.\n    legacy_load_counter_list(mzx_world, vf, file_version, 1);\n\n    // Patch in the score.\n    set_counter(mzx_world, \"SCORE\", score, 0);\n  }\n\n  // No custom palette or global robot in 1.x.\n  *faded = 0;\n  default_palette();\n  create_blank_robot(&mzx_world->global_robot);\n  create_blank_robot_program(&mzx_world->global_robot);\n\n  // Board list is the same as 2.x, though the board structs are different.\n  num_boards = vfgetc(vf);\n  legacy_load_world_boards(mzx_world, vf, savegame, file_version, num_boards);\n\n  meter_restore_screen();\n  vfclose(vf);\n  return;\n\nerr_close:\n  // Note that this file had already been successfully validated for length\n  // and opened with no issue before this error occurred, and that the only\n  // way to reach this error is a failed fread before any board/robot data\n  // was loaded. Something seriously went wrong somewhere.\n\n  error_message(E_IO_READ, 0, NULL);\n  meter_restore_screen();\n  vfclose(vf);\n}\n\n/**\n * Load a world or save file from the legacy world format.\n * This will close the provided vfile handle.\n */\nvoid legacy_load_world(struct world *mzx_world, vfile *vf, const char *file,\n boolean savegame, int file_version, char *name, boolean *faded)\n{\n  int i;\n  int num_boards;\n  int global_robot_pos;\n  unsigned char *charset_mem;\n  unsigned char r, g, b;\n\n  int meter_target = 2, meter_curr = 0;\n\n  if(file_version < V200)\n  {\n    /* Too many differences to properly version here :( */\n    legacy_load_world_v1(mzx_world, vf, file, savegame, file_version, name, faded);\n    return;\n  }\n\n  if(savegame)\n  {\n    vfseek(vf, 5, SEEK_SET);\n    mzx_world->version = vfgetw(vf);\n    mzx_world->current_board_id = vfgetc(vf);\n  }\n  else\n  {\n    vfseek(vf, 29, SEEK_SET);\n    strcpy(mzx_world->name, name);\n    mzx_world->version = file_version;\n    mzx_world->current_board_id = 0;\n  }\n\n  meter_initial_draw(meter_curr, meter_target, \"Loading...\");\n\n  charset_mem = cmalloc(CHAR_SIZE * CHARSET_SIZE);\n  if(!vfread(charset_mem, CHAR_SIZE * CHARSET_SIZE, 1, vf))\n  {\n    free(charset_mem);\n    goto err_close;\n  }\n  ec_clear_set();\n  ec_mem_load_set(charset_mem, CHAR_SIZE * CHARSET_SIZE);\n  free(charset_mem);\n\n  // Idchars array...\n  memset(id_chars, 0, ID_CHARS_SIZE);\n  memset(id_dmg, 0, ID_DMG_SIZE);\n  memset(bullet_color, 0, ID_BULLET_COLOR_SIZE);\n\n  if(!vfread(id_chars, LEGACY_ID_CHARS_SIZE, 1, vf))\n    goto err_close;\n  missile_color = vfgetc(vf);\n  if(!vfread(bullet_color, LEGACY_ID_BULLET_COLOR_SIZE, 1, vf))\n    goto err_close;\n  if(!vfread(id_dmg, LEGACY_ID_DMG_SIZE, 1, vf))\n    goto err_close;\n\n  // Status counters...\n  if(vfread((char *)mzx_world->status_counters_shown, COUNTER_NAME_SIZE,\n   NUM_STATUS_COUNTERS, vf) != NUM_STATUS_COUNTERS)\n    goto err_close;\n\n  // Don't trust legacy null termination...\n  for(i = 0; i < NUM_STATUS_COUNTERS; i++)\n    mzx_world->status_counters_shown[i][COUNTER_NAME_SIZE - 1] = '\\0';\n\n  if(savegame)\n  {\n    if(!vfread(mzx_world->keys, NUM_KEYS, 1, vf))\n      goto err_close;\n    mzx_world->blind_dur = vfgetc(vf);\n    mzx_world->firewalker_dur = vfgetc(vf);\n    mzx_world->freeze_time_dur = vfgetc(vf);\n    mzx_world->slow_time_dur = vfgetc(vf);\n    mzx_world->wind_dur = vfgetc(vf);\n\n    for(i = 0; i < 8; i++)\n    {\n      mzx_world->pl_saved_x[i] = vfgetw(vf);\n    }\n\n    for(i = 0; i < 8; i++)\n    {\n      mzx_world->pl_saved_y[i] = vfgetw(vf);\n    }\n\n    if(file_version >= V280)\n    {\n      // Saved positions 3-8 were broken due to a bug and 1-2 were\n      // saved as endian-dependent dwords.\n      mzx_world->pl_saved_board[0] = vfgetd(vf) & 0xff;\n      mzx_world->pl_saved_board[1] = vfgetd(vf) & 0xff;\n\n      for(i = 2; i < 8; i++)\n        mzx_world->pl_saved_board[i] = 0;\n    }\n    else\n    {\n      for(i = 0; i < 8; i++)\n        mzx_world->pl_saved_board[i] = vfgetc(vf);\n    }\n\n    mzx_world->saved_pl_color = vfgetc(vf);\n    mzx_world->under_player_id = vfgetc(vf);\n    mzx_world->under_player_color = vfgetc(vf);\n    mzx_world->under_player_param = vfgetc(vf);\n    mzx_world->mesg_edges = vfgetc(vf);\n    mzx_world->scroll_base_color = vfgetc(vf);\n    mzx_world->scroll_corner_color = vfgetc(vf);\n    mzx_world->scroll_pointer_color = vfgetc(vf);\n    mzx_world->scroll_title_color = vfgetc(vf);\n    mzx_world->scroll_arrow_color = vfgetc(vf);\n\n    {\n      size_t len = vfgetw(vf);\n      if(len >= MAX_PATH)\n        len = MAX_PATH - 1;\n\n      if(len && !vfread(mzx_world->real_mod_playing, len, 1, vf))\n        goto err_close;\n      mzx_world->real_mod_playing[len] = 0;\n    }\n  }\n\n  mzx_world->edge_color = vfgetc(vf);\n  mzx_world->first_board = vfgetc(vf);\n  mzx_world->endgame_board = vfgetc(vf);\n  mzx_world->death_board = vfgetc(vf);\n  mzx_world->endgame_x = vfgetw(vf);\n  mzx_world->endgame_y = vfgetw(vf);\n  mzx_world->game_over_sfx = vfgetc(vf);\n  mzx_world->death_x = vfgetw(vf);\n  mzx_world->death_y = vfgetw(vf);\n  mzx_world->starting_lives = vfgetw(vf);\n  mzx_world->lives_limit = vfgetw(vf);\n  mzx_world->starting_health = vfgetw(vf);\n  mzx_world->health_limit = vfgetw(vf);\n  mzx_world->enemy_hurt_enemy = vfgetc(vf);\n  mzx_world->clear_on_exit = vfgetc(vf);\n  mzx_world->only_from_swap = vfgetc(vf);\n\n  // Palette...\n  // Note: always loaded to the MZX mode palette as SMZX hasn't been set yet.\n  for(i = 0; i < 16; i++)\n  {\n    r = vfgetc(vf);\n    g = vfgetc(vf);\n    b = vfgetc(vf);\n\n    set_rgb(i, r, g, b);\n  }\n\n  if(savegame)\n  {\n    unsigned int vlayer_size;\n    int screen_mode;\n\n    for(i = 0; i < 16; i++)\n    {\n      set_color_intensity(i, vfgetc(vf));\n    }\n\n    *faded = vfgetc(vf);\n\n    mzx_world->player_restart_x = vfgetw(vf);\n    mzx_world->player_restart_y = vfgetw(vf);\n    mzx_world->under_player_id = vfgetc(vf);\n    mzx_world->under_player_color = vfgetc(vf);\n    mzx_world->under_player_param = vfgetc(vf);\n\n    // Read counters\n    legacy_load_counter_list(mzx_world, vf, file_version, 0);\n\n    // Read strings (2.80 and up; DOS handled this differently)\n    if(file_version >= V280)\n      legacy_load_string_list(mzx_world, vf, file_version);\n\n    // Sprite data\n    for(i = 0; i < MAX_SPRITES; i++)\n    {\n      (mzx_world->sprite_list[i])->x = vfgetw(vf);\n      (mzx_world->sprite_list[i])->y = vfgetw(vf);\n      (mzx_world->sprite_list[i])->ref_x = vfgetw(vf);\n      (mzx_world->sprite_list[i])->ref_y = vfgetw(vf);\n      (mzx_world->sprite_list[i])->color = vfgetc(vf);\n      (mzx_world->sprite_list[i])->flags = vfgetc(vf);\n      (mzx_world->sprite_list[i])->width = vfgetc(vf);\n      (mzx_world->sprite_list[i])->height = vfgetc(vf);\n      (mzx_world->sprite_list[i])->col_x = vfgetc(vf);\n      (mzx_world->sprite_list[i])->col_y = vfgetc(vf);\n      (mzx_world->sprite_list[i])->col_width = vfgetc(vf);\n      (mzx_world->sprite_list[i])->col_height = vfgetc(vf);\n    }\n\n    // total sprites\n    mzx_world->active_sprites = vfgetc(vf);\n    // y order flag\n    mzx_world->sprite_y_order = vfgetc(vf);\n    // collision info\n    mzx_world->collision_count = vfgetw(vf);\n\n    for(i = 0; i < MAX_SPRITES; i++)\n    {\n      mzx_world->collision_list[i] = vfgetw(vf);\n    }\n\n    // Multiplier\n    mzx_world->multiplier = vfgetw(vf);\n    // Divider\n    mzx_world->divider = vfgetw(vf);\n    // Circle divisions\n    mzx_world->c_divisions = vfgetw(vf);\n    // String FREAD and FWRITE Delimiters\n    mzx_world->fread_delimiter = vfgetw(vf);\n    mzx_world->fwrite_delimiter = vfgetw(vf);\n    // Builtin shooting/message status\n    mzx_world->bi_shoot_status = vfgetc(vf);\n    mzx_world->bi_mesg_status = vfgetc(vf);\n\n    // Load input file name, open later\n    {\n      size_t len = vfgetw(vf);\n      if(len >= MAX_PATH)\n        len = MAX_PATH - 1;\n\n      if(len && !vfread(mzx_world->input_file_name, len, 1, vf))\n        goto err_close;\n      mzx_world->input_file_name[len] = 0;\n    }\n    mzx_world->temp_input_pos = vfgetd(vf);\n\n    // Load output file name, open later\n    {\n      size_t len = vfgetw(vf);\n      if(len >= MAX_PATH)\n        len = MAX_PATH - 1;\n\n      if(len && !vfread(mzx_world->output_file_name, len, 1, vf))\n        goto err_close;\n      mzx_world->output_file_name[len] = 0;\n    }\n    mzx_world->temp_output_pos = vfgetd(vf);\n\n    screen_mode = vfgetw(vf);\n\n    // If it's at SMZX mode 2, set default palette as loaded\n    // so the .sav one doesn't get overwritten\n    if(screen_mode == 2)\n    {\n      smzx_palette_loaded(true);\n    }\n    set_screen_mode(screen_mode);\n\n    // Also get the palette\n    if(screen_mode > 1)\n    {\n      for(i = 0; i < 256; i++)\n      {\n        r = vfgetc(vf);\n        g = vfgetc(vf);\n        b = vfgetc(vf);\n\n        set_rgb(i, r, g, b);\n      }\n    }\n\n    mzx_world->commands = vfgetd(vf);\n\n    vlayer_size = vfgetd(vf);\n    vlayer_size = CLAMP(vlayer_size, 1, MAX_BOARD_SIZE);\n    mzx_world->vlayer_width = vfgetw(vf);\n    mzx_world->vlayer_height = vfgetw(vf);\n    mzx_world->vlayer_size = vlayer_size;\n\n    // This might have been allocated already...\n    mzx_world->vlayer_chars = crealloc(mzx_world->vlayer_chars, vlayer_size);\n    mzx_world->vlayer_colors = crealloc(mzx_world->vlayer_colors, vlayer_size);\n\n    if(vfread(mzx_world->vlayer_chars, 1, vlayer_size, vf) != vlayer_size ||\n     vfread(mzx_world->vlayer_colors, 1, vlayer_size, vf) != vlayer_size)\n      goto err_close;\n  }\n\n  // Get position of global robot...\n  global_robot_pos = vfgetd(vf);\n  // Get number of boards\n  num_boards = vfgetc(vf);\n\n  if(num_boards == 0)\n  {\n    struct sfx_list *custom_sfx = &mzx_world->custom_sfx;\n    char sfx_buf[LEGACY_SFX_SIZE];\n    size_t sfx_size;\n    // Sfx\n    vfgetw(vf); // Skip word size\n\n    //Read sfx\n    for(i = 0; i < NUM_BUILTIN_SFX; i++)\n    {\n      // Already bound checked...\n      sfx_size = vfgetc(vf);\n      if(vfread(sfx_buf, 1, sfx_size, vf) < sfx_size)\n        goto err_close;\n\n      sfx_set_string(custom_sfx, i, sfx_buf, sfx_size);\n    }\n    num_boards = vfgetc(vf);\n  }\n  else\n  {\n    sfx_free(&mzx_world->custom_sfx);\n  }\n\n  legacy_load_world_boards(mzx_world, vf, savegame, file_version, num_boards);\n\n  // Read global robot\n  vfseek(vf, global_robot_pos, SEEK_SET); //don't worry if this fails\n  legacy_load_robot(mzx_world, &mzx_world->global_robot, vf, savegame,\n   file_version);\n\n  // Some old worlds have the global_robot marked unused. Always mark it used.\n  mzx_world->global_robot.used = 1;\n\n  meter_restore_screen();\n  vfclose(vf);\n  return;\n\nerr_close:\n  // Note that this file had already been successfully validated for length\n  // and opened with no issue before this error occurred, and that the only\n  // way to reach this error is a failed fread before any board/robot data\n  // was loaded. Something seriously went wrong somewhere.\n\n  error_message(E_IO_READ, 0, NULL);\n  meter_restore_screen();\n  vfclose(vf);\n}\n"
  },
  {
    "path": "src/legacy_world.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __LEGACY_WORLD_H\n#define __LEGACY_WORLD_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"world.h\"\n#include \"world_struct.h\"\n#include \"io/vfile.h\"\n\n/****************************\n * LEGACY WORLD FORMAT INFO *\n ****************************/\n\n/* world:\n * 25 name\n * 1  protected?\n * 3  magic\n *\n * 4129 block 1\n * 3584 char set\n *  455 charid table\n *   90 status counters\n *\n *   72 block 2\n *   24 global info settings\n *   48 palette\n *\n *@4230:\n * ???? block 3 -- board and robot data, and custom sfx\n *    4 global robot offset\n *+   1 number of boards?  if that byte was actually 0:\n *    {\n *      2 sfx size\n *+    50 sfx strings up to 68 chars long each\n *      1 number of boards\n *    }\n *+  25 times the number of boards -- board names\n *    8 times the number of boards -- size, offset\n */\n\n/* save (2.84):\n *    5 save magic\n *    2 world magic\n *    1 current board\n *\n * (block 1)\n *\n *+  73 save block 1\n *   71 keys, potion timers, save player position data, misc\n *+   2 real_mod_playing, up to 514\n *\n * (block 2)\n *\n *+7508 save block 2\n *   24 intensity, faded, coords, player under\n *+   4 counters, up to 2^31 * counter size\n *+   4 strings, up to 2^31 * max string size\n * 4612 sprites\n *(4096   data\n *    4   info\n *  512   collision list)\n *   12 misc settings\n *+   2 fread filename, up to 514\n *    4 fread pos\n *+   2 fwrite filename, up to 514\n *    4 fwrite pos\n *    2 screen mode\n *  768 smzx palette\n *    4 commands\n *+   8 vlayer size, w, h; then data: vlayer size (up to 2^31) * 2\n *\n * (block 3)\n */\n\nvoid legacy_load_world(struct world *mzx_world, vfile *vf, const char *file,\n boolean savegame, int file_version, char *name, boolean *faded);\n\nvfile *validate_legacy_world_file(struct world *mzx_world,\n const char *file, boolean savegame);\n\n__M_END_DECLS\n\n#endif //__LEGACY_WORLD_H\n"
  },
  {
    "path": "src/main.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2002 B.D.A. (Koji) - Koji_Takeo@worldmailer.com\n * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <string.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <time.h>\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#include \"compat.h\"\n#include \"platform.h\"\n\n#include \"configure.h\"\n#include \"core.h\"\n#include \"error.h\"\n#include \"event.h\"\n#include \"helpsys.h\"\n#include \"graphics.h\"\n#include \"window.h\"\n#include \"data.h\"\n#include \"game.h\"\n#include \"error.h\"\n#include \"idput.h\"\n#include \"util.h\"\n#include \"world.h\"\n#include \"counter.h\"\n#include \"run_stubs.h\"\n#include \"io/path.h\"\n#include \"io/vio.h\"\n\n#include \"audio/audio.h\"\n#include \"audio/sfx.h\"\n#include \"network/network.h\"\n\n#ifdef CONFIG_SDL\n#if CONFIG_SDL == 3\n#ifdef _WIN32\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n#endif\n#include <SDL3/SDL_main.h>\n#else\n#include <SDL.h> /* SDL_main */\n#endif\n#endif\n\n#ifndef VERSION\n#error Must define VERSION for MegaZeux version string\n#endif\n\n#ifndef VERSION_DATE\n#define VERSION_DATE\n#endif\n\n#define CAPTION \"MegaZeux \" VERSION VERSION_DATE\n\n#ifdef __WIN32__\n// Export symbols to indicate MegaZeux would be prefer to be run with\n// the better video card on switchable graphics platforms.\n\n__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; // Nvidia\n__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; // AMD\n#endif\n\nstatic char startup_dir[MAX_PATH];\n\n#ifdef CONFIG_PLEDGE\n/**\n * Experimental OpenBSD pledge(2) support.\n * This has to be done after init_video and possibly after init_audio.\n *\n * FIXME MZX will abort if pretty much anything happens that requires\n * destroying or creating a window (fullscreen, renderer switching, exiting).\n * Additionally, in OpenBSD 6.5, Mesa seems to use something that aborts\n * when drawing the current frame, so the GL renderers only work in 6.4 or\n * earlier. I don't think there's anything that can be done about this in\n * the near future.\n */\nstatic void init_pledge(void)\n{\n  static const char * const promises = \"stdio rpath wpath cpath audio drm\";\n\n  debug(\"Promises: '%s'\\n\", promises);\n  if(pledge(promises, \"\"))\n    perror(\"Pledge failed\");\n}\n#endif\n\n#if defined(CONFIG_UPDATER) && defined(__WIN32__)\n#define CAN_RESTART\nstatic char **rewrite_argv_for_execv(int argc, char **argv)\n{\n  char **new_argv = cmalloc((argc+1) * sizeof(char *));\n  char *arg;\n  int length;\n  int pos;\n  int i;\n  int i2;\n\n  // Due to a bug in execv, args with spaces present are treated as multiple\n  // args in the new process. Each arg in argv must be wrapped in double quotes\n  // to work properly. Because of this, \" and \\ must also be escaped.\n\n  for(i = 0; i < argc; i++)\n  {\n    length = strlen(argv[i]);\n    arg = cmalloc(length * 2 + 2);\n    arg[0] = '\"';\n\n    for(i2 = 0, pos = 1; i2 < length; i2++, pos++)\n    {\n      switch(argv[i][i2])\n      {\n        case '\"':\n        case '\\\\':\n          arg[pos] = '\\\\';\n          pos++;\n          break;\n      }\n      arg[pos] = argv[i][i2];\n    }\n\n    arg[pos] = '\"';\n    arg[pos + 1] = '\\0';\n\n    new_argv[i] = arg;\n  }\n\n  new_argv[argc] = NULL;\n\n  return new_argv;\n}\n#endif /* CONFIG_UPDATER && __WIN32__ */\n\n#ifdef __amigaos__\n#define __libspec LIBSPEC\n#else\n#define __libspec\n#endif\n\n__libspec int main(int argc, char *argv[])\n{\n  char *_backup_argv[] = { (char*) SHAREDIR \"/megazeux\" };\n  int err = 1;\n\n  core_context *core_data;\n  struct config_info *conf;\n#ifdef CAN_RESTART\n  boolean need_restart = false;\n#endif\n\n  // Keep this 7.2k structure off the stack..\n  static struct world mzx_world;\n\n  if(!platform_init())\n    goto err_out;\n\n  check_alloc_init();\n\n  // We need to store the current working directory so it's\n  // always possible to get back to it..\n  vgetcwd(startup_dir, MAX_PATH);\n  snprintf(current_dir, MAX_PATH, \"%s\", startup_dir);\n\n#ifdef CONFIG_STDIO_REDIRECT\n  // Do this after platform_init() since, even though platform_init() might\n  // log stuff, it actually initializes the filesystem on some platforms.\n  if(!redirect_stdio_init(startup_dir, true))\n    if(!redirect_stdio_init(SHAREDIR, false))\n      redirect_stdio_init(getenv(\"HOME\"), false);\n#endif\n\n#ifdef __APPLE__\n  if(!strcmp(current_dir, \"/\") || !strncmp(current_dir, \"/App\", 4))\n  {\n    // Mac .APPs start at / and we don't like that.\n    char *user = getlogin();\n    snprintf(current_dir, MAX_PATH, \"/Users/%s\", user);\n  }\n#endif // __APPLE__\n\n#ifdef ANDROID\n  // Accept argv[1] passed in from the Java side as the \"intended\" argv[0].\n  if(argc >= 2)\n  {\n    argv++;\n    argc--;\n  }\n#endif\n\n#ifdef STARTUPDIR\n  // Some ports (Android and Vita) require a custom startup directory.\n  path_navigate(current_dir, MAX_PATH, STARTUPDIR);\n#endif\n\n  // argc may be 0 on e.g. some Wii homebrew loaders.\n#if !defined(CONFIG_WIIU) && !defined(CONFIG_PSVITA)\n  if(argc == 0)\n#endif\n  {\n    argv = _backup_argv;\n    argc = 1;\n  }\n\n  if(mzx_res_init(argv[0], is_editor()))\n    goto err_free_res;\n\n  editor_init();\n\n  // Figure out where all configuration files should be loaded\n  // form. For game.cnf, et al this should eventually be wrt\n  // the game directory, not the config.txt's path.\n  path_get_directory(config_dir, MAX_PATH, mzx_res_get_by_id(CONFIG_TXT));\n\n  // Move into the configuration file's directory so that any\n  // \"include\" statements are done wrt this path. Move back\n  // into the \"current\" directory after loading.\n  vchdir(config_dir);\n\n  default_config();\n  default_editor_config();\n  set_config_from_file(SYSTEM_CNF, mzx_res_get_by_id(CONFIG_TXT));\n  set_config_from_command_line(&argc, argv);\n  conf = get_config();\n\n  store_editor_config_backup();\n\n  init_macros();\n\n  // Startup path might be relative, so change back before checking it.\n  vchdir(current_dir);\n\n  // At this point argv should have all the config options\n  // of the form var=value removed, leaving only unparsed\n  // parameters. Interpret the first unparsed parameter\n  // as a file to load (overriding startup_file etc.)\n  if(argc > 1 && argv != _backup_argv)\n    set_config_startup_path_and_file(argv[1]);\n\n  if(strlen(conf->startup_path))\n  {\n    debug(\"Config: Using startup path '%s'\\n\", conf->startup_path);\n    snprintf(current_dir, MAX_PATH, \"%s\", conf->startup_path);\n  }\n\n  vchdir(current_dir);\n\n  // Initialize the VFS in the final startup directory.\n  if(conf->vfs_enable)\n  {\n    if(!vio_filesystem_init(conf->vfs_max_cache_size,\n     conf->vfs_max_cache_file_size, conf->vfs_enable_auto_cache))\n      warn(\"failed to initialize virtual filesystem!\\n\");\n  }\n\n  counter_fsg();\n\n  rng_seed_init();\n\n  mouse_size(8, 14);\n\n  init_event(conf);\n\n  if(!init_video(conf, CAPTION))\n    goto err_free_config;\n  init_audio(conf);\n\n#ifdef CONFIG_PLEDGE\n  init_pledge();\n#endif\n\n  core_data = core_init(&mzx_world);\n\n  if(network_layer_init(conf))\n  {\n#ifdef CONFIG_UPDATER\n    if(is_updater())\n    {\n      if(updater_init())\n      {\n        // No auto update checks on repo builds.\n        if((strstr(VERSION, \"GIT\") || strstr(VERSION, \"PRE\")) &&\n         !strcmp(conf->update_branch_pin, \"Stable\"))\n          conf->update_auto_check = UPDATE_AUTO_CHECK_OFF;\n\n        if(conf->update_auto_check)\n          if(check_for_updates((context *)core_data, true))\n            goto update_restart_mzx;\n      }\n      else\n        info(\"Updater disabled.\\n\");\n    }\n#endif\n  }\n  else\n    info(\"Network layer disabled.\\n\");\n\n  warp_mouse(39, 12);\n  cursor_off();\n  default_scroll_values(&mzx_world);\n\n#ifdef CONFIG_HELPSYS\n  help_open(&mzx_world, mzx_res_get_by_id(MZX_HELP_FIL));\n#endif\n\n  snprintf(curr_file, MAX_PATH, \"%s\", conf->startup_file);\n  snprintf(curr_sav, MAX_PATH, \"%s\", conf->default_save_name);\n  mzx_world.mzx_speed = conf->mzx_speed;\n\n  // Run main game (mouse is hidden and palette is faded)\n  title_screen((context *)core_data);\n  core_run(core_data);\n\n  vquick_fadeout();\n\n  if(mzx_world.active)\n  {\n    clear_world(&mzx_world);\n    clear_global_data(&mzx_world);\n  }\n\n#ifdef CONFIG_HELPSYS\n  help_close(&mzx_world);\n#endif\n\n#ifdef CONFIG_UPDATER\nupdate_restart_mzx:\n#endif\n#ifdef CAN_RESTART\n  need_restart = core_restart_requested(core_data);\n#endif\n  core_free(core_data);\n  network_layer_exit(conf);\n  quit_audio();\n\n#ifdef CAN_RESTART\n  // TODO: eventually any platform with execv will need to be able to allow\n  // this for config/standalone-invoked restarts. Locking it to the updater\n  // for now because that's the only thing that currently uses it.\n  if(need_restart)\n  {\n    char **new_argv = rewrite_argv_for_execv(argc, argv);\n\n    info(\"Attempting to restart MegaZeux...\\n\");\n    if(!vchdir(startup_dir))\n    {\n      execv(argv[0], (const void *)new_argv);\n      perror(\"execv\");\n    }\n\n    error_message(E_INVOKE_SELF_FAILED, 0, NULL);\n  }\n#endif\n\n  quit_video();\n\n  err = 0;\nerr_free_config:\n  // FIXME maybe shouldn't be here...?\n  if(mzx_world.update_done)\n    free(mzx_world.update_done);\n  vio_filesystem_exit();\n  free_config();\n  free_editor_config();\nerr_free_res:\n#ifdef CONFIG_STDIO_REDIRECT\n  redirect_stdio_exit();\n#endif\n  mzx_res_free();\n  platform_quit();\nerr_out:\n#ifdef CONFIG_DJGPP\n  chdir(startup_dir);\n#endif\n  return err;\n}\n"
  },
  {
    "path": "src/memcasecmp.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __MEMCASECMP_H\n#define __MEMCASECMP_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <assert.h>\n#include <inttypes.h>\n#include \"platform_endian.h\"\n\n/**\n * Minimum string length before aligned comparison should be invoked.\n */\n#define _memcasecmp32_size (sizeof(uint32_t))\n#define _memcasecmp32_threshold (_memcasecmp32_size * 2)\n\n#if ARCHITECTURE_BITS >= 64\n#define _memcasecmp64_size (sizeof(uint64_t))\n#define _memcasecmp64_threshold (_memcasecmp64_size * 2)\n#endif\n\n/**\n * Macros to get byte b of unsigned int x.\n */\n#if PLATFORM_BYTE_ORDER == PLATFORM_LIL_ENDIAN\n#define _memcaseget32(x,b) memtolower((x >> (b * 8)) & 0xFF)\n#define _memcaseget64(x,b) _memcaseget32(x,b)\n#else /* PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN */\n#define _memcaseget32(x,b) memtolower((x >> (24 - (b * 8))) & 0xFF)\n#define _memcaseget64(x,b) memtolower((x >> (56 - (b * 8))) & 0xFF)\n#endif\n\nstatic const unsigned char memtolower_table[256] =\n{\n   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,\n  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,\n  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,\n  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,\n  64,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,\n 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122,  91,  92,  93,  94,  95,\n  96,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,\n 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,\n 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,\n 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,\n 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,\n 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,\n 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,\n 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,\n 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,\n 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,\n};\n\n/**\n * Convert ASCII uppercase characters (65-90) to lowercase characters (97-122)\n * using a lookup table. This is faster than calling glibc's toupper().\n */\nstatic inline int memtolower(unsigned char c)\n{\n  return memtolower_table[c];\n}\n\n#if ARCHITECTURE_BITS >= 64\n\nstatic inline int _memcasecmp64(const uint8_t *a_value, const uint8_t *b_value,\n size_t cmp_length, size_t *position)\n{\n  const uint64_t *a_value_64b = (const uint64_t *)a_value;\n  const uint64_t *b_value_64b = (const uint64_t *)b_value;\n  size_t length_64b = cmp_length / _memcasecmp64_size;\n  uint64_t val_a;\n  uint64_t val_b;\n  size_t i;\n  int cmp;\n\n  for(i = 0; i < length_64b; i++)\n  {\n    val_a = a_value_64b[i];\n    val_b = b_value_64b[i];\n\n    if(val_a != val_b)\n    {\n      cmp = _memcaseget64(val_a, 0) - _memcaseget64(val_b, 0);\n      if(cmp)\n        return cmp;\n\n      cmp = _memcaseget64(val_a, 1) - _memcaseget64(val_b, 1);\n      if(cmp)\n        return cmp;\n\n      cmp = _memcaseget64(val_a, 2) - _memcaseget64(val_b, 2);\n      if(cmp)\n        return cmp;\n\n      cmp = _memcaseget64(val_a, 3) - _memcaseget64(val_b, 3);\n      if(cmp)\n        return cmp;\n\n      cmp = _memcaseget64(val_a, 4) - _memcaseget64(val_b, 4);\n      if(cmp)\n        return cmp;\n\n      cmp = _memcaseget64(val_a, 5) - _memcaseget64(val_b, 5);\n      if(cmp)\n        return cmp;\n\n      cmp = _memcaseget64(val_a, 6) - _memcaseget64(val_b, 6);\n      if(cmp)\n        return cmp;\n\n      cmp = _memcaseget64(val_a, 7) - _memcaseget64(val_b, 7);\n      if(cmp)\n        return cmp;\n    }\n  }\n\n  *position = length_64b * _memcasecmp64_size;\n  return 0;\n}\n\n#endif\n\nstatic inline int _memcasecmp32(const uint8_t *a_value, const uint8_t *b_value,\n size_t cmp_length, size_t *position)\n{\n  const uint32_t *a_value_32b = (const uint32_t *)a_value;\n  const uint32_t *b_value_32b = (const uint32_t *)b_value;\n  size_t length_32b = cmp_length / _memcasecmp32_size;\n  uint32_t val_a;\n  uint32_t val_b;\n  size_t i;\n  int cmp;\n\n  for(i = 0; i < length_32b; i++)\n  {\n    val_a = a_value_32b[i];\n    val_b = b_value_32b[i];\n\n    if(val_a != val_b)\n    {\n      cmp = _memcaseget32(val_a, 0) - _memcaseget32(val_b, 0);\n      if(cmp)\n        return cmp;\n\n      cmp = _memcaseget32(val_a, 1) - _memcaseget32(val_b, 1);\n      if(cmp)\n        return cmp;\n\n      cmp = _memcaseget32(val_a, 2) - _memcaseget32(val_b, 2);\n      if(cmp)\n        return cmp;\n\n      cmp = _memcaseget32(val_a, 3) - _memcaseget32(val_b, 3);\n      if(cmp)\n        return cmp;\n    }\n  }\n\n  *position = length_32b * _memcasecmp32_size;\n  return 0;\n}\n\n/**\n * Compare two strings ignoring case for ASCII chars (65-90, 97-122).\n * For best results, both strings should be 4 aligned (32-bit platforms) or\n * 8 aligned (64-bit platforms), but this function is guaranteed to work\n * with input strings of any alignment.\n *\n * @param  A            A non-terminated string.\n * @param  B            A non-terminated string.\n * @param  cmp_length   The length to compare.\n * @return              0 if A==B; >0 if A>B; <0 if A<B.\n */\nstatic inline int memcasecmp(const void *A, const void *B, size_t cmp_length)\n{\n  const uint8_t *a_value = (const uint8_t *)A;\n  const uint8_t *b_value = (const uint8_t *)B;\n  size_t i = 0;\n  size_t a_align;\n  size_t b_align;\n  int cmp;\n\n#if ARCHITECTURE_BITS >= 64\n  /**\n   * Attempt an 8-aligned compare first. If both strings aren't of the same\n   * alignment or the string is too short for this to be worth it, attempt a\n   * 4-aligned compare instead.\n   *\n   * TODO: glibc memcpy uses a technique where it aligns the first input and\n   * then uses a slower alternate function if the second input isn't also\n   * aligned, which might be worth looking into.\n   */\n  if(cmp_length >= _memcasecmp64_threshold)\n  {\n    a_align = (size_t)(a_value) % ALIGN_64_MODULO;\n    b_align = (size_t)(b_value) % ALIGN_64_MODULO;\n\n    if(a_align == b_align)\n    {\n      while((size_t)a_value % ALIGN_64_MODULO)\n      {\n        cmp = memtolower(*(a_value++)) - memtolower(*(b_value++));\n        if(cmp)\n          return cmp;\n        cmp_length--;\n      }\n\n      cmp = _memcasecmp64(a_value, b_value, cmp_length, &i);\n      if(cmp)\n        return cmp;\n    }\n    else\n      goto try_32;\n  }\n  else\n#endif\n\n  /**\n   * Attempt a 4-aligned compare. If both strings aren't of the same alignment\n   * or the string is too short for this to be worth it, fall back to the slow\n   * compare loop instead.\n   */\n  if(cmp_length >= _memcasecmp32_threshold)\n  {\n#if ARCHITECTURE_BITS >= 64\ntry_32:\n#endif\n    a_align = (size_t)(a_value) % ALIGN_32_MODULO;\n    b_align = (size_t)(b_value) % ALIGN_32_MODULO;\n\n    if(a_align == b_align)\n    {\n      while((size_t)a_value % ALIGN_32_MODULO)\n      {\n        cmp = memtolower(*(a_value++)) - memtolower(*(b_value++));\n        if(cmp)\n          return cmp;\n        cmp_length--;\n      }\n\n      cmp = _memcasecmp32(a_value, b_value, cmp_length, &i);\n      if(cmp)\n        return cmp;\n    }\n  }\n\n  /**\n   * Either the aligned compares could not be used or there are a few bytes\n   * left to compare. Finish comparing the string byte-by-byte...\n   */\n  for(; i < cmp_length; i++)\n  {\n    cmp = memtolower(a_value[i]) - memtolower(b_value[i]);\n    if(cmp)\n      return cmp;\n  }\n\n  return 0;\n}\n\n/**\n * Compare two strings known to be aligned to 4 bytes, ignoring case for\n * ASCII chars (65-90, 97-122). This is useful primarily for counter and\n * string name comparisons, which are always aligned to 4 bytes and are\n * typically too short to benefit from the 64-bit compare conditionally\n * used in the general case function.\n *\n * @param  A            A non-terminated string aligned to 4 bytes.\n * @param  B            A non-terminated string aligned to 4 bytes.\n * @param  cmp_length   The length to compare.\n * @return              0 if A==B; >0 if A>B; <0 if A<B.\n */\nstatic inline int memcasecmp32(const void *A, const void *B, size_t cmp_length)\n{\n  const uint8_t *a_value = (const uint8_t *)A;\n  const uint8_t *b_value = (const uint8_t *)B;\n  size_t i = 0;\n  int cmp;\n\n  // The 4 alignment apparently can't be guaranteed 100% of the time, so just\n  // fall back to the regular compare if there's an issue.\n  if((size_t)A % ALIGN_32_MODULO || (size_t)B % ALIGN_32_MODULO)\n    return memcasecmp(A, B, cmp_length);\n\n  cmp = _memcasecmp32(a_value, b_value, cmp_length, &i);\n  if(cmp)\n    return cmp;\n\n  for(; i < cmp_length; i++)\n  {\n    cmp = memtolower(a_value[i]) - memtolower(b_value[i]);\n    if(cmp)\n      return cmp;\n  }\n  return 0;\n}\n\n__M_END_DECLS\n\n#endif /* __MEMCASECMP_H */\n"
  },
  {
    "path": "src/mzm.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n * Copyright (C) 2017-2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n\n#include \"mzm.h\"\n\n#include \"data.h\"\n#include \"error.h\"\n#include \"idput.h\"\n#include \"legacy_robot.h\"\n#include \"legacy_world.h\"\n#include \"robot.h\"\n#include \"str.h\"\n#include \"util.h\"\n#include \"world.h\"\n#include \"world_format.h\"\n#include \"world_struct.h\"\n#include \"io/memfile.h\"\n#include \"io/vio.h\"\n#include \"io/zip.h\"\n\n\n/* The MZM format hasn't changed a whole lot with zips; only the robots\n * section is really different. This is because MZMs are in general meant to\n * be a raw data format, and furthermore accessing the raw data might be useful.\n */\n\n// This is assumed to not go over the edges.\n\n/**\n * Calculate the total storage size and storage mode of an MZM.\n */\nstatic size_t save_mzm_calculate_size(struct world *mzx_world, int start_x,\n int start_y, int width, int height, int mode, int savegame,\n enum mzm_storage_mode *storage_mode)\n{\n  size_t mzm_size;\n  *storage_mode = mode ? MZM_STORAGE_MODE_LAYER : MZM_STORAGE_MODE_BOARD;\n\n  // Header size.\n  mzm_size = 20;\n\n  // Raw tile data storage space.\n  mzm_size += (width * height) * (*storage_mode ? 2 : 6);\n\n  // Robot storage space (board to board storage mode only).\n  if(mode == MZM_BOARD_TO_BOARD_STORAGE)\n  {\n    struct board *src_board = mzx_world->current_board;\n    struct robot **robot_list = src_board->robot_list_name_sorted;\n    int num_robots_active = src_board->num_robots_active;\n    int num_robots_alloc = 0;\n    int i;\n\n    for(i = 0; i < num_robots_active; i++)\n    {\n      struct robot *cur_robot = robot_list[i];\n      if(cur_robot)\n      {\n        if(cur_robot->xpos >= start_x && cur_robot->xpos < start_x + width &&\n         cur_robot->ypos >= start_y && cur_robot->ypos < start_y + height)\n        {\n          mzm_size += save_robot_calculate_size(mzx_world, cur_robot,\n           savegame, MZX_VERSION);\n          num_robots_alloc++;\n        }\n      }\n    }\n\n    // If there aren't any robots, we don't need to bother making this a ZIP.\n    if(num_robots_alloc)\n    {\n      // Add the total overhead of the zip file.\n      // File names are all \"r##\", so the max name length is 3.\n      mzm_size += zip_bound_total_header_usage(num_robots_alloc, 3);\n    }\n  }\n  return mzm_size;\n}\n\nstatic size_t save_mzm_common(struct world *mzx_world,\n int start_x, int start_y, int width, int height, int mode, int savegame,\n const enum mzm_storage_mode storage_mode, struct memfile *mf)\n{\n  if(savegame)\n    savegame = 1;\n\n  mfwrite(\"MZM3\", 4, 1, mf);\n\n  mfputw(width, mf);\n  mfputw(height, mf);\n  // Come back here later if there's robot information.\n  mfputd(0, mf); // Robot offset.\n  mfputc(0, mf); // Robot count.\n  mfputc(storage_mode, mf);\n  mfputc(0, mf); // Savegame mode (only set non-zero if there are robots).\n\n  mfputw(MZX_VERSION, mf);\n  // Reserved bytes\n  mfputc(0, mf);\n  mfputc(0, mf);\n  mfputc(0, mf);\n\n  switch(mode)\n  {\n    // Board, raw\n    case MZM_BOARD_TO_BOARD_STORAGE:\n    {\n      struct zip_archive *zp;\n\n      struct board *src_board = mzx_world->current_board;\n      int board_width = src_board->board_width;\n      char *level_id = src_board->level_id;\n      char *level_param = src_board->level_param;\n      char *level_color = src_board->level_color;\n      char *level_under_id = src_board->level_under_id;\n      char *level_under_param = src_board->level_under_param;\n      char *level_under_color = src_board->level_under_color;\n      int x, y;\n      int offset = start_x + (start_y * board_width);\n      int line_skip = board_width - width;\n      int num_robots = 0;\n      int robot_numbers[256];\n      int robot_table_position;\n      enum thing current_id;\n      int i;\n\n      for(y = 0; y < height; y++)\n      {\n        for(x = 0; x < width; x++, offset++)\n        {\n          current_id = (enum thing)level_id[offset];\n\n          if(is_robot(current_id))\n          {\n            // Robot\n            robot_numbers[num_robots] = level_param[offset];\n            num_robots++;\n\n            mfputc(current_id, mf);\n            mfputc(0, mf);\n            mfputc(level_color[offset], mf);\n            mfputc(level_under_id[offset], mf);\n            mfputc(level_under_param[offset], mf);\n            mfputc(level_under_color[offset], mf);\n          }\n          else\n\n          if((current_id == SENSOR) || is_signscroll(current_id) ||\n           (current_id == PLAYER))\n          {\n            // Sensor, scroll, sign, or player\n            // Put customblock fake\n            mfputc((int)CUSTOM_BLOCK, mf);\n            mfputc(get_id_char(src_board, offset), mf);\n            mfputc(get_id_color(src_board, offset), mf);\n            mfputc(level_under_id[offset], mf);\n            mfputc(level_under_param[offset], mf);\n            mfputc(level_under_color[offset], mf);\n          }\n          else\n          {\n            mfputc(current_id, mf);\n            mfputc(level_param[offset], mf);\n            mfputc(level_color[offset], mf);\n            mfputc(level_under_id[offset], mf);\n            mfputc(level_under_param[offset], mf);\n            mfputc(level_under_color[offset], mf);\n          }\n        }\n        offset += line_skip;\n      }\n\n      if(num_robots)\n      {\n        struct robot **robot_list = src_board->robot_list;\n        uint64_t total_size;\n        char name[4];\n\n        // Now we're at the position we want to start writing robots.\n        robot_table_position = mftell(mf);\n\n        // Go back to header to put robot table information.\n        mfseek(mf, 8, SEEK_SET);\n        // Where the robots will be stored\n        // (not actually necessary anymore)\n        mfputd(robot_table_position, mf);\n        // Number of robots\n        mfputc(num_robots, mf);\n        // Skip board storage mode\n        mf->current += 1;\n        // Savegame mode for robots\n        mfputc(savegame, mf);\n\n        // Open the zip and write our robots into it.\n        zp = zip_open_mem_write(mf->start, mf->end - mf->start,\n         robot_table_position);\n\n        for(i = 0; i < num_robots; i++)\n        {\n          sprintf(name, \"r%2.2X\", (unsigned char) i);\n\n          // Save each robot\n          save_robot(mzx_world, robot_list[robot_numbers[i]], zp,\n           savegame, MZX_VERSION, name);\n        }\n\n        zip_close(zp, &total_size);\n        return total_size;\n      }\n      return mftell(mf);\n    }\n\n    // Overlay/Vlayer\n    case MZM_OVERLAY_TO_LAYER_STORAGE:\n    case MZM_VLAYER_TO_LAYER_STORAGE:\n    {\n      struct board *src_board = mzx_world->current_board;\n      int board_width;\n      int x, y;\n      int offset;\n      int line_skip;\n      char *chars;\n      char *colors;\n\n      if(mode == MZM_OVERLAY_TO_LAYER_STORAGE)\n      {\n        // Overlay\n        if(!src_board->overlay_mode)\n          setup_overlay(src_board, 3);\n\n        chars = src_board->overlay;\n        colors = src_board->overlay_color;\n        board_width = src_board->board_width;\n      }\n      else\n      {\n        // Vlayer\n        chars = mzx_world->vlayer_chars;\n        colors = mzx_world->vlayer_colors;\n        board_width = mzx_world->vlayer_width;\n      }\n\n      offset = start_x + (start_y * board_width);\n      line_skip = board_width - width;\n\n      for(y = 0; y < height; y++)\n      {\n        for(x = 0; x < width; x++, offset++)\n        {\n          mfputc(chars[offset], mf);\n          mfputc(colors[offset], mf);\n        }\n        offset += line_skip;\n      }\n      return mftell(mf);\n    }\n\n    // Board, char based\n    case MZM_BOARD_TO_LAYER_STORAGE:\n    {\n      struct board *src_board = mzx_world->current_board;\n      int board_width = src_board->board_width;\n      int x, y;\n      int offset = start_x + (start_y * board_width);\n      int line_skip = board_width - width;\n\n      for(y = 0; y < height; y++)\n      {\n        for(x = 0; x < width; x++, offset++)\n        {\n          mfputc(get_id_char(src_board, offset), mf);\n          mfputc(get_id_color(src_board, offset), mf);\n        }\n        offset += line_skip;\n      }\n      return mftell(mf);\n    }\n  }\n  return 0;\n}\n\nvoid save_mzm(struct world *mzx_world, char *name, int start_x, int start_y,\n int width, int height, int mode, int savegame)\n{\n  vfile *output_file = vfopen_unsafe(name, \"wb\");\n  enum mzm_storage_mode storage_mode;\n  struct memfile mf;\n  void *buffer;\n  size_t mzm_size;\n\n  if(output_file)\n  {\n    mzm_size = save_mzm_calculate_size(mzx_world, start_x, start_y, width,\n     height, mode, savegame, &storage_mode);\n\n    buffer = cmalloc(mzm_size);\n    mfopen_wr(buffer, mzm_size, &mf);\n\n    mzm_size = save_mzm_common(mzx_world, start_x, start_y, width, height, mode,\n     savegame, storage_mode, &mf);\n\n    vfwrite(buffer, mzm_size, 1, output_file);\n    free(buffer);\n    vfclose(output_file);\n  }\n}\n\nvoid save_mzm_string(struct world *mzx_world, const char *name, int start_x,\n int start_y, int width, int height, int mode, int savegame, int id)\n{\n  enum mzm_storage_mode storage_mode;\n  struct memfile mf;\n  struct string *str;\n  size_t mzm_size;\n\n  mzm_size = save_mzm_calculate_size(mzx_world, start_x, start_y, width,\n   height, mode, savegame, &storage_mode);\n\n  str = new_string(mzx_world, name, mzm_size, id);\n  if(str)\n  {\n    mfopen_wr(str->value, mzm_size, &mf);\n\n    mzm_size = save_mzm_common(mzx_world, start_x, start_y, width, height, mode,\n     savegame, storage_mode, &mf);\n\n    // Shrink the string length to the final size of the MZM.\n    str->length = mzm_size;\n  }\n}\n\nstatic boolean read_mzm_header(struct memfile *mf, size_t file_length,\n struct mzm_header *mzm_header)\n{\n  char magic_string[5];\n\n  if(file_length < 16)\n    return false;\n\n  // MegaZeux 2.83 is the last version that won't save the ver,\n  // so if we don't have a ver, just act like it's 2.83\n  mzm_header->world_version = V283;\n\n  mfread(magic_string, 4, 1, mf);\n  magic_string[4] = 0;\n\n  if(!strncmp(magic_string, \"MZMX\", 4))\n  {\n    // An MZM1 file is always storage mode 0\n    mzm_header->storage_mode = MZM_STORAGE_MODE_BOARD;\n    mzm_header->savegame_mode = 0;\n    mzm_header->num_robots = 0;\n    mzm_header->robots_location = 0;\n    mzm_header->width = mfgetc(mf);\n    mzm_header->height = mfgetc(mf);\n    mf->current += 10; // unused\n  }\n  else\n\n  if(!strncmp(magic_string, \"MZM2\", 4))\n  {\n    mzm_header->width = mfgetw(mf);\n    mzm_header->height = mfgetw(mf);\n    mzm_header->robots_location = mfgetd(mf);\n    mzm_header->num_robots = mfgetc(mf);\n    mzm_header->storage_mode = mfgetc(mf);\n    mzm_header->savegame_mode = mfgetc(mf);\n    mf->current += 1; // unused\n  }\n  else\n\n  if(!strncmp(magic_string, \"MZM3\", 4))\n  {\n    if(file_length < 20)\n      return false;\n\n    // MZM3 is like MZM2, except the robots are stored as source code if\n    // savegame_mode is 0 and version >= VERSION_SOURCE.\n    mzm_header->width = mfgetw(mf);\n    mzm_header->height = mfgetw(mf);\n    mzm_header->robots_location = mfgetd(mf);\n    mzm_header->num_robots = mfgetc(mf);\n    mzm_header->storage_mode = mfgetc(mf);\n    mzm_header->savegame_mode = mfgetc(mf);\n    mzm_header->world_version = mfgetw(mf);\n    mf->current += 3; // unused\n  }\n  else\n    return false;\n\n  // Perform minimal validation on the header data to make sure it isn't junk.\n  if(mzm_header->savegame_mode < 0 || mzm_header->savegame_mode > 1)\n    return false;\n\n  if(mzm_header->storage_mode != MZM_STORAGE_MODE_BOARD\n   && mzm_header->storage_mode != MZM_STORAGE_MODE_LAYER)\n    return false;\n\n  if(mzm_header->width < 1 || mzm_header->width >= 32768 ||\n   mzm_header->height < 1 || mzm_header->height >= 32768 ||\n   (mzm_header->width * mzm_header->height) > MAX_BOARD_SIZE)\n    return false;\n\n  return true;\n}\n\n// This will clip.\n\nstatic int load_mzm_common(struct world *mzx_world, struct memfile *mf,\n int file_length, int start_x, int start_y, int mode, int savegame,\n enum thing layer_convert_id, char *name)\n{\n  struct mzm_header mzm;\n\n  int data_start;\n  int expected_data_size;\n\n  if(!is_storageless(layer_convert_id))\n    layer_convert_id = CUSTOM_BLOCK;\n\n  if(!read_mzm_header(mf, file_length, &mzm))\n    goto err_invalid;\n\n  data_start = mftell(mf);\n  expected_data_size = (mzm.width * mzm.height) * (mzm.storage_mode ? 2 : 6);\n\n  // Validate MZM size, robots location (the other fields have been validated).\n  if((file_length - data_start < expected_data_size) // not enough space\n   || (file_length < mzm.robots_location) // The end of file is before the robots\n   || (mzm.robots_location && (expected_data_size + data_start > mzm.robots_location)))\n    goto err_invalid;\n\n  // If the mzm version is newer than the MZX version, notify\n  if(mzm.world_version > MZX_VERSION)\n  {\n    error_message(E_MZM_FILE_VERSION_TOO_RECENT, mzm.world_version, name);\n  }\n\n  // If the MZM is a save MZM but we're not loading at runtime, notify\n  if(mzm.savegame_mode > savegame)\n  {\n    error_message(E_MZM_FILE_FROM_SAVEGAME, 0, name);\n  }\n\n  switch(mode)\n  {\n    // Write to board\n    case MZM_LOAD_TO_BOARD:\n    {\n      struct board *src_board = mzx_world->current_board;\n      int board_width = src_board->board_width;\n      int board_height = src_board->board_height;\n      int effective_width = mzm.width;\n      int effective_height = mzm.height;\n      int file_line_skip;\n      int line_skip;\n      int x, y;\n      int offset = start_x + (start_y * board_width);\n      char *level_id = src_board->level_id;\n      char *level_param = src_board->level_param;\n      char *level_color = src_board->level_color;\n      char *level_under_id = src_board->level_under_id;\n      char *level_under_param = src_board->level_under_param;\n      char *level_under_color = src_board->level_under_color;\n      enum thing src_id;\n\n      // Clip\n\n      if((effective_width + start_x) >= board_width)\n        effective_width = board_width - start_x;\n\n      if((effective_height + start_y) >= board_height)\n        effective_height = board_height - start_y;\n\n      line_skip = board_width - effective_width;\n\n      switch(mzm.storage_mode)\n      {\n        case MZM_STORAGE_MODE_BOARD:\n        {\n          // Board style, write as is\n          enum thing current_id;\n          int current_robot_loaded = 0;\n          int robot_x_locations[256];\n          int robot_y_locations[256];\n          int width_difference;\n          int i;\n\n          width_difference = mzm.width - effective_width;\n\n          for(y = 0; y < effective_height; y++)\n          {\n            for(x = 0; x < effective_width; x++, offset++)\n            {\n              current_id = (enum thing)mfgetc(mf);\n\n              if(current_id >= SENSOR)\n              {\n                if(is_robot(current_id))\n                {\n                  robot_x_locations[current_robot_loaded] = x + start_x;\n                  robot_y_locations[current_robot_loaded] = y + start_y;\n                  current_robot_loaded++;\n                }\n                // Wipe a bunch of crap we don't want in MZMs with spaces\n                else\n                  current_id = 0;\n              }\n\n              src_id = (enum thing)level_id[offset];\n\n              if(src_id >= SENSOR)\n              {\n                if(src_id == SENSOR)\n                  clear_sensor_id(src_board, level_param[offset]);\n                else\n\n                if(is_signscroll(src_id))\n                  clear_scroll_id(src_board, level_param[offset]);\n                else\n\n                if(is_robot(src_id))\n                  clear_robot_id(src_board, level_param[offset]);\n              }\n\n              // Don't allow the player to be overwritten\n              if(src_id != PLAYER)\n              {\n                level_id[offset] = current_id;\n                level_param[offset] = mfgetc(mf);\n                level_color[offset] = mfgetc(mf);\n                level_under_id[offset] = mfgetc(mf);\n                level_under_param[offset] = mfgetc(mf);\n                level_under_color[offset] = mfgetc(mf);\n\n                // We don't want this on the under layer, thanks\n                if(level_under_id[offset] >= SENSOR)\n                {\n                  level_under_id[offset] = 0;\n                  level_under_param[offset] = 0;\n                  level_under_color[offset] = 7;\n                }\n              }\n              else\n                mf->current += 5;\n            }\n\n            offset += line_skip;\n\n            // Gotta run through and mark the next robots to be skipped\n            for(i = 0; i < width_difference; i++)\n            {\n              current_id = (enum thing)mfgetc(mf);\n              mf->current += 5;\n\n              if(is_robot(current_id))\n              {\n                robot_x_locations[current_robot_loaded] = -1;\n                current_robot_loaded++;\n              }\n            }\n          }\n\n          for(i = current_robot_loaded; i < mzm.num_robots; i++)\n          {\n            robot_x_locations[i] = -1;\n          }\n\n          if(mzm.num_robots)\n          {\n            struct zip_archive *zp;\n            unsigned int file_id;\n            unsigned int robot_id;\n            int result;\n\n            struct robot *cur_robot;\n            int current_x, current_y;\n            int offset;\n            int new_param;\n            int robot_calculated_size = 0;\n            int robot_partial_size = 0;\n            int current_position;\n            int dummy = 0;\n\n            // We suppress the errors that will generally occur here and barely\n            // error check the zip functions. Why? This needs to run all the way\n            // through, regardless of whether it finds errors. Otherwise, we'll\n            // get invalid robots with ID=0 littered around the board.\n\n            set_error_suppression(E_WORLD_ROBOT_MISSING, 1);\n            set_error_suppression(E_BOARD_ROBOT_CORRUPT, 1);\n\n            // Reset the error count.\n            get_and_reset_error_count();\n\n            if(mzm.world_version <= MZX_LEGACY_FORMAT_VERSION)\n            {\n              robot_partial_size =\n               legacy_calculate_partial_robot_size(mzm.savegame_mode,\n               mzm.world_version);\n\n              mfseek(mf, mzm.robots_location, SEEK_SET);\n              zp = NULL;\n            }\n            else\n            {\n              zp = zip_open_mem_read(mf->start, file_length);\n              world_assign_file_ids(zp, false);\n            }\n\n            // If we're loading a \"runtime MZM\" then it means that we're loading\n            // bytecode. And to do this we must both be in-game and must be\n            // running the same version this was made in. But since loading\n            // dynamically created MZMs in the editor is still useful, we'll just\n            // dummy out the robots.\n\n            if((mzm.savegame_mode > savegame) ||\n             (MZX_VERSION < mzm.world_version))\n            {\n              dummy = 1;\n            }\n\n            for(i = 0; i < mzm.num_robots; i++)\n            {\n              cur_robot = cmalloc(sizeof(struct robot));\n\n              current_x = robot_x_locations[i];\n              current_y = robot_y_locations[i];\n\n              // TODO: Skipped legacy robots aren't checked for, so the loaded\n              // chars for dummy robots on clipped MZMs might be off. This\n              // shouldn't matter too often though.\n\n              if(mzm.world_version <= MZX_LEGACY_FORMAT_VERSION)\n              {\n                create_blank_robot(cur_robot);\n\n                current_position = mftell(mf);\n\n                // If we fail, we have to continue, or robots won't be dummied\n                // correctly. In this case, seek to the end.\n\n                if(current_position + robot_partial_size <= file_length)\n                {\n                  robot_calculated_size =\n                   legacy_load_robot_calculate_size(mf->current, mzm.savegame_mode,\n                   mzm.world_version);\n\n                  if(current_position + robot_calculated_size <= file_length)\n                  {\n                    struct memfile r_mf;\n                    mfopen(mf->current, robot_calculated_size, &r_mf);\n                    legacy_load_robot_from_memory(mzx_world, cur_robot, &r_mf,\n                     mzm.savegame_mode, mzm.world_version, (int)current_position);\n                  }\n                  else\n                  {\n                    mfseek(mf, 0, SEEK_END);\n                    dummy = 1;\n                  }\n                }\n                else\n                {\n                  mfseek(mf, 0, SEEK_END);\n                  dummy = 1;\n                }\n\n                mf->current += robot_calculated_size;\n              }\n\n              // Search the zip until a robot is found.\n              else do\n              {\n                result = zip_get_next_mzx_file_id(zp, &file_id, NULL, &robot_id);\n\n                if(result != ZIP_SUCCESS)\n                {\n                  // We have to continue, or we'll get screwed up robots.\n                  create_blank_robot(cur_robot);\n                  create_blank_robot_program(cur_robot);\n                  dummy = 1;\n                  break;\n                }\n                else\n\n                if(file_id != FILE_ID_ROBOT || (int)robot_id < i)\n                {\n                  // Not a robot or is a skipped robot\n                  zip_skip_file(zp);\n                }\n                else\n\n                if((int)robot_id > i)\n                {\n                  // There's a robot missing.\n                  create_blank_robot(cur_robot);\n                  create_blank_robot_program(cur_robot);\n                  break;\n                }\n\n                else\n                {\n                  load_robot(mzx_world, cur_robot, zp, mzm.savegame_mode,\n                   mzm.world_version);\n                  break;\n                }\n              }\n              while(1);\n\n              if(dummy)\n              {\n                // Unfortunately, getting the actual character for the robot is\n                // kind of a lot of work right now. We have to load it then\n                // throw it away.\n\n                // If this is from a futer version and the format changed, we'll\n                // just end up with an 'R' char, but this shouldn't happen again\n                if(current_x != -1)\n                {\n                  offset = current_x + (current_y * board_width);\n                  level_id[offset] = CUSTOM_BLOCK;\n                  level_param[offset] = cur_robot->robot_char;\n                }\n                clear_robot(cur_robot);\n              }\n\n              else\n              {\n                cur_robot->world_version = mzx_world->version;\n\n#ifdef CONFIG_DEBYTECODE\n                // If we're loading source code at runtime, we need to compile it\n                if(mzm.savegame_mode < savegame)\n                  prepare_robot_bytecode(mzx_world, cur_robot);\n#endif\n\n                if(current_x != -1)\n                {\n                  new_param = find_free_robot(src_board);\n                  offset = current_x + (current_y * board_width);\n\n                  if(new_param != -1)\n                  {\n                    if((enum thing)level_id[offset] != PLAYER)\n                    {\n                      add_robot_name_entry(src_board, cur_robot,\n                        cur_robot->robot_name);\n                      src_board->robot_list[new_param] = cur_robot;\n                      cur_robot->xpos = current_x;\n                      cur_robot->ypos = current_y;\n                      cur_robot->compat_xpos = current_x;\n                      cur_robot->compat_ypos = current_y;\n                      level_param[offset] = new_param;\n                    }\n                    else\n                    {\n                      clear_robot(cur_robot);\n                    }\n                  }\n                  else\n                  {\n                    clear_robot(cur_robot);\n                    level_id[offset] = 0;\n                    level_param[offset] = 0;\n                    level_color[offset] = 7;\n                  }\n                }\n                else\n                {\n                  clear_robot(cur_robot);\n                }\n              }\n            }\n\n            if(mzm.world_version > MZX_LEGACY_FORMAT_VERSION)\n            {\n              zip_close(zp, NULL);\n            }\n\n            // If any errors were encountered, report\n            if(get_and_reset_error_count())\n              goto err_robots;\n          }\n          break;\n        }\n\n        case MZM_STORAGE_MODE_LAYER:\n        {\n          // Compact style; expand to customblocks\n          // Board style, write as is\n\n          file_line_skip = (mzm.width - effective_width) * 2;\n\n          for(y = 0; y < effective_height; y++)\n          {\n            for(x = 0; x < effective_width; x++, offset++)\n            {\n              src_id = (enum thing)level_id[offset];\n\n              if(src_id == SENSOR)\n                clear_sensor_id(src_board, level_param[offset]);\n              else\n\n              if(is_signscroll(src_id))\n                clear_scroll_id(src_board, level_param[offset]);\n              else\n\n              if(is_robot(src_id))\n                clear_robot_id(src_board, level_param[offset]);\n\n              // Don't allow the player to be overwritten\n              if(src_id != PLAYER)\n              {\n                level_id[offset] = layer_convert_id;\n                level_param[offset] = mfgetc(mf);\n                level_color[offset] = mfgetc(mf);\n                level_under_id[offset] = 0;\n                level_under_param[offset] = 0;\n                level_under_color[offset] = 0;\n              }\n              else\n                mf->current += 2;\n            }\n\n            offset += line_skip;\n            mf->current += file_line_skip;\n          }\n          break;\n        }\n      }\n      break;\n    }\n\n    // Overlay/vlayer\n    case MZM_LOAD_TO_OVERLAY:\n    case MZM_LOAD_TO_VLAYER:\n    {\n      struct board *src_board = mzx_world->current_board;\n      int dest_width = src_board->board_width;\n      int dest_height = src_board->board_height;\n      char *dest_chars;\n      char *dest_colors;\n      int effective_width = mzm.width;\n      int effective_height = mzm.height;\n      int file_line_skip;\n      int line_skip;\n      int x, y;\n      int offset;\n\n      if(mode == MZM_LOAD_TO_OVERLAY)\n      {\n        // Overlay\n        if(!src_board->overlay_mode)\n          setup_overlay(src_board, 3);\n\n        dest_chars = src_board->overlay;\n        dest_colors = src_board->overlay_color;\n        dest_width = src_board->board_width;\n        dest_height = src_board->board_height;\n      }\n      else\n      {\n        // Vlayer\n        dest_chars = mzx_world->vlayer_chars;\n        dest_colors = mzx_world->vlayer_colors;\n        dest_width = mzx_world->vlayer_width;\n        dest_height = mzx_world->vlayer_height;\n      }\n\n      offset = start_x + (start_y * dest_width);\n\n      // Clip\n\n      if((effective_width + start_x) >= dest_width)\n        effective_width = dest_width - start_x;\n\n      if((effective_height + start_y) >= dest_height)\n        effective_height = dest_height - start_y;\n\n      line_skip = dest_width - effective_width;\n\n      switch(mzm.storage_mode)\n      {\n        case MZM_STORAGE_MODE_BOARD:\n        {\n          // Coming from board storage; for now use param as char\n\n          file_line_skip = (mzm.width - effective_width) * 6;\n\n          for(y = 0; y < effective_height; y++)\n          {\n            for(x = 0; x < effective_width; x++, offset++)\n            {\n              // Skip ID\n              mf->current += 1;\n              dest_chars[offset] = mfgetc(mf);\n              dest_colors[offset] = mfgetc(mf);\n              // Skip under parts\n              mf->current += 3;\n            }\n\n            offset += line_skip;\n            mf->current += file_line_skip;\n          }\n          break;\n        }\n\n        case MZM_STORAGE_MODE_LAYER:\n        {\n          // Coming from layer storage; transfer directly\n\n          file_line_skip = (mzm.width - effective_width) * 2;\n\n          for(y = 0; y < effective_height; y++)\n          {\n            for(x = 0; x < effective_width; x++, offset++)\n            {\n              dest_chars[offset] = mfgetc(mf);\n              dest_colors[offset] = mfgetc(mf);\n            }\n\n            offset += line_skip;\n            mf->current += file_line_skip;\n          }\n          break;\n        }\n\n      }\n      break;\n    }\n  }\n\n  // Clear none of the validation error suppressions:\n  // 1) in combination with poor practice, they could lock MZX,\n  // 2) they'll get reset after reloading the world.\n  return 0;\n\nerr_robots:\n  // The main file loaded fine, but there was a problem handling robots\n  error_message(E_MZM_ROBOT_CORRUPT, 0, name);\n  return 0;\n\nerr_invalid:\n  error_message(E_MZM_FILE_INVALID, 0, name);\n  return -1;\n}\n\nint load_mzm(struct world *mzx_world, char *name, int start_x, int start_y,\n int mode, int savegame, enum thing layer_convert_id)\n{\n  vfile *input_file;\n  size_t file_size;\n  void *buffer;\n  int success;\n  int count;\n  struct memfile mf;\n  input_file = vfopen_unsafe(name, \"rb\");\n  if(input_file)\n  {\n    file_size = vfilelength(input_file, false);\n    buffer = cmalloc(file_size);\n    count = vfread(buffer, file_size, 1, input_file);\n    vfclose(input_file);\n\n    if(!count)\n    {\n      free(buffer);\n      return -1;\n    }\n\n    mfopen(buffer, file_size, &mf);\n    success = load_mzm_common(mzx_world, &mf, (int)file_size, start_x,\n     start_y, mode, savegame, layer_convert_id, name);\n    free(buffer);\n    return success;\n  }\n  else\n  {\n    error_message(E_MZM_DOES_NOT_EXIST, 0, name);\n    return -1;\n  }\n}\n\nint load_mzm_memory(struct world *mzx_world, char *name, int start_x,\n int start_y, int mode, int savegame, enum thing layer_convert_id,\n const void *buffer, size_t length)\n{\n  struct memfile mf;\n  mfopen(buffer, length, &mf);\n  return load_mzm_common(mzx_world, &mf, (int)length, start_x, start_y,\n   mode, savegame, layer_convert_id, name);\n}\n\nboolean load_mzm_header(char *name, struct mzm_header *mzm_header)\n{\n  vfile *input_file;\n  struct memfile mf;\n  char buffer[20];\n  int read_length;\n\n  mzm_header->width = -1;\n  mzm_header->height = -1;\n\n  input_file = vfopen_unsafe(name, \"rb\");\n  if(input_file)\n  {\n    read_length = vfread(buffer, 1, 20, input_file);\n    vfclose(input_file);\n\n    mfopen(buffer, read_length, &mf);\n    if(read_mzm_header(&mf, read_length, mzm_header))\n      return true;\n\n    error_message(E_MZM_FILE_INVALID, 0, name);\n  }\n  return false;\n}\n"
  },
  {
    "path": "src/mzm.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __MZM_H\n#define __MZM_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"world_struct.h\"\n\nenum mzm_save_mode\n{\n  MZM_BOARD_TO_BOARD_STORAGE    = 0,\n  MZM_OVERLAY_TO_LAYER_STORAGE  = 1,\n  MZM_VLAYER_TO_LAYER_STORAGE   = 2,\n  MZM_BOARD_TO_LAYER_STORAGE    = 3,\n};\n\nenum mzm_load_mode\n{\n  MZM_LOAD_TO_BOARD   = 0,\n  MZM_LOAD_TO_OVERLAY = 1,\n  MZM_LOAD_TO_VLAYER  = 2,\n};\n\nenum mzm_storage_mode\n{\n  MZM_STORAGE_MODE_BOARD = 0,\n  MZM_STORAGE_MODE_LAYER = 1,\n};\n\nstruct mzm_header\n{\n  int width;\n  int height;\n  int robots_location;\n  int num_robots;\n  enum mzm_storage_mode storage_mode;\n  int savegame_mode;\n  int world_version;\n};\n\nCORE_LIBSPEC void save_mzm(struct world *mzx_world, char *name,\n int start_x, int start_y, int width, int height, int mode, int savegame);\nCORE_LIBSPEC void save_mzm_string(struct world *mzx_world, const char *name,\n int start_x, int start_y, int width, int height, int mode, int savegame, int id);\nCORE_LIBSPEC int load_mzm(struct world *mzx_world, char *name,\n int start_x, int start_y, int mode, int savegame, enum thing layer_convert_id);\nCORE_LIBSPEC int load_mzm_memory(struct world *mzx_world, char *name, int start_x,\n int start_y, int mode, int savegame, enum thing layer_convert_id,\n const void *buffer, size_t length);\nCORE_LIBSPEC boolean load_mzm_header(char *name, struct mzm_header *mzm_header);\n\n__M_END_DECLS\n\n#endif // __MZM_H\n"
  },
  {
    "path": "src/network/DNS.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018-2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Asynchronous wrapper for getaddrinfo. This was easier to implement than\n// trying to work with platform-dependent solutions.\n\n#include <assert.h>\n#include <stdlib.h>\n\n#include \"DNS.hpp\"\n#include \"Socket.hpp\"\n\n#include \"../configure.h\"\n#include \"../platform.h\"\n#include \"../util.h\"\n\n#define LOCK(d)         platform_mutex_lock(&(d->mutex))\n#define UNLOCK(d)       platform_mutex_unlock(&(d->mutex))\n#define WAIT(d)         platform_cond_wait(&(d->cond), &(d->mutex))\n#define TIMED_WAIT(d,t) platform_cond_timedwait(&(d->cond), &(d->mutex), t)\n#define SIGNAL(d)       platform_cond_signal(&(d->cond))\n\nenum dns_state\n{\n  STATE_INIT = 0,\n  STATE_EXIT,\n  STATE_STANDBY,\n  STATE_LOOKUP,\n  STATE_SUCCESS,\n  STATE_ABORT,\n  NUM_DNS_STATES\n};\n\nstruct dns_data\n{\n  platform_thread thread;\n  platform_mutex mutex;\n  platform_cond cond;\n  enum dns_state state;\n  char *node;\n  char *service;\n  struct addrinfo hints;\n  struct addrinfo *res;\n  int ret;\n  boolean has_hints;\n};\n\nstatic struct dns_data *threads;\nstatic int threads_count;\nstatic int threads_max;\nstatic int init_ref_count;\n\nstatic void set_dns_thread_data(struct dns_data *data,\n const char *node, const char *service, const struct addrinfo *hints)\n{\n  size_t node_len = strlen(node) + 1;\n  size_t service_len = strlen(service) + 1;\n\n  data->node = (char *)cmalloc(node_len);\n  data->service = (char *)cmalloc(service_len);\n  data->has_hints = false;\n  memcpy(data->node, node, node_len);\n  memcpy(data->service, service, service_len);\n  memset(&(data->hints), 0, sizeof(struct addrinfo));\n  if(hints)\n  {\n    data->hints.ai_flags = hints->ai_flags;\n    data->hints.ai_family = hints->ai_family;\n    data->hints.ai_socktype = hints->ai_socktype;\n    data->hints.ai_protocol = hints->ai_protocol;\n    data->has_hints = true;\n  }\n  data->res = NULL;\n}\n\nstatic void free_dns_thread_data(struct dns_data *data, boolean free_result)\n{\n  free(data->node);\n  free(data->service);\n  if(free_result && data->res)\n    Socket::freeaddrinfo(data->res);\n\n  data->node = NULL;\n  data->service = NULL;\n  data->res = NULL;\n}\n\nstatic THREAD_RES run_dns_thread(void *_data)\n{\n  struct dns_data *data = (struct dns_data *)_data;\n  int ret = -1;\n\n  trace(\"--DNS-- New thread running.\\n\");\n  LOCK(data);\n  data->state = STATE_STANDBY;\n\n  do\n  {\n    SIGNAL(data);\n    WAIT(data);\n    UNLOCK(data);\n\n    if(data->state == STATE_LOOKUP)\n    {\n      trace(\"--DNS-- Starting lookup.\\n\");\n      ret = Socket::getaddrinfo(data->node, data->service,\n       data->has_hints ? &(data->hints) : nullptr, &(data->res));\n    }\n\n    if(data->state == STATE_EXIT)\n    {\n      trace(\"--DNS-- Thread exited.\\n\");\n      THREAD_RETURN;\n    }\n\n    LOCK(data);\n\n    switch(data->state)\n    {\n      case(STATE_LOOKUP):\n      {\n        trace(\"--DNS-- Lookup completed.\\n\");\n        data->state = STATE_SUCCESS;\n        data->ret = ret;\n        break;\n      }\n\n      default:\n      {\n        trace(\"--DNS-- Lookup completed (discarding; %d).\\n\", data->state);\n        free_dns_thread_data(data, true);\n        data->state = STATE_STANDBY;\n        break;\n      }\n    }\n  }\n  while(true);\n}\n\nstatic void create_dns_thread(struct dns_data *data)\n{\n  platform_mutex_init(&(data->mutex));\n  platform_cond_init(&(data->cond));\n\n  LOCK(data);\n\n  if(!platform_thread_create(&(data->thread),\n   (platform_thread_fn)run_dns_thread, (void *)data))\n  {\n    UNLOCK(data);\n    platform_mutex_destroy(&(data->mutex));\n    platform_cond_destroy(&(data->cond));\n  }\n  else\n  {\n    // Allow thread to initialize.\n    WAIT(data);\n    UNLOCK(data);\n  }\n}\n\nstatic void destroy_dns_thread(struct dns_data *data)\n{\n  LOCK(data);\n  data->state = STATE_EXIT;\n  SIGNAL(data);\n  UNLOCK(data);\n\n  platform_thread_join(&(data->thread));\n  platform_mutex_destroy(&(data->mutex));\n  platform_cond_destroy(&(data->cond));\n  free_dns_thread_data(data, true);\n}\n\nint DNS::lookup(const char *node, const char *service,\n const struct addrinfo *hints, struct addrinfo **res, uint32_t timeout)\n{\n  int ret;\n  int i;\n\n  for(i = 0; i < threads_max; i++)\n  {\n    struct dns_data *data = threads + i;\n\n    if(data->state == STATE_INIT)\n    {\n      trace(\"--DNS-- Starting DNS thread %d.\\n\", threads_count);\n      create_dns_thread(data);\n      threads_count++;\n    }\n\n    if(data->state == STATE_STANDBY)\n    {\n      trace(\"--DNS-- Using DNS thread %d.\\n\", i);\n      set_dns_thread_data(data, node, service, hints);\n      data->state = STATE_LOOKUP;\n\n      LOCK(data);\n      SIGNAL(data);\n\n      trace(\"--DNS-- Waiting for response.\\n\");\n      TIMED_WAIT(data, timeout);\n\n      if(data->state == STATE_SUCCESS)\n      {\n        ret = data->ret;\n        *res = data->res;\n        trace(\"--DNS-- Received response (return code %d)\\n\", ret);\n        free_dns_thread_data(data, false);\n        data->state = STATE_STANDBY;\n      }\n      else\n      {\n        trace(\"--DNS-- Timed out.\\n\");\n        data->state = STATE_ABORT;\n        ret = EAI_AGAIN;\n        *res = NULL;\n      }\n\n      UNLOCK(data);\n      return ret;\n    }\n  }\n\n  // Try to run manually instead.\n  trace(\"--DNS-- No DNS threads available; running lookup in main thread.\\n\");\n  ret = Socket::getaddrinfo(node, service, hints, res);\n  trace(\"--DNS-- Completed successfully (return code %d)\\n\", ret);\n  return ret;\n}\n\nboolean DNS::init(struct config_info *conf)\n{\n  if(!init_ref_count)\n  {\n    if(!Socket::init(conf))\n      return false;\n\n    threads_max = DNS::DEFAULT_MAX_THREADS;\n\n#ifdef CONFIG_UPDATER\n    if(threads_max < conf->update_host_count)\n      threads_max = conf->update_host_count;\n#endif\n\n    threads = (struct dns_data *)ccalloc(threads_max, sizeof(struct dns_data));\n    threads_count = 0;\n  }\n  init_ref_count++;\n  return true;\n}\n\nvoid DNS::exit(void)\n{\n  assert(init_ref_count > 0);\n  init_ref_count--;\n  if(init_ref_count != 0)\n    return;\n\n  for(int i = 0; i < threads_count; i++)\n  {\n    if(threads[i].state != STATE_INIT)\n    {\n      trace(\"--DNS-- Waiting for DNS thread %d\\n\", i);\n      destroy_dns_thread(threads + i);\n      trace(\"--DNS-- Thread %d joined.\\n\", i);\n    }\n  }\n\n  free(threads);\n  threads = nullptr;\n  threads_count = 0;\n  Socket::exit();\n}\n"
  },
  {
    "path": "src/network/DNS.hpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __DNS_HPP\n#define __DNS_HPP\n\n#include \"../compat.h\"\n\n#include \"Socket.hpp\"\n\n#include <stdint.h>\n\nclass DNS final\n{\nprivate:\n  DNS() {}\n\n  // Default thread cap for lookups (note: update host count can override).\n  static const int DEFAULT_MAX_THREADS = 3;\n\npublic:\n  static boolean init(struct config_info *conf);\n  static void exit();\n\n  /**\n   * Wrapper for `Socket::getaddrinfo`. The `getaddrinfo` call will be performed\n   * asynchronously and this function will block for up to a provided timeout\n   * duration for the call to finish. This is useful as `getaddrinfo` calls\n   * block for an OS-specified amount of time (for Windows, up to 10 seconds)\n   * that may be unacceptable in some situations.\n   *\n   * @param node      Hostname.\n   * @param service   Port number string.\n   * @param hints     Hints to provide to `getaddrinfo`.\n   * @param res       Pointer for the return value of `getaddrinfo`.\n   * @param timeout   Duration (ms) to wait for `getaddrinfo` to resolve.\n   *\n   * @return a `getaddrinfo` return code (see `getaddrinfo`, `gai_strerror`).\n   */\n  static int lookup(const char *node, const char *service,\n   const struct addrinfo *hints, struct addrinfo **res, uint32_t timeout);\n};\n\n#endif /* __DNS_HPP */\n"
  },
  {
    "path": "src/network/HTTPHost.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2020-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <assert.h>\n#include <ctype.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <zlib.h>\n\n#include \"HTTPHost.hpp\"\n#include \"Scoped.hpp\"\n#include \"../util.h\"\n#include \"../io/vio.h\"\n\n#ifdef IS_CXX_11\n#include <type_traits>\n#endif\n\n#define BLOCK_SIZE    4096UL\n#define LINE_BUF_LEN  256\n\nstatic ssize_t zlib_skip_gzip_header(Bytef *initial, unsigned long len)\n{\n  Bytef *gzip = initial;\n  uint8_t flags;\n\n  if(len < 10)\n    return HOST_ZLIB_INVALID_DATA;\n\n  /* Unfortunately, Apache (and probably other httpds) send deflated\n   * data in the gzip format. Internally, gzip is identical to zlib's\n   * DEFLATE format, but it has some additional headers that must be\n   * skipped before we can proceed with the inflation.\n   *\n   * RFC 1952 details the gzip headers.\n   */\n  if(*gzip++ != 0x1F || *gzip++ != 0x8B || *gzip++ != Z_DEFLATED)\n    return HOST_ZLIB_INVALID_GZIP_HEADER;\n\n  // Grab gzip flags from header and skip MTIME+XFL+OS\n  flags = *gzip++;\n  gzip += 6;\n\n  // Header \"reserved\" bits must not be set\n  if(flags & 0xE0)\n    return HOST_ZLIB_INVALID_GZIP_HEADER;\n\n  // Skip extended headers\n  if(flags & 0x4)\n    gzip += 2 + *gzip + (*(gzip + 1) << 8);\n\n  // Skip filename (null terminated string)\n  if(flags & 0x8)\n    while(*gzip++);\n\n  // Skip comment\n  if(flags & 0x10)\n    while(*gzip++);\n\n  // FIXME: Skip CRC16 (should verify if exists)\n  if(flags & 0x2)\n    gzip += 2;\n\n  // Return number of bytes to skip from buffer\n  return gzip - initial;\n}\n\n#ifdef NETWORK_DEADCODE\n\nstatic int zlib_forge_gzip_header(char *buffer)\n{\n  // GZIP magic (see RFC 1952)\n  buffer[0] = 0x1F;\n  buffer[1] = 0x8B;\n  buffer[2] = Z_DEFLATED;\n\n  // Flags (no flags required)\n  buffer[3] = 0x0;\n\n  // Zero mtime etc.\n  memset(&buffer[4], 0, 6);\n\n  // GZIP header is 10 bytes\n  return 10;\n}\n\n#endif\n\nconst char * const HTTPRequestInfo::plaintext_types[] =\n{\n  \"text/plain\",\n  \"text/plain;charset=us\",\n  \"text/plain;charset=utf-8\",\n  nullptr\n};\n\nconst char * const HTTPRequestInfo::binary_types[] =\n{\n  \"application/octet-stream;*\",\n  nullptr\n};\n\nvoid HTTPRequestInfo::clear()\n{\n  memset(this, 0, sizeof(HTTPRequestInfo));\n}\n\nvoid HTTPRequestInfo::clear_response()\n{\n  status_type = 0;\n  status_code = 0;\n  status_message[0] = '\\0';\n  content_type[0] = '\\0';\n  content_type_params[0] = '\\0';\n  content_encoding[0] = '\\0';\n  transfer_encoding[0] = '\\0';\n  content_length = 0;\n  final_length = 0;\n  transfer_encoding_type = HTTPRequestInfo::EN_NORMAL;\n  content_encoding_type = HTTPRequestInfo::EN_NORMAL;\n}\n\nvoid HTTPRequestInfo::print_response() const\n{\n  boolean params = this->content_type_params[0] != '\\0';\n  fprintf(mzxerr,\n    \"  URL               : %s\\n\"\n    \"  Status            : %d %s\\n\"\n    \"  Content-Type      : %s%s%s\\n\"\n    \"  Content-Encoding  : %s\\n\"\n    \"  Transfer-Encoding : %s\\n\"\n    \"  Content-Length    : %zu\\n\",\n    this->url,\n    this->status_code, this->status_message,\n    this->content_type,\n    (params ? \"; \" : \"\"),\n    (params ? this->content_type_params : \"\"),\n    this->content_encoding,\n    this->transfer_encoding,\n    this->content_length\n  );\n  fflush(mzxerr);\n}\n\nconst char *HTTPHost::get_error_string(HTTPHostStatus status)\n{\n#ifdef IS_CXX_11\n  static_assert(HOST_STATUS_ERROR_MAX < 0, \"HOST_STATUS_ERROR_MAX not < 0!\");\n#endif\n  switch(status)\n  {\n    case HOST_SUCCESS:\n      return \"Operation completed successfully.\";\n    case HOST_INIT_FAILED:\n      return \"FIXME not used\";\n    case HOST_FREAD_FAILED:\n      return \"Failed to fread file.\";\n    case HOST_FWRITE_FAILED:\n      return \"Failed to fwrite file.\";\n    case HOST_SEND_FAILED:\n      return \"Connection issue occurred (send() failed).\";\n    case HOST_RECV_FAILED:\n      return \"Connection issue occurred (receive() failed).\";\n    case HOST_ALLOC_FAILED:\n      return \"Failed to allocate buffer memory.\";\n    case HOST_HTTP_EXCEEDED_BUFFER:\n      return \"Operation would exceed the provided buffer\"\n       \" (INTERNAL ERROR: REPORT THIS!)\";\n    case HOST_HTTP_INFO:\n      return \"Unexpected response of type 1xx (informational).\";\n    case HOST_HTTP_REDIRECT:\n      return \"Unexpected response of type 3xx (redirect).\";\n    case HOST_HTTP_CLIENT_ERROR:\n      return \"Unexpected response of type 4xx (client error).\";\n    case HOST_HTTP_SERVER_ERROR:\n      return \"Unexpected response of type 5xx (server error).\";\n    case HOST_HTTP_INVALID_STATUS:\n      return \"Response status is invalid.\";\n    case HOST_HTTP_INVALID_HEADER:\n      return \"Response header is invalid.\";\n    case HOST_HTTP_INVALID_CONTENT_LENGTH:\n      return \"Response 'Content-Length' value is invalid.\";\n    case HOST_HTTP_INVALID_TRANSFER_ENCODING:\n      return \"Response 'Transfer-Encoding' value is invalid\"\n       \" (only 'chunked' is accepted).\";\n    case HOST_HTTP_INVALID_CONTENT_TYPE:\n      return \"Response 'Content-Type' does not match the expected value(s).\";\n    case HOST_HTTP_INVALID_CONTENT_ENCODING:\n      return \"Response 'Content-Encoding' value is invalid\"\n       \" (only 'gzip' is accepted).\";\n    case HOST_HTTP_INVALID_CHUNK_LENGTH:\n      return \"Response chunk length is invalid.\";\n    case HOST_ZLIB_INVALID_DATA:\n      return \"Response gzip header is missing.\";\n    case HOST_ZLIB_INVALID_GZIP_HEADER:\n      return \"Response gzip header is invalid.\";\n    case HOST_ZLIB_DEFLATE_FAILED:\n      return \"Failed to compress response data.\";\n    case HOST_ZLIB_INFLATE_FAILED:\n      return \"Failed to decompress response data.\";\n    case HOST_STATUS_ERROR_MAX:\n      return \"INTERNAL ERROR: REPORT THIS!\";\n  }\n  return \"invalid status code!\";\n}\n\nssize_t HTTPHost::http_receive_line(char *buffer, size_t len)\n{\n  size_t pos;\n\n  // Grab data bytewise until we find CRLF characters\n  for(pos = 0; pos < len; pos++)\n  {\n    // If recv() times out or fails, abort\n    if(!this->receive(buffer + pos, 1))\n    {\n      trace(\"--HOST-- HTTPHost::http_receive_line (failed)\\n\");\n      return HOST_RECV_FAILED;\n    }\n\n    // Erase terminating CRLF and fix up count\n    if(buffer[pos] == '\\n')\n    {\n      if(pos < 1)\n        return HOST_HTTP_INVALID_HEADER;\n\n      buffer[pos--] = 0;\n      buffer[pos] = 0;\n      break;\n    }\n  }\n\n  // We didn't find CRLF; this is bad\n  if(pos == len)\n    return HOST_HTTP_EXCEEDED_BUFFER;\n\n  trace(\"--HOST-- HTTPHost::http_receive_line: %.*s\\n\", (int)pos, buffer);\n  return pos;\n}\n\nssize_t HTTPHost::http_send_line(const char *message)\n{\n  char line[LINE_BUF_LEN];\n  ssize_t len;\n\n  trace(\"--HOST-- HTTPHost::http_send_line( %s )\\n\", message);\n  snprintf(line, LINE_BUF_LEN, \"%s\\r\\n\", message);\n  len = (ssize_t)strlen(line);\n\n  if(!this->send(line, len))\n    return HOST_SEND_FAILED;\n\n  return len;\n}\n\nHTTPHostStatus HTTPHost::http_read_status(HTTPRequestInfo &dest,\n const char *status, size_t status_len)\n{\n  /* These conditionals check the status line is formatted:\n   *   \"HTTP/1.? XXX ...\" (where ? is 0 or 1, XXX is the status code,\n   *                      and ... is the status message)\n   *\n   * This is because partially HTTP/1.1 capable servers supporting\n   * pipelining may not advertise full HTTP/1.1 compliance (e.g.\n   * `perlbal' httpd).\n   *\n   * MegaZeux only cares about pipelining.\n   */\n  char ver[2];\n  char code[4];\n  int res;\n  int c;\n\n  dest.status_message[0] = 0;\n\n  res = sscanf(status, \"HTTP/1.%1s %3s %31[^\\r\\n]\", ver, code,\n   dest.status_message);\n\n  trace(\"--HOST-- HTTPHost::http_read_status: %s (%s, %s, %s)\\n\",\n   status, ver, code, dest.status_message);\n  if(res != 3 || (ver[0] != '0' && ver[0] != '1'))\n    return HOST_HTTP_INVALID_STATUS;\n\n  // Make sure the code is a 3-digit number\n  c = strtol(code, NULL, 10);\n  if(c < 100)\n    return HOST_HTTP_INVALID_STATUS;\n\n  dest.status_code = c;\n  dest.status_type = c / 100;\n\n  return HOST_SUCCESS;\n}\n\nHTTPHostStatus HTTPHost::http_read_header_line(HTTPRequestInfo &dest, char *h,\n size_t h_len)\n{\n  char *next = h;\n  char *key = strsep(&next, \":\");\n  char *value = strsep(&next, \":\");\n\n  if(!key || !value)\n    return HOST_HTTP_INVALID_HEADER;\n\n  // Skip common prefix space if present.\n  while(isspace(*value))\n    value++;\n\n  /* Parse pertinent headers. These are:\n   *\n   *   Content-Length     Necessary to determine payload length\n   *   Transfer-Encoding  Instead of Content-Length, can only be \"chunked\"\n   *   Content-Type       Text or binary; also used for sanity checks\n   *   Content-Encoding   Present and set to 'gzip' if deflated\n   *\n   * NOTE: all header field names are case-insensitive.\n   * https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2\n   * https://tools.ietf.org/html/rfc7230#section-3.2\n   *\n   * NOTE: all content coding names are case-insensitive.\n   * https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5\n   * https://tools.ietf.org/html/rfc7231#section-3.1.2.1\n   *\n   * NOTE: all transfer coding names are case-insensitive.\n   * https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6\n   * https://tools.ietf.org/html/rfc7230#section-4\n   */\n\n  if(strcasecmp(key, \"Content-Length\") == 0)\n  {\n    char *endptr;\n\n    dest.content_length = strtoul(value, &endptr, 10);\n    if(endptr[0])\n      return HOST_HTTP_INVALID_CONTENT_LENGTH;\n\n    dest.transfer_encoding_type = HTTPRequestInfo::EN_NORMAL;\n  }\n  else\n\n  if(strcasecmp(key, \"Transfer-Encoding\") == 0)\n  {\n    const size_t len = ARRAY_SIZE(dest.transfer_encoding);\n    snprintf(dest.transfer_encoding, len, \"%s\", value);\n    dest.transfer_encoding[len - 1] = '\\0';\n\n    dest.transfer_encoding_type = HTTPRequestInfo::EN_UNSUPPORTED;\n    if(strcasecmp(value, \"chunked\") == 0)\n      dest.transfer_encoding_type = HTTPRequestInfo::EN_CHUNKED;\n  }\n  else\n\n  if(strcasecmp(key, \"Content-Type\") == 0)\n  {\n    const char *type = strsep(&value, \";\");\n\n    size_t len = ARRAY_SIZE(dest.content_type);\n    snprintf(dest.content_type, len, \"%s\", type);\n    dest.content_type[len - 1] = '\\0';\n    if(value)\n    {\n      while(isspace(*value))\n        value++;\n\n      len = ARRAY_SIZE(dest.content_type_params);\n      snprintf(dest.content_type_params, len, \"%s\", value);\n      dest.content_type_params[len - 1] = '\\0';\n    }\n    else\n      dest.content_type_params[0] = '\\0';\n  }\n  else\n\n  if(strcasecmp(key, \"Content-Encoding\") == 0)\n  {\n    const size_t len = ARRAY_SIZE(dest.content_encoding);\n    snprintf(dest.content_encoding, len, \"%s\", value);\n    dest.content_encoding[len - 1] = '\\0';\n\n    dest.content_encoding_type = HTTPRequestInfo::EN_UNSUPPORTED;\n    if(strcasecmp(value, \"gzip\") == 0 || strcasecmp(value, \"x-gzip\") == 0)\n      dest.content_encoding_type = HTTPRequestInfo::EN_GZIP;\n  }\n  return HOST_SUCCESS;\n}\n\n/**\n * Check the content_type and content_type_params fields of a request response\n * against the allowed_types array provided with the request.\n *\n * NOTE: all media types, subtypes, and parameter names are case-insensitive.\n * https://tools.ietf.org/html/rfc7231#section-3.1.1.1\n *\n * NOTE: all charset tokens are case-insensitive.\n * https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.4\n * https://tools.ietf.org/html/rfc7231#section-3.1.1.2\n */\nHTTPHostStatus HTTPHost::http_filter_content_type(const HTTPRequestInfo &request) const\n{\n  if(request.allowed_types)\n  {\n    size_t i;\n    for(i = 0; request.allowed_types[i]; i++)\n    {\n      /**\n       * The provided type may have a list of params. If the type has params\n       * (denoted by ;), the request params must exactly match the allowed type\n       * params. If the allowed type has no params, the response must also not\n       * have any params. If the allowed type has \"*\" provided in place of\n       * params, any or no params are accepted.\n       */\n      const char *type = request.allowed_types[i];\n      const char *params = strchr(type, ';');\n      if(params)\n      {\n        size_t type_len = (params - type);\n        if(type_len >= ARRAY_SIZE(request.content_type))\n        {\n          warn(\"Content-Type filter '%s' exceeds buffer length (REPORT THIS).\\n\",\n           type);\n          continue;\n        }\n\n        if(strncasecmp(type, request.content_type, type_len) == 0 &&\n         request.content_type[type_len] == '\\0')\n        {\n          params++;\n          while(isspace(*params))\n            params++;\n\n          if(strcmp(params, \"*\") == 0)\n            return HOST_SUCCESS;\n\n          if(strcasecmp(params, request.content_type_params) == 0)\n            return HOST_SUCCESS;\n        }\n      }\n      else\n\n      if(request.content_type_params[0] == '\\0')\n      {\n        if(strcasecmp(type, request.content_type) == 0)\n          return HOST_SUCCESS;\n      }\n    }\n    return HOST_HTTP_INVALID_CONTENT_TYPE;\n  }\n  return HOST_SUCCESS;\n}\n\nboolean HTTPHost::http_skip_headers()\n{\n  char buffer[LINE_BUF_LEN];\n\n  while(true)\n  {\n    int len = this->http_receive_line(buffer, LINE_BUF_LEN);\n\n    // We read the final newline (empty, with CRLF)\n    if(len == 0)\n      return true;\n\n    // Connection probably failed; fail hard\n    if(len < 0)\n      return false;\n  }\n}\n\nHTTPHostStatus HTTPHost::head(HTTPRequestInfo &request)\n{\n  char line[LINE_BUF_LEN];\n  ssize_t line_len;\n\n  trace(\"--HOST-- HTTPHost::head\\n\");\n  request.clear_response();\n\n  snprintf(line, LINE_BUF_LEN, \"HEAD %s HTTP/1.1\", request.url);\n  line[LINE_BUF_LEN - 1] = 0;\n  if(http_send_line(line) < 0)\n    return HOST_SEND_FAILED;\n\n  snprintf(line, LINE_BUF_LEN, \"Host: %s\", this->get_host_name());\n  line[LINE_BUF_LEN - 1] = 0;\n  if(http_send_line(line) < 0)\n    return HOST_SEND_FAILED;\n\n  if(http_send_line(\"\") < 0)\n    return HOST_SEND_FAILED;\n\n  line_len = http_receive_line(line, LINE_BUF_LEN);\n  if(line_len < 0)\n  {\n    warn(\"No response for url '%s': %zd!\\n\", request.url, line_len);\n    return (HTTPHostStatus)line_len;\n  }\n\n  if(http_read_status(request, line, line_len) != HOST_SUCCESS)\n  {\n    warn(\"Invalid status: %s\\nFailed for url '%s'\\n\", line, request.url);\n    return HOST_HTTP_INVALID_STATUS;\n  }\n\n  while(true)\n  {\n    line_len = http_receive_line(line, LINE_BUF_LEN);\n    if(line_len < 0)\n      return (HTTPHostStatus)line_len;\n\n    if(line_len == 0)\n      break;\n\n    HTTPHostStatus result = http_read_header_line(request, line, line_len);\n    if(result != HOST_SUCCESS)\n      return result;\n  }\n\n  return HOST_SUCCESS;\n}\n\nHTTPHostStatus HTTPHost::get(HTTPRequestInfo &request, vfile *file)\n{\n  boolean mid_inflate = false;\n  boolean mid_chunk = false;\n  size_t len = 0;\n  size_t pos = 0;\n  char line[LINE_BUF_LEN];\n  z_stream stream;\n\n  trace(\"--HOST-- HTTPHost::get\\n\");\n  request.clear_response();\n\n  // Tell the server that we support pipelining\n  snprintf(line, LINE_BUF_LEN, \"GET %s HTTP/1.1\", request.url);\n  line[LINE_BUF_LEN - 1] = 0;\n  if(http_send_line(line) < 0)\n    return HOST_SEND_FAILED;\n\n  snprintf(line, LINE_BUF_LEN, \"Host: %s\", this->get_host_name());\n\n  line[LINE_BUF_LEN - 1] = 0;\n  if(http_send_line(line) < 0)\n    return HOST_SEND_FAILED;\n\n  // We support DEFLATE/GZIP payloads\n  if(http_send_line(\"Accept-Encoding: gzip\") < 0)\n    return HOST_SEND_FAILED;\n\n  // Blank line tells server we are done\n  if(http_send_line(\"\") < 0)\n    return HOST_SEND_FAILED;\n\n  // Read in the HTTP status line\n  ssize_t line_len = http_receive_line(line, LINE_BUF_LEN);\n  if(line_len < 0)\n  {\n    warn(\"No response for url '%s': %zd!\\n\", request.url, line_len);\n    return (HTTPHostStatus)line_len;\n  }\n\n  if(http_read_status(request, line, line_len) != HOST_SUCCESS)\n  {\n    warn(\"Invalid status: %s\\nFailed for url '%s'\\n\", line, request.url);\n    return HOST_HTTP_INVALID_STATUS;\n  }\n\n  // Unhandled status categories\n  switch(request.status_type)\n  {\n    case 1:\n      return HOST_HTTP_INFO;\n\n    case 3:\n      return HOST_HTTP_REDIRECT;\n\n    case 4:\n      return HOST_HTTP_CLIENT_ERROR;\n\n    case 5:\n      return HOST_HTTP_SERVER_ERROR;\n  }\n\n  // Now parse the HTTP headers, extracting only the pertinent fields\n\n  while(true)\n  {\n    ssize_t len = http_receive_line(line, LINE_BUF_LEN);\n\n    if(len < 0)\n      return HOST_HTTP_INVALID_HEADER;\n\n    if(len == 0)\n      break;\n\n    HTTPHostStatus result = http_read_header_line(request, line, len);\n    if(result != HOST_SUCCESS)\n      return result;\n  }\n\n  // Filter by Content-Type (if requested).\n  HTTPHostStatus result = http_filter_content_type(request);\n  if(result != HOST_SUCCESS)\n    return result;\n\n  if(request.transfer_encoding_type == HTTPRequestInfo::EN_UNSUPPORTED)\n    return HOST_HTTP_INVALID_TRANSFER_ENCODING;\n\n  if(request.content_encoding_type == HTTPRequestInfo::EN_UNSUPPORTED)\n    return HOST_HTTP_INVALID_CONTENT_ENCODING;\n\n  // Use Bytef for these buffers since they'll mostly interact with zlib...\n  // outbuf will be expanded when/if it's needed.\n  ScopedBuffer<Bytef> block(BLOCK_SIZE);\n  ScopedBuffer<Bytef> outbuf;\n  if(!block)\n    return HOST_ALLOC_FAILED;\n\n  while(true)\n  {\n    unsigned long block_size;\n\n    /* Both transfer mechanisms need preambles. For NORMAL, this will\n     * happen only once, because we have a predetermined length for\n     * transfer. However, for CHUNKED we don't know the total payload\n     * size, so this will be invoked each time we exhaust a chunk.\n     *\n     * The CHUNKED handling basically involves chopping away the\n     * headers and determining the next chunk size.\n     */\n    if(!mid_chunk)\n    {\n      if(request.transfer_encoding_type == HTTPRequestInfo::EN_NORMAL)\n      {\n        len = request.content_length;\n      }\n      else\n\n      if(request.transfer_encoding_type == HTTPRequestInfo::EN_CHUNKED)\n      {\n        char *endptr, *length, *buf = line;\n\n        // Get a chunk_length;parameters formatted line (CRLF terminated)\n        if(http_receive_line(line, LINE_BUF_LEN) <= 0)\n          return HOST_HTTP_INVALID_CHUNK_LENGTH;\n\n        // HTTP 1.1 says we can ignore trailing parameters\n        length = strsep(&buf, \";\");\n        if(!length)\n          return HOST_HTTP_INVALID_CHUNK_LENGTH;\n\n        // Convert hex length to unsigned long; check for conversion errors\n        len = strtoul(length, &endptr, 16);\n        if(endptr[0])\n          return HOST_HTTP_INVALID_CHUNK_LENGTH;\n      }\n\n      mid_chunk = true;\n      pos = 0;\n    }\n\n    /* For NORMAL transfers, this indicates that there was a zero byte\n     * payload. This is unusual but we can handle it safely by aborting.\n     *\n     * For CHUNKED transfers, zero indicates that there are no more chunks\n     * to process, and that final footer handling should occur. We then\n     * abort as with NORMAL.\n     */\n    if(len == 0)\n    {\n      if(request.transfer_encoding_type == HTTPRequestInfo::EN_CHUNKED)\n        if(!http_skip_headers())\n          return HOST_HTTP_INVALID_HEADER;\n      break;\n    }\n\n    /* For a NORMAL transfer, the block_size computation should yield\n     * BLOCK_SIZE until the final block, which will be len % BLOCK_SIZE.\n     *\n     * For CHUNKED, this block_size can be more volatile. In most cases it\n     * will be BLOCK_SIZE if chunk size > BLOCK_SIZE, until the final block.\n     *\n     * However for very small chunks (which are unlikely) this will always\n     * be shorter than BLOCK_SIZE.\n     */\n    block_size = MIN(BLOCK_SIZE, len - pos);\n\n    /* In either case, all headers and block computation has now been done,\n     * and the buffer can be streamed to disk.\n     */\n    if(!this->receive(block, block_size))\n      return HOST_RECV_FAILED;\n\n    if(request.content_encoding_type == HTTPRequestInfo::EN_GZIP)\n    {\n      /* This is the first block requiring inflation. In this case, we must\n       * parse the GZIP header in order to compute an offset to the DEFLATE\n       * formatted data.\n       */\n      if(!mid_inflate)\n      {\n        ssize_t deflate_offset = 0;\n\n        /* Compute the offset within this block to begin the inflation\n         * process. For all but the first block, deflate_offset will be\n         * zero.\n         */\n        deflate_offset = zlib_skip_gzip_header(block, block_size);\n        if(deflate_offset < 0)\n          return (HTTPHostStatus)deflate_offset;\n\n        /* Now we can initialize the decompressor. Pass along the block\n         * without the GZIP header (and for a GZIP, this is also without\n         * the DEFLATE header too, which is what the -MAX_WBITS trick is for).\n         */\n        stream.avail_in = block_size - (unsigned long)deflate_offset;\n        stream.next_in = &block[deflate_offset];\n        stream.zalloc = Z_NULL;\n        stream.zfree = Z_NULL;\n        stream.opaque = Z_NULL;\n\n        if(inflateInit2(&stream, -MAX_WBITS) != Z_OK)\n          return HOST_ZLIB_INFLATE_FAILED;\n\n        mid_inflate = true;\n      }\n      else\n      {\n        stream.avail_in = block_size;\n        stream.next_in = block;\n      }\n\n      while(true)\n      {\n        outbuf.resize(BLOCK_SIZE);\n        if(!outbuf)\n          return HOST_ALLOC_FAILED;\n\n        // Each pass, only decompress a maximum of BLOCK_SIZE\n        stream.avail_out = BLOCK_SIZE;\n        stream.next_out = outbuf;\n\n        /* Perform the inflation (this will modify avail_in and\n         * next_in automatically.\n         */\n        int ret = inflate(&stream, Z_NO_FLUSH);\n        if(ret != Z_OK && ret != Z_STREAM_END)\n          return HOST_ZLIB_INFLATE_FAILED;\n\n        size_t amount = BLOCK_SIZE - stream.avail_out;\n        if(amount && vfwrite(outbuf, 1, amount, file) != amount)\n          return HOST_FWRITE_FAILED;\n\n        // If the stream has terminated, flag it and break out\n        if(ret == Z_STREAM_END)\n        {\n          mid_inflate = false;\n          break;\n        }\n\n        /* The stream hasn't terminated but we've exhausted input\n         * data for this pass.\n         */\n        if(stream.avail_in == 0)\n          break;\n      }\n\n      // The stream terminated, so we should free associated data-structures\n      if(!mid_inflate)\n        inflateEnd(&stream);\n    }\n    else\n    {\n      /* If the transfer is not deflated, we can simply write out\n       * block_size bytes to the file now.\n       */\n      if(vfwrite(block, 1, block_size, file) != block_size)\n        return HOST_FWRITE_FAILED;\n    }\n\n    pos += block_size;\n\n    if(this->receive_callback)\n      this->receive_callback(vftell(file));\n\n    /* For NORMAL transfers we can now abort since we have reached the end\n     * of our payload.\n     *\n     * For CHUNKED transfers, we remove the trailing newline and flag that\n     * a new set of chunk headers should be read.\n     */\n    if(len == pos)\n    {\n      if(request.transfer_encoding_type == HTTPRequestInfo::EN_NORMAL)\n      {\n        break;\n      }\n      else\n\n      if(request.transfer_encoding_type == HTTPRequestInfo::EN_CHUNKED)\n      {\n        if(http_receive_line(line, LINE_BUF_LEN) != 0)\n          return HOST_HTTP_INVALID_HEADER;\n        mid_chunk = false;\n      }\n    }\n  }\n  request.final_length = vftell(file);\n  return HOST_SUCCESS;\n}\n\nHTTPHostStatus HTTPHost::get(HTTPRequestInfo &request, char *buffer, size_t len)\n{\n  vfile *vf = vfile_init_mem(buffer, len, \"wb\");\n  HTTPHostStatus result = this->get(request, vf);\n  vfclose(vf);\n  return result;\n}\n\n#ifdef NETWORK_DEADCODE\n\nHTTPHostStatus HTTPHost::send_file(vfile *file, const char *mime_type)\n{\n  boolean mid_deflate = false;\n  char line[LINE_BUF_LEN];\n  uint32_t crc, uSize;\n  z_stream stream;\n  long size;\n\n  trace(\"--HOST-- HTTPHost::send_file\\n\");\n\n  // Tell the client that we're going to use HTTP/1.1 features\n  if(http_send_line(\"HTTP/1.1 200 OK\") < 0)\n    return HOST_SEND_FAILED;\n\n  /* To bring ourselves into complete HTTP 1.1 compliance, send\n   * some headers that we know our client doesn't actually need.\n   */\n  if(http_send_line(\"Accept-Ranges: bytes\") < 0)\n    return HOST_SEND_FAILED;\n  if(http_send_line(\"Vary: Accept-Encoding\") < 0)\n    return HOST_SEND_FAILED;\n\n  // Always zlib deflate content; keeps code simple\n  if(http_send_line(\"Content-Encoding: gzip\") < 0)\n    return HOST_SEND_FAILED;\n\n  // We'll just send everything chunked, unconditionally\n  if(http_send_line(\"Transfer-Encoding: chunked\") < 0)\n    return HOST_SEND_FAILED;\n\n  // Pass along a type hint for the client (mandatory sanity check for MZX)\n  snprintf(line, LINE_BUF_LEN, \"Content-Type: %s\", mime_type);\n  line[LINE_BUF_LEN - 1] = 0;\n  if(http_send_line(line) < 0)\n    return HOST_SEND_FAILED;\n\n  // Terminate the headers with a blank line\n  if(http_send_line(\"\") < 0)\n    return HOST_SEND_FAILED;\n\n  // Initialize CRC for GZIP footer\n  crc = crc32(0L, Z_NULL, 0);\n\n  // Record uncompressed size for GZIP footer\n  size = vfilelength(file, true);\n  uSize = (uint32_t)size;\n  if(size < 0)\n    return HOST_FREAD_FAILED;\n\n  while(true)\n  {\n    int deflate_offset = 0, ret = Z_OK, deflate_flag = Z_SYNC_FLUSH;\n    char block[BLOCK_SIZE], zblock[BLOCK_SIZE];\n    size_t block_size;\n\n    /* Read a block from the disk source and compute a CRC32 on the\n     * fly. This CRC will be dumped at the end of the deflated data\n     * and is required for RFC 1952 compliancy.\n     */\n    block_size = vfread(block, 1, BLOCK_SIZE, file);\n    crc = crc32(crc, (Bytef *)block, block_size);\n\n    /* The fread() above pretty much guarantees that block_size will\n     * be BLOCK_SIZE up to the final block. However, fread() also\n     * returns a short count if there was an I/O error, and we must\n     * detect this. If it's legitimately the final block, give the\n     * compressor this information.\n     */\n    if(block_size != BLOCK_SIZE)\n    {\n      /* FIXME\n      if(!feof(file))\n        return HOST_FREAD_FAILED;\n      */\n      deflate_flag = Z_FINISH;\n    }\n\n    /* We exhausted input at a block boundary. This is unlikely,\n     * but in the event that it happens we can simply ignore\n     * the deflate stage and write out the terminal chunk signature.\n     */\n    if(block_size == 0)\n      break;\n\n    /* Regardless of whether we are initializing the compressor in\n     * the next section or not, we always have BLOCK_SIZE aligned\n     * input data available.\n     */\n    stream.avail_in = block_size;\n    stream.next_in = (Bytef *)block;\n\n    if(!mid_deflate)\n    {\n      deflate_offset = zlib_forge_gzip_header(zblock);\n\n      stream.avail_out = BLOCK_SIZE - (unsigned long)deflate_offset;\n      stream.next_out = (Bytef *)&zblock[deflate_offset];\n      stream.zalloc = Z_NULL;\n      stream.zfree = Z_NULL;\n      stream.opaque = Z_NULL;\n\n      ret = deflateInit2(&stream, Z_BEST_COMPRESSION,\n       Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);\n      if(ret != Z_OK)\n        return HOST_ZLIB_DEFLATE_FAILED;\n\n      mid_deflate = true;\n    }\n    else\n    {\n      stream.avail_out = BLOCK_SIZE;\n      stream.next_out = (Bytef *)zblock;\n    }\n\n    while(true)\n    {\n      unsigned long chunk_size;\n\n      // Deflate a chunk of the input (partially or fully)\n      ret = deflate(&stream, deflate_flag);\n      if(ret != Z_OK && ret != Z_STREAM_END)\n        return HOST_ZLIB_INFLATE_FAILED;\n\n      // Compute chunk length (final chunk includes GZIP footer)\n      chunk_size = BLOCK_SIZE - stream.avail_out;\n      if(ret == Z_STREAM_END)\n        chunk_size += 2 * sizeof(uint32_t);\n\n      // Dump compressed chunk length\n      snprintf(line, LINE_BUF_LEN, \"%lx\", chunk_size);\n      if(http_send_line(line) < 0)\n        return HOST_SEND_FAILED;\n\n      // Send the compressed output block over the socket\n      if(!this->send(zblock, BLOCK_SIZE - stream.avail_out))\n        return HOST_SEND_FAILED;\n\n      /* We might not have finished the entire stream, but the\n       * available input is likely to have been exhausted. With\n       * Z_SYNC_FLUSH this will commonly result in zero bytes\n       * remaining in the input source. Additionally, if this is\n       * the final chunk (and Z_FINISH was flagged), Z_STREAM_END\n       * will be set. In either case, we must break out.\n       */\n      if((ret == Z_OK && stream.avail_in == 0) || ret == Z_STREAM_END)\n        break;\n\n      // Output has been flushed; start over for the remaining input (if any)\n      stream.avail_out = BLOCK_SIZE;\n      stream.next_out = (Bytef *)zblock;\n    }\n\n    /* Z_FINISH was flagged, stream ended\n     * Terminate compression\n     */\n    if(ret == Z_STREAM_END)\n    {\n      // Free any zlib allocated resources\n      deflateEnd(&stream);\n\n      // Write out GZIP `CRC32' footer\n      if(!this->send(&crc, sizeof(uint32_t)))\n        return HOST_SEND_FAILED;\n\n      // Write out GZIP `ISIZE' footer\n      if(!this->send(&uSize, sizeof(uint32_t)))\n        return HOST_SEND_FAILED;\n\n      mid_deflate = false;\n    }\n\n    // Newline after chunk's data\n    if(http_send_line(\"\") < 0)\n      return HOST_SEND_FAILED;\n\n    // Final block; can break out\n    if(block_size != BLOCK_SIZE)\n      break;\n  }\n\n  // Terminal chunk signature, so called \"trailer\"\n  if(http_send_line(\"0\") < 0)\n    return HOST_SEND_FAILED;\n\n  // Post-trailer newline\n  if(http_send_line(\"\") < 0)\n    return HOST_SEND_FAILED;\n\n  return HOST_SUCCESS;\n}\n\nstatic const char resp_404[] =\n \"<html>\"\n  \"<head><title>404</title></head>\"\n  \"<body><pre>404 ;-(</pre></body>\"\n \"</html>\";\n\nboolean HTTPHost::handle_request()\n{\n  const char *mime_type = \"application/octet-stream\";\n  char buffer[LINE_BUF_LEN], *buf = buffer;\n  char *cmd_type, *path, *proto;\n  HTTPHostStatus ret;\n  size_t path_len;\n  vfile *f;\n\n  trace(\"--HOST-- HTTPHost::handle_request\\n\");\n\n  if(http_receive_line(buffer, LINE_BUF_LEN) < 0)\n  {\n    warn(\"Failed to receive HTTP request\\n\");\n    return false;\n  }\n\n  cmd_type = strsep(&buf, \" \");\n  if(!cmd_type)\n    return false;\n\n  if(strcmp(cmd_type, \"GET\") != 0)\n    return false;\n\n  path = strsep(&buf, \" \");\n  if(!path)\n    return false;\n\n  proto = strsep(&buf, \" \");\n  if(!proto)\n    return false;\n\n  if(strncmp(\"HTTP/1.1\", proto, 8) != 0)\n  {\n    warn(\"Client must support HTTP 1.1, rejecting\\n\");\n    return false;\n  }\n\n  if(!http_skip_headers())\n  {\n    warn(\"Failed to skip HTTP headers\\n\");\n    return false;\n  }\n\n  path++;\n  trace(\"Received request for '%s'\\n\", path);\n\n  f = vfopen_unsafe(path, \"rb\");\n  if(!f)\n  {\n    warn(\"Failed to open file '%s', sending 404\\n\", path);\n\n    snprintf(buffer, LINE_BUF_LEN,\n     \"HTTP/1.1 404 Not Found\\r\\n\"\n     \"Content-Length: %zd\\r\\n\"\n     \"Content-Type: text/html\\r\\n\"\n     \"Connection: close\\r\\n\\r\\n\", strlen(resp_404));\n\n    if(this->send(buffer, strlen(buffer)))\n    {\n      if(!this->send(resp_404, strlen(resp_404)))\n        warn(\"Failed to send 404 payload\\n\");\n    }\n    else\n      warn(\"Failed to send 404 status code\\n\");\n\n    return false;\n  }\n\n  path_len = strlen(path);\n  if(path_len >= 4 && strcasecmp(&path[path_len - 4], \".txt\") == 0)\n    mime_type = \"text/plain\";\n\n  ret = this->send_file(f, mime_type);\n  vfclose(f);\n  if(ret != HOST_SUCCESS)\n  {\n    warn(\"Failed to send file '%s' over HTTP (error %d)\\n\", path, ret);\n    return false;\n  }\n\n  return true;\n}\n\n#endif\n"
  },
  {
    "path": "src/network/HTTPHost.hpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __HTTPHOST_HPP\n#define __HTTPHOST_HPP\n\n#include \"../compat.h\"\n#include \"../io/vfile.h\"\n#include \"Host.hpp\"\n\n#include <stdio.h> // for FILE\n\n/**\n * Enum for HTTP host error statuses.\n */\nenum HTTPHostStatus\n{\n  HOST_SUCCESS,\n  HOST_INIT_FAILED = -99,\n  HOST_FREAD_FAILED,\n  HOST_FWRITE_FAILED,\n  HOST_SEND_FAILED,\n  HOST_RECV_FAILED,\n  HOST_ALLOC_FAILED,\n  HOST_HTTP_EXCEEDED_BUFFER,\n  HOST_HTTP_INFO,\n  HOST_HTTP_REDIRECT,\n  HOST_HTTP_CLIENT_ERROR,\n  HOST_HTTP_SERVER_ERROR,\n  HOST_HTTP_INVALID_STATUS,\n  HOST_HTTP_INVALID_HEADER,\n  HOST_HTTP_INVALID_CONTENT_LENGTH,\n  HOST_HTTP_INVALID_TRANSFER_ENCODING,\n  HOST_HTTP_INVALID_CONTENT_TYPE,\n  HOST_HTTP_INVALID_CONTENT_ENCODING,\n  HOST_HTTP_INVALID_CHUNK_LENGTH,\n  HOST_ZLIB_INVALID_DATA,\n  HOST_ZLIB_INVALID_GZIP_HEADER,\n  HOST_ZLIB_DEFLATE_FAILED,\n  HOST_ZLIB_INFLATE_FAILED,\n  HOST_STATUS_ERROR_MAX\n};\n\n/**\n * Struct for HTTP request and response data.\n */\nstruct UPDATER_LIBSPEC HTTPRequestInfo\n{\n  static const int URL_BUF_SIZE = 256;\n  static const int STATUS_BUF_SIZE = 32;\n  static const int CONTENT_TYPE_BUF_SIZE = 64;\n  static const int ENC_BUF_SIZE = 32;\n\n  static const char * const plaintext_types[];\n  static const char * const binary_types[];\n\n  enum HTTPEncodingType\n  {\n    EN_UNSUPPORTED,\n    EN_NORMAL,\n    EN_CHUNKED,\n    EN_GZIP\n  };\n\n  /**\n   * This field must be set to the remote URL to request.\n   * It may be overwritten in the case of a redirect.\n   */\n  char url[URL_BUF_SIZE];\n\n  /**\n   * Set this field to a nullptr-terminated list of strings to filter the\n   * Content-Type of the response. Leaving it nullptr allows all content types.\n   */\n  const char * const *allowed_types;\n\n  // Response fields.\n  int status_type;\n  int status_code;\n  char status_message[STATUS_BUF_SIZE];\n  char content_type[CONTENT_TYPE_BUF_SIZE];\n  char content_type_params[CONTENT_TYPE_BUF_SIZE];\n  char content_encoding[ENC_BUF_SIZE];\n  char transfer_encoding[ENC_BUF_SIZE];\n  size_t content_length;\n  size_t final_length;\n  HTTPEncodingType transfer_encoding_type;\n  HTTPEncodingType content_encoding_type;\n\n  void clear();\n  void clear_response();\n  void print_response() const;\n};\n\n/**\n * Class for network connections over the HTTP protocol.\n */\nclass UPDATER_LIBSPEC HTTPHost: public Host\n{\nprotected:\n  ssize_t http_receive_line(char *buffer, size_t len);\n  ssize_t http_send_line(const char *message);\n  HTTPHostStatus http_read_status(HTTPRequestInfo &dest, const char *status,\n   size_t status_len);\n  HTTPHostStatus http_read_header_line(HTTPRequestInfo &dest, char *h,\n   size_t h_len);\n  HTTPHostStatus http_filter_content_type(const HTTPRequestInfo &request) const;\n  boolean http_skip_headers();\n\npublic:\n  /**\n   * See `Host()`.\n   */\n  HTTPHost(enum host_type type, enum host_family family): Host(type, family) {}\n\n  /**\n   * Get a description string for an `HTTPHostStatus` value.\n   *\n   * @param status        `HTTPHostStatus` value to get a description for.\n   *\n   * @return description of the provided status.\n   */\n  static const char *get_error_string(HTTPHostStatus status);\n\n  /**\n   * Send a HEAD request and return the response header info (if any).\n   *\n   * @param request       HTTP request to send; returns response data.\n   *\n   * @return see HTTPHostStatus.\n   */\n  HTTPHostStatus head(HTTPRequestInfo &request);\n\n  /**\n   * Send a GET request and stream the response (if any) to a file.\n   *\n   * @param request       HTTP request to send; returns response data.\n   * @param file          File pointer to stream to.\n   *\n   * @return see HTTPHostStatus.\n   */\n  HTTPHostStatus get(HTTPRequestInfo &request, vfile *file);\n\n  /**\n   * Send a GET request and stream the response (if any) to a buffer.\n   * Use request.final_length to determine the actual length of the response.\n   *\n   * @param request       HTTP request to send; returns response data.\n   * @param buffer        Buffer to stream to.\n   * @param buffer_len    Size of buffer.\n   *\n   * @return see HTTPHostStatus.\n   */\n  HTTPHostStatus get(HTTPRequestInfo &request, char *buffer, size_t buffer_len);\n\n#ifdef NETWORK_DEADCODE\n\n  /**\n   * Transmit a response to a GET request.\n   * Streams a file from disk to the network socket.\n   *\n   * @param file        File to stream to socket (must already exist).\n   * @param mime_type   MIME type of payload.\n   *\n   * @return see HTTPHostStatus.\n   */\n  HTTPHostStatus send_file(vfile *file, const char *mime_type);\n\n  /**\n   * Handle an incoming HTTP request.\n   *\n   * @return `true` if the request was successfully handled, otherwise `false`.\n   */\n  boolean handle_request();\n\n#endif\n\n};\n\n#endif /* __HTTPHOST_HPP */\n"
  },
  {
    "path": "src/network/Host.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"Host.hpp\"\n#include \"DNS.hpp\"\n#include \"Socket.hpp\"\n\n#include \"../platform.h\"\n#include \"../util.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <algorithm>\n#include <utility>\n\nstatic struct config_info *conf;\n\nstatic inline const char *get_proxy_error(enum proxy_status s)\n{\n  switch(s)\n  {\n    case PROXY_SUCCESS:\n      return \"proxy connection successful\";\n    case PROXY_INVALID_CONFIG:\n      return \"user configuration error\";\n    case PROXY_CONNECTION_FAILED:\n      return \"connection error (receive failed)\";\n    case PROXY_SEND_ERROR:\n      return \"connection error (send failed)\";\n    case PROXY_AUTH_FAILED:\n      return \"authentication failure\";\n    case PROXY_AUTH_VERSION_UNSUPPORTED:\n      return \"authentication protocol version not supported by remote host\";\n    case PROXY_AUTH_METHOD_UNSUPPORTED:\n      return \"authentication method not supported by remote host\";\n    case PROXY_HANDSHAKE_FAILED:\n      return \"protocol not supported by remote host\";\n    case PROXY_REFLECTION_FAILED:\n      return \"error establishing proxy connection\";\n    case PROXY_TARGET_REFUSED:\n      return \"connection refused by remote host\";\n    case PROXY_ADDRESS_TYPE_UNSUPPORTED:\n      return \"address provided by getaddrinfo is not supported by client\";\n    case PROXY_ADDRESS_INVALID:\n      return \"address provided by getaddrinfo is invalid\";\n    case PROXY_ACCESS_DENIED:\n      return \"connection not allowed by ruleset\";\n    case PROXY_UNKNOWN_ERROR:\n      break;\n  }\n  return \"unknown error (REPORT THIS!)\";\n}\n\nstatic inline const char *get_host_type_string(enum host_type type)\n{\n  switch(type)\n  {\n    case HOST_TYPE_TCP:\n      return \"TCP\";\n    case HOST_TYPE_UDP:\n      return \"UDP\";\n    case NUM_HOST_TYPES:\n      break;\n  }\n  return \"(unknown)\";\n}\n\nstatic inline const char *get_host_family_string(enum host_family family)\n{\n  switch(family)\n  {\n    case HOST_FAMILY_IPV4:\n      return \"IPv4\";\n    case HOST_FAMILY_IPV6:\n      return \"IPv6\";\n    case HOST_FAMILY_ANY:\n      return \"any\";\n    case NUM_HOST_FAMILIES:\n      break;\n  }\n  return \"(unknown)\";\n}\n\nstatic int host_type_to_proto(enum host_type type)\n{\n  switch(type)\n  {\n    case HOST_TYPE_UDP:\n      return IPPROTO_UDP;\n\n    default:\n      return IPPROTO_TCP;\n  }\n}\n\nstatic int host_type_to_socktype(enum host_type type)\n{\n  switch(type)\n  {\n    case HOST_TYPE_UDP:\n      return SOCK_DGRAM;\n\n    default:\n      return SOCK_STREAM;\n  }\n}\n\nstatic int host_family_to_af(enum host_family family)\n{\n  switch(family)\n  {\n#ifdef CONFIG_IPV6\n    case HOST_FAMILY_IPV6:\n      return AF_INET6;\n\n    // This allows getaddrinfo to return both IPv4 and IPv6 addresses.\n    case HOST_FAMILY_ANY:\n      return AF_UNSPEC;\n#endif\n    default:\n      return AF_INET;\n  }\n}\n\nstatic int host_family_hint_flags(enum host_family family)\n{\n  switch(family)\n  {\n#ifdef CONFIG_IPV6\n    // This flag allows IPv4-mapped addresses if no IPv6 addresses are found.\n    case HOST_FAMILY_IPV6:\n      return AI_V4MAPPED;\n\n    // Both allowed--try to autodetect which ones work based on system config.\n    case HOST_FAMILY_ANY:\n      return AI_ADDRCONFIG;\n#endif\n    default:\n      return 0;\n  }\n}\n\nboolean Host::host_layer_init(struct config_info *in_conf)\n{\n  if(!Socket::init(in_conf))\n    return false;\n\n  if(!DNS::init(in_conf))\n    return false;\n\n  conf = in_conf;\n  return true;\n}\n\nboolean Host::host_layer_init_check()\n{\n  if(!conf)\n    return false;\n\n  return Socket::init_late();\n}\n\nvoid Host::host_layer_exit(void)\n{\n  DNS::exit();\n  Socket::exit();\n}\n\nvoid Host::swap(Host &a, Host &b)\n{\n  std::swap(a.state, b.state);\n  std::swap(a.type, b.type);\n  std::swap(a.preferred_family, b.preferred_family);\n  std::swap(a.hint_af, b.hint_af);\n  std::swap(a.hint_socktype, b.hint_socktype);\n  std::swap(a.hint_proto, b.hint_proto);\n  std::swap(a.hint_flags, b.hint_flags);\n  std::swap(a.name, b.name);\n  std::swap(a.endpoint, b.endpoint);\n  std::swap(a.proxied, b.proxied);\n  std::swap(a.trace_raw, b.trace_raw);\n  std::swap(a.af, b.af);\n  std::swap(a.sockfd, b.sockfd);\n  std::swap(a.proto, b.proto);\n  std::swap(a.timeout_ms, b.timeout_ms);\n  std::swap(a.receive_callback, b.receive_callback);\n  std::swap(a.cancel_callback, b.cancel_callback);\n}\n\nHost::Host(enum host_type type, enum host_family family)\n{\n  trace(\"--HOST-- Host::Host(%s, %s)\\n\",\n   get_host_type_string(type), get_host_family_string(family));\n\n  this->state = HOST_UNINITIALIZED;\n  this->type = type;\n  this->preferred_family = family;\n  this->timeout_ms = Host::TIMEOUT_DEFAULT;\n\n  // Hints to provide to getaddrinfo.\n  this->hint_af = host_family_to_af(family);\n  this->hint_socktype = host_type_to_socktype(type);\n  this->hint_proto = host_type_to_proto(type);\n  this->hint_flags = host_family_hint_flags(family);\n\n  this->name = nullptr;\n  this->endpoint = nullptr;\n  this->receive_callback = nullptr;\n  this->cancel_callback = nullptr;\n  this->proxied = false;\n  this->trace_raw = false;\n}\n\nHost::~Host()\n{\n  trace(\"--HOST-- Host::~Host\\n\");\n  this->close();\n}\n\n/**\n * Creates a socket and initializes sockfd and af.\n * This may be called multiple times as part connect(), bind(), sendto(),\n * or recvfrom().\n *\n * @param type     Transport protocol to use (HOST_TYPE_TCP or HOST_TYPE_UDP).\n * @param family   Address family to use (HOST_FAMILY_IPV4 or HOST_FAMILY_IPV6).\n *\n * @return `true` on successful socket creation, otherwise `false`.\n */\nboolean Host::create_socket(enum host_type type, enum host_family family)\n{\n  struct linger linger = { 1, 30 };\n  const uint32_t on = 1;\n  int err, fd, af, proto;\n\n  trace(\"--HOST-- Host::create_socket(%s, %s)\\n\",\n   get_host_type_string(type), get_host_family_string(family));\n  assert(type == HOST_TYPE_TCP || type == HOST_TYPE_UDP);\n  assert(family == HOST_FAMILY_IPV4 || family == HOST_FAMILY_IPV6);\n\n  assert(this->state == HOST_UNINITIALIZED);\n  if(this->state != HOST_UNINITIALIZED)\n    return false;\n\n  af = host_family_to_af(family);\n\n  switch(type)\n  {\n    case HOST_TYPE_UDP:\n      fd = Socket::socket(af, SOCK_DGRAM, IPPROTO_UDP);\n      proto = IPPROTO_UDP;\n      break;\n\n    default:\n      fd = Socket::socket(af, SOCK_STREAM, IPPROTO_TCP);\n      proto = IPPROTO_TCP;\n      break;\n  }\n\n  if(fd < 0)\n  {\n    Socket::perror(\"socket\");\n    return false;\n  }\n\n#if defined(CONFIG_IPV6) && defined(IPV6_V6ONLY)\n  if(af == AF_INET6)\n  {\n    const uint32_t off = 0;\n    err = Socket::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&off, 4);\n    if(err < 0)\n    {\n      Socket::perror(\"setsockopt(IPV6_V6ONLY)\");\n      goto err_close;\n    }\n  }\n#endif\n\n  /* We need to turn off bind address checking, allowing\n   * port numbers to be reused; otherwise, TIME_WAIT will\n   * delay binding to these ports for 2 * MSL seconds.\n   */\n  err = Socket::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, 4);\n  if(err < 0)\n  {\n    Socket::perror(\"setsockopt(SO_REUSEADDR)\");\n    goto err_close;\n  }\n\n  /* When connection is closed, we want to \"linger\" to\n   * make sure everything is transmitted. Enable this\n   * feature here. However, we can only do this on TCP\n   * sockets, as some platforms (winsock) don't like\n   * modifying linger on UDP sockets.\n   */\n  if(proto == IPPROTO_TCP)\n  {\n    err = Socket::setsockopt(fd, SOL_SOCKET, SO_LINGER,\n     (void *)&linger, sizeof(struct linger));\n    if(err < 0)\n    {\n      Socket::perror(\"setsockopt(SO_LINGER)\");\n      goto err_close;\n    }\n  }\n\n#ifdef SO_NOSIGPIPE\n  /**\n   * Disable SIGPIPE for this socket on platforms that support this option.\n   * This is redundant with the send() flag MSG_NOSIGNAL, as neither seems to\n   * be universally supported.\n   */\n  err = Socket::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&on, sizeof(on));\n  if(err < 0)\n  {\n    Socket::perror(\"setsockopt(SO_NOSIGPIPE)\");\n    goto err_close;\n  }\n#endif\n\n  // Initially the socket is blocking\n  Socket::set_blocking(fd, true);\n\n  this->state = HOST_INITIALIZED;\n  this->af = af;\n  this->sockfd = fd;\n  this->proto = proto;\n  return true;\n\nerr_close:\n  Socket::close(fd);\n  return false;\n}\n\nvoid Host::close()\n{\n  if(this->state != HOST_UNINITIALIZED)\n  {\n    Socket::close(this->sockfd);\n    this->state = HOST_UNINITIALIZED;\n  }\n}\n\nconst char *Host::get_host_name()\n{\n  if(this->proxied)\n    return this->endpoint;\n\n  return this->name;\n}\n\nvoid Host::set_timeout_ms(uint32_t timeout_ms)\n{\n  this->timeout_ms = timeout_ms;\n}\n\nboolean Host::send(const void *buffer, size_t len)\n{\n  const char *buf = (const char *)buffer;\n  uint32_t start, now;\n  ssize_t count;\n  size_t pos;\n\n  start = get_ticks();\n\n  // send stuff in blocks, incrementally\n  for(pos = 0; pos < len; pos += count)\n  {\n    now = get_ticks();\n\n    // time out if no data is sent\n    if(now - start > this->timeout_ms)\n      return false;\n\n    // normally it won't all get through at once\n    count = Socket::send(this->sockfd, buf + pos, len - pos, MSG_NOSIGNAL);\n    if(count < 0 && Socket::is_last_error_fatal())\n    {\n      return false;\n    }\n    else\n\n    if(count <= 0)\n    {\n      // non-blocking socket, so can fail and still be ok\n      // Short delay to prevent excessive CPU usage.\n      delay(10);\n      count = 0;\n    }\n\n    if(this->cancel_callback && this->cancel_callback())\n      return false;\n\n#ifdef DEBUG_TRACE\n    if(trace_raw && count)\n    {\n      trace(\"--HOST-- Host::send    \");\n      for(size_t i = pos; i < pos + count; i++)\n        fprintf(mzxerr, \"%02x \", buf[i]);\n      fprintf(mzxerr, \"\\n\");\n      fflush(mzxerr);\n    }\n#endif\n  }\n  return true;\n}\n\nboolean Host::receive(void *buffer, size_t len)\n{\n  char *buf = (char *)buffer;\n  uint32_t start, now;\n  ssize_t count;\n  size_t pos;\n\n  start = get_ticks();\n\n  // receive stuff in blocks, incrementally\n  for(pos = 0; pos < len; pos += count)\n  {\n    now = get_ticks();\n\n    // time out if no data is received\n    if(now - start > this->timeout_ms)\n      return false;\n\n    // normally it won't all get through at once\n    count = Socket::recv(this->sockfd, buf + pos, len - pos, 0);\n    if(count < 0 && Socket::is_last_error_fatal())\n    {\n      return false;\n    }\n    else\n\n    if(count <= 0)\n    {\n      // non-blocking socket, so can fail and still be ok\n      // Add a short delay to prevent excessive CPU use\n      delay(10);\n      count = 0;\n    }\n\n    if(this->cancel_callback && this->cancel_callback())\n      return false;\n\n#ifdef DEBUG_TRACE\n    if(trace_raw && count)\n    {\n      trace(\"--HOST-- Host::receive \");\n      for(size_t i = pos; i < pos + count; i++)\n        fprintf(mzxerr, \"%02x \", buf[i]);\n      fprintf(mzxerr, \"\\n\");\n      fflush(mzxerr);\n    }\n#endif\n  }\n  return true;\n}\n\nstatic boolean create_connection_check_state(int sockfd, struct addrinfo *ai)\n{\n  int error = 0;\n  socklen_t len = sizeof(int);\n  int res = Socket::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);\n  if(res || len != sizeof(int))\n  {\n    Socket::perror(\"create_connection_check_state: getsockopt(SO_ERROR)\");\n    return false;\n  }\n\n#ifdef CONFIG_3DS\n  /**\n   * libctru getsockopt returns this value, which is apparently an internal\n   * code for EINPROGRESS that hasn't been converted to a newlib errno.\n   *\n   * Recommended workaround is \"try just calling connect() again\".\n   * https://github.com/devkitPro/libctru/issues/412\n   */\n  if(error == -26)\n  {\n    res = Socket::connect(sockfd, ai->ai_addr, (socklen_t)ai->ai_addrlen);\n    if(res != 0)\n    {\n      int tmp_error = Socket::get_errno();\n      if(tmp_error == EISCONN)\n      {\n        trace(\n          \"--HOST-- second connect attempt returned %d; assuming \"\n          \"the connection was successful.\\n\", tmp_error\n        );\n        error = 0;\n      }\n      else\n        Socket::perror(\"create_connection_check_state: 3ds connect retry hack\");\n    }\n  }\n#endif\n\n  if(error == 0)\n    return true;\n\n  trace(\"--HOST-- getsockopt(SO_ERROR) returned %d, aborting.\\n\", error);\n  return false;\n}\n\n/**\n * Create a socket and connect to a host.\n *\n * @param ai       `addrinfo` struct for host to connect to.\n * @param family   Address family to use (HOST_FAMILY_IPV4 or HOST_FAMILY_IPV6).\n *\n * @return `true` on successful socket creation/connection, otherwise `false`.\n *         On failure, any opened socket will be closed.\n */\nboolean Host::create_connection(struct addrinfo *ai, enum host_family family)\n{\n  int res;\n\n  trace(\"--HOST-- Host::create_connection\\n\");\n\n  if(!this->create_socket(this->type, family))\n    return false;\n\n  // Disable blocking on the socket so a timeout can be enforced.\n  Socket::set_blocking(this->sockfd, false);\n\n  res = Socket::connect(this->sockfd, ai->ai_addr, (socklen_t)ai->ai_addrlen);\n  if(res)\n  {\n    // This may be caused by an actual error or it might just be EINPROGRESS,\n    // which means the connect call worked.\n    if(Socket::is_last_error_fatal())\n    {\n      Socket::perror(\"create_connection: connect\");\n      Socket::close(this->sockfd);\n      this->state = HOST_UNINITIALIZED;\n      return false;\n    }\n  }\n\n  res = this->poll(Host::POLL_WRITE, this->timeout_ms);\n  if(Host::is_poll_error(res) || !(res & Host::POLL_WRITE))\n  {\n    if(res >= 0)\n    {\n      trace(\"--HOST-- Host::create_connection: Host::poll returned %d\\n\", res);\n      warn(\"Connection failed or timed out.\\n\");\n    }\n    else\n      Socket::perror(\"create_connection: Host::poll\");\n\n    Socket::close(this->sockfd);\n    this->state = HOST_UNINITIALIZED;\n    return false;\n  }\n\n  // The select can apparently return successfully even if the connection\n  // failed. Check the socket error state directly.\n  if(!create_connection_check_state(this->sockfd, ai))\n  {\n    Socket::close(this->sockfd);\n    this->state = HOST_UNINITIALIZED;\n    return false;\n  }\n\n  Socket::set_blocking(this->sockfd, true);\n  return true;\n}\n\n/**\n * `address_op` callback for Host::raw_connect.\n */\nstruct addrinfo *Host::create_connection_op(struct addrinfo *ais, void *priv)\n{\n  struct addrinfo *ai;\n  trace(\"--HOST-- Host::create_connection_op\\n\");\n\n#ifdef CONFIG_IPV6\n  /* First try to connect to an IPv6 address (if any)\n   */\n  for(ai = ais; ai; ai = ai->ai_next)\n  {\n    if(ai->ai_family == AF_INET6)\n    {\n      if(this->create_connection(ai, HOST_FAMILY_IPV6))\n        return ai;\n    }\n  }\n#endif\n\n  /* No IPv6 addresses could be connected; try IPv4 (if any)\n   */\n  for(ai = ais; ai; ai = ai->ai_next)\n  {\n    if(ai->ai_family == AF_INET)\n    {\n      if(this->create_connection(ai, HOST_FAMILY_IPV4))\n        return ai;\n    }\n  }\n  return nullptr;\n}\n\n/**\n * Perform an operation that requires the iteration of an address lookup.\n *\n * @param hostname  Host to connect to.\n * @param port      Host port to connect to.\n * @param priv      Pointer to pass on to `op`.\n * @param op        Operation to perform on the resulting `addrinfo` list.\n *\n * @return `true` if the operation completed successfully, otherwise `false`.\n */\nboolean Host::address_op(const char *hostname, int port, void *priv,\n struct addrinfo *(Host::*op)(struct addrinfo *ais, void *priv))\n{\n  struct addrinfo hints, *ais, *ai;\n  char port_str[6];\n  int ret;\n\n  trace(\"--HOST-- Host::address_op\\n\");\n\n  // Stringify user port for \"service\" argument below\n  snprintf(port_str, 6, \"%d\", port);\n  port_str[5] = 0;\n\n  // Set some hints for the getaddrinfo() call\n  memset(&hints, 0, sizeof(struct addrinfo));\n  hints.ai_socktype = this->hint_socktype;\n  hints.ai_protocol = this->hint_proto;\n  hints.ai_family = this->hint_af;\n  hints.ai_flags = this->hint_flags;\n\n  // Look up host(s) matching hints\n  ret = DNS::lookup(hostname, port_str, &hints, &ais, this->timeout_ms);\n  if(ret != 0)\n  {\n    Socket::getaddrinfo_perror(\"address_op\", ret);\n    warn(\"Failed to look up '%s'\\n\", hostname);\n    return false;\n  }\n\n  trace(\"--HOST-- Host::address_op: calling this->*op!\\n\");\n  ai = (this->*op)(ais, priv);\n  Socket::freeaddrinfo(ais);\n\n  // None of the listed hosts (if any) were connectable\n  if(!ai)\n  {\n    warn(\"No routeable host '%s' found\\n\", hostname);\n    return false;\n  }\n\n  trace(\"--HOST-- Host::address_op: completed successfully!\\n\");\n\n  // TODO should duplicate this. It's safe right now since the hostname always\n  // comes from the config file, but that won't always be the case.\n  if(!this->name)\n    this->name = hostname;\n\n  return true;\n}\n\nboolean Host::connect_direct(const char *hostname, int port)\n{\n  trace(\"--HOST-- Host::connect_direct(%s, %d)\\n\", hostname, port);\n  return this->address_op(hostname, port, nullptr, &Host::create_connection_op);\n}\n\nenum proxy_status Host::connect_socks4a(const char *target_host, int target_port)\n{\n  char handshake[8];\n\n  trace(\"--HOST-- Host::connect_socks4a\\n\");\n  target_port = Socket::hton_short(target_port);\n\n  if(!this->connect_direct(conf->socks_host, conf->socks_port))\n    return PROXY_CONNECTION_FAILED;\n\n  // Temporarily disable blocking in case the connection needs to time out.\n  Socket::scoped_nonblocking nb(this->sockfd);\n\n  if(!this->send(\"\\4\\1\", 2))\n    return PROXY_SEND_ERROR;\n  if(!this->send(&target_port, 2))\n    return PROXY_SEND_ERROR;\n  if(!this->send(\"\\0\\0\\0\\1anonymous\", 14))\n    return PROXY_SEND_ERROR;\n  if(!this->send(target_host, strlen(target_host)))\n    return PROXY_SEND_ERROR;\n  if(!this->send(\"\\0\", 1))\n    return PROXY_SEND_ERROR;\n\n  if(!this->receive(handshake, 8))\n    return PROXY_CONNECTION_FAILED;\n  if(handshake[0] != 0x00)\n    return PROXY_HANDSHAKE_FAILED;\n  switch(handshake[1])\n  {\n    case 0x5A:\n      break;\n    case 0x5B:\n      return PROXY_TARGET_REFUSED;\n    case 0x5C:\n    case 0x5D:\n      return PROXY_AUTH_FAILED;\n    default:\n      return PROXY_HANDSHAKE_FAILED;\n  }\n  return PROXY_SUCCESS;\n}\n\nenum proxy_status Host::connect_socks4(struct addrinfo *ai_data)\n{\n  char handshake[8];\n\n  trace(\"--HOST-- Host::connect_socks4\\n\");\n\n  if(!this->connect_direct(conf->socks_host, conf->socks_port))\n    return PROXY_CONNECTION_FAILED;\n\n  // Temporarily disable blocking in case the connection needs to time out.\n  Socket::scoped_nonblocking nb(this->sockfd);\n\n  struct sockaddr_in *addr = reinterpret_cast<struct sockaddr_in *>(ai_data->ai_addr);\n  if(addr->sin_family != AF_INET)\n    return PROXY_ADDRESS_INVALID;\n\n  if(!this->send(\"\\4\\1\", 2))\n    return PROXY_SEND_ERROR;\n  if(!this->send(&(addr->sin_port), 2))\n    return PROXY_SEND_ERROR;\n  if(!this->send(&(addr->sin_addr), 4))\n    return PROXY_SEND_ERROR;\n  if(!this->send(\"anonymous\\0\", 10))\n    return PROXY_SEND_ERROR;\n\n  if(!this->receive(handshake, 8))\n    return PROXY_CONNECTION_FAILED;\n  if(handshake[0] != 0x00)\n    return PROXY_HANDSHAKE_FAILED;\n  switch(handshake[1])\n  {\n    case 0x5A:\n      break;\n    case 0x5B:\n      return PROXY_TARGET_REFUSED;\n    case 0x5C:\n    case 0x5D:\n      return PROXY_AUTH_FAILED;\n    default:\n      return PROXY_HANDSHAKE_FAILED;\n  }\n  return PROXY_SUCCESS;\n}\n\nenum proxy_status Host::connect_socks5(struct addrinfo *ai_data)\n{\n  /* This handshake is more complicated than SOCKS4 or 4a...\n   * and we're also only supporting none or user/password auth.\n   */\n\n  char handshake[32];\n\n  trace(\"--HOST-- Host::connect_socks5\\n\");\n\n  if(!this->connect_direct(conf->socks_host, conf->socks_port))\n    return PROXY_CONNECTION_FAILED;\n\n  // Temporarily disable blocking in case the connection needs to time out.\n  Socket::scoped_nonblocking nb(this->sockfd);\n\n  // Version[0x05]|Number of auth methods|auth methods (0=none, 2=user/pw)\n  const char *init = \"\\5\\1\\0\";\n  size_t init_len = 3;\n  if(conf->socks_username[0] && conf->socks_password[0])\n  {\n    init = \"\\5\\2\\0\\2\";\n    init_len = 4;\n  }\n\n  if(!this->send(init, init_len))\n    return PROXY_SEND_ERROR;\n\n  if(!this->receive(handshake, 2))\n    return PROXY_CONNECTION_FAILED;\n  if(handshake[0] != 0x5)\n    return PROXY_HANDSHAKE_FAILED;\n\n  switch(handshake[1])\n  {\n    // None.\n    case 0x0:\n      break;\n\n    // Username/Password.\n    // https://tools.ietf.org/html/rfc1929\n    case 0x2:\n    {\n      size_t username_len = strlen(conf->socks_username);\n      size_t password_len = strlen(conf->socks_password);\n      if(username_len > 255 || password_len > 255)\n        return PROXY_INVALID_CONFIG;\n\n      uint8_t username_len_b = static_cast<uint8_t>(username_len);\n      uint8_t password_len_b = static_cast<uint8_t>(password_len);\n\n      if(!this->send(\"\\1\", 1))\n        return PROXY_SEND_ERROR;\n      if(!this->send(&username_len_b, 1))\n        return PROXY_SEND_ERROR;\n      if(!this->send(conf->socks_username, username_len))\n        return PROXY_SEND_ERROR;\n      if(!this->send(&password_len_b, 1))\n        return PROXY_SEND_ERROR;\n      if(!this->send(conf->socks_password, password_len))\n        return PROXY_SEND_ERROR;\n\n      if(!this->receive(handshake, 2))\n        return PROXY_CONNECTION_FAILED;\n      if(handshake[0] != 0x1)\n        return PROXY_AUTH_VERSION_UNSUPPORTED;\n      if(handshake[1] != 0x0)\n        return PROXY_AUTH_FAILED;\n      break;\n    }\n\n    // No supported methods (0xFF) or some other value.\n    default:\n      return PROXY_AUTH_METHOD_UNSUPPORTED;\n  }\n\n  struct sockaddr *ai_addr = ai_data->ai_addr;\n  switch(ai_data->ai_family)\n  {\n#ifdef CONFIG_IPV6\n    case AF_INET6:\n    {\n      struct sockaddr_in6 *addr = reinterpret_cast<struct sockaddr_in6 *>(ai_addr);\n      if(addr->sin6_family != AF_INET6)\n        return PROXY_ADDRESS_INVALID;\n\n      // Version[0x05]|Command|0x00|address type (4=IPv6)|address|port\n      if(!this->send(\"\\5\\1\\0\\4\", 4))\n        return PROXY_SEND_ERROR;\n      if(!this->send(&(addr->sin6_addr), 16))\n        return PROXY_SEND_ERROR;\n      if(!this->send(&(addr->sin6_port), 2))\n        return PROXY_SEND_ERROR;\n      break;\n    }\n#endif\n\n    case AF_INET:\n    {\n      struct sockaddr_in *addr = reinterpret_cast<struct sockaddr_in *>(ai_addr);\n      if(addr->sin_family != AF_INET)\n        return PROXY_ADDRESS_INVALID;\n\n      // Version[0x05]|Command|0x00|address type (1=IPv4)|address|port\n      if(!this->send(\"\\5\\1\\0\\1\", 4))\n        return PROXY_SEND_ERROR;\n      if(!this->send(&(addr->sin_addr), 4))\n        return PROXY_SEND_ERROR;\n      if(!this->send(&(addr->sin_port), 2))\n        return PROXY_SEND_ERROR;\n      break;\n    }\n\n    default:\n      return PROXY_ADDRESS_TYPE_UNSUPPORTED;\n  }\n\n  // Version[0x05]|Status|0x00|address type|address|port\n  if(!this->receive(handshake, 4))\n    return PROXY_CONNECTION_FAILED;\n  switch(handshake[1])\n  {\n    case 0x0:\n      break;\n    case 0x1:\n    case 0x7:\n      return PROXY_UNKNOWN_ERROR;\n    case 0x2:\n      return PROXY_ACCESS_DENIED;\n    case 0x3:\n    case 0x4:\n    case 0x6:\n      return PROXY_REFLECTION_FAILED;\n    case 0x5:\n      return PROXY_TARGET_REFUSED;\n    case 0x8:\n      return PROXY_ADDRESS_TYPE_UNSUPPORTED;\n    default:\n      return PROXY_UNKNOWN_ERROR;\n  }\n\n  switch(handshake[3])\n  {\n    // IPv4\n    case 0x1:\n      if(!this->receive(handshake + 4, 6))\n        return PROXY_CONNECTION_FAILED;\n      break;\n\n    // IPv6\n    case 0x4:\n      if(!this->receive(handshake + 4, 18))\n        return PROXY_CONNECTION_FAILED;\n      break;\n\n    default:\n      return PROXY_ADDRESS_TYPE_UNSUPPORTED;\n  }\n  return PROXY_SUCCESS;\n}\n\n// SOCKS Proxy support\nenum proxy_status Host::connect_proxy(const char *target_host, int target_port)\n{\n  struct addrinfo target_hints, *target_ais, *target_ai;\n  char port_str[6];\n  int ret;\n\n  trace(\"--HOST-- Host::connect_proxy(%s, %d)\\n\", target_host, target_port);\n\n  snprintf(port_str, 6, \"%d\", target_port);\n  port_str[5] = 0;\n\n  memset(&target_hints, 0, sizeof(struct addrinfo));\n  target_hints.ai_socktype = this->hint_socktype;\n  target_hints.ai_protocol = this->hint_proto;\n  target_hints.ai_family = this->hint_af;\n  target_hints.ai_flags = this->hint_flags;\n  ret = DNS::lookup(target_host, port_str, &target_hints, &target_ais,\n   this->timeout_ms);\n\n  /* Some perimeter gateways block access to DNS [wifi hotspots are\n   * particularly notorious for this] so we have to fall back to SOCKS4a\n   * to force the proxy to DNS the address for us. If this fails, we abort.\n   */\n  if(ret != 0)\n  {\n    enum proxy_status ret = this->connect_socks4a(target_host, target_port);\n\n    if(ret != PROXY_SUCCESS)\n      trace(\"--HOST-- Host::connect_socks4a failed: %s\\n\", get_proxy_error(ret));\n\n    return ret;\n  }\n\n#ifdef CONFIG_IPV6\n  for(target_ai = target_ais; target_ai; target_ai = target_ai->ai_next)\n  {\n    if(target_ai->ai_family == AF_INET6)\n    {\n      enum proxy_status ret = this->connect_socks5(target_ai);\n      if(ret == PROXY_SUCCESS)\n      {\n        Socket::freeaddrinfo(target_ais);\n        return PROXY_SUCCESS;\n      }\n      trace(\"--HOST-- Host::connect_socks5 failed: %s\\n\", get_proxy_error(ret));\n      this->close();\n    }\n  }\n#endif\n\n  for(target_ai = target_ais; target_ai; target_ai = target_ai->ai_next)\n  {\n    if(target_ai->ai_family == AF_INET)\n    {\n      enum proxy_status ret = this->connect_socks5(target_ai);\n      if(ret == PROXY_SUCCESS)\n      {\n        Socket::freeaddrinfo(target_ais);\n        return PROXY_SUCCESS;\n      }\n      this->close();\n      trace(\"--HOST-- Host::connect_socks5 failed: %s\\n\", get_proxy_error(ret));\n      ret = this->connect_socks4(target_ai);\n      if(ret == PROXY_SUCCESS)\n      {\n        Socket::freeaddrinfo(target_ais);\n        return PROXY_SUCCESS;\n      }\n      this->close();\n      trace(\"--HOST-- Host::connect_socks4 failed: %s\\n\", get_proxy_error(ret));\n    }\n  }\n  Socket::freeaddrinfo(target_ais);\n  return PROXY_CONNECTION_FAILED;\n}\n\nboolean Host::connect(const char *hostname, int port)\n{\n  trace(\"--HOST-- Host::connect(%s, %d)\\n\", hostname, port);\n  assert(this->state == HOST_UNINITIALIZED);\n  if(this->state != HOST_UNINITIALIZED)\n    return false;\n\n  this->proxied = false;\n  if(strlen(conf->socks_host) > 0)\n  {\n    this->trace_raw = true;\n    enum proxy_status ret = this->connect_proxy(hostname, port);\n    this->trace_raw = false;\n\n    if(ret == PROXY_SUCCESS)\n    {\n      this->proxied = true;\n      this->endpoint = hostname;\n      return true;\n    }\n    this->close();\n    return false;\n  }\n  return this->connect_direct(hostname, port);\n}\n\nvoid Host::set_callbacks(void (*send_callback)(long offset),\n void (*receive_callback)(long offset), boolean (*cancel_callback)(void))\n{\n  trace(\"--HOST-- Host::set_callbacks\\n\");\n  assert(send_callback == nullptr);\n  this->receive_callback = receive_callback;\n  this->cancel_callback = cancel_callback;\n}\n\nboolean Host::is_last_error_fatal()\n{\n  return Socket::is_last_error_fatal();\n}\n\nvoid Host::set_blocking(boolean blocking)\n{\n  trace(\"--HOST-- Host::set_blocking(%d)\\n\", (int)blocking);\n  assert(this->state != HOST_UNINITIALIZED);\n  Socket::set_blocking(this->sockfd, blocking);\n}\n\nboolean Host::accept(Host &client_host)\n{\n  boolean retval = false;\n  struct sockaddr *addr;\n  socklen_t addrlen;\n  int newfd;\n\n  assert(this->state == HOST_BOUND);\n\n  switch(this->af)\n  {\n#ifdef CONFIG_IPV6\n    case AF_INET6:\n      addrlen = sizeof(struct sockaddr_in6);\n      break;\n#endif\n    default:\n      addrlen = sizeof(struct sockaddr_in);\n      break;\n  }\n\n  addr = (struct sockaddr *)cmalloc(addrlen);\n\n  newfd = Socket::accept(this->sockfd, addr, &addrlen);\n  if(newfd >= 0)\n  {\n    trace(\"--HOST-- Host::accept: accepting new connection!\\n\");\n    assert(addr->sa_family == this->af);\n\n    // Make sure any open connection on the provided client host is released.\n    client_host.close();\n\n    Socket::set_blocking(newfd, true);\n    client_host.state = HOST_CONNECTED;\n    client_host.sockfd = newfd;\n    client_host.af = addr->sa_family;\n    client_host.proto = this->proto;\n    client_host.name = nullptr;\n    client_host.endpoint = nullptr;\n    client_host.proxied = false;\n    client_host.timeout_ms = this->timeout_ms;\n    retval = true;\n  }\n  else\n  {\n    if(Socket::is_last_error_fatal())\n      Socket::perror(\"accept\");\n  }\n\n  free(addr);\n  return retval;\n}\n\nstruct addrinfo *Host::bind_op(struct addrinfo *ais, void *priv)\n{\n  struct addrinfo *ai;\n  trace(\"--HOST-- Host::bind_op\\n\");\n\n#ifdef CONFIG_IPV6\n  /* First try to bind to an IPv6 address (if any)\n   */\n  for(ai = ais; ai; ai = ai->ai_next)\n  {\n    if(ai->ai_family == AF_INET6)\n    {\n      if(!this->create_socket(this->type, HOST_FAMILY_IPV6))\n        return nullptr;\n\n      if(Socket::bind(this->sockfd, ai->ai_addr, ai->ai_addrlen) >= 0)\n      {\n        this->state = HOST_BOUND;\n        return ai;\n      }\n      Socket::perror(\"bind\");\n      Socket::close(this->sockfd);\n      this->state = HOST_UNINITIALIZED;\n    }\n  }\n#endif\n\n  /* No IPv6 addresses could be bound; try IPv4 (if any)\n   */\n  for(ai = ais; ai; ai = ai->ai_next)\n  {\n    if(ai->ai_family == AF_INET)\n    {\n      if(!this->create_socket(this->type, HOST_FAMILY_IPV4))\n        return nullptr;\n\n      if(Socket::bind(this->sockfd, ai->ai_addr, ai->ai_addrlen) >= 0)\n      {\n        this->state = HOST_BOUND;\n        return ai;\n      }\n      Socket::perror(\"bind\");\n      Socket::close(this->sockfd);\n      this->state = HOST_UNINITIALIZED;\n    }\n  }\n  return nullptr;\n}\n\nboolean Host::bind(const char *hostname, int port)\n{\n  trace(\"--HOST-- Host::bind(%s, %d)\\n\", hostname, port);\n  assert(this->state == HOST_UNINITIALIZED);\n  return Host::address_op(hostname, port, nullptr, &Host::bind_op);\n}\n\nboolean Host::listen()\n{\n  assert(this->state == HOST_BOUND);\n\n  if(Socket::listen(this->sockfd, 0) < 0)\n  {\n    Socket::perror(\"listen\");\n    return false;\n  }\n  return true;\n}\n\n#ifdef NETWORK_DEADCODE\n\nstruct buf_priv_data\n{\n  char *buffer;\n  size_t len;\n  boolean ret;\n};\n\nstruct addrinfo *Host::receive_from_op(struct addrinfo *ais, void *priv)\n{\n  // timeout currently unimplemented\n  struct buf_priv_data *buf_priv = (struct buf_priv_data *)priv;\n  char *buffer = buf_priv->buffer;\n  size_t len = buf_priv->len;\n  struct addrinfo *ai;\n  ssize_t ret;\n\n  trace(\"--HOST-- Host::receive_from_op\\n\");\n\n  for(ai = ais; ai; ai = ai->ai_next)\n  {\n    socklen_t addrlen = ai->ai_addrlen;\n\n    if(ai->ai_family != this->af)\n      continue;\n\n    ret = Socket::recvfrom(this->sockfd, buffer, len, 0, ai->ai_addr, &addrlen);\n\n    if(ret < 0)\n    {\n      warn(\"Failed to recvfrom() any data.\\n\");\n      buf_priv->ret = false;\n    }\n    else\n\n    if((size_t)ret != len)\n    {\n      warn(\"Failed to recvfrom() all data (sent %zd wanted %zu).\\n\", ret, len);\n      buf_priv->ret = false;\n    }\n\n    break;\n  }\n\n  if(!ai)\n    buf_priv->ret = false;\n\n  return ai;\n}\n\nstruct addrinfo *Host::send_to_op(struct addrinfo *ais, void *priv)\n{\n  // timeout currently unimplemented\n  struct buf_priv_data *buf_priv = (struct buf_priv_data *)priv;\n  const char *buffer = buf_priv->buffer;\n  size_t len = buf_priv->len;\n  struct addrinfo *ai;\n  ssize_t ret;\n\n  trace(\"--HOST-- Host::send_to_op\\n\");\n\n  for(ai = ais; ai; ai = ai->ai_next)\n  {\n    if(ai->ai_family != this->af)\n      continue;\n\n    ret = Socket::sendto(this->sockfd, buffer, len, MSG_NOSIGNAL,\n     ai->ai_addr, ai->ai_addrlen);\n\n    if(ret < 0)\n    {\n      warn(\"Failed to sendto() any data.\\n\");\n      buf_priv->ret = false;\n    }\n    else\n\n    if((size_t)ret != len)\n    {\n      warn(\"Failed to sendto() all data (sent %zd wanted %zu).\\n\", ret, len);\n      buf_priv->ret = false;\n    }\n\n    break;\n  }\n\n  if(!ai)\n    buf_priv->ret = false;\n\n  return ai;\n}\n\nboolean Host::receive_from(char *buffer, size_t len,\n const char *hostname, int port)\n{\n  struct buf_priv_data buf_priv = { buffer, len, true };\n  trace(\"--HOST-- Host::receive_from(%s, %d)\\n\", hostname, port);\n  this->address_op(hostname, port, &buf_priv, &Host::receive_from_op);\n  return buf_priv.ret;\n}\n\nboolean Host::send_to(const char *buffer, size_t len,\n const char *hostname, int port)\n{\n  struct buf_priv_data buf_priv = { (char *)buffer, len, true };\n  trace(\"--HOST-- Host::send_to(%s, %d)\\n\", hostname, port);\n  this->address_op(hostname, port, &buf_priv, &Host::send_to_op);\n  return buf_priv.ret;\n}\n\n#endif // NETWORK_DEADCODE\n\nint Host::poll(int flags, uint32_t timeout_ms)\n{\n  struct pollfd p;\n  p.fd = this->sockfd;\n  p.events = flags;\n  p.revents = 0;\n\n  /**\n   * POSIX is a little vague about whether or not poll() should return before\n   * the timeout is complete, and while most implementations seem to return\n   * immediately, others may block for the entire timeout. Splitting up the\n   * timeout over multiple calls to avoid this shouldn't hurt anything.\n   */\n  uint32_t now = get_ticks();\n  uint32_t end = now + timeout_ms;\n  while(now < end)\n  {\n    int dur = MIN(end - now, 100);\n    int ret = Socket::poll(&p, 1, dur);\n    if(ret < 0)\n      return -1;\n\n    if(ret > 0)\n      return p.revents;\n\n    now = get_ticks();\n  }\n  return 0;\n}\n"
  },
  {
    "path": "src/network/Host.hpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __HOST_HPP\n#define __HOST_HPP\n\n#include \"../compat.h\"\n#include \"../configure.h\"\n\n#include \"Socket.hpp\"\n\n#include <stdint.h>\n#include <stdio.h> // for FILE\n\nenum host_type\n{\n  /** Use TCP protocol for sockets */\n  HOST_TYPE_TCP,\n  /** Use UDP protocol for sockets */\n  HOST_TYPE_UDP,\n  NUM_HOST_TYPES\n};\n\nenum proxy_status\n{\n  PROXY_SUCCESS,\n  PROXY_INVALID_CONFIG,\n  PROXY_CONNECTION_FAILED,\n  PROXY_AUTH_FAILED,\n  PROXY_AUTH_VERSION_UNSUPPORTED,\n  PROXY_AUTH_METHOD_UNSUPPORTED,\n  PROXY_SEND_ERROR,\n  PROXY_HANDSHAKE_FAILED,\n  PROXY_REFLECTION_FAILED,\n  PROXY_TARGET_REFUSED,\n  PROXY_ADDRESS_TYPE_UNSUPPORTED,\n  PROXY_ADDRESS_INVALID,\n  PROXY_ACCESS_DENIED,\n  PROXY_UNKNOWN_ERROR\n};\n\n/**\n * Base class for network connections.\n * Most uses will want to use a specialized host class instead (see HTTPHost).\n */\nclass UPDATER_LIBSPEC Host\n{\nprivate:\n  enum host_state\n  {\n    HOST_UNINITIALIZED, // No socket or connection.\n    HOST_INITIALIZED,   // Has a socket, but no connection.\n    HOST_CONNECTED,     // Has a connection.\n    HOST_BOUND,         // Is bound to a port.\n    NUM_HOST_STATES\n  };\n  enum host_state state;\n  enum host_type type;\n  enum host_family preferred_family;\n  int hint_af;\n  int hint_socktype;\n  int hint_proto;\n  int hint_flags;\n\n  const char *name;\n  const char *endpoint;\n  boolean proxied;\n  boolean trace_raw;\n  int af;\n  int sockfd;\n  int proto;\n  uint32_t timeout_ms;\n\nprotected:\n  // TODO send_callback\n  void (*receive_callback)(long offset);\n  boolean (*cancel_callback)(void);\n\nprivate:\n  // No copies (or moves since they aren't as portable). Use Host::swap.\n  Host(Host &h) {}\n  Host &operator=(const Host &h) { return *this; }\n#ifdef IS_CXX_11\n  Host(Host &&h) {};\n  Host &operator=(Host &&h) { return *this; };\n#endif\n\n  boolean create_socket(enum host_type type, enum host_family family);\n  boolean address_op(const char *hostname, int port, void *priv,\n   struct addrinfo *(Host::*op)(struct addrinfo *ais, void *priv));\n\n  boolean create_connection(struct addrinfo *ai, enum host_family family);\n  struct addrinfo *create_connection_op(struct addrinfo *ais, void *priv);\n  boolean connect_direct(const char *hostname, int port);\n  enum proxy_status connect_socks4a(const char *hostname, int port);\n  enum proxy_status connect_socks4(struct addrinfo *ai);\n  enum proxy_status connect_socks5(struct addrinfo *ai);\n  enum proxy_status connect_proxy(const char *hostname, int port);\n\n  struct addrinfo *bind_op(struct addrinfo *ais, void *priv);\n\n  struct addrinfo *receive_from_op(struct addrinfo *ais, void *priv);\n  struct addrinfo *send_to_op(struct addrinfo *ais, void *priv);\n\npublic:\n  /**\n   * Event bitmask flags for poll(). These are defined to be the same as the\n   * poll event flags for convenience, but these should be referenced instead.\n   */\n  enum host_poll\n  {\n    POLL_READ       = POLLIN,\n    POLL_WRITE      = POLLOUT,\n    POLL_EXCEPT     = POLLPRI,\n    POLL_ERROR      = POLLERR, // Return value only.\n    POLL_DISCONNECT = POLLHUP, // Return value only.\n    POLL_INVALID    = POLLNVAL // Return value only.\n  };\n\n  /**\n   * Default time to wait for requests before aborting.\n   */\n  static const int TIMEOUT_DEFAULT = (10 * 1000);\n\n  /**\n   * Initializes the host layer. Must be called before all other host functions.\n   *\n   * @return Whether initialization succeeded, or not\n   */\n  static boolean host_layer_init(struct config_info *conf);\n\n  /**\n   * Check the host layer initialization status. If the current platform needs\n   * to perform any late initialization (e.g. when initializing the network\n   * would take too long for it to be worth performing on startup), this\n   * function will handle that.\n   *\n   * @return `true` if the host layer is ready, otherwise `false`.\n   */\n  static boolean host_layer_init_check();\n\n  /**\n   * Shuts down the host layer.\n   * Frees any associated operating system resources.\n   */\n  static void host_layer_exit(void);\n\n  /**\n   * Swap the connection data of two Host objects.\n   *\n   * @param a        Object to swap.\n   * @param b        Object to swap.\n   */\n  static void swap(Host &a, Host &b);\n\n  /**\n   * Initialize a host object.\n   *\n   * @param type     IP protocol to use (HOST_TYPE_TCP or HOST_TYPE_UDP).\n   * @param family   Preferred address family (HOST_FAMILY_IPV4 or HOST_FAMILY_IPV6).\n   *                 Any resulting connection will not necessarily use this.\n   */\n  Host(enum host_type type, enum host_family family);\n\n  /**\n   * Destructor (close the socket and free any data/resources if needed).\n   */\n  ~Host();\n\n  /**\n   * Resets this host object to its initial state.\n   * Closes the connection (if connected) and releases the socket (if created).\n   * This function doesn't change the callbacks assigned to this Host.\n   */\n  void close();\n\n  /**\n   * Gets the hostname of this connection. For proxied connections, this will\n   * return the endpoint name.\n   *\n   * @return the hostname of the current connection, or `NULL` if there is none.\n   */\n  const char *get_host_name();\n\n  /**\n   * Connects to the specified remote host and port.\n   *\n   * @param hostname Remote hostname or IP address to connect to.\n   * @param port     Target port (service) to use.\n   *\n   * @return `true` on a successful connection, otherwise `false`.\n   */\n  boolean connect(const char *hostname, int port);\n\n  /**\n   * Sets the timeout for sending and receiving packets (default is 10s).\n   *\n   * @param timeout_ms  Timeout (in ms).\n   */\n  void set_timeout_ms(uint32_t timeout_ms);\n\n  /**\n   * Set send/recv callbacks which will be called (potentially many times) as\n   * the library fills the send/recv buffers for \"block transfers\". HTTP headers\n   * and other preambles are explicitly ignored. Raw transfers are always fully\n   * accounted. This code is primarily used by UI widgets like progress meters.\n   *\n   * Additionally, include a callback for cancellation. This will cause the\n   * send/recv functions to fail and subsequently abort the transfer process.\n   * This is again useful for cancelling long or slow transfers with UI widgets.\n   *\n   * Please note that this code does not abstract away protocol specific\n   * knowledge. For example, CHUNKED HTTP transfers will appear as many\n   * incrementally filled, fixed size buffers. Therefore, you should not use\n   * `len' below to compute the transfer amount remaining. If you do not have\n   * any expectations about transfer size, these callbacks may in fact not be\n   * very useful.\n   *\n   * TODO maybe replace this?\n   *\n   * @param send_cb   Implementation of a send callback (or NULL for none)\n   * @param recv_cb   Implementation of a recv callback (or NULL for none)\n   * @param cancel_cb Implementation of a cancel callback (or NULL for none)\n   */\n  void set_callbacks(void (*send_cb)(long offset), void (*recv_cb)(long offset),\n   boolean (*cancel_cb)(void));\n\n  /**\n   * Some socket operations \"fail\" for non-fatal reasons. If using non-blocking\n   * sockets, they may fail with EAGAIN, EINTR, or in some other\n   * platform-specific way, if there was no data available at that time.\n   *\n   * @return `true` if the last socket error was fatal, otherwise `false`.\n   */\n  boolean is_last_error_fatal();\n\n  /**\n   * Sets the socket blocking mode on the given host.\n   * Should only be used after connect() or bind().\n   *\n   * @param blocking `true` if the socket should block, `false` otherwise.\n   */\n  void set_blocking(boolean blocking);\n\n  /**\n   * Accepts a connection from a host processed by `host_bind` and\n   * `host_listen` previously. The new connection is assigned to the\n   * provided `Host` instance. The new connection will be blocking by default.\n   *\n   * @param client_host `Host` instance to accept incoming client connection.\n   *\n   * @return `true` on a successful connection to the client, otherwise `false`.\n   */\n  boolean accept(Host &client_host);\n\n  /**\n   * Binds the managed host to the specified host and port.\n   *\n   * @param hostname Hostname or IP address to bind to\n   * @param port     Target port (service) to use\n   *\n   * @return `true` if the bind was successful, otherwise `false`.\n   */\n  boolean bind(const char *hostname, int port);\n\n  /**\n   * Prepares a socket processed with HostServer::bind to listen for\n   * connections. Only applies if the managed socket is HOST_TYPE_TCP.\n   *\n   * @return `true` if listening is successful, otherwise `false`.\n   */\n  boolean listen();\n\n  /**\n   * Polls a host via raw socket access.\n   *\n   * @param flags       Bitmask of events to poll for (see `enum host_poll`).\n   * @param timeout_ms  Timeout in milliseconds for poll.\n   *\n   * @return <0 if there was a failure, 0 if there was no activity,\n   *         or a >0 bitmask of event flags indicating the type(s) of activity.\n   */\n  int poll(int flags, uint32_t timeout_ms);\n\n  /**\n   * Check if a poll() result is an error.\n   *\n   * @param poll_result Return value from poll().\n   *\n   * @return `true` if the result is <0 or Host::POLL_ERROR, Host::POLL_INVALID,\n   *         or Host::POLL_DISCONNECT is set; otherwise, `false`.\n   */\n  static boolean is_poll_error(int poll_result)\n  {\n    return poll_result < 0 ||\n     (poll_result & (POLL_ERROR | POLL_DISCONNECT | POLL_INVALID));\n  }\n\nprotected:\n  /**\n   * These functions are not exposed externally as use of them might interfere\n   * with an active stream.\n   */\n\n  /**\n   * Receive a buffer on a host via raw socket access.\n   *\n   * @param buffer    Buffer to receive data into (pre-allocated).\n   * @param len       Amount of data to receive into the buffer.\n   *\n   * @return `true` if the buffer was received, otherwise `false`.\n   */\n  boolean receive(void *buffer, size_t len);\n\n  /**\n   * Send a buffer on a host via raw socket access.\n   *\n   * @param buffer    Buffer to send from (pre-allocated).\n   * @param len       Length of data to send.\n   *\n   * @return `true` if the buffer was sent, otherwise `false`.\n   */\n  boolean send(const void *buffer, size_t len);\n\n#ifdef NETWORK_DEADCODE\n\n  /**\n   * Connect to and receive a buffer from a remote host.\n   *\n   * @param buffer    Buffer to receive data into (pre-allocated).\n   * @param len       Maximum amount of data that can be received into the buffer.\n   * @param hostname  Remote hostname or IP address to receive from.\n   * @param port      Remote host port to receive from.\n   *\n   * @return `true` if a buffer was successfully received, otherwise `false`.\n   */\n  boolean receive_from(char *buffer, size_t len, const char *hostname, int port);\n\n  /**\n   * Connect to and send a buffer to a remote host.\n   *\n   * @param buffer    Buffer to send from (pre-allocated).\n   * @param len       Length of data to send.\n   * @param hostname  Remote hostname or IP address to send to.\n   * @param port      Remote host port to send to.\n   *\n   * @return `true` if the buffer was successfully sent, otherwise `false`.\n   */\n  boolean send_to(const char *buffer, size_t len, const char *hostname, int port);\n\n#endif // NETWORK_DEADCODE\n};\n\nnamespace std\n{\n  inline void swap(Host &a, Host &b)\n  {\n    Host::swap(a, b);\n  }\n}\n\n#endif /* __HOST_HPP */\n"
  },
  {
    "path": "src/network/Manifest.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"Manifest.hpp\"\n#include \"HTTPHost.hpp\"\n#include \"Scoped.hpp\"\n#include \"../util.h\"\n#include \"../io/memfile.h\"\n#include \"../io/path.h\"\n#include \"../io/vio.h\"\n\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n#include <time.h>\n\n#define BLOCK_SIZE    4096UL\n#define LINE_BUF_LEN  256\n\nvoid ManifestEntry::init(const uint32_t (&_sha256)[8], size_t _size,\n const char *name)\n{\n  size_t name_len = strlen(name);\n  this->size = _size;\n  for(size_t i = 0; i < ARRAY_SIZE(sha256); i++)\n    this->sha256[i] = _sha256[i];\n\n  this->name = new char[name_len + 1];\n  memcpy(this->name, name, name_len + 1);\n}\n\nManifestEntry::ManifestEntry(const uint32_t (&_sha256)[8], size_t _size,\n const char *name)\n{\n  this->init(_sha256, _size, name);\n  this->next = nullptr;\n}\n\nManifestEntry::ManifestEntry(const ManifestEntry &e)\n{\n  this->init(e.sha256, e.size, e.name);\n  this->next = nullptr;\n}\n\nManifestEntry &ManifestEntry::operator=(const ManifestEntry &e)\n{\n  delete[] this->name;\n  this->init(e.sha256, e.size, e.name);\n  return *this;\n}\n\nManifestEntry::~ManifestEntry()\n{\n  delete[] this->name;\n}\n\nboolean ManifestEntry::compute_sha256(SHA256_ctx &ctx, vfile *vf, size_t len)\n{\n  unsigned long pos = 0;\n\n  SHA256_init(&ctx);\n  ScopedBuffer<char> block(BLOCK_SIZE);\n  if(!block)\n    return false;\n\n  while(pos < len)\n  {\n    unsigned long block_size = MIN(BLOCK_SIZE, len - pos);\n\n    if(vfread(block, block_size, 1, vf) != 1)\n      return false;\n\n    SHA256_update(&ctx, block, block_size);\n    pos += block_size;\n  }\n\n  SHA256_final(&ctx);\n  return true;\n}\n\nboolean ManifestEntry::validate(vfile *vf) const\n{\n  SHA256_ctx ctx;\n\n  // It must be the same length\n  if((size_t)vfilelength(vf, true) != this->size)\n    return false;\n\n  /* Compute the SHA256 digest for this file. Do it block-wise so as to\n  * conserve RAM and scale to enormously large files.\n  */\n  if(!ManifestEntry::compute_sha256(ctx, vf, this->size))\n    return false;\n\n  // Verify the digest against the manifest\n  if(memcmp(ctx.H, this->sha256, sizeof(uint32_t) * 8) != 0)\n    return false;\n\n  return true;\n}\n\nboolean ManifestEntry::validate() const\n{\n  // This should already have been checked but check it again anyway.\n  if(!ManifestEntry::validate_filename(this->name))\n    return false;\n\n  ScopedFile<vfile, vfclose> vf = vfopen_unsafe(this->name, \"rb\");\n  if(!vf)\n    return false;\n\n  return this->validate(vf);\n}\n\nboolean ManifestEntry::validate_filename(const char *filename)\n{\n  if(path_safety_check(filename, PATH_SAFE_ANY))\n    return false;\n\n  return true;\n}\n\nManifestEntry *ManifestEntry::create_from_file(const char *filename)\n{\n  SHA256_ctx ctx;\n  size_t size;\n\n  if(!ManifestEntry::validate_filename(filename))\n    return nullptr;\n\n  ScopedFile<vfile, vfclose> vf = vfopen_unsafe(filename, \"rb\");\n  if(!vf)\n    return nullptr;\n\n  size = vfilelength(vf, false);\n\n  boolean ret = ManifestEntry::compute_sha256(ctx, vf, size);\n  if(!ret)\n    return nullptr;\n\n  return new ManifestEntry(ctx.H, size, filename);\n}\n\nManifest::~Manifest()\n{\n  this->clear();\n}\n\nvoid Manifest::clear()\n{\n  ManifestEntry *current = this->head;\n  ManifestEntry *next;\n  while(current)\n  {\n    next = current->next;\n    delete current;\n    current = next;\n  }\n  this->head = nullptr;\n}\n\nstatic boolean manifest_parse_sha256(const char *p, uint32_t (&sha256)[8])\n{\n  int i, j;\n\n  // A SHA256 digest is a fixed length (prefix zeroes)\n  if(strlen(p) != 8 * 8)\n    return false;\n\n  // Walk the sha256 \"registers\" and decompose 8byte hex chunks\n  for(i = 0, j = 0; i < 8; i++, j += 8)\n  {\n    char reg_hex[9], *end_ptr;\n\n    // Pinch an 8 byte hex block from source string\n    memcpy(reg_hex, p + j, 8);\n    reg_hex[8] = 0;\n\n    // Convert the hex string into an integer and check for errors\n    sha256[i] = (uint32_t)strtoul(reg_hex, &end_ptr, 16);\n    if(end_ptr[0])\n      return false;\n  }\n\n  return true;\n}\n\nstatic boolean manifest_parse_size(char *p, size_t *size)\n{\n  char *endptr;\n  *size = strtoul(p, &endptr, 10);\n  if(endptr[0])\n    return false;\n  return true;\n}\n\nstatic ManifestEntry *manifest_parse_line(char *buffer)\n{\n  if(buffer[0])\n  {\n    uint32_t sha256[8];\n    size_t size;\n    char *m = buffer, *line;\n\n    // Grab the sha256 token\n    line = strsep(&m, \" \");\n    if(!line)\n      return nullptr;\n\n    /* Break the 64 character (8x8) hex string down into\n     * eight constituent 32bit SHA result registers (32*8=256).\n     */\n    if(!manifest_parse_sha256(line, sha256))\n      return nullptr;\n\n    // Grab the size token\n    line = strsep(&m, \" \");\n    if(!line)\n      return nullptr;\n\n    // Validate and parse it\n    if(!manifest_parse_size(line, &size))\n      return nullptr;\n\n    // Grab the filename token\n    line = strsep(&m, \"\\n\");\n    if(!line)\n      return nullptr;\n\n    // Reject any invalid or insecure filenames.\n    if(!ManifestEntry::validate_filename(line))\n      return nullptr;\n\n    return new ManifestEntry(sha256, size, line);\n  }\n  return nullptr;\n}\n\nvoid Manifest::create(vfile *vf)\n{\n  char buffer[LINE_BUF_LEN];\n  boolean has_errors = false;\n  ManifestEntry *current = nullptr;\n  this->clear();\n\n  // Walk the manifest line by line\n  while(vfsafegets(buffer, LINE_BUF_LEN, vf))\n  {\n    ManifestEntry *next = manifest_parse_line(buffer);\n    if(!next)\n    {\n      has_errors = true;\n      continue;\n    }\n\n    if(current)\n      current->next = next;\n    else\n      this->head = next;\n    current = next;\n  }\n\n  if(has_errors)\n    warn(\"Malformed manifest file.\\n\");\n}\n\nboolean Manifest::create(const char *filename)\n{\n  ScopedFile<vfile, vfclose> vf = vfopen_unsafe(filename, \"rb\");\n  if(!vf)\n  {\n    warn(\"Failed to open manifest file '%s'!\\n\", filename);\n    return false;\n  }\n\n  this->create(vf);\n  return true;\n}\n\nvoid Manifest::create(const void *data, size_t data_len)\n{\n  char buffer[LINE_BUF_LEN];\n  ManifestEntry *current = nullptr;\n  struct memfile mf;\n  this->clear();\n\n  mfopen(data, data_len, &mf);\n\n  while(mfsafegets(buffer, LINE_BUF_LEN, &mf))\n  {\n    ManifestEntry *next = manifest_parse_line(buffer);\n    if(!next)\n      continue;\n\n    if(current)\n      current->next = next;\n    else\n      this->head = next;\n    current = next;\n  }\n}\n\nvoid Manifest::create(const Manifest &src)\n{\n  const ManifestEntry *src_current = src.head;\n  ManifestEntry *current = nullptr;\n  ManifestEntry *next;\n  this->clear();\n\n  while(src_current)\n  {\n    next = new ManifestEntry(*src_current);\n    src_current = src_current->next;\n\n    if(current)\n      current->next = next;\n    else\n      this->head = next;\n    current = next;\n  }\n}\n\nvoid Manifest::append(Manifest &src)\n{\n  this->append(src.head);\n  src.head = nullptr;\n}\n\nvoid Manifest::append(ManifestEntry *entry)\n{\n  if(entry)\n  {\n    ManifestEntry *tail = this->head;\n    if(tail)\n    {\n      while(tail->next)\n        tail = tail->next;\n\n      tail->next = entry;\n    }\n    else\n      this->head = entry;\n  }\n}\n\nboolean Manifest::check_if_remote_exists(HTTPHost &http,\n HTTPRequestInfo &request, const char *basedir)\n{\n  HTTPHostStatus ret;\n\n  trace(\"--MANIFEST-- Manifest::check_if_remote_exists\\n\");\n\n  request.clear();\n  snprintf(request.url, LINE_BUF_LEN, \"%s/\" MANIFEST_TXT, basedir);\n\n  ret = http.head(request);\n  if(ret != HOST_SUCCESS)\n  {\n    warn(\"Check for remote \" MANIFEST_TXT \" failed (code %d; error: %s)\\n\",\n     request.status_code, HTTPHost::get_error_string(ret));\n    request.print_response();\n    return false;\n  }\n  trace(\"Check for remote \" MANIFEST_TXT \" successful, code %d\\n\",\n   request.status_code);\n\n  if(request.status_code == 200)\n    return true;\n\n  return false;\n}\n\nHTTPHostStatus Manifest::get_remote(HTTPHost &http, HTTPRequestInfo &request,\n const char *basedir)\n{\n  HTTPHostStatus ret;\n\n  trace(\"--MANIFEST-- Manifest::get_remote\\n\");\n\n  this->clear();\n  request.clear();\n  snprintf(request.url, LINE_BUF_LEN, \"%s/\" MANIFEST_TXT, basedir);\n  request.allowed_types = HTTPRequestInfo::plaintext_types;\n\n  ScopedFile<vfile, vfclose> vf = vfopen_unsafe(REMOTE_MANIFEST_TXT, \"w+b\");\n  if(!vf)\n  {\n    warn(\"Failed to open local \" REMOTE_MANIFEST_TXT \" for writing\\n\");\n    return HOST_FWRITE_FAILED;\n  }\n\n  ret = http.get(request, vf);\n  if(ret != HOST_SUCCESS)\n  {\n    warn(\"Processing \" REMOTE_MANIFEST_TXT \" failed (code %d; error: %s)\\n\",\n     request.status_code, HTTPHost::get_error_string(ret));\n    request.print_response();\n    return ret;\n  }\n\n  vrewind(vf);\n  this->create(vf);\n  return ret;\n}\n\n/**\n * Filter duplicates between the two provided Manifests into this Manifest.\n * Duplicates from the local list will be deleted and duplicates from the\n * remote list will be moved into this Manifest.\n *\n * @param local     Manifest to filter (duplicates are deleted).\n * @param remote    Manifest to filter (duplicates are moved).\n */\nvoid Manifest::filter_duplicates(Manifest &local, Manifest &remote)\n{\n  ManifestEntry *l, *prev_l, *next_l;\n  ManifestEntry *tail = this->head;\n\n  trace(\"--MANIFEST-- Manifest::filter_duplicates\\n\");\n\n  // Skip to the end of this manifest.\n  if(tail)\n    while(tail->next)\n      tail = tail->next;\n\n  for(l = local.head, prev_l = nullptr; l; l = next_l)\n  {\n    ManifestEntry *r, *prev_r, *next_r;\n\n    for(r = remote.head, prev_r = nullptr; r; r = next_r)\n    {\n      next_r = r->next;\n\n      if(strcmp(l->name, r->name) == 0)\n      {\n        if(prev_r)\n          prev_r->next = next_r;\n        else\n          remote.head = next_r;\n\n        // Move duplicate element from the remote manifest to this manifest.\n        r->next = nullptr;\n        if(tail)\n          tail->next = r;\n        else\n          this->head = r;\n        tail = r;\n        break;\n      }\n      else\n        prev_r = r;\n    }\n\n    next_l = l->next;\n\n    if(r)\n    {\n      if(prev_l)\n        prev_l->next = next_l;\n      else\n        local.head = next_l;\n\n      delete l;\n    }\n    else\n      prev_l = l;\n  }\n}\n\nvoid Manifest::filter_existing_files()\n{\n  ManifestEntry *prev = nullptr;\n  ManifestEntry *e, *e_next;\n\n  trace(\"--MANIFEST-- Manifest::filter_existing_files\\n\");\n\n  for(e = this->head; e; e = e_next)\n  {\n    e_next = e->next;\n\n    ScopedFile<vfile, vfclose> vf = vfopen_unsafe(e->name, \"rb\");\n    if(vf)\n    {\n      if(e->validate(vf))\n      {\n        trace(\"Local file '%s' matches the remote manifest; ignoring.\\n\", e->name);\n        if(prev)\n          prev->next = e_next;\n        else\n          this->head = e_next;\n\n        delete e;\n      }\n      else\n      {\n        trace(\"Local file '%s' doesn't match the remote manifest entry.\\n\", e->name);\n        prev = e;\n      }\n    }\n    else\n    {\n      trace(\"Local file '%s' from remote manifest doesn't exist.\\n\", e->name);\n      prev = e;\n    }\n  }\n}\n\nHTTPHostStatus Manifest::get_updates(HTTPHost &http, HTTPRequestInfo &request,\n const char *basedir, Manifest &removed, Manifest &replaced, Manifest &added)\n{\n  Manifest local, remote;\n  HTTPHostStatus status;\n\n  trace(\"--MANIFEST-- Manifest::get_updates\\n\");\n\n  status = remote.get_remote(http, request, basedir);\n  if(status != HOST_SUCCESS)\n    return status;\n\n  local.create(LOCAL_MANIFEST_TXT);\n  replaced.clear();\n  removed.clear();\n  added.clear();\n\n  if(local.has_entries())\n  {\n    /**\n     * Filter duplicates out of both local and remote. The duplicates from\n     * remote will be moved into the replaced list; the duplicates from local\n     * will be deleted.\n     */\n    replaced.filter_duplicates(local, remote);\n\n    /**\n     * The local list is now the removed list and the remote list is now the\n     * added list.\n     */\n    removed.append(local);\n    added.append(remote);\n  }\n  else\n  {\n    // If there's no local manifest added vs. replaced can't really be\n    // determined reliably. Just treat everything as replaced.\n    replaced.append(remote);\n  }\n\n  /**\n   * Check the duplicates from the remote manifest against their corresponding\n   * files. If the file exists and matches the manifest entry, remove it. The\n   * remaining list will contain files that need to be replaced.\n   */\n  replaced.filter_existing_files();\n  return HOST_SUCCESS;\n}\n\nboolean Manifest::download_and_replace_entry(HTTPHost &http,\n HTTPRequestInfo &request, const char *basedir, const ManifestEntry *e,\n Manifest &delete_list)\n{\n  char buf[LINE_BUF_LEN];\n  HTTPHostStatus ret;\n\n  assert(e);\n\n  /* Try to open our target file. If we can't open it, it might be\n   * write protected or in-use. In this case, it may be possible to\n   * rename the original file out of the way. Try this trick first.\n   */\n  ScopedFile<vfile, vfclose> vf = vfopen_unsafe(e->name, \"w+b\");\n  if(!vf)\n  {\n    snprintf(buf, LINE_BUF_LEN, \"%s-%u~\", e->name, (unsigned int)time(NULL));\n    if(vrename(e->name, buf))\n    {\n      warn(\"Failed to rename in-use file '%s' to '%s'\\n\", e->name, buf);\n      return false;\n    }\n\n    delete_list.append(ManifestEntry::create_from_file(buf));\n\n    vf.reset(vfopen_unsafe(e->name, \"w+b\"));\n    if(!vf)\n    {\n      warn(\"Unable to open file '%s' for writing\\n\", e->name);\n      return false;\n    }\n  }\n\n  request.clear();\n  request.allowed_types = HTTPRequestInfo::binary_types;\n  snprintf(request.url, LINE_BUF_LEN, \"%s/%08x%08x%08x%08x%08x%08x%08x%08x\", basedir,\n    e->sha256[0], e->sha256[1], e->sha256[2], e->sha256[3],\n    e->sha256[4], e->sha256[5], e->sha256[6], e->sha256[7]);\n\n  ret = http.get(request, vf);\n  if(ret != HOST_SUCCESS)\n  {\n    warn(\"File '%s' could not be downloaded (code %d; error: %s)\\n\", e->name,\n     request.status_code, HTTPHost::get_error_string(ret));\n    return false;\n  }\n\n  vrewind(vf);\n  if(!e->validate(vf))\n  {\n    warn(\"File '%s' failed validation\\n\", e->name);\n    return false;\n  }\n\n  return true;\n}\n\nboolean Manifest::write_to_file(const char *filename) const\n{\n  if(this->head)\n  {\n    ScopedFile<vfile, vfclose> vf = vfopen_unsafe(filename, \"ab\");\n    if(!vf)\n      return false;\n\n    for(ManifestEntry *e = this->head; e; e = e->next)\n    {\n      vf_printf(vf, \"%08x%08x%08x%08x%08x%08x%08x%08x %zu %s\\n\",\n       e->sha256[0], e->sha256[1], e->sha256[2], e->sha256[3],\n       e->sha256[4], e->sha256[5], e->sha256[6], e->sha256[7],\n       e->size, e->name);\n    }\n  }\n  return true;\n}\n"
  },
  {
    "path": "src/network/Manifest.hpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __MANIFEST_HPP\n#define __MANIFEST_HPP\n\n#include \"../compat.h\"\n#include \"../io/vfile.h\"\n\n#include \"sha256.h\"\n#include \"HTTPHost.hpp\"\n\n#include <stdint.h>\n\n#define MANIFEST_TXT \"manifest.txt\"\n#define LOCAL_MANIFEST_TXT MANIFEST_TXT\n#define REMOTE_MANIFEST_TXT \"manifest.remote.txt\"\n\nclass UPDATER_LIBSPEC ManifestEntry\n{\npublic:\n  ManifestEntry *next;\n  uint32_t sha256[8];\n  size_t size;\n  char *name;\n\n  ManifestEntry(const uint32_t (&_sha256)[8], size_t _size, const char *name);\n  ManifestEntry(const ManifestEntry &e);\n  ManifestEntry &operator=(const ManifestEntry &e);\n  ~ManifestEntry();\n\n  /**\n   * Check this manifest entry against its corresponding file.\n   *\n   * @param vf          File pointer to validate.\n   *\n   * @return `true` if the file exists and is an exact match, otherwise `false`.\n   */\n  boolean validate(vfile *vf) const;\n\n  /**\n   * Check this manifest entry against its corresponding file (indicated by\n   * its `name` field).\n   *\n   * @return `true` if the file has a valid filename, exists, and matches,\n   *         otherwise `false`.\n   */\n  boolean validate() const;\n\n  /**\n   * Filter invalid ManifestEntry filenames.\n   *\n   * @return `true` if the filename is a valid filename, otherwise `false`.\n   */\n  static boolean validate_filename(const char *filename);\n\n  /**\n   * Create a ManifestEntry from an existing file.\n   *\n   * @param filename    Filename of file to create ManifestEntry from.\n   *\n   * @return pointer to a new ManifestEntry object on success, otherwise null.\n   */\n  static ManifestEntry *create_from_file(const char *filename);\n\nprivate:\n  void init(const uint32_t (&_sha256)[8], size_t _size, const char *name);\n  static boolean compute_sha256(SHA256_ctx &ctx, vfile *vf, size_t len);\n};\n\nclass UPDATER_LIBSPEC Manifest\n{\npublic:\n  ManifestEntry *head;\n\nprivate:\n  Manifest(const Manifest &) {}\n  Manifest &operator=(const Manifest &) { return *this; }\n#ifdef IS_CXX_11\n  Manifest(Manifest &&) {}\n  Manifest &operator=(Manifest &&) { return *this; }\n#endif\n\n  void create(vfile *vf);\n  HTTPHostStatus get_remote(HTTPHost &http, HTTPRequestInfo &request,\n   const char *basedir);\n  void filter_duplicates(Manifest &local, Manifest &remote);\n  void filter_existing_files();\n\npublic:\n  Manifest(): head(nullptr) {}\n  ~Manifest();\n  void clear();\n\n  boolean create(const char *filename);\n  void create(const void *data, size_t data_len);\n  void create(const Manifest &src);\n\n  /**\n   * Move the contents of the provided manifest to the end of this manifest.\n   * The provided manifest will be empty after this operation.\n   *\n   * @param src           Manifest to move the contents of.\n   */\n  void append(Manifest &src);\n\n  /**\n   * Append a ManifestEntry to the end of this manifest.\n   * This operation will not duplicate the ManifestEntry or modify entry.next.\n   *\n   * @param entry         ManifestEntry to append to this Manifest.\n   */\n  void append(ManifestEntry *entry);\n\n  boolean has_entries() const { return this->head != nullptr; }\n  ManifestEntry *first() const { return this->head; }\n  boolean write_to_file(const char *filename) const;\n\n  static boolean check_if_remote_exists(HTTPHost &http, HTTPRequestInfo &request,\n   const char *basedir);\n  static HTTPHostStatus get_updates(HTTPHost &http, HTTPRequestInfo &request,\n   const char *basedir, Manifest &removed, Manifest &replaced, Manifest &added);\n\n  /**\n   * Download the remote file represented by a manifest entry and replace the\n   * local file if it exists. If the local file can't be deleted, it will be\n   * added to the provided delete list to be deleted at a later time.\n   *\n   * @param http          Active connection to download from.\n   * @param request       Request object to return response data in. Its\n   *                      contents will be overwritten.\n   * @param basedir       Remote basedir (/VERSION/PLATFORM).\n   * @param e             Manifest entry to fetch remote file for.\n   * @param delete_list   List to log file deletions to perform at a later time.\n   *                      Files are only added to this if they could not be\n   *                      deleted during the execution of this function.\n   *\n   * @return `true` on success, otherwise `false`.\n   */\n  static boolean download_and_replace_entry(HTTPHost &http,\n   HTTPRequestInfo &request, const char *basedir, const ManifestEntry *e,\n   Manifest &delete_list);\n};\n\n#endif /* __MANIFEST_HPP */\n"
  },
  {
    "path": "src/network/Scoped.hpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * RAII wrappers for misc. data types used in MZX.\n */\n\n#ifndef __SCOPED_HPP\n#define __SCOPED_HPP\n\n#include \"../compat.h\"\n#include \"../util.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#ifdef IS_CXX_11\n#include <type_traits>\n#endif\n\n/**\n * RAII wrapper for <FILE, fclose> and <vfile, vfclose>.\n */\ntemplate<class T, int (*RELEASE_FN)(T *)>\nclass ScopedFile\n{\nprivate:\n  T *ptr;\n\n  ScopedFile &operator=(const ScopedFile &) { return *this; }\n\npublic:\n  ScopedFile(T *p = nullptr): ptr(p) {}\n  ~ScopedFile()\n  {\n    if(ptr)\n      RELEASE_FN(ptr);\n  }\n\n  void reset(T *p = nullptr)\n  {\n    if(p != ptr)\n    {\n      if(ptr)\n        RELEASE_FN(ptr);\n      ptr = p;\n    }\n  }\n\n  operator T *() const\n  {\n    return ptr;\n  }\n\n  maybe_explicit operator bool() const\n  {\n    return ptr != nullptr;\n  }\n};\n\n/**\n * RAII wrapper for a scoped resizable char buffer (as vector requires\n * libstdc++ and linking that causes apk bloat and MinGW dependency creep).\n * Also, vector<bool> and no .data() in C++98 :(\n */\ntemplate<class T>\nclass ScopedBuffer\n{\nprivate:\n  T *ptr;\n  size_t alloc;\n  ScopedBuffer(const ScopedBuffer &) {}\n  ScopedBuffer &operator=(const ScopedBuffer &) { return *this; }\n\npublic:\n  explicit ScopedBuffer(size_t length = 0): ptr(nullptr), alloc(length)\n  {\n#ifdef IS_CXX_11\n    static_assert(std::is_trivial<T>::value,\n     \"Please use ScopedBuffer for trivial data types only ;-(\");\n#endif\n    if(length)\n      ptr = (T *)cmalloc(length);\n  }\n  ~ScopedBuffer()\n  {\n    if(ptr)\n      free(ptr);\n  }\n\n  // NOTE: this function invalidates external copies of this pointer!\n  boolean resize(size_t new_length)\n  {\n    if(new_length == alloc)\n      return true;\n\n    if(new_length == 0)\n    {\n      free(ptr);\n      ptr = nullptr;\n      alloc = 0;\n      return true;\n    }\n\n    T *newptr = (T *)crealloc(ptr, new_length);\n    if(newptr)\n    {\n      ptr = newptr;\n      alloc = new_length;\n      return true;\n    }\n    return false;\n  }\n\n  size_t size() const\n  {\n    return alloc;\n  }\n\n  T *data() const\n  {\n    return ptr;\n  }\n\n  operator T *() const\n  {\n    return ptr;\n  }\n\n  maybe_explicit operator bool() const\n  {\n    return ptr != nullptr;\n  }\n};\n\n/**\n * RAII wrapper to delete a pointer allocated by new.\n * The pointer will be deleted when this instance exits scope or when reset()\n * is explicitly called (optionally, with a new pointer allocated by new).\n * Once this wrapper has been given control of a pointer, the pointer cannot\n * be released from this wrapper without delete being used on it.\n */\ntemplate<class T>\nclass ScopedPtr\n{\nprivate:\n  T *ptr;\n  ScopedPtr &operator=(const ScopedPtr &) { return *this; }\n\npublic:\n  ScopedPtr(T *p = nullptr): ptr(p) {}\n  ~ScopedPtr()\n  {\n    if(ptr)\n      delete ptr;\n  }\n\n  void reset(T *p = nullptr)\n  {\n    if(p != ptr)\n    {\n      if(ptr)\n        delete ptr;\n      ptr = p;\n    }\n  }\n\n#if IS_CXX_11\n  ScopedPtr &operator=(T *&&p)\n  {\n    reset(p);\n    return *this;\n  }\n#endif\n\n  operator T *() const\n  {\n    return ptr;\n  }\n\n  T *operator->() const\n  {\n    return ptr;\n  }\n\n  T *get() const\n  {\n    return ptr;\n  }\n\n  maybe_explicit operator bool() const\n  {\n    return ptr != nullptr;\n  }\n};\n\n/**\n * RAII wrapper to delete[] a pointer allocated by new[].\n * The pointer will be delete[]ed when this instance exits scope or when reset()\n * is explicitly called (optionally, with a new pointer allocated by new[]).\n * Once this wrapper has been given control of a pointer, the pointer cannot\n * be released from this wrapper without delete[] being used on it.\n */\ntemplate<class T>\nclass ScopedPtr<T[]>\n{\nprivate:\n  T *ptr;\n  ScopedPtr &operator=(const ScopedPtr &) { return *this; }\n\npublic:\n  ScopedPtr(T *p = nullptr): ptr(p) {}\n  ~ScopedPtr()\n  {\n    if(ptr)\n      delete[] ptr;\n  }\n\n  void reset(T *p = nullptr)\n  {\n    if(p != ptr)\n    {\n      if(ptr)\n        delete[] ptr;\n      ptr = p;\n    }\n  }\n\n#if IS_CXX_11\n  ScopedPtr &operator=(T *&&p)\n  {\n    reset(p);\n    return *this;\n  }\n#endif\n\n  operator T *() const\n  {\n    return ptr;\n  }\n\n  T *operator->() const\n  {\n    return ptr;\n  }\n\n  T *get() const\n  {\n    return ptr;\n  }\n\n  maybe_explicit operator bool() const\n  {\n    return ptr != nullptr;\n  }\n};\n\n#endif /* __SCOPED_HPP */\n"
  },
  {
    "path": "src/network/Socket.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2018-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"Socket.hpp\"\n\n#include \"../platform.h\"\n#include \"../util.h\"\n\n#include <assert.h>\n#include <inttypes.h>\n#include <stdlib.h>\n\n#ifndef _MSC_VER\n#include <sys/time.h>\n#endif\n\n#if defined(__WIN32__) || !defined(CONFIG_GETADDRINFO)\n\n/**\n * gethostbyname may use a static struct that is shared across threads\n * in some implementations. For safety, acquire a lock until the call to it\n * and all use of its return value is complete.\n */\nstatic platform_mutex gai_lock;\n\nint Socket::getaddrinfo_alt(const char *node, const char *service,\n const struct addrinfo *hints, struct addrinfo **res)\n{\n  struct addrinfo *r = NULL, *r_head = NULL;\n  struct hostent *hostent;\n  int i;\n\n  trace(\"--SOCKET-- Socket::getaddrinfo_alt\\n\");\n\n  // This relies on hints being provided to initialize the addrinfo structs...\n  if(!hints)\n    return EAI_FAIL;\n\n  /**\n   * If hints.ai_family is provided as AF_UNSPEC, getaddrinfo is expected to\n   * return anything found (including AF_INET), so it's acceptable here.\n   * https://pubs.opengroup.org/onlinepubs/9699919799/functions/freeaddrinfo.html\n   */\n  if(hints->ai_family != AF_INET && hints->ai_family != AF_UNSPEC)\n    return EAI_FAMILY;\n\n  platform_mutex_lock(&gai_lock);\n\n  hostent = Socket::gethostbyname(node);\n  if(!hostent || hostent->h_addrtype != AF_INET)\n  {\n    platform_mutex_unlock(&gai_lock);\n    return EAI_NONAME;\n  }\n\n  /* Walk the h_addr_list and create faked addrinfo structures\n   * corresponding to the addresses. We don't support non-IPV4\n   * addresses or any other magic, but that's an acceptable\n   * fallback, and it won't affect users on XP or newer.\n   */\n  for(i = 0; hostent->h_addr_list[i]; i++)\n  {\n    struct sockaddr_in *addr;\n\n    if(r_head)\n    {\n      r->ai_next = (struct addrinfo *)cmalloc(sizeof(struct addrinfo));\n      r = r->ai_next;\n    }\n    else\n      r_head = r = (struct addrinfo *)cmalloc(sizeof(struct addrinfo));\n\n    // Zero the fake addrinfo and fill out the essential fields\n    memset(r, 0, sizeof(struct addrinfo));\n    r->ai_family = hostent->h_addrtype;\n    r->ai_socktype = hints->ai_socktype;\n    r->ai_protocol = hints->ai_protocol;\n    r->ai_addrlen = sizeof(struct sockaddr_in);\n    r->ai_addr = (struct sockaddr *)cmalloc(r->ai_addrlen);\n\n    // Zero the fake ipv4 addr and fill our all of the fields\n    addr = (struct sockaddr_in *)r->ai_addr;\n    memcpy(&addr->sin_addr.s_addr, hostent->h_addr_list[i], sizeof(uint32_t));\n    addr->sin_family = r->ai_family;\n    addr->sin_port = Socket::hton_short(atoi(service));\n  }\n\n  platform_mutex_unlock(&gai_lock);\n  *res = r_head;\n  return 0;\n}\n\nvoid Socket::freeaddrinfo_alt(struct addrinfo *res)\n{\n  struct addrinfo *r, *r_next;\n  for(r = res; r; r = r_next)\n  {\n    r_next = r->ai_next;\n    free(r->ai_addr);\n    free(r);\n  }\n}\n\nconst char *Socket::gai_strerror_alt(int errcode)\n{\n  switch(errcode)\n  {\n    case EAI_NONAME:\n      return \"Node or service is not known.\";\n    case EAI_AGAIN:\n      return \"Temporary failure in name resolution\";\n    case EAI_FAIL:\n      return \"Non-recoverable failure in name resolution\";\n    case EAI_FAMILY:\n      return \"Address family is not supported.\";\n    default:\n      return \"Unknown error.\";\n  }\n}\n\n#ifndef __WIN32__\nint Socket::getaddrinfo(const char *node, const char *service,\n const struct addrinfo *hints, struct addrinfo **res)\n{\n  return Socket::getaddrinfo_alt(node, service, hints, res);\n}\n\nvoid Socket::freeaddrinfo(struct addrinfo *res)\n{\n  return Socket::freeaddrinfo_alt(res);\n}\n\nvoid Socket::getaddrinfo_perror(const char *message, int errcode)\n{\n  warn(\"%s (code %d): %s\\n\", message, errcode,\n   Socket::gai_strerror_alt(errcode));\n}\n#endif\n#endif // __WIN32__ || !CONFIG_GETADDRINFO\n\n#if defined(__WIN32__) || !defined(CONFIG_POLL)\n\nstatic void reset_timeout(struct timeval *tv, int timeout)\n{\n  /* Because of the way select() works on Unix platforms, this needs to\n   * be reset every time select() is used on it. */\n  tv->tv_sec = (timeout / 1000);\n  tv->tv_usec = (timeout % 1000) * 1000;\n}\n\n/**\n * Alternative implementation of poll() that uses select() internally.\n * Only use this if poll() isn't available (examples: couldn't load WSAPoll;\n * POSIX <2001; broken poll() in some Mac OS X versions; some console SDKs).\n */\nint Socket::poll_alt(struct pollfd *fds, size_t nfds, int timeout_ms)\n{\n  struct timeval tv;\n  fd_set readfds;\n  fd_set writefds;\n  fd_set exceptfds;\n  int maxfd = -1;\n  int ret;\n\n  FD_ZERO(&readfds);\n  FD_ZERO(&writefds);\n  FD_ZERO(&exceptfds);\n\n  if(nfds >= FD_SETSIZE)\n  {\n    warn(\n      \"--SOCKET-- Socket::poll_alt: provided number of fds for select() %d >= \"\n      \"FD_SETSIZE %d\\n\",\n      (int)nfds, (int)FD_SETSIZE\n    );\n  }\n\n  for(size_t i = 0; i < nfds; i++)\n  {\n    struct pollfd &p = fds[i];\n\n#ifndef __WIN32__\n    /**\n     * Reject any fds over FD_SETSIZE. These can't be supported and attempting\n     * to work around this is prone to security issues.\n     *\n     * This doesn't matter for Windows, which uses a list instead of a bitmask.\n     */\n    if(p.fd >= FD_SETSIZE)\n    {\n      warn(\n        \"--SOCKET-- Socket::poll_alt: ignoring fd %d >= FD_SETSIZE %d for \"\n        \"select()\\n\",\n        (int)p.fd, (int)FD_SETSIZE\n      );\n      continue;\n    }\n#endif\n\n    maxfd = MAX(maxfd, (int)p.fd);\n    p.revents = 0;\n    if(p.events & POLLIN)\n      FD_SET(p.fd, &readfds);\n    if(p.events & POLLOUT)\n      FD_SET(p.fd, &writefds);\n    if(p.events & POLLPRI)\n      FD_SET(p.fd, &exceptfds);\n  }\n\n  reset_timeout(&tv, timeout_ms);\n  ret = Socket::select(maxfd + 1, &readfds, &writefds, &exceptfds, &tv);\n  if(ret < 0)\n    return -1;\n\n  if(ret > 0)\n  {\n    for(size_t i = 0; i < nfds; i++)\n    {\n      struct pollfd &p = fds[i];\n\n      if((p.events & POLLIN) && FD_ISSET(p.fd, &readfds))\n        p.revents |= POLLIN;\n      if((p.events & POLLOUT) && FD_ISSET(p.fd, &writefds))\n        p.revents |= POLLOUT;\n      if((p.events & POLLPRI) && FD_ISSET(p.fd, &exceptfds))\n        p.revents |= POLLPRI;\n    }\n    return ret;\n  }\n  return 0;\n}\n\n#ifndef __WIN32__\nint Socket::poll(struct pollfd *fds, unsigned int nfds, int timeout_ms)\n{\n  return Socket::poll_alt(fds, nfds, timeout_ms);\n}\n#endif\n#endif /* __WIN32__ || !CONFIG_POLL */\n\n#ifdef __WIN32__\n\n/* MegaZeux needs ws2_32.dll for getaddrinfo() and friends, which are\n * relatively new POSIX APIs added to simplify the lookup of (and\n * association with) a host. They passively support IPv6 lookups,\n * for example, and can deal with DNS extensions like SRV for\n * protocol specific resolves. This is useful stuff for the future.\n *\n * Therefore, it was decided to rely on Winsock2, specifically the\n * version introduced in Windows XP. If the new XP-added functions cannot\n * be loaded, the old gethostbyname() method will be wrapped to produce\n * an interface identical to that of getaddrinfo(). This allows us to\n * support all Winsock 2.0 platforms, which was officially 98 onwards,\n * but 95 was also supported with an additional download.\n */\n\nstatic struct\n{\n  /* These are Winsock 2.0 functions that should be present all the\n   * way back to 98 (and 95 with the additional Winsock 2.0 update).\n   */\n  int (PASCAL *accept)(SOCKET, const struct sockaddr *, int *);\n  int (PASCAL *bind)(SOCKET, const struct sockaddr *, int);\n  int (PASCAL *closesocket)(SOCKET);\n  int (PASCAL *connect)(SOCKET, const struct sockaddr *, int);\n  struct hostent *(PASCAL *gethostbyname)(const char*);\n  u_short (PASCAL *htons)(u_short);\n  int (PASCAL *ioctlsocket)(SOCKET, long, u_long *);\n  int (PASCAL *listen)(SOCKET, int);\n  int (PASCAL *select)(int, fd_set *, fd_set *, fd_set *,\n   const struct timeval *);\n  int (PASCAL *send)(SOCKET, const char *, int, int);\n  int (PASCAL *sendto)(SOCKET, const char*, int, int,\n   const struct sockaddr *, int);\n  int (PASCAL *getsockopt)(SOCKET, int, int, char *, int *);\n  int (PASCAL *setsockopt)(SOCKET, int, int, const char *, int);\n  SOCKET (PASCAL *socket)(int, int, int);\n  int (PASCAL *recv)(SOCKET, char *, int, int);\n  int (PASCAL *recvfrom)(SOCKET, char*, int, int,\n   struct sockaddr *, int *);\n\n  // Similar to above but these are extensions of Berkeley sockets\n  int (PASCAL *WSACancelBlockingCall)(void);\n  int (PASCAL *WSACleanup)(void);\n  int (PASCAL *WSAGetLastError)(void);\n  int (PASCAL *WSAStartup)(WORD, LPWSADATA);\n  int (PASCAL *__WSAFDIsSet)(SOCKET, fd_set *);\n\n  // These functions were only implemented as of Windows XP (5.1)\n  void (WSAAPI *freeaddrinfo)(struct addrinfo *);\n  int (WSAAPI *getaddrinfo)(const char *, const char *,\n   const struct addrinfo *, struct addrinfo **);\n\n  // These functions were only implemented as of Windows Vista.\n  int (WSAAPI *WSAPoll)(struct pollfd *fds, unsigned long nfds, int timeout);\n\n  struct dso_library *handle;\n}\nsocksyms;\n\nstatic const struct dso_syms_map socksyms_map[] =\n{\n  { \"accept\",                { &socksyms.accept }},\n  { \"bind\",                  { &socksyms.bind }},\n  { \"closesocket\",           { &socksyms.closesocket }},\n  { \"connect\",               { &socksyms.connect }},\n  { \"gethostbyname\",         { &socksyms.gethostbyname }},\n  { \"getsockopt\",            { &socksyms.getsockopt }},\n  { \"htons\",                 { &socksyms.htons }},\n  { \"ioctlsocket\",           { &socksyms.ioctlsocket }},\n  { \"listen\",                { &socksyms.listen }},\n  { \"select\",                { &socksyms.select }},\n  { \"send\",                  { &socksyms.send }},\n  { \"sendto\",                { &socksyms.sendto }},\n  { \"setsockopt\",            { &socksyms.setsockopt }},\n  { \"socket\",                { &socksyms.socket }},\n  { \"recv\",                  { &socksyms.recv }},\n  { \"recvfrom\",              { &socksyms.recvfrom }},\n\n  { \"WSACancelBlockingCall\", { &socksyms.WSACancelBlockingCall }},\n  { \"WSACleanup\",            { &socksyms.WSACleanup }},\n  { \"WSAGetLastError\",       { &socksyms.WSAGetLastError }},\n  { \"WSAStartup\",            { &socksyms.WSAStartup }},\n  { \"__WSAFDIsSet\",          { &socksyms.__WSAFDIsSet }},\n\n  { \"freeaddrinfo\",          { &socksyms.freeaddrinfo }},\n  { \"getaddrinfo\",           { &socksyms.getaddrinfo }},\n\n  { \"WSAPoll\",               { &socksyms.WSAPoll }},\n\n  DSO_MAP_END\n};\n\n#define WINSOCK2 \"ws2_32.dll\"\n#define WINSOCK  \"wsock32.dll\"\n\nstatic void socket_free_syms(void)\n{\n  if(socksyms.handle)\n  {\n    platform_unload_library(socksyms.handle);\n    socksyms.handle = NULL;\n  }\n}\n\nstatic boolean socket_load_syms(void)\n{\n  int i;\n\n  socksyms.handle = platform_load_library(WINSOCK2);\n  if(!socksyms.handle)\n  {\n    warn(\"Failed to load Winsock 2.0, falling back to Winsock\\n\");\n    socksyms.handle = platform_load_library(WINSOCK);\n    if(!socksyms.handle)\n    {\n      warn(\"Failed to load Winsock fallback\\n\");\n      return false;\n    }\n  }\n\n  for(i = 0; socksyms_map[i].name; i++)\n  {\n    if(!platform_load_function(socksyms.handle, &(socksyms_map[i])))\n    {\n      // Skip these NT 5.1 WS2 extensions; we can fall back\n      if((strcmp(socksyms_map[i].name, \"freeaddrinfo\") == 0) ||\n         (strcmp(socksyms_map[i].name, \"getaddrinfo\") == 0) ||\n         (strcmp(socksyms_map[i].name, \"WSAPoll\") == 0))\n        continue;\n\n      // However all other Winsock symbols must be loaded, or we fail hard\n      warn(\"Failed to load Winsock symbol '%s'\\n\", socksyms_map[i].name);\n      socket_free_syms();\n      return false;\n    }\n  }\n\n  return true;\n}\n\nboolean Socket::platform_init(struct config_info *conf)\n{\n  WORD version = MAKEWORD(1, 0);\n  WSADATA ws_data;\n\n  if(!socket_load_syms())\n    return false;\n\n  if(socksyms.WSAStartup(version, &ws_data) < 0)\n    return false;\n\n  return true;\n}\n\nboolean Socket::platform_init_late()\n{\n  return true;\n}\n\nvoid Socket::platform_exit(void)\n{\n  if(socksyms.WSACleanup() == SOCKET_ERROR)\n  {\n    if(socksyms.WSAGetLastError() == WSAEINPROGRESS)\n    {\n      socksyms.WSACancelBlockingCall();\n      socksyms.WSACleanup();\n    }\n  }\n  socket_free_syms();\n}\n\nboolean Socket::is_last_error_fatal(void)\n{\n  /**\n   * NOTE: WSA error codes are not intercompatible with errno, so there's no\n   * point in calling is_last_errno_fatal from this function. Just handle the\n   * WSA error codes directly.\n   */\n  switch(Socket::get_errno())\n  {\n    case 0:\n    case WSAEINPROGRESS:\n    case WSAEWOULDBLOCK:\n    case WSAEINTR:\n      return false;\n    default:\n      return true;\n  }\n}\n\n/**\n * It turns out perror is completely useless for Winsock error messages, so\n * as a workaround, use Win32 functions instead (these should be safe back to\n * Windows 95).\n */\nstatic void winsock_perror(const char *message, int code)\n{\n  LPSTR err_message = NULL;\n\n  FormatMessage(\n    FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,\n    0,\n    code,\n    0,\n    (LPSTR)&err_message,\n    0,\n    NULL\n  );\n\n  warn(\"%s (code %d): %s\", message, code, err_message);\n  LocalFree(err_message);\n}\n\nint Socket::get_errno()\n{\n  return socksyms.WSAGetLastError();\n}\n\nvoid Socket::perror(const char *message)\n{\n  winsock_perror(message, Socket::get_errno());\n}\n\nint Socket::accept(int sockfd,\n struct sockaddr *addr, socklen_t *addrlen)\n{\n  return socksyms.accept(sockfd, addr, addrlen);\n}\n\nint Socket::bind(int sockfd,\n const struct sockaddr *addr, socklen_t addrlen)\n{\n  return socksyms.bind(sockfd, addr, addrlen);\n}\n\nvoid Socket::close(int fd)\n{\n  socksyms.closesocket(fd);\n}\n\nint Socket::connect(int sockfd,\n const struct sockaddr *serv_addr, socklen_t addrlen)\n{\n  return socksyms.connect(sockfd, serv_addr, addrlen);\n}\n\nstruct hostent *Socket::gethostbyname(const char *name)\n{\n  return socksyms.gethostbyname(name);\n}\n\nint Socket::getsockopt(int sockfd, int level, int optname, void *optval,\n socklen_t *optlen)\n{\n  return socksyms.getsockopt(sockfd, level, optname, (char *)optval, optlen);\n}\n\nuint16_t Socket::hton_short(uint16_t hostshort)\n{\n  return socksyms.htons(hostshort);\n}\n\nvoid Socket::freeaddrinfo(struct addrinfo *res)\n{\n  if(socksyms.freeaddrinfo)\n    socksyms.freeaddrinfo(res);\n  else\n    Socket::freeaddrinfo_alt(res);\n}\n\nint Socket::getaddrinfo(const char *node, const char *service,\n const struct addrinfo *hints, struct addrinfo **res)\n{\n  if(socksyms.getaddrinfo)\n    return socksyms.getaddrinfo(node, service, hints, res);\n  return Socket::getaddrinfo_alt(node, service, hints, res);\n}\n\n/**\n * A Win32 version of gai_strerror is available but apparently uses a 1K\n * static buffer for the error message, making it non-thread safe.\n * Fortunately, the workaround for this is the same as for perror.\n */\nvoid Socket::getaddrinfo_perror(const char *message, int errcode)\n{\n  winsock_perror(message, errcode);\n}\n\nint Socket::listen(int sockfd, int backlog)\n{\n  return socksyms.listen(sockfd, backlog);\n}\n\nint Socket::poll(struct pollfd *fds, unsigned int nfds, int timeout_ms)\n{\n  if(socksyms.WSAPoll)\n    return socksyms.WSAPoll(fds, nfds, timeout_ms);\n  return Socket::poll_alt(fds, nfds, timeout_ms);\n}\n\nint Socket::select(int nfds, fd_set *readfds,\n fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)\n{\n  return socksyms.select(nfds, readfds, writefds, exceptfds, timeout);\n}\n\nssize_t Socket::send(int sockfd, const void *buf, size_t len,\n int flags)\n{\n  return socksyms.send(sockfd, (const char *)buf, (int)len, flags);\n}\n\nssize_t Socket::sendto(int sockfd, const void *buf, size_t len,\n int flags, const struct sockaddr *to, socklen_t tolen)\n{\n  return socksyms.sendto(sockfd, (const char *)buf, (int)len, flags, to, tolen);\n}\n\nint Socket::setsockopt(int sockfd, int level, int optname,\n const void *optval, socklen_t optlen)\n{\n  return socksyms.setsockopt(sockfd, level, optname, (const char *)optval, optlen);\n}\n\nint Socket::socket(int af, int type, int protocol)\n{\n  return (int)socksyms.socket(af, type, protocol);\n}\n\nvoid Socket::set_blocking(int sockfd, boolean blocking)\n{\n  unsigned long mode = !blocking;\n  socksyms.ioctlsocket(sockfd, FIONBIO, &mode);\n}\n\nssize_t Socket::recv(int sockfd, void *buf, size_t len, int flags)\n{\n  return socksyms.recv(sockfd, (char *)buf, (int)len, flags);\n}\n\nssize_t Socket::recvfrom(int sockfd, void *buf, size_t len,\n int flags, struct sockaddr *from, socklen_t *fromlen)\n{\n  return socksyms.recvfrom(sockfd, (char *)buf, (int)len, flags, from, fromlen);\n}\n\nint Socket::__WSAFDIsSet(int sockfd, fd_set *set)\n{\n  return socksyms.__WSAFDIsSet(sockfd, set);\n}\n\n#endif /*__WIN32__*/\n\n\n#ifdef CONFIG_3DS\n\n#include <3ds.h>\n#include <malloc.h>\n\n// Just going off what the examples use for the buffer size here...\n#define SOC_ALIGN       0x1000\n#define SOC_BUFFERSIZE  0x100000\n\nstatic boolean socket_3ds_init = false;\nstatic u32 *socket_ctx_buffer = nullptr;\n\nboolean Socket::platform_init(struct config_info *conf)\n{\n  return true;\n}\n\nboolean Socket::platform_init_late()\n{\n  if(!socket_3ds_init)\n  {\n    void *buf = memalign(SOC_ALIGN, SOC_BUFFERSIZE);\n    if(!buf)\n      return false;\n\n    socket_ctx_buffer = static_cast<u32 *>(buf);\n\n    Result ret = socInit(socket_ctx_buffer, SOC_BUFFERSIZE);\n    if(ret != 0)\n    {\n      free(socket_ctx_buffer);\n      socket_ctx_buffer = nullptr;\n      return false;\n    }\n    socket_3ds_init = true;\n  }\n  return true;\n}\n\nvoid Socket::platform_exit()\n{\n  if(socket_3ds_init)\n  {\n    socExit();\n    free(socket_ctx_buffer);\n    socket_3ds_init = false;\n    socket_ctx_buffer = nullptr;\n  }\n}\n\n#endif /* CONFIG_3DS */\n\n\n#ifdef CONFIG_SWITCH\n\n#include <switch.h>\n\nstatic boolean socket_switch_init = false;\n\nboolean Socket::platform_init(struct config_info *conf)\n{\n  return true;\n}\n\nboolean Socket::platform_init_late()\n{\n  if(!socket_switch_init)\n  {\n    Result ret = socketInitializeDefault();\n    if(ret != 0)\n      return false;\n\n    socket_switch_init = true;\n  }\n  return true;\n}\n\nvoid Socket::platform_exit()\n{\n  if(socket_switch_init)\n  {\n    socketExit();\n    socket_switch_init = false;\n  }\n}\n\n#endif /* CONFIG_SWITCH */\n\n\nint Socket::init_ref_count = 0;\n\nboolean Socket::init(struct config_info *conf)\n{\n  if(!init_ref_count)\n  {\n    if(!Socket::platform_init(conf))\n      return false;\n\n#if defined(__WIN32__) || !defined(CONFIG_GETADDRINFO)\n    platform_mutex_init(&gai_lock);\n#endif\n  }\n  init_ref_count++;\n  return true;\n}\n\nboolean Socket::init_late()\n{\n  return Socket::platform_init_late();\n}\n\nvoid Socket::exit()\n{\n  assert(init_ref_count > 0);\n  init_ref_count--;\n  if(!init_ref_count)\n  {\n    Socket::platform_exit();\n\n#if defined(__WIN32__) || !defined(CONFIG_GETADDRINFO)\n    platform_mutex_destroy(&gai_lock);\n    gai_lock = 0;\n#endif\n  }\n}\n"
  },
  {
    "path": "src/network/Socket.hpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2018-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __SOCKET_HPP\n#define __SOCKET_HPP\n\n#include \"../compat.h\"\n#include \"../util.h\"\n\n#include <errno.h>\n#include <stdio.h>\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#ifdef __WIN32__\n\n// Winsock symbols are dynamically loaded, so disable the inline versions.\n#define UNIX_INLINE(x)\n#ifndef __WIN64__\n// Windows XP WinSock2 needed for getaddrinfo() API\n#ifdef CONFIG_GETADDRINFO\n#undef _WIN32_WINNT\n#define _WIN32_WINNT 0x0501\n#endif\n#endif // !__WIN64__\n// Windows Vista WinSock2 is needed for poll().\n#ifdef CONFIG_POLL\n#undef _WIN32_WINNT\n#define _WIN32_WINNT 0x0600\n#endif\n#define WIN32_LEAN_AND_MEAN\n#include <winsock2.h>\n#include <ws2tcpip.h>\n\n#elif defined(CONFIG_WII)\n\n// See arch/wii/network.cpp.\n#include <network.h>\n#include <fcntl.h>\n#define UNIX_INLINE(x)\n\n// libogc uses a structure different from the regular structure and does not\n// declare pollfd. Declare this to be identical to the libogc version...\nstruct pollfd\n{\n  s32 fd;\n  u32 events;\n  u32 revents;\n};\n\n#else // !__WIN32__ && !CONFIG_WII\n\n#include <netinet/in.h>\n#include <sys/socket.h>\n#include <sys/select.h>\n#include <sys/types.h>\n#include <sys/time.h>\n#include <arpa/inet.h>\n#include <netdb.h>\n#include <fcntl.h>\n#ifdef CONFIG_POLL\n#include <poll.h>\n#endif\n// Not clear what the intent of including this was but it's not present for PSP.\n// Commenting out for now until it's needed (if at all).\n//#include <net/if.h>\n\n#endif // __WIN32__\n\n#if defined(CONFIG_GETADDRINFO) && (!defined(EAI_AGAIN) && !defined(EAI_FAMILY))\n#error \"Missing getaddrinfo() support; configure with --disable-getaddrinfo.\"\n#endif\n\n#if defined(CONFIG_POLL) && !defined(POLLPRI)\n#error \"Missing poll support; configure with --disable-poll.\"\n#endif\n\n#if defined(CONFIG_IPV6) && !defined(AF_INET6)\n#error \"Missing IPv6 support; configure with --disable-ipv6.\"\n#endif\n\n#if !defined(CONFIG_GETADDRINFO)\n\n// Amiga doesn't have getaddrinfo and needs to use a fallback implementation.\n// This affects various other legacy platforms and some console SDKs as well.\n#define GETADDRINFO_MAYBE_INLINE(x)\n\n#if !defined(EAI_AGAIN)\n#define EAI_NONAME -2\n#define EAI_AGAIN  -3\n#define EAI_FAIL   -4\n#define EAI_FAMILY -6\n\nstruct addrinfo\n{\n  int ai_flags;\n  int ai_family;\n  int ai_socktype;\n  int ai_protocol;\n  size_t ai_addrlen;\n  struct sockaddr *ai_addr;\n  char *ai_canonname;\n  struct addrinfo *ai_next;\n};\n#endif\n#endif\n\n#if !defined(CONFIG_POLL)\n\n// PSP and probably a lot of older systems don't have poll and need to fall\n// back to using select().\n#define POLL_MAYBE_INLINE(x)\n\n#if !defined(POLLPRI)\n#define POLLIN   0x001\n#define POLLPRI  0x002\n#define POLLOUT  0x004\n#define POLLERR  0x008\n#define POLLHUP  0x010\n#define POLLNVAL 0x020\n\nstruct pollfd\n{\n  int fd;\n  short events;\n  short revents;\n};\n#endif\n#endif\n\n#if defined(CONFIG_WII) || defined(CONFIG_3DS) || defined(CONFIG_SWITCH)\n#define SOCKET_INIT_MAYBE_INLINE(x)\n#endif\n\n#if !defined(EAI_AGAIN) && defined(EAI_NONAME)\n#define EAI_AGAIN EAI_NONAME\n#endif\n\n#ifndef AI_V4MAPPED\n#define AI_V4MAPPED (0)\n#endif\n\n#ifndef AI_ADDRCONFIG\n#define AI_ADDRCONFIG (0)\n#endif\n\n#ifndef MSG_NOSIGNAL\n#define MSG_NOSIGNAL (0)\n#endif\n\n#ifndef UNIX_INLINE\n#define UNIX_INLINE(x) x\n#endif\n\n#ifndef SOCKET_INIT_MAYBE_INLINE\n#define SOCKET_INIT_MAYBE_INLINE(x) UNIX_INLINE(x)\n#endif\n\n#ifndef GETADDRINFO_MAYBE_INLINE\n#define GETADDRINFO_MAYBE_INLINE(x) UNIX_INLINE(x)\n#endif\n\n#ifndef POLL_MAYBE_INLINE\n#define POLL_MAYBE_INLINE(x) UNIX_INLINE(x)\n#endif\n\nstruct config_info;\n\n/**\n * Low-level abstraction for misc. socket symbols.\n * Don't use directly outside of src/network/.\n */\nclass Socket final\n{\nprivate:\n  static int init_ref_count;\n\n  Socket() {}\n\n  static boolean platform_init(struct config_info *conf) SOCKET_INIT_MAYBE_INLINE\n  ({\n    return true;\n  });\n\n  static boolean platform_init_late() SOCKET_INIT_MAYBE_INLINE\n  ({\n    return true;\n  });\n\n  static void platform_exit() SOCKET_INIT_MAYBE_INLINE\n  ({\n    return;\n  });\n\n  // Potentially non thread-safe. Only allow internally in getaddrinfo_alt.\n  static struct hostent *gethostbyname(const char *name) UNIX_INLINE\n  ({\n    return ::gethostbyname(name);\n  });\n\n  static int getaddrinfo_alt(const char *node, const char *service,\n   const struct addrinfo *hints, struct addrinfo **res);\n  static void freeaddrinfo_alt(struct addrinfo *res);\n  static const char *gai_strerror_alt(int errcode);\n\n  static int poll_alt(struct pollfd *fds, size_t nfds, int timeout_ms);\n\npublic:\n  static boolean init(struct config_info *conf);\n  static boolean init_late();\n  static void exit();\n\n  static int getaddrinfo(const char *node, const char *service,\n   const struct addrinfo *hints, struct addrinfo **res) GETADDRINFO_MAYBE_INLINE\n  ({\n    return ::getaddrinfo(node, service, hints, res);\n  });\n\n  static void freeaddrinfo(struct addrinfo *res) GETADDRINFO_MAYBE_INLINE\n  ({\n    ::freeaddrinfo(res);\n  });\n\n  static void getaddrinfo_perror(const char *message, int errcode) GETADDRINFO_MAYBE_INLINE\n  ({\n    warn(\"%s (code %d): %s\\n\", message, errcode, ::gai_strerror(errcode));\n  });\n\n  static int get_errno() UNIX_INLINE\n  ({\n    return errno;\n  });\n\n  static void perror(const char *message) UNIX_INLINE\n  ({\n    ::perror(message);\n  });\n\n  static int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) UNIX_INLINE\n  ({\n    return ::accept(sockfd, addr, addrlen);\n  });\n\n  static int bind(int sockfd, const struct sockaddr *addr,\n   socklen_t addrlen) UNIX_INLINE\n  ({\n    return ::bind(sockfd, addr, addrlen);\n  });\n\n  static void close(int fd) UNIX_INLINE\n  ({\n    ::close(fd);\n  });\n\n  static int connect(int sockfd, const struct sockaddr *serv_addr,\n   socklen_t addrlen) UNIX_INLINE\n  ({\n    return ::connect(sockfd, serv_addr, addrlen);\n  });\n\n  static int getsockopt(int sockfd, int level, int optname, void *optval,\n   socklen_t *optlen) UNIX_INLINE\n  ({\n    return ::getsockopt(sockfd, level, optname, (char *)optval, optlen);\n  });\n\n  /**\n   * Convert a short from host byte order to network byte order (big endian).\n   * Don't use \"htons\" as the function name; BSD defines it as a macro.\n   */\n  static uint16_t hton_short(uint16_t hostshort) UNIX_INLINE\n  ({\n    return htons(hostshort);\n  });\n\n  static int listen(int sockfd, int backlog) UNIX_INLINE\n  ({\n    return ::listen(sockfd, backlog);\n  });\n\n  static int poll(struct pollfd *fds, unsigned int nfds, int timeout_ms) POLL_MAYBE_INLINE\n  ({\n    return ::poll(fds, nfds, timeout_ms);\n  });\n\n  static int select(int nfds, fd_set *readfds, fd_set *writefds,\n   fd_set *exceptfds, struct timeval *timeout) UNIX_INLINE\n  ({\n    return ::select(nfds, readfds, writefds, exceptfds, timeout);\n  });\n\n  static ssize_t send(int sockfd, const void *buf, size_t len, int flags) UNIX_INLINE\n  ({\n    return ::send(sockfd, (const char *)buf, len, flags);\n  });\n\n  static ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,\n   const struct sockaddr *to, socklen_t tolen) UNIX_INLINE\n  ({\n    return ::sendto(sockfd, (const char *)buf, len, flags, to, tolen);\n  });\n\n  static int setsockopt(int sockfd, int level, int optname,\n   const void *optval, socklen_t optlen) UNIX_INLINE\n  ({\n    return ::setsockopt(sockfd, level, optname, (const char *)optval, optlen);\n  });\n\n  static int socket(int af, int type, int protocol) UNIX_INLINE\n  ({\n    return ::socket(af, type, protocol);\n  });\n\n  static ssize_t recv(int sockfd, void *buf, size_t len, int flags) UNIX_INLINE\n  ({\n    return ::recv(sockfd, (char *)buf, len, flags);\n  });\n\n  static ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,\n   struct sockaddr *from, socklen_t *fromlen) UNIX_INLINE\n  ({\n    return ::recvfrom(sockfd, (char *)buf, len, flags, from, fromlen);\n  });\n\n#ifdef __WIN32__\n  static int __WSAFDIsSet(int sockfd, fd_set *set);\n#undef  FD_ISSET\n#define FD_ISSET(fd,set) Socket::__WSAFDIsSet((int)fd, (fd_set *)(set))\n#endif\n\n   /**\n    * These functions don't correspond to POSIX/Berkeley socket functions but\n    * are provided for convenience.\n    */\n\n  static boolean is_last_error_fatal() UNIX_INLINE\n  ({\n    return is_last_errno_fatal();\n  });\n\n  static void set_blocking(int sockfd, boolean blocking) UNIX_INLINE\n  ({\n    int flags = fcntl(sockfd, F_GETFL);\n\n    if(!blocking)\n      flags |= O_NONBLOCK;\n    else\n      flags &= ~O_NONBLOCK;\n\n    fcntl(sockfd, F_SETFL, flags);\n  });\n\n  /**\n   * Class to disable blocking until this object exits scope.\n   */\n  class scoped_nonblocking final\n  {\n  private:\n    int sockfd;\n  public:\n    scoped_nonblocking(int _sockfd): sockfd(_sockfd)\n    {\n      Socket::set_blocking(sockfd, false);\n    }\n    ~scoped_nonblocking()\n    {\n      Socket::set_blocking(sockfd, true);\n    }\n  };\n\nprivate:\n#ifndef __WIN32__\n  static boolean is_last_errno_fatal()\n  {\n    switch(Socket::get_errno())\n    {\n      case 0:\n      case EINPROGRESS:\n      case EAGAIN:\n      case EINTR:\n        return false;\n      default:\n        return true;\n    }\n  }\n#endif\n};\n\n#endif /* __SOCKET_HPP */\n"
  },
  {
    "path": "src/network/network.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"network.h\"\n#include \"../error.h\"\n\n#include \"Host.hpp\"\n\nboolean network_layer_init(struct config_info *conf)\n{\n  if(!conf->network_enabled)\n    return false;\n\n  if(!Host::host_layer_init(conf))\n  {\n    error(\"Failed to initialize network layer.\",\n     ERROR_T_ERROR, ERROR_OPT_OK, 0);\n    return false;\n  }\n\n  return true;\n}\n\nvoid network_layer_exit(struct config_info *conf)\n{\n  if(conf->network_enabled)\n  {\n    Host::host_layer_exit();\n  }\n}\n"
  },
  {
    "path": "src/network/network.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __NETWORK_H\n#define __NETWORK_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"../configure.h\"\n\n#ifdef CONFIG_NETWORK\n\nCORE_LIBSPEC boolean network_layer_init(struct config_info *conf);\nCORE_LIBSPEC void network_layer_exit(struct config_info *conf);\n\n#else /* !CONFIG_NETWORK */\n\nstatic inline boolean network_layer_init(struct config_info *conf)\n{\n  return true;\n}\n\nstatic inline void network_layer_exit(struct config_info *conf) {}\n\n#endif /* CONFIG_NETWORK */\n\n__M_END_DECLS\n\n#endif // __NETWORK_H\n"
  },
  {
    "path": "src/network/server.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Easiest way to get this test code to run:\n *\n * 1) Add \"#define NETWORK_DEADCODE\" to Host.hpp.\n *\n * 2) add this rule to src/Makefile.in (NOTE: needs tabs instead of spaces)\n\n${network_obj}/server.o: ${network_src}/server.cpp\n        $(if ${V},,@echo \"  CXX     \" $<)\n        ${CXX} -MD ${core_cxxflags} ${core_flags} -c $< -o $@\n\n * then include \"${network_obj}/server.o\" in ${mzx_objs} instead of main.o.\n *\n * 3) ./megazeux\n *\n * 4) Test by sending a request to: http://localhost:5656/config.txt\n */\n\n#include \"HTTPHost.hpp\"\n\n#include \"../SDLmzx.h\"\n#include \"../const.h\"\n#include \"../util.h\"\n\n#include <assert.h>\n\n#define INBOUND_PORT 5656\n\nint main(int argc, char *argv[])\n{\n  struct config_info conf;\n  memset(&conf, 0, sizeof(struct config_info));\n\n  if(Host::host_layer_init(&conf))\n  {\n    Host server(HOST_TYPE_TCP, HOST_FAMILY_IPV4);\n    HTTPHost http_client(HOST_TYPE_TCP, HOST_FAMILY_IPV4);\n\n    if(!server.bind(\"localhost\", INBOUND_PORT))\n    {\n      warn(\"Failed to bind host\\n\");\n      goto exit_socket_layer;\n    }\n\n    server.set_blocking(false);\n    if(!server.listen())\n    {\n      warn(\"Failed to listen with host\\n\");\n      goto exit_socket_layer;\n    }\n\n    while(true)\n    {\n      if(server.accept(http_client))\n      {\n        while(true)\n        {\n          if(!http_client.handle_request())\n          {\n            warn(\"Failure handling HTTP request\\n\");\n            http_client.close();\n            break;\n          }\n        }\n      }\n      SDL_Delay(10);\n    }\n  }\n  else\n    warn(\"Error initializing socket layer\\n\");\n\n  return 0;\n\nexit_socket_layer:\n  Host::host_layer_exit();\n  return 0;\n}\n"
  },
  {
    "path": "src/network/sha256.c",
    "content": "/* This implementation of SHA-256 is based on an implementation in the\n * public domain, which was in turn based on another public domain\n * implementation of SHA-1 by Adam Back.\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"sha256.h\"\n\n#include \"../platform_endian.h\"\n#include \"../util.h\"\n\n#include <string.h>\n\n#define S(x,n)     (((x) >> (n)) | ((x) << (32 - (n))))\n#define R(x,n)     ((x) >> (n))\n\n#define Ch(x,y,z)  (((x) & (y)) | (~(x) & (z)))\n#define Maj(x,y,z) (((x) & (y)) |  ((x) & (z)) | ((y) & (z)))\n\n#define SIG0(x)    (S(x, 2) ^ S(x,13) ^ S(x,22))\n#define SIG1(x)    (S(x, 6) ^ S(x,11) ^ S(x,25))\n#define sig0(x)    (S(x, 7) ^ S(x,18) ^ R(x, 3))\n#define sig1(x)    (S(x,17) ^ S(x,19) ^ R(x,10))\n\nstatic const uint32_t K[] =\n{\n  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,\n  0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n  0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,\n  0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n  0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,\n  0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n  0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,\n  0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n  0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,\n  0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n  0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,\n  0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,\n  0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n  0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,\n  0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\n};\n\nstatic const uint32_t H_initial[] = {\n  0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,\n  0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19\n};\n\n#if PLATFORM_BYTE_ORDER == PLATFORM_LIL_ENDIAN\n\nstatic void convert_to_bigendian(void *data, int len)\n{\n  uint32_t *data_as_words = (uint32_t *)data;\n  uint8_t *data_as_bytes;\n  uint32_t temp;\n  uint8_t *temp_as_bytes = (uint8_t *)&temp;\n  int i;\n\n  for(i = 0; i < len / 4; i++)\n  {\n    temp = data_as_words[i];\n    data_as_bytes = (uint8_t *)&data_as_words[i];\n\n    data_as_bytes[0] = temp_as_bytes[3];\n    data_as_bytes[1] = temp_as_bytes[2];\n    data_as_bytes[2] = temp_as_bytes[1];\n    data_as_bytes[3] = temp_as_bytes[0];\n  }\n}\n\n#else\n\nstatic inline void convert_to_bigendian(void *data, int len) { }\n\n#endif\n\nstatic void SHA256_transform(struct SHA256_ctx *ctx)\n {\n  uint32_t A = ctx->H[0];\n  uint32_t B = ctx->H[1];\n  uint32_t C = ctx->H[2];\n  uint32_t D = ctx->H[3];\n  uint32_t E = ctx->H[4];\n  uint32_t F = ctx->H[5];\n  uint32_t G = ctx->H[6];\n  uint32_t H = ctx->H[7];\n  uint32_t T1, T2;\n  uint32_t W[64];\n  int t;\n\n  memcpy(W, ctx->M, 64);\n\n  for(t = 16; t < 64; t++)\n    W[t] = sig1(W[t - 2]) + W[t - 7] + sig0(W[t - 15]) + W[t - 16];\n\n  for(t = 0; t < 64; t++)\n  {\n    T1 = H + SIG1(E) + Ch(E,F,G) + K[t] + W[t];\n    T2 = SIG0(A) + Maj(A,B,C);\n    H = G;\n    G = F;\n    F = E;\n    E = D + T1;\n    D = C;\n    C = B;\n    B = A;\n    A = T1 + T2;\n  }\n\n  ctx->H[0] += A;\n  ctx->H[1] += B;\n  ctx->H[2] += C;\n  ctx->H[3] += D;\n  ctx->H[4] += E;\n  ctx->H[5] += F;\n  ctx->H[6] += G;\n  ctx->H[7] += H;\n}\n\nvoid SHA256_init(struct SHA256_ctx *ctx)\n{\n  memcpy(ctx->H, H_initial, 8 * sizeof(uint32_t));\n  ctx->lbits = 0;\n  ctx->hbits = 0;\n  ctx->mlen = 0;\n}\n\nvoid SHA256_update(struct SHA256_ctx *ctx, const void *vdata, size_t data_len)\n{\n  const uint8_t *data = (const uint8_t *)vdata;\n  uint32_t low_bits;\n  size_t use;\n\n  /* convert data_len to bits and add to the 64 bit word\n   * formed by lbits and hbits\n   */\n\n  ctx->hbits += data_len >> 29;\n  low_bits = data_len << 3;\n  ctx->lbits += low_bits;\n\n  if(ctx->lbits < low_bits)\n    ctx->hbits++;\n\n  /* deal with first block */\n\n  use = MIN((size_t)(64 - ctx->mlen), data_len);\n  memcpy(ctx->M + ctx->mlen, data, use);\n  ctx->mlen += use;\n  data_len -= use;\n  data += use;\n\n  while(ctx->mlen == 64)\n  {\n    convert_to_bigendian(ctx->M, 64);\n    SHA256_transform(ctx);\n    use = MIN(64, data_len);\n    memcpy(ctx->M, data, use);\n    ctx->mlen = use;\n    data_len -= use;\n    data += use; /* was missing */\n  }\n}\n\nvoid SHA256_final(struct SHA256_ctx *ctx)\n{\n  if(ctx->mlen < 56)\n  {\n    ctx->M[ctx->mlen++] = 0x80;\n    memset(ctx->M + ctx->mlen, 0x00, 56 - ctx->mlen);\n    convert_to_bigendian(ctx->M, 56);\n  }\n  else\n  {\n    ctx->M[ctx->mlen++] = 0x80;\n    memset(ctx->M + ctx->mlen, 0x00, 64 - ctx->mlen);\n    convert_to_bigendian(ctx->M, 64);\n    SHA256_transform(ctx);\n    memset(ctx->M, 0x00, 56);\n  }\n\n  memcpy(ctx->M + 56, &ctx->hbits, 8);\n  SHA256_transform(ctx);\n}\n\n#ifdef SHA256TEST\n\nint main(void)\n{\n  struct SHA256_ctx ctx;\n  int i;\n\n  const char *test1 = \"abc\";\n  const uint32_t result1[8] = {\n    0xba7816bf, 0x8f01cfea, 0x414140de, 0x5dae2223,\n    0xb00361a3, 0x96177a9c, 0xb410ff61, 0xf20015ad\n  };\n\n  const char *test2 =\n    \"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq\";\n  const uint32_t result2[8] = {\n    0x248d6a61, 0xd20638b8, 0xe5c02693, 0x0c3e6039,\n    0xa33ce459, 0x64ff2167, 0xf6ecedd4, 0x19db06c1\n  };\n\n  printf(\"test 1\\n\\n\");\n  SHA256_init(&ctx);\n  SHA256_update(&ctx, test1, strlen(test1));\n  SHA256_final(&ctx);\n\n  printf(\"SHA256(\\\"%s\\\") = \\n\\t\", test1);\n\n  for(i = 0; i < 8; i++)\n    printf(\"%08x\", ctx.H[i]);\n\n  if(memcmp(ctx.H, result1, 32) == 0)\n    printf(\" test ok\\n\\n\");\n  else\n    printf(\" test failed\\n\\n\");\n\n  printf(\"test 2\\n\\n\");\n  SHA256_init(&ctx);\n  SHA256_update(&ctx, test2, strlen(test2));\n  SHA256_final(&ctx);\n\n  printf(\"SHA256(\\\"%s\\\") = \\n\\t\", test2);\n\n  for(i = 0; i < 8; i++)\n    printf(\"%08x\", ctx.H[i]);\n\n  if(memcmp(ctx.H, result2, 32) == 0)\n    printf(\" test ok\\n\\n\");\n  else\n    printf(\" test failed\\n\\n\");\n\n  return 0;\n}\n\n#endif // SHA256TEST\n"
  },
  {
    "path": "src/network/sha256.h",
    "content": "/* This implementation of SHA-256 is based on an implementation in the\n * public domain, which was in turn based on another public domain\n * implementation of SHA-1 by Adam Back.\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __SHA256_H\n#define __SHA256_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stdint.h>\n\nstruct SHA256_ctx\n{\n  uint32_t H[8];\n  uint32_t hbits;\n  uint32_t lbits;\n  uint8_t M[64];\n  uint8_t mlen;\n};\n\nvoid SHA256_init(struct SHA256_ctx *ctx);\nvoid SHA256_update(struct SHA256_ctx *ctx, const void *vdata, size_t data_len);\nvoid SHA256_final(struct SHA256_ctx *ctx);\n\n__M_END_DECLS\n\n#endif // __SHA256_H\n"
  },
  {
    "path": "src/nostdc++.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Compatibility functions for C++ when not linking libstdc++/libc++.\n * For platforms (Xcode, Nintendo Switch) or configurations (currently none)\n * that require linking one of those, this file can be disregarded.\n */\n\n#include \"compat.h\"\n#include \"util.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <new>\n\nextern \"C\" CORE_LIBSPEC void __cxa_pure_virtual();\nextern \"C\" CORE_LIBSPEC void __cxa_pure_virtual()\n{\n  fprintf(mzxerr, \"Attempted to call pure virtual function! Aborting!\\n\");\n  fflush(mzxerr);\n  exit(1);\n}\n\nextern \"C\" CORE_LIBSPEC void __cxa_deleted_virtual();\nextern \"C\" CORE_LIBSPEC void __cxa_deleted_virtual()\n{\n  fprintf(mzxerr, \"Attempted to call deleted virtual function! Aborting!\\n\");\n  fflush(mzxerr);\n  exit(1);\n}\n\nCORE_LIBSPEC void *operator new(size_t count)\n{\n  void *ptr = cmalloc(count);\n  if(!ptr)\n  {\n    // Allocation failed in thread--abort!\n    fprintf(mzxerr, \"Failed to allocate memory for new! Aborting!\\n\");\n    fflush(mzxerr);\n    exit(1);\n  }\n  return ptr;\n}\n\nCORE_LIBSPEC void *operator new[](size_t count)\n{\n  void *ptr = cmalloc(count);\n  if(!ptr)\n  {\n    // Allocation failed in thread--abort!\n    fprintf(mzxerr, \"Failed to allocate memory for new[]! Aborting!\\n\");\n    fflush(mzxerr);\n    exit(1);\n  }\n  return ptr;\n}\n\nCORE_LIBSPEC void operator delete(void *ptr) noexcept\n{\n  free(ptr);\n}\n\nCORE_LIBSPEC void operator delete[](void *ptr) noexcept\n{\n  free(ptr);\n}\n"
  },
  {
    "path": "src/old/legacy_save.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"legacy_world_save.h\"\n\n#include \"board.h\"\n#include \"const.h\"\n#include \"counter.h\"\n#include \"error.h\"\n#include \"extmem.h\"\n#include \"graphics.h\"\n#include \"idput.h\"\n#include \"legacy_rasm.h\"\n#include \"rasm.h\"\n#include \"sprite.h\"\n#include \"window.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n#include \"util.h\"\n\n#include \"audio/sfx.h\"\n\n/* This file is a collection of the 2.84 file format saving functions.\n * Nothing here is guaranteed to work; this file exists mainly for reference.\n */\n\n#ifndef CONFIG_LOADSAVE_METER\n\nstatic inline void meter_update_screen(int *curr, int target) {}\nstatic inline void meter_restore_screen(void) {}\nstatic inline void meter_initial_draw(int curr, int target, const char *title) {}\n\n#endif //!CONFIG_LOADSAVE_METER\n\n\n/*********/\n/* Robot */\n/*********/\n\nsize_t legacy_save_robot_calculate_size(struct world *mzx_world,\n struct robot *cur_robot, int savegame, int version)\n{\n  // This both prepares a robot for saving (in the case of a savegame robot in debytecode mzx)\n  // and calculates the amount of space it will require. As a result, it is mandatory to\n  // call this function before calling save_robot_memory.\n  int program_length;\n  size_t robot_size = 41; // Default size\n\n#ifdef CONFIG_DEBYTECODE\n  if(savegame)\n  {\n    prepare_robot_bytecode(mzx_world, cur_robot);\n    program_length = cur_robot->program_bytecode_length;\n  }\n  else\n  {\n    program_length = cur_robot->program_source_length;\n  }\n#else /* !CONFIG_DEBYTECODE */\n  program_length = cur_robot->program_bytecode_length;\n#endif /* !CONFIG_DEBYTECODE */\n\n  if(savegame)\n  {\n    if(version >= V284)\n      robot_size += 4; // 4 bytes added for loopcount\n    robot_size += 4 * 32; // 32 local counters\n    robot_size += 4; // stack size\n    robot_size += 4; // stack pointer\n    robot_size += 4 * cur_robot->stack_size / 2; // stack\n  }\n  robot_size += program_length;\n  return robot_size;\n}\n\nvoid legacy_save_robot_to_memory(struct robot *cur_robot, struct memfile *mf,\n int savegame, int version)\n{\n  int program_length;\n  int i;\n\n#ifdef CONFIG_DEBYTECODE\n  // Write the program's source code if it's a world file, or the\n  // bytecode if it's a save file. For save files we currently must write\n  // bytecode because it stores the zap status inside the programs\n  // themselves (even though it's in the label cache as well).\n\n  if(savegame)\n  {\n    program_length = cur_robot->program_bytecode_length;\n  }\n  else\n  {\n    program_length = cur_robot->program_source_length;\n  }\n\n  // As of 2.83 we're writing out 4 byte sizes.\n  mfputd(program_length, mf);\n#else /* !CONFIG_DEBYTECODE */\n  program_length = cur_robot->program_bytecode_length;\n  mfputw(program_length, mf);\n  // Junk\n  mfputw(0, mf);\n#endif /* !CONFIG_DEBYTECODE */\n\n  mfwrite(cur_robot->robot_name, LEGACY_ROBOT_NAME_SIZE, 1, mf);\n\n  mfputc(cur_robot->robot_char, mf);\n  if(savegame)\n  {\n    mfputw(cur_robot->cur_prog_line, mf);\n    mfputc(cur_robot->pos_within_line, mf);\n    mfputc(cur_robot->robot_cycle, mf);\n    mfputc(cur_robot->cycle_count, mf);\n    mfputc(cur_robot->bullet_type, mf);\n    mfputc(cur_robot->is_locked, mf);\n    mfputc(cur_robot->can_lavawalk, mf);\n    mfputc(cur_robot->walk_dir, mf);\n    mfputc(cur_robot->last_touch_dir, mf);\n    mfputc(cur_robot->last_shot_dir, mf);\n    mfputw(cur_robot->xpos, mf);\n    mfputw(cur_robot->ypos, mf);\n    mfputc(cur_robot->status, mf);\n  }\n  else\n  {\n    // Put some \"default\" values here instead\n    mfputw(1, mf);\n    mfputc(0, mf);\n    mfputc(0, mf);\n    mfputc(0, mf);\n    mfputc(1, mf);\n    mfputc(0, mf);\n    mfputc(0, mf);\n    mfputc(0, mf);\n    mfputc(0, mf);\n    mfputc(0, mf);\n    mfputw(cur_robot->xpos, mf);\n    mfputw(cur_robot->ypos, mf);\n    mfputc(0, mf);\n  }\n\n  // Junk local\n  mfputw(0, mf);\n  mfputc(cur_robot->used, mf);\n  // loop_count\n  if(version <= V283)\n    mfputw(cur_robot->loop_count, mf);\n  else // junk\n    mfputw(0, mf);\n\n  // If savegame, there's some additional information to get\n  if(savegame)\n  {\n    int stack_size = cur_robot->stack_size;\n\n    // Write the local counters\n    if(version >= V284)\n      mfputd(cur_robot->loop_count, mf);\n\n    for(i = 0; i < 32; i++)\n    {\n      mfputd(cur_robot->local[i], mf);\n    }\n\n    // Put the stack size\n    // Divide by two; we don't want to save pos_within_line values.\n    mfputd(stack_size/2, mf);\n\n    // Put the stack pointer\n    mfputd(cur_robot->stack_pointer/2, mf);\n\n    // Put the stack\n    // Only put even indices; odd indices are pos_within_line values.\n    for(i = 0; i < stack_size; i += 2)\n    {\n      mfputd(cur_robot->stack[i], mf);\n    }\n  }\n\n#ifdef CONFIG_DEBYTECODE\n  // NOTE: This will one day be a good thing to move out of the save game;\n  // this will require that the programs become immutable and thus have\n  // the zap status stored somewhere else.\n\n  if(!savegame)\n    mfwrite(cur_robot->program_source, program_length, 1, mf);\n  else\n#endif\n    mfwrite(cur_robot->program_bytecode, program_length, 1, mf);\n}\n\nvoid legacy_save_robot(struct world *mzx_world, struct robot *cur_robot,\n FILE *fp, int savegame, int version)\n{\n  size_t robot_size = legacy_save_robot_calculate_size(mzx_world, cur_robot,\n   savegame, version);\n  void *buffer = cmalloc(robot_size);\n  struct memfile mf;\n\n  mfopen_wr(buffer, robot_size, &mf);\n  legacy_save_robot_to_memory(cur_robot, &mf, savegame, version);\n\n  fwrite(buffer, robot_size, 1, fp);\n  free(buffer);\n}\n\nvoid legacy_save_scroll(struct scroll *cur_scroll, FILE *fp, int savegame)\n{\n  int scroll_size = (int)cur_scroll->mesg_size;\n\n  fputw(cur_scroll->num_lines, fp);\n  fputw(0, fp);\n  fputw(scroll_size, fp);\n  fputc(cur_scroll->used, fp);\n\n  fwrite(cur_scroll->mesg, scroll_size, 1, fp);\n}\n\nvoid legacy_save_sensor(struct sensor *cur_sensor, FILE *fp, int savegame)\n{\n  fwrite(cur_sensor->sensor_name, 15, 1, fp);\n  fputc(cur_sensor->sensor_char, fp);\n  fwrite(cur_sensor->robot_to_mesg, 15, 1, fp);\n  fputc(cur_sensor->used, fp);\n}\n\n\n/*********/\n/* Board */\n/*********/\n\nstatic void save_RLE2_plane(char *plane, FILE *fp, int size)\n{\n  int i, runsize;\n  char current_char;\n\n  for(i = 0; i < size; i++)\n  {\n    current_char = plane[i];\n    runsize = 1;\n\n    while((i < (size - 1)) && (plane[i + 1] == current_char) &&\n     (runsize < 127))\n    {\n      i++;\n      runsize++;\n    }\n\n    // Put the runsize if necessary\n    if((runsize > 1) || current_char & 0x80)\n    {\n      fputc(runsize | 0x80, fp);\n      // Put the run character\n      fputc(current_char, fp);\n    }\n    else\n    {\n      fputc(current_char, fp);\n    }\n  }\n}\n\nint legacy_save_board(struct world *mzx_world, struct board *cur_board,\n FILE *fp, int savegame, int file_version)\n{\n  int num_robots, num_scrolls, num_sensors;\n  int start_location = ftell(fp);\n  int board_width = cur_board->board_width;\n  int board_height = cur_board->board_height;\n  int board_size = board_width * board_height;\n  int i;\n\n  // Board mode is now ignored, put 0\n  fputc(0, fp);\n  // Put overlay mode\n\n  if(cur_board->overlay_mode)\n  {\n    fputc(0, fp);\n    fputc(cur_board->overlay_mode, fp);\n    fputw(cur_board->board_width, fp);\n    fputw(cur_board->board_height, fp);\n    save_RLE2_plane(cur_board->overlay, fp, board_size);\n    fputw(cur_board->board_width, fp);\n    fputw(cur_board->board_height, fp);\n    save_RLE2_plane(cur_board->overlay_color, fp, board_size);\n  }\n\n  fputw(board_width, fp);\n  fputw(board_height, fp);\n  save_RLE2_plane(cur_board->level_id, fp, board_size);\n  fputw(board_width, fp);\n  fputw(board_height, fp);\n  save_RLE2_plane(cur_board->level_color, fp, board_size);\n  fputw(board_width, fp);\n  fputw(board_height, fp);\n  save_RLE2_plane(cur_board->level_param, fp, board_size);\n  fputw(board_width, fp);\n  fputw(board_height, fp);\n  save_RLE2_plane(cur_board->level_under_id, fp, board_size);\n  fputw(board_width, fp);\n  fputw(board_height, fp);\n  save_RLE2_plane(cur_board->level_under_color, fp, board_size);\n  fputw(board_width, fp);\n  fputw(board_height, fp);\n  save_RLE2_plane(cur_board->level_under_param, fp, board_size);\n\n  // Save board parameters\n\n  {\n    size_t len = strlen(cur_board->mod_playing);\n    fputw((int)len, fp);\n    if(len)\n      fwrite(cur_board->mod_playing, len, 1, fp);\n  }\n\n  fputc(cur_board->viewport_x, fp);\n  fputc(cur_board->viewport_y, fp);\n  fputc(cur_board->viewport_width, fp);\n  fputc(cur_board->viewport_height, fp);\n  fputc(cur_board->can_shoot, fp);\n  fputc(cur_board->can_bomb, fp);\n  fputc(cur_board->fire_burn_brown, fp);\n  fputc(cur_board->fire_burn_space, fp);\n  fputc(cur_board->fire_burn_fakes, fp);\n  fputc(cur_board->fire_burn_trees, fp);\n  fputc(cur_board->explosions_leave, fp);\n  fputc(cur_board->save_mode, fp);\n  fputc(cur_board->forest_becomes, fp);\n  fputc(cur_board->collect_bombs, fp);\n  fputc(cur_board->fire_burns, fp);\n\n  for(i = 0; i < 4; i++)\n  {\n    fputc(cur_board->board_dir[i], fp);\n  }\n\n  fputc(cur_board->restart_if_zapped, fp);\n  fputw(cur_board->time_limit, fp);\n\n  if(savegame)\n  {\n    size_t len;\n\n    fputc(cur_board->last_key, fp);\n    fputw(cur_board->num_input, fp);\n    fputw((int)cur_board->input_size, fp);\n\n    len = strlen(cur_board->input_string);\n    fputw((int)len, fp);\n    if(len)\n      fwrite(cur_board->input_string, len, 1, fp);\n\n    fputc(cur_board->player_last_dir, fp);\n\n    len = strlen(cur_board->bottom_mesg);\n    fputw((int)len, fp);\n    if(len)\n      fwrite(cur_board->bottom_mesg, len, 1, fp);\n\n    fputc(cur_board->b_mesg_timer, fp);\n    fputc(cur_board->lazwall_start, fp);\n    fputc(cur_board->b_mesg_row, fp);\n    fputc(cur_board->b_mesg_col, fp);\n    fputw(cur_board->scroll_x, fp);\n    fputw(cur_board->scroll_y, fp);\n    fputw(cur_board->locked_x, fp);\n    fputw(cur_board->locked_y, fp);\n  }\n\n  fputc(cur_board->player_ns_locked, fp);\n  fputc(cur_board->player_ew_locked, fp);\n  fputc(cur_board->player_attack_locked, fp);\n\n  if(savegame)\n  {\n    fputc(cur_board->volume, fp);\n    fputc(cur_board->volume_inc, fp);\n    fputc(cur_board->volume_target, fp);\n  }\n\n  // Save robots\n  num_robots = cur_board->num_robots;\n  fputc(num_robots, fp);\n\n  if(num_robots)\n  {\n    struct robot *cur_robot;\n\n    for(i = 1; i <= num_robots; i++)\n    {\n      cur_robot = cur_board->robot_list[i];\n      legacy_save_robot(mzx_world, cur_robot, fp, savegame, file_version);\n    }\n  }\n\n  // Save scrolls\n  num_scrolls = cur_board->num_scrolls;\n  putc(num_scrolls, fp);\n\n  if(num_scrolls)\n  {\n    struct scroll *cur_scroll;\n\n    for(i = 1; i <= num_scrolls; i++)\n    {\n      cur_scroll = cur_board->scroll_list[i];\n      legacy_save_scroll(cur_scroll, fp, savegame);\n    }\n  }\n\n  // Save sensors\n  num_sensors = cur_board->num_sensors;\n  fputc(num_sensors, fp);\n\n  if(num_sensors)\n  {\n    struct sensor *cur_sensor;\n\n    for(i = 1; i <= num_sensors; i++)\n    {\n      cur_sensor = cur_board->sensor_list[i];\n      legacy_save_sensor(cur_sensor, fp, savegame);\n    }\n  }\n\n  return (ftell(fp) - start_location);\n}\n\n\n/*********/\n/* World */\n/*********/\n\nstatic inline void legacy_save_counter(FILE *fp, struct counter *src_counter)\n{\n  size_t name_length = strlen(src_counter->name);\n\n  fputd(src_counter->value, fp);\n  fputd((int)name_length, fp);\n  fwrite(src_counter->name, name_length, 1, fp);\n}\n\nstatic inline void legacy_save_string(FILE *fp, struct string *src_string)\n{\n  size_t name_length = strlen(src_string->name);\n  size_t str_length = src_string->length;\n\n  fputd((int)name_length, fp);\n  fputd((int)str_length, fp);\n  fwrite(src_string->name, name_length, 1, fp);\n  fwrite(src_string->value, str_length, 1, fp);\n}\n\n\nint legacy_save_world(struct world *mzx_world, const char *file, int savegame)\n{\n  int file_version = MZX_LEGACY_FORMAT_VERSION;\n\n  int i, num_boards;\n  int gl_rob_position, gl_rob_save_position;\n  int board_offsets_position, board_begin_position;\n  int board_size;\n  unsigned int *size_offset_list;\n  unsigned char *charset_mem;\n  unsigned char r, g, b;\n  struct board *cur_board;\n  FILE *fp;\n\n  int meter_target = 2 + mzx_world->num_boards, meter_curr = 0;\n\n  fp = fopen_unsafe(file, \"wb\");\n  if(!fp)\n  {\n    error_message(E_WORLD_IO_SAVING, 0, NULL);\n    return -1;\n  }\n\n  meter_initial_draw(meter_curr, meter_target, \"Saving...\");\n\n  if(savegame)\n  {\n    // Write this MZX's version string\n    fputs(\"MZS\", fp);\n    fputc((file_version >> 8) & 0xff, fp);\n    fputc(file_version & 0xff, fp);\n\n    // Write the version of the loaded world for this SAV\n    fputw(mzx_world->version, fp);\n\n    fputc(mzx_world->current_board_id, fp);\n  }\n  else\n  {\n    fwrite(mzx_world->name, BOARD_NAME_SIZE, 1, fp);\n\n    // No protection\n    fputc(0, fp);\n\n    // Write this MZX's version string\n    fputc('M', fp);\n    fputc((file_version >> 8) & 0xff, fp);\n    fputc(file_version & 0xff, fp);\n  }\n\n  // Save charset\n  charset_mem = cmalloc(3584);\n  ec_mem_save_set_var(charset_mem, 3584, 0);\n  fwrite(charset_mem, 3584, 1, fp);\n  free(charset_mem);\n\n  // Save idchars array.\n  fwrite(id_chars, LEGACY_ID_CHARS_SIZE, 1, fp);\n  fputc(missile_color, fp);\n  fwrite(bullet_color, LEGACY_ID_BULLET_COLOR_SIZE, 1, fp);\n  fwrite(id_dmg, LEGACY_ID_DMG_SIZE, 1, fp);\n\n  // Save status counters.\n  fwrite((char *)mzx_world->status_counters_shown, COUNTER_NAME_SIZE,\n   NUM_STATUS_COUNTERS, fp);\n\n  /* Older MZX sources refer to SAVE_INDIVIDUAL, but it has always been\n   * defined. Exo eventually removed the conditional code in 2.80.\n   * We don't need to think about it.\n   */\n\n  if(savegame)\n  {\n    fwrite(mzx_world->keys, NUM_KEYS, 1, fp);\n    fputc(mzx_world->blind_dur, fp);\n    fputc(mzx_world->firewalker_dur, fp);\n    fputc(mzx_world->freeze_time_dur, fp);\n    fputc(mzx_world->slow_time_dur, fp);\n    fputc(mzx_world->wind_dur, fp);\n\n    for(i = 0; i < 8; i++)\n    {\n      fputw(mzx_world->pl_saved_x[i], fp);\n    }\n\n    for(i = 0; i < 8; i++)\n    {\n      fputw(mzx_world->pl_saved_y[i], fp);\n    }\n\n    fwrite(mzx_world->pl_saved_board, 8, 1, fp);\n    fputc(mzx_world->saved_pl_color, fp);\n    fputc(mzx_world->under_player_id, fp);\n    fputc(mzx_world->under_player_color, fp);\n    fputc(mzx_world->under_player_param, fp);\n    fputc(mzx_world->mesg_edges, fp);\n    fputc(mzx_world->scroll_base_color, fp);\n    fputc(mzx_world->scroll_corner_color, fp);\n    fputc(mzx_world->scroll_pointer_color, fp);\n    fputc(mzx_world->scroll_title_color, fp);\n    fputc(mzx_world->scroll_arrow_color, fp);\n\n    {\n      size_t len = strlen(mzx_world->real_mod_playing);\n      fputw((int)len, fp);\n      if(len)\n        fwrite(mzx_world->real_mod_playing, len, 1, fp);\n    }\n  }\n\n  fputc(mzx_world->edge_color, fp);\n  fputc(mzx_world->first_board, fp);\n  fputc(mzx_world->endgame_board, fp);\n  fputc(mzx_world->death_board, fp);\n  fputw(mzx_world->endgame_x, fp);\n  fputw(mzx_world->endgame_y, fp);\n  fputc(mzx_world->game_over_sfx, fp);\n  fputw(mzx_world->death_x, fp);\n  fputw(mzx_world->death_y, fp);\n  fputw(mzx_world->starting_lives, fp);\n  fputw(mzx_world->lives_limit, fp);\n  fputw(mzx_world->starting_health, fp);\n  fputw(mzx_world->health_limit, fp);\n  fputc(mzx_world->enemy_hurt_enemy, fp);\n  fputc(mzx_world->clear_on_exit, fp);\n  fputc(mzx_world->only_from_swap, fp);\n\n  // Palette...\n  for(i = 0; i < 16; i++)\n  {\n    get_rgb(i, &r, &g, &b);\n    fputc(r, fp);\n    fputc(g, fp);\n    fputc(b, fp);\n  }\n\n  if(savegame)\n  {\n    struct counter *mzx_speed, *lock_speed;\n    int vlayer_size;\n\n    for(i = 0; i < 16; i++)\n    {\n      fputc(get_color_intensity(i), fp);\n    }\n    fputc(get_fade_status(), fp);\n\n    fputw(mzx_world->player_restart_x, fp);\n    fputw(mzx_world->player_restart_y, fp);\n    fputc(mzx_world->under_player_id, fp);\n    fputc(mzx_world->under_player_color, fp);\n    fputc(mzx_world->under_player_param, fp);\n\n    // Write regular counters + mzx_speed\n    fputd(mzx_world->num_counters + 2, fp);\n    for(i = 0; i < mzx_world->num_counters; i++)\n    {\n      legacy_save_counter(fp, mzx_world->counter_list[i]);\n    }\n\n    // Stupid hack\n    mzx_speed = malloc(sizeof(struct counter) + sizeof(\"mzx_speed\") - 1);\n    mzx_speed->value = mzx_world->mzx_speed;\n    strcpy(mzx_speed->name, \"mzx_speed\");\n    legacy_save_counter(fp, mzx_speed);\n    free(mzx_speed);\n\n    // another stupid hack\n    lock_speed = malloc(sizeof(struct counter) + sizeof(\"_____lock_speed\") - 1);\n    lock_speed->value = mzx_world->lock_speed;\n    strcpy(lock_speed->name, \"_____lock_speed\");\n    legacy_save_counter(fp, lock_speed);\n    free(lock_speed);\n\n    // Write strings\n    fputd(mzx_world->num_strings, fp);\n\n    for(i = 0; i < mzx_world->num_strings; i++)\n    {\n      legacy_save_string(fp, mzx_world->string_list[i]);\n    }\n\n    // Sprite data\n    for(i = 0; i < MAX_SPRITES; i++)\n    {\n      fputw((mzx_world->sprite_list[i])->x, fp);\n      fputw((mzx_world->sprite_list[i])->y, fp);\n      fputw((mzx_world->sprite_list[i])->ref_x, fp);\n      fputw((mzx_world->sprite_list[i])->ref_y, fp);\n      fputc((mzx_world->sprite_list[i])->color, fp);\n      fputc((mzx_world->sprite_list[i])->flags, fp);\n      fputc((mzx_world->sprite_list[i])->width, fp);\n      fputc((mzx_world->sprite_list[i])->height, fp);\n      fputc((mzx_world->sprite_list[i])->col_x, fp);\n      fputc((mzx_world->sprite_list[i])->col_y, fp);\n      fputc((mzx_world->sprite_list[i])->col_width, fp);\n      fputc((mzx_world->sprite_list[i])->col_height, fp);\n    }\n    // total sprites\n    fputc(mzx_world->active_sprites, fp);\n    // y order flag\n    fputc(mzx_world->sprite_y_order, fp);\n    // collision info\n    fputw(mzx_world->collision_count, fp);\n\n    for(i = 0; i < MAX_SPRITES; i++)\n    {\n      fputw(mzx_world->collision_list[i], fp);\n    }\n\n    // Multiplier\n    fputw(mzx_world->multiplier, fp);\n    // Divider\n    fputw(mzx_world->divider, fp);\n    // Circle divisions\n    fputw(mzx_world->c_divisions, fp);\n    // String FREAD and FWRITE Delimiters\n    fputw(mzx_world->fread_delimiter, fp);\n    fputw(mzx_world->fwrite_delimiter, fp);\n    // Builtin shooting/message status\n    fputc(mzx_world->bi_shoot_status, fp);\n    fputc(mzx_world->bi_mesg_status, fp);\n\n    // Write input file name and if open, position\n    {\n      size_t len = strlen(mzx_world->input_file_name);\n      fputw((int)len, fp);\n      if(len)\n        fwrite(mzx_world->input_file_name, len, 1, fp);\n    }\n    fputd(mzx_world->temp_input_pos, fp);\n\n    // Write output file name and if open, position\n    {\n      size_t len = strlen(mzx_world->output_file_name);\n      fputw((int)len, fp);\n      if(len)\n        fwrite(mzx_world->output_file_name, len, 1, fp);\n    }\n    fputd(mzx_world->temp_output_pos, fp);\n\n    fputw(get_screen_mode(), fp);\n\n    if(get_screen_mode() > 1)\n    {\n      // Put SMZX mode 2 palette\n      for(i = 0; i < 256; i++)\n      {\n        get_rgb(i, &r, &g, &b);\n        fputc(r, fp);\n        fputc(g, fp);\n        fputc(b, fp);\n      }\n    }\n\n    fputd(mzx_world->commands, fp);\n\n    vlayer_size = mzx_world->vlayer_size;\n    fputd(vlayer_size, fp);\n    fputw(mzx_world->vlayer_width, fp);\n    fputw(mzx_world->vlayer_height, fp);\n\n    fwrite(mzx_world->vlayer_chars, 1, vlayer_size, fp);\n    fwrite(mzx_world->vlayer_colors, 1, vlayer_size, fp);\n  }\n\n  // Put position of global robot later\n  gl_rob_save_position = ftell(fp);\n  // Put some 0's\n  fputd(0, fp);\n\n  // Put custom fx?\n  if(mzx_world->custom_sfx_on == 1)\n  {\n    int offset = 0;\n    size_t sfx_len;\n    int length_slot_pos, next_pos, total_len;\n    fputc(0, fp);\n    length_slot_pos = ftell(fp);\n    fputw(0, fp);\n    for(i = 0; i < NUM_SFX; i++, offset += LEGACY_SFX_SIZE)\n    {\n      sfx_len = strlen(mzx_world->custom_sfx + offset);\n      fputc((int)sfx_len, fp);\n      fwrite(mzx_world->custom_sfx + offset, sfx_len, 1, fp);\n    }\n    // Get size of the block\n    next_pos = ftell(fp);\n    total_len = (next_pos - length_slot_pos) - 2;\n    fseek(fp, length_slot_pos, SEEK_SET);\n    fputw(total_len, fp);\n    fseek(fp, next_pos, SEEK_SET);\n  }\n\n  meter_update_screen(&meter_curr, meter_target);\n\n  num_boards = mzx_world->num_boards;\n  fputc(num_boards, fp);\n\n  // Put the names\n  for(i = 0; i < num_boards; i++)\n  {\n    fwrite((mzx_world->board_list[i])->board_name, 25, 1, fp);\n  }\n\n  /* Due to some bugs in the NDS's libfat library, seeking backwards\n   * from the end results in data corruption. To prevent this, waste\n   * a little bit of memory caching the offsets of the board data so\n   * we can rewrite the size/offset list with less seeking later.\n   */\n  size_offset_list = cmalloc(8 * num_boards);\n  board_offsets_position = ftell(fp);\n  fseek(fp, 8 * num_boards, SEEK_CUR);\n\n  for(i = 0; i < num_boards; i++)\n  {\n    cur_board = mzx_world->board_list[i];\n\n    // Before messing with the board, make sure the board is\n    // rid of any gaps in the object lists...\n    optimize_null_objects(cur_board);\n\n    // First save the offset of where the board will be placed\n    board_begin_position = ftell(fp);\n    // Now save the board and get the size\n    board_size = legacy_save_board(mzx_world, cur_board, fp, savegame,\n     file_version);\n    // board_end_position, unused\n    ftell(fp);\n    // Record size/offset information.\n    size_offset_list[2 * i] = board_size;\n    size_offset_list[2 * i + 1] = board_begin_position;\n\n    meter_update_screen(&meter_curr, meter_target);\n  }\n\n  // Save for global robot position\n  gl_rob_position = ftell(fp);\n  legacy_save_robot(mzx_world, &mzx_world->global_robot, fp, savegame, file_version);\n\n  meter_update_screen(&meter_curr, meter_target);\n\n  // Go back to where the global robot position should be saved\n  fseek(fp, gl_rob_save_position, SEEK_SET);\n  fputd(gl_rob_position, fp);\n\n  // Go back to offsets/size list\n  fseek(fp, board_offsets_position, SEEK_SET);\n  for(i = 0; i < num_boards; i++)\n  {\n    fputd(size_offset_list[2 * i  ], fp);\n    fputd(size_offset_list[2 * i + 1], fp);\n  }\n  free(size_offset_list);\n\n  meter_restore_screen();\n\n  fclose(fp);\n  return 0;\n}\n"
  },
  {
    "path": "src/old/legacy_save.h",
    "content": "/* MegaZeux\r\n *\r\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\r\n * Copyright (C) 2017-2020 Alice Rowan <petrifiedrowan@gmail.com>\r\n *\r\n * This program is free software; you can redistribute it and/or\r\n * modify it under the terms of the GNU General Public License as\r\n * published by the Free Software Foundation; either version 2 of\r\n * the License, or (at your option) any later version.\r\n *\r\n * This program is distributed in the hope that it will be useful,\r\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r\n * General Public License for more details.\r\n *\r\n * You should have received a copy of the GNU General Public License\r\n * along with this program; if not, write to the Free Software\r\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\r\n */\r\n\r\n#ifndef __LEGACY_WORLD_SAVE_H\r\n#define __LEGACY_WORLD_SAVE_H\r\n\r\n#include \"compat.h\"\r\n\r\n__M_BEGIN_DECLS\r\n\r\n#include \"const.h\"\r\n#include \"world_struct.h\"\r\n#include \"io/memfile.h\"\r\n\r\nsize_t legacy_save_robot_calculate_size(struct world *mzx_world,\r\n struct robot *cur_robot, int savegame, int version);\r\n\r\nvoid legacy_save_robot_to_memory(struct robot *cur_robot, struct memfile *mf,\r\n int savegame, int version);\r\n\r\nvoid legacy_save_robot(struct world *mzx_world, struct robot *cur_robot,\r\n FILE *fp, int savegame, int version);\r\n\r\n\r\nvoid legacy_save_scroll(struct scroll *cur_scroll, FILE *fp, int savegame);\r\n\r\nvoid legacy_save_sensor(struct sensor *cur_sensor, FILE *fp, int savegame);\r\n\r\nint legacy_save_board(struct world *mzx_world,\r\n struct board *cur_board, FILE *fp, int savegame, int version);\r\n\r\nint legacy_save_world(struct world *mzx_world, const char *file, int savegame);\r\n\r\n__M_END_DECLS\r\n\r\n#endif // __LEGACY_WORLD_SAVE_H\r\n"
  },
  {
    "path": "src/old/render_layer_code.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// This file recursively includes itself to apply a sort of pseudo-templating\n// in order to generate many versions of the render_func_... function. The\n// right function to call is selected through a dispatch mechanism, also\n// generated through recursive inclusion.\n\n// Errors and warnings in this file are duplicated hundreds of times over, so\n// when working on this function it can be good to redirect make's stderr\n// stream to a file.\n\n#ifndef RENDERER_BPP\n\n#include \"platform_endian.h\"\n\n#define CONCAT(a,b,c,d,e,f,g,h,i,j,k,l,m) \\\n a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l ## m\n#define RENDER_FUNCTION_NAME(b, t, a, s, c, p) \\\n CONCAT(render_func_, b, bpp_, t, trans_, a, align_, s, smzx_, c, clip_, p, ppal)\n#define STR_HELPER(x) #x\n#define STR(x) STR_HELPER(x)\n\n/**\n * Renderer bits-per-pixel (8, 16, or 32).\n * Sets of renderers for lower bpps are larger since they try to support more\n * alignment options, so several platforms disable them altogether to reduce\n * executable size and/or compilation time.\n */\n\n#ifndef SKIP_8BPP\n#define RENDERER_BPP  8\n#include \"render_layer_code.h\"\n#undef RENDERER_BPP\n#endif\n\n#ifndef SKIP_16BPP\n#define RENDERER_BPP  16\n#include \"render_layer_code.h\"\n#undef RENDERER_BPP\n#endif\n\n#ifndef SKIP_32BPP\n#define RENDERER_BPP  32\n#include \"render_layer_code.h\"\n#undef RENDERER_BPP\n#endif\n\nstatic inline void RENDER_FUNCTION_NAME(X, X, X, X, X, X) (void *pixels,\n Uint32 pitch, struct graphics_data *graphics, struct video_layer *layer,\n int ppal, int clip, int smzx, int align, int trans, int bpp)\n{\n  switch(bpp)\n  {\n#ifndef SKIP_8BPP\n    case 8:\n      RENDER_FUNCTION_NAME(8, X, X, X, X, X)\n       (pixels, pitch, graphics, layer, ppal, clip, smzx, align, trans);\n      break;\n#endif\n#ifndef SKIP_16BPP\n    case 16:\n      RENDER_FUNCTION_NAME(16, X, X, X, X, X)\n       (pixels, pitch, graphics, layer, ppal, clip, smzx, align, trans);\n      break;\n#endif\n#ifndef SKIP_32BPP\n    case 32:\n      RENDER_FUNCTION_NAME(32, X, X, X, X, X)\n       (pixels, pitch, graphics, layer, ppal, clip, smzx, align, trans);\n      break;\n#endif\n    default:\n      fprintf(stderr, \"INVALID RENDERER ARG bpp=%d\\n\"\n       \"(is this bpp enabled for this platform?)\\n\", bpp);\n      exit(1);\n      break;\n  }\n}\n\n#else /* RENDERER_BPP */\n\n/**\n * Layer transparency enabled (1) or disabled (0).\n */\n\n#ifndef RENDERER_TR\n\n#define RENDERER_TR  0\n#include \"render_layer_code.h\"\n#undef RENDERER_TR\n\n#define RENDERER_TR  1\n#include \"render_layer_code.h\"\n#undef RENDERER_TR\n\nstatic inline void RENDER_FUNCTION_NAME(RENDERER_BPP, X, X, X, X, X)\n (void *pixels, Uint32 pitch, struct graphics_data *graphics,\n struct video_layer *layer, int ppal, int clip, int smzx, int align, int trans)\n{\n  switch(trans)\n  {\n    case 0:\n      RENDER_FUNCTION_NAME(RENDERER_BPP, 0, X, X, X, X)\n       (pixels, pitch, graphics, layer, ppal, clip, smzx, align);\n      break;\n\n    case 1:\n      RENDER_FUNCTION_NAME(RENDERER_BPP, 1, X, X, X, X)\n       (pixels, pitch, graphics, layer, ppal, clip, smzx, align);\n      break;\n\n    default:\n      fprintf(stderr, \"INVALID RENDERER ARG trans=%d\\n\", trans);\n      exit(1);\n      break;\n  }\n}\n\n#else /* RENDERER_TR */\n\n/**\n * Alignment of pixel buffer.\n * This always must be >= the current renderer's bits-per-pixel.\n */\n\n#ifndef RENDERER_ALIGN\n\n#if ARCHITECTURE_BITS >= 64 && !defined(SKIP_64_ALIGN)\n#define RENDERER_ALIGN  64\n#include \"render_layer_code.h\"\n#undef RENDERER_ALIGN\n#endif /* ARCHITECTURE_BITS >= 64 */\n\n#if RENDERER_BPP <= 32\n#define RENDERER_ALIGN  32\n#include \"render_layer_code.h\"\n#undef RENDERER_ALIGN\n#if RENDERER_BPP <= 16\n#define RENDERER_ALIGN  16\n#include \"render_layer_code.h\"\n#undef RENDERER_ALIGN\n#if RENDERER_BPP <= 8\n#define RENDERER_ALIGN  8\n#include \"render_layer_code.h\"\n#undef RENDERER_ALIGN\n#endif /* RENDERER_BPP <= 8 */\n#endif /* RENDERER_BPP <= 16 */\n#endif /* RENDERER_BPP <= 32 */\n\nstatic inline void RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, X, X, X, X)\n (void *pixels, Uint32 pitch, struct graphics_data *graphics,\n struct video_layer *layer, int ppal, int clip, int smzx, int align)\n{\n  switch(align)\n  {\n#if ARCHITECTURE_BITS >= 64 && !defined(SKIP_64_ALIGN)\n    case 64:\n      RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, 64, X, X, X)\n       (pixels, pitch, graphics, layer, ppal, clip, smzx);\n      break;\n#endif /* ARCHITECTURE_BITS >= 64 */\n\n#if RENDERER_BPP <= 32\n    case 32:\n      RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, 32, X, X, X)\n       (pixels, pitch, graphics, layer, ppal, clip, smzx);\n      break;\n\n#if RENDERER_BPP <= 16\n    case 16:\n      RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, 16, X, X, X)\n       (pixels, pitch, graphics, layer, ppal, clip, smzx);\n      break;\n\n#if RENDERER_BPP <= 8\n    case 8:\n      RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, 8, X, X, X)\n       (pixels, pitch, graphics, layer, ppal, clip, smzx);\n      break;\n#endif /* RENDERER_BPP <= 8 */\n#endif /* RENDERER_BPP <= 16 */\n#endif /* RENDERER_BPP <= 32 */\n\n    default:\n      fprintf(stderr, \"INVALID RENDERER ARG align=%d bpp=%d\\n\",\n       align, RENDERER_BPP);\n      exit(1);\n      break;\n  }\n}\n\n#else /* RENDERER_ALIGN */\n\n/**\n * Renderer is SMZX (1) or normal MZX (0).\n */\n\n#ifndef RENDERER_SMZX\n\n#define RENDERER_SMZX  0\n#include \"render_layer_code.h\"\n#undef RENDERER_SMZX\n\n#define RENDERER_SMZX  1\n#include \"render_layer_code.h\"\n#undef RENDERER_SMZX\n\nstatic inline void RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, RENDERER_ALIGN, X, X, X)\n (void *pixels, Uint32 pitch, struct graphics_data *graphics,\n struct video_layer *layer, int ppal, int clip, int smzx)\n{\n  switch(smzx)\n  {\n    case 0:\n      RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, RENDERER_ALIGN, 0, X, X)\n       (pixels, pitch, graphics, layer, ppal, clip);\n      break;\n\n    case 1:\n    case 2:\n    case 3:\n      RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, RENDERER_ALIGN, 1, X, X)\n       (pixels, pitch, graphics, layer, ppal, clip);\n      break;\n\n    default:\n      fprintf(stderr, \"INVALID RENDERER ARG smzx=%d\\n\", smzx);\n      exit(1);\n      break;\n  }\n}\n\n#else /* RENDERER_SMZX */\n\n/**\n * Renderer should clip the layer at the screen boundaries (1) or not (0).\n */\n\n#ifndef RENDERER_CLIP\n\n#define RENDERER_CLIP  0\n#include \"render_layer_code.h\"\n#undef RENDERER_CLIP\n\n#define RENDERER_CLIP  1\n#include \"render_layer_code.h\"\n#undef RENDERER_CLIP\n\nstatic inline void RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, RENDERER_ALIGN, RENDERER_SMZX, X, X)\n (void *pixels, Uint32 pitch, struct graphics_data *graphics,\n struct video_layer *layer, int ppal, int clip)\n{\n  switch(clip)\n  {\n    case 0:\n      RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, RENDERER_ALIGN, RENDERER_SMZX, 0, X)\n       (pixels, pitch, graphics, layer, ppal);\n      break;\n\n    case 1:\n      RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, RENDERER_ALIGN, RENDERER_SMZX, 1, X)\n       (pixels, pitch, graphics, layer, ppal);\n      break;\n\n    default:\n      fprintf(stderr, \"INVALID RENDERER ARG clip=%d\\n\", clip);\n      exit(1);\n      break;\n  }\n}\n\n#else /* RENDERER_CLIP */\n\n/**\n * Protected palette offset is 16 or 256.\n * This doesn't necessarily correspond to the current SMZX mode, since layers\n * can be drawn in normal mode while SMZX is technically enabled.\n */\n\n#ifndef RENDERER_PPAL\n\n#define RENDERER_PPAL  256\n#include \"render_layer_code.h\"\n#undef RENDERER_PPAL\n\n#define RENDERER_PPAL  16\n#include \"render_layer_code.h\"\n#undef RENDERER_PPAL\n\nstatic inline void RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, RENDERER_ALIGN, RENDERER_SMZX, RENDERER_CLIP, X)\n (void *pixels, Uint32 pitch, struct graphics_data *graphics,\n struct video_layer *layer, int ppal)\n{\n  switch(ppal)\n  {\n    case 256:\n      RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, RENDERER_ALIGN, RENDERER_SMZX, RENDERER_CLIP, 256)\n       (pixels, pitch, graphics, layer);\n      break;\n\n    case 16:\n      RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, RENDERER_ALIGN, RENDERER_SMZX, RENDERER_CLIP, 16)\n       (pixels, pitch, graphics, layer);\n      break;\n\n    default:\n      fprintf(stderr, \"INVALID RENDERER ARG ppal=%d\\n\", ppal);\n      exit(1);\n      break;\n  }\n}\n\n#else /* RENDERER_PPAL */\n\n#if RENDERER_BPP == 32\n#define PIXTYPE Uint32\n#elif RENDERER_BPP == 16\n#define PIXTYPE Uint16\n#else\n#define PIXTYPE Uint8\n#endif\n\n#if RENDERER_ALIGN == 64\n#define ALIGNTYPE Uint64\n#elif RENDERER_ALIGN == 32\n#define ALIGNTYPE Uint32\n#elif RENDERER_ALIGN == 16\n#define ALIGNTYPE Uint16\n#else\n#define ALIGNTYPE Uint8\n#endif\n\n#define PPW (RENDERER_ALIGN / RENDERER_BPP)\n\n//#pragma message (STR(RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, RENDERER_ALIGN, RENDERER_SMZX, RENDERER_CLIP, RENDERER_PPAL)))\n\nstatic inline void RENDER_FUNCTION_NAME(RENDERER_BPP, RENDERER_TR, RENDERER_ALIGN, RENDERER_SMZX, RENDERER_CLIP, RENDERER_PPAL)\n (PIXTYPE *pixels, Uint32 pitch, struct graphics_data *graphics, struct video_layer *layer)\n{\n  Uint32 mode = RENDERER_SMZX;\n\n  Uint32 ch_x, ch_y;\n  Uint16 c;\n\n  struct char_element *src = layer->data;\n  int row;\n  #if RENDERER_TR\n  int tcol = layer->transparent_col;\n  #if ((PPW > 1) || (RENDERER_SMZX == 0))\n  ALIGNTYPE bgdata;\n  ALIGNTYPE mask = (PIXTYPE)(~0);\n  #endif /* ((PPW > 1) || (RENDERER_SMZX == 0)) */\n  #endif /* RENDERER_TR */\n\n  Uint8 *char_ptr;\n  Uint8 current_char_byte;\n  PIXTYPE pcol;\n\n  ALIGNTYPE *drawPtr;\n  ALIGNTYPE pix;\n\n  ALIGNTYPE *outPtr;\n  Uint32 align_pitch = pitch / sizeof(ALIGNTYPE);\n  Uint32 advance_char = CHAR_W * sizeof(PIXTYPE) / sizeof(ALIGNTYPE);\n  Uint32 advance_char_row = align_pitch * CHAR_H - advance_char * layer->w;\n\n  int i;\n\n  PIXTYPE char_colors[4];\n  int char_idx[4];\n  int write_pos;\n\n  #if RENDERER_CLIP\n  int pixel_x, pixel_y;\n  #endif\n\n  // Position the output ptr at the location of the first char\n  outPtr = (ALIGNTYPE *)pixels;\n  outPtr += layer->y * (int)align_pitch;\n  outPtr += layer->x * (int)sizeof(PIXTYPE) / (int)sizeof(ALIGNTYPE);\n\n  for (ch_y = 0; ch_y < layer->h; ch_y++) {\n    for (ch_x = 0; ch_x < layer->w; ch_x++) {\n      c = src->char_value;\n      #if RENDERER_CLIP\n      pixel_x = layer->x + ch_x * CHAR_W;\n      pixel_y = layer->y + ch_y * CHAR_H;\n      if (pixel_x <= -CHAR_W || pixel_y <= -CHAR_H ||\n          pixel_x >= CHAR_W * SCREEN_W || pixel_y >= CHAR_H * SCREEN_H)\n        c = INVISIBLE_CHAR;\n      #endif\n      if (c != INVISIBLE_CHAR) {\n        // Char values of 256+, prior to offsetting, are from the protected set\n        if (c > 0xFF) {\n          c = (c & 0xFF) + PROTECTED_CHARSET_POSITION;\n        } else {\n          c += layer->offset;\n          c %= PROTECTED_CHARSET_POSITION;\n        }\n\n        if (mode) {\n          for (i = 0; i < 4; i++) {\n            char_idx[i] = graphics->smzx_indices[((src->bg_color & 0xF) << 4 | (src->fg_color & 0xF)) * 4 + i];\n            if (RENDERER_BPP > 8)\n              char_colors[i] = graphics->flat_intensity_palette[char_idx[i]];\n            else\n              char_colors[i] = char_idx[i];\n          }\n        } else {\n          if ((RENDERER_BPP > 8 || RENDERER_PPAL < 240) && src->bg_color >= 16) char_idx[0] = (src->bg_color - 16) % 16 + RENDERER_PPAL;\n          else char_idx[0] = src->bg_color & 0x0F;\n          if ((RENDERER_BPP > 8 || RENDERER_PPAL < 240) && src->fg_color >= 16) char_idx[1] = (src->fg_color - 16) % 16 + RENDERER_PPAL;\n          else char_idx[1] = src->fg_color & 0x0F;\n          for (i = 0; i < 2; i++) {\n            if (RENDERER_BPP > 8)\n              char_colors[i] = graphics->flat_intensity_palette[char_idx[i]];\n            else\n              char_colors[i] = char_idx[i];\n          }\n        }\n        char_ptr = graphics->charset + (c * CHAR_H);\n        drawPtr = outPtr;\n\n        for (row = 0; row < CHAR_H; row++) {\n          current_char_byte = char_ptr[row];\n\n          #if RENDERER_CLIP\n          if (pixel_y + row >= 0 && pixel_y + row < CHAR_H * SCREEN_H)\n          #endif\n          {\n            for (write_pos = 0; write_pos < CHAR_W / PPW; write_pos++) {\n              #if RENDERER_CLIP\n              if (\n                #if RENDERER_SMZX == 0 && PPW == 1\n                pixel_x + write_pos >= 0\n                #else /* RENDERER_SMZX != 0 || PPW != 1*/\n                pixel_x + write_pos >= -1\n                #endif /* RENDERER_SMZX == 0 && PPW == 1 */\n                &&\n                pixel_x + write_pos < CHAR_W * SCREEN_W\n                )\n              #endif /* RENDERER_CLIP */\n              {\n                #if RENDERER_SMZX == 0\n                  #if RENDERER_TR\n                  bgdata = drawPtr[write_pos];\n                  #endif /* RENDERER_TR */\n                  pix = 0;\n                  pcol = (current_char_byte & (0x80 >> (write_pos*PPW+(PPW-1)))) << (write_pos*PPW+(PPW-1)) >> 7;\n                  #if RENDERER_TR\n                  if (char_idx[pcol] == tcol) pix |= bgdata & (mask << RENDERER_BPP*(PPW-1)); else\n                  #endif /* RENDERER_TR */\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-1);\n                  #if PPW > 1\n                  pcol = (current_char_byte & (0x80 >> (write_pos*PPW+(PPW-2)))) << (write_pos*PPW+(PPW-2)) >> 7;\n                  #if RENDERER_TR\n                  if (char_idx[pcol] == tcol) pix |= bgdata & (mask << RENDERER_BPP*(PPW-2)); else\n                  #endif /* RENDERER_TR */\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-2);\n                  #if PPW > 2\n                  pcol = (current_char_byte & (0x80 >> (write_pos*PPW+(PPW-3)))) << (write_pos*PPW+(PPW-3)) >> 7;\n                  #if RENDERER_TR\n                  if (char_idx[pcol] == tcol) pix |= bgdata & (mask << RENDERER_BPP*(PPW-3)); else\n                  #endif /* RENDERER_TR */\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-3);\n                  #if PPW > 3\n                  pcol = (current_char_byte & (0x80 >> (write_pos*PPW+(PPW-4)))) << (write_pos*PPW+(PPW-4)) >> 7;\n                  #if RENDERER_TR\n                  if (char_idx[pcol] == tcol) pix |= bgdata & (mask << RENDERER_BPP*(PPW-4)); else\n                  #endif /* RENDERER_TR */\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-4);\n                  #if PPW > 4\n                  pcol = (current_char_byte & (0x80 >> (write_pos*PPW+(PPW-5)))) << (write_pos*PPW+(PPW-5)) >> 7;\n                  #if RENDERER_TR\n                  if (char_idx[pcol] == tcol) pix |= bgdata & (mask << RENDERER_BPP*(PPW-5)); else\n                  #endif /* RENDERER_TR */\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-5);\n                  #if PPW > 5\n                  pcol = (current_char_byte & (0x80 >> (write_pos*PPW+(PPW-6)))) << (write_pos*PPW+(PPW-6)) >> 7;\n                  #if RENDERER_TR\n                  if (char_idx[pcol] == tcol) pix |= bgdata & (mask << RENDERER_BPP*(PPW-6)); else\n                  #endif /* RENDERER_TR */\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-6);\n                  #if PPW > 6\n                  pcol = (current_char_byte & (0x80 >> (write_pos*PPW+(PPW-7)))) << (write_pos*PPW+(PPW-7)) >> 7;\n                  #if RENDERER_TR\n                  if (char_idx[pcol] == tcol) pix |= bgdata & (mask << RENDERER_BPP*(PPW-7)); else\n                  #endif /* RENDERER_TR */\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-7);\n                  #if PPW > 7\n                  pcol = (current_char_byte & (0x80 >> (write_pos*PPW+(PPW-8)))) << (write_pos*PPW+(PPW-8)) >> 7;\n                  #if RENDERER_TR\n                  if (char_idx[pcol] == tcol) pix |= bgdata & (mask << RENDERER_BPP*(PPW-8)); else\n                  #endif /* RENDERER_TR */\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-8);\n                  #if PPW > 8\n                  #error UNSUPPORTED PPW\n                  #endif /* PPW > 8 */\n                  #endif /* PPW > 7 */\n                  #endif /* PPW > 6 */\n                  #endif /* PPW > 5 */\n                  #endif /* PPW > 4 */\n                  #endif /* PPW > 3 */\n                  #endif /* PPW > 2 */\n                  #endif /* PPW > 1 */\n                #else /* RENDERER_SMZX != 0 */\n                  #if PPW > 1\n                  pix = 0;\n                  #if RENDERER_TR\n                  bgdata = drawPtr[write_pos];\n                  #endif /* RENDERER_TR */\n                  pcol = (current_char_byte & (0xC0 >> (write_pos*PPW+(PPW-2)))) << (write_pos*PPW+(PPW-2)) >> 6;\n\n                  #if RENDERER_TR\n                  if (char_idx[pcol] == tcol) pix |= bgdata & ((mask << RENDERER_BPP*(PPW-1) | (mask << RENDERER_BPP*(PPW-2)))); else\n                  #endif /* RENDERER_TR */\n                  {\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-2);\n                  #if RENDERER_TR\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-1);\n                  #endif /* RENDERER_TR */\n                  }\n                  #if PPW > 2\n                  pcol = (current_char_byte & (0xC0 >> (write_pos*PPW+(PPW-4)))) << (write_pos*PPW+(PPW-4)) >> 6;\n                  #if RENDERER_TR\n                  if (char_idx[pcol] == tcol) pix |= bgdata & ((mask << RENDERER_BPP*(PPW-3) | (mask << RENDERER_BPP*(PPW-4)))); else\n                  #endif /* RENDERER_TR */\n                  {\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-4);\n                  #if RENDERER_TR\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-3);\n                  #endif /* RENDERER_TR */\n                  }\n                  #if PPW > 4\n                  pcol = (current_char_byte & (0xC0 >> (write_pos*PPW+(PPW-6)))) << (write_pos*PPW+(PPW-6)) >> 6;\n                  #if RENDERER_TR\n                  if (char_idx[pcol] == tcol) pix |= bgdata & ((mask << RENDERER_BPP*(PPW-5) | (mask << RENDERER_BPP*(PPW-6)))); else\n                  #endif /* RENDERER_TR */\n                  {\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-6);\n                  #if RENDERER_TR\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-5);\n                  #endif /* RENDERER_TR */\n                  }\n                  #if PPW > 6\n                  pcol = (current_char_byte & (0xC0 >> (write_pos*PPW+(PPW-8)))) << (write_pos*PPW+(PPW-8)) >> 6;\n                  #if RENDERER_TR\n                  if (char_idx[pcol] == tcol) pix |= bgdata & ((mask << RENDERER_BPP*(PPW-7) | (mask << RENDERER_BPP*(PPW-8)))); else\n                  #endif /* RENDERER_TR */\n                  {\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-8);\n                  #if RENDERER_TR\n                  pix |= (ALIGNTYPE)char_colors[pcol] << RENDERER_BPP*(PPW-7);\n                  #endif /* RENDERER_TR */\n                  }\n                  #if PPW > 8\n                  #error UNSUPPORTED PPW\n                  #endif /* PPW > 8 */\n                  #endif /* PPW > 6 */\n                  #endif /* PPW > 4 */\n                  #endif /* PPW > 2 */\n\n                  #if !RENDERER_TR\n                  pix |= (pix << RENDERER_BPP);\n                  #endif /* !RENDERER_TR */\n                  #else /* PPW == 1 */\n                  pcol = (current_char_byte & (0xC0 >> write_pos)) << write_pos >> 6;\n\n\n                  pix = char_colors[pcol];\n                  #if RENDERER_CLIP\n                  if (pixel_x + write_pos >= 0)\n                  #endif /* RENDERER_CLIP */\n                  {\n                  #if RENDERER_TR\n                  if (tcol == char_idx[pcol])\n                    pix = drawPtr[write_pos];\n                  #endif /* RENDERER_TR */\n                  drawPtr[write_pos] = pix;\n                  }\n\n                  write_pos++;\n                  #if RENDERER_TR\n                  if (tcol == char_idx[pcol])\n                    pix = drawPtr[write_pos];\n                  #endif /* RENDERER_TR */\n\n                  #endif /* PPW > 1 */\n\n                #endif /* RENDERER_SMZX == 0 */\n\n                #if RENDERER_CLIP\n                if (pixel_x + write_pos < CHAR_W * SCREEN_W)\n                #endif /* RENDERER_CLIP */\n                drawPtr[write_pos] = pix;\n              }\n            }\n\n          }\n\n          drawPtr += align_pitch;\n        }\n      }\n      src++;\n      outPtr += advance_char;\n    }\n    outPtr += advance_char_row;\n  }\n}\n\n#undef PPW\n#undef ALIGNTYPE\n#undef PIXTYPE\n\n#endif /* RENDERER_PPAL */\n\n#endif /* RENDERER_CLIP */\n\n#endif /* RENDERER_SMZX */\n\n#endif /* RENDERER_ALIGN */\n\n#endif /* RENDERER_TR */\n\n#endif /* RENDERER_BPP */\n"
  },
  {
    "path": "src/platform.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __PLATFORM_H\n#define __PLATFORM_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"platform_endian.h\"\n\n#ifndef CONFIG_SDL\n\n#if defined(CONFIG_WII) || defined(CONFIG_NDS) || defined(CONFIG_3DS) || defined(CONFIG_DREAMCAST)\nint real_main(int argc, char *argv[]);\n#define main real_main\n#endif // CONFIG_WII || CONFIG_NDS || CONFIG_3DS\n\n#endif // !CONFIG_SDL\n\n/**\n * Audio, networking, and other misc. optional features require threading\n * support. Include a platform-specific thread header here if it's available.\n * If not, a dummy implementation will be included.\n *\n * Most of the dummy functions will emit errors when used.\n * If this happens, implement proper threading functions for that platform.\n */\n#ifdef CONFIG_PTHREAD\n#include \"thread_pthread.h\"\n#elif defined(CONFIG_WII)\n#include \"../arch/wii/thread.h\"\n#elif defined(CONFIG_3DS)\n#include \"../arch/3ds/thread.h\"\n#elif defined(CONFIG_DJGPP)\n#include \"../arch/djgpp/thread.h\"\n#elif defined(CONFIG_DREAMCAST)\n#include \"../arch/dreamcast/thread.h\"\n#elif defined(CONFIG_SDL) && !defined(SKIP_SDL)\n#include \"thread_sdl.h\"\n#elif defined(_WIN32) /* Fallback, prefer SDL when possible. */\n#include \"thread_win32.h\"\n#else\n#if defined(CONFIG_NDS)\n#define THREAD_DUMMY_ALLOW_MUTEX\n#endif\n#include \"thread_dummy.h\"\n#endif\n\n#include <stdint.h>\n#include <time.h>\n\n/* Use as (dso_fn_ptr *) to store a loaded void(*)(void) to a function pointer. */\ntypedef void (*dso_fn_ptr)(void);\nstruct dso_library;\n\n/* Initialize a (dso_fn_ptr *) via (void *) to avoid strict aliasing warnings. */\nunion dso_fn_ptr_ptr\n{\n  void *in;\n  dso_fn_ptr *value;\n};\n\n/* SDL1/2, dlopen return void * instead of a function pointer. */\nunion dso_suppress_warning\n{\n  void *in;\n  dso_fn_ptr out;\n};\n\nstruct dso_syms_map\n{\n  const char *name;\n  union dso_fn_ptr_ptr sym_ptr;\n};\n\n#define DSO_MAP_END { NULL, { NULL }}\n\nCORE_LIBSPEC void delay(uint32_t ms);\nCORE_LIBSPEC uint64_t get_ticks(void);\nCORE_LIBSPEC boolean platform_init(void);\nCORE_LIBSPEC void platform_quit(void);\nCORE_LIBSPEC boolean platform_system_time(struct tm *tm,\n int64_t *epoch, int32_t *nano);\n\nCORE_LIBSPEC struct dso_library *platform_load_library(const char *name);\nCORE_LIBSPEC void platform_unload_library(struct dso_library *library);\nCORE_LIBSPEC boolean platform_load_function(struct dso_library *library,\n const struct dso_syms_map *syms_map);\n\n__M_END_DECLS\n\n#endif // __PLATFORM_H\n"
  },
  {
    "path": "src/platform_attribute.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __PLATFORM_ATTRIBUTE_H\n#define __PLATFORM_ATTRIBUTE_H\n\n/**\n * Header for misc. compiler-specific attributes.\n */\n\n#ifdef __has_attribute\n#define HAS_ATTRIBUTE(x) __has_attribute(x)\n#else\n#define HAS_ATTRIBUTE(x) 0\n#endif\n\n/**\n * Catch-all for GCC printf instrumentation. GCC will emit warnings\n * if this attribute is not used on printf-like functions.\n */\n#if (defined(__GNUC__) && !defined(__clang__)) || HAS_ATTRIBUTE(format)\n#if __GNUC__ >= 5 || (__GNUC__ == 4 &&  __GNUC_MINOR__ >= 4)\n\n// Note: param numbers are 1-indexed for normal functions and\n// 2-indexed for member functions (including constructors).\n#define ATTRIBUTE_PRINTF(string_index, first_to_check) \\\n __attribute__((format(gnu_printf, string_index, first_to_check)))\n\n#else\n\n#define ATTRIBUTE_PRINTF(string_index, first_to_check) \\\n __attribute__((format(printf, string_index, first_to_check)))\n\n#endif\n#endif\n\n#ifndef ATTRIBUTE_PRINTF\n#define ATTRIBUTE_PRINTF(string_index, first_to_check)\n#endif\n\n/**\n * Turn off sanitizer instrumentation for a particular location.\n */\n#if (defined(__GNUC__) && __GNUC__ >= 9) || \\\n (defined(__clang__) && HAS_ATTRIBUTE(no_sanitize))\n#define ATTRIBUTE_NO_SANITIZE(...) __attribute__((no_sanitize(__VA_ARGS__)))\n#endif\n\n#ifndef ATTRIBUTE_NO_SANITIZE\n#define ATTRIBUTE_NO_SANITIZE(...)\n#endif\n\n#undef HAS_ATTRIBUTE\n\n#endif // __PLATFORM_ATTRIBUTE_H\n"
  },
  {
    "path": "src/platform_dummy.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Below is a very simple example of the stubs you must implement to port\n// to another non-SDL platform. Please note that this file exists to have\n// non-SDL builds compile, but is in no way functional.\n\n#include \"platform.h\"\n#include \"event.h\"\n\n#ifdef CONFIG_AUDIO\n#include \"audio/audio.h\"\n#endif\n\n#include <sys/time.h>\n\n#include <unistd.h>\n#include <stdio.h>\n#include <time.h>\n\nvoid delay(uint32_t ms)\n{\n  usleep(1000 * ms);\n}\n\nuint64_t get_ticks(void)\n{\n  struct timeval tv;\n\n  if(gettimeofday(&tv, NULL) < 0)\n  {\n    perror(\"gettimeofday\");\n    return 0;\n  }\n\n  return (uint64_t)(tv.tv_sec * 1000 + tv.tv_usec / 1000);\n}\n\nboolean platform_init(void)\n{\n  // stub\n  return true;\n}\n\nvoid platform_quit(void)\n{\n  // stub\n}\n\nvoid platform_init_event(void)\n{\n  // stub\n}\n\nboolean platform_has_screen_keyboard(void)\n{\n  // stub\n  return false;\n}\n\nboolean platform_show_screen_keyboard(void)\n{\n  // stub\n  return false;\n}\n\nboolean platform_hide_screen_keyboard(void)\n{\n  // stub\n  return false;\n}\n\nboolean platform_is_screen_keyboard_active(void)\n{\n  // stub\n  return false;\n}\n\nboolean __update_event_status(void)\n{\n  // stub\n  return false;\n}\n\nboolean __peek_exit_input(void)\n{\n  // stub\n  return false;\n}\n\nvoid __wait_event(void)\n{\n  // stub\n}\n\nvoid __warp_mouse(int x, int y)\n{\n  // stub\n}\n\n#ifdef CONFIG_AUDIO\n\nvoid init_audio_platform(struct config_info *conf)\n{\n  // stub\n}\n\nvoid quit_audio_platform(void)\n{\n  // stub\n}\n\n#endif // CONFIG_AUDIO\n"
  },
  {
    "path": "src/platform_endian.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk\n * Copyright (C) 2020, 2024-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __ENDIAN_H\n#define __ENDIAN_H\n\n/* Specific architecture defines. */\n#if defined(__x86_64__) || defined(_M_AMD64) || defined(_M_X64)\n#define PLATFORM_IS_X86_64\n#endif\n#if defined(__i386__) || defined(_M_IX86) || defined(PLATFORM_IS_X86_64)\n#define PLATFORM_IS_X86\n#endif\n\n#if defined(__aarch64__) || defined(_M_ARM64)\n#define PLATFORM_IS_ARM64\n#endif\n#if defined(__arm__) || defined(_M_ARM) || defined(PLATFORM_IS_ARM64)\n#define PLATFORM_IS_ARM\n#endif\n\n#if defined(__powerpc64__) || defined(__PPC64__) || \\\n defined(__ppc64__) || defined(_ARCH_PPC64)\n#define PLATFORM_IS_PPC64\n#endif\n#if defined(__powerpc__) || defined(__PPC__) || \\\n defined(__ppc__) || defined(_ARCH_PPC) || defined(__POWERPC__) || \\\n defined(PLATFORM_IS_PPC64)\n#define PLATFORM_IS_PPC\n#endif\n\n#if defined(__m68k__) || defined(mc68000) || defined(_M_M68K)\n#define PLATFORM_IS_M68K\n#endif\n\n/* Use GCC/clang or a list of architectures (both checks borrowed from SDL) to\n * determine the endianness. If SDL is enabled, platform_sdl.c will check this.\n */\n\n#define PLATFORM_LIL_ENDIAN 0x1234\n#define PLATFORM_BIG_ENDIAN 0x4321\n\n#if defined(__BIG_ENDIAN__)\n#define PLATFORM_BYTE_ORDER PLATFORM_BIG_ENDIAN\n#elif defined(__LITTLE_ENDIAN__)\n#define PLATFORM_BYTE_ORDER PLATFORM_LIL_ENDIAN\n/* predefs from newer gcc and clang versions: */\n#elif defined(__ORDER_LITTLE_ENDIAN__) && defined(__ORDER_BIG_ENDIAN__) && defined(__BYTE_ORDER__)\n#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__\n#define PLATFORM_BYTE_ORDER PLATFORM_LIL_ENDIAN\n#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__\n#define PLATFORM_BYTE_ORDER PLATFORM_BIG_ENDIAN\n#else\n#error Unsupported endianness\n#endif /**/\n#elif defined(__hppa__) || \\\n    defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \\\n    ((defined(__mips__) || defined(__mips) || defined(__MIPS__)) && defined(__MIPSEB__)) || \\\n    defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \\\n    defined(__s390__) || defined(__s390x__) || defined(__zarch__) || defined(__SYSC_ZARCH__) || \\\n    defined(__sparc__)\n#define PLATFORM_BYTE_ORDER PLATFORM_BIG_ENDIAN\n#else\n#define PLATFORM_BYTE_ORDER PLATFORM_LIL_ENDIAN\n#endif\n\n/* ModPlug and XMP both use this name to find out about endianness. It's not\n * too bad to pollute our namespace with it, so just do so here.\n */\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n#define WORDS_BIGENDIAN\n#endif\n\n/**\n * Also try to get the platform bit width.\n * Emscripten natively supports 64-bit math when compiling to Wasm.\n */\n#if defined(_WIN64) || defined(__EMSCRIPTEN__) || \\\n  defined(PLATFORM_IS_X86_64) || \\\n  defined(PLATFORM_IS_PPC64) || \\\n  defined(PLATFORM_IS_ARM64) || \\\n  (defined(__sparc__) && defined(__arch64__)) || \\\n  ((defined(__riscv) || defined(__riscv__)) && __riscv_xlen >= 64) || \\\n  ((defined(__mips__) || defined(__mips) || defined(__MIPS__)) && \\\n    defined(_MIPS_SIM) && defined(_ABI64) && _MIPS_SIM == _ABI64) || \\\n  (defined(__loongarch__) && defined(__loongarch_grlen) && \\\n    __loongarch_grlen == 64) || \\\n  (defined(__GNUC__) && \\\n     (defined(__alpha__) || \\\n     defined(__s390x__) || defined(__zarch__)))\n#define ARCHITECTURE_BITS 64\n#else\n#define ARCHITECTURE_BITS 32\n#endif\n\n/**\n * Also define some useful constants for byte alignment.\n * The ALIGN macros are for algorithms attempting to keep as aligned as useful.\n *\n * The UNALIGN macros are for algorithms where fast unaligned accesses are\n * more beneficial than keeping strictly aligned. For platforms where unaligned\n * accesses may result in a SIGBUS (ARM), or where accesses technically are\n * supported but are too slow to be worthwhile (e.g. RISC-V), do not define\n * the UNALIGN macros. (This includes ARMv8+, where alignment exceptions still\n * exist when no MMU is present or by activating a processor flag.)\n */\n#define ALIGN_16_MODULO 0x02\n\n/**\n * Some 32-bit-capable processors (such as the Motorola 68000) still align\n * their data to 16-bit boundaries.\n */\n#ifdef PLATFORM_IS_M68K\n#define ALIGN_32_MODULO ALIGN_16_MODULO\n#else\n#define ALIGN_32_MODULO 0x04\n#endif\n\n#if ARCHITECTURE_BITS >= 64\n#define ALIGN_64_MODULO 0x08\n#else\n#define ALIGN_64_MODULO ALIGN_32_MODULO\n#endif\n\n/* x86, x86-64, and Wasm can access 32-bit and 64-bit integers unaligned.\n * This is slower than aligned accesses but is useful for rendering. */\n#if defined(PLATFORM_IS_X86) || defined(__EMSCRIPTEN__)\n#define PLATFORM_UNALIGN_32 0x01\n#if ARCHITECTURE_BITS >= 64\n#define PLATFORM_UNALIGN_64 0x01\n#endif\n#endif\n\n/* ARMv7+ can access 32-bit (AArch64: and 64-bit) integers unaligned if\n * the system allows this. This is almost always (but not necessarily) enabled\n * for AArch64, and sometimes for ARMv7 and AArch32. Currently trusting\n * __ARM_FEATURE_UNALIGNED to be accurate for any relevant environment.\n * This is currently disabled for 32-bit ARM because clang 19 -O3 will emit\n * STM instructions that trap even if __ARM_FEATURE_UNALIGNED is defined. */\n#if defined(PLATFORM_IS_ARM64) && defined(__ARM_FEATURE_UNALIGNED)\n#define PLATFORM_UNALIGN_32 0x01\n#if ARCHITECTURE_BITS >= 64\n#define PLATFORM_UNALIGN_64 0x01\n#endif\n#endif\n\n/* PowerPC can access 32-bit ints unaligned, but 64-bit ints must be aligned.\n * POWER8 and up have safe unaligned access for 64-bit ints. */\n#if defined(PLATFORM_IS_PPC)\n#define PLATFORM_UNALIGN_32 0x01\n#if defined(_ARCH_PWR8)\n#define PLATFORM_UNALIGN_64 0x01\n#endif\n#endif\n\n/* Motorola 68000 has limited unaligned safety, see above. */\n#ifdef PLATFORM_IS_M68K\n#define PLATFORM_UNALIGN_32 0x02\n#endif\n\n#endif // __ENDIAN_H\n"
  },
  {
    "path": "src/platform_sdl.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2008 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2008 Simon Parzer <simon.parzer@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"SDLmzx.h\"\n#include \"platform.h\"\n#include \"util.h\"\n\n#ifdef CONFIG_PSP\n#include <psppower.h>\n#endif\n\n#ifdef CONFIG_GP2X\n#include <unistd.h> //for chdir, execl\n#endif\n\n#ifdef CONFIG_WII\n#include <sys/iosupport.h>\n#include <fat.h>\n#endif\n\n/* Verify that SDL's endianness matches what platform_endian.h claims... */\n#if SDL_BYTEORDER == SDL_BIG_ENDIAN && PLATFORM_BYTE_ORDER != PLATFORM_BIG_ENDIAN\n#error Endian mismatch: SDL detected big, but MZX detected little. Report this!\n#endif\n#if SDL_BYTEORDER == SDL_LIL_ENDIAN && PLATFORM_BYTE_ORDER != PLATFORM_LIL_ENDIAN\n#error Endian mismatch: SDL detected little, but MZX detected big. Report this!\n#endif\n\n#ifdef __EMSCRIPTEN__\n#include <emscripten.h>\nvoid delay(uint32_t ms)\n{\n  emscripten_sleep(ms);\n}\n#else\nvoid delay(uint32_t ms)\n{\n  SDL_Delay(ms);\n}\n#endif\n\nuint64_t get_ticks(void)\n{\n  // SDL_GetTicks returns a 64-bit value in SDL3, but\n  // SDL_GetTicks64 had to be used prior to that.\n#if SDL_VERSION_ATLEAST(2,0,18) && !SDL_VERSION_ATLEAST(3,0,0)\n  return SDL_GetTicks64();\n#else\n  return SDL_GetTicks();\n#endif\n}\n\n#if SDL_VERSION_ATLEAST(3,0,0)\ntypedef SDL_SharedObject *dso_library_ptr;\n#else\ntypedef void *dso_library_ptr;\n#endif\n\n/* Note: for OpenGL, use (SDL_)GL_LoadLibrary instead. */\nstruct dso_library *platform_load_library(const char *name)\n{\n  dso_library_ptr handle = SDL_LoadObject(name);\n  if(handle)\n    return (struct dso_library *)handle;\n  return NULL;\n}\n\nvoid platform_unload_library(struct dso_library *library)\n{\n  dso_library_ptr handle = (dso_library_ptr)library;\n  SDL_UnloadObject(handle);\n}\n\n/* Note: for OpenGL, use (SDL_)GL_GetProcAddress (via gl_load_syms) instead. */\nboolean platform_load_function(struct dso_library *library,\n const struct dso_syms_map *syms_map)\n{\n  dso_library_ptr handle = (dso_library_ptr)library;\n\n#if SDL_VERSION_ATLEAST(3,0,0)\n  SDL_FunctionPointer *dest = (SDL_FunctionPointer *)syms_map->sym_ptr.value;\n#else\n  void **dest = (void **)syms_map->sym_ptr.in;\n#endif\n  if(!syms_map->name || !dest)\n    return false;\n\n  *dest = SDL_LoadFunction(handle, syms_map->name);\n  if(!*dest)\n  {\n    debug(\"--DSO--- failed to load: %s\\n\", syms_map->name);\n    return false;\n  }\n  return true;\n}\n\n#ifdef __WIN32__\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#include <windows.h>\n\n/**\n * Set DPI awareness for Windows to avoid bad window scaling and various\n * fullscreen-related bugs. This function was added in Windows Vista so it\n * needs to be dynamically loaded.\n */\nstatic void set_dpi_aware(void)\n{\n  BOOL (*_SetProcessDPIAware)(void) = NULL;\n\n  struct dso_library *handle = platform_load_library(\"User32.dll\");\n  if(handle)\n  {\n    struct dso_syms_map sym = { \"SetProcessDPIAware\", { &_SetProcessDPIAware } };\n\n    if(platform_load_function(handle, &sym) && !_SetProcessDPIAware())\n    {\n      warn(\"failed to SetProcessDPIAware!\\n\");\n    }\n    else\n\n    if(!_SetProcessDPIAware)\n      debug(\"couldn't load SetProcessDPIAware.\\n\");\n\n    platform_unload_library(handle);\n  }\n  else\n    debug(\"couldn't load User32.dll: %s\\n\", SDL_GetError());\n}\n#endif\n\nstatic inline boolean sdl_init(Uint32 flags)\n{\n#if SDL_VERSION_ATLEAST(3,0,0)\n  return SDL_Init(flags);\n#else\n  return SDL_Init(flags) >= 0;\n#endif\n}\n\nboolean platform_init(void)\n{\n  Uint32 flags = SDL_INIT_VIDEO | SDL_INIT_JOYSTICK;\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n  flags |= SDL_INIT_GAMEPAD;\n#endif\n\n#ifdef CONFIG_PSP\n  scePowerSetClockFrequency(333, 333, 166);\n#endif\n\n#ifdef CONFIG_WII\n  if(!fatInitDefault())\n    return false;\n#endif\n\n#if defined(DEBUG) && !SDL_VERSION_ATLEAST(3,0,0)\n  // Removed in SDL 3.\n  flags |= SDL_INIT_NOPARACHUTE;\n#endif\n\n#ifdef CONFIG_AUDIO\n  flags |= SDL_INIT_AUDIO;\n#endif\n\n#ifdef __WIN32__\n  set_dpi_aware();\n#endif\n\n  if(!sdl_init(flags))\n  {\n    debug(\"Failed to initialize SDL; attempting with joystick support disabled: %s\\n\", SDL_GetError());\n\n    // try again without joystick support\n    flags &= ~SDL_INIT_JOYSTICK;\n#if SDL_VERSION_ATLEAST(2,0,0)\n    flags &= ~SDL_INIT_GAMEPAD;\n#endif\n\n    if(!sdl_init(flags))\n    {\n      warn(\"Failed to initialize SDL: %s\\n\", SDL_GetError());\n      return false;\n    }\n  }\n  return true;\n}\n\nvoid platform_quit(void)\n{\n  SDL_Quit();\n\n#ifdef CONFIG_WII\n  {\n    int i;\n\n    for(i = 0; i < STD_MAX; i++)\n      if(devoptab_list[i] && devoptab_list[i]->chdir_r)\n        fatUnmount(devoptab_list[i]->name);\n  }\n#endif\n\n#ifdef CONFIG_GP2X\n  chdir(\"/usr/gp2x\");\n  execl(\"/usr/gp2x/gp2xmenu\", \"/usr/gp2x/gp2xmenu\", NULL);\n#endif\n}\n"
  },
  {
    "path": "src/platform_time.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2023-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"platform.h\"\n#include \"util.h\"\n\n#include <time.h>\n\n#ifndef _MSC_VER\n#include <unistd.h> /* _POSIX_TIMERS */\n#include <sys/time.h> /* gettimeofday */\n#endif\n\n#if defined(CONFIG_DREAMCAST) && defined(_POSIX_TIMERS)\n// KallistiOS may incorrectly define this to nothing when\n// it should leave it undefined.\n#undef _POSIX_TIMERS\n#endif\n\n#ifdef _WIN32\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#include <windows.h>\n#include \"SDLmzx.h\"\n\n#define WINDOWS_TO_UNIX_SECONDS 11644473600LL\n#define WINDOWS_TO_UNIX_100NS   10000000LL\n\nstatic void convert_timestamp(int64_t *epoch, int32_t *nano,\n DWORD high, DWORD low)\n{\n  uint64_t full = ((uint64_t)high << 32) | (uint64_t)low;\n\n  *epoch = (full / WINDOWS_TO_UNIX_100NS) - WINDOWS_TO_UNIX_SECONDS;\n  *nano = (full % WINDOWS_TO_UNIX_100NS) * 100;\n}\n\n/**\n * Get the system timestamp using Win32 function calls directly.\n */\nstatic boolean system_time_win32(int64_t *epoch, int32_t *nano)\n{\n  static struct dso_library *dll;\n  static void (WINAPI *_GetSystemTimePreciseAsFileTime)(LPFILETIME) = NULL;\n  static const struct dso_syms_map sym =\n   { \"GetSystemTimePreciseAsFileTime\", { &_GetSystemTimePreciseAsFileTime } };\n  static boolean init = false;\n  FILETIME ft;\n\n  if(!init)\n  {\n    // Note: can't unload, or the function pointer will no longer be valid.\n    dll = platform_load_library(\"Kernel32.dll\");\n    if(dll)\n      platform_load_function(dll, &sym);\n\n    init = true;\n  }\n\n  if(_GetSystemTimePreciseAsFileTime)\n  {\n    /* Windows 8, precise to 100ns. */\n    _GetSystemTimePreciseAsFileTime(&ft);\n  }\n  else\n  {\n    /* Windows 95, NT 3.5; precision not well defined. */\n    GetSystemTimeAsFileTime(&ft);\n  }\n\n  convert_timestamp(epoch, nano, ft.dwHighDateTime, ft.dwLowDateTime);\n  return true;\n}\n#endif /* _WIN32 */\n\n/**\n * Get the system timestamp via `clock_gettime(CLOCK_REALTIME)`.\n * Disable for MinGW (implemented by winpthread); not supported by MSVC.\n * TODO: glibc prior to 2.17 requires manually linking librt, which can't\n * be linked safely currently (no equivalent to autoconf to check for it).\n */\nstatic boolean system_time_clock_gettime(int64_t *epoch, int32_t *nano)\n{\n#if !defined(_WIN32) && defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && \\\n (!defined(__GLIBC__) || !defined(__GLIBC_MINOR__) || __GLIBC__ >= 3 || \\\n  (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17))\n  struct timespec tp;\n\n  if(clock_gettime(CLOCK_REALTIME, &tp))\n    return false;\n\n  *epoch = tp.tv_sec;\n  *nano = (unsigned long)tp.tv_nsec % 1000000000;\n  return true;\n#else\n  return false;\n#endif\n}\n\n/**\n * Get the system timestamp via `gettimeofday`, which exists in most libc\n * implementations. Safe for MinGW (implemented by MinGW), not for MSVC.\n */\nstatic boolean system_time_gettimeofday(int64_t *epoch, int32_t *nano)\n{\n#ifndef _MSC_VER\n  struct timeval tv;\n\n  if(gettimeofday(&tv, NULL))\n    return false;\n\n  *epoch = tv.tv_sec;\n  *nano = ((unsigned long)tv.tv_usec % 1000000) * 1000;\n  return true;\n#else\n  return false;\n#endif\n}\n\n/**\n * Use `time` as a fallback :(. Modern implementations, even for 32-bit\n * platforms, typically use a 64-bit `time_t` now.\n */\nstatic void system_time_fallback(int64_t *epoch, int32_t *nano)\n{\n  *epoch = time(NULL);\n  *nano = 0;\n}\n\n/**\n * Get the system clock time in the system clock timezone.\n * All parameters must be present and will be written to.\n *\n * @param tm    destination for `localtime` info. May be 0-filled on failure.\n * @param epoch destination for seconds since Jan 1 1970.\n * @param nano  destination for nanoseconds since Jan 1 1970.\n * @return      `true` if all fields are filled and have microsecond precision,\n *              otherwise `false`. Nanosecond precision is not reliable.\n */\nboolean platform_system_time(struct tm *tm, int64_t *epoch, int32_t *nano)\n{\n  struct tm *local;\n  boolean ret;\n  time_t tmp;\n\n#ifdef _WIN32\n  if(system_time_win32(epoch, nano))\n  {\n    ret = true;\n  }\n  else\n#endif\n\n  if(system_time_clock_gettime(epoch, nano))\n  {\n    ret = true;\n  }\n  else\n\n  if(system_time_gettimeofday(epoch, nano))\n  {\n    ret = true;\n  }\n  else\n  {\n    system_time_fallback(epoch, nano);\n    ret = false;\n  }\n\n  tmp = *epoch;\n  local = localtime(&tmp);\n  if(local)\n  {\n    memcpy(tm, local, sizeof(struct tm));\n    return ret;\n  }\n  else\n  {\n    memset(tm, 0, sizeof(struct tm));\n    return false;\n  }\n}\n"
  },
  {
    "path": "src/pngops.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2024-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"graphics.h\"\n#include \"pngops.h\"\n#include \"util.h\"\n#include \"io/vio.h\"\n\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#ifdef NEED_PNG_WRITE_SCREEN\n\n#ifdef CONFIG_PNG\n\n#include <png.h>\n\n/* Prior to libpng 1.5.16, png_write_row takes a non-const pointer despite\n * it never modifying the row data. In some very old libpng versions, the\n * type png_const_bytep does not even exist (encountered in 1.2.49). */\n#if defined(PNG_LIBPNG_VER) && PNG_LIBPNG_VER >= 10516\ntypedef png_const_bytep png_maybeconst_bytep;\n#else\ntypedef png_bytep png_maybeconst_bytep;\n#endif\n\nstatic void png_write_vfile(png_struct *png_ptr, png_byte *data, png_size_t length)\n{\n  vfile *vf = (vfile *)png_get_io_ptr(png_ptr);\n  if(vfwrite(data, 1, length, vf) < length)\n    png_error(png_ptr, \"write error\");\n}\n\nstatic void png_flush_vfile(png_struct *png_ptr) {}\n\n/* Trivial PNG dumper; this routine is a modification (simplification) of\n * code pinched from http://www2.autistici.org/encelo/prog_sdldemos.php.\n *\n * Palette support was added, the original support was broken.\n *\n * Copyright (C) 2006 Angelo \"Encelo\" Theodorou\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n */\n/*\nint png_write_image_8bpp(const char *name, size_t w, size_t h,\n const struct rgb_color *pal, unsigned count, void *priv,\n const uint8_t *(*row_pixels_callback)(size_t num_pixels, void *priv))\n{\n  png_structp png_ptr = NULL;\n  png_infop info_ptr = NULL;\n  png_colorp volatile pal_ptr = NULL;\n  int volatile ret = false;\n  int type;\n  size_t i;\n  vfile *vf;\n\n  vf = vfopen_unsafe(name, \"wb\");\n  if(!vf)\n    goto exit_out;\n\n  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);\n  if(!png_ptr)\n    goto exit_close;\n\n  info_ptr = png_create_info_struct(png_ptr);\n  if(!info_ptr)\n    goto exit_free_close;\n\n  if(setjmp(png_jmpbuf(png_ptr)))\n    goto exit_free_close;\n\n  png_set_write_fn(png_ptr, vf, png_write_vfile, png_flush_vfile);\n\n  // we know we have an 8-bit surface; save a palettized PNG\n  type = PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE;\n  png_set_IHDR(png_ptr, info_ptr, w, h, 8, type,\n   PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,\n   PNG_FILTER_TYPE_DEFAULT);\n\n  pal_ptr = (png_colorp)cmalloc(count * sizeof(png_color));\n  if(!pal_ptr)\n    goto exit_free_close;\n\n  for(i = 0; i < count; i++)\n  {\n    pal_ptr[i].red = pal[i].r;\n    pal_ptr[i].green = pal[i].g;\n    pal_ptr[i].blue = pal[i].b;\n  }\n  png_set_PLTE(png_ptr, info_ptr, pal_ptr, count);\n\n  // do the write of the header\n  png_write_info(png_ptr, info_ptr);\n  png_set_packing(png_ptr);\n\n  // and then the surface\n  for(i = 0; i < h; i++)\n  {\n    const uint8_t *row = row_pixels_callback(w, priv);\n    if(!row)\n      goto exit_free_close;\n\n    png_write_row(png_ptr, (png_maybeconst_bytep)row);\n  }\n  png_write_end(png_ptr, info_ptr);\n\n  // all done\n  ret = true;\n\nexit_free_close:\n  png_destroy_write_struct(&png_ptr, &info_ptr);\n  free(pal_ptr);\nexit_close:\n  vfclose(vf);\nexit_out:\n  return ret;\n}\n*/\n\nint png_write_image_32bpp(const char *name, size_t w, size_t h, void *priv,\n const uint32_t *(*row_pixels_callback)(size_t num_pixels, void *priv))\n{\n  png_structp png_ptr = NULL;\n  png_infop info_ptr = NULL;\n  int volatile ret = false;\n  int type;\n  size_t i;\n  vfile *vf;\n\n  vf = vfopen_unsafe(name, \"wb\");\n  if(!vf)\n    goto exit_out;\n\n  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);\n  if(!png_ptr)\n    goto exit_close;\n\n  info_ptr = png_create_info_struct(png_ptr);\n  if(!info_ptr)\n    goto exit_free_close;\n\n  if(setjmp(png_jmpbuf(png_ptr)))\n    goto exit_free_close;\n\n  png_set_write_fn(png_ptr, vf, png_write_vfile, png_flush_vfile);\n\n  // 24-bit png\n  type = PNG_COLOR_TYPE_RGB;\n  png_set_IHDR(png_ptr, info_ptr, w, h, 8, type,\n   PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,\n   PNG_FILTER_TYPE_DEFAULT);\n\n  // do the write of the header\n  png_write_info(png_ptr, info_ptr);\n  png_set_packing(png_ptr);\n\n  // our surface is 32bpp ABGR, so set up filler and order\n  png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);\n  //png_set_bgr(png_ptr);\n\n  // and then the surface\n  for(i = 0; i < h; i++)\n  {\n    const uint32_t *row = row_pixels_callback(w, priv);\n    if(!row)\n      goto exit_free_close;\n\n    png_write_row(png_ptr, (png_maybeconst_bytep)row);\n  }\n  png_write_end(png_ptr, info_ptr);\n\n  // all done\n  ret = true;\n\nexit_free_close:\n  png_destroy_write_struct(&png_ptr, &info_ptr);\nexit_close:\n  vfclose(vf);\nexit_out:\n  return ret;\n}\n\n#else /* !CONFIG_PNG */\n\n/* Much more limited fallback PNG writer for builds without libpng.\n * This has fewer features than libpng but, since MegaZeux already relies\n * on zlib, it doesn't increase binary size by much more than a BMP writer.\n * Unlike a BMP writer, it allows large board/vlayer images to be compressed.\n */\n#include <zlib.h>\n\n#define MAGIC_BE(buf, str) do { \\\n  (buf)[0] = (str)[0]; \\\n  (buf)[1] = (str)[1]; \\\n  (buf)[2] = (str)[2]; \\\n  (buf)[3] = (str)[3]; \\\n} while(0)\n\n#define PUT32_BE(buf, val) do { \\\n  (buf)[0] = (val) >> 24; \\\n  (buf)[1] = (val) >> 16; \\\n  (buf)[2] = (val) >> 8; \\\n  (buf)[3] = (val); \\\n} while(0)\n\n#define DEFLATE_OUT(p) do { \\\n  z.next_out = tmp2; \\\n  z.avail_out = sizeof(tmp2); \\\n  ret = deflate(&z, p); \\\n  if(ret < 0) \\\n    goto err2; \\\n  sz = sizeof(tmp2) - z.avail_out; \\\n  len += sz; \\\n  crc = crc32(crc, tmp2, sz); \\\n  if(vfwrite(tmp2, 1, sz, vf) < sz) \\\n    goto err2; \\\n} while(0)\n\nint png_write_image_32bpp(const char *name, size_t w, size_t h, void *priv,\n const uint32_t *(*row_pixels_callback)(size_t num_pixels, void *priv))\n{\n  uint8_t tmp[1029];\n  uint8_t tmp2[1024];\n  uint32_t crc;\n  uint32_t len = 0;\n  size_t x, y, i, sz;\n  int ret;\n  z_stream z;\n\n  vfile *vf = vfopen_unsafe(name, \"w+b\");\n  if(!vf)\n    return false;\n\n  memset(&z, 0, sizeof(z));\n  if(deflateInit2(&z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15, 8, Z_FILTERED) != Z_OK)\n    if(deflateInit2(&z, Z_BEST_SPEED, Z_DEFLATED, 9, 1, Z_RLE) != Z_OK)\n      goto err;\n\n  vfputs(\"\\x89PNG\\r\\n\\x1a\\n\", vf);\n  PUT32_BE(tmp, 13);\n  MAGIC_BE(tmp + 4, \"IHDR\");\n  PUT32_BE(tmp + 8, w);\n  PUT32_BE(tmp + 12, h);\n  tmp[16] = 8; // 8 bits per component\n  tmp[17] = 6; // RGB\n  tmp[18] = 0; // deflate\n  tmp[19] = 0; // filter 0\n  tmp[20] = 0; // no interlace\n  crc = crc32(0, tmp + 4, 17);\n  PUT32_BE(tmp + 21, crc);\n  vfwrite(tmp, 25, 1, vf);\n\n  vfputd(0, vf);\n  MAGIC_BE(tmp, \"IDAT\");\n  crc = crc32(0, tmp, 4);\n  vfwrite(tmp, 4, 1, vf);\n  for(y = 0; y < h; y++)\n  {\n    const uint8_t *row = (const uint8_t *)row_pixels_callback(w, priv);\n    uint8_t *pos = tmp + 5;\n    if(!row)\n      goto err2;\n\n    tmp[0] = 1; // left filter\n    for(i = 0; i < 4; i++)\n      tmp[i + 1] = row[i];\n\n    for(x = 1; x < w;)\n    {\n      size_t num = MIN(w - x, 256);\n      x += num;\n      // Prefilter: since it's always left filter RGBA, delta with the\n      // same component of the previous pixel (the value 4 bytes ago).\n      while(num)\n      {\n        for(i = 0; i < 4; i++)\n          pos[i] = row[i + 4] - row[i];\n        pos += 4;\n        row += 4;\n        num--;\n      }\n\n      z.next_in = tmp;\n      z.avail_in = pos - tmp;\n      do\n      {\n        DEFLATE_OUT(Z_NO_FLUSH);\n      } while(z.avail_out == 0);\n      pos = tmp;\n    }\n  }\n  do\n  {\n    DEFLATE_OUT(Z_FINISH);\n  } while(ret != Z_STREAM_END);\n  deflateEnd(&z);\n\n  PUT32_BE(tmp, crc);\n  vfwrite(tmp, 4, 1, vf);\n\n  vfputd(0, vf);\n  MAGIC_BE(tmp, \"IEND\");\n  crc = crc32(0, tmp, 4);\n  PUT32_BE(tmp + 4, crc);\n  vfwrite(tmp, 8, 1, vf);\n\n  vfseek(vf, 33, SEEK_SET);\n  PUT32_BE(tmp, len);\n  vfwrite(tmp, 4, 1, vf);\n  vfclose(vf);\n  return true;\n\nerr2:\n  deflateEnd(&z);\nerr:\n  vfclose(vf);\n  return false;\n}\n#endif /* !CONFIG_PNG */\n\n#endif /* NEED_PNG_WRITE_SCREEN */\n\n#ifdef NEED_PNG_READ_FILE\n#include <png.h>\n\nstatic boolean png_check_stream(vfile *vf)\n{\n  png_byte header[8];\n\n  if(vfread(header, 1, 8, vf) < 8)\n    return false;\n\n  if(png_sig_cmp(header, 0, 8))\n    return false;\n\n  return true;\n}\n\nstatic void png_read_vfile(png_structp png_ptr, png_bytep data, size_t length)\n{\n  vfile *vf = (vfile *)png_get_io_ptr(png_ptr);\n  if(vfread(data, 1, length, vf) < length)\n    png_error(png_ptr, \"read error\");\n}\n\n/**\n * If `checked` is true, the first 8 bytes of the stream have already been\n * read and verified to be the PNG signature.\n */\nvoid *png_read_stream(vfile *vf, png_uint_32 *_w, png_uint_32 *_h, boolean checked,\n check_w_h_constraint_t constraint, rgba_surface_allocator_t allocator)\n{\n  png_uint_32 i, w, h, stride;\n  png_structp png_ptr = NULL;\n  png_infop info_ptr = NULL;\n  png_bytep * volatile row_ptrs = NULL;\n  void * volatile s = NULL;\n  void *pixels;\n  int type;\n  int bpp;\n\n  if(!checked && !png_check_stream(vf))\n    goto exit_out;\n\n  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);\n  if(!png_ptr)\n    goto exit_out;\n\n  info_ptr = png_create_info_struct(png_ptr);\n  if(!info_ptr)\n    goto exit_free_close;\n\n  if(setjmp(png_jmpbuf(png_ptr)))\n    goto exit_free_close;\n\n  png_set_read_fn(png_ptr, vf, png_read_vfile);\n  png_set_sig_bytes(png_ptr, 8);\n  png_read_info(png_ptr, info_ptr);\n  png_get_IHDR(png_ptr, info_ptr, &w, &h, &bpp, &type, NULL, NULL, NULL);\n\n  if(!constraint(w, h))\n  {\n    warn(\"Requested image failed dimension checks.\\n\");\n    goto exit_free_close;\n  }\n\n#if PNG_LIBPNG_VER < 10504\n#define png_set_scale_16(p) png_set_strip_16(p)\n#endif\n  /* This SHOULD convert everything to RGBA32.\n   * See the far too complicated table in libpng-manual.txt for more info. */\n  if(bpp == 16)\n    png_set_scale_16(png_ptr);\n  if(type & PNG_COLOR_MASK_PALETTE)\n    png_set_palette_to_rgb(png_ptr);\n  if(!(type & PNG_COLOR_MASK_COLOR))\n    png_set_gray_to_rgb(png_ptr);\n\n  if(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))\n  {\n    png_set_tRNS_to_alpha(png_ptr);\n  }\n#if PNG_LIBPNG_VER >= 10207\n  else\n\n  if(!(type & PNG_COLOR_MASK_ALPHA))\n    png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER);\n#endif\n\n  row_ptrs = (png_bytep *)cmalloc(sizeof(png_bytep) * h);\n  if(!row_ptrs)\n    goto exit_free_close;\n\n  s = allocator(w, h, &stride, &pixels);\n  if(!s)\n    goto exit_free_close;\n\n  for(i = 0; i < h; i++)\n    row_ptrs[i] = (png_bytep)(unsigned char *)pixels + i * stride;\n\n  png_read_image(png_ptr, row_ptrs);\n\n  if(_w)\n    *_w = w;\n  if(_h)\n    *_h = h;\n\nexit_free_close:\n  png_destroy_read_struct(&png_ptr, &info_ptr, NULL);\n  free(row_ptrs);\nexit_out:\n  return s;\n}\n\nvoid *png_read_file(const char *name, png_uint_32 *_w, png_uint_32 *_h,\n check_w_h_constraint_t constraint, rgba_surface_allocator_t allocator)\n{\n  void *s;\n\n  vfile *vf = vfopen_unsafe(name, \"rb\");\n  if(!vf)\n    return NULL;\n\n  s = png_read_stream(vf, _w, _h, false, constraint, allocator);\n  vfclose(vf);\n\n  if(!s)\n    warn(\"Failed to load '%s'\\n\", name);\n\n  return s;\n}\n\n#endif // NEED_PNG_READ_FILE\n"
  },
  {
    "path": "src/pngops.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __PNGOPS_H\n#define __PNGOPS_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#if defined(CONFIG_EDITOR) || defined(CONFIG_ENABLE_SCREENSHOTS)\n#define NEED_PNG_WRITE_SCREEN\n#endif\n\n#ifdef CONFIG_PNG\n#if (defined(CONFIG_SDL) && defined(CONFIG_ICON) && !defined(__WIN32__)) || \\\n    defined(CONFIG_UTILS) || defined(CONFIG_3DS)\n#define NEED_PNG_READ_FILE\n#endif\n#endif\n\n#ifdef NEED_PNG_WRITE_SCREEN\n\n#include <stdint.h>\n\nstruct rgb_color;\n\n/*\nint png_write_image_8bpp(const char *name, size_t w, size_t h,\n const struct rgb_color *pal, unsigned count, void *priv,\n const uint8_t *(*row_pixels_callback)(size_t num_pixels, void *priv));\n*/\nint png_write_image_32bpp(const char *name, size_t w, size_t h, void *priv,\n const uint32_t *(*row_pixels_callback)(size_t num_pixels, void *priv));\n\n#endif // NEED_PNG_WRITE_SCREEN\n\n#ifdef NEED_PNG_READ_FILE\n\n#include <png.h>\n#include \"io/vfile.h\"\n\ntypedef boolean (*check_w_h_constraint_t)(png_uint_32 w, png_uint_32 h);\ntypedef void *(*rgba_surface_allocator_t)(png_uint_32 w, png_uint_32 h,\n                                          png_uint_32 *stride, void **pixels);\n\nvoid *png_read_stream(vfile *fp, png_uint_32 *_w, png_uint_32 *_h, boolean checked,\n check_w_h_constraint_t constraint, rgba_surface_allocator_t allocator);\n\nvoid *png_read_file(const char *name, png_uint_32 *_w, png_uint_32 *_h,\n check_w_h_constraint_t constraint, rgba_surface_allocator_t allocator);\n\n#endif // NEED_PNG_READ_FILE\n\n__M_END_DECLS\n\n#endif // __PNGOPS_H\n"
  },
  {
    "path": "src/rasm.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <ctype.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"counter.h\"\n#include \"data.h\"\n#include \"memcasecmp.h\"\n#include \"rasm.h\"\n#include \"str.h\"\n#include \"util.h\"\n#include \"io/fsafeopen.h\"\n#include \"io/memfile.h\"\n#include \"io/vio.h\"\n\n#ifdef CONFIG_DEBYTECODE\n\n#define ARG_TYPE_FRAGMENT_NOT            ARG_TYPE_FRAGMENT | 0\n#define ARG_TYPE_FRAGMENT_ANY            ARG_TYPE_FRAGMENT | 1\n#define ARG_TYPE_FRAGMENT_PLAYER         ARG_TYPE_FRAGMENT | 2\n#define ARG_TYPE_FRAGMENT_NS             ARG_TYPE_FRAGMENT | 3\n#define ARG_TYPE_FRAGMENT_EW             ARG_TYPE_FRAGMENT | 4\n#define ARG_TYPE_FRAGMENT_ATTACK         ARG_TYPE_FRAGMENT | 5\n#define ARG_TYPE_FRAGMENT_ITEM           ARG_TYPE_FRAGMENT | 6\n#define ARG_TYPE_FRAGMENT_SELF           ARG_TYPE_FRAGMENT | 7\n#define ARG_TYPE_FRAGMENT_RANDOM         ARG_TYPE_FRAGMENT | 8\n#define ARG_TYPE_FRAGMENT_STRING         ARG_TYPE_FRAGMENT | 9\n#define ARG_TYPE_FRAGMENT_CHAR           ARG_TYPE_FRAGMENT | 10\n#define ARG_TYPE_FRAGMENT_ALL            ARG_TYPE_FRAGMENT | 11\n#define ARG_TYPE_FRAGMENT_EDIT           ARG_TYPE_FRAGMENT | 12\n#define ARG_TYPE_FRAGMENT_PUSHABLE       ARG_TYPE_FRAGMENT | 13\n#define ARG_TYPE_FRAGMENT_NONPUSHABLE    ARG_TYPE_FRAGMENT | 14\n#define ARG_TYPE_FRAGMENT_LAVAWALKER     ARG_TYPE_FRAGMENT | 15\n#define ARG_TYPE_FRAGMENT_NONLAVAWALKER  ARG_TYPE_FRAGMENT | 16\n#define ARG_TYPE_FRAGMENT_ROW            ARG_TYPE_FRAGMENT | 17\n#define ARG_TYPE_FRAGMENT_COUNTERS       ARG_TYPE_FRAGMENT | 18\n#define ARG_TYPE_FRAGMENT_ID             ARG_TYPE_FRAGMENT | 19\n#define ARG_TYPE_FRAGMENT_MOD            ARG_TYPE_FRAGMENT | 20\n#define ARG_TYPE_FRAGMENT_ORDER          ARG_TYPE_FRAGMENT | 21\n#define ARG_TYPE_FRAGMENT_THICK          ARG_TYPE_FRAGMENT | 22\n#define ARG_TYPE_FRAGMENT_ARROW          ARG_TYPE_FRAGMENT | 23\n#define ARG_TYPE_FRAGMENT_THIN           ARG_TYPE_FRAGMENT | 24\n#define ARG_TYPE_FRAGMENT_MAXHEALTH      ARG_TYPE_FRAGMENT | 25\n#define ARG_TYPE_FRAGMENT_POSITION       ARG_TYPE_FRAGMENT | 26\n#define ARG_TYPE_FRAGMENT_MESG           ARG_TYPE_FRAGMENT | 27\n#define ARG_TYPE_FRAGMENT_COLUMN         ARG_TYPE_FRAGMENT | 28\n#define ARG_TYPE_FRAGMENT_COLOR          ARG_TYPE_FRAGMENT | 29\n#define ARG_TYPE_FRAGMENT_SIZE           ARG_TYPE_FRAGMENT | 30\n#define ARG_TYPE_FRAGMENT_BULLETN        ARG_TYPE_FRAGMENT | 31\n#define ARG_TYPE_FRAGMENT_BULLETS        ARG_TYPE_FRAGMENT | 32\n#define ARG_TYPE_FRAGMENT_BULLETE        ARG_TYPE_FRAGMENT | 33\n#define ARG_TYPE_FRAGMENT_BULLETW        ARG_TYPE_FRAGMENT | 34\n#define ARG_TYPE_FRAGMENT_BULLETCOLOR    ARG_TYPE_FRAGMENT | 35\n#define ARG_TYPE_FRAGMENT_FIRST          ARG_TYPE_FRAGMENT | 36\n#define ARG_TYPE_FRAGMENT_LAST           ARG_TYPE_FRAGMENT | 37\n#define ARG_TYPE_FRAGMENT_FADE           ARG_TYPE_FRAGMENT | 38\n#define ARG_TYPE_FRAGMENT_OUT            ARG_TYPE_FRAGMENT | 39\n#define ARG_TYPE_FRAGMENT_IN             ARG_TYPE_FRAGMENT | 40\n#define ARG_TYPE_FRAGMENT_BLOCK          ARG_TYPE_FRAGMENT | 41\n#define ARG_TYPE_FRAGMENT_SFX            ARG_TYPE_FRAGMENT | 42\n#define ARG_TYPE_FRAGMENT_INTENSITY      ARG_TYPE_FRAGMENT | 43\n#define ARG_TYPE_FRAGMENT_SET            ARG_TYPE_FRAGMENT | 44\n#define ARG_TYPE_FRAGMENT_PALETTE        ARG_TYPE_FRAGMENT | 45\n#define ARG_TYPE_FRAGMENT_WORLD          ARG_TYPE_FRAGMENT | 46\n#define ARG_TYPE_FRAGMENT_ALIGNEDROBOT   ARG_TYPE_FRAGMENT | 47\n#define ARG_TYPE_FRAGMENT_GO             ARG_TYPE_FRAGMENT | 48\n#define ARG_TYPE_FRAGMENT_SAVING         ARG_TYPE_FRAGMENT | 49\n#define ARG_TYPE_FRAGMENT_SENSORONLY     ARG_TYPE_FRAGMENT | 50\n#define ARG_TYPE_FRAGMENT_ON             ARG_TYPE_FRAGMENT | 51\n#define ARG_TYPE_FRAGMENT_STATIC         ARG_TYPE_FRAGMENT | 52\n#define ARG_TYPE_FRAGMENT_TRANSPARENT    ARG_TYPE_FRAGMENT | 53\n#define ARG_TYPE_FRAGMENT_OVERLAY        ARG_TYPE_FRAGMENT | 54\n#define ARG_TYPE_FRAGMENT_START          ARG_TYPE_FRAGMENT | 55\n#define ARG_TYPE_FRAGMENT_LOOP           ARG_TYPE_FRAGMENT | 56\n#define ARG_TYPE_FRAGMENT_EDGE           ARG_TYPE_FRAGMENT | 57\n#define ARG_TYPE_FRAGMENT_SAM            ARG_TYPE_FRAGMENT | 58\n#define ARG_TYPE_FRAGMENT_PLAY           ARG_TYPE_FRAGMENT | 59\n#define ARG_TYPE_FRAGMENT_PERCENT        ARG_TYPE_FRAGMENT | 60\n#define ARG_TYPE_FRAGMENT_HIGH           ARG_TYPE_FRAGMENT | 61\n#define ARG_TYPE_FRAGMENT_MATCHES        ARG_TYPE_FRAGMENT | 62\n#define ARG_TYPE_FRAGMENT_NONE           ARG_TYPE_FRAGMENT | 63\n#define ARG_TYPE_FRAGMENT_INPUT          ARG_TYPE_FRAGMENT | 64\n#define ARG_TYPE_FRAGMENT_DIR            ARG_TYPE_FRAGMENT | 65\n#define ARG_TYPE_FRAGMENT_COUNTER        ARG_TYPE_FRAGMENT | 66\n#define ARG_TYPE_FRAGMENT_DUPLICATE      ARG_TYPE_FRAGMENT | 67\n#define ARG_TYPE_FRAGMENT_NO             ARG_TYPE_FRAGMENT | 68\n\n#define ARG_TYPE_IGNORE_A                ARG_TYPE_IGNORE | 0\n#define ARG_TYPE_IGNORE_AN               ARG_TYPE_IGNORE | 1\n#define ARG_TYPE_IGNORE_AND              ARG_TYPE_IGNORE | 2\n#define ARG_TYPE_IGNORE_AS               ARG_TYPE_IGNORE | 3\n#define ARG_TYPE_IGNORE_AT               ARG_TYPE_IGNORE | 4\n#define ARG_TYPE_IGNORE_BY               ARG_TYPE_IGNORE | 5\n#define ARG_TYPE_IGNORE_ELSE             ARG_TYPE_IGNORE | 6\n#define ARG_TYPE_IGNORE_FOR              ARG_TYPE_IGNORE | 7\n#define ARG_TYPE_IGNORE_FROM             ARG_TYPE_IGNORE | 8\n#define ARG_TYPE_IGNORE_IS               ARG_TYPE_IGNORE | 9\n#define ARG_TYPE_IGNORE_OF               ARG_TYPE_IGNORE | 10\n#define ARG_TYPE_IGNORE_THE              ARG_TYPE_IGNORE | 11\n#define ARG_TYPE_IGNORE_THEN             ARG_TYPE_IGNORE | 12\n#define ARG_TYPE_IGNORE_TO               ARG_TYPE_IGNORE | 13\n#define ARG_TYPE_IGNORE_WITH             ARG_TYPE_IGNORE | 14\n\n#define ARG_TYPE_NUMERIC_INDIRECT                                              \\\n (ARG_TYPE_IMMEDIATE | ARG_TYPE_COUNTER_LOAD_NAME)                             \\\n\n#define ARG_TYPE_COLOR_INDIRECT                                                \\\n (ARG_TYPE_COLOR | ARG_TYPE_COUNTER_LOAD_NAME)                                 \\\n\n#define ARG_TYPE_CHARACTER_INDIRECT                                            \\\n (ARG_TYPE_CHARACTER | ARG_TYPE_IMMEDIATE | ARG_TYPE_COUNTER_LOAD_NAME)        \\\n\n#define ARG_TYPE_PARAM_INDIRECT                                                \\\n (ARG_TYPE_PARAM | ARG_TYPE_COUNTER_LOAD_NAME)                                 \\\n\n// wait for N\nstatic const enum arg_type cm2[] =\n{\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// cycle N\nstatic const enum arg_type cm3[] =\n{\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// go DIR for N\nstatic const enum arg_type cm4[] =\n{\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// walk N\nstatic const enum arg_type cm5[] =\n{\n  ARG_TYPE_DIRECTION\n};\n\n// become COLOR ARG_TYPE_THING PARAM\nstatic const enum arg_type cm6[] =\n{\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT\n};\n\n// char CHAR\nstatic const enum arg_type cm7[] =\n{\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// color COLOR\nstatic const enum arg_type cm8[] =\n{\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// gotoxy N N\nstatic const enum arg_type cm9[] =\n{\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// set COUNTER to N\nstatic const enum arg_type cm10[] =\n{\n  ARG_TYPE_COUNTER_STORE_NAME,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// inc COUNTER by N\nstatic const enum arg_type cm11[] =\n{\n  ARG_TYPE_COUNTER_STORE_NAME,\n  ARG_TYPE_IGNORE_BY,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// dec COUNTER by N\nstatic const enum arg_type cm12[] =\n{\n  ARG_TYPE_COUNTER_STORE_NAME,\n  ARG_TYPE_IGNORE_BY,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// if COUNTER EQUALITY N then LABEL\nstatic const enum arg_type cm16[] =\n{\n  ARG_TYPE_COUNTER_LOAD_NAME,\n  ARG_TYPE_EQUALITY,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// if CONDITION then LABEL\nstatic const enum arg_type cm18[] =\n{\n  ARG_TYPE_CONDITION,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// if not CONDITION then LABEL\nstatic const enum arg_type cm19[] =\n{\n  ARG_TYPE_FRAGMENT_NOT,\n  ARG_TYPE_CONDITION,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// if any COLOR THING PARAM then LABEL\nstatic const enum arg_type cm20[] =\n{\n  ARG_TYPE_FRAGMENT_ANY,\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// if no COLOR THING PARAM then LABEL\nstatic const enum arg_type cm21[] =\n{\n  ARG_TYPE_FRAGMENT_NO,\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// if COLOR THING PARAM at DIR then LABEL\nstatic const enum arg_type cm22[] =\n{\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// if not COLOR THING PARAM at DIR then LABEL\nstatic const enum arg_type cm23[] =\n{\n  ARG_TYPE_FRAGMENT_NOT,\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// if COLOR THING PARAM at N N then LABEL\nstatic const enum arg_type cm24[] =\n{\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// ifat N N then LABEL\nstatic const enum arg_type cm25[] =\n{\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// if at DIR of player is COLOR THING PARAM then LABEL\nstatic const enum arg_type cm26[] =\n{\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_OF,\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// double COUNTER\nstatic const enum arg_type cm27[] =\n{\n  ARG_TYPE_COUNTER_STORE_NAME\n};\n\n// half COUNTER\nstatic const enum arg_type cm28[] =\n{\n  ARG_TYPE_COUNTER_STORE_NAME\n};\n\n// goto LABEL\nstatic const enum arg_type cm29[] =\n{\n  ARG_TYPE_LABEL_NAME\n};\n\n// send ROBOT to EXT_LABEL\nstatic const enum arg_type cm30[] =\n{\n  ARG_TYPE_ROBOT_NAME,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_EXT_LABEL_NAME\n};\n\n// explode N\nstatic const enum arg_type cm31[] =\n{\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// put COLOR THING PARAM to DIR\nstatic const enum arg_type cm32[] =\n{\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_DIRECTION\n};\n\n// give N ITEM\nstatic const enum arg_type cm33[] =\n{\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_ITEM\n};\n\n// take N ITEM\nstatic const enum arg_type cm34[] =\n{\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_ITEM\n};\n\n// take N ITEM else LABEL\nstatic const enum arg_type cm35[] =\n{\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_ITEM,\n  ARG_TYPE_IGNORE_ELSE,\n  ARG_TYPE_LABEL_NAME\n};\n\n// mod STRING\nstatic const enum arg_type cm38[] =\n{\n  ARG_TYPE_STRING\n};\n\n// sam N STRING\nstatic const enum arg_type cm39[] =\n{\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_STRING\n};\n\n// volume N\nstatic const enum arg_type cm40[] =\n{\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// end mod\nstatic const enum arg_type cm41[] =\n{\n  ARG_TYPE_FRAGMENT_MOD\n};\n\n// end sam\nstatic const enum arg_type cm42[] =\n{\n  ARG_TYPE_FRAGMENT_SAM\n};\n\n// play STRING\nstatic const enum arg_type cm43[] =\n{\n  ARG_TYPE_STRING\n};\n\n// end play\nstatic const enum arg_type cm44[] =\n{\n  ARG_TYPE_FRAGMENT_PLAY\n};\n\n// end play STRING\nstatic const enum arg_type cm45[] =\n{\n  ARG_TYPE_FRAGMENT_PLAY,\n  ARG_TYPE_STRING\n};\n\n// wait play\nstatic const enum arg_type cm46[] =\n{\n  ARG_TYPE_FRAGMENT_PLAY\n};\n\n// sfx N\nstatic const enum arg_type cm48[] =\n{\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// play sfx STRING\nstatic const enum arg_type cm49[] =\n{\n  ARG_TYPE_FRAGMENT_SFX,\n  ARG_TYPE_STRING\n};\n\n// open at DIR\nstatic const enum arg_type cm50[] =\n{\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_DIRECTION\n};\n\n// send at DIR to EXT_LABEL\nstatic const enum arg_type cm53[] =\n{\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_EXT_LABEL_NAME\n};\n\n// zap LABEL N\nstatic const enum arg_type cm54[] =\n{\n  ARG_TYPE_LABEL_NAME,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// restore LABEL N\nstatic const enum arg_type cm55[] =\n{\n  ARG_TYPE_LABEL_NAME,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// lockplayer ns\nstatic const enum arg_type cm58[] =\n{\n  ARG_TYPE_FRAGMENT_NS\n};\n\n// lockplayer ew\nstatic const enum arg_type cm59[] =\n{\n  ARG_TYPE_FRAGMENT_EW\n};\n\n// lockplayer attack\nstatic const enum arg_type cm60[] =\n{\n  ARG_TYPE_FRAGMENT_ATTACK\n};\n\n// move player to DIR\nstatic const enum arg_type cm61[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_DIRECTION\n};\n\n// move player to DIR else LABEL\nstatic const enum arg_type cm62[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_ELSE,\n  ARG_TYPE_LABEL_NAME\n};\n\n// put player at N N\nstatic const enum arg_type cm63[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// if player at N N then LABEL\nstatic const enum arg_type cm66[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// put player DIR\nstatic const enum arg_type cm67[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_DIRECTION\n};\n\n// try DIR else LABEL\nstatic const enum arg_type cm68[] =\n{\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_ELSE,\n  ARG_TYPE_LABEL_NAME\n};\n\n// switch DIR with DIR\nstatic const enum arg_type cm71[] =\n{\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_WITH,\n  ARG_TYPE_DIRECTION\n};\n\n// shoot to DIR\nstatic const enum arg_type cm72[] =\n{\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_DIRECTION\n};\n\n// laybomb DIR\nstatic const enum arg_type cm73[] =\n{\n  ARG_TYPE_DIRECTION\n};\n\n// laybomb high DIR\nstatic const enum arg_type cm74[] =\n{\n  ARG_TYPE_FRAGMENT_HIGH,\n  ARG_TYPE_DIRECTION\n};\n\n// shootmissile DIR\nstatic const enum arg_type cm75[] =\n{\n  ARG_TYPE_DIRECTION\n};\n\n// shootseeker DIR\nstatic const enum arg_type cm76[] =\n{\n  ARG_TYPE_DIRECTION\n};\n\n// spitfire DIR\nstatic const enum arg_type cm77[] =\n{\n  ARG_TYPE_DIRECTION\n};\n\n// lazerwall DIR for N\nstatic const enum arg_type cm78[] =\n{\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// put COLOR THING PARAM at N N\nstatic const enum arg_type cm79[] =\n{\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// die as an item\nstatic const enum arg_type cm80[] =\n{\n  ARG_TYPE_IGNORE_AS,\n  ARG_TYPE_IGNORE_AN,\n  ARG_TYPE_FRAGMENT_ITEM\n};\n\n// send at N N to LABEL\nstatic const enum arg_type cm81[] =\n{\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_EXT_LABEL_NAME\n};\n\n// copyrobot ROBOT\nstatic const enum arg_type cm82[] =\n{\n  ARG_TYPE_ROBOT_NAME\n};\n\n// copyrobot at N N\nstatic const enum arg_type cm83[] =\n{\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// copyrobot from DIR\nstatic const enum arg_type cm84[] =\n{\n  ARG_TYPE_IGNORE_FROM,\n  ARG_TYPE_DIRECTION\n};\n\n// duplicate self DIR\nstatic const enum arg_type cm85[] =\n{\n  ARG_TYPE_FRAGMENT_SELF,\n  ARG_TYPE_DIRECTION\n};\n\n// duplicate self to N N\nstatic const enum arg_type cm86[] =\n{\n  ARG_TYPE_FRAGMENT_SELF,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// bulletn is CHAR\nstatic const enum arg_type cm87[] =\n{\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// bullets is CHAR\nstatic const enum arg_type cm88[] =\n{\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// bullete is CHAR\nstatic const enum arg_type cm89[] =\n{\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// bullete is CHAR\nstatic const enum arg_type cm90[] =\n{\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// givekey COLOR\nstatic const enum arg_type cm91[] =\n{\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// givekey COLOR else LABEL\nstatic const enum arg_type cm92[] =\n{\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_IGNORE_ELSE,\n  ARG_TYPE_LABEL_NAME\n};\n\n// takekey COLOR\nstatic const enum arg_type cm93[] =\n{\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// takekey COLOR else LABEL\nstatic const enum arg_type cm94[] =\n{\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_IGNORE_ELSE,\n  ARG_TYPE_LABEL_NAME\n};\n\n// inc COUNTER by random N to N\nstatic const enum arg_type cm95[] =\n{\n  ARG_TYPE_COUNTER_STORE_NAME,\n  ARG_TYPE_IGNORE_BY,\n  ARG_TYPE_FRAGMENT_RANDOM,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// dec COUNTER by random N to N\nstatic const enum arg_type cm96[] =\n{\n  ARG_TYPE_COUNTER_STORE_NAME,\n  ARG_TYPE_IGNORE_BY,\n  ARG_TYPE_FRAGMENT_RANDOM,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// set COUNTER by random N to N\nstatic const enum arg_type cm97[] =\n{\n  ARG_TYPE_COUNTER_STORE_NAME,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_FRAGMENT_RANDOM,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// trade N item for N item else LABEL\nstatic const enum arg_type cm98[] =\n{\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_ITEM,\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_ITEM,\n  ARG_TYPE_IGNORE_ELSE,\n  ARG_TYPE_LABEL_NAME\n};\n\n// send at DIR of PLAYER to EXT_LABEL\nstatic const enum arg_type cm99[] =\n{\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_OF,\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_EXT_LABEL_NAME\n};\n\n// put COLOR THING PARAM to DIR of player\nstatic const enum arg_type cm100[] =\n{\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_OF,\n  ARG_TYPE_FRAGMENT_PLAYER\n};\n\n// / STRING\nstatic const enum arg_type cm101[] =\n{\n  ARG_TYPE_STRING\n};\n\n// * STRING\nstatic const enum arg_type cm102[] =\n{\n  ARG_TYPE_STRING\n};\n\n// [ STRING\nstatic const enum arg_type cm103[] =\n{\n  ARG_TYPE_STRING\n};\n\n// ? LABEL STRING\nstatic const enum arg_type cm104[] =\n{\n  ARG_TYPE_LABEL_NAME,\n  ARG_TYPE_STRING\n};\n\n// ? COUNTER LABEL STRING\nstatic const enum arg_type cm105[] =\n{\n  ARG_TYPE_COUNTER_LOAD_NAME,\n  ARG_TYPE_LABEL_NAME,\n  ARG_TYPE_STRING\n};\n\n// : LABEL\nstatic const enum arg_type cm106[] =\n{\n  ARG_TYPE_LABEL_NAME\n};\n\n// . STRING\nstatic const enum arg_type cm107[] =\n{\n  ARG_TYPE_STRING\n};\n\n// | LABEL\nstatic const enum arg_type cm108[] =\n{\n  ARG_TYPE_LABEL_NAME\n};\n\n// teleport player to BOARD at N N\nstatic const enum arg_type cm109[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_BOARD_NAME,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// scrollview DIR for N\nstatic const enum arg_type cm110[] =\n{\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// input name STRING\nstatic const enum arg_type cm111[] =\n{\n  ARG_TYPE_FRAGMENT_STRING,\n  ARG_TYPE_STRING\n};\n\n// if string is STRING then LABEL\nstatic const enum arg_type cm112[] =\n{\n  ARG_TYPE_FRAGMENT_STRING,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_STRING,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// if string IS not STRING then LABEL\nstatic const enum arg_type cm113[] =\n{\n  ARG_TYPE_FRAGMENT_STRING,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_FRAGMENT_NOT,\n  ARG_TYPE_STRING,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// if string matches STRING then LABEL\nstatic const enum arg_type cm114[] =\n{\n  ARG_TYPE_FRAGMENT_STRING,\n  ARG_TYPE_FRAGMENT_MATCHES,\n  ARG_TYPE_STRING,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// player char is CHAR\nstatic const enum arg_type cm115[] =\n{\n  ARG_TYPE_FRAGMENT_CHAR,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// % STRING\nstatic const enum arg_type cm116[] =\n{\n  ARG_TYPE_STRING\n};\n\n// & STRING\nstatic const enum arg_type cm117[] =\n{\n  ARG_TYPE_STRING\n};\n\n// move all COLOR THING PARAM to DIR\nstatic const enum arg_type cm118[] =\n{\n  ARG_TYPE_FRAGMENT_ALL,\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_DIRECTION\n};\n\n// copy at N N to N N\nstatic const enum arg_type cm119[] =\n{\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// set edge color to COLOR\nstatic const enum arg_type cm120[] =\n{\n  ARG_TYPE_FRAGMENT_EDGE,\n  ARG_TYPE_FRAGMENT_COLOR,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// board to the DIR is BOARD\nstatic const enum arg_type cm121[] =\n{\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_IGNORE_THE,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_BOARD_NAME\n};\n\n// board to the DIR is none\nstatic const enum arg_type cm122[] =\n{\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_IGNORE_THE,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_FRAGMENT_NONE\n};\n\n// char edit CHAR to N N N N N N N N N N N N N N\nstatic const enum arg_type cm123[] =\n{\n  ARG_TYPE_FRAGMENT_EDIT,\n  ARG_TYPE_CHARACTER_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// become pushable\nstatic const enum arg_type cm124[] =\n{\n  ARG_TYPE_FRAGMENT_PUSHABLE\n};\n\n// become nonpushable\nstatic const enum arg_type cm125[] =\n{\n  ARG_TYPE_FRAGMENT_NONPUSHABLE\n};\n\n// blind for N\nstatic const enum arg_type cm126[] =\n{\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// firewalker for N\nstatic const enum arg_type cm127[] =\n{\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// freezetime for N\nstatic const enum arg_type cm128[] =\n{\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// slowtime for N\nstatic const enum arg_type cm129[] =\n{\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// wind for N\nstatic const enum arg_type cm130[] =\n{\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// copy from DIR to DIR\nstatic const enum arg_type cm132[] =\n{\n  ARG_TYPE_IGNORE_FROM,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_DIRECTION\n};\n\n// become a lavawalker\nstatic const enum arg_type cm133[] =\n{\n  ARG_TYPE_IGNORE_A,\n  ARG_TYPE_FRAGMENT_LAVAWALKER\n};\n\n// become a nonlavawalker\nstatic const enum arg_type cm134[] =\n{\n  ARG_TYPE_IGNORE_A,\n  ARG_TYPE_FRAGMENT_NONLAVAWALKER\n};\n\n// change from COLOR THING PARAM to COLOR THING PARAM\nstatic const enum arg_type cm135[] =\n{\n  ARG_TYPE_IGNORE_FROM,\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_THING,\n  ARG_TYPE_PARAM_INDIRECT\n};\n\n// playercolor is COLOR\nstatic const enum arg_type cm136[] =\n{\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// bulletcolor is COLOR\nstatic const enum arg_type cm137[] =\n{\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// missiliecolor is COLOR\nstatic const enum arg_type cm138[] =\n{\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// message row is N\nstatic const enum arg_type cm139[] =\n{\n  ARG_TYPE_FRAGMENT_ROW,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// rel to self\nstatic const enum arg_type cm140[] =\n{\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_FRAGMENT_SELF\n};\n\n// rel to player\nstatic const enum arg_type cm141[] =\n{\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_FRAGMENT_PLAYER\n};\n\n// rel to counters\nstatic const enum arg_type cm142[] =\n{\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_FRAGMENT_COUNTERS\n};\n\n// change char id N to CHAR\nstatic const enum arg_type cm143[] =\n{\n  ARG_TYPE_FRAGMENT_CHAR,\n  ARG_TYPE_FRAGMENT_ID,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// jump to mod order N\nstatic const enum arg_type cm144[] =\n{\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_FRAGMENT_MOD,\n  ARG_TYPE_FRAGMENT_ORDER,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// ask STRING\nstatic const enum arg_type cm145[] =\n{\n  ARG_TYPE_STRING\n};\n\n// change thick arrow char DIR to CHAR\nstatic const enum arg_type cm147[] =\n{\n  ARG_TYPE_FRAGMENT_THICK,\n  ARG_TYPE_FRAGMENT_ARROW,\n  ARG_TYPE_FRAGMENT_CHAR,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// change thin arrow char DIR to CHAR\nstatic const enum arg_type cm148[] =\n{\n  ARG_TYPE_FRAGMENT_THIN,\n  ARG_TYPE_FRAGMENT_ARROW,\n  ARG_TYPE_FRAGMENT_CHAR,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// set maxhealth to N\nstatic const enum arg_type cm149[] =\n{\n  ARG_TYPE_FRAGMENT_MAXHEALTH,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// save player position\nstatic const enum arg_type cm150[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_FRAGMENT_POSITION\n};\n\n// restore player position\nstatic const enum arg_type cm151[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_FRAGMENT_POSITION\n};\n\n// exchange player position\nstatic const enum arg_type cm152[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_FRAGMENT_POSITION\n};\n\n// set mesg column to N\nstatic const enum arg_type cm153[] =\n{\n  ARG_TYPE_FRAGMENT_MESG,\n  ARG_TYPE_FRAGMENT_COLUMN,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n// clear mesg\nstatic const enum arg_type cm154[] =\n{\n  ARG_TYPE_FRAGMENT_MESG\n};\n\n// center mesg\nstatic const enum arg_type cm155[] =\n{\n  ARG_TYPE_FRAGMENT_MESG\n};\n\n// mod sam N N\nstatic const enum arg_type cm157[] =\n{\n  ARG_TYPE_FRAGMENT_SAM,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// volume N\nstatic const enum arg_type cm158[] =\n{\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// scrollbase color is COLOR\nstatic const enum arg_type cm159[] =\n{\n  ARG_TYPE_FRAGMENT_COLOR,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// scrollcorner color is COLOR\nstatic const enum arg_type cm160[] =\n{\n  ARG_TYPE_FRAGMENT_COLOR,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// scrolltitle color is COLOR\nstatic const enum arg_type cm161[] =\n{\n  ARG_TYPE_FRAGMENT_COLOR,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// scrollpointer color is COLOR\nstatic const enum arg_type cm162[] =\n{\n  ARG_TYPE_FRAGMENT_COLOR,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// scrollarrow color is COLOR\nstatic const enum arg_type cm163[] =\n{\n  ARG_TYPE_FRAGMENT_COLOR,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// viewport is at N N\nstatic const enum arg_type cm164[] =\n{\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// viewport size is N by N\nstatic const enum arg_type cm165[] =\n{\n  ARG_TYPE_FRAGMENT_SIZE,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_BY,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// save player position to N\nstatic const enum arg_type cm168[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_FRAGMENT_POSITION,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// restore player position from N\nstatic const enum arg_type cm169[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_FRAGMENT_POSITION,\n  ARG_TYPE_IGNORE_FROM,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// exchange player position with N\nstatic const enum arg_type cm170[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_FRAGMENT_POSITION,\n  ARG_TYPE_IGNORE_WITH,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// restore player position from N and duplicate self\nstatic const enum arg_type cm171[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_FRAGMENT_POSITION,\n  ARG_TYPE_IGNORE_FROM,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_AND,\n  ARG_TYPE_FRAGMENT_DUPLICATE,\n  ARG_TYPE_FRAGMENT_SELF\n};\n\n// exchange player position with N and duplicate self\nstatic const enum arg_type cm172[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_FRAGMENT_POSITION,\n  ARG_TYPE_IGNORE_WITH,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_AND,\n  ARG_TYPE_FRAGMENT_DUPLICATE,\n  ARG_TYPE_FRAGMENT_SELF\n};\n\n// player bulletn is CHAR\nstatic const enum arg_type cm173[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETN,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// player bullets is CHAR\nstatic const enum arg_type cm174[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETS,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// player bullete is CHAR\nstatic const enum arg_type cm175[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETE,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// player bulletw is CHAR\nstatic const enum arg_type cm176[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETW,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// neutral bulletn is CHAR\nstatic const enum arg_type cm177[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETN,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// neutral bullets is CHAR\nstatic const enum arg_type cm178[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETS,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// neutral bullete is CHAR\nstatic const enum arg_type cm179[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETE,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// neutral bulletw is CHAR\nstatic const enum arg_type cm180[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETW,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// enemy bulletn is CHAR\nstatic const enum arg_type cm181[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETN,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// enemy bullets is CHAR\nstatic const enum arg_type cm182[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETS,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// enemy bullete is CHAR\nstatic const enum arg_type cm183[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETE,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// enemy bulletw is CHAR\nstatic const enum arg_type cm184[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETW,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// player bulletcolor is COLOR\nstatic const enum arg_type cm185[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETCOLOR,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// neutral bulletcolor is COLOR\nstatic const enum arg_type cm186[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETCOLOR,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// enemy bulletcolor is COLOR\nstatic const enum arg_type cm187[] =\n{\n  ARG_TYPE_FRAGMENT_BULLETCOLOR,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// rel self first\nstatic const enum arg_type cm193[] =\n{\n  ARG_TYPE_FRAGMENT_SELF,\n  ARG_TYPE_FRAGMENT_FIRST\n};\n\n// rel self last\nstatic const enum arg_type cm194[] =\n{\n  ARG_TYPE_FRAGMENT_SELF,\n  ARG_TYPE_FRAGMENT_LAST\n};\n\n// rel player first\nstatic const enum arg_type cm195[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_FRAGMENT_FIRST\n};\n\n// rel player last\nstatic const enum arg_type cm196[] =\n{\n  ARG_TYPE_FRAGMENT_PLAYER,\n  ARG_TYPE_FRAGMENT_LAST\n};\n\n// rel counters first\nstatic const enum arg_type cm197[] =\n{\n  ARG_TYPE_FRAGMENT_COUNTERS,\n  ARG_TYPE_FRAGMENT_FIRST\n};\n\n// rel counters last\nstatic const enum arg_type cm198[] =\n{\n  ARG_TYPE_FRAGMENT_COUNTERS,\n  ARG_TYPE_FRAGMENT_LAST\n};\n\n// mod fade out\nstatic const enum arg_type cm199[] =\n{\n  ARG_TYPE_FRAGMENT_FADE,\n  ARG_TYPE_FRAGMENT_OUT\n};\n\n// mod fade in STRING\nstatic const enum arg_type cm200[] =\n{\n  ARG_TYPE_FRAGMENT_FADE,\n  ARG_TYPE_FRAGMENT_IN,\n  ARG_TYPE_STRING\n};\n\n// copy block at N N for N by N to N N\nstatic const enum arg_type cm201[] =\n{\n  ARG_TYPE_FRAGMENT_BLOCK,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_BY,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// clip input\nstatic const enum arg_type cm202[] =\n{\n  ARG_TYPE_FRAGMENT_INPUT\n};\n\n// push DIR\nstatic const enum arg_type cm203[] =\n{\n  ARG_TYPE_DIRECTION\n};\n\n// scroll char CHAR DIR\nstatic const enum arg_type cm204[] =\n{\n  ARG_TYPE_FRAGMENT_CHAR,\n  ARG_TYPE_CHARACTER_INDIRECT,\n  ARG_TYPE_DIRECTION\n};\n\n// flip char CHAR DIR\nstatic const enum arg_type cm205[] =\n{\n  ARG_TYPE_FRAGMENT_CHAR,\n  ARG_TYPE_CHARACTER_INDIRECT,\n  ARG_TYPE_DIRECTION\n};\n\n// copy char CHAR to CHAR\nstatic const enum arg_type cm206[] =\n{\n  ARG_TYPE_FRAGMENT_CHAR,\n  ARG_TYPE_CHARACTER_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// change SFX N to STRING\nstatic const enum arg_type cm210[] =\n{\n  ARG_TYPE_FRAGMENT_SFX,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_STRING\n};\n\n// color intensity is at N percent\nstatic const enum arg_type cm211[] =\n{\n  ARG_TYPE_FRAGMENT_INTENSITY,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_FRAGMENT_PERCENT\n};\n\n// color intensity N is at N percent\nstatic const enum arg_type cm212[] =\n{\n  ARG_TYPE_FRAGMENT_INTENSITY,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_FRAGMENT_PERCENT\n};\n\n// color fade out\nstatic const enum arg_type cm213[] =\n{\n  ARG_TYPE_FRAGMENT_FADE,\n  ARG_TYPE_FRAGMENT_OUT\n};\n\n// color fade in\nstatic const enum arg_type cm214[] =\n{\n  ARG_TYPE_FRAGMENT_FADE,\n  ARG_TYPE_FRAGMENT_IN\n};\n\n// set color N to N N N\nstatic const enum arg_type cm215[] =\n{\n  ARG_TYPE_FRAGMENT_COLOR,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// load char set STRING\nstatic const enum arg_type cm216[] =\n{\n  ARG_TYPE_FRAGMENT_CHAR,\n  ARG_TYPE_FRAGMENT_SET,\n  ARG_TYPE_STRING\n};\n\n// multiply COUNTER by N\nstatic const enum arg_type cm217[] =\n{\n  ARG_TYPE_COUNTER_STORE_NAME,\n  ARG_TYPE_IGNORE_BY,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// divide counter by N\nstatic const enum arg_type cm218[] =\n{\n  ARG_TYPE_COUNTER_STORE_NAME,\n  ARG_TYPE_IGNORE_BY,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// modulo counter by N\nstatic const enum arg_type cm219[] =\n{\n  ARG_TYPE_COUNTER_STORE_NAME,\n  ARG_TYPE_IGNORE_BY,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// player char DIR is CHAR\nstatic const enum arg_type cm220[] =\n{\n  ARG_TYPE_FRAGMENT_CHAR,\n  ARG_TYPE_DIRECTION,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// load palette STRING\nstatic const enum arg_type cm222[] =\n{\n  ARG_TYPE_FRAGMENT_PALETTE,\n  ARG_TYPE_STRING\n};\n\n// mod fade to N by N\nstatic const enum arg_type cm224[] =\n{\n  ARG_TYPE_FRAGMENT_FADE,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_BY,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// scrollview position N N\nstatic const enum arg_type cm225[] =\n{\n  ARG_TYPE_FRAGMENT_POSITION,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// swap world STRING\nstatic const enum arg_type cm226[] =\n{\n  ARG_TYPE_FRAGMENT_WORLD,\n  ARG_TYPE_STRING\n};\n\n// if alignedrobot with ROBOT then LABEL\nstatic const enum arg_type cm227[] =\n{\n  ARG_TYPE_FRAGMENT_ALIGNEDROBOT,\n  ARG_TYPE_IGNORE_WITH,\n  ARG_TYPE_ROBOT_NAME,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// if first string is STRING then LABEL\nstatic const enum arg_type cm231[] =\n{\n  ARG_TYPE_FRAGMENT_FIRST,\n  ARG_TYPE_FRAGMENT_STRING,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_STRING,\n  ARG_TYPE_IGNORE_THEN,\n  ARG_TYPE_LABEL_NAME\n};\n\n// persistent go STRING\nstatic const enum arg_type cm232[] =\n{\n  ARG_TYPE_FRAGMENT_GO,\n  ARG_TYPE_STRING\n};\n\n// wait for mod fade\nstatic const enum arg_type cm233[] ={\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_FRAGMENT_MOD,\n  ARG_TYPE_FRAGMENT_FADE\n};\n\n// enable saving\nstatic const enum arg_type cm235[] =\n{\n  ARG_TYPE_FRAGMENT_SAVING\n};\n\n// disable saving\nstatic const enum arg_type cm236[] =\n{\n  ARG_TYPE_FRAGMENT_SAVING\n};\n\n// enable sensoronly saving\nstatic const enum arg_type cm237[] ={\n  ARG_TYPE_FRAGMENT_SENSORONLY,\n  ARG_TYPE_FRAGMENT_SAVING\n};\n\n// status counter N is COUNTER\nstatic const enum arg_type cm238[] =\n{\n  ARG_TYPE_FRAGMENT_COUNTER,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_COUNTER_STORE_NAME\n};\n\n// overlay is on\nstatic const enum arg_type cm239[] =\n{\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_FRAGMENT_ON\n};\n\n// overlay is static\nstatic const enum arg_type cm240[] =\n{\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_FRAGMENT_STATIC\n};\n\n// overlay is transparent\nstatic const enum arg_type cm241[] =\n{\n  ARG_TYPE_IGNORE_IS,\n  ARG_TYPE_FRAGMENT_TRANSPARENT\n};\n\n// put COLOR CHAR overlay to N N\nstatic const enum arg_type cm242[] =\n{\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_CHARACTER_INDIRECT,\n  ARG_TYPE_FRAGMENT_OVERLAY,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// copy overlay block at N N for N by N to N N\nstatic const enum arg_type cm243[] =\n{\n  ARG_TYPE_FRAGMENT_OVERLAY,\n  ARG_TYPE_FRAGMENT_BLOCK,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_BY,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// change overlay COLOR CHAR to COLOR CHAR\nstatic const enum arg_type cm245[] =\n{\n  ARG_TYPE_FRAGMENT_OVERLAY,\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_CHARACTER_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_CHARACTER_INDIRECT\n};\n\n// change overlay COLOR to COLOR\nstatic const enum arg_type cm246[] =\n{\n  ARG_TYPE_FRAGMENT_OVERLAY,\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_IGNORE_TO,\n  ARG_TYPE_COLOR_INDIRECT\n};\n\n// write overlay COLOR STRING at N N\nstatic const enum arg_type cm247[] =\n{\n  ARG_TYPE_FRAGMENT_OVERLAY,\n  ARG_TYPE_COLOR_INDIRECT,\n  ARG_TYPE_STRING,\n  ARG_TYPE_IGNORE_AT,\n  ARG_TYPE_NUMERIC_INDIRECT,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// loop start\nstatic const enum arg_type cm251[] =\n{\n  ARG_TYPE_FRAGMENT_START\n};\n\n// loop for N\nstatic const enum arg_type cm252[] =\n{\n  ARG_TYPE_IGNORE_FOR,\n  ARG_TYPE_NUMERIC_INDIRECT\n};\n\n// abort loop\nstatic const enum arg_type cm253[] =\n{\n  ARG_TYPE_FRAGMENT_LOOP\n};\n\n// disable mesg edge\nstatic const enum arg_type cm254[] =\n{\n  ARG_TYPE_FRAGMENT_MESG,\n  ARG_TYPE_FRAGMENT_EDGE\n};\n\n// enable mesg edge\nstatic const enum arg_type cm255[] =\n{\n  ARG_TYPE_FRAGMENT_MESG,\n  ARG_TYPE_FRAGMENT_EDGE\n};\n\nstruct mzx_command\n{\n  const char *name;\n  int parameters;\n  const enum arg_type *param_types;\n};\n\nstatic const struct mzx_command command_list[] =\n{\n  { \"end\",            0,  NULL  },\n  { \"die\",            0,  NULL  },\n  { \"wait\",           1,  cm2   },\n  { \"cycle\",          1,  cm3   },\n  { \"go\",             2,  cm4   },\n  { \"walk\",           1,  cm5   },\n  { \"become\",         3,  cm6   },\n  { \"char\",           1,  cm7   },\n  { \"color\",          1,  cm8   },\n  { \"gotoxy\",         2,  cm9   },\n  { \"set\",            2,  cm10  },\n  { \"inc\",            2,  cm11  },\n  { \"dec\",            2,  cm12  },\n  { \"__unused\",       0,  NULL  },\n  { \"__unused\",       0,  NULL  },\n  { \"__unused\",       0,  NULL  },\n  { \"if\",             4,  cm16  },\n  { \"__unused\",       0,  NULL  },\n  { \"if\",             2,  cm18  },\n  { \"if\",             3,  cm19  },\n  { \"if\",             5,  cm20  },\n  { \"if\",             5,  cm21  },\n  { \"if\",             5,  cm22  },\n  { \"if\",             6,  cm23  },\n  { \"if\",             6,  cm24  },\n  { \"if\",             3,  cm25  },\n  { \"if\",             6,  cm26  },\n  { \"double\",         1,  cm27  },\n  { \"half\",           1,  cm28  },\n  { \"goto\",           1,  cm29  },\n  { \"send\",           2,  cm30  },\n  { \"explode\",        1,  cm31  },\n  { \"put\",            4,  cm32  },\n  { \"give\",           2,  cm33  },\n  { \"take\",           2,  cm34  },\n  { \"take\",           3,  cm35  },\n  { \"endgame\",        0,  NULL  },\n  { \"endlife\",        0,  NULL  },\n  { \"mod\",            1,  cm38  },\n  { \"sam\",            2,  cm39  },\n  { \"volume\",         1,  cm40  },\n  { \"end\",            1,  cm41  },\n  { \"end\",            1,  cm42  },\n  { \"play\",           1,  cm43  },\n  { \"end\",            1,  cm44  },\n  { \"wait\",           2,  cm45  },\n  { \"wait\",           1,  cm46  },\n  { \"_blank_line\",    0,  NULL  },\n  { \"sfx\",            1,  cm48  },\n  { \"play\",           2,  cm49  },\n  { \"open\",           1,  cm50  },\n  { \"lockself\",       0,  NULL  },\n  { \"unlockself\",     0,  NULL  },\n  { \"send\",           2,  cm53  },\n  { \"zap\",            2,  cm54  },\n  { \"restore\",        2,  cm55  },\n  { \"lockplayer\",     0,  NULL  },\n  { \"unlockplayer\",   0,  NULL  },\n  { \"lockplayer\",     1,  cm58  },\n  { \"lockplayer\",     1,  cm59  },\n  { \"lockplayer\",     1,  cm60  },\n  { \"move\",           2,  cm61  },\n  { \"move\",           3,  cm62  },\n  { \"put\",            3,  cm63  },\n  { \"__unused\",       0,  NULL  },\n  { \"__unused\",       0,  NULL  },\n  { \"if\",             4,  cm66  },\n  { \"put\",            2,  cm67  },\n  { \"try\",            2,  cm68  },\n  { \"rotatecw\",       0,  NULL  },\n  { \"rotateccw\",      0,  NULL  },\n  { \"switch\",         2,  cm71  },\n  { \"shoot\",          1,  cm72  },\n  { \"laybomb\",        1,  cm73  },\n  { \"laybomb\",        2,  cm74  },\n  { \"shootmissile\",   1,  cm75  },\n  { \"shootseeker\",    1,  cm76  },\n  { \"spitfire\",       1,  cm77  },\n  { \"lazerwall\",      2,  cm78  },\n  { \"put\",            5,  cm79  },\n  { \"die\",            1,  cm80  },\n  { \"send\",           3,  cm81  },\n  { \"copyrobot\",      1,  cm82  },\n  { \"copyrobot\",      2,  cm83  },\n  { \"copyrobot\",      1,  cm84  },\n  { \"duplicate\",      2,  cm85  },\n  { \"duplicate\",      3,  cm86  },\n  { \"bulletn\",        1,  cm87  },\n  { \"bullets\",        1,  cm88  },\n  { \"bullete\",        1,  cm89  },\n  { \"bulletw\",        1,  cm90  },\n  { \"givekey\",        1,  cm91  },\n  { \"givekey\",        2,  cm92  },\n  { \"takekey\",        1,  cm93  },\n  { \"takekey\",        2,  cm94  },\n  { \"inc\",            4,  cm95  },\n  { \"dec\",            4,  cm96  },\n  { \"set\",            4,  cm97  },\n  { \"trade\",          5,  cm98  },\n  { \"send\",           3,  cm99  },\n  { \"put\",            5,  cm100 },\n  { \"/\",              1,  cm101 },\n  { \"*\",              1,  cm102 },\n  { \"[\",              1,  cm103 },\n  { \"?\",              2,  cm104 },\n  { \"?\",              3,  cm105 },\n  { \":\",              1,  cm106 },\n  { \".\",              1,  cm107 },\n  { \"|\",              1,  cm108 },\n  { \"teleport\",       4,  cm109 },\n  { \"scrollview\",     2,  cm110 },\n  { \"input\",          2,  cm111 },\n  { \"if\",             3,  cm112 },\n  { \"if\",             4,  cm113 },\n  { \"if\",             4,  cm114 },\n  { \"player\",         2,  cm115 },\n  { \"%\",              1,  cm116 },\n  { \"&\",              1,  cm117 },\n  { \"move\",           5,  cm118 },\n  { \"copy\",           4,  cm119 },\n  { \"set\",            3,  cm120 },\n  { \"board\",          2,  cm121 },\n  { \"board\",          2,  cm122 },\n  { \"char\",           16, cm123 },\n  { \"become\",         1,  cm124 },\n  { \"become\",         1,  cm125 },\n  { \"blind\",          1,  cm126 },\n  { \"firewalker\",     1,  cm127 },\n  { \"freezetime\",     1,  cm128 },\n  { \"slowtime\",       1,  cm129 },\n  { \"wind\",           1,  cm130 },\n  { \"avalanche\",      0,  NULL  },\n  { \"copy\",           2,  cm132 },\n  { \"become\",         1,  cm133 },\n  { \"become\",         1,  cm134 },\n  { \"change\",         6,  cm135 },\n  { \"playercolor\",    1,  cm136 },\n  { \"bulletcolor\",    1,  cm137 },\n  { \"missilecolor\",   1,  cm138 },\n  { \"message\",        2,  cm139 },\n  { \"rel\",            1,  cm140 },\n  { \"rel\",            1,  cm141 },\n  { \"rel\",            1,  cm142 },\n  { \"change\",         4,  cm143 },\n  { \"jump\",           3,  cm144 },\n  { \"ask\",            1,  cm145 },\n  { \"fillhealth\",     0,  NULL  },\n  { \"change\",         5,  cm147 },\n  { \"change\",         5,  cm148 },\n  { \"set\",            2,  cm149 },\n  { \"save\",           2,  cm150 },\n  { \"restore\",        2,  cm151 },\n  { \"exchange\",       2,  cm152 },\n  { \"set\",            3,  cm153 },\n  { \"center\",         1,  cm154 },\n  { \"clear\",          1,  cm155 },\n  { \"resetview\",      0,  NULL  },\n  { \"mod\",            3,  cm157 },\n  { \"volume\",         1,  cm158 },\n  { \"scrollbase\",     2,  cm159 },\n  { \"scrollcorner\",   2,  cm160 },\n  { \"scrolltitle\",    2,  cm161 },\n  { \"scrollpointer\",  2,  cm162 },\n  { \"scrollarrow\",    2,  cm163 },\n  { \"viewport\",       2,  cm164 },\n  { \"viewport\",       3,  cm165 },\n  { \"__unused\",       0,  NULL  },\n  { \"__unused\",       0,  NULL  },\n  { \"save\",           3,  cm168 },\n  { \"restore\",        3,  cm169 },\n  { \"exchange\",       3,  cm170 },\n  { \"restore\",        5,  cm171 },\n  { \"exchange\",       5,  cm172 },\n  { \"player\",         2,  cm173 },\n  { \"player\",         2,  cm174 },\n  { \"player\",         2,  cm175 },\n  { \"player\",         2,  cm176 },\n  { \"neutral\",        2,  cm177 },\n  { \"neutral\",        2,  cm178 },\n  { \"neutral\",        2,  cm179 },\n  { \"neutral\",        2,  cm180 },\n  { \"enemy\",          2,  cm181 },\n  { \"enemy\",          2,  cm182 },\n  { \"enemy\",          2,  cm183 },\n  { \"enemy\",          2,  cm184 },\n  { \"player\",         2,  cm185 },\n  { \"neutral\",        2,  cm186 },\n  { \"enemy\",          2,  cm187 },\n  { \"__unused\",       0,  NULL  },\n  { \"__unused\",       0,  NULL  },\n  { \"__unused\",       0,  NULL  },\n  { \"__unused\",       0,  NULL  },\n  { \"__unused\",       0,  NULL  },\n  { \"rel\",            2,  cm193 },\n  { \"rel\",            2,  cm194 },\n  { \"rel\",            2,  cm195 },\n  { \"rel\",            2,  cm196 },\n  { \"rel\",            2,  cm197 },\n  { \"rel\",            2,  cm198 },\n  { \"mod\",            2,  cm199 },\n  { \"mod\",            3,  cm200 },\n  { \"copy\",           7,  cm201 },\n  { \"clip\",           1,  cm202 },\n  { \"push\",           1,  cm203 },\n  { \"scroll\",         3,  cm204 },\n  { \"flip\",           3,  cm205 },\n  { \"copy\",           3,  cm206 },\n  { \"__unused\",       0,  NULL  },\n  { \"__unused\",       0,  NULL  },\n  { \"__unused\",       0,  NULL  },\n  { \"change\",         3,  cm210 },\n  { \"color\",          3,  cm211 },\n  { \"color\",          4,  cm212 },\n  { \"color\",          2,  cm213 },\n  { \"color\",          2,  cm214 },\n  { \"set\",            5,  cm215 },\n  { \"load\",           3,  cm216 },\n  { \"multiply\",       2,  cm217 },\n  { \"divide\",         2,  cm218 },\n  { \"modulo\",         2,  cm219 },\n  { \"player\",         3,  cm220 },\n  { \"__unused\",       0,  NULL  },\n  { \"load\",           2,  cm222 },\n  { \"__unused\",       0,  NULL  },\n  { \"mod\",            3,  cm224 },\n  { \"scrollview\",     3,  cm225 },\n  { \"swap\",           2,  cm226 },\n  { \"if\",             3,  cm227 },\n  { \"__unused\",       0,  NULL  },\n  { \"lockscroll\",     0,  NULL  },\n  { \"unlockscroll\",   0,  NULL  },\n  { \"if\",             4,  cm231 },\n  { \"persistent\",     2,  cm232 },\n  { \"wait\",           2,  cm233 },\n  { \"__unused\",       0,  NULL  },\n  { \"enable\",         1,  cm235 },\n  { \"disable\",        1,  cm236 },\n  { \"enable\",         2,  cm237 },\n  { \"status\",         3,  cm238 },\n  { \"overlay\",        1,  cm239 },\n  { \"overlay\",        1,  cm240 },\n  { \"overlay\",        1,  cm241 },\n  { \"put\",            5,  cm242 },\n  { \"copy\",           8,  cm243 },\n  { \"__unused\",       0,  NULL  },\n  { \"change\",         5,  cm245 },\n  { \"change\",         3,  cm246 },\n  { \"write\",          5,  cm247 },\n  { \"__unused\",       0,  NULL  },\n  { \"__unused\",       0,  NULL  },\n  { \"__unused\",       0,  NULL  },\n  { \"loop\",           1,  cm251 },\n  { \"loop\",           1,  cm252 },\n  { \"abort\",          1,  cm253 },\n  { \"disable\",        2,  cm254 },\n  { \"enable\",         2,  cm255 }\n};\n\nstatic const struct mzx_command empty_command = { \"\", 0, NULL };\n\nstatic const char *const dir_names[20] =\n{\n  \"IDLE\",\n  \"NORTH\",\n  \"SOUTH\",\n  \"EAST\",\n  \"WEST\",\n  \"RANDNS\",\n  \"RANDEW\",\n  \"RANDNE\",\n  \"RANDNB\",\n  \"SEEK\",\n  \"RANDANY\",\n  \"BENEATH\",\n  \"ANYDIR\",\n  \"FLOW\",\n  \"NODIR\",\n  \"RANDB\",\n  \"RANDP\",\n  \"CW\",\n  \"OPP\",\n  \"RANDNOT\"\n};\n\nstatic const char *const equality_names[] =\n{\n  \"=\",\n  \"<\",\n  \">\",\n  \">=\",\n  \"<=\",\n  \"!=\",\n  \"===\",\n  \"?=\",\n  \"?==\"\n};\n\nstatic const char *const condition_names[18] =\n{\n  \"walking\",\n  \"swimming\",\n  \"firewalking\",\n  \"touching\",\n  \"blocked\",\n  \"aligned\",\n  \"alignedns\",\n  \"alignedew\",\n  \"lastshot\",\n  \"lasttouch\",\n  \"rightpressed\",\n  \"leftpressed\",\n  \"uppressed\",\n  \"downpressed\",\n  \"spacepressed\",\n  \"delpressed\",\n  \"musicon\",\n  \"pcsfxon\"\n};\n\nstatic const char *const item_names[9] =\n{\n  \"GEMS\",\n  \"AMMOS\",\n  \"TIME\",\n  \"SCORE\",\n  \"HEALTHS\",\n  \"LIVES\",\n  \"LOBOMBS\",\n  \"HIBOMBS\",\n  \"COINS\"\n};\n\n\nstatic const char *const ignore_type_names[21] =\n{\n  \"a\",\n  \"an\",\n  \"and\",\n  \"as\",\n  \"at\",\n  \"by\",\n  \"else\",\n  \"for\",\n  \"from\",\n  \"is\",\n  \"of\",\n  \"the\",\n  \"then\",\n  \"to\",\n  \"with\"\n};\n\nstatic const char *const command_fragment_type_names[69] =\n{\n  \"not\",\n  \"any\",\n  \"player\",\n  \"ns\",\n  \"ew\",\n  \"attack\",\n  \"item\",\n  \"self\",\n  \"random\",\n  \"string\",\n  \"char\",\n  \"all\",\n  \"edit\",\n  \"pushable\",\n  \"nonpushable\",\n  \"lavawalker\",\n  \"nonlavawalker\",\n  \"row\",\n  \"counters\",\n  \"id\",\n  \"mod\",\n  \"order\",\n  \"thick\",\n  \"arrow\",\n  \"thin\",\n  \"maxhealth\",\n  \"position\",\n  \"mesg\",\n  \"column\",\n  \"color\",\n  \"size\",\n  \"bulletn\",\n  \"bullets\",\n  \"bullete\",\n  \"bulletw\",\n  \"bulletcolor\",\n  \"first\",\n  \"last\",\n  \"fade\",\n  \"out\",\n  \"in\",\n  \"block\",\n  \"sfx\",\n  \"intensity\",\n  \"set\",\n  \"palette\",\n  \"world\",\n  \"alignedrobot\",\n  \"go\",\n  \"saving\",\n  \"sensoronly\",\n  \"on\",\n  \"static\",\n  \"transparent\",\n  \"overlay\",\n  \"start\",\n  \"loop\",\n  \"edge\",\n  \"sam\",\n  \"play\",\n  \"percent\",\n  \"high\",\n  \"matches\",\n  \"none\",\n  \"input\",\n  \"dir\",\n  \"counter\",\n  \"duplicate\",\n  \"no\"\n};\n\nstatic const struct special_word sorted_special_word_list[] =\n{\n  { \"a\",                ARG_TYPE_IGNORE_A,               ARG_TYPE_IGNORE    },\n  { \"aligned\",          ALIGNED,                         ARG_TYPE_CONDITION },\n  { \"alignedew\",        ALIGNED_EW,                      ARG_TYPE_CONDITION },\n  { \"alignedns\",        ALIGNED_NS,                      ARG_TYPE_CONDITION },\n  { \"alignedrobot\",     ARG_TYPE_FRAGMENT_ALIGNEDROBOT,  ARG_TYPE_FRAGMENT  },\n  { \"all\",              ARG_TYPE_FRAGMENT_ALL,           ARG_TYPE_FRAGMENT  },\n  { \"ammo\",             AMMO,                            ARG_TYPE_THING     },\n  { \"ammos\",            GIVE_AMMO,                       ARG_TYPE_ITEM      },\n  { \"an\",               ARG_TYPE_IGNORE_AN,              ARG_TYPE_IGNORE    },\n  { \"and\",              ARG_TYPE_IGNORE_AND,             ARG_TYPE_IGNORE    },\n  { \"any\",              ARG_TYPE_FRAGMENT_ANY,           ARG_TYPE_FRAGMENT  },\n  { \"anydir\",           ANYDIR,                          ARG_TYPE_DIRECTION },\n  { \"arrow\",            ARG_TYPE_FRAGMENT_ARROW,         ARG_TYPE_FRAGMENT  },\n  { \"as\",               ARG_TYPE_IGNORE_AS,              ARG_TYPE_IGNORE    },\n  { \"at\",               ARG_TYPE_IGNORE_AT,              ARG_TYPE_IGNORE    },\n  { \"attack\",           ARG_TYPE_FRAGMENT_ATTACK,        ARG_TYPE_FRAGMENT  },\n  { \"bear\",             BEAR,                            ARG_TYPE_THING     },\n  { \"bearcub\",          BEAR_CUB,                        ARG_TYPE_THING     },\n  { \"beneath\",          BENEATH,                         ARG_TYPE_DIRECTION },\n  { \"block\",            ARG_TYPE_FRAGMENT_BLOCK,         ARG_TYPE_FRAGMENT  },\n  { \"blocked\",          BLOCKED,                         ARG_TYPE_CONDITION },\n  { \"bomb\",             BOMB,                            ARG_TYPE_THING     },\n  { \"boulder\",          BOULDER,                         ARG_TYPE_THING     },\n  { \"box\",              BOX,                             ARG_TYPE_THING     },\n  { \"breakaway\",        BREAKAWAY,                       ARG_TYPE_THING     },\n  { \"bullet\",           BULLET,                          ARG_TYPE_THING     },\n  { \"bulletcolor\",      ARG_TYPE_FRAGMENT_BULLETCOLOR,   ARG_TYPE_FRAGMENT  },\n  { \"bullete\",          ARG_TYPE_FRAGMENT_BULLETE,       ARG_TYPE_FRAGMENT  },\n  { \"bulletgun\",        BULLET_GUN,                      ARG_TYPE_THING     },\n  { \"bulletn\",          ARG_TYPE_FRAGMENT_BULLETN,       ARG_TYPE_FRAGMENT  },\n  { \"bullets\",          ARG_TYPE_FRAGMENT_BULLETS,       ARG_TYPE_FRAGMENT  },\n  { \"bulletw\",          ARG_TYPE_FRAGMENT_BULLETW,       ARG_TYPE_FRAGMENT  },\n  { \"by\",               ARG_TYPE_IGNORE_BY,              ARG_TYPE_IGNORE    },\n  { \"carpet\",           CARPET,                          ARG_TYPE_THING     },\n  { \"cave\",             CAVE,                            ARG_TYPE_THING     },\n  { \"ccwrotate\",        CCW_ROTATE,                      ARG_TYPE_THING     },\n  { \"char\",             ARG_TYPE_FRAGMENT_CHAR,          ARG_TYPE_FRAGMENT  },\n  { \"chest\",            CHEST,                           ARG_TYPE_THING     },\n  { \"coin\",             COIN,                            ARG_TYPE_THING     },\n  { \"coins\",            GIVE_COINS,                      ARG_TYPE_ITEM      },\n  { \"color\",            ARG_TYPE_FRAGMENT_COLOR,         ARG_TYPE_FRAGMENT  },\n  { \"column\",           ARG_TYPE_FRAGMENT_COLUMN,        ARG_TYPE_FRAGMENT  },\n  { \"counter\",          ARG_TYPE_FRAGMENT_COUNTER,       ARG_TYPE_FRAGMENT  },\n  { \"counters\",         ARG_TYPE_FRAGMENT_COUNTERS,      ARG_TYPE_FRAGMENT  },\n  { \"crate\",            CRATE,                           ARG_TYPE_THING     },\n  { \"customblock\",      CUSTOM_BLOCK,                    ARG_TYPE_THING     },\n  { \"custombox\",        CUSTOM_BOX,                      ARG_TYPE_THING     },\n  { \"custombreak\",      CUSTOM_BREAK,                    ARG_TYPE_THING     },\n  { \"customfloor\",      CUSTOM_FLOOR,                    ARG_TYPE_THING     },\n  { \"customhurt\",       CUSTOM_HURT,                     ARG_TYPE_THING     },\n  { \"custompush\",       CUSTOM_PUSH,                     ARG_TYPE_THING     },\n  { \"cw\",               CW,                              ARG_TYPE_DIRECTION },\n  { \"cwrotate\",         CW_ROTATE,                       ARG_TYPE_THING     },\n  { \"delpressed\",       DELPRESSED,                      ARG_TYPE_CONDITION },\n  { \"dir\",              ARG_TYPE_FRAGMENT_DIR,           ARG_TYPE_FRAGMENT  },\n  { \"door\",             DOOR,                            ARG_TYPE_THING     },\n  { \"down\",             SOUTH,                           ARG_TYPE_DIRECTION },\n  { \"downpressed\",      DOWNPRESSED,                     ARG_TYPE_CONDITION },\n  { \"dragon\",           DRAGON,                          ARG_TYPE_THING     },\n  { \"duplicate\",        ARG_TYPE_FRAGMENT_DUPLICATE,     ARG_TYPE_FRAGMENT  },\n  { \"e\",                EAST,                            ARG_TYPE_DIRECTION },\n  { \"east\",             EAST,                            ARG_TYPE_DIRECTION },\n  { \"edge\",             ARG_TYPE_FRAGMENT_EDGE,          ARG_TYPE_FRAGMENT  },\n  { \"edit\",             ARG_TYPE_FRAGMENT_EDIT,          ARG_TYPE_FRAGMENT  },\n  { \"else\",             ARG_TYPE_IGNORE_ELSE,            ARG_TYPE_IGNORE    },\n  { \"emovingwall\",      E_MOVING_WALL,                   ARG_TYPE_THING     },\n  { \"energizer\",        ENERGIZER,                       ARG_TYPE_THING     },\n  { \"ew\",               ARG_TYPE_FRAGMENT_EW,            ARG_TYPE_FRAGMENT  },\n  { \"ewater\",           E_WATER,                         ARG_TYPE_THING     },\n  { \"explosion\",        EXPLOSION,                       ARG_TYPE_THING     },\n  { \"eye\",              EYE,                             ARG_TYPE_THING     },\n  { \"fade\",             ARG_TYPE_FRAGMENT_FADE,          ARG_TYPE_FRAGMENT  },\n  { \"fake\",             FAKE,                            ARG_TYPE_THING     },\n  { \"fire\",             FIRE,                            ARG_TYPE_THING     },\n  { \"firewalking\",      FIRE_WALKING,                    ARG_TYPE_CONDITION },\n  { \"first\",            ARG_TYPE_FRAGMENT_FIRST,         ARG_TYPE_FRAGMENT  },\n  { \"fish\",             FISH,                            ARG_TYPE_THING     },\n  { \"floor\",            FLOOR,                           ARG_TYPE_THING     },\n  { \"flow\",             FLOW,                            ARG_TYPE_DIRECTION },\n  { \"for\",              ARG_TYPE_IGNORE_FOR,             ARG_TYPE_IGNORE    },\n  { \"forest\",           FOREST,                          ARG_TYPE_THING     },\n  { \"from\",             ARG_TYPE_IGNORE_FROM,            ARG_TYPE_IGNORE    },\n  { \"gate\",             GATE,                            ARG_TYPE_THING     },\n  { \"gem\",              GEM,                             ARG_TYPE_THING     },\n  { \"gems\",             GIVE_GEMS,                       ARG_TYPE_ITEM      },\n  { \"ghost\",            GHOST,                           ARG_TYPE_THING     },\n  { \"go\",               ARG_TYPE_FRAGMENT_GO,            ARG_TYPE_FRAGMENT  },\n  { \"goblin\",           GOBLIN,                          ARG_TYPE_THING     },\n  { \"goop\",             GOOP,                            ARG_TYPE_THING     },\n  { \"health\",           HEALTH,                          ARG_TYPE_THING     },\n  { \"healths\",          GIVE_HEALTH,                     ARG_TYPE_ITEM      },\n  { \"hibombs\",          GIVE_HIBOMBS,                    ARG_TYPE_ITEM      },\n  { \"high\",             ARG_TYPE_FRAGMENT_HIGH,          ARG_TYPE_FRAGMENT  },\n  { \"ice\",              ICE,                             ARG_TYPE_THING     },\n  { \"id\",               ARG_TYPE_FRAGMENT_ID,            ARG_TYPE_FRAGMENT  },\n  { \"idle\",             IDLE,                            ARG_TYPE_DIRECTION },\n  { \"image_file\",       IMAGE_FILE,                      ARG_TYPE_THING     },\n  { \"in\",               ARG_TYPE_FRAGMENT_IN,            ARG_TYPE_FRAGMENT  },\n  { \"input\",            ARG_TYPE_FRAGMENT_INPUT,         ARG_TYPE_FRAGMENT  },\n  { \"intensity\",        ARG_TYPE_FRAGMENT_INTENSITY,     ARG_TYPE_FRAGMENT  },\n  { \"inviswall\",        INVIS_WALL,                      ARG_TYPE_THING     },\n  { \"is\",               ARG_TYPE_IGNORE_IS,              ARG_TYPE_IGNORE    },\n  { \"item\",             ARG_TYPE_FRAGMENT_ITEM,          ARG_TYPE_FRAGMENT  },\n  { \"key\",              KEY,                             ARG_TYPE_THING     },\n  { \"last\",             ARG_TYPE_FRAGMENT_LAST,          ARG_TYPE_FRAGMENT  },\n  { \"lastshot\",         LASTSHOT,                        ARG_TYPE_CONDITION },\n  { \"lasttouch\",        LASTTOUCH,                       ARG_TYPE_CONDITION },\n  { \"lava\",             LAVA,                            ARG_TYPE_THING     },\n  { \"lavawalker\",       ARG_TYPE_FRAGMENT_LAVAWALKER,    ARG_TYPE_FRAGMENT  },\n  { \"lazer\",            LAZER,                           ARG_TYPE_THING     },\n  { \"lazergun\",         LAZER_GUN,                       ARG_TYPE_THING     },\n  { \"left\",             WEST,                            ARG_TYPE_DIRECTION },\n  { \"leftpressed\",      LEFTPRESSED,                     ARG_TYPE_CONDITION },\n  { \"life\",             LIFE,                            ARG_TYPE_THING     },\n  { \"line\",             LINE,                            ARG_TYPE_THING     },\n  { \"litbomb\",          LIT_BOMB,                        ARG_TYPE_THING     },\n  { \"lives\",            GIVE_LIVES,                      ARG_TYPE_ITEM      },\n  { \"lobombs\",          GIVE_LOBOMBS,                    ARG_TYPE_ITEM      },\n  { \"lock\",             LOCK,                            ARG_TYPE_THING     },\n  { \"loop\",             ARG_TYPE_FRAGMENT_LOOP,          ARG_TYPE_FRAGMENT  },\n  { \"magicgem\",         MAGIC_GEM,                       ARG_TYPE_THING     },\n  { \"matches\",          ARG_TYPE_FRAGMENT_MATCHES,       ARG_TYPE_FRAGMENT  },\n  { \"maxhealth\",        ARG_TYPE_FRAGMENT_MAXHEALTH,     ARG_TYPE_FRAGMENT  },\n  { \"mesg\",             ARG_TYPE_FRAGMENT_MESG,          ARG_TYPE_FRAGMENT  },\n  { \"mine\",             MINE,                            ARG_TYPE_THING     },\n  { \"missile\",          MISSILE,                         ARG_TYPE_THING     },\n  { \"missilegun\",       MISSILE_GUN,                     ARG_TYPE_THING     },\n  { \"mod\",              ARG_TYPE_FRAGMENT_MOD,           ARG_TYPE_FRAGMENT  },\n  { \"musicon\",          MUSICON,                         ARG_TYPE_CONDITION },\n  { \"n\",                NORTH,                           ARG_TYPE_DIRECTION },\n  { \"nmovingwall\",      N_MOVING_WALL,                   ARG_TYPE_THING     },\n  { \"no\",               ARG_TYPE_FRAGMENT_NO,            ARG_TYPE_FRAGMENT  },\n  { \"nodir\",            NODIR,                           ARG_TYPE_DIRECTION },\n  { \"none\",             ARG_TYPE_FRAGMENT_NONE,          ARG_TYPE_FRAGMENT  },\n  { \"nonlavawalker\",    ARG_TYPE_FRAGMENT_NONLAVAWALKER, ARG_TYPE_FRAGMENT  },\n  { \"nonpushable\",      ARG_TYPE_FRAGMENT_NONPUSHABLE,   ARG_TYPE_FRAGMENT  },\n  { \"normal\",           NORMAL,                          ARG_TYPE_THING     },\n  { \"north\",            NORTH,                           ARG_TYPE_DIRECTION },\n  { \"not\",              ARG_TYPE_FRAGMENT_NOT,           ARG_TYPE_FRAGMENT  },\n  { \"ns\",               ARG_TYPE_FRAGMENT_NS,            ARG_TYPE_FRAGMENT  },\n  { \"nwater\",           N_WATER,                         ARG_TYPE_THING     },\n  { \"of\",               ARG_TYPE_IGNORE_OF,              ARG_TYPE_IGNORE    },\n  { \"on\",               ARG_TYPE_FRAGMENT_ON,            ARG_TYPE_FRAGMENT  },\n  { \"opendoor\",         OPEN_DOOR,                       ARG_TYPE_THING     },\n  { \"opengate\",         OPEN_GATE,                       ARG_TYPE_THING     },\n  { \"opp\",              OPP,                             ARG_TYPE_DIRECTION },\n  { \"order\",            ARG_TYPE_FRAGMENT_ORDER,         ARG_TYPE_FRAGMENT  },\n  { \"out\",              ARG_TYPE_FRAGMENT_OUT,           ARG_TYPE_FRAGMENT  },\n  { \"overlay\",          ARG_TYPE_FRAGMENT_OVERLAY,       ARG_TYPE_FRAGMENT  },\n  { \"palette\",          ARG_TYPE_FRAGMENT_PALETTE,       ARG_TYPE_FRAGMENT  },\n  { \"pcsfxon\",          SOUNDON,                         ARG_TYPE_CONDITION },\n  { \"percent\",          ARG_TYPE_FRAGMENT_PERCENT,       ARG_TYPE_FRAGMENT  },\n  { \"play\",             ARG_TYPE_FRAGMENT_PLAY,          ARG_TYPE_FRAGMENT  },\n  { \"player\",           ARG_TYPE_FRAGMENT_PLAYER,        ARG_TYPE_FRAGMENT  },\n  { \"position\",         ARG_TYPE_FRAGMENT_POSITION,      ARG_TYPE_FRAGMENT  },\n  { \"potion\",           POTION,                          ARG_TYPE_THING     },\n  { \"pouch\",            POUCH,                           ARG_TYPE_THING     },\n  { \"pushable\",         ARG_TYPE_FRAGMENT_PUSHABLE,      ARG_TYPE_FRAGMENT  },\n  { \"pushablerobot\",    ROBOT_PUSHABLE,                  ARG_TYPE_THING     },\n  { \"pusher\",           PUSHER,                          ARG_TYPE_THING     },\n  { \"randany\",          RANDANY,                         ARG_TYPE_DIRECTION },\n  { \"randb\",            RANDB,                           ARG_TYPE_DIRECTION },\n  { \"randew\",           RANDEW,                          ARG_TYPE_DIRECTION },\n  { \"randnb\",           RANDNB,                          ARG_TYPE_DIRECTION },\n  { \"randne\",           RANDNE,                          ARG_TYPE_DIRECTION },\n  { \"randnot\",          RANDNOT,                         ARG_TYPE_DIRECTION },\n  { \"randns\",           RANDNS,                          ARG_TYPE_DIRECTION },\n  { \"random\",           ARG_TYPE_FRAGMENT_RANDOM,        ARG_TYPE_FRAGMENT  },\n  { \"randp\",            RANDP,                           ARG_TYPE_DIRECTION },\n  { \"ricochet\",         RICOCHET,                        ARG_TYPE_THING     },\n  { \"ricochetpanel\",    RICOCHET_PANEL,                  ARG_TYPE_THING     },\n  { \"right\",            EAST,                            ARG_TYPE_DIRECTION },\n  { \"rightpressed\",     RIGHTPRESSED,                    ARG_TYPE_CONDITION },\n  { \"ring\",             RING,                            ARG_TYPE_THING     },\n  { \"robot\",            ROBOT,                           ARG_TYPE_THING     },\n  { \"row\",              ARG_TYPE_FRAGMENT_ROW,           ARG_TYPE_FRAGMENT  },\n  { \"runner\",           RUNNER,                          ARG_TYPE_THING     },\n  { \"s\",                SOUTH,                           ARG_TYPE_DIRECTION },\n  { \"sam\",              ARG_TYPE_FRAGMENT_SAM,           ARG_TYPE_FRAGMENT  },\n  { \"saving\",           ARG_TYPE_FRAGMENT_SAVING,        ARG_TYPE_FRAGMENT  },\n  { \"score\",            GIVE_SCORE,                      ARG_TYPE_ITEM      },\n  { \"scroll\",           SCROLL,                          ARG_TYPE_THING     },\n  { \"seek\",             SEEK,                            ARG_TYPE_DIRECTION },\n  { \"seeker\",           SEEKER,                          ARG_TYPE_THING     },\n  { \"self\",             ARG_TYPE_FRAGMENT_SELF,          ARG_TYPE_FRAGMENT  },\n  { \"sensor\",           SENSOR,                          ARG_TYPE_THING     },\n  { \"sensoronly\",       ARG_TYPE_FRAGMENT_SENSORONLY,    ARG_TYPE_FRAGMENT  },\n  { \"set\",              ARG_TYPE_FRAGMENT_SET,           ARG_TYPE_FRAGMENT  },\n  { \"sfx\",              ARG_TYPE_FRAGMENT_SFX,           ARG_TYPE_FRAGMENT  },\n  { \"shark\",            SHARK,                           ARG_TYPE_THING     },\n  { \"shootingfire\",     SHOOTING_FIRE,                   ARG_TYPE_THING     },\n  { \"sign\",             SIGN,                            ARG_TYPE_THING     },\n  { \"size\",             ARG_TYPE_FRAGMENT_SIZE,          ARG_TYPE_FRAGMENT  },\n  { \"sliderew\",         SLIDER_EW,                       ARG_TYPE_THING     },\n  { \"sliderns\",         SLIDER_NS,                       ARG_TYPE_THING     },\n  { \"slimeblob\",        SLIMEBLOB,                       ARG_TYPE_THING     },\n  { \"smovingwall\",      S_MOVING_WALL,                   ARG_TYPE_THING     },\n  { \"snake\",            SNAKE,                           ARG_TYPE_THING     },\n  { \"solid\",            SOLID,                           ARG_TYPE_THING     },\n  { \"south\",            SOUTH,                           ARG_TYPE_DIRECTION },\n  { \"space\",            SPACE,                           ARG_TYPE_THING     },\n  { \"spacepressed\",     SPACEPRESSED,                    ARG_TYPE_CONDITION },\n  { \"spider\",           SPIDER,                          ARG_TYPE_THING     },\n  { \"spike\",            SPIKE,                           ARG_TYPE_THING     },\n  { \"spinninggun\",      SPINNING_GUN,                    ARG_TYPE_THING     },\n  { \"spittingtiger\",    SPITTING_TIGER,                  ARG_TYPE_THING     },\n  { \"sprite\",           SPRITE,                          ARG_TYPE_THING     },\n  { \"sprite_colliding\", SPR_COLLISION,                   ARG_TYPE_THING     },\n  { \"stairs\",           STAIRS,                          ARG_TYPE_THING     },\n  { \"start\",            ARG_TYPE_FRAGMENT_START,         ARG_TYPE_FRAGMENT  },\n  { \"static\",           ARG_TYPE_FRAGMENT_STATIC,        ARG_TYPE_FRAGMENT  },\n  { \"stillwater\",       STILL_WATER,                     ARG_TYPE_THING     },\n  { \"string\",           ARG_TYPE_FRAGMENT_STRING,        ARG_TYPE_FRAGMENT  },\n  { \"swater\",           S_WATER,                         ARG_TYPE_THING     },\n  { \"swimming\",         SWIMMING,                        ARG_TYPE_CONDITION },\n  { \"text\",             TEXT_ID,                         ARG_TYPE_THING     },\n  { \"the\",              ARG_TYPE_IGNORE_THE,             ARG_TYPE_IGNORE    },\n  { \"then\",             ARG_TYPE_IGNORE_THEN,            ARG_TYPE_IGNORE    },\n  { \"thick\",            ARG_TYPE_FRAGMENT_THICK,         ARG_TYPE_FRAGMENT  },\n  { \"thickweb\",         THICK_WEB,                       ARG_TYPE_THING     },\n  { \"thief\",            THIEF,                           ARG_TYPE_THING     },\n  { \"thin\",             ARG_TYPE_FRAGMENT_THIN,          ARG_TYPE_FRAGMENT  },\n  { \"tiles\",            TILES,                           ARG_TYPE_THING     },\n  { \"time\",             GIVE_TIME,                       ARG_TYPE_ITEM      },\n  { \"to\",               ARG_TYPE_IGNORE_TO,              ARG_TYPE_IGNORE    },\n  { \"touching\",         TOUCHING,                        ARG_TYPE_CONDITION },\n  { \"transparent\",      ARG_TYPE_FRAGMENT_TRANSPARENT,   ARG_TYPE_FRAGMENT  },\n  { \"transport\",        TRANSPORT,                       ARG_TYPE_THING     },\n  { \"tree\",             TREE,                            ARG_TYPE_THING     },\n  { \"under\",            BENEATH,                         ARG_TYPE_DIRECTION },\n  { \"up\",               NORTH,                           ARG_TYPE_DIRECTION },\n  { \"uppressed\",        UPPRESSED,                       ARG_TYPE_CONDITION },\n  { \"w\",                WEST,                            ARG_TYPE_DIRECTION },\n  { \"walking\",          WALKING,                         ARG_TYPE_CONDITION },\n  { \"web\",              WEB,                             ARG_TYPE_THING     },\n  { \"west\",             WEST,                            ARG_TYPE_DIRECTION },\n  { \"whirlpool\",        WHIRLPOOL_1,                     ARG_TYPE_THING     },\n  { \"whirlpool2\",       WHIRLPOOL_2,                     ARG_TYPE_THING     },\n  { \"whirlpool3\",       WHIRLPOOL_3,                     ARG_TYPE_THING     },\n  { \"whirlpool4\",       WHIRLPOOL_4,                     ARG_TYPE_THING     },\n  { \"with\",             ARG_TYPE_IGNORE_WITH,            ARG_TYPE_IGNORE    },\n  { \"wmovingwall\",      W_MOVING_WALL,                   ARG_TYPE_THING     },\n  { \"world\",            ARG_TYPE_FRAGMENT_WORLD,         ARG_TYPE_FRAGMENT  },\n  { \"wwater\",           W_WATER,                         ARG_TYPE_THING     }\n};\n\nstatic const int num_special_words =\n sizeof(sorted_special_word_list) / sizeof(struct special_word);\n\nstatic const struct special_word *find_special_word(const char *name,\n int name_length)\n{\n  char name_copy[33];\n  int bottom = 0, top = num_special_words - 1, middle;\n  const struct special_word *base = sorted_special_word_list;\n  int cmpval;\n\n  if(name_length > 32)\n    name_length = 32;\n\n  memcpy(name_copy, name, name_length);\n  name_copy[name_length] = 0;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    cmpval = strcasecmp(name_copy, (sorted_special_word_list[middle]).name);\n\n    if(cmpval > 0)\n      bottom = middle + 1;\n    else\n\n    if(cmpval < 0)\n      top = middle - 1;\n    else\n      return base + middle;\n  }\n\n  return NULL;\n}\n\n#define skip_sequence(condition)                                               \\\n  while(condition)                                                             \\\n  {                                                                            \\\n    str++;                                                                     \\\n  }                                                                            \\\n                                                                               \\\n  return str;                                                                  \\\n\nstatic inline boolean is_identifier_char(char c)\n{\n  return isalnum((int)c) || (c == '$') || (c == '_') ||\n                            (c == '#') || (c == '.');\n}\n\nstatic char *skip_whitespace(char *str)\n{\n  skip_sequence(isspace((int)*str));\n}\n\nstatic char *skip_decimal(char *str)\n{\n  skip_sequence(isdigit((int)*str));\n}\n\nstatic char *skip_hex(char *str)\n{\n  skip_sequence(isxdigit((int)*str));\n}\n\nstatic char *find_whitespace(char *str)\n{\n  skip_sequence(*str && !isspace((int)*str));\n}\n\nstatic char *find_newline(char *str)\n{\n  skip_sequence(*str && (*str != '\\n'));\n}\n\nchar *find_non_identifier_char(char *str)\n{\n  skip_sequence(is_identifier_char(*str));\n}\n\nstatic char *find_comment(char *src)\n{\n  char *next;\n\n  if(src[1] == '/')\n  {\n    // End of line, C++ style comment\n    next = find_newline(src + 2);\n    return next;\n  }\n\n  if(src[1] == '*')\n  {\n    // C style comment until '*/'\n    int nest_level = 0;\n    next = src + 2;\n\n    while(*next)\n    {\n      if((next[0] == '/') && (next[1] == '*'))\n      {\n        next++;\n        nest_level++;\n      }\n\n      if((next[0] == '*') && (next[1] == '/'))\n      {\n        next++;\n        if(nest_level == 0)\n          return next + 1;\n\n        nest_level--;\n      }\n\n      next++;\n    }\n  }\n\n  return src;\n}\n\nstatic boolean is_color(char *value)\n{\n  if((value[0] == 'c') && (value[1] != 0) &&\n   ((isxdigit((int)value[1]) || (value[1] == '?')) &&\n   (isxdigit((int)value[2]) || (value[2] == '?')) &&\n   (isspace(value[3]) || (value[3] == '\\0'))))\n  {\n    return true;\n  }\n  else\n  {\n    return false;\n  }\n}\n\nstatic boolean is_basic_color(char *value)\n{\n  if((value[0] == 'c') && (value[1] != 0) &&\n   isxdigit((int)value[1]) &&\n   isxdigit((int)value[2]) &&\n   (isspace(value[3]) || (value[3] == '\\0')))\n  {\n    return true;\n  }\n  else\n  {\n    return false;\n  }\n}\n\nstatic boolean is_param(char *value)\n{\n  if((value[0] == 'p') && (value[1] != 0) &&\n   ((isxdigit((int)value[1]) && isxdigit((int)value[2])) ||\n   ((value[1] == '?') && (value[2] == '?'))) &&\n   (isspace(value[3]) || (value[3] == '\\0')))\n  {\n    return true;\n  }\n  else\n  {\n    return false;\n  }\n}\n\nstatic boolean is_basic_param(char *value)\n{\n  if((value[0] == 'p') && (value[1] != 0) &&\n   isxdigit((int)value[1]) &&\n   isxdigit((int)value[2]) &&\n   (isspace(value[3]) || (value[3] == '\\0')))\n  {\n    return true;\n  }\n  else\n  {\n    return false;\n  }\n}\n\nstatic boolean is_string_valued_ident(char *value, size_t len, boolean is_interpolation)\n{\n  boolean ret;\n  char tmp;\n\n  // TODO: make this $INPUT?\n  if(is_interpolation && len == 5 && !memcasecmp(value, \"INPUT\", len))\n    return true;\n\n  // TODO: this may need to account for interpolation too...\n  tmp = value[len];\n  value[len] = '\\0';\n  ret = is_string(value);\n  value[len] = tmp;\n  return ret;\n}\n\nstatic int get_param(char *cmd_line)\n{\n  if((cmd_line[1] == '?') && (cmd_line[2] == '?'))\n    return 0x100;\n\n  return strtol(cmd_line + 1, NULL, 16);\n}\n\n#ifdef CONFIG_EDITOR\n\nint get_thing(char *name, int name_length)\n{\n  const struct special_word *special_word =\n   find_special_word(name, name_length);\n\n  if((special_word == NULL) || (special_word->arg_type != ARG_TYPE_THING))\n    return -1;\n\n  return special_word->instance_type;\n}\n\n#endif /* CONFIG_EDITOR */\n\n__editor_maybe_static int unescape_char(char *dest, char c)\n{\n  switch(c)\n  {\n    case 0:\n      dest[0] = '\\\\';\n      dest[1] = '0';\n      return 2;\n\n    case '\\n':\n      dest[0] = '\\\\';\n      dest[1] = 'n';\n      return 2;\n\n    case '\\r':\n      dest[0] = '\\\\';\n      dest[1] = 'r';\n      return 2;\n\n    case '\\t':\n      dest[0] = '\\\\';\n      dest[1] = 't';\n      return 2;\n\n    case '\\\\':\n      dest[0] = '\\\\';\n      dest[1] = '\\\\';\n      return 2;\n  }\n\n  dest[0] = c;\n  return 1;\n}\n\nstatic int escape_chars(char *dest, char *src, int escape_char,\n boolean escape_interpolation)\n{\n  if(src[0] == 0)\n    return 0;\n\n  if(src[0] == '\\\\')\n  {\n    if(escape_interpolation && (src[1] == '{' || src[1] == '}'))\n    {\n      *dest = src[1];\n      return 2;\n    }\n\n    if(src[1] == escape_char)\n    {\n      *dest = escape_char;\n      return 2;\n    }\n\n    switch(src[1])\n    {\n      case '0':\n        *dest = 0;\n        return 2;\n\n      case 'n':\n        *dest = '\\n';\n        return 2;\n\n      case 'r':\n        *dest = '\\r';\n        return 2;\n\n      case 't':\n        *dest = '\\t';\n        return 2;\n\n      case '\\\\':\n        *dest = '\\\\';\n        return 2;\n    }\n  }\n\n  *dest = *src;\n  return 1;\n}\n\nstatic char *get_character_interpolated(char *src, char terminator,\n int *contains_expressions);\nstatic char *get_expression(char *src, const char terminator1, const char terminator2);\n\nenum is_op_type\n{\n  NOT_OPERATOR = 0,\n  IS_OPERATOR_UNARY = 0, // ignore these; they'll get picked up as token pieces.\n\n  IS_OPERATOR = 1,\n  IS_OPERATOR_TERNARY_OPEN = 2,\n  IS_OPERATOR_TERNARY_CLOSE = 3,\n};\n\nstatic char *get_expr_binary_operator_token(char *src, int *_is_operator)\n{\n  int is_operator = IS_OPERATOR;\n\n  switch(*src)\n  {\n    // One char operators, not the start of a two char one.\n    case '+':\n    case '-': // Can be a regular operator or a unary operator.\n    case '/':\n    case '%':\n    case '&':\n    case '|':\n    case '^':\n    case '=':\n      break;\n\n    // Unary only.\n    case '~':\n      is_operator = IS_OPERATOR_UNARY;\n      break;\n\n    // Ternary operator special case - this needs to be counted\n    case '?':\n      is_operator = IS_OPERATOR_TERNARY_OPEN;\n      break;\n\n    // Ternary operator special case - this needs to be counted\n    case ':':\n      is_operator = IS_OPERATOR_TERNARY_CLOSE;\n      break;\n\n    case '*':\n      if(src[1] == '*')\n        src++;\n      break;\n\n    case '>':\n      if(src[1] == '=')\n      {\n        src++;\n      }\n      else\n\n      if(src[1] == '>')\n      {\n        if(src[2] == '>')\n        {\n          src++;\n        }\n        src++;\n      }\n      break;\n\n    case '<':\n      if((src[1] == '<') ||\n       (src[1] == '='))\n      {\n        src++;\n      }\n      break;\n\n    case '!':\n      if(src[1] == '=')\n      {\n        src++;\n      }\n      else\n      {\n        is_operator = IS_OPERATOR_UNARY;\n      }\n      break;\n\n    default:\n      is_operator = NOT_OPERATOR;\n      break;\n  }\n\n  if(is_operator)\n    src++;\n\n  *_is_operator = is_operator;\n  return src;\n}\n\nstatic char *get_expr_value_token(char *src)\n{\n  char *next = src;\n\n  // Unary operators are okay, just eats up spaces until next ones.\n  while((*next == '-') || (*next == '~') || (*next == '!'))\n    next = skip_whitespace(next + 1);\n\n  switch(*next)\n  {\n    case '0':\n      if(next[1] == 'x')\n        next = skip_hex(next + 2);\n      else\n        next = skip_decimal(next);\n      break;\n\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9':\n      next = skip_decimal(next);\n      break;\n\n    case '(':\n      // Start of an expression, verify that it is one.\n      next = get_expression(next + 1, ')', ')');\n      if(*next != ')')\n        return src;\n\n      next++;\n      break;\n\n    case '`':\n    {\n      // This is exactly like a name in normal contexts.\n      int contains_expressions;\n      next = get_character_interpolated(next + 1, '`', &contains_expressions);\n\n      if(*next != '`')\n        return src;\n\n      next++;\n      break;\n    }\n\n    default:\n    {\n      char *start = next;\n\n      // Grab a valid identifier out of this.\n      next = find_non_identifier_char(next);\n\n      // After skipping spaces/unaries, we're at a non-identifier char? Invalid\n      if(next == start)\n        return src;\n\n      break;\n    }\n  }\n\n  return next;\n}\n\nstatic char *get_expression(char *src, const char terminator1, const char terminator2)\n{\n  char *next, *next_next;\n  int ternary_level = 0;\n  int is_operator;\n\n  // Skip initial whitespace.\n  src = skip_whitespace(src);\n\n  // Need a value to kick things off.\n  next = get_expr_value_token(src);\n\n  if(next == src)\n    return src;\n\n  // Now keep going until we hit the end - for every\n  // time we don't we need an operator then a value.\n  while(true)\n  {\n    next = skip_whitespace(next);\n    if(*next == terminator1 || *next == terminator2)\n      break;\n    if(*next == '\\0')\n      return src;\n\n    next = get_expr_binary_operator_token(next, &is_operator);\n    if(!is_operator)\n      return src;\n\n    if(is_operator == IS_OPERATOR_TERNARY_OPEN)\n      ternary_level++;\n\n    else if(is_operator == IS_OPERATOR_TERNARY_CLOSE)\n      ternary_level--;\n\n    if(ternary_level < 0)\n      return src;\n\n    next = skip_whitespace(next);\n    // Now get a value token.\n    next_next = get_expr_value_token(next);\n    if(next_next == next)\n      return src;\n\n    next = next_next;\n  }\n\n  if(ternary_level > 0)\n    return src;\n\n  return next;\n}\n\nstatic char *get_interpolation_expression(char *src)\n{\n  char *next = src;\n  char *next_next;\n  int contains_expressions;\n  boolean numeric_only;\n\n  // Now keep going until we hit the end.\n  while(*next != '}')\n  {\n    next = skip_whitespace(next);\n\n    // TODO: replace this with proper format syntax.\n    numeric_only = false;\n    if(*next == '#' || *next == '+')\n    {\n      numeric_only = true;\n      next = skip_whitespace(next + 1);\n    }\n\n    switch(*next)\n    {\n      case 0:\n        return src;\n\n      case '(':\n        /* Numeric expression. */\n        next = get_expression(next + 1, ')', ')');\n        if(*next != ')')\n          return src;\n\n        next++;\n        break;\n\n      case '\"':\n        // String literal.\n        next = get_character_interpolated(next + 1, '\"',\n         &contains_expressions);\n        if(*next != '\"')\n          return src;\n        if(numeric_only)\n          return src;\n\n        next++;\n        break;\n\n      case '`':\n        // String identifier?\n        next_next = get_character_interpolated(next + 1, '`',\n         &contains_expressions);\n        if(next_next <= next + 1 || *next_next != '`')\n          return src;\n\n        // Note: this can't reliably detect all strings due to nested string\n        // interpolation. This shouldn't hurt anything (currently).\n        if(!is_string_valued_ident(next + 1, next_next - next - 1, false))\n        {\n          // Numeric expression without parentheses?\n          next_next = get_expression(next, ',', '}');\n          if(next_next <= next)\n            return src;\n        }\n        else\n        {\n          if(numeric_only)\n            return src;\n\n          next_next++;\n        }\n\n        next = next_next;\n        break;\n\n      default:\n        // String identifier?\n        next_next = find_non_identifier_char(next);\n\n        if(next_next <= next ||\n         !is_string_valued_ident(next, next_next - next, true))\n        {\n          // Numeric expression without parentheses?\n          next_next = get_expression(next, ',', '}');\n          if(next_next <= next)\n            return src;\n        }\n        else\n        {\n          if(numeric_only)\n            return src;\n        }\n\n        next = next_next;\n        break;\n    }\n\n    // Find comma or terminating >.\n    next = skip_whitespace(next);\n    if(*next != ',' && *next != '}')\n      return src;\n    if(*next == ',')\n      next++;\n  }\n\n  return next;\n}\n\nstatic char *get_character_interpolated(char *src, char terminator,\n int *contains_expressions)\n{\n  int num_expressions = 0;\n  char current_char = *src;\n  char *next = src;\n\n  while(current_char && (current_char != terminator))\n  {\n    if(current_char == '}')\n    {\n      // Unescaped } is a compile error.\n      return src;\n    }\n    else\n\n    if(current_char == '{')\n    {\n      next = get_interpolation_expression(next + 1);\n      num_expressions++;\n      if(*next != '}')\n      {\n        *contains_expressions = -num_expressions;\n        return src;\n      }\n    }\n    else\n\n    if((current_char == '\\\\') && (next[1] != 0))\n    {\n      next++;\n    }\n\n    next++;\n    current_char = *next;\n  }\n\n  *contains_expressions = num_expressions;\n\n  return next;\n}\n\nstatic char *get_token(char *src, struct token *token)\n{\n  char *next;\n  int contains_expression = 0;\n  enum token_type token_type;\n\n  src = skip_whitespace(src);\n\n  switch(*src)\n  {\n    case '\"':\n      // String literal.\n      next = get_character_interpolated(src + 1, '\"', &contains_expression);\n\n      token_type = TOKEN_TYPE_STRING_LITERAL;\n\n      if(*next != '\"')\n        token_type |= TOKEN_TYPE_INVALID;\n      else\n        next++;\n\n      break;\n\n    case '`':\n      // Name.\n      next = get_character_interpolated(src + 1, '`', &contains_expression);\n\n      token_type = TOKEN_TYPE_NAME;\n\n      if(*next != '`')\n        token_type |= TOKEN_TYPE_INVALID;\n      else\n        next++;\n\n      break;\n\n\n    case '-':\n    case '0':\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9':\n      // Immediate - maybe a hex immediate.\n      if((src[0] == '0') && (src[1] == 'x'))\n      {\n        token->arg_value.numeric_literal = strtol(src, &next, 16);\n        token_type = TOKEN_TYPE_NUMERIC_LITERAL_BASE16;\n      }\n      else\n      {\n        token->arg_value.numeric_literal = strtol(src, &next, 10);\n        token_type = TOKEN_TYPE_NUMERIC_LITERAL_BASE10;\n      }\n\n      // TODO: remove when new bytecode\n      if(token->arg_value.numeric_literal < -32768 ||\n       token->arg_value.numeric_literal > 32767)\n        token_type |= TOKEN_TYPE_INVALID;\n\n      if(next == src)\n        token_type |= TOKEN_TYPE_INVALID;\n\n      break;\n\n\n    case '\\'':\n      // If the first letter is a single quote then it's a character;\n      token_type = TOKEN_TYPE_CHARACTER_LITERAL;\n\n      next = src + 1 + escape_chars(&(token->arg_value.char_literal),\n       src + 1, -1, false);\n\n      if((next == src + 1) || (*next != '\\''))\n        token_type |= TOKEN_TYPE_INVALID;\n      else\n        next++;\n\n      break;\n\n    case '(':\n      // Expression.\n      next = get_expression(src + 1, ')', ')');\n\n      token_type = TOKEN_TYPE_EXPRESSION;\n\n      if(*next != ')')\n        token_type |= TOKEN_TYPE_INVALID;\n      else\n        next++;\n\n      break;\n\n    case '<':\n      // Match <, <=, or <>\n      token_type = TOKEN_TYPE_EQUALITY;\n      next = src + 1;\n      if(src[1] == '=')\n      {\n        token->arg_value.equality_type = LESS_THAN_OR_EQUAL;\n        next++;\n      }\n      else\n\n      if(src[1] == '>')\n      {\n        token->arg_value.equality_type = NOT_EQUAL;\n        next++;\n      }\n      else\n      {\n        token->arg_value.equality_type = LESS_THAN;\n      }\n\n      break;\n\n    // Match >, >=, or ><\n    case '>':\n      token_type = TOKEN_TYPE_EQUALITY;\n      next = src + 1;\n      if(src[1] == '=')\n      {\n        token->arg_value.equality_type = GREATER_THAN_OR_EQUAL;\n        next++;\n      }\n      else\n\n      if(src[1] == '<')\n      {\n        token->arg_value.equality_type = NOT_EQUAL;\n        next++;\n      }\n      else\n      {\n        token->arg_value.equality_type = GREATER_THAN;\n      }\n\n      break;\n\n    // Match =, ==, ===, =<, or =>\n    case '=':\n      token_type = TOKEN_TYPE_EQUALITY;\n      token->arg_value.equality_type = EQUAL;\n\n      next = src + 1;\n      if(src[1] == '=')\n      {\n        if(src[2] == '=')\n        {\n          token->arg_value.equality_type = EXACTLY_EQUAL;\n          next++;\n        }\n        next++;\n      }\n      else\n\n      if(src[1] == '<')\n      {\n        token->arg_value.equality_type = LESS_THAN_OR_EQUAL;\n        next++;\n      }\n      else\n\n      if(src[1] == '>')\n      {\n        token->arg_value.equality_type = GREATER_THAN_OR_EQUAL;\n        next++;\n      }\n\n      break;\n\n    // Match ! or !=\n    case '!':\n      token_type = TOKEN_TYPE_EQUALITY;\n      next = src + 1;\n      if(src[1] == '=')\n      {\n        token->arg_value.equality_type = NOT_EQUAL;\n        next += 1;\n      }\n      else\n      {\n        token_type |= TOKEN_TYPE_INVALID;\n      }\n      break;\n\n    // Match ?= or ?==\n    case '?':\n      token_type = TOKEN_TYPE_EQUALITY;\n      next = src + 1;\n      if(src[1] == '=')\n      {\n        token->arg_value.equality_type = WILD_EQUAL;\n        next++;\n\n        if(src[2] == '=')\n        {\n          token->arg_value.equality_type = WILD_EXACTLY_EQUAL;\n          next++;\n        }\n      }\n      else\n      {\n        token_type |= TOKEN_TYPE_INVALID;\n      }\n      break;\n\n    // Comment, maybe\n    case '/':\n      token_type = TOKEN_TYPE_COMMENT;\n      next = find_comment(src);\n      if(next == src)\n        token_type |= TOKEN_TYPE_INVALID;\n      break;\n\n    default:\n      if(is_color(src))\n      {\n        // Color\n        next = src + 3;\n        // We need two different types, since basic colors can also be\n        // identifiers. Wildcard colors can't, since they have ?s.\n        if(is_basic_color(src))\n          token_type = TOKEN_TYPE_BASIC_COLOR;\n        else\n          token_type = TOKEN_TYPE_WILDCARD_COLOR;\n      }\n      else\n\n      if(is_param(src))\n      {\n        // Param\n        next = src + 3;\n        // We need two different types, since basic params can also be\n        // identifiers. Wildcard params can't, since they have ?s.\n        if(is_basic_param(src))\n          token_type = TOKEN_TYPE_BASIC_PARAM;\n        else\n          token_type = TOKEN_TYPE_WILDCARD_PARAM;\n      }\n\n      else\n      {\n        // It's just a string - could be an ignore word or otherwise a basic\n        // identifier.\n        next = find_non_identifier_char(src);\n        token_type = TOKEN_TYPE_BASIC_IDENTIFIER;\n      }\n\n      // Until we decide we care about what special word this is then this\n      // stays NULL.\n      token->value_is_cached = false;\n      break;\n  }\n\n  // The next character must be the terminator or a space.\n  if(!isspace(*next) && *next != '\\0')\n    token_type = TOKEN_TYPE_INVALID;\n\n  token->length = next - src;\n  token->value = src;\n  token->type = token_type;\n\n  return next;\n}\n\nstruct command_set\n{\n  const char *const name;\n  int name_length;\n  int count;\n  const int offsets[16];\n};\n\n#define command_set(name, count, ...) \\\n  { name, sizeof(name) - 1, count, { __VA_ARGS__ } }\n\nstatic const struct command_set sorted_command_list[] =\n{\n  command_set(\"%\",              1, 116                                 ),\n  command_set(\"&\",              1, 117                                 ),\n  command_set(\"*\",              1, 102                                 ),\n  command_set(\".\",              1, 107                                 ),\n  command_set(\"/\",              1, 101                                 ),\n  command_set(\":\",              1, 106                                 ),\n  //                               3    2\n  command_set(\"?\",              2, 105, 104                            ),\n  command_set(\"[\",              1, 103                                 ),\n  command_set(\"abort\",          1, 253                                 ),\n  command_set(\"ask\",            1, 145                                 ),\n  command_set(\"avalanche\",      1, 131                                 ),\n  //                               3  1    1    1    1\n  command_set(\"become\",         5, 6, 134, 124, 125, 133               ),\n  command_set(\"blind\",          1, 126                                 ),\n  //                               2    2\n  command_set(\"board\",          2, 121, 122                            ),\n  command_set(\"bulletcolor\",    1, 137                                 ),\n  command_set(\"bullete\",        1, 89                                  ),\n  command_set(\"bulletn\",        1, 87                                  ),\n  command_set(\"bullets\",        1, 88                                  ),\n  command_set(\"bulletw\",        1, 90                                  ),\n  command_set(\"center\",         1, 154                                 ),\n  //                               6    5    5    5    4    3    3\n  command_set(\"change\",         7, 135, 147, 148, 245, 143, 210, 246   ),\n  //                               16 1\n  command_set(\"char\",           2, 7, 123                              ),\n  command_set(\"clear\",          1, 155                                 ),\n  command_set(\"clip\",           1, 202                                 ),\n  //                               4    3    2    2    1\n  command_set(\"color\",          5, 212, 211, 213, 214, 8               ),\n  //                               8    7    4    3    2\n  command_set(\"copy\",           5, 243, 201, 119, 206, 132             ),\n  //                               2   1   1\n  command_set(\"copyrobot\",      3, 83, 82, 84                          ),\n  command_set(\"cycle\",          1, 3                                   ),\n  //                               4   2\n  command_set(\"dec\",            2, 96, 12                              ),\n  //                               1   0\n  command_set(\"die\",            2, 80, 1                               ),\n  //                               2    1\n  command_set(\"disable\",        2, 254, 236                            ),\n  command_set(\"divide\",         1, 218                                 ),\n  command_set(\"double\",         1, 27                                  ),\n  //                               3   2\n  command_set(\"duplicate\",      2, 86, 85                              ),\n  //                               2    2    1\n  command_set(\"enable\",         3, 237, 255, 235                       ),\n  //                               1   1   1   0\n  command_set(\"end\",            4, 41, 42, 44, 0                       ),\n  command_set(\"endgame\",        1, 36                                  ),\n  command_set(\"endlife\",        1, 37                                  ),\n  //                               2    2    2    2    2\n  command_set(\"enemy\",          5, 181, 182, 183, 184, 187             ),\n  //                               5    3    2\n  command_set(\"exchange\",       3, 172, 170, 152                       ),\n  command_set(\"explode\",        1, 31                                  ),\n  command_set(\"fillhealth\",     1, 146                                 ),\n  command_set(\"firewalker\",     1, 127                                 ),\n  command_set(\"flip\",           1, 205                                 ),\n  command_set(\"freezetime\",     1, 128                                 ),\n  command_set(\"give\",           1, 33                                  ),\n  //                               2   1\n  command_set(\"givekey\",        2, 92, 91                              ),\n  command_set(\"go\",             1, 4                                   ),\n  command_set(\"goto\",           1, 29                                  ),\n  command_set(\"gotoxy\",         1, 9                                   ),\n  command_set(\"half\",           1, 28                                  ),\n  command_set(\"if\",             16,\n  // 6   6   6   5   5   5   4   4   4    4    4    3   3   3    3    2\n    23, 24, 26, 20, 21, 22, 16, 66, 113, 114, 231, 19, 25, 112, 227, 18),\n  //                               4   2\n  command_set(\"inc\",            2, 95, 11                              ),\n  command_set(\"input\",          1, 111                                 ),\n  command_set(\"jump\",           1, 144                                 ),\n  //                               2   1\n  command_set(\"laybomb\",        2, 74, 73                              ),\n  command_set(\"lazerwall\",      1, 78                                  ),\n  //                               3    2\n  command_set(\"load\",           2, 216, 222                            ),\n  //                               1   1   1   0\n  command_set(\"lockplayer\",     4, 58, 59, 60, 56                      ),\n  command_set(\"lockscroll\",     1, 229                                 ),\n  command_set(\"lockself\",       1, 51                                  ),\n  //                               1    1\n  command_set(\"loop\",           2, 251, 252                            ),\n  command_set(\"message\",        1, 139                                 ),\n  command_set(\"missilecolor\",   1, 138                                 ),\n  //                               3    3    3    2    1\n  command_set(\"mod\",            5, 157, 200, 224, 199, 38              ),\n  command_set(\"modulo\",         1, 219                                 ),\n  //                               5    3   2\n  command_set(\"move\",           3, 118, 62, 61                         ),\n  command_set(\"multiply\",       1, 217                                 ),\n  //                               2    2    2    2    2\n  command_set(\"neutral\",        5, 177, 178, 179, 180, 186             ),\n  command_set(\"open\",           1, 50                                  ),\n  //                               1    1    1\n  command_set(\"overlay\",        3, 239, 240, 241                       ),\n  command_set(\"persistent\",     1, 232                                 ),\n  //                               2   1\n  command_set(\"play\",           2, 49, 43                              ),\n  //                               3    2    2    2    2    2    2\n  command_set(\"player\",         7, 220, 115, 173, 174, 175, 176, 185   ),\n  command_set(\"playercolor\",    1, 136                                 ),\n  command_set(\"push\",           1, 203                                 ),\n  //                               5   5    5    4   3   2\n  command_set(\"put\",            6, 79, 100, 242, 32, 63, 67            ),\n  command_set(\"rel\",            9,\n  // 2    2    2    2    2    2    1    1    1\n     193, 194, 195, 196, 197, 198, 140, 141, 142                       ),\n  command_set(\"resetview\",      1, 156                                 ),\n  //                               5    3    2   2\n  command_set(\"restore\",        4, 171, 169, 55, 151                   ),\n  command_set(\"rotateccw\",      1, 70                                  ),\n  command_set(\"rotatecw\",       1, 69                                  ),\n  command_set(\"sam\",            1, 39                                  ),\n  //                               3    2\n  command_set(\"save\",           2, 168, 150                            ),\n  command_set(\"scroll\",         1, 204                                 ),\n  command_set(\"scrollarrow\",    1, 163                                 ),\n  command_set(\"scrollbase\",     1, 159                                 ),\n  command_set(\"scrollcorner\",   1, 160                                 ),\n  command_set(\"scrollpointer\",  1, 162                                 ),\n  command_set(\"scrolltitle\",    1, 161                                 ),\n  //                               3    2\n  command_set(\"scrollview\",     2, 225, 110                            ),\n  //                               3   3   3   2\n  command_set(\"send\",           4, 81, 99, 30, 53,                     ),\n  //                               5    4   3    3    2   2\n  command_set(\"set\",            6, 215, 97, 120, 153, 10, 149          ),\n  command_set(\"sfx\",            1, 48                                  ),\n  command_set(\"shoot\",          1, 72                                  ),\n  command_set(\"shootmissile\",   1, 75                                  ),\n  command_set(\"shootseeker\",    1, 76                                  ),\n  command_set(\"slowtime\",       1, 129                                 ),\n  command_set(\"spitfire\",       1, 77                                  ),\n  command_set(\"status\",         1, 238                                 ),\n  command_set(\"swap\",           1, 226                                 ),\n  command_set(\"switch\",         1, 71                                  ),\n  //                               3   2\n  command_set(\"take\",           2, 35, 34                              ),\n  //                               2   1\n  command_set(\"takekey\",        2, 94, 93                              ),\n  command_set(\"teleport\",       1, 109                                 ),\n  command_set(\"trade\",          1, 98                                  ),\n  command_set(\"try\",            1, 68                                  ),\n  command_set(\"unlockplayer\",   1, 57                                  ),\n  command_set(\"unlockscroll\",   1, 230                                 ),\n  command_set(\"unlockself\",     1, 52                                  ),\n  //                               3    2\n  command_set(\"viewport\",       2, 165, 164                            ),\n  //                               1   1\n  command_set(\"volume\",         2, 40, 158                             ),\n  //                               2    2   1  1\n  command_set(\"wait\",           4, 233, 45, 2, 46                      ),\n  command_set(\"walk\",           1, 5                                   ),\n  command_set(\"wind\",           1, 130                                 ),\n  command_set(\"write\",          1, 247                                 ),\n  command_set(\"zap\",            1, 54                                  ),\n  command_set(\"|\",              1, 108                                 )\n};\n\nstatic const int num_command_names =\n sizeof(sorted_command_list) / sizeof(struct command_set);\n\nstatic struct command_set empty_command_set = { \"\", 0, 0, { 0 } };\n\nstatic const struct command_set *find_command_set(const char *name,\n int name_length)\n{\n  char name_copy[33];\n  int bottom = 0, top = num_command_names - 1, middle;\n  const struct command_set *base = sorted_command_list;\n  int cmpval;\n\n  if(name_length > 32)\n    name_length = 32;\n\n  memcpy(name_copy, name, name_length);\n  name_copy[name_length] = 0;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    cmpval = strcasecmp(name_copy, (sorted_command_list[middle]).name);\n\n    if(cmpval > 0)\n      bottom = middle + 1;\n    else\n\n    if(cmpval < 0)\n      top = middle - 1;\n    else\n      return base + middle;\n  }\n\n  return NULL;\n}\n\n// This structure acts as a cache so we don't have to keep grabbing the same\n// tokens we already got.\n\nstruct token_collection\n{\n  struct token *tokens;\n  int token_count;\n  int tokens_allocated;\n  int token_position;\n  enum arg_type_indexed *match_types;\n  int match_types_allocated;\n  char *next;\n};\n\nstatic void token_collection_initialize(struct token_collection\n *token_collection, char *src)\n{\n  token_collection->tokens = malloc(sizeof(struct token));\n  token_collection->token_count = 1;\n  token_collection->tokens_allocated = 1;\n  token_collection->next = src;\n}\n\nstatic void token_collection_start_match_sequence(struct token_collection\n *token_collection)\n{\n  token_collection->token_position = 1;\n  token_collection->match_types = malloc(sizeof(enum arg_type_indexed));\n  token_collection->match_types_allocated = 1;\n}\n\nstatic struct token *token_collection_get_token(struct token_collection\n *token_collection, enum arg_type_indexed **_match_type)\n{\n  struct token new_token;\n  struct token *tokens = token_collection->tokens;\n  enum arg_type_indexed *match_types = token_collection->match_types;\n  int token_count = token_collection->token_count;\n  int tokens_allocated = token_collection->tokens_allocated;\n  int token_position = token_collection->token_position;\n  int match_types_allocated = token_collection->match_types_allocated;\n  char *next;\n\n  // See if we need to get a new token off the stream, or if we already\n  // have one cached.\n  if(token_position >= token_count)\n  {\n    next = get_token(token_collection->next, &new_token);\n\n    // If the token received is not valid then we return NULL to\n    // signify that we couldn't get a token.\n    if((new_token.length == 0) || (new_token.type & TOKEN_TYPE_INVALID))\n      return NULL;\n\n    token_collection->next = next;\n\n    // We may need to allocate more space for the incoming token.\n    if(token_count >= tokens_allocated)\n    {\n      tokens_allocated *= 2;\n      tokens = realloc(tokens, sizeof(struct token) * tokens_allocated);\n      token_collection->tokens_allocated = tokens_allocated;\n      token_collection->tokens = tokens;\n    }\n\n    memcpy(&(tokens[token_count]), &new_token, sizeof(struct token));\n\n    token_collection->token_count = token_count + 1;\n  }\n\n  // We may need to allocate more in the match table.\n  if(token_position >= match_types_allocated)\n  {\n    match_types_allocated *= 2;\n    match_types = realloc(match_types,\n     sizeof(enum arg_type_indexed) * match_types_allocated);\n\n    token_collection->match_types_allocated = match_types_allocated;\n    token_collection->match_types = match_types;\n  }\n\n  token_collection->token_position = token_position + 1;\n\n  *_match_type = &(match_types[token_position]);\n  return &(tokens[token_position]);\n}\n\nstatic boolean match_special_word_condition(struct token *current_token,\n enum token_type token_type)\n{\n  if(token_type != TOKEN_TYPE_BASIC_IDENTIFIER)\n    return false;\n\n  if(!current_token->value_is_cached)\n  {\n    current_token->arg_value.special_word =\n     find_special_word(current_token->value, current_token->length);\n    current_token->value_is_cached = true;\n  }\n\n  return current_token->arg_value.special_word != NULL;\n}\n\nstatic boolean match_special_word_type(struct token *current_token,\n enum token_type token_type, enum arg_type arg_type_instance)\n{\n  return match_special_word_condition(current_token, token_type) &&\n   current_token->arg_value.special_word->arg_type == arg_type_instance;\n}\n\nstatic boolean match_arg_and_special_word_type(struct token *current_token,\n enum token_type token_type, enum arg_type arg_type,\n enum arg_type arg_type_instance)\n{\n  return (arg_type & arg_type_instance) &&\n   match_special_word_type(current_token, token_type, arg_type_instance);\n}\n\n#define get_token_skip_comments(do_on_null)                                    \\\n  current_token = token_collection_get_token(token_collection, &match_type);   \\\n  if(current_token == NULL)                                                    \\\n    do_on_null;                                                                \\\n                                                                               \\\n  while(current_token->type == TOKEN_TYPE_COMMENT)                             \\\n  {                                                                            \\\n    *match_type = ARG_TYPE_INDEXED_COMMENT;                                    \\\n    current_token = token_collection_get_token(token_collection, &match_type); \\\n                                                                               \\\n    if(current_token == NULL)                                                  \\\n      do_on_null;                                                              \\\n  }                                                                            \\\n  token_type = current_token->type                                             \\\n\nstatic struct token *match_direction(struct token *current_token,\n struct token_collection *token_collection)\n{\n  int current_direction = current_token->arg_value.special_word->instance_type;\n\n  if(current_direction > RANDB)\n  {\n    enum arg_type_indexed *match_type;\n    enum token_type token_type;\n    int modifiers_found = current_direction;\n\n    do\n    {\n      // Must get another token, must be direction too.\n      get_token_skip_comments(return NULL);\n\n      // And it must be a root direction.\n      token_type = current_token->type;\n\n      if(match_special_word_type(current_token, token_type,\n                                 ARG_TYPE_DIRECTION))\n      {\n        *match_type = ARG_TYPE_INDEXED_DIRECTION;\n        current_direction =\n         current_token->arg_value.special_word->instance_type;\n\n        // If it's a terminal (non-modifier) direction we end here,\n        // otherwise it accumulates.\n        if(current_direction <= RANDB)\n        {\n          return current_token;\n        }\n        else\n        {\n          if(modifiers_found & current_direction)\n            return NULL;\n\n          modifiers_found |= current_direction;\n        }\n      }\n      else\n      {\n        return NULL;\n      }\n    } while(1);\n  }\n  return current_token;\n}\n\nstatic boolean whitespace_until_newline(char *src, char **_next)\n{\n  char *next = src;\n\n  while(1)\n  {\n    if(*next == '\\n')\n    {\n      next++;\n      break;\n    }\n\n    if(*next == 0)\n      break;\n\n    // Make sure there's only whitespace until the newline or null terminator.\n    if(!isspace((int)*next))\n    {\n      *_next = next;\n      return false;\n    }\n\n    next++;\n  }\n\n  *_next = next;\n  return true;\n}\n\n// Returns -1 for no match, otherwise returns a strength. 0 is the strongest,\n// higher numbers are weaker.\n\nstatic int match_command(const struct mzx_command *command,\n struct token_collection *token_collection, char **_next)\n{\n  enum arg_type arg_type;\n  enum arg_type_indexed *match_type;\n  char *next;\n  struct token *current_token = &(token_collection->tokens[0]);\n  enum token_type token_type;\n  int arg_position = 0;\n  int parameters = command->parameters;\n  int strength = 0;\n  int match_next_arg = 0;\n\n  // Step through each parameter in the command - it requires 0 to N\n  // tokens (possibly depending on what tokens we have). If we need more\n  // tokens then use get_token to get them to token_collection.\n\n  token_collection_start_match_sequence(token_collection);\n\n  while(arg_position < parameters)\n  {\n    arg_type = command->param_types[arg_position];\n    arg_position++;\n\n    // All args will take at least one token. If a token wants to try to match\n    // the next argument in the list, don't get a new token.\n\n    if(!match_next_arg)\n    {\n      // Alters current_token, token_type, and sometimes match_type\n      get_token_skip_comments(goto no_match);\n    }\n    match_next_arg = 0;\n\n    if(arg_type & ARG_TYPE_IGNORE)\n    {\n      parameters++;\n\n      if(match_special_word_condition(current_token, token_type) &&\n       (current_token->arg_value.special_word->arg_type == ARG_TYPE_IGNORE) &&\n       (current_token->arg_value.special_word->instance_type == arg_type))\n      {\n        // We entered the optional ignore word - suck it up\n        // and move on to the next argument.\n        *match_type = ARG_TYPE_INDEXED_IGNORE;\n        continue;\n      }\n\n      // Try to match the next argument in the list.\n      match_next_arg = 1;\n      continue;\n    }\n\n    if(arg_type & ARG_TYPE_FRAGMENT)\n    {\n      if(match_special_word_condition(current_token, token_type) &&\n       (current_token->arg_value.special_word->arg_type == ARG_TYPE_FRAGMENT)\n       && (current_token->arg_value.special_word->instance_type == arg_type))\n      {\n        *match_type = ARG_TYPE_INDEXED_FRAGMENT;\n        continue;\n      }\n      else\n      {\n        // Must match ARG_TYPE_FRAGMENT of this instance - we can't escape\n        // this one.\n        goto no_match;\n      }\n    }\n    else\n    {\n      // If the token can't be a command fragment then it can't be a command\n      // word. So make this bail here if it is one.\n\n      if(token_type == TOKEN_TYPE_BASIC_IDENTIFIER)\n      {\n        if(find_command_set(current_token->value, current_token->length))\n          goto no_match;\n      }\n    }\n\n    // Otherwise, check if it's any of these in order from lowest to greatest\n    // precedence.\n    if((arg_type & ARG_TYPE_IMMEDIATE) &&\n     ((token_type == TOKEN_TYPE_NUMERIC_LITERAL_BASE10) ||\n     (token_type == TOKEN_TYPE_NUMERIC_LITERAL_BASE16)))\n    {\n      *match_type = ARG_TYPE_INDEXED_IMMEDIATE;\n      continue;\n    }\n\n    if((arg_type & ARG_TYPE_STRING) &&\n     (token_type == TOKEN_TYPE_STRING_LITERAL))\n    {\n      *match_type = ARG_TYPE_INDEXED_STRING;\n      continue;\n    }\n\n    if((arg_type & ARG_TYPE_CHARACTER) &&\n     (token_type == TOKEN_TYPE_CHARACTER_LITERAL))\n    {\n      *match_type = ARG_TYPE_INDEXED_CHARACTER;\n      continue;\n    }\n\n    if((arg_type & ARG_TYPE_COLOR) &&\n     ((token_type == TOKEN_TYPE_BASIC_COLOR) ||\n     (token_type == TOKEN_TYPE_WILDCARD_COLOR)))\n    {\n      *match_type = ARG_TYPE_INDEXED_COLOR;\n      continue;\n    }\n\n    if((arg_type & ARG_TYPE_PARAM) &&\n     ((token_type == TOKEN_TYPE_BASIC_PARAM) ||\n     (token_type == TOKEN_TYPE_WILDCARD_PARAM)))\n    {\n      *match_type = ARG_TYPE_INDEXED_PARAM;\n      continue;\n    }\n\n    if(match_arg_and_special_word_type(current_token, token_type, arg_type,\n                                       ARG_TYPE_DIRECTION))\n    {\n      // Directions are peculiar things. They can actually contain multiple\n      // direction words, so parse forward the extra ones.\n      *match_type = ARG_TYPE_INDEXED_DIRECTION;\n      current_token = match_direction(current_token, token_collection);\n\n      if(current_token)\n        continue;\n\n      goto no_match;\n    }\n\n    if(match_arg_and_special_word_type(current_token, token_type, arg_type,\n                                       ARG_TYPE_THING))\n    {\n      *match_type = ARG_TYPE_INDEXED_THING;\n      continue;\n    }\n\n    if((arg_type & ARG_TYPE_EQUALITY) && (token_type == TOKEN_TYPE_EQUALITY))\n    {\n      *match_type = ARG_TYPE_INDEXED_EQUALITY;\n      continue;\n    }\n\n    if(match_arg_and_special_word_type(current_token, token_type, arg_type,\n                                       ARG_TYPE_CONDITION))\n    {\n      enum condition condition_type =\n       current_token->arg_value.special_word->instance_type;\n      *match_type = ARG_TYPE_INDEXED_CONDITION;\n\n      switch(condition_type)\n      {\n        case WALKING:\n        case TOUCHING:\n        case BLOCKED:\n        case LASTSHOT:\n        case LASTTOUCH:\n          // These condition types require a direction as well.\n          get_token_skip_comments(goto no_match);\n\n          if(match_special_word_type(current_token, token_type,\n                                     ARG_TYPE_DIRECTION))\n          {\n            // Again directions can be two part.\n            *match_type = ARG_TYPE_INDEXED_DIRECTION;\n            current_token = match_direction(current_token, token_collection);\n\n            if(current_token)\n              continue;\n          }\n          goto no_match;\n\n        default:\n          continue;\n      }\n    }\n\n    if(match_arg_and_special_word_type(current_token, token_type, arg_type,\n                                       ARG_TYPE_ITEM))\n    {\n      *match_type = ARG_TYPE_INDEXED_ITEM;\n      continue;\n    }\n\n    if((arg_type & (ARG_TYPE_COUNTER_LOAD_NAME | ARG_TYPE_COUNTER_STORE_NAME |\n     ARG_TYPE_LABEL_NAME | ARG_TYPE_EXT_LABEL_NAME | ARG_TYPE_BOARD_NAME |\n     ARG_TYPE_ROBOT_NAME)) &&\n     ((token_type == TOKEN_TYPE_NAME) ||\n     (token_type == TOKEN_TYPE_BASIC_IDENTIFIER) ||\n     (token_type == TOKEN_TYPE_EXPRESSION) ||\n     (token_type == TOKEN_TYPE_BASIC_COLOR) ||\n     (token_type == TOKEN_TYPE_BASIC_PARAM)))\n    {\n      // A name match is a weak match, so take away from the strength by\n      // decrementing it.\n      strength++;\n      if(arg_type & (ARG_TYPE_LABEL_NAME | ARG_TYPE_EXT_LABEL_NAME))\n        *match_type  = ARG_TYPE_INDEXED_LABEL;\n      else\n\n      if(token_type == TOKEN_TYPE_EXPRESSION)\n        *match_type = ARG_TYPE_INDEXED_EXPRESSION;\n      else\n        *match_type = ARG_TYPE_INDEXED_NAME;\n      continue;\n    }\n\n    goto no_match;\n  }\n\n  next = current_token->value + current_token->length;\n\n  while(1)\n  {\n    if(whitespace_until_newline(next, &next))\n    {\n      *_next = next;\n      return strength;\n    }\n    else\n    {\n      // Pick off a comment.\n      current_token = token_collection_get_token(token_collection,\n       &match_type);\n\n      if((current_token == NULL) || (current_token->type != TOKEN_TYPE_COMMENT))\n        goto no_match;\n\n      *match_type = ARG_TYPE_INDEXED_COMMENT;\n      next = current_token->value + current_token->length;\n    }\n  }\n\n no_match:\n  free(token_collection->match_types);\n  return -1;\n}\n\n__editor_maybe_static struct token *parse_command(char *src, char **_next,\n int *_num_tokens)\n{\n  const struct command_set *command_set;\n  const struct mzx_command *current_command = NULL;\n  struct token *tokens;\n  int command_number;\n  int command_in_set;\n  int command_name_length;\n  int arg_in_match;\n  char *command_base;\n  char *next;\n  int match_strength;\n  int best_match_strength = 10000;\n  int best_match_tokens = -1;\n  int best_match_command_number = -1;\n\n  enum arg_type_indexed *best_match_types = NULL;\n  char *match_next;\n  char *best_match_next = NULL;\n\n  struct token_collection token_collection;\n\n  next = skip_whitespace(src);\n\n  // Could be a comment.\n  if((next[0] == '/') && ((next[1] == '/') || (next[1] == '*')))\n  {\n    // Starting a command with a comment is like matching a command that has\n    // zero length.\n    command_set = &empty_command_set;\n    current_command = &empty_command;\n  }\n  else\n  {\n    // What we need to do is get the first token, but this is a special one\n    // that must just be an name.\n\n    if(special_first_char[(int)(*next)])\n      command_name_length = 1;\n    else\n      command_name_length = find_whitespace(next) - next;\n\n    command_set = find_command_set(next, command_name_length);\n\n    if(command_set == NULL)\n      return NULL;\n  }\n\n  command_base = next;\n  next += command_set->name_length;\n\n  token_collection_initialize(&token_collection, next);\n\n  tokens = token_collection.tokens;\n  tokens[0].value = command_base;\n  tokens[0].length = command_set->name_length;\n  tokens[0].arg_type_indexed = ARG_TYPE_INDEXED_COMMAND;\n\n  // Check to see if any of the commands in the set is a match.\n  for(command_in_set = 0; command_in_set < command_set->count;\n   command_in_set++)\n  {\n    command_number = command_set->offsets[command_in_set];\n    current_command = &(command_list[command_number]);\n\n    // See how well the current command matches.\n    match_strength = match_command(current_command, &token_collection,\n     &match_next);\n\n    if(match_strength == -1)\n    {\n      // This means there was no match.\n      continue;\n    }\n\n    // See if we got a stronger match than the strongest we have.\n    if((match_strength < best_match_strength) &&\n     (token_collection.token_position >= best_match_tokens))\n    {\n      // If so, throw out the best one and make this the best.\n      if(best_match_types != NULL)\n        free(best_match_types);\n\n      best_match_command_number = command_number;\n      best_match_tokens = token_collection.token_position;\n      best_match_strength = match_strength;\n      best_match_types = token_collection.match_types;\n      best_match_next = match_next;\n    }\n    else\n    {\n      // Otherwise, throw out this one.\n      free(token_collection.match_types);\n    }\n\n    // You can't get stronger than 0 - if that's what we got then we can\n    // stop looking.\n    if(best_match_strength == 0)\n      break;\n  }\n\n  if(command_set->count == 0)\n  {\n    match_strength = match_command(current_command, &token_collection,\n     &match_next);\n    if(match_strength >= 0)\n    {\n      best_match_tokens = token_collection.token_position;\n      best_match_types = token_collection.match_types;\n      best_match_next = match_next;\n      best_match_command_number = -1;\n    }\n  }\n\n  if(best_match_tokens != -1)\n  {\n    // Size token array down to what we actually used.\n\n    tokens = token_collection.tokens;\n    tokens[0].arg_value.command_number = best_match_command_number;\n\n    if(best_match_tokens > 1)\n    {\n      for(arg_in_match = 1; arg_in_match < best_match_tokens; arg_in_match++)\n      {\n        tokens[arg_in_match].arg_type_indexed = best_match_types[arg_in_match];\n      }\n    }\n    free(best_match_types);\n\n    if(_next)\n      *_next = best_match_next;\n\n    *_num_tokens = best_match_tokens;\n    return tokens;\n  }\n\n  if(_next)\n    *_next = src;\n\n  *_num_tokens = -1;\n\n  free(token_collection.tokens);\n  return NULL;\n}\n\n#define assemble_get_token_skip_comments()                                     \\\n  token++;                                                                     \\\n                                                                               \\\n  while(token->type == TOKEN_TYPE_COMMENT)                                     \\\n  {                                                                            \\\n    token++;                                                                   \\\n  }                                                                            \\\n\nstatic int assemble_direction(struct token **_token)\n{\n  struct token *token = *_token;\n  int combined_arg_value =\n   token->arg_value.special_word->instance_type;\n  int arg_value = combined_arg_value;\n\n  while(arg_value > RANDB)\n  {\n    assemble_get_token_skip_comments();\n    arg_value = token->arg_value.special_word->instance_type;\n    combined_arg_value |= arg_value;\n  }\n\n  *_token = token;\n\n  return combined_arg_value;\n}\n\nstatic int assemble_arg(struct token **_token, char *output,\n boolean escape_interpolation)\n{\n  boolean string_type = false;\n  int arg_value = 0;\n  char *value;\n  int length;\n  struct token *token = *_token;\n  int escape_char = -1;\n\n  switch(token->arg_type_indexed)\n  {\n    case ARG_TYPE_INDEXED_IMMEDIATE:\n      arg_value = token->arg_value.numeric_literal;\n      break;\n\n    case ARG_TYPE_INDEXED_CHARACTER:\n      arg_value = token->arg_value.char_literal;\n      break;\n\n    case ARG_TYPE_INDEXED_EQUALITY:\n      arg_value = token->arg_value.equality_type;\n      break;\n\n    case ARG_TYPE_INDEXED_COLOR:\n      arg_value = get_color(token->value);\n      break;\n\n    case ARG_TYPE_INDEXED_PARAM:\n      arg_value = get_param(token->value);\n      break;\n\n    case ARG_TYPE_INDEXED_DIRECTION:\n      arg_value = assemble_direction(&token);\n      break;\n\n    case ARG_TYPE_INDEXED_CONDITION:\n      arg_value = token->arg_value.special_word->instance_type;\n\n      switch(arg_value)\n      {\n        case WALKING:\n        case TOUCHING:\n        case BLOCKED:\n        case LASTSHOT:\n        case LASTTOUCH:\n          assemble_get_token_skip_comments();\n          arg_value |= (assemble_direction(&token) << 8);\n          break;\n      }\n      break;\n\n    case ARG_TYPE_INDEXED_ITEM:\n    case ARG_TYPE_INDEXED_THING:\n      arg_value = token->arg_value.special_word->instance_type;\n      break;\n\n    case ARG_TYPE_INDEXED_NAME:\n    case ARG_TYPE_INDEXED_LABEL:\n      string_type = true;\n      value = token->value;\n      length = token->length;\n\n      if(*value == '`')\n      {\n        value++;\n        length -= 2;\n        escape_char = '`';\n      }\n      break;\n\n    case ARG_TYPE_INDEXED_STRING:\n      string_type = true;\n      value = token->value;\n      length = token->length;\n\n      value++;\n      length -= 2;\n      escape_char = '\"';\n      break;\n\n    case ARG_TYPE_INDEXED_EXPRESSION:\n      string_type = true;\n      value = token->value;\n      length = token->length;\n      break;\n\n    case ARG_TYPE_INDEXED_COMMENT:\n    case ARG_TYPE_INDEXED_IGNORE:\n    case ARG_TYPE_INDEXED_FRAGMENT:\n    case ARG_TYPE_INDEXED_COMMAND:\n      *_token = token + 1;\n      return 0;\n  }\n\n  *_token = token + 1;\n\n  if(string_type)\n  {\n    int raw_string_length = 0;\n    char *output_base = output;\n    int string_offset = 0;\n\n    output++;\n\n    while(string_offset < length)\n    {\n      if((value[string_offset] == '\\\\') &&\n       (value[string_offset + 1] == '\\\\') && (escape_interpolation == false))\n      {\n        // This is a strange case where the \\\\ is preserved as such so it can\n        // be escaped at runtime, but still has to be parsed as \\\\ so as to avoid\n        // escaping what comes next.\n        string_offset += 2;\n        output[0] = '\\\\';\n        output[1] = '\\\\';\n        output += 2;\n        raw_string_length += 2;\n      }\n      else\n      {\n        string_offset +=\n         escape_chars(output, value + string_offset, escape_char,\n         escape_interpolation);\n\n        output++;\n        raw_string_length++;\n      }\n    }\n    *output_base = raw_string_length + 1;\n    *output = 0;\n\n    return raw_string_length + 2;\n  }\n  else\n  {\n    output[0] = 0;\n    output[1] = arg_value;\n    output[2] = arg_value >> 8;\n    return 3;\n  }\n}\n\nstatic char *assemble_command(char *src, char **_output)\n{\n  char *next;\n  int num_parse_tokens;\n  struct token *parse_tokens = parse_command(src, &next, &num_parse_tokens);\n\n  if(parse_tokens)\n  {\n    int command_number = parse_tokens[0].arg_value.command_number;\n\n    if(command_number != -1)\n    {\n      char *output_base = *_output;\n      char *output = output_base + 2;\n      struct token *current_token = &(parse_tokens[1]);\n      int command_length = 1;\n      int arg_length;\n\n      output_base[1] = command_number;\n\n      while(current_token != &(parse_tokens[num_parse_tokens]))\n      {\n        arg_length = assemble_arg(&current_token, output,\n         (command_number == ROBOTIC_CMD_LABEL));\n        output += arg_length;\n        command_length += arg_length;\n      }\n\n      output_base[0] = command_length;\n      output_base[command_length + 1] = command_length;\n      *_output = output + 1;\n    }\n    free(parse_tokens);\n    return next;\n  }\n\n  {\n    char short_output[512];\n    int newline_pos = strcspn(src, \"\\n\");\n    if(newline_pos > 0)\n    {\n      snprintf(short_output, sizeof(short_output), \"%.*s\", newline_pos, src);\n      short_output[sizeof(short_output) - 1] = 0;\n      warn(\"Failed to assemble command: %s\\n\", short_output);\n    }\n  }\n  return NULL;\n}\n\nvoid assemble_program(char *program_source, char **_bytecode,\n int *_bytecode_length, struct command_mapping **_command_map,\n int *_command_map_length)\n{\n  int bytecode_length = 1;\n  int bytecode_offset = 1;\n  int bytecode_length_allocated = 256;\n  char *program_bytecode = malloc(bytecode_length_allocated);\n\n  char command_buffer[512];\n  char *output;\n\n  int command_length;\n\n  int cmd_map_length = 1;\n  int cmd_map_allocated = 256;\n  struct command_mapping *cmd_map = NULL;\n  char *src_start = program_source;\n  char *line_start = NULL;\n  char *line_text = NULL;\n  int real_line = 1;\n\n  if(_command_map)\n  {\n    cmd_map = malloc(cmd_map_allocated * sizeof(struct command_mapping));\n    cmd_map[0].real_line = 0;\n    cmd_map[0].bc_pos = 0;\n    cmd_map[0].src_pos = -1;\n  }\n\n  program_bytecode[0] = 0xFF;\n\n  do\n  {\n    if(cmd_map)\n    {\n      line_start = program_source;\n      line_text = program_source;\n\n      // Count whitespace lines until start of token.\n      // FIXME this is a hack\n      while(whitespace_until_newline(line_text, &line_text))\n      {\n        line_start = line_text;\n        real_line++;\n        if(!*line_text)\n          break;\n      }\n\n      if(cmd_map_length >= cmd_map_allocated)\n      {\n        cmd_map_allocated *= 2;\n        cmd_map =\n         realloc(cmd_map, cmd_map_allocated * sizeof(struct command_mapping));\n      }\n\n      cmd_map[cmd_map_length].real_line = real_line;\n      cmd_map[cmd_map_length].bc_pos = bytecode_offset;\n      cmd_map[cmd_map_length].src_pos = line_start - src_start;\n      cmd_map_length++;\n\n      // Disassemble command into command_buffer.\n      output = command_buffer;\n      program_source = assemble_command(program_source, &output);\n\n      // Comment? backtrack\n      if(output == command_buffer)\n        cmd_map_length--;\n\n      // Count the number of lines passed during assembly.\n      // FIXME this is a hack\n      while(line_text < program_source)\n      {\n        if(*line_text == '\\n')\n          real_line++;\n\n        line_text++;\n      }\n    }\n\n    else\n    {\n      // Disassemble command into command_buffer.\n      output = command_buffer;\n      program_source = assemble_command(program_source, &output);\n    }\n\n    // See if it's valid\n    if(program_source)\n    {\n      // Increment the total size (plus size for new line)\n      command_length = output - command_buffer;\n      bytecode_length += command_length;\n\n      // Reallocate buffer if necessary.\n      while(bytecode_length > bytecode_length_allocated)\n      {\n        bytecode_length_allocated *= 2;\n        program_bytecode =\n         realloc(program_bytecode, bytecode_length_allocated);\n      }\n\n      // Copy new assembly into buffer.\n      memcpy(program_bytecode + bytecode_offset, command_buffer,\n       command_length);\n\n      // Offset bytecode (source) position.\n      bytecode_offset += command_length;\n    }\n  } while(program_source);\n\n  // Terminate program with 0.\n  bytecode_length++;\n  program_bytecode = realloc(program_bytecode, bytecode_length);\n  program_bytecode[bytecode_offset] = 0;\n\n  *_bytecode = program_bytecode;\n  *_bytecode_length = bytecode_length;\n\n  // Shrink the command mapping.\n  if(cmd_map)\n  {\n    cmd_map = realloc(cmd_map, cmd_map_length * sizeof(struct command_mapping));\n\n    *_command_map = cmd_map;\n    *_command_map_length = cmd_map_length;\n  }\n}\n\n\nenum legacy_command_number\n{\n  ROBOTIC_CMD_UNUSED  = 0xFF00,\n  ROBOTIC_CMD_REMOVED = 0xFF01\n};\n\nstatic const int legacy_command_to_current[256] =\n{\n  ROBOTIC_CMD_END,\n  ROBOTIC_CMD_DIE,\n  ROBOTIC_CMD_WAIT,\n  ROBOTIC_CMD_CYCLE,\n  ROBOTIC_CMD_GO,\n  ROBOTIC_CMD_WALK,\n  ROBOTIC_CMD_BECOME,\n  ROBOTIC_CMD_CHAR,\n  ROBOTIC_CMD_COLOR,\n  ROBOTIC_CMD_GOTOXY,\n  ROBOTIC_CMD_SET,\n  ROBOTIC_CMD_INC,\n  ROBOTIC_CMD_DEC,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_IF,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_IF_CONDITION,\n  ROBOTIC_CMD_IF_NOT_CONDITION,\n  ROBOTIC_CMD_IF_ANY,\n  ROBOTIC_CMD_IF_NO,\n  ROBOTIC_CMD_IF_THING_DIR,\n  ROBOTIC_CMD_IF_NOT_THING_DIR,\n  ROBOTIC_CMD_IF_THING_XY,\n  ROBOTIC_CMD_IF_AT,\n  ROBOTIC_CMD_IF_DIR_OF_PLAYER,\n  ROBOTIC_CMD_DOUBLE,\n  ROBOTIC_CMD_HALF,\n  ROBOTIC_CMD_GOTO,\n  ROBOTIC_CMD_SEND,\n  ROBOTIC_CMD_EXPLODE,\n  ROBOTIC_CMD_PUT_DIR,\n  ROBOTIC_CMD_GIVE,\n  ROBOTIC_CMD_TAKE,\n  ROBOTIC_CMD_TAKE_OR,\n  ROBOTIC_CMD_ENDGAME,\n  ROBOTIC_CMD_ENDLIFE,\n  ROBOTIC_CMD_MOD,\n  ROBOTIC_CMD_SAM,\n  ROBOTIC_CMD_VOLUME,\n  ROBOTIC_CMD_END_MOD,\n  ROBOTIC_CMD_END_SAM,\n  ROBOTIC_CMD_PLAY,\n  ROBOTIC_CMD_END_PLAY,\n  ROBOTIC_CMD_WAIT_THEN_PLAY,\n  ROBOTIC_CMD_WAIT_PLAY,\n  ROBOTIC_CMD_BLANK_LINE,\n  ROBOTIC_CMD_SFX,\n  ROBOTIC_CMD_PLAY_IF_SILENT,\n  ROBOTIC_CMD_OPEN,\n  ROBOTIC_CMD_LOCKSELF,\n  ROBOTIC_CMD_UNLOCKSELF,\n  ROBOTIC_CMD_SEND_DIR,\n  ROBOTIC_CMD_ZAP,\n  ROBOTIC_CMD_RESTORE,\n  ROBOTIC_CMD_LOCKPLAYER,\n  ROBOTIC_CMD_UNLOCKPLAYER,\n  ROBOTIC_CMD_LOCKPLAYER_NS,\n  ROBOTIC_CMD_LOCKPLAYER_EW,\n  ROBOTIC_CMD_LOCKPLAYER_ATTACK,\n  ROBOTIC_CMD_MOVE_PLAYER_DIR,\n  ROBOTIC_CMD_MOVE_PLAYER_DIR_OR,\n  ROBOTIC_CMD_PUT_PLAYER_XY,\n  ROBOTIC_CMD_REMOVED,\n  ROBOTIC_CMD_REMOVED,\n  ROBOTIC_CMD_IF_PLAYER_XY,\n  ROBOTIC_CMD_PUT_PLAYER_DIR,\n  ROBOTIC_CMD_TRY_DIR,\n  ROBOTIC_CMD_ROTATECW,\n  ROBOTIC_CMD_ROTATECCW,\n  ROBOTIC_CMD_SWITCH,\n  ROBOTIC_CMD_SHOOT,\n  ROBOTIC_CMD_LAYBOMB,\n  ROBOTIC_CMD_LAYBOMB_HIGH,\n  ROBOTIC_CMD_SHOOTMISSILE,\n  ROBOTIC_CMD_SHOOTSEEKER,\n  ROBOTIC_CMD_SPITFIRE,\n  ROBOTIC_CMD_LAZERWALL,\n  ROBOTIC_CMD_PUT_XY,\n  ROBOTIC_CMD_DIE_ITEM,\n  ROBOTIC_CMD_SEND_XY,\n  ROBOTIC_CMD_COPYROBOT_NAMED,\n  ROBOTIC_CMD_COPYROBOT_XY,\n  ROBOTIC_CMD_COPYROBOT_DIR,\n  ROBOTIC_CMD_DUPLICATE_SELF_DIR,\n  ROBOTIC_CMD_DUPLICATE_SELF_XY,\n  ROBOTIC_CMD_BULLETN,\n  ROBOTIC_CMD_BULLETS,\n  ROBOTIC_CMD_BULLETE,\n  ROBOTIC_CMD_BULLETW,\n  ROBOTIC_CMD_GIVEKEY,\n  ROBOTIC_CMD_GIVEKEY_OR,\n  ROBOTIC_CMD_TAKEKEY,\n  ROBOTIC_CMD_TAKEKEY_OR,\n  ROBOTIC_CMD_INC_RANDOM,\n  ROBOTIC_CMD_DEC_RANDOM,\n  ROBOTIC_CMD_SET_RANDOM,\n  ROBOTIC_CMD_TRADE,\n  ROBOTIC_CMD_SEND_DIR_PLAYER,\n  ROBOTIC_CMD_PUT_DIR_PLAYER,\n  ROBOTIC_CMD_SLASH,\n  ROBOTIC_CMD_MESSAGE_LINE,\n  ROBOTIC_CMD_MESSAGE_BOX_LINE,\n  ROBOTIC_CMD_MESSAGE_BOX_OPTION,\n  ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION,\n  ROBOTIC_CMD_LABEL,\n  ROBOTIC_CMD_COMMENT,\n  ROBOTIC_CMD_ZAPPED_LABEL,\n  ROBOTIC_CMD_TELEPORT,\n  ROBOTIC_CMD_SCROLLVIEW,\n  ROBOTIC_CMD_INPUT,\n  ROBOTIC_CMD_IF_INPUT,\n  ROBOTIC_CMD_IF_INPUT_NOT,\n  ROBOTIC_CMD_IF_INPUT_MATCHES,\n  ROBOTIC_CMD_PLAYER_CHAR,\n  ROBOTIC_CMD_MESSAGE_BOX_COLOR_LINE,\n  ROBOTIC_CMD_MESSAGE_BOX_CENTER_LINE,\n  ROBOTIC_CMD_MOVE_ALL,\n  ROBOTIC_CMD_COPY,\n  ROBOTIC_CMD_SET_EDGE_COLOR,\n  ROBOTIC_CMD_BOARD,\n  ROBOTIC_CMD_BOARD_IS_NONE,\n  ROBOTIC_CMD_CHAR_EDIT,\n  ROBOTIC_CMD_BECOME_PUSHABLE,\n  ROBOTIC_CMD_BECOME_NONPUSHABLE,\n  ROBOTIC_CMD_BLIND,\n  ROBOTIC_CMD_FIREWALKER,\n  ROBOTIC_CMD_FREEZETIME,\n  ROBOTIC_CMD_SLOWTIME,\n  ROBOTIC_CMD_WIND,\n  ROBOTIC_CMD_AVALANCHE,\n  ROBOTIC_CMD_COPY_DIR,\n  ROBOTIC_CMD_BECOME_LAVAWALKER,\n  ROBOTIC_CMD_BECOME_NONLAVAWALKER,\n  ROBOTIC_CMD_CHANGE,\n  ROBOTIC_CMD_PLAYERCOLOR,\n  ROBOTIC_CMD_BULLETCOLOR,\n  ROBOTIC_CMD_MISSILECOLOR,\n  ROBOTIC_CMD_MESSAGE_ROW,\n  ROBOTIC_CMD_REL_SELF,\n  ROBOTIC_CMD_REL_PLAYER,\n  ROBOTIC_CMD_REL_COUNTERS,\n  ROBOTIC_CMD_SET_ID_CHAR,\n  ROBOTIC_CMD_JUMP_MOD_ORDER,\n  ROBOTIC_CMD_ASK,\n  ROBOTIC_CMD_FILLHEALTH,\n  ROBOTIC_CMD_THICK_ARROW,\n  ROBOTIC_CMD_THIN_ARROW,\n  ROBOTIC_CMD_SET_MAX_HEALTH,\n  ROBOTIC_CMD_SAVE_PLAYER_POSITION,\n  ROBOTIC_CMD_RESTORE_PLAYER_POSITION,\n  ROBOTIC_CMD_EXCHANGE_PLAYER_POSITION,\n  ROBOTIC_CMD_MESSAGE_COLUMN,\n  ROBOTIC_CMD_CENTER_MESSAGE,\n  ROBOTIC_CMD_CLEAR_MESSAGE,\n  ROBOTIC_CMD_RESETVIEW,\n  ROBOTIC_CMD_MOD_SAM,\n  ROBOTIC_CMD_VOLUME2,\n  ROBOTIC_CMD_SCROLLBASE,\n  ROBOTIC_CMD_SCROLLCORNER,\n  ROBOTIC_CMD_SCROLLTITLE,\n  ROBOTIC_CMD_SCROLLPOINTER,\n  ROBOTIC_CMD_SCROLLARROW,\n  ROBOTIC_CMD_VIEWPORT,\n  ROBOTIC_CMD_VIEWPORT_WIDTH,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_SAVE_PLAYER_POSITION_N,\n  ROBOTIC_CMD_RESTORE_PLAYER_POSITION_N,\n  ROBOTIC_CMD_EXCHANGE_PLAYER_POSITION_N,\n  ROBOTIC_CMD_RESTORE_PLAYER_POSITION_N_DUPLICATE_SELF,\n  ROBOTIC_CMD_EXCHANGE_PLAYER_POSITION_N_DUPLICATE_SELF,\n  ROBOTIC_CMD_PLAYER_BULLETN,\n  ROBOTIC_CMD_PLAYER_BULLETS,\n  ROBOTIC_CMD_PLAYER_BULLETE,\n  ROBOTIC_CMD_PLAYER_BULLETW,\n  ROBOTIC_CMD_NEUTRAL_BULLETN,\n  ROBOTIC_CMD_NEUTRAL_BULLETS,\n  ROBOTIC_CMD_NEUTRAL_BULLETE,\n  ROBOTIC_CMD_NEUTRAL_BULLETW,\n  ROBOTIC_CMD_ENEMY_BULLETN,\n  ROBOTIC_CMD_ENEMY_BULLETS,\n  ROBOTIC_CMD_ENEMY_BULLETE,\n  ROBOTIC_CMD_ENEMY_BULLETW,\n  ROBOTIC_CMD_PLAYER_BULLET_COLOR,\n  ROBOTIC_CMD_NEUTRAL_BULLET_COLOR,\n  ROBOTIC_CMD_ENEMY_BULLET_COLOR,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_REL_SELF_FIRST,\n  ROBOTIC_CMD_REL_SELF_LAST,\n  ROBOTIC_CMD_REL_PLAYER_FIRST,\n  ROBOTIC_CMD_REL_PLAYER_LAST,\n  ROBOTIC_CMD_REL_COUNTERS_FIRST,\n  ROBOTIC_CMD_REL_COUNTERS_LAST,\n  ROBOTIC_CMD_MOD_FADE_OUT,\n  ROBOTIC_CMD_MOD_FADE_IN,\n  ROBOTIC_CMD_COPY_BLOCK,\n  ROBOTIC_CMD_CLIP_INPUT,\n  ROBOTIC_CMD_PUSH,\n  ROBOTIC_CMD_SCROLL_CHAR,\n  ROBOTIC_CMD_FLIP_CHAR,\n  ROBOTIC_CMD_COPY_CHAR,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_CHANGE_SFX,\n  ROBOTIC_CMD_COLOR_INTENSITY_ALL,\n  ROBOTIC_CMD_COLOR_INTENSITY_N,\n  ROBOTIC_CMD_COLOR_FADE_OUT,\n  ROBOTIC_CMD_COLOR_FADE_IN,\n  ROBOTIC_CMD_SET_COLOR,\n  ROBOTIC_CMD_LOAD_CHAR_SET,\n  ROBOTIC_CMD_MULTIPLY,\n  ROBOTIC_CMD_DIVIDE,\n  ROBOTIC_CMD_MODULO,\n  ROBOTIC_CMD_PLAYER_CHAR_DIR,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_LOAD_PALETTE,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_MOD_FADE_TO,\n  ROBOTIC_CMD_SCROLLVIEW_XY,\n  ROBOTIC_CMD_SWAP_WORLD,\n  ROBOTIC_CMD_IF_ALIGNEDROBOT,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_LOCKSCROLL,\n  ROBOTIC_CMD_UNLOCKSCROLL,\n  ROBOTIC_CMD_IF_FIRST_INPUT,\n  ROBOTIC_CMD_PERSISTENT_GO,\n  ROBOTIC_CMD_WAIT_MOD_FADE,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_ENABLE_SAVING,\n  ROBOTIC_CMD_DISABLE_SAVING,\n  ROBOTIC_CMD_ENABLE_SENSORONLY_SAVING,\n  ROBOTIC_CMD_STATUS_COUNTER,\n  ROBOTIC_CMD_OVERLAY_ON,\n  ROBOTIC_CMD_OVERLAY_STATIC,\n  ROBOTIC_CMD_OVERLAY_TRANSPARENT,\n  ROBOTIC_CMD_OVERLAY_PUT_OVERLAY,\n  ROBOTIC_CMD_COPY_OVERLAY_BLOCK,\n  ROBOTIC_CMD_UNUSED_244,\n  ROBOTIC_CMD_CHANGE_OVERLAY,\n  ROBOTIC_CMD_CHANGE_OVERLAY_COLOR,\n  ROBOTIC_CMD_WRITE_OVERLAY,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_UNUSED,\n  ROBOTIC_CMD_LOOP_START,\n  ROBOTIC_CMD_LOOP_FOR,\n  ROBOTIC_CMD_ABORT_LOOP,\n  ROBOTIC_CMD_DISABLE_MESG_EDGE,\n  ROBOTIC_CMD_ENABLE_MESG_EDGE\n};\n\nstatic boolean is_simple_identifier_name(char *src, int length,\n boolean allow_special_words)\n{\n  // Length can't be zero (for ``)\n  if(length == 0)\n    return false;\n\n  // It must not be a command name.\n  if(allow_special_words == false)\n  {\n    if(find_command_set(src, length))\n      return false;\n\n    if(find_special_word(src, length))\n      return false;\n  }\n\n  // Can't start with numeric\n  if(isdigit((int)*src))\n    return false;\n\n  // And it must not contain any invalid chars.\n  while(length)\n  {\n    if(!is_identifier_char(*src))\n      return false;\n\n    src++;\n    length--;\n  }\n\n  return true;\n}\n\n#define legacy_disassemble_print_sequence(condition)                          \\\n  int string_length = *_string_length;                                        \\\n  char *output = *_output;                                                    \\\n                                                                              \\\n  while((condition) && string_length)                                         \\\n  {                                                                           \\\n    *output = *src;                                                           \\\n    output++;                                                                 \\\n    src++;                                                                    \\\n    string_length--;                                                          \\\n  }                                                                           \\\n                                                                              \\\n  *_output = output;                                                          \\\n  *_string_length = string_length;                                            \\\n  return src;                                                                 \\\n\n\nstatic char *legacy_disassemble_print_spaces(char *src, char **_output,\n int *_string_length)\n{\n  legacy_disassemble_print_sequence(*src == ' ');\n}\n\nstatic char *legacy_disassemble_print_decimal(char *src, char **_output,\n int *_string_length)\n{\n  legacy_disassemble_print_sequence(isdigit((int)*src));\n}\n\nstatic char *legacy_disassemble_print_hex(char *src, char **_output,\n int *_string_length)\n{\n  legacy_disassemble_print_sequence(isxdigit((int)*src));\n}\n\nstatic char *legacy_disassemble_print_expression(char *src, char **_output,\n int *_string_length, boolean is_interpolation);\nstatic char *legacy_disassemble_print_string_expressions(char *src,\n char **_output, int *_string_length, int early_terminator,\n int escape_char, int is_expression_interpolation);\n\nstatic char *legacy_disassemble_print_binary_operator(char *src,\n char **_output, int *_string_length)\n{\n  char *output = *_output;\n  char operator_char = *src;\n  int string_length = *_string_length - 1;\n\n  switch(operator_char)\n  {\n    // One char operators, not the start of a two char one.\n    case '+':\n    case '-':\n    case '/':\n    case '%':\n    case '~':\n    case '*':\n    case '=':\n    case '?':\n    case ':':\n      *output = operator_char;\n      output++;\n      break;\n\n    case 'x':\n      *output = '^';\n      output++;\n      break;\n\n    case 'o':\n      *output = '|';\n      output++;\n      break;\n\n    case 'a':\n      *output = '&';\n      output++;\n      break;\n\n    case '^':\n      output[0] = '*';\n      output[1] = '*';\n      output += 2;\n      break;\n\n    case '>':\n    case '<':\n      *output = operator_char;\n      output++;\n      if(src[1] == operator_char)\n      {\n        *output = operator_char;\n        output++;\n        if(src[2] == operator_char)\n        {\n          *output = operator_char;\n          output++;\n          src++;\n          string_length--;\n        }\n        src++;\n        string_length--;\n      }\n      if(src[1] == '=')\n      {\n        *output = '=';\n        output++;\n        src++;\n        string_length--;\n      }\n      break;\n\n    case '!':\n      *output = operator_char;\n      output++;\n      if(src[1] == '=')\n      {\n        *output = '=';\n        output++;\n        src++;\n        string_length--;\n      }\n      break;\n\n\n    default:\n      return src;\n      break;\n  }\n\n  *_output = output;\n  *_string_length = string_length;\n  return src + 1;\n}\n\n\nstatic char *legacy_disassemble_print_expr_value_token(char *src,\n char **_output, int *_string_length, boolean is_first, boolean is_interpolation)\n{\n  int string_length = *_string_length;\n  char *output = *_output;\n  char *next = src;\n  char *next_next;\n\n  // Unary operators are okay, just eats up spaces until next ones.\n  while((*next == '-') || (*next == '~') || (*next == '!'))\n  {\n    *output = *next;\n    output++;\n    string_length--;\n    next = legacy_disassemble_print_spaces(next + 1, &output, &string_length);\n\n    if(string_length == 0)\n      return src;\n  }\n\n  // If it's an operator then it's not an expression.\n  switch(*next)\n  {\n    // Immediate or hex\n    case '0':\n      if(next[1] == 'x')\n      {\n        if(string_length < 3)\n          return 0;\n        string_length -= 2;\n        output[0] = '0';\n        output[1] = 'x';\n        output += 2;\n        next = legacy_disassemble_print_hex(next + 2, &output, &string_length);\n      }\n      else\n      {\n        next = legacy_disassemble_print_decimal(next, &output, &string_length);\n      }\n\n      if(string_length == 0)\n        return src;\n\n      break;\n\n    case '1':\n    case '2':\n    case '3':\n    case '4':\n    case '5':\n    case '6':\n    case '7':\n    case '8':\n    case '9':\n      next = legacy_disassemble_print_decimal(next, &output, &string_length);\n\n      if(string_length == 0)\n        return src;\n\n      break;\n\n    case '(':\n      // Start of an expression, verify that it is one.\n      next_next =\n       legacy_disassemble_print_expression(next, &output, &string_length, false);\n\n      if((string_length == 0) || (next_next == next))\n        return src;\n\n      next = next_next;\n      break;\n\n    case '\\'':\n    case '&':\n    {\n      int name_length;\n      char base_char = *next;\n      char *base_output = output;\n      boolean wrap_parentheses = false;\n      boolean wrap_backticks = false;\n\n      next_next = legacy_disassemble_print_string_expressions(next + 1,\n       &output, &string_length, base_char, '`', false);\n\n      name_length = output - base_output;\n\n      if(*next_next != base_char)\n        return src;\n\n      // Take off the enclosing chars that weren't printed.\n      string_length -= 2;\n\n      if(is_first && is_interpolation &&\n       is_string_valued_ident(next + 1, name_length, is_interpolation))\n      {\n        // Hack: wrap in parentheses so this doesn't get mistaken for a\n        // string interpolation. It might be nice to have a more readable\n        // way of supporting this someday e.g. int($string).\n        wrap_parentheses = true;\n      }\n\n      if(!is_simple_identifier_name(next + 1, name_length, true))\n      {\n        wrap_backticks = true;\n      }\n\n      if(wrap_parentheses || wrap_backticks)\n      {\n        int num = wrap_parentheses + wrap_backticks;\n        int pos = 0;\n        memmove(base_output + num, base_output, name_length);\n\n        output += num * 2;\n        if(wrap_parentheses)\n        {\n          base_output[pos] = '(';\n          output[-1 - pos] = ')';\n          pos++;\n        }\n        if(wrap_backticks)\n        {\n          base_output[pos] = '`';\n          output[-1 - pos] = '`';\n          pos++;\n        }\n      }\n\n      next = next_next + 1;\n      break;\n    }\n\n    default:\n      // Invalid.\n      return src;\n  }\n\n  *_string_length = string_length;\n  *_output = output;\n  return next;\n}\n\nstatic char *legacy_disassemble_print_expression(char *src, char **_output,\n int *_string_length, boolean is_interpolation)\n{\n  char *next, *next_next;\n  char *output = *_output;\n  int string_length = *_string_length;\n\n  // First character must be the opening paren, but if it weren't we\n  // wouldn't be here. However, the string_length must be greater than\n  // 1.\n  // When interpolating, convert to interpolation brackets instead of ().\n\n  *output = is_interpolation ? '{' : '(';\n  output++;\n  next = src + 1;\n  string_length--;\n\n  next = legacy_disassemble_print_spaces(next, &output, &string_length);\n  if(string_length == 0)\n    return src;\n\n  // Need a value to kick things off.\n  next_next = legacy_disassemble_print_expr_value_token(next, &output,\n   &string_length, true, is_interpolation);\n\n  if((string_length == 0) || (next_next == next))\n    return src;\n\n  next = next_next;\n\n  // Now keep going until we hit the end - for every\n  // time we don't we need an operator then a value.\n  while(*next != ')')\n  {\n    if(string_length == 0)\n      return src;\n\n    next = legacy_disassemble_print_spaces(next, &output, &string_length);\n\n    if(*next == ')')\n      break;\n\n    next_next = legacy_disassemble_print_binary_operator(next, &output,\n     &string_length);\n\n    if((string_length == 0) || (next_next == next))\n      return src;\n\n    next = legacy_disassemble_print_spaces(next_next, &output, &string_length);\n\n    next_next = legacy_disassemble_print_expr_value_token(next, &output,\n     &string_length, false, is_interpolation);\n\n    if((string_length == 0) || (next_next == next))\n      return src;\n\n    next = legacy_disassemble_print_spaces(next_next, &output, &string_length);\n  }\n\n  *output = is_interpolation ? '}' : ')';\n  output++;\n  next++;\n  string_length--;\n\n  *_string_length = string_length;\n  *_output = output;\n  return next;\n}\n\n// If you don't want an early terminator then give this -1.\nstatic char *legacy_disassemble_print_string_expressions(char *src,\n char **_output, int *_string_length, int early_terminator,\n int escape_char, int is_expression_interpolation)\n{\n  char current_char = *src;\n  char *output = *_output;\n  int string_length = *_string_length;\n\n  while(string_length && (current_char != early_terminator))\n  {\n    // Hack: legacy Robotic allowed unclosed interpolation in expression\n    // identifiers to work, so we need to as well.\n    if(current_char == '\\'' && is_expression_interpolation)\n      break;\n\n    if(current_char == '(')\n    {\n      char *next = legacy_disassemble_print_expression(src, &output,\n       &string_length, true);\n\n      if(next == src)\n      {\n        output[0] = '(';\n        output++;\n        src++;\n        string_length--;\n      }\n      else\n      {\n        src = next;\n      }\n    }\n    else\n\n    if(current_char == '&')\n    {\n      // Convert old interpolation to new interpolation.\n      if((string_length > 1) && (src[1] == '&'))\n      {\n        // Or just print an &.. no more of this && stuff.\n        output[0] = '&';\n        output++;\n        src += 2;\n        string_length -= 2;\n      }\n      else\n      {\n        int name_length;\n        char *next;\n        char *base_output;\n        char *name_offset;\n\n        src++;\n\n        base_output = output;\n\n        // Make room for the expression bracket.\n        output++;\n\n        string_length--;\n\n        // Special formatting for + and #\n        if((*src == '+') || (*src == '#'))\n        {\n          *output = *src;\n          output++;\n          src++;\n          string_length--;\n        }\n\n        name_offset = output;\n\n        next = legacy_disassemble_print_string_expressions(src, &output,\n         &string_length, '&', -1, true);\n\n        name_length = output - name_offset;\n\n        // Consume the terminating char if it's an &. Interpolation can also\n        // terminate with a null or a ' (due to a bug); in these cases, don't.\n        if(*next == '&')\n        {\n          string_length--;\n          next++;\n        }\n\n        if(!is_simple_identifier_name(name_offset, name_length, true))\n        {\n          memmove(name_offset + 1, name_offset, name_length);\n          *name_offset = '`';\n          output[1] = '`';\n          output += 2;\n        }\n\n        *base_output = '{';\n        *output = '}';\n        output++;\n        src = next;\n      }\n    }\n    else\n    {\n      if((current_char == '{') || (current_char == '}') ||\n       (current_char == escape_char))\n      {\n        output[0] = '\\\\';\n        output[1] = current_char;\n        output += 2;\n      }\n      else\n      {\n        output += unescape_char(output, current_char);\n      }\n      src++;\n      string_length--;\n    }\n    current_char = *src;\n  }\n\n  *_string_length = string_length;\n  *_output = output;\n  return src;\n}\n\n\n#define write_arg_word(str)                                                    \\\n{                                                                              \\\n  const char *_str = str;                                                      \\\n  strcpy(output, _str);                                                        \\\n  *_output = output + strlen(_str);                                            \\\n  return src;                                                                  \\\n}                                                                              \\\n\n__editor_maybe_static void print_color(int color, char *color_buffer)\n{\n  /* Simulate the 2.x interpreter function fix_color here to guarantee\n   * that invalid values are accurately disassembled as they are interpreted.\n   * Invalid values below 0 (signed 16-bit) are ultimately handled mod 256.\n   * Invalid values above 0x120 are treated identically to c??.\n   *\n   * This interpreter behavior dates back to 2.51 or older, but the DOS\n   * disassembler would treat all invalid values as c??, and port versions\n   * prior to 2.93c would print one of two forms of invalid junk instead.\n   */\n  if((int16_t)color < 0x100)\n  {\n    sprintf(color_buffer, \"c%02x\", color & 0xff);\n  }\n  else\n\n  if(color < 0x110)\n  {\n    sprintf(color_buffer, \"c?%1x\", color & 0x0f);\n  }\n  else\n\n  if(color < 0x120)\n  {\n    sprintf(color_buffer, \"c%1x?\", color & 0x0f);\n  }\n  else\n  {\n    sprintf(color_buffer, \"c??\");\n  }\n}\n\nstatic char *print_dir(int dir, char *output)\n{\n  if(dir & 0x10)\n  {\n    strcpy(output, \"RANDP \");\n    output += 6;\n  }\n\n  if(dir & 0x20)\n  {\n    strcpy(output, \"CW \");\n    output += 3;\n  }\n\n  if(dir & 0x40)\n  {\n    strcpy(output, \"OPP \");\n    output += 4;\n  }\n\n  if(dir & 0x80)\n  {\n    strcpy(output, \"RANDNOT \");\n    output += 8;\n  }\n\n  strcpy(output, dir_names[dir & 0xF]);\n  return output + strlen(dir_names[dir & 0xF]);\n}\n\nstatic char *legacy_disassemble_arg(enum arg_type arg_type, char *src,\n char **_output, boolean print_ignores, int base)\n{\n  char *output = *_output;\n  int compiled_arg_type;\n\n  // First two take nothing from the arg stream.\n  if(arg_type & ARG_TYPE_FRAGMENT)\n    write_arg_word(command_fragment_type_names[arg_type & ~ARG_TYPE_FRAGMENT]);\n\n  if(arg_type & ARG_TYPE_IGNORE)\n  {\n    if(print_ignores)\n      write_arg_word(ignore_type_names[arg_type & ~ARG_TYPE_IGNORE]);\n\n    return src;\n  }\n\n  // 0 is int, non-zero is string of that length.\n  compiled_arg_type = *src;\n\n  if(compiled_arg_type == 0)\n  {\n    int compiled_arg_value = src[1] | (src[2] << 8);\n    src += 3;\n\n    if(arg_type & ARG_TYPE_ITEM)\n    {\n      if(compiled_arg_value < (int)ARRAY_SIZE(item_names))\n      {\n        write_arg_word(item_names[compiled_arg_value]);\n      }\n      else\n      {\n        write_arg_word(\"INVALID_ITEM\");\n      }\n    }\n\n    if(arg_type & ARG_TYPE_CONDITION)\n    {\n      int condition = compiled_arg_value & 0xFF;\n      int direction = compiled_arg_value >> 8;\n      const char *_str;\n\n      if(condition < (int)ARRAY_SIZE(condition_names))\n        _str = condition_names[condition];\n\n      else\n        _str = \"invalid_condition\";\n\n      strcpy(output, _str);\n      output += strlen(_str);\n\n      // Some conditions have a direction after them too.\n      switch(condition)\n      {\n        case WALKING:\n        case TOUCHING:\n        case BLOCKED:\n        case LASTSHOT:\n        case LASTTOUCH:\n          // Get next arg\n          *output = ' ';\n          output++;\n          output = print_dir(direction, output);\n          break;\n      }\n      *_output = output;\n      return src;\n    }\n\n    if(arg_type & ARG_TYPE_EQUALITY)\n    {\n      if(compiled_arg_value < (int)ARRAY_SIZE(equality_names))\n      {\n        write_arg_word(equality_names[compiled_arg_value]);\n      }\n      else\n      {\n        write_arg_word(\"??\");\n      }\n    }\n\n    if(arg_type & ARG_TYPE_THING)\n    {\n      if(compiled_arg_value < (int)ARRAY_SIZE(thing_names))\n      {\n        write_arg_word(thing_names[compiled_arg_value]);\n      }\n      else\n      {\n        write_arg_word(\"Invalid_thing\");\n      }\n    }\n\n    if(arg_type & ARG_TYPE_DIRECTION)\n    {\n      *_output = print_dir(compiled_arg_value, output);\n      return src;\n    }\n\n    if(arg_type & ARG_TYPE_PARAM)\n    {\n      /* Note: disassembling invalid values modulo 256 to match how\n       * they are interpreted. The DOS disassembler would disassemble\n       * all invalid values to p?? and the port disassembler would\n       * produce either p?? or junk prior to 2.93c.\n       */\n      output[0] = 'p';\n      if(compiled_arg_value == 0x100)\n      {\n        output[1] = '?';\n        output[2] = '?';\n        output[3] = 0;\n      }\n      else\n      {\n        sprintf(output + 1, \"%02x\", compiled_arg_value & 0xff);\n      }\n      *_output = output + 3;\n      return src;\n    }\n\n    if(arg_type & ARG_TYPE_COLOR)\n    {\n      print_color(compiled_arg_value, output);\n      *_output = output + 3;\n      return src;\n    }\n\n    /* Character: prefer immediate representation if not a normal char. */\n    if((arg_type & ARG_TYPE_CHARACTER) && (compiled_arg_value & 0xff00))\n      arg_type &= ~ARG_TYPE_CHARACTER;\n\n    if(arg_type & ARG_TYPE_CHARACTER)\n    {\n      *output = '\\'';\n      output++;\n      output += unescape_char(output, compiled_arg_value);\n      *output = '\\'';\n      *_output = output + 1;\n\n      return src;\n    }\n\n    if(arg_type & ARG_TYPE_IMMEDIATE)\n    {\n      char immediate_value[16];\n\n      if(base == 10)\n        sprintf(immediate_value, \"%d\", (short)compiled_arg_value);\n      else\n        sprintf(immediate_value, \"0x%x\", (short)compiled_arg_value);\n\n      write_arg_word(immediate_value);\n    }\n  }\n  else\n  {\n    int arg_length = compiled_arg_type - 1;\n    src++;\n\n    // An expression is an expression if it's allowed to be one.\n    if((src[0] == '(') && (arg_type & ARG_TYPE_COUNTER_LOAD_NAME))\n    {\n      int expression_length = arg_length;\n      char *base_output = output;\n      char *next =\n       legacy_disassemble_print_expression(src, &output, &expression_length, false);\n\n      // For now, letting this fall through if we get something like:\n      // (expr)trailing\n      if(expression_length == 0)\n      {\n        // What happens here is that if the expression (just a normal one,\n        // not interpolated in a name) is just an expression, then it passes\n        // through to be one of these other things, as a name with its pieces\n        // escaped.\n        if(next != src)\n        {\n          *_output = output;\n          return next + 1;\n        }\n      }\n      output = base_output;\n    }\n\n    if(arg_type & (ARG_TYPE_COUNTER_LOAD_NAME | ARG_TYPE_COUNTER_STORE_NAME |\n     ARG_TYPE_LABEL_NAME | ARG_TYPE_EXT_LABEL_NAME | ARG_TYPE_BOARD_NAME |\n     ARG_TYPE_ROBOT_NAME))\n    {\n      if(is_simple_identifier_name(src, arg_length, false))\n      {\n        src = legacy_disassemble_print_string_expressions(src, &output,\n         &arg_length, -1, -1, false);\n      }\n      else\n      {\n        *output = '`';\n        output++;\n        src = legacy_disassemble_print_string_expressions(src, &output,\n         &arg_length, -1, '`', false);\n        *output = '`';\n        output++;\n      }\n\n      *_output = output;\n      return src + 1;\n    }\n\n    if(arg_type & ARG_TYPE_STRING)\n    {\n      *output = '\"';\n      output++;\n      src = legacy_disassemble_print_string_expressions(src, &output,\n       &arg_length, -1, '\"', false);\n      *output = '\"';\n\n      *_output = output + 1;\n      return src + 1;\n    }\n  }\n\n  return src;\n}\n\n__editor_maybe_static\nint legacy_disassemble_command(char *command_base, char *output_base,\n int *line_length, int bytecode_length, boolean print_ignores, int base)\n{\n  int command_length = *command_base;\n  char *command_src = command_base;\n  char *output = output_base;\n  const struct mzx_command *command;\n  enum arg_type arg_type;\n\n  if(command_length > bytecode_length)\n    return 0;\n\n  if(command_length)\n  {\n    int legacy_command_number = command_src[1];\n    int current_command_number;\n    int parameters;\n    int i;\n\n    // Get the command number we're using now, if any.\n    current_command_number = legacy_command_to_current[legacy_command_number];\n\n    switch(current_command_number)\n    {\n      case ROBOTIC_CMD_REMOVED:\n        switch(legacy_command_number)\n        {\n          case ROBOTIC_CMD_OBSOLETE_IF_PLAYER_DIR:\n            current_command_number = ROBOTIC_CMD_IF_CONDITION;\n            command_src[4] = command_src[3];\n            command_src[3] = TOUCHING;\n            break;\n\n          case ROBOTIC_CMD_OBSOLETE_IF_NOT_PLAYER_DIR:\n            current_command_number = ROBOTIC_CMD_IF_NOT_CONDITION;\n            command_src[4] = command_src[3];\n            command_src[3] = TOUCHING;\n            break;\n        }\n        break;\n\n      case ROBOTIC_CMD_BLANK_LINE:\n        *line_length = 0;\n        return 1;\n\n      case ROBOTIC_CMD_COMMENT:\n      {\n        int comment_length = command_src[2];\n\n        if(command_src[3] != '@')\n        {\n          output[0] = '/';\n          output[1] = '/';\n          output[2] = ' ';\n          output += 3;\n\n          command_src += 3;\n          comment_length--;\n          while(comment_length)\n          {\n            *output = *command_src;\n            output++;\n            command_src++;\n            comment_length--;\n          }\n          *line_length = output - output_base;\n          return command_length;\n        }\n      }\n    }\n\n    // If it's no longer supported we might have to do some magic.\n    command = &(command_list[current_command_number]);\n\n    strcpy(output, command->name);\n    output += strlen(command->name);\n    *output = 0;\n    *line_length = output - output_base;\n\n    command_src += 2;\n    parameters = command->parameters;\n\n    *output = ' ';\n    output++;\n\n    for(i = 0; i < parameters; i++)\n    {\n      arg_type = command->param_types[i];\n      command_src = legacy_disassemble_arg(arg_type, command_src, &output,\n       print_ignores, base);\n\n      if(arg_type & ARG_TYPE_IGNORE)\n      {\n        parameters++;\n        if(!print_ignores)\n          break;\n      }\n\n      if(i < (parameters - 1))\n      {\n        *output = ' ';\n        output++;\n      }\n    }\n\n    *line_length = output - output_base;\n  }\n\n  return command_length;\n}\n\nchar *legacy_disassemble_program(char *program_bytecode, int bytecode_length,\n int *_disasm_length, boolean print_ignores, int base)\n{\n  int disasm_length = 0;\n  int disasm_offset = 0;\n  int disasm_length_allocated = 256;\n  char *program_disasm = malloc(disasm_length_allocated);\n\n  // Needs enough room for any expansions - right now that's just ^ to **.\n  char command_buffer[512];\n\n  int command_length;\n  int disasm_line_length;\n\n  program_bytecode++;\n\n  do\n  {\n    // Disassemble command into command_buffer.\n    command_length = legacy_disassemble_command(program_bytecode,\n     command_buffer, &disasm_line_length, bytecode_length,\n     print_ignores, base);\n\n    // See if it's not an ending command.\n    if(command_length)\n    {\n      // Increment the total size (plus size for new line)\n      disasm_length += disasm_line_length + 1;\n\n      // Reallocate buffer if necessary.\n      while(disasm_length > disasm_length_allocated)\n      {\n        disasm_length_allocated *= 2;\n        program_disasm =  realloc(program_disasm, disasm_length_allocated);\n      }\n\n      // Copy new line into buffer.\n      memcpy(program_disasm + disasm_offset, command_buffer,\n       disasm_line_length);\n      command_buffer[disasm_line_length] = 0;\n\n      // Write newline char.\n      disasm_offset += disasm_line_length;\n      program_disasm[disasm_offset] = '\\n';\n      disasm_offset++;\n\n      // Offset bytecode (source) position.\n      program_bytecode += command_length + 2;\n      bytecode_length -= command_length + 2;\n    }\n  } while(command_length);\n\n  // Null terminate and return total size.\n  disasm_length++;\n  program_disasm = realloc(program_disasm, disasm_length);\n  program_disasm[disasm_offset] = 0;\n  *_disasm_length = disasm_length;\n\n  return program_disasm;\n}\n\nchar *legacy_convert_file(char *file_name, int *_disasm_length,\n boolean print_ignores, int base)\n{\n  vfile *legacy_source_file = fsafeopen(file_name, \"rt\");\n\n  if(legacy_source_file)\n  {\n    int disasm_length = 0;\n    int disasm_offset = 0;\n    int disasm_length_allocated = 256;\n    char *program_disasm = malloc(disasm_length_allocated);\n\n    char source_buffer[256];\n    char command_buffer[512];\n    char bytecode_buffer[256];\n    char errors[256];\n\n    int disasm_line_length;\n\n    while(vfsafegets(source_buffer, 256, legacy_source_file))\n    {\n      // Assemble line\n      legacy_assemble_line(source_buffer, bytecode_buffer, errors,\n       NULL, NULL);\n\n      // Disassemble command into command_buffer.\n      legacy_disassemble_command(bytecode_buffer, command_buffer,\n       &disasm_line_length, 256, print_ignores, base);\n      command_buffer[disasm_line_length] = 0;\n\n      // Increment the total size (plus size for new line)\n      disasm_length += disasm_line_length + 1;\n\n      // Reallocate buffer if necessary.\n      while(disasm_length > disasm_length_allocated)\n      {\n        disasm_length_allocated *= 2;\n        program_disasm =  realloc(program_disasm, disasm_length_allocated);\n      }\n\n      // Copy new line into buffer.\n      memcpy(program_disasm + disasm_offset, command_buffer,\n       disasm_line_length);\n\n      // Write newline char.\n      disasm_offset += disasm_line_length;\n      program_disasm[disasm_offset] = '\\n';\n      disasm_offset++;\n    }\n\n    // Null terminate and return total size.\n    disasm_length++;\n    program_disasm = realloc(program_disasm, disasm_length);\n    program_disasm[disasm_offset] = 0;\n    *_disasm_length = disasm_length;\n\n    vfclose(legacy_source_file);\n    return program_disasm;\n  }\n\n  return NULL;\n}\n\nchar *legacy_convert_program(char *src, int len, int *_disasm_length,\n boolean print_ignores, int base)\n{\n  if(len)\n  {\n    struct memfile mf;\n\n    int disasm_length = 0;\n    int disasm_offset = 0;\n    int disasm_length_allocated = 256;\n    char *program_disasm = malloc(disasm_length_allocated);\n\n    char source_buffer[256];\n    char command_buffer[512];\n    char bytecode_buffer[256];\n    char errors[256];\n\n    int disasm_line_length;\n\n    mfopen(src, len, &mf);\n\n    // Copying to a buffer isn't the quickest solution, but trying to\n    // handle it differently turns into spaghetti.\n    while(mfsafegets(source_buffer, 256, &mf))\n    {\n      // Assemble line\n      legacy_assemble_line(source_buffer, bytecode_buffer, errors,\n       NULL, NULL);\n\n      // Disassemble command into command_buffer.\n      legacy_disassemble_command(bytecode_buffer, command_buffer,\n       &disasm_line_length, 256, print_ignores, base);\n      command_buffer[disasm_line_length] = 0;\n\n      // Increment the total size (plus size for new line)\n      disasm_length += disasm_line_length + 1;\n\n      // Reallocate buffer if necessary.\n      while(disasm_length > disasm_length_allocated)\n      {\n        disasm_length_allocated *= 2;\n        program_disasm =  realloc(program_disasm, disasm_length_allocated);\n      }\n\n      // Copy new line into buffer.\n      memcpy(program_disasm + disasm_offset, command_buffer,\n       disasm_line_length);\n\n      // Write newline char.\n      disasm_offset += disasm_line_length;\n      program_disasm[disasm_offset] = '\\n';\n      disasm_offset++;\n    }\n\n    // Null terminate and return total size.\n    disasm_length++;\n    program_disasm = realloc(program_disasm, disasm_length);\n    program_disasm[disasm_offset] = 0;\n    *_disasm_length = disasm_length;\n\n    return program_disasm;\n  }\n\n  return NULL;\n}\n\n#endif /* CONFIG_DEBYTECODE */\n"
  },
  {
    "path": "src/rasm.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __RASM_H\n#define __RASM_H\n\n#include \"compat.h\"\n#include \"legacy_rasm.h\"\n\n#ifdef CONFIG_DEBYTECODE\n\n#include \"data.h\"\n\n#include <limits.h>\n\n// I really didn't want to have all this stuff in the header file, but exposing\n// tokens to the outside world dragged it all in.\n\n\n// The argument type both specifies what it is lexically and what it is\n// functionally. The functionality is a finer grained specification than\n// the lexical type. For instance, any \"name\" token will match any of the\n// arg types ending in \"NAME\", but the type of name could affect what kind\n// of further compile time checking is done (can that name be available,\n// for instance) and it can affect what it is compiled to.\n\n// A command's parameters can accept any one of the following parameter\n// types, but in practice it'll use a few common groupings (basically,\n// anything that will resolve to the same type, IE immediate and\n// counter_load_name). The exceptions to this are for \"ignore\" and\n// \"fragment\", which use the lower bits to determine a specific type\n// that has to be matched.\n\n// These are listed in order of priority. What that means is that if a\n// token can ambiguously be multiple kinds of arguments then it will\n// match in the order here from top to bottom. In practice I don't\n// think that much ambiguity will be possible, but for instance something\n// like \"c00\" will always be a color before it is a counter load name.\n\nenum arg_type\n{\n  ARG_TYPE_IMMEDIATE          = (1 << 0),\n  ARG_TYPE_STRING             = (1 << 1),\n  ARG_TYPE_CHARACTER          = (1 << 2),\n  ARG_TYPE_COLOR              = (1 << 3),\n  ARG_TYPE_PARAM              = (1 << 4),\n  ARG_TYPE_COUNTER_LOAD_NAME  = (1 << 5),\n  ARG_TYPE_COUNTER_STORE_NAME = (1 << 6),\n  ARG_TYPE_LABEL_NAME         = (1 << 7),\n  ARG_TYPE_EXT_LABEL_NAME     = (1 << 8),\n  ARG_TYPE_BOARD_NAME         = (1 << 9),\n  ARG_TYPE_ROBOT_NAME         = (1 << 10),\n  ARG_TYPE_DIRECTION          = (1 << 11),\n  ARG_TYPE_THING              = (1 << 12),\n  ARG_TYPE_EQUALITY           = (1 << 13),\n  ARG_TYPE_CONDITION          = (1 << 14),\n  ARG_TYPE_ITEM               = (1 << 15),\n  ARG_TYPE_UNKNOWN            = (1 << 16),\n\n  ARG_TYPE_IGNORE             = (1 << 30),\n\n  /**\n   * Hi, I'm GCC. Having (1 << 31) would be too easy, so I'll complain about\n   * that not being a constant integer expression. Lower the shift values so\n   * there aren't negative numbers? Guess what, now it's an unsigned enum!\n   * Think you can still get an unsigned enum with (1u << 31)? Nope, enum is\n   * restricted to the range of int and I'm going to complain about that too.\n   * This is what you have to do to get this value to work:\n   */\n  ARG_TYPE_FRAGMENT           = (int)(1u << 31)\n};\n\nenum arg_type_indexed\n{\n  ARG_TYPE_INDEXED_IMMEDIATE,\n  ARG_TYPE_INDEXED_STRING,\n  ARG_TYPE_INDEXED_CHARACTER,\n  ARG_TYPE_INDEXED_COLOR,\n  ARG_TYPE_INDEXED_PARAM,\n  ARG_TYPE_INDEXED_NAME,\n  ARG_TYPE_INDEXED_DIRECTION,\n  ARG_TYPE_INDEXED_THING,\n  ARG_TYPE_INDEXED_EQUALITY,\n  ARG_TYPE_INDEXED_CONDITION,\n  ARG_TYPE_INDEXED_ITEM,\n  ARG_TYPE_INDEXED_IGNORE,\n  ARG_TYPE_INDEXED_FRAGMENT,\n  ARG_TYPE_INDEXED_COMMAND,\n  ARG_TYPE_INDEXED_EXPRESSION,\n  ARG_TYPE_INDEXED_LABEL,\n  ARG_TYPE_INDEXED_COMMENT,\n};\n\nenum token_type\n{\n  TOKEN_TYPE_NUMERIC_LITERAL_BASE10,\n  TOKEN_TYPE_NUMERIC_LITERAL_BASE16,\n  TOKEN_TYPE_STRING_LITERAL,\n  TOKEN_TYPE_CHARACTER_LITERAL,\n  TOKEN_TYPE_NAME,\n  TOKEN_TYPE_EXPRESSION,\n  TOKEN_TYPE_EQUALITY,\n  TOKEN_TYPE_BASIC_IDENTIFIER,\n  TOKEN_TYPE_COMMENT,\n  TOKEN_TYPE_BASIC_COLOR,\n  TOKEN_TYPE_BASIC_PARAM,\n  TOKEN_TYPE_WILDCARD_COLOR,\n  TOKEN_TYPE_WILDCARD_PARAM,\n  TOKEN_TYPE_INVALID              = 0x4000000,\n  TOKEN_TYPE_CONTAINS_EXPRESSIONS = 0x8000000,\n};\n\nenum token_basic_string_type\n{\n  TOKEN_BASIC_STRING_DIRECTION,\n  TOKEN_BASIC_STRING_THING,\n  TOKEN_BASIC_STRING_CONDITION,\n  TOKEN_BASIC_STRING_ITEM,\n  TOKEN_BASIC_STRING_IGNORE,\n  TOKEN_BASIC_STRING_FRAGMENT\n};\n\nstruct special_word\n{\n  const char *const name;\n  int instance_type;\n  enum arg_type arg_type;\n};\n\n\n// A token is exactly one of these.\n\n// All tokens are separated by spaces. Tokens may contain spaces if they're\n// delimitted by (double) quotes, backticks, or are expressions. If\n// delimitted by a particular character but can contain expressions, then\n// that character does not act as a delimitter if it shows up within the\n// expressions themselves. If a token contains expressions then its type\n// will have TOKEN_TYPE_CONTAINS_EXPRESSIONS logically ORed into it.\n\n// Numeric literal base10: a base 10 number.\n//  Can range from -2147483648 to 2147483647.\n//  Eg: 123456789\n\n// Numeric literal base16: a base 16 number starting with 0x (not $ like\n//  the old style).\n//  Can range from 0x00000000 to 0xFFFFFFFF.\n//  Eg: 0xDEADF00D\n\n// String literal: a character string enclosed in double quote characters.\n//  Refers to text that is processed directly as such in-game.\n//  May contain expressions.\n//  Eg: \"Hello world!\"\n\n// Character literal: a single character enclosed in single quotes.\n//  Eg: 'q'\n\n// Name: a character string enclosed in backticks. May contain expressions.\n//  Refers to some named entity like a counter, robot, board, label, etc.\n//  Eg: `x marks the counter`\n\n// Equality symbol: symbol used for comparison\n//  One of: <, >, =, ==, <>, ><, and !=\n\n// Expression: A mathematical expression separated by parentheses. The\n//  grammar for expressions is as follows (\\| is literal | character)\n//  expr           := (expr_terms)\n//  expr_terms     := expr_value | expr_value expr_binary_op expr_terms |\n//                    expr_unary_op expr_terms | expr\n//  expr_value     := numeric_literal | name\n//  expr_binary_op := + | - | * | / | % | ** | >> | >>> | << | > | < | >= |\n//                    <= | = | != | & | \\| | ^\n//  expr_unary_op  := - | ~\n\n// Where the following binary operations correspond to the following things:\n// +    Addition\n// -    Subtraction\n// *    Multiplication\n// /    Division\n// %    Modulo\n// **   Exponent (a ** b is a to the b order/power)\n// >>   Logical bitshift right\n// >>>  Arithmetic bitshift right\n// <<   Bitshift left\n// >    Greater than; evaluates to 1 if true, 0 if false\n// <    Less than; evaluates to 1 if true, 0 if false\n// >=   Greater than or equal to; evaluates to 1 if true, 0 if false\n// <=   Less than or equal to; evaluates to 1 if true, 0 if false\n// =    Equal to; evaluates to 1 if true, 0 if false\n// !=   Not equal to; evaluates to 1 if true, 0 if false\n// &    Bitwise AND (not logical AND)\n// |    Bitwise OR (not logical OR)\n// ^    Bitwise XOR (not logical XOR)\n\n// And the following unary operations correspond to the follow things:\n// -    Two's complement negation\n// ~    One's complement negation (bitwise NOT)\n\n// Basic identifier: A string not delimitted by a special character that\n//  could be a name or could be part of a command, aliased argument, param,\n//  color, etc.\n//  Must not contain spaces. Note that this does not include anything that\n//  falls under \"ignored\" strings.\n//  Eg: this_counter\n//  Eg: NORTH\n//  Eg: p??\n\n// Ignore: Connecting words that are thrown out by the tokenizer and are\n//  only allowed to make code more expressive/readible.\n//  Includes:\n//  , (comma)\n//  ; (semicolon)\n//  a\n//  an\n//  and\n//  as\n//  at\n//  by\n//  else\n//  for\n//  from\n//  into\n//  is\n//  of\n//  the\n//  then\n//  there\n//  through\n//  thru\n//  to\n//  with\n\n// Invalid means that it won't let it pass. This includes malformed expressions\n// (anything that begins with an ( and goes wrong from there)\n\nunion token_value\n{\n  int command_number;\n  int numeric_literal;\n  char char_literal;\n  enum equality equality_type;\n  const struct special_word *special_word;\n};\n\nstruct token\n{\n  enum token_type type;\n  union token_value arg_value;\n  enum arg_type_indexed arg_type_indexed;\n  boolean value_is_cached;\n  char *value;\n  int length;\n};\n\n\n__M_BEGIN_DECLS\n\nchar *legacy_disassemble_program(char *program_bytecode, int bytecode_length,\n int *_disasm_length, boolean print_ignores, int base);\nchar *legacy_convert_file(char *file_name, int *_disasm_length,\n boolean print_ignores, int base);\nchar *legacy_convert_program(char *src, int len, int *_disasm_length,\n boolean print_ignores, int base);\n\nchar *find_non_identifier_char(char *str);\n\nCORE_LIBSPEC void assemble_program(char *program_source, char **_bytecode,\n int *_bytecode_length, struct command_mapping **_command_map,\n int *_command_map_length);\n\n#ifdef CONFIG_EDITOR\n\nCORE_LIBSPEC int legacy_disassemble_command(char *command_base, char *output_base,\n int *line_length, int bytecode_length, boolean print_ignores, int base);\n\nCORE_LIBSPEC struct token *parse_command(char *src, char **_next,\n int *num_parse_tokens);\nCORE_LIBSPEC void print_color(int color, char *color_buffer);\nCORE_LIBSPEC int unescape_char(char *dest, char c);\nCORE_LIBSPEC int get_thing(char *name, int name_length);\n\n#endif // CONFIG_EDITOR\n\n__M_END_DECLS\n\n#endif // CONFIG_DEBYTECODE\n\n#endif // __RASM_H\n"
  },
  {
    "path": "src/render.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n\n#include \"graphics.h\"\n#include \"platform_endian.h\"\n#include \"render.h\"\n#include \"render_layer.h\"\n#include \"util.h\"\n#include \"yuv.h\"\n\nstatic void set_colors8_mzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg)\n{\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n  char_colors[0] = (bg << 24) | (bg << 16) | (bg << 8) | bg;\n  char_colors[1] = (bg << 24) | (bg << 16) | (bg << 8) | fg;\n  char_colors[2] = (bg << 24) | (bg << 16) | (fg << 8) | bg;\n  char_colors[3] = (bg << 24) | (bg << 16) | (fg << 8) | fg;\n  char_colors[4] = (bg << 24) | (fg << 16) | (bg << 8) | bg;\n  char_colors[5] = (bg << 24) | (fg << 16) | (bg << 8) | fg;\n  char_colors[6] = (bg << 24) | (fg << 16) | (fg << 8) | bg;\n  char_colors[7] = (bg << 24) | (fg << 16) | (fg << 8) | fg;\n  char_colors[8] = (fg << 24) | (bg << 16) | (bg << 8) | bg;\n  char_colors[9] = (fg << 24) | (bg << 16) | (bg << 8) | fg;\n  char_colors[10] = (fg << 24) | (bg << 16) | (fg << 8) | bg;\n  char_colors[11] = (fg << 24) | (bg << 16) | (fg << 8) | fg;\n  char_colors[12] = (fg << 24) | (fg << 16) | (bg << 8) | bg;\n  char_colors[13] = (fg << 24) | (fg << 16) | (bg << 8) | fg;\n  char_colors[14] = (fg << 24) | (fg << 16) | (fg << 8) | bg;\n  char_colors[15] = (fg << 24) | (fg << 16) | (fg << 8) | fg;\n#else\n  char_colors[0] = (bg << 24) | (bg << 16) | (bg << 8) | bg;\n  char_colors[1] = (fg << 24) | (bg << 16) | (bg << 8) | bg;\n  char_colors[2] = (bg << 24) | (fg << 16) | (bg << 8) | bg;\n  char_colors[3] = (fg << 24) | (fg << 16) | (bg << 8) | bg;\n  char_colors[4] = (bg << 24) | (bg << 16) | (fg << 8) | bg;\n  char_colors[5] = (fg << 24) | (bg << 16) | (fg << 8) | bg;\n  char_colors[6] = (bg << 24) | (fg << 16) | (fg << 8) | bg;\n  char_colors[7] = (fg << 24) | (fg << 16) | (fg << 8) | bg;\n  char_colors[8] = (bg << 24) | (bg << 16) | (bg << 8) | fg;\n  char_colors[9] = (fg << 24) | (bg << 16) | (bg << 8) | fg;\n  char_colors[10] = (bg << 24) | (fg << 16) | (bg << 8) | fg;\n  char_colors[11] = (fg << 24) | (fg << 16) | (bg << 8) | fg;\n  char_colors[12] = (bg << 24) | (bg << 16) | (fg << 8) | fg;\n  char_colors[13] = (fg << 24) | (bg << 16) | (fg << 8) | fg;\n  char_colors[14] = (bg << 24) | (fg << 16) | (fg << 8) | fg;\n  char_colors[15] = (fg << 24) | (fg << 16) | (fg << 8) | fg;\n#endif\n}\n\nstatic void set_colors8_smzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg)\n{\n  const uint8_t *indices = graphics->smzx_indices;\n  uint32_t bb, bf, fb, ff;\n  bg &= 0x0F;\n  fg &= 0x0F;\n  indices += ((bg << 4) | fg) << 2;\n\n  bb = indices[0];\n  bf = indices[1];\n  fb = indices[2];\n  ff = indices[3];\n\n  bb |= bb << 8;\n  bf |= bf << 8;\n  fb |= fb << 8;\n  ff |= ff << 8;\n\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n  char_colors[0] = (bb << 16) | bb;\n  char_colors[1] = (bb << 16) | bf;\n  char_colors[2] = (bb << 16) | fb;\n  char_colors[3] = (bb << 16) | ff;\n  char_colors[4] = (bf << 16) | bb;\n  char_colors[5] = (bf << 16) | bf;\n  char_colors[6] = (bf << 16) | fb;\n  char_colors[7] = (bf << 16) | ff;\n  char_colors[8] = (fb << 16) | bb;\n  char_colors[9] = (fb << 16) | bf;\n  char_colors[10] = (fb << 16) | fb;\n  char_colors[11] = (fb << 16) | ff;\n  char_colors[12] = (ff << 16) | bb;\n  char_colors[13] = (ff << 16) | bf;\n  char_colors[14] = (ff << 16) | fb;\n  char_colors[15] = (ff << 16) | ff;\n#else\n  char_colors[0] = (bb << 16) | bb;\n  char_colors[1] = (bf << 16) | bb;\n  char_colors[2] = (fb << 16) | bb;\n  char_colors[3] = (ff << 16) | bb;\n  char_colors[4] = (bb << 16) | bf;\n  char_colors[5] = (bf << 16) | bf;\n  char_colors[6] = (fb << 16) | bf;\n  char_colors[7] = (ff << 16) | bf;\n  char_colors[8] = (bb << 16) | fb;\n  char_colors[9] = (bf << 16) | fb;\n  char_colors[10] = (fb << 16) | fb;\n  char_colors[11] = (ff << 16) | fb;\n  char_colors[12] = (bb << 16) | ff;\n  char_colors[13] = (bf << 16) | ff;\n  char_colors[14] = (fb << 16) | ff;\n  char_colors[15] = (ff << 16) | ff;\n#endif\n}\n\nstatic void set_colors16_mzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg)\n{\n  uint32_t cb_bg, cb_fg;\n\n  cb_bg = graphics->flat_intensity_palette[bg];\n  cb_fg = graphics->flat_intensity_palette[fg];\n\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n        char_colors[0] = (cb_bg << 16) | cb_bg;\n        char_colors[1] = (cb_bg << 16) | cb_fg;\n        char_colors[2] = (cb_fg << 16) | cb_bg;\n        char_colors[3] = (cb_fg << 16) | cb_fg;\n#else\n        char_colors[0] = (cb_bg << 16) | cb_bg;\n        char_colors[1] = (cb_fg << 16) | cb_bg;\n        char_colors[2] = (cb_bg << 16) | cb_fg;\n        char_colors[3] = (cb_fg << 16) | cb_fg;\n#endif\n}\n\nstatic void set_colors16_smzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg)\n{\n  const uint8_t *indices = graphics->smzx_indices;\n  bg &= 0x0F;\n  fg &= 0x0F;\n  indices += ((bg << 4) | fg) << 2;\n\n  char_colors[0] = graphics->flat_intensity_palette[indices[0]];\n  char_colors[1] = graphics->flat_intensity_palette[indices[1]];\n  char_colors[2] = graphics->flat_intensity_palette[indices[2]];\n  char_colors[3] = graphics->flat_intensity_palette[indices[3]];\n\n  char_colors[0] = (char_colors[0] << 16) | char_colors[0];\n  char_colors[1] = (char_colors[1] << 16) | char_colors[1];\n  char_colors[2] = (char_colors[2] << 16) | char_colors[2];\n  char_colors[3] = (char_colors[3] << 16) | char_colors[3];\n}\n\nstatic void set_colors32_mzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg)\n{\n  char_colors[0] = graphics->flat_intensity_palette[bg];\n  char_colors[1] = graphics->flat_intensity_palette[fg];\n}\n\nstatic void set_colors32_smzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg)\n{\n  const uint8_t *indices = graphics->smzx_indices;\n  bg &= 0x0F;\n  fg &= 0x0F;\n  indices += ((bg << 4) | fg) << 2;\n\n  char_colors[0] = graphics->flat_intensity_palette[indices[0]];\n  char_colors[1] = graphics->flat_intensity_palette[indices[1]];\n  char_colors[2] = graphics->flat_intensity_palette[indices[2]];\n  char_colors[3] = graphics->flat_intensity_palette[indices[3]];\n}\n\nconst set_colors_function set_colors8[4] =\n{\n  set_colors8_mzx,\n  set_colors8_smzx,\n  set_colors8_smzx,\n  set_colors8_smzx\n};\n\nconst set_colors_function set_colors16[4] =\n{\n  set_colors16_mzx,\n  set_colors16_smzx,\n  set_colors16_smzx,\n  set_colors16_smzx\n};\n\n/* The 32-bit set colors functions should be inlined in render_graph32 and\n * render_graph32s, but they may be needed by non-32 bit render_graph\n * implementations (e.g. SMZX with chroma subsampling in render_graph16), so\n * also provide them as an array.\n */\nconst set_colors_function set_colors32[4] =\n{\n  set_colors32_mzx,\n  set_colors32_smzx,\n  set_colors32_smzx,\n  set_colors32_smzx\n};\n\n/* YUY2 chroma subsampling set_colors function for use with render_graph16. */\nvoid yuy2_subsample_set_colors_mzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg)\n{\n  uint32_t yuv_bg = graphics->flat_intensity_palette[bg];\n  uint32_t yuv_fg = graphics->flat_intensity_palette[fg];\n\n  char_colors[0] = yuv_bg;\n  char_colors[1] = yuy2_subsample(yuv_bg, yuv_fg);\n  char_colors[2] = yuy2_subsample(yuv_fg, yuv_bg);\n  char_colors[3] = yuv_fg;\n}\n\n/* UYVY chroma subsampling set_colors function for use with render_graph16. */\nvoid uyvy_subsample_set_colors_mzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg)\n{\n  uint32_t yuv_bg = graphics->flat_intensity_palette[bg];\n  uint32_t yuv_fg = graphics->flat_intensity_palette[fg];\n\n  char_colors[0] = yuv_bg;\n  char_colors[1] = uyvy_subsample(yuv_bg, yuv_fg);\n  char_colors[2] = uyvy_subsample(yuv_fg, yuv_bg);\n  char_colors[3] = yuv_fg;\n}\n\n/* YVYU chroma subsampling set_colors function for use with render_graph16. */\nvoid yvyu_subsample_set_colors_mzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg)\n{\n  uint32_t yuv_bg = graphics->flat_intensity_palette[bg];\n  uint32_t yuv_fg = graphics->flat_intensity_palette[fg];\n\n  char_colors[0] = yuv_bg;\n  char_colors[1] = yvyu_subsample(yuv_bg, yuv_fg);\n  char_colors[2] = yvyu_subsample(yuv_fg, yuv_bg);\n  char_colors[3] = yuv_fg;\n}\n\n// Nominally 8-bit (Character graphics 8 bytes wide)\nvoid render_graph8(uint8_t * RESTRICT pixels, size_t pitch,\n const struct graphics_data *graphics, set_colors_function set_colors)\n{\n  uint32_t *dest;\n  uint32_t *ldest, *ldest2;\n  const struct char_element *src = graphics->text_video;\n  const uint8_t *char_ptr;\n  uint32_t char_colors[16];\n  unsigned int old_bg = 255;\n  unsigned int old_fg = 255;\n  unsigned int current_char_byte;\n  unsigned int i, i2, i3;\n  size_t line_advance = pitch / 4;\n  size_t row_advance = line_advance * 14;\n\n  dest = (uint32_t *)pixels;\n\n  for(i = 0; i < 25; i++)\n  {\n    ldest2 = dest;\n    for(i2 = 0; i2 < 80; i2++)\n    {\n      ldest = dest;\n      if((src->bg_color != old_bg) || (src->fg_color != old_fg))\n      {\n        set_colors(graphics, char_colors, src->bg_color, src->fg_color);\n        old_bg = src->bg_color;\n        old_fg = src->fg_color;\n      }\n\n      char_ptr = graphics->charset + (src->char_value * 14);\n      src++;\n      for(i3 = 0; i3 < 14; i3++)\n      {\n        current_char_byte = *char_ptr;\n        char_ptr++;\n        *dest = char_colors[current_char_byte >> 4];\n        *(dest + 1) = char_colors[current_char_byte & 0x0F];\n        dest += line_advance;\n      }\n\n      dest = ldest + 2;\n    }\n    dest = ldest2 + row_advance;\n  }\n}\n\n// Nominally 16-bit (Character graphics 16 bytes wide)\nvoid render_graph16(uint16_t * RESTRICT pixels, size_t pitch,\n const struct graphics_data *graphics, set_colors_function set_colors)\n{\n  uint32_t *dest;\n  uint32_t *ldest, *ldest2;\n  const struct char_element *src = graphics->text_video;\n  const uint8_t *char_ptr;\n  uint32_t char_colors[4];\n  unsigned int old_bg = 255;\n  unsigned int old_fg = 255;\n  unsigned int current_char_byte;\n  unsigned int i, i2, i3;\n  size_t line_advance = pitch / 4;\n  size_t row_advance = line_advance * 14;\n\n  dest = (uint32_t *)pixels;\n\n  for(i = 0; i < 25; i++)\n  {\n    ldest2 = dest;\n    for(i2 = 0; i2 < 80; i2++)\n    {\n      ldest = dest;\n      if((src->bg_color != old_bg) || (src->fg_color != old_fg))\n      {\n        set_colors(graphics, char_colors, src->bg_color, src->fg_color);\n        old_bg = src->bg_color;\n        old_fg = src->fg_color;\n      }\n\n      char_ptr = graphics->charset + (src->char_value * 14);\n      src++;\n      for(i3 = 0; i3 < 14; i3++)\n      {\n        current_char_byte = *char_ptr;\n        char_ptr++;\n        *dest = char_colors[current_char_byte >> 6];\n        *(dest + 1) = char_colors[(current_char_byte >> 4) & 0x03];\n        *(dest + 2) = char_colors[(current_char_byte >> 2) & 0x03];\n        *(dest + 3) = char_colors[current_char_byte & 0x03];\n        dest += line_advance;\n      }\n\n      dest = ldest + 4;\n    }\n    dest = ldest2 + row_advance;\n  }\n}\n\n/* Nominally 32-bit (Character graphics 32 bytes wide)\n * Because the render_graph32 functions are guaranteed to map a single pixel to\n * a single 32-bit color they can used fixed set_colors functions, hopefully\n * saving time on platforms where that would actually matter.\n */\nvoid render_graph32(uint32_t * RESTRICT pixels, size_t pitch,\n const struct graphics_data *graphics)\n{\n  uint32_t *dest;\n  uint32_t *ldest, *ldest2;\n  const struct char_element *src = graphics->text_video;\n  const uint8_t *char_ptr;\n  uint32_t char_colors[2];\n  unsigned int old_bg = 255;\n  unsigned int old_fg = 255;\n  unsigned int current_char_byte;\n  unsigned int i, i2, i3;\n  int i4;\n  size_t line_advance = pitch / 4;\n  size_t line_advance_sub = line_advance - 8;\n  size_t row_advance = line_advance * 14;\n\n  dest = pixels;\n\n  for(i = 0; i < 25; i++)\n  {\n    ldest2 = dest;\n    for(i2 = 0; i2 < 80; i2++)\n    {\n      ldest = dest;\n      if((src->bg_color != old_bg) || (src->fg_color != old_fg))\n      {\n        set_colors32_mzx(graphics, char_colors, src->bg_color, src->fg_color);\n        old_bg = src->bg_color;\n        old_fg = src->fg_color;\n      }\n\n      char_ptr = graphics->charset + (src->char_value * 14);\n      src++;\n      for(i3 = 0; i3 < 14; i3++)\n      {\n        current_char_byte = *char_ptr;\n        char_ptr++;\n        for(i4 = 7; i4 >= 0; i4--, dest++)\n        {\n          *dest = char_colors[(current_char_byte >> i4) & 0x01];\n        }\n        dest += line_advance_sub;\n      }\n\n      dest = ldest + 8;\n    }\n    dest = ldest2 + row_advance;\n  }\n}\n\nvoid render_graph32s(uint32_t * RESTRICT pixels, size_t pitch,\n const struct graphics_data *graphics)\n{\n  uint32_t *dest;\n  uint32_t *ldest, *ldest2;\n  const struct char_element *src = graphics->text_video;\n  const uint8_t *char_ptr;\n  uint32_t char_colors[4];\n  uint32_t current_color;\n  unsigned int old_bg = 255;\n  unsigned int old_fg = 255;\n  unsigned int current_char_byte;\n  unsigned int i, i2, i3;\n  int i4;\n  size_t line_advance = pitch / 4;\n  size_t line_advance_sub = line_advance - 8;\n  size_t row_advance = line_advance * 14;\n\n  dest = pixels;\n\n  for(i = 0; i < 25; i++)\n  {\n    ldest2 = dest;\n    for(i2 = 0; i2 < 80; i2++)\n    {\n      ldest = dest;\n      if((src->bg_color != old_bg) || (src->fg_color != old_fg))\n      {\n        set_colors32_smzx(graphics, char_colors, src->bg_color, src->fg_color);\n        old_bg = src->bg_color;\n        old_fg = src->fg_color;\n      }\n\n      char_ptr = graphics->charset + (src->char_value * 14);\n      src++;\n      for(i3 = 0; i3 < 14; i3++)\n      {\n        current_char_byte = *char_ptr;\n        char_ptr++;\n        for(i4 = 6; i4 >= 0; i4 -= 2, dest += 2)\n        {\n          current_color = char_colors[(current_char_byte >> i4) & 0x03];\n          *dest = current_color;\n          *(dest + 1) = current_color;\n        }\n        dest += line_advance_sub;\n      }\n\n      dest = ldest + 8;\n    }\n    dest = ldest2 + row_advance;\n  }\n}\n\nvoid render_cursor(uint32_t *pixels, size_t pitch, uint8_t bpp, unsigned int x,\n unsigned int y, uint32_t flatcolor, uint8_t lines, uint8_t offset)\n{\n  unsigned int i, j;\n  unsigned int size = bpp / 4;\n  size_t line_advance = pitch / 4;\n  size_t line_advance_sub = line_advance - size;\n  size_t row_advance = line_advance * 14;\n\n  uint32_t *dest = pixels + (x * size) + (y * row_advance) + (offset * line_advance);\n\n  for(i = 0; i < lines; i++)\n  {\n    for(j = 0; j < size; j++)\n      *(dest++) = flatcolor;\n\n    dest += line_advance_sub;\n  }\n}\n\nvoid render_mouse(uint32_t *pixels, size_t pitch, uint8_t bpp, unsigned int x,\n unsigned int y, uint32_t mask, uint32_t amask, uint8_t w, uint8_t h)\n{\n  unsigned int i, j;\n  unsigned int size = w * bpp / 32;\n  size_t line_advance = pitch / 4;\n  size_t line_advance_sub = line_advance - size;\n\n  uint32_t *dest = pixels + (x * bpp / 32) + (y * line_advance);\n\n  for(i = 0; i < h; i++)\n  {\n    for(j = 0; j < size; j++)\n    {\n      *dest = amask | (*dest ^ mask);\n      dest++;\n    }\n    dest += line_advance_sub;\n  }\n}\n\nvoid get_screen_coords_viewport(struct graphics_data *graphics,\n struct video_window *window, int screen_x, int screen_y,\n int *x, int *y, int *min_x, int *min_y, int *max_x, int *max_y)\n{\n  int rel_x = screen_x - window->viewport_x;\n  int rel_y = screen_y - window->viewport_y;\n\n  if(window->viewport_width != SCREEN_PIX_W)\n    rel_x = rel_x * SCREEN_PIX_W / window->viewport_width;\n\n  if(window->viewport_height != SCREEN_PIX_H)\n    rel_y = rel_y * SCREEN_PIX_H / window->viewport_height;\n\n  *x = CLAMP(rel_x, 0, SCREEN_PIX_W - 1);\n  *y = CLAMP(rel_y, 0, SCREEN_PIX_H - 1);\n  *min_x = window->viewport_x;\n  *min_y = window->viewport_y;\n  *max_x = window->viewport_x + window->viewport_width - 1;\n  *max_y = window->viewport_y + window->viewport_height - 1;\n}\n\nvoid set_screen_coords_viewport(struct graphics_data *graphics,\n struct video_window *window, int x, int y,\n int *screen_x, int *screen_y)\n{\n  *screen_x = x * window->viewport_width / SCREEN_PIX_W + window->viewport_x;\n  *screen_y = y * window->viewport_height / SCREEN_PIX_H + window->viewport_y;\n}\n\nvoid set_window_viewport_centered(struct graphics_data *graphics,\n struct video_window *window)\n{\n  window->viewport_x = ((int)window->width_px - SCREEN_PIX_W) >> 1;\n  window->viewport_y = ((int)window->height_px - SCREEN_PIX_H) >> 1;\n  window->viewport_width = SCREEN_PIX_W;\n  window->viewport_height = SCREEN_PIX_H;\n  window->is_integer_scaled = true;\n}\n\n#ifdef HAVE_SET_WINDOW_VIEWPORT_SCALED\n\nvoid set_window_viewport_scaled(struct graphics_data *graphics,\n struct video_window *window)\n{\n  int numerator = window->ratio_numerator;\n  int denominator = window->ratio_denominator;\n  int width = MAX(window->width_px, 1);\n  int height = MAX(window->height_px, 1);\n\n  if(numerator > 0 && denominator > 0)\n  {\n    // (width / height) < (numerator / denominator)\n    // Multiply both sides by (height * denominator):\n    if(width * denominator < height * numerator)\n    {\n      height = (width * denominator) / numerator;\n      height = MAX(height, 1);\n    }\n    else\n    {\n      width = (height * numerator) / denominator;\n      width = MAX(width, 1);\n    }\n  }\n\n  window->viewport_x = ((int)window->width_px - width) >> 1;\n  window->viewport_y = ((int)window->height_px - height) >> 1;\n  window->viewport_width = width;\n  window->viewport_height = height;\n  window->is_integer_scaled = false;\n\n  if((width % SCREEN_PIX_W == 0) && (height % SCREEN_PIX_H == 0))\n    window->is_integer_scaled = true;\n}\n\n#endif /* HAVE_SET_WINDOW_VIEWPORT_SCALED */\n"
  },
  {
    "path": "src/render.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __RENDER_H\n#define __RENDER_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"graphics.h\"\n\ntypedef void (*set_colors_function)(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg);\n\nextern const set_colors_function set_colors8[4];\nextern const set_colors_function set_colors16[4];\nextern const set_colors_function set_colors32[4];\n\nvoid yuy2_subsample_set_colors_mzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg);\nvoid uyvy_subsample_set_colors_mzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg);\nvoid yvyu_subsample_set_colors_mzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg);\n\nvoid render_graph8(uint8_t * RESTRICT pixels, size_t pitch,\n const struct graphics_data *graphics, set_colors_function set_colors);\nvoid render_graph16(uint16_t * RESTRICT pixels, size_t pitch,\n const struct graphics_data *graphics, set_colors_function set_colors);\nvoid render_graph32(uint32_t * RESTRICT pixels, size_t pitch,\n const struct graphics_data *graphics);\nvoid render_graph32s(uint32_t * RESTRICT pixels, size_t pitch,\n const struct graphics_data *graphics);\n\nvoid render_cursor(uint32_t *pixels, size_t pitch, uint8_t bpp, unsigned int x,\n unsigned int y, uint32_t flatcolor, uint8_t lines, uint8_t offset);\nvoid render_mouse(uint32_t *pixels, size_t pitch, uint8_t bpp, unsigned int x,\n unsigned int y, uint32_t mask, uint32_t amask, uint8_t w, uint8_t h);\n\nvoid get_screen_coords_viewport(struct graphics_data *graphics,\n struct video_window *window, int screen_x, int screen_y,\n int *x, int *y, int *min_x, int *min_y, int *max_x, int *max_y);\nvoid set_screen_coords_viewport(struct graphics_data *graphics,\n struct video_window *window, int x, int y,\n int *screen_x, int *screen_y);\nvoid set_window_viewport_centered(struct graphics_data *graphics,\n struct video_window *window);\n\n#if defined(CONFIG_RENDER_GL_FIXED) || defined(CONFIG_RENDER_GL_PROGRAM) \\\n || defined(CONFIG_RENDER_SOFTSCALE) || defined(CONFIG_RENDER_SDLACCEL) \\\n || defined(CONFIG_RENDER_YUV) || defined(CONFIG_RENDER_GX) \\\n || defined(MZX_UNIT_TESTS)\n\n#define HAVE_SET_WINDOW_VIEWPORT_SCALED\n\nvoid set_window_viewport_scaled(struct graphics_data *graphics,\n struct video_window *window);\n\n#endif /* CONFIG_RENDER_GL_FIXED || CONFIG_RENDER_GL_PROGRAM || ... */\n\n__M_END_DECLS\n\n#endif // __RENDER_H\n"
  },
  {
    "path": "src/render_egl.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <GLES/gl.h>\n#include \"render_egl.h\"\n\n#include \"util.h\"\n\n#include <assert.h>\n#include <dlfcn.h>\n\n#ifdef CONFIG_X11\n#include <X11/Xlib.h>\n#endif\n\nstatic void *glso;\nstatic enum gl_lib_type gl_type;\n\n#ifdef ANDROID\n\ndso_fn_ptr GL_GetProcAddress(const char *proc)\n{\n  union dso_suppress_warning value;\n  value.in = dlsym(glso, proc);\n  return value.out;\n}\n\n#endif /* ANDROID */\n\nboolean GL_LoadLibrary(enum gl_lib_type type)\n{\n  const char *filename;\n\n  gl_type = type;\n\n  if(type == GL_LIB_FIXED)\n    filename = \"libGLESv1_CM.so\";\n  else\n    filename = \"libGLESv2.so\";\n\n  glso = dlopen(filename, RTLD_NOW);\n  return glso != NULL;\n}\n\n// EGL is not directly comparable to SDL as it does not handle \"windows\"\n// in the traditional sense. It is expected that the platform provides a\n// native \"window\" type that handles things like resolution and resizing.\n\nstatic EGLint config16[] =\n{\n  EGL_RED_SIZE,        5,\n  EGL_GREEN_SIZE,      6,\n  EGL_BLUE_SIZE,       5,\n  EGL_ALPHA_SIZE,      0,\n  EGL_DEPTH_SIZE,      0,\n  EGL_RENDERABLE_TYPE, 0, /* Placeholder */\n  EGL_NONE,\n};\n\nstatic EGLint config32[] =\n{\n  EGL_RED_SIZE,        8,\n  EGL_GREEN_SIZE,      8,\n  EGL_BLUE_SIZE,       8,\n  EGL_ALPHA_SIZE,      8,\n  EGL_DEPTH_SIZE,      0,\n  EGL_RENDERABLE_TYPE, 0, /* Placeholder */\n  EGL_NONE,\n};\n\nstatic void update_config_attribs(EGLint *config)\n{\n  int i;\n\n  for(i = 0; config[i] != EGL_NONE; i++)\n  {\n    if(config[i] != EGL_RENDERABLE_TYPE)\n      continue;\n\n    if(gl_type == GL_LIB_FIXED)\n      config[i + 1] = EGL_OPENGL_ES_BIT;\n    else\n      config[i + 1] = EGL_OPENGL_ES2_BIT;\n\n    break;\n  }\n}\n\nstatic const EGLint *get_current_config(int depth)\n{\n  if(depth == 32)\n  {\n    debug(\"Selected RGBA 8888 EGLConfig\\n\");\n    update_config_attribs(config32);\n    return config32;\n  }\n\n  debug(\"Selected RGB 565 EGLConfig\\n\");\n  update_config_attribs(config16);\n  return config16;\n}\n\nstatic const EGLint gles_v1_attribs[] =\n{\n  EGL_CONTEXT_CLIENT_VERSION, 1, EGL_NONE\n};\n\nstatic const EGLint gles_v2_attribs[] =\n{\n  EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE\n};\n\nboolean gl_create_window(struct graphics_data *graphics,\n struct video_window *window, struct gl_version req_ver)\n{\n  struct egl_render_data *egl_render_data = graphics->render_data;\n  const EGLint *attribs = gles_v1_attribs;\n  EGLNativeWindowType egl_window = 0;\n  EGLint num_configs;\n  EGLint w, h;\n\n  assert(egl_render_data != NULL);\n\n#ifdef CONFIG_X11\n  egl_render_data->native_display = XOpenDisplay(NULL);\n  if(egl_render_data->native_display == NULL)\n  {\n    warn(\"XOpenDisplay failed\\n\");\n    return false;\n  }\n\n  egl_window = XCreateSimpleWindow(egl_render_data->native_display,\n                               RootWindow(egl_render_data->native_display, 0),\n                               0, 0, 640, 350, 0,\n                               BlackPixel(egl_render_data->native_display, 0),\n                               BlackPixel(egl_render_data->native_display, 0));\n  XMapWindow(egl_render_data->native_display, egl_window);\n  XFlush(egl_render_data->native_display);\n#endif\n\n  egl_render_data->display = eglGetDisplay(egl_render_data->native_display);\n  if(egl_render_data->display == EGL_NO_DISPLAY)\n  {\n    warn(\"eglGetDisplay failed\\n\");\n    return false;\n  }\n\n  if(!eglInitialize(egl_render_data->display, NULL, NULL))\n  {\n    warn(\"eglInitialize failed\\n\");\n    return false;\n  }\n\n  if(!eglChooseConfig(egl_render_data->display,\n                      get_current_config(window->bits_per_pixel),\n                      &egl_render_data->config, 1, &num_configs))\n  {\n    warn(\"eglChooseConfig failed\\n\");\n    return false;\n  }\n\n  egl_render_data->surface = eglCreateWindowSurface(egl_render_data->display,\n                                                    egl_render_data->config,\n                                                    egl_window, NULL);\n  if(egl_render_data->surface == EGL_NO_SURFACE)\n  {\n    warn(\"eglCreateWindowSurface failed\\n\");\n    goto err_cleanup;\n  }\n\n  if(!eglBindAPI(EGL_OPENGL_ES_API))\n  {\n    warn(\"eglBindAPI failed\\n\");\n    goto err_cleanup;\n  }\n\n  if(gl_type == GL_LIB_PROGRAMMABLE)\n    attribs = gles_v2_attribs;\n\n  egl_render_data->context = eglCreateContext(egl_render_data->display,\n                                              egl_render_data->config,\n                                              EGL_NO_CONTEXT, attribs);\n  if(egl_render_data->context == EGL_NO_CONTEXT)\n  {\n    warn(\"eglCreateContext failed\\n\");\n    goto err_cleanup;\n  }\n\n  if(!eglQuerySurface(egl_render_data->display, egl_render_data->surface,\n   EGL_WIDTH, &w))\n  {\n    warn(\"eglQuerySurface (width) failed\\n\");\n    goto err_cleanup;\n  }\n\n  if(!eglQuerySurface(egl_render_data->display, egl_render_data->surface,\n   EGL_HEIGHT, &h))\n  {\n    warn(\"eglQuerySurface (height) failed\\n\");\n    goto err_cleanup;\n  }\n  window->width_px = w;\n  window->height_px = h;\n\n  if(!eglMakeCurrent(egl_render_data->display, egl_render_data->surface,\n                     egl_render_data->surface, egl_render_data->context))\n  {\n    warn(\"eglMakeCurrent failed\\n\");\n    goto err_cleanup;\n  }\n\n  // Can now issue GL ES commands\n  return true;\n\nerr_cleanup:\n  gl_cleanup(graphics);\n  return false;\n}\n\nboolean gl_resize_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct gl_version dummy = { 0, 0 }; // TODO: fix this\n  gl_cleanup(graphics);\n  return gl_create_window(graphics, window, dummy);\n}\n\nboolean gl_set_window_caption(struct graphics_data *graphics,\n struct video_window *window, const char *caption)\n{\n  // TODO: implement\n  return false;\n}\n\nboolean gl_set_window_icon(struct graphics_data *graphics,\n struct video_window *window, const char *icon_path)\n{\n  // TODO: implement\n  return false;\n}\n\nvoid gl_set_attributes(struct graphics_data *graphics)\n{\n  // Note that this function is called twice- both before and after\n  // gl_set_video_mode\n  struct egl_render_data *egl_render_data = graphics->render_data;\n\n  assert(egl_render_data != NULL);\n\n  // EGL is not concerned with double buffering -- per-platform hacks should\n  // go here if double buffering isn't the default or can be toggled.\n\n  if(graphics->gl_vsync == 0)\n    eglSwapInterval(egl_render_data->display, 0);\n  else if(graphics->gl_vsync >= 1)\n    eglSwapInterval(egl_render_data->display, 1);\n}\n\nboolean gl_swap_buffers(struct graphics_data *graphics)\n{\n  struct egl_render_data *egl_render_data = graphics->render_data;\n\n  assert(egl_render_data != NULL);\n\n  return eglSwapBuffers(egl_render_data->display, egl_render_data->surface);\n}\n\nvoid gl_cleanup(struct graphics_data *graphics)\n{\n  struct egl_render_data *egl_render_data = graphics->render_data;\n\n  assert(egl_render_data != NULL);\n\n  if(egl_render_data->display != EGL_NO_DISPLAY)\n  {\n    eglMakeCurrent(egl_render_data->display, EGL_NO_SURFACE, EGL_NO_SURFACE,\n     EGL_NO_CONTEXT);\n\n    if(egl_render_data->context != EGL_NO_CONTEXT)\n    {\n      eglDestroyContext(egl_render_data->display, egl_render_data->context);\n      egl_render_data->context = EGL_NO_CONTEXT;\n    }\n\n    if(egl_render_data->surface != EGL_NO_SURFACE)\n    {\n      eglDestroySurface(egl_render_data->display, egl_render_data->surface);\n      egl_render_data->surface = EGL_NO_SURFACE;\n    }\n\n    eglTerminate(egl_render_data->display);\n    egl_render_data->display = EGL_NO_DISPLAY;\n  }\n\n#ifdef CONFIG_X11\n  if(egl_render_data->native_display)\n  {\n    XCloseDisplay(egl_render_data->native_display);\n    egl_render_data->native_display = NULL;\n  }\n#endif\n}\n"
  },
  {
    "path": "src/render_egl.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __RENDER_EGL_H\n#define __RENDER_EGL_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"render_gl.h\"\n\n#include <EGL/egl.h>\n\nboolean gl_create_window(struct graphics_data *graphics,\n struct video_window *window, struct gl_version req_ver);\nboolean gl_resize_window(struct graphics_data *graphics,\n struct video_window *window);\nboolean gl_set_window_caption(struct graphics_data *graphics,\n struct video_window *window, const char *caption);\nboolean gl_set_window_icon(struct graphics_data *graphics,\n struct video_window *window, const char *icon_path);\nvoid gl_set_attributes(struct graphics_data *graphics);\nboolean gl_swap_buffers(struct graphics_data *graphics);\nvoid gl_cleanup(struct graphics_data *graphics);\n\nboolean GL_LoadLibrary(enum gl_lib_type type);\n\n#ifndef ANDROID\n\nstatic inline dso_fn_ptr GL_GetProcAddress(const char *proc)\n{\n  return eglGetProcAddress(proc);\n}\n\n#else /* ANDROID */\n\n/* Android's eglGetProcAddress is currently broken, so we\n * have to roll our own..\n */\ndso_fn_ptr GL_GetProcAddress(const char *proc);\n\n#endif /* !ANDROID */\n\nstruct egl_render_data\n{\n  EGLNativeDisplayType native_display;\n  EGLDisplay display;\n  EGLConfig config;\n  EGLContext context;\n  EGLSurface surface;\n};\n\n__M_END_DECLS\n\n#endif // __RENDER_EGL_H\n"
  },
  {
    "path": "src/render_gl.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007,2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"platform.h\"\n#include \"render.h\"\n#include \"util.h\"\n\n#ifdef CONFIG_SDL\n#include \"render_sdl.h\"\n#endif\n\n#ifdef CONFIG_EGL\n#include <GLES/gl.h>\n#include \"render_egl.h\"\n#endif\n\n#include \"render_gl.h\"\n\nconst float vertex_array_single[2 * 4] = {\n  -1.0f,  1.0f,\n  -1.0f, -1.0f,\n   1.0f,  1.0f,\n   1.0f, -1.0f,\n};\n\n#ifdef DEBUG\n\nvoid gl_error(const char *file, int line,\n              GLenum (GL_APIENTRY *glGetError)(void))\n{\n  const char *error;\n\n  switch(glGetError())\n  {\n    case GL_NO_ERROR:\n      return;\n    case GL_INVALID_ENUM:\n      error = \"Invalid enum\";\n      break;\n    case GL_INVALID_VALUE:\n      error = \"Invalid value\";\n      break;\n    case GL_INVALID_OPERATION:\n      error = \"Invalid operation\";\n      break;\n#if !defined(CONFIG_GLES) || defined(CONFIG_RENDER_GL_FIXED)\n    // These aren't defined by OpenGL ES 2.\n    case GL_STACK_OVERFLOW:\n      error = \"Stack overflow\";\n      break;\n    case GL_STACK_UNDERFLOW:\n      error = \"Stack underflow\";\n      break;\n#endif\n    case GL_OUT_OF_MEMORY:\n      error = \"Out of memory\";\n      break;\n    default:\n      error = \"Unknown error\";\n      break;\n  }\n\n  warn(\"%s:%d: GL Error: %s\\n\", file, line, error);\n}\n\n#endif // DEBUG\n\nboolean gl_load_syms(const struct dso_syms_map *map)\n{\n  int i = 0;\n\n  for(i = 0; map[i].name != NULL; i++)\n  {\n    dso_fn_ptr *sym_ptr = map[i].sym_ptr.value;\n\n    *sym_ptr = GL_GetProcAddress(map[i].name);\n    if(!*sym_ptr)\n    {\n      warn(\"Failed to load GL function '%s', aborting..\\n\", map[i].name);\n      return false;\n    }\n  }\n\n  return true;\n}\n\nvoid gl_set_filter_method(enum gl_filter_type method,\n void (GL_APIENTRY *glTexParameterf_p)(GLenum target, GLenum pname,\n  GLfloat param))\n{\n  GLfloat gl_filter_method = GL_LINEAR;\n\n  if(method == CONFIG_GL_FILTER_NEAREST)\n    gl_filter_method = GL_NEAREST;\n\n  glTexParameterf_p(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_method);\n  glTexParameterf_p(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_method);\n  glTexParameterf_p(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);\n  glTexParameterf_p(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);\n}\n"
  },
  {
    "path": "src/render_gl.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007,2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __RENDER_GL_H\n#define __RENDER_GL_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"configure.h\"\n#include \"graphics.h\"\n#include \"platform.h\"\n#include \"util.h\"\n\n#ifdef CONFIG_SDL\n# include \"SDLmzx.h\"\n# ifdef CONFIG_GLES\n#   ifdef CONFIG_RENDER_GL_FIXED\n#     if SDL_VERSION_ATLEAST(3,0,0)\n#       include <SDL3/SDL_opengles.h>\n#     else\n#       include <SDL_opengles.h>\n#     endif\n#   endif\n#   ifdef CONFIG_RENDER_GL_PROGRAM\n#     if SDL_VERSION_ATLEAST(3,0,0)\n#       include <SDL3/SDL_opengles2.h>\n#     else\n#       include <SDL_opengles2.h>\n#     endif\n#   endif\n# else\n#   if SDL_VERSION_ATLEAST(3,0,0)\n#     include <SDL3/SDL_opengl.h>\n#   else\n#     include <SDL_opengl.h>\n#   endif\n# endif\n#endif\n\n#ifndef GLAPIENTRY\n#define GLAPIENTRY APIENTRY\n#endif\n\n#ifndef GL_APIENTRY\n#define GL_APIENTRY GLAPIENTRY\n#endif\n\n// Next power of 2 over SCREEN_PIX_W\n#define GL_POWER_2_WIDTH          1024\n// Next power of 2 over SCREEN_PIX_H\n#define GL_POWER_2_HEIGHT         512\n\nextern const float vertex_array_single[2 * 4];\n\n#ifdef DEBUG\nvoid gl_error(const char *file, int line,\n              GLenum (GL_APIENTRY *glGetError)(void));\n#else\nstatic inline void gl_error(const char *file, int line,\n                            GLenum (GL_APIENTRY *glGetError)(void)) { }\n#endif\n\nboolean gl_load_syms(const struct dso_syms_map *map);\nvoid gl_set_filter_method(enum gl_filter_type method,\n void (GL_APIENTRY *glTexParameterf_p)(GLenum target, GLenum pname,\n  GLfloat param));\n\n// Used to request an OpenGL API version with gl_set_video_mode.\n// Currently this is only used to configure SDL on platforms that require\n// OpenGL ES, as OpenGL ES 1 and OpenGL ES 2 are not compatible.\nstruct gl_version\n{\n  int major;\n  int minor;\n};\n\nenum gl_lib_type\n{\n  GL_LIB_FIXED,\n  GL_LIB_PROGRAMMABLE,\n};\n\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n/**\n * The GL renderers use GL_UNSIGNED_BYTE for 32-bit texture buffers for GLES 2\n * compatibility. This leads to some problems when packing bytes on big endian\n * machines because each of these packings assumes GL_UNSIGNED_INT_8_8_8_8_REV\n * (which isn't in GLES 2).\n *\n * TODO: bswap32 here if compatibility defines for it are added.\n */\nstatic inline uint32_t gl_pack_u32(uint32_t x)\n{\n  return\n   ((x & 0xFF000000) >> 24) |\n   ((x & 0x00FF0000) >> 8) |\n   ((x & 0x0000FF00) << 8) |\n   ((x & 0x000000FF) << 24);\n}\n#else\n/**\n * For little endian, GL_UNSIGNED_BYTE and GL_UNSIGNED_INT_8_8_8_8_REV are\n * equivalent, so don't change anything.\n */\n#define gl_pack_u32(x) (x)\n#endif\n\n__M_END_DECLS\n\n#endif // __RENDER_GL_H\n"
  },
  {
    "path": "src/render_gl1.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2006-2007 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007,2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"compat.h\"\n#include \"platform.h\"\n#include \"render.h\"\n#include \"render_layer.h\"\n#include \"renderers.h\"\n#include \"util.h\"\n\n#ifdef CONFIG_SDL\n#include \"render_sdl.h\"\n#endif\n\n#ifdef CONFIG_EGL\n#include <GLES/gl.h>\n#include \"render_egl.h\"\n#endif\n\n#include \"render_gl.h\"\n\n// NOTE: This renderer should work with almost any GL capable video card.\n//       However, if you plan to change it, please bear in mind that this\n//       has been carefully written to work with both OpenGL 1.x and\n//       OpenGL ES 1.x. The latter API lacks many functions present in\n//       desktop OpenGL. GL ES is typically used on cellphones.\n\nstatic const struct gl_version gl_required_version = { 1, 1 };\n\nstatic struct\n{\n  void (GL_APIENTRY *glBindTexture)(GLenum target, GLuint texture);\n  void (GL_APIENTRY *glClear)(GLbitfield mask);\n  void (GL_APIENTRY *glClearColor)(GLclampf red, GLclampf green,\n   GLclampf blue, GLclampf alpha);\n  void (GL_APIENTRY *glDeleteTextures)(GLsizei n, GLuint *textures);\n  void (GL_APIENTRY *glDisableClientState)(GLenum cap);\n  void (GL_APIENTRY *glDrawArrays)(GLenum mode, GLint first, GLsizei count);\n  void (GL_APIENTRY *glEnable)(GLenum cap);\n  void (GL_APIENTRY *glEnableClientState)(GLenum cap);\n  void (GL_APIENTRY *glGenTextures)(GLsizei n, GLuint *textures);\n  GLenum (GL_APIENTRY *glGetError)(void);\n  const GLubyte* (GL_APIENTRY *glGetString)(GLenum name);\n  void (GL_APIENTRY *glTexCoordPointer)(GLint size, GLenum type,\n   GLsizei stride, const GLvoid *ptr);\n  void (GL_APIENTRY *glTexImage2D)(GLenum target, GLint level,\n   GLint internalformat,GLsizei width, GLsizei height, GLint border,\n   GLenum format, GLenum type, const GLvoid *pixels);\n  void (GL_APIENTRY *glTexParameterf)(GLenum target, GLenum pname,\n   GLfloat param);\n  void (GL_APIENTRY *glTexSubImage2D)(GLenum target, GLint level,\n   GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,\n   GLenum format, GLenum type, const GLvoid *pixels);\n  void (GL_APIENTRY *glVertexPointer)(GLint size, GLenum type,\n   GLsizei stride, const GLvoid *ptr);\n  void (GL_APIENTRY *glViewport)(GLint x, GLint y, GLsizei width,\n   GLsizei height);\n}\ngl1;\n\nstatic const struct dso_syms_map gl1_syms_map[] =\n{\n  { \"glBindTexture\",        { &gl1.glBindTexture }},\n  { \"glClear\",              { &gl1.glClear }},\n  { \"glClearColor\",         { &gl1.glClearColor }},\n  { \"glDeleteTextures\",     { &gl1.glDeleteTextures }},\n  { \"glDisableClientState\", { &gl1.glDisableClientState }},\n  { \"glDrawArrays\",         { &gl1.glDrawArrays }},\n  { \"glEnable\",             { &gl1.glEnable }},\n  { \"glEnableClientState\",  { &gl1.glEnableClientState }},\n  { \"glGenTextures\",        { &gl1.glGenTextures }},\n  { \"glGetError\",           { &gl1.glGetError }},\n  { \"glGetString\",          { &gl1.glGetString }},\n  { \"glTexCoordPointer\",    { &gl1.glTexCoordPointer }},\n  { \"glTexImage2D\",         { &gl1.glTexImage2D }},\n  { \"glTexParameterf\",      { &gl1.glTexParameterf }},\n  { \"glTexSubImage2D\",      { &gl1.glTexSubImage2D }},\n  { \"glVertexPointer\",      { &gl1.glVertexPointer }},\n  { \"glViewport\",           { &gl1.glViewport }},\n  DSO_MAP_END\n};\n\n#define gl_check_error() gl_error(__FILE__, __LINE__, gl1.glGetError)\n\nstruct gl1_render_data\n{\n#ifdef CONFIG_EGL\n  struct egl_render_data egl;\n#endif\n#ifdef CONFIG_SDL\n  struct sdl_render_data sdl;\n#endif\n  uint32_t *pixels;\n  GLuint texture_number;\n};\n\nstatic boolean gl1_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  struct gl1_render_data *render_data =\n   (struct gl1_render_data *)cmalloc(sizeof(struct gl1_render_data));\n\n  if(!render_data)\n    goto err_out;\n\n  if(!GL_LoadLibrary(GL_LIB_FIXED))\n    goto err_free_render_data;\n\n  memset(render_data, 0, sizeof(struct gl1_render_data));\n  graphics->render_data = render_data;\n\n  graphics->ratio = conf->video_ratio;\n\n  graphics->gl_vsync = conf->gl_vsync;\n  graphics->allow_resize = conf->allow_resize;\n  graphics->gl_filter_method = conf->gl_filter_method;\n  graphics->bits_per_pixel = 32;\n\n  // OpenGL only supports 16/32bit colour\n  if(conf->force_bpp == 16 || conf->force_bpp == 32)\n    graphics->bits_per_pixel = conf->force_bpp;\n\n  return true;\n\nerr_free_render_data:\n  free(render_data);\n  graphics->render_data = NULL;\nerr_out:\n  return false;\n}\n\nstatic boolean gl1_resize_callback(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct gl1_render_data *render_data = graphics->render_data;\n\n  // Initial clear color <0,0,0,0> may be interpreted as transparent (Wayland).\n  gl1.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n  gl_check_error();\n  // Clear entire window to prevent background leak-through.\n  gl1.glViewport(0, 0, window->width_px, window->height_px);\n  gl_check_error();\n  gl1.glClear(GL_COLOR_BUFFER_BIT);\n  gl_check_error();\n\n  gl1.glViewport(window->viewport_x, window->viewport_y,\n   window->viewport_width, window->viewport_height);\n  gl_check_error();\n\n  gl1.glEnableClientState(GL_TEXTURE_COORD_ARRAY);\n  gl1.glEnableClientState(GL_VERTEX_ARRAY);\n\n  gl1.glEnable(GL_TEXTURE_2D);\n\n  // Free any preexisting textures if they exist\n  gl1.glDeleteTextures(1, &render_data->texture_number);\n  gl_check_error();\n\n  gl1.glGenTextures(1, &render_data->texture_number);\n  gl_check_error();\n\n  gl1.glBindTexture(GL_TEXTURE_2D, render_data->texture_number);\n  gl_check_error();\n\n  gl1.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, GL_POWER_2_WIDTH,\n   GL_POWER_2_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);\n\n  gl_set_filter_method(graphics->gl_filter_method, gl1.glTexParameterf);\n  return true;\n}\n\nstatic boolean gl1_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct gl1_render_data *render_data = graphics->render_data;\n\n  gl_set_attributes(graphics);\n\n  if(!gl_create_window(graphics, window, gl_required_version))\n    return false;\n\n  gl_set_attributes(graphics);\n\n  if(!gl_load_syms(gl1_syms_map))\n  {\n    gl_cleanup(graphics);\n    return false;\n  }\n\n  // We need a specific version of OpenGL; desktop GL must be 1.1.\n  // All OpenGL ES 1.x implementations are supported, so don't do\n  // the check with these configurations.\n#ifndef CONFIG_GLES\n  {\n    static boolean initialized = false;\n\n    if(!initialized)\n    {\n      const char *version;\n      double version_float;\n\n      version = (const char *)gl1.glGetString(GL_VERSION);\n      if(!version)\n      {\n        warn(\"Could not load GL version string.\\n\");\n        gl_cleanup(graphics);\n        return false;\n      }\n\n      version_float = atof(version);\n      if(version_float < 1.1)\n      {\n        warn(\"Need >= OpenGL 1.1, got OpenGL %.1f.\\n\", version_float);\n        gl_cleanup(graphics);\n        return false;\n      }\n\n      initialized = true;\n    }\n  }\n#endif\n\n  render_data->pixels = (uint32_t *)cmalloc(sizeof(uint32_t) * SCREEN_PIX_W * SCREEN_PIX_H);\n  if(!render_data->pixels)\n  {\n    gl_cleanup(graphics);\n    return false;\n  }\n\n  return gl1_resize_callback(graphics, window);\n}\n\nstatic void gl1_free_video(struct graphics_data *graphics)\n{\n  struct gl1_render_data *render_data = graphics->render_data;\n\n  if(render_data)\n  {\n    if(gl1.glDeleteTextures)\n    {\n      gl1.glDeleteTextures(1, &render_data->texture_number);\n      gl_check_error();\n    }\n\n    gl_cleanup(graphics);\n    free(render_data);\n    graphics->render_data = NULL;\n  }\n}\n\nstatic void gl1_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned int count)\n{\n  unsigned int i;\n  for(i = 0; i < count; i++)\n  {\n    graphics->flat_intensity_palette[i] = gl_pack_u32((0xff << 24) |\n     (palette[i].b << 16) | (palette[i].g << 8) | palette[i].r);\n  }\n}\n\nstatic void gl1_render_graph(struct graphics_data *graphics)\n{\n  struct gl1_render_data *render_data = graphics->render_data;\n  unsigned int mode = graphics->screen_mode;\n  unsigned pitch = SCREEN_PIX_W * sizeof(uint32_t);\n\n  if(!mode)\n    render_graph32(render_data->pixels, pitch, graphics);\n  else\n    render_graph32s(render_data->pixels, pitch, graphics);\n}\n\nstatic void gl1_render_layer(struct graphics_data *graphics,\n struct video_layer *layer)\n{\n  struct gl1_render_data *render_data = graphics->render_data;\n\n  render_layer(render_data->pixels, SCREEN_PIX_W, SCREEN_PIX_H,\n   SCREEN_PIX_W * sizeof(uint32_t), 32, graphics, layer);\n}\n\nstatic void gl1_render_cursor(struct graphics_data *graphics, unsigned int x,\n unsigned int y, uint16_t color, unsigned int lines, unsigned int offset)\n{\n  struct gl1_render_data *render_data = graphics->render_data;\n\n  render_cursor(render_data->pixels, SCREEN_PIX_W * sizeof(uint32_t), 32, x, y,\n   graphics->flat_intensity_palette[color], lines, offset);\n}\n\nstatic void gl1_render_mouse(struct graphics_data *graphics,\n unsigned int x, unsigned int y, unsigned int w, unsigned int h)\n{\n  struct gl1_render_data *render_data = graphics->render_data;\n\n  render_mouse(render_data->pixels, SCREEN_PIX_W * sizeof(uint32_t), 32, x, y, 0xFFFFFFFF,\n   0x0, w, h);\n}\n\nstatic void gl1_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct gl1_render_data *render_data = graphics->render_data;\n\n  const float texture_width = 1.0f * SCREEN_PIX_W / GL_POWER_2_WIDTH;\n  const float texture_height = 1.0f * SCREEN_PIX_H / GL_POWER_2_HEIGHT;\n\n  const float tex_coord_array[2 * 4] = {\n    0.0f,          0.0f,\n    0.0f,          texture_height,\n    texture_width, 0.0f,\n    texture_width, texture_height\n  };\n\n  gl1.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, SCREEN_PIX_W, SCREEN_PIX_H,\n   GL_RGBA, GL_UNSIGNED_BYTE, render_data->pixels);\n  gl_check_error();\n\n  gl1.glClear(GL_COLOR_BUFFER_BIT);\n  gl_check_error();\n\n  gl1.glTexCoordPointer(2, GL_FLOAT, 0, tex_coord_array);\n  gl_check_error();\n\n  gl1.glVertexPointer(2, GL_FLOAT, 0, vertex_array_single);\n  gl_check_error();\n\n  gl1.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n  gl_check_error();\n\n  gl_swap_buffers(graphics);\n}\n\nvoid render_gl1_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = gl1_init_video;\n  renderer->free_video = gl1_free_video;\n  renderer->create_window = gl1_create_window;\n  renderer->resize_window = gl_resize_window;\n  renderer->resize_callback = gl1_resize_callback;\n  renderer->set_viewport = set_window_viewport_scaled;\n  renderer->set_window_caption = gl_set_window_caption;\n  renderer->set_window_icon = gl_set_window_icon;\n  renderer->update_colors = gl1_update_colors;\n  renderer->render_graph = gl1_render_graph;\n  renderer->render_layer = gl1_render_layer;\n  renderer->render_cursor = gl1_render_cursor;\n  renderer->render_mouse = gl1_render_mouse;\n  renderer->sync_screen = gl1_sync_screen;\n}\n"
  },
  {
    "path": "src/render_gl2.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2007,2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"graphics.h\"\n#include \"platform.h\"\n#include \"render.h\"\n#include \"render_layer.h\"\n#include \"renderers.h\"\n#include \"util.h\"\n\n#ifdef CONFIG_SDL\n#include \"render_sdl.h\"\n#endif\n\n#ifdef CONFIG_EGL\n#include <GLES/gl.h>\n#include \"render_egl.h\"\n#endif\n\n#include \"render_gl.h\"\n\n// NOTE: This renderer should work with almost any GL capable video card.\n//       However, if you plan to change it, please bear in mind that this\n//       has been carefully written to work with both OpenGL 1.x and\n//       OpenGL ES 1.x. The latter API lacks many functions present in\n//       desktop OpenGL. GL ES is typically used on cellphones.\n\nstatic const struct gl_version gl_required_version = { 1, 1 };\n\n// We need to keep two versions of each char: the regular char, and an\n// inverted version for foreground transparency with layer rendering.\n#define CHAR_2W (CHAR_W * 2)\n\n// The char texture in this renderer will be 1024x1024\n#define CHARSET_COLS 64\n#define CHARSET_ROWS (FULL_CHARSET_SIZE / CHARSET_COLS)\n#define TEX_CHARSET_WIDTH 1024.0f\n#define TEX_CHARSET_HEIGHT 1024.0f\n#define TEX_BG_WIDTH 128\n#define TEX_BG_HEIGHT 32\n\n#define TEX_CHAR_W (((float)CHAR_W) / TEX_CHARSET_WIDTH)\n#define TEX_CHAR_H (((float)CHAR_H) / TEX_CHARSET_HEIGHT)\n\nenum\n{\n  TEX_SCREEN_ID,\n  TEX_DATA_ID,\n  TEX_BG_ID,\n  NUM_TEXTURES\n};\n\nstatic struct\n{\n  void (GL_APIENTRY *glAlphaFunc)(GLenum func, GLclampf ref);\n  void (GL_APIENTRY *glBindTexture)(GLenum target, GLuint texture);\n  void (GL_APIENTRY *glBlendFunc)(GLenum sfactor, GLenum dfactor);\n  void (GL_APIENTRY *glClear)(GLbitfield mask);\n  void (GL_APIENTRY *glClearColor)(GLclampf red, GLclampf green,\n   GLclampf blue, GLclampf alpha);\n  void (GL_APIENTRY *glColorPointer)(GLint size, GLenum type, GLsizei stride,\n   const GLvoid *pointer);\n  void (GL_APIENTRY *glCopyTexImage2D)(GLenum target, GLint level,\n   GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height,\n   GLint border);\n  void (GL_APIENTRY *glDeleteTextures)(GLsizei n, GLuint *textures);\n  void (GL_APIENTRY *glDisable)(GLenum cap);\n  void (GL_APIENTRY *glDisableClientState)(GLenum cap);\n  void (GL_APIENTRY *glDrawArrays)(GLenum mode, GLint first, GLsizei count);\n  void (GL_APIENTRY *glEnable)(GLenum cap);\n  void (GL_APIENTRY *glEnableClientState)(GLenum cap);\n  void (GL_APIENTRY *glGenTextures)(GLsizei n, GLuint *textures);\n  GLenum (GL_APIENTRY *glGetError)(void);\n  const GLubyte* (GL_APIENTRY *glGetString)(GLenum name);\n  void (GL_APIENTRY *glTexCoordPointer)(GLint size, GLenum type,\n   GLsizei stride, const GLvoid *ptr);\n  void (GL_APIENTRY *glTexImage2D)(GLenum target, GLint level,\n   GLint internalformat, GLsizei width, GLsizei height, GLint border,\n   GLenum format, GLenum type, const GLvoid *pixels);\n  void (GL_APIENTRY *glTexParameterf)(GLenum target, GLenum pname,\n   GLfloat param);\n  void (GL_APIENTRY *glTexSubImage2D)(GLenum target, GLint level,\n   GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,\n   GLenum format, GLenum type, const GLvoid *pixels);\n  void (GL_APIENTRY *glVertexPointer)(GLint size, GLenum type,\n   GLsizei stride, const GLvoid *ptr);\n  void (GL_APIENTRY *glViewport)(GLint x, GLint y, GLsizei width,\n   GLsizei height);\n}\ngl2;\n\nstatic const struct dso_syms_map gl2_syms_map[] =\n{\n  { \"glAlphaFunc\",          { &gl2.glAlphaFunc }},\n  { \"glBindTexture\",        { &gl2.glBindTexture }},\n  { \"glBlendFunc\",          { &gl2.glBlendFunc }},\n  { \"glClear\",              { &gl2.glClear }},\n  { \"glClearColor\",         { &gl2.glClearColor }},\n  { \"glColorPointer\",       { &gl2.glColorPointer }},\n  { \"glCopyTexImage2D\",     { &gl2.glCopyTexImage2D }},\n  { \"glDeleteTextures\",     { &gl2.glDeleteTextures }},\n  { \"glDisable\",            { &gl2.glDisable }},\n  { \"glDisableClientState\", { &gl2.glDisableClientState }},\n  { \"glDrawArrays\",         { &gl2.glDrawArrays }},\n  { \"glEnable\",             { &gl2.glEnable }},\n  { \"glEnableClientState\",  { &gl2.glEnableClientState }},\n  { \"glGenTextures\",        { &gl2.glGenTextures }},\n  { \"glGetError\",           { &gl2.glGetError }},\n  { \"glGetString\",          { &gl2.glGetString }},\n  { \"glTexCoordPointer\",    { &gl2.glTexCoordPointer }},\n  { \"glTexImage2D\",         { &gl2.glTexImage2D }},\n  { \"glTexParameterf\",      { &gl2.glTexParameterf }},\n  { \"glTexSubImage2D\",      { &gl2.glTexSubImage2D }},\n  { \"glVertexPointer\",      { &gl2.glVertexPointer }},\n  { \"glViewport\",           { &gl2.glViewport }},\n  DSO_MAP_END\n};\n\n#define gl_check_error() gl_error(__FILE__, __LINE__, gl2.glGetError)\n\nstruct gl2_render_data\n{\n#ifdef CONFIG_EGL\n  struct egl_render_data egl;\n#endif\n#ifdef CONFIG_SDL\n  struct sdl_render_data sdl;\n#endif\n  uint32_t *pixels;\n  uint8_t charset_texture[CHAR_2W * CHAR_H * FULL_CHARSET_SIZE];\n  uint32_t background_texture[TEX_BG_WIDTH * TEX_BG_HEIGHT];\n  GLuint textures[NUM_TEXTURES];\n  GLubyte palette[3 * FULL_PAL_SIZE];\n  boolean remap_texture;\n  boolean remap_char[FULL_CHARSET_SIZE];\n  boolean ignore_linear;\n  GLubyte color_array[TEX_BG_WIDTH * TEX_BG_HEIGHT * 4 * 4];\n  float tex_coord_array[TEX_BG_WIDTH * TEX_BG_HEIGHT * 8];\n  float vertex_array[TEX_BG_WIDTH * TEX_BG_HEIGHT * 8];\n  boolean viewport_shrunk;\n};\n\nstatic const GLubyte color_array_white[4 * 4] =\n{\n  255, 255, 255, 255,\n  255, 255, 255, 255,\n  255, 255, 255, 255,\n  255, 255, 255, 255\n};\n\nstatic boolean gl2_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  struct gl2_render_data *render_data =\n   (struct gl2_render_data *)cmalloc(sizeof(struct gl2_render_data));\n\n  if(!render_data)\n    return false;\n\n  if(!GL_LoadLibrary(GL_LIB_FIXED))\n    goto err_free_render_data;\n\n  memset(render_data, 0, sizeof(struct gl2_render_data));\n  graphics->render_data = render_data;\n\n  graphics->gl_vsync = conf->gl_vsync;\n  graphics->allow_resize = conf->allow_resize;\n  graphics->gl_filter_method = conf->gl_filter_method;\n  graphics->ratio = conf->video_ratio;\n  graphics->bits_per_pixel = 32;\n\n  // OpenGL only supports 16/32bit colour\n  if(conf->force_bpp == 16 || conf->force_bpp == 32)\n    graphics->bits_per_pixel = conf->force_bpp;\n\n  // We want to deal internally with 32bit surfaces\n  render_data->pixels = (uint32_t *)cmalloc(sizeof(uint32_t) * GL_POWER_2_WIDTH *\n   GL_POWER_2_HEIGHT);\n\n  if(!render_data->pixels)\n    goto err_free_render_data;\n\n  return true;\n\nerr_free_render_data:\n  free(render_data);\n  graphics->render_data = NULL;\n  return false;\n}\n\nstatic void gl2_free_video(struct graphics_data *graphics)\n{\n  struct gl2_render_data *render_data = graphics->render_data;\n\n  if(render_data)\n  {\n    if(gl2.glDeleteTextures)\n    {\n      gl2.glDeleteTextures(NUM_TEXTURES, render_data->textures);\n      gl_check_error();\n    }\n\n    gl_cleanup(graphics);\n    free(render_data->pixels);\n    free(render_data);\n    graphics->render_data = NULL;\n  }\n}\n\nstatic void gl2_remap_char_range(struct graphics_data *graphics, uint16_t first,\n uint16_t count)\n{\n  struct gl2_render_data *render_data = graphics->render_data;\n\n  if(first + count > FULL_CHARSET_SIZE)\n    count = FULL_CHARSET_SIZE - first;\n\n  // FIXME arbitrary\n  if(count <= 256)\n    memset(render_data->remap_char + first, 1, count);\n\n  else\n    render_data->remap_texture = true;\n}\n\nstatic void gl2_remap_char(struct graphics_data *graphics, uint16_t chr)\n{\n  struct gl2_render_data *render_data = graphics->render_data;\n  render_data->remap_char[chr] = true;\n}\n\nstatic void gl2_remap_charbyte(struct graphics_data *graphics,\n uint16_t chr, uint8_t byte)\n{\n  gl2_remap_char(graphics, chr);\n}\n\nstatic void gl2_set_shrunk_viewport(const struct video_window *window)\n{\n  gl2.glViewport((window->width_px - SCREEN_PIX_W) >> 1,\n   (window->height_px - SCREEN_PIX_H) >> 1, SCREEN_PIX_W, SCREEN_PIX_H);\n  gl_check_error();\n}\n\nstatic void gl2_set_real_viewport(const struct video_window *window)\n{\n  gl2.glViewport(window->viewport_x, window->viewport_y,\n   window->viewport_width, window->viewport_height);\n  gl_check_error();\n}\n\nstatic boolean gl2_resize_callback(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct gl2_render_data *render_data = graphics->render_data;\n  int charset_width, charset_height;\n\n  // Initial clear color <0,0,0,0> may be interpreted as transparent (Wayland).\n  gl2.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n  gl_check_error();\n  // Clear entire window to prevent background leak-through.\n  gl2.glViewport(0, 0, window->width_px, window->height_px);\n  gl_check_error();\n  gl2.glClear(GL_COLOR_BUFFER_BIT);\n  gl_check_error();\n\n  // If the window is exactly 640x350, then any filtering is useless.\n  // Turning filtering off here might speed things up.\n  //\n  // Otherwise, linear filtering breaks if the window is smaller than\n  // 640x350, so also turn it off here.\n  if(window->width_px < SCREEN_PIX_W || window->height_px < SCREEN_PIX_H ||\n   window->is_integer_scaled || graphics->gl_filter_method != CONFIG_GL_FILTER_LINEAR)\n  {\n    render_data->viewport_shrunk = false;\n  }\n  else\n  {\n    render_data->viewport_shrunk = true;\n  }\n  // Use the real viewport at all times that don't involve drawing the frame.\n  gl2_set_real_viewport(window);\n\n  // Free any preexisting textures if they exist\n  gl2.glDeleteTextures(NUM_TEXTURES, render_data->textures);\n  gl_check_error();\n\n  gl2.glGenTextures(NUM_TEXTURES, render_data->textures);\n  gl_check_error();\n\n  gl2.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_SCREEN_ID]);\n  gl_check_error();\n\n  gl_set_filter_method(graphics->gl_filter_method, gl2.glTexParameterf);\n\n  gl2.glEnableClientState(GL_TEXTURE_COORD_ARRAY);\n  gl2.glEnableClientState(GL_VERTEX_ARRAY);\n  gl2.glEnableClientState(GL_COLOR_ARRAY);\n  gl2.glEnable(GL_TEXTURE_2D);\n\n  gl2.glAlphaFunc(GL_GREATER, 0.565f);\n  gl_check_error();\n\n  gl2.glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);\n  gl_check_error();\n\n  // Fill the entire screen texture transparent to avoid edge leak-through.\n  memset(render_data->pixels, 0,\n   sizeof(uint32_t) * GL_POWER_2_WIDTH * GL_POWER_2_HEIGHT);\n\n  gl2.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, GL_POWER_2_WIDTH,\n   GL_POWER_2_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE,\n   render_data->pixels);\n  gl_check_error();\n\n  gl2.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_DATA_ID]);\n  gl_check_error();\n\n  gl_set_filter_method(CONFIG_GL_FILTER_NEAREST, gl2.glTexParameterf);\n  charset_width = round_to_power_of_two(CHARSET_COLS * CHAR_2W);\n  charset_height = round_to_power_of_two((CHARSET_ROWS + 1) * CHAR_H);\n\n#ifdef DEBUG\n  if(charset_width != (int)TEX_CHARSET_WIDTH ||\n   charset_height != (int)TEX_CHARSET_HEIGHT)\n    warn(\"texture size mismatch: %d %d\\n\", charset_width, charset_height);\n#endif\n\n  gl2.glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, charset_width, charset_height,\n   0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL);\n  gl_check_error();\n\n  gl2_remap_char_range(graphics, 0, FULL_CHARSET_SIZE);\n\n  gl2.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_BG_ID]);\n  gl_check_error();\n\n  gl_set_filter_method(CONFIG_GL_FILTER_NEAREST, gl2.glTexParameterf);\n\n  gl2.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEX_BG_WIDTH, TEX_BG_HEIGHT,\n   0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);\n  gl_check_error();\n  return true;\n}\n\nstatic boolean gl2_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  gl_set_attributes(graphics);\n\n  if(!gl_create_window(graphics, window, gl_required_version))\n    return false;\n\n  gl_set_attributes(graphics);\n\n  if(!gl_load_syms(gl2_syms_map))\n  {\n    gl_cleanup(graphics);\n    return false;\n  }\n\n  // We need a specific version of OpenGL; desktop GL must be 1.1.\n  // All OpenGL ES 1.x implementations are supported, so don't do\n  // the check with these configurations.\n#ifndef CONFIG_GLES\n  {\n    static boolean initialized = false;\n\n    if(!initialized)\n    {\n      const char *version;\n      double version_float;\n\n      version = (const char *)gl2.glGetString(GL_VERSION);\n      if(!version)\n      {\n        warn(\"Could not load GL version string.\\n\");\n        gl_cleanup(graphics);\n        return false;\n      }\n\n      version_float = atof(version);\n      if(version_float < 1.1)\n      {\n        warn(\"Need >= OpenGL 1.1, got OpenGL %.1f.\\n\", version_float);\n        gl_cleanup(graphics);\n        return false;\n      }\n\n      initialized = true;\n    }\n  }\n#endif\n\n  return gl2_resize_callback(graphics, window);\n}\n\nstatic void gl2_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned int count)\n{\n  struct gl2_render_data *render_data = graphics->render_data;\n  unsigned int i;\n  for(i = 0; i < count; i++)\n  {\n    graphics->flat_intensity_palette[i] = gl_pack_u32((0xFF << 24) |\n     (palette[i].b << 16) | (palette[i].g << 8) | palette[i].r);\n    render_data->palette[i*3  ] = (GLubyte)palette[i].r;\n    render_data->palette[i*3+1] = (GLubyte)palette[i].g;\n    render_data->palette[i*3+2] = (GLubyte)palette[i].b;\n  }\n}\n\nstatic char *gl2_char_bitmask_to_texture(signed char *c, char *p)\n{\n  // This tests the 7th bit, if 0, result is 0.\n  // If 1, result is 255 (because of sign extended bit shift!).\n  // Note the use of char constants to force 8bit calculation.\n  *(p++) = *c << 24 >> 31;\n  *(p++) = *c << 25 >> 31;\n  *(p++) = *c << 26 >> 31;\n  *(p++) = *c << 27 >> 31;\n  *(p++) = *c << 28 >> 31;\n  *(p++) = *c << 29 >> 31;\n  *(p++) = *c << 30 >> 31;\n  *(p++) = *c << 31 >> 31;\n  return p;\n}\n\nstatic inline void gl2_do_remap_charsets(struct graphics_data *graphics)\n{\n  struct gl2_render_data *render_data = graphics->render_data;\n  signed char *c = (signed char *)graphics->charset;\n  char *p = (char *)render_data->charset_texture;\n  unsigned int i, j, k;\n  signed char inv;\n\n  for(i = 0; i < CHARSET_ROWS; i++, c += -CHAR_H + CHARSET_COLS * CHAR_H)\n  {\n    for(j = 0; j < CHAR_H; j++, c += -CHARSET_COLS * CHAR_H + 1)\n    {\n      for(k = 0; k < CHARSET_COLS; k++, c += CHAR_H)\n      {\n        inv = ~(*c);\n        p = gl2_char_bitmask_to_texture(c, p);\n        p = gl2_char_bitmask_to_texture(&inv, p);\n      }\n    }\n  }\n\n  gl2.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,\n   CHARSET_COLS * CHAR_2W,\n   CHARSET_ROWS * CHAR_H,\n   GL_ALPHA, GL_UNSIGNED_BYTE, render_data->charset_texture);\n  gl_check_error();\n}\n\nstatic inline void gl2_do_remap_char(struct graphics_data *graphics,\n uint16_t chr)\n{\n  struct gl2_render_data *render_data = graphics->render_data;\n  signed char *c = (signed char *)graphics->charset;\n  char *p = (char *)render_data->charset_texture;\n  unsigned int i;\n  signed char inv;\n\n  if(chr < FULL_CHARSET_SIZE)\n  {\n    c += chr * 14;\n\n    for(i = 0; i < 14; i++, c++)\n    {\n      inv = ~(*c);\n      p = gl2_char_bitmask_to_texture(c, p);\n      p = gl2_char_bitmask_to_texture(&inv, p);\n    }\n  }\n  else\n  {\n    memset(render_data->charset_texture, 0, CHAR_2W * CHAR_H);\n  }\n\n  gl2.glTexSubImage2D(GL_TEXTURE_2D, 0,\n   chr % CHARSET_COLS * CHAR_2W,\n   chr / CHARSET_COLS * CHAR_H,\n   CHAR_2W,\n   CHAR_H,\n   GL_ALPHA, GL_UNSIGNED_BYTE, render_data->charset_texture);\n  gl_check_error();\n}\n\nstatic void gl2_check_remap_chars(struct graphics_data *graphics)\n{\n  struct gl2_render_data *render_data = graphics->render_data;\n  int i;\n\n  gl2.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_DATA_ID]);\n  gl_check_error();\n\n  if(render_data->remap_texture)\n  {\n    gl2_do_remap_charsets(graphics);\n\n    // Also remap the invisible char while we're at it.\n    gl2_do_remap_char(graphics, FULL_CHARSET_SIZE);\n\n    render_data->remap_texture = false;\n    memset(render_data->remap_char, false, sizeof(uint8_t) * FULL_CHARSET_SIZE);\n  }\n  else\n  {\n    for(i = 0; i < FULL_CHARSET_SIZE; i++)\n    {\n      if(render_data->remap_char[i])\n      {\n        gl2_do_remap_char(graphics, i);\n        render_data->remap_char[i] = false;\n      }\n    }\n  }\n}\n\nstatic inline int translate_layer_color(struct graphics_data *graphics,\n uint8_t color)\n{\n  if(color >= 16)\n    return (color & 0xF) + graphics->protected_pal_position;\n\n  return color;\n}\n\nstatic inline uint16_t translate_layer_char(uint16_t chr, uint16_t offset)\n{\n  if(chr == INVISIBLE_CHAR)\n    return FULL_CHARSET_SIZE;\n\n  if(chr > 0xFF)\n    return (chr & 0xFF) + PROTECTED_CHARSET_POSITION;\n\n  return (chr + offset) % PROTECTED_CHARSET_POSITION;\n}\n\nstatic void gl2_render_layer(struct graphics_data *graphics,\n struct video_layer *layer)\n{\n  struct gl2_render_data *render_data = graphics->render_data;\n  struct char_element *src = layer->data;\n  int i, i2, i3, layer_w = layer->w, layer_h = layer->h;\n  uint32_t *dest;\n  uint16_t char_value;\n  int fg_color, bg_color;\n\n  // TODO: do this elsewhere with proper access to the window struct.\n  if(render_data->viewport_shrunk)\n    gl2_set_shrunk_viewport(&(graphics->window));\n\n  if(!layer->mode)\n  {\n    float *tex_coord_array = render_data->tex_coord_array;\n    float *vertex_array    = render_data->vertex_array;\n    GLubyte *color_array   = render_data->color_array;\n    int charset_x;\n    int charset_y;\n    float x1 = layer->x;\n    float y1 = layer->y;\n    float x2 = layer->x + layer->w * CHAR_W;\n    float y2 = layer->y + layer->h * CHAR_H;\n\n    float tex_coord_array_single[2 * 4] =\n    {\n      0.0f,                           0.0f,\n      0.0f,                           1.0f * layer_h / TEX_BG_HEIGHT,\n      1.0f * layer_w / TEX_BG_WIDTH,  0.0f,\n      1.0f * layer_w / TEX_BG_WIDTH,  1.0f * layer_h / TEX_BG_HEIGHT\n    };\n\n    float vertex_array_single[2 * 4] =\n    {\n      x1 / SCREEN_PIX_W * 2.0f - 1.0f,\n      (y1 / SCREEN_PIX_H * 2.0f - 1.0f) * -1.0f,\n\n      x1 / SCREEN_PIX_W * 2.0f - 1.0f,\n      (y2 / SCREEN_PIX_H * 2.0f - 1.0f) * -1.0f,\n\n      x2 / SCREEN_PIX_W * 2.0f - 1.0f,\n      (y1 / SCREEN_PIX_H * 2.0f - 1.0f) * -1.0f,\n\n      x2 / SCREEN_PIX_W * 2.0f - 1.0f,\n      (y2 / SCREEN_PIX_H * 2.0f - 1.0f) * -1.0f,\n    };\n\n    // Charsets\n    gl2_check_remap_chars(graphics);\n\n    // Background\n    dest = render_data->background_texture;\n\n    for(i = 0; i < layer_w * layer_h; i++, dest++, src++)\n    {\n      fg_color = translate_layer_color(graphics, src->fg_color);\n      bg_color = translate_layer_color(graphics, src->bg_color);\n\n      if(src->char_value != INVISIBLE_CHAR &&\n       bg_color != layer->transparent_col && fg_color != layer->transparent_col)\n        *dest = graphics->flat_intensity_palette[bg_color];\n      else\n        *dest = 0x00000000;\n    }\n\n    gl2.glEnable(GL_ALPHA_TEST);\n\n    gl2.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_BG_ID]);\n    gl_check_error();\n\n    gl2.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, layer_w, layer_h, GL_RGBA,\n     GL_UNSIGNED_BYTE, render_data->background_texture);\n    gl_check_error();\n\n    // Draw background\n    gl2.glTexCoordPointer(2, GL_FLOAT, 0, tex_coord_array_single);\n    gl_check_error();\n\n    gl2.glVertexPointer(2, GL_FLOAT, 0, vertex_array_single);\n    gl_check_error();\n\n    gl2.glColorPointer(4, GL_UNSIGNED_BYTE, 0, color_array_white);\n    gl_check_error();\n\n    gl2.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n    gl_check_error();\n\n    // Draw screen\n    gl2.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_DATA_ID]);\n    gl_check_error();\n\n    src = layer->data;\n\n    gl2.glTexCoordPointer(2, GL_FLOAT, 0, render_data->tex_coord_array);\n    gl_check_error();\n\n    gl2.glVertexPointer(2, GL_FLOAT, 0, render_data->vertex_array);\n    gl_check_error();\n\n    gl2.glColorPointer(4, GL_UNSIGNED_BYTE, 0, render_data->color_array);\n    gl_check_error();\n\n    i = 0;\n    while(true)\n    {\n      GLubyte *pal_base = NULL;\n\n      for(i2 = 0; i2 < layer_w; i2++)\n      {\n        bg_color = translate_layer_color(graphics, src->bg_color);\n        fg_color = translate_layer_color(graphics, src->fg_color);\n        char_value = translate_layer_char(src->char_value, layer->offset);\n\n        // Multiply X by 2 as there are normal and inverted copies of each char\n        charset_x = char_value % CHARSET_COLS * 2;\n        charset_y = char_value / CHARSET_COLS;\n\n        tex_coord_array[0] = (charset_x + 0.0f) * TEX_CHAR_W;\n        tex_coord_array[1] = (charset_y + 1.0f) * TEX_CHAR_H;\n\n        tex_coord_array[2] = (charset_x + 0.0f) * TEX_CHAR_W;\n        tex_coord_array[3] = (charset_y + 0.0f) * TEX_CHAR_H;\n\n        tex_coord_array[4] = (charset_x + 1.0f) * TEX_CHAR_W;\n        tex_coord_array[5] = (charset_y + 1.0f) * TEX_CHAR_H;\n\n        tex_coord_array[6] = (charset_x + 1.0f) * TEX_CHAR_W;\n        tex_coord_array[7] = (charset_y + 0.0f) * TEX_CHAR_H;\n\n        x1 = (i2 + 0.0f) * CHAR_W + layer->x;\n        x2 = (i2 + 1.0f) * CHAR_W + layer->x;\n        y1 = (i  + 0.0f) * CHAR_H + layer->y;\n        y2 = (i  + 1.0f) * CHAR_H + layer->y;\n\n        vertex_array[0] =  x1 * 2.0f / SCREEN_PIX_W - 1.0f;\n        vertex_array[1] = (y2 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f;\n\n        vertex_array[2] =  x1 * 2.0f / SCREEN_PIX_W - 1.0f;\n        vertex_array[3] = (y1 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f;\n\n        vertex_array[4] =  x2 * 2.0f / SCREEN_PIX_W - 1.0f;\n        vertex_array[5] = (y2 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f;\n\n        vertex_array[6] =  x2 * 2.0f / SCREEN_PIX_W - 1.0f;\n        vertex_array[7] = (y1 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f;\n\n        if(fg_color != layer->transparent_col)\n        {\n          pal_base = &render_data->palette[fg_color * 3];\n        }\n        else\n\n        if(bg_color != layer->transparent_col)\n        {\n          pal_base = &render_data->palette[bg_color * 3];\n\n          // Offset to the inverted copy of the current char\n          tex_coord_array[0] += TEX_CHAR_W;\n          tex_coord_array[2] += TEX_CHAR_W;\n          tex_coord_array[4] += TEX_CHAR_W;\n          tex_coord_array[6] += TEX_CHAR_W;\n        }\n        else\n        {\n          pal_base = NULL;\n        }\n\n        if(pal_base)\n        {\n          for(i3 = 0; i3 < 4; i3++)\n          {\n            memcpy(&color_array[i3 * 4], pal_base, 3);\n            color_array[i3 * 4 + 3] = 255;\n          }\n        }\n        else\n        {\n          for(i3 = 0; i3 < 4; i3++)\n          {\n            color_array[i3 * 4 + 3] = 0;\n          }\n        }\n\n        tex_coord_array += 8;\n        vertex_array += 8;\n        color_array += 4 * 4;\n\n        src++;\n      }\n\n      if(++i == layer_h)\n        break;\n      src += layer_w;\n\n      // Draw this row backwards, so we can keep the strip contiguous\n\n      for(i2 = layer_w - 1; i2 >= 0; i2--)\n      {\n        src--;\n        bg_color = translate_layer_color(graphics, src->bg_color);\n        fg_color = translate_layer_color(graphics, src->fg_color);\n        char_value = translate_layer_char(src->char_value, layer->offset);\n\n        // Multiply X by 2 as there are normal and inverted copies of each char\n        charset_x = char_value % CHARSET_COLS * 2;\n        charset_y = char_value / CHARSET_COLS;\n\n        tex_coord_array[0] = (charset_x + 1.0f) * TEX_CHAR_W;\n        tex_coord_array[1] = (charset_y + 1.0f) * TEX_CHAR_H;\n\n        tex_coord_array[2] = (charset_x + 1.0f) * TEX_CHAR_W;\n        tex_coord_array[3] = (charset_y + 0.0f) * TEX_CHAR_H;\n\n        tex_coord_array[4] = (charset_x + 0.0f) * TEX_CHAR_W;\n        tex_coord_array[5] = (charset_y + 1.0f) * TEX_CHAR_H;\n\n        tex_coord_array[6] = (charset_x + 0.0f) * TEX_CHAR_W;\n        tex_coord_array[7] = (charset_y + 0.0f) * TEX_CHAR_H;\n\n        x1 = (i2 + 0.0f) * CHAR_W + layer->x;\n        x2 = (i2 + 1.0f) * CHAR_W + layer->x;\n        y1 = (i  + 0.0f) * CHAR_H + layer->y;\n        y2 = (i  + 1.0f) * CHAR_H + layer->y;\n\n        vertex_array[0] =  x2 * 2.0f / SCREEN_PIX_W - 1.0f;\n        vertex_array[1] = (y2 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f;\n\n        vertex_array[2] =  x2 * 2.0f / SCREEN_PIX_W - 1.0f;\n        vertex_array[3] = (y1 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f;\n\n        vertex_array[4] =  x1 * 2.0f / SCREEN_PIX_W - 1.0f;\n        vertex_array[5] = (y2 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f;\n\n        vertex_array[6] =  x1 * 2.0f / SCREEN_PIX_W - 1.0f;\n        vertex_array[7] = (y1 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f;\n\n        if(fg_color != layer->transparent_col)\n        {\n          pal_base = &render_data->palette[fg_color * 3];\n        }\n        else\n\n        if(bg_color != layer->transparent_col)\n        {\n          pal_base = &render_data->palette[bg_color * 3];\n\n          // Offset to the inverted copy of the current char\n          tex_coord_array[0] += TEX_CHAR_W;\n          tex_coord_array[2] += TEX_CHAR_W;\n          tex_coord_array[4] += TEX_CHAR_W;\n          tex_coord_array[6] += TEX_CHAR_W;\n        }\n        else\n        {\n          pal_base = NULL;\n        }\n\n        if(pal_base)\n        {\n          for(i3 = 0; i3 < 4; i3++)\n          {\n            memcpy(&color_array[i3 * 4], pal_base, 3);\n            color_array[i3 * 4 + 3] = 255;\n          }\n        }\n        else\n        {\n          for(i3 = 0; i3 < 4; i3++)\n          {\n            color_array[i3 * 4 + 3] = 0;\n          }\n        }\n\n        tex_coord_array += 8;\n        vertex_array += 8;\n        color_array += 4 * 4;\n      }\n\n      if(++i == layer_h)\n        break;\n      src += layer_w;\n    }\n\n    gl2.glDrawArrays(GL_TRIANGLE_STRIP, 0, layer_w * layer_h * 4);\n    gl_check_error();\n\n    gl2.glDisable(GL_ALPHA_TEST);\n  }\n  else\n  {\n    // SMZX mode- use software layer renderer\n    static const float tex_coord_array_single[2 * 4] =\n    {\n      0.0f,             0.0f,\n      0.0f,             350.0f / 512.0f,\n      640.0f / 1024.0f, 0.0f,\n      640.0f / 1024.0f, 350.0f / 512.0f,\n    };\n\n    render_layer(render_data->pixels, SCREEN_PIX_W, SCREEN_PIX_H,\n     SCREEN_PIX_W * 4, 32, graphics, layer);\n\n    gl2.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_SCREEN_ID]);\n    gl_check_error();\n\n    gl2.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 640, 350, GL_RGBA,\n      GL_UNSIGNED_BYTE, render_data->pixels);\n    gl_check_error();\n\n    gl2.glTexCoordPointer(2, GL_FLOAT, 0, tex_coord_array_single);\n    gl_check_error();\n\n    gl2.glVertexPointer(2, GL_FLOAT, 0, vertex_array_single);\n    gl_check_error();\n\n    gl2.glColorPointer(4, GL_UNSIGNED_BYTE, 0, color_array_white);\n    gl_check_error();\n\n    gl2.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n    gl_check_error();\n\n    gl2.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_DATA_ID]);\n    gl_check_error();\n  }\n}\n\nstatic void gl2_render_cursor(struct graphics_data *graphics, unsigned int x,\n unsigned int y, uint16_t color, unsigned int lines, unsigned int offset)\n{\n  struct gl2_render_data *render_data = graphics->render_data;\n  GLubyte *pal_base = &render_data->palette[color * 3];\n\n  const float vertex_array[2 * 4] =\n  {\n    (x * 8)*2.0f/640.0f-1.0f,     (y * 14 + offset)*-2.0f/350.0f+1.0f,\n    (x * 8)*2.0f/640.0f-1.0f,     (y * 14 + lines + offset)*-2.0f/350.0f+1.0f,\n    (x * 8 + 8)*2.0f/640.0f-1.0f, (y * 14 + offset)*-2.0f/350.0f+1.0f,\n    (x * 8 + 8)*2.0f/640.0f-1.0f, (y * 14 + lines + offset)*-2.0f/350.0f+1.0f\n  };\n\n  const GLubyte color_array[4 * 4] =\n  {\n    pal_base[0], pal_base[1], pal_base[2], 255,\n    pal_base[0], pal_base[1], pal_base[2], 255,\n    pal_base[0], pal_base[1], pal_base[2], 255,\n    pal_base[0], pal_base[1], pal_base[2], 255\n  };\n\n  gl2.glDisableClientState(GL_TEXTURE_COORD_ARRAY);\n  gl2.glDisable(GL_TEXTURE_2D);\n\n  gl2.glVertexPointer(2, GL_FLOAT, 0, vertex_array);\n  gl_check_error();\n\n  gl2.glColorPointer(4, GL_UNSIGNED_BYTE, 0, color_array);\n  gl_check_error();\n\n  gl2.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n  gl_check_error();\n\n  gl2.glEnableClientState(GL_TEXTURE_COORD_ARRAY);\n  gl2.glEnable(GL_TEXTURE_2D);\n}\n\nstatic void gl2_render_mouse(struct graphics_data *graphics,\n unsigned int x, unsigned int y, unsigned int w, unsigned int h)\n{\n  const float vertex_array[2 * 4] =\n  {\n     x*2.0f/640.0f-1.0f,       y*-2.0f/350.0f+1.0f,\n     x*2.0f/640.0f-1.0f,      (y + h)*-2.0f/350.0f+1.0f,\n    (x + w)*2.0f/640.0f-1.0f,  y*-2.0f/350.0f+1.0f,\n    (x + w)*2.0f/640.0f-1.0f, (y + h)*-2.0f/350.0f+1.0f\n  };\n\n  gl2.glDisableClientState(GL_TEXTURE_COORD_ARRAY);\n  gl2.glDisable(GL_TEXTURE_2D);\n  gl2.glEnable(GL_BLEND);\n\n  gl2.glVertexPointer(2, GL_FLOAT, 0, vertex_array);\n  gl_check_error();\n\n  gl2.glColorPointer(4, GL_UNSIGNED_BYTE, 0, color_array_white);\n  gl_check_error();\n\n  gl2.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n  gl_check_error();\n\n  gl2.glEnableClientState(GL_TEXTURE_COORD_ARRAY);\n  gl2.glEnable(GL_TEXTURE_2D);\n  gl2.glDisable(GL_BLEND);\n}\n\nstatic void gl2_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct gl2_render_data *render_data = graphics->render_data;\n\n  if(render_data->viewport_shrunk)\n  {\n    static const float tex_coord_array_single[2 * 4] =\n    {\n       0.0f,             350.0f / 512.0f,\n       0.0f,             0.0f,\n       640.0f / 1024.0f, 350.0f / 512.0f,\n       640.0f / 1024.0f, 0.0f,\n    };\n\n    gl2.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_SCREEN_ID]);\n    gl_check_error();\n\n    gl2.glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,\n     (window->width_px - SCREEN_PIX_W) >> 1,\n     (window->height_px - SCREEN_PIX_H) >> 1, 1024, 512, 0);\n    gl_check_error();\n\n    gl2_set_real_viewport(window);\n\n    gl2.glClear(GL_COLOR_BUFFER_BIT);\n    gl_check_error();\n\n    gl2.glTexCoordPointer(2, GL_FLOAT, 0, tex_coord_array_single);\n    gl_check_error();\n\n    gl2.glVertexPointer(2, GL_FLOAT, 0, vertex_array_single);\n    gl_check_error();\n\n    gl2.glColorPointer(4, GL_UNSIGNED_BYTE, 0, color_array_white);\n    gl_check_error();\n\n    gl2.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n    gl_check_error();\n\n    gl2.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_DATA_ID]);\n    gl_check_error();\n  }\n\n  gl_swap_buffers(graphics);\n\n  gl2.glClear(GL_COLOR_BUFFER_BIT);\n  gl_check_error();\n}\n\nvoid render_gl2_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = gl2_init_video;\n  renderer->free_video = gl2_free_video;\n  renderer->create_window = gl2_create_window;\n  renderer->resize_window = gl_resize_window;\n  renderer->resize_callback = gl2_resize_callback;\n  renderer->set_viewport = set_window_viewport_scaled;\n  renderer->set_window_caption = gl_set_window_caption;\n  renderer->set_window_icon = gl_set_window_icon;\n  renderer->update_colors = gl2_update_colors;\n  renderer->remap_char_range = gl2_remap_char_range;\n  renderer->remap_char = gl2_remap_char;\n  renderer->remap_charbyte = gl2_remap_charbyte;\n  renderer->render_layer = gl2_render_layer;\n  renderer->render_cursor = gl2_render_cursor;\n  renderer->render_mouse = gl2_render_mouse;\n  renderer->sync_screen = gl2_sync_screen;\n}\n"
  },
  {
    "path": "src/render_glsl.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Joel Bouchard Lamontagne <logicow@gmail.com>\n * Copyright (C) 2006-2007 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n\n#include \"data.h\"\n#include \"graphics.h\"\n#include \"platform.h\"\n#include \"render.h\"\n#include \"render_layer.h\"\n#include \"renderers.h\"\n#include \"util.h\"\n#include \"io/path.h\"\n#include \"io/vio.h\"\n\n// Uncomment to enable GL debug output (requires OpenGL 4.3+).\n//#define ENABLE_GL_DEBUG_OUTPUT 1\n\n#ifdef CONFIG_SDL\n#include \"render_sdl.h\"\n#ifdef ENABLE_GL_DEBUG_OUTPUT\n#if SDL_VERSION_ATLEAST(3,0,0)\n#include <SDL3/SDL_opengl_glext.h>\n#else\n#include <SDL_opengl_glext.h>\n#endif\n#endif\n#endif\n\n#ifdef CONFIG_EGL\n#include <GLES2/gl2.h>\n#include \"render_egl.h\"\n#endif\n\n#ifdef CONFIG_GLES\ntypedef GLenum GLiftype;\n#else\ntypedef GLint GLiftype;\n#endif\n\n#include \"render_gl.h\"\n\n#ifndef GL_FRAMEBUFFER\n#define GL_FRAMEBUFFER GL_FRAMEBUFFER_EXT\n#endif\n\n#ifndef GL_COLOR_ATTACHMENT0\n#define GL_COLOR_ATTACHMENT0 GL_COLOR_ATTACHMENT0_EXT\n#endif\n\nstatic const struct gl_version gl_required_version = { 2, 0 };\n\n#define CHARSET_COLS 64\n#define CHARSET_ROWS (FULL_CHARSET_SIZE / CHARSET_COLS)\n#define BG_WIDTH 128\n#define BG_HEIGHT 32\n\n#define SCREEN_PIX_W_F (1.0f * SCREEN_PIX_W)\n#define SCREEN_PIX_H_F (1.0f * SCREEN_PIX_H)\n\n// Must be powers of 2\n#define TEX_DATA_WIDTH 512\n#define TEX_DATA_HEIGHT 1024\n\n// Charsets: CHARSET_COLS * CHAR_W (512) x CHARSET_ROWS * CHAR_H (896)\n#define TEX_DATA_CHR_X 0\n#define TEX_DATA_CHR_Y 0\n\n// Palette: FULL_PAL_SIZE + 1 (273) x 1\n#define TEX_DATA_PAL_X 0\n#define TEX_DATA_PAL_Y 896\n\n// Indicies: SMZX_PAL_SIZE (256) x 4\n#define TEX_DATA_IDX_X 0\n#define TEX_DATA_IDX_Y 897\n\n// Layer: 81 x 26 (max)\n#define TEX_DATA_LAYER_X 0\n#define TEX_DATA_LAYER_Y 901\n\n// NOTE: Layer data packing scheme\n// This is intentionally wasteful so the components don't interfere with each\n// other on old and embedded cards with poor float precision.\n// C = char.\n// B = background color (0-15 normal; >=16 protected)\n// F = foreground color (0-15 normal; >=16 protected)\n// For SMZX, the subpalette number P is sent in place of B and F. If this value\n// is FULL_PAL_SIZE, the char will be treated as transparent. If B or F is sent\n// as PAL_SIZE + PROTECTED_PAL_SIZE, that individual color will be transparent.\n// w        z        y        x\n// 00000000 00000000 00000000 00000000\n// CCCCCCCC CCCCCCCC BBBBBBBB FFFFFFFF\n#define LAYER_SUBPALETTE_POS 0\n#define LAYER_FG_POS 0\n#define LAYER_BG_POS 8\n#define LAYER_CHAR_POS 16\n\nenum\n{\n  TEX_SCREEN_ID,\n  TEX_DATA_ID,\n  NUM_TEXTURES\n};\n\nenum\n{\n  FBO_SCREEN_TEX,\n  NUM_FBOS\n};\n\nenum\n{\n  ATTRIB_POSITION,\n  ATTRIB_TEXCOORD,\n  ATTRIB_COLOR,\n};\n\n/**\n * Some GL drivers attempt to run GLSL in software, resulting in extremely poor\n * performance for MegaZeux. When one of the drivers in this blacklist is\n * detected, video output will fall back to a different renderer instead.\n */\nstruct blacklist_entry\n{\n  const char *match_string; // Compare with GL_RENDERER output to detect driver.\n  const char *reason;       // Reason for blacklisting. Displays to the user.\n};\n\nstatic struct blacklist_entry auto_glsl_blacklist[] =\n{\n  { \"swrast\",\n    \"  MESA software renderer.\\n\"\n    \"  Blacklisted due to poor performance on some machines.\\n\" },\n  { \"softpipe\",\n    \"  MESA software renderer.\\n\"\n    \"  Blacklisted due to poor performance on some machines.\\n\" },\n  { \"llvmpipe\",\n    \"  MESA software renderer.\\n\"\n    \"  Blacklisted due to poor performance on some machines.\\n\" },\n  { \"Software Rasterizer\",\n    \"  MESA software renderer.\\n\"\n    \"  Blacklisted due to poor performance on some machines.\\n\" },\n  { \"Chromium\",\n    \"  Chromium redirects GL commands to other GL drivers. The destination\\n\"\n    \"  often seems to be a software renderer, causing poor performance on\\n\"\n    \"  some machines.\\n\" },\n  { \"Intel EMGD\",\n    \"  This driver may have extremely questionable \\\"OpenGL 2.0\\\" support.\\n\"\n    \"  It may output a wall of spurious/nonsensical warnings when compiling\\n\"\n    \"  the shaders, and it may claim that these shaders compiled\\n\"\n    \"  successfully in cases where they actually did not.\\n\" },\n};\n\nstatic int auto_glsl_blacklist_len = ARRAY_SIZE(auto_glsl_blacklist);\n\nstatic struct\n{\n  void (GL_APIENTRY *glAttachShader)(GLuint program, GLuint shader);\n  void (GL_APIENTRY *glBindAttribLocation)(GLuint program, GLuint index,\n   const char *name);\n  void (GL_APIENTRY *glBindTexture)(GLenum target, GLuint texture);\n  void (GL_APIENTRY *glBlendFunc)(GLenum sfactor, GLenum dfactor);\n  void (GL_APIENTRY *glClear)(GLbitfield mask);\n  void (GL_APIENTRY *glClearColor)(GLclampf red, GLclampf green,\n   GLclampf blue, GLclampf alpha);\n  void (GL_APIENTRY *glCompileShader)(GLuint shader);\n  void (GL_APIENTRY *glCopyTexImage2D)(GLenum target, GLint level,\n   GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height,\n   GLint border);\n  GLenum (GL_APIENTRY *glCreateProgram)(void);\n  GLenum (GL_APIENTRY *glCreateShader)(GLenum type);\n  void (GL_APIENTRY *glDisable)(GLenum cap);\n  void (GL_APIENTRY *glDisableVertexAttribArray)(GLuint index);\n  void (GL_APIENTRY *glDrawArrays)(GLenum mode, GLint first, GLsizei count);\n  void (GL_APIENTRY *glEnable)(GLenum cap);\n  void (GL_APIENTRY *glEnableVertexAttribArray)(GLuint index);\n  void (GL_APIENTRY *glGenTextures)(GLsizei n, GLuint *textures);\n  void (GL_APIENTRY *glDeleteTextures)(GLsizei n, GLuint *textures);\n  GLenum (GL_APIENTRY *glGetError)(void);\n  void (GL_APIENTRY *glGetProgramInfoLog)(GLuint program, GLsizei bufsize,\n   GLsizei *len, char *infolog);\n  void (GL_APIENTRY *glGetShaderiv)(GLuint shader, GLenum pname, GLint *params);\n  void (GL_APIENTRY *glGetShaderInfoLog)(GLuint shader, GLsizei bufsize,\n   GLsizei *len, char *infolog);\n  const GLubyte* (GL_APIENTRY *glGetString)(GLenum name);\n  void (GL_APIENTRY *glLinkProgram)(GLuint program);\n  void (GL_APIENTRY *glShaderSource)(GLuint shader, GLsizei count,\n   const char **strings, const GLint *length);\n  void (GL_APIENTRY *glTexImage2D)(GLenum target, GLint level,\n   GLiftype internalformat, GLsizei width, GLsizei height, GLint border,\n   GLenum format, GLenum type, const GLvoid *pixels);\n  void (GL_APIENTRY *glTexParameterf)(GLenum target, GLenum pname,\n   GLfloat param);\n  void (GL_APIENTRY *glTexSubImage2D)(GLenum target, GLint level,\n   GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,\n   GLenum format, GLenum type, const void *pixels);\n  void (GL_APIENTRY *glUseProgram)(GLuint program);\n  void (GL_APIENTRY *glVertexAttribPointer)(GLuint index, GLint size,\n   GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer);\n  void (GL_APIENTRY *glViewport)(GLint x, GLint y, GLsizei width,\n   GLsizei height);\n  void (GL_APIENTRY *glDeleteShader)(GLuint shader);\n  void (GL_APIENTRY *glDetachShader)(GLuint program, GLuint shader);\n  void (GL_APIENTRY *glDeleteProgram)(GLuint program);\n  void (GL_APIENTRY *glGetAttachedShaders)(GLuint program, GLsizei maxCount,\n   GLsizei *count, GLuint *shaders);\n  GLint (GL_APIENTRY *glGetUniformLocation)(GLuint program, const char *name);\n  void (GL_APIENTRY *glUniform1f)(GLint location, GLfloat v0);\n#ifdef CONFIG_GLES\n  void (GL_APIENTRY *glGetShaderPrecisionFormat)(GLenum shaderType,\n   GLenum precisionType, GLint *range, GLint *precision);\n#endif\n#ifdef ENABLE_GL_DEBUG_OUTPUT\n  void (GL_APIENTRY *glDebugMessageCallback)(GLDEBUGPROC callback, void *param);\n#endif\n\n  // FBO functions are optional for GL (requires 3.0+), mandatory for GLES.\n  boolean has_fbo;\n  void (GL_APIENTRY *glGenFramebuffers)(GLsizei n, GLuint *ids);\n  void (GL_APIENTRY *glDeleteFramebuffers)(GLsizei n, GLuint *framebuffers);\n  void (GL_APIENTRY *glBindFramebuffer)(GLenum target, GLuint framebuffer);\n  void (GL_APIENTRY *glFramebufferTexture2D)(GLenum target, GLenum attachment,\n   GLenum textarget, GLuint texture, GLint level);\n}\nglsl;\n\nstatic const struct dso_syms_map glsl_syms_map[] =\n{\n  { \"glAttachShader\",             { &glsl.glAttachShader }},\n  { \"glBindAttribLocation\",       { &glsl.glBindAttribLocation }},\n  { \"glBindTexture\",              { &glsl.glBindTexture }},\n  { \"glBlendFunc\",                { &glsl.glBlendFunc }},\n  { \"glClear\",                    { &glsl.glClear }},\n  { \"glClearColor\",               { &glsl.glClearColor }},\n  { \"glCompileShader\",            { &glsl.glCompileShader }},\n  { \"glCopyTexImage2D\",           { &glsl.glCopyTexImage2D }},\n  { \"glCreateProgram\",            { &glsl.glCreateProgram }},\n  { \"glCreateShader\",             { &glsl.glCreateShader }},\n  { \"glDeleteProgram\",            { &glsl.glDeleteProgram }},\n  { \"glDeleteShader\",             { &glsl.glDeleteShader }},\n  { \"glDeleteTextures\",           { &glsl.glDeleteTextures }},\n  { \"glDetachShader\",             { &glsl.glDetachShader }},\n  { \"glDisable\",                  { &glsl.glDisable }},\n  { \"glDisableVertexAttribArray\", { &glsl.glDisableVertexAttribArray }},\n  { \"glDrawArrays\",               { &glsl.glDrawArrays }},\n  { \"glEnable\",                   { &glsl.glEnable }},\n  { \"glEnableVertexAttribArray\",  { &glsl.glEnableVertexAttribArray }},\n  { \"glGenTextures\",              { &glsl.glGenTextures }},\n  { \"glGetAttachedShaders\",       { &glsl.glGetAttachedShaders }},\n  { \"glGetError\",                 { &glsl.glGetError }},\n  { \"glGetProgramInfoLog\",        { &glsl.glGetProgramInfoLog }},\n  { \"glGetShaderInfoLog\",         { &glsl.glGetShaderInfoLog }},\n  { \"glGetShaderiv\",              { &glsl.glGetShaderiv }},\n  { \"glGetString\",                { &glsl.glGetString }},\n  { \"glGetUniformLocation\",       { &glsl.glGetUniformLocation }},\n  { \"glLinkProgram\",              { &glsl.glLinkProgram }},\n  { \"glShaderSource\",             { &glsl.glShaderSource }},\n  { \"glTexImage2D\",               { &glsl.glTexImage2D }},\n  { \"glTexParameterf\",            { &glsl.glTexParameterf }},\n  { \"glTexSubImage2D\",            { &glsl.glTexSubImage2D }},\n  { \"glUniform1f\",                { &glsl.glUniform1f }},\n  { \"glUseProgram\",               { &glsl.glUseProgram }},\n  { \"glVertexAttribPointer\",      { &glsl.glVertexAttribPointer }},\n  { \"glViewport\",                 { &glsl.glViewport }},\n#ifdef CONFIG_GLES\n  { \"glGetShaderPrecisionFormat\", { &glsl.glGetShaderPrecisionFormat }},\n#endif\n#ifdef ENABLE_GL_DEBUG_OUTPUT\n  { \"glDebugMessageCallback\",     { &glsl.glDebugMessageCallback }},\n#endif\n  DSO_MAP_END\n};\n\nstatic const struct dso_syms_map glsl_syms_map_fbo[] =\n{\n  { \"glBindFramebuffer\",          { &glsl.glBindFramebuffer }},\n  { \"glDeleteFramebuffers\",       { &glsl.glDeleteFramebuffers }},\n  { \"glFramebufferTexture2D\",     { &glsl.glFramebufferTexture2D }},\n  { \"glGenFramebuffers\",          { &glsl.glGenFramebuffers }},\n  DSO_MAP_END\n};\n\n#define gl_check_error() gl_error(__FILE__, __LINE__, glsl.glGetError)\n\nstruct glsl_render_data\n{\n#ifdef CONFIG_EGL\n  struct egl_render_data egl;\n#endif\n#ifdef CONFIG_SDL\n  struct sdl_render_data sdl;\n#endif\n  uint32_t *pixels;\n  uint32_t charset_texture[CHAR_H * FULL_CHARSET_SIZE * CHAR_W];\n  uint32_t background_texture[BG_WIDTH * BG_HEIGHT];\n  GLuint textures[NUM_TEXTURES];\n  GLuint fbos[NUM_FBOS];\n  GLuint uniform_tilemap_pro_pal;\n  GLubyte palette[3 * FULL_PAL_SIZE];\n  boolean remap_texture;\n  boolean remap_char[FULL_CHARSET_SIZE];\n  boolean dirty_palette;\n  boolean dirty_indices;\n  boolean use_software_renderer;\n  int last_tcol;\n  GLuint scaler_program;\n  GLuint tilemap_program;\n  GLuint tilemap_smzx_program;\n  GLuint mouse_program;\n  GLuint cursor_program;\n  struct config_info *conf;\n};\n\nstatic char *source_cache[GLSL_SHADER_RES_COUNT];\n\n#define INFO_MAX 1000\n\nstatic GLint glsl_verify_compile(struct glsl_render_data *render_data,\n GLenum shader)\n{\n  GLint compile_status;\n  char buffer[INFO_MAX];\n  GLsizei len = 0;\n\n  glsl.glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);\n  glsl.glGetShaderInfoLog(shader, INFO_MAX - 1, &len, buffer);\n  buffer[len] = 0;\n\n  if(len > 0)\n    warn(\"%s\", buffer);\n\n  if(compile_status == GL_FALSE)\n    warn(\"Shader compilation failed!\\n\");\n\n  return compile_status;\n}\n\nstatic void glsl_verify_link(struct glsl_render_data *render_data,\n GLenum program)\n{\n  char buffer[INFO_MAX];\n  GLsizei len = 0;\n\n  glsl.glGetProgramInfoLog(program, INFO_MAX - 1, &len, buffer);\n  buffer[len] = 0;\n\n  if(len > 0)\n    warn(\"%s\", buffer);\n}\n\nstatic char *glsl_load_string(const char *filename)\n{\n  char *buffer = NULL;\n  unsigned long size;\n  vfile *vf;\n\n  vf = vfopen_unsafe(filename, \"rb\");\n  if(!vf)\n    goto err_out;\n\n  size = vfilelength(vf, false);\n  if(size <= 0)\n    goto err_close;\n\n  buffer = cmalloc(size + 1);\n  buffer[size] = '\\0';\n\n  if(vfread(buffer, size, 1, vf) != 1)\n  {\n    free(buffer);\n    buffer = NULL;\n  }\n\nerr_close:\n  vfclose(vf);\nerr_out:\n  return buffer;\n}\n\nstatic boolean glsl_scaling_shader_is_default(struct graphics_data *graphics)\n{\n  return graphics->gl_scaling_shader[0] == '\\0';\n}\n\n/**\n * Sets the name of the current scaling shader. If shader is NULL, this will be\n * set to the default scaling shader.\n */\nstatic void glsl_set_scaling_shader(struct graphics_data *graphics,\n const char *shader)\n{\n  if(shader)\n  {\n    size_t buf_len = ARRAY_SIZE(graphics->gl_scaling_shader);\n    snprintf(graphics->gl_scaling_shader, buf_len, \"%s\", shader);\n    graphics->gl_scaling_shader[buf_len - 1] = '\\0';\n  }\n  else\n    graphics->gl_scaling_shader[0] = '\\0';\n}\n\nstatic GLuint glsl_load_shader(struct graphics_data *graphics,\n enum resource_id res, GLenum type)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  int index = res - GLSL_SHADER_RES_FIRST;\n  int loaded_config = 0;\n  boolean is_user_scaler = false;\n  GLint compile_status;\n  GLenum shader;\n\n  assert(res >= GLSL_SHADER_RES_FIRST && res <= GLSL_SHADER_RES_LAST);\n\n  if(res == GLSL_SHADER_SCALER_VERT || res == GLSL_SHADER_SCALER_FRAG)\n    if(!glsl_scaling_shader_is_default(graphics))\n      is_user_scaler = true;\n\n  // If we've already seen this shader, it's loaded, and we don't\n  // need to do any more file I/O.\n\n  if(!source_cache[index])\n  {\n    if(is_user_scaler)\n    {\n      // Try to load these from config before loading the default.\n\n      char *path = cmalloc(MAX_PATH);\n      size_t path_len = path_clean_copy(path, MAX_PATH,\n       mzx_res_get_by_id(GLSL_SHADER_SCALER_DIRECTORY));\n\n      switch(res)\n      {\n        case GLSL_SHADER_SCALER_VERT:\n          snprintf(path + path_len, MAX_PATH - path_len,\n           DIR_SEPARATOR \"%s.vert\", graphics->gl_scaling_shader);\n          break;\n\n        case GLSL_SHADER_SCALER_FRAG:\n          snprintf(path + path_len, MAX_PATH - path_len,\n           DIR_SEPARATOR \"%s.frag\", graphics->gl_scaling_shader);\n          break;\n\n        default:\n          break;\n      }\n      path[MAX_PATH - 1] = '\\0';\n\n      source_cache[index] = glsl_load_string(path);\n\n      if(source_cache[index])\n      {\n        loaded_config = 1;\n      }\n      else\n      {\n        debug(\"Failed to find shader '%s'; falling back to default\\n\", path);\n        loaded_config = 0;\n      }\n\n      free(path);\n    }\n\n    if(!loaded_config)\n    {\n      is_user_scaler = false;\n\n      if(res == GLSL_SHADER_SCALER_FRAG)\n        glsl_set_scaling_shader(graphics, NULL);\n\n      source_cache[index] = glsl_load_string(mzx_res_get_by_id(res));\n      if(!source_cache[index])\n      {\n        warn(\"Failed to find required shader '%s'\\n\", mzx_res_get_by_id(res));\n        return 0;\n      }\n    }\n  }\n\n  shader = glsl.glCreateShader(type);\n\n#ifdef CONFIG_GLES\n  {\n    /**\n     * OpenGL ES really doesn't like '#version 110' being specified. This\n     * is unfortunate, because some drivers (e.g. Intel HD) will emit warnings\n     * if it isn't explicit. Comment these directives out if found.\n     */\n\n    char *pos = source_cache[index];\n\n    while((pos = strstr(pos, \"#version 110\")))\n    {\n      debug(\"Found '#version 110' at %d in %s; commenting out\\n\",\n       (int)(pos - source_cache[index]),\n       mzx_res_get_by_id(res));\n      pos[0] = '/';\n      pos[1] = '/';\n    }\n  }\n#endif // CONFIG_GLES\n\n#ifdef CONFIG_GLES\n  {\n    const GLchar *sources[2];\n    GLint lengths[2];\n\n    sources[0] = \"precision mediump float;\";\n    sources[1] = source_cache[index];\n\n    lengths[0] = (GLint)strlen(sources[0]);\n    lengths[1] = (GLint)strlen(source_cache[index]);\n\n    glsl.glShaderSource(shader, 2, sources, lengths);\n  }\n#else\n  {\n    GLint length = (GLint)strlen(source_cache[index]);\n    const char **source_ptr = (const char **)&source_cache[index];\n\n    glsl.glShaderSource(shader, 1, source_ptr, &length);\n  }\n#endif\n\n  glsl.glCompileShader(shader);\n  compile_status = glsl_verify_compile(render_data, shader);\n\n  if(compile_status == GL_FALSE)\n  {\n    if(is_user_scaler)\n    {\n      // Attempt the default shader\n      glsl_set_scaling_shader(graphics, NULL);\n      free(source_cache[index]);\n      source_cache[index] = NULL;\n\n      return glsl_load_shader(graphics, res, type);\n    }\n    else\n    {\n      warn(\"Required shader '%s' failed to compile.\\n\", mzx_res_get_by_id(res));\n      return 0;\n    }\n  }\n\n  return shader;\n}\n\nstatic GLuint glsl_load_program(struct graphics_data *graphics,\n enum resource_id v_res, enum resource_id f_res)\n{\n  GLenum vertex, fragment;\n  GLuint program = 0;\n  char *path;\n\n  path = cmalloc(MAX_PATH);\n\n  vertex = glsl_load_shader(graphics, v_res, GL_VERTEX_SHADER);\n  if(!vertex)\n    goto err_free_path;\n\n  fragment = glsl_load_shader(graphics, f_res, GL_FRAGMENT_SHADER);\n  if(!fragment)\n    goto err_free_path;\n\n  program = glsl.glCreateProgram();\n\n  glsl.glAttachShader(program, vertex);\n  glsl.glAttachShader(program, fragment);\n\nerr_free_path:\n  free(path);\n  return program;\n}\n\nstatic void glsl_delete_shaders(GLuint program)\n{\n  GLuint shaders[16];\n  GLsizei shader_count, i;\n  glsl.glGetAttachedShaders(program, 16, &shader_count, shaders);\n  gl_check_error();\n  for(i = 0; i < shader_count; i++)\n  {\n    glsl.glDetachShader(program, shaders[i]);\n    gl_check_error();\n    glsl.glDeleteShader(shaders[i]);\n    gl_check_error();\n  }\n}\n\nstatic void glsl_load_shaders(struct graphics_data *graphics)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n\n  render_data->scaler_program = glsl_load_program(graphics,\n   GLSL_SHADER_SCALER_VERT, GLSL_SHADER_SCALER_FRAG);\n  if(render_data->scaler_program)\n  {\n    glsl.glBindAttribLocation(render_data->scaler_program,\n     ATTRIB_POSITION, \"Position\");\n    glsl.glBindAttribLocation(render_data->scaler_program,\n     ATTRIB_TEXCOORD, \"Texcoord\");\n    glsl.glLinkProgram(render_data->scaler_program);\n    glsl_verify_link(render_data, render_data->scaler_program);\n    glsl_delete_shaders(render_data->scaler_program);\n  }\n\n  if(render_data->use_software_renderer)\n    return;\n\n  render_data->tilemap_program = glsl_load_program(graphics,\n   GLSL_SHADER_TILEMAP_VERT, GLSL_SHADER_TILEMAP_FRAG);\n  if(render_data->tilemap_program)\n  {\n    glsl.glBindAttribLocation(render_data->tilemap_program,\n     ATTRIB_POSITION, \"Position\");\n    glsl.glBindAttribLocation(render_data->tilemap_program,\n     ATTRIB_TEXCOORD, \"Texcoord\");\n    glsl.glLinkProgram(render_data->tilemap_program);\n    glsl_verify_link(render_data, render_data->tilemap_program);\n    glsl_delete_shaders(render_data->tilemap_program);\n\n    render_data->uniform_tilemap_pro_pal = glsl.glGetUniformLocation(\n     render_data->tilemap_program, \"protected_pal_position\");\n  }\n\n  render_data->tilemap_smzx_program = glsl_load_program(graphics,\n   GLSL_SHADER_TILEMAP_VERT, GLSL_SHADER_TILEMAP_SMZX_FRAG);\n  if(render_data->tilemap_smzx_program)\n  {\n    glsl.glBindAttribLocation(render_data->tilemap_smzx_program,\n     ATTRIB_POSITION, \"Position\");\n    glsl.glBindAttribLocation(render_data->tilemap_smzx_program,\n     ATTRIB_TEXCOORD, \"Texcoord\");\n    glsl.glLinkProgram(render_data->tilemap_smzx_program);\n    glsl_verify_link(render_data, render_data->tilemap_smzx_program);\n    glsl_delete_shaders(render_data->tilemap_smzx_program);\n  }\n\n  render_data->mouse_program = glsl_load_program(graphics,\n   GLSL_SHADER_MOUSE_VERT, GLSL_SHADER_MOUSE_FRAG);\n  if(render_data->mouse_program)\n  {\n    glsl.glBindAttribLocation(render_data->mouse_program,\n     ATTRIB_POSITION, \"Position\");\n    glsl.glLinkProgram(render_data->mouse_program);\n    glsl_verify_link(render_data, render_data->mouse_program);\n    glsl_delete_shaders(render_data->mouse_program);\n  }\n\n  render_data->cursor_program  = glsl_load_program(graphics,\n   GLSL_SHADER_CURSOR_VERT, GLSL_SHADER_CURSOR_FRAG);\n  if(render_data->cursor_program)\n  {\n    glsl.glBindAttribLocation(render_data->cursor_program,\n     ATTRIB_POSITION, \"Position\");\n    glsl.glBindAttribLocation(render_data->cursor_program,\n     ATTRIB_COLOR, \"Color\");\n    glsl.glLinkProgram(render_data->cursor_program);\n    glsl_verify_link(render_data, render_data->cursor_program);\n    glsl_delete_shaders(render_data->cursor_program);\n  }\n}\n\nstatic boolean glsl_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  struct glsl_render_data *render_data =\n   (struct glsl_render_data *)cmalloc(sizeof(struct glsl_render_data));\n  if(!render_data)\n    return false;\n\n  if(!GL_LoadLibrary(GL_LIB_PROGRAMMABLE))\n    goto err_free;\n\n  memset(render_data, 0, sizeof(struct glsl_render_data));\n  graphics->render_data = render_data;\n\n  graphics->gl_vsync = conf->gl_vsync;\n  graphics->allow_resize = conf->allow_resize;\n  graphics->gl_filter_method = conf->gl_filter_method;\n  graphics->ratio = conf->video_ratio;\n  graphics->bits_per_pixel = 32;\n\n  glsl_set_scaling_shader(graphics, conf->gl_scaling_shader);\n\n  // OpenGL only supports 16/32bit colour\n  if(conf->force_bpp == 16 || conf->force_bpp == 32)\n    graphics->bits_per_pixel = conf->force_bpp;\n\n  // Buffer for screen texture\n  render_data->pixels = (uint32_t *)cmalloc(sizeof(uint32_t) *\n   GL_POWER_2_WIDTH * GL_POWER_2_HEIGHT);\n  render_data->conf = conf;\n  if(!render_data->pixels)\n    goto err_free;\n\n  return true;\n\nerr_free:\n  free(render_data);\n  graphics->render_data = NULL;\n  return false;\n}\n\nstatic void glsl_free_video(struct graphics_data *graphics)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  int i;\n\n  for(i = 0; i < GLSL_SHADER_RES_COUNT; i++)\n  {\n    if(source_cache[i])\n    {\n      free((void *)source_cache[i]);\n      source_cache[i] = NULL;\n    }\n  }\n\n  if(render_data)\n  {\n    if(glsl.has_fbo)\n    {\n      glsl.glDeleteFramebuffers(NUM_FBOS, render_data->fbos);\n      gl_check_error();\n    }\n\n    if(glsl.glDeleteTextures)\n    {\n      glsl.glDeleteTextures(NUM_TEXTURES, render_data->textures);\n      gl_check_error();\n\n      glsl.glUseProgram(0);\n      gl_check_error();\n    }\n\n    gl_cleanup(graphics);\n    free(render_data->pixels);\n    free(render_data);\n    graphics->render_data = NULL;\n  }\n}\n\nstatic void glsl_remap_char_range(struct graphics_data *graphics, uint16_t first,\n uint16_t count)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n\n  if(first + count > FULL_CHARSET_SIZE)\n    count = FULL_CHARSET_SIZE - first;\n\n  // FIXME arbitrary\n  if(count <= 256)\n    memset(render_data->remap_char + first, true, count);\n\n  else\n    render_data->remap_texture = true;\n}\n\nstatic boolean glsl_resize_callback(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  int width = window->width_px;\n  int height = window->height_px;\n\n  glsl.glViewport(0, 0, width, height);\n  gl_check_error();\n\n  // Initial clear color <0,0,0,0> may be interpreted as transparent (Wayland).\n  glsl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n  gl_check_error();\n  // Clear entire window to prevent background leak-through.\n  glsl.glClear(GL_COLOR_BUFFER_BIT);\n  gl_check_error();\n\n  // Free any preexisting textures if they exist\n  glsl.glDeleteTextures(NUM_TEXTURES, render_data->textures);\n  gl_check_error();\n\n  glsl.glGenTextures(NUM_TEXTURES, render_data->textures);\n  gl_check_error();\n\n  // Screen texture\n  glsl.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_SCREEN_ID]);\n  gl_check_error();\n\n  gl_set_filter_method(CONFIG_GL_FILTER_LINEAR, glsl.glTexParameterf);\n\n  glsl.glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);\n\n  // Fill the entire screen texture transparent to avoid edge leak-through.\n  memset(render_data->pixels, 0,\n   sizeof(uint32_t) * GL_POWER_2_WIDTH * GL_POWER_2_HEIGHT);\n\n  glsl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, GL_POWER_2_WIDTH,\n   GL_POWER_2_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE,\n   render_data->pixels);\n  gl_check_error();\n\n  if(glsl.has_fbo)\n  {\n    glsl.glDeleteFramebuffers(NUM_FBOS, render_data->fbos);\n    gl_check_error();\n\n    glsl.glGenFramebuffers(NUM_FBOS, render_data->fbos);\n    gl_check_error();\n\n    glsl.glBindFramebuffer(GL_FRAMEBUFFER, render_data->fbos[FBO_SCREEN_TEX]);\n    gl_check_error();\n\n    glsl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,\n     GL_TEXTURE_2D, render_data->textures[TEX_SCREEN_ID], 0);\n  }\n\n  // Data texture\n  if(!render_data->use_software_renderer)\n  {\n    glsl.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_DATA_ID]);\n    gl_check_error();\n\n    gl_set_filter_method(CONFIG_GL_FILTER_NEAREST, glsl.glTexParameterf);\n\n    glsl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEX_DATA_WIDTH, TEX_DATA_HEIGHT,\n     0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);\n    gl_check_error();\n\n    glsl_remap_char_range(graphics, 0, FULL_CHARSET_SIZE);\n    render_data->dirty_palette = true;\n    render_data->dirty_indices = true;\n  }\n\n  glsl_load_shaders(graphics);\n  return true;\n}\n\n#ifdef ENABLE_GL_DEBUG_OUTPUT\nstatic void glsl_debug_callback(GLenum source, GLenum type, GLuint id,\n GLenum severity, GLsizei length, const GLchar *message, const void *userParam)\n{\n  length = length < 0 ? (GLsizei)strlen(message) : length;\n  debug(\"GL (source 0x%x, type 0x%x, severity 0x%x): %.*s\\n\",\n    source, type, severity, length, message\n  );\n}\n#endif\n\nstatic boolean glsl_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n#ifdef CONFIG_GLES\n  struct glsl_render_data *render_data = graphics->render_data;\n#endif\n  boolean load_fbo_syms = true;\n\n  gl_set_attributes(graphics);\n\n  if(!gl_create_window(graphics, window, gl_required_version))\n    return false;\n\n  gl_set_attributes(graphics);\n\n  if(!gl_load_syms(glsl_syms_map))\n  {\n    gl_cleanup(graphics);\n    return false;\n  }\n\n  // GLSL needs a specific version of OpenGL; desktop GL must be 2.0.\n  // All OpenGL ES 2.0 implementations are supported, so don't do\n  // the check with these configurations.\n#ifndef CONFIG_GLES\n  {\n    const char *version;\n    double version_float;\n\n    version = (const char *)glsl.glGetString(GL_VERSION);\n    if(!version)\n    {\n      warn(\"Could not load GL version string.\\n\");\n      gl_cleanup(graphics);\n      return false;\n    }\n\n    version_float = atof(version);\n    if(version_float < 2.0)\n    {\n      warn(\"Need >= OpenGL 2.0, got OpenGL %.1f.\\n\", version_float);\n      gl_cleanup(graphics);\n      return false;\n    }\n\n    load_fbo_syms = false;\n    if(version_float >= 3.0)\n    {\n      debug(\"Attempting to load FBO syms...\\n\");\n      load_fbo_syms = true;\n    }\n  }\n#else\n  if(!render_data->use_software_renderer)\n  {\n    // All OpenGL ES 2.0 implementations are \"technically\" supported, but some\n    // may not support highp floats and may not provide adequate precision in\n    // their mediump floats. Print some warnings in this case.\n    GLint range[2];\n    GLint precision;\n\n    glsl.glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_FLOAT,\n     range, &precision);\n    if(precision == 0)\n    {\n      warn(\"no support for high-precision floats in the fragment shader!\\n\");\n\n      glsl.glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_MEDIUM_FLOAT,\n       range, &precision);\n      warn(\"fragment mediump float range = (2^-%d, 2^%d), precision = 2^-%d\\n\",\n       range[0], range[1], precision);\n      if(range[0] <= 11 || precision <= 10)\n      {\n        warn(\"poor medium float precision! \"\n         \"This renderer may look bad; use \\\"glslscale\\\" or \\\"softscale\\\" instead.\\n\");\n      }\n    }\n  }\n#endif\n\n  if(load_fbo_syms && gl_load_syms(glsl_syms_map_fbo))\n  {\n    debug(\"Using FBO syms for GLSL renderer.\\n\");\n    glsl.has_fbo = true;\n  }\n  else\n    glsl.has_fbo = false;\n\n#ifdef ENABLE_GL_DEBUG_OUTPUT\n  glsl.glEnable(GL_DEBUG_OUTPUT);\n  glsl.glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);\n  glsl.glDebugMessageCallback(glsl_debug_callback, NULL);\n#endif\n\n  return glsl_resize_callback(graphics, window);\n}\n\nstatic boolean glsl_software_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  render_data->use_software_renderer = true;\n  return glsl_create_window(graphics, window);\n}\n\nstatic boolean glsl_auto_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  const char *renderer;\n  int i;\n\n  if(glsl_software_create_window(graphics, window))\n  {\n    // Driver blacklist. If the renderer is on the blacklist, fall back\n    // on software as these renderers have disastrous GLSL performance.\n\n    renderer = (const char *)glsl.glGetString(GL_RENDERER);\n\n    // Print the full renderer string for reference.\n    info(\"GL driver: %s\\n\", renderer);\n    info(\"GL version: %s\\n\\n\", (const char *)glsl.glGetString(GL_VERSION));\n\n    for(i = 0; i < auto_glsl_blacklist_len; i++)\n    {\n      if(strstr(renderer, auto_glsl_blacklist[i].match_string))\n      {\n        warn(\n          \"Detected blacklisted driver: \\\"%s\\\". Disabling glsl. Reason:\\n\\n\"\n          \"%s\\n\\n\"\n          \"Run again with \\\"video_output=glsl\\\" or \\\"video_output=glslscale\\\" \"\n          \"to force-enable glsl.\\n\\n\",\n          auto_glsl_blacklist[i].match_string,\n          auto_glsl_blacklist[i].reason\n        );\n        gl_cleanup(graphics);\n        return false;\n      }\n    }\n\n    // Switch from auto_glsl to glslscale now that it is known to work.\n    graphics->renderer.create_window = glsl_software_create_window;\n    strcpy(render_data->conf->video_output, \"glslscale\");\n\n    return true;\n  }\n  return false;\n}\n\nstatic void glsl_remap_char(struct graphics_data *graphics, uint16_t chr)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  render_data->remap_char[chr] = true;\n}\n\nstatic void glsl_remap_charbyte(struct graphics_data *graphics,\n uint16_t chr, uint8_t byte)\n{\n  glsl_remap_char(graphics, chr);\n}\n\nstatic int *glsl_char_bitmask_to_texture(signed char *c, int *p)\n{\n  // This tests the 7th bit, if 0, result is 0.\n  // If 1, result is 255 (because of sign extended bit shift!).\n  // Note the use of char constants to force 8bit calculation.\n  *(p++) = *c << 24 >> 31;\n  *(p++) = *c << 25 >> 31;\n  *(p++) = *c << 26 >> 31;\n  *(p++) = *c << 27 >> 31;\n  *(p++) = *c << 28 >> 31;\n  *(p++) = *c << 29 >> 31;\n  *(p++) = *c << 30 >> 31;\n  *(p++) = *c << 31 >> 31;\n  return p;\n}\n\nstatic inline void glsl_do_remap_charsets(struct graphics_data *graphics)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  signed char *c = (signed char *)graphics->charset;\n  int *p = (int *)render_data->charset_texture;\n  unsigned int i, j, k;\n\n  for(i = 0; i < CHARSET_ROWS; i++, c += -CHAR_H + CHARSET_COLS * CHAR_H)\n    for(j = 0; j < CHAR_H; j++, c += -CHARSET_COLS * CHAR_H + 1)\n      for(k = 0; k < CHARSET_COLS; k++, c += CHAR_H)\n        p = glsl_char_bitmask_to_texture(c, p);\n\n  glsl.glTexSubImage2D(GL_TEXTURE_2D, 0,\n   TEX_DATA_CHR_X,\n   TEX_DATA_CHR_Y,\n   CHARSET_COLS * CHAR_W,\n   CHARSET_ROWS * CHAR_H,\n   GL_RGBA, GL_UNSIGNED_BYTE, render_data->charset_texture);\n  gl_check_error();\n}\n\nstatic inline void glsl_do_remap_char(struct graphics_data *graphics,\n uint16_t chr)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  signed char *c = (signed char *)graphics->charset;\n  int *p = (int *)render_data->charset_texture;\n  unsigned int i;\n\n  c += chr * 14;\n\n  for(i = 0; i < 14; i++, c++)\n    p = glsl_char_bitmask_to_texture(c, p);\n\n  glsl.glTexSubImage2D(GL_TEXTURE_2D, 0,\n   TEX_DATA_CHR_X + (chr % CHARSET_COLS) * CHAR_W,\n   TEX_DATA_CHR_Y + (chr / CHARSET_COLS) * CHAR_H,\n   CHAR_W,\n   CHAR_H,\n   GL_RGBA, GL_UNSIGNED_BYTE, render_data->charset_texture);\n  gl_check_error();\n}\n\nstatic void glsl_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned int count)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  unsigned int i;\n  for(i = 0; i < count; i++)\n  {\n    graphics->flat_intensity_palette[i] = gl_pack_u32((0xFF << 24) |\n     (palette[i].b << 16) | (palette[i].g << 8) | palette[i].r);\n    render_data->palette[i*3  ] = (GLubyte)palette[i].r;\n    render_data->palette[i*3+1] = (GLubyte)palette[i].g;\n    render_data->palette[i*3+2] = (GLubyte)palette[i].b;\n  }\n}\n\nstatic void glsl_render_layer(struct graphics_data *graphics,\n struct video_layer *layer)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  struct char_element *src = layer->data;\n  uint32_t *colorptr;\n  uint32_t *dest;\n  unsigned int char_value, fg_color, bg_color, subpalette;\n  unsigned int i, j;\n  int width, height;\n\n  int x1 = layer->x;\n  int x2 = layer->x + layer->w * CHAR_W;\n  int y1 = layer->y;\n  int y2 = layer->y + layer->h * CHAR_H;\n\n  float v_left =   1.0f * x1 / SCREEN_PIX_W_F * 2.0f - 1.0f;\n  float v_right =  1.0f * x2 / SCREEN_PIX_W_F * 2.0f - 1.0f;\n  float v_top =    1.0f * y1 / SCREEN_PIX_H_F * 2.0f - 1.0f;\n  float v_bottom = 1.0f * y2 / SCREEN_PIX_H_F * 2.0f - 1.0f;\n\n  float vertex_array_single[2 * 4] =\n  {\n    v_left, -v_top,\n    v_left, -v_bottom,\n    v_right, -v_top,\n    v_right, -v_bottom,\n  };\n\n  float tex_coord_array_single[2 * 4] =\n  {\n    0.0f,     0.0f,\n    0.0f,     layer->h,\n    layer->w, 0.0f,\n    layer->w, layer->h,\n  };\n\n  if(render_data->use_software_renderer)\n  {\n    render_layer(render_data->pixels, SCREEN_PIX_W, SCREEN_PIX_H,\n     SCREEN_PIX_W * 4, 32, graphics, layer);\n    return;\n  }\n\n  // Clamp draw area to size of screen texture.\n  // TODO: do this elsewhere with proper access to the window struct.\n  width = graphics->window.width_px;\n  height = graphics->window.height_px;\n  if(width < SCREEN_PIX_W || height < SCREEN_PIX_H)\n  {\n    if(width >= GL_POWER_2_WIDTH)\n      width = GL_POWER_2_WIDTH;\n\n    if(height >= GL_POWER_2_HEIGHT)\n      height = GL_POWER_2_HEIGHT;\n  }\n  else\n  {\n    width = SCREEN_PIX_W;\n    height = SCREEN_PIX_H;\n  }\n\n  glsl.glViewport(0, 0, width, height);\n  gl_check_error();\n\n  if(layer->mode == 0)\n  {\n    glsl.glUseProgram(render_data->tilemap_program);\n    gl_check_error();\n\n    glsl.glUniform1f(render_data->uniform_tilemap_pro_pal,\n     graphics->protected_pal_position);\n  }\n  else\n    glsl.glUseProgram(render_data->tilemap_smzx_program);\n  gl_check_error();\n\n  glsl.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_DATA_ID]);\n  gl_check_error();\n\n  if(render_data->remap_texture)\n  {\n    glsl_do_remap_charsets(graphics);\n    render_data->remap_texture = false;\n    memset(render_data->remap_char, false, sizeof(uint8_t) * FULL_CHARSET_SIZE);\n  }\n  else\n  {\n    for(i = 0; i < FULL_CHARSET_SIZE; i++)\n    {\n      if(render_data->remap_char[i])\n      {\n        glsl_do_remap_char(graphics, i);\n        render_data->remap_char[i] = false;\n      }\n    }\n  }\n\n  // Layer data\n  dest = render_data->background_texture;\n\n  for(i = 0; i < layer->w * layer->h; i++, dest++, src++)\n  {\n    // NOTE: leave the bg_color and fg_color in their current form where the\n    // protected palette starts at 16. This makes it easier to send data to the\n    // shader.\n    char_value = src->char_value;\n    bg_color = src->bg_color;\n    fg_color = src->fg_color;\n    subpalette = ((bg_color & 0xF) << 4) | (fg_color & 0xF);\n\n    if(char_value != INVISIBLE_CHAR)\n    {\n      if(char_value < PROTECTED_CHARSET_POSITION)\n        char_value = (char_value + layer->offset) % PROTECTED_CHARSET_POSITION;\n    }\n    else\n    {\n      bg_color = PAL_SIZE + PROTECTED_PAL_SIZE;\n      fg_color = PAL_SIZE + PROTECTED_PAL_SIZE;\n      subpalette = FULL_PAL_SIZE;\n    }\n\n    if(layer->mode == 0)\n      *dest = gl_pack_u32((char_value << LAYER_CHAR_POS) |\n       (bg_color << LAYER_BG_POS) | (fg_color << LAYER_FG_POS));\n    else\n      *dest = gl_pack_u32((char_value << LAYER_CHAR_POS) |\n       (subpalette << LAYER_SUBPALETTE_POS));\n  }\n\n  glsl.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_DATA_ID]);\n  gl_check_error();\n\n  glsl.glTexSubImage2D(GL_TEXTURE_2D, 0,\n   TEX_DATA_LAYER_X, TEX_DATA_LAYER_Y, layer->w, layer->h,\n   GL_RGBA, GL_UNSIGNED_BYTE, render_data->background_texture);\n  gl_check_error();\n\n  // Palette\n  if(render_data->dirty_palette ||\n   render_data->last_tcol != layer->transparent_col)\n  {\n    unsigned int transparent = graphics->protected_pal_position + PROTECTED_PAL_SIZE;\n    render_data->dirty_palette = false;\n    render_data->last_tcol = layer->transparent_col;\n\n    colorptr = graphics->flat_intensity_palette;\n    dest = render_data->background_texture;\n\n    for(i = 0; i < graphics->protected_pal_position + PROTECTED_PAL_SIZE; i++)\n      dest[i] = colorptr[i];\n\n    if(layer->transparent_col != -1)\n      render_data->background_texture[layer->transparent_col] = 0x00000000;\n    render_data->background_texture[transparent] = 0x00000000;\n\n    glsl.glTexSubImage2D(GL_TEXTURE_2D, 0,\n     TEX_DATA_PAL_X, TEX_DATA_PAL_Y, FULL_PAL_SIZE + 1, 1,\n     GL_RGBA, GL_UNSIGNED_BYTE, render_data->background_texture);\n    gl_check_error();\n  }\n\n  // Indices\n  if(render_data->dirty_indices && layer->mode)\n  {\n    render_data->dirty_indices = false;\n    dest = render_data->background_texture;\n    for(i = 0; i < 4; i++)\n      for(j = 0; j < SMZX_PAL_SIZE; j++, dest++)\n        *dest = gl_pack_u32(graphics->smzx_indices[j * 4 + i]);\n\n    glsl.glTexSubImage2D(GL_TEXTURE_2D, 0,\n     TEX_DATA_IDX_X, TEX_DATA_IDX_Y, SMZX_PAL_SIZE, 4,\n     GL_RGBA, GL_UNSIGNED_BYTE, render_data->background_texture);\n    gl_check_error();\n  }\n\n  glsl.glEnableVertexAttribArray(ATTRIB_POSITION);\n  glsl.glEnableVertexAttribArray(ATTRIB_TEXCOORD);\n\n  glsl.glEnable(GL_BLEND);\n  glsl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n\n  glsl.glVertexAttribPointer(ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0,\n   vertex_array_single);\n  gl_check_error();\n\n  glsl.glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 0,\n   tex_coord_array_single);\n  gl_check_error();\n\n  glsl.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n  gl_check_error();\n\n  glsl.glDisableVertexAttribArray(ATTRIB_POSITION);\n  glsl.glDisableVertexAttribArray(ATTRIB_TEXCOORD);\n  glsl.glDisable(GL_BLEND);\n}\n\nstatic void glsl_render_cursor(struct graphics_data *graphics, unsigned int x,\n unsigned int y, uint16_t color, unsigned int lines, unsigned int offset)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  GLubyte *pal_base = &render_data->palette[color * 3];\n\n  int x1 = x * 8;\n  int x2 = x * 8 + 8;\n  int y1 = y * 14 + offset;\n  int y2 = y * 14 + offset + lines;\n\n  const float vertex_array[2 * 4] =\n  {\n    x1 * 2.0f / SCREEN_PIX_W - 1.0f, (y1 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f,\n    x1 * 2.0f / SCREEN_PIX_W - 1.0f, (y2 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f,\n    x2 * 2.0f / SCREEN_PIX_W - 1.0f, (y1 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f,\n    x2 * 2.0f / SCREEN_PIX_W - 1.0f, (y2 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f,\n  };\n\n  const float color_array[4 * 4] =\n  {\n    pal_base[0]/255.0f, pal_base[1]/255.0f, pal_base[2]/255.0f,\n    pal_base[0]/255.0f, pal_base[1]/255.0f, pal_base[2]/255.0f,\n    pal_base[0]/255.0f, pal_base[1]/255.0f, pal_base[2]/255.0f,\n    pal_base[0]/255.0f, pal_base[1]/255.0f, pal_base[2]/255.0f,\n  };\n\n  if(render_data->use_software_renderer)\n  {\n    render_cursor(render_data->pixels, SCREEN_PIX_W * 4, 32, x, y,\n     graphics->flat_intensity_palette[color], lines, offset);\n    return;\n  }\n\n  glsl.glUseProgram(render_data->cursor_program);\n  gl_check_error();\n\n  glsl.glEnableVertexAttribArray(ATTRIB_POSITION);\n  glsl.glEnableVertexAttribArray(ATTRIB_COLOR);\n\n  glsl.glVertexAttribPointer(ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0,\n   vertex_array);\n  gl_check_error();\n\n  glsl.glVertexAttribPointer(ATTRIB_COLOR, 3, GL_FLOAT, GL_FALSE, 0,\n   color_array);\n  gl_check_error();\n\n  glsl.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n  gl_check_error();\n\n  glsl.glDisableVertexAttribArray(ATTRIB_POSITION);\n  glsl.glDisableVertexAttribArray(ATTRIB_COLOR);\n}\n\nstatic void glsl_render_mouse(struct graphics_data *graphics,\n unsigned int x, unsigned int y, unsigned int w, unsigned int h)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n\n  int x2 = x + w;\n  int y2 = y + h;\n\n  const float vertex_array[2 * 4] =\n  {\n    x  * 2.0f / SCREEN_PIX_W - 1.0f, (y  * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f,\n    x  * 2.0f / SCREEN_PIX_W - 1.0f, (y2 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f,\n    x2 * 2.0f / SCREEN_PIX_W - 1.0f, (y  * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f,\n    x2 * 2.0f / SCREEN_PIX_W - 1.0f, (y2 * 2.0f / SCREEN_PIX_H - 1.0f) * -1.0f,\n  };\n\n  if(render_data->use_software_renderer)\n  {\n    render_mouse(render_data->pixels, SCREEN_PIX_W * 4, 32, x, y, 0xFFFFFFFF,\n     0x0, w, h);\n    return;\n  }\n\n  glsl.glEnable(GL_BLEND);\n  glsl.glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);\n\n  glsl.glUseProgram(render_data->mouse_program);\n  gl_check_error();\n\n  glsl.glEnableVertexAttribArray(ATTRIB_POSITION);\n\n  glsl.glVertexAttribPointer(ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0,\n   vertex_array);\n  gl_check_error();\n\n  glsl.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n  gl_check_error();\n\n  glsl.glDisableVertexAttribArray(ATTRIB_POSITION);\n\n  glsl.glDisable(GL_BLEND);\n}\n\nstatic void glsl_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  int width = window->width_px;\n  int height = window->height_px;\n\n  glsl.glViewport(window->viewport_x, window->viewport_y,\n   window->viewport_width, window->viewport_height);\n\n  // Clamp draw area to size of screen texture.\n  if(width < SCREEN_PIX_W || height < SCREEN_PIX_H)\n  {\n    if(width >= GL_POWER_2_WIDTH)\n      width = GL_POWER_2_WIDTH;\n\n    if(height >= GL_POWER_2_HEIGHT)\n      height = GL_POWER_2_HEIGHT;\n  }\n  else\n  {\n    width = SCREEN_PIX_W;\n    height = SCREEN_PIX_H;\n  }\n\n  glsl.glUseProgram(render_data->scaler_program);\n  gl_check_error();\n\n  glsl.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_SCREEN_ID]);\n  gl_check_error();\n\n  if(render_data->use_software_renderer)\n  {\n    // Screen data was software rendered to the pixels buffer.\n    // Stream it to the screen texture.\n    glsl.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, SCREEN_PIX_W, SCREEN_PIX_H,\n     GL_RGBA, GL_UNSIGNED_BYTE, render_data->pixels);\n    width = SCREEN_PIX_W;\n    height = SCREEN_PIX_H;\n    gl_check_error();\n  }\n  else\n\n  if(!glsl.has_fbo)\n  {\n    // If FBOs are enabled, the screen was rendered to the screen texture\n    // and nothing extra needs to be done here. If FBOs are not enabled, then\n    // the screen needs to be copied to the texture off the window framebuffer.\n    glsl.glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0,\n     GL_POWER_2_WIDTH, GL_POWER_2_HEIGHT, 0);\n    gl_check_error();\n  }\n\n  if(glsl.has_fbo)\n  {\n    // Select the screen framebuffer to draw the scaled screen to.\n    glsl.glBindFramebuffer(GL_FRAMEBUFFER, 0);\n    gl_check_error();\n  }\n\n  glsl.glClear(GL_COLOR_BUFFER_BIT);\n  gl_check_error();\n\n  glsl.glEnableVertexAttribArray(ATTRIB_POSITION);\n  glsl.glEnableVertexAttribArray(ATTRIB_TEXCOORD);\n\n  glsl.glVertexAttribPointer(ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0,\n   vertex_array_single);\n  gl_check_error();\n\n  {\n    const float width_f = width / (1.0f * GL_POWER_2_WIDTH);\n    const float height_f = height / (1.0f * GL_POWER_2_HEIGHT);\n    const float tex_coord_array_single_hardware[2 * 4] =\n    {\n      0.0f,     height_f,\n      0.0f,     0.0f,\n      width_f,  height_f,\n      width_f,  0.0f,\n    };\n    // The software rendered origin is at the top (rather than the bottom).\n    const float tex_coord_array_single_software[2 * 4] =\n    {\n      0.0f,     0.0f,\n      0.0f,     height_f,\n      width_f,  0.0f,\n      width_f,  height_f,\n    };\n    const float *tex_coord_array_single = render_data->use_software_renderer ?\n     tex_coord_array_single_software : tex_coord_array_single_hardware;\n\n    glsl.glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 0,\n     tex_coord_array_single);\n    gl_check_error();\n\n    glsl.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n    gl_check_error();\n  }\n\n  glsl.glDisableVertexAttribArray(ATTRIB_POSITION);\n  glsl.glDisableVertexAttribArray(ATTRIB_TEXCOORD);\n\n  if(!render_data->use_software_renderer)\n  {\n    glsl.glBindTexture(GL_TEXTURE_2D, render_data->textures[TEX_DATA_ID]);\n    gl_check_error();\n  }\n\n  gl_swap_buffers(graphics);\n  render_data->dirty_palette = true;\n  render_data->dirty_indices = true;\n\n  if(glsl.has_fbo)\n  {\n    /* Switch back to the texture framebuffer. This needs to be done after\n     * gl_swap_buffers or the screen won't draw properly for some drivers.\n     * The SDL documentation cites macOS, but MegaZeux bugs were encountered\n     * using the Intel HD Graphics 2500 driver in Windows too.\n     */\n    glsl.glBindFramebuffer(GL_FRAMEBUFFER, render_data->fbos[FBO_SCREEN_TEX]);\n    gl_check_error();\n  }\n\n  /**\n   * Clear the framebuffer so elements of this frame don't bleed through.\n   * When FBOs are not available, the next window frame needs to be cleared\n   * since the screen will be temporarily drawn there before scaling. This call\n   * was previously disabled for Emscripten.\n   */\n  glsl.glClear(GL_COLOR_BUFFER_BIT);\n  gl_check_error();\n}\n\nstatic boolean glsl_switch_shader(struct graphics_data *graphics,\n const char *name)\n{\n  struct glsl_render_data *render_data = graphics->render_data;\n  int index = GLSL_SHADER_SCALER_VERT - GLSL_SHADER_RES_FIRST;\n\n  free(source_cache[index]);\n  source_cache[index] = NULL;\n\n  index = GLSL_SHADER_SCALER_FRAG - GLSL_SHADER_RES_FIRST;\n  free(source_cache[index]);\n  source_cache[index] = NULL;\n\n  glsl_set_scaling_shader(graphics, name);\n\n  glsl.glDeleteProgram(render_data->scaler_program);\n  gl_check_error();\n\n  /**\n   * If the user-specified shader fails to load, this will fall back to the\n   * default scaling shader.\n   */\n  render_data->scaler_program = glsl_load_program(graphics,\n   GLSL_SHADER_SCALER_VERT, GLSL_SHADER_SCALER_FRAG);\n  if(render_data->scaler_program)\n  {\n    glsl.glBindAttribLocation(render_data->scaler_program,\n     ATTRIB_POSITION, \"Position\");\n    glsl.glBindAttribLocation(render_data->scaler_program,\n     ATTRIB_TEXCOORD, \"Texcoord\");\n    glsl.glLinkProgram(render_data->scaler_program);\n    glsl_verify_link(render_data, render_data->scaler_program);\n    glsl_delete_shaders(render_data->scaler_program);\n  }\n  return !glsl_scaling_shader_is_default(graphics);\n}\n\nvoid render_glsl_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = glsl_init_video;\n  renderer->free_video = glsl_free_video;\n  renderer->create_window = glsl_create_window;\n  renderer->resize_window = gl_resize_window;\n  renderer->resize_callback = glsl_resize_callback;\n  renderer->set_viewport = set_window_viewport_scaled;\n  renderer->set_window_caption = gl_set_window_caption;\n  renderer->set_window_icon = gl_set_window_icon;\n  renderer->update_colors = glsl_update_colors;\n  renderer->remap_char_range = glsl_remap_char_range;\n  renderer->remap_char = glsl_remap_char;\n  renderer->remap_charbyte = glsl_remap_charbyte;\n  renderer->render_layer = glsl_render_layer;\n  renderer->render_cursor = glsl_render_cursor;\n  renderer->render_mouse = glsl_render_mouse;\n  renderer->sync_screen = glsl_sync_screen;\n  renderer->switch_shader = glsl_switch_shader;\n}\n\nvoid render_glsl_software_register(struct renderer *renderer)\n{\n  render_glsl_register(renderer);\n  renderer->create_window = glsl_software_create_window;\n}\n\nvoid render_auto_glsl_register(struct renderer *renderer)\n{\n  render_glsl_register(renderer);\n  renderer->create_window = glsl_auto_create_window;\n}\n"
  },
  {
    "path": "src/render_gp2x.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"SDLmzx.h\"\n#include \"platform.h\"\n#include \"graphics.h\"\n#include \"render.h\"\n#include \"render_sdl.h\"\n#include \"renderers.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\nstruct gp2x_render_data\n{\n  struct sdl_render_data sdl;\n  uint32_t halfmask;\n  uint16_t buffer[320*350];\n};\n\nstatic SDL_Surface *\ngp2x_get_screen_surface(struct gp2x_render_data *render_data)\n{\n  return render_data->sdl.shadow ?\n   render_data->sdl.shadow : render_data->sdl.screen;\n}\n\nstatic void gp2x_set_colors_mzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg)\n{\n  struct gp2x_render_data *render_data = graphics->render_data;\n  uint32_t cb_bg, cb_fg, cb_mx;\n\n  cb_bg = graphics->flat_intensity_palette[bg];\n  cb_fg = graphics->flat_intensity_palette[fg];\n  if(cb_bg == cb_fg)\n    cb_mx = cb_bg;\n  else\n    cb_mx = ((cb_bg >> 1) & render_data->halfmask) +\n     ((cb_fg >> 1) & render_data->halfmask);\n\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n  char_colors[0] = (cb_bg << 16) | cb_bg;\n  char_colors[1] = (cb_bg << 16) | cb_mx;\n  char_colors[2] = (cb_bg << 16) | cb_mx;\n  char_colors[3] = (cb_bg << 16) | cb_fg;\n  char_colors[4] = (cb_mx << 16) | cb_bg;\n  char_colors[5] = (cb_mx << 16) | cb_mx;\n  char_colors[6] = (cb_mx << 16) | cb_mx;\n  char_colors[7] = (cb_mx << 16) | cb_fg;\n  char_colors[8] = (cb_mx << 16) | cb_bg;\n  char_colors[9] = (cb_mx << 16) | cb_mx;\n  char_colors[10] = (cb_mx << 16) | cb_mx;\n  char_colors[11] = (cb_mx << 16) | cb_fg;\n  char_colors[12] = (cb_fg << 16) | cb_bg;\n  char_colors[13] = (cb_fg << 16) | cb_mx;\n  char_colors[14] = (cb_fg << 16) | cb_mx;\n  char_colors[15] = (cb_fg << 16) | cb_fg;\n#else\n  char_colors[0] = (cb_bg << 16) | cb_bg;\n  char_colors[1] = (cb_mx << 16) | cb_bg;\n  char_colors[2] = (cb_mx << 16) | cb_bg;\n  char_colors[3] = (cb_fg << 16) | cb_bg;\n  char_colors[4] = (cb_bg << 16) | cb_mx;\n  char_colors[5] = (cb_mx << 16) | cb_mx;\n  char_colors[6] = (cb_mx << 16) | cb_mx;\n  char_colors[7] = (cb_fg << 16) | cb_mx;\n  char_colors[8] = (cb_bg << 16) | cb_mx;\n  char_colors[9] = (cb_mx << 16) | cb_mx;\n  char_colors[10] = (cb_mx << 16) | cb_mx;\n  char_colors[11] = (cb_fg << 16) | cb_mx;\n  char_colors[12] = (cb_bg << 16) | cb_fg;\n  char_colors[13] = (cb_mx << 16) | cb_fg;\n  char_colors[14] = (cb_mx << 16) | cb_fg;\n  char_colors[15] = (cb_fg << 16) | cb_fg;\n#endif\n}\n\nstatic void gp2x_set_colors_smzx(const struct graphics_data *graphics,\n uint32_t * RESTRICT char_colors, uint8_t bg, uint8_t fg)\n{\n  const uint8_t *indices = graphics->smzx_indices;\n  uint32_t bb, bf, fb, ff;\n  bg &= 0x0F;\n  fg &= 0x0F;\n  indices += ((bg << 4) | fg) << 2;\n\n  bb = graphics->flat_intensity_palette[indices[0]];\n  bf = graphics->flat_intensity_palette[indices[1]];\n  fb = graphics->flat_intensity_palette[indices[2]];\n  ff = graphics->flat_intensity_palette[indices[3]];\n\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n  char_colors[0] = (bb << 16) | bb;\n  char_colors[1] = (bb << 16) | bf;\n  char_colors[2] = (bb << 16) | fb;\n  char_colors[3] = (bb << 16) | ff;\n  char_colors[4] = (bf << 16) | bb;\n  char_colors[5] = (bf << 16) | bf;\n  char_colors[6] = (bf << 16) | fb;\n  char_colors[7] = (bf << 16) | ff;\n  char_colors[8] = (fb << 16) | bb;\n  char_colors[9] = (fb << 16) | bf;\n  char_colors[10] = (fb << 16) | fb;\n  char_colors[11] = (fb << 16) | ff;\n  char_colors[12] = (ff << 16) | bb;\n  char_colors[13] = (ff << 16) | bf;\n  char_colors[14] = (ff << 16) | fb;\n  char_colors[15] = (ff << 16) | ff;\n#else\n  char_colors[0] = (bb << 16) | bb;\n  char_colors[1] = (bf << 16) | bb;\n  char_colors[2] = (fb << 16) | bb;\n  char_colors[3] = (ff << 16) | bb;\n  char_colors[4] = (bb << 16) | bf;\n  char_colors[5] = (bf << 16) | bf;\n  char_colors[6] = (fb << 16) | bf;\n  char_colors[7] = (ff << 16) | bf;\n  char_colors[8] = (bb << 16) | fb;\n  char_colors[9] = (bf << 16) | fb;\n  char_colors[10] = (fb << 16) | fb;\n  char_colors[11] = (ff << 16) | fb;\n  char_colors[12] = (bb << 16) | ff;\n  char_colors[13] = (bf << 16) | ff;\n  char_colors[14] = (fb << 16) | ff;\n  char_colors[15] = (ff << 16) | ff;\n#endif\n}\n\nstatic const set_colors_function gp2x_set_colors[4] =\n{\n  gp2x_set_colors_mzx,\n  gp2x_set_colors_smzx,\n  gp2x_set_colors_smzx,\n  gp2x_set_colors_smzx\n};\n\nstatic boolean gp2x_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  struct gp2x_render_data *render_data =\n   (struct gp2x_render_data *)cmalloc(sizeof(struct gp2x_render_data));\n\n  if(!render_data)\n    return false;\n\n  memset(render_data, 0, sizeof(struct gp2x_render_data));\n  graphics->render_data = render_data;\n\n  graphics->allow_resize = 0;\n\n  graphics->bits_per_pixel = 16;\n\n  graphics->resolution_width = 320;\n  graphics->resolution_height = 240;\n  graphics->window_width = 320;\n  graphics->window_height = 240;\n  return true;\n}\n\nstatic void gp2x_free_video(struct graphics_data *graphics)\n{\n  sdl_destruct_window(graphics);\n\n  free(graphics->render_data);\n  graphics->render_data = NULL;\n}\n\nstatic boolean gp2x_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct gp2x_render_data *render_data = graphics->render_data;\n  const SDL_PixelFormatDetails *format;\n  uint32_t halfmask;\n\n  if(!sdl_create_window_soft(graphics, window))\n    return false;\n\n  format = render_data->sdl.flat_format;\n  halfmask = (format->Rmask >> 1) & format->Rmask;\n  halfmask |= (format->Gmask >> 1) & format->Gmask;\n  halfmask |= (format->Bmask >> 1) & format->Bmask;\n  render_data->halfmask = halfmask;\n\n  return true;\n}\n\nstatic void gp2x_set_window_viewport(struct graphics_data *graphics,\n struct video_window *window)\n{\n  window->viewport_x = 0;\n  window->viewport_y = 0;\n  window->viewport_width = 320;\n  window->viewport_height = 240;\n  window->is_integer_scaled = false;\n}\n\n#if 0\nstatic void gp2x_get_screen_coords(struct graphics_data *graphics,\n int screen_x, int screen_y, int *x, int *y, int *min_x, int *min_y,\n int *max_x, int *max_y)\n{\n  *x = screen_x * 2;\n  *y = screen_y * 35 / 24;\n  *min_x = 0;\n  *min_y = 0;\n  *max_x = 319;\n  *max_y = 239;\n}\n\nstatic void gp2x_set_screen_coords(struct graphics_data *graphics,\n int x, int y, int *screen_x, int *screen_y)\n{\n  *screen_x = x / 2;\n  *screen_y = y * 24 / 35;\n}\n#endif\n\nstatic void gp2x_render_graph(struct graphics_data *graphics)\n{\n  struct gp2x_render_data *render_data = graphics->render_data;\n  render_graph8((uint8_t *)render_data->buffer, 640, graphics,\n   gp2x_set_colors[graphics->screen_mode]);\n}\n\nstatic void gp2x_render_cursor(struct graphics_data *graphics, unsigned int x,\n unsigned int y, uint16_t color, unsigned int lines, unsigned int offset)\n{\n  struct gp2x_render_data *render_data = graphics->render_data;\n  uint32_t flatcolor = graphics->flat_intensity_palette[color];\n  flatcolor |= flatcolor << 16;\n  render_cursor((uint32_t *)render_data->buffer, 640, 8, x, y, flatcolor, lines,\n   offset);\n}\n\nstatic void gp2x_render_mouse(struct graphics_data *graphics,\n unsigned int x, unsigned int y, unsigned int w, unsigned int h)\n{\n  struct gp2x_render_data *render_data = graphics->render_data;\n  render_mouse((uint32_t *)render_data->buffer, 640, 8, x, y, 0xFFFFFFFF,\n   0x0, w, h);\n}\n\nstatic void gp2x_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct gp2x_render_data *render_data = graphics->render_data;\n  SDL_Surface *screen = gp2x_get_screen_surface(render_data);\n\n  unsigned int line_advance = screen->pitch / 2;\n  uint16_t *dest = (uint16_t *)screen->pixels;\n  uint16_t *src = render_data->buffer;\n  unsigned int i, j, skip = 0;\n\n  SDL_LockSurface(screen);\n  for(i = 0; i < 240; i++)\n  {\n    skip += 35 - 24;\n    if(skip < 24)\n      memcpy(dest, src, sizeof(uint16_t) * 320);\n    else\n    {\n      for(j = 0; j < 320; j++)\n      {\n        if(src[j] == src[j+320])\n          dest[j] = src[j];\n        else\n          dest[j] = ((src[j] >> 1) & render_data->halfmask) +\n           ((src[j+320] >> 1) & render_data->halfmask);\n      }\n      skip -= 24;\n      src += 320;\n    }\n    src += 320;\n    dest += line_advance;\n  }\n\n  SDL_UnlockSurface(screen);\n\n  if(render_data->sdl.shadow)\n  {\n    SDL_Rect src_rect;\n    SDL_Rect dest_rect;\n    SDL_GetSurfaceClipRect(render_data->sdl.shadow, &src_rect);\n    SDL_GetSurfaceClipRect(render_data->sdl.screen, &dest_rect);\n    SDL_BlitSurface(render_data->sdl.shadow, &src_rect,\n     render_data->sdl.screen, &dest_rect);\n  }\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n  SDL_UpdateWindowSurface(render_data->sdl.window);\n#else\n  SDL_Flip(render_data->sdl.screen);\n#endif\n}\n\nvoid render_gp2x_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = gp2x_init_video;\n  renderer->free_video = gp2x_free_video;\n  renderer->create_window = gp2x_create_window;\n  renderer->resize_window = gp2x_create_window;\n  renderer->set_viewport = gp2x_set_window_viewport;\n  renderer->set_window_caption = sdl_set_window_caption;\n  renderer->set_window_icon = sdl_set_window_icon;\n  renderer->update_colors = sdl_update_colors;\n  renderer->render_graph = gp2x_render_graph;\n  renderer->render_cursor = gp2x_render_cursor;\n  renderer->render_mouse = gp2x_render_mouse;\n  renderer->sync_screen = gp2x_sync_screen;\n}\n"
  },
  {
    "path": "src/render_layer.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n * Copyright (C) 2024-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <assert.h>\n\n#include \"graphics.h\"\n#include \"platform_endian.h\"\n#include \"render_layer.h\"\n\n// Skip unused variants to reduce compile time on these platforms.\n#if defined(CONFIG_WII) || defined(ANDROID) || defined(__EMSCRIPTEN__) || \\\n defined(CONFIG_PSVITA)\n#define SKIP_8BPP\n#define SKIP_16BPP\n#endif\n\n#if defined(CONFIG_3DS)\n#define SKIP_8BPP\n#if !defined(CONFIG_SDL)\n#define SKIP_16BPP\n#endif\n#endif\n\n#if defined(CONFIG_DJGPP) && !defined(CONFIG_DOS_SVGA)\n#define SKIP_8BPP\n#define SKIP_16BPP\n#endif\n\n#if defined(CONFIG_DREAMCAST)\n#define SKIP_8BPP\n#endif\n\n#include \"render_layer_code.hpp\"\n\n#ifdef BUILD_REFERENCE_RENDERER\n// This layer renderer is very slow, but it should work properly.\n// The renderers in render_layer_code.hpp should generally be used instead.\n// It might be useful to build for tests or benchmarking, though.\n\nstatic inline void reference_renderer(uint32_t * RESTRICT pixels,\n int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer)\n{\n  unsigned int mode = layer->mode;\n\n  unsigned int ch_x, ch_y;\n  uint16_t c;\n\n  const struct char_element *src = layer->data;\n  int row, col;\n  int tcol = layer->transparent_col;\n\n  const uint8_t *char_ptr;\n  uint8_t current_char_byte;\n\n  uint32_t *drawPtr;\n  uint32_t pix;\n  int pix_pos;\n  //size_t advance = (pitch / 4) - 8;\n\n  int x, y, i;\n\n  uint32_t char_colors[4];\n  int char_idx[4];\n  int idx;\n\n  unsigned int protected_pal_position = graphics->protected_pal_position;\n\n  for(ch_y = 0; ch_y < layer->h; ch_y++)\n  {\n    for(ch_x = 0; ch_x < layer->w; ch_x++)\n    {\n      c = src->char_value;\n      if(c != INVISIBLE_CHAR)\n      {\n        // Char values of 256+, prior to offsetting, are from the protected set\n        if(c > 0xFF)\n        {\n          c = (c & 0xFF) + PROTECTED_CHARSET_POSITION;\n        }\n        else\n        {\n          c += layer->offset;\n          c %= PROTECTED_CHARSET_POSITION;\n        }\n\n        if(mode)\n        {\n          for(i = 0; i < 4; i++)\n          {\n            idx = ((src->bg_color & 0xF) << 4 | (src->fg_color & 0xF)) * 4 + i;\n            char_idx[i] = graphics->smzx_indices[idx];\n            char_colors[i] = graphics->flat_intensity_palette[char_idx[i]];\n          }\n        }\n        else\n        {\n          if(src->bg_color >= 16)\n            char_idx[0] = (src->bg_color - 16) % 16 + protected_pal_position;\n          else\n            char_idx[0] = src->bg_color;\n\n          if(src->fg_color >= 16)\n            char_idx[1] = (src->fg_color - 16) % 16 + protected_pal_position;\n          else\n            char_idx[1] = src->fg_color;\n\n          for(i = 0; i < 2; i++)\n            char_colors[i] = graphics->flat_intensity_palette[char_idx[i]];\n        }\n\n        char_ptr = graphics->charset + (c * CHAR_H);\n        for(row = 0; row < CHAR_H; row++)\n        {\n          current_char_byte = *char_ptr;\n          if(mode == 0)\n          {\n            for(col = 0; col < CHAR_W; col++)\n            {\n              y = layer->y + ch_y * 14 + row;\n              x = layer->x + ch_x * 8 + col;\n\n              if(x >= 0 && x < width_px && y >= 0 && y < height_px)\n              {\n                drawPtr = pixels + (pitch / sizeof(uint32_t)) * y + x;\n\n                pix_pos = (current_char_byte & (0x80 >> col)) << col >> 7;\n\n                if(char_idx[pix_pos] != tcol)\n                  *drawPtr = char_colors[pix_pos];\n              }\n            }\n          }\n          else\n          {\n            for(col = 0; col < CHAR_W; col+=2)\n            {\n              y = layer->y + ch_y * 14 + row;\n              x = layer->x + ch_x * 8 + col;\n\n              if(x >= -1 && x < width_px && y >= 0 && y < height_px)\n              {\n                drawPtr = pixels + (pitch / sizeof(uint32_t)) * y + x;\n\n                pix_pos = (current_char_byte & (0xC0 >> col)) << col >> 6;\n\n                if(char_idx[pix_pos] != tcol)\n                {\n                  pix = char_colors[pix_pos];\n                  if(x >= 0)\n                    *drawPtr = pix;\n                  if(x < width_px - 1)\n                    *(++drawPtr) = pix;\n                }\n              }\n            }\n          }\n          char_ptr++;\n        }\n      }\n      src++;\n    }\n  }\n}\n#endif\n\n/* Return the alignment of a particular value (typically pixel array address).\n * Multiple addresses can be ORed together to get the lowest-common alignment\n * for multiple inputs. This function should not take architecture unalignment\n * capabilities or vector rendering into consideration.\n */\nstatic size_t get_align_for_offset(size_t value)\n{\n#ifndef SKIP_64_ALIGN\n  if(value % sizeof(uint64_t) == 0)\n  {\n#if ARCHITECTURE_BITS < 64\n    static boolean printed = false;\n    if(!printed)\n    {\n      warn(\"ARCHITECTURE_BITS not configured for this arch -- report this\\n\");\n      printed = true;\n    }\n    return ARCHITECTURE_BITS;\n#endif\n    return 64;\n  }\n  else\n#endif\n\n  if(value % sizeof(uint32_t) == 0)\n  {\n    return 32;\n  }\n  else\n\n  if(value % sizeof(uint16_t) == 0)\n  {\n    return 16;\n  }\n  else\n    return 8;\n}\n\n/* Select the safest renderer given the alignment of the pixel\n * array, row pitch, and size_t. This function should not take architecture\n * alignment capabilities or vector rendering into consideration.\n */\nstatic inline void select_aligned_renderer(const void *pixels,\n size_t width_px, size_t height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer,\n int &bpp, int &align, int &smzx, int &trans, int &clip)\n{\n  smzx = layer->mode;\n  trans = layer->transparent_col != -1;\n  clip = 0;\n\n  if(layer->x < 0 || layer->y < 0 ||\n   (layer->x + layer->w * CHAR_W) > width_px ||\n   (layer->y + layer->h * CHAR_H) > height_px)\n    clip = 1;\n\n  if(bpp == -1)\n    bpp = graphics->bits_per_pixel;\n\n  size_t drawStart =\n   (size_t)((char *)pixels + layer->y * (ptrdiff_t)pitch + (layer->x * bpp / 8));\n\n  /**\n   * Select the highest pixel align the current platform is capable of.\n   * Additionally, to simplify the renderer code, the align must also be\n   * capable of addressing the first pixel of the draw (e.g. a 64-bit platform\n   * will use a 32-bit align if the layer is on an odd horizontal pixel).\n   * This must be true for the first pixel of every character row drawn, so\n   * the pitch must be in alignment too.\n   *\n   * In other words, the alignment is the minimum of:\n   *   the number of bits of size_t (rough approximation of arch width),\n   *   the number of bits of the first pixel's alignment in memory,\n   *   the number of bits of the pitch's alignment (for rows after the first).\n   */\n  align = get_align_for_offset(sizeof(size_t) | drawStart | pitch);\n}\n\n/* Override selected alignment for platforms that support fast unaligned.\n */\nstatic inline void select_unaligned_renderer(int &align)\n{\n#if defined(PLATFORM_UNALIGN_64)\n  if(align >= PLATFORM_UNALIGN_64 * 8)\n    align = 64;\n#elif defined(PLATFORM_UNALIGN_32)\n  if(align >= PLATFORM_UNALIGN_32 * 8 && align < 32)\n    align = 32;\n#endif\n}\n\nvoid render_layer(void * RESTRICT pixels,\n size_t width_px, size_t height_px, size_t pitch, int bpp,\n const struct graphics_data *graphics, const struct video_layer *layer)\n{\n#if defined(BUILD_REFERENCE_RENDERER) && !defined(MZX_UNIT_TESTS)\n  reference_renderer(reinterpret_cast<uint32_t * RESTRICT>(pixels),\n   width_px, height_px, pitch, graphics, layer);\n  return;\n#endif\n\n  int smzx = 0;\n  int trans = 0;\n  int align = 0;\n  int clip = 0;\n  select_aligned_renderer(pixels, width_px, height_px, pitch, graphics, layer,\n   bpp, align, smzx, trans, clip);\n\n  select_unaligned_renderer(align);\n\n  render_layer_func(pixels, width_px, height_px, pitch, graphics, layer,\n   bpp, align, smzx, trans, clip);\n}\n"
  },
  {
    "path": "src/render_layer.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __RENDER_LAYER_H\n#define __RENDER_LAYER_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"graphics.h\"\n\nvoid render_layer(void * RESTRICT pixels,\n size_t width_px, size_t height_px, size_t pitch, int bpp,\n const struct graphics_data *graphics, const struct video_layer *layer);\n\n__M_END_DECLS\n\n#endif // __RENDER_LAYER_H\n"
  },
  {
    "path": "src/render_layer_code.hpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n * Copyright (C) 2020, 2024-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// This file recursively instantiates template functions to generate many\n// versions of the render_layer_func function. The right function to call is\n// selected through a dispatch mechanism.\n\n// Errors and warnings in this file are duplicated hundreds of times over, so\n// when working on this function it can be good to redirect make's stderr\n// stream to a file.\n\n#include \"graphics.h\"\n#include \"platform_attribute.h\"\n#include \"platform_endian.h\"\n#include \"render_layer_common.hpp\"\n#include \"util.h\"\n\n#include <stdlib.h>\n\n#ifdef IS_CXX_11\n#include <type_traits>\n#endif\n\n#ifdef _MSC_VER\n#include <cstdio>\n#endif\n\n#if defined(PLATFORM_UNALIGN_32) || defined(PLATFORM_UNALIGN_64)\n/* May be using intentional unaligned accesses; tell UBSan to calm down. */\n#define RENDER_EXTRA_ATTRIBUTES ATTRIBUTE_NO_SANITIZE(\"alignment\")\n#else\n#define RENDER_EXTRA_ATTRIBUTES\n#endif\n\ntemplate<typename PIXTYPE>\nstatic void render_layer_func(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer,\n int align, int smzx, int trans, int clip);\n\ntemplate<typename PIXTYPE, typename ALIGNTYPE>\nstatic void render_layer_func(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer,\n int smzx, int trans, int clip);\n\ntemplate<typename PIXTYPE, typename ALIGNTYPE, int SMZX>\nstatic void render_layer_func(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer,\n int trans, int clip);\n\ntemplate<typename PIXTYPE, typename ALIGNTYPE, int SMZX, int TR>\nstatic void render_layer_func(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer,\n int clip);\n\ntemplate<typename PIXTYPE, typename ALIGNTYPE, int SMZX, int TR, int CLIP>\nstatic void render_layer_func(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer)\n RENDER_EXTRA_ATTRIBUTES;\n\n/**\n * Alignment of pixel buffer (8bpp).\n * This always must be >= the current renderer's bits-per-pixel.\n */\ntemplate<>\ninline void render_layer_func<uint8_t>(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer,\n int align, int smzx, int trans, int clip)\n{\n  switch(align)\n  {\n#if ARCHITECTURE_BITS >= 64 && !defined(SKIP_64_ALIGN)\n    case 64:\n      render_layer_func<uint8_t, uint64_t>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       smzx, trans, clip);\n      break;\n#endif /* ARCHITECTURE_BITS >= 64 */\n\n    case 32:\n      render_layer_func<uint8_t, uint32_t>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       smzx, trans, clip);\n      break;\n\n    case 16:\n      render_layer_func<uint8_t, uint16_t>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       smzx, trans, clip);\n      break;\n\n    case 8:\n      render_layer_func<uint8_t, uint8_t>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       smzx, trans, clip);\n      break;\n\n    default:\n      fprintf(mzxerr, \"INVALID RENDERER ARG align=%d (8bpp)\\n\", align);\n      exit(1);\n      break;\n  }\n}\n\n/**\n * Alignment of pixel buffer (16bpp).\n * This always must be >= the current renderer's bits-per-pixel.\n */\ntemplate<>\ninline void render_layer_func<uint16_t>(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer,\n int align, int smzx, int trans, int clip)\n{\n  switch(align)\n  {\n#if ARCHITECTURE_BITS >= 64 && !defined(SKIP_64_ALIGN)\n    case 64:\n      render_layer_func<uint16_t, uint64_t>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       smzx, trans, clip);\n      break;\n#endif /* ARCHITECTURE_BITS >= 64 */\n\n    case 32:\n      render_layer_func<uint16_t, uint32_t>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       smzx, trans, clip);\n      break;\n\n    case 16:\n      render_layer_func<uint16_t, uint16_t>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       smzx, trans, clip);\n      break;\n\n    default:\n      fprintf(mzxerr, \"INVALID RENDERER ARG align=%d (16bpp)\\n\", align);\n      exit(1);\n      break;\n  }\n}\n\n/**\n * Alignment of pixel buffer (32bpp).\n * This always must be >= the current renderer's bits-per-pixel.\n */\ntemplate<>\ninline void render_layer_func<uint32_t>(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer,\n int align, int smzx, int trans, int clip)\n{\n  switch(align)\n  {\n#if ARCHITECTURE_BITS >= 64 && !defined(SKIP_64_ALIGN)\n    case 64:\n      render_layer_func<uint32_t, uint64_t>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       smzx, trans, clip);\n      break;\n#endif /* ARCHITECTURE_BITS >= 64 */\n\n    case 32:\n      render_layer_func<uint32_t, uint32_t>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       smzx, trans, clip);\n      break;\n\n    default:\n      fprintf(mzxerr, \"INVALID RENDERER ARG align=%d (32bpp)\\n\", align);\n      exit(1);\n      break;\n  }\n}\n\n/**\n * Renderer bits-per-pixel (8, 16, or 32).\n * Sets of renderers for lower bpps are larger since they try to support more\n * alignment options, so several platforms disable them altogether to reduce\n * executable size and/or compilation time.\n */\nstatic inline void render_layer_func(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer,\n int bpp, int align, int smzx, int trans, int clip)\n{\n  switch(bpp)\n  {\n#ifndef SKIP_8BPP\n    case 8:\n      render_layer_func<uint8_t>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       align, smzx, trans, clip);\n      break;\n#endif\n#ifndef SKIP_16BPP\n    case 16:\n      render_layer_func<uint16_t>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       align, smzx, trans, clip);\n      break;\n#endif\n#ifndef SKIP_32BPP\n    case 32:\n      render_layer_func<uint32_t>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       align, smzx, trans, clip);\n      break;\n#endif\n    default:\n      fprintf(mzxerr, \"INVALID RENDERER ARG bpp=%d\\n\"\n       \"(is this bpp enabled for this platform?)\\n\", bpp);\n      exit(1);\n      break;\n  }\n}\n\n/**\n * Renderer is SMZX (1) or normal MZX (0).\n */\ntemplate<typename PIXTYPE, typename ALIGNTYPE>\nstatic inline void render_layer_func(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer,\n int smzx, int trans, int clip)\n{\n  switch(smzx)\n  {\n    case 0:\n      render_layer_func<PIXTYPE, ALIGNTYPE, 0>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       trans, clip);\n      break;\n\n    case 1:\n    case 2:\n    case 3:\n      render_layer_func<PIXTYPE, ALIGNTYPE, 1>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       trans, clip);\n      break;\n\n    default:\n      fprintf(mzxerr, \"INVALID RENDERER ARG smzx=%d\\n\", smzx);\n      exit(1);\n      break;\n  }\n}\n\n/**\n * Layer transparency enabled (1) or disabled (0).\n */\ntemplate<typename PIXTYPE, typename ALIGNTYPE, int SMZX>\nstatic inline void render_layer_func(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer,\n int trans, int clip)\n{\n  switch(trans)\n  {\n    case 0:\n      render_layer_func<PIXTYPE, ALIGNTYPE, SMZX, 0>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       clip);\n      break;\n\n    case 1:\n      render_layer_func<PIXTYPE, ALIGNTYPE, SMZX, 1>(\n       pixels, width_px, height_px, pitch, graphics, layer,\n       clip);\n      break;\n\n    default:\n      fprintf(mzxerr, \"INVALID RENDERER ARG trans=%d\\n\", trans);\n      exit(1);\n      break;\n  }\n}\n\n/**\n * Renderer should clip the layer at the screen boundaries (1) or not (0).\n */\ntemplate<typename PIXTYPE, typename ALIGNTYPE, int SMZX, int TR>\nstatic inline void render_layer_func(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer,\n int clip)\n{\n  switch(clip)\n  {\n    case 0:\n      render_layer_func<PIXTYPE, ALIGNTYPE, SMZX, TR, 0>(\n       pixels, width_px, height_px, pitch, graphics, layer);\n      break;\n\n    case 1:\n      render_layer_func<PIXTYPE, ALIGNTYPE, SMZX, TR, 1>(\n       pixels, width_px, height_px, pitch, graphics, layer);\n      break;\n\n    default:\n      fprintf(mzxerr, \"INVALID RENDERER ARG clip=%d\\n\", clip);\n      exit(1);\n      break;\n  }\n}\n\n// Macros to perform these shifts while ignoring spurious compiler warnings\n// that can't be turned off for older versions of GCC. Since BPP is constexpr\n// these should all optimize to single shifts.\n#define BPPx1(c) (c << (BPP - 1) << 1)\n#define BPPx2(c) (c << (BPP - 1) << (BPP - 1) << 2)\n#define BPPx3(c) (c << (BPP - 1) << (BPP - 1) << (BPP - 1) << 3)\n\n/**\n * Precompute an array of color combinations for multipixel write renderers.\n *\n * Since this requires 1<<PPW values to be computed, 8 PPW renderers would\n * need 256 combinations, which would probably hurt performance more than help\n * it. Instead, 8 PPW just uses a special case of the 4 PPW code (see below).\n */\ntemplate<int BPP, int PPW, typename ALIGNTYPE>\nstatic inline void set_colors_mzx(ALIGNTYPE (&dest)[16], ALIGNTYPE bg, ALIGNTYPE fg)\n{\n#if PLATFORM_BYTE_ORDER == PLATFORM_LIL_ENDIAN\n  switch(PPW)\n  {\n    case 1:\n      break;\n\n    case 2:\n      dest[0] = BPPx1(bg) | bg;\n      dest[1] = BPPx1(fg) | bg;\n      dest[2] = BPPx1(bg) | fg;\n      dest[3] = BPPx1(fg) | fg;\n      break;\n\n    case 4:\n    case 8:\n      dest[0]  = BPPx3(bg) | BPPx2(bg) | BPPx1(bg) | bg;\n      dest[1]  = BPPx3(fg) | BPPx2(bg) | BPPx1(bg) | bg;\n      dest[2]  = BPPx3(bg) | BPPx2(fg) | BPPx1(bg) | bg;\n      dest[3]  = BPPx3(fg) | BPPx2(fg) | BPPx1(bg) | bg;\n      dest[4]  = BPPx3(bg) | BPPx2(bg) | BPPx1(fg) | bg;\n      dest[5]  = BPPx3(fg) | BPPx2(bg) | BPPx1(fg) | bg;\n      dest[6]  = BPPx3(bg) | BPPx2(fg) | BPPx1(fg) | bg;\n      dest[7]  = BPPx3(fg) | BPPx2(fg) | BPPx1(fg) | bg;\n      dest[8]  = BPPx3(bg) | BPPx2(bg) | BPPx1(bg) | fg;\n      dest[9]  = BPPx3(fg) | BPPx2(bg) | BPPx1(bg) | fg;\n      dest[10] = BPPx3(bg) | BPPx2(fg) | BPPx1(bg) | fg;\n      dest[11] = BPPx3(fg) | BPPx2(fg) | BPPx1(bg) | fg;\n      dest[12] = BPPx3(bg) | BPPx2(bg) | BPPx1(fg) | fg;\n      dest[13] = BPPx3(fg) | BPPx2(bg) | BPPx1(fg) | fg;\n      dest[14] = BPPx3(bg) | BPPx2(fg) | BPPx1(fg) | fg;\n      dest[15] = BPPx3(fg) | BPPx2(fg) | BPPx1(fg) | fg;\n      break;\n  }\n#else\n  switch(PPW)\n  {\n    case 1:\n      break;\n\n    case 2:\n      dest[0] = BPPx1(bg) | bg;\n      dest[1] = BPPx1(bg) | fg;\n      dest[2] = BPPx1(fg) | bg;\n      dest[3] = BPPx1(fg) | fg;\n      break;\n\n    case 4:\n    case 8:\n      dest[0]  = BPPx3(bg) | BPPx2(bg) | BPPx1(bg) | bg;\n      dest[1]  = BPPx3(bg) | BPPx2(bg) | BPPx1(bg) | fg;\n      dest[2]  = BPPx3(bg) | BPPx2(bg) | BPPx1(fg) | bg;\n      dest[3]  = BPPx3(bg) | BPPx2(bg) | BPPx1(fg) | fg;\n      dest[4]  = BPPx3(bg) | BPPx2(fg) | BPPx1(bg) | bg;\n      dest[5]  = BPPx3(bg) | BPPx2(fg) | BPPx1(bg) | fg;\n      dest[6]  = BPPx3(bg) | BPPx2(fg) | BPPx1(fg) | bg;\n      dest[7]  = BPPx3(bg) | BPPx2(fg) | BPPx1(fg) | fg;\n      dest[8]  = BPPx3(fg) | BPPx2(bg) | BPPx1(bg) | bg;\n      dest[9]  = BPPx3(fg) | BPPx2(bg) | BPPx1(bg) | fg;\n      dest[10] = BPPx3(fg) | BPPx2(bg) | BPPx1(fg) | bg;\n      dest[11] = BPPx3(fg) | BPPx2(bg) | BPPx1(fg) | fg;\n      dest[12] = BPPx3(fg) | BPPx2(fg) | BPPx1(bg) | bg;\n      dest[13] = BPPx3(fg) | BPPx2(fg) | BPPx1(bg) | fg;\n      dest[14] = BPPx3(fg) | BPPx2(fg) | BPPx1(fg) | bg;\n      dest[15] = BPPx3(fg) | BPPx2(fg) | BPPx1(fg) | fg;\n      break;\n  }\n#endif\n}\n\n/* Colors should be pre-doubled here. */\ntemplate<int BPP, int PPW, typename ALIGNTYPE>\nstatic inline void set_colors_smzx(ALIGNTYPE (&dest)[16], ALIGNTYPE (&cols)[4])\n{\n  ALIGNTYPE c0 = cols[0];\n  ALIGNTYPE c1 = cols[1];\n  ALIGNTYPE c2 = cols[2];\n  ALIGNTYPE c3 = cols[3];\n  switch(PPW)\n  {\n    case 1:\n    case 2:\n      break;\n\n    case 4:\n    case 8:\n#if PLATFORM_BYTE_ORDER == PLATFORM_LIL_ENDIAN\n      dest[0]  = BPPx2(c0) | c0;\n      dest[1]  = BPPx2(c1) | c0;\n      dest[2]  = BPPx2(c2) | c0;\n      dest[3]  = BPPx2(c3) | c0;\n      dest[4]  = BPPx2(c0) | c1;\n      dest[5]  = BPPx2(c1) | c1;\n      dest[6]  = BPPx2(c2) | c1;\n      dest[7]  = BPPx2(c3) | c1;\n      dest[8]  = BPPx2(c0) | c2;\n      dest[9]  = BPPx2(c1) | c2;\n      dest[10] = BPPx2(c2) | c2;\n      dest[11] = BPPx2(c3) | c2;\n      dest[12] = BPPx2(c0) | c3;\n      dest[13] = BPPx2(c1) | c3;\n      dest[14] = BPPx2(c2) | c3;\n      dest[15] = BPPx2(c3) | c3;\n#else\n      dest[0]  = BPPx2(c0) | c0;\n      dest[1]  = BPPx2(c0) | c1;\n      dest[2]  = BPPx2(c0) | c2;\n      dest[3]  = BPPx2(c0) | c3;\n      dest[4]  = BPPx2(c1) | c0;\n      dest[5]  = BPPx2(c1) | c1;\n      dest[6]  = BPPx2(c1) | c2;\n      dest[7]  = BPPx2(c1) | c3;\n      dest[8]  = BPPx2(c2) | c0;\n      dest[9]  = BPPx2(c2) | c1;\n      dest[10] = BPPx2(c2) | c2;\n      dest[11] = BPPx2(c2) | c3;\n      dest[12] = BPPx2(c3) | c0;\n      dest[13] = BPPx2(c3) | c1;\n      dest[14] = BPPx2(c3) | c2;\n      dest[15] = BPPx2(c3) | c3;\n#endif\n      break;\n  }\n}\n\ntemplate<int PPW>\nstatic inline unsigned get_colors_index(unsigned char_byte, int write_pos)\n{\n  return ((char_byte << (write_pos * PPW)) & 0xff) >> (8 - PPW);\n}\n\n/**\n * Use the set_colors array to compute multiple pixel values simultaneously.\n * This optimization is only useful for PPW >= 2 renderers.\n */\ntemplate<int PPW, typename ALIGNTYPE>\nstatic inline ALIGNTYPE get_colors(ALIGNTYPE (&set_colors)[16], unsigned idx)\n{\n  switch(PPW)\n  {\n    // Should be unreachable, but some compilers complain...\n    case 1:\n      break;\n\n    case 2:\n    case 4:\n      return set_colors[idx];\n\n    case 8:\n    {\n      unsigned int left = (idx & 0xF0) >> 4;\n      unsigned int right = (idx & 0x0F);\n#if PLATFORM_BYTE_ORDER == PLATFORM_LIL_ENDIAN\n      return (set_colors[right] << sizeof(ALIGNTYPE) * 4) | set_colors[left];\n#else\n      return (set_colors[left] << sizeof(ALIGNTYPE) * 4) | set_colors[right];\n#endif\n    }\n  }\n  return 0;\n}\n\n#if PLATFORM_BYTE_ORDER == PLATFORM_LIL_ENDIAN\n#define PIXEL_POS(i)      (BPP * (PPW - 1 - (i)))\n#define PIXEL_POS_PAIR(i) (BPP * (PPW - 2 - (i)))\n#else\n#define PIXEL_POS(i)      (BPP * (i))\n#define PIXEL_POS_PAIR(i) (BPP * (i))\n#endif\n\n/**\n * Finally, render the layer.\n * The optimizer will optimize out the unnecessary parts for relevant renderers.\n */\ntemplate<typename PIXTYPE, typename ALIGNTYPE, int SMZX, int TR, int CLIP>\nstatic inline void render_layer_func(\n void * RESTRICT pixels, int width_px, int height_px, size_t pitch,\n const struct graphics_data *graphics, const struct video_layer *layer)\n{\n#ifdef IS_CXX_11\n  constexpr int BPP = sizeof(PIXTYPE) * 8;\n  constexpr int PPW = sizeof(ALIGNTYPE) / sizeof(PIXTYPE);\n\n  // Make sure some pointless/impossible renderers are never instantiated.\n  static_assert((PPW >= 1), \"invalid ppw < 1\");\n  static_assert((PPW <= 8), \"invalid ppw > 8\");\n  static_assert((PPW == 1) || (PPW == 2) || (PPW == 4) || (PPW == 8),\n   \"invalid ppw (must be power of 2)\");\n#else\n// Use these if for whatever reason C++11 isn't available.\n#define BPP (int)(sizeof(PIXTYPE) * 8)\n#define PPW (int)(sizeof(ALIGNTYPE) / sizeof(PIXTYPE))\n#endif\n\n#ifdef DEBUG\n  static boolean printed = false;\n  if(!printed)\n  {\n    fprintf(mzxerr, \"%s\\n\", __PRETTY_FUNCTION__);\n    fflush(mzxerr);\n    printed = true;\n  }\n#endif\n\n  int x, y;\n  uint16_t c;\n\n  const struct char_element *src = layer->data;\n  int row;\n\n  // Transparency vars...\n  int tcol = layer->transparent_col;\n  ALIGNTYPE mask = (PIXTYPE)(~0);\n  ALIGNTYPE smask = BPPx1(mask) | mask;\n\n  const uint8_t *char_ptr;\n  unsigned int current_char_byte;\n  unsigned int pcol;\n  unsigned idx;\n\n  ALIGNTYPE pix;\n\n  int i;\n\n  // This array will actually only store PIXTYPE values, but making it\n  // ALIGNTYPE instead helps avoid some warnings.\n  ALIGNTYPE char_colors[4];\n  int char_idx[4];\n  int ppal = graphics->protected_pal_position;\n  int write_pos;\n\n  ALIGNTYPE set_colors[16];\n  ALIGNTYPE set_opaque[16];\n  unsigned prev = 0x10000;\n  boolean has_tcol = false;\n  boolean all_tcol = true;\n  // In mode 0 it's possible to quickly determine if an entire byte is\n  // transparent. If it is, there's no point in drawing it. SMZX modes\n  // can try to do a similar trick, but it won't work if the char has\n  // multiple indices set to the transparent color.\n  unsigned int byte_tcol = 0xFFFF;\n\n  size_t pix_pitch = pitch / sizeof(PIXTYPE);\n  size_t pix_skip = pix_pitch * CHAR_H;\n  PIXTYPE *dest_ptr = reinterpret_cast<PIXTYPE *>(pixels);\n  PIXTYPE *out_ptr;\n  PIXTYPE *start_ptr = dest_ptr;\n  PIXTYPE *end_ptr = dest_ptr + (height_px - 1) * pix_pitch + width_px;\n\n  // CLIP variables\n  int start_x = 0;\n  int start_y = 0;\n  int end_x = layer->w;\n  int end_y = layer->h;\n  int dest_x = layer->x;\n  int dest_y = layer->y;\n  size_t src_skip = 0;\n  int clip_xl = -1;\n  int clip_xr = -1;\n  int clip_yt = -1;\n  int clip_yb = -1;\n\n  if(CLIP)\n  {\n    if(precompute_clip(start_x, start_y, end_x, end_y, dest_x, dest_y,\n     width_px, height_px, layer))\n    {\n      // Precalculate clipping boundaries.\n      if(layer->x + start_x * CHAR_W < 0)\n        clip_xl = start_x;\n      if(layer->x + end_x * CHAR_W > width_px)\n        clip_xr = end_x - 1;\n\n      if(layer->y + start_y * CHAR_H < 0)\n        clip_yt = start_y;\n      if(layer->y + end_y * CHAR_H > height_px)\n        clip_yb = end_y - 1;\n    }\n    src_skip = layer->w - (end_x - start_x);\n  }\n\n  dest_ptr += dest_y * (ptrdiff_t)pix_pitch;\n  dest_ptr += dest_x;\n  src = layer->data + start_x + (start_y * layer->w);\n\n  pix_skip -= (end_x - start_x) * CHAR_W;\n\n  /* Enable 1PPW writes for >1PPW renderers at X clip boundaries if anything\n   * in the render will be off of alignment. */\n  const int unaligned =\n   ((size_t)pixels | (size_t)dest_ptr | pitch) % sizeof(ALIGNTYPE);\n\n  for(y = start_y; y < end_y; y++, src += src_skip, dest_ptr += pix_skip)\n  {\n    int clip_y = CLIP && (y == clip_yt || y == clip_yb);\n    int pix_y = layer->y + y * CHAR_H;\n\n    for(x = start_x; x < end_x; x++, src++, dest_ptr += CHAR_W)\n    {\n      int clip_x = CLIP && (x == clip_xl || x == clip_xr);\n      int pix_x = layer->x + x * CHAR_W;\n\n      c = select_char(src, layer);\n      if(c != INVISIBLE_CHAR)\n      {\n        /* For 4PPW and 8PPW, only update the color arrays if the palette has\n         * actually changed. This optimization is negligible or worse for lower\n         * write sizes since it blocks other compiler optimizations. */\n        unsigned both_cols = both_colors(src);\n        if(PPW <= 2 || prev != both_cols)\n        {\n          prev = both_cols;\n          if(SMZX)\n          {\n            unsigned int pal = ((src->bg_color & 0xF) << 4) | (src->fg_color & 0xF);\n            ALIGNTYPE masks[4];\n            all_tcol = true;\n            has_tcol = false;\n            byte_tcol = 0xFFFF;\n            for(i = 0; i < 4; i++)\n            {\n              char_idx[i] = graphics->smzx_indices[pal * 4 + i];\n\n              if(BPP > 8)\n                char_colors[i] = graphics->flat_intensity_palette[char_idx[i]];\n              else\n                char_colors[i] = char_idx[i];\n\n              if(TR)\n              {\n                if(!has_tcol)\n                {\n                  static const uint8_t tcol_bytes[] = { 0x00, 0x55, 0xAA, 0xFF };\n                  byte_tcol = tcol_bytes[i];\n                }\n                has_tcol |= char_idx[i] == tcol;\n                all_tcol &= char_idx[i] == tcol;\n                if(PPW > 2)\n                  masks[i] = char_idx[i] == tcol ? 0 : smask;\n              }\n\n              // If writing more than 2 pixels at once, preemptively double\n              // them. This saves having to perform this operation later...\n              if(PPW > 1)\n                char_colors[i] |= BPPx1(char_colors[i]);\n            }\n            if(PPW > 2)\n            {\n              set_colors_smzx<BPP, PPW>(set_colors, char_colors);\n              if(TR && has_tcol)\n                set_colors_smzx<BPP, PPW>(set_opaque, masks);\n            }\n          }\n          else\n          {\n            char_idx[0] = select_color_16(src->bg_color, ppal);\n            char_idx[1] = select_color_16(src->fg_color, ppal);\n\n            for(i = 0; i < 2; i++)\n            {\n              if(BPP > 8)\n                char_colors[i] = graphics->flat_intensity_palette[char_idx[i]];\n              else\n                char_colors[i] = (uint8_t)char_idx[i];\n            }\n\n            if(TR)\n            {\n              has_tcol = (char_idx[0] == tcol || char_idx[1] == tcol);\n              all_tcol = (char_idx[0] == tcol && char_idx[1] == tcol);\n              byte_tcol = (char_idx[1] == tcol) ? 0xFF : 0x00;\n            }\n\n            if(PPW > 1)\n            {\n              set_colors_mzx<BPP, PPW>(set_colors, char_colors[0], char_colors[1]);\n              if(TR && has_tcol)\n              {\n                ALIGNTYPE m0 = !byte_tcol ? 0 : mask;\n                ALIGNTYPE m1 = byte_tcol ? 0 : mask;\n                set_colors_mzx<BPP, PPW>(set_opaque, m0, m1);\n              }\n            }\n          }\n        }\n\n        // Don't bother drawing chars that are completely transparent.\n        if(TR && all_tcol)\n          continue;\n\n        out_ptr = dest_ptr;\n        char_ptr = graphics->charset + (c * CHAR_H);\n\n        // Force 1 PPW if this renderer is using unaligned writes on an edge.\n        if(PPW == 1 || (CLIP && clip_x && unaligned))\n        {\n          for(row = 0; row < CHAR_H; row++, out_ptr += pix_pitch)\n          {\n            current_char_byte = char_ptr[row];\n\n            if(TR && has_tcol && current_char_byte == byte_tcol)\n              continue;\n            if(CLIP && clip_y && (pix_y + row < 0 || out_ptr >= end_ptr))\n              continue;\n\n            for(write_pos = 0; write_pos < CHAR_W; write_pos++)\n            {\n              if(!SMZX)\n              {\n                if(CLIP && clip_x && (pix_x + write_pos < 0 || pix_x + write_pos >= width_px))\n                  continue;\n\n                pcol = !!(current_char_byte & (0x80 >> write_pos));\n                if(!TR || !has_tcol || tcol != char_idx[pcol])\n                  out_ptr[write_pos] = char_colors[pcol];\n              }\n              else\n              {\n                pcol = (current_char_byte & (0xC0 >> write_pos)) << write_pos >> 6;\n                if(TR && has_tcol && tcol == char_idx[pcol])\n                {\n                  write_pos++;\n                  continue;\n                }\n\n                pix = char_colors[pcol];\n                if(!CLIP || !clip_x || (pix_x + write_pos >= 0 && pix_x + write_pos < width_px))\n                  out_ptr[write_pos] = pix;\n\n                write_pos++;\n\n                if(!CLIP || !clip_x || (pix_x + write_pos >= 0 && pix_x + write_pos < width_px))\n                  out_ptr[write_pos] = pix;\n              }\n            }\n          }\n        }\n        else\n        {\n          for(row = 0; row < CHAR_H; row++, out_ptr += pix_pitch)\n          {\n            current_char_byte = char_ptr[row];\n\n            if(TR && has_tcol && current_char_byte == byte_tcol)\n              continue;\n            if(CLIP && clip_y && (out_ptr < start_ptr || out_ptr >= end_ptr))\n              continue;\n\n            ALIGNTYPE *write_ptr = reinterpret_cast<ALIGNTYPE *>(out_ptr);\n\n            for(write_pos = 0; write_pos < CHAR_W / PPW; write_pos++)\n            {\n              if(!CLIP || !clip_x ||\n               ((pix_x + write_pos * PPW >= 0) &&\n                (pix_x + write_pos * PPW + PPW - 1 < width_px)))\n              {\n                if(SMZX && PPW == 2)\n                {\n                  ALIGNTYPE shift = write_pos * PPW;\n                  pcol = (current_char_byte & (0xC0 >> shift)) << shift >> 6;\n\n                  if(!TR || !has_tcol || tcol != char_idx[pcol])\n                    write_ptr[write_pos] = char_colors[pcol];\n                }\n                else\n                {\n                  idx = get_colors_index<PPW>(current_char_byte, write_pos);\n                  pix = get_colors<PPW>(set_colors, idx);\n\n                  if(TR && has_tcol)\n                  {\n                    ALIGNTYPE opaque = get_colors<PPW>(set_opaque, idx);\n                    pix = (pix & opaque) | (write_ptr[write_pos] & ~opaque);\n                  }\n                  write_ptr[write_pos] = pix;\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/render_layer_common.hpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __RENDER_LAYER_COMMON_HPP\n#define __RENDER_LAYER_COMMON_HPP\n\n#include \"graphics.h\"\n#include \"platform_endian.h\"\n\n/**\n * Select the real display character for a char element.\n * TODO: this should be done at draw time.\n */\nstatic inline int select_char(const struct char_element *src,\n const struct video_layer *layer)\n{\n  int ch = src->char_value;\n  if(ch >= INVISIBLE_CHAR)\n    return INVISIBLE_CHAR;\n\n  // Char values >= 256, prior to offsetting, are from the protected charset.\n  if(ch > 0xff)\n  {\n    return (ch & 0xff) + PROTECTED_CHARSET_POSITION;\n  }\n  else\n    return ((ch & 0xff) + layer->offset) % PROTECTED_CHARSET_POSITION;\n}\n\n/**\n * Mode 0 and UI layer color selection function.\n * This needs to be done for both colors.\n */\nstatic inline int select_color_16(uint8_t color, int ppal)\n{\n  // Palette values >= 16, prior to offsetting, are from the protected palette.\n  if(color >= 16)\n  {\n    return (color - 16) % 16 + ppal;\n  }\n  else\n    return color;\n}\n\n/**\n * Precompute clipping ranges for a layer.\n * Because the precision of the input is in chars (not pixels),\n * the destination x/y may be negative, and the effective final\n * destination x/y may extend beyond the screen. The caller is\n * responsible for correctly handling these clip areas.\n *\n * @param start_x   starting x within the layer, in chars.\n * @param start_y   starting y within the layer, in chars.\n * @param end_x     last x + 1 within the layer, in chars.\n * @param end_y     last y + 1 within the layer, in chars.\n * @param dest_x    first x to draw, in pixels. May be negative.\n * @param dest_y    first y to draw, in pixels. May be negative.\n */\nstatic inline boolean precompute_clip(int &start_x, int &start_y,\n int &end_x, int &end_y, int &dest_x, int &dest_y,\n int width_px, int height_px, const struct video_layer *layer)\n{\n  int dest_last_x = layer->x + layer->w * CHAR_W;\n  int dest_last_y = layer->y + layer->h * CHAR_H;\n  boolean ret = false;\n\n  if(layer->x < 0)\n  {\n    start_x = -(layer->x / CHAR_W);\n    dest_x = layer->x % CHAR_W; // intentional truncated modulo\n    ret = true;\n  }\n  else\n  {\n    start_x = 0;\n    dest_x = layer->x;\n  }\n\n  if(layer->y < 0)\n  {\n    start_y = -(layer->y / CHAR_H);\n    dest_y = layer->y % CHAR_H; // intentional truncated modulo\n    ret = true;\n  }\n  else\n  {\n    start_y = 0;\n    dest_y = layer->y;\n  }\n\n  end_x = layer->w;\n  end_y = layer->h;\n\n  if(dest_last_x > width_px)\n  {\n    end_x -= (dest_last_x - width_px) / CHAR_W;\n    ret = true;\n  }\n\n  if(dest_last_y > height_px)\n  {\n    end_y -= (dest_last_y - height_px) / CHAR_H;\n    ret = true;\n  }\n  return ret;\n}\n\n/**\n * Get the combined colors value from a char_element.\n */\nstatic inline unsigned both_colors(const char_element *src)\n{\n#ifdef IS_CXX_11\n  static_assert(offsetof(char_element, bg_color) == 2, \"\");\n  static_assert(offsetof(char_element, fg_color) == 3, \"\");\n#endif\n  return reinterpret_cast<const uint16_t *>(src)[1];\n}\n\n#endif /* __RENDER_LAYER_COMMON_HPP */\n"
  },
  {
    "path": "src/render_sdl.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007-2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2019-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"SDLmzx.h\"\n#include \"event.h\"\n#include \"render.h\"\n#include \"render_sdl.h\"\n#include \"util.h\"\n#include \"yuv.h\"\n\n#include <limits.h>\n#include <stdlib.h>\n\nstatic void sdl_set_system_cursor(struct graphics_data *graphics)\n{\n#if SDL_VERSION_ATLEAST(3,0,0)\n  if(graphics->system_mouse == SYSTEM_MOUSE_OFF)\n    SDL_HideCursor();\n  else\n    SDL_ShowCursor();\n#else\n  int value = (graphics->system_mouse == SYSTEM_MOUSE_OFF) ? SDL_DISABLE : SDL_ENABLE;\n  SDL_ShowCursor(value);\n#endif\n}\n\nstatic void sdl_set_window_grab(struct sdl_render_data *render_data,\n boolean enable)\n{\n#if SDL_VERSION_ATLEAST(3,0,0)\n  SDL_SetWindowMouseGrab(render_data->window, enable);\n#elif SDL_VERSION_ATLEAST(2,0,0)\n  SDL_SetWindowGrab(render_data->window, enable);\n#else\n  // Not a perfect equivalent; the SDL 1.2 version will grab even if unfocused\n  SDL_GrabMode mode = enable ? SDL_GRAB_ON : SDL_GRAB_OFF;\n  SDL_WM_GrabInput(mode);\n#endif\n}\n\n#if SDL_VERSION_ATLEAST(2,0,0)\nstatic void sdl_set_screensaver_enabled(boolean enable)\n{\n  debug(\"SDL %s screensaver.\\n\", enable ? \"enabling\" : \"disabling\");\n  if(enable)\n    SDL_EnableScreenSaver();\n  else\n    SDL_DisableScreenSaver();\n}\n#endif\n\nint sdl_flags(const struct video_window *window)\n{\n  int flags = 0;\n\n  if(window->is_fullscreen)\n  {\n#if SDL_VERSION_ATLEAST(2,0,0) && !SDL_VERSION_ATLEAST(3,0,0)\n    /* SDL3 removed the ability to specify this at window creation time. */\n    if(window->is_fullscreen_windowed)\n      flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;\n    else\n#endif\n      flags |= SDL_WINDOW_FULLSCREEN;\n\n#if !SDL_VERSION_ATLEAST(2,0,0)\n    if(window->bits_per_pixel == 8)\n      flags |= SDL_HWPALETTE;\n#endif\n  }\n  else\n    if(window->allow_resize)\n      flags |= SDL_WINDOW_RESIZABLE;\n\n  return flags;\n}\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n// Get the current desktop resolution, if possible.\nstatic boolean sdl_get_desktop_display_mode(SDL_DisplayMode *display_mode)\n{\n#if SDL_VERSION_ATLEAST(3,0,0)\n\n  const SDL_DisplayMode **list;\n  const SDL_DisplayMode *mode;\n  int count;\n\n  SDL_DisplayID id = SDL_GetPrimaryDisplay();\n  if(id == 0)\n    return false;\n\n  mode = SDL_GetDesktopDisplayMode(id);\n  if(mode)\n  {\n    *display_mode = *mode;\n    return true;\n  }\n\n  warn(\"Failed to query desktop display mode: %s\\n\", SDL_GetError());\n  list = (const SDL_DisplayMode **)SDL_GetFullscreenDisplayModes(0, &count);\n  if(list)\n  {\n    if(count)\n      mode = list[0];\n\n    SDL_free(list);\n    if(mode)\n    {\n      *display_mode = *mode;\n      return true;\n    }\n  }\n\n#else\n\n  if(SDL_GetDesktopDisplayMode(0, display_mode) == 0)\n  {\n    debug(\"Queried desktop mode: %d x %d, %dHz, %s\\n\",\n     display_mode->w, display_mode->h, display_mode->refresh_rate,\n     SDL_GetPixelFormatName(display_mode->format));\n    return true;\n  }\n\n  warn(\"Failed to query desktop display mode: %s\\n\", SDL_GetError());\n\n  if(SDL_GetNumDisplayModes(0))\n    if(SDL_GetDisplayMode(0, 0, display_mode) == 0)\n      return true;\n\n#endif\n\n  return false;\n}\n\n// Get the smallest possible resolution bigger than both 640x350 and requested.\n// Smaller means the software renderer will occupy more screen space.\nstatic boolean sdl_get_closest_usable_display_mode(SDL_DisplayMode *display_mode,\n int width, int height)\n{\n#if SDL_VERSION_ATLEAST(3,0,0)\n\n  int min_size = INT_MAX;\n  int count;\n  int i;\n\n  const SDL_DisplayMode **list;\n  SDL_DisplayID id = SDL_GetPrimaryDisplay();\n  if(id == 0)\n    return false;\n\n  list = (const SDL_DisplayMode **)SDL_GetFullscreenDisplayModes(id, &count);\n  if(!list)\n    return false;\n\n  debug(\"Display modes:\\n\");\n\n  for(i = 0; i < count; i++)\n  {\n    if(!list[i])\n      continue;\n\n    debug(\"%d: %d x %d, %.2fHz, %s\\n\", i, list[i]->w, list[i]->h,\n     list[i]->refresh_rate, SDL_GetPixelFormatName(list[i]->format));\n\n    if((list[i]->w * list[i]->h < min_size) &&\n     (list[i]->w >= width) && (list[i]->h >= height))\n    {\n      min_size = list[i]->w * list[i]->h;\n      *display_mode = *list[i];\n    }\n  }\n  SDL_free(list);\n\n  if(min_size < INT_MAX)\n    return true;\n\n#else\n\n  SDL_DisplayMode mode;\n  int min_size = INT_MAX;\n  int count;\n  int i;\n\n  count = SDL_GetNumDisplayModes(0);\n\n  debug(\"Display modes:\\n\");\n\n  for(i = 0; i < count; i++)\n  {\n    if(SDL_GetDisplayMode(0, i, &mode) != 0)\n      continue;\n\n    debug(\"%d: %d x %d, %dHz, %s\\n\", i, mode.w, mode.h, mode.refresh_rate,\n     SDL_GetPixelFormatName(mode.format));\n\n    if((mode.w * mode.h < min_size) && (mode.w >= width) && (mode.h >= height))\n    {\n      min_size = mode.w * mode.h;\n      *display_mode = mode;\n    }\n  }\n  if(min_size < INT_MAX)\n    return true;\n\n#endif /* !SDL_VERSION_ATLEAST(3,0,0) */\n\n  return false;\n}\n#endif /* SDL_VERSION_ATLEAST(2,0,0) */\n\nstatic boolean sdl_get_fullscreen_resolution(int *width, int *height,\n boolean match_current_desktop)\n{\n#if SDL_VERSION_ATLEAST(2,0,0)\n\n  SDL_DisplayMode display_mode;\n  boolean have_mode = false;\n\n  if(match_current_desktop)\n  {\n    // Use the current desktop resolution.\n    have_mode = sdl_get_desktop_display_mode(&display_mode);\n  }\n  else\n  {\n    have_mode = sdl_get_closest_usable_display_mode(&display_mode, 640, 350);\n  }\n\n  if(have_mode)\n  {\n    debug(\"\\n\");\n    debug(\"selected: %d x %d, %.02fHz, %s\\n\", display_mode.w, display_mode.h,\n     (float)display_mode.refresh_rate, SDL_GetPixelFormatName(display_mode.format));\n    *width = display_mode.w;\n    *height = display_mode.h;\n    return true;\n  }\n  else\n    warn(\"Failed to get display mode: %s\\n\", SDL_GetError());\n#endif\n\n  return false;\n}\n\n/**\n * Automatically detect and correct the fullscreen size if required.\n * This should be done even if the window isn't initially fullscreen.\n * If the window is fullscreen, the window size will also be updated.\n */\nstatic void auto_fullscreen_size(struct graphics_data *graphics,\n struct video_window *window, boolean renderer_supports_scaling)\n{\n  int width = graphics->resolution_width;\n  int height = graphics->resolution_height;\n\n  // Fullscreen windowed should always autodetect the size regardless of config.\n  if(width <= 0 || height <= 0 || window->is_fullscreen_windowed)\n  {\n    if(!sdl_get_fullscreen_resolution(&width, &height,\n     renderer_supports_scaling || window->is_fullscreen_windowed))\n    {\n      width = 640;\n      height = 480;\n    }\n\n    graphics->resolution_width = width;\n    graphics->resolution_height = height;\n    if(window->is_fullscreen)\n    {\n      window->width_px = graphics->resolution_width;\n      window->height_px = graphics->resolution_height;\n    }\n  }\n  video_window_update_viewport(window);\n}\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n/**\n * Determine if MZX supports a particular SDL pixel format. Returns a priority\n * value if the format is supported or 0 if the format is not supported. Values\n * returned for separate formats can be compared; higher values correspond to\n * higher priority.\n *\n * A bits_per_pixel value can be provided to filter out pixel formats that do\n * not correspond to the desired BPP value.\n */\nstatic uint32_t sdl_pixel_format_priority(uint32_t pixel_format,\n uint32_t bits_per_pixel, uint32_t yuv_priority)\n{\n  switch(pixel_format)\n  {\n    case SDL_PIXELFORMAT_INDEX8:\n    {\n      if(bits_per_pixel == BPP_AUTO || bits_per_pixel == 8)\n        return 256;\n\n      break;\n    }\n\n#if SDL_VERSION_ATLEAST(2,0,12)\n    case SDL_PIXELFORMAT_XBGR4444:\n#endif\n    case SDL_PIXELFORMAT_XRGB4444:\n    case SDL_PIXELFORMAT_ARGB4444:\n    case SDL_PIXELFORMAT_RGBA4444:\n    case SDL_PIXELFORMAT_ABGR4444:\n    case SDL_PIXELFORMAT_BGRA4444:\n    {\n      // Favor 16bpp modes with more colors if possible.\n      if(bits_per_pixel == BPP_AUTO || bits_per_pixel == 16)\n        return 444;\n\n      break;\n    }\n\n    case SDL_PIXELFORMAT_XRGB1555:\n    case SDL_PIXELFORMAT_XBGR1555:\n    case SDL_PIXELFORMAT_ARGB1555:\n    case SDL_PIXELFORMAT_RGBA5551:\n    case SDL_PIXELFORMAT_ABGR1555:\n    case SDL_PIXELFORMAT_BGRA5551:\n    {\n      if(bits_per_pixel == BPP_AUTO || bits_per_pixel == 16)\n        return 555;\n\n      break;\n    }\n\n    case SDL_PIXELFORMAT_RGB565:\n    case SDL_PIXELFORMAT_BGR565:\n    {\n      if(bits_per_pixel == BPP_AUTO || bits_per_pixel == 16)\n        return 565;\n\n      break;\n    }\n\n    case SDL_PIXELFORMAT_XRGB8888:\n    case SDL_PIXELFORMAT_XBGR8888:\n    case SDL_PIXELFORMAT_RGBX8888:\n    case SDL_PIXELFORMAT_BGRX8888:\n    case SDL_PIXELFORMAT_ARGB8888:\n    case SDL_PIXELFORMAT_RGBA8888:\n    case SDL_PIXELFORMAT_ABGR8888:\n    case SDL_PIXELFORMAT_BGRA8888:\n    case SDL_PIXELFORMAT_ARGB2101010:\n#if SDL_VERSION_ATLEAST(3,0,0)\n    case SDL_PIXELFORMAT_XRGB2101010:\n    case SDL_PIXELFORMAT_XBGR2101010:\n    case SDL_PIXELFORMAT_ABGR2101010:\n#endif\n    {\n      // Any 32-bit RGB format is okay.\n      if(bits_per_pixel == BPP_AUTO || bits_per_pixel == 32)\n        return 888;\n\n      break;\n    }\n\n    case SDL_PIXELFORMAT_YUY2:\n    case SDL_PIXELFORMAT_UYVY:\n    case SDL_PIXELFORMAT_YVYU:\n    {\n      if(!yuv_priority)\n        break;\n\n      // These can work as either 32-bpp (full encode) or partial 16-bpp\n      // (chroma subsampling for render_graph, full encode for layers).\n      if(bits_per_pixel == BPP_AUTO || bits_per_pixel == 16 || bits_per_pixel == 32)\n        return yuv_priority;\n\n      break;\n    }\n  }\n  return 0;\n}\n#endif /* SDL_VERSION_ATLEAST(2,0,0) */\n\n#if !SDL_VERSION_ATLEAST(2,0,0)\n/**\n * Precheck a provided video mode and select a BPP (if requested). This\n * works somewhat opposite to how SDL 2 works: instead of a pixel format\n * being picked by SDL (which needs to be checked for MZX support), MZX\n * instead needs to test its preferred pixel format by querying SDL.\n */\nboolean sdl_check_video_mode(struct graphics_data *graphics,\n struct video_window *window, boolean renderer_supports_scaling, int flags)\n{\n  static int system_bpp = 0;\n  static boolean has_system_bpp = false;\n  int width;\n  int height;\n  int in_depth = window->bits_per_pixel;\n  int out_depth;\n\n  auto_fullscreen_size(graphics, window, renderer_supports_scaling);\n  width = window->width_px;\n  height = window->height_px;\n\n  if(!has_system_bpp)\n  {\n    // Query the \"best\" video mode. This might actually be the current video\n    // mode if another renderer has been initialized, so always do this first\n    // (even if the provided depth isn't BPP_AUTO).\n    const SDL_VideoInfo *info = SDL_GetVideoInfo();\n    if(info && info->vfmt)\n    {\n      system_bpp = info->vfmt->BytesPerPixel * 8;\n      has_system_bpp = true;\n      debug(\"SDL_GetVideoInfo BPP=%d\\n\", system_bpp);\n    }\n  }\n\n  if(in_depth == BPP_AUTO)\n    in_depth = system_bpp;\n\n  out_depth = SDL_VideoModeOK(width, height, in_depth, flags);\n  if(out_depth <= 0)\n  {\n    debug(\"SDL_VideoModeOK failed.\\n\");\n    return false;\n  }\n\n  if(window->bits_per_pixel == BPP_AUTO)\n  {\n    if(out_depth == 8 || out_depth == 16 || out_depth == 32)\n    {\n      debug(\"SDL_VideoModeOK recommends BPP=%d\\n\", out_depth);\n      graphics->bits_per_pixel = out_depth;\n      window->bits_per_pixel = out_depth;\n    }\n    else\n\n    if(out_depth > 0)\n    {\n      debug(\"SDL_VideoModeOK recommends unsupported BPP=%d; using 32bpp\\n\", out_depth);\n      graphics->bits_per_pixel = 32;\n      window->bits_per_pixel = 32;\n    }\n  }\n  return true;\n}\n#endif /* !SDL_VERSION_ATLEAST(2,0,0) */\n\nvoid sdl_destruct_window(struct graphics_data *graphics)\n{\n  struct sdl_render_data *render_data = graphics->render_data;\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n  size_t i;\n\n  // Used by the software renderer as a fallback if the window surface doesn't\n  // match the pixel format MZX wants.\n  if(render_data->shadow)\n  {\n    SDL_DestroySurface(render_data->shadow);\n    render_data->shadow = NULL;\n  }\n\n  // Used for 8bpp support for the software renderer.\n  // This is attached to the surface in SDL3 and should not be destroyed.\n  if(render_data->palette)\n  {\n#if !SDL_VERSION_ATLEAST(3,0,0)\n    SDL_DestroyPalette(render_data->palette);\n#endif\n    render_data->palette = NULL;\n  }\n\n#if !SDL_VERSION_ATLEAST(3,0,0)\n  // Used for generating mapped colors for the SDL_Renderer renderers.\n  if(render_data->pixel_format)\n  {\n    SDL_FreeFormat(render_data->pixel_format);\n    render_data->pixel_format = NULL;\n  }\n#endif\n\n  // Used by the SDL renderer-based renderers for HW acceleration.\n  for(i = 0; i < ARRAY_SIZE(render_data->texture); i++)\n  {\n    if(render_data->texture[i])\n    {\n      SDL_DestroyTexture(render_data->texture[i]);\n      render_data->texture[i] = NULL;\n    }\n  }\n\n  // Used by the softscale renderer for HW acceleration. Don't use for software.\n  // Destroying this when exiting fullscreen can be slow for some reason.\n  if(render_data->renderer)\n  {\n    SDL_DestroyRenderer(render_data->renderer);\n    render_data->renderer = NULL;\n  }\n\n  // Used by the OpenGL renderers.\n  if(render_data->context)\n  {\n    SDL_GL_DestroyContext(render_data->context);\n    render_data->context = NULL;\n  }\n\n  // Used by all SDL renderers.\n  if(render_data->window)\n  {\n    SDL_DestroyWindow(render_data->window);\n    render_data->window = NULL;\n  }\n\n  // This is attached to the window for renderers that use it. Just clear it...\n  render_data->screen = NULL;\n\n#else /* !SDL_VERSION_ATLEAST(2,0,0) */\n\n  // Used by the YUV renderers.\n  if(render_data->overlay)\n  {\n    SDL_FreeYUVOverlay(render_data->overlay);\n    render_data->overlay = NULL;\n  }\n\n  // Don't free render_data->screen. This pointer is managed by SDL.\n  // The shadow surface isn't used by SDL 1.2 builds.\n  render_data->screen = NULL;\n  render_data->shadow = NULL;\n\n#endif\n\n  // Used to send the palette to SDL in indexed mode.\n  if(render_data->palette_colors)\n  {\n    free(render_data->palette_colors);\n    render_data->palette_colors = NULL;\n  }\n  render_data->flat_format = NULL;\n}\n\nboolean sdl_set_window_caption(struct graphics_data *graphics,\n struct video_window *window, const char *caption)\n{\n  struct sdl_render_data *render_data = graphics->render_data;\n\n#if SDL_VERSION_ATLEAST(3,0,0)\n  if(!SDL_SetWindowTitle(render_data->window, caption))\n  {\n    debug(\"failed SDL_SetWindowTitle: %s\\n\", SDL_GetError());\n    return false;\n  }\n#else\n  SDL_SetWindowTitle(render_data->window, caption);\n#endif\n  return true;\n}\n\n#if defined(CONFIG_PNG) && defined(CONFIG_ICON) && !defined(_WIN32)\n#include \"pngops.h\"\n\nstatic boolean icon_w_h_constraint(png_uint_32 w, png_uint_32 h)\n{\n  // Icons must be multiples of 16 and square\n  return (w == h) && ((w % 16) == 0) && ((h % 16) == 0);\n}\n\nstatic void *sdl_alloc_rgba_surface(png_uint_32 w, png_uint_32 h,\n png_uint_32 *stride, void **pixels)\n{\n#if SDL_VERSION_ATLEAST(2,0,0)\n  SDL_Surface *s = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_RGBA32);\n#else\n  Uint32 rmask, gmask, bmask, amask;\n  SDL_Surface *s;\n\n#if SDL_BYTEORDER == SDL_BIG_ENDIAN\n  rmask = 0xff000000;\n  gmask = 0x00ff0000;\n  bmask = 0x0000ff00;\n  amask = 0x000000ff;\n#else\n  rmask = 0x000000ff;\n  gmask = 0x0000ff00;\n  bmask = 0x00ff0000;\n  amask = 0xff000000;\n#endif\n\n  s = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, rmask, gmask, bmask, amask);\n#endif\n  if(!s)\n    return NULL;\n\n  *stride = s->pitch;\n  *pixels = s->pixels;\n  return s;\n}\n\nstatic SDL_Surface *png_read_icon(const char *name)\n{\n  return png_read_file(name, NULL, NULL, icon_w_h_constraint,\n   sdl_alloc_rgba_surface);\n}\n#endif // CONFIG_PNG && CONFIG_ICON && !_WIN32\n\nboolean sdl_set_window_icon(struct graphics_data *graphics,\n struct video_window *window, const char *icon_path)\n{\n#ifdef CONFIG_ICON\n  struct sdl_render_data *render_data = graphics->render_data;\n  /* This function may be a NOP for some configurations. */\n  (void)render_data;\n\n#ifdef _WIN32\n  {\n    /* Load the icon directly from the executable resource. */\n    HICON icon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(1));\n    if(icon)\n    {\n      SendMessage((HWND)SDL_GetWindowProperty_HWND(render_data->window),\n       WM_SETICON, ICON_BIG, (LPARAM)icon);\n      return true;\n    }\n    else\n      debug(\"failed to open embedded icon\\n\");\n  }\n#elif defined(CONFIG_PNG) // !_WIN32\n  {\n    SDL_Surface *icon;\n    if(!icon_path)\n    {\n      warn(\"NULL icon path, report this!\\n\");\n      return false;\n    }\n\n    icon = png_read_icon(icon_path);\n    if(icon)\n    {\n#if SDL_VERSION_ATLEAST(3,0,0)\n      if(!SDL_SetWindowIcon(render_data->window, icon))\n      {\n        debug(\"failed SDL_SetWindowIcon: %s\\n\", SDL_GetError());\n        SDL_DestroySurface(icon);\n        return false;\n      }\n#else\n      SDL_SetWindowIcon(render_data->window, icon);\n#endif\n      SDL_DestroySurface(icon);\n      return true;\n    }\n    else\n      warn(\"failed to open icon file '%s'\\n\", icon_path);\n  }\n#endif // !_WIN32 && !CONFIG_PNG\n#endif // CONFIG_ICON\n  return false;\n}\n\nvoid sdl_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned int count)\n{\n  struct sdl_render_data *render_data = (struct sdl_render_data *)graphics->render_data;\n  unsigned int i;\n\n  if(graphics->bits_per_pixel == 8)\n  {\n    if(!render_data->palette_colors)\n      return;\n    if(count > 256)\n      count = 256;\n\n    for(i = 0; i < count; i++)\n    {\n      render_data->palette_colors[i].r = palette[i].r;\n      render_data->palette_colors[i].g = palette[i].g;\n      render_data->palette_colors[i].b = palette[i].b;\n    }\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n    SDL_SetPaletteColors(render_data->palette,\n     render_data->palette_colors, 0, count);\n#else\n    SDL_SetColors(render_data->screen, render_data->palette_colors, 0, count);\n#endif\n  }\n  else\n\n  if(!render_data->rgb_to_yuv)\n  {\n    if(!render_data->flat_format)\n      return;\n    for(i = 0; i < count; i++)\n    {\n#if SDL_VERSION_ATLEAST(3,0,0)\n      graphics->flat_intensity_palette[i] =\n       SDL_MapRGBA(render_data->flat_format, NULL,\n        palette[i].r, palette[i].g, palette[i].b, SDL_ALPHA_OPAQUE);\n#else\n      graphics->flat_intensity_palette[i] =\n       SDL_MapRGBA(render_data->flat_format,\n        palette[i].r, palette[i].g, palette[i].b, SDL_ALPHA_OPAQUE);\n#endif\n    }\n  }\n  else\n  {\n    for(i = 0; i < count; i++)\n    {\n      graphics->flat_intensity_palette[i] =\n       render_data->rgb_to_yuv(palette[i].r, palette[i].g, palette[i].b);\n    }\n  }\n}\n\n\n/* SDL software renderer functions for: *************************************/\n// software\n// gp2x\n\nboolean sdl_create_window_soft(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct sdl_render_data *render_data = graphics->render_data;\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n  SDL_Surface *target;\n  int depth = window->bits_per_pixel;\n  boolean matched = false;\n  Uint32 fmt;\n\n  sdl_destruct_window(graphics);\n\n  auto_fullscreen_size(graphics, window, false);\n\n#if defined(__EMSCRIPTEN__) && SDL_VERSION_ATLEAST(2,0,10)\n  // Also, a hint needs to be set to make SDL_UpdateWindowSurface not crash.\n  SDL_SetHint(SDL_HINT_EMSCRIPTEN_ASYNCIFY, \"0\");\n#endif\n\n  render_data->window = SDL_CreateWindow(\"MegaZeux\",\n   window->width_px, window->height_px, sdl_flags(window));\n\n  if(!render_data->window)\n  {\n    warn(\"Failed to create window: %s\\n\", SDL_GetError());\n    goto err_free;\n  }\n\n  render_data->screen = SDL_GetWindowSurface(render_data->window);\n  if(!render_data->screen)\n  {\n    warn(\"Failed to get window surface: %s\\n\", SDL_GetError());\n    goto err_free;\n  }\n\n  /* SDL 2.0 allows the window system to offer up buffers of a specific\n   * format, and expects the application to perform a blit if the format\n   * doesn't match what the app wanted. To accomodate this, allocate a\n   * shadow screen that we render to in the case the formats do not match,\n   * and blit it at present time.\n   */\n\n  fmt = SDL_GetWindowPixelFormat(render_data->window);\n  debug(\"Window pixel format: %s\\n\", SDL_GetPixelFormatName(fmt));\n\n  if(!sdl_pixel_format_priority(fmt, depth, YUV_DISABLE))\n  {\n    switch(depth)\n    {\n      case 8:\n        fmt = SDL_PIXELFORMAT_INDEX8;\n        break;\n      case 16:\n        fmt = SDL_PIXELFORMAT_RGB565;\n        break;\n      case 32:\n      default:\n        fmt = SDL_PIXELFORMAT_RGBA8888;\n        break;\n    }\n  }\n  else\n    matched = true;\n\n  debug(\"Using pixel format: %s\\n\", SDL_GetPixelFormatName(fmt));\n\n  if(depth == BPP_AUTO)\n    graphics->bits_per_pixel = window->bits_per_pixel = SDL_BYTESPERPIXEL(fmt) * 8;\n\n  if((unsigned)render_data->screen->w < window->width_px ||\n   (unsigned)render_data->screen->h < window->height_px)\n  {\n    matched = false;\n    warn(\"Window surface (%dx%d) is smaller than expected (%ux%u).\\n\",\n     render_data->screen->w, render_data->screen->h,\n     window->width_px, window->height_px);\n  }\n\n  if(!matched)\n  {\n    render_data->shadow = SDL_CreateSurface(window->width_px,\n     window->height_px, fmt);\n    debug(\"Blitting enabled. Rendering performance will be reduced.\\n\");\n  }\n  else\n  {\n    render_data->shadow = NULL;\n  }\n\n  target = render_data->shadow ? render_data->shadow : render_data->screen;\n#if SDL_VERSION_ATLEAST(3,0,0)\n  render_data->flat_format = SDL_GetPixelFormatDetails(target->format);\n#else\n  render_data->flat_format = target->format;\n#endif\n\n  if(fmt == SDL_PIXELFORMAT_INDEX8)\n  {\n#if SDL_VERSION_ATLEAST(3,0,0)\n    render_data->palette = SDL_CreateSurfacePalette(target);\n    if(!render_data->palette)\n    {\n      warn(\"Failed to create surface palette: %s\\n\", SDL_GetError());\n      goto err_free;\n    }\n#else\n    render_data->palette = SDL_AllocPalette(SMZX_PAL_SIZE);\n    if(!render_data->palette)\n    {\n      warn(\"Failed to allocate palette: %s\\n\", SDL_GetError());\n      goto err_free;\n    }\n\n    if(SDL_SetPixelFormatPalette(target->format, render_data->palette))\n    {\n      warn(\"Failed to set pixel format palette: %s\\n\", SDL_GetError());\n      goto err_free;\n    }\n#endif\n\n    render_data->palette_colors =\n     (SDL_Color *)ccalloc(SMZX_PAL_SIZE, sizeof(SDL_Color));\n    if(!render_data->palette_colors)\n    {\n      warn(\"Failed to allocate palette colors\\n\");\n      goto err_free;\n    }\n  }\n  else\n  {\n    render_data->palette = NULL;\n    render_data->palette_colors = NULL;\n  }\n\n  window->platform_id = SDL_GetWindowID(render_data->window);\n  sdl_set_screensaver_enabled(graphics->disable_screensaver == SCREENSAVER_ENABLE);\n\n#else // !SDL_VERSION_ATLEAST(2,0,0)\n\n  if(!sdl_check_video_mode(graphics, window, false, sdl_flags(window)))\n    return false;\n\n  render_data->screen = SDL_SetVideoMode(window->width_px, window->height_px,\n   window->bits_per_pixel, sdl_flags(window));\n\n  if(!render_data->screen)\n    return false;\n\n  render_data->shadow = NULL;\n  render_data->flat_format = render_data->screen->format;\n\n  if(window->bits_per_pixel == 8)\n  {\n    render_data->palette_colors =\n     (SDL_Color *)ccalloc(SMZX_PAL_SIZE, sizeof(SDL_Color));\n    if(!render_data->palette_colors)\n      return false;\n  }\n\n#endif // !SDL_VERSION_ATLEAST(2,0,0)\n\n  // Wipe the letterbox area, if any.\n  SDL_FillSurfaceRect(render_data->screen, NULL, 0);\n\n  sdl_set_system_cursor(graphics);\n  sdl_set_window_grab(render_data, window->grab_mouse);\n  sdl_init_window_text_events(window->platform_id);\n  return true;\n\n#if SDL_VERSION_ATLEAST(2,0,0)\nerr_free:\n  sdl_destruct_window(graphics);\n  return false;\n#endif // SDL_VERSION_ATLEAST(2,0,0)\n}\n\nboolean sdl_resize_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n#if SDL_VERSION_ATLEAST(2,0,0)\n  struct sdl_render_data *render_data = graphics->render_data;\n\n  if(window->is_fullscreen)\n  {\n    // Unlike window creation, SDL can't be nicely provided with a target\n    // resolution for real fullscreen, so MZX has to manually find one.\n    SDL_DisplayMode mode;\n    boolean real_fullscreen = false;\n\n    if(!window->is_fullscreen_windowed &&\n     sdl_get_closest_usable_display_mode(&mode, window->width_px, window->height_px))\n    {\n      debug(\"\\n\");\n      debug(\"selected: %d x %d, %.02fHz, %s\\n\", mode.w, mode.h,\n       (float)mode.refresh_rate, SDL_GetPixelFormatName(mode.format));\n\n#if SDL_VERSION_ATLEAST(3,0,0)\n      if(SDL_SetWindowFullscreenMode(render_data->window, &mode) &&\n         SDL_SetWindowFullscreen(render_data->window, true))\n#else\n      if(SDL_SetWindowDisplayMode(render_data->window, &mode) == 0 &&\n         SDL_SetWindowFullscreen(render_data->window, SDL_WINDOW_FULLSCREEN) == 0)\n#endif\n      {\n        graphics->resolution_width = mode.w;\n        graphics->resolution_height = mode.h;\n        window->width_px = mode.w;\n        window->height_px = mode.h;\n        // graphics.c will do this anyway.\n        //video_window_update_viewport(window);\n        real_fullscreen = true;\n      }\n      else\n      {\n        debug(\"failed to set selected fullscreen mode: %s\\n\", SDL_GetError());\n        debug(\"falling back to fullscreen windowed\\n\");\n      }\n    }\n\n    if(!real_fullscreen)\n    {\n#if SDL_VERSION_ATLEAST(3,0,0)\n      // Select borderless desktop fullscreen.\n      SDL_SetWindowFullscreenMode(render_data->window, NULL);\n      SDL_SetWindowFullscreen(render_data->window, true);\n#else\n      SDL_SetWindowFullscreen(render_data->window, SDL_WINDOW_FULLSCREEN_DESKTOP);\n#endif\n    }\n  }\n  else\n  {\n    SDL_SetWindowFullscreen(render_data->window, 0);\n    SDL_SetWindowBordered(render_data->window, 1);\n    SDL_SetWindowResizable(render_data->window, window->allow_resize);\n    // TODO: doesn't seem to reliably work??\n    SDL_SetWindowSize(render_data->window, window->width_px, window->height_px);\n    SDL_SetWindowPosition(render_data->window,\n     SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);\n  }\n\n  sdl_set_system_cursor(graphics);\n  sdl_set_window_grab(render_data, window->grab_mouse);\n  return true;\n\n#else\n  // SDL_SetVideoMode\n  return sdl_create_window_soft(graphics, window);\n#endif\n}\n\n\n/* SDL hardware renderer API functions for: *********************************/\n// softscale\n// sdlaccel\n\n#if defined(CONFIG_RENDER_SOFTSCALE) || defined(CONFIG_RENDER_SDLACCEL)\n\n#if SDL_VERSION_ATLEAST(3,0,0)\n#define BEST_RENDERER NULL\n#else\n#define BEST_RENDERER -1\n#endif\n\n/* The official SDL line on SDL_LockTexture is that the buffer is write-only,\n * but in practice, this is variable. MegaZeux doesn't care about whether or\n * not the buffer is initialized, but it needs to be able to read after writing\n * for transparent layers. The \"official\" way this should be done is using a\n * buffer and SDL_UpdateTexture.\n *\n * + direct3d, software, PS2, PSP, Vita, Wii U:\n *   SDL_UpdateTexture calls SDL_LockTexture and SDL_memcpy internally.\n *   SDL_LockTexture in these instances returns a read+write buffer.\n *   This is safe for rendering and faster than using SDL_UpdateTexture.\n * + OpenGL(ES) uses glTexSubImage2D either way, so negligible.\n * + direct3d11/direct3d12 map texture memory as write-only and incur massive\n *   performance penalties for ANY read. Always use SDL_UpdateTexture.\n * + vulkan, gpu may use direct3d11/direct3d12 mapping internally.\n * + metal is only supported on new enough computers that it doesn't matter.\n * + ogc (Wii/GC) has a faster SDL_UpdateTexture.\n */\n#if defined(CONFIG_PS2) || defined(CONFIG_PSP) || \\\n    defined(CONFIG_PSVITA) || defined(CONFIG_WIIU)\n#define USE_TEXTURE_STREAMING(driver) (true)\n#elif defined(_WIN32)\n#define USE_TEXTURE_STREAMING(driver) \\\n (is_software_renderer || !strcasecmp((driver), \"direct3d\"))\n#else\n#define USE_TEXTURE_STREAMING(driver) (is_software_renderer)\n#endif\n\nstatic uint32_t get_format_amask(uint32_t format)\n{\n  Uint32 rmask, gmask, bmask, amask;\n  int bpp;\n\n  SDL_GetMasksForPixelFormat(format, &bpp, &rmask, &gmask, &bmask, &amask);\n  return amask;\n}\n\nstatic void find_texture_format(struct graphics_data *graphics,\n boolean requires_blend_ops)\n{\n  struct sdl_render_data *render_data = (struct sdl_render_data *)graphics->render_data;\n  uint32_t texture_format = SDL_PIXELFORMAT_UNKNOWN;\n  unsigned int texture_bpp = 0;\n  uint32_t texture_amask = 0;\n  boolean allow_subsampling = false;\n  boolean need_alpha = false;\n  uint32_t yuv_priority = YUV_PRIORITY;\n  uint32_t priority = 0;\n  boolean is_software_renderer = false;\n  const char *renderer_name = NULL;\n  int num_formats;\n\n#if SDL_VERSION_ATLEAST(3,0,0)\n\n  SDL_PropertiesID props = SDL_GetRendererProperties(render_data->renderer);\n  const SDL_PixelFormat *formats = NULL;\n  const SDL_PixelFormat *pos;\n\n  renderer_name = SDL_GetRendererName(render_data->renderer);\n  if(!strcmp(renderer_name, SDL_SOFTWARE_RENDERER))\n    is_software_renderer = true;\n\n  formats = (const SDL_PixelFormat *)SDL_GetPointerProperty(props,\n   SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, NULL);\n  num_formats = 0;\n  for(pos = formats; pos && *pos != SDL_PIXELFORMAT_UNKNOWN; pos++)\n    num_formats++;\n\n#else\n\n  SDL_RendererInfo rinfo;\n  const uint32_t *formats = NULL;\n\n  if(!SDL_GetRendererInfo(render_data->renderer, &rinfo))\n  {\n    renderer_name = rinfo.name;\n    num_formats = rinfo.num_texture_formats;\n    formats = rinfo.texture_formats;\n\n    if(rinfo.flags & SDL_RENDERER_SOFTWARE)\n      is_software_renderer = true;\n  }\n  else\n    warn(\"Failed to get renderer info!\\n\");\n\n#endif /* !SDL_VERSION_ATLEAST(3,0,0) */\n\n  if(formats)\n  {\n    unsigned int depth = graphics->bits_per_pixel;\n    int i;\n\n    info(\"SDL render driver: '%s'\\n\", renderer_name);\n    if(is_software_renderer)\n      warn(\"Accelerated renderer not available. Rendering will be SLOW!\\n\");\n\n#ifdef __MACOSX__\n    // Not clear if Metal supports the custom Apple YUV texture format.\n    if(!strcasecmp(renderer_name, \"opengl\"))\n      yuv_priority = YUV_PRIORITY_APPLE;\n#endif\n\n    // Anything using hardware blending must support alpha.\n    if(requires_blend_ops)\n      need_alpha = true;\n\n    // Try to use a native texture format to improve performance.\n    for(i = 0; i < num_formats; i++)\n    {\n      uint32_t format = formats[i];\n      unsigned int format_priority;\n\n      debug(\"%d: %s\\n\", i, SDL_GetPixelFormatName(format));\n\n      if(SDL_ISPIXELFORMAT_INDEXED(format))\n        continue;\n      if(need_alpha && !SDL_ISPIXELFORMAT_ALPHA(format))\n        continue;\n\n      format_priority = sdl_pixel_format_priority(format, depth, yuv_priority);\n      if(format_priority > priority)\n      {\n        texture_format = format;\n        priority = format_priority;\n      }\n    }\n  }\n\n  if(texture_format == SDL_PIXELFORMAT_UNKNOWN)\n  {\n    // 16bpp RGB seems moderately faster than YUV with chroma subsampling\n    // when neither are natively supported.\n    if(graphics->bits_per_pixel == 16)\n      texture_format = SDL_PIXELFORMAT_RGB565;\n    else\n      texture_format = SDL_PIXELFORMAT_ARGB8888;\n\n    debug(\"No matching pixel format. Using %s. Rendering may be slower.\\n\",\n     SDL_GetPixelFormatName(texture_format));\n  }\n  else\n    debug(\"Using pixel format %s.\\n\", SDL_GetPixelFormatName(texture_format));\n\n  if(priority == yuv_priority)\n  {\n    texture_bpp = 32;\n\n    if(texture_format == SDL_PIXELFORMAT_YUY2)\n    {\n      render_data->subsample_set_colors = yuy2_subsample_set_colors_mzx;\n      render_data->rgb_to_yuv = rgb_to_yuy2;\n    }\n\n    if(texture_format == SDL_PIXELFORMAT_UYVY)\n    {\n      render_data->subsample_set_colors = uyvy_subsample_set_colors_mzx;\n#ifdef __MACOSX__\n      render_data->rgb_to_yuv = rgb_to_apple_ycbcr_422;\n#else\n      render_data->rgb_to_yuv = rgb_to_uyvy;\n#endif\n    }\n\n    if(texture_format == SDL_PIXELFORMAT_YVYU)\n    {\n      render_data->subsample_set_colors = yvyu_subsample_set_colors_mzx;\n      render_data->rgb_to_yuv = rgb_to_yvyu;\n    }\n\n    // If this renderer was activated with force_bpp=16, enable subsampling\n    // support for render_graph.\n    if(graphics->bits_per_pixel == 16)\n    {\n      debug(\"Allowing YUV subsampling for render_graph.\\n\");\n      allow_subsampling = true;\n    }\n  }\n  else\n  {\n    texture_bpp = SDL_BYTESPERPIXEL(texture_format) * 8;\n    texture_amask = get_format_amask(texture_format);\n  }\n\n  if(graphics->bits_per_pixel == BPP_AUTO)\n    graphics->bits_per_pixel = texture_bpp;\n\n  // Initialize the texture format data.\n  render_data->texture_format = texture_format;\n  render_data->texture_amask = texture_amask;\n  render_data->texture_bpp = texture_bpp;\n  render_data->allow_subsampling = allow_subsampling;\n  render_data->use_texture_streaming = USE_TEXTURE_STREAMING(renderer_name);\n}\n\n/**\n * Initialize a window for SDL Renderer-based renderers. This function will\n * automatically select a native texture format for the renderer (if applicable)\n * for better performance. This texture format is stored to `sdl_render_data`.\n * In some cases (but usually not), this may include YUV texture formats. The\n * corresponding `sdlrender_update_colors` should be used with these renderers.\n *\n * This function will also pick nearest or linear scaling automatically based\n * on the scaling ratio and window size.\n */\nboolean sdl_create_window_renderer(struct graphics_data *graphics,\n struct video_window *window, boolean requires_blend_ops)\n{\n  struct sdl_render_data *render_data = graphics->render_data;\n\n  sdl_destruct_window(graphics);\n\n  auto_fullscreen_size(graphics, window, true);\n\n#if !SDL_VERSION_ATLEAST(2,0,12)\n  // Use linear filtering unless the display is being integer scaled.\n  if(window->is_integer_scaled)\n    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, \"0\");\n  else\n    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, \"1\");\n#endif\n\n#if defined(__EMSCRIPTEN__) && SDL_VERSION_ATLEAST(2,0,10)\n  // Not clear if this hint is required to make this renderer not crash, but\n  // considering both software and GLSL need it...\n  SDL_SetHint(SDL_HINT_EMSCRIPTEN_ASYNCIFY, \"0\");\n#endif\n\n  if(graphics->sdl_render_driver[0])\n  {\n    info(\"Requesting SDL render driver: '%s'\\n\", graphics->sdl_render_driver);\n    SDL_SetHint(SDL_HINT_RENDER_DRIVER, graphics->sdl_render_driver);\n  }\n\n  render_data->window = SDL_CreateWindow(\"MegaZeux\",\n   window->width_px, window->height_px, sdl_flags(window));\n\n  if(!render_data->window)\n  {\n    warn(\"Failed to create window: %s\\n\", SDL_GetError());\n    goto err_free;\n  }\n\n#if SDL_VERSION_ATLEAST(3,0,0)\n  render_data->renderer = SDL_CreateRenderer(render_data->window, BEST_RENDERER);\n#else\n  render_data->renderer = SDL_CreateRenderer(render_data->window, BEST_RENDERER,\n   requires_blend_ops ? SDL_RENDERER_TARGETTEXTURE : 0);\n#endif\n  if(!render_data->renderer)\n  {\n    warn(\"Failed to create renderer: %s\\n\", SDL_GetError());\n    goto err_free;\n  }\n\n  find_texture_format(graphics, requires_blend_ops);\n  window->bits_per_pixel = graphics->bits_per_pixel;\n\n  if(!render_data->rgb_to_yuv)\n  {\n#if SDL_VERSION_ATLEAST(3,0,0)\n    render_data->flat_format =\n     SDL_GetPixelFormatDetails(render_data->texture_format);\n#else\n    // This is required for SDL_MapRGBA to work, but YUV formats can ignore it.\n    render_data->pixel_format = SDL_AllocFormat(render_data->texture_format);\n    if(!render_data->pixel_format)\n    {\n      warn(\"Failed to allocate pixel format: %s\\n\", SDL_GetError());\n      goto err_free;\n    }\n    render_data->flat_format = render_data->pixel_format;\n#endif\n  }\n\n  SDL_SetRenderDrawColor(render_data->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);\n  SDL_RenderClear(render_data->renderer);\n\n  window->platform_id = SDL_GetWindowID(render_data->window);\n  sdl_set_screensaver_enabled(graphics->disable_screensaver == SCREENSAVER_ENABLE);\n  sdl_set_system_cursor(graphics);\n  sdl_set_window_grab(render_data, window->grab_mouse);\n  sdl_init_window_text_events(window->platform_id);\n  return true;\n\nerr_free:\n  sdl_destruct_window(graphics);\n  return false;\n}\n\n/* Set the texture scaling mode for a given texture.\n * If allow_non_integer is true, the best is selected given whether or not\n * the screen is an integer multiple of 640x350. If false, nearest is used.\n * This only works for SDL 2.0.12 and up; prior should rely on hints. */\nvoid sdl_set_texture_scale_mode(struct graphics_data *graphics,\n struct video_window *window, int texture_id, boolean allow_non_integer)\n{\n#if SDL_VERSION_ATLEAST(2,0,12)\n  struct sdl_render_data *render_data =\n   (struct sdl_render_data *)graphics->render_data;\n\n  if(render_data->texture[texture_id])\n  {\n    SDL_ScaleMode mode;\n    if(!allow_non_integer || window->is_integer_scaled)\n      mode = SDL_SCALEMODE_NEAREST;\n    else\n      mode = SDL_SCALEMODE_LINEAR;\n\n    SDL_SetTextureScaleMode(render_data->texture[texture_id], mode);\n  }\n  else\n    warn(\"Texture %d is null!\\n\", texture_id);\n#endif\n}\n\n#endif /* CONFIG_RENDER_SOFTSCALE || CONFIG_RENDER_SDLACCEL */\n\n\n/* SDL direct OpenGL functions for: *****************************************/\n// opengl1\n// opengl2\n// glsl / glslscale\n\n#if defined(CONFIG_RENDER_GL_FIXED) || defined(CONFIG_RENDER_GL_PROGRAM)\n\nboolean gl_create_window(struct graphics_data *graphics,\n struct video_window *window, struct gl_version req_ver)\n{\n  struct sdl_render_data *render_data = graphics->render_data;\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n  sdl_destruct_window(graphics);\n\n  auto_fullscreen_size(graphics, window, true);\n\n#ifdef CONFIG_GLES\n  // Hints to make SDL use OpenGL ES drivers (e.g. ANGLE) on Windows/Linux.\n  // These may be ignored by SDL unless using the Windows or X11 video drivers.\n#if SDL_VERSION_ATLEAST(2,0,6)\n  SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, \"1\");\n#endif\n#if SDL_VERSION_ATLEAST(2,0,2)\n  SDL_SetHint(SDL_HINT_VIDEO_WIN_D3DCOMPILER, \"none\");\n#endif\n\n  // Declare the OpenGL version to source functions from. This is necessary\n  // since OpenGL ES 1.x and OpenGL ES 2.x are not compatible. This must be\n  // done after old windows are destroyed and before new windows are created.\n  SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);\n  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, req_ver.major);\n  SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, req_ver.minor);\n#endif\n\n// Emscripten's EGL requires explicitly declaring alpha\n// for RGBA textures to work.\n#ifdef __EMSCRIPTEN__\n  SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);\n  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);\n  SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);\n\n#if SDL_VERSION_ATLEAST(2,0,10)\n  // Also, a hint needs to be set to make gl_swap_buffers not crash. Brilliant.\n  SDL_SetHint(SDL_HINT_EMSCRIPTEN_ASYNCIFY, \"0\");\n#endif\n#endif\n\n  render_data->window = SDL_CreateWindow(\"MegaZeux\",\n   window->width_px, window->height_px, GL_STRIP_FLAGS(sdl_flags(window)));\n\n  if(!render_data->window)\n  {\n    warn(\"Failed to create window: %s\\n\", SDL_GetError());\n    goto err_free;\n  }\n\n  /* This automatically makes the context current. */\n  render_data->context = SDL_GL_CreateContext(render_data->window);\n  if(!render_data->context)\n  {\n    warn(\"Failed to create context: %s\\n\", SDL_GetError());\n    goto err_free;\n  }\n\n  window->platform_id = SDL_GetWindowID(render_data->window);\n  sdl_set_screensaver_enabled(graphics->disable_screensaver == SCREENSAVER_ENABLE);\n\n#else // !SDL_VERSION_ATLEAST(2,0,0)\n\n  if(!sdl_check_video_mode(graphics, window, true, GL_STRIP_FLAGS(sdl_flags(window))))\n    return false;\n\n  if(!SDL_SetVideoMode(window->width_px, window->height_px, window->bits_per_pixel,\n       GL_STRIP_FLAGS(sdl_flags(window))))\n    return false;\n\n#endif // !SDL_VERSION_ATLEAST(2,0,0)\n\n  render_data->screen = NULL;\n  render_data->shadow = NULL;\n\n  sdl_set_system_cursor(graphics);\n  sdl_set_window_grab(render_data, window->grab_mouse);\n  sdl_init_window_text_events(window->platform_id);\n  return true;\n\n#if SDL_VERSION_ATLEAST(2,0,0)\nerr_free:\n  sdl_destruct_window(graphics);\n  return false;\n#endif // SDL_VERSION_ATLEAST(2,0,0)\n}\n\nboolean gl_resize_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n#if SDL_VERSION_ATLEAST(2,0,0)\n  return sdl_resize_window(graphics, window);\n#else\n  // SDL_SetVideoMode\n  struct gl_version dummy = { 0, 0 };\n  return gl_create_window(graphics, window, dummy);\n#endif\n}\n\nvoid gl_set_attributes(struct graphics_data *graphics)\n{\n  // Note that this function is called twice- both before and after\n  // gl_create_window\n  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);\n\n  if(graphics->gl_vsync == 0)\n    SDL_GL_SetSwapInterval(0);\n  else if(graphics->gl_vsync >= 1)\n    SDL_GL_SetSwapInterval(1);\n}\n\nboolean gl_swap_buffers(struct graphics_data *graphics)\n{\n#if SDL_VERSION_ATLEAST(2,0,0)\n  struct sdl_render_data *render_data = graphics->render_data;\n  SDL_GL_SwapWindow(render_data->window);\n#else\n  SDL_GL_SwapBuffers();\n#endif\n  return true;\n}\n\n#endif // CONFIG_RENDER_GL_FIXED || CONFIG_RENDER_GL_PROGRAM\n"
  },
  {
    "path": "src/render_sdl.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007-2008 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __RENDER_SDL_H\n#define __RENDER_SDL_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"SDLmzx.h\"\n#include \"graphics.h\"\n#include \"render.h\"\n\nstruct sdl_render_data\n{\n#if SDL_VERSION_ATLEAST(2,0,0)\n  SDL_Renderer *renderer;\n  SDL_Texture *texture[3];\n  SDL_Palette *palette;\n  SDL_GLContext context;\n  SDL_PixelFormatDetails *pixel_format;\n#else\n  SDL_Overlay *overlay;\n#endif\n  SDL_Window *window; // always NULL in SDL 1.2\n  SDL_Surface *screen;\n  SDL_Surface *shadow;\n  SDL_Color *palette_colors;\n  const SDL_PixelFormatDetails *flat_format; // format used by sdl_update_colors.\n\n  // SDL Renderer and overlay renderer texture format configuration.\n  uint32_t (*rgb_to_yuv)(uint8_t r, uint8_t g, uint8_t b);\n  set_colors_function subsample_set_colors;\n  uint32_t texture_format;\n  uint32_t texture_amask;\n  uint32_t texture_bpp;\n  boolean allow_subsampling;\n  boolean use_texture_streaming;\n};\n\n// Mac OS X has a special OpenGL YCbCr native texture mode which is\n// faster than RGB for older machines.\n#define YUV_PRIORITY_APPLE 1000000\n#define YUV_PRIORITY 422\n#define YUV_DISABLE 0\n\nstatic inline SDL_Window *sdl_get_current_window(void)\n{\n  const struct video_window *window = video_get_window(1);\n  return SDL_GetWindowFromID(window ? window->platform_id : 0);\n}\n\nint sdl_flags(const struct video_window *window);\nvoid sdl_destruct_window(struct graphics_data *graphics);\nvoid sdl_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned int count);\n\nboolean sdl_create_window_soft(struct graphics_data *graphics,\n struct video_window *window);\nboolean sdl_resize_window(struct graphics_data *graphics,\n struct video_window *window);\nboolean sdl_set_window_caption(struct graphics_data *graphics,\n struct video_window *window, const char *caption);\nboolean sdl_set_window_icon(struct graphics_data *graphics,\n struct video_window *window, const char *icon_path);\n\n#if !SDL_VERSION_ATLEAST(2,0,0)\n// Used internally only.\nboolean sdl_check_video_mode(struct graphics_data *graphics,\n struct video_window *window, boolean renderer_supports_scaling, int flags);\n#endif\n\n#if SDL_VERSION_ATLEAST(2,0,0)\nboolean sdl_create_window_renderer(struct graphics_data *graphics,\n struct video_window *window, boolean requires_blend_ops);\nvoid sdl_set_texture_scale_mode(struct graphics_data *graphics,\n struct video_window *window, int texture_id, boolean allow_non_integer);\n#endif\n\n#if defined(CONFIG_RENDER_GL_FIXED) || defined(CONFIG_RENDER_GL_PROGRAM)\n\n#include \"render_gl.h\"\n\n#if SDL_VERSION_ATLEAST(2,0,0) && !SDL_VERSION_ATLEAST(3,0,0)\n/* SDL_WINDOW_FULLSCREEN_DESKTOP removed in SDL3. */\n#define GL_ALLOW_FLAGS \\\n (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_RESIZABLE)\n#else\n#define GL_ALLOW_FLAGS (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_RESIZABLE)\n#endif\n\n#define GL_STRIP_FLAGS(A) ((A & GL_ALLOW_FLAGS) | SDL_WINDOW_OPENGL)\n\n#define gl_set_window_caption sdl_set_window_caption\n#define gl_set_window_icon    sdl_set_window_icon\n\nboolean gl_create_window(struct graphics_data *graphics,\n struct video_window *window, struct gl_version req_ver);\nboolean gl_resize_window(struct graphics_data *graphics,\n struct video_window *window);\nvoid gl_set_attributes(struct graphics_data *graphics);\nboolean gl_swap_buffers(struct graphics_data *graphics);\n\nstatic inline void gl_cleanup(struct graphics_data *graphics)\n{\n  sdl_destruct_window(graphics);\n}\n\nstatic inline boolean GL_LoadLibrary(enum gl_lib_type type)\n{\n#if SDL_VERSION_ATLEAST(3,0,0)\n  return SDL_GL_LoadLibrary(NULL);\n#else\n  if(SDL_GL_LoadLibrary(NULL) == 0)\n    return true;\n\n#if !SDL_VERSION_ATLEAST(2,0,0)\n  // If the context already exists, don't reload the library\n  // This is for SDL 1.2 which doesn't let us unload OpenGL\n  if(strcmp(SDL_GetError(), \"OpenGL context already created\") == 0) return true;\n#endif\n  return false;\n#endif\n}\n\nstatic inline dso_fn_ptr GL_GetProcAddress(const char *proc)\n{\n#if SDL_VERSION_ATLEAST(3,0,0)\n  return SDL_GL_GetProcAddress(proc);\n#else\n  /* SDL1/2 returns void * instead of a function pointer. */\n  union dso_suppress_warning value;\n  value.in = SDL_GL_GetProcAddress(proc);\n  return value.out;\n#endif\n}\n\n#endif // CONFIG_RENDER_GL_FIXED || CONFIG_RENDER_GL_PROGRAM\n\n__M_END_DECLS\n\n#endif // __RENDER_SDL_H\n"
  },
  {
    "path": "src/render_sdlaccel.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019, 2024-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* SDL hardware accelerated renderer using the SDL_Renderer API.\n * This thing is too slow to have much use in practice right now, but it might\n * be useful to keep around for reference. SMZX is not implemented yet.\n *\n * The old SDL_RenderTexture implementation (<2.0.18) is just plain slow.\n * SDL_RenderGeometry is better, but not enough to justify using it currently.\n *\n * The other main performance problem here is writes to the chars texture.\n * There's no compelling way to make it faster, and even a few rows updated can\n * cause a significant FPS drop at speed 2. Having a worker thread helps.\n */\n\n#include \"SDLmzx.h\"\n#include \"graphics.h\"\n#include \"render.h\"\n#include \"renderers.h\"\n#include \"render_sdl.h\"\n#include \"util.h\"\n\n#include <stdlib.h>\n\n// 6 versions of each char, 16*256 chars -> 24576 total \"chars\"\n// -> 49152 8x8s -> sqrt ~= 221 > 32 * 6\n\n//#define THREADED_CHARSETS\n\n#if SDL_VERSION_ATLEAST(2,0,18)\n#define RENDER_GEOMETRY\n#endif\n\n#define TEX_SCREEN      0\n#define TEX_CHARS       1\n#define TEX_BACKGROUND  2\n\n// 32 * 6 * 8 = 1536 px\n// 128 * 1 * 14 = 1792 px\n#define CHARS_ROW_SIZE 32\n#define CHARS_NUM_ROWS (FULL_CHARSET_SIZE / CHARS_ROW_SIZE)\n\n#define NUM_CHAR_VARIANTS 6\n\n// ARGB8888\n#define CHARS_RAW_ROW_W (CHAR_W * NUM_CHAR_VARIANTS * CHARS_ROW_SIZE)\n#define CHARS_RAW_ROW_H (CHAR_H)\n#define CHARS_RAW_ROW_PITCH (CHARS_RAW_ROW_W * sizeof(uint32_t))\n#ifdef THREADED_CHARSETS\n#define CHARS_RAW_ROW_SIZE (CHARS_RAW_ROW_W * CHARS_RAW_ROW_H)\n#endif\n\n#define TEX_CHARS_PIX_W (CHAR_W * NUM_CHAR_VARIANTS * CHARS_ROW_SIZE)\n#define TEX_CHARS_PIX_H (CHAR_H * CHARS_NUM_ROWS)\n#define TEX_CHAR_W ((float)CHAR_W / 2048.0)\n#define TEX_CHAR_H ((float)CHAR_H / 2048.0)\n\n#define TEX_SCREEN_W 1024\n#define TEX_SCREEN_H 512\n\n#define TEX_BG_W 128\n#define TEX_BG_H 32\n\n#define WHITE (0xFFFFFFFF)\n\n#ifdef THREADED_CHARSETS\n#define NUM_WORKERS 1\n#define MAX_WORKER_ROWS 4\n\nstruct entry\n{\n  uint8_t first_row;\n  uint8_t count;\n};\n#endif\n\nstruct sdlaccel_render_data\n{\n  struct sdl_render_data sdl;\n#ifdef RENDER_GEOMETRY\n  SDL_Vertex vertex[6 * (SCREEN_W + 2) * (SCREEN_H + 2)];\n#endif\n  uint8_t palette[FULL_PAL_SIZE * 3];\n  boolean chars_dirty_row[CHARS_NUM_ROWS];\n  boolean chars_dirty;\n#ifdef THREADED_CHARSETS\n  uint32_t chars_raw[TEX_CHARS_PIX_W * TEX_CHARS_PIX_H];\n  platform_thread t[NUM_WORKERS];\n  platform_cond cond_main;\n  platform_cond cond_worker;\n  platform_mutex lock;\n  boolean chars_needed_row[CHARS_NUM_ROWS];\n  boolean join;\n#endif\n};\n\nstatic void write_char_byte_mzx(uint32_t **_char_data, uint8_t byte)\n{\n  uint32_t *char_data = *_char_data;\n  *(char_data++) = (uint32_t)((int32_t)byte << 24 >> 31);\n  *(char_data++) = (uint32_t)((int32_t)byte << 25 >> 31);\n  *(char_data++) = (uint32_t)((int32_t)byte << 26 >> 31);\n  *(char_data++) = (uint32_t)((int32_t)byte << 27 >> 31);\n  *(char_data++) = (uint32_t)((int32_t)byte << 28 >> 31);\n  *(char_data++) = (uint32_t)((int32_t)byte << 29 >> 31);\n  *(char_data++) = (uint32_t)((int32_t)byte << 30 >> 31);\n  *(char_data++) = (uint32_t)((int32_t)byte << 31 >> 31);\n  *_char_data = char_data;\n}\n\nstatic void write_char_byte_smzx(uint32_t **_char_data, uint8_t byte, uint8_t col)\n{\n  uint32_t *char_data = *_char_data;\n  *(char_data++) = ((byte >> 6) & 3) == col ? WHITE : 0;\n  *(char_data++) = ((byte >> 6) & 3) == col ? WHITE : 0;\n  *(char_data++) = ((byte >> 4) & 3) == col ? WHITE : 0;\n  *(char_data++) = ((byte >> 4) & 3) == col ? WHITE : 0;\n  *(char_data++) = ((byte >> 2) & 3) == col ? WHITE : 0;\n  *(char_data++) = ((byte >> 2) & 3) == col ? WHITE : 0;\n  *(char_data++) = ((byte >> 0) & 3) == col ? WHITE : 0;\n  *(char_data++) = ((byte >> 0) & 3) == col ? WHITE : 0;\n  *_char_data = char_data;\n}\n\nstatic void sdlaccel_do_remap_row(struct graphics_data *graphics,\n struct sdlaccel_render_data *render_data, uint32_t *dest, uint32_t pitch, int row)\n{\n  uint8_t char_byte;\n  uint32_t skip = (pitch - CHARS_RAW_ROW_PITCH) / sizeof(uint32_t);\n  int x;\n  int y;\n\n  for(y = 0; y < CHAR_H; y++)\n  {\n    for(x = 0; x < CHARS_ROW_SIZE; x++)\n    {\n      char_byte = graphics->charset[y + CHAR_H * (x + row * CHARS_ROW_SIZE)];\n      write_char_byte_mzx(&dest, char_byte);\n      write_char_byte_mzx(&dest, char_byte ^ 0xFF);\n      write_char_byte_smzx(&dest, char_byte, 0);\n      write_char_byte_smzx(&dest, char_byte, 1);\n      write_char_byte_smzx(&dest, char_byte, 2);\n      write_char_byte_smzx(&dest, char_byte, 3);\n    }\n    dest += skip;\n  }\n}\n\n#ifdef THREADED_CHARSETS\nstatic void enqueue(struct sdlaccel_render_data *render_data, int first_row,\n int count)\n{\n  platform_mutex_lock(&(render_data->lock));\n  memset(render_data->chars_needed_row + first_row, true, count);\n  platform_cond_signal(&(render_data->cond_worker));\n  platform_mutex_unlock(&(render_data->lock));\n}\n\nstatic THREAD_RES sdlaccel_remap_worker(void *data)\n{\n  struct graphics_data *graphics = (struct graphics_data *)data;\n  struct sdlaccel_render_data *render_data = graphics->render_data;\n  uint32_t *pixels;\n  int rows[MAX_WORKER_ROWS];\n  int i, j;\n\n  platform_mutex_lock(&(render_data->lock));\n\n  while(!render_data->join)\n  {\n    platform_cond_wait(&(render_data->cond_worker), &(render_data->lock));\n\n    if(render_data->join)\n      break;\n\n    while(true)\n    {\n      for(i = 0, j = 0; i < CHARS_NUM_ROWS && j < MAX_WORKER_ROWS; i++)\n      {\n        if(render_data->chars_needed_row[i])\n        {\n          render_data->chars_needed_row[i] = false;\n          rows[j++] = i;\n        }\n      }\n      if(j == 0)\n        break;\n\n      platform_mutex_unlock(&(render_data->lock));\n      for(i = 0; i < j; i++)\n      {\n        pixels = render_data->chars_raw + CHARS_RAW_ROW_SIZE * rows[i];\n        sdlaccel_do_remap_row(graphics, render_data, pixels, CHARS_RAW_ROW_PITCH, rows[i]);\n      }\n\n      platform_mutex_lock(&(render_data->lock));\n    }\n\n    platform_cond_signal(&(render_data->cond_main));\n  }\n\n  platform_mutex_unlock(&(render_data->lock));\n  THREAD_RETURN;\n}\n#endif\n\nstatic void sdlaccel_free_video(struct graphics_data *graphics)\n{\n  struct sdlaccel_render_data *render_data = graphics->render_data;\n\n  if(render_data)\n  {\n#ifdef THREADED_CHARSETS\n    int i;\n\n    platform_mutex_lock(&(render_data->lock));\n\n    render_data->join = true;\n    platform_cond_broadcast(&(render_data->cond_worker));\n\n    platform_mutex_unlock(&(render_data->lock));\n\n    for(i = 0; i < NUM_WORKERS; i++)\n      platform_thread_join(&(render_data->t[i]));\n\n    platform_cond_destroy(&(render_data->cond_main));\n    platform_cond_destroy(&(render_data->cond_worker));\n    platform_mutex_destroy(&(render_data->lock));\n#endif\n\n    sdl_destruct_window(graphics);\n    graphics->render_data = NULL;\n    free(render_data);\n  }\n}\n\nstatic boolean sdlaccel_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  struct sdlaccel_render_data *render_data =\n   (struct sdlaccel_render_data *)cmalloc(sizeof(struct sdlaccel_render_data));\n#ifdef THREADED_CHARSETS\n  int i;\n#endif\n\n  if(!render_data)\n    return false;\n\n  memset(render_data, 0, sizeof(struct sdlaccel_render_data));\n  memset(render_data->chars_dirty_row, true, CHARS_NUM_ROWS);\n  render_data->chars_dirty = true;\n  graphics->render_data = render_data;\n  graphics->allow_resize = conf->allow_resize;\n  graphics->ratio = conf->video_ratio;\n  graphics->bits_per_pixel = 32;\n\n#ifdef THREADED_CHARSETS\n  memset(render_data->chars_needed_row, true, CHARS_NUM_ROWS);\n\n  platform_mutex_init(&(render_data->lock));\n  platform_cond_init(&(render_data->cond_main));\n  platform_cond_init(&(render_data->cond_worker));\n\n  for(i = 0; i < NUM_WORKERS; i++)\n    platform_thread_create(&(render_data->t[i]), sdlaccel_remap_worker, graphics);\n#endif\n\n  return true;\n}\n\nstatic boolean sdlaccel_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct sdlaccel_render_data *render_data =\n   (struct sdlaccel_render_data *)graphics->render_data;\n\n  SDL_Texture **texture = render_data->sdl.texture;\n  int tex_chars_w;\n  int tex_chars_h;\n\n  // This requires that the underlying driver supports framebuffer objects.\n  if(!sdl_create_window_renderer(graphics, window, true))\n    return false;\n\n  texture[TEX_SCREEN] =\n   SDL_CreateTexture(render_data->sdl.renderer, render_data->sdl.texture_format,\n    SDL_TEXTUREACCESS_TARGET, TEX_SCREEN_W, TEX_SCREEN_H);\n\n  if(!texture[TEX_SCREEN])\n  {\n    warn(\"Failed to create screen texture: %s\\n\", SDL_GetError());\n    goto err_free;\n  }\n\n#if !SDL_VERSION_ATLEAST(2,0,12)\n  // Always use nearest neighbor for the charset and background textures.\n  SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, \"0\");\n#endif\n\n  tex_chars_w = round_to_power_of_two(TEX_CHARS_PIX_W);\n  tex_chars_h = round_to_power_of_two(TEX_CHARS_PIX_H);\n\n  // Don't use SDL_TEXTUREACCESS_STREAMING here right now.\n  //\n  // The main difference at the moment is it keeps a duplicate buffer of the\n  // entire texture in memory. Changing arbitrary parts of this texture\n  // requires locking the whole thing, after which the whole thing will be\n  // uploaded (SLOW). It's better for the typical use case to write to a\n  // smaller local version and only upload recently updated parts.\n  texture[TEX_CHARS] =\n   SDL_CreateTexture(render_data->sdl.renderer, render_data->sdl.texture_format,\n    SDL_TEXTUREACCESS_STREAMING, tex_chars_w, tex_chars_h);\n\n  if(!texture[TEX_CHARS])\n  {\n    warn(\"Failed to create texture: %s\\n\", SDL_GetError());\n    goto err_free;\n  }\n\n  // Initialize the background texture.\n  texture[TEX_BACKGROUND] =\n   SDL_CreateTexture(render_data->sdl.renderer, render_data->sdl.texture_format,\n    SDL_TEXTUREACCESS_STREAMING, TEX_BG_W, TEX_BG_H);\n\n  if(!texture[TEX_BACKGROUND])\n  {\n    warn(\"Failed to create texture2: %s\\n\", SDL_GetError());\n    goto err_free;\n  }\n\n  SDL_SetTextureBlendMode(texture[TEX_CHARS], SDL_BLENDMODE_BLEND);\n  SDL_SetTextureBlendMode(texture[TEX_BACKGROUND], SDL_BLENDMODE_BLEND);\n\n  sdl_set_texture_scale_mode(graphics, window, TEX_SCREEN, true);\n  sdl_set_texture_scale_mode(graphics, window, TEX_CHARS, false);\n  sdl_set_texture_scale_mode(graphics, window, TEX_BACKGROUND, false);\n\n  SDL_SetRenderTarget(render_data->sdl.renderer, texture[TEX_SCREEN]);\n  return true;\n\nerr_free:\n  sdl_destruct_window(graphics);\n  return false;\n}\n\nstatic boolean sdlaccel_resize_callback(struct graphics_data *graphics,\n struct video_window *window)\n{\n  sdl_set_texture_scale_mode(graphics, window, TEX_SCREEN, true);\n  sdl_set_texture_scale_mode(graphics, window, TEX_CHARS, false);\n  sdl_set_texture_scale_mode(graphics, window, TEX_BACKGROUND, false);\n  return true;\n}\n\nstatic void sdlaccel_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, uint32_t count)\n{\n  struct sdlaccel_render_data *render_data = graphics->render_data;\n  uint32_t i;\n\n  sdl_update_colors(graphics, palette, count);\n\n  for(i = 0; i < count; i++)\n  {\n    render_data->palette[i*3  ] = palette[i].r;\n    render_data->palette[i*3+1] = palette[i].g;\n    render_data->palette[i*3+2] = palette[i].b;\n  }\n}\n\nstatic void sdlaccel_remap_char_range(struct graphics_data *graphics,\n uint16_t first, uint16_t count)\n{\n  struct sdlaccel_render_data *render_data = graphics->render_data;\n  int last;\n\n  if(first + count > FULL_CHARSET_SIZE)\n    count = FULL_CHARSET_SIZE - first;\n\n  last = (first + count - 1) / CHARS_ROW_SIZE;\n  first = first / CHARS_ROW_SIZE;\n\n  memset(render_data->chars_dirty_row + first, true, last - first + 1);\n  render_data->chars_dirty = true;\n\n#ifdef THREADED_CHARSETS\n  enqueue(render_data, first, last - first + 1);\n#endif\n}\n\nstatic void sdlaccel_remap_char(struct graphics_data *graphics, uint16_t chr)\n{\n  struct sdlaccel_render_data *render_data = graphics->render_data;\n  int row;\n\n  if(chr >= FULL_CHARSET_SIZE)\n    chr = FULL_CHARSET_SIZE - 1;\n\n  row = chr / CHARS_ROW_SIZE;\n  render_data->chars_dirty_row[row] = true;\n  render_data->chars_dirty = true;\n\n#ifdef THREADED_CHARSETS\n  enqueue(render_data, row, 1);\n#endif\n}\n\nstatic void sdlaccel_remap_charbyte(struct graphics_data *graphics, uint16_t chr,\n uint8_t byte)\n{\n  sdlaccel_remap_char(graphics, chr);\n}\n\nstatic void sdlaccel_do_remap_chars(struct graphics_data *graphics,\n struct sdlaccel_render_data *render_data)\n{\n  SDL_Rect rect = { 0, 0, CHARS_RAW_ROW_W, CHARS_RAW_ROW_H };\n\n  int i;\n\n  void *pixels;\n  int pitch = CHARS_RAW_ROW_PITCH;\n\n#ifndef THREADED_CHARSETS\n\n  for(i = 0; i < CHARS_NUM_ROWS; i++)\n  {\n    if(render_data->chars_dirty_row[i])\n    {\n      rect.y = i * CHAR_H;\n      SDL_LockTexture(render_data->sdl.texture[TEX_CHARS], &rect, &pixels, &pitch);\n\n      sdlaccel_do_remap_row(graphics, render_data, pixels, pitch, i);\n\n      SDL_UnlockTexture(render_data->sdl.texture[TEX_CHARS]);\n      render_data->chars_dirty_row[i] = false;\n    }\n  }\n\n#else /* THREADED_CHARSETS */\n\n  int start;\n\n  platform_mutex_lock(&(render_data->lock));\n  for(i = 0; i < CHARS_NUM_ROWS; i++)\n  {\n    if(render_data->chars_needed_row[i])\n    {\n      platform_cond_signal(&(render_data->cond_worker));\n      platform_cond_wait(&(render_data->cond_main), &(render_data->lock));\n    }\n  }\n  platform_mutex_unlock(&(render_data->lock));\n\n  for(i = 0; i < CHARS_NUM_ROWS; i++)\n  {\n    if(render_data->chars_dirty_row[i])\n    {\n      start = i;\n      do\n      {\n        render_data->chars_dirty_row[i] = false;\n        i++;\n      }\n      while(i < CHARS_NUM_ROWS && render_data->chars_dirty_row[i]);\n\n      pixels = render_data->chars_raw + start * CHARS_RAW_ROW_SIZE;\n\n      rect.y = start * CHAR_H;\n      rect.h = (i - start) * CHARS_RAW_ROW_H;\n      SDL_UpdateTexture(render_data->sdl.texture[TEX_CHARS], &rect, pixels, pitch);\n    }\n  }\n\n#endif /* THREADED_CHARSETS */\n}\n\n#ifdef RENDER_GEOMETRY\n\nstatic void vertex_char(struct SDL_Vertex *vertex, float topleft_x,\n float topleft_y, float tex_x, float tex_y, SDL_Color _sdl_color)\n{\n#if SDL_VERSION_ATLEAST(3,0,0)\n  SDL_FColor sdl_color =\n  {\n    _sdl_color.r / 255.0,\n    _sdl_color.g / 255.0,\n    _sdl_color.b / 255.0,\n    _sdl_color.a / 255.0,\n  };\n#else\n  SDL_Color sdl_color = _sdl_color;\n#endif\n  vertex[0].position.x = topleft_x;\n  vertex[0].position.y = topleft_y;\n  vertex[0].tex_coord.x = tex_x;\n  vertex[0].tex_coord.y = tex_y;\n  vertex[0].color = sdl_color;\n  vertex[1].position.x = topleft_x + CHAR_W;\n  vertex[1].position.y = topleft_y;\n  vertex[1].tex_coord.x = tex_x + TEX_CHAR_W;\n  vertex[1].tex_coord.y = tex_y;\n  vertex[1].color = sdl_color;\n  vertex[2].position.x = topleft_x + CHAR_W;\n  vertex[2].position.y = topleft_y + CHAR_H;\n  vertex[2].tex_coord.x = tex_x + TEX_CHAR_W;\n  vertex[2].tex_coord.y = tex_y + TEX_CHAR_H;\n  vertex[2].color = sdl_color;\n\n  vertex[3] = vertex[0];\n  vertex[4].position.x = topleft_x;\n  vertex[4].position.y = topleft_y + CHAR_H;\n  vertex[4].tex_coord.x = tex_x;\n  vertex[4].tex_coord.y = tex_y + TEX_CHAR_H;\n  vertex[4].color = sdl_color;\n  vertex[5] = vertex[2];\n}\n\n#else\n\nstatic void sdlaccel_set_color(struct graphics_data *graphics, int color, int mode)\n{\n  struct sdlaccel_render_data *render_data = graphics->render_data;\n  uint8_t *palette = render_data->palette;\n\n  if(!mode && color >= 16)\n    color = graphics->protected_pal_position + (color % 16);\n\n  SDL_SetTextureColorMod(render_data->sdl.texture[TEX_CHARS],\n   palette[color * 3], palette[color * 3 + 1], palette[color * 3 + 2]);\n}\n\n#endif\n\nstatic void sdlaccel_render_layer(struct graphics_data *graphics,\n struct video_layer *layer)\n{\n  struct sdlaccel_render_data *render_data = graphics->render_data;\n  struct char_element *next = layer->data;\n  int offx = layer->x;\n  int offy = layer->y;\n  int w = layer->w;\n  int h = layer->h;\n  int x;\n  int y;\n  int transparent_color = layer->transparent_col;\n  unsigned chr;\n\n  SDL_Renderer *renderer = render_data->sdl.renderer;\n  SDL_Texture *chars_tex = render_data->sdl.texture[TEX_CHARS];\n  SDL_Texture *bg_tex = render_data->sdl.texture[TEX_BACKGROUND];\n\n  SDL_Rect_mzx dest_bg = sdl_render_rect(offx, offy, w * CHAR_W, h * CHAR_H);\n  SDL_Rect_mzx src_bg = sdl_render_rect(0, 0, w, h);\n\n  void *_bg;\n  int bg_pitch;\n  uint32_t *bg;\n\n#ifdef RENDER_GEOMETRY\n  SDL_Vertex *vertex = render_data->vertex;\n  SDL_Color sdl_color;\n  int vertices = 0;\n  float topleft_x;\n  float topleft_y;\n  float tex_x;\n  float tex_y;\n#endif\n\n  if(render_data->chars_dirty)\n  {\n    sdlaccel_do_remap_chars(graphics, render_data);\n    render_data->chars_dirty = false;\n  }\n\n  if(!layer->mode)\n  {\n    SDL_LockTexture(bg_tex, NULL, &_bg, &bg_pitch);\n    bg = (uint32_t *)_bg;\n    bg_pitch >>= 2;\n    memset(bg, 0, TEX_BG_W * TEX_BG_H * sizeof(uint32_t));\n\n    for(y = 0; y < h; y++, bg += bg_pitch)\n    {\n      for(x = 0; x < w; x++, next++)\n      {\n        uint32_t color;\n\n        if(next->char_value == INVISIBLE_CHAR)\n          continue;\n\n        /* Render background. */\n        if(next->bg_color != transparent_color && next->fg_color != transparent_color)\n        {\n          color = next->bg_color;\n          if(color >= 16)\n            color = graphics->protected_pal_position + (color & 15);\n\n          bg[x] = graphics->flat_intensity_palette[color];\n        }\n\n#ifdef RENDER_GEOMETRY\n        /* Render foreground and partially transparent chars. */\n        chr = next->char_value + layer->offset;\n        tex_x = (chr % CHARS_ROW_SIZE) * (TEX_CHAR_W * NUM_CHAR_VARIANTS);\n        tex_y = (chr / CHARS_ROW_SIZE) * TEX_CHAR_H;\n\n        if(next->fg_color != transparent_color)\n        {\n          color = next->fg_color;\n        }\n        else\n\n        if(next->bg_color != transparent_color)\n        {\n          color = next->bg_color;\n          tex_x += TEX_CHAR_W;\n        }\n        else\n          continue;\n\n        if(color >= 16)\n          color = graphics->protected_pal_position + (color & 15);\n\n        sdl_color.r = render_data->palette[color * 3 + 0];\n        sdl_color.g = render_data->palette[color * 3 + 1];\n        sdl_color.b = render_data->palette[color * 3 + 2];\n        sdl_color.a = 255;\n\n        topleft_x = offx + x * CHAR_W;\n        topleft_y = offy + y * CHAR_H;\n\n        vertex_char(vertex, topleft_x, topleft_y, tex_x, tex_y, sdl_color);\n        vertex += 6;\n        vertices += 6;\n#endif\n      }\n    }\n    SDL_UnlockTexture(bg_tex);\n    SDL_RenderTexture(renderer, bg_tex, &src_bg, &dest_bg);\n\n#ifdef RENDER_GEOMETRY\n\n    SDL_RenderGeometry(renderer, chars_tex, render_data->vertex, vertices, NULL, 0);\n\n#else /* !RENDER_GEOMETRY */\n    /* Render foreground and partially transparent chars. */\n    {\n    SDL_Rect dest_rect = { 0, 0, CHAR_W, CHAR_H };\n    SDL_Rect src_rect = { 0, 0, CHAR_W, CHAR_H };\n    next = layer->data;\n    for(y = 0; y < h; y++)\n    {\n      for(x = 0; x < w; x++, next++)\n      {\n        if(next->char_value == INVISIBLE_CHAR)\n          continue;\n\n        chr = next->char_value + layer->offset;\n        src_rect.x = (chr % CHARS_ROW_SIZE) * CHAR_W * NUM_CHAR_VARIANTS;\n        src_rect.y = (chr / CHARS_ROW_SIZE) * CHAR_H;\n        dest_rect.x = offx + x * CHAR_W;\n        dest_rect.y = offy + y * CHAR_H;\n\n        if(next->fg_color != transparent_color)\n        {\n          sdlaccel_set_color(graphics, next->fg_color, 0);\n        }\n        else\n\n        if(next->bg_color != transparent_color)\n        {\n          sdlaccel_set_color(graphics, next->bg_color, 0);\n          src_rect.x += CHAR_W;\n        }\n        else\n          continue;\n\n        SDL_RenderCopy(renderer, chars_tex, &src_rect, &dest_rect);\n      }\n    }\n    }\n#endif /* !RENDER_GEOMETRY */\n  }\n  else\n  {\n    // FIXME\n  }\n}\n\nstatic void sdlaccel_render_cursor(struct graphics_data *graphics, unsigned x,\n unsigned y, uint16_t color, unsigned lines, unsigned offset)\n{\n  struct sdlaccel_render_data *render_data = graphics->render_data;\n  uint8_t *palette = render_data->palette;\n\n  // Input coordinates are on the character grid.\n  SDL_Rect_mzx dest =\n   sdl_render_rect(x * CHAR_W, y * CHAR_H + offset, CHAR_W, lines);\n\n  SDL_SetRenderDrawBlendMode(render_data->sdl.renderer, SDL_BLENDMODE_NONE);\n  SDL_SetRenderDrawColor(render_data->sdl.renderer,\n   palette[color * 3], palette[color * 3 + 1], palette[color * 3 + 2],\n   SDL_ALPHA_OPAQUE);\n\n  SDL_RenderFillRect(render_data->sdl.renderer, &dest);\n}\n\nstatic void sdlaccel_render_mouse(struct graphics_data *graphics, unsigned x,\n unsigned y, unsigned w, unsigned h)\n{\n  struct sdlaccel_render_data *render_data = graphics->render_data;\n\n  // Input coordinates are pixel values.\n  SDL_Rect_mzx dest = sdl_render_rect(x, y, w, h);\n\n  /* There is no preset inversion blend mode, so make a custom blend mode.\n   * Lower SDL versions should simply fall back to drawing a white rectangle.\n   */\n#if SDL_VERSION_ATLEAST(2,0,6)\n  {\n    SDL_BlendMode invert = SDL_ComposeCustomBlendMode(\n      /* Color: invert destination color. */\n      SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR, SDL_BLENDFACTOR_ZERO, SDL_BLENDOPERATION_ADD,\n      /* Alpha: keep destination alpha. SDL docs don't mention this, but the\n       * blend operations must be the same to work in OpenGL. */\n      SDL_BLENDFACTOR_DST_ALPHA, SDL_BLENDFACTOR_ZERO, SDL_BLENDOPERATION_ADD);\n\n    SDL_SetRenderDrawBlendMode(render_data->sdl.renderer, invert);\n  }\n#endif\n  SDL_SetRenderDrawColor(render_data->sdl.renderer, 255, 255, 255, 255);\n\n  SDL_RenderFillRect(render_data->sdl.renderer, &dest);\n}\n\nstatic void sdlaccel_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct sdlaccel_render_data *render_data = graphics->render_data;\n  SDL_Renderer *renderer = render_data->sdl.renderer;\n  SDL_Texture *screen_tex = render_data->sdl.texture[TEX_SCREEN];\n  SDL_Rect_mzx src;\n  SDL_Rect_mzx dest;\n\n  src = sdl_render_rect(0, 0, SCREEN_PIX_W, SCREEN_PIX_H);\n\n  dest.x = window->viewport_x;\n  dest.y = window->viewport_y;\n  dest.w = window->viewport_width;\n  dest.h = window->viewport_height;\n\n  SDL_SetRenderTarget(renderer, NULL);\n  SDL_RenderTexture(renderer, screen_tex, &src, &dest);\n\n  SDL_RenderPresent(renderer);\n\n  SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);\n  SDL_RenderClear(renderer);\n\n  SDL_SetRenderTarget(renderer, screen_tex);\n}\n\nvoid render_sdlaccel_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = sdlaccel_init_video;\n  renderer->free_video = sdlaccel_free_video;\n  renderer->create_window = sdlaccel_create_window;\n  renderer->resize_window = sdl_resize_window;\n  renderer->resize_callback = sdlaccel_resize_callback;\n  renderer->set_viewport = set_window_viewport_scaled;\n  renderer->set_window_caption = sdl_set_window_caption;\n  renderer->set_window_icon = sdl_set_window_icon;\n  renderer->update_colors = sdlaccel_update_colors;\n  renderer->remap_char_range = sdlaccel_remap_char_range;\n  renderer->remap_char = sdlaccel_remap_char;\n  renderer->remap_charbyte = sdlaccel_remap_charbyte;\n  renderer->render_layer = sdlaccel_render_layer;\n  renderer->render_cursor = sdlaccel_render_cursor;\n  renderer->render_mouse = sdlaccel_render_mouse;\n  renderer->sync_screen = sdlaccel_sync_screen;\n}\n"
  },
  {
    "path": "src/render_soft.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004-2006 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n\n#include \"graphics.h\"\n#include \"render.h\"\n#include \"render_layer.h\"\n#include \"renderers.h\"\n#include \"util.h\"\n\n#ifdef CONFIG_SDL\n#include \"SDLmzx.h\"\n#include \"render_sdl.h\"\n#endif\n\nstruct soft_render_data\n{\n#ifdef CONFIG_SDL\n  struct sdl_render_data sdl;\n#else\n  unsigned pitch;\n  unsigned bpp;\n  uint8_t buffer[SCREEN_PIX_W * SCREEN_PIX_H];\n#endif\n};\n\n#ifdef CONFIG_SDL\n\nstatic SDL_Surface *soft_get_screen_surface(struct soft_render_data *_render_data)\n{\n  struct sdl_render_data *render_data = &(_render_data->sdl);\n  return render_data->shadow ? render_data->shadow : render_data->screen;\n}\n\nstatic void soft_lock_buffer(struct soft_render_data *render_data,\n uint32_t **pixels, unsigned *pitch, unsigned *bpp, uint32_t *amask)\n{\n  const SDL_PixelFormatDetails *format = render_data->sdl.flat_format;\n  SDL_Surface *screen = soft_get_screen_surface(render_data);\n\n  *pixels = (uint32_t *)screen->pixels;\n  *pitch = screen->pitch;\n#if SDL_VERSION_ATLEAST(3,0,0)\n  *bpp = format->bytes_per_pixel * 8;\n#else\n  *bpp = format->BytesPerPixel * 8;\n#endif\n\n  *pixels += *pitch * ((screen->h - 350) / 8);\n  *pixels += (screen->w - 640) * *bpp / 64;\n\n  if(amask)\n    *amask = format->Amask;\n\n  SDL_LockSurface(screen);\n}\n\nstatic void soft_unlock_buffer(struct soft_render_data *render_data)\n{\n  SDL_Surface *screen = soft_get_screen_surface(render_data);\n  SDL_UnlockSurface(screen);\n}\n\n#else /* !CONFIG_SDL */\n\nstatic void soft_lock_buffer(struct soft_render_data *render_data,\n uint32_t **pixels, unsigned *pitch, unsigned *bpp, uint32_t *amask)\n{\n  *pixels = (uint32_t *)render_data->buffer;\n  *pitch = render_data->pitch;\n  *bpp = render_data->bpp;\n  if(amask)\n    *amask = 0;\n}\n\nstatic void soft_unlock_buffer(struct soft_render_data *render_data)\n{\n  // do nothing\n}\n\n#endif /* !CONFIG_SDL */\n\nstatic boolean soft_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  struct soft_render_data *render_data =\n   (struct soft_render_data *)ccalloc(1, sizeof(struct soft_render_data));\n  if(!render_data)\n    return false;\n\n  graphics->render_data = render_data;\n  graphics->allow_resize = 0;\n  graphics->bits_per_pixel = 32;\n\n  // Screens smaller than 640x350 do weird things with the software renderer\n  if(graphics->resolution_width < 640)\n    graphics->resolution_width = 640;\n  if(graphics->resolution_height < 350)\n    graphics->resolution_height = 350;\n  if(graphics->window_width < 640)\n    graphics->window_width = 640;\n  if(graphics->window_height < 350)\n    graphics->window_height = 350;\n\n  // We have 8-bit, 16-bit, and 32-bit software renderers\n  if(conf->force_bpp == BPP_AUTO || conf->force_bpp == 8 ||\n   conf->force_bpp == 16 || conf->force_bpp == 32)\n    graphics->bits_per_pixel = conf->force_bpp;\n\n  return true;\n}\n\nstatic void soft_free_video(struct graphics_data *graphics)\n{\n#ifdef CONFIG_SDL\n  sdl_destruct_window(graphics);\n#endif\n\n  free(graphics->render_data);\n  graphics->render_data = NULL;\n}\n\nstatic boolean soft_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n#ifdef CONFIG_SDL\n  return sdl_create_window_soft(graphics, window);\n#else\n\n  struct soft_render_data *render_data =\n   (struct soft_render_data *)graphics->render_data;\n\n  window->bits_per_pixel = 8;\n  render_data->pitch = (window->bits_per_pixel / 8) * SCREEN_PIX_W;\n  render_data->bpp = window->bits_per_pixel;\n\n  window->is_headless = true;\n  return true;\n\n#endif /* !CONFIG_SDL */\n}\n\nstatic void soft_update_colors(struct graphics_data *graphics,\n struct rgb_color *palette, unsigned int count)\n{\n#ifdef CONFIG_SDL\n  sdl_update_colors(graphics, palette, count);\n#endif /* CONFIG_SDL */\n}\n\nstatic void soft_render_graph(struct graphics_data *graphics)\n{\n  struct soft_render_data *render_data = graphics->render_data;\n\n  uint32_t *pixels;\n  unsigned int pitch;\n  unsigned int bpp;\n  unsigned int mode = graphics->screen_mode;\n\n  soft_lock_buffer(render_data, &pixels, &pitch, &bpp, NULL);\n\n  if(bpp == 8)\n  {\n    render_graph8((uint8_t *)pixels, pitch, graphics, set_colors8[mode]);\n  }\n  else\n\n  if(bpp == 16)\n  {\n    render_graph16((uint16_t *)pixels, pitch, graphics, set_colors16[mode]);\n  }\n  else\n\n  if(bpp == 32)\n  {\n    if(!mode)\n      render_graph32(pixels, pitch, graphics);\n    else\n      render_graph32s(pixels, pitch, graphics);\n\n    /* This just adds a 3x3 red box to the top left of the screen\n       It's useful for debugging because it indicates when the\n       fallback renderer is used\n\n    *(pixels + 0) = 0xFFFF0000;\n    *(pixels + 1) = 0xFFFF0000;\n    *(pixels + 2) = 0xFFFF0000;\n    *(pixels + (pitch/4)) = 0xFFFF0000;\n    *(pixels + (pitch/4) + 1) = 0xFFFF0000;\n    *(pixels + (pitch/4) + 2) = 0xFFFF0000;\n    *(pixels + (pitch/2)) = 0xFFFF0000;\n    *(pixels + (pitch/2) + 1) = 0xFFFF0000;\n    *(pixels + (pitch/2) + 2) = 0xFFFF0000;\n    */\n  }\n  soft_unlock_buffer(render_data);\n}\n\nstatic void soft_render_layer(struct graphics_data *graphics,\n struct video_layer *layer)\n{\n  struct soft_render_data *render_data = graphics->render_data;\n  uint32_t *pixels;\n  unsigned int pitch;\n  unsigned int bpp;\n\n  soft_lock_buffer(render_data, &pixels, &pitch, &bpp, NULL);\n  render_layer(pixels, SCREEN_PIX_W, SCREEN_PIX_H, pitch, bpp, graphics, layer);\n  soft_unlock_buffer(render_data);\n}\n\nstatic void soft_render_cursor(struct graphics_data *graphics, unsigned int x,\n unsigned int y, uint16_t color, unsigned int lines, unsigned int offset)\n{\n  struct soft_render_data *render_data = graphics->render_data;\n  uint32_t *pixels;\n  unsigned int pitch;\n  unsigned int bpp;\n  uint32_t flatcolor;\n\n  soft_lock_buffer(render_data, &pixels, &pitch, &bpp, NULL);\n\n  if(bpp == 8)\n  {\n    color &= 0xFF;\n    flatcolor = (color << 24) | (color << 16) | (color << 8) | color;\n  }\n  else\n  {\n    flatcolor = graphics->flat_intensity_palette[color];\n  }\n\n  if(bpp == 16)\n    flatcolor |= flatcolor << 16;\n\n  render_cursor(pixels, pitch, bpp, x, y, flatcolor, lines, offset);\n  soft_unlock_buffer(render_data);\n}\n\nstatic void soft_render_mouse(struct graphics_data *graphics,\n unsigned int x, unsigned int y, unsigned int w, unsigned int h)\n{\n  struct soft_render_data *render_data = graphics->render_data;\n  uint32_t *pixels;\n  unsigned int pitch;\n  unsigned int bpp;\n  uint32_t mask, amask;\n\n  soft_lock_buffer(render_data, &pixels, &pitch, &bpp, &amask);\n\n  if((bpp == 8) && !graphics->screen_mode)\n    mask = 0x0F0F0F0F;\n  else\n    mask = 0xFFFFFFFF;\n\n  render_mouse(pixels, pitch, bpp, x, y, mask, amask, w, h);\n  soft_unlock_buffer(render_data);\n}\n\nstatic void soft_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n#ifdef CONFIG_SDL\n  struct sdl_render_data *render_data = graphics->render_data;\n\n  if(render_data->shadow)\n  {\n    SDL_Rect src_rect;\n    SDL_Rect dest_rect;\n    SDL_GetSurfaceClipRect(render_data->shadow, &src_rect);\n    SDL_GetSurfaceClipRect(render_data->screen, &dest_rect);\n    SDL_BlitSurface(render_data->shadow, &src_rect,\n     render_data->screen, &dest_rect);\n  }\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n  SDL_UpdateWindowSurface(render_data->window);\n#else\n  SDL_Flip(render_data->screen);\n#endif\n#endif /* CONFIG_SDL */\n}\n\nvoid render_soft_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = soft_init_video;\n  renderer->free_video = soft_free_video;\n  renderer->create_window = soft_create_window;\n  renderer->resize_window = soft_create_window;\n  renderer->set_viewport = set_window_viewport_centered;\n#ifdef CONFIG_SDL\n  renderer->set_window_caption = sdl_set_window_caption;\n  renderer->set_window_icon = sdl_set_window_icon;\n#endif\n  renderer->update_colors = soft_update_colors;\n  renderer->render_graph = soft_render_graph;\n  renderer->render_layer = soft_render_layer;\n  renderer->render_cursor = soft_render_cursor;\n  renderer->render_mouse = soft_render_mouse;\n  renderer->sync_screen = soft_sync_screen;\n}\n"
  },
  {
    "path": "src/render_softscale.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Software renderer with SDL hardware accelerated scaling. This renderer takes\n * advantage of texture streaming, as well as texture format optimization that\n * is now performed by `sdlrender_set_video_mode`.\n */\n\n#include <stdlib.h>\n\n#include \"SDLmzx.h\"\n#include \"graphics.h\"\n#include \"render.h\"\n#include \"render_layer.h\"\n#include \"render_sdl.h\"\n#include \"renderers.h\"\n#include \"util.h\"\n\nstruct softscale_render_data\n{\n  struct sdl_render_data sdl;\n  SDL_Rect texture_rect;\n  void *private_pixels;\n  size_t private_pitch;\n  uint32_t *texture_pixels;\n  unsigned int texture_pitch;\n  unsigned int texture_width;\n  boolean enable_subsampling;\n};\n\nstatic void softscale_free_video(struct graphics_data *graphics)\n{\n  struct softscale_render_data *render_data = graphics->render_data;\n\n  if(render_data)\n  {\n    sdl_destruct_window(graphics);\n\n    graphics->render_data = NULL;\n    free(render_data->private_pixels);\n    free(render_data);\n  }\n}\n\nstatic boolean softscale_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  struct softscale_render_data *render_data = (struct softscale_render_data *)\n   cmalloc(sizeof(struct softscale_render_data));\n  if(!render_data)\n    return false;\n\n  memset(render_data, 0, sizeof(struct softscale_render_data));\n  graphics->render_data = render_data;\n  graphics->allow_resize = conf->allow_resize;\n  graphics->ratio = conf->video_ratio;\n  graphics->bits_per_pixel = 32;\n\n  // This renderer can technically do 8bpp indexed or RGB332. The former is\n  // too slow to be worthwhile and the latter requires fixing assumptions in\n  // the layer renderer and a new set_colors8 (also, it looks awful).\n  if(conf->force_bpp == BPP_AUTO || conf->force_bpp == 16 || conf->force_bpp == 32)\n    graphics->bits_per_pixel = conf->force_bpp;\n\n  snprintf(graphics->sdl_render_driver, ARRAY_SIZE(graphics->sdl_render_driver),\n   \"%s\", conf->sdl_render_driver);\n\n  return true;\n}\n\nstatic boolean softscale_create_window(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct softscale_render_data *render_data =\n   (struct softscale_render_data *)graphics->render_data;\n  SDL_Texture *tex;\n  void *tmp;\n\n  if(!sdl_create_window_renderer(graphics, window, false))\n    return false;\n\n  // YUV texture modes are effectively 16-bit to SDL, but MegaZeux treats them\n  // like 32-bit most of the time. This means each MZX pixel needs two YUV\n  // pixels when subsampling is off.\n  render_data->texture_width = SCREEN_PIX_W;\n  if(render_data->sdl.rgb_to_yuv)\n    render_data->texture_width <<= 1;\n\n  render_data->texture_pixels = NULL;\n\n  if(!render_data->sdl.use_texture_streaming)\n  {\n    render_data->private_pitch = render_data->texture_width *\n     SDL_BYTESPERPIXEL(render_data->sdl.texture_format);\n\n    tmp = crealloc(render_data->private_pixels, render_data->private_pitch * SCREEN_PIX_H);\n    if(!tmp)\n      goto err_free;\n\n    render_data->private_pixels = tmp;\n  }\n\n  // Initialize the screen texture.\n  tex = SDL_CreateTexture(render_data->sdl.renderer, render_data->sdl.texture_format,\n   SDL_TEXTUREACCESS_STREAMING, render_data->texture_width, SCREEN_PIX_H);\n  if(!tex)\n  {\n    warn(\"Failed to create texture: %s\\n\", SDL_GetError());\n    goto err_free;\n  }\n  render_data->sdl.texture[0] = tex;\n\n  sdl_set_texture_scale_mode(graphics, window, 0, true);\n  return true;\n\nerr_free:\n  sdl_destruct_window(graphics);\n  return false;\n}\n\nstatic boolean softscale_resize_callback(struct graphics_data *graphics,\n struct video_window *window)\n{\n  sdl_set_texture_scale_mode(graphics, window, 0, true);\n  return true;\n}\n\n/**\n * Prepare the texture for streaming. This is a lot faster than handling a\n * separate pixels buffer. If the texture is already locked, only return the\n * stored pixels and pitch.\n */\nstatic void softscale_lock_texture(struct softscale_render_data *render_data,\n boolean is_render_graph, uint32_t **pixels, unsigned int *pitch, unsigned int *bpp)\n{\n  if(!render_data->texture_pixels)\n  {\n    SDL_Rect *texture_rect = &(render_data->texture_rect);\n    void *pixels;\n    int pitch;\n\n    texture_rect->x = 0;\n    texture_rect->y = 0;\n    texture_rect->w = render_data->texture_width;\n    texture_rect->h = SCREEN_PIX_H;\n\n    if(is_render_graph && render_data->sdl.allow_subsampling)\n    {\n      // Chroma subsampling can be used to draw half as much at the cost of\n      // color accuracy. This trick only works with render_graph.\n      texture_rect->w = render_data->texture_width / 2;\n      render_data->enable_subsampling = true;\n    }\n    else\n      render_data->enable_subsampling = false;\n\n    if(render_data->sdl.use_texture_streaming)\n    {\n      SDL_LockTexture(render_data->sdl.texture[0], texture_rect, &pixels, &pitch);\n      render_data->texture_pixels = pixels;\n      render_data->texture_pitch = pitch;\n    }\n    else\n    {\n      render_data->texture_pixels = render_data->private_pixels;\n      render_data->texture_pitch = render_data->private_pitch;\n    }\n  }\n\n  *pixels = render_data->texture_pixels;\n  *pitch = render_data->texture_pitch;\n  *bpp = render_data->enable_subsampling ? 16 : render_data->sdl.texture_bpp;\n}\n\n/**\n * Commit the newly rendered screen to the texture and invalidate the pixel\n * array pointer.\n */\nstatic void softscale_unlock_texture(struct softscale_render_data *render_data)\n{\n  if(render_data->texture_pixels)\n  {\n    if(render_data->sdl.use_texture_streaming)\n    {\n      SDL_UnlockTexture(render_data->sdl.texture[0]);\n    }\n    else\n    {\n      SDL_UpdateTexture(render_data->sdl.texture[0],\n       &render_data->texture_rect, render_data->texture_pixels,\n       render_data->texture_pitch);\n    }\n    render_data->texture_pixels = NULL;\n  }\n}\n\nstatic void softscale_render_graph(struct graphics_data *graphics)\n{\n  struct softscale_render_data *render_data = graphics->render_data;\n  int mode = graphics->screen_mode;\n  uint32_t *pixels;\n  unsigned int pitch;\n  unsigned int bpp;\n\n  softscale_lock_texture(render_data, true, &pixels, &pitch, &bpp);\n\n  if(render_data->enable_subsampling)\n  {\n    if(!mode)\n      render_graph16((uint16_t *)pixels, pitch, graphics,\n       render_data->sdl.subsample_set_colors);\n    else\n      render_graph16((uint16_t *)pixels, pitch, graphics, set_colors32[mode]);\n  }\n  else\n\n  if(bpp == 16)\n    render_graph16((uint16_t *)pixels, pitch, graphics, set_colors16[mode]);\n  else\n\n  if(bpp == 32)\n  {\n    if(!mode)\n      render_graph32(pixels, pitch, graphics);\n    else\n      render_graph32s(pixels, pitch, graphics);\n  }\n}\n\nstatic void softscale_render_layer(struct graphics_data *graphics,\n struct video_layer *layer)\n{\n  struct softscale_render_data *render_data = graphics->render_data;\n  uint32_t *pixels;\n  unsigned int pitch;\n  unsigned int bpp;\n\n  softscale_lock_texture(render_data, false, &pixels, &pitch, &bpp);\n  render_layer(pixels, SCREEN_PIX_W, SCREEN_PIX_H, pitch, bpp, graphics, layer);\n}\n\nstatic void softscale_render_cursor(struct graphics_data *graphics, unsigned int x,\n unsigned int y, uint16_t color, unsigned int lines, unsigned int offset)\n{\n  struct softscale_render_data *render_data = graphics->render_data;\n  uint32_t flatcolor;\n  uint32_t *pixels;\n  unsigned int pitch;\n  unsigned int bpp;\n\n  flatcolor = graphics->flat_intensity_palette[color];\n\n  softscale_lock_texture(render_data, false, &pixels, &pitch, &bpp);\n\n  if(bpp == 16 && !render_data->enable_subsampling)\n    flatcolor |= flatcolor << 16;\n\n  render_cursor(pixels, pitch, bpp, x, y, flatcolor, lines, offset);\n}\n\nstatic void softscale_render_mouse(struct graphics_data *graphics,\n unsigned int x, unsigned int y, unsigned int w, unsigned int h)\n{\n  struct softscale_render_data *render_data = graphics->render_data;\n  uint32_t amask = render_data->sdl.texture_amask;\n  uint32_t *pixels;\n  unsigned int pitch;\n  unsigned int bpp;\n\n  softscale_lock_texture(render_data, false, &pixels, &pitch, &bpp);\n\n  if(bpp == 16)\n    amask |= amask << 16;\n\n  render_mouse(pixels, pitch, bpp, x, y, 0xFFFFFFFF, amask, w, h);\n}\n\nstatic void softscale_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct softscale_render_data *render_data = graphics->render_data;\n  SDL_Renderer *renderer = render_data->sdl.renderer;\n  SDL_Texture *texture = render_data->sdl.texture[0];\n  SDL_Rect *texture_rect = &(render_data->texture_rect);\n  SDL_Rect_mzx src_rect;\n  SDL_Rect_mzx dest_rect;\n\n  src_rect = sdl_render_rect(texture_rect->x, texture_rect->y,\n   texture_rect->w, texture_rect->h);\n\n  dest_rect.x = window->viewport_x;\n  dest_rect.y = window->viewport_y;\n  dest_rect.w = window->viewport_width;\n  dest_rect.h = window->viewport_height;\n\n  softscale_unlock_texture(render_data);\n\n  SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);\n  SDL_RenderClear(renderer);\n\n  SDL_RenderTexture(renderer, texture, &src_rect, &dest_rect);\n  SDL_RenderPresent(renderer);\n}\n\nvoid render_softscale_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = softscale_init_video;\n  renderer->free_video = softscale_free_video;\n  renderer->create_window = softscale_create_window;\n  renderer->resize_window = sdl_resize_window;\n  renderer->resize_callback = softscale_resize_callback;\n  renderer->set_viewport = set_window_viewport_scaled;\n  renderer->set_window_caption = sdl_set_window_caption;\n  renderer->set_window_icon = sdl_set_window_icon;\n  renderer->update_colors = sdl_update_colors;\n  renderer->render_graph = softscale_render_graph;\n  renderer->render_layer = softscale_render_layer;\n  renderer->render_cursor = softscale_render_cursor;\n  renderer->render_mouse = softscale_render_mouse;\n  renderer->sync_screen = softscale_sync_screen;\n}\n"
  },
  {
    "path": "src/render_yuv.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n\n#include \"graphics.h\"\n#include \"render.h\"\n#include \"render_layer.h\"\n#include \"render_sdl.h\"\n#include \"renderers.h\"\n#include \"util.h\"\n#include \"yuv.h\"\n\n#if SDL_VERSION_ATLEAST(2,0,0)\n#error Overlay renderer no longer supported for SDL 2+. Use softscale!\n#endif\n\n#define YUV1_OVERLAY_WIDTH   (SCREEN_PIX_W * 2)\n#define YUV1_OVERLAY_HEIGHT  SCREEN_PIX_H\n\n#define YUV2_OVERLAY_WIDTH   SCREEN_PIX_W\n#define YUV2_OVERLAY_HEIGHT  SCREEN_PIX_H\n\nstruct yuv_render_data\n{\n  struct sdl_render_data sdl;\n  Uint32 bpp;\n};\n\nstatic boolean yuv_set_video_mode_size(struct graphics_data *graphics,\n struct video_window *window, int yuv_width, int yuv_height)\n{\n  struct yuv_render_data *render_data = graphics->render_data;\n\n  window->bits_per_pixel = 32;\n  if(!sdl_check_video_mode(graphics, window, true, sdl_flags(window) | SDL_ANYFORMAT))\n    return false;\n\n  // the YUV renderer _requires_ 32bit colour\n  render_data->sdl.screen =\n   SDL_SetVideoMode(window->width_px, window->height_px, 32,\n    sdl_flags(window) | SDL_ANYFORMAT);\n\n  if(!render_data->sdl.screen)\n    goto err_free;\n\n  // try with a YUY2 pixel format first\n  render_data->sdl.rgb_to_yuv = rgb_to_yuy2;\n  render_data->sdl.subsample_set_colors = yuy2_subsample_set_colors_mzx;\n  render_data->sdl.overlay = SDL_CreateYUVOverlay(yuv_width, yuv_height,\n   SDL_YUY2_OVERLAY, render_data->sdl.screen);\n\n  // didn't work, try with a UYVY pixel format next\n  if(!render_data->sdl.overlay)\n  {\n    render_data->sdl.rgb_to_yuv = rgb_to_uyvy;\n    render_data->sdl.subsample_set_colors = uyvy_subsample_set_colors_mzx;\n    render_data->sdl.overlay = SDL_CreateYUVOverlay(yuv_width, yuv_height,\n     SDL_UYVY_OVERLAY, render_data->sdl.screen);\n  }\n\n  // Since we support this format now too, might as well try.\n  if(!render_data->sdl.overlay)\n  {\n    render_data->sdl.rgb_to_yuv = rgb_to_yvyu;\n    render_data->sdl.subsample_set_colors = yvyu_subsample_set_colors_mzx;\n    render_data->sdl.overlay = SDL_CreateYUVOverlay(yuv_width, yuv_height,\n     SDL_YVYU_OVERLAY, render_data->sdl.screen);\n  }\n\n  // failed to create an overlay\n  if(!render_data->sdl.overlay)\n    goto err_free;\n\n  // Wipe the letterbox area, if any.\n  // This must be performed on the window surface, not the overlay.\n  SDL_FillRect(render_data->sdl.screen, NULL, 0);\n  SDL_Flip(render_data->sdl.screen);\n  return true;\n\nerr_free:\n  sdl_destruct_window(graphics);\n  return false;\n}\n\nstatic boolean yuv1_set_video_mode(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct yuv_render_data *render_data = graphics->render_data;\n  render_data->bpp = 32;\n\n  return yuv_set_video_mode_size(graphics, window,\n   YUV1_OVERLAY_WIDTH, YUV1_OVERLAY_HEIGHT);\n}\n\nstatic boolean yuv2_set_video_mode(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct yuv_render_data *render_data = graphics->render_data;\n  render_data->bpp = 16;\n\n  return yuv_set_video_mode_size(graphics, window,\n   YUV2_OVERLAY_WIDTH, YUV2_OVERLAY_HEIGHT);\n}\n\nstatic boolean yuv_init_video(struct graphics_data *graphics,\n struct config_info *conf)\n{\n  struct yuv_render_data *render_data =\n   (struct yuv_render_data *)cmalloc(sizeof(struct yuv_render_data));\n  if(!render_data)\n    return false;\n\n  memset(render_data, 0, sizeof(struct yuv_render_data));\n\n  graphics->bits_per_pixel = 32;\n  graphics->render_data = render_data;\n  graphics->allow_resize = conf->allow_resize;\n  graphics->ratio = conf->video_ratio;\n  return true;\n}\n\nstatic void yuv_free_video(struct graphics_data *graphics)\n{\n  sdl_destruct_window(graphics);\n\n  free(graphics->render_data);\n  graphics->render_data = NULL;\n}\n\nstatic void yuv_lock_overlay(struct yuv_render_data *render_data,\n Uint32 **pixels, Uint32 *pitch)\n{\n  SDL_LockYUVOverlay(render_data->sdl.overlay);\n\n  *pixels = (Uint32 *)render_data->sdl.overlay->pixels[0];\n  *pitch = render_data->sdl.overlay->pitches[0];\n}\n\nstatic void yuv_unlock_overlay(struct yuv_render_data *render_data)\n{\n  SDL_UnlockYUVOverlay(render_data->sdl.overlay);\n}\n\nstatic void yuv_render_graph(struct graphics_data *graphics)\n{\n  struct yuv_render_data *render_data = graphics->render_data;\n  Uint32 mode = graphics->screen_mode;\n  Uint32 bpp = render_data->bpp;\n  Uint32 *pixels;\n  Uint32 pitch;\n\n  yuv_lock_overlay(render_data, &pixels, &pitch);\n\n  if(bpp == 16)\n  {\n    // Subsampled mode\n    if(!mode)\n      render_graph16((Uint16 *)pixels, pitch, graphics,\n       render_data->sdl.subsample_set_colors);\n    else\n      render_graph16((Uint16 *)pixels, pitch, graphics, set_colors32[mode]);\n  }\n  else\n  {\n    if(!mode)\n      render_graph32(pixels, pitch, graphics);\n    else\n      render_graph32s(pixels, pitch, graphics);\n  }\n\n  yuv_unlock_overlay(render_data);\n}\n\nstatic void yuv1_render_layer(struct graphics_data *graphics,\n struct video_layer *layer)\n{\n  struct yuv_render_data *render_data = graphics->render_data;\n  Uint32 bpp = render_data->bpp;\n  Uint32 *pixels;\n  Uint32 pitch;\n\n  yuv_lock_overlay(render_data, &pixels, &pitch);\n\n  render_layer(pixels, SCREEN_PIX_W, SCREEN_PIX_H, pitch, bpp, graphics, layer);\n\n  yuv_unlock_overlay(render_data);\n}\n\nstatic void yuv_render_cursor(struct graphics_data *graphics, unsigned int x,\n unsigned int y, uint16_t color, unsigned int lines, unsigned int offset)\n{\n  struct yuv_render_data *render_data = graphics->render_data;\n  Uint32 bpp = render_data->bpp;\n  Uint32 *pixels;\n  Uint32 pitch;\n\n  yuv_lock_overlay(render_data, &pixels, &pitch);\n\n  render_cursor(pixels, pitch, bpp, x, y,\n   graphics->flat_intensity_palette[color], lines, offset);\n\n  yuv_unlock_overlay(render_data);\n}\n\nstatic void yuv_render_mouse(struct graphics_data *graphics,\n unsigned int x, unsigned int y, unsigned int w, unsigned int h)\n{\n  struct yuv_render_data *render_data = graphics->render_data;\n  Uint32 bpp = render_data->bpp;\n  Uint32 *pixels;\n  Uint32 pitch;\n\n  yuv_lock_overlay(render_data, &pixels, &pitch);\n\n  render_mouse(pixels, pitch, bpp, x, y, 0xFFFFFFFF, 0x0, w, h);\n\n  yuv_unlock_overlay(render_data);\n}\n\nstatic void yuv_sync_screen(struct graphics_data *graphics,\n struct video_window *window)\n{\n  struct yuv_render_data *render_data = graphics->render_data;\n  SDL_Rect rect;\n\n  rect.x = window->viewport_x;\n  rect.y = window->viewport_y;\n  rect.w = window->viewport_width;\n  rect.h = window->viewport_height;\n\n  SDL_DisplayYUVOverlay(render_data->sdl.overlay, &rect);\n}\n\nvoid render_yuv1_register(struct renderer *renderer)\n{\n  memset(renderer, 0, sizeof(struct renderer));\n  renderer->init_video = yuv_init_video;\n  renderer->free_video = yuv_free_video;\n  renderer->create_window = yuv1_set_video_mode;\n  renderer->resize_window = yuv1_set_video_mode;\n  renderer->set_viewport = set_window_viewport_scaled;\n  renderer->set_window_caption = sdl_set_window_caption;\n  renderer->set_window_icon = sdl_set_window_icon;\n  renderer->update_colors = sdl_update_colors;\n  renderer->render_graph = yuv_render_graph;\n  renderer->render_layer = yuv1_render_layer;\n  renderer->render_cursor = yuv_render_cursor;\n  renderer->render_mouse = yuv_render_mouse;\n  renderer->sync_screen = yuv_sync_screen;\n}\n\nvoid render_yuv2_register(struct renderer *renderer)\n{\n  render_yuv1_register(renderer);\n  renderer->create_window = yuv2_set_video_mode;\n  renderer->resize_window = yuv2_set_video_mode;\n  renderer->render_layer = NULL;\n}\n"
  },
  {
    "path": "src/renderers.h",
    "content": "/* MegaZeux\n *\n *   Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __RENDERERS_H\n#define __RENDERERS_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"graphics.h\"\n\nstruct renderer_data\n{\n  const char *name;\n  void (*reg)(struct renderer *renderer);\n};\n\n#if defined(CONFIG_RENDER_SOFT)\nvoid render_soft_register(struct renderer *renderer);\n#endif\n#if defined(CONFIG_RENDER_SOFTSCALE)\nvoid render_softscale_register(struct renderer *renderer);\n#endif\n#if defined(CONFIG_RENDER_SDLACCEL)\nvoid render_sdlaccel_register(struct renderer *renderer);\n#endif\n#if defined(CONFIG_RENDER_GL_FIXED)\nvoid render_gl1_register(struct renderer *renderer);\nvoid render_gl2_register(struct renderer *renderer);\n#endif\n#if defined(CONFIG_RENDER_GL_PROGRAM)\nvoid render_glsl_register(struct renderer *renderer);\nvoid render_glsl_software_register(struct renderer *renderer);\nvoid render_auto_glsl_register(struct renderer *renderer);\n#endif\n#if defined(CONFIG_RENDER_YUV)\nvoid render_yuv1_register(struct renderer *renderer);\nvoid render_yuv2_register(struct renderer *renderer);\n#endif\n#if defined(CONFIG_RENDER_GP2X)\nvoid render_gp2x_register(struct renderer *renderer);\n#endif\n#if defined(CONFIG_NDS)\nvoid render_nds_register(struct renderer *renderer);\n#endif\n#if defined(CONFIG_3DS)\nvoid render_ctr_register(struct renderer *renderer);\n#endif\n#if defined(CONFIG_WII)\n#if defined(CONFIG_RENDER_GX)\nvoid render_gx_register(struct renderer *renderer);\n#endif\nvoid render_xfb_register(struct renderer *renderer);\n#endif\n#if defined(CONFIG_DJGPP)\nvoid render_ega_register(struct renderer *renderer);\nvoid render_svga_register(struct renderer *renderer);\n#endif\n#if defined(CONFIG_DREAMCAST)\nvoid render_dc_register(struct renderer *renderer);\nvoid render_dc_fb_register(struct renderer *renderer);\n#endif\n\n__M_END_DECLS\n\n#endif // __RENDERERS_H\n"
  },
  {
    "path": "src/robot.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <ctype.h>\n#include <assert.h>\n\n#include \"board.h\"\n#include \"const.h\"\n#include \"counter.h\"\n#include \"error.h\"\n#include \"event.h\"\n#include \"expr.h\"\n#include \"extmem.h\"\n#include \"game_ops.h\"\n#include \"game_player.h\"\n#include \"graphics.h\"\n#include \"idarray.h\"\n#include \"legacy_rasm.h\"\n#include \"memcasecmp.h\"\n#include \"robot.h\"\n#include \"rasm.h\"\n#include \"scrdisp.h\"\n#include \"str.h\"\n#include \"util.h\"\n#include \"window.h\"\n#include \"world.h\"\n#include \"world_format.h\"\n#include \"world_struct.h\"\n#include \"io/memfile.h\"\n#include \"io/zip.h\"\n\nvoid create_blank_robot(struct robot *cur_robot)\n{\n  int i;\n\n  // Clear the robot before use.\n  cur_robot->robot_name[0] = 0;\n  cur_robot->robot_char = 'R';\n\n  cur_robot->stack = NULL;\n  cur_robot->stack_size = 0;\n  cur_robot->stack_pointer = 0;\n\n  cur_robot->label_list = NULL;\n  cur_robot->num_labels = 0;\n\n  cur_robot->program_bytecode_length = 0;\n  cur_robot->program_bytecode = NULL;\n  cur_robot->program_source_length = 0;\n  cur_robot->program_source = NULL;\n\n  cur_robot->cur_prog_line = 1;\n  cur_robot->pos_within_line = 0;\n  cur_robot->robot_cycle = 0;\n  cur_robot->cycle_count = 0;\n  cur_robot->bullet_type = 1;\n  cur_robot->is_locked = 0;\n  cur_robot->can_lavawalk = 0;\n  cur_robot->can_goopwalk = 0;\n  cur_robot->walk_dir = 0;\n  cur_robot->last_touch_dir = 0;\n  cur_robot->last_shot_dir = 0;\n  cur_robot->xpos = -1;\n  cur_robot->ypos = -1;\n  cur_robot->compat_xpos = -1;\n  cur_robot->compat_ypos = -1;\n  cur_robot->status = 0;\n  cur_robot->used = 1;\n\n  cur_robot->loop_count = 0;\n\n  for(i = 0; i<32; i++)\n    cur_robot->local[i] = 0;\n\n#ifdef CONFIG_EDITOR\n  cur_robot->command_map = NULL;\n  cur_robot->command_map_length = 0;\n\n  cur_robot->commands_total = 0;\n  cur_robot->commands_cycle = 0;\n#endif\n}\n\nvoid create_blank_robot_program(struct robot *cur_robot)\n{\n  // Add a blank robot program to a robot.\n\n#ifdef CONFIG_DEBYTECODE\n  cur_robot->program_source_length = 2;\n  cur_robot->program_source = cmalloc(2);\n  strcpy(cur_robot->program_source, \"\\n\");\n#else\n  cur_robot->program_bytecode_length = 2;\n  cur_robot->program_bytecode = cmalloc(2);\n  strcpy(cur_robot->program_bytecode, \"\\xFF\");\n#endif\n}\n\n#define err_if_skipped(idn) if(last_ident < idn) { goto err_invalid; }\n\nstatic int load_robot_from_memory(struct world *mzx_world, struct robot *cur_robot,\n struct memfile *mf, int savegame, int file_version)\n{\n#ifdef CONFIG_DEBYTECODE\n  char *program_legacy_bytecode = NULL;\n  size_t program_legacy_bytecode_length = 0;\n  char *saved_label_zaps = NULL;\n  int num_label_zaps = 0;\n#endif\n  struct memfile prop;\n  int last_ident = -1;\n  int v_size;\n  int ident;\n  int size;\n  int i;\n\n  create_blank_robot(cur_robot);\n\n  while(next_prop(&prop, &ident, &size, mf))\n  {\n    switch(ident)\n    {\n      case RPROP_EOF:\n        mfseek(mf, 0, SEEK_END);\n        break;\n\n      case RPROP_ROBOT_NAME:\n        size = MIN(size, ROBOT_NAME_SIZE - 1);\n        mfread(cur_robot->robot_name, size, 1, &prop);\n        cur_robot->robot_name[size] = '\\0';\n        break;\n\n      case RPROP_ROBOT_CHAR:\n        err_if_skipped(RPROP_ROBOT_NAME);\n        cur_robot->robot_char = load_prop_int(&prop);\n        break;\n\n      case RPROP_XPOS:\n        err_if_skipped(RPROP_ROBOT_CHAR);\n        cur_robot->xpos = load_prop_int_s(&prop, -1, 32767);\n        cur_robot->compat_xpos = cur_robot->xpos;\n        break;\n\n      case RPROP_YPOS:\n        err_if_skipped(RPROP_XPOS);\n        cur_robot->ypos = load_prop_int_s(&prop, -1, 32767);\n        cur_robot->compat_ypos = cur_robot->ypos;\n        break;\n\n      // Legacy world, now save\n      case RPROP_CUR_PROG_LINE:\n        cur_robot->cur_prog_line = load_prop_int(&prop);\n        break;\n\n      case RPROP_POS_WITHIN_LINE:\n        cur_robot->pos_within_line = load_prop_int(&prop);\n        break;\n\n      case RPROP_ROBOT_CYCLE:\n        cur_robot->robot_cycle = load_prop_int(&prop);\n        break;\n\n      case RPROP_CYCLE_COUNT:\n        cur_robot->cycle_count = load_prop_int(&prop);\n        break;\n\n      case RPROP_BULLET_TYPE:\n        cur_robot->bullet_type = load_prop_int(&prop) & 255;\n        break;\n\n      case RPROP_IS_LOCKED:\n        cur_robot->is_locked = load_prop_boolean(&prop);\n        break;\n\n      case RPROP_CAN_LAVAWALK:\n        cur_robot->can_lavawalk = load_prop_int(&prop) & 255;\n        break;\n\n      case RPROP_WALK_DIR:\n        cur_robot->walk_dir = load_prop_int(&prop);\n        break;\n\n      case RPROP_LAST_TOUCH_DIR:\n        cur_robot->last_touch_dir = load_prop_int(&prop);\n        break;\n\n      case RPROP_LAST_SHOT_DIR:\n        cur_robot->last_shot_dir = load_prop_int(&prop);\n        break;\n\n      case RPROP_STATUS:\n        cur_robot->status = load_prop_int(&prop);\n        break;\n\n      // Legacy save\n      case RPROP_LOOP_COUNT:\n        cur_robot->loop_count = load_prop_int(&prop);\n        break;\n\n      case RPROP_LOCALS:\n        for(i = 0; i < 32; i++)\n          cur_robot->local[i] = mfgetd(&prop);\n        break;\n\n      case RPROP_STACK_POINTER:\n        cur_robot->stack_pointer = load_prop_int(&prop) & ~1;\n        break;\n\n      case RPROP_STACK:\n        // # of stack values is file size /4.\n        // Furthermore, there should be an even number of values, and a count\n        // over ROBOT_MAX_STACK is invalid.\n        if(cur_robot->stack)\n          break;\n        size = MIN((size / 4) & ~1, ROBOT_MAX_STACK);\n        cur_robot->stack_size = size;\n        cur_robot->stack = cmalloc(size * sizeof(int));\n        for(i = 0; i < size; i++)\n          cur_robot->stack[i] = mfgetd(&prop);\n        break;\n\n      // New\n      case RPROP_CAN_GOOPWALK:\n        cur_robot->can_goopwalk = load_prop_int(&prop) & 255;\n        break;\n\n      // Source/bytecode are slated for separation from these files.\n      // When that happens, it might also be good to merge all robots into\n      // one file per board.\n#ifdef CONFIG_DEBYTECODE\n\n      case RPROP_PROGRAM_LABEL_ZAPS:\n      {\n        err_if_skipped(RPROP_YPOS);\n\n        // This does not need to be supported in the world format--zaps\n        // in world files should be expressed directly in the source code.\n        if(!savegame)\n          break;\n\n        // These have to be handled after loading the program.\n        saved_label_zaps = (char *)prop.start;\n        num_label_zaps = size;\n        break;\n      }\n\n      case RPROP_PROGRAM_SOURCE:\n      {\n        err_if_skipped(RPROP_YPOS);\n\n        if(cur_robot->program_source || program_legacy_bytecode)\n          break;\n\n        if(size > MAX_OBJ_SIZE)\n          goto err_invalid;\n\n        // This program is source.\n        cur_robot->program_source = cmalloc(size + 1);\n        cur_robot->program_source_length = size;\n\n        mfread(cur_robot->program_source, size, 1, &prop);\n        cur_robot->program_source[size] = 0;\n        break;\n      }\n\n      case RPROP_PROGRAM_BYTECODE:\n      {\n        err_if_skipped(RPROP_YPOS);\n\n        if(cur_robot->program_source || program_legacy_bytecode)\n          break;\n\n        if(size > MAX_OBJ_SIZE)\n          goto err_invalid;\n\n        // This field should never be found in files saved in >= VERSION_SOURCE.\n        if(file_version < VERSION_SOURCE)\n        {\n          program_legacy_bytecode = cmalloc(size);\n          program_legacy_bytecode_length = size;\n\n          mfread(program_legacy_bytecode, size, 1, &prop);\n        }\n        break;\n      }\n\n#else /* !CONFIG_DEBYTECODE */\n\n      case RPROP_PROGRAM_BYTECODE:\n      {\n        err_if_skipped(RPROP_YPOS);\n\n        if(cur_robot->program_bytecode)\n          break;\n\n        if(size > MAX_OBJ_SIZE)\n          goto err_invalid;\n\n        if(size > 0)\n        {\n          cur_robot->program_bytecode = cmalloc(size);\n          cur_robot->program_bytecode_length = size;\n\n          mfread(cur_robot->program_bytecode, size, 1, &prop);\n        }\n        break;\n      }\n\n#endif /* !CONFIG_DEBYTECODE */\n\n      default:\n        break;\n    }\n    last_ident = ident;\n  }\n\n  // Check the stack for invalid states...\n  if(savegame && cur_robot->stack_pointer)\n  {\n    if(!cur_robot->stack || !cur_robot->stack_size || cur_robot->stack_pointer < 0)\n    {\n      cur_robot->stack_pointer = 0;\n    }\n    else\n\n    if(cur_robot->stack_pointer > cur_robot->stack_size)\n      cur_robot->stack_pointer = cur_robot->stack_size;\n  }\n\n#ifdef CONFIG_DEBYTECODE\n\n  if(program_legacy_bytecode)\n  {\n    // Load and translate legacy bytecode to modern source.\n    v_size = (int)program_legacy_bytecode_length;\n    if(!validate_legacy_bytecode(&program_legacy_bytecode, &v_size,\n     &cur_robot->cur_prog_line))\n    {\n      free(program_legacy_bytecode);\n      goto err_invalid;\n    }\n\n    cur_robot->program_source =\n     legacy_disassemble_program(program_legacy_bytecode, v_size,\n      &(cur_robot->program_source_length), true, 10);\n  }\n\n  if(!cur_robot->program_source)\n  {\n    // Saved without a program? Give it a blank program.\n    create_blank_robot_program(cur_robot);\n    cur_robot->cur_prog_line = 0;\n  }\n  else\n  {\n    // The saved robot bytecode offsets are actually command numbers (3.00+)\n    // or legacy bytecode offsets (<3.00). Translate the loaded offsets into\n    // usable bytecode offsets. This may compile the robot program.\n    translate_robot_bytecode_offsets(mzx_world, cur_robot, file_version);\n\n    if(saved_label_zaps)\n    {\n      // Apply saved label zap data.\n      prepare_robot_bytecode(mzx_world, cur_robot);\n\n      if(num_label_zaps == cur_robot->num_labels)\n      {\n        for(i = 0; i < num_label_zaps; i++)\n          cur_robot->label_list[i]->zapped = saved_label_zaps[i];\n      }\n    }\n  }\n\n  free(program_legacy_bytecode);\n\n#else /* !CONFIG_DEBYTECODE */\n\n  if(!cur_robot->program_bytecode)\n    goto err_invalid;\n\n  // Validate to fix trash programs or out-of-bounds program offsets.\n  v_size = cur_robot->program_bytecode_length;\n  if(!validate_legacy_bytecode(&cur_robot->program_bytecode, &v_size,\n   &(cur_robot->cur_prog_line)))\n    goto err_invalid;\n\n  cur_robot->program_bytecode_length = v_size;\n\n  // The stack must also be checked for invalid offsets.\n  fix_robot_stack_offsets(cur_robot);\n\n  // Create the label cache for this robot\n  cache_robot_labels(cur_robot);\n\n#endif\n\n  return 0;\n\nerr_invalid:\n  // One of the essential fields of the robot is missing altogether.\n\n  // Nuke the robot\n  clear_robot_contents(cur_robot);\n  create_blank_robot(cur_robot);\n  create_blank_robot_program(cur_robot);\n  strcpy(cur_robot->robot_name, \"<<error>>\");\n  cur_robot->cur_prog_line = 0;\n\n#ifdef CONFIG_DEBYTECODE\n  free(program_legacy_bytecode);\n#endif\n\n  // Trigger error message\n  return -1;\n}\n\nvoid load_robot(struct world *mzx_world, struct robot *cur_robot,\n struct zip_archive *zp, int savegame, int file_version)\n{\n  char *buffer = NULL;\n  size_t actual_size;\n  struct memfile mf;\n\n  unsigned int method;\n  unsigned int board_id;\n  unsigned int id;\n  boolean is_stream = false;\n\n  zip_get_next_mzx_file_id(zp, NULL, &board_id, &id);\n  zip_get_next_method(zp, &method);\n\n  // We aren't saving or loading null robots.\n  cur_robot->world_version = mzx_world->version;\n  cur_robot->used = 1;\n\n  // If this is an uncompressed memory zip, we can read the memory directly.\n  if(zp->is_memory && method == ZIP_M_NONE)\n  {\n    zip_read_open_mem_stream(zp, &mf);\n    is_stream = true;\n  }\n  else\n  {\n    zip_get_next_uncompressed_size(zp, &actual_size);\n    buffer = cmalloc(actual_size);\n    zip_read_file(zp, buffer, actual_size, &actual_size);\n\n    mfopen(buffer, actual_size, &mf);\n  }\n\n  if(load_robot_from_memory(mzx_world, cur_robot, &mf, savegame, file_version))\n  {\n    error_message(E_BOARD_ROBOT_CORRUPT, (board_id << 8)|id, NULL);\n  }\n\n  if(is_stream)\n    zip_read_close_stream(zp);\n\n  free(buffer);\n}\n\nstruct robot *load_robot_allocate(struct world *mzx_world,\n struct zip_archive *zp, int savegame, int file_version)\n{\n  struct robot *cur_robot = cmalloc(sizeof(struct robot));\n\n  load_robot(mzx_world, cur_robot, zp, savegame, file_version);\n\n  return cur_robot;\n}\n\nstruct scroll *load_scroll_allocate(struct zip_archive *zp)\n{\n  struct scroll *cur_scroll = ccalloc(1, sizeof(struct scroll));\n\n  size_t actual_size;\n  void *buffer;\n  struct memfile mf;\n  struct memfile prop;\n  int ident;\n  int size;\n\n  zip_get_next_uncompressed_size(zp, &actual_size);\n  if(actual_size > SCROLL_PROPS_SIZE + MAX_OBJ_SIZE)\n    actual_size = SCROLL_PROPS_SIZE + MAX_OBJ_SIZE;\n\n  buffer = cmalloc(actual_size);\n  if(!buffer)\n  {\n    zip_skip_file(zp);\n    goto err;\n  }\n\n  // We aren't saving or loading null scrolls.\n  cur_scroll->used = 1;\n\n  zip_read_file(zp, buffer, actual_size, &actual_size);\n\n  mfopen(buffer, actual_size, &mf);\n\n  while(next_prop(&prop, &ident, &size, &mf))\n  {\n    switch(ident)\n    {\n      case SCRPROP_EOF:\n        mfseek(&mf, 0, SEEK_END);\n        break;\n\n      case SCRPROP_NUM_LINES:\n        cur_scroll->num_lines = load_prop_int(&prop);\n        break;\n\n      case SCRPROP_MESG:\n        if(cur_scroll->mesg)\n          break;\n        if(size < 3)\n          goto err;\n\n        // TODO: return value (scroll display doesn't support NULL here)\n        size = MIN(size, MAX_OBJ_SIZE);\n        cur_scroll->mesg_size = size;\n        cur_scroll->mesg = (char *)cmalloc(size);\n        mfread(cur_scroll->mesg, size, 1, &prop);\n        if(size > 0)\n          cur_scroll->mesg[size - 1] = '\\0';\n        break;\n\n      default:\n        break;\n    }\n  }\n\n  if(cur_scroll->mesg_size < 3)\n  {\nerr:\n    // We have an incomplete scroll, so slip in an empty scroll.\n    cur_scroll->num_lines = 1;\n    cur_scroll->mesg_size = 3;\n\n    // TODO: return value (scroll display doesn't support NULL here)\n    free(cur_scroll->mesg);\n    cur_scroll->mesg = (char *)cmalloc(3);\n    strcpy(cur_scroll->mesg, \"\\x01\\x0A\");\n  }\n\n  free(buffer);\n  return cur_scroll;\n}\n\nstruct sensor *load_sensor_allocate(struct zip_archive *zp)\n{\n  struct sensor *cur_sensor = ccalloc(1, sizeof(struct sensor));\n\n  size_t actual_size;\n  void *buffer;\n  struct memfile mf;\n  struct memfile prop;\n  int ident;\n  int size;\n\n  zip_get_next_uncompressed_size(zp, &actual_size);\n  if(actual_size > MAX_OBJ_SIZE) // SENSOR_PROPS_SIZE, but just in case...\n    actual_size = MAX_OBJ_SIZE;\n\n  buffer = cmalloc(actual_size);\n  if(!buffer)\n  {\n    zip_skip_file(zp);\n    return cur_sensor;\n  }\n\n  // We aren't saving or loading null sensors.\n  cur_sensor->used = 1;\n\n  zip_read_file(zp, buffer, actual_size, &actual_size);\n\n  mfopen(buffer, actual_size, &mf);\n\n  while(next_prop(&prop, &ident, &size, &mf))\n  {\n    switch(ident)\n    {\n      case SENPROP_EOF:\n        mfseek(&mf, 0, SEEK_END);\n        break;\n\n      case SENPROP_SENSOR_NAME:\n        size = MIN(size, ROBOT_NAME_SIZE - 1);\n        mfread(cur_sensor->sensor_name, size, 1, &prop);\n        cur_sensor->sensor_name[size] = '\\0';\n        break;\n\n      case SENPROP_SENSOR_CHAR:\n        cur_sensor->sensor_char = load_prop_int(&prop);\n        break;\n\n      case SENPROP_ROBOT_TO_MESG:\n        size = MIN(size, ROBOT_NAME_SIZE - 1);\n        mfread(cur_sensor->robot_to_mesg, size, 1, &prop);\n        cur_sensor->robot_to_mesg[size] = '\\0';\n        break;\n\n      default:\n        break;\n    }\n  }\n\n  // Zeros work fine as filler for incomplete sensors here\n\n  free(buffer);\n  return cur_sensor;\n}\n\nsize_t save_robot_calculate_size(struct world *mzx_world,\n struct robot *cur_robot, int savegame, int file_version)\n{\n  int size = ROBOT_PROPS_SIZE;\n\n  /* Saved name field size is version dependent, see save_prop_s_293. */\n  size += (file_version >= V293) ? strlen(cur_robot->robot_name) : ROBOT_NAME_SIZE;\n\n  if(savegame)\n  {\n    size += ROBOT_SAVE_PROPS_SIZE;\n    size += 4 * cur_robot->stack_size;\n  }\n\n#ifdef CONFIG_DEBYTECODE\n\n  size += cur_robot->program_source_length;\n\n  if(savegame)\n    size += PROP_HEADER_SIZE + cur_robot->num_labels;\n\n#else // !CONFIG_DEBYTECODE\n\n  size += cur_robot->program_bytecode_length;\n\n#endif // !CONFIG_DEBYTECODE\n\n  return size;\n}\n\nstatic void save_robot_to_memory(struct robot *cur_robot,\n struct memfile *mf, int savegame, int file_version)\n{\n  struct memfile prop;\n\n  save_prop_s_293(RPROP_ROBOT_NAME, cur_robot->robot_name, ROBOT_NAME_SIZE, mf);\n  save_prop_c(RPROP_ROBOT_CHAR, cur_robot->robot_char, mf);\n  save_prop_w(RPROP_XPOS, cur_robot->xpos, mf);\n  save_prop_w(RPROP_YPOS, cur_robot->ypos, mf);\n\n#ifdef CONFIG_DEBYTECODE\n\n  if(cur_robot->program_source)\n  {\n    int src_len = cur_robot->program_source_length;\n\n    save_prop_v(RPROP_PROGRAM_SOURCE, src_len, &prop, mf);\n\n    mfwrite(cur_robot->program_source, src_len, 1, &prop);\n  }\n\n#else // !CONFIG_DEBYTECODE\n\n  if(cur_robot->program_bytecode)\n  {\n    int bc_len = cur_robot->program_bytecode_length;\n\n    save_prop_v(RPROP_PROGRAM_BYTECODE, bc_len, &prop, mf);\n\n    mfwrite(cur_robot->program_bytecode, bc_len, 1, &prop);\n  }\n\n#endif // !CONFIG_DEBYTECODE\n\n  if(savegame)\n  {\n    int stack_size = cur_robot->stack_size;\n    int program_line = cur_robot->cur_prog_line;\n    int i;\n\n#ifdef CONFIG_DEBYTECODE\n\n    // The current bytecode offset isn't very useful when saved with\n    // source code. Save the command number within the program instead.\n    program_line = get_program_command_num(cur_robot, program_line);\n\n    // Label zaps.\n    // TODO only do this if anything has actually been zapped/restored from the\n    // default so the robot doesn't have to be compiled when it's loaded.\n    save_prop_v(RPROP_PROGRAM_LABEL_ZAPS, cur_robot->num_labels, &prop, mf);\n    for(i = 0; i < cur_robot->num_labels; i++)\n    {\n      struct label *cur = cur_robot->label_list[i];\n      prop.start[i] = cur->zapped;\n    }\n\n#endif // CONFIG_DEBYTECODE\n\n    save_prop_d(RPROP_CUR_PROG_LINE, program_line, mf);\n    save_prop_d(RPROP_POS_WITHIN_LINE, cur_robot->pos_within_line, mf);\n    save_prop_c(RPROP_ROBOT_CYCLE, cur_robot->robot_cycle, mf);\n    save_prop_c(RPROP_CYCLE_COUNT, cur_robot->cycle_count, mf);\n    save_prop_c(RPROP_BULLET_TYPE, cur_robot->bullet_type, mf);\n    save_prop_c(RPROP_IS_LOCKED, cur_robot->is_locked, mf);\n    save_prop_c(RPROP_CAN_LAVAWALK, cur_robot->can_lavawalk, mf);\n    save_prop_c(RPROP_WALK_DIR, cur_robot->walk_dir, mf);\n    save_prop_c(RPROP_LAST_TOUCH_DIR, cur_robot->last_touch_dir, mf);\n    save_prop_c(RPROP_LAST_SHOT_DIR, cur_robot->last_shot_dir, mf);\n    save_prop_c(RPROP_STATUS, cur_robot->status, mf);\n\n    save_prop_d(RPROP_LOOP_COUNT, cur_robot->loop_count, mf);\n    save_prop_v(RPROP_LOCALS, 4*32, &prop, mf);\n\n    for(i = 0; i < 32; i++)\n      mfputd(cur_robot->local[i], &prop);\n\n    save_prop_d(RPROP_STACK_POINTER, cur_robot->stack_pointer, mf);\n    save_prop_v(RPROP_STACK, stack_size * 4, &prop, mf);\n\n    for(i = 0; i < stack_size; i += 2)\n    {\n      program_line = cur_robot->stack[i];\n#ifdef CONFIG_DEBYTECODE\n      // The stack bytecode offsets have the same problem as the current\n      // bytecode offset above, so convert these to line numbers too.\n      program_line = get_program_command_num(cur_robot, program_line);\n#endif\n      mfputd(program_line, &prop);\n      mfputd(cur_robot->stack[i + 1], &prop);\n    }\n\n    save_prop_c(RPROP_CAN_GOOPWALK, cur_robot->can_goopwalk, mf);\n  }\n\n  save_prop_eof(mf);\n}\n\nvoid save_robot(struct world *mzx_world, struct robot *cur_robot,\n struct zip_archive *zp, int savegame, int file_version, const char *name)\n{\n  struct memfile mf;\n  void *buffer = NULL;\n  size_t actual_size = 0;\n  boolean is_stream = false;\n\n  if(cur_robot->used)\n  {\n#ifdef CONFIG_DEBYTECODE\n    // This will generally be done when calculating the size.\n    // Since memory zips are expecting this to be done beforehand, do it\n    // again anyway just in case.\n\n    if(savegame)\n      prepare_robot_bytecode(mzx_world, cur_robot);\n#endif\n\n    actual_size = save_robot_calculate_size(mzx_world, cur_robot, savegame,\n     file_version);\n\n    if(zp->is_memory)\n    {\n      // The regular way works with memory zips too, but this is faster.\n      if(zip_write_open_mem_stream(zp, &mf, name, actual_size) == ZIP_SUCCESS)\n        is_stream = true;\n    }\n\n    if(!is_stream)\n    {\n      buffer = cmalloc(actual_size);\n\n      mfopen_wr(buffer, actual_size, &mf);\n    }\n\n    save_robot_to_memory(cur_robot, &mf, savegame, file_version);\n\n    if(is_stream)\n    {\n      zip_write_close_mem_stream(zp, &mf);\n    }\n    else\n    {\n      zip_write_file(zp, name, buffer, actual_size, ZIP_M_NONE);\n\n      free(buffer);\n    }\n  }\n}\n\nvoid save_scroll(struct scroll *cur_scroll, struct zip_archive *zp,\n int file_version, const char *name)\n{\n  void *buffer;\n  struct memfile mf;\n  size_t scroll_size;\n  size_t actual_size;\n\n  if(cur_scroll->used)\n  {\n    scroll_size = cur_scroll->mesg_size;\n    actual_size = scroll_size + SCROLL_PROPS_SIZE;\n\n    buffer = cmalloc(actual_size);\n\n    mfopen_wr(buffer, actual_size, &mf);\n\n    save_prop_w(SCRPROP_NUM_LINES, cur_scroll->num_lines, &mf);\n    save_prop_a(SCRPROP_MESG, cur_scroll->mesg, scroll_size, 1, &mf);\n    save_prop_eof(&mf);\n\n    zip_write_file(zp, name, buffer, actual_size, ZIP_M_NONE);\n\n    free(buffer);\n  }\n}\n\nvoid save_sensor(struct sensor *cur_sensor, struct zip_archive *zp,\n int file_version, const char *name)\n{\n  char buffer[SENSOR_PROPS_SIZE];\n  struct memfile mf;\n\n  if(cur_sensor->used)\n  {\n    mfopen_wr(buffer, SENSOR_PROPS_SIZE, &mf);\n\n    save_prop_s_293(SENPROP_SENSOR_NAME, cur_sensor->sensor_name, ROBOT_NAME_SIZE, &mf);\n    save_prop_c(SENPROP_SENSOR_CHAR, cur_sensor->sensor_char, &mf);\n    save_prop_s_293(SENPROP_ROBOT_TO_MESG, cur_sensor->robot_to_mesg, ROBOT_NAME_SIZE, &mf);\n    save_prop_eof(&mf);\n\n    zip_write_file(zp, name, buffer, mftell(&mf), ZIP_M_NONE);\n  }\n}\n\n\nstatic int cmp_labels(const void *dest, const void *src)\n{\n  struct label *lsrc = *((struct label **)src);\n  struct label *ldest = *((struct label **)dest);\n  int cmp_primary = strcasecmp(ldest->name, lsrc->name);\n\n  // A match needs to go on a secondary criteria.\n  if(!cmp_primary)\n  {\n    if(ldest->position == 0)\n      return 1;\n    if(lsrc->position == 0)\n      return -1;\n\n    return ldest->position - lsrc->position;\n  }\n  else\n  {\n    return cmp_primary;\n  }\n}\n\n// TODO: If bytecode isn't valid then this is done at a bad time. It should\n// really be done when robots are assembled, rather than when they're loaded.\n// So it's bundled with the function for that.\n\nvoid cache_robot_labels(struct robot *cur_robot)\n{\n  int labels_allocated = 16;\n  int labels_found = 0;\n  int cmd;\n  int next;\n  int i;\n\n  char *robot_program = cur_robot->program_bytecode;\n  struct label **label_list;\n  struct label *current_label;\n\n  cur_robot->label_list = NULL;\n  cur_robot->num_labels = 0;\n\n  if(!robot_program)\n    return;\n\n  label_list = ccalloc(16, sizeof(struct label *));\n\n  for(i = 1; i < (cur_robot->program_bytecode_length - 1); i++)\n  {\n    // Is it a label?\n    cmd = robot_program[i + 1];\n    next = i + robot_program[i] + 1;\n\n    if((cmd == ROBOTIC_CMD_LABEL) || (cmd == ROBOTIC_CMD_ZAPPED_LABEL))\n    {\n      current_label = cmalloc(sizeof(struct label));\n      current_label->name = robot_program + i + 3;\n\n      current_label->cmd_position = i + 1;\n\n      if(next >= (cur_robot->program_bytecode_length - 2))\n        current_label->position = 0;\n      else\n      {\n        //compatibility fix for 2.80 to 2.83\n        if(cur_robot->world_version >= V280 && cur_robot->world_version <= V283)\n          current_label->position = next + 1;\n        else\n          current_label->position = i;\n      }\n\n      if(cmd == ROBOTIC_CMD_ZAPPED_LABEL)\n        current_label->zapped = true;\n      else\n        current_label->zapped = false;\n\n      // Do we need more room?\n      if(labels_found == labels_allocated)\n      {\n        labels_allocated *= 2;\n        label_list = crealloc(label_list,\n         sizeof(struct label *) * labels_allocated);\n      }\n      label_list[labels_found] = current_label;\n      labels_found++;\n    }\n\n    // Go to next command\n    i = next;\n  }\n\n  if(!labels_found)\n  {\n    free(label_list);\n    return;\n  }\n\n  if(labels_found != labels_allocated)\n  {\n    label_list =\n     crealloc(label_list, sizeof(struct label *) * labels_found);\n  }\n\n  // Now sort the list\n  qsort(label_list, labels_found, sizeof(struct label *), cmp_labels);\n\n  cur_robot->label_list = label_list;\n  cur_robot->num_labels = labels_found;\n  return;\n}\n\nvoid clear_label_cache(struct robot *cur_robot)\n{\n  int i;\n\n  if(cur_robot->label_list)\n  {\n    for(i = 0; i < cur_robot->num_labels; i++)\n    {\n      free(cur_robot->label_list[i]);\n    }\n\n    free(cur_robot->label_list);\n  }\n\n  cur_robot->label_list = NULL;\n  cur_robot->num_labels = 0;\n}\n\nvoid clear_robot_contents(struct robot *cur_robot)\n{\n  free(cur_robot->stack);\n  cur_robot->stack = NULL;\n\n#ifdef CONFIG_EDITOR\n  free(cur_robot->command_map);\n  cur_robot->command_map = NULL;\n  cur_robot->command_map_length = 0;\n#endif\n\n  // If it was created by the game or loaded via a save file\n  // then it won't have source code.\n  free(cur_robot->program_source);\n  cur_robot->program_source = NULL;\n  cur_robot->program_source_length = 0;\n\n  // It could be in the editor, or possibly it was never executed.\n  if(cur_robot->program_bytecode)\n  {\n    clear_label_cache(cur_robot);\n    free(cur_robot->program_bytecode);\n    cur_robot->program_bytecode = NULL;\n    cur_robot->program_bytecode_length = 0;\n  }\n}\n\nvoid clear_robot(struct robot *cur_robot)\n{\n  clear_robot_contents(cur_robot);\n  free(cur_robot);\n}\n\nvoid clear_scroll(struct scroll *cur_scroll)\n{\n  free(cur_scroll->mesg);\n  free(cur_scroll);\n}\n\n// Does not remove entry from the normal list\nstatic void remove_robot_name_entry(struct board *src_board,\n struct robot *cur_robot, char *name)\n{\n  // Find the position\n  int first, last;\n  int active = src_board->num_robots_active;\n  struct robot **name_list = src_board->robot_list_name_sorted;\n\n  find_robot(src_board, name, &first, &last);\n\n  // Find the one that matches the robot\n  while(name_list[first] != cur_robot)\n    first++;\n\n  // Remove from name list\n  active--;\n\n  if(first != active)\n  {\n    memmove(name_list + first, name_list + first + 1,\n     (active - first) * sizeof(struct robot *));\n  }\n  src_board->num_robots_active = active;\n}\n\nvoid clear_robot_id(struct board *src_board, int id)\n{\n  struct robot *cur_robot = src_board->robot_list[id];\n\n  if(id)\n  {\n    remove_robot_name_entry(src_board, cur_robot, cur_robot->robot_name);\n    clear_robot(cur_robot);\n    src_board->robot_list[id] = NULL;\n  }\n  else\n  {\n    clear_robot_contents(cur_robot);\n    cur_robot->used = 0;\n  }\n}\n\nvoid clear_scroll_id(struct board *src_board, int id)\n{\n  clear_scroll(src_board->scroll_list[id]);\n  src_board->scroll_list[id] = NULL;\n}\n\nvoid clear_sensor_id(struct board *src_board, int id)\n{\n  clear_sensor(src_board->sensor_list[id]);\n  src_board->sensor_list[id] = NULL;\n}\n\nvoid clear_sensor(struct sensor *cur_sensor)\n{\n  free(cur_sensor);\n}\n\n#ifndef CONFIG_DEBYTECODE\n\nvoid reallocate_robot(struct robot *robot, int size)\n{\n  robot->program_bytecode = crealloc(robot->program_bytecode, size);\n  robot->program_bytecode_length = size;\n}\n\n#endif /* CONFIG_DEBYTECODE */\n\nvoid reallocate_scroll(struct scroll *scroll, size_t size)\n{\n  scroll->mesg = crealloc(scroll->mesg, size);\n  scroll->mesg_size = size;\n}\n\n/**\n * Due to MZX bugs in older versions the xpos/ypos for in-game features\n * may not be the robot's actual position on the board. This function\n * will return compatibility positions for the affected versions or\n * the robot's real board position otherwise.\n */\nvoid get_robot_position(struct robot *cur_robot, int *xpos, int *ypos)\n{\n  if(cur_robot)\n  {\n    if(cur_robot->world_version >= V292)\n    {\n      *xpos = cur_robot->xpos;\n      *ypos = cur_robot->ypos;\n    }\n    else\n    {\n      *xpos = cur_robot->compat_xpos;\n      *ypos = cur_robot->compat_ypos;\n    }\n  }\n}\n\nint get_robot_id(struct board *src_board, const char *name)\n{\n  int first, last;\n\n  if(find_robot(src_board, name, &first, &last))\n  {\n    struct robot *cur_robot = src_board->robot_list_name_sorted[first];\n    // This is a cheap trick for now since robots don't have\n    // a back-reference for IDs\n    enum thing d_id;\n    int offset;\n    int thisx = 0;\n    int thisy = 0;\n    get_robot_position(cur_robot, &thisx, &thisy);\n\n    offset = thisx + (thisy * src_board->board_width);\n    d_id = (enum thing)src_board->level_id[offset];\n\n    if(is_robot(d_id))\n    {\n      return src_board->level_param[offset];\n    }\n    else\n    {\n      int i;\n      for(i = 1; i <= src_board->num_robots; i++)\n      {\n        if(!src_board->robot_list[i])\n          continue;\n        if(!strcasecmp(name, (src_board->robot_list[i])->robot_name))\n          return i;\n      }\n    }\n  }\n\n  return -1;\n}\n\nstatic struct label *find_label(struct robot *cur_robot, const char *name)\n{\n  int total = cur_robot->num_labels - 1;\n  int bottom = 0, top = total, middle = 0;\n  int cmpval = 0;\n  struct label **base = cur_robot->label_list;\n  struct label *current;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    current = base[middle];\n\n    cmpval = strcasecmp(name, current->name);\n\n    if(cmpval > 0)\n    {\n      bottom = middle + 1;\n    }\n    else\n    {\n      if(cmpval < 0)\n      {\n        top = middle - 1;\n      }\n      else\n      {\n        // Found a match, see if there's an earlier one\n        while(middle)\n        {\n          current = base[middle - 1];\n          if(!strcasecmp(current->name, name))\n          {\n            middle--;\n          }\n          else\n          {\n            break;\n          }\n        }\n\n        current = base[middle];\n\n        // Now find a non-zapped one\n        while(middle <= total)\n        {\n          if(current->zapped)\n          {\n            if(middle == total)\n              return NULL;\n\n            middle++;\n            current = base[middle];\n            if(strcasecmp(current->name, name))\n            {\n              return NULL;\n            }\n          }\n          else\n          {\n            break;\n          }\n        }\n\n        return current;\n      }\n    }\n  }\n\n  return NULL;\n}\n\nstatic int find_label_position(struct robot *cur_robot, const char *name)\n{\n  struct label *cur_label = find_label(cur_robot, name);\n\n  if(cur_label)\n  {\n    return cur_label->position;\n  }\n  else\n  {\n    return -1;\n  }\n}\n\n// This should return the last zapped label found, for restore label\n// to work correctly.\n\nstatic struct label *find_zapped_label(struct robot *cur_robot, char *name)\n{\n  int total = cur_robot->num_labels - 1;\n  int bottom = 0, top = total, middle = 0;\n  int cmpval = 0;\n  struct label **base = cur_robot->label_list;\n  struct label *current;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    current = base[middle];\n    cmpval = strcasecmp(name, current->name);\n\n    if(cmpval > 0)\n    {\n      bottom = middle + 1;\n    }\n    else\n    {\n      if(cmpval < 0)\n      {\n        top = middle - 1;\n      }\n      else\n      {\n       // Found a match, see if there's a later one\n        while(middle < total)\n        {\n          current = base[middle + 1];\n          if(!strcasecmp(current->name, name))\n          {\n            middle++;\n          }\n          else\n          {\n            break;\n          }\n        }\n\n        current = base[middle];\n\n        // Now find a zapped one\n        while(middle >= 0)\n        {\n          if(!current->zapped)\n          {\n            if(!middle)\n              return NULL;\n\n            middle--;\n            current = base[middle];\n            if(strcasecmp(current->name, name))\n            {\n              return NULL;\n            }\n          }\n          else\n          {\n            break;\n          }\n        }\n\n        return current;\n      }\n    }\n  }\n\n  return NULL;\n}\n\n// Returns 1 if found, first is the first robot in the list,\n// last is the last. If not found, first and last are the position to place\n// into.\n\nint find_robot(struct board *src_board, const char *name,\n int *first, int *last)\n{\n  int total = src_board->num_robots_active - 1;\n  int bottom = 0, top = total, middle = 0;\n  int cmpval = 0;\n  struct robot **base = src_board->robot_list_name_sorted;\n  struct robot *current;\n  int f, l;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    current = base[middle];\n    cmpval = strcasecmp(name, current->robot_name);\n\n    if(cmpval > 0)\n    {\n      bottom = middle + 1;\n    }\n    else\n    {\n      if(cmpval < 0)\n      {\n        top = middle - 1;\n      }\n      else\n      {\n        // Find any prior occurances\n        f = middle;\n        l = middle;\n\n        while((f > 0) && (!strcasecmp(name, (base[f - 1])->robot_name)))\n        {\n          f--;\n        }\n\n        *first = f;\n\n        while((l < total) && (!strcasecmp(name, (base[l + 1])->robot_name)))\n        {\n          l++;\n        }\n\n        *last = l;\n        return 1;\n      }\n    }\n  }\n\n  if(cmpval > 0)\n  {\n    *first = middle + 1;\n    *last = middle + 1;\n  }\n  else\n  {\n    *first = middle;\n    *last = middle;\n  }\n\n  return 0;\n}\n\n/* Built-in label only wrappers for send_robot_id and send_robot_all */\nint send_robot_id_def(struct world *mzx_world, int robot_id, const char *mesg,\n int ignore_lock)\n{\n  char submesg[ROBOT_MAX_TR];\n  int result = send_robot_id(mzx_world, robot_id, mesg, ignore_lock), subresult;\n\n  //now, attempt to send the subroutine version of it\n  if(mzx_world->version >= V284)\n  {\n    snprintf(submesg, ROBOT_MAX_TR, \"#%s\", mesg);\n    submesg[ROBOT_MAX_TR - 1] = '\\0';\n    subresult = send_robot_id(mzx_world, robot_id, submesg, ignore_lock);\n    if(result && !subresult)\n      result = subresult;\n  }\n  return result;\n}\n\nvoid send_robot_all_def(struct world *mzx_world, const char *mesg)\n{\n  char submesg[ROBOT_MAX_TR];\n  send_robot_all(mzx_world, mesg, 0);\n\n  //now, attempt to send the subroutine version of it\n  if(mzx_world->version >= V284)\n  {\n    snprintf(submesg, ROBOT_MAX_TR, \"#%s\", mesg);\n    submesg[ROBOT_MAX_TR - 1] = '\\0';\n    send_robot_all(mzx_world, submesg, 0);\n  }\n}\n\nvoid send_robot_def(struct world *mzx_world, int robot_id,\n enum builtin_label mesg_id)\n{\n  switch(mesg_id)\n  {\n    case LABEL_TOUCH:\n      send_robot_id_def(mzx_world, robot_id, \"TOUCH\", 0);\n      break;\n\n    case LABEL_BOMBED:\n      send_robot_id_def(mzx_world, robot_id, \"BOMBED\", 0);\n      break;\n\n    case LABEL_INVINCO:\n      send_robot_all_def(mzx_world, \"INVINCO\");\n      break;\n\n    case LABEL_PUSHED:\n      send_robot_id_def(mzx_world, robot_id, \"PUSHED\", 0);\n      break;\n\n    case LABEL_PLAYERSHOT:\n      if(send_robot_id_def(mzx_world, robot_id, \"PLAYERSHOT\", 0))\n      {\n        send_robot_id_def(mzx_world, robot_id, \"SHOT\", 0);\n      }\n      break;\n\n    case LABEL_NEUTRALSHOT:\n      if(send_robot_id_def(mzx_world, robot_id, \"NEUTRALSHOT\", 0))\n      {\n        send_robot_id_def(mzx_world, robot_id, \"SHOT\", 0);\n      }\n      break;\n\n    case LABEL_ENEMYSHOT:\n      if(send_robot_id_def(mzx_world, robot_id, \"ENEMYSHOT\", 0))\n      {\n        send_robot_id_def(mzx_world, robot_id, \"SHOT\", 0);\n      }\n      break;\n\n    case LABEL_PLAYERHIT:\n      send_robot_all_def(mzx_world, \"PLAYERHIT\");\n      break;\n\n    case LABEL_LAZER:\n      send_robot_id_def(mzx_world, robot_id, \"LAZER\", 0);\n      break;\n\n    case LABEL_SPITFIRE:\n      send_robot_id_def(mzx_world, robot_id, \"SPITFIRE\", 0);\n      break;\n\n    case LABEL_JUSTLOADED: //no subroutine version\n      send_robot_all(mzx_world, \"JUSTLOADED\", 0);\n      break;\n\n    case LABEL_JUSTENTERED: //no subroutine version\n      send_robot_all(mzx_world, \"JUSTENTERED\", 0);\n      break;\n\n    case LABEL_GOOPTOUCHED:\n      send_robot_all_def(mzx_world, \"GOOPTOUCHED\");\n      break;\n\n    case LABEL_PLAYERHURT:\n      send_robot_all_def(mzx_world, \"PLAYERHURT\");\n      break;\n\n    case LABEL_PLAYERDIED:\n      send_robot_all_def(mzx_world, \"PLAYERDIED\");\n      break;\n  }\n}\n\nstatic void send_sensor_command(struct world *mzx_world, int id, int command)\n{\n  struct board *src_board = mzx_world->current_board;\n  struct sensor *cur_sensor = src_board->sensor_list[id];\n  int x = -1, y = -1, under = -1;\n  char *level_under_id = src_board->level_under_id;\n  char *level_under_param = src_board->level_under_param;\n  char *level_under_color = src_board->level_under_color;\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n  char *level_color = src_board->level_color;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  int player_x = mzx_world->player_x;\n  int player_y = mzx_world->player_y;\n  int player_offset = player_x + (player_y * board_width);\n  int offset;\n  int move_status;\n\n  // Find sensor\n  if(!(command & 0x100))\n  {\n    // Don't bother for a char cmd\n    under = 0;\n    if((level_under_id[player_offset] == 122) &&\n     (level_under_param[player_offset] == id))\n    {\n      under = 1;\n      x = player_x;\n      y = player_y;\n    }\n    else\n    {\n      int found = 0;\n      for(y = 0, offset = 0; y < board_height; y++)\n      {\n        for(x = 0; x < board_width; x++, offset++)\n        {\n          if((level_id[offset] == 122) &&\n           (level_param[offset] == id))\n          {\n            found = 1;\n            break;\n          }\n        }\n        if(found)\n          break;\n      }\n    }\n  }\n\n  // Okay, this means the sensor is unlinked. Due to some\n  // errors in world 1 format files or ver1to2 or something\n  // this can happen (see Caverns). It's very unpleasant.\n\n  if(y == board_height)\n    return;\n\n  // Cmd\n  switch(command)\n  {\n    case 0:\n    case 1:\n    case 2:\n    case 3:\n    {\n      move_status = 0;\n      if(under)\n      {\n        // Attempt to move player, then if ok,\n        // put sensor underneath and delete from\n        // original space.\n        move_status = move(mzx_world, x, y, command, CAN_PUSH |\n         CAN_TRANSPORT | CAN_LAVAWALK | CAN_FIREWALK | CAN_WATERWALK);\n\n        if(move_status == NO_HIT)\n        {\n          // Find player...\n          find_player(mzx_world);\n          player_x = mzx_world->player_x;\n          player_y = mzx_world->player_y;\n          player_offset = player_x + (player_y * board_width);\n\n          move_status = HIT_PLAYER;\n        }\n        else\n        {\n          send_robot(mzx_world, cur_sensor->robot_to_mesg, \"SENSORTHUD\", 0);\n        }\n      }\n      else\n      {\n        // Attempt to move sensor.\n        move_status = move(mzx_world, x, y, command,\n         CAN_PUSH | CAN_TRANSPORT | CAN_LAVAWALK | CAN_FIREWALK |\n         CAN_WATERWALK | REACT_PLAYER);\n\n        if(move_status == HIT_PLAYER)\n        {\n          step_sensor(mzx_world, id);\n        }\n      }\n\n      if(move_status == HIT_PLAYER)\n      {\n        // Met player- so put under player!\n        mzx_world->under_player_id = level_under_id[player_offset];\n        mzx_world->under_player_param = level_under_param[player_offset];\n        mzx_world->under_player_color = level_under_color[player_offset];\n        level_under_id[player_offset] = 122;\n        level_under_param[player_offset] = id;\n        level_under_color[player_offset] = level_color[x + (y * board_width)];\n        id_remove_top(mzx_world, x, y);\n      }\n      else\n\n      if(move_status != NO_HIT)\n      {\n        // Sensorthud!\n        send_robot(mzx_world, cur_sensor->robot_to_mesg, \"SENSORTHUD\", 0);\n      }\n      break;\n    }\n\n    case 4:\n    {\n      if(under)\n      {\n        id_remove_under(mzx_world, x, y);\n        clear_sensor_id(src_board, id);\n      }\n      else\n      {\n        id_remove_top(mzx_world, x, y);\n        clear_sensor_id(src_board, id);\n      }\n      break;\n    }\n\n    default:\n    {\n      if(command & 0x100)\n      {\n        cur_sensor->sensor_char = command - 256;\n      }\n      else\n      {\n        if(under)\n        {\n          level_under_color[player_offset] = command - 512;\n        }\n        else\n        {\n          src_board->level_color[x + (y * board_width)] =\n           command - 512;\n        }\n      }\n      break;\n    }\n  }\n}\n\nstatic void send_sensors(struct world *mzx_world, char *name, const char *mesg)\n{\n  struct board *src_board = mzx_world->current_board;\n\n  if(src_board->num_sensors)\n  {\n    // Sensors\n    // Set command- 0-3 move, 4 die, 256 | # char, 512 | # color (hex)\n    int command = -1; // No command yet\n\n    // Check movement commands\n    if(mesg[1] == 0)\n    {\n      char first_letter = mesg[0];\n      if((first_letter >= 'a') && (first_letter <= 'z'))\n        first_letter -= 32;\n\n      switch(first_letter)\n      {\n        case 'N':\n          command = 0;\n          break;\n\n        case 'S':\n          command = 1;\n          break;\n\n        case 'E':\n          command = 2;\n          break;\n\n        case 'W':\n          command = 3;\n          break;\n      }\n    }\n\n    // Die?\n    if(!strcasecmp(\"DIE\", mesg))\n      command = 4;\n\n    // Char___? (___ can be ### or 'c')\n    if(!strncasecmp(\"CHAR\", mesg, 4))\n    {\n     command = 256;\n      if(mesg[4] == '\\'')\n        command = 0x100 | mesg[5];\n      else\n        command = 0x100 | (strtol(mesg + 4, NULL, 10) & 0xFF);\n    }\n\n    // Color__? (__ is hex)\n    if(!strncasecmp(\"COLOR\", mesg, 5))\n    {\n      command = 512 | (strtol(mesg + 5, NULL, 16) & 0xFF);\n    }\n\n    if(command != -1)\n    {\n      struct sensor **sensor_list = src_board->sensor_list;\n      struct sensor *current_sensor;\n      int i;\n\n      if(!strcasecmp(name, \"ALL\"))\n      {\n        for(i = 1; i <= src_board->num_sensors; i++)\n        {\n          current_sensor = sensor_list[i];\n\n          if(current_sensor)\n          {\n            send_sensor_command(mzx_world, i, command);\n          }\n        }\n      }\n      else\n      {\n        for(i = 1; i <= src_board->num_sensors; i++)\n        {\n          current_sensor = sensor_list[i];\n          if((current_sensor != NULL) &&\n           !strcasecmp(name, current_sensor->sensor_name))\n          {\n            send_sensor_command(mzx_world, i, command);\n          }\n        }\n      }\n    }\n  }\n}\n\nstatic void robot_stack_push(struct robot *cur_robot, int position,\n int position_in_line)\n{\n  int stack_pointer = cur_robot->stack_pointer;\n  int stack_size = cur_robot->stack_size;\n  int *stack = cur_robot->stack;\n\n  if((stack_pointer + 1) >= stack_size)\n  {\n    // Initialize or double the stack. Don't let it get too large though!\n    if(stack_size == 0)\n      stack_size = 2;\n    else\n      stack_size *= 2;\n\n    if(stack_size > ROBOT_MAX_STACK)\n      return;\n\n    cur_robot->stack = crealloc(stack, stack_size * sizeof(int));\n    stack = cur_robot->stack;\n    cur_robot->stack_size = stack_size;\n  }\n\n  stack[stack_pointer++] = position;\n  stack[stack_pointer++] = position_in_line;\n  cur_robot->stack_pointer = stack_pointer;\n}\n\nstatic int robot_stack_pop(struct robot *cur_robot, int *position_in_line)\n{\n  int stack_pointer = cur_robot->stack_pointer;\n  int *stack = cur_robot->stack;\n\n  if(stack_pointer)\n  {\n    stack_pointer--;\n    *position_in_line = stack[stack_pointer];\n\n    stack_pointer--;\n    cur_robot->stack_pointer = stack_pointer;\n    return stack[stack_pointer];\n  }\n  else\n  {\n    *position_in_line = 0;\n    return -1;\n  }\n}\n\nstatic void set_robot_position(struct world *mzx_world,\n struct robot *cur_robot, int position, int position_in_line)\n{\n  cur_robot->cur_prog_line = position;\n  cur_robot->pos_within_line = position_in_line;\n  cur_robot->cycle_count = cur_robot->robot_cycle - 1;\n\n  // Popping a subroutine's retval from the stack may legitimately\n  // try to position another robot's code beyond the end of its\n  // program. Ensure that if this happens, the robot's program\n  // will terminate in the next cycle. We need -2 here to ignore\n  // the initial 0xff and terminal 0x00 signature bytes.\n\n  if(cur_robot->cur_prog_line > cur_robot->program_bytecode_length - 2)\n    cur_robot->cur_prog_line = 0;\n\n  // MegaZeux 1.x always sets its equivalent flag here.\n  if(cur_robot->status == 1 || mzx_world->version < V200)\n    cur_robot->status = 2;\n}\n\nstatic int send_robot_direct(struct world *mzx_world, struct robot *cur_robot,\n const char *mesg, int ignore_lock, int send_self)\n{\n  char *robot_program;\n  int new_position;\n\n#ifdef CONFIG_DEBYTECODE\n  prepare_robot_bytecode(mzx_world, cur_robot);\n#endif\n  robot_program = cur_robot->program_bytecode;\n\n  if((cur_robot->is_locked) && (!ignore_lock))\n    return 1; // Locked\n\n  if(cur_robot->program_bytecode_length < 3)\n    return 2; // No program!\n\n  // Are we going to a subroutine? Returning?\n  if(mesg[0] == '#')\n  {\n    // returning?\n    if(!strcasecmp(mesg + 1, \"return\"))\n    {\n      if(cur_robot->stack_pointer)\n      {\n        int return_pos_in_line;\n        int return_pos = robot_stack_pop(cur_robot, &return_pos_in_line);\n        set_robot_position(mzx_world, cur_robot, return_pos, return_pos_in_line);\n      }\n      else\n      {\n        // Contextually invalid return; treat like an invalid label\n        return 2;\n      }\n    }\n    else\n\n    // returning to the TOP?\n    if(!strcasecmp(mesg + 1, \"top\"))\n    {\n      if(cur_robot->stack_pointer)\n      {\n        set_robot_position(mzx_world, cur_robot, cur_robot->stack[0],\n         cur_robot->stack[1]);\n\n        cur_robot->stack_pointer = 0;\n      }\n      else\n      {\n        // Contextually invalid top; treat like an invalid label\n        return 2;\n      }\n    }\n    else\n    {\n      new_position = find_label_position(cur_robot, mesg);\n\n      if(new_position != -1)\n      {\n        // Push the current address onto the stack\n        // If a maximum overflow happened, this simply won't work.\n        int robot_position = cur_robot->cur_prog_line;\n        int return_position_in_line = 0;\n        int return_position;\n\n        if(robot_position)\n        {\n          return_position = robot_position;\n\n          // 2.90+: we want the pos_within_line too.\n          if(cur_robot->world_version >= V290)\n            return_position_in_line = cur_robot->pos_within_line;\n\n          // In DOS versions, ALL subroutine sends returned to the next command.\n          // Self sends in any version should also return to the next command.\n          if(send_self || cur_robot->world_version < VERSION_PORT)\n            return_position += robot_program[robot_position] + 2;\n        }\n        else\n        {\n          return_position = 0;\n        }\n\n        robot_stack_push(cur_robot, return_position,\n         return_position_in_line);\n\n        // Do the jump\n        set_robot_position(mzx_world, cur_robot, new_position, 0);\n        return 0;\n      }\n\n      return 2;\n    }\n  }\n  else\n  {\n    new_position = find_label_position(cur_robot, mesg);\n\n    if(new_position != -1)\n    {\n      set_robot_position(mzx_world, cur_robot, new_position, 0);\n      return 0;\n    }\n\n    return 2;\n  }\n\n  return 0;\n}\n\nvoid send_robot(struct world *mzx_world, char *name, const char *mesg,\n int ignore_lock)\n{\n  struct board *src_board = mzx_world->current_board;\n  int first, last;\n\n  if(!strcasecmp(name, \"all\"))\n  {\n    send_robot_all(mzx_world, mesg, ignore_lock);\n  }\n  else\n  {\n    // See if it's the global robot\n    if(!strcasecmp(name, mzx_world->global_robot.robot_name) &&\n     mzx_world->global_robot.used)\n    {\n      send_robot_direct(mzx_world, &mzx_world->global_robot, mesg,\n       ignore_lock, 0);\n    }\n\n    if(find_robot(src_board, name, &first, &last))\n    {\n      while(first <= last)\n      {\n        send_robot_direct(mzx_world, src_board->robot_list_name_sorted[first],\n         mesg, ignore_lock, 0);\n        first++;\n      }\n    }\n  }\n\n  send_sensors(mzx_world, name, mesg);\n}\n\nint send_robot_id(struct world *mzx_world, int id, const char *mesg,\n int ignore_lock)\n{\n  struct robot *cur_robot = mzx_world->current_board->robot_list[id];\n  return send_robot_direct(mzx_world, cur_robot, mesg, ignore_lock, 0);\n}\n\nint send_robot_self(struct world *mzx_world, struct robot *src_robot,\n const char *mesg, int ignore_lock)\n{\n  return send_robot_direct(mzx_world, src_robot, mesg, ignore_lock, 1);\n}\n\nvoid send_robot_all(struct world *mzx_world, const char *mesg, int ignore_lock)\n{\n  struct board *src_board = mzx_world->current_board;\n  int i;\n\n  if(mzx_world->global_robot.used)\n  {\n    send_robot_direct(mzx_world, &mzx_world->global_robot,\n     mesg, ignore_lock, 0);\n  }\n\n  for(i = 0; i < src_board->num_robots_active; i++)\n  {\n    send_robot_direct(mzx_world, src_board->robot_list_name_sorted[i],\n     mesg, ignore_lock, 0);\n  }\n}\n\n// Run a set of x/y pairs through the prefixes\nvoid prefix_first_last_xy(struct world *mzx_world, int *fx, int *fy,\n int *lx, int *ly, int robotx, int roboty)\n{\n  struct board *src_board = mzx_world->current_board;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  int tfx = *fx;\n  int tfy = *fy;\n  int tlx = *lx;\n  int tly = *ly;\n\n  switch(mzx_world->first_prefix)\n  {\n    case REL_TO_SELF:\n    case REL_TO_SELF_FIRST_OR_LAST:\n    {\n      tfx += robotx;\n      tfy += roboty;\n      break;\n    }\n\n    case REL_TO_PLAYER:\n    case REL_TO_PLAYER_FIRST_OR_LAST:\n    {\n      find_player(mzx_world);\n      tfx += mzx_world->player_x;\n      tfy += mzx_world->player_y;\n      break;\n    }\n\n    case REL_TO_XPOS_YPOS_FIRST_OR_LAST:\n    {\n      tfx += get_counter(mzx_world, \"FIRSTXPOS\", 0);\n      tfy += get_counter(mzx_world, \"FIRSTYPOS\", 0);\n      break;\n    }\n\n    case REL_TO_XPOS_YPOS:\n    {\n      tfx += get_counter(mzx_world, \"XPOS\", 0);\n      tfy += get_counter(mzx_world, \"YPOS\", 0);\n      break;\n    }\n  }\n\n  switch(mzx_world->last_prefix)\n  {\n    case REL_TO_SELF:\n    case REL_TO_SELF_FIRST_OR_LAST:\n    {\n      tlx += robotx;\n      tly += roboty;\n      break;\n    }\n\n    case REL_TO_PLAYER:\n    case REL_TO_PLAYER_FIRST_OR_LAST:\n    {\n      find_player(mzx_world);\n      tlx += mzx_world->player_x;\n      tly += mzx_world->player_y;\n      break;\n    }\n\n    case REL_TO_XPOS_YPOS_FIRST_OR_LAST:\n    {\n      tlx += get_counter(mzx_world, \"LASTXPOS\", 0);\n      tly += get_counter(mzx_world, \"LASTYPOS\", 0);\n      break;\n    }\n\n    case REL_TO_XPOS_YPOS:\n    {\n      tlx += get_counter(mzx_world, \"XPOS\", 0);\n      tly += get_counter(mzx_world, \"YPOS\", 0);\n      break;\n    }\n  }\n\n  if(tfx < 0)\n    tfx = 0;\n\n  if(tfy < 0)\n    tfy = 0;\n\n  if(tlx < 0)\n    tlx = 0;\n\n  if(tly < 0)\n    tly = 0;\n\n  if(tfx >= board_width)\n    tfx = board_width - 1;\n\n  if(tfy >= board_height)\n    tfy = board_height - 1;\n\n  if(tlx >= board_width)\n    tlx = board_width - 1;\n\n  if(tly >= board_height)\n    tly = board_height - 1;\n\n  *fx = tfx;\n  *fy = tfy;\n  *lx = tlx;\n  *ly = tly;\n}\n\n// These are primarily for copy {overlay} block. Allows a variable\n// width/height so that coordinates can be fixed against more than\n// simply the board (for instance vlayer). In the future this could\n// also be used to copy inbetween boards, perhaps.\n\nvoid prefix_first_xy_var(struct world *mzx_world, int *fx, int *fy,\n int robotx, int roboty, int width, int height)\n{\n  int tfx = *fx;\n  int tfy = *fy;\n\n  switch(mzx_world->first_prefix)\n  {\n    case REL_TO_SELF:\n    case REL_TO_SELF_FIRST_OR_LAST:\n    {\n      tfx += robotx;\n      tfy += roboty;\n      break;\n    }\n\n    case REL_TO_PLAYER:\n    case REL_TO_PLAYER_FIRST_OR_LAST:\n    {\n      find_player(mzx_world);\n      tfx += mzx_world->player_x;\n      tfy += mzx_world->player_y;\n      break;\n    }\n\n    case REL_TO_XPOS_YPOS_FIRST_OR_LAST:\n    {\n      tfx += get_counter(mzx_world, \"FIRSTXPOS\", 0);\n      tfy += get_counter(mzx_world, \"FIRSTYPOS\", 0);\n      break;\n    }\n\n    case REL_TO_XPOS_YPOS:\n    {\n      tfx += get_counter(mzx_world, \"XPOS\", 0);\n      tfy += get_counter(mzx_world, \"YPOS\", 0);\n      break;\n    }\n  }\n\n  if(tfx < 0)\n    tfx = 0;\n\n  if(tfy < 0)\n    tfy = 0;\n\n  if(tfx >= width)\n    tfx = width - 1;\n\n  if(tfy >= height)\n    tfy = height - 1;\n\n  *fx = tfx;\n  *fy = tfy;\n}\n\nvoid prefix_last_xy_var(struct world *mzx_world, int *lx, int *ly,\n int robotx, int roboty, int width, int height)\n{\n  int tlx = *lx;\n  int tly = *ly;\n\n  switch(mzx_world->last_prefix)\n  {\n    case REL_TO_SELF:\n    case REL_TO_SELF_FIRST_OR_LAST:\n    {\n      tlx += robotx;\n      tly += roboty;\n      break;\n    }\n\n    case REL_TO_PLAYER:\n    case REL_TO_PLAYER_FIRST_OR_LAST:\n    {\n      find_player(mzx_world);\n      tlx += mzx_world->player_x;\n      tly += mzx_world->player_y;\n      break;\n    }\n\n    case REL_TO_XPOS_YPOS_FIRST_OR_LAST:\n    {\n      tlx += get_counter(mzx_world, \"LASTXPOS\", 0);\n      tly += get_counter(mzx_world, \"LASTYPOS\", 0);\n      break;\n    }\n\n    case REL_TO_XPOS_YPOS:\n    {\n      tlx += get_counter(mzx_world, \"XPOS\", 0);\n      tly += get_counter(mzx_world, \"YPOS\", 0);\n      break;\n    }\n  }\n\n  if(tlx < 0)\n    tlx = 0;\n\n  if(tly < 0)\n    tly = 0;\n\n  if(tlx >= width)\n    tlx = width - 1;\n\n  if(tly >= height)\n    tly = height - 1;\n\n  *lx = tlx;\n  *ly = tly;\n}\n\nvoid prefix_mid_xy_var(struct world *mzx_world, int *mx, int *my,\n int robotx, int roboty, int width, int height)\n{\n  int tmx = *mx;\n  int tmy = *my;\n\n  switch(mzx_world->mid_prefix)\n  {\n    case REL_TO_SELF:\n    {\n      tmx += robotx;\n      tmy += roboty;\n      break;\n    }\n\n    case REL_TO_PLAYER:\n    {\n      find_player(mzx_world);\n      tmx += mzx_world->player_x;\n      tmy += mzx_world->player_y;\n      break;\n    }\n\n    case REL_TO_XPOS_YPOS:\n    {\n      tmx += get_counter(mzx_world, \"XPOS\", 0);\n      tmy += get_counter(mzx_world, \"YPOS\", 0);\n      break;\n    }\n  }\n\n  if(tmx < 0)\n    tmx = 0;\n\n  if(tmy < 0)\n    tmy = 0;\n\n  if(tmx >= width)\n    tmx = width - 1;\n\n  if(tmy >= height)\n    tmy = height - 1;\n\n  *mx = tmx;\n  *my = tmy;\n}\n\n// Unbounded version.\nvoid prefix_mid_xy_unbound(struct world *mzx_world, int *mx, int *my, int x, int y)\n{\n  int tmx = *mx;\n  int tmy = *my;\n\n  switch(mzx_world->mid_prefix)\n  {\n    case REL_TO_SELF:\n    {\n      tmx += x;\n      tmy += y;\n      break;\n    }\n\n    case REL_TO_PLAYER:\n    {\n      find_player(mzx_world);\n      tmx += mzx_world->player_x;\n      tmy += mzx_world->player_y;\n      break;\n    }\n\n    case REL_TO_XPOS_YPOS:\n    {\n      tmx += get_counter(mzx_world, \"XPOS\", 0);\n      tmy += get_counter(mzx_world, \"YPOS\", 0);\n      break;\n    }\n  }\n\n  *mx = tmx;\n  *my = tmy;\n}\n\n// Just does the middle prefixes, since those are all that's usually\n// needed...\nvoid prefix_mid_xy(struct world *mzx_world, int *mx, int *my, int x, int y)\n{\n  struct board *src_board = mzx_world->current_board;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  prefix_mid_xy_unbound(mzx_world, mx, my, x, y);\n\n  if(*mx < 0)\n    *mx = 0;\n  if(*my < 0)\n    *my = 0;\n  if(*mx >= board_width)\n    *mx = board_width - 1;\n  if(*my >= board_height)\n    *my = board_height - 1;\n}\n\n/**\n * Apply middle prefixes with respect to a different board than the current.\n */\nvoid prefix_mid_xy_ext(struct world *mzx_world, struct board *dest_board,\n int *mx, int *my, int x, int y)\n{\n  struct board *cur_board = mzx_world->current_board;\n\n  if(!mzx_world->mid_prefix)\n    return;\n\n  if(cur_board != dest_board)\n  {\n    mzx_world->current_board = dest_board;\n    retrieve_board_from_extram(dest_board);\n  }\n\n  prefix_mid_xy(mzx_world, mx, my, x, y);\n\n  if(cur_board != dest_board)\n  {\n    store_board_to_extram(dest_board);\n    mzx_world->current_board = cur_board;\n    find_player(mzx_world);\n  }\n}\n\n// Move an x/y pair in a given direction. Returns non-0 if edge reached.\nint move_dir(struct board *src_board, int *x, int *y, enum dir dir)\n{\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  int tx = *x;\n  int ty = *y;\n\n  switch(dir)\n  {\n    case NORTH:\n    {\n      if(ty == 0)\n      {\n        return 1;\n      }\n      ty--;\n      break;\n    }\n\n    case SOUTH:\n    {\n      if(ty == (board_height - 1))\n      {\n        return 1;\n      }\n      ty++;\n      break;\n    }\n\n    case EAST:\n    {\n      if(tx == (board_width - 1))\n      {\n        return 1;\n      }\n      tx++;\n      break;\n    }\n\n    case WEST:\n    {\n      if(tx == 0)\n      {\n        return 1;\n      }\n      tx--;\n      break;\n    }\n\n    default:\n      break;\n  }\n\n  *x = tx;\n  *y = ty;\n\n  return 0;\n}\n\n// Internal only. NOTE- IF WE EVER ALLOW ZAPPING OF LABELS NOT IN CURRENT\n// ROBOT, USE A COPY OF THE *LABEL BEFORE THE PREPARE_ROBOT_MEM!\n\n// TODO: What we'll want to do is use the label cache instead, so that\n// we're not actually modifying the program.\n\n// Both of these can only be done from an actively executing program,\n// so they will have valid bytecode.\n\nint restore_label(struct robot *cur_robot, char *label)\n{\n  struct label *dest_label = find_zapped_label(cur_robot, label);\n\n  if(dest_label)\n  {\n    cur_robot->program_bytecode[dest_label->cmd_position] = ROBOTIC_CMD_LABEL;\n    dest_label->zapped = false;\n    return 1;\n  }\n\n  return 0;\n}\n\nint zap_label(struct robot *cur_robot, char *label)\n{\n  struct label *dest_label = find_label(cur_robot, label);\n\n  if(dest_label)\n  {\n    cur_robot->program_bytecode[dest_label->cmd_position] = ROBOTIC_CMD_ZAPPED_LABEL;\n    dest_label->zapped = true;\n    return 1;\n  }\n\n  return 0;\n}\n\n/**\n * Return true if this command can be displayed/skipped in the robot box.\n * FIXME this does NOT allow zapped labels; when zapping stops modifying the\n * bytecode, special handling needs to be added for labels. This means access\n * to the robot's label cache HERE.\n */\nboolean is_robot_box_command(int cmd)\n{\n  return\n   (cmd == ROBOTIC_CMD_BLANK_LINE) ||\n   (cmd == ROBOTIC_CMD_MESSAGE_BOX_LINE) ||\n   (cmd == ROBOTIC_CMD_MESSAGE_BOX_OPTION) ||\n   (cmd == ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION) ||\n   (cmd == ROBOTIC_CMD_LABEL) ||\n   (cmd == ROBOTIC_CMD_MESSAGE_BOX_COLOR_LINE) ||\n   (cmd == ROBOTIC_CMD_MESSAGE_BOX_CENTER_LINE) ||\n   // While executing, the robot box temporarily replaces inaccessible options\n   // with this dummy command.\n   (cmd == ROBOTIC_CMD_UNUSED_249);\n}\n\n/**\n * Return true if this command should be skipped over when scrolling instead\n * of displaying as a blank line.\n *\n * FIXME this does NOT allow zapped labels; when zapping stops modifying the\n * bytecode, special handling needs to be added for labels. This means access\n * to the robot's label cache HERE.\n */\nstatic boolean is_robot_box_skip_command(int cmd)\n{\n  return (cmd == ROBOTIC_CMD_LABEL) || (cmd == ROBOTIC_CMD_UNUSED_249);\n}\n\nstatic int robot_box_down(char *program, int pos, int count)\n{\n  int i, cur_cmd = -1;\n  int old_pos;\n  int done = 0;\n\n  for(i = 0; (i < count) && (!done); i++)\n  {\n    old_pos = pos;\n    do\n    {\n      // Go forward a line (if possible)\n      pos += program[pos] + 2;\n      if(program[pos] == 0)\n      {\n        pos = old_pos;\n        done = 1;\n      }\n      else\n      {\n        cur_cmd = program[pos + 1];\n        if(!is_robot_box_command(cur_cmd))\n        {\n          pos = old_pos;\n          done = 1;\n        }\n      }\n    } while(is_robot_box_skip_command(cur_cmd) && (!done));\n\n    if(i == 100000)\n      i = 99999;\n  }\n\n  return pos;\n}\n\nstatic int robot_box_up(char *program, int pos, int count)\n{\n  int i, cur_cmd = -1;\n  int old_pos;\n  int done = 0;\n\n  for(i = 0; (i < count) && (!done); i++)\n  {\n    old_pos = pos;\n    do\n    {\n      // Go backwards a line (if possible)\n      if(program[pos - 1] == 0xFF)\n      {\n        pos = old_pos;\n        done = 1;\n      }\n      else\n      {\n        pos -= program[pos - 1] + 2;\n        cur_cmd = program[pos + 1];\n        if(!is_robot_box_command(cur_cmd))\n        {\n          pos = old_pos;\n          done = 1;\n        }\n      }\n    } while(is_robot_box_skip_command(cur_cmd) && (!done));\n\n    if(i == 100000)\n      i = 99999;\n  }\n\n  return pos;\n}\n\nstatic void clip_color_string(char *buf, size_t len, size_t pos)\n{\n  size_t idx = color_string_index_of(buf, len, pos, '\\0');\n  buf[idx] = '\\0';\n  return;\n}\n\nstatic void display_robot_line(struct world *mzx_world, char *program,\n int y, int id)\n{\n  char ibuff[ROBOT_MAX_TR];\n  char *next;\n  int scroll_base_color = mzx_world->scroll_base_color;\n  int scroll_arrow_color = mzx_world->scroll_arrow_color;\n  mzx_world->command_cache = 0;\n\n  switch(program[1])\n  {\n    case ROBOTIC_CMD_MESSAGE_BOX_LINE: // Normal message\n    {\n      // On the off-chance something actually relies on this bug...\n      int flags = 0;\n      if((mzx_world->version >= VERSION_PORT) && (mzx_world->version < V291))\n        flags |= WR_TAB;\n\n      tr_msg(mzx_world, program + 3, id, ibuff);\n      ibuff[64] = 0; // Clip\n      write_string_ext(ibuff, 8, y, scroll_base_color, flags, 0, 0);\n      break;\n    }\n\n    case ROBOTIC_CMD_MESSAGE_BOX_OPTION: // Option\n    {\n      // Skip over label...\n      // next is pos of string\n      next = next_param_pos(program + 2);\n      tr_msg(mzx_world, next + 1, id, ibuff);\n      clip_color_string(ibuff, ROBOT_MAX_TR, 62); // Clip\n      color_string_ext(ibuff, 10, y, scroll_base_color, false, 0, 0);\n      draw_char_ext('\\x10', scroll_arrow_color, 8, y, 0, 0);\n      break;\n    }\n\n    case ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION: // Counter-based option\n    {\n      // Check counter\n      int val = parse_param(mzx_world, program + 2, id);\n      if(val)\n      {\n        // Skip over counter and label...\n        // next is pos of string\n        next = next_param_pos(program + 2);\n        next = next_param_pos(next);\n        tr_msg(mzx_world, next + 1, id, ibuff);\n        clip_color_string(ibuff, ROBOT_MAX_TR, 62); // Clip\n        color_string_ext(ibuff, 10, y, scroll_base_color, false, 0, 0);\n        draw_char_ext('\\x10', scroll_arrow_color, 8, y, 0, 0);\n      }\n      break;\n    }\n\n    case ROBOTIC_CMD_MESSAGE_BOX_COLOR_LINE: // Colored message\n    {\n      tr_msg(mzx_world, program + 3, id, ibuff);\n      clip_color_string(ibuff, ROBOT_MAX_TR, 64); // Clip\n      color_string_ext(ibuff, 8, y, scroll_base_color, false, 0, 0);\n      break;\n    }\n\n    case ROBOTIC_CMD_MESSAGE_BOX_CENTER_LINE: // Centered message\n    {\n      int length, x_position;\n      tr_msg(mzx_world, program + 3, id, ibuff);\n      clip_color_string(ibuff, ROBOT_MAX_TR, 64); // Clip\n      length = color_string_length(ibuff, ROBOT_MAX_TR);\n      x_position = 40 - (length / 2);\n      assert(x_position >= 0);\n      color_string_ext(ibuff, x_position, y, scroll_base_color, false, 0, 0);\n      break;\n    }\n  }\n\n  // Others, like ROBOTIC_CMD_BLANK_LINE and ROBOTIC_CMD_LABEL, are blank lines\n}\n\nstatic void robot_frame(struct world *mzx_world, char *program, int id)\n{\n  // Displays one frame of a robot. The scroll edging, arrows, and title\n  // must already be shown. Simply prints each line. The pointer points\n  // to the center line.\n  int scroll_base_color = mzx_world->scroll_base_color;\n  int i, pos = 0;\n  int old_pos;\n\n  select_layer(GAME_UI_LAYER);\n\n  // Display center line\n  fill_line_ext(64, 8, 12, 32, scroll_base_color, 0, 0);\n  display_robot_line(mzx_world, program, 12, id);\n\n  // Display lines above center line\n  for(i = 11; i >= 6; i--)\n  {\n    fill_line_ext(64, 8, i, 32, scroll_base_color, 0, 0);\n    // Go backward to previous line\n    old_pos = pos;\n    pos = robot_box_up(program, pos, 1);\n    if(old_pos != pos)\n      display_robot_line(mzx_world, program + pos, i, id);\n  }\n\n  // Display lines below center line\n  pos = 0;\n\n  for(i = 13; i <= 18; i++)\n  {\n    fill_line_ext(64, 8, i, 32, scroll_base_color, 0, 0);\n    old_pos = pos;\n    pos = robot_box_down(program, pos, 1);\n    if(old_pos != pos)\n      display_robot_line(mzx_world, program + pos, i, id);\n  }\n\n  select_layer(UI_LAYER);\n}\n\nvoid robot_box_display(struct world *mzx_world, char *program,\n char *label_storage, int id)\n{\n  struct board *src_board = mzx_world->current_board;\n  struct robot *cur_robot = src_board->robot_list[id];\n  int pos = 0, key, mouse_press;\n  int joystick_key;\n\n  label_storage[0] = 0;\n\n  // Draw screen\n  save_screen();\n  m_show();\n\n  dialog_fadein();\n\n  scroll_edging_ext(mzx_world, 4, false);\n\n  // Write robot name\n  select_layer(GAME_UI_LAYER);\n  if(!cur_robot->robot_name[0])\n  {\n    write_string_ext(\"Interaction\", 35, 4,\n     mzx_world->scroll_title_color, false, 0, 0);\n  }\n  else\n  {\n    write_string_ext(cur_robot->robot_name,\n     40 - (unsigned int)strlen(cur_robot->robot_name) / 2, 4,\n     mzx_world->scroll_title_color, false, 0, 0);\n  }\n  select_layer(UI_LAYER);\n\n  // Scan section and mark all invalid counter-controlled options as codes\n  // ROBOTIC_CMD_UNUSED_249.\n\n  do\n  {\n    if(program[pos + 1] == ROBOTIC_CMD_UNUSED_249)\n      program[pos + 1] = ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION;\n\n    if(program[pos + 1] == ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION)\n    {\n      if(!parse_param(mzx_world, program + (pos + 2), id))\n        program[pos + 1] = ROBOTIC_CMD_UNUSED_249;\n    }\n\n    pos += program[pos] + 2;\n  } while(program[pos]);\n\n  pos = 0;\n\n  // Backwards\n  do\n  {\n    if(program[pos + 1] == ROBOTIC_CMD_UNUSED_249)\n      program[pos + 1] = ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION;\n\n    if(program[pos + 1] == ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION)\n    {\n      if(!parse_param(mzx_world, program + (pos + 2), id))\n        program[pos + 1] = ROBOTIC_CMD_UNUSED_249;\n    }\n\n    if(program[pos - 1] == 0xFF)\n      break;\n\n    pos -= program[pos - 1] + 2;\n  } while(1);\n\n  pos = 0;\n\n  // If we're starting on an unavailable option, try to seek down\n  if(program[pos + 1] == ROBOTIC_CMD_UNUSED_249)\n    pos = robot_box_down(program, pos, 1);\n\n  // Loop\n  do\n  {\n    // Display scroll\n    robot_frame(mzx_world, program + pos, id);\n    cursor_hint(8, 12);\n    update_screen();\n\n    update_event_status_delay();\n    key = get_key(keycode_internal_wrt_numlock);\n\n    joystick_key = get_joystick_ui_key();\n    if(joystick_key)\n      key = joystick_key;\n\n    // Exit event--mimic Escape\n    if(get_exit_status())\n      key = IKEY_ESCAPE;\n\n    mouse_press = get_mouse_press_ext();\n\n    if(mouse_press && (mouse_press <= MOUSE_BUTTON_RIGHT))\n    {\n      int mouse_x, mouse_y;\n      get_mouse_position(&mouse_x, &mouse_y);\n\n      if((mouse_y >= 6) && (mouse_y <= 18) &&\n       (mouse_x >= 8) && (mouse_x <= 71))\n      {\n        int count = mouse_y - 12;\n        if(!count)\n        {\n          key = IKEY_RETURN;\n        }\n        else\n        {\n          if(count > 0)\n            pos = robot_box_down(program, pos, count);\n          else\n            pos = robot_box_up(program, pos, -count);\n\n          warp_mouse(mouse_x, mouse_y - count);\n        }\n      }\n    }\n    else\n\n    if(mouse_press == MOUSE_BUTTON_WHEELUP)\n    {\n      pos = robot_box_up(program, pos, 3);\n    }\n    else\n\n    if(mouse_press == MOUSE_BUTTON_WHEELDOWN)\n    {\n      pos = robot_box_down(program, pos, 3);\n    }\n\n    switch(key)\n    {\n      case IKEY_UP:\n        pos = robot_box_up(program, pos, 1);\n        break;\n\n      case IKEY_DOWN:\n        pos = robot_box_down(program, pos, 1);\n        break;\n\n      case IKEY_RETURN:\n      {\n        key = IKEY_ESCAPE;\n\n        if((program[pos + 1] == ROBOTIC_CMD_MESSAGE_BOX_OPTION) ||\n         (program[pos + 1] == ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION))\n        {\n          char *next;\n          if(program[pos + 1] == ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION)\n            next = next_param_pos(program + pos + 2) + 1;\n          else\n            next = program + pos + 3;\n\n          // Goto option! Stores in label_storage\n          strcpy(label_storage, next);\n        }\n        break;\n      }\n\n      case IKEY_PAGEDOWN:\n        pos = robot_box_down(program, pos, 6);\n        break;\n\n      case IKEY_PAGEUP:\n        pos = robot_box_up(program, pos, 6);\n        break;\n\n      case IKEY_HOME:\n        pos = robot_box_up(program, pos, 100000);\n        break;\n\n      case IKEY_END:\n        pos = robot_box_down(program, pos, 100000);\n        break;\n\n      default:\n      case IKEY_ESCAPE:\n      case 0:\n        break;\n    }\n  } while(key != IKEY_ESCAPE);\n\n  // Scan section and mark all invalid counter-controlled options as codes\n  // ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION.\n\n  pos = 0;\n\n  do\n  {\n    if(program[pos + 1] == ROBOTIC_CMD_UNUSED_249)\n    {\n      program[pos + 1] = ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION;\n    }\n\n    pos += program[pos] + 2;\n\n  } while(program[pos]);\n\n  pos = 0;\n\n  // Backwards\n  do\n  {\n    if(program[pos + 1] == ROBOTIC_CMD_UNUSED_249)\n    {\n      program[pos + 1] = ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION;\n    }\n    if(program[pos - 1] == 0xFF)\n      break;\n    pos -= program[pos - 1] + 2;\n\n  } while(1);\n\n  // Due to a faulty check, 2.83 through 2.91f always stay faded in here.\n  if((mzx_world->version < V283) || (mzx_world->version >= V291))\n    dialog_fadeout();\n\n  // Restore screen and exit\n  m_hide();\n  cursor_off();\n  restore_screen();\n  update_event_status();\n}\n\nvoid push_sensor(struct world *mzx_world, int id)\n{\n  struct board *src_board = mzx_world->current_board;\n  send_robot(mzx_world, (src_board->sensor_list[id])->robot_to_mesg,\n   \"SENSORPUSHED\", 0);\n}\n\nvoid step_sensor(struct world *mzx_world, int id)\n{\n  struct board *src_board = mzx_world->current_board;\n  send_robot(mzx_world, (src_board->sensor_list[id])->robot_to_mesg,\n   \"SENSORON\", 0);\n}\n\n// Don't do this if the entry is not in the normal list\nvoid add_robot_name_entry(struct board *src_board, struct robot *cur_robot,\n char *name)\n{\n  // Find the position\n  int first, last;\n  int active = src_board->num_robots_active;\n  struct robot **name_list = src_board->robot_list_name_sorted;\n\n  find_robot(src_board, name, &first, &last);\n  // Insert into name list, if it's not at the end\n  if(first != active)\n  {\n    memmove(name_list + first + 1, name_list + first,\n     (active - first) * sizeof(struct robot *));\n  }\n  name_list[first] = cur_robot;\n  src_board->num_robots_active = active + 1;\n}\n\n// This could probably be done in a more efficient manner.\nvoid change_robot_name(struct board *src_board, struct robot *cur_robot,\n char *new_name)\n{\n  // Remove the old one\n  remove_robot_name_entry(src_board, cur_robot, cur_robot->robot_name);\n  // Add the new one\n  add_robot_name_entry(src_board, cur_robot, new_name);\n  // And change the actual name\n  strcpy(cur_robot->robot_name, new_name);\n}\n\n// Works with the ID-list. Will make room for a new one if there aren't any.\nint find_free_robot(struct board *src_board)\n{\n  int num_robots = src_board->num_robots;\n  int i;\n  struct robot **robot_list = src_board->robot_list;\n\n  for(i = 1; i <= num_robots; i++)\n  {\n    if(robot_list[i] == NULL)\n      break;\n  }\n\n  if(i < 256)\n  {\n    // Perhaps make a new one\n    if(i > num_robots)\n    {\n\n      int num_robots_allocated = src_board->num_robots_allocated;\n      if(num_robots == num_robots_allocated)\n      {\n        if(num_robots_allocated)\n          num_robots_allocated *= 2;\n        else\n          num_robots_allocated = 1;\n\n        src_board->robot_list = crealloc(robot_list,\n         (num_robots_allocated + 1) * sizeof(struct robot *));\n\n        src_board->robot_list_name_sorted =\n         crealloc(src_board->robot_list_name_sorted,\n         (num_robots_allocated) * sizeof(struct robot *));\n        src_board->num_robots_allocated = num_robots_allocated;\n      }\n      src_board->num_robots = num_robots + 1;\n    }\n    return i;\n  }\n  return -1;\n}\n\n// Like find_free_robot, but for scrolls. Will also expand the list if\n// necessary.\nstatic int find_free_scroll(struct board *src_board)\n{\n  int num_scrolls = src_board->num_scrolls;\n  int i;\n  struct scroll **scroll_list = src_board->scroll_list;\n\n  for(i = 1; i <= num_scrolls; i++)\n  {\n    if(scroll_list[i] == NULL)\n      break;\n  }\n\n  if(i < 256)\n  {\n    // Perhaps make a new one\n    if(i > num_scrolls)\n    {\n      int num_scrolls_allocated = src_board->num_scrolls_allocated;\n      if(num_scrolls == num_scrolls_allocated)\n      {\n        if(num_scrolls_allocated)\n          num_scrolls_allocated *= 2;\n        else\n          num_scrolls_allocated = 1;\n\n        src_board->scroll_list = crealloc(scroll_list,\n         (num_scrolls_allocated + 1) * sizeof(struct scroll *));\n        src_board->num_scrolls_allocated = num_scrolls_allocated;\n      }\n      src_board->num_scrolls = num_scrolls + 1;\n    }\n    return i;\n  }\n\n  return -1;\n}\n\n// Like find_free_robot, but for sensors. Will also expand the list if\n// necessary.\nstatic int find_free_sensor(struct board *src_board)\n{\n  int num_sensors = src_board->num_sensors;\n  int i;\n  struct sensor **sensor_list = src_board->sensor_list;\n\n  for(i = 1; i <= num_sensors; i++)\n  {\n    if(sensor_list[i] == NULL)\n      break;\n  }\n\n  if(i < 256)\n  {\n    // Perhaps make a new one\n    if(i > num_sensors)\n    {\n      int num_sensors_allocated = src_board->num_sensors_allocated;\n      if(num_sensors == num_sensors_allocated)\n      {\n        if(num_sensors_allocated)\n          num_sensors_allocated *= 2;\n        else\n          num_sensors_allocated = 1;\n\n        src_board->sensor_list = crealloc(sensor_list,\n         (num_sensors_allocated + 1) * sizeof(struct sensor *));\n        src_board->num_sensors_allocated = num_sensors_allocated;\n      }\n      src_board->num_sensors = num_sensors + 1;\n    }\n    return i;\n  }\n\n  return -1;\n}\n\n// Duplicates the contents of one robot to a new one, returning the ID\n// of the duplicate. Supply the x/y position of where you want it to be\n// placed. Returns the ID of location. Does NOT place the robot on the\n// board (so be sure to do that). The given id is the slot to add it in;\n// be sure that this is a valid (NULL) entry!\n\n// This function must not be called in the editor because it won't copy\n// source code. It's only for runtime (use duplicate_robot_direct_source\n// there instead)\n\nvoid duplicate_robot_direct(struct world *mzx_world, struct robot *cur_robot,\n struct robot *copy_robot, int x, int y, int preserve_state)\n{\n  char *dest_program_location, *src_program_location;\n  struct label *src_label, *dest_label;\n  int i, program_length, num_labels;\n  ptrdiff_t program_offset;\n\n#ifdef CONFIG_DEBYTECODE\n  prepare_robot_bytecode(mzx_world, cur_robot);\n#endif\n  program_length = cur_robot->program_bytecode_length;\n  num_labels = cur_robot->num_labels;\n\n  // Copy all the contents\n  memcpy(copy_robot, cur_robot, sizeof(struct robot));\n  // We need unique copies of the program and the label cache.\n  copy_robot->program_bytecode = cmalloc(program_length);\n\n  src_program_location = cur_robot->program_bytecode;\n  dest_program_location = copy_robot->program_bytecode;\n\n  memcpy(dest_program_location, src_program_location, program_length);\n\n  if(num_labels)\n    copy_robot->label_list = ccalloc(num_labels, sizeof(struct label *));\n  else\n    copy_robot->label_list = NULL;\n\n  program_offset = dest_program_location - src_program_location;\n\n  // Copy each individual label pointer over\n  for(i = 0; i < num_labels; i++)\n  {\n    copy_robot->label_list[i] = cmalloc(sizeof(struct label));\n\n    src_label = cur_robot->label_list[i];\n    dest_label = copy_robot->label_list[i];\n\n    memcpy(dest_label, src_label, sizeof(struct label));\n    // The name pointer actually has to be readjusted to match the new program\n    dest_label->name += program_offset;\n  }\n\n  copy_robot->program_source = NULL;\n  copy_robot->program_source_length = 0;\n\n#ifdef CONFIG_DEBYTECODE\n  if(cur_robot->program_source)\n  {\n    // Copy the source too.\n    copy_robot->program_source = cmalloc(cur_robot->program_source_length);\n    memcpy(copy_robot->program_source, cur_robot->program_source,\n     cur_robot->program_source_length);\n    copy_robot->program_source_length = cur_robot->program_source_length;\n  }\n#endif\n\n#ifdef CONFIG_EDITOR\n  copy_robot->command_map = NULL;\n  copy_robot->command_map_length = 0;\n\n#ifdef CONFIG_DEBYTECODE\n  if(mzx_world->editing && cur_robot->command_map)\n  {\n    // Duplicate the command map too if this is testing.\n    int cmd_map_size =\n     cur_robot->command_map_length * sizeof(struct command_mapping);\n\n    // And the command map\n    copy_robot->command_map = cmalloc(cmd_map_size);\n    memcpy(copy_robot->command_map, cur_robot->command_map, cmd_map_size);\n    copy_robot->command_map_length = cur_robot->command_map_length;\n  }\n#endif\n#endif\n\n  if(preserve_state && mzx_world->version < VERSION_PORT)\n  {\n    // In DOS versions, copy and copy block preserved the current robot state\n    // Therefore leave all robot vars alone and copy the stack over\n    size_t stack_capacity = copy_robot->stack_size * sizeof(int);\n    if(stack_capacity)\n    {\n      copy_robot->stack = cmalloc(stack_capacity);\n      memcpy(copy_robot->stack, cur_robot->stack, stack_capacity);\n    }\n    else\n    {\n      copy_robot->stack = NULL;\n      copy_robot->stack_pointer = 0;\n    }\n  }\n  else\n  {\n    // Give the robot an empty stack.\n    copy_robot->stack = NULL;\n    copy_robot->stack_size = 0;\n    copy_robot->stack_pointer = 0;\n\n    if(cur_robot->cur_prog_line)\n      copy_robot->cur_prog_line = 1;\n\n    copy_robot->pos_within_line = 0;\n    copy_robot->status = 0;\n  }\n  copy_robot->xpos = x;\n  copy_robot->ypos = y;\n  copy_robot->compat_xpos = x;\n  copy_robot->compat_ypos = y;\n}\n\n// Finds a robot ID then duplicates a robot there. Must not be called\n// in the editor (use duplicate_robot_source instead).\n\nint duplicate_robot(struct world *mzx_world,\n struct board *src_board, struct robot *cur_robot,\n int x, int y, int preserve_state)\n{\n  int dest_id = find_free_robot(src_board);\n  if(dest_id != -1)\n  {\n    struct robot *copy_robot = cmalloc(sizeof(struct robot));\n    duplicate_robot_direct(mzx_world, cur_robot, copy_robot, x, y,\n      preserve_state);\n    add_robot_name_entry(src_board, copy_robot, copy_robot->robot_name);\n    src_board->robot_list[dest_id] = copy_robot;\n  }\n\n  return dest_id;\n}\n\n// Makes the dest robot a replication of the source. ID's are given\n// so that it can modify the ID table. The dest position is assumed\n// to already contain something, and is thus cleared first.\n// Will not allow replacing the global robot.\nvoid replace_robot(struct world *mzx_world, struct board *src_board,\n struct robot *src_robot, int dest_id)\n{\n  char old_name[64];\n  int x = (src_board->robot_list[dest_id])->xpos;\n  int y = (src_board->robot_list[dest_id])->ypos;\n  struct robot *cur_robot = src_board->robot_list[dest_id];\n\n  strcpy(old_name, cur_robot->robot_name);\n\n  clear_robot_contents(cur_robot);\n  duplicate_robot_direct(mzx_world, src_robot, cur_robot, x, y, 0);\n  strcpy(cur_robot->robot_name, old_name);\n\n  if(dest_id)\n    change_robot_name(src_board, cur_robot, src_robot->robot_name);\n}\n\n// Like duplicate_robot_direct, but for scrolls.\nvoid duplicate_scroll_direct(struct scroll *cur_scroll,\n struct scroll *copy_scroll)\n{\n  size_t mesg_size = cur_scroll->mesg_size;\n\n  // Copy all the contents\n  memcpy(copy_scroll, cur_scroll, sizeof(struct scroll));\n  // We need unique copies of the program and the label cache.\n  copy_scroll->mesg = cmalloc(mesg_size);\n  memcpy(copy_scroll->mesg, cur_scroll->mesg, mesg_size);\n}\n\n// Like duplicate_robot_direct, but for sensors.\nvoid duplicate_sensor_direct(struct sensor *cur_sensor,\n struct sensor *copy_sensor)\n{\n  // Copy all the contents\n  memcpy(copy_sensor, cur_sensor, sizeof(struct sensor));\n}\n\nint duplicate_scroll(struct board *src_board, struct scroll *cur_scroll)\n{\n  int dest_id = find_free_scroll(src_board);\n  if(dest_id != -1)\n  {\n    struct scroll *copy_scroll = cmalloc(sizeof(struct scroll));\n    duplicate_scroll_direct(cur_scroll, copy_scroll);\n    src_board->scroll_list[dest_id] = copy_scroll;\n  }\n\n  return dest_id;\n}\n\nint duplicate_sensor(struct board *src_board, struct sensor *cur_sensor)\n{\n  int dest_id = find_free_sensor(src_board);\n  if(dest_id != -1)\n  {\n    struct sensor *copy_sensor = cmalloc(sizeof(struct sensor));\n    duplicate_sensor_direct(cur_sensor, copy_sensor);\n    src_board->sensor_list[dest_id] = copy_sensor;\n  }\n\n  return dest_id;\n}\n\n// This function will remove any null entries in the object lists\n// (for robots, scrolls, and sensors), and adjust all of the board\n// params to compensate. This should always be used before saving\n// the world/game, and ideally when loading too.\n\nvoid optimize_null_objects(struct board *src_board)\n{\n  int num_robots = src_board->num_robots;\n  int num_scrolls = src_board->num_scrolls;\n  int num_sensors = src_board->num_sensors;\n  struct robot **robot_list = src_board->robot_list;\n  struct scroll **scroll_list = src_board->scroll_list;\n  struct sensor **sensor_list = src_board->sensor_list;\n  struct robot **optimized_robot_list =\n   ccalloc(num_robots + 1, sizeof(struct robot *));\n  struct scroll **optimized_scroll_list =\n   ccalloc(num_scrolls + 1, sizeof(struct scroll *));\n  struct sensor **optimized_sensor_list =\n   ccalloc(num_sensors + 1, sizeof(struct sensor *));\n  int *robot_id_translation_list = ccalloc(num_robots + 1, sizeof(int));\n  int *scroll_id_translation_list = ccalloc(num_scrolls + 1, sizeof(int));\n  int *sensor_id_translation_list = ccalloc(num_sensors + 1, sizeof(int));\n  struct robot *cur_robot;\n  struct scroll *cur_scroll;\n  struct sensor *cur_sensor;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  int i, i2;\n  int x, y, offset;\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n  enum thing d_id;\n  int d_param, d_new_param;\n  int do_modify = 0;\n\n  for(i = 1, i2 = 1; i <= num_robots; i++)\n  {\n    cur_robot = robot_list[i];\n    if(cur_robot != NULL)\n    {\n      optimized_robot_list[i2] = cur_robot;\n      robot_id_translation_list[i] = i2;\n      i2++;\n    }\n    else\n    {\n     // FIXME: Hack, probably bogus!\n     robot_id_translation_list[i] = 0;\n    }\n  }\n\n  if(i2 != i)\n  {\n    do_modify |= 1;\n    optimized_robot_list[0] = robot_list[0];\n    free(robot_list);\n    src_board->robot_list =\n     crealloc(optimized_robot_list, sizeof(struct robot *) * i2);\n    src_board->num_robots = i2 - 1;\n    src_board->num_robots_allocated = i2 - 1;\n  }\n  else\n  {\n    free(optimized_robot_list);\n  }\n\n  for(i = 1, i2 = 1; i <= num_scrolls; i++)\n  {\n    cur_scroll = scroll_list[i];\n    if(cur_scroll != NULL)\n    {\n      optimized_scroll_list[i2] = cur_scroll;\n      scroll_id_translation_list[i] = i2;\n      i2++;\n    }\n  }\n\n  if(i2 != i)\n  {\n    do_modify |= 1;\n    optimized_scroll_list[0] = scroll_list[0];\n    free(scroll_list);\n    src_board->scroll_list =\n     crealloc(optimized_scroll_list, sizeof(struct scroll *) * i2);\n    src_board->num_scrolls = i2 - 1;\n    src_board->num_scrolls_allocated = i2 - 1;\n  }\n  else\n  {\n    free(optimized_scroll_list);\n  }\n\n  for(i = 1, i2 = 1; i <= num_sensors; i++)\n  {\n    cur_sensor = sensor_list[i];\n    if(cur_sensor != NULL)\n    {\n      // If there's a gap, fill it up\n      optimized_sensor_list[i2] = cur_sensor;\n      sensor_id_translation_list[i] = i2;\n      i2++;\n    }\n  }\n\n  if(i2 != i)\n  {\n    do_modify |= 1;\n    optimized_sensor_list[0] = sensor_list[0];\n    free(sensor_list);\n    src_board->sensor_list =\n     crealloc(optimized_sensor_list, sizeof(struct sensor *) * i2);\n    src_board->num_sensors = i2 - 1;\n    src_board->num_sensors_allocated = i2 - 1;\n  }\n  else\n  {\n    free(optimized_sensor_list);\n  }\n\n  // Make sure this is up to date\n  robot_list = src_board->robot_list;\n\n  if(do_modify)\n  {\n    // Now, physically modify all references on the board\n    for(y = 0, offset = 0; y < board_height; y++)\n    {\n      for(x = 0; x < board_width; x++, offset++)\n      {\n        d_id = (enum thing)level_id[offset];\n        // Is it a robot?\n        if(is_robot(d_id))\n        {\n          d_param = level_param[offset];\n          d_new_param = robot_id_translation_list[d_param];\n          level_param[offset] = d_new_param;\n          // Also, as a service, set the x/y coordinates, just in case\n          // they haven't been initialized (this is a potential pitfall)\n          cur_robot = robot_list[d_new_param];\n          cur_robot->xpos = x;\n          cur_robot->ypos = y;\n          cur_robot->compat_xpos = x;\n          cur_robot->compat_ypos = y;\n        }\n        else\n\n        // Is it a scoll?\n        if(is_signscroll(d_id))\n        {\n          d_param = level_param[offset];\n          d_new_param = scroll_id_translation_list[d_param];\n          level_param[offset] = d_new_param;\n        }\n        else\n\n        // Is it a sensor?\n        if(d_id == SENSOR)\n        {\n          d_param = level_param[offset];\n          d_new_param = sensor_id_translation_list[d_param];\n          level_param[offset] = d_new_param;\n        }\n      }\n    }\n  }\n\n  // Free the lists\n  free(robot_id_translation_list);\n  free(scroll_id_translation_list);\n  free(sensor_id_translation_list);\n}\n\n#ifndef CONFIG_DEBYTECODE\n/* Fix nonsense robot stack values, 2.x edition.\n * This should eventually be merged into translate_robot_bytecode_offsets\n * for 3.x.\n */\nvoid fix_robot_stack_offsets(struct robot *cur_robot)\n{\n  const char *bc;\n  int program_length;\n  int *stack_start;\n  int *stack_pos;\n  int *stack;\n  int i = 1;\n\n  if(!cur_robot->stack || !cur_robot->stack_pointer)\n    return;\n\n  bc = cur_robot->program_bytecode;\n  program_length = cur_robot->program_bytecode_length;\n  stack = cur_robot->stack;\n  stack_start = stack + cur_robot->stack_pointer - 2;\n\n  for(stack_pos = stack_start; stack_pos >= stack; stack_pos -= 2)\n  {\n    if(*stack_pos < 0 || *stack_pos >= program_length)\n    {\n      debug(\"Robot has out-of-bounds stack frame %zd: %d (len: %d)\\n\",\n       (stack_pos - stack) >> 1, *stack_pos, program_length);\n      *stack_pos = 0;\n    }\n  }\n\n  while(i < program_length)\n  {\n    int cmd_pos = i;\n    int cmd_next = i + (unsigned char)bc[i] + 2;\n\n    for(stack_pos = stack_start; stack_pos >= stack; stack_pos -= 2)\n    {\n      if(*stack_pos > cmd_pos && *stack_pos < cmd_next)\n      {\n        debug(\"Robot has invalid stack frame %zd: %d @ offset %d (len: %d)\\n\",\n         (stack_pos - stack) >> 1, *stack_pos, cmd_pos, program_length);\n        *stack_pos = 0;\n      }\n    }\n\n    i = cmd_next;\n  }\n}\n#endif\n\n#ifdef CONFIG_DEBYTECODE\n\nvoid prepare_robot_bytecode(struct world *mzx_world, struct robot *cur_robot)\n{\n  if(cur_robot->program_bytecode == NULL)\n  {\n#ifdef CONFIG_EDITOR\n    if(cur_robot->command_map)\n    {\n      free(cur_robot->command_map);\n      cur_robot->command_map = NULL;\n      cur_robot->command_map_length = 0;\n    }\n\n    if(mzx_world->editing)\n    {\n      // Assemble and map the program.\n      assemble_program(cur_robot->program_source,\n       &cur_robot->program_bytecode, &cur_robot->program_bytecode_length,\n       &cur_robot->command_map, &cur_robot->command_map_length);\n    }\n    else\n#endif\n    {\n      // Assemble the program.\n      assemble_program(cur_robot->program_source,\n       &cur_robot->program_bytecode, &cur_robot->program_bytecode_length,\n       NULL, NULL);\n    }\n\n    // This was moved here from load robot - only build up the labels once the\n    // robot's actually used. But eventually this should be combined with\n    // assemble_program.\n    cache_robot_labels(cur_robot);\n  }\n}\n\nstatic int command_num_to_program_pos(struct robot *cur_robot, int command_num)\n{\n  char *bc = cur_robot->program_bytecode;\n  int program_length = cur_robot->program_bytecode_length;\n  int i = 1;\n\n#ifdef CONFIG_EDITOR\n  struct command_mapping *cmd_map = cur_robot->command_map;\n\n  if(cmd_map && command_num < cur_robot->command_map_length)\n    return cmd_map[command_num].bc_pos;\n#endif\n\n  if(!bc || program_length == 0)\n    return 0;\n\n  bc++;\n  while(*bc)\n  {\n    if(i == command_num)\n      return (int)(bc - cur_robot->program_bytecode);\n\n    bc += *bc + 2;\n    i++;\n  }\n  return 0;\n}\n\nstatic int get_legacy_bytecode_command_num(char *legacy_bc, int pos_in_bc)\n{\n  char *end = legacy_bc + pos_in_bc;\n  int i = 1;\n\n  if(!legacy_bc || pos_in_bc == 0)\n    return 0;\n\n  legacy_bc++;\n  while(1)\n  {\n    // Only exact positions are valid.\n    // Offsets in the middle of the command previously executed arbitrary\n    // Robotic and could easily run past the end of the program. In newer\n    // versions, the robot is simply stopped if this is ever found.\n    if(legacy_bc == end)\n      return i;\n\n    if(legacy_bc > end)\n    {\n      debug(\"invalid legacy bytecode position %d ignored\\n\", pos_in_bc);\n      return 0;\n    }\n\n    if(*legacy_bc == 0)\n      break;\n\n    legacy_bc += *legacy_bc + 2;\n    i++;\n  }\n  debug(\"out-of-bounds legacy bytecode position %d ignored\\n\", pos_in_bc);\n  return 0;\n}\n\n/**\n * The cur_prog_line value loaded for this robot is pretty much guaranteed to\n * not be a true bytecode position but either a command number (3.00+) or a\n * legacy bytecode position (<3.00). This is also true for all robot positions\n * stored on the robot stack. If present these need to be converted to real\n * bytecode positions in the compiled source so everything works properly.\n *\n * Note that if cur_prog_line is 0 (robot ended) or 1 (always the start of the\n * first command) it does not need to be translated, and if the robot stack\n * doesn't exist or the robot is currently at the bottom of it then the stack\n * doesn't need to be translated. Checking for these cases is necessary to\n * avoid compiling every robot in the entire world/save on load.\n */\nvoid translate_robot_bytecode_offsets(struct world *mzx_world,\n struct robot *cur_robot, int file_version)\n{\n  if(cur_robot->cur_prog_line > 1 ||\n   (cur_robot->stack && cur_robot->stack_pointer))\n  {\n    int cmd_num = 0;\n\n    prepare_robot_bytecode(mzx_world, cur_robot);\n\n    if(file_version < VERSION_SOURCE)\n    {\n      // FIXME the following relies upon the new bytecode having the same number\n      // of commands as the old bytecode, which SHOULD be true, but it is not.\n      // Uncomment this when rasm gets the ability to compile comments and\n      // blank lines into NOPs.\n      cur_robot->cur_prog_line = 1;\n      cur_robot->pos_within_line = 0;\n      /*\n      if(program_legacy_bytecode)\n        cmd_num = get_legacy_bytecode_command_num(program_legacy_bytecode,\n          cur_robot->cur_prog_line);\n      */\n    }\n    else\n      cmd_num = cur_robot->cur_prog_line;\n\n    if(cmd_num > 1)\n      cur_robot->cur_prog_line = command_num_to_program_pos(cur_robot, cmd_num);\n\n    // Also do the stack.\n    if(cur_robot->stack && cur_robot->stack_pointer)\n    {\n      int i;\n      for(i = 0; i < cur_robot->stack_pointer; i += 2)\n      {\n        int *stack_pos = cur_robot->stack + i;\n\n        if(file_version < VERSION_SOURCE)\n        {\n          // FIXME see above for why this doesn't work right now...\n          cur_robot->stack_pointer = 0;\n          break;\n          /*\n          cmd_num = get_legacy_bytecode_command_num(program_legacy_bytecode,\n           *stack_pos);\n          */\n        }\n        else\n          cmd_num = *stack_pos;\n\n        *stack_pos = command_num_to_program_pos(cur_robot, cmd_num);\n      }\n    }\n  }\n}\n\n#endif /* CONFIG_DEBYTECODE */\n\n#if defined(CONFIG_EDITOR) || defined(CONFIG_DEBYTECODE)\n\nint get_program_command_num(struct robot *cur_robot, int program_pos)\n{\n  int a = 0;\n\n#ifdef CONFIG_EDITOR\n  struct command_mapping *cmd_map = cur_robot->command_map;\n  int b = cur_robot->command_map_length - 1;\n  int i;\n\n  int d;\n\n  // If mapping information is available, do a binary search.\n  if(cmd_map && program_pos > 0)\n  {\n    while(b-a > 1)\n    {\n      i = (b - a)/2 + a;\n\n      d = cmd_map[i].bc_pos - program_pos;\n\n      if(d >= 0) b = i;\n      if(d <= 0) a = i;\n    }\n\n    if(program_pos >= cmd_map[b].bc_pos)\n      a = b;\n\n    return a;\n  }\n  else\n#endif\n\n  if(program_pos == 0)\n    return 0;\n\n  // Otherwise, step through the program line by line.\n  if(cur_robot->program_bytecode)\n  {\n    char *bc = cur_robot->program_bytecode;\n    char *end = bc + program_pos;\n    a = 1;\n\n    bc++;\n    while(*bc)\n    {\n      if(bc >= end)\n        return a;\n\n      bc += *bc + 2;\n      a++;\n    }\n  }\n  return 0;\n}\n\n#endif /* defined(CONFIG_EDITOR) || defined(CONFIG_DEBYTECODE) */\n"
  },
  {
    "path": "src/robot.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __ROBOT_H\n#define __ROBOT_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"core.h\"\n#include \"data.h\"\n\nstruct zip_archive;\n\n// Let's not let a robot's stack get larger than 64k right now.\n// The value is a bit arbitrary, but it's mainly there to prevent MZX from\n// crashing when under infinite recursion.\n\n#define ROBOT_START_STACK 8\n#define ROBOT_MAX_STACK   65536\n\n#define DEBUG_EXIT 1\n#define DEBUG_GOTO 2\n#define DEBUG_HALT 3\n\n/* Internal use only */\nenum builtin_label\n{\n  LABEL_TOUCH,\n  LABEL_BOMBED,\n  LABEL_INVINCO,\n  LABEL_PUSHED,\n  LABEL_PLAYERSHOT,\n  LABEL_NEUTRALSHOT,\n  LABEL_ENEMYSHOT,\n  LABEL_PLAYERHIT,\n  LABEL_LAZER,\n  LABEL_SPITFIRE,\n  LABEL_JUSTLOADED,\n  LABEL_JUSTENTERED,\n  LABEL_GOOPTOUCHED,\n  LABEL_PLAYERHURT,\n  LABEL_PLAYERDIED\n};\n\nenum rel_prefix\n{\n  REL_NONE = 0,\n  REL_TO_SELF = 1,\n  REL_TO_PLAYER = 2,\n  REL_TO_XPOS_YPOS = 3,\n  REL_TO_SELF_FIRST_OR_LAST = 5,\n  REL_TO_PLAYER_FIRST_OR_LAST = 6,\n  REL_TO_XPOS_YPOS_FIRST_OR_LAST = 7,\n};\n\n#ifdef CONFIG_DEBYTECODE\n\nCORE_LIBSPEC void change_robot_name(struct board *src_board,\n struct robot *cur_robot, char *new_name);\nCORE_LIBSPEC void add_robot_name_entry(struct board *src_board,\n struct robot *cur_robot, char *name);\nCORE_LIBSPEC int find_free_robot(struct board *src_board);\n\nvoid prepare_robot_bytecode(struct world *mzx_world, struct robot *cur_robot);\nvoid translate_robot_bytecode_offsets(struct world *mzx_world,\n struct robot *cur_robot, int file_version);\n\n#else /* !CONFIG_DEBYTECODE */\n\nCORE_LIBSPEC void reallocate_robot(struct robot *robot, int size);\n\nvoid change_robot_name(struct board *src_board, struct robot *cur_robot,\n char *new_name);\nvoid add_robot_name_entry(struct board *src_board, struct robot *cur_robot,\n char *name);\nint find_free_robot(struct board *src_board);\n\nvoid fix_robot_stack_offsets(struct robot *cur_robot);\n\n#endif /* !CONFIG_DEBYTECODE */\n\nCORE_LIBSPEC void cache_robot_labels(struct robot *robot);\nCORE_LIBSPEC void clear_label_cache(struct robot *cur_robot);\n\nCORE_LIBSPEC void clear_robot_contents(struct robot *cur_robot);\nCORE_LIBSPEC void clear_robot_id(struct board *src_board, int id);\nCORE_LIBSPEC void clear_scroll_id(struct board *src_board, int id);\nCORE_LIBSPEC void clear_sensor_id(struct board *src_board, int id);\nCORE_LIBSPEC void replace_robot(struct world *mzx_world,\n struct board *src_board, struct robot *src_robot, int dest_id);\nCORE_LIBSPEC int duplicate_robot(struct world *mzx_world,\n struct board *src_board, struct robot *cur_robot, int x, int y,\n int preserve_state);\nCORE_LIBSPEC int duplicate_scroll(struct board *src_board,\n struct scroll *cur_scroll);\nCORE_LIBSPEC int duplicate_sensor(struct board *src_board,\n struct sensor *cur_sensor);\nCORE_LIBSPEC int send_robot_id_def(struct world *mzx_world, int robot_id,\n const char *mesg, int ignore_lock);\nCORE_LIBSPEC void send_robot_all_def(struct world *mzx_world, const char *mesg);\nCORE_LIBSPEC void send_robot_def(struct world *mzx_world, int robot_id,\n enum builtin_label mesg_id);\nCORE_LIBSPEC void optimize_null_objects(struct board *src_board);\n\nCORE_LIBSPEC int place_at_xy(struct world *mzx_world, enum thing id,\n int color, int param, int x, int y);\nCORE_LIBSPEC int place_player_xy(struct world *mzx_world, int x, int y);\nCORE_LIBSPEC void setup_overlay(struct board *src_board, int mode);\nCORE_LIBSPEC void replace_player(struct world *mzx_world);\n\nvoid load_robot(struct world *mzx_world, struct robot *cur_robot,\n struct zip_archive *zp, int savegame, int file_version);\n\nstruct robot *load_robot_allocate(struct world *mzx_world,\n struct zip_archive *zp, int savegame, int file_version);\n\nstruct scroll *load_scroll_allocate(struct zip_archive *zp);\nstruct sensor *load_sensor_allocate(struct zip_archive *zp);\n\nsize_t save_robot_calculate_size(struct world *mzx_world,\n struct robot *cur_robot, int savegame, int file_version);\n\nvoid save_robot(struct world *mzx_world, struct robot *cur_robot,\n struct zip_archive *zp, int savegame, int file_version, const char *name);\n\nvoid save_scroll(struct scroll *cur_scroll, struct zip_archive *zp,\n int file_version, const char *name);\nvoid save_sensor(struct sensor *cur_sensor, struct zip_archive *zp,\n int file_version, const char *name);\n\nvoid create_blank_robot(struct robot *cur_robot);\nvoid create_blank_robot_program(struct robot *cur_robot);\n\nvoid clear_robot(struct robot *cur_robot);\nvoid clear_scroll(struct scroll *cur_scroll);\nvoid clear_sensor(struct sensor *cur_sensor);\nvoid reallocate_scroll(struct scroll *scroll, size_t size);\n\nCORE_LIBSPEC void send_robot(struct world *mzx_world, char *name,\n const char *mesg, int ignore_lock);\nCORE_LIBSPEC int send_robot_id(struct world *mzx_world, int id,\n const char *mesg, int ignore_lock);\n\nint find_robot(struct board *src_board, const char *name,\n int *first, int *last);\nvoid send_robot_all(struct world *mzx_world, const char *mesg, int ignore_lock);\nint send_robot_self(struct world *mzx_world, struct robot *src_robot,\n const char *mesg, int ignore_lock);\nint move_dir(struct board *src_board, int *x, int *y, enum dir dir);\nvoid prefix_first_last_xy(struct world *mzx_world, int *fx, int *fy,\n int *lx, int *ly, int robotx, int roboty);\nvoid prefix_mid_xy_unbound(struct world *mzx_world, int *mx, int *my, int x, int y);\nvoid prefix_mid_xy(struct world *mzx_world, int *mx, int *my, int x, int y);\nvoid prefix_mid_xy_ext(struct world *mzx_world, struct board *dest_board,\n int *mx, int *my, int x, int y);\nvoid prefix_last_xy_var(struct world *mzx_world, int *lx, int *ly,\n int robotx, int roboty, int width, int height);\nvoid prefix_mid_xy_var(struct world *mzx_world, int *mx, int *my,\n int robotx, int roboty, int width, int height);\nvoid prefix_first_xy_var(struct world *mzx_world, int *fx, int *fy,\n int robotx, int roboty, int width, int height);\nint restore_label(struct robot *cur_robot, char *label);\nint zap_label(struct robot *cur_robot, char *label);\nint next_param(char *ptr, int pos);\nchar *next_param_pos(char *ptr);\nint parse_param(struct world *mzx_world, char *robot, int id);\nenum thing parse_param_thing(struct world *mzx_world, char *program);\nenum dir parse_param_dir(struct world *mzx_world, char *program);\nenum equality parse_param_eq(struct world *mzx_world, char *program);\nenum condition parse_param_cond(struct world *mzx_world, char *program,\n enum dir *direction);\nboolean is_robot_box_command(int cmd);\nvoid robot_box_display(struct world *mzx_world, char *program,\n char *label_storage, int id);\nvoid push_sensor(struct world *mzx_world, int id);\nvoid step_sensor(struct world *mzx_world, int id);\nint get_robot_id(struct board *src_board, const char *name);\n\nCORE_LIBSPEC void get_robot_position(struct robot *cur_robot, int *xpos,\n int *ypos);\n\nvoid run_robot(context *ctx, int id, int x, int y);\n\nCORE_LIBSPEC void duplicate_robot_direct(struct world *mzx_world,\n struct robot *cur_robot, struct robot *copy_robot, int x, int y,\n int preserve_state);\nCORE_LIBSPEC void duplicate_scroll_direct(struct scroll *cur_scroll,\n struct scroll *copy_scroll);\nCORE_LIBSPEC void duplicate_sensor_direct(struct sensor *cur_sensor,\n struct sensor *copy_sensor);\n\nCORE_LIBSPEC int get_program_command_num(struct robot *cur_robot,\n int program_pos);\n\n#ifdef CONFIG_EDITOR\nCORE_LIBSPEC extern const int def_params[128];\n#endif // CONFIG_EDITOR\n\n__M_END_DECLS\n\n#endif // __ROBOT_H\n"
  },
  {
    "path": "src/robot_struct.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __ROBOT_STRUCT_H\n#define __ROBOT_STRUCT_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"data.h\"\n#include \"legacy_rasm.h\"\n\nstruct label\n{\n  // Point this to the name in the robot\n  char *name;\n  int position;\n  // Used for zapping.\n  int cmd_position;\n  // Set to true if zapped\n  boolean zapped;\n};\n\nstruct scroll\n{\n  int num_lines;\n\n  // Pointer to scroll's message\n  char *mesg;\n  size_t mesg_size;\n\n  char used;\n};\n\nstruct sensor\n{\n  char sensor_name[ROBOT_NAME_SIZE];\n  char sensor_char;\n  char robot_to_mesg[ROBOT_NAME_SIZE];\n\n  char used;\n};\n\nstruct robot\n{\n  int world_version;\n  int program_source_length;\n  char *program_source;\n  int program_bytecode_length;\n  char *program_bytecode;         // Pointer to robot's program\n  char robot_name[ROBOT_NAME_SIZE];\n  unsigned char robot_char;\n  // Location of start of line (pt to FF for none)\n  int cur_prog_line;\n  int pos_within_line;            // Countdown for GO and WAIT\n  int robot_cycle;\n  int cycle_count;\n  char bullet_type;\n  char is_locked;\n  char can_lavawalk;              // Can always travel on fire\n  char can_goopwalk;\n  enum dir walk_dir;\n  enum dir last_touch_dir;\n  enum dir last_shot_dir;\n\n  // Used for IF ALIGNED \"robot\", THISX/THISY, PLAYERDIST,\n  // HORIZPLD, VERTPLD, and others. Keep updated at all\n  // times. Set to -1/-1 for global robot.\n  int xpos, ypos;\n\n  // Yeah, that note about keeping xpos/ypos \"udpated\" at all times?\n  // Versions prior to 2.92 didn't take that seriously, causing\n  // all of the features using those values to break in several cases.\n  // The easiest way to deal with this is to have compatibility\n  // position values for features that rely on xpos/ypos.\n  int compat_xpos, compat_ypos;\n\n  // 0 = Un-run yet, 1=Was run but only END'd, WAIT'd, or was\n  // inactive, 2=To be re-run on a second robot-run due to a\n  // rec'd message\n  char status;\n\n  // This is deprecated. It's only there for legacy reasons.\n  char used;\n\n  // Loop count. Loops go back to first seen LOOP\n  // START, loop at first seen LOOP #, and an ABORT\n  // LOOP jumps to right after first seen LOOP #.\n  int loop_count;\n\n  int num_labels;\n  struct label **label_list;\n\n  int stack_size;\n  int stack_pointer;\n  int *stack;\n\n  // Local counters - store in save file\n  int local[32];\n\n#ifdef CONFIG_EDITOR\n  // A mapping of bytecode lines to source lines.\n  struct command_mapping *command_map;\n  int command_map_length;\n\n  // Total commands run; commands run in cycle; commands seen by debugger\n  int commands_total;\n  int commands_cycle;\n  int commands_caught;\n#endif\n};\n\n__M_END_DECLS\n\n#endif // __ROBOT_STRUCT_H\n"
  },
  {
    "path": "src/run_robot.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n#include <string.h>\n\n#include \"block.h\"\n#include \"board.h\"\n#include \"const.h\"\n#include \"core.h\"\n#include \"counter.h\"\n#include \"data.h\"\n#include \"error.h\"\n#include \"event.h\"\n#include \"expr.h\"\n#include \"game.h\"\n#include \"game_ops.h\"\n#include \"game_player.h\"\n#include \"graphics.h\"\n#include \"idarray.h\"\n#include \"idput.h\"\n#include \"intake.h\"\n#include \"mzm.h\"\n#include \"robot.h\"\n#include \"scrdisp.h\"\n#include \"sprite.h\"\n#include \"str.h\"\n#include \"util.h\"\n#include \"window.h\"\n#include \"world.h\"\n#include \"io/fsafeopen.h\"\n\n#include \"audio/audio.h\"\n#include \"audio/sfx.h\"\n\n#define parsedir(a, b, c, d) \\\n parsedir(mzx_world, a, b, c, d, _bl[0], _bl[1], _bl[2], _bl[3])\n\nstatic const char *const item_to_counter[9] =\n{\n  \"GEMS\",\n  \"AMMO\",\n  \"TIME\",\n  \"SCORE\",\n  \"HEALTH\",\n  \"LIVES\",\n  \"LOBOMBS\",\n  \"HIBOMBS\",\n  \"COINS\"\n};\n\n// Default parameters (-2 = 0 and no edit, -3 = character, -4 = board)\n__editor_maybe_static const int def_params[128] =\n{\n  -2, -2, -2, -2, -2, -3, -2, -3, -2, -2, -3, -2, -3, -2, -2, -2, // 0x00 - 0x0F\n  -2, -3, -2, -2, -2, -2, -2, -2, -2, -2, -2,  0, -2, -2, 10,  0, // 0x10 - 0x1F\n   0, -2, -2,  5,  0,128, 32, -2, -2,  0, -2, -4, -4, -2, -2,  0, // 0x20 - 0x2F\n  -2,  0, -2, -3, -3, -3, -3, 17,  0, -2, -2, -2,  0,  4,  0, -2, // 0x30 - 0x3F\n  -2, -2, -2, -4, -4, -4, -4, -2,  0, -2, 48,  0, -3, -3,  0,127, // 0x40 - 0x4F\n  32, 26,  2,  1,  0,  2, 65,  2, 66,  2,129, 34, 66, 66, 12, 10, // 0x50 - 0x5F\n  -2,194, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, // 0x60 - 0x6F\n  -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,  0,  0,  0,  0,  0, -2  // 0x70 - 0x7F\n};\n\nstatic void magic_load_mod(struct world *mzx_world, char *filename)\n{\n  struct board *src_board = mzx_world->current_board;\n  size_t mod_name_size;\n\n  if(load_game_module(mzx_world, filename, false))\n    strcpy(src_board->mod_playing, filename);\n\n  // If a * was provided, set the current board mod to *.\n  mod_name_size = strlen(filename);\n  if(mod_name_size && filename[mod_name_size - 1] == '*')\n  {\n    strcpy(src_board->mod_playing, \"*\");\n  }\n}\n\n/**\n * MegaZeux 1.x performed the board changes for TELEPORT, RESTORE PLAYER\n * POSITION, and EXCHANGE PLAYER POSITION immediately. To prevent executing\n * part of the new board, TELEPORT would clear the label received flags\n * instruct the board scan to abort. Unfortunately, this needs to be supported.\n * The RESTORE/EXCHANGE commands do not do this and can only be supported in\n * a partial manner by canceling the cycle if the boards are different.\n */\nstatic void immediate_board_change_compat_v1(struct world *mzx_world,\n boolean is_teleport)\n{\n  if(mzx_world->version < V200 &&\n   (is_teleport || mzx_world->current_board_id != mzx_world->target_board))\n  {\n    mzx_world->change_game_state = CHANGE_STATE_INTERRUPT_CYCLE;\n  }\n}\n\nstatic void save_player_position(struct world *mzx_world, int pos)\n{\n  mzx_world->pl_saved_x[pos] = mzx_world->player_x;\n  mzx_world->pl_saved_y[pos] = mzx_world->player_y;\n  mzx_world->pl_saved_board[pos] = mzx_world->current_board_id;\n}\n\nstatic void restore_player_position(struct world *mzx_world, int pos)\n{\n  mzx_world->target_x = mzx_world->pl_saved_x[pos];\n  mzx_world->target_y = mzx_world->pl_saved_y[pos];\n  mzx_world->target_board = mzx_world->pl_saved_board[pos];\n  mzx_world->target_where = TARGET_POSITION;\n  immediate_board_change_compat_v1(mzx_world, false);\n}\n\nstatic void calculate_blocked(struct world *mzx_world, int x, int y, int id,\n int bl[4])\n{\n  struct board *src_board = mzx_world->current_board;\n  int board_width = src_board->board_width;\n  char *level_id = src_board->level_id;\n\n  if(id)\n  {\n    int i, offset, new_x, new_y;\n    for(i = 0; i < 4; i++)\n    {\n      new_x = x;\n      new_y = y;\n\n      if(!move_dir(src_board, &new_x, &new_y, int_to_dir(i)))\n      {\n        offset = new_x + (new_y * board_width);\n        // Not edge... blocked?\n        if((flags[(int)level_id[offset]] & A_UNDER) != A_UNDER)\n          bl[i] = 1;\n        else\n          bl[i] = 0;\n      }\n      else\n      {\n        bl[i] = 1; // Edge is considered blocked\n      }\n    }\n  }\n}\n\n// Turns a color (including those w/??) to a real color (0-255)\nstatic int fix_color(int color, int def)\n{\n  if(color < 256)\n    return color;\n  if(color < 272)\n    return (color & 0x0F) + (def & 0xF0);\n  if(color < 288)\n    return ((color - 272) << 4) + (def & 0x0F);\n\n  return def;\n}\n\nint place_at_xy(struct world *mzx_world, enum thing id, int color, int param,\n int x, int y)\n{\n  struct board *src_board = mzx_world->current_board;\n  int board_width = src_board->board_width;\n  int offset = x + (y * board_width);\n  enum thing old_id = (enum thing)src_board->level_id[offset];\n\n  // 2.80+: wildcard parameter will preserve the parameter of the\n  // destination if both things are currently configured to have an ID char\n  // of 255 (use parameter for char). Otherwise, the wildcard expands to the\n  // default parameter of the replacement thing instead.\n  if(param == 256 && mzx_world->version >= V280)\n  {\n    if((id_chars[old_id] != 255) || (id_chars[id] != 255))\n    {\n      if(def_params[id] > 0)\n        param = def_params[id];\n      else\n        param = 0;\n    }\n    else\n    {\n      param = src_board->level_param[offset];\n    }\n  }\n  // Invalid parameters and p?? (pre-2.80) evaluate modulo 256.\n  param &= 0xff;\n\n  if(old_id == SENSOR)\n  {\n    // Okay to put player on sensor; it won't destroy the sensor\n    if(id != PLAYER)\n    {\n      int old_param = src_board->level_param[offset];\n      clear_sensor_id(src_board, old_param);\n    }\n  }\n  else\n\n  if(is_signscroll(old_id))\n  {\n    int old_param = src_board->level_param[offset];\n    clear_scroll_id(src_board, old_param);\n  }\n  else\n\n  if(is_robot(old_id))\n  {\n    int old_param = src_board->level_param[offset];\n    clear_robot_id(src_board, old_param);\n  }\n  else\n\n  if(old_id == PLAYER)\n  {\n    return 0; // Don't allow the player to be overwritten\n  }\n\n  id_place(mzx_world, x, y, id,\n   fix_color(color, src_board->level_color[offset]), param);\n\n  return 1;\n}\n\nstatic int place_under_xy(struct world *mzx_world, struct board *src_board,\n enum thing id, int color, int param, int x, int y)\n{\n  int board_width = src_board->board_width;\n  char *level_under_id = src_board->level_under_id;\n  char *level_under_color = src_board->level_under_color;\n  char *level_under_param = src_board->level_under_param;\n\n  int offset = x + (y * board_width);\n  // 2.80+: wildcard parameter will preserve the parameter of whatever is\n  // beneath, regardless of the destination and replacement things' ID chars.\n  // Interesting oversight!\n  if(param == 256 && mzx_world->version >= V280)\n    param = level_under_param[offset];\n\n  color = fix_color(color, level_under_color[offset]);\n  if(level_under_id[offset] == SENSOR)\n    clear_sensor_id(src_board, level_under_param[offset]);\n\n  level_under_id[offset] = (char)id;\n  level_under_color[offset] = color;\n  level_under_param[offset] = param;\n\n  return 1;\n}\n\nstatic int place_dir_xy(struct world *mzx_world, enum thing id, int color,\n int param, int x, int y, enum dir direction, struct robot *cur_robot,\n int *_bl)\n{\n  struct board *src_board = mzx_world->current_board;\n\n  // Check under\n  if(direction == BENEATH)\n  {\n    return place_under_xy(mzx_world, src_board, id, color, param, x, y);\n  }\n  else\n  {\n    int new_x = x;\n    int new_y = y;\n    direction = parsedir(direction, x, y, cur_robot->walk_dir);\n\n    if(is_cardinal_dir(direction))\n    {\n      if(!move_dir(src_board, &new_x, &new_y, direction))\n      {\n        return place_at_xy(mzx_world, id, color, param,\n         new_x, new_y);\n      }\n    }\n    return 0;\n  }\n}\n\nint place_player_xy(struct world *mzx_world, int x, int y)\n{\n  if((mzx_world->player_x != x) || (mzx_world->player_y != y))\n  {\n    struct board *src_board = mzx_world->current_board;\n    int offset = x + (y * src_board->board_width);\n    int did = src_board->level_id[offset];\n    int dparam = src_board->level_param[offset];\n\n    if(is_robot(did))\n    {\n      clear_robot_id(src_board, dparam);\n    }\n    else\n\n    if(is_signscroll(did))\n    {\n      clear_scroll_id(src_board, dparam);\n    }\n    else\n\n    if(did == SENSOR)\n    {\n      step_sensor(mzx_world, dparam);\n    }\n\n    id_remove_top(mzx_world, mzx_world->player_x, mzx_world->player_y);\n    id_place(mzx_world, x, y, PLAYER, 0, 0);\n    mzx_world->player_x = x;\n    mzx_world->player_y = y;\n\n    return 1;\n  }\n\n  return 0;\n}\n\nstatic void send_at_xy(struct world *mzx_world, int id, int x, int y,\n char *label)\n{\n  struct board *src_board = mzx_world->current_board;\n  int offset = x + (y * src_board->board_width);\n  enum thing d_id = (enum thing)src_board->level_id[offset];\n\n  if(is_robot(d_id))\n  {\n    char label_buffer[ROBOT_MAX_TR];\n    int d_param = src_board->level_param[offset];\n\n    tr_msg(mzx_world, label, id, label_buffer);\n\n    if(d_param == id)\n    {\n      send_robot_self(mzx_world,\n       mzx_world->current_board->robot_list[id], label_buffer, 0);\n    }\n    else\n    {\n      send_robot_id(mzx_world, src_board->level_param[offset],\n       label_buffer, 0);\n    }\n  }\n}\n\nstatic int get_random_range(int min_value, int max_value)\n{\n  int result;\n  uint64_t difference;\n\n  if(min_value == max_value)\n  {\n    result = min_value;\n  }\n  else\n  {\n    if(max_value > min_value)\n    {\n      difference = (int64_t)max_value - (int64_t)min_value;\n    }\n    else\n    {\n      difference = (int64_t)min_value - (int64_t)max_value;\n      min_value = max_value;\n    }\n\n    result = Random(difference + 1) + min_value;\n  }\n\n  return result;\n}\n\nstatic int send_self_label_tr(struct world *mzx_world, char *param, int id)\n{\n  char label_buffer[ROBOT_MAX_TR];\n  tr_msg(mzx_world, param, id, label_buffer);\n\n  if(send_robot_self(mzx_world,\n   mzx_world->current_board->robot_list[id], label_buffer, 1))\n  {\n    return 0;\n  }\n  else\n  {\n    return 1;\n  }\n}\n\nstatic void split_colors(int color, int *fg, int *bg)\n{\n  if(color & 256)\n  {\n    color ^= 256;\n    if(color == 32)\n    {\n      *fg = 16;\n      *bg = 16;\n    }\n    else\n\n    if(color < 16)\n    {\n      *bg = 16;\n      *fg = color;\n    }\n    else\n    {\n      *bg = color - 16;\n      *fg = 16;\n    }\n  }\n  else\n  {\n    *bg = color >> 4;\n    *fg = color & 0x0F;\n  }\n}\n\n/**\n * The change command has the same broken semantics on the replacement\n * thing as on the match thing prior to 2.80. This nasty hack converts\n * invalid edge cases to be compatible with fix_colors/param modulo.\n */\nstatic void replacement_version_fix(struct world *mzx_world, int *color,\n int *param, boolean *weird_color)\n{\n  if(mzx_world->version < VERSION_PORT)\n  {\n    if(*color < 0 || *color > 288)\n    {\n      int fg, bg;\n      split_colors(*color, &fg, &bg);\n\n      /* DOS has a very weird edge case with color<0, bit 8 set where it ends\n       * up ORing the source background color with the replacement. */\n      if(fg < 0 && bg == 16)\n      {\n        *color &= 0xff;\n        *weird_color = true;\n      }\n      else\n\n      if(fg >= 16 && bg >= 16)\n        *color = 288;\n      else\n      if(fg >= 16)\n        *color = 272 | (bg & 0x0f);\n      else\n      if(bg >= 16)\n        *color = 256 | (fg & 0x0f);\n      else\n        *color &= 0xff;\n    }\n    if(*param > 256)\n      *param = 256;\n  }\n}\n\n/**\n * Prior to 2.80, match parameters >256 act identically to p??.\n * Post-split_colors fg and bg >16 act identically to a ? for that component.\n */\nstatic void match_version_fix(struct world *mzx_world, int *fg, int *bg, int *param)\n{\n  if(mzx_world->version < VERSION_PORT)\n  {\n    if(*fg > 16)\n      *fg = 16;\n    if(*bg > 16)\n      *bg = 16;\n    if(param && *param > 256)\n      *param = 256;\n  }\n}\n\n/* Some nuances here that need to be preserved in the off-chance that something\n * accidentally relied on them:\n *\n * - The match param may be <0 or >256, which always fails the match.\n * - The match colors may be <0 or >16, which always fails the match\n *   (see split_colors). Pre-2.80, params >256 and components >16 acted like\n *   wildcards (see match_version_fix).\n */\nstatic inline boolean match_thing(enum thing d_id, int dcolor, int dparam,\n enum thing match_id, int match_fg, int match_bg, int match_param)\n{\n  if((d_id == match_id) &&\n   (((dcolor & 0x0F) == match_fg) || (match_fg == 16)) &&\n   (((dcolor >> 4) == match_bg) || (match_bg == 16)) &&\n   ((dparam == match_param) || (match_param == 256)))\n  {\n    return true;\n  }\n\n  return false;\n}\n\n/* The colors here can also be <0 or >16 (see split_colors). */\nstatic inline boolean match_overlay_color(int d_color, int fg, int bg)\n{\n  if(((bg == 16) || (bg == (d_color >> 4))) &&\n   ((fg == 16) || (fg == (d_color & 0x0F))))\n    return true;\n\n  return false;\n}\n\nstatic int check_at_xy(struct board *src_board, enum thing id, int fg, int bg,\n int param, int offset)\n{\n  enum thing d_id = (enum thing)src_board->level_id[offset];\n  int dcolor = src_board->level_color[offset];\n  int dparam = src_board->level_param[offset];\n\n  // Whirlpool matches down\n  if(is_whirlpool(d_id))\n    d_id = WHIRLPOOL_1;\n\n  return match_thing(d_id, dcolor, dparam, id, fg, bg, param);\n}\n\nstatic int check_under_xy(struct board *src_board, enum thing id,\n int fg, int bg, int param, int offset)\n{\n  enum thing did = (enum thing)src_board->level_under_id[offset];\n  int dcolor = src_board->level_under_color[offset];\n  int dparam = src_board->level_under_param[offset];\n\n  return match_thing(did, dcolor, dparam, id, fg, bg, param);\n}\n\nstatic int check_dir_xy(struct world *mzx_world, enum thing id, int color,\n int param, int x, int y, enum dir direction, struct robot *cur_robot,\n int *_bl)\n{\n  struct board *src_board = mzx_world->current_board;\n  int board_width = src_board->board_width;\n  int fg, bg;\n  int offset;\n\n  split_colors(color, &fg, &bg);\n  match_version_fix(mzx_world, &fg, &bg, &param);\n  // TODO: fix whirlpool id\n\n  // Check under\n  if(direction == BENEATH)\n  {\n    offset = x + (y * board_width);\n    if(check_under_xy(src_board, id, fg, bg, param, offset))\n      return 1;\n\n    return 0;\n  }\n  else\n  {\n    int new_x = x;\n    int new_y = y;\n\n    if((direction & 0x1F) == SEEK)\n    {\n      get_robot_position(cur_robot, &x, &y);\n    }\n\n    direction = parsedir(direction, x, y, cur_robot->walk_dir);\n\n    if(!move_dir(src_board, &new_x, &new_y, direction))\n    {\n      offset = new_x + (new_y * board_width);\n\n      if(check_at_xy(src_board, id, fg, bg, param, offset))\n        return 1;\n    }\n    return 0;\n  }\n}\n\nstatic void copy_xy_to_xy(struct world *mzx_world, int src_x, int src_y,\n int dest_x, int dest_y)\n{\n  struct board *src_board = mzx_world->current_board;\n  int board_width = src_board->board_width;\n  int src_offset = src_x + (src_y * board_width);\n  int dest_offset = dest_x + (dest_y * board_width);\n  enum thing src_id = (enum thing)src_board->level_id[src_offset];\n  enum thing dest_id = (enum thing)src_board->level_id[dest_offset];\n\n  // Cannot copy from the player or onto the player. Check for the player at\n  // the destination now so this doesn't pointlessly waste storage object IDs.\n  if((src_id != PLAYER) && (dest_id != PLAYER))\n  {\n    int src_param = src_board->level_param[src_offset];\n\n    if(is_robot(src_id))\n    {\n      struct robot *src_robot = src_board->robot_list[src_param];\n      src_param = duplicate_robot(mzx_world, src_board, src_robot,\n       dest_x, dest_y, 1);\n    }\n    else\n\n    if(is_signscroll(src_id))\n    {\n      struct scroll *src_scroll = src_board->scroll_list[src_param];\n      src_param = duplicate_scroll(src_board, src_scroll);\n    }\n    else\n\n    if(src_id == SENSOR)\n    {\n      struct sensor *src_sensor = src_board->sensor_list[src_param];\n      src_param = duplicate_sensor(src_board, src_sensor);\n    }\n\n    // Now perform the copy; this should perform any necessary\n    // deletions at the destination as well.\n    if(src_param != -1)\n    {\n      place_at_xy(mzx_world, src_id,\n       src_board->level_color[src_offset], src_param, dest_x, dest_y);\n    }\n  }\n}\n\nvoid setup_overlay(struct board *src_board, int mode)\n{\n  if(!mode && src_board->overlay_mode)\n  {\n    // Deallocate overlay\n    free(src_board->overlay);\n    free(src_board->overlay_color);\n  }\n\n  if(!src_board->overlay_mode && mode)\n  {\n    int board_size = src_board->board_width * src_board->board_height;\n    // Allocate an overlay\n    src_board->overlay = cmalloc(board_size);\n    src_board->overlay_color = cmalloc(board_size);\n    memset(src_board->overlay, 32, board_size);\n    memset(src_board->overlay_color, 7, board_size);\n  }\n  src_board->overlay_mode = mode;\n}\n\nstatic void copy_block(struct world *mzx_world, int id, int x, int y,\n int src_type, int dest_type, int src_x, int src_y, int width, int height,\n int dest_x, int dest_y, char *dest, int dest_param,\n char dest_name_buffer[ROBOT_MAX_TR])\n{\n  struct board *src_board = mzx_world->current_board;\n  int src_width = src_board->board_width;\n  int src_height = src_board->board_height;\n  int dest_width = src_board->board_width;\n  int dest_height = src_board->board_height;\n  int src_offset;\n  int dest_offset;\n\n  // Process for source type and handle prefixing\n  switch(src_type)\n  {\n    case 2:\n    {\n      src_width = mzx_world->vlayer_width;\n      src_height = mzx_world->vlayer_height;\n      break;\n    }\n    case 1:\n    {\n      if(!src_board->overlay_mode)\n        setup_overlay(src_board, 3);\n    }\n  }\n\n  prefix_first_xy_var(mzx_world, &src_x, &src_y, x, y,\n   src_width, src_height);\n\n  // Clip and verify; the prefixer already handled the\n  // base coordinates, so just handle the width/height\n  if(width < 1)\n    width = 1;\n  if(height < 1)\n    height = 1;\n  if((src_x + width) > src_width)\n    width = src_width - src_x;\n  if((src_y + height) > src_height)\n    height = src_height - src_y;\n\n  // Get the offset that will generally be used.\n  src_offset = src_x + (src_y * src_width);\n\n  // Process for dest type and handle prefixing if we aren't already done\n  switch(dest_type)\n  {\n    //string - dest_param is the terminator\n    case 3:\n    {\n      switch(src_type)\n      {\n        case 0:\n        {\n          // Board to string\n          load_string_board(mzx_world, dest_name_buffer,\n           src_board->level_param + src_offset, src_width,\n           width, height, dest_param);\n          break;\n        }\n        case 1:\n        {\n          // Overlay to string\n          load_string_board(mzx_world, dest_name_buffer,\n           src_board->overlay + src_offset, src_width,\n           width, height, dest_param);\n          break;\n        }\n        case 2:\n        {\n          // Vlayer to string\n          load_string_board(mzx_world, dest_name_buffer,\n           mzx_world->vlayer_chars + src_offset, src_width,\n           width, height, dest_param);\n          break;\n        }\n      }\n      return;\n    }\n    //mzm - if dest_param is !=0, the board is saved like a layer.\n    case 4:\n    {\n      char *translated_name = cmalloc(MAX_PATH);\n      int err;\n\n      switch(src_type)\n      {\n        case 0:\n          if(dest_param)\n            src_type = MZM_BOARD_TO_LAYER_STORAGE;\n          else\n            src_type = MZM_BOARD_TO_BOARD_STORAGE;\n          break;\n\n        case 1:\n          src_type = MZM_OVERLAY_TO_LAYER_STORAGE;\n          break;\n\n        case 2:\n          src_type = MZM_VLAYER_TO_LAYER_STORAGE;\n          break;\n      }\n\n      // Save MZM to string (2.90+)\n      if(mzx_world->version >= V290 && is_string(dest_name_buffer))\n      {\n        save_mzm_string(mzx_world, dest_name_buffer, src_x, src_y,\n         width, height, src_type, 1, id);\n      }\n\n      // Save MZM to file\n      else\n      {\n        err = fsafetranslate(dest_name_buffer, translated_name, MAX_PATH);\n        if(err == -FSAFE_SUCCESS || err == -FSAFE_MATCH_FAILED)\n        {\n          save_mzm(mzx_world, translated_name, src_x, src_y,\n          width, height, src_type, 1);\n        }\n      }\n      free(translated_name);\n      return;\n    }\n    case 2:\n    {\n      dest_width = mzx_world->vlayer_width;\n      dest_height = mzx_world->vlayer_height;\n      break;\n    }\n    case 1:\n    {\n      if(!src_board->overlay_mode)\n        setup_overlay(src_board, 3);\n    }\n  }\n\n  prefix_last_xy_var(mzx_world, &dest_x, &dest_y, x, y,\n   dest_width, dest_height);\n\n  // Clip to destination dimensions as well\n  if((dest_x + width) > dest_width)\n    width = dest_width - dest_x;\n  if((dest_y + height) > dest_height)\n    height = dest_height - dest_y;\n\n  // Get the offset that will generally be used.\n  dest_offset = dest_x + (dest_y * dest_width);\n\n  switch((dest_type << 2) | (src_type))\n  {\n    // Board to board\n    case 0:\n    {\n      copy_board_to_board(mzx_world,\n       src_board, src_offset,\n       src_board, dest_offset,\n       width, height);\n\n      break;\n    }\n    // Overlay to board\n    case 1:\n    {\n      copy_layer_to_board(\n       src_board->overlay, src_board->overlay_color, src_width, src_offset,\n       src_board, dest_offset,\n       width, height, CUSTOM_BLOCK);\n\n      break;\n    }\n    // Vlayer to board\n    case 2:\n    {\n      copy_layer_to_board(\n       mzx_world->vlayer_chars, mzx_world->vlayer_colors, src_width, src_offset,\n       src_board, dest_offset,\n       width, height, CUSTOM_BLOCK);\n\n      break;\n    }\n\n    // Board to overlay\n    case 4:\n    {\n      copy_board_to_layer(\n       src_board, src_offset,\n       src_board->overlay, src_board->overlay_color, dest_width, dest_offset,\n       width, height);\n\n      break;\n    }\n    // Overlay to overlay\n    case 5:\n    {\n      copy_layer_to_layer(\n       src_board->overlay, src_board->overlay_color, src_width, src_offset,\n       src_board->overlay, src_board->overlay_color, dest_width, dest_offset,\n       width, height);\n\n      break;\n    }\n    // Vlayer to overlay\n    case 6:\n    {\n      copy_layer_to_layer(\n       mzx_world->vlayer_chars, mzx_world->vlayer_colors, src_width, src_offset,\n       src_board->overlay, src_board->overlay_color, dest_width, dest_offset,\n       width, height);\n\n      break;\n    }\n\n    // Board to vlayer\n    case 8:\n    {\n      copy_board_to_layer(\n       src_board, src_offset,\n       mzx_world->vlayer_chars, mzx_world->vlayer_colors, dest_width, dest_offset,\n       width, height);\n\n      break;\n    }\n    // Overlay to vlayer\n    case 9:\n    {\n      copy_layer_to_layer(\n       src_board->overlay, src_board->overlay_color, src_width, src_offset,\n       mzx_world->vlayer_chars, mzx_world->vlayer_colors, dest_width, dest_offset,\n       width, height);\n\n      break;\n    }\n    // Vlayer to vlayer\n    case 10:\n    {\n      copy_layer_to_layer(\n       mzx_world->vlayer_chars, mzx_world->vlayer_colors, src_width, src_offset,\n       mzx_world->vlayer_chars, mzx_world->vlayer_colors, dest_width, dest_offset,\n       width, height);\n\n      break;\n    }\n  }\n}\n\nstatic void copy_xy_to_xy_wrapper(struct world *mzx_world, int id, int x, int y,\n int src_type, int dest_type, int src_x, int src_y, int dest_x, int dest_y)\n{\n  struct board *cur_board = mzx_world->current_board;\n  int board_width = cur_board->board_width;\n  int board_height = cur_board->board_height;\n  int vlayer_width = mzx_world->vlayer_width;\n  int vlayer_height = mzx_world->vlayer_height;\n  int src_offset;\n  int dest_offset;\n\n  switch(src_type | (dest_type << 4))\n  {\n    // Board-to-board. Use the original implementation...\n    case 0x00:\n      prefix_first_last_xy(mzx_world, &src_x, &src_y, &dest_x, &dest_y, x, y);\n      copy_xy_to_xy(mzx_world, src_x, src_y, dest_x, dest_y);\n      break;\n\n    // Overlay-to-overlay.\n    case 0x11:\n      if(!cur_board->overlay_mode)\n        setup_overlay(cur_board, 3);\n\n      prefix_first_last_xy(mzx_world, &src_x, &src_y, &dest_x, &dest_y, x, y);\n      src_offset = src_y * board_width + src_x;\n      dest_offset = dest_y * board_width + dest_x;\n      cur_board->overlay[dest_offset] = cur_board->overlay[src_offset];\n      cur_board->overlay_color[dest_offset] = cur_board->overlay_color[src_offset];\n      break;\n\n    // Vlayer-to-overlay.\n    case 0x12:\n      if(!cur_board->overlay_mode)\n        setup_overlay(cur_board, 3);\n\n      prefix_first_xy_var(mzx_world, &src_x, &src_y, x, y,\n       vlayer_width, vlayer_height);\n      prefix_last_xy_var(mzx_world, &dest_x, &dest_y, x, y,\n       board_width, board_height);\n\n      src_offset = src_y * vlayer_width + src_x;\n      dest_offset = dest_y * board_width + dest_x;\n      cur_board->overlay[dest_offset] = mzx_world->vlayer_chars[src_offset];\n      cur_board->overlay_color[dest_offset] = mzx_world->vlayer_colors[src_offset];\n      break;\n\n    // Overlay-to-vlayer.\n    case 0x21:\n      if(!cur_board->overlay_mode)\n        setup_overlay(cur_board, 3);\n\n      prefix_first_xy_var(mzx_world, &src_x, &src_y, x, y,\n       board_width, board_height);\n      prefix_last_xy_var(mzx_world, &dest_x, &dest_y, x, y,\n       vlayer_width, vlayer_height);\n\n      src_offset = src_y * board_width + src_x;\n      dest_offset = dest_y * vlayer_width + dest_x;\n      mzx_world->vlayer_chars[dest_offset] = cur_board->overlay[src_offset];\n      mzx_world->vlayer_colors[dest_offset] = cur_board->overlay_color[src_offset];\n      break;\n\n    // Vlayer-to-vlayer.\n    case 0x22:\n      prefix_first_xy_var(mzx_world, &src_x, &src_y, x, y,\n       vlayer_width, vlayer_height);\n      prefix_last_xy_var(mzx_world, &dest_x, &dest_y, x, y,\n       vlayer_width, vlayer_height);\n\n      src_offset = src_y * vlayer_width + src_x;\n      dest_offset = dest_y * vlayer_width + dest_x;\n      mzx_world->vlayer_chars[dest_offset] = mzx_world->vlayer_chars[src_offset];\n      mzx_world->vlayer_colors[dest_offset] = mzx_world->vlayer_colors[src_offset];\n      break;\n\n    // All others should just use copy_block.\n    case 0x01:\n    case 0x02:\n    case 0x10:\n    case 0x20:\n      copy_block(mzx_world, id, x, y, src_type, dest_type, src_x, src_y, 1, 1,\n       dest_x, dest_y, NULL, 0, NULL);\n      break;\n  }\n}\n\n// Gets the type for a single copy block param.\nstatic int copy_block_param(struct world *mzx_world, int id, char *param,\n int *coord)\n{\n  int type = 0;\n  if(*param)\n  {\n    if(*(param + 1) == '+')\n    {\n      type = 1;\n    }\n    else\n\n    if(*(param + 1) == '#')\n    {\n      type = 2;\n    }\n  }\n\n  if(type == 0)\n  {\n    *coord = parse_param(mzx_world, param, id);\n  }\n  else\n\n  if((type == 1) || (type == 2))\n  {\n    char src_char_buffer[ROBOT_MAX_TR];\n    tr_msg(mzx_world, param + 2, id, src_char_buffer);\n    *coord = strtol(src_char_buffer, NULL, 10);\n  }\n  return type;\n}\n\n// Gets the type for the X coord of the copy block destination.\n// This argument has two special cases: it can be a $string (after tr_msg) or\n// an MZM (always prefixed with @). These cases need to also return the name\n// of their destination string/MZM.\nstatic int copy_block_param_special(struct world *mzx_world, int id,\n char *param, int *coord, char dest_name_buffer[ROBOT_MAX_TR])\n{\n  char first = *(param + 1);\n  if(!(*param) || first == '(' || first == '+' || first == '#')\n    return copy_block_param(mzx_world, id, param, coord);\n\n  if(first == '@')\n  {\n    tr_msg(mzx_world, param + 2, id, dest_name_buffer);\n    return 4;\n  }\n\n  tr_msg(mzx_world, param + 1, id, dest_name_buffer);\n  if(is_string(dest_name_buffer))\n    return 3;\n\n  *coord = get_counter(mzx_world, dest_name_buffer, id);\n  return 0;\n}\n\nvoid replace_player(struct world *mzx_world)\n{\n  struct board *src_board = mzx_world->current_board;\n  char *level_id = src_board->level_id;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  int dx, dy, offset;\n\n  for(dy = 0, offset = 0; dy < board_height; dy++)\n  {\n    for(dx = 0; dx < board_width; dx++, offset++)\n    {\n      if(A_UNDER & flags[(int)level_id[offset]])\n      {\n        // Place the player here\n        mzx_world->player_x = dx;\n        mzx_world->player_y = dy;\n        id_place(mzx_world, dx, dy, PLAYER, 0, 0);\n        return;\n      }\n    }\n  }\n\n  // Place the player here\n  mzx_world->player_x = 0;\n  mzx_world->player_y = 0;\n  place_at_xy(mzx_world, PLAYER, 0, 0, 0, 0);\n}\n\n// Returns the numeric value pointed to OR the numeric value represented\n// by the counter string pointed to. (the ptr is at the param within the\n// command)\n// Sign extends the result, for now...\n\nint parse_param(struct world *mzx_world, char *program, int id)\n{\n  char ibuff[ROBOT_MAX_TR];\n\n  if(program[0] == 0)\n  {\n    // Numeric\n    return (signed short)((int)program[1] | (int)(program[2] << 8));\n  }\n\n  // Expressions - Exo\n  if((program[1] == '(') && mzx_world->version >= V268)\n  {\n    char *e_ptr = program + 2;\n    int val, error;\n\n    val = parse_expression(mzx_world, &e_ptr, &error, id);\n    if(!error && !(*e_ptr))\n      return val;\n  }\n\n  tr_msg(mzx_world, program + 1, id, ibuff);\n\n  return get_counter(mzx_world, ibuff, id);\n}\n\n// Check for the cases of parse_param that treat the param as a name.\n// Use this if tr_msg is required to check for a string in a place where an\n// expression is valid (currently only the IF and COPY BLOCK $string commands).\nstatic boolean is_name_param(struct world *mzx_world, char *program)\n{\n  return (program[0] != 0) &&\n  ((program[1] != '(') || mzx_world->version < V268);\n}\n\n// These will always return numeric values\nenum thing parse_param_thing(struct world *mzx_world, char *program)\n{\n  return (enum thing)\n   ((int)program[1] | (int)(program[2] << 8));\n}\n\nenum dir parse_param_dir(struct world *mzx_world, char *program)\n{\n  return (enum dir)\n   ((int)program[1] | (int)(program[2] << 8));\n}\n\nenum equality parse_param_eq(struct world *mzx_world, char *program)\n{\n  return (enum equality)\n   ((int)program[1] | (int)(program[2] << 8));\n}\n\nenum condition parse_param_cond(struct world *mzx_world, char *program,\n enum dir *direction)\n{\n  *direction = (enum dir)program[2];\n  return (enum condition)program[1];\n}\n\n// Returns location of next parameter (pos is loc of current parameter)\nint next_param(char *ptr, int pos)\n{\n  if(ptr[pos])\n  {\n    return ptr[pos] + 1;\n  }\n  else\n  {\n    return 3;\n  }\n}\n\nchar *next_param_pos(char *ptr)\n{\n  int index = *ptr;\n  if(index)\n  {\n    return ptr + index + 1;\n  }\n  else\n  {\n    return ptr + 3;\n  }\n}\n\nstatic void advance_line(struct robot *cur_robot, char *program)\n{\n  cur_robot->cur_prog_line += program[cur_robot->cur_prog_line] + 2;\n  cur_robot->pos_within_line = 0;\n}\n\nstatic void end_cycle(struct world *mzx_world, struct robot *cur_robot,\n int lines_run, int x, int y)\n{\n#ifdef CONFIG_EDITOR\n  cur_robot->commands_total += lines_run;\n  cur_robot->commands_cycle = lines_run;\n#endif\n\n  cur_robot->cycle_count = 0; // In case a label changed it\n\n  // Older versions have a really sloppy method of updating the robot pos\n  // that causes a lot of bugs, and sadly this needs to be emulated.\n  cur_robot->compat_xpos = x;\n  cur_robot->compat_ypos = y;\n\n  // Send commands in 1.x always set the label sent flag, but this\n  // robot already received whatever label it sent itself, so clear it.\n  if(mzx_world->version < V200)\n    cur_robot->status = 0;\n}\n\nstatic void end_program(struct robot *cur_robot)\n{\n  cur_robot->cur_prog_line = 0;\n  cur_robot->pos_within_line = 0;\n}\n\n#define ADVANCE_LINE do{ advance_line(cur_robot, program); }while(0);\n\n// NOTE: Should always return after using one of these.\n#define END_CYCLE do{ end_cycle(mzx_world, cur_robot, lines_run, x, y); }while(0);\n#define END_PROGRAM do{ END_CYCLE; end_program(cur_robot); }while(0);\n\n// Run a single robot through a single cycle.\n// If id is negative, only run it if status is 2\nvoid run_robot(context *ctx, int id, int x, int y)\n{\n  struct world *mzx_world = ctx->world;\n  struct board *src_board = mzx_world->current_board;\n  struct robot *cur_robot;\n  int cmd; // Command to run\n  int lines_run = 0;\n  int gotoed;\n\n  int old_pos; // Old position to verify gotos DID something\n  int last_label = -1;\n  // Whether blocked in a given direction (2 = OUR bullet)\n  int _bl[4] = { 0, 0, 0, 0 };\n  char *program;\n  char *cmd_ptr;\n  char done = 0;\n  char update_blocked = 0;\n  char first_cmd = 1;\n  char *level_id = src_board->level_id;\n  char *level_param = src_board->level_param;\n  char *level_color = src_board->level_color;\n  char *level_under_id = src_board->level_under_id;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n\n  if((id < 0) && ((src_board->robot_list[-id])->status != 2))\n    return;\n\n  // Reset global prefixes\n  mzx_world->first_prefix = 0;\n  mzx_world->mid_prefix = 0;\n  mzx_world->last_prefix = 0;\n  mzx_world->command_cache = 0;\n\n  if(id < 0)\n  {\n    id = -id;\n    cur_robot = src_board->robot_list[id];\n    cur_robot->xpos = x;\n    cur_robot->ypos = y;\n    cur_robot->compat_xpos = x;\n    cur_robot->compat_ypos = y;\n    cur_robot->cycle_count = 0;\n    cur_robot->status = 0;\n  }\n  else\n  {\n    enum dir walk_dir;\n    cur_robot = src_board->robot_list[id];\n\n    walk_dir = cur_robot->walk_dir;\n\n    // Reset x/y\n    cur_robot->xpos = x;\n    cur_robot->ypos = y;\n    cur_robot->compat_xpos = x;\n    cur_robot->compat_ypos = y;\n    cur_robot->status = 0; // Clear this here too for 1.x compat.\n\n#ifdef CONFIG_EDITOR\n    cur_robot->commands_cycle = 0;\n    cur_robot->commands_caught = 0;\n#endif\n\n    // Update cycle count\n    cur_robot->cycle_count++;\n    if(cur_robot->cycle_count < cur_robot->robot_cycle)\n    {\n      cur_robot->status = 1;\n      return;\n    }\n\n    cur_robot->cycle_count = 0;\n\n#ifdef CONFIG_DEBYTECODE\n    prepare_robot_bytecode(mzx_world, cur_robot);\n#endif\n\n    // Does program exist?\n    if(cur_robot->program_bytecode_length < 3)\n    {\n      return; // (nope)\n    }\n\n    // Walk?\n    if(id && is_cardinal_dir(walk_dir))\n    {\n      enum move_status status = move(mzx_world, x, y, dir_to_int(walk_dir),\n       CAN_PUSH | CAN_TRANSPORT | CAN_FIREWALK | CAN_WATERWALK |\n       CAN_LAVAWALK * cur_robot->can_lavawalk |\n       (cur_robot->can_goopwalk ? CAN_GOOPWALK : 0));\n\n      if(status == HIT_EDGE)\n      {\n        // Send to edge, if no response, then to thud.\n        if(send_robot_id_def(mzx_world, id, \"edge\", 1))\n          send_robot_id_def(mzx_world, id, \"thud\", 1);\n      }\n      else if(status == NO_HIT)\n      {\n        enum thing l_id;\n\n        if(mzx_world->version >= V292)\n        {\n          // Source the current position off of the robot's new real position.\n          x = cur_robot->xpos;\n          y = cur_robot->ypos;\n        }\n        else\n        {\n          // Source the current position off of a hack that doesn't really work.\n          move_dir(src_board, &x, &y, walk_dir);\n        }\n\n        /* Normally, WALK doesn't end the cycle. But due to long-standing\n         * bugs in the transport() and push() functions, the board is actually\n         * updated without updating the x,y co-ordinates. Presumably, move_dir\n         * was designed to get things \"back in sync\". Unfortunately, because\n         * robots can enter transports, they may not be in the x,y location\n         * that move_dir recomputes. Because there is no easy way to get the\n         * updated x,y location of the robot for this cycle, we simply end the\n         * cycle if the robot enters a transport.\n         */\n        l_id = (enum thing)level_id[x + (y * board_width)];\n        if(l_id == TRANSPORT)\n        {\n          END_CYCLE;\n          return;\n        }\n      }\n      else\n        send_robot_id_def(mzx_world, id, \"thud\", 1);\n    }\n\n    if(cur_robot->cur_prog_line == 0)\n    {\n      // Robot is inactive\n      cur_robot->status = 1;\n      END_CYCLE;\n      return;\n    }\n  }\n\n  // Get program location\n  program = cur_robot->program_bytecode;\n\n  // NOW quit if inactive (had to do walk first)\n  if(cur_robot->cur_prog_line == 0)\n  {\n    cur_robot->status = 1;\n    END_PROGRAM;\n    return;\n  }\n\n  // Figure blocked vars (accurate until robot program ends OR a put\n  // or change command is issued)\n\n  calculate_blocked(mzx_world, x, y, id, _bl);\n\n  // Update player x/y if necessary\n  find_player(mzx_world);\n\n  // Main robot loop\n\n  do\n  {\n    gotoed = 0;\n    old_pos = cur_robot->cur_prog_line;\n\n    // Get ptr to command\n    cmd_ptr = program + old_pos + 1;\n\n    // Get command number\n    cmd = cmd_ptr[0];\n\n#ifdef CONFIG_EDITOR\n    // Check to see if the current command triggers a breakpoint.\n    if(mzx_world->editing && debug_robot_break)\n    {\n      switch(debug_robot_break(ctx, cur_robot, id, lines_run))\n      {\n        case DEBUG_EXIT:\n          break;\n\n        case DEBUG_GOTO:\n          // Restart the loop on the new destination command.\n          // Don't count the goto as a command.\n          lines_run--;\n          continue;\n\n        case DEBUG_HALT:\n          END_CYCLE;\n          return;\n      }\n    }\n#endif\n\n    // Act according to command\n    switch(cmd)\n    {\n      case ROBOTIC_CMD_END: // End\n      {\n        if(first_cmd)\n          cur_robot->status = 1;\n\n        END_PROGRAM;\n        return;\n      }\n\n      case ROBOTIC_CMD_DIE: // Die\n      {\n        if(id)\n        {\n          clear_robot_id(src_board, id);\n          id_remove_top(mzx_world, x, y);\n        }\n        // Robot no longer exists; exit.\n        return;\n      }\n\n      case ROBOTIC_CMD_DIE_ITEM: // Die item\n      {\n        if(id)\n        {\n          clear_robot_id(src_board, id);\n          id_remove_top(mzx_world, x, y);\n          place_player_xy(mzx_world, x, y);\n        }\n        // Robot no longer exists; exit.\n        return;\n      }\n\n      case ROBOTIC_CMD_WAIT: // Wait\n      {\n        int wait_time = parse_param(mzx_world, cmd_ptr + 1, id) & 0xFF;\n        if(wait_time <= cur_robot->pos_within_line)\n          break;\n\n        cur_robot->pos_within_line++;\n        if(first_cmd)\n          cur_robot->status = 1;\n\n        END_CYCLE;\n        return;\n      }\n\n      case ROBOTIC_CMD_CYCLE: // Cycle\n      {\n        cur_robot->robot_cycle = parse_param(mzx_world, cmd_ptr + 1,id);\n        done = 1;\n        break;\n      }\n\n      case ROBOTIC_CMD_GO: // Go dir num\n      {\n        if(id)\n        {\n          enum dir direction =\n           parsedir((enum dir)cmd_ptr[2], x, y, cur_robot->walk_dir);\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          int num = parse_param(mzx_world, p2, id);\n\n          // Inc. pos. or break\n          if(num > cur_robot->pos_within_line)\n          {\n            cur_robot->pos_within_line++;\n\n            // Parse dir\n            if(is_cardinal_dir(direction))\n            {\n              enum move_status status;\n\n              status = move(mzx_world, x, y, dir_to_int(direction),\n               CAN_PUSH | CAN_TRANSPORT | CAN_FIREWALK | CAN_WATERWALK |\n               CAN_LAVAWALK * cur_robot->can_lavawalk |\n               (cur_robot->can_goopwalk ? CAN_GOOPWALK : 0));\n\n              if(status == NO_HIT)\n              {\n                move_dir(src_board, &x, &y, direction);\n              }\n            }\n            END_CYCLE;\n            return;\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_TRY_DIR: // Try dir label\n      {\n        if(id)\n        {\n          // Parse dir\n          enum dir direction = parsedir((enum dir)cmd_ptr[2], x, y,\n           cur_robot->walk_dir);\n\n          if(is_cardinal_dir(direction))\n          {\n            enum move_status status;\n\n            status = move(mzx_world, x, y, dir_to_int(direction),\n             CAN_PUSH | CAN_TRANSPORT | CAN_FIREWALK | CAN_WATERWALK |\n             CAN_LAVAWALK * cur_robot->can_lavawalk |\n             (cur_robot->can_goopwalk ? CAN_GOOPWALK : 0));\n\n            if(status)\n            {\n              // blocked- send to label\n              char *p2 = next_param_pos(cmd_ptr + 1);\n              gotoed = send_self_label_tr(mzx_world,  p2 + 1, id);\n            }\n            else\n            {\n              move_dir(src_board, &x, &y, direction);\n              // not blocked- make sure only moves once!\n              done = 1;\n            }\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_WALK: // Walk dir\n      {\n        if(id)\n        {\n          enum dir direction =\n           parsedir((enum dir)cmd_ptr[2], x, y, cur_robot->walk_dir);\n\n          if(!is_cardinal_dir(direction))\n          {\n            cur_robot->walk_dir = IDLE;\n          }\n          else\n          {\n            cur_robot->walk_dir = direction;\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_BECOME: // Become color thing param\n      {\n        if(id)\n        {\n          int offset = x + (y * board_width);\n          int color = parse_param(mzx_world, cmd_ptr + 1, id);\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          enum thing new_id = parse_param_thing(mzx_world, p2);\n          int param = parse_param(mzx_world, p2 + 3, id);\n          color = fix_color(color, level_color[offset]);\n          level_color[offset] = color;\n          level_id[offset] = new_id;\n\n          // Param- Only change if not becoming a robot\n          if(!is_robot(new_id))\n          {\n            level_param[offset] = param;\n            clear_robot_id(src_board, id);\n\n            // If became a scroll, sensor, or sign...\n            if((new_id >= SENSOR) && (new_id <= PLAYER))\n              id_remove_top(mzx_world, x, y);\n\n            // Delete \"under\"? (if became another type of \"under\")\n            if(flags[new_id] & A_UNDER)\n            {\n              // Became an under, so delete under.\n              src_board->level_under_param[offset] = SPACE;\n              src_board->level_under_id[offset] = 0;\n              src_board->level_under_color[offset] = 7;\n            }\n\n            // Robot no longer exists; exit\n            return;\n          }\n        }\n\n        // Became a robot.\n        break;\n      }\n\n      case ROBOTIC_CMD_CHAR: // Char\n      {\n        cur_robot->robot_char = parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_COLOR: // Color\n      {\n        if(id)\n        {\n          int offset = x + (y * board_width);\n          level_color[offset] =\n           fix_color(parse_param(mzx_world, cmd_ptr + 1, id),\n           level_color[offset]);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_GOTOXY: // Gotoxy\n      {\n        if(id)\n        {\n          int new_x = parse_param(mzx_world, cmd_ptr + 1, id);\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          int new_y = parse_param(mzx_world, p2, id);\n          prefix_mid_xy(mzx_world, &new_x, &new_y, x, y);\n\n          if((new_x != x) || (new_y != y))\n          {\n            // delete at x y\n            int offset = x + (y * board_width);\n            enum thing new_id = (enum thing)level_id[offset];\n            int color = level_color[offset];\n            if(place_at_xy(mzx_world, new_id, color, id, new_x, new_y))\n            {\n              id_remove_top(mzx_world, x, y);\n              cur_robot->xpos = new_x;\n              cur_robot->ypos = new_y;\n              x = new_x;\n              y = new_y;\n            }\n          }\n          done = 1;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_SET: // set counter #\n      {\n        char *dest_string = cmd_ptr + 2;\n        char *src_string = next_param_pos(cmd_ptr + 1);\n        char src_buffer[ROBOT_MAX_TR];\n        char dest_buffer[ROBOT_MAX_TR];\n        tr_msg(mzx_world, dest_string, id, dest_buffer);\n\n        // Setting a string\n        if(is_string(dest_buffer))\n        {\n          struct string dest;\n\n          // Is it a non-immediate\n          if(*src_string)\n          {\n            // Translate into src buffer\n            tr_msg(mzx_world, src_string + 1, id, src_buffer);\n\n            if(is_string(src_buffer))\n            {\n              // Is it another string?\n              if(!get_string(mzx_world, src_buffer, &dest, id))\n              {\n                dest.value = src_buffer;\n                src_buffer[0] = '\\0';\n              }\n            }\n            else\n            {\n              dest.value = src_buffer;\n              dest.length = strlen(src_buffer);\n            }\n          }\n          else\n          {\n            // Set it to immediate representation\n            size_t tmp;\n            dest.value = tr_int_to_string(src_buffer,\n             parse_param(mzx_world, src_string, id), &tmp);\n            dest.length = tmp;\n          }\n\n          gotoed = set_string(mzx_world, dest_buffer, &dest, id);\n\n          // Loading source/robots from strings might have changed these\n          if(gotoed)\n          {\n            program = cur_robot->program_bytecode;\n            cmd_ptr = program + cur_robot->cur_prog_line;\n          }\n        }\n        else\n        {\n          // Set to counter\n          int value;\n          mzx_world->special_counter_return = FOPEN_NONE;\n          value = parse_param(mzx_world, src_string, id);\n\n          if(mzx_world->special_counter_return != FOPEN_NONE)\n          {\n            gotoed = set_counter_special(mzx_world, dest_buffer, value, id);\n\n            // On a game state change, we need to return to the main game loop.\n            if(mzx_world->change_game_state)\n              return;\n\n            // Some specials might have changed these\n#ifdef CONFIG_DEBYTECODE\n            prepare_robot_bytecode(mzx_world, cur_robot);\n#endif\n            program = cur_robot->program_bytecode;\n            cmd_ptr = program + cur_robot->cur_prog_line;\n\n            // Prior to 2.90, SAVE_GAME works immediately and requires the\n            // robot's cycle to be ended for it to safely work. After 2.90\n            // SAVE_GAME takes place at the end of the cycle, so this is\n            // no longer necessary.\n            if(mzx_world->special_counter_return == FOPEN_SAVE_GAME)\n            {\n              if(mzx_world->version < V290)\n              {\n                if(!program[cur_robot->cur_prog_line])\n                  cur_robot->cur_prog_line = 0;\n\n                END_CYCLE;\n                return;\n              }\n            }\n          }\n          else\n          {\n            set_counter(mzx_world, dest_buffer, value, id);\n          }\n        }\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_INC: // inc counter #\n      {\n        char *dest_string = cmd_ptr + 2;\n        char *src_string = next_param_pos(cmd_ptr + 1);\n        char src_buffer[ROBOT_MAX_TR];\n        char dest_buffer[ROBOT_MAX_TR];\n        tr_msg(mzx_world, dest_string, id, dest_buffer);\n\n        // Incrementing a string\n        if(is_string(dest_buffer))\n        {\n          // Must be a non-immediate\n          if(*src_string)\n          {\n            struct string dest;\n            // Translate into src buffer\n            tr_msg(mzx_world, src_string + 1, id, src_buffer);\n\n            if(is_string(src_buffer))\n            {\n              // Is it another string? Grab it\n              if(!get_string(mzx_world, src_buffer, &dest, id))\n                break;\n            }\n            else\n            {\n              dest.value = src_buffer;\n              dest.length = strlen(src_buffer);\n            }\n            // Set it\n            inc_string(mzx_world, dest_buffer, &dest, id);\n          }\n        }\n        else\n        {\n          // Set to counter\n          int value = parse_param(mzx_world, src_string, id);\n          inc_counter(mzx_world, dest_buffer, value, id);\n        }\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_DEC: // dec counter #\n      {\n        char *dest_string = cmd_ptr + 2;\n        char *src_string = next_param_pos(cmd_ptr + 1);\n        char dest_buffer[ROBOT_MAX_TR];\n        int value;\n\n        tr_msg(mzx_world, dest_string, id, dest_buffer);\n        value = parse_param(mzx_world, src_string, id);\n\n        // Decrementing a string\n        if(is_string(dest_buffer))\n        {\n          // Set it to immediate representation\n          dec_string_int(mzx_world, dest_buffer, value, id);\n        }\n        else\n        {\n          // Set to counter\n          dec_counter(mzx_world, dest_buffer, value, id);\n        }\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_IF: // if c?n l\n      {\n        int dest_value = 0, src_value = 0;\n        char *dest_string = cmd_ptr + 1;\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        char *src_string = p2 + 3;\n        enum equality comparison = parse_param_eq(mzx_world, p2);\n        char src_buffer[ROBOT_MAX_TR];\n        char dest_buffer[ROBOT_MAX_TR];\n        int success = 0;\n        boolean has_dest_buffer = false;\n\n        // NOTE: versions prior to 2.92 never did this before is_string.\n        if(is_name_param(mzx_world, dest_string))\n        {\n          tr_msg(mzx_world, dest_string + 1, id, dest_buffer);\n          has_dest_buffer = true;\n        }\n\n        if(has_dest_buffer && is_string(dest_buffer))\n        {\n          struct string dest;\n          struct string src;\n\n          // NOTE: versions prior to 2.92 did tr_msg here instead of above.\n\n          // Get a pointer to the dest string\n          get_string(mzx_world, dest_buffer, &dest, id);\n\n          // Is the second argument immediate?\n          if(*src_string)\n          {\n            tr_msg(mzx_world, src_string + 1, id, src_buffer);\n            if(is_string(src_buffer))\n            {\n              get_string(mzx_world, src_buffer, &src, id);\n            }\n            else\n            {\n              src.value = src_buffer;\n              src.length = strlen(src_buffer);\n            }\n          }\n          else\n          {\n            size_t tmp;\n            src.value = tr_int_to_string(src_buffer,\n             parse_param(mzx_world, src_string, id), &tmp);\n            src.length = tmp;\n          }\n\n          // Non-terminated string compares (2.81+)\n          if(mzx_world->version >= V281)\n          {\n            boolean exact_case = false;\n            boolean wildcards = false;\n\n            // String equality extensions (2.91+)\n            if(mzx_world->version >= V291)\n            {\n              if(comparison == EXACTLY_EQUAL || comparison == WILD_EXACTLY_EQUAL)\n                exact_case = true;\n\n              if(comparison == WILD_EQUAL || comparison == WILD_EXACTLY_EQUAL)\n                wildcards = true;\n            }\n\n            src_value = 0;\n            dest_value = compare_strings(&dest, &src, exact_case, wildcards);\n          }\n          // Null-terminated string compares (2.80X and prior).\n          else\n          {\n            src_value = 0;\n            dest_value = compare_strings_null_terminated(&dest, &src);\n          }\n        }\n        else\n\n        if(has_dest_buffer)\n        {\n          dest_value = get_counter(mzx_world, dest_buffer, id);\n          src_value = parse_param(mzx_world, src_string, id);\n        }\n\n        else\n        {\n          dest_value = parse_param(mzx_world, dest_string, id);\n          src_value = parse_param(mzx_world, src_string, id);\n        }\n\n        if(mzx_world->version < V290)\n        {\n          // In port releases prior to 2.90b there was a horrible\n          // bug stopping comparisons between numbers with a\n          // difference greater than 2^31-1.\n          // To our great regret we must support this functionality\n          // for older worlds.\n          dest_value = dest_value - src_value;\n          src_value = 0;\n        }\n\n        switch(comparison)\n        {\n          case EQUAL:\n          case EXACTLY_EQUAL:\n          case WILD_EQUAL:\n          case WILD_EXACTLY_EQUAL:\n          {\n            if(dest_value == src_value)\n              success = 1;\n            break;\n          }\n\n          case LESS_THAN:\n          {\n            if(dest_value < src_value)\n              success = 1;\n            break;\n          }\n\n          case GREATER_THAN:\n          {\n            if(dest_value > src_value)\n              success = 1;\n            break;\n          }\n\n          case GREATER_THAN_OR_EQUAL:\n          {\n            if(dest_value >= src_value)\n              success = 1;\n            break;\n          }\n\n          case LESS_THAN_OR_EQUAL:\n          {\n            if(dest_value <= src_value)\n              success = 1;\n            break;\n          }\n\n          case NOT_EQUAL:\n          {\n            if(dest_value != src_value)\n              success = 1;\n            break;\n          }\n        }\n\n        if(success)\n        {\n          char *p3 = next_param_pos(p2 + 3);\n          gotoed = send_self_label_tr(mzx_world,  p3 + 1, id);\n        }\n\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_CONDITION: // if condition label\n      case ROBOTIC_CMD_IF_NOT_CONDITION: // if not cond. label\n      {\n        enum dir direction;\n        enum condition condition =\n         parse_param_cond(mzx_world, cmd_ptr + 1, &direction);\n        int success = 0;\n        direction = parsedir(direction, x, y, cur_robot->walk_dir);\n\n        switch(condition)\n        {\n          case WALKING:\n          {\n            if(direction < RANDNS)\n            {\n              if(cur_robot->walk_dir == direction)\n                success = 1;\n              break;\n            }\n            else\n\n            if(direction == NODIR)\n            {\n              if(cur_robot->walk_dir == 0)\n                success = 1;\n            }\n            else\n\n            // assumed anydir\n            if(cur_robot->walk_dir != 0)\n              success = 1;\n            break;\n          }\n\n          case SWIMMING:\n          {\n            if(id)\n            {\n              int offset = x + (y * board_width);\n              int under = level_under_id[offset];\n\n              if(is_water(under))\n                success = 1;\n            }\n            break;\n          }\n\n          case FIRE_WALKING:\n          {\n            if(id)\n            {\n              int offset = x + (y * board_width);\n              enum thing under = (enum thing)level_under_id[offset];\n\n              if((under == LAVA) || (under == FIRE))\n                success = 1;\n            }\n            break;\n          }\n\n          case TOUCHING:\n          {\n            if(id)\n            {\n              int new_x;\n              int new_y;\n\n              if(is_cardinal_dir(direction))\n              {\n                new_x = x;\n                new_y = y;\n                if(!move_dir(src_board, &new_x, &new_y, direction))\n                {\n                  if((mzx_world->player_x == new_x) &&\n                   (mzx_world->player_y == new_y))\n                  {\n                    success = 1;\n                  }\n                }\n              }\n              else\n              {\n                if(direction >= ANYDIR)\n                {\n                  int i;\n                  // either anydir or nodir\n                  // is player touching at all?\n\n                  for(i = 0; i < 4; i++)\n                  {\n                    // try all dirs\n                    new_x = x;\n                    new_y = y;\n                    if(!move_dir(src_board, &new_x, &new_y, int_to_dir(i)))\n                    {\n                      if((mzx_world->player_x == new_x) &&\n                       (mzx_world->player_y == new_y))\n                      {\n                        success = 1;\n                      }\n                    }\n                  }\n\n                  // We want NODIR though, so reverse success\n                  if(direction == NODIR)\n                    success ^= 1;\n                }\n                else\n                {\n                  success = 0;\n                }\n              }\n            }\n\n            break;\n          }\n\n          case BLOCKED:\n          {\n            int new_bl[4];\n\n            // If REL PLAYER or REL COUNTERS, use special code\n            switch(mzx_world->mid_prefix)\n            {\n              case REL_TO_PLAYER:\n              {\n                // Give an ID of -1 to throw it off from not\n                // allowing global robot.\n                calculate_blocked(mzx_world, mzx_world->player_x,\n                 mzx_world->player_y, -1, new_bl);\n                update_blocked = 1;\n                break;\n              }\n\n              case REL_TO_XPOS_YPOS:\n              {\n                // Give an ID of -1 to throw it off from not\n                // allowing global robot.\n                calculate_blocked(mzx_world,\n                 get_counter(mzx_world, \"XPOS\", 0),\n                 get_counter(mzx_world, \"YPOS\", 0), -1, new_bl);\n                update_blocked = 1;\n                break;\n              }\n\n              default:\n              {\n                // Use the blocked list that's already there\n                memcpy(new_bl, _bl, sizeof(int) * 4);\n              }\n            }\n\n            if(is_cardinal_dir(direction))\n            {\n              success = new_bl[dir_to_int(direction)];\n            }\n            else\n            {\n              // either anydir or nodir\n              // is blocked any dir at all?\n              success = new_bl[0] | new_bl[1] | new_bl[2] | new_bl[3];\n              // success = 1 for anydir\n\n              // We want NODIR though, so reverse success\n\n              if((direction == NODIR) || (direction == IDLE))\n                success ^= 1;\n            }\n            break;\n          }\n\n          case ALIGNED:\n          {\n            if(id)\n            {\n              if((mzx_world->player_x == x) || (mzx_world->player_y == y))\n                success = 1;\n            }\n            break;\n          }\n\n          case ALIGNED_NS:\n          {\n            if(id)\n            {\n              if(mzx_world->player_x == x)\n                success = 1;\n            }\n            break;\n          }\n\n          case ALIGNED_EW:\n          {\n            if(id)\n            {\n              if(mzx_world->player_y == y)\n                success = 1;\n            }\n            break;\n          }\n\n          case LASTSHOT:\n          {\n            if(id)\n            {\n              if(is_cardinal_dir(direction))\n              {\n                if(direction == cur_robot->last_shot_dir)\n                  success = 1;\n              }\n            }\n            break;\n          }\n\n          case LASTTOUCH:\n          {\n            if(id)\n            {\n              direction = parsedir(direction, x, y,\n               cur_robot->walk_dir);\n              if(is_cardinal_dir(direction))\n              {\n                if(direction == cur_robot->last_touch_dir)\n                  success = 1;\n              }\n            }\n            break;\n          }\n\n          case RIGHTPRESSED:\n          {\n            success =\n             get_key_status(keycode_internal_wrt_numlock, IKEY_RIGHT) > 0;\n            break;\n          }\n\n          case LEFTPRESSED:\n          {\n            success =\n             get_key_status(keycode_internal_wrt_numlock, IKEY_LEFT) > 0;\n            break;\n          }\n\n          case UPPRESSED:\n          {\n            success =\n             get_key_status(keycode_internal_wrt_numlock, IKEY_UP) > 0;\n            break;\n          }\n\n          case DOWNPRESSED:\n          {\n            success =\n             get_key_status(keycode_internal_wrt_numlock, IKEY_DOWN) > 0;\n            break;\n          }\n\n          case SPACEPRESSED:\n          {\n            success =\n             get_key_status(keycode_internal_wrt_numlock, IKEY_SPACE) > 0;\n            break;\n          }\n\n          case DELPRESSED:\n          {\n            success =\n             get_key_status(keycode_internal_wrt_numlock, IKEY_DELETE) > 0;\n            break;\n          }\n\n          case MUSICON:\n          {\n            success = audio_get_music_on();\n            break;\n          }\n\n          case SOUNDON:\n          {\n            success = audio_get_pcs_on();\n            break;\n          }\n        }\n\n        // Reverse truth if NOT is present\n        if(cmd == ROBOTIC_CMD_IF_NOT_CONDITION)\n          success ^= 1;\n\n        if(success)\n        {\n          // jump\n          gotoed = send_self_label_tr(mzx_world, cmd_ptr + 5, id);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_ANY: // if any thing label\n      {\n        // Get foreg/backg allowed in fb/bg\n        int offset;\n        int color = parse_param(mzx_world, cmd_ptr + 1, id);\n        int fg, bg;\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        enum thing check_id = parse_param_thing(mzx_world, p2);\n        char *p3 = next_param_pos(p2);\n        // Param\n        int check_param = parse_param(mzx_world, p3, id);\n\n        split_colors(color, &fg, &bg);\n        match_version_fix(mzx_world, &fg, &bg, &check_param);\n        // TODO: fix whirlpool id\n\n        for(offset = 0; offset < (board_width * board_height); offset++)\n        {\n          if(check_at_xy(src_board, check_id, fg, bg, check_param, offset))\n          {\n            char *p4 = next_param_pos(p3);\n            gotoed = send_self_label_tr(mzx_world,  p4 + 1, id);\n\n            // The port up through 2.84 allowed this to iterate the entire board.\n            if(mzx_world->version < VERSION_PORT || mzx_world->version > V284)\n              break;\n          }\n        }\n\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_NO: // if no thing label\n      {\n        // Get foreg/backg allowed in fb/bg\n        int offset;\n        int color = parse_param(mzx_world, cmd_ptr + 1, id);\n        int fg, bg;\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        enum thing check_id = parse_param_thing(mzx_world, p2);\n        char *p3 = next_param_pos(p2);\n        // Param\n        int check_param = parse_param(mzx_world, p3, id);\n\n        split_colors(color, &fg, &bg);\n        match_version_fix(mzx_world, &fg, &bg, &check_param);\n        // TODO: fix whirlpool id\n\n        for(offset = 0; offset < (board_width * board_height); offset++)\n        {\n          if(check_at_xy(src_board, check_id, fg, bg, check_param, offset))\n            break;\n        }\n\n        if(offset == (board_width * board_height))\n        {\n          char *p4 = next_param_pos(p3);\n          gotoed = send_self_label_tr(mzx_world,  p4 + 1, id);\n        }\n\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_THING_DIR: // if thing dir\n      {\n        if(id)\n        {\n          int check_color = parse_param(mzx_world, cmd_ptr + 1, id);\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          enum thing check_id = parse_param_thing(mzx_world, p2);\n          char *p3 = next_param_pos(p2);\n          int check_param = parse_param(mzx_world, p3, id);\n          char *p4 = next_param_pos(p3);\n          enum dir direction = parse_param_dir(mzx_world, p4);\n\n          // Note: beneath never goes to label prior to 2.51s2.\n          if(check_dir_xy(mzx_world, check_id, check_color,\n           check_param, x, y, direction, cur_robot, _bl))\n          {\n            char *p5 = next_param_pos(p4);\n            gotoed = send_self_label_tr(mzx_world, p5 + 1, id);\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_NOT_THING_DIR: // if NOT thing dir\n      {\n        if(id)\n        {\n          int check_color = parse_param(mzx_world, cmd_ptr + 1, id);\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          enum thing check_id = parse_param_thing(mzx_world, p2);\n          char *p3 = next_param_pos(p2);\n          int check_param = parse_param(mzx_world, p3, id);\n          char *p4 = next_param_pos(p3);\n          enum dir direction = parse_param_dir(mzx_world, p4);\n\n          // Note: beneath always goes to label prior to 2.51s2.\n          if(!check_dir_xy(mzx_world, check_id, check_color,\n           check_param, x, y, direction, cur_robot, _bl))\n          {\n            char *p5 = next_param_pos(p4);\n            gotoed = send_self_label_tr(mzx_world, p5 + 1, id);\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_THING_XY: // if thing x y\n      {\n        int check_color = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        enum thing check_id = parse_param_thing(mzx_world, p2);\n        char *p3 = next_param_pos(p2);\n        unsigned int check_param = parse_param(mzx_world, p3, id);\n        char *p4 = next_param_pos(p3);\n        int check_x = parse_param(mzx_world, p4, id);\n        char *p5 = next_param_pos(p4);\n        int check_y = parse_param(mzx_world, p5, id);\n        int fg, bg;\n        int offset;\n\n        if(check_id == SPRITE)\n        {\n          int ret = 0;\n\n          if(check_param < MAX_SPRITES &&\n           mzx_world->sprite_list[check_param]->flags & SPRITE_UNBOUND)\n            prefix_mid_xy_unbound(mzx_world, &check_x, &check_y, x, y);\n          else\n            prefix_mid_xy(mzx_world, &check_x, &check_y, x, y);\n\n          // Versions from 2.69c to 2.82b would use sprite_num instead of the\n          // param if the provided color was c??. This was unintuitive and\n          // redundant with newer language extensions, so it was removed.\n          if((check_color == 288) &&\n           (mzx_world->version >= V269c) && (mzx_world->version <= V282))\n            check_param = (unsigned int)mzx_world->sprite_num;\n\n          /* 256 == p?? */\n          if(check_param == 256)\n          {\n            int i;\n\n            // This check was added in 2.84 to fix the removal of the above\n            // check in 2.83. In 2.83, using \"if c?? Sprite\" always failed as\n            // check_color would always be higher than MAX_SPRITES initially.\n            // Add a version check if this breaks something.\n            if(check_color == 288)\n              check_color = 0;\n\n            for(i = check_color; i < MAX_SPRITES; i++)\n            {\n              if(sprite_at_xy(mzx_world, mzx_world->sprite_list[i],\n               check_x, check_y))\n                break;\n            }\n            if(i == MAX_SPRITES)\n            {\n              i = -1;\n              ret = 0;\n            }\n            else\n            {\n              mzx_world->sprite_num = i;\n              ret = 1;\n            }\n          }\n          else\n          {\n            if((unsigned int)check_param < 256)\n            {\n              ret = sprite_at_xy(mzx_world, mzx_world->sprite_list[check_param],\n               check_x, check_y);\n            }\n          }\n\n          if(ret)\n          {\n            char *p6 = next_param_pos(p5);\n            gotoed = send_self_label_tr(mzx_world, p6 + 1, id);\n          }\n        }\n        else\n\n        // Check collision detection for sprite - Exo\n\n        if(check_id == SPR_COLLISION)\n        {\n          struct sprite *check_sprite;\n\n          int ret;\n          if(check_param >= 256)\n          {\n            check_param = (unsigned int)mzx_world->sprite_num;\n\n            if(check_param >= 256)\n              break;\n          }\n\n          check_sprite = mzx_world->sprite_list[check_param];\n\n          if(check_color == 288)\n          {\n            check_x += check_sprite->x;\n            check_y += check_sprite->y;\n          }\n          if(check_sprite->flags & SPRITE_UNBOUND)\n            prefix_mid_xy_unbound(mzx_world, &check_x, &check_y, x, y);\n          else\n            prefix_mid_xy(mzx_world, &check_x, &check_y, x, y);\n          offset = check_x + (check_y * board_width);\n\n          ret = sprite_colliding_xy(mzx_world, check_sprite,\n           check_x, check_y);\n\n          if(ret > 0)\n          {\n            char *p6 = next_param_pos(p5);\n            gotoed = send_self_label_tr(mzx_world, p6 + 1, id);\n          }\n        }\n        else\n        {\n          prefix_mid_xy(mzx_world, &check_x, &check_y, x, y);\n          offset = check_x + (check_y * board_width);\n\n          split_colors(check_color, &fg, &bg);\n          match_version_fix(mzx_world, &fg, &bg, (int *)&check_param);\n          // TODO: fix whirlpool id\n          if(check_at_xy(src_board, check_id, fg, bg, check_param, offset))\n          {\n            char *p6 = next_param_pos(p5);\n            gotoed = send_self_label_tr(mzx_world, p6 + 1, id);\n          }\n        }\n\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_AT: // if at x y label\n      {\n        if(id)\n        {\n          int check_x = parse_param(mzx_world, cmd_ptr + 1, id);\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          int check_y = parse_param(mzx_world, p2, id);\n\n          prefix_mid_xy(mzx_world, &check_x, &check_y, x, y);\n          if((check_x == x) && (check_y == y))\n          {\n            char *p3 = next_param_pos(p2);\n            gotoed = send_self_label_tr(mzx_world, p3 + 1, id);\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_DIR_OF_PLAYER: // if dir of player is thing, \"label\"\n      {\n        enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int check_color = parse_param(mzx_world, p2, id);\n        char *p3 = next_param_pos(p2);\n        enum thing check_id = parse_param_thing(mzx_world, p3);\n        char *p4 = next_param_pos(p3);\n        int check_param = parse_param(mzx_world, p4, id);\n\n        if(check_dir_xy(mzx_world, check_id, check_color,\n         check_param, mzx_world->player_x, mzx_world->player_y,\n         direction, cur_robot, _bl))\n        {\n          char *p5 = next_param_pos(p4);\n          gotoed = send_self_label_tr(mzx_world, p5 + 1, id);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_DOUBLE: // double c\n      {\n        char dest_buffer[ROBOT_MAX_TR];\n        tr_msg(mzx_world, cmd_ptr + 2, id, dest_buffer);\n        mul_counter(mzx_world, dest_buffer, 2, id);\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_HALF: // half c\n      {\n        char dest_buffer[ROBOT_MAX_TR];\n        tr_msg(mzx_world, cmd_ptr + 2, id, dest_buffer);\n        div_counter(mzx_world, dest_buffer, 2, id);\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_GOTO: // Goto\n      {\n        gotoed = send_self_label_tr(mzx_world, cmd_ptr + 2, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_SEND: // Send robot label\n      {\n        char robot_name_buffer[ROBOT_MAX_TR];\n        char label_buffer[ROBOT_MAX_TR];\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        tr_msg(mzx_world, cmd_ptr + 2, id, robot_name_buffer);\n        tr_msg(mzx_world, p2 + 1, id, label_buffer);\n\n        send_robot(mzx_world, robot_name_buffer, label_buffer, 0);\n\n        // Did the position get changed? (send to self)\n        if(old_pos != cur_robot->cur_prog_line)\n          gotoed = 1;\n\n        break;\n      }\n\n      case ROBOTIC_CMD_EXPLODE: // Explode\n      {\n        if(id)\n        {\n          int offset = x + (y * board_width);\n          level_param[offset] =\n           (parse_param(mzx_world, cmd_ptr + 1, id) - 1) * 16;\n          level_id[offset] = (char)EXPLOSION;\n          clear_robot_id(src_board, id);\n        }\n\n        // Robot no longer exists; exit\n        return;\n      }\n\n      case ROBOTIC_CMD_PUT_DIR: // put thing dir\n      {\n        if(id)\n        {\n          int put_color = parse_param(mzx_world, cmd_ptr + 1, id);\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          enum thing put_id = parse_param_thing(mzx_world, p2);\n          char *p3 = next_param_pos(p2);\n          int put_param = parse_param(mzx_world, p3, id);\n          char *p4 = next_param_pos(p3);\n          enum dir direction = parse_param_dir(mzx_world, p4);\n\n          if(put_id < SENSOR)\n          {\n            if(mzx_world->version < V270 && direction == BENEATH)\n              break;\n            place_dir_xy(mzx_world, put_id, put_color, put_param, x, y,\n             direction, cur_robot, _bl);\n          }\n          update_blocked = 1;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_GIVE: // Give # item\n      {\n        int amount = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int item_number = *(p2 + 1);\n        inc_counter(mzx_world, item_to_counter[item_number], amount, 0);\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_TAKE: // Take # item\n      {\n        int amount = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int item_number = *(p2 + 1);\n\n        if(get_counter(mzx_world, item_to_counter[item_number], 0) >=\n         amount)\n        {\n          dec_counter(mzx_world, item_to_counter[item_number], amount, 0);\n        }\n\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_TAKE_OR: // Take # item \"label\"\n      {\n        int amount = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int item_number = *(p2 + 1);\n\n        if(get_counter(mzx_world, item_to_counter[item_number], 0) >=\n         amount)\n        {\n          dec_counter(mzx_world, item_to_counter[item_number], amount, 0);\n        }\n        else\n        {\n          gotoed = send_self_label_tr(mzx_world,  p2 + 4, id);\n        }\n\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_ENDGAME: // Endgame\n      {\n        set_counter(mzx_world, \"LIVES\", 0, 0);\n        set_counter(mzx_world, \"HEALTH\", 0, 0);\n        break;\n      }\n\n      case ROBOTIC_CMD_ENDLIFE: // Endlife\n      {\n        set_counter(mzx_world, \"HEALTH\", 0, 0);\n        break;\n      }\n\n      case ROBOTIC_CMD_MOD: // Mod\n      {\n        char mod_name_buffer[ROBOT_MAX_TR];\n        tr_msg(mzx_world, cmd_ptr + 2, id, mod_name_buffer);\n        magic_load_mod(mzx_world, mod_name_buffer);\n        audio_set_module_volume(src_board->volume);\n        break;\n      }\n\n      case ROBOTIC_CMD_SAM: // sam\n      {\n        char sam_name_buffer[ROBOT_MAX_TR];\n        int frequency = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        tr_msg(mzx_world, p2 + 1, id, sam_name_buffer);\n\n        if(frequency < 0)\n          frequency = 0;\n\n        audio_play_sample(sam_name_buffer, true, frequency);\n\n        break;\n      }\n\n      case ROBOTIC_CMD_VOLUME2:\n      case ROBOTIC_CMD_VOLUME: // Volume\n      {\n        int volume = parse_param(mzx_world, cmd_ptr + 1, id);\n\n        // Pre-port versions bounded volume by using a char.\n        if(mzx_world->version < VERSION_PORT)\n          volume &= 255;\n\n        else\n          volume = CLAMP(volume, 0, 255);\n\n        src_board->volume = volume;\n        src_board->volume_target = volume;\n\n        audio_set_module_volume(volume);\n        break;\n      }\n\n      case ROBOTIC_CMD_END_MOD: // End mod\n      {\n        audio_end_module();\n        src_board->mod_playing[0] = 0;\n        mzx_world->real_mod_playing[0] = 0;\n        break;\n      }\n\n      case ROBOTIC_CMD_END_SAM: // End sam\n      {\n        audio_end_sample();\n        break;\n      }\n\n      case ROBOTIC_CMD_PLAY: // Play notes\n      {\n        play_string(cmd_ptr + 2, 0);\n        break;\n      }\n\n      case ROBOTIC_CMD_END_PLAY: // End play\n      {\n        sfx_clear_queue();\n        break;\n      }\n\n      // FIXME - This probably needs a different implementation\n      case ROBOTIC_CMD_WAIT_THEN_PLAY: // wait play \"str\"\n      {\n        int index_dif = sfx_length_left();\n\n        if(index_dif > 10)\n        {\n          END_CYCLE;\n          return;\n        }\n\n        play_string(cmd_ptr + 2, 0);\n\n        break;\n      }\n\n      case ROBOTIC_CMD_WAIT_PLAY: // wait play\n      {\n        int index_dif = sfx_length_left();\n\n        if(index_dif > 10)\n        {\n          END_CYCLE;\n          return;\n        }\n\n        break;\n      }\n\n      case ROBOTIC_CMD_SFX: // sfx num\n      {\n        int sfx_num = parse_param(mzx_world, cmd_ptr + 1, id);\n        play_sfx(mzx_world, sfx_num);\n        break;\n      }\n\n      case ROBOTIC_CMD_PLAY_IF_SILENT: // play sfx notes\n      {\n        if(!sfx_is_playing())\n          play_string(cmd_ptr + 2, 0);\n\n        break;\n      }\n\n      case ROBOTIC_CMD_OPEN: // open\n      {\n        if(id)\n        {\n          enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n          parsedir(direction, x, y, cur_robot->walk_dir);\n\n          if(is_cardinal_dir(direction))\n          {\n            int new_x = x;\n            int new_y = y;\n            if(!move_dir(src_board, &new_x, &new_y, direction))\n            {\n              int new_offset = new_x + (new_y * board_width);\n              enum thing new_id = (enum thing)level_id[new_offset];\n\n              if((new_id == DOOR) || (new_id == GATE))\n              {\n                // Become pushable for right now\n                int offset = x + (y * board_width);\n                enum thing old_id = (enum thing)level_id[offset];\n                level_id[offset] = (char)ROBOT_PUSHABLE;\n                grab_item(mzx_world, new_x, new_y, 0);\n\n                // If a door opened in the direction of the robot, the robot\n                // was pushed and its position needs to be updated. This might\n                // have been through a transport so just use xpos/ypos.\n                if(level_id[offset] != ROBOT_PUSHABLE)\n                {\n                  x = cur_robot->xpos;\n                  y = cur_robot->ypos;\n                  offset = x + (y * board_width);\n                }\n\n                level_id[offset] = old_id;\n                update_blocked = 1;\n              }\n            }\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_LOCKSELF: // Lockself\n      {\n        cur_robot->is_locked = 1;\n        break;\n      }\n\n      case ROBOTIC_CMD_UNLOCKSELF: // Unlockself\n      {\n        cur_robot->is_locked = 0;\n        break;\n      }\n\n      case ROBOTIC_CMD_SEND_DIR: // Send DIR \"label\"\n      {\n        if(id)\n        {\n          int send_x = x;\n          int send_y = y;\n          enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          direction = parsedir(direction, x, y, cur_robot->walk_dir);\n\n          if(is_cardinal_dir(direction))\n          {\n            if(!move_dir(src_board, &send_x, &send_y, direction))\n            {\n              send_at_xy(mzx_world, id, send_x, send_y, p2 + 1);\n              // Did the position get changed? (send to self)\n              if(old_pos != cur_robot->cur_prog_line)\n                gotoed = 1;\n            }\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_ZAP: // Zap label num\n      {\n        char label_buffer[ROBOT_MAX_TR];\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int num_times = parse_param(mzx_world, p2, id);\n        int i;\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, label_buffer);\n        for(i = 0; i < num_times; i++)\n        {\n          if(!zap_label(cur_robot, label_buffer))\n            break;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_RESTORE: // Restore label num\n      {\n        char label_buffer[ROBOT_MAX_TR];\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int num_times = parse_param(mzx_world, p2, id);\n        int i;\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, label_buffer);\n        for(i = 0; i < num_times; i++)\n        {\n          if(!restore_label(cur_robot, label_buffer))\n            break;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_LOCKPLAYER: // Lockplayer\n      {\n        src_board->player_ns_locked = 1;\n        src_board->player_ew_locked = 1;\n        src_board->player_attack_locked = 1;\n        break;\n      }\n\n      case ROBOTIC_CMD_UNLOCKPLAYER: // unlockplayer\n      {\n        src_board->player_ns_locked = 0;\n        src_board->player_ew_locked = 0;\n        src_board->player_attack_locked = 0;\n        break;\n      }\n\n      case ROBOTIC_CMD_LOCKPLAYER_NS: // lockplayer ns\n      {\n        src_board->player_ns_locked = 1;\n        break;\n      }\n\n      case ROBOTIC_CMD_LOCKPLAYER_EW: // lockplayer ew\n      {\n        src_board->player_ew_locked = 1;\n        break;\n      }\n\n      case ROBOTIC_CMD_LOCKPLAYER_ATTACK: // lockplayer attack\n      {\n        src_board->player_attack_locked = 1;\n        break;\n      }\n\n      case ROBOTIC_CMD_MOVE_PLAYER_DIR: // move player dir\n      case ROBOTIC_CMD_MOVE_PLAYER_DIR_OR: // move pl dir \"label\"\n      {\n        enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n        direction = parsedir(direction, x, y, cur_robot->walk_dir);\n        if(is_cardinal_dir(direction))\n        {\n          int old_x = mzx_world->player_x;\n          int old_y = mzx_world->player_y;\n          int old_board = mzx_world->current_board_id;\n          enum board_target old_target = mzx_world->target_where;\n\n          // Have to fix vars and move to next command NOW, in case player\n          // is sent to another screen!\n          mzx_world->first_prefix = 0;\n          mzx_world->mid_prefix = 0;\n          mzx_world->last_prefix = 0;\n          cur_robot->pos_within_line = 0;\n          cur_robot->cur_prog_line +=\n           program[cur_robot->cur_prog_line] + 2;\n\n          if(!program[cur_robot->cur_prog_line])\n            cur_robot->cur_prog_line = 0;\n\n          // Move player\n          move_player(mzx_world, dir_to_int(direction));\n\n          if((cmd == ROBOTIC_CMD_MOVE_PLAYER_DIR_OR) &&\n           (mzx_world->player_x == old_x) &&\n           (mzx_world->player_y == old_y) &&\n           (mzx_world->current_board_id == old_board) &&\n           (mzx_world->target_where == old_target))\n          {\n            char *p2 = next_param_pos(cmd_ptr + 1);\n            gotoed = send_self_label_tr(mzx_world,  p2 + 1, id);\n          }\n\n          END_CYCLE;\n          return;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_PUT_PLAYER_XY: // Put player x y\n      {\n        int put_x = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int put_y = parse_param(mzx_world, p2, id);\n\n        prefix_mid_xy(mzx_world, &put_x, &put_y, x, y);\n\n        if(place_player_xy(mzx_world, put_x, put_y))\n        {\n          done = 1;\n          if((mzx_world->player_x == x) && (mzx_world->player_y == y))\n          {\n            // Robot no longer exists; exit\n            return;\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_PLAYER_XY: // if player x y\n      {\n        int check_x = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int check_y = parse_param(mzx_world, p2, id);\n        prefix_mid_xy(mzx_world, &check_x, &check_y, x, y);\n\n        if((check_x == mzx_world->player_x) &&\n         (check_y == mzx_world->player_y))\n        {\n          char *p3 = next_param_pos(p2);\n          gotoed = send_self_label_tr(mzx_world, p3 + 1, id);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_PUT_PLAYER_DIR: // put player dir\n      {\n        enum dir put_dir = parse_param_dir(mzx_world, cmd_ptr + 1);\n        put_dir = parsedir(put_dir, x, y, cur_robot->walk_dir);\n\n        if(is_cardinal_dir(put_dir))\n        {\n          int put_x = x;\n          int put_y = y;\n          if(!move_dir(src_board, &put_x, &put_y, put_dir))\n          {\n            if(place_player_xy(mzx_world, put_x, put_y))\n            {\n              if((mzx_world->player_x == x) && (mzx_world->player_y == y))\n              {\n                // Robot no longer exists; exit\n                return;\n              }\n\n              done = 1;\n            }\n          }\n        }\n\n        break;\n      }\n\n      case ROBOTIC_CMD_ROTATECW: // rotate cw\n      {\n        if(id)\n        {\n          rotate(mzx_world, x, y, 0);\n          // Figure blocked vars\n          update_blocked = 1;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_ROTATECCW: // rotate ccw\n      {\n        if(id)\n        {\n          rotate(mzx_world, x, y, 1);\n          // Figure blocked vars\n          update_blocked = 1;\n          break;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_SWITCH: // switch dir dir\n      {\n        if(id)\n        {\n          int src_x = x;\n          int dest_x = x;\n          int src_y = y;\n          int dest_y = y;\n          enum dir dest_dir = parse_param_dir(mzx_world, cmd_ptr + 1);\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          enum dir src_dir = parse_param_dir(mzx_world, p2);\n\n          src_dir = parsedir(src_dir, x, y, cur_robot->walk_dir);\n          dest_dir = parsedir(dest_dir, x, y, cur_robot->walk_dir);\n\n          if(is_cardinal_dir(src_dir) && is_cardinal_dir(dest_dir))\n          {\n            int status = move_dir(src_board, &src_x, &src_y, src_dir);\n\n            status |= move_dir(src_board, &dest_x, &dest_y, dest_dir);\n            if(!status)\n            {\n              // Switch src_x, src_y with dest_x, dest_y\n              // The nice thing is that nothing gets deleted,\n              // nothing gets copied.\n              if((src_x != dest_x) || (src_y != dest_y))\n              {\n                struct robot *src_robot;\n                int src_offset = src_x + (src_y * board_width);\n                int dest_offset = dest_x + (dest_y * board_width);\n                enum thing cp_id = (enum thing)level_id[src_offset];\n                int cp_param = level_param[src_offset];\n                int cp_color = level_color[src_offset];\n                level_id[src_offset] = level_id[dest_offset];\n                level_param[src_offset] = level_param[dest_offset];\n                level_color[src_offset] = level_color[dest_offset];\n                level_id[dest_offset] = cp_id;\n                level_param[dest_offset] = cp_param;\n                level_color[dest_offset] = cp_color;\n                // Figure blocked vars\n                update_blocked = 1;\n\n                // This might have moved robots. Fix their xpos/ypos values.\n                // Old versions didn't fix these, so don't touch the compat pos.\n                if(is_robot(cp_id))\n                {\n                  src_robot = src_board->robot_list[cp_param];\n                  src_robot->xpos = dest_x;\n                  src_robot->ypos = dest_y;\n                }\n                if(is_robot(level_id[src_offset]))\n                {\n                  cp_param = level_param[src_offset];\n                  src_robot = src_board->robot_list[cp_param];\n                  src_robot->xpos = src_x;\n                  src_robot->ypos = src_y;\n                }\n              }\n            }\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_SHOOT: // Shoot\n      {\n        if(id)\n        {\n          enum dir direction =\n           parsedir((enum dir)cmd_ptr[2], x, y, cur_robot->walk_dir);\n          if(is_cardinal_dir(direction) && !(_bl[dir_to_int(direction)] & 2))\n          {\n            // Block\n            shoot(mzx_world, x, y, dir_to_int(direction),\n             MIN(cur_robot->bullet_type, 2));\n\n            // Versions 2.80 through 2.91X had the logic inverted here, meaning\n            // the blocked array would never get updated when shooting. If this\n            // fix breaks something, exclude those versions from setting this.\n            if(!_bl[dir_to_int(direction)])\n              _bl[dir_to_int(direction)] = 3;\n          }\n        }\n        // MZX 2.83 erroneously ended the cycle here, some games depend on it...\n        if(mzx_world->version == V283)\n        {\n          // Continue to the next line.\n          ADVANCE_LINE;\n          END_CYCLE;\n          return;\n        }\n\n        break;\n      }\n\n      case ROBOTIC_CMD_LAYBOMB: // laybomb\n      {\n        if(id)\n        {\n          enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n          place_dir_xy(mzx_world, LIT_BOMB, 8, 0, x, y, direction,\n           cur_robot, _bl);\n          update_blocked = 1;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_LAYBOMB_HIGH: // laybomb high\n      {\n        if(id)\n        {\n          enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n          place_dir_xy(mzx_world, LIT_BOMB, 8, 128, x, y, direction,\n           cur_robot, _bl);\n          update_blocked = 1;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_SHOOTMISSILE: // shoot missile\n      {\n        if(id)\n        {\n          enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n          direction = parsedir(direction, x, y, cur_robot->walk_dir);\n          if(is_cardinal_dir(direction))\n          {\n            shoot_missile(mzx_world, x, y, dir_to_int(direction));\n            calculate_blocked(mzx_world, x, y, id, _bl);\n          }\n        }\n        // MZX 2.83 erroneously ended the cycle here, some games depend on it...\n        if(mzx_world->version == V283)\n        {\n          // Continue to the next line.\n          ADVANCE_LINE;\n          END_CYCLE;\n          return;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_SHOOTSEEKER: // shoot seeker\n      {\n        if(id)\n        {\n          enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n          direction = parsedir(direction, x, y, cur_robot->walk_dir);\n          if(is_cardinal_dir(direction))\n          {\n            shoot_seeker(mzx_world, x, y, dir_to_int(direction));\n            calculate_blocked(mzx_world, x, y, id, _bl);\n          }\n        }\n        // MZX 2.83 erroneously ended the cycle here, some games depend on it...\n        if(mzx_world->version == V283)\n        {\n          // Continue to the next line.\n          ADVANCE_LINE;\n          END_CYCLE;\n          return;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_SPITFIRE: // spit fire\n      {\n        if(id)\n        {\n          enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n          direction = parsedir(direction, x, y, cur_robot->walk_dir);\n          if(is_cardinal_dir(direction))\n          {\n            shoot_fire(mzx_world, x, y, dir_to_int(direction));\n            calculate_blocked(mzx_world, x, y, id, _bl);\n          }\n        }\n        // MZX 2.83 erroneously ended the cycle here, some games depend on it...\n        if(mzx_world->version == V283)\n        {\n          // Continue to the next line.\n          ADVANCE_LINE;\n          END_CYCLE;\n          return;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_LAZERWALL: // lazer wall\n      {\n        if(id)\n        {\n          enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n          direction = parsedir(direction, x, y, cur_robot->walk_dir);\n\n          if(is_cardinal_dir(direction))\n          {\n            char *p2 = next_param_pos(cmd_ptr + 1);\n            int duration = parse_param(mzx_world, p2, id);\n            shoot_lazer(mzx_world, x, y, dir_to_int(direction),\n             duration, level_color[x + (y * board_width)]);\n            // Figure blocked vars\n            update_blocked = 1;\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_PUT_XY: // put at xy\n      {\n        // Defer initialization of color until later because it\n        // might be an MZM name string instead.\n        int put_color;\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        enum thing put_id = parse_param_thing(mzx_world, p2);\n        char *p3 = next_param_pos(p2);\n        int put_param = parse_param(mzx_world, p3, id);\n        char *p4 = next_param_pos(p3);\n        int put_x = parse_param(mzx_world, p4, id);\n        char *p5 = next_param_pos(p4);\n        int put_y = parse_param(mzx_world, p5, id);\n\n        // MZM image file\n        if((put_id == IMAGE_FILE) &&\n         *(cmd_ptr + 1) && (*(cmd_ptr + 2) == '@'))\n        {\n          int dest_width, dest_height;\n          char mzm_name_buffer[ROBOT_MAX_TR];\n          char *translated_name = cmalloc(MAX_PATH);\n\n          // \"Type\" must be 0, 1, or 2; board, overlay, or vlayer\n          put_param %= 3;\n\n          if(put_param == 2)\n          {\n            // Use vlayer dimensions\n            dest_width = mzx_world->vlayer_width;\n            dest_height = mzx_world->vlayer_height;\n          }\n          else\n          {\n            dest_width = src_board->board_width;\n            dest_height = src_board->board_height;\n          }\n\n          // Note: due to a bug between 2.80 and 2.93, using a REL FIRST\n          // command after a REL command prevented this from working.\n          prefix_mid_xy_var(mzx_world, &put_x, &put_y, x, y,\n           dest_width, dest_height);\n\n          tr_msg(mzx_world, cmd_ptr + 3, id, mzm_name_buffer);\n\n          if(mzx_world->version >= V290 && is_string(mzm_name_buffer))\n          {\n            struct string src;\n\n            if(get_string(mzx_world, mzm_name_buffer, &src, id))\n              load_mzm_memory(mzx_world, mzm_name_buffer, put_x, put_y,\n               put_param, 1, CUSTOM_BLOCK, src.value, src.length);\n          }\n          else\n          {\n            if(!fsafetranslate(mzm_name_buffer, translated_name, MAX_PATH))\n            {\n              load_mzm(mzx_world, translated_name, put_x, put_y,\n               put_param, 1, CUSTOM_BLOCK);\n            }\n          }\n          free(translated_name);\n          if(id)\n          {\n            int offset = x + (y * board_width);\n            enum thing d_id = (enum thing)level_id[offset];\n\n            if(!is_robot(d_id))\n            {\n              // Robot no longer exists; exit\n              return;\n            }\n\n            id = level_param[offset];\n            cur_robot = src_board->robot_list[id];\n\n            // Update position\n            program = cur_robot->program_bytecode;\n            cmd_ptr = program + cur_robot->cur_prog_line;\n\n            update_blocked = 1;\n          }\n        }\n        else\n\n        // Sprite\n        if(put_id == SPRITE)\n        {\n          put_color = parse_param(mzx_world, cmd_ptr + 1, id);\n          if(put_param == 256)\n            put_param = mzx_world->sprite_num;\n\n          if((unsigned int)put_param < 256)\n          {\n            if(mzx_world->sprite_list[put_param]->flags & SPRITE_UNBOUND)\n              prefix_mid_xy_unbound(mzx_world, &put_x, &put_y, x, y);\n            else\n              prefix_mid_xy(mzx_world, &put_x, &put_y, x, y);\n\n            plot_sprite(mzx_world, mzx_world->sprite_list[put_param],\n             put_color, put_x, put_y);\n          }\n        }\n        else\n        {\n          // Shouldn't be able to place robots, scrolls etc this way!\n          if(put_id < SENSOR)\n          {\n            put_color = parse_param(mzx_world, cmd_ptr + 1, id);\n            prefix_mid_xy(mzx_world, &put_x, &put_y, x, y);\n            place_at_xy(mzx_world, put_id, put_color, put_param,\n             put_x, put_y);\n            // Still alive?\n            if((put_x == x) && (put_y == y))\n            {\n              // Robot no longer exists; exit\n              return;\n            }\n\n            update_blocked = 1;\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_SEND_XY: // Send x y \"label\"\n      {\n        int send_x = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int send_y = parse_param(mzx_world, p2, id);\n        char *p3 = next_param_pos(p2);\n        prefix_mid_xy(mzx_world, &send_x, &send_y, x, y);\n\n        send_at_xy(mzx_world, id, send_x, send_y, p3 + 1);\n        // Did the position get changed? (send to self)\n        if(old_pos != cur_robot->cur_prog_line)\n          gotoed = 1;\n\n        break;\n      }\n\n      case ROBOTIC_CMD_COPYROBOT_NAMED: // copyrobot \"\"\n      {\n        int first, last;\n        char robot_name_buffer[ROBOT_MAX_TR];\n        // Get the robot name\n        tr_msg(mzx_world, cmd_ptr + 2, id, robot_name_buffer);\n\n        // Check the global robot\n        if(!strcasecmp(mzx_world->global_robot.robot_name,\n         robot_name_buffer))\n        {\n          replace_robot(mzx_world, src_board, &mzx_world->global_robot, id);\n        }\n        else\n        {\n          // Find the first robot that matches\n          if(find_robot(src_board, robot_name_buffer, &first, &last))\n          {\n            struct robot *found_robot =\n             src_board->robot_list_name_sorted[first];\n\n            if(found_robot != cur_robot)\n            {\n              replace_robot(mzx_world, src_board, found_robot, id);\n            }\n          }\n        }\n        cur_robot = src_board->robot_list[id];\n        END_CYCLE;\n        return;\n      }\n\n      case ROBOTIC_CMD_COPYROBOT_XY: // copyrobot x y\n      {\n        int copy_x = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int copy_y = parse_param(mzx_world, p2, id);\n        int offset;\n        int d_id;\n\n        prefix_mid_xy(mzx_world, &copy_x, &copy_y, x, y);\n        offset = copy_x + (copy_y * board_width);\n        d_id = (enum thing)level_id[offset];\n\n        if(is_robot(d_id))\n        {\n          int idx = level_param[offset];\n          replace_robot(mzx_world, src_board, src_board->robot_list[idx], id);\n        }\n\n        cur_robot = src_board->robot_list[id];\n        END_CYCLE;\n        return;\n      }\n\n      case ROBOTIC_CMD_COPYROBOT_DIR: // copyrobot dir\n      {\n        enum dir copy_dir = parse_param_dir(mzx_world, cmd_ptr + 1);\n        int offset;\n        enum thing d_id;\n        int copy_x = x;\n        int copy_y = y;\n\n        copy_dir = parsedir(copy_dir, x, y, cur_robot->walk_dir);\n\n        if(is_cardinal_dir(copy_dir))\n        {\n          if(!move_dir(src_board, &copy_x, &copy_y, copy_dir))\n          {\n            offset = copy_x + (copy_y * board_width);\n            d_id = (enum thing)level_id[offset];\n\n            if(is_robot(d_id))\n            {\n              int idx = level_param[offset];\n              replace_robot(mzx_world, src_board, src_board->robot_list[idx],\n               id);\n            }\n          }\n        }\n        cur_robot = src_board->robot_list[id];\n        END_CYCLE;\n        return;\n      }\n\n      case ROBOTIC_CMD_DUPLICATE_SELF_DIR: // dupe self dir\n      {\n        if(id)\n        {\n          enum dir duplicate_dir = parse_param_dir(mzx_world, cmd_ptr + 1);\n          int dest_id;\n          enum thing duplicate_id;\n          int duplicate_color, offset;\n          int duplicate_x = x;\n          int duplicate_y = y;\n\n          duplicate_dir = parsedir(duplicate_dir, x, y,\n           cur_robot->walk_dir);\n\n          if(is_cardinal_dir(duplicate_dir))\n          {\n            offset = x + (y * board_width);\n            duplicate_color = level_color[offset];\n            duplicate_id = (enum thing)level_id[offset];\n\n            if(!move_dir(src_board, &duplicate_x, &duplicate_y, duplicate_dir))\n            {\n              // Fail if the player is at the destination; this would create a\n              // buggy robot that doesn't exist on the board.\n              if(level_id[duplicate_x + (duplicate_y * board_width)] == PLAYER)\n                break;\n\n              dest_id = duplicate_robot(mzx_world, src_board, cur_robot,\n               duplicate_x, duplicate_y, 0);\n\n              if(dest_id != -1)\n                place_at_xy(mzx_world, duplicate_id, duplicate_color,\n                 dest_id, duplicate_x, duplicate_y);\n            }\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_DUPLICATE_SELF_XY: // dupe self xy\n      {\n        int duplicate_x = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int duplicate_y = parse_param(mzx_world, p2, id);\n        int dest_id;\n        enum thing duplicate_id;\n        int duplicate_color, offset;\n\n        prefix_mid_xy(mzx_world, &duplicate_x, &duplicate_y, x, y);\n\n        // Fail if the player is at the destination; this would create a\n        // buggy robot that doesn't exist on the board.\n        if(level_id[duplicate_x + (duplicate_y * board_width)] == PLAYER)\n          break;\n\n        if((duplicate_x != x) || (duplicate_y != y))\n        {\n          if(id)\n          {\n            offset = x + (y * board_width);\n            duplicate_color = level_color[offset];\n            duplicate_id = (enum thing)level_id[offset];\n          }\n          else\n          {\n            duplicate_color = 7;\n            duplicate_id = ROBOT;\n          }\n\n          dest_id = duplicate_robot(mzx_world, src_board, cur_robot,\n           duplicate_x, duplicate_y, 0);\n\n          if(dest_id != -1)\n          {\n            place_at_xy(mzx_world, duplicate_id,\n             duplicate_color, dest_id, duplicate_x, duplicate_y);\n          }\n        }\n        else\n        {\n          cur_robot->cur_prog_line = 1;\n          gotoed = 1;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_BULLETN: // bulletn\n      {\n        int new_char = parse_param(mzx_world, cmd_ptr + 1, id);\n        id_chars[bullet_char + 0] = new_char;\n        id_chars[bullet_char + 4] = new_char;\n        id_chars[bullet_char + 8] = new_char;\n        break;\n      }\n\n      case ROBOTIC_CMD_BULLETS: // bullets\n      {\n        int new_char = parse_param(mzx_world, cmd_ptr + 1, id);\n        id_chars[bullet_char + 1] = new_char;\n        id_chars[bullet_char + 5] = new_char;\n        id_chars[bullet_char + 9] = new_char;\n        break;\n      }\n\n      case ROBOTIC_CMD_BULLETE: // bullete\n      {\n        int new_char = parse_param(mzx_world, cmd_ptr + 1, id);\n        id_chars[bullet_char + 2] = new_char;\n        id_chars[bullet_char + 6] = new_char;\n        id_chars[bullet_char + 10] = new_char;\n        break;\n      }\n\n      case ROBOTIC_CMD_BULLETW: // bulletw\n      {\n        int new_char = parse_param(mzx_world, cmd_ptr + 1, id);\n        id_chars[bullet_char + 3] = new_char;\n        id_chars[bullet_char + 7] = new_char;\n        id_chars[bullet_char + 11] = new_char;\n        break;\n      }\n\n      case ROBOTIC_CMD_GIVEKEY: // givekey col\n      {\n        int key_num = parse_param(mzx_world, cmd_ptr + 1, id) & 0x0F;\n        give_key(mzx_world, key_num);\n        break;\n      }\n\n      case ROBOTIC_CMD_GIVEKEY_OR: // givekey col \"l\"\n      {\n        int key_num = parse_param(mzx_world, cmd_ptr + 1, id) & 0x0F;\n        if(give_key(mzx_world, key_num))\n        {\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          gotoed = send_self_label_tr(mzx_world, p2 + 1, id);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_TAKEKEY: // takekey col\n      {\n        int key_num = parse_param(mzx_world, cmd_ptr + 1, id) & 0x0F;\n        take_key(mzx_world, key_num);\n        break;\n      }\n\n      case ROBOTIC_CMD_TAKEKEY_OR: // takekey col \"l\"\n      {\n        int key_num = parse_param(mzx_world, cmd_ptr + 1, id) & 0x0F;\n        if(take_key(mzx_world, key_num))\n        {\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          gotoed = send_self_label_tr(mzx_world, p2 + 1, id);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_INC_RANDOM: // inc c r\n      {\n        char dest_buffer[ROBOT_MAX_TR];\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        char *p3 = next_param_pos(p2);\n        int min_value = parse_param(mzx_world, p2, id);\n        int max_value = parse_param(mzx_world, p3, id);\n        int result;\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, dest_buffer);\n        result = get_random_range(min_value, max_value);\n        inc_counter(mzx_world, dest_buffer, result, id);\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_DEC_RANDOM: // dec c r\n      {\n        char dest_buffer[ROBOT_MAX_TR];\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        char *p3 = next_param_pos(p2);\n        int min_value = parse_param(mzx_world, p2, id);\n        int max_value = parse_param(mzx_world, p3, id);\n        int result;\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, dest_buffer);\n        result = get_random_range(min_value, max_value);\n        dec_counter(mzx_world, dest_buffer, result, id);\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_SET_RANDOM: // set c r\n      {\n        char dest_buffer[ROBOT_MAX_TR];\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        char *p3 = next_param_pos(p2);\n        int min_value = parse_param(mzx_world, p2, id);\n        int max_value = parse_param(mzx_world, p3, id);\n        int result;\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, dest_buffer);\n        result = get_random_range(min_value, max_value);\n        set_counter(mzx_world, dest_buffer, result, id);\n        last_label = -1;\n        break;\n      }\n\n      // Trade givenum givetype takenum taketype poorlabel\n      case ROBOTIC_CMD_TRADE:\n      {\n        int give_num = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int give_type = parse_param(mzx_world, p2, id);\n        char *p3 = next_param_pos(p2);\n        int take_num = parse_param(mzx_world, p3, id);\n        char *p4 = next_param_pos(p3);\n        int take_type = parse_param(mzx_world, p4, id);\n        int amount_held = get_counter(mzx_world, item_to_counter[take_type], 0);\n\n        if(amount_held < take_num)\n        {\n          char *p5 = next_param_pos(p4);\n          gotoed = send_self_label_tr(mzx_world, p5 + 1, id);\n        }\n        else\n        {\n          dec_counter(mzx_world, item_to_counter[take_type], take_num, 0);\n          inc_counter(mzx_world, item_to_counter[give_type], give_num, 0);\n        }\n\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_SEND_DIR_PLAYER: // Send DIR of player \"label\"\n      {\n        int send_x = mzx_world->player_x;\n        int send_y = mzx_world->player_y;\n        enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n        direction = parsedir(direction, send_x, send_y,\n         cur_robot->walk_dir);\n\n        if(!move_dir(src_board, &send_x, &send_y, direction))\n        {\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          send_at_xy(mzx_world, id, send_x, send_y, p2 + 1);\n          // Did the position get changed? (send to self)\n          if(old_pos != cur_robot->cur_prog_line)\n            gotoed = 1;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_PUT_DIR_PLAYER: // put thing dir of player\n      {\n        int put_color = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        enum thing put_id = parse_param_thing(mzx_world, p2);\n        char *p3 = next_param_pos(p2);\n        int put_param = parse_param(mzx_world, p3, id);\n        char *p4 = next_param_pos(p3);\n        enum dir direction = parse_param_dir(mzx_world, p4);\n\n        if(put_id < SENSOR)\n        {\n          int player_bl[4];\n\n          calculate_blocked(mzx_world, mzx_world->player_x,\n           mzx_world->player_y, 1, player_bl);\n\n          place_dir_xy(mzx_world, put_id, put_color, put_param,\n           mzx_world->player_x, mzx_world->player_y, direction,\n           cur_robot, player_bl);\n\n          /* Ensure that if the put involves overwriting this robot,\n           * we immediately abort execution of it. However, the global\n           * robot should not have this check done, since it doesn't\n           * exist on the board.\n           */\n          if(id && !is_robot(level_id[x + (y * board_width)]))\n          {\n            // Robot no longer exists; exit\n            return;\n          }\n        }\n\n        update_blocked = 1;\n        break;\n      }\n\n      case ROBOTIC_CMD_SLASH: // /\"dirs\"\n      case ROBOTIC_CMD_PERSISTENT_GO: // Persistent go [str]\n      {\n        if(id)\n        {\n          int current_char;\n          int direction;\n          char dir_str_buffer[ROBOT_MAX_TR];\n\n          tr_msg(mzx_world, cmd_ptr + 2, id, dir_str_buffer);\n\n          // get next dir char, if none, next cmd\n          current_char = dir_str_buffer[cur_robot->pos_within_line];\n          cur_robot->pos_within_line++;\n          // current_char must be 'n', 's', 'w', 'e' or 'i'\n          switch(current_char)\n          {\n            case 'n':\n            case 'N':\n              direction = NORTH;\n              break;\n            case 's':\n            case 'S':\n              direction = SOUTH;\n              break;\n            case 'e':\n            case 'E':\n              direction = EAST;\n              break;\n            case 'w':\n            case 'W':\n              direction = WEST;\n              break;\n            case 'i':\n            case 'I':\n              direction = IDLE;\n              break;\n            default:\n              direction = -1;\n          }\n\n          if(is_cardinal_dir(direction))\n          {\n            if((move(mzx_world, x, y, dir_to_int(direction),\n             CAN_PUSH | CAN_TRANSPORT | CAN_FIREWALK | CAN_WATERWALK |\n             CAN_LAVAWALK * cur_robot->can_lavawalk |\n             (cur_robot->can_goopwalk ? CAN_GOOPWALK : 0))) &&\n             (cmd == ROBOTIC_CMD_PERSISTENT_GO))\n            {\n              cur_robot->pos_within_line--; // persistent...\n            }\n\n            move_dir(src_board, &x, &y, direction);\n          }\n\n          if(direction != -1)\n          {\n            END_CYCLE;\n            return;\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_MESSAGE_LINE: // Mesg\n      {\n        char message_buffer[ROBOT_MAX_TR];\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, message_buffer);\n        set_mesg_direct(src_board, message_buffer);\n        break;\n      }\n\n      case ROBOTIC_CMD_MESSAGE_BOX_LINE:\n      case ROBOTIC_CMD_MESSAGE_BOX_OPTION:\n      case ROBOTIC_CMD_MESSAGE_BOX_MAYBE_OPTION:\n      case ROBOTIC_CMD_MESSAGE_BOX_COLOR_LINE:\n      case ROBOTIC_CMD_MESSAGE_BOX_CENTER_LINE:\n      {\n        // Box messages!\n        char label_buffer[ROBOT_MAX_TR];\n        int next_prog_line, next_cmd;\n\n        robot_box_display(mzx_world, cmd_ptr - 1, label_buffer, id);\n\n        // Move to end of all box mesg cmds.\n        while(1)\n        {\n          // jump command length, command length bytes, command length\n          next_prog_line = cur_robot->cur_prog_line +\n                           program[cur_robot->cur_prog_line] + 2;\n\n          // At next line- check type\n          if(!program[next_prog_line])\n          {\n            END_PROGRAM;\n            return;\n          }\n\n          next_cmd = program[next_prog_line + 1];\n          if(!is_robot_box_command(next_cmd))\n            break;\n\n          cur_robot->cur_prog_line = next_prog_line;\n        }\n\n        // Send label\n        if(label_buffer[0])\n          gotoed = send_self_label_tr(mzx_world, label_buffer, id);\n\n        /* If this isn't a label jump, or the jump failed, don't\n         * execute the workaround for subroutines. Subroutine jumps\n         * play tricks with the cur_prog_line variable, so we must not\n         * increment it ourselves. Non-subroutine jumps don't care, so\n         * we don't need to detect these.\n         */\n        if(!gotoed)\n          ADVANCE_LINE;\n\n        END_CYCLE;\n        return;\n      }\n\n      case ROBOTIC_CMD_COMMENT: // comment-do nothing! Maybe.\n      {\n        // (unless first char is a @)\n        if(cmd_ptr[2] == '@')\n        {\n          char name_buffer[ROBOT_MAX_TR];\n          tr_msg(mzx_world, cmd_ptr + 3, id, name_buffer);\n          name_buffer[ROBOT_NAME_SIZE - 1] = 0;\n\n          if(id)\n          {\n            change_robot_name(src_board, cur_robot, name_buffer);\n          }\n          else\n          {\n            // Special code for global robot\n            strcpy(cur_robot->robot_name, name_buffer);\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_TELEPORT: // teleport\n      {\n        char board_dest_buffer[ROBOT_MAX_TR];\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int teleport_x = parse_param(mzx_world, p2, id);\n        char *p3 = next_param_pos(p2);\n        int teleport_y = parse_param(mzx_world, p3, id);\n        int board_id;\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, board_dest_buffer);\n        board_id = find_board(mzx_world, board_dest_buffer);\n\n        if(board_id != NO_BOARD)\n        {\n          prefix_mid_xy_ext(mzx_world, mzx_world->board_list[board_id],\n           &teleport_x, &teleport_y, x, y);\n\n          mzx_world->target_board = board_id;\n          mzx_world->target_x = teleport_x;\n          mzx_world->target_y = teleport_y;\n          mzx_world->target_where = TARGET_TELEPORT;\n          done = 1;\n\n          // MegaZeux 1.x performs the board change IMMEDIATELY.\n          immediate_board_change_compat_v1(mzx_world, true);\n        }\n\n        break;\n      }\n\n      case ROBOTIC_CMD_SCROLLVIEW: // scrollview dir num\n      {\n        enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n        direction = parsedir(direction, x, y, cur_robot->walk_dir);\n\n        if(is_cardinal_dir(direction))\n        {\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          int num = parse_param(mzx_world, p2, id);\n\n          switch(direction)\n          {\n            case 1:\n              src_board->scroll_y -= num;\n              break;\n            case 2:\n              src_board->scroll_y += num;\n              break;\n            case 3:\n              src_board->scroll_x += num;\n              break;\n            case 4:\n              src_board->scroll_x -= num;\n              break;\n\n            default:\n              break;\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_INPUT: // input string\n      {\n        char input_buffer[ROBOT_MAX_TR];\n        char title_buffer[ROBOT_MAX_TR];\n        char *break_pos;\n        size_t len;\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, title_buffer);\n        title_buffer[71] = '\\0';\n\n        // Newlines break the UI. :(\n        break_pos = strchr(title_buffer, '\\n');\n        if(break_pos)\n          *break_pos = '\\0';\n\n        input_buffer[0] = 0;\n\n        dialog_fadein();\n        input_window(mzx_world, title_buffer, input_buffer, 70);\n\n        // Due to a faulty check, 2.83 through 2.91f always stay faded in here.\n        // If something is found that relies on that, make this conditional.\n        dialog_fadeout();\n\n        len = strlen(input_buffer);\n        board_set_input_string(src_board, input_buffer, len);\n        src_board->input_size = len;\n        src_board->num_input = len ? atoi(src_board->input_string) : 0;\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_INPUT: // If string \"\" \"l\"\n      {\n        const char *input_string = src_board->input_string ? src_board->input_string : \"\";\n        char cmp_buffer[ROBOT_MAX_TR];\n        tr_msg(mzx_world, cmd_ptr + 2, id, cmp_buffer);\n        if(!strcasecmp(cmp_buffer, input_string))\n        {\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          gotoed = send_self_label_tr(mzx_world, p2 + 1, id);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_INPUT_NOT: // If string not \"\" \"l\"\n      {\n        const char *input_string = src_board->input_string ? src_board->input_string : \"\";\n        char cmp_buffer[ROBOT_MAX_TR];\n        tr_msg(mzx_world, cmd_ptr + 2, id, cmp_buffer);\n        if(strcasecmp(cmp_buffer, input_string))\n        {\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          gotoed = send_self_label_tr(mzx_world, p2 + 1, id);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_INPUT_MATCHES: // If string matches \"\" \"l\"\n      {\n        // compare\n        const char *input_string = src_board->input_string ? src_board->input_string : \"\";\n        char cmp_buffer[ROBOT_MAX_TR];\n        size_t i, cmp_len;\n        char current_char;\n        char cmp_char;\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, cmp_buffer);\n        cmp_len = strlen(cmp_buffer);\n\n        for(i = 0; i < cmp_len; i++)\n        {\n          cmp_char = cmp_buffer[i];\n          current_char = input_string[i];\n\n          if((cmp_char >= 'a') && (cmp_char <= 'z'))\n            cmp_char -= 32;\n          if((current_char >= 'a') && (current_char <= 'z'))\n            current_char -= 32;\n\n          if(cmp_char == '?') continue;\n          if(cmp_char == '#')\n          {\n            if((current_char < '0') || (current_char > '9'))\n              break;\n            continue;\n          }\n          if(cmp_char == '_')\n          {\n            if((current_char < 'A') || (current_char > 'Z'))\n              break;\n            continue;\n          }\n          if(cmp_char == '*')\n          {\n            i = 1000000;\n            break;\n          }\n\n          if(cmp_char != current_char) break;\n        }\n\n        if(i >= cmp_len)\n        {\n          // Matches\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          gotoed = send_self_label_tr(mzx_world, p2 + 1, id);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_PLAYER_CHAR: // Player char\n      {\n        int new_char = parse_param(mzx_world, cmd_ptr + 1, id);\n        int i;\n\n        for(i = 0; i < 4; i++)\n        {\n          id_chars[player_char + i] = new_char;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_MOVE_ALL: // move all thing dir\n      {\n        int move_color = parse_param(mzx_world, cmd_ptr + 1, id); // Color\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        enum thing move_id = parse_param_thing(mzx_world, p2);\n        char *p3 = next_param_pos(p2);\n        int move_param = parse_param(mzx_world, p3, id);\n        char *p4 = next_param_pos(p3);\n        enum dir move_dir = parse_param_dir(mzx_world, p4);\n        int int_dir;\n        int lx, ly;\n        int fg, bg;\n        int offset;\n\n        move_dir = parsedir(move_dir, x, y, cur_robot->walk_dir);\n        split_colors(move_color, &fg, &bg);\n        match_version_fix(mzx_world, &fg, &bg, &move_param);\n\n        // TODO: fix whirlpool id AND level id(!)\n\n        if(is_cardinal_dir(move_dir))\n        {\n          int_dir = dir_to_int(move_dir);\n          // if dir is 1 or 4, search top to bottom.\n          // if dir is 2 or 3, search bottom to top.\n          if((int_dir == 0) || (int_dir == 3))\n          {\n            for(ly = 0, offset = 0; ly < board_height; ly++)\n            {\n              for(lx = 0; lx < board_width; lx++, offset++)\n              {\n                if(match_thing(level_id[offset], level_color[offset],\n                 level_param[offset], move_id, fg, bg, move_param))\n                {\n                  move(mzx_world, lx, ly, int_dir,\n                   CAN_PUSH | CAN_TRANSPORT | CAN_LAVAWALK |\n                   CAN_FIREWALK | CAN_WATERWALK);\n                }\n              }\n            }\n          }\n          else\n          {\n            offset = (board_width * board_height) - 1;\n            for(ly = board_height - 1; ly >= 0; ly--)\n            {\n              for(lx = board_width - 1; lx >= 0; lx--, offset--)\n              {\n                if(match_thing(level_id[offset], level_color[offset],\n                 level_param[offset], move_id, fg, bg, move_param))\n                {\n                  move(mzx_world, lx, ly, int_dir,\n                   CAN_PUSH | CAN_TRANSPORT | CAN_LAVAWALK |\n                   CAN_FIREWALK | CAN_WATERWALK);\n                }\n              }\n            }\n          }\n        }\n        done = 1;\n        break;\n      }\n\n      case ROBOTIC_CMD_COPY: // copy x y x y\n      {\n        char *p1 = cmd_ptr + 1;\n        char *p2 = next_param_pos(p1);\n        char *p3 = next_param_pos(p2);\n        char *p4 = next_param_pos(p3);\n        int src_x, src_y, dest_x, dest_y;\n\n        int src_type = -1, dest_type = 1;\n        int type[4] = { -1 };\n\n        type[0] = copy_block_param(mzx_world, id, p1, &src_x);\n        type[1] = copy_block_param(mzx_world, id, p2, &src_y);\n        type[2] = copy_block_param(mzx_world, id, p3, &dest_x);\n        type[3] = copy_block_param(mzx_world, id, p4, &dest_y);\n\n        if((type[0] == type[1]) && (type[0] <= 2))\n          src_type = type[0];\n        if((type[2] == type[3]) && (type[2] <= 2))\n          dest_type = type[2];\n\n        // Do the copy. This may invoke copy_block internally.\n        copy_xy_to_xy_wrapper(mzx_world, id, x, y, src_type, dest_type,\n         src_x, src_y, dest_x, dest_y);\n\n        // If this robot was deleted, exit. NOTE: all port versions prior\n        // to 2.92 had a faulty check here that would only check dest_x\n        // and dest_y and not whether or not the robot was actually\n        // overwritten. If something actually relied on this, add a\n        // version check.\n        if(id && dest_type == 0)\n        {\n          int offset = x + (y * board_width);\n          int d_id = (enum thing)level_id[offset];\n          int d_param = level_param[offset];\n\n          if((d_id != ROBOT && d_id != ROBOT_PUSHABLE) || (d_param != id))\n            return;\n\n          update_blocked = 1;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_SET_EDGE_COLOR: // set edge color\n      {\n        int new_color = parse_param(mzx_world, cmd_ptr + 1, id);\n        mzx_world->edge_color =\n         fix_color(new_color, mzx_world->edge_color);\n        break;\n      }\n\n      case ROBOTIC_CMD_BOARD: // board dir\n      {\n        enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n        direction = parsedir(direction, x, y, cur_robot->walk_dir);\n        if(is_cardinal_dir(direction))\n        {\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          char board_name_buffer[ROBOT_MAX_TR];\n          int board_number;\n\n          tr_msg(mzx_world, p2 + 1, id, board_name_buffer);\n          board_number = find_board(mzx_world, board_name_buffer);\n\n          if(board_number != NO_BOARD)\n            src_board->board_dir[dir_to_int(direction)] = board_number;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_BOARD_IS_NONE: // board dir is none\n      {\n        enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n        direction = parsedir(direction, x, y, cur_robot->walk_dir);\n        if(is_cardinal_dir(direction))\n        {\n          src_board->board_dir[dir_to_int(direction)] = NO_BOARD;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_CHAR_EDIT: // char edit\n      {\n        int char_num = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *next_param = next_param_pos(cmd_ptr + 1);\n        char char_buffer[14];\n        int i;\n\n        for(i = 0; i < 14; i++)\n        {\n          char_buffer[i] = parse_param(mzx_world, next_param, id);\n          next_param = next_param_pos(next_param);\n        }\n        // Prior to 2.90 char params are clipped\n        if(mzx_world->version < V290)\n          char_num &= 0xFF;\n\n        ec_change_char(char_num, char_buffer);\n        break;\n      }\n\n      case ROBOTIC_CMD_BECOME_PUSHABLE: // Become push\n      {\n        if(id)\n        {\n          level_id[x + (y * board_width)] = ROBOT_PUSHABLE;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_BECOME_NONPUSHABLE: // Become nonpush\n      {\n        if(id)\n        {\n          level_id[x + (y * board_width)] = ROBOT;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_BLIND: // blind\n      {\n        mzx_world->blind_dur = parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_FIREWALKER: // firewalker\n      {\n        mzx_world->firewalker_dur =\n         parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_FREEZETIME: // freezetime\n      {\n        mzx_world->freeze_time_dur =\n         parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_SLOWTIME: // slow time\n      {\n        mzx_world->slow_time_dur =\n         parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_WIND: // wind\n      {\n        mzx_world->wind_dur =\n         parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_AVALANCHE: // avalanche\n      {\n        int placement_period = 18;\n        int x, y, offset, d_flag;\n\n        for(y = 0, offset = 0; y < board_height; y++)\n        {\n          for(x = 0; x < board_width; x++, offset++)\n          {\n            d_flag = flags[(int)level_id[offset]];\n\n            if((d_flag & A_UNDER) && !(d_flag & A_ENTRANCE) &&\n             (Random(placement_period)) == 0)\n            {\n              id_place(mzx_world, x, y, BOULDER, 7, 0);\n            }\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_COPY_DIR: // copy dir dir\n      {\n        if(id)\n        {\n          enum dir src_dir = parse_param_dir(mzx_world, cmd_ptr + 1);\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          enum dir dest_dir = parse_param_dir(mzx_world, p2);\n\n          src_dir = parsedir(src_dir, x, y, cur_robot->walk_dir);\n          dest_dir = parsedir(dest_dir, x, y, cur_robot->walk_dir);\n\n          if(is_cardinal_dir(src_dir) && is_cardinal_dir(dest_dir))\n          {\n            int src_x = x;\n            int src_y = y;\n            int dest_x = x;\n            int dest_y = y;\n\n            if(!move_dir(src_board, &src_x, &src_y, src_dir) &&\n             !move_dir(src_board, &dest_x, &dest_y, dest_dir))\n            {\n              copy_xy_to_xy(mzx_world, src_x, src_y, dest_x, dest_y);\n\n              // If this robot was deleted, exit. NOTE: all port versions prior\n              // to 2.92 had a faulty check here that would only check dest_x\n              // and dest_y and not whether or not the robot was actually\n              // overwritten. If something actually relied on this, add a\n              // version check. That said, this command overwriting the current\n              // robot does not seem to be possible anyway.\n              {\n                int offset = x + (y * board_width);\n                int d_id = (enum thing)level_id[offset];\n                int d_param = level_param[offset];\n\n                if((d_id != ROBOT && d_id != ROBOT_PUSHABLE) || (d_param != id))\n                  return;\n              }\n              update_blocked = 1;\n            }\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_BECOME_LAVAWALKER: // become lavawalker\n      {\n        if(id)\n        {\n          cur_robot->can_lavawalk = 1;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_BECOME_NONLAVAWALKER: // become non lavawalker\n      {\n        if(id)\n        {\n          cur_robot->can_lavawalk = 0;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_CHANGE: // change color thing param color thing param\n      {\n        int check_color = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        enum thing check_id = parse_param_thing(mzx_world, p2);\n        char *p3 = next_param_pos(p2);\n        int check_param = parse_param(mzx_world, p3, id);\n        char *p4 = next_param_pos(p3);\n        int put_color = parse_param(mzx_world, p4, id);\n        char *p5 = next_param_pos(p4);\n        enum thing put_id = parse_param_thing(mzx_world, p5);\n        char *p6 = next_param_pos(p5);\n        int put_param = parse_param(mzx_world, p6, id);\n        int check_fg, check_bg;\n        enum thing d_id;\n        int offset, d_param, d_color;\n        boolean weird_color = false;\n\n        split_colors(check_color, &check_fg, &check_bg);\n        match_version_fix(mzx_world, &check_fg, &check_bg, &check_param);\n        replacement_version_fix(mzx_world, &put_color, &put_param, &weird_color);\n\n        // Make sure the change isn't incompatable\n        // Only robots (pushable or otherwise) can be changed to\n        // robots. The same goes for scrolls/signs and sensors.\n        // Players cannot be changed at all\n\n        if(((put_id != SENSOR) || (check_id == SENSOR)) &&\n         (((put_id != ROBOT_PUSHABLE) || ((check_id == ROBOT)\n         || (check_id == ROBOT_PUSHABLE))) &&\n          ((put_id != ROBOT) || ((check_id == ROBOT_PUSHABLE) ||\n          (check_id == ROBOT))) &&\n          ((put_id != SIGN) || (check_id == SCROLL))) &&\n          ((put_id != SCROLL) || (check_id == SIGN)) &&\n          ((put_id != PLAYER) && (check_id != PLAYER)))\n        {\n          // Clear out params for param-less objects,\n          // lava, fire, ice, energizer, rotates, or life\n          if((put_id == ICE) || (put_id == LAVA) ||\n           (put_id == ENERGIZER) || (put_id == CW_ROTATE) ||\n           (put_id == CCW_ROTATE) || (put_id == FIRE) ||\n           (put_id == LIFE))\n          {\n            put_param = 0;\n          }\n\n          // Ignore upper stages for explosions\n          if(put_id == EXPLOSION)\n            put_param &= 0xF3;\n\n          // Open door becomes door\n          if(check_id == OPEN_DOOR)\n            check_id = DOOR;\n\n          // Whirlpool becomes base whirlpool\n          if(is_whirlpool(check_id))\n            check_id = WHIRLPOOL_1;\n\n          // Cannot change param on these\n          if(put_id > SENSOR)\n            put_param = 256;\n\n          for(offset = 0; offset < (board_width * board_height); offset++)\n          {\n            d_id = (enum thing)level_id[offset];\n            d_param = level_param[offset];\n            d_color = level_color[offset];\n\n            // open door becomes door\n            if(d_id == OPEN_DOOR)\n              d_id = DOOR;\n\n            // Whirpool becomes base one\n            if(is_whirlpool(d_id))\n              d_id = WHIRLPOOL_1;\n\n            if(match_thing(d_id, d_color, d_param,\n             check_id, check_fg, check_bg, check_param))\n            {\n              // Change the color and the ID\n              level_color[offset] = fix_color(put_color, d_color);\n              level_id[offset] = put_id;\n\n              // Regrettably supporting extreme edge case DOS-era nonsense\n              if(weird_color)\n                level_color[offset] = (d_color & 0xf0) | put_color;\n\n              if(((d_id == ROBOT_PUSHABLE) || (d_id == ROBOT)) &&\n               (put_id != ROBOT) && (put_id != ROBOT_PUSHABLE))\n              {\n                // delete robot if not changing to a robot\n                clear_robot_id(src_board, d_param);\n              }\n              else\n\n              if(((d_id == SIGN) || (d_id == SCROLL)) &&\n               (put_id != SIGN) && (put_id != SCROLL))\n              {\n                // delete scroll if not changing to a scroll/sign\n                clear_scroll_id(src_board, d_param);\n              }\n              else\n\n              if((d_id == SENSOR) && (put_id != SENSOR))\n              {\n                // delete sensor if not changing to a sensor\n                clear_sensor_id(src_board, d_param);\n              }\n\n              if(put_id == 0)\n              {\n                offs_remove_id(mzx_world, offset);\n                // If this LEAVES a space, use given color\n                if(level_id[offset] == 0)\n                  level_color[offset] = fix_color(put_color, d_color);\n              }\n              else\n              {\n                if(put_param != 256)\n                  level_param[offset] = put_param;\n              }\n            }\n          }\n          // If we got deleted, exit\n\n          if(id)\n          {\n            d_id = (enum thing)level_id[x + (y * board_width)];\n\n            if(!is_robot(d_id))\n              return;\n\n            update_blocked = 1;\n          }\n        }\n\n        break;\n      }\n\n      case ROBOTIC_CMD_PLAYERCOLOR: // player color\n      {\n        int new_color = parse_param(mzx_world, cmd_ptr + 1, id);\n        if(get_counter(mzx_world, \"INVINCO\", 0))\n        {\n          mzx_world->saved_pl_color =\n           fix_color(new_color, mzx_world->saved_pl_color);\n        }\n        else\n        {\n          id_chars[player_color] = fix_color(new_color,\n           id_chars[player_color]);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_BULLETCOLOR: // bullet color\n      {\n        int new_color = parse_param(mzx_world, cmd_ptr + 1, id);\n        int i;\n\n        for(i = 0; i < 3; i++)\n        {\n          bullet_color[i] =\n           fix_color(new_color, bullet_color[i]);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_MISSILECOLOR: // missile color\n      {\n        missile_color =\n         fix_color(parse_param(mzx_world, cmd_ptr + 1, id), missile_color);\n        break;\n      }\n\n      case ROBOTIC_CMD_MESSAGE_ROW: // message row\n      {\n        int b_mesg_row = parse_param(mzx_world, cmd_ptr + 1, id);\n        if(b_mesg_row > 24)\n          b_mesg_row = 24;\n\n        if(b_mesg_row < 0)\n          b_mesg_row = 0;\n\n        src_board->b_mesg_row = b_mesg_row;\n        break;\n      }\n\n      case ROBOTIC_CMD_REL_SELF: // rel self\n      {\n        if(id)\n        {\n          mzx_world->first_prefix = REL_TO_SELF;\n          mzx_world->mid_prefix = REL_TO_SELF;\n          mzx_world->last_prefix = REL_TO_SELF;\n          lines_run--;\n          goto next_cmd_prefix;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_REL_PLAYER: // rel player\n      {\n        mzx_world->first_prefix = REL_TO_PLAYER;\n        mzx_world->mid_prefix = REL_TO_PLAYER;\n        mzx_world->last_prefix = REL_TO_PLAYER;\n        lines_run--;\n        goto next_cmd_prefix;\n      }\n\n      case ROBOTIC_CMD_REL_COUNTERS: // rel counters\n      {\n        mzx_world->first_prefix = REL_TO_XPOS_YPOS;\n        mzx_world->mid_prefix = REL_TO_XPOS_YPOS;\n        mzx_world->last_prefix = REL_TO_XPOS_YPOS;\n\n        // The REL COUNTERS commands are broken in 2.00+ - the usage of\n        // XPOS/YPOS and FIRSTXPOS/FIRSTYPOS/LASTXPOS/LASTYPOS are reversed.\n        // This seems to be intentional but is completely backwards from what\n        // the 2.00 help file claimed (and what would be sensible).\n        if(mzx_world->version >= V200)\n        {\n          mzx_world->first_prefix = REL_TO_XPOS_YPOS_FIRST_OR_LAST;\n          mzx_world->last_prefix = REL_TO_XPOS_YPOS_FIRST_OR_LAST;\n        }\n        lines_run--;\n        goto next_cmd_prefix;\n      }\n\n      case ROBOTIC_CMD_SET_ID_CHAR: // set id char # to 'c'\n      {\n        int id_char = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int id_value = parse_param(mzx_world, p2, id);\n\n        set_id_char_by_legacy_index(id_char, id_value);\n        break;\n      }\n\n      case ROBOTIC_CMD_JUMP_MOD_ORDER: // jump mod order #\n      {\n        audio_set_module_order(parse_param(mzx_world, cmd_ptr + 1, id));\n        break;\n      }\n\n      case ROBOTIC_CMD_ASK: // ask yes/no\n      {\n        char question_buffer[ROBOT_MAX_TR];\n        char *break_pos;\n        int send_status;\n\n        dialog_fadein();\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, question_buffer);\n\n        // Kick da line break in da pants!\n        if((break_pos = strchr(question_buffer, '\\n')))\n          *break_pos = '\\0';\n\n        if(!ask_yes_no(mzx_world, question_buffer))\n          send_status = send_robot_id(mzx_world, id, \"YES\", 1);\n        else\n          send_status = send_robot_id(mzx_world, id, \"NO\", 1);\n\n        if(!send_status)\n          gotoed = 1;\n\n        // Due to a faulty check, 2.83 through 2.91f always stay faded in here.\n        // If something is found that relies on that, make this conditional.\n        dialog_fadeout();\n        break;\n      }\n\n      case ROBOTIC_CMD_FILLHEALTH: // fill health\n      {\n        set_counter(mzx_world, \"HEALTH\", mzx_world->health_limit, 0);\n        break;\n      }\n\n      case ROBOTIC_CMD_THICK_ARROW: // thick arrow dir char\n      {\n        enum dir dir = parse_param_dir(mzx_world, cmd_ptr + 1);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        dir = parsedir(dir, x, y, cur_robot->walk_dir);\n\n        if(is_cardinal_dir(dir))\n        {\n          id_chars[249 + dir] = parse_param(mzx_world, p2, id);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_THIN_ARROW: // thin arrow dir char\n      {\n        enum dir dir = parse_param_dir(mzx_world, cmd_ptr + 1);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        dir = parsedir(dir, x, y, cur_robot->walk_dir);\n\n        if(is_cardinal_dir(dir))\n        {\n          id_chars[253 + dir] =\n           parse_param(mzx_world, p2, id);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_SET_MAX_HEALTH: // set max health\n      {\n        mzx_world->health_limit = parse_param(mzx_world, cmd_ptr + 1, id);\n        inc_counter(mzx_world, \"HEALTH\", 0, 0);\n        break;\n      }\n\n      case ROBOTIC_CMD_SAVE_PLAYER_POSITION: // save pos\n      {\n        save_player_position(mzx_world, 0);\n        break;\n      }\n\n      case ROBOTIC_CMD_RESTORE_PLAYER_POSITION: // restore\n      {\n        restore_player_position(mzx_world, 0);\n        done = 1;\n        break;\n      }\n\n      case ROBOTIC_CMD_EXCHANGE_PLAYER_POSITION: // exchange\n      {\n        restore_player_position(mzx_world, 0);\n        save_player_position(mzx_world, 0);\n        done = 1;\n        break;\n      }\n\n      case ROBOTIC_CMD_MESSAGE_COLUMN: // mesg col\n      {\n        int b_mesg_col = parse_param(mzx_world, cmd_ptr + 1, id);\n\n        if(b_mesg_col > 79)\n          b_mesg_col = 79;\n\n        if(b_mesg_col < 0)\n          b_mesg_col = 0;\n\n        src_board->b_mesg_col = b_mesg_col;\n        break;\n      }\n\n      case ROBOTIC_CMD_CENTER_MESSAGE: // center mesg\n      {\n        src_board->b_mesg_col = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_CLEAR_MESSAGE: // clear mesg\n      {\n        src_board->b_mesg_timer = 0;\n        clear_intro_mesg();\n        break;\n      }\n\n      case ROBOTIC_CMD_RESETVIEW: // resetview\n      {\n        src_board->scroll_x = 0;\n        src_board->scroll_y = 0;\n        break;\n      }\n\n      case ROBOTIC_CMD_MOD_SAM: // modsam freq num\n      {\n        // Lock this to DOS worlds because it only ever had a use in DOS vers.\n        // Also, no port worlds ever used it because it never worked.\n\n        if((mzx_world->version < VERSION_PORT) && audio_get_music_on())\n        {\n          int frequency = parse_param(mzx_world, cmd_ptr + 1, id);\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          int sam_num = parse_param(mzx_world, p2, id);\n\n          audio_spot_sample(frequency, sam_num);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_SCROLLBASE: // scrollbase\n      {\n        mzx_world->scroll_base_color =\n         parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_SCROLLCORNER: // scrollcorner\n      {\n        mzx_world->scroll_corner_color =\n         parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_SCROLLTITLE: // scrolltitle\n      {\n        mzx_world->scroll_title_color =\n         parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_SCROLLPOINTER: // scrollpointer\n      {\n        mzx_world->scroll_pointer_color =\n         parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_SCROLLARROW: // scrollarrow\n      {\n        mzx_world->scroll_arrow_color =\n         parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_VIEWPORT: // viewport x y\n      {\n        int viewport_x = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int viewport_y = parse_param(mzx_world, p2, id);\n        unsigned int viewport_width = src_board->viewport_width;\n        unsigned int viewport_height = src_board->viewport_height;\n\n        if(viewport_x < 0 || (viewport_x + viewport_width) > 80)\n          viewport_x = 80 - viewport_width;\n        if(viewport_y < 0 || (viewport_y + viewport_height) > 25)\n          viewport_y = 25 - viewport_height;\n\n        src_board->viewport_x = viewport_x;\n        src_board->viewport_y = viewport_y;\n\n        break;\n      }\n\n      case ROBOTIC_CMD_VIEWPORT_WIDTH: // viewport width height\n      {\n        int viewport_width = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int viewport_height = parse_param(mzx_world, p2, id);\n        int viewport_x = src_board->viewport_x;\n        int viewport_y = src_board->viewport_y;\n\n        if((viewport_width < 0) || (viewport_width > 80))\n          viewport_width = 80;\n\n        if((viewport_height < 0) || (viewport_height > 25))\n          viewport_height = 25;\n\n        if(viewport_width == 0)\n          viewport_width = 1;\n\n        if(viewport_height == 0)\n          viewport_height = 1;\n\n        if(viewport_width > src_board->board_width)\n          viewport_width = src_board->board_width;\n\n        if(viewport_height > src_board->board_height)\n          viewport_height = src_board->board_height;\n\n        if((viewport_x + viewport_width) > 80)\n          src_board->viewport_x = 80 - viewport_width;\n\n        if((viewport_y + viewport_height) > 25)\n          src_board->viewport_y = 25 - viewport_height;\n\n        src_board->viewport_width = viewport_width;\n        src_board->viewport_height = viewport_height;\n\n        break;\n      }\n\n      case ROBOTIC_CMD_SAVE_PLAYER_POSITION_N: // save pos #\n      {\n        int pos = parse_param(mzx_world, cmd_ptr + 1, id) - 1;\n        if((pos < 0) || (pos > 7))\n          pos = 0;\n        save_player_position(mzx_world, pos);\n        break;\n      }\n\n      case ROBOTIC_CMD_RESTORE_PLAYER_POSITION_N: // restore pos #\n      {\n        int pos = parse_param(mzx_world, cmd_ptr + 1, id) - 1;\n        if((pos < 0) || (pos > 7))\n          pos = 0;\n        restore_player_position(mzx_world, pos);\n        done = 1;\n        break;\n      }\n\n      case ROBOTIC_CMD_EXCHANGE_PLAYER_POSITION_N: // exchange pos #\n      {\n        int pos = parse_param(mzx_world, cmd_ptr + 1, id) - 1;\n        if((pos < 0) || (pos > 7))\n          pos = 0;\n        restore_player_position(mzx_world, pos);\n        save_player_position(mzx_world, pos);\n        done = 1;\n        break;\n      }\n\n      // restore pos # duplicate self\n      case ROBOTIC_CMD_RESTORE_PLAYER_POSITION_N_DUPLICATE_SELF:\n      {\n        int pos = parse_param(mzx_world, cmd_ptr + 1, id) - 1;\n        int duplicate_x = mzx_world->player_x;\n        int duplicate_y = mzx_world->player_y;\n        int duplicate_color, duplicate_id, dest_id;\n        int offset;\n\n        if((pos < 0) || (pos > 7))\n          pos = 0;\n\n        restore_player_position(mzx_world, pos);\n        // Duplicate the robot to where the player was\n\n        if(id)\n        {\n          offset = x + (y * board_width);\n          duplicate_color = level_color[offset];\n          duplicate_id = (enum thing)level_id[offset];\n        }\n        else\n        {\n          duplicate_color = 7;\n          duplicate_id = ROBOT;\n        }\n\n        offset = duplicate_x + (duplicate_y * board_width);\n        dest_id =\n         duplicate_robot(mzx_world, src_board, cur_robot,\n          duplicate_x, duplicate_y, 0);\n\n        if(dest_id != -1)\n        {\n          level_id[offset] = duplicate_id;\n          level_color[offset] = duplicate_color;\n          level_param[offset] = dest_id;\n\n          // This robot doesn't actually move. Who knows why this is here, but\n          // removing it might be a compatibility problem with xpos/ypos...\n          x = duplicate_x;\n          y = duplicate_y;\n\n          replace_player(mzx_world);\n\n          done = 1;\n        }\n        break;\n      }\n\n      // exchange pos # duplicate self\n      case ROBOTIC_CMD_EXCHANGE_PLAYER_POSITION_N_DUPLICATE_SELF:\n      {\n        int pos = parse_param(mzx_world, cmd_ptr + 1, id) - 1;\n        int duplicate_x = mzx_world->player_x;\n        int duplicate_y = mzx_world->player_y;\n        int duplicate_color, duplicate_id, dest_id;\n        int offset;\n\n        if((pos < 0) || (pos > 7))\n          pos = 0;\n\n        restore_player_position(mzx_world, pos);\n        save_player_position(mzx_world, pos);\n        // Duplicate the robot to where the player was\n\n        if(id)\n        {\n          offset = x + (y * board_width);\n          duplicate_color = level_color[offset];\n          duplicate_id = (enum thing)level_id[offset];\n        }\n        else\n        {\n          duplicate_color = 7;\n          duplicate_id = ROBOT;\n        }\n\n        offset = duplicate_x + (duplicate_y * board_width);\n        dest_id =\n         duplicate_robot(mzx_world, src_board, cur_robot,\n          duplicate_x, duplicate_y, 0);\n\n        if(dest_id != -1)\n        {\n          level_id[offset] = duplicate_id;\n          level_color[offset] = duplicate_color;\n          level_param[offset] = dest_id;\n\n          // This robot doesn't actually move. Who knows why this is here, but\n          // removing it might be a compatibility problem with xpos/ypos...\n          x = duplicate_x;\n          y = duplicate_y;\n\n          replace_player(mzx_world);\n\n          done = 1;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_PLAYER_BULLETN: // Pl bulletn\n      case ROBOTIC_CMD_PLAYER_BULLETS: // Pl bullets\n      case ROBOTIC_CMD_PLAYER_BULLETE: // Pl bullete\n      case ROBOTIC_CMD_PLAYER_BULLETW: // Pl bulletw\n      case ROBOTIC_CMD_NEUTRAL_BULLETN: // Nu bulletn\n      case ROBOTIC_CMD_NEUTRAL_BULLETS: // Nu bullets\n      case ROBOTIC_CMD_NEUTRAL_BULLETE: // Nu bullete\n      case ROBOTIC_CMD_NEUTRAL_BULLETW: // Nu bulletw\n      case ROBOTIC_CMD_ENEMY_BULLETN: // En bulletn\n      case ROBOTIC_CMD_ENEMY_BULLETS: // En bullets\n      case ROBOTIC_CMD_ENEMY_BULLETE: // En bullete\n      case ROBOTIC_CMD_ENEMY_BULLETW: // En bulletw\n      {\n        id_chars[bullet_char + (cmd - ROBOTIC_CMD_PLAYER_BULLETN)] =\n         parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_PLAYER_BULLET_COLOR: // Pl bcolor\n      case ROBOTIC_CMD_NEUTRAL_BULLET_COLOR: // Nu bcolor\n      case ROBOTIC_CMD_ENEMY_BULLET_COLOR: // En bcolor\n      {\n        bullet_color[cmd - ROBOTIC_CMD_PLAYER_BULLET_COLOR] =\n         parse_param(mzx_world, cmd_ptr + 1, id);\n        break;\n      }\n\n      case ROBOTIC_CMD_REL_SELF_FIRST: // Rel self first\n      {\n        if(id)\n        {\n          mzx_world->first_prefix = REL_TO_SELF_FIRST_OR_LAST;\n          lines_run--;\n          goto next_cmd_prefix;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_REL_SELF_LAST: // Rel self last\n      {\n        if(id)\n        {\n          mzx_world->last_prefix = REL_TO_SELF_FIRST_OR_LAST;\n          lines_run--;\n          goto next_cmd_prefix;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_REL_PLAYER_FIRST: // Rel player first\n      {\n        mzx_world->first_prefix = REL_TO_PLAYER_FIRST_OR_LAST;\n        lines_run--;\n        goto next_cmd_prefix;\n      }\n\n      case ROBOTIC_CMD_REL_PLAYER_LAST: // Rel player last\n      {\n        mzx_world->last_prefix = REL_TO_PLAYER_FIRST_OR_LAST;\n        lines_run--;\n        goto next_cmd_prefix;\n      }\n\n      case ROBOTIC_CMD_REL_COUNTERS_FIRST: // Rel counters first\n      {\n        // BUG: should be REL_TO_XPOS_YPOS_FIRST_OR_LAST (see REL COUNTERS).\n        mzx_world->first_prefix = REL_TO_XPOS_YPOS;\n        lines_run--;\n        goto next_cmd_prefix;\n      }\n\n      case ROBOTIC_CMD_REL_COUNTERS_LAST: // Rel counters last\n      {\n        // BUG: should be REL_TO_XPOS_YPOS_FIRST_OR_LAST (see REL COUNTERS).\n        mzx_world->last_prefix = REL_TO_XPOS_YPOS;\n        lines_run--;\n        goto next_cmd_prefix;\n      }\n\n      case ROBOTIC_CMD_MOD_FADE_OUT: // Mod fade out\n      {\n        src_board->volume_inc = -8;\n        src_board->volume_target = 0;\n        break;\n      }\n\n      case ROBOTIC_CMD_MOD_FADE_IN: // Mod fade in\n      {\n        char name_buffer[ROBOT_MAX_TR];\n        src_board->volume_inc = 8;\n        src_board->volume_target = 255;\n        tr_msg(mzx_world, cmd_ptr + 2, id, name_buffer);\n\n        magic_load_mod(mzx_world, name_buffer);\n        src_board->volume = 0;\n        audio_set_module_volume(0);\n        break;\n      }\n\n      case ROBOTIC_CMD_COPY_BLOCK: // Copy block sx sy width height dx dy\n      case ROBOTIC_CMD_COPY_OVERLAY_BLOCK: // Copy overlay block etc\n      {\n        char dest_name_buffer[ROBOT_MAX_TR];\n        char *p1 = cmd_ptr + 1;\n        char *p2 = next_param_pos(p1);\n        char *p3 = next_param_pos(p2);\n        char *p4 = next_param_pos(p3);\n        char *p5 = next_param_pos(p4);\n        char *p6 = next_param_pos(p5);\n        // These will always be set, but the compiler doesn't think so.\n        int src_x = 0;\n        int src_y = 0;\n        int dest_x = 0;\n        int dest_y = 0;\n        int width = parse_param(mzx_world, p3, id);\n        int height = parse_param(mzx_world, p4, id);\n\n        // 0 is board, 1 is overlay, 2 is vlayer (dest: 3 is string, 4 is mzm)\n        int type[4] = { 0 };\n        int src_type = -1, dest_type = -1;\n        type[0] = copy_block_param(mzx_world, id, p1, &src_x);\n        type[1] = copy_block_param(mzx_world, id, p2, &src_y);\n        type[2] = copy_block_param_special(mzx_world, id, p5, &dest_x,\n         dest_name_buffer);\n        type[3] = copy_block_param(mzx_world, id, p6, &dest_y);\n\n        if((type[0] == type[1]) && (type[0] <= 2))\n          src_type = type[0];\n\n        if((type[2] == type[3]) || ((type[2] > 2) && (type[3] == 0)))\n          dest_type = type[2];\n\n        // something is wrong with the params, abort\n        if((src_type < 0) || (dest_type < 0))\n          break;\n\n        if(cmd == ROBOTIC_CMD_COPY_OVERLAY_BLOCK)\n        {\n          if(src_type < 2)\n            src_type ^= 1;\n          if(dest_type < 2)\n            dest_type ^= 1;\n        }\n\n        copy_block(mzx_world, id, x, y, src_type, dest_type, src_x, src_y,\n         width, height, dest_x, dest_y, p5, dest_y, dest_name_buffer);\n\n        // If we got deleted, exit\n        if(id)\n        {\n          int offset = x + (y * board_width);\n          int d_id = (enum thing)level_id[offset];\n          int d_param = level_param[offset];\n\n          if(!is_robot(d_id) || (d_param != id))\n            return;\n\n          update_blocked = 1;\n        }\n\n        break;\n      }\n\n      case ROBOTIC_CMD_CLIP_INPUT: // Clip input\n      {\n        const char *input_string = src_board->input_string ? src_board->input_string : \"\";\n        size_t input_size = src_board->input_size;\n        size_t i = 0;\n\n        // Chop up to and through first section of whitespace.\n        // First, until non space or end\n        while(*input_string && i < input_size)\n        {\n          if(*input_string == ' ')\n            break;\n          input_string++;\n          i++;\n        }\n\n        while(*input_string == ' ' && i < input_size)\n          input_string++, i++;\n\n        i = strlen(input_string);\n        board_set_input_string(src_board, input_string, i);\n        src_board->input_size = i;\n        src_board->num_input = i ? atoi(src_board->input_string) : 0;\n        break;\n      }\n\n      case ROBOTIC_CMD_PUSH: // Push dir\n      {\n        if(id)\n        {\n          enum dir push_dir = parse_param_dir(mzx_world, cmd_ptr + 1);\n          push_dir = parsedir(push_dir, x, y, cur_robot->walk_dir);\n\n          if(is_cardinal_dir(push_dir))\n          {\n            int push_x = x;\n            int push_y = y;\n            int int_dir = dir_to_int(push_dir);\n\n            if(!move_dir(src_board, &push_x, &push_y, push_dir))\n            {\n              int offset = push_x + (push_y * board_width);\n              int d_id = (enum thing)level_id[offset];\n              int d_flag = flags[d_id];\n\n              if((d_id == ROBOT_PUSHABLE) || (d_id == PLAYER) ||\n               ((int_dir < 2) && (d_flag & A_PUSHNS)) ||\n               ((int_dir >= 2) && (d_flag & A_PUSHEW)) ||\n               (d_id == SENSOR))\n              {\n                push(mzx_world, x, y, int_dir, 0);\n                update_blocked = 1;\n              }\n            }\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_SCROLL_CHAR: // Scroll char dir\n      {\n        int char_num = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        enum dir scroll_dir = parse_param_dir(mzx_world, p2);\n        scroll_dir = parsedir(scroll_dir, x, y, cur_robot->walk_dir);\n\n        if(is_cardinal_dir(scroll_dir))\n        {\n          char char_buffer[14];\n          int i;\n\n          // Prior to 2.90 char params are clipped\n          if(mzx_world->version < V290) char_num &= 0xFF;\n\n          ec_read_char(char_num, char_buffer);\n\n          switch(scroll_dir)\n          {\n            case 1:\n            {\n              char wrap_row = char_buffer[0];\n\n              for(i = 0; i < 13; i++)\n              {\n                char_buffer[i] = char_buffer[i + 1];\n              }\n              char_buffer[13] = wrap_row;\n\n              break;\n            }\n\n            case 2:\n            {\n              char wrap_row = char_buffer[13];\n\n              for(i = 13; i > 0; i--)\n              {\n                char_buffer[i] = char_buffer[i - 1];\n              }\n              char_buffer[0] = wrap_row;\n\n              break;\n            }\n\n            case 3:\n            {\n              char wrap_bit;\n\n              for(i = 0; i < 14; i++)\n              {\n                wrap_bit = char_buffer[i] & 1;\n                char_buffer[i] >>= 1;\n                if(wrap_bit)\n                  char_buffer[i] |= 0x80;\n              }\n              break;\n            }\n\n            case 4:\n            {\n              char wrap_bit;\n\n              for(i = 0; i < 14; i++)\n              {\n                wrap_bit = char_buffer[i] & 0x80;\n                char_buffer[i] <<= 1;\n                if(wrap_bit)\n                  char_buffer[i] |= 1;\n              }\n            }\n\n            default:\n              break;\n          }\n\n          ec_change_char(char_num, char_buffer);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_FLIP_CHAR: // Flip char dir\n      {\n        int char_num = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        enum dir flip_dir = parse_param_dir(mzx_world, p2);\n        char char_buffer[14];\n        char current_row;\n        int i;\n\n        flip_dir = parsedir(flip_dir, x, y, cur_robot->walk_dir);\n\n        if(is_cardinal_dir(flip_dir))\n        {\n          // Prior to 2.90 char params are clipped\n          if(mzx_world->version < V290) char_num &= 0xFF;\n\n          ec_read_char(char_num, char_buffer);\n\n          switch(flip_dir)\n          {\n            case 1:\n            case 2:\n            {\n              for(i = 0; i < 7; i++)\n              {\n                current_row = char_buffer[i];\n                char_buffer[i] = char_buffer[13 - i];\n                char_buffer[13 - i] = current_row;\n              }\n              break;\n            }\n\n            case 3:\n            case 4:\n            {\n              for(i = 0; i < 14; i++)\n              {\n                current_row = char_buffer[i];\n                current_row = (current_row << 4) | (current_row >> 4);\n                current_row = ((current_row & 0xCC) >> 2) |\n                 ((current_row & 0x33) << 2);\n                current_row = ((current_row & 0xAA) >> 1) |\n                 ((current_row & 0x55) << 1);\n\n                char_buffer[i] = current_row;\n              }\n              break;\n            }\n\n            default:\n              break;\n          }\n\n          ec_change_char(char_num, char_buffer);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_COPY_CHAR: // copy char char\n      {\n        int src_char = parse_param(mzx_world, cmd_ptr + 1, id);\n        char char_buffer[14];\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int dest_char = parse_param(mzx_world, p2, id);\n\n        // Prior to 2.90 char params are clipped\n        if(mzx_world->version < V290)\n        {\n          src_char &= 0xFF;\n          dest_char &= 0xFF;\n        }\n\n        ec_read_char(src_char, char_buffer);\n        ec_change_char(dest_char, char_buffer);\n        break;\n      }\n\n      case ROBOTIC_CMD_CHANGE_SFX: // change sfx\n      {\n        int fx_num = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n\n        sfx_set_string(&mzx_world->custom_sfx, fx_num, p2, strlen(p2));\n        break;\n      }\n\n      case ROBOTIC_CMD_COLOR_INTENSITY_ALL: // color intensity #%\n      {\n        int intensity = parse_param(mzx_world, cmd_ptr + 1, id);\n        if(intensity < 0)\n          intensity = 0;\n\n        set_palette_intensity(intensity);\n        break;\n      }\n\n      case ROBOTIC_CMD_COLOR_INTENSITY_N: // color intensity # #%\n      {\n        int color = parse_param(mzx_world, cmd_ptr + 1, id) & 0xFF;\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int intensity = parse_param(mzx_world, p2, id);\n        if(intensity < 0)\n          intensity = 0;\n\n        set_color_intensity(color, intensity);\n        break;\n      }\n\n      case ROBOTIC_CMD_COLOR_FADE_OUT: // color fade out\n      {\n        vquick_fadeout();\n        break;\n      }\n\n      case ROBOTIC_CMD_COLOR_FADE_IN: // color fade in\n      {\n        vquick_fadein();\n        break;\n      }\n\n      case ROBOTIC_CMD_SET_COLOR: // set color # r g b\n      {\n        // Now you can set all 256 colors this way (for SMZX)\n        int pal_number = parse_param(mzx_world, cmd_ptr + 1, id) & 0xFF;\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int r = parse_param(mzx_world, p2, id);\n        char *p3 = next_param_pos(p2);\n        int g = parse_param(mzx_world, p3, id);\n        char *p4 = next_param_pos(p3);\n        int b = parse_param(mzx_world, p4, id);\n\n        // It's pretty sad that this is necessary, but some people don't\n        // know how to use set color apparently (see SM4MZX for details)\n        r = CLAMP(r, 0, 63);\n        g = CLAMP(g, 0, 63);\n        b = CLAMP(b, 0, 63);\n\n        set_rgb(pal_number, r, g, b);\n        break;\n      }\n\n      case ROBOTIC_CMD_LOAD_CHAR_SET: // Load char set \"\"\n      {\n        char charset_name[ROBOT_MAX_TR];\n        char *translated_name = cmalloc(MAX_PATH);\n        char *src_name;\n        int pos;\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, charset_name);\n\n        // This will load a charset to a different position - Exo\n        if(charset_name[0] == '+')\n        {\n          char tempc = charset_name[3];\n\n          charset_name[3] = 0;\n          pos = (int)strtol(charset_name + 1, &src_name, 16);\n          charset_name[3] = tempc;\n        }\n        else\n\n        if(charset_name[0] == '@')\n        {\n          char tempc;\n          int maxlen;\n          // The offset string length was increased in 2.90 to\n          // allow for accessing extended char sets.\n          if(mzx_world->version < V290)\n            maxlen = 3;\n          else\n            maxlen = 4;\n          tempc = charset_name[maxlen+1];\n          charset_name[maxlen+1] = 0;\n          pos = (int)strtol(charset_name + 1, &src_name, 10);\n          charset_name[maxlen+1] = tempc;\n        }\n        else\n        {\n          src_name = charset_name;\n          pos = 0;\n        }\n\n        // Load from string (2.90+)\n        if(mzx_world->version >= V290 && is_string(src_name))\n        {\n          struct string src;\n\n          if(get_string(mzx_world, src_name, &src, id))\n            ec_mem_load_set_var(src.value, src.length, pos, mzx_world->version);\n        }\n        else\n\n        // Load from file\n        if(!fsafetranslate(src_name, translated_name, MAX_PATH))\n        {\n          ec_load_set_var(translated_name, pos, mzx_world->version);\n        }\n\n        free(translated_name);\n        break;\n      }\n\n      case ROBOTIC_CMD_MULTIPLY: // multiply counter #\n      {\n        char *dest_string = cmd_ptr + 2;\n        char *src_string = cmd_ptr + next_param(cmd_ptr, 1);\n        char dest_buffer[ROBOT_MAX_TR];\n        int value = parse_param(mzx_world, src_string + 1, id);\n        tr_msg(mzx_world, dest_string, id, dest_buffer);\n\n        mul_counter(mzx_world, dest_buffer, value, id);\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_DIVIDE: // divide counter #\n      {\n        char *dest_string = cmd_ptr + 2;\n        char *src_string = cmd_ptr + next_param(cmd_ptr, 1);\n        char dest_buffer[ROBOT_MAX_TR];\n        int value = parse_param(mzx_world, src_string + 1, id);\n        tr_msg(mzx_world, dest_string, id, dest_buffer);\n\n        div_counter(mzx_world, dest_buffer, value, id);\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_MODULO: // mod counter #\n      {\n        char *dest_string = cmd_ptr + 2;\n        char *src_string = cmd_ptr + next_param(cmd_ptr, 1);\n        char dest_buffer[ROBOT_MAX_TR];\n        int value = parse_param(mzx_world, src_string + 1, id);\n        tr_msg(mzx_world, dest_string, id, dest_buffer);\n\n        mod_counter(mzx_world, dest_buffer, value, id);\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_PLAYER_CHAR_DIR: // Player char dir 'c'\n      {\n        enum dir direction = parse_param_dir(mzx_world, cmd_ptr + 1);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int new_char = parse_param(mzx_world, p2, id);\n        direction = parsedir(direction, x, y, cur_robot->walk_dir);\n\n        if(is_cardinal_dir(direction))\n        {\n          id_chars[player_char + dir_to_int(direction)] = new_char;\n        }\n        break;\n      }\n\n      // Can load full 256 color ones too now. Will use file size to\n      // determine how many to load.\n      case ROBOTIC_CMD_LOAD_PALETTE: // Load palette\n      {\n        char name_buffer[ROBOT_MAX_TR];\n        char *translated_name = cmalloc(MAX_PATH);\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, name_buffer);\n\n        // Load palette from string (2.90+)\n        if(mzx_world->version >= V290 && is_string(name_buffer))\n        {\n          struct string src;\n\n          if(get_string(mzx_world, name_buffer, &src, id))\n            load_palette_mem(src.value, src.length);\n        }\n        else\n\n        // Load palette from file\n        if(!fsafetranslate(name_buffer, translated_name, MAX_PATH))\n        {\n          load_palette(translated_name);\n        }\n\n        free(translated_name);\n        break;\n      }\n\n      case ROBOTIC_CMD_MOD_FADE_TO: // Mod fade #t #s\n      {\n        int volume_target = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int volume = src_board->volume;\n        int volume_inc = parse_param(mzx_world, p2, id);\n\n        // Pre-port versions bounded volume_target by using a char.\n        if(mzx_world->version < VERSION_PORT)\n          volume_target &= 255;\n\n        else\n          volume_target = CLAMP(volume_target, 0, 255);\n\n        if(volume_target == volume)\n        {\n          src_board->volume_inc = 0;\n          src_board->volume_target = 0;\n        }\n        else\n        {\n          if(volume_inc == 0)\n            volume_inc = 1;\n          if((volume < volume_target) && (volume_inc < 0))\n            volume_inc = -volume_inc;\n          if((volume > volume_target) && (volume_inc > 0))\n            volume_inc = -volume_inc;\n\n          src_board->volume_target = volume_target;\n          src_board->volume_inc = volume_inc;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_SCROLLVIEW_XY: // Scrollview x y\n      {\n        int scroll_x = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int scroll_y = parse_param(mzx_world, p2, id);\n        int n_scroll_x, n_scroll_y;\n\n        prefix_mid_xy(mzx_world, &scroll_x, &scroll_y, x, y);\n\n        // scroll_x/scrolly target to put in upper left corner\n        // offset off of player position\n        src_board->scroll_x = 0;\n        src_board->scroll_y = 0;\n        calculate_xytop(mzx_world, &n_scroll_x, &n_scroll_y);\n        src_board->scroll_x = scroll_x - n_scroll_x;\n        src_board->scroll_y = scroll_y - n_scroll_y;\n        break;\n      }\n\n      case ROBOTIC_CMD_SWAP_WORLD: // Swap world str\n      {\n        char name_buffer[ROBOT_MAX_TR];\n        char *translated_name = cmalloc(MAX_PATH);\n        int redo_load;\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, name_buffer);\n\n        // Couldn't find world to swap to; abort cleanly\n        if(fsafetranslate(name_buffer, translated_name, MAX_PATH))\n        {\n          free(translated_name);\n          break;\n        }\n\n        /* If the swap world fails, give them Fail and Retry.\n         * World validation prevents the bad swap from clearing data. */\n        do\n        {\n          boolean ignore; // FIXME: Hack!\n          redo_load = 0;\n          if(!reload_swap(mzx_world, translated_name, &ignore))\n          {\n            redo_load = error(\"Error swapping to next world\",\n             ERROR_T_ERROR, ERROR_OPT_FAIL|ERROR_OPT_RETRY, 0x2C01);\n          }\n        } while(redo_load == ERROR_OPT_RETRY);\n        free(translated_name);\n\n        // User asked to \"Fail\" on error message above\n        if(redo_load == ERROR_OPT_FAIL)\n          break;\n\n        mzx_world->change_game_state = CHANGE_STATE_SWAP_WORLD;\n        return;\n      }\n\n      case ROBOTIC_CMD_IF_ALIGNEDROBOT: // If allignedrobot str str\n      {\n        if(id)\n        {\n          struct robot *dest_robot;\n          int dest_x, dest_y;\n          int first, last;\n          char robot_name_buffer[ROBOT_MAX_TR];\n          tr_msg(mzx_world, cmd_ptr + 2, id, robot_name_buffer);\n\n          if(!find_robot(src_board, robot_name_buffer, &first, &last))\n          {\n            /* Versions 2.80 through 2.93 didn't bother checking the return\n             * value of find_robot. When find_robot returns 0, no robot with\n             * that name exists and the values of both first and last contain\n             * the index where a robot with that name should be inserted into\n             * the name sorted list. In other words, this command checked the\n             * robot alphabetically following the searched name or the stale\n             * pointer at the end of the list (resulting in a crash).\n             * It's doubtful anything relies on this, but allow just in case.\n             */\n            if(mzx_world->version < VERSION_PORT || mzx_world->version >= V293 ||\n             last >= src_board->num_robots_active)\n              break;\n          }\n\n          while(first <= last)\n          {\n            dest_robot = src_board->robot_list_name_sorted[first];\n            get_robot_position(dest_robot, &dest_x, &dest_y);\n\n            if(dest_robot && ((dest_x == x) || (dest_y == y)))\n            {\n              char *p2 = next_param_pos(cmd_ptr + 1);\n              gotoed = send_self_label_tr(mzx_world, p2 + 1, id);\n              // DOS versions only send once here.\n              if(mzx_world->version < VERSION_PORT)\n                break;\n            }\n            first++;\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_LOCKSCROLL: // Lockscroll\n      {\n        // The scrolling is locked at the current position.\n        // Further scrollview cmds can change it.\n        int scroll_x = src_board->scroll_x;\n        int scroll_y = src_board->scroll_y;\n        int n_scroll_x, n_scroll_y;\n        src_board->scroll_x = 0;\n        src_board->scroll_y = 0;\n        calculate_xytop(mzx_world, &n_scroll_x, &n_scroll_y);\n        src_board->locked_x = n_scroll_x;\n        src_board->locked_y = n_scroll_y;\n        src_board->scroll_x = scroll_x;\n        src_board->scroll_y = scroll_y;\n        break;\n      }\n\n      case ROBOTIC_CMD_UNLOCKSCROLL: // Unlockscroll\n      {\n        src_board->locked_x = -1;\n        src_board->locked_y = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_IF_FIRST_INPUT: // If first string str str\n      {\n        char tmp[2] = \"\";\n        char *input_string = src_board->input_string;\n        char match_string_buffer[ROBOT_MAX_TR];\n        ssize_t i = 0;\n\n        if(!input_string)\n          input_string = tmp;\n\n        if(src_board->input_size)\n        {\n          do\n          {\n            if(input_string[i] == 32) break;\n            i++;\n          } while(i < (ssize_t)src_board->input_size && input_string[i]);\n        }\n\n        if(input_string[i] == 32)\n          input_string[i] = 0;\n        else\n          i = -1;\n\n        tr_msg(mzx_world, cmd_ptr + 2, id, match_string_buffer);\n\n        // Compare\n        if(!strcasecmp(input_string, match_string_buffer))\n        {\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          gotoed = send_self_label_tr(mzx_world, p2 + 1, id);\n        }\n\n        if(i >= 0)\n          input_string[i] = 32;\n\n        break;\n      }\n\n      case ROBOTIC_CMD_WAIT_MOD_FADE: // Wait mod fade\n      {\n        if(src_board->volume != src_board->volume_target)\n        {\n          END_CYCLE;\n          return;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_ENABLE_SAVING: // Enable saving\n      {\n        src_board->save_mode = CAN_SAVE;\n        break;\n      }\n\n      case ROBOTIC_CMD_DISABLE_SAVING: // Disable saving\n      {\n        src_board->save_mode = CANT_SAVE;\n        break;\n      }\n\n      case ROBOTIC_CMD_ENABLE_SENSORONLY_SAVING: // Enable sensoronly saving\n      {\n        src_board->save_mode = CAN_SAVE_ON_SENSOR;\n        break;\n      }\n\n      case ROBOTIC_CMD_STATUS_COUNTER: // Status counter ## str\n      {\n        int counter_slot = parse_param(mzx_world, cmd_ptr + 1, id);\n        if((counter_slot >= 1) && (counter_slot <= 6))\n        {\n          char *p2 = next_param_pos(cmd_ptr + 1);\n          char counter_name[ROBOT_MAX_TR];\n          tr_msg(mzx_world, p2 + 1, id, counter_name);\n\n          if(strlen(counter_name) >= COUNTER_NAME_SIZE)\n            counter_name[COUNTER_NAME_SIZE - 1] = 0;\n\n          strcpy(mzx_world->status_counters_shown[counter_slot - 1],\n           counter_name);\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_OVERLAY_ON: // Overlay on\n      {\n        setup_overlay(src_board, 1);\n        break;\n      }\n\n      case ROBOTIC_CMD_OVERLAY_STATIC: // Overlay static\n      {\n        setup_overlay(src_board, 2);\n        break;\n      }\n\n      case ROBOTIC_CMD_OVERLAY_TRANSPARENT: // Overlay transparent\n      {\n        setup_overlay(src_board, 3);\n        break;\n      }\n\n      case ROBOTIC_CMD_OVERLAY_PUT_OVERLAY: // put col ch overlay x y\n      {\n        int put_color = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int put_char = parse_param(mzx_world, p2, id);\n        char *p3 = next_param_pos(p2);\n        int put_x = parse_param(mzx_world, p3, id);\n        char *p4 = next_param_pos(p3);\n        int put_y = parse_param(mzx_world, p4, id);\n        int offset;\n\n        if(!src_board->overlay_mode)\n          setup_overlay(src_board, 3);\n\n        prefix_mid_xy(mzx_world, &put_x, &put_y, x, y);\n        offset = put_x + (put_y * board_width);\n\n        put_color =\n          fix_color(put_color, src_board->overlay_color[offset]);\n\n        src_board->overlay[offset] = put_char;\n        src_board->overlay_color[offset] = put_color;\n        break;\n      }\n\n      case ROBOTIC_CMD_CHANGE_OVERLAY: // Change overlay col ch col ch\n      {\n        int src_color = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int src_char = parse_param(mzx_world, p2, id);\n        char *p3 = next_param_pos(p2);\n        int dest_color = parse_param(mzx_world, p3, id);\n        char *p4 = next_param_pos(p3);\n        int dest_char = parse_param(mzx_world, p4, id);\n        int src_fg, src_bg, i;\n        char *overlay, *overlay_color;\n        int d_color;\n\n        if(!src_board->overlay_mode)\n          setup_overlay(src_board, 3);\n\n        overlay = src_board->overlay;\n        overlay_color = src_board->overlay_color;\n\n        split_colors(src_color, &src_fg, &src_bg);\n        match_version_fix(mzx_world, &src_fg, &src_bg, NULL);\n\n        for(i = 0; i < (board_width * board_height); i++)\n        {\n          d_color = overlay_color[i];\n\n          if((overlay[i] == src_char) &&\n           match_overlay_color(d_color, src_fg, src_bg))\n          {\n            overlay[i] = dest_char;\n            overlay_color[i] = fix_color(dest_color, d_color);\n          }\n        }\n\n        break;\n      }\n\n      case ROBOTIC_CMD_CHANGE_OVERLAY_COLOR: // Change overlay col col\n      {\n        int src_color = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        int dest_color = parse_param(mzx_world, p2, id);\n        int src_fg, src_bg, i;\n        char *overlay_color;\n        int d_color;\n\n        if(!src_board->overlay_mode)\n          setup_overlay(src_board, 3);\n\n        overlay_color = src_board->overlay_color;\n\n        split_colors(src_color, &src_fg, &src_bg);\n        match_version_fix(mzx_world, &src_fg, &src_bg, NULL);\n\n        for(i = 0; i < (board_width * board_height); i++)\n        {\n          d_color = overlay_color[i];\n          if(match_overlay_color(d_color, src_fg, src_bg))\n          {\n            overlay_color[i] = fix_color(dest_color, d_color);\n          }\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_WRITE_OVERLAY: // Write overlay col str # #\n      {\n        int write_color = parse_param(mzx_world, cmd_ptr + 1, id);\n        char *p2 = next_param_pos(cmd_ptr + 1);\n        char *write_string = p2 + 1;\n        char *p3 = next_param_pos(p2);\n        int write_x = parse_param(mzx_world, p3, id);\n        char *p4 = next_param_pos(p3);\n        int write_y = parse_param(mzx_world, p4, id);\n        int offset;\n        char string_buffer[ROBOT_MAX_TR];\n        char current_char;\n        char *overlay, *overlay_color;\n\n        prefix_mid_xy(mzx_world, &write_x, &write_y, x, y);\n        offset = write_x + (write_y * board_width);\n\n        tr_msg(mzx_world, write_string, id, string_buffer);\n\n        write_string = string_buffer;\n        current_char = *write_string;\n\n        if(!src_board->overlay_mode)\n          setup_overlay(src_board, 3);\n\n        overlay_color = src_board->overlay_color;\n        overlay = src_board->overlay;\n\n        while(current_char != 0)\n        {\n          overlay_color[offset] = write_color;\n          overlay[offset] = current_char;\n          write_x++;\n          if(write_x == board_width)\n            break;\n          write_string++;\n          current_char = *write_string;\n          offset++;\n        }\n        break;\n      }\n\n      case ROBOTIC_CMD_LOOP_START: // Loop start\n      {\n        cur_robot->loop_count = 0;\n        break;\n      }\n\n      case ROBOTIC_CMD_LOOP_FOR: // Loop #\n      {\n        int loop_amount = parse_param(mzx_world, cmd_ptr + 1, id);\n        int loop_count = cur_robot->loop_count;\n        int back_cmd;\n\n        if(loop_count < loop_amount)\n        {\n          back_cmd = cur_robot->cur_prog_line;\n          do\n          {\n            if(program[back_cmd - 1] == 0xFF)\n              break;\n\n            back_cmd -= program[back_cmd - 1] + 2;\n          } while(program[back_cmd + 1] != ROBOTIC_CMD_LOOP_START);\n\n          cur_robot->cur_prog_line = back_cmd;\n        }\n\n        cur_robot->loop_count = loop_count + 1;\n        last_label = -1;\n        break;\n      }\n\n      case ROBOTIC_CMD_ABORT_LOOP: // Abort loop\n      {\n        int forward_cmd = cur_robot->cur_prog_line;\n\n        do\n        {\n          if(!program[forward_cmd])\n            break;\n\n          forward_cmd += program[forward_cmd] + 2;\n        } while(program[forward_cmd + 1] != ROBOTIC_CMD_LOOP_FOR);\n\n        if(program[forward_cmd])\n          cur_robot->cur_prog_line = forward_cmd;\n\n        break;\n      }\n\n      case ROBOTIC_CMD_DISABLE_MESG_EDGE: // Disable mesg edge\n      {\n        mzx_world->mesg_edges = 0;\n        break;\n      }\n\n\n      case ROBOTIC_CMD_ENABLE_MESG_EDGE: // Enable mesg edge\n      {\n        mzx_world->mesg_edges = 1;\n        break;\n      }\n\n      case ROBOTIC_CMD_LABEL:\n      {\n        // Prior to the port, MZX would end the cycle if a label was\n        // seen more than once in that cycle. This behaviour was\n        // exploited in certain pre-port games such as \"Kya's Sword\" and\n        // \"Stones & Roks II\", where it would interact with the SHOOT\n        // command and alter the timing when SHOOT was in a tight loop.\n\n        // We don't really care for this behaviour in the port, and the\n        // compatibility has been broken forever, so only do this for\n        // old worlds.\n\n        if(mzx_world->version < VERSION_PORT)\n        {\n          if(last_label == cur_robot->cur_prog_line)\n          {\n            END_CYCLE;\n            return;\n          }\n          last_label = cur_robot->cur_prog_line;\n        }\n\n        lines_run--;\n        break;\n      }\n\n      case ROBOTIC_CMD_ZAPPED_LABEL:\n      {\n        lines_run--;\n        break;\n      }\n    }\n\n    // Go to next command! First erase prefixes...\n    mzx_world->first_prefix = 0;\n    mzx_world->mid_prefix = 0;\n    mzx_world->last_prefix = 0;\n    mzx_world->command_cache = 0;\n\n    next_cmd_prefix:\n    // Next line\n    first_cmd = 0;\n\n    if(!cur_robot->cur_prog_line)\n    {\n      cur_robot->pos_within_line = 0;\n      break;\n    }\n\n#ifdef CONFIG_EDITOR\n    // Check to see if a watchpoint triggered before incrementing the program.\n    if(mzx_world->editing && debug_robot_watch)\n    {\n      // Returns 1 if the user chose to stop the program.\n      switch(debug_robot_watch(ctx, cur_robot, id, lines_run))\n      {\n        case DEBUG_EXIT:\n          break;\n\n        case DEBUG_GOTO:\n          // If this robot received a label, don't advance the program.\n          if(old_pos != cur_robot->cur_prog_line)\n            gotoed = 1;\n          break;\n\n        case DEBUG_HALT:\n          END_CYCLE;\n          return;\n      }\n    }\n#endif\n\n    // If we're returning from a subroutine, we don't want to set the\n    // pos_within_line. Other sends will set it to zero anyway.\n    if(!gotoed)\n      ADVANCE_LINE;\n\n    if(!program[cur_robot->cur_prog_line])\n    {\n      //End of program\n      END_PROGRAM;\n      return;\n    }\n\n    if(update_blocked)\n    {\n      calculate_blocked(mzx_world, x, y, id, _bl);\n    }\n    find_player(mzx_world);\n\n    // Some commands can decrement lines_run, putting it at -1 here,\n    // so add 2 to lines_run for the check. Originally this checked every 1 mil\n    // commands, but it turns out checking every 64k commands is faster due to\n    // better x86 optimizations using the lower word of lines_run.\n    if(((lines_run + 2) & 0xffff) == 0)\n    {\n      // Only check after 1 mil commands to reduce false positives.\n      if(lines_run >= 1000000 && peek_exit_input())\n      {\n        update_event_status();\n\n        if(get_exit_status() || get_key_status(keycode_internal, IKEY_ESCAPE))\n        {\n          boolean exit_game;\n          dialog_fadein();\n          exit_game = !confirm(mzx_world,\n           \"MegaZeux appears to have frozen. Do you want to exit?\");\n          dialog_fadeout();\n          update_screen();\n\n          if(exit_game)\n          {\n            mzx_world->change_game_state = CHANGE_STATE_REQUEST_EXIT;\n            break;\n          }\n        }\n      }\n    }\n\n  } while(((++lines_run) < mzx_world->commands) && (!done));\n\n  END_CYCLE;\n}\n"
  },
  {
    "path": "src/run_stubs.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"run_stubs.h\"\n\n#ifdef CONFIG_EDITOR\nboolean is_editor(void) { return false; }\nvoid editor_init(void) { }\nvoid init_macros(void) { }\n\nvoid default_editor_config(void) {}\nvoid store_editor_config_backup(void) {}\nvoid free_editor_config(void) {}\n#endif\n\n#ifdef CONFIG_UPDATER\nboolean updater_init(void) { return true; }\nboolean is_updater(void) { return false; }\n#endif\n"
  },
  {
    "path": "src/run_stubs.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __RUN_STUBS_H\n#define __RUN_STUBS_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#ifdef CONFIG_EDITOR\nboolean is_editor(void);\nvoid editor_init(void);\nvoid init_macros(void);\n\nvoid default_editor_config(void);\nvoid store_editor_config_backup(void);\nvoid free_editor_config(void);\n#else\nstatic inline boolean is_editor(void) { return false; }\nstatic inline void editor_init(void) {}\nstatic inline void init_macros(void) {}\n\nstatic inline void default_editor_config(void) {}\nstatic inline void store_editor_config_backup(void) {}\nstatic inline void free_editor_config(void) {}\n#endif\n\n\n#ifdef CONFIG_UPDATER\nboolean updater_init(void);\nboolean is_updater(void);\n#else\nstatic inline boolean updater_init(void)\n{ return true; }\nstatic inline boolean is_updater(void)\n{ return false; }\n#endif\n\n__M_END_DECLS\n\n#endif // __RUN_STUBS_H\n"
  },
  {
    "path": "src/scrdisp.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* Scroll display code */\n\n#include <string.h>\n#include <stdio.h>\n#include <ctype.h>\n\n#include \"configure.h\"\n#include \"event.h\"\n#include \"data.h\"\n#include \"scrdisp.h\"\n#include \"window.h\"\n#include \"graphics.h\"\n#include \"error.h\"\n#include \"intake.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n#include \"robot.h\"\n\nstatic const char scr_nm_strs[5][12] =\n { \"  Scroll   \", \"   Sign    \", \"Edit Scroll\", \"   Help    \", \"\" };\n\nstatic int scroll_clip_position(char *string, int pos, int offset, int draw_flags)\n{\n  if(draw_flags & WR_COLOR)\n  {\n    return pos + color_string_index_of(string + pos,\n     SCROLL_MAX_LINE_LEN, offset, '\\n');\n  }\n  else\n  {\n    int tmp = strcspn(string + pos, \"\\n\");\n    return pos + (tmp < offset ? tmp : offset);\n  }\n}\n\nstatic int scroll_draw_flags(struct world *mzx_world, boolean mask_chars,\n boolean mask_colors)\n{\n  int flags = WR_LINE;\n\n  if(mask_chars)\n    flags |= WR_MASK;\n\n  if(!mask_colors)\n    if(mzx_world->version < V200 || mzx_world->version >= V293)\n      flags |= WR_COLOR;\n\n  return flags;\n}\n\nstatic void scroll_frame(struct world *mzx_world, struct scroll *scroll,\n int pos, boolean mask_chars, boolean mask_colors)\n{\n  // Displays one frame of a scroll. The scroll edging, arrows, and title\n  // must already be shown. Simply prints each line. POS is the position\n  // of the scroll's line in the text. This is of the center line.\n  int t1;\n  unsigned int old_pos = pos;\n  char *where;\n  int scroll_base_color = mzx_world->scroll_base_color;\n  int c_offset = 16;\n  int flags = scroll_draw_flags(mzx_world, mask_chars, false);\n  int tmp_pos;\n  char tmp;\n\n  where = scroll->mesg;\n\n  select_layer(UI_LAYER);\n\n  if(!mask_colors)\n  {\n    // Clear the UI over the frame area in case the frame is masked (editor)\n    // but the contents aren't.\n    erase_area(8, 6, 71, 18);\n    select_layer(GAME_UI_LAYER);\n    c_offset = 0;\n  }\n\n  // Display center line\n  tmp_pos = scroll_clip_position(where, pos, 64, flags);\n  tmp = where[tmp_pos];\n  where[tmp_pos] = '\\0';\n  fill_line_ext(64, 8, 12, 32, scroll_base_color, 0, c_offset);\n  write_string_ext(where + pos, 8, 12, scroll_base_color, flags, 0, c_offset);\n  where[tmp_pos] = tmp;\n\n  // Display lines above center line\n  for(t1 = 11; t1 >= 6; t1--)\n  {\n    fill_line_ext(64, 8, t1, 32, scroll_base_color, 0, c_offset);\n    // Go backward to previous line\n    if(where[pos] != 1)\n    {\n      pos--;\n      if(where[pos] == '\\n')\n      {\n        do\n        {\n          pos--;\n        } while((where[pos] != '\\n') && (where[pos] != 1));\n        // At start of prev. line -1. Display.\n        pos++;\n        tmp_pos = scroll_clip_position(where, pos, 64, flags);\n        tmp = where[tmp_pos];\n        where[tmp_pos] = '\\0';\n        write_string_ext(where + pos, 8, t1, scroll_base_color, flags, 0, c_offset);\n        where[tmp_pos] = tmp;\n      }\n    }\n  }\n\n  // Display lines below center line\n  pos = old_pos;\n  for(t1 = 13; t1 <= 18; t1++)\n  {\n    fill_line_ext(64, 8, t1, 32, scroll_base_color, 0, c_offset);\n    if(where[pos] == 0) continue;\n    // Go forward to next line\n    while(where[pos] != '\\n') pos++;\n    // At end of current. If next is a 0, it's the end of the scroll.\n    pos++;\n    if(where[pos])\n    {\n      tmp_pos = scroll_clip_position(where, pos, 64, flags);\n      tmp = where[tmp_pos];\n      where[tmp_pos] = '\\0';\n      write_string_ext(where + pos, 8, t1, scroll_base_color, flags, 0, c_offset);\n      where[tmp_pos] = tmp;\n    }\n  }\n\n  select_layer(UI_LAYER);\n}\n\n// Also used to display scrolls. Use a type of 0 for Show Scroll, 1 for Show\n// Sign, 2 for Edit Scroll.\n\nvoid scroll_edit(struct world *mzx_world, struct scroll *scroll, int type)\n{\n  char *where; // Scroll message pointer.\n  char line_buffer[SCROLL_MAX_LINE_LEN];\n  size_t line_src_len = 0;\n  size_t edit_len;\n  size_t tail_len;\n  size_t sz;\n  int page_lines;\n\n  unsigned int pos = 1, old_pos; // Where IN scroll?\n  int currx = 0; // X position in line\n  int key; // Key returned by intake()\n  int scroll_base_color = mzx_world->scroll_base_color;\n  boolean editing = (type == 2);\n  // If the user wants to mask chars and we're in the editor..\n  boolean mask_chars = get_config()->mask_midchars && editing;\n  boolean mask_colors = editing;\n  boolean update_border = true;\n\n  m_show();\n  save_screen();\n  dialog_fadein();\n\n  where = scroll->mesg;\n\n  if(!editing && mzx_world->version >= VERSION_PORT && mzx_world->version < V291)\n  {\n    // MegaZeux 2.80h through 2.91f expect the protected palette to be used\n    // for scrolls. Furthermore, 2.90 through 2.91f had broken scroll borders\n    // that mixed colors. The versions picked for this behavior are a little\n    // arbitrary because there are no good version cutoffs for supporting this.\n    mask_colors = true;\n  }\n\n  do\n  {\n    if(update_border)\n    {\n      // Mask edging chars/colors in the scroll editor, but display natural\n      // chars/colors during gameplay.\n      scroll_edging_ext(mzx_world, type, mask_colors);\n      update_border = false;\n    }\n    // Display scroll. If editing, the colors of the frame need to be masked.\n    // Otherwise, they should display using game colors.\n    scroll_frame(mzx_world, scroll, pos, mask_chars, mask_colors);\n    cursor_hint(8, 12);\n    update_screen();\n\n    if(editing)\n    {\n      // Get the length of the current editing line.\n      for(sz = pos; sz < scroll->mesg_size; sz++)\n      {\n        if(where[sz] == '\\n')\n          break;\n      }\n      line_src_len = sz - pos;\n\n      edit_len = line_src_len;\n      if(edit_len >= sizeof(line_buffer))\n        edit_len = sizeof(line_buffer) - 1;\n\n      memcpy(line_buffer, where + pos, edit_len);\n      line_buffer[edit_len] = '\\0';\n\n      // Edit line\n      key = intake(mzx_world, line_buffer, sizeof(line_buffer), 64, 8, 12,\n       scroll_base_color, INTK_EXIT_ANY, mask_colors, &currx);\n\n      // Modify scroll to hold new line (give errors here)\n      edit_len = strlen(line_buffer);\n      tail_len = scroll->mesg_size - pos - line_src_len;\n\n      // Resize and move\n      if(edit_len > line_src_len)\n      {\n        reallocate_scroll(scroll, scroll->mesg_size + edit_len - line_src_len);\n        where = scroll->mesg;\n\n        memmove(where + pos + edit_len, where + pos + line_src_len, tail_len);\n      }\n      else\n\n      if(edit_len < line_src_len)\n      {\n        memmove(where + pos + edit_len, where + pos + line_src_len, tail_len);\n\n        reallocate_scroll(scroll, scroll->mesg_size + edit_len - line_src_len);\n        where = scroll->mesg;\n      }\n\n      // Copy in new line\n      memcpy(where + pos, line_buffer, edit_len);\n      where[pos + edit_len] = '\\n';\n    }\n    else\n    {\n      int joystick_key;\n\n      update_event_status_delay();\n      key = get_key(keycode_internal_wrt_numlock);\n\n      joystick_key = get_joystick_ui_key();\n      if(joystick_key)\n        key = joystick_key;\n    }\n\n    // Exit event -- mimic Escape\n    if(get_exit_status())\n      key = IKEY_ESCAPE;\n\n    old_pos = pos;\n\n    if(get_mouse_press() || (key == -1))\n    {\n      int mouse_x, mouse_y;\n      get_mouse_position(&mouse_x, &mouse_y);\n\n      if((mouse_y >= 6) && (mouse_y <= 18) && (mouse_x >= 8) &&\n       (mouse_x <= 71))\n      {\n        page_lines = mouse_y - 12;\n        if(page_lines)\n        {\n          if(page_lines < 0)\n            goto pgup;\n          else\n            goto pgdn;\n        }\n      }\n      key = IKEY_ESCAPE;\n    }\n\n    switch(key)\n    {\n      case IKEY_UP:\n      {\n        // Go back a line (if possible)\n        if(where[pos - 1] == 1) break; // Can't.\n        pos--;\n        // Go to start of this line.\n        do\n        {\n          pos--;\n        } while((where[pos] != '\\n') && (where[pos] != 1));\n        pos++;\n        // Done.\n        break;\n      }\n\n      case IKEY_DOWN:\n      {\n        // Go forward a line (if possible)\n        while(where[pos] != '\\n')\n          pos++;\n        // At end of current. Is there a next line?\n        pos++;\n        if(where[pos] == 0)\n        {\n          // Nope.\n          pos = old_pos;\n          break;\n        }\n        // Yep. Done.\n        break;\n      }\n\n      case IKEY_RETURN:\n      {\n        if(type < 2)\n        {\n          key = IKEY_ESCAPE;\n        }\n        else\n        {\n          // Add in new line below. Need only add one byte.\n          sz = scroll->mesg_size;\n          reallocate_scroll(scroll, scroll->mesg_size + 1);\n          where = scroll->mesg;\n          // Move all at pos + currx up a space\n          memmove(where + pos + currx + 1, where + pos + currx,\n           sz - pos - currx);\n          // Insert a \\n\n          where[pos + currx] = '\\n';\n          // Change pos and currx\n          pos = pos + currx + 1;\n          currx = 0;\n          scroll->num_lines++;\n        }\n        break;\n      }\n\n      case IKEY_BACKSPACE:\n      {\n        size_t line_prev_len;\n        if(type < 2)\n          break;\n\n        // Get the length of the previous line, if it exists.\n        if(where[pos - 1] == 1)\n          break;\n\n        pos--;\n        // Go to start of this line, COUNTING CHARACTERS.\n        line_prev_len = 0;\n        do\n        {\n          pos--;\n          line_prev_len++;\n        } while((where[pos] != '\\n') && (where[pos] != 1));\n\n        // If the length of the current line plus the length of the previous\n        // line exceeds the maximum length of text the buffer can hold, these\n        // lines can't be joined. Note line_prev_len includes its terminator.\n        if(line_src_len + line_prev_len > sizeof(line_buffer))\n        {\n          pos = old_pos;\n          break;\n        }\n\n        // OKAY! Just copy backwards over the \\n in the middle to\n        // append...\n        memmove(where + (old_pos - 1), where + old_pos, scroll->mesg_size - (old_pos + 1));\n        // ...and reallocate to one space less!\n        reallocate_scroll(scroll, scroll->mesg_size - 1);\n        where = scroll->mesg;\n        where[scroll->mesg_size - 1] = '\\0';\n\n        // pos is one before this start. Fix to the start of this new\n        // line. Set currx to the length of the old line this was.\n        pos++;\n        currx = (int)line_prev_len - 1;\n        scroll->num_lines--;\n\n        // Done.\n        break;\n      }\n\n      case IKEY_PAGEDOWN:\n      {\n        for(page_lines = 6; page_lines > 0; page_lines--)\n        {\n          pgdn:\n          // Go forward a line (if possible)\n          old_pos = pos;\n          while(where[pos] != '\\n') pos++;\n          // At end of current. Is there a next line?\n          pos++;\n          if(where[pos] == 0)\n          {\n            // Nope.\n            pos = old_pos;\n            break;\n          }\n          // Yep. Done.\n        }\n        break;\n      }\n\n      case IKEY_PAGEUP:\n      {\n        for(page_lines = -6; page_lines < 0; page_lines++)\n        {\n          pgup:\n          // Go back a line (if possible)\n          if(where[pos - 1] == 1) break; // Can't.\n          pos--;\n          // Go to start of this line.\n          do\n          {\n            pos--;\n          } while((where[pos] != '\\n') && (where[pos] != 1));\n          pos++;\n          // Done.\n        }\n        break;\n      }\n\n      case IKEY_HOME:\n      {\n        // FIXME - This is so dirty. Please replace it.\n        page_lines = -30000;\n        goto pgup;\n      }\n\n      case IKEY_END:\n      {\n        // FIXME - See above.\n        page_lines = 30000;\n        goto pgdn;\n      }\n\n      case IKEY_v:\n      {\n        if(editing && get_alt_status(keycode_internal))\n        {\n          // Game/edit view (toggle masking).\n          mask_colors = !mask_colors;\n          mask_chars = !mask_chars && get_config()->mask_midchars;\n          update_border = true;\n        }\n        break;\n      }\n\n      default:\n      case IKEY_ESCAPE:\n      case 0:\n        break;\n    }\n    // Continue?\n  } while(key != IKEY_ESCAPE);\n  // Restore screen and exit\n  cursor_off();\n  restore_screen();\n  // Due to a faulty check, 2.83 through 2.91f always stay faded in here.\n  // If something is found that relies on that, make this conditional.\n  dialog_fadeout();\n}\n\nvoid scroll_edging_ext(struct world *mzx_world, int type, boolean mask)\n{\n  int scroll_base_color = mzx_world->scroll_base_color;\n  int scroll_corner_color = mzx_world->scroll_corner_color;\n  int scroll_pointer_color = mzx_world->scroll_pointer_color;\n  int scroll_title_color = mzx_world->scroll_title_color;\n  int bottom_y = (type == 3) ? 22 : 21;\n  int offset = PROTECTED_CHARSET_POSITION;\n  int c_offset = 16;\n  int i;\n\n  select_layer(UI_LAYER);\n\n  if(!mask)\n  {\n    // Clear the place on the UI layer where this will display. This is\n    // necessary because the UI may have been filled to simulate the faded\n    // out state. Then, draw the shadow on the UI layer.\n    erase_area(5, 3, 74, bottom_y);\n\n    // Right shadow\n    for(i = 4; i < bottom_y; i++)\n      draw_char(32, 0, 75, i);\n\n    // Bottom shadow\n    color_line(70, 6, bottom_y + 1, 0);\n\n    select_layer(GAME_UI_LAYER);\n    offset = 0;\n    c_offset = 0;\n  }\n\n  // Displays the edging box of scrolls. Type=0 for Scroll, 1 for Sign, 2\n  // for Edit Scroll, 3 for Help, 4 for Robot (w/o title)\n  // Doesn't save the screen.\n  // Box for the title\n  draw_window_box_ext(5, 3, 74, 5, scroll_base_color & 0xF0, scroll_base_color,\n   scroll_corner_color, 1, 1, offset, c_offset);\n  // Main box\n  draw_window_box_ext(5, 5, 74, 19, scroll_base_color, scroll_base_color & 0xF0,\n   scroll_corner_color, 1, 1, offset, c_offset); /* Text on 6 - 18 */\n  // Shows keys in a box at the bottom\n  if(type == 3)\n  {\n    draw_window_box_ext(5, 19, 74, 22, scroll_base_color & 0xF0,\n     scroll_base_color, scroll_corner_color, 1, 1, offset, c_offset);\n  }\n  else\n  {\n    draw_window_box_ext(5, 19, 74, 21, scroll_base_color & 0xF0,\n     scroll_base_color, scroll_corner_color, 1, 1, offset, c_offset);\n  }\n\n  // Fix chars on edging\n  draw_char_ext(217, scroll_base_color, 74, 5, offset, c_offset);\n  draw_char_ext(217, scroll_base_color & 0xF0, 74, 19, offset, c_offset);\n  // Add arrows\n  draw_char_ext(16, scroll_pointer_color, 6, 12, offset, c_offset);\n  draw_char_ext(17, scroll_pointer_color, 73, 12, offset, c_offset);\n  // Write title\n  write_string_ext(scr_nm_strs[type], 34, 4, scroll_title_color, WR_NONE,\n   offset, c_offset);\n  // Write key reminders\n  if(type == 2)\n  {\n    write_string_ext(\"\\x12\\x1d: Move cursor   Alt+C: Character \"\n     \"  Alt+V:XXXX view   Esc: Exit\", 8, 20, scroll_corner_color, WR_NONE,\n     offset, c_offset);\n\n    write_string_ext(mask ? \"Game\" : \"Edit\", 51, 20, scroll_corner_color,\n     WR_NONE, offset, c_offset);\n  }\n  else\n\n  if(type < 2)\n  {\n    write_string_ext(\"\\x12: Scroll text   Esc/Enter: End reading\",\n     21, 20, scroll_corner_color, WR_NONE, offset, c_offset);\n  }\n  else\n\n  if(type == 3)\n  {\n    write_string_ext(\n     \"\\x12:Scroll text  Esc:Exit help  Enter:Select  Alt+P:Print\\n\"\n     \"\\t  F1:Help on Help  Alt+F1:Table of Contents\",\n     13, 20, scroll_corner_color, WR_TAB | WR_NEWLINE, offset, c_offset);\n  }\n  else\n  {\n    write_string_ext(\"\\x12:Scroll text  Esc:Exit  Enter:Select\",\n     21, 20, scroll_corner_color, WR_NONE, offset, c_offset);\n  }\n  select_layer(UI_LAYER);\n  update_screen();\n}\n\n#ifdef CONFIG_HELPSYS\n\nstatic void help_frame(struct world *mzx_world, char *help, int pos)\n{\n  // Displays one frame of the help. Simply prints each line. POS is the\n  // position of the center line.\n  int t1, t2;\n  int first = 12;\n  int scroll_base_color = mzx_world->scroll_base_color;\n  int scroll_arrow_color = mzx_world->scroll_arrow_color;\n  unsigned int next_pos;\n\n  // Work backwards to line\n  do\n  {\n    if(help[pos - 1] == 1) break; // Can't.\n    pos--;\n    // Go to start of this line.\n    do\n    {\n      pos--;\n    } while((help[pos] != '\\n') && (help[pos] != 1));\n    pos++;\n    //Back a line!\n    first--;\n  } while(first > 6);\n  //First holds first line pos (6-12) to draw\n  if(first > 6)\n  {\n    for(t1 = 6; t1 < first; t1++)\n      fill_line(64, 8, t1, 32, scroll_base_color);\n  }\n  // Display from First to either 18 or end of help\n  for(t1 = first; t1 < 19; t1++)\n  {\n    // Fill...\n    fill_line(64, 8, t1, 32, scroll_base_color);\n    // Find NEXT line NOW - Actually get end of this one.\n    next_pos = pos;\n    while(help[next_pos] != '\\n')\n      next_pos++;\n\n    // Temp. make a 0\n    help[next_pos] = 0;\n    // Write- What TYPE is it?\n    if(help[pos] != 255) //Normal\n      color_string(help + pos, 8, t1, scroll_base_color);\n    else\n    {\n      pos++;\n      switch(help[pos])\n      {\n        case '$':\n          // Centered. :)\n          pos++;\n          t2 = color_string_length(help + pos, next_pos - pos);\n          color_string(help + pos, 40 - (t2 >> 1), t1, scroll_base_color);\n          break;\n        case '>':\n        case '<':\n          // Option- Jump to AFTER dest. label/fill\n          pos += help[pos + 1] + 3;\n          // Now show, two spaces over\n          color_string(help + pos, 10, t1, scroll_base_color);\n          // Add arrow\n          draw_char('\\x10', scroll_arrow_color, 8, t1);\n          break;\n        case ':':\n          // Label- Jump to mesg and show\n          pos += help[pos + 1] + 3;\n          color_string(help + pos, 8, t1, scroll_base_color);\n          break;\n      }\n    }\n    // Now fix EOS to be a \\n\n    help[next_pos] = '\\n';\n    // Next line...\n    next_pos++;\n    pos = next_pos;\n    if(help[pos] == 0)  break;\n  }\n  if(t1 < 19)\n  {\n    for(t1 += 1; t1 < 19; t1++)\n      fill_line(64, 8, t1, 32, scroll_base_color);\n  }\n\n  update_screen();\n}\n\nvoid help_display(struct world *mzx_world, char *help, int offs, char *file,\n char *label)\n{\n  // Display a help file\n  int pos = offs, old_pos; // Where\n  int joystick_key;\n  int key = 0;\n  int t1;\n  char mclick;\n  // allow_help = 0;\n\n  m_show();\n  save_screen();\n  dialog_fadein();\n\n  scroll_edging_ext(mzx_world, 3, true);\n\n  // Loop\n  file[0] = label[0] = 0;\n  do\n  {\n    // Display scroll\n    help_frame(mzx_world, help, pos);\n    mclick = 0;\n\n    cursor_hint(8, 12);\n    update_screen();\n\n    update_event_status_delay();\n\n    if(get_mouse_press())\n    {\n      int mouse_x, mouse_y;\n      get_mouse_position(&mouse_x, &mouse_y);\n\n      // Move to line clicked on if mouse is in scroll, else exit\n      if((mouse_y >= 6) && (mouse_y <= 18) &&\n       (mouse_x >= 8) && (mouse_x <= 71))\n      {\n        mclick = 1;\n        t1 = mouse_y - 12;\n        if(t1 == 0)\n          goto option;\n\n        //t1<0 = PGUP t1 lines\n        //t1>0 = PGDN t1 lines\n        if(t1 < 0)\n          goto pgup;\n\n        goto pgdn;\n      }\n      key = IKEY_ESCAPE;\n    }\n\n    key = get_key(keycode_internal_wrt_numlock);\n\n    joystick_key = get_joystick_ui_key();\n    if(joystick_key)\n      key = joystick_key;\n\n    // Exit event -- mimic Escape\n    if(get_exit_status())\n      key = IKEY_ESCAPE;\n\n    old_pos = pos;\n    switch(key)\n    {\n      case IKEY_F1:\n      {\n        if(get_alt_status(keycode_internal))\n        {\n          // Jump to label 072 in MAIN.HLP\n          strcpy(file,\"MAIN.HLP\");\n          strcpy(label,\"072\");\n        }\n        else\n        {\n          // Jump to label 000 in HELPONHE.HLP\n          strcpy(file, \"HELPONHE.HLP\");\n          strcpy(label, \"000\");\n        }\n        goto ex;\n      }\n\n      case IKEY_UP:\n      {\n        // Go back a line (if possible)\n        if(help[pos - 1] == 1) break; // Can't.\n        pos--;\n        // Go to start of this line.\n        do\n        {\n          pos--;\n        } while((help[pos] != '\\n') && (help[pos] != 1));\n        pos++;\n        // Done.\n        break;\n      }\n\n      case IKEY_DOWN:\n      {\n        // Go forward a line (if possible)\n        while(help[pos] != '\\n') pos++;\n        // At end of current. Is there a next line?\n        pos++;\n        if(help[pos] == 0)\n        {\n          // Nope.\n          pos = old_pos;\n          break;\n        }\n        // Yep. Done.\n        break;\n      }\n\n      case IKEY_RETURN:\n      {\n        option:\n        // Option?\n        if((help[pos] == 255) && ((help[pos + 1] == '>') ||\n         (help[pos + 1] == '<')))\n        {\n          // Yep!\n          pos++;\n          if(help[pos] == '<')\n          {\n            // Get file and label and exit\n            t1 = 0;\n            pos++;\n            do\n            {\n              pos++;\n              file[t1] = help[pos];\n              t1++;\n            } while(help[pos] != ':');\n            file[t1 - 1] = 0;\n            strcpy(label, help + pos + 1);\n            goto ex;\n          }\n          // Get label and jump\n          strcpy(label, help + pos + 2);\n\n          // Search backwards for a 1\n          do\n          {\n            pos--;\n          } while(help[pos] != 1);\n          // Search for label OR a \\n followed by a \\0\n          do\n          {\n            pos++;\n            if(help[pos] == 255)\n              if(help[pos + 1] == ':')\n                if(!strcmp(help + pos + 3, label))\n                  // pos is correct!\n                  goto labdone;\n            if(help[pos] == '\\n')\n              if(help[pos + 1] == 0) break;\n          } while(1);\n        }\n        // If there WAS an option, any existing label was found.\n        labdone:\n        break;\n      }\n\n      case IKEY_PAGEDOWN:\n      {\n        for(t1 = 6; t1 > 0; t1--)\n        {\n          pgdn:\n          // Go forward a line (if possible)\n          old_pos = pos;\n          while(help[pos] != '\\n') pos++;\n          // At end of current. Is there a next line?\n          pos++;\n          if(help[pos] == 0)\n          {\n            // Nope.\n            pos = old_pos;\n            break;\n          }\n          // Yep. Done.\n        }\n        if(mclick)\n          goto option;\n\n        break;\n      }\n\n      case IKEY_PAGEUP:\n      {\n        for(t1 = -6; t1 < 0; t1++)\n        {\n          pgup:\n          // Go back a line (if possible)\n          if(help[pos - 1] == 1) break; // Can't.\n          pos--;\n          // Go to start of this line.\n          do\n          {\n            pos--;\n          } while((help[pos] != '\\n') && (help[pos] != 1));\n          pos++;\n          // Done.\n        }\n        if(mclick)\n          goto option;\n        break;\n      }\n\n      case IKEY_HOME:\n      {\n        // FIXME - :(\n        t1 = -30000;\n        goto pgup;\n      }\n\n      case IKEY_END:\n      {\n        t1 = 30000;\n        goto pgdn;\n      }\n\n      default:\n      {\n        break;\n      }\n    }\n  } while(key != IKEY_ESCAPE);\n\n  // Restore screen and exit\nex:\n  dialog_fadeout();\n\n  cursor_off();\n  restore_screen();\n}\n\n#endif // CONFIG_HELPSYS\n"
  },
  {
    "path": "src/scrdisp.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __SCRDISP_H\n#define __SCRDISP_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"world_struct.h\"\n\n// Maximum line length of scrolls, including the newline or terminator.\n#define SCROLL_MAX_LINE_LEN 256\n\n// Type 0/1 to DISPLAY a scroll/sign\nCORE_LIBSPEC void scroll_edit(struct world *mzx_world, struct scroll *scroll,\n int type);\n\n// type == 0 scroll, 1 sign, 2 scroll edit\nvoid scroll_edging_ext(struct world *mzx_world, int type, boolean mask);\nvoid help_display(struct world *mzx_world, char *help, int offs,\n char *file, char *label);\n\n__M_END_DECLS\n\n#endif // __SCRDISP_H\n"
  },
  {
    "path": "src/settings.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2018-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"configure.h\"\n#include \"core.h\"\n#include \"error.h\"\n#include \"event.h\"\n#include \"game.h\"\n#include \"graphics.h\"\n#include \"settings.h\"\n#include \"util.h\"\n#include \"window.h\"\n#include \"world_struct.h\"\n\n#include \"audio/audio.h\"\n\n#include <string.h>\n\n// Settings dialog-\n\n//----------------------------\n//\n//    Speed- [   NN][^][v]\n//\n//   ( ) Digitized music on\n//   ( ) Digitized music off\n//\n//   ( ) PC speaker SFX on\n//   ( ) PC speaker SFX off\n//\n//  Sound card volumes-       //\n//  Overall volume- 12345678  //\n//  SoundFX volume- 12345678  //\n//  PC Speaker SFX- 12345678  //\n//\n//      [OK]     [Cancel]\n//\n//----------------------------//\n\n//  [OK]  [Cancel]  [Shader]  //\n\nenum\n{\n  RESULT_CANCEL,\n  RESULT_CONFIRM,\n  RESULT_KEEP_OPEN,\n  RESULT_SET_VIDEO_OUTPUT,\n  RESULT_SET_SHADER\n};\n\n#ifdef CONFIG_RENDER_GL_PROGRAM\n// Note- we search for .frags, then load the matching .vert too if present.\nstatic const char *shader_exts[] = { \".frag\", NULL };\nstatic char shader_default_text[20];\nstatic char shader_path[MAX_PATH];\nstatic char shader_name[MAX_PATH];\nstatic boolean is_glsl = false;\n#endif\n\nstatic int choose_video_output(struct world *mzx_world, const char **vo,\n int num_vo, int current_vo)\n{\n  int retval;\n  struct dialog di;\n  struct element *elements[] =\n  {\n    construct_list_box(2, 1, vo, num_vo, 12, 20, 0, &current_vo, NULL, false)\n  };\n\n  construct_dialog(&di, \"Choose renderer...\", 28, 5, 24, 14,\n   elements, ARRAY_SIZE(elements), 0);\n\n  retval = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if(!retval)\n    return current_vo;\n  else\n    return -1;\n}\n\nvoid game_settings(struct world *mzx_world)\n{\n  struct board *src_board = mzx_world->current_board;\n  int mzx_speed, music, pcs;\n  int prev_music;\n  int music_volume, sound_volume, pcs_volume;\n  struct dialog di;\n  int dialog_result;\n  int y_offset;\n  int y_offset_buttons;\n  int window_height;\n  int num_elements;\n  int start_option = -1;\n\n  int ok_pos;\n\n  const char *radio_strings_1[2] =\n  {\n    \"Music/sample SFX on\", \"Music/sample SFX off\"\n  };\n  const char *radio_strings_2[2] =\n  {\n    \"PC speaker SFX on\", \"PC speaker SFX off\"\n  };\n  struct element *elements[11];\n\n  struct config_info *conf = get_config();\n\n  const char *available_video_outputs[32];\n  int num_available_video_outputs = 0;\n  int cur_video_output = get_current_video_output();\n\n  num_available_video_outputs =\n   get_available_video_output_list(available_video_outputs,\n    ARRAY_SIZE(available_video_outputs));\n\n  mzx_speed = mzx_world->mzx_speed;\n  music = !audio_get_music_on();\n  pcs = !audio_get_pcs_on();\n  music_volume = audio_get_music_volume();\n  sound_volume = audio_get_sound_volume();\n  pcs_volume = audio_get_pcs_volume();\n  prev_music = !music;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  set_context(CTX_CONFIGURE);\n\n  do\n  {\n    ok_pos = 6;\n    num_elements = 8;\n    y_offset = 0;\n    y_offset_buttons = 0;\n\n    if(!mzx_world->lock_speed)\n    {\n      y_offset += 2;\n      y_offset_buttons += 2;\n      num_elements++;\n    }\n\n#if !defined(CONFIG_WII) && !defined(CONFIG_SWITCH) && \\\n !defined(__EMSCRIPTEN__) && !defined(CONFIG_PSVITA)\n    // Emscripten's SDL port crashes on re-entry attempts.\n    // Wii has multiple renderers but shouldn't display this option.\n    // FIXME this is a hack. Fix the Wii renderers so they're switchable.\n    // FIXME: Switch has undiagnosed crash bugs related to renderer switching.\n    if(num_available_video_outputs > 1)\n    {\n      elements[ok_pos] = construct_button(4, 13 + y_offset_buttons,\n       \" Select renderer... \", RESULT_SET_VIDEO_OUTPUT);\n\n      ok_pos++;\n      num_elements++;\n      y_offset_buttons += 2;\n    }\n#endif\n\n#ifdef CONFIG_RENDER_GL_PROGRAM\n    {\n      const char *cur_output_name = conf->video_output;\n\n      if(num_available_video_outputs)\n        cur_output_name = available_video_outputs[cur_video_output];\n\n      is_glsl = strstr(cur_output_name, \"glsl\") ? true : false;\n    }\n\n    if(is_glsl)\n    {\n      if(!shader_default_text[0])\n      {\n        if(conf->gl_scaling_shader[0])\n          snprintf(shader_default_text, 20, \"<conf: %.11s>\",\n           conf->gl_scaling_shader);\n\n        else\n          strcpy(shader_default_text, \"<default: semisoft>\");\n      }\n      strcpy(shader_path, mzx_res_get_by_id(GLSL_SHADER_SCALER_DIRECTORY));\n\n      elements[ok_pos] = construct_file_selector(3, 13 + y_offset_buttons,\n       \"Scaling shader-\", \"Choose a scaling shader...\", shader_exts,\n       shader_default_text, 21, 0, shader_path, shader_name, RESULT_SET_SHADER);\n\n      ok_pos++;\n      num_elements++;\n      y_offset_buttons += 3;\n    }\n#endif\n\n    if(!mzx_world->lock_speed)\n    {\n      elements[ok_pos + 2] = construct_number_box(2, 2, \"Speed- \", 1, 16,\n       NUMBER_SLIDER, &mzx_speed);\n\n      // Start at the Speed setting if enabled.\n      if(start_option < 0)\n        start_option = ok_pos + 2;\n    }\n\n    // Start at the music/samples toggle by default.\n    if(start_option < 0)\n      start_option = 0;\n\n    elements[0] = construct_radio_button(4, 2 + y_offset,\n     radio_strings_1, 2, 20, &music);\n    elements[1] = construct_radio_button(4, 5 + y_offset,\n     radio_strings_2, 2, 18, &pcs);\n    elements[2] = construct_label(2, 8 + y_offset,\n     \"Audio volumes-\");\n    elements[3] = construct_number_box(7, 9 + y_offset,\n     \"Music- \", 0, 10, NUMBER_SLIDER, &music_volume);\n    elements[4] = construct_number_box(5, 10 + y_offset,\n     \"Samples- \", 0, 10, NUMBER_SLIDER, &sound_volume);\n    elements[5] = construct_number_box(2, 11 + y_offset,\n     \"PC Speaker- \", 0, 10, NUMBER_SLIDER, &pcs_volume);\n\n    elements[ok_pos] = construct_button(7, 13 + y_offset_buttons,\n     \"OK\", RESULT_CONFIRM);\n\n    elements[ok_pos + 1] = construct_button(15, 13 + y_offset_buttons,\n     \"Cancel\", RESULT_CANCEL);\n\n    window_height = (16 + y_offset_buttons);\n    construct_dialog(&di, \"Game Settings\", 25, (25 - window_height) / 2,\n     30, window_height, elements, num_elements, start_option);\n\n    dialog_result = run_dialog(mzx_world, &di);\n    start_option = di.current_element;\n\n    // Prevent UI keys from carrying through.\n    force_release_all_keys();\n\n    if(dialog_result == RESULT_CONFIRM)\n    {\n      audio_set_pcs_volume(pcs_volume);\n\n      if(music_volume != audio_get_music_volume())\n      {\n        audio_set_music_volume(music_volume);\n\n        if(mzx_world->active)\n          audio_set_module_volume(src_board->volume);\n      }\n\n      if(sound_volume != audio_get_sound_volume())\n        audio_set_sound_volume(sound_volume);\n\n      music ^= 1;\n      pcs ^= 1;\n\n      audio_set_pcs_on(pcs);\n      audio_set_music_on(music);\n\n      if(!music && prev_music && mzx_world->active)\n      {\n        // Turn off music and sound effects.\n        audio_end_module();\n        audio_end_sample();\n      }\n      else\n\n      if(music && !prev_music && mzx_world->active)\n      {\n        // Turn on music.\n        load_game_module(mzx_world, mzx_world->real_mod_playing, false);\n      }\n\n      mzx_world->mzx_speed = mzx_speed;\n    }\n    else\n\n    if(dialog_result == RESULT_SET_VIDEO_OUTPUT)\n    {\n      int new_video_output = choose_video_output(mzx_world,\n       available_video_outputs, num_available_video_outputs, cur_video_output);\n\n      if(new_video_output >= 0)\n      {\n        if(!change_video_output(conf, available_video_outputs[new_video_output]))\n          error(\"Failed to set renderer.\", ERROR_T_ERROR, ERROR_OPT_OK, 0x6000);\n\n        cur_video_output = get_current_video_output();\n\n        // HACK: after creating a new X11 window SDL seems to like to send the\n        // previous frame's events again sometimes, so just flush the events\n        update_event_status();\n      }\n    }\n\n#ifdef CONFIG_RENDER_GL_PROGRAM\n    else\n\n    // Shader selection\n    if(is_glsl && (dialog_result == RESULT_SET_SHADER) && shader_name[0])\n    {\n      size_t offset = strlen(shader_path) + 1;\n      boolean shader_res;\n      char *pos;\n\n      if(strlen(shader_name) > offset)\n      {\n        pos = strrchr(shader_name + offset, '.');\n\n        if(pos) *pos = 0;\n        shader_res = switch_shader(shader_name + offset);\n        if(pos) *pos = '.';\n\n        if(!shader_res)\n        {\n          // If the shader failed to load, the default should have been loaded.\n          strcpy(shader_default_text, \"<default: semisoft>\");\n          shader_name[0] = 0;\n        }\n      }\n    }\n#endif\n    destruct_dialog(&di);\n  }\n  while(dialog_result >= RESULT_KEEP_OPEN);\n\n  pop_context();\n}\n"
  },
  {
    "path": "src/settings.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __GAME_SETTINGS_H\n#define __GAME_SETTINGS_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"world_struct.h\"\n\nvoid game_settings(struct world *mzx_world);\n\n__M_END_DECLS\n\n#endif // __GAME_SETTINGS_H\n"
  },
  {
    "path": "src/sprite.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <assert.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"data.h\"\n#include \"error.h\"\n#include \"game_ops.h\"\n#include \"graphics.h\"\n#include \"idput.h\"\n#include \"sprite.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n\nstatic inline int is_blank(uint16_t c)\n{\n  int cp[4];\n  int blank = 0;\n\n  cp[3] = 0;\n\n  ec_read_char(c, (char *)cp);\n\n  blank |= cp[0];\n  blank |= cp[1];\n  blank |= cp[2];\n  blank |= cp[3];\n\n  return !blank;\n}\n\nvoid plot_sprite(struct world *mzx_world, struct sprite *cur_sprite, int color,\n int x, int y)\n{\n  /**\n   * Prior to 2.80, only one of these had to be set, and it was extremely\n   * likely at least one WOULD be set because DOS versions would not clear\n   * anything but the flags between game loads. Instead of emulating the\n   * quirks of pre-port MZX for this, just unconditionally allow placement.\n   */\n  if((cur_sprite->width && cur_sprite->height) ||\n   (mzx_world->version < VERSION_PORT))\n  {\n    cur_sprite->x = x;\n    cur_sprite->y = y;\n\n    if(color == 288)\n    {\n      cur_sprite->flags |= SPRITE_SRC_COLORS;\n    }\n    else\n    {\n      cur_sprite->flags &= ~SPRITE_SRC_COLORS;\n      cur_sprite->color = color;\n    }\n\n    if(!(cur_sprite->flags & SPRITE_INITIALIZED))\n    {\n      cur_sprite->flags |= SPRITE_INITIALIZED;\n      mzx_world->active_sprites++;\n    }\n  }\n}\n\n// Sort by zorder, then by sprite number\nstatic int compare_spr_normal(const void *dest, const void *src)\n{\n  struct sprite *spr_dest = *((struct sprite **)dest);\n  struct sprite *spr_src = *((struct sprite **)src);\n\n  int diff = spr_dest->z - spr_src->z;\n\n  return diff ? diff : (spr_dest->qsort_order - spr_src->qsort_order);\n}\n\n// Sort by zorder, then by yorder, then by sprite number\nstatic int compare_spr_yorder(const void *dest, const void *src)\n{\n  struct sprite *spr_dest = *((struct sprite **)dest);\n  struct sprite *spr_src = *((struct sprite **)src);\n\n  int diff = spr_dest->z - spr_src->z;\n  int dest_y, src_y;\n\n  if (diff != 0) return diff;\n\n  dest_y = spr_dest->y * (spr_dest->flags & SPRITE_UNBOUND ? 1 : CHAR_H);\n  src_y = spr_src->y * (spr_src->flags & SPRITE_UNBOUND ? 1 : CHAR_H);\n\n  diff = ((dest_y + spr_dest->col_y) - (src_y + spr_src->col_y));\n\n  return diff ? diff : (spr_dest->qsort_order - spr_src->qsort_order);\n}\n\nstatic inline void sort_sprites(const struct sprite **sorted_list,\n struct sprite **sprite_list, int spr_yorder)\n{\n  // Fill the sorted list with the active sprites in the beginning\n  // and the inactive sprites in the end.\n  struct sprite *cur_sprite;\n  int i;\n  int i_active;\n  int i_inactive;\n  int (*spr_compare)(const void *, const void *);\n\n  for(i = 0, i_active = 0, i_inactive = MAX_SPRITES - 1; i < MAX_SPRITES; i++)\n  {\n    cur_sprite = sprite_list[i];\n\n    // This is used to stabilize the sort\n    cur_sprite->qsort_order = i;\n\n    if(cur_sprite->flags & SPRITE_INITIALIZED)\n    {\n      sorted_list[i_active] = cur_sprite;\n      i_active++;\n    }\n    else\n    {\n      sorted_list[i_inactive] = cur_sprite;\n      i_inactive--;\n    }\n  }\n\n  if(spr_yorder)\n    spr_compare = compare_spr_yorder;\n\n  else\n    spr_compare = compare_spr_normal;\n\n  qsort(sorted_list, i_active, sizeof(struct sprite *), spr_compare);\n}\n\nvoid draw_sprites(struct world *mzx_world)\n{\n  struct board *src_board = mzx_world->current_board;\n  int start_x, start_y, offset_x, offset_y;\n  int i, x, y;\n  int src_offset;\n  int overlay_offset;\n  int src_skip;\n  int overlay_skip;\n  int draw_width, draw_height, ref_x, ref_y, screen_x, screen_y;\n  int src_width;\n  int src_height;\n  boolean use_vlayer;\n  struct sprite **sprite_list = mzx_world->sprite_list;\n  const struct sprite *sorted_list[MAX_SPRITES];\n  const struct sprite *cur_sprite;\n  uint16_t ch;\n  char color;\n  int viewport_x = src_board->viewport_x;\n  int viewport_y = src_board->viewport_y;\n  int viewport_width = src_board->viewport_width;\n  int viewport_height = src_board->viewport_height;\n  int board_width = src_board->board_width;\n  int overlay_mode = src_board->overlay_mode;\n  char *overlay = src_board->overlay;\n  char *src_chars;\n  char *src_colors;\n  uint32_t layer;\n  int draw_layer_order;\n  boolean unbound;\n  int transparent_color;\n\n  calculate_xytop(mzx_world, &screen_x, &screen_y);\n\n  sort_sprites(sorted_list, sprite_list, mzx_world->sprite_y_order);\n\n  // draw this on top of the SCREEN window.\n  for(i = 0; i < MAX_SPRITES; i++)\n  {\n    cur_sprite = sorted_list[i];\n\n    if(!(cur_sprite->flags & SPRITE_INITIALIZED))\n      continue;\n\n    if(cur_sprite->flags & SPRITE_UNBOUND)\n      unbound = true;\n    else\n      unbound = false;\n\n    ref_x = cur_sprite->ref_x;\n    ref_y = cur_sprite->ref_y;\n    offset_x = 0;\n    offset_y = 0;\n    start_x = viewport_x;\n    start_y = viewport_y;\n\n    start_x += cur_sprite->x;\n    start_y += cur_sprite->y;\n\n    if(!(cur_sprite->flags & SPRITE_STATIC))\n    {\n      start_x -= screen_x;\n      start_y -= screen_y;\n    }\n\n    draw_width = cur_sprite->width;\n    draw_height = cur_sprite->height;\n\n    // clip draw position against viewport\n    if(!unbound)\n    {\n      if(start_x < viewport_x)\n      {\n        offset_x = viewport_x - start_x;\n        draw_width -= offset_x;\n        if(draw_width < 1)\n          continue;\n\n        start_x = viewport_x;\n      }\n\n      if(start_y < viewport_y)\n      {\n        offset_y = viewport_y - start_y;\n        draw_height -= offset_y;\n        if(draw_height < 1)\n          continue;\n\n        start_y = viewport_y;\n      }\n\n      if((start_x > (viewport_x + viewport_width)) ||\n       (start_y > (viewport_y + viewport_height)))\n      {\n        continue;\n      }\n\n      if((start_x + draw_width) > (viewport_x + viewport_width))\n        draw_width = viewport_x + viewport_width - start_x;\n\n      if((start_y + draw_height) > (viewport_y + viewport_height))\n        draw_height = viewport_y + viewport_height - start_y;\n    }\n\n    if(cur_sprite->flags & SPRITE_VLAYER)\n    {\n      use_vlayer = true;\n      src_width = mzx_world->vlayer_width;\n      src_height = mzx_world->vlayer_height;\n      src_chars = mzx_world->vlayer_chars;\n      src_colors = mzx_world->vlayer_colors;\n    }\n    else\n    {\n      use_vlayer = false;\n      src_width = board_width;\n      src_height = src_board->board_height;\n      src_chars = src_board->level_param;\n      src_colors = src_board->level_color;\n    }\n\n    if(ref_x < 0)\n    {\n      draw_width += ref_x;\n      ref_x = 0;\n    }\n\n    if(ref_y < 0)\n    {\n      draw_height += ref_y;\n      ref_y = 0;\n    }\n\n    if((ref_x + draw_width) > src_width)\n      draw_width = src_width - ref_x;\n\n    if((ref_y + draw_height) > src_height)\n      draw_height = src_height - ref_y;\n\n    if(draw_width <= 0 || draw_height <= 0)\n      continue;\n\n    if(unbound)\n    {\n      int clip_x = (viewport_width + viewport_x + 1) * CHAR_W;\n      int clip_y = (viewport_height + viewport_y + 1) * CHAR_H;\n      int end_x;\n      int end_y;\n\n      // Are portions of this sprite entirely outside the viewport?\n      // If so, clip them off\n      start_x = cur_sprite->x + viewport_x * CHAR_W;\n      start_y = cur_sprite->y + viewport_y * CHAR_H;\n\n      if(!(cur_sprite->flags & SPRITE_STATIC))\n      {\n        start_x -= screen_x * CHAR_W;\n        start_y -= screen_y * CHAR_H;\n      }\n\n      end_x = start_x + (draw_width * CHAR_W);\n      end_y = start_y + (draw_height * CHAR_H);\n\n      if(start_x < -CHAR_W + 1)\n      {\n        offset_x += start_x / -CHAR_W;\n        draw_width -= start_x / -CHAR_W;\n        start_x += (start_x / -CHAR_W) * CHAR_W;\n      }\n      if(start_y < -CHAR_H + 1)\n      {\n        offset_y += start_y / -CHAR_H;\n        draw_height -= start_y / -CHAR_H;\n        start_y += (start_y / -CHAR_H) * CHAR_H;\n      }\n      if(end_x > clip_x)\n      {\n        draw_width -= (end_x - clip_x) / CHAR_W;\n        end_x = clip_x;\n      }\n      if(end_y > clip_y)\n      {\n        draw_height -= (end_y - clip_y) / CHAR_H;\n        end_y = clip_y;\n      }\n\n      // If there is nothing left of the sprite, don't draw it\n      if(draw_width < 1 || draw_height < 1)\n        continue;\n\n      // Zero out start_x and start_y for now\n      // We will then modify their values directly\n      start_x = 0;\n      start_y = 0;\n    }\n\n    if(cur_sprite->flags & SPRITE_OVER_OVERLAY)\n      draw_layer_order = LAYER_DRAWORDER_OVERLAY + 1 + i;\n    else\n      draw_layer_order = LAYER_DRAWORDER_BOARD + 1 + i;\n\n    // Only unbound sprites can have a transparent colour\n    if(unbound)\n      transparent_color = cur_sprite->transparent_color;\n    else\n      transparent_color = -1;\n\n    layer = create_layer(start_x * CHAR_W, start_y * CHAR_H, draw_width,\n     draw_height, draw_layer_order, transparent_color, cur_sprite->offset,\n     unbound);\n    select_layer(layer);\n\n    // Offset and skip value for sprite reference\n    src_offset = (ref_y + offset_y) * src_width + ref_x + offset_x;\n    src_skip = src_width - draw_width;\n\n    // Offset and skip value for overlay (as it may cover part of a sprite)\n    overlay_skip = board_width - draw_width;\n    if(overlay_mode == OVERLAY_STATIC)\n    {\n      // Static overlay\n      overlay_offset = ((start_y - viewport_y) * board_width) +\n       start_x - viewport_x;\n    }\n    else\n    {\n      // Normal overlay\n      overlay_offset = ((cur_sprite->y + offset_y) * board_width) +\n       cur_sprite->x + offset_x;\n    }\n\n    for(y = 0; y < draw_height; y++)\n    {\n      for(x = 0; x < draw_width; x++)\n      {\n        if(use_vlayer)\n        {\n          color = src_colors[src_offset];\n          ch = src_chars[src_offset];\n        }\n        else\n        {\n          color = get_id_color(src_board, src_offset);\n          ch = get_id_char(src_board, src_offset);\n        }\n\n        if(!(cur_sprite->flags & SPRITE_SRC_COLORS))\n          color = cur_sprite->color;\n\n        if(unbound || (cur_sprite->flags & SPRITE_OVER_OVERLAY) ||\n         !(overlay_mode && overlay_mode != OVERLAY_TRANSPARENT &&\n           overlay[overlay_offset] != 32))\n        {\n          if(unbound || ch != 32)\n          {\n            if((cur_sprite->flags &\n             (SPRITE_CHAR_CHECK | SPRITE_CHAR_CHECK2)) != SPRITE_CHAR_CHECK2 ||\n             !is_blank((ch + cur_sprite->offset) % PROTECTED_CHARSET_POSITION))\n            {\n              if(!unbound)\n              {\n                // This implements the legacy sprite \"transparency\" effect.\n                draw_char_bleedthru_ext(ch, color, x + start_x, y + start_y, 0, 0);\n              }\n              else\n                draw_char_to_layer(ch, color, x, y, 0, 0);\n            }\n          }\n        }\n        src_offset++;\n        overlay_offset++;\n      }\n      src_offset += src_skip;\n      overlay_offset += overlay_skip;\n    }\n\n    if(unbound)\n    {\n      // Fix the x and y positions of this sprite\n      start_x = cur_sprite->x + viewport_x * CHAR_W + offset_x * CHAR_W;\n      start_y = cur_sprite->y + viewport_y * CHAR_H + offset_y * CHAR_H;\n\n      if(!(cur_sprite->flags & SPRITE_STATIC))\n      {\n        start_x -= screen_x * CHAR_W;\n        start_y -= screen_y * CHAR_H;\n      }\n      move_layer(layer, start_x, start_y);\n    }\n  }\n}\n\n/**\n * This is legacy sprite-checking code.\n * Use it in worlds <=2.84 for maximum compat. (The real reason it was left\n * alone is because it's a dumpster fire and no one wants to touch it.)\n */\nstatic int sprite_colliding_xy_old(struct world *mzx_world,\n struct sprite *check_sprite, int x, int y)\n{\n  struct board *src_board = mzx_world->current_board;\n  int board_width = src_board->board_width;\n  int board_height = src_board->board_height;\n  int ref_x = check_sprite->ref_x + check_sprite->col_x;\n  int ref_y = check_sprite->ref_y + check_sprite->col_y;\n  int col_width = check_sprite->col_width;\n  int col_height = check_sprite->col_height;\n  int check_x = x + check_sprite->col_x;\n  int check_y = y + check_sprite->col_y;\n  struct sprite **sprite_list = mzx_world->sprite_list;\n  struct sprite *cur_sprite;\n  int colliding = 0;\n  int skip, skip2, i, i2, i3, i4, i5;\n  int bwidth, bheight;\n  int x1, x2, y1, y2;\n  int mw, mh;\n  boolean use_vlayer, use_vlayer2;\n  unsigned int x_lmask, x_gmask, y_lmask, y_gmask;\n  unsigned int xl, xg, yl, yg, wl, hl, wg, hg;\n  char *vlayer_chars = mzx_world->vlayer_chars;\n  char *level_id = src_board->level_id;\n  int *collision_list = mzx_world->collision_list;\n  int vlayer_width = mzx_world->vlayer_width;\n  int board_collide = 0;\n\n  if(!(check_sprite->flags & SPRITE_INITIALIZED))\n    return -1;\n\n  // Check against the background, will only collide against\n  // customblock for now (id 5)\n\n  if(check_sprite->flags & SPRITE_VLAYER)\n  {\n    use_vlayer = true;\n    bwidth = vlayer_width;\n    bheight = mzx_world->vlayer_height;\n  }\n  else\n  {\n    use_vlayer = false;\n    bwidth = board_width;\n    bheight = src_board->board_height;\n  }\n\n  if(ref_x < 0)\n  {\n    col_width += ref_x;\n    ref_x = 0;\n  }\n\n  if(ref_y < 0)\n  {\n    col_height += ref_y;\n    ref_y = 0;\n  }\n\n  if(check_x < 0)\n  {\n    col_width += check_x;\n    check_x = 0;\n  }\n\n  if(check_y < 0)\n  {\n    col_height += check_y;\n    check_y = 0;\n  }\n\n  if((ref_x + col_width) >= bwidth)\n    col_width = bwidth - ref_x;\n\n  if((ref_y + col_height) >= bheight)\n    col_height = bheight - ref_y;\n\n  if((check_x + col_width) >= board_width)\n    col_width = board_width - check_x;\n\n  if((check_y + col_height) >= board_height)\n    col_height = board_height - check_y;\n\n  // Check for <= 0 width or height (prevent crashing)\n  if((col_width <= 0) || (col_height <= 0))\n  {\n    mzx_world->collision_count = 0;\n    return 0;\n  }\n\n  // Scan board area\n\n  skip = board_width - col_width;\n  skip2 = bwidth - col_width;\n\n  i3 = (check_y * board_width) + check_x;\n  i4 = (ref_y * bwidth) + ref_x;\n\n  for(i = 0; i < col_height; i++)\n  {\n    for(i2 = 0; i2 < col_width; i2++)\n    {\n      // First, if ccheck is on, it won't care if the source is 32\n      int c;\n\n      if(use_vlayer)\n      {\n        c = vlayer_chars[i4];\n      }\n      else\n      {\n        c = get_id_char(src_board, i4);\n      }\n\n      if(!(check_sprite->flags & SPRITE_CHAR_CHECK) || (c != 32))\n      {\n        // if ccheck2 is on and the char is blank, don't trigger.\n        if(!(check_sprite->flags & SPRITE_CHAR_CHECK2) || !is_blank(c))\n        {\n          if(level_id[i3] == CUSTOM_BLOCK)\n          {\n            // Colliding against background\n            if(board_collide) break;\n            collision_list[colliding] = -1;\n            colliding++;\n            board_collide = 1;\n          }\n        }\n      }\n\n      i3++;\n      i4++;\n    }\n    i3 += skip;\n    i4 += skip2;\n  }\n\n  for(i = 0; i < MAX_SPRITES; i++)\n  {\n    cur_sprite = sprite_list[i];\n    if((cur_sprite == check_sprite) ||\n     !(cur_sprite->flags & SPRITE_INITIALIZED))\n      continue;\n\n    if(!(cur_sprite->col_width) || !(cur_sprite->col_height))\n     continue;\n\n    x1 = cur_sprite->x + cur_sprite->col_x;\n    x2 = x + check_sprite->col_x;\n    y1 = cur_sprite->y + cur_sprite->col_y;\n    y2 = y + check_sprite->col_y;\n    x_lmask = (signed int)(x1 - x2) >> 31;\n    x_gmask = ~x_lmask;\n    y_lmask = (signed int)(y1 - y2) >> 31;\n    y_gmask = ~y_lmask;\n    xl = (x1 & x_lmask) | (x2 & x_gmask);\n    xg = (x1 & x_gmask) | (x2 & x_lmask);\n    yl = (y1 & y_lmask) | (y2 & y_gmask);\n    yg = (y1 & y_gmask) | (y2 & y_lmask);\n    wl = (cur_sprite->col_width & x_lmask) |\n     (check_sprite->col_width & x_gmask);\n    hl = (cur_sprite->col_height & y_lmask) |\n     (check_sprite->col_height & y_gmask);\n    if((((xg - xl) - wl) & ((yg - yl) - hl)) >> 31)\n    {\n      // Does it require char/char verification?\n      if(check_sprite->flags & (SPRITE_CHAR_CHECK | SPRITE_CHAR_CHECK2))\n      {\n        // The sub rectangle is going to be somewhere within both sprites;\n        // It's going to start at the beginning of the component further\n        // along (xg/yg) which is an absolute board position; find the\n        // i5 both sprites are from this... and you will iterate in a\n        // rectangle there that is (xl + wl) - xg... the difference between\n        // where the first one ends and the second one begins\n        mw = (xl + wl) - xg;\n        mh = (yl + hl) - yg;\n        // Reuse these.. i5s. For both you must look at PHYSICAL data, that\n        // is, where the chars of the sprite itself is.\n        x1 = cur_sprite->ref_x + (xg - x1) + cur_sprite->col_x;\n        y1 = cur_sprite->ref_y + (yg - y1) + cur_sprite->col_y;\n        x2 = check_sprite->ref_x + (xg - x2) + check_sprite->col_x;\n        y2 = check_sprite->ref_y + (yg - y2) + check_sprite->col_y;\n        // Check to make sure draw area doesn't go over.\n        wg = (cur_sprite->col_width & x_gmask) |\n         (check_sprite->col_width & x_lmask);\n        hg = (cur_sprite->col_height & y_gmask) |\n         (check_sprite->col_height & y_lmask);\n\n        if((unsigned int)mw > wg)\n        {\n          mw = wg;\n        }\n        if((unsigned int)mh > hg)\n        {\n          mh = hg;\n        }\n\n        // Now iterate through the rect; if both are NOT 32 then a\n        // collision is flagged.\n\n        if(cur_sprite->flags & SPRITE_VLAYER)\n        {\n          use_vlayer2 = true;\n          i4 = (y1 * vlayer_width) + x1;\n          skip = vlayer_width - mw;\n        }\n        else\n        {\n          use_vlayer2 = false;\n          i4 = (y1 * board_width) + x1;\n          skip = board_width - mw;\n        }\n\n        i5 = (y2 * bwidth) + x2;\n        skip2 = bwidth - mw;\n\n        // If ccheck mode 2 is on it should do a further strength check.\n        if(check_sprite->flags & SPRITE_CHAR_CHECK2)\n        {\n          for(i2 = 0; i2 < mh; i2++)\n          {\n            for(i3 = 0; i3 < mw; i3++)\n            {\n              char c1, c2;\n              if(!use_vlayer2)\n              {\n                c1 = get_id_char(src_board, i4);\n              }\n              else\n              {\n                c1 = vlayer_chars[i4];\n              }\n              if(!use_vlayer)\n              {\n                c2 = get_id_char(src_board, i5);\n              }\n              else\n              {\n                c2 = vlayer_chars[i5];\n              }\n              if((c1 != 32) && (c2 != 32))\n              {\n                // Collision.. maybe.\n                // Neither char may be blank\n                if(!(is_blank(c1) || is_blank(c2)))\n                {\n                  collision_list[colliding] = i;\n                  colliding++;\n\n                  goto next;\n                }\n              }\n              i4++;\n              i5++;\n            }\n            i4 += skip;\n            i5 += skip2;\n          }\n        }\n        else\n        {\n          for(i2 = 0; i2 < mh; i2++)\n          {\n            for(i3 = 0; i3 < mw; i3++)\n            {\n              char c1, c2;\n              if(!use_vlayer2)\n              {\n                c1 = get_id_char(src_board, i4);\n              }\n              else\n              {\n                c1 = vlayer_chars[i4];\n              }\n              if(!use_vlayer)\n              {\n                c2 = get_id_char(src_board, i5);\n              }\n              else\n              {\n                c2 = vlayer_chars[i5];\n              }\n              if((c1 != 32) && (c2 != 32))\n              {\n                // Collision!\n                collision_list[colliding] = i;\n                colliding++;\n\n                goto next;\n              }\n              i4++;\n              i5++;\n            }\n            i4 += skip;\n            i5 += skip;\n          }\n        }\n      }\n      else\n      {\n        collision_list[colliding] = i;\n        colliding++;\n      }\n\n      next:\n      continue;\n    }\n  }\n\n  mzx_world->collision_count = colliding;\n  return colliding;\n}\n\nstruct rect {\n  int x, y, w, h;\n};\n\nstatic inline struct rect rectangle(int x, int y, int w, int h)\n{\n  struct rect r = {x, y, w, h};\n  return r;\n}\n\nstatic inline boolean rectangle_overlap(struct rect a,\n struct rect b)\n{\n  //debug(\"rectangle_overlap(%d, %d, %d, %d, %d, %d, %d, %d)\\n\",\n  // a.x, a.y, a.w, a.h, b.x, b.y, b.w, b.h);\n  if(a.x < b.x + b.w &&\n     a.x + a.w > b.x &&\n     a.y < b.y + b.h &&\n     a.y + a.h > b.y)\n    return true;\n  return false;\n}\n\nstatic inline boolean constrain_rectangle(struct rect a,\n struct rect *b)\n{\n  // Constrain rectangle b to only cover the portion that exists within\n  // rectangle a\n\n  // Returns true if there is any of rectangle b left afterwards\n  if(b->w <= 0 || b->h <= 0)\n    return false;\n\n  if(b->x < a.x)\n  {\n    b->w -= (a.x - b->x);\n    b->x += (a.x - b->x);\n    if(b->w <= 0)\n      return false;\n  }\n  if(b->y < a.y)\n  {\n    b->h -= (a.y - b->y);\n    b->y += (a.y - b->y);\n    if(b->h <= 0)\n      return false;\n  }\n  if(b->x + b->w > a.x + a.w)\n  {\n    b->w -= (b->x + b->w) - (a.x + a.w);\n    if(b->w <= 0)\n      return false;\n  }\n  if(b->y + b->h > a.y + a.h)\n  {\n    b->h -= (b->y + b->h) - (a.y + a.h);\n    if(b->h <= 0)\n      return false;\n  }\n  return true;\n}\n\nstatic inline struct rect sprite_rectangle(const struct sprite *spr)\n{\n  struct rect sprite_rect = rectangle(spr->x, spr->y, spr->width, spr->height);\n\n  // Multiply out the coords of unbound sprites\n  if(!(spr->flags & SPRITE_UNBOUND))\n  {\n    sprite_rect.x *= CHAR_W;\n    sprite_rect.y *= CHAR_H;\n  }\n  sprite_rect.w *= CHAR_W;\n  sprite_rect.h *= CHAR_H;\n\n  return sprite_rect;\n}\n\nstatic inline struct rect collision_rectangle(const struct sprite *spr)\n{\n  struct rect collision_rect = rectangle(\n    spr->col_x,\n    spr->col_y,\n    spr->col_width,\n    spr->col_height\n  );\n\n  if(!(spr->flags & SPRITE_UNBOUND))\n  {\n    collision_rect.x *= CHAR_W;\n    collision_rect.y *= CHAR_H;\n    collision_rect.w *= CHAR_W;\n    collision_rect.h *= CHAR_H;\n  }\n  return collision_rect;\n}\n\nstatic inline struct rect board_rectangle(struct rect r)\n{\n  // Get the containing rectangle in board chars of an input rectangle in pixels\n  int x1 = r.x;\n  int y1 = r.y;\n  int x2 = r.x + r.w - 1;\n  int y2 = r.y + r.h - 1;\n\n  return rectangle(\n    x1 / CHAR_W,\n    y1 / CHAR_H,\n    x2 / CHAR_W - x1 / CHAR_W + 1,\n    y2 / CHAR_H - y1 / CHAR_H + 1\n  );\n}\n\nboolean sprite_at_xy(struct world *mzx_world, struct sprite *spr, int x, int y)\n{\n  struct rect sprite_rect;\n  struct rect pos_rect;\n\n  // If the sprite isn't on, this instantly fails\n  if(!(spr->flags & SPRITE_INITIALIZED))\n    return false;\n\n  sprite_rect = sprite_rectangle(spr);\n  pos_rect = rectangle(x * CHAR_W, y * CHAR_H, CHAR_W, CHAR_H);\n\n  // Static sprite collision occurs at its apparent position from 2.93 onward.\n  if((spr->flags & SPRITE_STATIC) && mzx_world->version >= V293)\n  {\n    int screen_x, screen_y;\n    calculate_xytop(mzx_world, &screen_x, &screen_y);\n    sprite_rect.x += screen_x * CHAR_W;\n    sprite_rect.y += screen_y * CHAR_H;\n  }\n\n  if(rectangle_overlap(sprite_rect, pos_rect))\n    return true;\n  return false;\n}\n\nstatic inline void get_sprite_tile(struct world *mzx_world,\n const struct sprite *spr, int x, int y, int *ch, int *col)\n{\n  struct board *cur_board;\n  int offset;\n\n  if(spr->flags & SPRITE_VLAYER)\n  {\n    if(x >= 0 && x < mzx_world->vlayer_width &&\n     y >= 0 && y < mzx_world->vlayer_height)\n    {\n      offset = x + y * mzx_world->vlayer_width;\n\n      *ch = mzx_world->vlayer_chars[offset];\n      if(col) *col = mzx_world->vlayer_colors[offset];\n    }\n    else\n    {\n      *ch = -1;\n      if(col) *col = -1;\n    }\n  }\n  else\n  {\n    cur_board = mzx_world->current_board;\n    if(x >= 0 && x < cur_board->board_width &&\n     y >= 0 && y < cur_board->board_height)\n    {\n      offset = x + y * cur_board->board_width;\n\n      *ch = get_id_char(cur_board, offset);\n      if(col) *col = get_id_color(cur_board, offset);\n    }\n    else\n    {\n      *ch = -1;\n      if(col) *col = -1;\n    }\n  }\n\n  if(col && !(spr->flags & SPRITE_SRC_COLORS))\n    *col = spr->color;\n}\n\nstatic inline boolean collision_char(struct world *mzx_world,\n const struct sprite *spr, char flags, int x, int y)\n{\n  int ch;\n  get_sprite_tile(mzx_world, spr, x, y, &ch, NULL);\n\n  if(ch == -1) return false;\n\n  ch = (ch + spr->offset) % PROTECTED_CHARSET_POSITION;\n\n  if((flags & SPRITE_CHAR_CHECK) && ch != 32) return true;\n  if((flags & SPRITE_CHAR_CHECK2) && !is_blank(ch)) return true;\n  return false;\n}\n\nstatic inline boolean collision_in(struct world *mzx_world,\n const struct sprite *spr, char flags, struct rect c)\n{\n  int cx, cy, c2x, c2y;\n  //debug(\"spr collision_in(%d,%d,%d,%d)\\n\", c.x, c.y, c.w, c.h);\n\n  if(!(flags & (SPRITE_CHAR_CHECK | SPRITE_CHAR_CHECK2))) return true;\n  if((flags & SPRITE_PIXCHECK) == SPRITE_PIXCHECK) return true;\n\n  cx = c.x / CHAR_W + spr->ref_x;\n  cy = c.y / CHAR_H + spr->ref_y;\n  if(collision_char(mzx_world, spr, flags, cx, cy)) return true;\n\n  c2x = (c.x + c.w - 1) / CHAR_W + spr->ref_x;\n  if(c2x != cx && collision_char(mzx_world, spr, flags, c2x, cy)) return true;\n\n  c2y = (c.y + c.h - 1) / CHAR_H + spr->ref_y;\n  if(c2y != cy && collision_char(mzx_world, spr, flags, cx, c2y)) return true;\n\n  if((c2x != cx || c2y != cy) &&\n   collision_char(mzx_world, spr, flags, c2x, c2y)) return true;\n\n  return false;\n}\n\nstruct mask\n{\n  struct rect dim;\n  uint8_t *mapping;\n  uint8_t *data;\n};\n\nstatic inline struct mask allocate_mask(const struct sprite *spr)\n{\n  struct mask m;\n  m.dim = sprite_rectangle(spr);\n  m.mapping = ccalloc(spr->width * spr->height, 1);\n  m.data = cmalloc(spr->width * spr->height * CHAR_SIZE);\n  return m;\n}\n\nstatic inline struct mask null_mask(void)\n{\n  struct mask m = {rectangle(0, 0, 0, 0), NULL, NULL};\n  return m;\n}\n\nstatic inline void destroy_mask(struct mask m)\n{\n  free(m.mapping);\n  free(m.data);\n}\n\nstatic inline int mask_get_pixel(struct world *mzx_world,\n const struct sprite *spr, struct mask m, unsigned int pixel_x, unsigned int pixel_y)\n{\n  unsigned int ch = (pixel_y / CHAR_H * spr->width + pixel_x / CHAR_W);\n\n  if(!m.mapping[ch])\n  {\n    uint8_t *output = &m.data[ch * CHAR_SIZE];\n    int tcol = spr->transparent_color;\n    int x = pixel_x / CHAR_W;\n    int y = pixel_y / CHAR_H;\n    int chr;\n    int col;\n\n    m.mapping[ch] = 1;\n    get_sprite_tile(mzx_world, spr, x + spr->ref_x, y + spr->ref_y, &chr, &col);\n\n    if(chr != -1)\n    {\n      if(get_char_visible_bitmask((chr + spr->offset) % PRO_CH, col, tcol, output))\n        m.mapping[ch] = 2;\n    }\n  }\n\n  if(m.mapping[ch] < 2)\n    return 0;\n\n  return m.data[ch * CHAR_SIZE + (pixel_y % CHAR_H)] & (0x80 >> (pixel_x % CHAR_W));\n}\n\nstatic inline boolean collision_pix_in(struct world *mzx_world,\n const struct sprite *spr, struct mask m, struct rect c)\n{\n  unsigned int px, py;\n  int x, y;\n\n  if((spr->flags & SPRITE_PIXCHECK) != SPRITE_PIXCHECK)\n    return true;\n\n  // mask_get_pixel relies on these relations being true to speed up its math\n  // with unsigned int params. Furthermore, negative ints would break its math.\n  assert(c.x >= m.dim.x);\n  assert(c.y >= m.dim.y);\n\n  // Pixel by pixel collision check\n  for(y = c.y; y < c.y + c.h; y++)\n  {\n    py = y - m.dim.y;\n    for(x = c.x; x < c.x + c.w; x++)\n    {\n      px = x - m.dim.x;\n      if(mask_get_pixel(mzx_world, spr, m, px, py))\n        return true;\n    }\n  }\n  return false;\n}\n\nstatic inline boolean collision_pix_between(struct world *mzx_world,\n const struct sprite *spr, struct mask spr_m,\n const struct sprite *targ, struct mask targ_m, struct rect c)\n{\n  // Determine if there is a collision between two sprites with\n  // overlapping chars. Unbound sprites in CCHECK mode 3 require pixel checking.\n\n  if((spr->flags & SPRITE_PIXCHECK) != SPRITE_PIXCHECK)\n  {\n    // Automatic success, since we know chars are overlapping\n    if((targ->flags & SPRITE_PIXCHECK) != SPRITE_PIXCHECK)\n      return true;\n\n    // Only target sprite does a pixel check\n    return collision_pix_in(mzx_world, targ, targ_m, c);\n  }\n  else\n\n  if((targ->flags & SPRITE_PIXCHECK) != SPRITE_PIXCHECK)\n  {\n    // Only the checked sprite does a pixel check\n    return collision_pix_in(mzx_world, spr, spr_m, c);\n  }\n\n  else\n  {\n    // Both sprites need a pixel check\n    int x, y, sx, sy, tx, ty;\n\n    // Pixel by pixel collision check\n    for(y = c.y; y < c.y + c.h; y++)\n    {\n      sy = y - spr_m.dim.y;\n      ty = y - targ_m.dim.y;\n\n      for(x = c.x; x < c.x + c.w; x++)\n      {\n        sx = x - spr_m.dim.x;\n        tx = x - targ_m.dim.x;\n        if(mask_get_pixel(mzx_world, spr, spr_m, sx, sy) &&\n         mask_get_pixel(mzx_world, targ, targ_m, tx, ty))\n          return true;\n      }\n    }\n  }\n  return false;\n}\n\nint sprite_colliding_xy(struct world *mzx_world, struct sprite *spr,\n int x, int y)\n{\n  struct sprite collision_sprite = *spr;\n  struct sprite *target_spr;\n  struct rect sprite_rect;\n  struct rect col_rect;\n  struct rect col_board_rect;\n  struct rect board_rect;\n  struct rect board_full_rect;\n  struct rect target_spr_rect;\n  struct rect target_col_rect;\n  struct rect check_rect;\n  struct rect check_rect_tr;\n  int *collision_list = mzx_world->collision_list;\n  int *collisions = &mzx_world->collision_count;\n  struct board *cur_board = mzx_world->current_board;\n  char *level_id = cur_board->level_id;\n  int board_width = cur_board->board_width;\n  int screen_x = 0;\n  int screen_y = 0;\n  int bx, by;\n  int sprite_idx;\n  int cx, cy;\n  boolean sprite_collided;\n  char target_flags;\n  struct mask spr_mask = null_mask();\n  struct mask target_mask = null_mask();\n  boolean spr_mask_allocated = false;\n  boolean target_mask_allocated;\n\n  if(mzx_world->version < V290)\n    return sprite_colliding_xy_old(mzx_world, spr, x, y);\n\n  // Adjust static sprites to match their apparent positions onscreen.\n  if(mzx_world->version >= V293)\n  {\n    calculate_xytop(mzx_world, &screen_x, &screen_y);\n    screen_x *= CHAR_W;\n    screen_y *= CHAR_H;\n  }\n\n  board_rect = rectangle(\n    0,\n    0,\n    cur_board->board_width,\n    cur_board->board_height\n  );\n\n  board_full_rect = rectangle(\n    0,\n    0,\n    cur_board->board_width * CHAR_W,\n    cur_board->board_height * CHAR_H\n  );\n\n  collision_sprite.x = x;\n  collision_sprite.y = y;\n\n  //debug(\"sprite_colliding_xy(%d,%d,%d,%d)\\n\",\n  // spr->x, spr->y, spr->width, spr->height);\n\n  if(!(spr->flags & SPRITE_INITIALIZED))\n    return -1;\n\n  // Reset the collision list. To match old behaviour, this only happens\n  // if the initialisation check passes\n  *collisions = 0;\n\n  sprite_rect = sprite_rectangle(&collision_sprite);\n  col_rect = collision_rectangle(&collision_sprite);\n  if(spr->flags & SPRITE_STATIC)\n  {\n    sprite_rect.x += screen_x;\n    sprite_rect.y += screen_y;\n  }\n  col_rect.x += sprite_rect.x;\n  col_rect.y += sprite_rect.y;\n\n  if(!(spr->flags & SPRITE_UNBOUND))\n    if(!constrain_rectangle(board_full_rect, &col_rect))\n      return -1;\n\n  if((spr->flags & SPRITE_PIXCHECK) == SPRITE_PIXCHECK)\n  {\n    if(!constrain_rectangle(sprite_rect, &col_rect))\n      return -1;\n\n    spr_mask = allocate_mask(&collision_sprite);\n    spr_mask_allocated = true;\n  }\n\n  // Check the contents of the board\n  col_board_rect = board_rectangle(col_rect);\n  if(constrain_rectangle(board_rect, &col_board_rect))\n  {\n    for(by = col_board_rect.y; by < col_board_rect.y + col_board_rect.h; by++)\n    {\n      for(bx = col_board_rect.x; bx < col_board_rect.x + col_board_rect.w; bx++)\n      {\n        if(level_id[by * board_width + bx] != CUSTOM_BLOCK)\n          continue;\n\n        check_rect = rectangle(bx * CHAR_W, by * CHAR_H, CHAR_W, CHAR_H);\n\n        if(!constrain_rectangle(col_rect, &check_rect))\n          continue;\n\n        check_rect_tr = check_rect;\n        check_rect_tr.x -= sprite_rect.x;\n        check_rect_tr.y -= sprite_rect.y;\n\n        if(!collision_in(mzx_world, spr, spr->flags, check_rect_tr))\n          continue;\n\n        if(!collision_pix_in(mzx_world, spr, spr_mask, check_rect))\n          continue;\n\n        collision_list[(*collisions)++] = -1;\n        break;\n      }\n\n      if(*collisions)\n        break;\n    }\n  }\n\n  for(sprite_idx = 0; sprite_idx < MAX_SPRITES; sprite_idx++)\n  {\n    target_spr = mzx_world->sprite_list[sprite_idx];\n\n    if(!(target_spr->flags & SPRITE_INITIALIZED))\n      continue;\n\n    if(target_spr == spr)\n      continue;\n\n    target_spr_rect = sprite_rectangle(target_spr);\n    target_col_rect = collision_rectangle(target_spr);\n    if(target_spr->flags & SPRITE_STATIC)\n    {\n      target_spr_rect.x += screen_x;\n      target_spr_rect.y += screen_y;\n    }\n    //debug(\"sprite #%d a: %d %d %d %d\\n\",\n    // target_rect.x, target_rect.y, target_rect.w, target_rect.h);\n    target_col_rect.x += target_spr_rect.x;\n    target_col_rect.y += target_spr_rect.y;\n\n    if(!(target_spr->flags & SPRITE_UNBOUND))\n      if(!constrain_rectangle(board_full_rect, &target_col_rect))\n        continue;\n\n    // ccheck between bound sprites works by looking at the flags of the\n    // sprite doing the collision checking only. This is to maintain backwards\n    // compatibility with code that expects this.\n    // The reason this is not tied into a version check is to allow old code\n    // to continue working even in a new world.\n    // If either sprite is unbound we switch to sensible behaviour (using the\n    // ccheck of each sprite to determine its respective collision mask).\n\n    if((spr->flags | target_spr->flags) & SPRITE_UNBOUND)\n      target_flags = target_spr->flags;\n    else\n      target_flags = spr->flags;\n\n    // Get the overlapping area of the two sprite collision rectangles.\n    // If the collision rectangles aren't overlapping at all, skip this sprite.\n    if(!constrain_rectangle(col_rect, &target_col_rect))\n      continue;\n\n    // Look closer to see if these sprites are actually colliding.\n    target_mask_allocated = false;\n    if((target_spr->flags & SPRITE_PIXCHECK) == SPRITE_PIXCHECK)\n    {\n      // In unbound sprite CCHECK mode 3, we're checking for a collision against\n      // the sprite's visible pixels. Make sure the collision is in the sprite's\n      // visible area.\n      if(!constrain_rectangle(target_spr_rect, &target_col_rect))\n        continue;\n\n      target_mask = allocate_mask(target_spr);\n      target_mask_allocated = true;\n    }\n\n    sprite_collided = false;\n\n    for(cy = col_rect.y; cy < col_rect.y + col_rect.h; cy += CHAR_H)\n    {\n      for(cx = col_rect.x; cx < col_rect.x + col_rect.w; cx += CHAR_W)\n      {\n        check_rect = rectangle(cx, cy, CHAR_W, CHAR_H);\n\n        if(!constrain_rectangle(target_col_rect, &check_rect))\n          continue;\n\n        check_rect_tr = check_rect;\n        check_rect_tr.x -= sprite_rect.x;\n        check_rect_tr.y -= sprite_rect.y;\n\n        if(!collision_in(mzx_world, spr, spr->flags, check_rect_tr))\n          continue;\n\n        check_rect_tr = check_rect;\n        check_rect_tr.x -= target_spr_rect.x;\n        check_rect_tr.y -= target_spr_rect.y;\n\n        if(!collision_in(mzx_world, target_spr, target_flags, check_rect_tr))\n          continue;\n\n        if(!collision_pix_between(mzx_world, spr, spr_mask,\n         target_spr, target_mask, check_rect))\n          continue;\n\n        collision_list[(*collisions)++] = sprite_idx;\n        sprite_collided = true;\n        break;\n      }\n\n      // We've already seen a collision with this sprite\n      if(sprite_collided)\n        break;\n    }\n\n    if(target_mask_allocated)\n      destroy_mask(target_mask);\n  }\n\n  if(spr_mask_allocated)\n    destroy_mask(spr_mask);\n\n  return *collisions;\n}\n"
  },
  {
    "path": "src/sprite.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __SPRITE_H\n#define __SPRITE_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"sprite_struct.h\"\n#include \"world_struct.h\"\n\nenum\n{\n  SPRITE_INITIALIZED  = (1 << 0), // Is active\n  SPRITE_CHAR_CHECK   = (1 << 1), // CHAR_CHECK flag 1 (see below)\n  SPRITE_OVER_OVERLAY = (1 << 2), // Is drawn over the overlay\n  SPRITE_SRC_COLORS   = (1 << 3), // Uses \"natural\" colors\n  SPRITE_STATIC       = (1 << 4), // Is static relative to viewport\n  SPRITE_CHAR_CHECK2  = (1 << 5), // CHAR_CHECK flag 2 (see below)\n  SPRITE_VLAYER       = (1 << 6), // References the vlayer\n  SPRITE_UNBOUND      = (1 << 7), // Uses pixel positioning and collision\n  SPRITE_OFF_ON_EXIT  = (1 << 8), // Turn off sprite when switching boards\n\n  // Internal flag combination for pixel collision.\n  SPRITE_PIXCHECK     = SPRITE_UNBOUND | SPRITE_CHAR_CHECK | SPRITE_CHAR_CHECK2,\n};\n\n/**\n * Classic sprite CHAR_CHECK:\n *\n * neither:  sprite collides with all of its chars\n * 1 set:    sprite doesn't collide when checking against char 32\n * 2 set:    sprite doesn't collide when checking against blank chars\n *\n * Additionally, if flag 2 is set, blank chars will not be drawn.\n */\n\n/**\n * Unbound sprite CHAR_CHECK (if either colliding sprite is unbound):\n *\n * neither:  OTHER sprites collide with its full collision box\n * 1 set:    OTHER sprites won't collide when checking against char 32\n * 2 set:    OTHER sprites won't collide when checking against blank chars\n * 1 and 2:  Only visible pixels can be collided against in this sprite.\n */\n\nvoid plot_sprite(struct world *mzx_world, struct sprite *cur_sprite, int color,\n int x, int y);\nvoid draw_sprites(struct world *mzx_world);\nboolean sprite_at_xy(struct world *mzx_world, struct sprite *cur_sprite, int x, int y);\nint sprite_colliding_xy(struct world *mzx_world, struct sprite *check_sprite,\n int x, int y);\n\n__M_END_DECLS\n\n#endif // __SPRITE_H\n"
  },
  {
    "path": "src/sprite_struct.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __SPRITE_STRUCT_H\n#define __SPRITE_STRUCT_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#define MAX_SPRITES         256\n\nstruct sprite\n{\n  int x;\n  int y;\n  int ref_x;\n  int ref_y;\n  char color;\n  unsigned int flags;\n  unsigned int width;\n  unsigned int height;\n  signed int col_x;\n  signed int col_y;\n  unsigned int col_width;\n  unsigned int col_height;\n  int transparent_color;\n  int offset;\n  int qsort_order;\n  int z;\n};\n\nstruct collision_list\n{\n  int num;\n  int collisions[MAX_SPRITES];\n};\n\n__M_END_DECLS\n\n#endif // __SPRITE_STRUCT_H\n"
  },
  {
    "path": "src/str.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <ctype.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"str.h\"\n\n#include \"counter.h\"\n#include \"error.h\"\n#include \"graphics.h\"\n#include \"memcasecmp.h\"\n#include \"rasm.h\"\n#include \"robot.h\"\n#include \"util.h\"\n#include \"world.h\"\n#include \"world_struct.h\"\n#include \"io/vio.h\"\n\n/**\n * TODO: String lookups are currently case-insensitive, which is somewhat of\n * a performance concern. A good future (3.xx) feature might be to lowercase\n * all string names.\n */\n\n#ifdef CONFIG_COUNTER_HASH_TABLES\n#include \"hashtable.h\"\nHASH_SET_INIT(STRING, struct string *, name, name_length)\n#endif\n\n// Please only use string literals with this, thanks.\n\n#define special_name(n)                                         \\\n  ((src_length == (sizeof(n) - 1)) &&                           \\\n   !strncasecmp(src_value, n, sizeof(n) - 1))                   \\\n\n#define special_name_partial(n)                                 \\\n  ((src_length >= (int)(sizeof(n) - 1)) &&                      \\\n   !strncasecmp(src_value, n, sizeof(n) - 1))                   \\\n\n\nstatic unsigned int get_board_x_board_y_offset(struct world *mzx_world, int id)\n{\n  int board_x = get_counter(mzx_world, \"board_x\", id);\n  int board_y = get_counter(mzx_world, \"board_y\", id);\n\n  board_x = CLAMP(board_x, 0, mzx_world->current_board->board_width - 1);\n  board_y = CLAMP(board_y, 0, mzx_world->current_board->board_height - 1);\n\n  return board_y * mzx_world->current_board->board_width + board_x;\n}\n\nstatic struct robot *get_robot_by_id(struct world *mzx_world, int id)\n{\n  if(id >= 0 && id <= mzx_world->current_board->num_robots)\n    return mzx_world->current_board->robot_list[id];\n  else\n    return NULL;\n}\n\nstatic struct string *find_string(struct string_list *string_list,\n const char *name, int *next)\n{\n  struct string *current = NULL;\n\n#if defined(CONFIG_COUNTER_HASH_TABLES)\n  size_t name_length = strlen(name);\n  HASH_FIND(STRING, string_list->hash_table, name, name_length, current);\n  *next = string_list->num_strings;\n  return current;\n\n#else\n  struct string **base = string_list->strings;\n  int bottom = 0, top = (string_list->num_strings) - 1, middle = 0;\n  int cmpval = 0;\n\n  while(bottom <= top)\n  {\n    middle = (top + bottom) / 2;\n    current = base[middle];\n    cmpval = strcasecmp(name, current->name);\n\n    if(cmpval > 0)\n    {\n      bottom = middle + 1;\n    }\n    else\n\n    if(cmpval < 0)\n    {\n      top = middle - 1;\n    }\n    else\n    {\n      *next = middle;\n      return current;\n    }\n  }\n\n  if(cmpval > 0)\n    *next = middle + 1;\n  else\n    *next = middle;\n\n  return NULL;\n#endif\n}\n\nstatic size_t get_string_alloc_size(size_t name_length)\n{\n  // Attempt to reclaim any padding bytes at the end of the struct...\n  return MAX(sizeof(struct string),\n   offsetof(struct string, name) + name_length + 1);\n}\n\n/**\n * Allocate a new string and initialize its name, length, and pointer fields.\n * This function does not add the new string to the string list or initialize\n * its value.\n */\nstatic struct string *allocate_new_string(const char *name, size_t name_length,\n size_t length)\n{\n  struct string *dest = (struct string *)cmalloc(get_string_alloc_size(name_length));\n  char *value = (char *)cmalloc(MAX(length, 1));\n  if(!dest || !value)\n  {\n    free(dest);\n    free(value);\n    return NULL;\n  }\n\n  memcpy(dest->name, name, name_length);\n  dest->name[name_length] = 0;\n\n  dest->name_length = name_length;\n  dest->allocated_length = length;\n  dest->length = length;\n  dest->value = value;\n  return dest;\n}\n\n/**\n * Create a named string in the string list and preallocate it to the given\n * length. Returns NULL if the strings list is full.\n */\nstatic struct string *add_string_preallocate(struct string_list *string_list,\n const char *name, size_t length, unsigned int position)\n{\n  unsigned int count = string_list->num_strings;\n  unsigned int allocated = string_list->num_strings_allocated;\n  size_t name_length = strlen(name);\n  struct string **base = string_list->strings;\n  struct string *dest;\n\n  // Need a reallocation?\n  if(count == allocated)\n  {\n    if(allocated)\n    {\n      // Gracefully fail if this tries to go over 2b...\n      if(allocated >= (size_t)(INT32_MAX))\n        return NULL;\n\n      allocated *= 2;\n    }\n    else\n      allocated = MIN_STRING_ALLOCATE;\n\n    base = (struct string **)crealloc(base, sizeof(struct string *) * allocated);\n    if(!base)\n      return NULL;\n\n    string_list->strings = base;\n    string_list->num_strings_allocated = allocated;\n  }\n\n  // Doesn't exist, so create it\n  // First make space for it if it's not at the top\n  if(position != count)\n  {\n    base += position;\n    memmove((char *)(base + 1), (char *)base,\n     (count - position) * sizeof(struct string *));\n  }\n\n  dest = allocate_new_string(name, name_length, length);\n  if(!dest)\n    return NULL;\n\n  // Initialize the value to zero.\n  if(length > 0)\n    memset(dest->value, ' ', length);\n\n  string_list->strings[position] = dest;\n  string_list->num_strings = count + 1;\n\n#ifdef CONFIG_COUNTER_HASH_TABLES\n  HASH_ADD(STRING, string_list->hash_table, dest);\n#endif\n\n  return dest;\n}\n\n/**\n * Reallocate an existing string.\n */\nstatic struct string *reallocate_string(struct string_list *string_list,\n struct string *src, int pos, size_t length)\n{\n  char *value = (char *)crealloc(src->value, MAX(length, 1));\n  if(!value)\n    return NULL;\n\n  src->value = value;\n\n  // any new bits of the string should be space filled\n  // versions up to and including 2.81h used to fill this with garbage\n  if(src->allocated_length < length)\n  {\n    memset(&src->value[src->allocated_length], ' ',\n     length - src->allocated_length);\n  }\n\n  src->allocated_length = length;\n  return src;\n}\n\n/**\n * Set a string's length and reallocate it if necessary.\n * If the string does not exist, it will be created.\n * Returns false if a string could not be created, otherwise true.\n */\nstatic boolean force_string_length(struct string_list *string_list,\n const char *name, int next, struct string **str, size_t *length)\n{\n  if(*length > MAX_STRING_LEN)\n    *length = MAX_STRING_LEN;\n\n  if(!*str)\n  {\n    *str = add_string_preallocate(string_list, name, *length, next);\n    if(!*str)\n      return false;\n  }\n  else\n\n  if(*length > (*str)->allocated_length)\n  {\n    *str = reallocate_string(string_list, *str, next, *length);\n    if(!*str)\n      return false;\n  }\n\n  /* Wipe string if the length has increased but not the allocated memory */\n  if(*length > (*str)->length)\n    memset(&((*str)->value[(*str)->length]), ' ', (*length) - (*str)->length);\n\n  return true;\n}\n\n/**\n * Bound a splice of a string and/or truncate a string.\n * If the string does not exist, it will be created.\n * Returns false if a string could not be created, otherwise true.\n */\nstatic boolean force_string_splice(struct string_list *string_list,\n const char *name, int next, struct string **str, size_t s_length,\n size_t offset, boolean offset_specified, size_t *size, boolean size_specified)\n{\n  if(!force_string_length(string_list, name, next, str, &s_length))\n    return false;\n\n  if((*size == 0 && !size_specified) || *size > s_length)\n    *size = s_length;\n\n  if((offset == 0 && !offset_specified) || (offset + *size > (*str)->length))\n  {\n    if(offset + *size > (*str)->length)\n    {\n      size_t length = offset + *size;\n      if(!force_string_length(string_list, name, next, str, &length))\n        return false;\n\n      (*str)->length = length;\n    }\n    else\n      (*str)->length = offset + *size;\n  }\n  return true;\n}\n\n/**\n * Copy an external char buffer over a string or string splice (memcpy).\n * If the source of the copy is another string, use force_string_move instead.\n * If the string does not exist, it will be created.\n * Returns false if a string could not be created, otherwise true.\n */\nstatic boolean force_string_copy(struct string_list *string_list,\n const char *name, int next, struct string **str, size_t s_length,\n size_t offset, boolean offset_specified, size_t *size, boolean size_specified,\n const char *src)\n{\n  if(!force_string_splice(string_list, name, next, str, s_length,\n   offset, offset_specified, size, size_specified))\n    return false;\n\n  if(offset <= (*str)->length - *size)\n    memcpy((*str)->value + offset, src, *size);\n\n  return true;\n}\n\n/**\n * Copy a char buffer over a string or string splice safely (memmove). This\n * function is guaranteed to work if the source is a substring of or overlaps\n * the destination. Otherwise, it is equivalent to force_string_copy.\n *\n * If the string does not exist, it will be created.\n * Returns false if a string could not be created, otherwise true.\n */\nstatic boolean force_string_move(struct string_list *string_list,\n const char *name, int next, struct string **str, size_t s_length,\n size_t offset, boolean offset_specified, size_t *size, boolean size_specified,\n const char *src)\n{\n  boolean src_dest_match = false;\n  ptrdiff_t off = 0;\n\n  if(*str)\n  {\n    off = src - (*str)->value;\n    if(off >= 0 && off < (ptrdiff_t)(*str)->length)\n      src_dest_match = true;\n  }\n\n  if(!force_string_splice(string_list, name, next, str, s_length,\n   offset, offset_specified, size, size_specified))\n    return false;\n\n  if(src_dest_match)\n    src = (*str)->value + off;\n\n  if(offset <= (*str)->length - *size)\n    memmove((*str)->value + offset, src, *size);\n\n  return true;\n}\n\nstatic void get_string_dot_value(char *dot_ptr, int *index,\n size_t *size, boolean *index_specified)\n{\n  char *error;\n\n  if(dot_ptr[0])\n  {\n    // Index\n    *index = strtol(dot_ptr, &error, 10);\n\n    // Multi-byte index\n    if(error[0] == '#' && error[1])\n    {\n      int result = strtoul(error + 1, &error, 10);\n\n      *size = CLAMP(result, 1, 4);\n    }\n\n    if(!error[0])\n    {\n      *index_specified = true;\n    }\n  }\n}\n\nstatic boolean get_string_real_index(struct string *src, int index,\n size_t *real)\n{\n  // Handle negative indexing\n  if(index < 0)\n  {\n    // Negative indexes only work with existing strings\n    if(!src)\n      return true;\n\n    index += src->length;\n\n    if(index < 0)\n    {\n      // Invalid index\n      return true;\n    }\n  }\n\n  *real = index;\n  return false;\n}\n\nstatic int get_string_numeric_value(struct string *src)\n{\n  // strtol wants a null terminator and we don't want to duplicate the string,\n  // so reimplement a base 10 strtol to use the string length instead. This\n  // also seems to run a lot faster than strtol anyway.\n  boolean negative = false;\n  int overflow = INT_MAX;\n  int overflow_d = INT_MAX % 10;\n  int value = 0;\n  int digit;\n  char *end;\n  char *pos;\n\n  if(src && src->length)\n  {\n    pos = src->value;\n    end = src->value + src->length;\n\n    // Skip whitespace\n    while(pos < end && isspace((int)*pos))\n      pos++;\n\n    if(pos >= end)\n      return 0;\n\n    if(*pos == '-')\n    {\n      negative = true;\n      overflow = INT_MIN;\n      overflow_d = -(INT_MIN % 10);\n      pos++;\n    }\n    else\n\n    if(*pos == '+')\n      pos++;\n\n    while(pos < end && isdigit((int)*pos))\n    {\n      digit = *(pos++) - '0';\n\n      // Overflow\n      if(value >= (INT_MAX / 10))\n        if(value > (INT_MAX / 10) || digit >= overflow_d)\n          return overflow;\n\n      value = value * 10 + digit;\n    }\n\n    if(negative)\n      value = -value;\n\n    return (int)value;\n  }\n  return 0;\n}\n\n/**\n * Read a string as a counter. This occurs either when the string length is\n * read, a string index is read, or when a counter is set to a string or a\n * string is referenced in an expression. The provided buffer WILL be modified\n * to the real name of the string.\n *\n * @param  mzx_world    World data.\n * @param  name_buffer  Mutable buffer containing the string name.\n * @param  id           Current robot ID or 0 for global.\n * @return              Value read from the string.\n */\nint string_read_as_counter(struct world *mzx_world,\n char *name_buffer, int id)\n{\n  struct string_list *string_list = &(mzx_world->string_list);\n  char *dot_ptr = strchr(name_buffer + 1, '.');\n  struct string src;\n\n  // User may have provided $str.N or $str.length explicitly\n  if(dot_ptr)\n  {\n    struct string *src;\n    int next;\n\n    *dot_ptr = 0;\n    src = find_string(string_list, name_buffer, &next);\n\n    // Fix the dot because currently stuff like inc/dec/multiply\n    // does a read function call followed by a write function call\n    *dot_ptr = '.';\n    dot_ptr++;\n\n    if(!src)\n    {\n      return 0;\n    }\n    else\n\n    if(!strcasecmp(dot_ptr, \"length\"))\n    {\n      // str.length\n      return (int)src->length;\n    }\n\n    else\n    {\n      // string.[index]\n      boolean index_specified = false;\n      size_t real_index;\n      size_t size = 1;\n      int index;\n      int value = 0;\n\n      // Check to see if there's a valid string indexing\n      get_string_dot_value(dot_ptr, &index, &size, &index_specified);\n\n      if(index_specified)\n      {\n        // Negative indices and multiple indexing (2.91+)\n        if(mzx_world->version >= V291)\n        {\n          if(get_string_real_index(src, index, &real_index))\n            return 0;\n\n          if(src->length < size + real_index)\n            size = src->length - real_index;\n        }\n        else\n        {\n          real_index = index;\n          size = 1;\n        }\n\n        // If we're in bounds return the char at this offset\n        if(real_index < src->length)\n        {\n          switch(size)\n          {\n            case 4: value |= (unsigned int)src->value[real_index + 3] << 24; // fallthru\n            case 3: value |= (int)src->value[real_index + 2] << 16; // fallthru\n            case 2: value |= (int)src->value[real_index + 1] << 8;  // fallthru\n            case 1: value |= (int)src->value[real_index];\n          }\n          return value;\n        }\n      }\n    }\n  }\n  else\n\n  // Otherwise fall back to looking up a regular string\n  if(get_string(mzx_world, name_buffer, &src, id))\n    return get_string_numeric_value(&src);\n\n  // The string wasn't found or the request was out of bounds\n  return 0;\n}\n\n/**\n * Set a string as a counter. This occurs either when the string length is set,\n * a string index is set, or when a string or string splice is used in a numeric\n * command. The provided buffer WILL be modified to the real name of the string.\n *\n * @param  mzx_world    World data.\n * @param  name_buffer  Mutable buffer containing the string name.\n * @param  value        Numeric value to set the string to.\n * @param  id           Current robot ID or 0 for global.\n */\nvoid string_write_as_counter(struct world *mzx_world,\n char *name_buffer, int value, int id)\n{\n  struct string_list *string_list = &(mzx_world->string_list);\n  char *dot_ptr = strrchr(name_buffer + 1, '.');\n\n  // User may have provided $str.N notation \"write char at offset\"\n  if(dot_ptr)\n  {\n    struct string *src;\n    boolean index_specified = false;\n    size_t old_length;\n    size_t new_length;\n    size_t alloc_length;\n    size_t write_size = 1;\n    size_t write_index = 0;\n    int next;\n\n    *dot_ptr = 0;\n    dot_ptr++;\n\n    /* As a special case, alter the string's length if '$str.length'\n     * is written to.\n     */\n    if(!strcasecmp(dot_ptr, \"length\"))\n    {\n      // Ignore impossible lengths\n      if(value < 0)\n        return;\n\n      // Writing to length from a non-existent string has no effect\n      src = find_string(string_list, name_buffer, &next);\n      if(!src)\n        return;\n\n      new_length = value;\n      alloc_length = new_length + 1;\n    }\n\n    else\n    {\n      int index;\n\n      // Check to see if there's a valid string indexing\n      get_string_dot_value(dot_ptr, &index, &write_size, &index_specified);\n      if(!index_specified)\n        return;\n\n      src = find_string(string_list, name_buffer, &next);\n\n      // Negative indices (2.91+)\n      if(index < 0 && mzx_world->version < V291)\n        return;\n\n      if(get_string_real_index(src, index, &write_index))\n        return;\n\n      // Multiple size (2.91+)\n      if(mzx_world->version < V291)\n        write_size = 1;\n\n      // Tentatively increase the string's length to cater for this write\n      new_length = write_index + write_size;\n      alloc_length = new_length;\n    }\n\n    if(new_length > MAX_STRING_LEN)\n      return;\n\n    /* As a kind of unnecessary optimisation, if the string already exists\n     * and we're asking to extend its length, increase the length by a power\n     * of two rather than just by the amount necessary.\n     */\n\n    if(src != NULL)\n    {\n      old_length = src->allocated_length;\n\n      if(alloc_length > old_length)\n      {\n        unsigned int i;\n\n        for(i = (unsigned int)1 << 31; i != 0; i >>= 1)\n          if(alloc_length & i)\n            break;\n\n        alloc_length = i << 1;\n      }\n    }\n\n    if(!force_string_length(string_list, name_buffer, next, &src, &alloc_length))\n      return;\n\n    if(index_specified)\n    {\n      switch(write_size)\n      {\n        case 4: src->value[write_index + 3] = value >> 24; // fallthru\n        case 3: src->value[write_index + 2] = value >> 16; // fallthru\n        case 2: src->value[write_index + 1] = value >> 8; // fallthru\n        case 1: src->value[write_index] = value;\n      }\n      if(src->length < new_length)\n        src->length = new_length;\n    }\n    else\n    {\n      src->value[new_length] = 0;\n      src->length = new_length;\n\n      // Before 2.84, the length would be set to the alloc_length\n      if(mzx_world->version < V284)\n        src->length = alloc_length;\n    }\n  }\n  else\n  {\n    struct string src_str;\n    char n_buffer[16];\n    snprintf(n_buffer, 16, \"%d\", value);\n\n    src_str.value = n_buffer;\n    src_str.length = strlen(n_buffer);\n    set_string(mzx_world, name_buffer, &src_str, id);\n  }\n}\n\nstatic void add_string(struct string_list *string_list, const char *name,\n struct string *src, unsigned int position)\n{\n  struct string *dest = add_string_preallocate(string_list, name,\n   src->length, position);\n\n  if(dest)\n    memcpy(dest->value, src->value, src->length);\n}\n\nstatic boolean get_string_size_offset(char *name_buffer, size_t *ssize,\n boolean *size_specified, int *soffset, boolean *offset_specified)\n{\n  char *offset_position = strchr(name_buffer, '+');\n  char *size_position = strchr(name_buffer, '#');\n  char *error;\n\n  if(size_position)\n    *size_position = 0;\n\n  if(offset_position)\n    *offset_position = 0;\n\n  if(size_position)\n  {\n    size_t ret = strtoul(size_position + 1, &error, 10);\n\n    if(error[0])\n      return true;\n\n    *size_specified = true;\n    *ssize = ret;\n  }\n\n  if(offset_position)\n  {\n    int ret = strtol(offset_position + 1, &error, 10);\n\n    if(error[0])\n      return true;\n\n    *offset_specified = true;\n    *soffset = ret;\n  }\n\n  return false;\n}\n\nstatic int load_string_board_direct(char *dest_chars, int dest_size,\n char *src_chars, int src_width, int block_width, int block_height,\n char terminator)\n{\n  char *dest_pos = dest_chars;\n  int dest_height = dest_size / block_width;\n  int dest_left = dest_size % block_width;\n  int i;\n\n  for(i = 0; i < dest_height; i++)\n  {\n    memcpy(dest_pos, src_chars, block_width);\n    dest_pos += block_width;\n    src_chars += src_width;\n  }\n\n  if(dest_left)\n    memcpy(dest_pos, src_chars, dest_left);\n\n  if(terminator)\n  {\n    for(i = 0; i < dest_size; i++)\n    {\n      if(dest_chars[i] == terminator)\n        break;\n    }\n\n    return i;\n  }\n\n  return dest_size;\n}\n\n/**\n * Load a portion of the provided chars layer to the given string. This may\n * be performed on a string with an offset or size suffix; in this situation,\n * the provided name buffer WILL be truncated to the actual string name. The\n * input block dimensions should be clipped prior to calling this function.\n *\n * @param  mzx_world    World data.\n * @param  name_buffer  Mutable buffer containing the string name.\n * @param  src_chars    Pointer to start of board or layer char data array to copy.\n * @param  src_width    Width of board or layer.\n * @param  block_width  Width of block to read from.\n * @param  block_height Height of block to read from.\n * @param  terminator   Terminator char to stop reading from, or 0 for none.\n */\nvoid load_string_board(struct world *mzx_world, char *name_buffer,\n char *src_chars, int src_width, int block_width, int block_height,\n char terminator)\n{\n  struct string_list *string_list = &(mzx_world->string_list);\n  boolean offset_specified = false;\n  boolean size_specified = false;\n  size_t dest_size = (size_t)(block_width * block_height);\n  size_t dest_offset = 0;\n  int input_offset = 0;\n  struct string *dest;\n  char *copy_buffer;\n  size_t copy_size;\n  int next;\n\n  if(get_string_size_offset(name_buffer, &dest_size, &size_specified,\n   &input_offset, &offset_specified))\n    return;\n\n  // Size/offset support (2.91+)\n  if(mzx_world->version < V291 && (offset_specified || size_specified))\n    return;\n\n  dest = find_string(string_list, name_buffer, &next);\n\n  if(get_string_real_index(dest, input_offset, &dest_offset))\n    return;\n\n  // Copying to a buffer makes this slower but consistent with set.\n  copy_buffer = cmalloc(dest_size);\n\n  copy_size = load_string_board_direct(copy_buffer, dest_size,\n   src_chars, src_width, block_width, block_height, terminator);\n\n  force_string_copy(string_list, name_buffer, next, &dest, copy_size,\n   dest_offset, offset_specified, &dest_size, size_specified, copy_buffer);\n\n  free(copy_buffer);\n}\n\n/**\n * Set a string represented by a given name. The name may include an offset\n * or size suffix; in this situation, the provided name buffer WILL be truncated\n * to the actual string name.\n *\n * @param  mzx_world    World data.\n * @param  name_buffer  Mutable buffer containing the string name.\n * @param  src          String to set the destination string to.\n * @param  id           Current robot ID or 0 for global.\n * @return              1 if the robot program was modified, otherwise 0.\n */\nint set_string(struct world *mzx_world, char *name, struct string *src,\n int id)\n{\n  struct string_list *string_list = &(mzx_world->string_list);\n  boolean offset_specified = false;\n  boolean size_specified = false;\n  size_t offset = 0;\n  size_t size = 0;\n  int input_offset = 0;\n  size_t src_length = src->length;\n  char *src_value = src->value;\n  struct string *dest;\n  int next = 0;\n\n  if(get_string_size_offset(name, &size, &size_specified,\n   &input_offset, &offset_specified))\n    return 0;\n\n  dest = find_string(string_list, name, &next);\n\n  // Negative offsets (2.91+)\n  if(input_offset < 0 && mzx_world->version < V291)\n    return 0;\n\n  if(get_string_real_index(dest, input_offset, &offset))\n    return 0;\n\n  if(special_name_partial(\"fread\") &&\n   !mzx_world->input_is_dir && mzx_world->input_file)\n  {\n    vfile *input_file = mzx_world->input_file;\n\n    if(src_length > 5)\n    {\n      unsigned int read_count = strtoul(src_value + 5, NULL, 10);\n      long current_pos, file_size;\n      size_t actual_read = 0;\n\n      read_count = MIN(read_count, MAX_STRING_LEN);\n\n      file_size = vfilelength(input_file, false);\n      current_pos = vftell(input_file);\n\n      /* We then truncate the user read to the maximum difference between the\n       * current position and the file end; this won't affect normal reads,\n       * it just prevents people from crashing MZX by reading 2G from a 3K file.\n       */\n      if(current_pos + read_count > (unsigned int)file_size)\n        read_count = file_size - current_pos;\n\n      if(!force_string_splice(string_list, name, next, &dest,\n       read_count, offset, offset_specified, &size, size_specified))\n        return 0;\n\n      // MZX currently ignores all spliced writes that would extend past the\n      // string size limit (rather than truncating). See force_string_copy.\n      if(offset <= dest->length - size)\n        actual_read = vfread(dest->value + offset, 1, size, input_file);\n      if(actual_read < read_count)\n        vfseek(input_file, read_count - actual_read, SEEK_CUR);\n\n      if(offset == 0 && !offset_specified)\n        dest->length = actual_read;\n    }\n    else\n    {\n      const char terminate_char = mzx_world->fread_delimiter;\n      char *dest_value;\n      int current_char = 0;\n      unsigned int read_pos = 0;\n      unsigned int allocated = 32;\n\n      if(offset_specified || size_specified)\n      {\n        /* The string splice rules require that the source string's length is\n         * known ahead of time. The least bad way to handle this is an\n         * intermediate buffer.\n         */\n        size_t max_read = MAX_STRING_LEN;\n        char *new_value;\n        char *tmp;\n\n        // Preemtively constrain the read to the string maximum limits.\n        if(offset_specified)\n          max_read = MIN(max_read, MAX_STRING_LEN - MIN(offset, MAX_STRING_LEN));\n        if(size_specified)\n          max_read = MIN(max_read, size);\n\n        if(offset_specified && offset >= MAX_STRING_LEN)\n          return 0;\n\n        new_value = (char *)cmalloc(allocated);\n        if(!new_value)\n          return 0;\n\n        while(read_pos < max_read)\n        {\n          if(read_pos >= allocated)\n          {\n            if((allocated *= 2) > MAX_STRING_LEN)\n              allocated = MAX_STRING_LEN;\n\n            tmp = (char *)crealloc(new_value, allocated);\n            if(!tmp)\n              break;\n            new_value = tmp;\n          }\n          current_char = vfgetc(input_file);\n\n          if(current_char == terminate_char || current_char == EOF)\n            break;\n\n          new_value[read_pos++] = current_char;\n        }\n\n        force_string_copy(string_list, name, next, &dest, read_pos,\n         offset, offset_specified, &size, size_specified, new_value);\n        free(new_value);\n      }\n      else\n      {\n        /* Since there's no splice, chars can be directly read to the string. */\n        if(!force_string_splice(string_list, name, next, &dest,\n         allocated, offset, offset_specified, &size, size_specified))\n          return 0;\n\n        dest_value = dest->value;\n        allocated = dest->allocated_length;\n\n        while(read_pos < MAX_STRING_LEN)\n        {\n          if(read_pos >= allocated)\n          {\n            if((allocated *= 2) > MAX_STRING_LEN)\n              allocated = MAX_STRING_LEN;\n\n            if(!reallocate_string(string_list, dest, next, allocated))\n              break;\n            dest_value = dest->value;\n          }\n          current_char = vfgetc(input_file);\n\n          if(current_char == terminate_char || current_char == EOF)\n            break;\n\n          dest_value[read_pos++] = current_char;\n        }\n        // Should always be true.\n        if(offset == 0 && !offset_specified)\n          dest->length = read_pos;\n      }\n\n      // Find the terminator if it wasn't already found.\n      while(current_char != terminate_char && current_char != EOF)\n        current_char = vfgetc(input_file);\n    }\n  }\n  else\n\n  if(special_name_partial(\"fread\") && mzx_world->input_is_dir)\n  {\n    char entry[MAX_PATH];\n\n    while(1)\n    {\n      // Read entries until there are none left\n      if(!vdir_read(mzx_world->input_directory, entry, MAX_PATH, NULL))\n      {\n        entry[0] = '\\0';\n        break;\n      }\n\n      // Ignore . and ..\n      if(!strcmp(entry, \".\") || !strcmp(entry, \"..\"))\n        continue;\n\n      // And ignore anything with '*' or '/' in the name\n      if(strchr(entry, '*') || strchr(entry, '/'))\n        continue;\n\n      break;\n    }\n\n    force_string_copy(string_list, name, next, &dest, strlen(entry),\n     offset, offset_specified, &size, size_specified, entry);\n  }\n  else\n\n  if(special_name(\"board_name\"))\n  {\n    char *board_name = mzx_world->current_board->board_name;\n    size_t str_length = strlen(board_name);\n    force_string_copy(string_list, name, next, &dest, str_length,\n     offset, offset_specified, &size, size_specified, board_name);\n  }\n  else\n\n  if(special_name(\"robot_name\"))\n  {\n    char *robot_name = (mzx_world->current_board->robot_list[id])->robot_name;\n    size_t str_length = strlen(robot_name);\n    force_string_copy(string_list, name, next, &dest, str_length,\n     offset, offset_specified, &size, size_specified, robot_name);\n  }\n  else\n\n  if(special_name(\"mod_name\"))\n  {\n    char *mod_name = mzx_world->real_mod_playing;\n    size_t str_length = strlen(mod_name);\n    force_string_copy(string_list, name, next, &dest, str_length,\n     offset, offset_specified, &size, size_specified, mod_name);\n  }\n  else\n\n  if(special_name(\"input\"))\n  {\n    const char *input_string = mzx_world->current_board->input_string;\n    size_t str_length;\n\n    if(!input_string)\n      input_string = \"\";\n\n    str_length = strlen(input_string);\n    force_string_copy(string_list, name, next, &dest, str_length,\n     offset, offset_specified, &size, size_specified, input_string);\n  }\n  else\n\n  // Okay, I implemented this stupid thing, you can all die.\n  if(special_name(\"board_scan\"))\n  {\n    struct board *src_board = mzx_world->current_board;\n    unsigned int board_width, board_size, board_pos;\n    size_t read_length = 63;\n\n    board_width = src_board->board_width;\n    board_size = board_width * src_board->board_height;\n    board_pos = get_board_x_board_y_offset(mzx_world, id);\n\n    if(!force_string_length(string_list, name, next, &dest, &read_length))\n      return 0;\n\n    if(board_pos < board_size)\n    {\n      if((board_pos + read_length) > board_size)\n        read_length = board_size - board_pos;\n\n      dest->length = load_string_board_direct(dest->value,\n       (int)read_length, src_board->level_param + board_pos, board_width,\n       (int)read_length, 1, '*');\n    }\n  }\n  else\n\n  // This isn't a read (set), it's a write but it fits here now.\n\n  if(special_name_partial(\"fwrite\") && mzx_world->output_file)\n  {\n    // When no length argument is specified, always write a delimiter.\n    boolean write_delimiter = (src_length == 6);\n\n    /* You can't write a string that doesn't exist, or a string\n     * of zero length (the file will still be created, of course).\n     */\n    if(dest != NULL && dest->length > 0)\n    {\n      vfile *output_file = mzx_world->output_file;\n      char *dest_value = dest->value;\n      size_t dest_length = dest->length;\n\n      if(src_length > 6)\n        size = strtol(src_value + 6, NULL, 10);\n\n      if(size == 0)\n        size = dest_length;\n\n      if(offset >= dest_length)\n        offset = dest_length - 1;\n\n      if(offset + size > dest_length)\n        size = dest_length - offset;\n\n      vfwrite(dest_value + offset, size, 1, output_file);\n    }\n    else\n    {\n      // 2.80X through 2.91X wouldn't write delimiters for unset strings.\n      if((mzx_world->version >= V280) && (mzx_world->version <= V291) && !dest)\n        write_delimiter = false;\n\n      // 2.82b through 2.91X wouldn't write them for 0-length strings either.\n      if((mzx_world->version >= V282) && (mzx_world->version <= V291))\n        write_delimiter = false;\n    }\n\n    if(write_delimiter)\n      vfputc(mzx_world->fwrite_delimiter, mzx_world->output_file);\n  }\n  else\n\n  // Load SMZX indices from a string\n\n  if(special_name(\"smzx_indices\"))\n  {\n    if(dest && dest->length > 0)\n      load_indices(dest->value, dest->length);\n  }\n  else\n\n  // Load source code from a string\n\n#ifdef CONFIG_DEBYTECODE\n  if(special_name_partial(\"load_robot\") && mzx_world->version >= V290)\n  {\n    // Load legacy source code (2.90+)\n\n    struct robot *cur_robot;\n    int load_id = id;\n\n    // If there's a number at the end, we're loading to another robot.\n    if(src_length > 10)\n      load_id = strtol(src_value + 10, NULL, 10);\n\n    cur_robot = get_robot_by_id(mzx_world, load_id);\n\n    if(cur_robot && dest && dest->length)\n    {\n      int new_length = 0;\n      char *new_source;\n\n      // Source world? Assume new source. Otherwise, assume old source.\n      // TODO issues caused by this will be resolved when these counters get\n      // translated into actual commands eventually.\n      if(mzx_world->version >= VERSION_SOURCE)\n      {\n        new_length = dest->length;\n        new_source = cmalloc(new_length + 1);\n        memcpy(new_source, dest->value, dest->length);\n        new_source[new_length] = 0;\n      }\n      else\n        new_source = legacy_convert_program(dest->value, dest->length,\n         &new_length, SAVE_ROBOT_DISASM_EXTRAS, SAVE_ROBOT_DISASM_BASE);\n\n      if(new_source)\n      {\n        if(cur_robot->program_source)\n          free(cur_robot->program_source);\n\n        cur_robot->program_source = new_source;\n        cur_robot->program_source_length = new_length;\n\n        // TODO: Move this outside of here.\n        if(cur_robot->program_bytecode)\n        {\n          free(cur_robot->program_bytecode);\n          cur_robot->program_bytecode = NULL;\n        }\n        cur_robot->stack_pointer = 0;\n        cur_robot->cur_prog_line = 1;\n        prepare_robot_bytecode(mzx_world, cur_robot);\n\n        // Restart this robot if either it was just a LOAD_ROBOT\n        // OR LOAD_ROBOTn was used where n is &robot_id&.\n        if(load_id == id)\n          return 1;\n      }\n    }\n  }\n  else\n\n  if(special_name_partial(\"save_robot\") && mzx_world->version >= V292)\n  {\n    // Save robot source to string (2.92+)\n    // FIXME old worlds will save new source. Fixing this would ideally\n    // involve allowing new source, old source, or old bytecode to all be\n    // considered the current program \"source\", but doing this in a clean\n    // way probably relies on separating programs from robots internally.\n\n    struct robot *cur_robot;\n    int load_id = id;\n\n    if(src_length > 10)\n      load_id = strtol(src_value + 10, NULL, 10);\n\n    cur_robot = get_robot_by_id(mzx_world, load_id);\n\n    if(cur_robot)\n    {\n      force_string_copy(string_list, name, next, &dest,\n       cur_robot->program_source_length, offset, offset_specified,\n       &size, size_specified, cur_robot->program_source);\n    }\n  }\n\n#else //!CONFIG_DEBYTECODE\n  if(special_name_partial(\"load_robot\") && mzx_world->version >= V290)\n  {\n    // Load robot from string (2.90+)\n\n    char *new_program = NULL;\n    int new_size;\n\n    if(dest && dest->length)\n      new_program = assemble_program(dest->value, dest->length, &new_size);\n\n    if(new_program)\n    {\n      struct robot *cur_robot;\n      int load_id = id;\n\n      // If there's a number at the end, we're loading to another robot.\n      if(src_length > 10)\n        load_id = strtol(src_value + 10, NULL, 10);\n\n      cur_robot = get_robot_by_id(mzx_world, load_id);\n\n      if(cur_robot)\n      {\n        reallocate_robot(cur_robot, new_size);\n        clear_label_cache(cur_robot);\n\n        memcpy(cur_robot->program_bytecode, new_program, new_size);\n        cur_robot->stack_pointer = 0;\n        cur_robot->cur_prog_line = 1;\n        cache_robot_labels(cur_robot);\n\n        // Free the robot's source and command map\n        free(cur_robot->program_source);\n        cur_robot->program_source = NULL;\n        cur_robot->program_source_length = 0;\n#ifdef CONFIG_EDITOR\n        free(cur_robot->command_map);\n        cur_robot->command_map_length = 0;\n        cur_robot->command_map = NULL;\n#endif\n\n        // Restart this robot if either it was just a LOAD_ROBOT\n        // OR LOAD_ROBOTn was used where n is &robot_id&.\n        if(load_id == id)\n        {\n          free(new_program);\n          return 1;\n        }\n      }\n\n      free(new_program);\n    }\n  }\n  else\n\n  if(special_name_partial(\"save_robot\") && mzx_world->version >= V292)\n  {\n    // Save robot to string (2.92+)\n    struct robot *cur_robot;\n    char *robot_source = NULL;\n    int robot_source_length;\n    int load_id = id;\n\n    if(src_length > 10)\n      load_id = strtol(src_value + 10, NULL, 10);\n\n    cur_robot = get_robot_by_id(mzx_world, load_id);\n\n    if(cur_robot)\n    {\n      disassemble_program(cur_robot->program_bytecode,\n       cur_robot->program_bytecode_length,\n       &robot_source, &robot_source_length, NULL, NULL);\n\n      if(robot_source)\n      {\n        force_string_copy(string_list, name, next, &dest, robot_source_length,\n         offset, offset_specified, &size, size_specified, robot_source);\n        free(robot_source);\n      }\n    }\n  }\n#endif\n\n  else\n  {\n    // Just a normal string here.\n    force_string_move(string_list, name, next, &dest, src_length,\n     offset, offset_specified, &size, size_specified, src_value);\n  }\n\n  return 0;\n}\n\n/**\n * Creates a new string and adds it to the strings list if it doesn't already\n * exist; otherwise, resizes the string to exactly the provided length. Returns\n * NULL if the new string could not be added to the string list or if the\n * requested size is too large.\n */\nstruct string *new_string(struct world *mzx_world, const char *name,\n size_t length, int id)\n{\n  struct string_list *string_list = &(mzx_world->string_list);\n  size_t actual_length = length;\n  struct string *str;\n  int next = 0;\n\n  str = find_string(string_list, name, &next);\n  if(!force_string_length(string_list, name, next, &str, &actual_length))\n    return NULL;\n\n  /* Make sure the string size wasn't bounded... */\n  if(length > actual_length)\n    return NULL;\n\n  str->length = length;\n  return str;\n}\n\n/**\n * Get a string by name and initialize the provided string struct with its\n * value. An offset or size suffix can be provided with the name buffer; in\n * this situation, the name buffer WILL be truncated to the actual string name.\n *\n * @param  mzx_world    World data.\n * @param  name_buffer  Mutable buffer containing the string name.\n * @param  dest         Destination string struct for the string value. This\n *                      struct will only be initialized on success.\n * @param  id           Current robot ID or 0 for global.\n * @return              1 if a valid string was found (success), otherwise 0.\n */\nint get_string(struct world *mzx_world, char *name_buffer, struct string *dest,\n int id)\n{\n  struct string_list *string_list = &(mzx_world->string_list);\n  boolean offset_specified = false;\n  boolean size_specified = false;\n  size_t size = 0;\n  size_t offset = 0;\n  int input_offset = 0;\n  struct string *src;\n  int next;\n\n  dest->value = NULL;\n  dest->length = 0;\n\n  if(get_string_size_offset(name_buffer, &size, &size_specified,\n   &input_offset, &offset_specified))\n    return 0;\n\n  src = find_string(string_list, name_buffer, &next);\n  if(src)\n  {\n    // Negative offsets (2.91+)\n    if(input_offset < 0 && mzx_world->version < V291)\n      return 0;\n\n    if(get_string_real_index(src, input_offset, &offset))\n      return 0;\n\n    if((size == 0 && !size_specified) || size > src->length)\n      size = src->length;\n\n    if(offset > src->length)\n      offset = src->length;\n\n    if(offset + size > src->length)\n      size = src->length - offset;\n\n    dest->value = src->value + offset;\n    dest->length = size;\n    return 1;\n  }\n\n  return 0;\n}\n\n/**\n * Get a string by name and return a pointer to it. This function does not work\n * with string splices; use get_string instead. The pointer this function\n * returns is guaranteed to be stable for the duration of the gameplay session.\n *\n * @param  mzx_world    World data.\n * @param  name         Name of string to look up.\n * @param  id           Current robot ID or 0 for global.\n * @return              string pointer if found, otherwise NULL.\n */\nconst struct string *get_string_pointer(struct world *mzx_world,\n const char *name, int id)\n{\n  struct string_list *string_list = &(mzx_world->string_list);\n  int next;\n\n  return find_string(string_list, name, &next);\n}\n\n// You can't increment spliced strings (it's not really useful and\n// would introduce a world of problems..)\n\n/**\n * Increase a string by a provided value. This operation is invalid on a string\n * splice, but this function will check the given name buffer for an offset or\n * size suffix and WILL truncate the name buffer if one is found.\n *\n * @param  mzx_world    World data.\n * @param  name_buffer  Mutable buffer containing the string name.\n * @param  src          String to increase the destination string by.\n * @param  id           Current robot ID or 0 for global.\n */\nvoid inc_string(struct world *mzx_world, char *name_buffer, struct string *src,\n int id)\n{\n  struct string_list *string_list = &(mzx_world->string_list);\n  struct string *dest;\n  int next;\n\n  dest = find_string(string_list, name_buffer, &next);\n\n  if(dest)\n  {\n    size_t new_length = src->length + dest->length;\n    if(new_length > MAX_STRING_LEN)\n      return;\n\n    // Concatenate\n    if(new_length > dest->allocated_length)\n    {\n      // Handle collisions, for incrementing something by a splice\n      // of itself, which could relocate the string and the value...\n      char *src_end = src->value + src->length;\n      if((src_end <= (dest->value + dest->length)) &&\n       (src_end >= dest->value))\n      {\n        char *old_dest_value = dest->value;\n        dest = reallocate_string(string_list, dest, next, new_length);\n        src->value += (dest->value - old_dest_value);\n      }\n      else\n      {\n        dest = reallocate_string(string_list, dest, next, new_length);\n      }\n    }\n\n    memcpy(dest->value + dest->length, src->value, src->length);\n    dest->length = new_length;\n  }\n  else\n  {\n    // Make sure this isn't a splice, malformed or otherwise.\n    boolean offset_specified = false;\n    boolean size_specified = false;\n    size_t size;\n    int offset;\n\n    boolean error = get_string_size_offset(name_buffer, &size,\n     &size_specified, &offset, &offset_specified);\n\n    if(error || offset_specified || size_specified)\n      return;\n\n    add_string(string_list, name_buffer, src, next);\n  }\n}\n\nvoid dec_string_int(struct world *mzx_world, const char *name, int value,\n int id)\n{\n  struct string_list *string_list = &(mzx_world->string_list);\n  struct string *dest;\n  int next;\n\n  dest = find_string(string_list, name, &next);\n\n  if(dest)\n  {\n    // Simply decrease the length\n    if((int)dest->length - value < 0)\n      dest->length = 0;\n    else\n      dest->length -= value;\n  }\n}\n\nstatic boolean wildcard_char_is_escapable(unsigned char c)\n{\n  return (c == '%') || (c == '?') || (c == '\\\\');\n}\n\n#if 0\n#define WILDCARD_PRINT() do { \\\n    for(size_t k = 0; k <= str_len; k++) \\\n      fprintf(mzxerr, \"%c \", (k+1 < left ? ' ' : str_matched[k] ? 'Y' : 'n')); \\\n    fprintf(mzxerr, \"\\n\"); \\\n} while(0)\n#endif\n\n// Slower wildcard compare algorithm that manipulates an array of booleans to\n// determine the match state of the entire source string at once.\n// This approach seems to perform a bit better than a stack.\n//\n// Example pattern:\n//      z a b c d e f g\n//    Y\n// z  Y Y - - - - - - -\n// ?  Y - Y - - - - - -\n// %  Y - Y Y Y Y Y Y Y (fill left to end with Y)\n// c  Y - - N Y N N N N (attempt from left to right)\n// %  Y - - - Y Y Y Y Y (fill left to end with Y)\n// g  Y - - - - N N N Y\nstatic int compare_wildcard_slow(const char *str, size_t str_len,\n const char *pat, size_t pat_len, boolean exact_case)\n{\n  char *str_matched = ccalloc(1, str_len + 1);\n  size_t left = 1;\n  size_t right = 1;\n  size_t new_left = 1;\n  size_t new_right;\n  size_t i = 0;\n  size_t j;\n  char next = 0;\n  int res = -1;\n\n#ifdef WILDCARD_PRINT\n  debug(\"--WILDCARD-- Slow: %.*s ?=%s %.*s\\n\",\n   (int)str_len, str, exact_case?\"=\":\"\", (int)pat_len, pat);\n#endif\n\n  str_matched[0] = 1;\n\n  while(i < pat_len && left <= str_len)\n  {\n    next = exact_case ? pat[i] : memtolower(pat[i]);\n    i++;\n\n#ifdef WILDCARD_PRINT\n    WILDCARD_PRINT();\n#endif\n\n    switch(next)\n    {\n      case '?':\n      case '%':\n      {\n        // ? matches any character.\n        // % can match the entire string or nothing.\n        // Consume all wildcards in a block to reduce the number of calls.\n        boolean match_any = false;\n        new_left = left;\n        new_right = right;\n        while(true)\n        {\n          if(next == '%')\n          {\n            new_right = str_len;\n            match_any = true;\n          }\n          else\n\n          if(next == '?')\n          {\n            if(new_left <= str_len)\n              new_left++;\n            if(new_right <= str_len)\n              new_right++;\n          }\n          else\n          {\n            i--;\n            break;\n          }\n\n          if(i >= pat_len)\n            break;\n\n          next = pat[i++];\n        }\n\n        if(match_any)\n        {\n          memset(str_matched + left, 1, str_len - left + 1);\n        }\n        else\n          memmove(str_matched + new_left - 1, str_matched + left - 1,\n           new_right - new_left + 1);\n\n        break;\n      }\n\n      case '\\\\':\n      {\n        // Might be an escaped character\n        if(i < pat_len)\n        {\n          if(wildcard_char_is_escapable(pat[i]))\n          {\n            next = pat[i];\n            i++;\n          }\n        }\n      }\n\n      /* fallthrough */\n\n      default:\n      {\n        // Matches next character if previous character matched and\n        // the current character is the same as the pattern. If no matches are\n        // found, left will be set >str_len and terminate the search.\n        new_left = (size_t)-1;\n        new_right = 0;\n\n        if(exact_case)\n        {\n          for(j = right; j >= left; j--)\n          {\n            str_matched[j] = str_matched[j-1] && (str[j-1] == next);\n            if(str_matched[j])\n            {\n              new_left = j+1;\n              if(!new_right)\n                new_right = j+(j < str_len);\n            }\n          }\n        }\n        else\n        {\n          for(j = right; j >= left; j--)\n          {\n            str_matched[j] = str_matched[j-1] && memtolower(str[j-1]) == next;\n            if(str_matched[j])\n            {\n              new_left = j+1;\n              if(!new_right)\n                new_right = j+(j < str_len);\n            }\n          }\n        }\n        break;\n      }\n    }\n\n    left = new_left;\n    right = new_right;\n  }\n\n  // Consume any trailing multiple wildcards\n  while(i < pat_len && pat[i] == '%')\n    i++;\n\n#ifdef WILDCARD_PRINT\n    WILDCARD_PRINT();\n#endif\n\n  if(str_matched[str_len] && i == pat_len)\n    res = 0;\n\n  free(str_matched);\n  return res;\n}\n\n// Basic wildcard match-- supports anything but % followed by literals.\n// This is a lot faster than the older algorithm, but if the aforementioned\n// case is encountered, it has to call the old algorithm instead.\nstatic int compare_wildcard(const char *str, size_t str_len,\n const char *pat, size_t pat_len, boolean exact_case)\n{\n  size_t s = 0;\n  size_t w = 0;\n\n#ifdef WILDCARD_PRINT\n  debug(\"--WILDCARD-- Fast: %.*s ?=%s %.*s\\n\",\n   (int)str_len, str, exact_case?\"=\":\"\", (int)pat_len, pat);\n#endif\n\n  // Pattern is length 0: match if str is length 0, otherwise not a match.\n  if(pat_len == 0)\n    return (str_len == 0) ? 0 : 1;\n\n  while(s < str_len && w < pat_len)\n  {\n    switch(pat[w])\n    {\n      case '%':\n      {\n        // Consume extra wildcards.\n        size_t oldw = w;\n        size_t olds = s;\n        size_t left = 0;\n        size_t i;\n        char lookahead;\n        w++;\n        while(w < pat_len)\n        {\n          if(pat[w] == '%')\n          {\n            w++;\n          }\n          else\n\n          if(pat[w] == '?')\n          {\n            w++;\n            s++;\n          }\n          else\n            break;\n        }\n\n        // Not enough source characters? Not a match.\n        if(s > str_len)\n          return 1;\n\n        // End of the pattern and >=0 characters left? Match.\n        if(w == pat_len)\n          return 0;\n\n        // No % wildcards present in the rest of the pattern? Align with the\n        // end of the string and keep going.\n        for(i = w; i < pat_len; i++)\n        {\n          if(pat[i] == '%')\n            break;\n          else\n          if(pat[i] == '\\\\' && i+1 < pat_len)\n            if(wildcard_char_is_escapable(pat[i+1]))\n              i++;\n          left++;\n        }\n        if(i == pat_len)\n        {\n          s = MAX(s, str_len - left);\n          break;\n        }\n\n        // Lookahead char not present anywhere in the source? Not a match.\n        // This is a good opportunity to reduce the size of the source if it\n        // has to go to the old search algorithm too.\n        lookahead = pat[w];\n        if(lookahead == '\\\\' && w+1 < pat_len)\n          if(wildcard_char_is_escapable(pat[w+1]))\n            lookahead = pat[w+1];\n\n        while(s < str_len)\n        {\n          if(str[s] == lookahead)\n            break;\n          olds++;\n          s++;\n        }\n        if(s >= str_len)\n          return 1;\n\n        // Bring out the slow search algorithm :(\n        str += olds;\n        str_len -= olds;\n        pat += oldw;\n        pat_len -= oldw;\n        return compare_wildcard_slow(str, str_len, pat, pat_len, exact_case);\n      }\n\n      case '?':\n      {\n        // Consume extra wildcards.\n        w++;\n        s++;\n        while(w < pat_len && pat[w] == '?')\n        {\n          w++;\n          s++;\n        }\n        continue;\n      }\n\n      case '\\\\':\n      {\n        if(wildcard_char_is_escapable(pat[w+1]))\n          w++;\n        if(w >= pat_len)\n          return 1;\n      }\n\n      /* fall-through */\n\n      default:\n      {\n        if(exact_case)\n        {\n          if(str[s] != pat[w])\n            return 1;\n        }\n        else\n        {\n          if(memtolower(str[s]) != memtolower(pat[w]))\n            return 1;\n        }\n        w++;\n        s++;\n        continue;\n      }\n    }\n  }\n  // Skip trailing wildcards if they exist\n  while(w < pat_len && pat[w] == '%') w++;\n\n  // Both exactly consumed--successful match.\n  if(s == str_len && w == pat_len)\n    return 0;\n\n  return 1;\n}\n\nint compare_strings(struct string *A, struct string *B, boolean exact_case,\n boolean allow_wildcards)\n{\n  size_t cmp_length = MIN(A->length, B->length);\n  int res = 0;\n\n  if(!allow_wildcards)\n  {\n    if(exact_case)\n    {\n      res = memcmp(A->value, B->value, cmp_length);\n    }\n    else\n    {\n      res = memcasecmp(A->value, B->value, cmp_length);\n    }\n\n    // NOTE: Versions prior to 2.91e have a string ordering bug.\n    // If it's necessary to emulate this bug,\n    // then passing mzx_world into this function\n    // and using this instead will suffice for most affected versions:\n    //if(res != 0 && mzx_world->version < V291)\n    if(res != 0)\n      return res;\n\n    if(A->length > B->length)\n      return 1;\n\n    if(A->length < B->length)\n      return -1;\n\n    return res;\n  }\n\n  return compare_wildcard(A->value, A->length, B->value, B->length, exact_case);\n}\n\n/**\n * String comparison prior to 2.81 used strcasecmp, which worked because string\n * values were guaranteed to be null terminated. Some games relied on this\n * behavior by inserting nulls into strings (Mines of Madness). String values\n * are not null terminated anymore, so approximate this with strncasecmp.\n */\nint compare_strings_null_terminated(struct string *A, struct string *B)\n{\n  size_t cmp_length = MIN(A->length, B->length);\n  int res;\n\n  res = strncasecmp(A->value, B->value, cmp_length);\n  if(res)\n    return res;\n\n  if(A->length > B->length)\n    return A->value[cmp_length];\n\n  if(A->length < B->length)\n    return -B->value[cmp_length];\n\n  return 0;\n}\n\n// Create a new string from loading a save file. This skips find_string.\nstruct string *load_new_string(struct string_list *string_list, int index,\n const char *name, int name_length, int str_length)\n{\n  struct string *dest = allocate_new_string(name, name_length, str_length);\n\n  string_list->strings[index] = dest;\n\n#ifdef CONFIG_COUNTER_HASH_TABLES\n  HASH_ADD(STRING, string_list->hash_table, dest);\n#endif\n\n  return dest;\n}\n\nstatic int string_sort_fcn(const void *a, const void *b)\n{\n  return strcasecmp(\n   (*(const struct string **)a)->name,\n   (*(const struct string **)b)->name);\n}\n\nvoid sort_string_list(struct string_list *string_list)\n{\n  qsort(string_list->strings, (size_t)string_list->num_strings,\n   sizeof(struct string *), string_sort_fcn);\n}\n\nvoid clear_string_list(struct string_list *string_list)\n{\n  unsigned int i;\n\n#ifdef CONFIG_COUNTER_HASH_TABLES\n  HASH_CLEAR(STRING, string_list->hash_table);\n  string_list->hash_table = NULL;\n#endif\n\n  for(i = 0; i < string_list->num_strings; i++)\n  {\n    free(string_list->strings[i]->value);\n    free(string_list->strings[i]);\n  }\n\n  free(string_list->strings);\n\n  string_list->num_strings = 0;\n  string_list->num_strings_allocated = 0;\n  string_list->strings = NULL;\n}\n\n#ifdef CONFIG_EDITOR\n\nvoid string_list_size(struct string_list *string_list,\n size_t *list_size, size_t *table_size, size_t *strings_size)\n{\n  if(list_size)\n    *list_size = string_list->num_strings_allocated * sizeof(struct string *);\n\n  if(table_size)\n  {\n    *table_size = 0;\n#ifdef CONFIG_COUNTER_HASH_TABLES\n    HASH_MEMORY_USAGE(STRING, string_list->hash_table, *table_size);\n#endif\n  }\n\n  if(strings_size)\n  {\n    size_t total = 0;\n    size_t i;\n\n    if(string_list->strings)\n    {\n      for(i = 0; i < string_list->num_strings; i++)\n      {\n        struct string *s = string_list->strings[i];\n        if(s)\n        {\n          total += get_string_alloc_size(s->name_length);\n          total += s->allocated_length;\n        }\n      }\n    }\n    *strings_size = total;\n  }\n}\n\n#endif /* CONFIG_EDITOR */\n"
  },
  {
    "path": "src/str.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2017-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __STR_H\n#define __STR_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"world_struct.h\"\n#include \"counter_struct.h\"\n\n// These take up more room...\n#define MIN_STRING_ALLOCATE 4\n\n// Strings cannot be longer than 4M (orig 1M)\n#define MAX_STRING_LEN (1 << 22)\n\nstatic inline boolean is_string(const char *buffer)\n{\n  size_t namelen;\n\n  // String identifiers always begin with '$'.\n  if(buffer[0] != '$')\n    return false;\n\n  /* String identifiers can contain three different delimiters:\n   * '+' indicates a string offset, which has a string value.\n   * '#' indicates a string length limit, which has a string value.\n   * '.' indicates a string subscript or length, which has a numeric value.\n   *     A subscript may be followed by a '#' to indicate integer width.\n   *\n   * If a '.' is the first delimiter found, the identifier is not a string.\n   */\n  namelen = strcspn(buffer, \"#+.\");\n  return buffer[namelen] != '.';\n}\n\nCORE_LIBSPEC int get_string(struct world *mzx_world, char *name_buffer,\n struct string *dest, int id);\nCORE_LIBSPEC const struct string *get_string_pointer(struct world *mzx_world,\n const char *name, int id);\nCORE_LIBSPEC int set_string(struct world *mzx_world, char *name_buffer,\n struct string *src, int id);\nCORE_LIBSPEC struct string *new_string(struct world *mzx_world,\n const char *name, size_t length, int id);\nCORE_LIBSPEC void sort_string_list(struct string_list *string_list);\nCORE_LIBSPEC void string_list_size(struct string_list *string_list,\n size_t *list_size, size_t *table_size, size_t *strings_size);\n\nint string_read_as_counter(struct world *mzx_world,\n char *name_buffer, int id);\nvoid string_write_as_counter(struct world *mzx_world,\n char *name_buffer, int value, int id);\n\nvoid load_string_board(struct world *mzx_world, char *name_buffer,\n char *src_chars, int src_width, int block_width, int block_height,\n char terminator);\n\nvoid inc_string(struct world *mzx_world, char *name_buffer, struct string *src,\n int id);\nvoid dec_string_int(struct world *mzx_world, const char *name, int value,\n int id);\nint compare_strings(struct string *A, struct string *B, boolean exact_case,\n boolean allow_wildcards);\nint compare_strings_null_terminated(struct string *A, struct string *B);\n\nstruct string *load_new_string(struct string_list *string_list, int index,\n const char *name, int name_length, int str_length);\n\nvoid clear_string_list(struct string_list *string_list);\n\n__M_END_DECLS\n\n#endif // __STR_H\n"
  },
  {
    "path": "src/thread_debug.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2020 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Functions for debugging mutexes.\n */\n\n#ifndef __THREAD_DEBUG_H\n#define __THREAD_DEBUG_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"platform.h\"\n#include \"util.h\"\n\nstruct platform_mutex_debug\n{\n  char message[32];\n  platform_thread_id lock_thread;\n  boolean locked;\n};\n\ntypedef struct platform_mutex_debug platform_mutex_debug;\n\nstatic inline void platform_mutex_lock_debug(platform_mutex *mutex,\n platform_mutex *debug_mutex, platform_mutex_debug *dinfo,\n const char *file, int line)\n{\n  // lock may be held here, but it shouldn't be held by the current thread.\n  platform_thread_id cur_thread = platform_get_thread_id();\n\n  platform_mutex_lock(debug_mutex);\n\n  if(dinfo->locked)\n  {\n    if(dinfo->lock_thread == cur_thread)\n      warn(\"%s:%d (TID %zu): locked at %s (TID %zu) already!\\n\",\n       file, line, (size_t)cur_thread, dinfo->message, (size_t)dinfo->lock_thread);\n    else\n      trace(\"%s:%d (TID %zu): waiting on mutex locked by %s (TID %zu)\\n\",\n       file, line, (size_t)cur_thread, dinfo->message, (size_t)dinfo->lock_thread);\n  }\n\n  platform_mutex_unlock(debug_mutex);\n\n  // acquire the mutex\n  platform_mutex_lock(mutex);\n\n  platform_mutex_lock(debug_mutex);\n\n  // store information on this lock\n  snprintf((char *)dinfo->message, 32, \"%s:%d\", file, line);\n  dinfo->message[31] = '\\0';\n\n  dinfo->lock_thread = cur_thread;\n  dinfo->locked = true;\n\n  platform_mutex_unlock(debug_mutex);\n}\n\nstatic inline void platform_mutex_unlock_debug(platform_mutex *mutex,\n platform_mutex *debug_mutex, platform_mutex_debug *dinfo,\n const char *file, int line)\n{\n  platform_thread_id cur_thread = platform_get_thread_id();\n\n  platform_mutex_lock(debug_mutex);\n\n  // lock should be held here\n  if(!dinfo->locked)\n  {\n    warn(\"%s:%d (TID %zu): tried to unlock when not locked!\\n\",\n     file, line, (size_t)cur_thread);\n  }\n  else\n\n  // ...but not by another thread.\n  if(dinfo->lock_thread != cur_thread)\n    warn(\"%s:%d (TID %zu): tried to unlock mutex held by %s (TID %zu)!\\n\",\n     file, line, (size_t)cur_thread, dinfo->message, (size_t)dinfo->lock_thread);\n\n  dinfo->locked = false;\n\n  platform_mutex_unlock(debug_mutex);\n\n  platform_mutex_unlock(mutex);\n}\n\n__M_END_DECLS\n\n#endif /* __THREAD_DEBUG_H */\n"
  },
  {
    "path": "src/thread_dummy.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Dummy threading functions to avoid ifdefs in the check alloc functions.\n * If your platform needs actual threading functions and SDL or pthread are\n * not available, see platform.h.\n */\n\n#ifndef __THREAD_DUMMY_H\n#define __THREAD_DUMMY_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#define PLATFORM_NO_THREADING\n\n#define THREAD_ERROR_MSG \"Provide a valid thread.h implementation for this platform!\"\n\n#if defined(__clang__) && defined(__has_extension) && !defined(THREAD_ERROR)\n#if __has_extension(attribute_unavailable_with_message)\n#define THREAD_ERROR __attribute__((unavailable(THREAD_ERROR_MSG)))\n#endif\n#endif\n\n#if defined(__GNUC__) && !defined(THREAD_ERROR) && \\\n (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))\n#define THREAD_ERROR __attribute__((error(THREAD_ERROR_MSG)))\n#endif\n\n#ifndef THREAD_ERROR\n// Just let it fail to link...\n#define THREAD_ERROR\n#endif\n\n#define THREAD_RES void\n#define THREAD_RETURN do { return; } while(0)\n\ntypedef int platform_cond;\ntypedef int platform_mutex;\ntypedef int platform_sem;\ntypedef int platform_thread;\ntypedef int platform_thread_id;\ntypedef THREAD_RES (*platform_thread_fn)(void *);\n\n/**\n * This should pretty much only be used to get audio.c to build for platforms\n * that don't have proper mutexes. Don't add hacks like this for the other\n * functions in this file; anything relying on those functions WILL NOT WORK\n * without a proper threading/synchronization implementation.\n */\n#ifdef THREAD_DUMMY_ALLOW_MUTEX\nstatic inline boolean platform_mutex_init(platform_mutex *mutex)\n{\n  *mutex = 1;\n  return true;\n}\n\nstatic inline boolean platform_mutex_destroy(platform_mutex *mutex)\n{\n  *mutex = 0;\n  return true;\n}\n\nstatic inline boolean platform_mutex_lock(platform_mutex *mutex)\n{\n  return true;\n}\n\nstatic inline boolean platform_mutex_unlock(platform_mutex *mutex)\n{\n  return true;\n}\n#else\nTHREAD_ERROR\nboolean platform_mutex_init(platform_mutex *mutex);\n\nTHREAD_ERROR\nboolean platform_mutex_destroy(platform_mutex *mutex);\n\nTHREAD_ERROR\nboolean platform_mutex_lock(platform_mutex *mutex);\n\nTHREAD_ERROR\nboolean platform_mutex_unlock(platform_mutex *mutex);\n#endif\n\nTHREAD_ERROR\nboolean platform_cond_init(platform_cond *cond);\n\nTHREAD_ERROR\nboolean platform_cond_destroy(platform_cond *cond);\n\nTHREAD_ERROR\nboolean platform_cond_wait(platform_cond *cond, platform_mutex *mutex);\n\nTHREAD_ERROR\nboolean platform_cond_timedwait(platform_cond *cond,\n platform_mutex *muted, unsigned int timeout_ms);\n\nTHREAD_ERROR\nboolean platform_cond_signal(platform_cond *cond);\n\nTHREAD_ERROR\nboolean platform_cond_broadcast(platform_cond *cond);\n\nTHREAD_ERROR\nboolean platform_sem_init(platform_sem *sem, unsigned init_value);\n\nTHREAD_ERROR\nboolean platform_sem_destroy(platform_sem *sem);\n\nTHREAD_ERROR\nboolean platform_sem_wait(platform_sem *sem);\n\nTHREAD_ERROR\nboolean platform_sem_post(platform_sem *sem);\n\nTHREAD_ERROR\nboolean platform_thread_create(platform_thread *thread,\n platform_thread_fn start_function, void *data);\n\nTHREAD_ERROR\nboolean platform_thread_join(platform_thread *thread);\n\n/**\n * Safe to use with no thread implementation (required by util.c).\n */\nstatic inline platform_thread_id platform_get_thread_id(void)\n{\n  return 0;\n}\n\n/**\n * Safe to use with no thread implementation (required by util.c).\n */\nstatic inline boolean platform_is_same_thread(platform_thread_id a,\n platform_thread_id b)\n{\n  return true;\n}\n\n__M_END_DECLS\n\n#endif /* __THREAD_DUMMY_H */\n"
  },
  {
    "path": "src/thread_pthread.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2009 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __THREAD_PTHREAD_H\n#define __THREAD_PTHREAD_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <sched.h>\n#include <semaphore.h>\n#include <time.h>\n#include \"pthread.h\"\n\n#define THREAD_RES void *\n#define THREAD_RETURN do { return NULL; } while(0)\n\ntypedef pthread_cond_t platform_cond;\ntypedef pthread_mutex_t platform_mutex;\ntypedef sem_t platform_sem;\ntypedef pthread_t platform_thread;\ntypedef pthread_t platform_thread_id;\ntypedef THREAD_RES (*platform_thread_fn)(void *);\n\nstatic inline boolean platform_mutex_init(platform_mutex *mutex)\n{\n  if(pthread_mutex_init(mutex, NULL))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_mutex_destroy(platform_mutex *mutex)\n{\n  if(pthread_mutex_destroy(mutex))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_mutex_lock(platform_mutex *mutex)\n{\n  if(pthread_mutex_lock(mutex))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_mutex_unlock(platform_mutex *mutex)\n{\n  if(pthread_mutex_unlock(mutex))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_init(platform_cond *cond)\n{\n  if(pthread_cond_init(cond, NULL))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_destroy(platform_cond *cond)\n{\n  if(pthread_cond_destroy(cond))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_wait(platform_cond *cond,\n platform_mutex *mutex)\n{\n  if(pthread_cond_wait(cond, mutex))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_timedwait(platform_cond *cond,\n platform_mutex *mutex, unsigned int timeout_ms)\n{\n  struct timespec timeout;\n\n  clock_gettime(CLOCK_REALTIME, &timeout);\n  timeout.tv_sec  += (timeout_ms / 1000);\n  timeout.tv_nsec += (timeout_ms % 1000) * 1000000;\n\n  if(pthread_cond_timedwait(cond, mutex, &timeout))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_signal(platform_cond *cond)\n{\n  if(pthread_cond_signal(cond))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_cond_broadcast(platform_cond *cond)\n{\n  if(pthread_cond_broadcast(cond))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_init(platform_sem *sem, unsigned init_value)\n{\n  if(sem_init(sem, 0, init_value))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_destroy(platform_sem *sem)\n{\n  if(sem_destroy(sem))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_wait(platform_sem *sem)\n{\n  if(sem_wait(sem))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_post(platform_sem *sem)\n{\n  if(sem_post(sem))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_thread_create(platform_thread *thread,\n platform_thread_fn start_function, void *data)\n{\n  if(pthread_create(thread, NULL, start_function, data))\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_thread_join(platform_thread *thread)\n{\n  if(pthread_join(*thread, NULL))\n    return false;\n  return true;\n}\n\nstatic inline platform_thread_id platform_get_thread_id(void)\n{\n  return pthread_self();\n}\n\nstatic inline boolean platform_is_same_thread(platform_thread_id a,\n platform_thread_id b)\n{\n  return pthread_equal(a, b) != 0;\n}\n\nstatic inline void platform_yield(void)\n{\n  sched_yield();\n}\n\n__M_END_DECLS\n\n#endif // __THREAD_PTHREAD_H\n"
  },
  {
    "path": "src/thread_sdl.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __THREAD_SDL_H\n#define __THREAD_SDL_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"SDLmzx.h\"\n\n#define THREAD_RES int\n#define THREAD_RETURN do { return 0; } while(0)\n\ntypedef SDL_Condition *platform_cond;\ntypedef SDL_Mutex *platform_mutex;\ntypedef SDL_Semaphore *platform_sem;\ntypedef SDL_Thread *platform_thread;\ntypedef SDL_ThreadFunction platform_thread_fn;\n\n// Can't fix this with typedefs--SDL_ThreadID meant something else in SDL 2.\n#if SDL_VERSION_ATLEAST(3,0,0)\ntypedef SDL_ThreadID platform_thread_id;\n#else\ntypedef SDL_threadID platform_thread_id;\n#endif\n\nstatic inline boolean platform_mutex_init(platform_mutex *mutex)\n{\n  platform_mutex m = SDL_CreateMutex();\n  if(m)\n  {\n    *mutex = m;\n    return true;\n  }\n  return false;\n}\n\nstatic inline boolean platform_mutex_destroy(platform_mutex *mutex)\n{\n  SDL_DestroyMutex(*mutex);\n  return true;\n}\n\nstatic inline boolean platform_mutex_lock(platform_mutex *mutex)\n{\n  // Returns void as of SDL 3.\n  SDL_LockMutex(*mutex);\n  return true;\n}\n\nstatic inline boolean platform_mutex_unlock(platform_mutex *mutex)\n{\n  // Returns void as of SDL 3.\n  SDL_UnlockMutex(*mutex);\n  return true;\n}\n\nstatic inline boolean platform_cond_init(platform_cond *cond)\n{\n  platform_cond c = SDL_CreateCondition();\n  if(c)\n  {\n    *cond = c;\n    return true;\n  }\n  return false;\n}\n\nstatic inline boolean platform_cond_destroy(platform_cond *cond)\n{\n  SDL_DestroyCondition(*cond);\n  return true;\n}\n\nstatic inline boolean platform_cond_wait(platform_cond *cond,\n platform_mutex *mutex)\n{\n  // Returns void as of SDL 3.\n  SDL_WaitCondition(*cond, *mutex);\n  return true;\n}\n\nstatic inline boolean platform_cond_timedwait(platform_cond *cond,\n platform_mutex *mutex, unsigned int timeout_ms)\n{\n  // Returns void as of SDL 3.\n  SDL_WaitConditionTimeout(*cond, *mutex, (Uint32)timeout_ms);\n  return true;\n}\n\nstatic inline boolean platform_cond_signal(platform_cond *cond)\n{\n  // Returns void as of SDL 3.\n  SDL_SignalCondition(*cond);\n  return true;\n}\n\nstatic inline boolean platform_cond_broadcast(platform_cond *cond)\n{\n  // Returns void as of SDL 3.\n  SDL_BroadcastCondition(*cond);\n  return true;\n}\n\nstatic inline boolean platform_sem_init(platform_sem *sem, unsigned init_value)\n{\n  platform_sem ret = SDL_CreateSemaphore(init_value);\n  if(ret)\n  {\n    *sem = ret;\n    return true;\n  }\n  return false;\n}\n\nstatic inline boolean platform_sem_destroy(platform_sem *sem)\n{\n  SDL_DestroySemaphore(*sem);\n  return true;\n}\n\nstatic inline boolean platform_sem_wait(platform_sem *sem)\n{\n  // Returns void as of SDL 3.\n  SDL_WaitSemaphore(*sem);\n  return true;\n}\n\nstatic inline boolean platform_sem_post(platform_sem *sem)\n{\n  // Returns void as of SDL 3.\n  SDL_SignalSemaphore(*sem);\n  return true;\n}\n\nstatic inline boolean platform_thread_create(platform_thread *thread,\n platform_thread_fn start_function, void *data)\n{\n#if SDL_VERSION_ATLEAST(2,0,0)\n  platform_thread t = SDL_CreateThread(start_function, \"\", data);\n#else\n  platform_thread t = SDL_CreateThread(start_function, data);\n#endif\n\n  if(t)\n  {\n    *thread = t;\n    return true;\n  }\n  return false;\n}\n\nstatic inline boolean platform_thread_join(platform_thread *thread)\n{\n  SDL_WaitThread(*thread, NULL);\n  return true;\n}\n\nstatic inline platform_thread_id platform_get_thread_id(void)\n{\n  return SDL_GetCurrentThreadID();\n}\n\nstatic inline boolean platform_is_same_thread(platform_thread_id a,\n platform_thread_id b)\n{\n  return a == b;\n}\n\n__M_END_DECLS\n\n#endif // __THREAD_SDL_H\n"
  },
  {
    "path": "src/thread_win32.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Fallback Windows threading implementation. This uses Win32 function calls\n * which should work all the way back to Windows 95. Since Win32 didn't have\n * condition variables until Vista, this is pretty hacky and terrible!\n * Only use this when SDL isn't available, e.g. for the utils.\n */\n\n#ifndef __THREAD_WIN32_H\n#define __THREAD_WIN32_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#define THREAD_RES DWORD WINAPI\n#define THREAD_RETURN do { return 0; } while(0)\n\ntypedef DWORD platform_cond;\ntypedef CRITICAL_SECTION platform_mutex;\ntypedef HANDLE platform_sem;\ntypedef HANDLE platform_thread;\ntypedef DWORD platform_thread_id;\ntypedef THREAD_RES (*platform_thread_fn)(void *);\n\nstatic inline boolean platform_mutex_init(platform_mutex *mutex)\n{\n  InitializeCriticalSection(mutex);\n  return true;\n}\n\nstatic inline boolean platform_mutex_destroy(platform_mutex *mutex)\n{\n  DeleteCriticalSection(mutex);\n  return true;\n}\n\nstatic inline boolean platform_mutex_lock(platform_mutex *mutex)\n{\n  EnterCriticalSection(mutex);\n  return true;\n}\n\nstatic inline boolean platform_mutex_unlock(platform_mutex *mutex)\n{\n  LeaveCriticalSection(mutex);\n  return true;\n}\n\n// TODO: Dynamically link Vista condvars or approximate with semaphores...\nstatic inline boolean platform_cond_init(platform_cond *cond)\n{\n  return false;\n}\n\nstatic inline boolean platform_cond_destroy(platform_cond *cond)\n{\n  return false;\n}\n\nstatic inline boolean platform_cond_timedwait(platform_cond *cond,\n platform_mutex *mutex, unsigned time)\n{\n  abort();\n  return false;\n}\n\nstatic inline boolean platform_cond_wait(platform_cond *cond,\n platform_mutex *mutex)\n{\n  abort();\n  return false;\n}\n\nstatic inline boolean platform_cond_signal(platform_cond *cond)\n{\n  abort();\n  return false;\n}\n\nstatic inline boolean platform_cond_broadcast(platform_cond *cond)\n{\n  abort();\n  return false;\n}\n\nstatic inline boolean platform_sem_init(platform_sem *sem, unsigned init_value)\n{\n  HANDLE s = CreateSemaphore(NULL, init_value, LONG_MAX, NULL);\n  if(s)\n  {\n    *sem = s;\n    return true;\n  }\n  return false;\n}\n\nstatic inline boolean platform_sem_destroy(platform_sem *sem)\n{\n  return CloseHandle(*sem);\n}\n\nstatic inline boolean platform_sem_wait(platform_sem *sem)\n{\n  if(WaitForSingleObject(*sem, INFINITE) != WAIT_OBJECT_0)\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_sem_post(platform_sem *sem)\n{\n  if(ReleaseSemaphore(*sem, 1, NULL) == 0)\n    return false;\n  return true;\n}\n\nstatic inline boolean platform_thread_create(platform_thread *thread,\n platform_thread_fn start_function, void *data)\n{\n  HANDLE t = CreateThread(NULL, 0, start_function, data, 0, NULL);\n  if(t)\n  {\n    *thread = t;\n    return true;\n  }\n  return false;\n}\n\nstatic inline boolean platform_thread_join(platform_thread *thread)\n{\n  if(WaitForSingleObject(*thread, INFINITE) != WAIT_OBJECT_0)\n    return false;\n  return true;\n}\n\nstatic inline platform_thread_id platform_get_thread_id(void)\n{\n  return GetCurrentThreadId();\n}\n\nstatic inline boolean platform_is_same_thread(platform_thread_id a,\n platform_thread_id b)\n{\n  return a == b;\n}\n\n__M_END_DECLS\n\n#endif /* __THREAD_WIN32_H */\n"
  },
  {
    "path": "src/updater.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2020-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"caption.h\"\n#include \"const.h\"\n#include \"core.h\"\n#include \"error.h\"\n#include \"event.h\"\n#include \"graphics.h\"\n#include \"helpsys.h\"\n#include \"platform.h\"\n#include \"updater.h\"\n#include \"util.h\"\n#include \"window.h\"\n#include \"io/memfile.h\"\n#include \"io/path.h\"\n#include \"io/vio.h\"\n\n#include \"editor/window.h\"\n\n#include \"network/HTTPHost.hpp\"\n#include \"network/Manifest.hpp\"\n#include \"network/Scoped.hpp\"\n\n#include <sys/types.h>\n#include <sys/stat.h>\n\n#include <assert.h>\n#include <ctype.h>\n#include <string.h>\n#include <errno.h>\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#ifndef PLATFORM\n#error Must define a valid \"friendly\" platform name!\n#endif\n\n#define MAX_RETRIES   3\n\n#define OUTBOUND_PORT 80\n#define LINE_BUF_LEN  256\n\n#define UPDATES_TXT   \"updates.txt\"\n#define DELETE_TXT    \"delete.txt\"\n\n#define UPDATES_TXT_MAX_SIZE ((size_t)1 << 20)\n\n#define WIDGET_BUF_LEN 80\n\nstatic char widget_buf[WIDGET_BUF_LEN];\n\nstatic char previous_dir[MAX_PATH];\n\nstatic long final_size = -1;\nstatic boolean cancel_update;\n\n/**\n * Functions and enum for updater startup error codes (namespace because no\n * enum classes in C++98...).\n */\nnamespace UpdaterInit\n{\n  enum StatusValue\n  {\n    SUCCESS,\n    UNINITIALIZED,\n    INCOMPATIBLE_ASSETS,\n    INCOMPATIBLE_CONFDIR,\n    FAILED_CHDIR_TO_EXEC_DIR,\n    FAILED_TO_STAT_MANIFEST,\n    FAILED_FILE_WRITE,\n    FAILED_FILE_UNLINK\n  };\n  static StatusValue status = UpdaterInit::UNINITIALIZED;\n\n  static boolean allow_full_updates()\n  {\n    return status == SUCCESS || status == FAILED_TO_STAT_MANIFEST;\n  }\n\n  static const char *status_string(StatusValue st)\n  {\n    switch(st)\n    {\n      case SUCCESS:\n        return \"updater initialization successful\";\n      case UNINITIALIZED:\n        return \"updater hasn't been initialized\";\n      case INCOMPATIBLE_ASSETS:\n      case INCOMPATIBLE_CONFDIR:\n        return \"platform not configured for full updates\";\n      case FAILED_CHDIR_TO_EXEC_DIR:\n        return \"failed to chdir to executable dir\";\n      case FAILED_TO_STAT_MANIFEST:\n        return \"failed to stat manifest.txt\";\n      case FAILED_FILE_WRITE:\n        return \"failed to write to executable dir\";\n      case FAILED_FILE_UNLINK:\n        return \"failed to delete file from executable dir\";\n    }\n    return \"unknown error\";\n  }\n\n#ifdef IS_CXX_11\n  static constexpr int const_strcmp(const char *a, const char *b)\n  {\n    return (a[0] == b[0] && (a[0] == '\\0' || const_strcmp(a + 1, b + 1))) ? 0 : -1;\n  }\n#else\n#define const_strcmp(a,b) strcmp(a,b)\n#endif\n\n  /**\n   * Compile time checks for the paranoid to disable the parts of the updater\n   * that can delete things on UNIX-like platforms and Mac OS X.\n   */\n  static maybe_constexpr StatusValue _const_safety_checks()\n  {\n    /**\n     * The config file should exist in the executable dir.\n     * If it doesn't, this is probably a UNIX-like platform or Mac OS X.\n     * TODO other checks may be possible here e.g. make sure SHAREDIR is\n     * prefixed by GAMEDIR--requires GAMEDIR being added to config.h.\n     */\n    return const_strcmp(CONFDIR, \"./\") ? INCOMPATIBLE_CONFDIR : SUCCESS;\n  }\n  static maybe_constexpr const StatusValue const_safety_checks = _const_safety_checks();\n}\n\nenum new_version_opts\n{\n  OPT_EXIT,\n  OPT_TRY_NEXT_HOST,\n  OPT_UPDATE_CURRENT\n};\n\n/**\n * A new version has been released and there are updates available on the\n * current host for this platform. Prompt the user to either update to the\n * new version or attempt to update the current version. Returns the version\n * selected by the user, or NULL if canceled by the user.\n */\nstatic const char *ui_new_version_available(context *ctx,\n const char *current_ver, const char *new_ver)\n{\n  struct world *mzx_world = ctx->world;\n  struct element *elements[6];\n  struct dialog di;\n  int result;\n\n  int buf_len = snprintf(widget_buf, WIDGET_BUF_LEN,\n   \"A new version is available (%s)\", new_ver);\n  widget_buf[WIDGET_BUF_LEN - 1] = 0;\n  buf_len = MAX(0, buf_len);\n\n  elements[0] = construct_label((55 - buf_len) >> 1, 2, widget_buf);\n\n  elements[1] = construct_label(2, 4,\n   \"You can continue to receive updates for the version\\n\"\n   \"installed (if available), or you can upgrade to the\\n\"\n   \"newest version (recommended).\");\n\n  elements[2] = construct_label(2, 8,\n   \"If you do not upgrade, this question will be asked\\n\"\n   \"again the next time you run the updater.\\n\");\n\n  elements[3] = construct_button(9, 11, \"Upgrade\", 0);\n  elements[4] = construct_button(21, 11, \"Update Old\", 1);\n  elements[5] = construct_button(36, 11, \"Cancel\", 2);\n\n  construct_dialog(&di, \"New Version\", 12, 6, 55, 14, elements, 6, 3);\n  result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  // User pressed Escape, abort all updates\n  if(result < 0 || result == 2)\n    return NULL;\n\n  // User pressed Upgrade, use new version.\n  if(result == 0)\n    return new_ver;\n\n  // Check for updates on the current version.\n  return current_ver;\n}\n\n/**\n * A new version has been released, but either there are no updates available\n * on the current host or something went wrong in initializing the updater.\n * Either way, the update can't be performed, so explain why and possibly\n * offer some other useful options.\n */\nstatic enum new_version_opts ui_new_version_error(context *ctx,\n const char *new_ver, boolean platform_has_remote_manifest)\n{\n  char reason[LINE_BUF_LEN];\n  struct world *mzx_world = ctx->world;\n  struct element *elements[7];\n  struct dialog di;\n  int result;\n  int num_elements = ARRAY_SIZE(elements) - 2;\n\n  int buf_len = snprintf(widget_buf, WIDGET_BUF_LEN,\n   \"A new version is available (%s)\", new_ver);\n  widget_buf[WIDGET_BUF_LEN - 1] = '\\0';\n  buf_len = MAX(0, buf_len);\n\n  elements[0] = construct_label((55 - buf_len) >> 1, 2, widget_buf);\n\n  elements[1] = construct_label(2, 4,\n   \"This update can not be downloaded from the remote\\n\"\n   \"host for the following reason(s):\");\n\n  int reason_lines = 0;\n  buf_len = 0;\n  if(UpdaterInit::const_safety_checks != UpdaterInit::SUCCESS)\n  {\n    int res = snprintf(reason + buf_len, LINE_BUF_LEN - buf_len,\n     \"* This platform does not support updates.\\n\");\n    buf_len += MAX(0, res);\n    reason_lines += 1;\n  }\n  else\n  {\n    if(!platform_has_remote_manifest)\n    {\n      int res = snprintf(reason + buf_len, LINE_BUF_LEN - buf_len,\n       \"* The remote host does not have updates for your\\n\"\n       \"  platform (%s).\\n\", PLATFORM);\n      buf_len += MAX(0, res);\n      reason_lines += 2;\n    }\n\n    if(!UpdaterInit::allow_full_updates())\n    {\n      int res = snprintf(reason + buf_len, LINE_BUF_LEN - buf_len,\n       \"* The updater failed to initialize on startup:\\n\"\n       \"  %s.\\n\", UpdaterInit::status_string(UpdaterInit::status));\n      buf_len += MAX(0, res);\n      reason_lines += 2;\n    }\n  }\n  elements[2] = construct_label(2, 7, reason);\n\n  elements[3] = construct_label(2, 8 + reason_lines,\n   \"Check digitalmzx.com or github.com/AliceLR/megazeux\\n\"\n   \"to download the latest release for your platform.\");\n\n  if(UpdaterInit::allow_full_updates())\n  {\n    elements[4] = construct_button(9, 11 + reason_lines, \"OK\", OPT_EXIT);\n    elements[5] = construct_button(16, 11 + reason_lines, \"Update Old\",\n     OPT_UPDATE_CURRENT);\n    elements[6] = construct_button(31, 11 + reason_lines, \"Try next host\",\n     OPT_TRY_NEXT_HOST);\n\n    num_elements += 2;\n  }\n  else\n    elements[4] = construct_button(25, 11 + reason_lines, \"OK\", OPT_EXIT);\n\n  int h = 12 + reason_lines + 2;\n  int y = (25 - h) / 2;\n\n  construct_dialog(&di, \"New Version\", 12, y, 55, h, elements, num_elements, 4);\n  result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  // User pressed \"Update Old\"; attempt to update the current version.\n  if(result == OPT_UPDATE_CURRENT)\n    return OPT_UPDATE_CURRENT;\n\n  // User pressed \"Try next host\"\n  if(result == OPT_TRY_NEXT_HOST)\n    return OPT_TRY_NEXT_HOST;\n\n  // User pressed escape or OK; abort.\n  return OPT_EXIT;\n}\n\n/**\n * No changes have been detected between the local manifest and the remote\n * manifest. Prompt the user to either try the next host or to abort. Return\n * true if the user selected to try the next host, otherwise false.\n */\nstatic boolean ui_version_is_current(context *ctx, boolean has_next_host)\n{\n  struct world *mzx_world = ctx->world;\n  struct element *elements[3];\n  struct dialog di;\n  int result;\n\n  elements[0] = construct_label(2, 2, \"This client is already current.\");\n  elements[1] = construct_button(7, 4, \"OK\", 0);\n  elements[2] = construct_button(13, 4, \"Try next host\", 1);\n\n  construct_dialog(&di, \"No Updates\", 22, 9, 35, 6, elements, 3, 1);\n  result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  if((result == 1) && has_next_host)\n    return true;\n\n  return false;\n}\n\n/**\n * Show the user the list of changes to be applied. Return the number of\n * changes to be applied (or 0 if canceled).\n */\nstatic int ui_confirm_changes(context *ctx, const Manifest &removed,\n const Manifest &replaced, const Manifest &added)\n{\n  const ManifestEntry *e;\n  char **list_entries;\n  int list_entry_width = 0;\n  int entries = 0;\n  int result;\n  int i = 0;\n\n  for(e = removed.first(); e; e = e->next, entries++)\n    list_entry_width = MAX(list_entry_width, 2 + (int)strlen(e->name)+1+1);\n  for(e = replaced.first(); e; e = e->next, entries++)\n    list_entry_width = MAX(list_entry_width, 2 + (int)strlen(e->name)+1+1);\n  for(e = added.first(); e; e = e->next, entries++)\n    list_entry_width = MAX(list_entry_width, 2 + (int)strlen(e->name)+1+1);\n\n  // We don't want the listbox to be too wide\n  list_entry_width = MIN(list_entry_width, 60);\n\n  list_entries = (char **)cmalloc(entries * sizeof(char *));\n\n  for(e = removed.first(); e; e = e->next, i++)\n  {\n    list_entries[i] = (char *)cmalloc(list_entry_width);\n    snprintf(list_entries[i], list_entry_width, \"- %s\", e->name);\n    list_entries[i][list_entry_width - 1] = 0;\n  }\n\n  for(e = replaced.first(); e; e = e->next, i++)\n  {\n    list_entries[i] = (char *)cmalloc(list_entry_width);\n    snprintf(list_entries[i], list_entry_width, \"* %s\", e->name);\n    list_entries[i][list_entry_width - 1] = 0;\n  }\n\n  for(e = added.first(); e; e = e->next, i++)\n  {\n    list_entries[i] = (char *)cmalloc(list_entry_width);\n    snprintf(list_entries[i], list_entry_width, \"+ %s\", e->name);\n    list_entries[i][list_entry_width - 1] = 0;\n  }\n\n  draw_window_box(19, 1, 59, 4, DI_MAIN, DI_DARK, DI_CORNER, 1, 1);\n  write_string(\" Task Summary \", 33, 1, DI_TITLE, 0);\n  write_string(\"ESC   - Cancel   [+] Add   [-] Delete\", 21, 2, DI_TEXT, 0);\n  write_string(\"ENTER - Proceed  [*] Replace  \", 21, 3, DI_TEXT, 0);\n\n  result = list_menu((const char **)list_entries, list_entry_width,\n   NULL, 0, entries, ((80 - (list_entry_width + 9)) >> 1) + 1, 4);\n\n  for(i = 0; i < entries; i++)\n    free(list_entries[i]);\n  free(list_entries);\n\n  clear_screen();\n  update_screen();\n\n  if(result >= 0)\n    return entries;\n\n  return 0;\n}\n\n/**\n * Inform the user that the update process is complete.\n */\nstatic void ui_update_finished(context *ctx)\n{\n  struct world *mzx_world = ctx->world;\n  struct element *elements[2];\n  struct dialog di;\n\n  elements[0] = construct_label(2, 2,\n   \"This client will now attempt to restart itself.\");\n  elements[1] = construct_button(23, 4, \"OK\", 0);\n\n  construct_dialog(&di, \"Update Successful\", 14, 9, 51, 6, elements, 2, 1);\n  run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n}\n\n/**\n * Clear the screen.\n */\nstatic void display_clear(void)\n{\n  clear_screen();\n  update_screen();\n}\n\n/**\n * Indicate that the network is currently being initialized.\n */\nstatic void display_initializing()\n{\n  static const char str[] = \"Initializing network...\";\n  m_hide();\n  draw_window_box(3, 11, 76, 13, DI_MAIN, DI_DARK, DI_CORNER, 1, 1);\n  write_string(str, (WIDGET_BUF_LEN - strlen(str)) / 2, 12, DI_TEXT, 0);\n  update_screen();\n  m_show();\n}\n\n/**\n * Indicate that the client is currently connecting to a remote host.\n */\nstatic void display_connecting(const char *host_name)\n{\n  size_t buf_len;\n\n  buf_len = snprintf(widget_buf, WIDGET_BUF_LEN,\n   \"Connecting to \\\"%s\\\". Please wait..\", host_name);\n  widget_buf[WIDGET_BUF_LEN - 1] = 0;\n\n  m_hide();\n  draw_window_box(3, 11, 76, 13, DI_MAIN, DI_DARK, DI_CORNER, 1, 1);\n  write_string(widget_buf, (WIDGET_BUF_LEN - buf_len) >> 1, 12, DI_TEXT, 0);\n  update_screen();\n  m_show();\n}\n\n/**\n * Indicate that MZX is currently trying to determine if a remote manifest\n * exists for this platform.\n */\nstatic void display_manifest_check()\n{\n  static const char str[] = \"Checking for remote \" MANIFEST_TXT \"..\";\n  m_hide();\n  draw_window_box(3, 11, 76, 13, DI_MAIN, DI_DARK, DI_CORNER, 1, 1);\n  write_string(str, (WIDGET_BUF_LEN - strlen(str)) / 2, 12, DI_TEXT, 0);\n  update_screen();\n  m_show();\n}\n\n/**\n * Indicate that the manifest is currently being processed.\n */\nstatic void display_manifest(void)\n{\n  static const char *str = \"Fetching remote \" MANIFEST_TXT \"..\";\n  m_hide();\n  draw_window_box(3, 11, 76, 13, DI_MAIN, DI_DARK, DI_CORNER, 1, 1);\n  write_string(str, (WIDGET_BUF_LEN - strlen(str)) / 2, 12, DI_TEXT, 0);\n  update_screen();\n  m_show();\n}\n\n/**\n * Indicate the current download.\n */\nstatic void display_download_init(const char *filename, int cur, int total)\n{\n  char name[72];\n\n  m_hide();\n  snprintf(name, 72, \"%s (%ldb) [%u/%u]\", filename, final_size, cur, total);\n  meter(name, 0, final_size);\n  update_screen();\n  m_show();\n}\n\n/**\n * Update the download progress bar.\n */\nstatic void display_download_update(long progress)\n{\n  m_hide();\n  meter_interior(progress, final_size);\n  update_screen();\n  m_show();\n}\n\nstatic boolean check_prune_basedir(const char *file)\n{\n  char path[MAX_PATH];\n  ssize_t ret;\n\n  ret = path_get_directory(path, MAX_PATH, file);\n  if(ret < 0)\n  {\n    error_message(E_UPDATE, 0, \"Failed to prune directories (path too long)\");\n    return false;\n  }\n\n  // This file has no base directory\n  if(ret == 0)\n    return true;\n\n  // Attempt to remove the directory.\n  while(!vrmdir(path))\n  {\n    ssize_t len = strlen(path);\n    info(\"--UPDATER-- Pruned empty directory '%s'\\n\", path);\n\n    // If that worked, also try to remove the parent directory recursively.\n    ssize_t ret = path_navigate(path, MAX_PATH, PATH_PARENT_DIR);\n    if(ret < 0 || ret >= len)\n      break;\n  }\n  return true;\n}\n\nstatic boolean check_create_basedir(const char *file)\n{\n  enum path_create_error ret = path_create_parent_recursively(file);\n  if(ret)\n    warn(\"Failed to mkdir() parent directory of '%s'\\n\", file);\n\n  switch(ret)\n  {\n    case PATH_CREATE_SUCCESS:\n      return true;\n\n    case PATH_CREATE_ERR_BUFFER:\n      error_message(E_UPDATE, 1, \"Failed to mkdir(); path is too long\");\n      break;\n\n    case PATH_CREATE_ERR_STAT_ERROR:\n      error_message(E_UPDATE, 1, \"Failed to mkdir(); unknown stat error occurred\");\n      break;\n\n    case PATH_CREATE_ERR_MKDIR_FAILED:\n      error_message(E_UPDATE, 1, \"Failed to mkdir(); check permissions\");\n      break;\n\n    case PATH_CREATE_ERR_FILE_EXISTS:\n    {\n      error_message(E_UPDATE, 1,\n       \"Failed to mkdir(); file exists with the specified name\");\n      break;\n    }\n  }\n  return false;\n}\n\nstatic void check_cancel_update(void)\n{\n  update_event_status();\n  if(get_key(keycode_internal) == IKEY_ESCAPE\n   || get_exit_status())\n    cancel_update = true;\n}\n\nstatic void recv_cb(long offset)\n{\n  check_cancel_update();\n\n  if(final_size > 0 && offset > final_size)\n  {\n    error_message(E_UPDATE, 4,\n     \"Transferred more than expected uncompressed size.\");\n    cancel_update = true;\n    return;\n  }\n\n  display_download_update(offset);\n}\n\nstatic boolean cancel_cb(void)\n{\n  return cancel_update;\n}\n\nstatic boolean swivel_current_dir(boolean have_video)\n{\n  const char *executable_dir = mzx_res_get_by_id(MZX_EXECUTABLE_DIR);\n  char base_path[MAX_PATH];\n\n  if(!executable_dir)\n  {\n    if(have_video)\n      error_message(E_UPDATE, 25,\n       \"Updater: couldn't determine install directory.\");\n    else\n      warn(\"--UPDATER-- Couldn't determine install directory.\\n\");\n    return false;\n  }\n\n  // Store the user's current directory, so we can get back to it\n  vgetcwd(previous_dir, MAX_PATH);\n\n  if(vchdir(executable_dir))\n  {\n    info(\"--UPDATER-- getcwd(): %s\\n\", previous_dir);\n    info(\"--UPDATER-- attempted chdir() to: %s\\n\", base_path);\n    if(have_video)\n      error_message(E_UPDATE, 5,\n       \"Updater: failed to change into install directory.\");\n    else\n      warn(\"--UPDATER-- Failed to change into install directory.\\n\");\n    return false;\n  }\n  trace(\"--UPDATER-- chdir() to exec dir successful: %s\\n\", executable_dir);\n  return true;\n}\n\nstatic boolean swivel_current_dir_back(boolean have_video)\n{\n  if(vchdir(previous_dir))\n  {\n    if(have_video)\n      error_message(E_UPDATE, 6,\n       \"Updater: failed to change back to user directory.\");\n    else\n      warn(\"--UPDATER-- Failed to change back to user directory.\\n\");\n    return false;\n  }\n  trace(\"--UPDATER-- chdir() to user dir successful: %s\\n\", previous_dir);\n  return true;\n}\n\n/**\n * Replace the old manifest with the new manifest.\n * This should only be called if the update was successful.\n */\nstatic boolean replace_manifest(void)\n{\n  struct stat s;\n\n  // The new manifest should exist...\n  if(vstat(REMOTE_MANIFEST_TXT, &s))\n    return false;\n\n  // Remove the old manifest.\n  if(vunlink(LOCAL_MANIFEST_TXT))\n  {\n    error_message(E_UPDATE, 7,\n     \"Failed to remove \" LOCAL_MANIFEST_TXT \". Check permissions.\");\n    return false;\n  }\n\n  // Rename the new manifest.\n  if(vrename(REMOTE_MANIFEST_TXT, LOCAL_MANIFEST_TXT))\n  {\n    error_message(E_UPDATE, 8,\n     \"Failed to rename \" REMOTE_MANIFEST_TXT \". Check permissions.\");\n    return false;\n  }\n\n  return true;\n}\n\nstatic boolean write_delete_list(const Manifest &delete_list)\n{\n  if(!delete_list.write_to_file(DELETE_TXT))\n  {\n    error_message(E_UPDATE, 9,\n     \"Failed to create \\\"\" DELETE_TXT \"\\\". Check permissions.\");\n    return false;\n  }\n  return true;\n}\n\nstatic void apply_delete_list(Manifest &delete_list)\n{\n  ManifestEntry *e_next = delete_list.first();\n  ManifestEntry *e_prev = NULL;\n  ManifestEntry *e;\n  int retry_times = 0;\n  struct stat s;\n  boolean files_failed = false;\n\n  while(e_next)\n  {\n    e = e_next;\n    e_next = e->next;\n\n    if(!vstat(e->name, &s))\n    {\n      if(e->validate())\n      {\n        if(vunlink(e->name))\n          goto err_delete_failed;\n\n        info(\"--UPDATER-- Deleted '%s'\\n\", e->name);\n        // Also try to delete the base directory. If it's empty, this won't\n        // do anything.\n        check_prune_basedir(e->name);\n      }\n      else\n        info(\"--UPDATER-- Skipping invalid entry '%s'\\n\", e->name);\n    }\n\n    // File was removed, doesn't exist, or is non-applicable; remove from list\n    if(delete_list.head == e)\n      delete_list.head = e_next;\n\n    // Keep the link on the last failed file up-to-date.\n    if(e_prev)\n      e_prev->next = e_next;\n\n    delete e;\n    continue;\n\nerr_delete_failed:\n    {\n      int errval;\n\n      warn(\"--UPDATER-- Failed to delete '%s'\\n\", e->name);\n\n      switch(retry_times)\n      {\n        case 0:\n        {\n          if(!strcmp(e->name, \"mzx_help.fil\") ||\n           !strcmp(e->name, \"assets/help.fil\"))\n          {\n            // HACK: Older MZX versions do not properly close these files\n            // because that would have been too easy. Silently skip them for\n            // now; they'll be deleted the next time MZX is started.\n            errval = ERROR_OPT_FAIL;\n            break;\n          }\n\n          // 1st failure: delay a little bit and then retry automatically.\n          delay(200);\n          errval = ERROR_OPT_RETRY;\n          break;\n        }\n\n        case 1:\n        {\n          // 2nd failure: give user the option to either retry or fail.\n          char buf[72];\n          snprintf(buf, 72, \"Failed to delete \\\"%.30s\\\". Retry?\", e->name);\n          buf[71] = 0;\n\n          errval = error_message(E_UPDATE_RETRY, 10, buf);\n          break;\n        }\n\n        default:\n        {\n          // 3rd failure: auto fail so we're not here all day. Also, display\n          // an error message when this loop is finished.\n          errval = ERROR_OPT_FAIL;\n          files_failed = true;\n          break;\n        }\n      }\n\n      if(errval == ERROR_OPT_RETRY)\n      {\n        info(\"--UPDATER-- Retrying '%s'...\\n\", e->name);\n        retry_times++;\n\n        // Set the next file to this file to try again...\n        e_next = e;\n      }\n      else\n      {\n        info(\"--UPDATER-- Skipping '%s'...\\n\", e->name);\n        retry_times = 0;\n\n        // Track this file so its link can be updated.\n        e_prev = e;\n      }\n      continue;\n    }\n  }\n\n  if(files_failed)\n    error_message(E_UPDATE, 24,\n     \"Failed to delete files; check permissions and restart MegaZeux\");\n}\n\nstatic boolean reissue_connection(HTTPHost &http, const char *host_name,\n boolean is_automatic)\n{\n  boolean ret = false;\n\n  /**\n   * The provided host may have an existing connection; in this case, close the\n   * connection so a new one can be opened.\n   */\n  http.close();\n\n  // If this is an automatic check, make the timeout duration shorter than the\n  // default so it's less annoying.\n  if(is_automatic)\n    http.set_timeout_ms(1000);\n\n  display_connecting(host_name);\n\n  if(!http.connect(host_name, OUTBOUND_PORT))\n  {\n    if(!is_automatic)\n    {\n      snprintf(widget_buf, WIDGET_BUF_LEN,\n       \"Connection to \\\"%s\\\" failed.\", host_name);\n      widget_buf[WIDGET_BUF_LEN - 1] = 0;\n      error_message(E_UPDATE, 12, widget_buf);\n    }\n  }\n  else\n  {\n    trace(\"--UPDATER-- Successfully connected to %s:%d.\\n\", host_name, OUTBOUND_PORT);\n    ret = true;\n  }\n\n  display_clear();\n  return ret;\n}\n\n/**\n * Run a synchronous update check.\n * @param ctx           Current context\n * @param is_automatic  Disable more annoying UI displays for automated checks.\n * @return              true if the update completed, otherwise false.\n */\nstatic boolean __check_for_updates(context *ctx, boolean is_automatic)\n{\n  const struct config_info *conf = get_config();\n  int cur_host;\n  const char *update_host;\n  boolean in_exec_dir = false;\n  boolean delete_remote_manifest = false;\n  boolean try_next_host = true;\n  boolean ret = false;\n\n  set_error_suppression(E_UPDATE, false);\n\n  if(conf->update_host_count < 1)\n  {\n    error_message(E_UPDATE, 14, \"No updater hosts defined! Aborting.\");\n    return false;\n  }\n\n  display_initializing();\n  if(!Host::host_layer_init_check())\n  {\n    error_message(E_UPDATE, 15, \"Failed to initialize network layer.\");\n    display_clear();\n    return false;\n  }\n  display_clear();\n\n  set_context(CTX_UPDATER);\n\n  ScopedBuffer<char> updates_txt(LINE_BUF_LEN);\n  ScopedBuffer<char> url_base(LINE_BUF_LEN);\n\n  for(cur_host = 0; (cur_host < conf->update_host_count) && try_next_host; cur_host++)\n  {\n    HTTPHost http(HOST_TYPE_TCP, conf->network_address_family);\n    HTTPRequestInfo request;\n    HTTPHostStatus status;\n\n    Manifest removed, replaced, added, delete_list;\n    ManifestEntry *e;\n\n    char buffer[LINE_BUF_LEN];\n    char *value = nullptr;\n    int i = 0;\n    int entries = 0;\n    char update_branch[LINE_BUF_LEN];\n    const char *version = VERSION;\n    unsigned int retries;\n    boolean reconnect = false;\n\n    update_host = conf->update_hosts[cur_host];\n\n    for(retries = 0; retries < MAX_RETRIES; retries++)\n    {\n      if(!reissue_connection(http, update_host, is_automatic))\n        goto err_try_next_host;\n\n      // Grab the file containing the names of the current Stable and Unstable\n      request.clear();\n      strcpy(request.url, \"/\" UPDATES_TXT);\n      request.allowed_types = HTTPRequestInfo::plaintext_types;\n\n      status = http.get(request, updates_txt, updates_txt.size());\n\n      if(status == HOST_SUCCESS)\n        break;\n\n      trace(\"--UPDATER-- Failed to fetch \" UPDATES_TXT \".\\n\");\n\n      // Stop early on redirect and client error codes\n      if(status == HOST_HTTP_REDIRECT || status == HOST_HTTP_CLIENT_ERROR)\n      {\n        retries = MAX_RETRIES;\n        break;\n      }\n\n      /**\n       * In the unlikely event this legitimately failed because updates.txt is\n       * too big for the buffer, expand it. Use x2 the reported Content-Length\n       * since update hosts should almost always be serving gzipped files.\n       */\n      if(status == HOST_FWRITE_FAILED)\n      {\n        size_t new_size = MAX(request.content_length, updates_txt.size()) * 2;\n        if(!updates_txt.resize(MIN(new_size, UPDATES_TXT_MAX_SIZE)))\n        {\n          retries = MAX_RETRIES;\n          break;\n        }\n      }\n    }\n\n    if(retries == MAX_RETRIES)\n    {\n      if(!is_automatic)\n      {\n        warn(\"Failed to download \\\"\" UPDATES_TXT \"\\\" (code %d; error: %s)\\n\",\n         request.status_code, HTTPHost::get_error_string(status));\n        request.print_response();\n\n        snprintf(widget_buf, WIDGET_BUF_LEN, \"Failed to download \\\"\"\n         UPDATES_TXT \"\\\" (%d/%d).\\n\", request.status_code, status);\n        widget_buf[WIDGET_BUF_LEN - 1] = 0;\n        error_message(E_UPDATE, 16, widget_buf);\n      }\n      goto err_try_next_host;\n    }\n\n    trace(\"--UPDATER-- Checking \" UPDATES_TXT \" for updates.\\n\");\n    snprintf(update_branch, LINE_BUF_LEN, \"Current-%.240s\",\n     conf->update_branch_pin);\n\n    // Walk this list (of two, hopefully)\n    struct memfile mf;\n    mfopen(updates_txt, request.final_length, &mf);\n    while(mfsafegets(buffer, LINE_BUF_LEN, &mf))\n    {\n      char *m = buffer;\n      char *key;\n      value = nullptr;\n\n      key = strsep(&m, \":\\n\");\n      if(!key)\n        break;\n\n      value = strsep(&m, \":\\n\");\n      if(!value)\n        break;\n\n      if(strcmp(key, update_branch) == 0)\n        break;\n    }\n\n    /* There was no \"Current-XXX: Version\" found; we cannot proceed with the\n     * update because we cannot compute an update URL below.\n     */\n    if(!value)\n    {\n      if(!is_automatic)\n        error_message(E_UPDATE, 17,\n         \"Failed to identify applicable update version.\");\n      goto err_try_next_host;\n    }\n\n    /* There's likely to be a space prepended to the version number.\n     * Skip it here.\n     */\n    while(isspace(*value))\n      value++;\n\n    /* We found the latest update version, but we should check to see if that\n     * matches the version we're already using. The user may choose to receive\n     * \"stability\" updates for their current major version, or upgrade to the\n     * newest one.\n     */\n    if(strcmp(value, version) != 0)\n    {\n      // Notify the user that updates are available.\n      caption_set_updates_available(true);\n\n      // If this is an auto check and silent mode is enabled, we can stop here.\n      if(is_automatic && conf->update_auto_check == UPDATE_AUTO_CHECK_SILENT)\n      {\n        try_next_host = false;\n        goto err_try_next_host;\n      }\n\n      /**\n       * Compute a temporary url base and use it to check if the current branch\n       * supports full updates for this platform.\n       */\n      display_manifest_check();\n\n      snprintf(url_base, LINE_BUF_LEN, \"/%s/\" PLATFORM, value);\n      boolean platform_has_remote_manifest =\n       Manifest::check_if_remote_exists(http, request, url_base);\n\n      display_clear();\n\n      if(!platform_has_remote_manifest || !UpdaterInit::allow_full_updates())\n      {\n        switch(ui_new_version_error(ctx, value, platform_has_remote_manifest))\n        {\n          case OPT_EXIT:\n            version = nullptr;\n            break;\n          case OPT_UPDATE_CURRENT:\n            version = VERSION;\n            break;\n          case OPT_TRY_NEXT_HOST:\n            goto err_try_next_host;\n        }\n      }\n      else\n        version = ui_new_version_available(ctx, version, value);\n\n      reconnect = true;\n\n      // Abort if no version was selected.\n      if(version == NULL)\n      {\n        try_next_host = false;\n        goto err_try_next_host;\n      }\n    }\n\n    /**\n     * To continue, the updater needs to switch to the executable directory.\n     * Platforms that don't pass the startup checks should stop here. Explicitly\n     * checking the constexpr check result here lets the compiler optimize out\n     * large sections of inaccessible code for platforms like Linux.\n     */\n    if(UpdaterInit::const_safety_checks != UpdaterInit::SUCCESS ||\n     !UpdaterInit::allow_full_updates())\n      break;\n\n    if(!in_exec_dir)\n    {\n      if(!swivel_current_dir(true))\n        break;\n\n      in_exec_dir = true;\n    }\n\n    /* We can now compute a unique URL base for the updater. This will\n     * be composed of a user-selected version and a static platform-archicture\n     * name.\n     */\n    snprintf(url_base, LINE_BUF_LEN, \"/%s/\" PLATFORM, version);\n    debug(\"Update base URL: %s\\n\", url_base.data());\n\n    for(retries = 0; retries < MAX_RETRIES; retries++)\n    {\n      if(reconnect && !reissue_connection(http, update_host, false))\n        goto err_try_next_host;\n\n      display_manifest();\n\n      status = Manifest::get_updates(http, request, url_base,\n       removed, replaced, added);\n      delete_remote_manifest = true;\n\n      display_clear();\n\n      if(status == HOST_SUCCESS)\n        break;\n\n      // Unsupported platform.\n      if(status == HOST_HTTP_REDIRECT || status == HOST_HTTP_CLIENT_ERROR)\n      {\n        error_message(E_UPDATE, 19, \"No updates available for this platform.\");\n        goto err_try_next_host;\n      }\n      reconnect = true;\n    }\n\n    if(retries == MAX_RETRIES)\n    {\n      error_message(E_UPDATE, 20, \"Failed to fetch or process update manifest\");\n      goto err_try_next_host;\n    }\n\n    // At this point, we have a successful manifest, so we won't need another host\n    try_next_host = false;\n\n    if(!removed.has_entries() && !replaced.has_entries() && !added.has_entries())\n    {\n      boolean has_next_host = (cur_host < conf->update_host_count);\n\n      if(is_automatic)\n        goto err_try_next_host;\n\n      // The user may want to attempt an update from the next host.\n      try_next_host = ui_version_is_current(ctx, has_next_host);\n      goto err_try_next_host;\n    }\n\n    // Set the updates available notification if it hasn't been set already.\n    caption_set_updates_available(true);\n\n    // Switch back to the normal checking timeout for the rest of the process.\n    if(is_automatic)\n    {\n      if(conf->update_auto_check == UPDATE_AUTO_CHECK_SILENT)\n        goto err_try_next_host;\n\n      http.set_timeout_ms(Host::TIMEOUT_DEFAULT);\n      is_automatic = 0;\n    }\n\n    /* Show the user the list of changes to perform and prompt the user to\n     * confirm or cancel.\n     */\n    entries = ui_confirm_changes(ctx, removed, replaced, added);\n    reconnect = true;\n\n    if(entries <= 0)\n      goto err_try_next_host;\n\n    /* Defer deletions until we restart; any of these files may still be\n     * in use by this (old) process. Reduce the number of entries by the\n     * number of removed items for the progress meter below. Since the\n     * local manifest might be wrong, generate new manifest entries for this.\n     */\n    for(e = removed.first(); e; e = e->next, entries--)\n      delete_list.append(ManifestEntry::create_from_file(e->name));\n\n    /* Since the operations for adding and replacing a file are identical,\n     * move the added list to the end of the replaced list.\n     */\n    replaced.append(added);\n\n    cancel_update = false;\n    http.set_callbacks(NULL, recv_cb, cancel_cb);\n\n    i = 1;\n    for(e = replaced.first(); e; e = e->next, i++)\n    {\n      for(retries = 0; retries < MAX_RETRIES; retries++)\n      {\n        boolean m_ret;\n\n        if(reconnect)\n        {\n          if(reissue_connection(http, update_host, 0))\n          {\n            http.set_callbacks(NULL, recv_cb, cancel_cb);\n            reconnect = false;\n          }\n          else\n            goto err_try_next_host;\n        }\n\n        if(!check_create_basedir(e->name))\n          goto err_try_next_host;\n\n        final_size = (long)e->size;\n        display_download_init(e->name, i, entries);\n\n        m_ret = Manifest::download_and_replace_entry(http, request, url_base,\n         e, delete_list);\n\n        display_clear();\n\n        if(m_ret)\n          break;\n\n        if(cancel_update)\n        {\n          error_message(E_UPDATE, 21, \"Download was cancelled; update aborted.\");\n          goto err_try_next_host;\n        }\n        reconnect = true;\n      }\n\n      if(retries == MAX_RETRIES)\n      {\n        snprintf(widget_buf, WIDGET_BUF_LEN,\n         \"Failed to download \\\"%s\\\" (after %d attempts).\", e->name, retries);\n        widget_buf[WIDGET_BUF_LEN - 1] = 0;\n        error_message(E_UPDATE, 22, widget_buf);\n        goto err_try_next_host;\n      }\n    }\n\n    if(!write_delete_list(delete_list))\n      goto err_try_next_host;\n\n    delete_remote_manifest = false;\n    if(!replace_manifest())\n      goto err_try_next_host;\n\n    try_next_host = false;\n    ret = true;\nerr_try_next_host:\n    http.close();\n  } //end host for loop\n\n  if(delete_remote_manifest)\n    vunlink(REMOTE_MANIFEST_TXT);\n\n  if(in_exec_dir)\n    swivel_current_dir_back(true);\n\n  pop_context();\n\n  if(ret)\n  {\n    // Inform the user the update was successful.\n    ui_update_finished(ctx);\n\n    // Signal core to exit and restart MZX.\n    core_full_restart(ctx);\n    return true;\n  }\n  return false;\n}\n\n/**\n * Perform some sanity checks to make sure certain parts of this code will\n * never run on platforms that don't support it (e.g. Linux).\n */\nstatic UpdaterInit::StatusValue updater_init_safety_checks()\n{\n  /**\n   * The assets should should be relative to the executable dir.\n   * If they aren't, this is probably the same as above. Use the protected\n   * charset to test this (though this really should apply to all assets).\n   */\n  const char *executable_dir = mzx_res_get_by_id(MZX_EXECUTABLE_DIR);\n  const char *edit_chr = mzx_res_get_by_id(MZX_EDIT_CHR);\n  size_t len = strlen(executable_dir);\n  if(strncmp(executable_dir, edit_chr, len))\n    return UpdaterInit::INCOMPATIBLE_ASSETS;\n\n  return UpdaterInit::SUCCESS;\n}\n\n/**\n * Perform several steps to determine whether full updater capabilities are\n * available in the exec dir.\n *\n * 1) File read + make sure manifest.txt exists.\n * 2) File create.\n * 3) File unlink.\n */\nstatic UpdaterInit::StatusValue updater_init_exec_dir_check()\n{\n  struct stat stat_info;\n\n  vfile *vf = vfopen_unsafe(REMOTE_MANIFEST_TXT \"~\", \"wb\");\n  if(!vf)\n    return UpdaterInit::FAILED_FILE_WRITE;\n\n  vfclose(vf);\n  if(vunlink(REMOTE_MANIFEST_TXT \"~\"))\n    return UpdaterInit::FAILED_FILE_UNLINK;\n\n  if(vstat(LOCAL_MANIFEST_TXT, &stat_info))\n    return UpdaterInit::FAILED_TO_STAT_MANIFEST;\n\n  return UpdaterInit::SUCCESS;\n}\n\n/**\n * Check for a delete.txt manifest indicating files to delete from a prior\n * update. If found, delete the files listed and update (or remove) delete.txt.\n */\nstatic void updater_init_delete_txt_check()\n{\n  Manifest delete_list;\n  struct stat stat_info;\n\n  if(vstat(DELETE_TXT, &stat_info))\n    return;\n\n  if(!delete_list.create(DELETE_TXT))\n    return;\n\n  apply_delete_list(delete_list);\n  vunlink(DELETE_TXT);\n\n  if(delete_list.has_entries())\n    write_delete_list(delete_list);\n}\n\nboolean updater_init(void)\n{\n  const struct config_info *conf = get_config();\n  if(!conf->updater_enabled)\n    return false;\n\n  check_for_updates = __check_for_updates;\n\n  /**\n   * Explicitly compare against the constexpr checks first so the rest gets\n   * optimized out as inaccessible code for platforms like Linux.\n   */\n  UpdaterInit::status = UpdaterInit::const_safety_checks;\n  if(UpdaterInit::const_safety_checks != UpdaterInit::SUCCESS)\n    return true;\n\n  /**\n   * More checks to filter out platforms where the full update process is\n   * entirely non-applicable.\n   */\n  UpdaterInit::status = updater_init_safety_checks();\n  if(UpdaterInit::status != UpdaterInit::SUCCESS)\n    return true;\n\n  if(swivel_current_dir(false))\n  {\n    UpdaterInit::status = updater_init_exec_dir_check();\n\n    if(UpdaterInit::allow_full_updates())\n      updater_init_delete_txt_check();\n\n    swivel_current_dir_back(false);\n  }\n  else\n    UpdaterInit::status = UpdaterInit::FAILED_CHDIR_TO_EXEC_DIR;\n\n  if(!UpdaterInit::allow_full_updates())\n  {\n    warn(\"Updater startup checks failed; full updates will not be available (%s).\\n\",\n     UpdaterInit::status_string(UpdaterInit::status));\n  }\n\n  return true;\n}\n\nboolean is_updater(void)\n{\n  return true;\n}\n"
  },
  {
    "path": "src/updater.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __UPDATER_H\n#define __UPDATER_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\nboolean updater_init(void);\nboolean is_updater(void);\n\n__M_END_DECLS\n\n#endif // __UPDATER_H\n"
  },
  {
    "path": "src/util.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2012-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <sys/stat.h>\n#include <ctype.h>\n#include <time.h>\n\n// Must include after <time.h> (MSVC bug)\n#include \"util.h\"\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#include <inttypes.h>\n#include <stdlib.h>\n#include <string.h>\n\n#ifdef __WIN32__\n#include \"io/vio_win32.h\" // for windows.h, WIDE_PATHS, and utf16_to_utf8\n#endif\n\n#include \"const.h\" // for MAX_PATH\n#include \"error.h\"\n#include \"platform.h\"\n#include \"io/path.h\"\n#include \"io/vio.h\"\n\nstruct mzx_resource\n{\n  const char *const base_name;\n  char *path;\n\n  /* So mzxrun requires fewer files even in CONFIG_EDITOR=1 build */\n  const boolean editor_only;\n\n  /* Optional resource--do not abort if not found, but print a warning. */\n  const boolean optional;\n};\n\n/* Using C99 initializers would be nicer here, but MSVC doesn't support\n * them and we try to allow compilation with that compiler.\n *\n * As a result, these must be in exactly the same order as the\n * enum resource_id enumeration defines them.\n */\nstatic struct mzx_resource mzx_res[] =\n{\n#define ASSETS \"assets/\"\n#ifndef ICONFILE\n#define ICONFILE NULL\n#endif\n  { \"(binpath)\",                        NULL, false, true },\n  { CONFFILE,                           NULL, false, false },\n  { ICONFILE,                           NULL, false, true },\n  { ASSETS \"default.chr\",               NULL, false, false },\n  { ASSETS \"edit.chr\",                  NULL, false, false },\n  { ASSETS \"smzx.pal\",                  NULL, false, false },\n#ifdef CONFIG_EDITOR\n  { ASSETS \"ascii.chr\",                 NULL, true,  false },\n  { ASSETS \"blank.chr\",                 NULL, true,  false },\n  { ASSETS \"smzx.chr\",                  NULL, true,  false },\n  { ASSETS \"smzx2.chr\",                 NULL, true,  false },\n#endif\n#ifdef CONFIG_HELPSYS\n  { ASSETS \"help.fil\",                  NULL, false, true },\n#endif\n#ifdef CONFIG_RENDER_GL_PROGRAM\n#define GLSL_SHADERS ASSETS \"glsl/\"\n#define GLSL_SCALERS GLSL_SHADERS \"scalers/\"\n  { GLSL_SCALERS,                       NULL, false, false },\n  { GLSL_SHADERS \"scaler.vert\",         NULL, false, false },\n  { GLSL_SCALERS \"semisoft.frag\",       NULL, false, false },\n  { GLSL_SHADERS \"tilemap.vert\",        NULL, false, false },\n  { GLSL_SHADERS \"tilemap.frag\",        NULL, false, false },\n  { GLSL_SHADERS \"tilemap.smzx.frag\",   NULL, false, false },\n  { GLSL_SHADERS \"mouse.vert\",          NULL, false, false },\n  { GLSL_SHADERS \"mouse.frag\",          NULL, false, false },\n  { GLSL_SHADERS \"cursor.vert\",         NULL, false, false },\n  { GLSL_SHADERS \"cursor.frag\",         NULL, false, false },\n#endif\n#ifdef CONFIG_GAMECONTROLLERDB\n  { ASSETS \"gamecontrollerdb.txt\",      NULL, false, true },\n#endif\n};\n\n#ifdef CONFIG_CHECK_ALLOC\n\nstatic platform_thread_id main_thread_id;\n\nvoid check_alloc_init(void)\n{\n  main_thread_id = platform_get_thread_id();\n}\n\nstatic void out_of_memory_check(void *p, const char *file, int line)\n{\n  char msgbuf[128];\n  if(!p)\n  {\n    platform_thread_id cur_thread_id = platform_get_thread_id();\n    if(platform_is_same_thread(cur_thread_id, main_thread_id))\n    {\n      snprintf(msgbuf, sizeof(msgbuf), \"Out of memory in %s:%d\", file, line);\n      msgbuf[sizeof(msgbuf)-1] = '\\0';\n      error(msgbuf, ERROR_T_FATAL, ERROR_OPT_EXIT|ERROR_OPT_NO_HELP, 0);\n    }\n    else\n      warn(\"Out of memory in in %s:%d (thread %zu)\\n\",\n       file, line, (size_t)cur_thread_id);\n  }\n}\n\n// Attempt to free buffered memory from the virtual filesystem for reuse.\nstatic boolean check_free_buffered(size_t amount, boolean *retry)\n{\n  if(!*retry || !amount)\n    return false;\n\n  if(!vio_invalidate_at_least(&amount))\n  {\n    vio_invalidate_all();\n    *retry = false;\n  }\n  return true;\n}\n\nvoid *check_calloc(size_t nmemb, size_t size, const char *file, int line)\n{\n  void *result = calloc(nmemb, size);\n  if(!result && nmemb * size >= size)\n  {\n    boolean retry = true;\n    while(!result && check_free_buffered(nmemb * size, &retry))\n      result = calloc(nmemb, size);\n  }\n  out_of_memory_check(result, file, line);\n  return result;\n}\n\nvoid *check_malloc(size_t size, const char *file, int line)\n{\n  void *result = malloc(size);\n  if(!result)\n  {\n    boolean retry = true;\n    while(!result && check_free_buffered(size, &retry))\n      result = malloc(size);\n  }\n  out_of_memory_check(result, file, line);\n  return result;\n}\n\nvoid *check_realloc(void *ptr, size_t size, const char *file, int line)\n{\n  void *result = realloc(ptr, size);\n  if(!result)\n  {\n    boolean retry = true;\n    while(!result && check_free_buffered(size, &retry))\n      result = realloc(ptr, size);\n  }\n  out_of_memory_check(result, file, line);\n  return result;\n}\n\n#endif /* CONFIG_CHECK_ALLOC */\n\n// Determine the executable dir.\nstatic ssize_t find_executable_dir(char *dest, size_t dest_len, const char *argv0)\n{\n#ifdef __WIN32__\n  {\n    // Windows may not reliably give a full path in argv[0]. Fortunately,\n    // there's a Windows solution for this.\n    HMODULE module = GetModuleHandle(NULL);\n    char filename[MAX_PATH];\n\n#ifdef WIDE_PATHS\n    wchar_t filename_wide[MAX_PATH];\n    DWORD ret = GetModuleFileNameW(module, filename_wide, MAX_PATH);\n\n    if(ret > 0 && ret < MAX_PATH)\n    {\n      if(!utf16_to_utf8(filename_wide, filename, MAX_PATH))\n        ret = 0;\n    }\n#else /* !WIDE_PATHS */\n    DWORD ret = GetModuleFileNameA(module, filename, MAX_PATH);\n#endif\n\n    if(ret > 0 && ret < MAX_PATH)\n    {\n      ssize_t len = path_get_directory(dest, dest_len, filename);\n      if(len > 0)\n        return len;\n    }\n\n    warn(\"--RES-- Failed to get executable path from Win32\\n\");\n  }\n#endif /* __WIN32__ */\n\n  if(argv0)\n  {\n#ifdef CONFIG_PSP\n    // FIXME: EBOOT.PBP may be treated as a directory, always get the parent.\n    ssize_t len = path_get_parent(dest, dest_len, argv0);\n#else\n    ssize_t len = path_get_directory(dest, dest_len, argv0);\n#endif\n    if(len > 0)\n    {\n      // Change to the executable dir and get it as an absolute path.\n      if(!vchdir(dest) && vgetcwd(dest, dest_len))\n        return strlen(dest);\n    }\n\n    // Might be on PATH, don't print a warning.\n    debug(\"--RES-- Failed to get executable path from argv[0]: %s\\n\", argv0);\n  }\n  else\n    warn(\"--RES-- Failed to get executable path from argv[0]: (null)\\n\");\n\n  dest[0] = 0;\n  return 0;\n}\n\nint mzx_res_init(const char *argv0, boolean editor)\n{\n  int i;\n  ssize_t bin_path_len;\n  struct stat file_info;\n  char *full_path;\n  char *bin_path;\n  char *p_dir;\n  int ret = 0;\n\n  bin_path = cmalloc(MAX_PATH);\n  p_dir = cmalloc(MAX_PATH);\n\n  bin_path_len = find_executable_dir(bin_path, MAX_PATH, argv0);\n  if(bin_path_len > 0)\n  {\n    // Shrink the allocation...\n    bin_path = crealloc(bin_path, bin_path_len + 1);\n  }\n  else\n  {\n    free(bin_path);\n    bin_path = NULL;\n  }\n\n  /* Always try to use SHAREDIR/CONFDIR first, if possible. On some\n   * platforms, such as Linux, this will be something other than\n   * the current working directory (i.e. '.'). If this fails, try\n   * to look up the resources relative to the binary's install\n   * location, which should allow the MegaZeux binary to be more\n   * \"portable\".\n   */\n  for(i = 0; i < END_RESOURCE_ID_T; i++)\n  {\n    size_t base_name_len;\n    size_t full_path_len;\n    size_t p_dir_len;\n\n    if(i == MZX_EXECUTABLE_DIR)\n    {\n      // Special--this was already determined above if applicable.\n      mzx_res[i].path = bin_path;\n      continue;\n    }\n\n    if(!mzx_res[i].base_name)\n      continue;\n\n    base_name_len = strlen(mzx_res[i].base_name);\n\n    // Path may be an absolute path.\n    if(mzx_res[i].base_name[0] == '/')\n    {\n      full_path_len = base_name_len + 1;\n      full_path = cmalloc(full_path_len);\n\n      if(path_clean_copy(full_path, full_path_len,\n       mzx_res[i].base_name) > 0)\n      {\n        if(!vstat(full_path, &file_info))\n        {\n          mzx_res[i].path = full_path;\n          continue;\n        }\n      }\n      // Do not attempt to load absolute files relatively.\n      free(full_path);\n      continue;\n    }\n\n    if(i == CONFIG_TXT)\n      vchdir(CONFDIR);\n    else\n      vchdir(SHAREDIR);\n\n    vgetcwd(p_dir, MAX_PATH);\n    p_dir_len = strlen(p_dir);\n\n    full_path_len = p_dir_len + base_name_len + 2;\n    full_path = cmalloc(full_path_len);\n\n    // The provided buffer should always be big enough, but check anyway.\n    if(path_join(full_path, full_path_len, p_dir, mzx_res[i].base_name) > 0)\n    {\n      if(!vstat(full_path, &file_info))\n      {\n        mzx_res[i].path = full_path;\n        continue;\n      }\n    }\n\n    free(full_path);\n\n    // Try to locate the resource relative to the bin_path\n    if(bin_path)\n    {\n      vchdir(bin_path);\n      if(!vstat(mzx_res[i].base_name, &file_info))\n      {\n        full_path_len = bin_path_len + base_name_len + 2;\n        full_path = cmalloc(full_path_len);\n\n        // The provided buffer should always be big enough, but check anyway.\n        if(path_join(full_path, full_path_len, bin_path, mzx_res[i].base_name) > 0)\n        {\n          mzx_res[i].path = full_path;\n          continue;\n        }\n        free(full_path);\n      }\n    }\n  }\n\n  for(i = 0; i < END_RESOURCE_ID_T; i++)\n  {\n    /* Skip editor resources if this isn't the editor. */\n    if(!editor && mzx_res[i].editor_only)\n      continue;\n\n    if(!mzx_res[i].path)\n    {\n      // Finding this fails from installed builds in Linux, just ignore.\n      if(i == MZX_EXECUTABLE_DIR)\n        continue;\n\n      if(mzx_res[i].optional)\n      {\n        if(!mzx_res[i].base_name)\n        {\n          trace(\"--RES-- %d -> NULL\\n\", i);\n          continue;\n        }\n        warn(\"Failed to locate non-critical resource (%d) '%s'\\n\",\n         i, mzx_res[i].base_name);\n        continue;\n      }\n\n      warn(\"Failed to locate critical resource (%d) '%s'.\\n\",\n       i, mzx_res[i].base_name);\n      ret = 1;\n    }\n    else\n      trace(\"--RES-- %d -> %s\\n\", i, mzx_res[i].path);\n  }\n\n  free(p_dir);\n  return ret;\n}\n\nvoid mzx_res_free(void)\n{\n  int i;\n  for(i = 0; i < END_RESOURCE_ID_T; i++)\n    if(mzx_res[i].path)\n      free(mzx_res[i].path);\n}\n\n#ifdef USERCONFFILE\n#define COPY_BUFFER_SIZE  4096\nstatic unsigned char copy_buffer[COPY_BUFFER_SIZE];\n#endif\n\nconst char *mzx_res_get_by_id(enum resource_id id)\n{\n#ifdef USERCONFFILE\n  static char userconfpath[MAX_PATH];\n  if(id == CONFIG_TXT)\n  {\n    vfile *vf;\n\n    // Special handling for CONFIG_TXT to allow for user\n    // configuration files\n#ifdef CONFIG_PSVITA\n    snprintf(userconfpath, MAX_PATH, \"%s\", USERCONFFILE);\n#else\n    snprintf(userconfpath, MAX_PATH, \"%s/%s\", getenv(\"HOME\"), USERCONFFILE);\n#endif\n\n    // Check if the file can be opened for reading\n    vf = vfopen_unsafe(userconfpath, \"rb\");\n\n    if(vf)\n    {\n      vfclose(vf);\n      return (char *)userconfpath;\n    }\n    // Otherwise, try to open the file for writing\n    vf = vfopen_unsafe(userconfpath, \"wb\");\n    if(vf)\n    {\n      vfile *original = vfopen_unsafe(mzx_res[id].path, \"rb\");\n      if(original)\n      {\n        size_t bytes_read;\n        for(;;)\n        {\n          bytes_read = vfread(copy_buffer, 1, COPY_BUFFER_SIZE, original);\n          if(bytes_read)\n            vfwrite(copy_buffer, 1, bytes_read, vf);\n          else\n            break;\n        }\n        vfclose(vf);\n        vfclose(original);\n        return (char *)userconfpath;\n      }\n      vfclose(vf);\n    }\n\n    // If that's no good, just read the normal config file\n  }\n#endif /* USERCONFFILE */\n  return mzx_res[id].path;\n}\n\n#ifdef CONFIG_STDIO_REDIRECT\n\nFILE *mzxout_h = NULL;\nFILE *mzxerr_h = NULL;\n\n/**\n * Some platforms may not be able to display console output without extra work.\n * On these platforms, open stdout/stderr replacement files instead. The log\n * macros and any other references to `mzxout` and `mzxerr` will use these\n * files instead of stdio.\n *\n * Previously, this was implemented using `freopen` on stdout/stderr, but\n * doing this in some console SDKs does not work correctly (NDS, PS Vita).\n */\nboolean redirect_stdio_init(const char *base_path, boolean require_conf)\n{\n  char dest_path[MAX_PATH];\n  size_t dest_len;\n  FILE *fp_wr;\n  uint64_t t;\n\n  if(!base_path)\n    return false;\n\n  dest_len = path_clean_copy(dest_path, MAX_PATH - 10, base_path);\n\n  if(require_conf)\n  {\n    // If the config file is required, attempt to stat it.\n    struct stat stat_info;\n\n    path_append(dest_path, MAX_PATH, \"config.txt\");\n    if(vstat(dest_path, &stat_info))\n      return false;\n    dest_path[dest_len] = '\\0';\n  }\n\n  // Clean up existing handles from a previous attempt.\n  redirect_stdio_exit();\n\n  // Test directory for write access.\n  path_append(dest_path, MAX_PATH, \"stdout.txt\");\n  fp_wr = fopen_unsafe(dest_path, \"w\");\n  if(!fp_wr)\n  {\n    fprintf(stdout, \"Failed to redirect stdout\\n\");\n    fflush(stdout);\n    return false;\n  }\n\n  t = (uint64_t)time(NULL);\n\n  // Redirect mzxout to stdout.txt.\n  fprintf(stdout, \"Redirecting logs to '%s'...\\n\", dest_path);\n  fflush(stdout);\n  mzxout_h = fp_wr;\n  fprintf(mzxout, \"MegaZeux: Logging to '%s' (%\" PRIu64 \")\\n\", dest_path, t);\n  fflush(mzxout);\n\n  // Redirect mzxerr to stderr.txt.\n  dest_path[dest_len] = '\\0';\n  path_append(dest_path, MAX_PATH, \"stderr.txt\");\n  fp_wr = fopen_unsafe(dest_path, \"w\");\n  if(!fp_wr)\n  {\n    fprintf(stderr, \"Failed to redirect stderr\\n\");\n    fflush(stderr);\n    return false;\n  }\n\n  fprintf(stderr, \"Redirecting logs to '%s'...\\n\", dest_path);\n  fflush(stderr);\n  mzxerr_h = fp_wr;\n  fprintf(mzxerr, \"MegaZeux: Logging to '%s' (%\" PRIu64 \")\\n\", dest_path, t);\n  fflush(mzxerr);\n\n  return true;\n}\n\nvoid redirect_stdio_exit(void)\n{\n  if(mzxout_h)\n  {\n    fclose(mzxout_h);\n    mzxout_h = NULL;\n  }\n\n  if(mzxerr_h)\n  {\n    fclose(mzxerr_h);\n    mzxerr_h = NULL;\n  }\n}\n\n#endif /* CONFIG_STDIO_REDIRECT */\n\n// Random function, returns an integer [0-range)\n\nstatic uint64_t rng_state;\n\n// Seed the RNG from system time on startup\nvoid rng_seed_init(void)\n{\n  uint64_t seed = (((uint64_t)time(NULL)) << 32) | clock();\n  rng_set_seed(seed);\n}\n\nuint64_t rng_get_seed(void)\n{\n  return rng_state;\n}\n\nvoid rng_set_seed(uint64_t seed)\n{\n  rng_state = seed;\n}\n\n// xorshift*\n// Implementation from https://en.wikipedia.org/wiki/Xorshift\nunsigned int Random(uint64_t range)\n{\n  uint64_t x = rng_state;\n  if(x == 0) x = 1;\n  x ^= x >> 12; // a\n  x ^= x << 25; // b\n  x ^= x >> 27; // c\n  rng_state = x;\n  return (((x * 0x2545F4914F6CDD1D) >> 32) * range) >> 32;\n}\n\n#if defined(__WIN32__) && defined(__STRICT_ANSI__)\n\n/* On WIN32 with C99 defining __STRICT_ANSI__ these POSIX.1-2001 functions\n * are not available. The stricmp/strnicmp functions are available, but not\n * to C99 programs.\n *\n * Redefine them here; these are copied from glibc, and should be optimal.\n */\n\nint strcasecmp(const char *s1, const char *s2)\n{\n  while(*s1 != '\\0' && tolower(*s1) == tolower(*s2))\n  {\n    s1++;\n    s2++;\n  }\n\n  return tolower(*(unsigned char *)s1) - tolower(*(unsigned char *)s2);\n}\n\nint strncasecmp(const char *s1, const char *s2, size_t n)\n{\n  if(n == 0)\n    return 0;\n\n  while(n-- != 0 && tolower(*s1) == tolower(*s2))\n  {\n    if(n == 0 || *s1 == '\\0' || *s2 == '\\0')\n      break;\n    s1++;\n    s2++;\n  }\n\n  return tolower(*(unsigned char *)s1) - tolower(*(unsigned char *)s2);\n}\n\n#endif // __WIN32__ && __STRICT_ANSI__\n\n#if defined(__WIN32__) || defined(__amigaos__)\n\n// strsep() stolen from glibc (GPL)\n\nchar *strsep(char **stringp, const char *delim)\n{\n  char *begin, *end;\n\n  begin = *stringp;\n  if(!begin)\n    return NULL;\n\n  if(delim[0] == '\\0' || delim[1] == '\\0')\n  {\n    char ch = delim[0];\n    if(ch == '\\0')\n      end = NULL;\n    else\n    {\n      if(*begin == ch)\n        end = begin;\n      else if(*begin == '\\0')\n        end = NULL;\n      else\n        end = strchr(begin + 1, ch);\n    }\n  }\n  else\n    end = strpbrk(begin, delim);\n\n  if(end)\n  {\n    *end++ = '\\0';\n    *stringp = end;\n  }\n  else\n    *stringp = NULL;\n\n  return begin;\n}\n\n#endif // __WIN32__ || __amigaos__\n\n#if defined(__amigaos__)\n\nlong __stack_chk_guard[8];\n\nvoid __stack_chk_fail(void)\n{\n  warn(\"Stack overflow detected; terminated\");\n  exit(0);\n}\n\n#endif // __amigaos__\n"
  },
  {
    "path": "src/util.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Useful, generic utility functions\n\n#ifndef __UTIL_H\n#define __UTIL_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stdint.h>\n#include <stdio.h>\n#if !defined(_MSC_VER) && !defined(__amigaos__)\n#include <unistd.h>\n#endif\n\n/* Fix redefinition warnings from some libraries (examples: musl, DirectFB) */\n#undef MIN\n#undef MAX\n#undef CLAMP\n\n#define MIN(a, b) (((a) < (b)) ? (a) : (b))\n#define MAX(a, b) (((a) > (b)) ? (a) : (b))\n\n#define CLAMP(x, low, high) \\\n  (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))\n\n#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))\n\n#define SGN(x) ((x > 0) - (x < 0))\n\n#ifdef CONFIG_STDIO_REDIRECT\nCORE_LIBSPEC extern FILE *mzxout_h;\nCORE_LIBSPEC extern FILE *mzxerr_h;\n#define mzxout (mzxout_h ? mzxout_h : stdout)\n#define mzxerr (mzxerr_h ? mzxerr_h : stderr)\n#else\n#define mzxout stdout\n#define mzxerr stderr\n#endif\n\nenum resource_id\n{\n  MZX_EXECUTABLE_DIR = 0,\n  CONFIG_TXT,\n  MZX_ICON_PNG,\n  MZX_DEFAULT_CHR,\n  MZX_EDIT_CHR,\n  SMZX_PAL,\n#ifdef CONFIG_EDITOR\n  MZX_ASCII_CHR,\n  MZX_BLANK_CHR,\n  MZX_SMZX_CHR,\n  MZX_SMZX2_CHR,\n#endif\n#ifdef CONFIG_HELPSYS\n  MZX_HELP_FIL,\n#endif\n#ifdef CONFIG_RENDER_GL_PROGRAM\n  GLSL_SHADER_SCALER_DIRECTORY,\n  GLSL_SHADER_SCALER_VERT,\n  GLSL_SHADER_SCALER_FRAG,\n  GLSL_SHADER_TILEMAP_VERT,\n  GLSL_SHADER_TILEMAP_FRAG,\n  GLSL_SHADER_TILEMAP_SMZX_FRAG,\n  GLSL_SHADER_MOUSE_VERT,\n  GLSL_SHADER_MOUSE_FRAG,\n  GLSL_SHADER_CURSOR_VERT,\n  GLSL_SHADER_CURSOR_FRAG,\n#endif\n#ifdef CONFIG_GAMECONTROLLERDB\n  GAMECONTROLLERDB_TXT,\n#endif\n  END_RESOURCE_ID_T // must be last\n};\n\n#ifdef CONFIG_RENDER_GL_PROGRAM\n#define GLSL_SHADER_RES_FIRST GLSL_SHADER_SCALER_VERT\n#define GLSL_SHADER_RES_LAST  GLSL_SHADER_CURSOR_FRAG\n#define GLSL_SHADER_RES_COUNT (GLSL_SHADER_RES_LAST - GLSL_SHADER_RES_FIRST + 1)\n#endif\n\nCORE_LIBSPEC int mzx_res_init(const char *argv0, boolean editor);\nCORE_LIBSPEC void mzx_res_free(void);\nCORE_LIBSPEC const char *mzx_res_get_by_id(enum resource_id id);\n\n#ifdef CONFIG_STDIO_REDIRECT\nCORE_LIBSPEC boolean redirect_stdio_init(const char *base_path, boolean require_conf);\nCORE_LIBSPEC void redirect_stdio_exit(void);\n#endif\n\nCORE_LIBSPEC void rng_seed_init(void);\nuint64_t rng_get_seed(void);\nvoid rng_set_seed(uint64_t seed);\nunsigned int Random(uint64_t range);\n\nstatic inline size_t round_to_power_of_two(size_t v)\n{\n  v -= (v > 0);\n  v |= v >> 1;\n  v |= v >> 2;\n  v |= v >> 4;\n  v |= v >> 8;\n  v |= v >> 16;\n  return v + 1;\n}\n\n#include <sys/types.h>\n\n#if defined(__WIN32__) && defined(__STRICT_ANSI__)\nCORE_LIBSPEC int strcasecmp(const char *s1, const char *s2);\nCORE_LIBSPEC int strncasecmp(const char *s1, const char *s2, size_t n);\n#endif // __WIN32__ && __STRICT_ANSI__\n\n#if defined(__WIN32__) || defined(__amigaos__)\nCORE_LIBSPEC char *strsep(char **stringp, const char *delim);\n#endif // __WIN32__ || __amigaos__\n\n#ifndef __WIN32__\n// POSIX strcasecmp and strncasecmp are in strings.h,\n// which may or may not be included by string.h\n#include <string.h>\n#include <strings.h>\n#endif // !__WIN32__\n\n#if defined(__amigaos__)\nCORE_LIBSPEC extern long __stack_chk_guard[8];\nCORE_LIBSPEC void __stack_chk_fail(void);\n#endif\n\n#if defined(ANDROID)\n\n#include <android/log.h>\n\n#define info(...)  __android_log_print(ANDROID_LOG_INFO, \"MegaZeux\", __VA_ARGS__)\n#define warn(...)  __android_log_print(ANDROID_LOG_WARN, \"MegaZeux\", __VA_ARGS__)\n\n#ifdef DEBUG\n#define debug(...)  __android_log_print(ANDROID_LOG_DEBUG, \"MegaZeux\", __VA_ARGS__)\n#else\n#define debug(...) do { } while(0)\n#endif\n\n#if defined(DEBUG) && defined(DEBUG_TRACE)\n#define trace(...)  __android_log_print(ANDROID_LOG_VERBOSE, \"MegaZeux\", __VA_ARGS__)\n#else\n#define trace(...) do { } while(0)\n#endif\n\n#elif defined(CONFIG_DREAMCAST)\n\n#include <kos.h>\n\n#define info(...) \\\n do { \\\n   dbgio_printf(\"INFO: \" __VA_ARGS__); \\\n   dbgio_flush(); \\\n } while(0)\n\n#define warn(...) \\\n do { \\\n   dbgio_printf(\"WARNING: \" __VA_ARGS__); \\\n   dbgio_flush(); \\\n } while(0)\n\n#ifdef DEBUG\n#define debug(...) \\\n do { \\\n   dbgio_printf(\"DEBUG: \" __VA_ARGS__); \\\n   dbgio_flush(); \\\n } while(0)\n#else\n#define debug(...) do { } while(0)\n#endif\n\n#if defined(DEBUG) && defined(DEBUG_TRACE)\n#define trace(...) \\\n  do { \\\n    dbgio_printf(\"TRACE: \" __VA_ARGS__); \\\n    dbgio_flush(); \\\n  } while(0)\n#else\n#define trace(...) do { } while(0)\n#endif\n\n#else /* ANDROID */\n\n#define info(...) \\\n do { \\\n   fprintf(mzxout, \"INFO: \" __VA_ARGS__); \\\n   fflush(mzxout); \\\n } while(0)\n\n#define warn(...) \\\n do { \\\n   fprintf(mzxerr, \"WARNING: \" __VA_ARGS__); \\\n   fflush(mzxerr); \\\n } while(0)\n\n#ifdef DEBUG\n#define debug(...) \\\n do { \\\n   fprintf(mzxerr, \"DEBUG: \" __VA_ARGS__); \\\n   fflush(mzxerr); \\\n } while(0)\n#else\n#define debug(...) do { } while(0)\n#endif\n#if defined(DEBUG) && defined(DEBUG_TRACE)\n#define trace(...) \\\n do { \\\n    fprintf(mzxerr, \"TRACE: \" __VA_ARGS__); \\\n    fflush(mzxerr); \\\n } while(0)\n#else\n#define trace(...) do { } while(0)\n#endif\n\n#endif /* ANDROID */\n\n__M_END_DECLS\n\n#endif // __UTIL_H\n"
  },
  {
    "path": "src/utils/Makefile.in",
    "content": "##\n# MegaZeux Utils Makefile fragment\n##\n\n#\n# FIXME: This whole file is a giant wart on the new build system\n#        and needs some love (and refactoring) to fix it\n#\n\n.PHONY: utils utils.debug utils_clean\n\nutils_src = src/utils\nutils_obj = src/utils/.build\n\n# Some core objects are required for file format purposes.\n# Define UTILS_LIBSPEC so these properly link.\nutils_cflags := ${LIBPNG_CFLAGS} ${PTHREAD_CFLAGS} -DUTILS_LIBSPEC=\n\n# Hack for static linking the utilities on macOS.\nUTILS_ZLIB_LDFLAGS ?= ${ZLIB_LDFLAGS}\nUTILS_LIBPNG_LDFLAGS ?= ${LIBPNG_LDFLAGS}\n\nzip_objs  := ${io_obj}/path.o ${io_obj}/vio_no_vfs.o ${io_obj}/zip.o ${io_obj}/zip_stream.o\nimg_objs  := ${utils_obj}/image_file.o ${utils_obj}/image_gif.o\nimg_flags :=\n\nifneq (${LIBPNG},)\nimg_flags += ${UTILS_LIBPNG_LDFLAGS} ${UTILS_ZLIB_LDFLAGS} -lm\nendif\n\ncheckres := ${utils_src}/checkres${BINEXT}\ncheckres_objs := ${utils_obj}/checkres.o \\\n                 ${io_obj}/fsafeopen.o \\\n                 ${zip_objs}\ncheckres_ldflags := ${UTILS_ZLIB_LDFLAGS}\n\ndownver := ${utils_src}/downver${BINEXT}\ndownver_objs := ${utils_obj}/downver.o ${zip_objs}\ndownver_ldflags := ${UTILS_ZLIB_LDFLAGS}\n\nhlp2html := ${utils_src}/hlp2html${BINEXT}\nhlp2html_objs := ${utils_obj}/hlp2html.o\n\nhlp2txt := ${utils_src}/hlp2txt${BINEXT}\nhlp2txt_objs := ${utils_obj}/hlp2txt.o\n\ntxt2hlp := ${utils_src}/txt2hlp${BINEXT}\ntxt2hlp_objs := ${utils_obj}/txt2hlp.o\n\npng2smzx := ${utils_src}/png2smzx${BINEXT}\npng2smzx_objs := ${utils_obj}/png2smzx.o ${utils_obj}/smzxconv.o\npng2smzx_objs += ${img_objs}\npng2smzx_ldflags := ${img_flags}\n\ny4m2smzx := ${utils_src}/y4m2smzx${BINEXT}\ny4m2smzx_objs := ${utils_obj}/y4m2smzx.o ${utils_obj}/smzxconv.o ${utils_obj}/y4m.o\ny4m2smzx_ldflags :=\nifeq (${PTHREAD},1)\ny4m2smzx_ldflags += ${PTHREAD_LDFLAGS}\nendif\n\nccv := ${utils_src}/ccv${BINEXT}\nccv_objs := ${utils_obj}/ccv.o\nccv_objs += ${img_objs}\nccv_ldflags := ${img_flags}\n\n${utils_obj}/%.o: ${utils_src}/%.c src/config.h\n\t$(if ${V},,@echo \"  CC      \" $<)\n\t${CC} -MD ${CFLAGS} ${utils_cflags} -I${core_src} \\\n\t  ${ZLIB_CFLAGS} -c $< -o $@\n\n-include $(checkres_objs:.o=.d)\n-include $(downver_objs:.o=.d)\n-include $(hlp2html_objs:.o=.d)\n-include $(hlp2txt_objs:.o=.d)\n-include $(txt2hlp_objs:.o=.d)\n-include $(png2smzx_objs:.o=.d)\n-include $(y4m2smzx_objs:.o=.d)\n-include $(ccv_objs:.o=.d)\n\n${checkres}: ${checkres_objs}\n\t$(if ${V},,@echo \"  LINK    \" ${checkres})\n\t${CC} ${checkres_objs} -o ${checkres} \\\n\t  ${ARCH_EXE_LDFLAGS} ${LDFLAGS} ${checkres_ldflags} ${ARCH_LIBS}\n\n${downver}: ${downver_objs}\n\t$(if ${V},,@echo \"  LINK    \" ${downver})\n\t${CC} ${downver_objs} -o ${downver} \\\n\t  ${ARCH_EXE_LDFLAGS} ${LDFLAGS} ${downver_ldflags} ${ARCH_LIBS}\n\n${hlp2html}: ${hlp2html_objs}\n\t$(if ${V},,@echo \"  LINK    \" ${hlp2html})\n\t${CC} ${hlp2html_objs} -o ${hlp2html} \\\n\t  ${ARCH_EXE_LDFLAGS} ${LDFLAGS} ${ARCH_LIBS}\n\n${hlp2txt}: ${hlp2txt_objs}\n\t$(if ${V},,@echo \"  LINK    \" ${hlp2txt})\n\t${CC} ${hlp2txt_objs} -o ${hlp2txt} \\\n\t  ${ARCH_EXE_LDFLAGS} ${LDFLAGS} ${ARCH_LIBS}\n\n${txt2hlp}: ${txt2hlp_objs}\n\t$(if ${V},,@echo \"  LINK    \" ${txt2hlp})\n\t${CC} ${txt2hlp_objs} -o ${txt2hlp} \\\n\t  ${ARCH_EXE_LDFLAGS} ${LDFLAGS} ${ARCH_LIBS}\n\n${png2smzx}: ${png2smzx_objs}\n\t$(if ${V},,@echo \"  LINK    \" ${png2smzx})\n\t${CC} ${png2smzx_objs} -o ${png2smzx} \\\n\t  ${ARCH_EXE_LDFLAGS} ${LDFLAGS} ${png2smzx_ldflags} ${ARCH_LIBS}\n\n${y4m2smzx}: ${y4m2smzx_objs}\n\t$(if ${V},,@echo \"  LINK    \" ${y4m2smzx})\n\t${CC} ${y4m2smzx_objs} -o ${y4m2smzx} \\\n\t  ${ARCH_EXE_LDFLAGS} ${LDFLAGS} ${y4m2smzx_ldflags} ${ARCH_LIBS}\n\n${ccv}: ${ccv_objs}\n\t$(if ${V},,@echo \"  LINK    \" ${ccv})\n\t${CC} ${ccv_objs} -o ${ccv} \\\n\t  ${ARCH_EXE_LDFLAGS} ${LDFLAGS} ${ccv_ldflags} ${ARCH_LIBS}\n\nutils: $(filter-out $(wildcard ${utils_obj}), ${utils_obj})\n\nutils: ${checkres} ${downver} ${hlp2html} ${hlp2txt} ${txt2hlp}\nutils: ${ccv} ${png2smzx} ${y4m2smzx}\n\nutils.debug: ${checkres}.debug ${downver}.debug ${ccv}.debug\nutils.debug: ${hlp2html}.debug ${hlp2txt}.debug ${txt2hlp}.debug\nutils.debug: ${png2smzx}.debug ${y4m2smzx}.debug\n\nutils_clean:\n\t$(if ${V},,@echo \"  RM      \" ${utils_obj})\n\t${RM} -r ${utils_obj}\n\t$(if ${V},,@echo \"  RM      \" ${checkres} ${checkres}.debug)\n\t${RM} ${checkres} ${checkres}.debug\n\t$(if ${V},,@echo \"  RM      \" ${downver} ${downver}.debug)\n\t${RM} ${downver} ${downver}.debug\n\t$(if ${V},,@echo \"  RM      \" ${hlp2html} ${hlp2html}.debug)\n\t${RM} ${hlp2html} ${hlp2html}.debug\n\t$(if ${V},,@echo \"  RM      \" ${hlp2txt} ${hlp2txt}.debug)\n\t${RM} ${hlp2txt} ${hlp2txt}.debug\n\t$(if ${V},,@echo \"  RM      \" ${txt2hlp} ${txt2hlp}.debug)\n\t${RM} ${txt2hlp} ${txt2hlp}.debug\n\t$(if ${V},,@echo \"  RM      \" ${png2smzx} ${png2smzx}.debug)\n\t${RM} ${png2smzx} ${png2smzx}.debug\n\t$(if ${V},,@echo \"  RM      \" ${y4m2smzx} ${y4m2smzx}.debug)\n\t${RM} ${y4m2smzx} ${y4m2smzx}.debug\n\t$(if ${V},,@echo \"  RM      \" ${ccv} ${ccv}.debug)\n\t${RM} ${ccv} ${ccv}.debug\n"
  },
  {
    "path": "src/utils/ccv.c",
    "content": "/* Copyright (C) 2017 Dr Lancer-X <drlancer@megazeux.org>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#define CCV_VERSION \"v0.075\"\n\n// With fix contributed by Lachesis\n\n#define CORE_LIBSPEC\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <ctype.h>\n#include <limits.h>\n#include \"../config.h\"\n\n#include \"image_file.h\"\n#include \"utils_alloc.h\"\n\n#ifndef VERSION_DATE\n#define VERSION_DATE\n#endif\n\n#ifdef CONFIG_PLEDGE_UTILS\n#include <unistd.h>\n#define PROMISES \"stdio rpath wpath cpath\"\n#endif\n\n// This software requires uthash to compile. Download it from\n// http://troydhanson.github.com/uthash/\n\n#include \"uthash.h\"\n\nstatic const char USAGE[] =\n\"ccv \" CCV_VERSION \" :: MegaZeux \" VERSION VERSION_DATE \"\\n\"\n\"char conversion tool for megazeux by Dr Lancer-X <drlancer@megazeux.org>\\n\"\n\"Usage: ccv <input files...> [options...]\\n\";\n\nstatic const char USAGE_DESC[] =\n\"\\n\"\n\"  By default, ccv converts input file 'input.fil' to output charset 'input.chr'\\n\"\n\"  with no char reuse.\\n\\n\"\n\n\"  If the input file '-' is provided, it will be read from stdin.\\n\\n\"\n\n\"  Supported image file formats are:\\n\\n\"\n\n\"  * PNG\\n\"\n\"  * GIF (multi-image GIFs will be flattened into a single image;\\n\"\n\"         non-square pixel aspect ratios are supported via upscaling)\\n\"\n\"  * BMP (1bpp, 2bpp, 4bpp, 8bpp, 16bpp, 24bpp, 32bpp, RLE8, RLE4, bitfields)\\n\"\n\"  * TGA (all)\\n\"\n\"  * Netpbm/PNM (.pbm, .pgm, .ppm, .pnm, .pam)\\n\"\n\"  * farbfeld (.ff)\\n\"\n\"  * raw (see raw format)\\n\"\n\"\\n\";\n\nstatic const char USAGE_OPTS[] =\n\"Options:\\n\\n\"\n\n\"  -blank [#]       Specifies a blank char index (default is -1 for none).\\n\"\n\"  -c [#]           Specifies the maximum number of chars to output, or 0 for\\n\"\n\"                   no maximum char limit (default).\\n\"\n\"  -dither [type]   Specifies a dithering algorithm to apply (default is none).\\n\"\n\"                   Supported values: floyd-steinberg, stucki, jarvis-judice-ninke,\\n\"\n\"                   burkes, sierra, stevenson-arce. These values can be abbreviated,\\n\"\n\"                   e.g. 'floyd' for 'floyd-steinberg'.\\n\"\n\"  -exclude [...]   Exclude a single char or range of chars from conversion.\\n\"\n\"                   The parameter can be a single number (32), a range (128-159),\\n\"\n\"                   or a comma-separated list of numbers and ranges. In the\\n\"\n\"                   output charset, these will be left blank.\\n\"\n\"  -help            Display this message.\\n\"\n\"  -mzm             Output both an MZM and a CHR.\\n\"\n\"  -offset [#]      Specifies the starting char offset for MZMs. Intended for use\\n\"\n\"                   with LOAD CHAR SET \\\"@###file.chr\\\", where ### is the offset.\\n\"\n\"  -output [name]   Specifies an output filename prefix. For example, '-output da_bomb'\\n\"\n\"                   will output 'da_bomb.chr' (and 'da_bomb.mzm' if -mzm is specified).\\n\"\n\"                   By default, this is the prefix of the input file, or 'out' if the\\n\"\n\"                   input file is stdin.\\n\"\n\"  -q               Suppress stdout messages.\\n\"\n\"  -reuse -noreuse  Specify whether or not chars should be reused.\\n\"\n\"                   On by default with -mzm or -c, off by default otherwise.\\n\"\n\"  -threshold [#]   Specify the intensity threshold for quantization. This is a value\\n\"\n\"                   from 0 to 255; if the intensity of a pixel is greater-than-or-equal\\n\"\n\"                   to this threshold, it will be considered 'on'; if less than this\\n\"\n\"                   threshold, it will be considered 'off'. Default is 127.\\n\"\n\"  -w [#]           Specify raw format width (see raw format).\\n\"\n\"  -h [#]           Specify raw format height (see raw format).\\n\"\n\"\\n\";\n\nstatic const char USAGE_RAW[] =\n\"Raw format:\\n\\n\"\n\n\"  ccv allows input of raw greyscale images if -w and -h are specified. Each\\n\"\n\"  byte of the input file represents the intensity of one pixel.\\n\"\n\n\"  Prior ccv versions treated these intensities as boolean values (zero for off,\\n\"\n\"  non-zero for on). For equivalent behavior to this, use -threshold 1.\\n\"\n\"\\n\";\n\nstatic const char USAGE_EXAMPLES[] =\n\"Examples:\\n\\n\"\n\n\"  ccv input.bmp\\n\\n\"\n\n\"    converts input.bmp to input.chr. The conversion will convert the image to\\n\"\n\"    a charset or partial charset containing a quantised representation of the\\n\"\n\"    image. No char reuse will be done.\\n\\n\"\n\n\"  ccv input.png -mzm -output output\\n\\n\"\n\n\"    converts input.bmp to output.chr and output.mzm. If the same character would\\n\"\n\"    have been used multiple times, it will be reused. Hence while the .chr\\n\"\n\"    itself may not match the input image, the combination of the .chr and .mzm will.\\n\\n\"\n\n\"  ccv input1.bmp input2.bmp -mzm\\n\\n\"\n\n\"    converts input1.bmp and input2.bmp to input1.chr, input2.chr,\\n\"\n\"    input1.mzm and input2.mzm.\\n\\n\"\n\n\"  ccv input.bmp -mzm -blank 32\\n\\n\"\n\n\"    converts input.bmp to input.chr and input.mzm. Char 32 will be blank and blank\\n\"\n\"    parts of the image will be assigned char #32 instead of some other char.\\n\\n\"\n\n\"  ccv -c 30 input.bmp -mzm\\n\\n\"\n\n\"    converts input.bmp to input.chr and input.mzm, with no more than 30 characters\\n\"\n\"    used. If necessary, close characters will be reused. -c implies -reuse.\\n\\n\"\n\n\"  ccv -offset 128 input.bmp -mzm\\n\\n\"\n\n\"    converts input.bmp to input.chr and input.mzm with the char indices in\\n\"\n\"    input.mzm being offset by 128 places. Hence loading the charset with\\n\"\n\"    LOAD CHAR SET \\\"@128input.chr\\\" and placing the MZM should result in\\n\"\n\"    accurate graphics.\\n\"\n\n\"  ccv -exclude 0,32-63 input.bmp -mzm\\n\\n\"\n\n\"    converts input.bmp to input.chr and input.mzm. If char #0 or chars #32 through #63\\n\"\n\"    would otherwise be used in the conversion, they will be replaced with blank\\n\"\n\"    chars instead and will not be referenced in the output -mzm.\\n\\n\"\n\n\"  ccv input.raw -w 256 -h 112 -threshold 32\\n\\n\"\n\n\"    converts input.raw to input.chr. All bytes >=32 will be treated as white and\\n\"\n\"    bytes <32 will be treated as black.\\n\\n\"\n\n\"  ccv input.bmp -dither floyd-steinberg -threshold 160\\n\\n\"\n\n\"    converts input.bmp to input.chr using floyd-steinberg dithering and treating\\n\"\n\"    intensities >=160 as white.\\n\"\n\"\\n\";\n\nstatic void Usage(void)\n{\n  fprintf(stderr, USAGE);\n  fprintf(stderr, \"Type \\\"ccv -help\\\" for extended options information\\n\");\n}\n\nstatic void Help(void)\n{\n  fprintf(stderr, USAGE);\n  fprintf(stderr, USAGE_DESC);\n  fprintf(stderr, USAGE_OPTS);\n  fprintf(stderr, USAGE_RAW);\n  fprintf(stderr, USAGE_EXAMPLES);\n}\n\nstatic void Error(const char *string, ...)\n{\n  va_list vaList;\n  va_start(vaList, string);\n  fprintf(stderr, \"Error: \");\n  vfprintf(stderr, string, vaList);\n  fprintf(stderr, \"\\n\");\n  va_end(vaList);\n  exit(1);\n}\n\nstatic inline int lc_strncmp(const char *A, const char *B, int n)\n{\n  const unsigned char *a = (const unsigned char *)A;\n  const unsigned char *b = (const unsigned char *)B;\n  int i;\n\n  if(A == NULL) return -1;\n  if(B == NULL) return 1;\n  for(i = 0; i < n; i++)\n  {\n    if(tolower(*a) < tolower(*b)) return -1;\n    if(tolower(*a) > tolower(*b)) return 1;\n    if(*a == '\\0') return 0;\n    a++;\n    b++;\n  }\n  return 0;\n}\n\nstatic inline void file_write32(int val, FILE *fp)\n{\n  fputc((val >> 0) & 0xFF, fp);\n  fputc((val >> 8) & 0xFF, fp);\n  fputc((val >> 16) & 0xFF, fp);\n  fputc((val >> 24) & 0xFF, fp);\n}\n\n/*\nstatic inline int file_read32(FILE *fp)\n{\n  unsigned int r = 0;\n  r |= fgetc(fp);\n  r |= fgetc(fp) << 8;\n  r |= fgetc(fp) << 16;\n  r |= fgetc(fp) << 24;\n  return r;\n}\n*/\n\nstatic inline void file_write16(int val, FILE *fp)\n{\n  fputc((val >> 0) & 0xFF, fp);\n  fputc((val >> 8) & 0xFF, fp);\n}\n\n/*\nstatic inline int file_read16(FILE *fp)\n{\n  unsigned int r = 0;\n  r |= fgetc(fp);\n  r |= fgetc(fp) << 8;\n  return r;\n}\n*/\n\ntypedef struct\n{\n  char path[MAX_PATH+1];\n  UT_hash_handle hh;\n} InputFile;\n\ntypedef struct\n{\n  InputFile *files;\n\n  int w;\n  int h;\n\n  int mzm;\n  int reuse;\n  int noreuse;\n  int quiet;\n\n  int c;\n  int threshold;\n\n  int offset;\n  int exclude_on;\n  char exclude[256];\n\n  int blank;\n\n  char output[MAX_PATH - 4 + 1];\n  char dither[256];\n} Config;\n\nstatic Config *DefaultConfig(void)\n{\n  Config *cfg = cmalloc(sizeof(Config));\n  cfg->files = NULL;\n\n  cfg->w = 0;\n  cfg->h = 0;\n\n  cfg->mzm = 0;\n  cfg->reuse = 0;\n  cfg->noreuse = 0;\n  cfg->quiet = 0;\n\n  cfg->offset = 0;\n  cfg->exclude_on = 0;\n  for (int i = 0; i < 256; i++) {\n    cfg->exclude[i] = 0;\n  }\n  cfg->blank = -1;\n\n  cfg->c = 0;\n  cfg->threshold = 127;\n\n  cfg->output[0] = '\\0';\n  cfg->dither[0] = '\\0';\n\n  return cfg;\n}\n\nstatic int Option(const char *option_name, int arguments, char *argument, int args_left)\n{\n  if(strcmp(argument, option_name) != 0)\n    return 0;\n\n  if(args_left < arguments)\n    Error(\"Insufficient arguments for %s\", option_name);\n\n  return 1;\n}\n\nstatic Config *LoadConfig(int argc, char **argv)\n{\n  Config *cfg = DefaultConfig();\n  int i;\n\n  if(argc >= 1 && strcmp(argv[0], \"-help\") == 0)\n  {\n    Help();\n    exit(0);\n  }\n\n  for(i = 0; i < argc; i++)\n  {\n    if(argv[i][0] == '-' && argv[i][1] != '\\0') // Argument\n    {\n      int args_left = argc - i - 1;\n      if(Option(\"-q\", 0, argv[i], args_left))\n      {\n        cfg->quiet = 1;\n        continue;\n      }\n      if(Option(\"-w\", 1, argv[i], args_left))\n      {\n        cfg->w = atoi(argv[i+1]);\n        i++;\n        continue;\n      }\n      if(Option(\"-h\", 1, argv[i], args_left))\n      {\n        cfg->h = atoi(argv[i+1]);\n        i++;\n        continue;\n      }\n      if(Option(\"-mzm\", 0, argv[i], args_left))\n      {\n        cfg->mzm = 1;\n        cfg->reuse = 1;\n        continue;\n      }\n      if(Option(\"-reuse\", 0, argv[i], args_left))\n      {\n        cfg->reuse = 1;\n        continue;\n      }\n      if(Option(\"-noreuse\", 0, argv[i], args_left))\n      {\n        cfg->noreuse = 1;\n        continue;\n      }\n      if(Option(\"-output\", 1, argv[i], args_left))\n      {\n        snprintf(cfg->output, MAX_PATH - 4, \"%s\", argv[i+1]);\n        cfg->output[MAX_PATH-4] = '\\0';\n        i++;\n        continue;\n      }\n      if(Option(\"-c\", 1, argv[i], args_left))\n      {\n        cfg->c = atoi(argv[i+1]);\n        i++;\n        continue;\n      }\n      if(Option(\"-dither\", 1, argv[i], args_left))\n      {\n        snprintf(cfg->dither, sizeof(cfg->dither), \"%s\", argv[i+1]);\n        cfg->dither[255] = '\\0';\n        i++;\n        continue;\n      }\n      if(Option(\"-threshold\", 1, argv[i], args_left))\n      {\n        cfg->threshold = atoi(argv[i+1]);\n        i++;\n        continue;\n      }\n      if(Option(\"-offset\", 1, argv[i], args_left))\n      {\n        cfg->offset = atoi(argv[i+1]);\n        i++;\n        continue;\n      }\n      if(Option(\"-blank\", 1, argv[i], args_left))\n      {\n        cfg->blank = atoi(argv[i+1]);\n        i++;\n        continue;\n      }\n      if(Option(\"-exclude\", 1, argv[i], args_left))\n      {\n        char *s = argv[i+1];\n        char tok[256];\n        char op;\n        int curr = 0;\n        int range_st = -1;\n        int range_ed = -1;\n\n        cfg->exclude_on = 1;\n\n        while(*s)\n        {\n          if(sscanf(s, \"%[0-9]\", tok) == 0)\n            break;\n          s += strlen(tok);\n\n          cfg->exclude[atoi(tok)] = 1;\n\n          if(curr == 1)\n          {\n            curr = 0;\n            range_ed = atoi(tok);\n\n            for(int ch = range_st; range_st <= range_ed ? ch <= range_ed : ch >= range_ed; range_st <= range_ed ? ch++ : ch--)\n              cfg->exclude[ch] = 1;\n          }\n\n          op = s[0];\n          if(op)\n            s++;\n\n          if(op == '-')\n          {\n            range_st = atoi(tok);\n            curr = 1;\n          }\n        }\n\n        i++;\n        continue;\n      }\n    }\n    else // Filename\n    {\n      InputFile *file = cmalloc(sizeof(InputFile));\n      snprintf(file->path, MAX_PATH + 1, \"%s\", argv[i]);\n      file->path[MAX_PATH] = 0;\n      HASH_ADD_STR(cfg->files, path, file);\n    }\n  }\n\n  if(cfg->files == NULL)\n  {\n    Usage();\n    exit(1);\n  }\n\n  return cfg;\n}\n\ntypedef struct\n{\n  int w, h;\n  int channels;\n  unsigned char *pixels;\n} Image;\n\nstatic Image *CreateImage(int w, int h, int channels)\n{\n  Image *image = cmalloc(sizeof(Image));\n  image->pixels = cmalloc(w * h * channels);\n  image->w = w;\n  image->h = h;\n  image->channels = channels;\n  return image;\n}\n\nstatic void LoadImage(struct image_file *dest, Config *cfg, const char *path)\n{\n  struct image_raw_format format;\n  struct image_raw_format *use_format = NULL;\n  enum image_error ret;\n\n  if((cfg->w > 0) && (cfg->h > 0))\n  {\n    format.width = cfg->w;\n    format.height = cfg->h;\n    format.bytes_per_pixel = 1; // TODO\n    format.force_raw = true;\n    use_format = &format;\n  }\n\n  ret = load_image_from_file(path, dest, use_format);\n  if(ret)\n    Error(\"Failed to load file '%s': %s\", path, image_error_string(ret));\n}\n\nstatic void FreeImage(Image *img)\n{\n  free(img->pixels);\n  free(img);\n}\n\nstatic int Brightness(const struct image_file *img, uint32_t x, uint32_t y)\n{\n  const struct rgba_color *color = &(img->data[y * img->width + x]);\n  return (color->r * 30 + color->g * 59 + color->b * 11) / 100;\n}\n\ntypedef struct\n{\n  char method[256];\n  int diffuse[1024];\n  unsigned w, h, div;\n} Diffuse;\n\nstatic const Diffuse diffmethods[] = {\n{\"Floyd-Steinberg\", {0, -1, 7,\n                    3, 5, 1}, 3, 2, 16},\n\n{\"Jarvis-Judice-Ninke\", {0, 0, -1, 7, 5,\n                         3, 5,  7, 5, 3,\n                         1, 3,  5, 3, 1}, 5, 3, 48},\n\n{\"Stucki\", {0, 0, -1, 7, 5,\n            2, 4,  8, 4, 2,\n            1, 2,  4, 2, 1}, 5, 3, 42},\n\n{\"Burkes\", {0, 0, -1, 8, 4,\n            2, 4, 8, 4, 2}, 5, 2, 32},\n\n{\"Sierra\", {0, 0, -1, 5, 3,\n            2, 4,  5, 4, 2,\n            0, 2,  3, 2, 0}, 5, 3, 32},\n\n{\"Stevenson-Arce\", {0,  0,  0,  -1, 0,  32, 0,\n                    12, 0,  26, 0,  30, 0,  16,\n                    0,  12, 0,  26, 0,  12, 0,\n                    5,  0,  12, 0,  12, 0,  5}, 7, 4, 200}\n};\n\nstatic void QuantiseDiffuse(struct image_file *img, Image *qimg, int threshold, const char *method)\n{\n  int *err = cmalloc(img->width * img->height * sizeof(int));\n  int method_i = -1;\n  unsigned int i;\n  int mx, my;\n  uint32_t x, y;\n  Diffuse D;\n  int v_noerr, v, wr;\n  memset(err, 0, img->width * img->height * sizeof(int));\n\n  for(i = 0; i < sizeof(diffmethods) / sizeof(diffmethods[0]); i++)\n  {\n    if(lc_strncmp(method, diffmethods[i].method, strlen(method)) == 0)\n      method_i = i;\n  }\n  if(method_i == -1)\n    Error(\"-dither method '%s' not found. Try another setting\", method);\n\n  D = diffmethods[method_i];\n  mx = 0;\n  my = 0;\n  for(i = 0; i < D.w * D.h; i++)\n  {\n    x = i % D.w;\n    y = i / D.w;\n    if(D.diffuse[i] == -1)\n    {\n      mx = x;\n      my = y;\n      D.diffuse[i] = 0;\n    }\n  }\n  for(y = 0; y < img->height; y++)\n  {\n    for(x = 0; x < img->width; x++)\n    {\n      v_noerr = Brightness(img, x, y);\n      v = v_noerr + err[y * img->width + x] / D.div;\n      if (v > 255) v = 255;\n      if (v < 0) v = 0;\n      wr = v >= threshold ? 255 : 0;\n      qimg->pixels[y * img->width + x] = v >= threshold ? 1 : 0;\n\n      if((v_noerr - wr) != 0)\n      {\n        for(i = 0; i < D.w * D.h; i++)\n        {\n          int ox = (i % D.w) - mx + x;\n          int oy = (i / D.w) - my + y;\n          if((D.diffuse[i] > 0) && (ox >= 0) && (oy >= 0) &&\n           ((size_t)ox < img->width) && ((size_t)oy < img->height))\n          {\n            err[oy * img->width + ox] += D.diffuse[i] * (v - wr);\n          }\n        }\n      }\n    }\n  }\n  free(err);\n}\n\nstatic void QuantiseTheshold(struct image_file *img, Image *qimg, int threshold)\n{\n  unsigned int i, x, y;\n\n  for(i = 0, y = 0; y < img->height; y++)\n  {\n    for(x = 0; x < img->width; x++, i++)\n    {\n      int v = Brightness(img, x, y);\n      qimg->pixels[i] = v >= threshold ? 1 : 0;\n    }\n  }\n}\n\nstatic Image *Quantise(Config *cfg, struct image_file *img)\n{\n  Image *qimg = CreateImage(img->width, img->height, 1);\n\n  if(cfg->dither[0] == '\\0')\n  {\n    QuantiseTheshold(img, qimg, cfg->threshold);\n  }\n  else\n    QuantiseDiffuse(img, qimg, cfg->threshold, cfg->dither);\n\n  return qimg;\n}\n\ntypedef struct\n{\n  int chars;\n  unsigned char *data;\n} Charset;\n\nstatic Charset *CreateCharset(Image *img)\n{\n  Charset *cset = cmalloc(sizeof(Charset));\n  int cset_size = (img->w / 8) * (img->h / 14);\n  int chr, cy, cx, py, px;\n  cset->data = cmalloc(cset_size * 14);\n  memset(cset->data, 0, cset_size * 14);\n\n  cset->chars = cset_size;\n\n  chr = 0;\n  for(cy = 0; cy < img->h / 14; cy++)\n  {\n    for(cx = 0; cx < img->w / 8; cx++)\n    {\n      for(py = 0; py < 14; py++)\n      {\n        for(px = 0; px < 8; px++)\n        {\n          cset->data[chr * 14 + py] |= img->pixels[(cy * 14 + py) * img->w + (cx * 8 + px)] ? 128 >> px : 0;\n        }\n      }\n      chr++;\n    }\n  }\n\n  return cset;\n}\n\ntypedef struct\n{\n  int w, h;\n  int *data;\n} Mzm;\n\nstatic Mzm *CreateMzm(int w, int h)\n{\n  Mzm *mzm = cmalloc(sizeof(Mzm));\n  int i;\n  mzm->w = w;\n  mzm->h = h;\n  mzm->data = cmalloc(w * h * sizeof(int));\n\n  for(i = 0; i < w * h; i++)\n    mzm->data[i] = i;\n\n  return mzm;\n}\n\nstatic void FreeMzm(Mzm *mzm)\n{\n  free(mzm->data);\n  free(mzm);\n}\n\ntypedef struct\n{\n  unsigned char data[14];\n  int i;\n  UT_hash_handle hh;\n} Char;\n\nstatic void Reuse(Charset *cset, Mzm *mzm)\n{\n  Char *clist = NULL;\n  Char *c, *tmp;\n  int out_chars = 0;\n  int i;\n  unsigned char *cdata;\n  for(i = 0; i < cset->chars; i++)\n  {\n    cdata = cset->data + i * 14;\n\n    HASH_FIND(hh, clist, cdata, sizeof(clist->data), c);\n    if(!c)\n    {\n      c = cmalloc(sizeof(Char));\n      memcpy(c->data, cdata, 14);\n\n      if(mzm)\n        c->i = mzm->data[i] = out_chars;\n\n      memmove(cset->data + out_chars * 14, cset->data + i * 14, 14);\n      HASH_ADD(hh, clist, data, sizeof(clist->data), c);\n      out_chars++;\n    }\n    else\n    {\n      if(mzm)\n        mzm->data[i] = c->i;\n    }\n  }\n  cset->chars = out_chars;\n  cset->data = realloc(cset->data, cset->chars * 14);\n\n  HASH_ITER(hh, clist, c, tmp)\n  {\n    HASH_DEL(clist, c);\n    free(c);\n  }\n}\n\nstatic char *OutputPath(const char *input, Config *cfg, const char *ext)\n{\n  char *output_path = NULL;\n  char buf[MAX_PATH+1];\n  char *buf_e;\n  size_t output_len;\n  if(cfg->output[0] == '\\0')\n  {\n    if(!strcmp(input, \"-\"))\n      input = \"out\";\n\n    snprintf(buf, MAX_PATH, \"%s\", input);\n    buf[MAX_PATH] = '\\0';\n\n    buf_e = strrchr(buf, '.');\n    if(buf_e)\n      *buf_e = '\\0';\n\n    output_len = strlen(buf) + strlen(ext) + 1;\n    output_path = cmalloc(output_len);\n    snprintf(output_path, output_len, \"%s%s\", buf, ext);\n    output_path[output_len - 1] = '\\0';\n  }\n  else\n  {\n    output_len = strlen(cfg->output) + strlen(ext) + 1;\n    output_path = cmalloc(output_len);\n    snprintf(output_path, output_len, \"%s%s\", cfg->output, ext);\n    output_path[output_len - 1] = '\\0';\n  }\n  return output_path;\n}\n\nstatic void WriteCharset(const char *path, Charset *cset)\n{\n  FILE *fp = fopen_unsafe(path, \"wb\");\n  fwrite(cset->data, 14, cset->chars, fp);\n  fclose(fp);\n}\n\nstatic void WriteMzm(const char *path, Mzm *mzm)\n{\n  FILE *fp = fopen_unsafe(path, \"wb\");\n  int i;\n  fwrite(\"MZM2\", 1, 4, fp);\n\n  file_write16(mzm->w, fp);\n  file_write16(mzm->h, fp);\n  file_write32(0, fp);\n  fputc(0, fp); // # of robots\n  fputc(1, fp); // storage mode\n  fputc(0, fp); // robot storage mode\n  fputc(0, fp); // reserved\n\n  for(i = 0; i < mzm->w * mzm->h; i++)\n  {\n    fputc(mzm->data[i] & 0xFF, fp);\n    fputc(15, fp);\n  }\n  fclose(fp);\n}\n\nstatic inline int CountBits(const unsigned char *A)\n{\n  int c, i;\n  const unsigned int *a;\n  unsigned int v;\n  unsigned int count;\n  unsigned char val;\n  if(sizeof(unsigned int) == 4)\n  {\n    c = 0;\n\n    a = (const unsigned int*)A;\n    for(i = 0; i < 3; i++)\n    {\n      v = *a;\n      v = v - ((v >> 1) & 0x55555555);\n      v = (v & 0x33333333) + ((v >> 2) & 0x33333333);\n      c += (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;\n      a++;\n    }\n    v = (A[13] << 8 | A[12]);\n    v = v - ((v >> 1) & 0x55555555);\n    v = (v & 0x33333333) + ((v >> 2) & 0x33333333);\n    c += (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;\n\n    return c;\n  }\n  else\n  {\n    count = 0;\n    for(int i = 0; i < 14; i++)\n    {\n      val = A[i];\n      for(; val; count++)\n        val &= val - 1; // clear the least significant bit set\n    }\n    return count;\n  }\n  return -1;\n}\n\nstatic inline int CharDist(const unsigned char *A, const unsigned char *B)\n{\n  unsigned char v[14];\n  int i;\n  for(i = 0; i < 14; i++)\n    v[i] = A[i] ^ B[i];\n\n  return CountBits(v);\n}\n\n// Simple quantisation- combine the two closest pairs of chars until the right number of chars are available\nstatic void Reduce_Simple(Charset *cset, int chars)\n{\n  int pair_i, pair_j, pair_dist, data_dist;\n  int i, j;\n  unsigned char *data_i, *data_j;\n  int bits;\n\n  while(cset->chars > chars)\n  {\n    pair_i = -1;\n    pair_j = -1;\n    pair_dist = INT_MAX;\n\n    for(i = 0; i < cset->chars; i++)\n    {\n      data_i = cset->data + i * 14;\n      for(j = i + 1; j < cset->chars; j++)\n      {\n        data_j = cset->data + j * 14;\n\n        data_dist = CharDist(data_i, data_j);\n        if(data_dist < pair_dist)\n        {\n          pair_dist = data_dist;\n          pair_i = i;\n          pair_j = j;\n        }\n      }\n    }\n\n    data_i = cset->data + pair_i * 14;\n    data_j = cset->data + pair_j * 14;\n    bits = CountBits(data_i) + CountBits(data_j);\n    if(bits >= 112)\n    {\n      for(i = 0; i < 14; i++)\n      {\n        //data_i[i] = data_i[i] > data_j[i] ? data_i[i] : data_j[i];\n        data_i[i] = data_i[i] | data_j[i];\n      }\n    }\n    else\n    {\n      for(i = 0; i < 14; i++)\n        data_i[i] = ~(~data_i[i] | ~data_j[i]);\n    }\n\n    memmove(data_j, data_j + 14, (cset->chars - pair_j - 1) * 14);\n    cset->chars--;\n  }\n\n  cset->data = realloc(cset->data, cset->chars * 14);\n}\n\nstatic int ChrImageDist(const unsigned char *cdata, Image *img, int xoff, int yoff)\n{\n  int tdist = 0;\n  int x, y, px, py, chr_lightness, img_lightness;\n  for(y = 0; y < 14; y++)\n  {\n    for(x = 0; x < 8; x++)\n    {\n      px = xoff + x;\n      py = yoff + y;\n      chr_lightness = cdata[y] & (128 >> x) ? 1 : 0;\n      img_lightness = img->pixels[((py * img->w) + px)] ? 1 : 0;\n      tdist += abs(chr_lightness - img_lightness);\n    }\n  }\n  return tdist;\n}\n\nstatic void RemapMzm(Charset *cset, Mzm *mzm, Image *qimage)\n{\n  // Remap the MZM to the charset to create the closest approximation of the quantised image.\n  int x, y, c;\n  for (y = 0; y < mzm->h; y++) {\n    for (x = 0; x < mzm->w; x++) {\n      int closest_chr = -1;\n      int closest_dist = INT_MAX;\n\n      for (c = 0; c < cset->chars; c++) {\n        int chr_dist = ChrImageDist(cset->data + c * 14, qimage, x * 8, y * 14);\n        if (chr_dist < closest_dist) {\n          closest_dist = chr_dist;\n          closest_chr = c;\n        }\n      }\n      mzm->data[y * mzm->w + x] = closest_chr;\n    }\n  }\n}\n\nstatic void Reduce(Charset *cset, Mzm *mzm, Image *original, int chars, Config *cfg)\n{\n  if(cset->chars <= chars)\n    return;\n\n  Reduce_Simple(cset, chars);\n  RemapMzm(cset, mzm, original);\n}\n\nstatic void OffsetMzm(Mzm *mzm, int offset)\n{\n  int i;\n  if(mzm)\n  {\n    for(i = 0; i < mzm->w * mzm->h; i++)\n      mzm->data[i] += offset;\n  }\n}\n\nstatic void ExcludeChars(Charset *cset, Mzm *mzm, char *exclude, int offset)\n{\n  int remap[256];\n  int remap_offset = 0;\n  int ci, i;\n  for(i = 0; i < cset->chars && i < 256; i++)\n  {\n    remap[i] = i;\n    ci = i + remap_offset + offset;\n    if((ci >= 0) && (ci < 256) && (exclude[ci]))\n    {\n      cset->data = realloc(cset->data, (cset->chars + 1) * 14);\n      remap[i] = cset->chars;\n      memmove(cset->data + (remap[i])*14, cset->data + i * 14, 14);\n      memset(cset->data + i * 14, 0, 14);\n\n      cset->chars++;\n    }\n  }\n\n  if(mzm)\n  {\n    for(i = 0; i < mzm->w * mzm->h; i++)\n    {\n      while((mzm->data[i] != remap[mzm->data[i]]))\n        mzm->data[i] = remap[mzm->data[i]];\n    }\n  }\n}\n\nstatic void SwapBlank(Charset *cset, Mzm *mzm, int blank)\n{\n  int current_blank = -1;\n  int i, j, isblank;\n  unsigned char tmp[14];\n  for(i = 0; i < cset->chars; i++)\n  {\n    isblank = 1;\n    for(j = 0; j < 14; j++)\n    {\n      if(cset->data[i * 14 + j] != 0)\n      {\n        isblank = 0;\n        break;\n      }\n    }\n\n    if(isblank)\n      current_blank = i;\n    break;\n  }\n  if(current_blank != -1)\n  {\n    memcpy(tmp, cset->data + current_blank*14, 14);\n    memcpy(cset->data + current_blank*14, cset->data + blank*14, 14);\n    memcpy(cset->data + blank*14, tmp, 14);\n\n    if(mzm)\n    {\n      for(i = 0; i < mzm->w * mzm->h; i++)\n      {\n        if(mzm->data[i] == current_blank)\n        {\n          mzm->data[i] = blank;\n        }\n        else\n\n        if(mzm->data[i] == blank)\n        {\n          mzm->data[i] = current_blank;\n        }\n      }\n    }\n  }\n}\n\nint main(int argc, char **argv)\n{\n  Config *cfg = LoadConfig(argc - 1, argv + 1);\n  InputFile *file;\n  Image *qimage;\n  Charset *cset;\n  Mzm *mzm;\n\n#ifdef CONFIG_PLEDGE_UTILS\n  // TODO unveil\n  if(pledge(PROMISES, \"\"))\n  {\n    Error(\"Failed pledge!\");\n    return 1;\n  }\n#endif\n\n  for(file = cfg->files; file != NULL; file = file->hh.next)\n  {\n    struct image_file image;\n    LoadImage(&image, cfg, file->path);\n\n    if(((image.width % 8) != 0) || ((image.height % 14) != 0))\n      Error(\"Image dimensions are not divisible by 8x14\");\n\n    qimage = Quantise(cfg, &image);\n\n    cset = CreateCharset(qimage);\n\n    mzm = NULL;\n    if(cfg->mzm)\n      mzm = CreateMzm(qimage->w / 8, qimage->h / 14);\n\n    if(cfg->reuse && !cfg->noreuse)\n      Reuse(cset, mzm);\n\n    if(cfg->c)\n      Reduce(cset, mzm, qimage, cfg->c, cfg);\n\n    if(cfg->blank != -1)\n      SwapBlank(cset, mzm, cfg->blank);\n\n    if(cfg->exclude_on)\n      ExcludeChars(cset, mzm, cfg->exclude, cfg->offset);\n\n    if(cfg->offset)\n      OffsetMzm(mzm, cfg->offset);\n\n    image_free(&image);\n    FreeImage(qimage);\n\n    { // Output charset\n      char *filename_chr = OutputPath(file->path, cfg, \".chr\");\n\n      WriteCharset(filename_chr, cset);\n\n      if (!cfg->quiet) {\n        const char *in_name = strcmp(file->path, \"-\") ? file->path : \"<stdin>\";\n        printf(\"%s -> %s (%d chars)\", in_name, filename_chr, cset->chars);\n      }\n      free(filename_chr);\n    }\n\n    if(cfg->mzm)\n    { // Output MZM\n      char *filename_mzm = OutputPath(file->path, cfg, \".mzm\");\n      WriteMzm(filename_mzm, mzm);\n      FreeMzm(mzm);\n\n      if(!cfg->quiet)\n        printf(\", %s\", filename_mzm);\n\n      free(filename_mzm);\n    }\n\n    if(!cfg->quiet)\n      printf(\"\\n\");\n  }\n\n  return 0;\n}\n"
  },
  {
    "path": "src/utils/checkres.c",
    "content": "/**\n * Find external resources in a MZX world. World is parsed semantically,\n * so false positives are theoretically impossible.\n *\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2007 Josh Matthews <josh@joshmatthews.net>\n * Copyright (C) 2017-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../config.h\"\n\n#ifndef VERSION_DATE\n#define VERSION_DATE\n#endif\n\n#define USAGE \\\n \"checkres :: MegaZeux \" VERSION VERSION_DATE \"\\n\" \\\n \"Usage: checkres [options] \" \\\n \"mzx/mzb/dir/zip file [-extra path/zip file [-in relative path] ...] ... \\n\" \\\n \"\\n\" \\\n \"  checkres scans a MZX, MZB, or ZIP file (or a directory) for any file\\n\"   \\\n \"  dependencies MegaZeux might encounter. This is useful to check e.g. if\\n\" \\\n \"  all files are present before releasing a game. Each required or present\\n\"\\\n \"  file may have one of the following statuses:\\n\\n\"                         \\\n \"    FOUND:     the file is required and was found;\\n\"                       \\\n \"    NOT FOUND: the file is required and was not found;\\n\"                   \\\n \"    CREATED:   the file is required and wasn't found, but may be created;\\n\"\\\n \"    CREATED*:  as above, except more tenuous as this file may be created\\n\" \\\n \"               only depending on the result of expressions/interpolation;\\n\"\\\n \"    PATTERN*:  the file is specified as part of a command that can only\\n\"  \\\n \"               be evaluated while MegaZeux is running, but potential\\n\"     \\\n \"               matches can be inferred contextually using wildcards. This\\n\"\\\n \"               includes things like DOS filenames (xxxxxx~1.ext).\\n\"        \\\n \"    MATCH*:    files potentially matched by a wildcard pattern;\\n\"          \\\n \"    UNUSED:    the file is present but is not used in any MZX/MZB file.\\n\"  \\\n \"\\nGeneral options:\\n\" \\\n \" -h   Display this message and exit.\\n\"                                   \\\n \"\\nStatus options:\\n\" \\\n \" -a   Display all found, created, missing, wildcard, and unused files.\\n\" \\\n \" -A   Display all found, created, missing, and wildcard files.\\n\"         \\\n \" -C   Only display created files.\\n\"                                      \\\n \" -F   Only display found files.\\n\"                                        \\\n \" -M   Only display missing files (default).\\n\"                            \\\n \" -U   Only display unused files.\\n\"                                       \\\n \" -W   Only display wildcard-matched files.\\n\"                             \\\n \" -c   Also display created files.\\n\"                                      \\\n \" -f   Also display found files.\\n\"                                        \\\n \" -m   Also display missing files.\\n\"                                      \\\n \" -u   Also display unused files.\\n\"                                       \\\n \" -w   Also display wildcard-matched files.\\n\"                             \\\n \"\\nDetail options:\\n\" \\\n \" -s   Summary: unique filenames, status, source, world file (default).\\n\" \\\n \" -v   Display all references with sfx#, board#, robot#.\\n\"                \\\n \" -vv  Display all references with sfx#, board#, robot#, line#, coords.\\n\" \\\n \" -1   Display unique filenames, one per line, with no other info.\\n\"      \\\n \" -z   Also display CRC-32 values from zip central directory (zip only).\\n\"\\\n \"\\nOutput options:\\n\" \\\n \" -V   Output CSV instead of preformatted text.\\n\"                         \\\n \"\\nSorting options:\\n\" \\\n \" -N   Sort by referenced filename, then by location (default).\\n\"         \\\n \" -L   Sort by location of reference: world, board#, robot#.\\n\"            \\\n \"\\n\"\n\n#include <ctype.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n#include <sys/stat.h>\n\n#ifdef __WIN32__\n#include <strings.h>\n#endif\n\n// Defines so checkres builds when MZX headers are included.\n#define CORE_LIBSPEC\n\n// From MZX itself:\n\n// Safe- self sufficient or completely macros/static inlines\n#include \"../const.h\"\n#include \"../hashtable.h\"\n#include \"../memcasecmp.h\"\n#include \"../world_format.h\"\n#include \"../io/memfile.h\"\n#include \"../io/vio.h\"\n#include \"../io/zip.h\"\n\n// Contains some CORE_LIBSPEC functions, which should be fine if the object\n// is included in linking due to the CORE_LIBSPEC define above. Right now,\n// checkres needs fsafeopen.o and util.o.\n#include \"../io/fsafeopen.h\"\n#include \"../util.h\"\n#include \"../world.h\"\n\n#include \"utils_alloc.h\"\n\n#ifdef CONFIG_PLEDGE_UTILS\n#include <unistd.h>\n#define PROMISES \"stdio rpath\"\n#endif\n\n// From const.h (copied here for convenience)\n#define BOARD_NAME_SIZE 25\n#define MAX_BOARDS 250\n\n// From data.h\n#define IMAGE_FILE 100\n\n// From sfx.h\n#define NUM_BUILTIN_SFX 50\n//#define MAX_NUM_SFX 256\n#define LEGACY_SFX_SIZE 69\n#define MAX_SFX_SIZE 256\n\n#define LEGACY_WORLD_PROTECTED_OFFSET      BOARD_NAME_SIZE\n#define LEGACY_WORLD_GLOBAL_OFFSET_OFFSET  4230\n#define LEGACY_WORLD_BOARD_COUNT_100       4009\n\n#define MAX_PATH_DEPTH 10\n\nstatic const char *found_append = \"FOUND\";\nstatic const char *created_append = \"CREATED\";\nstatic const char *not_found_append = \"NOT FOUND\";\nstatic const char *unused_append = \"UNUSED\";\nstatic const char *pattern_append = \"PATTERN*\";\nstatic const char *maybe_used_append = \"MATCH*\";\nstatic const char *maybe_created_append = \"CREATED*\";\n\n// Status options\nstatic boolean display_not_found = true;\nstatic boolean display_found = false;\nstatic boolean display_created = false;\nstatic boolean display_unused = false;\nstatic boolean display_wildcard = false;\n\n// Detail options\nstatic boolean display_filename_only = false;\nstatic boolean display_first_only = true;\nstatic boolean display_details = false;\nstatic boolean display_all_details = false;\nstatic boolean display_crc32 = false;\n\n// Output format options\nstatic boolean output_format_csv = false;\n\nstatic enum\n{\n  SORT_BY_LOCATION,\n  SORT_BY_FILENAME,\n}\nsort_by = SORT_BY_FILENAME;\n\nenum status\n{\n  SUCCESS = 0,\n  INVALID_ARGUMENTS,\n  CORRUPT_WORLD,\n  FOPEN_FAILED,\n  GET_PATH_FAILED,\n  CHDIR_FAILED,\n  FREAD_FAILED,\n  FSEEK_FAILED,\n  MALLOC_FAILED,\n  PROTECTED_WORLD,\n  MAGIC_CHECK_FAILED,\n  BAD_100_PASSWORD,\n  DIRENT_FAILED,\n  ZIP_FAILED,\n  NO_WORLD,\n  MISSING_FILE\n};\n\n#define warnhere(...) \\\n do{ fprintf(stderr, \"At \" __FILE__ \":%d: \", __LINE__); \\\n  fprintf(stderr, \"\" __VA_ARGS__); fflush(stderr); }while(0)\n\nstatic const char *decode_status(enum status status)\n{\n  switch(status)\n  {\n    case INVALID_ARGUMENTS:\n      return \"Invalid file or argument provided.\";\n    case CORRUPT_WORLD:\n      return \"World corruption or truncation detected.\";\n    case FOPEN_FAILED:\n      return \"Could not open file.\";\n    case GET_PATH_FAILED:\n      return \"Failed to obtain path from filename.\";\n    case CHDIR_FAILED:\n      return \"Failed to chdir() into parent path.\";\n    case FREAD_FAILED:\n      return \"File read failed or was short (corrupt file?).\";\n    case FSEEK_FAILED:\n      return \"Seeking in file failed (too short?).\";\n    case MALLOC_FAILED:\n      return \"Memory allocation failed or was zero.\";\n    case PROTECTED_WORLD:\n      return \"Protected worlds currently unsupported.\";\n    case MAGIC_CHECK_FAILED:\n      return \"File magic not consistent with MZX world or board.\";\n    case BAD_100_PASSWORD:\n      return \"Invalid version 1.x password length.\";\n    case DIRENT_FAILED:\n      return \"Failed to read a required directory.\";\n    case ZIP_FAILED:\n      return \"Something is wrong with the zip file.\";\n    case NO_WORLD:\n      return \"No world file present.\";\n    default:\n      return \"Unknown error.\";\n  }\n}\n\nstatic boolean _get_path(char *dest, const char *src)\n{\n  int i;\n\n  // Copy the file's path to the path string.\n  i = strlen(src)-1;\n  while((src[i] != '/') && (src[i] != '\\\\') && i)\n    i--;\n\n  if(i > 0)\n  {\n    memcpy(dest, src, i);\n    dest[i] = '\\0';\n    return true;\n  }\n  dest[0] = '\\0';\n  return false;\n}\n\nstatic void join_path(char *dest, const char *dir, const char *file)\n{\n  if(dir && dir[0])\n  {\n    if(file && file[0])\n    {\n      if(dir[strlen(dir) - 1] != '/')\n        snprintf(dest, MAX_PATH, \"%s/%s\", dir, file);\n\n      else\n        snprintf(dest, MAX_PATH, \"%s%s\", dir, file);\n    }\n\n    else\n      snprintf(dest, MAX_PATH, \"%s\", dir);\n  }\n\n  else\n  {\n    snprintf(dest, MAX_PATH, \"%s\", file);\n  }\n\n  dest[MAX_PATH - 1] = 0;\n}\n\nstatic boolean is_simple_path(const char *src, boolean allow_expressions)\n{\n  size_t len = strlen(src);\n  unsigned int i;\n\n  // No empty filename.\n  if(len == 0)\n    return false;\n\n  // No strings.\n  if(len >= 1)\n    if(src[0] == '$')\n      return false;\n\n  for(i = 0; i < len; i++)\n  {\n    // No interpolation. A single & at the end of an expression is a\n    // valid interpolation due to a longstanding MZX bug.\n\n    if(allow_expressions && src[i] == '&')\n      if(i+1 == len || src[i+1] != '&')\n        return false;\n\n    // No expressions.\n    if(allow_expressions && src[i] == '(')\n      return false;\n  }\n\n  // No truncated DOS filenames (attempt to wildcard these)\n  if(len <= 12)\n  {\n    const char *tpos = strchr(src, '~');\n    if(tpos)\n    {\n      while(*(++tpos))\n      {\n        if(isdigit((unsigned char)*tpos)) continue;\n        if(*tpos == '.' || *tpos == '\\0')\n          return false;\n      }\n    }\n  }\n  return true;\n}\n\nstatic const char *skip_expression(const char *src)\n{\n  int level = 0;\n  if(*src != '(')\n    return src;\n\n  do\n  {\n    if(*src == '(')\n      level++;\n    if(*src == ')')\n      level--;\n    src++;\n  }\n  while(*src && level > 0);\n\n  if(level == 0)\n    return src;\n\n  return NULL;\n}\n\nstatic const char *skip_interpolation(const char *src)\n{\n  if(*src != '&')\n    return src;\n  src++;\n\n  if(*src == '&')\n    return src;\n\n  // Due to an MZX bug, the end of the string is a valid terminator.\n  while(src && *src && *src != '&')\n  {\n    if(*src == '(')\n      src = skip_expression(src);\n    else\n      src++;\n  }\n  if(src && *src == '&')\n    src++;\n  return src;\n}\n\nstatic boolean get_wildcard_path(char dest[MAX_PATH], const char *src)\n{\n  size_t len = strlen(src);\n  size_t i;\n  size_t j;\n\n  // No empty filename\n  if(len == 0)\n    return false;\n\n  // No strings.\n  if(len >= 1)\n    if(src[0] == '$')\n      return false;\n\n  for(i = 0, j = 0; i < len && j < MAX_PATH; i++)\n  {\n    if(src[i] == '&' && src[i+1] != '&')\n    {\n      size_t start = i + 1;\n      const char *end = skip_interpolation(src + i);\n      if(!end)\n        return false;\n\n      i = end - src - 1;\n\n      // String (assume interpolation for now)\n      if(src[start] == '$')\n        dest[j++] = '*';\n\n      // Special: INPUT is also a string when interpolated.\n      else\n      if(!strncasecmp(src + start, \"INPUT\", strlen(\"INPUT\")))\n        dest[j++] = '*';\n\n      // Counter\n      else\n        dest[j++] = '#';\n    }\n    else\n\n    if(src[i] == '(')\n    {\n      // Expression\n      const char *end = skip_expression(src + i);\n      if(!end)\n        return false;\n\n      i = end - src - 1;\n      dest[j++] = '#';\n    }\n    else\n\n    if(src[i] == '~')\n    {\n      // Truncated DOS filename--replace ~### with wildcard\n      size_t backup = i;\n      if(i + 1 < len && isdigit((unsigned char)src[i + 1]))\n      {\n        while(i + 1 < len && isdigit((unsigned char)src[i + 1])) i++;\n        if(i + 1 >= len || src[i + 1] == '.')\n        {\n          dest[j++] = '*';\n          continue;\n        }\n      }\n      i = backup;\n      dest[j++] = '~';\n    }\n    else\n\n    if(src[i] == '*')\n    {\n      // Escape %\n      dest[j++] = '|';\n      dest[j++] = '*';\n    }\n    else\n\n    if(src[i] == '#')\n    {\n      // Escape #\n      dest[j++] = '|';\n      dest[j++] = '#';\n    }\n    else\n\n    if(src[i] == '|')\n    {\n      // Escape | (can't use backslash because it'll be recognized as a\n      // path separator)\n      dest[j++] = '|';\n      dest[j++] = '|';\n    }\n\n    else\n      dest[j++] = src[i];\n  }\n  dest[MIN(j, MAX_PATH - 1)] = 0;\n  return true;\n}\n\nstatic boolean check_wildcard_path(const char *path, const char *wildcard)\n{\n#define MAX_STATE 1024\n#define MAX_ITER 1024\n  size_t iter = 0;\n  struct _state\n  {\n    uint16_t p;\n    uint16_t w;\n  }\n  state[MAX_STATE] = {{0, 0}};\n  int pos = 0;\n\n  size_t wildcard_len = strlen(wildcard);\n  size_t path_len = strlen(path);\n  size_t i;\n  size_t p;\n  size_t w;\n  char next;\n\n  if(path_len > MAX_PATH)\n    return false;\n\n  while(pos >= 0)\n  {\n    iter++;\n    if(iter >= MAX_ITER) return -1;\n\n    p = state[pos].p;\n    w = state[pos].w;\n\n    if(p == path_len || w == wildcard_len)\n    {\n      // Skip trailing wildcards if they exist\n      while(wildcard[w] == '*') w++;\n\n      if(p == path_len && w == wildcard_len)\n        return true;\n\n      pos--;\n      continue;\n    }\n\n    next = wildcard[w++];\n\n    switch(next)\n    {\n      case '*':\n      {\n        // Skip duplicate wildcards if they exist, pop current state\n        while(wildcard[w] == '*') w++;\n        pos--;\n\n        for(i = p; i <= path_len; i++)\n        {\n          if(pos == MAX_STATE - 1) break;\n          pos++;\n          state[pos].p = i;\n          state[pos].w = w;\n        }\n        break;\n      }\n\n      case '#':\n      {\n        // Numeric\n        // Consume '-' if it exists, pop current state\n        if(path[p] == '-') p++;\n        pos--;\n\n        // Allow up to 10 digits to be consumed\n        for(i = p; i < MIN(p + 10, path_len); i++)\n        {\n          if(pos == MAX_STATE - 1) break;\n          if(isdigit((unsigned char)path[i]))\n          {\n            pos++;\n            state[pos].p = i + 1;\n            state[pos].w = w;\n          }\n          else\n            break;\n        }\n        break;\n      }\n\n      case '|':\n        next = wildcard[w++];\n        /* fall-through */\n\n      default:\n      {\n        if(memtolower(path[p]) == memtolower(next))\n        {\n          state[pos].p++;\n          state[pos].w = w;\n        }\n        else\n          pos--;\n\n        break;\n      }\n    }\n  }\n  return false;\n}\n\nstatic void strcpy_fsafe(char *dest, size_t dest_len, const char *src)\n{\n  int result = fsafetranslate(src, dest, dest_len);\n\n  // SUCCESS - file was actually found physically\n  // MATCH_FAILED - no file was found physically, which is fine here\n  // MATCHED_DIRECTORY - a physical directory was found, which is fine here\n\n  if(result != FSAFE_SUCCESS && result != -FSAFE_MATCH_FAILED &&\n   result != -FSAFE_MATCHED_DIRECTORY)\n    dest[0] = 0;\n}\n\nstatic boolean path_search(const char *path_name, size_t base_len, int max_depth,\n void *data, void (*found_fn)(void *data, const char *name, size_t name_len))\n{\n  vdir *dir;\n  struct stat st;\n  boolean join_paths = true;\n\n  if(!strlen(path_name) || !strcmp(path_name, \".\"))\n  {\n    dir = vdir_open(\".\");\n    max_depth = MAX_PATH_DEPTH;\n    join_paths = false;\n    base_len = 0;\n  }\n  else\n    dir = vdir_open(path_name);\n\n  if(dir)\n  {\n    char buffer[MAX_PATH];\n    char *_current = cmalloc(MAX_PATH);\n    const char *current = NULL;\n    enum vdir_type type;\n\n    while(vdir_read(dir, buffer, MAX_PATH, &type))\n    {\n      if(strcmp(buffer, \".\") && strcmp(buffer, \"..\"))\n      {\n        if(join_paths)\n        {\n          join_path(_current, path_name, buffer);\n          current = _current;\n        }\n        else\n          current = buffer;\n\n        if(type == DIR_TYPE_UNKNOWN)\n        {\n          if(!vstat(current, &st))\n          {\n            if(S_ISREG(st.st_mode))\n              type = DIR_TYPE_FILE;\n\n            if(S_ISDIR(st.st_mode))\n              type = DIR_TYPE_DIR;\n          }\n        }\n\n        if(type == DIR_TYPE_DIR)\n        {\n          // yolo\n          if(max_depth > 0)\n            path_search(current, base_len, max_depth - 1, data, found_fn);\n        }\n        else\n\n        if(type == DIR_TYPE_FILE)\n        {\n          // Strip off the base path if requested.\n          if(base_len && strlen(current) > base_len)\n          {\n            current += base_len;\n            while(*current == '\\\\' || *current == '/') current++;\n          }\n\n          found_fn(data, current, strlen(current));\n        }\n      }\n    }\n    vdir_close(dir);\n    free(_current);\n    return true;\n  }\n  return false;\n}\n\n#define PARENT_DEFAULT_LEN 13\n#define RESOURCE_DEFAULT_LEN 14\n#define DETAILS_MAX_LEN 35\n#define DETAILS_SHORT_LEN 12\n#define GLOBAL_ROBOT 0\n#define DONT_PRINT -255\n#define IS_SFX -1\n#define IS_BOARD_MOD -3\n#define IS_BOARD_CHARSET -2\n#define IS_BOARD_PALETTE -1\n\nstatic int started_table = 0;\nstatic int parent_max_len = PARENT_DEFAULT_LEN;\nstatic int resource_max_len = RESOURCE_DEFAULT_LEN;\nstatic boolean table_has_crc32 = false;\n\nstatic void output_preformatted(const char *required_by,\n int board_num, int robot_num, int line_num, int x, int y,\n const char *resource_path, const char *status, const char *found_in,\n uint32_t crc32, boolean has_crc32)\n{\n  char details[DETAILS_MAX_LEN];\n  char crc[9] = \"\\0\";\n  int details_max_len = display_details ?\n   (display_all_details ? DETAILS_MAX_LEN : DETAILS_SHORT_LEN) : 0;\n  int crc32_len = (display_crc32 && table_has_crc32) ? 11 : 0;\n\n  if(!display_filename_only)\n  {\n    found_in = found_in ? found_in : \"\";\n\n    if(!started_table)\n    {\n      fprintf(stdout, \"\\n\");\n\n      fprintf(stdout, \"%-*.*s  %-*.*s%-*.*s  %-10s%-*.*s %s\\n\",\n       parent_max_len, parent_max_len, \"Required by\",\n       details_max_len, details_max_len, \"B#    R#    Line     Position\",\n       resource_max_len, resource_max_len, \"Expected file\",\n       \"Status\",\n       crc32_len, crc32_len, \"CRC-32\",\n       \"Found in\"\n      );\n\n      fprintf(stdout, \"%-*.*s  %-*.*s%-*.*s  %-10s%-*.*s %s\\n\",\n       parent_max_len, parent_max_len, \"-----------\",\n       details_max_len, details_max_len, \"---   ---   ------   -----------\",\n       resource_max_len, resource_max_len, \"-------------\",\n       \"------\",\n       crc32_len, crc32_len, \"------\",\n       \"--------\"\n      );\n\n      started_table = 1;\n    }\n\n    if(display_details && board_num != DONT_PRINT)\n    {\n      char board[6];\n      char robot[6];\n      char line[8];\n      char xy[14];\n      snprintf(board, 6, \"%d\", board_num);\n      snprintf(robot, 6, \"%d\", robot_num);\n      snprintf(line, 8, \"%d\", line_num);\n\n      if(board_num == IS_SFX)\n        snprintf(details, DETAILS_MAX_LEN, \"sfx   %-3.3s\", robot);\n      else\n      if(robot_num > 0)\n      {\n        snprintf(xy, 14, \"%d, %d\", (int16_t)x, (int16_t)y);\n        snprintf(details, DETAILS_MAX_LEN,\n          \"b%-3.3s  r%-3.3s  %-6.6s   %-11.11s\",\n          board, robot, line, xy\n        );\n      }\n      else\n      if(board_num == NO_BOARD && robot_num == GLOBAL_ROBOT)\n      {\n        snprintf(details, DETAILS_MAX_LEN,\n         \"      gl    %-6.6s   -1, -1\", line);\n      }\n      else\n      if(robot_num == IS_BOARD_MOD)\n        snprintf(details, DETAILS_MAX_LEN, \"b%-3.3s  mod  \", board);\n      else\n      if(robot_num == IS_BOARD_CHARSET)\n        snprintf(details, DETAILS_MAX_LEN, \"b%-3.3s  chr  \", board);\n      else\n      if(robot_num == IS_BOARD_PALETTE)\n        snprintf(details, DETAILS_MAX_LEN, \"b%-3.3s  pal  \", board);\n      else\n        snprintf(details, DETAILS_MAX_LEN, \"(unknown)\");\n      details[DETAILS_MAX_LEN - 1] = '\\0';\n    }\n    else\n      details[0] = 0;\n\n    if(crc32_len && has_crc32)\n      snprintf(crc, 9, \"%8.8\"PRIx32, crc32);\n\n    fprintf(stdout, \"%-*.*s  %-*.*s%-*.*s  %-10s%-*.*s %s\\n\",\n     parent_max_len, parent_max_len, required_by,\n     details_max_len, details_max_len, details,\n     resource_max_len, resource_max_len, resource_path,\n     status,\n     crc32_len, crc32_len, crc,\n     found_in\n    );\n  }\n}\n\nstatic void output_csv(const char *required_by,\n int board_num, int robot_num, int line_num, int x, int y,\n const char *resource_path, const char *status, const char *found_in,\n uint32_t crc32, boolean has_crc32)\n{\n  // TODO add proper escaping for the filenames.\n  found_in = found_in ? found_in : \"\";\n\n  if(!started_table)\n  {\n    fprintf(stdout, \"Required by,\");\n    if(display_details)\n    {\n      fprintf(stdout, \"Board #,Robot #,\");\n      if(display_all_details)\n        fprintf(stdout, \"Line,Position,\");\n    }\n    fprintf(stdout, \"Expected file,Status,\");\n\n    if(display_crc32)\n      fprintf(stdout, \"CRC-32,\");\n\n    fprintf(stdout, \"Found in\\n\");\n\n    started_table = 1;\n  }\n\n  fprintf(stdout, \"%s,\", required_by);\n\n  if(display_details)\n  {\n    const char *filler = display_all_details ? \",,\" : \"\";\n\n    if(board_num == DONT_PRINT)\n    {\n      fprintf(stdout, \",,%s\", filler);\n    }\n    else\n\n    if(board_num == IS_SFX)\n    {\n      fprintf(stdout, \"sfx,%d,%s\", robot_num, filler);\n    }\n    else\n\n    if(robot_num > 0)\n    {\n      if(display_all_details)\n      {\n        fprintf(stdout, \"b%d,r%d,%d,\\\"(%d,%d)\\\",\",\n          board_num, robot_num, line_num, x, y\n        );\n      }\n      else\n        fprintf(stdout, \"b%d,r%d,\", board_num, robot_num);\n    }\n    else\n\n    if(board_num == NO_BOARD && robot_num == GLOBAL_ROBOT)\n    {\n      if(display_all_details)\n      {\n        fprintf(stdout, \",gl,%d,\\\"(-1,-1)\\\",\", line_num);\n      }\n      else\n        fprintf(stdout, \",gl,\");\n    }\n    else\n\n    if(robot_num == IS_BOARD_MOD)\n    {\n      fprintf(stdout, \"b%d,mod,%s\", board_num, filler);\n    }\n    else\n\n    if(robot_num == IS_BOARD_CHARSET)\n    {\n      fprintf(stdout, \"b%d,chr,%s\", board_num, filler);\n    }\n    else\n\n    if(robot_num == IS_BOARD_PALETTE)\n    {\n      fprintf(stdout, \"b%d,pal,%s\", board_num, filler);\n    }\n    else\n      fprintf(stdout, \"(unknown),,%s\", filler);\n  }\n\n  fprintf(stdout, \"%s,%s,\", resource_path, status);\n\n  if(display_crc32)\n  {\n    if(has_crc32)\n    {\n      fprintf(stdout, \"%8.8\"PRIx32\",\", crc32);\n    }\n    else\n      fprintf(stdout, \",\");\n  }\n\n  fprintf(stdout, \"%s\\n\", found_in);\n}\n\nstatic void output(const char *required_by,\n int board_num, int robot_num, int line_num, int x, int y,\n const char *resource_path, const char *status, const char *found_in,\n uint32_t crc32, boolean has_crc32)\n{\n  if(display_filename_only)\n  {\n    // Quiet mode- just print the resource paths\n    fprintf(stdout, \"%s\\n\", resource_path);\n  }\n  else\n\n  if(output_format_csv)\n  {\n    output_csv(required_by, board_num, robot_num, line_num, x, y, resource_path,\n     status, found_in, crc32, has_crc32);\n  }\n\n  else\n  {\n    output_preformatted(required_by, board_num, robot_num, line_num, x, y,\n     resource_path, status, found_in, crc32, has_crc32);\n  }\n}\n\n/*******************/\n/* Data processing */\n/*******************/\n\nstruct base_path_file\n{\n  char file_path[MAX_PATH];\n  int file_path_len;\n  uint32_t hash;\n  uint32_t crc32;\n  boolean has_crc32;\n  boolean used;\n  boolean used_wildcard;\n};\n\nHASH_SET_INIT(BASE_PATH_FILE, struct base_path_file *, file_path, file_path_len)\n\nstruct base_path\n{\n  char actual_path[MAX_PATH];\n  char relative_path[MAX_PATH];\n  struct zip_archive *zp;\n  hash_t(BASE_PATH_FILE) *file_list_table;\n};\n\nstruct base_file\n{\n  char file_name[MAX_PATH];\n  char relative_path[MAX_PATH];\n  char password[16];\n  int password_length;\n  int protection_method;\n  int world_version;\n  int file_version;\n};\n\nstruct resource\n{\n  char path[MAX_PATH * 2];\n  int path_len;\n  int key_len;\n  int board_num;\n  int robot_num;\n  int line_num;\n  int x;\n  int y;\n  uint32_t hash;\n  boolean is_wildcard;\n  struct base_file *parent;\n};\n\nHASH_SET_INIT(RESOURCE, struct resource *, path, key_len)\n\n// NULL is important\nstatic hash_t(RESOURCE) *requirement_table = NULL;\nstatic hash_t(RESOURCE) *resource_table = NULL;\n\nstatic int16_t robot_xpos[256];\nstatic int16_t robot_ypos[256];\n\nstatic struct resource *add_requirement_ext(const char *src,\n struct base_file *file, int board_num, int robot_num, int line_num, int x,\n int y, boolean allow_expressions)\n{\n  // A resource file required by a world/board.\n  struct resource *req = NULL;\n  char fsafe_buffer[MAX_PATH * 2];\n  char temporary_buffer[MAX_PATH];\n  boolean is_wildcard = false;\n  int fsafe_len;\n\n  // Offset the required file's path with the relative path of its parent\n  // The file might require wildcard conversion first (if allowed)\n  if(!is_simple_path(src, allow_expressions))\n  {\n    if(!get_wildcard_path(fsafe_buffer, src))\n      return NULL;\n\n    join_path(temporary_buffer, file->relative_path, fsafe_buffer);\n    is_wildcard = true;\n  }\n  else\n    join_path(temporary_buffer, file->relative_path, src);\n\n  strcpy_fsafe(fsafe_buffer, MAX_PATH, temporary_buffer);\n  fsafe_len = strlen(fsafe_buffer);\n\n  if(!display_first_only)\n  {\n    // add more info to the key.\n    snprintf(fsafe_buffer + fsafe_len + 1, MAX_PATH, \"%04x%04x%08x%s\",\n     (int16_t)board_num, (int16_t)robot_num, line_num, file->file_name);\n    fsafe_len += 1 + strlen(fsafe_buffer + fsafe_len + 1);\n  }\n\n  HASH_FIND(RESOURCE, requirement_table, fsafe_buffer, fsafe_len, req);\n\n  if(!req)\n  {\n    req = cmalloc(sizeof(struct resource));\n\n    // NOTE: might be copying data past the terminator, see key extension above\n    memcpy(req->path, fsafe_buffer, fsafe_len + 1);\n    req->path_len = strlen(fsafe_buffer);\n    req->key_len = fsafe_len;\n    req->board_num = board_num;\n    req->robot_num = robot_num;\n    req->line_num = line_num;\n    req->x = x;\n    req->y = y;\n    req->is_wildcard = is_wildcard;\n    req->parent = file;\n\n    // Only need to compute these sizes if there's a situation where a\n    // requirement might be displayed. The only time this isn't the case\n    // currently is when only unused files are being displayed, though.\n    if(display_not_found || display_found || display_created || display_wildcard)\n    {\n      resource_max_len = MAX(resource_max_len, req->path_len);\n      parent_max_len = MAX(parent_max_len, (int)strlen(file->file_name));\n    }\n    HASH_ADD(RESOURCE, requirement_table, req);\n  }\n  return req;\n}\n\nstatic struct resource *add_requirement_sfx(const char *src,\n struct base_file *file, int board_num, int robot_num, int line_num)\n{\n  return add_requirement_ext(src, file, board_num, robot_num, line_num, -1, -1, false);\n}\n\nstatic struct resource *add_requirement_board(const char *src,\n struct base_file *file, int board_num, int resource_type)\n{\n  if(board_num < 0 || resource_type >= 0) return NULL;\n  return add_requirement_ext(src, file, board_num, resource_type, -1, -1, -1, false);\n}\n\nstatic struct resource *add_requirement_robot(const char *src,\n struct base_file *file, int board_num, int robot_num, int line_num)\n{\n  int x, y;\n  if(robot_num < 0 || board_num < 0 ||\n   (board_num == NO_BOARD && robot_num != GLOBAL_ROBOT))\n    return NULL;\n\n  if(robot_num != GLOBAL_ROBOT)\n  {\n    x = robot_xpos[robot_num];\n    y = robot_ypos[robot_num];\n  }\n  else\n    x = y = -1;\n\n  return add_requirement_ext(src, file, board_num, robot_num, line_num, x, y, true);\n}\n\nstatic struct resource *add_resource(const char *src, struct base_file *file)\n{\n  // A filename found in Robotic code that may fulfill a requirement for a file.\n  struct resource *res = NULL;\n  char fsafe_buffer[MAX_PATH];\n  char temporary_buffer[MAX_PATH];\n  boolean is_wildcard = false;\n  int fsafe_len;\n\n  // Offset the required file's path with the relative path of its parent\n  // The file might require wildcard conversion first (if allowed)\n  if(!is_simple_path(src, true))\n  {\n    if(!get_wildcard_path(fsafe_buffer, src))\n      return NULL;\n\n    join_path(temporary_buffer, file->relative_path, fsafe_buffer);\n    is_wildcard = true;\n  }\n  else\n    join_path(temporary_buffer, file->relative_path, src);\n\n  strcpy_fsafe(fsafe_buffer, MAX_PATH, temporary_buffer);\n  fsafe_len = strlen(fsafe_buffer);\n\n  HASH_FIND(RESOURCE, resource_table, fsafe_buffer, fsafe_len, res);\n\n  if(!res)\n  {\n    res = cmalloc(sizeof(struct resource));\n\n    snprintf(res->path, MAX_PATH, \"%s\", fsafe_buffer);\n    res->path_len = fsafe_len;\n    res->key_len = fsafe_len;\n    res->board_num = -1;\n    res->robot_num = -1;\n    res->line_num = -1;\n    res->x = -1;\n    res->y = -1;\n    res->is_wildcard = is_wildcard;\n    res->parent = NULL;\n\n    // Created resources are never displayed directly so this isn't necessary.\n    // However, if this changes, uncomment this.\n    /*if(display_created)\n    {\n      resource_max_len = MAX(resource_max_len, fsafe_len);\n      parent_max_len = MAX(parent_max_len, (int)strlen(file->file_name));\n    }*/\n    HASH_ADD(RESOURCE, resource_table, res);\n  }\n\n  return res;\n}\n\nstatic void add_base_path_file(struct base_path *path,\n const char *file_name, size_t file_name_length, uint32_t crc32,\n boolean has_crc32)\n{\n  struct base_path_file *entry;\n\n  if(file_name_length >= MAX_PATH)\n    file_name_length = MAX_PATH - 1;\n\n  HASH_FIND(BASE_PATH_FILE, path->file_list_table, file_name, file_name_length,\n    entry);\n\n  if(!entry)\n  {\n    entry = cmalloc(sizeof(struct base_path_file));\n\n    memcpy(entry->file_path, file_name, file_name_length);\n    entry->file_path[file_name_length] = '\\0';\n    entry->file_path_len = file_name_length;\n    entry->crc32 = crc32;\n    entry->has_crc32 = has_crc32;\n    entry->used = false;\n    entry->used_wildcard = false;\n\n    HASH_ADD(BASE_PATH_FILE, path->file_list_table, entry);\n\n    if(display_unused || display_wildcard)\n      resource_max_len = MAX(resource_max_len, (int)file_name_length);\n  }\n}\n\nstatic void add_base_path_file_wr(void *path, const char *file_name,\n size_t file_name_length)\n{\n  add_base_path_file((struct base_path *)path, file_name, file_name_length,\n   0, false);\n}\n\nstatic void build_zip_base_path_table(struct base_path *path,\n struct zip_archive *zp)\n{\n  struct zip_file_header **files = zp->files;\n  struct zip_file_header *fh;\n  int num_files = zp->num_files;\n  int i;\n\n  for(i = 0; i < num_files; i++)\n  {\n    fh = files[i];\n    add_base_path_file(path, fh->file_name, fh->file_name_length,\n     fh->crc32, true);\n  }\n  table_has_crc32 = true;\n}\n\nstatic void change_base_path_dir(struct base_path *current_path,\n const char *new_relative_path)\n{\n  size_t len;\n  fsafetranslate(new_relative_path, current_path->relative_path, MAX_PATH);\n\n  len = strlen(current_path->relative_path);\n  if(current_path->relative_path[len - 1] != '/' && len < MAX_PATH - 1)\n  {\n    current_path->relative_path[len++] = '/';\n    current_path->relative_path[len] = '\\0';\n  }\n}\n\nstatic struct base_path *add_base_path(const char *path_name,\n struct base_path ***path_list, int *path_list_size, int *path_list_alloc)\n{\n  struct stat st;\n  struct base_path *new_path;\n  int alloc = *path_list_alloc;\n  int size = *path_list_size;\n\n  size_t path_name_len = strlen(path_name);\n\n  if(vstat(path_name, &st))\n    return NULL;\n\n  new_path = ccalloc(1, sizeof(struct base_path));\n\n  if(S_ISREG(st.st_mode))\n  {\n    // Attempt to open the path as a zip archive.\n    struct zip_archive *zp = zip_open_file_read(path_name);\n\n    if(!zp)\n    {\n      free(new_path);\n      return NULL;\n    }\n\n    build_zip_base_path_table(new_path, zp);\n    new_path->zp = zp;\n  }\n  else\n\n  if(S_ISDIR(st.st_mode))\n  {\n    // Attempt to recursively build a file list of this directory's contents.\n    if(!path_search(path_name, path_name_len, MAX_PATH_DEPTH,\n     (void *)new_path, add_base_path_file_wr))\n    {\n      free(new_path);\n      return NULL;\n    }\n  }\n  else\n  {\n    free(new_path);\n    return NULL;\n  }\n\n  snprintf(new_path->actual_path, MAX_PATH, \"%s\", path_name);\n\n  if(size == alloc)\n  {\n    if(alloc == 0)\n    {\n      *path_list_alloc = 4;\n      alloc = 4;\n    }\n\n    *path_list = crealloc(*path_list, 2 * alloc * sizeof(struct base_path *));\n    *path_list_alloc *= 2;\n  }\n\n  (*path_list)[size] = new_path;\n  (*path_list_size)++;\n\n  return new_path;\n}\n\nstatic struct base_file *add_base_file(const char *path_name,\n struct base_file ***file_list, int *file_list_size, int *file_list_alloc)\n{\n  struct base_file *new_file = ccalloc(1, sizeof(struct base_file));\n  int alloc = *file_list_alloc;\n  int size = *file_list_size;\n\n  snprintf(new_file->file_name, MAX_PATH, \"%s\", path_name);\n\n  if(size == alloc)\n  {\n    if(alloc == 0)\n    {\n      *file_list_alloc = 4;\n      alloc = 4;\n    }\n\n    *file_list = crealloc(*file_list, 2 * alloc * sizeof(struct base_file *));\n    *file_list_alloc *= 2;\n  }\n\n  (*file_list)[size] = new_file;\n  (*file_list_size)++;\n\n  return new_file;\n}\n\nstruct base_file_list_data\n{\n  struct base_file ***file_list;\n  int *file_list_size;\n  int *file_list_alloc;\n};\n\nstatic void base_file_found_fn(void *data, const char *name, size_t name_len)\n{\n  struct base_file_list_data *d = (struct base_file_list_data *)data;\n  const char *ext = name_len >= 4 ? name + name_len - 4 : NULL;\n\n  if(ext && (!strcasecmp(ext, \".MZX\") || !strcasecmp(ext, \".MZB\")))\n  {\n    struct base_file *bf =\n     add_base_file(name, d->file_list, d->file_list_size, d->file_list_alloc);\n\n    if(bf)\n      _get_path(bf->relative_path, name);\n  }\n}\n\nstatic boolean add_base_files_from_path(const char *path_name,\n struct base_file ***file_list, int *file_list_size, int *file_list_alloc)\n{\n  struct base_file_list_data d = { file_list, file_list_size, file_list_alloc };\n  return path_search(path_name, strlen(path_name), MAX_PATH_DEPTH,\n   (void *)&d, base_file_found_fn);\n}\n\nstatic int req_sort_by_location_fn(const void *A, const void *B)\n{\n  const struct resource *a = *(struct resource **)A;\n  const struct resource *b = *(struct resource **)B;\n  int parent_cmp = strcmp(a->parent->file_name, b->parent->file_name);\n  return\n    parent_cmp ? parent_cmp :\n    a->board_num != b->board_num ? a->board_num - b->board_num :\n    a->robot_num != b->robot_num ? a->robot_num - b->robot_num :\n    a->line_num != b->line_num ? a->line_num - b->line_num :\n    strcmp(a->path, b->path);\n}\n\nstatic int req_sort_by_filename_fn(const void *A, const void *B)\n{\n  const struct resource *a = *(struct resource **)A;\n  const struct resource *b = *(struct resource **)B;\n  int path_cmp = strcmp(a->path, b->path);\n  int parent_cmp = strcmp(a->parent->file_name, b->parent->file_name);\n  return\n    path_cmp ? path_cmp :\n    parent_cmp ? parent_cmp :\n    a->board_num != b->board_num ? a->board_num - b->board_num :\n    a->robot_num != b->robot_num ? a->robot_num - b->robot_num :\n    a->line_num - b->line_num;\n}\n\nstatic int bpf_sort_fn(const void *A, const void *B)\n{\n  const struct base_path_file *a = *(struct base_path_file **)A;\n  const struct base_path_file *b = *(struct base_path_file **)B;\n  return\n  (a->used_wildcard != b->used_wildcard) ? b->used_wildcard - a->used_wildcard :\n   strcmp(a->file_path, b->file_path);\n}\n\nstatic void process_requirements(struct base_path **path_list,\n int path_list_size)\n{\n#define FOUND_WILDCARD 2\n  struct stat stat_info;\n  struct base_path *current_path = NULL;\n  struct base_path_file *bpf;\n  struct resource *req;\n  struct resource *res;\n  char path_buffer[MAX_PATH];\n  char *translated_path;\n  size_t len;\n  boolean found;\n  boolean found_has_crc32;\n  uint32_t found_crc32;\n  size_t i;\n  int j;\n\n  struct resource **req_sorted;\n  size_t num_reqs;\n\n  int (*sort_fn)(const void *, const void *);\n  switch(sort_by)\n  {\n    default:\n    case SORT_BY_LOCATION:\n      sort_fn = req_sort_by_location_fn;\n      break;\n\n    case SORT_BY_FILENAME:\n      sort_fn = req_sort_by_filename_fn;\n      break;\n  }\n\n  if(!requirement_table)\n    return;\n\n  num_reqs = kh_size(requirement_table);\n  req_sorted = cmalloc(num_reqs * sizeof(struct resource *));\n\n  // Build a list of the requirements from the hash table and sort it.\n  // This is entirely for the purpose of having more useful output.\n  i = 0;\n  HASH_ITER(RESOURCE, requirement_table, req,\n  {\n    req_sorted[i++] = req;\n  });\n  qsort(req_sorted, num_reqs, sizeof(struct resource *), sort_fn);\n\n  // Now, actually process the requirements.\n  for(i = 0; i < num_reqs; i++)\n  {\n    req = req_sorted[i];\n    found = false;\n    found_crc32 = 0;\n    found_has_crc32 = false;\n\n    for(j = 0; j < path_list_size; j++)\n    {\n      current_path = path_list[j];\n      len = strlen(current_path->relative_path);\n\n      // The required resource's path must start with the relative path\n      if(strncasecmp(req->path, current_path->relative_path, len))\n        continue;\n\n      translated_path = req->path + len;\n\n      if(current_path->file_list_table)\n      {\n        if(req->is_wildcard)\n        {\n          HASH_ITER(BASE_PATH_FILE, current_path->file_list_table, bpf,\n          {\n            if(check_wildcard_path(bpf->file_path, translated_path))\n            {\n              bpf->used_wildcard = true;\n              found = FOUND_WILDCARD;\n\n              // If unused/wildcards are being displayed, every single thing\n              // this possibly matches needs to be detected. Otherwise, break\n              if(!display_unused && !display_wildcard)\n                break;\n            }\n          });\n\n          // See: note above.\n          if(found && !display_unused && !display_wildcard)\n            break;\n\n          continue;\n        }\n\n        // Normal resource: try to find the file in the base path's hash table\n        HASH_FIND(BASE_PATH_FILE, current_path->file_list_table,\n         translated_path, strlen(translated_path), bpf);\n\n        if(bpf)\n        {\n          bpf->used = true;\n          found = true;\n          found_crc32 = bpf->crc32;\n          found_has_crc32 = bpf->has_crc32;\n          break;\n        }\n      }\n\n      else\n      {\n        // Try to find an actual file\n        join_path(path_buffer, current_path->actual_path, translated_path);\n\n        if(!vstat(path_buffer, &stat_info))\n        {\n          found = true;\n          break;\n        }\n      }\n    }\n\n    if(found == FOUND_WILDCARD)\n    {\n      if(display_wildcard)\n        output(req->parent->file_name, req->board_num, req->robot_num,\n         req->line_num, req->x, req->y, req->path, pattern_append,\n         current_path->actual_path, 0, false);\n    }\n    else\n\n    if(found)\n    {\n      if(display_found)\n        output(req->parent->file_name, req->board_num, req->robot_num,\n         req->line_num, req->x, req->y, req->path, found_append,\n         current_path->actual_path, found_crc32, found_has_crc32);\n    }\n    else\n    {\n      // Try to find in the created resources table\n      HASH_FIND(RESOURCE, resource_table, req->path, req->path_len, res);\n\n      if(!res && resource_table)\n      {\n        // There might be wildcard created resources...\n        HASH_ITER(RESOURCE, resource_table, res,\n        {\n          if(check_wildcard_path(req->path, res->path))\n            break;\n          res = NULL;\n        });\n      }\n\n      if(res)\n      {\n        const char *append =\n         (res->is_wildcard ? maybe_created_append : created_append);\n\n        if(display_created)\n          output(req->parent->file_name, req->board_num, req->robot_num,\n           req->line_num, req->x, req->y, req->path, append, NULL, 0, false);\n      }\n      else\n      {\n        if(display_not_found)\n          output(req->parent->file_name, req->board_num, req->robot_num,\n           req->line_num, req->x, req->y, req->path, not_found_append, NULL, 0,\n           false);\n      }\n    }\n  }\n  free(req_sorted);\n\n  if(display_unused || display_wildcard)\n  {\n    // Now go through all of the base paths and print the files that aren't\n    // actually used by anything... yikes.\n    struct base_path_file **bpf_sorted;\n    size_t bpf_alloc = 0;\n\n    for(j = 0; j < path_list_size; j++)\n      if(path_list[j]->file_list_table)\n        bpf_alloc = MAX(bpf_alloc, kh_size(path_list[j]->file_list_table));\n\n    if(bpf_alloc)\n    {\n      bpf_sorted = cmalloc(bpf_alloc * sizeof(struct base_path_file *));\n\n      for(j = 0; j < path_list_size; j++)\n      {\n        current_path = path_list[j];\n\n        if(current_path->file_list_table)\n        {\n          size_t k;\n          i = 0;\n          HASH_ITER(BASE_PATH_FILE, current_path->file_list_table, bpf,\n          {\n            if(!bpf->used)\n              bpf_sorted[i++] = bpf;\n          });\n          qsort(bpf_sorted, i, sizeof(struct base_path_file *), bpf_sort_fn);\n\n          for(k = 0; k < i; k++)\n          {\n            const char *file_path;\n            size_t len;\n            bpf = bpf_sorted[k];\n            file_path = bpf->file_path;\n\n            // Don't print \"unused\" MZX/MZB files.\n            len = strlen(file_path);\n            if(len >= 4 &&\n             (!strcasecmp(file_path + len - 4, \".MZX\") ||\n              !strcasecmp(file_path + len - 4, \".MZB\")))\n              continue;\n\n            // No \"unused\" directories either.\n            if(len && file_path[len - 1] == '/')\n              continue;\n\n            // Want to display this to the user as being in its relative path\n            if(path_list[j]->relative_path[0])\n            {\n              join_path(path_buffer, path_list[j]->relative_path, file_path);\n              file_path = path_buffer;\n            }\n\n            if(display_unused && !bpf->used_wildcard)\n            {\n              output(\"\", DONT_PRINT, -1, -1, -1, -1, file_path, unused_append,\n               current_path->actual_path, bpf->crc32, bpf->has_crc32);\n            }\n            else\n\n            if(display_wildcard && bpf->used_wildcard)\n            {\n              output(\"\", DONT_PRINT, -1, -1, -1, -1, file_path, maybe_used_append,\n               current_path->actual_path, bpf->crc32, bpf->has_crc32);\n            }\n          }\n        }\n      }\n      free(bpf_sorted);\n    }\n  }\n\n  // Reset these for next time\n  started_table = 0;\n  parent_max_len = PARENT_DEFAULT_LEN;\n  resource_max_len = RESOURCE_DEFAULT_LEN;\n}\n\nstatic void clear_data(struct base_path **path_list,\n int path_list_size, struct base_file **file_list, int file_list_size)\n{\n  struct base_path *bp;\n  struct resource *res;\n  struct base_path_file *fp;\n  int i;\n\n  HASH_ITER(RESOURCE, requirement_table, res,\n  {\n    HASH_DELETE(RESOURCE, requirement_table, res);\n    free(res);\n  });\n  HASH_CLEAR(RESOURCE, requirement_table);\n\n  HASH_ITER(RESOURCE, resource_table, res,\n  {\n    HASH_DELETE(RESOURCE, resource_table, res);\n    free(res);\n  });\n  HASH_CLEAR(RESOURCE, resource_table);\n\n  for(i = 0; i < path_list_size; i++)\n  {\n    bp = path_list[i];\n\n    HASH_ITER(BASE_PATH_FILE, bp->file_list_table, fp,\n    {\n      HASH_DELETE(BASE_PATH_FILE, bp->file_list_table, fp);\n      free(fp);\n    });\n    HASH_CLEAR(BASE_PATH_FILE, bp->file_list_table);\n\n    if(bp->zp)\n      zip_close(bp->zp, NULL);\n\n    free(bp);\n  }\n  free(path_list);\n\n  for(i = 0; i < file_list_size; i++)\n    free(file_list[i]);\n\n  free(file_list);\n}\n\n\n/**********/\n/* Common */\n/**********/\n\n// From world.c\nstatic int _world_magic(const unsigned char magic_string[3])\n{\n  if(magic_string[0] == 'M')\n  {\n    if(magic_string[1] == 'Z')\n    {\n      switch(magic_string[2])\n      {\n        case 'X':\n          return V100;\n        case '2':\n          return V200;\n        case 'A':\n          return V251s1;\n      }\n    }\n    else\n    {\n      if((magic_string[1] > 1) && (magic_string[1] < 10))\n        return ((int)magic_string[1] << 8) + (int)magic_string[2];\n    }\n  }\n\n  return 0;\n}\n\n// From editor/board.c\nstatic int board_magic(const unsigned char magic_string[4])\n{\n  if(magic_string[0] == 0xFF)\n  {\n    if(magic_string[1] == 'M')\n    {\n      if(magic_string[2] == 'B' && magic_string[3] == '2')\n        return V251;\n\n      if((magic_string[2] > 1) && (magic_string[2] < 10))\n        return ((int)magic_string[2] << 8) + (int)magic_string[3];\n    }\n  }\n\n  return 0;\n}\n\nstatic enum status parse_sfx(char *sfx_buf, struct base_file *file,\n int board_num, int robot_num, int line_num)\n{\n  char *start, *end = sfx_buf - 1, str_buf_len;\n  enum status ret = SUCCESS;\n\n  str_buf_len = strlen(sfx_buf);\n\n  while(true)\n  {\n    // no starting & was found\n    start = strchr(end + 1, '&');\n    if(!start || start - sfx_buf + 1 > str_buf_len)\n      break;\n\n    // no ending & was found\n    end = strchr(start + 1, '&');\n    if(!end || end - sfx_buf + 1 > str_buf_len)\n      break;\n\n    // Wipe out the &s to get a token\n    *start = 0;\n    *end = 0;\n\n    trace(\"  SFX: %s\\n\", start + 1);\n    add_requirement_sfx(start + 1, file, board_num, robot_num, line_num);\n  }\n\n  return ret;\n}\n\n#define match(s, reqv) ((world_version >= reqv) && \\\n (fn_len == (ssize_t)sizeof(s)-1) && (!strcasecmp(function_counter, s)))\n\n#define match_partial(s, reqv) ((world_version >= reqv) && \\\n (fn_len >= (ssize_t)sizeof(s)-1) && (!strncasecmp(function_counter, s, sizeof(s)-1)))\n\n#define TERMINATE(s,slen) \\\n do{ if(slen && s[slen - 1] == '\\0') slen--; else s[slen]='\\0'; }while(0)\n\n/* Get an int16 param (1.x and 2.x). */\nstatic int param_int(struct memfile *mf, struct base_file *file)\n{\n  if(file->file_version >= V200)\n  {\n    size_t len = mfgetc(mf);\n    if(len != 0)\n    {\n      mfseek(mf, len, SEEK_CUR);\n      return INT_MIN;\n    }\n  }\n  return mfgetw(mf);\n}\n\n/* Get a null-terminated string param (1.x and 2.x). */\nstatic ssize_t param_string(char *dest, size_t dest_len,\n struct memfile *mf, struct base_file *file)\n{\n  size_t len;\n\n  if(file->file_version >= V200)\n  {\n    len = mfgetc(mf);\n    if(len == 0)\n    {\n      // Skip int.\n      mfgetw(mf);\n      dest[0] = '\\0';\n      return 0;\n    }\n\n    if(!mfread(dest, len, 1, mf))\n      return -1;\n\n    // Don't trust the program's null termination.\n    TERMINATE(dest, len);\n  }\n  else\n  {\n    // Null-terminated string parameter with no prefixed length.\n    unsigned char *pos;\n\n    for(pos = mf->current; pos < mf->end && *pos; pos++);\n    len = pos - mf->current;\n\n    if(pos >= mf->end || len + 1 > dest_len)\n      return -1;\n\n    if(!mfread(dest, len + 1, 1, mf))\n      return -1;\n\n    dest[len] = '\\0';\n  }\n  return len;\n}\n\n/* Skip a param (2.x only). */\nstatic void param_skip(struct memfile *mf, struct base_file *file)\n{\n  int len = mfgetc(mf);\n  mfseek(mf, len ? len : 2, SEEK_CUR);\n}\n\nstatic enum status parse_legacy_bytecode(struct memfile *mf,\n unsigned int program_size, struct base_file *file, int board_num, int robot_num)\n{\n  int world_version = file->world_version;\n  enum status ret = SUCCESS;\n  int command_length;\n  int command;\n  int line_num = 0;\n\n  char function_counter[256];\n  ssize_t fn_len;\n\n  char src[256];\n  ssize_t src_len;\n\n  // skip 0xff marker\n  if(mfgetc(mf) != 0xff)\n  {\n    warnhere(\"Invalid program start byte\\n\");\n    return CORRUPT_WORLD;\n  }\n\n  while(1)\n  {\n    if(mftell(mf) >= (long int)program_size)\n    {\n      warnhere(\"Program exceeded expected length\\n\");\n      return CORRUPT_WORLD;\n    }\n\n    command_length = mfgetc(mf);\n    if(command_length == 0)\n      break;\n\n    command = mfgetc(mf);\n    line_num++;\n\n    // These constants may eventually change in debytecode versions.\n    // Since this is for legacy bytecode, leave the fixed numbers.\n    switch(command)\n    {\n      case 10: // ROBOTIC_CMD_SET\n      {\n        // File-based SET commands were introduced starting in MegaZeux 2.6.\n        if(file->world_version < V260)\n        {\n          mfseek(mf, command_length - 1, SEEK_CUR);\n          break;\n        }\n\n        // Destination\n        src_len = param_string(src, sizeof(src), mf, file);\n        if(src_len < 0)\n        {\n          warnhere(\"Truncated command\\n\");\n          return CORRUPT_WORLD;\n        }\n\n        // Parameter\n        fn_len = param_string(function_counter, sizeof(function_counter), mf, file);\n        if(fn_len < 0)\n        {\n          warnhere(\"Truncated command\\n\");\n          return CORRUPT_WORLD;\n        }\n\n        // Parameter is integer or empty string?\n        if(fn_len == 0)\n          break;\n\n        if( match(\"FREAD_OPEN\", V260)\n         || match(\"SMZX_PALETTE\", V269)\n         || match(\"SMZX_INDICES\", V291)\n         || match(\"LOAD_COUNTERS\", V290)\n         || match(\"LOAD_GAME\", V268)\n         || match_partial(\"LOAD_BC\", V270)\n         || match_partial(\"LOAD_ROBOT\", V270))\n        {\n          trace(\"  SET: %s (%s)\\n\", src, function_counter);\n          add_requirement_robot(src, file, board_num, robot_num, line_num);\n          break;\n        }\n\n        if( match(\"FWRITE_OPEN\", V260)\n         || match(\"FWRITE_APPEND\", V260)\n         || match(\"FWRITE_MODIFY\", V260)\n         || match(\"SAVE_COUNTERS\", V290)\n         || match(\"SAVE_GAME\", V268)\n         || match_partial(\"SAVE_BC\", V270)\n         || match_partial(\"SAVE_ROBOT\", V270))\n        {\n          trace(\"  SET: %s (%s)\\n\", src, function_counter);\n          add_resource(src, file);\n          break;\n        }\n\n        if( match(\"SAVE_WORLD\", V269c) )\n        {\n          // Maximum version\n          if(world_version < V290)\n          {\n            trace(\"  SET: %s (%s)\\n\", src, function_counter);\n            add_resource(src, file);\n          }\n          break;\n        }\n\n        break;\n      }\n\n      case 38: // ROBOTIC_CMD_MOD\n      {\n        // Filename\n        src_len = param_string(src, sizeof(src), mf, file);\n        if(src_len < 0)\n        {\n          warnhere(\"Truncated command\\n\");\n          return CORRUPT_WORLD;\n        }\n\n        // ignore MOD \"\", MOD \"*\"\n        if(!src_len || !strcmp(src, \"*\"))\n          break;\n\n        // Clip filename.mod*\n        if(src[src_len - 1] == '*')\n          src[src_len - 1] = 0;\n\n        trace(\"  MOD: %s\\n\", src);\n        add_requirement_robot(src, file, board_num, robot_num, line_num);\n        break;\n      }\n\n      case 39: // ROBOTIC_CMD_SAM\n      {\n        // Period\n        param_int(mf, file);\n\n        // Filename\n        src_len = param_string(src, sizeof(src), mf, file);\n        if(src_len < 0)\n        {\n          warnhere(\"Truncated command\\n\");\n          return CORRUPT_WORLD;\n        }\n\n        trace(\"  SAM: %s\\n\", src);\n        add_requirement_robot(src, file, board_num, robot_num, line_num);\n        break;\n      }\n\n      case 43: // ROBOTIC_CMD_PLAY\n      case 45: // ROBOITC_CMD_WAIT_THEN_PLAY\n      case 49: // ROBOTIC_CMD_PLAY_IF_SILENT\n      {\n        // Play string\n        src_len = param_string(src, sizeof(src), mf, file);\n        if(src_len < 0)\n        {\n          warnhere(\"Truncated command\\n\");\n          return CORRUPT_WORLD;\n        }\n\n        ret = parse_sfx(src, file, board_num, robot_num, line_num);\n        if(ret != SUCCESS)\n          return ret;\n        break;\n      }\n\n      case 79: // ROBOTIC_CMD_PUT_XY\n      {\n        int id;\n        // MZMs were introduced in MegaZeux 2.68.\n        if(file->world_version < V268)\n        {\n          mfseek(mf, command_length - 1, SEEK_CUR);\n          break;\n        }\n\n        // Filename... maybe.\n        src_len = param_string(src, sizeof(src), mf, file);\n        if(src_len != 0)\n        {\n          if(src_len < 0)\n          {\n            warnhere(\"Truncated command\\n\");\n            return CORRUPT_WORLD;\n          }\n\n          // Thing\n          id = param_int(mf, file);\n          if(id < 0)\n          {\n            warnhere(\"Invalid parameter\\n\");\n            return CORRUPT_WORLD;\n          }\n\n          if(id == IMAGE_FILE && src[0] == '@')\n          {\n            trace(\"  PUT @file: %s\\n\", src + 1);\n            add_requirement_robot(src + 1, file, board_num, robot_num, line_num);\n          }\n        }\n        else\n          param_skip(mf, file); // thing\n\n        param_skip(mf, file); // param\n        param_skip(mf, file); // x\n        param_skip(mf, file); // y\n        break;\n      }\n\n      case 200: // ROBOTIC_CMD_MOD_FADE_IN\n      {\n        // Filename\n        src_len = param_string(src, sizeof(src), mf, file);\n        if(src_len < 0)\n        {\n          warnhere(\"Truncated command\\n\");\n          return CORRUPT_WORLD;\n        }\n\n        // ignore MOD \"\", MOD \"*\"\n        if(!src_len || !strcmp(src, \"*\"))\n          break;\n\n        // Clip filename.mod*\n        if(src[src_len - 1] == '*')\n          src[src_len - 1] = 0;\n\n        trace(\"  MOD FADE IN: %s\\n\", src);\n        add_requirement_robot(src, file, board_num, robot_num, line_num);\n        break;\n      }\n\n      case 201: // ROBOTIC_CMD_COPY_BLOCK\n      {\n        // MZMs were introduced in MegaZeux 2.68.\n        if(file->world_version < V268)\n        {\n          mfseek(mf, command_length - 1, SEEK_CUR);\n          break;\n        }\n\n        param_skip(mf, file); // x1\n        param_skip(mf, file); // y1\n        param_skip(mf, file); // w\n        param_skip(mf, file); // h\n\n        // x2 or @filename\n        src_len = param_string(src, sizeof(src), mf, file);\n        if(src_len != 0)\n        {\n          if(src_len < 0)\n          {\n            warnhere(\"Truncated command\\n\");\n            return CORRUPT_WORLD;\n          }\n\n          if(src[0] == '@')\n          {\n            trace(\"  COPY BLOCK @file: %s\\n\", src + 1);\n            add_resource(src + 1, file);\n          }\n        }\n\n        param_skip(mf, file); // y2\n        break;\n      }\n\n      case 216: // ROBOTIC_CMD_LOAD_CHAR_SET\n      {\n        const char *rest = src;\n\n        // Filename\n        src_len = param_string(src, sizeof(src), mf, file);\n        if(src_len < 0)\n        {\n          warnhere(\"Truncated command\\n\");\n          return CORRUPT_WORLD;\n        }\n\n        if(src[0] == '+')\n        {\n          if(src[1] == '&')\n          {\n            rest = skip_interpolation(src + 1);\n          }\n          else\n\n          if(src[1] == '(')\n          {\n            rest = skip_expression(src + 1);\n          }\n          else\n          {\n            char tempc = src[3];\n            src[3] = 0;\n            strtol(src + 1, (char **)(&rest), 16);\n            src[3] = tempc;\n          }\n        }\n        else\n\n        if(src[0] == '@')\n        {\n          char tempc;\n          int maxlen;\n\n          if(src[1] == '&')\n          {\n            rest = skip_interpolation(src + 1);\n          }\n          else\n\n          if(src[1] == '(')\n          {\n            rest = skip_expression(src + 1);\n          }\n\n          else\n          {\n            if(world_version < V290)\n              maxlen = 3;\n            else\n              maxlen = 4;\n\n            tempc = src[maxlen+1];\n            src[maxlen+1] = 0;\n            strtol(src + 1, (char **)(&rest), 10);\n            src[maxlen+1] = tempc;\n          }\n          if(!rest)\n            rest = src;\n        }\n\n        trace(\"  LOAD CHAR SET: %s\\n\", rest);\n        add_requirement_robot(rest, file, board_num, robot_num, line_num);\n        break;\n      }\n\n      case 222: // ROBOTIC_CMD_LOAD_PALETTE\n      {\n        // Filename\n        src_len = param_string(src, sizeof(src), mf, file);\n        if(src_len < 0)\n        {\n          warnhere(\"Truncated command\\n\");\n          return CORRUPT_WORLD;\n        }\n\n        trace(\"  LOAD PALETTE: %s\\n\", src);\n        add_requirement_robot(src, file, board_num, robot_num, line_num);\n        break;\n      }\n\n      case 226: // ROBOTIC_CMD_SWAP_WORLD\n      {\n        // Filename\n        src_len = param_string(src, sizeof(src), mf, file);\n        if(src_len < 0)\n        {\n          warnhere(\"Truncated command\\n\");\n          return CORRUPT_WORLD;\n        }\n\n        trace(\"  SWAP WORLD: %s\\n\", src);\n        add_requirement_robot(src, file, board_num, robot_num, line_num);\n        break;\n      }\n\n      default:\n      {\n        if(mfseek(mf, command_length - 1, SEEK_CUR) != 0)\n        {\n          warnhere(\"Truncated command\\n\");\n          return CORRUPT_WORLD;\n        }\n        break;\n      }\n    }\n\n    if(ret != SUCCESS)\n      return ret;\n\n    if(mfgetc(mf) != command_length)\n    {\n      warnhere(\"Command start length != end length (%d)\\n\", command);\n      return CORRUPT_WORLD;\n    }\n  }\n\n  return ret;\n}\n\n\n/*****************/\n/* Legacy Worlds */\n/*****************/\n\n#define MAX_PASSWORD_LENGTH 15\n\n// From legacy_world.c\nstatic uint8_t get_pw_xor_code(char *password, int password_length, int pro_method)\n{\n  static const char magic_code[16] =\n   \"\\xE6\\x52\\xEB\\xF2\\x6D\\x4D\\x4A\\xB7\\x87\\xB2\\x92\\x88\\xDE\\x91\\x24\";\n\n  int work = 85; // Start with 85... (01010101)\n  size_t i;\n\n  // First, normalize password...\n  for(i = 0; i < MAX_PASSWORD_LENGTH; i++)\n  {\n    password[i] ^= magic_code[i];\n    password[i] -= 0x12 + pro_method;\n    password[i] ^= 0x8D;\n  }\n  // 1.x passwords store a length instead of a terminator.\n  password[password_length] = '\\0';\n\n  // Clear pw after first null\n  for(i = strlen(password); i < MAX_PASSWORD_LENGTH; i++)\n  {\n    password[i] = 0;\n  }\n\n  for(i = 0; i < MAX_PASSWORD_LENGTH; i++)\n  {\n    //For each byte, roll once to the left and xor in pw byte if it\n    //is an odd character, or add in pw byte if it is an even character.\n    work <<= 1;\n    if(work > 255)\n      work ^= 257; // Wraparound from roll\n\n    if(i & 1)\n    {\n      work += (signed char)password[i]; // Add (even byte)\n      if(work > 255)\n        work ^= 257; // Wraparound from add\n    }\n    else\n    {\n      work ^= (signed char)password[i]; // XOR (odd byte);\n    }\n  }\n  // To factor in protection method, add it in and roll one last time\n  work += pro_method;\n  if(work > 255)\n    work ^= 257;\n\n  work <<= 1;\n  if(work > 255)\n    work ^= 257;\n\n  // Can't be 0-\n  if(work == 0)\n    work = 86; // (01010110)\n  // Done!\n  return work;\n}\n\n#if ARCHITECTURE_BITS == 64\n#define ALIGN_TYPE uint64_t\n#define ALIGN_XOR(x) ((x) | (x << 8) | (x << 16) | (x << 24) | \\\n (x << 32) | (x << 40) | (x << 48) | (x << 56))\n#else\n#define ALIGN_TYPE uint32_t\n#define ALIGN_XOR(x) ((x) | (x << 8) | (x << 16) | (x << 24))\n#endif\n\nstatic void _decrypt_legacy_world(struct memfile *mf, struct base_file *file)\n{\n  ALIGN_TYPE xor = get_pw_xor_code(file->password, file->password_length, file->protection_method);\n  ALIGN_TYPE xor_w = ALIGN_XOR(xor);\n  unsigned char *pos = mf->current;\n\n  if(!display_first_only)\n  {\n    fprintf(stderr, \"password: %s\\n\", file->password);\n    fprintf(stderr, \"--------  %.*s\\n\", (int)strlen(file->password), \"----------------\");\n    fflush(stderr);\n  }\n\n  /* 1.x doesn't actually encrypt anything... */\n  if(file->file_version < V200)\n    return;\n\n  if(!display_first_only)\n  {\n    fprintf(stderr, \"xor: %u\\n\", (unsigned int)xor);\n    fflush(stderr);\n  }\n\n  while(pos < mf->end && ((size_t)pos) % sizeof(ALIGN_TYPE))\n    *(pos++) ^= xor;\n\n  while(mf->end - pos >= (ptrdiff_t)sizeof(ALIGN_TYPE))\n  {\n    *((ALIGN_TYPE *)pos) ^= xor_w;\n    pos += sizeof(ALIGN_TYPE);\n  }\n\n  while(pos < mf->end)\n    *(pos++) ^= xor;\n}\n\nstatic enum status parse_legacy_robot_100(struct memfile *mf,\n struct base_file *file, int board_num, int robot_num)\n{\n  uint32_t program_size;\n  struct memfile prog;\n  enum status ret = SUCCESS;\n\n  // program_length (4),\n  program_size = mfgetd(mf);\n\n  // program_location (4), robot_name (15), robot char (1),\n  // cur_prog_line (4), pos_within_line (1), robot_cycle (1), cycle_count (1),\n  // bullet_type (1), is_locked (1), can_lavawalk (1), walk_dir (1),\n  // last_touch_dir (1), last_shot_dir (1)\n  if(mfseek(mf, 33, SEEK_CUR) != 0)\n    return FSEEK_FAILED;\n\n  if(program_size > 2)\n  {\n    mfopen(mf->current, program_size, &prog);\n    ret = parse_legacy_bytecode(&prog, program_size, file, board_num, robot_num);\n  }\n\n  mfseek(mf, program_size, SEEK_CUR);\n  return ret;\n}\n\nstatic enum status parse_legacy_robot(struct memfile *mf,\n struct base_file *file, int board_num, int robot_num)\n{\n  unsigned int program_size;\n  struct memfile prog;\n\n  enum status ret = SUCCESS;\n  boolean used = false;\n\n  if(file->file_version < V200)\n    return parse_legacy_robot_100(mf, file, board_num, robot_num);\n\n  // program_length (2),\n  program_size = mfgetw(mf);\n\n  // program_location (2), robot_name (15), robot_char (1),\n  // cur_prog_line (2), pos_within_line (1), robot_cycle (1), cycle_count (1),\n  // bullet_type (1), is_locked (1), can_lavawalk (1), walk_dir (1),\n  // last_touch_dir (1), last_shot_dir (1), xpos (2), ypos (2),\n  // status (1), legacy local1 (2),\n  if(mfseek(mf, 41 - 5, SEEK_CUR) != 0)\n    return FSEEK_FAILED;\n\n  // used (1),\n  used = !!mfgetc(mf);\n\n  // legacy loop_count (2),\n  if(mfseek(mf, 2, SEEK_CUR) != 0)\n    return FSEEK_FAILED;\n\n  // VER1TO2 worlds (e.g. Forest, Catacombs) sometimes have invalid programs\n  // of size 2. It's safe to just ignore anything of size 2 or less.\n  if(program_size > 2)\n  {\n    mfopen(mf->current, program_size, &prog);\n\n    ret = parse_legacy_bytecode(&prog, program_size, file, board_num, robot_num);\n\n    // Ignore errors on unused robots since these don't really matter.\n    // This includes the global robots in Slave Pit and Wes.\n    if(ret && !used)\n    {\n      warnhere(\"Unused robot with corruption detected (this is safe to ignore).\\n\");\n      ret = SUCCESS;\n    }\n  }\n\n  mfseek(mf, program_size, SEEK_CUR);\n  return ret;\n}\n\n/* Version 1.x RLE. */\nstatic boolean skip_rle(struct memfile *mf)\n{\n  uint16_t w;\n  uint16_t h;\n  size_t size;\n  size_t pos = 0;\n\n  // There may be a terminating 0-length run prefixed(?!).\n  if(mf->current + 1 < mf->end)\n  {\n    uint8_t count = mfgetc(mf);\n    if(count == 0)\n      mf->current++;\n    else\n      mf->current--;\n  }\n  if(mf->current + 1 >= mf->end)\n    return false;\n\n  w = mfgetc(mf);\n  h = mfgetc(mf);\n  size = w * h;\n  if(size < 1)\n    return false;\n\n  while(pos < size && mf->current + 1 < mf->end)\n  {\n    uint8_t count = mfgetc(mf);\n    mf->current++;\n    pos += count;\n  }\n  return mf->current < mf->end;\n}\n\nstatic boolean load_rle(char **dest, uint16_t *width, uint16_t *height,\n struct memfile *mf)\n{\n  uint16_t w;\n  uint16_t h;\n  size_t size;\n  size_t pos = 0;\n  char *plane;\n\n  // There may be a terminating 0-length run prefixed(?!).\n  if(mf->current + 1 < mf->end)\n  {\n    uint8_t count = mfgetc(mf);\n    if(count == 0)\n      mf->current++;\n    else\n      mf->current--;\n  }\n  if(mf->current + 1 >= mf->end)\n    return false;\n\n  w = mfgetc(mf);\n  h = mfgetc(mf);\n  size = w * h;\n  if(size < 1)\n    return false;\n\n  plane = cmalloc(size);\n\n  while(pos < size && mf->current + 1 < mf->end)\n  {\n    uint8_t count = mfgetc(mf);\n    uint8_t byte = mfgetc(mf);\n\n    if(pos + count > size)\n    {\n      free(plane);\n      return false;\n    }\n\n    memset(plane + pos, byte, count);\n    pos += count;\n  }\n\n  if(mf->current < mf->end)\n  {\n    *dest = plane;\n    *width = w;\n    *height = h;\n    return true;\n  }\n\n  free(plane);\n  return false;\n}\n\n/* Version 2.x RLE. */\nstatic boolean skip_rle2(struct memfile *mf)\n{\n  uint16_t w = mfgetw(mf);\n  uint16_t h = mfgetw(mf);\n  int pos = 0;\n\n  /* RLE \"decoder\"; just to skip stuff */\n  while(pos < w * h && mf->current < mf->end)\n  {\n    unsigned char c = (unsigned char)mfgetc(mf);\n\n    if(!(c & 0x80))\n      pos++;\n    else\n    {\n      c &= ~0x80;\n      pos += c;\n      mfgetc(mf);\n    }\n  }\n  return (mf->current < mf->end);\n}\n\nstatic boolean load_rle2(char **dest, uint16_t *width, uint16_t *height,\n struct memfile *mf)\n{\n  uint16_t w = mfgetw(mf);\n  uint16_t h = mfgetw(mf);\n  size_t size = w * h;\n  size_t runsize;\n  size_t i;\n  char *plane;\n\n  if(size > MAX_BOARD_SIZE)\n    return false;\n\n  plane = cmalloc(size);\n\n  for(i = 0; i < size && mf->current < mf->end; i++)\n  {\n    unsigned char c = (unsigned char)mfgetc(mf);\n    if(!(c & 0x80))\n    {\n      // Regular character\n      plane[i] = (char)c;\n    }\n    else\n    {\n      // A run\n      runsize = (c & 0x7F);\n      if((i + runsize) > size)\n      {\n        free(plane);\n        return false;\n      }\n\n      memset(plane + i, mfgetc(mf), runsize);\n      i += (runsize - 1);\n    }\n  }\n\n  if(mf->current < mf->end)\n  {\n    *dest = plane;\n    *width = w;\n    *height = h;\n    return true;\n  }\n\n  free(plane);\n  return false;\n}\n\nstatic enum status parse_legacy_board(struct memfile *mf,\n struct base_file *file, int board_num)\n{\n  int i, num_robots, skip_bytes;\n  unsigned short board_mod_len;\n  enum status ret = SUCCESS;\n  // NOTE: the higher of the two possible MAX_PATH values that might have been\n  // used to store this.\n  char board_mod[512];\n  char *level_id = NULL;\n  char *level_param = NULL;\n  uint16_t width;\n  uint16_t height;\n  boolean rle_success;\n\n  if(file->file_version >= V200)\n  {\n    // Skip legacy board_mode, which was used to control maximum board dimensions.\n    mfgetc(mf);\n\n    // whether the overlay is enabled or not\n    if(mfgetc(mf))\n    {\n      // not enabled, so rewind this last read\n      if(mfseek(mf, -1, SEEK_CUR) != 0)\n        return FSEEK_FAILED;\n    }\n    else\n    {\n      // junk overlay_mode\n      mfgetc(mf);\n\n      // Skip overlay char and color RLE blocks.\n      if(!skip_rle2(mf) || !skip_rle2(mf))\n      {\n        warnhere(\"Failed to unpack overlay RLE\\n\");\n        return CORRUPT_WORLD;\n      }\n    }\n\n    // NOTE: Robot xpos and ypos variables were always set to 0 in DOS-era worlds\n    // and can't be trusted. Instead, the level_id/level_param arrays need to be\n    // unpacked and scanned.\n\n    // level_id, level_color, level_param,\n    // level_under_id, level_under_color, level_under_param\n    rle_success =\n      load_rle2(&level_id, &width, &height, mf) &&\n      skip_rle2(mf) &&\n      load_rle2(&level_param, &width, &height, mf) &&\n      skip_rle2(mf) &&\n      skip_rle2(mf) &&\n      skip_rle2(mf);\n  }\n  else\n  {\n    // Version 1.x worlds start immediately with the RLE,\n    // i.e. no board_mode or overlay support.\n    rle_success =\n      load_rle(&level_id, &width, &height, mf) &&\n      skip_rle(mf) &&\n      load_rle(&level_param, &width, &height, mf) &&\n      skip_rle(mf) &&\n      skip_rle(mf) &&\n      skip_rle(mf);\n  }\n\n  if(!rle_success)\n  {\n    warnhere(\"Failed to unpack board RLE\\n\");\n    free(level_id);\n    free(level_param);\n    return CORRUPT_WORLD;\n  }\n  trace(\"  width  %d\\n\", width);\n  trace(\"  height %d\\n\", height);\n\n  if(level_id && level_param)\n  {\n    for(i = 0; i < (int)(width * height); i++)\n    {\n      if(is_robot((enum thing)level_id[i]))\n      {\n        unsigned char param = (unsigned char)level_param[i];\n        robot_xpos[param] = (i % width);\n        robot_ypos[param] = (i / width);\n      }\n    }\n  }\n  free(level_id);\n  free(level_param);\n\n  // get length of board MOD string\n  if(file->world_version < V283)\n    board_mod_len = 13;\n  else\n    board_mod_len = mfgetw(mf);\n\n  // In practice, the board mod could never be longer than this.\n  // If it is, something's wrong with the world file (e.g. 2.83 beta worlds\n  // have the 2.83 magic but expect a length of 12)\n  if(board_mod_len >= sizeof(board_mod))\n  {\n    warnhere(\"Board mod length invalid (%d)\\n\", board_mod_len);\n    return CORRUPT_WORLD;\n  }\n\n  // grab board's default MOD\n  if(mfread(board_mod, 1, board_mod_len, mf) != board_mod_len)\n  {\n    warnhere(\"Failed to read board mod (truncated)\\n\");\n    return CORRUPT_WORLD;\n  }\n\n  board_mod[board_mod_len] = '\\0';\n\n  // check the board MOD exists\n  if(strlen(board_mod) > 0 && strcmp(board_mod, \"*\"))\n  {\n    trace(\"  BOARD MOD: %s\\n\", board_mod);\n    add_requirement_board(board_mod, file, board_num, IS_BOARD_MOD);\n  }\n\n  if(file->file_version < V200)\n  {\n    skip_bytes = 208;\n  }\n  else\n\n  if(file->file_version < V283)\n  {\n    skip_bytes = 207;\n  }\n  else\n    skip_bytes = 25;\n\n  // skip to the robot count\n  if(mfseek(mf, skip_bytes, SEEK_CUR) != 0)\n  {\n    warnhere(\"Failed to seek to start of robots\\n\");\n    return CORRUPT_WORLD;\n  }\n\n  // walk the robot list, scan the robotic\n  num_robots = mfgetc(mf);\n  trace(\"  robots %d\\n\", num_robots);\n  for(i = 0; i < num_robots; i++)\n  {\n    ret = parse_legacy_robot(mf, file, board_num, i + 1);\n    if(ret != SUCCESS)\n    {\n      warnhere(\"Error processing robot %d\\n\", i + 1);\n      break;\n    }\n  }\n\n  return ret;\n}\n\nstruct legacy_board {\n  int offset;\n  int size;\n};\n\nstatic enum status parse_legacy_world(struct memfile *mf, struct base_file *file)\n{\n  struct legacy_board board_list[MAX_BOARDS];\n  int global_robot_offset;\n  int num_boards;\n  int i;\n\n  enum status ret = SUCCESS;\n\n  if(file->file_version >= V200)\n  {\n    // Jump to the global robot offset\n    if(mfseek(mf, LEGACY_WORLD_GLOBAL_OFFSET_OFFSET, SEEK_SET) != 0)\n    {\n      warnhere(\"couldn't seek to global robot position (truncated)\\n\");\n      return CORRUPT_WORLD;\n    }\n    // Protected worlds: everything is offset 15 bytes.\n    if(file->protection_method)\n      mfseek(mf, MAX_PASSWORD_LENGTH, SEEK_CUR);\n\n    // Absolute offset (in bytes) of global robot\n    global_robot_offset = mfgetd(mf);\n  }\n  else\n  {\n    // Jump to the board count (1.x doesn't have a global robot).\n    if(mfseek(mf, LEGACY_WORLD_BOARD_COUNT_100, SEEK_SET) != 0)\n    {\n      warnhere(\"couldn't seek to board count position (truncated)\\n\");\n      return CORRUPT_WORLD;\n    }\n    // Protected worlds: everything is offset 16 bytes.\n    if(file->protection_method)\n      mfseek(mf, MAX_PASSWORD_LENGTH + 1, SEEK_CUR);\n\n    global_robot_offset = 0;\n  }\n\n  // num_boards doubles as the check for custom sfx, if zero.\n  num_boards = mfgetc(mf);\n\n  // If the world has custom sfx, check them for resources.\n  // 1.x worlds don't have custom sfx and are limited to 127 boards.\n  if(num_boards == 0 && file->file_version >= V200)\n  {\n    unsigned short sfx_len_total = mfgetw(mf);\n    char sfx_buf[LEGACY_SFX_SIZE + 1];\n    int sfx_len;\n\n    trace(\"SFX table (input length: %d)\\n\", sfx_len_total);\n\n    for(i = 0; i < NUM_BUILTIN_SFX; i++)\n    {\n      sfx_len = mfgetc(mf);\n      if(sfx_len > LEGACY_SFX_SIZE)\n      {\n        warnhere(\"invalid SFX length of %d\\n\", sfx_len);\n        return CORRUPT_WORLD;\n      }\n\n      if(sfx_len > 0)\n      {\n        if(mfread(sfx_buf, 1, sfx_len, mf) != (size_t)sfx_len)\n        {\n          warnhere(\"couldn't read SFX %d (truncated)\\n\", i);\n          return CORRUPT_WORLD;\n        }\n        sfx_buf[sfx_len] = 0;\n\n        ret = parse_sfx(sfx_buf, file, IS_SFX, i, -1);\n        if(ret != SUCCESS)\n        {\n          warnhere(\"error parsing SFX %d\\n\", i);\n          return ret;\n        }\n      }\n\n      // 1 for length byte + sfx string\n      sfx_len_total -= 1 + sfx_len;\n    }\n\n    // better check we moved by whole amount\n    if(sfx_len_total != 0)\n    {\n      warnhere(\"Failed sfx total check: remaining is %d\\n\", sfx_len_total);\n      return CORRUPT_WORLD;\n    }\n\n    // read the \"real\" num_boards\n    num_boards = mfgetc(mf);\n  }\n\n  if(num_boards == 0)\n    warnhere(\"File contains 0 boards\\n\");\n  if(file->file_version < V200 && num_boards > 127)\n    warnhere(\"Version 1.x file contains more than 127 boards\\n\");\n\n  // skip board names; we simply don't care\n  if(mfseek(mf, num_boards * BOARD_NAME_SIZE, SEEK_CUR) != 0)\n  {\n    warnhere(\"Failed to skip board names (truncated)\\n\");\n    return CORRUPT_WORLD;\n  }\n\n  // grab the board sizes/offsets\n  for(i = 0; i < num_boards; i++)\n  {\n    board_list[i].size = mfgetd(mf);\n    board_list[i].offset = mfgetd(mf);\n  }\n\n  // walk all boards\n  for(i = 0; i < num_boards; i++)\n  {\n    struct legacy_board *board = &board_list[i];\n\n    trace(\"Board %d\\n\", i);\n\n    // don't care about deleted boards\n    if(board->size == 0)\n      continue;\n\n    trace(\"  size   %d\\n\", board->size);\n    trace(\"  offset %d\\n\", board->offset);\n\n    // seek to board offset within world\n    if(mfseek(mf, board->offset, SEEK_SET) != 0)\n    {\n      warnhere(\"Failed to seek to position of board %d\\n\", i);\n      continue;\n    }\n\n    // parse this board atomically\n    ret = parse_legacy_board(mf, file, i);\n    if(ret != SUCCESS)\n    {\n      warnhere(\"Error processing board %d\\n\", i);\n      continue;\n    }\n  }\n\n  if(global_robot_offset)\n  {\n    trace(\"Global robot\\n\");\n\n    // Do the global robot too..\n    if(mfseek(mf, global_robot_offset, SEEK_SET) != 0)\n    {\n      warnhere(\"Failed to seek to global robot position\\n\");\n      return CORRUPT_WORLD;\n    }\n\n    ret = parse_legacy_robot(mf, file, NO_BOARD, GLOBAL_ROBOT);\n    if(ret != SUCCESS)\n      warnhere(\"Failed processing global robot\\n\");\n  }\n  else\n    trace(\"No global robot\\n\");\n\n  return ret;\n}\n\n\n/*****************/\n/* Modern Worlds */\n/*****************/\n\nstatic enum status parse_robot_info(struct memfile *mf, struct base_file *file,\n int board_num, int robot_num)\n{\n  struct memfile prop;\n  int ident;\n  int len;\n\n  while(next_prop(&prop, &ident, &len, mf))\n  {\n    switch(ident)\n    {\n      // These vars are more reliable in ZIP worlds than they were prior,\n      // and can be trusted instead of scanning the board data.\n      case RPROP_XPOS:\n        robot_xpos[(unsigned char)robot_num] = load_prop_int_s(&prop, -1, 32767);\n        break;\n\n      case RPROP_YPOS:\n        robot_ypos[(unsigned char)robot_num] = load_prop_int_s(&prop, -1, 32767);\n        break;\n\n      case RPROP_PROGRAM_BYTECODE:\n        return parse_legacy_bytecode(&prop, (unsigned int)len, file,\n         board_num, robot_num);\n    }\n  }\n\n  return SUCCESS;\n}\n\nstatic enum status parse_board_info(struct memfile *mf, struct base_file *file,\n int board_num)\n{\n  struct memfile prop;\n  int ident;\n  int len;\n\n  char buffer[MAX_PATH+1];\n\n  while(next_prop(&prop, &ident, &len, mf))\n  {\n    switch(ident)\n    {\n      case BPROP_MOD_PLAYING:\n      {\n        // No * for the mod\n        if(len && *(prop.start) != '*')\n        {\n          mfread(buffer, len, 1, &prop);\n          buffer[len] = 0;\n\n          trace(\"BOARD MOD: %s\\n\", buffer);\n          add_requirement_board(buffer, file, board_num, IS_BOARD_MOD);\n        }\n        break;\n      }\n      case BPROP_CHARSET_PATH:\n      case BPROP_PALETTE_PATH:\n      {\n        if(len)\n        {\n          mfread(buffer, len, 1, &prop);\n          buffer[len] = 0;\n\n          trace(\"BOARD CHR/PAL: %s\\n\", buffer);\n          add_requirement_board(buffer, file, board_num,\n           (ident == BPROP_CHARSET_PATH) ? IS_BOARD_CHARSET : IS_BOARD_PALETTE);\n        }\n        break;\n      }\n    }\n  }\n\n  return SUCCESS;\n}\n\nstatic enum status parse_sfx_file(struct memfile *mf, struct base_file *file)\n{\n  char sfx_buf[MAX_SFX_SIZE];\n  struct memfile prop;\n  int ident;\n  int len;\n  int i;\n\n  enum status ret = SUCCESS;\n\n  if(mfread(sfx_buf, 8, 1, mf) && !memcmp(sfx_buf, \"MZFX\\x1a\", 6))\n  {\n    trace(\"SFX properties found\\n\");\n\n    i = 0;\n    while(next_prop(&prop, &ident, &len, mf))\n    {\n      switch(ident)\n      {\n        case SFXPROP_SET_ID:\n          i = load_prop_int(&prop);\n          break;\n\n        case SFXPROP_STRING:\n          len = MIN(len, MAX_SFX_SIZE - 1);\n          mfread(sfx_buf, len, 1, &prop);\n          sfx_buf[len] = '\\0';\n\n          ret = parse_sfx(sfx_buf, file, IS_SFX, i, -1);\n          if(ret != SUCCESS)\n            return ret;\n          break;\n      }\n    }\n  }\n  else\n  {\n    trace(\"SFX array found\\n\");\n    mfseek(mf, 0, SEEK_SET);\n\n    for(i = 0; i < NUM_BUILTIN_SFX; i++)\n    {\n      if(!mfread(sfx_buf, LEGACY_SFX_SIZE, 1, mf))\n        return FREAD_FAILED;\n\n      sfx_buf[LEGACY_SFX_SIZE] = 0;\n\n      ret = parse_sfx(sfx_buf, file, IS_SFX, i, -1);\n      if(ret != SUCCESS)\n        return ret;\n    }\n  }\n  return ret;\n}\n\nstatic enum status parse_world(struct memfile *mf, struct base_file *file,\n boolean is_a_world)\n{\n  struct zip_archive *zp = zip_open_mem_read(mf->start,\n   mf->end - mf->start);\n\n  char *buffer = NULL;\n  size_t actual_size;\n  size_t allocated_size = 0;\n  struct memfile buf_file;\n  unsigned int file_id;\n  unsigned int board_id;\n  unsigned int robot_id;\n\n  enum status ret = SUCCESS;\n\n  if(!zp)\n  {\n    ret = CORRUPT_WORLD;\n    goto err_close;\n  }\n\n  world_assign_file_ids(zp, is_a_world);\n\n  while(ZIP_SUCCESS == zip_get_next_mzx_file_id(zp, &file_id, &board_id, &robot_id))\n  {\n    switch(file_id)\n    {\n      case FILE_ID_WORLD_GLOBAL_ROBOT:\n      case FILE_ID_BOARD_INFO:\n      case FILE_ID_ROBOT:\n      case FILE_ID_WORLD_SFX:\n      {\n        if(zip_get_next_uncompressed_size(zp, &actual_size) != ZIP_SUCCESS)\n        {\n          zip_skip_file(zp);\n          continue;\n        }\n\n        if(allocated_size < actual_size)\n        {\n          allocated_size = actual_size;\n          buffer = crealloc(buffer, allocated_size);\n        }\n\n        zip_read_file(zp, buffer, actual_size, &actual_size);\n        mfopen(buffer, actual_size, &buf_file);\n\n        if(file_id == FILE_ID_BOARD_INFO)\n        {\n          trace(\"Board %u\\n\", board_id);\n          ret = parse_board_info(&buf_file, file, (int)board_id);\n        }\n        else\n\n        if(file_id == FILE_ID_ROBOT)\n        {\n          ret = parse_robot_info(&buf_file, file, (int)board_id, (int)robot_id);\n        }\n        else\n\n        if(file_id == FILE_ID_WORLD_GLOBAL_ROBOT)\n        {\n          trace(\"Global robot\\n\");\n          ret = parse_robot_info(&buf_file, file, NO_BOARD, GLOBAL_ROBOT);\n        }\n        else\n\n        if(file_id == FILE_ID_WORLD_SFX)\n        {\n          trace(\"SFX table\\n\");\n          ret = parse_sfx_file(&buf_file, file);\n        }\n\n        break;\n      }\n\n      default:\n      {\n        zip_skip_file(zp);\n        break;\n      }\n    }\n\n    if(ret != SUCCESS)\n      goto err_close;\n  }\n\nerr_close:\n  zip_close(zp, NULL);\n  free(buffer);\n  return ret;\n}\n\n\n/******************/\n/* Common Formats */\n/******************/\n\n// same as internal boards except for a 4 byte magic header\n\nstatic enum status parse_board_file(struct memfile *mf, struct base_file *file)\n{\n  unsigned char magic[4];\n  int file_version;\n\n  if(mfread(magic, 1, 4, mf) != 4)\n    return FREAD_FAILED;\n\n  file_version = board_magic(magic);\n  if(file_version <= 0)\n    return MAGIC_CHECK_FAILED;\n\n  file->world_version = file_version;\n  file->file_version = file_version;\n\n  if(file_version <= MZX_LEGACY_FORMAT_VERSION)\n    return parse_legacy_board(mf, file, -1);\n\n  if(file_version <= MZX_VERSION)\n    return parse_world(mf, file, false);\n\n  return MAGIC_CHECK_FAILED;\n}\n\nstatic enum status parse_world_file(struct memfile *mf, struct base_file *file)\n{\n  unsigned char magic[4];\n  int file_version;\n\n  // Truncation safety check.\n  if(!mfhasspace(64, mf))\n    return FREAD_FAILED;\n\n  // skip to protected byte; don't care about world name\n  if(mfseek(mf, LEGACY_WORLD_PROTECTED_OFFSET, SEEK_SET) != 0)\n    return FSEEK_FAILED;\n\n  file->protection_method = mfgetc(mf);\n  if(file->protection_method != 0)\n  {\n    // World is probably protected (or possibly junk).\n    long password_pos;\n    long magic_pos;\n\n    if(file->protection_method < 0 || file->protection_method > 3)\n      return MAGIC_CHECK_FAILED;\n\n    password_pos = mftell(mf);\n    if(mfread(file->password, 1, MAX_PASSWORD_LENGTH, mf) != MAX_PASSWORD_LENGTH)\n      return FREAD_FAILED;\n    file->password[MAX_PASSWORD_LENGTH] = '\\0';\n    file->password_length = MAX_PASSWORD_LENGTH;\n\n    magic_pos = mftell(mf);\n    if(mfread(magic, 1, 4, mf) != 4)\n      return FREAD_FAILED;\n\n    file_version = _world_magic(magic);\n    if(file_version <= 0)\n    {\n      // 1.xx world magic is located one byte further in the file.\n      file_version = _world_magic(magic + 1);\n      if(file_version != V100)\n        return MAGIC_CHECK_FAILED;\n\n      // 1.x passwords are stored with a length, not an obfuscated nul.\n      mfseek(mf, password_pos, SEEK_SET);\n      file->password_length = mfgetc(mf);\n      if(file->password_length > MAX_PASSWORD_LENGTH)\n        return BAD_100_PASSWORD;\n\n      mfread(file->password, 1, file->password_length, mf);\n      magic_pos++;\n    }\n\n    file->file_version = file_version;\n    mfseek(mf, magic_pos + 3, SEEK_SET);\n    _decrypt_legacy_world(mf, file);\n    mfseek(mf, magic_pos, SEEK_SET);\n  }\n\n  // read in world magic (version)\n  if(mfread(magic, 1, 3, mf) != 3)\n    return FREAD_FAILED;\n\n  // can only support 2.00+ versioned worlds\n  file_version = _world_magic(magic);\n  if(file_version <= 0)\n    return MAGIC_CHECK_FAILED;\n\n  file->world_version = file_version;\n  file->file_version = file_version;\n\n  trace(\"File version: %03x\\n\", file->file_version);\n\n  if(file_version <= MZX_LEGACY_FORMAT_VERSION)\n    return parse_legacy_world(mf, file);\n\n  if(file_version <= MZX_VERSION)\n    return parse_world(mf, file, true);\n\n  return MAGIC_CHECK_FAILED;\n}\n\nstatic char *load_file(vfile *vf, size_t *buf_size)\n{\n  char *buffer;\n  *buf_size = vfilelength(vf, true);\n\n  buffer = cmalloc(*buf_size);\n  *buf_size = vfread(buffer, 1, *buf_size, vf);\n\n  return buffer;\n}\n\nstatic enum status parse_file(const char *file_name,\n struct base_path **path_list, int path_list_size)\n{\n  char file_dir[MAX_PATH];\n  struct stat stat_info;\n\n  int path_list_alloc = path_list_size;\n\n  struct base_file **file_list = NULL;\n  struct base_file *current_file;\n  int file_list_alloc = 0;\n  int file_list_size = 0;\n\n  int len;\n  char *ext;\n\n  struct memfile mf;\n  char *buffer = NULL;\n  size_t buf_size;\n  vfile *vf;\n  int i;\n\n  enum status ret = SUCCESS;\n\n  vf = vfopen_unsafe(file_name, \"rb\");\n  len = strlen(file_name);\n  ext = len >= 4 ? (char *)file_name + len - 4 : NULL;\n\n  if(!_get_path(file_dir, file_name))\n    snprintf(file_dir, sizeof(file_dir), \".\");\n\n  if(vf && ext && !strcasecmp(ext, \".MZX\"))\n  {\n    buffer = load_file(vf, &buf_size);\n    vfclose(vf);\n\n    // Add the containing directory as a base path\n    add_base_path(file_dir, &path_list, &path_list_size, &path_list_alloc);\n\n    current_file = add_base_file(file_name,\n     &file_list, &file_list_size, &file_list_alloc);\n    mfopen(buffer, buf_size, &mf);\n\n    ret = parse_world_file(&mf, current_file);\n    free(buffer);\n  }\n  else\n\n  if(vf && ext && !strcasecmp(ext, \".MZB\"))\n  {\n    buffer = load_file(vf, &buf_size);\n    vfclose(vf);\n\n    // Add the containing directory as a base path\n    add_base_path(file_dir, &path_list, &path_list_size, &path_list_alloc);\n\n    current_file = add_base_file(file_name,\n     &file_list, &file_list_size, &file_list_alloc);\n    mfopen(buffer, buf_size, &mf);\n\n    ret = parse_board_file(&mf, current_file);\n    free(buffer);\n  }\n  else\n\n  if(vf && !vstat(file_name, &stat_info) && S_ISREG(stat_info.st_mode))\n  {\n    // Is a file but isn't an .mzx or an .mzb? Try to read it as a zip...\n    struct base_path *zip_base;\n    struct zip_archive *zp;\n\n    size_t actual_size;\n    char name_buffer[MAX_PATH];\n\n    vfclose(vf);\n\n    // Add the zip as a base path\n    // This will open the zip and read its directory\n\n    zip_base = add_base_path(file_name, &path_list,\n     &path_list_size, &path_list_alloc);\n\n    if(!zip_base || !zip_base->zp)\n      goto error;\n\n    zp = zip_base->zp;\n\n    // Iterate the ZIP\n    while(ZIP_SUCCESS == zip_get_next_uncompressed_size(zp, &actual_size))\n    {\n      zip_get_next_name(zp, name_buffer, MAX_PATH-1);\n\n      len = strlen(name_buffer);\n      ext = len >= 4 ? (char *)name_buffer + len - 4 : NULL;\n\n      if(ext && (!strcasecmp(ext, \".MZX\") || !strcasecmp(ext, \".MZB\")))\n      {\n        buffer = ccalloc(1, actual_size);\n        if(ZIP_SUCCESS != zip_read_file(zp, buffer, actual_size, &actual_size))\n        {\n          warnhere(\"Error processing '%s': %s\\n\\n\", name_buffer,\n           decode_status(ZIP_FAILED));\n          free(buffer);\n          continue;\n        }\n\n        current_file = add_base_file(name_buffer,\n         &file_list, &file_list_size, &file_list_alloc);\n\n        // Files in zips need a relative path.\n        _get_path(current_file->relative_path, name_buffer);\n\n        mfopen(buffer, actual_size, &mf);\n\n        if(!strcasecmp(ext, \".MZX\"))\n          ret = parse_world_file(&mf, current_file);\n        else\n          ret = parse_board_file(&mf, current_file);\n\n        if(ret != SUCCESS)\n        {\n          // Keep going; other files in the archive may not be corrupt.\n          warnhere(\"Error processing '%s': %s\\n\\n\", name_buffer, decode_status(ret));\n          ret = SUCCESS;\n        }\n        free(buffer);\n      }\n      else\n      {\n        zip_skip_file(zp);\n      }\n    }\n\n    // The file and zip will be closed in clear_data().\n  }\n  else\n\n  // Try to open the file as a directory.\n  if(add_base_files_from_path(file_name, &file_list, &file_list_size,\n   &file_list_alloc) && file_list_size)\n  {\n    struct base_path *dir_base = add_base_path(file_name, &path_list,\n     &path_list_size, &path_list_alloc);\n    char name_buffer[MAX_PATH];\n\n    if(vf) vfclose(vf);\n    if(!dir_base)\n      return DIRENT_FAILED;\n\n    for(i = 0; i < file_list_size; i++)\n    {\n      current_file = file_list[i];\n      join_path(name_buffer, file_name, current_file->file_name);\n\n      vf = vfopen_unsafe(name_buffer, \"rb\");\n      len = strlen(current_file->file_name);\n      ext = len >= 4 ? current_file->file_name + len - 4 : NULL;\n      if(vf)\n      {\n        // NOTE: the relative paths of these are automatically added in\n        // add_base_files_from_path. The base file itself doesn't need a\n        // relative path because it's being created as the cwd.\n        buffer = load_file(vf, &buf_size);\n        vfclose(vf);\n\n        mfopen(buffer, buf_size, &mf);\n\n        if(ext && !strcasecmp(ext, \".MZX\"))\n          ret = parse_world_file(&mf, current_file);\n        else\n\n        if(ext && !strcasecmp(ext, \".MZB\"))\n          ret = parse_board_file(&mf, current_file);\n\n        free(buffer);\n\n        if(ret != SUCCESS)\n        {\n          // Keep going; other files in the path may not be corrupt.\n          warnhere(\"Error processing '%s': %s\\n\", current_file->file_name,\n           decode_status(ret));\n          ret = SUCCESS;\n        }\n      }\n      else\n        warnhere(\"Failed to open '%s' for reading\\n\", current_file->file_name);\n    }\n  }\n\n  else\n  {\nerror:\n    fprintf(stderr,\n      \"'%s' is not a .MZX (world), a .MZB (board), \"\n      \"a directory containing .MZX/.MZB files, or a ZIP archive.\\n\",\n      file_name\n    );\n    return INVALID_ARGUMENTS;\n  }\n\n  process_requirements(path_list, path_list_size);\n\n  clear_data(path_list, path_list_size,\n   file_list, file_list_size);\n\n  if(!display_filename_only && !output_format_csv)\n   fprintf(stdout, \"\\nFinished processing '%s'.\\n\\n\", file_name);\n\n  fflush(stdout);\n\n  return ret;\n}\n\nint main(int argc, char *argv[])\n{\n  struct base_path **path_list = NULL;\n  int path_list_alloc = 0;\n  int path_list_size = 0;\n  enum status ret = SUCCESS;\n  int i;\n\n  struct base_path *current_path = NULL;\n  char *file_name = NULL;\n  char *param;\n\n#ifdef CONFIG_PLEDGE_UTILS\n  // Hard to predict where this will read ahead of time, so no unveil right now.\n  if(pledge(PROMISES, \"\"))\n  {\n    fprintf(stderr, \"ERROR: Failed pledge!\\n\");\n    return 1;\n  }\n#endif\n\n  if(argc < 2)\n  {\n    fprintf(stderr, USAGE);\n    return INVALID_ARGUMENTS;\n  }\n\n  for(i = 1; i < argc; i++)\n  {\n    if(argv[i][0] == '-')\n    {\n      param = argv[i] + 1;\n\n      if(!strcmp(param, \"extra\"))\n      {\n        if(file_name)\n        {\n          i++;\n          if(i < argc)\n          {\n            current_path = add_base_path(argv[i], &path_list, &path_list_size,\n             &path_list_alloc);\n          }\n          else\n          {\n            fprintf(stderr, \"ERROR: expected path or zip file\\n\");\n            return INVALID_ARGUMENTS;\n          }\n        }\n        else\n        {\n          fprintf(stderr, \"ERROR: -extra must follow base file\\n\");\n          return INVALID_ARGUMENTS;\n        }\n      }\n      else\n      if(!strcmp(param, \"in\"))\n      {\n        if(current_path)\n        {\n          i++;\n          if(i < argc)\n          {\n            change_base_path_dir(current_path, argv[i]);\n            current_path = NULL;\n          }\n          else\n          {\n            fprintf(stderr, \"ERROR: expected relative path\\n\");\n            return INVALID_ARGUMENTS;\n          }\n        }\n        else\n        {\n          fprintf(stderr, \"ERROR: -in must follow extra\\n\");\n          return INVALID_ARGUMENTS;\n        }\n      }\n      else\n\n      while(*param)\n      {\n        switch(*param)\n        {\n          case 'h':\n            fprintf(stderr, USAGE);\n            return SUCCESS;\n\n          case 'A':\n            display_not_found = true;\n            display_found = true;\n            display_created = true;\n            display_unused = false;\n            display_wildcard = true;\n            break;\n\n          case 'C':\n            display_not_found = false;\n            display_found = false;\n            display_created = true;\n            display_unused = false;\n            display_wildcard = false;\n            break;\n\n          case 'F':\n            display_not_found = false;\n            display_found = true;\n            display_created = false;\n            display_unused = false;\n            display_wildcard = false;\n            break;\n\n          case 'M':\n            display_not_found = true;\n            display_found = false;\n            display_created = false;\n            display_unused = false;\n            display_wildcard = false;\n            break;\n\n          case 'U':\n            display_not_found = false;\n            display_found = false;\n            display_created = false;\n            display_unused = true;\n            display_wildcard = false;\n            break;\n\n          case 'W':\n            display_not_found = false;\n            display_found = false;\n            display_created = false;\n            display_unused = false;\n            display_wildcard = true;\n            break;\n\n          case 'a':\n            display_not_found = true;\n            display_found = true;\n            display_created = true;\n            display_unused = true;\n            display_wildcard = true;\n            break;\n\n          case 'c':\n            display_created = true;\n            break;\n\n          case 'f':\n            display_found = true;\n            break;\n\n          case 'm':\n            display_not_found = true;\n            break;\n\n          case 'u':\n            display_unused = true;\n            break;\n\n          case 'w':\n            display_wildcard = true;\n            break;\n\n          case 's':\n            display_filename_only = false;\n            display_first_only = true;\n            display_details = false;\n            display_all_details = false;\n            break;\n\n          case 'v':\n          {\n            if(param[1] == 'v')\n            {\n              display_all_details = true;\n              param++;\n            }\n            else\n              display_all_details = false;\n\n            display_filename_only = false;\n            display_first_only = false;\n            display_details = true;\n            break;\n          }\n\n          case '1':\n          case 'q': // Legacy equivalent of -1\n            display_first_only = true;\n            display_filename_only = true;\n            display_details = false;\n            display_all_details = false;\n            break;\n\n          case 'z':\n            display_crc32 = true;\n            break;\n\n          case 'V':\n            output_format_csv = true;\n            break;\n\n          case 'L':\n            sort_by = SORT_BY_LOCATION;\n            break;\n\n          case 'N':\n            sort_by = SORT_BY_FILENAME;\n            break;\n\n          default:\n            fprintf(stderr, \"Unrecognized argument '%c'\\n\", *param);\n            return INVALID_ARGUMENTS;\n        }\n        param++;\n      }\n    }\n\n    else\n    {\n      // Parse the previous base file.\n      if(file_name)\n      {\n        ret = parse_file(file_name, path_list, path_list_size);\n\n        path_list = NULL;\n        path_list_size = 0;\n        path_list_alloc = 0;\n\n        if(ret != SUCCESS)\n          break;\n      }\n      file_name = argv[i];\n      current_path = NULL;\n    }\n  }\n\n  // Parse the final base file\n  if(file_name && ret == SUCCESS)\n  {\n    ret = parse_file(file_name, path_list, path_list_size);\n  }\n\n  if(ret != SUCCESS)\n  {\n    fprintf(stderr, \"ERROR: %s\\n\", decode_status(ret));\n  }\n\n  fflush(stderr);\n  return ret;\n}\n"
  },
  {
    "path": "src/utils/downver.c",
    "content": "/* MegaZeux\n *\n * This is a very trivial tool to downgrade a world or board file to the\n * previous binary version. It allows a world or board created in the current\n * version of MegaZeux to be loadable in the previous version (i.e. before the\n * world format change). Of course, the downgraded world may not work, but it\n * should be loadable by the editor.\n *\n * This tool is a good insurance policy for brand new releases, to prevent\n * the case where people start work in a newer version of MegaZeux, only\n * for there to be a critical bug discovered, and for the user to be \"locked\n * in\" to the buggy version.\n *\n * Primarily, this tool is intended for DoZs where a new version may be used,\n * but a serious bug cannot reasonably be corrected before the end of the\n * competition.\n *\n * Since the tool will change with every release, it is hard coded to downgrade\n * only to one previous version, from one current version. This is intentional,\n * to make the tool easy to use for newbies (simply drag and drop).\n *\n * Copyright (C) 2008-2009 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2017-2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdio.h>\n#include <string.h>\n\n#ifdef __WIN32__\n#include <strings.h>\n#endif\n\n#define CORE_LIBSPEC\n#include \"../compat.h\"\n#include \"utils_alloc.h\"\n\n#ifdef CONFIG_PLEDGE_UTILS\n#include <unistd.h>\n#define PROMISES \"stdio rpath wpath cpath\"\n#endif\n\n#include \"../const.h\"\n#include \"../graphics.h\"\n#include \"../util.h\"\n#include \"../world.h\"\n#include \"../world_format.h\"\n#include \"../io/memfile.h\"\n#include \"../io/vio.h\"\n#include \"../io/zip.h\"\n\n#define DOWNVER_VERSION \"2.93\"\n#define DOWNVER_EXT \".292\"\n\n#define MZX_VERSION_HI ((MZX_VERSION >> 8) & 0xff)\n#define MZX_VERSION_LO (MZX_VERSION & 0xff)\n\n#define MZX_VERSION_PREV_HI ((MZX_VERSION_PREV >> 8) & 0xff)\n#define MZX_VERSION_PREV_LO (MZX_VERSION_PREV & 0xff)\n\nenum status\n{\n  SUCCESS = 0,\n  ARCHIVE_ERROR,\n  READ_ERROR,\n  WRITE_ERROR,\n  SEEK_ERROR,\n};\n\n#define error(...) \\\n  { \\\n    fprintf(stderr, __VA_ARGS__); \\\n    fflush(stderr); \\\n  }\n\nstruct downver_state\n{\n  struct zip_archive *out;\n  struct zip_archive *in;\n\n  /* 2.93 conversion vars */\n  int screen_mode;\n};\n\nstatic inline void save_prop_p(int ident, struct memfile *prop,\n struct memfile *mf)\n{\n  save_prop_a(ident, prop->start, (prop->end - prop->start), 1, mf);\n}\n\n/* <2.92d did not properly terminate this field on loading it, convert it back\n * to an ASCIIZ string. */\nstatic inline void save_prop_s_to_asciiz(int ident, size_t len,\n struct memfile *prop, struct memfile *mf)\n{\n  char buf[BOARD_NAME_SIZE];\n  len = MIN(len, BOARD_NAME_SIZE - 1);\n\n  len = mfread(buf, 1, len, prop);\n  buf[len] = '\\0';\n  save_prop_a(ident, buf, len + 1, 1, mf);\n}\n\nstatic enum zip_error zip_duplicate_file(struct downver_state *dv,\n void (*handler)(struct downver_state *, struct memfile *, struct memfile *))\n{\n  enum zip_error result;\n  unsigned int method;\n  char name[9];\n  void *buffer;\n  size_t actual_size;\n\n  result = zip_get_next_name(dv->in, name, 9);\n  if(result)\n    return result;\n\n  result = zip_get_next_method(dv->in, &method);\n  if(result)\n    return result;\n\n  result = zip_get_next_uncompressed_size(dv->in, &actual_size);\n  if(result)\n    return result;\n\n  buffer = malloc(actual_size);\n  if(!buffer)\n    return ZIP_ALLOC_ERROR;\n\n  result = zip_read_file(dv->in, buffer, actual_size, &actual_size);\n  if(result)\n    goto err_free;\n\n  if(handler)\n  {\n    struct memfile mf_in;\n    struct memfile mf_out;\n\n    void *buffer2 = malloc(actual_size * 2);\n    if(buffer2)\n    {\n      mfopen(buffer, actual_size, &mf_in);\n      mfopen_wr(buffer2, actual_size * 2, &mf_out);\n\n      handler(dv, &mf_out, &mf_in);\n\n      // Free the old buffer, then replace it with the new buffer.\n      free(buffer);\n      mfsync(&buffer, &actual_size, &mf_out);\n    }\n  }\n\n  result = zip_write_file(dv->out, name, buffer, actual_size, (int)method);\n\nerr_free:\n  free(buffer);\n  return result;\n}\n\nstatic void convert_293_to_292_status_counters(struct downver_state *dv,\n struct memfile *dest, struct memfile *src)\n{\n  char counters[NUM_STATUS_COUNTERS * COUNTER_NAME_SIZE];\n  struct memfile prop;\n  int ident;\n  int len;\n  int num = 0;\n\n  memset(counters, 0, sizeof(counters));\n\n  while(next_prop(&prop, &ident, &len, src))\n  {\n    switch(ident)\n    {\n      case STATCTRPROP_SET_ID:\n        num = load_prop_int(&prop);\n        break;\n\n      case STATCTRPROP_NAME:\n        if(num < NUM_STATUS_COUNTERS)\n        {\n          char *pos = counters + num * COUNTER_NAME_SIZE;\n          len = MIN(len, COUNTER_NAME_SIZE - 1);\n          len = mfread(pos, 1, len, &prop);\n          pos[len] = '\\0';\n        }\n        break;\n    }\n  }\n  save_prop_a(WPROP_STATUS_COUNTERS, counters,\n   COUNTER_NAME_SIZE, NUM_STATUS_COUNTERS, dest);\n}\n\nstatic void convert_293_to_292_world_info(struct downver_state *dv,\n struct memfile *dest, struct memfile *src)\n{\n  struct memfile prop;\n  int ident;\n  int len;\n\n  while(next_prop(&prop, &ident, &len, src))\n  {\n    switch(ident)\n    {\n      case WPROP_WORLD_VERSION:\n      case WPROP_FILE_VERSION:\n        // Replace the version number\n        save_prop_w(ident, MZX_VERSION_PREV, dest);\n        break;\n\n      case WPROP_WORLD_NAME:\n        /* NO loading termination pre-2.92d */\n        save_prop_s_to_asciiz(ident, BOARD_NAME_SIZE, &prop, dest);\n        break;\n\n      case WPROP_STATUS_COUNTERS:\n        convert_293_to_292_status_counters(dv, dest, &prop);\n        break;\n\n      case WPROP_SMZX_MODE:\n        /* Load for palette conversion. */\n        dv->screen_mode = load_prop_int(&prop);\n        save_prop_c(ident, dv->screen_mode, dest);\n        break;\n\n      case WPROP_OUTPUT_MODE:\n        /* Added in 2.93 */\n        break;\n\n      default:\n        save_prop_p(ident, &prop, dest);\n        break;\n    }\n  }\n\n  save_prop_eof(dest);\n  mfresize(mftell(dest), dest);\n}\n\nstatic void convert_293_to_292_board_info(struct downver_state *dv,\n struct memfile *dest, struct memfile *src)\n{\n  struct memfile prop;\n  int ident;\n  int len;\n\n  while(next_prop(&prop, &ident, &len, src))\n  {\n    switch(ident)\n    {\n      case BPROP_FILE_VERSION:\n        // Replace the version number\n        save_prop_w(ident, MZX_VERSION_PREV, dest);\n        break;\n\n      case BPROP_BOARD_NAME:\n        /* loading termination expects BOARD_NAME_SIZE input pre-2.93 */\n        save_prop_s_to_asciiz(ident, BOARD_NAME_SIZE, &prop, dest);\n        break;\n\n      case BPROP_RESET_ON_ENTRY_SAME_BOARD:\n      case BPROP_DRAGONS_CAN_RANDOMLY_MOVE:\n      case BPROP_BLIND_DUR:\n      case BPROP_FIREWALKER_DUR:\n      case BPROP_FREEZE_TIME_DUR:\n      case BPROP_SLOW_TIME_DUR:\n      case BPROP_WIND_DUR:\n        /* Added in 2.93 */\n        break;\n\n      default:\n        save_prop_p(ident, &prop, dest);\n        break;\n    }\n  }\n\n  save_prop_eof(dest);\n  mfresize(mftell(dest), dest);\n}\n\nstatic void convert_293_to_292_robot_info(struct downver_state *dv,\n struct memfile *dest, struct memfile *src)\n{\n  struct memfile prop;\n  int ident;\n  int len;\n\n  while(next_prop(&prop, &ident, &len, src))\n  {\n    switch(ident)\n    {\n      case RPROP_ROBOT_NAME:\n        /* NO loading termination pre-2.92d */\n        save_prop_s_to_asciiz(ident, ROBOT_NAME_SIZE, &prop, dest);\n        break;\n\n      default:\n        save_prop_p(ident, &prop, dest);\n        break;\n    }\n  }\n\n  save_prop_eof(dest);\n  mfresize(mftell(dest), dest);\n}\n\nstatic void convert_293_to_292_sensor_info(struct downver_state *dv,\n struct memfile *dest, struct memfile *src)\n{\n  struct memfile prop;\n  int ident;\n  int len;\n\n  while(next_prop(&prop, &ident, &len, src))\n  {\n    switch(ident)\n    {\n      case SENPROP_SENSOR_NAME:\n      case SENPROP_ROBOT_TO_MESG:\n        /* NO loading termination pre-2.92d */\n        save_prop_s_to_asciiz(ident, ROBOT_NAME_SIZE, &prop, dest);\n        break;\n\n      default:\n        save_prop_p(ident, &prop, dest);\n        break;\n    }\n  }\n\n  save_prop_eof(dest);\n  mfresize(mftell(dest), dest);\n}\n\nstatic void convert_293_to_292_sfx(struct downver_state *dv,\n struct memfile *dest, struct memfile *src)\n{\n  struct memfile prop;\n  int ident;\n  int len;\n  int num = 0;\n  char buf[8];\n\n  mfresize(NUM_BUILTIN_SFX * LEGACY_SFX_SIZE, dest);\n  memset(dest->start, 0, NUM_BUILTIN_SFX * LEGACY_SFX_SIZE);\n\n  if(mfread(buf, 8, 1, src) && !memcmp(buf, \"MZFX\\x1a\", 6))\n  {\n    while(next_prop(&prop, &ident, &len, src))\n    {\n      switch(ident)\n      {\n        case SFXPROP_SET_ID:\n          num = load_prop_int(&prop);\n          break;\n\n        case SFXPROP_STRING:\n          if(num >= 0 && num < NUM_BUILTIN_SFX)\n          {\n            len = MIN(len, LEGACY_SFX_SIZE - 1);\n            mfseek(dest, num * LEGACY_SFX_SIZE, SEEK_SET);\n            mfread(dest->current, len, 1, &prop);\n          }\n          break;\n      }\n    }\n  }\n  else\n  {\n    mfseek(src, 0, SEEK_SET);\n    mfread(dest->start, LEGACY_SFX_SIZE, NUM_BUILTIN_SFX, src);\n    dest->end[-1] = '\\0';\n  }\n}\n\nstatic enum status convert_293_to_292(struct downver_state *dv)\n{\n  enum zip_error err = ZIP_SUCCESS;\n  unsigned int file_id;\n  unsigned int board_id;\n  unsigned int robot_id;\n\n  if(!dv->in || !dv->out)\n  {\n    zip_close(dv->in, NULL);\n    zip_close(dv->out, NULL);\n    return ARCHIVE_ERROR;\n  }\n\n  world_assign_file_ids(dv->in, true);\n\n  while(ZIP_SUCCESS == zip_get_next_mzx_file_id(dv->in, &file_id, &board_id, &robot_id))\n  {\n    switch(file_id)\n    {\n      case FILE_ID_WORLD_INFO:\n        err = zip_duplicate_file(dv, convert_293_to_292_world_info);\n        break;\n\n      case FILE_ID_BOARD_INFO:\n        err = zip_duplicate_file(dv, convert_293_to_292_board_info);\n        break;\n\n      case FILE_ID_WORLD_GLOBAL_ROBOT:\n      case FILE_ID_ROBOT:\n        err = zip_duplicate_file(dv, convert_293_to_292_robot_info);\n        break;\n\n      case FILE_ID_SENSOR:\n        err = zip_duplicate_file(dv, convert_293_to_292_sensor_info);\n        break;\n\n      case FILE_ID_WORLD_SFX:\n        err = zip_duplicate_file(dv, convert_293_to_292_sfx);\n        break;\n\n      case FILE_ID_WORLD_PAL:\n        if(!dv->screen_mode)\n          err = zip_duplicate_file(dv, NULL);\n        else\n          zip_skip_file(dv->in);\n        break;\n\n      case FILE_ID_WORLD_PAL_SMZX:\n        if(dv->screen_mode)\n        {\n          char buf[SMZX_PAL_SIZE * 3];\n          memset(buf, 0, sizeof(buf));\n          err = zip_read_file(dv->in, buf, SMZX_PAL_SIZE * 3, NULL);\n          err = zip_write_file(dv->out, \"pal\", buf, SMZX_PAL_SIZE * 3, ZIP_M_DEFLATE);\n        }\n        else\n          zip_skip_file(dv->in);\n        break;\n\n      case FILE_ID_WORLD_PAL_INTENSITY:\n        if(!dv->screen_mode)\n        {\n          char buf[PAL_SIZE * 4];\n          int i;\n\n          memset(buf, 0, sizeof(buf));\n          err = zip_read_file(dv->in, buf, PAL_SIZE * 4, NULL);\n          if(err == ZIP_SUCCESS)\n          {\n            // Convert to the old format.\n            for(i = 0; i < PAL_SIZE; i++)\n              buf[i] = buf[i * 4];\n\n            zip_write_file(dv->out, \"palint\", buf, PAL_SIZE, ZIP_M_NONE);\n          }\n        }\n        else\n          zip_skip_file(dv->in);\n        break;\n\n      case FILE_ID_WORLD_PAL_INTENSITY_SMZX:\n        if(dv->screen_mode)\n        {\n          char buf[SMZX_PAL_SIZE * 4];\n          int i;\n\n          memset(buf, 0, sizeof(buf));\n          err = zip_read_file(dv->in, buf, SMZX_PAL_SIZE * 4, NULL);\n          if(err == ZIP_SUCCESS)\n          {\n            // Convert to the old format.\n            for(i = 0; i < SMZX_PAL_SIZE; i++)\n              buf[i] = buf[i * 4];\n\n            zip_write_file(dv->out, \"palint\", buf, SMZX_PAL_SIZE, ZIP_M_NONE);\n          }\n        }\n        else\n          zip_skip_file(dv->in);\n        break;\n\n      default:\n        err = zip_duplicate_file(dv, NULL);\n        break;\n    }\n\n    if(err != ZIP_SUCCESS)\n    {\n      // Skip files that don't work\n      zip_skip_file(dv->in);\n      err = ZIP_SUCCESS;\n    }\n  }\n\n  zip_close(dv->in, NULL);\n  zip_close(dv->out, NULL);\n  return SUCCESS;\n}\n\n/* Validate the magic string. */\nstatic boolean check_magic(const char *magic, boolean save)\n{\n  if(save)\n  {\n    if(magic[0] != 'M' || magic[1] != 'Z' || magic[2] != 'S')\n    {\n      error(\"Save file is corrupt or unsupported.\\n\");\n      return false;\n    }\n    magic += 3;\n  }\n  else\n  {\n    if(magic[0] != 'M')\n    {\n      error(\"World or board file is corrupt or unsupported.\\n\");\n      return false;\n    }\n    magic++;\n  }\n\n  if(magic[0] != MZX_VERSION_HI || magic[1] != MZX_VERSION_LO)\n  {\n    error(\"This tool only supports worlds, saves, or boards from %d.%d.\\n\",\n     MZX_VERSION_HI, MZX_VERSION_LO);\n    return false;\n  }\n  return true;\n}\n\n/* Replace the new magic string with the old magic string. */\nstatic void fix_magic(char *magic, boolean save)\n{\n  if(save)\n  {\n    magic[3] = MZX_VERSION_PREV_HI;\n    magic[4] = MZX_VERSION_PREV_LO;\n\n    /* Also lower the original world version value (little endian). */\n    if(magic[6] > MZX_VERSION_HI ||\n     (magic[6] == MZX_VERSION_HI && magic[5] > MZX_VERSION_LO))\n    {\n      magic[5] = MZX_VERSION_PREV_LO;\n      magic[6] = MZX_VERSION_PREV_HI;\n    }\n  }\n  else\n  {\n    magic[1] = MZX_VERSION_PREV_HI;\n    magic[2] = MZX_VERSION_PREV_LO;\n  }\n}\n\nint main(int argc, char *argv[])\n{\n  char fname[MAX_PATH + 4];\n  char magic[8];\n  struct downver_state dv;\n  enum status ret;\n  long ext_pos;\n  int byte;\n  boolean world = false;\n  boolean save = false;\n  vfile *in;\n  vfile *out;\n\n  if(strcmp(VERSION, DOWNVER_VERSION) < 0 && 0)\n  {\n    error(\"[ERROR] Update downver for \" VERSION \"!\\n\");\n    return 1;\n  }\n\n  if(argc <= 1)\n  {\n    error(\"Usage: %s [mzx/mzb file]\\n\", argv[0]);\n    goto exit_out;\n  }\n\n  ext_pos = strlen(argv[1]) - 4;\n  if(ext_pos < 0)\n  {\n    error(\"File '%s' of an unknown type.\\n\", argv[1]);\n    goto exit_out;\n  }\n\n  if(!strcasecmp(argv[1] + ext_pos, \".mzb\"))\n  {\n    snprintf(fname, sizeof(fname), \"%.*s\" DOWNVER_EXT \".mzb\",\n     (int)ext_pos, argv[1]);\n    fname[sizeof(fname) - 1] = '\\0';\n  }\n  else\n\n  if(!strcasecmp(argv[1] + ext_pos, \".mzx\"))\n  {\n    snprintf(fname, sizeof(fname), \"%.*s\" DOWNVER_EXT \".mzx\",\n     (int)ext_pos, argv[1]);\n    fname[sizeof(fname) - 1] = '\\0';\n    world = true;\n  }\n  else\n\n  if(!strcasecmp(argv[1] + ext_pos, \".sav\"))\n  {\n    snprintf(fname, sizeof(fname), \"%.*s\" DOWNVER_EXT \".sav\",\n     (int)ext_pos, argv[1]);\n    fname[sizeof(fname) - 1] = '\\0';\n    save = true;\n  }\n  else\n  {\n    error(\"Unknown extension '%s'.\\n\", argv[1] + ext_pos);\n    goto exit_out;\n  }\n\n#ifdef CONFIG_PLEDGE_UTILS\n#ifdef PLEDGE_HAS_UNVEIL\n  if(unveil(argv[1], \"r\") || unveil(fname, \"cw\") || unveil(NULL, NULL))\n  {\n    error(\"[ERROR] Failed unveil!\\n\");\n    return 1;\n  }\n#endif\n\n  if(pledge(PROMISES, \"\"))\n  {\n    error(\"[ERROR] Failed pledge!\\n\");\n    return 1;\n  }\n#endif\n\n  in = vfopen_unsafe(argv[1], \"rb\");\n  if(!in)\n  {\n    error(\"Could not open '%s' for read.\\n\", argv[1]);\n    goto exit_out;\n  }\n\n  out = vfopen_unsafe(fname, \"wb\");\n  if(!out)\n  {\n    error(\"Could not open '%s' for write.\\n\", fname);\n    vfclose(in);\n    goto exit_out;\n  }\n\n  /* World files need some crap at the beginning skipped. Also, world files\n   * can theoretically be encrypted, though practically this will not happen\n   * with modern worlds. Just in case, check for it and abort.\n   *\n   * Board files just need the 0xFF byte at the start skipped.\n   */\n  if(world)\n  {\n    char name[BOARD_NAME_SIZE];\n    size_t len;\n\n    // Duplicate the world name\n    len = vfread(name, BOARD_NAME_SIZE, 1, in);\n    if(len != 1)\n      goto err_read;\n\n    len = vfwrite(name, BOARD_NAME_SIZE, 1, out);\n    if(len != 1)\n      goto err_write;\n\n    // Check protection isn't enabled\n    byte = vfgetc(in);\n    if(byte < 0)\n    {\n      goto err_read;\n    }\n    else\n\n    if(byte != 0)\n    {\n      error(\"Protected worlds are not supported.\\n\");\n      goto exit_close;\n    }\n    vfputc(0, out);\n\n    if(vfread(magic, 1, 3, in) < 3)\n      goto err_read;\n\n    if(!check_magic(magic, false))\n      goto exit_close;\n\n    fix_magic(magic, false);\n    vfwrite(magic, 1, 3, out);\n  }\n  else\n\n  if(save)\n  {\n    if(vfread(magic, 1, 8, in) < 8)\n      goto err_read;\n\n    if(!check_magic(magic, true))\n      goto exit_close;\n\n    fix_magic(magic, true);\n    vfwrite(magic, 1, 8, out);\n  }\n  else\n  {\n    byte = vfgetc(in);\n    if(byte < 0)\n    {\n      goto err_read;\n    }\n    else\n\n    if(byte != 0xFF)\n    {\n      error(\"Board file is corrupt or unsupported.\\n\");\n      goto exit_close;\n    }\n    vfputc(0xFF, out);\n\n    if(vfread(magic, 1, 3, in) < 3)\n      goto exit_close;\n\n    if(!check_magic(magic, false))\n      goto exit_close;\n\n    fix_magic(magic, false);\n    vfwrite(magic, 1, 3, out);\n  }\n\n  // Worlds, saves, and boards are the same from here out.\n  // Conversion closes the file pointers, so NULL them.\n\n  memset(&dv, 0, sizeof(dv));\n  dv.in  = zip_open_vf_read(in);\n  dv.out = zip_open_vf_write(out);\n\n  // Zip64 support was added in 2.93.\n  if(MZX_VERSION_PREV < V293)\n    zip_set_zip64_enabled(dv.out, false);\n\n  ret = convert_293_to_292(&dv);\n  out = NULL;\n  in = NULL;\n\n  switch(ret)\n  {\n    case ARCHIVE_ERROR: goto err_zip;\n    case SEEK_ERROR:    goto err_seek;\n    case READ_ERROR:    goto err_read;\n    case WRITE_ERROR:   goto err_write;\n    case SUCCESS:       break;\n  }\n\n  fprintf(stdout,\n   \"File '%s' successfully downgraded from %d.%d to %d.%d.\\n\"\n   \"Saved to '%s'.\\n\",\n   argv[1], MZX_VERSION_HI, MZX_VERSION_LO,\n   MZX_VERSION_PREV_HI, MZX_VERSION_PREV_LO, fname);\n\nexit_close:\n  if(out)\n    vfclose(out);\n  if(in)\n    vfclose(in);\n\nexit_out:\n  return 0;\n\nerr_zip:\n  error(\"Error opening world archive, aborting.\\n\")\n  goto exit_close;\n\nerr_seek:\n  error(\"Seek error, aborting.\\n\");\n  goto exit_close;\n\nerr_read:\n  error(\"Read error, aborting.\\n\");\n  goto exit_close;\n\nerr_write:\n  error(\"Write error, aborting.\\n\");\n  goto exit_close;\n}\n"
  },
  {
    "path": "src/utils/hlp2html.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2018-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <ctype.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n// Defines so hlp2html builds when this is included.\n#define CORE_LIBSPEC\n#include \"../compat.h\"\n#include \"../hashtable.h\"\n#include \"../util.h\"\n#include \"../io/memfile.h\"\n\n#include \"utils_alloc.h\"\n\n#ifdef CONFIG_PLEDGE_UTILS\n#include <unistd.h>\n#define PROMISES \"stdio rpath wpath cpath\"\n#endif\n\n#ifndef VERSION_DATE\n#define VERSION_DATE\n#endif\n\n#define EOL \"\\n\"\n#define MZXFONT_OFFSET (0xE000)\n\n#define USAGE \\\n \"hlp2html - Convert help text file to HTML.\" EOL \\\n \"usage: hlp2html input.txt output.html [options]...\" EOL EOL \\\n \"options:\" EOL \\\n \"    -c : color+printable version (default)\" EOL \\\n \"    -p : printable-only version\" EOL\n\n#define RESOURCES \"contrib/hlp2html/\"\nstatic const char * const font_file =         RESOURCES \"fonts.css\";\nstatic const char * const style_file =        RESOURCES \"style.css\";\nstatic const char * const style_extras_file = RESOURCES \"style_color.css\";\n\n#define MAX_FILES 256\n#define MAX_LINE 256\n#define MAX_FILE_NAME 16\n#define MAX_ANCHOR_NAME 32\n\nstruct html_buffer\n{\n  char *data;\n  int length;\n  int alloc;\n};\n\nstruct help_file\n{\n  // HTML ID- needs to be unique. Use the real internal file name.\n  char name[MAX_FILE_NAME];\n  int name_length;\n  uint32_t hash;\n\n  struct html_buffer html;\n};\n\nstruct help_anchor\n{\n  struct help_file *parent;\n\n  // HTML ID- needs to be unique. Use the real link name.\n  char name[MAX_ANCHOR_NAME];\n  int name_length;\n  uint32_t hash;\n};\n\nstruct help_link\n{\n  struct help_file *parent;\n  // HTML ID of target anchor's enclosing file.\n  char target_file[MAX_FILE_NAME];\n  // HTML ID of target anchor.\n  char target_anchor[MAX_ANCHOR_NAME];\n\n  // HTML ID- needs to be unique. Intended to be used to inject JS, but not\n  // used for anything right now.\n  char name[MAX_ANCHOR_NAME];\n  int name_length;\n  uint32_t hash;\n};\n\nHASH_SET_INIT(FILES, struct help_file *, name, name_length)\nHASH_SET_INIT(ANCHORS, struct help_anchor *, name, name_length)\nHASH_SET_INIT(LINKS, struct help_link *, name, name_length)\n\nstatic hash_t(FILES) *files_table = NULL;\nstatic hash_t(ANCHORS) *anchors_table = NULL;\nstatic hash_t(LINKS) *links_table = NULL;\n\nstatic struct help_file *help_file_list[MAX_FILES];\nstatic int num_help_files = 0;\n\nstatic void load_file(char **_output, size_t *_output_len, const char *filename)\n{\n  FILE *fp = fopen_unsafe(filename, \"rb\");\n  char *output = NULL;\n  size_t output_len = 0;\n\n  if(fp)\n  {\n    fseek(fp, 0, SEEK_END);\n    output_len = ftell(fp);\n    fseek(fp, 0, SEEK_SET);\n    output = cmalloc(output_len + 1);\n    if(!fread(output, output_len, 1, fp))\n      fprintf(stderr, \"ERROR: could not read input file\\n\");\n    output[output_len] = 0;\n    fclose(fp);\n  }\n  else\n    fprintf(stderr, \"ERROR: failed to read input file '%s'\\n\", filename);\n\n  *_output = output;\n  *_output_len = output_len;\n}\n\n/**\n * Help file management.\n */\n\nstatic char *expand_buffer(struct html_buffer *buffer, int required_length)\n{\n  if(buffer->alloc == 0)\n    buffer->alloc = 1024;\n\n  while(buffer->alloc < required_length)\n    buffer->alloc *= 2;\n\n  buffer->data = crealloc(buffer->data, buffer->alloc);\n  return buffer->data;\n}\n\nstatic void append_html(struct help_file *current, const char *html)\n{\n  struct html_buffer *buffer = &(current->html);\n  int html_length = strlen(html);\n  int new_length = buffer->length + html_length;\n\n  if(new_length + 1 > buffer->alloc)\n    expand_buffer(buffer, new_length + 1);\n\n  memcpy(buffer->data + buffer->length, html, html_length);\n  buffer->length = new_length;\n  buffer->data[new_length] = 0;\n}\n\nstatic void append_file(struct help_file *current, const char *filename)\n{\n  char *buffer;\n  size_t length;\n\n  load_file(&buffer, &length, filename);\n  if(buffer)\n  {\n    append_html(current, buffer);\n    append_html(current, EOL);\n    free(buffer);\n  }\n}\n\nstatic struct help_file *new_help_file(const char *title)\n{\n  struct help_file *check;\n  struct help_file *current = ccalloc(1, sizeof(struct help_file));\n  snprintf(current->name, MAX_FILE_NAME, \"%s\", title);\n  current->name_length = strlen(current->name);\n\n  HASH_FIND(FILES, files_table, current->name, current->name_length, check);\n  if(check)\n  {\n    fprintf(stderr, \"WARNING: Duplicate file '%s' found. Please debug with \"\n     \"'make help_check'.\\n\", current->name);\n\n    free(current);\n    return NULL;\n  }\n  else\n  {\n    HASH_ADD(FILES, files_table, current);\n    help_file_list[num_help_files] = current;\n    num_help_files++;\n    return current;\n  }\n}\n\nstatic void free_help_files(void)\n{\n  struct help_file *current;\n\n  HASH_ITER(FILES, files_table, current, {\n    free(current->html.data);\n    free(current);\n  });\n\n  HASH_CLEAR(FILES, files_table);\n}\n\n/**\n * Anchor management.\n */\n\nstatic void new_anchor(struct help_file *parent, const char *name)\n{\n  struct help_anchor *check;\n  struct help_anchor *anchor = ccalloc(1, sizeof(struct help_anchor));\n  snprintf(anchor->name, MAX_ANCHOR_NAME, \"%s\", name);\n  anchor->name_length = strlen(anchor->name);\n  anchor->parent = parent;\n\n  HASH_FIND(ANCHORS, anchors_table, anchor->name, anchor->name_length, check);\n  if(check)\n  {\n    fprintf(stderr, \"WARNING: Duplicate anchor '%s' found. Please debug with \"\n     \"'make help_check'.\\n\", anchor->name);\n\n    free(anchor);\n  }\n  else\n  {\n    HASH_ADD(ANCHORS, anchors_table, anchor);\n  }\n}\n\nstatic void free_anchors(void)\n{\n  struct help_anchor *current;\n\n  HASH_ITER(ANCHORS, anchors_table, current, {\n    free(current);\n  });\n\n  HASH_CLEAR(ANCHORS, anchors_table);\n}\n\n/**\n * Links management.\n */\n\nstatic void new_link(struct help_file *parent, const char *name,\n const char *target_file, const char *target_anchor)\n{\n  struct help_link *link = ccalloc(1, sizeof(struct help_link));\n  snprintf(link->name, MAX_ANCHOR_NAME, \"%s\", name);\n  link->name_length = strlen(link->name);\n  link->parent = parent;\n\n  snprintf(link->target_file, MAX_FILE_NAME, \"%s\", target_file);\n  snprintf(link->target_anchor, MAX_ANCHOR_NAME, \"%s\", target_anchor);\n\n  // The name is generated with a unique number; don't bother checking.\n  HASH_ADD(LINKS, links_table, link);\n}\n\nstatic void validate_links(void)\n{\n  // FIXME should implement this.\n}\n\nstatic void free_links(void)\n{\n  struct help_link *current;\n\n  HASH_ITER(LINKS, links_table, current, {\n    free(current);\n  });\n\n  HASH_CLEAR(LINKS, links_table);\n}\n\n/**\n * Start of line commands:\n *\n * #FILE.HLP (start of new file-- note MAIN.HLP is the first and implicit)\n * :AAA (anchor with name \"AAA\", does not display a new line)\n * :AAA:Text (anchor with name \"AAA\", displays a new line with \"Text\")\n * >AAA:Text (link targeting anchor \"AAA\" in current file)\n * >#FILE.HLP:AAA:Text (external link targeting \"AAA\" in \"FILE.HLP\")\n * $Centered Text\n * .$Text (escape command; displays \"$Text\")\n *\n * Anywhere in the line commands:\n *\n * ~A color text foreground (0-9, A-F, a-f)\n * @A color text background (0-9, A-F, a-f)\n *\n * Note: there's a special case for the scroll char where, when\n * encountered as 228, it should be displayed as 232.\n *\n * Note: If an anchor is followed by anything (even a single space), this\n * means the line should be displayed. Otherwise, hide the line.\n */\n\nstatic boolean write_printable_html = false;\nstatic boolean color_code_active = false;\nstatic unsigned char current_fg_color_char;\nstatic unsigned char current_bg_color_char;\n\nstatic char validate_color_char(char input)\n{\n  if(input >= '0' && input <= '9')\n    return input;\n\n  if(toupper(input) >= 'A' && toupper(input) <= 'F')\n    return input;\n\n  return 0;\n}\n\nstatic void set_current_fg_color(unsigned char input)\n{\n  input = validate_color_char(input);\n  if(input > 0)\n    current_fg_color_char = input;\n}\n\nstatic void set_current_bg_color(unsigned char input)\n{\n  input = validate_color_char(input);\n  if(input > 0)\n    current_bg_color_char = input;\n}\n\nstatic void reset_current_color(void)\n{\n  current_fg_color_char = 0;\n  current_bg_color_char = 0;\n  color_code_active = false;\n}\n\nstatic void apply_color_codes(struct help_file *current)\n{\n  char buffer[4];\n\n  if(write_printable_html)\n    return;\n\n  if(current_fg_color_char == 0 && current_bg_color_char == 0)\n    return;\n\n  if(color_code_active)\n    append_html(current, \"</span>\");\n\n  append_html(current, \"<span class=\\\"\");\n\n  if(current_fg_color_char > 0)\n  {\n    buffer[0] = 'f';\n    buffer[1] = current_fg_color_char;\n    buffer[2] = 0;\n\n    if(current_bg_color_char > 0)\n    {\n      buffer[2] = ' ';\n      buffer[3] = 0;\n    }\n    append_html(current, buffer);\n  }\n\n  if(current_bg_color_char > 0)\n  {\n    buffer[0] = 'b';\n    buffer[1] = current_bg_color_char;\n    buffer[2] = 0;\n    append_html(current, buffer);\n  }\n\n  append_html(current, \"\\\">\");\n  color_code_active = true;\n}\n\nstatic void close_color_codes(struct help_file *current)\n{\n  if(color_code_active)\n    append_html(current, \"</span>\");\n\n  reset_current_color();\n}\n\nstatic struct help_file *parse_line(struct help_file *current, char *line)\n{\n  boolean is_new_color = false;\n  boolean is_text = false;\n  boolean is_centered = false;\n  boolean is_anchor = false;\n  unsigned char cur;\n  char buffer[MAX_ANCHOR_NAME];\n\n  if(!current)\n    return NULL;\n\n  // Check for commands.\n  switch(line[0])\n  {\n    case '#':\n    {\n      // Start a new help file.\n      return new_help_file(line + 1);\n    }\n\n    case '@':\n    {\n      // End of help file.\n      return NULL;\n    }\n\n    case ':':\n    {\n      // Anchor. If there is a ':' after the label name, display text.\n      char *label = line + 1;\n      size_t label_len;\n\n      line = strchr(label, ':');\n      if(line)\n      {\n        *line = 0;\n        line++;\n      }\n\n      label_len = strlen(label);\n\n      if(label_len == 1 || label_len == 10)\n        fprintf(stderr, \"WARNING: label length of 1 or 10 found. \"\n         \"Please debug with 'make help_check'.\\n\");\n\n      snprintf(buffer, MAX_ANCHOR_NAME, \"%s__%s\", current->name, label);\n      new_anchor(current, buffer);\n      append_html(current, \"<a class=\\\"hA\\\" name=\\\"\");\n      append_html(current, buffer);\n      append_html(current, \"\\\">\");\n      is_anchor = true;\n      break;\n    }\n\n    case '>':\n    {\n      // Link.\n      static int link_count = 0;\n      char target_anchor[MAX_ANCHOR_NAME];\n      char *target_file = current->name;\n      char *label;\n\n      line++;\n      if(line[0] == '#')\n      {\n        // External link.\n        target_file = line + 1;\n\n        line = strchr(target_file, ':');\n        if(line)\n        {\n          *line = 0;\n          line++;\n        }\n      }\n\n      label = line;\n      line = strchr(label, ':');\n      if(line)\n      {\n        *line = 0;\n        line++;\n      }\n\n      snprintf(target_anchor, MAX_ANCHOR_NAME, \"%s__%s\", target_file, label);\n      snprintf(buffer, MAX_ANCHOR_NAME, \"%d__%s\", (++link_count), label);\n      new_link(current, buffer, target_file, target_anchor);\n      append_html(current, \"<a class=\\\"hL\\\" href=\\\"#\");\n      append_html(current, target_anchor);\n      append_html(current, \"\\\">\");\n      is_anchor = true;\n      break;\n    }\n\n    case '$':\n    {\n      // Centered line.\n      append_html(current, \"<p class=\\\"hC\\\">\");\n      is_centered = true;\n      line++;\n      break;\n    }\n\n    case '.':\n    {\n      // Escape command.\n      line++;\n      break;\n    }\n  }\n\n  reset_current_color();\n\n  if(line)\n  {\n    while((cur = *(line++)))\n    {\n      if(cur == '~')\n      {\n        cur = *(line++);\n        if(cur != '~')\n        {\n          set_current_fg_color(cur);\n          is_new_color = true;\n          continue;\n        }\n      }\n\n      if(cur == '@')\n      {\n        cur = *(line++);\n        if(cur != '@')\n        {\n          set_current_bg_color(cur);\n          is_new_color = true;\n          continue;\n        }\n      }\n\n      is_text = true;\n      if(is_new_color)\n      {\n        apply_color_codes(current);\n        is_new_color = false;\n      }\n\n      if(cur < 32 || cur > 126)\n      {\n        // Special case for scroll char.\n        if(cur == 228)\n          cur = 232;\n\n        snprintf(buffer, 32, \"&#x%X;\", MZXFONT_OFFSET + cur);\n        append_html(current, buffer);\n      }\n      else\n      {\n        // Certain chars need to be escaped to entities to display reliably.\n        switch(cur)\n        {\n          case '&':\n            append_html(current, \"&amp;\");\n            break;\n\n          case '<':\n            append_html(current, \"&lt;\");\n            break;\n\n          case '>':\n            append_html(current, \"&gt;\");\n            break;\n\n          default:\n            buffer[0] = cur;\n            buffer[1] = 0;\n            append_html(current, buffer);\n            break;\n        }\n      }\n    }\n  }\n\n  close_color_codes(current);\n\n  if(is_anchor)\n  {\n    // Append an extra space if this anchor is supposed to be consuming a line.\n    if(line && !is_text)\n      append_html(current, \" \");\n    append_html(current, \"</a>\");\n  }\n\n  if(is_centered)\n    append_html(current, \"</p>\");\n  else\n    append_html(current, EOL);\n\n  return current;\n}\n\nstatic void parse_file(char *file_buffer, size_t file_length)\n{\n  struct help_file *current = new_help_file(\"MAIN.HLP\");\n  char line_buffer[MAX_LINE];\n  struct memfile mf;\n\n  mfopen(file_buffer, file_length, &mf);\n\n  while(current && mfsafegets(line_buffer, MAX_LINE, &mf))\n    current = parse_line(current, line_buffer);\n\n  // Make sure all links connect to a valid anchor.\n  validate_links();\n}\n\n#define TITLE \"MegaZeux \" VERSION VERSION_DATE \" - Help File\"\n\nstatic void append_nav_url(struct help_file *current, const char *target_file,\n const char *target_anchor, const char *text)\n{\n  char buffer[512];\n  snprintf(buffer, 512, \"<li><a href=\\\"#%s__%s\\\">%s</a></li>\",\n   target_file, target_anchor, text);\n\n  append_html(current, buffer);\n}\n\nstatic void write_html(const char *output)\n{\n  FILE *fp_out;\n  struct help_file root;\n  struct help_file *current;\n  int i;\n\n  memset(&root, 0, sizeof(struct help_file));\n\n  append_html(&root, \"<!DOCTYPE html>\" EOL);\n  append_html(&root, \"<html>\" EOL \"<head>\" EOL);\n  append_html(&root, \"<title>\" TITLE \"</title>\" EOL);\n\n  append_html(&root, \"<meta charset=\\\"UTF-8\\\">\" EOL);\n  append_html(&root, \"<meta name=\\\"title\\\" content=\\\"\" TITLE \"\\\">\" EOL);\n  append_html(&root, \"<meta name=\\\"twitter:card\\\" content=\\\"summary\\\">\" EOL);\n  append_html(&root, \"<meta name=\\\"twitter:title\\\" content=\\\"\" TITLE \"\\\">\" EOL);\n\n  append_html(&root, \"<style>\" EOL);\n  append_file(&root, font_file);\n  append_html(&root, \"</style>\" EOL);\n\n  append_html(&root, \"<style>\" EOL);\n  append_file(&root, style_file);\n  append_html(&root, \"</style>\" EOL);\n\n  if(!write_printable_html)\n  {\n    append_html(&root, \"<style>\" EOL);\n    append_file(&root, style_extras_file);\n    append_html(&root, \"</style>\" EOL);\n  }\n\n  append_html(&root, \"</head>\" EOL EOL \"<body>\" EOL);\n\n  if(!write_printable_html)\n  {\n    append_html(&root, \"<div id=\\\"helpnav\\\">\" EOL);\n    append_html(&root, \"<h1>\" TITLE \"</h1>\" EOL);\n    append_html(&root, \"<div class=\\\"helpnavcentered\\\"><ul>\" EOL);\n    append_nav_url(&root, \"MAIN.HLP\", \"072\", \"Contents\");\n    append_nav_url(&root, \"EDITINGK.HLP\", \"080\", \"Editor Keys\");\n    append_nav_url(&root, \"ROBOTICR.HLP\", \"087\", \"Robotic Manual\");\n    append_nav_url(&root, \"COMMANDR.HLP\", \"1st\", \"Commands\");\n    append_nav_url(&root, \"COUNTERS.HLP\", \"1st\", \"Counters\");\n    append_nav_url(&root, \"NEWINVER.HLP\", \"1st\", \"Changelog\");\n    append_html(&root, \"</ul></div>\" EOL \"</div>\" EOL EOL);\n  }\n  else\n  {\n    append_html(&root, \"<div id=\\\"helpnavprint\\\"><h1>\" TITLE \"</h1></div>\" EOL);\n  }\n\n  append_html(&root, \"<div id=\\\"helpcontainer\\\">\" EOL);\n\n  for(i = 0; i < num_help_files; i++)\n  {\n    current = help_file_list[i];\n    append_html(&root, \"<div class=\\\"hF\\\" id=\\\"\");\n    append_html(&root, current->name);\n    append_html(&root, \"\\\">\" EOL);\n    append_html(&root, current->html.data);\n    append_html(&root, EOL \"<hr>\" \"</div>\" EOL EOL);\n  }\n\n  append_html(&root, \"</div>\" EOL \"</body>\" EOL \"</html>\" EOL);\n\n  fp_out = fopen_unsafe(output, \"wb\");\n  if(!fp_out || !fwrite(root.html.data, root.html.length, 1, fp_out))\n    fprintf(stderr, \"ERROR: could not write output file\\n\");\n  fclose(fp_out);\n\n  free(root.html.data);\n}\n\nint main(int argc, char *argv[])\n{\n  char *file_buffer;\n  size_t file_buffer_len;\n  int i;\n\n  if(argc < 3)\n  {\n    fprintf(stdout, USAGE);\n    exit(0);\n  }\n\n#ifdef CONFIG_PLEDGE_UTILS\n#ifdef PLEDGE_HAS_UNVEIL\n  if(unveil(argv[1], \"r\") || unveil(argv[2], \"cw\") ||\n   unveil(RESOURCES, \"r\") || unveil(NULL, NULL))\n  {\n    fprintf(stderr, \"ERROR: Failed unveil!\\n\");\n    return 1;\n  }\n#endif\n\n  if(pledge(PROMISES, \"\"))\n  {\n    fprintf(stderr, \"ERROR: Failed pledge!\\n\");\n    return 1;\n  }\n#endif\n\n  for(i = 3; i < argc; i++)\n  {\n    if(!strcasecmp(argv[i], \"-c\"))\n    {\n      write_printable_html = false;\n      continue;\n    }\n\n    if(!strcasecmp(argv[i], \"-p\"))\n    {\n      write_printable_html = true;\n      continue;\n    }\n  }\n\n  load_file(&file_buffer, &file_buffer_len, argv[1]);\n\n  if(file_buffer)\n  {\n    parse_file(file_buffer, file_buffer_len);\n    write_html(argv[2]);\n\n    free(file_buffer);\n    free_help_files();\n    free_anchors();\n    free_links();\n  }\n  return 0;\n}\n"
  },
  {
    "path": "src/utils/hlp2txt.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <stdio.h>\n\n#include \"../compat.h\"\n\n#ifdef CONFIG_PLEDGE_UTILS\n#include <unistd.h>\n#define PROMISES \"stdio rpath wpath cpath\"\n#endif\n\nstatic int fgetw(FILE *fp)\n{\n  int r = fgetc(fp);\n  r |= fgetc(fp) << 8;\n  return r;\n}\n\nstatic int fgetd(FILE *fp)\n{\n  int r = fgetc(fp);\n  r |= fgetc(fp) << 8;\n  r |= fgetc(fp) << 16;\n  r |= fgetc(fp) << 24;\n  return r;\n}\n\n#define error(...) \\\n  { \\\n    fprintf(stderr, __VA_ARGS__); \\\n    fflush(stderr); \\\n  }\n\nstruct subfile\n{\n  char name[13];\n  int offset;\n  int length;\n};\n\nint main(int argc, char *argv[])\n{\n  FILE *in = NULL, *out = NULL;\n  int i, num_files, ret = 1;\n  struct subfile *subfiles;\n\n  if(argc != 3)\n  {\n    error(\"usage: %s [help.fil] [help.txt]\\n\", argv[0]);\n    goto exit_out;\n  }\n\n#ifdef CONFIG_PLEDGE_UTILS\n#ifdef PLEDGE_HAS_UNVEIL\n  if(unveil(argv[1], \"r\") || unveil(argv[2], \"cw\") || unveil(NULL, NULL))\n  {\n    fprintf(stderr, \"ERROR: Failed unveil!\\n\");\n    return 1;\n  }\n#endif\n\n  if(pledge(PROMISES, \"\"))\n  {\n    error(\"ERROR: Failed pledge!\\n\");\n    return 1;\n  }\n#endif\n\n  in = fopen_unsafe(argv[1], \"rb\");\n  if(!in)\n  {\n    error(\"Failed to open input file '%s' for reading.\\n\", argv[1]);\n    goto exit_close;\n  }\n\n  out = fopen_unsafe(argv[2], \"w\");\n  if(!out)\n  {\n    error(\"Failed to open output file '%s' for writing.\\n\", argv[2]);\n    goto exit_close;\n  }\n\n  /* Each help FIL is split into subfiles internally, aligned to pages,\n   * and with a fixed offset and length. Walk these subfiles here.\n   */\n  num_files = fgetw(in);\n  subfiles = calloc(num_files, sizeof(struct subfile));\n\n  for(i = 0; i < num_files; i++)\n  {\n    if(fread(&subfiles[i].name, 13, 1, in) != 1)\n      goto err_read_io;\n    subfiles[i].offset = fgetd(in);\n    subfiles[i].length = fgetd(in);\n  }\n\n  /* FIXME: Hack, remove!!\n   * txt2hlp seems to inject an unnecessary 0x0A at the end of the file\n   * which means we can't use hlp2txt for validation. Hack the final\n   * subfile so it's two characters short.\n   */\n  subfiles[i - 1].length-=2;\n\n  for(i = 0; i < num_files; i++)\n  {\n    int start_pos;\n\n    if(fseek(in, subfiles[i].offset, SEEK_SET) != 0)\n      goto err_read_io;\n\n    /* Each help subfile contains a single byte indicating the index\n     * of the subfile. We must ignore this for converting out.\n     */\n    if(fseek(in, 1, SEEK_CUR) != 0)\n      goto err_read_io;\n    subfiles[i].length--;\n\n    /* We're going through line-wise but we need an absolute reference. */\n    start_pos = ftell(in);\n    if(start_pos < 0)\n      goto err_read_io;\n\n    if(i > 0)\n    {\n      /* If we're not the zero'th subfile we need to write out our name. */\n      if(fprintf(out, \"#%s\\n\", subfiles[i].name) < 0)\n        goto err_write_io;\n    }\n\n    while(1)\n    {\n      int byte, curr_pos;\n      char buffer[256];\n\n      curr_pos = ftell(in);\n      if(curr_pos < 0)\n        goto err_read_io;\n\n      if(curr_pos - start_pos >= subfiles[i].length)\n        break;\n\n      /* Peek at the next byte to check it's not 0xFF. This character\n       * indicates that the line is something special, like a link.\n       */\n      byte = fgetc(in);\n      if(byte == EOF)\n        goto err_read_io;\n\n      /* We got a control sequence of some kind. Try to decode it. */\n      if(byte == 0xff)\n      {\n        byte = fgetc(in);\n        if(byte == EOF)\n          goto err_read_io;\n\n        switch(byte)\n        {\n          /* Centered line */\n          case '$':\n            /* Write out the control code */\n            if(fputc('$', out) == EOF)\n              goto err_write_io;\n            break;\n\n          /* Label */\n          case ':':\n          /* Choice (non-file) */\n          case '>':\n            /* Write out the control code */\n            if(fputc(byte, out) == EOF)\n              goto err_write_io;\n\n            /* Read label length in bytes */\n            byte = fgetc(in);\n            if(byte == EOF)\n              goto err_read_io;\n\n            /* Read this many bytes and write them out */\n            if(fread(buffer, byte, 1, in) != 1)\n              goto err_read_io;\n            if(fwrite(buffer, byte, 1, out) != 1)\n              goto err_write_io;\n\n            /* Skip over NULL terminator, and defer */\n            if(fseek(in, 1, SEEK_CUR) != 0)\n              goto err_read_io;\n\n            /* Write out separator */\n            if(fputc(':', out) == EOF)\n              goto err_write_io;\n            break;\n\n          /* Choice (file) */\n          case '<':\n            /* Write out the control code */\n            if(fputc('>', out) == EOF)\n              goto err_write_io;\n\n            /* FIXME: Assume it's a file link */\n            if(fputc('#', out) == EOF)\n              goto err_write_io;\n\n            /* Read label length in bytes */\n            byte = fgetc(in);\n            if(byte == EOF)\n              goto err_read_io;\n\n            /* Read this many bytes and write them out */\n            if(fread(buffer, byte, 1, in) != 1)\n              goto err_read_io;\n            if(fwrite(buffer, byte, 1, out) != 1)\n              goto err_write_io;\n\n            /* Write out separator */\n            if(fputc(':', out) == EOF)\n              goto err_write_io;\n\n            /* Skip over NULL terminator, and defer */\n            if(fseek(in, 1, SEEK_CUR) != 0)\n              goto err_read_io;\n            break;\n\n          default:\n            error(\"Unhandled control sequence '%c', trying to skip.\\n\", byte);\n            if(!fgets(buffer, 256, in))\n              goto err_read_io;\n            continue;\n        }\n\n        /* Read the tail end of the command as a regular string. */\n        if(!fgets(buffer, 256, in))\n          goto err_read_io;\n      }\n      else\n      {\n        /* Otherwise backtrace this peek, prepping for a normal read. */\n        if(fseek(in, -1, SEEK_CUR) != 0)\n          goto err_read_io;\n\n        /* Regular string; can be read as ascii. */\n        if(!fgets(buffer, 256, in))\n          goto err_read_io;\n\n        /* Special case escaping -- obviously since the first column of any\n         * line can contain \"control\" characters, we must not emit these with\n         * \"normal\" strings. The system has a reserved '.' character for this.\n         */\n        switch(buffer[0])\n        {\n          case '>':\n          case '<':\n          case ':':\n          case '#':\n          case '.':\n            if(fputc('.', out) == EOF)\n              goto err_write_io;\n            break;\n        }\n      }\n\n      if(fprintf(out, \"%s\", buffer) < 0)\n        goto err_write_io;\n    }\n  }\n\n  ret = 0;\n\nexit_free:\n  free(subfiles);\nexit_close:\n  if(out)\n    fclose(out);\n  if(in)\n    fclose(in);\nexit_out:\n  return ret;\n\nerr_read_io:\n  error(\"I/O error on read, aborting.\\n\");\n  goto exit_free;\n\nerr_write_io:\n  error(\"I/O error on write, aborting.\\n\");\n  goto exit_free;\n}\n"
  },
  {
    "path": "src/utils/image_common.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2022 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __UTILS_IMAGE_COMMON_H\n#define __UTILS_IMAGE_COMMON_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#if defined(_MSC_VER) && _MSC_VER < 1400\nstatic void imagedbg(...) {}\nstatic void imagenodbg(...) {}\n#else\n#define imagedbg(...) \\\n do { fprintf(stderr, __VA_ARGS__); fflush(stderr); } while(0)\n#define imagenodbg(...) do {} while(0)\n#endif\n\n#ifndef RESTRICT\n#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) || \\\n    (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \\\n    (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus))\n#define RESTRICT __restrict\n#else\n#define RESTRICT\n#endif\n#endif\n\n#undef ARRAY_SIZE\n#undef IMAGE_MAX\n#undef IMAGE_MIN\n#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))\n#define IMAGE_MAX(a,b) ((a) > (b) ? (a) : (b))\n#define IMAGE_MIN(a,b) ((a) < (b) ? (a) : (b))\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* __UTILS_IMAGE_COMMON_H */\n"
  },
  {
    "path": "src/utils/image_file.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2021-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"image_common.h\"\n#include \"image_file.h\"\n#include \"image_gif.h\"\n\n#include <ctype.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#ifdef _WIN32\n#include <fcntl.h>\n#endif\n\n#if 0\n#define debug(...) imagedbg(\"IMG: \" __VA_ARGS__)\n#else\n#define debug imagenodbg\n#endif\n\n/* TODO: hack for MegaZeux fopen_unsafe */\n#undef fopen\n\n/* Internal compat for MegaZeux boolean type. */\ntypedef image_bool boolean;\n#ifndef __cplusplus\n#undef true\n#undef false\n#define true IMAGE_TRUE\n#define false IMAGE_FALSE\n#endif\n\n// These constraints are determined by the maximum size of a board/vlayer/MZM,\n// multiplied by the number of pixels per char in a given dimension.\n#define MAXIMUM_WIDTH  (32767u * 8u)\n#define MAXIMUM_HEIGHT (32767u * 14u)\n#define MAXIMUM_PIXELS ((16u << 20u) * 8u * 14u)\n\n// Maximum number of magic bytes.\n// BMP, NetPBM: 2; GIF: 3; PNG: 8; TGA: none, but 18 header bytes.\n#define CHECK_LENGTH 18\n\n#define ROUND_32 (1u << 31u)\n\n#define IMAGE_EOF -1\n\ntypedef struct imageinfo\n{\n  struct image_file *out;\n  void *in;\n  image_read_function readfn;\n  int unget_chr;\n}\nimageinfo;\n\nconst char *image_error_string(enum image_error err)\n{\n  switch(err)\n  {\n    case IMAGE_OK:\n      return \"success\";\n    case IMAGE_ERROR_UNKNOWN:\n      return \"unknown error\";\n    case IMAGE_ERROR_IO:\n      return \"read error\";\n    case IMAGE_ERROR_MEM:\n      return \"out of memory\";\n    case IMAGE_ERROR_SIGNATURE:\n      return \"unrecognized image type\";\n    case IMAGE_ERROR_CONSTRAINT:\n      return \"image size exceeds maximum\";\n\n    case IMAGE_ERROR_RAW_UNSUPPORTED_BPP:\n      return \"unsupported RAW bits per pixel\";\n\n    case IMAGE_ERROR_PNG_INIT:\n      return \"failed to init PNG decoder\";\n    case IMAGE_ERROR_PNG_FAILED:\n      return \"unknown PNG decoder error\";\n\n    case IMAGE_ERROR_GIF_INVALID:\n      return \"invalid GIF data\";\n    case IMAGE_ERROR_GIF_SIGNATURE:\n      return \"unsupported GIF type\";\n\n    case IMAGE_ERROR_BMP_UNSUPPORTED_DIB:\n      return \"unsupported BMP DIB header size\";\n    case IMAGE_ERROR_BMP_UNSUPPORTED_PLANES:\n      return \"unsupported BMP plane count\";\n    case IMAGE_ERROR_BMP_UNSUPPORTED_COMPRESSION:\n      return \"unsupported BMP compression method\";\n    case IMAGE_ERROR_BMP_UNSUPPORTED_BPP:\n      return \"unsupported BMP bits per pixel\";\n    case IMAGE_ERROR_BMP_UNSUPPORTED_BPP_RLE8:\n      return \"unsupported BMP bits per pixel (must be 8 for BI_RLE8)\";\n    case IMAGE_ERROR_BMP_UNSUPPORTED_BPP_RLE4:\n      return \"unsupported BMP bits per pixel (must be 4 for BI_RLE4)\";\n    case IMAGE_ERROR_BMP_UNSUPPORTED_BPP_BITFIELDS:\n      return \"unsupported BMP bits per pixel (must be 16 or 32 for BI_BITFIELDS)\";\n    case IMAGE_ERROR_BMP_BAD_1BPP:\n      return \"error unpacking 1BPP BMP\";\n    case IMAGE_ERROR_BMP_BAD_2BPP:\n      return \"error unpacking 2BPP BMP\";\n    case IMAGE_ERROR_BMP_BAD_4BPP:\n      return \"error unpacking 4BPP BMP\";\n    case IMAGE_ERROR_BMP_BAD_8BPP:\n      return \"error loading 8BPP BMP\";\n    case IMAGE_ERROR_BMP_BAD_16BPP:\n      return \"error loading 16BPP BMP\";\n    case IMAGE_ERROR_BMP_BAD_24BPP:\n      return \"error loading 24BPP BMP\";\n    case IMAGE_ERROR_BMP_BAD_32BPP:\n      return \"error loading 32BPP BMP\";\n    case IMAGE_ERROR_BMP_BAD_RLE:\n      return \"error decoding RLE BMP\";\n    case IMAGE_ERROR_BMP_BAD_SIZE:\n      return \"invalid BMP image size\";\n    case IMAGE_ERROR_BMP_BAD_COLOR_TABLE:\n      return \"invalid BMP color table\";\n    case IMAGE_ERROR_BMP_BAD_BITFIELDS_DIB:\n      return \"invalid BMP DIB version for BI_BITFIELDS\";\n    case IMAGE_ERROR_BMP_BAD_BITFIELDS_MASK_CONTINUITY:\n      return \"invalid non-continuous BMP BI_BITFIELDS mask\";\n    case IMAGE_ERROR_BMP_BAD_BITFIELDS_MASK_OVERLAP:\n      return \"invalid overlapping BMP BI_BITFIELDS mask\";\n    case IMAGE_ERROR_BMP_BAD_BITFIELDS_MASK_RANGE:\n      return \"invalid out-of-range BMP BI_BITFIELDS mask for 16bpp\";\n\n    case IMAGE_ERROR_PBM_BAD_HEADER:\n      return \"failed to read PBM header\";\n    case IMAGE_ERROR_PBM_BAD_MAXVAL:\n      return \"bad PBM header maxval\";\n    case IMAGE_ERROR_PAM_BAD_WIDTH:\n      return \"bad PAM width\";\n    case IMAGE_ERROR_PAM_BAD_HEIGHT:\n      return \"bad PAM height\";\n    case IMAGE_ERROR_PAM_BAD_DEPTH:\n      return \"bad PAM depth\";\n    case IMAGE_ERROR_PAM_BAD_MAXVAL:\n      return \"bad PAM maxval\";\n    case IMAGE_ERROR_PAM_BAD_TUPLTYPE:\n      return \"bad PAM tupltype\";\n    case IMAGE_ERROR_PAM_MISSING_ENDHDR:\n      return \"missing PAM endhdr\";\n    case IMAGE_ERROR_PAM_DEPTH_TUPLTYPE_MISMATCH:\n      return \"PAM tupltype does not support image depth\";\n\n    case IMAGE_ERROR_TGA_NOT_A_TGA:\n      return \"not a TGA\";\n    case IMAGE_ERROR_TGA_BAD_RLE:\n      return \"bad TGA RLE stream\";\n  }\n  return \"unknown error\";\n}\n\nstatic uint16_t read16be(const uint8_t in[2])\n{\n  return (in[0] << 8u) | in[1];\n}\n\nstatic uint32_t read32be(const uint8_t in[4])\n{\n  return (in[0] << 24u) | (in[1] << 16u) | (in[2] << 8u) | in[3];\n}\n\nstatic uint16_t read16le(const uint8_t in[2])\n{\n  return (in[1] << 8u) | in[0];\n}\n\nstatic uint32_t read32le(const uint8_t in[4])\n{\n  return (in[3] << 24u) | (in[2] << 16u) | (in[1] << 8u) | in[0];\n}\n\n/**\n * Image dimensions check for image_init and PNG/GIF loaders.\n */\nstatic boolean image_constraint(uint32_t width, uint32_t height)\n{\n  debug(\"Image constraint: %zu by %zu\\n\", (size_t)width, (size_t)height);\n  if(!width || !height || width > MAXIMUM_WIDTH || height > MAXIMUM_HEIGHT ||\n   (uint64_t)width * (uint64_t)height > MAXIMUM_PIXELS)\n    return false;\n\n  return true;\n}\n\n/**\n * Initialize an image file's pixel array and other values.\n */\nstatic enum image_error image_init(uint32_t width, uint32_t height,\n struct image_file *dest)\n{\n  struct rgba_color *data;\n\n  if(!image_constraint(width, height))\n    return IMAGE_ERROR_CONSTRAINT;\n\n  data = (struct rgba_color *)calloc(width * height, sizeof(struct rgba_color));\n  if(!data)\n    return IMAGE_ERROR_MEM;\n\n  dest->width = width;\n  dest->height = height;\n  dest->data = data;\n  return IMAGE_OK;\n}\n\n/**\n * Free an image struct's allocated data. Does not modify any other fields.\n */\nvoid image_free(struct image_file *dest)\n{\n  free(dest->data);\n  dest->data = NULL;\n}\n\n\n#ifdef CONFIG_PNG\n\n/**\n * PNG loader.\n */\n\n#include <setjmp.h>\n#include <png.h>\n\n#if PNG_LIBPNG_VER < 10504\n#define png_set_scale_16(p) png_set_strip_16(p)\n#endif\n\nstatic void png_read_fn(png_struct *png, png_byte *dest, size_t count)\n{\n  imageinfo *s = (imageinfo *)png_get_io_ptr(png);\n  if(s->readfn(dest, count, s->in) < count)\n    png_error(png, \"eof\");\n}\n\nstatic enum image_error load_png(imageinfo *s)\n{\n  struct image_file *dest = s->out;\n  png_struct *png = NULL;\n  png_info *info = NULL;\n  png_byte ** volatile row_ptrs = NULL;\n  png_uint_32 w;\n  png_uint_32 h;\n  png_uint_32 i;\n  png_uint_32 j;\n  int bit_depth;\n  int color_type;\n  enum image_error ret;\n\n  debug(\"Image type: PNG\\n\");\n  dest->data = NULL;\n\n  png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);\n  if(!png)\n    return IMAGE_ERROR_PNG_INIT;\n  info = png_create_info_struct(png);\n  if(!info)\n  {\n    ret = IMAGE_ERROR_PNG_INIT;\n    goto error;\n  }\n\n  if(setjmp(png_jmpbuf(png)))\n  {\n    ret = IMAGE_ERROR_PNG_FAILED;\n    goto error;\n  }\n\n  png_set_read_fn(png, s, png_read_fn);\n  png_set_sig_bytes(png, 8);\n\n  png_read_info(png, info);\n  png_get_IHDR(png, info, &w, &h, &bit_depth, &color_type, NULL, NULL, NULL);\n\n  ret = image_init(w, h, dest);\n  if(ret)\n    goto error;\n\n  row_ptrs = (png_byte **)malloc(sizeof(png_byte *) * h);\n  if(!row_ptrs)\n  {\n    ret = IMAGE_ERROR_MEM;\n    goto error;\n  }\n\n  for(i = 0, j = 0; i < h; i++, j += w)\n    row_ptrs[i] = (png_byte *)(dest->data + j);\n\n  /* This SHOULD convert everything to RGBA32.\n   * See the far too complicated table in libpng-manual.txt for more info. */\n  if(bit_depth == 16)\n    png_set_scale_16(png);\n  if(color_type & PNG_COLOR_MASK_PALETTE)\n    png_set_palette_to_rgb(png);\n  if(!(color_type & PNG_COLOR_MASK_COLOR))\n    png_set_gray_to_rgb(png);\n#if PNG_LIBPNG_VER >= 10207\n  if(!(color_type & PNG_COLOR_MASK_ALPHA))\n    png_set_add_alpha(png, 0xff, PNG_FILLER_AFTER);\n#endif\n  if(png_get_valid(png, info, PNG_INFO_tRNS))\n    png_set_tRNS_to_alpha(png);\n\n  png_read_image(png, row_ptrs);\n  png_read_end(png, NULL);\n  png_destroy_read_struct(&png, &info, NULL);\n\n  free(row_ptrs);\n  dest->width = w;\n  dest->height = h;\n  return IMAGE_OK;\n\nerror:\n  png_destroy_read_struct(&png, info ? &info : NULL, NULL);\n\n  free(dest->data);\n  free(row_ptrs);\n  return ret;\n}\n\n#endif /* CONFIG_PNG */\n\n\n/**\n * GIF loader.\n */\n\nstatic enum image_error convert_gif_error(enum gif_error err)\n{\n  switch(err)\n  {\n    case GIF_OK:        return IMAGE_OK;\n    case GIF_IO:        return IMAGE_ERROR_IO;\n    case GIF_MEM:       return IMAGE_ERROR_MEM;\n    case GIF_INVALID:   return IMAGE_ERROR_GIF_INVALID;\n    case GIF_SIGNATURE: return IMAGE_ERROR_GIF_SIGNATURE;\n  }\n  return IMAGE_ERROR_UNKNOWN;\n}\n\nstatic enum image_error load_gif(imageinfo *s)\n{\n  struct image_file *dest = s->out;\n  struct gif_info gif;\n  struct gif_rgba *pixels;\n  enum gif_error ret;\n  unsigned width;\n  unsigned height;\n\n  debug(\"Image type: GIF\\n\");\n\n  ret = gif_read(&gif, s->in, s->readfn, true);\n  if(ret != GIF_OK)\n  {\n    debug(\"read failed: %s\\n\", gif_error_string(ret));\n    return convert_gif_error(ret);\n  }\n\n  gif_composite_size(&width, &height, &gif);\n\n  if(!image_constraint(width, height))\n  {\n    gif_close(&gif);\n    return IMAGE_ERROR_CONSTRAINT;\n  }\n\n  ret = gif_composite(&pixels, &gif, malloc);\n  if(ret == GIF_OK)\n  {\n    dest->data = (struct rgba_color *)pixels;\n    dest->width = width;\n    dest->height = height;\n  }\n  else\n    debug(\"composite failed: %s\\n\", gif_error_string(ret));\n\n  gif_close(&gif);\n  return convert_gif_error(ret);\n}\n\n\n/**\n * BMP loader.\n */\n\n#define BMP_HEADER_LEN  14\n#define BMP_DIB_MAX_LEN 128\n\nenum dib_type\n{\n  DIB_UNKNOWN,\n  BITMAPCOREHEADER,     // 12\n  OS22XBITMAPHEADER,    // 64 or 16\n  BITMAPINFOHEADER,     // 40\n  BITMAPV2INFOHEADER,   // 52\n  BITMAPV3INFOHEADER,   // 56\n  BITMAPV4HEADER,       // 108\n  BITMAPV5HEADER,       // 124\n};\n\nenum dib_compression\n{\n  BI_RGB            = 0,  // None\n  BI_RLE8           = 1,  // 8bpp RLE\n  BI_RLE4           = 2,  // 4bpp RLE\n  BI_BITFIELDS      = 3,  // RGB (v2) or RGBA (v3+) bitfield masks\n  BI_OS2_HUFFMAN    = 3,  // OS22XBITMAPHEADER has this, not BI_BITFIELDS.\n  BI_JPEG           = 4,  // JPEG\n  BI_OS2_RLE24      = 4,  // OS22XBITMAPHEADER has this, not BI_JPEG.\n  BI_PNG            = 5,  // PNG\n  BI_ALPHABITFIELDS = 6,  // RGBA bitfield masks (Windows CE 5.0)\n  BI_CMYK           = 11,\n  BI_BMYKRLE8       = 12,\n  BI_CMYKRLE4       = 13,\n  BI_MAX\n};\n\nstruct bmp_bitfield_mask\n{\n  uint32_t mask;\n  unsigned shift;\n  uint64_t maxscale;\n};\n\n// NOTE: not packed, don't fread directly into this.\nstruct bmp_header\n{\n  uint16_t magic;\n  uint32_t filesize;\n  uint16_t reserved0;\n  uint16_t reserved1;\n  uint32_t pixarray;\n\n  struct rgba_color *color_table;\n  uint32_t streamidx;\n  uint32_t rowpadding;\n  enum dib_type type;\n\n  // Core DIB header.\n  uint32_t dibsize;\n  int32_t width;\n  int32_t height;\n  uint16_t planes;\n  uint16_t bpp;\n\n  // BITMAPINFOHEADER fields.\n  uint32_t compr_method;\n  uint32_t image_size;\n  uint32_t horiz_res;\n  uint32_t vert_res;\n  uint32_t palette_size;\n  uint32_t important;\n\n  // BITMAPV2INFOHEADER and BITMAPV3INFOHEADER fields.\n  struct bmp_bitfield_mask r_mask;\n  struct bmp_bitfield_mask g_mask;\n  struct bmp_bitfield_mask b_mask;\n  struct bmp_bitfield_mask a_mask;\n};\n\nstatic enum dib_type bmp_get_dib_type(uint32_t length)\n{\n  switch(length)\n  {\n    case 12:  return BITMAPCOREHEADER;\n    case 64:  return OS22XBITMAPHEADER;\n    case 16:  return OS22XBITMAPHEADER;\n    case 40:  return BITMAPINFOHEADER;\n    case 52:  return BITMAPV2INFOHEADER;\n    case 56:  return BITMAPV3INFOHEADER;\n    case 108: return BITMAPV4HEADER;\n    case 124: return BITMAPV5HEADER;\n    default:  return DIB_UNKNOWN;\n  }\n}\n\nstatic enum image_error bmp_init_bitfields_mask(const struct bmp_header *bmp,\n struct bmp_bitfield_mask *m, const char *name)\n{\n  unsigned shift = 0;\n  unsigned maxval;\n\n  if(m->mask != 0)\n  {\n    uint32_t mask = m->mask;\n\n    // Skip leading bits and count the shift.\n    while(~mask & 1)\n    {\n      mask >>= 1;\n      shift++;\n    }\n    // Mask should be continuous.\n    while(mask & 1)\n      mask >>= 1;\n\n    if(mask != 0)\n    {\n      debug(\"non-continuous bitfield.%s: %08x\\n\", name, (unsigned)m->mask);\n      return IMAGE_ERROR_BMP_BAD_BITFIELDS_MASK_CONTINUITY;\n    }\n  }\n\n  if(bmp->bpp == 16 && m->mask & 0xffff0000u)\n  {\n    debug(\"nonsense bitfield.%s for 16bpp: %08x\\n\", name, (unsigned)m->mask);\n    return IMAGE_ERROR_BMP_BAD_BITFIELDS_MASK_RANGE;\n  }\n\n  maxval = m->mask >> shift;\n\n  m->shift = shift;\n  m->maxscale = maxval ? ((uint64_t)255 << 32u) / maxval : 0;\n\n  debug(\"bitfield.%s: (%08xh >> %2u) *%3x.%08xh\\n\", name,\n   (unsigned)m->mask, m->shift, (unsigned)(m->maxscale >> 32), (unsigned)m->maxscale);\n\n  return IMAGE_OK;\n}\n\nstatic enum image_error bmp_init_bitfields(struct bmp_header *bmp)\n{\n  enum image_error ret;\n\n  ret = bmp_init_bitfields_mask(bmp, &(bmp->r_mask), \"r\");\n  if(ret)\n    return ret;\n\n  ret = bmp_init_bitfields_mask(bmp, &(bmp->g_mask), \"g\");\n  if(ret)\n    return ret;\n\n  ret = bmp_init_bitfields_mask(bmp, &(bmp->b_mask), \"b\");\n  if(ret)\n    return ret;\n\n  ret = bmp_init_bitfields_mask(bmp, &(bmp->a_mask), \"a\");\n  if(ret)\n    return ret;\n\n  if((bmp->r_mask.mask & bmp->g_mask.mask) ||\n     (bmp->r_mask.mask & bmp->b_mask.mask) ||\n     (bmp->r_mask.mask & bmp->a_mask.mask) ||\n     (bmp->g_mask.mask & bmp->b_mask.mask) ||\n     (bmp->g_mask.mask & bmp->a_mask.mask) ||\n     (bmp->b_mask.mask & bmp->a_mask.mask))\n  {\n    debug(\"overlapping bitfield masks: %08x %08x %08x %08x\\n\",\n     (unsigned)bmp->r_mask.mask, (unsigned)bmp->g_mask.mask,\n     (unsigned)bmp->b_mask.mask, (unsigned)bmp->a_mask.mask);\n    return IMAGE_ERROR_BMP_BAD_BITFIELDS_MASK_OVERLAP;\n  }\n  return IMAGE_OK;\n}\n\nstatic inline uint32_t bmp_decode_bitfields_component(\n const struct bmp_bitfield_mask *m, uint32_t value)\n{\n  return (((value & m->mask) >> m->shift) * m->maxscale + ROUND_32) >> 32u;\n}\n\nstatic inline void bmp_decode_bitfields(const struct bmp_header *bmp,\n struct rgba_color *out, uint32_t value, image_bool alpha)\n{\n  out->r = bmp_decode_bitfields_component(&(bmp->r_mask), value);\n  out->g = bmp_decode_bitfields_component(&(bmp->g_mask), value);\n  out->b = bmp_decode_bitfields_component(&(bmp->b_mask), value);\n\n  if(alpha)\n    out->a = bmp_decode_bitfields_component(&(bmp->a_mask), value);\n  else\n    out->a = 255;\n}\n\nstatic struct rgba_color *bmp_read_color_table(struct bmp_header *bmp, imageinfo *s)\n{\n  uint32_t palette_alloc = (1 << bmp->bpp);\n  uint32_t palette_size = bmp->palette_size;\n  uint32_t i;\n  size_t col_len;\n  uint8_t d[4];\n\n  struct rgba_color *color_table = (struct rgba_color *)calloc(palette_alloc, sizeof(struct rgba_color));\n  struct rgba_color *pos = color_table;\n  if(!color_table)\n    return NULL;\n\n  // Ignore values after the allocated palette if they exist...\n  if(palette_size > palette_alloc)\n    palette_size = palette_alloc;\n\n  // OS/2 1.x uses RGB, everything else uses XRGB.\n  // (Bitfields may change this, but currently aren't supported.)\n  if(bmp->type == BITMAPCOREHEADER)\n  {\n    col_len = 3;\n  }\n  else\n    col_len = 4;\n\n  for(i = 0; i < palette_size; i++)\n  {\n    if(s->readfn(d, col_len, s->in) < col_len)\n      break;\n\n    pos->r = d[2];\n    pos->g = d[1];\n    pos->b = d[0];\n    pos->a = 255;\n    pos++;\n  }\n  bmp->streamidx += col_len * palette_size;\n\n  if(i < palette_size)\n  {\n    free(color_table);\n    return NULL;\n  }\n\n  return color_table;\n}\n\nstatic enum image_error bmp_pixarray_u1(const struct bmp_header *bmp,\n imageinfo * RESTRICT s)\n{\n  const struct rgba_color *color_table = bmp->color_table;\n  struct image_file *dest = s->out;\n  uint8_t d[4];\n  ssize_t y;\n  ssize_t x;\n  int i;\n\n  for(y = bmp->height - 1; y >= 0; y--)\n  {\n    struct rgba_color *pos = &(dest->data[y * bmp->width]);\n\n    for(x = 0; x < bmp->width;)\n    {\n      char v;\n      int value;\n      if(s->readfn(&v, 1, s->in) < 1)\n        return IMAGE_ERROR_BMP_BAD_1BPP;\n\n      for(value = v, i = 7; i >= 0 && x < bmp->width; i--, x++)\n      {\n        int idx = (value >> i) & 0x01;\n        *(pos++) = color_table[idx];\n      }\n    }\n\n    if(bmp->rowpadding && s->readfn(d, bmp->rowpadding, s->in) < bmp->rowpadding)\n      return IMAGE_ERROR_BMP_BAD_1BPP;\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error bmp_pixarray_u2(const struct bmp_header *bmp,\n imageinfo * RESTRICT s)\n{\n  const struct rgba_color *color_table = bmp->color_table;\n  struct image_file *dest = s->out;\n  uint8_t d[4];\n  ssize_t y;\n  ssize_t x;\n  int i;\n\n  for(y = bmp->height - 1; y >= 0; y--)\n  {\n    struct rgba_color *pos = &(dest->data[y * bmp->width]);\n\n    for(x = 0; x < bmp->width;)\n    {\n      char v;\n      int value;\n      if(s->readfn(&v, 1, s->in) < 1)\n        return IMAGE_ERROR_BMP_BAD_2BPP;\n\n      for(value = v, i = 6; i >= 0 && x < bmp->width; i -= 2, x++)\n      {\n        int idx = (value >> i) & 0x03;\n        *(pos++) = color_table[idx];\n      }\n    }\n\n    if(bmp->rowpadding && s->readfn(d, bmp->rowpadding, s->in) < bmp->rowpadding)\n      return IMAGE_ERROR_BMP_BAD_2BPP;\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error bmp_pixarray_u4(const struct bmp_header *bmp,\n imageinfo * RESTRICT s)\n{\n  const struct rgba_color *color_table = bmp->color_table;\n  struct image_file *dest = s->out;\n  uint8_t d[4];\n  ssize_t y;\n  ssize_t x;\n\n  for(y = bmp->height - 1; y >= 0; y--)\n  {\n    struct rgba_color *pos = &(dest->data[y * bmp->width]);\n\n    for(x = 0; x < bmp->width; x += 2)\n    {\n      char value;\n      if(s->readfn(&value, 1, s->in) < 1)\n        return IMAGE_ERROR_BMP_BAD_4BPP;\n\n      d[0] = (value >> 0) & 0x0f;\n      d[1] = (value >> 4) & 0x0f;\n\n      *(pos++) = color_table[d[0]];\n      if(x + 1 < bmp->width)\n        *(pos++) = color_table[d[1]];\n    }\n\n    if(bmp->rowpadding && s->readfn(d, bmp->rowpadding, s->in) < bmp->rowpadding)\n      return IMAGE_ERROR_BMP_BAD_4BPP;\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error bmp_pixarray_u8(const struct bmp_header *bmp,\n imageinfo * RESTRICT s)\n{\n  const struct rgba_color *color_table = bmp->color_table;\n  struct image_file *dest = s->out;\n  uint8_t d[4];\n  ssize_t y;\n  ssize_t x;\n\n  for(y = bmp->height - 1; y >= 0; y--)\n  {\n    struct rgba_color *pos = &(dest->data[y * bmp->width]);\n\n    for(x = 0; x < bmp->width; x++)\n    {\n      uint8_t value;\n      if(s->readfn(&value, 1, s->in) < 1)\n        return IMAGE_ERROR_BMP_BAD_8BPP;\n\n      *(pos++) = color_table[value];\n    }\n\n    if(bmp->rowpadding && s->readfn(d, bmp->rowpadding, s->in) < bmp->rowpadding)\n      return IMAGE_ERROR_BMP_BAD_8BPP;\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error bmp_pixarray_u16(const struct bmp_header *bmp,\n imageinfo * RESTRICT s, const image_bool bitfields)\n{\n  struct image_file *dest = s->out;\n  const image_bool alpha = !!bmp->a_mask.mask;\n  uint8_t d[4];\n  ssize_t y;\n  ssize_t x;\n\n  for(y = bmp->height - 1; y >= 0; y--)\n  {\n    struct rgba_color *pos = &(dest->data[y * bmp->width]);\n\n    for(x = 0; x < bmp->width; x++)\n    {\n      uint16_t value;\n      if(s->readfn(d, 2, s->in) < 2)\n        return IMAGE_ERROR_BMP_BAD_16BPP;\n\n      value = read16le(d);\n\n      if(bitfields)\n      {\n        bmp_decode_bitfields(bmp, pos++, value, alpha);\n        continue;\n      }\n\n      pos->r = (((value >> 10) & 0x1f) * 255u + 16) / 31u;\n      pos->g = (((value >>  5) & 0x1f) * 255u + 16) / 31u;\n      pos->b = (((value >>  0) & 0x1f) * 255u + 16) / 31u;\n      pos->a = 255;\n      pos++;\n    }\n\n    if(bmp->rowpadding && s->readfn(d, bmp->rowpadding, s->in) < bmp->rowpadding)\n      return IMAGE_ERROR_BMP_BAD_16BPP;\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error bmp_pixarray_u24(const struct bmp_header *bmp,\n imageinfo * RESTRICT s)\n{\n  struct image_file *dest = s->out;\n  uint8_t d[4];\n  ssize_t y;\n  ssize_t x;\n\n  for(y = bmp->height - 1; y >= 0; y--)\n  {\n    struct rgba_color *pos = &(dest->data[y * bmp->width]);\n\n    for(x = 0; x < bmp->width; x++)\n    {\n      if(s->readfn(d, 3, s->in) < 3)\n        return IMAGE_ERROR_BMP_BAD_24BPP;\n\n      pos->r = d[2];\n      pos->g = d[1];\n      pos->b = d[0];\n      pos->a = 255;\n      pos++;\n    }\n\n    if(bmp->rowpadding && s->readfn(d, bmp->rowpadding, s->in) < bmp->rowpadding)\n      return IMAGE_ERROR_BMP_BAD_24BPP;\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error bmp_pixarray_u32(const struct bmp_header *bmp,\n imageinfo * RESTRICT s, const image_bool bitfields)\n{\n  struct image_file *dest = s->out;\n  const image_bool alpha = !!bmp->a_mask.mask;\n  uint8_t d[4];\n  ssize_t y;\n  ssize_t x;\n\n  for(y = bmp->height - 1; y >= 0; y--)\n  {\n    struct rgba_color *pos = &(dest->data[y * bmp->width]);\n\n    for(x = 0; x < bmp->width; x++)\n    {\n      if(s->readfn(d, 4, s->in) < 4)\n        return IMAGE_ERROR_BMP_BAD_32BPP;\n\n      if(bitfields)\n      {\n        bmp_decode_bitfields(bmp, pos++, read32le(d), alpha);\n        continue;\n      }\n\n      pos->r = d[2];\n      pos->g = d[1];\n      pos->b = d[0];\n      pos->a = 255;\n      pos++;\n    }\n    // 32bpp is always aligned, no padding required.\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error bmp_read_pixarray_uncompressed(const struct bmp_header *bmp,\n imageinfo * RESTRICT s)\n{\n  switch(bmp->bpp)\n  {\n    case 1:   return bmp_pixarray_u1(bmp, s);\n    case 2:   return bmp_pixarray_u2(bmp, s);\n    case 4:   return bmp_pixarray_u4(bmp, s);\n    case 8:   return bmp_pixarray_u8(bmp, s);\n    case 16:  return bmp_pixarray_u16(bmp, s, IMAGE_FALSE);\n    case 24:  return bmp_pixarray_u24(bmp, s);\n    case 32:  return bmp_pixarray_u32(bmp, s, IMAGE_FALSE);\n  }\n  return IMAGE_ERROR_BMP_UNSUPPORTED_BPP;\n}\n\nstatic enum image_error bmp_read_pixarray_bitfields(const struct bmp_header *bmp,\n imageinfo * RESTRICT s)\n{\n  switch(bmp->bpp)\n  {\n    case 16: return bmp_pixarray_u16(bmp, s, IMAGE_TRUE);\n    case 32: return bmp_pixarray_u32(bmp, s, IMAGE_TRUE);\n  }\n  return IMAGE_ERROR_BMP_UNSUPPORTED_BPP;\n}\n\n/**\n * Microsoft RLE8 and RLE4 compression.\n */\nstatic enum image_error bmp_read_pixarray_rle(const struct bmp_header *bmp,\n imageinfo * RESTRICT s)\n{\n  const struct rgba_color *color_table = bmp->color_table;\n  struct image_file *dest = s->out;\n  ssize_t x = 0;\n  ssize_t y = bmp->height - 1;\n  ssize_t i;\n  uint8_t d[2];\n\n  if(bmp->bpp != 4 && bmp->bpp != 8) // Should never happen...\n    return IMAGE_ERROR_UNKNOWN;\n\n  while(x < bmp->width && y >= 0)\n  {\n    struct rgba_color *pos = &(dest->data[y * bmp->width + x]);\n    //debug(\"RLE%u now at: %zd %zd\\n\", bmp->bpp, x, y);\n\n    while(true)\n    {\n      if(s->readfn(d, 2, s->in) < 2)\n        return IMAGE_ERROR_BMP_BAD_RLE;\n\n      if(!d[0])\n      {\n        if(d[1] == 0)\n        {\n          // End of line.\n          //debug(\"RLE%u EOL  : %3u %3u\\n\", bmp->bpp, d[0], d[1]);\n          x = 0;\n          y--;\n          break;\n        }\n        else\n\n        if(d[1] == 1)\n        {\n          // End of file.\n          //debug(\"RLE%u EOF  : %3u %3u\\n\", bmp->bpp, d[0], d[1]);\n          return IMAGE_OK;\n        }\n        else\n\n        if(d[1] == 2)\n        {\n          // Position delta.\n          // Documentation refers to unsigned y moving the current position\n          // \"down\", but since this is still OS/2 line order, it means \"up\".\n          if(s->readfn(d, 2, s->in) < 2)\n            return IMAGE_ERROR_BMP_BAD_RLE;\n\n          //debug(\"RLE%u delta: %3u %3u\\n\", bmp->bpp, d[0], d[1]);\n          x += d[0];\n          y -= d[1];\n          break;\n        }\n        else\n        {\n          // Absolute mode.\n          uint8_t idx;\n          //debug(\"RLE%u abs. : %3u %3u\\n\", bmp->bpp, d[0], d[1]);\n          if(bmp->bpp == 8)\n          {\n            for(i = 0; i < d[1] && x < bmp->width; i++, x++)\n            {\n              if(s->readfn(&idx, 1, s->in) < 1)\n                return IMAGE_ERROR_BMP_BAD_RLE;\n\n              *(pos++) = color_table[idx];\n            }\n            // Absolute mode runs are padded to word boundaries.\n            if(d[1] & 1)\n              s->readfn(&idx, 1, s->in);\n          }\n          else // bpp == 4\n          {\n            for(i = 0; i < d[1] && x < bmp->width;)\n            {\n              if(s->readfn(&idx, 1, s->in) < 1)\n                return IMAGE_ERROR_BMP_BAD_RLE;\n\n              *(pos++) = color_table[(idx >> 4) & 0x0f];\n              i++, x++;\n\n              if(i < d[1] && x < bmp->width)\n              {\n                *(pos++) = color_table[(idx >> 0) & 0x0f];\n                i++, x++;\n              }\n            }\n            // Absolute mode runs are padded to word boundaries.\n            if((d[1] & 0x03) == 1 || (d[1] & 0x03) == 2)\n              s->readfn(&idx, 1, s->in);\n          }\n\n          if(i < d[1])\n          {\n            debug(\"RLE%u reached x=bmp->width during absolute run (should this be valid)?\\n\",\n             bmp->bpp);\n            return IMAGE_ERROR_BMP_BAD_RLE;\n          }\n        }\n      }\n      else\n      {\n        // Run.\n        //debug(\"RLE%u run  : %3u %02xh\\n\", bmp->bpp, d[0], d[1]);\n        if(bmp->bpp == 8)\n        {\n          for(i = 0; i < d[0] && x < bmp->width; i++, x++)\n            *(pos++) = color_table[d[1]];\n        }\n        else // bpp == 4\n        {\n          uint8_t a = (d[1] >> 4) & 0x0f;\n          uint8_t b = (d[1] >> 0) & 0x0f;\n          for(i = 0; i < d[0] && x < bmp->width;)\n          {\n            *(pos++) = color_table[a];\n            i++, x++;\n\n            if(i < d[0] && x < bmp->width)\n            {\n              *(pos++) = color_table[b];\n              i++, x++;\n            }\n          }\n        }\n\n        if(i < d[0])\n        {\n          debug(\"RLE%u reached x=bmp->width during encoded run (should this be valid)?\\n\",\n           bmp->bpp);\n          return IMAGE_ERROR_BMP_BAD_RLE;\n        }\n      }\n    }\n  }\n\n  // Check for EOF following EOL.\n  if(x == 0 && y == -1)\n  {\n    if(s->readfn(d, 2, s->in) < 2 || d[0] != 0 || d[1] != 1)\n      debug(\"RLE%u missing EOF!\\n\", bmp->bpp);\n\n    return IMAGE_OK;\n  }\n\n  debug(\"RLE%u out of bounds\\n\", bmp->bpp);\n  return IMAGE_ERROR_BMP_BAD_RLE;\n}\n\nstatic enum image_error load_bmp(imageinfo *s)\n{\n  struct image_file *dest = s->out;\n  uint8_t buf[BMP_DIB_MAX_LEN] = { 'B', 'M' };\n  struct bmp_header bmp;\n  enum image_error ret;\n\n  debug(\"Image type: BMP\\n\");\n\n  // Note: already read magic bytes.\n  if(s->readfn(buf + 2, BMP_HEADER_LEN - 2, s->in) < BMP_HEADER_LEN - 2)\n    return IMAGE_ERROR_IO;\n\n  memset(&bmp, 0, sizeof(bmp));\n\n  bmp.filesize  = read32le(buf + 2);\n  bmp.reserved0 = read16le(buf + 6);\n  bmp.reserved0 = read16le(buf + 8);\n  bmp.pixarray  = read32le(buf + 10);\n  bmp.streamidx = 14;\n\n  // DIB header size.\n  if(s->readfn(buf, 4, s->in) < 4)\n    return IMAGE_ERROR_IO;\n\n  bmp.dibsize = read32le(buf + 0);\n  bmp.type = bmp_get_dib_type(bmp.dibsize);\n\n  if(bmp.type == DIB_UNKNOWN)\n  {\n    debug(\"Unknown BMP DIB header size %zu!\\n\", (size_t)bmp.dibsize);\n    return IMAGE_ERROR_BMP_UNSUPPORTED_DIB;\n  }\n\n  if(s->readfn(buf + 4, bmp.dibsize - 4, s->in) < bmp.dibsize - 4)\n    return IMAGE_ERROR_IO;\n\n  if(bmp.type == BITMAPCOREHEADER)\n  {\n    debug(\"BITMAPCOREHEADER\\n\");\n    bmp.width           = read16le(buf + 4);\n    bmp.height          = read16le(buf + 6);\n    bmp.planes          = read16le(buf + 8);\n    bmp.bpp             = read16le(buf + 10);\n  }\n  else\n  {\n    debug(\"BITMAPINFOHEADER or compatible (size %u)\\n\", (unsigned)bmp.dibsize);\n    bmp.width           = (int32_t)read32le(buf + 4);\n    bmp.height          = (int32_t)read32le(buf + 8);\n    bmp.planes          = read16le(buf + 12);\n    bmp.bpp             = read16le(buf + 14);\n\n    if(bmp.dibsize > 16)\n    {\n      bmp.compr_method  = read32le(buf + 16);\n      bmp.image_size    = read32le(buf + 20);\n      bmp.horiz_res     = read32le(buf + 24);\n      bmp.vert_res      = read32le(buf + 28);\n      bmp.palette_size  = read32le(buf + 32);\n      bmp.important     = read32le(buf + 36);\n    }\n\n    if(bmp.dibsize >= 52 && bmp.type >= BITMAPV2INFOHEADER)\n    {\n      bmp.r_mask.mask   = read32le(buf + 40);\n      bmp.g_mask.mask   = read32le(buf + 44);\n      bmp.b_mask.mask   = read32le(buf + 48);\n    }\n\n    if(bmp.dibsize >= 56 && bmp.type >= BITMAPV3INFOHEADER)\n    {\n      bmp.a_mask.mask   = read32le(buf + 52);\n    }\n  }\n  bmp.streamidx += bmp.dibsize;\n\n  if(bmp.width < 0 || bmp.height < 0)\n  {\n    debug(\"invalid BMP dimensions %zd x %zd\", (ssize_t)bmp.width, (ssize_t)bmp.height);\n    return IMAGE_ERROR_BMP_BAD_SIZE;\n  }\n\n  if(bmp.planes != 1)\n  {\n    debug(\"invalid BMP planes %u\\n\", bmp.planes);\n    return IMAGE_ERROR_BMP_UNSUPPORTED_PLANES;\n  }\n\n  /* This compression method is ambiguous with the supported BI_BITFIELDS. */\n  if(bmp.type == OS22XBITMAPHEADER && bmp.compr_method == BI_OS2_HUFFMAN)\n  {\n    debug(\"unsupported BMP compression type OS/2 Huffman 1D\\n\");\n    return IMAGE_ERROR_BMP_UNSUPPORTED_COMPRESSION;\n  }\n\n  if(bmp.compr_method != BI_RGB &&\n   bmp.compr_method != BI_RLE8 && bmp.compr_method != BI_RLE4 &&\n   bmp.compr_method != BI_BITFIELDS && bmp.compr_method != BI_ALPHABITFIELDS)\n  {\n    debug(\"unsupported BMP compression type %zu\\n\", (size_t)bmp.compr_method);\n    return IMAGE_ERROR_BMP_UNSUPPORTED_COMPRESSION;\n  }\n  debug(\"Compression: %zu\\n\", (size_t)bmp.compr_method);\n\n  if(bmp.bpp != 1 && bmp.bpp != 2 && bmp.bpp != 4 && bmp.bpp != 8 &&\n   bmp.bpp != 16 && bmp.bpp != 24 && bmp.bpp != 32)\n  {\n    debug(\"unsupported BMP BPP %u\\n\", bmp.bpp);\n    return IMAGE_ERROR_BMP_UNSUPPORTED_BPP;\n  }\n  debug(\"BPP: %u\\n\", bmp.bpp);\n\n  if(bmp.compr_method == BI_RLE8 && bmp.bpp != 8)\n  {\n    debug(\"unsupported BPP %u for BI_RLE8\\n\", bmp.bpp);\n    return IMAGE_ERROR_BMP_UNSUPPORTED_BPP_RLE8;\n  }\n\n  if(bmp.compr_method == BI_RLE4 && bmp.bpp != 4)\n  {\n    debug(\"unsupported BPP %u for BI_RLE4\\n\", bmp.bpp);\n    return IMAGE_ERROR_BMP_UNSUPPORTED_BPP_RLE4;\n  }\n\n  /* Init BI_BITFIELDS data if applicable. The relevant DIB fields must have\n   * been loaded and the BPP must be 16 or 32.\n   */\n  if(bmp.compr_method == BI_BITFIELDS || bmp.compr_method == BI_ALPHABITFIELDS)\n  {\n    if(bmp.type < BITMAPV2INFOHEADER ||\n     (bmp.type < BITMAPV3INFOHEADER && bmp.compr_method == BI_ALPHABITFIELDS))\n    {\n      debug(\"unsupported DIB size %d for BI_BITFIELDS %u\", bmp.type,\n       (unsigned)bmp.compr_method);\n      return IMAGE_ERROR_BMP_BAD_BITFIELDS_DIB;\n    }\n\n    if(bmp.bpp != 16 && bmp.bpp != 32)\n    {\n      debug(\"unsupported BPP %u for BI_BITFIELDS\\n\", bmp.bpp);\n      return IMAGE_ERROR_BMP_UNSUPPORTED_BPP_BITFIELDS;\n    }\n\n    ret = bmp_init_bitfields(&bmp);\n    if(ret)\n    {\n      debug(\"failed to init BMP bitfields masks\\n\");\n      return ret;\n    }\n  }\n\n  if(bmp.bpp <= 8)\n  {\n    // If palette_size == 0, use 2^bpp.\n    if(!bmp.palette_size)\n    {\n      bmp.palette_size = (1 << bmp.bpp);\n      debug(\"Palette size: %zu (default)\\n\", (size_t)bmp.palette_size);\n    }\n    else\n      debug(\"Palette size: %zu\\n\", (size_t)bmp.palette_size);\n\n    // Read color table.\n    bmp.color_table = bmp_read_color_table(&bmp, s);\n    if(!bmp.color_table)\n    {\n      debug(\"failed to read BMP color table\\n\");\n      return IMAGE_ERROR_BMP_BAD_COLOR_TABLE;\n    }\n  }\n\n  // Padding: round up row size (bpp*width) to a whole number of bytes. Padding\n  // size is 0 to 3 extra bytes required to pad that value to a multiple of 4.\n  bmp.rowpadding = (4 - (((bmp.bpp * (uint64_t)bmp.width + 7) / 8))) & 3;\n  debug(\"Row padding: %zu\\n\", (size_t)bmp.rowpadding);\n\n  if(bmp.streamidx < bmp.pixarray)\n    debug(\"Bytes to skip for pixarray: %zu\\n\", (size_t)bmp.pixarray - bmp.streamidx);\n\n  // Assume non-seeking stream, skip everything prior to pixarray with fread.\n  while(bmp.streamidx < bmp.pixarray)\n  {\n    size_t r = IMAGE_MIN(bmp.pixarray - bmp.streamidx, sizeof(buf));\n    bmp.streamidx += r;\n\n    if(s->readfn(buf, 1, s->in) < 1)\n    {\n      ret = IMAGE_ERROR_IO;\n      goto err_free;\n    }\n  }\n\n  ret = image_init(bmp.width, bmp.height, dest);\n  if(ret)\n    goto err_free;\n\n  switch(bmp.compr_method)\n  {\n    case BI_RGB:\n      ret = bmp_read_pixarray_uncompressed(&bmp, s);\n      break;\n\n    // ImageMagick tends to automatically emit this for 8bpp BMPs.\n    // GIMP can emit both types of RLE, though this is turned off by default.\n    case BI_RLE8:\n    case BI_RLE4:\n      ret = bmp_read_pixarray_rle(&bmp, s);\n      break;\n\n    case BI_BITFIELDS:\n    case BI_ALPHABITFIELDS:\n      ret = bmp_read_pixarray_bitfields(&bmp, s);\n      break;\n\n    default:\n      // Shouldn't happen--method is filtered above!\n      ret = IMAGE_ERROR_UNKNOWN;\n      break;\n  }\n\n  if(ret)\n    goto err_free_image;\n\n  free(bmp.color_table);\n  return IMAGE_OK;\n\nerr_free_image:\n  debug(\"error reading pixarray\\n\");\n  image_free(dest);\nerr_free:\n  free(bmp.color_table);\n  return ret;\n}\n\n\n/**\n * Truevision TGA (Targa) loader.\n */\n\nenum tga_colormap_type\n{\n  TGA_NO_COLORMAP,\n  TGA_HAS_COLORMAP,\n  TGA_MAX_COLORMAP_TYPE\n};\n\nenum tga_image_type\n{\n  TGA_TYPE_NO_IMAGE       = 0,\n  TGA_TYPE_INDEXED        = 1,\n  TGA_TYPE_TRUECOLOR      = 2,\n  TGA_TYPE_GREYSCALE      = 3,\n  TGA_TYPE_INDEXED_RLE    = 9,\n  TGA_TYPE_TRUECOLOR_RLE  = 10,\n  TGA_TYPE_GREYSCALE_RLE  = 11,\n  TGA_MAX_IMAGE_TYPE\n};\n\nstruct tga_header\n{\n  uint8_t   image_id_length;\n  uint8_t   map_type;\n  uint8_t   image_type;\n  uint16_t  map_first_index;\n  uint16_t  map_length;\n  uint8_t   map_depth;\n  uint16_t  x_origin;\n  uint16_t  y_origin;\n  uint16_t  width;\n  uint16_t  height;\n  uint8_t   depth;\n  uint8_t   descriptor;\n  // Derived\n  uint8_t   alpha_depth;\n  uint8_t   interleaving;\n  boolean   left_to_right;\n  boolean   top_to_bottom;\n  boolean   alpha;\n  unsigned  bytes_per_pixel;\n  unsigned  bytes_per_color;\n  int       offset_in_image;\n  int       offset_in_row;\n  int       row_inc;\n  int       pix_inc;\n\n  struct rgba_color *map;\n};\n\nstruct tga_cursor\n{\n  struct rgba_color *pixel_row;\n  struct rgba_color *pixel;\n  struct rgba_color *pixel_stop;\n};\n\nstatic void tga_cursor_init(struct tga_cursor *out, const struct tga_header *tga,\n struct rgba_color *dest)\n{\n  out->pixel_row = dest + tga->offset_in_image;\n  out->pixel = out->pixel_row + tga->offset_in_row;\n  out->pixel_stop = out->pixel + (tga->pix_inc * tga->width);\n}\n\nstatic void tga_cursor_inc(struct tga_cursor *out, const struct tga_header *tga)\n{\n  out->pixel += tga->pix_inc;\n  if(out->pixel == out->pixel_stop)\n  {\n    out->pixel_row += tga->row_inc;\n    out->pixel_stop += tga->row_inc;\n    out->pixel = out->pixel_row + tga->offset_in_row;\n  }\n}\n\nstatic void tga_cursor_rle(struct tga_cursor *out, const struct tga_header *tga,\n struct rgba_color color, size_t num)\n{\n  for(; num > 0; num--)\n  {\n    *(out->pixel) = color;\n    tga_cursor_inc(out, tga);\n  }\n}\n\nstatic int tga_bytes_per_pixel(uint8_t depth, uint8_t alpha_depth)\n{\n  if(depth == 15 && alpha_depth == 0)\n    return 2;\n\n  if(depth == 16 && (alpha_depth == 0 || alpha_depth == 1))\n    return 2;\n\n  if(depth == 24 && alpha_depth == 0)\n    return 3;\n\n  if(depth == 32 && (alpha_depth == 0 || alpha_depth == 8))\n    return 4;\n\n  return 0;\n}\n\ntypedef struct rgba_color (*tga_decode_fn)(uint8_t *buf,\n const struct tga_header *tga);\n\nstatic struct rgba_color tga_decode_index8(uint8_t *buf,\n const struct tga_header *tga)\n{\n  struct rgba_color dummy = { 0, 0, 0, 255 };\n  if(*buf < tga->map_length)\n    return tga->map[*buf];\n\n  return dummy;\n}\n\nstatic struct rgba_color tga_decode_index16(uint8_t *buf,\n const struct tga_header *tga)\n{\n  struct rgba_color dummy = { 0, 0, 0, 255 };\n  uint16_t index = read16le(buf);\n  if(index < tga->map_length)\n    return tga->map[index];\n\n  return dummy;\n}\n\nstatic struct rgba_color tga_decode_greyscale(uint8_t *buf,\n const struct tga_header *tga)\n{\n  struct rgba_color ret = { *buf, *buf, *buf, 255 };\n  return ret;\n}\n\nstatic struct rgba_color tga_decode_color16(uint8_t *buf,\n const struct tga_header *tga)\n{\n  struct rgba_color ret;\n  uint16_t value = read16le(buf);\n  ret.r = (((value >> 10) & 0x1f) * 255u + 16) / 31u;\n  ret.g = (((value >>  5) & 0x1f) * 255u + 16) / 31u;\n  ret.b = (((value >>  0) & 0x1f) * 255u + 16) / 31u;\n  ret.a = !tga->alpha || (value >> 15) ? 255 : 0;\n  return ret;\n}\n\nstatic struct rgba_color tga_decode_color24(uint8_t *buf,\n const struct tga_header *tga)\n{\n  struct rgba_color ret = { buf[2], buf[1], buf[0], 255 };\n  return ret;\n}\n\nstatic struct rgba_color tga_decode_color32(uint8_t *buf,\n const struct tga_header *tga)\n{\n  struct rgba_color ret = { buf[2], buf[1], buf[0], buf[3] };\n  return ret;\n}\n\nstatic enum image_error tga_load_colormap(struct tga_header *tga, imageinfo *s)\n{\n  const int depth = tga->map_depth;\n  uint8_t buf[4];\n  size_t i;\n\n  tga->map = (struct rgba_color *)calloc(tga->map_length, sizeof(struct rgba_color));\n  if(!tga->map)\n    return IMAGE_ERROR_MEM;\n\n  for(i = 0; i < tga->map_length; i++)\n  {\n    if(s->readfn(buf, tga->bytes_per_color, s->in) < tga->bytes_per_color)\n      return IMAGE_ERROR_IO;\n\n    switch(depth)\n    {\n      case 15:\n      case 16:\n        tga->map[i] = tga_decode_color16(buf, tga);\n        break;\n      case 24:\n        tga->map[i] = tga_decode_color24(buf, tga);\n        break;\n      case 32:\n        tga->map[i] = tga_decode_color32(buf, tga);\n        break;\n    }\n  }\n  return IMAGE_OK;\n}\n\nstatic inline enum image_error tga_load_inner(const tga_decode_fn decode,\n struct tga_cursor *out, size_t num,\n const struct tga_header *tga, uint8_t * RESTRICT rowbuf, imageinfo *s)\n{\n  const size_t bytes_per_pixel = tga->bytes_per_pixel;\n  uint8_t *pos;\n\n  while(num > 0)\n  {\n    size_t sz = IMAGE_MIN(num, tga->width);\n    num -= sz;\n    if(s->readfn(rowbuf, sz * bytes_per_pixel, s->in) < sz * bytes_per_pixel)\n      return IMAGE_ERROR_IO;\n\n    pos = rowbuf;\n    while(sz > 0)\n    {\n      *(out->pixel) = decode(pos, tga);\n      tga_cursor_inc(out, tga);\n      pos += bytes_per_pixel;\n      sz--;\n    }\n  }\n  return IMAGE_OK;\n}\n\nstatic inline enum image_error tga_load_image(const tga_decode_fn decode,\n const struct tga_header *tga, uint8_t * RESTRICT rowbuf, imageinfo *s)\n{\n  struct tga_cursor out;\n  tga_cursor_init(&out, tga, s->out->data);\n  return tga_load_inner(decode, &out, tga->width * tga->height, tga, rowbuf, s);\n}\n\nstatic inline enum image_error tga_load_image_rle(const tga_decode_fn decode,\n const struct tga_header *tga, uint8_t * RESTRICT rowbuf, imageinfo *s)\n{\n  struct tga_cursor out;\n  struct rgba_color color;\n  const size_t bytes_per_pixel = tga->bytes_per_pixel;\n  enum image_error ret;\n  size_t num, total;\n\n  tga_cursor_init(&out, tga, s->out->data);\n  for(total = tga->width * tga->height; total > 0;)\n  {\n    if(s->readfn(rowbuf, bytes_per_pixel + 1, s->in) < bytes_per_pixel + 1)\n      return IMAGE_ERROR_IO;\n\n    num = (rowbuf[0] & 0x7f) + 1;\n    if(num > total)\n      return IMAGE_ERROR_TGA_BAD_RLE;\n    total -= num;\n\n    color = decode(rowbuf + 1, tga);\n    if(rowbuf[0] & 0x80)\n    {\n      tga_cursor_rle(&out, tga, color, num);\n    }\n    else\n    {\n      *(out.pixel) = color;\n      tga_cursor_inc(&out, tga);\n\n      if(num > 1)\n      {\n        ret = tga_load_inner(decode, &out, num - 1, tga, rowbuf, s);\n        if(ret)\n          return ret;\n      }\n    }\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error load_tga(imageinfo *s, const uint8_t *header,\n size_t header_sz)\n{\n  struct tga_header tga;\n  struct image_file *dest = s->out;\n  uint8_t *rowbuf;\n  uint8_t buf[256];\n  size_t total_pixels;\n  enum image_error ret;\n\n  if(header_sz < 18)\n    return IMAGE_ERROR_TGA_NOT_A_TGA;\n\n  memset(&tga, 0, sizeof(tga));\n\n  tga.image_id_length = header[0];\n  tga.map_type        = header[1];\n  tga.image_type      = header[2];\n  //tga.map_first_index = read16le(header + 3);\n  tga.map_length      = read16le(header + 5);\n  tga.map_depth       = header[7];\n  //tga.x_origin        = read16le(header + 8);\n  //tga.y_origin        = read16le(header + 10);\n  tga.width           = read16le(header + 12);\n  tga.height          = read16le(header + 14);\n  tga.depth           = header[16];\n  tga.descriptor      = header[17];\n\n  //tga.map_last_index = tga.map_first_index + tga.map_length;\n  tga.alpha_depth = tga.descriptor & 0x0f;\n  tga.interleaving = tga.descriptor >> 6;\n  tga.left_to_right = (tga.descriptor & 0x10) == 0;\n  tga.top_to_bottom = (tga.descriptor & 0x20) != 0;\n\n  switch(tga.image_type)\n  {\n    case TGA_TYPE_INDEXED:\n    case TGA_TYPE_INDEXED_RLE:\n    {\n      if(tga.map_type != TGA_HAS_COLORMAP)\n        goto not_a_tga;\n      if(tga.depth != 8 && tga.depth != 15 && tga.depth != 16)\n        goto not_a_tga;\n      if(!tga.map_length)\n        goto not_a_tga;\n      if(tga.depth == 8 && tga.map_length > 256)\n        goto not_a_tga;\n      tga.bytes_per_color = tga_bytes_per_pixel(tga.map_depth, tga.alpha_depth);\n      if(!tga.bytes_per_color)\n        goto not_a_tga;\n\n      tga.bytes_per_pixel = (tga.depth > 8) ? 2 : 1;\n      tga.alpha = tga.map_depth == 16 || tga.map_depth == 32;\n      break;\n    }\n\n    case TGA_TYPE_TRUECOLOR:\n    case TGA_TYPE_TRUECOLOR_RLE:\n    {\n      if(tga.map_type != TGA_NO_COLORMAP)\n        goto not_a_tga;\n      tga.bytes_per_pixel = tga_bytes_per_pixel(tga.depth, tga.alpha_depth);\n      if(!tga.bytes_per_pixel)\n        goto not_a_tga;\n      tga.alpha = tga.depth == 16 || tga.depth == 32;\n      break;\n    }\n\n    case TGA_TYPE_GREYSCALE:\n    case TGA_TYPE_GREYSCALE_RLE:\n    {\n      if(tga.map_type != TGA_NO_COLORMAP)\n        goto not_a_tga;\n      if(tga.depth != 8)\n        goto not_a_tga;\n      if(tga.alpha_depth != 0)\n        goto not_a_tga;\n\n      tga.bytes_per_pixel = 1;\n      break;\n    }\n\n    case TGA_TYPE_NO_IMAGE:\n    default:\n      goto not_a_tga;\n  }\n\n  if(tga.width == 0 || tga.height == 0 || tga.interleaving != 0)\n    goto not_a_tga;\n\n  debug(\"Image type: Truevision TGA\\n\");\n  debug(\"  type: %u\\n\", tga.image_type);\n  debug(\"  v. order: %s\\n\", tga.top_to_bottom ? \"top-to-bottom\" : \"bottom-to-top\");\n  debug(\"  h. order: %s\\n\", tga.left_to_right ? \"left-to-right\" : \"right-to-left\");\n  debug(\"  indexed: %s\\n\", tga.map_type ? \"yes\" : \"no\");\n  debug(\"  pixel depth: %u\\n\", tga.depth);\n  if(tga.map_type)\n  {\n    debug(\"  palette start: %u\\n\", tga.map_first_index);\n    debug(\"  stored colors: %u\\n\", tga.map_length);\n    debug(\"  palette depth: %u\\n\", tga.map_depth);\n  }\n\n  /* Skip image identification field. */\n  if(s->readfn(buf, tga.image_id_length, s->in) < tga.image_id_length)\n    return IMAGE_ERROR_IO;\n\n  /* Load color map. */\n  if(tga.map_type == TGA_HAS_COLORMAP)\n  {\n    if(tga.image_type == TGA_TYPE_INDEXED ||\n     tga.image_type == TGA_TYPE_INDEXED_RLE)\n    {\n      ret = tga_load_colormap(&tga, s);\n      if(ret)\n      {\n        debug(\"error loading colormap\\n\");\n        return ret;\n      }\n    }\n    else\n    {\n      /* Skip map if present (shouldn't happen). */\n      size_t map_len = (size_t)tga.bytes_per_pixel * tga.map_length;\n      while(map_len)\n      {\n        size_t sz = IMAGE_MIN(map_len, sizeof(buf));\n        if(s->readfn(buf, sz, s->in) < sz)\n        {\n          debug(\"short read skipping colormap\\n\");\n          return IMAGE_ERROR_MEM;\n        }\n        map_len -= sz;\n      }\n    }\n  }\n\n  /* Image data */\n  rowbuf = (uint8_t *)malloc(tga.width * tga.bytes_per_pixel);\n  if(!rowbuf)\n  {\n    debug(\"failed to allocate row buffer\\n\");\n    ret = IMAGE_ERROR_MEM;\n    goto err_free;\n  }\n\n  ret = image_init(tga.width, tga.height, dest);\n  if(ret)\n  {\n    free(rowbuf);\n    return ret;\n  }\n\n  total_pixels = (size_t)tga.width * tga.height;\n  tga.offset_in_image = 0;\n  tga.offset_in_row = 0;\n  tga.row_inc = tga.width;\n  tga.pix_inc = 1;\n\n  if(!tga.top_to_bottom)\n  {\n    tga.offset_in_image = total_pixels - tga.width;\n    tga.row_inc = -tga.width;\n  }\n  if(!tga.left_to_right)\n  {\n    tga.offset_in_row = tga.width - 1;\n    tga.pix_inc = -1;\n  }\n\n  ret = IMAGE_ERROR_UNKNOWN;\n  switch(tga.image_type)\n  {\n    case TGA_TYPE_INDEXED:\n    {\n      switch(tga.depth)\n      {\n        case 8:\n          ret = tga_load_image(tga_decode_index8, &tga, rowbuf, s);\n          break;\n\n        case 15:\n        case 16:\n          ret = tga_load_image(tga_decode_index16, &tga, rowbuf, s);\n          break;\n      }\n      break;\n    }\n\n    case TGA_TYPE_INDEXED_RLE:\n    {\n      switch(tga.depth)\n      {\n        case 8:\n          ret = tga_load_image_rle(tga_decode_index8, &tga, rowbuf, s);\n          break;\n\n        case 15:\n        case 16:\n          ret = tga_load_image_rle(tga_decode_index16, &tga, rowbuf, s);\n          break;\n      }\n      break;\n    }\n\n    case TGA_TYPE_TRUECOLOR:\n    {\n      switch(tga.depth)\n      {\n        case 15:\n        case 16:\n          ret = tga_load_image(tga_decode_color16, &tga, rowbuf, s);\n          break;\n        case 24:\n          ret = tga_load_image(tga_decode_color24, &tga, rowbuf, s);\n          break;\n        case 32:\n          ret = tga_load_image(tga_decode_color32, &tga, rowbuf, s);\n          break;\n      }\n      break;\n    }\n\n    case TGA_TYPE_TRUECOLOR_RLE:\n    {\n      switch(tga.depth)\n      {\n        case 15:\n        case 16:\n          ret = tga_load_image_rle(tga_decode_color16, &tga, rowbuf, s);\n          break;\n        case 24:\n          ret = tga_load_image_rle(tga_decode_color24, &tga, rowbuf, s);\n          break;\n        case 32:\n          ret = tga_load_image_rle(tga_decode_color32, &tga, rowbuf, s);\n          break;\n      }\n      break;\n    }\n\n    case TGA_TYPE_GREYSCALE:\n      ret = tga_load_image(tga_decode_greyscale, &tga, rowbuf, s);\n      break;\n\n    case TGA_TYPE_GREYSCALE_RLE:\n      ret = tga_load_image_rle(tga_decode_greyscale, &tga, rowbuf, s);\n      break;\n  }\n  if(ret)\n  {\n    debug(\"error reading image data\\n\");\n    image_free(dest);\n  }\n\nerr_free:\n  free(rowbuf);\n  free(tga.map);\n  return ret;\n\nnot_a_tga:\n  return IMAGE_ERROR_TGA_NOT_A_TGA;\n}\n\n\n/**\n * NetPBM loaders.\n */\n\nstatic int next_byte(imageinfo *s)\n{\n  uint8_t chr;\n  if(s->unget_chr >= 0)\n  {\n    int tmp = s->unget_chr;\n    s->unget_chr = -1;\n    return tmp;\n  }\n\n  if(s->readfn(&chr, 1, s->in) < 1)\n    return IMAGE_EOF;\n\n  return chr;\n}\n\nstatic boolean skip_whitespace(imageinfo *s)\n{\n  int value = next_byte(s);\n  if(value < 0)\n    return false;\n\n  if(value == '#')\n  {\n    // Skip line comment...\n    while(value != '\\n' && value >= 0)\n      value = next_byte(s);\n\n    return true;\n  }\n\n  if(isspace(value))\n    return true;\n\n  s->unget_chr = value;\n  return false;\n}\n\nstatic int next_number(uint32_t *output, imageinfo *s)\n{\n  int value;\n  int count;\n  *output = 0;\n\n  while(skip_whitespace(s));\n\n  count = 0;\n  for(count = 0; count <= 10; count++)\n  {\n    value = next_byte(s);\n    if(!isdigit(value))\n    {\n      if(value == '#')\n        skip_whitespace(s);\n      else\n\n      if(!isspace(value))\n        break;\n\n      return value;\n    }\n\n    *output = (*output * 10) + (value - '0');\n  }\n\n  // Digit count overflowed the uint32_t return value, or invalid character.\n  return IMAGE_EOF;\n}\n\nstatic int next_value(uint32_t maxval, imageinfo *s)\n{\n  uint32_t v;\n  if(next_number(&v, s) < 0 || v > maxval)\n    return IMAGE_EOF;\n\n  return v;\n}\n\nstatic int next_bit_value(imageinfo *s)\n{\n  int v;\n\n  // Unlike plaintext PGM and PPM, plaintext PBM doesn't require whitespace to\n  // separate components--each component is always a single digit character.\n  while(skip_whitespace(s));\n\n  v = next_byte(s);\n  if(v != '0' && v != '1')\n    return IMAGE_EOF;\n\n  return (v - '0');\n}\n\nstatic int next_binary(uint32_t maxval, imageinfo *s)\n{\n  uint32_t v;\n  if(maxval > 255)\n    v = (next_byte(s) << 8) | next_byte(s);\n  else\n    v = next_byte(s);\n\n  return (v > maxval) ? IMAGE_EOF : (int)v;\n}\n\nstatic char *next_line(char *dest, int dest_len, imageinfo *s)\n{\n  char *pos = dest;\n  int num = 1;\n  int v = -1;\n  while(num < dest_len)\n  {\n    v = next_byte(s);\n    if(v < 0)\n    {\n      if(num == 1)\n        return NULL;\n      break;\n    }\n    if(v == '\\r' || v == '\\n')\n      break;\n\n    *(pos++) = v;\n    num++;\n  }\n  if(v == '\\r')\n  {\n    v = next_byte(s);\n    if(v != '\\n')\n      s->unget_chr = v;\n  }\n  *pos = '\\0';\n  return dest;\n}\n\nstatic enum image_error pbm_header(uint32_t *width, uint32_t *height, imageinfo *s)\n{\n  // NOTE: already read magic.\n  uint32_t w = 0;\n  uint32_t h = 0;\n\n  if(next_number(&w, s) < 0 ||\n   next_number(&h, s) < 0)\n    return IMAGE_ERROR_PBM_BAD_HEADER;\n\n  // Skip exactly one whitespace (for binary files).\n  skip_whitespace(s);\n\n  *width = w;\n  *height = h;\n  return IMAGE_OK;\n}\n\nstatic enum image_error ppm_header(uint32_t *width, uint32_t *height,\n uint32_t *maxval, imageinfo *s)\n{\n  // NOTE: already read magic. PGM and PPM use the same header format.\n  uint32_t w = 0;\n  uint32_t h = 0;\n  uint32_t m = 0;\n\n  if(next_number(&w, s) < 0 ||\n   next_number(&h, s) < 0 ||\n   next_number(&m, s) < 0)\n    return IMAGE_ERROR_PBM_BAD_HEADER;\n\n  // Skip exactly one whitespace (for binary files).\n  skip_whitespace(s);\n\n  if(!m || m > 65535)\n    return IMAGE_ERROR_PBM_BAD_MAXVAL;\n\n  *width = w;\n  *height = h;\n  *maxval = m;\n  return IMAGE_OK;\n}\n\nstatic enum image_error load_portable_bitmap_plain(imageinfo *s)\n{\n  struct image_file *dest = s->out;\n  struct rgba_color *pos;\n  enum image_error ret;\n  uint32_t width;\n  uint32_t height;\n  uint32_t size;\n  uint32_t i;\n\n  debug(\"Image type: PBM (P1)\\n\");\n\n  ret = pbm_header(&width, &height, s);\n  if(ret)\n    return ret;\n\n  ret = image_init(width, height, dest);\n  if(ret)\n    return ret;\n\n  pos = dest->data;\n  size = dest->width * dest->height;\n\n  for(i = 0; i < size; i++)\n  {\n    int val = next_bit_value(s);\n    if(val < 0)\n    {\n      debug(\"short read in PBM P1 (read %zu of %zu)\\n\", (size_t)i, (size_t)size);\n      break;\n    }\n\n    val = val ? 0 : 255;\n    pos->r = val;\n    pos->g = val;\n    pos->b = val;\n    pos->a = 255;\n    pos++;\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error load_portable_bitmap_binary(imageinfo *s)\n{\n  struct image_file *dest = s->out;\n  struct rgba_color *pos;\n  enum image_error ret;\n  uint32_t width;\n  uint32_t height;\n  uint32_t x;\n  uint32_t y;\n\n  debug(\"Image type: PBM (P4)\\n\");\n\n  ret = pbm_header(&width, &height, s);\n  if(ret)\n    return ret;\n\n  ret = image_init(width, height, dest);\n  if(ret)\n    return ret;\n\n  pos = dest->data;\n\n  // Each row is padded to a whole number of bytes...\n  for(y = 0; y < height; y++)\n  {\n    for(x = 0; x < width;)\n    {\n      int val = next_byte(s);\n      int mask;\n      if(val < 0)\n      {\n        debug(\"short read in PBM P4\\n\");\n        return IMAGE_OK;\n      }\n\n      for(mask = 0x80; mask && x < width; mask >>= 1, x++)\n      {\n        int pixval = (val & mask) ? 0 : 255;\n        pos->r = pixval;\n        pos->g = pixval;\n        pos->b = pixval;\n        pos->a = 255;\n        pos++;\n      }\n    }\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error load_portable_greymap_plain(imageinfo *s)\n{\n  struct image_file *dest = s->out;\n  struct rgba_color *pos;\n  enum image_error ret;\n  uint32_t width;\n  uint32_t height;\n  uint32_t maxval;\n  uint64_t maxscale;\n  uint32_t size;\n  uint32_t i;\n\n  debug(\"Image type: PGM (P2)\\n\");\n\n  ret = ppm_header(&width, &height, &maxval, s);\n  if(ret)\n    return ret;\n\n  ret = image_init(width, height, dest);\n  if(ret)\n    return ret;\n\n  pos = dest->data;\n  size = dest->width * dest->height;\n  maxscale = ((uint64_t)255 << 32u) / maxval;\n\n  for(i = 0; i < size; i++)\n  {\n    int val = next_value(maxval, s);\n    if(val < 0)\n    {\n      debug(\"short read in PGM P2 (read %zu of %zu)\\n\", (size_t)i, (size_t)size);\n      break;\n    }\n\n    val = (val * maxscale + ROUND_32) >> 32u;\n    pos->r = val;\n    pos->g = val;\n    pos->b = val;\n    pos->a = 255;\n    pos++;\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error load_portable_greymap_binary(imageinfo *s)\n{\n  struct image_file *dest = s->out;\n  struct rgba_color *pos;\n  enum image_error ret;\n  uint32_t width;\n  uint32_t height;\n  uint32_t maxval;\n  uint64_t maxscale;\n  uint32_t size;\n  uint32_t i;\n\n  debug(\"Image type: PGM (P5)\\n\");\n\n  ret = ppm_header(&width, &height, &maxval, s);\n  if(ret)\n    return ret;\n\n  ret = image_init(width, height, dest);\n  if(ret)\n    return ret;\n\n  pos = dest->data;\n  size = dest->width * dest->height;\n  maxscale = ((uint64_t)255 << 32u) / maxval;\n\n  for(i = 0; i < size; i++)\n  {\n    int val = next_binary(maxval, s);\n    if(val < 0)\n    {\n      debug(\"short read in PGM P5 (read %zu of %zu)\\n\", (size_t)i, (size_t)size);\n      break;\n    }\n\n    val = (val * maxscale + ROUND_32) >> 32u;\n    pos->r = val;\n    pos->g = val;\n    pos->b = val;\n    pos->a = 255;\n    pos++;\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error load_portable_pixmap_plain(imageinfo *s)\n{\n  struct image_file *dest = s->out;\n  struct rgba_color *pos;\n  enum image_error ret;\n  uint32_t width;\n  uint32_t height;\n  uint32_t maxval;\n  uint64_t maxscale;\n  uint32_t size;\n  uint32_t i;\n\n  debug(\"Image type: PPM (P3)\\n\");\n\n  ret = ppm_header(&width, &height, &maxval, s);\n  if(ret)\n    return ret;\n\n  ret = image_init(width, height, dest);\n  if(ret)\n    return ret;\n\n  pos = dest->data;\n  size = dest->width * dest->height;\n  maxscale = ((uint64_t)255 << 32u) / maxval;\n\n  for(i = 0; i < size; i++)\n  {\n    int r = next_value(maxval, s);\n    int g = next_value(maxval, s);\n    int b = next_value(maxval, s);\n    if(r < 0 || g < 0 || b < 0)\n    {\n      debug(\"short read in PPM P3 (read %zu of %zu)\\n\", (size_t)i, (size_t)size);\n      break;\n    }\n\n    pos->r = (r * maxscale + ROUND_32) >> 32u;\n    pos->g = (g * maxscale + ROUND_32) >> 32u;\n    pos->b = (b * maxscale + ROUND_32) >> 32u;\n    pos->a = 255;\n    pos++;\n  }\n  return IMAGE_OK;\n}\n\nstatic enum image_error load_portable_pixmap_binary(imageinfo *s)\n{\n  struct image_file *dest = s->out;\n  struct rgba_color *pos;\n  enum image_error ret;\n  uint32_t width;\n  uint32_t height;\n  uint32_t maxval;\n  uint64_t maxscale;\n  uint32_t size;\n  uint32_t i;\n\n  debug(\"Image type: PPM (P6)\\n\");\n\n  ret = ppm_header(&width, &height, &maxval, s);\n  if(ret)\n    return ret;\n\n  ret = image_init(width, height, dest);\n  if(ret)\n    return ret;\n\n  pos = dest->data;\n  size = dest->width * dest->height;\n  maxscale = ((uint64_t)255 << 32u) / maxval;\n\n  for(i = 0; i < size; i++)\n  {\n    int r = next_binary(maxval, s);\n    int g = next_binary(maxval, s);\n    int b = next_binary(maxval, s);\n    if(r < 0 || g < 0 || b < 0)\n    {\n      debug(\"short read in PPM P6 (read %zu of %zu)\\n\", (size_t)i, (size_t)size);\n      break;\n    }\n\n    pos->r = (r * maxscale + ROUND_32) >> 32u;\n    pos->g = (g * maxscale + ROUND_32) >> 32u;\n    pos->b = (b * maxscale + ROUND_32) >> 32u;\n    pos->a = 255;\n    pos++;\n  }\n  return IMAGE_OK;\n}\n\nstruct pam_tupl\n{\n  const char *name;\n  uint8_t mindepth;\n  uint8_t red_pos;\n  uint8_t green_pos;\n  uint8_t blue_pos;\n  int8_t alpha_pos;\n};\n\nstatic enum image_error pam_set_tupltype(struct pam_tupl *dest, const char *tuplstr)\n{\n  static const struct pam_tupl tupltypes[] =\n  {\n    { \"BLACKANDWHITE\",        1, 0, 0, 0, -1 },\n    { \"GRAYSCALE\",            1, 0, 0, 0, -1 },\n    { \"RGB\",                  3, 0, 1, 2, -1 },\n    { \"BLACKANDWHITE_ALPHA\",  2, 0, 0, 0,  1 },\n    { \"GRAYSCALE_ALPHA\",      2, 0, 0, 0,  1 },\n    { \"RGB_ALPHA\",            4, 0, 1, 2,  3 },\n  };\n\n  size_t i;\n  for(i = 0; i < ARRAY_SIZE(tupltypes); i++)\n  {\n    if(!strcasecmp(tupltypes[i].name, tuplstr))\n    {\n      *dest = tupltypes[i];\n      return IMAGE_OK;\n    }\n  }\n  debug(\"unsupported TUPLTYPE '%s'!\\n\", tuplstr);\n  return IMAGE_ERROR_PAM_BAD_TUPLTYPE;\n}\n\nstatic enum image_error pam_header(uint32_t *width, uint32_t *height,\n uint32_t *maxval, uint32_t *depth, struct pam_tupl *tupltype, imageinfo *s)\n{\n  char linebuf[256];\n  char tuplstr[256];\n  char *key;\n  char *value;\n  boolean has_w = false;\n  boolean has_h = false;\n  boolean has_m = false;\n  boolean has_d = false;\n  boolean has_e = false;\n  boolean has_tu = false;\n  enum image_error ret;\n\n  while(next_line(linebuf, 256, s))\n  {\n    value = linebuf;\n    while(isspace((uint8_t )*value))\n      value++;\n\n    if(!*value || *value == '#')\n      continue;\n\n    key = value;\n    while(*value && !isspace((uint8_t)*value))\n      value++;\n\n    if(*value)\n      *(value++) = '\\0';\n\n    if(!strcasecmp(key, \"ENDHDR\"))\n    {\n      has_e = true;\n      break;\n    }\n    else\n\n    if(!strcasecmp(key, \"WIDTH\"))\n    {\n      if(!*value || has_w)\n        return IMAGE_ERROR_PAM_BAD_WIDTH;\n\n      *width = strtoul(value, NULL, 10);\n      has_w = true;\n    }\n    else\n\n    if(!strcasecmp(key, \"HEIGHT\"))\n    {\n      if(!*value || has_h)\n        return IMAGE_ERROR_PAM_BAD_HEIGHT;\n\n      *height = strtoul(value, NULL, 10);\n      has_h = true;\n    }\n    else\n\n    if(!strcasecmp(key, \"DEPTH\"))\n    {\n      if(!*value || has_d)\n        return IMAGE_ERROR_PAM_BAD_DEPTH;\n\n      *depth = strtoul(value, NULL, 10);\n      has_d = true;\n    }\n    else\n\n    if(!strcasecmp(key, \"MAXVAL\"))\n    {\n      if(!*value || has_m)\n        return IMAGE_ERROR_PAM_BAD_MAXVAL;\n\n      *maxval = strtoul(value, NULL, 10);\n      has_m = true;\n    }\n    else\n\n    if(!strcasecmp(key, \"TUPLTYPE\"))\n    {\n      // NOTE: the spec supports combining multiple of these, but doesn't use it.\n      if(!*value || has_tu)\n        return IMAGE_ERROR_PAM_BAD_TUPLTYPE;\n\n      snprintf(tuplstr, sizeof(tuplstr), \"%s\", value);\n      has_tu = true;\n    }\n  }\n\n  if(!has_w)\n    return IMAGE_ERROR_PAM_BAD_WIDTH;\n  if(!has_h)\n    return IMAGE_ERROR_PAM_BAD_HEIGHT;\n  if(!has_d)\n    return IMAGE_ERROR_PAM_BAD_DEPTH;\n  if(!has_m)\n    return IMAGE_ERROR_PAM_BAD_MAXVAL;\n  if(!has_e)\n    return IMAGE_ERROR_PAM_MISSING_ENDHDR;\n\n  if(*depth < 1 || *depth > 4)\n  {\n    debug(\"invalid depth '%u'!\\n\", (unsigned int)*depth);\n    return IMAGE_ERROR_PAM_BAD_DEPTH;\n  }\n\n  if(*maxval < 1 || *maxval > 65535)\n  {\n    debug(\"invalid maxval '%u'!\\n\", (unsigned int)*maxval);\n    return IMAGE_ERROR_PAM_BAD_MAXVAL;\n  }\n\n  if(has_tu)\n    has_tu = pam_set_tupltype(tupltype, tuplstr);\n\n  if(!has_tu)\n  {\n    // Guess TUPLTYPE from depth.\n    const char *guess = \"\";\n    switch(*depth)\n    {\n      case 1: guess = \"GRAYSCALE\"; break;\n      case 2: guess = \"GRAYSCALE_ALPHA\"; break;\n      case 3: guess = \"RGB\"; break;\n      case 4: guess = \"RGB_ALPHA\"; break;\n    }\n    ret = pam_set_tupltype(tupltype, guess);\n    if(ret)\n      return ret;\n  }\n\n  if(tupltype->mindepth > *depth)\n    return IMAGE_ERROR_PAM_DEPTH_TUPLTYPE_MISMATCH;\n\n  return IMAGE_OK;\n}\n\nstatic enum image_error load_portable_arbitrary_map(imageinfo *s)\n{\n  struct image_file *dest = s->out;\n  struct rgba_color *pos;\n  struct pam_tupl tupltype;\n  uint32_t width = 0;\n  uint32_t height = 0;\n  uint32_t depth = 0;\n  uint32_t maxval = 0;\n  uint64_t maxscale = 0;\n  uint32_t size;\n  uint32_t i;\n  enum image_error ret;\n  int v[4] = { 0 };\n\n  debug(\"Image type: PAM (P7)\\n\");\n\n  // Shut up GCC 14 false positive\n  memset(&tupltype, 0, sizeof(tupltype));\n\n  ret = pam_header(&width, &height, &maxval, &depth, &tupltype, s);\n  if(ret)\n    return ret;\n\n  ret = image_init(width, height, dest);\n  if(ret)\n    return ret;\n\n  pos = dest->data;\n  size = dest->width * dest->height;\n  maxscale = ((uint64_t)255 << 32u) / maxval;\n\n  for(i = 0; i < size; i++)\n  {\n    uint32_t j;\n\n    for(j = 0; j < depth; j++)\n    {\n      v[j] = next_binary(maxval, s);\n      if(v[j] < 0)\n      {\n        debug(\"short read in PAM P7 (read %zu of %zu)\\n\", (size_t)i, (size_t)size);\n        return IMAGE_OK;\n      }\n\n      v[j] = (v[j] * maxscale + ROUND_32) >> 32u;\n    }\n\n    pos->r = v[tupltype.red_pos];\n    pos->g = v[tupltype.green_pos];\n    pos->b = v[tupltype.blue_pos];\n    pos->a = tupltype.alpha_pos >= 0 ? v[tupltype.alpha_pos] : 255;\n    pos++;\n  }\n  return IMAGE_OK;\n}\n\n\n/**\n * Farbfeld loader.\n */\n\nstatic uint8_t convert_16b_to_8b(uint16_t component)\n{\n  return (uint32_t)(component * 255u + 32768u) / 65535u;\n}\n\nstatic enum image_error load_farbfeld(imageinfo *s)\n{\n  struct image_file *dest = s->out;\n  struct rgba_color *pos;\n  uint8_t buf[8];\n  uint32_t width;\n  uint32_t height;\n  uint32_t size;\n  uint32_t i;\n  enum image_error ret;\n\n  debug(\"Image type: farbfeld\\n\");\n\n  if(s->readfn(buf, 8, s->in) < 8)\n    return IMAGE_ERROR_IO;\n\n  width  = read32be(buf + 0);\n  height = read32be(buf + 4);\n  ret = image_init(width, height, dest);\n  if(ret)\n    return ret;\n\n  pos = dest->data;\n  size = width * height;\n\n  for(i = 0; i < size; i++)\n  {\n    if(s->readfn(buf, 8, s->in) < 8)\n    {\n      debug(\"short read in ff (read %zu of %zu)\\n\", (size_t)i, (size_t)size);\n      break;\n    }\n\n    pos->r = convert_16b_to_8b(read16be(buf + 0));\n    pos->g = convert_16b_to_8b(read16be(buf + 2));\n    pos->b = convert_16b_to_8b(read16be(buf + 4));\n    pos->a = convert_16b_to_8b(read16be(buf + 6));\n    pos++;\n  }\n  return IMAGE_OK;\n}\n\n\n/**\n * Raw format loader.\n * `not_magic` is the first few bytes of the file read for the purposes of\n * identifying a file format. For raw files, this is actually pixel data.\n */\nstatic enum image_error load_raw(imageinfo *s,\n const struct image_raw_format *format, const uint8_t *not_magic,\n size_t not_magic_length)\n{\n  struct image_file *dest = s->out;\n  struct rgba_color *dest_pos;\n  uint8_t *src_pos;\n  uint8_t *data;\n  uint64_t size = (uint64_t)format->width * format->height;\n  uint64_t raw_size = size * format->bytes_per_pixel;\n  uint64_t i;\n  enum image_error ret = IMAGE_OK;\n  char tmp;\n\n  if(format->bytes_per_pixel < 1 || format->bytes_per_pixel > 4)\n  {\n    debug(\"unsupported raw bytes per pixel %zu\\n\", (size_t)format->bytes_per_pixel);\n    return IMAGE_ERROR_RAW_UNSUPPORTED_BPP;\n  }\n\n  debug(\"checking if this is a raw file with properties: %zu %zu %zu\\n\",\n   (size_t)format->width, (size_t)format->height, (size_t)format->bytes_per_pixel);\n\n  data = (uint8_t *)malloc(raw_size);\n  if(!data)\n  {\n    debug(\"can't allocate memory for raw image with given size\\n\");\n    return IMAGE_ERROR_MEM;\n  }\n\n  if(not_magic_length)\n    memcpy(data, not_magic, IMAGE_MIN(raw_size, not_magic_length));\n\n  if(raw_size > not_magic_length)\n  {\n    raw_size -= not_magic_length;\n    if(s->readfn(data + not_magic_length, raw_size, s->in) < raw_size)\n    {\n      debug(\"failed to read required data for raw image\\n\");\n      ret = IMAGE_ERROR_IO;\n      goto err_free;\n    }\n  }\n\n  debug(\"Image type: raw\\n\");\n\n  if(s->readfn(&tmp, 1, s->in) != 0)\n    debug(\"data exists after expected EOF in raw file\\n\");\n\n  ret = image_init(format->width, format->height, dest);\n  if(ret)\n    goto err_free;\n\n  dest_pos = dest->data;\n  src_pos = data;\n  for(i = 0; i < size; i++)\n  {\n    uint8_t r, g, b, a;\n    if(format->bytes_per_pixel < 3)\n    {\n      r = src_pos[0];\n      g = src_pos[0];\n      b = src_pos[0];\n      a = (format->bytes_per_pixel == 2) ? src_pos[1] : 255;\n    }\n    else\n    {\n      r = src_pos[0];\n      g = src_pos[1];\n      b = src_pos[2];\n      a = (format->bytes_per_pixel == 4) ? src_pos[3] : 255;\n    }\n\n    dest_pos->r = r;\n    dest_pos->g = g;\n    dest_pos->b = b;\n    dest_pos->a = a;\n    dest_pos++;\n    src_pos += format->bytes_per_pixel;\n  }\n\nerr_free:\n  free(data);\n  return ret;\n}\n\n\n/**\n * Common file handling.\n */\n\n#define MAGIC(a,b) (((a) << 8) | (b))\n\n/**\n * Load an image from a stream.\n * Assume this stream can NOT be seeked.\n */\nenum image_error load_image_from_stream(void *fp, image_read_function readfn,\n struct image_file *dest, const struct image_raw_format *raw_format)\n{\n  imageinfo s = { dest, fp, readfn, -1 };\n  uint8_t magic[CHECK_LENGTH];\n  size_t sz = 0;\n  enum image_error ret;\n\n  memset(dest, 0, sizeof(struct image_file));\n  memset(magic, 0, sizeof(magic));\n\n  if(raw_format && raw_format->force_raw)\n    goto force_raw;\n\n  if(readfn(magic, 2, fp) < 2)\n    return IMAGE_ERROR_SIGNATURE;\n\n  switch(MAGIC(magic[0], magic[1]))\n  {\n    /* BMP */\n    case MAGIC('B','M'): return load_bmp(&s);\n\n    /* NetPBM */\n    case MAGIC('P','1'): return load_portable_bitmap_plain(&s);\n    case MAGIC('P','2'): return load_portable_greymap_plain(&s);\n    case MAGIC('P','3'): return load_portable_pixmap_plain(&s);\n    case MAGIC('P','4'): return load_portable_bitmap_binary(&s);\n    case MAGIC('P','5'): return load_portable_greymap_binary(&s);\n    case MAGIC('P','6'): return load_portable_pixmap_binary(&s);\n    case MAGIC('P','7'): return load_portable_arbitrary_map(&s);\n  }\n\n  if(readfn(magic + 2, 1, fp) < 1)\n    return IMAGE_ERROR_SIGNATURE;\n\n  /* GIF */\n  if(!memcmp(magic, \"GIF\", 3))\n    return load_gif(&s);\n\n  if(readfn(magic + 3, 5, fp) < 5)\n    return IMAGE_ERROR_SIGNATURE;\n\n  /* farbfeld */\n  if(!memcmp(magic, \"farbfeld\", 8))\n    return load_farbfeld(&s);\n\n#ifdef CONFIG_PNG\n\n  /* PNG (via libpng). */\n  if(png_sig_cmp(magic, 0, 8) == 0)\n    return load_png(&s);\n\n#else\n\n  if(!memcmp(magic, \"\\x89PNG\\r\\n\\x1A\\n\", 8))\n    debug(\"MegaZeux utils were compiled without PNG support--is this a PNG?\\n\");\n\n#endif\n\n  sz = 8 + readfn(magic + 8, 10, fp);\n  if(sz == 18)\n  {\n    /* TGA has no signature. If the header looks wrong no bytes will be read. */\n    ret = load_tga(&s, magic, 18);\n    if(ret != IMAGE_ERROR_TGA_NOT_A_TGA)\n      return ret;\n  }\n\nforce_raw:\n  if(raw_format)\n  {\n    enum image_error res = load_raw(&s, raw_format, magic, sz);\n    if(res != IMAGE_ERROR_IO && res != IMAGE_ERROR_MEM)\n      return res;\n  }\n\n  debug(\"unknown format\\n\");\n  return IMAGE_ERROR_SIGNATURE;\n}\n\nstatic size_t stdio_read_fn(void *dest, size_t num, void *handle)\n{\n  return fread(dest, 1, num, (FILE *)handle);\n}\n\n/**\n * Load an image from a file.\n */\nenum image_error load_image_from_file(const char *filename,\n struct image_file *dest, const struct image_raw_format *raw_format)\n{\n  FILE *fp;\n  enum image_error ret;\n\n  if(!strcmp(filename, \"-\"))\n  {\n#ifdef _WIN32\n    /* Windows forces stdin to be text mode by default, fix it. */\n    _setmode(_fileno(stdin), _O_BINARY);\n#endif\n    return load_image_from_stream(stdin, stdio_read_fn, dest, raw_format);\n  }\n\n  fp = fopen(filename, \"rb\");\n  if(!fp)\n    return IMAGE_ERROR_IO;\n\n  setvbuf(fp, NULL, _IOFBF, 32768);\n\n  ret = load_image_from_stream(fp, stdio_read_fn, dest, raw_format);\n  fclose(fp);\n\n  return ret;\n}\n"
  },
  {
    "path": "src/utils/image_file.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2021-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __UTILS_IMAGE_FILE_H\n#define __UTILS_IMAGE_FILE_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include <stdint.h>\n#include <stdlib.h>\n\n/* Read up to `num` bytes from `handle` into the buffer pointed to by `dest`.\n * Returns the number of bytes actually read from `handle`. */\ntypedef size_t (*image_read_function)(void *dest, size_t num, void *handle);\ntypedef unsigned char image_bool;\n\nenum image_bool_values\n{\n  IMAGE_FALSE,\n  IMAGE_TRUE\n};\n\nenum image_error\n{\n  IMAGE_OK = 0,\n  IMAGE_ERROR_UNKNOWN,\n  IMAGE_ERROR_IO,\n  IMAGE_ERROR_MEM,\n  IMAGE_ERROR_SIGNATURE,\n  IMAGE_ERROR_CONSTRAINT,\n  /* RAW errors. */\n  IMAGE_ERROR_RAW_UNSUPPORTED_BPP,\n  /* PNG errors. */\n  IMAGE_ERROR_PNG_INIT,\n  IMAGE_ERROR_PNG_FAILED,\n  /* GIF errors. */\n  IMAGE_ERROR_GIF_INVALID,\n  IMAGE_ERROR_GIF_SIGNATURE,\n  /* BMP errors. */\n  IMAGE_ERROR_BMP_UNSUPPORTED_DIB,\n  IMAGE_ERROR_BMP_UNSUPPORTED_PLANES,\n  IMAGE_ERROR_BMP_UNSUPPORTED_COMPRESSION,\n  IMAGE_ERROR_BMP_UNSUPPORTED_BPP,\n  IMAGE_ERROR_BMP_UNSUPPORTED_BPP_RLE8,\n  IMAGE_ERROR_BMP_UNSUPPORTED_BPP_RLE4,\n  IMAGE_ERROR_BMP_UNSUPPORTED_BPP_BITFIELDS,\n  IMAGE_ERROR_BMP_BAD_1BPP,\n  IMAGE_ERROR_BMP_BAD_2BPP,\n  IMAGE_ERROR_BMP_BAD_4BPP,\n  IMAGE_ERROR_BMP_BAD_8BPP,\n  IMAGE_ERROR_BMP_BAD_16BPP,\n  IMAGE_ERROR_BMP_BAD_24BPP,\n  IMAGE_ERROR_BMP_BAD_32BPP,\n  IMAGE_ERROR_BMP_BAD_RLE,\n  IMAGE_ERROR_BMP_BAD_SIZE,\n  IMAGE_ERROR_BMP_BAD_COLOR_TABLE,\n  IMAGE_ERROR_BMP_BAD_BITFIELDS_DIB,\n  IMAGE_ERROR_BMP_BAD_BITFIELDS_MASK_CONTINUITY,\n  IMAGE_ERROR_BMP_BAD_BITFIELDS_MASK_OVERLAP,\n  IMAGE_ERROR_BMP_BAD_BITFIELDS_MASK_RANGE,\n  /* NetPBM errors. */\n  IMAGE_ERROR_PBM_BAD_HEADER,\n  IMAGE_ERROR_PBM_BAD_MAXVAL,\n  IMAGE_ERROR_PAM_BAD_WIDTH,\n  IMAGE_ERROR_PAM_BAD_HEIGHT,\n  IMAGE_ERROR_PAM_BAD_DEPTH,\n  IMAGE_ERROR_PAM_BAD_MAXVAL,\n  IMAGE_ERROR_PAM_BAD_TUPLTYPE,\n  IMAGE_ERROR_PAM_MISSING_ENDHDR,\n  IMAGE_ERROR_PAM_DEPTH_TUPLTYPE_MISMATCH,\n  /* TGA errors. */\n  IMAGE_ERROR_TGA_NOT_A_TGA,\n  IMAGE_ERROR_TGA_BAD_RLE,\n};\n\nstruct rgba_color\n{\n  uint8_t r;\n  uint8_t g;\n  uint8_t b;\n  uint8_t a;\n};\n\nstruct image_file\n{\n  uint32_t width;\n  uint32_t height;\n  struct rgba_color *data;\n};\n\n/**\n * If raw images are allowed, let the caller specify how the raw data should be\n * interpreted.\n */\nstruct image_raw_format\n{\n  uint32_t width;\n  uint32_t height;\n  uint32_t bytes_per_pixel;\n  image_bool force_raw;\n};\n\nenum image_error load_image_from_file(const char *filename,\n struct image_file *dest, const struct image_raw_format *raw_format);\nenum image_error load_image_from_stream(void *handle, image_read_function readfn,\n struct image_file *dest, const struct image_raw_format *raw_format);\nvoid image_free(struct image_file *dest);\n\n/**\n * Get the error string for a given error.\n *\n * @param   err   a `image_error` value.\n * @returns       a statically allocated string corresponding to the error.\n */\nconst char *image_error_string(enum image_error err);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* __UTILS_IMAGE_FILE_H */\n"
  },
  {
    "path": "src/utils/image_gif.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2022 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <limits.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"image_common.h\"\n#include \"image_gif.h\"\n\n#if 0\n#define gifdbg(...) imagedbg(\"GIF: \" __VA_ARGS__)\n#else\n#define gifdbg imagenodbg\n#endif\n\n#if 0\n#define giflzwdbg(...) imagedbg(\"GIF: \" __VA_ARGS__)\n#else\n#define giflzwdbg imagenodbg\n#endif\n\nstatic inline uint16_t gif_u16(const void *_buf)\n{\n  const uint8_t *buf = (const uint8_t *)_buf;\n  return buf[0] | (buf[1] << 8);\n}\n\nstatic inline uint32_t gif_u32(const void *_buf)\n{\n  const uint8_t *buf = (const uint8_t *)_buf;\n  return buf[0] | (buf[1] << 8) | ((uint32_t)buf[2] << 16) | ((uint32_t)buf[3] << 24);\n}\n\nconst char *gif_error_string(enum gif_error err)\n{\n  switch(err)\n  {\n    case GIF_OK:\n      return \"ok\";\n    case GIF_IO:\n      return \"IO error\";\n    case GIF_MEM:\n      return \"out of memory\";\n    case GIF_INVALID:\n      return \"invalid stream\";\n    case GIF_SIGNATURE:\n      return \"not a GIF\";\n  }\n  return \"unknown\";\n}\n\n\nstruct gif_lzw_node\n{\n#define GIF_MAX_CODES 0x1000\n#define GIF_NO_CODE 0x1000\n  uint16_t len;\n  uint16_t prev;\n  uint8_t chr;\n};\n\nstruct gif_bitstream\n{\n  uint8_t *in;\n  unsigned in_left;\n  unsigned bits_left;\n  size_t bits;\n};\n\nstatic gif_bool gif_fill_buffer(struct gif_bitstream *b)\n{\n  unsigned bytes_free = (sizeof(size_t) > 4 ? 64 - b->bits_left : 32 - b->bits_left) >> 3;\n  unsigned to_read = IMAGE_MIN(bytes_free, b->in_left);\n\n  if(to_read >= 4 && sizeof(size_t) > 4) /* rarely useful for 32-bit buffers */\n  {\n    b->bits |= (size_t)gif_u32(b->in) << b->bits_left;\n    b->bits_left += 32;\n    b->in_left -= 4;\n    b->in += 4;\n    return GIF_TRUE;\n  }\n  if(to_read >= 2)\n  {\n    b->bits |= (size_t)gif_u16(b->in) << b->bits_left;\n    b->bits_left += 16;\n    b->in_left -= 2;\n    b->in += 2;\n    return GIF_TRUE;\n  }\n  if(to_read >= 1)\n  {\n    b->bits |= (size_t)*(b->in) << b->bits_left;\n    b->bits_left += 8;\n    b->in_left--;\n    b->in++;\n    return GIF_TRUE;\n  }\n  return GIF_FALSE;\n}\n\nstatic uint16_t gif_read_code(struct gif_bitstream *b, unsigned code_len)\n{\n  uint16_t code;\n  if(b->bits_left < code_len)\n    if(!gif_fill_buffer(b) || b->bits_left < code_len)\n      return GIF_NO_CODE;\n\n  code = b->bits & ((1u << code_len) - 1);\n  b->bits >>= code_len;\n  b->bits_left -= code_len;\n  return code;\n}\n\n/**\n * Unpack a GIF image. GIF images are stored using a LZW variant split into\n * blocks of up to 255 bytes. The minimum allowed bit width is 3 and the\n * maximum allowed bit width is 12. See GIF89a Appendix F.\n */\nstatic enum gif_error gif_decode(uint8_t *out, struct gif_lzw_node **_tree,\n struct gif_image *layer, void *handle, const gif_read_function readfn)\n{\n  struct gif_lzw_node *tree = *_tree;\n  struct gif_lzw_node *current;\n  struct gif_bitstream b;\n  uint8_t *out_end = out + layer->width * layer->height;\n  uint8_t *pos;\n  uint8_t buf[255];\n  uint8_t size;\n  uint8_t first_chr = 0;\n  uint8_t code_len_init;\n  unsigned prev_code = GIF_NO_CODE;\n  unsigned next_code;\n  unsigned clear_code;\n  unsigned end_code;\n  unsigned code_len;\n  unsigned i;\n\n  if(!tree)\n  {\n    *_tree = tree = (struct gif_lzw_node *)calloc(1<<12, sizeof(struct gif_lzw_node));\n    if(!tree)\n      return GIF_MEM;\n  }\n  else\n    memset(tree, 0, (1<<12) * sizeof(struct gif_lzw_node));\n\n  if(readfn(&code_len_init, 1, handle) < 1)\n    return GIF_IO;\n\n  code_len_init++;\n  if(code_len_init < 2 || code_len_init > 12)\n    return GIF_INVALID;\n\n  code_len = code_len_init;\n  for(i = 0; i < (1u << (code_len - 1)); i++)\n  {\n    tree[i].prev = GIF_NO_CODE;\n    tree[i].len = 1;\n    tree[i].chr = i;\n  }\n  clear_code = i;\n  end_code = i + 1;\n  next_code = i + 2;\n  for(; i < GIF_MAX_CODES; i++)\n    tree[i].prev = GIF_NO_CODE;\n\n  b.bits_left = 0;\n  b.bits = 0;\n\n  giflzwdbg(\"  Depack\\t\\t\\t code_len:%d\\n\", code_len);\n\n  while(1)\n  {\n    if(readfn(&size, 1, handle) < 1)\n      return GIF_IO;\n    giflzwdbg(\"  BLOCK size:%d\\n\", size);\n    if(size == 0)\n      break;\n\n    if(readfn(buf, size, handle) < size)\n      return GIF_IO;\n\n    b.in = buf;\n    b.in_left = size;\n\n    while(1)\n    {\n      uint16_t code = gif_read_code(&b, code_len);\n      int kwkwk = 0;\n      giflzwdbg(\"    %d\\n\", code);\n      if(code >= GIF_MAX_CODES || code > next_code || code == end_code)\n        break;\n\n      /* NOTE: clear is supposed to reset the entire table, so no encoder\n       * should rely on stale codes after a reset and the current length\n       * optimization should be safe. If a file that uses these is found\n       * this should be revised. */\n      if(code == clear_code)\n      {\n        code_len = code_len_init;\n        next_code = end_code + 1;\n        prev_code = GIF_NO_CODE;\n        first_chr = 0;\n        continue;\n      }\n\n      /* Add (kwkwk) */\n      if(code == next_code && next_code < GIF_MAX_CODES)\n      {\n        if(prev_code >= GIF_NO_CODE) /* This should never be emitted first. */\n          break;\n\n        giflzwdbg(\"      add:%d prev:%d prevlen:%d (kwkwk)\\n\",\n         next_code, prev_code, tree[prev_code].len);\n\n        current = &tree[next_code++];\n        current->prev = prev_code;\n        current->len = tree[prev_code].len + 1;\n        current->chr = first_chr;\n        kwkwk = 1;\n      }\n\n      /* Emit */\n      current = &tree[code];\n      out += current->len;\n      pos = out;\n      giflzwdbg(\"      len:%d\\n\", current->len);\n      if(out > out_end)\n      {\n        giflzwdbg(\"    early end of buffer\\n\");\n        return GIF_INVALID;\n      }\n\n      while(1)\n      {\n        *(--pos) = first_chr = current->chr;\n        if(current->prev >= GIF_NO_CODE)\n          break;\n        current = &tree[current->prev];\n      }\n\n      /* Add (normal) */\n      if(!kwkwk && prev_code < GIF_NO_CODE && next_code < GIF_MAX_CODES)\n      {\n        giflzwdbg(\"      add:%d prev:%d prevlen:%d\\n\",\n         next_code, prev_code, tree[prev_code].len);\n\n        current = &tree[next_code++];\n        current->prev = prev_code;\n        current->len = tree[prev_code].len + 1;\n        current->chr = first_chr;\n      }\n\n      /* Expand after adding */\n      if(next_code >= (1u << code_len))\n      {\n        if(code_len < 12)\n        {\n          code_len++;\n          giflzwdbg(\"    code len is now: %d\\n\", code_len);\n        }\n        else\n          giflzwdbg(\"    out of codes\\n\");\n      }\n\n      prev_code = code;\n    }\n\n    if(b.in_left)\n    {\n      giflzwdbg(\"  unexpected end code or invalid code (%u bits left in buffer,\"\n       \"%u bytes left in buffer, %zd left in output)\\n\",\n       b.bits_left, b.in_left, out_end - out);\n      return GIF_INVALID;\n    }\n\n    giflzwdbg(\"  bits_left:%u\\n\", b.bits_left);\n  }\n  return GIF_OK;\n}\n\n/**\n * Deinterlace an image that has been stored interlaced. Rows are stored in the\n * following format (See GIF89a Appendix E):\n *\n * 1) Every 8th row starting from row 0.\n * 2) Every 8th row starting from row 4.\n * 3) Every 4th row starting from row 2.\n * 4) Every 2nd row starting from row 1.\n */\nstatic void gif_deinterlace(uint8_t * RESTRICT out, const uint8_t *in,\n unsigned width, unsigned height)\n{\n  uint8_t *pos;\n  uint8_t *end = out + (width * height);\n  unsigned pitch = width * 8;\n\n  for(pos = out; pos < end; pos += pitch, in += width)\n    memcpy(pos, in, width);\n\n  for(pos = out + width * 4; pos < end; pos += pitch, in += width)\n    memcpy(pos, in, width);\n\n  pitch = width * 4;\n  for(pos = out + width * 2; pos < end; pos += pitch, in += width)\n    memcpy(pos, in, width);\n\n  pitch = width * 2;\n  for(pos = out + width; pos < end; pos += pitch, in += width)\n    memcpy(pos, in, width);\n}\n\nstatic unsigned gif_next_power_of_two(unsigned sz)\n{\n  sz |= sz >> 1;\n  sz |= sz >> 2;\n  sz |= sz >> 4;\n  sz |= sz >> 8;\n  sz |= sz >> 16;\n  return sz + 1;\n}\n\nstatic unsigned gif_ptr_size(unsigned sz)\n{\n  return sz * sizeof(void *);\n}\n\n#define RESIZE(type, ptr, alloc, target, size_fn) \\\n  do { \\\n    unsigned tmp = IMAGE_MAX((alloc), (target)); \\\n    unsigned new_size = gif_next_power_of_two(tmp); \\\n    unsigned a_size = size_fn(new_size); \\\n    void *buf; \\\n    if(new_size < tmp || a_size < new_size) \\\n      return GIF_MEM; \\\n    buf = realloc((ptr), a_size); \\\n    if(!buf) \\\n      return GIF_MEM; \\\n    ptr = (type)buf; \\\n    alloc = new_size; \\\n  } while(0)\n\n#define RESIZE_LIST(type, ptr, alloc, target) \\\n RESIZE(type, ptr, alloc, target, gif_ptr_size)\n\nstatic struct gif_color_table *gif_alloc_color_table(unsigned num_entries)\n{\n  size_t sz = num_entries * sizeof(struct gif_color);\n  sz += sizeof(struct gif_color_table);\n\n  return (struct gif_color_table *)malloc(sz);\n}\n\nstatic enum gif_error gif_add_block(struct gif_info *gif, unsigned size,\n struct gif_block **_block)\n{\n  struct gif_block *block;\n\n  if(gif->num_blocks >= gif->num_blocks_alloc)\n  {\n    RESIZE_LIST(struct gif_block **, gif->blocks, gif->num_blocks_alloc,\n     gif->num_blocks + 1);\n  }\n\n  block = (struct gif_block *)calloc(1, size);\n  if(!block)\n    return GIF_MEM;\n\n  gif->blocks[gif->num_blocks++] = block;\n  *_block = block;\n  return GIF_OK;\n}\n\nstatic enum gif_error gif_add_control(struct gif_info *gif,\n struct gif_graphics_control **_control)\n{\n  struct gif_block *block;\n\n  if(gif_add_block(gif, sizeof(struct gif_graphics_control), &block) != GIF_OK)\n    return GIF_MEM;\n\n  block->type = GIF_CONTROL;\n  *_control = (struct gif_graphics_control *)block;\n  return GIF_OK;\n}\n\nstatic unsigned gif_add_image_size(unsigned size)\n{\n  return IMAGE_MAX(sizeof(struct gif_image),\n   offsetof(struct gif_image, pixels) + size);\n}\n\nstatic enum gif_error gif_add_image(struct gif_info *gif, unsigned size,\n struct gif_image **_layer)\n{\n  struct gif_block *block;\n  unsigned a_size = gif_add_image_size(size);\n\n  if(a_size < size || gif_add_block(gif, a_size, &block) != GIF_OK)\n    return GIF_MEM;\n\n  block->type = GIF_IMAGE;\n  *_layer = (struct gif_image *)block;\n  return GIF_OK;\n}\n\nstatic unsigned gif_add_plaintext_size(unsigned size)\n{\n  return IMAGE_MAX(sizeof(struct gif_plaintext),\n   offsetof(struct gif_plaintext, text) + size + 1);\n}\n\nstatic enum gif_error gif_add_plaintext(struct gif_info *gif, unsigned size,\n struct gif_plaintext **_layer)\n{\n  struct gif_block *block;\n  unsigned a_size = gif_add_plaintext_size(size);\n\n  if(a_size < size || gif_add_block(gif, a_size, &block) != GIF_OK)\n    return GIF_MEM;\n\n  block->type = GIF_PLAINTEXT;\n  *_layer = (struct gif_plaintext *)block;\n  (*_layer)->length = 0;\n  (*_layer)->length_alloc = size;\n  (*_layer)->text[0] = '\\0';\n  return GIF_OK;\n}\n\nstatic enum gif_error gif_add_plaintext_append(struct gif_block **_layer,\n uint8_t *data, uint8_t length)\n{\n  struct gif_plaintext *layer = (struct gif_plaintext *)*_layer;\n  if(layer->length_alloc - layer->length < length)\n  {\n    RESIZE(struct gif_plaintext *, layer, layer->length_alloc,\n     layer->length + length, gif_add_plaintext_size);\n    *_layer = (struct gif_block *)layer;\n  }\n  memcpy(layer->text + layer->length, data, length);\n  layer->length += length;\n  layer->text[layer->length] = '\\0';\n  return GIF_OK;\n}\n\nstatic unsigned gif_add_comment_size(unsigned size)\n{\n  return IMAGE_MAX(sizeof(struct gif_comment),\n   offsetof(struct gif_comment, comment) + size + 1);\n}\n\nstatic enum gif_error gif_add_comment(struct gif_info *gif, unsigned size)\n{\n  struct gif_block *block;\n  struct gif_comment *c;\n  unsigned a_size = gif_add_comment_size(size);\n\n  if(a_size < size || gif_add_block(gif, a_size, &block) != GIF_OK)\n    return GIF_MEM;\n\n  if(!gif->comments_end)\n    gif->comments_start = gif->num_blocks - 1;\n  gif->comments_end = gif->num_blocks;\n\n  block->type = GIF_COMMENT;\n  c = (struct gif_comment *)block;\n  c->length = 0;\n  c->length_alloc = size;\n  c->comment[0] = '\\0';\n  return GIF_OK;\n}\n\nstatic enum gif_error gif_add_comment_append(struct gif_block **_comment,\n uint8_t *data, uint8_t length)\n{\n  struct gif_comment *c = (struct gif_comment *)*_comment;\n  if(c->length_alloc - c->length < length)\n  {\n    RESIZE(struct gif_comment *, c, c->length_alloc, c->length + length,\n     gif_add_comment_size);\n    *_comment = (struct gif_block *)c;\n  }\n  memcpy(c->comment + c->length, data, length);\n  c->length += length;\n  c->comment[c->length] = '\\0';\n  return GIF_OK;\n}\n\nstatic enum gif_error gif_add_application(struct gif_info *gif)\n{\n  struct gif_block *block;\n\n  if(gif_add_block(gif, sizeof(struct gif_application), &block) != GIF_OK)\n    return GIF_MEM;\n\n  if(!gif->application_end)\n    gif->application_start = gif->num_blocks - 1;\n  gif->application_end = gif->num_blocks;\n\n  block->type = GIF_APPLICATION;\n  return GIF_OK;\n}\n\nstatic enum gif_error gif_add_appdata(struct gif_block *_app,\n uint8_t *appdata, uint8_t length)\n{\n  struct gif_application *app = (struct gif_application *)_app;\n  struct gif_appdata *a;\n\n  if(app->length >= app->length_alloc)\n  {\n    RESIZE_LIST(struct gif_appdata **, app->appdata, app->length_alloc,\n     app->length + 1);\n  }\n\n  a = (struct gif_appdata *)malloc(sizeof(struct gif_appdata) + length);\n  if(!a)\n    return GIF_MEM;\n\n  a->length = length;\n  memcpy(a->appdata, appdata, length);\n  a->appdata[length] = '\\0';\n\n  app->appdata[app->length++] = a;\n  return GIF_OK;\n}\n\nenum gif_error gif_read(struct gif_info *gif, void *handle,\n const gif_read_function readfn, gif_bool skip_signature)\n{\n  struct gif_lzw_node *unpack_tree = NULL;\n  struct gif_color_table *table;\n  uint8_t buffer[256];\n  enum gif_error ret;\n  unsigned current = 0;\n\n  memset(gif, 0, sizeof(*gif));\n\n  /* Header */\n  if(!skip_signature)\n  {\n    if(readfn(gif->signature, 3, handle) < 3)\n      return GIF_IO;\n    if(readfn(gif->signature, 3, handle) < 3 || memcmp(gif->signature, \"GIF\", 3))\n      return GIF_SIGNATURE;\n  }\n\n  if(readfn(gif->version, 3, handle) < 3)\n    return GIF_IO;\n\n  if(gif->version[0] < '0' || gif->version[0] > '9' ||\n   gif->version[1] < '0' || gif->version[1] > '9' ||\n   gif->version[2] < 'a' || gif->version[2] > 'z')\n    return GIF_SIGNATURE;\n\n  gifdbg(\"GIF%3.3s\\n\", gif->version);\n\n  /* Logical Screen Descriptor */\n  if(readfn(buffer, 7, handle) < 7)\n    return GIF_IO;\n\n  gif->width          = gif_u16(buffer + 0);\n  gif->height         = gif_u16(buffer + 2);\n  gif->component_res  = ((buffer[4] & 0x70) >> 4) + 1;\n  gif->bg_color       = buffer[5];\n  gif->pixel_ratio    = buffer[6];\n\n  gifdbg(\"  Logical Screen\\t\\t w:%d h:%d rgbres:%d bgcolor:%d pixelratio:%d\\n\",\n   gif->width, gif->height, gif->component_res, gif->bg_color, gif->pixel_ratio);\n\n  /* Global Color Table */\n  if(buffer[4] & 0x80)\n  {\n    unsigned num_entries = 2 << (buffer[4] & 7);\n\n    table = gif_alloc_color_table(num_entries);\n    if(!table)\n      return GIF_MEM;\n\n    table->is_sorted = (buffer[4] & 0x08) >> 3;\n    table->num_entries = num_entries;\n    gif->global_color_table = table;\n\n    gifdbg(\"  Global Color Table\\t sorted:%d entries:%d\\n\",\n     table->is_sorted, table->num_entries);\n\n    if(readfn(table->entries, num_entries * 3, handle) < num_entries * 3)\n    {\n      ret = GIF_IO;\n      goto error;\n    }\n  }\n\n  /* All other blocks */\n  while(1)\n  {\n    if(readfn(buffer, 1, handle) < 1)\n      break;\n\n    switch(buffer[0])\n    {\n      case 0x21:        /* Extension Block */\n      {\n        unsigned type;\n        unsigned sz;\n        if(readfn(buffer, 2, handle) < 1)\n        {\n          ret = GIF_IO;\n          goto error;\n        }\n        type = buffer[0];\n        sz = buffer[1];\n\n        gifdbg(\"Extension\\t\\t\\t type:%u\\n\", type);\n\n        for(; sz != 0; sz = buffer[sz])\n        {\n          if(readfn(buffer, sz + 1, handle) < sz + 1)\n          {\n            ret = GIF_IO;\n            goto error;\n          }\n\n          switch(type)\n          {\n            default:\n            {\n              gifdbg(\"  ignoring subblock\\t size:%u\\n\", sz);\n              break;\n            }\n\n            case 0xF9:  /* Graphics Control Extension */\n            {\n              struct gif_graphics_control *control;\n              if(sz < 4)\n                continue;\n\n              ret = gif_add_control(gif, &control);\n              if(ret != GIF_OK)\n                goto error;\n\n              control->disposal_method    = (buffer[0] & 0x1C) >> 2;\n              control->input_required     = (buffer[0] & 0x02) >> 1;\n              control->delay_time         = gif_u16(buffer + 1);\n              control->transparent_color  = (buffer[0] & 1) ? buffer[3] : -1;\n\n              gifdbg(\"  Graphics Control\\t\\t size:%u disp:%d input:%d delay:%d tr:%d\\n\",\n               sz, control->disposal_method, control->input_required,\n               control->delay_time, control->transparent_color);\n\n              type += 0x100; // Only read one.\n              break;\n            }\n\n            case 0xFE:  /* Comment */\n            {\n              gifdbg(\"  Comment\\t\\t\\t size:%u\\n\", sz);\n              current = gif->num_blocks;\n              if(gif_add_comment(gif, sz) != GIF_OK)\n              {\n                type = 0; // Skip everything else\n                break;\n              }\n              type += 0x100;\n            }\n            /* fall-through */\n\n            case 0x1FE: /* Comment (data blocks) */\n            {\n              if(current >= gif->num_blocks || gif->blocks[current]->type != GIF_COMMENT)\n                continue;\n\n              if(gif_add_comment_append(&(gif->blocks[current]), buffer, sz) != GIF_OK)\n                type = 0; // Skip everything else\n\n              gifdbg(\"    text: %*.*s\\n\", (int)sz, (int)sz, buffer);\n              break;\n            }\n\n            case 0xFF:  /* Application */\n            {\n              struct gif_application *app;\n              if(sz < 11)\n              {\n                type = 0; // Skip everything else\n                break;\n              }\n              current = gif->num_blocks;\n              if(gif_add_application(gif) != GIF_OK)\n              {\n                type = 0;\n                break;\n              }\n              app = (struct gif_application *)gif->blocks[current];\n\n              memcpy(app->application, buffer, 11);\n              app->application[11] = '\\0';\n\n              gifdbg(\"  Application\\t\\t size:%u application:%s\\n\",\n               sz, app->application);\n\n              type += 0x100;\n              break;\n            }\n\n            case 0x1FF: /* Application (data blocks) */\n            {\n              if(current >= gif->num_blocks || gif->blocks[current]->type != GIF_APPLICATION)\n                continue;\n\n              gif_add_appdata(gif->blocks[current], buffer, sz);\n              gifdbg(\"  Application data\\t\\t size:%u\\n\", sz);\n              break;\n            }\n\n            case 0x01:  /* Plain Text */\n            {\n              struct gif_plaintext *layer;\n              if(sz < 12)\n              {\n                type = 0; // Skip everything else\n                continue;\n              }\n              current = gif->num_blocks;\n\n              ret = gif_add_plaintext(gif, 32, &layer);\n              if(ret != GIF_OK)\n                continue;\n\n              layer->left         = gif_u16(buffer + 0);\n              layer->top          = gif_u16(buffer + 2);\n              layer->width        = gif_u16(buffer + 4);\n              layer->height       = gif_u16(buffer + 6);\n              layer->char_width   = buffer[8];\n              layer->char_height  = buffer[9];\n              layer->fg_color     = buffer[10];\n              layer->bg_color     = buffer[11];\n\n              gifdbg(\"  Plain text\\t\\t size:%u width:%u height:%u\\n\", sz,\n               layer->width, layer->height);\n              gifdbg(\"\\t\\t\\t\\t left:%d top:%d charw:%d charh:%d fg:%d bg:%d\\n\",\n               layer->left, layer->top, layer->char_width, layer->char_height,\n               layer->fg_color, layer->bg_color);\n              type += 0x100;\n              break;\n            }\n\n            case 0x101:  /* Plain Text (data blocks) */\n            {\n              if(current >= gif->num_blocks || gif->blocks[current]->type != GIF_PLAINTEXT)\n                continue;\n\n              gif_add_plaintext_append(&(gif->blocks[current]), buffer, sz);\n              gifdbg(\"  Plain text data\\t\\t size:%u\\n\", sz);\n              break;\n            }\n          }\n        }\n        break;\n      }\n\n      case 0x2C:        /* Image Descriptor */\n      {\n        struct gif_image *layer;\n        uint8_t *dest;\n        unsigned width;\n        unsigned height;\n\n        if(readfn(buffer, 9, handle) < 9)\n        {\n          ret = GIF_IO;\n          goto error;\n        }\n\n        width = gif_u16(buffer + 4);\n        height = gif_u16(buffer + 6);\n\n        gifdbg(\"Image Descriptor\\t\\t width:%u height:%u\\n\", width, height);\n\n        ret = gif_add_image(gif, width * height, &layer);\n        if(ret != GIF_OK)\n          goto error;\n\n        layer->left           = gif_u16(buffer + 0);\n        layer->top            = gif_u16(buffer + 2);\n        layer->width          = width;\n        layer->height         = height;\n        layer->is_interlaced  = (buffer[8] & 0x40) >> 6;\n\n        gifdbg(\"\\t\\t\\t\\t left:%d top:%d interlaced:%d\\n\",\n         layer->left, layer->top, layer->is_interlaced);\n\n        /* Local Color Table */\n        if(buffer[8] & 0x80)\n        {\n          unsigned num_entries = 2 << (buffer[8] & 0x07);\n          table = gif_alloc_color_table(num_entries);\n          if(!table)\n          {\n            ret = GIF_MEM;\n            goto error;\n          }\n          layer->color_table = table;\n\n          table->is_sorted = (buffer[8] & 0x20) >> 5;\n          table->num_entries = num_entries;\n\n          gifdbg(\"  Local Color Table\\t sorted:%d entries:%d\\n\",\n           table->is_sorted, table->num_entries);\n\n          if(readfn(table->entries, num_entries * 3, handle) < num_entries * 3)\n          {\n            ret = GIF_IO;\n            goto error;\n          }\n        }\n\n        if(layer->is_interlaced)\n        {\n          dest = (uint8_t *)calloc(1, width * height);\n          if(!dest)\n          {\n            ret = GIF_MEM;\n            goto error;\n          }\n        }\n        else\n          dest = layer->pixels;\n\n        ret = gif_decode(dest, &unpack_tree, layer, handle, readfn);\n        if(ret != GIF_OK)\n        {\n          if(layer->is_interlaced)\n            free(dest);\n          goto error;\n        }\n\n        if(layer->is_interlaced)\n        {\n          gif_deinterlace(layer->pixels, dest, width, height);\n          free(dest);\n        }\n        break;\n      }\n\n      case 0x3B:        /* Trailer */\n        gifdbg(\"End of GIF\\n\\n\");\n        goto end_of_stream;\n    }\n  }\nend_of_stream:\n  gifdbg(\"  Comments start:%u end:%u\\n\", gif->comments_start, gif->comments_end);\n  gifdbg(\"  Application start:%u end:%u\\n\", gif->application_start, gif->application_end);\n  free(unpack_tree);\n  return GIF_OK;\n\nerror:\n  free(unpack_tree);\n  gif_close(gif);\n  return ret;\n}\n\n\nvoid gif_close(struct gif_info *gif)\n{\n  unsigned i, j;\n\n  free(gif->global_color_table);\n  gif->global_color_table = NULL;\n\n  if(gif->blocks)\n  {\n    for(i = 0; i < gif->num_blocks; i++)\n    {\n      struct gif_block *block = gif->blocks[i];\n      if(block)\n      {\n        if(block->type == GIF_APPLICATION)\n        {\n          struct gif_application *app = (struct gif_application *)block;\n          if(app->appdata)\n          {\n            for(j = 0; j < app->length; j++)\n              free(app->appdata[j]);\n            free(app->appdata);\n          }\n        }\n\n        if(block->type == GIF_IMAGE)\n          free(((struct gif_image *)block)->color_table);\n      }\n      free(block);\n    }\n    free(gif->blocks);\n    gif->blocks = NULL;\n  }\n}\n\n\n#ifndef GIF_NO_COMPOSITOR\n\n/***********************************************/\n/* Multi-image GIF compositor for GrafX2 GIFs. */\n/***********************************************/\n\n#define FP_SHIFT    13\n#define FP_1        (1 << FP_SHIFT)\n#define FP_AND      ((FP_1) - 1)\n#define FP_CEIL(x)  (((x) + FP_AND) & ~FP_AND)\n\nstatic const struct gif_graphics_control default_control =\n{\n  { GIF_CONTROL },    /* base */\n  1,                  /* do not dispose */\n  0,                  /* no user input */\n  0,                  /* no delay */\n  -1                  /* no transparent color */\n};\n\nstruct gif_composite\n{\n  struct gif_rgba *pixels;\n  unsigned width;\n  unsigned height;\n  unsigned scale;\n  uint32_t scalex;\n  uint32_t scaley;\n};\n\nstatic void gif_composite_scale(uint32_t *x, uint32_t *y, const struct gif_info *gif)\n{\n  if(gif->pixel_ratio < (64 - 15))\n  {\n    *x = FP_1;\n    *y = FP_1 * 64 / (gif->pixel_ratio + 15);\n  }\n  else\n  {\n    *x = FP_1 * (gif->pixel_ratio + 15) / 64;\n    *y = FP_1;\n  }\n}\n\nvoid gif_composite_size(unsigned *w, unsigned *h, const struct gif_info *gif)\n{\n  if(gif->pixel_ratio)\n  {\n    uint32_t x, y;\n    gif_composite_scale(&x, &y, gif);\n    *w = FP_CEIL((uint32_t)gif->width * x) >> FP_SHIFT;\n    *h = FP_CEIL((uint32_t)gif->height * y) >> FP_SHIFT;\n  }\n  else\n  {\n    *w = gif->width;\n    *h = gif->height;\n  }\n}\n\nstatic enum gif_error gif_composite_init(struct gif_composite * RESTRICT image,\n const struct gif_info *gif, const gif_alloc_function allocfn)\n{\n  uint64_t size;\n  memset(image, 0, sizeof(struct gif_composite));\n\n  if(gif->pixel_ratio)\n  {\n    image->scale = 1;\n    gif_composite_scale(&image->scalex, &image->scaley, gif);\n    gif_composite_size(&image->width, &image->height, gif);\n  }\n  else\n  {\n    image->width = gif->width;\n    image->height = gif->height;\n  }\n\n  size = (uint64_t)image->width * (uint64_t)image->height;\n  if(size > SIZE_MAX || size * sizeof(struct gif_rgba) > SIZE_MAX)\n    return GIF_MEM;\n\n  image->pixels = (struct gif_rgba *)allocfn(size * sizeof(struct gif_rgba));\n  if(!image->pixels)\n    return GIF_MEM;\n\n  return GIF_OK;\n}\n\nstatic void gif_composite_palette(struct gif_rgba palette[256],\n const struct gif_color_table *table)\n{\n  const struct gif_color *in;\n  unsigned count;\n  unsigned i;\n\n  memset(palette, 0, sizeof(struct gif_rgba) * 256);\n  if(!table)\n    return;\n\n  in = table->entries;\n  count = IMAGE_MIN(table->num_entries, 256);\n\n  for(i = 0; i < count; i++)\n  {\n    palette->r = in->r;\n    palette->g = in->g;\n    palette->b = in->b;\n    palette->a = 255;\n\n    palette++;\n    in++;\n  }\n}\n\nstatic void gif_composite_line(struct gif_rgba * RESTRICT dest,\n const struct gif_rgba *palette, const uint8_t *line, size_t length)\n{\n  size_t x;\n  for(x = 0; x < length; x++)\n    *(dest++) = palette[line[x]];\n}\n\nstatic void gif_composite_line_tr(struct gif_rgba * RESTRICT dest,\n const struct gif_rgba *palette, const uint8_t *line, size_t length,\n unsigned tcol)\n{\n  size_t x;\n  for(x = 0; x < length; x++)\n  {\n    if(line[x] != tcol)\n      *dest = palette[line[x]];\n    dest++;\n  }\n}\n\nstatic void gif_composite_line_sc(struct gif_rgba * RESTRICT dest,\n const struct gif_rgba *palette, const uint8_t *line, size_t length,\n unsigned left_offset, uint32_t scalex)\n{\n  // Align the FP counters to the starting position to avoid overflow.\n  uint32_t scale_stop = scalex * left_offset;\n  uint32_t scale_pos = FP_CEIL(scale_stop);\n  size_t x;\n\n  for(x = 0; x < length; x++)\n  {\n    struct gif_rgba col = palette[line[x]];\n    for(scale_stop += scalex; scale_pos < scale_stop; scale_pos += FP_1)\n      *(dest++) = col;\n  }\n}\n\nstatic void gif_composite_line_sc_tr(struct gif_rgba * RESTRICT dest,\n const struct gif_rgba *palette, const uint8_t *line, size_t length,\n unsigned left_offset, uint32_t scalex, unsigned tcol)\n{\n  // Align the FP counters to the starting position to avoid overflow.\n  uint32_t scale_stop = scalex * left_offset;\n  uint32_t scale_pos = FP_CEIL(scale_stop);\n  size_t x;\n\n  for(x = 0; x < length; x++)\n  {\n    struct gif_rgba col = palette[line[x]];\n    for(scale_stop += scalex; scale_pos < scale_stop; scale_pos += FP_1)\n    {\n      if(line[x] != tcol)\n        *dest = col;\n      dest++;\n    }\n  }\n}\n\nstatic void gif_composite_image(struct gif_composite * RESTRICT image,\n const struct gif_rgba *palette, const struct gif_info *gif,\n const struct gif_image *layer, const struct gif_graphics_control *control)\n{\n  const unsigned left = IMAGE_MIN(gif->width, layer->left);\n  const unsigned top = IMAGE_MIN(gif->height, layer->top);\n  const unsigned width = IMAGE_MIN(gif->width - left, layer->width);\n  const unsigned height = IMAGE_MIN(gif->height - top, layer->height);\n  const unsigned layer_pitch = layer->width;\n\n  const unsigned output_left = image->scale ? FP_CEIL(left * image->scalex) >> FP_SHIFT : left;\n  const unsigned output_top = image->scale ? FP_CEIL(top * image->scaley) >> FP_SHIFT : top;\n\n  struct gif_rgba *pixels = image->pixels + output_left + (image->width * output_top);\n  const uint8_t *line = layer->pixels;\n\n  const int scale = image->scale;\n  const int tcol = control->transparent_color;\n\n  unsigned y;\n\n  if(!width || !height)\n    return;\n\n  if(scale)\n  {\n    // Align the FP counters to the starting position to avoid overflow.\n    uint32_t scale_stop = image->scaley * top;\n    uint32_t scale_pos = FP_CEIL(scale_stop);\n\n    for(y = 0; y < height; y++)\n    {\n      // Draw each row multiple times :( it sucks, oh well.\n      for(scale_stop += image->scaley; scale_pos < scale_stop; scale_pos += FP_1)\n      {\n        if(tcol >= 0)\n          gif_composite_line_sc_tr(pixels, palette, line, width, left, image->scalex, tcol);\n        else\n          gif_composite_line_sc(pixels, palette, line, width, left, image->scalex);\n\n        pixels += image->width;\n      }\n      line += layer_pitch;\n    }\n  }\n  else\n  {\n    for(y = 0; y < height; y++)\n    {\n      if(tcol >= 0)\n        gif_composite_line_tr(pixels, palette, line, width, tcol);\n      else\n        gif_composite_line(pixels, palette, line, width);\n\n      pixels += image->width;\n      line += layer_pitch;\n    }\n  }\n}\n\nstatic void gif_composite_background(struct gif_composite * RESTRICT image,\n const struct gif_rgba *palette, const struct gif_info *gif)\n{\n  struct gif_rgba *dest = image->pixels;\n  struct gif_rgba col;\n  unsigned i;\n  unsigned j;\n\n  if(!gif->global_color_table)\n    return;\n\n  col = palette[gif->bg_color];\n\n  for(i = 0; i < image->height; i++)\n    for(j = 0; j < image->width; j++)\n      *(dest++) = col;\n}\n\nenum gif_error gif_composite(struct gif_rgba **_pixels,\n const struct gif_info *gif, const gif_alloc_function allocfn)\n{\n  struct gif_composite image;\n  struct gif_rgba global_palette[256];\n  struct gif_rgba layer_palette[256];\n  const struct gif_graphics_control *control = &default_control;\n  unsigned i;\n\n  enum gif_error err = gif_composite_init(&image, gif, allocfn);\n  if(err != GIF_OK)\n    return err;\n\n  *_pixels = image.pixels;\n\n  gif_composite_palette(global_palette, gif->global_color_table);\n  gif_composite_background(&image, global_palette, gif);\n\n  if(!gif->blocks)\n    return GIF_OK;\n\n  for(i = 0; i < gif->num_blocks; i++)\n  {\n    const struct gif_block *block = gif->blocks[i];\n    struct gif_rgba *palette = global_palette;\n\n    if(!block)\n      continue;\n\n    if(block->type == GIF_CONTROL)\n    {\n      control = (const struct gif_graphics_control *)block;\n      continue;\n    }\n    else\n\n    if(block->type == GIF_IMAGE)\n    {\n      const struct gif_image *layer = (const struct gif_image *)block;\n\n      if(layer->color_table)\n      {\n        gif_composite_palette(layer_palette, layer->color_table);\n        palette = layer_palette;\n      }\n      gif_composite_image(&image, palette, gif, layer, control);\n    }\n\n    control = &default_control;\n  }\n  return GIF_OK;\n}\n\n#endif /* !GIF_NO_COMPOSITOR */\n"
  },
  {
    "path": "src/utils/image_gif.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2022 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __UTILS_IMAGE_GIF_H\n#define __UTILS_IMAGE_GIF_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include <stdint.h>\n#include <stdlib.h>\n\n/* Read up to `num` bytes from `handle` into the buffer pointed to by `dest`.\n * Returns the number of bytes actually read from `handle`. */\ntypedef size_t (*gif_read_function)(void *dest, size_t num, void *handle);\n/* Allocate user-managed memory. Used for pixel buffers returned by gif_composite. */\ntypedef void *(*gif_alloc_function)(size_t sz);\ntypedef unsigned char gif_bool;\n\nenum gif_bool_values\n{\n  GIF_FALSE,\n  GIF_TRUE\n};\n\nenum gif_error\n{\n  GIF_OK = 0,\n  GIF_IO,\n  GIF_MEM,\n  GIF_INVALID,\n  GIF_SIGNATURE\n};\n\nenum gif_block_type\n{\n  GIF_CONTROL,\n  GIF_IMAGE,\n  GIF_PLAINTEXT,\n  GIF_COMMENT,\n  GIF_APPLICATION,\n};\n\n/* The loader does not normalize color tables to 24bpp.\n * This is performed in the compositor instead. */\nstruct gif_color\n{\n  uint8_t r; /* red component (0 to gif->component_max) */\n  uint8_t g; /* green component (0 to gif->component_max) */\n  uint8_t b; /* blue component (0 to gif->component_max) */\n};\n\nstruct gif_rgba /* for gif_composite output */\n{\n  uint8_t r; /* red component (0 to 255) */\n  uint8_t g; /* green component (0 to 255) */\n  uint8_t b; /* blue component (0 to 255) */\n  uint8_t a; /* alpha component (usually 0 or 255) */\n};\n\nstruct gif_color_table\n{\n  gif_bool  is_sorted;          /* If true, entries are sorted from most to least used. */\n  uint16_t  num_entries;        /* Number of entries in table (1 to 256). */\n  struct gif_color entries[1];  /* All color entries in table (flexible array member). */\n};\n\nstruct gif_block\n{\n  /* Use this field to determine the correct struct type. */\n  enum gif_block_type type;\n};\n\n/**\n * Graphics Control Extension\n * This affects the IMMEDIATELY following graphic-rendering block (either\n * an Image Descriptor or Plain Text Extension block). Certain libraries too\n * good to follow the standard only respect/generate a Graphics Control block\n * at the very end of the file (currently unsupported by gif_composite).\n */\nstruct gif_graphics_control\n{\n  struct gif_block base;\n\n  /*pack*/ uint8_t  disposal_method;    /* Image disposal method after display (0 to 7). */\n  /*pack*/ uint8_t  input_required;     /* Image requires user input to advance (0 or 1). */\n  /*  1 */ uint16_t delay_time;         /* Image delay duration in 10 ms increments. */\n  /*  3 */ int16_t  transparent_color;  /* Image transparent color (0 to 255, or -1 for none). */\n};\n\n/**\n * Image Descriptor\n */\nstruct gif_image\n{\n  struct gif_block base;\n\n  /*  0 */ uint16_t left;               /* Left corner X position on canvas. */\n  /*  2 */ uint16_t top;                /* Top corner Y position on canvas. */\n  /*  4 */ uint16_t width;              /* Width of image. */\n  /*  6 */ uint16_t height;             /* Height of image. */\n  /*pack*/ gif_bool is_interlaced;      /* GIF_TRUE if this image was stored interlaced. */\n\n  /* Local Color Table */\n  struct gif_color_table *color_table;\n\n  /* Data (uncompressed, deinterlaced, flexible array member). */\n  uint8_t pixels[1];\n};\n\n/**\n * Plain Text Extension\n */\nstruct gif_plaintext\n{\n  struct gif_block base;\n\n  /*  0 */ uint16_t left;               /* Left corner X position on canvas. */\n  /*  2 */ uint16_t top;                /* Top corner Y position on canvas. */\n  /*  4 */ uint16_t width;              /* Width of image. */\n  /*  6 */ uint16_t height;             /* Height of image. */\n  /*  8 */ uint8_t  char_width;         /* Width of characters, in pixels. */\n  /*  9 */ uint8_t  char_height;        /* Height of characters, in pixels. */\n  /* 10 */ uint8_t  fg_color;           /* Text foreground color (global table). */\n  /* 11 */ uint8_t  bg_color;           /* Text background color (global table). */\n\n  /* Data (flexible array member, \\0 added, can contain any byte value). */\n  unsigned length;\n  unsigned length_alloc;\n  char text[1];\n};\n\n/**\n * Comment Extension\n */\nstruct gif_comment\n{\n  struct gif_block base;\n\n  unsigned length;                      /* Length of comment */\n  unsigned length_alloc;\n  char comment[1];                      /* Comment (\\0 terminator appended). */\n};\n\nstruct gif_appdata\n{\n  uint8_t length;                       /* Length of application data. */\n  uint8_t appdata[1];                   /* Application data (\\0 appended). */\n};\n\n/**\n * Application Extension\n */\nstruct gif_application\n{\n  struct gif_block base;\n\n  char application[12];                 /* Application string (8) and code (3),\n                                         * ex. \"NETSCAPE2.0\" (\\0 appended). */\n\n  unsigned length;                      /* Number of application data blocks */\n  unsigned length_alloc;\n  struct gif_appdata **appdata;         /* Application data blocks (NULL if none) */\n};\n\nstruct gif_info\n{\n  /* Header */\n  /*  0 */ char     signature[3];       /* \"GIF\" */\n  /*  3 */ char     version[3];         /* \"87a\" or \"89a\" */\n\n  /* Logical Screen Descriptor */\n  /*  6 */ uint16_t width;              /* Canvas width. */\n  /*  8 */ uint16_t height;             /* Canvas height. */\n  /*pack*/ uint8_t  component_res;      /* Original image component bit depth? */\n  /* 11 */ uint8_t  bg_color;           /* Index of background color in global color table. */\n  /* 12 */ uint8_t  pixel_ratio;        /* Pixel aspect ratio. ratio = (val+15)/64 */\n\n  /* Global Color Table */\n  struct gif_color_table *global_color_table; /* NULL if not present. */\n\n  /* Blocks */\n  unsigned num_blocks_alloc;\n  unsigned num_blocks;                  /* Number of blocks in GIF. */\n  struct gif_block **blocks;            /* Array of blocks, or NULL if none. */\n\n  /* Useful block offsets - graphical blocks need to be processed sequentially\n   * but these might be useful to access directly. There may be unrelated\n   * blocks between the block offsets. If end <= start, none are present. */\n  unsigned comments_start;              /* Index of first comment. */\n  unsigned comments_end;                /* Index of last comment + 1. */\n  unsigned application_start;           /* Index of first application. */\n  unsigned application_end;             /* Index of last application + 1. */\n};\n\n\n/**\n * Get the error string for a given error.\n *\n * @param   err   a `gif_error` value.\n * @returns       a statically allocated string corresponding to the error.\n */\nconst char *gif_error_string(enum gif_error err);\n\n/**\n * Read all GIF data structures from the input handle into RAM.\n *\n * @param   gif             `gif_info` struct to store all GIF data to.\n * @param   handle          stream handle for readfn.\n * @param   readfn          function which returns bytes from `handle` stream.\n * @param   skip_signature  if `GIF_TRUE`, do not read \"GIF\" and immediately\n *                          attempt to read the version string instead. Use\n *                          this if the file format has already been IDed.\n * @returns                 `GIF_OK` on success, otherwise a `gif_error` value.\n */\nenum gif_error gif_read(struct gif_info *gif, void *handle,\n const gif_read_function readfn, gif_bool skip_signature);\n\n/**\n * Free all GIF data structures from a `gif_info` struct.\n *\n * @param   gif             `gif_info` struct to free GIF data from.\n */\nvoid gif_close(struct gif_info *gif);\n\n\n#ifndef GIF_NO_COMPOSITOR\n\n/**\n * Returns the dimensions of the output composited GIF.\n *\n * @param   w               width of output image is stored here.\n * @param   h               height of output image is stored here.\n * @param   gif             `gif_info` struct representing the GIF to composite.\n */\nvoid gif_composite_size(unsigned *w, unsigned *h, const struct gif_info *gif);\n\n/**\n * Composite a GIF into a linear array of `gif_rgba` pixels. The width and\n * height of the generated image can be obtained using `gif_composite_size`.\n * The pointer written to `pixels` must be freed by the user.\n *\n * @param   pixels          destination for generated composite pixel array.\n * @param   gif             `gif_info` struct representing the GIF to composite.\n * @param   allocfn         allocator function for the `gif_rgba` array.\n * @returns                 `GIF_OK` on success, otherwise a `gif_error` value.\n */\nenum gif_error gif_composite(struct gif_rgba **pixels, const struct gif_info *gif,\n const gif_alloc_function allocfn);\n\n#endif /* !GIF_NO_COMPOSITOR */\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* __UTILS_IMAGE_GIF_H */\n"
  },
  {
    "path": "src/utils/png2smzx.c",
    "content": "/* Copyright (C) 2010 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n\n#include \"../compat.h\"\n\n#include \"image_file.h\"\n#include \"smzxconv.h\"\n#include \"utils_alloc.h\"\n\n#ifdef CONFIG_PLEDGE_UTILS\n#include <unistd.h>\n#define PROMISES \"stdio rpath wpath cpath\"\n#endif\n\n#ifndef MAX_PATH\n#define MAX_PATH 512\n#endif\n\nint main(int argc, char **argv)\n{\n  FILE *fp;\n  unsigned char mzmhead[16] =\n  {\n    'M', 'Z', 'M', '2', /* Magic */\n    0, 0, /* Width */\n    0, 0, /* Height */\n    0, 0, 0, 0, 0, 1, 0, 0 /* Stuff */\n  };\n\n  char input_file_name[MAX_PATH] = { '\\0' };\n  char output_mzm_name[MAX_PATH] = { '\\0' };\n  char output_chr_name[MAX_PATH] = { '\\0' };\n  char output_pal_name[MAX_PATH] = { '\\0' };\n  char output_base_name[MAX_PATH] = { '\\0' };\n  const char *ext;\n\n  int skip_char = -1;\n\n  struct image_file img;\n  enum image_error ret;\n  uint32_t i, t;\n  mzx_tile *tile;\n  mzx_glyph chr[256];\n  mzx_color pal[256];\n  smzx_converter *c;\n\n  if(argc < 2 || (argv[1][0] == '-' && argv[1][1] != '\\0'))\n  {\n    fprintf(stderr,\n\"png2smzx Image Conversion Utility\\n\\n\"\n\n\"Usage: %s <in.png|-> [<out> | <out.mzm> \"\n\"[<out.chr] [<out.pal>]] [options]\\n\\n\"\n\n\"  If the input file is '-', it will be read from stdin.\\n\\n\"\n\n\"  Supported file formats are:\\n\\n\"\n\n\"  * PNG\\n\"\n\"  * GIF (multi-image GIFs will be flattened into a single image;\\n\"\n\"         non-square pixel aspect ratios are supported via upscaling)\\n\"\n\"  * BMP (1bpp, 2bpp, 4bpp, 8bpp, 16bpp, 24bpp, 32bpp, RLE8, RLE4, bitfields)\\n\"\n\"  * TGA (all)\\n\"\n\"  * Netpbm/PNM (.pbm, .pgm, .ppm, .pnm, .pam)\\n\"\n\"  * farbfeld (.ff)\\n\"\n\"\\n\"\n\"Options:\\n\\n\"\n\n\"  --skip-char=[value 0-255]\tSkip this char in the conversion process.\\n\"\n\"\\n\",\n\n      argv[0]\n    );\n    return 1;\n  }\n\n  snprintf(input_file_name, MAX_PATH, \"%s\", argv[1]);\n  input_file_name[MAX_PATH - 1] = '\\0';\n\n  // Read the input files\n  for(i = 2; i < (unsigned int)argc; i++)\n  {\n    if(strlen(argv[i]) >= 4)\n    {\n      ext = argv[i] + strlen(argv[i]) - 4;\n\n      if(!strcasecmp(ext, \".mzm\"))\n        snprintf(output_mzm_name, MAX_PATH, \"%s\", argv[i]);\n      if(!strcasecmp(ext, \".chr\"))\n        snprintf(output_chr_name, MAX_PATH, \"%s\", argv[i]);\n      if(!strcasecmp(ext, \".pal\"))\n        snprintf(output_pal_name, MAX_PATH, \"%s\", argv[i]);\n    }\n\n    if(!strncasecmp(argv[i], \"--skip-char\", 11) &&\n     (argv[i][11] == '=') && argv[i][12])\n    {\n      skip_char = strtol(argv[i] + 12, NULL, 10) % 256;\n      fprintf(stderr, \"Skipping char %i.\", skip_char);\n    }\n  }\n  output_mzm_name[MAX_PATH - 1] = '\\0';\n  output_chr_name[MAX_PATH - 1] = '\\0';\n  output_pal_name[MAX_PATH - 1] = '\\0';\n\n  // Fill in the missing filenames\n  if(output_mzm_name[0])\n  {\n    size_t base_name_len = strlen(output_mzm_name) - 4;\n\n    snprintf(output_base_name, MAX_PATH, \"%s\", output_mzm_name);\n    output_base_name[base_name_len] = '\\0';\n  }\n  else\n  {\n    const char *src = input_file_name;\n    if(!strcmp(input_file_name, \"-\"))\n      src = \"out\";\n\n    if(argc >= 3 && argv[2] && argv[2][0] != '-')\n      src = argv[2];\n\n    snprintf(output_base_name, MAX_PATH - 5, \"%s\", src);\n    output_base_name[MAX_PATH - 6] = '\\0';\n\n    snprintf(output_mzm_name, MAX_PATH, \"%s.mzm\", output_base_name);\n    output_pal_name[0] = '\\0';\n    output_chr_name[0] = '\\0';\n  }\n\n  if(!output_chr_name[0])\n  {\n    snprintf(output_chr_name, MAX_PATH, \"%s.chr\", output_base_name);\n  }\n\n  if(!output_pal_name[0])\n  {\n    snprintf(output_pal_name, MAX_PATH, \"%s.pal\", output_base_name);\n  }\n\n#ifdef CONFIG_PLEDGE_UTILS\n#ifdef PLEDGE_HAS_UNVEIL\n  if(unveil(input_file_name, \"r\") || unveil(output_mzm_name, \"cw\") ||\n   unveil(output_chr_name, \"cw\") || unveil(output_pal_name, \"cw\") ||\n   unveil(NULL, NULL))\n  {\n    fprintf(stderr, \"ERROR: Failed unveil!\\n\");\n    return 1;\n  }\n#endif\n\n  if(pledge(PROMISES, \"\"))\n  {\n    fprintf(stderr, \"ERROR: Failed pledge!\\n\");\n    return 1;\n  }\n#endif\n\n  // Do stuff\n  ret = load_image_from_file(input_file_name, &img, NULL);\n  if(ret)\n  {\n    fprintf(stderr, \"Error reading image: %s\\n\", image_error_string(ret));\n    return 1;\n  }\n\n  if((img.width % 8) || (img.height % 14))\n  {\n    fprintf(stderr, \"Image not divisible by 8x14; cropping...\\n\");\n    if(img.width % 8)\n    {\n      t = img.width / 8 * 8;\n      for(i = 1; i < img.height; i++)\n        memmove(img.data + i * t, img.data + i * img.width, sizeof(struct rgba_color) * t);\n    }\n  }\n  img.width /= 8;\n  img.height /= 14;\n\n  c = smzx_convert_init(img.width, img.height, 0, skip_char, 256, 0, 16);\n  if(!c)\n  {\n    fprintf(stderr, \"Error initializing converter.\\n\");\n    image_free(&img);\n    return 1;\n  }\n\n  tile = malloc(sizeof(mzx_tile) * img.width * img.height);\n  if(!tile)\n  {\n    fprintf(stderr, \"Error allocating tile buffer.\\n\");\n    smzx_convert_free(c);\n    image_free(&img);\n    return 1;\n  }\n\n  smzx_convert(c, img.data, tile, chr, pal);\n  smzx_convert_free(c);\n  image_free(&img);\n  mzmhead[4] = img.width & 0xFF;\n  mzmhead[5] = img.width >> 8;\n  mzmhead[6] = img.height & 0xFF;\n  mzmhead[7] = img.height >> 8;\n\n  fp = fopen_unsafe(output_mzm_name, \"wb\");\n  if(!fp)\n  {\n    fprintf(stderr, \"Error opening MZM file.\\n\");\n    free(tile);\n    return 1;\n  }\n\n  if(fwrite(mzmhead, sizeof(mzmhead), 1, fp) != 1)\n  {\n    fprintf(stderr, \"Error writing MZM header.\\n\");\n    fclose(fp);\n    free(tile);\n    return 1;\n  }\n\n  if(fwrite(tile, sizeof(mzx_tile) * img.width * img.height, 1, fp) != 1)\n  {\n    fprintf(stderr, \"Error writing MZM data.\\n\");\n    fclose(fp);\n    free(tile);\n    return 1;\n  }\n  free(tile);\n  fclose(fp);\n\n  fp = fopen_unsafe(output_chr_name, \"wb\");\n  if(!fp)\n  {\n    fprintf(stderr, \"Error opening CHR file.\\n\");\n    return 1;\n  }\n\n  if(fwrite(chr, sizeof(chr), 1, fp) != 1)\n  {\n    fprintf(stderr, \"Error writing CHR data.\\n\");\n    fclose(fp);\n    return 1;\n  }\n  fclose(fp);\n\n  fp = fopen_unsafe(output_pal_name, \"wb\");\n  if(!fp)\n  {\n    fprintf(stderr, \"Error opening PAL file.\\n\");\n    return 1;\n  }\n\n  if(fwrite(pal, sizeof(pal), 1, fp) != 1)\n  {\n    fprintf(stderr, \"Error writing PAL data.\\n\");\n    fclose(fp);\n    return 1;\n  }\n  fclose(fp);\n\n  return 0;\n}\n\n"
  },
  {
    "path": "src/utils/smzxconv.c",
    "content": "/* Copyright (C) 2010 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"image_file.h\"\n#include \"smzxconv.h\"\n\ntypedef struct {\n\tint r;\n\tint g;\n\tint b;\n\tint c;\n} rgba_accum;\n\ntypedef struct {\n\tmzx_glyph g;\n\tunsigned short w;\n\tunsigned int d;\n} glyph_dist;\n\nstruct _smzx_converter {\n\tint w, h;\n\tint iw, ih;\n\tint tsiz, isiz;\n\tint chroff, chrskip, chrlen;\n\tint chrprelen;\n\tint clroff, clrlen;\n\tstruct rgba_color *src;\n\tunsigned char *dest;\n\tunsigned char *hist;\n\tint *lerr;\n\tglyph_dist *tgly;\n\tglyph_dist *cset;\n};\n\nstatic int gdist_table[256][256];\nstatic int grdist_table[256][256];\n\nsmzx_converter *smzx_convert_init (int w, int h, int chroff, int chrskip,\n\tint chrlen, int clroff, int clrlen) {\n\tsmzx_converter *c;\n\tint chrprelen = chrlen;\n\tint iw = w * 4;\n\tint ih = h * 14;\n\tint tsiz = w * h;\n\tint isiz = iw * ih;\n\tint setsize = tsiz;\n\tif ((chrskip > chroff) && (chrskip < chroff + chrlen)) chrprelen--;\n\tif (setsize < chroff + chrlen) setsize = chroff + chrlen;\n\tc = malloc(sizeof(smzx_converter));\n\tif (!c) return NULL;\n\tc->w = w;\n\tc->h = h;\n\tc->iw = iw;\n\tc->ih = ih;\n\tc->tsiz = tsiz;\n\tc->isiz = isiz;\n\tc->chroff = chroff;\n\tc->chrskip = chrskip;\n\tc->chrlen = chrlen;\n\tc->chrprelen = chrprelen;\n\tc->clroff = clroff;\n\tc->clrlen = clrlen;\n\tc->src = malloc(sizeof(struct rgba_color) * isiz);\n\tc->dest = malloc(isiz);\n\tc->hist = malloc(isiz);\n\tc->lerr = malloc(sizeof(int) * (iw + 2) * 2);\n\tc->tgly = malloc(sizeof(glyph_dist) * setsize);\n\tc->cset = malloc(sizeof(glyph_dist) * setsize);\n\tif (!c->src || !c->dest || !c->hist || !c->tgly || !c->cset) {\n\t\tfree(c->cset);\n\t\tfree(c->tgly);\n\t\tfree(c->hist);\n\t\tfree(c->dest);\n\t\tfree(c->src);\n\t\tfree(c);\n\t\treturn NULL;\n\t}\n\treturn c;\n}\n\nstatic int ccmp (const void *av, const void *bv) {\n\tconst unsigned char *a = av;\n\tconst unsigned char *b = bv;\n\tif (*a < *b) return -1;\n\tif (*a > *b) return 1;\n\treturn 0;\n}\n\nstatic void init_gdist_tables(void)\n{\n\tstatic int init = 0;\n\tconst unsigned char swap[4] = {0, 2, 1, 3};\n\tint x, y;\n\n\tif (!init) {\n\t\tfor (y = 0; y < 4; y++)\n\t\t\tfor (x = 0; x < 4; x++)\n\t\t\t\tgdist_table[x][y] = (swap[x] - swap[y]) * (swap[x] - swap[y]);\n\t\tfor (y = 0; y < 256; y++) {\n\t\t\tfor (x = 0; x < 256; x++) {\n\t\t\t\tgdist_table[x][y] = gdist_table[x&3][y&3] + gdist_table[(x>>2)&3][(y>>2)&3]\n\t\t\t\t\t+ gdist_table[(x>>4)&3][(y>>4)&3] + gdist_table[x>>6][y>>6];\n\t\t\t\tgrdist_table[x][y ^ 0xff] = gdist_table[x][y];\n\t\t\t}\n\t\t}\n\t\tinit = 1;\n\t}\n}\n\nstatic int gdist (const mzx_glyph a, const mzx_glyph b) {\n\tint i, res;\n\tres = 0;\n\tinit_gdist_tables();\n\tfor (i = 0; i < 14; i++)\n\t\tres += gdist_table[a[i]][b[i]];\n\treturn res;\n}\n\nstatic int grdist (const mzx_glyph a, const mzx_glyph b) {\n\tint i, res;\n\tres = 0;\n\tinit_gdist_tables();\n\tfor (i = 0; i < 14; i++)\n\t\tres += grdist_table[a[i]][b[i]];\n\treturn res;\n}\n\nstatic int gpdist (const mzx_glyph a, const mzx_glyph b) {\n\tint res, rev;\n\tres = gdist(a, b);\n\trev = grdist(a, b);\n\treturn (res < rev) ? res : rev;\n}\n\nstatic int gcmp (const void *av, const void *bv) {\n\tconst mzx_glyph blank = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};\n\tconst glyph_dist *a = av;\n\tconst glyph_dist *b = bv;\n\tint ad, bd, i;\n\tif (a->d > b->d) return -1;\n\tif (a->d < b->d) return 1;\n\tad = gdist(a->g, blank);\n\tbd = gdist(b->g, blank);\n\tif (ad < bd) return -1;\n\tif (ad > bd) return 1;\n\tfor (i = 0; i < 14; i++) {\n\t\tif (a->g[i] < b->g[i]) return -1;\n\t\tif (a->g[i] > b->g[i]) return 1;\n\t}\n\treturn 0;\n}\n\nint smzx_convert (smzx_converter *c, const struct rgba_color *img, mzx_tile *tile,\n\tmzx_glyph *chr, mzx_color *pal) {\n\tconst mzx_glyph blank = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};\n\tconst mzx_glyph full = {255, 255, 255, 255, 255, 255, 255, 255, 255,\n\t\t255, 255, 255, 255, 255};\n\tconst struct rgba_color *ipx;\n\tstruct rgba_color *spx;\n\tunsigned char *dpx, *hpx;\n\tint *epx;\n\tunsigned char gpal[16], rpal[256];\n\tunsigned char lut[256];\n\tunsigned char bg, fg, tmp;\n\trgba_accum apal[256];\n\tint i, j, u, v, x, y;\n\n\tif (!c) return 0;\n\n\tmemset(apal, 0, sizeof(rgba_accum) * 256);\n\n\t/* Half-width copy image */\n\tfor (i = 0, ipx = img, spx = c->src; i < c->isiz;\n\t\ti++, ipx += 2, spx++) {\n\t\tspx->r = (ipx[0].r * ipx[0].a + ipx[1].r * ipx[1].a) / 2040;\n\t\tspx->g = (ipx[0].g * ipx[0].a + ipx[1].g * ipx[1].a) / 2040;\n\t\tspx->b = (ipx[0].b * ipx[0].a + ipx[1].b * ipx[1].a) / 2040;\n\t\tspx->a = 63;\n\t}\n\t/* Grayscale image */\n\tfor (i = 0, spx = c->src, dpx = c->dest; i < c->isiz;\n\t\ti++, spx++, dpx++)\n\t\t*dpx = (spx->r * 30 + spx->g * 59 + spx->b * 11) * spx->a\n\t\t\t/ 6300;\n\t/* Create histogram-based grayscale palette */\n\tmemcpy(c->hist, c->dest, c->isiz);\n\tqsort(c->hist, c->isiz, 1, ccmp);\n\tfor (i = 0; i < c->clrlen; i++)\n\t\tgpal[i] = (c->hist[i*(c->isiz-1)/(c->clrlen-1)] * 2\n\t\t\t+ (c->hist[0] * (c->clrlen - 1 - i)\n\t\t\t+ c->hist[c->isiz-1] * i) / (c->clrlen - 1)) / 3;\n\tmemmove(gpal + c->clroff, gpal, c->clrlen);\n\t/* Find min/max values for each tile and assign tile colors accordingly */\n\tfor (v = 0; v < c->h; v++) {\n\t\tfor (u = 0; u < c->w; u++) {\n\t\t\tbg = 255;\n\t\t\tfg = 0;\n\t\t\tdpx = c->dest + u * 4 + v * 14 * c->iw;\n\t\t\tfor (y = 0; y < 14; y++) {\n\t\t\t\tfor (x = 0; x < 4; x++) {\n\t\t\t\t\ttmp = dpx[x+y*c->iw];\n\t\t\t\t\tif (bg > tmp) bg = tmp;\n\t\t\t\t\tif (fg < tmp) fg = tmp;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (i = c->clroff; i < c->clroff + c->clrlen; i++)\n\t\t\t\tif (gpal[i] <= bg) tmp = i;\n\t\t\ttile[u+v*c->w].clr = tmp << 4;\n\t\t\tfor (i = c->clroff + c->clrlen - 1; i >= c->clroff;\n\t\t\t\ti--)\n\t\t\t\tif (gpal[i] >= fg) tmp = i;\n\t\t\ttile[u+v*c->w].clr |= tmp;\n\t\t}\n\t}\n\t/* Create SMZX interpolated palette from tile histograms */\n\tfor (i = c->clroff; i < c->clroff + c->clrlen; i++)\n\t\trpal[(i<<4)|i] = gpal[i];\n\tfor (i = c->clroff; i < c->clroff + c->clrlen - 1; i++) {\n\t\tfor (j = i + 1; j < c->clroff + c->clrlen; j++) {\n\t\t\thpx = c->hist;\n\t\t\tfor (x = v = 0; v < c->h; v++) for (u = 0; u < c->w; u++, x++) {\n\t\t\t\tif ((tile[x].clr == ((i << 4) | j))\n\t\t\t\t\t|| (tile[x].clr == ((j << 4) | i))) {\n\t\t\t\t\tdpx = c->dest + u * 4 + v * 14 * c->iw;\n\t\t\t\t\tfor (y = 0; y < 14; y++, hpx += 4, dpx += c->iw)\n\t\t\t\t\t\tmemcpy(hpx, dpx, 4);\n\t\t\t\t}\n\t\t\t}\n\t\t\tx = hpx - c->hist;\n\t\t\tif (x) {\n\t\t\t\tqsort(c->hist, x, 1, ccmp);\n\t\t\t\trpal[(j<<4)|i] = (c->hist[(x-1)/3] * 2\n\t\t\t\t\t+ (c->hist[0] * 2 + c->hist[x-1]) / 3)\n\t\t\t\t\t/ 3;\n\t\t\t\trpal[(i<<4)|j] = (c->hist[2*(x-1)/3] * 2\n\t\t\t\t\t+ (c->hist[0] + c->hist[x-1] * 2) / 3)\n\t\t\t\t\t/ 3;\n\t\t\t} else {\n\t\t\t\trpal[(j<<4)|i] = (gpal[i] * 2 + gpal[j]) / 3;\n\t\t\t\trpal[(i<<4)|j] = (gpal[i] + gpal[j] * 2) / 3;\n\t\t\t}\n\t\t}\n\t}\n\t/* Create characters for each tile (w/ Floyd-Steinberg dithering) */\n\tmemset(c->lerr + c->iw + 1, 0, sizeof(int) * c->iw);\n\tfor (y = 0, dpx = c->dest, hpx = c->hist; y < c->ih; y++) {\n\t\tv = y / 14;\n\t\tmemcpy(c->lerr, c->lerr + c->iw + 1, sizeof(int) * c->iw);\n\t\tmemset(c->lerr + c->iw + 1, 0, sizeof(int) * c->iw);\n\t\tfor (x = 0, epx = c->lerr; x < c->iw; x++, dpx++, hpx++, epx++) {\n\t\t\tu = x / 4;\n\t\t\ttmp = tile[u+v*c->w].clr;\n\t\t\tbg = tmp >> 4;\n\t\t\tfg = tmp & 15;\n\t\t\ttmp = (rpal[(bg<<4)|bg] + rpal[(fg<<4)|fg]) / 2;\n\t\t\t*epx += *dpx;\n\t\t\tif (*epx >= tmp) {\n\t\t\t\ttmp = (rpal[(bg<<4)|fg] + rpal[(fg<<4)|fg]) / 2;\n\t\t\t\tif (*epx >= tmp) *hpx = (fg << 4) | fg;\n\t\t\t\telse *hpx = (bg << 4) | fg;\n\t\t\t} else {\n\t\t\t\ttmp = (rpal[(bg<<4)|bg] + rpal[(fg<<4)|bg]) / 2;\n\t\t\t\tif (*epx >= tmp) *hpx = (fg << 4) | bg;\n\t\t\t\telse *hpx = (bg << 4) | bg;\n\t\t\t}\n\t\t\tif (gpal[bg] == gpal[fg]) *hpx = (bg << 4) | bg;\n\t\t\t*epx -= rpal[*hpx];\n\t\t\tepx[1] += *epx * 7 / 16;\n\t\t\tepx[c->iw] += *epx * 3 / 16;\n\t\t\tepx[c->iw+1] += *epx * 5 / 16;\n\t\t\tepx[c->iw+2] += *epx - (*epx * 15 / 16);\n\t\t}\n\t}\n\t/* Convert characters to SMZX format */\n\tfor (v = 0; v < c->h; v++) {\n\t\tfor (u = 0; u < c->w; u++) {\n\t\t\thpx = c->hist + u * 4 + v * 14 * c->iw;\n\t\t\ttmp = tile[u+v*c->w].clr;\n\t\t\tbg = tmp >> 4;\n\t\t\tfg = tmp & 15;\n\t\t\tlut[(bg<<4)|bg] = 0;\n\t\t\tlut[(fg<<4)|bg] = 2;\n\t\t\tlut[(bg<<4)|fg] = 1;\n\t\t\tlut[(fg<<4)|fg] = 3;\n\t\t\tfor (y = 0; y < 14; y++, hpx += c->iw)\n\t\t\t\tc->tgly[u+v*c->w].g[y] = (lut[hpx[0]] << 6)\n\t\t\t\t\t| (lut[hpx[1]] << 4)\n\t\t\t\t\t| (lut[hpx[2]] << 2) | lut[hpx[3]];\n\t\t\tc->tgly[u+v*c->w].w = abs(gpal[fg] - gpal[bg]);\n\t\t}\n\t}\n\t/* Reduce character set to specified size */\n\tmemcpy(c->cset, c->tgly, sizeof(glyph_dist) * c->tsiz);\n\tfor (i = 0; i < c->tsiz; i++) {\n\t\tc->cset[i].d = 32767;\n\t\tif (gdist(c->cset[i].g, blank) > gdist(c->cset[i].g, full))\n\t\t\tfor (j = 0; j < 14; j++)\n\t\t\t\tc->cset[i].g[j] ^= 0xFF;\n\t}\n\tqsort(c->cset, c->tsiz, sizeof(glyph_dist), gcmp);\n\tfor (i = 0; i < c->tsiz - 1; i++) {\n\t\tfor (j = i + 1; j < c->tsiz; j++) {\n\t\t\tx = gpdist(c->cset[i].g, c->cset[j].g);\n\t\t\tif ((unsigned int)x < c->cset[j].d) c->cset[j].d = x;\n\t\t}\n\t}\n\tfor (i = 0; i < c->tsiz; i++)\n\t\tc->cset[i].d *= c->cset[i].w;\n\tqsort(c->cset, c->tsiz, sizeof(glyph_dist), gcmp);\n\tfor (i = 0; i < c->chrprelen; i++)\n\t\tc->cset[i].d = 0;\n\tqsort(c->cset, c->chrprelen, sizeof(glyph_dist), gcmp);\n\tmemmove(c->cset + c->chroff, c->cset,\n\t\tsizeof(glyph_dist) * c->chrprelen);\n\tif (c->chrprelen < c->chrlen) {\n\t\tmemmove(c->cset + c->chrskip + 1, c->cset + c->chrskip,\n\t\t\tsizeof(glyph_dist) * (c->chroff + c->chrprelen\n\t\t\t- c->chrskip));\n\t\tmemset(c->cset + c->chrskip, 0, sizeof(glyph_dist));\n\t}\n\tfor (i = 0; i < c->chrlen; i++)\n\t\tmemcpy(chr[i], c->cset[i+c->chroff].g, sizeof(mzx_glyph));\n\t/* Map tiles to characters from reduced set */\n\tfor (i = u = 0; i < c->tsiz; i++) {\n\t\tx = gdist(c->tgly[i].g, c->cset->g) + 1;\n\t\tfor (j = c->chroff; j < c->chroff + c->chrlen; j++) {\n\t\t\tif (j == c->chrskip) continue;\n\t\t\ty = gdist(c->tgly[i].g, c->cset[j].g);\n\t\t\tif (y < x) {\n\t\t\t\tx = y;\n\t\t\t\ttile[i].chr = j;\n\t\t\t\tu = 0;\n\t\t\t}\n\t\t\ty = grdist(c->tgly[i].g, c->cset[j].g);\n\t\t\tif (y < x) {\n\t\t\t\tx = y;\n\t\t\t\ttile[i].chr = j;\n\t\t\t\tu = 1;\n\t\t\t}\n\t\t}\n\t\tif (u) tile[i].clr = (tile[i].clr << 4) | (tile[i].clr >> 4);\n\t}\n\t/* Draw resulting grayscale image */\n\tfor (v = 0; v < c->h; v++) {\n\t\tfor (u = 0; u < c->w; u++) {\n\t\t\thpx = c->hist + u * 4 + v * 14 * c->iw;\n\t\t\ttmp = tile[u+v*c->w].clr;\n\t\t\tbg = tmp >> 4;\n\t\t\tfg = tmp & 15;\n\t\t\tlut[0] = (bg << 4) | bg;\n\t\t\tlut[2] = (fg << 4) | bg;\n\t\t\tlut[1] = (bg << 4) | fg;\n\t\t\tlut[3] = (fg << 4) | fg;\n\t\t\tfor (y = 0; y < 14; y++, hpx += c->iw) {\n\t\t\t\thpx[0] = lut[c->cset[tile[u+v*c->w].chr].g[y]>>6];\n\t\t\t\thpx[1] = lut[(c->cset[tile[u+v*c->w].chr].g[y]>>4)&3];\n\t\t\t\thpx[2] = lut[(c->cset[tile[u+v*c->w].chr].g[y]>>2)&3];\n\t\t\t\thpx[3] = lut[c->cset[tile[u+v*c->w].chr].g[y]&3];\n\t\t\t}\n\t\t}\n\t}\n\t/* Calculate average color of pixels assigned to each index */\n\tfor (i = 0, spx = c->src, dpx = c->dest, hpx = c->hist; i < c->isiz;\n\t\ti++, spx++, dpx++, hpx++) {\n\t\t\tapal[*hpx].r += spx->r;\n\t\t\tapal[*hpx].g += spx->g;\n\t\t\tapal[*hpx].b += spx->b;\n\t\t\tapal[*hpx].c += *dpx;\n\t}\n\tfor (i = 0; i < 256; i++) {\n\t\tbg = i >> 4;\n\t\tfg = i & 15;\n\t\tif (bg < c->clroff) continue;\n\t\tif (fg < c->clroff) continue;\n\t\tif (bg >= c->clroff + c->clrlen) continue;\n\t\tif (fg >= c->clroff + c->clrlen) continue;\n\t\tif (apal[i].c) {\n\t\t\tapal[i].r = apal[i].r * rpal[i] / apal[i].c;\n\t\t\tapal[i].g = apal[i].g * rpal[i] / apal[i].c;\n\t\t\tapal[i].b = apal[i].b * rpal[i] / apal[i].c;\n\t\t\tif (apal[i].r > 63) apal[i].r = 63;\n\t\t\tif (apal[i].g > 63) apal[i].g = 63;\n\t\t\tif (apal[i].b > 63) apal[i].b = 63;\n\t\t} else\n\t\t\tapal[i].r = apal[i].g = apal[i].b = rpal[i];\n\t\tpal[i].r = apal[i].r;\n\t\tpal[i].g = apal[i].g;\n\t\tpal[i].b = apal[i].b;\n\t}\n\treturn 1;\n}\n\nvoid smzx_convert_free (smzx_converter *c) {\n\tif (!c) return;\n\tfree(c->src);\n\tfree(c->dest);\n\tfree(c->hist);\n\tfree(c->tgly);\n\tfree(c->cset);\n\tfree(c);\n}\n"
  },
  {
    "path": "src/utils/smzxconv.h",
    "content": "/* Copyright (C) 2010 Alan Williams <mralert@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __SMZXCONV_H\n#define __SMZXCONV_H\n\nstruct rgba_color;\n\ntypedef struct {\n  unsigned char chr;\n  unsigned char clr;\n} mzx_tile;\n\ntypedef unsigned char mzx_glyph[14];\n\ntypedef struct {\n  unsigned char r;\n  unsigned char g;\n  unsigned char b;\n} mzx_color;\n\ntypedef struct _smzx_converter smzx_converter;\n\nsmzx_converter *smzx_convert_init (int w, int h, int chroff, int chrskip,\n int chrlen, int clroff, int clrlen);\nint smzx_convert (smzx_converter *c, const struct rgba_color *img, mzx_tile *tile,\n mzx_glyph *chr, mzx_color *pal);\nvoid smzx_convert_free (smzx_converter *c);\n\n#endif /* __SMZXCONV_H */\n"
  },
  {
    "path": "src/utils/txt2hlp.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2012-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <ctype.h>\n\n#include \"../compat.h\"\n\n#ifdef CONFIG_PLEDGE_UTILS\n#include <unistd.h>\n#define PROMISES \"stdio rpath wpath cpath\"\n#endif\n\n// Cleaned up/revised txt2hlp.cpp for compiling outside of DOS\n\nstatic void fputw(int src, FILE *fp)\n{\n  fputc(src & 0xFF, fp);\n  fputc(src >> 8, fp);\n}\n\n// Custom fgetc function to dump \\r's and replace \\xFF's and /x1's.\n\nstatic int fgetc2(FILE *fp)\n{\n  int current_char = fgetc(fp);\n  switch(current_char)\n  {\n    case '\\r':\n    {\n      return fgetc2(fp);\n    }\n\n    case 255:\n    {\n      return 32;\n    }\n\n    case 1:\n    {\n      return 2;\n    }\n\n    default:\n    {\n      return current_char;\n    }\n  }\n}\n\n#define fgetc(f) fgetc2(f)\n\nint main(int argc, char *argv[])\n{\n  // Temp vars\n  char previous_char;\n  char current_char;\n  int colon_count;\n\n  int str_len;\n  char tstr[81] = \"\";\n  int tlong;\n  int i;\n\n  // Source/dest files open. Now scan source for number of filenames.\n  // A new file is designated by a # as the first character of a line.\n\n  int num_files = 1;\n\n  // Count context sensitive links too. A link is designated by a : as\n  // the first character of a line, followed by three numerals, followed\n  // by the end of the line. (a \\n)\n\n  int num_links = 0;\n\n  FILE *source;\n  FILE *dest;\n\n  char curr_file[13] = \"MAIN.HLP\";\n  char max_file[13] = \"(none)\";\n  long file_offset;\n  long file_len = 1;          // For first byte\n  long curr_file_storage = 2; // Location to store current file's info\n\n  // Links info-\n  int curr_link_ref = 0;      // Ref- not actual link number\n  int link_numbers[1000];     // Actual numbers of the links\n  long link_offsets[1000];    // Offsets within current file\n\n  // Current line info-\n  int display_line;           // Show text?\n  int line_display_len = 0;   // Used for warnings\n  int current_line_num = 1;   // For error/warning info\n\n  // Global info-\n  int end_of_file = 0;        // Have we hit @ yet?\n  long biggest_file = 0;      // For help allocation\n  int global_line_num = 0;    // For error/warning info\n\n  printf(\"\\n\\n\");\n\n  if(argc < 3)\n  {\n    printf(\"Usage: TXT2HLP source.txt output.fil\\n\");\n    return 1;\n  }\n\n#ifdef CONFIG_PLEDGE_UTILS\n#ifdef PLEDGE_HAS_UNVEIL\n  if(unveil(argv[1], \"r\") || unveil(argv[2], \"cw\") || unveil(NULL, NULL))\n  {\n    fprintf(stderr, \"ERROR: Failed unveil!\\n\");\n    return 1;\n  }\n#endif\n\n  if(pledge(PROMISES, \"\"))\n  {\n    fprintf(stderr, \"ERROR: Failed pledge!\\n\");\n    return 1;\n  }\n#endif\n\n  source = fopen_unsafe(argv[1], \"rb\");\n  if(source == NULL)\n  {\n    printf(\"Error opening %s for input.\\n\", argv[1]);\n    return -1;\n  }\n\n  dest = fopen_unsafe(argv[2],\"wb\");\n  if(dest == NULL)\n  {\n    fclose(source);\n    printf(\"Error opening %s for output.\\n\", argv[2]);\n    return 1;\n  }\n\n  // Count the # of files and links\n  do\n  {\n    current_char = fgetc(source);\n\n    switch(current_char)\n    {\n      case '#':\n      {\n        num_files++;\n        break;\n      }\n\n      case ':':\n      {\n        int link;\n\n        // Get upcoming link\n        if(!fscanf(source, \"%3d\", &link))\n          break;\n\n        // Get next char\n        current_char = fgetc(source);\n\n        if(((current_char == '\\n') || (current_char == ':')) &&\n         link >= num_links)\n          num_links = link + 1;\n\n      }\n    }\n\n    // Skip the rest of the line\n    while((current_char != '\\n') && !feof(source))\n      current_char = fgetc(source);\n\n  } while(!feof(source));\n\n  printf(\"Files: %d  Links: %d\\n\", num_files, num_links);\n\n  // Number of files obtained. Write header, with room, to dest. This\n  // includes number of links and blank spaces to add in filenames and\n  // link info.\n\n  fputw(num_files, dest);\n  for(i = 0; i < num_files; i++)\n    fwrite(\"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\", 21, 1, dest);\n\n  fputw(num_links, dest);\n  if(num_links > 0)\n    for(i = 0; i < num_links; i++)\n      fwrite(\"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\", 12, 1, dest);\n\n  file_offset = ftell(dest);\n\n  // Reset position on source...\n  fseek(source, 0, SEEK_SET);\n  // ...and begin our parse! New files start with a 1.\n  fputc(1, dest);\n  printf(\"Processing file %s...\\n\", curr_file);\n\n  do\n  {\n    //Our loop. Read first char of current line\n    line_display_len = 0;\n\n    // We only use this var for counting line lengths w/ display chars\n    previous_char = '\\0';\n    current_char = fgetc(source);\n\n    // Generally we'll want to have text after our commands, but we may not\n    display_line = 1;\n\n    switch(current_char)\n    {\n      // Centered line- write prefix...\n      case '$':\n      {\n        fputc(255, dest);\n        fputc('$', dest);\n        file_len += 2;\n        // we can't stop here this is bat country\n      }\n\n      /* fallthrough */\n\n\n\n      // Normal line that can start with reserved char\n      case '.':\n      {\n        current_char = fgetc(source);\n        break;\n      }\n\n\n\n      // Choice of some type\n      case '>':\n      {\n        fputc(255, dest);\n        file_len++;\n\n        // See if dest is normal or file\n        current_char = fgetc(source);\n\n        if(current_char == '#')\n        {\n          // File.\n          fputc('<', dest);\n          file_len++;\n\n          current_char = fgetc(source);\n          colon_count = 0;\n        }\n        else\n        {\n          // Label.\n          fputc('>',dest);\n          file_len++;\n\n          colon_count = 1;\n        }\n\n        //Grab name.\n        str_len = 0;\n        do\n        {\n          if(current_char == '\\n')\n            break;\n\n          if(current_char == ':')\n          {\n             if(colon_count < 1)\n               colon_count++;\n             else\n               break;\n          }\n\n          tstr[str_len] = current_char;\n          str_len++;\n\n          current_char = fgetc(source);\n\n        } while(!feof(source));\n\n        tstr[str_len] = '\\0';\n\n        if(str_len == 1) // 1 len not allowed\n          printf(\"Error- Link target length of 1 on line %d (g:%d), file %s.\\n\",\n                 current_line_num, global_line_num, curr_file);\n\n        if(str_len == 10) // 10 len not allowed\n          printf(\"Error- Link target length of 10 on line %d (g:%d), file %s.\\n\",\n                 current_line_num, global_line_num, curr_file);\n\n        fputc(str_len, dest);\n        file_len++;\n\n        fwrite(tstr, (str_len + 1), 1, dest);\n        file_len += (str_len + 1);\n\n        //Now do actual message after the label/file.\n        current_char = fgetc(source);\n        break;\n      }\n\n\n\n      //Label, possibly with a message attached\n      case ':':\n      {\n        fputc(255, dest);\n        fputc(':', dest);\n        file_len += 2;\n\n        //Grab name.\n        str_len = 0;\n        do\n        {\n          current_char = fgetc(source);\n\n          if((current_char == ':') || (current_char == '\\n'))\n            break;\n\n          tstr[str_len] = current_char;\n          str_len++;\n\n        } while(!feof(source));\n\n        tstr[str_len] = '\\0';\n\n        if(str_len == 1) //1 len not allowed\n          printf(\"Error- Label length of 1 on line %d (g:%d), file %s.\\n\",\n                 current_line_num, global_line_num, curr_file);\n\n        if(str_len == 10) //10 len not allowed\n          printf(\"Error- Label length of 10 on line %d (g:%d), file %s.\\n\",\n                 current_line_num, global_line_num, curr_file);\n\n        fputc(str_len, dest);\n        file_len++;\n\n        fwrite(tstr, (str_len + 1), 1, dest);\n        file_len += (str_len + 1);\n\n        //Is this a context-link?\n        if((str_len == 3) &&\n         isdigit((unsigned char)tstr[0]) &&\n         isdigit((unsigned char)tstr[1]) &&\n         isdigit((unsigned char)tstr[2]))\n        {\n          //Yep.\n          link_numbers[curr_link_ref] = atoi(tstr);\n          link_offsets[curr_link_ref] = file_len - 7;\n          curr_link_ref++;\n        }\n\n        // If there isn't a :, don't put a message, end line\n        if(current_char != ':')\n        {\n          fputc('\\n', source);\n          file_len++;\n          display_line = 0;\n        }\n\n        current_char = fgetc(source);\n        break;\n      }\n\n\n      // End\n      case '@':\n      {\n        save_last_file:\n        end_of_file = 1;\n      }\n\n      /* fallthrough */\n\n      // New file\n      case '#':\n      {\n        // End current file\n        fputc(0, dest);\n        file_len++;\n\n        if(file_len > biggest_file)\n        {\n          biggest_file = file_len;\n          memcpy(max_file, curr_file, 12);\n          max_file[12] = '\\0';\n        }\n\n        // Put new file's info at the start\n        tlong = ftell(dest);\n        fseek(dest, curr_file_storage, SEEK_SET);\n        fwrite(curr_file, 1, 13, dest);\n        fwrite(&file_offset, 1, 4, dest);\n        fwrite(&file_len, 1, 4, dest);\n\n        if(file_len > 65535)\n          printf(\"Warning- File %s over 64k bytes in length.\\n\",\n                 curr_file);\n\n        //Write in links info\n        for(i = 0; i < curr_link_ref; i++)\n        {\n          fseek(dest, 4 + num_files * 21 + link_numbers[i] * 12, SEEK_SET);\n          fwrite(&file_offset, 1, 4, dest);\n          fwrite(&file_len, 1, 4, dest);\n          fwrite(&link_offsets[i], 1, 4, dest);\n        }\n\n        if(end_of_file)\n          goto alldone;\n\n        //Start next file\n        fseek(dest, tlong, SEEK_SET);\n        file_offset = tlong;\n        current_line_num = 1;\n\n        curr_file_storage += 21;\n        curr_link_ref = 0;\n\n        fputc(1,dest);\n        file_len = 1;\n\n        //Get new filename\n        str_len = 0;\n        do\n        {\n          current_char = fgetc(source);\n          if(current_char == '\\n')\n            break;\n\n          tstr[str_len] = current_char;\n          str_len++;\n\n        } while(!feof(source));\n\n        tstr[str_len] = '\\0';\n        tstr[12] = '\\0';\n\n        //Copy over new filename\n        memcpy(curr_file, tstr, 12);\n        curr_file[12] = '\\0';\n\n        //Next file ready to roar!\n        printf(\"Processing file %s...\\n\", curr_file);\n\n        display_line = 0;\n        break;\n      }\n    }\n\n    // Now parse the line for display text.\n    if(display_line)\n    {\n      while(!feof(source) && (current_char != '\\n') && display_line)\n      {\n        fputc(current_char, dest);\n        file_len++;\n\n        // This won't be true unless there are two non ~/@ chars\n        // adjacent, two ~s adjacent, or two @s adjacent\n        if(\n         ((current_char == '~') ^ (previous_char != '~')) &&\n         ((current_char == '@') ^ (previous_char != '@')))\n        {\n          // Clear this so we don't miscount the next char\n          previous_char = '\\0';\n         line_display_len++;\n        }\n        else\n          previous_char = current_char;\n\n        current_char = fgetc(source);\n      }\n\n      // End the line!\n      fputc('\\n', dest);\n      file_len++;\n    }\n\n    if(line_display_len > 64)\n      printf(\"Warning: Line %d (g:%d) over 64 chars in file %s.\\n\",\n       current_line_num, global_line_num, curr_file);\n\n    //Done with this line.\n    current_line_num++;\n    global_line_num++;\n\n  } while(!feof(source));\n\n  // Abrupt file end!\n  printf(\"Warning: Encountered end of file before end of file marker '@'\\n\");\n  goto save_last_file;\n\n  alldone:\n\n  //All done! Close files.\n  fclose(dest);\n  fclose(source);\n  printf(\"Done! Biggest file- %ld bytes. (%s)\\n\\n\",\n         biggest_file, max_file);\n\n  return 0;\n}\n\n"
  },
  {
    "path": "src/utils/uthash.h",
    "content": "/*\nCopyright (c) 2003-2017, Troy D. Hanson     http://troydhanson.github.com/uthash/\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\nIS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\nTO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\nPARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER\nOR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\nEXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\nPROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n\n#ifndef UTHASH_H\n#define UTHASH_H\n\n#define UTHASH_VERSION 2.0.2\n\n#include <string.h>   /* memcmp, memset, strlen */\n#include <stddef.h>   /* ptrdiff_t */\n#include <stdlib.h>   /* exit */\n\n/* These macros use decltype or the earlier __typeof GNU extension.\n   As decltype is only available in newer compilers (VS2010 or gcc 4.3+\n   when compiling c++ source) this code uses whatever method is needed\n   or, for VS2008 where neither is available, uses casting workarounds. */\n#if !defined(DECLTYPE) && !defined(NO_DECLTYPE)\n#if defined(_MSC_VER)   /* MS compiler */\n#if _MSC_VER >= 1600 && defined(__cplusplus)  /* VS2010 or newer in C++ mode */\n#define DECLTYPE(x) (decltype(x))\n#else                   /* VS2008 or older (or VS2010 in C mode) */\n#define NO_DECLTYPE\n#endif\n#elif defined(__BORLANDC__) || defined(__ICCARM__) || defined(__LCC__) || defined(__WATCOMC__)\n#define NO_DECLTYPE\n#else                   /* GNU, Sun and other compilers */\n#define DECLTYPE(x) (__typeof(x))\n#endif\n#endif\n\n#ifdef NO_DECLTYPE\n#define DECLTYPE(x)\n#define DECLTYPE_ASSIGN(dst,src)                                                 \\\ndo {                                                                             \\\n  char **_da_dst = (char**)(&(dst));                                             \\\n  *_da_dst = (char*)(src);                                                       \\\n} while (0)\n#else\n#define DECLTYPE_ASSIGN(dst,src)                                                 \\\ndo {                                                                             \\\n  (dst) = DECLTYPE(dst)(src);                                                    \\\n} while (0)\n#endif\n\n/* a number of the hash function use uint32_t which isn't defined on Pre VS2010 */\n#if defined(_WIN32)\n#if defined(_MSC_VER) && _MSC_VER >= 1600\n#include <stdint.h>\n#elif defined(__WATCOMC__) || defined(__MINGW32__) || defined(__CYGWIN__)\n#include <stdint.h>\n#else\ntypedef unsigned int uint32_t;\ntypedef unsigned char uint8_t;\n#endif\n#elif defined(__GNUC__) && !defined(__VXWORKS__)\n#include <stdint.h>\n#else\ntypedef unsigned int uint32_t;\ntypedef unsigned char uint8_t;\n#endif\n\n#ifndef uthash_fatal\n#define uthash_fatal(msg) exit(-1)        /* fatal error (out of memory,etc) */\n#endif\n#ifndef uthash_malloc\n#define uthash_malloc(sz) malloc(sz)      /* malloc fcn                      */\n#endif\n#ifndef uthash_free\n#define uthash_free(ptr,sz) free(ptr)     /* free fcn                        */\n#endif\n#ifndef uthash_bzero\n#define uthash_bzero(a,n) memset(a,'\\0',n)\n#endif\n#ifndef uthash_memcmp\n#define uthash_memcmp(a,b,n) memcmp(a,b,n)\n#endif\n#ifndef uthash_strlen\n#define uthash_strlen(s) strlen(s)\n#endif\n\n#ifndef uthash_noexpand_fyi\n#define uthash_noexpand_fyi(tbl)          /* can be defined to log noexpand  */\n#endif\n#ifndef uthash_expand_fyi\n#define uthash_expand_fyi(tbl)            /* can be defined to log expands   */\n#endif\n\n/* initial number of buckets */\n#define HASH_INITIAL_NUM_BUCKETS 32U     /* initial number of buckets        */\n#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */\n#define HASH_BKT_CAPACITY_THRESH 10U     /* expand when bucket count reaches */\n\n/* calculate the element whose hash handle address is hhp */\n#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))\n/* calculate the hash handle from element address elp */\n#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle *)(((char*)(elp)) + ((tbl)->hho)))\n\n#define HASH_VALUE(keyptr,keylen,hashv)                                          \\\ndo {                                                                             \\\n  HASH_FCN(keyptr, keylen, hashv);                                               \\\n} while (0)\n\n#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out)                 \\\ndo {                                                                             \\\n  (out) = NULL;                                                                  \\\n  if (head) {                                                                    \\\n    unsigned _hf_bkt;                                                            \\\n    HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt);                  \\\n    if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) {                         \\\n      HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \\\n    }                                                                            \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_FIND(hh,head,keyptr,keylen,out)                                     \\\ndo {                                                                             \\\n  unsigned _hf_hashv;                                                            \\\n  HASH_VALUE(keyptr, keylen, _hf_hashv);                                         \\\n  HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out);               \\\n} while (0)\n\n#ifdef HASH_BLOOM\n#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM)\n#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL)\n#define HASH_BLOOM_MAKE(tbl)                                                     \\\ndo {                                                                             \\\n  (tbl)->bloom_nbits = HASH_BLOOM;                                               \\\n  (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN);                 \\\n  if (!(tbl)->bloom_bv) {                                                        \\\n    uthash_fatal(\"out of memory\");                                               \\\n  }                                                                              \\\n  uthash_bzero((tbl)->bloom_bv, HASH_BLOOM_BYTELEN);                             \\\n  (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE;                                       \\\n} while (0)\n\n#define HASH_BLOOM_FREE(tbl)                                                     \\\ndo {                                                                             \\\n  uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN);                              \\\n} while (0)\n\n#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U)))\n#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U)))\n\n#define HASH_BLOOM_ADD(tbl,hashv)                                                \\\n  HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))\n\n#define HASH_BLOOM_TEST(tbl,hashv)                                               \\\n  HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1UL << (tbl)->bloom_nbits) - 1U)))\n\n#else\n#define HASH_BLOOM_MAKE(tbl)\n#define HASH_BLOOM_FREE(tbl)\n#define HASH_BLOOM_ADD(tbl,hashv)\n#define HASH_BLOOM_TEST(tbl,hashv) (1)\n#define HASH_BLOOM_BYTELEN 0U\n#endif\n\n#define HASH_MAKE_TABLE(hh,head)                                                 \\\ndo {                                                                             \\\n  (head)->hh.tbl = (UT_hash_table*)uthash_malloc(sizeof(UT_hash_table));         \\\n  if (!(head)->hh.tbl) {                                                         \\\n    uthash_fatal(\"out of memory\");                                               \\\n  }                                                                              \\\n  uthash_bzero((head)->hh.tbl, sizeof(UT_hash_table));                           \\\n  (head)->hh.tbl->tail = &((head)->hh);                                          \\\n  (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS;                        \\\n  (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2;              \\\n  (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head);                    \\\n  (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc(                      \\\n      HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket));                 \\\n  if (!(head)->hh.tbl->buckets) {                                                \\\n    uthash_fatal(\"out of memory\");                                               \\\n  }                                                                              \\\n  uthash_bzero((head)->hh.tbl->buckets,                                          \\\n      HASH_INITIAL_NUM_BUCKETS * sizeof(struct UT_hash_bucket));                 \\\n  HASH_BLOOM_MAKE((head)->hh.tbl);                                               \\\n  (head)->hh.tbl->signature = HASH_SIGNATURE;                                    \\\n} while (0)\n\n#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \\\ndo {                                                                             \\\n  (replaced) = NULL;                                                             \\\n  HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \\\n  if (replaced) {                                                                \\\n    HASH_DELETE(hh, head, replaced);                                             \\\n  }                                                                              \\\n  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \\\n} while (0)\n\n#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \\\ndo {                                                                             \\\n  (replaced) = NULL;                                                             \\\n  HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \\\n  if (replaced) {                                                                \\\n    HASH_DELETE(hh, head, replaced);                                             \\\n  }                                                                              \\\n  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \\\n} while (0)\n\n#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced)                   \\\ndo {                                                                             \\\n  unsigned _hr_hashv;                                                            \\\n  HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv);                         \\\n  HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \\\n} while (0)\n\n#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn)    \\\ndo {                                                                             \\\n  unsigned _hr_hashv;                                                            \\\n  HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv);                         \\\n  HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \\\n} while (0)\n\n#define HASH_APPEND_LIST(hh, head, add)                                          \\\ndo {                                                                             \\\n  (add)->hh.next = NULL;                                                         \\\n  (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail);           \\\n  (head)->hh.tbl->tail->next = (add);                                            \\\n  (head)->hh.tbl->tail = &((add)->hh);                                           \\\n} while (0)\n\n#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn)                                 \\\ndo {                                                                             \\\n  do {                                                                           \\\n    if (cmpfcn(DECLTYPE(head)(_hs_iter), add) > 0) {                             \\\n      break;                                                                     \\\n    }                                                                            \\\n  } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next));           \\\n} while (0)\n\n#ifdef NO_DECLTYPE\n#undef HASH_AKBI_INNER_LOOP\n#define HASH_AKBI_INNER_LOOP(hh,head,add,cmpfcn)                                 \\\ndo {                                                                             \\\n  char *_hs_saved_head = (char*)(head);                                          \\\n  do {                                                                           \\\n    DECLTYPE_ASSIGN(head, _hs_iter);                                             \\\n    if (cmpfcn(head, add) > 0) {                                                 \\\n      DECLTYPE_ASSIGN(head, _hs_saved_head);                                     \\\n      break;                                                                     \\\n    }                                                                            \\\n    DECLTYPE_ASSIGN(head, _hs_saved_head);                                       \\\n  } while ((_hs_iter = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->next));           \\\n} while (0)\n#endif\n\n#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \\\ndo {                                                                             \\\n  unsigned _ha_bkt;                                                              \\\n  (add)->hh.hashv = (hashval);                                                   \\\n  (add)->hh.key = (char*) (keyptr);                                              \\\n  (add)->hh.keylen = (unsigned) (keylen_in);                                     \\\n  if (!(head)) {                                                                 \\\n    (add)->hh.next = NULL;                                                       \\\n    (add)->hh.prev = NULL;                                                       \\\n    (head) = (add);                                                              \\\n    HASH_MAKE_TABLE(hh, head);                                                   \\\n  } else {                                                                       \\\n    void *_hs_iter = (head);                                                     \\\n    (add)->hh.tbl = (head)->hh.tbl;                                              \\\n    HASH_AKBI_INNER_LOOP(hh, head, add, cmpfcn);                                 \\\n    if (_hs_iter) {                                                              \\\n      (add)->hh.next = _hs_iter;                                                 \\\n      if (((add)->hh.prev = HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev)) {     \\\n        HH_FROM_ELMT((head)->hh.tbl, (add)->hh.prev)->next = (add);              \\\n      } else {                                                                   \\\n        (head) = (add);                                                          \\\n      }                                                                          \\\n      HH_FROM_ELMT((head)->hh.tbl, _hs_iter)->prev = (add);                      \\\n    } else {                                                                     \\\n      HASH_APPEND_LIST(hh, head, add);                                           \\\n    }                                                                            \\\n  }                                                                              \\\n  (head)->hh.tbl->num_items++;                                                   \\\n  HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt);                    \\\n  HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], &(add)->hh);                 \\\n  HASH_BLOOM_ADD((head)->hh.tbl, hashval);                                       \\\n  HASH_EMIT_KEY(hh, head, keyptr, keylen_in);                                    \\\n  HASH_FSCK(hh, head, \"HASH_ADD_KEYPTR_BYHASHVALUE_INORDER\");                    \\\n} while (0)\n\n#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn)             \\\ndo {                                                                             \\\n  unsigned _hs_hashv;                                                            \\\n  HASH_VALUE(keyptr, keylen_in, _hs_hashv);                                      \\\n  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \\\n} while (0)\n\n#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \\\n  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn)\n\n#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn)                 \\\n  HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn)\n\n#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add)        \\\ndo {                                                                             \\\n  unsigned _ha_bkt;                                                              \\\n  (add)->hh.hashv = (hashval);                                                   \\\n  (add)->hh.key = (char*) (keyptr);                                              \\\n  (add)->hh.keylen = (unsigned) (keylen_in);                                     \\\n  if (!(head)) {                                                                 \\\n    (add)->hh.next = NULL;                                                       \\\n    (add)->hh.prev = NULL;                                                       \\\n    (head) = (add);                                                              \\\n    HASH_MAKE_TABLE(hh, head);                                                   \\\n  } else {                                                                       \\\n    (add)->hh.tbl = (head)->hh.tbl;                                              \\\n    HASH_APPEND_LIST(hh, head, add);                                             \\\n  }                                                                              \\\n  (head)->hh.tbl->num_items++;                                                   \\\n  HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt);                    \\\n  HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], &(add)->hh);                 \\\n  HASH_BLOOM_ADD((head)->hh.tbl, hashval);                                       \\\n  HASH_EMIT_KEY(hh, head, keyptr, keylen_in);                                    \\\n  HASH_FSCK(hh, head, \"HASH_ADD_KEYPTR_BYHASHVALUE\");                            \\\n} while (0)\n\n#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add)                            \\\ndo {                                                                             \\\n  unsigned _ha_hashv;                                                            \\\n  HASH_VALUE(keyptr, keylen_in, _ha_hashv);                                      \\\n  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add);      \\\n} while (0)\n\n#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add)            \\\n  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add)\n\n#define HASH_ADD(hh,head,fieldname,keylen_in,add)                                \\\n  HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add)\n\n#define HASH_TO_BKT(hashv,num_bkts,bkt)                                          \\\ndo {                                                                             \\\n  bkt = ((hashv) & ((num_bkts) - 1U));                                           \\\n} while (0)\n\n/* delete \"delptr\" from the hash table.\n * \"the usual\" patch-up process for the app-order doubly-linked-list.\n * The use of _hd_hh_del below deserves special explanation.\n * These used to be expressed using (delptr) but that led to a bug\n * if someone used the same symbol for the head and deletee, like\n *  HASH_DELETE(hh,users,users);\n * We want that to work, but by changing the head (users) below\n * we were forfeiting our ability to further refer to the deletee (users)\n * in the patch-up process. Solution: use scratch space to\n * copy the deletee pointer, then the latter references are via that\n * scratch pointer rather than through the repointed (users) symbol.\n */\n#define HASH_DELETE(hh,head,delptr)                                              \\\ndo {                                                                             \\\n  struct UT_hash_handle *_hd_hh_del;                                             \\\n  if (((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL)) {              \\\n    HASH_BLOOM_FREE((head)->hh.tbl);                                             \\\n    uthash_free((head)->hh.tbl->buckets,                                         \\\n                (head)->hh.tbl->num_buckets * sizeof(struct UT_hash_bucket));    \\\n    uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                          \\\n    (head) = NULL;                                                               \\\n  } else {                                                                       \\\n    unsigned _hd_bkt;                                                            \\\n    _hd_hh_del = &((delptr)->hh);                                                \\\n    if ((delptr) == ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail)) {        \\\n      (head)->hh.tbl->tail = HH_FROM_ELMT((head)->hh.tbl, (delptr)->hh.prev);    \\\n    }                                                                            \\\n    if ((delptr)->hh.prev != NULL) {                                             \\\n      HH_FROM_ELMT((head)->hh.tbl, (delptr)->hh.prev)->next = (delptr)->hh.next; \\\n    } else {                                                                     \\\n      DECLTYPE_ASSIGN(head, (delptr)->hh.next);                                  \\\n    }                                                                            \\\n    if (_hd_hh_del->next != NULL) {                                              \\\n      HH_FROM_ELMT((head)->hh.tbl, _hd_hh_del->next)->prev = _hd_hh_del->prev;   \\\n    }                                                                            \\\n    HASH_TO_BKT(_hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt);        \\\n    HASH_DEL_IN_BKT(hh, (head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del);           \\\n    (head)->hh.tbl->num_items--;                                                 \\\n  }                                                                              \\\n  HASH_FSCK(hh, head, \"HASH_DELETE\");                                            \\\n} while (0)\n\n\n/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */\n#define HASH_FIND_STR(head,findstr,out)                                          \\\n    HASH_FIND(hh,head,findstr,(unsigned)uthash_strlen(findstr),out)\n#define HASH_ADD_STR(head,strfield,add)                                          \\\n    HASH_ADD(hh,head,strfield[0],(unsigned)uthash_strlen(add->strfield),add)\n#define HASH_REPLACE_STR(head,strfield,add,replaced)                             \\\n    HASH_REPLACE(hh,head,strfield[0],(unsigned)uthash_strlen(add->strfield),add,replaced)\n#define HASH_FIND_INT(head,findint,out)                                          \\\n    HASH_FIND(hh,head,findint,sizeof(int),out)\n#define HASH_ADD_INT(head,intfield,add)                                          \\\n    HASH_ADD(hh,head,intfield,sizeof(int),add)\n#define HASH_REPLACE_INT(head,intfield,add,replaced)                             \\\n    HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced)\n#define HASH_FIND_PTR(head,findptr,out)                                          \\\n    HASH_FIND(hh,head,findptr,sizeof(void *),out)\n#define HASH_ADD_PTR(head,ptrfield,add)                                          \\\n    HASH_ADD(hh,head,ptrfield,sizeof(void *),add)\n#define HASH_REPLACE_PTR(head,ptrfield,add,replaced)                             \\\n    HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced)\n#define HASH_DEL(head,delptr)                                                    \\\n    HASH_DELETE(hh,head,delptr)\n\n/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined.\n * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined.\n */\n#ifdef HASH_DEBUG\n#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0)\n#define HASH_FSCK(hh,head,where)                                                 \\\ndo {                                                                             \\\n  struct UT_hash_handle *_thh;                                                   \\\n  if (head) {                                                                    \\\n    unsigned _bkt_i;                                                             \\\n    unsigned _count = 0;                                                         \\\n    char *_prev;                                                                 \\\n    for (_bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; ++_bkt_i) {           \\\n      unsigned _bkt_count = 0;                                                   \\\n      _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head;                            \\\n      _prev = NULL;                                                              \\\n      while (_thh) {                                                             \\\n        if (_prev != (char*)(_thh->hh_prev)) {                                   \\\n          HASH_OOPS(\"%s: invalid hh_prev %p, actual %p\\n\",                       \\\n              (where), (void*)_thh->hh_prev, (void*)_prev);                      \\\n        }                                                                        \\\n        _bkt_count++;                                                            \\\n        _prev = (char*)(_thh);                                                   \\\n        _thh = _thh->hh_next;                                                    \\\n      }                                                                          \\\n      _count += _bkt_count;                                                      \\\n      if ((head)->hh.tbl->buckets[_bkt_i].count !=  _bkt_count) {                \\\n        HASH_OOPS(\"%s: invalid bucket count %u, actual %u\\n\",                    \\\n            (where), (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count);         \\\n      }                                                                          \\\n    }                                                                            \\\n    if (_count != (head)->hh.tbl->num_items) {                                   \\\n      HASH_OOPS(\"%s: invalid hh item count %u, actual %u\\n\",                     \\\n          (where), (head)->hh.tbl->num_items, _count);                           \\\n    }                                                                            \\\n    _count = 0;                                                                  \\\n    _prev = NULL;                                                                \\\n    _thh =  &(head)->hh;                                                         \\\n    while (_thh) {                                                               \\\n      _count++;                                                                  \\\n      if (_prev != (char*)_thh->prev) {                                          \\\n        HASH_OOPS(\"%s: invalid prev %p, actual %p\\n\",                            \\\n            (where), (void*)_thh->prev, (void*)_prev);                           \\\n      }                                                                          \\\n      _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh);                         \\\n      _thh = (_thh->next ? HH_FROM_ELMT((head)->hh.tbl, _thh->next) : NULL);     \\\n    }                                                                            \\\n    if (_count != (head)->hh.tbl->num_items) {                                   \\\n      HASH_OOPS(\"%s: invalid app item count %u, actual %u\\n\",                    \\\n          (where), (head)->hh.tbl->num_items, _count);                           \\\n    }                                                                            \\\n  }                                                                              \\\n} while (0)\n#else\n#define HASH_FSCK(hh,head,where)\n#endif\n\n/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to\n * the descriptor to which this macro is defined for tuning the hash function.\n * The app can #include <unistd.h> to get the prototype for write(2). */\n#ifdef HASH_EMIT_KEYS\n#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)                                   \\\ndo {                                                                             \\\n  unsigned _klen = fieldlen;                                                     \\\n  write(HASH_EMIT_KEYS, &_klen, sizeof(_klen));                                  \\\n  write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen);                        \\\n} while (0)\n#else\n#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)\n#endif\n\n/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */\n#ifdef HASH_FUNCTION\n#define HASH_FCN HASH_FUNCTION\n#else\n#define HASH_FCN HASH_JEN\n#endif\n\n/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */\n#define HASH_BER(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _hb_keylen = (unsigned)keylen;                                        \\\n  const unsigned char *_hb_key = (const unsigned char*)(key);                    \\\n  (hashv) = 0;                                                                   \\\n  while (_hb_keylen-- != 0U) {                                                   \\\n    (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++;                           \\\n  }                                                                              \\\n} while (0)\n\n\n/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at\n * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */\n#define HASH_SAX(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _sx_i;                                                                \\\n  const unsigned char *_hs_key = (const unsigned char*)(key);                    \\\n  hashv = 0;                                                                     \\\n  for (_sx_i=0; _sx_i < keylen; _sx_i++) {                                       \\\n    hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i];                       \\\n  }                                                                              \\\n} while (0)\n/* FNV-1a variation */\n#define HASH_FNV(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _fn_i;                                                                \\\n  const unsigned char *_hf_key = (const unsigned char*)(key);                    \\\n  (hashv) = 2166136261U;                                                         \\\n  for (_fn_i=0; _fn_i < keylen; _fn_i++) {                                       \\\n    hashv = hashv ^ _hf_key[_fn_i];                                              \\\n    hashv = hashv * 16777619U;                                                   \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_OAT(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _ho_i;                                                                \\\n  const unsigned char *_ho_key=(const unsigned char*)(key);                      \\\n  hashv = 0;                                                                     \\\n  for(_ho_i=0; _ho_i < keylen; _ho_i++) {                                        \\\n      hashv += _ho_key[_ho_i];                                                   \\\n      hashv += (hashv << 10);                                                    \\\n      hashv ^= (hashv >> 6);                                                     \\\n  }                                                                              \\\n  hashv += (hashv << 3);                                                         \\\n  hashv ^= (hashv >> 11);                                                        \\\n  hashv += (hashv << 15);                                                        \\\n} while (0)\n\n#define HASH_JEN_MIX(a,b,c)                                                      \\\ndo {                                                                             \\\n  a -= b; a -= c; a ^= ( c >> 13 );                                              \\\n  b -= c; b -= a; b ^= ( a << 8 );                                               \\\n  c -= a; c -= b; c ^= ( b >> 13 );                                              \\\n  a -= b; a -= c; a ^= ( c >> 12 );                                              \\\n  b -= c; b -= a; b ^= ( a << 16 );                                              \\\n  c -= a; c -= b; c ^= ( b >> 5 );                                               \\\n  a -= b; a -= c; a ^= ( c >> 3 );                                               \\\n  b -= c; b -= a; b ^= ( a << 10 );                                              \\\n  c -= a; c -= b; c ^= ( b >> 15 );                                              \\\n} while (0)\n\n#define HASH_JEN(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned _hj_i,_hj_j,_hj_k;                                                    \\\n  unsigned const char *_hj_key=(unsigned const char*)(key);                      \\\n  hashv = 0xfeedbeefu;                                                           \\\n  _hj_i = _hj_j = 0x9e3779b9u;                                                   \\\n  _hj_k = (unsigned)(keylen);                                                    \\\n  while (_hj_k >= 12U) {                                                         \\\n    _hj_i +=    (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 )                      \\\n        + ( (unsigned)_hj_key[2] << 16 )                                         \\\n        + ( (unsigned)_hj_key[3] << 24 ) );                                      \\\n    _hj_j +=    (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 )                      \\\n        + ( (unsigned)_hj_key[6] << 16 )                                         \\\n        + ( (unsigned)_hj_key[7] << 24 ) );                                      \\\n    hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 )                         \\\n        + ( (unsigned)_hj_key[10] << 16 )                                        \\\n        + ( (unsigned)_hj_key[11] << 24 ) );                                     \\\n                                                                                 \\\n     HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                          \\\n                                                                                 \\\n     _hj_key += 12;                                                              \\\n     _hj_k -= 12U;                                                               \\\n  }                                                                              \\\n  hashv += (unsigned)(keylen);                                                   \\\n  switch ( _hj_k ) {                                                             \\\n    case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */         \\\n    case 10: hashv += ( (unsigned)_hj_key[9] << 16 );  /* FALLTHROUGH */         \\\n    case 9:  hashv += ( (unsigned)_hj_key[8] << 8 );   /* FALLTHROUGH */         \\\n    case 8:  _hj_j += ( (unsigned)_hj_key[7] << 24 );  /* FALLTHROUGH */         \\\n    case 7:  _hj_j += ( (unsigned)_hj_key[6] << 16 );  /* FALLTHROUGH */         \\\n    case 6:  _hj_j += ( (unsigned)_hj_key[5] << 8 );   /* FALLTHROUGH */         \\\n    case 5:  _hj_j += _hj_key[4];                      /* FALLTHROUGH */         \\\n    case 4:  _hj_i += ( (unsigned)_hj_key[3] << 24 );  /* FALLTHROUGH */         \\\n    case 3:  _hj_i += ( (unsigned)_hj_key[2] << 16 );  /* FALLTHROUGH */         \\\n    case 2:  _hj_i += ( (unsigned)_hj_key[1] << 8 );   /* FALLTHROUGH */         \\\n    case 1:  _hj_i += _hj_key[0];                                                \\\n  }                                                                              \\\n  HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                             \\\n} while (0)\n\n/* The Paul Hsieh hash function */\n#undef get16bits\n#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__)             \\\n  || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)\n#define get16bits(d) (*((const uint16_t *) (d)))\n#endif\n\n#if !defined (get16bits)\n#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)             \\\n                       +(uint32_t)(((const uint8_t *)(d))[0]) )\n#endif\n#define HASH_SFH(key,keylen,hashv)                                               \\\ndo {                                                                             \\\n  unsigned const char *_sfh_key=(unsigned const char*)(key);                     \\\n  uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen;                                \\\n                                                                                 \\\n  unsigned _sfh_rem = _sfh_len & 3U;                                             \\\n  _sfh_len >>= 2;                                                                \\\n  hashv = 0xcafebabeu;                                                           \\\n                                                                                 \\\n  /* Main loop */                                                                \\\n  for (;_sfh_len > 0U; _sfh_len--) {                                             \\\n    hashv    += get16bits (_sfh_key);                                            \\\n    _sfh_tmp  = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv;              \\\n    hashv     = (hashv << 16) ^ _sfh_tmp;                                        \\\n    _sfh_key += 2U*sizeof (uint16_t);                                            \\\n    hashv    += hashv >> 11;                                                     \\\n  }                                                                              \\\n                                                                                 \\\n  /* Handle end cases */                                                         \\\n  switch (_sfh_rem) {                                                            \\\n    case 3: hashv += get16bits (_sfh_key);                                       \\\n            hashv ^= hashv << 16;                                                \\\n            hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18;              \\\n            hashv += hashv >> 11;                                                \\\n            break;                                                               \\\n    case 2: hashv += get16bits (_sfh_key);                                       \\\n            hashv ^= hashv << 11;                                                \\\n            hashv += hashv >> 17;                                                \\\n            break;                                                               \\\n    case 1: hashv += *_sfh_key;                                                  \\\n            hashv ^= hashv << 10;                                                \\\n            hashv += hashv >> 1;                                                 \\\n  }                                                                              \\\n                                                                                 \\\n  /* Force \"avalanching\" of final 127 bits */                                    \\\n  hashv ^= hashv << 3;                                                           \\\n  hashv += hashv >> 5;                                                           \\\n  hashv ^= hashv << 4;                                                           \\\n  hashv += hashv >> 17;                                                          \\\n  hashv ^= hashv << 25;                                                          \\\n  hashv += hashv >> 6;                                                           \\\n} while (0)\n\n#ifdef HASH_USING_NO_STRICT_ALIASING\n/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads.\n * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error.\n * MurmurHash uses the faster approach only on CPU's where we know it's safe.\n *\n * Note the preprocessor built-in defines can be emitted using:\n *\n *   gcc -m64 -dM -E - < /dev/null                  (on gcc)\n *   cc -## a.c (where a.c is a simple test file)   (Sun Studio)\n */\n#if (defined(__i386__) || defined(__x86_64__)  || defined(_M_IX86))\n#define MUR_GETBLOCK(p,i) p[i]\n#else /* non intel */\n#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 3UL) == 0UL)\n#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 3UL) == 1UL)\n#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 3UL) == 2UL)\n#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 3UL) == 3UL)\n#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL))\n#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__))\n#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24))\n#define MUR_TWO_TWO(p)   ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16))\n#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >>  8))\n#else /* assume little endian non-intel */\n#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24))\n#define MUR_TWO_TWO(p)   ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16))\n#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) <<  8))\n#endif\n#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) :           \\\n                            (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \\\n                             (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) :  \\\n                                                      MUR_ONE_THREE(p))))\n#endif\n#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r))))\n#define MUR_FMIX(_h) \\\ndo {                 \\\n  _h ^= _h >> 16;    \\\n  _h *= 0x85ebca6bu; \\\n  _h ^= _h >> 13;    \\\n  _h *= 0xc2b2ae35u; \\\n  _h ^= _h >> 16;    \\\n} while (0)\n\n#define HASH_MUR(key,keylen,hashv)                                     \\\ndo {                                                                   \\\n  const uint8_t *_mur_data = (const uint8_t*)(key);                    \\\n  const int _mur_nblocks = (int)(keylen) / 4;                          \\\n  uint32_t _mur_h1 = 0xf88D5353u;                                      \\\n  uint32_t _mur_c1 = 0xcc9e2d51u;                                      \\\n  uint32_t _mur_c2 = 0x1b873593u;                                      \\\n  uint32_t _mur_k1 = 0;                                                \\\n  const uint8_t *_mur_tail;                                            \\\n  const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+(_mur_nblocks*4)); \\\n  int _mur_i;                                                          \\\n  for (_mur_i = -_mur_nblocks; _mur_i != 0; _mur_i++) {                \\\n    _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i);                        \\\n    _mur_k1 *= _mur_c1;                                                \\\n    _mur_k1 = MUR_ROTL32(_mur_k1,15);                                  \\\n    _mur_k1 *= _mur_c2;                                                \\\n                                                                       \\\n    _mur_h1 ^= _mur_k1;                                                \\\n    _mur_h1 = MUR_ROTL32(_mur_h1,13);                                  \\\n    _mur_h1 = (_mur_h1*5U) + 0xe6546b64u;                              \\\n  }                                                                    \\\n  _mur_tail = (const uint8_t*)(_mur_data + (_mur_nblocks*4));          \\\n  _mur_k1=0;                                                           \\\n  switch ((keylen) & 3U) {                                             \\\n    case 0: break;                                                     \\\n    case 3: _mur_k1 ^= (uint32_t)_mur_tail[2] << 16; /* FALLTHROUGH */ \\\n    case 2: _mur_k1 ^= (uint32_t)_mur_tail[1] << 8;  /* FALLTHROUGH */ \\\n    case 1: _mur_k1 ^= (uint32_t)_mur_tail[0];                         \\\n    _mur_k1 *= _mur_c1;                                                \\\n    _mur_k1 = MUR_ROTL32(_mur_k1,15);                                  \\\n    _mur_k1 *= _mur_c2;                                                \\\n    _mur_h1 ^= _mur_k1;                                                \\\n  }                                                                    \\\n  _mur_h1 ^= (uint32_t)(keylen);                                       \\\n  MUR_FMIX(_mur_h1);                                                   \\\n  hashv = _mur_h1;                                                     \\\n} while (0)\n#endif  /* HASH_USING_NO_STRICT_ALIASING */\n\n/* iterate over items in a known bucket to find desired item */\n#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out)               \\\ndo {                                                                             \\\n  if ((head).hh_head != NULL) {                                                  \\\n    DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head));                     \\\n  } else {                                                                       \\\n    (out) = NULL;                                                                \\\n  }                                                                              \\\n  while ((out) != NULL) {                                                        \\\n    if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) {       \\\n      if (uthash_memcmp((out)->hh.key, keyptr, keylen_in) == 0) {                \\\n        break;                                                                   \\\n      }                                                                          \\\n    }                                                                            \\\n    if ((out)->hh.hh_next != NULL) {                                             \\\n      DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next));                \\\n    } else {                                                                     \\\n      (out) = NULL;                                                              \\\n    }                                                                            \\\n  }                                                                              \\\n} while (0)\n\n/* add an item to a bucket  */\n#define HASH_ADD_TO_BKT(head,addhh)                                              \\\ndo {                                                                             \\\n  head.count++;                                                                  \\\n  (addhh)->hh_next = head.hh_head;                                               \\\n  (addhh)->hh_prev = NULL;                                                       \\\n  if ((head).hh_head != NULL) {                                                  \\\n    (head).hh_head->hh_prev = (addhh);                                           \\\n  }                                                                              \\\n  (head).hh_head=addhh;                                                          \\\n  if ((head.count >= ((head.expand_mult+1U) * HASH_BKT_CAPACITY_THRESH))         \\\n      && ((addhh)->tbl->noexpand != 1U)) {                                       \\\n    HASH_EXPAND_BUCKETS((addhh)->tbl);                                           \\\n  }                                                                              \\\n} while (0)\n\n/* remove an item from a given bucket */\n#define HASH_DEL_IN_BKT(hh,head,hh_del)                                          \\\ndo {                                                                             \\\n  (head).count--;                                                                \\\n  if ((head).hh_head == (hh_del)) {                                              \\\n    (head).hh_head = (hh_del)->hh_next;                                          \\\n  }                                                                              \\\n  if ((hh_del)->hh_prev) {                                                       \\\n    (hh_del)->hh_prev->hh_next = (hh_del)->hh_next;                              \\\n  }                                                                              \\\n  if ((hh_del)->hh_next) {                                                       \\\n    (hh_del)->hh_next->hh_prev = (hh_del)->hh_prev;                              \\\n  }                                                                              \\\n} while (0)\n\n/* Bucket expansion has the effect of doubling the number of buckets\n * and redistributing the items into the new buckets. Ideally the\n * items will distribute more or less evenly into the new buckets\n * (the extent to which this is true is a measure of the quality of\n * the hash function as it applies to the key domain).\n *\n * With the items distributed into more buckets, the chain length\n * (item count) in each bucket is reduced. Thus by expanding buckets\n * the hash keeps a bound on the chain length. This bounded chain\n * length is the essence of how a hash provides constant time lookup.\n *\n * The calculation of tbl->ideal_chain_maxlen below deserves some\n * explanation. First, keep in mind that we're calculating the ideal\n * maximum chain length based on the *new* (doubled) bucket count.\n * In fractions this is just n/b (n=number of items,b=new num buckets).\n * Since the ideal chain length is an integer, we want to calculate\n * ceil(n/b). We don't depend on floating point arithmetic in this\n * hash, so to calculate ceil(n/b) with integers we could write\n *\n *      ceil(n/b) = (n/b) + ((n%b)?1:0)\n *\n * and in fact a previous version of this hash did just that.\n * But now we have improved things a bit by recognizing that b is\n * always a power of two. We keep its base 2 log handy (call it lb),\n * so now we can write this with a bit shift and logical AND:\n *\n *      ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)\n *\n */\n#define HASH_EXPAND_BUCKETS(tbl)                                                 \\\ndo {                                                                             \\\n  unsigned _he_bkt;                                                              \\\n  unsigned _he_bkt_i;                                                            \\\n  struct UT_hash_handle *_he_thh, *_he_hh_nxt;                                   \\\n  UT_hash_bucket *_he_new_buckets, *_he_newbkt;                                  \\\n  _he_new_buckets = (UT_hash_bucket*)uthash_malloc(                              \\\n           2UL * (tbl)->num_buckets * sizeof(struct UT_hash_bucket));            \\\n  if (!_he_new_buckets) {                                                        \\\n    uthash_fatal(\"out of memory\");                                               \\\n  }                                                                              \\\n  uthash_bzero(_he_new_buckets,                                                  \\\n          2UL * (tbl)->num_buckets * sizeof(struct UT_hash_bucket));             \\\n  (tbl)->ideal_chain_maxlen =                                                    \\\n     ((tbl)->num_items >> ((tbl)->log2_num_buckets+1U)) +                        \\\n     ((((tbl)->num_items & (((tbl)->num_buckets*2U)-1U)) != 0U) ? 1U : 0U);      \\\n  (tbl)->nonideal_items = 0;                                                     \\\n  for (_he_bkt_i = 0; _he_bkt_i < (tbl)->num_buckets; _he_bkt_i++) {             \\\n    _he_thh = (tbl)->buckets[ _he_bkt_i ].hh_head;                               \\\n    while (_he_thh != NULL) {                                                    \\\n      _he_hh_nxt = _he_thh->hh_next;                                             \\\n      HASH_TO_BKT(_he_thh->hashv, (tbl)->num_buckets * 2U, _he_bkt);             \\\n      _he_newbkt = &(_he_new_buckets[_he_bkt]);                                  \\\n      if (++(_he_newbkt->count) > (tbl)->ideal_chain_maxlen) {                   \\\n        (tbl)->nonideal_items++;                                                 \\\n        _he_newbkt->expand_mult = _he_newbkt->count / (tbl)->ideal_chain_maxlen; \\\n      }                                                                          \\\n      _he_thh->hh_prev = NULL;                                                   \\\n      _he_thh->hh_next = _he_newbkt->hh_head;                                    \\\n      if (_he_newbkt->hh_head != NULL) {                                         \\\n        _he_newbkt->hh_head->hh_prev = _he_thh;                                  \\\n      }                                                                          \\\n      _he_newbkt->hh_head = _he_thh;                                             \\\n      _he_thh = _he_hh_nxt;                                                      \\\n    }                                                                            \\\n  }                                                                              \\\n  uthash_free((tbl)->buckets, (tbl)->num_buckets * sizeof(struct UT_hash_bucket)); \\\n  (tbl)->num_buckets *= 2U;                                                      \\\n  (tbl)->log2_num_buckets++;                                                     \\\n  (tbl)->buckets = _he_new_buckets;                                              \\\n  (tbl)->ineff_expands = ((tbl)->nonideal_items > ((tbl)->num_items >> 1)) ?     \\\n      ((tbl)->ineff_expands+1U) : 0U;                                            \\\n  if ((tbl)->ineff_expands > 1U) {                                               \\\n    (tbl)->noexpand = 1;                                                         \\\n    uthash_noexpand_fyi(tbl);                                                    \\\n  }                                                                              \\\n  uthash_expand_fyi(tbl);                                                        \\\n} while (0)\n\n\n/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */\n/* Note that HASH_SORT assumes the hash handle name to be hh.\n * HASH_SRT was added to allow the hash handle name to be passed in. */\n#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn)\n#define HASH_SRT(hh,head,cmpfcn)                                                 \\\ndo {                                                                             \\\n  unsigned _hs_i;                                                                \\\n  unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize;               \\\n  struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail;            \\\n  if (head != NULL) {                                                            \\\n    _hs_insize = 1;                                                              \\\n    _hs_looping = 1;                                                             \\\n    _hs_list = &((head)->hh);                                                    \\\n    while (_hs_looping != 0U) {                                                  \\\n      _hs_p = _hs_list;                                                          \\\n      _hs_list = NULL;                                                           \\\n      _hs_tail = NULL;                                                           \\\n      _hs_nmerges = 0;                                                           \\\n      while (_hs_p != NULL) {                                                    \\\n        _hs_nmerges++;                                                           \\\n        _hs_q = _hs_p;                                                           \\\n        _hs_psize = 0;                                                           \\\n        for (_hs_i = 0; _hs_i < _hs_insize; ++_hs_i) {                           \\\n          _hs_psize++;                                                           \\\n          _hs_q = ((_hs_q->next != NULL) ?                                       \\\n            HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL);                   \\\n          if (_hs_q == NULL) {                                                   \\\n            break;                                                               \\\n          }                                                                      \\\n        }                                                                        \\\n        _hs_qsize = _hs_insize;                                                  \\\n        while ((_hs_psize != 0U) || ((_hs_qsize != 0U) && (_hs_q != NULL))) {    \\\n          if (_hs_psize == 0U) {                                                 \\\n            _hs_e = _hs_q;                                                       \\\n            _hs_q = ((_hs_q->next != NULL) ?                                     \\\n              HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL);                 \\\n            _hs_qsize--;                                                         \\\n          } else if ((_hs_qsize == 0U) || (_hs_q == NULL)) {                     \\\n            _hs_e = _hs_p;                                                       \\\n            if (_hs_p != NULL) {                                                 \\\n              _hs_p = ((_hs_p->next != NULL) ?                                   \\\n                HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL);               \\\n            }                                                                    \\\n            _hs_psize--;                                                         \\\n          } else if ((cmpfcn(                                                    \\\n                DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_p)),             \\\n                DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl, _hs_q))              \\\n                )) <= 0) {                                                       \\\n            _hs_e = _hs_p;                                                       \\\n            if (_hs_p != NULL) {                                                 \\\n              _hs_p = ((_hs_p->next != NULL) ?                                   \\\n                HH_FROM_ELMT((head)->hh.tbl, _hs_p->next) : NULL);               \\\n            }                                                                    \\\n            _hs_psize--;                                                         \\\n          } else {                                                               \\\n            _hs_e = _hs_q;                                                       \\\n            _hs_q = ((_hs_q->next != NULL) ?                                     \\\n              HH_FROM_ELMT((head)->hh.tbl, _hs_q->next) : NULL);                 \\\n            _hs_qsize--;                                                         \\\n          }                                                                      \\\n          if ( _hs_tail != NULL ) {                                              \\\n            _hs_tail->next = ((_hs_e != NULL) ?                                  \\\n              ELMT_FROM_HH((head)->hh.tbl, _hs_e) : NULL);                       \\\n          } else {                                                               \\\n            _hs_list = _hs_e;                                                    \\\n          }                                                                      \\\n          if (_hs_e != NULL) {                                                   \\\n            _hs_e->prev = ((_hs_tail != NULL) ?                                  \\\n              ELMT_FROM_HH((head)->hh.tbl, _hs_tail) : NULL);                    \\\n          }                                                                      \\\n          _hs_tail = _hs_e;                                                      \\\n        }                                                                        \\\n        _hs_p = _hs_q;                                                           \\\n      }                                                                          \\\n      if (_hs_tail != NULL) {                                                    \\\n        _hs_tail->next = NULL;                                                   \\\n      }                                                                          \\\n      if (_hs_nmerges <= 1U) {                                                   \\\n        _hs_looping = 0;                                                         \\\n        (head)->hh.tbl->tail = _hs_tail;                                         \\\n        DECLTYPE_ASSIGN(head, ELMT_FROM_HH((head)->hh.tbl, _hs_list));           \\\n      }                                                                          \\\n      _hs_insize *= 2U;                                                          \\\n    }                                                                            \\\n    HASH_FSCK(hh, head, \"HASH_SRT\");                                             \\\n  }                                                                              \\\n} while (0)\n\n/* This function selects items from one hash into another hash.\n * The end result is that the selected items have dual presence\n * in both hashes. There is no copy of the items made; rather\n * they are added into the new hash through a secondary hash\n * hash handle that must be present in the structure. */\n#define HASH_SELECT(hh_dst, dst, hh_src, src, cond)                              \\\ndo {                                                                             \\\n  unsigned _src_bkt, _dst_bkt;                                                   \\\n  void *_last_elt = NULL, *_elt;                                                 \\\n  UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL;                         \\\n  ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst));                 \\\n  if ((src) != NULL) {                                                           \\\n    for (_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) {    \\\n      for (_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head;               \\\n        _src_hh != NULL;                                                         \\\n        _src_hh = _src_hh->hh_next) {                                            \\\n        _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh);                         \\\n        if (cond(_elt)) {                                                        \\\n          _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho);                 \\\n          _dst_hh->key = _src_hh->key;                                           \\\n          _dst_hh->keylen = _src_hh->keylen;                                     \\\n          _dst_hh->hashv = _src_hh->hashv;                                       \\\n          _dst_hh->prev = _last_elt;                                             \\\n          _dst_hh->next = NULL;                                                  \\\n          if (_last_elt_hh != NULL) {                                            \\\n            _last_elt_hh->next = _elt;                                           \\\n          }                                                                      \\\n          if ((dst) == NULL) {                                                   \\\n            DECLTYPE_ASSIGN(dst, _elt);                                          \\\n            HASH_MAKE_TABLE(hh_dst, dst);                                        \\\n          } else {                                                               \\\n            _dst_hh->tbl = (dst)->hh_dst.tbl;                                    \\\n          }                                                                      \\\n          HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt);      \\\n          HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt], _dst_hh);             \\\n          HASH_BLOOM_ADD(_dst_hh->tbl, _dst_hh->hashv);                          \\\n          (dst)->hh_dst.tbl->num_items++;                                        \\\n          _last_elt = _elt;                                                      \\\n          _last_elt_hh = _dst_hh;                                                \\\n        }                                                                        \\\n      }                                                                          \\\n    }                                                                            \\\n  }                                                                              \\\n  HASH_FSCK(hh_dst, dst, \"HASH_SELECT\");                                         \\\n} while (0)\n\n#define HASH_CLEAR(hh,head)                                                      \\\ndo {                                                                             \\\n  if ((head) != NULL) {                                                          \\\n    HASH_BLOOM_FREE((head)->hh.tbl);                                             \\\n    uthash_free((head)->hh.tbl->buckets,                                         \\\n                (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket));      \\\n    uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                          \\\n    (head) = NULL;                                                               \\\n  }                                                                              \\\n} while (0)\n\n#define HASH_OVERHEAD(hh,head)                                                   \\\n (((head) != NULL) ? (                                                           \\\n (size_t)(((head)->hh.tbl->num_items   * sizeof(UT_hash_handle))   +             \\\n          ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket))   +             \\\n           sizeof(UT_hash_table)                                   +             \\\n           (HASH_BLOOM_BYTELEN))) : 0U)\n\n#ifdef NO_DECLTYPE\n#define HASH_ITER(hh,head,el,tmp)                                                \\\nfor(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \\\n  (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL)))\n#else\n#define HASH_ITER(hh,head,el,tmp)                                                \\\nfor(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL));      \\\n  (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL)))\n#endif\n\n/* obtain a count of items in the hash */\n#define HASH_COUNT(head) HASH_CNT(hh,head)\n#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U)\n\ntypedef struct UT_hash_bucket {\n   struct UT_hash_handle *hh_head;\n   unsigned count;\n\n   /* expand_mult is normally set to 0. In this situation, the max chain length\n    * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If\n    * the bucket's chain exceeds this length, bucket expansion is triggered).\n    * However, setting expand_mult to a non-zero value delays bucket expansion\n    * (that would be triggered by additions to this particular bucket)\n    * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH.\n    * (The multiplier is simply expand_mult+1). The whole idea of this\n    * multiplier is to reduce bucket expansions, since they are expensive, in\n    * situations where we know that a particular bucket tends to be overused.\n    * It is better to let its chain length grow to a longer yet-still-bounded\n    * value, than to do an O(n) bucket expansion too often.\n    */\n   unsigned expand_mult;\n\n} UT_hash_bucket;\n\n/* random signature used only to find hash tables in external analysis */\n#define HASH_SIGNATURE 0xa0111fe1u\n#define HASH_BLOOM_SIGNATURE 0xb12220f2u\n\ntypedef struct UT_hash_table {\n   UT_hash_bucket *buckets;\n   unsigned num_buckets, log2_num_buckets;\n   unsigned num_items;\n   struct UT_hash_handle *tail; /* tail hh in app order, for fast append    */\n   ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */\n\n   /* in an ideal situation (all buckets used equally), no bucket would have\n    * more than ceil(#items/#buckets) items. that's the ideal chain length. */\n   unsigned ideal_chain_maxlen;\n\n   /* nonideal_items is the number of items in the hash whose chain position\n    * exceeds the ideal chain maxlen. these items pay the penalty for an uneven\n    * hash distribution; reaching them in a chain traversal takes >ideal steps */\n   unsigned nonideal_items;\n\n   /* ineffective expands occur when a bucket doubling was performed, but\n    * afterward, more than half the items in the hash had nonideal chain\n    * positions. If this happens on two consecutive expansions we inhibit any\n    * further expansion, as it's not helping; this happens when the hash\n    * function isn't a good fit for the key domain. When expansion is inhibited\n    * the hash will still work, albeit no longer in constant time. */\n   unsigned ineff_expands, noexpand;\n\n   uint32_t signature; /* used only to find hash tables in external analysis */\n#ifdef HASH_BLOOM\n   uint32_t bloom_sig; /* used only to test bloom exists in external analysis */\n   uint8_t *bloom_bv;\n   uint8_t bloom_nbits;\n#endif\n\n} UT_hash_table;\n\ntypedef struct UT_hash_handle {\n   struct UT_hash_table *tbl;\n   void *prev;                       /* prev element in app order      */\n   void *next;                       /* next element in app order      */\n   struct UT_hash_handle *hh_prev;   /* previous hh in bucket order    */\n   struct UT_hash_handle *hh_next;   /* next hh in bucket order        */\n   void *key;                        /* ptr to enclosing struct's key  */\n   unsigned keylen;                  /* enclosing struct's key len     */\n   unsigned hashv;                   /* result of hash-fcn(key)        */\n} UT_hash_handle;\n\n#endif /* UTHASH_H */\n"
  },
  {
    "path": "src/utils/utils_alloc.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>\n * Copyright (C) 2020-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Standalone copy of the check_alloc functions so the utils can link fewer\n * core objects from MZX.\n */\n\n#ifndef __UTILS_ALLOC_H\n#define __UTILS_ALLOC_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n/* Workaround for linking zip.o */\nFILE *mzxout_h = NULL;\nFILE *mzxerr_h = NULL;\n\n#ifdef CONFIG_CHECK_ALLOC\n\n#include <stdlib.h>\n\nstatic void out_of_memory_check(void *p, const char *file, int line)\n{\n  if(!p)\n  {\n    fprintf(stderr, \"Out of memory in %s:%d\\n\", file, line);\n    fflush(stderr);\n    exit(-1);\n  }\n}\n\nCORE_LIBSPEC void *check_calloc(size_t nmemb, size_t size, const char *file, int line)\n{\n  void *result = calloc(nmemb, size);\n  out_of_memory_check(result, file, line);\n  return result;\n}\n\nCORE_LIBSPEC void *check_malloc(size_t size, const char *file, int line)\n{\n  void *result = malloc(size);\n  out_of_memory_check(result, file, line);\n  return result;\n}\n\nCORE_LIBSPEC void *check_realloc(void *ptr, size_t size, const char *file, int line)\n{\n  void *result = realloc(ptr, size);\n  out_of_memory_check(result, file, line);\n  return result;\n}\n\n#endif\n\n__M_END_DECLS\n\n#endif /* __UTILS_ALLOC_H */\n"
  },
  {
    "path": "src/utils/y4m.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Simple Y4M support library, as mjpegtools is missing from most repositories.\n */\n\n#include <stdlib.h>\n#include <ctype.h>\n\n#include \"y4m.h\"\n\n#define Y4M_CLAMP(v, min, max) ((v) < (min) ? (min) : ((v) > (max) ? (max) : (v)))\n\nstatic boolean unsigned_value(uint32_t *v, const char *buf)\n{\n  char *end;\n  *v = strtoul(buf, &end, 10);\n  if(end == buf || end[0] != '\\0')\n    return false;\n\n  return true;\n}\n\nstatic boolean ratio_value(uint32_t *n, uint32_t *d, const char *buf)\n{\n  char *end;\n  char *end2;\n  *n = strtoul(buf, &end, 10);\n  if(end == buf || end[0] != ':')\n    return false;\n\n  end++;\n  *d = strtoul(end, &end2, 10);\n  if(end == end2 || end2[0] != '\\0')\n    return false;\n\n  return true;\n}\n\nstatic boolean char_value(char *c, const char *buf)\n{\n  *c = *buf;\n  if(!isprint((unsigned char)*c) || buf[1] != '\\0')\n    return false;\n\n  return true;\n}\n\nstatic boolean subsampling_value(enum y4m_subsampling *sub, const char *buf)\n{\n  if(!strcmp(buf, \"420jpeg\"))\n  {\n    *sub = Y4M_SUB_420JPEG;\n    return true;\n  }\n  if(!strcmp(buf, \"420mpeg2\"))\n  {\n    *sub = Y4M_SUB_420MPEG2;\n    return true;\n  }\n  if(!strcmp(buf, \"420paldv\"))\n  {\n    *sub = Y4M_SUB_420PALDV;\n    return true;\n  }\n  if(!strcmp(buf, \"411\"))\n  {\n    *sub = Y4M_SUB_411;\n    return true;\n  }\n  if(!strcmp(buf, \"422\"))\n  {\n    *sub = Y4M_SUB_422;\n    return true;\n  }\n  if(!strcmp(buf, \"444\"))\n  {\n    *sub = Y4M_SUB_444;\n    return true;\n  }\n  if(!strcmp(buf, \"444alpha\"))\n  {\n    *sub = Y4M_SUB_444ALPHA;\n    return true;\n  }\n  return false;\n}\n\nstatic boolean interlacing_value(enum y4m_interlacing *inter, const char *buf)\n{\n  char type;\n  if(!char_value(&type, buf))\n    return false;\n\n  switch(type)\n  {\n    case '?':\n      *inter = Y4M_INTER_UNKNOWN;\n      return true;\n    case 'p':\n      *inter = Y4M_INTER_PROGRESSIVE;\n      return true;\n    case 't':\n      *inter = Y4M_INTER_TOP_FIRST;\n      return true;\n    case 'b':\n      *inter = Y4M_INTER_BOTTOM_FIRST;\n      return true;\n    case 'm':\n      *inter = Y4M_INTER_MIXED;\n      return true;\n  }\n  return false;\n}\n\nstatic boolean color_range_value(enum y4m_color_range *range, const char *buf)\n{\n  if(!strcmp(buf, \"FULL\"))\n  {\n    *range = Y4M_RANGE_FULL;\n    return true;\n  }\n  if(!strcmp(buf, \"LIMITED\"))\n  {\n    *range = Y4M_RANGE_STUDIO;\n    return true;\n  }\n  return false;\n}\n\nstatic boolean read_field(char *buf, size_t sz, FILE *fp)\n{\n  size_t i;\n  int c;\n\n  c = fgetc(fp);\n  if(c != ' ')\n  {\n    ungetc(c, fp);\n    return false;\n  }\n\n  for(i = 0; i < sz; i++)\n  {\n    c = fgetc(fp);\n    if(c < 0)\n      return false;\n\n    if(c == ' ' || c == '\\n')\n    {\n      ungetc(c, fp);\n      break;\n    }\n    buf[i] = c;\n  }\n  buf[i] = '\\0';\n  return true;\n}\n\nboolean y4m_init(struct y4m_data *y4m, FILE *fp)\n{\n  char buf[256];\n  memset(y4m, 0, sizeof(*y4m));\n  y4m->subsampling = Y4M_SUB_420JPEG;\n  y4m->interlacing = Y4M_INTER_UNKNOWN;\n\n  if(!fread(buf, 9, 1, fp) || memcmp(buf, \"YUV4MPEG2\", 9))\n    return false;\n\n  while(read_field(buf, sizeof(buf), fp))\n  {\n    switch(buf[0])\n    {\n      case 'W': /* Width */\n        if(!unsigned_value(&y4m->width, buf + 1))\n          return false;\n        break;\n      case 'H': /* Height */\n        if(!unsigned_value(&y4m->height, buf + 1))\n          return false;\n        break;\n      case 'C': /* Chroma subsampling */\n        if(!subsampling_value(&y4m->subsampling, buf + 1))\n          return false;\n        break;\n      case 'I': /* Interlacing */\n        if(!interlacing_value(&y4m->interlacing, buf + 1))\n          return false;\n        break;\n      case 'F': /* Framerate */\n        if(!ratio_value(&y4m->framerate_n, &y4m->framerate_d, buf + 1))\n          return false;\n        break;\n      case 'A': /* Pixel aspect ratio */\n        if(!ratio_value(&y4m->pixel_n, &y4m->pixel_d, buf + 1))\n          return false;\n        break;\n      case 'X': /* metadata */\n        if(!strncmp(buf + 1, \"COLORRANGE=\", 11))\n        {\n          if(!color_range_value(&y4m->color_range, buf + 12))\n            return false;\n        }\n        break;\n    }\n  }\n  if(fgetc(fp) != '\\n')\n    return false;\n\n  if(!y4m->width || !y4m->height)\n    return false;\n\n  y4m->y_size = y4m->width * y4m->height;\n  y4m->c_size = 0;\n  switch(y4m->subsampling)\n  {\n    case Y4M_SUB_420JPEG:\n    case Y4M_SUB_420MPEG2:\n    case Y4M_SUB_420PALDV:\n      y4m->c_size = y4m->y_size >> 2;\n      y4m->c_width = y4m->width >> 1;\n      y4m->c_height = y4m->height >> 1;\n      y4m->c_x_shift = 1;\n      y4m->c_y_shift = 1;\n      break;\n\n    case Y4M_SUB_411:\n      y4m->c_size = y4m->y_size >> 2;\n      y4m->c_width = y4m->width >> 2;\n      y4m->c_height = y4m->height;\n      y4m->c_x_shift = 2;\n      break;\n\n    case Y4M_SUB_422:\n      y4m->c_size = y4m->y_size >> 1;\n      y4m->c_width = y4m->width >> 1;\n      y4m->c_height = y4m->height;\n      y4m->c_x_shift = 1;\n      break;\n\n    case Y4M_SUB_444:\n    case Y4M_SUB_444ALPHA:\n      y4m->c_size = y4m->y_size;\n      y4m->c_width = y4m->width;\n      y4m->c_height = y4m->height;\n      break;\n\n    case Y4M_SUB_MONO:\n      /* No chroma planes */\n      break;\n  }\n\n  y4m->ram_per_frame = y4m->y_size;\n  if(y4m->c_size)\n    y4m->ram_per_frame += 2 * y4m->c_size;\n  if(y4m->subsampling == Y4M_SUB_444ALPHA)\n    y4m->ram_per_frame += y4m->y_size;\n\n  y4m->rgba_buffer_size = y4m->y_size * sizeof(struct y4m_rgba_color);\n  return true;\n}\n\nboolean y4m_init_frame(const struct y4m_data *y4m, struct y4m_frame_data *yf)\n{\n  memset(yf, 0, sizeof(*yf));\n\n  yf->y = (uint8_t *)malloc(y4m->y_size);\n  if(!yf->y)\n    return false;\n\n  if(y4m->c_size)\n  {\n    yf->pb = (uint8_t *)malloc(y4m->c_size);\n    yf->pr = (uint8_t *)malloc(y4m->c_size);\n    if(!yf->pb || !yf->pr)\n    {\n      y4m_free_frame(yf);\n      return false;\n    }\n  }\n\n  if(y4m->subsampling == Y4M_SUB_444ALPHA)\n  {\n    yf->a = (uint8_t *)malloc(y4m->y_size);\n    if(!yf->a)\n    {\n      y4m_free_frame(yf);\n      return false;\n    }\n  }\n\n  return true;\n}\n\nboolean y4m_begin_frame(const struct y4m_data *y4m, struct y4m_frame_data *yf, FILE *fp)\n{\n  char buf[256];\n\n  if(!fread(buf, 5, 1, fp) || memcmp(buf, \"FRAME\", 5))\n    return false;\n\n  while(read_field(buf, sizeof(buf), fp))\n  {\n    switch(buf[0])\n    {\n      case 'I': /* Interlacing */\n        // FIXME: implement\n        break;\n\n      case 'X': /* metadata */\n        break;\n    }\n  }\n  if(fgetc(fp) != '\\n')\n    return false;\n\n  return true;\n}\n\nboolean y4m_read_frame(const struct y4m_data *y4m, struct y4m_frame_data *yf, FILE *fp)\n{\n  if(fread(yf->y, 1, y4m->y_size, fp) < y4m->y_size)\n    return false;\n\n  if(y4m->c_size)\n  {\n    if(fread(yf->pb, 1, y4m->c_size, fp) < y4m->c_size ||\n     fread(yf->pr, 1, y4m->c_size, fp) < y4m->c_size)\n      return false;\n  }\n  if(yf->a)\n  {\n    if(fread(yf->a, 1, y4m->y_size, fp) < y4m->y_size)\n      return false;\n  }\n  return true;\n}\n\nvoid y4m_convert_frame_rgba(const struct y4m_data *y4m,\n const struct y4m_frame_data *yf, struct y4m_rgba_color *dest)\n{\n  // TODO: siting?\n  // TODO: interlacing?\n  const uint8_t *y = yf->y;\n  const uint8_t *pb = yf->pb;\n  const uint8_t *pr = yf->pr;\n  const uint8_t *a = yf->a;\n  size_t shift = y4m->c_x_shift;\n  size_t max_sub = 1 << y4m->c_y_shift;\n  size_t row_sub = 0;\n  size_t i;\n  size_t j;\n  boolean chroma = (pb && pr);\n\n  for(i = 0; i < y4m->height; i++)\n  {\n    for(j = 0; j < y4m->width; j++)\n    {\n      int yval = y[j];\n      int pbval = chroma ? pb[j >> shift] - 128 : 0;\n      int prval = chroma ? pr[j >> shift] - 128 : 0;\n      int aval = a ? a[j] : 255;\n\n      int r = yval + 359 * prval / 256;\n      int g = yval - (88 * pbval + 183 * prval) / 256;\n      int b = yval + 453 * pbval / 256;\n\n      if(y4m->color_range == Y4M_RANGE_STUDIO)\n      {\n        r = (r - 16) * 255 / 224;\n        g = (g - 16) * 255 / 224;\n        b = (b - 16) * 255 / 224;\n      }\n\n      dest->r = Y4M_CLAMP(r, 0, 255);\n      dest->g = Y4M_CLAMP(g, 0, 255);\n      dest->b = Y4M_CLAMP(b, 0, 255);\n      dest->a = aval;\n      dest++;\n    }\n\n    y += y4m->width;\n    if(chroma)\n    {\n      row_sub++;\n      if(row_sub >= max_sub)\n      {\n        row_sub = 0;\n        pb += y4m->c_width;\n        pr += y4m->c_width;\n      }\n    }\n    if(a)\n      a += y4m->width;\n  }\n}\n\nvoid y4m_free_frame(struct y4m_frame_data *yf)\n{\n  free(yf->y);\n  free(yf->pb);\n  free(yf->pr);\n  free(yf->a);\n  yf->y = yf->pb = yf->pr = yf->a = NULL;\n}\n\nvoid y4m_free(struct y4m_data *y4m)\n{\n  // nop\n}\n"
  },
  {
    "path": "src/utils/y4m.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __UTILS_Y4M_H\n#define __UTILS_Y4M_H\n\n#include \"../compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stdint.h>\n#include <stdio.h>\n\nenum y4m_subsampling\n{\n  Y4M_SUB_420JPEG,\n  Y4M_SUB_420MPEG2,\n  Y4M_SUB_420PALDV,\n  Y4M_SUB_411,\n  Y4M_SUB_422,\n  Y4M_SUB_444,\n  Y4M_SUB_444ALPHA,\n  Y4M_SUB_MONO,\n};\n\nenum y4m_interlacing\n{\n  Y4M_INTER_UNKNOWN,\n  Y4M_INTER_PROGRESSIVE,\n  Y4M_INTER_TOP_FIRST,\n  Y4M_INTER_BOTTOM_FIRST,\n  Y4M_INTER_MIXED,\n};\n\nenum y4m_color_range\n{\n  Y4M_RANGE_FULL,\n  Y4M_RANGE_STUDIO,\n};\n\nenum y4m_frame_interlacing\n{\n  Y4M_FRAME_PRESENT,\n  Y4M_FRAME_TOP_FIRST,\n  Y4M_FRAME_TOP_FIRST_REPEAT,\n  Y4M_FRAME_BOTTOM_FIRST,\n  Y4M_FRAME_BOTTOM_FIRST_REPEAT,\n  Y4M_FRAME_SINGLE_PROGRESSIVE,\n  Y4M_FRAME_DOUBLE_PROGRESSIVE,\n  Y4M_FRAME_TRIPLE_PROGRESSIVE,\n};\n\nenum y4m_frame_temporal_sampling\n{\n  Y4M_FRAME_TEMP_PROGRESSIVE,\n  Y4M_FRAME_TEMP_INTERLACED,\n};\n\nenum y4m_frame_subsampling\n{\n  Y4M_FRAME_SUB_PROGRESSIVE,\n  Y4M_FRAME_SUB_INTERLACED,\n  Y4M_FRAME_SUB_UNKNOWN,\n};\n\nstruct y4m_data\n{\n  size_t rgba_buffer_size;\n  size_t ram_per_frame;\n\n  uint32_t width;\n  uint32_t height;\n  enum y4m_subsampling subsampling;\n  enum y4m_interlacing interlacing;\n  enum y4m_color_range color_range;\n  uint32_t framerate_n;\n  uint32_t framerate_d;\n  uint32_t pixel_n;\n  uint32_t pixel_d;\n\n  size_t y_size;\n  size_t c_size;\n  uint32_t c_width;\n  uint32_t c_height;\n  unsigned c_x_shift;\n  unsigned c_y_shift;\n};\n\nstruct y4m_frame_data\n{\n  uint8_t *y;\n  uint8_t *pb;\n  uint8_t *pr;\n  uint8_t *a;\n\n  enum y4m_frame_interlacing frame_interlacing;\n  enum y4m_frame_temporal_sampling frame_temporal_sampling;\n  enum y4m_frame_subsampling frame_subsampling;\n};\n\nstruct y4m_rgba_color\n{\n  uint8_t r;\n  uint8_t g;\n  uint8_t b;\n  uint8_t a;\n};\n\nboolean y4m_init(struct y4m_data *y4m, FILE *fp);\nboolean y4m_init_frame(const struct y4m_data *y4m, struct y4m_frame_data *yf);\nboolean y4m_begin_frame(const struct y4m_data *y4m, struct y4m_frame_data *yf, FILE *fp);\nboolean y4m_read_frame(const struct y4m_data *y4m, struct y4m_frame_data *yf, FILE *fp);\nvoid y4m_convert_frame_rgba(const struct y4m_data *y4m,\n const struct y4m_frame_data *yf, struct y4m_rgba_color *dest);\nvoid y4m_free_frame(struct y4m_frame_data *yf);\nvoid y4m_free(struct y4m_data *y4m);\n\n__M_END_DECLS\n\n#endif /* __UTILS_Y4M_H */\n"
  },
  {
    "path": "src/utils/y4m2smzx.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2010 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2023-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdio.h>\n\n#include \"image_file.h\"\n#include \"smzxconv.h\"\n#include \"y4m.h\"\n\n#define SKIP_SDL\n#ifdef SKIP_SDL\n// Shut Up GCC\n#endif\n#include \"../graphics.h\"\n#include \"../platform.h\"\n#include \"../util.h\"\n#include \"../io/memfile.h\"\n\n#ifdef _WIN32\n#include <fcntl.h>\n#endif\n\n#ifdef CONFIG_PLEDGE_UTILS\n#include <unistd.h>\n#define PROMISES \"stdio rpath wpath cpath\"\n#endif\n\n//#define Y4M_DEBUG\n\n#ifndef PLATFORM_NO_THREADING\n#define MAX_WORKERS 256\n#endif\n\n#define STDIO_BUFFER_SIZE (1 << 15)\n\n#define DEFAULT_MZX_SPEED   4\n#define DEFAULT_FRAMERATE_N 125\n#define DEFAULT_FRAMERATE_D (2 * (DEFAULT_MZX_SPEED - 1))\n\nstatic const char USAGE[] =\n{\n\"y4m2smzx Image Conversion Utility\\n\\n\"\n\n\"Usage: %s <in.y4m|-> <out.dat|-> [options]\\n\\n\"\n\n\"  Converts a .y4m input file to a custom video format easily loaded by Robotic.\\n\\n\"\n\n\"  If the input file is '-', it will be read from stdin.\\n\"\n\"  If the output file is '-', it will be written to stdout.\\n\\n\"\n\n\"  Options:\\n\\n\"\n\n\"    --             Do not parse any of the following arguments as options.\\n\"\n\"    -f#:#          Set the framerate ratio of the output. The default is the\\n\"\n\"                   framerate specified in the input .y4m, or 125:6 if none.\\n\"\n\"    -j#            Set the number of worker threads (0=synchronous, default).\\n\"\n\"    --skip-char=#  Set the character to skip (one max, -1=none, default).\\n\"\n\"\\n\"\n\n\"  The binary output format is an unpadded IFF-like:\\n\\n\"\n\n\"    Head:  MZXV [len:u32le] [chunks...] (len may be zero)\\n\"\n\"    Chunk: [FOURCC identifier] [len:u32le] [data...]\\n\\n\"\n\n\"  Supported header chunk FOURCCs (must preceed frame data):\\n\\n\"\n\n\"    fwid     [display_width_in_chars:u32le] (required)\\n\"\n\"    fhei     [display_height_in_chars:u32le] (required)\\n\"\n\"    bwid     [buffer_width_in_chars:u32le] (default fwid)\\n\"\n\"    bhei     [buffer_height_in_chars:u32le] (default fhei)\\n\"\n\"    sprx     [sprite_x_pitch:u32le] (default 0)\\n\"\n\"    spry     [sprite_y_pitch:u32le] (default 0)\\n\"\n\"    sprw     [sprites_per_row:u32le] (default 1)\\n\"\n\"    sprh     [sprites_per_column:u32le] (default 1)\\n\"\n\"    spru     [sprites_unbound:u32le] (default 0)\\n\"\n\"    spro     [sprite_offset_per_sprite:u32le] (default 0)\\n\"\n\"    sprt     [sprite_tcol:s32le] (default -1, bottom sprite always -1)\\n\"\n\"\\n\"\n\"  Supported frame chunk FOURCCs (omitted data persists between frames):\\n\\n\"\n\n\"    smzx     [smzxmode:u32le] (default 0)\\n\"\n\"    rate     [numerator:u32le] [denominator:u32le] (default 125/6)\\n\"\n\"    fpal     [palette:len bytes]\\n\"\n\"    fidx     [indices:len bytes]\\n\"\n\"    fchr     [charset:len bytes]\\n\"\n\"    f1ch     [char_plane:len bytes] (frame displays upon parsing)\\n\"\n\"    f1co     [color_plane:len bytes] (frame displays upon parsing)\\n\"\n\"    f2in     [interleaved_planes:len bytes] (frame displays upon parsing)\\n\"\n\"\\n\"\n};\n\nstruct y4m_convert_data\n{\n  const struct y4m_data *y4m;\n  struct y4m_frame_data yf;\n  struct rgba_color *rgba_buffer;\n  smzx_converter *conv;\n  mzx_tile *tile;\n  mzx_glyph chr[256];\n  mzx_color pal[256];\n  size_t tile_size;\n  boolean init;\n  boolean ready;\n};\n\n#ifdef MAX_WORKERS\nstatic platform_mutex worker_lock;\nstatic platform_sem worker_sem;\nstatic platform_sem main_sem;\nstatic platform_thread workers[MAX_WORKERS];\nstatic boolean worker_exit = false;\nstatic boolean sync_init = false;\nstatic int num_workers_allocated = 0;\nstatic int queue_worker_head = 0;\nstatic int queue_main_head = 0;\n#endif\nstatic int num_workers = 0;\n\nstatic struct y4m_convert_data *queue = NULL;\nstatic int queue_size = 0;\nstatic int mzm_size = 0;\nstatic int framerate_n = -1;\nstatic int framerate_d = -1;\nstatic int skip_char = -1;\nstatic size_t ram_usage = 0;\n\nstatic void fourcc(const char *fourcc, size_t len, struct memfile *mf)\n{\n  mfwrite(fourcc, 4, 1, mf);\n  mfputud(len, mf);\n}\n\nstatic boolean y4m_queue_init(const struct y4m_data *y4m, int num,\n int width_chars, int height_chars)\n{\n  int i;\n  if(queue)\n  {\n    fprintf(stderr, \"ERROR: conversion data already initialized!\\n\");\n    return false;\n  }\n\n  queue = (struct y4m_convert_data *)calloc(num, sizeof(struct y4m_convert_data));\n  if(!queue)\n  {\n    fprintf(stderr, \"ERROR: failed to conversion data!\\n\");\n    return false;\n  }\n  ram_usage += num * sizeof(struct y4m_convert_data);\n  queue_size = num;\n  mzm_size = sizeof(mzx_tile) * width_chars * height_chars;\n\n  for(i = 0; i < queue_size; i++)\n  {\n    queue[i].y4m = y4m;\n    queue[i].ready = true;\n    if(!y4m_init_frame(y4m, &(queue[i].yf)))\n    {\n      fprintf(stderr, \"ERROR: failed to initialize y4m frame buffer %d.\\n\", i);\n      return false;\n    }\n    ram_usage += y4m->ram_per_frame;\n\n    queue[i].conv = smzx_convert_init(width_chars, height_chars, 0, skip_char, 256, 0, 16);\n    if(!queue[i].conv)\n    {\n      fprintf(stderr, \"ERROR: failed to initialize converter %d.\\n\", i);\n      return false;\n    }\n    // TODO: smzx converter RAM\n\n    queue[i].tile = (mzx_tile *)malloc(mzm_size);\n    if(!queue[i].tile)\n    {\n      fprintf(stderr, \"ERROR: failed to allocate tile buffer %d.\\n\", i);\n      return false;\n    }\n    ram_usage += mzm_size;\n\n    queue[i].rgba_buffer = (struct rgba_color *)malloc(y4m->rgba_buffer_size);\n    if(!queue[i].rgba_buffer)\n    {\n      fprintf(stderr, \"ERROR: failed to allocate frame buffer %d.\\n\", i);\n      return false;\n    }\n    ram_usage += y4m->rgba_buffer_size;\n  }\n  return true;\n}\n\nstatic void y4m_queue_destroy(void)\n{\n  int i;\n  if(!queue)\n    return;\n\n  for(i = 0; i < queue_size; i++)\n  {\n    y4m_free_frame(&(queue[i].yf));\n\n    if(queue[i].conv)\n      smzx_convert_free(queue[i].conv);\n\n    free(queue[i].tile);\n    free(queue[i].rgba_buffer);\n  }\n  free(queue);\n  queue = NULL;\n}\n\nstatic void y4m_smzx_convert(struct y4m_convert_data *d)\n{\n  y4m_convert_frame_rgba(d->y4m, &(d->yf), (struct y4m_rgba_color *)d->rgba_buffer);\n  smzx_convert(d->conv, d->rgba_buffer, d->tile, d->chr, d->pal);\n}\n\n#ifdef MAX_WORKERS\nstatic THREAD_RES y4m_worker_function(void *opaque)\n{\n  struct y4m_convert_data *d = NULL;\n#ifdef Y4M_DEBUG\n  int n;\n#endif\n\n  platform_mutex_lock(&worker_lock);\n  while(!worker_exit)\n  {\n    platform_mutex_unlock(&worker_lock);\n    platform_sem_wait(&worker_sem);\n    platform_mutex_lock(&worker_lock);\n    if(worker_exit)\n      break;\n\n#ifdef Y4M_DEBUG\n    n = queue_worker_head;\n    fprintf(stderr, \"convert at %d\\n\", n);\n    fflush(stderr);\n#endif\n\n    d = &queue[queue_worker_head];\n    queue_worker_head++;\n    if(queue_worker_head >= queue_size)\n      queue_worker_head = 0;\n\n    platform_mutex_unlock(&worker_lock);\n\n    y4m_smzx_convert(d);\n\n    platform_mutex_lock(&worker_lock);\n    platform_sem_post(&main_sem);\n    d->ready = true;\n#ifdef Y4M_DEBUG\n    fprintf(stderr, \"done at %d\\n\", n);\n    fflush(stderr);\n#endif\n  }\n  platform_mutex_unlock(&worker_lock);\n  THREAD_RETURN;\n}\n#endif\n\nstatic boolean y4m_workers_init(int num)\n{\n#ifdef MAX_WORKERS\n  int i;\n  if(num < 1)\n    return true;\n\n  if(sync_init || num_workers_allocated > 0)\n    return false;\n\n  if(!platform_mutex_init(&worker_lock) ||\n     !platform_sem_init(&worker_sem, 0) ||\n     !platform_sem_init(&main_sem, queue_size))\n      return false;\n  sync_init = true;\n\n  for(i = 0; i < num; i++)\n  {\n    if(!platform_thread_create(&workers[i], y4m_worker_function, NULL))\n    {\n      fprintf(stderr, \"ERROR: failed to initialize worker %d.\\n\", i);\n      return false;\n    }\n  }\n  num_workers_allocated = num;\n#endif\n\n  return true;\n}\n\nstatic void y4m_workers_destroy(void)\n{\n#ifdef MAX_WORKERS\n  if(num_workers_allocated)\n  {\n    int i;\n    platform_mutex_lock(&worker_lock);\n    worker_exit = true;\n    platform_mutex_unlock(&worker_lock);\n\n    for(i = 0; i < num_workers_allocated; i++)\n      platform_sem_post(&worker_sem);\n\n    for(i = 0; i < num_workers_allocated; i++)\n      platform_thread_join(&workers[i]);\n\n    num_workers_allocated = 0;\n  }\n\n  if(sync_init)\n  {\n    platform_sem_destroy(&main_sem);\n    platform_sem_destroy(&worker_sem);\n    platform_mutex_destroy(&worker_lock);\n    sync_init = false;\n  }\n#endif\n}\n\nstatic void y4m_write_frame(struct y4m_convert_data *d, FILE *out)\n{\n  struct memfile mf;\n  uint8_t buffer[8];\n\n  mfopen_wr(buffer, sizeof(buffer), &mf);\n  fourcc(\"fchr\", CHAR_SIZE * CHARSET_SIZE, &mf);\n  fwrite(buffer, 8, 1, out);\n  fwrite(d->chr, CHAR_SIZE, CHARSET_SIZE, out);\n\n  mfseek(&mf, 0, SEEK_SET);\n  fourcc(\"fpal\", SMZX_PAL_SIZE * 3, &mf);\n  fwrite(buffer, 8, 1, out);\n  fwrite(d->pal, SMZX_PAL_SIZE, 3, out);\n\n  mfseek(&mf, 0, SEEK_SET);\n  fourcc(\"f2in\", mzm_size, &mf);\n  fwrite(buffer, 8, 1, out);\n  fwrite(d->tile, mzm_size, 1, out);\n}\n\nstatic boolean y4m_do_conversion(struct y4m_data *y4m,\n int width_chars, int height_chars, FILE *in, FILE *out)\n{\n  struct memfile mf;\n  uint8_t buffer[256];\n\n  struct y4m_convert_data *d;\n#ifdef Y4M_DEBUG\n  int n = 0;\n#endif\n\n  size_t frames_in = 0;\n  size_t frames_out = 0;\n  boolean input_done = false;\n  int pending = 0;\n\n  if(framerate_n <= 0 || framerate_d <= 0)\n  {\n    if(y4m->framerate_n && y4m->framerate_d)\n    {\n      framerate_n = y4m->framerate_n;\n      framerate_d = y4m->framerate_d;\n    }\n    else\n    {\n      framerate_n = DEFAULT_FRAMERATE_N;\n      framerate_d = DEFAULT_FRAMERATE_D;\n    }\n  }\n\n  /* Headerless mode 1 MZM output. */\n  mzm_size = 2 * width_chars * height_chars;\n\n  mfopen_wr(buffer, sizeof(buffer), &mf);\n  fourcc(\"MZXV\", 0, &mf);\n  fourcc(\"fwid\", 4, &mf);\n  mfputud(width_chars, &mf);\n  fourcc(\"fhei\", 4, &mf);\n  mfputud(height_chars, &mf);\n  fourcc(\"smzx\", 4, &mf);\n  mfputud(2, &mf);\n  fourcc(\"rate\", 8, &mf);\n  mfputud(framerate_n, &mf);\n  mfputud(framerate_d, &mf);\n\n  if(!fwrite(buffer, mftell(&mf), 1, out))\n    return false;\n\n  while(!input_done || frames_out < frames_in)\n  {\n#ifdef MAX_WORKERS\n    if(num_workers)\n    {\n      platform_mutex_lock(&worker_lock);\n      d = &queue[queue_main_head];\n      if(pending == 0 || !d->ready)\n      {\n        platform_mutex_unlock(&worker_lock);\n        platform_sem_wait(&main_sem);\n        pending++;\n        continue;\n      }\n\n#ifdef Y4M_DEBUG\n      n = queue_main_head;\n#endif\n      queue_main_head++;\n      if(queue_main_head >= queue_size)\n        queue_main_head = 0;\n\n      platform_mutex_unlock(&worker_lock);\n    }\n    else\n#endif\n      d = queue;\n\n    if(d->init)\n    {\n#ifdef Y4M_DEBUG\n      fprintf(stderr, \"write from %d\\n\", n);\n      fflush(stderr);\n#endif\n      if(!(frames_out & 0xff))\n      {\n        fprintf(stderr, \".\");\n        fflush(stderr);\n      }\n      y4m_write_frame(d, out);\n      frames_out++;\n      pending--;\n    }\n\n    if(input_done)\n      continue;\n\n    if(!y4m_begin_frame(y4m, &(d->yf), in))\n    {\n      input_done = true;\n      continue;\n    }\n\n    if(!y4m_read_frame(y4m, &(d->yf), in))\n    {\n      fprintf(stderr, \"ERROR: failed to process frame\\n\");\n      return false;\n    }\n    d->ready = false;\n    d->init = true;\n    frames_in++;\n\n#ifdef Y4M_DEBUG\n    fprintf(stderr, \"input to %d\\n\", n);\n    fflush(stderr);\n#endif\n\n#ifdef MAX_WORKERS\n    if(num_workers)\n      platform_sem_post(&worker_sem);\n    else\n#endif\n      y4m_smzx_convert(d);\n  }\n\n  fprintf(stderr, \"\\n%zu frames in, %zu frames out\\n\", frames_in, frames_out);\n  return true;\n}\n\n\nint main(int argc, char **argv)\n{\n  struct y4m_data y4m;\n  int num_queue;\n\n  const char *input_file_name = NULL;\n  const char *output_file_name = NULL;\n  FILE *in = NULL;\n  FILE *out = NULL;\n  boolean init = false;\n  boolean noopt = false;\n  int ret = 2;\n  char *end;\n  int i;\n\n  unsigned width_chars;\n  unsigned height_chars;\n\n  for(i = 1; i < argc; i++)\n  {\n    if(argv[i][0] == '-' && argv[i][1] == '-' && !noopt)\n    {\n      if(!strcmp(argv[i], \"--\"))\n      {\n        noopt = true;\n      }\n      else\n\n      if(!strncmp(argv[i], \"--skip-char=\", 12))\n      {\n        skip_char = strtol(argv[i] + 12, &end, 10);\n        if(skip_char < -1 || skip_char >= 256 || *end != '\\0')\n          goto err_param;\n      }\n    }\n    else\n\n    if(argv[i][0] == '-' && argv[i][1] != '\\0' && !noopt)\n    {\n      int j;\n      for(j = 1; argv[i][j]; j++)\n      {\n        if(argv[i][j] == 'f')\n        {\n          framerate_n = strtoul(argv[i] + j + 1, &end, 10);\n          if(*end != ':' || framerate_n <= 0)\n            goto err_param;\n          framerate_d = strtoul(end + 1, &end, 10);\n          if(*end != '\\0' || framerate_d <= 0)\n            goto err_param;\n          break;\n        }\n        else\n\n        if(argv[i][j] == 'j')\n        {\n          num_workers = strtoul(argv[i] + j + 1, &end, 10);\n          if(*end != '\\0')\n            goto err_param;\n          break;\n        }\n        else\n          goto err_param;\n      }\n    }\n    else\n\n    if(!input_file_name)\n    {\n      input_file_name = argv[i];\n    }\n    else\n\n    if(!output_file_name)\n    {\n      output_file_name = argv[i];\n    }\n    continue;\n\nerr_param:\n    fprintf(stderr, \"ERROR: invalid parameter '%s'\\n\", argv[i]);\n    return 1;\n  }\n\n  if(!input_file_name || !output_file_name)\n  {\n    fprintf(stderr, USAGE, argv[0]);\n    return 1;\n  }\n\n#ifdef MAX_WORKERS\n  num_workers = CLAMP(num_workers, 0, MAX_WORKERS);\n  num_queue = num_workers * 2;\n#else\n  num_workers = 0;\n#endif\n  if(num_workers < 1)\n    num_queue = 1;\n\n  if(!strcmp(input_file_name, \"-\"))\n  {\n    input_file_name = NULL;\n    in = stdin;\n#ifdef _WIN32\n    /* Windows forces stdin to be text mode by default, fix it. */\n    _setmode(_fileno(stdin), _O_BINARY);\n#endif\n  }\n\n  if(!strcmp(output_file_name, \"-\"))\n  {\n    output_file_name = NULL;\n    out = stdout;\n#ifdef _WIN32\n    /* Windows forces stdout to be text mode by default, fix it. */\n    _setmode(_fileno(stdout), _O_BINARY);\n#endif\n  }\n\n#ifdef CONFIG_PLEDGE_UTILS\n#ifdef PLEDGE_HAS_UNVEIL\n  if((input_file_name && unveil(input_file_name, \"r\")) ||\n   (output_file_name && unveil(output_file_name, \"cw\")) ||\n   unveil(NULL, NULL))\n  {\n    fprintf(stderr, \"ERROR: Failed unveil!\\n\");\n    return 1;\n  }\n#endif\n\n  if(pledge(PROMISES, \"\"))\n  {\n    fprintf(stderr, \"ERROR: Failed pledge!\\n\");\n    return 1;\n  }\n#endif\n\n  if(input_file_name)\n  {\n    in = fopen_unsafe(input_file_name, \"rb\");\n    if(!in)\n    {\n      fprintf(stderr, \"ERROR: failed to open input file.\\n\");\n      goto err;\n    }\n    setvbuf(in, NULL, _IOFBF, STDIO_BUFFER_SIZE);\n    ram_usage += STDIO_BUFFER_SIZE;\n  }\n  if(!y4m_init(&y4m, in))\n  {\n    fprintf(stderr, \"ERROR: input file is not y4m\\n\");\n    goto err;\n  }\n  init = true;\n\n  if(y4m.width % 8)\n  {\n    fprintf(stderr, \"ERROR: image width not divisible by 8\\n\");\n    goto err;\n  }\n  if(y4m.height % 14)\n  {\n    fprintf(stderr, \"WARNING: image height not divisible by 14; image will be cropped\\n\");\n  }\n  width_chars = y4m.width / 8;\n  height_chars = y4m.height / 14;\n\n  if(!y4m_queue_init(&y4m, num_queue, width_chars, height_chars))\n    goto err;\n\n  if(!y4m_workers_init(num_workers))\n    goto err;\n\n  if(output_file_name)\n  {\n    out = fopen_unsafe(output_file_name, \"wb\");\n    if(!out)\n    {\n      fprintf(stderr, \"ERROR: failed to open output file.\\n\");\n      goto err;\n    }\n    setvbuf(out, NULL, _IOFBF, STDIO_BUFFER_SIZE);\n    ram_usage += STDIO_BUFFER_SIZE;\n  }\n\n  fprintf(stderr, \"Converter ready - allocated %zu of buffers.\\n\", ram_usage);\n  fflush(stderr);\n\n  if(!y4m_do_conversion(&y4m, width_chars, height_chars, in, out))\n    goto err;\n\n  // Insert total output file length (if not stdout).\n  if(output_file_name)\n  {\n    long total = ftell(out) - 8;\n    if(total > 0 && fseek(out, 4, SEEK_SET) == 0)\n    {\n      fputc(total & 0xff, out);\n      fputc((total >> 8) & 0xff, out);\n      fputc((total >> 16) & 0xff, out);\n      fputc((total >> 24) & 0xff, out);\n    }\n  }\n  ret = 0;\n\nerr:\n  y4m_workers_destroy();\n  y4m_queue_destroy();\n\n  if(init)\n    y4m_free(&y4m);\n\n  if(input_file_name && in)\n    fclose(in);\n\n  if(output_file_name && out)\n    fclose(out);\n\n  return ret;\n}\n\n#if 0\n#include <yuv4mpeg.h>\n\n#define MZX_SPEED 4\n\nstatic const y4m_ratio_t dest_fps = { 125, 2 * (MZX_SPEED - 1) };\n\nint main(int argc, char **argv)\n{\n  int sf, df, stop = 0;\n  int fd, frame_count, csub_type, csub_x, csub_y, x, y, yc, ic, i, r, g, b;\n  smzx_converter *c;\n  struct rgba_color img[640 * 350];\n  mzx_tile tile[80 * 25];\n  mzx_glyph chr[256];\n  mzx_color pal[256];\n  int yuvlen[3];\n  uint8_t *yuv[3];\n  y4m_ratio_t src_fps;\n  y4m_ratio_t frame_ratio;\n  y4m_stream_info_t istream;\n  y4m_frame_info_t iframe;\n\n  fd = fileno(stdin);\n  y4m_accept_extensions(1);\n  y4m_init_stream_info(&istream);\n  y4m_init_frame_info(&iframe);\n\n  if(y4m_read_stream_header(fd, &istream) != Y4M_OK)\n  {\n    fprintf(stderr, \"Error reading stream header.\\n\");\n    return 1;\n  }\n  if(y4m_si_get_plane_count(&istream) != 3)\n  {\n    fprintf(stderr, \"Only 3-plane formats supported.\\n\");\n    return 1;\n  }\n  if((y4m_si_get_width(&istream) != 640) || (y4m_si_get_height(&istream) != 350))\n  {\n    fprintf(stderr, \"Video not 640x350.\\n\");\n    return 1;\n  }\n\n  src_fps = y4m_si_get_framerate(&istream);\n  frame_ratio.n = src_fps.d * dest_fps.n;\n  frame_ratio.d = src_fps.n * dest_fps.d;\n  y4m_ratio_reduce(&frame_ratio);\n  frame_count = frame_ratio.d;\n  csub_type = y4m_si_get_chroma(&istream);\n  csub_x = y4m_chroma_ss_x_ratio(csub_type).d;\n  csub_y = y4m_chroma_ss_y_ratio(csub_type).d;\n  yuvlen[0] = y4m_si_get_plane_length(&istream, 0);\n  yuvlen[1] = y4m_si_get_plane_length(&istream, 1);\n  yuvlen[2] = y4m_si_get_plane_length(&istream, 2);\n\n  yuv[0] = malloc(yuvlen[0]);\n  yuv[1] = malloc(yuvlen[1]);\n  yuv[2] = malloc(yuvlen[2]);\n  if(!yuv[0] || !yuv[1] || !yuv[2])\n  {\n    fprintf(stderr, \"Error allocating YUV buffer.\\n\");\n    free(yuv[0]);\n    free(yuv[1]);\n    free(yuv[2]);\n    y4m_fini_frame_info(&iframe);\n    y4m_fini_stream_info(&istream);\n    return 1;\n  }\n\n  c = smzx_convert_init(80, 25, 0, -1, 256, 0, 16);\n  if(!c)\n  {\n    fprintf(stderr, \"Error initializing converter.\\n\");\n    free(yuv[0]);\n    free(yuv[1]);\n    free(yuv[2]);\n    y4m_fini_frame_info(&iframe);\n    y4m_fini_stream_info(&istream);\n    return 1;\n  }\n\n  sf = df = 0;\n  while(!stop && (y4m_read_frame(fd, &istream, &iframe, yuv) == Y4M_OK))\n  {\n    frame_count += frame_ratio.n;\n    if(frame_count > frame_ratio.d)\n    {\n      for(i = y = 0; y < 350; y++)\n      {\n        yc = y / csub_y * 640 / csub_x;\n        for(x = 0; x < 640; x++, i++)\n        {\n          ic = yc + x / csub_x;\n          r = yuv[0][i]\n            + 359 * (yuv[2][ic] - 128) / 256;\n          g = yuv[0][i]\n            - 88 * (yuv[1][ic] - 128) / 256\n            - 183 * (yuv[2][ic] - 128) / 256;\n          b = yuv[0][i]\n            + 453 * (yuv[1][ic] - 128) / 256;\n          r = (r < 0) ? 0 : ((r > 255) ? 255 : r);\n          g = (g < 0) ? 0 : ((g > 255) ? 255 : g);\n          b = (b < 0) ? 0 : ((b > 255) ? 255 : b);\n          img[i].r = r;\n          img[i].g = g;\n          img[i].b = b;\n          img[i].a = 255;\n        }\n      }\n      smzx_convert(c, img, tile, chr, pal);\n      fprintf(stderr, \"Converted src: %d dest: %d\\n\", sf, df);\n    }\n\n    while(frame_count > frame_ratio.d)\n    {\n      if(!fwrite(pal, sizeof(pal), 1, stdout))\n      {\n        fprintf(stderr, \"Error writing stream.\\n\");\n        stop = 1;\n        break;\n      }\n      if(!fwrite(chr, sizeof(chr), 1, stdout))\n      {\n        fprintf(stderr, \"Error writing stream.\\n\");\n        stop = 1;\n        break;\n      }\n      if(!fwrite(tile, sizeof(tile), 1, stdout))\n      {\n        fprintf(stderr, \"Error writing stream.\\n\");\n        stop = 1;\n        break;\n      }\n      frame_count -= frame_ratio.d;\n      df++;\n    }\n    sf++;\n  }\n\n  free(yuv[0]);\n  free(yuv[1]);\n  free(yuv[2]);\n  y4m_fini_frame_info(&iframe);\n  y4m_fini_stream_info(&istream);\n  return 0;\n}\n#endif\n"
  },
  {
    "path": "src/window.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2012-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// Windowing code- Save/restore screen and draw windows and other elements,\n//                 also displays and runs dialog boxes.\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <ctype.h>\n#include <sys/stat.h>\n#include <assert.h>\n\n#include \"board.h\"\n#include \"configure.h\" // TODO for help file only\n#include \"const.h\"\n#include \"core.h\"\n#include \"counter.h\"\n#include \"data.h\"\n#include \"error.h\"\n#include \"event.h\"\n#include \"game_menu.h\"\n#include \"graphics.h\"\n#include \"helpsys.h\"\n#include \"intake.h\"\n#include \"platform.h\"\n#include \"robot.h\"\n#include \"window.h\"\n#include \"world.h\"\n#include \"util.h\"\n#include \"io/path.h\"\n#include \"io/vio.h\"\n\n#include \"audio/sfx.h\"\n\n#ifdef CONFIG_NDS\n// Should be sufficient with a disabled editor.\n#define NUM_SAVSCR 3\n#else\n#define NUM_SAVSCR 6\n#endif\n\nstatic struct char_element screen_storage[NUM_SAVSCR][SET_SCREEN_SIZE];\n\n// Current space for save_screen and restore_screen\nstatic int cur_screen = 0;\n\n// The last-used saved game slot, and the current slot prefix.\nstatic int cur_slot = 0;\nstatic char *cur_slot_prefix = NULL;\n\n// Some slot selector definitions.\n#define SLOT_DISABLED   -1\n#define SLOT_NORMAL     0\n#define SLOT_HIGHLIGHT  1\n\n#define SLOTSEL_BAR_Y  2\n\n#define SLOTSEL_MAX_ELEMENTS        4\n#define SLOTSEL_OKAY_BUTTON         0\n#define SLOTSEL_CANCEL_BUTTON       1\n#define SLOTSEL_FILE_MANAGER_BUTTON 2\n#define SLOTSEL_SELECTOR            3\n#define SLOTSEL_NUM_SLOTS           10\n\n// Free up memory.\n\n// The following functions do NOT check to see if memory is reserved, in\n// the interest of time, space, and simplicity. Make sure you have already\n// called window_cpp_init.\n\n// Saves current screen to buffer and increases buffer count. Returns\n// non-0 if the buffer for screens is already full. (IE 6 count)\n\nint save_screen(void)\n{\n  if(cur_screen >= NUM_SAVSCR)\n  {\n    cur_screen = 0;\n    error(\"Windowing code bug\", ERROR_T_FATAL, ERROR_OPT_EXIT, 0x1F01);\n  }\n\n  get_screen(screen_storage[cur_screen]);\n  cur_screen++;\n  return 0;\n}\n\n// Restores top screen from buffer to screen and decreases buffer count.\n// Returns non-0 if there are no screens in the buffer.\n\nint restore_screen(void)\n{\n  if(cur_screen == 0)\n    error(\"Windowing code bug\", ERROR_T_FATAL, ERROR_OPT_EXIT, 0x1F02);\n\n  cur_screen--;\n  set_screen(screen_storage[cur_screen]);\n  return 0;\n}\n\n// Draws an outlined and filled box from x1/y1 to x2/y2. Color is used to\n// fill box and for upper left lines. Dark_color is used for lower right\n// lines. Corner_color is used for the single chars in the upper right and\n// lower left corners. If Corner_color equals 0, these are auto calculated.\n// Returns non-0 for invalid parameters. Shadow (black spaces) is drawn and\n// clipped if specified. Operation on a size smaller than 3x3 is undefined.\n// This routine is highly unoptimized. Center is not filled if fill_center\n// is set to 0. (defaults to 1)\n\nint draw_window_box(int x1, int y1, int x2, int y2, int color,\n int dark_color, int corner_color, boolean shadow, boolean fill_center)\n{\n  return draw_window_box_ext(x1, y1, x2, y2, color, dark_color,\n   corner_color, shadow, fill_center, PRO_CH, 16);\n}\n\nint draw_window_box_ext(int x1, int y1, int x2, int y2, int color,\n int dark_color, int corner_color, boolean shadow, boolean fill_center,\n int offset, int c_offset)\n{\n  int t1, t2;\n  //Validate parameters\n  if((x1 < 0) || (y1 < 0) || (x1 > 79) || (y1 > 24)) return 1;\n  if((x2 < 0) || (y2 < 0) || (x2 > 79) || (y2 > 24)) return 1;\n  if(x2 < x1)\n  {\n    t1 = x1;\n    x1 = x2;\n    x2 = t1;\n  }\n  if(y2 < y1)\n  {\n    t1 = y1;\n    y1 = y2;\n    y2 = t1;\n  }\n\n  // Fill center\n  if(fill_center)\n  {\n    for(t1 = x1 + 1; t1 < x2; t1++)\n    {\n      for(t2 = y1 + 1; t2 < y2; t2++)\n      {\n        draw_char_ext(' ', color, t1, t2, offset, c_offset);\n      }\n    }\n  }\n\n  // Draw top and bottom edges\n  for(t1 = x1 + 1; t1 < x2; t1++)\n  {\n    draw_char_ext('\\xC4', color, t1, y1, offset, c_offset);\n    draw_char_ext('\\xC4', dark_color, t1, y2, offset, c_offset);\n  }\n\n  // Draw left and right edges\n  for(t2 = y1 + 1; t2 < y2; t2++)\n  {\n    draw_char_ext('\\xB3', color, x1, t2, offset, c_offset);\n    draw_char_ext('\\xB3', dark_color, x2, t2, offset, c_offset);\n  }\n\n  // Draw corners\n  draw_char_ext('\\xDA', color, x1, y1, offset, c_offset);\n  draw_char_ext('\\xD9', dark_color, x2, y2, offset, c_offset);\n  if(corner_color)\n  {\n    draw_char_ext('\\xC0', corner_color, x1, y2, offset, c_offset);\n    draw_char_ext('\\xBF', corner_color, x2, y1, offset, c_offset);\n  }\n  else\n  {\n    draw_char_ext('\\xC0', color, x1, y2, offset, c_offset);\n    draw_char_ext('\\xBF', dark_color, x2, y1, offset, c_offset);\n  }\n\n  // Draw shadow if applicable\n  if(shadow)\n  {\n    // Right edge\n    if(x2 < 79)\n    {\n      for(t2 = y1 + 1; t2 <= y2; t2++)\n      {\n        draw_char_ext(' ', 0, x2 + 1, t2, offset, c_offset);\n      }\n    }\n\n    // Lower edge\n    if(y2 < 24)\n    {\n      for(t1 = x1 + 1; t1 <= x2; t1++)\n      {\n        draw_char_ext(' ', 0, t1, y2 + 1, offset, c_offset);\n      }\n    }\n\n    // Lower right corner\n    if((y2 < 24) && (x2 < 79))\n    {\n      draw_char_ext(' ', 0, x2 + 1, y2 + 1, offset, c_offset);\n    }\n  }\n  return 0;\n}\n\n// Strings for drawing different dialog box elements.\n// All parts are assumed 3 wide.\n\n#define radio_on \"(\\x07)\"\n#define radio_off \"( )\"\n#define num_buttons \" \\x18  \\x19 \"\n#define list_button \" \\x1F \"\n\n#define char_sel_arrows_0 '\\x1E'\n#define char_sel_arrows_1 '\\x1F'\n#define char_sel_arrows_2 '\\x10'\n#define char_sel_arrows_3 '\\x11'\n\n// Char selection screen colors- Window colors same as dialog, non-current\n// characters same as nonactive, current character same as active, arrows\n// along edges same as text, title same as title.\n\n// Put up a character selection box. Returns selected, or negative selected\n// for ESC, -256 for -0. Returns OUT_OF_WIN_MEM if out of window mem.\n// Display is 32 across, 8 down. All work done on given page.\n\n// Mouse support- Click on a character to select it. If it is the current\n// character, it exits.\n\n// allow_char_255 -- if this is set to zero, char 255 will have a special\n// display corresponding to Custom* type behavior. This is meant for the char\n// ID editor only.\n\n// TODO this shouldn't really be here, but it's the best place for now.\n__editor_maybe_static int char_select_next_tile(int current_char,\n int direction, int highlight_width, int highlight_height)\n{\n  // -1 is previous, 1 is next\n  int x = current_char & 31;\n  int y = (current_char & 0xFF) >> 5;\n\n  int mod_x = x % highlight_width;\n  int mod_y = y % highlight_height;\n\n  int tiles_width = (32 - mod_x) / highlight_width;\n  int tiles_height = (8 - mod_y) / highlight_height;\n\n  int last_x = (tiles_width - 1) * highlight_width + mod_x;\n  int last_y = (tiles_height - 1) * highlight_height + mod_y;\n\n  if(direction > 0)\n  {\n    if(highlight_height == 1)\n    {\n      // No need for tiling with N x 1 selection\n      x += highlight_width;\n    }\n    else\n\n    if(x == last_x)\n    {\n      x = mod_x;\n\n      if(y == last_y)\n        y = mod_y;\n\n      else\n        y += highlight_height;\n    }\n    else\n    {\n      x += highlight_width;\n    }\n  }\n  else\n\n  if(direction < 0)\n  {\n    if(highlight_height == 1)\n    {\n      x -= highlight_width;\n    }\n    else\n\n    if(x == mod_x)\n    {\n      x = last_x;\n\n      if(y == mod_y)\n        y = last_y;\n\n      else\n        y -= highlight_height;\n    }\n    else\n    {\n      x -= highlight_width;\n    }\n  }\n\n  // Clear the char bits of current_char and replace with the new position\n  current_char &= (~0xFF);\n  current_char |= (x + (y * 32)) & 0xFF;\n\n  return current_char;\n}\n\n__editor_maybe_static int char_selection_ext(int current, int allow_char_255,\n int *width_ptr, int *height_ptr, int *select_charset, int selection_pal)\n{\n  uint32_t pal_layer = OVERLAY_LAYER;\n  uint32_t chars_layer = UI_LAYER;\n  int allow_multichar = 0;\n  int current_charset = 0;\n  int screen_mode = 0;\n  int shifted = 0;\n  int highlight_x = 0;\n  int highlight_y = 0;\n  int start_x = 0;\n  int start_y = 0;\n  int width = 1;\n  int height = 1;\n  int exit = 0;\n  int bottom = 16;\n  int x, y;\n  int i, i2;\n  int joystick_key;\n  int key;\n\n  if(width_ptr && height_ptr)\n  {\n    allow_multichar = 1;\n    width = *width_ptr;\n    height = *height_ptr;\n  }\n\n  if(select_charset)\n  {\n    pal_layer = create_layer(0, 0, 80, 25, LAYER_DRAWORDER_UI - 2,\n     -1, *select_charset * 256, 1);\n\n    chars_layer = create_layer(0, 0, 80, 25, LAYER_DRAWORDER_UI - 1,\n     -1, *select_charset * 256, 1);\n\n    set_layer_mode(chars_layer, 0);\n\n    current_charset = *select_charset;\n    screen_mode = get_screen_mode();\n    bottom++;\n  }\n\n  if(selection_pal < 0)\n  {\n    screen_mode = 0;\n  }\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n  save_screen();\n\n  do\n  {\n    // Draw box and inner box\n    draw_window_box(20, 5, 57, bottom, DI_MAIN, DI_DARK, DI_CORNER, 1, 1);\n    draw_window_box(22, 6, 55, 15, DI_DARK, DI_MAIN, DI_CORNER, 0, 0);\n\n    // Add title\n    if(allow_multichar)\n      write_string(\" Select characters \", 22, 5, DI_TITLE, 0);\n    else\n      write_string(\" Select a character \", 22, 5, DI_TITLE, 0);\n\n    if(select_charset)\n    {\n      // Shift layer offsets to display the current charset\n      set_layer_offset(pal_layer, current_charset * 256);\n      set_layer_offset(chars_layer, current_charset * 256);\n\n      write_string(\"X PgUp \\t\\t\\t\\t PgDn X\", 22, 16, DI_NONACTIVE, 1);\n      draw_char(char_sel_arrows_3, DI_NONACTIVE, 22, 16);\n      draw_char(char_sel_arrows_2, DI_NONACTIVE, 55, 16);\n\n      if(current_charset)\n      {\n        write_string(\"Extended Set ## \", 32, 16, DI_TEXT_GREY, 0);\n        write_number(current_charset, DI_TEXT_GREY, 46, 16, 2, 1, 10);\n      }\n      else\n      {\n        write_string(\" Main Charset  \", 32, 16, DI_TEXT_GREY, 0);\n      }\n\n      // Clear the UI where the char display will appear\n      for(x = 0; x < 32; x++)\n        for(y = 0; y < 8; y++)\n          erase_char(x + 23, y + 7);\n    }\n\n    select_layer(chars_layer);\n\n    // Draw character set\n    for(x = 0; x < 32; x++)\n    {\n      for(y = 0; y < 8; y++)\n      {\n        draw_char_ext(x + (y * 32), DI_NONACTIVE,\n         x + 23, y + 7, 0, 16);\n      }\n    }\n\n    x = (current & 31);\n    y = (current >> 5);\n    cursor_hint(x + 23, y + 7);\n\n    if(get_shift_status(keycode_internal) && allow_multichar)\n    {\n      // Update selection\n      if(!shifted)\n      {\n        highlight_x = x;\n        highlight_y = y;\n        start_x = x;\n        start_y = y;\n        shifted = 1;\n      }\n      else\n      {\n        start_x = highlight_x;\n        start_y = highlight_y;\n\n        if(start_x > x)\n        {\n          start_x = x;\n          width = highlight_x - x + 1;\n        }\n        else\n        {\n          width = x - highlight_x + 1;\n        }\n\n        if(start_y > y)\n        {\n          start_y = y;\n          height = highlight_y - y + 1;\n        }\n        else\n        {\n          height = y - highlight_y + 1;\n        }\n\n        for(i = 0; i < height; i++)\n        {\n          color_line(width, start_x + 23, start_y + i + 7, 0x9F);\n        }\n      }\n    }\n    else\n    {\n      // Highlight active character(s)\n      int char_offset;\n      int skip = 32 - width;\n      int x2, y2;\n\n      if(shifted == 1)\n      {\n        int size = width * height;\n\n        if((size > 18) || (size == 7) || (size == 11) ||\n         (size == 13) || (size == 14) || (size == 16) ||\n         (size == 17))\n        {\n          width = 1;\n          height = 1;\n        }\n\n        x = start_x;\n        y = start_y;\n        current = (y * 32) + x;\n      }\n\n      char_offset = current;\n      shifted = 0;\n\n      for(i = 0; i < height; i++, char_offset += skip)\n      {\n        for(i2 = 0; i2 < width; i2++, char_offset++)\n        {\n          x2 = (char_offset & 31) + 23;\n          y2 = ((char_offset & 255) >> 5) + 7;\n\n          if(screen_mode)\n          {\n            select_layer(chars_layer);\n            erase_char(x2, y2);\n            select_layer(pal_layer);\n            draw_char_ext(char_offset, selection_pal, x2, y2, 0, 0);\n          }\n          else\n\n          if(selection_pal >= 0)\n          {\n            draw_char_ext(char_offset, selection_pal, x2, y2, 0, 0);\n          }\n\n          else\n          {\n            draw_char_ext(char_offset, DI_ACTIVE, x2, y2, 0, 16);\n          }\n        }\n      }\n    }\n\n    select_layer(UI_LAYER);\n\n    // Special display for allow_char_255 == 0\n    if(!allow_char_255)\n    {\n      int color = 0x05;\n\n      if(current == 255)\n        color = 0x0D;\n\n      draw_char('?', color, 31+23, 7+7);\n    }\n\n    // Draw arrows\n    draw_char(char_sel_arrows_0, DI_TEXT, x + 23, 15);\n    draw_char(char_sel_arrows_1, DI_TEXT, x + 23, 6);\n    draw_char(char_sel_arrows_2, DI_TEXT, 22, y + 7);\n    draw_char(char_sel_arrows_3, DI_TEXT, 55, y + 7);\n\n    // Write number of character\n    write_number(current, DI_MAIN, 53, bottom, 3, 0, 10);\n\n    update_screen();\n    update_event_status_delay();\n    key = get_key(keycode_internal_wrt_numlock);\n\n    joystick_key = get_joystick_ui_key();\n    if(joystick_key)\n      key = joystick_key;\n\n    if(get_exit_status())\n      key = IKEY_ESCAPE;\n\n    if(get_mouse_press())\n    {\n      int mouse_x, mouse_y;\n      get_mouse_position(&mouse_x, &mouse_y);\n\n      if((mouse_x >= 23) && (mouse_x <= 54) &&\n       (mouse_y >= 7) && (mouse_y <= 14))\n      {\n        int new_pos = mouse_x - 23 + ((mouse_y - 7) << 5);\n        if(current == new_pos)\n        {\n          pop_context();\n          restore_screen();\n          return current;\n        }\n        current = new_pos;\n      }\n    }\n\n    // Process key\n\n    switch(key)\n    {\n      case IKEY_ESCAPE:\n      {\n        if(current == 0)\n          current = -256;\n\n        else\n          current = -current;\n\n        exit = 1;\n        break;\n      }\n\n      case IKEY_SPACE:\n      case IKEY_RETURN:\n      {\n        if(get_shift_status(keycode_internal) && allow_multichar)\n        {\n          int size = width * height;\n\n          if((size > 18) || (size == 7) || (size == 11) ||\n           (size == 13) || (size == 14) || (size == 16) ||\n           (size == 17))\n          {\n            width = 1;\n            height = 1;\n            break;\n          }\n\n          x = start_x;\n          y = start_y;\n          current = (y * 32) + x;\n        }\n\n        if(allow_multichar)\n        {\n          if(height > 1)\n          {\n            // Clip if the selection wraps around the edge\n            int abort = 0;\n\n            if(width + x > 32)\n            {\n              width = 32 - x;\n              abort = 1;\n            }\n\n            if(height + y > 8)\n            {\n              height = 8 - y;\n              abort = 1;\n            }\n\n            if(abort)\n              break;\n          }\n\n          *width_ptr = width;\n          *height_ptr = height;\n        }\n\n        if(select_charset)\n        {\n          *select_charset = current_charset;\n        }\n\n        exit = 1;\n        break;\n      }\n\n      case IKEY_UP:\n      {\n        current = (current - 32) & 255;\n        break;\n      }\n\n      case IKEY_DOWN:\n      {\n        current = (current + 32) & 255;\n        break;\n      }\n\n      case IKEY_LEFT:\n      {\n        current = (current - 1) & 255;\n        break;\n      }\n\n      case IKEY_RIGHT:\n      {\n        current = (current + 1) & 255;\n        break;\n      }\n\n      case IKEY_HOME:\n      {\n        current = 0;\n        break;\n      }\n\n      case IKEY_END:\n      {\n        current = 288 - width - (height * 32);\n        break;\n      }\n\n      case IKEY_PAGEUP:\n      {\n        if(select_charset)\n        {\n          current_charset--;\n\n          if(current_charset < 0)\n            current_charset = NUM_CHARSETS - 2;\n        }\n\n        break;\n      }\n\n      case IKEY_PAGEDOWN:\n      {\n        if(select_charset)\n        {\n          current_charset++;\n\n          if(current_charset == NUM_CHARSETS - 1)\n            current_charset = 0;\n        }\n\n        break;\n      }\n\n      case IKEY_KP_MINUS:\n      case IKEY_MINUS:\n      {\n        if(allow_multichar && (width != 1 || height != 1))\n        {\n          // Move backward in tile increment.\n          if(!get_shift_status(keycode_internal))\n            current = char_select_next_tile(current, -1, width, height);\n          break;\n        }\n      }\n      /* fall-through */\n\n      case IKEY_KP_PLUS:\n      case IKEY_EQUALS:\n      {\n        if(allow_multichar && (width != 1 || height != 1))\n        {\n          // Move forward in a tile increment.\n          if(!get_shift_status(keycode_internal))\n            current = char_select_next_tile(current, 1, width, height);\n          break;\n        }\n      }\n      /* fall-through */\n\n      default:\n      {\n        if(!current_charset)\n        {\n          // If this is from 32 to 255, jump there.\n          int key_char = get_key(keycode_text_ascii);\n\n          if(allow_multichar && (width != 1 || height != 1))\n          {\n            // Ignore text from these keys in situations where they are used\n            // for tile movement instead...\n            if(key_char == '=' || key_char == '+' || key_char == '-' ||\n             get_key_status(keycode_internal, IKEY_MINUS) ||\n             get_key_status(keycode_internal, IKEY_EQUALS) ||\n             get_key_status(keycode_internal, IKEY_KP_MINUS) ||\n             get_key_status(keycode_internal, IKEY_KP_PLUS))\n              break;\n          }\n\n          if(key_char >= 32 && key_char <= 255)\n            current = key_char;\n        }\n\n        break;\n      }\n    }\n  } while(!exit);\n\n  // Get rid of the extra layers required for extended charsets\n  if(select_charset)\n  {\n    destruct_extra_layers(chars_layer);\n    destruct_extra_layers(pal_layer);\n  }\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n  restore_screen();\n  cursor_off();\n\n  return current;\n}\n\nint char_selection(int current)\n{\n  return char_selection_ext(current, 1, NULL, NULL, NULL, -1);\n}\n\n// Display a simple string input window. Return 0 on enter, -1 if cancelled.\nint input_window(struct world *mzx_world, const char *title,\n char *buffer, int max_len)\n{\n  boolean two_lines = false;\n  int title_len = strlen(title);\n  int x = 16;\n  int y = 11;\n  int w;\n  int h = 3;\n  int ret;\n\n  m_show();\n  save_screen();\n\n  if(title_len > 71)\n    title_len = 71;\n\n  if(max_len > 70)\n    max_len = 70;\n\n  // Title + spacing + input length + extra char for intake cursor + 4 (border)\n  w = title_len + 1 + max_len + 1 + 4;\n\n  // Split label and input box onto separate lines if the box would be too wide.\n  if(w > 74)\n  {\n    w = MAX(title_len, max_len + 1) + 4;\n    two_lines = true;\n    h++;\n  }\n\n  // Center wide boxes.\n  if(x > (81 - w) / 2)\n    x = (81 - w) / 2;\n\n  draw_window_box(x, y, (x + w - 1), (y + h - 1),\n   DI_INPUT_BOX, DI_INPUT_BOX_DARK, DI_INPUT_BOX_CORNER, true, true);\n\n  x += 2;\n  y += 1;\n  write_string(title, x, y, DI_INPUT_BOX_LABEL, false);\n\n  if(two_lines)\n    y++;\n  else\n    x += title_len + 1;\n\n  ret = intake(mzx_world, buffer, max_len, max_len, x, y, 15,\n   INTK_EXIT_ENTER_ESC, true, NULL);\n\n  restore_screen();\n\n  if(ret == IKEY_ESCAPE || get_exit_status())\n    return -1;\n\n  return 0;\n}\n\n__editor_maybe_static void construct_element(struct element *e, int x, int y,\n int width, int height,\n void (* draw_function)(struct world *mzx_world, struct dialog *di,\n  struct element *e, int color, int active),\n int (* key_function)(struct world *mzx_world, struct dialog *di,\n  struct element *e, int key),\n int (* click_function)(struct world *mzx_world, struct dialog *di,\n  struct element *e, int mouse_button, int mouse_x, int mouse_y,\n  int new_active),\n int (* drag_function)(struct world *mzx_world, struct dialog *di,\n  struct element *e, int mouse_button, int mouse_x, int mouse_y),\n int (* idle_function)(struct world *mzx_world, struct dialog *di,\n  struct element *e))\n{\n  e->x = x;\n  e->y = y;\n  e->width = width;\n  e->height = height;\n  e->draw_function = draw_function;\n  e->key_function = key_function;\n  e->click_function = click_function;\n  e->drag_function = drag_function;\n  e->idle_function = idle_function;\n}\n\nstatic void fill_vid_usage(struct dialog *di, struct element *e,\n signed char *vid_usage, int vid_fill)\n{\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n  signed char *vid_ptr = vid_usage + (y * 80) + x;\n  int i;\n\n  for(i = 0; i < e->height; i++)\n  {\n    memset(vid_ptr, vid_fill, e->width);\n    vid_ptr += 80;\n  }\n}\n\n// Prototype- Internal function that displays a dialog box element.\nvoid display_element(struct world *mzx_world, int type, int x, int y,\n char *str, int p1, int p2, void *value, int active, int curr_check,\n int set_vid_usage, int space_label);\n\nstatic void unhighlight_element(struct world *mzx_world, struct dialog *di,\n int current_element_num)\n{\n  (di->elements[current_element_num])->draw_function(mzx_world, di,\n   di->elements[current_element_num], DI_NONACTIVE, 0);\n}\n\nstatic void highlight_element(struct world *mzx_world, struct dialog *di,\n int current_element_num)\n{\n  (di->elements[current_element_num])->draw_function(mzx_world, di,\n   di->elements[current_element_num], DI_ACTIVE, 1);\n}\n\nstatic int find_first_element(struct world *mzx_world, struct dialog *di,\n int current_element_num)\n{\n  int i;\n\n  unhighlight_element(mzx_world, di, current_element_num);\n\n  for(i = 0; i < di->num_elements; i++)\n  {\n    if((di->elements[i])->key_function)\n      return i;\n  }\n\n  highlight_element(mzx_world, di, i);\n\n  return -1;\n}\n\nstatic int find_last_element(struct world *mzx_world, struct dialog *di,\n int current_element_num)\n{\n  int i;\n\n  unhighlight_element(mzx_world, di, current_element_num);\n\n  for(i = di->num_elements - 1; i >= 0; i--)\n  {\n    if((di->elements[i])->key_function)\n      return i;\n  }\n\n  highlight_element(mzx_world, di, i);\n\n  return -1;\n}\n\nstatic int change_current_element(struct world *mzx_world, struct dialog *di,\n int current_element_num, int displacement)\n{\n  int increment = 1;\n  int i = 0;\n\n  unhighlight_element(mzx_world, di, current_element_num);\n\n  if(displacement < 0)\n    increment = -1;\n\n  while(i != displacement)\n  {\n    current_element_num += increment;\n\n    if(current_element_num < 0)\n      current_element_num = di->num_elements - 1;\n\n    if(current_element_num == di->num_elements)\n      current_element_num = 0;\n\n    if((di->elements[current_element_num])->key_function)\n      i += increment;\n  }\n\n  highlight_element(mzx_world, di, current_element_num);\n\n  return current_element_num;\n}\n\nint run_dialog(struct world *mzx_world, struct dialog *di)\n{\n  int exit;\n  int mouse_press;\n  int mouse_drag_state;\n  int x = di->x;\n  int y = di->y;\n  int title_x_offset = x + (di->width / 2) - ((int)strlen(di->title) / 2);\n  struct element *current_element = di->elements[di->current_element];\n\n  int current_element_num = di->current_element;\n  signed char vid_usage[2000];\n  int current_key, new_key;\n  int i;\n\n  if(get_context(NULL) == CTX_MAIN)\n    set_context(CTX_DIALOG_BOX);\n  else\n    set_context(get_context(NULL));\n\n  m_show();\n\n  save_screen();\n\n  di->done = 0;\n\n  // Draw main box and title\n\n  draw_window_box(x, y, x + di->width - 1,\n   y + di->height - 1, DI_MAIN, DI_DARK, DI_CORNER, 1, 1);\n\n  write_string(di->title, title_x_offset, y, DI_TITLE, 0);\n  draw_char(' ', DI_TITLE, title_x_offset - 1, y);\n  draw_char(' ', DI_TITLE, title_x_offset + (unsigned int)strlen(di->title), y);\n\n  memset(vid_usage, -1, 2000);\n\n  // Draw elements and set vid usage\n  for(i = 0; i < di->num_elements; i++)\n  {\n    unhighlight_element(mzx_world, di, i);\n    fill_vid_usage(di, di->elements[i], vid_usage, i);\n  }\n\n  do\n  {\n    cursor_off();\n    highlight_element(mzx_world, di, current_element_num);\n    update_screen();\n\n    current_element = di->elements[current_element_num];\n    update_event_status_delay();\n    current_key = get_key(keycode_internal_wrt_numlock);\n\n    if(!current_key && has_unicode_input())\n      current_key = IKEY_UNICODE;\n\n    new_key = get_joystick_ui_key();\n    if(new_key)\n      current_key = new_key;\n\n    new_key = 0;\n\n    if(current_element->idle_function)\n    {\n      new_key =\n       current_element->idle_function(mzx_world, di,\n       current_element);\n\n      if(new_key > 0)\n        current_key = new_key;\n    }\n\n    exit = get_exit_status();\n\n    mouse_press = get_mouse_press_ext();\n    mouse_drag_state = get_mouse_drag();\n\n    if(mouse_drag_state &&\n     (mouse_press <= MOUSE_BUTTON_RIGHT) &&\n     (current_element->drag_function))\n    {\n      int mouse_x, mouse_y;\n      get_mouse_position(&mouse_x, &mouse_y);\n\n      new_key = current_element->drag_function(mzx_world,\n       di, current_element, mouse_press,\n       mouse_x - di->x - current_element->x,\n       mouse_y - di->y - current_element->y);\n\n      if(new_key > 0)\n        current_key = new_key;\n    }\n    else\n\n    if((mouse_press && (mouse_press <= MOUSE_BUTTON_RIGHT)\n     && !mouse_drag_state)\n     || (new_key == -1))\n    {\n      do\n      {\n        int mouse_x, mouse_y;\n        int element_under;\n        get_mouse_position(&mouse_x, &mouse_y);\n\n        element_under = vid_usage[(mouse_y * 80) + mouse_x];\n        new_key = 0;\n\n        if((element_under != -1) &&\n         (element_under != current_element_num) &&\n         ((di->elements[element_under])->click_function))\n        {\n          unhighlight_element(mzx_world, di, current_element_num);\n          current_element_num = element_under;\n          current_element = di->elements[element_under];\n          highlight_element(mzx_world, di, element_under);\n\n          new_key = current_element->click_function(mzx_world, di,\n           current_element, mouse_press,\n           mouse_x - di->x - current_element->x,\n           mouse_y - di->y - current_element->y, 1);\n\n          if(new_key > 0)\n            current_key = new_key;\n        }\n        else\n\n        if((element_under != -1) &&\n         ((di->elements[element_under])->click_function))\n        {\n          current_element_num = element_under;\n          current_element = di->elements[element_under];\n\n          if(current_element->click_function)\n          {\n            new_key = current_element->click_function(mzx_world, di,\n             current_element, mouse_press,\n             mouse_x - di->x - current_element->x,\n             mouse_y - di->y - current_element->y, 0);\n\n            if(new_key > 0)\n              current_key = new_key;\n          }\n        }\n      } while(new_key == -1);\n\n      if(get_exit_status())\n        exit = 1;\n    }\n    else\n\n    if(mouse_press == MOUSE_BUTTON_WHEELUP)\n    {\n      current_key = IKEY_UP;\n    }\n    else\n\n    if(mouse_press == MOUSE_BUTTON_WHEELDOWN)\n    {\n      current_key = IKEY_DOWN;\n    }\n\n    if(current_element->key_function && current_key)\n    {\n      current_key =\n       current_element->key_function(mzx_world, di,\n       current_element, current_key);\n   }\n\n    di->current_element = current_element_num;\n\n    if(di->idle_function)\n    {\n      current_key =\n       di->idle_function(mzx_world, di, current_key);\n\n      current_element_num = di->current_element;\n    }\n\n    switch(current_key)\n    {\n      case IKEY_TAB: // Tab\n      {\n        if(get_shift_status(keycode_internal))\n        {\n          current_element_num = change_current_element(mzx_world,\n           di, current_element_num, -1);\n        }\n        else\n        {\n          current_element_num = change_current_element(mzx_world,\n           di, current_element_num, 1);\n        }\n        break;\n      }\n\n      case IKEY_SPACE:\n      case IKEY_RETURN:\n      case IKEY_PAGEDOWN:\n      case IKEY_RIGHT:\n      case IKEY_DOWN:\n      {\n        current_element_num = change_current_element(mzx_world,\n         di, current_element_num, 1);\n        break;\n      }\n\n      case IKEY_PAGEUP:\n      case IKEY_LEFT:\n      case IKEY_UP:\n      {\n        current_element_num = change_current_element(mzx_world,\n         di, current_element_num, -1);\n        break;\n      }\n\n      case IKEY_HOME:\n      {\n        current_element_num = find_first_element(mzx_world, di,\n         current_element_num);\n        break;\n      }\n\n      case IKEY_END:\n      {\n        current_element_num = find_last_element(mzx_world, di,\n         current_element_num);\n        break;\n      }\n\n      case IKEY_ESCAPE: // ESC\n      {\n        // Only work on press.  Ignore autorepeat.\n        //if(get_key_status(keycode_internal, IKEY_ESCAPE) == 1)\n          exit = 1;\n\n        break;\n      }\n\n#ifdef CONFIG_HELPSYS\n      case IKEY_F1: // F1\n      {\n        if(allow_help_system(mzx_world, true))\n        {\n          // FIXME context\n          help_system(NULL, mzx_world);\n        }\n        break;\n      }\n#endif\n\n      default:\n      {\n        break;\n      }\n    }\n\n    // ESC or exit event\n    if(exit)\n    {\n      force_release_all_keys();\n      pop_context();\n      cursor_off();\n      return -1;\n    }\n\n  } while(di->done != 1);\n\n  pop_context();\n  cursor_off();\n\n  return di->return_value;\n}\n\nstatic int find_entry(const char **choices, char *name, int total_num)\n{\n  int current_entry;\n  int cmpval = 0;\n  int offset = 0;\n  size_t name_length = strlen(name);\n\n  // Hack so seeking works on the string counter debugger without\n  // having to press '$'. Probably shouldn't be here, but oh well.\n  // Most lists are alphabetically sorted or have things like (new)\n  // at the end, so only enable if the first and last elements start\n  // with '$'.\n\n  if(total_num && choices[0][0] == '$' && choices[total_num - 1][0] == '$')\n  {\n    offset = 1;\n  }\n\n  for(current_entry = 0; current_entry < total_num; current_entry++)\n  {\n    // Hack to avoid seeking to drive letters under Windows. This\n    // is a terrible place for it, and I'd like to tear it out, but\n    // it would likely upset a lot of the existing code.\n    // (note: Exo added this hack, not me!)\n#ifdef __WIN32__\n    if(choices[current_entry][1] == ':')\n      continue;\n    else\n#endif\n\n    cmpval = strncasecmp(name, choices[current_entry] + offset, name_length);\n\n    if(cmpval == 0)\n    {\n      return current_entry;\n    }\n  }\n\n  return -1;\n}\n\n// \"Member\" functions for GUI elements\n\nstatic void draw_label(struct world *mzx_world, struct dialog *di,\n struct element *e, int color, int active)\n{\n  struct label_element *src = (struct label_element *)e;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n\n  if(src->respect_colors)\n    color_string_ext(src->text, x, y, DI_TEXT, true, PRO_CH, 16);\n  else\n    write_string_ext(src->text, x, y, DI_TEXT, true, PRO_CH, 16);\n}\n\nstatic void draw_input_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int color, int active)\n{\n  struct input_box *src = (struct input_box *)e;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n  int question_length = (int)strlen(src->question) + di->pad_space;\n\n  write_string(src->question, x, y, color, 0);\n  fill_line(src->max_length + 1, x + question_length, y,\n   32, DI_INPUT);\n  write_string(src->result, x + question_length, y,\n   DI_INPUT, 0);\n}\n\nstatic void draw_radio_button(struct world *mzx_world, struct dialog *di,\n struct element *e, int color, int active)\n{\n  struct radio_button *src = (struct radio_button *)e;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n  int i;\n\n  for(i = 0; i < src->num_choices; i++)\n  {\n    if(i != *(src->result))\n    {\n      write_string(radio_off, x, y + i, DI_NONACTIVE, 0);\n      write_string(src->choices[i], x + 4, y + i,\n       DI_NONACTIVE, 0);\n    }\n    else\n    {\n      write_string(radio_on, x, y + *(src->result),\n       DI_NONACTIVE, 0);\n      write_string(src->choices[i], x + 4, y + i,\n       DI_NONACTIVE, 0);\n    }\n  }\n\n  if(active)\n  {\n    color_line(src->max_length + 4, x, y + *(src->result),\n     DI_ACTIVE);\n    cursor_hint(x + 1, y + *(src->result));\n  }\n}\n\nstatic void draw_button(struct world *mzx_world, struct dialog *di,\n struct element *e, int color, int active)\n{\n  struct button *src = (struct button *)e;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n\n  if(active)\n    color = DI_ACTIVEBUTTON;\n  else\n    color = DI_BUTTON;\n\n  write_string(src->label, x + 1, y, color, 0);\n  draw_char(' ', color, x, y);\n  draw_char(' ', color, x + (unsigned int)strlen(src->label) + 1, y);\n\n  if(active)\n    cursor_hint(x + 1, y);\n}\n\nstatic void draw_number_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int color, int active)\n{\n  struct number_box *src = (struct number_box *)e;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n  int increment = 1;\n  int i;\n\n  if(src->type == NUMBER_BOX_MULT_FIVE)\n    increment = 5;\n\n  write_string(src->question, x, y, color, 0);\n  if(active)\n    cursor_hint(x, y);\n\n  x += (int)strlen(src->question) + di->pad_space;\n\n  if(src->type == NUMBER_LINE)\n  {\n    // Draw a number line\n    for(i = 1; i <= src->upper_limit; i++)\n    {\n      if(i == *(src->result))\n        draw_char('0' + i, DI_ACTIVE, x + i - 1, y);\n      else\n        draw_char('0' + i, DI_NONACTIVE, x + i - 1, y);\n    }\n  }\n  else\n\n  if(src->type == NUMBER_SLIDER)\n  {\n    int upper_x = x + (src->upper_limit - src->lower_limit) + 2;\n    int slider_x = x + (*(src->result) - src->lower_limit);\n    int slider_col = active ? DI_ARROWBUTTON : DI_NUMERIC;\n\n    for(i = x + 1; i < upper_x; i++)\n      draw_char('\\xC4', DI_DARK, i, y);\n\n    write_number(src->lower_limit, DI_NONACTIVE, x, y, 0, false, 10);\n    write_number(src->upper_limit, DI_NONACTIVE, upper_x, y, 0, false, 10);\n\n    draw_char(' ', slider_col, slider_x, y);\n    draw_char(' ', slider_col, slider_x + 2 + (*(src->result) >= 10), y);\n    write_number(*(src->result), slider_col, slider_x + 1, y, 0, false, 10);\n  }\n\n  else\n  {\n    // Draw a number\n    char num_buffer[32];\n    if(!src->is_null)\n      sprintf(num_buffer, \"%d\", *(src->result) * increment);\n    else\n      sprintf(num_buffer, \" \");\n    fill_line(7, x, y, 32, DI_NUMERIC);\n    write_string(num_buffer, x + 6 - (unsigned int)strlen(num_buffer), y,\n     DI_NUMERIC, 0);\n    // Buttons\n    write_string(num_buttons, x + 7, y, DI_ARROWBUTTON, 0);\n  }\n}\n\nstatic int get_slot_state(int slot, boolean *highlighted_slots,\n boolean *disabled_slots)\n{\n  if(disabled_slots != NULL && disabled_slots[slot])\n    return SLOT_DISABLED;\n\n  if(highlighted_slots != NULL && highlighted_slots[slot])\n    return SLOT_HIGHLIGHT;\n\n  return SLOT_NORMAL;\n}\n\nstatic void draw_slot_selector(struct world *mzx_world, struct dialog *di,\n struct element *e, int color, int active)\n{\n  struct slot_selector *src = (struct slot_selector *)e;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n  int slot_x;\n  int slot_y = y + SLOTSEL_BAR_Y;\n  int i;\n  int slot_color = DI_SLOTSEL_NORMAL;\n\n  write_string(src->title, x, y, color, 0);\n\n  for(i = 0; i < src->num_slots; i++)\n  {\n    slot_x = x + (i * 4);\n    switch(get_slot_state(i, src->highlighted_slots, src->disabled_slots))\n    {\n      case SLOT_DISABLED:\n      {\n        slot_color = DI_SLOTSEL_DISABLE;\n        break;\n      }\n\n      case SLOT_HIGHLIGHT:\n      {\n        slot_color = i == src->selected_slot\n         ? DI_ACTIVEBUTTON\n         : DI_SLOTSEL_HIGHLIGHT;\n        break;\n      }\n\n      case SLOT_NORMAL:\n      {\n        slot_color = i == src->selected_slot\n         ? DI_ARROWBUTTON\n         : DI_SLOTSEL_NORMAL;\n        break;\n      }\n    }\n\n    fill_line(4, slot_x, slot_y, 0, slot_color);\n    write_number(i + 1, slot_color, slot_x + 2, slot_y, 1, true, 10);\n\n    if(active && i == src->selected_slot)\n      cursor_hint(slot_x + 1, slot_y);\n  }\n}\n\nstatic void draw_file_selector(struct world *mzx_world, struct dialog *di,\n struct element *e, int color, int active)\n{\n  struct file_selector *src = (struct file_selector *)e;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n  int width = e->width;\n  char *path = src->result;\n  int len = strlen(path);\n\n  write_string(src->title, x, y, color, 0);\n  fill_line(width, x, y + 1, 32, DI_LIST);\n  if(active)\n    cursor_hint(x, y);\n\n  if(path[0])\n  {\n    if(len > width-5)\n    {\n      path = path + len - (width-5);\n    }\n    write_string(path, x + 1, y + 1, DI_LIST, 0);\n  }\n  else\n  {\n    write_string(src->none_mesg, x + 1, y + 1, DI_LIST, 0);\n  }\n\n  // Draw button\n  write_string(list_button, x + width - 3, y + 1, DI_ARROWBUTTON, 0);\n}\n\n#define MAX_NAME_BUFFER 512\n\nstatic void draw_list_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int color, int active)\n{\n  struct list_box *src = (struct list_box *)e;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n  int choice_length = src->choice_length;\n  int num_choices = src->num_choices;\n  int num_choices_visible = src->num_choices_visible;\n  int current_choice = *(src->result);\n  int scroll_offset = src->scroll_offset;\n  const char **choices = src->choices;\n  int i, num_draw;\n  int current_in_window = current_choice - scroll_offset;\n  char name_buffer[MAX_NAME_BUFFER];\n  int draw_width = MIN(choice_length, MAX_NAME_BUFFER);\n\n  num_draw = num_choices_visible;\n\n  if(num_choices_visible >= num_choices)\n    num_draw = num_choices;\n\n  current_in_window = current_choice - scroll_offset;\n\n  // Draw choices\n  for(i = 0; i < num_draw; i++)\n  {\n    fill_line(choice_length, x, y + i, 32, DI_LIST);\n    snprintf(name_buffer, draw_width, \"%s\", choices[scroll_offset + i]);\n    name_buffer[draw_width - 1] = '\\0';\n\n    if(src->respect_color_codes)\n      color_string(name_buffer, x, y + i, DI_LIST);\n    else\n      write_string(name_buffer, x, y + i, DI_LIST, false);\n  }\n\n  for(; i < num_choices_visible; i++)\n  {\n    fill_line(choice_length, x, y + i, 32, DI_LIST);\n  }\n\n  if(num_choices)\n  {\n    if(active)\n      color = DI_ACTIVELIST;\n    else\n      color = DI_SEMIACTIVELIST;\n\n    fill_line(choice_length, x, y + current_in_window,\n     32, color);\n    if(active)\n      cursor_hint(x, y + current_in_window);\n\n    snprintf(name_buffer, draw_width, \"%s\", choices[current_choice]);\n    name_buffer[draw_width - 1] = '\\0';\n\n    if(src->respect_color_codes)\n      color_string(name_buffer, x, y + current_in_window, color);\n    else\n      write_string(name_buffer, x, y + current_in_window, color, false);\n  }\n\n  if(num_choices > num_choices_visible)\n  {\n    int side_length = num_choices_visible - 2;\n    int progress_bar_size = (side_length * side_length +\n     (side_length / 2)) / num_choices;\n    int progress_bar_offset = ((scroll_offset *\n     (side_length - progress_bar_size)) +\n     ((num_choices - num_choices_visible) / 2)) /\n     (num_choices - num_choices_visible) + y + 1;\n\n    draw_char(pc_top_arrow, DI_PCARROW, x + choice_length, y);\n    draw_char(pc_bottom_arrow, DI_PCARROW, x + choice_length,\n     y + side_length + 1);\n\n    for(i = 0; i < side_length; i++)\n    {\n      draw_char(pc_filler, DI_PCFILLER, x + choice_length, y + i + 1);\n    }\n\n    for(i = 0; i < progress_bar_size; i++,\n     progress_bar_offset++)\n    {\n      draw_char(pc_meter, DI_PCDOT, x + choice_length,\n       progress_bar_offset);\n    }\n  }\n}\n\nstatic int key_input_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int key)\n{\n  struct input_box *src = (struct input_box *)e;\n\n  if(get_alt_status(keycode_internal) && (key == IKEY_t) &&\n   di->sfx_test_for_input)\n  {\n    // Play a sfx\n    sfx_clear_queue();\n    play_string(src->result, 0);\n  }\n\n  return key;\n}\n\nstatic int key_radio_button(struct world *mzx_world, struct dialog *di,\n struct element *e, int key)\n{\n  struct radio_button *src = (struct radio_button *)e;\n\n  switch(key)\n  {\n    case IKEY_PAGEUP:\n    {\n      *(src->result) = 0;\n      break;\n    }\n\n    case IKEY_LEFT:\n    case IKEY_UP:\n    {\n      if(*(src->result))\n        (*(src->result))--;\n\n      break;\n    }\n\n    case IKEY_PAGEDOWN:\n    {\n      *(src->result) = src->num_choices - 1;\n      break;\n    }\n\n    case IKEY_RIGHT:\n    case IKEY_DOWN:\n    {\n      if(*(src->result) < (src->num_choices - 1))\n        (*(src->result))++;\n\n      break;\n    }\n\n    default:\n    {\n      return key;\n    }\n  }\n\n  return 0;\n}\n\nstatic int key_button(struct world *mzx_world, struct dialog *di,\n struct element *e, int key)\n{\n  struct button *src = (struct button *)e;\n\n  switch(key)\n  {\n    case IKEY_SPACE:\n    case IKEY_RETURN:\n    {\n      // 0=not pressed (i.e. this is a fake press from clicking)\n      // 1=pressed\n      // 2=repeat. We want to ignore that.\n      if (get_key_status(keycode_internal_wrt_numlock, key) > 1)\n        return 0;\n\n      // Flag that the dialog is done processing\n      di->done = 1;\n      di->return_value = src->return_value;\n      break;\n    }\n\n    default:\n    {\n      return key;\n    }\n  }\n\n  return 0;\n}\n\nstatic int key_number_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int key)\n{\n  struct number_box *src = (struct number_box *)e;\n  int increment_value = 0;\n  int current_value = *(src->result);\n\n  switch(key)\n  {\n    case IKEY_HOME:\n    {\n      *(src->result) = src->lower_limit;\n      break;\n    }\n\n    case IKEY_END:\n    {\n      *(src->result) = src->upper_limit;\n      break;\n    }\n\n    case IKEY_RIGHT:\n    case IKEY_UP:\n    {\n      if(get_alt_status(keycode_internal) ||\n       get_ctrl_status(keycode_internal))\n      {\n        increment_value = 10;\n      }\n      else\n      {\n        increment_value = 1;\n      }\n\n      break;\n    }\n\n    case IKEY_PAGEUP:\n    {\n      increment_value = 100;\n      break;\n    }\n\n    case IKEY_LEFT:\n    case IKEY_DOWN:\n    {\n      if(get_alt_status(keycode_internal) ||\n       get_ctrl_status(keycode_internal))\n      {\n        increment_value = -10;\n      }\n      else\n      {\n        increment_value = -1;\n      }\n\n      break;\n    }\n\n    case IKEY_PAGEDOWN:\n    {\n      increment_value = -100;\n      break;\n    }\n\n    case IKEY_BACKSPACE:\n    {\n      int result = current_value / 10;\n      if(result == 0 || result < src->lower_limit)\n      {\n        result = src->lower_limit;\n        if(src->upper_limit > 9)\n          src->is_null = true;\n      }\n\n      *(src->result) = result;\n\n      // Fall through to allow for an empty box.\n    }\n\n    /* fallthrough */\n\n    default:\n    {\n      int key_char = get_key(keycode_text_ascii);\n\n      if((key_char >= '0') && (key_char <= '9'))\n      {\n        if(current_value == src->upper_limit || src->is_null)\n        {\n          current_value = (key_char - '0');\n        }\n        else\n        {\n          current_value = (current_value * 10) + (key_char - '0');\n        }\n\n        if(src->type == NUMBER_BOX_MULT_FIVE)\n          current_value -= current_value % 5;\n\n        if(current_value < src->lower_limit)\n          current_value = src->lower_limit;\n\n        if(current_value > src->upper_limit)\n          current_value = src->upper_limit;\n\n        *(src->result) = current_value;\n        break;\n      }\n\n      if(key != IKEY_BACKSPACE &&\n       !get_shift_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) &&\n       !get_alt_status(keycode_internal))\n        src->is_null = false;\n\n      return key;\n    }\n  }\n\n  if(increment_value > 0 && src->is_null)\n    increment_value -= src->lower_limit;\n  src->is_null = false;\n\n  if(increment_value)\n  {\n    current_value += increment_value;\n\n    if(current_value < src->lower_limit)\n      current_value = src->lower_limit;\n\n    if(current_value > src->upper_limit)\n      current_value = src->upper_limit;\n\n    *(src->result) = current_value;\n  }\n  return 0;\n}\n\nstatic int key_slot_selector(struct world *mzx_world, struct dialog *di,\n struct element *e, int key)\n{\n  struct slot_selector *src = (struct slot_selector *)e;\n  int this_slot = src->selected_slot;\n  int target_slot;\n  int i;\n\n  switch(key)\n  {\n    case IKEY_LEFT:\n    {\n      for(i = 0; i < SLOTSEL_NUM_SLOTS; i++)\n      {\n        if(--src->selected_slot < 0)\n          src->selected_slot = src->num_slots - 1;\n\n        if(src->selected_slot == this_slot)\n          break;\n\n        if(get_slot_state(src->selected_slot, src->highlighted_slots,\n         src->disabled_slots) != SLOT_DISABLED)\n          break;\n      }\n      break;\n    }\n\n    case IKEY_RIGHT:\n    {\n      for(i = 0; i < SLOTSEL_NUM_SLOTS; i++)\n      {\n        if(++src->selected_slot >= src->num_slots)\n          src->selected_slot = 0;\n\n        if(src->selected_slot == this_slot)\n          break;\n\n        if(get_slot_state(src->selected_slot, src->highlighted_slots,\n         src->disabled_slots) != SLOT_DISABLED)\n          break;\n      }\n      break;\n    }\n\n    case IKEY_1:\n    case IKEY_2:\n    case IKEY_3:\n    case IKEY_4:\n    case IKEY_5:\n    case IKEY_6:\n    case IKEY_7:\n    case IKEY_8:\n    case IKEY_9:\n    {\n      target_slot = key - IKEY_1;\n      if(src->save || get_slot_state(target_slot, src->highlighted_slots,\n       src->disabled_slots) != SLOT_DISABLED)\n        src->selected_slot = target_slot;\n      break;\n    }\n\n    case IKEY_0:\n    {\n      // Special case for the 10th element.\n      if(src->save || get_slot_state(9, src->highlighted_slots,\n       src->disabled_slots) != SLOT_DISABLED)\n        src->selected_slot = 9;\n      break;\n    }\n\n    default:\n    {\n      return key;\n    }\n  }\n\n  return 0;\n}\n\nstatic int key_file_selector(struct world *mzx_world, struct dialog *di,\n struct element *e, int key)\n{\n  struct file_selector *src = (struct file_selector *)e;\n\n  switch(key)\n  {\n    case IKEY_SPACE:\n    case IKEY_RETURN:\n    {\n      char new_path[MAX_PATH] = { 0 };\n      strcpy(new_path, src->base_path);\n\n      if(!choose_file_ch(mzx_world, src->file_manager_exts, new_path,\n       src->file_manager_title, ALLOW_SUBDIRS))\n      {\n        strcpy(src->result, new_path);\n        di->done = 1;\n        di->return_value = src->return_value;\n      }\n\n      break;\n    }\n\n    case IKEY_DELETE:\n    case IKEY_BACKSPACE:\n    {\n      if(src->allow_unset)\n        src->result[0] = 0;\n      break;\n    }\n\n    default:\n    {\n      return key;\n    }\n  }\n\n  return 0;\n}\n\nstatic int key_list_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int key)\n{\n  struct list_box *src = (struct list_box *)e;\n  int current_choice = *(src->result);\n  int num_choices = src->num_choices;\n  int num_choices_visible = src->num_choices_visible;\n\n  switch(key)\n  {\n    case IKEY_UP:\n    {\n      if(current_choice)\n      {\n        if(src->scroll_offset == current_choice)\n          (src->scroll_offset)--;\n        current_choice--;\n      }\n      break;\n    }\n\n    case IKEY_DOWN:\n    {\n      if(current_choice < (num_choices - 1))\n      {\n        if(current_choice ==\n         (src->scroll_offset + num_choices_visible - 1))\n        {\n          (src->scroll_offset)++;\n        }\n        current_choice++;\n      }\n      break;\n    }\n\n    case IKEY_LEFT:\n    {\n      return IKEY_LEFT;\n    }\n\n    case IKEY_RIGHT:\n    {\n      return IKEY_RIGHT;\n    }\n\n    case IKEY_HOME:\n    {\n      current_choice = 0;\n      src->scroll_offset = 0;\n      break;\n    }\n\n    case IKEY_END:\n    {\n      current_choice = num_choices - 1;\n      src->scroll_offset =\n       num_choices - num_choices_visible;\n      if(src->scroll_offset < 0)\n        src->scroll_offset = 0;\n      break;\n    }\n\n    case IKEY_PAGEUP:\n    {\n      current_choice -= num_choices_visible;\n      src->scroll_offset -= num_choices_visible;\n\n      if(src->scroll_offset < 0)\n      {\n        src->scroll_offset = 0;\n      }\n\n      if(current_choice < 0)\n      {\n        current_choice = 0;\n      }\n      break;\n    }\n\n    case IKEY_PAGEDOWN:\n    {\n      current_choice += num_choices_visible;\n      src->scroll_offset += num_choices_visible;\n\n      if(src->scroll_offset + num_choices_visible >\n       num_choices)\n      {\n        src->scroll_offset =\n         num_choices - num_choices_visible;\n        if(src->scroll_offset < 0)\n          src->scroll_offset = 0;\n      }\n\n      if(current_choice >= num_choices)\n      {\n        current_choice = num_choices - 1;\n      }\n\n      break;\n    }\n\n    case IKEY_SPACE:\n    case IKEY_RETURN:\n    {\n      di->return_value = src->return_value;\n      di->done = 1;\n      break;\n    }\n\n    default:\n    {\n      int key_char = get_key(keycode_text_ascii);\n      if(!get_alt_status(keycode_internal) &&\n       !get_ctrl_status(keycode_internal) && (key_char >= 32))\n      {\n        char *key_buffer = src->key_buffer;\n        int key_position = src->key_position;\n        int last_keypress_time = src->last_keypress_time;\n        int ticks = get_ticks();\n        int new_choice;\n\n        if(((ticks - last_keypress_time) >= TIME_SUSPEND) ||\n         (key_position == 63))\n        {\n          // Go back to zero\n          key_position = 0;\n        }\n\n        src->last_keypress_time = ticks;\n        key_buffer[key_position] = key_char;\n        key_position++;\n        key_buffer[key_position] = 0;\n        src->key_position = key_position;\n\n        new_choice =\n         find_entry(src->choices, key_buffer, num_choices);\n\n        if(new_choice != -1)\n          current_choice = new_choice;\n\n        if((current_choice < src->scroll_offset) ||\n         (current_choice >= (src->scroll_offset +\n         num_choices_visible)))\n        {\n          src->scroll_offset =\n           current_choice - num_choices_visible / 2;\n\n          if(src->scroll_offset < 0)\n            src->scroll_offset = 0;\n\n          if(src->scroll_offset + num_choices_visible >\n           num_choices)\n          {\n            src->scroll_offset =\n             num_choices - num_choices_visible;\n            if(src->scroll_offset < 0)\n              src->scroll_offset = 0;\n          }\n        }\n      }\n      else\n      {\n        return key;\n      }\n      break;\n    }\n  }\n\n  *(src->result) = current_choice;\n\n  if(src->result_offset)\n    *(src->result_offset) = src->scroll_offset;\n\n  return 0;\n}\n\nstatic int click_input_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y,\n int new_active)\n{\n  struct input_box *src = (struct input_box *)e;\n  size_t question_len = strlen(src->question);\n  int start_x = mouse_x - (int)question_len;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n\n  if(start_x >= 0)\n  {\n    return intake(mzx_world, src->result, src->max_length, src->max_length,\n     x + (int)question_len + di->pad_space, y, DI_INPUT, INTK_EXIT_ANY, true,\n     &start_x);\n  }\n  else\n  {\n    return 0;\n  }\n}\n\nstatic int click_radio_button(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y,\n int new_active)\n{\n  struct radio_button *src = (struct radio_button *)e;\n  *(src->result) = mouse_y;\n\n  return 0;\n}\n\nstatic int click_button(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y,\n int new_active)\n{\n  if(!new_active)\n  {\n    return IKEY_RETURN;\n  }\n\n  return 0;\n}\n\nstatic int click_number_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y,\n int new_active)\n{\n  struct number_box *src = (struct number_box *)e;\n  mouse_x -= (int)strlen(src->question) + 7;\n\n  if(src->type == NUMBER_LINE)\n  {\n    // Select number IF on the number line itself\n    mouse_x += 7;\n    if((mouse_x < src->upper_limit) && (mouse_x >= 0))\n      *(src->result) = mouse_x + 1;\n  }\n  else\n\n  if(src->type == NUMBER_SLIDER)\n  {\n    mouse_x += 7;\n    if(mouse_x >= 0)\n      *(src->result) = CLAMP(mouse_x - 1 + src->lower_limit,\n       src->lower_limit, src->upper_limit);\n  }\n  else\n\n  if((mouse_x >= 0) && (mouse_x <= 2))\n  {\n    return IKEY_UP;\n  }\n  else\n\n  if((mouse_x >= 3) && (mouse_x <= 5))\n  {\n    return IKEY_DOWN;\n  }\n\n  return 0;\n}\n\nstatic int drag_number_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y)\n{\n  struct number_box *src = (struct number_box *)e;\n  int mouse_press = get_mouse_press();\n\n  mouse_x -= (int)strlen(src->question) + 7;\n\n  if(src->type == NUMBER_LINE)\n  {\n    // Select number IF on the number line itself\n    mouse_x += 7;\n    if((mouse_x < src->upper_limit) && (mouse_x >= 0))\n      *(src->result) = mouse_x + 1;\n  }\n  else\n\n  if(src->type == NUMBER_SLIDER)\n  {\n    mouse_x += 7;\n    if(mouse_x >= 0)\n      *(src->result) = CLAMP(mouse_x - 1 + src->lower_limit,\n       src->lower_limit, src->upper_limit);\n  }\n  else\n\n  // get_mouse_press() has repeating, which we want here.\n  if((mouse_x >= 0) && (mouse_x <= 2) && mouse_press)\n  {\n    return IKEY_UP;\n  }\n  else\n\n  if((mouse_x >= 3) && (mouse_x <= 5) && mouse_press)\n  {\n    return IKEY_DOWN;\n  }\n\n  return 0;\n}\n\nstatic boolean slot_save_exists(const char *file, int slot)\n{\n  struct stat s;\n  char path[MAX_PATH];\n\n  snprintf(path, MAX_PATH, \"%s%i%s\",\n   cur_slot_prefix, slot, get_config()->save_slots_ext);\n  return !vstat(path, &s);\n}\n\nstatic int click_slot_selector(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y,\n int new_active)\n{\n  struct slot_selector *src = (struct slot_selector *)e;\n  int target_slot = 0;\n\n  if((mouse_x < 0) || (mouse_x >= 40) || (mouse_y != SLOTSEL_BAR_Y))\n    return 0;\n\n  target_slot = mouse_x / 4;\n\n  if(src->save || slot_save_exists(cur_slot_prefix, target_slot))\n  {\n    src->selected_slot = target_slot;\n  }\n\n  return 0;\n}\n\nstatic int drag_slot_selector(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y)\n{\n  return click_slot_selector(mzx_world, di, e, mouse_button, mouse_x, mouse_y, 0);\n}\n\nstatic int click_file_selector(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y,\n int new_active)\n{\n  return IKEY_RETURN;\n}\n\nstatic int click_list_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y,\n int new_active)\n{\n  struct list_box *src = (struct list_box *)e;\n  int scroll_offset = src->scroll_offset;\n  int choice_length = src->choice_length;\n  int num_choices = src->num_choices;\n  int num_choices_visible = src->num_choices_visible;\n  int current_in_window = *(src->result) - scroll_offset;\n\n  src->clicked_scrollbar = 0;\n\n  if((num_choices > num_choices_visible) &&\n   (mouse_x == choice_length))\n  {\n    if(mouse_y == 0)\n      return IKEY_UP;\n\n    if(mouse_y == (num_choices_visible - 1))\n      return IKEY_DOWN;\n\n    src->clicked_scrollbar = 1;\n\n    scroll_offset =\n     (mouse_y - 1) *\n     (num_choices + ((num_choices_visible - 2) / 2)) /\n     (num_choices_visible - 2) - (num_choices_visible / 2);\n\n    if(scroll_offset < 0)\n      scroll_offset = 0;\n\n    if(scroll_offset + num_choices_visible >\n     num_choices)\n    {\n      scroll_offset = num_choices - num_choices_visible;\n      if(scroll_offset < 0)\n        scroll_offset = 0;\n    }\n\n    src->scroll_offset = scroll_offset;\n    *(src->result) = scroll_offset + (num_choices_visible / 2);\n  }\n  else\n  {\n    if(src->num_choices)\n    {\n      int offset = (mouse_y - current_in_window);\n      if(offset)\n      {\n        int current_choice = *(src->result) + offset;\n        if(current_choice < src->num_choices)\n          *(src->result) = current_choice;\n      }\n      else\n      {\n        if(!new_active)\n        {\n          di->return_value = src->return_value;\n          di->done = 1;\n        }\n      }\n    }\n  }\n\n  if(src->result_offset)\n    *(src->result_offset) = scroll_offset;\n\n  return 0;\n}\n\nstatic int drag_list_box(struct world *mzx_world, struct dialog *di,\n struct element *e, int mouse_button, int mouse_x, int mouse_y)\n{\n  struct list_box *src = (struct list_box *)e;\n\n  if(src->clicked_scrollbar)\n  {\n    if((mouse_y > 0) &&\n     (mouse_y < src->num_choices_visible - 1))\n    {\n      click_list_box(mzx_world, di, e, mouse_button,\n       src->choice_length, mouse_y, 0);\n      src->clicked_scrollbar = 1;\n    }\n  }\n  else\n\n  if((mouse_y >= 0) && (mouse_y < e->height) &&\n   (mouse_x >= 0))\n  {\n    if( ((mouse_x < e->width - 1) &&\n     (mouse_y != *(src->result) - src->scroll_offset)) ||\n     ((mouse_x == e->width - 1) && mouse_button)  )\n    {\n      return click_list_box(mzx_world, di, e, mouse_button,\n       mouse_x, mouse_y, 0);\n    }\n  }\n\n  return 0;\n}\n\nstatic int idle_input_box(struct world *mzx_world, struct dialog *di,\n struct element *e)\n{\n  struct input_box *src = (struct input_box *)e;\n  int x = di->x + e->x;\n  int y = di->y + e->y;\n\n  return intake(mzx_world, src->result, src->max_length, src->max_length,\n   x + (int)strlen(src->question) + di->pad_space, y, DI_INPUT, INTK_EXIT_ANY,\n   true, NULL);\n}\n\nvoid construct_dialog(struct dialog *src, const char *title, int x, int y,\n int width, int height, struct element **elements, int num_elements,\n int start_element)\n{\n  src->title = title;\n  src->x = x;\n  src->y = y;\n  src->width = width;\n  src->height = height;\n  src->elements = elements;\n  src->num_elements = num_elements;\n  src->sfx_test_for_input = 0;\n  src->pad_space = 0;\n  src->done = 0;\n  src->return_value = 0;\n  src->current_element = start_element;\n  src->idle_function = NULL;\n}\n\n__editor_maybe_static void construct_dialog_ext(struct dialog *src,\n const char *title, int x, int y, int width, int height,\n struct element **elements, int num_elements, int sfx_test_for_input,\n int pad_space, int start_element,\n int (* idle_function)(struct world *mzx_world, struct dialog *di, int key))\n{\n  src->title = title;\n  src->x = x;\n  src->y = y;\n  src->width = width;\n  src->height = height;\n  src->elements = elements;\n  src->num_elements = num_elements;\n  src->sfx_test_for_input = sfx_test_for_input;\n  src->pad_space = pad_space;\n  src->done = 0;\n  src->return_value = 0;\n  src->current_element = start_element;\n  src->idle_function = idle_function;\n}\n\nvoid destruct_dialog(struct dialog *src)\n{\n  int i;\n\n  for(i = 0; i < src->num_elements; i++)\n  {\n    free(src->elements[i]);\n  }\n\n  restore_screen();\n}\n\nstruct element *construct_label(int x, int y, const char *text)\n{\n  struct label_element *src = cmalloc(sizeof(struct label_element));\n  src->text = text;\n  src->respect_colors = true;\n  construct_element(&(src->e), x, y, (int)strlen(text), 1,\n   draw_label, NULL, NULL, NULL, NULL);\n\n  return (struct element *)src;\n}\n\n__editor_maybe_static struct element *construct_input_box(int x, int y,\n const char *question, int max_length, char *result)\n{\n  struct input_box *src = cmalloc(sizeof(struct input_box));\n  src->question = question;\n  src->max_length = max_length;\n  src->result = result;\n  construct_element(&(src->e), x, y,\n   max_length + (int)strlen(question) + 1, 1,\n   draw_input_box, key_input_box, click_input_box,\n   NULL, idle_input_box);\n\n  return (struct element *)src;\n}\n\nstruct element *construct_radio_button(int x, int y,\n const char **choices, int num_choices, int max_length, int *result)\n{\n  struct radio_button *src = cmalloc(sizeof(struct radio_button));\n  src->choices = choices;\n  src->num_choices = num_choices;\n  src->result = result;\n  src->max_length = max_length;\n  construct_element(&(src->e), x, y, max_length + 4,\n   num_choices, draw_radio_button, key_radio_button,\n   click_radio_button, NULL, NULL);\n\n  return (struct element *)src;\n}\n\nstruct element *construct_button(int x, int y, const char *label,\n int return_value)\n{\n  struct button *src = cmalloc(sizeof(struct button));\n  src->label = label;\n  src->return_value = return_value;\n\n  construct_element(&(src->e), x, y, (int)strlen(src->label) + 2,\n   1, draw_button, key_button, click_button, NULL, NULL);\n\n  return (struct element *)src;\n}\n\nstruct element *construct_number_box(int x, int y,\n const char *question, int lower_limit, int upper_limit,\n enum number_box_type type, int *result)\n{\n  struct number_box *src = cmalloc(sizeof(struct number_box));\n  int width;\n\n  src->question = question;\n  src->lower_limit = lower_limit;\n  src->upper_limit = upper_limit;\n  src->type = type;\n  src->result = result;\n  src->is_null = false;\n  width = (int)strlen(question) + 1;\n\n  if(src->type == NUMBER_LINE)\n  {\n    if(lower_limit != 1 || upper_limit >= 10)\n      src->type = NUMBER_BOX;\n\n    else\n      width += upper_limit - 1;\n  }\n  else\n\n  if(src->type == NUMBER_SLIDER)\n  {\n    // Currently only allow lower limits from 0 to 9.\n    if(lower_limit < 0 || lower_limit >= 10)\n      src->type = NUMBER_BOX;\n\n    else\n      width += (upper_limit - lower_limit) + (upper_limit >= 10) + 2;\n  }\n\n  if(src->type == NUMBER_BOX || src->type == NUMBER_BOX_MULT_FIVE)\n  {\n    width += 13;\n  }\n\n  construct_element(&(src->e), x, y, width, 1,\n   draw_number_box, key_number_box, click_number_box, drag_number_box, NULL);\n\n  return (struct element *)src;\n}\n\nstruct element *construct_slot_selector(int x, int y,\n const char *title, int num_slots, boolean *highlighted_slots,\n boolean *disabled_slots, int default_slot, int save)\n{\n  struct slot_selector *src = cmalloc(sizeof(struct slot_selector));\n\n  src->title = title;\n  src->num_slots = num_slots;\n  src->highlighted_slots = highlighted_slots;\n  src->disabled_slots = disabled_slots;\n  src->selected_slot = default_slot;\n  src->save = save;\n\n  construct_element(&(src->e), x, y, num_slots * 4, 3, draw_slot_selector,\n   key_slot_selector, click_slot_selector, drag_slot_selector, NULL);\n\n  return (struct element *)src;\n}\n\nstruct element *construct_file_selector(int x, int y,\n const char *title, const char *file_manager_title,\n const char *const *file_manager_exts, const char *none_mesg,\n int show_width, int allow_unset, const char *base_path, char *result,\n int return_value)\n{\n  struct file_selector *src = cmalloc(sizeof(struct file_selector));\n\n  src->title = title;\n  src->file_manager_title = file_manager_title;\n  src->file_manager_exts = file_manager_exts;\n  src->base_path = base_path;\n  src->none_mesg = none_mesg;\n  src->allow_unset = allow_unset;\n  src->return_value = return_value;\n  src->result = result;\n\n  construct_element(&(src->e), x, y, show_width + 3,\n   2, draw_file_selector, key_file_selector, click_file_selector,\n   NULL, NULL);\n\n  return (struct element *)src;\n}\n\nstruct element *construct_list_box(int x, int y,\n const char **choices, int num_choices, int num_choices_visible,\n int choice_length, int return_value, int *result, int *result_offset,\n boolean respect_color_codes)\n{\n  int scroll_offset;\n\n  struct list_box *src = cmalloc(sizeof(struct list_box));\n  src->choices = choices;\n  src->num_choices = num_choices;\n  src->num_choices_visible = num_choices_visible;\n  src->choice_length = choice_length;\n  src->result = result;\n  src->result_offset = result_offset;\n  src->return_value = return_value;\n  src->key_position = 0;\n  src->last_keypress_time = 0;\n  src->clicked_scrollbar = 0;\n  src->respect_color_codes = respect_color_codes;\n\n  if(result_offset)\n    scroll_offset = *result_offset;\n\n  else\n    scroll_offset = *result - (num_choices_visible / 2);\n\n  if(scroll_offset < 0)\n    scroll_offset = 0;\n\n  if(scroll_offset + num_choices_visible > num_choices)\n  {\n    scroll_offset =\n      num_choices - num_choices_visible;\n\n    if(scroll_offset < 0)\n      scroll_offset = 0;\n  }\n\n  src->scroll_offset = scroll_offset;\n\n  construct_element(&(src->e), x, y, choice_length + 1,\n   num_choices_visible, draw_list_box, key_list_box,\n   click_list_box, drag_list_box, NULL);\n\n  return (struct element *)src;\n}\n\n// Shell for run_dialog()\nint confirm(struct world *mzx_world, const char *str)\n{\n  struct dialog di;\n  struct element *elements[2];\n  int dialog_result;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  elements[0] = construct_button(15, 2, \"OK\", 0);\n  elements[1] = construct_button(37, 2, \"Cancel\", 1);\n  construct_dialog(&di, str, 10, 9, 60, 5, elements,\n   2, 0);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  return dialog_result;\n}\n\n// Shell for run_dialog()\nint confirm_input(struct world *mzx_world, const char *name,\n const char *label, char *str)\n{\n  struct dialog di;\n  struct element *elements[3];\n  int dialog_result;\n\n  int input_length = 32;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  // Don't pass anything through that isn't this big plz\n  str[input_length] = '\\0';\n\n  elements[0] = construct_input_box(2, 2, label, input_length, str);\n  elements[1] = construct_button(15, 4, \"OK\", 0);\n  elements[2] = construct_button(37, 4, \"Cancel\", 1);\n  construct_dialog(&di, name, 11, 8, 57, 7, elements, 3, 0);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  return dialog_result;\n}\n\n// Shell for run_dialog()\nint ask_yes_no(struct world *mzx_world, char *str)\n{\n  struct dialog di;\n  struct element *elements[2];\n  int dialog_result;\n  int dialog_width = 60;\n  size_t str_length = strlen(str);\n\n  int yes_button_pos;\n  int no_button_pos;\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  // Is this string too long for the normal ask dialog?\n  if(str_length > 56)\n  {\n    // Is the string small enough for a resized ask dialog?\n    if(str_length <= 76)\n    {\n      // Use a bigger ask dialog to fit the string\n      dialog_width = (int)str_length + 4;\n      // If the dialog width is odd, bump it up to the next\n      // even number, otherwise it will look uneven\n      if((dialog_width % 2) == 1)\n      {\n        dialog_width++;\n      }\n    }\n    else\n    {\n      // Clip the string at the maximum (76 characters)\n      str[76] = 0;\n      dialog_width = 80;\n    }\n  }\n\n  // The 'Yes' button should be about 1/3 along, compensating for\n  // button width\n  yes_button_pos = (dialog_width - 4) / 3 - 2;\n\n  // The 'No' button should be about 2/3 along\n  no_button_pos = (dialog_width - 4) * 2 / 3 - 1;\n\n  elements[0] = construct_button(yes_button_pos, 2, \"Yes\", 0);\n  elements[1] = construct_button(no_button_pos, 2, \"No\", 1);\n\n  construct_dialog(&di, str, 40 - dialog_width / 2, 9, dialog_width,\n    5, elements, 2, 0);\n\n  dialog_result = run_dialog(mzx_world, &di);\n  destruct_dialog(&di);\n\n  // Prevent UI keys from carrying through.\n  force_release_all_keys();\n\n  return dialog_result;\n}\n\nstatic int sort_function(const void *dest_str_ptr, const void *src_str_ptr)\n{\n  char *dest_str = *((char **)dest_str_ptr);\n  char *src_str = *((char **)src_str_ptr);\n\n  if(src_str[0] == '.')\n    return 1;\n\n  if(dest_str[0] == '.')\n    return -1;\n\n  return strcasecmp(dest_str, src_str);\n}\n\nstatic boolean update_slot_prefix(void)\n{\n  char *world_name;\n  size_t i;\n  size_t pos = 0;\n  int token_pos = -1;\n  const char *fmt;\n  boolean parse_token = false;\n  size_t fmt_len;\n  size_t world_len;\n\n  cur_slot_prefix = crealloc(cur_slot_prefix, sizeof(char) * MAX_PATH);\n\n  fmt = get_config()->save_slots_name;\n  fmt_len = strlen(fmt);\n  for(i = 0; i < fmt_len; i++)\n  {\n    if(fmt[i] == '%')\n    {\n      token_pos = i;\n      break;\n    }\n  }\n\n  // If no tokens were found, just copy the string over as-is.\n  if(token_pos < 0)\n  {\n    snprintf(cur_slot_prefix, MAX_PATH, \"%s\", fmt);\n    cur_slot_prefix[fmt_len] = 0;\n    debug(\"update_slot_prefix: slot prefix: %s\\n\", cur_slot_prefix);\n    return true;\n  }\n\n  // Fetch the world name, sans extension.\n  world_name = strrchr(curr_file, DIR_SEPARATOR_CHAR);\n  world_name = world_name ? world_name + 1 : curr_file;\n  world_len = strlen(world_name);\n\n  if(world_len < 4 || strcasecmp(world_name + world_len - 4, \".mzx\"))\n  {\n    debug(\"update_slot_prefix: filename not set or is invalid\\n\");\n    return false;\n  }\n  world_len -= 4;\n\n  // We already know where the first token is. Copy everything before that\n  // if possible.\n  if(token_pos > 0)\n  {\n    memcpy(cur_slot_prefix, fmt, token_pos);\n    pos += token_pos;\n  }\n\n  // Walk through the format string and replace tokens when necessary.\n  for(i = pos; i < fmt_len && pos < MAX_PATH - 1; i++)\n  {\n    if(!parse_token && fmt[i] != '%')\n    {\n      cur_slot_prefix[pos++] = fmt[i];\n      continue;\n    }\n\n    if(parse_token)\n    {\n      switch(fmt[i])\n      {\n        case '%':\n        {\n          cur_slot_prefix[pos++] = '%';\n          break;\n        }\n\n        case 'w':\n        {\n          if(pos + world_len >= MAX_PATH)\n            break;\n\n          memcpy(cur_slot_prefix + pos, world_name, world_len);\n          pos += world_len;\n          break;\n        }\n\n        default:\n        {\n          debug(\"update_slot_prefix: invalid token '%c'\\n\", fmt[i]);\n          return false;\n        }\n      }\n\n      parse_token = false;\n      continue;\n    }\n\n    parse_token = true;\n  }\n\n  cur_slot_prefix[pos] = '\\0';\n  debug(\"update_slot_prefix: slot prefix: %s\\n\", cur_slot_prefix);\n  return true;\n}\n\nint slot_manager(struct world *mzx_world, char *ret,\n const char *title, boolean save)\n{\n  int i;\n  struct dialog di;\n  struct element *elements[SLOTSEL_MAX_ELEMENTS];\n  int dialog_result = 0;\n  int return_value = 1;\n  int selected_slot = cur_slot;\n  boolean *highlighted_slots = NULL;\n  boolean *disabled_slots = NULL;\n\n  if(!update_slot_prefix())\n    return SLOTSEL_FILE_MANAGER_RESULT;\n\n  while(return_value > 0)\n  {\n    highlighted_slots = cmalloc(sizeof(boolean) * SLOTSEL_NUM_SLOTS);\n    for(i = 0; i < SLOTSEL_NUM_SLOTS; i++)\n    {\n      highlighted_slots[i] = slot_save_exists(cur_slot_prefix, i);\n    }\n\n    if(!save)\n    {\n      disabled_slots = cmalloc(sizeof(boolean) * SLOTSEL_NUM_SLOTS);\n      for(i = 0; i < SLOTSEL_NUM_SLOTS; i++)\n      {\n        disabled_slots[i] = !highlighted_slots[i];\n      }\n\n      // If the current slot is empty, try to set it to one that isn't.\n      if(disabled_slots[selected_slot])\n      {\n        for(i = 0; i < SLOTSEL_NUM_SLOTS; i++)\n        {\n          if(!disabled_slots[i])\n          {\n            selected_slot = i;\n            break;\n          }\n        }\n      }\n    }\n\n    elements[SLOTSEL_SELECTOR] =\n     construct_slot_selector(3, 2, \"Choose a slot:\", SLOTSEL_NUM_SLOTS,\n     highlighted_slots, disabled_slots, selected_slot, save);\n    elements[SLOTSEL_OKAY_BUTTON] =\n     construct_button(7, 6, \"OK\", SLOTSEL_OK_RESULT);\n    elements[SLOTSEL_CANCEL_BUTTON] =\n     construct_button(14, 6, \"Cancel\", SLOTSEL_CANCEL_RESULT);\n    elements[SLOTSEL_FILE_MANAGER_BUTTON] =\n     construct_button(25, 6, \"File Manager\", SLOTSEL_FILE_MANAGER_RESULT);\n\n    construct_dialog(&di, title, 17, 8, 46, 9, elements,\n     SLOTSEL_MAX_ELEMENTS, SLOTSEL_SELECTOR);\n    dialog_result = run_dialog(mzx_world, &di);\n\n    switch(dialog_result)\n    {\n      case SLOTSEL_OK_RESULT:\n      {\n        cur_slot =\n         ((struct slot_selector *)elements[SLOTSEL_SELECTOR])->selected_slot;\n        return_value = 0;\n\n        // Add one more quick check to make sure that we don't try to restore an\n        // empty slot.\n        if(!save && disabled_slots[cur_slot])\n          return_value = -1;\n\n        break;\n      }\n\n      default:\n      {\n        return_value = dialog_result;\n        break;\n      }\n    }\n\n    force_release_all_keys();\n\n    destruct_dialog(&di);\n\n    free(highlighted_slots);\n    free(disabled_slots);\n  }\n\n  if(return_value != 0)\n    return return_value;\n\n  snprintf(ret, MAX_PATH, \"%s%i%s\",\n   cur_slot_prefix, cur_slot, get_config()->save_slots_ext);\n\n  return return_value;\n}\n\n// Shell for list_menu() (copies file chosen to ret and returns -1 for ESC)\n\n#define FILESEL_MAX_ELEMENTS  64\n#define FILESEL_BASE_ELEMENTS 7\n#define FILESEL_OKAY_BUTTON   0\n#define FILESEL_CANCEL_BUTTON 1\n#define FILESEL_FILE_LIST     2\n#define FILESEL_DIR_LIST      3\n#define FILESEL_FILENAME      4\n#define FILESEL_FILES_LABEL   5\n#define FILESEL_DIRS_LABEL    6\n\n#define MAX_FILE_LIST_DISPLAY 56\n#define MAX_FILE_LIST_DISPLAY_MZX 30\n\nstruct file_list_entry\n{\n  // This is the string displayed by the file list interface. It must be the\n  // first thing in this struct.\n  char display[MAX_FILE_LIST_DISPLAY];\n\n  boolean is_mzx_file;\n  boolean loaded_world_name;\n  char filename[1];\n};\n\n/**\n * Read the name of a world file from the file.\n * Conveniently, this is typically just the first 25 bytes of the world.\n */\nstatic void file_list_get_mzx_world_name(struct file_list_entry *entry)\n{\n  // Don't add this file to the cache as there may be a LOT of these.\n  vfile *mzx_file = vfopen_unsafe_ext(entry->filename, \"rb\", V_DONT_CACHE);\n  char *world_name = entry->display + MAX_FILE_LIST_DISPLAY_MZX;\n  if(!mzx_file)\n    return;\n\n  if(!vfread(world_name, 24, 1, mzx_file))\n    strcpy(world_name, \"@0~c\\x10 name read failed \\x11\");\n  else\n  if(!memcmp(world_name, \"PK\\x03\\x04\", 4))\n    strcpy(world_name, \"@0~c\\x10 rearchived world \\x11\");\n\n  vfclose(mzx_file);\n\n  entry->display[MAX_FILE_LIST_DISPLAY - 1] = '\\0';\n  entry->loaded_world_name = true;\n}\n\n/**\n * Create an entry in the file list.\n */\nstatic struct file_list_entry *construct_file_list_entry(const char *file_name)\n{\n  size_t file_name_length = strlen(file_name);\n  struct file_list_entry *entry =\n   cmalloc(sizeof(struct file_list_entry) + file_name_length);\n\n  memcpy(entry->filename, file_name, file_name_length + 1);\n\n  if(file_name_length >= 4 &&\n   !strcasecmp(file_name + file_name_length - 4, \".mzx\"))\n  {\n    // Special handling for MZX worlds- display their world names.\n    entry->is_mzx_file = true;\n    entry->loaded_world_name = false;\n\n    // Display the filename in a smaller area and space-pad the world name.\n    snprintf(entry->display, sizeof(entry->display), \"%-*.*s%*s\",\n     MAX_FILE_LIST_DISPLAY_MZX, MAX_FILE_LIST_DISPLAY_MZX, file_name,\n     MAX_FILE_LIST_DISPLAY - MAX_FILE_LIST_DISPLAY_MZX, \" \");\n\n    // Display names that are too long with ...\n    if(file_name_length >= MAX_FILE_LIST_DISPLAY_MZX)\n      memcpy(entry->display + MAX_FILE_LIST_DISPLAY_MZX - 4, \"... \", 4);\n\n    // TODO it would be nice to be able to delay this on the 3DS but it's\n    // annoying to implement right now.\n    file_list_get_mzx_world_name(entry);\n  }\n  else\n  {\n    entry->is_mzx_file = false;\n    snprintf(entry->display, MAX_FILE_LIST_DISPLAY, \"%s\", file_name);\n\n    // Display names that are too long with ...\n    if(file_name_length > (MAX_FILE_LIST_DISPLAY - 1))\n      snprintf(entry->display + MAX_FILE_LIST_DISPLAY - 4, 4, \"...\");\n  }\n  return entry;\n}\n\nstatic int file_dialog_function(struct world *mzx_world, struct dialog *di,\n int key)\n{\n  int current_element_num = di->current_element;\n  struct element *current_element = di->elements[current_element_num];\n\n  switch(current_element_num)\n  {\n    case FILESEL_DIR_LIST:\n    case FILESEL_FILE_LIST:\n    {\n      struct list_box *src = (struct list_box *)current_element;\n      struct input_box *dest =\n       (struct input_box *)di->elements[FILESEL_FILENAME];\n      struct element *e = (struct element *)dest;\n\n      if(src->num_choices)\n      {\n        const char *file_name = src->choices[*(src->result)];\n\n        if(get_alt_status(keycode_internal) &&\n         !get_ctrl_status(keycode_internal) && !get_shift_status(keycode_internal))\n        {\n          switch(key)\n          {\n            case IKEY_r:\n            {\n              if(current_element_num == FILESEL_DIR_LIST)\n                di->return_value = 7;\n              else\n                di->return_value = 5;\n\n              di->done = 1;\n              break;\n            }\n\n            case IKEY_d:\n            {\n              if(current_element_num == FILESEL_DIR_LIST)\n                di->return_value = 6;\n              else\n                di->return_value = 4;\n\n              di->done = 1;\n              break;\n            }\n\n            case IKEY_n:\n            {\n              di->done = 1;\n              di->return_value = 3;\n              break;\n            }\n          }\n        }\n\n        if(current_element_num == FILESEL_FILE_LIST)\n        {\n          struct file_list_entry *entry = (struct file_list_entry *)file_name;\n          snprintf(dest->result, dest->max_length, \"%s\", entry->filename);\n          dest->result[dest->max_length - 1] = '\\0';\n          e->draw_function(mzx_world, di, e, DI_NONACTIVE, 0);\n        }\n      }\n      else\n      {\n        dest->result[0] = 0;\n        e->draw_function(mzx_world, di, e, DI_NONACTIVE, 0);\n      }\n\n      if(key == IKEY_DELETE)\n      {\n        if(current_element_num == FILESEL_DIR_LIST)\n          di->return_value = 6;\n        else\n          di->return_value = 4;\n\n        di->done = 1;\n      }\n\n      if(key == IKEY_BACKSPACE)\n      {\n        di->done = 1;\n        di->return_value = -2;\n        return 0;\n      }\n\n      break;\n    }\n\n    case FILESEL_FILENAME:\n    {\n      struct input_box *src = (struct input_box *)current_element;\n      struct list_box *dest =\n       (struct list_box *)di->elements[FILESEL_FILE_LIST];\n      struct element *e = (struct element *)dest;\n      int current_choice = *(dest->result);\n      int new_choice =\n       find_entry(dest->choices, src->result, dest->num_choices);\n\n      if(new_choice != -1)\n        current_choice = new_choice;\n\n      if((current_choice < dest->scroll_offset) ||\n       (current_choice >= (dest->scroll_offset +\n       dest->num_choices_visible)))\n      {\n        dest->scroll_offset =\n         current_choice - (dest->num_choices_visible / 2);\n\n        if(dest->scroll_offset < 0)\n          dest->scroll_offset = 0;\n\n        if(dest->scroll_offset + dest->num_choices_visible >\n         dest->num_choices)\n        {\n          dest->scroll_offset =\n           dest->num_choices - dest->num_choices_visible;\n          if(dest->scroll_offset < 0)\n            dest->scroll_offset = 0;\n        }\n      }\n\n      *(dest->result) = current_choice;\n      e->draw_function(mzx_world, di, e, DI_NONACTIVE, 0);\n\n      if(key == IKEY_RETURN)\n      {\n        di->done = 1;\n        di->return_value = 0;\n        return 0;\n      }\n\n      break;\n    }\n  }\n\n  return key;\n}\n\nstatic boolean remove_files(char *directory_name, boolean remove_recursively)\n{\n  vdir *current_dir;\n  char *current_dir_name;\n  struct stat file_info;\n  char *file_name;\n  boolean success = true;\n\n  current_dir = vdir_open(directory_name);\n  if(!current_dir)\n    return false;\n\n  current_dir_name = cmalloc(MAX_PATH);\n  file_name = cmalloc(MAX_PATH);\n\n  vgetcwd(current_dir_name, MAX_PATH);\n  vchdir(directory_name);\n\n  while(1)\n  {\n    if(!vdir_read(current_dir, file_name, MAX_PATH, NULL))\n      break;\n\n    if(vstat(file_name, &file_info) < 0)\n      continue;\n\n    if(!S_ISDIR(file_info.st_mode))\n    {\n      // Only attempt to remove contents if remove_recursively is set...\n      if(!remove_recursively || vunlink(file_name))\n        success = false;\n    }\n    else\n\n    if(strcmp(file_name, PATH_CURRENT_DIR) && strcmp(file_name, PATH_PARENT_DIR))\n    {\n      if(!remove_recursively ||\n       !remove_files(file_name, true) || vrmdir(file_name))\n        success = false;\n    }\n  }\n\n  vchdir(current_dir_name);\n\n  free(file_name);\n  free(current_dir_name);\n\n  vdir_close(current_dir);\n  return success;\n}\n\n__editor_maybe_static int file_manager(struct world *mzx_world,\n const char *const *wildcards, const char *default_ext, char *ret,\n const char *title, enum allow_dirs allow_dirs, enum allow_new allow_new,\n struct element **dialog_ext, int num_ext, int ext_height)\n{\n  // FIXME no buffer size parameter for ret. this function assumes MAX_PATH.\n  vdir *current_dir;\n  char *file_name;\n  struct stat file_info;\n  char *current_dir_name;\n  char current_dir_short[56];\n  size_t current_dir_length;\n  char *return_dir_name;\n  char *base_dir_name;\n  int total_filenames_allocated;\n  int total_dirnames_allocated;\n  char **file_list;\n  char **dir_list;\n  int num_files;\n  int num_dirs;\n  size_t file_name_length;\n  ssize_t ext_pos = -1;\n  int chosen_file, chosen_dir;\n  int dialog_result = 1;\n  int return_value = 1;\n  struct dialog di;\n  struct element *elements[FILESEL_MAX_ELEMENTS];\n  int list_length = 17 - ext_height;\n  int last_element = FILESEL_FILE_LIST;\n  boolean return_dir_is_base_dir = true;\n  int volumes_pos;\n  int i;\n\n  // Buffers for the return file's path and name.\n  char ret_path[MAX_PATH];\n  char ret_file[MAX_PATH];\n\n  // Prevent previous keys from carrying through.\n  force_release_all_keys();\n\n  // These are stack heavy so put them on the heap\n  // This function is not performance sensitive anyway.\n  file_name = cmalloc(MAX_PATH);\n\n  // The current directory the file manager is in.\n  current_dir_name = cmalloc(MAX_PATH);\n\n  // Where the file manager is allowed to search.\n  base_dir_name = cmalloc(MAX_PATH);\n\n  // Where the file manager needs to return on exit.\n  return_dir_name = cmalloc(MAX_PATH);\n\n  if(allow_new != NO_NEW_FILES)\n    last_element = FILESEL_FILENAME;\n\n  // TODO: some platforms don't return this in the format MZX needs it.\n  vgetcwd(return_dir_name, MAX_PATH);\n  path_clean(return_dir_name, MAX_PATH);\n\n  snprintf(current_dir_name, MAX_PATH, \"%s\", return_dir_name);\n  current_dir_name[MAX_PATH - 1] = '\\0';\n\n  // Check the input path for a directory. If there is a directory component\n  // to the input, that should be used as the base directory.\n  // TODO: this is a bad way to do this. The base dir should be a param instead.\n  if(path_get_directory(ret_path, MAX_PATH, ret) > 0)\n  {\n    path_navigate(current_dir_name, MAX_PATH, ret_path);\n    return_dir_is_base_dir = false;\n  }\n\n  snprintf(base_dir_name, MAX_PATH, \"%s\", current_dir_name);\n  base_dir_name[MAX_PATH - 1] = '\\0';\n\n  while(return_value == 1)\n  {\n    total_filenames_allocated = 32;\n    total_dirnames_allocated = 32;\n    file_list = ccalloc(32, sizeof(char *));\n    dir_list = ccalloc(32, sizeof(char *));\n\n    num_files = 0;\n    num_dirs = 0;\n    chosen_file = 0;\n    chosen_dir = 0;\n\n    /**\n     * Change to the current directory to make things like reading MZX files\n     * to display world names a little easier.\n     */\n    vchdir(current_dir_name);\n\n    current_dir = vdir_open(current_dir_name);\n    if(!current_dir)\n      goto skip_dir;\n\n    // Hide .. if changing directories isn't allowed or if the selected file\n    // should be in the current directory or a subdirectory of it. Also hide it\n    // if this is a root directory.\n    if(!(allow_dirs == NO_DIRS) &&\n     !(allow_dirs == ALLOW_SUBDIRS && !strcmp(current_dir_name, base_dir_name)) &&\n     !path_is_root(current_dir_name))\n    {\n      dir_list[num_dirs] = (char *)cmalloc(strlen(PATH_PARENT_DIR) + 1);\n      strcpy(dir_list[num_dirs], PATH_PARENT_DIR);\n      num_dirs++;\n    }\n\n    while(1)\n    {\n      enum vdir_type dir_type;\n      if(!vdir_read(current_dir, file_name, MAX_PATH, &dir_type))\n        break;\n\n      file_name_length = strlen(file_name);\n\n      // Exclude ./.. and hidden files\n      if(file_name[0] == '.')\n        continue;\n\n      // The file type value from dirent isn't particularly reliable; might\n      // need to use stat instead.\n      if(dir_type == DIR_TYPE_UNKNOWN)\n      {\n        if(vstat(file_name, &file_info) < 0)\n          continue;\n\n        if(S_ISDIR(file_info.st_mode))\n          dir_type = DIR_TYPE_DIR;\n\n        else\n        if(S_ISREG(file_info.st_mode))\n          dir_type = DIR_TYPE_FILE;\n      }\n\n      if(dir_type == DIR_TYPE_DIR)\n      {\n        if(allow_dirs != NO_DIRS)\n        {\n          dir_list[num_dirs] = cmalloc(file_name_length + 1);\n          memcpy(dir_list[num_dirs], file_name, file_name_length + 1);\n          dir_list[num_dirs][file_name_length] = '\\0';\n          num_dirs++;\n        }\n      }\n      else\n\n      if(dir_type == DIR_TYPE_FILE)\n      {\n        // Find the extension.\n        ext_pos = path_get_ext_offset(file_name);\n\n        for(i = 0; wildcards[i] != NULL; i++)\n        {\n          if(ext_pos >= 0 && !strcasecmp(file_name + ext_pos, wildcards[i]))\n          {\n            struct file_list_entry *e = construct_file_list_entry(file_name);\n            file_list[num_files] = (char *)e;\n            num_files++;\n            break;\n          }\n        }\n      }\n\n      if(num_files == total_filenames_allocated)\n      {\n        file_list = crealloc(file_list, sizeof(char *) *\n         total_filenames_allocated * 2);\n        memset(file_list + total_filenames_allocated, 0,\n         sizeof(char *) * total_filenames_allocated);\n        total_filenames_allocated *= 2;\n      }\n\n      if(num_dirs == total_dirnames_allocated)\n      {\n        dir_list = crealloc(dir_list, sizeof(char *) *\n         total_dirnames_allocated * 2);\n        memset(dir_list + total_dirnames_allocated, 0,\n         sizeof(char *) * total_dirnames_allocated);\n        total_dirnames_allocated *= 2;\n      }\n    }\n\n    vdir_close(current_dir);\nskip_dir:\n\n    vchdir(return_dir_name);\n\n    qsort(file_list, num_files, sizeof(char *), sort_function);\n    qsort(dir_list, num_dirs, sizeof(char *), sort_function);\n\n    volumes_pos = num_dirs;\n    if(allow_dirs == ALLOW_ALL_DIRS)\n    {\n      vvolumelist *volumes = vvolumelist_open();\n      if(volumes)\n      {\n        const char *dir;\n\n        while((dir = vvolumelist_read(volumes)))\n        {\n          size_t sz = strlen(dir) + 1;\n\n          /* Strip out / for now. */\n          if(!strcmp(dir, DIR_SEPARATOR))\n            continue;\n\n          dir_list[num_dirs] = (char *)cmalloc(sz);\n          if(!dir_list[num_dirs])\n            break;\n\n          memcpy(dir_list[num_dirs], dir, sz);\n          num_dirs++;\n\n          if(num_dirs == total_dirnames_allocated)\n          {\n            dir_list = crealloc(dir_list, sizeof(char *) *\n             total_dirnames_allocated * 2);\n            memset(dir_list + total_dirnames_allocated, 0,\n             sizeof(char *) * total_dirnames_allocated);\n            total_dirnames_allocated *= 2;\n          }\n        }\n        vvolumelist_close(volumes);\n      }\n    }\n\n    current_dir_length = strlen(current_dir_name);\n\n    if(current_dir_length > 55)\n    {\n      memcpy(current_dir_short, \"...\", 3);\n      memcpy(current_dir_short + 3,\n       current_dir_name + current_dir_length - 52, 52);\n      current_dir_short[55] = 0;\n    }\n    else\n    {\n      memcpy(current_dir_short, current_dir_name,\n       current_dir_length + 1);\n    }\n\n    // Make ret relative for the display.\n    path_to_filename(ret, MAX_PATH);\n    ret[55] = '\\0'; // Just in case\n\n    elements[FILESEL_FILE_LIST] =\n     construct_list_box(2, 2, (const char **)file_list, num_files,\n     list_length, (MAX_FILE_LIST_DISPLAY - 1), 1, &chosen_file, NULL, true);\n    elements[FILESEL_DIR_LIST] =\n     construct_list_box(59, 2, (const char **)dir_list, num_dirs,\n     list_length, 15, 2, &chosen_dir, NULL, true);\n    elements[FILESEL_FILENAME] =\n     construct_input_box(2, list_length + 3, \"\", 55, ret);\n    elements[FILESEL_OKAY_BUTTON] =\n     construct_button(60, list_length + 3, \"OK\", 0);\n    elements[FILESEL_CANCEL_BUTTON] =\n     construct_button(65, list_length + 3, \"Cancel\", -1);\n    elements[FILESEL_FILES_LABEL] =\n     construct_label(2, 1, current_dir_short);\n    elements[FILESEL_DIRS_LABEL] =\n     construct_label(59, 1, \"Directories\");\n\n    if(num_ext)\n    {\n      memcpy(elements + FILESEL_BASE_ELEMENTS, dialog_ext,\n       sizeof(struct element *) * num_ext);\n    }\n\n    construct_dialog_ext(&di, title, 2, 1, 76, 23,\n     elements, FILESEL_BASE_ELEMENTS + num_ext, 0, 0,\n     last_element, file_dialog_function);\n\n    dialog_result = run_dialog(mzx_world, &di);\n\n    // Prevent UI keys from carrying through.\n    force_release_all_keys();\n\n    switch(dialog_result)\n    {\n      // Pressed Backspace\n      case -2:\n      {\n        path_navigate(current_dir_name, MAX_PATH, PATH_PARENT_DIR);\n        break;\n      }\n\n      // Pressed Escape\n      case -1:\n      {\n        return_value = -1;\n        break;\n      }\n\n      // Entered File/Pressed Okay\n      case 0:\n      case 1:\n      {\n        int stat_result;\n\n        if(di.current_element == FILESEL_FILE_LIST)\n        {\n          /* If a file was selected from the file list, ignore the filename\n           * entry box value, which may be wrong--join the full filename to\n           * the (absolute) current directory instead. */\n          struct file_list_entry *e =\n           (struct file_list_entry *)file_list[chosen_file];\n\n          if(path_join(ret, MAX_PATH, current_dir_name, e->filename) < 0)\n          {\n            ret[0] = '\\0';\n            break;\n          }\n        }\n        else\n        {\n          /* Entered filenames are more messy--split them into components,\n           * derive a new current directory, and make ret absolute. */\n\n          /* This function will never set ret_file to an existing directory.\n           * TODO: since ret is probably relative, the internal stat() needs\n           * to be performed from the current directory to get reliable results.\n           */\n          vchdir(current_dir_name);\n          path_get_directory_and_filename(ret_path, MAX_PATH, ret_file, MAX_PATH, ret);\n          vchdir(return_dir_name);\n\n          if(ret_path[0] != '\\0')\n          {\n            if(path_navigate(current_dir_name, MAX_PATH, ret_path) < 0)\n            {\n              error(\"Directory does not exist or permission denied.\",\n               ERROR_T_ERROR, ERROR_OPT_OK, 0x0000);\n              ret[0] = '\\0';\n              break;\n            }\n          }\n\n          if(ret_file[0] == '\\0' ||\n           path_join(ret, MAX_PATH, current_dir_name, ret_file) < 0)\n          {\n            /* Directory entered, nothing else to do; or path is too long. */\n            ret[0] = '\\0';\n            break;\n          }\n        }\n\n        if(default_ext)\n          path_force_ext(ret, MAX_PATH, default_ext);\n\n        stat_result = vstat(ret, &file_info);\n\n        if((stat_result >= 0) && S_ISDIR(file_info.st_mode))\n        {\n          /* Should be unreachable unless someone swapped out a file\n           * for a directory while the file manager wasn't looking,\n           * but this is harmless to check anyway.\n           */\n          if(path_navigate(current_dir_name, MAX_PATH, ret) < 0)\n            error(\"Directory does not exist or permission denied.\",\n             ERROR_T_ERROR, ERROR_OPT_OK, 0x0000);\n          ret[0] = '\\0';\n          break;\n        }\n\n        // Directory restrictions or DOS/Windows? Filename needs safety checks:\n#if !defined(_WIN32) && !defined(CONFIG_DJGPP)\n        if(allow_dirs != ALLOW_ALL_DIRS)\n#endif\n        {\n          enum path_safe_mask check_result;\n          size_t base_dir_offset = 0;\n\n          if(allow_dirs != ALLOW_ALL_DIRS)\n          {\n            base_dir_offset = strlen(base_dir_name);\n\n            /* This is a duplicate of a later check out of necessity... */\n            if(strncmp(ret, base_dir_name, base_dir_offset))\n            {\n              error(\"File is outside of allowed base directory.\",\n               ERROR_T_ERROR, ERROR_OPT_OK, 0x0000);\n              ret[0] = '\\0';\n              break;\n            }\n          }\n          else\n            base_dir_offset = strlen(current_dir_name);\n\n          if((check_result = path_safety_check(ret + base_dir_offset,\n           PATH_SAFE_UNIX_PARENT | PATH_SAFE_DOS_CHARACTER | PATH_SAFE_DOS_DEVICE)))\n          {\n            snprintf(file_name, MAX_PATH, \"File name failed safety check: %s\",\n             path_safety_strerror(check_result));\n            error(file_name, ERROR_T_ERROR, ERROR_OPT_OK, 0x0000);\n            ret[0] = '\\0';\n            break;\n          }\n        }\n\n        // We're creating a new file\n        if(allow_new != NO_NEW_FILES)\n        {\n          // File Exists\n          if((stat_result >= 0) && (allow_new == ALLOW_NEW_FILES))\n          {\n            char confirm_string[512];\n            snprintf(confirm_string, 512, \"%s already exists, overwrite?\",\n             ret_file);\n            if(!ask_yes_no(mzx_world, confirm_string))\n              return_value = 0;\n          }\n          else\n          {\n            if(ret[0])\n              return_value = 0;\n          }\n        }\n        else\n\n        // It's a file, open it\n        if(stat_result >= 0)\n          return_value = 0;\n        else\n          ret[0] = 0;\n\n        break;\n      }\n\n      // Selected a directory from the list\n      case 2:\n      {\n        if(dir_list && dir_list[chosen_dir])\n          if(path_navigate(current_dir_name, MAX_PATH, dir_list[chosen_dir]) < 0)\n            error(\"Directory does not exist or permission denied.\",\n             ERROR_T_ERROR, ERROR_OPT_OK, 0x0000);\n\n        break;\n      }\n\n      // Create a new directory\n      case 3:\n      {\n        char *new_name;\n        char full_name[MAX_PATH];\n\n        new_name = cmalloc(MAX_PATH);\n        new_name[0] = 0;\n\n        if(!confirm_input(mzx_world, \"Create New Directory\",\n         \"New directory name:\", new_name))\n        {\n          path_join(full_name, MAX_PATH, current_dir_name, new_name);\n\n          if(vstat(full_name, &file_info) < 0)\n          {\n            vmkdir(full_name, 0777);\n          }\n          else\n          {\n            char error_str[512];\n            sprintf(error_str, \"%s already exists.\", new_name);\n            error(error_str, ERROR_T_ERROR, ERROR_OPT_OK, 0x0000);\n          }\n        }\n\n        free(new_name);\n        break;\n      }\n\n      // Delete file\n      case 4:\n      {\n        char *del_name = (char *)cmalloc(MAX_PATH);\n\n        struct file_list_entry *e =\n         (struct file_list_entry *)file_list[chosen_file];\n\n        if(strcmp(e->filename, PATH_PARENT_DIR) &&\n         strcmp(e->filename, PATH_CURRENT_DIR) &&\n         path_join(del_name, MAX_PATH, current_dir_name, e->filename) >= 0 &&\n         vstat(del_name, &file_info) >= 0)\n        {\n          char confirm_string[64];\n\n          if(strlen(e->filename) <= 33)\n          {\n            snprintf(confirm_string, sizeof(confirm_string),\n             \"Delete %.33s - are you sure?\", e->filename);\n          }\n          else\n          {\n            snprintf(confirm_string, sizeof(confirm_string),\n             \"Delete %.30s... - are you sure?\", e->filename);\n          }\n\n          if(!confirm(mzx_world, confirm_string))\n            if(vunlink(del_name))\n              error(\"File could not be deleted.\",\n               ERROR_T_WARNING, ERROR_OPT_OK, 0x0000);\n        }\n\n        free(del_name);\n        break;\n      }\n\n      // Rename file\n      case 5:\n      {\n        char *old_path = cmalloc(MAX_PATH);\n        char *new_path = cmalloc(MAX_PATH);\n        char *new_name = cmalloc(MAX_PATH);\n\n        struct file_list_entry *e =\n         (struct file_list_entry *)file_list[chosen_file];\n\n        snprintf(new_name, MAX_PATH, \"%s\", e->filename);\n\n        if(!confirm_input(mzx_world, \"Rename File\", \"New file name:\", new_name))\n        {\n          path_join(old_path, MAX_PATH, current_dir_name, e->filename);\n          path_join(new_path, MAX_PATH, current_dir_name, new_name);\n\n          if(strcmp(old_path, new_path))\n            if(vrename(old_path, new_path))\n              error(\"File rename failed.\",\n               ERROR_T_WARNING, ERROR_OPT_OK, 0x0000);\n\n        }\n\n        free(old_path);\n        free(new_path);\n        free(new_name);\n        break;\n      }\n\n      // Delete directory\n      case 6:\n      {\n        if(strcmp(dir_list[chosen_dir], PATH_PARENT_DIR) &&\n         strcmp(dir_list[chosen_dir], PATH_CURRENT_DIR) &&\n         chosen_dir < volumes_pos)\n        {\n          char confirm_string[70];\n          snprintf(confirm_string, 70, \"Delete %s: are you sure?\",\n           dir_list[chosen_dir]);\n          confirm_string[69] = 0;\n\n          if(!confirm(mzx_world, confirm_string))\n          {\n            char file_name_ch[MAX_PATH];\n            path_join(file_name_ch, MAX_PATH, current_dir_name,\n             dir_list[chosen_dir]);\n\n            if(!ask_yes_no(mzx_world,\n             (char *)\"Delete subdirectories recursively?\"))\n            {\n              if(!remove_files(file_name_ch, true) || vrmdir(file_name_ch))\n                error(\"Directory could not be deleted.\",\n                 ERROR_T_WARNING, ERROR_OPT_OK, 0x0000);\n            }\n            else\n            {\n              if(!remove_files(file_name_ch, false) || vrmdir(file_name_ch))\n                error(\"Directory contains files or could not be deleted.\",\n                 ERROR_T_WARNING, ERROR_OPT_OK, 0x0000);\n            }\n          }\n        }\n        break;\n      }\n\n      // Rename directory\n      case 7:\n      {\n        if(strcmp(dir_list[chosen_dir], PATH_PARENT_DIR) &&\n         strcmp(dir_list[chosen_dir], PATH_CURRENT_DIR) &&\n         chosen_dir < volumes_pos)\n        {\n          char *old_path = cmalloc(MAX_PATH);\n          char *new_path = cmalloc(MAX_PATH);\n          char *new_name = cmalloc(MAX_PATH);\n\n          snprintf(new_name, MAX_PATH, \"%s\", dir_list[chosen_dir]);\n          new_name[MAX_PATH - 1] = '\\0';\n\n          if(!confirm_input(mzx_world, \"Rename Directory\",\n           \"New directory name:\", new_name))\n          {\n            path_join(old_path, MAX_PATH, current_dir_name, dir_list[chosen_dir]);\n            path_join(new_path, MAX_PATH, current_dir_name, new_name);\n\n            if(strcmp(old_path, new_path))\n              if(vrename(old_path, new_path))\n                error(\"Directory rename failed.\",\n                 ERROR_T_WARNING, ERROR_OPT_OK, 0x0000);\n\n          }\n\n          free(old_path);\n          free(new_path);\n          free(new_name);\n        }\n        break;\n      }\n    }\n\n    // Filter out paths that violate the allowed directories mode.\n    if(allow_dirs != ALLOW_ALL_DIRS)\n    {\n      size_t base_dir_len = strlen(base_dir_name);\n\n      // If the base path isn't part of the return path\n      if(strncmp(base_dir_name, current_dir_name,  base_dir_len) ||\n       strstr(current_dir_name, \"..\") ||\n\n      // or if there's an unallowed subdirectory\n       (allow_dirs == NO_DIRS && path_has_directory(current_dir_name + base_dir_len)))\n      {\n        memcpy(current_dir_name, base_dir_name, base_dir_len + 1);\n        return_value = 1;\n        ret[0] = 0;\n      }\n      else\n\n      // If the base dir and return dir are the same and the selected path is\n      // prefixed with the base dir, make it a relative path. This is necessary\n      // for files selected for use in a game from the editor.\n      // TODO should maybe do this regardless of the return path. The only\n      // thing that would be affected is probably GLSL shader selection.\n      if(return_dir_is_base_dir)\n      {\n        path_remove_prefix(ret, MAX_PATH, base_dir_name, base_dir_len);\n      }\n    }\n\n    // Hack - don't allow the added elements to be destroyed\n    di.num_elements = FILESEL_BASE_ELEMENTS;\n    last_element = di.current_element;\n\n    for(i = 0; i < total_filenames_allocated; i++)\n    {\n      if(file_list[i])\n        free(file_list[i]);\n    }\n    free(file_list);\n\n    for(i = 0; i < total_dirnames_allocated; i++)\n    {\n      if(dir_list[i])\n        free(dir_list[i]);\n    }\n    free(dir_list);\n\n    destruct_dialog(&di);\n  }\n\n  // Now we can destroy the additional elements\n  for(i = 0; i < num_ext; i++)\n  {\n    free(dialog_ext[i]);\n  }\n\n  if(return_value == -1)\n  {\n    ret[0] = 0;\n  }\n\n  free(base_dir_name);\n  free(return_dir_name);\n  free(current_dir_name);\n  free(file_name);\n\n  return return_value;\n}\n\nint choose_file_ch(struct world *mzx_world, const char *const *wildcards,\n char *ret, const char *title, enum allow_dirs allow_dirs)\n{\n  return file_manager(mzx_world, wildcards, NULL, ret, title, allow_dirs,\n   NO_NEW_FILES, NULL, 0, 0);\n}\n\nint new_file(struct world *mzx_world, const char *const *wildcards,\n const char *default_ext, char *ret, const char *title, enum allow_dirs allow_dirs)\n{\n  return file_manager(mzx_world, wildcards, default_ext, ret, title, allow_dirs,\n   ALLOW_NEW_FILES, NULL, 0, 0);\n}\n\n// Calculates the percent from progress and out_of as in (progress/out_of).\nvoid meter(const char *title, unsigned int progress, unsigned int out_of)\n{\n  int titlex = 40 - ((int)strlen(title) >> 1);\n\n  assert(titlex > 0);\n\n  draw_window_box(5, 10, 74, 12, DI_MAIN, DI_DARK, DI_CORNER, 1, 1);\n  // Add title\n  write_string(title, titlex, 10, DI_TITLE, 0);\n  draw_char(' ', DI_TITLE, titlex - 1, 10);\n  draw_char(' ', DI_TITLE, titlex + (unsigned int)strlen(title), 10);\n  meter_interior(progress, out_of);\n}\n\n// Draws the meter but only the interior where the percent is.\nvoid meter_interior(unsigned int progress, unsigned int out_of)\n{\n  // The actual meter has 66 spaces, or 132 half spaces, to use, so barsize is\n  // the number of half spaces to display.\n  unsigned int barsize = progress * 132ULL / out_of;\n  unsigned int percent = progress * 100ULL / out_of;\n  int revcolor = ((DI_METER & 15) << 4) + (DI_METER >> 4);\n  char percentstr[5];\n\n  assert(progress <= out_of);\n\n  fill_line(66, 7, 11, 32, revcolor);\n  // Draw half-space if appropriate\n  if(barsize & 1)\n    draw_char('\\xDD', DI_METER, 7 + (barsize >> 1), 11);\n\n  // Determine percentage\n  snprintf(percentstr, 5, \"%3u%%\", percent);\n  write_string(percentstr, 37, 11, DI_METER, 1);\n\n  // Fill in meter\n  if(barsize > 1)\n    color_line(barsize >> 1, 7, 11, revcolor);\n  if(barsize < 131)\n    color_line((133 - barsize) >> 1, 7 + (barsize >> 1), 11, DI_METER);\n\n  // Done! :)\n}\n"
  },
  {
    "path": "src/window.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/* WINDOW.H- Declarations for WINDOW.CPP */\n\n#ifndef __WINDOW_H\n#define __WINDOW_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"world_struct.h\"\n\n// For name seeking in list_menu\n#define TIME_SUSPEND 500\n\n// All screen-affecting code preserves the mouse cursor\nCORE_LIBSPEC int save_screen(void);\nCORE_LIBSPEC int restore_screen(void);\nCORE_LIBSPEC int draw_window_box(int x1, int y1, int x2, int y2, int color,\n int dark_color, int corner_color, boolean shadow, boolean fill_center);\nCORE_LIBSPEC int char_selection(int current);\n\n// Shell for run_dialog() (returns 0 for ok, 1 for cancel, -1 for ESC)\n// Confirm_input is the same but takes an additional label name and input/output str\nCORE_LIBSPEC int confirm(struct world *mzx_world, const char *str);\nCORE_LIBSPEC int confirm_input(struct world *mzx_world, const char *title,\n const char *label, char *str);\nCORE_LIBSPEC int ask_yes_no(struct world *mzx_world, char *str);\n\nint draw_window_box_ext(int x1, int y1, int x2, int y2, int color,\n int dark_color, int corner_color, boolean shadow, boolean fill_center,\n int offset, int c_offset);\n\n// Dialog box structure definition\n\nstruct dialog\n{\n  int x, y;\n  int width, height;\n  const char *title;\n  char num_elements;\n  struct element **elements;\n  int current_element;\n  int done;\n  int return_value;\n  int sfx_test_for_input;\n  int pad_space;\n  int (* idle_function)(struct world *mzx_world, struct dialog *di, int key);\n};\n\nstruct element\n{\n  int x, y;\n  int width, height;\n  void (* draw_function)(struct world *mzx_world, struct dialog *di,\n   struct element *e, int color, int active);\n  int (* key_function)(struct world *mzx_world, struct dialog *di,\n   struct element *e, int key);\n  int (* click_function)(struct world *mzx_world, struct dialog *di,\n   struct element *e, int mouse_button, int mouse_x, int mouse_y,\n   int new_active);\n  int (* drag_function)(struct world *mzx_world, struct dialog *di,\n   struct element *e, int mouse_button, int mouse_x, int mouse_y);\n  int (* idle_function)(struct world *mzx_world, struct dialog *di,\n   struct element *e);\n};\n\nstruct label_element\n{\n  struct element e;\n  const char *text;\n  boolean respect_colors;\n};\n\nstruct box\n{\n  struct element e;\n};\n\nenum align\n{\n  vertical,\n  horizontal\n};\n\nstruct line\n{\n  struct element e;\n  enum align alignment;\n};\n\nstruct input_box\n{\n  struct element e;\n  const char *question;\n  int max_length;\n  char *result;\n};\n\nstruct check_box\n{\n  struct element e;\n  const char **choices;\n  int num_choices;\n  int current_choice;\n  int max_length;\n  int *results;\n};\n\nstruct radio_button\n{\n  struct element e;\n  const char **choices;\n  int num_choices;\n  int max_length;\n  int *result;\n};\n\nstruct char_box\n{\n  struct element e;\n  const char *question;\n  int allow_char_255;\n  int *result;\n};\n\nstruct color_box\n{\n  struct element e;\n  const char *question;\n  int allow_wildcard;\n  int *result;\n};\n\nstruct button\n{\n  struct element e;\n  const char *label;\n  int return_value;\n};\n\nenum number_box_type\n{\n  NUMBER_BOX,\n  NUMBER_BOX_MULT_FIVE,\n  NUMBER_LINE,\n  NUMBER_SLIDER\n};\n\nstruct number_box\n{\n  struct element e;\n  const char *question;\n  int lower_limit;\n  int upper_limit;\n  enum number_box_type type;\n  boolean is_null;\n  int *result;\n};\n\nstruct list_box\n{\n  struct element e;\n  int num_choices;\n  int num_choices_visible;\n  int choice_length;\n  int return_value;\n  const char **choices;\n  int *result;\n  int *result_offset;\n  int scroll_offset;\n  char key_buffer[64];\n  int key_position;\n  int last_keypress_time;\n  int clicked_scrollbar;\n  boolean respect_color_codes;\n};\n\nstruct board_list\n{\n  struct element e;\n  const char *title;\n  int board_zero_as_none;\n  int *result;\n};\n\nstruct slot_selector\n{\n  struct element e;\n  const char *title;\n  int num_slots;\n  boolean *highlighted_slots;\n  boolean *disabled_slots;\n  int selected_slot;\n  int save;\n};\n\nstruct file_selector\n{\n  struct element e;\n  const char *title;\n  const char *file_manager_title;\n  const char *const *file_manager_exts;\n  const char *base_path;\n  const char *none_mesg;\n  int allow_unset;\n  int return_value;\n  char *result;\n};\n\nenum allow_dirs\n{\n  NO_DIRS,\n  ALLOW_ALL_DIRS,\n  ALLOW_SUBDIRS,\n};\n\nenum allow_new\n{\n  NO_NEW_FILES,\n  ALLOW_NEW_FILES,\n  ALLOW_WILDCARD_FILES,\n};\n\nCORE_LIBSPEC int input_window(struct world *mzx_world, const char *title,\n char *buffer, int max_len);\n\nCORE_LIBSPEC void construct_dialog(struct dialog *src, const char *title,\n int x, int y, int width, int height, struct element **elements,\n int num_elements, int start_element);\nCORE_LIBSPEC void destruct_dialog(struct dialog *src);\n\nCORE_LIBSPEC struct element *construct_label(int x, int y, const char *text);\nCORE_LIBSPEC struct element *construct_radio_button(int x, int y,\n const char **choices, int num_choices, int max_length, int *result);\nCORE_LIBSPEC struct element *construct_button(int x, int y, const char *label,\n int return_value);\nCORE_LIBSPEC struct element *construct_slot_selector(int x, int y,\n const char *title, int num_slots, boolean *highlighted_slots,\n boolean *disabled_slots, int default_slot, int save);\nCORE_LIBSPEC struct element *construct_number_box(int x, int y,\n const char *question, int lower_limit, int upper_limit,\n enum number_box_type type, int *result);\nCORE_LIBSPEC struct element *construct_file_selector(int x, int y,\n const char *title, const char *file_manager_title,\n const char *const *file_manager_exts, const char *none_mesg,\n int show_width, int allow_unset, const char *base_path, char *result,\n int return_value);\nCORE_LIBSPEC struct element *construct_list_box(int x, int y,\n const char **choices, int num_choices, int num_choices_visible,\n int choice_length, int return_value, int *result, int *result_offset,\n boolean respect_color_codes);\n\nCORE_LIBSPEC int choose_file_ch(struct world *mzx_world,\n const char *const *wildcards, char *ret, const char *title,\n enum allow_dirs allow_dirs);\nCORE_LIBSPEC int new_file(struct world *mzx_world,\n const char *const *wildcards, const char *default_ext, char *ret,\n const char *title, enum allow_dirs allow_dirs);\n\nCORE_LIBSPEC void meter(const char *title, unsigned int progress,\n unsigned int out_of);\nCORE_LIBSPEC void meter_interior(unsigned int progress, unsigned int out_of);\n\n// Dialog box color #define's-\n#define DI_MAIN             31\n#define DI_DARK             16\n#define DI_CORNER           25\n#define DI_TITLE            31\n#define DI_LINE             16\n#define DI_TEXT             27\n#define DI_TEXT_GREY        23\n#define DI_NONACTIVE        25\n#define DI_ACTIVE           31\n#define DI_INPUT            159\n#define DI_CHAR             159\n#define DI_NUMERIC          159\n#define DI_LIST             159\n#define DI_BUTTON           176\n#define DI_ACTIVEBUTTON     252\n#define DI_ARROWBUTTON      249\n#define DI_METER            159\n#define DI_PCARROW          30\n#define DI_PCFILLER         25\n#define DI_PCDOT            144\n#define DI_ACTIVELIST       249\n#define DI_SEMIACTIVELIST   159\n\n#define DI_GREY             143\n#define DI_GREY_DARK        128\n#define DI_GREY_CORNER      135\n#define DI_GREY_TEXT        143\n#define DI_GREY_NUMBER      142\n#define DI_GREY_EDIT        126\n\n#define DI_INPUT_BOX        76\n#define DI_INPUT_BOX_DARK   64\n#define DI_INPUT_BOX_CORNER 70\n#define DI_INPUT_BOX_LABEL  78\n\n#define DI_DEBUG_BOX          DI_INPUT_BOX\n#define DI_DEBUG_BOX_DARK     DI_INPUT_BOX_DARK\n#define DI_DEBUG_BOX_CORNER   DI_INPUT_BOX_CORNER\n#define DI_DEBUG_LABEL        DI_INPUT_BOX_LABEL\n#define DI_DEBUG_NUMBER       79\n\n#define DI_SLOTSEL_NORMAL     8\n#define DI_SLOTSEL_HIGHLIGHT  10\n#define DI_SLOTSEL_DISABLE    128\n\n#define SLOTSEL_OK_RESULT           0\n#define SLOTSEL_CANCEL_RESULT       -1\n#define SLOTSEL_FILE_MANAGER_RESULT -2\n\n#define arrow_char '\\x10'\n#define pc_top_arrow '\\x1E'\n#define pc_bottom_arrow '\\x1F'\n#define pc_filler '\\xB1'\n#define pc_dot '\\xFE'\n#define pc_meter 219\n\nCORE_LIBSPEC int run_dialog(struct world *mzx_world, struct dialog *di);\nCORE_LIBSPEC int slot_manager(struct world *mzx_world, char *ret,\n const char *title, boolean save);\n\n#ifdef CONFIG_EDITOR\nCORE_LIBSPEC void construct_element(struct element *e, int x, int y,\n int width, int height,\n void (* draw_function)(struct world *mzx_world, struct dialog *di,\n  struct element *e, int color, int active),\n int (* key_function)(struct world *mzx_world, struct dialog *di,\n  struct element *e, int key),\n int (* click_function)(struct world *mzx_world, struct dialog *di,\n  struct element *e, int mouse_button, int mouse_x, int mouse_y,\n  int new_active),\n int (* drag_function)(struct world *mzx_world, struct dialog *di,\n  struct element *e, int mouse_button, int mouse_x, int mouse_y),\n int (* idle_function)(struct world *mzx_world, struct dialog *di,\n  struct element *e));\nCORE_LIBSPEC struct element *construct_input_box(int x, int y,\n const char *question, int max_length, char *result);\nCORE_LIBSPEC void construct_dialog_ext(struct dialog *src, const char *title,\n int x, int y, int width, int height, struct element **elements,\n int num_elements, int sfx_test_for_input, int pad_space, int start_element,\n int (* idle_function)(struct world *mzx_world, struct dialog *di, int key));\n\nCORE_LIBSPEC int char_selection_ext(int current, int allow_char_255,\n int *width_ptr, int *height_ptr, int *charset, int selection_pal);\nCORE_LIBSPEC int char_select_next_tile(int current_char,\n int direction, int highlight_width, int highlight_height);\n\nCORE_LIBSPEC int file_manager(struct world *mzx_world,\n const char *const *wildcards, const char *default_ext, char *ret,\n const char *title, enum allow_dirs allow_dirs, enum allow_new allow_new,\n struct element **dialog_ext, int num_ext, int ext_height);\n#endif // CONFIG_EDITOR\n\n__M_END_DECLS\n\n#endif // __WINDOW_H\n"
  },
  {
    "path": "src/world.c",
    "content": "/* MegaZeux\n *\n * Copyright (C) 1996 Alexis Janson\n * Copyright (C) 1999 Charles Goetzman\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n * Copyright (C) 2017-2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdint.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <assert.h>\n#include <sys/stat.h>\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#include \"world.h\"\n#include \"world_format.h\"\n#include \"legacy_world.h\"\n\n#include \"board.h\"\n#include \"configure.h\"\n#include \"const.h\"\n#include \"counter.h\"\n#include \"data.h\"\n#include \"error.h\"\n#include \"event.h\"\n#include \"extmem.h\"\n#include \"game_player.h\"\n#include \"graphics.h\"\n#include \"idput.h\"\n#include \"robot.h\"\n#include \"sprite.h\"\n#include \"str.h\"\n#include \"util.h\"\n#include \"window.h\"\n#include \"io/fsafeopen.h\"\n#include \"io/memfile.h\"\n#include \"io/path.h\"\n#include \"io/vio.h\"\n#include \"io/zip.h\"\n\n#include \"audio/audio.h\"\n#include \"audio/sfx.h\"\n\n#ifdef CONFIG_LOADSAVE_METER\n\n#include \"platform.h\"\n\nstatic uint32_t last_ticks;\n\nvoid meter_update_screen(int *curr, int target)\n{\n  uint32_t ticks = get_ticks();\n\n  (*curr)++;\n  meter_interior(*curr, target);\n\n  // Draw updates to the screen at roughly the UI framerate\n  if(ticks - last_ticks > UPDATE_DELAY)\n  {\n    update_screen();\n    last_ticks = get_ticks();\n  }\n}\n\nvoid meter_restore_screen(void)\n{\n  restore_screen();\n  update_screen();\n}\n\nvoid meter_initial_draw(int curr, int target, const char *title)\n{\n  save_screen();\n  meter(title, curr, target);\n  update_screen();\n  last_ticks = get_ticks();\n}\n\n#else //!CONFIG_LOADSAVE_METER\n\nstatic inline void meter_update_screen(int *curr, int target) {}\nstatic inline void meter_restore_screen(void) {}\nstatic inline void meter_initial_draw(int curr, int target, const char *title) {}\n\n#endif //CONFIG_LOADSAVE_METER\n\n\nint world_magic(const char magic_string[3])\n{\n  if(magic_string[0] == 'M')\n  {\n    if(magic_string[1] == 'Z')\n    {\n      switch(magic_string[2])\n      {\n        case 'X':\n          return V100;\n        case '2':\n          return V200;\n        case 'A':\n          return V251s1;\n      }\n    }\n    else\n    {\n      // I hope to God that MZX doesn't last beyond 9.x\n      if((magic_string[1] > 1) && (magic_string[1] < 10))\n        return ((int)magic_string[1] << 8) + (int)magic_string[2];\n    }\n  }\n\n  return 0;\n}\n\nint save_magic(const char magic_string[5])\n{\n  if((magic_string[0] == 'M') && (magic_string[1] == 'Z'))\n  {\n    switch(magic_string[2])\n    {\n      case 'S':\n        if((magic_string[3] == 'A') && (magic_string[4] == 'V'))\n        {\n          return V100;\n        }\n        else\n\n        if((magic_string[3] == 'V') && (magic_string[4] == '2'))\n        {\n          return V251;\n        }\n        else\n\n        if((magic_string[3] >= 2) && (magic_string[3] <= 10))\n        {\n          return ((int)magic_string[3] << 8) + magic_string[4];\n        }\n        return 0;\n\n      case 'X':\n        if((magic_string[3] == 'S') && (magic_string[4] == 'A'))\n        {\n          return V251s1;\n        }\n        return 0;\n\n      default:\n        return 0;\n    }\n  }\n  else\n  {\n    return 0;\n  }\n}\n\nint get_version_string(char buffer[16], enum mzx_version version)\n{\n  switch(version)\n  {\n    case V100:\n      sprintf(buffer, \"1.xx\");\n      break;\n\n    case V251:\n      sprintf(buffer, \"2.xx/2.51\");\n      break;\n\n    case V251s1:\n      sprintf(buffer, \"2.51s1\");\n      break;\n\n    case V251s2: // through V261\n      sprintf(buffer, \"2.51s2/2.61\");\n      break;\n\n    case VERSION_DECRYPT:\n      sprintf(buffer, \"<decrypted>\");\n      break;\n\n    case V262:\n      sprintf(buffer, \"2.62/2.62b\");\n      break;\n\n    case V265: // Also 2.68 because the magic wasn't actually incremented.\n      sprintf(buffer, \"2.65/2.68\");\n      break;\n\n    case V268:\n      sprintf(buffer, \"2.68\");\n      break;\n\n    case V269:\n      sprintf(buffer, \"2.69\");\n      break;\n\n    case V269b:\n      sprintf(buffer, \"2.69b\");\n      break;\n\n    case V269c:\n      sprintf(buffer, \"2.69c\");\n      break;\n\n    case V270:\n      sprintf(buffer, \"2.70\");\n      break;\n\n    default:\n    {\n      if(version < VERSION_PORT)\n      {\n        sprintf(buffer, \"<unknown %4.4Xh>\", version);\n      }\n\n      else\n      {\n        buffer[11] = 0;\n        snprintf(buffer, 11, \"%d.%2.2d\",\n         (version >> 8) & 0xFF, version & 0xFF);\n      }\n\n      break;\n    }\n  }\n\n  return strlen(buffer);\n}\n\n\n// World info\nstatic inline int save_world_info(struct world *mzx_world,\n struct zip_archive *zp, boolean savegame, int file_version,\n const char *name)\n{\n  enum mzx_version world_version = mzx_world->version;\n\n  char *buffer;\n  size_t buf_size = WORLD_PROP_SIZE;\n  struct memfile _mf;\n  struct memfile _prop;\n  struct memfile *mf = &_mf;\n  struct memfile *prop = &_prop;\n  size_t size;\n  int i;\n\n  int result;\n\n  if(savegame)\n    buf_size += SAVE_PROP_SIZE;\n\n  buffer = cmalloc(buf_size);\n\n  mfopen_wr(buffer, buf_size, mf);\n\n  // Save everything sorted.\n\n  // Header redundant properties\n  save_prop_s_293(WPROP_WORLD_NAME, mzx_world->name, BOARD_NAME_SIZE, mf);\n  save_prop_w(WPROP_FILE_VERSION, file_version, mf);\n\n  if(savegame)\n  {\n    save_prop_w(WPROP_WORLD_VERSION, world_version, mf);\n    save_prop_c(WPROP_SAVE_START_BOARD, mzx_world->current_board_id, mf);\n    save_prop_c(WPROP_SAVE_TEMPORARY_BOARD, mzx_world->temporary_board, mf);\n  }\n  else\n  {\n    save_prop_w(WPROP_WORLD_VERSION, file_version, mf);\n  }\n\n  save_prop_c(WPROP_NUM_BOARDS,         mzx_world->num_boards, mf);\n\n  // ID Chars\n  save_prop_a(WPROP_ID_CHARS,           id_chars, ID_CHARS_SIZE, 1, mf);\n  save_prop_c(WPROP_ID_MISSILE_COLOR,   missile_color, mf);\n  save_prop_a(WPROP_ID_BULLET_COLOR,    bullet_color, ID_BULLET_COLOR_SIZE, 1, mf);\n  save_prop_a(WPROP_ID_DMG,             id_dmg, ID_DMG_SIZE, 1, mf);\n\n  // Status counters\n  if(file_version >= V293)\n  {\n    char counters[STATCTR_PROP_SIZE];\n    mfopen_wr(counters, sizeof(counters), prop);\n\n    for(i = 0; i < NUM_STATUS_COUNTERS; i++)\n    {\n      char *ctr = mzx_world->status_counters_shown[i];\n      if(ctr[0])\n      {\n        save_prop_c(STATCTRPROP_SET_ID, i, prop);\n        save_prop_s(STATCTRPROP_NAME, ctr, prop);\n      }\n    }\n    save_prop_eof(prop);\n    save_prop_a(WPROP_STATUS_COUNTERS, counters, mftell(prop), 1, mf);\n  }\n  else\n  {\n    save_prop_v(WPROP_STATUS_COUNTERS, COUNTER_NAME_SIZE * NUM_STATUS_COUNTERS,\n     prop, mf);\n\n    for(i = 0; i < NUM_STATUS_COUNTERS; i++)\n      mfwrite(mzx_world->status_counters_shown[i], COUNTER_NAME_SIZE, 1, prop);\n  }\n\n  // Global properties\n  save_prop_c(WPROP_EDGE_COLOR,         mzx_world->edge_color, mf);\n  save_prop_c(WPROP_FIRST_BOARD,        mzx_world->first_board, mf);\n  save_prop_c(WPROP_ENDGAME_BOARD,      mzx_world->endgame_board, mf);\n  save_prop_c(WPROP_DEATH_BOARD,        mzx_world->death_board, mf);\n  save_prop_w(WPROP_ENDGAME_X,          mzx_world->endgame_x, mf);\n  save_prop_w(WPROP_ENDGAME_Y,          mzx_world->endgame_y, mf);\n  save_prop_c(WPROP_GAME_OVER_SFX,      mzx_world->game_over_sfx, mf);\n  save_prop_w(WPROP_DEATH_X,            mzx_world->death_x, mf);\n  save_prop_w(WPROP_DEATH_Y,            mzx_world->death_y, mf);\n  save_prop_w(WPROP_STARTING_LIVES,     mzx_world->starting_lives, mf);\n  save_prop_w(WPROP_LIVES_LIMIT,        mzx_world->lives_limit, mf);\n  save_prop_w(WPROP_STARTING_HEALTH,    mzx_world->starting_health, mf);\n  save_prop_w(WPROP_HEALTH_LIMIT,       mzx_world->health_limit, mf);\n  save_prop_c(WPROP_ENEMY_HURT_ENEMY,   mzx_world->enemy_hurt_enemy, mf);\n  save_prop_c(WPROP_CLEAR_ON_EXIT,      mzx_world->clear_on_exit, mf);\n  save_prop_c(WPROP_ONLY_FROM_SWAP,     mzx_world->only_from_swap, mf);\n\n  // SMZX and vlayer are global properties 2.91+\n  if(savegame || world_version >= V291)\n  {\n    save_prop_c(WPROP_SMZX_MODE,        get_screen_mode(), mf);\n    save_prop_w(WPROP_VLAYER_WIDTH,     mzx_world->vlayer_width, mf);\n    save_prop_w(WPROP_VLAYER_HEIGHT,    mzx_world->vlayer_height, mf);\n    save_prop_d(WPROP_VLAYER_SIZE,      mzx_world->vlayer_size, mf);\n  }\n\n  if(savegame)\n  {\n    // Save properties\n    save_prop_s(WPROP_REAL_MOD_PLAYING, mzx_world->real_mod_playing, mf);\n\n    save_prop_c(WPROP_MZX_SPEED,        mzx_world->mzx_speed, mf);\n    save_prop_c(WPROP_LOCK_SPEED,       mzx_world->lock_speed, mf);\n    save_prop_d(WPROP_COMMANDS,         mzx_world->commands, mf);\n    save_prop_d(WPROP_COMMANDS_STOP,    mzx_world->commands_stop, mf);\n    save_prop_v(WPROP_SAVED_POSITIONS,  40, prop, mf);\n\n    for(i = 0; i < 8; i++)\n    {\n      mfputw(mzx_world->pl_saved_x[i], prop);\n      mfputw(mzx_world->pl_saved_y[i], prop);\n      mfputc(mzx_world->pl_saved_board[i], prop);\n    }\n\n    save_prop_v(WPROP_UNDER_PLAYER, 3, prop, mf);\n\n    mfputc(mzx_world->under_player_id, prop);\n    mfputc(mzx_world->under_player_color, prop);\n    mfputc(mzx_world->under_player_param, prop);\n\n    save_prop_w(WPROP_PLAYER_RESTART_X, mzx_world->player_restart_x, mf);\n    save_prop_w(WPROP_PLAYER_RESTART_Y, mzx_world->player_restart_y, mf);\n    save_prop_c(WPROP_SAVED_PL_COLOR,   mzx_world->saved_pl_color, mf);\n    save_prop_a(WPROP_KEYS,             mzx_world->keys, NUM_KEYS, 1, mf);\n    save_prop_d(WPROP_BLIND_DUR,        mzx_world->blind_dur, mf);\n    save_prop_d(WPROP_FIREWALKER_DUR,   mzx_world->firewalker_dur, mf);\n    save_prop_d(WPROP_FREEZE_TIME_DUR,  mzx_world->freeze_time_dur, mf);\n    save_prop_d(WPROP_SLOW_TIME_DUR,    mzx_world->slow_time_dur, mf);\n    save_prop_d(WPROP_WIND_DUR,         mzx_world->wind_dur, mf);\n    save_prop_c(WPROP_SCROLL_BASE_COLOR,    mzx_world->scroll_base_color, mf);\n    save_prop_c(WPROP_SCROLL_CORNER_COLOR,  mzx_world->scroll_corner_color, mf);\n    save_prop_c(WPROP_SCROLL_POINTER_COLOR, mzx_world->scroll_pointer_color, mf);\n    save_prop_c(WPROP_SCROLL_TITLE_COLOR,   mzx_world->scroll_title_color, mf);\n    save_prop_c(WPROP_SCROLL_ARROW_COLOR,   mzx_world->scroll_arrow_color, mf);\n    save_prop_c(WPROP_MESG_EDGES,       mzx_world->mesg_edges, mf);\n    save_prop_c(WPROP_BI_SHOOT_STATUS,  mzx_world->bi_shoot_status, mf);\n    save_prop_c(WPROP_BI_MESG_STATUS,   mzx_world->bi_mesg_status, mf);\n    save_prop_c(WPROP_FADED,            get_fade_status(), mf);\n\n    save_prop_s(WPROP_INPUT_FILE_NAME,  mzx_world->input_file_name, mf);\n    save_prop_d(WPROP_INPUT_POS,        mzx_world->temp_input_pos, mf);\n    save_prop_d(WPROP_FREAD_DELIMITER,  mzx_world->fread_delimiter, mf);\n\n    save_prop_s(WPROP_OUTPUT_FILE_NAME, mzx_world->output_file_name, mf);\n    save_prop_d(WPROP_OUTPUT_POS,       mzx_world->temp_output_pos, mf);\n    save_prop_d(WPROP_FWRITE_DELIMITER, mzx_world->fwrite_delimiter, mf);\n    if(file_version >= V293)\n      save_prop_c(WPROP_OUTPUT_MODE,    mzx_world->output_mode, mf);\n\n    save_prop_d(WPROP_MULTIPLIER,       mzx_world->multiplier, mf);\n    save_prop_d(WPROP_DIVIDER,          mzx_world->divider, mf);\n    save_prop_d(WPROP_C_DIVISIONS,      mzx_world->c_divisions, mf);\n    save_prop_d(WPROP_MAX_SAMPLES,      mzx_world->max_samples, mf);\n    save_prop_c(WPROP_SMZX_MESSAGE,     mzx_world->smzx_message, mf);\n    save_prop_c(WPROP_JOY_SIMULATE_KEYS,mzx_world->joystick_simulate_keys, mf);\n  }\n\n  save_prop_eof(mf);\n\n  size = mftell(mf);\n\n  result = zip_write_file(zp, name, buffer, size, ZIP_M_NONE);\n\n  free(buffer);\n  return result;\n}\n\n#define check(id) {                                                           \\\n  while(true)                                                                 \\\n  {                                                                           \\\n    boolean ret = next_prop(&prop, &ident, &size, &mf);                       \\\n    if(!ret || ident > id || ident == WPROP_EOF)                              \\\n    {                                                                         \\\n      missing_ident = id;                                                     \\\n      goto err_free;                                                          \\\n    }                                                                         \\\n    if(ident == id)                                                           \\\n      break;                                                                  \\\n  }                                                                           \\\n  last_ident = ident;                                                         \\\n}\n\nstatic inline enum val_result validate_world_info(struct world *mzx_world,\n struct zip_archive *zp, boolean savegame, int *file_version)\n{\n  int world_version;\n\n  char *buffer;\n  struct memfile mf;\n  struct memfile prop;\n  size_t actual_size;\n\n  int missing_ident;\n  int last_ident = -1;\n  int ident = 0;\n  int size = 0;\n  int info_file_version;\n\n  if(zip_get_next_uncompressed_size(zp, &actual_size) != ZIP_SUCCESS)\n    return VAL_INVALID;\n\n  buffer = cmalloc(actual_size);\n\n  zip_read_file(zp, buffer, actual_size, NULL);\n\n  mfopen(buffer, actual_size, &mf);\n\n  mzx_world->raw_world_info = buffer;\n  mzx_world->raw_world_info_size = actual_size;\n\n  // Get the file version out of order.\n  check(WPROP_FILE_VERSION);\n  info_file_version = load_prop_int(&prop);\n  if(*file_version)\n  {\n    if(*file_version != info_file_version)\n    {\n      warn(\"validate_world_info: header/internal file version mismatch: %04x %04x\\n\",\n       *file_version, info_file_version);\n      return VAL_VERSION;\n    }\n  }\n  else\n  {\n    // This should be reached by rearchived worlds only...\n    if(info_file_version < V290 || info_file_version > MZX_VERSION)\n    {\n      warn(\"validate_world_info: bad file version %04x\\n\", info_file_version);\n      return VAL_VERSION;\n    }\n    *file_version = info_file_version;\n  }\n\n  // Check everything else sorted.\n  check(WPROP_WORLD_VERSION);\n  world_version = load_prop_int(&prop);\n  if(world_version < V100 || world_version > *file_version)\n  {\n    warn(\"validate_world_info: bad compat version %04x (file: %04x)\\n\",\n     world_version, *file_version);\n    return VAL_VERSION;\n  }\n\n  // World data.\n  check(WPROP_NUM_BOARDS);\n  check(WPROP_ID_CHARS);\n  check(WPROP_ID_MISSILE_COLOR);\n  check(WPROP_ID_BULLET_COLOR);\n  check(WPROP_ID_DMG);\n  check(WPROP_STATUS_COUNTERS);\n  check(WPROP_EDGE_COLOR);\n  check(WPROP_FIRST_BOARD);\n  check(WPROP_ENDGAME_BOARD);\n  check(WPROP_DEATH_BOARD);\n  check(WPROP_ENDGAME_X);\n  check(WPROP_ENDGAME_Y);\n  check(WPROP_GAME_OVER_SFX);\n  check(WPROP_DEATH_X);\n  check(WPROP_DEATH_Y);\n  check(WPROP_STARTING_LIVES);\n  check(WPROP_LIVES_LIMIT);\n  check(WPROP_STARTING_HEALTH);\n  check(WPROP_HEALTH_LIMIT);\n  check(WPROP_ENEMY_HURT_ENEMY);\n  check(WPROP_CLEAR_ON_EXIT);\n  check(WPROP_ONLY_FROM_SWAP);\n\n  if(!savegame && world_version < V291)\n  {\n    return VAL_SUCCESS;\n  }\n\n  // World data (2.91+), save-only prior.\n  check(WPROP_SMZX_MODE);\n  check(WPROP_VLAYER_WIDTH);\n  check(WPROP_VLAYER_HEIGHT);\n  check(WPROP_VLAYER_SIZE);\n\n  if(!savegame)\n  {\n    return VAL_SUCCESS;\n  }\n\n  // Save-only data.\n  check(WPROP_REAL_MOD_PLAYING);\n  check(WPROP_MZX_SPEED);\n  check(WPROP_LOCK_SPEED);\n  check(WPROP_COMMANDS);\n  check(WPROP_COMMANDS_STOP);\n  check(WPROP_SAVED_POSITIONS);\n  check(WPROP_UNDER_PLAYER);\n  check(WPROP_PLAYER_RESTART_X);\n  check(WPROP_PLAYER_RESTART_Y);\n  check(WPROP_SAVED_PL_COLOR);\n  check(WPROP_KEYS);\n  check(WPROP_BLIND_DUR);\n  check(WPROP_FIREWALKER_DUR);\n  check(WPROP_FREEZE_TIME_DUR);\n  check(WPROP_SLOW_TIME_DUR);\n  check(WPROP_WIND_DUR);\n  check(WPROP_SCROLL_BASE_COLOR);\n  check(WPROP_SCROLL_CORNER_COLOR);\n  check(WPROP_SCROLL_POINTER_COLOR);\n  check(WPROP_SCROLL_TITLE_COLOR);\n  check(WPROP_SCROLL_ARROW_COLOR);\n  check(WPROP_MESG_EDGES);\n  check(WPROP_BI_SHOOT_STATUS);\n  check(WPROP_BI_MESG_STATUS);\n  check(WPROP_FADED);\n  check(WPROP_INPUT_FILE_NAME);\n  check(WPROP_INPUT_POS);\n  check(WPROP_FREAD_DELIMITER);\n  check(WPROP_OUTPUT_FILE_NAME);\n  check(WPROP_OUTPUT_POS);\n  check(WPROP_FWRITE_DELIMITER);\n  // ignore optional WPROP_OUTPUT_MODE (2.93)\n  check(WPROP_MULTIPLIER);\n  check(WPROP_DIVIDER);\n  check(WPROP_C_DIVISIONS);\n\n  if(world_version >= V291)\n  {\n    // Added in 2.91\n    check(WPROP_MAX_SAMPLES);\n    check(WPROP_SMZX_MESSAGE);\n  }\n\n  if(world_version >= V292)\n  {\n    // Added in 2.92\n    check(WPROP_JOY_SIMULATE_KEYS);\n  }\n\n  return VAL_SUCCESS;\n\nerr_free:\n  fprintf(mzxerr,\n   \"load_world_info: expected ID %xh not found (found %xh, last %xh)\\n\",\n   missing_ident, ident, last_ident);\n  fflush(mzxerr);\n\n  free(buffer);\n  mzx_world->raw_world_info = NULL;\n  mzx_world->raw_world_info_size = 0;\n  return VAL_INVALID;\n}\n\nstatic inline void load_status_counter_info(struct world *mzx_world,\n int *file_version, struct memfile *mf)\n{\n  struct memfile prop;\n  int ident;\n  int len;\n  size_t num = 0;\n  boolean load_properties = false;\n  int i;\n\n  if(*file_version >= V293)\n  {\n    // Allow old format status counters in 2.93 worlds for convenience.\n    // The old format is 90 bytes long and should fail the properties file check.\n    if(mf->end - mf->start != NUM_STATUS_COUNTERS * COUNTER_NAME_SIZE ||\n     check_properties_file(mf, STATCTRPROP_NAME))\n    {\n      load_properties = true;\n    }\n  }\n\n  if(load_properties)\n  {\n    while(next_prop(&prop, &ident, &len, mf))\n    {\n      switch(ident)\n      {\n        case STATCTRPROP_SET_ID:\n          num = load_prop_int(&prop);\n          break;\n\n        case STATCTRPROP_NAME:\n          if(num < ARRAY_SIZE(mzx_world->status_counters_shown))\n          {\n            len = MIN((size_t)len, sizeof(mzx_world->status_counters_shown[num]) - 1);\n            len = mfread(mzx_world->status_counters_shown[num], 1, len, &prop);\n            mzx_world->status_counters_shown[num][len] = '\\0';\n          }\n          break;\n      }\n    }\n  }\n  else\n  {\n    for(i = 0; i < NUM_STATUS_COUNTERS; i++)\n    {\n      mfread(mzx_world->status_counters_shown[i], COUNTER_NAME_SIZE, 1, mf);\n      mzx_world->status_counters_shown[i][COUNTER_NAME_SIZE - 1] = '\\0';\n    }\n  }\n}\n\n#define if_savegame         if(!savegame) { break; }\n#define if_savegame_or_291  if(!savegame && *file_version < V291) { break; }\n\nstatic inline void load_world_info(struct world *mzx_world,\n struct zip_archive *zp, boolean savegame, int *file_version, boolean *faded)\n{\n  char *buffer;\n  struct memfile _mf;\n  struct memfile _prop;\n  struct memfile *mf = &_mf;\n  struct memfile *prop = &_prop;\n  size_t actual_size;\n  int ident = -1;\n  int size;\n  int i;\n\n  // This should absolutely be set, but just in case it isn't...\n  if(!mzx_world->raw_world_info)\n  {\n    zip_get_next_uncompressed_size(zp, &actual_size);\n    buffer = cmalloc(actual_size);\n\n    zip_read_file(zp, buffer, actual_size, NULL);\n  }\n  else\n  {\n    zip_skip_file(zp);\n    buffer = mzx_world->raw_world_info;\n    actual_size = mzx_world->raw_world_info_size;\n    mzx_world->raw_world_info = NULL;\n    mzx_world->raw_world_info_size = 0;\n  }\n\n  mfopen(buffer, actual_size, mf);\n\n  while(next_prop(prop, &ident, &size, mf))\n  {\n    switch(ident)\n    {\n      case WPROP_EOF:\n        mfseek(mf, 0, SEEK_END);\n        break;\n\n      // Header redundant properties\n      case WPROP_WORLD_NAME:\n        size = MIN(size, BOARD_NAME_SIZE - 1);\n        mfread(mzx_world->name, size, 1, prop);\n        mzx_world->name[size] = '\\0';\n        break;\n\n      case WPROP_WORLD_VERSION:\n        // Already bounds checked.\n        mzx_world->version = load_prop_int(prop);\n        break;\n\n      case WPROP_FILE_VERSION:\n      {\n        // Already read this during validation.\n        break;\n      }\n\n      case WPROP_SAVE_START_BOARD:\n        if(savegame)\n        mzx_world->current_board_id = load_prop_int_u(prop, 0, MAX_BOARDS - 1);\n        break;\n\n      case WPROP_SAVE_TEMPORARY_BOARD:\n        if(savegame)\n        mzx_world->temporary_board = load_prop_boolean(prop);\n        break;\n\n      case WPROP_NUM_BOARDS:\n        mzx_world->num_boards = load_prop_int_u(prop, 1, MAX_BOARDS);\n        break;\n\n      // ID Chars\n      case WPROP_ID_CHARS:\n      {\n        if(size > ID_CHARS_SIZE)\n          size = ID_CHARS_SIZE;\n\n        mfread(id_chars, size, 1, prop);\n        memset(id_chars + size, 0, ID_CHARS_SIZE - size);\n        break;\n      }\n\n      case WPROP_ID_MISSILE_COLOR:\n      {\n        missile_color = load_prop_int(prop) & 255;\n        break;\n      }\n\n      case WPROP_ID_BULLET_COLOR:\n      {\n        if(size > ID_BULLET_COLOR_SIZE)\n          size = ID_BULLET_COLOR_SIZE;\n\n        mfread(bullet_color, size, 1, prop);\n        memset(bullet_color + size, 0, ID_BULLET_COLOR_SIZE - size);\n        break;\n      }\n\n      case WPROP_ID_DMG:\n      {\n        if(size > ID_DMG_SIZE)\n          size = ID_DMG_SIZE;\n\n        mfread(id_dmg, size, 1, prop);\n        memset(id_dmg + size, 0, ID_DMG_SIZE - size);\n        break;\n      }\n\n      // Status counters\n      case WPROP_STATUS_COUNTERS:\n        load_status_counter_info(mzx_world, file_version, prop);\n        break;\n\n      // Global properties\n      case WPROP_EDGE_COLOR:\n        mzx_world->edge_color = load_prop_int(prop) & 255;\n        break;\n\n      case WPROP_FIRST_BOARD:\n        mzx_world->first_board = load_prop_int_u(prop, 0, NO_BOARD);\n        break;\n\n      case WPROP_ENDGAME_BOARD:\n        mzx_world->endgame_board = load_prop_int_u(prop, 0, NO_BOARD);\n        break;\n\n      case WPROP_DEATH_BOARD:\n        mzx_world->death_board = load_prop_int_u(prop, 0, NO_BOARD);\n        break;\n\n      case WPROP_ENDGAME_X:\n        mzx_world->endgame_x = load_prop_int_u(prop, 0, 32767);\n        break;\n\n      case WPROP_ENDGAME_Y:\n        mzx_world->endgame_y = load_prop_int_u(prop, 0, 32767);\n        break;\n\n      case WPROP_GAME_OVER_SFX:\n        mzx_world->game_over_sfx = load_prop_boolean(prop);\n        break;\n\n      case WPROP_DEATH_X:\n        mzx_world->death_x = load_prop_int_u(prop, 0, 32767);\n        break;\n\n      case WPROP_DEATH_Y:\n        mzx_world->death_y = load_prop_int_u(prop, 0, 32767);\n        break;\n\n      case WPROP_STARTING_LIVES:\n        mzx_world->starting_lives = load_prop_int_u(prop, 0, 32767);\n        break;\n\n      case WPROP_LIVES_LIMIT:\n        mzx_world->lives_limit = load_prop_int_u(prop, 0, 32767);\n        break;\n\n      case WPROP_STARTING_HEALTH:\n        mzx_world->starting_health = load_prop_int_u(prop, 0, 32767);\n        break;\n\n      case WPROP_HEALTH_LIMIT:\n        mzx_world->health_limit = load_prop_int_u(prop, 0, 32767);\n        break;\n\n      case WPROP_ENEMY_HURT_ENEMY:\n        mzx_world->enemy_hurt_enemy = load_prop_boolean(prop);\n        break;\n\n      case WPROP_CLEAR_ON_EXIT:\n        mzx_world->clear_on_exit = load_prop_boolean(prop);\n        break;\n\n      case WPROP_ONLY_FROM_SWAP:\n        mzx_world->only_from_swap = load_prop_boolean(prop);\n        break;\n\n      // Global properties II\n      case WPROP_SMZX_MODE:\n        if_savegame_or_291\n        set_screen_mode(load_prop_int(prop));\n        break;\n\n      case WPROP_VLAYER_WIDTH:\n        if_savegame_or_291\n        mzx_world->vlayer_width = load_prop_int_u(prop, 0, 32767);\n        break;\n\n      case WPROP_VLAYER_HEIGHT:\n        if_savegame_or_291\n        mzx_world->vlayer_height = load_prop_int_u(prop, 0, 32767);\n        break;\n\n      case WPROP_VLAYER_SIZE:\n      {\n        unsigned int vlayer_size;\n        if_savegame_or_291\n\n        vlayer_size = load_prop_int(prop);\n        vlayer_size = CLAMP(vlayer_size, 1, MAX_BOARD_SIZE);\n\n        mzx_world->vlayer_size = vlayer_size;\n\n        mzx_world->vlayer_chars = crealloc(mzx_world->vlayer_chars, vlayer_size);\n        mzx_world->vlayer_colors = crealloc(mzx_world->vlayer_colors, vlayer_size);\n        break;\n      }\n\n      // Save properties\n      case WPROP_REAL_MOD_PLAYING:\n        if_savegame\n        size = MIN(size, MAX_PATH-1);\n        mfread(mzx_world->real_mod_playing, size, 1, prop);\n        mzx_world->real_mod_playing[size] = 0;\n        break;\n\n      case WPROP_MZX_SPEED:\n        if_savegame\n        mzx_world->mzx_speed = load_prop_int_u(prop, 1, 16);\n        break;\n\n      case WPROP_LOCK_SPEED:\n        if_savegame\n        mzx_world->lock_speed = load_prop_boolean(prop);\n        break;\n\n      case WPROP_COMMANDS:\n        if_savegame\n        mzx_world->commands = load_prop_int(prop);\n        break;\n\n      case WPROP_COMMANDS_STOP:\n        if_savegame\n        mzx_world->commands_stop = load_prop_int(prop);\n        break;\n\n      case WPROP_SAVED_POSITIONS:\n        if_savegame\n        if(size >= 40)\n        {\n          for(i = 0; i < 8; i++)\n          {\n            mzx_world->pl_saved_x[i] = mfgetw(prop);\n            mzx_world->pl_saved_y[i] = mfgetw(prop);\n            mzx_world->pl_saved_board[i] = mfgetc(prop);\n          }\n        }\n        break;\n\n      case WPROP_UNDER_PLAYER:\n        if_savegame\n        if(size >= 3)\n        {\n          mzx_world->under_player_id = mfgetc(prop);\n          mzx_world->under_player_color = mfgetc(prop);\n          mzx_world->under_player_param = mfgetc(prop);\n        }\n        break;\n\n      case WPROP_PLAYER_RESTART_X:\n        if_savegame\n        mzx_world->player_restart_x = load_prop_int_u(prop, 0, 32767);\n        break;\n\n      case WPROP_PLAYER_RESTART_Y:\n        if_savegame\n        mzx_world->player_restart_y = load_prop_int_u(prop, 0, 32767);\n        break;\n\n      case WPROP_SAVED_PL_COLOR:\n        if_savegame\n        mzx_world->saved_pl_color = load_prop_int(prop) & 255;\n        break;\n\n      case WPROP_KEYS:\n        if_savegame\n        mfread(mzx_world->keys, NUM_KEYS, 1, prop);\n        break;\n\n      case WPROP_BLIND_DUR:\n        if_savegame\n        mzx_world->blind_dur = load_prop_int(prop);\n        break;\n\n      case WPROP_FIREWALKER_DUR:\n        if_savegame\n        mzx_world->firewalker_dur = load_prop_int(prop);\n        break;\n\n      case WPROP_FREEZE_TIME_DUR:\n        if_savegame\n        mzx_world->freeze_time_dur = load_prop_int(prop);\n        break;\n\n      case WPROP_SLOW_TIME_DUR:\n        if_savegame\n        mzx_world->slow_time_dur = load_prop_int(prop);\n        break;\n\n      case WPROP_WIND_DUR:\n        if_savegame\n        mzx_world->wind_dur = load_prop_int(prop);\n        break;\n\n      case WPROP_SCROLL_BASE_COLOR:\n        if_savegame\n        mzx_world->scroll_base_color = load_prop_int(prop) & 255;\n        break;\n\n      case WPROP_SCROLL_CORNER_COLOR:\n        if_savegame\n        mzx_world->scroll_corner_color = load_prop_int(prop) & 255;\n        break;\n\n      case WPROP_SCROLL_POINTER_COLOR:\n        if_savegame\n        mzx_world->scroll_pointer_color = load_prop_int(prop) & 255;\n        break;\n\n      case WPROP_SCROLL_TITLE_COLOR:\n        if_savegame\n        mzx_world->scroll_title_color = load_prop_int(prop) & 255;\n        break;\n\n      case WPROP_SCROLL_ARROW_COLOR:\n        if_savegame\n        mzx_world->scroll_arrow_color = load_prop_int(prop) & 255;\n        break;\n\n      case WPROP_MESG_EDGES:\n        if_savegame\n        mzx_world->mesg_edges = load_prop_boolean(prop);\n        break;\n\n      case WPROP_BI_SHOOT_STATUS:\n        if_savegame\n        mzx_world->bi_shoot_status = load_prop_boolean(prop);\n        break;\n\n      case WPROP_BI_MESG_STATUS:\n        if_savegame\n        mzx_world->bi_mesg_status = load_prop_boolean(prop);\n        break;\n\n      case WPROP_FADED:\n        if_savegame\n        *faded = load_prop_boolean(prop);\n        break;\n\n      case WPROP_INPUT_FILE_NAME:\n        if_savegame\n        size = MIN(size, MAX_PATH - 1);\n        mfread(mzx_world->input_file_name, size, 1, prop);\n        mzx_world->input_file_name[size] = 0;\n        break;\n\n      case WPROP_INPUT_POS:\n        if_savegame\n        mzx_world->temp_input_pos = load_prop_int(prop);\n        break;\n\n      case WPROP_FREAD_DELIMITER:\n        if_savegame\n        mzx_world->fread_delimiter = load_prop_int(prop) & 255;\n        break;\n\n      case WPROP_OUTPUT_FILE_NAME:\n        if_savegame\n        size = MIN(size, MAX_PATH - 1);\n        mfread(mzx_world->output_file_name, size, 1, prop);\n        mzx_world->output_file_name[size] = 0;\n        break;\n\n      case WPROP_OUTPUT_POS:\n        if_savegame\n        mzx_world->temp_output_pos = load_prop_int(prop);\n        break;\n\n      case WPROP_FWRITE_DELIMITER:\n        if_savegame\n        mzx_world->fwrite_delimiter = load_prop_int(prop) & 255;\n        break;\n\n      case WPROP_OUTPUT_MODE:\n        if_savegame\n        if(*file_version >= V293)\n        {\n          int tmp = load_prop_int_u(prop, FWRITE_MODE_UNKNOWN, FWRITE_MODE_MAX);\n          mzx_world->output_mode =\n           tmp < FWRITE_MODE_MAX ? (enum fwrite_mode)tmp : FWRITE_MODE_UNKNOWN;\n        }\n        break;\n\n      case WPROP_MULTIPLIER:\n        if_savegame\n        mzx_world->multiplier = load_prop_int(prop);\n        break;\n\n      case WPROP_DIVIDER:\n        if_savegame\n        mzx_world->divider = load_prop_int(prop);\n        break;\n\n      case WPROP_C_DIVISIONS:\n        if_savegame\n        mzx_world->c_divisions = load_prop_int(prop);\n        break;\n\n      // Added in 2.91\n      case WPROP_MAX_SAMPLES:\n        if_savegame\n        if(mzx_world->version >= V291)\n          mzx_world->max_samples = load_prop_int(prop);\n        break;\n\n      case WPROP_SMZX_MESSAGE:\n        if_savegame\n        if(mzx_world->version >= V291)\n          mzx_world->smzx_message = load_prop_boolean(prop);\n        break;\n\n      // Added in 2.92\n      case WPROP_JOY_SIMULATE_KEYS:\n        if_savegame\n        if(mzx_world->version >= V292)\n          mzx_world->joystick_simulate_keys = load_prop_boolean(prop);\n        break;\n\n      default:\n        break;\n    }\n  }\n\n  free(buffer);\n}\n\n\n// Global robot\nstatic inline int save_world_global_robot(struct world *mzx_world,\n struct zip_archive *zp, boolean savegame, int file_version, const char *name)\n{\n  save_robot(mzx_world, &mzx_world->global_robot, zp, savegame,\n   file_version, name);\n\n  return 0;\n}\n\nstatic inline int load_world_global_robot(struct world *mzx_world,\n struct zip_archive *zp, boolean savegame, int file_version)\n{\n  load_robot(mzx_world, &mzx_world->global_robot, zp, savegame, file_version);\n  return 0;\n}\n\n\n// SFX\nstatic inline int save_world_sfx(struct world *mzx_world,\n struct zip_archive *zp, int file_version, const char *name)\n{\n  struct sfx_list *custom_sfx = &mzx_world->custom_sfx;\n\n  // Only save if custom SFX are enabled\n  if(custom_sfx->list)\n  {\n    char *tmp;\n    size_t size;\n    size_t required;\n    int ret;\n\n    size = sfx_save_to_memory(custom_sfx, file_version, NULL, 0, &required);\n    if(size || !required)\n      return -1;\n\n    tmp = (char *)cmalloc(required);\n    if(!tmp)\n      return -2;\n\n    size = sfx_save_to_memory(custom_sfx, file_version, tmp, required, NULL);\n    if(!size)\n    {\n      free(tmp);\n      return -3;\n    }\n\n    ret = zip_write_file(zp, name, tmp, size, ZIP_M_DEFLATE);\n    free(tmp);\n    return ret;\n  }\n\n  return ZIP_SUCCESS;\n}\n\nstatic inline int load_world_sfx(struct world *mzx_world,\n struct zip_archive *zp, int file_version)\n{\n  struct sfx_list *custom_sfx = &mzx_world->custom_sfx;\n\n  // No custom SFX loaded yet\n  if(!custom_sfx->list)\n  {\n    char *tmp;\n    size_t size;\n    int ret;\n\n    ret = zip_get_next_uncompressed_size(zp, &size);\n    if(ret != ZIP_SUCCESS)\n    {\n      zip_skip_file(zp);\n      return ret;\n    }\n\n    tmp = (char *)cmalloc(size);\n    if(!tmp)\n    {\n      zip_skip_file(zp);\n      return -1;\n    }\n\n    ret = zip_read_file(zp, tmp, size, NULL);\n    if(ret != ZIP_SUCCESS)\n    {\n      free(tmp);\n      return ret;\n    }\n\n    if(!sfx_load_from_memory(custom_sfx, file_version, tmp, size))\n    {\n      free(tmp);\n      return -2;\n    }\n\n    free(tmp);\n    return ZIP_SUCCESS;\n  }\n  // Already loaded custom SFX; skip\n  else\n  {\n    return zip_skip_file(zp);\n  }\n}\n\n\n// Charset\nstatic inline int save_world_chars(struct world *mzx_world,\n struct zip_archive *zp, boolean savegame, const char *name)\n{\n  // Save every charset\n  unsigned char *buffer;\n  size_t size = PROTECTED_CHARSET_POSITION;\n  int result;\n\n  buffer = cmalloc(size * CHAR_SIZE);\n  ec_mem_save_set_var(buffer, size * CHAR_SIZE, 0);\n\n  result = zip_write_file(zp, name, buffer, size * CHAR_SIZE, ZIP_M_DEFLATE);\n\n  free(buffer);\n  return result;\n}\n\nstatic inline int load_world_chars(struct world *mzx_world,\n struct zip_archive *zp, boolean savegame)\n{\n  unsigned char *buffer;\n  size_t actual_size;\n  int result;\n\n  zip_get_next_uncompressed_size(zp, &actual_size);\n  actual_size = MIN(actual_size, NUM_CHARSETS * CHAR_SIZE * CHARSET_SIZE);\n\n  buffer = cmalloc(actual_size);\n  result = zip_read_file(zp, buffer, actual_size, &actual_size);\n  if(result == ZIP_SUCCESS)\n  {\n    // Load only the first charset (2.90 worlds but not 2.90 saves)\n    if(mzx_world->version == V290 && !savegame)\n      if(actual_size > CHAR_SIZE * CHARSET_SIZE)\n        actual_size = CHAR_SIZE * CHARSET_SIZE;\n\n    ec_clear_set();\n    ec_mem_load_set(buffer, actual_size);\n  }\n\n  free(buffer);\n  return result;\n}\n\n\n// Palette\nstatic inline int save_world_pal(struct world *mzx_world,\n struct zip_archive *zp, int file_version, const char *name)\n{\n  unsigned char buffer[SMZX_PAL_SIZE * 3];\n  unsigned char *cur = buffer;\n  size_t size;\n  int i;\n\n  if(file_version >= V293)\n  {\n    // Always save the 16 color MZX palette, even in SMZX mode.\n    size = PAL_SIZE;\n    for(i = 0; i < PAL_SIZE; i++)\n    {\n      get_rgb_mzx(i, cur, cur+1, cur+2);\n      cur += 3;\n    }\n  }\n  else\n  {\n    // Prior versions store all 256 entries of the active palette.\n    size = SMZX_PAL_SIZE;\n    for(i = 0; i < SMZX_PAL_SIZE; i++)\n    {\n      get_rgb(i, cur, cur+1, cur+2);\n      cur += 3;\n    }\n  }\n  return zip_write_file(zp, name, buffer, size * 3, ZIP_M_NONE);\n}\n\nstatic inline int load_world_pal(struct world *mzx_world,\n struct zip_archive *zp, int file_version)\n{\n  unsigned char buffer[SMZX_PAL_SIZE * 3];\n  unsigned char *cur;\n  unsigned int i;\n  size_t size;\n  int result;\n  int mode = get_screen_mode();\n\n  result = zip_read_file(zp, buffer, SMZX_PAL_SIZE*3, &size);\n  if(result == ZIP_SUCCESS)\n  {\n    cur = buffer;\n    size /= 3;\n\n    for(i = 0; i < size; i++)\n    {\n      // In <2.93 files in SMZX mode, this stores the SMZX palette.\n      // Always load to the MZX palette anyway, even in SMZX mode in <2.93.\n      if(i < PAL_SIZE)\n        set_rgb_mzx(i, cur[0], cur[1], cur[2]);\n\n      if(mode >= 2 && file_version < V293)\n        set_rgb(i, cur[0], cur[1], cur[2]);\n      cur += 3;\n    }\n  }\n  return result;\n}\n\n\n// Palette (SMZX) (2.93+ only)\n// The MZX palette has already been loaded and SMZX is already enabled.\nstatic inline int save_world_pal_smzx(struct world *mzx_world,\n struct zip_archive *zp, const char *name)\n{\n  unsigned char buffer[SMZX_PAL_SIZE * 3];\n  unsigned char *cur = buffer;\n  int i;\n\n  /* Only save in 256 color SMZX modes. */\n  if(get_screen_mode() < 2)\n    return 0;\n\n  for(i = 0; i < SMZX_PAL_SIZE; i++)\n  {\n    get_rgb(i, cur, cur+1, cur+2);\n    cur += 3;\n  }\n\n  return zip_write_file(zp, name, buffer, SMZX_PAL_SIZE * 3, ZIP_M_DEFLATE);\n}\n\nstatic inline int load_world_pal_smzx(struct world *mzx_world,\n struct zip_archive *zp)\n{\n  unsigned char buffer[SMZX_PAL_SIZE * 3];\n  unsigned char *cur;\n  unsigned int i;\n  size_t size;\n  int result;\n\n  /* Only load in 256 color SMZX modes. */\n  if(get_screen_mode() < 2)\n    return 0;\n\n  result = zip_read_file(zp, buffer, SMZX_PAL_SIZE*3, &size);\n  if(result == ZIP_SUCCESS)\n  {\n    cur = buffer;\n    size /= 3;\n\n    for(i = 0; i < size; i++)\n    {\n      set_rgb(i, cur[0], cur[1], cur[2]);\n      cur += 3;\n    }\n  }\n  return result;\n}\n\n\n// Palette index\nstatic inline int save_world_pal_index(struct world *mzx_world,\n struct zip_archive *zp, const char *name)\n{\n  int result = ZIP_SUCCESS;\n\n  // Only save these while in the SMZX mode where they matter\n  if(get_screen_mode() == 3)\n  {\n    char *buffer = cmalloc(SMZX_PAL_SIZE * 4);\n    save_indices(buffer);\n\n    result = zip_write_file(zp, name, buffer, SMZX_PAL_SIZE * 4, ZIP_M_DEFLATE);\n\n    free(buffer);\n  }\n  return result;\n}\n\nstatic inline int load_world_pal_index(struct world *mzx_world,\n struct zip_archive *zp, int file_version)\n{\n  char *buffer = cmalloc(SMZX_PAL_SIZE * 4);\n  size_t actual_size;\n  int result;\n\n  result = zip_read_file(zp, buffer, SMZX_PAL_SIZE * 4, &actual_size);\n\n  if(result == ZIP_SUCCESS)\n  {\n    if(file_version >= V291)\n      load_indices(buffer, actual_size);\n\n    // 2.90 stored the internal indices instead of user-friendly indices\n    else\n      load_indices_direct(buffer, actual_size);\n  }\n\n  free(buffer);\n  return result;\n}\n\n\n/**\n * Palette intensities\n * As of MZX 2.93, this file contains the 16 32-bit MZX palette intensities.\n * Older versions stored the 256 active palette intensities as bytes.\n */\nstatic inline int save_world_pal_inten(struct world *mzx_world,\n struct zip_archive *zp, int file_version, const char *name)\n{\n  char buffer[SMZX_PAL_SIZE];\n  struct memfile mf;\n  int i;\n  size_t size;\n\n  mfopen_wr(buffer, sizeof(buffer), &mf);\n\n  if(file_version >= V293)\n  {\n    size = PAL_SIZE * 4;\n    for(i = 0; i < PAL_SIZE; i++)\n      mfputud(get_color_intensity_mzx(i), &mf);\n  }\n  else\n  {\n    size = SMZX_PAL_SIZE;\n    for(i = 0; i < SMZX_PAL_SIZE; i++)\n      mfputc(get_color_intensity(i), &mf);\n  }\n\n  return zip_write_file(zp, name, buffer, size, ZIP_M_NONE);\n}\n\nstatic inline int load_world_pal_inten(struct world *mzx_world,\n struct zip_archive *zp, int file_version)\n{\n  char buffer[SMZX_PAL_SIZE];\n  struct memfile mf;\n  size_t size;\n  unsigned int i;\n  int result;\n\n  result = zip_read_file(zp, buffer, SMZX_PAL_SIZE, &size);\n  if(result == ZIP_SUCCESS)\n  {\n    mfopen(buffer, size, &mf);\n\n    if(file_version >= V293)\n    {\n      size >>= 2;\n      for(i = 0; i < size; i++)\n        set_color_intensity_mzx(i, mfgetud(&mf));\n    }\n    else\n    {\n      for(i = 0; i < size; i++)\n        set_color_intensity(i, mfgetc(&mf));\n    }\n  }\n  return result;\n}\n\n\n/**\n * Palette intensities (SMZX) (2.93+ only)\n * This stores the 256 32-bit SMZX palette intensities when modes 2 or 3 are active.\n */\nstatic inline int save_world_pal_inten_smzx(struct world *mzx_world,\n struct zip_archive *zp, const char *name)\n{\n  char buffer[SMZX_PAL_SIZE * 4];\n  struct memfile mf;\n  int i;\n\n  /* Only save in 256 color SMZX modes. */\n  if(get_screen_mode() < 2)\n    return 0;\n\n  mfopen_wr(buffer, sizeof(buffer), &mf);\n\n  for(i = 0; i < SMZX_PAL_SIZE; i++)\n    mfputud(get_color_intensity(i), &mf);\n\n  return zip_write_file(zp, name, buffer, sizeof(buffer), ZIP_M_DEFLATE);\n}\n\nstatic inline int load_world_pal_inten_smzx(struct world *mzx_world,\n struct zip_archive *zp)\n{\n  char buffer[SMZX_PAL_SIZE * 4];\n  struct memfile mf;\n  size_t size;\n  unsigned int i;\n  int result;\n\n  /* Only load in 256 color SMZX modes. */\n  if(get_screen_mode() < 2)\n    return 0;\n\n  result = zip_read_file(zp, buffer, sizeof(buffer), &size);\n  if(result == ZIP_SUCCESS)\n  {\n    mfopen(buffer, size, &mf);\n    size >>= 2;\n\n    for(i = 0; i < size; i++)\n      set_color_intensity(i, mfgetud(&mf));\n  }\n  return result;\n}\n\n\n// Vlayer colors\nstatic inline int save_world_vco(struct world *mzx_world,\n struct zip_archive *zp, const char *name)\n{\n  return zip_write_file(zp, name,\n   mzx_world->vlayer_colors, mzx_world->vlayer_size, ZIP_M_DEFLATE);\n}\n\nstatic inline int load_world_vco(struct world *mzx_world,\n struct zip_archive *zp)\n{\n  int vlayer_size = mzx_world->vlayer_size;\n\n  return zip_read_file(zp, mzx_world->vlayer_colors, vlayer_size, NULL);\n}\n\n// Vlayer chars\nstatic inline int save_world_vch(struct world *mzx_world,\n struct zip_archive *zp, const char *name)\n{\n  return zip_write_file(zp, name,\n   mzx_world->vlayer_chars, mzx_world->vlayer_size, ZIP_M_DEFLATE);\n}\n\nstatic inline int load_world_vch(struct world *mzx_world,\n struct zip_archive *zp)\n{\n  int vlayer_size = mzx_world->vlayer_size;\n\n  return zip_read_file(zp, mzx_world->vlayer_chars, vlayer_size, NULL);\n}\n\n\n// Sprites\nstatic inline int save_world_sprites(struct world *mzx_world,\n struct zip_archive *zp, const char *name)\n{\n  char *buffer;\n  size_t collision_size = mzx_world->collision_count * 4;\n  size_t buf_size = SPRITE_PROPS_SIZE + collision_size;\n  struct sprite *spr;\n  struct memfile mf;\n  struct memfile prop;\n  int i;\n\n  buffer = cmalloc(buf_size);\n  mfopen_wr(buffer, buf_size, &mf);\n\n  // For each\n  for(i = 0; i < MAX_SPRITES; i++)\n  {\n    spr = mzx_world->sprite_list[i];\n\n    save_prop_c(SPROP_SET_ID,             i, &mf);\n    save_prop_d(SPROP_X,                  spr->x, &mf);\n    save_prop_d(SPROP_Y,                  spr->y, &mf);\n    save_prop_d(SPROP_REF_X,              spr->ref_x, &mf);\n    save_prop_d(SPROP_REF_Y,              spr->ref_y, &mf);\n    save_prop_d(SPROP_COLOR,              spr->color, &mf);\n    save_prop_d(SPROP_FLAGS,              spr->flags, &mf);\n    save_prop_d(SPROP_WIDTH,              spr->width, &mf);\n    save_prop_d(SPROP_HEIGHT,             spr->height, &mf);\n    save_prop_d(SPROP_COL_X,              spr->col_x, &mf);\n    save_prop_d(SPROP_COL_Y,              spr->col_y, &mf);\n    save_prop_d(SPROP_COL_WIDTH,          spr->col_width, &mf);\n    save_prop_d(SPROP_COL_HEIGHT,         spr->col_height, &mf);\n    save_prop_d(SPROP_TRANSPARENT_COLOR,  spr->transparent_color, &mf);\n    save_prop_d(SPROP_CHARSET_OFFSET,     spr->offset, &mf);\n    save_prop_d(SPROP_Z,                  spr->z, &mf);\n  }\n\n  // Only once\n  save_prop_d(SPROP_ACTIVE_SPRITES,       mzx_world->active_sprites, &mf);\n  save_prop_d(SPROP_SPRITE_Y_ORDER,       mzx_world->sprite_y_order, &mf);\n  save_prop_d(SPROP_COLLISION_COUNT,      mzx_world->collision_count, &mf);\n  save_prop_d(SPROP_SPRITE_NUM,           mzx_world->sprite_num, &mf);\n\n  // Collision list\n  save_prop_v(SPROP_COLLISION_LIST, collision_size, &prop, &mf);\n\n  for(i = 0; i < mzx_world->collision_count; i++)\n    mfputd(mzx_world->collision_list[i], &prop);\n\n  save_prop_eof(&mf);\n\n  zip_write_file(zp, name, buffer, buf_size, ZIP_M_DEFLATE);\n\n  free(buffer);\n  return 0;\n}\n\nstatic inline int load_world_sprites(struct world *mzx_world,\n struct zip_archive *zp)\n{\n  char *buffer;\n  size_t actual_size;\n\n  struct sprite *spr = NULL;\n  struct memfile mf;\n  struct memfile prop;\n  int ident;\n  int length;\n  int value;\n  int num_collisions = 0;\n\n  int result;\n\n  result = zip_get_next_uncompressed_size(zp, &actual_size);\n  if(result != ZIP_SUCCESS)\n    return result;\n\n  buffer = cmalloc(actual_size);\n\n  result = zip_read_file(zp, buffer, actual_size, NULL);\n  if(result != ZIP_SUCCESS)\n    goto err_free;\n\n  mfopen(buffer, actual_size, &mf);\n\n  while(next_prop(&prop, &ident, &length, &mf))\n  {\n    // Mostly numeric values here, and anything that isn't can seek back.\n    value = load_prop_int(&prop);\n\n    switch(ident)\n    {\n      case SPROP_EOF:\n        goto err_free;\n\n      case SPROP_SET_ID:\n        if(value >= 0 && value < MAX_SPRITES)\n          spr = mzx_world->sprite_list[value];\n        else\n          spr = NULL;\n        break;\n\n      case SPROP_X:\n        if(spr) spr->x = value;\n        break;\n\n      case SPROP_Y:\n        if(spr) spr->y = value;\n        break;\n\n      case SPROP_REF_X:\n        if(spr) spr->ref_x = value;\n        break;\n\n      case SPROP_REF_Y:\n        if(spr) spr->ref_y = value;\n        break;\n\n      case SPROP_COLOR:\n        if(spr) spr->color = value;\n        break;\n\n      case SPROP_FLAGS:\n        if(spr) spr->flags = value;\n        break;\n\n      case SPROP_WIDTH:\n        if(spr) spr->width = (unsigned int)value;\n        break;\n\n      case SPROP_HEIGHT:\n        if(spr) spr->height = (unsigned int)value;\n        break;\n\n      case SPROP_COL_X:\n        if(spr) spr->col_x = value;\n        break;\n\n      case SPROP_COL_Y:\n        if(spr) spr->col_y = value;\n        break;\n\n      case SPROP_COL_WIDTH:\n        if(spr) spr->col_width = (unsigned int)value;\n        break;\n\n      case SPROP_COL_HEIGHT:\n        if(spr) spr->col_height = (unsigned int)value;\n        break;\n\n      case SPROP_TRANSPARENT_COLOR:\n        if(spr) spr->transparent_color = value;\n        break;\n\n      case SPROP_CHARSET_OFFSET:\n        if(spr) spr->offset = value;\n        break;\n\n      case SPROP_Z:\n        if(mzx_world->version >= V292 && spr) spr->z = value;\n        break;\n\n      case SPROP_ACTIVE_SPRITES:\n        mzx_world->active_sprites = value;\n        break;\n\n      case SPROP_SPRITE_Y_ORDER:\n        mzx_world->sprite_y_order = value;\n        break;\n\n      case SPROP_COLLISION_COUNT:\n        num_collisions = CLAMP(value, 0, MAX_SPRITES);\n        mzx_world->collision_count = num_collisions;\n        break;\n\n      case SPROP_COLLISION_LIST:\n      {\n        int collision;\n\n        mfseek(&prop, 0, SEEK_SET);\n        if(num_collisions * 4 <= length)\n          for(collision = 0; collision < num_collisions; collision++)\n            mzx_world->collision_list[collision] = mfgetd(&prop);\n\n        break;\n      }\n\n      case SPROP_SPRITE_NUM:\n        mzx_world->sprite_num = value;\n        break;\n\n      default:\n        break;\n    }\n  }\n\nerr_free:\n  free(buffer);\n  return result;\n}\n\n\n// Counters\nstatic inline int save_world_counters(struct world *mzx_world,\n struct zip_archive *zp, const char *name)\n{\n  uint8_t buffer[8];\n  struct memfile mf;\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  struct counter *src_counter;\n  size_t name_length;\n  size_t i;\n  int result;\n\n  result = zip_write_open_file_stream(zp, name, ZIP_M_DEFLATE);\n  if(result != ZIP_SUCCESS)\n    return result;\n\n  mfopen_wr(buffer, 8, &mf);\n  mfputud(counter_list->num_counters, &mf);\n  zwrite(buffer, 4, zp);\n\n  for(i = 0; i < counter_list->num_counters; i++)\n  {\n    src_counter = counter_list->counters[i];\n    name_length = src_counter->name_length;\n\n    mf.current = buffer;\n    mfputd(src_counter->value, &mf);\n    mfputud(name_length, &mf);\n    zwrite(buffer, 8, zp);\n    zwrite(src_counter->name, name_length, zp);\n  }\n\n  return zip_write_close_stream(zp);\n}\n\nstatic inline int load_world_counters(struct world *mzx_world,\n struct zip_archive *zp)\n{\n  uint8_t buffer[8];\n  struct memfile mf;\n  char name_buffer[ROBOT_MAX_TR];\n  size_t name_length;\n  int value;\n\n  struct counter_list *counter_list = &(mzx_world->counter_list);\n  size_t num_prev_allocated;\n  size_t num_counters;\n  size_t i;\n\n  enum zip_error result;\n\n  // Stream counters out of the file instead of reading them all at once as\n  // the size of the counters file can vary greatly. This shouldn't hurt\n  // performance too much because of buffering.\n  result = zip_read_open_file_stream(zp, NULL);\n  if(result)\n    return result;\n\n  mfopen(buffer, 8, &mf);\n  zread(buffer, 4, zp);\n\n  num_counters = mfgetud(&mf);\n  num_prev_allocated = counter_list->num_counters_allocated;\n\n  // If there aren't already any counters, allocate manually.\n  if(!num_prev_allocated)\n  {\n    counter_list->num_counters = num_counters;\n    counter_list->num_counters_allocated = num_counters;\n    counter_list->counters = ccalloc(num_counters, sizeof(struct counter *));\n  }\n\n  for(i = 0; i < num_counters; i++)\n  {\n    zread(buffer, 8, zp);\n    mf.current = buffer;\n    value = mfgetd(&mf);\n    name_length = mfgetud(&mf);\n\n    if(name_length >= ROBOT_MAX_TR)\n      break;\n\n    if(ZIP_SUCCESS != zread(name_buffer, name_length, zp))\n      break;\n\n    // If there were already counters, use new_counter to set or add them\n    // into the existing counters as-needed.\n    if(num_prev_allocated)\n    {\n      name_buffer[name_length] = 0;\n      new_counter(mzx_world, name_buffer, value, -1);\n    }\n\n    // Otherwise, put them in the list manually.\n    else\n    {\n      load_new_counter(counter_list, i, name_buffer, name_length, value);\n    }\n  }\n\n  // If there weren't any previously allocated, the number successfully read is\n  // the new number of counters.\n  if(!num_prev_allocated)\n    counter_list->num_counters = i;\n\n#ifndef CONFIG_COUNTER_HASH_TABLES\n  // Versions without the hash table require this to be sorted at all times\n  sort_counter_list(counter_list);\n#endif\n\n  return zip_read_close_stream(zp);;\n}\n\n\n// Strings\nstatic inline int save_world_strings(struct world *mzx_world,\n struct zip_archive *zp, const char *name)\n{\n  uint8_t buffer[8];\n  struct memfile mf;\n  struct string_list *string_list = &(mzx_world->string_list);\n  struct string *src_string;\n  size_t name_length;\n  size_t str_length;\n  size_t i;\n  int result;\n\n  result = zip_write_open_file_stream(zp, name, ZIP_M_DEFLATE);\n  if(result != ZIP_SUCCESS)\n    return result;\n\n  mfopen_wr(buffer, 8, &mf);\n  mfputud(string_list->num_strings, &mf);\n  zwrite(buffer, 4, zp);\n\n  for(i = 0; i < string_list->num_strings; i++)\n  {\n    src_string = string_list->strings[i];\n    name_length = src_string->name_length;\n    str_length = src_string->length;\n\n    mf.current = buffer;\n    mfputud(name_length, &mf);\n    mfputud(str_length, &mf);\n    zwrite(buffer, 8, zp);\n    zwrite(src_string->name, name_length, zp);\n    zwrite(src_string->value, str_length, zp);\n  }\n\n  return zip_write_close_stream(zp);\n}\n\nstatic inline int load_world_strings(struct world *mzx_world,\n struct zip_archive *zp)\n{\n  uint8_t buffer[8];\n  struct memfile mf;\n  struct string_list *string_list = &(mzx_world->string_list);\n  struct string *src_string;\n  char name_buffer[ROBOT_MAX_TR];\n  size_t name_length;\n  size_t str_length;\n\n  size_t num_prev_allocated;\n  size_t num_strings;\n  size_t i;\n\n  enum zip_error result;\n\n  // Stream strings out of the file instead of reading them all at once as\n  // the size of the strings file can vary greatly. This shouldn't hurt\n  // performance too much because of buffering.\n  result = zip_read_open_file_stream(zp, NULL);\n  if(result != ZIP_SUCCESS)\n    return result;\n\n  mfopen(buffer, 8, &mf);\n  zread(buffer, 4, zp);\n\n  num_strings = mfgetud(&mf);\n  num_prev_allocated = string_list->num_strings_allocated;\n\n  // If there aren't already any strings, allocate manually.\n  if(!num_prev_allocated)\n  {\n    string_list->num_strings = num_strings;\n    string_list->num_strings_allocated = num_strings;\n    string_list->strings = ccalloc(num_strings, sizeof(struct string *));\n  }\n\n  for(i = 0; i < num_strings; i++)\n  {\n    zread(buffer, 8, zp);\n    mf.current = buffer;\n    name_length = mfgetud(&mf);\n    str_length = mfgetud(&mf);\n\n    if(name_length >= ROBOT_MAX_TR || str_length > MAX_STRING_LEN)\n      break;\n\n    if(ZIP_SUCCESS != zread(name_buffer, name_length, zp))\n      break;\n\n    // If there were already string, use new_string to set or add them\n    // into the existing strings as-needed.\n    if(num_prev_allocated)\n    {\n      name_buffer[name_length] = 0;\n      src_string = new_string(mzx_world, name_buffer, str_length, -1);\n      if(!src_string)\n        break;\n    }\n\n    // Otherwise, put them in the list manually.\n    else\n    {\n      src_string = load_new_string(string_list, i,\n       name_buffer, name_length, str_length);\n    }\n\n    zread(src_string->value, str_length, zp);\n    src_string->length = str_length;\n  }\n\n  // If there weren't any previously allocated, the number successfully read is\n  // the new number of strings.\n  if(!num_prev_allocated)\n    string_list->num_strings = i;\n\n#ifndef CONFIG_COUNTER_HASH_TABLES\n  // Versions without the hash table require this to be sorted at all times\n  sort_string_list(string_list);\n#endif\n\n  return zip_read_close_stream(zp);\n}\n\n\nvoid save_counters_file(struct world *mzx_world, const char *file)\n{\n  vfile *vf = vfopen_unsafe_ext(file, \"wb\", V_LARGE_BUFFER);\n  struct zip_archive *zp;\n\n  if(!vf)\n    return;\n\n  if(!vfwrite(\"COUNTERS\", 8, 1, vf))\n    goto err;\n\n  zp = zip_open_vf_write(vf);\n  if(!zp)\n    goto err;\n\n  save_world_counters(mzx_world, zp,      \"counter\");\n  save_world_strings(mzx_world, zp,       \"string\");\n\n  zip_close(zp, NULL);\n  return;\n\nerr:\n  vfclose(vf);\n  return;\n}\n\n\nint load_counters_file(struct world *mzx_world, const char *file)\n{\n  vfile *vf = vfopen_unsafe_ext(file, \"rb\", V_LARGE_BUFFER);\n  struct zip_archive *zp;\n  enum zip_error err;\n  char magic[8];\n\n  unsigned int prop_id;\n\n  if(!vf)\n  {\n    error_message(E_FILE_DOES_NOT_EXIST, 0, NULL);\n    return -1;\n  }\n\n  if(!vfread(magic, 8, 1, vf))\n    goto err_close_file;\n\n  if(memcmp(magic, \"COUNTERS\", 8))\n    goto err_close_file;\n\n  zp = zip_open_vf_read(vf);\n\n  if(!zp)\n    goto err_close_zip;\n\n  // Treat this like a world file since this contains world file IDs...\n  world_assign_file_ids(zp, true);\n\n  while(ZIP_SUCCESS == zip_get_next_mzx_file_id(zp, &prop_id, NULL, NULL))\n  {\n    err = ZIP_SUCCESS;\n\n    switch(prop_id)\n    {\n      case FILE_ID_WORLD_COUNTERS:\n        err = load_world_counters(mzx_world, zp);\n        break;\n\n      case FILE_ID_WORLD_STRINGS:\n        err = load_world_strings(mzx_world, zp);\n        break;\n\n      default:\n        err = zip_skip_file(zp);\n        break;\n    }\n\n    // File failed to load? Skip it\n    if(err != ZIP_SUCCESS)\n    {\n      fprintf(mzxerr, \"ERROR - Read error @ file ID %u\\n\", prop_id);\n      fflush(mzxerr);\n      if(zip_skip_file(zp) != ZIP_SUCCESS)\n        break;\n    }\n  }\n\n  zip_close(zp, NULL);\n  return 0;\n\nerr_close_zip:\n  zip_close(zp, NULL);\n  vf = NULL;\n\nerr_close_file:\n  if(vf)\n    vfclose(vf);\n  error_message(E_SAVE_FILE_INVALID, 0, NULL);\n  return -1;\n}\n\n\nstatic enum val_result validate_world_zip(struct world *mzx_world,\n struct zip_archive *zp, boolean savegame, int *file_version)\n{\n  unsigned int file_id;\n  int result;\n\n  int has_world = 0;\n  int has_chars = 0;\n  int has_pal = 0;\n  int has_counter = 0;\n  int has_string = 0;\n\n  // The directory has already been read by this point.\n  world_assign_file_ids(zp, true);\n\n  // Step through the directory and make sure the mandatory files exist.\n  while(ZIP_SUCCESS == zip_get_next_mzx_file_id(zp, &file_id, NULL, NULL))\n  {\n    // Can we stop early?\n    if((!savegame && has_pal) || has_string)\n      break;\n\n    switch(file_id)\n    {\n      // Everything needs this, no negotiations.\n      case FILE_ID_WORLD_INFO:\n        result = validate_world_info(mzx_world, zp, savegame, file_version);\n        if(result != VAL_SUCCESS)\n          return result;\n        // Continue so it doesn't skip the file.\n        has_world = 1;\n        continue;\n\n      // These are pretty much the bare minimum of what counts as a world\n      case FILE_ID_WORLD_CHARS:\n        has_chars = 1;\n        break;\n\n      case FILE_ID_WORLD_PAL:\n        has_pal = 1;\n        break;\n\n      // These are pretty much the bare minimum of what counts as a save\n      case FILE_ID_WORLD_COUNTERS:\n        has_counter = 1;\n        break;\n\n      case FILE_ID_WORLD_STRINGS:\n        has_string = 1;\n        break;\n\n      // These should exist, but can be replaced with defaults if missing.\n      case FILE_ID_WORLD_GLOBAL_ROBOT:\n      case FILE_ID_WORLD_PAL_INDEX:\n      case FILE_ID_WORLD_PAL_INTENSITY:\n      case FILE_ID_WORLD_PAL_INTENSITY_SMZX:\n      case FILE_ID_WORLD_VCO:\n      case FILE_ID_WORLD_VCH:\n      case FILE_ID_WORLD_SPRITES:\n        break;\n\n      // Completely optional.\n      case FILE_ID_WORLD_SFX:\n        break;\n\n      // Everything else: who knows\n      default:\n        break;\n    }\n    zip_skip_file(zp);\n  }\n\n  if(!(has_world && has_pal && has_chars))\n    goto err_out;\n\n  if(savegame && !(has_counter && has_string))\n    goto err_out;\n\n  return VAL_SUCCESS;\n\nerr_out:\n  if(has_world)\n  {\n    free(mzx_world->raw_world_info);\n    mzx_world->raw_world_info = NULL;\n    mzx_world->raw_world_info_size = 0;\n  }\n\n  return VAL_MISSING;\n}\n\n\nstatic int save_world_zip(struct world *mzx_world, const char *file,\n boolean savegame, int file_version)\n{\n  vfile *vf;\n  struct zip_archive *zp = NULL;\n  struct board *cur_board;\n  int i;\n\n  int meter_curr = 0;\n  int meter_target = 2 + mzx_world->num_boards + mzx_world->temporary_board;\n\n  meter_initial_draw(meter_curr, meter_target, \"Saving...\");\n\n  vf = vfopen_unsafe_ext(file, \"wb\", V_LARGE_BUFFER);\n  if(!vf)\n    goto err;\n\n  // Header\n  if(!savegame)\n  {\n    // World name\n    // This array is fixed size, so make sure it's zero padded. Previous\n    // versions could save bits of other world titles in the header.\n    char name[BOARD_NAME_SIZE] = { 0 };\n    snprintf(name, BOARD_NAME_SIZE, \"%s\", mzx_world->name);\n\n    if(!vfwrite(name, BOARD_NAME_SIZE, 1, vf))\n      goto err_close;\n\n    // Protection method -- always zero\n    vfputc(0, vf);\n\n    // Version string\n    vfputc('M', vf);\n    vfputc((file_version >> 8) & 0xFF, vf);\n    vfputc(file_version & 0xFF, vf);\n  }\n  else\n  {\n    // Version string\n    if(!vfwrite(\"MZS\", 3, 1, vf))\n      goto err_close;\n\n    vfputc((file_version >> 8) & 0xFF, vf);\n    vfputc(file_version & 0xFF, vf);\n\n    // MZX world version\n    vfputw(mzx_world->version, vf);\n\n    // Current board ID\n    vfputc(mzx_world->current_board_id, vf);\n  }\n\n  zp = zip_open_vf_write(vf);\n  if(!zp)\n    goto err_close;\n\n  // Zip64 support was added in 2.93.\n  if(file_version < V293)\n    zip_set_zip64_enabled(zp, false);\n\n  if(save_world_info(mzx_world, zp, savegame, file_version, \"world\"))\n    goto err_close;\n\n  if(save_world_global_robot(mzx_world, zp, savegame, file_version, \"gr\"))\n    goto err_close;\n\n  if(save_world_sfx(mzx_world, zp, file_version,\"sfx\"))     goto err_close;\n  if(save_world_chars(mzx_world, zp, savegame,  \"chars\"))   goto err_close;\n  if(save_world_pal(mzx_world, zp, file_version,\"pal\"))     goto err_close;\n  if(save_world_pal_smzx(mzx_world, zp,         \"palsmzx\")) goto err_close;\n  if(save_world_pal_index(mzx_world, zp,        \"palidx\"))  goto err_close;\n  if(save_world_vco(mzx_world, zp,              \"vco\"))     goto err_close;\n  if(save_world_vch(mzx_world, zp,              \"vch\"))     goto err_close;\n\n  if(savegame)\n  {\n    if(save_world_pal_inten(mzx_world, zp, file_version, \"palint\"))   goto err_close;\n    if(save_world_pal_inten_smzx(mzx_world, zp,\"palints\"))  goto err_close;\n    if(save_world_sprites(mzx_world, zp,       \"spr\"))      goto err_close;\n    if(save_world_counters(mzx_world, zp,      \"counter\"))  goto err_close;\n    if(save_world_strings(mzx_world, zp,       \"string\"))   goto err_close;\n  }\n\n  meter_update_screen(&meter_curr, meter_target);\n\n  for(i = 0; i < mzx_world->num_boards; i++)\n  {\n    cur_board = mzx_world->board_list[i];\n\n    if(cur_board)\n    {\n      if(cur_board != mzx_world->current_board)\n        retrieve_board_from_extram(cur_board);\n\n      if(save_board(mzx_world, cur_board, zp, savegame, file_version, i))\n        goto err_close;\n\n      if(cur_board != mzx_world->current_board)\n        store_board_to_extram(cur_board);\n    }\n\n    meter_update_screen(&meter_curr, meter_target);\n  }\n\n  if(mzx_world->temporary_board)\n  {\n    if(save_board(mzx_world, mzx_world->current_board, zp, savegame,\n     file_version, TEMPORARY_BOARD))\n      goto err_close;\n\n    meter_update_screen(&meter_curr, meter_target);\n  }\n\n  meter_update_screen(&meter_curr, meter_target);\n\n  meter_restore_screen();\n\n  zip_close(zp, NULL);\n  return 0;\n\nerr_close:\n  if(zp)\n    zip_close(zp, NULL);\n  else\n    vfclose(vf);\n\nerr:\n  error_message(E_WORLD_IO_SAVING, 0, NULL);\n  meter_restore_screen();\n  return -1;\n}\n\n#undef if_savegame\n#undef if_savegame_or_291\n\n#define if_savegame         if(!savegame) { zip_skip_file(zp); break; }\n#define if_savegame_or_291  if(!savegame && mzx_world->version < V291) \\\n                             { zip_skip_file(zp); break; }\n\nstatic int load_world_zip(struct world *mzx_world, struct zip_archive *zp,\n boolean savegame, int file_version, boolean *faded)\n{\n  unsigned int file_id;\n  unsigned int board_id;\n  enum zip_error err;\n\n  int loaded_global_robot = 0;\n\n  int meter_curr = 0;\n  int meter_target = 2;\n  boolean loaded_temp_board = false;\n\n  meter_initial_draw(meter_curr, meter_target, \"Loading...\");\n\n  // The directory has already been read by this point, and we're at the start.\n\n  while(ZIP_SUCCESS == zip_get_next_mzx_file_id(zp, &file_id, &board_id, NULL))\n  {\n    err = ZIP_SUCCESS;\n\n    switch(file_id)\n    {\n      case FILE_ID_NONE:\n      default:\n        zip_skip_file(zp);\n        break;\n\n      case FILE_ID_WORLD_INFO:\n      {\n        load_world_info(mzx_world, zp, savegame, &file_version, faded);\n\n        mzx_world->num_boards_allocated = mzx_world->num_boards;\n        mzx_world->board_list =\n         ccalloc(mzx_world->num_boards, sizeof(struct board *));\n\n        meter_target += mzx_world->num_boards + mzx_world->temporary_board;\n        meter_update_screen(&meter_curr, meter_target);\n        break;\n      }\n\n      case FILE_ID_WORLD_GLOBAL_ROBOT:\n        err = load_world_global_robot(mzx_world, zp, savegame, file_version);\n        loaded_global_robot = 1;\n        break;\n\n      case FILE_ID_WORLD_SFX:\n        err = load_world_sfx(mzx_world, zp, file_version);\n        break;\n\n      case FILE_ID_WORLD_CHARS:\n        err = load_world_chars(mzx_world, zp, savegame);\n        break;\n\n      case FILE_ID_WORLD_PAL:\n        err = load_world_pal(mzx_world, zp, file_version);\n        break;\n\n      case FILE_ID_WORLD_PAL_SMZX:\n        err = load_world_pal_smzx(mzx_world, zp);\n        break;\n\n      case FILE_ID_WORLD_PAL_INDEX:\n        if_savegame_or_291\n        err = load_world_pal_index(mzx_world, zp, file_version);\n        break;\n\n      case FILE_ID_WORLD_PAL_INTENSITY:\n        if_savegame\n        err = load_world_pal_inten(mzx_world, zp, file_version);\n        break;\n\n      case FILE_ID_WORLD_PAL_INTENSITY_SMZX:\n        if_savegame\n        err = load_world_pal_inten_smzx(mzx_world, zp);\n        break;\n\n      case FILE_ID_WORLD_VCO:\n        if_savegame_or_291\n        err = load_world_vco(mzx_world, zp);\n        break;\n\n      case FILE_ID_WORLD_VCH:\n        if_savegame_or_291\n        err = load_world_vch(mzx_world, zp);\n        break;\n\n      case FILE_ID_WORLD_SPRITES:\n        if_savegame\n        err = load_world_sprites(mzx_world, zp);\n        break;\n\n      case FILE_ID_WORLD_COUNTERS:\n        if_savegame\n        err = load_world_counters(mzx_world, zp);\n        break;\n\n      case FILE_ID_WORLD_STRINGS:\n        if_savegame\n        err = load_world_strings(mzx_world, zp);\n        break;\n\n      // Defer to the board loader.\n      case FILE_ID_BOARD_INFO:\n      {\n        if((int)board_id < mzx_world->num_boards)\n        {\n          mzx_world->board_list[board_id] =\n           load_board_allocate(mzx_world, zp, savegame, file_version, board_id);\n\n          store_board_to_extram(mzx_world->board_list[board_id]);\n          meter_update_screen(&meter_curr, meter_target);\n        }\n        else\n\n        // Load the temporary board\n        if(mzx_world->temporary_board &&\n         board_id == TEMPORARY_BOARD)\n        {\n          mzx_world->current_board =\n           load_board_allocate(mzx_world, zp, savegame, file_version, board_id);\n\n          meter_update_screen(&meter_curr, meter_target);\n          loaded_temp_board = true;\n        }\n        break;\n      }\n\n      case FILE_ID_BOARD_BID:\n      case FILE_ID_BOARD_BPR:\n      case FILE_ID_BOARD_BCO:\n      case FILE_ID_BOARD_UID:\n      case FILE_ID_BOARD_UPR:\n      case FILE_ID_BOARD_UCO:\n      case FILE_ID_BOARD_OCH:\n      case FILE_ID_BOARD_OCO:\n      case FILE_ID_ROBOT:\n      case FILE_ID_SCROLL:\n      case FILE_ID_SENSOR:\n        // Should never, ever encounter these here.\n        zip_skip_file(zp);\n        break;\n    }\n\n    // File failed to load? Skip it\n    if(err != ZIP_SUCCESS)\n    {\n      zip_skip_file(zp);\n      fprintf(mzxerr, \"ERROR - Read error @ file ID %u\\n\", file_id);\n      fflush(mzxerr);\n    }\n  }\n\n  // Check for missing global robot\n  if(!loaded_global_robot)\n  {\n    error_message(E_ZIP_ROBOT_MISSING_FROM_DATA, 0, \"gr\");\n\n    create_blank_robot(&mzx_world->global_robot);\n    create_blank_robot_program(&mzx_world->global_robot);\n  }\n\n  // Check for no title screen; make a dummy board if it's missing.\n  if(!mzx_world->board_list[0])\n  {\n    struct board *dummy = cmalloc(sizeof(struct board));\n    dummy_board(mzx_world, dummy);\n\n    dummy->board_name[0] = 0;\n    dummy->robot_list[0] = &mzx_world->global_robot;\n\n    mzx_world->board_list[0] = dummy;\n\n    error_message(E_WORLD_BOARD_MISSING, 0, NULL);\n  }\n\n  // Check for missing temporary board; if the temporary board is missing,\n  // clear the temporary flag so the temporary board will be regenerated.\n  if(mzx_world->temporary_board && !loaded_temp_board)\n    mzx_world->temporary_board = false;\n\n  meter_update_screen(&meter_curr, meter_target);\n\n  meter_restore_screen();\n\n  zip_close(zp, NULL);\n  return 0;\n}\n\n\n// Hack- forward declaration since this should stay near change_board.\nstatic void v1_store_globals_to_board(struct world *mzx_world);\nstatic void v1_load_globals_from_board(struct world *mzx_world);\n\nint save_world(struct world *mzx_world, const char *file, boolean savegame,\n int file_version)\n{\n#ifdef CONFIG_DEBYTECODE\n  // TODO we'll cross this bridge when we need to. That shouldn't be\n  // until debytecode gets an actual release, though.\n\n  if(file_version < VERSION_SOURCE)\n  {\n    error(\"Downver. not currently supported by debytecode.\",\n     ERROR_T_ERROR, ERROR_OPT_OK, 0x0000);\n    return -1;\n  }\n\n  if(!savegame)\n  {\n    vfile *vf = vfopen_unsafe(file, \"rb\");\n    if(vf)\n    {\n      if(!vfseek(vf, 0x1A, SEEK_SET))\n      {\n        char tmp[3];\n        if(vfread(tmp, 1, 3, vf) == 3)\n        {\n          int old_version = world_magic(tmp);\n\n          // If it's non-debytecode, abort\n          if(old_version < VERSION_SOURCE)\n          {\n            error_message(E_DBC_WORLD_OVERWRITE_OLD, old_version, NULL);\n            vfclose(vf);\n            return -1;\n          }\n        }\n      }\n      vfclose(vf);\n    }\n  }\n#endif /* CONFIG_DEBYTECODE */\n\n  // Prepare input pos\n  if(!mzx_world->input_is_dir && mzx_world->input_file)\n  {\n    mzx_world->temp_input_pos = vftell(mzx_world->input_file);\n  }\n  else\n\n  if(mzx_world->input_is_dir)\n  {\n    mzx_world->temp_input_pos = vdir_tell(mzx_world->input_directory);\n  }\n  else\n  {\n    mzx_world->temp_input_pos = 0;\n  }\n\n  // Prepare output pos\n  if(mzx_world->output_file)\n  {\n    mzx_world->temp_output_pos = vftell(mzx_world->output_file);\n  }\n  else\n  {\n    mzx_world->temp_output_pos = 0;\n  }\n\n  // Synchronize global variables in the current board.\n  v1_store_globals_to_board(mzx_world);\n\n  // Supported file format versions are the current version (typical world or\n  // save files) and the previous version (downver export from the editor).\n  if(file_version == MZX_VERSION || file_version == MZX_VERSION_PREV)\n  {\n    int world_version = mzx_world->version;\n    int ret_val;\n\n    if(!savegame)\n    {\n      // Temporarily update the world version to the file version. For world\n      // files, these versions are the same, but permanently changing the world\n      // version needs to happen during specific actions in the editor only.\n      mzx_world->version = file_version;\n    }\n\n    ret_val = save_world_zip(mzx_world, file, savegame, file_version);\n\n    mzx_world->version = world_version;\n    return ret_val;\n  }\n  else\n  {\n    warn(\"Attempted to save incompatible world file version %d.%d! Aborting!\\n\",\n     (file_version >> 8) & 0xFF, file_version & 0xFF);\n\n    return -1;\n  }\n}\n\n__editor_maybe_static\nvoid set_update_done(struct world *mzx_world)\n{\n  struct board **board_list = mzx_world->board_list;\n  struct board *cur_board;\n  int max_size = 0;\n  int cur_size;\n  int i;\n\n  for(i = 0; i < mzx_world->num_boards; i++)\n  {\n    cur_board = board_list[i];\n    cur_size = cur_board->board_width * cur_board->board_height;\n\n    if(cur_size > max_size)\n      max_size = cur_size;\n  }\n\n  if(max_size > mzx_world->update_done_size)\n  {\n    if(mzx_world->update_done == NULL)\n    {\n      mzx_world->update_done = cmalloc(max_size);\n    }\n    else\n    {\n      mzx_world->update_done =\n       crealloc(mzx_world->update_done, max_size);\n    }\n\n    mzx_world->update_done_size = max_size;\n  }\n}\n\n__editor_maybe_static\nvoid refactor_board_list(struct world *mzx_world,\n struct board **new_board_list, int new_list_size,\n int *board_id_translation_list)\n{\n  int i;\n  int i2;\n  int offset;\n  char *level_id;\n  char *level_param;\n  int d_param, d_flag;\n  int board_width;\n  int board_height;\n  int new_current_id = NO_BOARD;\n\n  int num_boards = mzx_world->num_boards;\n  struct board **board_list = mzx_world->board_list;\n  struct board *cur_board;\n\n  if(board_list[mzx_world->current_board_id])\n    new_current_id = board_id_translation_list[mzx_world->current_board_id];\n\n  free(board_list);\n  board_list =\n   crealloc(new_board_list, sizeof(struct board *) * new_list_size);\n\n  mzx_world->num_boards = new_list_size;\n  mzx_world->num_boards_allocated = new_list_size;\n\n  // Fix all entrances and exits in each board\n  for(i = 0; i < new_list_size; i++)\n  {\n    cur_board = board_list[i];\n    board_width = cur_board->board_width;\n    board_height = cur_board->board_height;\n\n    if(i != new_current_id)\n      retrieve_board_from_extram(cur_board);\n\n    level_id = cur_board->level_id;\n    level_param = cur_board->level_param;\n\n    // Fix entrances\n    for(offset = 0; offset < board_width * board_height; offset++)\n    {\n      d_flag = flags[(int)level_id[offset]];\n\n      if(d_flag & A_ENTRANCE)\n      {\n        d_param = level_param[offset];\n        if(d_param < num_boards)\n          level_param[offset] = board_id_translation_list[d_param];\n        else\n          level_param[offset] = NO_BOARD;\n      }\n    }\n\n    // Fix exits\n    for(i2 = 0; i2 < 4; i2++)\n    {\n      d_param = cur_board->board_dir[i2];\n\n      if(d_param < num_boards)\n        cur_board->board_dir[i2] = board_id_translation_list[d_param];\n      else\n        cur_board->board_dir[i2] = NO_BOARD;\n    }\n\n    if(i != new_current_id)\n      store_board_to_extram(cur_board);\n  }\n\n  // Fix current board\n  if(new_current_id != NO_BOARD)\n  {\n    d_param = mzx_world->current_board_id;\n    d_param = board_id_translation_list[d_param];\n    mzx_world->current_board_id = d_param;\n\n    if(!mzx_world->temporary_board)\n      mzx_world->current_board = board_list[d_param];\n  }\n\n  d_param = mzx_world->first_board;\n  if(d_param >= num_boards)\n    d_param = num_boards - 1;\n\n  d_param = board_id_translation_list[d_param];\n  mzx_world->first_board = d_param;\n\n  d_param = mzx_world->endgame_board;\n\n  if(d_param != NO_BOARD)\n  {\n    if(d_param >= num_boards)\n      d_param = num_boards - 1;\n\n    d_param = board_id_translation_list[d_param];\n    mzx_world->endgame_board = d_param;\n  }\n\n  d_param = mzx_world->death_board;\n\n  if((d_param != NO_BOARD) && (d_param != DEATH_SAME_POS))\n  {\n    if(d_param >= num_boards)\n      d_param = num_boards - 1;\n\n    d_param = board_id_translation_list[d_param];\n    mzx_world->death_board = d_param;\n  }\n\n  for(i = 0; i < 8; i++)\n  {\n    d_param = mzx_world->pl_saved_board[i];\n    if(d_param >= num_boards)\n      d_param = num_boards - 1;\n\n    d_param = board_id_translation_list[d_param];\n    mzx_world->pl_saved_board[i] = d_param;\n  }\n\n  mzx_world->board_list = board_list;\n}\n\n__editor_maybe_static\nvoid optimize_null_boards(struct world *mzx_world)\n{\n  // Optimize out null objects while keeping a translation list mapping\n  // board numbers from the old list to the new list.\n  int num_boards = mzx_world->num_boards;\n  struct board **board_list = mzx_world->board_list;\n  struct board **optimized_board_list =\n   ccalloc(num_boards, sizeof(struct board *));\n  int *board_id_translation_list = ccalloc(num_boards, sizeof(int));\n\n  struct board *cur_board;\n  int i, i2;\n\n  for(i = 0, i2 = 0; i < num_boards; i++)\n  {\n    cur_board = board_list[i];\n    if(cur_board != NULL)\n    {\n      optimized_board_list[i2] = cur_board;\n      board_id_translation_list[i] = i2;\n      i2++;\n    }\n    else\n    {\n      board_id_translation_list[i] = NO_BOARD;\n    }\n  }\n\n  // Might need to fix these first, to correct old MZX games.\n  if(mzx_world->first_board >= num_boards)\n    mzx_world->first_board = 0;\n\n  if((mzx_world->death_board >= num_boards) &&\n   (mzx_world->death_board < DEATH_SAME_POS))\n    mzx_world->death_board = NO_BOARD;\n\n  if(mzx_world->endgame_board >= num_boards)\n    mzx_world->endgame_board = NO_BOARD;\n\n  for(i = 0; i < 8; i++)\n    if(mzx_world->pl_saved_board[i] >= num_boards)\n      mzx_world->pl_saved_board[i] = 0;\n\n  if(i2 < num_boards)\n  {\n    refactor_board_list(mzx_world, optimized_board_list, i2,\n     board_id_translation_list);\n  }\n  else\n  {\n    free(optimized_board_list);\n  }\n\n  free(board_id_translation_list);\n}\n\n\nstatic void load_world(struct world *mzx_world, struct zip_archive *zp,\n vfile *vf, const char *file, boolean savegame, int file_version, char *name,\n boolean *faded)\n{\n  int file_name_len = strlen(file) - 4;\n  char config_file_name[MAX_PATH];\n  char current_dir[MAX_PATH];\n  char file_path[MAX_PATH];\n  struct stat file_info;\n\n  // Change to the game (or save) directory.\n  if(path_get_directory(file_path, MAX_PATH, file) > 0)\n  {\n    vgetcwd(current_dir, MAX_PATH);\n\n    if(strcmp(current_dir, file_path))\n      vchdir(file_path);\n  }\n\n  // load world config file\n  snprintf(config_file_name, MAX_PATH, \"%.*s.cnf\", file_name_len, file);\n  config_file_name[MAX_PATH - 1] = '\\0';\n\n  if(vstat(config_file_name, &file_info) >= 0)\n    set_config_from_file(GAME_CNF, config_file_name);\n\n  // Some initial setting(s)\n  sfx_free(&mzx_world->custom_sfx);\n  mzx_world->max_samples = -1;\n  mzx_world->joystick_simulate_keys = true;\n\n  default_palette();\n\n  // If we're here, there's either a zip (regular) or a file (legacy).\n  if(zp)\n  {\n    load_world_zip(mzx_world, zp, savegame, file_version, faded);\n  }\n  else\n  {\n    legacy_load_world(mzx_world, vf, file, savegame, file_version, name, faded);\n  }\n\n  update_palette();\n\n  initialize_gateway_functions(mzx_world);\n\n  // Open input file\n  if(mzx_world->input_file_name[0])\n  {\n    char translated_path[MAX_PATH];\n    int err;\n\n    mzx_world->input_is_dir = false;\n\n    err = fsafetranslate(mzx_world->input_file_name, translated_path, MAX_PATH);\n    if(err == -FSAFE_MATCHED_DIRECTORY)\n    {\n      mzx_world->input_directory = vdir_open(translated_path);\n      if(mzx_world->input_directory)\n      {\n        vdir_seek(mzx_world->input_directory, mzx_world->temp_input_pos);\n        mzx_world->input_is_dir = true;\n      }\n    }\n    else if(err == -FSAFE_SUCCESS)\n    {\n      mzx_world->input_file = vfopen_unsafe(translated_path, \"rb\");\n      if(mzx_world->input_file)\n        vfseek(mzx_world->input_file, mzx_world->temp_input_pos, SEEK_SET);\n    }\n  }\n\n  // Open output file\n  if(mzx_world->output_file_name[0])\n  {\n    // Truncation occurred during the initial FWRITE_APPEND,\n    // so wb and r+b should both be reopened as r+b.\n    if(mzx_world->output_mode == FWRITE_MODE_APPEND)\n      mzx_world->output_file = fsafeopen(mzx_world->output_file_name, \"ab\");\n    else\n      mzx_world->output_file = fsafeopen(mzx_world->output_file_name, \"r+b\");\n\n    if(mzx_world->output_file)\n    {\n      vfseek(mzx_world->output_file, mzx_world->temp_output_pos, SEEK_SET);\n    }\n  }\n\n  // Set up the initial current board (which might have a junk ID).\n  // If the current board is a temporary board, then nothing needs to be done.\n  // For worlds, this should always be 0 (title screen).\n  if(mzx_world->current_board_id >= mzx_world->num_boards)\n    mzx_world->current_board_id = 0;\n\n  if(!mzx_world->temporary_board)\n  {\n    mzx_world->current_board = NULL;\n    set_current_board_ext(mzx_world,\n     mzx_world->board_list[mzx_world->current_board_id]);\n    // change_board expects this to have been done, if applicable.\n    v1_load_globals_from_board(mzx_world);\n  }\n\n  // If this is a pre-port world, limit the number of samples\n  // playing to 4 (the maximum number in pre-port MZX)\n  if(mzx_world->version < VERSION_PORT)\n    mzx_world->max_samples = 4;\n\n  // This will be -1 (no limit) or whatever was loaded from a save\n  audio_set_max_samples(mzx_world->max_samples);\n\n  // This will generally be 'true' unless it was different in the save.\n  joystick_set_game_bindings(mzx_world->joystick_simulate_keys);\n\n  mzx_world->active = 1;\n\n  // Remove any null boards\n  // Also bounds checks the start, death, game over, and board exit board IDs.\n  optimize_null_boards(mzx_world);\n\n  // Resize this array if necessary\n  set_update_done(mzx_world);\n\n  // Find the player\n  find_player(mzx_world);\n}\n\n\n/**\n * Read the world header from a file.\n * Doesn't validate any info; only return false if there was an IO error.\n */\nstatic boolean read_world_header(vfile *vf, boolean savegame,\n int *file_version, int *protected, char *name)\n{\n  char magic[5];\n  int pr = 0;\n  int v;\n\n  if(savegame)\n  {\n    if(!vfread(magic, 5, 1, vf))\n      return false;\n\n    v = save_magic(magic);\n  }\n  else\n  {\n    if(!vfread(name, BOARD_NAME_SIZE, 1, vf))\n      return false;\n\n    // Protection byte\n    pr = vfgetc(vf);\n    if(protected)\n      *protected = pr;\n\n    if(pr)\n    {\n      vfseek(vf, 15, SEEK_CUR);\n      if(!vfread(magic, 4, 1, vf))\n        return false;\n\n      v = world_magic(magic);\n      if(v < V200)\n        v = world_magic(magic + 1);\n    }\n    else\n    {\n      if(!vfread(magic, 3, 1, vf))\n        return false;\n\n      v = world_magic(magic);\n    }\n  }\n\n  if(protected) *protected = pr;\n  if(file_version) *file_version = v;\n  return true;\n}\n\nstatic struct zip_archive *try_load_zip_world(struct world *mzx_world,\n const char *file, boolean savegame, int *file_version, int *protected,\n char *name)\n{\n  struct zip_archive *zp = NULL;\n  vfile *vf;\n  int pr = 0;\n  int v = 0;\n\n  int result;\n\n  vf = vfopen_unsafe_ext(file, \"rb\", V_LARGE_BUFFER);\n  if(!vf)\n    return NULL;\n\n  if(!read_world_header(vf, savegame, &v, &pr, name))\n    goto err_close;\n\n  *file_version = v;\n\n  /* If we got something useful from the version number, and it's legacy:\n   * - and it's a save, then it's not a headerless zip by default\n   * - and it's a regular world, then the first file name would have to be\n   *   extremely long to produce a valid version number from a headerless zip\n   *\n   * So we can just fail safely and let the legacy loader pick it up.\n   */\n\n  if(pr > 0 && pr <= 3 && strncmp(name, \"PK\", 2))\n    goto err_protected;\n\n  if(v > 0 && v <= MZX_LEGACY_FORMAT_VERSION)\n    goto err_close;\n\n  zp = zip_open_vf_read(vf);\n  if(!zp)\n  {\n    vf = NULL;\n    goto err_close;\n  }\n\n  // This may contain a different file format version...\n  result = validate_world_zip(mzx_world, zp, savegame, file_version);\n\n  if(result != VAL_SUCCESS)\n    goto err_close;\n\n  // Should never happen, but if it did, it's totally invalid.\n  if(*file_version <= MZX_LEGACY_FORMAT_VERSION)\n    goto err_close;\n\n  v = *file_version;\n  if(v > MZX_VERSION)\n    goto err_close;\n\n  zip_rewind(zp);\n\n  reset_error_suppression();\n  return zp;\n\nerr_close:\n\n  // Display an appropriate error.\n  if(savegame)\n  {\n    if(v > MZX_VERSION)\n    {\n      error_message(E_SAVE_VERSION_TOO_RECENT, v, NULL);\n    }\n    else\n\n    // Allow the last pre-ZIP version, but none before it\n    if(v > 0 && v < MZX_LEGACY_FORMAT_VERSION)\n    {\n      error_message(E_SAVE_VERSION_OLD, v, NULL);\n    }\n    else\n\n    if(v != MZX_LEGACY_FORMAT_VERSION)\n    {\n      error_message(E_SAVE_FILE_INVALID, 0, NULL);\n    }\n  }\n  else\n  {\n    if(v > MZX_VERSION)\n    {\n      error_message(E_WORLD_FILE_VERSION_TOO_RECENT, v, NULL);\n    }\n    else\n\n    if(v > 0 && v < V100)\n    {\n      error_message(E_WORLD_FILE_VERSION_OLD, v, NULL);\n    }\n    else\n\n    if(v == 0 || v > MZX_LEGACY_FORMAT_VERSION)\n    {\n      error_message(E_WORLD_FILE_INVALID, 0, NULL);\n    }\n  }\n\nerr_protected:\n  *protected = pr;\n  if(zp)\n  {\n    zip_close(zp, NULL);\n  }\n  else\n\n  if(vf)\n    vfclose(vf);\n\n  return NULL;\n}\n\nstatic vfile *try_load_legacy_world(struct world *mzx_world, const char *file,\n boolean savegame, int *file_version, char *name)\n{\n  vfile *vf;\n\n  // Validate the legacy world file and attempt decryption as needed.\n  vf = validate_legacy_world_file(mzx_world, file, savegame);\n  if(!vf)\n    return NULL;\n\n  vrewind(vf);\n\n  // Ignore the protected byte since the world should be decrypted by now.\n  if(!read_world_header(vf, savegame, file_version, NULL, name))\n  {\n    vfclose(vf);\n    return NULL;\n  }\n\n  reset_error_suppression();\n  return vf;\n}\n\n__editor_maybe_static\nvoid try_load_world(struct world *mzx_world, struct zip_archive **zp,\n vfile **vf, const char *file, boolean savegame, int *file_version, char *name)\n{\n  // Regular worlds use a zip_archive. Legacy worlds use a FILE.\n  struct zip_archive *_zp = NULL;\n  vfile *_vf = NULL;\n  int protected = 0;\n  int v = 0;\n\n  _zp = try_load_zip_world(mzx_world, file, savegame, &v, &protected, name);\n\n  if(!_zp)\n    if(protected || (v >= V100 && v <= MZX_LEGACY_FORMAT_VERSION))\n      _vf = try_load_legacy_world(mzx_world, file, savegame, &v, name);\n\n  *zp = _zp;\n  *vf = _vf;\n  *file_version = v;\n}\n\n\nstatic void v1_store_globals_to_board(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n\n  // MegaZeux 1.x- status durations were local to each board and need to be\n  // backed up to the current board.\n  if(mzx_world->version < V200 && cur_board)\n  {\n    cur_board->blind_dur_v1 = mzx_world->blind_dur;\n    cur_board->firewalker_dur_v1 = mzx_world->firewalker_dur;\n    cur_board->freeze_time_dur_v1 = mzx_world->freeze_time_dur;\n    cur_board->slow_time_dur_v1 = mzx_world->slow_time_dur;\n    cur_board->wind_dur_v1 = mzx_world->wind_dur;\n  }\n}\n\nstatic void v1_load_globals_from_board(struct world *mzx_world)\n{\n  struct board *cur_board = mzx_world->current_board;\n\n  // MegaZeux 1.x- status durations were board local, restore the saved copies.\n  if(mzx_world->version < V200 && cur_board)\n  {\n    mzx_world->blind_dur = cur_board->blind_dur_v1;\n    mzx_world->firewalker_dur = cur_board->firewalker_dur_v1;\n    mzx_world->freeze_time_dur = cur_board->freeze_time_dur_v1;\n    mzx_world->slow_time_dur = cur_board->slow_time_dur_v1;\n    mzx_world->wind_dur = cur_board->wind_dur_v1;\n  }\n}\n\nvoid change_board(struct world *mzx_world, int board_id)\n{\n  // Set the current board during gameplay.\n  struct board *cur_board = mzx_world->current_board;\n\n  // Save globals back to the current board.\n  v1_store_globals_to_board(mzx_world);\n\n  // Is this board temporary? Clear it\n  if(mzx_world->temporary_board)\n  {\n    assert(cur_board != NULL);\n    assert(cur_board->reset_on_entry != 0);\n\n    clear_board(cur_board);\n    mzx_world->current_board = NULL;\n    mzx_world->temporary_board = 0;\n  }\n\n  mzx_world->current_board_id = board_id;\n  set_current_board_ext(mzx_world, mzx_world->board_list[board_id]);\n\n  cur_board = mzx_world->current_board;\n\n  // Does this board need a duplicate? (2.90+)\n  if(mzx_world->version >= V290 && cur_board->reset_on_entry)\n  {\n    struct board *dup_board = duplicate_board(mzx_world, cur_board);\n    store_board_to_extram(cur_board);\n\n    mzx_world->current_board = dup_board;\n    mzx_world->temporary_board = 1;\n    cur_board = dup_board;\n  }\n\n  // Load globals from the current board.\n  v1_load_globals_from_board(mzx_world);\n}\n\nvoid change_board_set_values(struct world *mzx_world)\n{\n  // The sequel to change_board; set special values on board (re)entry.\n  struct board *cur_board = mzx_world->current_board;\n\n  // Set the timer.\n  set_counter(mzx_world, \"TIME\", cur_board->time_limit, 0);\n\n  // Set the player restart position.\n  find_player(mzx_world);\n  mzx_world->player_restart_x = mzx_world->player_x;\n  mzx_world->player_restart_y = mzx_world->player_y;\n}\n\nvoid change_board_load_assets(struct world *mzx_world)\n{\n  // The sequel to change_board; if there's a fadeout, use this after.\n  struct board *cur_board = mzx_world->current_board;\n  char translated_name[MAX_PATH];\n\n  // Does this board need a char set loaded? (2.90+)\n  if(mzx_world->version >= V290 && cur_board->charset_path)\n  {\n    if(fsafetranslate(cur_board->charset_path, translated_name, MAX_PATH) == FSAFE_SUCCESS)\n    {\n      // Bug: ec_load_set cleared the extended chars prior to 2.91e\n      if(mzx_world->version < V291)\n        ec_clear_set();\n\n      ec_load_set(translated_name);\n    }\n  }\n\n  // Does this board need a palette loaded? (2.90+)\n  if(mzx_world->version >= V290 && cur_board->palette_path)\n  {\n    if(fsafetranslate(cur_board->palette_path, translated_name, MAX_PATH) == FSAFE_SUCCESS)\n      load_palette(translated_name);\n  }\n}\n\n// This needs to happen before a world is loaded if clear_global_data was used.\n\nstatic void default_sprite_data(struct world *mzx_world)\n{\n  int i;\n\n  // Allocate space for sprites and clist\n  mzx_world->num_sprites = MAX_SPRITES;\n  mzx_world->num_sprites_allocated = MAX_SPRITES;\n  mzx_world->sprite_list = ccalloc(MAX_SPRITES, sizeof(struct sprite *));\n\n  for(i = 0; i < MAX_SPRITES; i++)\n  {\n    mzx_world->sprite_list[i] = ccalloc(1, sizeof(struct sprite));\n  }\n\n  mzx_world->collision_list = ccalloc(MAX_SPRITES, sizeof(int));\n  mzx_world->sprite_num = 0;\n}\n\n// This also needs to happen before a world is loaded.\n\n__editor_maybe_static void default_vlayer(struct world *mzx_world)\n{\n  // Allocate space for vlayer.\n  mzx_world->vlayer_size = 0x8000;\n  mzx_world->vlayer_width = 256;\n  mzx_world->vlayer_height = 128;\n  mzx_world->vlayer_chars = cmalloc(0x8000);\n  mzx_world->vlayer_colors = cmalloc(0x8000);\n  memset(mzx_world->vlayer_chars, 32, 0x8000);\n  memset(mzx_world->vlayer_colors, 7, 0x8000);\n}\n\n// After loading, use this to get default values. Use\n// for loading of worlds (as opposed to save games).\n\n__editor_maybe_static void default_global_data(struct world *mzx_world)\n{\n  int i;\n\n  // This might be a new world, so make sure we have sprites.\n  if(!mzx_world->sprite_list)\n    default_sprite_data(mzx_world);\n\n  // Set some default counter values\n  // The others have to be here so their gateway functions will stick\n  set_counter(mzx_world, \"AMMO\", 0, 0);\n  set_counter(mzx_world, \"COINS\", 0, 0);\n  set_counter(mzx_world, \"ENTER_MENU\", 1, 0);\n  set_counter(mzx_world, \"ESCAPE_MENU\", 1, 0);\n  set_counter(mzx_world, \"F2_MENU\", 1, 0);\n  set_counter(mzx_world, \"GEMS\", 0, 0);\n  set_counter(mzx_world, \"HEALTH\", mzx_world->starting_health, 0);\n  set_counter(mzx_world, \"HELP_MENU\", 1, 0);\n  set_counter(mzx_world, \"HIBOMBS\", 0, 0);\n  set_counter(mzx_world, \"INVINCO\", 0, 0);\n  set_counter(mzx_world, \"LIVES\", mzx_world->starting_lives, 0);\n  set_counter(mzx_world, \"LOAD_MENU\", 1, 0);\n  set_counter(mzx_world, \"LOBOMBS\", 0, 0);\n  set_counter(mzx_world, \"SCORE\", 0, 0);\n  set_counter(mzx_world, \"TIME\", 0, 0);\n\n  // Setup their gateways\n  initialize_gateway_functions(mzx_world);\n\n  mzx_world->multiplier = 10000;\n  mzx_world->divider = 10000;\n  mzx_world->c_divisions = 360;\n  mzx_world->fread_delimiter = '*';\n  mzx_world->fwrite_delimiter = '*';\n  mzx_world->bi_shoot_status = 1;\n  mzx_world->bi_mesg_status = 1;\n\n  // Default values for global params\n  memset(mzx_world->keys, NO_KEY, NUM_KEYS);\n  mzx_world->mesg_edges = 1;\n  mzx_world->real_mod_playing[0] = 0;\n  mzx_world->smzx_message = 1;\n  // In 2.90X, due to a bug the message could only display in mode 0.\n  if(mzx_world->version == V290)\n    mzx_world->smzx_message = 0;\n\n  mzx_world->blind_dur = 0;\n  mzx_world->firewalker_dur = 0;\n  mzx_world->freeze_time_dur = 0;\n  mzx_world->slow_time_dur = 0;\n  mzx_world->wind_dur = 0;\n\n  for(i = 0; i < 8; i++)\n  {\n    mzx_world->pl_saved_x[i] = 0;\n    mzx_world->pl_saved_y[i] = 0;\n    mzx_world->pl_saved_board[i] = 0;\n  }\n\n  mzx_world->saved_pl_color = 27;\n  mzx_world->player_restart_x = 0;\n  mzx_world->player_restart_y = 0;\n  mzx_world->under_player_id = 0;\n  mzx_world->under_player_color = 7;\n  mzx_world->under_player_param = 0;\n\n  mzx_world->commands = 40;\n  mzx_world->commands_stop = 2000000;\n\n  default_scroll_values(mzx_world);\n\n  scroll_color = 15;\n\n  mzx_world->lock_speed = 0;\n  mzx_world->mzx_speed = get_config()->mzx_speed;\n\n  assert(mzx_world->input_file == NULL);\n  assert(mzx_world->output_file == NULL);\n  assert(mzx_world->input_is_dir == false);\n\n  mzx_world->target_where = TARGET_NONE;\n}\n\nboolean reload_world(struct world *mzx_world, const char *file, boolean *faded)\n{\n  char name[BOARD_NAME_SIZE];\n  int version;\n\n  struct zip_archive *zp;\n  vfile *vf;\n\n  try_load_world(mzx_world, &zp, &vf, file, false, &version, name);\n\n  if(!zp && !vf)\n    return false;\n\n  if(mzx_world->active)\n  {\n    clear_world(mzx_world);\n    clear_global_data(mzx_world);\n  }\n\n  // Always switch back to regular mode before loading the world,\n  // because we want the world's intrinsic palette to be applied.\n  set_screen_mode(0);\n  smzx_palette_loaded(false);\n  set_palette_intensity(100);\n\n  default_sprite_data(mzx_world);\n  default_vlayer(mzx_world);\n\n  load_world(mzx_world, zp, vf, file, false, version, name, faded);\n  default_global_data(mzx_world);\n  *faded = false;\n\n  // Hack: 1.x seems to not clear some things...\n  v1_load_globals_from_board(mzx_world);\n\n  // Now that the world's loaded, fix the save path.\n  {\n    char save_name[MAX_PATH];\n    path_get_filename(save_name, MAX_PATH, curr_sav);\n\n    vgetcwd(curr_sav, MAX_PATH);\n    path_append(curr_sav, MAX_PATH, save_name);\n  }\n\n  return true;\n}\n\nboolean reload_savegame(struct world *mzx_world, const char *file,\n boolean *faded)\n{\n  char ignore[BOARD_NAME_SIZE];\n  int version;\n\n  struct zip_archive *zp;\n  vfile *vf;\n\n  try_load_world(mzx_world, &zp, &vf, file, true, &version, ignore);\n\n  if(!zp && !vf)\n    return false;\n\n  // It is, so wipe the old world\n  if(mzx_world->active)\n  {\n    clear_world(mzx_world);\n    clear_global_data(mzx_world);\n  }\n\n  default_sprite_data(mzx_world);\n  default_vlayer(mzx_world);\n\n  // And load the new one\n  load_world(mzx_world, zp, vf, file, true, version, NULL, faded);\n  return true;\n}\n\nboolean reload_swap(struct world *mzx_world, const char *file, boolean *faded)\n{\n  char name[BOARD_NAME_SIZE];\n  char file_name[MAX_PATH];\n  int version;\n\n  struct zip_archive *zp;\n  vfile *vf;\n\n  try_load_world(mzx_world, &zp, &vf, file, false, &version, name);\n\n  if(!zp && !vf)\n    return false;\n\n  if(mzx_world->active)\n    clear_world(mzx_world);\n\n  load_world(mzx_world, zp, vf, file, false, version, name, faded);\n\n  // Change to the first board of the new world.\n  change_board(mzx_world, mzx_world->first_board);\n  change_board_set_values(mzx_world);\n  change_board_load_assets(mzx_world);\n\n  // Give curr_file a full path\n  path_get_filename(file_name, MAX_PATH, file);\n  vgetcwd(curr_file, MAX_PATH);\n  path_append(curr_file, MAX_PATH, file_name);\n\n  return true;\n}\n\n// This only clears boards, no global data. Useful for swap world,\n// when you want to maintain counters and sprites and all that.\n\nvoid clear_world(struct world *mzx_world)\n{\n  // Do this before loading, when there's a world\n\n  int i;\n  int num_boards = mzx_world->num_boards;\n  struct board **board_list = mzx_world->board_list;\n\n  memset(mzx_world->status_counters_shown, 0, NUM_STATUS_COUNTERS * COUNTER_NAME_SIZE);\n\n  for(i = 0; i < num_boards; i++)\n  {\n    if(mzx_world->current_board_id != i)\n      clear_board_from_extram(board_list[i]);\n    clear_board(board_list[i]);\n  }\n  free(board_list);\n\n  // Make sure we nuke the duplicate too, if it exists.\n  if(mzx_world->temporary_board)\n  {\n    clear_board(mzx_world->current_board);\n  }\n\n  mzx_world->temporary_board = 0;\n  mzx_world->current_board_id = 0;\n  mzx_world->current_board = NULL;\n  mzx_world->board_list = NULL;\n\n  clear_robot_contents(&mzx_world->global_robot);\n\n  if(!mzx_world->input_is_dir && mzx_world->input_file)\n  {\n    vfclose(mzx_world->input_file);\n    mzx_world->input_file = NULL;\n  }\n  else\n\n  if(mzx_world->input_is_dir)\n  {\n    vdir_close(mzx_world->input_directory);\n    mzx_world->input_directory = NULL;\n    mzx_world->input_is_dir = false;\n  }\n\n  if(mzx_world->output_file)\n  {\n    vfclose(mzx_world->output_file);\n    mzx_world->output_file = NULL;\n  }\n  mzx_world->output_mode = FWRITE_MODE_UNKNOWN;\n\n  mzx_world->current_cycle_odd = false;\n  mzx_world->current_cycle_frozen = false;\n  mzx_world->player_shoot_cooldown = 0;\n  mzx_world->active = 0;\n\n  audio_end_sample();\n}\n\n// This clears the rest of the stuff.\n\nvoid clear_global_data(struct world *mzx_world)\n{\n  int i;\n  struct sprite **sprite_list = mzx_world->sprite_list;\n\n  free(mzx_world->vlayer_chars);\n  free(mzx_world->vlayer_colors);\n  mzx_world->vlayer_chars = NULL;\n  mzx_world->vlayer_colors = NULL;\n\n  // Clear all counters out of the counter list\n  clear_counter_list(&(mzx_world->counter_list));\n\n  // Clear all strings out of the string list\n  clear_string_list(&(mzx_world->string_list));\n\n  // This needs to be done whenever the counters/strings are cleared.\n  if(debug_robot_reset)\n    debug_robot_reset(mzx_world);\n\n  for(i = 0; i < MAX_SPRITES; i++)\n  {\n    free(sprite_list[i]);\n  }\n\n  free(sprite_list);\n  mzx_world->sprite_list = NULL;\n  mzx_world->num_sprites = 0;\n  mzx_world->num_sprites_allocated = 0;\n\n  free(mzx_world->collision_list);\n  mzx_world->collision_list = NULL;\n  mzx_world->collision_count = 0;\n\n  mzx_world->active_sprites = 0;\n  mzx_world->sprite_y_order = 0;\n\n  mzx_world->vlayer_size = 0;\n  mzx_world->vlayer_width = 0;\n  mzx_world->vlayer_height = 0;\n\n  mzx_world->output_file_name[0] = 0;\n  mzx_world->input_file_name[0] = 0;\n\n  mzx_world->robotic_save_type = SAVE_NONE;\n\n  sfx_free(&mzx_world->custom_sfx);\n\n  mzx_world->max_samples = -1;\n  audio_set_max_samples(mzx_world->max_samples);\n\n  mzx_world->joystick_simulate_keys = true;\n  joystick_set_game_bindings(mzx_world->joystick_simulate_keys);\n\n  mzx_world->bomb_type = 1;\n  mzx_world->dead = false;\n}\n\nvoid default_scroll_values(struct world *mzx_world)\n{\n  mzx_world->scroll_base_color = 143;\n  mzx_world->scroll_corner_color = 135;\n  mzx_world->scroll_pointer_color = 128;\n  mzx_world->scroll_title_color = 143;\n  mzx_world->scroll_arrow_color = 142;\n}\n\nvoid remap_vlayer(struct world *mzx_world,\n int new_width, int new_height)\n{\n  // Given a new width and height, translate the vlayer such that the area\n  // where the old dimensions and the new dimensions overlap is the same, and\n  // everything else is blank.\n\n  char *vlayer_chars = mzx_world->vlayer_chars;\n  char *vlayer_colors = mzx_world->vlayer_colors;\n  int vlayer_size = mzx_world->vlayer_size;\n\n  int old_width = mzx_world->vlayer_width;\n  int old_height = mzx_world->vlayer_height;\n  int new_pos = 0;\n  int old_pos = 0;\n  int i;\n\n  if(old_width * old_height > vlayer_size)\n  {\n    old_height = vlayer_size / old_width;\n  }\n\n  if(new_width < old_width)\n  {\n    // Decreased width -- go from start to end\n\n    for(i = 0; i < old_height; i++)\n    {\n      memmove(vlayer_chars + new_pos, vlayer_chars + old_pos, new_width);\n      memmove(vlayer_colors + new_pos, vlayer_colors + old_pos, new_width);\n\n      old_pos += old_width;\n      new_pos += new_width;\n    }\n\n    // Clear everything else\n    memset(vlayer_chars + new_pos, 32, vlayer_size - new_pos);\n    memset(vlayer_colors + new_pos, 7, vlayer_size - new_pos);\n  }\n  else\n\n  if(new_width > old_width)\n  {\n    // Increased width -- go from end to start\n    // Clear blank areas after every copy\n    int clear_width = new_width - old_width;\n\n    new_pos = new_width * (new_height - 1);\n    old_pos = old_width * (new_height - 1);\n\n    for(i = 0; i < new_height; i++)\n    {\n      memmove(vlayer_chars + new_pos, vlayer_chars + old_pos, new_width);\n      memmove(vlayer_colors + new_pos, vlayer_colors + old_pos, new_width);\n\n      // Clear blank area\n      memset(vlayer_chars + new_pos + old_width, 32, clear_width);\n      memset(vlayer_colors + new_pos + old_width, 7, clear_width);\n\n      old_pos -= old_width;\n      new_pos -= new_width;\n    }\n\n    // Clear anything after the new end\n    new_pos = new_width * new_height;\n    memset(vlayer_chars + new_pos, 32, vlayer_size - new_pos);\n    memset(vlayer_colors + new_pos, 7, vlayer_size - new_pos);\n  }\n\n  mzx_world->vlayer_width = new_width;\n  mzx_world->vlayer_height = new_height;\n}\n"
  },
  {
    "path": "src/world.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __WORLD_H\n#define __WORLD_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"world_struct.h\"\n#include \"io/vfile.h\"\n\n/* When making new versions, change the number below, and\n * change the number in the version strings to follow. From now on,\n * be sure that the last two characters are the hex equivalent of the\n * MINOR version number.\n */\n\n/**\n * READ THIS -- MAGIC INFO\n *\n * World files:\n *\n *  MZX - Ver 1.x MegaZeux                  (mzx_world->version == 0x0100)\n *  MZ2 - Ver 2.x MegaZeux                  (mzx_world->version == 0x0205)\n *  MZA - Ver 2.51S1 Megazeux               (mzx_world->version == 0x0208)\n *  M\\x02\\x09 - 2.5.1spider2+\n *  M\\x02\\x11 - Used for decrypted worlds.  (spider octal \"\\011\" misread as hex)\n *  M\\x02\\x32 - MZX 2.62 and MZX 2.62b      (from octal \"\\062\")\n *  M\\x02\\x41 - MZX 2.65                    (from decimal \"65\")\n *  M\\x02\\x44 - MZX 2.68                    (from decimal \"68\")\n *  M\\x02\\x45 - MZX 2.69                    (from decimal \"69\")\n *  M\\x02\\x46 - MZX 2.69b\n *  M\\x02\\x48 - MZX 2.69c\n *  M\\x02\\x49 - MZX 2.70\n *  M\\x02\\x50 - MZX 2.80 (non-DOS)          (from decimal \"80\")\n *  M\\x02\\x51 - MZX 2.81                    (and so on...)\n *  M\\x02\\x52 - MZX 2.82\n *  M\\x02\\x53 - MZX 2.83\n *  M\\x02\\x54 - MZX 2.84\n *  M\\x02\\x5A - MZX 2.90\n *  M\\x02\\x5B - MZX 2.91\n *  M\\x02\\x5C - MZX 2.92\n *  M\\x02\\x5D - MZX 2.93\n *\n * Save files:\n *\n *  MZSV2 - Ver 2.x MegaZeux                (mzx_world->version == 0x0205)\n *  MZXSA - Ver 2.51S1 MegaZeux             (mzx_world->version == 0x0208)\n *  MZS\\x02\\x09 - 2.5.1spider2+\n *  MZS\\x02\\x32 - MZX 2.62 and MZX 2.62b\n *  MZS\\x02\\x41 - MZX 2.65\n *  MZS\\x02\\x44 - MZX 2.68\n *  MZS\\x02\\x45 - MZX 2.69\n *  MZS\\x02\\x46 - MZX 2.69b\n *  MZS\\x02\\x48 - MZX 2.69c\n *  MZS\\x02\\x49 - MZX 2.70\n *  MZS\\x02\\x50 - MZX 2.80 (non-DOS)\n *  MZS\\x02\\x51 - MZX 2.81\n *  MZS\\x02\\x52 - MZX 2.82\n *  MZS\\x02\\x53 - MZX 2.83\n *  MZS\\x02\\x54 - MZX 2.84\n *  MZS\\x02\\x5A - MZX 2.90\n *  MZS\\x02\\x5B - MZX 2.91\n *  MZS\\x02\\x5C - MZX 2.92\n *  MZS\\x02\\x5D - MZX 2.93\n *\n * Board files follow a similar pattern to world files. Versions prior to\n * 2.51S1 are \"MB2\". For versions greater than 2.51S1, they match the\n * world file magic. For all boards, the first byte is always 0xFF.\n *\n * As of 2.80+, all letters after the name denote bug fixes/minor additions\n * and may NOT have different save formats. If a save format change is\n * necessitated, a numerical change must be enacted.\n */\n\nenum mzx_version\n{\n  V100            = 0x0100, // Magic: MZX\n  V200            = 0x0205, // Magic: MZ2\n  V251            = 0x0205,\n  V251s1          = 0x0208, // Magic: MZA\n  V251s2          = 0x0209,\n  V251s3          = 0x0209,\n  V260            = 0x0209,\n  V261            = 0x0209,\n  VERSION_DECRYPT = 0x0211, // Special version used for decrypted worlds only.\n  V262            = 0x0232,\n  V265            = 0x0241, // NOTE: 2.68 uses 0x241 for worlds and 0x244 for\n  V268            = 0x0244, // saves. Leave any 2.68 version checks at V268.\n  V269            = 0x0245,\n  V269b           = 0x0246,\n  V269c           = 0x0248,\n  V270            = 0x0249,\n  VERSION_PORT    = 0x0250, // For checks explicitly differentiating DOS and port\n  V280            = 0x0250,\n  V281            = 0x0251,\n  V282            = 0x0252,\n  V283            = 0x0253,\n  V284            = 0x0254,\n  V290            = 0x025A,\n  V291            = 0x025B,\n  V292            = 0x025C,\n  V293            = 0x025D,\n  V294            = 0x025E,\n#ifdef CONFIG_DEBYTECODE\n  VERSION_SOURCE  = 0x0300, // For checks dependent on Robotic source changes\n  V300            = 0x0300,\n#endif\n};\n\n/* The magic stamp for worlds, boards or SAV files created with this version\n * of MegaZeux. If you introduce a counter or any other incompatible change,\n * such as altering semantics or actually changing the binary format, this\n * value MUST be bumped.\n */\n#define MZX_VERSION      (V293)\n\n/* The world version that worlds will be saved as when Export Downver. World\n * is used from the editor. This function is also fulfilled by the downver util.\n *\n * If you increase MZX_VERSION, always make sure this is updated with its\n * previous value; this way, users can always downgrade their work to an\n * older version (if it at all makes sense to do so).\n */\n#define MZX_VERSION_PREV (V292)\n\n// This is the last version of MegaZeux to use the legacy world format.\n#define MZX_LEGACY_FORMAT_VERSION (V284)\n\n// FIXME: hack\n#ifdef CONFIG_DEBYTECODE\n#undef  MZX_VERSION_PREV\n#define MZX_VERSION_PREV (V293)\n#undef  MZX_VERSION\n#define MZX_VERSION      (VERSION_SOURCE)\n#endif\n\nenum val_result\n{\n  VAL_SUCCESS,\n  VAL_VERSION,    // Version issue\n  VAL_INVALID,    // Failed validation\n  VAL_TRUNCATED,  // Passed validation until it hit EOF\n  VAL_MISSING,    // file or ptr location in file does not exist\n  VAL_PROTECTED,  // Legacy file is protected, needs decryption\n  VAL_ABORTED,    // Load aborted by user\n};\n\nCORE_LIBSPEC int world_magic(const char magic_string[3]);\nCORE_LIBSPEC int save_magic(const char magic_string[5]);\nCORE_LIBSPEC int get_version_string(char buffer[16], enum mzx_version version);\n\nCORE_LIBSPEC int save_world(struct world *mzx_world, const char *file,\n boolean savegame, int file_version);\nCORE_LIBSPEC boolean reload_world(struct world *mzx_world, const char *file,\n boolean *faded);\nCORE_LIBSPEC void clear_world(struct world *mzx_world);\nCORE_LIBSPEC void clear_global_data(struct world *mzx_world);\nCORE_LIBSPEC void default_scroll_values(struct world *mzx_world);\n\nCORE_LIBSPEC void change_board(struct world *mzx_world, int board_id);\nCORE_LIBSPEC void change_board_set_values(struct world *mzx_world);\nCORE_LIBSPEC void change_board_load_assets(struct world *mzx_world);\n\nCORE_LIBSPEC void remap_vlayer(struct world *mzx_world,\n int new_width, int new_height);\n\nboolean reload_savegame(struct world *mzx_world, const char *file,\n boolean *faded);\nboolean reload_swap(struct world *mzx_world, const char *file, boolean *faded);\n\nvoid save_counters_file(struct world *mzx_world, const char *file);\nint load_counters_file(struct world *mzx_world, const char *file);\n\n#ifdef CONFIG_LOADSAVE_METER\nvoid meter_update_screen(int *curr, int target);\nvoid meter_restore_screen(void);\nvoid meter_initial_draw(int curr, int target, const char *title);\n#endif\n\n#ifdef CONFIG_EDITOR\n\n#include <stdio.h>\nstruct zip_archive;\n\nCORE_LIBSPEC void try_load_world(struct world *mzx_world,\n struct zip_archive **zp, vfile **vf, const char *file, boolean savegame,\n int *file_version, char *name);\n\nCORE_LIBSPEC void default_vlayer(struct world *mzx_world);\nCORE_LIBSPEC void default_global_data(struct world *mzx_world);\n\nCORE_LIBSPEC void set_update_done(struct world *mzx_world);\nCORE_LIBSPEC void refactor_board_list(struct world *mzx_world,\n struct board **new_board_list, int new_list_size,\n int *board_id_translation_list);\nCORE_LIBSPEC void optimize_null_boards(struct world *mzx_world);\n\n#endif // CONFIG_EDITOR\n\n__M_END_DECLS\n\n#endif // __WORLD_H\n"
  },
  {
    "path": "src/world_format.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2017-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __WORLD_FORMAT_H\n#define __WORLD_FORMAT_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"io/memfile.h\"\n#include \"io/zip.h\"\n#include \"util.h\" // CLAMP\n\n// Data and functions for the ZIP-based world/board/robot format.\n\n\n/*********/\n/* Files */\n/*********/\n\nenum world_file_id\n{\n  FILE_ID_NONE                    = 0x0000,\n  FILE_ID_WORLD_INFO              = 0x0001, // properties file\n  FILE_ID_WORLD_GLOBAL_ROBOT      = 0x0004, // properties file\n  FILE_ID_WORLD_SFX               = 0x0007, // properties file or data, 50 * 69\n  FILE_ID_WORLD_CHARS             = 0x0008, // data, 3584*15\n  FILE_ID_WORLD_PAL               = 0x0009, // data, PAL_SIZE * 3\n  FILE_ID_WORLD_PAL_SMZX          = 0x000A, // data, SMZX_PAL_SIZE * 3\n  FILE_ID_WORLD_PAL_INDEX         = 0x000B, // data, 1024\n  FILE_ID_WORLD_VCO               = 0x000C, // data\n  FILE_ID_WORLD_VCH               = 0x000D, // data\n  FILE_ID_WORLD_PAL_INTENSITY     = 0x000E, // data, PAL_SIZE * 4\n  FILE_ID_WORLD_PAL_INTENSITY_SMZX= 0x000F, // data, SMZX_PAL_SIZE * 4\n\n  FILE_ID_WORLD_SPRITES           = 0x0080, // properties file\n  FILE_ID_WORLD_COUNTERS          = 0x0081, // counter format, use stream\n  FILE_ID_WORLD_STRINGS           = 0x0082, // string format, use stream\n\n  FILE_ID_BOARD_INFO              = 0x0100, // properties file (board_id)\n  FILE_ID_BOARD_BID               = 0x0101, // data\n  FILE_ID_BOARD_BPR               = 0x0102, // data\n  FILE_ID_BOARD_BCO               = 0x0103, // data\n  FILE_ID_BOARD_UID               = 0x0104, // data\n  FILE_ID_BOARD_UPR               = 0x0105, // data\n  FILE_ID_BOARD_UCO               = 0x0106, // data\n  FILE_ID_BOARD_OCH               = 0x0107, // data\n  FILE_ID_BOARD_OCO               = 0x0108, // data\n\n  FILE_ID_ROBOT                   = 0x1000, // prop. file (board_id + robot_id)\n  FILE_ID_SCROLL                  = 0x2000, // prop. file (board_id + robot_id)\n  FILE_ID_SENSOR                  = 0x3000  // prop. file (board_id + robot_id)\n};\n\n\n/*******************/\n/* Properties Data */\n/*******************/\n\n#define PROP_HEADER_SIZE  6\n#define PROP_EOF_SIZE     2\n\n#define COUNT_STATCTR_PROPS (NUM_STATUS_COUNTERS * (2))\n#define BOUND_STATCTR_PROPS (NUM_STATUS_COUNTERS * (1 + COUNTER_NAME_SIZE))\n\n#define STATCTR_PROP_SIZE                   \\\n(                                           \\\n  BOUND_STATCTR_PROPS +                     \\\n  COUNT_STATCTR_PROPS * PROP_HEADER_SIZE +  \\\n  PROP_EOF_SIZE                             \\\n)\n\nenum status_counters_prop\n{\n  STATCTRPROP_SET_ID              = 0x0001, //  1\n  STATCTRPROP_NAME                = 0x0002, //  COUNTER_NAME_SIZE\n};\n\n// IF YOU ADD ANYTHING, MAKE SURE THIS GETS UPDATED!\n\n#define COUNT_WORLD_PROPS (              1 + 3 +   4 + 16 + 4 +                 1)\n#define BOUND_WORLD_PROPS (BOARD_NAME_SIZE + 5 + 455 + 24 + 9 + STATCTR_PROP_SIZE)\n\n#define COUNT_SAVE_PROPS  ( 2 +  33 +          3 +        1)\n#define BOUND_SAVE_PROPS  ( 2 + 121 + 3*MAX_PATH + NUM_KEYS)\n\n// For world files, use WORLD_PROP_SIZE\n// For save files, use WORLD_PROP_SIZE + SAVE_PROP_SIZE\n\n#define WORLD_PROP_SIZE                   \\\n(                                         \\\n  BOUND_WORLD_PROPS +                     \\\n  COUNT_WORLD_PROPS * PROP_HEADER_SIZE +  \\\n  PROP_EOF_SIZE                           \\\n)\n\n#define SAVE_PROP_SIZE                    \\\n(                                         \\\n  BOUND_SAVE_PROPS +                      \\\n  COUNT_SAVE_PROPS * PROP_HEADER_SIZE     \\\n)\n\nenum world_prop\n{\n  WPROP_EOF                       = 0x0000, // Size\n\n  // Header redundant properties      4 (6)     5 + BOARD_NAME_SIZE (+2)\n  WPROP_WORLD_NAME                = 0x0001, //  BOARD_NAME_SIZE\n  WPROP_WORLD_VERSION             = 0x0002, //   2\n  WPROP_FILE_VERSION              = 0x0003, //   2\n  WPROP_SAVE_START_BOARD          = 0x0004, //  (1)\n  WPROP_SAVE_TEMPORARY_BOARD      = 0x0005, //  (1)\n  WPROP_NUM_BOARDS                = 0x0008, //   1\n\n  // ID Chars                            4     455\n  WPROP_ID_CHARS                  = 0x0010, // 323\n  WPROP_ID_MISSILE_COLOR          = 0x0011, //   1\n  WPROP_ID_BULLET_COLOR           = 0x0012, //   3\n  WPROP_ID_DMG                    = 0x0013, // 128\n\n  // Status counters\n  WPROP_STATUS_COUNTERS           = 0x0018,\n\n  // Global properties                  16      24\n  WPROP_EDGE_COLOR                = 0x0020, //   1\n  WPROP_FIRST_BOARD               = 0x0021, //   1\n  WPROP_ENDGAME_BOARD             = 0x0022, //   1\n  WPROP_DEATH_BOARD               = 0x0023, //   1\n  WPROP_ENDGAME_X                 = 0x0024, //   2\n  WPROP_ENDGAME_Y                 = 0x0025, //   2\n  WPROP_GAME_OVER_SFX             = 0x0026, //   1\n  WPROP_DEATH_X                   = 0x0027, //   2\n  WPROP_DEATH_Y                   = 0x0028, //   2\n  WPROP_STARTING_LIVES            = 0x0029, //   2\n  WPROP_LIVES_LIMIT               = 0x002A, //   2\n  WPROP_STARTING_HEALTH           = 0x002B, //   2\n  WPROP_HEALTH_LIMIT              = 0x002C, //   2\n  WPROP_ENEMY_HURT_ENEMY          = 0x002D, //   1\n  WPROP_CLEAR_ON_EXIT             = 0x002E, //   1\n  WPROP_ONLY_FROM_SWAP            = 0x002F, //   1\n\n  // Global properties II                4       9\n  WPROP_SMZX_MODE                 = 0x8030, //   1\n  WPROP_VLAYER_WIDTH              = 0x8031, //   2\n  WPROP_VLAYER_HEIGHT             = 0x8032, //   2\n  WPROP_VLAYER_SIZE               = 0x8033, //   4\n\n  // Save properties                  32+4     105 + 3 MAX_PATH + NUM_KEYS\n  WPROP_REAL_MOD_PLAYING          = 0x8040, // MAX_PATH\n  WPROP_MZX_SPEED                 = 0x8041, //   1\n  WPROP_LOCK_SPEED                = 0x8042, //   1\n  WPROP_COMMANDS                  = 0x8043, //   4\n  WPROP_COMMANDS_STOP             = 0x8044, //   4\n  WPROP_SAVED_POSITIONS           = 0x8048, //  40 (2+2+1)*8\n  WPROP_UNDER_PLAYER              = 0x8049, //   3 (1+1+1)\n  WPROP_PLAYER_RESTART_X          = 0x804A, //   2\n  WPROP_PLAYER_RESTART_Y          = 0x804B, //   2\n  WPROP_SAVED_PL_COLOR            = 0x804C, //   1\n  WPROP_KEYS                      = 0x804D, // NUM_KEYS\n  WPROP_BLIND_DUR                 = 0x8050, //   4\n  WPROP_FIREWALKER_DUR            = 0x8051, //   4\n  WPROP_FREEZE_TIME_DUR           = 0x8052, //   4\n  WPROP_SLOW_TIME_DUR             = 0x8053, //   4\n  WPROP_WIND_DUR                  = 0x8054, //   4\n  WPROP_SCROLL_BASE_COLOR         = 0x8058, //   1\n  WPROP_SCROLL_CORNER_COLOR       = 0x8059, //   1\n  WPROP_SCROLL_POINTER_COLOR      = 0x805A, //   1\n  WPROP_SCROLL_TITLE_COLOR        = 0x805B, //   1\n  WPROP_SCROLL_ARROW_COLOR        = 0x805C, //   1\n  WPROP_MESG_EDGES                = 0x8060, //   1\n  WPROP_BI_SHOOT_STATUS           = 0x8061, //   1\n  WPROP_BI_MESG_STATUS            = 0x8062, //   1\n  WPROP_FADED                     = 0x8063, //   1\n  WPROP_INPUT_FILE_NAME           = 0x8070, // MAX_PATH\n  WPROP_INPUT_POS                 = 0x8074, //   4\n  WPROP_FREAD_DELIMITER           = 0x8075, //   4\n  WPROP_OUTPUT_FILE_NAME          = 0x8078, // MAX_PATH\n  WPROP_OUTPUT_POS                = 0x807C, //   4\n  WPROP_FWRITE_DELIMITER          = 0x807D, //   4\n  WPROP_OUTPUT_MODE               = 0x807E, //   1\n  WPROP_MULTIPLIER                = 0x8080, //   4\n  WPROP_DIVIDER                   = 0x8081, //   4\n  WPROP_C_DIVISIONS               = 0x8082, //   4\n  WPROP_MAX_SAMPLES               = 0x8090, //   4\n  WPROP_SMZX_MESSAGE              = 0x8091, //   1\n  WPROP_JOY_SIMULATE_KEYS         = 0x8092, //   1\n};\n\n\n#define COUNT_SFX_PROPS           (MAX_NUM_SFX * 3)\n#define BOUND_SFX_PROPS           (MAX_NUM_SFX * (1 + 11 + MAX_SFX_SIZE))\n\n#define SFX_PROPS_SIZE                  \\\n(                                       \\\n  BOUND_SFX_PROPS +                     \\\n  COUNT_SFX_PROPS * PROP_HEADER_SIZE +  \\\n  PROP_EOF_SIZE                         \\\n)\n\nenum sfx_prop\n{\n  SFXPROP_EOF                     = 0x0000,\n  SFXPROP_SET_ID                  = 0x0001, // 1\n  SFXPROP_STRING                  = 0x0002, // MAX_SFX_SIZE\n  SFXPROP_LABEL                   = 0x0003, // 11\n};\n\n\n// All sprite fields are saved as dwords (except SET_ID), so bound them as such\n\n#define COUNT_SPRITE_PROPS        16\n#define BOUND_SPRITE_PROPS        61\n#define COUNT_SPRITE_ONCE_PROPS   5\n#define BOUND_SPRITE_ONCE_PROPS   16 // + (collision_count * 4)\n\n#define SPRITE_PROPS_SIZE                       \\\n(                                               \\\n  (                                             \\\n    BOUND_SPRITE_PROPS +                        \\\n    COUNT_SPRITE_PROPS * PROP_HEADER_SIZE       \\\n  ) * MAX_SPRITES +                             \\\n  (                                             \\\n    BOUND_SPRITE_ONCE_PROPS +                   \\\n    COUNT_SPRITE_ONCE_PROPS * PROP_HEADER_SIZE  \\\n  ) +                                           \\\n  PROP_EOF_SIZE                                 \\\n)\n\nenum sprite_prop\n{\n  SPROP_EOF                       = 0x00,\n\n  // For each sprite\n  SPROP_SET_ID                    = 0x01, // 1, used to select a sprite #\n  SPROP_X                         = 0x02, // 2 (ignore size; we save as dwords)\n  SPROP_Y                         = 0x03, // 2\n  SPROP_REF_X                     = 0x04, // 2\n  SPROP_REF_Y                     = 0x05, // 2\n  SPROP_COLOR                     = 0x06, // 1\n  SPROP_FLAGS                     = 0x07, // 1\n  SPROP_WIDTH                     = 0x08, // 1\n  SPROP_HEIGHT                    = 0x09, // 1\n  SPROP_COL_X                     = 0x0A, // 1\n  SPROP_COL_Y                     = 0x0B, // 1\n  SPROP_COL_WIDTH                 = 0x0C, // 1\n  SPROP_COL_HEIGHT                = 0x0D, // 1\n  SPROP_TRANSPARENT_COLOR         = 0x0E, // 4\n  SPROP_CHARSET_OFFSET            = 0x0F, // 4\n  SPROP_Z                         = 0x10, // 4\n\n  // Only once\n  SPROP_ACTIVE_SPRITES            = 0x8000, // 1\n  SPROP_SPRITE_Y_ORDER            = 0x8001, // 1\n  SPROP_COLLISION_COUNT           = 0x8002, // 2\n  SPROP_COLLISION_LIST            = 0x8003, // collision count * 4\n  SPROP_SPRITE_NUM                = 0x8004, // 4\n};\n\n\n#define COUNT_BOARD_PROPS (              1 +  7 +          3 + 27)\n#define BOUND_BOARD_PROPS (BOARD_NAME_SIZE + 10 + 3*MAX_PATH + 28)\n\n#define COUNT_BOARD_SAVE_PROPS (             2 + 20)\n#define BOUND_BOARD_SAVE_PROPS (2*ROBOT_MAX_TR + 30)\n\n// For world files, use BOARD_PROPS_SIZE\n// For save files, use BOARD_PROPS_SIZE + BOARD_SAVE_PROPS_SIZE\n\n#define BOARD_PROPS_SIZE                      \\\n(                                             \\\n  BOUND_BOARD_PROPS +                         \\\n  COUNT_BOARD_PROPS * PROP_HEADER_SIZE +      \\\n  PROP_EOF_SIZE                               \\\n)\n\n#define BOARD_SAVE_PROPS_SIZE                 \\\n(                                             \\\n  BOUND_BOARD_SAVE_PROPS +                    \\\n  COUNT_BOARD_SAVE_PROPS * PROP_HEADER_SIZE   \\\n)\n\nenum board_prop\n{\n  BPROP_EOF                       = 0x0000,\n\n  // Essential                           8     10 + BOARD_NAME_SIZE\n  BPROP_BOARD_NAME                = 0x0001, // BOARD_NAME_SIZE\n  BPROP_BOARD_WIDTH               = 0x0002, // 2\n  BPROP_BOARD_HEIGHT              = 0x0003, // 2\n  BPROP_OVERLAY_MODE              = 0x0004, // 1\n  BPROP_NUM_ROBOTS                = 0x0005, // 1\n  BPROP_NUM_SCROLLS               = 0x0006, // 1\n  BPROP_NUM_SENSORS               = 0x0007, // 1\n  BPROP_FILE_VERSION              = 0x0008, // 2\n\n  // Non-essential                      27     28 + 3 MAX_PATH\n  BPROP_MOD_PLAYING               = 0x0010, // MAX_PATH\n  BPROP_VIEWPORT_X                = 0x0011, // 1\n  BPROP_VIEWPORT_Y                = 0x0012, // 1\n  BPROP_VIEWPORT_WIDTH            = 0x0013, // 1\n  BPROP_VIEWPORT_HEIGHT           = 0x0014, // 1\n  BPROP_CAN_SHOOT                 = 0x0015, // 1\n  BPROP_CAN_BOMB                  = 0x0016, // 1\n  BPROP_FIRE_BURN_BROWN           = 0x0017, // 1\n  BPROP_FIRE_BURN_SPACE           = 0x0018, // 1\n  BPROP_FIRE_BURN_FAKES           = 0x0019, // 1\n  BPROP_FIRE_BURN_TREES           = 0x001A, // 1\n  BPROP_EXPLOSIONS_LEAVE          = 0x001B, // 1\n  BPROP_SAVE_MODE                 = 0x001C, // 1\n  BPROP_FOREST_BECOMES            = 0x001D, // 1\n  BPROP_COLLECT_BOMBS             = 0x001E, // 1\n  BPROP_FIRE_BURNS                = 0x001F, // 1\n  BPROP_BOARD_N                   = 0x0020, // 1\n  BPROP_BOARD_S                   = 0x0021, // 1\n  BPROP_BOARD_E                   = 0x0022, // 1\n  BPROP_BOARD_W                   = 0x0023, // 1\n  BPROP_RESTART_IF_ZAPPED         = 0x0024, // 1\n  BPROP_TIME_LIMIT                = 0x0025, // 2\n  BPROP_PLAYER_NS_LOCKED          = 0x0026, // 1\n  BPROP_PLAYER_EW_LOCKED          = 0x0027, // 1\n  BPROP_PLAYER_ATTACK_LOCKED      = 0x0028, // 1\n  BPROP_RESET_ON_ENTRY            = 0x0029, // 1\n  BPROP_CHARSET_PATH              = 0x002A, // MAX_PATH\n  BPROP_PALETTE_PATH              = 0x002B, // MAX_PATH\n  BPROP_RESET_ON_ENTRY_SAME_BOARD = 0x002C, // 1\n  BPROP_DRAGONS_CAN_RANDOMLY_MOVE = 0x002D, // 1\n\n  // Save                               17     25 + 2 ROBOT_MAX_TR\n  BPROP_SCROLL_X                  = 0x0100, // 2\n  BPROP_SCROLL_Y                  = 0x0101, // 2\n  BPROP_LOCKED_X                  = 0x0102, // 2\n  BPROP_LOCKED_Y                  = 0x0103, // 2\n  BPROP_PLAYER_LAST_DIR           = 0x0104, // 1\n  BPROP_LAZWALL_START             = 0x010A, // 1\n  BPROP_LAST_KEY                  = 0x010B, // 1\n  BPROP_NUM_INPUT                 = 0x010C, // 4\n  BPROP_INPUT_SIZE                = 0x010D, // 4 (IS SEPARATE FROM INPUT_STRING)\n  BPROP_INPUT_STRING              = 0x010E, // ROBOT_MAX_TR\n  BRPOP_BOTTOM_MESG               = 0x0110, // ROBOT_MAX_TR\n  BPROP_BOTTOM_MESG_TIMER         = 0x0111, // 1\n  BPROP_BOTTOM_MESG_ROW           = 0x0112, // 1\n  BPROP_BOTTOM_MESG_COL           = 0x0113, // 1\n  BPROP_VOLUME                    = 0x0114, // 1\n  BPROP_VOLUME_INC                = 0x0115, // 1\n  BPROP_VOLUME_TARGET             = 0x0116, // 1\n  BPROP_BLIND_DUR                 = 0x0117, // 1\n  BPROP_FIREWALKER_DUR            = 0x0118, // 1\n  BPROP_FREEZE_TIME_DUR           = 0x0119, // 1\n  BPROP_SLOW_TIME_DUR             = 0x011a, // 1\n  BPROP_WIND_DUR                  = 0x011b, // 1\n};\n\n\n#define COUNT_ROBOT_PROPS (1 + 3 + 1)\n#define BOUND_ROBOT_PROPS (0 + 5 + 0) // +name, +prog OR source\n\n#define COUNT_ROBOT_SAVE_PROPS (11 + 2 +    1 + 1 + 1)\n#define BOUND_ROBOT_SAVE_PROPS (17 + 8 + 4*32 + 0 + 1) // +stack\n\n// For world files, use ROBOT_PROPS_SIZE\n// For save files, use ROBOT_PROPS_SIZE + ROBOT_SAVE_PROPS_SIZE\n\n#define ROBOT_PROPS_SIZE                          \\\n(                                                 \\\n  BOUND_ROBOT_PROPS +                             \\\n  COUNT_ROBOT_PROPS * PROP_HEADER_SIZE +          \\\n  PROP_EOF_SIZE                                   \\\n)\n\n#define ROBOT_SAVE_PROPS_SIZE                     \\\n(                                                 \\\n  COUNT_ROBOT_SAVE_PROPS * PROP_HEADER_SIZE +     \\\n  BOUND_ROBOT_SAVE_PROPS                          \\\n)\n\nenum robot_prop\n{\n  RPROP_EOF                       = 0x0000,\n  RPROP_ROBOT_NAME                = 0x0001, // ROBOT_NAME_SIZE\n  RPROP_ROBOT_CHAR                = 0x0002, // 1\n  RPROP_XPOS                      = 0x0003, // 2\n  RPROP_YPOS                      = 0x0004, // 2\n//RPROP_PROGRAM_ID                = 0x0005, // 4\n\n#ifdef CONFIG_DEBYTECODE\n  RPROP_PROGRAM_LABEL_ZAPS        = 0x00FD, // variable\n  RPROP_PROGRAM_SOURCE            = 0x00FE, // variable\n#endif\n  RPROP_PROGRAM_BYTECODE          = 0x00FF, // variable\n\n  // Legacy world, now save\n  RPROP_CUR_PROG_LINE             = 0x0100, // 4\n  RPROP_POS_WITHIN_LINE           = 0x0101, // 4\n  RPROP_ROBOT_CYCLE               = 0x0102, // 1\n  RPROP_CYCLE_COUNT               = 0x0103, // 1\n  RPROP_BULLET_TYPE               = 0x0104, // 1\n  RPROP_IS_LOCKED                 = 0x0105, // 1\n  RPROP_CAN_LAVAWALK              = 0x0106, // 1\n  RPROP_WALK_DIR                  = 0x0107, // 1\n  RPROP_LAST_TOUCH_DIR            = 0x0108, // 1\n  RPROP_LAST_SHOT_DIR             = 0x0109, // 1\n  RPROP_STATUS                    = 0x010A, // 1\n\n  // Legacy save\n  RPROP_LOOP_COUNT                = 0x010B, // 4\n  RPROP_LOCALS                    = 0x010C, // 4*32\n  RPROP_STACK_POINTER             = 0x0110, // 4\n  RPROP_STACK                     = 0x0111, // variable\n\n  // New\n  RPROP_CAN_GOOPWALK              = 0x0120, // 1\n};\n\n\n#define SCROLL_PROPS_SIZE   \\\n(                           \\\n  2 +                       \\\n  2 * PROP_HEADER_SIZE +    \\\n  PROP_EOF_SIZE             \\\n)\n\nenum scroll_prop\n{\n  SCRPROP_EOF                     = 0x00,\n  SCRPROP_NUM_LINES               = 0x01, // 2\n  SCRPROP_MESG                    = 0x02, // variable\n};\n\n\n#define SENSOR_PROPS_SIZE     \\\n(                             \\\n  (2 * ROBOT_NAME_SIZE) + 1 + \\\n  3 * PROP_HEADER_SIZE +      \\\n  PROP_EOF_SIZE               \\\n)\n\nenum sensor_prop\n{\n  SENPROP_EOF                     = 0x00,\n  SENPROP_SENSOR_NAME             = 0x01, // ROBOT_NAME_SIZE\n  SENPROP_SENSOR_CHAR             = 0x02, //  1\n  SENPROP_ROBOT_TO_MESG           = 0x03, // ROBOT_NAME_SIZE\n};\n\n\n/**\n * ZIP world format functions.\n */\n\n/**\n * To quickly match world filenames, they are converted to uint64_t. This\n * works because no internal world format files use names longer than 8 chars\n * and (hopefully) they won't ever need to. This seems to be a bit faster than\n * strcmp et al. optimizations even for 32-bit.\n */\n#define FILE_ID_8(a,b,c,d,e,f,g,h) \\\n (((uint64_t)a <<  0) | \\\n  ((uint64_t)b <<  8) | \\\n  ((uint64_t)c << 16) | \\\n  ((uint64_t)d << 24) | \\\n  ((uint64_t)e << 32) | \\\n  ((uint64_t)f << 40) | \\\n  ((uint64_t)g << 48) | \\\n  ((uint64_t)h << 56))\n\n#define FILE_ID_7(a,b,c,d,e,f,g) FILE_ID_8(a,b,c,d,e,f,g,'\\0')\n#define FILE_ID_6(a,b,c,d,e,f) FILE_ID_7(a,b,c,d,e,f,'\\0')\n#define FILE_ID_5(a,b,c,d,e) FILE_ID_6(a,b,c,d,e,'\\0')\n#define FILE_ID_4(a,b,c,d) FILE_ID_5(a,b,c,d,'\\0')\n#define FILE_ID_3(a,b,c) FILE_ID_4(a,b,c,'\\0')\n#define FILE_ID_2(a,b) FILE_ID_3(a,b,'\\0')\n#define FILE_ID_1(a) FILE_ID_2(a,'\\0')\n\n/**\n * Since only ASCII values are ever valid IDs, double bit 6 to bit 5 as a\n * cheap and tacky tolower replacement.\n */\n#define FILE_ID_TOLOWER(id) ((id) | (((id) & (uint64_t)0x4040404040404040) >> 1))\n\nstatic inline uint64_t world_file_id_value(const char *filename, size_t len)\n{\n  const char *f = filename;\n  switch(len)\n  {\n    case 1: return FILE_ID_TOLOWER(FILE_ID_1(f[0]));\n    case 2: return FILE_ID_TOLOWER(FILE_ID_2(f[0],f[1]));\n    case 3: return FILE_ID_TOLOWER(FILE_ID_3(f[0],f[1],f[2]));\n    case 4: return FILE_ID_TOLOWER(FILE_ID_4(f[0],f[1],f[2],f[3]));\n    case 5: return FILE_ID_TOLOWER(FILE_ID_5(f[0],f[1],f[2],f[3],f[4]));\n    case 6: return FILE_ID_TOLOWER(FILE_ID_6(f[0],f[1],f[2],f[3],f[4],f[5]));\n    case 7: return FILE_ID_TOLOWER(FILE_ID_7(f[0],f[1],f[2],f[3],f[4],f[5],f[6]));\n    // Currently no valid world format filenames with >=8 chars.\n  }\n  return 0;\n}\n\nstatic inline int world_file_id_cmp(const void *a, const void *b)\n{\n  struct zip_file_header *A = *(struct zip_file_header **)a;\n  struct zip_file_header *B = *(struct zip_file_header **)b;\n  int ab = A->mzx_board_id;\n  int bb = B->mzx_board_id;\n  int af = A->mzx_file_id;\n  int bf = B->mzx_file_id;\n  int ar = A->mzx_robot_id;\n  int br = B->mzx_robot_id;\n  uint32_t ao = A->offset;\n  uint32_t bo = B->offset;\n\n  return  (ab - bb) ? (ab - bb) :\n          (af - bf) ? (af - bf) :\n          (ar - br) ? (ar - br) :\n          (ao > bo) ? 1 : (ao < bo) ? -1 : 0;\n}\n\nstatic inline boolean compression_method_allowed(uint16_t method)\n{\n  // Only store and deflate are supported universally in all MZX builds;\n  // any other decompressors may be disabled for platforms that don't use them.\n  // Thus, any file in an MZX/etc that doesn't use store or deflate is invalid.\n  return (method == ZIP_M_NONE) || (method == ZIP_M_DEFLATE);\n}\n\nstatic inline void world_assign_file_ids_parse_board(const char *next, size_t len,\n unsigned int *_file_id, unsigned int *_board_id, unsigned int *_robot_id)\n{\n  unsigned int robot_id = 0;\n  char temp[3] = { 0 };\n\n  if(len < 3 || len > 7)\n    return;\n\n  temp[0] = next[1];\n  temp[1] = next[2];\n\n  *_board_id = strtoul(temp, NULL, 16);\n  next += 3;\n  len -= 3;\n\n  if(next[0])\n  {\n    if(next[0] == 'r' || next[0] == 'R')\n    {\n      robot_id = strtoul(next + 1, NULL, 16);\n      if(robot_id != 0)\n      {\n        *_file_id = FILE_ID_ROBOT;\n      }\n\n      *_robot_id = robot_id;\n    }\n    else\n\n    if(next[0] == 's' || next[0] == 'S')\n    {\n      if(next[1] == 'c' || next[1] == 'C')\n      {\n        robot_id = strtoul(next + 2, NULL, 16);\n        if(robot_id != 0)\n        {\n          *_file_id = FILE_ID_SCROLL;\n        }\n      }\n      else\n\n      if(next[1] == 'e' || next[1] == 'E')\n      {\n        robot_id = strtoul(next + 2, NULL, 16);\n        if(robot_id != 0)\n        {\n          *_file_id = FILE_ID_SENSOR;\n        }\n      }\n\n      *_robot_id = robot_id;\n    }\n    else\n\n    switch(world_file_id_value(next, len))\n    {\n      case FILE_ID_3('b','i','d'):\n        *_file_id = FILE_ID_BOARD_BID;\n        break;\n      case FILE_ID_3('b','p','r'):\n        *_file_id = FILE_ID_BOARD_BPR;\n        break;\n      case FILE_ID_3('b','c','o'):\n        *_file_id = FILE_ID_BOARD_BCO;\n        break;\n      case FILE_ID_3('u','i','d'):\n        *_file_id = FILE_ID_BOARD_UID;\n        break;\n      case FILE_ID_3('u','p','r'):\n        *_file_id = FILE_ID_BOARD_UPR;\n        break;\n      case FILE_ID_3('u','c','o'):\n        *_file_id = FILE_ID_BOARD_UCO;\n        break;\n      case FILE_ID_3('o','c','h'):\n        *_file_id = FILE_ID_BOARD_OCH;\n        break;\n      case FILE_ID_3('o','c','o'):\n        *_file_id = FILE_ID_BOARD_OCO;\n        break;\n    }\n  }\n  else\n  {\n    *_file_id = FILE_ID_BOARD_INFO;\n  }\n  if(!*_file_id)\n    *_board_id = 0;\n}\n\n/**\n * Assign world file ID values to files in the world ZIP archive.\n * This needs to be done once before every single world, board, and MZM load.\n *\n * TODO: maybe count number of misordered files and files with no ID, do an\n * insertion sort if it's low (<=log2(65536)=16) or no sort if there are none?\n */\nstatic inline void world_assign_file_ids(struct zip_archive *zp, boolean is_a_world)\n{\n  // Assign world file IDs to zip headers if they don't already exist.\n  struct zip_file_header **fh_list = zp->files;\n  struct zip_file_header *fh;\n  int num_fh = zp->num_files;\n\n  unsigned int file_id;\n  unsigned int board_id;\n  unsigned int robot_id;\n  const char *next;\n  int len;\n  int i;\n\n  // Special handling for non-worlds.\n  if(!is_a_world)\n  {\n    board_id = 0;\n\n    for(i = 0; i < num_fh; i++)\n    {\n      fh = fh_list[i];\n      next = fh->file_name;\n      len = strlen(next);\n\n      file_id = FILE_ID_NONE;\n      robot_id = 0;\n\n      if(len > 8 || !compression_method_allowed(fh->method))\n      {\n        fh->mzx_file_id = FILE_ID_NONE;\n        continue;\n      }\n      else\n\n      if(next[0] == 'r' || next[0] == 'R')\n      {\n        // Shorthand for robot on board 0\n        // Goes first to speed up MZM loads.\n        robot_id = strtoul(next + 1, NULL, 16);\n        file_id = FILE_ID_ROBOT;\n      }\n      else\n\n      if(next[0] == 'b' || next[0] == 'B')\n      {\n        world_assign_file_ids_parse_board(next, len, &file_id, &board_id, &robot_id);\n\n        // Non-world files shouldn't boards > 0\n        if(board_id)\n        {\n          file_id = FILE_ID_NONE;\n          board_id = 0;\n          robot_id = 0;\n        }\n      }\n\n      // Set the properties\n      fh->mzx_file_id = file_id;\n      fh->mzx_board_id = board_id;\n      fh->mzx_robot_id = robot_id;\n    }\n  }\n\n  // Regular world/save files.\n  else\n  {\n    for(i = 0; i < num_fh; i++)\n    {\n      fh = fh_list[i];\n      next = fh->file_name;\n      len = strlen(next);\n\n      file_id = FILE_ID_NONE;\n      board_id = 0;\n      robot_id = 0;\n\n      if(len > 8 || !compression_method_allowed(fh->method))\n      {\n        fh->mzx_file_id = FILE_ID_NONE;\n        continue;\n      }\n      else\n\n      if(next[0] == 'b' || next[0] == 'B')\n      {\n        world_assign_file_ids_parse_board(next, len, &file_id, &board_id, &robot_id);\n      }\n      else\n\n      switch(world_file_id_value(next, len))\n      {\n        case FILE_ID_5('w','o','r','l','d'):\n          file_id = FILE_ID_WORLD_INFO;\n          break;\n        case FILE_ID_2('g','r'):\n          file_id = FILE_ID_WORLD_GLOBAL_ROBOT;\n          break;\n        case FILE_ID_3('s','f','x'):\n          file_id = FILE_ID_WORLD_SFX;\n          break;\n        case FILE_ID_5('c','h','a','r','s'):\n          file_id = FILE_ID_WORLD_CHARS;\n          break;\n        case FILE_ID_3('p','a','l'):\n          file_id = FILE_ID_WORLD_PAL;\n          break;\n        case FILE_ID_7('p','a','l','s','m','z','x'):\n          file_id = FILE_ID_WORLD_PAL_SMZX;\n          break;\n        case FILE_ID_6('p','a','l','i','d','x'):\n          file_id = FILE_ID_WORLD_PAL_INDEX;\n          break;\n        case FILE_ID_6('p','a','l','i','n','t'):\n          file_id = FILE_ID_WORLD_PAL_INTENSITY;\n          break;\n        case FILE_ID_7('p','a','l','i','n','t','s'):\n          file_id = FILE_ID_WORLD_PAL_INTENSITY_SMZX;\n          break;\n        case FILE_ID_3('v','c','o'):\n          file_id = FILE_ID_WORLD_VCO;\n          break;\n        case FILE_ID_3('v','c','h'):\n          file_id = FILE_ID_WORLD_VCH;\n          break;\n        case FILE_ID_3('s','p','r'):\n          file_id = FILE_ID_WORLD_SPRITES;\n          break;\n        case FILE_ID_7('c','o','u','n','t','e','r'):\n          file_id = FILE_ID_WORLD_COUNTERS;\n          break;\n        case FILE_ID_6('s','t','r','i','n','g'):\n          file_id = FILE_ID_WORLD_STRINGS;\n          break;\n      }\n\n      // Set the properties\n      fh->mzx_file_id = file_id;\n      fh->mzx_board_id = board_id;\n      fh->mzx_robot_id = robot_id;\n    }\n  }\n\n  // Sort the archive and reset to the beginning\n  qsort(fh_list, num_fh, sizeof(struct zip_file_header *), world_file_id_cmp);\n  zp->pos = 0;\n}\n\n/**\n * These functions are used to save properties files in world saving. There\n * are no safety checks aside from the debug asserts in the memfile functions.\n * USE THE BOUNDING MACROS WHEN ALLOCATING.\n */\nstatic inline void save_prop_eof(struct memfile *mf)\n{\n  mfputw(0, mf);\n}\n\n// Write a (u)int8 property.\nstatic inline void save_prop_c(int ident, int value, struct memfile *mf)\n{\n  mfputw(ident, mf);\n  mfputd(1, mf);\n  mfputc(value, mf);\n}\n\n// Write a (u)int16 property.\nstatic inline void save_prop_w(int ident, int value, struct memfile *mf)\n{\n  mfputw(ident, mf);\n  mfputd(2, mf);\n  mfputw(value, mf);\n}\n\n// Write a (u)int32 property.\nstatic inline void save_prop_d(int ident, int value, struct memfile *mf)\n{\n  mfputw(ident, mf);\n  mfputd(4, mf);\n  mfputd(value, mf);\n}\n\n// Write a (u)int64 property.\nstatic inline void save_prop_q(int ident, int64_t value, struct memfile *mf)\n{\n  mfputw(ident, mf);\n  mfputd(8, mf);\n  mfputq(value, mf);\n}\n\n// Write an array property.\n// `src` should point to an array of `count` members of `len` length each.\nstatic inline void save_prop_a(int ident, const void *src, size_t len,\n size_t count, struct memfile *mf)\n{\n  mfputw(ident, mf);\n  mfputd(len * count, mf);\n  mfwrite(src, len, count, mf);\n}\n\n// Write a null terminated string property.\nstatic inline void save_prop_s(int ident, const char *src, struct memfile *mf)\n{\n  save_prop_a(ident, src, strlen(src), 1, mf);\n}\n\n// Some string properties had questionable termination behavior in older\n// versions. This is a convenience macro that saves as a string for 2.93+ and\n// as a block array for <2.93 (to help with downver export).\n#define save_prop_s_293(ident, src, buffer_len, mf) do { \\\n  if(file_version >= V293)  save_prop_s(ident, src, mf); \\\n  else                      save_prop_a(ident, src, buffer_len, 1, mf); \\\n} while(0)\n\n// Write an arbitrary-sized data property and return a memfile of it.\nstatic inline void save_prop_v(int ident, size_t len, struct memfile *prop,\n struct memfile *mf)\n{\n  assert(mfhasspace(len, mf));\n  mfputw(ident, mf);\n  mfputd(len, mf);\n  mfopen_wr(mf->current, len, prop);\n  mf->current += len;\n}\n\nstatic inline int load_prop_int(struct memfile *prop)\n{\n  switch(prop->end - prop->start)\n  {\n    case 1:\n      return mfgetc(prop);\n    case 2:\n      return mfgetw(prop);\n    case 4:\n      return mfgetd(prop);\n    default:\n      return 0;\n  }\n}\n\n// Unsigned clamp.\nstatic inline unsigned load_prop_int_u(struct memfile *prop, unsigned min, unsigned max)\n{\n  unsigned tmp = load_prop_int(prop);\n  return CLAMP(tmp, min, max);\n}\n\n// Forces sign extension of byte and word values, signed clamp.\nstatic inline int load_prop_int_s(struct memfile *prop, int min, int max)\n{\n  int tmp = 0;\n  switch(prop->end - prop->start)\n  {\n    case 1:\n      tmp = (signed char)mfgetc(prop);\n      break;\n    case 2:\n      tmp = (signed short)mfgetw(prop);\n      break;\n    case 4:\n      tmp = mfgetd(prop);\n      break;\n  }\n  return CLAMP(tmp, min, max);\n}\n\n// Forces sign extension of byte and word values, signed clamp.\nstatic inline int64_t load_prop_int_s64(struct memfile *prop, int64_t min, int64_t max)\n{\n  int64_t tmp = 0;\n  switch(prop->end - prop->start)\n  {\n    case 1:\n      tmp = (signed char)mfgetc(prop);\n      break;\n    case 2:\n      tmp = (signed short)mfgetw(prop);\n      break;\n    case 4:\n      tmp = mfgetd(prop);\n      break;\n    case 8:\n      tmp = mfgetq(prop);\n      break;\n  }\n  return CLAMP(tmp, min, max);\n}\n\n// All non-zero values -> 1.\nstatic inline boolean load_prop_boolean(struct memfile *prop)\n{\n  return !!load_prop_int(prop);\n}\n\n// This function is used to read properties files in world loading.\nstatic inline boolean next_prop(struct memfile *prop, int *ident, int *length,\n struct memfile *mf)\n{\n  unsigned char *end = mf->end;\n  unsigned char *cur;\n  unsigned int len;\n\n  if((end - mf->current) < PROP_HEADER_SIZE)\n  {\n    prop->current = NULL;\n    *ident = *length = 0;\n    return false;\n  }\n\n  *ident = mfgetw(mf);\n  len = mfgetud(mf);\n  cur = mf->current;\n\n  if((size_t)(end - cur) < len)\n  {\n    prop->current = NULL;\n    *ident = *length = 0;\n    return false;\n  }\n\n  *length = len;\n  mfopen(cur, len, prop);\n\n  mf->current += len;\n  return true;\n}\n\n/**\n * Returns true if the properties file in `mf` is valid.\n * This can be used to distinguish whether or not an ambiguous file or field\n * actually contains properties.\n */\nstatic inline boolean check_properties_file(struct memfile *mf,\n int maximum_ident)\n{\n  int ident;\n  unsigned length;\n  while(mf->end - mf->current >= 6)\n  {\n    ident = mfgetw(mf);\n    length = mfgetud(mf);\n    if(ident == 0 || ident > maximum_ident)\n      goto err;\n\n    if((unsigned)(mf->end - mf->current) < length)\n      goto err;\n\n    mf->current += length;\n  }\n\n  if((mf->end - mf->current == 2) && mfgetw(mf) == 0x0000) // EOF\n  {\n    mf->current = mf->start;\n    return true;\n  }\n\nerr:\n  mf->current = mf->start;\n  return false;\n}\n\n__M_END_DECLS\n\n#endif // __WORLD_FORMAT_H\n"
  },
  {
    "path": "src/world_struct.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef __WORLD_STRUCT_H\n#define __WORLD_STRUCT_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include <stdio.h>\n#include <stdint.h>\n#include <time.h>\n\n#include \"board_struct.h\"\n#include \"robot_struct.h\"\n#include \"counter_struct.h\"\n#include \"sprite_struct.h\"\n\n#include \"io/vfile.h\"\n#include \"audio/sfx.h\"\n\n#define COMMAND_CACHE_CURRENT_TIME (1 << 0)\n\nenum change_game_state_value\n{\n  CHANGE_STATE_NONE,\n  CHANGE_STATE_INTERRUPT_CYCLE, // Cycle interrupted without state change.\n  CHANGE_STATE_SWAP_WORLD,\n  CHANGE_STATE_LOAD_GAME_ROBOTIC,\n  CHANGE_STATE_EXIT_GAME_ROBOTIC,\n  CHANGE_STATE_PLAY_GAME_ROBOTIC,\n  CHANGE_STATE_REQUEST_EXIT\n};\n\nenum fwrite_mode\n{\n  FWRITE_MODE_UNKNOWN,\n  FWRITE_MODE_TRUNCATE,\n  FWRITE_MODE_MODIFY,\n  FWRITE_MODE_APPEND,\n  FWRITE_MODE_MAX\n};\n\nstruct world\n{\n  // 0 if a world has been loaded, 1 if it hasn't\n  int active;\n  char name[BOARD_NAME_SIZE];\n\n  int version;\n  // Move here eventually\n  //char id_chars[ID_CHARS_SIZE];\n  //char id_dmg[ID_DMG_SIZE];\n  //char bullet_color[ID_BULLET_COLOR_SIZE];\n  //char missile_color;\n  char status_counters_shown[NUM_STATUS_COUNTERS][COUNTER_NAME_SIZE];\n\n  // Save game material, part 1\n  char keys[NUM_KEYS];\n  int blind_dur;\n  int firewalker_dur;\n  int freeze_time_dur;\n  int slow_time_dur;\n  int wind_dur;\n  int pl_saved_x[8];\n  int pl_saved_y[8];\n  int pl_saved_board[8];\n  int saved_pl_color;\n  int under_player_id;\n  int under_player_color;\n  int under_player_param;\n  int mesg_edges;\n  int scroll_base_color;\n  int scroll_corner_color;\n  int scroll_pointer_color;\n  int scroll_title_color;\n  int scroll_arrow_color;\n  char real_mod_playing[MAX_PATH];\n  int max_samples;\n  int smzx_message;\n\n  int edge_color;\n  int first_board;\n  int endgame_board;\n  int death_board;\n  int endgame_x;\n  int endgame_y;\n  int game_over_sfx;\n  int death_x;\n  int death_y;\n  int starting_lives;\n  int lives_limit;\n  int starting_health;\n  int health_limit;\n  int enemy_hurt_enemy;\n  int clear_on_exit;\n  int only_from_swap;\n\n  // Save game material, part 2\n  struct counter_list counter_list;\n  struct string_list string_list;\n  int player_restart_x;\n  int player_restart_y;\n  int num_sprites;\n  int num_sprites_allocated;\n  int sprite_num;\n  struct sprite **sprite_list;\n  int active_sprites;\n  int sprite_y_order;\n  int collision_count;\n  int *collision_list;\n  int multiplier;\n  int divider;\n  int c_divisions;\n  int fread_delimiter;\n  int fwrite_delimiter;\n  int bi_shoot_status;\n  int bi_mesg_status;\n  char output_file_name[MAX_PATH];\n  char input_file_name[MAX_PATH];\n  vfile *output_file;\n  vfile *input_file;\n  vdir *input_directory;\n  boolean input_is_dir;\n  enum fwrite_mode output_mode;\n  int temp_input_pos;\n  int temp_output_pos;\n  int commands;\n  int commands_stop;\n  int vlayer_size;\n  int vlayer_width;\n  int vlayer_height;\n  char *vlayer_chars;\n  char *vlayer_colors;\n\n  int num_boards;\n  int num_boards_allocated;\n  struct board **board_list;\n  struct board *current_board;\n  int current_board_id;\n  int temporary_board;\n\n  struct robot global_robot;\n\n  struct sfx_list custom_sfx;\n\n  // Not part of world/save files, but runtime globals\n  int player_x;\n  int player_y;\n  int player_shoot_cooldown;\n\n  // For moving the player between boards\n  enum board_target target_where;\n  int target_board;\n  int target_x;\n  int target_y;\n  enum thing target_id;\n  int target_color;\n  enum thing target_d_id;\n  int target_d_color;\n\n  // Current bomb type\n  int bomb_type;\n\n  // Indiciates if the player is dead\n  boolean dead;\n\n  // Toggle used to skip the board update when slow time or\n  // freeze time are active.\n  boolean current_cycle_frozen;\n\n  // Toggle used by certain built-in mechanics that update every other cycle.\n  boolean current_cycle_odd;\n\n  // Did the player just move?\n  boolean player_moved;\n\n  // Was the player on an entrance before the board scan?\n  boolean player_was_on_entrance;\n\n  // Was the player damaged while 'restart if hurt' is active?\n  boolean was_zapped;\n\n  // For use in repeat delays for player movement\n  int key_up_delay;\n  int key_down_delay;\n  int key_right_delay;\n  int key_left_delay;\n\n  // 1-3 normal 5-7 is 1-3 but from a REL FIRST cmd\n  int first_prefix;\n  // Just 1-3\n  int mid_prefix;\n  // 1-3 normal 5-7 is 1-3 but from a REL LAST cmd\n  int last_prefix;\n  // Flags for data that persists during a command (keep in the prefix cache line).\n  int command_cache;\n\n  // Lets the get counter routines indiciate to the caller\n  // that the result is not a typical counter but something\n  // special. It's there for those horribly hacked file open\n  // counters. It should normally be set to FOPEN_NONE.\n  enum special_counter_return special_counter_return;\n\n  // These are used to handle SAVE_GAME\n  enum { SAVE_NONE, SAVE_GAME } robotic_save_type;\n  char robotic_save_path[MAX_PATH];\n\n  // Indicates a robotic world swap, savegame load, or game exit\n  // FIXME this might be better suited to the game context.\n  enum change_game_state_value change_game_state;\n\n  // Current speed of MZX world\n  int mzx_speed;\n  // If we can change the speed from the F2 menu.\n  int lock_speed;\n\n  // Joystick state data.\n  boolean joystick_simulate_keys;\n\n  // Editor specific state flags.\n  boolean editing;\n  boolean debug_mode;\n\n  // World validation: we don't want to alloc this file twice.\n  char *raw_world_info;\n  int raw_world_info_size;\n\n  // Keep this open, just once\n  vfile *help_file;\n\n  // An array for game2.cpp\n  char *update_done;\n  int update_done_size;\n\n  // Cached time for the current robot command.\n  struct tm current_time;\n  int64_t current_time_epoch;\n  int32_t current_time_nano;\n};\n\n__M_END_DECLS\n\n#endif // __WORLD_STRUCT_H\n"
  },
  {
    "path": "src/yuv.h",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2007 Alan Williams <mralert@gmail.com>\n * Copyright (C) 2019-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n// General macros and functions for YUV manipulation. Supports YUY2, UYVY, and\n// YVYU. SDL doesn't provide functions for packing/unpacking these.\n\n#ifndef __YUV_H\n#define __YUV_H\n\n#include \"compat.h\"\n\n__M_BEGIN_DECLS\n\n#include \"platform_endian.h\"\n#include <stdint.h>\n\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n\n#define YUY2_Y1_SHIFT 24\n#define YUY2_Y2_SHIFT  8\n#define YUY2_U_SHIFT  16\n#define YUY2_V_SHIFT   0\n\n#define UYVY_Y1_SHIFT 16\n#define UYVY_Y2_SHIFT  0\n#define UYVY_U_SHIFT  24\n#define UYVY_V_SHIFT   8\n\n#define YVYU_Y1_SHIFT 24\n#define YVYU_Y2_SHIFT  8\n#define YVYU_U_SHIFT   0\n#define YVYU_V_SHIFT  16\n\n#else\n\n#define YUY2_Y1_SHIFT  0\n#define YUY2_Y2_SHIFT 16\n#define YUY2_U_SHIFT   8\n#define YUY2_V_SHIFT  24\n\n#define UYVY_Y1_SHIFT  8\n#define UYVY_Y2_SHIFT 24\n#define UYVY_U_SHIFT   0\n#define UYVY_V_SHIFT  16\n\n#define YVYU_Y1_SHIFT  0\n#define YVYU_Y2_SHIFT 16\n#define YVYU_U_SHIFT  24\n#define YVYU_V_SHIFT   8\n\n#endif\n\n#define YUY2_Y1_MASK ((uint32_t)(0xFF) << YUY2_Y1_SHIFT)\n#define YUY2_Y2_MASK ((uint32_t)(0xFF) << YUY2_Y2_SHIFT)\n#define YUY2_U_MASK  ((uint32_t)(0xFF) << YUY2_U_SHIFT)\n#define YUY2_V_MASK  ((uint32_t)(0xFF) << YUY2_V_SHIFT)\n#define YUY2_UV_MASK (YUY2_U_MASK | YUY2_V_MASK)\n\n#define UYVY_Y1_MASK ((uint32_t)(0xFF) << UYVY_Y1_SHIFT)\n#define UYVY_Y2_MASK ((uint32_t)(0xFF) << UYVY_Y2_SHIFT)\n#define UYVY_U_MASK  ((uint32_t)(0xFF) << UYVY_U_SHIFT)\n#define UYVY_V_MASK  ((uint32_t)(0xFF) << UYVY_V_SHIFT)\n#define UYVY_UV_MASK (UYVY_U_MASK | UYVY_V_MASK)\n\n#define YVYU_Y1_MASK ((uint32_t)(0xFF) << YVYU_Y1_SHIFT)\n#define YVYU_Y2_MASK ((uint32_t)(0xFF) << YVYU_Y2_SHIFT)\n#define YVYU_U_MASK  ((uint32_t)(0xFF) << YVYU_U_SHIFT)\n#define YVYU_V_MASK  ((uint32_t)(0xFF) << YVYU_V_SHIFT)\n#define YVYU_UV_MASK (YVYU_U_MASK | YVYU_V_MASK)\n\nstatic inline uint32_t yuy2_pack(uint8_t y1, uint8_t u, uint8_t y2, uint8_t v)\n{\n  return\n   (y1 << YUY2_Y1_SHIFT) | (u << YUY2_U_SHIFT) |\n   (y2 << YUY2_Y2_SHIFT) | (v << YUY2_V_SHIFT);\n}\n\nstatic inline uint32_t uyvy_pack(uint8_t y1, uint8_t u, uint8_t y2, uint8_t v)\n{\n  return\n   (y1 << UYVY_Y1_SHIFT) | (u << UYVY_U_SHIFT) |\n   (y2 << UYVY_Y2_SHIFT) | (v << UYVY_V_SHIFT);\n}\n\nstatic inline uint32_t yvyu_pack(uint8_t y1, uint8_t u, uint8_t y2, uint8_t v)\n{\n  return\n   (y1 << YVYU_Y1_SHIFT) | (u << YVYU_U_SHIFT) |\n   (y2 << YVYU_Y2_SHIFT) | (v << YVYU_V_SHIFT);\n}\n\n// First byte is u or v -> LE must add first, BE must shift first\nstatic inline uint32_t yuv_subsample_uv0(uint32_t a, uint32_t b,\n uint32_t y1_mask, uint32_t y2_mask, uint32_t uv_mask)\n{\n  uint32_t y1 = a & y1_mask;\n  uint32_t y2 = b & y2_mask;\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n  uint32_t uv = (((a & uv_mask) >> 1) + ((b & uv_mask) >> 1)) & uv_mask;\n#else\n  uint32_t uv = (((a & uv_mask) + (b & uv_mask)) >> 1) & uv_mask;\n#endif\n  return y1 | y2 | uv;\n}\n\n// First byte is y1 or y2 -> LE must shift first, BE must add first\nstatic inline uint32_t yuv_subsample_y0(uint32_t a, uint32_t b,\n uint32_t y1_mask, uint32_t y2_mask, uint32_t uv_mask)\n{\n  uint32_t y1 = a & y1_mask;\n  uint32_t y2 = b & y2_mask;\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n  uint32_t uv = (((a & uv_mask) + (b & uv_mask)) >> 1) & uv_mask;\n#else\n  uint32_t uv = (((a & uv_mask) >> 1) + ((b & uv_mask) >> 1)) & uv_mask;\n#endif\n  return y1 | y2 | uv;\n}\n\nstatic inline uint32_t yuy2_subsample(uint32_t a, uint32_t b)\n{\n  return yuv_subsample_y0(a, b, YUY2_Y1_MASK, YUY2_Y2_MASK, YUY2_UV_MASK);\n}\n\nstatic inline uint32_t uyvy_subsample(uint32_t a, uint32_t b)\n{\n  return yuv_subsample_uv0(a, b, UYVY_Y1_MASK, UYVY_Y2_MASK, UYVY_UV_MASK);\n}\n\nstatic inline uint32_t yvyu_subsample(uint32_t a, uint32_t b)\n{\n  return yuv_subsample_y0(a, b, YVYU_Y1_MASK, YVYU_Y2_MASK, YVYU_UV_MASK);\n}\n\n// Full-swing RGB to YUV conversion.\nstatic inline void rgb_to_yuv(uint8_t r, uint8_t g, uint8_t b,\n                              uint8_t *y, uint8_t *u, uint8_t *v)\n{\n  *y = (9797 * r + 19237 * g + 3734 * b) >> 15;\n  *u = ((18492 * (b - *y)) >> 15) + 128;\n  *v = ((23372 * (r - *y)) >> 15) + 128;\n}\n\n/**\n * Studio-swing RGB to YUV/YCbCr converison.\n * This is required for Apple's OpenGL YUV texture extension.\n *\n * Note: an approach like above using B/R - Y' differences requires\n * computing a full-swing Y' separate from the output Y', which results\n * in only 1 fewer multiplication than using the matrix (so don't bother).\n *\n * Kv = [ 0.299, 0.587, 0.114 ]\n *\n * Yv  = Kv * 219 * (1 << 16) / 255\n * Cbv = (([ 0 0 1 ] - Kv) / (2 * (1 - Kb)) * 224 * (1 << 16) / 255\n * Crv = (([ 1 0 0 ] - Kv) / (2 * (1 - Kr)) * 224 * (1 << 16) / 255\n */\nstatic inline void rgb_to_ycbcr(uint8_t r, uint8_t g, uint8_t b,\n                                uint8_t *y, uint8_t *cb, uint8_t *cr)\n{\n  *y  = ((16829 * r +  33039 * g +  6416 * b + 32768) >> 16) + 16;\n  *cb = ((-9714 * r + -19071 * g + 28784 * b + 32768) >> 16) + 128;\n  *cr = ((28784 * r + -24103 * g + -4681 * b + 32768) >> 16) + 128;\n}\n\nstatic inline uint32_t rgb_to_yuy2(uint8_t r, uint8_t g, uint8_t b)\n{\n  uint8_t y, u, v;\n  rgb_to_yuv(r, g, b, &y, &u, &v);\n  return yuy2_pack(y, u, y, v);\n}\n\nstatic inline uint32_t rgb_to_uyvy(uint8_t r, uint8_t g, uint8_t b)\n{\n  uint8_t y, u, v;\n  rgb_to_yuv(r, g, b, &y, &u, &v);\n  return uyvy_pack(y, u, y, v);\n}\n\nstatic inline uint32_t rgb_to_yvyu(uint8_t r, uint8_t g, uint8_t b)\n{\n  uint8_t y, u, v;\n  rgb_to_yuv(r, g, b, &y, &u, &v);\n  return yvyu_pack(y, u, y, v);\n}\n\nstatic inline uint32_t rgb_to_apple_ycbcr_422(uint8_t r, uint8_t g, uint8_t b)\n{\n  uint8_t y, cb, cr;\n  rgb_to_ycbcr(r, g, b, &y, &cb, &cr);\n  return uyvy_pack(y, cb, y, cr);\n}\n\n__M_END_DECLS\n\n#endif /* __YUV_H */\n"
  },
  {
    "path": "testworlds/1.00/000 Format.txt",
    "content": "Title: 1.XX format test and swap hack test.\nAuthor: Alice Rowan\nDesc: Tests loading of 1.xx world files and of hacked SWAP WORLD commands. MegaZeux 1.xx does not support SWAP WORLD, but modern MZX allows it so that the regression tests can test 1.xx worlds.\n"
  },
  {
    "path": "testworlds/1.00/001 Locked.txt",
    "content": "Title: 1.XX locked world test.\nAuthor: Alice Rowan\nDesc: 1.xx locked worlds are NOT encrypted, just password protected. Loading these should work.\n"
  },
  {
    "path": "testworlds/1.00/002 Robo-P Convert.txt",
    "content": "Title: Robo-P Command Conversion\nAuthor: Alice Rowan\nDesc: This world contains a robot with every command, which should convert properly. This does not test execution of most commands.\n"
  },
  {
    "path": "testworlds/1.00/003 Char Escapes.txt",
    "content": "Title: v1 Counter Symbol Escape Test\nAuthor: Alice Rowan\nDesc: Make sure various important symbols in modern Robotic are escaped when a v1 program is converted, or otherwise have no side effects when interpreted.\n"
  },
  {
    "path": "testworlds/1.00/004 cur_prog_line.txt",
    "content": "Title: v1 Preset cur_prog_line\nAuthor: Alice Rowan\nDesc: Make sure v1 cur_prog_line is converted correctly.\n"
  },
  {
    "path": "testworlds/1.00/005 bad cur_prog_line.txt",
    "content": "Title: bad cur_prog_line\nAuthor: Alice Rowan\nDesc: These robots have bad cur_prog_line values, don't crash.\n"
  },
  {
    "path": "testworlds/1.00/006 firewalker_dur.txt",
    "content": "Title: Firewalker Duration Test\nAuthor: Alice Rowan\nDesc: Make sure the board firewalker duration value is loaded.\n"
  },
  {
    "path": "testworlds/1.00/007 wind_dur.txt",
    "content": "Title: Wind Duration Test\nAuthor: Alice Rowan\nDesc: Make sure the board wind duration value is loaded.\n"
  },
  {
    "path": "testworlds/1.00/008 slimeblob.txt",
    "content": "Title: Slime Spread Speed Test\nAuthor: Alice Rowan\nDesc: Test MegaZeux 1.x slime spread speeds 1-4 and '5-8', including bugs.\n"
  },
  {
    "path": "testworlds/1.00/009 scan.txt",
    "content": "Title: Scan Test\nAuthor: Alice Rowan\nDesc: Test that the scan is left-to-right, top-to-bottom, and that reverse send is performed by a second scan from right-to-left, bottom-to-top. In 1.x, any robot that receives a label before it is reached by the reverse scan will execute, even if it already ran normally.\n"
  },
  {
    "path": "testworlds/1.00/010 rel counters copy.txt",
    "content": "Title: REL COUNTERS COPY Test\nAuthor: Alice Rowan\nDesc: REL COUNTERS on COPY behaves sanely in 1.x: it applies XPOS and YPOS to both sets of coordinates. If only someone had checked what 1.x did when adding the 2.00 behavior!\n"
  },
  {
    "path": "testworlds/1.00/011 Entrances.txt",
    "content": "Title: Entrance Text (1.x)\nAuthor: Alice Rowan\nDesc: A (hopefully) comprehensive test for entrances. Tests: 1) 'voluntary' entrances and MOVE PLAYER trigger on same cycle; 2) pushed onto entrance from non-entrance does NOT trigger; 3) pushed onto entrance from entrance does not trigger; 4) zap onto entrance does not trigger; 5) entrance color rules: (a) full color match; (b) thing match; that the last matching entrance of each category is used; 6) that entrances can exit to other entrances on the same board.\n"
  },
  {
    "path": "testworlds/1.00/012 TELEPORT skip.txt",
    "content": "Title: 1.x TELEPORT skips the rest of the cycle test.\nAuthor: Alice Rowan\nDesc: make sure 1.x TELEPORT cancels the cycle early.\n"
  },
  {
    "path": "testworlds/1.00/013 Robo-P Color Param.txt",
    "content": "Title: Robo-P Colors/Params Test (1.00)\nAuthor: Alice Rowan\nDesc: Test colors, params, and chars in Robo-P, especially invalid values. See corresponding 2.00/2.60/2.70/2.80 tests for info.\n"
  },
  {
    "path": "testworlds/1.00/014 Tiger Intel. Movement.txt",
    "content": "Title: SpittingTiger Intelligence/Movement Test (1.00)\nAuthor: Alice Rowan\nDesc: There are 20 tigers on the board for each of the 8 possible intelligence levels. Every single one of them should move on the first cycle and none should self-destruct when they shoot. This test requires 'Enemies hurt enemies' enabled.\n"
  },
  {
    "path": "testworlds/2.51/000 Swap Test.txt",
    "content": "Title: Swap test\nAuthor: Alice Rowan\nDesc: Tests the swap testing architecture. Only needs to run to succeed.\n"
  },
  {
    "path": "testworlds/2.51/001 Copy No-Restart.txt",
    "content": "Title: Robot Copy No-Restart Tester\nAuthor: Alice Rowan\nDesc: Prior to 2.80X, robots duplicated with the COPY or COPY BLOCK commands would continue their programs from the same position as their source.\n"
  },
  {
    "path": "testworlds/2.51/002 Shoot No-Cycle.txt",
    "content": "Title: Non-cycle ending shoot test\nAuthor: Alice Rowan\nDesc: In 2.83, shoot ended the cycle. This happens in no other versions before or since.\n"
  },
  {
    "path": "testworlds/2.51/003 Endgame Teleport.txt",
    "content": "Title: Endgame disables endgame teleport\nAuthor: Alice Rowan\nDesc: In DOS versions of MZX, endgame teleport is disabled afer an endgame.\n"
  },
  {
    "path": "testworlds/2.51/004 Entrances.txt",
    "content": "Title: Entrances Test\nAuthor: Alice Rowan\nDesc: A (hopefully) comprehensive test for entrances. Tests: 1) 'voluntary' entrances and MOVE PLAYER trigger on same cycle; 2) pushed onto entrance from non-entrance triggers on same cycle; 3) pushed onto entrance from entrance does not trigger; 4) zap onto entrance does not trigger; 5) entrance color rules: (a) thing & full color match; (b) full color match; (c) thing & fg color match; (d) fg color match; (e) thing match; that the last matching entrance of each category is used; 6) that entrances can't exit to other entrances on the same board.\n"
  },
  {
    "path": "testworlds/2.51/005 COPY player thisx.txt",
    "content": "Title: COPY player over current robot test\nAuthor: Alice Rowan\nDesc: When a robot copies the player over itself, or attempts to copy a storage object over itself when there are no remaining IDs, this operation should fail and the robot should continue to the next command.\n\n"
  },
  {
    "path": "testworlds/2.51/006 ZAP RESTORE.txt",
    "content": "Title: ZAP/RESTORE Test\nAuthor: Alice Rowan\nDesc: Test the functionality of the ZAP and RESTORE commands.\n"
  },
  {
    "path": "testworlds/2.51/007 LOCAL.txt",
    "content": "Title: LOCAL test\nAuthor: Alice Rowan\nDesc: LOCAL should be a global counter in this version.\n"
  },
  {
    "path": "testworlds/2.51/008 Null Boards.txt",
    "content": "Title: Null Boards Test\nAuthor: Alice Rowan\nDesc: Make sure entrances work after null board removal. Also make sure null board removal doesn't print warnings.\n"
  },
  {
    "path": "testworlds/2.51/009 Shark Goop.txt",
    "content": "Title: Shark Goop Test\nAuthor: Alice Rowan\nDesc: Make sure sharks can move through goop. This was broken in the port for several years somehow.\n"
  },
  {
    "path": "testworlds/2.51/010 INPUT STRING.txt",
    "content": "Title: INPUT STRING tests\nAuthor: Alice Rowan\nDesc: Tests the INPUT STRING family of commands and INPUT interpolation into counter names. Since INPUT STRING requires user input that command is not actually tested: pre-2.83 worlds can preset board input buffers.\n"
  },
  {
    "path": "testworlds/2.51/011 Bad Message Row.txt",
    "content": "Title: Bad Message Row/Col Crash\nAuthor: Alice Rowan\nDesc: Invalid message rows and columns caused out-of-bounds message drawing in some port versions. The preset values in this board corresponded to text_video_layer.data in 2.92f.\n"
  },
  {
    "path": "testworlds/2.51/012 Custom SFX.txt",
    "content": "Title: Custom SFX Test\nAuthor: Alice Rowan\nDesc: Make sure custom SFX can be loaded from old worlds correctly. This can't do much to verify a successful load except play a sound and WAIT PLAY.\n"
  },
  {
    "path": "testworlds/2.51/013 slimeblob.txt",
    "content": "Title: Slime Spread Speed Test\nAuthor: Alice Rowan\nDesc: Test MegaZeux 2.x slime spread speeds 1-4 and '5-8', including bugs. This test does NOT work prior to 2.07, as older versions use 1.x slime speeds.\n"
  },
  {
    "path": "testworlds/2.51/014 scan.txt",
    "content": "Title: Scan Test\nAuthor: Alice Rowan\nDesc: Test that the scan is left-to-right, top-to-bottom, and that reverse send is performed by a second scan from right-to-left, bottom-to-top. In 2.x, a robot executes during the reverse scan only if it was already ended, executed an END or WAIT command, or if it did not execute due to its cycle value.\n"
  },
  {
    "path": "testworlds/2.51/015 rel counters copy.txt",
    "content": "Title: REL COUNTERS COPY Test\nAuthor: Alice Rowan\nDesc: REL COUNTERS and REL COUNTERS FIRST/LAST on COPY and COPY BLOCK have been broken since 2.00 in a possibly intentional and very pointless way: REL COUNTERS, despite it being claimed to apply XPOS and YPOS to both coordinate sets, instead applies FIRSTXPOS, FIRSTYPOS, LASTXPOS, and LASTYPOS. REL COUNTERS FIRST/LAST, despite them being claimed to apply the latter to either the first or last set of coordinates, applies XPOS and YPOS to their respective set of coordinates instead.\n"
  },
  {
    "path": "testworlds/2.51/016 IF ALIGNEDROBOT.txt",
    "content": "Title: IF ALIGNEDROBOT Test\nAuthor: Alice Rowan\nDesc: Test basic functionality of IF ALIGNEDROBOT.\n"
  },
  {
    "path": "testworlds/2.51/017 TELEPORT noskip.txt",
    "content": "Title: 2.x TELEPORT does not skip the cycle test.\nAuthor: Alice Rowan\nDesc: make sure 2.x TELEPORT does NOT cancel the cycle early.\n"
  },
  {
    "path": "testworlds/2.51/018 Robotic Color Param.txt",
    "content": "Title: Robotic Color/Parameter Test (2.00)\nAuthor: Alice Rowan\nDesc: Comprehensive test for color and parameter Robotic arguments, including normal, wildcards, invalid, and counters. These programs contain hex edited commands that may not disassemble. Not tested are IF (NOT) THING BENEATH (added in 2.51s2), char/overlay commands (can't query until 2.60), PUT THING BENEATH (added in 2.70), PLAYERCOLOR (can't query until 2.80), edge color and message decorations (can't query), variable parameters (full support in 2.80d).\n"
  },
  {
    "path": "testworlds/2.51/019 Tiger Intel. Movement.txt",
    "content": "Title: SpittingTiger Intelligence/Movement Test (2.00)\nAuthor: Alice Rowan\nDesc: There are 20 tigers on the board for each of the 8 possible intelligence levels. Every single one of them should move on the first cycle and none should self-destruct when they shoot. This test requires 'Enemies' bullets hurt other enemies' enabled.\n"
  },
  {
    "path": "testworlds/2.51s1/001 LOCAL.txt",
    "content": "Title: LOCAL test\nAuthor: Alice Rowan\nDesc: The LOCAL counter was added in 2.51s1. Make sure it works properly and that LOCAL# creates globals instead.\n"
  },
  {
    "path": "testworlds/2.60/001 SEND Lock.txt",
    "content": "Title: SEND Self infinite loop test\nAuthor: Alice Rowan\nDesc: The label caching in the port would cache the command after the current until 2.84, allowing some constructs that should have resulted in infinite loops (like the non-caching DOS) to not get trapped. Ensure that such a construct (see the robot at 2,0) does trap in DOS. This test uses 2.60 (instead of 2.51) to get access to the COMMANDS counter so MZX doesn't freeze.\n"
  },
  {
    "path": "testworlds/2.60/002 Label Cycle-Ending.txt",
    "content": "Title: Label Cycle Ending Test\nAuthor: Alice Rowan\nDesc: pre-port versions of MZX give the label command a special cycle-ending behavior; if the exact same label (i.e. exact position in bytecode) is the target of a GOTO/etc twice in a row and various commands were not encountered between the two jumps to that position, the label command will end the cycle. Test created using MZX 2.6 to get access to the COMMANDS counter for obvious reasons.\n"
  },
  {
    "path": "testworlds/2.60/003 Robotic Color Param.txt",
    "content": "Title: Robotic Color/Parameter Test (IF THING BENEATH, overlay)\nAuthor: Alice Rowan\nDesc: Comprehensive test for color and parameter Robotic arguments, including normal, wildcards, invalid, and counters. These programs contain hex edited commands that may not disassemble. This test only includes char commands (needs BOARD_CHAR), overlay commands (needs OVERLAY_COLOR) and IF THING BENEATH (added in 2.51s2, which looks identical to 2.60).\n"
  },
  {
    "path": "testworlds/2.62/001 SET string INPUT.txt",
    "content": "Title: SET $string INPUT test\nAuthor: Alice Rowan\nDesc: Test the feature to set a string to INPUT added in 2.62.\n"
  },
  {
    "path": "testworlds/2.65/003 SPR_NUM.txt",
    "content": "Title: SPR_NUM range test\nAuthor: Alice Rowan\nDesc: Test the range of SPR_NUM prior to 2.70, when it was stored as a signed char.\n"
  },
  {
    "path": "testworlds/2.65/004 Static Sprite Collision.txt",
    "content": "Title: Static Sprite Collision (2.65)\nAuthor: Alice Rowan\nDesc: Pre-2.90 static sprites display relative to the screen but do not collide relative to the screen. This world tests the original collision function from before unbound sprites.\n"
  },
  {
    "path": "testworlds/2.65/005 IF ALIGNEDROBOT.txt",
    "content": "Title: IF ALIGNEDROBOT Test (2.65)\nAuthor: Alice Rowan\nDesc: Test basic functionality of IF ALIGNEDROBOT. This includes making sure it only sends subroutines one time.\n"
  },
  {
    "path": "testworlds/2.70/005 Robotic Color Param.txt",
    "content": "Title: Robotic Color/Parameter Test (2.70)\nAuthor: Alice Rowan\nDesc: Comprehensive test for color and parameter Robotic arguments, including normal, wildcards, invalid, and counters. These programs contain hex edited commands that may not disassemble. This world only tests PUT THING BENEATH, which was added in 2.70. See the 1.00, 2.00, 2.60, and 2.80 worlds for more information.\n"
  },
  {
    "path": "testworlds/README.md",
    "content": "# MZX Test Worlds\n\nThis folder contains a set of worlds that test various aspects of MZX and\nparticularly Robotic that can be determined to be correct or incorrect from\nwithin MegaZeux itself. Run the full sequence of tests with `run.sh`, or\nuse the `make test` rule.\n\nThis system currently requires an `mzxrun` executable in the parent directory,\nand may not work correctly for all platforms.\n\n## Conventions\n\n### Naming\n\nTest worlds are named in the format\n\n```VVV/XXX [short description].mzx```\n\nwhere `VVV` is the MegaZeux version associated with the world (e.g. `2.51`)\nand XXX is the test number (e.g. `123`).\n\nExample version strings:\n* `2.02` for 2.02\n* `2.07` for 2.07\n* `2.51` for 2.51\n* `2.51s1` for 2.51s1\n* `2.51s2` for 2.51s2\n* `2.51s3` for 2.51s3\n* `2.51s3.1` for 2.51s3.1\n* `2.51s3.2` for 2.51s3.2\n* `2.60` for 2.6\n* `2.61` for 2.61\n* `2.62` for 2.62\n* `2.62b` for 2.62b\n* etc...\n\nLetter versions starting from 2.80 onward belong in the folder of their\nnon-lettered counterpart. Change the first digit of the test number to the\nversion letter to signify it is a test for a change in that particular\nlettered version. Example: a 2.80d test called \"Big Test\" could be located\nat the path `2.80/d01 Big Test.mzx`).\n\n### Dependencies\n\nExternal files used as dependencies by a test world should be located in the\nfolder `data`. Test worlds are copied to the testworlds base folder before they\nare executed, so they should have access to this folder when running as part\nof a test.\n\n### Robotic\n\nAdditionally, test worlds must conform to the following conventions:\n\n1) The world must not be encrypted, and must have a properly configured\nstarting board. A title is helpful but not necessary.\n\n2) The world will perform exactly one test or a group of closely related tests\n(e.g. testing various COPY BLOCK uses) and will be created and versioned for the\nearliest possible MegaZeux version it is applicable for. If versioned compatibility\nbehavior exists, create a version of the test for each behavior with similar\nfilenames and titles using the earliest applicable MZX versions for each.\n\n3) The test must assume it will be operating at MZX_SPEED 1 and with unbounded\nCOMMANDS, unless the purpose of the test requires a different MZX_SPEED or\nCOMMANDS value. Setting these defaults explicitly is not necessary.\n\n4) The robot driving the tests must be clearly visible.\n\n5) The first line(s) of the testing robot must be setting the `$title` string\nto the title of the test, the `$author` string to your identifier, and the\n`$desc` string to a description of the test. This must be wrapped to fit the\nrobot editor window with `inc \"$desc\" \"[more description]\"` as-needed.\n\n6) Upon completion or failure, the counter `result` must be set to one of the\nfollowing counters: `PASS`, `WARN`, `FAIL`, `BADF`, or `SKIP`.\n\n7) Extra testing notes may be included in the `$result` string.\n\n8) Counter and string names beginning with two underscores (e.g. `__abc`,\n`$__def`) are reserved and should not be used.\n\n9) When the test is finished running, the following snippet of code\nMUST be executed:\n\n```\n: \"exit\"\nif \"__continue\" = 1 then \"__swap\"\nend\n: \"__swap\"\nswap world \"next\"\n```\n\n### Special Counters\n\nThe following counters have special meaning:\n\n* `PASS`: Set `result` to this to indicate that the test passed.\n* `FAIL`: Set `result` to this to indicate that the test failed.\n* `WARN`: Set `result` to this to indicate that the test had an error condition but otherwise should count as a success.\n* `BADF`: Set `result` to this to indicate that the test failed due to a file loading error.\n* `SKIP`: Set `result` to this to indicate that the test was skipped.\n* `result`: indicates the result of the test. Defaults to `BADF`.\n* `$result`: indicates more details about the result of a test.\n* `$world`: the filename of the current world. NOTE: This is not necessarily the original filename of the world.\n* `$title`: the title of the test.\n* `$author`: the author of the test.\n* `$desc`: a description of the test.\n\nWorlds from MZX versions 2.62 to 2.70 should use the following compatible strings:\n\n* `$string0`: the filename of the current world. NOTE: This is not necessarily the original filename of the world.\n* `$string1`: the title of the test.\n* `$string2`: the author of the test.\n* `$string3`: a description of the test.\n* `$string4`: indicates more details about the result of a test.\n\n\n### Skipping Tests\n\nTo skip a test, set `result` to `SKIP`. The following counters indicate when a\ncertain test should be skipped:\n\n* `__skip_audio`: all audio in MZX is disabled. All audio tests should check this.\n* `__skip_mod`: no module player for the audio system has been enabled.\n* `__skip_vorbis`: ogg/vorbis support has been disabled.\n\n\n### Compatibility Notes\n\n1) DOS worlds are not capable of the string operations described above. Strings\nwere introduced in MegaZeux 2.62 in a very limited form. In versions 2.62, 2.62b,\nand 2.65, strings can be _ONLY **15** CHARACTERS LONG_. In versions 2.68 through\n2.70, strings can be up to **63** characters long. While it may be possible to\nset longer strings in old worlds using newer versions, it's recommended to stay\nwithin the original bounds and use the compatibility strings listed above.\n\nWorlds from MZX versions before 2.62 don't have access to any strings and need to\nprovide title, author, and description info in *both* comment form in the main\nrobot (for reference) and in a separate text file (to be added to the test output).\nThe text file must have the **exact same file name** as the test world but a .txt\nextension instead of a .mzx extension (ex: \"mytest.mzx\" → \"mytest.txt\").\n\nComment form:\n```\n. \"Title: A text\"\n. \"Author: You\"\n. \"Desc: This is a long description of the test.\"\n```\n\nText file format (the space after \"Title:\", etc. is required):\n```\nTitle: A test\nAuthor: You\nDesc: This is a long description of the test.\n```\n\nIt is not possible to provide a `$result` equivalent for these worlds currently,\nbut this functionality may be added in the future.\n"
  },
  {
    "path": "testworlds/data/README.md",
    "content": "Any testworlds data files that should be committed belong in this directory.\n"
  },
  {
    "path": "testworlds/data/loadassetentryA.pal",
    "content": "\u001f\u001f\u001f"
  },
  {
    "path": "testworlds/data/loadassetentryB.pal",
    "content": "???"
  },
  {
    "path": "testworlds/data/spaces.txt",
    "content": "         . \"check out my cool indentation\"\n                inc \"value\" 1\ninc value 1          \n* \"&value&\"\n"
  },
  {
    "path": "testworlds/run.sh",
    "content": "#!/bin/sh\n\n# Use make test\nusage()\n{\n\techo \"USAGE: ./run.sh [-q] {PLATFORM}\"\n\techo \"\"\n\techo \"OR:    ./run.sh [-q] {PLATFORM} /path/to/{libcore.so|libcore.dylib} (if modular enabled and platform is 'unix' or 'darwin'\"\n\techo \"\"\n\techo \"OR:    make test\"\n}\n\nget_asan()\n{\n\tif libs=$(ldd \"$1\" 2>/dev/null); then\n\t\tASAN=$(echo \"$libs\" | grep -i \"lib[^ ]*asan\" | awk '{print $3}')\n\tfi\n}\n\nget_preload()\n{\n\tpreload=\"$1\"\n\tget_asan \"$1\"\n\tif [ -z \"$ASAN\" ]; then\n\t\t# Some compilers may link it to mzxrun but not libcore.so...\n\t\tget_asan \"../mzxrun\"\n\tfi\n\tif [ -n \"$ASAN\" ]; then\n\t\tpreload=\"$ASAN $preload\"\n\tfi\n}\n\nquiet=\"no\"\nif [ \"$1\" = \"-q\" ];\nthen\n\tquiet=\"yes\"\n\tshift\nfi\n\nif [ -z \"$1\" ];\nthen\n\tusage\n\texit 1\nfi\n\nTESTS_DIR=$(dirname \"$0\")\ncd \"$TESTS_DIR\" || { echo \"ERROR: failed to cd to test dir: $TESTS_DIR\"; exit 1; }\n(\ncd .. || { echo \"ERROR: failed to cd to megazeux dir\"; exit 1; }\n\n# Unix release builds will try to find this if it isn't installed.\ncp config.txt megazeux-config\n\n# Give tests.mzx the MZX configuration so it can decide which tests to skip.\ncp src/config.h \"$TESTS_DIR\"\n)\n\nmkdir -p log\n\n# Clear out any backup files so they aren't mistaken for tests.\nrm -f backup*.mzx\n\n# Clear out the temp folder.\nfind temp/* ! -name README.md -exec rm -f {} \\;\n\n# Clear out the default logs\nrm -f log/detailed\nrm -f log/summary\nrm -f log/failures\n\n# Force mzxrun to use the libraries in its own directory.\n# Linux/BSD\nexport LD_LIBRARY_PATH=\".\"\n# macOS\nexport DYLD_FALLBACK_LIBRARY_PATH=\".\"\n# Haiku\nexport LIBRARY_PATH=\"$LIBRARY_PATH:.\"\n\npreload=\"\"\nif [ -n \"$2\" ];\nthen\n\tget_preload \"$2\"\n\t[ \"$quiet\" = \"yes\" ] || echo \"Test worlds preload: $preload\"\nfi\n\n# Coupled with the software renderer, this will disable video in MZX, speeding things up\n# and allowing for automated testing. Disabling the SDL audio driver will prevent annoying\n# noises from occuring during tests, but shouldn't affect audio-related tests.\n# SDL 1.2 and SDL2\nexport SDL_VIDEODRIVER=dummy\nexport SDL_AUDIODRIVER=dummy\n# SDL3\nexport SDL_VIDEO_DRIVER=dummy\nexport SDL_AUDIO_DRIVER=dummy\n\n# Standalone mode will allow tests.mzx to terminate MZX and no_titlescreen mode\n# simplifies things. Disable auto update checking to save time. Some platforms\n# might have issues detecting libraries and running from this folder, so run from\n# the base folder.\n[ \"$quiet\" = \"yes\" ] || printf \"Running test worlds\"\n\ndir=\"$(pwd)\"\ncd ..\n\nLD_PRELOAD=\"$preload\" \\\n./mzxrun \\\n  \"$TESTS_DIR/tests.mzx\" \\\n  video_output=software \\\n  update_auto_check=off \\\n  standalone_mode=1 \\\n  no_titlescreen=1 \\\n  &\n\ncd \"$dir\"\n\n# Attempt to detect a hang (e.g. an error occurred).\n# Note: running mzxrun in a subshell breaks this.\nmzxrun_pid=$!\npsopt=\"\"\ni=\"0\"\n\n# BusyBox ps may not support -e; mzxrun might not appear WITHOUT -e in a chroot.\n# -A is equivalent to -e in POSIX, but NetBSD uses -e for something else.\n# MSYS2 only supports -e (not -A) but it doesn't need it either.\nif ps -A 1>/dev/null 2>/dev/null; then psopt='-A'; fi\n# Haiku has completely different formatting and needs reordered fields:\nif [ \"$(uname -s 2>/dev/null)\" = \"Haiku\" ]; then psopt='-o Id Team'; fi\n\n# In some versions of MSYS2, mzxrun doesn't always appear in ps right away. :(\nsleep 1\n[ \"$quiet\" = \"yes\" ] || printf \".\"\n\nwhile ps $psopt | grep -q \"[ \\t]*$mzxrun_pid .*[m]zxrun\"\ndo\n\tsleep 1\n\ti=$((i+1))\n\t[ \"$quiet\" = \"yes\" ] || printf \".\"\n\tif [ $i -ge 180 ];\n\tthen\n\t\tkill -9 $mzxrun_pid\n\t\techo \"killing frozen process.\"\n\t\tbreak\n\tfi\ndone\n[ \"$quiet\" = \"yes\" ] || echo \"\"\n\n# Clean up some files that MegaZeux currently can't.\n\nrm -f next\nrm -f \"test\"\nrm -f saved.sav\nrm -f saved2.sav\nrm -f LOCKED.MZX\nrm -f LOCKED.MZX.locked\nrm -f ../megazeux-config\nrm -f config.h\nrm -f data/audio/drivin.s3m\n\nif [ \"$quiet\" != \"yes\" ];\nthen\n\tif command -v tput >/dev/null;\n\tthen\n\t\t# Color code PASS/FAIL tags and important numbers.\n\t\tCOL_END=$(tput sgr0)\n\t\tCOL_RED=$(tput bold)$(tput setaf 1)\n\t\tCOL_GREEN=$(tput bold)$(tput setaf 2)\n\t\tCOL_YELLOW=$(tput bold)$(tput setaf 3)\n\n\t\tcat log/failures \\\n\t\t  | sed -e \"s/\\[PASS\\]/\\[${COL_GREEN}PASS${COL_END}\\]/g\" \\\n\t\t  | sed -e \"s/\\[FAIL\\]/\\[${COL_RED}FAIL${COL_END}\\]/g\" \\\n\t\t  | sed -e \"s/\\[\\(WARN\\|SKIP\\)\\]/\\[${COL_YELLOW}\\1${COL_END}\\]/g\" \\\n\t\t  | sed -e \"s/passes: \\([1-9][0-9]*\\)/passes: ${COL_GREEN}\\1${COL_END}/g\" \\\n\t\t  | sed -e \"s/failures: \\([1-9][0-9]*\\)/failures: ${COL_RED}\\1${COL_END}/g\" \\\n\n\t\ttput bel\n\telse\n\t\tcat log/failures\n\tfi\nfi\n\n# Exit 1 if there are any failures.\n\nif [ ! -e log/failures ] || grep -q \"FAIL\" log/failures || grep -q \"ERROR\" log/failures;\nthen\n\texit 1\nelse\n\texit 0\nfi\n"
  },
  {
    "path": "testworlds/temp/README.md",
    "content": "All non-.SAV files generated by test worlds should go here.\n"
  },
  {
    "path": "unit/Makefile.in",
    "content": "#\n# Unit test Makefile fragment.\n#\n# Unit tests require C++11.\n#\n\n.PHONY: unit unittest unit_clean\n\nunit_src        := unit\nunit_obj        := unit/.build\nunit_src_audio  := unit/audio\nunit_obj_audio  := unit/audio/.build\nunit_src_editor := unit/editor\nunit_obj_editor := unit/editor/.build\nunit_src_io     := unit/io\nunit_obj_io     := unit/io/.build\nunit_src_network:= unit/network\nunit_obj_network:= unit/network/.build\nunit_src_utils  := unit/utils\nunit_obj_utils  := unit/utils/.build\n\nifneq (${BINEXT},)\nunit_ext        := ${BINEXT}\nelse\nunit_ext        := .test\nendif\n\nunit_common_objs := \\\n  ${unit_obj}/Unit.o                   \\\n  ${unit_obj}/UnitIO.o                 \\\n\nunit_objs := \\\n  ${unit_obj}/align${unit_ext}         \\\n  ${unit_obj}/expr${unit_ext}          \\\n  ${unit_obj}/render${unit_ext}        \\\n  ${unit_obj}/memcasecmp${unit_ext}    \\\n  ${unit_obj_audio}/mixer${unit_ext}   \\\n  ${unit_obj_io}/bitstream${unit_ext}  \\\n  ${unit_obj_io}/memfile${unit_ext}    \\\n  ${unit_obj_io}/path${unit_ext}       \\\n  ${unit_obj_io}/vfs${unit_ext}        \\\n  ${unit_obj_io}/vio${unit_ext}        \\\n  ${unit_obj_io}/zip${unit_ext}        \\\n\nifneq (${BUILD_EDITOR},)\n\nunit_objs += \\\n  ${unit_obj_editor}/stringsearch${unit_ext}\n\nendif\n\nifneq (${BUILD_NETWORK},)\n\nunit_objs += \\\n  ${unit_obj_network}/sha256${unit_ext}\n\nendif\n\nifneq (${BUILD_UTILS},)\n\nunit_objs += \\\n  ${unit_obj_utils}/image_file${unit_ext} \\\n\nendif\n\nifeq (${PLATFORM},mingw)\nunit_objs += \\\n  ${unit_obj}/thread_win32${unit_ext}\nendif\n\n#\n# Some unit tests only work with modular builds. The reason is usually\n# that the component(s) being tested are far too dependent on other\n# components of MegaZeux to work alone.\n#\nifneq (${BUILD_MODULAR},)\n\nunit_objs += \\\n  ${unit_obj}/configure${unit_ext}     \\\n  ${unit_obj}/intake${unit_ext}        \\\n  ${unit_obj}/sfx${unit_ext}           \\\n  ${unit_obj}/thread${unit_ext}        \\\n  ${unit_obj}/world${unit_ext}         \\\n\nunit_ldflags += -L. -lcore\n${unit_objs}: ${core_target}\n\nifneq (${BUILD_EDITOR},)\nunit_ldflags += -L. -leditor\n${unit_objs}: ${editor_target}\nendif\n\nifneq (${BUILD_NETWORK},)\nunit_objs += \\\n  ${unit_obj_network}/Manifest${unit_ext}\nendif\n\nelse # !BUILD_MODULAR\n\n#\n# Certain tests that don't rely on many core MZX features can be patched in\n# by directly linking their objects.\n#\nunit_common_objs += \\\n  ${io_obj}/path.o        \\\n  ${io_obj}/vfs.o         \\\n  ${io_obj}/vio.o         \\\n  ${io_obj}/zip.o         \\\n  ${io_obj}/zip_stream.o  \\\n\nendif\n\n#\n# Build unit tests with debug optimizations and without link-time optimzations.\n# (Overrides these optimization flags from the global flags.)\n#\nUNIT_OPTIMIZE_FLAGS := -O0 ${UNIT_OPTIMIZE_FLAGS}\nifeq (${LTO},1)\nifeq (${HAS_F_LTO},1)\nUNIT_OPTIMIZE_FLAGS := -fno-lto ${UNIT_OPTIMIZE_FLAGS}\nendif\nendif\n\n#\n# Build without -DDEBUG to suppress debug messages,\n# build without -DNDEBUG to allow assert().\n# Turn on exceptions explicitly, since the global CXXFLAGS turn them off.\n#\nunit_cflags ?= ${CXXFLAGS}\nunit_cflags += ${UNIT_OPTIMIZE_FLAGS} -UDEBUG -UNDEBUG -DMZX_UNIT_TESTS\nunit_cflags += -fexceptions -funsigned-char -std=gnu++11\nunit_ldflags += ${UNIT_OPTIMIZE_FLAGS}\n\nunit_cflags += ${SDL_CFLAGS} ${ZLIB_CFLAGS} -Umain\nunit_ldflags += ${SDL_LDFLAGS} ${ZLIB_LDFLAGS}\n\n# Required for image_file test.\nifneq (${LIBPNG},)\nunit_cflags  += ${LIBPNG_CFLAGS}\nunit_ldflags += ${LIBPNG_LDFLAGS}\nendif\n\n# Required for threading test.\nifeq (${PTHREAD},1)\nunit_cflags  += ${PTHREAD_CFLAGS}\nunit_ldflags += ${PTHREAD_LDFLAGS}\nendif\n\n# System libs need to be last in the search order.\nunit_ldflags += ${ARCH_LIBS}\n\nifneq (${HAS_CXX_11},1)\n\nunit unittest:\n\t$(if ${V},,@echo \"Skipping unit tests (requires C++11).\")\n\nunit_clean:\n\nelse\n\n${unit_objs}: ${unit_common_objs}\n\n${unit_obj}/%.o: ${unit_src}/%.cpp\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CXX} -MD ${unit_cflags} -c $< -o $@\n\n${unit_obj}/%${unit_ext}: ${unit_src}/%.cpp\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CXX} -MD ${unit_cflags} $< -o $@ ${unit_common_objs} ${unit_ldflags}\n\n${unit_obj_audio}/%${unit_ext}: ${unit_src_audio}/%.cpp\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CXX} -MD ${unit_cflags} $< -o $@ ${unit_common_objs} ${unit_ldflags}\n\n${unit_obj_editor}/%${unit_ext}: ${unit_src_editor}/%.cpp\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CXX} -MD ${unit_cflags} $< -o $@ ${unit_common_objs} ${unit_ldflags}\n\n${unit_obj_io}/%${unit_ext}: ${unit_src_io}/%.cpp\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CXX} -MD ${unit_cflags} $< -o $@ ${unit_common_objs} ${unit_ldflags}\n\n${unit_obj_network}/%${unit_ext}: ${unit_src_network}/%.cpp\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CXX} -MD ${unit_cflags} $< -o $@ ${unit_common_objs} ${unit_ldflags}\n\n${unit_obj_utils}/%${unit_ext}: ${unit_src_utils}/%.cpp\n\t$(if ${V},,@echo \"  CXX     \" $<)\n\t${CXX} -MD ${unit_cflags} $< -o $@ ${unit_common_objs} ${unit_ldflags}\n\n-include ${unit_objs:${unit_ext}=.d}\n-include ${unit_common_objs:.o=.d}\n\n${unit_objs} ${unit_common_objs}: $(filter-out $(wildcard ${unit_obj}), ${unit_obj})\n${unit_objs} ${unit_common_objs}: $(filter-out $(wildcard ${unit_obj_audio}), ${unit_obj_audio})\n${unit_objs} ${unit_common_objs}: $(filter-out $(wildcard ${unit_obj_editor}), ${unit_obj_editor})\n${unit_objs} ${unit_common_objs}: $(filter-out $(wildcard ${unit_obj_io}), ${unit_obj_io})\n${unit_objs} ${unit_common_objs}: $(filter-out $(wildcard ${unit_obj_network}), ${unit_obj_network})\n${unit_objs} ${unit_common_objs}: $(filter-out $(wildcard ${unit_obj_utils}), ${unit_obj_utils})\n\nunit unittest: ${unit_objs}\n\t@failcount=0; \\\n\tfor t in ${unit_objs}; do \\\n\t\tLD_LIBRARY_PATH=\".\" LIBRARY_PATH=\"$$LIBRARY_PATH:.\" ./$$t ; \\\n\t\tif [ \"$$?\" != \"0\" ] ; then \\\n\t\t\tfailcount=$$(($$failcount + 1)); \\\n\t\tfi; \\\n\tdone; \\\n\tif [ \"$$failcount\" -gt \"0\" ]; then \\\n\t\techo \"Failed $$failcount test set(s).\"; \\\n\t\texit 1; \\\n\tfi;\n\nunit_clean:\n\t$(if ${V},,@echo \"  RM      \" ${unit_obj} ${unit_obj_audio} ${unit_obj_editor} ${unit_obj_io} ${unit_obj_network} ${unit_obj_utils})\n\t${RM} -r ${unit_obj} ${unit_obj_audio} ${unit_obj_editor} ${unit_obj_io} ${unit_obj_network} ${unit_obj_utils}\n\nendif\n"
  },
  {
    "path": "unit/Unit.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"Unit.hpp\"\n#include \"../src/platform.h\"\n\n#include <assert.h>\n#include <stdarg.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <time.h>\n#include <unistd.h>\n\n#include <csignal>\n#include <type_traits>\n#include <utility>\n#include <vector>\n\n/* Enable exception diagnostics if -fno-exceptions or platform lacks\n * a stack unwinder. */\n#if defined(__GNUC__) && !defined(__clang__) && \\\n    (!defined(__EXCEPTIONS) || __EXCEPTIONS == 0)\n#define PRINT_EXCEPTION_DIAGNOSTICS\n#endif\n#if defined(CONFIG_AMIGA) && defined(__mc68000__)\n#define PRINT_EXCEPTION_DIAGNOSTICS\n#endif\n\n/* Utility functions that inlined MegaZeux source expects to exist. */\n#include \"../src/utils/utils_alloc.h\"\n\nstatic const clock_t get_ticks_base = clock();\nuint64_t get_ticks()\n{\n  return clock() - get_ticks_base;\n}\n\n/* Main functions. */\n\nstatic void sigabrt_handler(int signal)\n{\n  if(signal == SIGABRT)\n  {\n    Uerr(\"Received SIGABRT: \");\n  }\n  else\n    Uerr(\"Unexpected signal %d received: \", signal);\n\n  Unit::unittestrunner::get().signal_fail();\n}\n\nint main(int argc, char *argv[])\n{\n  if(argc && argv && argv[0])\n  {\n    char *fpos = strrchr(argv[0], '/');\n    char *bpos = strrchr(argv[0], '\\\\');\n    char tmp;\n\n    if(fpos || bpos)\n    {\n      fpos = (fpos > bpos) ? fpos : bpos;\n      tmp = *fpos;\n      *fpos = '\\0';\n      chdir(argv[0]);\n      *fpos = tmp;\n    }\n  }\n\n  if(!Unit::self_check())\n    return 1;\n\n  std::signal(SIGABRT, sigabrt_handler);\n  return !(Unit::unittestrunner::get().run());\n}\n\n\nnamespace Unit\n{\n  /************************************************************************\n   * Unit::arg functions.\n   */\n\n  arg::~arg()\n  {\n    delete[] allocbuf;\n  }\n\n  arg::arg() {}\n\n  const char *arg::fix_op(const arg &src)\n  {\n    if(src.op == src.tmpbuf)\n      return tmpbuf;\n\n    if(src.op == src.allocbuf)\n      return allocbuf;\n\n    return src.op;\n  }\n\n  arg::arg(arg &&a)\n  {\n    memcpy(tmpbuf, a.tmpbuf, sizeof(tmpbuf));\n    has_value = a.has_value;\n    allocbuf = a.allocbuf;\n    op = fix_op(a);\n\n    a.has_value = false;\n    a.op = nullptr;\n    a.allocbuf = nullptr;\n  }\n\n  arg::arg(const arg &a)\n  {\n    memcpy(tmpbuf, a.tmpbuf, sizeof(tmpbuf));\n    has_value = a.has_value;\n\n    if(a.allocbuf)\n    {\n      size_t len = strlen(a.allocbuf);\n      allocbuf = new char[len + 1];\n      memcpy(allocbuf, a.allocbuf, len + 1);\n    }\n    op = fix_op(a);\n  }\n\n  arg::arg(std::nullptr_t _op)\n  {\n    op = coalesce(_op);\n  }\n\n  arg::arg(const char *_op)\n  {\n    has_value = !!_op;\n    _op = coalesce(_op);\n\n    size_t len = strlen(_op);\n    allocbuf = new char[len + 1];\n    memcpy(allocbuf, _op, len + 1);\n    op = allocbuf;\n  }\n\n  arg::arg(const void *_op)\n  {\n    snprintf(tmpbuf, sizeof(tmpbuf), \"0x%08zx\", reinterpret_cast<size_t>(_op));\n    op = tmpbuf;\n    has_value = true;\n  }\n\n  arg::arg(unsigned char _op)\n  {\n    snprintf(tmpbuf, sizeof(tmpbuf), \"%u\", _op);\n    op = tmpbuf;\n    has_value = true;\n  }\n\n  arg::arg(unsigned short _op)\n  {\n    snprintf(tmpbuf, sizeof(tmpbuf), \"%u\", _op);\n    op = tmpbuf;\n    has_value = true;\n  }\n\n  arg::arg(unsigned int _op)\n  {\n    snprintf(tmpbuf, sizeof(tmpbuf), \"%d\", _op);\n    op = tmpbuf;\n    has_value = true;\n  }\n\n  arg::arg(unsigned long _op)\n  {\n    snprintf(tmpbuf, sizeof(tmpbuf), \"%lu\", _op);\n    op = tmpbuf;\n    has_value = true;\n  }\n\n  arg::arg(unsigned long long _op)\n  {\n    snprintf(tmpbuf, sizeof(tmpbuf), \"%llu\", _op);\n    op = tmpbuf;\n    has_value = true;\n  }\n\n  arg::arg(signed char _op)\n  {\n    snprintf(tmpbuf, sizeof(tmpbuf), \"%d\", _op);\n    op = tmpbuf;\n    has_value = true;\n  }\n\n  arg::arg(short _op)\n  {\n    snprintf(tmpbuf, sizeof(tmpbuf), \"%d\", _op);\n    op = tmpbuf;\n    has_value = true;\n  }\n\n  arg::arg(int _op)\n  {\n    snprintf(tmpbuf, sizeof(tmpbuf), \"%d\", _op);\n    op = tmpbuf;\n    has_value = true;\n  }\n\n  arg::arg(long _op)\n  {\n    snprintf(tmpbuf, sizeof(tmpbuf), \"%ld\", _op);\n    op = tmpbuf;\n    has_value = true;\n  }\n\n  arg::arg(long long _op)\n  {\n    snprintf(tmpbuf, sizeof(tmpbuf), \"%lld\", _op);\n    op = tmpbuf;\n    has_value = true;\n  }\n\n  template<class T, class E = std::enable_if<std::is_integral<T>::value>>\n  char *_set_operand_integral_ptr(const T *_op, size_t length)\n  {\n    if(_op)\n    {\n      char *buf;\n      char *pos;\n      size_t allocleft = length * (sizeof(T) * 2 + 1);\n\n      buf = new char[allocleft];\n      pos = buf;\n\n      for(size_t i = 0; i < length; i++)\n      {\n        typename std::make_unsigned<T>::type tmp = _op[i];\n        size_t n = snprintf(pos, allocleft, \"%0*lx \", (int)sizeof(T) * 2, (unsigned long)tmp);\n        if(n >= allocleft)\n          break;\n\n        pos += n;\n        allocleft -= n;\n      }\n\n      return buf;\n    }\n    return nullptr;\n  }\n\n  arg::arg(const void *_ptr, size_t length_bytes)\n  {\n    // Print most memcmp types (including  non-integral types) bytewise.\n    const unsigned char *_ptr8 = reinterpret_cast<const unsigned char *>(_ptr);\n    allocbuf = _set_operand_integral_ptr(_ptr8, length_bytes / sizeof(char));\n    op = allocbuf;\n    has_value = !!allocbuf;\n  }\n\n  arg::arg(const unsigned short *_ptr, size_t length_bytes)\n  {\n    allocbuf = _set_operand_integral_ptr(_ptr, length_bytes / sizeof(short));\n    op = allocbuf;\n    has_value = !!allocbuf;\n  }\n\n  arg::arg(const unsigned int *_ptr, size_t length_bytes)\n  {\n    allocbuf = _set_operand_integral_ptr(_ptr, length_bytes / sizeof(int));\n    op = allocbuf;\n    has_value = !!allocbuf;\n  }\n\n  arg::arg(const unsigned long *_ptr, size_t length_bytes)\n  {\n    allocbuf = _set_operand_integral_ptr(_ptr, length_bytes / sizeof(long));\n    op = allocbuf;\n    has_value = !!allocbuf;\n  }\n\n\n  /************************************************************************\n   * Unit::exception functions.\n   */\n\n  template<size_t N>\n  static void set_reason_fmt(Unit::exception *e, char (&reasonbuf)[N], const char *_reason_fmt, va_list vl)\n  {\n    /**\n     * The reason format may be prefixed with a ~. This is so the format string\n     * provided to Unit::exception constructors can be checked by the compiler\n     * and not be flagged as invalid due to an empty input string.\n     */\n    if(_reason_fmt[0] == '~')\n      _reason_fmt++;\n\n    if(_reason_fmt[0] == '\\0')\n      return;\n\n    vsnprintf(reasonbuf, sizeof(reasonbuf), _reason_fmt, vl);\n    e->reason = reasonbuf;\n    e->has_reason = true;\n  }\n\n  void exception::diagnostics()\n  {\n#ifdef PRINT_EXCEPTION_DIAGNOSTICS\n    Uerr(\"  ***** unit::exception *****\\n\");\n    Uerr(\"    at line %d: %s\", line, test);\n\n    if(has_reason)\n      Uerr(\" (%s)\\n\", reason);\n    else\n      Uerr(\"\\n\");\n\n    if(has_values)\n    {\n      Uerr(\"    Left:  %s\\n\", left);\n      Uerr(\"    Right: %s\\n\", right);\n    }\n    UerrFlush();\n#endif\n  }\n\n  exception::exception(const exception &e):\n   leftarg(e.leftarg), rightarg(e.rightarg)\n  {\n    memcpy(reasonbuf, e.reasonbuf, sizeof(reasonbuf));\n    line = e.line;\n    test = e.test;\n    reason = e.reason;\n    has_reason = e.has_reason;\n    left = e.left;\n    right = e.right;\n    has_values = e.has_values;\n  }\n\n  exception::exception(int _line, const char *_test):\n   line(_line), test(coalesce(_test)), reason(\"\")\n  {\n    diagnostics();\n  }\n\n  exception::exception(int _line, const char *_test, const char *_reason_fmt, ...):\n   line(_line), test(coalesce(_test))\n  {\n    if(_reason_fmt)\n    {\n      va_list vl;\n      va_start(vl, _reason_fmt);\n      set_reason_fmt(this, reasonbuf, _reason_fmt, vl);\n      va_end(vl);\n    }\n\n    diagnostics();\n  }\n\n  exception::exception(int _line, const char *_test, Unit::arg &&_left, Unit::arg &&_right,\n   const char *_reason_fmt, ...):\n   leftarg(std::move(_left)), rightarg(std::move(_right)), line(_line), test(coalesce(_test))\n  {\n    if(_reason_fmt)\n    {\n      va_list vl;\n      va_start(vl, _reason_fmt);\n      set_reason_fmt(this, reasonbuf, _reason_fmt, vl);\n      va_end(vl);\n    }\n\n    left = leftarg.op;\n    right = rightarg.op;\n    has_values = leftarg.has_value || rightarg.has_value;\n\n    diagnostics();\n  }\n\n\n  /************************************************************************\n   * Unit::unittest functions.\n   */\n\n  unittest::unittest(const char *_file, const char *_test_name):\n   file_name(_file), test_name(_test_name)\n  {\n    unittestrunner::get().addtest(this);\n  }\n\n  void unittest::run_section(void)\n  {\n    try\n    {\n      run_impl();\n    }\n    catch(const Unit::skip &e)\n    {\n      this->skip();\n    }\n    catch(const Unit::exception &e)\n    {\n      this->print_exception(e);\n    }\n  }\n\n  bool unittest::run()\n  {\n    assert(!has_run);\n    has_run = true;\n\n    // NOTE-- first time doesn't run any section if sections are present.\n    this->failed_sections = 0;\n    this->count_sections = 0;\n    this->expected_section = 0;\n    this->skipped_sections = 0;\n    this->entire_test_skipped = false;\n    run_section();\n\n    if(has_failed_main)\n      return false;\n\n    if(this->entire_test_skipped)\n    {\n      print_test_skipped();\n      return true;\n    }\n\n    this->num_sections = this->count_sections;\n\n    while(this->expected_section < this->num_sections)\n    {\n      this->count_sections = 0;\n      this->expected_section++;\n      run_section();\n    }\n\n    if(this->entire_test_skipped ||\n      (this->num_sections && this->skipped_sections == this->num_sections))\n    {\n      print_test_skipped();\n      this->entire_test_skipped = true;\n      return true;\n    }\n\n    if(this->last_failed_section)\n    {\n      unsigned int passed = this->passed_sections();\n      Uerr(\"  Failed %u section(s)\", this->failed_sections);\n\n      if(this->skipped_sections)\n      {\n        Uerr(\" (passed %u, skipped %u).\\n\", passed, this->skipped_sections);\n      }\n      else\n        Uerr(\" (passed %u).\\n\", passed);\n\n      UerrFlush();\n      return false;\n    }\n\n    print_test_success();\n    return true;\n  }\n\n  int unittest::passed_sections()\n  {\n    return this->count_sections - this->failed_sections - this->skipped_sections;\n  }\n\n  void unittest::print_test_success(void)\n  {\n    const char *_test_name = coalesce(test_name);\n    unsigned int passed = this->passed_sections();\n\n    Uerr(\"Passed test '%s::%s'\", file_name, _test_name);\n\n    if(num_sections > 0)\n    {\n      Uerr(\" (%u section%s\", passed, (passed > 1) ? \"s\" : \"\");\n\n      if(this->skipped_sections)\n        Uerr(\", %u skipped)\\n\", this->skipped_sections);\n      else\n        Uerr(\")\\n\");\n    }\n    else\n      Uerr(\"\\n\");\n\n    UerrFlush();\n  }\n\n  void unittest::print_test_failed(void)\n  {\n    if(!printed_failed)\n    {\n      const char *_test_name = coalesce(test_name);\n\n      Uerr(\"Failed test '%s::%s'\\n\", file_name, _test_name);\n      printed_failed = true;\n    }\n  }\n\n  void unittest::print_test_skipped(void)\n  {\n    const char *_test_name = coalesce(test_name);\n    Uerr(\"Skipping test '%s::%s'\", file_name, _test_name);\n\n    if(this->skipped_sections)\n    {\n      Uerr(\" (%u section%s)\\n\", this->skipped_sections,\n        (this->skipped_sections > 0) ? \"s\" : \"\");\n    }\n    else\n      Uerr(\"\\n\");\n\n    UerrFlush();\n  }\n\n  void unittest::signal_fail()\n  {\n    const char *_test_name = coalesce(this->test_name);\n    const char *_section_name = coalesce(this->section_name);\n\n    if(this->expected_section)\n    {\n      Uerr(\"Test '%s::%s' aborted in section '%s' (#%u out of %u)\\n\",\n        file_name, _test_name, _section_name, this->expected_section, this->num_sections);\n    }\n    else\n      Uerr(\"Test '%s::%s' aborted\\n\", file_name, _test_name);\n  }\n\n  void unittest::print_exception(const Unit::exception &e)\n  {\n    const char *_section_name = coalesce(this->section_name);\n\n    print_test_failed();\n\n    if(this->expected_section)\n    {\n      if(this->last_failed_section < this->expected_section)\n      {\n        this->failed_sections++;\n        Uerr(\"  In section '%s': \\n\", _section_name);\n        this->last_failed_section = this->expected_section;\n      }\n      Uerr(\"    Assert failed at line %d: %s\", e.line, e.test);\n    }\n    else\n    {\n      Uerr(\"  Assert failed at line %d: %s\", e.line, e.test);\n      this->has_failed_main = true;\n    }\n\n    if(e.has_reason)\n      Uerr(\" (%s)\\n\", e.reason);\n    else\n      Uerr(\"\\n\");\n\n    if(e.has_values)\n    {\n      Uerr(\"    Left:  %s\\n\", e.left);\n      Uerr(\"    Right: %s\\n\", e.right);\n    }\n    UerrFlush();\n  }\n\n  void unittest::skip()\n  {\n    if(!this->expected_section)\n    {\n      this->entire_test_skipped = true;\n    }\n    else\n      this->skipped_sections++;\n  }\n\n\n  /************************************************************************\n   * Unit::unittestrunner_cls and support functions.\n   * Use singletons for globals to force initialization order.\n   */\n\n  static std::vector<unittest *> &gettests()\n  {\n    static std::vector<unittest *> tests;\n    return tests;\n  }\n\n  unittestrunner &unittestrunner::get()\n  {\n    static unittestrunner inst;\n    return inst;\n  }\n\n  void unittestrunner::print_status()\n  {\n    if(!total)\n    {\n      Uerr(\"ERROR: no tests defined!\\n\\n\");\n      failed = 1;\n      return;\n    }\n\n    if(total == count && total == passed && !failed && !skipped)\n    {\n      // Print a shorter summary for the general case...\n      Uerr(\"Passed %u test%s.\\n\\n\", total, (total > 1) ? \"s\" : \"\");\n      UerrFlush();\n      return;\n    }\n\n    Uerr(\"\\nSummary:\\n  Tests total: %u\\n\", total);\n\n    if(passed)  Uerr(\"  Tests passed: %u\\n\", passed);\n    if(failed)  Uerr(\"  Tests failed: %u\\n\", failed);\n    if(skipped) Uerr(\"  Tests skipped: %u\\n\", skipped);\n\n    Uerr(\"\\n\");\n    UerrFlush();\n  }\n\n  bool unittestrunner::run()\n  {\n    count = 0;\n    passed = 0;\n    failed = 0;\n    skipped = 0;\n    total = gettests().size();\n\n    for(unittest *t : gettests())\n    {\n      count++;\n      current_test = t;\n      bool result = t->run();\n\n      if(result)\n      {\n        if(!t->entire_test_skipped)\n          passed++;\n        else\n          skipped++;\n      }\n      else\n        failed++;\n    }\n\n    print_status();\n    return !failed;\n  }\n\n  void unittestrunner::signal_fail()\n  {\n    if(current_test)\n      current_test->signal_fail();\n    else\n      Uerr(\"ERROR: NULL test!\\n\");\n\n    failed++;\n    skipped += total - count;\n    print_status();\n  }\n\n  void unittestrunner::addtest(unittest *t)\n  {\n    gettests().push_back(t);\n  }\n\n\n  /************************************************************************\n   * Unit::self_check\n   *\n   * Precheck Unit::arg and Unit::exception to make sure they will behave\n   * as expected if one of the actual tests fails.\n   */\n\n  static bool match(const char *name, const Unit::arg &value, const char *expected, boolean has_value=true)\n  {\n    if(value.has_value != has_value)\n    {\n      Uerr(\"ERROR: self check '%s' failed: has_value %u != %u\\n\", name, value.has_value, has_value);\n      return false;\n    }\n    else\n\n    if(value.op == nullptr || expected == nullptr)\n    {\n      if(value.op != expected)\n      {\n        Uerr(\"ERROR: self check '%s' failed: %p != %p\\n\", name, (void *)value.op, (void *)expected);\n        return false;\n      }\n    }\n    else\n\n    if(strcmp(value.op, expected))\n    {\n      Uerr(\"ERROR: self check '%s' failed: '%s' != '%s'\\n\", name, value.op, expected);\n      return false;\n    }\n    return true;\n  }\n\n  static bool match(const char *name, const Unit::exception &e, int line,\n   const char *test, const char *left, const char *right, const char *reason)\n  {\n    if(!e.test)\n    {\n      Uerr(\"ERROR: self check '%s' failed: NULL test\\n\", name);\n      return false;\n    }\n    if(!e.left)\n    {\n      Uerr(\"ERROR: self check '%s' failed: NULL left\\n\", name);\n      return false;\n    }\n    if(!e.right)\n    {\n      Uerr(\"ERROR: self check '%s' failed: NULL right\\n\", name);\n      return false;\n    }\n    if(!e.reason)\n    {\n      Uerr(\"ERROR: self check '%s' failed: NULL reason\\n\", name);\n      return false;\n    }\n    if(e.line != line)\n    {\n      Uerr(\"ERROR: self check '%s' failed: line %d != %d\\n\", name, e.line, line);\n      return false;\n    }\n    if(strcmp(e.test, test))\n    {\n      Uerr(\"ERROR: self check '%s' failed: test '%s' != '%s'\\n\", name, e.test, test);\n      return false;\n    }\n    if(strcmp(e.left, left))\n    {\n      Uerr(\"ERROR: self check '%s' failed: left '%s' != '%s'\\n\", name, e.left, left);\n      return false;\n    }\n    if(strcmp(e.right, right))\n    {\n      Uerr(\"ERROR: self check '%s' failed: right '%s' != '%s'\\n\", name, e.right, right);\n      return false;\n    }\n    if(strcmp(e.reason, reason))\n    {\n      Uerr(\"ERROR: self check '%s' failed: reason '%s' != '%s'\\n\", name, e.reason, reason);\n      return false;\n    }\n    return true;\n  }\n\n  bool self_check()\n  {\n    /* Test Unit::arg. */\n    if(!match(\"arg::default\", Unit::arg(), nullptr, false))\n      return false;\n\n    if(!match(\"arg::nullptr\", Unit::arg(nullptr), \"NULL\", false))\n      return false;\n\n    if(!match(\"arg::string\", Unit::arg(\"abfjksdsfl\"), \"abfjksdsfl\"))\n      return false;\n\n    if(!match(\"arg::void *\", Unit::arg((const void *)0x12345678), \"0x12345678\"))\n      return false;\n\n    if(!match(\"arg::unsigned char\", Unit::arg((unsigned char)255), \"255\"))\n      return false;\n\n    if(!match(\"arg::unsigned short\", Unit::arg((unsigned short)65535), \"65535\"))\n      return false;\n\n    if(!match(\"arg::unsigned int\", Unit::arg(65535U), \"65535\"))\n      return false;\n\n    if(!match(\"arg::unsigned long\", Unit::arg(2147483647UL), \"2147483647\"))\n      return false;\n\n    Unit::arg ulln(18446744073709551615ULL);\n    if(!match(\"arg::unsigned long long\", ulln, \"18446744073709551615\"))\n      return false;\n\n    if(!match(\"arg::signed char\", Unit::arg((signed char)-1), \"-1\"))\n      return false;\n\n    if(!match(\"arg::short\", Unit::arg((signed short)-32767), \"-32767\"))\n      return false;\n\n    if(!match(\"arg::int\", Unit::arg(-32767), \"-32767\"))\n      return false;\n\n    if(!match(\"arg::long\", Unit::arg(-2147483647L), \"-2147483647\"))\n      return false;\n\n    Unit::arg lln(-9223372036854775807LL);\n    if(!match(\"arg::long long\", lln, \"-9223372036854775807\"))\n      return false;\n\n    int8_t values8s[] = { -1, -2, -3, -4, -5 };\n    uint8_t values8[] = { 1, 2, 3, 4, 5 };\n    uint16_t values16[] = { 0x1234, 0x5678, 0x9abc, 0xdef0 };\n    uint32_t values32[] = { 0xfedc, 0xab98, 0x7654, 0x3210 };\n\n    Unit::arg alloc8s(values8s, sizeof(values8s));\n    if(!match(\"arg::int8_t array\", alloc8s, \"ff fe fd fc fb\"))\n      return false;\n\n    Unit::arg alloc8(values8, sizeof(values8));\n    if(!match(\"arg::uint8_t array\", alloc8, \"01 02 03 04 05\"))\n      return false;\n\n    Unit::arg alloc16(values16, sizeof(values16));\n    if(!match(\"arg::uint16_t array\", alloc16, \"1234 5678 9abc def0\"))\n      return false;\n\n    Unit::arg alloc32(values32, sizeof(values32));\n    if(!match(\"arg::uint32_t array\", alloc32, \"0000fedc 0000ab98 00007654 00003210\"))\n      return false;\n\n    Unit::arg src(12345U);\n    if(!match(\"arg::copy int dest\", Unit::arg(src), \"12345\"))\n      return false;\n    if(!match(\"arg::copy int src\", src, \"12345\"))\n      return false;\n\n    Unit::arg str(\"abcdef\");\n    if(!match(\"arg::copy str dest\", Unit::arg(str), \"abcdef\"))\n      return false;\n    if(!match(\"arg::copy str src\", str, \"abcdef\"))\n      return false;\n\n    if(!match(\"arg::copy array dest\", Unit::arg(alloc8s), \"ff fe fd fc fb\"))\n      return false;\n    if(!match(\"arg::copy array src\", alloc8s, \"ff fe fd fc fb\"))\n      return false;\n\n    if(!match(\"arg::move int dest\", Unit::arg(std::move(src)), \"12345\"))\n      return false;\n    if(!match(\"arg::move int src\", src, nullptr, false))\n      return false;\n\n    if(!match(\"arg::move str dest\", Unit::arg(std::move(str)), \"abcdef\"))\n      return false;\n    if(!match(\"arg::move str src\", str, nullptr, false))\n      return false;\n\n    if(!match(\"arg::move array dest\", Unit::arg(std::move(alloc8)), \"01 02 03 04 05\"))\n      return false;\n    if(!match(\"arg::move array src\", alloc8, nullptr, false))\n      return false;\n\n    /* Test Unit::exception. */\n\n    if(!match(\"exception::fixed\", Unit::exception(1, \"Test name only.\"),\n     1, \"Test name only.\", coalesce(nullptr), coalesce(nullptr), \"\"))\n      return false;\n\n    if(!match(\"exception::fail\", Unit::exception(2, \"Test name+reason\", \"%s%d\", \"abc\", 123),\n     2, \"Test name+reason\", coalesce(nullptr), coalesce(nullptr), \"abc123\"))\n      return false;\n\n    Unit::exception e_equal(3, \"Test equal\", Unit::arg(\"abc\"), Unit::arg(456),\n     \"~Should remove the ~.\");\n    if(!match(\"exception::equal\", e_equal, 3, \"Test equal\", \"abc\", \"456\", \"Should remove the ~.\"))\n      return false;\n\n    Unit::exception e_memcmp(4, \"Test memcmp\",\n     Unit::arg(\"0123\", 4), Unit::arg(\"4567\", 4), \"mesg 0x%08x\", 0x1234);\n    if(!match(\"exception::memcmp\", e_memcmp, 4, \"Test memcmp\",\n     \"30 31 32 33\", \"34 35 36 37\", \"mesg 0x00001234\"))\n      return false;\n\n    Unit::exception e_noreason(5, \"No reason!\", Unit::arg(1), Unit::arg(2), \"~\");\n    if(!match(\"exception::noreason\", e_noreason, 5, \"No reason!\", \"1\", \"2\", coalesce(nullptr)))\n      return false;\n\n    return true;\n  }\n}\n"
  },
  {
    "path": "unit/Unit.hpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Unit test class macros and template. Requires C++11 or higher. Includes a\n * main() implementation, a SIGABRT handler, and custom check_alloc function\n * implementations. To implement a test, simply include this header and write\n * tests as follows:\n *\n * UNITTEST(TestName)\n * {\n *   // test here\n *   // run this once standalone + once for each section\n *\n *   SECTION(SectionName)\n *   {\n *     // only run this once\n *   }\n *\n *   SECTION(SectionName2)\n *   {\n *     // only run this once, but separately from SectionName...\n *   }\n * }\n *\n * The file can contain multiple UNITTEST() {...}. Each must have a unique name.\n * The following macros can be used in tests:\n *\n * [mfmt,...] = optional message to display on failure, passed to vsnprintf.\n *              mfmt must be a string literal.\n *\n * ASSERT(t[,mfmt,...])         t must be non-zero.\n * ASSERTEQ(a,b[,mfmt,...])     a must equal b.\n * ASSERTCMP(a,b[,mfmt,...])    a and b must be null-terminated C strings and must\n *                              be exactly equal (strcmp).\n * ASSERTNCMP(a,b,n[,mfmt,...]) a and b must be null-terminated C strings and must\n *                              be exactly equal for the first n chars (strncmp).\n * ASSERTMEM(a,b,l,[mfmt,...])  the memory pointed to by a and b must be identical\n *                              for l bytes (memcmp).\n * FAIL([mfmt,...])             unconditionally fail the test.\n * SKIP()                       unconditionally skip the test.\n *\n * Additionally, failed assert() assertions will be detected and print error\n * messages (generally, these should only be used in the code being tested).\n *\n * This is not a replacement for the testworlds tests, and likely will be\n * useful only for the subset of MegaZeux algorithm and IO code that can be\n * tested completely standalone from the rest of MegaZeux.\n */\n\n#ifndef UNIT_HPP\n#define UNIT_HPP\n\n#ifdef __M_BEGIN_DECLS\n#error \"Include Unit.hpp first!\"\n#endif\n\n#define CORE_LIBSPEC\n#define EDITOR_LIBSPEC\n#define SKIP_SDL\n#include \"../src/compat.h\"\n#include \"../src/platform_attribute.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define __STDC_FORMAT_MACROS\n#include <inttypes.h>\n\n/**\n * Utility templates.\n */\n\ntemplate<class T, class S, int A, int B>\nstatic inline constexpr int samesize(T (&a)[A], S (&b)[B])\n{\n  static_assert(A == B, \"array size mismatch\");\n  return 0;\n}\n\ntemplate<class T, int A>\nstatic inline constexpr int arraysize(T (&a)[A])\n{\n  return A;\n}\n\ntemplate<class T>\nstatic inline const T coalesce(const T var)\n{\n  return var;\n}\n\nstatic inline unsigned int coalesce(boolean var)\n{\n  return var;\n}\n\nstatic inline const char *coalesce(const char *var)\n{\n  return (var ? var : \"NULL\");\n}\n\nstatic inline constexpr const char *coalesce(decltype(nullptr) ignore)\n{\n  return \"NULL\";\n}\n\ntemplate<class aligntype, size_t A=128>\nclass alignstr\n{\nprotected:\n  union\n  {\n    char arr[A];\n    aligntype ignore;\n  } u;\n\npublic:\n  template<int B>\n  alignstr(const char (&str)[B])\n  {\n    static_assert(A >= B, \"alignstr buffer is too small!\");\n    memcpy(u.arr, str, B);\n  }\n\n  alignstr(const char * const str)\n  {\n    size_t slen = strlen(str);\n    if(slen + 1 > A)\n      abort();\n    memcpy(u.arr, str, slen);\n  }\n\n  constexpr const char *c_str() const\n  {\n    return u.arr;\n  }\n};\n\n/**\n * Unit test macros.\n */\n\n#define UNIMPLEMENTED() \\\n  do\\\n  {\\\n    throw Unit::exception(__LINE__, \"Test is not yet implemented\"); \\\n  } while(0)\n\n#define ASSERT(test, ...) \\\n  do\\\n  {\\\n    if(!(test)) \\\n    { \\\n      throw Unit::exception(__LINE__, #test, \"~\" __VA_ARGS__); \\\n    } \\\n  } while(0)\n\n#define ASSERTEQ(a, b, ...) \\\n  do\\\n  {\\\n    if(!((a) == (b)))\\\n    { \\\n      throw Unit::exception(__LINE__, #a \" == \" #b, \\\n       Unit::arg(a), Unit::arg(b), \"~\" __VA_ARGS__); \\\n    } \\\n  } while(0)\n\n#define ASSERTCMP(a, b, ...) \\\n  do\\\n  {\\\n    if(strcmp(a,b)) \\\n    {\\\n      throw Unit::exception(__LINE__, \"strcmp(\" #a \", \" #b \")\", \\\n       Unit::arg(a), Unit::arg(b), \"~\" __VA_ARGS__); \\\n    }\\\n  } while(0)\n\n#define ASSERTNCMP(a, b, n, ...) \\\n  do\\\n  {\\\n    if(strncmp(a,b,n)) \\\n    {\\\n      throw Unit::exception(__LINE__, \"strncmp(\" #a \", \" #b \", \" #n \")\", \\\n       Unit::arg(a), Unit::arg(b), \"~\" __VA_ARGS__); \\\n    }\\\n  } while(0)\n\n#define ASSERTMEM(a, b, l, ...) \\\n  do\\\n  {\\\n    if(memcmp(a,b,l)) \\\n    {\\\n      throw Unit::exception(__LINE__, \"memcmp(\" #a \", \" #b \", \" #l \")\", \\\n       Unit::arg(a, l), Unit::arg(b, l), \"~\" __VA_ARGS__); \\\n    }\\\n  } while(0)\n\n#define FAIL(...) \\\n  do\\\n  {\\\n    throw Unit::exception(__LINE__, nullptr, \"\" __VA_ARGS__); \\\n  } while(0)\n\n#define SKIP() \\\n  do\\\n  {\\\n    throw Unit::skip(); \\\n  } while(0)\n\n#define SECTION(sectionname) \\\n  this->section_name = #sectionname; \\\n  if((++this->count_sections) == this->expected_section)\n\n#define UNITTEST(testname) \\\nclass testname ## _unittest_impl final : public Unit::unittest \\\n{\\\n  void run_impl() override;\\\npublic:\\\n  testname ## _unittest_impl(): Unit::unittest(__FILE__, #testname) {}\\\n}\\\nstatic testname ## _unittest_inst;\\\n\\\ninline void testname ## _unittest_impl::run_impl(void)\n\n#define Uerr(...) do{ fprintf(stderr, \"\" __VA_ARGS__); }while(0)\n#define UerrFlush() do{ fflush(stderr); }while(0)\n\nnamespace Unit\n{\n  class unittest;\n\n  template<class T>\n  constexpr const T &min(const T &a, const T &b)\n  {\n    return (a < b) ? a : b;\n  }\n\n  template<class T>\n  constexpr const T &max(const T &a, const T &b)\n  {\n    return (a > b) ? a : b;\n  }\n\n  template<class T>\n  constexpr const T &clamp(const T &a, const T &_min, const T &_max)\n  {\n    return Unit::max(_min, Unit::min(_max, a));\n  }\n\n  /**\n   * Input value to a failed assertion. These are generated for Unit::exception\n   * instances thrown from assertions with multiple operands. This provides a\n   * printable text representation of the operand (i.e. std::string but worse)\n   * and also disambiguates the correct Unit::exception constructor to use.\n   */\n  class arg final\n  {\n    static constexpr int BUF_SIZE = 23;\n    char *allocbuf = nullptr;\n    char tmpbuf[BUF_SIZE] = { '\\0' };\n\n  public:\n    boolean has_value = false;\n    const char *op = nullptr;\n\n    arg();\n    arg(arg &&a);\n    arg(const arg &a);\n    ~arg();\n\n    explicit arg(decltype(nullptr) _op);\n    explicit arg(const char *_op);\n    explicit arg(const void *_op);\n    explicit arg(unsigned char _op);\n    explicit arg(unsigned short _op);\n    explicit arg(unsigned int _op);\n    explicit arg(unsigned long _op);\n    explicit arg(unsigned long long _op);\n    explicit arg(signed char _op);\n    explicit arg(short _op);\n    explicit arg(int _op);\n    explicit arg(long _op);\n    explicit arg(long long _op);\n\n    explicit arg(const void *_ptr, size_t length_bytes);\n    explicit arg(const unsigned short *_ptr, size_t length_bytes);\n    explicit arg(const unsigned int *_ptr, size_t length_bytes);\n    explicit arg(const unsigned long *_ptr, size_t length_bytes);\n\n  private:\n    const char *fix_op(const arg &src);\n  };\n\n  /**\n   * Thrown to signal the current test (or test section) has been skipped.\n   */\n  class skip final {};\n\n  /**\n   * Thrown on test failure (see ASSERT macros above).\n   */\n  class exception final\n  {\n  private:\n    char reasonbuf[1024];\n    Unit::arg leftarg;\n    Unit::arg rightarg;\n\n    void diagnostics();\n\n  public:\n    int line;\n    const char *test      = coalesce(nullptr);\n    const char *reason    = coalesce(nullptr);\n    bool has_reason       = false;\n    const char *left      = coalesce(nullptr);\n    const char *right     = coalesce(nullptr);\n    bool has_values       = false;\n\n    /* A copy constructor is required to be throwable. */\n    exception(const exception &e);\n\n    exception(int _line, const char *_test);\n    ATTRIBUTE_PRINTF(4, 5)\n    exception(int _line, const char *_test, const char *_reason_fmt, ...);\n    ATTRIBUTE_PRINTF(6, 7)\n    exception(int _line, const char *_test, Unit::arg &&_left, Unit::arg &&_right,\n     const char *_reason_fmt, ...);\n  };\n\n  /**\n   * Class for running all unit tests in the current set of unit tests.\n   */\n  class unittestrunner final\n  {\n  private:\n\n    unittest *current_test;\n    unsigned int count;\n    unsigned int passed;\n    unsigned int failed;\n    unsigned int skipped;\n    unsigned int total;\n\n    void print_status();\n\n  public:\n    static unittestrunner &get();\n    bool run();\n    void signal_fail();\n    void addtest(unittest *t);\n  };\n\n  /**\n   * Class for an individual unit test. Don't use directly; create individual\n   * test subclasses using the UNITTEST() macro.\n   */\n  class unittest\n  {\n    bool has_run = false;\n    bool has_failed_main = false;\n    unsigned int last_failed_section = 0;\n    bool printed_failed = false;\n\n  protected:\n\n    const char *file_name;\n    const char *test_name;\n    const char *section_name = nullptr;\n\n    unsigned int num_sections;\n    unsigned int count_sections;\n    unsigned int expected_section;\n    unsigned int failed_sections;\n    unsigned int skipped_sections;\n\n  public:\n\n    bool entire_test_skipped;\n\n    unittest(const char *_file, const char *_test_name);\n\n    bool run();\n    void signal_fail();\n\n  private:\n    void run_section(void);\n    int passed_sections();\n    void print_test_success(void);\n    void print_test_failed(void);\n    void print_test_skipped(void);\n    void print_exception(const Unit::exception &e);\n    void skip();\n\n    virtual void run_impl(void) = 0;\n  };\n\n  /**\n   * Check the unit test system before running any tests.\n   */\n  bool self_check();\n}\n\n#endif /* UNIT_HPP */\n"
  },
  {
    "path": "unit/UnitIO.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"UnitIO.hpp\"\n\n#include <vector>\n#include <zlib.h>\n\nstd::vector<uint8_t> unit::io::load_compressed(const char *path)\n{\n  std::vector<uint8_t> compressed = load(path);\n  ASSERT(compressed.size() > 18, \"not gzip\");\n  uint32_t uncompressed_size =\n   read32le(compressed.data() + compressed.size() - 4);\n\n  std::vector<uint8_t> out(uncompressed_size);\n\n  z_stream zs{};\n  zs.avail_out = uncompressed_size;\n  zs.next_out = out.data();\n  zs.avail_in = compressed.size();\n  zs.next_in = compressed.data();\n\n  int ret = inflateInit2(&zs, MAX_WBITS | 16);\n  ASSERTEQ(ret, Z_OK, \"failed inflate init\");\n  ret = inflate(&zs, Z_FINISH);\n  ASSERTEQ(ret, Z_STREAM_END, \"failed inflate\");\n  inflateEnd(&zs);\n\n  return out;\n}\n\nvoid unit::io::save_compressed(const std::vector<uint8_t> &src, const char *path)\n{\n  size_t path_len = strlen(path);\n  ASSERTCMP(path + path_len - 3, \".gz\", \"filename must end in .gz\");\n\n  z_stream zs{};\n  zs.avail_in = src.size();\n  zs.next_in = (Bytef *)src.data();\n\n  int ret = deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED,\n   MAX_WBITS | 16, 9, Z_DEFAULT_STRATEGY);\n  ASSERTEQ(ret, Z_OK, \"failed deflate init\");\n\n  size_t compressed_bound = deflateBound(&zs, src.size());\n\n  std::vector<uint8_t> compressed(compressed_bound);\n\n  zs.avail_out = compressed_bound;\n  zs.next_out = compressed.data();\n\n  ret = deflate(&zs, Z_FINISH);\n  ASSERTEQ(ret, Z_STREAM_END, \"failed deflate\");\n\n  size_t total_len = compressed_bound - zs.avail_out;\n  deflateEnd(&zs);\n\n  FILE *fp = fopen_unsafe(path, \"wb\");\n  ASSERT(fp, \"failed fopen\");\n  size_t out_len = fwrite(compressed.data(), 1, total_len, fp);\n  ASSERTEQ(out_len, total_len, \"failed fwrite\");\n  fclose(fp);\n}\n\n/* TGA writer: always RLE, outputs with gzip compression if filename is .gz */\ntemplate<typename T>\nstatic void write_tga(const std::vector<T> &pixels, unsigned w, unsigned h,\n const uint32_t *flat_palette, unsigned colors, const char *file)\n{\n  static constexpr int is_indexed = sizeof(T) == 1;\n  static constexpr int alphabits = sizeof(T) == 2 ? 1 : 8;\n\n  std::vector<uint8_t> out;\n  out.reserve(w * h * sizeof(T));\n\n  out.push_back(0);\n  out.push_back(is_indexed ? 1 : 0);\n  out.push_back(is_indexed ? 9 : 10);\n  unit::io::put16le(out, 0);\n  unit::io::put16le(out, is_indexed ? colors : 0);\n  out.push_back(is_indexed ? 32 : 0);\n  unit::io::put16le(out, 0);\n  unit::io::put16le(out, h);\n  unit::io::put16le(out, w);\n  unit::io::put16le(out, h);\n  out.push_back(sizeof(T) * 8);\n  out.push_back(0x20 | alphabits);\n\n  if(is_indexed)\n    unit::io::put_raw(out, flat_palette, colors * sizeof(uint32_t));\n\n  size_t offset = 0;\n  for(size_t y = 0; y < h; y++)\n  {\n    for(size_t x = 0; x < w; )\n    {\n      size_t num = 1;\n      size_t start = offset;\n      T color = pixels[offset++];\n      x++;\n      while(x < w && num < 128 && pixels[offset] == color)\n        offset++, x++, num++;\n      if(num > 1)\n      {\n        out.push_back(0x80 | (num - 1));\n        unit::io::put_raw(out, &color, sizeof(T));\n        continue;\n      }\n\n      while(x < w && num < 128 &&\n       !(x + 1 < w && pixels[offset] == pixels[offset + 1]))\n        offset++, x++, num++;\n\n      out.push_back(num - 1);\n      unit::io::put_raw(out, pixels.data() + start, num * sizeof(T));\n    }\n  }\n  unit::io::put32le(out, 0);\n  unit::io::put32le(out, 0);\n  unit::io::put_raw(out, \"TRUEVISION-XFILE.\", 18);\n\n  ssize_t ext_pos = strlen(file) - 3;\n  if(ext_pos >= 0 && !strcmp(file + ext_pos, \".gz\"))\n    unit::io::save_compressed(out, file);\n  else\n    unit::io::save(out, file);\n}\n\nvoid unit::io::save_tga(\n const std::vector<uint8_t> &pixels, unsigned w, unsigned h,\n const uint32_t *palette, unsigned colors, const char *path)\n{\n  write_tga(pixels, w, h, palette, colors, path);\n}\n\nvoid unit::io::save_tga(\n const std::vector<uint16_t> &pixels, unsigned w, unsigned h,\n const uint32_t *unused, unsigned unused2, const char *path)\n{\n  write_tga(pixels, w, h, nullptr, 0, path);\n}\n\nvoid unit::io::save_tga(\n const std::vector<uint32_t> &pixels, unsigned w, unsigned h,\n const uint32_t *unused, unsigned unused2, const char *path)\n{\n  write_tga(pixels, w, h, nullptr, 0, path);\n}\n\n/* TGA loader: only returns raw image data.\n * Currently only loads the subset of TGAs that are saved by save_tga. */\ntemplate<typename T>\nstatic std::vector<T> load_tga(const char *file)\n{\n  static constexpr int is_indexed = sizeof(T) == 1;\n  static constexpr int alphabits = sizeof(T) == 2 ? 1 : 8;\n\n  std::vector<uint8_t> image;\n  std::vector<T> pixels;\n  size_t offset = 18;\n\n  ssize_t ext_pos = strlen(file) - 3;\n  if(ext_pos >= 0 && !strcmp(file + ext_pos, \".gz\"))\n    image = unit::io::load_compressed(file);\n  else\n    image = unit::io::load(file);\n\n  ASSERT(image.size() > 44, \"minimum size check failed\");\n  ASSERTMEM(image.data() + image.size() - 18,\n   \"TRUEVISION-XFILE.\", 18, \"magic check failed\");\n\n  uint16_t w = unit::io::read16le(image.data() + 12);\n  uint16_t h = unit::io::read16le(image.data() + 14);\n\n  if(is_indexed)\n  {\n    uint16_t colors = unit::io::read16le(image.data() + 5);\n    ASSERTEQ(image[1], 1, \"not indexed\");\n    ASSERTEQ(image[2], 9, \"not indexed RLE\");\n    ASSERT(colors > 0, \"no colors\");\n    ASSERTEQ(image[7], 32, \"not 32bpp indexed\");\n    offset += colors * 4;\n  }\n  else\n  {\n    ASSERTEQ(image[1], 0, \"indexed\");\n    ASSERTEQ(image[2], 10, \"not truecolor RLE\");\n  }\n  ASSERT(w != 0, \"bad width\");\n  ASSERT(h != 0, \"bad height\");\n  ASSERTEQ(image[16], sizeof(T) * 8, \"depth mismatch\");\n  ASSERTEQ(image[17] & 0xf0, 0x20, \"not top-to-bottom left-to-right\");\n  ASSERTEQ(image[17] & 0x0f, alphabits, \"wrong alphabits\");\n\n  size_t total_px = (size_t)w * h;\n  pixels.resize(total_px * sizeof(T));\n\n  size_t end = image.size() - 18;\n  size_t pos = 0;\n  while(offset < end && pos < total_px)\n  {\n    size_t num = (image[offset] & 0x7f) + 1;\n    ASSERT(num < total_px && pos <= total_px - num, \"bad RLE\");\n    if(image[offset++] & 0x80)\n    {\n      T color;\n      memcpy(&color, image.data() + offset, sizeof(T));\n      offset += sizeof(T);\n      for(size_t i = 0; i < num; i++)\n        pixels[pos++] = color;\n    }\n    else\n    {\n      memcpy(pixels.data() + pos, image.data() + offset,\n       sizeof(T) * num);\n      offset += sizeof(T) * num;\n      pos += num;\n    }\n  }\n  return pixels;\n}\n\ntemplate<>\nstd::vector<uint8_t> unit::io::load_tga<uint8_t>(const char *path)\n{\n  return ::load_tga<uint8_t>(path);\n}\n\ntemplate<>\nstd::vector<uint16_t> unit::io::load_tga<uint16_t>(const char *path)\n{\n  return ::load_tga<uint16_t>(path);\n}\n\ntemplate<>\nstd::vector<uint32_t> unit::io::load_tga<uint32_t>(const char *path)\n{\n  return ::load_tga<uint32_t>(path);\n}\n"
  },
  {
    "path": "unit/UnitIO.hpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#ifndef UNIT_IO_HPP\n#define UNIT_IO_HPP\n\n#include \"Unit.hpp\"\n\n#include <stdint.h>\n#include <stdio.h>\n#include <vector>\n#include <zlib.h>\n\nnamespace unit\n{\n  class io final\n  {\n    FILE *file_handle = nullptr;\n    size_t file_length;\n\n    io(const char *path)\n    {\n      file_handle = fopen_unsafe(path, \"rb\");\n      ASSERT(file_handle, \"couldn't load file: %s\", path);\n\n      if(fseek(file_handle, 0, SEEK_END) < 0)\n        FAIL(\"failed to seek to end: %s\", path);\n\n#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64\n      int64_t len = ftello(file_handle);\n#else\n      long len = ftell(file_handle);\n#endif\n      ASSERT(len >= 0, \"failed ftell: %s\", path);\n      file_length = len;\n\n      rewind(file_handle);\n    }\n\n    ~io()\n    {\n      fclose(file_handle);\n    }\n\n  public:\n    static uint16_t read16le(const uint8_t *src)\n    {\n      return src[0] | (src[1] << 8u);\n    }\n    static uint16_t read16le(const std::vector<uint8_t> &src, size_t off)\n    {\n      return read16le(src.data() + off);\n    }\n    static void put16le(std::vector<uint8_t> &dest, uint16_t value)\n    {\n      dest.push_back(value & 0xff);\n      dest.push_back(value >> 8);\n    }\n\n    static uint32_t read32le(const uint8_t *src)\n    {\n      return src[0] | (src[1] << 8u) | (src[2] << 16u) | (src[3] << 24u);\n    }\n    static uint32_t read32le(const std::vector<uint8_t> &src, size_t off)\n    {\n      return read32le(src.data() + off);\n    }\n    static void put32le(std::vector<uint8_t> &dest, uint32_t value)\n    {\n      dest.push_back(value & 0xff);\n      dest.push_back(value >> 8);\n      dest.push_back(value >> 16);\n      dest.push_back(value >> 24);\n    }\n\n    static void put_raw(std::vector<uint8_t> &dest, const void *src, size_t bytes)\n    {\n      const uint8_t *pos = reinterpret_cast<const uint8_t *>(src);\n      dest.insert(dest.end(), pos, pos + bytes);\n    }\n\n    static std::vector<uint8_t> load(const char *path)\n    {\n      io in(path);\n\n      std::vector<uint8_t> out(in.file_length);\n\n      if(fread(out.data(), 1, in.file_length, in.file_handle) < in.file_length)\n        FAIL(\"failed to read file data: %s\", path);\n\n      return out;\n    }\n\n    static void load_vector(std::vector<uint8_t> &out, const char *path)\n    {\n      io in(path);\n\n      out.resize(in.file_length);\n\n      if(fread(out.data(), 1, in.file_length, in.file_handle) < in.file_length)\n        FAIL(\"failed to read file data: %s\", path);\n    }\n\n    template<typename T>\n    static T *load_buffer(T *buffer, size_t *buffer_len, const char *path)\n    {\n      io in(path);\n      ASSERT(in.file_length <= *buffer_len, \"buffer too small: %s\", path);\n\n      if(fread(buffer, 1, in.file_length, in.file_handle) < in.file_length)\n        FAIL(\"failed to read file data: %s\", path);\n\n      *buffer_len = in.file_length;\n      return buffer;\n    }\n\n    template<typename T>\n    static T *load_buffer(T *buffer, size_t buffer_len, const char *path)\n    {\n      return load_buffer(buffer, &buffer_len, path);\n    }\n\n    static void save(const std::vector<uint8_t> &src, const char *path)\n    {\n      FILE *out = fopen_unsafe(path, \"wb\");\n      ASSERT(out, \"failed fopen\");\n      size_t sz = fwrite(src.data(), 1, src.size(), out);\n      fclose(out);\n      ASSERTEQ(sz, src.size(), \"failed fwrite\");\n    }\n\n    static std::vector<uint8_t> load_compressed(const char *path);\n    static void save_compressed(const std::vector<uint8_t> &src, const char *path);\n\n    static void save_tga(\n     const std::vector<uint8_t> &pixels, unsigned w, unsigned h,\n     const uint32_t *palette, unsigned colors, const char *path);\n    static void save_tga(\n     const std::vector<uint16_t> &pixels, unsigned w, unsigned h,\n     const uint32_t *unused, unsigned unused2, const char *path);\n    static void save_tga(\n     const std::vector<uint32_t> &pixels, unsigned w, unsigned h,\n     const uint32_t *unused, unsigned unused2, const char *path);\n\n    template<typename T>\n    static std::vector<T> load_tga(const char *path);\n  };\n  template<> std::vector<uint8_t> io::load_tga<uint8_t>(const char *);\n  template<> std::vector<uint16_t> io::load_tga<uint16_t>(const char *);\n  template<> std::vector<uint32_t> io::load_tga<uint32_t>(const char *);\n}\n\n#endif /* UNIT_IO_HPP */\n"
  },
  {
    "path": "unit/align.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Test the alignment of several name fields in structs.\n * If enough inputs into memcmp and memcasecmp are consistently 4 aligned,\n * this allows faster comparisons to be performed and also allows some code\n * to assume alignment will be the general case.\n */\n\n#include \"Unit.hpp\"\n#include \"../src/counter_struct.h\"\n#include \"../src/platform_endian.h\"\n#include \"../src/io/zip.h\"\n\nUNITTEST(counter_struct_name)\n{\n  static_assert((offsetof(struct counter, name) & (ALIGN_32_MODULO - 1)) == 0,\n   \"struct counter::name is not aligned to 4 bytes!\");\n}\n\nUNITTEST(string_struct_name)\n{\n  static_assert((offsetof(struct string, name) & (ALIGN_32_MODULO - 1)) == 0,\n   \"struct string::name is not aligned to 4 bytes!\");\n}\n\nUNITTEST(zip_file_header_file_name)\n{\n  static_assert((offsetof(struct zip_file_header, file_name) & (ALIGN_32_MODULO - 1)) == 0,\n   \"struct zip_file_header::file_name is not aligned to 4 bytes!\");\n}\n"
  },
  {
    "path": "unit/audio/mixer.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2024-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../Unit.hpp\"\n#include \"../UnitIO.hpp\"\n\n#include \"../../src/audio/sampled_stream.cpp\"\n\n#include <math.h>\n#include <time.h>\n\n//#define GENERATE_TEST_FILES\n\n#define DATA_BASE_DIR \"../mixer\"\n\nvoid destruct_audio_stream(struct audio_stream *a_src)\n{\n  // nop to shut up linker\n}\n\nstruct audio audio; // shut up linker\n\nstruct sequence\n{\n  double delta;\n  unsigned volume;\n};\n\ntemplate<int N>\nvoid adjust_path(char (&buffer)[N], const char *filename)\n{\n  int ret = snprintf(buffer, sizeof(buffer), DATA_BASE_DIR \"/%s\", filename);\n  ASSERT(ret < N, \"wtf\");\n}\n\ntemplate<mixer_channels SRC_CHANNELS>\nclass mixer_input\n{\n  const char *filename;\n  std::vector<int16_t> input;\n  size_t input_frames = 0;\n  bool did_init = false;\n\npublic:\n  mixer_input(const char *f): filename(f) {}\n\n  void init()\n  {\n    if(did_init)\n      return;\n\n    char path[512];\n    adjust_path(path, filename);\n\n    std::vector<uint8_t> in = unit::io::load(path);\n    input_frames = in.size() / (SRC_CHANNELS * 2);\n    input.resize(input_frames * SRC_CHANNELS + PROLOGUE_LENGTH + EPILOGUE_LENGTH);\n\n    size_t i, j;\n    for(i = 0, j = PROLOGUE_LENGTH; i < in.size(); i += 2, j++)\n      input[j] = static_cast<int16_t>(unit::io::read16le(in, i));\n\n    for(j = 0; j < PROLOGUE_LENGTH; j++)\n      input[j] = 0;\n    for(j = 0; j < EPILOGUE_LENGTH; j++)\n      input[input.size() - j - 1] = 0;\n\n    did_init = true;\n  }\n\n  const std::vector<int16_t> &get() const\n  {\n    ASSERT(did_init, \"run init() before calling get()\");\n    return input;\n  }\n\n  const int16_t *start() const\n  {\n    ASSERT(did_init, \"run init() before calling start()\");\n    return input.data() + PROLOGUE_LENGTH;\n  }\n\n  size_t frames() const\n  {\n    ASSERT(did_init, \"run init() before calling frames()\");\n    return input_frames;\n  }\n};\n\n/**\n * Each test steps through its input sequence of notes, mixing each note\n * at the specified frequency-delta and volume and appending the result to\n * a buffer. This buffer is compared against the expected output file.\n * Each frequency-delta is (playback rate / sample rate), so 2.0 will play\n * the input sample at double speed.\n *\n * Each individual note is mixed into an initial pseudo-randomized noise\n * background, which is immediately subtracted out of the result after\n * mixing. ceil(input_samples / delta) are requested from the mix function.\n */\ntemplate<mixer_channels DEST_CHANNELS, mixer_channels SRC_CHANNELS,\n mixer_volume VOLUME, mixer_resample RESAMPLE>\nclass mixer_tester\n{\n  static constexpr int sample_rate = 8192;\n  struct sampled_stream strm{};\n  const mixer_input<SRC_CHANNELS> &input;\n  uint64_t seed = 0;\n  uint64_t stored_seed = 0;\n\n  uint32_t random(uint32_t range)\n  {\n    if(!seed)\n      seed = 1u;\n\n    uint64_t x = seed;\n    x ^= x >> 12;\n    x ^= x << 25;\n    x ^= x >> 27;\n    return (((x * 0x2545f4914f6cdd1dull) >> 32u) * range) >> 32u;\n  }\n\n  void init_noise_background(std::vector<int32_t> &dest)\n  {\n    stored_seed = seed;\n    for(int32_t &smp : dest)\n      smp = random(65536) - 32768;\n  }\n\n  void remove_noise_background(std::vector<int32_t> &dest)\n  {\n    seed = stored_seed;\n    for(int32_t &smp : dest)\n      smp -= random(65536) - 32768;\n  }\n\n  void mix(std::vector<int32_t> &_dest, size_t dest_frames)\n  {\n    int32_t *dest = _dest.data();\n    const int16_t *src = input.start();\n    int volume = strm.a.volume;\n    switch(RESAMPLE)\n    {\n      case FLAT:\n        flat_mix_loop<DEST_CHANNELS, SRC_CHANNELS, VOLUME>(\n         &strm, dest, dest_frames, src, volume);\n        break;\n      case NEAREST:\n        resample_mix_loop<DEST_CHANNELS, SRC_CHANNELS, VOLUME, nearest_mix<SRC_CHANNELS>>(\n         &strm, dest, dest_frames, src, volume);\n        break;\n      case LINEAR:\n        resample_mix_loop<DEST_CHANNELS, SRC_CHANNELS, VOLUME, linear_mix<SRC_CHANNELS>>(\n         &strm, dest, dest_frames, src, volume);\n        break;\n      case CUBIC:\n        resample_mix_loop<DEST_CHANNELS, SRC_CHANNELS, VOLUME, cubic_mix<SRC_CHANNELS>>(\n         &strm, dest, dest_frames, src, volume);\n        break;\n    }\n  }\n\n\npublic:\n  mixer_tester(const mixer_input<SRC_CHANNELS> &in): input(in)\n  {\n    seed = (uint64_t)time(NULL) << 32u;\n  }\n\n  template<int N>\n  void test(const sequence (&seq)[N], const char *expected_file,\n   bool allow_generate = false)\n  {\n    std::vector<int16_t> render;\n\n    char expected_path[512];\n    adjust_path(expected_path, expected_file);\n\n    strm.channels = SRC_CHANNELS; // TODO: surround\n    strm.dest_channels = DEST_CHANNELS; // TODO: surround\n    {\n      std::vector<int32_t> dest;\n      size_t dest_frames;\n\n      for(const sequence &s : seq)\n      {\n        if(RESAMPLE == FLAT)\n          ASSERT(s.delta == 1.0, \"invalid frequency delta for FLAT resampler\");\n\n        strm.a.volume = s.volume;\n        strm.sample_index = 0;\n        //strm.frequency = static_cast<size_t>(sample_rate * s.delta);\n        strm.frequency_delta = static_cast<int64_t>((1 << FP_SHIFT) * s.delta);\n\n        dest_frames = static_cast<size_t>(ceil(input.frames() / s.delta));\n        dest.resize(dest_frames * strm.dest_channels);\n\n        init_noise_background(dest);\n        mix(dest, dest_frames);\n        remove_noise_background(dest);\n\n        size_t pos = render.size();\n        render.resize(pos + dest.size());\n        for(size_t i = 0; i < dest.size(); i++)\n          render[pos + i] = Unit::clamp((int)dest[i], -32768, 32767);\n      }\n    }\n\n    std::vector<uint8_t> raw;\n    for(size_t i = 0; i < render.size(); i++)\n      unit::io::put16le(raw, render[i]);\n\n#ifdef GENERATE_TEST_FILES\n    if(allow_generate)\n      unit::io::save(raw, expected_path);\n#else\n    std::vector<uint8_t> expected = unit::io::load(expected_path);\n    ASSERTEQ(raw.size(), expected.size(), \"size mismatch\");\n    for(size_t j = 0; j < raw.size(); j++)\n    {\n      int diff = raw[j] - expected[j];\n      ASSERT(diff >= -1 && diff <= 1, \"data mismatch @ %zu: %d != %d\",\n       j, raw[j], expected[j]);\n    }\n#endif\n  }\n\n  void done()\n  {\n#ifdef GENERATE_TEST_FILES\n    FAIL(\"generation code enabled\");\n#endif\n  }\n};\n\n/**\n * Each target is tested twice--first with a sequence where every volume is\n * 256, and then with a sequence where the volume varies for every note.\n * \"Full\" targets should render both identical to all 256; \"dynamic\" should\n * render the same as \"full\" for all 256, but apply the input volume for\n * the second sequence.\n *\n * Since the \"flat\" resample mode does no actual resampling, only one rate\n * ratio is tested for it (1.0). All others test various ratios from 0.5 to\n * 2.0, but theoretically any ratio should be valid.\n */\n\nstatic constexpr sequence flat_full[] =\n{\n  { 1.0, 256 },\n};\n\nstatic constexpr sequence flat_dynamic[] =\n{\n  { 1.0, 192 },\n};\n\nstatic constexpr sequence resample_full[] =\n{\n  { 0.5, 256 },\n  { 0.75, 256 },\n  { 1.0, 256 },\n  { 1.5, 256 },\n  { 2.0, 256 },\n};\n\nstatic constexpr sequence resample_dynamic[] =\n{\n  { 0.5, 224 },\n  { 0.75, 256 },\n  { 1.0, 192 },\n  { 1.5, 157 },\n  { 2.0, 219 },\n};\n\nstatic mixer_input<MONO> mono(\"m.raw\");\nstatic mixer_input<STEREO> stereo(\"s.raw\");\n\n#define GENERATE_SECTIONS(seq_full, seq_dynamic, resample) \\\ndo { \\\n  mono.init(); \\\n  stereo.init(); \\\n  \\\n  SECTION(MonoToMono)                                           \\\n  {                                                             \\\n    mixer_tester<MONO, MONO, FULL, resample> t(mono);           \\\n    t.test(seq_full, #resample \"_mm.raw\");                      \\\n    t.test(seq_dynamic, #resample \"_mm.raw\", true);             \\\n    t.done();                                                   \\\n  }                                                             \\\n  SECTION(MonoToMonoDynamic)                                    \\\n  {                                                             \\\n    mixer_tester<MONO, MONO, DYNAMIC, resample> t(mono);        \\\n    t.test(seq_full, #resample \"_mm.raw\");                      \\\n    t.test(seq_dynamic, #resample \"_mm_dyn.raw\", true);         \\\n    t.done();                                                   \\\n  }                                                             \\\n  SECTION(MonoToStereo)                                         \\\n  {                                                             \\\n    mixer_tester<STEREO, MONO, FULL, resample> t(mono);         \\\n    t.test(seq_full, #resample \"_ms.raw\");                      \\\n    t.test(seq_dynamic, #resample \"_ms.raw\", true);             \\\n    t.done();                                                   \\\n  }                                                             \\\n  SECTION(MonoToStereoDynamic)                                  \\\n  {                                                             \\\n    mixer_tester<STEREO, MONO, DYNAMIC, resample> t(mono);      \\\n    t.test(seq_full, #resample \"_ms.raw\");                      \\\n    t.test(seq_dynamic, #resample \"_ms_dyn.raw\", true);         \\\n    t.done();                                                   \\\n  }                                                             \\\n  SECTION(StereoToMono)                                         \\\n  {                                                             \\\n    mixer_tester<MONO, STEREO, FULL, resample> t(stereo);       \\\n    t.test(seq_full, #resample \"_sm.raw\");                      \\\n    t.test(seq_dynamic, #resample \"_sm.raw\", true);             \\\n    t.done();                                                   \\\n  }                                                             \\\n  SECTION(StereoToMonoDynamic)                                  \\\n  {                                                             \\\n    mixer_tester<MONO, STEREO, DYNAMIC, resample> t(stereo);    \\\n    t.test(seq_full, #resample \"_sm.raw\");                      \\\n    t.test(seq_dynamic, #resample \"_sm_dyn.raw\", true);         \\\n    t.done();                                                   \\\n  }                                                             \\\n  SECTION(StereoToStereo)                                       \\\n  {                                                             \\\n    mixer_tester<STEREO, STEREO, FULL, resample> t(stereo);     \\\n    t.test(seq_full, #resample \"_ss.raw\");                      \\\n    t.test(seq_dynamic, #resample \"_ss.raw\", true);             \\\n    t.done();                                                   \\\n  }                                                             \\\n  SECTION(StereoToStereoDynamic)                                \\\n  {                                                             \\\n    mixer_tester<STEREO, STEREO, DYNAMIC, resample> t(stereo);  \\\n    t.test(seq_full, #resample \"_ss.raw\");                      \\\n    t.test(seq_dynamic, #resample \"_ss_dyn.raw\", true);         \\\n    t.done();                                                   \\\n  }                                                             \\\n} while(0)\n\nUNITTEST(Flat)\n{\n  GENERATE_SECTIONS(flat_full, flat_dynamic, FLAT);\n}\n\nUNITTEST(Nearest)\n{\n  GENERATE_SECTIONS(resample_full, resample_dynamic, NEAREST);\n}\n\nUNITTEST(Linear)\n{\n  GENERATE_SECTIONS(resample_full, resample_dynamic, LINEAR);\n}\n\nUNITTEST(Cubic)\n{\n  GENERATE_SECTIONS(resample_full, resample_dynamic, CUBIC);\n}\n"
  },
  {
    "path": "unit/configure.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <limits.h>\n#include <stdio.h>\n#include <limits>\n\n#include \"Unit.hpp\"\n\n#include \"../src/configure.h\"\n#include \"../src/const.h\"\n#include \"../src/event.h\"\n#include \"../src/keysym.h\"\n#include \"../src/util.h\"\n#include \"../src/io/vio.h\"\n\n#ifdef CONFIG_SDL\n#include \"../src/SDLmzx.h\"\n#undef IGNORE /* Windows defines this for some reason... */\n#endif\n\n#ifdef CONFIG_EDITOR\n#include \"../src/editor/configure.h\"\n#include \"../src/editor/robo_ed.h\"\n#endif\n\nstatic const int IGNORE = INT_MAX;\nstatic boolean game_allowed;\n\nstruct config_test_single\n{\n  const char * const value;\n  int expected;\n};\n\nstruct config_test_pair\n{\n  const char * const value;\n  int expected_a;\n  int expected_b;\n};\n\nstruct config_test_string\n{\n  const char * const value;\n  const char * const expected;\n};\n\n#ifdef CONFIG_EDITOR\nstruct config_test_board_size_viewport\n{\n  const char * const setting;\n  const char * const value;\n  int width;\n  int height;\n  int viewport_x;\n  int viewport_y;\n  int viewport_w;\n  int viewport_h;\n};\n\nstruct config_test_saved_position\n{\n  int which;\n  const char * const value;\n  struct saved_position expected;\n};\n#endif /* CONFIG_EDITOR */\n\n/**\n * For integer types, use min (if signed) or max (otherwise).\n * Enum types need to be specialized since numeric_limits doesn't work on them.\n */\ntemplate<class T>\nstatic constexpr T INVALID()\n{\n  static_assert(std::numeric_limits<T>::max() > 0, \"INVALID<> needs specialization.\");\n  return std::numeric_limits<T>::is_signed ?\n   std::numeric_limits<T>::min() : std::numeric_limits<T>::max();\n}\n\ntemplate<>\nconstexpr ratio_type INVALID<ratio_type>()\n{\n  return NUM_RATIO_TYPES;\n}\n\ntemplate<>\nconstexpr gl_filter_type INVALID<gl_filter_type>()\n{\n  return NUM_GL_FILTER_TYPES;\n}\n\ntemplate<>\nconstexpr system_mouse_type INVALID<system_mouse_type>()\n{\n  return NUM_SYSTEM_MOUSE_TYPES;\n}\n\ntemplate<>\nconstexpr cursor_mode_types INVALID<cursor_mode_types>()\n{\n  return NUM_CURSOR_MODE_TYPES;\n}\n\ntemplate<>\nconstexpr resample_mode INVALID<resample_mode>()\n{\n  return NUM_RESAMPLE_MODES;\n}\n\ntemplate<>\nconstexpr screensaver_disable_mode INVALID<screensaver_disable_mode>()\n{\n  return NUM_SCREENSAVER_MODES;\n}\n\ntemplate<>\nconstexpr allow_cheats_type INVALID<allow_cheats_type>()\n{\n  return NUM_ALLOW_CHEATS_TYPES;\n}\n\n#ifdef CONFIG_NETWORK\ntemplate<>\nconstexpr host_family INVALID<host_family>()\n{\n  return NUM_HOST_FAMILIES;\n}\n#endif\n\nstatic void load_arg(char *arg)\n{\n  char *tmp_args[2];\n  int argc = 0;\n\n  tmp_args[argc++] = nullptr;\n  tmp_args[argc++] = arg;\n\n  set_config_from_command_line(&argc, tmp_args);\n}\n\nstatic boolean write_config(const char *path, const char *config)\n{\n  // This vastly improves the performance in builds that support it.\n  vio_virtual_file(path);\n\n  vfile *vf = vfopen_unsafe(path, \"wb\");\n  ASSERT(vf, \"failed to open '%s'\", path);\n  if(vf)\n  {\n    size_t ret = vfwrite(config, strlen(config), 1, vf);\n    ASSERT(ret == 1, \"write error for '%s'\", path);\n    ret = vfclose(vf);\n    return true;\n  }\n  return false;\n}\n\nstatic void load_arg_file(const char *arg, boolean is_game_config)\n{\n  static const char CONFIG_FILENAME[] = \"_config_tmp\";\n\n  if(write_config(CONFIG_FILENAME, arg))\n  {\n    config_type type = is_game_config ? GAME_EDITOR_CNF : SYSTEM_CNF;\n    set_config_from_file(type, CONFIG_FILENAME);\n  }\n}\n\ntemplate<int SIZE>\nstatic void load_args(const char * const (&args)[SIZE])\n{\n  char *tmp_args[SIZE + 1];\n  int argc = 0;\n  int i;\n\n  tmp_args[argc++] = nullptr;\n\n  for(i = 0; i < SIZE; i++)\n  {\n    if(!args[i])\n      break;\n\n    tmp_args[argc++] = (char *)args[i];\n  }\n\n  set_config_from_command_line(&argc, tmp_args);\n}\n\ntemplate<class T>\nvoid TEST_INT(const char *setting_name, T &setting, ssize_t min, ssize_t max)\n{\n  constexpr T default_value = INVALID<T>();\n  char arg[512];\n  int lower_bound = (min >= -SSIZE_MAX / 2) ? -16 : 0;\n  int upper_bound = (max < SSIZE_MAX / 2) ? 32 : 16;\n\n  for(int i = lower_bound; i < upper_bound; i++)\n  {\n    ssize_t tmp;\n    if(max >= SSIZE_MAX / 16)\n      tmp = ((int64_t)max - min) / 16 * i + min;\n    else\n      tmp = ((int64_t)max - min) * i / 16 + min;\n\n    ssize_t expected = (tmp >= min && tmp <= max) ? tmp : static_cast<ssize_t>(default_value);\n\n    if(tmp < (ssize_t)std::numeric_limits<T>::min() ||\n     tmp > (ssize_t)std::numeric_limits<T>::max())\n      continue;\n\n    snprintf(arg, arraysize(arg), \"%s=%zd\", setting_name, tmp);\n\n    setting = default_value;\n    load_arg(arg);\n    ASSERTEQ((ssize_t)setting, expected, \"%s\", arg);\n\n    setting = default_value;\n    load_arg_file(arg, game_allowed);\n    ASSERTEQ((ssize_t)setting, expected, \"%s\", arg);\n\n    if(!game_allowed)\n    {\n      setting = default_value;\n      load_arg_file(arg, true);\n      ASSERTEQ(setting, default_value, \"%s\", arg);\n    }\n  }\n}\n\ntemplate<class T, int NUM_TESTS>\nvoid TEST_ENUM(const char *setting_name, T &setting,\n const config_test_single (&data)[NUM_TESTS])\n{\n  constexpr T default_value = INVALID<T>();\n  char arg[512];\n\n  for(int i = 0; i < NUM_TESTS; i++)\n  {\n    snprintf(arg, arraysize(arg), \"%s=%s\", setting_name, data[i].value);\n\n    setting = default_value;\n    load_arg(arg);\n    ASSERTEQ((int)setting, data[i].expected, \"%s\", arg);\n\n    setting = default_value;\n    load_arg_file(arg, game_allowed);\n    ASSERTEQ((int)setting, data[i].expected, \"%s\", arg);\n\n    if(!game_allowed)\n    {\n      setting = default_value;\n      load_arg_file(arg, true);\n      ASSERTEQ(setting, default_value, \"%s\", arg);\n    }\n  }\n}\n\ntemplate<class T, int NUM_TESTS>\nvoid TEST_PAIR(const char *setting_name, T &setting_a, T &setting_b,\n const config_test_pair (&data)[NUM_TESTS])\n{\n  constexpr T default_value = INVALID<T>();\n  char arg[512];\n\n  for(int i = 0; i < NUM_TESTS; i++)\n  {\n    snprintf(arg, 512, \"%s=%s\", setting_name, data[i].value);\n\n    setting_a = default_value;\n    setting_b = default_value;\n    load_arg(arg);\n    ASSERTEQ((int)setting_a, data[i].expected_a, \"%s\", arg);\n    ASSERTEQ((int)setting_b, data[i].expected_b, \"%s\", arg);\n\n    setting_a = default_value;\n    setting_b = default_value;\n    load_arg_file(arg, game_allowed);\n    ASSERTEQ((int)setting_a, data[i].expected_a, \"%s\", arg);\n    ASSERTEQ((int)setting_b, data[i].expected_b, \"%s\", arg);\n\n    if(!game_allowed)\n    {\n      setting_a = default_value;\n      setting_b = default_value;\n      load_arg_file(arg, true);\n      ASSERTEQ(setting_a, default_value, \"%s\", arg);\n      ASSERTEQ(setting_b, default_value, \"%s\", arg);\n    }\n  }\n}\n\ntemplate<size_t S, int NUM_TESTS>\nvoid TEST_STRING(const char *setting_name, char (&setting)[S],\n const config_test_string (&data)[NUM_TESTS])\n{\n  char arg[512];\n\n  for(int i = 0; i < NUM_TESTS; i++)\n  {\n    size_t len = Unit::min(strlen(data[i].expected), S - 1);\n    snprintf(arg, arraysize(arg), \"%s=%s\", setting_name, data[i].value);\n\n    setting[0] = '\\0';\n    load_arg(arg);\n    ASSERTNCMP(setting, data[i].expected, len, \"%s\", arg);\n    ASSERTEQ(setting[len], '\\0', \"%s\", arg);\n\n    setting[0] = '\\0';\n    load_arg_file(arg, game_allowed);\n    ASSERTNCMP(setting, data[i].expected, len, \"%s\", arg);\n    ASSERTEQ(setting[len], '\\0', \"%s\", arg);\n\n    if(!game_allowed)\n    {\n      setting[0] = '\\0';\n      load_arg_file(arg, true);\n      ASSERTEQ(setting[0], '\\0', \"%s\", arg);\n    }\n  }\n}\n\nUNITTEST(Settings)\n{\n  struct config_info *conf = get_config();\n\n  vio_filesystem_init(0, 0, false);\n  struct vfsclose\n  {\n    ~vfsclose() { vio_filesystem_exit(); }\n  } a;\n\n  static const config_test_single boolean_data[] =\n  {\n    { \"0\",    false },\n    { \"1\",    true },\n    { \"2\",    INVALID<boolean>() },\n    { \"23\",   INVALID<boolean>() },\n    { \"-12\",  INVALID<boolean>() },\n    { \"-1\",   INVALID<boolean>() },\n    { \"yes\",  INVALID<boolean>() },\n    { \"ABCD\", INVALID<boolean>() },\n    { \"0\\\\s\", INVALID<boolean>() },\n  };\n\n  static const config_test_pair pair_data[] =\n  {\n    { \"-1,-1\", -1, -1 },\n    { \"-1,0\", -1, 0 },\n    { \"0,-1\", 0, -1 },\n    { \"640,350\", 640, 350 },\n    { \"640,480\", 640, 480 },\n    { \"1280,700\", 1280, 700 },\n    { \"1920,1080\", 1920, 1080 },\n    { \"2560,1440\", 2560, 1440 },\n    { \"3840,2160\", 3840, 2160 },\n    { \"640, 350\", 640, 350 },\n    { \"1600,  1200\", 1600, 1200 },\n    { \"640,a\",        INVALID<int>(), INVALID<int>() },\n    { \"a,480\",        INVALID<int>(), INVALID<int>() },\n    { \"a,b\",          INVALID<int>(), INVALID<int>() },\n    { \"640px,350px\",  INVALID<int>(), INVALID<int>() },\n    { \"-2,4154\",      INVALID<int>(), INVALID<int>() },\n    { \"1233,-13513\",  INVALID<int>(), INVALID<int>() },\n  };\n\n  static const char LONGSTRING[] =\n    \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n    \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n    \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n    \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n    \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\";\n\n  static const config_test_string string_data[] =\n  {\n    { \"\", \"\" },\n    { \"software\", \"software\" },\n    { \"glsl\", \"glsl\" },\n    { \"crt\", \"crt\" },\n    { \"greyscale\", \"greyscale\" },\n    { \"really_long_setting_string_wtf_don't_do_this\",\n      \"really_long_setting_string_wtf_don't_do_this\" },\n    { LONGSTRING, LONGSTRING },\n    // NOTE: Both the command line and the config file translate \\s to a single\n    // space. Whitespace can't really be tested the same between the two as it\n    // isn't consumed from the command line but is unconditionally consumed\n    // from the config file. This leads to situations where \"test thing = a\"\n    // tries to set the option \"testthing\" from the file but \"test thing \" from\n    // the command line. Both of these are wrong but not really worth fixing.\n    { \"space\\\\sspace\", \"space space\" },\n    { \"copy\\\\soverlay\\\\sblock\\\\sat\\\\s0\\\\s0\\\\sfor\\\\s1\\\\sby\\\\s1\\\\sto\\\\s10\\\\s10\",\n      \"copy overlay block at 0 0 for 1 by 1 to 10 10\" },\n    { \"triple\\\\s\\\\s\\\\sspace\", \"triple   space\" },\n  };\n\n  game_allowed = false;\n  default_config();\n\n  // Video options.\n\n  SECTION(fullscreen)\n  {\n    TEST_ENUM(\"fullscreen\", conf->fullscreen, boolean_data);\n  }\n\n  SECTION(fullscreen_windowed)\n  {\n    TEST_ENUM(\"fullscreen_windowed\", conf->fullscreen_windowed, boolean_data);\n  }\n\n  SECTION(fullscreen_resolution)\n  {\n    TEST_PAIR(\"fullscreen_resolution\", conf->resolution_width,\n     conf->resolution_height, pair_data);\n  }\n\n  SECTION(window_resolution)\n  {\n    TEST_PAIR(\"window_resolution\", conf->window_width,\n     conf->window_height, pair_data);\n  }\n\n  SECTION(allow_resize)\n  {\n    TEST_ENUM(\"enable_resizing\", conf->allow_resize, boolean_data);\n  }\n\n  SECTION(video_output)\n  {\n    TEST_STRING(\"video_output\", conf->video_output, string_data);\n  }\n\n  SECTION(force_bpp)\n  {\n    constexpr int DEFAULT = INVALID<int>();\n    static const config_test_single data[] =\n    {\n      { \"0\", BPP_AUTO },\n      { \"8\", 8 },\n      { \"16\", 16 },\n      { \"32\", 32 },\n      { \"auto\", BPP_AUTO },\n      { \"-1\", DEFAULT },\n      { \"231\", DEFAULT },\n      { \"asdfsdf\", DEFAULT },\n      { \"autou\", DEFAULT },\n    };\n    TEST_ENUM(\"force_bpp\", conf->force_bpp, data);\n  }\n\n  SECTION(video_ratio)\n  {\n    constexpr ratio_type DEFAULT = INVALID<ratio_type>();\n    static const config_test_single data[] =\n    {\n      { \"classic\", RATIO_CLASSIC_4_3 },\n      { \"modern\", RATIO_MODERN_64_35 },\n      { \"stretch\", RATIO_STRETCH },\n      { \"-1\", DEFAULT },\n      { \"12312342\", DEFAULT },\n      { \"classiccc\", DEFAULT },\n    };\n    TEST_ENUM(\"video_ratio\", conf->video_ratio, data);\n  }\n\n  SECTION(gl_filter_method)\n  {\n    constexpr gl_filter_type DEFAULT = INVALID<gl_filter_type>();\n    static const config_test_single data[] =\n    {\n      { \"nearest\", CONFIG_GL_FILTER_NEAREST },\n      { \"linear\", CONFIG_GL_FILTER_LINEAR },\n      { \"213123\", DEFAULT },\n      { \"nearets\", DEFAULT },\n    };\n    TEST_ENUM(\"gl_filter_method\", conf->gl_filter_method, data);\n  }\n\n  SECTION(gl_vsync)\n  {\n    constexpr int DEFAULT = INVALID<int>();\n    static const config_test_single data[] =\n    {\n      { \"-1\", -1 },\n      { \"0\", 0 },\n      { \"1\", 1 },\n      { \"default\", -1 },\n      { \"-2\", DEFAULT },\n      { \"-12312\", DEFAULT },\n      { \"2\", DEFAULT },\n      { \"6456\", DEFAULT },\n      { \"1a\", DEFAULT },\n    };\n    TEST_ENUM(\"gl_vsync\", conf->gl_vsync, data);\n  }\n\n  SECTION(gl_scaling_shader)\n  {\n    game_allowed = true;\n    TEST_STRING(\"gl_scaling_shader\", conf->gl_scaling_shader, string_data);\n  }\n\n  SECTION(sdl_render_driver)\n  {\n    TEST_STRING(\"sdl_render_driver\", conf->sdl_render_driver, string_data);\n  }\n\n  SECTION(cursor_hint_mode)\n  {\n    constexpr cursor_mode_types DEFAULT = INVALID<cursor_mode_types>();\n    static const config_test_single data[] =\n    {\n      { \"0\", CURSOR_MODE_INVISIBLE },\n      { \"1\", CURSOR_MODE_HINT },\n      { \"off\", CURSOR_MODE_INVISIBLE },\n      { \"hidden\", CURSOR_MODE_HINT },\n      { \"underline\", CURSOR_MODE_UNDERLINE },\n      { \"solid\",  CURSOR_MODE_SOLID },\n      { \"-1\", DEFAULT },\n      { \"-24124124\", DEFAULT },\n      { \"2\", DEFAULT },\n      { \"938019831\", DEFAULT },\n      { \"1a\", DEFAULT },\n      { \"umderl1ne\", DEFAULT },\n    };\n    TEST_ENUM(\"dialog_cursor_hints\", conf->cursor_hint_mode, data);\n  }\n\n  SECTION(allow_screenshots)\n  {\n    TEST_ENUM(\"allow_screenshots\", conf->allow_screenshots, boolean_data);\n  }\n\n  // Audio options.\n\n  SECTION(audio_sample_rate)\n  {\n    TEST_INT(\"audio_sample_rate\", conf->audio_sample_rate, 1, INT_MAX);\n  }\n\n  SECTION(audio_buffer_samples)\n  {\n    TEST_INT(\"audio_buffer_samples\", conf->audio_buffer_samples, 1, INT_MAX);\n  }\n\n  SECTION(audio_output_channels)\n  {\n    TEST_INT(\"audio_output_channels\", conf->audio_output_channels, 1, 2);\n  }\n\n  SECTION(enable_oversampling)\n  {\n    TEST_ENUM(\"enable_oversampling\", conf->oversampling_on, boolean_data);\n  }\n\n  SECTION(resample_mode)\n  {\n    constexpr resample_mode DEFAULT = INVALID<resample_mode>();\n    static const config_test_single data[] =\n    {\n      { \"none\", RESAMPLE_MODE_NONE },\n      { \"linear\", RESAMPLE_MODE_LINEAR },\n      { \"cubic\", RESAMPLE_MODE_CUBIC },\n      { \"fir\", DEFAULT },\n      { \"sdfsedfsd\", DEFAULT },\n      { \"0\", DEFAULT },\n      { \"\", DEFAULT },\n    };\n    TEST_ENUM(\"resample_mode\", conf->resample_mode, data);\n  }\n\n  SECTION(module_resample_mode)\n  {\n    constexpr resample_mode DEFAULT = INVALID<resample_mode>();\n    static const config_test_single data[] =\n    {\n      { \"none\", RESAMPLE_MODE_NONE },\n      { \"linear\", RESAMPLE_MODE_LINEAR },\n      { \"cubic\", RESAMPLE_MODE_CUBIC },\n      { \"fir\", RESAMPLE_MODE_FIR },\n      { \"sdfsedfsd\", DEFAULT },\n      { \"0\", DEFAULT },\n      { \"\", DEFAULT },\n    };\n    TEST_ENUM(\"module_resample_mode\", conf->module_resample_mode, data);\n  }\n\n  SECTION(max_simultaneous_samples)\n  {\n    TEST_INT(\"max_simultaneous_samples\", conf->max_simultaneous_samples, -1, INT_MAX);\n  }\n\n  SECTION(music_volume)\n  {\n    TEST_INT(\"music_volume\", conf->music_volume, 0, 10);\n  }\n\n  SECTION(sam_volume)\n  {\n    TEST_INT(\"sample_volume\", conf->sam_volume, 0, 10);\n  }\n\n  SECTION(pc_speaker_volume)\n  {\n    TEST_INT(\"pc_speaker_volume\", conf->pc_speaker_volume, 0, 10);\n  }\n\n  SECTION(music_on)\n  {\n    TEST_ENUM(\"music_on\", conf->music_on, boolean_data);\n  }\n\n  SECTION(pc_speaker_on)\n  {\n    TEST_ENUM(\"pc_speaker_on\", conf->pc_speaker_on, boolean_data);\n  }\n\n  // Event options.\n\n#ifdef CONFIG_SDL\n#if SDL_VERSION_ATLEAST(2,0,0)\n  SECTION(gamecontroller_enable)\n  {\n    TEST_ENUM(\"gamecontroller_enable\", conf->allow_gamepad, boolean_data);\n  }\n\n  SECTION(gamepad_enable)\n  {\n    TEST_ENUM(\"gamepad_enable\", conf->allow_gamepad, boolean_data);\n  }\n#endif\n#endif\n\n  SECTION(pause_on_unfocus)\n  {\n    TEST_ENUM(\"pause_on_unfocus\", conf->pause_on_unfocus, boolean_data);\n  }\n\n  SECTION(key_left_alt_is_altgr)\n  {\n    TEST_ENUM(\"key_left_alt_is_altgr\", conf->key_left_alt_is_altgr, boolean_data);\n  }\n\n  SECTION(key_right_alt_is_altgr)\n  {\n    TEST_ENUM(\"key_right_alt_is_altgr\", conf->key_right_alt_is_altgr, boolean_data);\n  }\n\n  SECTION(num_buffered_events)\n  {\n    TEST_INT(\"num_buffered_events\", conf->num_buffered_events, 1, 256);\n  }\n\n  // Virtual filesystem options.\n  SECTION(vfs_enable)\n  {\n    TEST_ENUM(\"vfs_enable\", conf->vfs_enable, boolean_data);\n  }\n\n  SECTION(vfs_enable_auto_cache)\n  {\n    TEST_ENUM(\"vfs_enable_auto_cache\", conf->vfs_enable_auto_cache, boolean_data);\n  }\n\n  SECTION(vfs_max_cache_size)\n  {\n    TEST_INT(\"vfs_max_cache_size\", conf->vfs_max_cache_size, 0, SSIZE_MAX);\n  }\n\n  SECTION(vfs_max_cache_file_size)\n  {\n    TEST_INT(\"vfs_max_cache_file_size\", conf->vfs_max_cache_file_size, 0, SSIZE_MAX);\n  }\n\n  // Game options.\n\n  // TODO startup_path relies on stat.\n  // TODO startup_file relies on stat.\n\n  SECTION(save_file)\n  {\n    TEST_STRING(\"save_file\", conf->default_save_name, string_data);\n  }\n\n  SECTION(mzx_speed)\n  {\n    game_allowed = true;\n    TEST_INT(\"mzx_speed\", conf->mzx_speed, 1, 16);\n  }\n\n  SECTION(allow_cheats)\n  {\n    constexpr allow_cheats_type DEFAULT = INVALID<allow_cheats_type>();\n    static const config_test_single data[] =\n    {\n      { \"0\", ALLOW_CHEATS_NEVER },\n      { \"1\", ALLOW_CHEATS_ALWAYS },\n      { \"never\", ALLOW_CHEATS_NEVER },\n      { \"always\", ALLOW_CHEATS_ALWAYS },\n      { \"mzxrun\", ALLOW_CHEATS_MZXRUN },\n      { \"asdbfnsd\", DEFAULT },\n      { \"\", DEFAULT },\n    };\n    TEST_ENUM(\"allow_cheats\", conf->allow_cheats, data);\n  }\n\n  SECTION(auto_decrypt_worlds)\n  {\n    TEST_ENUM(\"auto_decrypt_worlds\", conf->auto_decrypt_worlds, boolean_data);\n  }\n\n  SECTION(startup_editor)\n  {\n    TEST_ENUM(\"startup_editor\", conf->startup_editor, boolean_data);\n  }\n\n  SECTION(standalone_mode)\n  {\n    TEST_ENUM(\"standalone_mode\", conf->standalone_mode, boolean_data);\n  }\n\n  SECTION(no_titlescreen)\n  {\n    TEST_ENUM(\"no_titlescreen\", conf->no_titlescreen, boolean_data);\n  }\n\n  SECTION(system_mouse)\n  {\n    constexpr system_mouse_type DEFAULT = INVALID<system_mouse_type>();\n    static const config_test_single data[] =\n    {\n      { \"0\", SYSTEM_MOUSE_OFF },\n      { \"1\", SYSTEM_MOUSE_ON },\n      { \"off\", SYSTEM_MOUSE_OFF },\n      { \"on\", SYSTEM_MOUSE_ON },\n      { \"only\", SYSTEM_MOUSE_HIDE_SOFTWARE_MOUSE },\n      { \"mzxrun\", DEFAULT },\n      { \"ksdjfksdf\", DEFAULT },\n      { \"2\", DEFAULT },\n      { \"\", DEFAULT },\n    };\n    TEST_ENUM(\"system_mouse\", conf->system_mouse, data);\n  }\n\n  SECTION(grab_mouse)\n  {\n    TEST_ENUM(\"grab_mouse\", conf->grab_mouse, boolean_data);\n  }\n\n  SECTION(disable_screensaver)\n  {\n    constexpr screensaver_disable_mode DEFAULT = INVALID<screensaver_disable_mode>();\n    static const config_test_single data[] =\n    {\n      { \"0\", SCREENSAVER_ENABLE },\n      { \"1\", SCREENSAVER_DISABLE },\n      { \"ingame\", DEFAULT },\n      { \"fgdjiogjf\", DEFAULT },\n      { \"\", DEFAULT },\n    };\n    TEST_ENUM(\"disable_screensaver\", conf->disable_screensaver, data);\n  }\n\n  SECTION(save_slots)\n  {\n    TEST_ENUM(\"save_slots\", conf->save_slots, boolean_data);\n  }\n\n  SECTION(save_slots_name)\n  {\n    TEST_STRING(\"save_slots_name\", conf->save_slots_name, string_data);\n  }\n\n  SECTION(save_slots_ext)\n  {\n    TEST_STRING(\"save_slots_ext\", conf->save_slots_ext, string_data);\n  }\n\n  // Editor options used by core.\n\n  SECTION(test_mode)\n  {\n    TEST_ENUM(\"test_mode\", conf->test_mode, boolean_data);\n  }\n\n  SECTION(test_mode_start_board)\n  {\n    TEST_INT(\"test_mode_start_board\", conf->test_mode_start_board, 0, MAX_BOARDS - 1);\n  }\n\n  SECTION(mask_midchars)\n  {\n    TEST_ENUM(\"mask_midchars\", conf->mask_midchars, boolean_data);\n  }\n\n#ifdef CONFIG_NETWORK\n\n  // Network options.\n\n  SECTION(network_enabled)\n  {\n    TEST_ENUM(\"network_enabled\", conf->network_enabled, boolean_data);\n  }\n\n  SECTION(network_address_family)\n  {\n    constexpr host_family DEFAULT = INVALID<host_family>();\n    static const config_test_single data[] =\n    {\n      { \"0\", HOST_FAMILY_ANY },\n      { \"any\", HOST_FAMILY_ANY },\n      { \"ipv4\", HOST_FAMILY_IPV4 },\n      { \"ipv6\", HOST_FAMILY_IPV6 },\n      { \"\", DEFAULT },\n      { \"1\", DEFAULT },\n      { \"sdfasdf\", DEFAULT },\n      { \"-1213\", DEFAULT }\n    };\n    TEST_ENUM(\"network_address_family\", conf->network_address_family, data);\n  }\n\n  SECTION(socks_host)\n  {\n    TEST_STRING(\"socks_host\", conf->socks_host, string_data);\n  }\n\n  SECTION(socks_port)\n  {\n    TEST_INT(\"socks_port\", conf->socks_port, 0, 65535);\n  }\n\n  SECTION(socks_username)\n  {\n    TEST_STRING(\"socks_username\", conf->socks_username, string_data);\n  }\n\n  SECTION(socks_password)\n  {\n    TEST_STRING(\"socks_password\", conf->socks_password, string_data);\n  }\n\n#endif /* CONFIG_NETWORK */\n\n#ifdef CONFIG_UPDATER\n\n  // Updater options.\n\n  SECTION(update_host)\n  {\n    static const char * const option = \"update_host\";\n    static const char * const hosts[] =\n    {\n      \"updates.doot.rip\",\n      \"updates.digitalmzx.com\",\n      \"updates.megazuex.org\",\n      \"looooo,.l\",\n      \"a website\",\n      \"sdjfjklsdfsdsdfsd\",\n      \"\",\n      \"PADDING\"\n    };\n    char buffer[512];\n\n    for(int i = 0; i < arraysize(hosts); i++)\n    {\n      snprintf(buffer, sizeof(buffer), \"%s=%s\", option, hosts[i]);\n      load_arg(buffer);\n\n      ASSERTEQ(conf->update_host_count, i+1, \"\");\n      for(int j = 0; j <= i; j++)\n        ASSERTCMP(conf->update_hosts[j], hosts[j], \"\");\n    }\n    free_config();\n  }\n\n  SECTION(update_branch_pin)\n  {\n    TEST_STRING(\"update_branch_pin\", conf->update_branch_pin, string_data);\n  }\n\n  SECTION(update_auto_check)\n  {\n    constexpr int DEFAULT = INVALID<int>();\n    static const config_test_single data[] =\n    {\n      { \"0\", UPDATE_AUTO_CHECK_OFF },\n      { \"1\", UPDATE_AUTO_CHECK_ON },\n      { \"off\", UPDATE_AUTO_CHECK_OFF },\n      { \"on\", UPDATE_AUTO_CHECK_ON },\n      { \"silent\", UPDATE_AUTO_CHECK_SILENT },\n      { \"2\", DEFAULT },\n      { \"asdfasdfasdf\", DEFAULT },\n      { \"\", DEFAULT },\n    };\n    TEST_ENUM(\"update_auto_check\", conf->update_auto_check, data);\n  }\n\n  SECTION(updater_enabled)\n  {\n    TEST_ENUM(\"updater_enabled\", conf->updater_enabled, boolean_data);\n  }\n\n#endif /* CONFIG_UPDATER */\n\n#ifdef CONFIG_EDITOR\n\n  struct editor_config_info *econf = get_editor_config();\n  default_editor_config();\n  game_allowed = true;\n\n  // Board editor options.\n\n  SECTION(board_editor_hide_help)\n  {\n    TEST_ENUM(\"board_editor_hide_help\", econf->board_editor_hide_help, boolean_data);\n  }\n\n  SECTION(editor_space_toggles)\n  {\n    TEST_ENUM(\"editor_space_toggles\", econf->editor_space_toggles, boolean_data);\n  }\n\n  SECTION(editor_tab_focuses_view)\n  {\n    TEST_ENUM(\"editor_tab_focuses_view\", econf->editor_tab_focuses_view, boolean_data);\n  }\n\n  SECTION(editor_load_board_assets)\n  {\n    TEST_ENUM(\"editor_load_board_assets\", econf->editor_load_board_assets, boolean_data);\n  }\n\n  SECTION(editor_thing_menu_places)\n  {\n    TEST_ENUM(\"editor_thing_menu_places\", econf->editor_thing_menu_places, boolean_data);\n  }\n\n  SECTION(editor_show_thing_toggles)\n  {\n    TEST_ENUM(\"editor_show_thing_toggles\", econf->editor_show_thing_toggles, boolean_data);\n  }\n\n  SECTION(editor_show_thing_blink_speed)\n  {\n    TEST_INT(\"editor_show_thing_blink_speed\", econf->editor_show_thing_blink_speed, 0, INT_MAX);\n  }\n\n  // Board defaults.\n\n  SECTION(viewport_and_size)\n  {\n    static const config_test_board_size_viewport data[] =\n    {\n      // Setting      Value     w       h       vx      vy      vw      vh\n      { \"width\",      \"100\",    100,    IGNORE, IGNORE, IGNORE, IGNORE, IGNORE },\n      { \"height\",     \"100\",    100,    100,    IGNORE, IGNORE, IGNORE, IGNORE },\n      { \"viewport_w\", \"74\",     100,    100,    IGNORE, IGNORE, 74,     IGNORE },\n      { \"viewport_h\", \"21\",     100,    100,    IGNORE, IGNORE, 74,     21 },\n      { \"viewport_x\", \"3\",      100,    100,    3,      IGNORE, 74,     21 },\n      { \"viewport_y\", \"2\",      100,    100,    3,      2,      74,     21 },\n\n      // Ignore invalid values...\n      { \"width\",      \"0\",      100,    100,    3,      2,      74,     21 },\n      { \"width\",      \"32768\",  100,    100,    3,      2,      74,     21 },\n      { \"height\",     \"0\",      100,    100,    3,      2,      74,     21 },\n      { \"height\",     \"32768\",  100,    100,    3,      2,      74,     21 },\n      { \"viewport_x\", \"-1\",     100,    100,    3,      2,      74,     21 },\n      { \"viewport_x\", \"80\",     100,    100,    3,      2,      74,     21 },\n      { \"viewport_y\", \"-1\",     100,    100,    3,      2,      74,     21 },\n      { \"viewport_y\", \"25\",     100,    100,    3,      2,      74,     21 },\n      { \"viewport_w\", \"0\",      100,    100,    3,      2,      74,     21 },\n      { \"viewport_w\", \"81\",     100,    100,    3,      2,      74,     21 },\n      { \"viewport_h\", \"0\",      100,    100,    3,      2,      74,     21 },\n      { \"viewport_h\", \"26\",     100,    100,    3,      2,      74,     21 },\n\n      // Width * height is capped to 16M.\n      { \"width\",      \"32767\",  32767,  100,    IGNORE, IGNORE, IGNORE, IGNORE },\n      { \"height\",     \"32767\",  512,    32767,  IGNORE, IGNORE, IGNORE, IGNORE },\n      { \"width\",      \"4097\",   4097,   4095,   IGNORE, IGNORE, IGNORE, IGNORE },\n      { \"height\",     \"4097\",   4095,   4097,   IGNORE, IGNORE, IGNORE, IGNORE },\n\n      // Width and height cap the viewport dimensions.\n      { \"width\",      \"40\",     40,     IGNORE, IGNORE, IGNORE, 40,     IGNORE },\n      { \"height\",     \"15\",     IGNORE, 15,     IGNORE, IGNORE, IGNORE, 15 },\n\n      // Viewport size values over the board size and under the cap work (capped).\n      { \"width\",      \"60\",     60,     IGNORE, IGNORE, IGNORE, IGNORE, IGNORE },\n      { \"viewport_w\", \"75\",     60,     IGNORE, IGNORE, IGNORE, 60,     IGNORE },\n      { \"height\",     \"20\",     IGNORE, 20,     IGNORE, IGNORE, IGNORE, IGNORE },\n      { \"viewport_h\", \"25\",     IGNORE, 20,     IGNORE, IGNORE, IGNORE, 20 },\n\n      // Viewport size overrides a previously set viewport position.\n      { \"width\",      \"100\",    100,    IGNORE, IGNORE, IGNORE, IGNORE, IGNORE },\n      { \"height\",     \"100\",    100,    100,    IGNORE, IGNORE, IGNORE, IGNORE },\n      { \"viewport_x\", \"10\",     IGNORE, IGNORE, 10,     IGNORE, IGNORE, IGNORE },\n      { \"viewport_y\", \"5\",      IGNORE, IGNORE, 10,     5,      IGNORE, IGNORE },\n      { \"viewport_w\", \"80\",     IGNORE, IGNORE, 0,      IGNORE, 80,     IGNORE },\n      { \"viewport_h\", \"25\",     IGNORE, IGNORE, IGNORE, 0,      IGNORE, 25 },\n\n      // Viewport position respects previously set viewport size.\n      { \"viewport_w\", \"70\",     IGNORE, IGNORE, IGNORE, IGNORE, 70,     IGNORE },\n      { \"viewport_h\", \"20\",     IGNORE, IGNORE, IGNORE, IGNORE, 70,     20 },\n      { \"viewport_x\", \"5\",      IGNORE, IGNORE, 5,      IGNORE, 70,     IGNORE },\n      { \"viewport_x\", \"15\",     IGNORE, IGNORE, 10,     IGNORE, 70,     IGNORE },\n      { \"viewport_y\", \"2\",      IGNORE, IGNORE, IGNORE, 2,      IGNORE, 20 },\n      { \"viewport_y\", \"10\",     IGNORE, IGNORE, IGNORE, 5,      IGNORE, 20 },\n    };\n    char buffer[512];\n\n    for(int i = 0; i < arraysize(data); i++)\n    {\n      const config_test_board_size_viewport &current = data[i];\n      snprintf(buffer, sizeof(buffer), \"board_default_%s=%s\",\n       current.setting, current.value);\n\n      load_arg(buffer);\n\n      if(current.width != IGNORE)\n        ASSERTEQ(econf->board_width, current.width, \"%s\", buffer);\n      if(current.height != IGNORE)\n        ASSERTEQ(econf->board_height, current.height, \"%s\", buffer);\n      if(current.viewport_x != IGNORE)\n        ASSERTEQ(econf->viewport_x, current.viewport_x, \"%s\", buffer);\n      if(current.viewport_y != IGNORE)\n        ASSERTEQ(econf->viewport_y, current.viewport_y, \"%s\", buffer);\n      if(current.viewport_w != IGNORE)\n        ASSERTEQ(econf->viewport_w, current.viewport_w, \"%s\", buffer);\n      if(current.viewport_h != IGNORE)\n        ASSERTEQ(econf->viewport_h, current.viewport_h, \"%s\", buffer);\n    }\n  }\n\n  SECTION(can_shoot)\n  {\n    TEST_ENUM(\"board_default_can_shoot\", econf->can_shoot, boolean_data);\n  }\n\n  SECTION(can_bomb)\n  {\n    TEST_ENUM(\"board_default_can_bomb\", econf->can_bomb, boolean_data);\n  }\n\n  SECTION(fire_burns_spaces)\n  {\n    TEST_ENUM(\"board_default_fire_burns_spaces\", econf->fire_burns_spaces, boolean_data);\n  }\n\n  SECTION(fire_burns_fakes)\n  {\n    TEST_ENUM(\"board_default_fire_burns_fakes\", econf->fire_burns_fakes, boolean_data);\n  }\n\n  SECTION(fire_burns_trees)\n  {\n    TEST_ENUM(\"board_default_fire_burns_trees\", econf->fire_burns_trees, boolean_data);\n  }\n\n  SECTION(fire_burns_brown)\n  {\n    TEST_ENUM(\"board_default_fire_burns_brown\", econf->fire_burns_brown, boolean_data);\n  }\n\n  SECTION(fire_burns_forever)\n  {\n    TEST_ENUM(\"board_default_fire_burns_forever\", econf->fire_burns_forever, boolean_data);\n  }\n\n  SECTION(forest_to_floor)\n  {\n    TEST_ENUM(\"board_default_forest_to_floor\", econf->forest_to_floor, boolean_data);\n  }\n\n  SECTION(collect_bombs)\n  {\n    TEST_ENUM(\"board_default_collect_bombs\", econf->collect_bombs, boolean_data);\n  }\n\n  SECTION(dragons_can_randomly_move)\n  {\n    TEST_ENUM(\"board_default_dragons_can_randomly_move\",\n     econf->dragons_can_randomly_move, boolean_data);\n  }\n\n  SECTION(restart_if_hurt)\n  {\n    TEST_ENUM(\"board_default_restart_if_hurt\", econf->restart_if_hurt, boolean_data);\n  }\n\n  SECTION(reset_on_entry)\n  {\n    TEST_ENUM(\"board_default_reset_on_entry\", econf->reset_on_entry, boolean_data);\n  }\n\n  SECTION(reset_on_entry_same_board)\n  {\n    TEST_ENUM(\"board_default_reset_on_entry_same_board\",\n     econf->reset_on_entry_same_board, boolean_data);\n  }\n\n  SECTION(player_locked_ns)\n  {\n    TEST_ENUM(\"board_default_player_locked_ns\", econf->player_locked_ns, boolean_data);\n  }\n\n  SECTION(player_locked_ew)\n  {\n    TEST_ENUM(\"board_default_player_locked_ew\", econf->player_locked_ew, boolean_data);\n  }\n\n  SECTION(player_locked_att)\n  {\n    TEST_ENUM(\"board_default_player_locked_att\", econf->player_locked_att, boolean_data);\n  }\n\n  SECTION(time_limit)\n  {\n    TEST_INT(\"board_default_time_limit\", econf->time_limit, 0, 32767);\n  }\n\n  SECTION(explosions_leave)\n  {\n    constexpr int DEFAULT = INVALID<int>();\n    static const config_test_single data[] =\n    {\n      { \"space\", EXPL_LEAVE_SPACE },\n      { \"ash\", EXPL_LEAVE_ASH },\n      { \"fire\", EXPL_LEAVE_FIRE },\n      { \"0\", DEFAULT },\n      { \"1\", DEFAULT },\n      { \"2\", DEFAULT },\n      { \"1a\", DEFAULT },\n      { \"\", DEFAULT },\n    };\n    TEST_ENUM(\"board_default_explosions_leave\", econf->explosions_leave, data);\n  }\n\n  SECTION(saving_enabled)\n  {\n    constexpr int DEFAULT = INVALID<int>();\n    static const config_test_single data[] =\n    {\n      { \"disabled\", CANT_SAVE },\n      { \"enabled\", CAN_SAVE },\n      { \"sensoronly\", CAN_SAVE_ON_SENSOR },\n      { \"0\", DEFAULT },\n      { \"1\", DEFAULT },\n      { \"2\", DEFAULT },\n      { \"sdfbv\", DEFAULT },\n      { \"\", DEFAULT },\n    };\n    TEST_ENUM(\"board_default_saving\", econf->saving_enabled, data);\n  }\n\n  SECTION(overlay_enabled)\n  {\n    constexpr int DEFAULT = INVALID<int>();\n    static const config_test_single data[] =\n    {\n      { \"disabled\", OVERLAY_OFF },\n      { \"enabled\", OVERLAY_ON },\n      { \"static\", OVERLAY_STATIC },\n      { \"transparent\", OVERLAY_TRANSPARENT },\n      { \"-1\", DEFAULT },\n      { \"1\", DEFAULT },\n      { \"2\", DEFAULT },\n      { \"disabledb\", DEFAULT },\n      { \"\", DEFAULT },\n    };\n    TEST_ENUM(\"board_default_overlay\", econf->overlay_enabled, data);\n  }\n\n  SECTION(charset_path)\n  {\n    TEST_STRING(\"board_default_charset_path\", econf->charset_path, string_data);\n  }\n\n  SECTION(palette_path)\n  {\n    TEST_STRING(\"board_default_palette_path\", econf->palette_path, string_data);\n  }\n\n  // Palette editor options.\n\n  SECTION(palette_editor_hide_help)\n  {\n    TEST_ENUM(\"palette_editor_hide_help\", econf->palette_editor_hide_help, boolean_data);\n  }\n\n  // Robot editor options.\n\n  SECTION(editor_enter_splits)\n  {\n    TEST_ENUM(\"editor_enter_splits\", econf->editor_enter_splits, boolean_data);\n  }\n\n  SECTION(color_codes)\n  {\n    static const struct\n    {\n      const char * const opt;\n      unsigned int idx;\n    } color_code_opts[] =\n    {\n      { \"ccode_chars\", ROBO_ED_CC_CHARACTERS },\n      { \"ccode_colors\", ROBO_ED_CC_COLORS },\n      { \"ccode_commands\", ROBO_ED_CC_COMMANDS },\n      { \"ccode_conditions\", ROBO_ED_CC_CONDITIONS },\n      { \"ccode_current_line\", ROBO_ED_CC_CURRENT_LINE },\n      { \"ccode_directions\", ROBO_ED_CC_DIRECTIONS },\n      { \"ccode_equalities\", ROBO_ED_CC_EQUALITIES },\n      { \"ccode_extras\", ROBO_ED_CC_EXTRAS },\n      { \"ccode_immediates\", ROBO_ED_CC_IMMEDIATES },\n      { \"ccode_items\", ROBO_ED_CC_ITEMS },\n      { \"ccode_params\", ROBO_ED_CC_PARAMS },\n      { \"ccode_strings\", ROBO_ED_CC_STRINGS },\n      { \"ccode_things\", ROBO_ED_CC_THINGS },\n    };\n    static const config_test_single color_data[] =\n    {\n      { \"255\", 255 },\n    };\n\n    for(int i = 0; i < arraysize(color_code_opts); i++)\n    {\n      auto opt = color_code_opts[i];\n      TEST_INT(opt.opt, econf->color_codes[opt.idx], 0, 15);\n    }\n\n    // Test the colors special case...\n    TEST_ENUM(\"ccode_colors\", econf->color_codes[ROBO_ED_CC_COLORS], color_data);\n  }\n\n  SECTION(color_coding_on)\n  {\n    TEST_ENUM(\"color_coding_on\", econf->color_coding_on, boolean_data);\n  }\n\n#ifndef CONFIG_DEBYTECODE\n  SECTION(default_invalid_status)\n  {\n    constexpr int DEFAULT = INVALID<int>();\n    static const config_test_single data[] =\n    {\n      { \"ignore\", invalid_uncertain },\n      { \"delete\", invalid_discard },\n      { \"comment\", invalid_comment },\n      { \"-1\", DEFAULT },\n      { \"1\", DEFAULT },\n      { \"2\", DEFAULT },\n      { \"disabledb\", DEFAULT },\n      { \"\", DEFAULT },\n    };\n    TEST_ENUM(\"default_invalid_status\", econf->default_invalid_status, data);\n  }\n#endif\n\n  SECTION(disassemble_extras)\n  {\n    TEST_ENUM(\"disassemble_extras\", econf->disassemble_extras, boolean_data);\n  }\n\n  SECTION(disassemble_base)\n  {\n    constexpr int DEFAULT = INVALID<int>();\n    static const config_test_single data[] =\n    {\n      { \"10\", 10 },\n      { \"16\", 16 },\n      { \"2\", DEFAULT },\n      { \"4\", DEFAULT },\n      { \"8\", DEFAULT },\n      { \"10bfd\", DEFAULT },\n      { \"11\", DEFAULT },\n      { \"\", DEFAULT },\n    };\n    TEST_ENUM(\"disassemble_base\", econf->disassemble_base, data);\n  }\n\n  SECTION(robot_editor_hide_help)\n  {\n    TEST_ENUM(\"robot_editor_hide_help\", econf->robot_editor_hide_help, boolean_data);\n  }\n\n  // Backup options.\n\n  SECTION(backup_count)\n  {\n    TEST_INT(\"backup_count\", econf->backup_count, 0, INT_MAX);\n  }\n\n  SECTION(backup_interval)\n  {\n    TEST_INT(\"backup_interval\", econf->backup_interval, 0, INT_MAX);\n  }\n\n  SECTION(backup_name)\n  {\n    TEST_STRING(\"backup_name\", econf->backup_name, string_data);\n  }\n\n  SECTION(backup_ext)\n  {\n    TEST_STRING(\"backup_ext\", econf->backup_ext, string_data);\n  }\n\n  // Macro options.\n\n  // TODO extended macros should be a separate test.\n\n  SECTION(default_macros)\n  {\n    char buffer[16];\n    for(int i = 0; i < arraysize(econf->default_macros); i++)\n    {\n      snprintf(buffer, sizeof(buffer), \"macro_%d\", i+1);\n      TEST_STRING(buffer, econf->default_macros[i], string_data);\n    }\n  }\n\n  // Saved positions.\n\n  SECTION(saved_position)\n  {\n    static const config_test_saved_position data[] =\n    {\n      { 0, \"1,2,3,4,5,0\",       { 1, 2, 3, 4, 5, 0 } },\n      { 0, \"\",                  { 1, 2, 3, 4, 5, 0 } },\n      { 1, \"9,8,7,6,5,60\",      { 9, 8, 7, 6, 5, 60 } },\n      { 2, \"249,32767,32767,32767,32767,60\", { 249, 32767, 32767, 32767, 32767, 60 } },\n      { 3, \"3,3,3,3,3,0\",       { 3, 3, 3, 3, 3, 0 } },\n      { 4, \"4,4,4,4,4,0\",       { 4, 4, 4, 4, 4, 0 } },\n      { 5, \"5,5,5,5,5,0\",       { 5, 5, 5, 5, 5, 0 } },\n      { 6, \"6,6,6,6,6,0\",       { 6, 6, 6, 6, 6, 0 } },\n      { 7, \"7,7,7,7,7,0\",       { 7, 7, 7, 7, 7, 0 } },\n      { 8, \"8,8,8,8,8,0\",       { 8, 8, 8, 8, 8, 0 } },\n      { 9, \"9,9,9,9,9,0\",       { 9, 9, 9, 9, 9, 0 } },\n      { 8, \"\",                  { 8, 8, 8, 8, 8, 0 } },\n      { 7, \"\",                  { 7, 7, 7, 7, 7, 0 } },\n      { 6, \"\",                  { 6, 6, 6, 6, 6, 0 } },\n      { 5, \"\",                  { 5, 5, 5, 5, 5, 0 } },\n      { 4, \"\",                  { 4, 4, 4, 4, 4, 0 } },\n      { 3, \"\",                  { 3, 3, 3, 3, 3, 0 } },\n      // Make sure this at least has rudimentary bounding for invalid things...\n      { 0, \"0,0,0,0,0,0\",       { 0, 0, 0, 0, 0, 0 } },\n      { 0, \"250,0,0,0,0,0\",     { 0, IGNORE, IGNORE, IGNORE, IGNORE, IGNORE } },\n      { 0, \"-1,0,0,0,0,0\",      { 0, IGNORE, IGNORE, IGNORE, IGNORE, IGNORE } },\n      { 0, \"9,32768,0,0,0,0,0\", { 0, 0,      IGNORE, IGNORE, IGNORE, IGNORE } },\n      { 0, \"9,-1,0,0,0,0\",      { 0, 0,      IGNORE, IGNORE, IGNORE, IGNORE } },\n      { 0, \"9,0,32768,0,0,0\",   { 0, IGNORE, 0,      IGNORE, IGNORE, IGNORE } },\n      { 0, \"9,0,-1,0,0,0\",      { 0, IGNORE, 0,      IGNORE, IGNORE, IGNORE } },\n      { 0, \"9,0,0,32768,0,0\",   { 0, IGNORE, IGNORE, 0,      IGNORE, IGNORE } },\n      { 0, \"9,0,0,-1,0,0\",      { 0, IGNORE, IGNORE, 0,      IGNORE, IGNORE } },\n      { 0, \"9,0,0,0,32768,0\",   { 0, IGNORE, IGNORE, IGNORE, 0,      IGNORE } },\n      { 0, \"9,0,0,0,-1,0\",      { 0, IGNORE, IGNORE, IGNORE, 0,      IGNORE } },\n      { 0, \"9,0,0,0,0,80\",      { 0, IGNORE, IGNORE, IGNORE, IGNORE, 0 } },\n      { 0, \"9,0,0,0,0,-1\",      { 0, IGNORE, IGNORE, IGNORE, IGNORE, 0 } },\n    };\n    const struct saved_position *dest;\n    const struct saved_position *expected;\n    char buffer[256];\n\n    for(int i = 0; i < arraysize(data); i++)\n    {\n      snprintf(buffer, sizeof(buffer), \"saved_position%d=%s\",\n       data[i].which, data[i].value);\n\n      load_arg(buffer);\n\n      dest = &(econf->saved_positions[data[i].which]);\n      expected = &(data[i].expected);\n\n      if(expected->board_id != IGNORE)\n        ASSERTEQ(dest->board_id, expected->board_id, \"%s\", buffer);\n      if(expected->cursor_x != IGNORE)\n        ASSERTEQ(dest->cursor_x, expected->cursor_x, \"%s\", buffer);\n      if(expected->cursor_y != IGNORE)\n        ASSERTEQ(dest->cursor_y, expected->cursor_y, \"%s\", buffer);\n      if(expected->scroll_x != IGNORE)\n        ASSERTEQ(dest->scroll_x, expected->scroll_x, \"%s\", buffer);\n      if(expected->scroll_y != IGNORE)\n        ASSERTEQ(dest->scroll_y, expected->scroll_y, \"%s\", buffer);\n      if(expected->debug_x != IGNORE)\n        ASSERTEQ(dest->debug_x, expected->debug_x, \"%s\", buffer);\n    }\n  }\n\n#endif /* CONFIG_EDITOR */\n}\n\n#define SP_AXIS     (1 << 13)\n#define SP_KEYBOARD (1 << 14)\n#define AXIS(n)     (SP_AXIS | (n))\n#define AXIS_NONE   (SP_AXIS | 255)\n\nstruct config_test_joystick\n{\n  const char * const setting;\n  const char * const value;\n  int first;\n  int last;\n  int which;\n  int expected[4];\n};\n\n// TODO only bothering to test these as strings right now, but they all should\n// work from game config files too.\n\ntemplate<int NUM_TESTS>\nvoid TEST_JOY_ALIAS(const config_test_single (&data)[NUM_TESTS])\n{\n  struct joystick_map *joy_global_map = get_joystick_map(true);\n  constexpr int16_t DEFAULT = INVALID<int16_t>();\n  char arg[512];\n\n  ASSERT(joy_global_map, \"\");\n  for(int i = 0; i < NUM_TESTS; i++)\n  {\n    joy_global_map->button[0][0] = DEFAULT;\n    snprintf(arg, arraysize(arg), \"joy1button1=%s\", data[i].value);\n    load_arg(arg);\n\n    ASSERTEQ(joy_global_map->button[0][0], data[i].expected, \"%s\", arg);\n  }\n}\n\ntemplate<int NUM_TESTS>\nvoid TEST_JOY_BUTTON(const config_test_joystick (&data)[NUM_TESTS])\n{\n  struct joystick_map *joy_global_map = get_joystick_map(true);\n  constexpr int16_t DEFAULT = INVALID<int16_t>();\n  char arg[512];\n\n  ASSERT(joy_global_map, \"\");\n  for(int i = 0; i < NUM_TESTS; i++)\n  {\n    const config_test_joystick &t = data[i];\n    if(t.which > MAX_JOYSTICK_BUTTONS)\n      continue;\n\n    for(int j = 0; j < MAX_JOYSTICKS; j++)\n      joy_global_map->button[j][t.which - 1] = DEFAULT;\n\n    snprintf(arg, arraysize(arg), \"%s=%s\", t.setting, t.value);\n    load_arg(arg);\n\n    for(int j = 0; j < MAX_JOYSTICKS; j++)\n    {\n      if(j >= t.first - 1 && j <= t.last - 1)\n        ASSERTEQ(joy_global_map->button[j][t.which - 1], t.expected[0], \"%s\", arg);\n      else\n        ASSERTEQ(joy_global_map->button[j][t.which - 1], DEFAULT, \"%s\", arg);\n    }\n  }\n}\n\ntemplate<int NUM_TESTS>\nvoid TEST_JOY_AXIS(const config_test_joystick (&data)[NUM_TESTS])\n{\n  struct joystick_map *joy_global_map = get_joystick_map(true);\n  constexpr int16_t DEFAULT = INVALID<int16_t>();\n  char arg[512];\n\n  ASSERT(joy_global_map, \"\");\n  for(int i = 0; i < NUM_TESTS; i++)\n  {\n    const config_test_joystick &t = data[i];\n    if(t.which > MAX_JOYSTICK_AXES)\n      continue;\n\n    for(int j = 0; j < MAX_JOYSTICKS; j++)\n    {\n      joy_global_map->axis[j][t.which - 1][0] = DEFAULT;\n      joy_global_map->axis[j][t.which - 1][1] = DEFAULT;\n    }\n\n    snprintf(arg, arraysize(arg), \"%s=%s\", t.setting, t.value);\n    load_arg(arg);\n\n    for(int j = 0; j < MAX_JOYSTICKS; j++)\n    {\n      if(j >= t.first - 1 && j <= t.last - 1)\n      {\n        ASSERTEQ(joy_global_map->axis[j][t.which - 1][0], t.expected[0], \"%s\", arg);\n        ASSERTEQ(joy_global_map->axis[j][t.which - 1][1], t.expected[1], \"%s\", arg);\n      }\n      else\n      {\n        ASSERTEQ(joy_global_map->axis[j][t.which - 1][0], DEFAULT, \"%s\", arg);\n        ASSERTEQ(joy_global_map->axis[j][t.which - 1][1], DEFAULT, \"%s\", arg);\n      }\n    }\n  }\n}\n\ntemplate<int NUM_TESTS>\nvoid TEST_JOY_HAT(const config_test_joystick (&data)[NUM_TESTS])\n{\n  struct joystick_map *joy_global_map = get_joystick_map(true);\n  constexpr int16_t DEFAULT = INVALID<int16_t>();\n  char arg[512];\n\n  ASSERT(joy_global_map, \"\");\n  for(int i = 0; i < NUM_TESTS; i++)\n  {\n    const config_test_joystick &t = data[i];\n\n    for(int j = 0; j < MAX_JOYSTICKS; j++)\n    {\n      joy_global_map->hat[j][JOYHAT_UP] = DEFAULT;\n      joy_global_map->hat[j][JOYHAT_DOWN] = DEFAULT;\n      joy_global_map->hat[j][JOYHAT_LEFT] = DEFAULT;\n      joy_global_map->hat[j][JOYHAT_RIGHT] = DEFAULT;\n    }\n\n    snprintf(arg, arraysize(arg), \"%s=%s\", t.setting, t.value);\n    load_arg(arg);\n\n    for(int j = 0; j < MAX_JOYSTICKS; j++)\n    {\n      if(j >= t.first - 1 && j <= t.last - 1)\n      {\n        ASSERTEQ(joy_global_map->hat[j][JOYHAT_UP], t.expected[JOYHAT_UP], \"%s\", arg);\n        ASSERTEQ(joy_global_map->hat[j][JOYHAT_DOWN], t.expected[JOYHAT_DOWN], \"%s\", arg);\n        ASSERTEQ(joy_global_map->hat[j][JOYHAT_LEFT], t.expected[JOYHAT_LEFT], \"%s\", arg);\n        ASSERTEQ(joy_global_map->hat[j][JOYHAT_RIGHT], t.expected[JOYHAT_RIGHT], \"%s\", arg);\n      }\n      else\n      {\n        ASSERTEQ(joy_global_map->hat[j][JOYHAT_UP], DEFAULT, \"%s\", arg);\n        ASSERTEQ(joy_global_map->hat[j][JOYHAT_DOWN], DEFAULT, \"%s\", arg);\n        ASSERTEQ(joy_global_map->hat[j][JOYHAT_LEFT], DEFAULT, \"%s\", arg);\n        ASSERTEQ(joy_global_map->hat[j][JOYHAT_RIGHT], DEFAULT, \"%s\", arg);\n      }\n    }\n  }\n}\n\nstatic void reset_mapping(struct joystick_map *map, int j, int which)\n{\n  if(which & SP_KEYBOARD)\n  {\n    map->show_screen_keyboard_action[j] = INVALID<int16_t>();\n  }\n  else\n\n  if(which & SP_AXIS)\n  {\n    memset(map->special_axis[j], 255, sizeof(map->special_axis[j]));\n  }\n  else\n    map->action[j][which] = INVALID<int16_t>();\n}\n\nstatic void check_mapping(const struct joystick_map *map, int j, int which,\n int expected, const char *arg)\n{\n  if(which & SP_KEYBOARD)\n  {\n    ASSERTEQ(map->show_screen_keyboard_action[j], expected, \"%s\", arg);\n  }\n  else\n\n  if(which & SP_AXIS)\n  {\n    which = (which & ~SP_AXIS) - 1;\n    for(int i = 0; i < arraysize(map->special_axis[j]); i++)\n    {\n      if(i == which && expected != INVALID<int16_t>())\n        ASSERTEQ(map->special_axis[j][i], expected, \"%s\", arg);\n      else\n        ASSERTEQ(map->special_axis[j][i], 255, \"%s\", arg);\n    }\n  }\n  else\n    ASSERTEQ(map->action[j][which], expected, \"%s\", arg);\n}\n\ntemplate<int NUM_TESTS>\nvoid TEST_JOY_ACTION(const config_test_joystick (&data)[NUM_TESTS])\n{\n  struct joystick_map *joy_global_map = get_joystick_map(true);\n  constexpr int16_t DEFAULT = INVALID<int16_t>();\n  char arg[512];\n\n  ASSERT(joy_global_map, \"\");\n  for(int i = 0; i < NUM_TESTS; i++)\n  {\n    const config_test_joystick &t = data[i];\n\n    // NOTE: unlike the other tests, don't subtract 1 from t.which here since\n    // it's an action number.\n    for(int j = 0; j < MAX_JOYSTICKS; j++)\n      reset_mapping(joy_global_map, j, t.which);\n\n    snprintf(arg, arraysize(arg), \"%s=%s\", t.setting, t.value);\n    load_arg(arg);\n\n    for(int j = 0; j < MAX_JOYSTICKS; j++)\n    {\n      if(j >= t.first - 1 && j <= t.last - 1)\n        check_mapping(joy_global_map, j, t.which, t.expected[0], arg);\n      else\n        check_mapping(joy_global_map, j, t.which, DEFAULT, arg);\n    }\n  }\n}\n\nUNITTEST(Joystick)\n{\n  constexpr int16_t DEFAULT = INVALID<int16_t>();\n\n  SECTION(joyN_button)\n  {\n    static const config_test_joystick data[] =\n    {\n      // Setting              Value           [#, #]  which   expected\n      { \"joy1button1\",        \"key_space\",    1,  1,  1,      { IKEY_SPACE }},\n      { \"joy[1,1]button2\",    \"key_insert\",   1,  1,  2,      { IKEY_INSERT }},\n      { \"joy16button256\",     \"key_escape\",   16, 16, 256,    { IKEY_ESCAPE, }},\n      { \"joy[1,16]button16\",  \"act_lstick\",   1,  16, 16,     { -JOY_LSTICK }},\n      { \"joy[1,16]button256\", \"act_a\",        1,  16, 256,    { -JOY_A }},\n      { \"joy[4,8]button10\",   \"act_start\",    4,  8,  10,     { -JOY_START }},\n      { \"joy[9,9]button123\",  \"27\",           9,  9,  123,    { IKEY_ESCAPE }},\n      { \"joy[10,11]button71\", \"13\",           10, 11, 71,     { IKEY_RETURN }},\n    };\n    TEST_JOY_BUTTON(data);\n  }\n\n  SECTION(joyN_axis)\n  {\n    static const config_test_joystick data[] =\n    {\n      // Setting              Value                   [#, #]  which   expected\n      { \"joy1axis1\",          \"key_left, key_right\",  1,  1,  1,      { IKEY_LEFT, IKEY_RIGHT }},\n      { \"joy[1,1]axis2\",      \"key_a,key_d\",          1,  1,  2,      { IKEY_a, IKEY_d }},\n      { \"joy16axis16\",        \"key_w,key_s\",          16, 16, 16,     { IKEY_w, IKEY_s }},\n      { \"joy[11,16]axis16\",   \"key_up, key_down\",     11, 16, 16,     { IKEY_UP, IKEY_DOWN }},\n      { \"joy[2,9]axis7\",      \"key_1,key_delete\",     2,  9,  7,      { IKEY_1, IKEY_DELETE }},\n      { \"joy[1,14]axis3\",     \"act_r_up,act_r_down\",  1,  14, 3,      { -JOY_R_UP, -JOY_R_DOWN }},\n      { \"joy[7,7]axis15\",     \"act_x,act_y\",          7,  7,  15,     { -JOY_X, -JOY_Y }},\n    };\n    TEST_JOY_AXIS(data);\n  }\n\n  SECTION(joyN_hat)\n  {\n    static const config_test_joystick data[] =\n    {\n      // Setting          Value                                 [#, #]  which\n      { \"joy1hat\",        \"key_up,key_down,key_left,key_right\", 1,  1,  0,\n        { IKEY_UP, IKEY_DOWN, IKEY_LEFT, IKEY_RIGHT }},\n      { \"joy[10,14]hat\",  \"key_w, key_s, key_a, key_d\",         10, 14, 0,\n        { IKEY_w, IKEY_s, IKEY_a, IKEY_d }},\n      { \"joy[1,16]hat\",   \"act_up,act_down,act_left,act_right\", 1,  16, 0,\n        { -JOY_UP, -JOY_DOWN, -JOY_LEFT, -JOY_RIGHT }},\n      { \"joy[7,12]hat\",   \"273,274,276,275\",                    7,  12, 0,\n        { IKEY_UP, IKEY_DOWN, IKEY_LEFT, IKEY_RIGHT }},\n    };\n    TEST_JOY_HAT(data);\n  }\n\n  SECTION(joyN_action)\n  {\n    static const config_test_joystick data[] =\n    {\n      // Make sure every action is assignable.\n      // Setting              Value           [#, #]  which           expected\n      { \"joy1.a\",             \"key_space\",    1,  1,  JOY_A,          { IKEY_SPACE }},\n      { \"joy1.b\",             \"key_space\",    1,  1,  JOY_B,          { IKEY_SPACE }},\n      { \"joy1.x\",             \"key_space\",    1,  1,  JOY_X,          { IKEY_SPACE }},\n      { \"joy1.y\",             \"key_space\",    1,  1,  JOY_Y,          { IKEY_SPACE }},\n      { \"joy1.select\",        \"key_space\",    1,  1,  JOY_SELECT,     { IKEY_SPACE }},\n      { \"joy1.start\",         \"key_space\",    1,  1,  JOY_START,      { IKEY_SPACE }},\n      { \"joy1.lstick\",        \"key_space\",    1,  1,  JOY_LSTICK,     { IKEY_SPACE }},\n      { \"joy1.rstick\",        \"key_space\",    1,  1,  JOY_RSTICK,     { IKEY_SPACE }},\n      { \"joy1.lshoulder\",     \"key_space\",    1,  1,  JOY_LSHOULDER,  { IKEY_SPACE }},\n      { \"joy1.rshoulder\",     \"key_space\",    1,  1,  JOY_RSHOULDER,  { IKEY_SPACE }},\n      { \"joy1.ltrigger\",      \"key_space\",    1,  1,  JOY_LTRIGGER,   { IKEY_SPACE }},\n      { \"joy1.rtrigger\",      \"key_space\",    1,  1,  JOY_RTRIGGER,   { IKEY_SPACE }},\n      { \"joy1.up\",            \"key_space\",    1,  1,  JOY_UP,         { IKEY_SPACE }},\n      { \"joy1.down\",          \"key_space\",    1,  1,  JOY_DOWN,       { IKEY_SPACE }},\n      { \"joy1.left\",          \"key_space\",    1,  1,  JOY_LEFT,       { IKEY_SPACE }},\n      { \"joy1.right\",         \"key_space\",    1,  1,  JOY_RIGHT,      { IKEY_SPACE }},\n      { \"joy1.l_up\",          \"key_space\",    1,  1,  JOY_L_UP,       { IKEY_SPACE }},\n      { \"joy1.l_down\",        \"key_space\",    1,  1,  JOY_L_DOWN,     { IKEY_SPACE }},\n      { \"joy1.l_left\",        \"key_space\",    1,  1,  JOY_L_LEFT,     { IKEY_SPACE }},\n      { \"joy1.l_right\",       \"key_space\",    1,  1,  JOY_L_RIGHT,    { IKEY_SPACE }},\n      { \"joy1.r_up\",          \"key_space\",    1,  1,  JOY_R_UP,       { IKEY_SPACE }},\n      { \"joy1.r_down\",        \"key_space\",    1,  1,  JOY_R_DOWN,     { IKEY_SPACE }},\n      { \"joy1.r_left\",        \"key_space\",    1,  1,  JOY_R_LEFT,     { IKEY_SPACE }},\n      { \"joy1.r_right\",       \"key_space\",    1,  1,  JOY_R_RIGHT,    { IKEY_SPACE }},\n\n      // Reject actions.\n      { \"joy1.ltrigger\",      \"0\",            1,  1,  JOY_LTRIGGER,   { DEFAULT }},\n      { \"joy1.select\",        \"act_up\",       1,  1,  JOY_SELECT,     { DEFAULT }},\n\n      // Ranges.\n      // Setting              Value           [#, #]  which           expected\n      { \"joy[1,16].select\",   \"key_escape\",   1,  16, JOY_SELECT,     { IKEY_ESCAPE }},\n      { \"joy[7,9].up\",        \"key_w\",        7,  9,  JOY_UP,         { IKEY_w }},\n      { \"joy[14,15].l_right\", \"key_7\",        14, 15, JOY_L_RIGHT,    { IKEY_7 }},\n      { \"joy[1,4].y\",         \"49\",           1,  4,  JOY_Y,          { IKEY_1 }},\n\n      // Special axes.\n      { \"joy1.axis_lx\",       \"3\",            1,  1,  AXIS(3),        { JOY_AXIS_LEFT_X }},\n      { \"joy1.axis_ly\",       \"1\",            1,  1,  AXIS(1),        { JOY_AXIS_LEFT_Y }},\n      { \"joy1.axis_rx\",       \"5\",            1,  1,  AXIS(5),        { JOY_AXIS_RIGHT_X }},\n      { \"joy1.axis_ry\",       \"10\",           1,  1,  AXIS(10),       { JOY_AXIS_RIGHT_Y }},\n      { \"joy1.axis_ltrigger\", \"16\",           1,  1,  AXIS(16),       { JOY_AXIS_LEFT_TRIGGER }},\n      { \"joy1.axis_rtrigger\", \"8\",            1,  1,  AXIS(8),        { JOY_AXIS_RIGHT_TRIGGER }},\n      { \"joy1.axis_lx\",       \"0\",            1,  1,  AXIS_NONE,      { JOY_AXIS_LEFT_X }},\n      { \"joy1.axis_lx\",       \"-1\",           1,  1,  AXIS_NONE,      { JOY_AXIS_LEFT_X }},\n      { \"joy1.axis_lx\",       \"17\",           1,  1,  AXIS_NONE,      { JOY_AXIS_LEFT_X }},\n      { \"joy1.axis_lx\",       \"act_x\",        1,  1,  AXIS_NONE,      { JOY_AXIS_LEFT_X }},\n      { \"joy[4,12].axis_lx\",  \"7\",            4, 12,  AXIS(7),        { JOY_AXIS_LEFT_X }},\n\n      // Special settings.\n      { \"joy1.show_screen_keyboard\", \"act_l_up\",   1, 1, SP_KEYBOARD, { JOY_L_UP }},\n      { \"joy1.show_screen_keyboard\", \"0\",          1, 1, SP_KEYBOARD, { JOY_NO_ACTION }},\n      { \"joy1.show_screen_keyboard\", \"key_space\",  1, 1, SP_KEYBOARD, { DEFAULT }},\n      { \"joy[2,9].show_screen_keyboard\", \"act_x\",  2, 9, SP_KEYBOARD, { JOY_X }},\n    };\n    TEST_JOY_ACTION(data);\n  }\n\n  SECTION(joystick_aliases)\n  {\n    /**\n     * That general case stuff worked?\n     * Great, now make sure every alias works! :(\n     *\n     * Uses joy1button1 internally. Action aliases translate to negative values.\n     */\n    static const config_test_single data[] =\n    {\n      // Keys (keep in the same order as the list in event.c).\n      { \"key_0\",            IKEY_0 },\n      { \"key_1\",            IKEY_1 },\n      { \"key_2\",            IKEY_2 },\n      { \"key_3\",            IKEY_3 },\n      { \"key_4\",            IKEY_4 },\n      { \"key_5\",            IKEY_5 },\n      { \"key_6\",            IKEY_6 },\n      { \"key_7\",            IKEY_7 },\n      { \"key_8\",            IKEY_8 },\n      { \"key_9\",            IKEY_9 },\n      { \"key_a\",            IKEY_a },\n      { \"key_b\",            IKEY_b },\n      { \"key_backquote\",    IKEY_BACKQUOTE },\n      { \"key_backslash\",    IKEY_BACKSLASH },\n      { \"key_backspace\",    IKEY_BACKSPACE },\n      { \"key_break\",        IKEY_BREAK },\n      { \"key_c\",            IKEY_c },\n      { \"key_capslock\",     IKEY_CAPSLOCK },\n      { \"key_comma\",        IKEY_COMMA },\n      { \"key_d\",            IKEY_d },\n      { \"key_delete\",       IKEY_DELETE },\n      { \"key_down\",         IKEY_DOWN },\n      { \"key_e\",            IKEY_e },\n      { \"key_end\",          IKEY_END },\n      { \"key_equals\",       IKEY_EQUALS },\n      { \"key_escape\",       IKEY_ESCAPE },\n      { \"key_f\",            IKEY_f },\n      { \"key_f1\",           IKEY_F1 },\n      { \"key_f10\",          IKEY_F10 },\n      { \"key_f11\",          IKEY_F11 },\n      { \"key_f12\",          IKEY_F12 },\n      { \"key_f2\",           IKEY_F2 },\n      { \"key_f3\",           IKEY_F3 },\n      { \"key_f4\",           IKEY_F4 },\n      { \"key_f5\",           IKEY_F5 },\n      { \"key_f6\",           IKEY_F6 },\n      { \"key_f7\",           IKEY_F7 },\n      { \"key_f8\",           IKEY_F8 },\n      { \"key_f9\",           IKEY_F9 },\n      { \"key_g\",            IKEY_g },\n      { \"key_h\",            IKEY_h },\n      { \"key_home\",         IKEY_HOME },\n      { \"key_i\",            IKEY_i },\n      { \"key_insert\",       IKEY_INSERT },\n      { \"key_j\",            IKEY_j },\n      { \"key_k\",            IKEY_k },\n      { \"key_kp0\",          IKEY_KP0 },\n      { \"key_kp1\",          IKEY_KP1 },\n      { \"key_kp2\",          IKEY_KP2 },\n      { \"key_kp3\",          IKEY_KP3 },\n      { \"key_kp4\",          IKEY_KP4 },\n      { \"key_kp5\",          IKEY_KP5 },\n      { \"key_kp6\",          IKEY_KP6 },\n      { \"key_kp7\",          IKEY_KP7 },\n      { \"key_kp8\",          IKEY_KP8 },\n      { \"key_kp9\",          IKEY_KP9 },\n      { \"key_kp_divide\",    IKEY_KP_DIVIDE },\n      { \"key_kp_enter\",     IKEY_KP_ENTER },\n      { \"key_kp_minus\",     IKEY_KP_MINUS },\n      { \"key_kp_multiply\",  IKEY_KP_MULTIPLY },\n      { \"key_kp_period\",    IKEY_KP_PERIOD },\n      { \"key_kp_plus\",      IKEY_KP_PLUS },\n      { \"key_l\",            IKEY_l },\n      { \"key_lalt\",         IKEY_LALT },\n      { \"key_lctrl\",        IKEY_LCTRL },\n      { \"key_left\",         IKEY_LEFT },\n      { \"key_leftbracket\",  IKEY_LEFTBRACKET },\n      { \"key_lshift\",       IKEY_LSHIFT },\n      { \"key_lsuper\",       IKEY_LSUPER },\n      { \"key_m\",            IKEY_m },\n      { \"key_menu\",         IKEY_MENU },\n      { \"key_minus\",        IKEY_MINUS },\n      { \"key_n\",            IKEY_n },\n      { \"key_numlock\",      IKEY_NUMLOCK },\n      { \"key_o\",            IKEY_o },\n      { \"key_p\",            IKEY_p },\n      { \"key_pagedown\",     IKEY_PAGEDOWN },\n      { \"key_pageup\",       IKEY_PAGEUP },\n      { \"key_period\",       IKEY_PERIOD },\n      { \"key_q\",            IKEY_q },\n      { \"key_quote\",        IKEY_QUOTE },\n      { \"key_r\",            IKEY_r },\n      { \"key_ralt\",         IKEY_RALT },\n      { \"key_rctrl\",        IKEY_RCTRL },\n      { \"key_return\",       IKEY_RETURN },\n      { \"key_right\",        IKEY_RIGHT },\n      { \"key_rightbracket\", IKEY_RIGHTBRACKET },\n      { \"key_rshift\",       IKEY_RSHIFT },\n      { \"key_rsuper\",       IKEY_RSUPER },\n      { \"key_s\",            IKEY_s },\n      { \"key_scrolllock\",   IKEY_SCROLLOCK },\n      { \"key_semicolon\",    IKEY_SEMICOLON },\n      { \"key_slash\",        IKEY_SLASH },\n      { \"key_space\",        IKEY_SPACE },\n      { \"key_sysreq\",       IKEY_SYSREQ },\n      { \"key_t\",            IKEY_t },\n      { \"key_tab\",          IKEY_TAB },\n      { \"key_u\",            IKEY_u },\n      { \"key_up\",           IKEY_UP },\n      { \"key_v\",            IKEY_v },\n      { \"key_w\",            IKEY_w },\n      { \"key_x\",            IKEY_x },\n      { \"key_y\",            IKEY_y },\n      { \"key_z\",            IKEY_z },\n\n      // Actions (keep in the same order as the list in event.c).\n      { \"act_a\",            -JOY_A },\n      { \"act_b\",            -JOY_B },\n      { \"act_down\",         -JOY_DOWN },\n      { \"act_l_down\",       -JOY_L_DOWN },\n      { \"act_l_left\",       -JOY_L_LEFT },\n      { \"act_l_right\",      -JOY_L_RIGHT },\n      { \"act_l_up\",         -JOY_L_UP },\n      { \"act_left\",         -JOY_LEFT },\n      { \"act_lshoulder\",    -JOY_LSHOULDER },\n      { \"act_lstick\",       -JOY_LSTICK },\n      { \"act_ltrigger\",     -JOY_LTRIGGER },\n      { \"act_r_down\",       -JOY_R_DOWN },\n      { \"act_r_left\",       -JOY_R_LEFT },\n      { \"act_r_right\",      -JOY_R_RIGHT },\n      { \"act_r_up\",         -JOY_R_UP },\n      { \"act_right\",        -JOY_RIGHT },\n      { \"act_rshoulder\",    -JOY_RSHOULDER },\n      { \"act_rstick\",       -JOY_RSTICK },\n      { \"act_rtrigger\",     -JOY_RTRIGGER },\n      { \"act_select\",       -JOY_SELECT },\n      { \"act_start\",        -JOY_START },\n      { \"act_up\",           -JOY_UP },\n      { \"act_x\",            -JOY_X },\n      { \"act_y\",            -JOY_Y }\n    };\n    TEST_JOY_ALIAS(data);\n  }\n\n  // TODO SDL2 gamecontroller options are static in event_sdl.c (annoying to test...)\n}\n\nUNITTEST(Include)\n{\n  struct config_info *conf = get_config();\n  default_config();\n\n#ifdef CONFIG_EDITOR\n  struct editor_config_info *econf = get_editor_config();\n  default_editor_config();\n#endif\n\n  SECTION(Include)\n  {\n    // This version only works from a config file.\n    boolean ret = write_config(\"a.cnf\", \"include b.cnf\");\n    ret &= write_config(\"b.cnf\", \"mzx_speed = 4\");\n    ASSERTEQ(ret, true, \"\");\n\n    conf->mzx_speed = 2;\n\n    set_config_from_file(SYSTEM_CNF, \"a.cnf\");\n    ASSERTEQ(conf->mzx_speed, 4, \"\");\n  }\n\n  SECTION(IncludeEquals)\n  {\n    // This version works from both the config file and the command line.\n    char include_conf[] = \"include=c.cnf\";\n    boolean ret = write_config(\"c.cnf\", \"mzx_speed = 6\");\n    ASSERTEQ(ret, true, \"\");\n\n    conf->mzx_speed = 1;\n\n    load_arg(include_conf);\n    ASSERTEQ(conf->mzx_speed, 6, \"%s\", include_conf);\n\n    conf->mzx_speed = 2;\n\n    load_arg_file(include_conf, false);\n    ASSERTEQ(conf->mzx_speed, 6, \"%s\", include_conf);\n  }\n\n  SECTION(Recursion)\n  {\n    // Make sure a sane amount of recursive includes work.\n    // Also make sure this works for both the main and editor configuration.\n    boolean ret = write_config(\"a.cnf\", \"include b.cnf\");\n    ret &= write_config(\"b.cnf\", \"include c.cnf\");\n    ret &= write_config(\"c.cnf\", \"mzx_speed=5\\nboard_editor_hide_help=1\");\n    ASSERTEQ(ret, true, \"\");\n\n    conf->mzx_speed = 1;\n#ifdef CONFIG_EDITOR\n    econf->board_editor_hide_help = false;\n#endif\n\n    load_arg((char *)\"include=a.cnf\");\n    ASSERTEQ(conf->mzx_speed, 5, \"\");\n#ifdef CONFIG_EDITOR\n    ASSERTEQ(econf->board_editor_hide_help, true, \"\");\n#endif\n  }\n\n  SECTION(RecursionLimit)\n  {\n    // Make sure recursive include fails gracefully. Even before the hard limit\n    // was added this would happen due to MZX hitting the maximum allowed number\n    // of file descriptors prior to running out of stack. This is pretty much\n    // a freebie, it just needs to not crash or run out of memory.\n    boolean ret = write_config(\"a.cnf\", \"include a.cnf\");\n    ASSERTEQ(ret, true, \"\");\n\n    set_config_from_file(SYSTEM_CNF, \"a.cnf\");\n  }\n}\n\n#ifdef CONFIG_EDITOR\n\n/*\nUNITTEST(ExtendedMacros)\n{\n  // FIXME\n  UNIMPLEMENTED();\n\n  free_editor_config();\n}\n*/\n\n#endif /* CONFIG_EDITOR */\n"
  },
  {
    "path": "unit/editor/stringsearch.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2019-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../Unit.hpp\"\n\n#include <string.h>\n\n#include \"../../src/editor/stringsearch.c\"\n\nstruct string_pair\n{\n  const char *needle;\n  const char *haystack;\n};\n\nstruct string_pair_idx\n{\n  const char *needle;\n  const char *haystack;\n  ssize_t where;\n};\n\nUNITTEST(Search)\n{\n  // left: needle, right: haystack\n  static const string_pair haystack_none[] =\n  {\n    { \"\", \"literally anything\" },\n    { \"literally anything\", \"\" },\n    { \"abcd\", \"abdacbdacbdabdcabd\" },\n    {\n      \"abce\",\n      \"lomgstringlomgstringlomgstringlomgstringlomgstring\"\n      \"lomgstringlomgstringlomgstringlomgstringlomgstring\"\n    },\n    { \"abcf\", \"s\" },\n    { \"some needle\", \"some needl\" },\n    { \"dome needle\", \"aome needle\" },\n    { \"case\", \"CASE\" },\n    { \"abcde\", \"cbdebcedbcdebcdbecdbedcbebcdebcdebcedbcbdcbedcbedbcdebcedbc\" },\n    { \"abcde\", \"cbdebcedbcdebcdbecAbcdEbebcdebABCDEedbcbdcbedcbedbcdebAbcde\" },\n    {\n      \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n      \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n      \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n      \"baaaaaaaaaaaaaaaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAA\"\n    },\n    { \"abcdabc\", \"abceabceabceabceabc\" },\n  };\n  static const string_pair_idx haystack_once[] =\n  {\n    { \"abcde\", \"abcde\", 0 },\n    { \"abcd\", \"aaaaaaaaaabcaaaabcdaaaaa\", 15 },\n    {\n      \"------------separator\",\n      \"fhs-----------fhsdklfhsdlseparator\"\n      \"aflskdfhsdjklsfhdj------------separator\", 52\n    },\n    { \"case\", \"CASEcaseCASE\", 4 },\n    {\n      \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n      \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n      \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n      \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n      141\n    },\n    { \"abcdabc\", \"abceabcdabceabceabc\", 4 },\n    { \"pfatt\",\n      \"set \\\"pf\\\" to \"\n      \"\\\"('pldir'*6+('pfatt'>0*3)+('pfwalk'=1 ? 1 : 'pfwalk'=3 ? 2 : 0))\\\"\", 26\n    },\n    {\n      \"textdone\", \"set \\\"macroar_!textdone\\\" to 0\", 14\n    },\n    {\n      \"!text\", \"set \\\"$macrof_!text:Info\\\" to \\\"$\\\"\", 13\n    },\n    {\n      \"scripts.txt\", \"set \\\"scripts.txt\\\" to \\\"FWRITE_OPEN\\\"\", 5\n    },\n    {\n      \"$local\", \"set \\\"$@\\\" to \\\"R&&p_id&&.&$local_('local12')&\\\"\", 24\n    },\n  };\n  static const string_pair haystack_twice[] =\n  {\n    { \"abcde\", \"abcdeabcde\" },\n    { \"abcd\", \"aaaaaaaaaabcaaaabcdaaaaabcdaaaaaaaaa\" },\n    { \"abcd\", \"aaaaaaaaaabcaaaabcdaaaaabcd\" },\n    { \"abcd\", \"abcdabcabcabcabcabcabcbcdbcdbcdbcdbcdbcdbcdbcdbcdbcdbcdabcd\" },\n    {\n      \"------------separator\",\n      \"fhsfhsdklfhsdl------------separator\"\n      \"aflskdfhsdjklsfhdj------------separator--\"\n    },\n    { \"case\", \"caseCASEcase\" },\n    { \"abba\", \"abbabba\" },\n    {\n      \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n      \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n      \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n      \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n    },\n    { \"abcdabc\", \"abceabcdabcdabceabc\" },\n  };\n  struct string_search_data data;\n  const char *A;\n  const char *B;\n  const void *dest;\n\n  SECTION(OneOff)\n  {\n    for(const string_pair &d : haystack_none)\n    {\n      A = d.haystack;\n      B = d.needle;\n\n      ASSERT(!string_search(A, strlen(A), B, strlen(B), nullptr, false), \"%s\", A);\n    }\n\n    for(const string_pair_idx &d : haystack_once)\n    {\n      A = d.haystack;\n      B = d.needle;\n      ssize_t where = d.where;\n\n      dest = string_search(A, strlen(A), B, strlen(B), nullptr, false);\n      ASSERT(dest, \"%s\", A);\n      ASSERT(dest >= A && dest < A + strlen(A), \"%s\", A);\n      ASSERTEQ((const char *)dest - A, where, \"%s\", A);\n    }\n\n    for(const string_pair &d : haystack_twice)\n    {\n      A = d.haystack;\n      B = d.needle;\n\n      dest = string_search(A, strlen(A), B, strlen(B), nullptr, false);\n      ASSERT(dest, \"%s\", A);\n      ASSERT(dest >= A && dest < A + strlen(A), \"%s\", A);\n    }\n  }\n\n  SECTION(Repeating)\n  {\n    for(const string_pair_idx &d : haystack_once)\n    {\n      const char *dest;\n      A = d.haystack;\n      B = d.needle;\n      ssize_t where = d.where;\n      ssize_t a_len = strlen(A);\n      ssize_t b_len = strlen(B);\n\n      string_search_index(B, b_len, &data, false);\n\n      dest = (const char *)string_search(A, a_len, B, b_len, &data, false);\n      ASSERT(dest, \"%s\", A);\n      ASSERT(dest >= A && dest < A + a_len, \"%s\", A);\n      ASSERTEQ((const char *)dest - A, where, \"%s\", A);\n      dest++;\n      a_len -= dest - A;\n      A = dest;\n\n      ASSERT(!string_search(A, a_len, B, b_len, &data, false), \"%s\", A);\n    }\n\n    for(const string_pair &d : haystack_twice)\n    {\n      const char *dest;\n      A = d.haystack;\n      B = d.needle;\n      ssize_t a_len = strlen(A);\n      ssize_t b_len = strlen(B);\n\n      string_search_index(B, b_len, &data, false);\n\n      dest = (const char *)string_search(A, a_len, B, b_len, &data, false);\n      ASSERT(dest, \"%s\", A);\n      ASSERT(dest >= A && dest < A + a_len, \"%s\", A);\n      dest++;\n      a_len -= dest - A;\n      A = dest;\n\n      dest = (const char *)string_search(A, a_len, B, b_len, &data, false);\n      ASSERT(dest, \"%s\", A);\n      ASSERT(dest >= A && dest < A + a_len, \"%s\", A);\n      dest++;\n      a_len -= dest - A;\n      A = dest;\n\n      ASSERT(!string_search(A, a_len, B, b_len, &data, false), \"%s\", A);\n    }\n  }\n}\n\nUNITTEST(SearchCaseInsensitive)\n{\n  // left: needle, right: haystack\n  static const string_pair haystack_none[] =\n  {\n    { \"\", \"literally anything\" },\n    { \"literally anything\", \"\" },\n    { \"abcd\", \"abdacbdacbdabdcabd\" },\n    {\n      \"abce\",\n      \"lomgstringlomgstringlomgstringlomgstringlomgstring\"\n      \"lomgstringlomgstringlomgstringlomgstringlomgstring\"\n    },\n    { \"abcf\", \"s\" },\n    { \"some needle\", \"some needl\" },\n    { \"dome needle\", \"aome needle\" },\n    { \"abcde\", \"cbdebcedbcdebcdbecdbedcbebcdebcdebcedbcbdcbedcbedbcdebcedbc\" },\n    {\n      \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAAaaaaaaaa\",\n      \"baaaaaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n      \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n      \"baaaaaaaaaaaaaaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n    },\n    { \"abcdabc\", \"abceABCeabceABCeabc\" },\n  };\n  static const string_pair haystack_once[] =\n  {\n    { \"abcde\", \"abcde\" },\n    { \"abcd\", \"aaaaaaaaaabcaaaabcdaaaaa\" },\n    {\n      \"------------separator\",\n      \"fhs-----------fhsdklfhsdlseparator\"\n      \"aflskdfhsdjklsfhdj------------separator\"\n    },\n    { \"case\", \"CASE\" },\n    {\n      \"aAAAAAaaaaaaaaaaaaaaaaaaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n      \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAAaaaaaaaaaaaaaaa\"\n      \"baaaaaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\n      \"baaaaaaaaaaaaaaaaaaaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAAaaaaaaa\"\n    },\n    { \"abcdabc\", \"abceaBcdAbCeabceabc\" },\n  };\n  static const string_pair haystack_twice[] =\n  {\n    { \"abcde\", \"abcdeabcde\" },\n    { \"abcd\", \"aaaaaaaaaabcaaaabcdaaaaabcdaaaaaaaaa\" },\n    { \"abcd\", \"aaaaaaaaaabcaaaabcdaaaaabcd\" },\n    { \"abcd\", \"abcdabcabcabcabcabcabcbcdbcdbcdbcdbcdbcdbcdbcdbcdbcdbcdabcd\" },\n    {\n      \"------------separator\",\n      \"fhsfhsdklfhsdl------------separator\"\n      \"aflskdfhsdjklsfhdj------------separator--\"\n    },\n    { \"case\", \"caseCASE\" },\n    { \"abba\", \"abbabba\" },\n    { \"abcde\", \"cbdebcedbcdebcdbecAbcdEbebcdebABCDEedbcbdcbedcbedbcdebAbcdb\" },\n    {\n      \"aaaaaaaaaaaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAAaa\",\n      \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaaaaa\"\n      \"baaaaaaaaaaaaaaaaaaaaaaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAA\"\n      \"baaaaaAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaAAAAAaaaaaaaa\"\n    },\n    { \"abcdabc\", \"abceABCDabcDabceABC\" },\n  };\n  struct string_search_data data;\n  const char *A;\n  const char *B;\n  const void *dest;\n\n  SECTION(OneOff)\n  {\n    for(const string_pair &d : haystack_none)\n    {\n      A = d.haystack;\n      B = d.needle;\n\n      ASSERT(!string_search(A, strlen(A), B, strlen(B), NULL, true), \"%s\", A);\n    }\n\n    for(const string_pair &d : haystack_once)\n    {\n      A = d.haystack;\n      B = d.needle;\n\n      dest = string_search(A, strlen(A), B, strlen(B), NULL, true);\n      ASSERT(dest, \"%s\", A);\n      ASSERT(dest >= A && dest < A + strlen(A), \"%s\", A);\n    }\n\n    for(const string_pair &d : haystack_twice)\n    {\n      A = d.haystack;\n      B = d.needle;\n\n      dest = string_search(A, strlen(A), B, strlen(B), NULL, true);\n      ASSERT(dest, \"%s\", A);\n      ASSERT(dest >= A && dest < A + strlen(A), \"%s\", A);\n    }\n  }\n\n  SECTION(Repeating)\n  {\n    for(const string_pair &d : haystack_once)\n    {\n      const char *dest;\n      A = d.haystack;\n      B = d.needle;\n      ssize_t a_len = strlen(A);\n      ssize_t b_len = strlen(B);\n\n      string_search_index(B, b_len, &data, true);\n\n      dest = (const char *)string_search(A, a_len, B, b_len, &data, true);\n      ASSERT(dest, \"%s\", A);\n      ASSERT(dest >= A && dest < A + a_len, \"%s\", A);\n      dest++;\n      a_len -= dest - A;\n      A = dest;\n\n      ASSERT(!string_search(A, a_len, B, b_len, &data, true), \"%s\", A);\n    }\n\n    for(const string_pair &d : haystack_twice)\n    {\n      const char *dest;\n      A = d.haystack;\n      B = d.needle;\n      ssize_t a_len = strlen(A);\n      ssize_t b_len = strlen(B);\n\n      string_search_index(B, b_len, &data, true);\n\n      dest = (const char *)string_search(A, a_len, B, b_len, &data, true);\n      ASSERT(dest, \"%s\", A);\n      ASSERT(dest >= A && dest < A + a_len, \"%s\", A);\n      dest++;\n      a_len -= dest - A;\n      A = dest;\n\n      dest = (const char *)string_search(A, a_len, B, b_len, &data, true);\n      ASSERT(dest, \"%s\", A);\n      ASSERT(dest >= A && dest < A + a_len, \"%s\", A);\n      dest++;\n      a_len -= dest - A;\n      A = dest;\n\n      ASSERT(!string_search(A, a_len, B, b_len, &data, true), \"%s\", A);\n    }\n  }\n}\n"
  },
  {
    "path": "unit/expr.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Test the custom Robotic interpreter replacements for sprintf %d and %x.\n * These should output exactly the same thing as sprintf %d and %x.\n */\n\n#include \"Unit.hpp\"\n#include \"../src/expr.h\"\n\n#include <limits.h>\n\nstruct tr_int_data\n{\n  int value;\n  char dec[12];\n  char hex[9];\n};\n\nstatic const tr_int_data data[] =\n{\n  { 0,            \"0\",            \"0\" },\n  { 1,            \"1\",            \"1\" },\n  { 10,           \"10\",           \"a\" },\n  { 16,           \"16\",           \"10\" },\n  { 100,          \"100\",          \"64\" },\n  { 1024,         \"1024\",         \"400\" },\n  { 32768,        \"32768\",        \"8000\" },\n  { 65535,        \"65535\",        \"ffff\" },\n  { 54321,        \"54321\",        \"d431\" },\n  { 123454321,    \"123454321\",    \"75bc371\" },\n  { 2147483647,   \"2147483647\",   \"7fffffff\" },\n  { -2147483648,  \"-2147483648\",  \"80000000\" },\n  { -1,           \"-1\",           \"ffffffff\" },\n  { -12345,       \"-12345\",       \"ffffcfc7\" },\n  { -123454321,   \"-123454321\",   \"f8a43c8f\" },\n  { -1000000000,  \"-1000000000\",  \"c4653600\" },\n};\n\nUNITTEST(tr_int_to_string)\n{\n  char tr_buf[12];\n  char *tmp;\n  size_t len;\n\n  for(const tr_int_data &d : data)\n  {\n    tmp = tr_int_to_string(tr_buf, d.value, &len);\n\n    ASSERTEQ(len, strlen(d.dec), \"%s\", d.dec);\n    ASSERTCMP(tmp, d.dec, \"\");\n  }\n}\n\nUNITTEST(tr_int_to_hex_string)\n{\n  char tr_buf[9];\n  char *tmp;\n  size_t len;\n\n  for(const tr_int_data &d : data)\n  {\n    tmp = tr_int_to_hex_string(tr_buf, d.value, &len);\n\n    ASSERTEQ(len, strlen(d.hex), \"%s\", d.hex);\n    ASSERTCMP(tmp, d.hex, \"\");\n  }\n}\n"
  },
  {
    "path": "unit/intake.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2021-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Automated test for the parts of intake that are testable.\n * Currently, this is mainly intake_apply_event_fixed.\n */\n\n#include \"Unit.hpp\"\n#include \"../src/intake.c\"\n\n// TODO shut up the linker as this isn't CORE_LIBSPEC currently.\nboolean has_unicode_input() { return false; }\n\nstruct int_pair\n{\n  int input;\n  int expected;\n};\n\nstruct event_input_data\n{\n  const char *base;\n  int start_pos;\n  int new_pos;\n  const char *input;\n  const char *expected;\n};\n\nstruct event_repeat_data\n{\n  const char *base;\n  int start_pos;\n  int new_pos;\n  int repeat_times;\n  const char *expected;\n};\n\nstruct event_partial_data\n{\n  enum intake_event_type type;\n  const char *base;\n  int old_pos;\n  int new_pos;\n  int value;\n};\n\nstruct event_ext_data\n{\n  enum intake_event_type type;\n  const char *base;\n  int old_pos;\n  int new_pos;\n  int value;\n  const char *input;\n  const char *expected;\n};\n\nstruct input_string_data\n{\n  const char *base;\n  int start_pos;\n  const char *input;\n  const char *expected;\n  ssize_t retval;\n  int linebreak_char;\n  boolean insert_on;\n};\n\nstatic const int_pair pos_data[] =\n{\n  { 0, 0 },\n  { 50, 50 },\n  { 100, 100 },\n  { 1000000, 100 },\n  { -1, 0 },\n  { -12781831, 0 },\n};\n\nstatic const int_pair length_data[] =\n{\n  { 0, 0 },\n  { 50, 50 },\n  { 240, 240 },\n  { 1000, 240 },\n  { 1000000, 240 },\n  { -1, 0 },\n  { -4983412, 0 },\n};\n\nstatic const char skip_template[256] =\n \"aaa bb`cc~dd!ee@ff#gg$hh%ii^jj&kk*ll(mm)\"\n \"nn-oo_pp=qq+rr[ss]tt{uu}vv\\\\ww|xx;yy:zz'01\\\"\"\n \"23,45<67.>89/00??0\";\n\nstatic const int skip_forward_positions[] =\n{\n   0,  3,  6,  9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39,\n  42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81,\n  84, 87, 90, 94, 97, 100\n};\n\nstatic const int skip_backward_positions[] =\n{\n  100, 99, 95, 92, 88, 85, 82,\n   79, 76, 73, 70, 67, 64, 61, 58, 55, 52, 49, 46, 43, 40,\n   37, 34, 31, 28, 25, 22, 19, 16, 13, 10,  7,  4,  0\n};\n\nstatic const char non_ascii_input_data[] =\n  \"sjdhfklsjdfdabc\\n\\r\\n\\t\\v\\bsdfjksdlkfjsdkfkd\"\n  \"\\xE6\\x52\\xEB\\xF2\\x6D\\x4D\\x4A\\xB7\\x87\\xB2\\x92\\x88\\xDE\\x91\\x24\";\n\nstatic const input_string_data input_data[] =\n{\n  { \"put  Solid p00 0 0\", 4, \"c0f\", \"put c0f Solid p00 0 0\", -1, -1, true },\n  { \"put c0f Solid  0 0\", 14, \"p00\", \"put c0f Solid p00 0 0\", -1, -1, true },\n  { \"char edit '@'\", 14, \" 0 0 0 0 0 0 0 0 0 0 0 0 0 0\",\n    \"char edit '@' 0 0 0 0 0 0 0 0 0 0 0 0 0 0\", -1, -1, true },\n  { \"char edit '@'\", 4, \" 0 0 0 0 0 0 0 0 0 0 0 0 0 0\",\n    \"char 0 0 0 0 0 0 0 0 0 0 0 0 0 0\", -1, -1, false },\n  { \".comment\", 0, \": justentered^\", \": justentered.comment\", 14, '^', true },\n  { \"abc\\ndef\\n\", 8, \"ghi\\njkl\\n\", \"abc\\ndef\\nghi\", 4, '\\n', true },\n  { \"abc\\ndef\\n\", 8, \"ghi\\njkl\\n\", \"abc\\ndef\\nghi\\njkl\\n\", -1, -1, true },\n  { \"abc\\ndef\\n\", 0, \"ghi\\njkl\\n\", \"ghi\\ndef\\n\", 4, '\\n', false },\n  { \"abc\\ndef\\n\", 0, \"ghi\\njkl\\n\", \"ghi\\njkl\\n\", -1, -1, false },\n  { \"\", 0, non_ascii_input_data, non_ascii_input_data, -1, -1, true },\n  { \"\", 0, non_ascii_input_data, non_ascii_input_data, -1, -1, false },\n  { \"\", 0, \"test\\0test\", \"test\", -1, -1, true },\n  { \"\", 0, \"test\\0test\", \"test\", -1, -1, false },\n  { \"whatever\", 4, \"\", \"whatever\", -1, -1, true },\n  { \"whatever\", 4, \"\", \"whatever\", -1, -1, false },\n};\n\n/**\n * Test internal and external functions for manipulating the position and\n * current length of the intake editor. A large portion of this is making sure\n * the external position and length pointers are syncronized correctly.\n */\nUNITTEST(PosLength)\n{\n  struct intake_subcontext intk{};\n  char dest[256];\n  int ext = 0;\n\n  intk.current_length = 100;\n  intk.max_length = 240;\n\n  samesize(dest, skip_template);\n  memcpy(dest, skip_template, arraysize(dest));\n\n  SECTION(intake_set_pos_no_external)\n  {\n    for(const int_pair &d : pos_data)\n    {\n      intake_set_pos(&intk, d.input);\n      ASSERTEQ(intk.pos, d.expected, \"%d -> %d\", d.input, d.expected);\n    }\n  }\n\n  SECTION(intake_set_pos_external)\n  {\n    intk.pos_external = &ext;\n\n    for(const int_pair &d : pos_data)\n    {\n      intake_set_pos(&intk, d.input);\n      ASSERTEQ(intk.pos, d.expected, \"%d -> %d\", d.input, d.expected);\n      ASSERTEQ(ext, d.expected, \"%d -> %d\", d.input, d.expected);\n    }\n  }\n\n  SECTION(intake_set_length_no_external)\n  {\n    for(const int_pair &d : length_data)\n    {\n      intake_set_length(&intk, d.input);\n      ASSERTEQ(intk.current_length, d.expected, \"%d -> %d\", d.input, d.expected);\n    }\n  }\n\n  SECTION(intake_set_length_external)\n  {\n    intk.length_external = &ext;\n\n    for(const int_pair &d : length_data)\n    {\n      intake_set_length(&intk, d.input);\n      ASSERTEQ(intk.current_length, d.expected, \"%d -> %d\", d.input, d.expected);\n      ASSERTEQ(ext, d.expected, \"%d -> %d\", d.input, d.expected);\n    }\n  }\n\n  // intake_sync should not crash when the provided intake is null.\n  SECTION(intake_sync_null)\n  {\n    intake_sync(nullptr);\n  }\n\n  /**\n   * When no buffer/external pointers are present, intake_sync should not\n   * modify the intake position or current length values.\n   */\n  SECTION(intake_sync_no_data_or_external)\n  {\n    for(const int_pair &d : pos_data)\n    {\n      intake_set_pos(&intk, d.input);\n      intake_sync((subcontext *)&intk);\n      ASSERTEQ(intk.pos, d.expected, \"%d -> %d\", d.input, d.expected);\n    }\n\n    for(const int_pair &d : length_data)\n    {\n      intake_set_length(&intk, d.input);\n      intake_sync((subcontext *)&intk);\n      ASSERTEQ(intk.current_length, d.expected, \"%d -> %d\", d.input, d.expected);\n    }\n  }\n\n  /**\n   * When an external position pointer is present, intake_sync should update\n   * the position to reflect the provided external position, bounded to the\n   * current length.\n   */\n  SECTION(intake_sync_pos)\n  {\n    intk.pos_external = &ext;\n\n    for(const int_pair &d : pos_data)\n    {\n      ext = d.input;\n      intake_sync((subcontext *)&intk);\n      ASSERTEQ(intk.pos, d.expected, \"%d -> %d\", d.input, d.expected);\n      ASSERTEQ(ext, d.expected, \"%d -> %d\", d.input, d.expected);\n    }\n  }\n\n  /**\n   * When a buffer is present, intake_sync should update the current length to\n   * the length of the buffer string.\n   */\n  SECTION(intake_sync_no_external)\n  {\n    int dest_len = strlen(dest);\n    intk.current_length = 100;\n    intk.dest = dest;\n    intk.pos = 97;\n\n    for(const int_pair &d : length_data)\n    {\n      dest_len = Unit::clamp(dest_len, 0, d.input);\n      intake_set_length(&intk, d.input);\n      intake_sync((subcontext *)&intk);\n      ASSERTEQ(intk.current_length, dest_len, \"%d -> %d\", d.input, dest_len);\n      ASSERTEQ(intk.pos, 97, \"%d -> %d\", d.input, dest_len);\n    }\n  }\n\n  /**\n   * When external pointers are present but there is no buffer, intake_sync\n   * should update the current length to the external length.\n   */\n  SECTION(intake_sync_no_dest)\n  {\n    intk.length_external = &ext;\n    intk.dest = nullptr;\n\n    for(const int_pair &d : length_data)\n    {\n      ext = d.input;\n      intake_sync((subcontext *)&intk);\n      ASSERTEQ(intk.current_length, d.expected, \"%d -> %d\", d.input, d.expected);\n      ASSERTEQ(ext, d.expected, \"%d -> %d\", d.input, d.expected);\n    }\n  }\n\n  /**\n   * When both dest and external length are present, dest should be preferred\n   * for updating the length.\n   */\n  SECTION(intake_sync_dest_and_external)\n  {\n    int dest_len = strlen(dest);\n    intk.length_external = &ext;\n    intk.dest = dest;\n\n    for(const int_pair &d : length_data)\n    {\n      ext = d.input;\n      intake_sync((subcontext *)&intk);\n      ASSERTEQ(intk.current_length, dest_len, \"%d -> %d\", d.input, dest_len);\n      ASSERTEQ(ext, dest_len, \"%d -> %d\", d.input, dest_len);\n    }\n  }\n}\n\n/**\n * Test the default fixed-size buffer intake editing operations.\n */\nUNITTEST(EventFixed)\n{\n  static const event_input_data insert_data[] =\n  {\n    { \"test string :)\", 0,  9,  \"Inserted \",                \"Inserted test string :)\" },\n    { \"abcdevwxyz\",     5,  29, \" put this in the middle \", \"abcde put this in the middle vwxyz\" },\n    { \"test string :)\", 15, 27, \" append this.\",            \"test string :) append this.\" },\n    { \"\",               0,  12, \"blank string\",             \"blank string\" },\n    { \"whatever\",       0,  0,  \"\",                         \"whatever\" },\n    { \"whatever\",       4,  4,  \"\",                         \"whatever\" },\n    { \"whatever\",       8,  8,  \"\",                         \"whatever\" },\n  };\n  static const event_input_data overwrite_data[] =\n  {\n    { \"test string :)\", 0,  11, \"overwritten\",              \"overwritten :)\" },\n    { \"abcdevwxyz\",     5,  26, \" put this at the end\",     \"abcde put this at the end\" },\n    { \"test string :)\", 15, 28, \" append this.\",            \"test string :) append this.\" },\n    { \"\",               0,  12, \"blank string\",             \"blank string\" },\n    { \"whatever\",       0,  0,  \"\",                         \"whatever\" },\n    { \"whatever\",       4,  0,  \"\",                         \"whatever\" },\n    { \"whatever\",       8,  0,  \"\",                         \"whatever\" },\n  };\n\n  struct intake_subcontext intk{};\n  subcontext *sub = reinterpret_cast<subcontext *>(&intk);\n  char dest[256];\n  boolean result;\n\n  intk.dest = dest;\n  intk.max_length = 240;\n  intk.current_length = 100;\n  intk.pos = 0;\n\n  samesize(dest, skip_template);\n  memcpy(dest, skip_template, arraysize(dest));\n\n  SECTION(intake_skip_back)\n  {\n    int ext = 100;\n    intk.pos = 100;\n    intk.pos_external = &ext;\n\n    for(int i : skip_backward_positions)\n    {\n      ASSERTEQ(intk.pos, i, \"%d\", i);\n      ASSERTEQ(ext, i, \"%d\", i);\n      intake_skip_back(&intk);\n    }\n  }\n\n  SECTION(intake_skip_forward)\n  {\n    int ext = 0;\n    intk.pos_external = &ext;\n\n    for(int i : skip_forward_positions)\n    {\n      ASSERTEQ(intk.pos, i, \"%d\", i);\n      ASSERTEQ(ext, i, \"%d\", i);\n      intake_skip_forward(&intk);\n    }\n  }\n\n  SECTION(intake_apply_event_fixed_errs)\n  {\n    // Should not crash with a null intake.\n    result = intake_apply_event_fixed(nullptr, INTK_MOVE, 0, 0, nullptr);\n    ASSERTEQ(result, false, \"\");\n\n    // Should not crash with a null dest.\n    intk.dest = nullptr;\n    result = intake_apply_event_fixed(sub, INTK_MOVE, 0, 0, nullptr);\n    ASSERTEQ(result, false, \"\");\n    intk.dest = dest;\n\n    // Should not crash if intk->pos is somehow invalid.\n    int invalid_pos[] = { -1, -12431, intk.current_length + 1, 100000 };\n    for(int i : invalid_pos)\n    {\n      intk.pos = i;\n      result = intake_apply_event_fixed(sub, INTK_MOVE, 0, 0, nullptr);\n      ASSERTEQ(result, false, \"\");\n    }\n  }\n\n  SECTION(INTK_NO_EVENT)\n  {\n    // Nothing should ever actually send this event (if it does, it's a bug).\n    result = intake_apply_event_fixed(sub, INTK_NO_EVENT, 0, 0, nullptr);\n    ASSERTEQ(result, false, \"\");\n  }\n\n  SECTION(INTK_MOVE)\n  {\n    // Applying this moves the cursor, that's it.\n    for(const int_pair &d : pos_data)\n    {\n      intk.pos = 25;\n      result = intake_apply_event_fixed(sub, INTK_MOVE, d.input, 0, nullptr);\n      ASSERTEQ(result, true, \"%d -> %d\", d.input, d.expected);\n      ASSERTEQ(intk.pos, d.expected, \"%d -> %d\", d.input, d.expected);\n    }\n  }\n\n  SECTION(INTK_MOVE_WORDS)\n  {\n    // This just wraps the skip forward/back functions.\n    int ext = 0;\n    intk.pos_external = &ext;\n\n    for(int i : skip_forward_positions)\n    {\n      ASSERTEQ(intk.pos, i, \"%d (forward)\", i);\n      ASSERTEQ(ext, i, \"%d (forward)\", i);\n      result = intake_apply_event_fixed(sub, INTK_MOVE_WORDS, intk.pos, 1, nullptr);\n      ASSERTEQ(result, true, \"%d (forward)\", i);\n    }\n\n    ext = 100;\n    intk.pos = 100;\n    for(int i : skip_backward_positions)\n    {\n      ASSERTEQ(intk.pos, i, \"%d (backward)\", i);\n      ASSERTEQ(ext, i, \"%d (backward)\", i);\n      result = intake_apply_event_fixed(sub, INTK_MOVE_WORDS, intk.pos, -1, nullptr);\n      ASSERTEQ(result, true, \"%d (backward)\", i);\n    }\n  }\n\n  SECTION(INTK_INSERT)\n  {\n    // Insert text into the buffer.\n    for(const event_input_data &d : insert_data)\n    {\n      int expected_len = strlen(d.expected);\n      snprintf(dest, sizeof(dest), \"%s\", d.base);\n      intake_set_length(&intk, strlen(dest));\n      intake_set_pos(&intk, d.start_pos);\n\n      for(size_t i = 0, len = strlen(d.input); i < len; i++)\n      {\n        result = intake_apply_event_fixed(sub, INTK_INSERT, intk.pos + 1, d.input[i], nullptr);\n        ASSERTEQ(result, true, \"%s\", d.expected);\n      }\n      ASSERTCMP(dest, d.expected, \"\");\n      ASSERTEQ(intk.current_length, expected_len, \"%s\", d.expected);\n      ASSERTEQ(intk.pos, d.new_pos, \"%s\", d.expected);\n    }\n\n    // Special: prevent inserting beyond the maximum length.\n    intk.max_length = 8;\n    intk.current_length = 7;\n    snprintf(dest, sizeof(dest), \"abcdefg\");\n\n    intk.pos = 7;\n    result = intake_apply_event_fixed(sub, INTK_INSERT, intk.pos + 1, 'h', nullptr);\n    ASSERTEQ(result, true, \"\");\n    result = intake_apply_event_fixed(sub, INTK_INSERT, intk.pos + 1, 'i', nullptr);\n    ASSERTEQ(result, false, \"\");\n    ASSERTEQ(intk.pos, 8, \"\");\n    intk.pos = 0;\n    result = intake_apply_event_fixed(sub, INTK_INSERT, intk.pos + 1, 'i', nullptr);\n    ASSERTEQ(result, false, \"\");\n    ASSERTEQ(intk.pos, 0, \"\");\n  }\n\n  SECTION(INTK_OVERWRITE)\n  {\n    // Overwrite text in the buffer. Can insert at the end of the line.\n    for(const event_input_data &d : overwrite_data)\n    {\n      int expected_len = strlen(d.expected);\n      snprintf(dest, sizeof(dest), \"%s\", d.base);\n      intake_set_length(&intk, strlen(dest));\n      intake_set_pos(&intk, d.start_pos);\n\n      for(size_t i = 0, len = strlen(d.input); i < len; i++)\n      {\n        result = intake_apply_event_fixed(sub, INTK_OVERWRITE, intk.pos + 1, d.input[i], nullptr);\n        ASSERTEQ(result, true, \"%s\", d.expected);\n      }\n      ASSERTCMP(dest, d.expected, \"\");\n      ASSERTEQ(intk.current_length, expected_len, \"\");\n    }\n\n    // Special: prevent inserting beyond the maximum length.\n    intk.max_length = 8;\n    intk.current_length = 7;\n    snprintf(dest, sizeof(dest), \"abcdefg\");\n\n    intk.pos = 7;\n    result = intake_apply_event_fixed(sub, INTK_OVERWRITE, intk.pos + 1, 'h', nullptr);\n    ASSERTEQ(result, true, \"\");\n    result = intake_apply_event_fixed(sub, INTK_OVERWRITE, intk.pos + 1, 'i', nullptr);\n    ASSERTEQ(result, false, \"\");\n    ASSERTEQ(intk.pos, 8, \"\");\n    // This should work, though.\n    intk.pos = 0;\n    result = intake_apply_event_fixed(sub, INTK_OVERWRITE, intk.pos + 1, 'i', nullptr);\n    ASSERTEQ(result, true, \"\");\n    ASSERTCMP(dest, \"ibcdefgh\", \"\");\n  }\n\n  SECTION(INTK_DELETE)\n  {\n    // Delete the char at the cursor.\n    static const event_repeat_data data[] =\n    {\n      { \"testing string :)\",  8,  8,  7,  \"testing :)\" },\n      { \"abcdef\",             0,  0,  6,  \"\" },\n      { \"abcdef\",             1,  1,  4,  \"af\" },\n      { \"\",                   0,  0,  10, \"\" },\n      { \"some stuff\",         10, 10, 10, \"some stuff\" },\n    };\n\n    for(const event_repeat_data &d : data)\n    {\n      int expected_len = strlen(d.expected);\n      snprintf(dest, sizeof(dest), \"%s\", d.base);\n      intake_set_length(&intk, strlen(dest));\n      intake_set_pos(&intk, d.start_pos);\n\n      for(int i = 0; i < d.repeat_times; i++)\n      {\n        result = intake_apply_event_fixed(sub, INTK_DELETE, intk.pos, 0, nullptr);\n        ASSERTEQ(result, true, \"%s\", d.expected);\n      }\n      ASSERTCMP(dest, d.expected, \"\");\n      ASSERTEQ(intk.current_length, expected_len, \"\");\n    }\n  }\n\n  SECTION(INTK_BACKSPACE)\n  {\n    // Delete the char before the cursor.\n    static const event_repeat_data data[] =\n    {\n      { \"testing string :)\",  14, 7, 7,  \"testing :)\" },\n      { \"abcdef\",             6,  0, 6,  \"\" },\n      { \"abcdef\",             5,  1, 4,  \"af\" },\n      { \"\",                   0,  0, 10, \"\" },\n      { \"some stuff\",         0,  0, 10, \"some stuff\" },\n    };\n\n    for(const event_repeat_data &d : data)\n    {\n      int expected_len = strlen(d.expected);\n      snprintf(dest, sizeof(dest), \"%s\", d.base);\n      intake_set_length(&intk, strlen(dest));\n      intake_set_pos(&intk, d.start_pos);\n\n      for(int i = 0; i < d.repeat_times; i++)\n      {\n        result = intake_apply_event_fixed(sub, INTK_BACKSPACE, intk.pos - 1, 0, nullptr);\n        ASSERTEQ(result, true, \"%s\", d.expected);\n      }\n      ASSERTCMP(dest, d.expected, \"\");\n      ASSERTEQ(intk.current_length, expected_len, \"%s\", d.expected);\n      ASSERTEQ(intk.pos, d.new_pos, \"%s\", d.expected);\n    }\n  }\n\n  SECTION(INTK_BACKSPACE_WORDS)\n  {\n    // Delete an entire word before the cursor.\n    // NOTE: old intake() deletes an extra non-alphanumeric char before the\n    // word, which seems like a bug and has not been replicated here.\n    static const event_repeat_data data[] =\n    {\n      { \"testing string :)\",  17, 8, 1,  \"testing \" },\n      { \"testing string :)\",  17, 0, 2,  \"\" },\n      { \"testing string :)\",  15, 0, 2,  \":)\" },\n      { \"testing{string}\",    14, 8, 1,  \"testing{}\" },\n      { \"abc def ghi\",        9,  4, 2,  \"abc hi\" },\n      { \"abcdef\",             6,  0, 1,  \"\" },\n      { \"abcdef\",             3,  0, 1,  \"def\" },\n      { \"\",                   0,  0, 2,  \"\" },\n      { \"some stuff\",         0,  0, 2,  \"some stuff\" },\n      { \"whatever\",           8,  8, 0,  \"whatever\" },\n      { \"whatever\",           4,  4, -1, \"whatever\" },\n    };\n\n    for(const event_repeat_data &d : data)\n    {\n      int expected_len = strlen(d.expected);\n      snprintf(dest, sizeof(dest), \"%s\", d.base);\n      intake_set_length(&intk, strlen(dest));\n      intake_set_pos(&intk, d.start_pos);\n\n      result = intake_apply_event_fixed(sub, INTK_BACKSPACE_WORDS, intk.pos, d.repeat_times, nullptr);\n      ASSERTEQ(result, true, \"%s\", d.expected);\n      ASSERTCMP(dest, d.expected, \"\");\n      ASSERTEQ(intk.current_length, expected_len, \"%s\", d.expected);\n      ASSERTEQ(intk.pos, d.new_pos, \"%s\", d.expected);\n    }\n  }\n\n  SECTION(INTK_CLEAR)\n  {\n    static const event_repeat_data data[] =\n    {\n      { \"testing string :)\",  17, 0, 1, nullptr },\n      { \"testing string :)\",  10, 0, 1, nullptr },\n      { \"testing string :)\",  0,  0, 1, nullptr },\n      { \"abc def ghi\",        9,  0, 1, nullptr },\n      { \"abcdef\",             6,  0, 1, nullptr },\n      { \"abcdef\",             3,  0, 1, nullptr },\n      { \"\",                   0,  0, 1, nullptr },\n    };\n\n    for(const event_repeat_data &d : data)\n    {\n      snprintf(dest, sizeof(dest), \"%s\", d.base);\n      intake_set_length(&intk, strlen(dest));\n      intake_set_pos(&intk, d.start_pos);\n\n      result = intake_apply_event_fixed(sub, INTK_CLEAR, 0, 0, nullptr);\n      ASSERTEQ(result, true, \"%s @ %d\", d.base, d.start_pos);\n      ASSERTCMP(dest, \"\", \"%s @ %d\", d.base, d.start_pos);\n      ASSERTEQ(intk.current_length, 0, \"%s @ %d\", d.base, d.start_pos);\n      ASSERTEQ(intk.pos, d.new_pos, \"%s @ %d\", d.base, d.start_pos);\n    }\n  }\n\n  SECTION(INTK_INSERT_BLOCK)\n  {\n    for(const event_input_data &d : insert_data)\n    {\n      snprintf(dest, sizeof(dest), \"%s\", d.base);\n      intake_set_length(&intk, strlen(dest));\n      intake_set_pos(&intk, d.start_pos);\n\n      int len = strlen(d.input);\n      result = intake_apply_event_fixed(sub, INTK_INSERT_BLOCK, intk.pos + len, len, d.input);\n      ASSERTEQ(result, true, \"%s\", d.expected);\n      ASSERTCMP(dest, d.expected, \"\");\n    }\n    // Reject null data...\n    result = intake_apply_event_fixed(sub, INTK_INSERT_BLOCK, intk.pos, 0, nullptr);\n    ASSERTEQ(result, false, \"\");\n  }\n\n  SECTION(INTK_OVERWRITE_BLOCK)\n  {\n    for(const event_input_data &d : overwrite_data)\n    {\n      snprintf(dest, sizeof(dest), \"%s\", d.base);\n      intake_set_length(&intk, strlen(dest));\n      intake_set_pos(&intk, d.start_pos);\n\n      int len = strlen(d.input);\n      result = intake_apply_event_fixed(sub, INTK_OVERWRITE_BLOCK, intk.pos + len, len, d.input);\n      ASSERTEQ(result, true, \"%s\", d.expected);\n      ASSERTCMP(dest, d.expected, \"\");\n    }\n    // Reject null data...\n    result = intake_apply_event_fixed(sub, INTK_OVERWRITE_BLOCK, intk.pos, 0, nullptr);\n    ASSERTEQ(result, false, \"\");\n  }\n\n  SECTION(intake_input_string)\n  {\n    /**\n     * Test the intake_input_string function with default intake event handling.\n     * This should mostly just invoke INTK_INSERT_BLOCK/INTK_OVERWRITE_BLOCK.\n     */\n    for(const input_string_data &d : input_data)\n    {\n      snprintf(dest, sizeof(dest), \"%s\", d.base);\n      intake_set_length(&intk, strlen(dest));\n      intake_set_pos(&intk, d.start_pos);\n      intake_set_insert(d.insert_on);\n\n      const char *ret = intake_input_string(sub, d.input, d.linebreak_char);\n      if(d.retval >= 0)\n      {\n        ASSERT(ret != nullptr, \"%s\", d.expected);\n        ASSERTEQ((ssize_t)(ret - d.input), d.retval, \"%s\", d.expected);\n      }\n      else\n        ASSERTEQ(ret, nullptr, \"%s\", d.expected);\n\n      ASSERTCMP(dest, d.expected, \"\");\n    }\n  }\n}\n\nstruct event_cb_data\n{\n  const char *expected;\n  int *pos_external;\n  int call_count;\n};\n\nstatic boolean event_callback(void *priv, subcontext *sub, enum intake_event_type type,\n int old_pos, int new_pos, int value, const char *data)\n{\n  struct intake_subcontext *intk = reinterpret_cast<struct intake_subcontext *>(sub);\n  event_cb_data *d = reinterpret_cast<event_cb_data *>(priv);\n\n  ASSERTEQ(old_pos, intk->pos, \"%s\", d->expected);\n  if(intk->dest)\n    ASSERTCMP(intk->dest, d->expected, \"\");\n\n  if(d->pos_external)\n    *(d->pos_external) = new_pos;\n\n  d->call_count++;\n  return true;\n}\n\n/**\n * Just make sure that the event callback is called and that the buffer (if\n * present) and intk->pos have not been modified at the time the callback is\n * called.\n */\nUNITTEST(EventCallback)\n{\n#define na 0\n  static constexpr char dummy_data[] = \"whatever\";\n  static constexpr int dummy_data_len = arraysize(dummy_data) - 1;\n\n  static const event_partial_data data[] =\n  {\n    { INTK_NO_EVENT,        \"no event\",       0, 0, na },\n    { INTK_MOVE,            \"move\",           0, 4, na },\n    { INTK_MOVE_WORDS,      \"move words\",     0, 0, 2 },\n    { INTK_MOVE_WORDS,      \"move words 2\",   12, 12, -2 },\n    { INTK_INSERT,          \"insert\",         3, 4, '$' },\n    { INTK_OVERWRITE,       \"overwrite\",      4, 5, 'W' },\n    { INTK_DELETE,          \"delete\",         5, 5, 1 },\n    { INTK_BACKSPACE,       \"backspace\",      9, 8, 1 },\n    { INTK_BACKSPACE_WORDS, \"bkspace words\",  13, 13, 1 },\n    { INTK_CLEAR,           \"clear string\",   0, 0, na },\n    { INTK_INSERT_BLOCK,    \"insertblock\",    0, dummy_data_len, dummy_data_len },\n    { INTK_OVERWRITE_BLOCK, \"overwriteblock\", 0, dummy_data_len, dummy_data_len },\n  };\n\n  struct intake_subcontext intk{};\n  subcontext *sub = reinterpret_cast<subcontext *>(&intk);\n  char dest[256];\n  int pos_external;\n\n  intk.dest = dest;\n  intk.max_length = 240;\n  intk.current_length = 100;\n  intk.event_cb = event_callback;\n  intk.pos_external = &pos_external;\n\n  SECTION(intake_event_ext_no_dest)\n  {\n    /**\n     * Call intake_event_ext for an intake with no destination buffer.\n     * Position and length modifications in such an intake will be determined\n     * solely by those properties, max_length, and external pointers.\n     */\n    intk.dest = nullptr;\n\n    for(const event_partial_data &d : data)\n    {\n      event_cb_data priv = { d.base, &pos_external, 0 };\n      intk.event_priv = &priv;\n      intk.pos = d.old_pos;\n      intake_event_ext(&intk, d.type, d.old_pos, d.new_pos, d.value, dummy_data);\n      ASSERTEQ(priv.call_count, 1, \"%s\", d.base);\n      ASSERTEQ(intk.pos, d.new_pos, \"%s\", d.base);\n    }\n  }\n\n  SECTION(intake_event_ext_dest)\n  {\n    /**\n     * Call intake_event_ext for an intake with a destination buffer.\n     * This is entirely just to make sure the destination buffer isn't modified.\n     * Note the new position check after calling intake_event_ext relies on the\n     * current length above being set beyond the actual string length (since\n     * no modification of the buffer is ever actually performed).\n     */\n    for(const event_partial_data &d : data)\n    {\n      event_cb_data priv = { d.base, &pos_external, 0 };\n      intk.event_priv = &priv;\n      intk.pos = d.old_pos;\n      snprintf(dest, sizeof(dest), \"%s\", d.base);\n      intake_event_ext(&intk, d.type, d.old_pos, d.new_pos, d.value, dummy_data);\n      ASSERTEQ(priv.call_count, 1, \"%s\", d.base);\n      ASSERTEQ(intk.pos, d.new_pos, \"%s\", d.base);\n    }\n  }\n\n  SECTION(intake_input_string)\n  {\n    /**\n     * Test the intake_input_string function with an event callback.\n     */\n    intk.dest = nullptr;\n\n    for(const input_string_data &d : input_data)\n    {\n      event_cb_data priv = { d.expected, &pos_external, 0 };\n      intk.event_priv = &priv;\n\n      intake_set_length(&intk, strlen(d.base));\n      intake_set_pos(&intk, d.start_pos);\n      intake_set_insert(d.insert_on);\n\n      const char *ret = intake_input_string(sub, d.input, d.linebreak_char);\n      if(strlen(d.input))\n        ASSERTEQ(priv.call_count, 1, \"%s\", d.base);\n\n      if(d.retval >= 0)\n      {\n        ASSERT(ret != nullptr, \"%s\", d.expected);\n        ASSERTEQ((ssize_t)(ret - d.input), d.retval, \"%s\", d.expected);\n      }\n      else\n        ASSERTEQ(ret, nullptr, \"%s\", d.expected);\n    }\n  }\n}\n"
  },
  {
    "path": "unit/io/bitstream.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Test the alignment of several name fields in structs.\n * If enough inputs into memcmp and memcasecmp are consistently 4 aligned,\n * this allows faster comparisons to be performed and also allows some code\n * to assume alignment will be the general case.\n */\n\n#include \"../Unit.hpp\"\n#include \"../../src/io/bitstream.h\"\n\n#include <stdio.h>\n\nstruct read_sequence\n{\n  uint16_t length;\n  int expected_value;\n};\n\n/**\n * Test reading large numbers of bits similar to how LZW codes are stored in\n * Shrink compression. Shrink stops at 13-bit codes but test up to 16 anyway.\n */\nUNITTEST(UnShrinkLike)\n{\n  static const unsigned char data[] =\n  {\n    // 9 bits\n    // 1 2 3 4 5 6 255 511\n    0x01, 0x04, 0x0C, 0x20, 0x50, 0xC0, 0xC0, 0xBF, 0xFF,\n\n    // 12 bits\n    // 4080 255 2730 1365 0 4095\n    0xF0, 0xFF, 0x0F, 0xAA, 0x5A, 0x55, 0x00, 0xF0, 0xFF,\n\n    // 16 bits\n    // 256 1 43497 60659\n    0xE9, 0xA9, 0x00, 0x01, 0x01, 0x00, 0xF3, 0xEC,\n  };\n  static const int expected9[] =\n  {\n    1, 2, 3, 4, 5, 6, 255, 511,\n  };\n  static const int expected12[] =\n  {\n    4080, 255, 2730, 1365, 0, 4095,\n  };\n  static const int expected16[] =\n  {\n    43497, 256, 1, 60659,\n  };\n\n  struct bitstream b{};\n  int out;\n  int i;\n\n  b.input = data;\n  b.input_left = arraysize(data);\n\n  for(i = 0; i < arraysize(expected9); i++)\n  {\n    out = BS_READ(&b, 9);\n    ASSERTEQ(out, expected9[i], \"code size 9, position %d\", i);\n  }\n\n  for(i = 0; i < arraysize(expected12); i++)\n  {\n    out = BS_READ(&b, 12);\n    ASSERTEQ(out, expected12[i], \"code size 12, position %d\", i);\n  }\n\n  for(i = 0; i < arraysize(expected16); i++)\n  {\n    out = BS_READ(&b, 16);\n    ASSERTEQ(out, expected16[i], \"code size 16, position %d\", i);\n  }\n\n  ASSERTEQ(b.input, nullptr, \"\");\n  ASSERTEQ(b.input_left, 0, \"\");\n\n  // NOTE: this won't always be true as streams are byte-aligned, but this\n  // test data has been designed so that this should be true.\n  ASSERTEQ(b.buf_left, 0, \"\");\n}\n\n/**\n * Test reading smaller numbers of bits, like might be expected from an\n * Implode compression stream.\n */\nUNITTEST(ExplodeLike)\n{\n  static const unsigned char data[] =\n  {\n    0xFF, 0x12, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD,\n    0xFE, 0x02, 0x5A, 0x1F, 0x24,\n    0x62, 0x14, 0xA5, 0x3F,\n    0xFF, 0x05, 0xF8, 0xA9,\n  };\n  static constexpr const read_sequence sequence[] =\n  {\n    { 8, 0xFF },\n    { 8, 0x12 },\n    { 8, 0x23 },\n    { 8, 0x45 },\n    { 8, 0x67 },\n    { 8, 0x89 },\n    { 8, 0xAB },\n    { 8, 0xCD },\n\n    { 1, 0 }, { 8, 0x7F, },\n    { 1, 1 }, { 7, 0x00, }, { 1, 1 }, { 1, 0 }, { 1, 1 },\n      { 1, 1 }, { 1, 0 }, { 1, 1 }, { 1, 0 },\n    { 1, 1 }, { 7, 0x0F, },\n    { 1, 0 }, { 7, 0x12, },\n\n    { 8, 0x62 },\n    { 8, 0x14 },\n    { 8, 0xA5 },\n    { 8, 0x3F },\n\n    { 1, 1 }, { 8, 0xFF }, { 1, 0 }, { 1, 1 }, { 1, 0 }, { 8, 0x80 },\n    { 1, 1 }, { 8, 0x4F }, { 1, 1 }, { 1, 0 }, { 1, 1 },\n  };\n\n  struct bitstream b{};\n  int out;\n  int i;\n\n  b.input = data;\n  b.input_left = arraysize(data);\n\n  for(i = 0; i < arraysize(sequence); i++)\n  {\n    out = BS_READ(&b, sequence[i].length);\n    ASSERTEQ(out, sequence[i].expected_value, \"position %d, %u bit(s)\", i,\n     sequence[i].length);\n  }\n\n  ASSERTEQ(b.input, nullptr, \"\");\n  ASSERTEQ(b.input_left, 0, \"\");\n\n  // NOTE: this won't always be true as streams are byte-aligned, but this\n  // test data has been designed so that this should be true.\n  ASSERTEQ(b.buf_left, 0, \"\");\n}\n\n/**\n * Make sure out-of-bounds reads always return EOF.\n */\nUNITTEST(OutOfBounds)\n{\n  static const unsigned char data[][8] =\n  {\n    {\n      0xFF, 0x00, 0xAB, 0x00, 0x0B, 0xB0, 0x00, 0x00,\n    },\n    {\n      0x15, 0x60, 0x55, 0xAA, 0x11, 0x22, 0x33, 0x44,\n    },\n    {\n      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    },\n    {\n      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n    },\n  };\n  static constexpr const read_sequence sequence[][16] =\n  {\n    {\n      { 16, 0x00FF },\n      { 16, 0x00AB },\n      { 16, 0xB00B },\n      { 16, 0x0000 },\n      { 16, EOF    },\n      { 16, EOF    },\n    },\n    {\n      { 8, 0x15 },\n      { 8, 0x60 },\n      { 8, 0x55 },\n      { 8, 0xAA },\n      { 8, 0x11 },\n      { 8, 0x22 },\n      { 8, 0x33 },\n      { 8, 0x44 },\n      { 8, EOF  },\n      { 8, EOF  },\n    },\n    {\n      { 13, 0x0000 },\n      { 13, 0x0000 },\n      { 13, 0x0000 },\n      { 13, 0x0000 },\n      { 13, EOF },\n      { 13, EOF },\n    },\n    {\n      { 5, 0x1F },\n      { 5, 0x1F },\n      { 5, 0x1F },\n      { 5, 0x1F },\n      { 5, 0x1F },\n      { 5, 0x1F },\n      { 5, 0x1F },\n      { 5, 0x1F },\n      { 5, 0x1F },\n      { 5, 0x1F },\n      { 5, 0x1F },\n      { 5, 0x1F },\n      { 5, EOF  },\n      { 5, EOF  },\n    },\n  };\n  samesize(data, sequence);\n\n  int out;\n  int i;\n  int j;\n\n  for(i = 0; i < arraysize(sequence); i++)\n  {\n    struct bitstream b{};\n\n    b.input = data[i];\n    b.input_left = arraysize(data[i]);\n\n    for(j = 0; i < arraysize(sequence[i]) && sequence[i][j].length; j++)\n    {\n      out = BS_READ(&b, sequence[i][j].length);\n      ASSERTEQ(out, sequence[i][j].expected_value,\n       \"array %d, position %d, %u bit(s)\", i, j, sequence[i][j].length);\n    }\n\n    ASSERTEQ(b.input, nullptr, \"\");\n    ASSERTEQ(b.input_left, 0, \"\");\n\n    // NOTE: may be bits left in this test, so don't try to test it...\n  }\n}\n"
  },
  {
    "path": "unit/io/data/dch1.txt",
    "content": "_3 May. Bistritz._--Left Munich at 8:35 P. M., on 1st May, arriving at\nVienna early next morning; should have arrived at 6:46, but train was an\nhour late. Buda-Pesth seems a wonderful place, from the glimpse which I\ngot of it from the train and the little I could walk through the\nstreets. I feared to go very far from the station, as we had arrived\nlate and would start as near the correct time as possible. The\nimpression I had was that we were leaving the West and entering the\nEast; the most western of splendid bridges over the Danube, which is\nhere of noble width and depth, took us among the traditions of Turkish\nrule.\n\nWe left in pretty good time, and came after nightfall to Klausenburgh.\nHere I stopped for the night at the Hotel Royale. I had for dinner, or\nrather supper, a chicken done up some way with red pepper, which was\nvery good but thirsty. (_Mem._, get recipe for Mina.) I asked the\nwaiter, and he said it was called \"paprika hendl,\" and that, as it was a\nnational dish, I should be able to get it anywhere along the\nCarpathians. I found my smattering of German very useful here; indeed, I\ndon't know how I should be able to get on without it.\n\nHaving had some time at my disposal when in London, I had visited the\nBritish Museum, and made search among the books and maps in the library\nregarding Transylvania; it had struck me that some foreknowledge of the\ncountry could hardly fail to have some importance in dealing with a\nnobleman of that country. I find that the district he named is in the\nextreme east of the country, just on the borders of three states,\nTransylvania, Moldavia and Bukovina, in the midst of the Carpathian\nmountains; one of the wildest and least known portions of Europe. I was\nnot able to light on any map or work giving the exact locality of the\nCastle Dracula, as there are no maps of this country as yet to compare\nwith our own Ordnance Survey maps; but I found that Bistritz, the post\ntown named by Count Dracula, is a fairly well-known place. I shall enter\nhere some of my notes, as they may refresh my memory when I talk over my\ntravels with Mina.\n\nIn the population of Transylvania there are four distinct nationalities:\nSaxons in the South, and mixed with them the Wallachs, who are the\ndescendants of the Dacians; Magyars in the West, and Szekelys in the\nEast and North. I am going among the latter, who claim to be descended\nfrom Attila and the Huns. This may be so, for when the Magyars conquered\nthe country in the eleventh century they found the Huns settled in it. I\nread that every known superstition in the world is gathered into the\nhorseshoe of the Carpathians, as if it were the centre of some sort of\nimaginative whirlpool; if so my stay may be very interesting. (_Mem._, I\nmust ask the Count all about them.)\n\nI did not sleep well, though my bed was comfortable enough, for I had\nall sorts of queer dreams. There was a dog howling all night under my\nwindow, which may have had something to do with it; or it may have been\nthe paprika, for I had to drink up all the water in my carafe, and was\nstill thirsty. Towards morning I slept and was wakened by the continuous\nknocking at my door, so I guess I must have been sleeping soundly then.\nI had for breakfast more paprika, and a sort of porridge of maize flour\nwhich they said was \"mamaliga,\" and egg-plant stuffed with forcemeat, a\nvery excellent dish, which they call \"impletata.\" (_Mem._, get recipe\nfor this also.) I had to hurry breakfast, for the train started a little\nbefore eight, or rather it ought to have done so, for after rushing to\nthe station at 7:30 I had to sit in the carriage for more than an hour\nbefore we began to move. It seems to me that the further east you go the\nmore unpunctual are the trains. What ought they to be in China?\n\nAll day long we seemed to dawdle through a country which was full of\nbeauty of every kind. Sometimes we saw little towns or castles on the\ntop of steep hills such as we see in old missals; sometimes we ran by\nrivers and streams which seemed from the wide stony margin on each side\nof them to be subject to great floods. It takes a lot of water, and\nrunning strong, to sweep the outside edge of a river clear. At every\nstation there were groups of people, sometimes crowds, and in all sorts\nof attire. Some of them were just like the peasants at home or those I\nsaw coming through France and Germany, with short jackets and round hats\nand home-made trousers; but others were very picturesque. The women\nlooked pretty, except when you got near them, but they were very clumsy\nabout the waist. They had all full white sleeves of some kind or other,\nand most of them had big belts with a lot of strips of something\nfluttering from them like the dresses in a ballet, but of course there\nwere petticoats under them. The strangest figures we saw were the\nSlovaks, who were more barbarian than the rest, with their big cow-boy\nhats, great baggy dirty-white trousers, white linen shirts, and enormous\nheavy leather belts, nearly a foot wide, all studded over with brass\nnails. They wore high boots, with their trousers tucked into them, and\nhad long black hair and heavy black moustaches. They are very\npicturesque, but do not look prepossessing. On the stage they would be\nset down at once as some old Oriental band of brigands. They are,\nhowever, I am told, very harmless and rather wanting in natural\nself-assertion.\n\nIt was on the dark side of twilight when we got to Bistritz, which is a\nvery interesting old place. Being practically on the frontier--for the\nBorgo Pass leads from it into Bukovina--it has had a very stormy\nexistence, and it certainly shows marks of it. Fifty years ago a series\nof great fires took place, which made terrible havoc on five separate\noccasions. At the very beginning of the seventeenth century it underwent\na siege of three weeks and lost 13,000 people, the casualties of war\nproper being assisted by famine and disease.\n\nCount Dracula had directed me to go to the Golden Krone Hotel, which I\nfound, to my great delight, to be thoroughly old-fashioned, for of\ncourse I wanted to see all I could of the ways of the country. I was\nevidently expected, for when I got near the door I faced a\ncheery-looking elderly woman in the usual peasant dress--white\nundergarment with long double apron, front, and back, of coloured stuff\nfitting almost too tight for modesty. When I came close she bowed and\nsaid, \"The Herr Englishman?\" \"Yes,\" I said, \"Jonathan Harker.\" She\nsmiled, and gave some message to an elderly man in white shirt-sleeves,\nwho had followed her to the door. He went, but immediately returned with\na letter:--\n\n     \"My Friend.--Welcome to the Carpathians. I am anxiously expecting\n     you. Sleep well to-night. At three to-morrow the diligence will\n     start for Bukovina; a place on it is kept for you. At the Borgo\n     Pass my carriage will await you and will bring you to me. I trust\n     that your journey from London has been a happy one, and that you\n     will enjoy your stay in my beautiful land.\n\n\"Your friend,\n\n\"DRACULA.\"\n"
  },
  {
    "path": "unit/io/data/zip64/zip64gen.cpp",
    "content": "#if 0\ngcc -g -Wall -Wextra -Wno-unused-parameter -fsanitize=address,undefined zip64gen.cpp -ozip64gen\nexit 0\n#endif\n\n#include <stdint.h>\n#include <stdio.h>\n\n#include \"../../../../src/io/memfile.h\"\n\nenum VariantFlags\n{\n  // File headers\n  DataDescriptor    = (1 << 0),\n  Local64           = (1 << 1),\n  Uncompressed64    = (1 << 2),\n  Compressed64      = (1 << 3),\n  Offset64          = (1 << 4),\n  // EOCD\n  Records64         = (1 << 5),\n  DirectorySize64   = (1 << 6),\n  DirectoryOffset64 = (1 << 7),\n  // Masks\n  Central64         = Uncompressed64|Compressed64|Offset64,\n  EOCD64            = Records64|DirectorySize64|DirectoryOffset64,\n};\n\n\nstatic constexpr const char contents[] =\n  \"1234567890 1234567890 1234567890\\r\\n\"\n  \"1234567890 1234567890 1234567890\\r\\n\"\n  \"1234567890 1234567890 1234567890\";\nstatic constexpr uint32_t content_size = 100;\nstatic constexpr uint32_t content_crc = 0xE3046765;\n\n\nstatic void local(int flags, FILE *out)\n{\n  uint8_t buf[30 + 4 + 20]{};\n  struct memfile mf;\n\n  mfopen_wr(buf, sizeof(buf), &mf);\n\n  mfwrite(\"PK\\x03\\x04\", 4, 1, &mf);\n  mfputw((flags & Local64) ? 45 : 20, &mf);\n  mfputw((flags & DataDescriptor) ? 0x0008 : 0x0000, &mf);\n  mfputw(0, &mf);   // Method\n  mfputud(0, &mf);  // Date and time\n\n  // CRC\n  if(flags & DataDescriptor)\n    mfputud(0, &mf);\n  else\n    mfputud(content_crc, &mf);\n\n  // Compressed, uncompressed\n  if(flags & Local64)\n  {\n    mfputud(0xffffffff, &mf);\n    mfputud(0xffffffff, &mf);\n  }\n  else\n  {\n    if(flags & DataDescriptor)\n    {\n      mfputud(0, &mf);\n      mfputud(0, &mf);\n    }\n    else\n    {\n      mfputud(content_size, &mf);\n      mfputud(content_size, &mf);\n    }\n  }\n\n  mfputw(4, &mf); // Filename\n  mfputw((flags & Local64) ? 20 : 0, &mf); // Extra\n  mfwrite(\"test\", 4, 1, &mf);\n\n  if(flags & Local64)\n  {\n    mfputw(0x0001, &mf);\n    mfputw(16, &mf);\n\n    if(flags & DataDescriptor)\n    {\n      mfputuq(0, &mf);\n      mfputuq(0, &mf);\n    }\n    else\n    {\n      mfputuq(content_size, &mf);\n      mfputuq(content_size, &mf);\n    }\n  }\n  fwrite(buf, mftell(&mf), 1, out);\n}\n\nstatic void descriptor(int flags, FILE *out)\n{\n  uint8_t buf[20];\n  struct memfile mf;\n\n  mfopen_wr(buf, sizeof(buf), &mf);\n\n  mfputud(content_crc, &mf);\n\n  if(flags & Local64)\n  {\n    mfputuq(content_size, &mf);\n    mfputuq(content_size, &mf);\n  }\n  else\n  {\n    mfputud(content_size, &mf);\n    mfputud(content_size, &mf);\n  }\n\n  fwrite(buf, mftell(&mf), 1, out);\n}\n\n\nstatic void central(int flags, FILE *out)\n{\n  uint8_t buf[46 + 4 + 32]{};\n  struct memfile mf;\n  size_t sz = 0;\n\n  mfopen_wr(buf, sizeof(buf), &mf);\n\n  mfwrite(\"PK\\x01\\x02\", 4, 1, &mf);\n  mfputw((flags & Central64) ? 45 : 20, &mf);\n  mfputw((flags & Central64) ? 45 : 20, &mf);\n  mfputw((flags & DataDescriptor) ? 0x0008 : 0x0000, &mf);\n  mfputw(0, &mf);   // Method\n  mfputud(0, &mf);  // Date and time\n  mfputud(content_crc, &mf);\n\n  // Compressed\n  if(flags & Compressed64)\n  {\n    mfputud(0xffffffff, &mf);\n    sz += 8;\n  }\n  else\n    mfputud(content_size, &mf);\n\n  // Uncompressed\n  if(flags & Uncompressed64)\n  {\n    mfputud(0xffffffff, &mf);\n    sz += 8;\n  }\n  else\n    mfputud(content_size, &mf);\n\n  if(flags & Offset64)\n    sz += 8;\n\n  mfputw(4, &mf); // Filename\n  mfputw((flags & Central64) ? sz + 4 : 0, &mf); // Extra\n  mfputw(0, &mf); // Comment\n  mfputw(0, &mf); // Disk number start\n  mfputw(0, &mf); // Internal file attributes\n  mfputud(0, &mf); // External file attributes\n\n  // Offset of local header\n  if(flags & Offset64)\n    mfputud(0xffffffff, &mf);\n  else\n    mfputud(0, &mf);\n\n  mfwrite(\"test\", 4, 1, &mf);\n\n  if(flags & Central64)\n  {\n    mfputw(0x0001, &mf);\n    mfputw(sz, &mf);\n\n    if(flags & Uncompressed64)\n      mfputuq(content_size, &mf);\n    if(flags & Compressed64)\n      mfputuq(content_size, &mf);\n    if(flags & Offset64)\n      mfputuq(0, &mf);\n  }\n  fwrite(buf, mftell(&mf), 1, out);\n}\n\n\nstatic void eocd64(int flags, size_t offset, size_t size, FILE *out)\n{\n  uint8_t buf[56];\n  struct memfile mf;\n\n  mfopen_wr(buf, sizeof(buf), &mf);\n\n  mfwrite(\"PK\\x06\\x06\", 4, 1, &mf);\n  mfputuq(44, &mf);\n  mfputw(45, &mf);\n  mfputw(45, &mf);\n  mfputud(0, &mf);\n  mfputud(0, &mf);\n\n  // Despite the spec implying the EOCD64 fields are only loaded\n  // if a maximum value is found in the corresponding normal field,\n  // PKZIP 6.0, Info-ZIP, and 7-Zip unconditionally replace all\n  // fields with those from the EOCD64, so write them all.\n\n  mfputuq(1, &mf); // Records in volume\n  mfputuq(1, &mf); // Total records\n\n  mfputuq(size, &mf); // Size of central directory\n  mfputuq(offset, &mf); // Offset of central directory in current volume\n\n  fwrite(buf, mftell(&mf), 1, out);\n}\n\nstatic void locator64(int flags, size_t offset, FILE *out)\n{\n  uint8_t buf[20];\n  struct memfile mf;\n\n  mfopen_wr(buf, sizeof(buf), &mf);\n\n  mfwrite(\"PK\\x06\\x07\", 4, 1, &mf);\n  mfputud(0, &mf);\n  mfputuq(offset, &mf);\n  mfputud(1, &mf);\n\n  fwrite(buf, mftell(&mf), 1, out);\n}\n\nstatic void eocd(int flags, size_t offset, size_t size, FILE *out)\n{\n  uint8_t buf[22];\n  struct memfile mf;\n\n  mfopen_wr(buf, sizeof(buf), &mf);\n\n  mfwrite(\"PK\\x05\\x06\", 4, 1, &mf);\n  mfputw(0, &mf);\n  mfputw(0, &mf);\n\n  if(flags & Records64)\n  {\n    mfputw(0xffff, &mf);\n    mfputw(0xffff, &mf);\n  }\n  else\n  {\n    mfputw(1, &mf);\n    mfputw(1, &mf);\n  }\n\n  if(flags & DirectorySize64)\n    mfputud(0xfffffffful, &mf);\n  else\n    mfputud(size, &mf);\n\n  if(flags & DirectoryOffset64)\n    mfputud(0xfffffffful, &mf);\n  else\n    mfputud(offset, &mf);\n\n  mfputw(0, &mf);\n\n  fwrite(buf, mftell(&mf), 1, out);\n}\n\n\nstatic void gen(const char *dest, int flags)\n{\n  FILE *out = fopen_unsafe(dest, \"wb\");\n\n  local(flags, out);\n  fwrite(contents, content_size, 1, out);\n  if(flags & DataDescriptor)\n    descriptor(flags, out);\n\n  long directory_offset = ftell(out);\n  central(flags, out);\n  long eocd64_offset = ftell(out);\n  long directory_size = eocd64_offset - directory_offset;\n\n  if(flags & EOCD64)\n  {\n    eocd64(flags, directory_offset, directory_size, out);\n    locator64(flags, eocd64_offset, out);\n  }\n  eocd(flags, directory_offset, directory_size, out);\n}\n\nint main()\n{\n  gen(\"local64.zip\",    Local64);\n  gen(\"localcd64.zip\",  Local64|Central64);\n  gen(\"localdd64.zip\",  Local64|DataDescriptor);\n  gen(\"localcddd64.zip\",Local64|DataDescriptor|Central64);\n  gen(\"cd64.zip\",       Central64);\n  gen(\"cduncomp64.zip\", Uncompressed64);\n  gen(\"cdcomp64.zip\",   Compressed64);\n  gen(\"cdoffset64.zip\", Offset64);\n\n  gen(\"eocd64.zip\",     EOCD64);\n  gen(\"eocd64rc.zip\",   Records64);\n  gen(\"eocd64sz.zip\",   DirectorySize64);\n  gen(\"eocd64of.zip\",   DirectoryOffset64);\n\n  gen(\"all64.zip\",      EOCD64|Local64|Central64);\n  gen(\"alldd64.zip\",    EOCD64|Local64|DataDescriptor|Central64);\n  return 0;\n}\n"
  },
  {
    "path": "unit/io/memfile.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../Unit.hpp\"\n#include \"../../src/io/memfile.h\"\n\n#include <limits.h>\n#include <stdint.h>\n#include <stdlib.h>\n\nUNITTEST(mfopen)\n{\n  unsigned char buffer[16];\n\n  SECTION(mfopen)\n  {\n    struct memfile mf;\n\n    mfopen(buffer, sizeof(buffer), &mf);\n    ASSERTEQ(mf.start, buffer, \"\");\n    ASSERTEQ(mf.current, buffer, \"\");\n    ASSERTEQ(mf.end, mf.start + sizeof(buffer), \"\");\n    ASSERTEQ(mf.alloc, false, \"\");\n    ASSERTEQ(mf.seek_past_end, false, \"\");\n    ASSERTEQ(mf.is_write, false, \"\");\n  }\n\n  SECTION(mfopen_wr)\n  {\n    struct memfile mf;\n\n    mfopen_wr(buffer, sizeof(buffer), &mf);\n    ASSERTEQ(mf.start, buffer, \"\");\n    ASSERTEQ(mf.current, buffer, \"\");\n    ASSERTEQ(mf.end, mf.start + sizeof(buffer), \"\");\n    ASSERTEQ(mf.alloc, false, \"\");\n    ASSERTEQ(mf.seek_past_end, false, \"\");\n    ASSERTEQ(mf.is_write, true, \"\");\n  }\n\n  SECTION(mfopen_alloc)\n  {\n    struct memfile *mf;\n\n    mf = mfopen_alloc(buffer, sizeof(buffer));\n    ASSERT(mf, \"\");\n    ASSERTEQ(mf->start, buffer, \"\");\n    ASSERTEQ(mf->current, buffer, \"\");\n    ASSERTEQ(mf->end, buffer + sizeof(buffer), \"\");\n    ASSERTEQ(mf->alloc, true, \"\");\n    ASSERTEQ(mf->seek_past_end, false, \"\");\n    ASSERTEQ(mf->is_write, false, \"\");\n\n    int ret = mf_alloc_free(mf);\n    ASSERTEQ(ret, 0, \"\");\n  }\n\n  SECTION(mf_alloc_free_on_static_memfile)\n  {\n    struct memfile mf;\n    mfopen(buffer, sizeof(buffer), &mf);\n\n    int ret = mf_alloc_free(&mf);\n    ASSERTEQ(ret, -1, \"\");\n    ASSERTEQ(mf.start, buffer, \"\");\n    ASSERTEQ(mf.current, buffer, \"\");\n    ASSERTEQ(mf.end, buffer + sizeof(buffer), \"\");\n  }\n}\n\nUNITTEST(mfsync)\n{\n  unsigned char buffers[4][32];\n  struct memfile mf;\n  int i;\n\n  void *buf;\n  size_t len;\n\n  for(i = 0; i < arraysize(buffers); i++)\n  {\n    mfopen(buffers[i], sizeof(buffers[i]), &mf);\n    ASSERTEQ(mf.start, buffers[i], \"%d\", i);\n    ASSERTEQ(mf.end, buffers[i] + sizeof(buffers[i]), \"%d\", i);\n\n    mfsync(&buf, &len, &mf);\n    ASSERTEQ(buf, (void *)buffers[i], \"%d\", i);\n    ASSERTEQ((int)len, sizeof(buffers[i]), \"%d\", i);\n\n    mfsync(&buf, nullptr, &mf);\n    ASSERTEQ(buf, (void *)buffers[i], \"%d\", i);\n\n    mfsync(nullptr, &len, &mf);\n    ASSERTEQ((int)len, sizeof(buffers[i]), \"%d\", i);\n\n    mfsync(nullptr, nullptr, &mf);\n  }\n}\n\nstruct buffer_data\n{\n  char *buffer;\n  size_t buffer_len;\n  boolean has0;\n  boolean has8;\n  boolean has16;\n  boolean has32;\n  boolean has64;\n  boolean has128;\n};\n\nUNITTEST(mfhasspace)\n{\n  char buffer8[8];\n  char buffera[16];\n  char bufferb[32];\n  char bufferc[64];\n  char bufferd[128];\n  char buffere[256];\n  struct buffer_data pairs[] =\n  {\n    { buffer8, sizeof(buffer8), true, true, false, false, false, false },\n    { buffera, sizeof(buffera), true, true, true,  false, false, false },\n    { bufferb, sizeof(bufferb), true, true, true,  true,  false, false },\n    { bufferc, sizeof(bufferc), true, true, true,  true,  true,  false },\n    { bufferd, sizeof(bufferd), true, true, true,  true,  true,  true  },\n    { buffere, sizeof(buffere), true, true, true,  true,  true,  true  },\n  };\n  struct memfile mf;\n  int i;\n\n  for(i = 0; i < arraysize(pairs); i++)\n  {\n    mfopen(pairs[i].buffer, pairs[i].buffer_len, &mf);\n    ASSERTEQ(pairs[i].has0,   mfhasspace(0, &mf), \"%d\", i);\n    ASSERTEQ(pairs[i].has8,   mfhasspace(8, &mf), \"%d\", i);\n    ASSERTEQ(pairs[i].has16,  mfhasspace(16, &mf), \"%d\", i);\n    ASSERTEQ(pairs[i].has32,  mfhasspace(32, &mf), \"%d\", i);\n    ASSERTEQ(pairs[i].has64,  mfhasspace(64, &mf), \"%d\", i);\n    ASSERTEQ(pairs[i].has128, mfhasspace(128, &mf), \"%d\", i);\n\n    mf.current = (mf.end - mf.start)/2 + mf.start;\n    ASSERTEQ(pairs[i].has16,  mfhasspace(8, &mf), \"%d\", i);\n    ASSERTEQ(pairs[i].has32,  mfhasspace(16, &mf), \"%d\", i);\n    ASSERTEQ(pairs[i].has64,  mfhasspace(32, &mf), \"%d\", i);\n    ASSERTEQ(pairs[i].has128, mfhasspace(64, &mf), \"%d\", i);\n\n    mf.current = mf.end;\n    ASSERTEQ(pairs[i].has0,   mfhasspace(0, &mf), \"%d\", i);\n  }\n\n  struct memfile tmp{};\n  ASSERTEQ(mfhasspace(0, &tmp), false, \"NULL memfile->current should return false\");\n}\n\nUNITTEST(mfmove)\n{\n  const int OFFSET = 96;\n  unsigned char buffera[128];\n  unsigned char bufferb[256];\n  unsigned char bufferc[64];\n  struct memfile mf;\n\n  static_assert(sizeof(bufferc) < OFFSET,\n   \"OFFSET should be larger than the size of bufferc.\");\n\n  mfopen_wr(buffera, sizeof(buffera), &mf);\n  mf.current = mf.start + 96;\n  ASSERTEQ(mf.current, buffera + 96, \"\");\n\n  mfmove(bufferb, sizeof(bufferb), &mf);\n  ASSERTEQ(mf.start, bufferb, \"\");\n  ASSERTEQ(mf.end, bufferb + sizeof(bufferb), \"\");\n  ASSERTEQ(mf.current, bufferb + 96, \"\");\n\n  mfmove(bufferc, sizeof(bufferc), &mf);\n  ASSERTEQ(mf.start, bufferc, \"\");\n  ASSERTEQ(mf.end, bufferc + sizeof(bufferc), \"\");\n  ASSERTEQ(mf.current, bufferc + sizeof(bufferc), \"\");\n}\n\nUNITTEST(mfresize)\n{\n  const int BUFSIZE = 128;\n  const int NEWSIZE = 256;\n  void *buf = malloc(BUFSIZE);\n  struct memfile mf;\n  size_t size;\n\n  ASSERT(buf, \"\");\n  mfopen(buf, BUFSIZE, &mf);\n  mfresize(NEWSIZE, &mf);\n  mfsync(&buf, &size, &mf);\n  ASSERTEQ(size, NEWSIZE, \"\");\n\n  mfresize(BUFSIZE, &mf);\n  mfsync(&buf, &size, &mf);\n  ASSERTEQ(size, BUFSIZE, \"\");\n\n  free(buf);\n}\n\nstruct seq\n{\n  long int position;\n  int next;\n  int retval;\n};\n\nUNITTEST(mfseek_mftell)\n{\n  unsigned char buffer[256];\n  struct memfile mf;\n  static const int offs_safe[] =\n  {\n    128,\n    256,\n    0,\n    32,\n    79,\n    84,\n    255,\n    1,\n    172,\n  };\n  static const int offs_unsafe[] =\n  {\n    -1,\n    -1000,\n    -128731282,\n  };\n  static const int offs_maybe_safe[] =\n  {\n    sizeof(buffer) + 1,\n    1024,\n    9999,\n  };\n  int ret;\n\n  mfopen_wr(buffer, sizeof(buffer), &mf);\n\n  SECTION(mftell)\n  {\n    for(int pos : offs_safe)\n    {\n      mf.current = mf.start + pos;\n      ASSERTEQ(mftell(&mf), pos, \"mftell safe %d\", pos);\n    }\n  }\n\n  SECTION(seek_set)\n  {\n    for(int pos : offs_safe)\n    {\n      ret = mfseek(&mf, pos, SEEK_SET);\n      ASSERTEQ(ret, 0, \"seek_set safe %d\", pos);\n      ASSERTEQ(mftell(&mf), pos, \"seek_set safe %d\", pos);\n    }\n\n    ASSERT(!mfseek(&mf, 129, SEEK_SET), \"\");\n\n    for(int pos : offs_unsafe)\n    {\n      ret = mfseek(&mf, pos, SEEK_SET);\n      ASSERTEQ(ret, -1, \"seek_set unsafe %d\", pos);\n      ASSERTEQ(mftell(&mf), 129, \"seek_set unsafe %d\", pos);\n    }\n\n    for(int pos : offs_maybe_safe)\n    {\n      ret = mfseek(&mf, pos, SEEK_SET);\n      ASSERTEQ(ret, -1, \"seek_set unsafe2 %d\", pos);\n      ASSERTEQ(mftell(&mf), 129, \"seek_set unsafe2 %d\", pos);\n    }\n  }\n\n  SECTION(seek_cur)\n  {\n    static const seq sequence[] =\n    {\n      {   0,   32,  0 },\n      {  32,  -17,  0 },\n      {  15,  200,  0 },\n      { 215,  -80,  0 },\n      { 135,   62,  0 },\n      { 197,  100, -1 },\n      { 197, -500, -1 },\n      { 197,    0,  0 }\n    };\n\n    for(const seq seq : sequence)\n    {\n      ASSERTEQ(mftell(&mf), seq.position, \"seek_cur sequence %ld->%d\", seq.position, seq.next);\n      ret = mfseek(&mf, seq.next, SEEK_CUR);\n      ASSERTEQ(ret, seq.retval, \"seek_cur sequence %ld->%d\", seq.position, seq.next);\n    }\n  }\n\n  SECTION(seek_end)\n  {\n    mfseek(&mf, 0, SEEK_END);\n    ASSERTEQ(mftell(&mf), sizeof(buffer), \"seek_end\");\n  }\n\n  SECTION(seek_past_end)\n  {\n    mf.seek_past_end = true;\n\n    for(int pos : offs_maybe_safe)\n    {\n      ret = mfseek(&mf, pos, SEEK_SET);\n      ASSERTEQ(ret, 0, \"seek_set past end %d\", pos);\n      ASSERTEQ(mftell(&mf), pos, \"seek_set past end %d\", pos);\n      ASSERTEQ(mfhasspace(1, &mf), false, \"seek_set past end %d\", pos);\n    }\n  }\n}\n\nUNITTEST(read_write)\n{\n  static const uint8_t data8[] =\n  {\n    0x00, 0x01, 0xFF, 0xFE, 0x7F, 0x53, 0xA3, 0xD8,\n    0xFF, 0x00, 0x12, 0x34, 0x45, 0x67, 0x89, 0xAB,\n  };\n  static const uint16_t data16[] =\n  {\n    0x0100, 0xFEFF, 0x537F, 0xD8A3,\n    0x00FF, 0x3412, 0x6745, 0xAB89,\n  };\n  static const int32_t data32s[] =\n  {\n    (int)(0xFEFF0100), (int)(0xD8A3537F),\n    (int)(0x341200FF), (int)(0xAB896745),\n  };\n  static const uint32_t data32u[] =\n  {\n    0xFEFF0100, 0xD8A3537F,\n    0x341200FF, 0xAB896745,\n  };\n  static const int64_t data64s[] =\n  {\n    static_cast<int64_t>(0xD8A3537FFEFF0100ULL),\n    static_cast<int64_t>(0xAB896745341200FFULL),\n  };\n  static const uint64_t data64u[] =\n  {\n    0xD8A3537FFEFF0100ULL,\n    0xAB896745341200FFULL,\n  };\n\n  const int SIZE = sizeof(data8);\n  uint8_t dest[SIZE * 2];\n  struct memfile mf;\n  int res;\n  int res2;\n  int i;\n\n  static_assert(arraysize(data16) * 2 == SIZE,\n   \"input array size doesn't match u16 output array size!\");\n\n  static_assert(arraysize(data32s) * 4 == SIZE,\n   \"input array size doesn't match s32 output array size!\");\n  static_assert(arraysize(data32u) * 4 == SIZE,\n   \"input array size doesn't match u32 output array size!\");\n\n  static_assert(arraysize(data64s) * 8 == SIZE,\n   \"input array size doesn't match s64 output array size!\");\n  static_assert(arraysize(data64u) * 8 == SIZE,\n   \"input array size doesn't match u64 output array size!\");\n\n  SECTION(mfgetc)\n  {\n    mfopen(data8, SIZE, &mf);\n\n    // NOTE: this function does not perform bounds checks since when using it\n    // it is assumed these have already been performed.\n    for(uint8_t value : data8)\n    {\n      uint8_t tmp = mfgetc(&mf);\n      ASSERTEQ(tmp, value, \"\");\n    }\n  }\n\n  SECTION(mfgetw)\n  {\n    // NOTE: this function does not perform bounds checks since when using it\n    // it is assumed these have already been performed.\n    mfopen(data8, SIZE, &mf);\n\n    for(uint16_t value : data16)\n    {\n      uint16_t tmp = mfgetw(&mf);\n      ASSERTEQ(tmp, value, \"\");\n    }\n  }\n\n  SECTION(mfgetd)\n  {\n    // NOTE: this function does not perform bounds checks since when using it\n    // it is assumed these have already been performed.\n    mfopen(data8, SIZE, &mf);\n\n    for(int32_t value : data32s)\n    {\n      int tmp = mfgetd(&mf);\n      ASSERTEQ(tmp, value, \"\");\n    }\n  }\n\n  SECTION(mfgetud)\n  {\n    // NOTE: this function does not perform bounds checks since when using it\n    // it is assumed these have already been performed.\n    mfopen(data8, SIZE, &mf);\n\n    for(uint32_t value : data32u)\n    {\n      uint32_t tmp = mfgetud(&mf);\n      ASSERTEQ(tmp, value, \"\");\n    }\n  }\n\n  SECTION(mfgetq)\n  {\n    // NOTE: this function does not perform bounds checks since when using it\n    // it is assumed these have already been performed.\n    mfopen(data8, SIZE, &mf);\n\n    for(int64_t value : data64s)\n    {\n      int64_t tmp = mfgetq(&mf);\n      ASSERTEQ(tmp, value, \"\");\n    }\n  }\n\n  SECTION(mfgetuq)\n  {\n    // NOTE: this function does not perform bounds checks since when using it\n    // it is assumed these have already been performed.\n    mfopen(data8, SIZE, &mf);\n\n    for(uint64_t value : data64u)\n    {\n      uint64_t tmp = mfgetuq(&mf);\n      ASSERTEQ(tmp, value, \"\");\n    }\n  }\n\n  SECTION(mfread)\n  {\n    mfopen(data8, SIZE, &mf);\n\n    res = mfread(dest, SIZE, 1, &mf);\n    ASSERTEQ(res, 1, \"read SIZE x 1\");\n    ASSERTMEM(data8, dest, SIZE, \"read SIZE x 1\");\n\n    mf.current = mf.start;\n    res = mfread(dest, 1, SIZE, &mf);\n    ASSERTEQ(res, SIZE, \"read 1 x SIZE\");\n    ASSERTMEM(data8, dest, SIZE, \"read 1 x SIZE\");\n\n    mf.current = mf.start;\n    res = mfread(dest, SIZE/2, 1, &mf);\n    res2 = mfread(dest + SIZE/2, 1, SIZE/2, &mf);\n    ASSERTEQ(res, 1, \"read SIZE/2\");\n    ASSERTEQ(res2, SIZE/2, \"read SIZE/2\");\n    ASSERTMEM(data8, dest, SIZE, \"read SIZE/2\");\n\n    mf.current = mf.start;\n    res = mfread(dest, SIZE, 2, &mf);\n    ASSERTEQ(res, 1, \"read SIZE x 2\");\n\n    mf.current = mf.start;\n    res = mfread(dest, 3, SIZE*2/3, &mf);\n    ASSERTEQ(res, SIZE/3, \"read 3 x (SIZE * 2/3), past end\");\n\n    res = mfread(dest, 1, SIZE - SIZE/3*3 + 1, &mf);\n    ASSERTEQ(res, SIZE - SIZE/3*3, \"read 1 x 2, past end\");\n\n    res = mfread(dest, 1, 1, &mf);\n    ASSERTEQ(res, 0, \"read 1, past end\");\n\n    // This shouldn't happen, but the end bounding should catch it anyway.\n    mf.current = mf.end + 1;\n    res = mfread(dest, 1, 1, &mf);\n    ASSERTEQ(res, 0, \"read 1, current past end\");\n\n    uint8_t noread = 0xef;\n    mf.current = mf.start;\n    res = mfread(&noread, 0, 1, &mf);\n    ASSERTEQ(res, 0, \"read 0 x 1\");\n    ASSERTEQ(noread, 0xef, \"read 0 x 1\");\n    ASSERTEQ(mf.start, mf.current, \"read 0 x 1\");\n\n    res = mfread(&noread, 1, 0, &mf);\n    ASSERTEQ(res, 0, \"read 1 x 0\");\n    ASSERTEQ(noread, 0xef, \"read 1 x 0\");\n    ASSERTEQ(mf.start, mf.current, \"read 1 x 0\");\n  }\n\n  SECTION(mfputc)\n  {\n    // NOTE: this function does not perform bounds checks since when using it\n    // it is assumed these have already been performed.\n    mfopen_wr(dest, sizeof(dest), &mf);\n\n    for(i = 0; i < arraysize(data8); i++)\n    {\n      res = mfputc(data8[i], &mf);\n      ASSERTEQ((unsigned)res, data8[i], \"\");\n      ASSERTEQ((unsigned)res, dest[i], \"\");\n    }\n  }\n\n  SECTION(mfputw)\n  {\n    // NOTE: this function does not perform bounds checks since when using it\n    // it is assumed these have already been performed.\n    mfopen_wr(dest, sizeof(dest), &mf);\n\n    for(i = 0; i < arraysize(data16); i++)\n    {\n      mfputw(data16[i], &mf);\n      ASSERTEQ((uint16_t)dest[i * 2 + 0], data16[i] & 0xFF, \"\");\n      ASSERTEQ((uint16_t)dest[i * 2 + 1], (data16[i] >> 8) & 0xFF, \"\");\n    }\n  }\n\n  SECTION(mfputd)\n  {\n    // NOTE: this function does not perform bounds checks since when using it\n    // it is assumed these have already been performed.\n    mfopen_wr(dest, sizeof(dest), &mf);\n\n    for(i = 0; i < arraysize(data32s); i++)\n    {\n      mfputd(data32s[i], &mf);\n      ASSERTEQ((int)dest[i * 4 + 0], data32s[i] & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 4 + 1], (data32s[i] >> 8) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 4 + 2], (data32s[i] >> 16) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 4 + 3], (data32s[i] >> 24) & 0xFF, \"\");\n    }\n  }\n\n  SECTION(mfputud)\n  {\n    // NOTE: this function does not perform bounds checks since when using it\n    // it is assumed these have already been performed.\n    mfopen_wr(dest, sizeof(dest), &mf);\n\n    for(i = 0; i < arraysize(data32u); i++)\n    {\n      mfputud(data32u[i], &mf);\n      ASSERTEQ((uint32_t)dest[i * 4 + 0], data32u[i] & 0xFF, \"\");\n      ASSERTEQ((uint32_t)dest[i * 4 + 1], (data32u[i] >> 8) & 0xFF, \"\");\n      ASSERTEQ((uint32_t)dest[i * 4 + 2], (data32u[i] >> 16) & 0xFF, \"\");\n      ASSERTEQ((uint32_t)dest[i * 4 + 3], (data32u[i] >> 24) & 0xFF, \"\");\n    }\n  }\n\n  SECTION(mfputq)\n  {\n    // NOTE: this function does not perform bounds checks since when using it\n    // it is assumed these have already been performed.\n    mfopen_wr(dest, sizeof(dest), &mf);\n\n    for(i = 0; i < arraysize(data64s); i++)\n    {\n      mfputq(data64s[i], &mf);\n      ASSERTEQ((int)dest[i * 8 + 0], data64s[i] & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 1], (data64s[i] >> 8) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 2], (data64s[i] >> 16) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 3], (data64s[i] >> 24) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 4], (data64s[i] >> 32) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 5], (data64s[i] >> 40) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 6], (data64s[i] >> 48) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 7], (data64s[i] >> 56) & 0xFF, \"\");\n    }\n  }\n\n  SECTION(mfputuq)\n  {\n    // NOTE: this function does not perform bounds checks since when using it\n    // it is assumed these have already been performed.\n    mfopen_wr(dest, sizeof(dest), &mf);\n\n    for(i = 0; i < arraysize(data64u); i++)\n    {\n      mfputq(data64u[i], &mf);\n      ASSERTEQ((int)dest[i * 8 + 0], data64u[i] & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 1], (data64u[i] >> 8) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 2], (data64u[i] >> 16) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 3], (data64u[i] >> 24) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 4], (data64u[i] >> 32) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 5], (data64u[i] >> 40) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 6], (data64u[i] >> 48) & 0xFF, \"\");\n      ASSERTEQ((int)dest[i * 8 + 7], (data64u[i] >> 56) & 0xFF, \"\");\n    }\n  }\n\n  SECTION(mfwrite)\n  {\n    mfopen_wr(dest, SIZE, &mf);\n\n    res = mfwrite(data8, SIZE, 1, &mf);\n    ASSERTEQ(res, 1, \"write SIZE x 1\");\n    ASSERTMEM(data8, dest, SIZE, \"write SIZE x 1\");\n\n    mf.current = mf.start;\n    res = mfwrite(data8, 1, SIZE, &mf);\n    ASSERTEQ(res, SIZE, \"write 1 x SIZE\");\n    ASSERTMEM(data8, dest, SIZE, \"write 1 x SIZE\");\n\n    mf.current = mf.start;\n    res = mfwrite(data8, SIZE/2, 1, &mf);\n    res2 = mfwrite(data8 + SIZE/2, 1, SIZE/2, &mf);\n    ASSERTEQ(res, 1, \"write SIZE/2\");\n    ASSERTEQ(res2, SIZE/2, \"write SIZE/2\");\n    ASSERTMEM(data8, dest, SIZE, \"write SIZE/2\");\n\n    mf.current = mf.start;\n    res = mfwrite(data8, SIZE, 2, &mf);\n    ASSERTEQ(res, 1, \"\");\n\n    mf.current = mf.start;\n    res = mfwrite(data8, 3, SIZE*2/3, &mf);\n    ASSERTEQ(res, SIZE/3, \"write 3 * (SIZE*2/3), past end\");\n\n    res = mfwrite(data8, 1, SIZE - SIZE/3*3 + 1, &mf);\n    ASSERTEQ(res, SIZE - SIZE/3*3, \"write 1 x 2, past end\");\n\n    res = mfwrite(data8, 1, 1, &mf);\n    ASSERTEQ(res, 0, \"write 1, past end\");\n\n    // This shouldn't happen, but the end bounding should catch it anyway.\n    mf.current = mf.end + 1;\n    res = mfwrite(dest, 1, 1, &mf);\n    ASSERTEQ(res, 0, \"write 1, current past end\");\n\n    uint8_t nowrite = 0xef;\n    mf.current = mf.start;\n    res = mfwrite(&nowrite, 0, 1, &mf);\n    ASSERTEQ(res, 0, \"write 0 x 1\");\n    ASSERTEQ(dest[0], data8[0], \"write 0 x 1\");\n    ASSERTEQ(mf.start, mf.current, \"write 0 x 1\");\n\n    res = mfwrite(&nowrite, 1, 0, &mf);\n    ASSERTEQ(res, 0, \"write 1 x 0\");\n    ASSERTEQ(dest[0], data8[0], \"write 1 x 0\");\n    ASSERTEQ(mf.start, mf.current, \"write 1 x 0\");\n  }\n}\n\nstruct memsafegets_data\n{\n  const char * const input;\n  const char * const output[8];\n};\n\nUNITTEST(mfsafegets)\n{\n  // NOTE: classic Mac line ends not currently supported, don't test...\n  static const memsafegets_data data[] =\n  {\n    {\n      \"a string\\nanother string\\n\",\n      {\n        \"a string\",\n        \"another string\",\n        nullptr\n      }\n    },\n    {\n      \"I'am wining this is so cool\\r\\nThere is no one versing you though\\r\\n\",\n      {\n        \"I'am wining this is so cool\",\n        \"There is no one versing you though\",\n        nullptr\n      }\n    },\n    {\n      \":loop\\nset a 123\\nwait 1\\ngoto loop\\n\",\n      {\n        \":loop\",\n        \"set a 123\",\n        \"wait 1\",\n        \"goto loop\",\n        nullptr\n      }\n    },\n    {\n      \":loop\\r\\nset a 123\\r\\nwait 1\\r\\ngoto loop\\r\\n\",\n      {\n        \":loop\",\n        \"set a 123\",\n        \"wait 1\",\n        \"goto loop\",\n        nullptr\n      }\n    },\n    {\n      \"a\\nb\\nc\\nd\\ne\\nf\\ng\",\n      { \"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", nullptr }\n    },\n    {\n      \"a\\r\\nb\\r\\nc\\r\\nd\\r\\ne\\r\\nf\\r\\ng\",\n      { \"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", nullptr }\n    },\n  };\n  const int SHORT_BUF_LEN = 32;\n  static const memsafegets_data short_data[] =\n  {\n    {\n      \"this should be truncated by the tiny buffer and split into multiple lines\"\n      \"\\nand still work after\\n\",\n      {\n        \"this should be truncated by the\",\n        \" tiny buffer and split into mul\",\n        \"tiple lines\",\n        \"and still work after\",\n        nullptr\n      }\n    },\n    {\n      \"wtf are you\\r\\nusing this very small and pathetic\\r\\nbuffer for anyway\"\n      \" it's 2020 already\\r\\n\",\n      {\n        \"wtf are you\",\n        \"using this very small and pathe\",\n        \"tic\",\n        \"buffer for anyway it's 2020 alr\",\n        \"eady\",\n        nullptr\n      }\n    }\n  };\n  struct memfile mf;\n  char buffer[512];\n  int j;\n\n  SECTION(NormalData)\n  {\n    for(const memsafegets_data &d : data)\n    {\n      mfopen(d.input, strlen(d.input), &mf);\n\n      for(j = 0; j < arraysize(d.output); j++)\n      {\n        char *result = mfsafegets(buffer, sizeof(buffer), &mf);\n        if(result && d.output[j])\n        {\n          ASSERTCMP(result, d.output[j], \"%s: %d\", d.input, j);\n        }\n        else\n          ASSERTEQ(result, d.output[j], \"%s: %d\", d.input, j);\n      }\n    }\n  }\n\n  SECTION(SmallData)\n  {\n    for(const memsafegets_data &d : short_data)\n    {\n      mfopen(d.input, strlen(d.input), &mf);\n\n      for(j = 0; j < arraysize(d.output); j++)\n      {\n        char *result = mfsafegets(buffer, SHORT_BUF_LEN, &mf);\n        if(result && d.output[j])\n        {\n          ASSERTCMP(result, d.output[j], \"%s: %d\", d.input, j);\n        }\n        else\n          ASSERTEQ(result, d.output[j], \"%s: %d\", d.input, j);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "unit/io/path.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../Unit.hpp\"\n#include \"../../src/io/path.h\"\n#include \"../../src/io/vio.h\"\n\n#include <errno.h>\n#include <sys/stat.h>\n\n/* path_is_absolute, path_clean, path_clean_copy, path_navigate, and\n * path_navigate_no_check have significantly different behavior between\n * platforms and require separate implementations. Many of these can be\n * tested in a cross-platform manner, so store test data for all platforms,\n * and use this selector to get the \"native\" data.\n */\n#ifdef PATH_AMIGA_STYLE_ROOTS\n#undef amiga\n#define PATH_SELECTOR amiga\n#elif defined(PATH_UNC_ROOTS)\n#define PATH_SELECTOR win32\n#elif defined(PATH_DOS_STYLE_ROOTS)\n#if DIR_SEPARATOR_CHAR == '\\\\'\n#define PATH_SELECTOR_DOS dos\n#else\n#define PATH_SELECTOR_DOS posixdos\n#endif\n#define PATH_SELECTOR PATH_SELECTOR_DOS\n#else\n#define PATH_SELECTOR posix\n#endif\n\n\n#define PATH_FILE_EXISTS        \"path_file_exists\"\n#define PATH_DIR_EXISTS         \"path_dir_exists\"\n#define PATH_DIR_EXISTS_2       PATH_DIR_EXISTS DIR_SEPARATOR \"path_dir_exists\"\n#define PATH_DIR_NOT_EXISTS     \"path_dir_kgdfsdlf\"\n#define PATH_DIR_RECURSIVE      \"path_dir_recursive\"\n#define PATH_DIR_RECURSIVE_2    \"path_dir_recursive\" DIR_SEPARATOR PATH_DIR_RECURSIVE\n#define PATH_DIR_RECURSIVE_3    \"path_dir_recursive\" DIR_SEPARATOR PATH_DIR_RECURSIVE_2\n#define PATH_FILE_RECURSIVE     PATH_DIR_RECURSIVE DIR_SEPARATOR PATH_FILE_EXISTS\n\n\nUNITTEST(Init)\n{\n  vfile *vf;\n  vrmdir(PATH_DIR_RECURSIVE_3);\n  vrmdir(PATH_DIR_RECURSIVE_2);\n  vmkdir(PATH_DIR_EXISTS, 0755);\n  vmkdir(PATH_DIR_EXISTS_2, 0755);\n  vmkdir(PATH_DIR_RECURSIVE, 0755);\n  vf = vfopen_unsafe(PATH_FILE_EXISTS, \"wb\");\n  vfclose(vf);\n  vf = vfopen_unsafe(PATH_FILE_RECURSIVE, \"wb\");\n  vfclose(vf);\n}\n\n\nstruct path_tokenize_result\n{\n  const char *input;\n  const char *expected[8];\n};\n\nUNITTEST(path_tokenize)\n{\n  static const path_tokenize_result data[] =\n  {\n    { nullptr,        { nullptr }},\n    { \"\",             { \"\", nullptr }},\n    { \"shdfkjshdf\",   { \"shdfkjshdf\", nullptr }},\n    { \"/\",            { \"\", \"\", nullptr }},\n    { \"\\\\\\\\unc\\\\p\",   { \"\", \"\", \"unc\", \"p\", nullptr }},\n    { \"\\\\\\\\.\\\\C:\",    { \"\", \"\", \".\", \"C:\" }},\n    { \"C:\\\\a\",        { \"C:\", \"a\", nullptr }},\n    { \"sdcard:/bees\", { \"sdcard:\", \"bees\", nullptr }},\n    { \"a\\\\lomg/path\", { \"a\", \"lomg\", \"path\", nullptr }},\n    { \"test///path\",  { \"test\", \"\", \"\", \"path\", nullptr }},\n    { \"trailing/\",    { \"trailing\", \"\", nullptr }},\n    {\n      u8\"/home/\\u00C8śŚ/megazeux/DE/saved.sav\",\n      { \"\", \"home\", u8\"\\u00C8śŚ\", \"megazeux\", \"DE\", \"saved.sav\", nullptr }\n    },\n  };\n\n  for(const path_tokenize_result &d : data)\n  {\n    char buffer[256];\n    char *cursor;\n    char *current;\n    int pos = 0;\n\n    if(d.input)\n    {\n      snprintf(buffer, sizeof(buffer), \"%s\", d.input);\n      cursor = buffer;\n    }\n    else\n      cursor = nullptr;\n\n    while((current = path_tokenize(&cursor)))\n    {\n      ASSERTCMP(current, d.expected[pos], \"%s : %d\", d.input, pos);\n      pos++;\n    }\n    ASSERTEQ(current, d.expected[pos], \"%s : %d\", d.input, pos);\n  }\n}\n\n\nUNITTEST(path_reverse_tokenize)\n{\n  static constexpr path_tokenize_result data[] =\n  {\n    { nullptr,          { nullptr }},\n    { \"\",               { \"\" }},\n    { \"abcde\",          { \"abcde\" }},\n    { \"path1/path2\",    { \"path2\", \"path1\" }},\n    { \"p1\\\\p2\\\\p3\",     { \"p3\", \"p2\", \"p1\" }},\n    { \"m1/m2\\\\m3/m4\",   { \"m4\", \"m3\", \"m2\", \"m1\" }},\n    { \"clean///first\",  { \"first\", \"\", \"\", \"clean\" }},\n    { \"../duhh/./\",     { \"\", \".\", \"duhh\", \"..\" }},\n    { \"/\",              { \"\", \"\" }},\n    { \"/dir\",           { \"dir\", \"\" }},\n    { \"/dir1/dir2\",     { \"dir2\", \"dir1\", \"\" }},\n    { \"C:\\\\\",           { \"\", \"C:\" }},\n    { \"C:\\\\WINDOWS\",    { \"WINDOWS\", \"C:\" }},\n    { \"C:\\\\a\\\\b\",       { \"b\", \"a\", \"C:\" }},\n    { \"sdcard:/butt\",   { \"butt\", \"sdcard:\" }},\n    { \"\\\\\\\\unc\\\\path\",  { \"path\", \"unc\", \"\", \"\" }},\n    { \"\\\\\\\\.\\\\C:\",      { \"C:\", \".\", \"\", \"\" }},\n    {\n      u8\"/home/\\u00C8śŚ/megazeux/DE/saved.sav\",\n      { \"saved.sav\", \"DE\", \"megazeux\", u8\"\\u00C8śŚ\", \"home\", \"\" }\n    },\n  };\n\n  size_t base_length;\n  char *base;\n  char *token;\n\n  token = path_reverse_tokenize(nullptr, nullptr);\n  ASSERTEQ(token, nullptr, \"\");\n  token = path_reverse_tokenize(nullptr, &base_length);\n  ASSERTEQ(token, nullptr, \"\");\n\n  for(auto &d : data)\n  {\n    char buffer[256];\n    int pos = 0;\n\n    const auto &init = [&]()\n    {\n      if(d.input)\n      {\n        base_length = snprintf(buffer, sizeof(buffer), \"%s\", d.input);\n        base = buffer;\n        ASSERT(base_length < sizeof(buffer), \"\");\n      }\n      else\n      {\n        base_length = 0xbaad; // Some junk, should be ignored.\n        base = nullptr;\n      }\n      pos = 0;\n    };\n\n    init();\n    while((token = path_reverse_tokenize(&base, &base_length)))\n    {\n      ASSERTCMP(token, d.expected[pos], \"%s : %d\", d.input, pos);\n      pos++;\n    }\n    ASSERTEQ(token, d.expected[pos], \"%s : %d\", d.input, pos);\n\n    // Should work identically without base_length, it's just slower.\n    init();\n    while((token = path_reverse_tokenize(&base, nullptr)))\n    {\n      ASSERT(d.expected[pos], \"%s : %d\", d.input, pos);\n      ASSERTCMP(token, d.expected[pos], \"%s : %d\", d.input, pos);\n      pos++;\n    }\n    ASSERTEQ(token, d.expected[pos], \"%s : %d\", d.input, pos);\n  }\n}\n\n\nstruct path_ext_result\n{\n  const char *path;\n  const char *ext;\n  const char *result;\n};\n\nUNITTEST(path_force_ext)\n{\n  static const path_ext_result data[] =\n  {\n    {\n      \"/here/is/a/path.chr\",\n      \".chr\",\n      \"/here/is/a/path.chr\"\n    },\n    {\n      \"/dont/modify/this/one/either/a.palidx\",\n      \".palidx\",\n      \"/dont/modify/this/one/either/a.palidx\"\n    },\n    {\n      \"/Ext/Should/Be/Case/Insensitive.EXT\",\n      \".ext\",\n      \"/Ext/Should/Be/Case/Insensitive.EXT\"\n    },\n    {\n      \"/Ext/Should/Be/Case/Insensitive.ext\",\n      \".EXT\",\n      \"/Ext/Should/Be/Case/Insensitive.ext\"\n    },\n    {\n      \"/add/the/ext/to/this/one.pal\",\n      \".chr\",\n      \"/add/the/ext/to/this/one.pal.chr\"\n    },\n    {\n      \"/lol.mxz\",\n      \".mzx\",\n      \"/lol.mxz.mzx\"\n    },\n    {\n      \"/hellowwolrd\",\n      \".sav\",\n      \"/hellowwolrd.sav\"\n    },\n    {\n      u8\"/home/\\u00C8śŚ/megazeux/DE/saved.sav\",\n      \".sav\",\n      u8\"/home/\\u00C8śŚ/megazeux/DE/saved.sav\",\n    },\n    {\n      u8\"/home/\\u00C8śŚ/megazeux/DE/saved.svaśŚ\",\n      \".sav\",\n      u8\"/home/\\u00C8śŚ/megazeux/DE/saved.svaśŚ.sav\",\n    },\n  };\n  // These should result in failures in the second section.\n  // All of them assume a buffer size of 32.\n  static const path_ext_result bad_data[] =\n  {\n    {\n      \"/an/input/path\",\n      \".verylongextensionwtfwhydidyoudothis\",\n      \"/an/input/path\"\n    },\n    {\n      \"/some/really/long/path/i.guess\",\n      \".lol\",\n      \"/some/really/long/path/i.guess\"\n    },\n  };\n  char buffer[MAX_PATH];\n  boolean result;\n\n  SECTION(NormalCases)\n  {\n    for(const path_ext_result &d : data)\n    {\n      snprintf(buffer, MAX_PATH, \"%s\", d.path);\n      result = path_force_ext(buffer, MAX_PATH, d.ext);\n\n      ASSERT(result, \"%s\", d.path);\n      ASSERTCMP(buffer, d.result, \"%s\", d.path);\n    }\n  }\n\n  SECTION(FailureCases)\n  {\n    for(const path_ext_result &d : bad_data)\n    {\n      snprintf(buffer, MAX_PATH, \"%s\", d.path);\n      result = path_force_ext(buffer, 32, d.ext);\n\n      ASSERT(!result, \"%s\", d.path);\n      ASSERTCMP(buffer, d.result, \"%s\", d.path);\n    }\n  }\n}\n\n\nstruct path_is_abs_result\n{\n  ssize_t root_len;\n  boolean is_root;\n};\n\nstruct path_is_abs_data\n{\n  const char *path;\n  struct path_is_abs_result posix;    /* POSIX with added root:// roots */\n  struct path_is_abs_result posixdos; /* POSIX with added root:/ roots (console SDKs) */\n  struct path_is_abs_result dos;      /* DOS */\n  struct path_is_abs_result win32;    /* DOS with UNC roots */\n  struct path_is_abs_result amiga;    /* Amiga */\n\n  constexpr static struct path_is_abs_data all(const char *p,\n   struct path_is_abs_result _all)\n  {\n    return path_is_abs_data{ p, _all, _all, _all, _all, _all };\n  }\n\n  constexpr static struct path_is_abs_data posixroot(const char *p,\n   struct path_is_abs_result _posix, struct path_is_abs_result _noposix)\n  {\n    return path_is_abs_data{ p, _posix, _posix, _posix, _posix, _noposix };\n  }\n\n  constexpr static struct path_is_abs_data dosroot(const char *p,\n   struct path_is_abs_result _posix, struct path_is_abs_result _dos)\n  {\n    return path_is_abs_data{ p, _posix, _dos, _dos, _dos, _dos };\n  }\n\n  constexpr static struct path_is_abs_data amigaroot(const char *p,\n   struct path_is_abs_result _noamiga, struct path_is_abs_result _amiga)\n  {\n    return path_is_abs_data{ p, _noamiga, _noamiga, _noamiga, _noamiga, _amiga };\n  }\n\n  constexpr static struct path_is_abs_data uncroot(const char *p,\n   struct path_is_abs_result _posix, struct path_is_abs_result _win32,\n   struct path_is_abs_result _amiga)\n  {\n    return path_is_abs_data{ p, _posix, _posix, _posix, _win32, _amiga };\n  }\n};\n\nUNITTEST(path_is_absolute)\n{\n  static constexpr const path_is_abs_data data[]\n  {\n    path_is_abs_data::all(\n      \"\",\n      { 0, false }\n    ),\n    path_is_abs_data::all(\n      \"sdhfjkshfjkds\",\n      { 0, false }\n    ),\n    path_is_abs_data::all(\n      \"relative/path/here\",\n      { 0, false }\n    ),\n    path_is_abs_data::amigaroot(\n      \":/wtf\",\n      { 0, false },\n      { 2, false }\n    ),\n    // Unix-style roots (all platforms but Amiga)\n    path_is_abs_data::posixroot(\n      \"/\",\n      { 1, true },\n      { 0, false }\n    ),\n    path_is_abs_data::posixroot(\n      \"/absolute/but/not/root\",\n      { 1, false },\n      { 0, false }\n    ),\n    // DOS-style paths (Windows, DOS, Amiga, and various consoles).\n    path_is_abs_data::dosroot(\n      \"A:\",\n      { 0, false },\n      { 2, true }\n    ),\n    path_is_abs_data::dosroot(\n      \"C:\\\\\",\n      { 0, false },\n      { 3, true }\n    ),\n    path_is_abs_data::dosroot(\n      \"sdcard:/\",\n      { 0, false },\n      { 8, true }\n    ),\n    path_is_abs_data::dosroot(\n      \"C:\\\\absolute/not\\\\root\",\n      { 0, false },\n      { 3, false }\n    ),\n    path_is_abs_data::dosroot(\n      \"software:\",\n      { 0, false },\n      { 9, true }\n    ),\n    // Amiga-style paths (Amiga-only).\n    path_is_abs_data::amigaroot(\n      \"sys:some/path\",\n      { 0, false },\n      { 4, false }\n    ),\n    path_is_abs_data::amigaroot(\n      \":\",\n      { 0, false },\n      { 1, true }\n    ),\n    path_is_abs_data::amigaroot(\n      \":System/Shell\",\n      { 0, false },\n      { 1, false }\n    ),\n    path_is_abs_data::amigaroot(\n      \"are/you/serious:\",\n      { 0, false },\n      { 16, true }\n    ),\n    path_is_abs_data::amigaroot(\n      \"w/t:f\",\n      { 0, false },\n      { 4, false }\n    ),\n    path_is_abs_data::amigaroot(\n      \"/lol:\",\n      { 1, false },\n      { 5, true }\n    ),\n    path_is_abs_data::amigaroot(\n      \"/lol:lmao\",\n      { 1, false },\n      { 5, false }\n    ),\n    // Modified DOS-style paths (all platforms).\n    path_is_abs_data::all(\n      \"C://\",\n      { 4, true }\n    ),\n    path_is_abs_data::all(\n      \"sdcard://\",\n      { 9, true }\n    ),\n    path_is_abs_data::all(\n      \"fat://absolute/but/not/root\",\n      { 6, false }\n    ),\n    // Windows UNC paths\n    path_is_abs_data::uncroot(\n      \"\\\\\\\\.\\\\C:\",\n      { 1, false },\n      { 6, true },\n      { 6, true }\n    ),\n    path_is_abs_data::uncroot(\n      \"\\\\\\\\?\\\\Z:\",\n      { 1, false },\n      { 6, true },\n      { 6, true }\n    ),\n    path_is_abs_data::uncroot(\n      \"\\\\\\\\unc\\\\root\\\\\",\n      { 1, false },\n      { 11, true },\n      { 0, false }\n    ),\n    path_is_abs_data::uncroot(\n      \"\\\\\\\\.\\\\unc\\\\localhost\\\\c$\",\n      { 1, false },\n      { 20, true },\n      { 0, false }\n    ),\n    path_is_abs_data::uncroot(\n      \"//?/unc/thisworks/too\",\n      { 1, false },\n      { 21, true },\n      { 0, false }\n    ),\n    path_is_abs_data::uncroot(\n      \"\\\\\\\\.\\\\unc\\\\localhost\\\\c$\\\\somefile\",\n      { 1, false },\n      { 21, false },\n      { 0, false }\n    ),\n    path_is_abs_data::posixroot(\n      \"\\\\\\\\bad_unc\",\n      { 1, false },\n      { 0, false }\n    ),\n  };\n\n  SECTION(path_is_absolute)\n  {\n    for(const path_is_abs_data &d : data)\n    {\n      ssize_t len = path_is_absolute(d.path);\n      ASSERTEQ(len, d.PATH_SELECTOR.root_len, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_is_absolute_dos)\n  {\n    for(const path_is_abs_data &d : data)\n    {\n      ssize_t len = path_is_absolute_dos(d.path);\n      ASSERTEQ(len, d.posixdos.root_len, \"%s\", d.path);\n      ASSERTEQ(len, d.dos.root_len, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_is_root)\n  {\n    for(const path_is_abs_data &d : data)\n    {\n      boolean is_root = path_is_root(d.path);\n      ASSERTEQ(is_root, d.PATH_SELECTOR.is_root, \"%s\", d.path);\n    }\n  }\n}\n\n\nstruct path_output_pair\n{\n  const char *path;\n  const char *expected;\n  ssize_t return_value;\n};\n\nUNITTEST(path_get_ext_offset)\n{\n  static const path_output_pair data[]\n  {\n    {\n      \"\",\n      nullptr,\n      -1\n    },\n    {\n      \"return -1 for directories/\",\n      nullptr,\n      -1\n    },\n    {\n      \"no really return -1 for directories\\\\\",\n      nullptr,\n      -1\n    },\n    {\n      \"filewithnoext\",\n      nullptr,\n      -1\n    },\n    {\n      \"somepath.not.an.ext/filewithnoext\",\n      nullptr,\n      -1\n    },\n    {\n      \"somepath.still.not.an.ext\\\\filewithnoext\",\n      nullptr,\n      -1\n    },\n    {\n      \"yeah this really counts.\",\n      \".\",\n      23\n    },\n    {\n      \"CAVERNS.MZX\",\n      \".MZX\",\n      7\n    },\n    {\n      \"zeux/forest/FOREST.MZX\",\n      \".MZX\",\n      18\n    },\n    {\n      \"assets/glsl/scalers/softscale.frag\",\n      \".frag\",\n      29\n    },\n    {\n      \"testgame\\\\smzx2.palidx\",\n      \".palidx\",\n      14\n    },\n    {\n      \"/lol.mxz.mzx\",\n      \".mzx\",\n      8\n    },\n    {\n      \".mzx\",\n      \".mzx\",\n      0\n    },\n    {\n      u8\"unicode_fileśŚ.mzx\",\n      \".mzx\",\n      16\n    },\n    {\n      u8\"unicode_pathśŚ/caverns.mzx\",\n      \".mzx\",\n      24\n    },\n    /* More absolute paths... */\n    {\n      \"c:/caverns.mzx\",\n      \".mzx\",\n      10\n    },\n    {\n      \"a:\\\\.mzx\",\n      \".mzx\",\n      3\n    },\n    {\n      \"fat://.awawa\",\n      \".awawa\",\n      6\n    },\n    {\n      \"any.oof://nope\",\n      nullptr,\n      -1\n    },\n    {\n      \"amiga:filename.ext\",\n      \".ext\",\n      14\n    },\n    {\n      \"amiga:.thistoo\",\n      \".thistoo\",\n      6,\n    },\n    {\n      \"amiga.oof:nope\",\n#ifndef PATH_AMIGA_STYLE_ROOTS\n      \".oof:nope\",\n      5,\n#else\n      nullptr,\n      -1\n#endif\n    }\n  };\n  ssize_t result;\n\n  for(const path_output_pair &d : data)\n  {\n    result = path_get_ext_offset(d.path);\n    ASSERTEQ(result, d.return_value, \"%s\", d.path);\n    if(result >= 0 && d.expected)\n      ASSERTCMP(d.path + result, d.expected, \"%s\", d.path);\n  }\n}\n\n\nstruct path_clean_result\n{\n  const char *result;\n};\n\nstruct path_clean_data\n{\n  const char *path;\n  struct path_clean_result posix;     /* POSIX with added root:// roots */\n  struct path_clean_result posixdos;  /* POSIX with added root:/ roots (console SDKs) */\n  struct path_clean_result dos;       /* DOS */\n  struct path_clean_result win32;     /* DOS with UNC roots */\n  struct path_clean_result amiga;     /* Amiga */\n\n  constexpr static struct path_clean_data all(const char *p,\n   struct path_clean_result _all)\n  {\n    return path_clean_data{ p, _all, _all, _all, _all, _all };\n  }\n\n  constexpr static struct path_clean_data posixroot(const char *p,\n   struct path_clean_result _posix, struct path_clean_result _dos)\n  {\n    return path_clean_data{ p, _posix, _posix, _dos, _dos, _posix };\n  }\n\n  constexpr static struct path_clean_data amigaroot(const char *p,\n   struct path_clean_result _posix, struct path_clean_result _dos,\n   struct path_clean_result _amiga)\n  {\n    return path_clean_data{ p, _posix, _posix, _dos, _dos, _amiga };\n  }\n\n  constexpr static struct path_clean_data extroot(const char *p,\n   struct path_clean_result _posix, struct path_clean_result _posixdos,\n   struct path_clean_result _dos, struct path_clean_result _amiga)\n  {\n    return path_clean_data{ p, _posix, _posixdos, _dos, _dos, _amiga };\n  }\n};\n\nUNITTEST(path_clean)\n{\n  static constexpr const path_clean_data data[] =\n  {\n    path_clean_data::all(\n      \"\",\n      { \"\" }\n    ),\n    path_clean_data::posixroot(\n      \"/a/path\",\n      { \"/a/path\" },\n      { \"\\\\a\\\\path\" }\n    ),\n    path_clean_data::posixroot(\n      \"/remove/trailing/slash/\",\n      { \"/remove/trailing/slash\" },\n      { \"\\\\remove\\\\trailing\\\\slash\" }\n    ),\n    path_clean_data::posixroot(\n      \"/normalize\\\\slashes/that\\\\are/like\\\\this\",\n      { \"/normalize/slashes/that/are/like/this\" },\n      { \"\\\\normalize\\\\slashes\\\\that\\\\are\\\\like\\\\this\" }\n    ),\n    path_clean_data::amigaroot(\n      \"/////remove////duplicate//////slashes/////\",\n      { \"/remove/duplicate/slashes\" },\n      { \"\\\\remove\\\\duplicate\\\\slashes\" },\n      { \"/////remove////duplicate//////slashes/////\" }\n    ),\n    path_clean_data::amigaroot(\n      \"C:\\\\work\\\\on\\\\dos\\\\style\\\\paths\\\\\",\n      { \"C:/work/on/dos/style/paths\" },\n      { \"C:\\\\work\\\\on\\\\dos\\\\style\\\\paths\" },\n      { \"C:work/on/dos/style/paths\" }\n    ),\n    path_clean_data::extroot(\n      \"C:\\\\\\\\\\\\remove\\\\\\\\\\\\duplicate\\\\\\\\slashes\\\\\\\\here\\\\\\\\too\\\\\",\n      { \"C://remove/duplicate/slashes/here/too\" },\n      { \"C:/remove/duplicate/slashes/here/too\" },\n      { \"C:\\\\remove\\\\duplicate\\\\slashes\\\\here\\\\too\" },\n      { \"C:remove///duplicate//slashes//here//too\" }\n    ),\n    path_clean_data::extroot(\n      \"C:\\\\\",\n      { \"C:\" },\n      { \"C:/\" },\n      { \"C:\\\\\" },\n      { \"C:\" }\n    ),\n    path_clean_data::extroot(\n      \"C:/\",\n      { \"C:\" },\n      { \"C:/\" },\n      { \"C:\\\\\" },\n      { \"C:\" }\n    ),\n    path_clean_data::extroot(\n      \"C:\\\\\\\\/\",\n      { \"C://\" },\n      { \"C:/\" },\n      { \"C:\\\\\" },\n      { \"C:\" }\n    ),\n    path_clean_data::posixroot(\n      \"/\",\n      { \"/\" },\n      { \"\\\\\" }\n    ),\n    path_clean_data::posixroot(\n      \"\\\\\",\n      { \"/\" },\n      { \"\\\\\" }\n    ),\n    // Windows UNC paths: do not remove duplicate slashes from the prefix.\n    path_clean_data::amigaroot(\n      \"\\\\\\\\.\\\\C:\\\\\",\n      { \"/./C:\" },\n      { \"\\\\\\\\.\\\\C:\\\\\" },\n      { \"\\\\\\\\.\\\\C:\" }\n    ),\n    path_clean_data::amigaroot(\n      \"\\\\\\\\?\\\\C:\\\\\",\n      { \"/?/C:\" },\n      { \"\\\\\\\\?\\\\C:\\\\\" },\n      { \"\\\\\\\\?\\\\C:\" }\n    ),\n    path_clean_data::amigaroot(\n      \"\\\\\\\\.\\\\unc\\\\\\\\localhost\\\\\\\\\\\\duhhr\",\n      { \"/./unc/localhost/duhhr\" },\n      { \"\\\\\\\\.\\\\unc\\\\localhost\\\\duhhr\" },\n      { \"//./unc//localhost///duhhr\" }\n    ),\n    path_clean_data::amigaroot(\n      \"\\\\\\\\?\\\\unc\\\\\\\\localhost\\\\\\\\\\\\duhhr\",\n      { \"/?/unc/localhost/duhhr\" },\n      { \"\\\\\\\\?\\\\unc\\\\localhost\\\\duhhr\" },\n      { \"//?/unc//localhost///duhhr\" }\n    ),\n  };\n  /* TODO: this should be combined into the prior array. */\n  static constexpr const path_clean_data clean_current_tokens[] =\n  {\n    /* path_clean* functions remove redundant POSIX current directory tokens,\n     * but keep non-redundant tokens. Don't do this on Amiga. path_navigate*\n     * is required to resolve parent directory tokens.\n     *\n     * TODO: this is a separate function from the clean functions, so it\n     * currently doesn't clean slashes. Every platform should get the POSIX\n     * results below currently; the other results assume this is part of\n     * a regular clean function.\n     */\n    path_clean_data::all(\n      \".\",\n      { \".\" }\n    ),\n    path_clean_data::amigaroot(\n      \"./.\",\n      { \".\" },\n      { \".\" },\n      { \"./.\" }\n    ),\n    path_clean_data::amigaroot(\n      \"../..\",\n      { \"../..\" },\n      { \"..\\\\..\" },\n      { \"../..\" }\n    ),\n    path_clean_data::amigaroot(\n      \"./what/././././dafuq/./?/./\",\n      { \"what/dafuq/?/\" },\n      { \"what\\\\dafuq\\\\?\\\\\" },\n      { \"./what/././././dafuq/./?/./\" }\n    ),\n    path_clean_data::amigaroot(\n      \"a/./mixe/../of/./tokens\",\n      { \"a/mixe/../of/tokens\" },\n      { \"a\\\\mixe\\\\..\\\\of\\\\tokens\" },\n      { \"a/./mixe/../of/./tokens\" }\n    ),\n    path_clean_data::extroot(\n      \"fat://././.\",\n      { \"fat://\" },\n      { \"fat:/\" },\n      { \"fat:\\\\\" },\n      { \"fat:././.\" }\n    ),\n  };\n  // Data for testing truncation (assume buffer size == 32). This only matters\n  // for path_clean_copy, which should return false in this case.\n  static constexpr const path_clean_data truncate_data[] =\n  {\n    path_clean_data::posixroot(\n      \"/a/rly/long/path/looooooooooooooooooooool/\",\n      { \"/a/rly/long/path/looooooooooooo\" },\n      { \"\\\\a\\\\rly\\\\long\\\\path\\\\looooooooooooo\" }\n    ),\n    path_clean_data::amigaroot(\n      \"C:\\\\truncate\\\\my\\\\dos\\\\style\\\\path\\\\pls\",\n      { \"C:/truncate/my/dos/style/path/p\" },\n      { \"C:\\\\truncate\\\\my\\\\dos\\\\style\\\\path\\\\p\" },\n      { \"C:truncate/my/dos/style/path/pl\" }\n    ),\n  };\n\n  char buffer[MAX_PATH];\n\n  SECTION(path_clean)\n  {\n    for(const path_clean_data &d : data)\n    {\n      snprintf(buffer, MAX_PATH, \"%s\", d.path);\n      buffer[MAX_PATH - 1] = '\\0';\n\n      path_clean(buffer, MAX_PATH);\n      ASSERTCMP(buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_clean_copy)\n  {\n    for(const path_clean_data &d : data)\n    {\n      size_t result = path_clean_copy(buffer, MAX_PATH, d.path);\n      ASSERTEQ(result, strlen(d.PATH_SELECTOR.result), \"%s\", d.path);\n      ASSERTCMP(buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_clean_copy_truncation)\n  {\n    for(const path_clean_data &d : truncate_data)\n    {\n      size_t result = path_clean_copy(buffer, 32, d.path);\n      ASSERTEQ(result, strlen(d.PATH_SELECTOR.result), \"%s\", d.path);\n      ASSERTCMP(buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_clean_copy_posixdos)\n  {\n    for(const path_clean_data &d : data)\n    {\n      size_t result = path_clean_copy_posixdos(buffer, MAX_PATH, d.path);\n      ASSERTEQ(result, strlen(d.posixdos.result), \"%s\", d.path);\n      ASSERTCMP(buffer, d.posixdos.result, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_clean_current_tokens)\n  {\n    for(const path_clean_data &d : clean_current_tokens)\n    {\n      snprintf(buffer, MAX_PATH, \"%s\", d.path);\n      buffer[MAX_PATH - 1] = '\\0';\n\n      path_clean_current_tokens(buffer, MAX_PATH);\n      ASSERTCMP(buffer, d.posix.result, \"%s\", d.path);\n    }\n  }\n}\n\n\nstruct path_return_result\n{\n  ssize_t return_value;\n  const char *result;\n};\n\nstruct path_split_data\n{\n  const char *path;\n  struct path_return_result posix;    /* POSIX with added root:// roots */\n  struct path_return_result posixdos; /* POSIX with added root:/ roots (console SDKs) */\n  struct path_return_result dos;      /* DOS */\n  struct path_return_result win32;    /* DOS with UNC roots */\n  struct path_return_result amiga;    /* Amiga */\n  struct path_return_result filename;\n  boolean dir_and_file_return_value;\n\n  static constexpr struct path_split_data all(const char *p,\n   struct path_return_result dir, struct path_return_result file,\n   boolean dir_and_file_ret)\n  {\n    return path_split_data{ p, dir, dir, dir, dir, dir, file, dir_and_file_ret };\n  }\n\n  static constexpr struct path_split_data posixroot(const char *p,\n   struct path_return_result posix_dir, struct path_return_result dos_dir,\n   struct path_return_result file, boolean dir_and_file_ret)\n  {\n    return path_split_data{ p, posix_dir, posix_dir, dos_dir, dos_dir,\n                            posix_dir, file, dir_and_file_ret };\n  }\n\n  static constexpr struct path_split_data posixroot(const char *p,\n   struct path_return_result posix_dir, struct path_return_result dos_dir,\n   struct path_return_result amiga_dir, struct path_return_result file,\n   boolean dir_and_file_ret)\n  {\n    return path_split_data{ p, posix_dir, posix_dir, dos_dir, dos_dir,\n                            amiga_dir, file, dir_and_file_ret };\n  }\n\n  static constexpr struct path_split_data posixdosroot(const char *p,\n   struct path_return_result posix_dir, struct path_return_result posixdos_dir,\n   struct path_return_result dos_dir, struct path_return_result amiga_dir,\n   struct path_return_result file, boolean dir_and_file_ret)\n  {\n    return path_split_data{ p, posix_dir, posixdos_dir, dos_dir, dos_dir,\n                            amiga_dir, file, dir_and_file_ret };\n  }\n};\n\nUNITTEST(path_split_functions)\n{\n  // All of these tests assume a directory stat fail on the input path.\n  static constexpr const path_split_data data[] =\n  {\n    path_split_data::all(\n      \"\",\n      { -1, nullptr },\n      { -1, nullptr },\n      false\n    ),\n    path_split_data::all(\n      \"a\",\n      { 0, \"\" },\n      { 1, \"a\" },\n      true\n    ),\n    path_split_data::all(\n      \"filename.ext\",\n      { 0, \"\" },\n      { 12, \"filename.ext\" },\n      true\n    ),\n    path_split_data::all(\n      \"input/filename.ext\",\n      { 5, \"input\" },\n      { 12, \"filename.ext\" },\n      true\n    ),\n    path_split_data::all(\n      \"input\\\\filename.ext\",\n      { 5, \"input\" },\n      { 12, \"filename.ext\" },\n      true\n    ),\n    path_split_data::all(\n      \"input/\",\n      { 5, \"input\" },\n      { 0, \"\" },\n      true\n    ),\n    path_split_data::all(\n      \"input\\\\\",\n      { 5, \"input\" },\n      { 0, \"\" },\n      true\n    ),\n    path_split_data::posixroot(\n      \"C:\\\\Users\\\\MegaZeux\\\\Desktop\\\\MegaZeux\\\\Zeux\\\\Caverns\\\\CAVERNS.MZX\",\n      { 47, \"C:/Users/MegaZeux/Desktop/MegaZeux/Zeux/Caverns\" },\n      { 47, \"C:\\\\Users\\\\MegaZeux\\\\Desktop\\\\MegaZeux\\\\Zeux\\\\Caverns\" },\n      { 46, \"C:Users/MegaZeux/Desktop/MegaZeux/Zeux/Caverns\" },\n      { 11, \"CAVERNS.MZX\" },\n      true\n    ),\n    path_split_data::posixroot(\n      u8\"/home/\\u00C8śŚ/megazeux/DE/DE_START.MZX\",\n      { 24, u8\"/home/\\u00C8śŚ/megazeux/DE\" },\n      { 24, u8\"\\\\home\\\\\\u00C8śŚ\\\\megazeux\\\\DE\" },\n      { 12, \"DE_START.MZX\" },\n      true\n    ),\n    path_split_data::posixroot(\n      u8\"/home/ćçáö/megazeux/DE/saved.\\u00C8śŚ.sav\",\n      { 26, u8\"/home/ćçáö/megazeux/DE\" },\n      { 26, u8\"\\\\home\\\\ćçáö\\\\megazeux\\\\DE\" },\n      { 16, u8\"saved.\\u00C8śŚ.sav\" },\n      true\n    ),\n    path_split_data::posixroot(\n      \"/\",\n      { 1, \"/\" },\n      { 1, \"\\\\\" },\n      { 0, \"\" },\n      true\n    ),\n    path_split_data::posixdosroot(\n      \"C:\\\\\",\n      { 2, \"C:\" },\n      { 3, \"C:/\" },\n      { 3, \"C:\\\\\" },\n      { 2, \"C:\" },\n      { 0, \"\" },\n      true\n    ),\n    path_split_data::posixroot(\n      \"/sdfjklfdjdskfdsfgdfsggdfgdfgfdgsgdfgfdgfgg\",\n      { 1, \"/\" },\n      { 1, \"\\\\\" },\n      { 42, \"sdfjklfdjdskfdsfgdfsggdfgdfgfdgsgdfgfdgfgg\" },\n      true\n    ),\n    path_split_data::posixdosroot(\n      \"C:\\\\sdfjklfdjdskfdsfgdfsggdfgdfgfdgsgdfgfdgfgg\",\n      { 2, \"C:\" },\n      { 3, \"C:/\" },\n      { 3, \"C:\\\\\" },\n      { 2, \"C:\" },\n      { 42, \"sdfjklfdjdskfdsfgdfsggdfgdfgfdgsgdfgfdgfgg\" },\n      true\n    ),\n    path_split_data::posixdosroot(\n      \"C://sdfjklf\",\n      { 4, \"C://\" },\n      { 3, \"C:/\" },\n      { 3, \"C:\\\\\" },\n      { 2, \"C:\" },\n      { 7, \"sdfjklf\" },\n      true\n    ),\n    // Internally all of these functions may stat the provided directory to\n    // determine how much of it is/isn't a path. These paths all assume that\n    // a directory stat succeeds for the input path.\n    path_split_data::all(\n      PATH_DIR_EXISTS,\n      { 15, PATH_DIR_EXISTS },\n      { 0, \"\" },\n      true\n    ),\n    path_split_data::all(\n      PATH_DIR_EXISTS_2,\n      { 31, PATH_DIR_EXISTS_2 },\n      { 0, \"\" },\n      true\n    ),\n  };\n  char dir_buffer[MAX_PATH];\n  char file_buffer[MAX_PATH];\n  ssize_t result;\n\n  SECTION(path_has_directory)\n  {\n    for(const path_split_data &d : data)\n    {\n      boolean result = path_has_directory(d.path);\n      ASSERTEQ(result, d.PATH_SELECTOR.return_value > 0, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_to_directory)\n  {\n    for(const path_split_data &d : data)\n    {\n      snprintf(dir_buffer, MAX_PATH, \"%s\", d.path);\n      dir_buffer[MAX_PATH - 1] = '\\0';\n\n      result = path_to_directory(dir_buffer, MAX_PATH);\n      ASSERTEQ(result, d.PATH_SELECTOR.return_value, \"%s\", d.path);\n      if(result >= 0 && d.PATH_SELECTOR.result)\n        ASSERTCMP(dir_buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_get_directory)\n  {\n    for(const path_split_data &d : data)\n    {\n      result = path_get_directory(dir_buffer, MAX_PATH, d.path);\n      ASSERTEQ(result, d.PATH_SELECTOR.return_value, \"%s\", d.path);\n      if(result >= 0 && d.PATH_SELECTOR.result)\n        ASSERTCMP(dir_buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_to_filename)\n  {\n    for(const path_split_data &d : data)\n    {\n      snprintf(file_buffer, MAX_PATH, \"%s\", d.path);\n      file_buffer[MAX_PATH - 1] = '\\0';\n\n      result = path_to_filename(file_buffer, MAX_PATH);\n      ASSERTEQ(result, d.filename.return_value, \"%s\", d.path);\n      if(result >= 0 && d.filename.result)\n        ASSERTCMP(file_buffer, d.filename.result, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_get_filename)\n  {\n    for(const path_split_data &d : data)\n    {\n      result = path_get_filename(file_buffer, MAX_PATH, d.path);\n      ASSERTEQ(result, d.filename.return_value, \"%s\", d.path);\n      if(result >= 0 && d.filename.result)\n        ASSERTCMP(file_buffer, d.filename.result, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_get_directory_and_filename)\n  {\n    boolean result;\n\n    for(const path_split_data &d : data)\n    {\n      result = path_get_directory_and_filename(\n       dir_buffer, MAX_PATH, file_buffer, MAX_PATH, d.path);\n      ASSERTEQ(result, d.dir_and_file_return_value, \"%s\", d.path);\n      if(result)\n      {\n        if(d.PATH_SELECTOR.result)\n          ASSERTCMP(dir_buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n\n        if(d.filename.result)\n          ASSERTCMP(file_buffer, d.filename.result, \"%s\", d.path);\n      }\n    }\n  }\n\n  SECTION(path_get_parent)\n  {\n    for(auto &d : data)\n    {\n      // Special: the tests that expect stat() are different.\n      if(!strcmp(d.path, PATH_DIR_EXISTS))\n      {\n        result = path_get_parent(dir_buffer, MAX_PATH, PATH_DIR_EXISTS);\n        ASSERTEQ(result, 0, \"%s\", PATH_DIR_EXISTS);\n        continue;\n      }\n      if(!strcmp(d.path, PATH_DIR_EXISTS_2))\n      {\n        result = path_get_parent(dir_buffer, MAX_PATH, PATH_DIR_EXISTS_2);\n        ASSERTEQ(result, strlen(PATH_DIR_EXISTS), \"%s\", PATH_DIR_EXISTS_2);\n        ASSERTCMP(dir_buffer, PATH_DIR_EXISTS, \"%s\", PATH_DIR_EXISTS_2);\n        continue;\n      }\n\n      // The rest behave exactly like path_get_directory.\n      result = path_get_parent(dir_buffer, MAX_PATH, d.path);\n      ASSERTEQ(result, d.PATH_SELECTOR.return_value, \"%s\", d.path);\n      if(result >= 0 && d.PATH_SELECTOR.result)\n        ASSERTCMP(dir_buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n    }\n  }\n}\n\n\nstruct path_target_output\n{\n  const char *path;\n  const char *target;\n  struct path_return_result posix;    /* POSIX with added root:// roots */\n  struct path_return_result posixdos; /* POSIX with added root:/ roots (console SDKs) */\n  struct path_return_result dos;      /* DOS */\n  struct path_return_result win32;    /* DOS with UNC roots */\n  struct path_return_result amiga;    /* Amiga */\n\n  static constexpr struct path_target_output all(const char *path,\n   const char *target, struct path_return_result any)\n  {\n    return path_target_output{ path, target, any, any, any, any, any };\n  }\n\n  static constexpr struct path_target_output posixroot(const char *path,\n   const char *target, struct path_return_result posix,\n   struct path_return_result dos)\n  {\n    return path_target_output{ path, target, posix, posix, dos, dos, posix };\n  }\n\n  static constexpr struct path_target_output posixroot(const char *path,\n   const char *target, struct path_return_result posix,\n   struct path_return_result dos, struct path_return_result amiga)\n  {\n    return path_target_output{ path, target, posix, posix, dos, dos, amiga };\n  }\n\n  static constexpr struct path_target_output posixdosroot(const char *path,\n   const char *target, struct path_return_result posix,\n   struct path_return_result posixdos, struct path_return_result dos,\n   struct path_return_result amiga)\n  {\n    return path_target_output{ path, target, posix, posixdos, dos, dos, amiga };\n  }\n\n  static constexpr struct path_target_output uncroot(const char *path,\n   const char *target, struct path_return_result posix,\n   struct path_return_result dos, struct path_return_result win32,\n   struct path_return_result amiga)\n  {\n    return path_target_output{ path, target, posix, posix, dos, win32, amiga };\n  }\n};\n\nUNITTEST(path_append_and_path_join)\n{\n  static constexpr const path_target_output data[] =\n  {\n    path_target_output::all(\n      \"\",\n      \"\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \"/a/base\",\n      \"\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \"\",\n      \"a/target\",\n      { -1, nullptr }\n    ),\n    path_target_output::posixroot(\n      \"/\",\n      \"awa\",\n      { 4, \"/awa\" },\n      { 4, \"\\\\awa\" }\n    ),\n    path_target_output::posixroot(\n      \"/base/path\",\n      \"a/target.ext\",\n      { 23, \"/base/path/a/target.ext\" },\n      { 23, \"\\\\base\\\\path\\\\a\\\\target.ext\" }\n    ),\n    path_target_output::posixroot(\n      \"/do/not/duplicate/\",\n      \"this/slash\",\n      { 28, \"/do/not/duplicate/this/slash\" },\n      { 28, \"\\\\do\\\\not\\\\duplicate\\\\this\\\\slash\" }\n    ),\n    path_target_output::posixroot(\n      \"/loool/\",\n      \"loool/\",\n      { 12, \"/loool/loool\" },\n      { 12, \"\\\\loool\\\\loool\" }\n    ),\n    path_target_output::posixroot(\n      \"C:\\\\dos\\\\path\",\n      \"to\\\\join\",\n      { 19, \"C:/dos/path/to/join\" },\n      { 19, \"C:\\\\dos\\\\path\\\\to\\\\join\" },\n      { 18, \"C:dos/path/to/join\" }\n    ),\n    path_target_output::posixroot(\n      \"some:amiga/nonsense///\",\n      \"augh\",\n      { 24, \"some:amiga/nonsense/augh\" },\n      { 24, \"some:amiga\\\\nonsense\\\\augh\" },\n      { 26, \"some:amiga/nonsense///augh\" }\n    ),\n  };\n  // Assume a buffer size of 32 for these.\n  static constexpr const path_target_output small_data[] =\n  {\n    path_target_output::posixroot(\n      \"/should/barely/fit\",\n      \"this/string\",\n      { 30, \"/should/barely/fit/this/string\" },\n      { 30, \"\\\\should\\\\barely\\\\fit\\\\this\\\\string\" }\n    ),\n    path_target_output::posixroot(\n      \"C:/an/exact/fit\",\n      \"and/should/pass\",\n      { 31, \"C:/an/exact/fit/and/should/pass\" },\n      { 31, \"C:\\\\an\\\\exact\\\\fit\\\\and\\\\should\\\\pass\" },\n      { 30, \"C:an/exact/fit/and/should/pass\" }\n    ),\n    path_target_output::all(\n      \"/should/not/be/able/to/fit\",\n      \"these/strings\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \"C:\\\\wow\\\\this\\\\path\\\\is\\\\kinda\\\\very\\\\long\\\\\",\n      \"whatever\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \"C:\\\\\",\n      \"wtf\\\\is\\\\wrong\\\\with\\\\you\\\\no\\\\seriously\",\n      { -1, nullptr }\n    ),\n  };\n\n  char buffer[MAX_PATH];\n  ssize_t result;\n\n  SECTION(path_append_NormalCases)\n  {\n    for(const path_target_output &d : data)\n    {\n      snprintf(buffer, MAX_PATH, \"%s\", d.path);\n      buffer[MAX_PATH - 1] = '\\0';\n\n      result = path_append(buffer, MAX_PATH, d.target);\n      ASSERTEQ(result, d.PATH_SELECTOR.return_value, \"%s\", d.path);\n      if(result && d.PATH_SELECTOR.result)\n        ASSERTCMP(buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_append_SmallBufferCases)\n  {\n    for(const path_target_output &d : small_data)\n    {\n      snprintf(buffer, MAX_PATH, \"%s\", d.path);\n      buffer[MAX_PATH - 1] = '\\0';\n\n      result = path_append(buffer, 32, d.target);\n      ASSERTEQ(result, d.PATH_SELECTOR.return_value, \"%s\", d.path);\n      if(result && d.PATH_SELECTOR.result)\n      {\n        ASSERTCMP(buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n      }\n      else\n        ASSERTCMP(buffer, d.path, \"\");\n    }\n  }\n\n  SECTION(path_join_NormalCases)\n  {\n    for(const path_target_output &d : data)\n    {\n      result = path_join(buffer, MAX_PATH, d.path, d.target);\n      ASSERTEQ(result, d.PATH_SELECTOR.return_value, \"%s\", d.path);\n      if(result && d.PATH_SELECTOR.result)\n        ASSERTCMP(buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_join_SmallBufferCases)\n  {\n    for(const path_target_output &d : small_data)\n    {\n      static const char *def = \"DO NOT MODIFY\";\n      snprintf(buffer, MAX_PATH, \"%s\", def);\n\n      result = path_join(buffer, 32, d.path, d.target);\n      ASSERTEQ(result, d.PATH_SELECTOR.return_value, \"%s\", d.path);\n      if(result && d.PATH_SELECTOR.result)\n      {\n        ASSERTCMP(buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n      }\n      else\n        ASSERTCMP(buffer, def, \"%s\", d.path);\n    }\n  }\n}\n\n\nUNITTEST(path_remove_prefix)\n{\n  static constexpr const path_target_output data[] =\n  {\n    path_target_output::all(\n      \"\",\n      \"\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \"valid path\",\n      \"\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \"\",\n      \"valid prefix\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \"/some/path/here/with/an/invalid/prefix\",\n      \"/some/p\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \"/some/regular/path\",\n      \"/some/regular/path/except/the/prefix/is/really/long\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \"C:\\\\dont\\\\mix\\\\root\\\\styles\",\n      \"\\\\dont\\\\mix\\\\root\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \"/some/path/here\",\n      \"/some/path\",\n      { 4, \"here\" }\n    ),\n    path_target_output::posixroot(\n      \"/some/prefix/some/path/here\",\n      \"/some/prefix/\",\n      { 14, \"some/path/here\" },\n      { 14, \"some\\\\path\\\\here\" }\n    ),\n    path_target_output::all(\n      \"C:\\\\a\\\\dos\\\\style\\\\prefixed\\\\path\",\n      \"C:\\\\a\\\\dos\\\\style\\\\prefixed\",\n      { 4, \"path\" }\n    ),\n    path_target_output::posixroot(\n      \"work\\\\on\\\\relative\\\\paths\\\\too\\\\thanks\",\n      \"work\\\\on\\\\relative\\\\\",\n      { 16, \"paths/too/thanks\" },\n      { 16, \"paths\\\\too\\\\thanks\" }\n    ),\n    path_target_output::all(\n      \"consume/all/slashes////////////////////////////////////thanks\",\n      \"consume/all/slashes\",\n      { 6, \"thanks\" }\n    ),\n    path_target_output::all(\n      \"/allow/mixed/slash/styles\",\n      \"\\\\allow/mixed\\\\slash\",\n      { 6, \"styles\" }\n    ),\n    path_target_output::all(\n      \"merge//prefix\\\\\\\\slashes//////thanks\",\n      \"merge/\\\\//\\\\\\\\prefix///////////\\\\slashes\",\n      { 6, \"thanks\" }\n    ),\n  };\n  char buffer[MAX_PATH];\n  ssize_t result;\n\n  SECTION(NoPrefixLength)\n  {\n    for(const path_target_output &d : data)\n    {\n      snprintf(buffer, MAX_PATH, \"%s\", d.path);\n      buffer[MAX_PATH - 1] = '\\0';\n\n      result = path_remove_prefix(buffer, MAX_PATH, d.target, 0);\n      ASSERTEQ(result, d.PATH_SELECTOR.return_value, \"%s\", d.path);\n      if(result >= 0)\n        ASSERTCMP(buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n      else\n        ASSERTCMP(buffer, d.path, \"\");\n    }\n  }\n\n  SECTION(PrefixLength)\n  {\n    for(const path_target_output &d : data)\n    {\n      snprintf(buffer, MAX_PATH, \"%s\", d.path);\n      buffer[MAX_PATH - 1] = '\\0';\n\n      result = path_remove_prefix(buffer, MAX_PATH, d.target,\n       strlen(d.target));\n      ASSERTEQ(result, d.PATH_SELECTOR.return_value, \"%s\", d.path);\n      if(result >= 0)\n        ASSERTCMP(buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n      else\n        ASSERTCMP(buffer, d.path, \"\");\n    }\n  }\n}\n\n\nstruct path_safe_output\n{\n  const char *path;\n  int result_mask;\n};\n\nUNITTEST(path_safety_check)\n{\n  static constexpr const struct path_safe_output data[] =\n  {\n    /* Should not be rejected by any of the checks. */\n    { \"\", 0 },\n    { \"a\", 0 },\n    { \"01\", 0 },\n    { \"filename.ext\", 0 },\n    { \"longfilename.longext\", 0, },\n    { \"longfi~1.lon\", 0, },\n    { \".\", 0, },\n    { \"./duhh\", 0, },\n    { \"why/./did/./you/./put\\\\.\\\\this/./in/./your/./game\\\\\", 0, },\n    { \"..wat\", 0, },\n    { \"samd..wich\", 0, },\n    { \"awful../\", 0, },\n    { \"a/..crime/\", 0, },\n    { \"another../crime\", 0, },\n    { \"its/allowed..\", 0, },\n    { \"...\", 0, },\n    { \"........\", 0, },\n    { \"con5\", 0, },\n    { \"auxx\", 0, },\n    { \"null\", 0, },\n    { \"hello.lpt5.txt\", 0, },\n    { \".aux\", 0, },\n    { \"hello.prn\", 0, },\n    { \"com\", 0, },\n    { \"lpt\", 0, },\n    { \"com#\", 0, },\n    { \"lpt$\", 0, },\n\n    /* Unix root check */\n    {\n      \"/hellow\",\n      PATH_SAFE_UNIX_ROOT,\n    },\n    {\n      \"\\\\mealso\",\n      PATH_SAFE_UNIX_ROOT,\n    },\n    /* DOS/Amiga root check (reject anything containing ':')\n     * This check is fully overlapped by PATH_SAFE_DOS_CHARACTER */\n    {\n      \"C:/dosroot\",\n      PATH_SAFE_DOS_ROOT | PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \"C:\\\\WINDOWS\\\\SYSTEM32\\\\SVCHOST.EXE\",\n      PATH_SAFE_DOS_ROOT | PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \"this:too\",\n      PATH_SAFE_DOS_ROOT | PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \"amiga/can\\\\do:this\",\n      PATH_SAFE_DOS_ROOT | PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \":also/amiga\",\n      PATH_SAFE_DOS_ROOT | PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \"/twoofthem:\",\n      PATH_SAFE_UNIX_ROOT | PATH_SAFE_DOS_ROOT | PATH_SAFE_DOS_CHARACTER,\n    },\n    /* Unix parent check (\"..\" at start/end or surrounded by slashes) */\n    {\n      \"..\",\n      PATH_SAFE_UNIX_PARENT,\n    },\n    {\n      \"..\\\\\",\n      PATH_SAFE_UNIX_PARENT,\n    },\n    {\n      \"loololol/../WINDOWS/SYSTEM32/BONER.DLL\",\n      PATH_SAFE_UNIX_PARENT,\n    },\n    {\n      \"../lol\",\n      PATH_SAFE_UNIX_PARENT,\n    },\n    {\n      \"not\\\\here\\\\either\\\\..\",\n      PATH_SAFE_UNIX_PARENT,\n    },\n    {\n      \"......./..\",\n      PATH_SAFE_UNIX_PARENT,\n    },\n    {\n      \"/..\",\n      PATH_SAFE_UNIX_ROOT | PATH_SAFE_UNIX_PARENT,\n    },\n    {\n      \"C:\\\\..\\\\a\",\n      PATH_SAFE_DOS_ROOT | PATH_SAFE_UNIX_PARENT | PATH_SAFE_DOS_CHARACTER,\n    },\n    /* Amiga parent check (any double-slashes) */\n    {\n      \"escape///sandbox//\",\n      PATH_SAFE_AMIGA_PARENT\n    },\n    {\n      \"\\\\\\\\unc\\\\localhost\\\\top\\\\seekrit\",\n      PATH_SAFE_UNIX_ROOT | PATH_SAFE_AMIGA_PARENT,\n    },\n    {\n      \"\\\\\\\\.\\\\c:\\\\windows\\\\system32\\\\lsass.exe\",\n      PATH_SAFE_UNIX_ROOT | PATH_SAFE_DOS_ROOT | PATH_SAFE_AMIGA_PARENT |\n      PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \"//?/c:/windows/system32/lsass.exe\",\n      PATH_SAFE_UNIX_ROOT | PATH_SAFE_DOS_ROOT | PATH_SAFE_AMIGA_PARENT |\n      PATH_SAFE_DOS_CHARACTER,\n    },\n    /* DOS reserved characters (NTFS/VFAT/exFAT only). */\n    {\n      \"\\\"hewwo\\\"\",\n      PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \"a*_path_finder.txt\",\n      PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \"<duhhh\",\n      PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \"invalid>\",\n      PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \"hope no one uses this??\",\n      PATH_SAFE_DOS_CHARACTER\n    },\n    {\n      \"ugh|hhh\",\n      PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \"this i guess\\r\\n\",\n      PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \"/barf/../o//matiq\\x1f\",\n      PATH_SAFE_UNIX_ROOT | PATH_SAFE_UNIX_PARENT |\n      PATH_SAFE_AMIGA_PARENT | PATH_SAFE_DOS_CHARACTER,\n    },\n    {\n      \"com\\x02\",\n      PATH_SAFE_DOS_CHARACTER,\n    },\n    /* DOS devices in final tokens. */\n    { \"AUX\",  PATH_SAFE_DOS_DEVICE, },\n    { \"aux\",  PATH_SAFE_DOS_DEVICE, },\n    { \"cOn\",  PATH_SAFE_DOS_DEVICE, },\n    { \"nul\",  PATH_SAFE_DOS_DEVICE, },\n    { \"prn\",  PATH_SAFE_DOS_DEVICE, },\n    { \"com1\", PATH_SAFE_DOS_DEVICE, },\n    { \"com2\", PATH_SAFE_DOS_DEVICE, },\n    { \"com3\", PATH_SAFE_DOS_DEVICE, },\n    { \"com4\", PATH_SAFE_DOS_DEVICE, },\n    { \"com5\", PATH_SAFE_DOS_DEVICE, },\n    { \"com6\", PATH_SAFE_DOS_DEVICE, },\n    { \"com7\", PATH_SAFE_DOS_DEVICE, },\n    { \"com8\", PATH_SAFE_DOS_DEVICE, },\n    { \"com9\", PATH_SAFE_DOS_DEVICE, },\n    { \"com\\xb9\", PATH_SAFE_DOS_DEVICE, },\n    { \"com\\xb2\", PATH_SAFE_DOS_DEVICE, },\n    { \"com\\xb3\", PATH_SAFE_DOS_DEVICE, },\n    { \"lpt1\", PATH_SAFE_DOS_DEVICE, },\n    { \"lpt2\", PATH_SAFE_DOS_DEVICE, },\n    { \"lpt3\", PATH_SAFE_DOS_DEVICE, },\n    { \"lpt4\", PATH_SAFE_DOS_DEVICE, },\n    { \"lpt5\", PATH_SAFE_DOS_DEVICE, },\n    { \"lpt6\", PATH_SAFE_DOS_DEVICE, },\n    { \"lpt7\", PATH_SAFE_DOS_DEVICE, },\n    { \"lpt8\", PATH_SAFE_DOS_DEVICE, },\n    { \"lpt9\", PATH_SAFE_DOS_DEVICE, },\n    { \"lpt\\xb9\", PATH_SAFE_DOS_DEVICE, },\n    { \"lpt\\xb2\", PATH_SAFE_DOS_DEVICE, },\n    { \"lpt\\xb3\", PATH_SAFE_DOS_DEVICE, },\n    {\n      \"fat://com4\",\n      PATH_SAFE_DOS_ROOT | PATH_SAFE_AMIGA_PARENT |\n      PATH_SAFE_DOS_CHARACTER | PATH_SAFE_DOS_DEVICE,\n    },\n    {\n      \"/home/awawa/lpt1\",\n      PATH_SAFE_UNIX_ROOT | PATH_SAFE_DOS_DEVICE,\n    },\n    {\n      \"xxydata/m/con.ogg\",\n      PATH_SAFE_DOS_DEVICE,\n    },\n    {\n      \"dagamezone\\\\aux.tar.gz\",\n      PATH_SAFE_DOS_DEVICE,\n    },\n    {\n      \"lpt7.sdjfsdjfds.sdjgjfdkhdfh.yes.really.txt\",\n      PATH_SAFE_DOS_DEVICE,\n    },\n    /* DOS devices in directory tokens.\n     * These only seem to cause issues when accessed as files, but check and\n     * reject them anyway to discourage Linux users from naming directories.\n     */\n    {\n      \"prn/some_files\",\n      PATH_SAFE_DOS_DEVICE,\n    },\n    {\n      \"com2\\\\yep\\\\reject\\\\this\",\n      PATH_SAFE_DOS_DEVICE\n    },\n    {\n      \"maybe/com1/this/should/fail\",\n      PATH_SAFE_DOS_DEVICE,\n    },\n    {\n      \"tiresome\\\\tbh\\\\nul.sdfjsdkljgfdlgfgm.b.cv.bvc.bkwke..bvc\\\\hewo.txt\",\n      PATH_SAFE_DOS_DEVICE,\n    },\n  };\n\n  const auto &do_tests = [&](int testvals)\n  {\n    enum path_safe_mask result;\n\n    for(const path_safe_output &d : data)\n    {\n      int shared = testvals & d.result_mask;\n      /* Any path that would flag multiple issues should\n       * always return the lowest-order flag. */\n      enum path_safe_mask expected = shared ?\n       static_cast<enum path_safe_mask>(shared & -shared) : PATH_SAFE_OK;\n\n      result = path_safety_check(d.path, testvals);\n      ASSERTEQ(result, expected, \"%s: %04xh & %04xh\", d.path, (int)result, d.result_mask);\n    }\n  };\n\n  SECTION(UnixRoot)\n    do_tests(PATH_SAFE_UNIX_ROOT);\n\n  SECTION(DosRoot)\n    do_tests(PATH_SAFE_DOS_ROOT);\n\n  SECTION(AnyRoot)\n    do_tests(PATH_SAFE_ANY_ROOT);\n\n  SECTION(UnixParent)\n    do_tests(PATH_SAFE_UNIX_PARENT);\n\n  SECTION(AmigaParent)\n    do_tests(PATH_SAFE_AMIGA_PARENT);\n\n  SECTION(AnyRootParent)\n    do_tests(PATH_SAFE_ANY_ROOT | PATH_SAFE_UNIX_PARENT | PATH_SAFE_AMIGA_PARENT);\n\n  SECTION(DosCharacter)\n    do_tests(PATH_SAFE_DOS_CHARACTER);\n\n  SECTION(DosDevice)\n    do_tests(PATH_SAFE_DOS_DEVICE);\n\n  SECTION(Any)\n    do_tests(PATH_SAFE_ANY);\n\n  SECTION(path_safety_strerror)\n  {\n    size_t i;\n    size_t j;\n\n    for(i = 0; i < PATH_SAFE_ANY * 4; i++)\n    {\n      const char *ret = path_safety_strerror(static_cast<enum path_safe_mask>(i));\n\n      if(i == 0)\n      {\n        ASSERTEQ(ret, path_safety_strerror(PATH_SAFE_OK), \"\");\n        continue;\n      }\n      for(j = 1; j < PATH_SAFE_ANY; j <<= 1)\n      {\n        if(~i & j)\n          continue;\n\n        ASSERTEQ(ret, path_safety_strerror(static_cast<enum path_safe_mask>(j)),\n         \"%zx and %zx should get the same error string\", i, j);\n        break;\n      }\n      if(j >= PATH_SAFE_ANY)\n        ASSERTCMP(ret, \"unknown\", \"\");\n    }\n  }\n}\n\n\nUNITTEST(path_navigate)\n{\n  static constexpr const path_target_output no_check[] =\n  {\n    path_target_output::all(\n      \"\",\n      \"\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \"lol\",\n      \"\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \"\",\n      \"lol\",\n      { -1, nullptr }\n    ),\n    path_target_output::posixdosroot(\n      \"/abc\",\n      \"malformed/root:/path/\",\n      { 25, \"/abc/malformed/root:/path\" },\n      { -1, nullptr },\n      { -1, nullptr },\n      { 19, \"malformed/root:path\"}\n    ),\n    path_target_output::posixroot(\n      \"/start/path\",\n      \"relative/target\",\n      { 27, \"/start/path/relative/target\" },\n      { 27, \"\\\\start\\\\path\\\\relative\\\\target\" }\n    ),\n    path_target_output::posixroot(\n      \"/\",\n      \"hello\",\n      { 6, \"/hello\" },\n      { 6, \"\\\\hello\" }\n    ),\n    path_target_output::posixroot(\n      \"C:\\\\\",\n      \"a\",\n      { 4, \"C:/a\" },\n      { 4, \"C:\\\\a\" },\n      { 3, \"C:a\" }\n    ),\n    path_target_output::posixroot(\n      \"/some/path\",\n      \"..\",\n      { 5, \"/some\" },\n      { 5, \"\\\\some\" },\n      { 13, \"/some/path/..\" }\n    ),\n    path_target_output::posixroot(\n      \"/\",\n      \"..\",\n      { 1, \"/\" },\n      { 1, \"\\\\\" },\n      { 3, \"/..\" }\n    ),\n    path_target_output::posixroot(\n      \"/another/path\",\n      \"./../path/../../../another/./\",\n      { 8, \"/another\" },\n      { 8, \"\\\\another\" },\n      { 42, \"/another/path/./../path/../../../another/.\" }\n    ),\n    path_target_output::posixroot(\n      \"/start/path\",\n      \"/an/absolute/path\",\n      { 17, \"/an/absolute/path\" },\n      { 17, \"\\\\an\\\\absolute\\\\path\" },\n      { 23, \"/start/an/absolute/path\" }\n    ),\n    path_target_output::posixroot(\n      \"jdflkjsdlfjksdklfjsdlksjdfklsd\",\n      \"\\\\also\\\\an\\\\absolute\\\\path\",\n      { 22, \"/also/an/absolute/path\" },\n      { 22, \"\\\\also\\\\an\\\\absolute\\\\path\" },\n      { 52, \"jdflkjsdlfjksdklfjsdlksjdfklsd/also/an/absolute/path\" }\n    ),\n    path_target_output::posixdosroot(\n      \"C:\\\\start\\\\path\",\n      \"D:\\\\folder\",\n      { 23, \"C:/start/path/D:/folder\" },\n      { 9, \"D:/folder\" },\n      { 9, \"D:\\\\folder\" },\n      { 8, \"D:folder\" }\n    ),\n    path_target_output::posixdosroot(\n      \"/some/directory\",\n      \"C:\",\n      { 18, \"/some/directory/C:\" },\n      { 3, \"C:/\" },\n      { 3, \"C:\\\\\" },\n      { 2, \"C:\" }\n    ),\n    path_target_output::posixdosroot(\n      \"C:\\\\\\\\start\\\\path\",\n      \"D:\\\\\\\\folder\",\n      { 10, \"D://folder\" },\n      { 9, \"D:/folder\" },\n      { 9, \"D:\\\\folder\" },\n      { 8, \"D:folder\" }\n    ),\n    path_target_output::posixdosroot(\n      \"/some/directory2\",\n      \"C://\",\n      { 4, \"C://\" },\n      { 3, \"C:/\" },\n      { 3, \"C:\\\\\" },\n      { 2, \"C:\" }\n    ),\n    path_target_output::posixdosroot(\n      \"ahhkillme://\",\n      \"..\",\n      { 12, \"ahhkillme://\" },\n      { 11, \"ahhkillme:/\" },\n      { 11, \"ahhkillme:\\\\\" },\n      { 12, \"ahhkillme:..\" }\n    ),\n    path_target_output::posixroot(\n      \"/cwd\",\n      \"mix\\\\up/some\\\\of/these\\\\slashes/lol\",\n      { 37, \"/cwd/mix/up/some/of/these/slashes/lol\" },\n      { 37, \"\\\\cwd\\\\mix\\\\up\\\\some\\\\of\\\\these\\\\slashes\\\\lol\" }\n    ),\n    path_target_output::posixroot(\n      \"/skdlfjlskdjfklsd/\",\n      \"i/am\\\\sure/..\\\\someone/relies\\\\..\\\\on/this\",\n      { 38, \"/skdlfjlskdjfklsd/i/am/someone/on/this\" },\n      { 38, \"\\\\skdlfjlskdjfklsd\\\\i\\\\am\\\\someone\\\\on\\\\this\" },\n      { 56, \"/skdlfjlskdjfklsd/i/am/sure/../someone/relies/../on/this\" }\n    ),\n    path_target_output::posixroot(\n      \"/.yeah/.actually/.dotfiles/.should/.work\",\n      \"../../.work\",\n      { 32, \"/.yeah/.actually/.dotfiles/.work\" },\n      { 32, \"\\\\.yeah\\\\.actually\\\\.dotfiles\\\\.work\" },\n      { 52, \"/.yeah/.actually/.dotfiles/.should/.work/../../.work\" }\n    ),\n    path_target_output::posixroot(\n      \"look/more/nonsense\",\n      \".../lol\",\n      { 26, \"look/more/nonsense/.../lol\" },\n      { 26, \"look\\\\more\\\\nonsense\\\\...\\\\lol\"}\n    ),\n    // Amiga-style parent directory navigation\n    path_target_output::posixroot(\n      \"sys:software/octamed\",\n      \"/\",\n      { 1, \"/\" },\n      { 1, \"\\\\\" },\n      { 12, \"sys:software\" }\n    ),\n    path_target_output::posixroot(\n      \"a1200:da_megazeux_zone\",\n      \"/\",\n      { 1, \"/\" },\n      { 1, \"\\\\\" },\n      { 6, \"a1200:\" }\n    ),\n    path_target_output::posixroot(\n      \"fd0:this/is/pretty/horrible/tbh\",\n      \"aaaa//wtf/awful//////working\",\n      { 54, \"fd0:this/is/pretty/horrible/tbh/aaaa/wtf/awful/working\" },\n      { 54, \"fd0:this\\\\is\\\\pretty\\\\horrible\\\\tbh\\\\aaaa\\\\wtf\\\\awful\\\\working\" },\n      { 19, \"fd0:this/is/working\" }\n    ),\n    path_target_output::posixdosroot(\n      \"daharddisk:ok/surely/this/will/be/normal\",\n      \":\",\n      { 42, \"daharddisk:ok/surely/this/will/be/normal/:\" },\n      { -1, nullptr },\n      { -1, nullptr },\n      { 1, \":\" }\n    ),\n    path_target_output::posixdosroot(\n      \"awawa:hello/world\",\n      \":Software\",\n      { 27, \"awawa:hello/world/:Software\" },\n      { -1, nullptr },\n      { -1, nullptr },\n      { 9, \":Software\" }\n    ),\n    path_target_output::posixroot(\n      \"cant:go/past/root\",\n      \"//////\",\n      { 1, \"/\" },\n      { 1, \"\\\\\" },\n      { 5, \"cant:\" }\n    ),\n    // Windows UNC paths\n    path_target_output::uncroot(\n      \"\\\\\\\\.\\\\C:\",\n      \"Program Files\",\n      { 19, \"/./C:/Program Files\" },\n      { -1, nullptr },\n      { 20, \"\\\\\\\\.\\\\C:\\\\Program Files\" },\n      { 19, \"\\\\\\\\.\\\\C:Program Files\" }\n    ),\n    path_target_output::uncroot(\n      \"\\\\\\\\localhost\\\\share\\\\folder\",\n      \"..\",\n      { 16, \"/localhost/share\" },\n      { 16, \"\\\\localhost\\\\share\" },\n      { 18, \"\\\\\\\\localhost\\\\share\\\\\" },\n      { 27, \"//localhost/share/folder/..\" }\n    ),\n    path_target_output::uncroot(\n      \"\\\\\\\\?\\\\unc\\\\localhost\\\\c$\\\\\",\n      \"whymustyoudothis/..\",\n      { 19, \"/?/unc/localhost/c$\" },\n      { 19, \"\\\\?\\\\unc\\\\localhost\\\\c$\" },\n      { 21, \"\\\\\\\\?\\\\unc\\\\localhost\\\\c$\\\\\" },\n      { 40, \"//?/unc/localhost/c$/whymustyoudothis/..\" }\n    ),\n    path_target_output::uncroot(\n      \"\\\\\\\\.\\\\C:\\\\nope\\\\\",\n      \"..\\\\..\",\n      { 2, \"/.\" },\n      { 2, \"\\\\.\" },\n      { 7, \"\\\\\\\\.\\\\C:\\\\\" },\n      { 16, \"\\\\\\\\.\\\\C:nope/../..\" }\n    ),\n    path_target_output::uncroot(\n      \"\\\\\\\\127.0.0.1\\\\why\",\n      \"..\",\n      { 10, \"/127.0.0.1\" },\n      { 10, \"\\\\127.0.0.1\" },\n      { 16, \"\\\\\\\\127.0.0.1\\\\why\\\\\" },\n      { 18, \"//127.0.0.1/why/..\" }\n    ),\n  };\n  static const path_target_output with_check[] =\n  {\n    path_target_output::all(\n      \"\",\n      \"\",\n      { -1, nullptr }\n    ),\n    path_target_output::all(\n      \".\",\n      PATH_DIR_NOT_EXISTS,\n      { -1, nullptr }\n    ),\n    path_target_output::posixroot(\n      \".\",\n      PATH_DIR_EXISTS,\n      { 17, \"./\" PATH_DIR_EXISTS },\n      { 17, \".\\\\\" PATH_DIR_EXISTS },\n      { -1, nullptr }\n    ),\n    path_target_output::posixroot(\n      PATH_FILE_RECURSIVE,\n      \"..\",\n      { 18, PATH_DIR_RECURSIVE },\n      { 18, PATH_DIR_RECURSIVE },\n      { -1, nullptr }\n    ),\n    path_target_output::posixroot(\n      PATH_FILE_RECURSIVE,\n      \"/\",\n      { 1, \"/\" },\n      { 1, \"\\\\\" },\n      { 18, PATH_DIR_RECURSIVE }\n    ),\n  };\n  char buffer[MAX_PATH];\n  ssize_t result;\n\n  SECTION(path_navigate_no_check)\n  {\n    for(const path_target_output &d : no_check)\n    {\n      snprintf(buffer, MAX_PATH, \"%s\", d.path);\n      buffer[MAX_PATH - 1] = '\\0';\n\n      result = path_navigate_no_check(buffer, MAX_PATH, d.target);\n      ASSERTEQ(result, d.PATH_SELECTOR.return_value, \"%s\", d.path);\n      if(result && d.PATH_SELECTOR.result)\n        ASSERTCMP(buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n    }\n  }\n\n  SECTION(path_navigate)\n  {\n    for(const path_target_output &d : with_check)\n    {\n      snprintf(buffer, MAX_PATH, \"%s\", d.path);\n      buffer[MAX_PATH - 1] = '\\0';\n\n      result = path_navigate(buffer, MAX_PATH, d.target);\n      ASSERTEQ(result, d.PATH_SELECTOR.return_value, \"%s\", d.path);\n      if(result && d.PATH_SELECTOR.result)\n        ASSERTCMP(buffer, d.PATH_SELECTOR.result, \"%s\", d.path);\n    }\n  }\n}\n\n\nstruct path_mkdir_data\n{\n  const char *path;\n  enum path_create_error expected;\n};\n\nUNITTEST(path_create_parent_recursively)\n{\n  static const path_mkdir_data data[] =\n  {\n    // Parent does not exist, call succeeds.\n    { PATH_DIR_RECURSIVE_3 DIR_SEPARATOR PATH_FILE_EXISTS,\n      PATH_CREATE_SUCCESS },\n    // No parent in the input path, call succeeds.\n    { \"config.txt\", PATH_CREATE_SUCCESS },\n    { \"lol.cnf\", PATH_CREATE_SUCCESS },\n    { \"megazeux.exe\", PATH_CREATE_SUCCESS },\n    // Parent exists, call succeeds.\n    { PATH_FILE_RECURSIVE, PATH_CREATE_SUCCESS },\n    // mkdir fails due to existing file.\n    { PATH_FILE_RECURSIVE DIR_SEPARATOR PATH_DIR_RECURSIVE,\n      PATH_CREATE_ERR_FILE_EXISTS },\n    // TODO: PATH_CREATE_ERR_MKDIR_FAILED is returned when mkdir fails.\n    // TODO: PATH_CREATE_ERR_STAT_ERROR is returned when stat fails.\n  };\n\n  for(const path_mkdir_data &cur : data)\n  {\n    enum path_create_error ret = path_create_parent_recursively(cur.path);\n    ASSERTEQ(ret, cur.expected, \"%s\", cur.path);\n  }\n}\n"
  },
  {
    "path": "unit/io/vfs.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2021-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../Unit.hpp\"\n\n#include \"../../src/io/path.h\"\n#include \"../../src/io/vfs.h\"\n#include \"../../src/io/vio.h\"\n\n#include <unistd.h>\n#include <sys/stat.h>\n\nstatic constexpr size_t GETCWD_BUF = 32;\nstatic constexpr enum vfs_error ignore = static_cast<vfs_error>(10000);\n\nclass ScopedVFS\n{\n  vfilesystem *vfs;\n\npublic:\n  ScopedVFS(vfilesystem *p = nullptr) : vfs(p) {}\n  ~ScopedVFS() { if(vfs) vfs_free(vfs); }\n\n  operator vfilesystem *() { return vfs; }\n};\n\nclass ScopedVFSDir : public vfs_dir\n{\npublic:\n  ScopedVFSDir()\n  {\n    files = nullptr;\n    num_files = 0;\n  }\n\n  ~ScopedVFSDir()\n  {\n    vfs_readdir_free(this);\n  }\n};\n\nstruct vfs_result\n{\n  const char *path;\n  int expected_ret; /* enum vfs_error or 0 */\n};\n\nenum op\n{\n  DO_STAT,\n  DO_CHDIR,\n  DO_GETCWD,\n  DO_CREATE,\n  DO_MKDIR,\n  DO_RENAME,\n  DO_UNLINK,\n  DO_RMDIR,\n  DO_ACCESS,\n  DO_MAKE_ROOT,\n} op;\n\nstruct vfs_stat_data\n{\n  ino_t inode;\n  mode_t filetype;\n  off_t size;\n};\n\nstruct vfs_stat_result\n{\n  const char *path;\n  int expected_ret; /* enum vfs_error or 0 */\n  vfs_stat_data st;\n};\n\nstruct vfs_op_result\n{\n  const char *path;\n  const char *path2;\n  enum op op;\n  int expected_ret; /* enum vfs_error or 0 */\n  int stat_ret;     /* enum vfs_error or 0 */\n  vfs_stat_data st;\n};\n\nstatic void check_stat(const char *path, const vfs_stat_data &d, struct stat &st,\n time_t create_time = -1, time_t modify_time = -1)\n{\n  ASSERTEQ(st.st_dev, VFS_MZX_DEVICE, \"%s\", path);\n  ASSERTEQ(st.st_ino, d.inode, \"%s\", path);\n  ASSERTEQ(st.st_mode & S_IFMT, d.filetype, \"%s\", path);\n  ASSERTEQ(st.st_size, d.size, \"%s\", path);\n  ASSERT(st.st_atime, \"%s\", path);\n  ASSERT(st.st_mtime, \"%s\", path);\n  ASSERT(st.st_ctime, \"%s\", path);\n  ASSERT(st.st_atime >= create_time, \"%s\", path);\n  ASSERT(st.st_mtime >= modify_time, \"%s\", path);\n  ASSERT(st.st_ctime >= modify_time, \"%s\", path);\n}\n\nstatic void do_op_and_stat(vfilesystem *vfs, const vfs_op_result &d,\n time_t create_time = -1, time_t modify_time = -1)\n{\n  const char *st_path = d.path;\n  char buffer[MAX_PATH];\n  enum vfs_error ret;\n  switch(d.op)\n  {\n    case DO_STAT:\n      break;\n    case DO_CHDIR:\n      st_path = \".\";\n      ret = vfs_chdir(vfs, d.path);\n      ASSERTEQ(ret, d.expected_ret, \"chdir: %s\", d.path);\n      break;\n    case DO_GETCWD:\n      char getcwd_buffer[GETCWD_BUF];\n      ret = vfs_getcwd(vfs, getcwd_buffer, sizeof(getcwd_buffer));\n      ASSERTEQ(ret, d.expected_ret, \"getcwd: %s\", d.path);\n      if(!ret)\n        ASSERTCMP(getcwd_buffer, d.path, \"getcwd\");\n      break;\n    case DO_CREATE:\n      ret = vfs_create_file_at_path(vfs, d.path);\n      ASSERTEQ(ret, d.expected_ret, \"create: %s\", d.path);\n      break;\n    case DO_MKDIR:\n      ret = vfs_mkdir(vfs, d.path, 0777);\n      ASSERTEQ(ret, d.expected_ret, \"mkdir: %s\", d.path);\n      break;\n    case DO_RENAME:\n      st_path = d.path2;\n      ret = vfs_rename(vfs, d.path, d.path2);\n      ASSERTEQ(ret, d.expected_ret, \"rename: %s -> %s\", d.path, d.path2);\n      break;\n    case DO_UNLINK:\n      ret = vfs_unlink(vfs, d.path);\n      ASSERTEQ(ret, d.expected_ret, \"unlink: %s\", d.path);\n      break;\n    case DO_RMDIR:\n      ret = vfs_rmdir(vfs, d.path);\n      ASSERTEQ(ret, d.expected_ret, \"rmdir: %s\", d.path);\n      break;\n    case DO_ACCESS:\n      ret = vfs_access(vfs, d.path, R_OK);\n      ASSERTEQ(ret, d.expected_ret, \"access: %s\", d.path);\n      break;\n    case DO_MAKE_ROOT:\n      ret = vfs_make_root(vfs, d.path);\n      ASSERTEQ(ret, d.expected_ret, \"make_root: %s\", d.path);\n      snprintf(buffer, MAX_PATH, \"%s://\", d.path);\n      st_path = buffer;\n      break;\n  }\n\n  struct stat st;\n  ret = vfs_stat(vfs, st_path, &st);\n  ASSERTEQ(ret, d.stat_ret, \"%s\", d.path);\n  if(!ret)\n    check_stat(d.path, d.st, st, create_time, modify_time);\n}\n\nUNITTEST(vfs_stat)\n{\n  // Simple test to make sure it works on known valid/invalid default files.\n  // This function will be further tested in other tests that use it to fetch\n  // info to verify other calls worked.\n\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static const vfs_stat_result valid_data[]\n  {\n    // These are all the default root since that's the only default file...\n    { \"..\", 0,        { 1, S_IFDIR, 0 }},\n    { \".\", 0,         { 1, S_IFDIR, 0 }},\n    { \"./\", 0,        { 1, S_IFDIR, 0 }},\n    { \"/\", 0,         { 1, S_IFDIR, 0 }},\n    { \"/./../\", 0,    { 1, S_IFDIR, 0 }},\n    { \"\\\\\", 0,        { 1, S_IFDIR, 0 }},\n    { \"\\\\..\\\\.\", 0,   { 1, S_IFDIR, 0 }},\n#ifdef VIRTUAL_FILESYSTEM_DOS_DRIVE\n    { \"C:/\", 0,       { 1, S_IFDIR, 0 }},\n    { \"C:\\\\\", 0,      { 1, S_IFDIR, 0 }},\n    { \"c:\\\\\", 0,      { 1, S_IFDIR, 0 }},\n#endif\n  };\n\n  static const vfs_result invalid_data[]\n  {\n    { \"\", VFS_ENOENT },\n    { \"jsdjfk\", VFS_ENOENT },\n    { \"sdcard:/\", VFS_ENOENT },\n    { \"sdcard://\", VFS_ENOENT },\n    { \"verylongrootnamewhywouldyouevennamearootthis:/\", VFS_ENOENT },\n    { \"D:\\\\\", VFS_ENOENT },\n    { \"D:\\\\\\\\\", VFS_ENOENT },\n  };\n\n  ScopedVFS vfs = vfs_init();\n  struct stat st;\n  enum vfs_error ret;\n  ASSERT(vfs, \"\");\n\n  SECTION(Valid)\n  {\n    for(const vfs_stat_result &d : valid_data)\n    {\n      ret = vfs_stat(vfs, d.path, &st);\n      ASSERTEQ(ret, d.expected_ret, \"%s\", d.path);\n      check_stat(d.path, d.st, st);\n    }\n  }\n\n  SECTION(Invalid)\n  {\n    for(const vfs_result &d : invalid_data)\n    {\n      ret = vfs_stat(vfs, d.path, &st);\n      ASSERTEQ(ret, d.expected_ret, \"%s\", d.path);\n    }\n  }\n}\n\nUNITTEST(vfs_make_root)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static const vfs_result valid_data[] =\n  {\n    { \"fat\",    0 },\n    { \"D\",      0 },\n    { \"sdcard\", 0 },\n  };\n\n  static const vfs_result invalid_data[] =\n  {\n    { \"/\",      VFS_EEXIST },\n    { \"fat\",    0 },\n    { \"fat\",    VFS_EEXIST },\n    { nullptr,  VFS_EINVAL },\n    { \"\",       VFS_EINVAL },\n    { \"wowe!\",  VFS_EINVAL },\n    { \"fat:/\",  VFS_EINVAL },\n    { \"fat://\", VFS_EINVAL },\n    { \"D:\",     VFS_EINVAL },\n    { \"\\\\\\\\.\\\\\", VFS_EINVAL },\n    { \"\\\\\\\\.\\\\u\", VFS_EINVAL },\n#ifdef VIRTUAL_FILESYSTEM_DOS_DRIVE\n    { \"C\",      VFS_EEXIST },\n#endif\n    //\n  };\n\n  ScopedVFS vfs = vfs_init();\n  struct stat st;\n  ASSERT(vfs, \"\");\n\n  time_t init_time = 0;\n  if(!vfs_stat(vfs, \"/\", &st))\n    init_time = st.st_mtime;\n\n  const auto &do_test = [&vfs, &init_time](const vfs_result &d)\n  {\n    enum vfs_error ret = vfs_make_root(vfs, d.path);\n    ASSERTEQ(ret, d.expected_ret, \"%s\", d.path);\n\n    if(ret == 0)\n    {\n      struct stat st;\n      char buffer[MAX_PATH];\n      snprintf(buffer, MAX_PATH, \"%s://\", d.path);\n      ret = vfs_stat(vfs, buffer, &st);\n      ASSERTEQ(ret, 0, \"stat %s\", buffer);\n      ASSERTEQ(S_ISDIR(st.st_mode), 1, \"stat s_isdir %s\", buffer);\n      ASSERT(st.st_mtime >= init_time, \"stat st_mtime %s\", buffer);\n    }\n  };\n\n  SECTION(Valid)\n  {\n    for(auto &d : valid_data)\n      do_test(d);\n  }\n\n  SECTION(Invalid)\n  {\n    for(auto &d : invalid_data)\n      do_test(d);\n  }\n}\n\nUNITTEST(vfs_create_file_at_path)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static const vfs_stat_result valid_data[] =\n  {\n    { \"file.txt\", 0,                            { 3, S_IFREG, 0 }},\n    { \"abc.def\", 0,                             { 4, S_IFREG, 0 }},\n    { \"reallylongfilename.reallylongext\", 0,    { 5, S_IFREG, 0 }},\n    { \"../initrd.img\", 0,                       { 6, S_IFREG, 0 }},\n    { \"é\", 0,                                   { 7, S_IFREG, 0 }},\n    { \"fat://testfile\", 0,                      { 8, S_IFREG, 0 }},\n#ifdef VIRTUAL_FILESYSTEM_DOS_DRIVE\n    { \"C:/dkdfjklsjdf\", 0,                      { 9, S_IFREG, 0 }},\n#endif\n#ifdef PATH_UNC_ROOTS\n    { \"\\\\\\\\.\\\\c:\\\\uncfile.txt\", 0,              { 10, S_IFREG, 0 }},\n    { \"\\\\\\\\?\\\\c:\\\\uncfile2.txt\", 0,             { 11, S_IFREG, 0 }},\n#endif\n  };\n\n  static const vfs_result invalid_data[] =\n  {\n    { \"file.txt\",           0 },\n    { \"file.txt\",           VFS_EEXIST },\n    { \"file.txt/file.txt\",  VFS_ENOTDIR },\n    { \"/\",                  VFS_EISDIR },\n    { \".\",                  VFS_EISDIR },\n    { \"..\",                 VFS_EISDIR },\n    { \"./\",                 VFS_EISDIR },\n    { \"\",                   VFS_ENOENT },\n    { \"abchnkdf/file.txt\",  VFS_ENOENT },\n    { \"fat:\\\\\\\\\",           VFS_EISDIR },\n    { \"sdcard://testfile\",  VFS_ENOENT },\n#ifdef VIRTUAL_FILESYSTEM_DOS_DRIVE\n    { \"c:\\\\\",               VFS_EISDIR },\n#endif\n#ifdef PATH_UNC_ROOTS\n    { \"\\\\\\\\.\\\\\",            VFS_EISDIR }, // resolves to /\n    { \"\\\\\\\\.\\\\c\",           VFS_ENOENT },\n    { \"\\\\\\\\.\\\\c:\",          VFS_ENOENT },\n    { \"\\\\\\\\.\\\\c:\\\\\",        VFS_EISDIR },\n    { \"\\\\\\\\?\\\\c:\\\\\",        VFS_EISDIR },\n    { \"\\\\\\\\.\\\\c:\\\\file.txt\", VFS_EEXIST },\n    { \"\\\\\\\\?\\\\c:\\\\file.txt\", VFS_EEXIST },\n#endif\n  };\n\n  ScopedVFS vfs = vfs_init();\n  struct stat st;\n  enum vfs_error ret;\n  ASSERT(vfs, \"\");\n\n  time_t init_time = 0;\n  if(!vfs_stat(vfs, \"/\", &st))\n    init_time = st.st_mtime;\n\n  int t = vfs_make_root(vfs, \"fat\");\n  ASSERTEQ(t, 0, \"\");\n\n  SECTION(Valid)\n  {\n    for(const vfs_stat_result &d : valid_data)\n    {\n      ret = vfs_create_file_at_path(vfs, d.path);\n      ASSERTEQ(ret, d.expected_ret, \"%s\", d.path);\n\n      ret = vfs_stat(vfs, d.path, &st);\n      ASSERTEQ(ret, 0, \"stat %s\", d.path);\n      check_stat(d.path, d.st, st, init_time, init_time);\n    }\n  }\n\n  SECTION(Invalid)\n  {\n    for(const vfs_result &d : invalid_data)\n    {\n      ret = vfs_create_file_at_path(vfs, d.path);\n      ASSERTEQ(ret, d.expected_ret, \"%s\", d.path);\n    }\n  }\n}\n\nUNITTEST(vfs_mkdir)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static const vfs_stat_result valid_data[] =\n  {\n    { \"aaa\", 0,                         { 3, S_IFDIR, 0 }},\n    { \"./aaa/b\", 0,                     { 4, S_IFDIR, 0 }},\n    { \"/ccc\", 0,                        { 5, S_IFDIR, 0 }},\n    { \"aaa/..\\\\ccc/d\", 0,               { 6, S_IFDIR, 0 }},\n    { \"ccc/d\\\\.././..\\\\aaa/b\\\\e/\", 0,   { 7, S_IFDIR, 0 }},\n    { \"0\", 0,                           { 8, S_IFDIR, 0 }}, // Test inserting before existing.\n    { \"1\", 0,                           { 9, S_IFDIR, 0 }},\n    { \"á\", 0,                           { 10, S_IFDIR, 0 }},\n    { \"fat:\\\\\\\\somedir\", 0,             { 11, S_IFDIR, 0 }},\n#ifdef VIRTUAL_FILESYSTEM_DOS_DRIVE\n    { \"c:/fff/\", 0,                     { 12, S_IFDIR, 0 }},\n#endif\n#ifdef PATH_UNC_ROOTS\n    { \"\\\\\\\\.\\\\c:\\\\uncdir\", 0,           { 13, S_IFDIR, 0 }},\n    { \"\\\\\\\\?\\\\c:\\\\uncdir2\", 0,          { 14, S_IFDIR, 0 }},\n#endif\n  };\n\n  static const vfs_result invalid_data[] =\n  {\n    { \"\",               VFS_ENOENT },\n    { \"aaa\",            0 },\n    { \"aaa\",            VFS_EEXIST },\n    { \"wtf/a\",          VFS_ENOENT },\n    { \"/\",              VFS_EEXIST },\n    { \".\",              VFS_EEXIST },\n    { \"..\",             VFS_EEXIST },\n    { \"./\",             VFS_EEXIST },\n    { \"D:\\\\\\\\\",         VFS_ENOENT },\n    { \"sdcardsfdfd://\", VFS_ENOENT },\n    { \"file.txt/abc\",   VFS_ENOTDIR },\n    { \"fat://\",         VFS_EEXIST },\n    { \"sdcard:\\\\\\\\a\",   VFS_ENOENT },\n#ifdef VIRTUAL_FILESYSTEM_DOS_DRIVE\n    { \"C:\\\\\",           VFS_EEXIST },\n#endif\n#ifdef PATH_UNC_ROOTS\n    { \"\\\\\\\\.\\\\\",        VFS_EEXIST }, // resolves to /\n    { \"\\\\\\\\.\\\\c\",       VFS_ENOENT },\n    { \"\\\\\\\\.\\\\c:\",      VFS_ENOENT },\n    { \"\\\\\\\\.\\\\c:\\\\\",    VFS_EEXIST },\n    { \"\\\\\\\\?\\\\c:\\\\\",    VFS_EEXIST },\n    { \"\\\\\\\\.\\\\c:\\\\aaa\", VFS_EEXIST },\n    { \"\\\\\\\\?\\\\c:\\\\aaa\", VFS_EEXIST },\n#endif\n  };\n\n  ScopedVFS vfs = vfs_init();\n  struct stat st;\n  enum vfs_error ret;\n  ASSERT(vfs, \"\");\n\n  time_t init_time = 0;\n  if(!vfs_stat(vfs, \"/\", &st))\n    init_time = st.st_mtime;\n\n  int t = vfs_make_root(vfs, \"fat\");\n  ASSERTEQ(t, 0, \"\");\n\n  SECTION(Valid)\n  {\n    for(const vfs_stat_result &d : valid_data)\n    {\n      ret = vfs_mkdir(vfs, d.path, 0755);\n      ASSERTEQ(ret, d.expected_ret, \"%s\", d.path);\n\n      ret = vfs_stat(vfs, d.path, &st);\n      ASSERTEQ(ret, 0, \"stat %s\", d.path);\n      check_stat(d.path, d.st, st, init_time, init_time);\n    }\n  }\n\n  SECTION(Invalid)\n  {\n    ret = vfs_create_file_at_path(vfs, \"file.txt\");\n    ASSERTEQ(ret, 0, \"\");\n\n    for(const vfs_result &d : invalid_data)\n    {\n      ret = vfs_mkdir(vfs, d.path, 0755);\n      ASSERTEQ(ret, d.expected_ret, \"%s\", d.path);\n    }\n  }\n}\n\nUNITTEST(vfs_chdir_getcwd)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n#ifdef VIRTUAL_FILESYSTEM_DOS_DRIVE\n#define BASE \"C:\\\\\"\n#else\n#define BASE \"/\"\n#endif\n\n#ifdef PATH_DOS_STYLE_ROOTS\n#define DIR_SEPARATOR2 DIR_SEPARATOR\n#else\n#define DIR_SEPARATOR2 DIR_SEPARATOR DIR_SEPARATOR\n#endif\n\n#define NAME32 \"0123456789.0123456789.0123456789\"\n#define NAME128 NAME32 \".\" NAME32 \".\" NAME32 \".\" NAME32\n#define NAME512 NAME128 \".\" NAME128 \".\" NAME128 \".\" NAME128\n  static const vfs_op_result valid_data[] =\n  {\n    { BASE, \"\", DO_GETCWD, 0,             0,        { 1, S_IFDIR, 0 }},\n    { \".\", \"\", DO_CHDIR, 0,               0,        { 1, S_IFDIR, 0 }},\n    { \"dir\", \"\", DO_MKDIR, 0,             0,        { 2, S_IFDIR, 0 }},\n    { \"file\", \"\", DO_CREATE, 0,           0,        { 3, S_IFREG, 0 }},\n    { \"dir\", \"\", DO_CHDIR, 0,             0,        { 2, S_IFDIR, 0 }},\n    { BASE \"dir\", \"\", DO_GETCWD, 0,       0,        { 2, S_IFDIR, 0 }},\n    { \".\", \"\", DO_STAT, ignore,           0,        { 2, S_IFDIR, 0 }},\n    { \"..\", \"\", DO_STAT, ignore,          0,        { 1, S_IFDIR, 0 }},\n    { \"../dir\", \"\", DO_STAT, ignore,      0,        { 2, S_IFDIR, 0 }},\n    { \"../file\", \"\", DO_STAT, ignore,     0,        { 3, S_IFREG, 0 }},\n    { \"dir2\", \"\", DO_MKDIR, 0,            0,        { 4, S_IFDIR, 0 }},\n    { \"file2\", \"\", DO_CREATE, 0,          0,        { 5, S_IFREG, 0 }},\n    { \"/dir/dir2\", \"\", DO_STAT, ignore,   0,        { 4, S_IFDIR, 0 }},\n    { \"/dir/file2\", \"\", DO_STAT, ignore,  0,        { 5, S_IFREG, 0 }},\n    { \"dir2\", \"\", DO_CHDIR, 0,            0,        { 4, S_IFDIR, 0 }},\n    { BASE \"dir\" DIR_SEPARATOR \"dir2\", \"\", DO_GETCWD, 0, 0, { 4, S_IFDIR, 0 }},\n    { \"../../file\", \"\", DO_STAT, ignore,  0,        { 3, S_IFREG, 0 }},\n    { \"/\", \"\", DO_CHDIR, 0,               0,        { 1, S_IFDIR, 0 }},\n    { BASE, \"\", DO_GETCWD, 0,             0,        { 1, S_IFDIR, 0 }},\n    { \"fat\", \"\", DO_MAKE_ROOT, 0,         0,        { 6, S_IFDIR, 0 }},\n    { \"fat://\", \"\", DO_CHDIR, 0,          0,        { 6, S_IFDIR, 0 }},\n    { \"fat:\" DIR_SEPARATOR2, \"\", DO_GETCWD, 0, 0,   { 6, S_IFDIR, 0 }},\n    { \"dir\", \"\", DO_MKDIR, 0,             0,        { 7, S_IFDIR, 0 }},\n    { \"dir\", \"\", DO_CHDIR, 0,             0,        { 7, S_IFDIR, 0 }},\n    { \"fat:\" DIR_SEPARATOR2 \"dir\", \"\", DO_GETCWD, 0, 0, { 7, S_IFDIR, 0 }},\n#ifdef VIRTUAL_FILESYSTEM_DOS_DRIVE\n    // \"/\" is the root of the current drive in these OSes.\n    { \"/\",  \"\", DO_CHDIR, 0,              0,        { 6, S_IFDIR, 0 }},\n    { \"fat:\" DIR_SEPARATOR2, \"\", DO_GETCWD, 0, 0,   { 6, S_IFDIR, 0 }},\n#endif\n#ifdef PATH_UNC_ROOTS\n    // VFS functions currently delete the UNC prefix.\n    { \"\\\\\\\\.\\\\fat:\\\\\", \"\", DO_CHDIR, 0,   0,        { 6, S_IFDIR, 0 }},\n    { \"fat:\" DIR_SEPARATOR2, \"\", DO_GETCWD, 0, 0,   { 6, S_IFDIR, 0 }},\n    { \"\\\\\\\\?\\\\fat:\\\\\", \"\", DO_CHDIR, 0,   0,        { 6, S_IFDIR, 0 }},\n    { \"fat:\" DIR_SEPARATOR2, \"\", DO_GETCWD, 0, 0,   { 6, S_IFDIR, 0 }},\n#endif\n  };\n\n  static const vfs_op_result invalid_data[] =\n  {\n    { \"dir\", \"\", DO_MKDIR, 0,             0,        { 2, S_IFDIR, 0 }},\n    { \"file\", \"\", DO_CREATE, 0,           0,        { 3, S_IFREG, 0 }},\n\n    // Invalid chdir.\n    { \"\", \"\", DO_CHDIR, VFS_ENOENT,       0,        { 1, S_IFDIR, 0 }},\n    { \"/nodir\", \"\", DO_CHDIR, VFS_ENOENT, 0,        { 1, S_IFDIR, 0 }},\n    { \"/nodir/a\", \"\", DO_CHDIR, VFS_ENOENT, 0,      { 1, S_IFDIR, 0 }},\n    { \"file\", \"\", DO_CHDIR, VFS_ENOTDIR,  0,        { 1, S_IFDIR, 0 }},\n    { \"file/dir\", \"\", DO_CHDIR, VFS_ENOTDIR, 0,     { 1, S_IFDIR, 0 }},\n    // The above shouldn't change the cwd...\n    { BASE, \"\", DO_GETCWD, 0,             0,        { 1, S_IFDIR, 0 }},\n    // Path too big for chdir (512).\n    { NAME512, \"\", DO_MKDIR, 0,           0,        { 4, S_IFDIR, 0 }},\n    { BASE NAME512, \"\", DO_CHDIR, VFS_ENAMETOOLONG, 0, { 1, S_IFDIR, 0 }},\n    // Path too big for small getcwd buffer (see GETCWD_BUF).\n    { NAME32, \"\", DO_MKDIR, 0,            0,        { 5, S_IFDIR, 0 }},\n    { NAME32, \"\", DO_CHDIR, 0,            0,        { 5, S_IFDIR, 0 }},\n    { \"\", \"\", DO_GETCWD, VFS_ERANGE,      VFS_ENOENT, {}},\n  };\n\n  ScopedVFS vfs = vfs_init();\n  struct stat st;\n  enum vfs_error ret;\n  ASSERT(vfs, \"\");\n\n  time_t init_time = 0;\n  if(!vfs_stat(vfs, \"/\", &st))\n    init_time = st.st_mtime;\n\n  SECTION(Valid)\n  {\n    for(const vfs_op_result &d : valid_data)\n      do_op_and_stat(vfs, d, init_time, init_time);\n  }\n\n  SECTION(Invalid)\n  {\n    for(const vfs_op_result &d : invalid_data)\n      do_op_and_stat(vfs, d, init_time, init_time);\n  }\n\n  SECTION(getcwdInvalid)\n  {\n    char buffer[1];\n    ret = vfs_getcwd(vfs, nullptr, 1235);\n    ASSERTEQ(ret, VFS_EINVAL, \"\");\n    ret = vfs_getcwd(vfs, buffer, 0);\n    ASSERTEQ(ret, VFS_EINVAL, \"\");\n  }\n}\n\nUNITTEST(vfs_rename)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static const vfs_op_result valid_data[] =\n  {\n    // No overwrite.\n    { \"file1\", \"\", DO_CREATE, 0,              0,          { 2, S_IFREG, 0 }},\n    { \"file1\", \"file2\", DO_RENAME, 0,         0,          { 2, S_IFREG, 0 }},\n    { \"file1\", \"\", DO_STAT, ignore,           VFS_ENOENT, {}},\n    { \"dir1\", \"\", DO_MKDIR, 0,                0,          { 3, S_IFDIR, 0 }},\n    { \"dir1\", \"dir2\", DO_RENAME, 0,           0,          { 3, S_IFDIR, 0 }},\n    { \"file2\", \"dir2/file\", DO_RENAME, 0,     0,          { 2, S_IFREG, 0 }},\n    { \"file2\", \"\", DO_STAT, ignore,           VFS_ENOENT, {}},\n    { \"dir2\", \"dir3\", DO_RENAME, 0,           0,          { 3, S_IFDIR, 0 }},\n    { \"dir2\", \"\", DO_STAT, ignore,            VFS_ENOENT, {}},\n    { \"dir3/file\", \"\", DO_STAT, ignore,       0,          { 2, S_IFREG, 0 }},\n    { \"dir4\", \"\", DO_MKDIR, 0,                0,          { 4, S_IFDIR, 0 }},\n    { \"dir3\", \"dir4/dir3\", DO_RENAME, 0,      0,          { 3, S_IFDIR, 0 }},\n    { \"dir3\", \"\", DO_STAT, ignore,            VFS_ENOENT, {}},\n    { \"dir4/dir3/file\", \"\", DO_STAT, ignore,  0,          { 2, S_IFREG, 0 }},\n    // Overwrite file->file.\n    { \"file\", \"\", DO_CREATE, 0,               0,          { 5, S_IFREG, 0 }},\n    { \"dir4/dir3/file\", \"file\", DO_RENAME, 0, 0,          { 2, S_IFREG, 0 }},\n    { \"dir4/dir3/file\", \"\", DO_STAT, ignore,  VFS_ENOENT, {}},\n    { \"file2\", \"\", DO_CREATE, 0,              0,          { 5, S_IFREG, 0 }},\n    // Overwrite dir->dir.\n    { \"dir\", \"\", DO_MKDIR, 0,                 0,          { 6, S_IFDIR, 0 }},\n    { \"dir4/dir3\", \"dir\", DO_RENAME, 0,       0,          { 3, S_IFDIR, 0 }},\n    { \"dir4/dir3\", \"\", DO_STAT, ignore,       VFS_ENOENT, {}},\n    { \"dir2\", \"\", DO_MKDIR, 0,                0,          { 6, S_IFDIR, 0 }},\n    // Same old/new should return success but change nothing.\n    { \"dir\", \"dir\", DO_RENAME, 0,             0,          { 3, S_IFDIR, 0 }},\n    { \"file\", \"file\", DO_RENAME, 0,           0,          { 2, S_IFREG, 0 }},\n    // Long names that are over the threshold to switch to name allocation.\n    { \"file\", \"abcdefghijklmnopqrstuvwxyz\", DO_RENAME, 0, 0, { 2, S_IFREG, 0 }},\n    { \"abcdefghijklmnopqrstuvwxyz\", \"file\", DO_RENAME, 0, 0, { 2, S_IFREG, 0 }},\n  };\n\n  static const vfs_op_result invalid_data[] =\n  {\n    { \"file\", \"\", DO_CREATE, 0,                   0,          { 2, S_IFREG, 0 }},\n    { \"dir\", \"\", DO_MKDIR, 0,                     0,          { 3, S_IFDIR, 0 }},\n    { \"dir2\", \"\", DO_MKDIR, 0,                    0,          { 4, S_IFDIR, 0 }},\n    { \"dir2/dir3\", \"\", DO_MKDIR, 0,               0,          { 5, S_IFDIR, 0 }},\n\n    { \"\", \"badsf\", DO_RENAME, VFS_ENOENT,         VFS_ENOENT, {}},\n    { \"badsf\", \"\", DO_RENAME, VFS_ENOENT,         VFS_ENOENT, {}},\n    { \"/\", \"asdf\", DO_RENAME, VFS_EBUSY,          VFS_ENOENT, {}},\n    { \"dir\", \"/\",  DO_RENAME, VFS_EBUSY,          0,          { 1, S_IFDIR, 0 }},\n    { \"dir\", \"..\", DO_RENAME, VFS_EBUSY,          0,          { 1, S_IFDIR, 0 }},\n    { \"/noexist\", \"dir\", DO_RENAME, VFS_ENOENT,   0,          { 3, S_IFDIR, 0 }},\n    { \"file\", \"/nodir/file\", DO_RENAME, VFS_ENOENT, VFS_ENOENT, {}},\n    { \"dir\", \"file/dir\", DO_RENAME, VFS_ENOTDIR,  VFS_ENOTDIR, {}},\n\n    // Overwrite dir->file.\n    { \"dir\", \"file\", DO_RENAME, VFS_ENOTDIR,      0,          { 2, S_IFREG, 0 }},\n    // Overwrite file->dir.\n    { \"file\", \"dir\", DO_RENAME, VFS_EISDIR,       0,          { 3, S_IFDIR, 0 }},\n    // Overwrite non-empty dir.\n    { \"dir\", \"dir2\", DO_RENAME, VFS_ENOTEMPTY,    0,          { 4, S_IFDIR, 0 }},\n    // Old dir must not be ancestor of new dir.\n    { \"dir\", \"dir/dir\", DO_RENAME, VFS_EINVAL,    VFS_ENOENT, {}},\n    { \"dir2\", \"dir2/dir3/dir4\", DO_RENAME, VFS_EINVAL, VFS_ENOENT, {}},\n    // Don't allow renaming CWD or overwriting CWD.\n    { \"dir\", \"\", DO_CHDIR, 0,                     0,          { 3, S_IFDIR, 0 }},\n    { \".\", \"deez\", DO_RENAME, VFS_EBUSY,          VFS_ENOENT, {}},\n    { \"../dir\", \"../deez\", DO_RENAME, VFS_EBUSY,  VFS_ENOENT, {}},\n    { \"../dir2\", \".\", DO_RENAME, VFS_EBUSY,       0,          { 3, S_IFDIR, 0 }},\n    { \"../dir2\", \"../dir\", DO_RENAME, VFS_EBUSY,  0,          { 3, S_IFDIR, 0 }},\n  };\n\n  ScopedVFS vfs = vfs_init();\n  struct stat st;\n  enum vfs_error ret;\n  ASSERT(vfs, \"\");\n\n  time_t init_time = 0;\n  if(!vfs_stat(vfs, \"/\", &st))\n    init_time = st.st_mtime;\n\n  SECTION(Valid)\n  {\n    for(const vfs_op_result &d : valid_data)\n      do_op_and_stat(vfs, d, init_time, init_time);\n  }\n\n  SECTION(Invalid)\n  {\n    for(const vfs_op_result &d : invalid_data)\n      do_op_and_stat(vfs, d, init_time, init_time);\n  }\n\n  SECTION(BusyFile)\n  {\n    // Special: attempting to overwrite an open file should always fail with EBUSY.\n    uint32_t inode;\n\n    ret = vfs_create_file_at_path(vfs, \"file\");\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_create_file_at_path(vfs, \"file2\");\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_open_if_exists(vfs, \"file\", true, &inode);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_rename(vfs, \"file2\", \"file\");\n    ASSERTEQ(ret, VFS_EBUSY, \"\");\n    ret = vfs_close(vfs, inode);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_rename(vfs, \"file2\", \"file\");\n    ASSERTEQ(ret, 0, \"\");\n  }\n}\n\nUNITTEST(vfs_unlink)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static const vfs_op_result valid_data[] =\n  {\n    { \"file1\", \"\", DO_CREATE, 0,              0,            { 2, S_IFREG, 0 }},\n    { \"file1\", \"\", DO_UNLINK, 0,              VFS_ENOENT,   {}},\n    { \"dir\", \"\", DO_MKDIR, 0,                 0,            { 2, S_IFDIR, 0 }},\n    { \"dir/file2\", \"\", DO_CREATE, 0,          0,            { 3, S_IFREG, 0 }},\n    { \"dir/file2\", \"\", DO_UNLINK, 0,          VFS_ENOENT,   {}},\n    { \"file3\", \"\", DO_CREATE, 0,              0,            { 3, S_IFREG, 0 }},\n  };\n\n  static const vfs_op_result invalid_data[] =\n  {\n    { \"dir\", \"\", DO_MKDIR, 0,                 0,            { 2, S_IFDIR, 0 }},\n    { \"file\", \"\", DO_CREATE, 0,               0,            { 3, S_IFREG, 0 }},\n\n    { \"\", \"\", DO_UNLINK, VFS_ENOENT,          VFS_ENOENT,   {}},\n    { \"/\", \"\", DO_UNLINK, VFS_EBUSY,          0,            { 1, S_IFDIR, 0 }},\n    { \"/noexist\", \"\", DO_UNLINK, VFS_ENOENT,  VFS_ENOENT,   {}},\n    { \"/nodir/a\", \"\", DO_UNLINK, VFS_ENOENT,  VFS_ENOENT,   {}},\n    { \"file/file\", \"\", DO_UNLINK, VFS_ENOTDIR, VFS_ENOTDIR, {}},\n    { \"dir\", \"\", DO_UNLINK, VFS_EPERM,        0,            { 2, S_IFDIR, 0 }},\n  };\n\n  ScopedVFS vfs = vfs_init();\n  struct stat st;\n  enum vfs_error ret;\n  ASSERT(vfs, \"\");\n\n  time_t init_time = 0;\n  if(!vfs_stat(vfs, \"/\", &st))\n    init_time = st.st_mtime;\n\n  SECTION(Valid)\n  {\n    for(const vfs_op_result &d : valid_data)\n      do_op_and_stat(vfs, d, init_time, init_time);\n  }\n\n  SECTION(Invalid)\n  {\n    for(const vfs_op_result &d : invalid_data)\n      do_op_and_stat(vfs, d, init_time, init_time);\n  }\n\n  SECTION(BusyFile)\n  {\n    // Special: attempting to unlink an open file should always fail with EBUSY.\n    uint32_t inode;\n\n    ret = vfs_create_file_at_path(vfs, \"file\");\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_open_if_exists(vfs, \"file\", true, &inode);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_unlink(vfs, \"file\");\n    ASSERTEQ(ret, VFS_EBUSY, \"\");\n    ret = vfs_close(vfs, inode);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_unlink(vfs, \"file\");\n    ASSERTEQ(ret, 0, \"\");\n  }\n}\n\nUNITTEST(vfs_rmdir)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static const vfs_op_result valid_data[] =\n  {\n    { \"file1\", \"\", DO_CREATE, 0,              0,            { 2, S_IFREG, 0 }},\n    { \"dir\", \"\", DO_MKDIR, 0,                 0,            { 3, S_IFDIR, 0 }},\n    { \"dir\", \"\", DO_RMDIR, 0,                 VFS_ENOENT,   {}},\n    { \"dir\", \"\", DO_MKDIR, 0,                 0,            { 3, S_IFDIR, 0 }},\n    { \"dir/dir2\", \"\", DO_MKDIR, 0,            0,            { 4, S_IFDIR, 0 }},\n    { \"dir\", \"\", DO_RMDIR, VFS_ENOTEMPTY,     0,            { 3, S_IFDIR, 0 }},\n    { \"dir/dir2\", \"\", DO_RMDIR, 0,            VFS_ENOENT,   {}},\n    { \"dir\", \"\", DO_RMDIR, 0,                 VFS_ENOENT,   {}},\n  };\n\n  static const vfs_op_result invalid_data[] =\n  {\n    { \"file\", \"\", DO_CREATE, 0,               0,            { 2, S_IFREG, 0 }},\n\n    { \"\", \"\", DO_RMDIR, VFS_ENOENT,           VFS_ENOENT,   {}},\n    { \"/\", \"\", DO_RMDIR, VFS_EBUSY,           0,            { 1, S_IFDIR, 0 }},\n    { \"file\", \"\", DO_RMDIR, VFS_ENOTDIR,      0,            { 2, S_IFREG, 0 }},\n    { \"/noexist\", \"\", DO_RMDIR, VFS_ENOENT,   VFS_ENOENT,   {}},\n    { \"/nodir/a\", \"\", DO_RMDIR, VFS_ENOENT,   VFS_ENOENT,   {}},\n    { \"file/dir\", \"\", DO_RMDIR, VFS_ENOTDIR,  VFS_ENOTDIR,  {}},\n    // Can't remove the CWD.\n    { \"dir\", \"\", DO_MKDIR, 0,                 0,            { 3, S_IFDIR, 0 }},\n    { \"dir\", \"\", DO_CHDIR, 0,                 0,            { 3, S_IFDIR, 0 }},\n    { \".\", \"\", DO_RMDIR, VFS_EBUSY,           0,            { 3, S_IFDIR, 0 }},\n    { \"../dir\", \"\", DO_RMDIR, VFS_EBUSY,      0,            { 3, S_IFDIR, 0 }},\n  };\n\n  ScopedVFS vfs = vfs_init();\n  struct stat st;\n  ASSERT(vfs, \"\");\n\n  time_t init_time = 0;\n  if(!vfs_stat(vfs, \"/\", &st))\n    init_time = st.st_mtime;\n\n  SECTION(Valid)\n  {\n    for(const vfs_op_result &d : valid_data)\n      do_op_and_stat(vfs, d, init_time, init_time);\n  }\n\n  SECTION(Invalid)\n  {\n    for(const vfs_op_result &d : invalid_data)\n      do_op_and_stat(vfs, d, init_time, init_time);\n  }\n}\n\nUNITTEST(vfs_access)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static const vfs_op_result valid_data[] =\n  {\n    { \"/\", \"\", DO_ACCESS, 0,                  0,            { 1, S_IFDIR, 0 }},\n    { \"file\", \"\", DO_CREATE, 0,               0,            { 2, S_IFREG, 0 }},\n    { \"file\", \"\", DO_ACCESS, 0,               0,            { 2, S_IFREG, 0 }},\n    { \"dir\", \"\", DO_MKDIR, 0,                 0,            { 3, S_IFDIR, 0 }},\n    { \"dir\", \"\", DO_ACCESS, 0,                0,            { 3, S_IFDIR, 0 }},\n  };\n\n  static const vfs_op_result invalid_data[] =\n  {\n    { \"file\", \"\", DO_CREATE, 0,               0,            { 2, S_IFREG, 0 }},\n\n    { \"\", \"\", DO_RMDIR, VFS_ENOENT,           VFS_ENOENT,   {}},\n    { \"/noexist\", \"\", DO_RMDIR, VFS_ENOENT,   VFS_ENOENT,   {}},\n    { \"/nodir/a\", \"\", DO_RMDIR, VFS_ENOENT,   VFS_ENOENT,   {}},\n    { \"file/file\", \"\", DO_RMDIR, VFS_ENOTDIR, VFS_ENOTDIR,  {}},\n  };\n\n  ScopedVFS vfs = vfs_init();\n  struct stat st;\n  ASSERT(vfs, \"\");\n\n  time_t init_time = 0;\n  if(!vfs_stat(vfs, \"/\", &st))\n    init_time = st.st_mtime;\n\n  SECTION(Valid)\n  {\n    for(const vfs_op_result &d : valid_data)\n      do_op_and_stat(vfs, d, init_time, init_time);\n  }\n\n  SECTION(Invalid)\n  {\n    for(const vfs_op_result &d : invalid_data)\n      do_op_and_stat(vfs, d, init_time, init_time);\n  }\n}\n\nUNITTEST(vfs_readdir)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  struct vfs_readdir_result\n  {\n    const char *path;\n    boolean is_dir;\n  };\n\n  // Keep this sorted.\n  static const vfs_readdir_result valid_files[] =\n  {\n    { \"!\", true },\n    { \"0\", false },\n    { \"9123125\", false },\n    { \"@\", true },\n#ifndef VIRTUAL_FILESYSTEM_CASE_INSENSITIVE\n    { \"AaaaaAAa\", true },\n    { \"X\", true },\n    { \"Z\", false },\n#endif\n    { \"[\", true },\n    { \"afdsf\", false },\n    { \"b5455ttyhn\", false },\n    { \"zzzzzzzz\", true },\n    { \"~\", false },\n  };\n\n  static const vfs_result invalid_data[] =\n  {\n    { \"\", VFS_ENOENT },\n    { \"file\", VFS_ENOTDIR },\n    { \"file/dir\", VFS_ENOTDIR },\n  };\n\n  ScopedVFS vfs = vfs_init();\n  ASSERT(vfs, \"\");\n\n  ScopedVFSDir vfsd;\n  int ret;\n\n  SECTION(ValidEmpty)\n  {\n    ret = vfs_readdir(vfs, \".\", &vfsd);\n    ASSERTEQ(ret, 0, \"readdir\");\n    ASSERTEQ(vfsd.files, nullptr, \"\");\n    ASSERTEQ(vfsd.num_files, 0, \"\");\n  }\n\n  SECTION(ValidFiles)\n  {\n    for(const vfs_readdir_result &d : valid_files)\n    {\n      if(d.is_dir)\n      {\n        ret = vfs_mkdir(vfs, d.path, 0755);\n        ASSERTEQ(ret, 0, \"mkdir: %s\", d.path);\n      }\n      else\n      {\n        ret = vfs_create_file_at_path(vfs, d.path);\n        ASSERTEQ(ret, 0, \"create: %s\", d.path);\n      }\n    }\n\n    ret = vfs_readdir(vfs, \".\", &vfsd);\n    ASSERTEQ(ret, 0, \"readdir\");\n    ASSERT(vfsd.files, \"\");\n    ASSERTEQ(vfsd.num_files, (size_t)arraysize(valid_files), \"\");\n\n    size_t i = 0;\n    for(const vfs_readdir_result &d : valid_files)\n    {\n      const struct vfs_dir_file *f = vfsd.files[i++];\n      ASSERT(f, \"%s\", d.path);\n      if(d.is_dir)\n        ASSERT(f->type == DIR_TYPE_DIR, \"%s\", d.path);\n      else\n        ASSERT(f->type == DIR_TYPE_FILE, \"%s\", d.path);\n\n      ASSERTCMP(f->name, d.path, \"\");\n    }\n  }\n\n  SECTION(Invalid)\n  {\n    ret = vfs_create_file_at_path(vfs, \"file\");\n    ASSERTEQ(ret, 0, \"\");\n\n    for(const vfs_result &d : invalid_data)\n    {\n      ret = vfs_readdir(vfs, d.path, &vfsd);\n      ASSERTEQ(ret, d.expected_ret, \"%s\", d.path);\n    }\n  }\n\n  // Make sure double free is safe (destructor will call again).\n  vfs_readdir_free(&vfsd);\n}\n\nUNITTEST(FileIO)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static const char name[] = \"file.ext\";\n  static const char *strs[] =\n  {\n    \"a test string\",\n    \"\",\n    \"asjdfl;kjsd;lfjksdl;fjksdl;fkjsdl;kfmsdl;kfsd;lmkl;mfsdl;mkdsl;kdsm;l\",\n  };\n\n  uint32_t inode;\n  int ret;\n\n  // Write lock vars.\n  unsigned char **_data;\n  size_t *_length;\n  size_t *_alloc;\n  // Read lock vars.\n  const unsigned char *data;\n  size_t length;\n  ssize_t length_ret;\n\n  ScopedVFS vfs = vfs_init();\n  struct stat st;\n  ASSERT(vfs, \"\");\n\n  SECTION(Valid)\n  {\n    ret = vfs_create_file_at_path(vfs, name);\n    ASSERTEQ(ret, 0, \"create: %s\", name);\n\n    ret = vfs_open_if_exists(vfs, name, true, &inode);\n    ASSERTEQ(ret, 0, \"open(w): %s\", name);\n\n    for(const char *str : strs)\n    {\n      unsigned char *buf = reinterpret_cast<unsigned char *>(strdup(str));\n      size_t len = strlen(str);\n      ASSERT(buf, \"strdup: %s\", str);\n\n      // 1. write.\n      ret = vfs_lock_file_write(vfs, inode, &_data, &_length, &_alloc);\n      ASSERTEQ(ret, 0, \"lock(w): %s\", str);\n\n      unsigned char *tmp = *_data;\n      *_data = buf;\n      *_length = len;\n      *_alloc = len + 1;\n\n      ret = vfs_unlock_file_write(vfs, inode);\n      ASSERTEQ(ret, 0, \"unlock(w): %s\", str);\n      free(tmp);\n\n      // 2. stat, check length.\n      ret = vfs_stat(vfs, name, &st);\n      ASSERTEQ(ret, 0, \"stat: %s\", str);\n      ASSERTEQ((size_t)st.st_size, len, \"length: %s\", str);\n      // vfs_filelength should return the same thing.\n      length_ret = vfs_filelength(vfs, inode);\n      ASSERT(length_ret >= 0, \"vfs_filelength: %s\", str);\n      ASSERTEQ((size_t)length_ret, len, \"vfs_filelength: %s\", str);\n\n      // 3. read.\n      ret = vfs_lock_file_read(vfs, inode, &data, &length);\n      ASSERTEQ(ret, 0, \"lock(r): %s\", str);\n\n      ASSERTMEM(str, data, len, \"compare: %s\", str);\n\n      ret = vfs_unlock_file_read(vfs, inode);\n      ASSERTEQ(ret, 0, \"unlock(r): %s\", str);\n\n      // 4. truncate.\n      ret = vfs_truncate(vfs, inode);\n      ASSERTEQ(ret, 0, \"truncate: %s\", str);\n      ret = vfs_stat(vfs, name, &st);\n      ASSERTEQ(ret, 0, \"stat: %s\", str);\n      ASSERTEQ(st.st_size, 0, \"length: %s\", str);\n      length_ret = vfs_filelength(vfs, inode);\n      ASSERTEQ(length_ret, 0, \"vfs_filelength: %s\", str);\n    }\n\n    ret = vfs_close(vfs, inode);\n    ASSERTEQ(ret, 0, \"close: %s\", name);\n  }\n\n  SECTION(Invalid)\n  {\n    // Opening a nonexistant file should fail.\n    ret = vfs_open_if_exists(vfs, \"abhjfd\", false, &inode);\n    ASSERTEQ(ret, VFS_ENOENT, \"\");\n\n    // Opening a dir should fail.\n    ret = vfs_open_if_exists(vfs, \"/\", false, &inode);\n    ASSERTEQ(ret, VFS_EISDIR, \"\");\n  }\n\n  SECTION(InvalidInode)\n  {\n    // Attempting to lock or unlock a nonexistant file should fail.\n    ret = vfs_lock_file_write(vfs, 12345, &_data, &_length, &_alloc);\n    ASSERTEQ(ret, VFS_EBADF, \"lock(w)\");\n\n    ret = vfs_unlock_file_write(vfs, 6523);\n    ASSERTEQ(ret, VFS_EBADF, \"unlock(w)\");\n\n    ret = vfs_lock_file_read(vfs, 12456, &data, &length);\n    ASSERTEQ(ret, VFS_EBADF, \"lock(r)\");\n\n    ret = vfs_unlock_file_read(vfs, 2222);\n    ASSERTEQ(ret, VFS_EBADF, \"unlock(r)\");\n  }\n}\n\n\n/**\n * Caching functions.\n */\n\nstruct vfs_cache_result\n{\n  const char *path;\n  int expected_ret;\n  int64_t modified_time;\n};\n\nstruct vfs_cache_content\n{\n  const char *path;\n  int expected_ret;\n  int64_t modified_time;\n  const char *content;\n};\n\nUNITTEST(vfs_cache_directory)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  ScopedVFS vfs = vfs_init();\n  ASSERT(vfs, \"\");\n\n  // Preset a cached file, virtual file, and virtual dir.\n  char buf[32]{};\n  int ret = vfs_cache_file(vfs, \"file\", buf, 32);\n  ASSERTEQ(ret, 0, \"\");\n  ret = vfs_create_file_at_path(vfs, \"vfile\");\n  ASSERTEQ(ret, 0, \"\");\n  ret = vfs_mkdir(vfs, \"vdir\", 0644);\n  ASSERTEQ(ret, 0, \"\");\n  ret = vfs_make_root(vfs, \"fat\");\n  ASSERTEQ(ret, 0, \"\");\n\n  static const vfs_cache_result valid_data[] =\n  {\n    { \"dir1\",           0, 1 },\n    { \"dir1/dir2\",      0, 2 },\n    { \"/dir3\",          0, 3 },\n    { \"dir3/../dir4\",   0, 4 },\n    { \"vdir/dir5\",      0, 5 },\n    { \"fat://dir6\",     0, 6 },\n#ifdef PATH_UNC_ROOTS\n    { \"\\\\\\\\.\\\\fat:\\\\dir7\", 0, 7 },\n    { \"\\\\\\\\?\\\\fat:\\\\dir8\", 0, 8 },\n#endif\n  };\n\n  static const vfs_cache_result invalid_data[] =\n  {\n    { \"\",                   VFS_ENOENT,   0 },\n    { \"/\",                  VFS_EEXIST,   0 },\n    { \".\",                  VFS_EEXIST,   0 },\n    { \"..\",                 VFS_EEXIST,   0 },\n    { \"./\",                 VFS_EEXIST,   0 },\n    { \"dirA/dirB\",          VFS_ENOENT,   0 },\n    { \"dirC\",               0,            12345 },\n    { \"dirC\",               VFS_EEXIST,   0 },\n    { \"file\",               VFS_EEXIST,   0 },\n    { \"file/dirD\",          VFS_ENOTDIR,  0 },\n    { \"vdir\",               VFS_EEXIST,   0 },\n    { \"vfile\",              VFS_EEXIST,   0 },\n    { \"vfile/dirE\",         VFS_ENOTDIR,  0 },\n    { \"fat://\",             VFS_EEXIST,   0 },\n    { \"fat://dirF\",         0,            56789 },\n    { \"fat://dirG/dirH\",    VFS_ENOENT,   0 },\n    { \"sdcard://dirI\",      VFS_ENOENT,   0 },\n#ifdef PATH_UNC_ROOTS\n    { \"\\\\\\\\.\\\\fat:\\\\\",      VFS_EEXIST,   0 },\n    { \"\\\\\\\\?\\\\fat:\\\\\",      VFS_EEXIST,   0 },\n    { \"\\\\\\\\.\\\\fat:\\\\dirF\",  VFS_EEXIST,   0 },\n    { \"\\\\\\\\?\\\\fat:\\\\dirF\",  VFS_EEXIST,   0 },\n    { \"\\\\\\\\.\\\\fat:\\\\dirJ\",  0,            66666 },\n    { \"\\\\\\\\?\\\\fat:\\\\dirJ\",  VFS_EEXIST,   0 },\n    { \"fat:\\\\dirJ\",         VFS_EEXIST,   0 },\n#endif\n  };\n\n  const auto &do_test = [&vfs](const struct vfs_cache_result &d)\n  {\n    struct stat st{};\n    st.st_mtime = d.modified_time;\n\n    int ret = vfs_cache_directory(vfs, d.path, &st);\n    ASSERTEQ(ret, d.expected_ret, \"%s\", d.path);\n\n    if(ret != 0)\n      return;\n\n    st.st_mtime = 0;\n    ret = vfs_stat(vfs, d.path, &st);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"%s\", d.path);\n    ASSERTEQ(st.st_mtime, d.modified_time, \"%s\", d.path);\n\n    // vfs_unlink should detect an existing cached file and do nothing.\n    ret = vfs_unlink(vfs, d.path);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"unlink %s\", d.path);\n    ret = vfs_stat(vfs, d.path, &st);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"unlink %s\", d.path);\n\n    // vfs_rmdir should detect an existing cached file and do nothing.\n    ret = vfs_rmdir(vfs, d.path);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"rmdir %s\", d.path);\n    ret = vfs_stat(vfs, d.path, &st);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"rmdir %s\", d.path);\n\n    // vfs_access should detect an existing cached file.\n    ret = vfs_access(vfs, d.path, R_OK);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"access %s\", d.path);\n  };\n\n  SECTION(Valid)\n  {\n    for(auto &d : valid_data)\n      do_test(d);\n  }\n\n  SECTION(Invalid)\n  {\n    for(auto &d : invalid_data)\n      do_test(d);\n  }\n\n  SECTION(vfs_rename)\n  {\n    // vfs_rename should detect an existing cached file, but still *work*.\n    static constexpr const char *targets[] =\n    {\n      \"dirC\",\n      \"dirB/dirA\",\n      \"dirV\",\n      \"dirA\",\n      \"dirA\",\n    };\n    struct stat st{};\n    ret = vfs_cache_directory(vfs, \"dirA\", &st);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_cache_directory(vfs, \"dirB\", &st);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_mkdir(vfs, \"dirV\", 0755);\n    ASSERTEQ(ret, 0, \"\");\n\n    const char *prev = \"dirA\";\n    for(const char *t : targets)\n    {\n      ret = vfs_rename(vfs, prev, t);\n      ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"%s -> %s\", prev, t);\n      ret = vfs_stat(vfs, t, &st);\n      ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"%s -> %s\", prev, t);\n      prev = t;\n    }\n\n    // Virtual should be moveable over cached too.\n    ret = vfs_mkdir(vfs, \"dirX\", 0755);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_rename(vfs, \"dirX\", \"dirA\");\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_stat(vfs, \"dirA\", &st);\n    ASSERTEQ(ret, 0, \"\");\n  }\n\n  SECTION(rmdirCachedNotEmpty)\n  {\n    // Special: vfs_rmdir should return VFS_ENOTEMPTY even for cached\n    // directories (to signal to the caller not to delete a real directory\n    // with virtual files inside of it).\n    struct stat st{};\n    ret = vfs_cache_directory(vfs, \"dirA\", &st);\n    ASSERTEQ(ret, 0, \"cache directory\");\n    ret = vfs_create_file_at_path(vfs, \"dirA/child\");\n    ASSERTEQ(ret, 0, \"create virtual file in cached dir\");\n    ret = vfs_rmdir(vfs, \"dirA\");\n    ASSERTEQ(ret, VFS_ENOTEMPTY,\n     \"vfs_rmdir on non-empty cached dir should return VFS_ENOTEMPTY\");\n  }\n}\n\nstruct read_fn_data\n{\n  const char *pos;\n  size_t left;\n};\nstatic size_t read_fn(void * RESTRICT dest, size_t nbytes, void * RESTRICT priv)\n{\n  read_fn_data *d = reinterpret_cast<read_fn_data *>(priv);\n  if(nbytes > d->left)\n    nbytes = d->left;\n\n  if(nbytes)\n  {\n    memcpy(dest, d->pos, nbytes);\n    d->pos += nbytes;\n    d->left -= nbytes;\n  }\n  return nbytes;\n}\n\nUNITTEST(vfs_cache_file)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  ScopedVFS vfs = vfs_init();\n  ASSERT(vfs, \"\");\n\n  // Preset a cached dir, virtual file, and virtual dir.\n  struct stat dummy{};\n  int ret = vfs_cache_directory(vfs, \"dirA\", &dummy);\n  ASSERTEQ(ret, 0, \"\");\n  ret = vfs_create_file_at_path(vfs, \"vfile\");\n  ASSERTEQ(ret, 0, \"\");\n  ret = vfs_mkdir(vfs, \"vdir\", 0644);\n  ASSERTEQ(ret, 0, \"\");\n  ret = vfs_make_root(vfs, \"fat\");\n  ASSERTEQ(ret, 0, \"\");\n\n  static const vfs_cache_content valid_data[] =\n  {\n    { \"file1\",          0, 0, \"abc\" },\n    { \"file2\",          0, 0, \"def\" },\n    { \"dirA/file3\",     0, 0, \"djsdjflksjdgklsd\" },\n    { \"/file4\",         0, 0, \"\" },\n    { \"file5\",          0, 0, nullptr },\n    { \"vdir/file6\",     0, 0, \"jffjddf\" },\n    { \"fat://file7\",    0, 0, \"toptwtpo\"},\n#ifdef PATH_UNC_ROOTS\n    { \"\\\\\\\\.\\\\fat:\\\\file8\", 0, 0, \"hewwo\" },\n    { \"\\\\\\\\?\\\\fat:\\\\file9\", 0, 0, \"tyis one too\" },\n#endif\n  };\n\n  static const vfs_cache_content invalid_data[] =\n  {\n    { \"\",                   VFS_ENOENT,   0,  \"fdskfds\" },\n    { \"/\",                  VFS_EISDIR,   0,  \"sdfsdf\" },\n    { \".\",                  VFS_EISDIR,   0,  nullptr },\n    { \"..\",                 VFS_EISDIR,   0,  \"dfssd\" },\n    { \"./\",                 VFS_EISDIR,   0,  \"fkd\" },\n    { \"fileA\",              0,            0,  \"sdjflksd\" },\n    { \"fileA\",              VFS_EEXIST,   0,  nullptr },\n    { \"dirA\",               VFS_EISDIR,   0,  \"fsdfdsf\" },\n    { \"dirB/fileB\",         VFS_ENOENT,   0,  \"fdkgfk\" },\n    { \"fileA/fileB\",        VFS_ENOTDIR,  0,  \"fffff\" },\n    { \"vfile\",              VFS_EEXIST,   0,  \"iriqrg\" },\n    { \"vdir\",               VFS_EISDIR,   0,  \"oregfg\" },\n    { \"vfile/fileC\",        VFS_ENOTDIR,  0,  \"vnccnm\" },\n    { \"fat://dir/fileD\",    VFS_ENOENT,   0,  \"eijety\" },\n    { \"sdcard://fileE\",     VFS_ENOENT,   0,  \"hhhhh\" },\n#ifdef PATH_UNC_ROOTS\n    { \"fat://fileF\",        0,            0,  \"yolo\" },\n    { \"\\\\\\\\.\\\\fat:\\\\fileF\", VFS_EEXIST,   0,  nullptr },\n    { \"\\\\\\\\?\\\\fat:\\\\fileF\", VFS_EEXIST,   0,  nullptr },\n    { \"\\\\\\\\.\\\\fat:\\\\fileG\", 0,            0,  \"yah boie\" },\n    { \"\\\\\\\\.\\\\fat:\\\\fileG\", VFS_EEXIST,   0,  nullptr },\n    { \"\\\\\\\\?\\\\fat:\\\\fileG\", VFS_EEXIST,   0,  nullptr },\n    { \"fat://fileG\",        VFS_EEXIST,   0,  nullptr },\n    { \"\\\\\\\\?\\\\fat:\\\\fileH\", 0,            0,  \"this 2\" },\n    { \"\\\\\\\\?\\\\fat:\\\\fileH\", VFS_EEXIST,   0,  nullptr },\n    { \"\\\\\\\\.\\\\fat:\\\\fileH\", VFS_EEXIST,   0,  nullptr },\n    { \"fat://fileH\",        VFS_EEXIST,   0,  nullptr },\n#endif\n  };\n\n  const auto &check_test = [&vfs](const struct vfs_cache_content &d)\n  {\n    size_t content_len = d.content ? strlen(d.content) : 0;\n    uint32_t inode;\n    int ret = vfs_open_if_exists(vfs, d.path, false, &inode);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"%s\", d.path);\n\n    const unsigned char *in;\n    size_t in_len;\n    ret = vfs_lock_file_read(vfs, inode, &in, &in_len);\n    ASSERTEQ(ret, 0, \"%s\", d.path);\n    ASSERTEQ(in_len, content_len, \"%s\", d.path);\n    if(in_len)\n      ASSERTMEM(in, d.content, in_len, \"%s\", d.path);\n\n    ret = vfs_unlock_file_read(vfs, inode);\n    ASSERTEQ(ret, 0, \"%s\", d.path);\n\n    ret = vfs_close(vfs, inode);\n    ASSERTEQ(ret, 0, \"%s\", d.path);\n\n    // vfs_stat should work and detect that this file is cached.\n    struct stat st{};\n    ret = vfs_stat(vfs, d.path, &st);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"stat %s\", d.path);\n    ASSERTEQ(st.st_dev, VFS_MZX_DEVICE, \"stat %s\", d.path);\n\n    // vfs_unlink should detect an existing cached file and do nothing.\n    ret = vfs_unlink(vfs, d.path);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"unlink %s\", d.path);\n    ret = vfs_stat(vfs, d.path, &st);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"unlink %s\", d.path);\n\n    // vfs_rmdir should detect an existing cached file and do nothing.\n    ret = vfs_rmdir(vfs, d.path);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"rmdir %s\", d.path);\n    ret = vfs_stat(vfs, d.path, &st);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"rmdir %s\", d.path);\n\n    // vfs_access should detect an existing cached file.\n    ret = vfs_access(vfs, d.path, R_OK);\n    ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"access %s\", d.path);\n  };\n\n  const auto &do_test = [&vfs, &check_test](const struct vfs_cache_content &d)\n  {\n    size_t content_len = d.content ? strlen(d.content) : 0;\n\n    int ret = vfs_cache_file(vfs, d.path, d.content, content_len);\n    ASSERTEQ(ret, d.expected_ret, \"%s\", d.path);\n\n    if(ret == 0)\n      check_test(d);\n  };\n\n  const auto &do_test_cb = [&vfs, &check_test](const struct vfs_cache_content &d)\n  {\n    size_t content_len = d.content ? strlen(d.content) : 0;\n    read_fn_data data{ d.content, content_len };\n\n    int ret = vfs_cache_file_callback(vfs, d.path, read_fn, &data, 64);\n    ASSERTEQ(ret, d.expected_ret, \"%s\", d.path);\n\n    if(ret == 0)\n      check_test(d);\n  };\n\n  SECTION(Valid)\n  {\n    for(auto &d : valid_data)\n      do_test(d);\n  }\n\n  SECTION(ValidCallback)\n  {\n    for(auto &d : valid_data)\n      do_test_cb(d);\n  }\n\n  SECTION(Invalid)\n  {\n    for(auto &d : invalid_data)\n      do_test(d);\n  }\n\n  SECTION(InvalidCallback)\n  {\n    for(auto &d : invalid_data)\n      do_test_cb(d);\n  }\n\n  SECTION(vfs_rename)\n  {\n    // vfs_rename should detect an existing cached file, but still *work*.\n    static constexpr const char *targets[] =\n    {\n      \"fileC\",\n      \"dirB/fileA\",\n      \"dirV/fileA\",\n      \"fileV\",\n      \"fileA\",\n      \"fileA\",\n    };\n    struct stat st{};\n    ret = vfs_cache_file(vfs, \"fileA\", NULL, 0);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_cache_directory(vfs, \"dirB\", &st);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_mkdir(vfs, \"dirV\", 0755);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_create_file_at_path(vfs, \"fileV\");\n    ASSERTEQ(ret, 0, \"\");\n\n    const char *prev = \"fileA\";\n    for(const char *t : targets)\n    {\n      ret = vfs_rename(vfs, prev, t);\n      ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"%s -> %s\", prev, t);\n      ret = vfs_stat(vfs, t, &st);\n      ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"%s -> %s\", prev, t);\n      prev = t;\n    }\n\n    // Virtual should be moveable over cached too.\n    ret = vfs_create_file_at_path(vfs, \"fileX\");\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_rename(vfs, \"fileX\", \"fileA\");\n    ASSERTEQ(ret, 0, \"\");\n    ret = vfs_stat(vfs, \"fileA\", &st);\n    ASSERTEQ(ret, 0, \"\");\n  }\n\n  SECTION(vfs_create_file_at_path)\n  {\n    // vfs_create_file_at_path should be able to unlink a cache entry.\n    static constexpr char data[] = \"lol\";\n    struct stat st{};\n\n    ret = vfs_cache_file(vfs, \"fileA\", data, sizeof(data));\n    ASSERTEQ(ret, 0, \"create cached file\");\n    ret = vfs_create_file_at_path(vfs, \"fileA\");\n    ASSERTEQ(ret, 0, \"create virtual file over cached file\");\n    ret = vfs_stat(vfs, \"fileA\", &st);\n    ASSERTEQ(ret, 0, \"should be virtual i.e. 0\");\n    ASSERTEQ(st.st_size, 0, \"new virtual file should be length 0\");\n  }\n}\n\n// Common VFS initial state for the cache size management tests.\n// The following cache tests all rely on the exact paths and sizes\n// set up here!\nstatic constexpr char fileopen_data[] = \"abcdefgh12345678ZYXWVUTS...:...:\";\nstatic constexpr size_t fileopen_len = sizeof(fileopen_data) - 1;\n\nstatic size_t setup_cache_testing_vfs(ScopedVFS &vfs)\n{\n  static const char *dirs[] =\n  {\n    \"dirA\",\n    \"dirA/dirB\",\n    \"dirA/dirC\",\n    \"fat://dirX\",\n  };\n\n  static const struct\n  {\n    const char *path;\n    size_t size;\n  } files[] =\n  {\n    { \"file1\", 256 },\n    { \"dirA/filedel\", 200 },\n    { \"dirA/file2\", 64 },\n    { \"dirA/dirB/file3\", 128 },\n    { \"dirA/dirB/file4\", 256 },\n    { \"dirA/dirC/file5\", 224 },\n    { \"dirA/dirC/file6\", 72 },\n    { \"fat://fileX\", 192 },\n    { \"fat://dirX/fileY\", 96 },\n  };\n\n  static const char file_buf[256]{};\n  size_t sz;\n  int ret;\n\n  // Initial size should be zero.\n  sz = vfs_get_cache_total_size(vfs);\n  ASSERTEQ(sz, 0, \"\");\n\n  // Making a root should not affect the cache size.\n  ret = vfs_make_root(vfs, \"fat\");\n  ASSERTEQ(ret, 0, \"\");\n\n  // Init several cache directories and verify they do NOT\n  // count toward the total cache size.\n  for(auto &d : dirs)\n  {\n    struct stat dummy{};\n    ret = vfs_cache_directory(vfs, d, &dummy);\n    ASSERTEQ(ret, 0, \"%s\", d);\n    sz = vfs_get_cache_total_size(vfs);\n    ASSERTEQ(sz, 0, \"%s\", d);\n  }\n\n  // Size should update after adding files.\n  size_t total = 0;\n  for(auto &f : files)\n  {\n    total += f.size;\n    ret = vfs_cache_file(vfs, f.path, file_buf, f.size);\n    ASSERTEQ(ret, 0, \"%s\", f.path);\n    sz = vfs_get_cache_total_size(vfs);\n    ASSERTEQ(sz, total, \"%s\", f.path);\n  }\n\n  // Create an extra file with non-trivial content to test invalidation.\n  total += fileopen_len;\n  ret = vfs_cache_file(vfs, \"fileopen\", fileopen_data, fileopen_len);\n  ASSERTEQ(ret, 0, \"fileopen\");\n  sz = vfs_get_cache_total_size(vfs);\n  ASSERTEQ(sz, total, \"fileopen\");\n  return total;\n}\n\nstatic uint32_t fileopen_prologue(ScopedVFS &vfs)\n{\n  uint32_t inode;\n  int ret = vfs_open_if_exists(vfs, \"fileopen\", false, &inode);\n  ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"\");\n  return inode;\n}\n\nstatic void fileopen_epilogue(ScopedVFS &vfs, uint32_t inode)\n{\n  // fileopen should have been invalidated at this point.\n  // However, because it has an open reference, it hasn't been freed yet\n  // and still counts toward the cache total size.\n  size_t sz;\n  int ret;\n\n  sz = vfs_get_cache_total_size(vfs);\n  ASSERTEQ(sz, fileopen_len, \"\");\n\n  const unsigned char *content;\n  size_t content_len;\n  ret = vfs_lock_file_read(vfs, inode, &content, &content_len);\n  ASSERTEQ(ret, 0, \"\");\n  ASSERTEQ(content_len, fileopen_len, \"\");\n  ASSERTMEM(content, fileopen_data, fileopen_len, \"\");\n\n  ret = vfs_unlock_file_read(vfs, inode);\n  ASSERTEQ(ret, 0, \"\");\n\n  ret = vfs_close(vfs, inode);\n  ASSERTEQ(ret, 0, \"\");\n\n  sz = vfs_get_cache_total_size(vfs);\n  ASSERTEQ(sz, 0, \"\");\n}\n\ntemplate<typename T>\nstatic void test_invalidate_current_dir(ScopedVFS &vfs, T invalidate)\n{\n  // The current working directory should NEVER be invalidated, but\n  // descendents should be.\n  static const struct\n  {\n    const char *path;\n    boolean is_dir;\n    int expected_ret;\n  } data[] =\n  {\n    { \"/dirA\",                  true,   VFS_ERR_IS_CACHED },\n    { \"/dirA/dirB\",             true,   VFS_ERR_IS_CACHED },\n    { \"/dirA/dirB/dirC\",        true,   VFS_ENOENT },\n    { \"/dirA/dirD\",             true,   VFS_ENOENT },\n    { \"/dirA/file1\",            false,  VFS_ENOENT },\n    { \"/dirA/dirB/file2\",       false,  VFS_ENOENT },\n    { \"/dirA/dirB/dirC/file3\",  false,  VFS_ENOENT },\n    { \"/dirA/dirD/file4\",       false,  VFS_ENOENT },\n  };\n\n  static const char file_buf[32]{};\n  int ret;\n\n  for(auto &d : data)\n  {\n    if(d.is_dir)\n    {\n      struct stat st{};\n      ret = vfs_cache_directory(vfs, d.path, &st);\n    }\n    else\n      ret = vfs_cache_file(vfs, d.path, file_buf, 32);\n\n    ASSERTEQ(ret, 0, \"%s\", d.path);\n  }\n\n  ret = vfs_chdir(vfs, \"/dirA/dirB\");\n  ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"\");\n  char buffer[MAX_PATH];\n  ret = vfs_getcwd(vfs, buffer, MAX_PATH);\n  ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"\");\n  ASSERTCMP(buffer, BASE \"dirA\" DIR_SEPARATOR \"dirB\", \"\");\n\n  ret = invalidate(vfs, \"/dirA\");\n  ASSERTEQ(ret, 0, \"\");\n\n  for(auto &d : data)\n  {\n    struct stat st{};\n    ret = vfs_stat(vfs, d.path, &st);\n    ASSERTEQ(ret, d.expected_ret, \"%s\", d.path);\n  }\n}\n\nUNITTEST(vfs_get_cache_total_size)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  ScopedVFS vfs = vfs_init();\n  ASSERT(vfs, \"\");\n  size_t total_minus_file1;\n  size_t total = 0;\n  size_t sz;\n  int ret;\n\n  total = setup_cache_testing_vfs(vfs);\n  total_minus_file1 = total - 256;\n\n  // Size should not be affected by creating a virtual file.\n  ret = vfs_create_file_at_path(vfs, \"vfile\");\n  ASSERTEQ(ret, 0, \"\");\n  sz = vfs_get_cache_total_size(vfs);\n  ASSERTEQ(sz, total, \"\");\n\n  // Size should not be affected by removing a virtual file.\n  ret = vfs_unlink(vfs, \"vfile\");\n  ASSERTEQ(ret, 0, \"\");\n  sz = vfs_get_cache_total_size(vfs);\n  ASSERTEQ(sz, total, \"\");\n\n  uint32_t inode;\n  ret = vfs_open_if_exists(vfs, \"file1\", true, &inode);\n  ASSERTEQ(ret, VFS_ERR_IS_CACHED, \"\");\n\n  // Size should update after a truncation.\n  // This function forces a minimum allocation size.\n  ret = vfs_truncate(vfs, inode);\n  ASSERTEQ(ret, 0, \"\");\n  sz = vfs_get_cache_total_size(vfs);\n  ASSERTEQ(sz, total_minus_file1 + 32, \"\");\n\n  // Size should update after an expanding write.\n  unsigned char **data;\n  size_t *data_length;\n  size_t *data_alloc;\n  ret = vfs_lock_file_write(vfs, inode, &data, &data_length, &data_alloc);\n  ASSERTEQ(ret, 0, \"\");\n\n  void *tmp = realloc(*data, 256);\n  ASSERT(tmp, \"\");\n\n  *data = reinterpret_cast<unsigned char *>(tmp);\n  *data_alloc = 256;\n  *data_length = 128;\n  memset(tmp, 'a', *data_length);\n\n  ret = vfs_unlock_file_write(vfs, inode);\n  ASSERTEQ(ret, 0, \"\");\n  sz = vfs_get_cache_total_size(vfs);\n  ASSERTEQ(sz, total_minus_file1 + 256, \"\");\n\n  // Size should update after a shrinking write.\n  ret = vfs_lock_file_write(vfs, inode, &data, &data_length, &data_alloc);\n  ASSERTEQ(ret, 0, \"\");\n\n  tmp = realloc(*data, 128);\n  ASSERT(tmp, \"\");\n\n  *data = reinterpret_cast<unsigned char *>(tmp);\n  *data_alloc = 128;\n\n  ret = vfs_unlock_file_write(vfs, inode);\n  ASSERTEQ(ret, 0, \"\");\n  sz = vfs_get_cache_total_size(vfs);\n  ASSERTEQ(sz, total_minus_file1 + 128, \"\");\n}\n\nUNITTEST(vfs_invalidate_at_path)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static constexpr struct\n  {\n    const char *path;\n    int expected_ret;\n    ptrdiff_t diff;\n  } data[] =\n  {\n    { \"noexist\",      VFS_ENOENT, 0 },\n    { \"sdcard://\",    VFS_ENOENT, 0 },\n    { \"dirA/fjsdf\",   VFS_ENOENT, 0 },\n    { \"file1\",        0,          -256 },\n    { \"file1\",        VFS_ENOENT, 0 },\n    { \"dirA/filedel\", 0,          -200 },\n    { \"dirA/dirB\",    0,          -384 },\n    { \"dirA/dirB\",    VFS_ENOENT, 0 },\n    { \"dirA\",         0,          -360 },\n    { \".\",            0,          0 },\n    { \"..\",           0,          0 },\n    { \"/\",            0,          0 },\n    { \"fat://\",       0,          -288 },\n    { \"fat://\",       0,          0 },\n  };\n\n  ScopedVFS vfs = vfs_init();\n  ASSERT(vfs, \"\");\n  ptrdiff_t diff;\n  size_t sz;\n  int ret;\n\n  size_t total = setup_cache_testing_vfs(vfs);\n  uint32_t inode = fileopen_prologue(vfs);\n\n  for(auto &d : data)\n  {\n    ret = vfs_invalidate_at_path(vfs, d.path);\n    ASSERTEQ(ret, d.expected_ret, \"%s %d\", d.path, d.expected_ret);\n    if(ret != 0)\n      continue;\n\n    sz = vfs_get_cache_total_size(vfs);\n    ASSERT(total >= sz, \"%s %d\", d.path, d.expected_ret);\n    diff = sz - total;\n    ASSERTEQ(diff, d.diff, \"%s %d\", d.path, d.expected_ret);\n    total = sz;\n  }\n\n  fileopen_epilogue(vfs, inode);\n\n  auto fn = [](vfilesystem *v, const char *p)\n   { return vfs_invalidate_at_path(v, p); };\n  test_invalidate_current_dir(vfs, fn);\n}\n\nUNITTEST(vfs_invalidate_at_least)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static constexpr struct\n  {\n    int expected_ret;\n    size_t request_diff;\n    size_t remaining_diff;\n    size_t remaining_total;\n    struct\n    {\n      const char *path;\n      int expected_stat;\n    } check[16];\n  } data[] =\n  {\n    { 0, 0, 0, 1520,\n     {{ \"file1\", VFS_ERR_IS_CACHED }}},\n    { 0, 256, 0, 1264,\n     {{ \"file1\", VFS_ENOENT }, { \"dirA/dirB/file4\", VFS_ERR_IS_CACHED }}},\n    { 0, 800, 0, 392,\n     {\n      { \"dirA/filedel\", VFS_ENOENT },\n      { \"dirA/dirB/file3\", VFS_ERR_IS_CACHED },\n      { \"dirA/dirB/file4\", VFS_ENOENT },\n      { \"dirA/dirC/file5\", VFS_ENOENT },\n      { \"fat://fileX\", VFS_ENOENT }}},\n    { 0, 1000, 640, fileopen_len,\n     {\n      { \"dirA/file2\", VFS_ENOENT },\n      { \"dirA/dirB/file3\", VFS_ENOENT },\n      { \"dirA/dirB/file6\", VFS_ENOENT },\n      { \"fat://dirX/fileY\", VFS_ENOENT }}},\n    { 0, 256, 256, fileopen_len, {}},\n  };\n\n  ScopedVFS vfs = vfs_init();\n  ASSERT(vfs, \"\");\n  size_t sz;\n  int ret;\n\n  // The sort used by this function uses timestamps to prioritize\n  // stale files for deletion. Disable them so this test is deterministic...\n  vfs_set_timestamps_enabled(vfs, false);\n\n  /*size_t total =*/ setup_cache_testing_vfs(vfs);\n  uint32_t inode = fileopen_prologue(vfs);\n\n  ret = vfs_invalidate_at_least(vfs, nullptr);\n  ASSERTEQ(ret, VFS_EINVAL, \"\");\n\n  char fmt[256];\n  for(auto &d : data)\n  {\n    snprintf(fmt, sizeof(fmt), \"%d %zu %zu %zu : %s\", d.expected_ret,\n     d.request_diff, d.remaining_diff, d.remaining_total, d.check[0].path);\n\n    size_t left = d.request_diff;\n    ret = vfs_invalidate_at_least(vfs, &left);\n    ASSERTEQ(ret, d.expected_ret, \"%s\", fmt);\n    ASSERTEQ(left, d.remaining_diff, \"%s\", fmt);\n    sz = vfs_get_cache_total_size(vfs);\n    ASSERTEQ(sz, d.remaining_total, \"%s\", fmt);\n\n    for(auto &c : d.check)\n    {\n      struct stat st{};\n      if(!c.path)\n        break;\n      ret = vfs_stat(vfs, c.path, &st);\n      ASSERTEQ(ret, c.expected_stat, \"%s (check: %s)\", fmt, c.path);\n    }\n  }\n\n  // Special: vfs_invalidate_at_least doesn't even try to invalidate\n  // open files.\n  ret = vfs_close(vfs, inode);\n  ASSERTEQ(ret, 0, \"\");\n  sz = vfs_get_cache_total_size(vfs);\n  ASSERTEQ(sz, fileopen_len, \"\");\n\n  //fileopen_epilogue(vfs, inode);\n}\n\nUNITTEST(vfs_invalidate_all)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  ScopedVFS vfs = vfs_init();\n  ASSERT(vfs, \"\");\n  int ret;\n\n  size_t total = setup_cache_testing_vfs(vfs);\n  uint32_t inode = fileopen_prologue(vfs);\n\n  // This function should reduce the size to JUST the size of fileopen.\n  ret = vfs_invalidate_all(vfs);\n  ASSERTEQ(ret, 0, \"\");\n  total = vfs_get_cache_total_size(vfs);\n  ASSERTEQ(total, fileopen_len, \"\");\n\n  fileopen_epilogue(vfs, inode);\n\n  auto fn = [](vfilesystem *v, const char *p)\n   { return vfs_invalidate_all(v); };\n  test_invalidate_current_dir(vfs, fn);\n}\n"
  },
  {
    "path": "unit/io/vio.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../Unit.hpp\"\n#include \"../../src/network/Scoped.hpp\"\n#include \"../../src/io/path.h\"\n#include \"../../src/io/vfs.h\" /* VIRTUAL_FILESYSTEM */\n#include \"../../src/io/vio.h\"\n\n#include <errno.h>\n\n#ifndef _WIN32\n#include <unistd.h>\n#endif\n\nstatic constexpr char TEST_READ_FILENAME[]  = \"VFILE_TEST_DATA\";\nstatic constexpr char TEST_WRITE_FILENAME[] = \"VFILE_TEST_WRITE\";\nstatic constexpr char TEST_DIR[]            = \"VFILE_TEST_DIR\";\n\n// NOTE: using the combining encodings of öè here to appease Mac OS X,\n// which converts some single codepoint encodings to combining encodings.\n// Some macOS SDKs (but not MacPorts clang) also have trouble with emoji.\n#define UTF8_DIR_STRING u8\"\\u00e6Ro\\u0308e\\u0300mMJ\\u00b7\\u2021\\u00b2\\u2019\\u02c6\\u00de\\u2018$\"\nstatic constexpr char UTF8_DIR[] = UTF8_DIR_STRING;\n//static constexpr char UTF8_FILE[] = u8\"\\u00A5\\u2014\\U0001F970\";\nstatic constexpr char UTF8_FILE[] = u8\"f\\u0302(\\u03be)=\\u222b(-\\u221e,\\u221e)f(x)e^(-i2\\u03c0\\u03bex)dx\";\nstatic constexpr char UTF8_FILE2[] = u8\"\\u222b(-\\u221e,\\u221e)f(x)e^(-i2\\u03c0\\u03bex)dx=f\\u0302(\\u03be)\";\n\n// Randomly generated binary data. Note: this is null terminated for vfputs.\n// The first byte is also \\n to help test vungetc/vfsafegets behavior.\nstatic const uint8_t test_data[] =\n{\n  0x0A,0xFF,0xE9,0x06,0x94,0xEE,0xC2,0xCE,0x87,0x72,0x5C,0xCE,0x5A,0xBD,0x72,0xE8,\n  0x3A,0xD3,0x47,0x5F,0x2B,0x3F,0x8E,0x31,0x04,0x04,0x87,0xA7,0xBB,0xD6,0x6C,0x2A,\n  0xE3,0x8A,0x0B,0x58,0x73,0xCC,0xB4,0x57,0xC9,0x10,0x55,0x69,0xEF,0xCB,0xE4,0xC1,\n  0x1C,0xE2,0xFD,0x96,0x7E,0x62,0xCD,0x75,0x36,0x73,0x2E,0xDB,0xA5,0x87,0x13,0x03,\n  0xE6,0xD7,0x27,0xFD,0x9B,0x10,0xB1,0x0D,0xED,0x49,0xBB,0x01,0xCE,0x39,0x73,0x84,\n  0x81,0xA7,0xD1,0xB3,0x5D,0x21,0x7A,0xAD,0xCE,0xF1,0xE5,0x20,0x9C,0x4D,0xBC,0x1A,\n  0x70,0xCE,0x85,0x1C,0x94,0x24,0x81,0x71,0xFA,0x07,0xD4,0xBD,0x80,0x70,0xE7,0xD9,\n  0x72,0x0A,0x0B,0xDE,0x50,0xE3,0x5F,0x80,0xD2,0x68,0xF3,0x9E,0x2A,0x90,0x2D,0x16,\n  0x89,0x57,0x6C,0xDE,0xE4,0x6E,0xEC,0x51,0xF8,0x31,0xEA,0xC6,0x8C,0xD9,0x08,0x67,\n  0xF6,0xF3,0xF2,0x41,0xE0,0x11,0x42,0x97,0x4C,0xBF,0xA1,0x7C,0xD6,0xB8,0x2F,0xA1,\n  0x39,0x5A,0x26,0x6B,0x15,0x58,0xBA,0x48,0xF0,0xAF,0x43,0x44,0x7A,0xDB,0x9D,0xD7,\n  0x15,0x4A,0xCF,0x01,0x94,0x11,0xEC,0x99,0x43,0xDE,0x37,0xE3,0x28,0x2D,0x8A,0x60,\n  0x89,0xBF,0xF9,0xEA,0xAF,0x48,0xB2,0x07,0xE9,0x69,0x27,0x5E,0xD3,0xDD,0x70,0xD0,\n  0xD7,0xF7,0xEA,0x49,0xF5,0x4C,0x25,0x2F,0xC0,0xAD,0xFD,0xFA,0xA9,0x58,0x06,0xFD,\n  0x80,0x6E,0x2E,0x83,0x38,0xA8,0x9D,0x1E,0xEB,0x46,0xE0,0x3C,0x1D,0x49,0x47,0xFB,\n  0x45,0xE2,0x8B,0x3F,0x8A,0x2A,0xB4,0x01,0xCA,0x13,0x3A,0xEA,0xE0,0x9F,0x6B,0x00,\n};\n\nstatic constexpr int VFSAFEGETS_BUFFER = 64;\nstatic constexpr int MAX_LINES = 10;\n\nstatic char execdir[1024];\n\nstruct vfsafegets_data\n{\n  const char *filename;\n  const char *input;\n  const char *output[MAX_LINES];\n};\n\nstatic const vfsafegets_data vfsafegets_test_data[] =\n{\n  // Test 1.\n  {\n    \"VFILE_TEST_DATA_TEXT\",\n\n    \"kjflkjsdlfksjdfksdj\\r\\n\"\n    \"abcdef\\n\"\n    //\"i hope no one is actually using these in 2020 ;-(\\r\"\n    \"use this line to fill past the buffer!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\\n\"\n    \"\\r\\n\"\n    \"end of file also counts as an end of line\",\n    {\n      \"kjflkjsdlfksjdfksdj\",\n      \"abcdef\",\n      //\"i hope no one is actually using these in 2020 ;-(\",\n      \"use this line to fill past the buffer!!!!!!!!!!!!!!!!!!!!!!!!!!\",\n      \"!!!!!!!!!\",\n      \"\",\n      \"end of file also counts as an end of line\",\n      nullptr\n    }\n  },\n  // Test 2.\n  {\n    \"VFILE_TEST_DATA_TEXT2\",\n    \"trailing EOL should result in a single line read.\\n\",\n    {\n      \"trailing EOL should result in a single line read.\",\n      nullptr\n    }\n  },\n  // Test 3.\n  {\n    \"VFILE_TEST_DATA_TEXT3\",\n    \"trailing EOL should result in a single line read.\\r\\n\",\n    {\n      \"trailing EOL should result in a single line read.\",\n      nullptr\n    }\n  },\n};\n\n#define back_up(bytes_from_end, vf) \\\n{ \\\n  static_assert(bytes_from_end < 0, \"invalid bytes from end value :(\"); \\\n  vfseek(vf, sizeof(test_data) + bytes_from_end - 1, SEEK_SET); \\\n  int c = vfgetc(vf); \\\n  ASSERTEQ(c, test_data[sizeof(test_data) + bytes_from_end - 1], \"not eof\"); \\\n}\n\nstatic void test_vfgetc(vfile *vf)\n{\n  for(size_t i = 0; i < sizeof(test_data); i++)\n  {\n    int c = vfgetc(vf);\n    ASSERTEQ(c, test_data[i], \"vfgetc offset=%zu\", i);\n  }\n  int c = vfgetc(vf);\n  ASSERTEQ(c, EOF, \"eof\");\n}\n\nstatic void test_vfgetw(vfile *vf)\n{\n  int expected;\n  int c;\n\n  for(size_t i = 0; i < sizeof(test_data); i += 2)\n  {\n    c = vfgetw(vf);\n    expected = test_data[i] | (test_data[i + 1] << 8);\n    ASSERTEQ(c, expected, \"vfgetw offset=%zu\", i);\n  }\n  // Both bytes should cause EOF.\n  c = vfgetw(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 0)\");\n\n  back_up(-1, vf);\n  c = vfgetw(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 1)\");\n}\n\nstatic void test_vfgetd(vfile *vf)\n{\n  int expected;\n  int c;\n\n  for(size_t i = 0; i < sizeof(test_data); i += 4)\n  {\n    c = vfgetd(vf);\n    expected = static_cast<int>(test_data[i] | (test_data[i + 1] << 8) |\n     (test_data[i + 2] << 16) | (test_data[i + 3] << 24));\n    ASSERTEQ(c, expected, \"vfgetd offset=%zu\", i);\n  }\n  // All four bytes should cause EOF.\n  c = vfgetd(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 0)\");\n\n  back_up(-1, vf);\n  c = vfgetd(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 1)\");\n\n  back_up(-2, vf);\n  c = vfgetd(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 2)\");\n\n  back_up(-3, vf);\n  c = vfgetd(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 3)\");\n}\n\nstatic void test_vfgetq(vfile *vf)\n{\n  int64_t expected;\n  int64_t c;\n\n  for(size_t i = 0; i < sizeof(test_data); i += 8)\n  {\n    c = vfgetq(vf);\n    expected = static_cast<int64_t>(test_data[i] | (test_data[i + 1] << 8) |\n     ((uint64_t)test_data[i + 2] << 16) | ((uint64_t)test_data[i + 3] << 24) |\n     ((uint64_t)test_data[i + 4] << 32) | ((uint64_t)test_data[i + 5] << 40) |\n     ((uint64_t)test_data[i + 6] << 48) | ((uint64_t)test_data[i + 7] << 56));\n    ASSERTEQ(c, expected, \"vfgetq offset=%zu\", i);\n  }\n  // All eight bytes should cause EOF.\n  c = vfgetq(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 0)\");\n\n  back_up(-1, vf);\n  c = vfgetq(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 1)\");\n\n  back_up(-2, vf);\n  c = vfgetq(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 2)\");\n\n  back_up(-3, vf);\n  c = vfgetq(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 3)\");\n\n  back_up(-4, vf);\n  c = vfgetq(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 4)\");\n\n  back_up(-5, vf);\n  c = vfgetq(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 5)\");\n\n  back_up(-6, vf);\n  c = vfgetq(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 6)\");\n\n  back_up(-7, vf);\n  c = vfgetq(vf);\n  ASSERTEQ(c, EOF, \"eof (byte 7)\");\n}\n\nstatic void test_vfread(vfile *vf)\n{\n  static constexpr int len = sizeof(test_data);\n  char buffer[len * 2];\n  int ret;\n\n  // One read >length should be refused.\n  ret = vfread(buffer, len * 2, 1, vf);\n  ASSERTEQ(ret, 0, \"vfread too much data\");\n  vrewind(vf);\n\n  // Two reads =length should read exactly length once.\n  ret = vfread(buffer, len, 2, vf);\n  ASSERTEQ(ret, 1, \"vfread 2x len\");\n  vrewind(vf);\n\n  // More reads...\n  ret = vfread(buffer, len / 2, 3, vf);\n  ASSERTEQ(ret, 2, \"vfread 1.5x len\");\n  vrewind(vf);\n\n  ret = vfread(buffer, len / 4, 5, vf);\n  ASSERTEQ(ret, 4, \"vfread 1.25x len\");\n  vrewind(vf);\n\n  ret = vfread(buffer, 3, len, vf);\n  ASSERTEQ(ret, len / 3, \"vfread len x 3\");\n  // NOTE: seems to be implementation-defined whether this ends up\n  // at 255 or 256, so don't bother testing that.\n\n  vrewind(vf);\n  ret = vfread(buffer, 0, len, vf);\n  ASSERTEQ(ret, 0, \"vfread 0 x len\");\n  ASSERTEQ(vftell(vf), 0, \"vfread 0 x len\");\n\n  ret = vfread(buffer, len, 0, vf);\n  ASSERTEQ(ret, 0, \"vfread len x 0\");\n  ASSERTEQ(vftell(vf), 0, \"vfread len x 0\");\n}\n\nstatic void test_write_check(vfile *vf)\n{\n  uint8_t tmp[sizeof(test_data)];\n  int r = vfread(tmp, sizeof(test_data), 1, vf);\n  ASSERTEQ(r, 1, \"test_write_check vfread\");\n  ASSERTMEM(test_data, tmp, sizeof(test_data), \"test_write_check memcmp\");\n  r = vfgetc(vf);\n  ASSERTEQ(r, EOF, \"test_write_check eof\");\n}\n\nstatic void test_vfputc(vfile *vf)\n{\n  //char buf[64];\n  long pos = vftell(vf);\n\n  for(size_t i = 0; i < sizeof(test_data); i++)\n  {\n    //snprintf(buf, sizeof(buf), \"vfputc offset=%d\", i);\n    vfputc(test_data[i], vf);\n  }\n  int r = vfseek(vf, pos, SEEK_SET);\n  ASSERTEQ(r, 0, \"vfseek\");\n  test_write_check(vf);\n}\n\nstatic void test_vfputw(vfile *vf)\n{\n  //char buf[64];\n  long pos = vftell(vf);\n\n  for(size_t i = 0; i < sizeof(test_data); i += 2)\n  {\n    int d = test_data[i] | (test_data[i + 1] << 8);\n    //snprintf(buf, sizeof(buf), \"vfputw offset=%zu\", i);\n    vfputw(d, vf);\n  }\n  int r = vfseek(vf, pos, SEEK_SET);\n  ASSERTEQ(r, 0, \"vfseek\");\n  test_write_check(vf);\n}\n\nstatic void test_vfputd(vfile *vf)\n{\n  //char buf[64];\n  long pos = vftell(vf);\n\n  for(size_t i = 0; i < sizeof(test_data); i += 4)\n  {\n    int d = test_data[i] | (test_data[i + 1] << 8) |\n     (test_data[i + 2] << 16) | (test_data[i + 3] << 24);\n    //snprintf(buf, sizeof(buf), \"vfputw offset=%zu, i);\n    vfputd(d, vf);\n  }\n  int r = vfseek(vf, pos, SEEK_SET);\n  ASSERTEQ(r, 0, \"vfseek\");\n  test_write_check(vf);\n}\n\nstatic void test_vfputq(vfile *vf)\n{\n  //char buf[64];\n  long pos = vftell(vf);\n\n  for(size_t i = 0; i < sizeof(test_data); i += 8)\n  {\n    int64_t d = static_cast<int64_t>(test_data[i] | (test_data[i + 1] << 8) |\n     ((uint64_t)test_data[i + 2] << 16) | ((uint64_t)test_data[i + 3] << 24) |\n     ((uint64_t)test_data[i + 4] << 32) | ((uint64_t)test_data[i + 5] << 40) |\n     ((uint64_t)test_data[i + 6] << 48) | ((uint64_t)test_data[i + 7] << 56));\n    //snprintf(buf, sizeof(buf), \"vfputw offset=%zu, i);\n    vfputq(d, vf);\n  }\n  int r = vfseek(vf, pos, SEEK_SET);\n  ASSERTEQ(r, 0, \"vfseek\");\n  test_write_check(vf);\n}\n\nstatic void test_vfwrite(vfile *vf)\n{\n  long pos = vftell(vf);\n\n  int r = vfwrite(test_data, 1, sizeof(test_data), vf);\n  ASSERTEQ(r, sizeof(test_data), \"vfwrite\");\n\n  r = vfseek(vf, pos, SEEK_SET);\n  ASSERTEQ(r, 0, \"vfseek\");\n  test_write_check(vf);\n\n  pos = vftell(vf);\n\n  r = vfwrite(test_data, 0, 1, vf);\n  ASSERTEQ(r, 0, \"vfwrite 0 x 1\");\n  ASSERTEQ(vftell(vf), pos, \"vfwrite 0 x 1\");\n\n  r = vfwrite(test_data, 1, 0, vf);\n  ASSERTEQ(r, 0, \"vfwrite 1 x 0\");\n  ASSERTEQ(vftell(vf), pos, \"vfwrite 1 x 0\");\n}\n\nstatic void test_vfputs(vfile *vf)\n{\n  long pos = vftell(vf);\n\n  int r = vfputs(reinterpret_cast<const char *>(test_data), vf);\n  ASSERTEQ(r, 0, \"vfputs\");\n\n  r = vfputc(0, vf);\n  ASSERTEQ(r, 0, \"vfputc\");\n\n  r = vfseek(vf, pos, SEEK_SET);\n  ASSERTEQ(r, 0, \"vfseek\");\n  test_write_check(vf);\n}\n\n// Note: vf_printf is a wrapper for vf_vprintf, so both are tested by this.\nstatic void test_vf_printf(vfile *vf)\n{\n  long pos = vftell(vf);\n\n  int ret = vf_printf(vf, \"%128.128s%.128s\", (const char *)test_data, (const char *)test_data + 128);\n  ASSERTEQ(ret, 255, \"vf_printf\");\n\n  // *printf doesn't write a null terminator.\n  ret = vfputc(0, vf);\n  ASSERTEQ(ret, 0, \"vfputc\");\n\n  ret = vfseek(vf, pos, SEEK_SET);\n  ASSERTEQ(ret, 0, \"vfseek\");\n  test_write_check(vf);\n}\n\nstatic void test_vfseek_vftell_vrewind_read(vfile *vf)\n{\n  static constexpr int set_valid[] = { 0, 128, 256, 7, 19, 157, 79, 9999 };\n  static constexpr int cur_valid[] = { 0, 128, 64, 32, 16, -16, -32, -64, -128 }; // Cumulative.\n  static constexpr int set_cur_invalid[] = { -15, -120312, -1 };\n  static constexpr int end_valid[] = { 0, -1, -5, -20, -256, 10, 9999 };\n  static constexpr int end_invalid[] = { -257, -2000, -127848, -2391231 };\n  static constexpr int len = sizeof(test_data);\n\n  long expected;\n  long pos;\n  int ret;\n\n  // SEEK_SET (valid).\n  for(int value : set_valid)\n  {\n    ret = vfseek(vf, value, SEEK_SET);\n    pos = vftell(vf);\n    ASSERTEQ(ret, 0, \"SEEK_SET valid: %d\", value);\n    ASSERTEQ(pos, value, \"SEEK_SET valid: %d\", value);\n  }\n  vrewind(vf);\n  pos = vftell(vf);\n  ASSERTEQ(pos, 0, \"vrewind after SEEK_SET\");\n\n  // SEEK_CUR (valid).\n  expected = 0;\n  for(int value : cur_valid)\n  {\n    expected += value;\n    ret = vfseek(vf, value, SEEK_CUR);\n    pos = vftell(vf);\n    ASSERTEQ(ret, 0, \"SEEK_CUR valid: %d\", value);\n    ASSERTEQ(pos, expected, \"SEEK_CUR valid: %d\", value);\n  }\n  vrewind(vf);\n  pos = vftell(vf);\n  ASSERTEQ(pos, 0, \"vrewind after SEEK_CUR\");\n\n  // SEEK_SET and SEEK_CUR (invalid).\n  for(int value : set_cur_invalid)\n  {\n    ret = vfseek(vf, value, SEEK_SET);\n    ASSERT(ret != 0, \"SEEK_SET invalid: %d\", value);\n    vrewind(vf);\n    pos = vftell(vf);\n    ASSERTEQ(pos, 0, \"SEEK_SET invalid: %d\", value);\n\n    ret = vfseek(vf, value, SEEK_CUR);\n    ASSERT(ret != 0, \"SEEK_CUR invalid: %d\", value);\n    vrewind(vf);\n    pos = vftell(vf);\n    ASSERTEQ(pos, 0, \"SEEK_CUR invalid: %d\", value);\n  }\n\n  // SEEK_END (valid).\n  for(int value : end_valid)\n  {\n    ret = vfseek(vf, value, SEEK_END);\n    pos = vftell(vf);\n    ASSERTEQ(ret, 0, \"SEEK_END valid: %d\", value);\n    ASSERTEQ(pos, len + value, \"SEEK_END valid: %d\", value);\n  }\n  vrewind(vf);\n  pos = vftell(vf);\n  ASSERTEQ(pos, 0, \"vrewind after SEEK_END\");\n\n  // SEEK_END (invalid).\n  for(int value : end_invalid)\n  {\n    ret = vfseek(vf, value, SEEK_END);\n    ASSERT(ret != 0, \"SEEK_END invalid: %d\", value);\n    vrewind(vf);\n    pos = vftell(vf);\n    ASSERTEQ(pos, 0, \"SEEK_END invalid: %d\", value);\n  }\n}\n\nstatic void test_filelength(long expected_len, vfile *vf)\n{\n  long pos = vftell(vf);\n  long len = vfilelength(vf, false);\n  long newpos = vftell(vf);\n\n  ASSERTEQ(len, expected_len, \"vfilelength\");\n  ASSERTEQ(pos, newpos, \"vfilelength with no rewind\");\n\n  if(len >> 1)\n  {\n    vfseek(vf, len >> 1, SEEK_SET);\n    len = vfilelength(vf, true);\n    newpos = vftell(vf);\n\n    ASSERTEQ(len, expected_len, \"vfilelength 2\");\n    ASSERTEQ(newpos, 0, \"vfilelength with rewind\");\n  }\n}\n\n// Make sure vfilelength accurately updates to match the current file position.\n// NOTE: this may not work on append files...\nstatic void test_filelength_write(vfile *vf)\n{\n  long pos = vftell(vf);\n  long len;\n  long size;\n  int ret;\n\n  // New file or appending to file, length should be vftell().\n  len = vfilelength(vf, false);\n  ASSERTEQ(len, pos, \"vfilelength == vftell (1)\");\n\n  // Write first half.\n  size = sizeof(test_data)/2;\n  ret = vfwrite(test_data, size, 1, vf);\n  ASSERTEQ(ret, 1, \"vfwrite (1)\");\n\n  len = vfilelength(vf, false);\n  ASSERTEQ(len, size + pos, \"vfilelength (2)\");\n  ASSERTEQ(vftell(vf), size + pos, \"vftell (2)\");\n\n  // Write some more junk.\n  ret = vfputc(0x12, vf);\n  ASSERTEQ(ret, 0x12, \"\");\n  ret = vfputc(0x34, vf);\n  ASSERTEQ(ret, 0x34, \"\");\n  ret = vfputw(0x7856, vf);\n  ASSERTEQ(ret, 0x7856, \"\");\n\n  size += 4;\n  len = vfilelength(vf, false);\n  ASSERTEQ(len, size + pos, \"vfilelength (3)\");\n  ASSERTEQ(vftell(vf), size + pos, \"vftell (3)\");\n}\n\nstatic void test_vungetc(vfile *vf)\n{\n  long pos;\n  int ret;\n  int64_t value;\n  int64_t first_qword;\n  char next_64[64];\n  char last_5[5];\n  char tmp[64];\n  char *retstr;\n\n  first_qword = vfgetq(vf);\n  vfread(next_64, 64, 1, vf);\n  vfseek(vf, sizeof(test_data) - 5, SEEK_SET);\n  vfread(last_5, 5, 1, vf);\n  vrewind(vf);\n\n  // vungetc should fail if EOF or some other junk is provided.\n  // Note: MSVCRT stdio &255s non-EOF values (?).\n  ret = vungetc(EOF, vf);\n  ASSERTEQ(ret, EOF, \"vungetc EOF\");\n  ret = vungetc(-128141, vf);\n  ASSERTEQ(ret, EOF, \"vungetc -128141\");\n  ret = vungetc(256, vf);\n  ASSERTEQ(ret, EOF, \"vungetc 256\");\n\n  // vfgetc should read the buffered char.\n  ret = vungetc(0xAB, vf);\n  ASSERTEQ(ret, 0xAB, \"vungetc 0xAB\");\n  value = vfgetc(vf);\n  ASSERTEQ(value, 0xAB, \"vfgetc 0xAB\");\n\n  // vfgetw should read the buffered char + one char from the data.\n  ret = vungetc(0xCD, vf);\n  ASSERTEQ(ret, 0xCD, \"vungetc 0xCD\");\n  value = vfgetw(vf);\n  ASSERTEQ(value, 0xCD | ((first_qword & 0xFF) << 8), \"vfgetw 0xCD ...\");\n\n  // vfgetd should read the buffered char + three chars from the data.\n  ret = vungetc(0xEF, vf);\n  ASSERTEQ(ret, 0xEF, \"vungetc 0xEF\");\n  value = vfgetd(vf);\n  ASSERTEQ(value, 0xEF | (first_qword & 0xFFFFFF00), \"vfgetd 0xEF ...\");\n\n  // vfgetq should read the buffered char + seven chars from the data.\n  ret = vfseek(vf, 1, SEEK_SET);\n  ASSERTEQ(ret, 0, \"vungetc 0x69\");\n  ret = vungetc(0x69, vf);\n  ASSERTEQ(ret, 0x69, \"vungetc 0x69\");\n  value = vfgetq(vf);\n  ASSERTEQ(value, 0x69 | (first_qword & ~0xFF), \"vfgetq 0x69 ...\");\n\n  // vfread should read the buffered char + n-1 chars from the data.\n  ret = vungetc(0x12, vf);\n  ASSERTEQ(ret, 0x12, \"vungetc 0x12\");\n  value = vfread(tmp, 64, 1, vf);\n  ASSERTEQ(value, 1, \"vfread 0x12 ...\");\n  ASSERTEQ(tmp[0], 0x12, \"vfread 0x12 ...\");\n  ASSERTMEM(tmp + 1, next_64, 63, \"vfread 0x12 ...\");\n\n  // vfsafegets should read the buffered char + n-1 chars from the data.\n  vfseek(vf, sizeof(test_data) - 5, SEEK_SET);\n  ret = vungetc(0xFF, vf);\n  ASSERTEQ(ret, 0xFF, \"vungetc 0xFF\");\n  retstr = vfsafegets(tmp, sizeof(tmp), vf);\n  ASSERT(retstr, \"vfsafegets 0xFF ...\");\n  ASSERTEQ(tmp[0], 0xFF, \"vfsafegets 0xFF ...\");\n  ASSERTMEM(tmp + 1, last_5, 5, \"vfsafegets 0xFF ...\");\n\n  // If the buffer char is \\r and the next char in the stream is \\n, consume both.\n  vfseek(vf, 0, SEEK_SET);\n  ret = vungetc(0x0D, vf);\n  ASSERTEQ(ret, 0x0D, \"vungetc 0x0D\");\n  retstr = vfsafegets(tmp, sizeof(tmp), vf);\n  ASSERT(retstr, \"vfsafegets 0x0D ...\");\n  ASSERTEQ((int)tmp[0], 0, \"vfsafegets 0x0D ...\");\n  pos = vftell(vf);\n  ASSERTEQ(pos, 1, \"vfsafegets 0x0D ...\");\n\n  // Reading a buffer char from the end of the file should not return NULL.\n  vfseek(vf, 0, SEEK_END);\n  ret = vungetc('a', vf);\n  ASSERTEQ(ret, 'a', \"vungetc 'a'\");\n  retstr = vfsafegets(tmp, sizeof(tmp), vf);\n  ASSERT(retstr, \"vsafegets 'a'\");\n  ASSERTCMP(retstr, \"a\", \"vsafegets 'a'\");\n  pos = vftell(vf);\n  ASSERTEQ(pos, sizeof(test_data), \"vsafegets 'a'\");\n\n  // vseek should discard the buffered char.\n  ret = vungetc(0x34, vf);\n  ASSERTEQ(ret, 0x34, \"vungetc 0x34\");\n  vfseek(vf, 128, SEEK_SET);\n  value = vfgetc(vf);\n  ASSERTEQ(value, test_data[128], \"vfgetc NOT 0x34\");\n\n  // vrewind should discard the buffered char.\n  ret = vungetc(0x56, vf);\n  ASSERTEQ(ret, 0x56, \"vungetc 0x56\");\n  vrewind(vf);\n  value = vfgetq(vf);\n  ASSERTEQ(value, first_qword, \"vfgetd NOT 0x56 ...\");\n\n  // vfilelength with rewind should discard the buffered char.\n  ret = vungetc(0x78, vf);\n  ASSERTEQ(ret, 0x78, \"vungetc 0x78\");\n  long len = vfilelength(vf, true);\n  ASSERTEQ(len, sizeof(test_data), \"vfgetd NOT 0x78\");\n  value = vfgetq(vf);\n  ASSERTEQ(value, first_qword, \"vfgetd NOT 0x78\");\n\n  // ftell should subtract the buffered char count from the cursor for binary\n  // streams. Calling ftell when (cursor - buffered char count)<0 is undefined.\n  ret = vfseek(vf, 128, SEEK_SET);\n  ASSERTEQ(ret, 0, \"vfseek\");\n  ret = vungetc(0x9A, vf);\n  ASSERTEQ(ret, 0x9A, \"vungetc 0x9A\");\n  pos = vftell(vf);\n  ASSERTEQ(pos, 127, \"vftell after vungetc\");\n}\n\nstatic void test_vfsafegets(vfile *vf, const char *filename,\n const char * const (&output)[MAX_LINES])\n{\n  char buffer[VFSAFEGETS_BUFFER];\n  char *cursor;\n  int line = 0;\n\n  while((cursor = vfsafegets(buffer, VFSAFEGETS_BUFFER, vf)))\n  {\n    ASSERT(line < MAX_LINES - 1, \"%s:%d\", filename, line);\n    ASSERT(output[line], \"%s:%d\", filename, line);\n    ASSERTCMP(buffer, output[line], \"%s:%d\", filename, line);\n    line++;\n  }\n  ASSERT(!output[line], \"\");\n}\n\n#define READ_TESTS(vf) do { \\\n  SECTION(read_vfgetc)      test_vfgetc(vf); \\\n  SECTION(read_vfgetw)      test_vfgetw(vf); \\\n  SECTION(read_vfgetd)      test_vfgetd(vf); \\\n  SECTION(read_vfgetq)      test_vfgetq(vf); \\\n  SECTION(read_vfread)      test_vfread(vf); \\\n  SECTION(read_vfseektell)  test_vfseek_vftell_vrewind_read(vf); \\\n  SECTION(read_filelength)  test_filelength(sizeof(test_data), vf); \\\n  SECTION(read_vungetc)     test_vungetc(vf); \\\n} while(0)\n\n#define WRITE_TESTS(vf, is_append) do { \\\n  SECTION(write_vfputc)     test_vfputc(vf); \\\n  SECTION(write_vfputw)     test_vfputw(vf); \\\n  SECTION(write_vfputd)     test_vfputd(vf); \\\n  SECTION(write_vfputq)     test_vfputq(vf); \\\n  SECTION(write_vfwrite)    test_vfwrite(vf); \\\n  SECTION(write_vfputs)     test_vfputs(vf); \\\n  SECTION(write_vf_printf)  test_vf_printf(vf); \\\n  SECTION(write_filelength) test_filelength_write(vf); \\\n  /*if(!is_append) { SECTION(write_vfseektell)  test_vfseek_vftell_rewind_write(vf); } */ \\\n} while(0)\n\nUNITTEST(Init)\n{\n  char *t = getcwd(execdir, sizeof(execdir));\n  ASSERTEQ(t, execdir, \"\");\n\n  FILE *fp = fopen_unsafe(TEST_READ_FILENAME, \"wb\");\n  ASSERT(fp, \"fopen_unsafe\");\n\n  int r = fwrite(test_data, sizeof(test_data), 1, fp);\n  ASSERTEQ(r, 1, \"fwrite\");\n\n  r = fclose(fp);\n  ASSERTEQ(r, 0, \"fclose\");\n\n  for(const vfsafegets_data &d : vfsafegets_test_data)\n  {\n    fp = fopen_unsafe(d.filename, \"wb\");\n    ASSERT(fp, \"fopen_unsafe\");\n\n    r = fwrite(d.input, strlen(d.input), 1, fp);\n    ASSERTEQ(r, 1, \"fwrite\");\n\n    r = fclose(fp);\n    ASSERTEQ(r, 0, \"fclose\");\n  }\n}\n\nUNITTEST(ModeFlags)\n{\n  struct mode_flag_pairs\n  {\n    const char *mode;\n    int expected;\n  };\n\n  static const mode_flag_pairs data[] =\n  {\n    { \"r\", VF_READ },\n    { \"w\", VF_WRITE | VF_TRUNCATE },\n    { \"a\", VF_WRITE | VF_APPEND },\n    { \"r+\", VF_READ | VF_WRITE },\n    { \"w+\", VF_READ | VF_WRITE | VF_TRUNCATE },\n    { \"a+\", VF_READ | VF_WRITE | VF_APPEND },\n    { \"rb\", VF_READ | VF_BINARY },\n    { \"wb\", VF_WRITE | VF_TRUNCATE | VF_BINARY },\n    { \"ab\", VF_WRITE | VF_APPEND | VF_BINARY },\n    // Both orderings of '+' and 'b' are valid.\n    { \"r+b\", VF_READ | VF_WRITE | VF_BINARY },\n    { \"w+b\", VF_READ | VF_WRITE | VF_TRUNCATE | VF_BINARY },\n    { \"a+b\", VF_READ | VF_WRITE | VF_APPEND | VF_BINARY },\n    { \"rb+\", VF_READ | VF_WRITE | VF_BINARY },\n    { \"wb+\", VF_READ | VF_WRITE | VF_TRUNCATE | VF_BINARY },\n    { \"ab+\", VF_READ | VF_WRITE | VF_APPEND | VF_BINARY },\n    // 't' to explicitly state text mode is non-standard but seems to be well-supported.\n    { \"rt\", VF_READ },\n    { \"wt\", VF_WRITE | VF_TRUNCATE },\n    { \"at\", VF_WRITE | VF_APPEND },\n    { \"r+t\", VF_READ | VF_WRITE },\n    { \"w+t\", VF_READ | VF_WRITE | VF_TRUNCATE },\n    { \"a+t\", VF_READ | VF_WRITE | VF_APPEND },\n    // 'r'/'w'/'a' must be first.\n    { \"br\", 0 },\n    { \"bw\", 0 },\n    { \"ba\", 0 },\n    { \"+r\", 0 },\n    { \"+w\", 0 },\n    { \"+a\", 0 },\n    { \"tr\", 0 },\n    // Other invalid junk.\n    { \"+\", 0 },\n    { \"b\", 0 },\n    { \"t\", 0 },\n    { \"    \", 0 },\n    { \"fjdskfdj\", 0 },\n    { \"\", 0 },\n  };\n\n  for(const mode_flag_pairs &d : data)\n  {\n    int res = vfile_get_mode_flags(d.mode);\n    ASSERTEQ(res, d.expected, \"%s\", d.mode);\n  }\n}\n\nUNITTEST(FileRead)\n{\n  ScopedFile<vfile, vfclose> vf_in =\n   vfopen_unsafe_ext(TEST_READ_FILENAME, \"rb\", V_SMALL_BUFFER);\n  ASSERT(vf_in, \"\");\n  ASSERT(~vfile_get_flags(vf_in) & VF_VIRTUAL, \"\");\n  READ_TESTS(vf_in);\n}\n\nUNITTEST(FileWrite)\n{\n  ScopedFile<vfile, vfclose> vf_out = vfopen_unsafe(TEST_WRITE_FILENAME, \"w+b\");\n  ASSERT(vf_out, \"\");\n  ASSERT(~vfile_get_flags(vf_out) & VF_VIRTUAL, \"\");\n  WRITE_TESTS(vf_out, false);\n}\n\nUNITTEST(FileAppend)\n{\n  ScopedFile<vfile, vfclose> vf_out = vfopen_unsafe(TEST_WRITE_FILENAME, \"a+b\");\n  ASSERT(vf_out, \"\");\n  ASSERT(~vfile_get_flags(vf_out) & VF_VIRTUAL, \"\");\n  // Align the read cursor with the write cursor.\n  vfseek(vf_out, 0, SEEK_END);\n  WRITE_TESTS(vf_out, true);\n}\n\nUNITTEST(MemoryRead)\n{\n  char buffer[sizeof(test_data)];\n  memcpy(buffer, test_data, sizeof(test_data));\n  ScopedFile<vfile, vfclose> vf_in = vfile_init_mem(buffer, sizeof(test_data), \"rb\");\n  ASSERT(vf_in, \"\");\n  ASSERT(~vfile_get_flags(vf_in) & VF_VIRTUAL, \"\");\n  READ_TESTS(vf_in);\n}\n\nUNITTEST(MemoryWrite)\n{\n  static constexpr int len = sizeof(test_data);\n  char buffer[len];\n  int ret;\n\n  ScopedFile<vfile, vfclose> vf_out = vfile_init_mem(buffer, len, \"w+b\");\n  ASSERT(vf_out, \"\");\n  ASSERT(~vfile_get_flags(vf_out) & VF_VIRTUAL, \"\");\n  WRITE_TESTS(vf_out, false);\n\n  SECTION(NoWritePastEnd)\n  {\n    /* Make sure writes that would exceed a fixed size buffer are refused. */\n    vfseek(vf_out, len, SEEK_SET);\n    ret = vfputc(0xAB, vf_out);\n    ASSERTEQ(ret, EOF, \"\");\n\n    vfseek(vf_out, len - 1, SEEK_SET);\n    ret = vfputw(0xCD, vf_out);\n    ASSERTEQ(ret, EOF, \"\");\n\n    vfseek(vf_out, len - 3, SEEK_SET);\n    ret = vfputd(0xEF, vf_out);\n    ASSERTEQ(ret, EOF, \"\");\n\n    static constexpr char tmp[] = \"abcdefghij\";\n    vfseek(vf_out, len - 7, SEEK_SET);\n    ret = vfputs(tmp, vf_out);\n    ASSERTEQ(ret, EOF, \"\");\n\n    vfseek(vf_out, 128, SEEK_SET);\n    ret = vfwrite(test_data, len, 1, vf_out);\n    ASSERTEQ(ret, 0, \"\");\n  }\n}\n\n/* Make sure buffer expansion works correctly. */\nUNITTEST(MemoryWriteExt)\n{\n  void *buffer = nullptr;\n  size_t size = 0;\n\n  ScopedFile<vfile, vfclose> vf_out = vfile_init_mem_ext(&buffer, &size, \"w+b\");\n  ASSERT(vf_out, \"\");\n  ASSERT(~vfile_get_flags(vf_out) & VF_VIRTUAL, \"\");\n  WRITE_TESTS(vf_out, false);\n  free(buffer);\n}\n\n/* That goes for append mode too... */\nUNITTEST(MemoryAppendExt)\n{\n  static constexpr size_t BUF_SIZE = 256;\n  void *buffer = malloc(BUF_SIZE);\n  size_t size = BUF_SIZE;\n  ASSERT(buffer, \"\");\n\n  {\n    ScopedFile<vfile, vfclose> vf_out = vfile_init_mem_ext(&buffer, &size, \"a+b\");\n    ASSERT(vf_out, \"\");\n    ASSERT(~vfile_get_flags(vf_out) & VF_VIRTUAL, \"\");\n    // Align the read cursor with the write cursor.\n    vfseek(vf_out, 0, SEEK_END);\n    WRITE_TESTS(vf_out, true);\n  }\n  free(buffer);\n  if(this->expected_section)\n    ASSERTEQ(size, sizeof(test_data) + BUF_SIZE, \"\");\n}\n\nUNITTEST(vfsafegets)\n{\n  SECTION(FileBinary)\n  {\n    for(const vfsafegets_data &d : vfsafegets_test_data)\n    {\n      ScopedFile<vfile, vfclose> vf = vfopen_unsafe_ext(d.filename, \"rb\", V_SMALL_BUFFER);\n      ASSERT(vf, \"\");\n      test_vfsafegets(vf, d.filename, d.output);\n    }\n  }\n\n  SECTION(FileText)\n  {\n    // In practice, nothing in MZX uses text streams (intentionally) because\n    // even most MZX text files can contain binary chars. See:\n    // https://www.digitalmzx.com/forums/index.php?app=tracker&showissue=592\n    for(const vfsafegets_data &d : vfsafegets_test_data)\n    {\n      ScopedFile<vfile, vfclose> vf = vfopen_unsafe(d.filename, \"r\");\n      ASSERT(vf, \"\");\n      test_vfsafegets(vf, d.filename, d.output);\n    }\n  }\n\n  SECTION(Memory)\n  {\n    // Memory vfiles don't bother implementing text mode so just assume binary.\n    for(const vfsafegets_data &d : vfsafegets_test_data)\n    {\n      size_t len = strlen(d.input);\n      ScopedPtr<char[]> tmp = new char[len + 1];\n\n      memcpy(tmp.get(), d.input, len + 1);\n      ScopedFile<vfile, vfclose> vf = vfile_init_mem(tmp.get(), len, \"rb\");\n      ASSERT(vf, \"\");\n      test_vfsafegets(vf, d.filename, d.output);\n    }\n  }\n}\n\nUNITTEST(vtempfile)\n{\n  SECTION(File)\n  {\n    ScopedFile<vfile, vfclose> vf = vtempfile(0);\n    ASSERT(vf, \"\");\n    test_vfwrite(vf);\n  }\n\n  SECTION(Memory)\n  {\n    ScopedFile<vfile, vfclose> vf = vtempfile(sizeof(test_data));\n    ASSERT(vf, \"\");\n    test_vfwrite(vf);\n  }\n\n  SECTION(MemorySmallInit)\n  {\n    ScopedFile<vfile, vfclose> vf = vtempfile(1);\n    ASSERT(vf, \"\");\n    test_vfputc(vf);\n  }\n}\n\nUNITTEST(Filesystem)\n{\n  static constexpr char TEST_RENAME_FILENAME[] = \"VFILE_TEST_dfbdfbshd\";\n  static constexpr char TEST_RENAME_DIR[] = \"VFILE_TEST_DIR_ndfjsdbnfjdfd\";\n  struct stat stat_info{}; // 0-init to silence MemorySanitizer.\n  int ret;\n\n  ret = vchdir(execdir);\n  ASSERTEQ(ret, 0, \"\");\n\n  SECTION(vchdir)\n  {\n    ret = vchdir(\"..\");\n    ASSERTEQ(ret, 0, \"\");\n    ret = vchdir(\"data\");\n    ASSERTEQ(ret, 0, \"\");\n\n    ScopedFile<vfile, vfclose> vf = vfopen_unsafe(\"CT_LEVEL.MOD\", \"rb\");\n    ASSERT(vf, \"\");\n    long len = vfilelength(vf, false);\n    ASSERTEQ(len, 111885, \"\");\n  }\n\n  SECTION(vgetcwd)\n  {\n    char buffer[1024];\n    char buffer2[1024];\n    char *t = vgetcwd(buffer, sizeof(buffer));\n    ASSERTEQ(t, buffer, \"\");\n    ret = vchdir(\"..\");\n    ASSERTEQ(ret, 0, \"\");\n    t = vgetcwd(buffer2, sizeof(buffer2));\n    ASSERTEQ(t, buffer2, \"\");\n    size_t len = strlen(buffer2);\n    ASSERT(len < sizeof(buffer), \"\");\n    ASSERTMEM(buffer, buffer2, len, \"\");\n  }\n\n  SECTION(vmkdir)\n  {\n    ret = vmkdir(TEST_DIR, 0777);\n    ASSERTEQ(ret, 0, \"\");\n    ret = stat(TEST_DIR, &stat_info);\n    ASSERTEQ(ret, 0, \"\");\n    ASSERT(S_ISDIR(stat_info.st_mode), \"\");\n  }\n\n  SECTION(vunlink)\n  {\n    ret = stat(TEST_WRITE_FILENAME, &stat_info);\n    ASSERTEQ(ret, 0, \"\");\n    ASSERT(S_ISREG(stat_info.st_mode), \"\");\n    ret = vunlink(TEST_WRITE_FILENAME);\n    ASSERTEQ(ret, 0, \"\");\n    ret = stat(TEST_WRITE_FILENAME, &stat_info);\n    ASSERT(ret != 0, \"\");\n  }\n\n  SECTION(vrmdir)\n  {\n    ret = stat(TEST_DIR, &stat_info);\n    ASSERTEQ(ret, 0, \"\");\n    ASSERT(S_ISDIR(stat_info.st_mode), \"\");\n    ret = vrmdir(TEST_DIR);\n    ASSERTEQ(ret, 0, \"\");\n    ret = stat(TEST_DIR, &stat_info);\n    ASSERT(ret != 0, \"\");\n  }\n\n  SECTION(vaccess)\n  {\n#ifndef _WIN32\n    static constexpr int access_flags = R_OK|W_OK|X_OK;\n#else\n    static constexpr int access_flags = R_OK|W_OK;\n#endif\n    ret = access(\".\", access_flags);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vaccess(\".\", R_OK|W_OK|X_OK);\n    ASSERTEQ(ret, 0, \"\");\n\n    ret = vchdir(\"..\");\n    ASSERTEQ(ret, 0, \"\");\n    ret = vchdir(\"data\");\n    ASSERTEQ(ret, 0, \"\");\n    ret = access(\"CT_LEVEL.MOD\", R_OK|W_OK);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vaccess(\"CT_LEVEL.MOD\", R_OK|W_OK);\n    ASSERTEQ(ret, 0, \"\");\n  }\n\n  SECTION(vstat)\n  {\n    struct stat stat_info2{};\n    ret = stat(\".\", &stat_info);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vstat(\".\", &stat_info2);\n    ASSERTEQ(ret, 0, \"\");\n    ASSERT(S_ISDIR(stat_info.st_mode), \"\");\n    ASSERT(S_ISDIR(stat_info2.st_mode), \"\");\n\n    ret = vchdir(\"..\");\n    ASSERTEQ(ret, 0, \"\");\n    ret = vchdir(\"data\");\n    ASSERTEQ(ret, 0, \"\");\n    ret = stat(\"CT_LEVEL.MOD\", &stat_info);\n    ASSERTEQ(ret, 0, \"\");\n    ret = vstat(\"CT_LEVEL.MOD\", &stat_info2);\n    ASSERTEQ(ret, 0, \"\");\n    ASSERT(S_ISREG(stat_info.st_mode), \"\");\n    ASSERT(S_ISREG(stat_info2.st_mode), \"\");\n  }\n\n  SECTION(vrename)\n  {\n    char buffer[1024];\n\n    // Clean up from any previous tests...\n    snprintf(buffer, sizeof(buffer), \"%s\" DIR_SEPARATOR \"%s\", TEST_RENAME_DIR, TEST_RENAME_FILENAME);\n    vunlink(buffer);\n    snprintf(buffer, sizeof(buffer), \"%s\" DIR_SEPARATOR \"%s\", TEST_DIR, TEST_RENAME_FILENAME);\n    vunlink(buffer);\n    vrmdir(TEST_RENAME_DIR);\n    vrmdir(TEST_DIR);\n\n    ret = vmkdir(TEST_DIR, 0777);\n    ASSERTEQ(ret, 0, \"\");\n\n    {\n      ScopedFile<vfile, vfclose> vf = vfopen_unsafe(TEST_WRITE_FILENAME, \"wb\");\n      ASSERT(vf, \"\");\n    }\n\n    ret = vstat(TEST_WRITE_FILENAME, &stat_info);\n    ASSERTEQ(ret, 0, \"\");\n\n    // Rename file.\n    ret = vrename(TEST_WRITE_FILENAME, buffer);\n    ASSERTEQ(ret, 0, \"\");\n\n    ret = vstat(buffer, &stat_info);\n    ASSERTEQ(ret, 0, \"\");\n\n    // Rename dir.\n    ret = vrename(TEST_DIR, TEST_RENAME_DIR);\n    ASSERTEQ(ret, 0, \"\");\n\n    ret = vstat(TEST_RENAME_DIR, &stat_info);\n    ASSERTEQ(ret, 0, \"\");\n\n    // Renamed filename still in dir?\n    ret = vchdir(TEST_RENAME_DIR);\n    ASSERTEQ(ret, 0, \"\");\n\n    ret = vstat(TEST_RENAME_FILENAME, &stat_info);\n    ASSERTEQ(ret, 0, \"\");\n  }\n\n  SECTION(UTF8)\n  {\n    static constexpr int utf8_dir_len = sizeof(UTF8_DIR) - 1;\n\n    ret = vstat(UTF8_DIR, &stat_info);\n    if(!ret)\n    {\n      ret = vchdir(UTF8_DIR);\n      ASSERTEQ(ret, 0, \"\");\n      ret = vunlink(UTF8_FILE);\n      ret = vunlink(UTF8_FILE2);\n      ret = vchdir(\"..\");\n      ASSERTEQ(ret, 0, \"\");\n      ret = vrmdir(UTF8_DIR);\n      ASSERTEQ(ret, 0, \"\");\n    }\n\n    ret = vmkdir(UTF8_DIR, 0777);\n    ASSERTEQ(ret, 0, \"\");\n\n    ret = vstat(UTF8_DIR, &stat_info);\n    ASSERTEQ(ret, 0, \"\");\n    ASSERT(S_ISDIR(stat_info.st_mode), \"\");\n\n    ret = vchdir(UTF8_DIR);\n    ASSERTEQ(ret, 0, \"\");\n    char buffer[1024];\n\n    char *t = vgetcwd(buffer, sizeof(buffer));\n    ASSERTEQ(t, buffer, \"\");\n    size_t len = strlen(buffer);\n    ASSERTCMP(buffer + len - utf8_dir_len, UTF8_DIR, \"\");\n\n    {\n      ScopedFile<vfile, vfclose> vf = vfopen_unsafe(UTF8_FILE, \"wb\");\n      ASSERT(vf, \"\");\n      ret = vfwrite(test_data, sizeof(test_data), 1, vf);\n      ASSERTEQ(ret, 1, \"\");\n    }\n\n    ret = vstat(UTF8_FILE, &stat_info);\n    ASSERTEQ(ret, 0, \"\");\n    ASSERTEQ(stat_info.st_size, sizeof(test_data), \"\");\n    ASSERT(S_ISREG(stat_info.st_mode), \"\");\n\n    ret = vaccess(UTF8_FILE, R_OK|W_OK);\n    ASSERTEQ(ret, 0, \"\");\n\n    ret = vrename(UTF8_FILE, UTF8_FILE2);\n    ASSERTEQ(ret, 0, \"\");\n\n    ret = vstat(UTF8_FILE2, &stat_info);\n    ASSERTEQ(ret, 0, \"\");\n    ASSERTEQ(stat_info.st_size, sizeof(test_data), \"\");\n    ASSERT(S_ISREG(stat_info.st_mode), \"\");\n\n    ret = vunlink(UTF8_FILE2);\n    ASSERTEQ(ret, 0, \"\");\n\n    ret = vchdir(\"..\");\n    ASSERTEQ(ret, 0, \"\");\n\n    ret = vrmdir(UTF8_DIR);\n    ASSERTEQ(ret, 0, \"\");\n  }\n}\n\n/**\n * Test dirent replacement functions separately from the other filesystem\n * functions, including UTF-8 cases.\n *\n * Note: files returned by dirent seem fairly platform-dependent, so filter\n * out anything starting with . for this test. Also, don't rely on the results\n * being ordered, because Linux doesn't sort them like Windows does.\n *\n * FIXME: should test the d_type return value too.\n */\n\ntemplate<long N>\nvoid test_dir_contents(const char *dirname, const char * const (&expected)[N],\n int flags = 0)\n{\n  boolean has_expected[N]{};\n  char buffer[1024];\n  boolean ret;\n  long length;\n  long i;\n\n  ScopedFile<vdir, vdir_close> dir = vdir_open_ext(dirname, flags);\n  ASSERT(dir != NULL, \"vdir_open(%s) failed\", dirname);\n\n  length = vdir_length(dir);\n  ASSERTEQ(vdir_tell(dir), 0, \"vdir_tell should be 0 after open: %s\", dirname);\n\n  while(vdir_read(dir, buffer, sizeof(buffer), NULL))\n  {\n    if(buffer[0] == '.')\n      continue;\n\n    for(i = 0; i < N; i++)\n    {\n      if(expected[i] && !strcmp(buffer, expected[i]))\n      {\n        has_expected[i] = true;\n        break;\n      }\n    }\n    if(i == N)\n      FAIL(\"unexpected file '%s' found in '%s'!\", buffer, dirname);\n  }\n  if(flags & VDIR_NO_SCAN)\n    goto skip_scan_checks;\n\n  // At EOF-vdir_length and vdir_tell should be equal.\n  ASSERTEQ(vdir_tell(dir), length, \"vdir_tell should be length after open: %s\", dirname);\n\n  ret = vdir_read(dir, NULL, 0, NULL);\n  ASSERTEQ(ret, false, \"vdir_read at directory EOF should return false: %s\", dirname);\n\n  // Seek back to start.\n  ret = vdir_seek(dir, 0);\n  ASSERTEQ(ret, true, \"vdir_seek should return true: %s\", dirname);\n  ASSERTEQ(vdir_tell(dir), 0, \"vdir_tell after seek back to start should be 0: %s\", dirname);\n\n  if(length >= 2)\n  {\n    ret = vdir_seek(dir, length / 2);\n    ASSERTEQ(ret, true, \"vdir_seek should return true: %s\", dirname);\n    ASSERTEQ(vdir_tell(dir), length / 2, \"vdir_tell failed: %s\", dirname);\n\n    ret = vdir_seek(dir, length);\n    ASSERTEQ(ret, true, \"vdir_seek should return true: %s\", dirname);\n    ASSERTEQ(vdir_tell(dir), length, \"vdir_tell failed: %s\", dirname);\n\n    ret = vdir_seek(dir, -19824);\n    ASSERTEQ(ret, false, \"vdir_seek should reject invalid negative seek value: %s\", dirname);\n    ASSERTEQ(vdir_tell(dir), length, \"vdir_tell should be unchanged: %s\", dirname);\n\n    ret = vdir_seek(dir, length + 10);\n    ASSERTEQ(ret, true, \"vdir_seek past end should be successful: %s\", dirname);\n    ASSERTEQ(vdir_tell(dir), length, \"vdir_seek past end should seek to end: %s\", dirname);\n  }\n\n  // Rewind back to start.\n  ret = vdir_rewind(dir);\n  ASSERTEQ(ret, true, \"vdir_rewind should return true: %s\", dirname);\n  ASSERTEQ(vdir_tell(dir), 0, \"vdir_tell after rewind back to start should be 0: %s\", dirname);\n\nskip_scan_checks:\n  for(i = 0; i < N; i++)\n  {\n    if(!expected[i])\n      continue;\n\n    if(!has_expected[i])\n      FAIL(\"missing expected file '%s' from '%s'!\", expected[i], dirname);\n  }\n}\n\nUNITTEST(dirent)\n{\n  static const char TEST_DIRENT_DIR[]     = \"VFILE_TEST_DIRENT\";\n  static const char TEST_DIRENT_DIR_UTF[] = u8\"VFILE_TEST_DIRENT_\" UTF8_DIR_STRING;\n\n  static const char *TEST_DIRENT_EMPTY[] = { nullptr };\n  static const char *TEST_DIRENT_NONEMPTY[] =\n  {\n    \"file1\",\n    \"file2\",\n    UTF8_FILE,\n    UTF8_FILE2,\n  };\n\n  // Cleanup from prior tests.\n  char buffer[1024];\n  int ret;\n\n  for(const char *filename : TEST_DIRENT_NONEMPTY)\n  {\n    snprintf(buffer, sizeof(buffer), \"%s\" DIR_SEPARATOR \"%s\", TEST_DIRENT_DIR, filename);\n    vunlink(buffer);\n    snprintf(buffer, sizeof(buffer), \"%s\" DIR_SEPARATOR \"%s\", TEST_DIRENT_DIR_UTF, filename);\n    vunlink(buffer);\n  }\n  vrmdir(TEST_DIRENT_DIR);\n  vrmdir(TEST_DIRENT_DIR_UTF);\n\n  ret = vmkdir(TEST_DIRENT_DIR, 0777);\n  ASSERTEQ(ret, 0, \"\");\n  ret = vmkdir(TEST_DIRENT_DIR_UTF, 0777);\n  ASSERTEQ(ret, 0, \"\");\n\n  SECTION(Empty)\n  {\n    test_dir_contents(TEST_DIRENT_DIR, TEST_DIRENT_EMPTY);\n  }\n\n  SECTION(EmptyNoScan)\n  {\n    test_dir_contents(TEST_DIRENT_DIR, TEST_DIRENT_EMPTY, VDIR_NO_SCAN);\n  }\n\n  SECTION(NonEmpty)\n  {\n    for(const char *filename : TEST_DIRENT_NONEMPTY)\n    {\n      snprintf(buffer, sizeof(buffer), \"%s\" DIR_SEPARATOR \"%s\", TEST_DIRENT_DIR, filename);\n      vfile *tmp = vfopen_unsafe(buffer, \"wb\");\n      ASSERT(tmp, \"failed to create file '%s'\", buffer);\n      vfclose(tmp);\n    }\n\n    test_dir_contents(TEST_DIRENT_DIR, TEST_DIRENT_NONEMPTY);\n  }\n\n  SECTION(NonEmptyNoScan)\n  {\n    for(const char *filename : TEST_DIRENT_NONEMPTY)\n    {\n      snprintf(buffer, sizeof(buffer), \"%s\" DIR_SEPARATOR \"%s\", TEST_DIRENT_DIR, filename);\n      vfile *tmp = vfopen_unsafe(buffer, \"wb\");\n      ASSERT(tmp, \"failed to create file '%s'\", buffer);\n      vfclose(tmp);\n    }\n    test_dir_contents(TEST_DIRENT_DIR, TEST_DIRENT_NONEMPTY, VDIR_NO_SCAN);\n  }\n\n  SECTION(EmptyUTF8)\n  {\n    test_dir_contents(TEST_DIRENT_DIR_UTF, TEST_DIRENT_EMPTY);\n  }\n\n  SECTION(NonEmptyUTF8)\n  {\n    for(const char *filename : TEST_DIRENT_NONEMPTY)\n    {\n      snprintf(buffer, sizeof(buffer), \"%s\" DIR_SEPARATOR \"%s\", TEST_DIRENT_DIR_UTF, filename);\n      vfile *tmp = vfopen_unsafe(buffer, \"wb\");\n      ASSERT(tmp, \"failed to create file '%s'\", buffer);\n      vfclose(tmp);\n    }\n\n    test_dir_contents(TEST_DIRENT_DIR_UTF, TEST_DIRENT_NONEMPTY);\n  }\n}\n\nstatic constexpr const char *expected_real_roots[] =\n{\n#if defined(_WIN32) || defined(CONFIG_DJGPP)\n  /* The only guaranteed root for these is C: */\n  \"C:\",\n#elif defined(CONFIG_AMIGA)\n  \"sys:\",\n#else\n  DIR_SEPARATOR\n#endif\n};\n\nUNITTEST(vvolumelist)\n{\n  boolean found[arraysize(expected_real_roots)]{};\n\n  ScopedFile<vvolumelist, vvolumelist_close> volumes = vvolumelist_open();\n  const char *file;\n\n  ASSERT(volumes, \"failed to open volume list\");\n\n  while((file = vvolumelist_read(volumes)))\n  {\n    for(int i = 0; i < arraysize(expected_real_roots); i++)\n    {\n      if(!strcasecmp(file, expected_real_roots[i]))\n      {\n        ASSERT(!found[i], \"root '%s' duplicated in volume list\", expected_real_roots[i]);\n        found[i] = true;\n      }\n    }\n  }\n  for(int i = 0; i < arraysize(found); i++)\n    ASSERT(found[i], \"root '%s' missing from volume list\", expected_real_roots[i]);\n}\n\n\n/**********************************************************************\n * Virtual filesystem tests.\n */\n\nstatic constexpr size_t cache_max_size = 16384;\n\nclass vfssetup\n{\npublic:\n  vfssetup(boolean enable_cache)\n  {\n    // Safety--purge the VFS if it exists first.\n    vio_filesystem_exit();\n\n    if(enable_cache)\n    {\n      boolean ret = vio_filesystem_init(cache_max_size, cache_max_size / 2, true);\n      ASSERT(ret, \"\");\n    }\n    else\n    {\n      boolean ret = vio_filesystem_init(0, 0, false);\n      ASSERT(ret, \"\");\n    }\n  }\n  ~vfssetup() { vio_filesystem_exit(); }\n};\n\nUNITTEST(VirtualRead)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static constexpr char read_file[] = \"virtual_read.bin\";\n  size_t sz;\n  int ret;\n\n  ret = vchdir(execdir);\n  ASSERTEQ(ret, 0, \"\");\n\n  vfssetup a(false);\n\n  ret = vio_virtual_file(read_file);\n  ASSERT(ret, \"\");\n\n  ScopedFile<vfile, vfclose> vf = vfopen_unsafe(read_file, \"w+b\");\n  ASSERT(vf, \"\");\n\n  int flags = vfile_get_flags(vf);\n  ASSERT(flags & VF_MEMORY, \"\");\n  ASSERT(flags & VF_VIRTUAL, \"\");\n\n  // Sadly one write is required currently :(\n  sz = vfwrite(test_data, 1, sizeof(test_data), vf);\n  ASSERTEQ(sz, sizeof(test_data), \"\");\n  vrewind(vf);\n\n  READ_TESTS(vf);\n}\n\nUNITTEST(VirtualWrite)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static constexpr char write_file[] = \"virtual_write.bin\";\n\n  vfssetup a(false);\n  int ret;\n\n  ret = vio_virtual_file(write_file);\n  ASSERT(ret, \"\");\n\n  ScopedFile<vfile, vfclose> vf = vfopen_unsafe(write_file, \"w+b\");\n  ASSERT(vf, \"\");\n\n  int flags = vfile_get_flags(vf);\n  ASSERT(flags & VF_MEMORY, \"\");\n  ASSERT(flags & VF_VIRTUAL, \"\");\n\n  WRITE_TESTS(vf, false);\n  vf.reset();\n\n  ScopedFile<vfile, vfclose> vf_trunc = vfopen_unsafe(write_file, \"wb\");\n  ASSERT(vf_trunc, \"\");\n  int64_t len = vfilelength(vf_trunc, false);\n  ASSERTEQ(len, 0, \"mode 'wb' should truncate virtual files\");\n}\n\nUNITTEST(VirtualFilesystem)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  /* Tests most filesystem aspects of virtual (not cached) files and\n   * directories, plus most aspects of cached directories (which are\n   * always created as they are required for virtual file support).\n   */\n  static constexpr char file_virt[] = \"vfile_please_dont_make_a_real_file.txt\";\n  static constexpr char file_over[] = \"overlay.txt\";\n  static constexpr char dir_virt[] = \"vdir_please_dont_make_a_real_dir\";\n  static constexpr char dir_over[] = \"overlay\";\n  static constexpr char dir_real[] = \"real_dir\";\n  static constexpr char dir_real_file[] = \"real_dir/file\";\n\n  static constexpr struct\n  {\n    const char *path;\n    int type;\n    int rm_result;\n  } data[] =\n  {\n    { file_virt,                    S_IFREG, 0 },\n    { file_over,                    S_IFREG, 0 },\n    { dir_virt,                     S_IFDIR, 0 },\n    { dir_over,                     S_IFDIR, 0 },\n    { \"../data/vdir\",               S_IFDIR, ENOTEMPTY },\n    { \"../data/vdir/vdir2\",         S_IFDIR, ENOTEMPTY },\n    { \"../data/vdir/vfile\",         S_IFREG, 0 },\n    { \"../data/vdir/vdir2/vfile2\",  S_IFREG, 0 },\n  };\n  struct stat st{}; // 0-init to silence MemorySanitizer.\n  char buf[MAX_PATH];\n  char *t;\n  FILE *fp;\n  int ret;\n\n  // Safety--some files need to be uncached for testing.\n  vio_filesystem_exit();\n\n  ret = vchdir(execdir);\n  ASSERTEQ(ret, 0, \"\");\n\n  // Make a real directory with no overlay or cache initially.\n  vunlink(dir_real_file);\n  vrmdir(dir_real);\n  ret = vmkdir(dir_real, 0755);\n  ASSERTEQ(ret, 0, \"vmkdir an uncached directory\");\n\n  vfssetup a(false);\n\n  // Make a virtual file. The filesystem functions should fully support it.\n  ret = vio_virtual_file(file_virt);\n  ASSERT(ret, \"\");\n\n  // Make a virtual directory. The filesystem functions should fully support it.\n  ret = vio_virtual_directory(dir_virt);\n  ASSERT(ret, \"\");\n\n  // Make a virtual file that is overlaid over a real file.\n  // The filesystem functions should ignore the real file.\n  fp = fopen_unsafe(file_over, \"wb\");\n  ASSERT(fp, \"\");\n  fclose(fp);\n  ret = vio_virtual_file(file_over);\n  ASSERT(ret, \"\");\n\n  // Make a virtual directory that is overlaid over a real directory.\n  // The filesystem functions should ignore the real directory, especially\n  // e.g. vmkdir should make a virtual child of the virtual directory, but\n  // not the real one.\n  vrmdir(dir_over);\n  ret = vmkdir(dir_over, 0755);\n  ASSERTEQ(ret, 0, \"\");\n  ret = vio_virtual_directory(dir_over);\n  ASSERT(ret, \"\");\n\n  // Make virtual directories and file that use relative path tokens.\n  ret = vio_virtual_directory(\"../data/vdir\");\n  ASSERT(ret, \"\");\n  ret = vio_virtual_directory(\"../data/./vdir/./vdir2\");\n  ASSERT(ret, \"\");\n  ret = vio_virtual_file(\"../data/vdir/./././vfile\");\n  ASSERT(ret, \"\");\n  ret = vio_virtual_file(\"../data/vdir/vdir2/../vdir2/vfile2\");\n  ASSERT(ret, \"\");\n\n  SECTION(vchdir_vgetcwd)\n  {\n    char expected[MAX_PATH];\n    char expected2[MAX_PATH];\n    char expected3[MAX_PATH];\n    path_join(expected, MAX_PATH, execdir, dir_virt);\n    path_join(expected2, MAX_PATH, execdir, dir_over);\n    memcpy(expected3, execdir, MAX_PATH);\n    path_navigate_no_check(expected3, MAX_PATH, \"../data/vdir/vdir2\");\n\n    ret = vchdir(dir_virt);\n    ASSERTEQ(ret, 0, \"vchdir into virtual directory\");\n    t = vgetcwd(buf, MAX_PATH);\n    ASSERT(t, \"vgetcwd in virtual directory\");\n    ASSERTCMP(t, expected, \"vgetcwd in virtual directory\");\n\n    // Should be able to change from the current (virtual) dir\n    // into the real *uncached* directory. This is important to test\n    // because the current directory of the VFS MUST be cached. Changing\n    // into this directory should cache it.\n    path_join(buf, MAX_PATH, \"..\", dir_real);\n    ret = vchdir(buf);\n    ASSERTEQ(ret, 0, \"vchdir into uncached directory\");\n    char expected_real[MAX_PATH];\n    path_join(expected_real, MAX_PATH, execdir, dir_real);\n    t = vgetcwd(buf, MAX_PATH);\n    ASSERT(t, \"vgetcwd from uncached directory\");\n    ASSERTCMP(t, expected_real, \"vgetcwd from uncached directory\");\n\n    path_join(buf, sizeof(buf), \"..\", dir_over);\n    ret = vchdir(buf);\n    ASSERTEQ(ret, 0, \"vchdir ../overlay\");\n    t = vgetcwd(buf, MAX_PATH);\n    ASSERT(t, \"vgetcwd in virtual directory 2\");\n    ASSERTCMP(t, expected2, \"vgetcwd in virtual directory 2\");\n\n    ret = vchdir(\"..\");\n    ASSERTEQ(ret, 0, \"vchdir back into a real dir\");\n    t = getcwd(buf, MAX_PATH);\n    ASSERT(t, \"real getcwd should be the original dir\");\n    ASSERTCMP(t, execdir, \"real getcwd should be the original dir\");\n\n    ret = vchdir(\"../data/vdir/vdir2\");\n    ASSERTEQ(ret, 0, \"vchdir into the directories made at ..\");\n    t = vgetcwd(buf, MAX_PATH);\n    ASSERT(t, \"vgetcwd in virtual directory 3\");\n    ASSERTCMP(t, expected3, \"vgetcwd in virtual directory 3\");\n\n    // This specifically checks an implementation detail of vchdir:\n    // vfs_chdir is used for virtual dirs, then real chdir, then vfs_chdir.\n    // If the dir is cached, there's a possibility both vfs_chdirs could be\n    // called, causing the real CWD and VFS CWD to desynchronize. This bug\n    // required the initial CWD to also be a real/cached directory.\n    // If the virtual file opens this is probably okay.\n    ret = vchdir(\"../../zip64\");\n    ASSERTEQ(ret, 0, \"vchdir into a real dir 2\");\n    ret = vchdir(\"../../.build\");\n    ASSERTEQ(ret, 0, \"vchdir back into the original dir\");\n\n    t = getcwd(buf, MAX_PATH);\n    ASSERT(t, \"real getcwd should be the original dir\");\n    ASSERTCMP(t, execdir, \"real getcwd should be the original dir\");\n    t = vgetcwd(buf, MAX_PATH);\n    ASSERT(t, \"vgetcwd should be the original dir\");\n    ASSERTCMP(t, execdir, \"vgetcwd should be the original dir\");\n\n    // See above.\n    {\n      ScopedFile<vfile, vfclose> vf = vfopen_unsafe(file_virt, \"rb\");\n      ASSERT(vf, \"cwd and VFS cwd desyncronized!\");\n    }\n  }\n\n  SECTION(vmkdir)\n  {\n    vrmdir(\"a_real_dir\");\n\n    // vmkdir should make a virtual directory while in a virtual directory.\n    ret = vchdir(dir_virt);\n    ASSERTEQ(ret, 0, \"vchdir into virtual directory\");\n    ret = vmkdir(\"another_dir\", 0755);\n    ASSERTEQ(ret, 0, \"vmkdir inside of a virtual directory\");\n    ret = vchdir(\"another_dir\");\n    ASSERTEQ(ret, 0, \"vchdir into new virtual directory\");\n\n    // vmkdir should be able to make real directories in real dirs while\n    // the current directory is a virtual dir, too.\n    char expected[MAX_PATH];\n    path_join(expected, MAX_PATH, execdir, \"a_real_dir\");\n\n    ret = vmkdir(\"../../a_real_dir\", 0755);\n    ASSERTEQ(ret, 0, \"vmkdir a real directory from a virtual directory\");\n    ret = vchdir(\"../../a_real_dir\");\n    ASSERTEQ(ret, 0, \"vchdir into the new real directory\");\n    t = getcwd(buf, MAX_PATH);\n    ASSERT(t, \"real getcwd should be the new directory\");\n    ASSERTCMP(t, expected, \"real getcwd should be the new directory\");\n\n    // Nonsense dir--should set errno to ENOENT.\n    ret = vmkdir(\"sdjfkdsjfsd/dfjsdfsd/kfglkfdlgfd\", 0755);\n    ASSERTEQ(ret, -1, \"nonsense vmkdir should fail\");\n    ASSERTEQ(errno, ENOENT, \"nonsense vmkdir should fail with ENOENT\");\n  }\n\n  SECTION(vrename)\n  {\n    // Should be able to rename virtual files.\n    ret = vrename(file_virt, \"boob\");\n    ASSERTEQ(ret, 0, \"vrename virt file\");\n\n    // ...but not into a nonexistent directory.\n    ret = vrename(\"boob\", \"sdhjfsdfdk/boob\");\n    ASSERTEQ(ret, -1, \"vrename virt file into nonsense directory\");\n    ASSERTEQ(errno, ENOENT, \"vrename virt file into nonsense directory\");\n\n    // Should be able to move virtual files into, within,\n    // and out of virtual directories.\n    path_join(buf, MAX_PATH, dir_virt, \"boob\");\n    ret = vrename(\"boob\", buf);\n    ASSERTEQ(ret, 0, \"vrename virt file into virt dir\");\n    char buf2[MAX_PATH];\n    path_join(buf2, MAX_PATH, dir_virt, file_virt);\n    ret = vrename(buf, buf2);\n    ASSERTEQ(ret, 0, \"rename virt file within virt dir\");\n    ret = vrename(buf2, file_virt);\n    ASSERTEQ(ret, 0, \"rename virt file back into real dir\");\n\n    // Moving a virtual directory should move the virtual files inside of the directory.\n    ret = vrename(file_virt, buf2);\n    ASSERTEQ(ret, 0, \"rename virt file back into virt dir\");\n    ret = vrename(dir_virt, \"da_new_dir\");\n    ASSERTEQ(ret, 0, \"rename virt dir with virt file inside\");\n    path_join(buf, MAX_PATH, \"da_new_dir\", file_virt);\n    ret = vstat(buf, &st);\n    ASSERTEQ(ret, 0, \"found virt file with the new virt dir name\");\n\n    // Moving a real directory should move the virtual files inside of the directory.\n    vunlink(\"da_new_real_dir/file\");\n    vrmdir(\"da_real_dir\");\n    vrmdir(\"da_new_real_dir\");\n    ret = vmkdir(\"da_real_dir\", 0755);\n    ASSERTEQ(ret, 0, \"make real dir\");\n    path_join(buf2, MAX_PATH, \"da_real_dir\", file_virt);\n    ret = vrename(buf, buf2);\n    ASSERTEQ(ret, 0, \"move virt file into real dir\");\n    ret = vrename(\"da_real_dir\", \"da_new_real_dir\");\n    ASSERTEQ(ret, 0, \"rename real dir with virt file inside\");\n    path_join(buf, MAX_PATH, \"da_new_real_dir\", file_virt);\n    ret = vstat(buf, &st);\n    ASSERTEQ(ret, 0, \"found virt file with the new real dir name\");\n\n    // Moving a directory over an uncached unempty directory shouldn't work.\n    {\n      ScopedFile<FILE, fclose> fp = fopen_unsafe(dir_real_file, \"wb\");\n      ASSERT(fp, \"create real file\");\n    }\n    ret = vrename(\"da_new_real_dir\", dir_real);\n    ASSERTEQ(ret, -1, \"move dir over unempty uncached dir\");\n    if(errno != EEXIST && errno != ENOTEMPTY)\n    {\n      ASSERTEQ(errno, ENOTEMPTY,\n       \"move dir over unempty uncached dir (should be EEXIST or ENOTEMPTY)\");\n    }\n    ret = vstat(buf, &st);\n    ASSERTEQ(ret, 0, \"virt file should exist at old location\");\n    ret = vstat(dir_real_file, &st);\n    ASSERTEQ(ret, 0, \"real file should exist\");\n  }\n\n  SECTION(vunlink)\n  {\n    for(auto &d : data)\n    {\n      ret = vunlink(d.path);\n      if(d.type == S_IFREG)\n        ASSERTEQ(ret, 0, \"%s\", d.path);\n      else\n      {\n        ASSERTEQ(ret, -1, \"%s\", d.path);\n        ASSERTEQ(errno, EPERM, \"%s\", d.path);\n      }\n    }\n    ret = stat(file_over, &st);\n    ASSERTEQ(ret, 0, \"real file at '%s' should still exist\", file_over);\n  }\n\n  SECTION(vrmdir)\n  {\n    for(auto &d : data)\n    {\n      ret = vrmdir(d.path);\n      if(d.type == S_IFREG)\n      {\n        ASSERTEQ(ret, -1, \"%s\", d.path);\n        ASSERTEQ(errno, ENOTDIR, \"%s\", d.path);\n      }\n      else\n\n      if(d.rm_result)\n      {\n        ASSERTEQ(ret, -1, \"%s\", d.path);\n        ASSERTEQ(errno, d.rm_result, \"%s\", d.path);\n      }\n      else\n        ASSERTEQ(ret, d.rm_result, \"%s\", d.path);\n    }\n    ret = stat(dir_over, &st);\n    ASSERTEQ(ret, 0, \"real dir at '%s' should still exist\", dir_over);\n\n    // vrmdir should delete the underlying directory of a cached dir.\n    ret = vchdir(dir_real);\n    ASSERTEQ(ret, 0, \"make sure dir_real is cached\");\n    ret = vchdir(\"..\");\n    ASSERTEQ(ret, 0, \"return to initial directory\");\n    ret = vrmdir(dir_real);\n    ASSERTEQ(ret, 0, \"delete cached dir_real\");\n    ret = stat(dir_real, &st);\n    ASSERTEQ(ret, -1, \"dir_real stat should fail\");\n    ASSERTEQ(errno, ENOENT, \"dir_real stat should fail with ENOENT\");\n  }\n\n  SECTION(vaccess)\n  {\n    for(auto &d: data)\n    {\n      ret = vaccess(d.path, R_OK|W_OK|X_OK);\n      ASSERTEQ(ret, 0, \"%s\", d.path);\n    }\n  }\n\n  SECTION(vstat)\n  {\n    for(auto &d : data)\n    {\n      ret = vstat(d.path, &st);\n      ASSERTEQ(ret, 0, \"%s\", d.path);\n      ASSERTEQ(st.st_dev, VFS_MZX_DEVICE, \"%s\", d.path);\n      ASSERTEQ((int)st.st_mode & S_IFMT, d.type, \"%s\", d.path);\n    }\n  }\n\n  SECTION(dirent)\n  {\n    enum vdir_type type;\n    boolean found[ARRAY_SIZE(data)]{};\n    boolean found_dir_real = false;\n\n    // Make sure dir_real IS cached.\n    ret = vchdir(dir_real);\n    ASSERTEQ(ret, 0, \"make sure dir_real is cached\");\n    ret = vchdir(\"..\");\n    ASSERTEQ(ret, 0, \"return to initial directory\");\n    t = vgetcwd(buf, MAX_PATH);\n    ASSERT(t, \"check initial directory\");\n    ASSERTCMP(t, execdir, \"check initial directory\");\n\n    ScopedFile<vdir, vdir_close> dir = vdir_open(\".\");\n    ASSERT(dir, \"\");\n\n    // Virtual files should be reported in a directory exactly once.\n    // Real cached files should not be counted twice.\n    // TODO: virtual files that exist OVER real files do show up\n    // twice for now because there's not really a good way around it.\n    while(vdir_read(dir, buf, MAX_PATH, &type))\n    {\n      if(buf[0] != '.')\n      {\n        ret = vstat(buf, &st);\n        ASSERTEQ(ret, 0, \"failed to vstat %s\", buf);\n      }\n\n      if(strcmp(buf, dir_real) == 0)\n      {\n        ASSERT(!found_dir_real, \"duplicate of %s\", dir_real);\n        found_dir_real = true;\n      }\n      else\n\n      for(size_t i = 0; i < ARRAY_SIZE(data); i++)\n      {\n        if(strcmp(buf, data[i].path) == 0)\n        {\n          // TODO: Hack to allow virtual files over real files to pass.\n          if(data[i].path != dir_over && data[i].path != file_over)\n            ASSERT(!found[i], \"duplicate of %s\", data[i].path);\n          found[i] = true;\n          break;\n        }\n      }\n    }\n    for(size_t i = 0; i < ARRAY_SIZE(data); i++)\n    {\n      // Ignore the files not in the current directory.\n      if(data[i].path[0] == '.')\n        continue;\n      ASSERT(found[i], \"didn't find %s\", data[i].path);\n    }\n    ASSERT(found_dir_real, \"didn't find %s\", dir_real);\n\n    // Should be able to open and step through a virtual dir.\n    ScopedFile<vdir, vdir_close> dir2 = vdir_open(dir_virt);\n    ASSERT(dir2, \"\");\n    while(vdir_read(dir2, buf, MAX_PATH, &type)) {}\n    ASSERTEQ(vdir_tell(dir2), vdir_length(dir2), \"\");\n  }\n\n  SECTION(vvolumelist)\n  {\n    // Straightforward--if there are virtual roots, list them after the real\n    // roots. Do not list cached roots twice.\n    // TODO: there is no function to add roots to the vio VFS yet.\n    SKIP();\n  }\n\n  SECTION(DeleteOpenFile)\n  {\n    // Can't unlink open file (EBUSY).\n    ScopedFile<vfile, vfclose> vf = vfopen_unsafe(file_virt, \"rb\");\n    ASSERT(vf, \"open virtual file\");\n    ret = vunlink(file_virt);\n    ASSERTEQ(ret, -1, \"can't vunlink open virtual file\");\n    ASSERTEQ(errno, EBUSY, \"can't vunlink open virtual file: EBUSY\");\n  }\n\n  SECTION(DeleteRealParent)\n  {\n    // If the real parent is deleted out from under a virtual file or\n    // directory, currently the virtual file will be left in a semi-\n    // accessible state. It's not clear if there's a way to do this better.\n    path_join(buf, MAX_PATH, dir_real, \"testfile\");\n    ret = vio_virtual_file(buf);\n    ASSERT(ret, \"create virtual file in real dir\");\n\n    ret = vrmdir(dir_real);\n    ASSERTEQ(ret, -1, \"vrmdir should fail due to the virtual file\");\n    ASSERTEQ(errno, ENOTEMPTY, \"vrmdir should fail due to the virtual file\");\n\n    ret = rmdir(dir_real);\n    ASSERTEQ(ret, 0, \"use unistd rmdir to delete the real dir\");\n\n    vio_invalidate_all();\n\n    ret = vstat(dir_real, &st);\n    ASSERTEQ(ret, 0, \"real dir stat still works (cached, can't invalidate)\");\n\n    ret = vstat(buf, &st);\n    ASSERTEQ(ret, 0, \"virtual file stat should still work\");\n  }\n}\n\nUNITTEST(CacheRead)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  int ret = vchdir(execdir);\n  ASSERTEQ(ret, 0, \"\");\n\n  vfssetup a(true);\n\n  ScopedFile<vfile, vfclose> vf = vfopen_unsafe(TEST_READ_FILENAME, \"rb\");\n  ASSERT(vf, \"\");\n\n  int flags = vfile_get_flags(vf);\n  ASSERT(flags & VF_FILE, \"\");\n  ASSERT(flags & VF_MEMORY, \"\");\n  ASSERT(flags & VF_VIRTUAL, \"\");\n\n  READ_TESTS(vf);\n}\n\nUNITTEST(CacheWrite)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static constexpr char test_writeback[] = \"test_writeback\";\n  static constexpr char data[] = \"writeback worked!\";\n  static constexpr size_t data_len = sizeof(data) - 1;\n  char buf[MAX_PATH];\n  size_t sz;\n  int ret;\n\n  vfssetup a(true);\n  vunlink(test_writeback);\n\n  ScopedFile<vfile, vfclose> vf = vfopen_unsafe(TEST_WRITE_FILENAME, \"w+b\");\n  ASSERT(vf, \"\");\n\n  int flags = vfile_get_flags(vf);\n  ASSERT(flags & VF_FILE, \"\");\n  ASSERT(flags & VF_MEMORY, \"\");\n  ASSERT(flags & VF_VIRTUAL, \"\");\n\n  WRITE_TESTS(vf, false);\n\n  SECTION(WritebackSeek)\n  {\n    ScopedFile<vfile, vfclose> wb = vfopen_unsafe(test_writeback, \"wb\");\n    ASSERT(wb, \"\");\n    sz = vfwrite(data, 1, data_len, wb);\n    ASSERTEQ(sz, data_len, \"\");\n    ret = vfseek(wb, 8, SEEK_SET); // Should writeback and flush.\n    ASSERTEQ(ret, 0, \"\");\n\n    ScopedFile<FILE, fclose> fp = fopen_unsafe(test_writeback, \"rb\");\n    sz = fread(buf, 1, data_len, fp);\n    ASSERTEQ(sz, data_len, \"\");\n    ASSERTMEM(data, buf, data_len, \"\");\n    ret = fgetc(fp);\n    ASSERTEQ(ret, EOF, \"should be EOF\");\n  }\n\n  SECTION(WritebackRewind)\n  {\n    ScopedFile<vfile, vfclose> wb = vfopen_unsafe(test_writeback, \"wb\");\n    ASSERT(wb, \"\");\n    sz = vfwrite(data, 1, data_len, wb);\n    ASSERTEQ(sz, data_len, \"\");\n    vrewind(wb); // Should writeback and flush.\n\n    ScopedFile<FILE, fclose> fp = fopen_unsafe(test_writeback, \"rb\");\n    sz = fread(buf, 1, data_len, fp);\n    ASSERTEQ(sz, data_len, \"\");\n    ASSERTMEM(data, buf, data_len, \"\");\n    ret = fgetc(fp);\n    ASSERTEQ(ret, EOF, \"should be EOF\");\n  }\n\n  SECTION(WritebackClose)\n  {\n    {\n      ScopedFile<vfile, vfclose> wb = vfopen_unsafe(test_writeback, \"wb\");\n      ASSERT(wb, \"\");\n      sz = vfwrite(data, 1, data_len, wb);\n      ASSERTEQ(sz, data_len, \"\");\n    } // Destructor should invoke vfclose -> writeback and flush.\n\n    ScopedFile<FILE, fclose> fp = fopen_unsafe(test_writeback, \"rb\");\n    sz = fread(buf, 1, data_len, fp);\n    ASSERTEQ(sz, data_len, \"\");\n    ASSERTMEM(data, buf, data_len, \"\");\n    ret = fgetc(fp);\n    ASSERTEQ(ret, EOF, \"should be EOF\");\n  }\n}\n\nstatic void force_cached(const char *filename, const char *message)\n{\n  struct stat st{};\n  int ret = stat(filename, &st);\n  ASSERTEQ(ret, 0, \"%s - %s\", filename, message);\n  ScopedFile<vfile, vfclose> vf = vfopen_unsafe(filename, \"rb\");\n  ASSERT(vf, \"%s - %s\", filename, message);\n  int flags = vfile_get_flags(vf);\n\n  // Only write mode cached files are guaranteed to have FILE handles.\n  //ASSERT(flags & VF_FILE, \"%s - %s\", filename, message);\n\n  ASSERT(flags & VF_MEMORY, \"%s - %s\", filename, message);\n  ASSERT(flags & VF_VIRTUAL, \"%s - %s\", filename, message);\n}\n\nUNITTEST(CacheFilesystem)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  static constexpr char cached_file[] = \"cached_file\";\n  static constexpr char cached_dir[] = \"cached_dir\";\n  static constexpr char cached_dir_renamed[] = \"new_dir_name\";\n  char buf[MAX_PATH];\n  char buf2[MAX_PATH];\n  struct stat st{};\n  size_t sz;\n  int ret;\n\n  path_join(buf, sizeof(buf), cached_dir, cached_file);\n  path_join(buf2, sizeof(buf), cached_dir_renamed, cached_file);\n  vunlink(buf);\n  vunlink(buf2);\n  vunlink(cached_file);\n  vrmdir(cached_dir);\n  vrmdir(cached_dir_renamed);\n\n  vfssetup a(true);\n\n  // The unistd filesystem functions should behave normally if there is a\n  // cached file or directory that they are operating on. This usually works\n  // by recursively purging the internal VFS cache files when detected.\n\n  // vchdir, vgetcwd, vrmdir, dirent are already tested by VirtualFilesystem\n  // as directory caching is required for virtual directories and files.\n  // vmkdir is irrelevant.\n\n  // This should create the test file, close it, and then cache it.\n  {\n    ScopedFile<FILE, fclose> fp = fopen_unsafe(cached_file, \"wb\");\n    ASSERT(fp, \"\");\n    sz = fwrite(test_data, 1, sizeof(test_data), fp);\n    ASSERTEQ(sz, sizeof(test_data), \"\");\n  }\n  force_cached(cached_file, \"force initial cached file\");\n\n  ret = vmkdir(cached_dir, 0755);\n  ASSERTEQ(ret, 0, \"\");\n\n  SECTION(vrename)\n  {\n    static constexpr char new_data[] = \"fdsjdfsdfdssdffdfgdfkg\";\n\n    path_join(buf, MAX_PATH, cached_dir, cached_file);\n    path_join(buf2, MAX_PATH, cached_dir_renamed, cached_file);\n\n    // vrename a cached file--it should relocate correctly.\n    ret = vrename(cached_file, buf);\n    ASSERTEQ(ret, 0, \"successfully renamed cached file\");\n\n    // Make sure it is cached again.\n    force_cached(buf, \"force renamed file cached\");\n\n    // vrename a directory with a cached file--it should relocate correctly\n    ret = vrename(cached_dir, cached_dir_renamed);\n    ASSERTEQ(ret, 0, \"successfully renamed cached dir with cached file\");\n    ret = vstat(buf2, &st);\n    ASSERTEQ(ret, 0, \"successful stat of cached file\");\n\n    // Make sure it is cached again (it should already be, though).\n    force_cached(buf2, \"force renamed file cached (2)\");\n\n    // and opening it after modifying and closing it should reflect the changes.\n    // This relies on working writeback.\n    {\n      ScopedFile<vfile, vfclose> vf = vfopen_unsafe(buf2, \"wb\");\n      ASSERT(vf, \"\");\n      sz = vfwrite(new_data, 1, sizeof(new_data), vf);\n      ASSERTEQ(sz, sizeof(new_data), \"\");\n\n      ret = vrename(buf2, cached_file);\n    }\n\n    // If the filesystem didn't allow the file to be moved, it should be in\n    // the same place.\n    const char *check_path = buf2;\n    // If the filesystem allowed this file to be moved, make sure the file\n    // also moved\n    if(ret == 0)\n      check_path = cached_file;\n\n    ScopedFile<FILE, fclose> fp = fopen_unsafe(check_path, \"rb\");\n    ASSERT(fp, \"checking at %s - vrename returned %d\", check_path, ret);\n    sz = fread(buf, 1, sizeof(new_data), fp);\n    ASSERTEQ(sz, sizeof(new_data),\n     \"checking at %s - vrename returned %d\", check_path, ret);\n    ASSERTMEM(buf, new_data, sizeof(new_data),\n     \"checking at %s - vrename returned %d\", check_path, ret);\n  }\n\n  SECTION(vunlink)\n  {\n    // This should remove the file from the real filesystem and from the cache.\n    ret = vunlink(cached_file);\n    ASSERTEQ(ret, 0, \"remove cached file\");\n    ret = stat(cached_file, &st);\n    ASSERTEQ(ret, -1, \"cached file stat should fail\");\n    ASSERTEQ(errno, ENOENT, \"cached file stat errno should be ENOENT\");\n  }\n\n  SECTION(vaccess)\n  {\n    // This should still work on cached files.\n    ret = vaccess(cached_file, R_OK|W_OK);\n    ASSERTEQ(ret, 0, \"vaccess should work on a cached file\");\n  }\n\n  SECTION(vstat)\n  {\n    // This should work on cached files, and currently returns a limited amount\n    // of cached information instead of real info.\n    ret = vstat(cached_file, &st);\n    ASSERTEQ(ret, 0, \"vstat should work on a cached file\");\n    ASSERTEQ(st.st_dev, VFS_MZX_DEVICE, \"cached vstat should have virtual device\");\n    ASSERTEQ(st.st_size, sizeof(test_data), \"cached vstat should have correct size\");\n  }\n\n  SECTION(VirtualCreateOverCached)\n  {\n    // Making a virtual file over a cached file should drop the cached\n    // file in favor of a virtual file, same as an uncached file.\n    ret = vio_virtual_file(cached_file);\n    ASSERT(ret, \"make virtual file over real (cached) file\");\n\n    ScopedFile<vfile, vfclose> vf = vfopen_unsafe(cached_file, \"rb\");\n    ASSERT(vf, \"open virtual file for read\");\n    int64_t len = vfilelength(vf, false);\n    ASSERTEQ(len, 0, \"length of virtual file should be zero\");\n  }\n\n  SECTION(VirtualRenameOverCached)\n  {\n    // Moving a virtual file over a cached file should drop the cached\n    // file in favor of a virtual file, same as an uncached file.\n    static constexpr char old_file[] = \"dsdjfkdsl\";\n    ret = vio_virtual_file(old_file);\n    ASSERT(ret, \"make virtual file\");\n    ret = vrename(old_file, cached_file);\n    ASSERTEQ(ret, 0, \"rename virtual file over real (cached) file\");\n\n    ScopedFile<vfile, vfclose> vf = vfopen_unsafe(cached_file, \"rb\");\n    ASSERT(vf, \"open virtual file for read\");\n    int64_t len = vfilelength(vf, false);\n    ASSERTEQ(len, 0, \"length of virtual file should be zero\");\n  }\n}\n\nstatic void generate_cached_file(const char *path, const void *data, size_t len,\n int flags = 0)\n{\n  // Generate the file using regular filesystem commands and then cache it.\n  // This shouldn't be done with vio since the file will be created at 0\n  // length, and the invalidation check occurs at file creation.\n  {\n    ScopedFile<FILE, fclose> fp = fopen_unsafe(path, \"wb\");\n    ASSERT(fp, \"create: %s\", path);\n    size_t count = fwrite(data, 1, len, fp);\n    ASSERTEQ(count, len, \"create: %s\", path);\n  }\n\n  // Now *try* to cache the file.\n  struct stat st{};\n  int ret = stat(path, &st);\n  ASSERTEQ(ret, 0, \"%s\", path);\n  ScopedFile<vfile, vfclose> vf = vfopen_unsafe_ext(path, \"rb\", flags);\n  ASSERT(vf, \"%s\", path);\n\n  flags = vfile_get_flags(vf);\n\n  // Not guaranteed to have a FILE unless it is in write mode.\n  if(flags & VF_WRITE)\n    ASSERT(flags & VF_FILE, \"%s\", path);\n\n  // In this case, the file should NEVER be cached.\n  // Takes precedence over V_FORCE_CACHE.\n  if(flags & V_DONT_CACHE)\n  {\n    ASSERT(~flags & VF_MEMORY, \"%s\", path);\n    ASSERT(~flags & VF_VIRTUAL, \"%s\", path);\n  }\n  else\n\n  // In this case, the file should ALWAYS be cached.\n  if(flags & V_FORCE_CACHE)\n  {\n    ASSERT(flags & VF_MEMORY, \"%s\", path);\n    ASSERT(flags & VF_VIRTUAL, \"%s\", path);\n  }\n}\n\nUNITTEST(CacheMemoryLimit)\n{\n  // vio will enforce a maximum cache size limit in most cases.\n  static constexpr size_t file_size = cache_max_size * 2 / 5;\n  static constexpr size_t file_oversize = cache_max_size * 3 / 5;\n  static constexpr size_t file_overmax = cache_max_size * 4;\n  static constexpr char filename1[] = \"cache_me1\";\n  static constexpr char filename2[] = \"cache_me2\";\n  static constexpr char filename3[] = \"cache_me3\";\n  static constexpr char data[file_overmax]{};\n  size_t single_usage;\n  size_t total;\n\n  vfssetup a(true);\n\n  total = vio_filesystem_total_cached_usage();\n  ASSERTEQ(total, 0, \"should be empty initially\");\n\n  vunlink(filename1);\n  vunlink(filename2);\n  vunlink(filename3);\n\n  SECTION(Multiple)\n  {\n    // Caching files over the maximum size limit should eject old files.\n    generate_cached_file(filename1, data, file_size);\n    single_usage = vio_filesystem_total_cached_usage();\n\n    // Should use twice as much memory as the first file.\n    // Note that the allocated memory might be bigger than the file's size,\n    // but it SHOULD be consistent for each file.\n    generate_cached_file(filename2, data, file_size);\n    total = vio_filesystem_total_cached_usage();\n    ASSERTEQ(total, single_usage * 2, \"\");\n\n    // Should invalidate one of the previous files, so the size is\n    // the exact same.\n    generate_cached_file(filename3, data, file_size);\n    total = vio_filesystem_total_cached_usage();\n    ASSERTEQ(total, single_usage * 2, \"\");\n  }\n\n  SECTION(Oversize)\n  {\n    // Oversize files should be rejected from the cache.\n    generate_cached_file(filename1, data, file_size);\n    single_usage = vio_filesystem_total_cached_usage();\n\n    generate_cached_file(filename1, data, file_oversize);\n    total = vio_filesystem_total_cached_usage();\n    ASSERTEQ(total, single_usage, \"oversize file should be rejected\");\n  }\n\n  SECTION(DontCache)\n  {\n    // The vio flag V_DONT_CACHE can be used to prevent files from being\n    // added to the cache by the current operation.\n    generate_cached_file(filename1, data, file_size);\n    single_usage = vio_filesystem_total_cached_usage();\n\n    generate_cached_file(filename2, data, file_size, V_DONT_CACHE);\n    total = vio_filesystem_total_cached_usage();\n    ASSERTEQ(single_usage, total, \"V_DONT_CACHE\");\n\n    generate_cached_file(filename3, data, file_size, V_DONT_CACHE|V_FORCE_CACHE);\n    total = vio_filesystem_total_cached_usage();\n    ASSERTEQ(single_usage, total, \"V_DONT_CACHE precedence over V_FORCE_CACHE\");\n  }\n\n  SECTION(ForceCache)\n  {\n    // The vio flag V_FORCE_CACHE can be used to cache oversize files\n    // and cache files in cases where it is impossible to free enough space.\n    generate_cached_file(filename1, data, file_size);\n    single_usage = vio_filesystem_total_cached_usage();\n\n    generate_cached_file(filename2, data, file_oversize, V_FORCE_CACHE);\n    size_t second = vio_filesystem_total_cached_usage();\n    ASSERT(second > single_usage, \"V_FORCE_CACHE overrides file size limit\");\n\n    generate_cached_file(filename3, data, file_overmax, V_FORCE_CACHE);\n    size_t third = vio_filesystem_total_cached_usage();\n    ASSERT(third > cache_max_size,\n     \"V_FORCE_CACHE overrides total size limit (%zu > %zu)\",\n     third, cache_max_size);\n  }\n}\n\nUNITTEST(vfile_force_to_memory)\n{\n#ifndef VIRTUAL_FILESYSTEM\n  SKIP();\n#endif\n\n  int ret;\n\n  // Safety--make sure this really is disabled.\n  vio_filesystem_exit();\n\n  ret = vchdir(execdir);\n  ASSERTEQ(ret, 0, \"\");\n\n  const auto &common_test = [](const char *mode, boolean expected_ret)\n  {\n    ScopedFile<vfile, vfclose> vf = vfopen_unsafe(TEST_READ_FILENAME, mode);\n    ASSERT(vf, \"\");\n\n    int ret = vfile_force_to_memory(vf);\n    ASSERTEQ(ret, expected_ret, \"\");\n\n    if(ret)\n    {\n      int flags = vfile_get_flags(vf);\n      ASSERT(flags & VF_MEMORY, \"\");\n      ASSERT(~flags & VF_FILE, \"\");\n\n      uint8_t buf[sizeof(test_data)];\n      size_t sz = vfread(buf, 1, sizeof(test_data), vf);\n      ASSERTEQ(sz, sizeof(test_data), \"\");\n      ASSERTMEM(buf, test_data, sizeof(test_data), \"\");\n\n      // This should always work because it's already in memory!\n      ret = vfile_force_to_memory(vf);\n      ASSERT(ret, \"\");\n    }\n  };\n\n  SECTION(NoVFS)\n  {\n    // File won't be in memory -> is converted to a memory tempfile.\n    common_test(\"rb\", true);\n  }\n\n  SECTION(VFS)\n  {\n    // File will already be in memory via the cache.\n    vfssetup a(true);\n    common_test(\"rb\", true);\n  }\n\n  SECTION(Write)\n  {\n    // This function should reject writes either way.\n    common_test(\"r+b\", false);\n\n    vfssetup a(true);\n    common_test(\"r+b\", false);\n  }\n}\n"
  },
  {
    "path": "unit/io/zip.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2026 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../Unit.hpp\"\n#include \"../UnitIO.hpp\"\n\n#include \"../../src/util.h\"\n#include \"../../src/io/memfile.h\"\n#include \"../../src/io/path.h\"\n#include \"../../src/io/zip.h\"\n#include \"../../src/io/zip_stream.h\"\n\nstatic const size_t BUFFER_SIZE = (1 << 17);\nstatic const char DATA_DIR[] = \"../data\";\n\nstruct pair\n{\n  /* const would be nice on these, but it breaks in GCC 4.8 */\n  const char *data;\n  size_t len;\n  uint16_t flags;\n  std::vector<uint8_t> external;\n};\n\nstruct zip_stream_test_data\n{\n  const char *testname; // For debug output.\n  pair expected;\n  pair deflate;\n  pair deflate64;\n  pair shrink;\n  pair reduce1;\n  pair reduce2;\n  pair reduce3;\n  pair reduce4;\n  pair implode;\n  boolean inputs_are_paths;\n  boolean expected_is_base64;\n  boolean compressed_is_base64;\n};\n\nstatic zip_stream_test_data data[] =\n{\n  // WHITE.PAL from Fred the Freak Gaiden was originally shrunk.\n  {\n    \"WHITE.PAL\",\n    { \"????????????????????????????????????????????????\", 48, 0, {} },\n    { \"sycRAAA=\", 5, 0, {} },\n    { \"sycRAAA=\", 5, 0, {} },\n    { \"PwIKHEiwoMGDCAUC\", 12, 0, {} },\n    {},\n    {},\n    {},\n    {},\n    { \"DwASAyQVNic4OWp7TJ1uHwkGARM05faW93/+CAZgAw==\", 31, 0, {} },\n    false,\n    false,\n    true\n  },\n  // FRED_01.PAL from Fred the Freak Gaiden was originally shrunk.\n  {\n    \"FRED_01.PAL\",\n    { \"AAAAAAAqACoAGz8bBz8HAjQCKhUAKioqFRUVFRU/FT8VFT8/PxUVPxU/MzMJPz8/\", 48, 0, {} },\n    { \"YwABLSCUtpdmt2dnMmHSEmXQ0tISBQF7IBS1t7cHs4yNOYFMAA==\", 37, 0, {} },\n    { \"YwABLSCUtpdmt2dnMmHSEmXQ0tISBQF7IBS1t7cHs4yNOYFMAA==\", 37, 0, {} },\n    { \"AAIGVAGA4IYfGw78OCCAhgAVFQqqgFihYoUfFyv+2Kjx4owZCTb+AA==\", 40, 0, {} },\n    {},\n    {},\n    {},\n    {},\n    {},\n    false,\n    true,\n    true\n  },\n  // FRED_02.PAL from Fred the Freak Gaiden was originally shrunk.\n  {\n    \"FRED_02.PAL\",\n    { \"AAAAAAAqACoAACoqKgAAKgAqBwcHDw8PCQkJCgoKFT8VFT8/FSoVKhUAPz8VPz8/\", 48, 0, {} },\n    { \"HYaxEQBACMLsPG2oWIIl2H+r502KpD6KJemuuwHMzO7SpE3FSm0/\", 39, 0, {} },\n    { \"HYaxEQBACMLsPG2oWIIl2H+r502KpD6KJemuuwHMzO7SpE3FSm0/\", 39, 0, {} },\n    { \"AAIGVAGAYEEVBgmqOMDwgcMEEBVIrPCjAsWKKipkBPCjYscf\", 36, 0, {} },\n    {},\n    {},\n    {},\n    {},\n    {},\n    false,\n    true,\n    true\n  },\n  // CN_S.CHR from Dark Nova was originally imploded.\n  {\n    \"CN_S.CHR\",\n    { \"CN_S.CHR\",           3584, 0, {} },\n    { \"CN_S.CHR.deflate\",   1618, 0, {} },\n    { \"CN_S.CHR.deflate\",   1618, 0, {} }, // Result is same as above.\n    { \"CN_S.CHR.shrink\",    1906, 0, {} },\n    { \"CN_S.CHR.reduce1\",   2123, 0, {} },\n    { \"CN_S.CHR.reduce2\",   2134, 0, {} },\n    { \"CN_S.CHR.reduce3\",   2147, 0, {} },\n    { \"CN_S.CHR.reduce4\",   2138, 0, {} },\n    { \"CN_S.CHR.implode\",   1793, 0x0000, {} },\n    true,\n    false,\n    false\n  },\n  // FREAKSOF.MZX from Freaks Collection was originally imploded.\n  {\n    \"FREAKSOF.MZX\",\n    { \"FREAKSOF.MZX\",       16525, 0, {} },\n    { \"freaksof.deflate\",   6132, 0, {} },\n    { \"freaksof.deflate64\", 6137, 0, {} },\n    { \"freaksof.shrink\",    9425, 0, {} },\n    { \"freaksof.reduce1\",   7600, 0, {} },\n    { \"freaksof.reduce2\",   7417, 0, {} },\n    { \"freaksof.reduce3\",   7386, 0, {} },\n    { \"freaksof.reduce4\",   7367, 0, {} },\n    { \"freaksof.implode\",   6586, 0x0000, {} },\n    true,\n    false,\n    false\n  },\n  {\n    \"dch1.txt\",\n    { \"dch1.txt\",           7045, 0, {} },\n    { \"dch1.deflate\",       3385, 0, {} },\n    { \"dch1.deflate64\",     3352, 0, {} },\n    { \"dch1.shrink\",        3980, 0, {} },\n    { \"dch1.reduce1\",       4549, 0, {} },\n    { \"dch1.reduce2\",       4505, 0, {} },\n    { \"dch1.reduce3\",       4459, 0, {} },\n    { \"dch1.reduce4\",       4411, 0, {} },\n    { \"dch1.implode\",       3666, 0x0006, {} },\n    true,\n    false,\n    false\n  },\n  {\n    \"CT_LEVEL.MOD\",\n    { \"CT_LEVEL.MOD\",       111885, 0, {} },\n    { \"ct_level.deflate\",   61105, 0, {} },\n    { \"ct_level.deflate64\", 61051, 0, {} },\n    { \"ct_level.shrink\",    80628, 0, {} },\n    { \"ct_level.reduce1\",   67216, 0, {} },\n    { \"ct_level.reduce2\",   66109, 0, {} },\n    { \"ct_level.reduce3\",   66077, 0, {} },\n    { \"ct_level.reduce4\",   65837, 0, {} },\n    { \"ct_level.implode\",   66879, 0x0000, {} },\n    true,\n    false,\n    false\n  },\n};\n\nstatic void debase64(std::vector<uint8_t> &dest, const void *_src, size_t src_len)\n{\n  static const char lut[256] =\n  {\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62,0, 0, 0, 63,\n    52,53,54,55,56,57,58,59,60,61,0, 0, 0, 0, 0, 0,\n    0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12,13,14,\n    15,16,17,18,19,20,21,22,23,24,25,0, 0, 0, 0, 0,\n    0, 26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,\n    41,42,43,44,45,46,47,48,49,50,51,0, 0, 0, 0, 0,\n  };\n  const uint8_t *src = (const uint8_t *)_src;\n  size_t i;\n\n  assert((src_len & 3) == 0);\n\n  dest.reserve(src_len * 3 / 4);\n  dest.resize(0);\n\n  for(i = 0; i < src_len; i += 4)\n  {\n    uint32_t x = lut[src[i+3]] | (lut[src[i+2]] << 6) |\n     (lut[src[i+1]] << 12) | (lut[src[i]] << 18);\n\n    dest.emplace_back((x >> 16) & 0xFF);\n    dest.emplace_back((x >> 8) & 0xFF);\n    dest.emplace_back(x & 0xFF);\n  }\n\n  if(src[src_len - 1] == '=')\n    dest.pop_back();\n  if(src[src_len - 2] == '=')\n    dest.pop_back();\n}\n\nstatic const uint8_t *load_file(std::vector<uint8_t> &buffer, const char *path)\n{\n  char path_buffer[MAX_PATH];\n  if(path_join(path_buffer, sizeof(path_buffer), DATA_DIR, path) < 1)\n    return nullptr;\n\n  unit::io::load_vector(buffer, path_buffer);\n  return buffer.data();\n}\n\nstatic const uint8_t *load_file(pair &pair)\n{\n  return load_file(pair.external, pair.data);\n}\n\nstatic void check_data(zip_stream_test_data &data)\n{\n  if(data.inputs_are_paths)\n  {\n    load_file(data.expected);\n    load_file(data.deflate);\n    load_file(data.deflate64);\n    load_file(data.shrink);\n    load_file(data.reduce1);\n    load_file(data.reduce2);\n    load_file(data.reduce3);\n    load_file(data.reduce4);\n    load_file(data.implode);\n    data.expected_is_base64 = false;\n    data.compressed_is_base64 = false;\n  }\n}\n\nstatic void check_data()\n{\n  static boolean loaded = false;\n  if(!loaded)\n  {\n    loaded = true;\n    for(zip_stream_test_data &d : data)\n      check_data(d);\n  }\n}\n\nstatic const zip_method_handler *get_stream(zip_compression_method method)\n{\n  assert(method <= arraysize(zip_method_handlers));\n  return zip_method_handlers[method];\n}\n\nstatic enum zip_error compress(const zip_method_handler *stream,\n zip_stream_data *sd, zip_compression_method method, uint16_t flags,\n const uint8_t *in, size_t in_len, std::vector<uint8_t> &out, size_t *final_out)\n{\n  enum zip_error result;\n\n  ASSERT(in, \"\");\n  ASSERT(final_out, \"\");\n  ASSERT(stream->compress_open, \"\");\n  ASSERT(stream->compress_block, \"\");\n  ASSERT(stream->input, \"\");\n  ASSERT(stream->output, \"\");\n  ASSERT(stream->close, \"\");\n\n  stream->compress_open(sd, method, flags);\n  stream->input(sd, in, in_len);\n  stream->output(sd, out.data(), out.size());\n\n  result = stream->compress_block(sd, true);\n\n  stream->close(sd, nullptr, final_out);\n  return result;\n}\n\nstatic enum zip_error decompress(const zip_method_handler *stream,\n zip_stream_data *sd, zip_compression_method method, uint16_t flags,\n const uint8_t *in, size_t in_len, std::vector<uint8_t> &out)\n{\n  enum zip_error result;\n\n  ASSERT(in, \"\");\n  ASSERT(stream->decompress_open, \"\");\n  ASSERT(stream->decompress_block || stream->decompress_file, \"\");\n  ASSERT(stream->input, \"\");\n  ASSERT(stream->output, \"\");\n  ASSERT(stream->close, \"\");\n\n  stream->decompress_open(sd, method, flags);\n  stream->input(sd, in, in_len);\n  stream->output(sd, out.data(), out.size());\n\n  if(stream->decompress_block)\n    result = stream->decompress_block(sd);\n  else\n    result = stream->decompress_file(sd);\n\n  size_t final_len = 0;\n  stream->close(sd, nullptr, &final_len);\n/*\n  char buf[32];\n  snprintf(buf, 32, \"%d.in\", method);\n  FILE *fp = fopen_unsafe(buf, \"wb\");\n  fwrite(in, in_len, 1, fp);\n  fclose(fp);\n  snprintf(buf, 32, \"%d.out\", method);\n  fp = fopen_unsafe(buf, \"wb\");\n  fwrite(out, final_len, 1, fp);\n  fclose(fp);\n*/\n  return result;\n}\n\nstatic void decompress_set(const uint8_t *&x, size_t &x_len, std::vector<uint8_t> &v,\n const pair &p, boolean is_base64, const zip_stream_test_data &d)\n{\n  if(is_base64)\n  {\n    debase64(v, p.data, strlen(p.data));\n  }\n  else\n\n  if(d.inputs_are_paths)\n  {\n    ASSERT(p.external.size(), \"%s: external data is blank\", d.testname);\n    v = p.external;\n  }\n  else\n  {\n    v.resize(p.len);\n    memcpy(v.data(), p.data, p.len);\n  }\n  x = v.data();\n  x_len = v.size();\n}\n\n#define SET_A(p,is_b64) \\\n  ASSERT((p).data, \"%s: data is null\", d.testname); \\\n  decompress_set(a, a_len, buffer_a, (p), (is_b64), d)\n\n#define SET_B(p,is_b64) \\\n  if(!(p).data) \\\n    continue; \\\n  flags = (p).flags; \\\n  decompress_set(b, b_len, buffer_b, (p), (is_b64), d)\n\n/**\n * Tests:\n * 1) a valid input should decompress to the exact expected output.\n * 2) a truncated input should generally fail (file) or return ZIP_INPUT_EMPTY (block).\n * 3) corrupted inputs (simulated by copying parts of the expected output over\n *    parts of the input) should just plain fail or produce a different output.\n */\ntemplate<enum zip_compression_method method>\nstatic void decompress_boilerplate(pair zip_stream_test_data::*field)\n{\n  const uint8_t *a;\n  const uint8_t *b;\n  size_t a_len;\n  size_t b_len;\n  uint16_t flags;\n  enum zip_error result;\n  int cmp;\n\n  const zip_method_handler *stream = get_stream(method);\n  if(!stream)\n  {\n    if(method == ZIP_M_DEFLATE)\n      FAIL(\"failed to get deflate stream!\");\n    else\n      SKIP();\n  }\n\n  ASSERT(stream->create, \"\");\n  ASSERT(stream->destroy, \"\");\n\n  struct zip_stream_data *sd = stream->create();\n\n  std::vector<uint8_t> buffer(BUFFER_SIZE);\n  std::vector<uint8_t> buffer_a(BUFFER_SIZE);\n  std::vector<uint8_t> buffer_b(BUFFER_SIZE);\n\n  for(const zip_stream_test_data &d : data)\n  {\n    const pair &input = d.*field;\n\n    SET_A(d.expected, d.expected_is_base64);\n    SET_B(input, d.compressed_is_base64);\n    result = decompress(stream, sd, method, flags, b, b_len, buffer);\n    cmp = memcmp(a, buffer.data(), a_len);\n    ASSERTEQ(cmp, 0, \"%s: valid\", d.testname);\n\n    for(int j = 7; j >= 0; j--)\n    {\n      std::fill(buffer.begin(), buffer.end(), 0);\n\n      result = decompress(stream, sd, method, flags, b, b_len * j / 8, buffer);\n      ASSERT(result != ZIP_OUTPUT_FULL && result != ZIP_EOF,\n       \"%s: truncated %d/8\", d.testname, j);\n\n      if(result == ZIP_STREAM_FINISHED)\n      {\n        cmp = !memcmp(a, buffer.data(), a_len);\n        ASSERTEQ(cmp, 0, \"%s: truncated %d/8\", d.testname, j);\n      }\n    }\n\n    for(int j = 0; j < 8; j++)\n    {\n      std::fill(buffer.begin(), buffer.end(), 0);\n\n      SET_B(input, d.compressed_is_base64);\n      size_t size = MAX(b_len / 8, 1);\n      if(size > a_len)\n        continue;\n\n      size_t a_pos = MIN(a_len - size, j * size);\n      size_t b_pos = MIN(b_len - size, j * size);\n      if(!memcmp(b + b_pos, a + a_pos, size))\n        continue;\n\n      memcpy(buffer_b.data() + b_pos, a + a_pos, size);\n      result = decompress(stream, sd, method, flags, b, b_len, buffer);\n      ASSERT(result != ZIP_EOF, \"%s: corrupt %d\", d.testname, j);\n\n      if(result == ZIP_STREAM_FINISHED)\n      {\n        cmp = !memcmp(a, buffer.data(), a_len);\n        ASSERTEQ(cmp, 0, \"%s: corrupt %d\", d.testname, j);\n      }\n    }\n  }\n  stream->destroy(sd);\n}\n\nUNITTEST(Decompress)\n{\n  check_data();\n\n  SECTION(Inflate)\n    decompress_boilerplate<ZIP_M_DEFLATE>(&zip_stream_test_data::deflate);\n\n  SECTION(Inflate64)\n    decompress_boilerplate<ZIP_M_DEFLATE64>(&zip_stream_test_data::deflate64);\n\n  SECTION(Unshrink)\n    decompress_boilerplate<ZIP_M_SHRUNK>(&zip_stream_test_data::shrink);\n\n  SECTION(Expand1)\n    decompress_boilerplate<ZIP_M_REDUCED_1>(&zip_stream_test_data::reduce1);\n\n  SECTION(Expand2)\n    decompress_boilerplate<ZIP_M_REDUCED_2>(&zip_stream_test_data::reduce2);\n\n  SECTION(Expand3)\n    decompress_boilerplate<ZIP_M_REDUCED_3>(&zip_stream_test_data::reduce3);\n\n  SECTION(Expand4)\n    decompress_boilerplate<ZIP_M_REDUCED_4>(&zip_stream_test_data::reduce4);\n\n  SECTION(Explode)\n    decompress_boilerplate<ZIP_M_IMPLODED>(&zip_stream_test_data::implode);\n}\n\n/**\n * Compress each of the raw test inputs, then decompress it and check to\n * make sure it's the same.\n */\nUNITTEST(Compress)\n{\n  std::vector<uint8_t> buffer_a(BUFFER_SIZE);\n  std::vector<uint8_t> buffer_cmp(BUFFER_SIZE);\n  std::vector<uint8_t> buffer_dcmp(BUFFER_SIZE);\n  const uint8_t *a;\n  size_t a_len;\n  size_t cmp_len;\n  enum zip_error result;\n\n  const zip_method_handler *stream = get_stream(ZIP_M_DEFLATE);\n  if(!stream)\n    FAIL(\"Failed to get deflate stream!\");\n\n  ASSERT(stream->create, \"\");\n  ASSERT(stream->destroy, \"\");\n\n  check_data();\n\n  struct zip_stream_data *sd = stream->create();\n  ASSERT(sd, \"\");\n\n  for(const zip_stream_test_data &d : data)\n  {\n    SET_A(d.expected, d.expected_is_base64);\n\n    result = compress(stream, sd, ZIP_M_DEFLATE, 0, a, a_len,\n     buffer_cmp, &cmp_len);\n    ASSERTEQ(result, ZIP_STREAM_FINISHED, \"%s\", d.testname);\n\n    result = decompress(stream, sd, ZIP_M_DEFLATE, 0,\n     buffer_cmp.data(), cmp_len, buffer_dcmp);\n    ASSERTEQ(result, ZIP_STREAM_FINISHED, \"%s\", d.testname);\n\n    ASSERTMEM(a, buffer_dcmp.data(), a_len, \"%s\", d.testname);\n  }\n  stream->destroy(sd);\n}\n\nstatic const char long_content[] =\n{\n  \"1234567890 1234567890 1234567890\\r\\n\"\n  \"1234567890 1234567890 1234567890\\r\\n\"\n  \"1234567890 1234567890 1234567890\"\n};\nstatic constexpr uint32_t long_len = sizeof(long_content) - 1;\nstatic constexpr uint32_t long_crc = 0xE3046765;\n\nenum contents_type\n{\n  CONTENTS_RAW,\n  CONTENTS_BASE64,\n  CONTENTS_FILE,\n};\n\nstruct zip_test_file_data\n{\n  const char *filename;\n  const char *contents;\n  uint16_t flags;\n  uint16_t method;\n  uint32_t crc32;\n  uint32_t uncompressed_size;\n  uint32_t compressed_size;\n  uint32_t offset;\n  enum contents_type contents_type;\n};\n\nstruct zip_test_data\n{\n  const char *testname; // For debug output.\n  const char *data;\n  size_t data_length;\n  enum zip_error open_result;\n  uint32_t num_files;\n  zip_test_file_data files[16];\n\n  static constexpr zip_test_data long_content_from_file(const char *filename,\n   const char *internal_filename, uint16_t flags)\n  {\n    return zip_test_data{ filename, filename,\n        0, ZIP_SUCCESS, 1,\n        {{ internal_filename, long_content,\n          flags, ZIP_M_NONE, long_crc, long_len, long_len, 0, CONTENTS_RAW }}};\n  }\n};\n\nstatic const zip_test_data raw_zip_data[] =\n{\n  {\n    \"EOCD only\",\n    \"PK\\x05\\x06\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\",\n    22, ZIP_SUCCESS, 0, {}\n  },\n  {\n    \"EOCD with prefix\",\n    \"prefixed data here!\"\n    \"PK\\x05\\x06\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\",\n    41, ZIP_SUCCESS, 0, {}\n  },\n  {\n    \"abcde.zip\",\n    \"abcde.zip\", 0, ZIP_SUCCESS, 3,\n    {\n      { \"abcde/\", \"\",\n        0x0000, ZIP_M_NONE, 0x00000000, 0, 0, 0, CONTENTS_RAW },\n      { \"abcde/a.txt\", \"abcde\",\n        0x0000, ZIP_M_NONE, 0x8587D865, 5, 5, 36, CONTENTS_RAW },\n      { \"abcde/b.txt\", long_content,\n        0x0000, ZIP_M_DEFLATE, long_crc, 100, 32, 82, CONTENTS_RAW },\n    }\n  },\n  {\n    \"dch1.zip\",\n    \"dch1.zip\", 0, ZIP_SUCCESS, 1,\n    {{\n      \"dch1.txt\", \"dch1.txt\",\n      0x0000, ZIP_M_DEFLATE, 0xA3898FBE, 7045, 3385, 0, CONTENTS_FILE }},\n  },\n  {\n    \"ct_level.zip\",\n    \"ct_level.zip\", 0, ZIP_SUCCESS, 1,\n    {{\n      \"CT_LEVEL.MOD\", \"CT_LEVEL.MOD\",\n      0x0000, ZIP_M_DEFLATE, 0x2AF73EBE, 111885, 61105, 0, CONTENTS_FILE }},\n  },\n  /* Zip64 */\n  {\n    \"EOCD (Zip64)\",\n    \"PK\\x06\\x06\\x2c\\0\\0\\0\\0\\0\\0\\0\\x2d\\0\\x2d\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n      \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n    \"PK\\x06\\x07\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x01\\0\\0\\0\"\n    \"PK\\x05\\x06\\x00\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\0\\0\",\n    98, ZIP_SUCCESS, 0, {}\n  },\n  {\n    \"dch1.zip64\",\n    \"dch1.zip64\", 0, ZIP_SUCCESS, 1,\n    {{ \"-\", \"dch1.txt\",\n      0x0002, ZIP_M_DEFLATE, 0xA3898FBE, 7045, 3435, 0, CONTENTS_FILE }},\n  },\n  /* Zip64 format variants which all contain the uncompressed long text. */\n  zip_test_data::long_content_from_file(\"zip64/local64.zip\", \"test\", 0x0000),\n  zip_test_data::long_content_from_file(\"zip64/localdd64.zip\", \"test\", ZIP_F_DATA_DESCRIPTOR),\n  zip_test_data::long_content_from_file(\"zip64/localcd64.zip\", \"test\", 0x0000),\n  zip_test_data::long_content_from_file(\"zip64/localcddd64.zip\", \"test\", ZIP_F_DATA_DESCRIPTOR),\n  zip_test_data::long_content_from_file(\"zip64/cd64.zip\", \"test\", 0x0000),\n  zip_test_data::long_content_from_file(\"zip64/cduncomp64.zip\", \"test\", 0x0000),\n  zip_test_data::long_content_from_file(\"zip64/cdcomp64.zip\", \"test\", 0x0000),\n  zip_test_data::long_content_from_file(\"zip64/cdoffset64.zip\", \"test\", 0x0000),\n  zip_test_data::long_content_from_file(\"zip64/eocd64.zip\", \"test\", 0x0000),\n  zip_test_data::long_content_from_file(\"zip64/eocd64rc.zip\", \"test\", 0x0000),\n  zip_test_data::long_content_from_file(\"zip64/eocd64sz.zip\", \"test\", 0x0000),\n  zip_test_data::long_content_from_file(\"zip64/eocd64of.zip\", \"test\", 0x0000),\n  zip_test_data::long_content_from_file(\"zip64/all64.zip\", \"test\", 0x0000),\n  zip_test_data::long_content_from_file(\"zip64/alldd64.zip\", \"test\", ZIP_F_DATA_DESCRIPTOR),\n};\n\n// The library should refuse to open these...\nstatic const zip_test_data raw_zip_invalid_data[] =\n{\n  {\n    \"empty file\",\n    \"\", 0, ZIP_NO_EOCD, 0, {}\n  },\n  {\n    \"EOCD truncated to 4 with prefix of 18\",\n    \"                  PK\\x05\\x06\", 22, ZIP_NO_EOCD, 0, {}\n  },\n  {\n    \"EOCD truncated to 21\",\n    \"PK\\x05\\x06\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\",\n    21, ZIP_NO_EOCD, 0, {}\n  },\n  {\n    \"Missing central directory\",\n    \"PK\\x05\\x06\\x00\\x00\\x00\\x00\\x01\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\",\n    22, ZIP_NO_CENTRAL_DIRECTORY, 0, {}\n  },\n  {\n    \"Multiple disks\",\n    \"PK\\x05\\x06\\x01\\x00\\x00\\x00\\x01\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\",\n    22, ZIP_UNSUPPORTED_MULTIPLE_DISKS, 0, {}\n  },\n};\n\nstatic zip_archive *zip_test_open(const char *filename)\n{\n  char buffer[MAX_PATH];\n  if(path_join(buffer, sizeof(buffer), DATA_DIR, filename) < 0)\n    return nullptr;\n\n  return zip_open_file_read(buffer);\n}\n\nstatic zip_archive *zip_test_open(const zip_test_data &d)\n{\n  if(!d.data_length)\n  {\n    char buffer[MAX_PATH];\n    if(path_join(buffer, sizeof(buffer), DATA_DIR, d.data) < 0)\n      return nullptr;\n\n    return zip_open_file_read(buffer);\n  }\n  else\n    return zip_open_mem_read((const void *)d.data, d.data_length);\n}\n\nstatic zip_archive *zip_test_open(const zip_test_data &d,\n std::vector<uint8_t> &buffer)\n{\n  if(!d.data_length)\n  {\n    if(!load_file(buffer, d.data))\n      return nullptr;\n\n    return zip_open_mem_read(buffer.data(), buffer.size());\n  }\n  else\n    return zip_open_mem_read((const void *)d.data, d.data_length);\n}\n\nstatic const uint8_t *zip_get_contents(const zip_test_file_data &df,\n std::vector<uint8_t> &buffer)\n{\n  size_t len = df.contents ? strlen(df.contents) : 0;\n  if(len)\n  {\n    switch(df.contents_type)\n    {\n      case CONTENTS_RAW:\n        return reinterpret_cast<const uint8_t *>(df.contents);\n\n      case CONTENTS_BASE64:\n        debase64(buffer, df.contents, len);\n        return buffer.data();\n\n      case CONTENTS_FILE:\n        if(load_file(buffer, df.contents))\n        {\n          assert(df.uncompressed_size == buffer.size());\n          return buffer.data();\n        }\n        break;\n    }\n  }\n  static const uint8_t nil[1] = { 0 };\n  return nil;\n}\n\n// Check that the zip exists and that its central directory headers match the\n// expected data.\nstatic void zip_check(const zip_test_data &d, struct zip_archive *zp)\n{\n  ASSERT(zp, \"%s\", d.testname);\n  ASSERTEQ(zp->num_files, d.num_files, \"%s\", d.testname);\n  if(d.num_files)\n    ASSERT(zp->files, \"%s\", d.testname);\n\n  for(size_t j = 0; j < d.num_files; j++)\n  {\n    const zip_test_file_data &df = d.files[j];\n    zip_file_header *fh = zp->files[j];\n\n    ASSERT(fh, \"%s file %zu\", d.testname, j);\n    ASSERTEQ(fh->flags, df.flags, \"%s file %zu\", d.testname, j);\n    ASSERTEQ(fh->method, df.method, \"%s file %zu\", d.testname, j);\n    ASSERTEQ(fh->crc32, df.crc32, \"%s file %zu\", d.testname, j);\n    ASSERTEQ(fh->compressed_size, df.compressed_size, \"%s file %zu\", d.testname, j);\n    ASSERTEQ(fh->uncompressed_size, df.uncompressed_size, \"%s file %zu\", d.testname, j);\n    ASSERTCMP(fh->file_name, df.filename, \"%s file %zu\", d.testname, j);\n    ASSERT(df.uncompressed_size <= BUFFER_SIZE, \"%s file %zu\", d.testname, j);\n  }\n}\n\n#define ZIP_GET_CONTENTS(df) zip_get_contents(df, db64_buffer)\n\nUNITTEST(ZipRead)\n{\n  std::vector<uint8_t> buffer(BUFFER_SIZE);\n  std::vector<uint8_t> db64_buffer;\n  std::vector<uint8_t> file_buffer;\n  struct zip_archive *zp;\n  enum zip_error result;\n  char small_buffer[32];\n  boolean has_files = false;\n  int cmp;\n\n  SECTION(OpenClose)\n  {\n    for(const zip_test_data &d : raw_zip_data)\n    {\n      zp = zip_test_open(d);\n      zip_check(d, zp);\n\n      result = zip_close(zp, nullptr);\n      ASSERTEQ(result, ZIP_SUCCESS, \"%s\", d.testname);\n    }\n  }\n\n  SECTION(OpenInvalid)\n  {\n    for(const zip_test_data &d : raw_zip_invalid_data)\n    {\n      zp = zip_test_open(d);\n      ASSERTEQ(zp, nullptr, \"%s\", d.testname);\n\n      // TODO: can't test the actual error returned from zip_read_directory anymore :(\n    }\n  }\n\n  SECTION(ReadFile)\n  {\n    for(const zip_test_data &d : raw_zip_data)\n    {\n      if(d.num_files)\n      {\n        has_files = true;\n        zp = zip_test_open(d);\n        zip_check(d, zp);\n\n        for(size_t j = 0; j < d.num_files; j++)\n        {\n          const zip_test_file_data &df = d.files[j];\n          size_t real_length = 0;\n\n          result = zip_read_file(zp, buffer.data(), BUFFER_SIZE, &real_length);\n          ASSERTEQ(result, ZIP_SUCCESS, \"%s file %zu\", d.testname, j);\n          ASSERTEQ(real_length, df.uncompressed_size, \"%s file %zu\", d.testname, j);\n\n          if(real_length)\n          {\n            const uint8_t *contents = ZIP_GET_CONTENTS(df);\n            cmp = memcmp(buffer.data(), contents, real_length);\n            ASSERTEQ(cmp, 0, \"%s file %zu\", d.testname, j);\n          }\n        }\n        result = zip_close(zp, nullptr);\n        ASSERTEQ(result, ZIP_SUCCESS, \"%s\", d.testname);\n      }\n    }\n    if(!has_files)\n      FAIL(\"Add test zips with files to read!\");\n  }\n\n  SECTION(ReadStream)\n  {\n    for(const zip_test_data &d : raw_zip_data)\n    {\n      if(d.num_files)\n      {\n        has_files = true;\n        zp = zip_test_open(d);\n        zip_check(d, zp);\n\n        for(size_t j = 0; j < d.num_files; j++)\n        {\n          const zip_test_file_data &df = d.files[j];\n          uint64_t real_length = 0;\n\n          result = zip_read_open_file_stream(zp, &real_length);\n          ASSERTEQ(result, ZIP_SUCCESS, \"%s file %zu\", d.testname, j);\n\n          const uint8_t *contents  = ZIP_GET_CONTENTS(df);\n          for(size_t k = 0; k < real_length; k += sizeof(small_buffer))\n          {\n            size_t n = MIN(sizeof(small_buffer), real_length - k);\n            result = zread(small_buffer, n, zp);\n            ASSERTEQ(result, ZIP_SUCCESS, \"%s file %zu\", d.testname, j);\n            cmp = memcmp(small_buffer, contents + k, n);\n            ASSERTEQ(cmp, 0, \"%s file %zu\", d.testname, j);\n          }\n          result = zip_read_close_stream(zp);\n          ASSERTEQ(result, ZIP_SUCCESS, \"%s file %zu\", d.testname, j);\n        }\n        result = zip_close(zp, nullptr);\n        ASSERTEQ(result, ZIP_SUCCESS, \"%s\", d.testname);\n      }\n    }\n    if(!has_files)\n      FAIL(\"Add test zips with files to read!\");\n  }\n\n  SECTION(ReadMemStream)\n  {\n    for(const zip_test_data &d : raw_zip_data)\n    {\n      if(d.num_files)\n      {\n        has_files = true;\n        zp = zip_test_open(d, file_buffer);\n        zip_check(d, zp);\n        ASSERTEQ(zp->is_memory, true, \"%s\", d.testname);\n\n        for(size_t j = 0; j < d.num_files; j++)\n        {\n          const zip_test_file_data &df = d.files[j];\n          struct memfile mf;\n          size_t real_length = 0;\n          unsigned int method;\n\n          result = zip_get_next_method(zp, &method);\n          ASSERTEQ(result, ZIP_SUCCESS, \"%s file %zu\", d.testname, j);\n\n          result = zip_get_next_uncompressed_size(zp, &real_length);\n          ASSERTEQ(result, ZIP_SUCCESS, \"%s file %zu\", d.testname, j);\n\n          result = zip_read_open_mem_stream(zp, &mf);\n          if(method == ZIP_M_NONE)\n          {\n            const uint8_t *contents  = ZIP_GET_CONTENTS(df);\n            ASSERTEQ(result, ZIP_SUCCESS, \"%s file %zu\", d.testname, j);\n\n            for(size_t k = 0; k < real_length; k += sizeof(small_buffer))\n            {\n              size_t n = MIN(sizeof(small_buffer), real_length - k);\n              cmp = mfread(small_buffer, n, 1, &mf);\n              ASSERTEQ(cmp, 1, \"%s file %zu\", d.testname, j);\n              cmp = memcmp(small_buffer, contents + k, n);\n              ASSERTEQ(cmp, 0, \"%s file %zu\", d.testname, j);\n            }\n            result = zip_read_close_stream(zp);\n            ASSERTEQ(result, ZIP_SUCCESS, \"%s file %zu\", d.testname, j);\n          }\n          else\n          {\n            ASSERTEQ(result, ZIP_UNSUPPORTED_METHOD_MEMORY_STREAM, \"%s file %zu\", d.testname, j);\n            result = zip_skip_file(zp);\n            ASSERTEQ(result, ZIP_SUCCESS, \"%s file %zu\", d.testname, j);\n          }\n        }\n        result = zip_close(zp, nullptr);\n        ASSERTEQ(result, ZIP_SUCCESS, \"%s\", d.testname);\n      }\n    }\n    if(!has_files)\n      FAIL(\"Add test zips with files to read!\");\n  }\n}\n\nstatic void verify_boilerplate(const zip_test_data &d, struct zip_archive *zp,\n const char *label, std::vector<uint8_t> &verify_buffer,\n std::vector<uint8_t> &db64_buffer)\n{\n  enum zip_error result;\n\n  verify_buffer.resize(BUFFER_SIZE);\n\n  ASSERTEQ(d.num_files, zp->num_files, \"%s %s\", label, d.testname);\n  for(size_t j = 0; j < d.num_files; j++)\n  {\n    const zip_test_file_data &df = d.files[j];\n    const uint8_t *contents = ZIP_GET_CONTENTS(df);\n\n    size_t real_length = 0;\n    result = zip_read_file(zp, verify_buffer.data(), BUFFER_SIZE, &real_length);\n    ASSERTEQ(result, ZIP_SUCCESS, \"%s %s %zu\", label, d.testname, j);\n    ASSERTEQ(real_length, df.uncompressed_size, \"%s %s %zu\", label, d.testname, j);\n\n    int cmp = memcmp(contents, verify_buffer.data(), real_length);\n    ASSERTEQ(cmp, 0, \"%s %s %zu\", label, d.testname, j);\n  }\n\n  result = zip_close(zp, nullptr);\n  ASSERTEQ(result, ZIP_SUCCESS, \"%s %s\", label, d.testname);\n}\n\nUNITTEST(ZipWrite)\n{\n  std::vector<uint8_t> verify_buffer;\n  std::vector<uint8_t> db64_buffer;\n  // This buffer needs to be resized by C code...\n  char *ext_buffer = (char *)cmalloc(32);\n  size_t ext_buffer_size = 32;\n\n  static const char OUTPUT_FILE[] = \"_output_file.zip\";\n  static const char LABEL[4][20] = { \"File\", \"File(Zip64)\", \"MemoryExt\", \"MemoryExt(Zip64)\" };\n  struct zip_archive *zp;\n  enum zip_error result;\n  uint64_t final_size;\n\n  SECTION(WriteFile)\n  {\n    for(int type = 0; type < 4; type++)\n    {\n      const char *label = LABEL[type];\n      for(const zip_test_data &d : raw_zip_data)\n      {\n        if(type < 2)\n          zp = zip_open_file_write(OUTPUT_FILE);\n        else\n          zp = zip_open_mem_write_ext((void **)&ext_buffer, &ext_buffer_size, 0);\n\n        ASSERT(zp, \"%s %s\", label, d.testname);\n\n        zip_set_zip64_enabled(zp, type & 1);\n\n        for(size_t j = 0; j < d.num_files; j++)\n        {\n          const zip_test_file_data &df = d.files[j];\n          const uint8_t *contents = ZIP_GET_CONTENTS(df);\n          result = zip_write_file(zp, df.filename, (const void *)contents,\n           df.uncompressed_size, df.method);\n\n          ASSERTEQ(result, ZIP_SUCCESS, \"%s %s %zu\", label, d.testname, j);\n        }\n        result = zip_close(zp, &final_size);\n        ASSERTEQ(result, ZIP_SUCCESS, \"%s %s\", label, d.testname);\n\n        if(type < 2)\n          zp = zip_open_file_read(OUTPUT_FILE);\n        else\n          zp = zip_open_mem_read(ext_buffer, final_size);\n\n        verify_boilerplate(d, zp, label, verify_buffer, db64_buffer);\n      }\n    }\n  }\n\n  SECTION(WriteStream)\n  {\n    for(int type = 0; type < 4; type++)\n    {\n      const char *label = LABEL[type];\n      for(const zip_test_data &d : raw_zip_data)\n      {\n        if(type < 2)\n          zp = zip_open_file_write(OUTPUT_FILE);\n        else\n          zp = zip_open_mem_write_ext((void **)&ext_buffer, &ext_buffer_size, 0);\n\n        ASSERT(zp, \"%s %s\", label, d.testname);\n\n        zip_set_zip64_enabled(zp, type & 1);\n\n        for(size_t j = 0; j < d.num_files; j++)\n        {\n          const zip_test_file_data &df = d.files[j];\n          const uint8_t *contents = ZIP_GET_CONTENTS(df);\n\n          result = zip_write_open_file_stream(zp, df.filename, df.method);\n          ASSERTEQ(result, ZIP_SUCCESS, \"%s %s %zu\", label, d.testname, j);\n          for(size_t k = 0; k < df.uncompressed_size; k += 32)\n          {\n            size_t size = MIN(df.uncompressed_size - k, 32);\n            result = zwrite(contents + k, size, zp);\n            ASSERTEQ(result, ZIP_SUCCESS, \"%s %s %zu\", label, d.testname, j);\n          }\n          result = zip_write_close_stream(zp);\n          ASSERTEQ(result, ZIP_SUCCESS, \"%s %s %zu\", label, d.testname, j);\n        }\n        result = zip_close(zp, &final_size);\n        ASSERTEQ(result, ZIP_SUCCESS, \"%s %s\", label, d.testname);\n\n        if(type < 2)\n          zp = zip_open_file_read(OUTPUT_FILE);\n        else\n          zp = zip_open_mem_read(ext_buffer, final_size);\n\n        verify_boilerplate(d, zp, label, verify_buffer, db64_buffer);\n      }\n    }\n  }\n\n  // This is a special version of streaming that allows direct write access to\n  // the buffer. This only works with the STORE method and likely doesn't work\n  // very well with expandable buffers right now.\n  SECTION(Memory_WriteMemStream)\n  {\n    const char *label = \"MemoryFixed\";\n    for(const zip_test_data &d : raw_zip_data)\n    {\n      // Precompute likely required size for the archive.\n      size_t max_name_size = 8;\n      ext_buffer_size = 0;\n      for(size_t j = 0; j < d.num_files; j++)\n      {\n        ext_buffer_size += d.files[j].uncompressed_size;\n        max_name_size = MAX(max_name_size, strlen(d.files[j].filename));\n      }\n      ext_buffer_size += zip_bound_total_header_usage(d.num_files, max_name_size);\n      ext_buffer = (char *)crealloc(ext_buffer, ext_buffer_size);\n\n      zp = zip_open_mem_write(ext_buffer, ext_buffer_size, 0);\n      ASSERT(zp, \"%s %s\", label, d.testname);\n\n      for(size_t j = 0; j < d.num_files; j++)\n      {\n        const zip_test_file_data &df = d.files[j];\n        const uint8_t *contents = ZIP_GET_CONTENTS(df);\n        struct memfile mf;\n\n        result = zip_write_open_mem_stream(zp, &mf, df.filename, df.uncompressed_size);\n        ASSERTEQ(result, ZIP_SUCCESS, \"%s %s %zu\", label, d.testname, j);\n        int res = mfwrite(contents, df.uncompressed_size, 1, &mf);\n        int expected = df.uncompressed_size != 0;\n        ASSERTEQ(res, expected, \"%s %s %zu\", label, d.testname, j);\n        result = zip_write_close_mem_stream(zp, &mf);\n        ASSERTEQ(result, ZIP_SUCCESS, \"%s %s %zu\", label, d.testname, j);\n      }\n      result = zip_close(zp, &final_size);\n      ASSERTEQ(result, ZIP_SUCCESS, \"%s %s\", label, d.testname);\n\n      zp = zip_open_mem_read(ext_buffer, final_size);\n      verify_boilerplate(d, zp, label, verify_buffer, db64_buffer);\n    }\n  }\n\n  // Make sure fixed buffer memory write operations don't write past the buffer...\n  SECTION(Memory_EOF)\n  {\n    char buffer[128];\n\n    // EOF during EOCD write.\n    zp = zip_open_mem_write(buffer, 20, 0);\n    ASSERT(zp, \"\");\n    result = zip_close(zp, nullptr);\n    ASSERTEQ(result, ZIP_EOF, \"Should fail EOCD write.\");\n\n    // EOF during local header write.\n    zp = zip_open_mem_write(buffer, 32, 0);\n    ASSERT(zp, \"\");\n    result = zip_write_file(zp, \"filename.ext\", \"abcde\", 5, ZIP_M_NONE);\n    ASSERTEQ(result, ZIP_EOF, \"Should fail local header write.\");\n    zip_close(zp, nullptr);\n\n    // EOF during file contents write.\n    zp = zip_open_mem_write(buffer, 48, 0);\n    ASSERT(zp, \"\");\n    zip_set_zip64_enabled(zp, false);\n    result = zip_write_open_file_stream(zp, \"filename.ext\", ZIP_M_NONE);\n    ASSERTEQ(result, ZIP_SUCCESS, \"Failed to open write stream.\");\n    result = zwrite(\"abcdefghij\", 10, zp);\n    ASSERTEQ(result, ZIP_EOF, \"Should fail file write.\");\n    zip_write_close_stream(zp);\n    zip_close(zp, nullptr);\n\n    // EOF during central directory write.\n    zp = zip_open_mem_write(buffer, 72, 0);\n    ASSERT(zp, \"\");\n    result = zip_write_file(zp, \"filename.ext\", \"abcdefghij\", 10, ZIP_M_NONE);\n    ASSERTEQ(result, ZIP_SUCCESS, \"Failed to write file.\");\n    result = zip_close(zp, nullptr);\n    ASSERTEQ(result, ZIP_EOF, \"Should fail to write central directory.\");\n\n    // EOF during EOCD write after successful central directory write.\n    zp = zip_open_mem_write(buffer, 128, 0);\n    ASSERT(zp, \"\");\n    result = zip_write_file(zp, \"filename.ext\", \"abcdefghij\", 10, ZIP_M_NONE);\n    ASSERTEQ(result, ZIP_SUCCESS, \"Failed to write file\");\n    result = zip_close(zp, nullptr);\n    ASSERTEQ(result, ZIP_EOF, \"Should fail to write EOCD (2).\");\n  }\n\n  // Make sure attempting to provide the external size buffer as the final\n  // length doesn't crash (this really shouldn't be done though)...\n  SECTION(Memory_Ext_DuplicateFinalLength)\n  {\n    char tmp[32]{};\n\n    zp = zip_open_mem_write_ext(reinterpret_cast<void **>(&ext_buffer), &ext_buffer_size, 0);\n    ASSERT(zp, \"\");\n\n    result = zip_write_file(zp, \"\", tmp, sizeof(tmp), ZIP_M_NONE);\n    ASSERTEQ(result, ZIP_SUCCESS, \"Failed to write dummy file.\");\n\n    // This is the bad thing you shouldn't do!\n    result = zip_close(zp, reinterpret_cast<uint64_t *>(&ext_buffer_size));\n    ASSERTEQ(result, ZIP_SUCCESS, \"Failed write central directory.\");\n\n    // Final size = local header (30) + data (32) + central header (46) + eocd (22).\n    // If data descriptors are enabled, it will be 142 instead (+12).\n    ASSERT(ext_buffer_size == 130 || ext_buffer_size == 142,\n     \"Incorrect final size: %zu\", ext_buffer_size);\n  }\n\n  free(ext_buffer);\n}\n\nUNITTEST(Zip64)\n{\n  // Special case test files for Zip64.\n  struct zip_archive *zp;\n  enum zip_error result;\n  size_t sz;\n\n  SECTION(Zip64FileCount)\n  {\n    // This archive contains an MZX 2.92f format world with over 65535 files,\n    // which requires an EOCD64 record and Zip64 support. The internal archive\n    // is about 8.5 MB in size.\n    zp = zip_test_open(\"zip64_file_count.zip\");\n    ASSERT(zp, \"\");\n\n    result = zip_get_next_uncompressed_size(zp, &sz);\n    ASSERTEQ(result, ZIP_SUCCESS, \"\");\n\n    std::vector<uint8_t> buffer(sz);\n    result = zip_read_file(zp, buffer.data(), sz, &sz);\n    ASSERTEQ(result, ZIP_SUCCESS, \"\");\n\n    zip_close(zp, NULL);\n\n    // Open and verify inner archive.\n    zp = zip_open_mem_read(buffer.data(), sz);\n    ASSERT(zp, \"\");\n    for(size_t i = 0; i < zp->num_files; i++)\n    {\n      result = zip_read_open_file_stream(zp, NULL);\n      ASSERTEQ(result, ZIP_SUCCESS, \"\");\n      // Close file to perform a CRC check.\n      result = zip_read_close_stream(zp);\n      ASSERTEQ(result, ZIP_SUCCESS, \"\");\n    }\n    zip_close(zp, NULL);\n  }\n\n  SECTION(Zip64Content)\n  {\n    // This double zipped archive contains an MZX counters file with a string\n    // file over 4GB. Extract the outer archive to RAM, then stream the inner\n    // archive to verify the uncompressed size works. For practical reasons\n    // the counters file archive can't really be tested against zip.c since\n    // vio.c can't transparently stream large files out of another archive.\n    // The inner archive is about 5 MB in size.\n    zp = zip_test_open(\"zip64_file_size.zip\");\n    ASSERT(zp, \"\");\n\n    result = zip_get_next_uncompressed_size(zp, &sz);\n    ASSERTEQ(result, ZIP_SUCCESS, \"\");\n\n    std::vector<uint8_t> buffer(sz);\n    result = zip_read_file(zp, buffer.data(), sz, &sz);\n    ASSERTEQ(result, ZIP_SUCCESS, \"\");\n\n    zip_close(zp, NULL);\n\n    zp = zip_open_mem_read(buffer.data(), sz);\n    ASSERT(zp, \"\");\n\n    // Verifying the CRC of the internal file is too slow, just check the header.\n    ASSERTEQ(zp->num_files, 1, \"\");\n    ASSERTEQ(zp->files[0]->uncompressed_size, 4299174197ull, \"\");\n    ASSERTEQ(zp->files[0]->compressed_size, 5062173, \"\");\n    ASSERTEQ(zp->files[0]->crc32, 0x3cf11c4f, \"\");\n\n    zip_close(zp, NULL);\n  }\n}\n"
  },
  {
    "path": "unit/memcasecmp.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"Unit.hpp\"\n#include \"../src/memcasecmp.h\"\n\n#include <string.h>\n\nstruct string_pair\n{\n  const char *a;\n  const char *b;\n};\n\nstruct string_pair_aligned\n{\n  alignstr<uint64_t> a;\n  alignstr<uint64_t> b;\n};\n\nstruct bad_string_pair\n{\n  const char *a;\n  const char *b;\n  int expected;\n};\n\nstruct bad_string_pair_aligned\n{\n  alignstr<uint64_t> a;\n  alignstr<uint64_t> b;\n  int expected;\n};\n\nUNITTEST(memtolower)\n{\n  int i;\n  for(i = 0; i < 256; i++)\n  {\n    if(i >= 'A' && i <= 'Z')\n      ASSERTEQ(memtolower(i), (i - 'A' + 'a'), \"\");\n    else\n      ASSERTEQ(memtolower(i), i, \"\");\n  }\n}\n\nUNITTEST(Matching)\n{\n  static const string_pair pairs[] =\n  {\n    { \"\", \"\" },\n    { \" \", \" \" },\n    { \"abcde\", \"ABCDE\" },\n    { \"testTESTtest\", \"TESTtestTEST\" },\n    { \"@!%$*^!@$&#!*([]\", \"@!%$*^!@$&#!*([]\" },\n    { u8\"śśśśśśśś\", u8\"śśśśśśśś\" },\n  };\n\n  static const string_pair_aligned pairs64[] =\n  {\n    { \"\", \"\" },\n    { \" \", \" \" },\n    { \"abcde\", \"ABCDE\" },\n    { \"testTESTtestTEST\", \"TESTtestTESTtest\" },\n    { \"aaaaaaaabaaaaaaabaaaaaaabaaaaaaa\", \"aaaaaaaabaaaaaaabaaaaaaabaaaaaaa\" },\n    { \"aaaaaaaabaaaaaaaBAAAAAAAbaaaaaaa\", \"aaaaaaaaBAAAAAAAbaaaaaaabaaaaaaa\" },\n    { \"@!%$*^!@$&#!*([]\", \"@!%$*^!@$&#!*([]\" },\n    { u8\"śśśśśśśś\", u8\"śśśśśśśś\" },\n  };\n  const char *a;\n  const char *b;\n\n  SECTION(memcasecmp)\n  {\n    for(const string_pair &p : pairs)\n    {\n      a = p.a;\n      b = p.b;\n      ASSERTEQ(strlen(a), strlen(b), \"%s\", a);\n      ASSERT(!memcasecmp(a, b, strlen(a)), \"%s\", a);\n    }\n\n    for(const string_pair_aligned &p : pairs64)\n    {\n      a = p.a.c_str();\n      b = p.b.c_str();\n\n      ASSERTEQ((size_t)a & (ALIGN_64_MODULO - 1), 0, \"a not aligned\");\n      ASSERTEQ((size_t)b & (ALIGN_64_MODULO - 1), 0, \"b not aligned\");\n      ASSERTEQ(strlen(a), strlen(b), \"%s\", a);\n      ASSERT(!memcasecmp(a, b, strlen(a)), \"%s\", a);\n    }\n  }\n\n  SECTION(memcasecmp32)\n  {\n    for(const string_pair &p : pairs)\n    {\n      a = p.a;\n      b = p.b;\n      ASSERTEQ(strlen(a), strlen(b), \"%s\", a);\n      ASSERT(!memcasecmp32(a, b, strlen(a)), \"%s\", a);\n    }\n\n    for(const string_pair_aligned &p : pairs64)\n    {\n      a = p.a.c_str();\n      b = p.b.c_str();\n\n      ASSERTEQ((size_t)a & (ALIGN_32_MODULO - 1), 0, \"a not aligned\");\n      ASSERTEQ((size_t)b & (ALIGN_32_MODULO - 1), 0, \"b not aligned\");\n      ASSERTEQ(strlen(a), strlen(b), \"%s\", a);\n      ASSERT(!memcasecmp32(a, b, strlen(a)), \"%s\", a);\n    }\n  }\n}\n\nUNITTEST(NoMatch)\n{\n  static const bad_string_pair pairs[] =\n  {\n    { \"-\", \"_\", ('-' - '_') },\n    { \"abcde\", \"ABCDF\", ('e' - 'f') },\n    { \"testTESTtest\", \"TASTtastTAST\", ('e' - 'a') },\n    { \"@!%$*^!@$&#!*([a\", \"@!%$*^!@$&#!*([b\", ('a' - 'b') },\n    { u8\"śśŚśśśŚś\", u8\"ŚśśśŚśśś\", 1 },\n  };\n\n  static const bad_string_pair_aligned pairs64[] =\n  {\n    { \"-\", \"_\", ('-' - '_') },\n    { \"testTESTtestTEST\", \"TASTtastTASTtast\", ('e' - 'a') },\n    { \"aaaaaaaa*aaaaaaaBAAAAAAA\", \"aaaaaaaaBAAAAAAAbaaaaaaa\", ('*' - 'b') },\n    { \"aaaaaaaab*aaaaaaBAAAAAAA\", \"aaaaaaaaBAAAAAAAbaaaaaaa\", ('*' - 'a') },\n    { \"aaaaaaaaba*aaaaaBAAAAAAA\", \"aaaaaaaaBAAAAAAAbaaaaaaa\", ('*' - 'a') },\n    { \"aaaaaaaabaa*aaaaBAAAAAAA\", \"aaaaaaaaBAAAAAAAbaaaaaaa\", ('*' - 'a') },\n    { \"aaaaaaaabaaa*aaaBAAAAAAA\", \"aaaaaaaaBAAAAAAAbaaaaaaa\", ('*' - 'a') },\n    { \"aaaaaaaabaaaa*aaBAAAAAAA\", \"aaaaaaaaBAAAAAAAbaaaaaaa\", ('*' - 'a') },\n    { \"aaaaaaaabaaaaa*aBAAAAAAA\", \"aaaaaaaaBAAAAAAAbaaaaaaa\", ('*' - 'a') },\n    { \"aaaaaaaabaaaaaa*BAAAAAAA\", \"aaaaaaaaBAAAAAAAbaaaaaaa\", ('*' - 'a') },\n    { \"@!%$*^!@$&#!*([a\", \"@!%$*^!@$&#!*([b\", ('a' - 'b') },\n    { u8\"śśŚśśśŚś\", u8\"ŚśśśŚśśś\", 1 },\n  };\n  const char *a;\n  const char *b;\n  int expected;\n\n  SECTION(memcasecmp)\n  {\n    for(const bad_string_pair &p : pairs)\n    {\n      a = p.a;\n      b = p.b;\n      expected = p.expected;\n      ASSERTEQ(strlen(a), strlen(b), \"%s\", a);\n      ASSERTEQ(memcasecmp(a, b, strlen(a)), expected, \"%s\", a);\n    }\n\n    for(const bad_string_pair_aligned &p : pairs64)\n    {\n      a = p.a.c_str();\n      b = p.b.c_str();\n      expected = p.expected;\n\n      ASSERTEQ((size_t)a & (ALIGN_64_MODULO - 1), 0, \"a not aligned\");\n      ASSERTEQ((size_t)b & (ALIGN_64_MODULO - 1), 0, \"b not aligned\");\n      ASSERTEQ(strlen(a), strlen(b), \"%s\", a);\n      ASSERTEQ(memcasecmp(a, b, strlen(a)), expected, \"%s\", a);\n    }\n  }\n\n  SECTION(memcasecmp32)\n  {\n    for(const bad_string_pair &p : pairs)\n    {\n      a = p.a;\n      b = p.b;\n      expected = p.expected;\n      ASSERTEQ(strlen(a), strlen(b), \"%s\", a);\n      ASSERTEQ(memcasecmp32(a, b, strlen(a)), expected, \"%s\", a);\n    }\n\n    for(const bad_string_pair_aligned &p : pairs64)\n    {\n      a = p.a.c_str();\n      b = p.b.c_str();\n      expected = p.expected;\n\n      ASSERTEQ((size_t)a & (ALIGN_32_MODULO - 1), 0, \"a not aligned\");\n      ASSERTEQ((size_t)b & (ALIGN_32_MODULO - 1), 0, \"b not aligned\");\n      ASSERTEQ(strlen(a), strlen(b), \"%s\", a);\n      ASSERTEQ(memcasecmp32(a, b, strlen(a)), expected, \"%s\", a);\n    }\n  }\n}\n"
  },
  {
    "path": "unit/network/Manifest.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include <stdint.h>\n\n#include \"../Unit.hpp\"\n\n#include \"../../src/util.h\"\n#include \"../../src/network/Scoped.hpp\"\n#include \"../../src/network/Manifest.hpp\"\n\nstatic const char *DATA_DIR = \"../data\";\nstatic const char *TEMP_FILE = \"DELETE_ME.txt\";\n\nclass pushd\n{\nprivate:\n  boolean success;\n  char prev[MAX_PATH];\n\n  pushd(const pushd &) {}\n\npublic:\n  pushd(const char *new_dir)\n  {\n    trace(\"--UNIT-- attempting to chdir to %s.\\n\", new_dir);\n    success = false;\n    if(getcwd(prev, MAX_PATH))\n      if(!chdir(new_dir))\n        success = true;\n\n    if(!success)\n      FAIL(\"chdir failed\");\n  }\n\n  ~pushd()\n  {\n    if(success)\n    {\n      trace(\"--UNIT-- attempting to chdir back to %s.\\n\", prev);\n      chdir(prev);\n    }\n  }\n};\n\nstruct manifestdata\n{\n  const char *filename;\n  const char *value;\n  size_t size;\n  uint32_t sha256[8];\n};\n\n// The lines here should match filedata's contents.\n// Intentionally using mixed line ends since either should be supported.\nstatic const char manifest_str[] =\n \"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad 3 1.txt\\n\"\n \"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1 56 2.txt\\r\\n\";\n\nstatic const manifestdata filedata[]\n{\n  {\n    \"1.txt\",\n    \"abc\",\n    3,\n    {\n      0xba7816bf, 0x8f01cfea, 0x414140de, 0x5dae2223,\n      0xb00361a3, 0x96177a9c, 0xb410ff61, 0xf20015ad\n    }\n  },\n  {\n    \"2.txt\",\n    \"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq\",\n    56,\n    {\n      0x248d6a61, 0xd20638b8, 0xe5c02693, 0x0c3e6039,\n      0xa33ce459, 0x64ff2167, 0xf6ecedd4, 0x19db06c1\n    }\n  }\n};\n\nstatic const manifestdata dummy_file =\n{\n  \"ascii.chr\",\n  nullptr,\n  3584,\n  {\n    0x657ca658, 0x8b6bf729, 0xf0ed71a3, 0xd3c781a4,\n    0x65bd30cd, 0xfb11eb5f, 0xc7e27594, 0xc0717ea1\n  }\n};\n\nUNITTEST(ManifestEntry)\n{\n  SECTION(ManifestEntry)\n  {\n    for(const manifestdata &f : filedata)\n    {\n      ManifestEntry e(f.sha256, f.size, f.filename);\n      ASSERTMEM(e.sha256, f.sha256, sizeof(f.sha256), \"%s\", f.filename);\n      ASSERTEQ(e.size, f.size, \"%s\", f.filename);\n      ASSERTCMP(e.name, f.filename, \"\");\n\n      ManifestEntry e2(e);\n      ASSERTMEM(e2.sha256, f.sha256, sizeof(f.sha256), \"%s\", f.filename);\n      ASSERTEQ(e2.size, f.size, \"%s\", f.filename);\n      ASSERTCMP(e2.name, f.filename, \"\");\n    }\n  }\n\n  SECTION(operator=)\n  {\n    for(const manifestdata &f : filedata)\n    {\n      ManifestEntry e(dummy_file.sha256, dummy_file.size, dummy_file.filename);\n      ManifestEntry e2(f.sha256, f.size, f.filename);\n      e = e2;\n\n      ASSERTMEM(e.sha256, f.sha256, sizeof(f.sha256), \"%s\", f.filename);\n      ASSERTEQ(e.size, f.size, \"%s\", f.filename);\n      ASSERTCMP(e.name, f.filename, \"\");\n    }\n  }\n\n  SECTION(validate)\n  {\n    pushd ch(DATA_DIR);\n\n    for(const manifestdata &f : filedata)\n    {\n      ManifestEntry e(f.sha256, f.size, f.filename);\n      boolean valid = e.validate();\n      ASSERT(valid, \"%s\", f.filename);\n    }\n  }\n\n  SECTION(validate_filename)\n  {\n    static const char *valid[] =\n    {\n      \"data/1.txt\",\n      \"sdfjsdlkfjdskljfds/sdfjsdlkfjsdlkjfsdkl/sfdjfklsjdflksd\",\n      \"aaaaaaaaaaa\"\n    };\n    /* Internally, this should be path_safety_check with most/all flags. */\n    static const char *invalid[] =\n    {\n      \"..\\\\lol\",\n      \"../lol\",\n      \"totally/normal/path/../../../../../../../../../bootmgr\",\n      \"..\",\n      \"C:\\\\Windows\\\\System32\",\n      \"C:/Windows/System32\",\n      \"/dev\",\n      \"\\\\dev\",\n      \"amigaroot:lol\",\n      \"amigaparent/////////System/Shell\",\n      \"no>pe\",\n      \"also<nope\",\n      \"not|even\",\n      \"\\\"nope\\\"\",\n      \"? not a chance\",\n      \"NO\\r\\n\",\n      \"s\\tahp\",\n      \"n\\x1f\",\n      \"com1\",\n      \"lpt1\",\n      \"aux\",\n      \"NUL\",\n      \"prn\",\n      \"con\",\n      \"com9.chr\",\n      \"lpt2.tar.gz\",\n      \"com\\xb2.html\",\n      \"com7/default.chr\",\n      \"awawa\\\\lpt\\xb9\",\n    };\n\n    for(const char *filename : valid)\n      ASSERT(ManifestEntry::validate_filename(filename), \"%s\", filename);\n\n    for(const char *filename : invalid)\n      ASSERT(!ManifestEntry::validate_filename(filename), \"%s\", filename);\n  }\n\n  SECTION(create_from_file)\n  {\n    pushd ch(DATA_DIR);\n\n    for(const manifestdata &f : filedata)\n    {\n      ScopedPtr<ManifestEntry> e = ManifestEntry::create_from_file(f.filename);\n      ASSERT(e, \"%s\", f.filename);\n\n      ASSERTMEM(e->sha256, f.sha256, sizeof(f.sha256), \"%s\", f.filename);\n      ASSERTEQ(e->size, f.size, \"%s\", f.filename);\n      ASSERTCMP(e->name, f.filename, \"\");\n    }\n  }\n}\n\nstatic void test_manifest(const Manifest &m, const char *comment)\n{\n  const ManifestEntry *e = m.first();\n  for(const manifestdata &f : filedata)\n  {\n    ASSERT(e, \"%s, %s\", f.filename, comment);\n    ASSERTMEM(e->sha256, f.sha256, sizeof(f.sha256), \"%s, %s\", f.filename, comment);\n    ASSERTEQ(e->size, f.size, \"%s, %s\", f.filename, comment);\n    ASSERTCMP(e->name, f.filename, \"%s, %s\", f.filename, comment);\n    ASSERT(e->validate(), \"%s, %s\", f.filename, comment);\n    e = e->next;\n  }\n  ASSERT(!e, \"unexpected data at end of Manifest\");\n}\n\nUNITTEST(Manifest)\n{\n  SECTION(create)\n  {\n    pushd ch(DATA_DIR);\n\n    Manifest m;\n    m.create(\"manifest.txt\");\n    test_manifest(m, \"create(filename)\");\n    m.clear();\n    ASSERT(!m.head, \"clear failed\");\n\n    m.create(manifest_str, arraysize(manifest_str));\n    test_manifest(m, \"create(buffer, size)\");\n\n    Manifest m2;\n    m2.create(m);\n    test_manifest(m2, \"create(const Manifest &)\");\n  }\n\n  SECTION(append)\n  {\n    Manifest m1, m2;\n    m1.create(manifest_str, arraysize(manifest_str));\n    m2.create(manifest_str, arraysize(manifest_str));\n\n    // append(Manifest &) consumes the source Manifest's data.\n    m1.append(m2);\n    ASSERT(!m2.head, \"append failed to clear source Manifest\");\n\n    ManifestEntry *tmp =\n     new ManifestEntry(dummy_file.sha256, dummy_file.size, dummy_file.filename);\n    m1.append(tmp);\n\n    const ManifestEntry *e = m1.first();\n    for(int j = 0; j < 2; j++)\n    {\n      for(const manifestdata &f : filedata)\n      {\n        ASSERT(e, \"%s\", f.filename);\n        ASSERTCMP(e->name, f.filename, \"\");\n        e = e->next;\n      }\n    }\n\n    ASSERT(e, \"%s\", dummy_file.filename);\n    ASSERTCMP(e->name, dummy_file.filename, \"\");\n    e = e->next;\n    ASSERT(!e, \"unexpected data at end of Manifest\");\n  }\n\n  SECTION(write_to_file)\n  {\n    pushd ch(DATA_DIR);\n\n    Manifest m;\n    m.create(manifest_str, arraysize(manifest_str));\n    m.write_to_file(TEMP_FILE);\n    m.clear();\n    ASSERT(!m.head, \"clear failed\");\n\n    m.create(TEMP_FILE);\n    test_manifest(m, \"create(filename)\");\n\n    unlink(TEMP_FILE);\n  }\n\n  // NOTE: check_if_remote_exists requires networking.\n  // NOTE: get_updates requires networking.\n  // NOTE: download_and_replace_entry requires networking.\n}\n"
  },
  {
    "path": "unit/network/data/1.txt",
    "content": "abc"
  },
  {
    "path": "unit/network/data/2.txt",
    "content": "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
  },
  {
    "path": "unit/network/data/manifest.txt",
    "content": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad 3 1.txt\n248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1 56 2.txt\n"
  },
  {
    "path": "unit/network/sha256.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2020-2021 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../Unit.hpp\"\n#include \"../../src/platform.h\"\n\n#include \"../../src/network/sha256.c\"\n\n#define ASSETS_DIR \"../../../assets\"\n#define IO_DATA_DIR \"../../io/data\"\n\nstruct SHA256_data\n{\n  const char *input;\n  uint32_t result[8];\n};\n\nUNITTEST(SHA256String)\n{\n  static const SHA256_data data[] =\n  {\n    {\n      \"abc\",\n      {\n        0xba7816bf, 0x8f01cfea, 0x414140de, 0x5dae2223,\n        0xb00361a3, 0x96177a9c, 0xb410ff61, 0xf20015ad\n      }\n    },\n    {\n      \"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq\",\n      {\n        0x248d6a61, 0xd20638b8, 0xe5c02693, 0x0c3e6039,\n        0xa33ce459, 0x64ff2167, 0xf6ecedd4, 0x19db06c1\n      }\n    }\n  };\n  struct SHA256_ctx ctx;\n\n  for(const SHA256_data &d : data)\n  {\n    SHA256_init(&ctx);\n    SHA256_update(&ctx, d.input, strlen(d.input));\n    SHA256_final(&ctx);\n    ASSERTMEM(ctx.H, d.result, sizeof(ctx.H), \"%s\", d.input);\n  }\n}\n\nUNITTEST(SHA256File)\n{\n  static const SHA256_data data[] =\n  {\n    {\n      ASSETS_DIR \"/ascii.chr\",\n      {\n        0x657ca658, 0x8b6bf729, 0xf0ed71a3, 0xd3c781a4,\n        0x65bd30cd, 0xfb11eb5f, 0xc7e27594, 0xc0717ea1\n      }\n    },\n    {\n      ASSETS_DIR \"/smzx.pal\",\n      {\n        0x60db72c5, 0xe0bcac9b, 0xd2093019, 0x4d45f363,\n        0xb49db55d, 0x93f4b404, 0xa488fbb8, 0x00d70627\n      }\n    },\n    {\n      IO_DATA_DIR \"/dch1.txt\",\n      {\n        0xd872892d, 0xdeb2a55f, 0x0207731f, 0x04290400,\n        0xa619cc08, 0x07abd24e, 0x2f2b715d, 0xf27ae020\n      }\n    },\n    {\n      IO_DATA_DIR \"/CN_S.CHR\",\n      {\n        0x2668c893, 0x09ec7ebf, 0xd7b387a3, 0x11b21395,\n        0xd5e6b34f, 0xd67f3e81, 0x0b407b84, 0x035667ef\n      }\n    },\n    {\n      IO_DATA_DIR \"/FREAKSOF.MZX\",\n      {\n        0x0a993ca6, 0x82909e78, 0x020f9905, 0x2083704e,\n        0xfcfa7929, 0x3d3fb8cd, 0xa6ccba4c, 0xedd0fa15\n      }\n    },\n    {\n      IO_DATA_DIR \"/CT_LEVEL.MOD\",\n      {\n        0x3cdda2de, 0xb45d0eac, 0xaa0c4f47, 0xcd8acd77,\n        0xe377ea2d, 0x3db5e0b4, 0x5046902f, 0x6722c2b1\n      }\n    },\n  };\n  struct SHA256_ctx ctx;\n  char buffer[8192];\n\n  for(const SHA256_data &d : data)\n  {\n    SHA256_init(&ctx);\n\n    FILE *fp = fopen_unsafe(d.input, \"rb\");\n    ASSERT(fp, \"%s\", d.input);\n\n    fseek(fp, 0, SEEK_END);\n    ssize_t file_len = ftell(fp);\n    rewind(fp);\n\n    ssize_t j = 0;\n    while(j < file_len)\n    {\n      ssize_t len = Unit::min(file_len - j, (ssize_t)arraysize(buffer));\n      size_t ret = fread(buffer, len, 1, fp);\n      ASSERTEQ(ret, 1, \"%s\", d.input);\n\n      SHA256_update(&ctx, buffer, len);\n      j += len;\n    }\n    fclose(fp);\n    SHA256_final(&ctx);\n    ASSERTMEM(ctx.H, d.result, sizeof(ctx.H), \"%s\", d.input);\n  }\n}\n"
  },
  {
    "path": "unit/render.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2024-2025 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"Unit.hpp\"\n#include \"UnitIO.hpp\"\n\n#include \"../src/io/path.h\"\n\nextern \"C\" {\n#include \"../src/render.c\"\n}\n\n#define BUILD_REFERENCE_RENDERER\n#include \"../src/render_layer.cpp\"\n\ntypedef void (*render_layer_fp)(void * RESTRICT pixels,\n size_t width_px, size_t height_px, size_t pitch, int bpp,\n const struct graphics_data *graphics, const struct video_layer *layer);\n\n//#define GENERATE_FILES\n\n#define DIR \"..\" DIR_SEPARATOR \"render\" DIR_SEPARATOR\n\nenum smzx_type\n{\n  MZX = 0,\n  SMZX = 1\n};\n\nenum flat_bpp\n{\n  FLAT16,\n  FLAT32,\n  FLAT_YUY2,\n  FLAT_UYVY,\n  FLAT_YVYU\n};\n\nstatic const uint8_t palette[][3] =\n{\n  {  0,  0,  0 },\n  {  0,  0, 42 },\n  {  0, 42,  0 },\n  {  0, 42, 42 },\n  { 42,  0,  0 },\n  { 42,  0, 42 },\n  { 42, 21,  0 },\n  { 42, 42, 42 },\n  { 21, 21, 21 },\n  { 21, 21, 63 },\n  { 21, 63, 21 },\n  { 21, 63, 63 },\n  { 63, 21, 21 },\n  { 63, 21, 63 },\n  { 63, 63, 21 },\n  { 63, 63, 63 }\n};\n\nstatic size_t calc_offset(size_t frame_w, size_t frame_h, size_t full_w, size_t full_h)\n{\n  return (full_h - frame_h) * full_w / 2 + (full_w - frame_w) / 2;\n}\n\ntemplate<typename T, int MISALIGN = 0, int GENERATE = 1>\nclass render_frame\n{\n  size_t frame_w;\n  size_t frame_h;\n  size_t full_width;\n  size_t full_height;\n  size_t start_offset;\n  std::vector<T> buf;\n\npublic:\n  static constexpr T fill = static_cast<T>(0xbabababababababa);\n\n  render_frame(size_t w, size_t h):\n   frame_w(w), frame_h(h), full_width(w + 16), full_height(h + 16),\n   buf(full_width * full_height, T(fill))\n  {\n    start_offset = calc_offset(frame_w, frame_h, full_width, full_height);\n  }\n\n  T *pixels()\n  {\n    return buf.data() + start_offset;\n  }\n\n  const T *pixels() const\n  {\n    return buf.data() + start_offset;\n  }\n\n  size_t pitch()\n  {\n    return sizeof(T) * (full_width - MISALIGN);\n  }\n\n  size_t bpp()\n  {\n    return 8 * sizeof(T);\n  }\n\n  void check_out_of_frame() const\n  {\n    size_t skip = full_width - frame_w;\n    size_t i;\n    size_t j;\n    size_t y;\n\n    // misalign rows if requested by reducing the skip size.\n    skip -= MISALIGN;\n\n    for(i = 0; i < start_offset; i++)\n      ASSERTEQ(buf[i], fill, \"letterbox mismatch @ %zu\", i);\n\n    for(y = 0; y < frame_h; y++)\n    {\n      i += frame_w;\n      for(j = 0; j < skip; j++, i++)\n        ASSERTEQ(buf[i], fill, \"letterbox mismatch @ %zu\", i);\n    }\n    size_t full_size = buf.size();\n    for(; i < full_size; i++)\n      ASSERTEQ(buf[i], fill, \"letterbox mismatch @ %zu\", i);\n  }\n\n  void check_in_frame(const graphics_data &graphics, const char *file) const\n  {\n    size_t frame_w_bytes = frame_w * sizeof(T);\n    size_t y;\n    const T *buf_pos;\n\n#ifdef GENERATE_FILES\n    if(GENERATE)\n    {\n      std::vector<T> out(frame_w * frame_h);\n      T *pix_pos = out.data();\n      buf_pos = pixels();\n      for(y = 0; y < frame_h; y++)\n      {\n        memcpy(pix_pos, buf_pos, frame_w_bytes);\n        buf_pos += full_width - MISALIGN;\n        pix_pos += frame_w;\n      }\n      unit::io::save_tga(out, frame_w, frame_h,\n       graphics.flat_intensity_palette, graphics.screen_mode ? 256 : 32, file);\n    }\n#endif\n\n    std::vector<T> expected = unit::io::load_tga<T>(file);\n    const T *exp_pos = expected.data();\n    buf_pos = buf.data() + start_offset;\n\n    for(y = 0; y < frame_h; y++)\n    {\n      ASSERTMEM(buf_pos, exp_pos, frame_w_bytes,\n       \"frame mismatch on line %zu: %s\", y, file);\n      buf_pos += full_width - MISALIGN;\n      exp_pos += frame_w;\n    }\n\n#ifdef GENERATE_FILES\n    FAIL(\"generation code enabled\");\n#endif\n  }\n\n  void check(const graphics_data &graphics, const char *file) const\n  {\n    check_in_frame(graphics, file);\n    check_out_of_frame();\n  }\n};\n\nstatic inline unsigned comp6to5(unsigned c)\n{\n  return c >> 1;\n}\n\nstatic inline unsigned comp6to8(unsigned c)\n{\n  return ((c * 255u) + 32u) / 63u;\n}\n\ntemplate<flat_bpp FLAT>\nstatic inline uint32_t flat_color(unsigned r, unsigned g, unsigned b);\n\n// TGA 16bpp\ntemplate<>\ninline uint32_t flat_color<FLAT16>(unsigned r, unsigned g, unsigned b)\n{\n  r = comp6to5(r);\n  g = comp6to5(g);\n  b = comp6to5(b);\n\n  uint32_t val = 0x8000 | (r << 10u) | (g << 5u) | b;\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n  val = ((val & 0xff) << 8) | (val >> 8);\n#endif\n  return val;\n}\n\n// TGA 32bpp\ntemplate<>\ninline uint32_t flat_color<FLAT32>(unsigned r, unsigned g, unsigned b)\n{\n  r = comp6to8(r);\n  g = comp6to8(g);\n  b = comp6to8(b);\n\n#if PLATFORM_BYTE_ORDER == PLATFORM_BIG_ENDIAN\n  return (b << 24u) | (g << 16u) | (r << 8u) | 0xffu;\n#else\n  return 0xff000000u | (r << 16u) | (g << 8u) | b;\n#endif\n}\n\ntemplate<>\ninline uint32_t flat_color<FLAT_YUY2>(unsigned r, unsigned g, unsigned b)\n{\n  return rgb_to_yuy2(comp6to8(r), comp6to8(g), comp6to8(b));\n}\n\ntemplate<>\ninline uint32_t flat_color<FLAT_UYVY>(unsigned r, unsigned g, unsigned b)\n{\n  return rgb_to_uyvy(comp6to8(r), comp6to8(g), comp6to8(b));\n}\n\ntemplate<>\ninline uint32_t flat_color<FLAT_YVYU>(unsigned r, unsigned g, unsigned b)\n{\n  return rgb_to_yvyu(comp6to8(r), comp6to8(g), comp6to8(b));\n}\n\n/* Preset enough of the graphics data for these tests to work.\n * Currently this requires:\n *\n *   charset (all renderers)\n *   flat_intensity_palette (16/32-bit renderers)\n *   smzx_indices (SMZX renderers)\n *   protected_pal_position (MZX renderers)\n *\n * Additionally, the render_graph* functions require layer data\n * in text_video and render_layer requires an initialized\n * video_layer struct (it does not need to be in the graphics data).\n *\n * The cursor and mouse render functions don't use the graphics data.\n */\nstatic void init_graphics_base(struct graphics_data &graphics)\n{\n  static std::vector<uint8_t> default_chr;\n  static std::vector<uint8_t> edit_chr;\n\n  if(!default_chr.size())\n    default_chr = unit::io::load(\"../../assets/default.chr\");\n  if(!edit_chr.size())\n    edit_chr = unit::io::load(\"../../assets/edit.chr\");\n\n  memcpy(graphics.charset, default_chr.data(), CHARSET_SIZE * CHAR_H);\n  memcpy(graphics.charset + PRO_CH * CHAR_H, edit_chr.data(), CHARSET_SIZE * CHAR_H);\n}\n\ntemplate<flat_bpp FLAT>\nstatic void init_graphics_mzx(struct graphics_data &graphics)\n{\n  graphics.screen_mode = 0;\n  graphics.protected_pal_position = 16;\n  for(size_t i = 0; i < 16; i++)\n  {\n    uint32_t flat = flat_color<FLAT>(palette[i][0], palette[i][1], palette[i][2]);\n    graphics.flat_intensity_palette[i] = flat;\n    graphics.flat_intensity_palette[i + 16] = flat;\n    graphics.flat_intensity_palette[i + 256] = flat;\n  }\n}\n\ntemplate<flat_bpp FLAT>\nstatic void init_graphics_smzx(struct graphics_data &graphics)\n{\n  init_graphics_mzx<FLAT>(graphics);\n  graphics.screen_mode = 3;\n  graphics.protected_pal_position = 256;\n\n  for(size_t i = 0; i < 256; i++)\n  {\n    size_t bg = i >> 4;\n    size_t fg = i & 0x0f;\n    size_t r = fg << 2;\n    size_t b = bg << 2;\n    graphics.flat_intensity_palette[i] = flat_color<FLAT>(r, b, b);\n\n    graphics.smzx_indices[i * 4 + 0] = (bg << 4) | bg;\n    graphics.smzx_indices[i * 4 + 1] = (bg << 4) | fg;\n    graphics.smzx_indices[i * 4 + 2] = (fg << 4) | bg;\n    graphics.smzx_indices[i * 4 + 3] = (fg << 4) | fg;\n  }\n}\n\n#define out(ch,bg,fg) do { \\\n  pos->char_value = (ch) + offset; \\\n  pos->bg_color = (bg); \\\n  pos->fg_color = (fg); \\\n  pos++; \\\n} while(0)\n\nstatic void init_layer_data(struct char_element *dest, int w, int h,\n int offset, int c_offset, int col)\n{\n  struct char_element *pos;\n  int x, y, ch;\n  int bg = (col >> 4) + c_offset;\n  int fg = (col & 0x0f) + c_offset;\n\n  // Border in given color containing all characters:\n  pos = dest;\n  ch = 0;\n  for(y = 0; y < h; y++)\n  {\n    if(y < 2 || h - y <= 2)\n    {\n      for(x = 0; x < w; x++, ch++)\n        out(ch & 255, bg, fg);\n    }\n    else\n    {\n      for(x = 0; x < 4; x++, ch++)\n        out(ch & 255, bg, fg);\n      pos += w - 8;\n      for(x = w - 4; x < w; x++, ch++)\n        out(ch & 255, bg, fg);\n    }\n  }\n\n  // Centered palette display.\n  if(w >= 64 && h >= 22)\n  {\n    pos = dest;\n    pos += w * ((h - 16) / 2);\n    pos += (w - 32) / 2;\n\n    for(y = 0; y < 16; y++)\n    {\n      for(x = 0; x < 16; x++)\n      {\n        out(176, y, x);\n        out(176, y, x);\n      }\n      pos += w - 32;\n    }\n  }\n}\n\nstatic void init_layer(struct video_layer &layer, int w, int h,\n int mode, struct char_element *data)\n{\n  layer.w = w;\n  layer.h = h;\n  layer.transparent_col = -1;\n  layer.mode = mode;\n  layer.data = data;\n}\n\ntemplate<smzx_type MODE, flat_bpp FLAT>\nstruct render_graph_init\n{\n  render_graph_init(struct graphics_data &graphics)\n  {\n    init_graphics_base(graphics);\n    if(MODE == SMZX)\n      init_graphics_smzx<FLAT>(graphics);\n    else\n      init_graphics_mzx<FLAT>(graphics);\n  }\n};\n\ntemplate<smzx_type MODE, flat_bpp FLAT>\nstruct render_layer_init : public render_graph_init<MODE, FLAT>\n{\n  static constexpr int w = 3;\n  static constexpr int h = 2;\n\n  struct video_layer layer_screen{};\n  struct video_layer layer_small{};\n  struct video_layer layer_ui{};\n  // NOTE: GCC 4.8.5 will ICE attempting to value-initialize these\n  // arrays. Do not move the constructor value-initializers here.\n  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58704\n  struct char_element big_data[132 * 43];\n  struct char_element small_data[w * h];\n  struct char_element ui_data[w * h];\n\n  render_layer_init(struct graphics_data &graphics):\n   render_graph_init<MODE, FLAT>(graphics), big_data{}, small_data{}, ui_data{}\n  {\n    init_layer(layer_screen, SCREEN_W, SCREEN_H,\n     graphics.screen_mode, big_data);\n    init_layer(layer_small, w, h, graphics.screen_mode, small_data);\n    init_layer(layer_ui, w, h, 0, ui_data);\n\n    init_layer_data(small_data, w, h, 0, 0, 0x1f);\n    init_layer_data(ui_data, w, h, PRO_CH, 16, 0x2f);\n  }\n};\n\n\n/* pix=8-bit align=32-bit */\nUNITTEST(render_graph8)\n{\n  struct graphics_data graphics{};\n  render_frame<uint8_t> frame(SCREEN_PIX_W, SCREEN_PIX_H);\n\n  uint8_t *pixels = frame.pixels();\n  size_t pitch = frame.pitch();\n\n  SECTION(MZX)\n  {\n    render_graph_init<MZX, FLAT32> d(graphics);\n    init_layer_data(graphics.text_video, SCREEN_W, SCREEN_H, 0, 0, 0x1f);\n\n    render_graph8(pixels, pitch, &graphics, set_colors8_mzx);\n    frame.check(graphics, DIR \"8.tga.gz\");\n  }\n\n  SECTION(MZXProtected)\n  {\n    render_graph_init<MZX, FLAT32> d(graphics);\n    init_layer_data(graphics.text_video, SCREEN_W, SCREEN_H, PRO_CH, 16, 0x2f);\n\n    render_graph8(pixels, pitch, &graphics, set_colors8_mzx);\n    frame.check(graphics, DIR \"8p.tga.gz\");\n  }\n\n  SECTION(SMZX)\n  {\n    render_graph_init<SMZX, FLAT32> d(graphics);\n    init_layer_data(graphics.text_video, SCREEN_W, SCREEN_H, 0, 0, 0x1f);\n\n    render_graph8(pixels, pitch, &graphics, set_colors8_smzx);\n    frame.check(graphics, DIR \"8s.tga.gz\");\n  }\n}\n\n/* pix=16-bit align=32-bit */\nUNITTEST(render_graph16)\n{\n  struct graphics_data graphics{};\n  render_frame<uint16_t> frame(SCREEN_PIX_W, SCREEN_PIX_H);\n\n  uint16_t *pixels = frame.pixels();\n  size_t pitch = frame.pitch();\n\n  SECTION(MZX)\n  {\n    render_graph_init<MZX, FLAT16> d(graphics);\n    init_layer_data(graphics.text_video, SCREEN_W, SCREEN_H, 0, 0, 0x1f);\n\n    render_graph16(pixels, pitch, &graphics, set_colors16_mzx);\n    frame.check(graphics, DIR \"16.tga.gz\");\n  }\n\n  SECTION(MZXProtected)\n  {\n    render_graph_init<MZX, FLAT16> d(graphics);\n    init_layer_data(graphics.text_video, SCREEN_W, SCREEN_H, PRO_CH, 16, 0x2f);\n\n    render_graph16(pixels, pitch, &graphics, set_colors16_mzx);\n    frame.check(graphics, DIR \"16p.tga.gz\");\n  }\n\n  SECTION(SMZX)\n  {\n    render_graph_init<SMZX, FLAT16> d(graphics);\n    init_layer_data(graphics.text_video, SCREEN_W, SCREEN_H, 0, 0, 0x1f);\n\n    render_graph16(pixels, pitch, &graphics, set_colors16_smzx);\n    frame.check(graphics, DIR \"16s.tga.gz\");\n  }\n}\n\nUNITTEST(render_graph16_yuv)\n{\n  struct graphics_data graphics{};\n  render_frame<uint32_t> frame(SCREEN_PIX_W / 2, SCREEN_PIX_H);\n\n  uint16_t *pixels = reinterpret_cast<uint16_t *>(frame.pixels());\n  size_t pitch = frame.pitch();\n\n  SECTION(YUY2)\n  {\n    render_graph_init<MZX, FLAT_YUY2> d(graphics);\n    init_layer_data(graphics.text_video, SCREEN_W, SCREEN_H, 0, 0, 0x1f);\n\n    render_graph16(pixels, pitch, &graphics, yuy2_subsample_set_colors_mzx);\n    frame.check(graphics, DIR \"16yuy2.tga.gz\");\n  }\n\n  SECTION(UYVY)\n  {\n    render_graph_init<MZX, FLAT_UYVY> d(graphics);\n    init_layer_data(graphics.text_video, SCREEN_W, SCREEN_H, 0, 0, 0x1f);\n\n    render_graph16(pixels, pitch, &graphics, uyvy_subsample_set_colors_mzx);\n    frame.check(graphics, DIR \"16uyvy.tga.gz\");\n  }\n\n  SECTION(YVYU)\n  {\n    render_graph_init<MZX, FLAT_YVYU> d(graphics);\n    init_layer_data(graphics.text_video, SCREEN_W, SCREEN_H, 0, 0, 0x1f);\n\n    render_graph16(pixels, pitch, &graphics, yvyu_subsample_set_colors_mzx);\n    frame.check(graphics, DIR \"16yvyu.tga.gz\");\n  }\n}\n\n/* pix=32-bit align=32-bit */\nUNITTEST(render_graph32)\n{\n  struct graphics_data graphics{};\n  render_frame<uint32_t> frame(SCREEN_PIX_W, SCREEN_PIX_H);\n\n  uint32_t *pixels = frame.pixels();\n  size_t pitch = frame.pitch();\n\n  SECTION(MZX)\n  {\n    render_graph_init<MZX, FLAT32> d(graphics);\n    init_layer_data(graphics.text_video, SCREEN_W, SCREEN_H, 0, 0, 0x1f);\n\n    render_graph32(pixels, pitch, &graphics);\n    frame.check(graphics, DIR \"32.tga.gz\");\n  }\n\n  SECTION(MZXProtected)\n  {\n    render_graph_init<MZX, FLAT32> d(graphics);\n    init_layer_data(graphics.text_video, SCREEN_W, SCREEN_H, PRO_CH, 16, 0x2f);\n\n    render_graph32(pixels, pitch, &graphics);\n    frame.check(graphics, DIR \"32p.tga.gz\");\n  }\n\n  SECTION(SMZX)\n  {\n    render_graph_init<SMZX, FLAT32> d(graphics);\n    init_layer_data(graphics.text_video, SCREEN_W, SCREEN_H, 0, 0, 0x1f);\n\n    render_graph32s(pixels, pitch, &graphics);\n    frame.check(graphics, DIR \"32s.tga.gz\");\n  }\n}\n\n\n// Test that render_cursor overwrites the fill color completely with the\n// cursor color at  the specified location and char lines.\ntemplate<typename T,\n unsigned width_ch = 4, unsigned height_ch = 3, uint32_t fill = 0xbabababau>\nstatic void test_render_cursor(unsigned x_ch, unsigned y_ch, uint32_t flatcolor,\n uint8_t lines, uint8_t offset)\n{\n  static constexpr unsigned width_px = width_ch * CHAR_W;\n  static constexpr unsigned height_px = height_ch * CHAR_H;\n  static constexpr unsigned buf_sz = width_px * height_px * sizeof(T) / sizeof(uint32_t);\n  static constexpr unsigned pitch = width_px * sizeof(T);\n  static constexpr unsigned bpp = 8 * sizeof(T);\n\n  T fill_px = static_cast<T>(fill);\n  T flat_px = static_cast<T>(flatcolor);\n\n  ASSERT(x_ch < width_ch, \"bad x\");\n  ASSERT(y_ch < height_ch, \"bad y\");\n\n  uint32_t buffer[buf_sz];\n  for(size_t i = 0; i < buf_sz; i++)\n    buffer[i] = fill;\n\n  render_cursor(buffer, pitch, bpp, x_ch, y_ch, flatcolor, lines, offset);\n\n  T *pos = reinterpret_cast<T *>(buffer);\n  for(unsigned y = 0; y < height_px; y++)\n  {\n    for(unsigned x = 0; x < width_px; x++, pos++)\n    {\n      if(x >= x_ch * CHAR_W && x < (x_ch + 1) * CHAR_W &&\n       y >= y_ch * CHAR_H + offset && y < y_ch * CHAR_H + offset + lines)\n      {\n        ASSERTEQ(*pos, flat_px, \"%u,%u\", x, y);\n      }\n      else\n        ASSERTEQ(*pos, fill_px, \"%u,%u\", x, y);\n    }\n  }\n}\n\nUNITTEST(render_cursor)\n{\n  SECTION(8bpp)\n  {\n    for(size_t y = 0; y < 3; y++)\n    {\n      for(size_t x = 0; x < 4; x++)\n      {\n        test_render_cursor<uint8_t>(x, y, 0x0f0f0f0f, 2, 12);\n        test_render_cursor<uint8_t>(x, y, 0x0f0f0f0f, 14, 0);\n      }\n    }\n  }\n\n  SECTION(16bpp)\n  {\n    for(size_t y = 0; y < 3; y++)\n    {\n      for(size_t x = 0; x < 4; x++)\n      {\n        test_render_cursor<uint16_t>(x, y, 0xabcfabcf, 2, 12);\n        test_render_cursor<uint16_t>(x, y, 0xabcfabcf, 14, 0);\n      }\n    }\n  }\n\n  SECTION(32bpp)\n  {\n    for(size_t y = 0; y < 3; y++)\n    {\n      for(size_t x = 0; x < 4; x++)\n      {\n        test_render_cursor<uint32_t>(x, y, 0x12345678, 2, 12);\n        test_render_cursor<uint32_t>(x, y, 0x12345678, 14, 0);\n      }\n    }\n  }\n}\n\n\n// Test that render_mouse inverts the data at the given (pixel) rectangle\n// according to the invert mask and sets pixels according to the alpha mask.\ntemplate<typename T,\n unsigned width_ch = 4, unsigned height_ch = 3>\nstatic void test_render_mouse(unsigned x_px, unsigned y_px,\n uint32_t mask, uint32_t amask, uint8_t width, uint8_t height)\n{\n  static constexpr unsigned width_px = width_ch * CHAR_W;\n  static constexpr unsigned height_px = height_ch * CHAR_H;\n  static constexpr unsigned buf_sz = width_px * height_px * sizeof(T) / sizeof(uint32_t);\n  static constexpr unsigned pitch = width_px * sizeof(T);\n  static constexpr unsigned bpp = 8 * sizeof(T);\n\n  T mask_px = static_cast<T>(mask);\n  T amask_px = static_cast<T>(amask);\n\n  ASSERT(x_px < width_px, \"bad x\");\n  ASSERT(y_px < height_px, \"bad y\");\n\n  uint32_t buffer[buf_sz];\n  T *pos = reinterpret_cast<T *>(buffer);\n  size_t i;\n  for(i = 0; i < width_px * height_px; i++)\n    *(pos++) = (i & 0xff) * 0x01010101;\n\n  render_mouse(buffer, pitch, bpp, x_px, y_px, mask, amask, width, height);\n\n  pos = reinterpret_cast<T *>(buffer);\n  i = 0;\n  for(unsigned y = 0; y < height_px; y++)\n  {\n    for(unsigned x = 0; x < width_px; x++, pos++, i++)\n    {\n      T expected = static_cast<T>((i & 0xff) * 0x01010101);\n      if(x >= x_px && x < x_px + width && y >= y_px && y < y_px + height)\n        expected = amask_px | (expected ^ mask_px);\n\n      ASSERTEQ(*pos, expected, \"%u,%u\", x, y);\n    }\n  }\n}\n\nUNITTEST(render_mouse)\n{\n  SECTION(8bpp)\n  {\n    for(size_t y = 0; y < 3; y++)\n    {\n      for(size_t x = 0; x < 4; x++)\n      {\n        test_render_mouse<uint8_t>(x * CHAR_W, y * CHAR_H, 0xffffffff, 0, 8, 14);\n        test_render_mouse<uint8_t>(x * CHAR_W, y * CHAR_H, 0xffffffff, 0, 8, 7);\n        test_render_mouse<uint8_t>(x * CHAR_W, y * CHAR_H + 7, 0xffffffff, 0, 8, 7);\n\n        test_render_mouse<uint8_t>(x * CHAR_W, y * CHAR_H, 0x3f3f3f3f, 0xc0c0c0c0, 8, 14);\n      }\n    }\n  }\n\n  SECTION(16bpp)\n  {\n    for(size_t y = 0; y < 3; y++)\n    {\n      for(size_t x = 0; x < 4; x++)\n      {\n        test_render_mouse<uint16_t>(x * CHAR_W, y * CHAR_H, 0x7fff7fff, 0x80008000, 8, 14);\n        test_render_mouse<uint16_t>(x * CHAR_W, y * CHAR_H, 0x7fff7fff, 0x80008000, 8, 7);\n        test_render_mouse<uint16_t>(x * CHAR_W, y * CHAR_H + 7, 0x7fff7fff, 0x80008000, 8, 7);\n      }\n    }\n  }\n\n  SECTION(32bpp)\n  {\n    for(size_t y = 0; y < 3; y++)\n    {\n      for(size_t x = 0; x < 4; x++)\n      {\n        test_render_mouse<uint32_t>(x * CHAR_W, y * CHAR_H, 0x00ffffff, 0xff000000, 8, 14);\n        test_render_mouse<uint32_t>(x * CHAR_W, y * CHAR_H, 0x00ffffff, 0xff000000, 8, 7);\n        test_render_mouse<uint32_t>(x * CHAR_W, y * CHAR_H + 7, 0x00ffffff, 0xff000000, 8, 7);\n      }\n    }\n  }\n}\n\n\nstatic constexpr int SM_W = 32;\nstatic constexpr int SM_H = 20;\nstatic constexpr int XL_W = 132;\nstatic constexpr int XL_H = 43;\n\ntemplate<typename T, smzx_type MODE, flat_bpp FLAT, int ALLOW_GENERATE = 1,\n render_layer_fp my_render_layer = render_layer>\nclass render_layer_tester\n{\npublic:\n/* Test rendering text_video data.\n * Output should be indistinguishable from render_graph with the same input. */\nstatic void graphic(const char *path)\n{\n  struct graphics_data graphics{};\n  render_frame<T, 0, 0> frame(SCREEN_PIX_W, SCREEN_PIX_H);\n\n  T *pixels = frame.pixels();\n  size_t pitch = frame.pitch();\n  size_t bpp = frame.bpp();\n\n  render_layer_init<MODE, FLAT> d(graphics);\n  init_layer_data(d.big_data, SCREEN_W, SCREEN_H, 0, 0, 0x1f);\n\n  my_render_layer(pixels, SCREEN_PIX_W, SCREEN_PIX_H,\n   pitch, bpp, &graphics, &d.layer_screen);\n\n  frame.check(graphics, path);\n}\n\n/* Test rendering layers at various alignments with or without clipping.\n * If TR=1, the layers will use transparent colors. */\ntemplate<int TR, int CLIP, int MISALIGN>\nstatic void layer_common(unsigned screen_w, unsigned screen_h,\n unsigned fill_color, const char *path)\n{\n  constexpr int GENERATE = !MISALIGN && ALLOW_GENERATE;\n\n  struct graphics_data graphics{};\n  size_t i;\n  int width_px = screen_w * CHAR_W;\n  int height_px = screen_h * CHAR_H;\n\n  render_frame<T, MISALIGN, GENERATE> frame(width_px, height_px);\n\n  T *pixels = frame.pixels();\n  size_t pitch = frame.pitch();\n  size_t bpp = frame.bpp();\n\n  render_layer_init<MODE, FLAT> d(graphics);\n\n  // Fill black\n  d.layer_screen.w = screen_w;\n  d.layer_screen.h = screen_h;\n  init_layer_data(d.big_data, screen_w, screen_h, 0, 0, fill_color);\n  my_render_layer(pixels, width_px, height_px,\n   pitch, bpp, &graphics, &d.layer_screen);\n\n  for(i = 0; i < 8; i++)\n  {\n    // no-clip: gradually increase misalignment for each of the 8 renders.\n    int x1 = (i * 25) + CHAR_W * 3;\n    int x2 = x1;\n    int x3 = x1;\n    int x4 = x1;\n    int y1 = (i * 1) + CHAR_H * 2;\n    int y2 = y1 + CHAR_H * 4;\n    int y3 = y1 + CHAR_H * 8;\n    int y4 = y1 + CHAR_H * 12;\n\n    // clip: same, except relocate each set to one of the four edges.\n    if(CLIP)\n    {\n      // top edge\n      y1 = (i * 1) - CHAR_H;\n      // left edge\n      x2 = (i * 1) - CHAR_W;\n      y2 = (i * 29) + CHAR_H;\n      // bottom edge\n      y3 = (i * 1) + (screen_h - 1) * CHAR_H - 7;\n      // right edge\n      x4 = (i * 1) + (screen_w - 2) * CHAR_W;\n      y4 = y2;\n    }\n\n    d.layer_small.x = x1;\n    d.layer_small.y = y1;\n    d.layer_small.transparent_col = TR ? (MODE ? 0x11 : 1) : -1;\n    my_render_layer(pixels, width_px, height_px,\n     pitch, bpp, &graphics, &d.layer_small);\n\n    d.layer_small.x = x2;\n    d.layer_small.y = y2;\n    d.layer_small.transparent_col = TR ? (MODE ? 0xff : 15) : -1;\n    my_render_layer(pixels, width_px, height_px,\n     pitch, bpp, &graphics, &d.layer_small);\n\n    if(!CLIP)\n    {\n      // Test UI rendering.\n      d.layer_ui.x = x3;\n      d.layer_ui.y = y3;\n      d.layer_ui.transparent_col = TR ? graphics.protected_pal_position + 2 : -1;\n      my_render_layer(pixels, width_px, height_px,\n       pitch, bpp, &graphics, &d.layer_ui);\n\n      d.layer_ui.x = x4;\n      d.layer_ui.y = y4;\n      d.layer_ui.transparent_col = TR ? graphics.protected_pal_position + 15 : -1;\n      my_render_layer(pixels, width_px, height_px,\n       pitch, bpp, &graphics, &d.layer_ui);\n    }\n    else\n    {\n      // Test normal clipping at bottom and right edges.\n      d.layer_small.x = x3;\n      d.layer_small.y = y3;\n      d.layer_small.transparent_col = TR ? (MODE ? 0x11 : 1) : -1;\n      my_render_layer(pixels, width_px, height_px,\n       pitch, bpp, &graphics, &d.layer_small);\n\n      d.layer_small.x = x4;\n      d.layer_small.y = y4;\n      d.layer_small.transparent_col = TR ? (MODE ? 0xff : 15) : -1;\n      my_render_layer(pixels, width_px, height_px,\n       pitch, bpp, &graphics, &d.layer_small);\n    }\n  }\n\n  if(CLIP)\n  {\n    // Corner clipping test.\n    d.layer_small.transparent_col = TR ? (MODE ? 0x11 : 1) : -1;\n    for(i = 0; i < 4; i++)\n    {\n      constexpr int show_x = CHAR_W * 2 - 1;\n      constexpr int show_y = CHAR_H * 1 - 7;\n      d.layer_small.x = (i & 1) ? width_px - show_x : show_x - CHAR_W * 3;\n      d.layer_small.y = (i & 2) ? height_px - show_y : show_y - CHAR_H * 2;\n      my_render_layer(pixels, width_px, height_px,\n       pitch, bpp, &graphics, &d.layer_small);\n    }\n  }\n\n  frame.check(graphics, path);\n}\n\n/* Test rendering layers at various alignments without clipping.\n * If TR=1, the layers will use transparent colors. */\nstatic void align(const char *path)\n{\n  layer_common<0, 0, 0>(SM_W, SM_H, 0, path);\n}\nstatic void align_tr(const char *path)\n{\n  layer_common<1, 0, 0>(SM_W, SM_H, 0, path);\n}\n/* Same, but with clipping. */\nstatic void clip(const char *path)\n{\n  layer_common<0, 1, 0>(SM_W, SM_H, 0, path);\n}\nstatic void clip_tr(const char *path)\n{\n  layer_common<1, 1, 0>(SM_W, SM_H, 0, path);\n}\n\n/* Test rendering layers at various alignments without clipping.\n * The buffer will be misaligned by one pixel to test that\n * the layer renderer selects an appropriately misaligned (1PPW) renderer.\n * If TR=1, the layers will use transparent colors. */\nstatic void misalign(const char *path)\n{\n  layer_common<0, 0, 1>(SM_W, SM_H, 0, path);\n}\nstatic void misalign_tr(const char *path)\n{\n  layer_common<1, 0, 1>(SM_W, SM_H, 0, path);\n}\n/* Same, but with clipping. */\nstatic void misclip(const char *path)\n{\n  layer_common<0, 1, 1>(SM_W, SM_H, 0, path);\n}\nstatic void misclip_tr(const char *path)\n{\n  layer_common<1, 1, 1>(SM_W, SM_H, 0, path);\n}\n\n/* Test rendering layers to a large destination at various alignments\n * with clipping. If TR=1, the layers will use transparent colors. */\nstatic void large(const char *path)\n{\n  layer_common<0, 1, 0>(XL_W, XL_H, 0x5f, path);\n}\n};\n\n/* These tests use the default render_layer dispatch path, and may select\n * any valid renderer as long as it renders correctly and doesn't crash.\n * Execution here is likely redundant with one of the tests below that\n * explicitly choose aligned/unaligned/vector rendering; this just makes\n * sure the actual dispatch that MegaZeux will execute.\n */\nUNITTEST(render_layer_mzx8)\n{\n#ifdef SKIP_8BPP\n  SKIP();\n#else\n  using test = render_layer_tester<uint8_t, MZX, FLAT32>;\n  SECTION(graphic)      test::graphic(DIR \"8.tga.gz\");\n  SECTION(align)        test::align(DIR \"8a.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"8ta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"8c.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"8tc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"8a.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"8ta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"8c.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"8tc.tga.gz\");\n  SECTION(large)        test::large(DIR \"8xl.tga.gz\");\n#endif\n}\n\nUNITTEST(render_layer_smzx8)\n{\n#ifdef SKIP_8BPP\n  SKIP();\n#else\n  using test = render_layer_tester<uint8_t, SMZX, FLAT32>;\n  SECTION(graphic)      test::graphic(DIR \"8s.tga.gz\");\n  SECTION(align)        test::align(DIR \"8sa.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"8sta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"8sc.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"8stc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"8sa.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"8sta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"8sc.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"8stc.tga.gz\");\n  SECTION(large)        test::large(DIR \"8sxl.tga.gz\");\n#endif\n}\n\nUNITTEST(render_layer_mzx16)\n{\n#ifdef SKIP_16BPP\n  SKIP();\n#else\n  using test = render_layer_tester<uint16_t, MZX, FLAT16>;\n  SECTION(graphic)      test::graphic(DIR \"16.tga.gz\");\n  SECTION(align)        test::align(DIR \"16a.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"16ta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"16c.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"16tc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"16a.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"16ta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"16c.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"16tc.tga.gz\");\n  SECTION(large)        test::large(DIR \"16xl.tga.gz\");\n#endif\n}\n\nUNITTEST(render_layer_smzx16)\n{\n#ifdef SKIP_16BPP\n  SKIP();\n#else\n  using test = render_layer_tester<uint16_t, SMZX, FLAT16>;\n  SECTION(graphic)      test::graphic(DIR \"16s.tga.gz\");\n  SECTION(align)        test::align(DIR \"16sa.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"16sta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"16sc.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"16stc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"16sa.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"16sta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"16sc.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"16stc.tga.gz\");\n  SECTION(large)        test::large(DIR \"16sxl.tga.gz\");\n#endif\n}\n\nUNITTEST(render_layer_mzx32)\n{\n  using test = render_layer_tester<uint32_t, MZX, FLAT32>;\n  SECTION(graphic)      test::graphic(DIR \"32.tga.gz\");\n  SECTION(align)        test::align(DIR \"32a.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"32ta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"32c.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"32tc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"32a.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"32ta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"32c.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"32tc.tga.gz\");\n  SECTION(large)        test::large(DIR \"32xl.tga.gz\");\n}\n\nUNITTEST(render_layer_smzx32)\n{\n  using test = render_layer_tester<uint32_t, SMZX, FLAT32>;\n  SECTION(graphic)      test::graphic(DIR \"32s.tga.gz\");\n  SECTION(align)        test::align(DIR \"32sa.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"32sta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"32sc.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"32stc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"32sa.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"32sta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"32sc.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"32stc.tga.gz\");\n  SECTION(large)        test::large(DIR \"32sxl.tga.gz\");\n}\n\n/* Manual layer renderer dispatch to ensure every valid alignment combination\n * is instantiated. Calls to render_layer may instead dispatch to unaligned\n * or vector renderers--making the lower-alignment renderers impossible to\n * reach--so the regular tests aren't sufficient for coverage.\n */\nstatic void render_layer_aligned(void * RESTRICT pixels,\n size_t width_px, size_t height_px, size_t pitch, int bpp,\n const struct graphics_data *graphics, const struct video_layer *layer)\n{\n  int smzx = 0;\n  int trans = 0;\n  int align = 0;\n  int clip = 0;\n  select_aligned_renderer(pixels, width_px, height_px, pitch, graphics, layer,\n   bpp, align, smzx, trans, clip);\n\n  render_layer_func(pixels, width_px, height_px, pitch, graphics, layer,\n   bpp, align, smzx, trans, clip);\n}\n\nUNITTEST(render_layer_mzx8_aligned)\n{\n#ifdef SKIP_8BPP\n  SKIP();\n#else\n  using test = render_layer_tester<uint8_t, MZX, FLAT32, 0, render_layer_aligned>;\n  SECTION(graphic)      test::graphic(DIR \"8.tga.gz\");\n  SECTION(align)        test::align(DIR \"8a.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"8ta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"8c.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"8tc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"8a.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"8ta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"8c.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"8tc.tga.gz\");\n  SECTION(large)        test::large(DIR \"8xl.tga.gz\");\n#endif\n}\n\nUNITTEST(render_layer_smzx8_aligned)\n{\n#ifdef SKIP_8BPP\n  SKIP();\n#else\n  using test = render_layer_tester<uint8_t, SMZX, FLAT32, 0, render_layer_aligned>;\n  SECTION(graphic)      test::graphic(DIR \"8s.tga.gz\");\n  SECTION(align)        test::align(DIR \"8sa.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"8sta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"8sc.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"8stc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"8sa.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"8sta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"8sc.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"8stc.tga.gz\");\n  SECTION(large)        test::large(DIR \"8sxl.tga.gz\");\n#endif\n}\n\nUNITTEST(render_layer_mzx16_aligned)\n{\n#ifdef SKIP_16BPP\n  SKIP();\n#else\n  using test = render_layer_tester<uint16_t, MZX, FLAT16, 0, render_layer_aligned>;\n  SECTION(graphic)      test::graphic(DIR \"16.tga.gz\");\n  SECTION(align)        test::align(DIR \"16a.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"16ta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"16c.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"16tc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"16a.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"16ta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"16c.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"16tc.tga.gz\");\n  SECTION(large)        test::large(DIR \"16xl.tga.gz\");\n#endif\n}\n\nUNITTEST(render_layer_smzx16_aligned)\n{\n#ifdef SKIP_16BPP\n  SKIP();\n#else\n  using test = render_layer_tester<uint16_t, SMZX, FLAT16, 0, render_layer_aligned>;\n  SECTION(graphic)      test::graphic(DIR \"16s.tga.gz\");\n  SECTION(align)        test::align(DIR \"16sa.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"16sta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"16sc.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"16stc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"16sa.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"16sta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"16sc.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"16stc.tga.gz\");\n  SECTION(large)        test::large(DIR \"16sxl.tga.gz\");\n#endif\n}\n\nUNITTEST(render_layer_mzx32_aligned)\n{\n  using test = render_layer_tester<uint32_t, MZX, FLAT32, 0, render_layer_aligned>;\n  SECTION(graphic)      test::graphic(DIR \"32.tga.gz\");\n  SECTION(align)        test::align(DIR \"32a.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"32ta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"32c.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"32tc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"32a.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"32ta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"32c.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"32tc.tga.gz\");\n  SECTION(large)        test::large(DIR \"32xl.tga.gz\");\n}\n\nUNITTEST(render_layer_smzx32_aligned)\n{\n  using test = render_layer_tester<uint32_t, SMZX, FLAT32, 0, render_layer_aligned>;\n  SECTION(graphic)      test::graphic(DIR \"32s.tga.gz\");\n  SECTION(align)        test::align(DIR \"32sa.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"32sta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"32sc.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"32stc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"32sa.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"32sta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"32sc.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"32stc.tga.gz\");\n  SECTION(large)        test::large(DIR \"32sxl.tga.gz\");\n}\n\n#if defined(PLATFORM_UNALIGN_32) || defined(PLATFORM_UNALIGN_64)\n/* Manual layer renderer dispatch to ensure every unaligned renderer is\n * used for every alignment combination. Calls to render_layer may instead\n * dispatch to vector renderers, so the regular tests aren't sufficient for\n * coverage.\n */\nstatic void render_layer_unaligned(void * RESTRICT pixels,\n size_t width_px, size_t height_px, size_t pitch, int bpp,\n const struct graphics_data *graphics, const struct video_layer *layer)\n{\n  int smzx = 0;\n  int trans = 0;\n  int align = 0;\n  int clip = 0;\n  select_aligned_renderer(pixels, width_px, height_px, pitch, graphics, layer,\n   bpp, align, smzx, trans, clip);\n\n  select_unaligned_renderer(align);\n\n  render_layer_func(pixels, width_px, height_px, pitch, graphics, layer,\n   bpp, align, smzx, trans, clip);\n}\n\nUNITTEST(render_layer_mzx8_unaligned)\n{\n#ifdef SKIP_8BPP\n  SKIP();\n#else\n  using test = render_layer_tester<uint8_t, MZX, FLAT32, 0, render_layer_unaligned>;\n  SECTION(graphic)      test::graphic(DIR \"8.tga.gz\");\n  SECTION(align)        test::align(DIR \"8a.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"8ta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"8c.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"8tc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"8a.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"8ta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"8c.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"8tc.tga.gz\");\n  SECTION(large)        test::large(DIR \"8xl.tga.gz\");\n#endif\n}\n\nUNITTEST(render_layer_smzx8_unaligned)\n{\n#ifdef SKIP_8BPP\n  SKIP();\n#else\n  using test = render_layer_tester<uint8_t, SMZX, FLAT32, 0, render_layer_unaligned>;\n  SECTION(graphic)      test::graphic(DIR \"8s.tga.gz\");\n  SECTION(align)        test::align(DIR \"8sa.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"8sta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"8sc.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"8stc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"8sa.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"8sta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"8sc.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"8stc.tga.gz\");\n  SECTION(large)        test::large(DIR \"8sxl.tga.gz\");\n#endif\n}\n\nUNITTEST(render_layer_mzx16_unaligned)\n{\n#ifdef SKIP_16BPP\n  SKIP();\n#else\n  using test = render_layer_tester<uint16_t, MZX, FLAT16, 0, render_layer_unaligned>;\n  SECTION(graphic)      test::graphic(DIR \"16.tga.gz\");\n  SECTION(align)        test::align(DIR \"16a.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"16ta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"16c.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"16tc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"16a.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"16ta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"16c.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"16tc.tga.gz\");\n  SECTION(large)        test::large(DIR \"16xl.tga.gz\");\n#endif\n}\n\nUNITTEST(render_layer_smzx16_unaligned)\n{\n#ifdef SKIP_16BPP\n  SKIP();\n#else\n  using test = render_layer_tester<uint16_t, SMZX, FLAT16, 0, render_layer_unaligned>;\n  SECTION(graphic)      test::graphic(DIR \"16s.tga.gz\");\n  SECTION(align)        test::align(DIR \"16sa.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"16sta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"16sc.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"16stc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"16sa.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"16sta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"16sc.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"16stc.tga.gz\");\n  SECTION(large)        test::large(DIR \"16sxl.tga.gz\");\n#endif\n}\n\nUNITTEST(render_layer_mzx32_unaligned)\n{\n  using test = render_layer_tester<uint32_t, MZX, FLAT32, 0, render_layer_unaligned>;\n  SECTION(graphic)      test::graphic(DIR \"32.tga.gz\");\n  SECTION(align)        test::align(DIR \"32a.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"32ta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"32c.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"32tc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"32a.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"32ta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"32c.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"32tc.tga.gz\");\n  SECTION(large)        test::large(DIR \"32xl.tga.gz\");\n}\n\nUNITTEST(render_layer_smzx32_unaligned)\n{\n  using test = render_layer_tester<uint32_t, SMZX, FLAT32, 0, render_layer_unaligned>;\n  SECTION(graphic)      test::graphic(DIR \"32s.tga.gz\");\n  SECTION(align)        test::align(DIR \"32sa.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"32sta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"32sc.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"32stc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"32sa.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"32sta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"32sc.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"32stc.tga.gz\");\n  SECTION(large)        test::large(DIR \"32sxl.tga.gz\");\n}\n#endif /* PLATFORM_UNALIGN_32 || PLATFORM_UNALIGN_64 */\n\nstatic void reference_renderer_wrap(void * RESTRICT pixels,\n size_t width_px, size_t height_px, size_t pitch, int bpp,\n const struct graphics_data *graphics, const struct video_layer *layer)\n{\n  ASSERTEQ(bpp, 32, \"reference renderer is 32-bit only\");\n  reference_renderer(reinterpret_cast<uint32_t * RESTRICT>(pixels),\n   width_px, height_px, pitch, graphics, layer);\n}\n\nUNITTEST(reference_renderer_mzx32)\n{\n  using test = render_layer_tester<uint32_t, MZX, FLAT32, 0, reference_renderer_wrap>;\n  SECTION(graphic)      test::graphic(DIR \"32.tga.gz\");\n  SECTION(align)        test::align(DIR \"32a.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"32ta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"32c.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"32tc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"32a.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"32ta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"32c.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"32tc.tga.gz\");\n  SECTION(large)        test::large(DIR \"32xl.tga.gz\");\n}\n\nUNITTEST(reference_renderer_smzx32)\n{\n  using test = render_layer_tester<uint32_t, SMZX, FLAT32, 0, reference_renderer_wrap>;\n  SECTION(graphic)      test::graphic(DIR \"32s.tga.gz\");\n  SECTION(align)        test::align(DIR \"32sa.tga.gz\");\n  SECTION(align_tr)     test::align_tr(DIR \"32sta.tga.gz\");\n  SECTION(clip)         test::clip(DIR \"32sc.tga.gz\");\n  SECTION(clip_tr)      test::clip_tr(DIR \"32stc.tga.gz\");\n  SECTION(misalign)     test::misalign(DIR \"32sa.tga.gz\");\n  SECTION(misalign_tr)  test::misalign_tr(DIR \"32sta.tga.gz\");\n  SECTION(misclip)      test::misclip(DIR \"32sc.tga.gz\");\n  SECTION(misclip_tr)   test::misclip_tr(DIR \"32stc.tga.gz\");\n  SECTION(large)        test::large(DIR \"32sxl.tga.gz\");\n}\n\nstruct viewport_test\n{\n  struct\n  {\n    int w;\n    int h;\n#define STRETCH 0, 0\n#define MODERN_64_35 64, 35\n#define CLASSIC_4_3 4, 3\n// GX stretched widescreen ratios (4:3 pixel aspect ratio).\n#define MODERN_64_35_GX (64 * 3), (35 * 4)\n#define CLASSIC_4_3_GX (4 * 3), (3 * 4)\n    int n;\n    int d;\n  } in;\n\n  struct\n  {\n    int x;\n    int y;\n    int w;\n    int h;\n    boolean is_integer_scaled;\n  } out;\n};\n\ntemplate<void (*set_viewport_fn)(struct graphics_data *graphics,\n struct video_window *window)>\nstatic void test_set_window_viewport(struct graphics_data *graphics,\n struct video_window *window, const viewport_test &test)\n{\n  window->width_px = test.in.w;\n  window->height_px = test.in.h;\n  window->ratio_numerator = test.in.n;\n  window->ratio_denominator = test.in.d;\n\n  set_viewport_fn(graphics, window);\n\n#undef OUT_STR\n#define OUT_STR \"%d, %d (%d:%d)\", test.in.w, test.in.h, test.in.n, test.in.d\n\n  ASSERTEQ(window->viewport_x, test.out.x, OUT_STR);\n  ASSERTEQ(window->viewport_y, test.out.y, OUT_STR);\n  ASSERTEQ(window->viewport_width, test.out.w, OUT_STR);\n  ASSERTEQ(window->viewport_height, test.out.h, OUT_STR);\n  ASSERTEQ(window->is_integer_scaled, test.out.is_integer_scaled, OUT_STR);\n}\n\n/* Screen-sized viewport, centered regardless of window size. */\nUNITTEST(set_window_viewport_centered)\n{\n  static constexpr viewport_test data[] =\n  {\n    { { 640, 350, MODERN_64_35 },     { 0, 0, 640, 350, true } },\n    { { 640, 480, CLASSIC_4_3 },      { 0, 65, 640, 350, true } },\n    { { 640, 350, CLASSIC_4_3 },      { 0, 0, 640, 350, true } },\n    { { 640, 350, MODERN_64_35_GX },  { 0, 0, 640, 350, true } },\n    { { 640, 350, CLASSIC_4_3_GX },   { 0, 0, 640, 350, true } },\n    { { 640, 350, STRETCH },          { 0, 0, 640, 350, true } },\n    { { 640, 350, 1, 0 },             { 0, 0, 640, 350, true } },\n    { { 640, 350, 0, 1 },             { 0, 0, 640, 350, true } },\n    { { 640, 350, -16, -32 },         { 0, 0, 640, 350, true } },\n    { { 1280, 350, MODERN_64_35 },    { 320, 0, 640, 350, true }},\n    { { 1280, 700, MODERN_64_35 },    { 320, 175, 640, 350, true } },\n    { { 320, 240, MODERN_64_35 },     { -160, -55, 640, 350, true } },\n    { { 0, 0, MODERN_64_35 },         { -320, -175, 640, 350, true } },\n  };\n  struct graphics_data graphics{};\n  struct video_window window{};\n\n  for(auto &d : data)\n    test_set_window_viewport<set_window_viewport_centered>(&graphics, &window, d);\n}\n\n/* Viewport scaled to fit the window according to the requested ratio. */\nUNITTEST(set_window_viewport_scaled)\n{\n  static constexpr viewport_test data[] =\n  {\n    { { 640, 350, MODERN_64_35 },     { 0, 0, 640, 350, true } },\n    { { 640, 480, CLASSIC_4_3 },      { 0, 0, 640, 480, false } },\n    { { 640, 560, STRETCH },          { 0, 0, 640, 560, false } },\n    { { 640, 480, MODERN_64_35_GX },  { 0, 7, 640, 466, false } },\n    { { 640, 640, CLASSIC_4_3_GX },   { 0, 0, 640, 640, false } },\n    { { 640, 480, 1, 0 },             { 0, 0, 640, 480, false } },\n    { { 640, 480, 0, 1 },             { 0, 0, 640, 480, false } },\n    { { 640, 480, -20, -10 },         { 0, 0, 640, 480, false } },\n    { { 1280, 350, MODERN_64_35 },    { 320, 0, 640, 350, true } },\n    { { 1280, 350, STRETCH },         { 0, 0, 1280, 350, true } },\n    { { 1280, 700, MODERN_64_35 },    { 0, 0, 1280, 700, true } },\n    { { 1280, 1024, MODERN_64_35 },   { 0, 162, 1280, 700, true } },\n    { { 1366, 768, MODERN_64_35 },    { 0, 10, 1366, 747, false } },\n    { { 1366, 768, CLASSIC_4_3 },     { 171, 0, 1024, 768, false } },\n    { { 1366, 768, STRETCH },         { 0, 0, 1366, 768, false } },\n    { { 1366, 768, CLASSIC_4_3_GX },  { 299, 0, 768, 768, false } },\n    { { 320, 240, MODERN_64_35 },     { 0, 32, 320, 175, false } },\n    { { 320, 240, CLASSIC_4_3 },      { 0, 0, 320, 240, false } },\n    { { 320, 240, CLASSIC_4_3_GX },   { 40, 0, 240, 240, false } },\n    { { 1920, 1080, MODERN_64_35 },   { 0, 15, 1920, 1050, true } },\n    { { 1920, 1080, CLASSIC_4_3 },    { 240, 0, 1440, 1080, false } },\n    { { 1920, 1080, STRETCH },        { 0, 0, 1920, 1080, false } },\n    { { 0, 0, MODERN_64_35 },         { -1, -1, 1, 1, false } },\n    { { 25600, 16800, CLASSIC_4_3 },  { 1600, 0, 22400, 16800, true } },\n  };\n  struct graphics_data graphics{};\n  struct video_window window{};\n\n  for(auto &d : data)\n    test_set_window_viewport<set_window_viewport_scaled>(&graphics, &window, d);\n}\n\nstruct viewport_coords_test\n{\n  struct\n  {\n    int x;\n    int y;\n    int w;\n    int h;\n  } viewport;\n\n  struct\n  {\n    int x;\n    int y;\n  } in;\n\n  struct\n  {\n    int x;\n    int y;\n  } out;\n};\n\n/* Window space -> screen space conversion for updating the logical mouse position.\n * Also returns the minimum and maximum viewport coordinates. */\nUNITTEST(get_screen_coords_viewport)\n{\n  static constexpr viewport_coords_test data[] =\n  {\n    { { 0, 0, 640, 350 }, { 456, 123 }, { 456, 123 } },\n    /* Clamp coordinates within the logical screen. */\n    { { 0, 0, 640, 350 }, { -1, -1 }, { 0, 0 } },\n    { { 0, 0, 640, 350 }, { 1000, 1000 }, { 639, 349 } },\n    /* Downscale coordinates of 2x window */\n    { { 0, 0, 1280, 700 }, { 1234, 456 }, { 617, 228 } },\n    { { 0, 0, 1280, 700 }, { 1279, 699 }, { 639, 349 } },\n    /* Upscale coordinates of GP2X half-size 4:3 window. */\n    { { 0, 0, 320, 240 }, { 160, 120 }, { 320, 175 } },\n    { { 0, 0, 320, 240 }, { 319, 239 }, { 638, 348 } },\n    /* 1920x1080 with 64:35 (15px letterbox top and bottom). */\n    { { 0, 15, 1920, 1050 }, { 0, 0 }, { 0, 0 } },\n    { { 0, 15, 1920, 1050 }, { 1600, 1079 }, { 533, 349 } },\n    { { 0, 15, 1920, 1050 }, { 1919, 17 }, { 639, 0 } },\n    { { 0, 15, 1920, 1050 }, { 1919, 18 }, { 639, 1 } },\n    { { 0, 15, 1920, 1050 }, { 1919, 20 }, { 639, 1 } },\n    { { 0, 15, 1920, 1050 }, { 1919, 21 }, { 639, 2 } },\n  };\n  struct graphics_data graphics{};\n  struct video_window window{};\n\n  for(auto &d : data)\n  {\n    window.viewport_x = d.viewport.x;\n    window.viewport_y = d.viewport.y;\n    window.viewport_width = d.viewport.w;\n    window.viewport_height = d.viewport.h;\n\n#define NO_VALUE -65536\n    int x = NO_VALUE;\n    int y = NO_VALUE;\n    int min_x = NO_VALUE;\n    int min_y = NO_VALUE;\n    int max_x = NO_VALUE;\n    int max_y = NO_VALUE;\n    get_screen_coords_viewport(&graphics, &window, d.in.x, d.in.y,\n     &x, &y, &min_x, &min_y, &max_x, &max_y);\n\n#undef OUT_STR\n#define OUT_STR \"%d,%d { %d,%d %dx%d }\", d.in.x, d.in.y, \\\n d.viewport.x, d.viewport.y, d.viewport.w, d.viewport.h\n\n    ASSERTEQ(x, d.out.x, OUT_STR);\n    ASSERTEQ(y, d.out.y, OUT_STR);\n    ASSERTEQ(min_x, d.viewport.x, OUT_STR);\n    ASSERTEQ(min_y, d.viewport.y, OUT_STR);\n    ASSERTEQ(max_x, d.viewport.x + d.viewport.w - 1, OUT_STR);\n    ASSERTEQ(max_y, d.viewport.y + d.viewport.h - 1, OUT_STR);\n  }\n}\n\n/* Screen space -> window space conversion for warping the system mouse. */\nUNITTEST(set_screen_coords_viewport)\n{\n  static constexpr viewport_coords_test data[] =\n  {\n    { { 0, 0, 640, 350 }, { 456, 123 }, { 456, 123 } },\n    /* No clamping within the window currently */\n    { { 0, 0, 640, 350 }, { -1, -1 }, { -1, -1 } },\n    { { 0, 0, 640, 350 }, { 1000, 1000 }, { 1000, 1000 } },\n    /* Upscale coordinates of 2x window. */\n    { { 0, 10, 1280, 700 }, { 320, 175 }, { 640, 360 } },\n    { { 0, 100, 1280, 700 }, { 320, 175 }, { 640, 450 } },\n    { { 0, 0, 1280, 700 }, { 639, 349 }, { 1278, 698 } },\n    /* Downscale coordinates of GP2X half-size 4:3 window. */\n    { { 0, 0, 320, 240 }, { 320, 175 }, { 160, 120 } },\n    { { 0, 0, 320, 240 }, { 639, 349 }, { 319, 239 } },\n    { { 160, 120, 320, 240 }, { -2, -2 }, { 159, 119 } },\n    { { 160, 120, 320, 240 }, { -1, -1 }, { 160, 120 } },\n    { { 160, 120, 320, 240 }, { 0, 0 }, { 160, 120 } },\n    { { 160, 120, 320, 240 }, { 1, 1 }, { 160, 120 } },\n    { { 160, 120, 320, 240 }, { 2, 2 }, { 161, 121 } },\n    /* Upscale coordinates of 1920x1080 (3x) window. */\n    { { 0, 15, 1920, 1050 }, { 0, 0 }, { 0, 15 } },\n    { { 0, 15, 1920, 1050 }, { 320, 175 }, { 960, 540 } },\n    { { 0, 15, 1920, 1050 }, { 639, 349 }, { 1917, 1062 } },\n  };\n  struct graphics_data graphics{};\n  struct video_window window{};\n\n  for(auto &d : data)\n  {\n    window.viewport_x = d.viewport.x;\n    window.viewport_y = d.viewport.y;\n    window.viewport_width = d.viewport.w;\n    window.viewport_height = d.viewport.h;\n\n    int x = NO_VALUE;\n    int y = NO_VALUE;\n    set_screen_coords_viewport(&graphics, &window, d.in.x, d.in.y, &x, &y);\n\n    ASSERTEQ(x, d.out.x, OUT_STR);\n    ASSERTEQ(y, d.out.y, OUT_STR);\n  }\n}\n"
  },
  {
    "path": "unit/sfx.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"Unit.hpp\"\n#include \"../src/audio/sfx.h\"\n#include \"../src/world.h\"\n\n#ifdef CONFIG_DEBYTECODE\n#define SAM_S \"(\"\n#define SAM_E \")\"\n#else\n#define SAM_S \"&\"\n#define SAM_E \"&\"\n#endif\n\nstruct auto_free\n{\n  struct sfx_list &sfx_list;\n  auto_free(struct sfx_list &s): sfx_list(s) {}\n  ~auto_free()\n  {\n    sfx_free(&sfx_list);\n  }\n};\n\nstruct input_data\n{\n  int index;\n  char label[16];\n  const char *string;\n};\n\nstruct io_data\n{\n  char label_in[16];\n  const char *string_in;\n  const char *label_out;\n  const char *string_out;\n};\n\nUNITTEST(GetSet)\n{\n  static constexpr io_data data[] =\n  {\n    { \"\",         \"\",             nullptr,      nullptr },\n    { \"Gem\",      \"5c-gec-gec\",   \"Gem\",        \"5c-gec-gec\" },\n    { \"EmptyStr\", \"\",             \"EmptyStr\",   nullptr },\n    { \"\",         \"EmptyLabel\",   nullptr,      \"EmptyLabel\" },\n    { \"TooLongLabel\", \"abc\",      \"TooLongLabe\",\"abc\", },\n    // No SFX conversions are performed by the core SFX functions.\n    { \"duhhh\",    \"&lmao.sam&3c\", \"duhhh\",      \"&lmao.sam&3c\", },\n    { \"a\",        \"&a.sam&3c_abc\",\"a\",          \"&a.sam&3c_abc\", },\n  };\n  static constexpr input_data invalid[] =\n  {\n    { -1, \"negative\", \"negative\", },\n    { -1000000000, \"negative2\", \"negative2\", },\n    { MAX_NUM_SFX, \"past max\", \"past max\", },\n    { 1<<30, \"large\", \"large\", },\n  };\n\n  struct sfx_list sfx_list{};\n  auto_free a(sfx_list);\n  boolean ret;\n  int i = 0;\n\n  SECTION(SetString)\n  {\n    for(auto &d : data)\n    {\n      ret = sfx_set_string(&sfx_list, i, d.string_in, strlen(d.string_in));\n      ASSERT(ret, \"%d: '%s'\", i, d.string_in);\n      if(d.string_out)\n      {\n        ASSERT(sfx_list.list[i], \"%d: '%s'\", i, d.string_in);\n        ASSERTCMP(sfx_list.list[i]->string, d.string_out, \"%d\", i);\n      }\n      else\n        ASSERT(!sfx_list.list || !sfx_list.list[i], \"%d should be null\", i);\n\n      i++;\n    }\n  }\n\n  SECTION(SetLabel)\n  {\n    for(auto &d : data)\n    {\n      ret = sfx_set_label(&sfx_list, i, d.label_in, strlen(d.label_in));\n      ASSERT(ret, \"%d: '%s'\", i, d.label_in);\n      if(d.label_out)\n      {\n        ASSERT(sfx_list.list[i], \"%d: '%s'\", i, d.label_in);\n        ASSERTCMP(sfx_list.list[i]->label, d.label_out, \"%d\", i);\n        ASSERTCMP(sfx_list.list[i]->string, \"\", \"%d\", i);\n      }\n      // Setting the string should retain the label.\n      ret = sfx_set_string(&sfx_list, i, d.string_in, strlen(d.string_in));\n      ASSERT(ret, \"%d: '%s'\", i, d.label_in);\n      if(d.label_out || d.string_out)\n      {\n        ASSERT(sfx_list.list[i], \"%d: '%s'\", i, d.label_in);\n        ASSERTCMP(sfx_list.list[i]->label, d.label_out ? d.label_out : \"\", \"%d\", i);\n        ASSERTCMP(sfx_list.list[i]->string, d.string_out ? d.string_out : \"\", \"%d\", i);\n      }\n      else\n        ASSERT(!sfx_list.list || !sfx_list.list[i], \"%d: '%s'\", i, d.label_in);\n\n      i++;\n    }\n  }\n\n  SECTION(Get)\n  {\n    for(auto &d : data)\n    {\n      ret = sfx_set_label(&sfx_list, i, d.label_in, strlen(d.label_in));\n      ASSERT(ret, \"%d: '%s'\", i, d.label_in);\n      ret = sfx_set_string(&sfx_list, i, d.string_in, strlen(d.string_in));\n      ASSERT(ret, \"%d: '%s'\", i, d.label_in);\n\n      if(sfx_list.list)\n      {\n        auto *p = sfx_get(&sfx_list, i);\n        ASSERTEQ(sfx_list.list[i], p, \"%d: '%s'\", i, d.label_in);\n      }\n      i++;\n    }\n  }\n\n  SECTION(Unset)\n  {\n    // Only set strings.\n    for(i = 0; i < 32; i++)\n    {\n      ret = sfx_set_string(&sfx_list, i, \"abc\", 3);\n      ASSERT(ret, \"%d\", i);\n      ASSERT(sfx_list.list, \"%d\", i);\n      ASSERT(sfx_list.list[i], \"%d\", i);\n    }\n    // Only set labels.\n    for(; i < 64; i++)\n    {\n      ret = sfx_set_label(&sfx_list, i, \"def\", 3);\n      ASSERT(ret, \"%d\", i);\n      ASSERT(sfx_list.list, \"%d\", i);\n      ASSERT(sfx_list.list[i], \"%d\", i);\n    }\n    // Should unset either...\n    for(i = 0; i < 64; i++)\n    {\n      sfx_unset(&sfx_list, i);\n      ASSERT(!sfx_list.list[i], \"%d\", i);\n    }\n  }\n\n  SECTION(Invalid)\n  {\n    auto *n = sfx_get(&sfx_list, 0);\n    ASSERT(!n, \"get from NULL sfx list\");\n\n    for(auto &d : invalid)\n    {\n      ret = sfx_set_string(&sfx_list, d.index, d.string, strlen(d.string));\n      ASSERTEQ(ret, 0, \"%d: '%s'\", d.index, d.string);\n      ret = sfx_set_label(&sfx_list, d.index, d.label, strlen(d.label));\n      ASSERTEQ(ret, 0, \"%d: '%s'\", d.index, d.string);\n      auto *p = sfx_get(&sfx_list, d.index);\n      ASSERT(!p, \"%d: '%s'\", d.index, d.string);\n    }\n  }\n\n  sfx_free(&sfx_list);\n  ASSERT(!sfx_list.list, \"\");\n  ASSERTEQ(sfx_list.num_alloc, 0, \"\");\n}\n\n\nstatic constexpr io_data save_data[] =\n{\n  { \"0\",  \"abc\",              \"0\",  \"abc\", },\n  { \"1\",  \"def\",              \"1\",  \"def\", },\n  { \"2\",  \"123515\",           \"2\",  \"123515\", },\n  { \"3\",  \"!!zc\",             \"3\",  \"!!zc\", },\n  { \"4\",  \"[[[%%\",            \"4\",  \"[[[%%\", },\n  // SFX conversions are performed by the loading functions.\n  { \"5\",  \"&shot.sam&3c\",     \"5\",  SAM_S \"shot.sam\" SAM_E \"3c\", },\n  { \"6\",  SAM_S \"a.sam\" SAM_E,\"6\",  SAM_S \"a.sam\" SAM_E, },\n  { \"7\",  \"&a.sam&3c&a.sam&\", \"7\",  SAM_S \"a.sam\" SAM_E \"3c\" SAM_S \"a.sam\" SAM_E, },\n  { \"8\",  \"&a.sam&3c_abc\",    \"8\",  SAM_S \"a.sam\" SAM_E \"3c_abc\", },\n  { \"9\",  \"(((())(()\",        \"9\",  \"(((())(()\", },\n  { \"10\", \"&&&&&&&\",          \"10\", SAM_S SAM_E SAM_S SAM_E SAM_S SAM_E \"&\", },\n  // Don't save empty fields...\n  { \"11\", \"\",                 \"11\", \"\", },\n  { \"\",   \"hhhhhhh\",          \"\",   \"hhhhhhh\", },\n  { \"\",   \"\",                 \"\",   \"\", },\n  // The rest should be omitted entirely.\n};\n\nstatic constexpr char v251_data[NUM_BUILTIN_SFX][LEGACY_SFX_SIZE] =\n{\n  \"abc\",\n  \"def\",\n  \"123515\",\n  \"!!zc\",\n  \"[[[%%\",\n  \"&shot.sam&3c\",\n  SAM_S \"a.sam\" SAM_E,\n  \"&a.sam&3c&a.sam&\",\n  \"&a.sam&3c_abc\",\n  \"(((())(()\",\n  \"&&&&&&&\",\n  \"\",\n  \"hhhhhhh\",\n  \"\",\n};\n\nstatic constexpr char v293_data[] =\n{\n  \"MZFX\\x1a\\x00\\x02\\x5d\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x00\"\n  \"\\x02\\x00\\x03\\x00\\x00\\x00\" \"abc\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"0\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x01\"\n  \"\\x02\\x00\\x03\\x00\\x00\\x00\" \"def\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"1\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x02\"\n  \"\\x02\\x00\\x06\\x00\\x00\\x00\" \"123515\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"2\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x03\"\n  \"\\x02\\x00\\x04\\x00\\x00\\x00\" \"!!zc\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"3\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x04\"\n  \"\\x02\\x00\\x05\\x00\\x00\\x00\" \"[[[%%\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"4\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x05\"\n  \"\\x02\\x00\\x0c\\x00\\x00\\x00\" \"&shot.sam&3c\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"5\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x06\"\n  \"\\x02\\x00\\x07\\x00\\x00\\x00\" SAM_S \"a.sam\" SAM_E\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"6\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x07\"\n  \"\\x02\\x00\\x10\\x00\\x00\\x00\" \"&a.sam&3c&a.sam&\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"7\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x08\"\n  \"\\x02\\x00\\x0d\\x00\\x00\\x00\" \"&a.sam&3c_abc\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"8\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x09\"\n  \"\\x02\\x00\\x09\\x00\\x00\\x00\" \"(((())(()\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"9\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x0a\"\n  \"\\x02\\x00\\x07\\x00\\x00\\x00\" \"&&&&&&&\"\n  \"\\x03\\x00\\x02\\x00\\x00\\x00\" \"10\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x0b\"\n  \"\\x03\\x00\\x02\\x00\\x00\\x00\" \"11\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x0c\"\n  \"\\x02\\x00\\x07\\x00\\x00\\x00\" \"hhhhhhh\"\n  \"\\x00\" // + nul completes terminator\n};\n\n#ifdef CONFIG_DEBYTECODE\nstatic constexpr char v300_data[] =\n{\n  \"MZFX\\x1a\\x00\\x03\\x00\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x00\"\n  \"\\x02\\x00\\x03\\x00\\x00\\x00\" \"abc\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"0\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x01\"\n  \"\\x02\\x00\\x03\\x00\\x00\\x00\" \"def\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"1\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x02\"\n  \"\\x02\\x00\\x06\\x00\\x00\\x00\" \"123515\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"2\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x03\"\n  \"\\x02\\x00\\x04\\x00\\x00\\x00\" \"!!zc\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"3\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x04\"\n  \"\\x02\\x00\\x05\\x00\\x00\\x00\" \"[[[%%\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"4\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x05\"\n  \"\\x02\\x00\\x0c\\x00\\x00\\x00\" SAM_S \"shot.sam\" SAM_E \"3c\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"5\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x06\"\n  \"\\x02\\x00\\x07\\x00\\x00\\x00\" SAM_S \"a.sam\" SAM_E\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"6\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x07\"\n  \"\\x02\\x00\\x10\\x00\\x00\\x00\" SAM_S \"a.sam\" SAM_E \"3c\" SAM_S \"a.sam\" SAM_E\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"7\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x08\"\n  \"\\x02\\x00\\x0d\\x00\\x00\\x00\" SAM_S \"a.sam\" SAM_E \"3c_abc\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"8\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x09\"\n  \"\\x02\\x00\\x09\\x00\\x00\\x00\" \"(((())(()\"\n  \"\\x03\\x00\\x01\\x00\\x00\\x00\" \"9\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x0a\"\n  \"\\x02\\x00\\x07\\x00\\x00\\x00\" SAM_S SAM_E SAM_S SAM_E SAM_S SAM_E \"&\"\n  \"\\x03\\x00\\x02\\x00\\x00\\x00\" \"10\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x0b\"\n  \"\\x03\\x00\\x02\\x00\\x00\\x00\" \"11\"\n  \"\\x01\\x00\\x01\\x00\\x00\\x00\\x0c\"\n  \"\\x02\\x00\\x07\\x00\\x00\\x00\" \"hhhhhhh\"\n  \"\\x00\" // + nul completes terminator\n};\n#endif\n\nUNITTEST(SaveSFX)\n{\n  struct sfx_list sfx_list{};\n  auto_free a(sfx_list);\n  char buffer[NUM_BUILTIN_SFX * LEGACY_SFX_SIZE];\n  size_t required = 0;\n  size_t sz;\n  boolean ret;\n  int i = 0;\n\n  for(auto &d : save_data)\n  {\n    ret = sfx_set_label(&sfx_list, i, d.label_out, strlen(d.label_out));\n    ASSERT(ret, \"%d\", i);\n    ret = sfx_set_string(&sfx_list, i, d.string_out, strlen(d.string_out));\n    ASSERT(ret, \"%d\", i);\n    i++;\n  }\n\n// FIXME: 3.x should either back-convert the SFX string or drop this feature entirely.\n#ifndef CONFIG_DEBYTECODE\n  SECTION(Array)\n  {\n    sz = sfx_save_to_memory(&sfx_list, V251, buffer, sizeof(buffer), nullptr);\n    ASSERTEQ(sz, sizeof(v251_data), \"\");\n    ASSERTMEM(buffer, v251_data, sizeof(buffer), \"\");\n  }\n\n  SECTION(Properties293)\n  {\n    sz = sfx_save_to_memory(&sfx_list, V293, nullptr, 0, &required);\n    ASSERTEQ(sz, 0, \"\");\n    ASSERTEQ(required, sizeof(v293_data), \"\");\n    sz = sfx_save_to_memory(&sfx_list, V293, buffer, required, nullptr);\n    ASSERTEQ(sz, required, \"\");\n    ASSERTMEM(buffer, v293_data, required, \"\");\n  }\n#endif\n\n#ifdef CONFIG_DEBYTECODE\n  SECTION(Properties300)\n  {\n    sz = sfx_save_to_memory(&sfx_list, V300, nullptr, 0, &required);\n    ASSERTEQ(sz, 0, \"\");\n    ASSERTEQ(required, sizeof(v300_data), \"\");\n    sz = sfx_save_to_memory(&sfx_list, V300, buffer, required, nullptr);\n    ASSERTEQ(sz, required, \"\");\n    ASSERTMEM(buffer, v300_data, required, \"\");\n  }\n#endif\n}\n\ntemplate<int N>\nstatic void compare_load(const struct sfx_list *sfx_list, const io_data (&data)[N], boolean skip_labels = false)\n{\n  int i = 0;\n  for(auto &d : data)\n  {\n    const struct custom_sfx *sfx = sfx_get(sfx_list, i);\n    size_t a = strlen(d.string_out);\n    size_t b = strlen(d.label_out);\n\n    if(a || (b && !skip_labels))\n    {\n      ASSERT(sfx, \"%d\", i);\n      ASSERTCMP(sfx->string, d.string_out, \"%d\", i);\n      if(!skip_labels)\n        ASSERTCMP(sfx->label, d.label_out, \"%d\", i);\n    }\n    else\n      ASSERTEQ(sfx, nullptr, \"%d\", i);\n\n    i++;\n  }\n}\n\nUNITTEST(LoadSFX)\n{\n  struct sfx_list sfx_list{};\n  auto_free a(sfx_list);\n  boolean ret;\n\n  SECTION(Array)\n  {\n    ret = sfx_load_from_memory(&sfx_list, V251, (const char *)v251_data, sizeof(v251_data));\n    ASSERT(ret, \"\");\n    compare_load(&sfx_list, save_data, true);\n  }\n\n  SECTION(Properties293)\n  {\n    ret = sfx_load_from_memory(&sfx_list, V293, v293_data, sizeof(v293_data));\n    ASSERT(ret, \"\");\n    compare_load(&sfx_list, save_data);\n  }\n\n#ifdef CONFIG_DEBYTECODE\n  SECTION(Properties300)\n  {\n    ret = sfx_load_from_memory(&sfx_list, V300, v300_data, sizeof(v300_data));\n    ASSERT(ret, \"\");\n    compare_load(&sfx_list, save_data);\n  }\n#endif\n}\n"
  },
  {
    "path": "unit/thread.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Perform thread tests with whichever thread.h implementation is used by the\n * MegaZeux executable (possibly including SDL).\n */\n\n#include \"Unit.hpp\"\n#include \"thread.hpp\"\n"
  },
  {
    "path": "unit/thread.hpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Lazy tests to verify thread.h wrappers are implemented correctly.\n * This needs to be in a header so the full test (SDL, pthread, etc.) and the\n * limited Win32 test (utils only) can include different headers.\n */\n\n#include \"Unit.hpp\"\n\n#ifndef WIN32_FALLBACK_TEST\n#undef SKIP_SDL\n#endif\n\n#include \"../src/platform.h\"\n#include \"../src/util.h\"\n\n#define NUM_THREADS 3\n#define REPEAT_TIMES 100\n\nstatic THREAD_RES thread_basic_fn(void *opaque)\n{\n  boolean *it_worked = reinterpret_cast<boolean *>(opaque);\n  *it_worked = true;\n  THREAD_RETURN;\n}\n\nUNITTEST(Thread)\n{\n  platform_thread thread;\n  boolean check = false;\n  boolean ret;\n\n  ret = platform_thread_create(&thread, thread_basic_fn, &check);\n  ASSERT(ret, \"thread create failed\");\n  platform_thread_join(&thread);\n\n  ASSERTEQ(check, true, \"thread failed to run or thread join failed\");\n}\n\n\nstruct mutex_data\n{\n  platform_mutex lock;\n  boolean lock_ret_false;\n  boolean unlock_ret_false;\n  boolean bad_value;\n  int check;\n};\n\nstatic THREAD_RES mutex_fn(void *opaque)\n{\n  struct mutex_data *d = reinterpret_cast<struct mutex_data *>(opaque);\n\n  for(int i = 0; i < REPEAT_TIMES; i++)\n  {\n    if(!platform_mutex_lock(&(d->lock)))\n      d->lock_ret_false = true;\n\n    // nonatomic without mutex\n    int tmp = d->check;\n    if((++d->check) != (tmp + 1))\n      d->bad_value = true;\n\n    if(!platform_mutex_unlock(&(d->lock)))\n      d->unlock_ret_false = true;\n  }\n  THREAD_RETURN;\n}\n\nUNITTEST(Mutex)\n{\n  platform_thread threads[NUM_THREADS];\n  struct mutex_data d{};\n  boolean ret;\n  int i;\n\n  ret = platform_mutex_init(&(d.lock));\n  ASSERT(ret, \"failed to init lock\");\n\n  for(i = 0; i < NUM_THREADS; i++)\n  {\n    ret = platform_thread_create(&threads[i], mutex_fn, &d);\n    ASSERT(ret, \"thread %d\", i);\n  }\n  for(i = 0; i < NUM_THREADS; i++)\n    platform_thread_join(&threads[i]);\n\n  platform_mutex_destroy(&(d.lock));\n\n  ASSERTEQ(d.lock_ret_false, false, \"lock() returned false\");\n  ASSERTEQ(d.unlock_ret_false, false, \"unlock() returned false\");\n  ASSERTEQ(d.bad_value, false, \"race condition in increment\");\n  ASSERTEQ(d.check, NUM_THREADS * REPEAT_TIMES, \"incorrect final value\");\n}\n\n\nstruct cond_data\n{\n  platform_mutex lock;\n  platform_cond cond;\n\n  uint8_t buffer[277];\n  unsigned sum[NUM_THREADS];\n  unsigned expected;\n  unsigned iter_read[NUM_THREADS];\n  unsigned iter_write;\n  unsigned iter_max;\n  unsigned num_readers;\n  unsigned thread_idx;\n  boolean is_write;\n\n  boolean lock_ret_false;\n  boolean unlock_ret_false;\n  boolean wait_ret_false;\n  boolean broadcast_ret_false;\n};\n\nstatic THREAD_RES cond_read_fn(void *opaque)\n{\n  struct cond_data *d = reinterpret_cast<struct cond_data *>(opaque);\n  size_t i;\n  int num;\n\n  if(!platform_mutex_lock(&(d->lock)))\n    d->lock_ret_false = true;\n\n  num = d->thread_idx;\n  d->thread_idx++;\n\n  while(d->iter_read[num] < d->iter_max)\n  {\n    while(d->is_write || d->iter_read[num] >= d->iter_write)\n    {\n      if(!platform_cond_wait(&(d->cond), &(d->lock)))\n        d->wait_ret_false = true;\n    }\n    d->num_readers++;\n\n    if(!platform_mutex_unlock(&(d->lock)))\n      d->unlock_ret_false = true;\n\n    for(i = 0; i < ARRAY_SIZE(d->buffer); i++)\n      d->sum[num] += d->buffer[i];\n\n    if(!platform_mutex_lock(&(d->lock)))\n      d->lock_ret_false = true;\n\n    d->iter_read[num]++;\n    d->num_readers--;\n\n    if(!platform_cond_broadcast(&(d->cond)))\n      d->broadcast_ret_false = true;\n  }\n  platform_mutex_unlock(&(d->lock));\n  THREAD_RETURN;\n}\n\nstatic THREAD_RES cond_write_fn(void *opaque)\n{\n  struct cond_data *d = reinterpret_cast<struct cond_data *>(opaque);\n  unsigned current = 0;\n  size_t i;\n\n  if(!platform_mutex_lock(&(d->lock)))\n    d->lock_ret_false = true;\n\n  while(d->iter_write < d->iter_max)\n  {\n    while(d->num_readers)\n    {\n      if(!platform_cond_wait(&(d->cond), &(d->lock)))\n        d->wait_ret_false = true;\n    }\n    boolean up_to_date = true;\n    for(i = 0; i < NUM_THREADS; i++)\n      if(d->iter_read[i] < d->iter_write)\n        up_to_date = false;\n\n    if(!up_to_date)\n    {\n      if(!platform_cond_wait(&(d->cond), &(d->lock)))\n        d->wait_ret_false = true;\n      continue;\n    }\n    d->is_write = true;\n\n    if(!platform_mutex_unlock(&(d->lock)))\n      d->unlock_ret_false = true;\n\n    for(i = 0; i < ARRAY_SIZE(d->buffer); i++)\n    {\n      d->buffer[i] = (current++);\n      d->expected += d->buffer[i];\n    }\n\n    if(!platform_mutex_lock(&(d->lock)))\n      d->lock_ret_false = true;\n\n    d->iter_write++;\n    d->is_write = false;\n\n    if(!platform_cond_broadcast(&(d->cond)))\n      d->broadcast_ret_false = true;\n  }\n  platform_mutex_unlock(&(d->lock));\n  THREAD_RETURN;\n}\n\nUNITTEST(Cond)\n{\n  platform_thread workers[NUM_THREADS];\n  platform_thread ctrl;\n  struct cond_data d{};\n  boolean ret;\n  int i;\n\n#if defined(_WIN32) && defined(SKIP_SDL)\n  // Not currently implemented.\n  SKIP();\n#endif\n\n  d.iter_max = REPEAT_TIMES;\n\n  ret = platform_mutex_init(&(d.lock));\n  ASSERT(ret, \"failed to init lock\");\n  ret = platform_cond_init(&(d.cond));\n  ASSERT(ret, \"failed to init cond\");\n\n  ret = platform_thread_create(&ctrl, cond_write_fn, &d);\n  ASSERT(ret, \"ctrl\");\n\n  for(i = 0; i < NUM_THREADS; i++)\n  {\n    ret = platform_thread_create(&workers[i], cond_read_fn, &d);\n    ASSERT(ret, \"worker %d\", i);\n  }\n\n  platform_thread_join(&ctrl);\n  for(i = 0; i < NUM_THREADS; i++)\n    platform_thread_join(&workers[i]);\n\n  platform_cond_destroy(&(d.cond));\n  platform_mutex_destroy(&(d.lock));\n\n  for(i = 0; i < NUM_THREADS; i++)\n    ASSERTEQ(d.sum[i], d.expected, \"incorrect value\");\n\n  ASSERTEQ(d.lock_ret_false, false, \"lock() returned false\");\n  ASSERTEQ(d.unlock_ret_false, false, \"unlock() returned false\");\n  ASSERTEQ(d.wait_ret_false, false, \"wait() returned false\");\n  ASSERTEQ(d.broadcast_ret_false, false, \"broadcast() returned false\");\n}\n\n\n#define NUM_WORK (NUM_THREADS) * 2\nstruct semaphore_data\n{\n  platform_mutex lock;\n  platform_sem worker_sem;\n  platform_sem ctrl_sem;\n\n  boolean ready[NUM_WORK];\n  int buffer[NUM_WORK];\n  int next_idx;\n  int input_idx;\n  boolean need_exit;\n\n  boolean lock_ret_false;\n  boolean unlock_ret_false;\n  boolean wait_ret_false;\n  boolean post_ret_false;\n};\n\nstatic THREAD_RES semaphore_worker_fn(void *opaque)\n{\n  struct semaphore_data *d = reinterpret_cast<struct semaphore_data *>(opaque);\n  int i;\n\n  if(!platform_mutex_lock(&(d->lock)))\n    d->lock_ret_false = true;\n\n  while(!d->need_exit)\n  {\n    if(!platform_mutex_unlock(&(d->lock)))\n      d->unlock_ret_false = true;\n    if(!platform_sem_wait(&(d->worker_sem)))\n      d->wait_ret_false = true;\n    if(!platform_mutex_lock(&(d->lock)))\n      d->lock_ret_false = true;\n\n    if(d->need_exit)\n      break;\n\n    int idx = d->next_idx;\n    d->next_idx++;\n    if(d->next_idx >= NUM_WORK)\n      d->next_idx = 0;\n\n    if(!platform_mutex_unlock(&(d->lock)))\n      d->unlock_ret_false = true;\n\n    // Do \"work\"\n    for(i = 0; i < REPEAT_TIMES; i++)\n      d->buffer[idx]++;\n\n    if(!platform_mutex_lock(&(d->lock)))\n      d->lock_ret_false = true;\n\n    d->ready[idx] = true;\n\n    if(!platform_sem_post(&(d->ctrl_sem)))\n      d->post_ret_false = true;\n  }\n  platform_mutex_unlock(&(d->lock));\n  THREAD_RETURN;\n}\n\nstatic THREAD_RES semaphore_ctrl_fn(void *opaque)\n{\n  struct semaphore_data *d = reinterpret_cast<struct semaphore_data *>(opaque);\n  int num_out = 0;\n  int num_in = 0;\n  int i;\n\n  while(num_out < NUM_WORK * REPEAT_TIMES)\n  {\n    if(!platform_mutex_lock(&d->lock))\n      d->lock_ret_false = true;\n\n    // Consume finished work in-order (y4m2smzx does this)\n    int idx = d->input_idx;\n    boolean ready = d->ready[idx];\n\n    if(num_in >= NUM_WORK && !ready)\n    {\n      if(!platform_mutex_unlock(&(d->lock)))\n        d->unlock_ret_false = true;\n      if(!platform_sem_wait(&(d->ctrl_sem)))\n        d->wait_ret_false = true;\n      continue;\n    }\n\n    d->input_idx++;\n    if(d->input_idx >= NUM_WORK)\n      d->input_idx = 0;\n\n    if(!platform_mutex_unlock(&(d->lock)))\n      d->unlock_ret_false = true;\n\n    if(num_in >= NUM_WORK)\n    {\n      // Consume\n      num_out++;\n    }\n\n    if(num_in >= NUM_WORK * REPEAT_TIMES)\n      continue;\n\n    // Prepare new \"work\"\n    d->buffer[idx]++;\n    d->ready[idx] = false;\n    num_in++;\n\n    if(!platform_sem_post(&(d->worker_sem)))\n      d->post_ret_false = true;\n  }\n\n  if(!platform_mutex_lock(&(d->lock)))\n    d->lock_ret_false = true;\n  d->need_exit = true;\n  if(!platform_mutex_unlock(&(d->lock)))\n    d->unlock_ret_false = true;\n\n  for(i = 0; i < NUM_THREADS; i++)\n  {\n    if(!platform_sem_post(&(d->worker_sem)))\n      d->post_ret_false = true;\n  }\n  THREAD_RETURN;\n}\n\nUNITTEST(Semaphore)\n{\n  platform_thread workers[NUM_THREADS];\n  platform_thread ctrl;\n  struct semaphore_data d{};\n  boolean ret;\n  int i;\n\n  ret = platform_mutex_init(&(d.lock));\n  ASSERT(ret, \"failed to init lock\");\n  ret = platform_sem_init(&(d.worker_sem), 0);\n  ASSERT(ret, \"failed to init worker semaphore\");\n  ret = platform_sem_init(&(d.ctrl_sem), 0);\n  ASSERT(ret, \"failed to init ctrl semaphore\");\n\n  for(i = 0; i < NUM_THREADS; i++)\n  {\n    ret = platform_thread_create(&workers[i], semaphore_worker_fn, &d);\n    ASSERT(ret, \"worker %d\", i);\n  }\n  for(i = 0; i < NUM_WORK; i++)\n  {\n    ASSERTEQ(d.buffer[i], 0, \"worker did not block\");\n    d.ready[i] = true;\n  }\n  ret = platform_thread_create(&ctrl, semaphore_ctrl_fn, &d);\n  ASSERT(ret, \"ctrl\");\n\n  platform_thread_join(&ctrl);\n  for(i = 0; i < NUM_THREADS; i++)\n    platform_thread_join(&workers[i]);\n\n  platform_sem_destroy(&(d.worker_sem));\n  platform_sem_destroy(&(d.ctrl_sem));\n  platform_mutex_destroy(&(d.lock));\n\n  for(i = 0; i < NUM_WORK; i++)\n    ASSERTEQ(d.buffer[i], REPEAT_TIMES * (REPEAT_TIMES + 1), \"incorrect value\");\n\n  ASSERTEQ(d.lock_ret_false, false, \"lock() returned false\");\n  ASSERTEQ(d.unlock_ret_false, false, \"unlock() returned false\");\n  ASSERTEQ(d.wait_ret_false, false, \"wait() returned false\");\n  ASSERTEQ(d.post_ret_false, false, \"post() returned false\");\n}\n"
  },
  {
    "path": "unit/thread_win32.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Perform thread tests with the Win32 fallback implementation for utils.\n */\n\n#define WIN32_FALLBACK_TEST\n\n#include \"Unit.hpp\"\n#include \"thread.hpp\"\n"
  },
  {
    "path": "unit/utils/image_file/p1.pbm",
    "content": "P1\n5 5\n10001\n00000\n00100\n00000\n10001\n"
  },
  {
    "path": "unit/utils/image_file/p2.pgm",
    "content": "P2\n4 7\n255\n255 255 255 255\n170 170 170 170\n 85  85  85  85\n  0   0   0   0\n255 255 255 255\n255 255 255 255\n255 255 255 255\n"
  },
  {
    "path": "unit/utils/image_file/p2_18.pgm",
    "content": "P2\n4 7\n63\n63 63 63 63\n42 42 42 42\n21 21 21 21\n 0  0  0  0\n63 63 63 63\n63 63 63 63\n63 63 63 63\n"
  },
  {
    "path": "unit/utils/image_file/p2_64.pgm",
    "content": "P2\n4 7\n65535\n65535 65535 65535 65535\n43690 43690 43690 43690\n21845 21845 21845 21845\n    0     0     0     0\n65535 65535 65535 65535\n65535 65535 65535 65535\n65535 65535 65535 65535\n"
  },
  {
    "path": "unit/utils/image_file/p3.ppm",
    "content": "P3\n8 6\n255\n255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0\n0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0\n0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255 0 0 255\n255 255 0 255 255 0 255 255 0 255 255 0 255 170 0 255 170 0 255 170 0 255 170 0\n255 0 255 255 0 255 255 0 255 255 0 255 170 0 255 170 0 255 170 0 255 170 0 255\n0 255 255 0 255 255 0 255 255 0 255 255 0 255 170 0 255 170 0 255 170 0 255 170\n"
  },
  {
    "path": "unit/utils/image_file/p3_18.ppm",
    "content": "P3\n8 6\n63\n63 0 0 63 0 0 63 0 0 63 0 0 63 0 0 63 0 0 63 0 0 63 0 0\n0 63 0 0 63 0 0 63 0 0 63 0 0 63 0 0 63 0 0 63 0 0 63 0\n0 0 63 0 0 63 0 0 63 0 0 63 0 0 63 0 0 63 0 0 63 0 0 63\n63 63 0 63 63 0 63 63 0 63 63 0  63 42 0 63 42 0 63 42 0 63 42 0\n63 0 63 63 0 63 63 0 63 63 0 63  42 0 63 42 0 63 42 0 63 42 0 63\n0 63 63 0 63 63 0 63 63 0 63 63  0 63 42 0 63 42 0 63 42 0 63 42\n"
  },
  {
    "path": "unit/utils/image_file/p3_64.ppm",
    "content": "P3\n8 6\n65535\n65535 0 0 65535 0 0 65535 0 0 65535 0 0 65535 0 0 65535 0 0 65535 0 0 65535 0 0\n0 65535 0 0 65535 0 0 65535 0 0 65535 0 0 65535 0 0 65535 0 0 65535 0 0 65535 0\n0 0 65535 0 0 65535 0 0 65535 0 0 65535 0 0 65535 0 0 65535 0 0 65535 0 0 65535\n65535 65535 0 65535 65535 0 65535 65535 0 65535 65535 0  65535 43690 0 65535 43690 0 65535 43690 0 65535 43690 0\n65535 0 65535 65535 0 65535 65535 0 65535 65535 0 65535  43690 0 65535 43690 0 65535 43690 0 65535 43690 0 65535\n0 65535 65535 0 65535 65535 0 65535 65535 0 65535 65535  0 65535 43690 0 65535 43690 0 65535 43690 0 65535 43690\n"
  },
  {
    "path": "unit/utils/image_file.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2021-2024 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n#include \"../Unit.hpp\"\n\n#include <zlib.h>\n\n#include \"../../src/utils/image_file.c\"\n#include \"../../src/utils/image_gif.c\"\n\n#define DATA_BASEDIR \"../image_file/\"\n\nstruct image_file_const\n{\n  unsigned int width;\n  unsigned int height;\n  const struct rgba_color *data;\n};\n\n/********** BW template. **********/\n\n#define WHITE { 255, 255, 255, 255 }\n#define BLACK {   0,   0,   0, 255 }\n\nstatic const struct rgba_color raw_bw[] =\n{\n  // R G B A; 5x5\n  BLACK, WHITE, WHITE, WHITE, BLACK,\n  WHITE, WHITE, WHITE, WHITE, WHITE,\n  WHITE, WHITE, BLACK, WHITE, WHITE,\n  WHITE, WHITE, WHITE, WHITE, WHITE,\n  BLACK, WHITE, WHITE, WHITE, BLACK,\n};\n\nstatic const struct image_file_const base_bw_img\n{\n  5, 5, raw_bw\n};\n\n/********** Greyscale template. **********/\n\nstatic const struct rgba_color raw_gs[] =\n{\n  // R G B A; 4x7\n  { 255, 255, 255, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 },\n  { 170, 170, 170, 255 }, { 170, 170, 170, 255 }, { 170, 170, 170, 255 }, { 170, 170, 170, 255 },\n  {  85,  85,  85, 255 }, {  85,  85,  85, 255 }, {  85,  85,  85, 255 }, {  85,  85,  85, 255 },\n  {   0,   0,   0, 255 }, {   0,   0,   0, 255 }, {   0,   0,   0, 255 }, {   0,   0,   0, 255 },\n  { 255, 255, 255, 170 }, { 255, 255, 255, 170 }, { 255, 255, 255, 170 }, { 255, 255, 255, 170 },\n  { 255, 255, 255,  85 }, { 255, 255, 255,  85 }, { 255, 255, 255,  85 }, { 255, 255, 255,  85 },\n  { 255, 255, 255,   0 }, { 255, 255, 255,   0 }, { 255, 255, 255,   0 }, { 255, 255, 255,   0 },\n};\n\nstatic const struct image_file_const base_gs_img =\n{\n  4, 7, raw_gs\n};\n\n/********** RGB(A) template. **********/\n\nstatic const struct rgba_color raw_rgba[] =\n{\n  // R G B A; 8x6\n  { 255, 0, 0, 255 }, { 255, 0, 0, 255 }, { 255, 0, 0, 255 }, { 255, 0, 0, 255 },\n  { 255, 0, 0, 255 }, { 255, 0, 0, 255 }, { 255, 0, 0, 255 }, { 255, 0, 0, 255 },\n\n  { 0, 255, 0, 255 }, { 0, 255, 0, 255 }, { 0, 255, 0, 255 }, { 0, 255, 0, 255 },\n  { 0, 255, 0, 255 }, { 0, 255, 0, 255 }, { 0, 255, 0, 255 }, { 0, 255, 0, 255 },\n\n  { 0, 0, 255, 255 }, { 0, 0, 255, 255 }, { 0, 0, 255, 255 }, { 0, 0, 255, 255 },\n  { 0, 0, 255, 255 }, { 0, 0, 255, 255 }, { 0, 0, 255, 255 }, { 0, 0, 255, 255 },\n\n  { 255, 255, 0, 170 }, { 255, 255, 0, 170 }, { 255, 255, 0, 170 }, { 255, 255, 0, 170 },\n  { 255, 170, 0,  85 }, { 255, 170, 0,  85 }, { 255, 170, 0,  85 }, { 255, 170, 0,  85 },\n\n  { 255, 0, 255, 170 }, { 255, 0, 255, 170 }, { 255, 0, 255, 170 }, { 255, 0, 255, 170 },\n  { 170, 0, 255,  85 }, { 170, 0, 255,  85 }, { 170, 0, 255,  85 }, { 170, 0, 255,  85 },\n\n  { 0, 255, 255, 170 }, { 0, 255, 255, 170 }, { 0, 255, 255, 170 }, { 0, 255, 255, 170 },\n  { 0, 255, 170,  85 }, { 0, 255, 170,  85 }, { 0, 255, 170,  85 }, { 0, 255, 170,  85 },\n};\n\nstatic const struct image_file_const base_rgba_img\n{\n  8, 6, raw_rgba\n};\n\n/********** RGB(A) template for 2x scaling formats. **********/\n\nstatic const struct rgba_color raw_rgba_2x[] =\n{\n  // R G B A; 4 x 4\n  { 255, 0, 0, 255 }, { 255, 0, 0, 255 }, { 0, 255, 0, 255 }, { 0, 255, 0, 255 },\n  { 255, 0, 0, 255 }, { 255, 0, 0, 255 }, { 0, 255, 0, 255 }, { 0, 255, 0, 255 },\n  { 0, 0, 255, 255 }, { 0, 0, 255, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 },\n  { 0, 0, 255, 255 }, { 0, 0, 255, 255 }, { 255, 255, 255, 255 }, { 255, 255, 255, 255 },\n  // Only present in 1x1_interlaced.gif\n  { 0, 0, 0, 255}, { 0, 0, 0, 255}, { 0, 0, 0, 255}, { 0, 0, 0, 255},\n  { 0, 0, 0, 255}, { 0, 0, 0, 255}, { 0, 0, 0, 255}, { 0, 0, 0, 255},\n  { 0, 0, 0, 255}, { 0, 0, 0, 255}, { 0, 0, 0, 255}, { 0, 0, 0, 255},\n  { 0, 0, 0, 255}, { 0, 0, 0, 255}, { 0, 0, 0, 255}, { 0, 0, 0, 255},\n};\n\nstatic const struct image_file_const base_scaling_img\n{\n  4, 4, raw_rgba_2x\n};\n\nstatic const struct image_file_const base_scaling_img_interlaced\n{\n  4, 8, raw_rgba_2x\n};\n\n/* Allow error in 16-bpp conversions since they can't accurately reproduce\n * the source data. +-3 seems good enough for the current inputs.\n */\ntemplate<unsigned ERROR>\nstatic boolean compare_16bpp(int a, int b)\n{\n  a -= b;\n  return a >= -(int)ERROR && a <= (int)ERROR;\n}\n\ntemplate<int ERROR>\nstatic boolean compare_rgb16(const rgba_color &base, const rgba_color &in)\n{\n  return  compare_16bpp<ERROR>(base.r, in.r) && compare_16bpp<ERROR>(base.g, in.g) &&\n          compare_16bpp<ERROR>(base.b, in.b) && in.a == 255;\n}\n\ntemplate<int ERROR>\nstatic boolean compare_rgba16(const rgba_color &base, const rgba_color &in)\n{\n  return  compare_16bpp<ERROR>(base.r, in.r) && compare_16bpp<ERROR>(base.g, in.g) &&\n          compare_16bpp<ERROR>(base.b, in.b) && compare_16bpp<ERROR>(base.a, in.a);\n}\n\nstatic boolean compare_rgb(const rgba_color &base, const rgba_color &in)\n{\n  return (base.r == in.r) && (base.g == in.g) && (base.b == in.b) && (in.a == 255);\n}\n\nstatic boolean compare_rgba(const rgba_color &base, const rgba_color &in)\n{\n  return (base.r == in.r) && (base.g == in.g) && (base.b == in.b) && (base.a == in.a);\n}\n\ntemplate<boolean (*COMPARE_FN)(const rgba_color &a, const rgba_color &b)>\nstatic void compare_image(const struct image_file_const &base,\n const struct image_file &in, const char *filename)\n{\n  ASSERTEQ(in.width, base.width, \"%s: width mismatch\", filename);\n  ASSERTEQ(in.height, base.height, \"%s: height mismatch\", filename);\n\n  size_t num = base.width * base.height;\n  for(size_t i = 0; i < num; i++)\n  {\n    if(!COMPARE_FN(base.data[i], in.data[i]))\n    {\n      FAIL(\"%s: pixel mismatch @ %zu - %08\" PRIx32 \" %08\" PRIx32, filename, i,\n       *(uint32_t *)(base.data + i), *(uint32_t *)(in.data + i));\n    }\n  }\n}\n\ntemplate<boolean (*COMPARE_FN)(const rgba_color &a, const rgba_color &b)>\nstatic void load_and_compare_image(const struct image_file_const &base,\n const char *filename)\n{\n  struct image_file img{};\n  char path[512];\n\n  snprintf(path, sizeof(path), DATA_BASEDIR \"%s\", filename);\n\n  enum image_error ret = load_image_from_file(path, &img, NULL);\n  ASSERT(ret == IMAGE_OK, \"%s: load failed: %s\", filename, image_error_string(ret));\n  compare_image<COMPARE_FN>(base, img, filename);\n  image_free(&img);\n}\n\n\nUNITTEST(PNG)\n{\n#ifndef CONFIG_PNG\n  SKIP();\n#else\n\n  /* TODO: all indexed greyscale */\n  static constexpr const char *inputs_bw[] =\n  {\n    \"rgb1.png\", // Indexed RGB\n  };\n  static constexpr const char *inputs_gs[] =\n  {\n    \"gs8.png\",  // Greyscale (GIMP only saves greyscale non-indexed...)\n    \"rgb2.png\", // Indexed RGB\n  };\n  static constexpr const char *inputs_gsa[] =\n  {\n    \"gsa8.png\", // Greyscale with alpha\n  };\n  static constexpr const char *inputs_rgb[] =\n  {\n    \"rgb4.png\", // Indexed RGB\n    \"rgb8.png\", // Indexed RGB\n    \"rgb24.png\",\n    \"rgb24_adam7.png\", // With interlacing\n    \"rgb48.png\",\n  };\n  static constexpr const char *inputs_rgba[] =\n  {\n    \"rgba8.png\",  // Indexed RGB with tRNS alpha\n    \"rgba.png\",\n    \"rgba64.png\",\n  };\n\n  SECTION(1bpp)\n  {\n    for(const char *filename : inputs_bw)\n      load_and_compare_image<compare_rgb>(base_bw_img, filename);\n  }\n\n  SECTION(Greyscale)\n  {\n    for(const char *filename : inputs_gs)\n      load_and_compare_image<compare_rgb>(base_gs_img, filename);\n  }\n\n  SECTION(GreyscaleAlpha)\n  {\n    for(const char *filename : inputs_gsa)\n      load_and_compare_image<compare_rgba>(base_gs_img, filename);\n  }\n\n  SECTION(RGB)\n  {\n    for(const char *filename : inputs_rgb)\n      load_and_compare_image<compare_rgb>(base_rgba_img, filename);\n  }\n\n  SECTION(RGBA)\n  {\n    for(const char *filename : inputs_rgba)\n      load_and_compare_image<compare_rgba>(base_rgba_img, filename);\n  }\n\n#endif /* CONFIG_PNG */\n}\n\n\nUNITTEST(GIF)\n{\n  static constexpr const char *simple_inputs[] =\n  {\n    \"1x1.gif\",\n    \"1x1_layer.gif\",\n    \"1x1_topleft.gif\",\n    \"1x2.gif\",\n    \"1x2_layer.gif\",\n    \"2x1.gif\",\n    \"2x1_layer.gif\"\n  };\n\n  static constexpr struct {\n    const char *filename;\n    uint32_t crc;\n  } complex_inputs[] =\n  {\n    { \"example.gif\", 0x733D9484 },\n    { \"tesseract.gif\", 0xC56E4F50 },\n    { \"truecolor.gif\", 0xE3064823 },\n  };\n\n  /* struct rgba_color and struct gif_rgba must be 100% interchangeable. */\n  static_assert(sizeof(struct rgba_color) == sizeof(struct gif_rgba), \"size mismatch\");\n  static_assert(offsetof(struct rgba_color, r) == offsetof(struct gif_rgba, r), \"r offset mismatch\");\n  static_assert(offsetof(struct rgba_color, g) == offsetof(struct gif_rgba, g), \"g offset mismatch\");\n  static_assert(offsetof(struct rgba_color, b) == offsetof(struct gif_rgba, b), \"b offset mismatch\");\n  static_assert(offsetof(struct rgba_color, a) == offsetof(struct gif_rgba, a), \"a offset mismatch\");\n\n  /* Simple inputs to test basic loading, as well as the scaling and\n   * layer functionality of the compositor. */\n  SECTION(Simple)\n  {\n    for(const char *filename : simple_inputs)\n      load_and_compare_image<compare_rgb>(base_scaling_img, filename);\n  }\n\n  /* GIMP refused to interlace a 4x4 GIF... */\n  SECTION(Interlaced)\n  {\n    load_and_compare_image<compare_rgb>(base_scaling_img_interlaced, \"1x1_interlaced.gif\");\n  }\n\n  /* More complex testing of the LZW depacking algorithm. */\n  SECTION(Complex)\n  {\n    struct image_file img{};\n    char path[512];\n    enum image_error ret;\n\n    for(const auto &in : complex_inputs)\n    {\n      snprintf(path, sizeof(path), DATA_BASEDIR \"%s\", in.filename);\n\n      ret = load_image_from_file(path, &img, NULL);\n      ASSERT(ret == IMAGE_OK, \"%s: load failed: %s\", in.filename, image_error_string(ret));\n      uint32_t chk = crc32(0ul, reinterpret_cast<uint8_t *>(img.data), img.width * img.height * 4);\n      ASSERTEQ(chk, in.crc, \"crc32 mismatch\");\n      image_free(&img);\n    }\n  }\n}\n\n\nUNITTEST(BMP)\n{\n  static const char *bw_inputs[] =\n  {\n    \"idx_1bpp.bmp\",\n  };\n\n  static const char *gs_inputs[] =\n  {\n    \"idx_2bpp.bmp\",\n  };\n\n  static const char *rgb_indexed_inputs[] =\n  {\n    \"idx_4bpp.bmp\",\n    \"idx_8bpp.bmp\",\n  };\n\n  static const char *rgb_rle_inputs[] =\n  {\n    \"rle_4bpp.bmp\",\n    \"rle_8bpp.bmp\",\n  };\n\n  static const char *rgb_truecolor16_inputs[] =\n  {\n    \"true_16bpp.bmp\",\n  };\n\n  static const char *rgb_truecolor_inputs[] =\n  {\n    \"true_24bpp.bmp\",\n    \"true_32bpp.bmp\",\n  };\n\n  /* These bitfields BMPs are 100% valid by the specification, but they\n   * confuse software aside from some web browsers:\n   *\n   * - Vivaldi (and possibly other Chromium browsers) displays all of them.\n   * - ImageMagick seems to be completely unaware of BITMAPV2INFOHEADER and\n   *   BITMAPV3INFOHEADER despite having output the original BITMAPV5HEADER\n   *   BMPs that these are based on.\n   * - Firefox can't display any of them either.\n   * - GIMP and misc. Windows applications can read the RGBA8888 and RGBA4444,\n   *   but not the RGB565 or RGB101010.\n   */\n  static const char *rgb_bitfields16_inputs[] =\n  {\n    \"bitfields565_16bpp.bmp\"\n  };\n\n  static const char *rgba_bitfields16_inputs[] =\n  {\n    \"bitfields4444_16bpp.bmp\"\n  };\n\n  static const char *rgb_bitfields32_inputs[] =\n  {\n    \"bitfields101010_32bpp.bmp\"\n  };\n\n  static const char *rgba_bitfields32_inputs[] =\n  {\n    \"bitfields8888_32bpp.bmp\"\n  };\n\n  SECTION(TrueColor)\n  {\n    for(const char *filename : rgb_truecolor_inputs)\n      load_and_compare_image<compare_rgb>(base_rgba_img, filename);\n\n    // 16bpp is lossy, needs a special compare...\n    for(const char *filename : rgb_truecolor16_inputs)\n      load_and_compare_image<compare_rgb16<3>>(base_rgba_img, filename);\n  }\n\n  SECTION(Indexed)\n  {\n    for(const char *filename : rgb_indexed_inputs)\n      load_and_compare_image<compare_rgb>(base_rgba_img, filename);\n\n    for(const char *filename : bw_inputs)\n      load_and_compare_image<compare_rgb>(base_bw_img, filename);\n\n    for(const char *filename : gs_inputs)\n      load_and_compare_image<compare_rgb>(base_gs_img, filename);\n  }\n\n  SECTION(RLE)\n  {\n    for(const char *filename : rgb_rle_inputs)\n      load_and_compare_image<compare_rgb>(base_rgba_img, filename);\n  }\n\n  SECTION(Bitfields)\n  {\n    for(const char *filename : rgb_bitfields32_inputs)\n      load_and_compare_image<compare_rgb>(base_rgba_img, filename);\n    for(const char *filename : rgba_bitfields32_inputs)\n      load_and_compare_image<compare_rgba>(base_rgba_img, filename);\n\n    // 16bpp is lossy, needs a special compare...\n    for(const char *filename : rgb_bitfields16_inputs)\n      load_and_compare_image<compare_rgb16<3>>(base_rgba_img, filename);\n    for(const char *filename : rgba_bitfields16_inputs)\n      load_and_compare_image<compare_rgba16<3>>(base_rgba_img, filename);\n  }\n}\n\n\nUNITTEST(TGA)\n{\n  static const char *bpp16_inputs[] =\n  {\n    \"tga_15bpp.tga\",\n    \"tga_16bpp.tga\",\n    \"tga_16bpp_rle.tga\",\n    \"tga_16bpp_ttb.tga\",\n    \"tga_16bpp_ttb_rle.tga\",\n    \"tga_idx8_15bpp.tga\",\n    \"tga_idx8_16bpp.tga\",\n    \"tga_idx8_16bpp_rle.tga\",\n    \"tga_idx8_16bpp_ttb.tga\",\n    \"tga_idx8_16bpp_ttb_rle.tga\",\n  };\n\n  static const char *bpp24_inputs[] =\n  {\n    \"tga_24bpp.tga\",\n    \"tga_24bpp_rle.tga\",\n    \"tga_24bpp_ttb.tga\",\n    \"tga_24bpp_ttb_rle.tga\",\n    \"tga_idx8_24bpp.tga\",\n    \"tga_idx8_24bpp_rle.tga\",\n    \"tga_idx8_24bpp_ttb.tga\",\n    \"tga_idx8_24bpp_ttb_rle.tga\",\n  };\n\n  static const char *bpp32_inputs[] =\n  {\n    \"tga_32bpp.tga\",\n    \"tga_32bpp_rle.tga\",\n    \"tga_32bpp_ttb.tga\",\n    \"tga_32bpp_ttb_rle.tga\",\n    \"tga_32bpp_rtl.tga\",      // Unlikely that anyone uses right-to-left TGAs,\n    \"tga_32bpp_rtl_ttb.tga\",  // but support them anyway...\n    \"tga_idx8_32bpp.tga\",\n    \"tga_idx8_32bpp_rle.tga\",\n    \"tga_idx8_32bpp_ttb.tga\",\n    \"tga_idx8_32bpp_ttb_rle.tga\",\n    // These are hexedited and follow the spec but nothing supports them :(\n    \"tga_idx16_32bpp.tga\",\n    \"tga_idx16_32bpp_rle.tga\",\n    \"tga_idx16_32bpp_ttb.tga\",\n    \"tga_idx16_32bpp_ttb_rle.tga\",\n  };\n\n  static const char *greyscale_inputs[] =\n  {\n    \"tga_g.tga\",\n    \"tga_g_rle.tga\",\n    \"tga_g_ttb.tga\",\n    \"tga_g_ttb_rle.tga\",\n  };\n\n  SECTION(16bpp)\n  {\n    // 16bpp is lossy, needs a special compare...\n    for(const char *filename : bpp16_inputs)\n      load_and_compare_image<compare_rgb16<3>>(base_rgba_img, filename);\n  }\n\n  SECTION(24bpp)\n  {\n    for(const char *filename : bpp24_inputs)\n      load_and_compare_image<compare_rgb>(base_rgba_img, filename);\n  }\n\n  SECTION(32bpp)\n  {\n    for(const char *filename : bpp32_inputs)\n      load_and_compare_image<compare_rgba>(base_rgba_img, filename);\n  }\n\n  SECTION(Greyscale)\n  {\n    for(const char *filename : greyscale_inputs)\n      load_and_compare_image<compare_rgb>(base_gs_img, filename);\n  }\n}\n\n\nUNITTEST(Netpbm)\n{\n  static const char *bw_inputs[] =\n  {\n    /* PBM */\n    \"p1.pbm\",\n    \"p4.pbm\",\n  };\n\n  static const char *grey_inputs[] =\n  {\n    /* PGM */\n    \"p2.pgm\",\n    \"p2_18.pgm\",\n    \"p2_64.pgm\",\n    \"p5.pgm\",\n    \"p5_18.pgm\",\n    \"p5_64.pgm\",\n    /* PAM GRAYSCALE */\n    \"p7_gs.pam\",\n    \"p7_gs18.pam\",\n    \"p7_gs64.pam\",\n  };\n\n  static const char *greya_inputs[] =\n  {\n    /* PAM GRAYSCALE_ALPHA */\n    \"p7_gsa.pam\",\n    \"p7_gsa18.pam\",\n    \"p7_gsa64.pam\",\n  };\n\n  static const char *rgb_inputs[] =\n  {\n    /* PPM */\n    \"p3.ppm\",\n    \"p3_18.ppm\",\n    \"p3_64.ppm\",\n    \"p6.ppm\",\n    \"p6_18.ppm\",\n    \"p6_64.ppm\",\n    /* PAM RGB */\n    \"p7_rgb.pam\",\n    \"p7_rgb18.pam\",\n    \"p7_rgb64.pam\",\n  };\n\n  static const char *rgba_inputs[] =\n  {\n    /* PAM RGB_ALPHA */\n    \"p7_rgba.pam\",\n    \"p7_rgba18.pam\",\n    \"p7_rgba64.pam\",\n  };\n\n  SECTION(Bitmap)\n  {\n    for(const char *filename : bw_inputs)\n      load_and_compare_image<compare_rgb>(base_bw_img, filename);\n  }\n\n  SECTION(Greymap)\n  {\n    for(const char *filename : grey_inputs)\n      load_and_compare_image<compare_rgb>(base_gs_img, filename);\n  }\n\n  SECTION(Pixmap)\n  {\n    for(const char *filename : rgb_inputs)\n      load_and_compare_image<compare_rgb>(base_rgba_img, filename);\n  }\n\n  SECTION(Alpha)\n  {\n    for(const char *filename : greya_inputs)\n      load_and_compare_image<compare_rgba>(base_gs_img, filename);\n\n    for(const char *filename : rgba_inputs)\n      load_and_compare_image<compare_rgba>(base_rgba_img, filename);\n  }\n}\n\n\nUNITTEST(farbfeld)\n{\n  load_and_compare_image<compare_rgba>(base_rgba_img, \"farbfeld.ff\");\n}\n\n\nUNITTEST(raw)\n{\n  struct image_file img{};\n  enum image_error ret;\n\n  static const struct image_raw_format gs_format = { base_gs_img.width, base_gs_img.height, 1, true};\n  static const struct image_raw_format gsa_format = { base_gs_img.width, base_gs_img.height, 2, true };\n  static const struct image_raw_format rgb_format = { base_rgba_img.width, base_rgba_img.height, 3, true };\n  static const struct image_raw_format rgba_format = { base_rgba_img.width, base_rgba_img.height, 4, true };\n\n  SECTION(Greyscale)\n  {\n    ret = load_image_from_file(DATA_BASEDIR \"raw_gs.raw\", &img, &gs_format);\n    ASSERT(ret == IMAGE_OK, \"raw_gs.raw: load failed: %s\", image_error_string(ret));\n    compare_image<compare_rgb>(base_gs_img, img, \"raw_gs.raw\");\n    image_free(&img);\n  }\n\n  SECTION(GreyscaleAlpha)\n  {\n    ret = load_image_from_file(DATA_BASEDIR \"raw_gsa.raw\", &img, &gsa_format);\n    ASSERT(ret == IMAGE_OK, \"raw_gsa.raw: load failed: %s\", image_error_string(ret));\n    compare_image<compare_rgba>(base_gs_img, img, \"raw_gsa.raw\");\n    image_free(&img);\n  }\n\n  SECTION(RGB)\n  {\n    ret = load_image_from_file(DATA_BASEDIR \"raw_rgb.raw\", &img, &rgb_format);\n    ASSERT(ret == IMAGE_OK, \"raw_rgb.raw: load failed: %s\", image_error_string(ret));\n    compare_image<compare_rgb>(base_rgba_img, img, \"raw_rgb.raw\");\n    image_free(&img);\n  }\n\n  SECTION(RGBA)\n  {\n    ret = load_image_from_file(DATA_BASEDIR \"raw_rgba.raw\", &img, &rgba_format);\n    ASSERT(ret == IMAGE_OK, \"raw_rgba.raw: load failed: %s\", image_error_string(ret));\n    compare_image<compare_rgba>(base_rgba_img, img, \"raw_rgba.raw\");\n    image_free(&img);\n  }\n}\n"
  },
  {
    "path": "unit/world.cpp",
    "content": "/* MegaZeux\n *\n * Copyright (C) 2021-2023 Alice Rowan <petrifiedrowan@gmail.com>\n *\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License as\n * published by the Free Software Foundation; either version 2 of\n * the License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n */\n\n/**\n * Unit tests for misc. world format functionality. More comprehensive world\n * world loader testing for valid worlds is performed by the test worlds.\n */\n\n#include \"Unit.hpp\"\n\n#include \"../src/world.h\"\n#include \"../src/world_format.h\"\n#include \"../src/io/memfile.h\"\n#include \"../src/io/zip.h\"\n\n#include \"../src/network/Scoped.hpp\"\n\n#include <limits.h>\n\n/**\n * world.c functions.\n */\n\nstruct world_magic_data\n{\n  const char *world_magic;\n  const char *save_magic;\n  const char *version_string;\n  int expected;\n};\n\nUNITTEST(Magic)\n{\n  static const char UNKNOWN[] = \"<unknown 0000h>\";\n  static const world_magic_data magic_data[] =\n  {\n    // Valid (DOS).\n    { \"MZX\",        \"MZSAV\",        \"1.xx\",         V100   },\n    { \"MZ2\",        \"MZSV2\",        \"2.xx/2.51\",    V200   },\n    { \"MZA\",        \"MZXSA\",        \"2.51s1\",       V251s1 },\n    { \"M\\x02\\x09\",  \"MZS\\x02\\x09\",  \"2.51s2/2.61\",  V251s2 },\n    { \"M\\x02\\x11\",  nullptr,        \"<decrypted>\",  VERSION_DECRYPT }, // Only generated by world decryption.\n    { \"M\\x02\\x32\",  \"MZS\\x02\\x32\",  \"2.62/2.62b\",   V262 },\n    { \"M\\x02\\x41\",  \"MZS\\x02\\x41\",  \"2.65/2.68\",    V265 },\n    { \"M\\x02\\x44\",  \"MZS\\x02\\x44\",  \"2.68\",         V268 },\n    { \"M\\x02\\x45\",  \"MZS\\x02\\x45\",  \"2.69\",         V269 },\n    { \"M\\x02\\x46\",  \"MZS\\x02\\x46\",  \"2.69b\",        V269b },\n    { \"M\\x02\\x48\",  \"MZS\\x02\\x48\",  \"2.69c\",        V269c },\n    { \"M\\x02\\x49\",  \"MZS\\x02\\x49\",  \"2.70\",         V270 },\n\n    // Valid (port).\n    { \"M\\x02\\x50\",  \"MZS\\x02\\x50\",  \"2.80\",   V280 },\n    { \"M\\x02\\x51\",  \"MZS\\x02\\x51\",  \"2.81\",   V281 },\n    { \"M\\x02\\x52\",  \"MZS\\x02\\x52\",  \"2.82\",   V282 },\n    { \"M\\x02\\x53\",  \"MZS\\x02\\x53\",  \"2.83\",   V283 },\n    { \"M\\x02\\x54\",  \"MZS\\x02\\x54\",  \"2.84\",   V284 },\n    { \"M\\x02\\x5A\",  \"MZS\\x02\\x5A\",  \"2.90\",   V290 },\n    { \"M\\x02\\x5B\",  \"MZS\\x02\\x5B\",  \"2.91\",   V291 },\n    { \"M\\x02\\x5C\",  \"MZS\\x02\\x5C\",  \"2.92\",   V292 },\n    { \"M\\x02\\x5D\",  \"MZS\\x02\\x5D\",  \"2.93\",   V293 },\n    { \"M\\x02\\x5E\",  \"MZS\\x02\\x5E\",  \"2.94\",   0x025E },\n    { \"M\\x02\\x5F\",  \"MZS\\x02\\x5F\",  \"2.95\",   0x025F },\n    { \"M\\x03\\x00\",  \"MZS\\x03\\x00\",  \"3.00\",   0x0300 },\n    { \"M\\x09\\xFF\",  \"MZS\\x09\\xFF\",  \"9.255\",  0x09FF },\n\n    // Invalid.\n    { \"M\\x01\\x00\",  \"MZS\\x01\\x00\",  UNKNOWN,  0 },\n    { \"MZB\",        \"MZXZB\",        UNKNOWN,  0 },\n    { \"MZM\",        \"MZSZM\",        UNKNOWN,  0 },\n    { \"MTM\",        \"MZDTM\",        UNKNOWN,  0 },\n    { \"MOD\",        \"MOD\",          UNKNOWN,  0 },\n    { \"NZX\",        \"NNNN\",         UNKNOWN,  0 },\n    { \"\\xff\\x00\",   \"\\xff\\x00\",     UNKNOWN,  0 },\n    { \"\",           \"\",             UNKNOWN,  0 },\n    { \"M\",          \"M\",            UNKNOWN,  0 },\n    { \"MZ\",         \"MZ\",           UNKNOWN,  0 },\n    { nullptr,      \"MZS\",          UNKNOWN,  0 },\n    { nullptr,      \"MZSV\",         UNKNOWN,  0 },\n    { nullptr,      \"MZX\",          UNKNOWN,  0 },\n    { nullptr,      \"MZXS\",         UNKNOWN,  0 },\n  };\n\n  SECTION(world_magic)\n  {\n    for(const world_magic_data &d : magic_data)\n    {\n      if(!d.world_magic)\n        continue;\n\n      int value = world_magic(d.world_magic);\n      ASSERTEQ(value, d.expected, \"%s\", d.world_magic);\n    }\n  }\n\n  SECTION(save_magic)\n  {\n    for(const world_magic_data &d : magic_data)\n    {\n      if(!d.save_magic)\n        continue;\n\n      int value = save_magic(d.save_magic);\n      ASSERTEQ(value, d.expected, \"%s\", d.save_magic);\n    }\n  }\n\n  SECTION(get_version_string)\n  {\n    char buffer[16];\n    for(const world_magic_data &d : magic_data)\n    {\n      size_t len = get_version_string(buffer, (enum mzx_version)d.expected);\n      ASSERTCMP(buffer, d.version_string, \"\");\n      ASSERTEQ(len, strlen(buffer), \"return value should be length: %s\", buffer);\n    }\n  }\n}\n\n/**\n * world_format.h functions.\n */\n\nstruct world_assign_file_input\n{\n  const char *filename;\n  uint16_t method;\n};\n\nstruct world_assign_file_result\n{\n  const char *filename;\n  uint32_t file_id;\n  uint8_t board_id;\n  uint8_t robot_id;\n};\n\nstatic void init_test_archive(struct zip_archive &zp,\n struct zip_file_header **fha, ScopedPtr<uint8_t[]> *fh_ptrs,\n const world_assign_file_input *input, size_t NUM_FILES)\n{\n  /* This function only needs these zip_archive fields. */\n  zp.files = fha;\n  zp.num_files = NUM_FILES;\n\n  for(size_t i = 0; i < NUM_FILES; i++)\n  {\n    const world_assign_file_input &d = input[i];\n    size_t name_len = strlen(d.filename);\n\n    fh_ptrs[i] = new uint8_t[sizeof(zip_file_header) + name_len]{};\n    fha[i] = reinterpret_cast<zip_file_header *>(fh_ptrs[i].get());\n\n    fha[i]->method = d.method;\n    fha[i]->offset = (i << 8);\n    fha[i]->file_name_length = name_len;\n    memcpy(fha[i]->file_name, d.filename, name_len + 1);\n  }\n}\n\nstatic void verify_test_archive(const struct zip_archive &zp,\n const world_assign_file_result *expected, size_t NUM_FILES)\n{\n  for(size_t i = 0; i < NUM_FILES; i++)\n  {\n    const world_assign_file_result &d = expected[i];\n    const zip_file_header *fh = zp.files[i];\n\n    ASSERTCMP(fh->file_name, d.filename, \"%s\", d.filename);\n    ASSERTEQ(fh->mzx_file_id, d.file_id, \"%s\", d.filename);\n    ASSERTEQ(fh->mzx_board_id, d.board_id, \"%s\", d.filename);\n    ASSERTEQ(fh->mzx_robot_id, d.robot_id, \"%s\", d.filename);\n  }\n}\n\n/**\n * Make sure world_assign_file_ids correctly assigns certain files their\n * corresponding world loading IDs and sorts them as expected. Also make\n * sure invalid compression methods are rejected a file ID.\n */\nUNITTEST(world_assign_file_ids)\n{\n  static const size_t MAX_FILES = 256;\n\n  ScopedPtr<uint8_t[]> fh_ptrs[MAX_FILES];\n  struct zip_file_header *fha[MAX_FILES];\n  struct zip_archive zp{};\n\n  SECTION(World)\n  {\n    static const world_assign_file_input input[] =\n    {\n      // Typical valid files.\n      { \"world\",    ZIP_M_NONE },\n      { \"gr\",       ZIP_M_NONE },\n      { \"sfx\",      ZIP_M_DEFLATE },  // Custom SFX only.\n      { \"chars\",    ZIP_M_DEFLATE },\n      { \"pal\",      ZIP_M_NONE },\n      { \"palidx\",   ZIP_M_NONE },     // SMZX 3 only.\n      { \"vco\",      ZIP_M_DEFLATE },\n      { \"vch\",      ZIP_M_DEFLATE },\n      { \"palint\",   ZIP_M_NONE },     // Save only.\n      { \"spr\",      ZIP_M_DEFLATE },  // Save only.\n      { \"counter\",  ZIP_M_DEFLATE },  // Save only.\n      { \"string\",   ZIP_M_DEFLATE },  // Save only.\n      { \"b00\",      ZIP_M_NONE },\n      { \"b01\",      ZIP_M_NONE },\n      { \"b01bid\",   ZIP_M_DEFLATE },\n      { \"b01bpr\",   ZIP_M_DEFLATE },\n      { \"b01bco\",   ZIP_M_DEFLATE },\n      { \"b01uid\",   ZIP_M_DEFLATE },\n      { \"b01upr\",   ZIP_M_DEFLATE },\n      { \"b01uco\",   ZIP_M_DEFLATE },\n      { \"b01och\",   ZIP_M_DEFLATE },\n      { \"b01oco\",   ZIP_M_DEFLATE },\n      { \"b01r01\",   ZIP_M_NONE },\n      { \"b01rFF\",   ZIP_M_NONE },\n      { \"b01sc01\",  ZIP_M_NONE },\n      { \"b01scFF\",  ZIP_M_NONE },\n      { \"b01se01\",  ZIP_M_NONE },\n      { \"b01seFF\",  ZIP_M_NONE },\n      // Filenames are case-insensitive.\n      { \"B02\",      ZIP_M_NONE },\n      { \"B02BID\",   ZIP_M_DEFLATE },\n      { \"B02bPr\",   ZIP_M_DEFLATE },\n      { \"B02SE7f\",  ZIP_M_NONE },\n      // Unusual compression methods.\n      { \"b03\",      ZIP_M_SHRUNK },\n      { \"b04\",      ZIP_M_REDUCED_1 },\n      { \"b05\",      ZIP_M_REDUCED_2 },\n      { \"b06\",      ZIP_M_REDUCED_3 },\n      { \"b07\",      ZIP_M_REDUCED_4 },\n      { \"b08\",      ZIP_M_IMPLODED },\n      { \"b09\",      ZIP_M_DEFLATE64 },\n      // Invalid names.\n      { \"worlddd\",  ZIP_M_NONE },\n      { \"\",         ZIP_M_NONE },\n      { \"a\",        ZIP_M_NONE },\n      { \"bb\",       ZIP_M_NONE },\n      { \"ccc\",      ZIP_M_NONE },\n      { \"dddd\",     ZIP_M_NONE },\n      { \"eeeee\",    ZIP_M_NONE },\n      { \"ffffff\",   ZIP_M_NONE },\n      { \"ggggggg\",  ZIP_M_NONE },\n      { \"hhhhhhhh\", ZIP_M_NONE },\n      { \"iiiiiiiii\",ZIP_M_NONE },\n      { \"b01obj\",   ZIP_M_DEFLATE },\n      { \"bFFbi\",    ZIP_M_NONE },\n      { \"countr\",   ZIP_M_NONE },\n      { \"bFFse001\", ZIP_M_NONE },\n      // tolower() hack shouldn't promote control codes to numbers.\n      { \"b00r\\x11\", ZIP_M_NONE },\n    };\n\n    static const world_assign_file_result expected[] =\n    {\n      // Invalid files get filtered to the start for now.\n      { \"b03\",      FILE_ID_NONE,                 0,  0 },\n      { \"b04\",      FILE_ID_NONE,                 0,  0 },\n      { \"b05\",      FILE_ID_NONE,                 0,  0 },\n      { \"b06\",      FILE_ID_NONE,                 0,  0 },\n      { \"b07\",      FILE_ID_NONE,                 0,  0 },\n      { \"b08\",      FILE_ID_NONE,                 0,  0 },\n      { \"b09\",      FILE_ID_NONE,                 0,  0 },\n      { \"worlddd\",  FILE_ID_NONE,                 0,  0 },\n      { \"\",         FILE_ID_NONE,                 0,  0 },\n      { \"a\",        FILE_ID_NONE,                 0,  0 },\n      { \"bb\",       FILE_ID_NONE,                 0,  0 },\n      { \"ccc\",      FILE_ID_NONE,                 0,  0 },\n      { \"dddd\",     FILE_ID_NONE,                 0,  0 },\n      { \"eeeee\",    FILE_ID_NONE,                 0,  0 },\n      { \"ffffff\",   FILE_ID_NONE,                 0,  0 },\n      { \"ggggggg\",  FILE_ID_NONE,                 0,  0 },\n      { \"hhhhhhhh\", FILE_ID_NONE,                 0,  0 },\n      { \"iiiiiiiii\",FILE_ID_NONE,                 0,  0 },\n      { \"b01obj\",   FILE_ID_NONE,                 0,  0 },\n      { \"bFFbi\",    FILE_ID_NONE,                 0,  0 },\n      { \"countr\",   FILE_ID_NONE,                 0,  0 },\n      { \"bFFse001\", FILE_ID_NONE,                 0,  0 },\n      { \"b00r\\x11\", FILE_ID_NONE,                 0,  0 },\n      // Valid files.\n      { \"world\",    FILE_ID_WORLD_INFO,           0,  0 },\n      { \"gr\",       FILE_ID_WORLD_GLOBAL_ROBOT,   0,  0 },\n      { \"sfx\",      FILE_ID_WORLD_SFX,            0,  0 },\n      { \"chars\",    FILE_ID_WORLD_CHARS,          0,  0 },\n      { \"pal\",      FILE_ID_WORLD_PAL,            0,  0 },\n      { \"palidx\",   FILE_ID_WORLD_PAL_INDEX,      0,  0 },\n      { \"vco\",      FILE_ID_WORLD_VCO,            0,  0 },\n      { \"vch\",      FILE_ID_WORLD_VCH,            0,  0 },\n      { \"palint\",   FILE_ID_WORLD_PAL_INTENSITY,  0,  0 },\n      { \"spr\",      FILE_ID_WORLD_SPRITES,        0,  0 },\n      { \"counter\",  FILE_ID_WORLD_COUNTERS,       0,  0 },\n      { \"string\",   FILE_ID_WORLD_STRINGS,        0,  0 },\n      { \"b00\",      FILE_ID_BOARD_INFO,           0,  0 },\n      { \"b01\",      FILE_ID_BOARD_INFO,           1,  0 },\n      { \"b01bid\",   FILE_ID_BOARD_BID,            1,  0 },\n      { \"b01bpr\",   FILE_ID_BOARD_BPR,            1,  0 },\n      { \"b01bco\",   FILE_ID_BOARD_BCO,            1,  0 },\n      { \"b01uid\",   FILE_ID_BOARD_UID,            1,  0 },\n      { \"b01upr\",   FILE_ID_BOARD_UPR,            1,  0 },\n      { \"b01uco\",   FILE_ID_BOARD_UCO,            1,  0 },\n      { \"b01och\",   FILE_ID_BOARD_OCH,            1,  0 },\n      { \"b01oco\",   FILE_ID_BOARD_OCO,            1,  0 },\n      { \"b01r01\",   FILE_ID_ROBOT,                1,  1 },\n      { \"b01rFF\",   FILE_ID_ROBOT,                1,  255 },\n      { \"b01sc01\",  FILE_ID_SCROLL,               1,  1 },\n      { \"b01scFF\",  FILE_ID_SCROLL,               1,  255 },\n      { \"b01se01\",  FILE_ID_SENSOR,               1,  1 },\n      { \"b01seFF\",  FILE_ID_SENSOR,               1,  255 },\n      { \"B02\",      FILE_ID_BOARD_INFO,           2,  0 },\n      { \"B02BID\",   FILE_ID_BOARD_BID,            2,  0 },\n      { \"B02bPr\",   FILE_ID_BOARD_BPR,            2,  0 },\n      { \"B02SE7f\",  FILE_ID_SENSOR,               2,  127 },\n    };\n\n    static const size_t NUM_FILES = arraysize(input);\n    static_assert(NUM_FILES < MAX_FILES, \"too many files!\");\n    samesize(input, expected);\n\n    init_test_archive(zp, fha, fh_ptrs, input, NUM_FILES);\n    world_assign_file_ids(&zp, true);\n    verify_test_archive(zp, expected, NUM_FILES);\n  }\n\n  SECTION(Board)\n  {\n    static const world_assign_file_input input[] =\n    {\n      // Valid files.\n      { \"b00\",      ZIP_M_NONE },\n      { \"b00bid\",   ZIP_M_NONE },\n      { \"b00bpr\",   ZIP_M_NONE },\n      { \"b00bco\",   ZIP_M_NONE },\n      { \"b00uid\",   ZIP_M_NONE },\n      { \"b00upr\",   ZIP_M_NONE },\n      { \"b00uco\",   ZIP_M_NONE },\n      { \"b00och\",   ZIP_M_NONE },\n      { \"b00oco\",   ZIP_M_NONE },\n      { \"b00r01\",   ZIP_M_NONE },\n      { \"b00rFF\",   ZIP_M_NONE },\n      { \"b00sc01\",  ZIP_M_NONE },\n      { \"b00scFF\",  ZIP_M_NONE },\n      { \"b00se01\",  ZIP_M_NONE },\n      { \"b00seFF\",  ZIP_M_NONE },\n      // This shorthand for robots is valid for board/MZM mode file ID assignment.\n      { \"r02\",      ZIP_M_NONE },\n      { \"rFE\",      ZIP_M_NONE },\n      // World files are ignored in board files.\n      { \"world\",    ZIP_M_NONE },\n      { \"counter\",  ZIP_M_NONE },\n      // Boards >0 are ignored in board files.\n      { \"b01\",      ZIP_M_NONE },\n      { \"bFFr7F\",   ZIP_M_NONE },\n      // Valid shorthand robot after invalid board ID should still be 0.\n      { \"r03\",      ZIP_M_NONE },\n      // Future file that is currently invalid.\n      { \"b00obj\",   ZIP_M_DEFLATE },\n    };\n    static const world_assign_file_result expected[] =\n    {\n      // Invalid files (filtered to the front for now).\n      { \"world\",    FILE_ID_NONE,       0, 0 },\n      { \"counter\",  FILE_ID_NONE,       0, 0 },\n      { \"b01\",      FILE_ID_NONE,       0, 0 },\n      { \"bFFr7F\",   FILE_ID_NONE,       0, 0 },\n      { \"b00obj\",   FILE_ID_NONE,       0, 0 },\n      // Valid files.\n      { \"b00\",      FILE_ID_BOARD_INFO, 0, 0 },\n      { \"b00bid\",   FILE_ID_BOARD_BID,  0, 0 },\n      { \"b00bpr\",   FILE_ID_BOARD_BPR,  0, 0 },\n      { \"b00bco\",   FILE_ID_BOARD_BCO,  0, 0 },\n      { \"b00uid\",   FILE_ID_BOARD_UID,  0, 0 },\n      { \"b00upr\",   FILE_ID_BOARD_UPR,  0, 0 },\n      { \"b00uco\",   FILE_ID_BOARD_UCO,  0, 0 },\n      { \"b00och\",   FILE_ID_BOARD_OCH,  0, 0 },\n      { \"b00oco\",   FILE_ID_BOARD_OCO,  0, 0 },\n      { \"b00r01\",   FILE_ID_ROBOT,      0, 1 },\n      { \"r02\",      FILE_ID_ROBOT,      0, 2 },\n      { \"r03\",      FILE_ID_ROBOT,      0, 3 },\n      { \"rFE\",      FILE_ID_ROBOT,      0, 254 },\n      { \"b00rFF\",   FILE_ID_ROBOT,      0, 255 },\n      { \"b00sc01\",  FILE_ID_SCROLL,     0, 1 },\n      { \"b00scFF\",  FILE_ID_SCROLL,     0, 255 },\n      { \"b00se01\",  FILE_ID_SENSOR,     0, 1 },\n      { \"b00seFF\",  FILE_ID_SENSOR,     0, 255 },\n    };\n\n    static const size_t NUM_FILES = arraysize(input);\n    static_assert(NUM_FILES < MAX_FILES, \"too many files!\");\n    samesize(input, expected);\n\n    init_test_archive(zp, fha, fh_ptrs, input, NUM_FILES);\n    world_assign_file_ids(&zp, false);\n    verify_test_archive(zp, expected, NUM_FILES);\n  }\n}\n\nUNITTEST(Properties)\n{\n  uint8_t buffer[32];\n  struct memfile mf;\n  struct memfile prop;\n\n  mfopen_wr(buffer, sizeof(buffer), &mf);\n  memset(buffer, '\\xff', sizeof(buffer));\n\n  SECTION(save_prop_eof)\n  {\n    static const uint8_t expected[] = { '\\0', '\\0' };\n    save_prop_eof(&mf);\n    ASSERTMEM(buffer, expected, sizeof(expected), \"\");\n    ASSERTEQ((size_t)mftell(&mf), sizeof(expected), \"\");\n  }\n\n  SECTION(save_prop_c)\n  {\n    static const uint8_t expected[] = { 'A', 'b', 0x01, '\\0', '\\0', '\\0', 'X' };\n    save_prop_c(0x6241, 'X', &mf);\n    ASSERTMEM(buffer, expected, sizeof(expected), \"\");\n    ASSERTEQ((size_t)mftell(&mf), sizeof(expected), \"\");\n  }\n\n  SECTION(save_prop_w)\n  {\n    static const uint8_t expected[] = { 'A', 'b', 0x02, '\\0', '\\0', '\\0', 0x34, 0x12 };\n    save_prop_w(0x6241, 0x1234, &mf);\n    ASSERTMEM(buffer, expected, sizeof(expected), \"\");\n    ASSERTEQ((size_t)mftell(&mf), sizeof(expected), \"\");\n  }\n\n  SECTION(save_prop_d)\n  {\n    static const uint8_t expected[] = { 'A', 'b', 0x04, '\\0', '\\0', '\\0', 0x78, 0x56, 0x34, 0x12 };\n    save_prop_d(0x6241, 0x12345678, &mf);\n    ASSERTMEM(buffer, expected, sizeof(expected), \"\");\n    ASSERTEQ((size_t)mftell(&mf), sizeof(expected), \"\");\n  }\n\n  SECTION(save_prop_q)\n  {\n    static constexpr uint8_t expected[] =\n     { 'A', 'b', 0x08, '\\0', '\\0', '\\0', 0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12 };\n    save_prop_q(0x6241, 0x123456789abcdef0ll, &mf);\n    ASSERTMEM(buffer, expected, sizeof(expected), \"\");\n    ASSERTEQ((size_t)mftell(&mf), sizeof(expected), \"\");\n  }\n\n  SECTION(save_prop_a)\n  {\n    const char *str = \"value! 1234567890\";\n    static const uint8_t expected[] =\n    {\n      'A', 'b', 0x11, '\\0', '\\0', '\\0',\n      'v', 'a', 'l', 'u', 'e', '!', ' ',\n      '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'\n    };\n\n    save_prop_a(0x6241, str, strlen(str), 1, &mf);\n    ASSERTMEM(buffer, expected, sizeof(expected), \"\");\n    ASSERTEQ((size_t)mftell(&mf), sizeof(expected), \"\");\n\n    mfseek(&mf, 0, SEEK_SET);\n\n    static const uint8_t arr[4][2] =\n    {\n      { 1, 2, },\n      { 3, 4, },\n      { 5, 6, },\n      { 7, 8, },\n    };\n    static const uint8_t expected2[] =\n    {\n      'A', 'b', 0x08, '\\0', '\\0', '\\0',\n      1, 2, 3, 4, 5, 6, 7, 8\n    };\n    save_prop_a(0x6241, arr, 2, 4, &mf);\n    ASSERTMEM(buffer, expected2, sizeof(expected2), \"\");\n    ASSERTEQ((size_t)mftell(&mf), sizeof(expected2), \"\");\n  }\n\n  SECTION(save_prop_s)\n  {\n    const char *str = \"string_2_save.\";\n    static const uint8_t expected[] =\n    {\n      'A', 'b', 0x0e, '\\0', '\\0', '\\0',\n      's', 't', 'r', 'i', 'n', 'g', '_', '2', '_', 's', 'a', 'v', 'e', '.',\n    };\n\n    save_prop_s(0x6241, str, &mf);\n    ASSERTMEM(buffer, expected, sizeof(expected), \"\");\n    ASSERTEQ((size_t)mftell(&mf), sizeof(expected), \"\");\n  }\n\n  SECTION(save_prop_v)\n  {\n    const char *str = \"value! 1234567890\";\n    size_t len = strlen(str);\n    static const uint8_t expected[] =\n    {\n      'A', 'b', 0x11, '\\0', '\\0', '\\0',\n      'v', 'a', 'l', 'u', 'e', '!', ' ',\n      '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'\n    };\n\n    save_prop_v(0x6241, len, &prop, &mf);\n    size_t ret = mfwrite(str, len, 1, &prop);\n    ASSERTEQ(ret, 1, \"\");\n\n    ASSERTMEM(buffer, expected, sizeof(expected), \"\");\n    ASSERTEQ((size_t)mftell(&mf), sizeof(expected), \"\");\n\n    // The following tests won't be writing past the property header,\n    // but to protect this against checks/asserts...\n    ScopedPtr<uint8_t[]> bigbuffer = new uint8_t[0x20000];\n    mfopen_wr(bigbuffer.get(), 0x20000, &mf);\n\n    static const uint8_t expected2[] = { 0x01, 0x00, 0xff, 0x7f, '\\0', '\\0' };\n    save_prop_v(0x0001, 0x7fff, &prop, &mf);\n    ASSERTMEM(bigbuffer.get(), expected2, sizeof(expected2), \"\");\n    ASSERTEQ(mftell(&mf), PROP_HEADER_SIZE + 0x7fff, \"\");\n\n    mfseek(&mf, 0, SEEK_SET);\n\n    static const uint8_t expected3[] = { 0x01, 0x00, 0xff, 0xff, 0x01, '\\0' };\n    save_prop_v(0x0001, 0x1ffff, &prop, &mf);\n    ASSERTMEM(bigbuffer.get(), expected3, sizeof(expected3), \"\");\n    ASSERTEQ(mftell(&mf), PROP_HEADER_SIZE + 0x1ffff, \"\");\n  }\n\n  SECTION(next_prop)\n  {\n    static const uint8_t input[] =\n    {\n      0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 'A', 'B', 'C', 'D', 'E',\n      0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF,\n      0x00, 0x00,\n    };\n    boolean ret;\n    int ident;\n    int length;\n\n    mfopen(input, sizeof(input), &mf);\n    ret = next_prop(&prop, &ident, &length, &mf);\n    ASSERT(ret, \"next_prop (1)\");\n    ASSERTEQ(ident, 0x0001, \"ident (1)\");\n    ASSERTEQ(length, 0x00000005, \"length (1)\");\n    mfread(buffer, length, 1, &prop);\n    ASSERTMEM(buffer, \"ABCDE\", 5, \"value (1)\");\n\n    ret = next_prop(&prop, &ident, &length, &mf);\n    ASSERT(ret, \"next_prop (2)\");\n    ASSERTEQ(ident, 0x0002, \"ident (2)\");\n    ASSERTEQ(length, 0x00000001, \"length (2)\");\n    mfread(buffer, length, 1, &prop);\n    ASSERTMEM(buffer, \"\\xff\", 1, \"value (2)\");\n\n    ret = next_prop(&prop, &ident, &length, &mf);\n    ASSERT(ret == false, \"next_prop (EOF)\");\n\n    // Currently doesn't actually read the EOF, do it manually.\n    uint16_t value = mfgetw(&mf);\n    ASSERTEQ(value, 0x0000, \"ident (EOF)\");\n    ASSERTEQ((size_t)mftell(&mf), sizeof(input), \"final pos\");\n\n    // Truncated header.\n    mfopen(input, PROP_HEADER_SIZE - 2, &mf);\n    ret = next_prop(&prop, &ident, &length, &mf);\n    ASSERT(ret == false, \"next_prop (truncated header)\");\n\n    // Truncated value.\n    mfopen(input, PROP_HEADER_SIZE + 2, &mf);\n    ret = next_prop(&prop, &ident, &length, &mf);\n    ASSERT(ret == false, \"next_prop (truncated value)\");\n\n    // Absurd length.\n    static const uint8_t input2[] =\n    {\n      0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 'A', 'B', 'C', 'D', 'E',\n    };\n    mfopen(input2, sizeof(input2), &mf);\n    ret = next_prop(&prop, &ident, &length, &mf);\n    ASSERT(ret == false, \"next_prop (large length)\");\n    ASSERTEQ((size_t)mftell(&mf), PROP_HEADER_SIZE, \"final pos (large length)\");\n  }\n\n  struct load_prop_int_data\n  {\n    int ident;\n    int length;\n    int value_u32;\n    int value_s32;\n    int64_t value_s64;\n    boolean value_b;\n  };\n\n  static constexpr uint8_t load_prop_int_input[] =\n  {\n    0x00, 0xff, 0x00, 0x00, 0x00, 0x00,\n    0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 'a',\n    0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 'b', 'b',\n    0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 'c', 'c', 'c',\n    0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 'd', 'd', 'd', 'd',\n    0x05, 0x05, 0x05, 0x00, 0x00, 0x00, 'e', 'e', 'e', 'e', 'e',\n    0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 'f', 'f', 'f', 'f', 'f', 'f',\n    0x07, 0x07, 0x07, 0x00, 0x00, 0x00, 'g', 'g', 'g', 'g', 'g', 'g', 'g',\n    0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h',\n    0x09, 0x09, 0x09, 0x00, 0x00, 0x00, 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i', 'i',\n    0x0a, 0x0a, 0x01, 0x00, 0x00, 0x00, 255,\n    0x0b, 0x0b, 0x02, 0x00, 0x00, 0x00, 255, 255,\n    0x00, 0x00,\n  };\n\n  static constexpr int b16 = ('b' << 8) | 'b';\n  static constexpr int d32 = ('d' << 24) | ('d' << 16) | ('d' << 8) | 'd';\n  static constexpr int64_t h64 =\n   ((int64_t)'h' << 56) | ((int64_t)'h' << 48) |\n   ((int64_t)'h' << 40) | ((int64_t)'h' << 32) |\n   ('h' << 24) | ('h' << 16) | ('h' << 8) | 'h';\n\n  static constexpr struct load_prop_int_data expected[] =\n  {\n    //            u32     s32     s64     boolean\n    { 0xff00, 0,  0,      0,      0,      false },\n    { 0x0101, 1,  'a',    'a',    'a',    true },\n    { 0x0202, 2,  b16,    b16,    b16,    true },\n    { 0x0303, 3,  0,      0,      0,      false },\n    { 0x0404, 4,  d32,    d32,    d32,    true },\n    { 0x0505, 5,  0,      0,      0,      false },\n    { 0x0606, 6,  0,      0,      0,      false },\n    { 0x0707, 7,  0,      0,      0,      false },\n    { 0x0808, 8,  0,      0,      h64,    false },\n    { 0x0909, 9,  0,      0,      0,      false },\n    { 0x0a0a, 1,  255,    -1,     -1,     true },\n    { 0x0b0b, 2,  65535,  -1,     -1,     true },\n  };\n  boolean ret;\n  int ident;\n  int length;\n  int64_t value;\n\n  SECTION(load_prop_int)\n  {\n    mfopen(load_prop_int_input, sizeof(load_prop_int_input), &mf);\n\n    for(auto &d : expected)\n    {\n      ret = next_prop(&prop, &ident, &length, &mf);\n      ASSERT(ret, \"%04x\", d.ident);\n\n      ASSERTEQ(ident, d.ident, \"%04x\", d.ident);\n      ASSERTEQ(length, d.length, \"%04x\", d.ident);\n\n      value = load_prop_int(&prop);\n      ASSERTEQ(value, d.value_u32, \"%04x\", d.ident);\n    }\n\n    ret = next_prop(&prop, &ident, &length, &mf);\n    ASSERT(ret == false, \"EOF\");\n    value = mfgetw(&mf);\n    ASSERTEQ(value, 0x0000, \"ident (EOF)\");\n  }\n\n  SECTION(load_prop_int_u)\n  {\n    mfopen(load_prop_int_input, sizeof(load_prop_int_input), &mf);\n\n    for(auto &d : expected)\n    {\n      ret = next_prop(&prop, &ident, &length, &mf);\n      ASSERT(ret, \"%04x\", d.ident);\n\n      ASSERTEQ(ident, d.ident, \"%04x\", d.ident);\n      ASSERTEQ(length, d.length, \"%04x\", d.ident);\n\n      value = load_prop_int_u(&prop, 0, INT_MAX);\n      ASSERTEQ(value, d.value_u32, \"%04x\", d.ident);\n    }\n\n    ret = next_prop(&prop, &ident, &length, &mf);\n    ASSERT(ret == false, \"EOF\");\n    value = mfgetw(&mf);\n    ASSERTEQ(value, 0x0000, \"ident (EOF)\");\n  }\n\n  SECTION(load_prop_int_s)\n  {\n    mfopen(load_prop_int_input, sizeof(load_prop_int_input), &mf);\n\n    for(auto &d : expected)\n    {\n      ret = next_prop(&prop, &ident, &length, &mf);\n      ASSERT(ret, \"%04x\", d.ident);\n\n      ASSERTEQ(ident, d.ident, \"%04x\", d.ident);\n      ASSERTEQ(length, d.length, \"%04x\", d.ident);\n\n      value = load_prop_int_s(&prop, -255, INT_MAX);\n      ASSERTEQ(value, d.value_s32, \"%04x\", d.ident);\n    }\n\n    ret = next_prop(&prop, &ident, &length, &mf);\n    ASSERT(ret == false, \"EOF\");\n    value = mfgetw(&mf);\n    ASSERTEQ(value, 0x0000, \"ident (EOF)\");\n  }\n\n  SECTION(load_prop_boolean)\n  {\n    mfopen(load_prop_int_input, sizeof(load_prop_int_input), &mf);\n\n    for(auto &d : expected)\n    {\n      ret = next_prop(&prop, &ident, &length, &mf);\n      ASSERT(ret, \"%04x\", d.ident);\n\n      ASSERTEQ(ident, d.ident, \"%04x\", d.ident);\n      ASSERTEQ(length, d.length, \"%04x\", d.ident);\n\n      value = load_prop_boolean(&prop);\n      ASSERTEQ(value, d.value_b, \"%04x\", d.ident);\n    }\n\n    ret = next_prop(&prop, &ident, &length, &mf);\n    ASSERT(ret == false, \"EOF\");\n    value = mfgetw(&mf);\n    ASSERTEQ(value, 0x0000, \"ident (EOF)\");\n  }\n\n  SECTION(load_prop_int_s64)\n  {\n    mfopen(load_prop_int_input, sizeof(load_prop_int_input), &mf);\n\n    for(auto &d : expected)\n    {\n      ret = next_prop(&prop, &ident, &length, &mf);\n      ASSERT(ret, \"%04x\", d.ident);\n\n      ASSERTEQ(ident, d.ident, \"%04x\", d.ident);\n      ASSERTEQ(length, d.length, \"%04x\", d.ident);\n\n      value = load_prop_int_s64(&prop, INT64_MIN, INT64_MAX);\n      ASSERTEQ(value, d.value_s64, \"%04x\", d.ident);\n    }\n\n    ret = next_prop(&prop, &ident, &length, &mf);\n    ASSERT(ret == false, \"EOF\");\n    value = mfgetw(&mf);\n    ASSERTEQ(value, 0x0000, \"ident (EOF)\");\n  }\n}\n"
  },
  {
    "path": "valgrind.supp",
    "content": "{\n   leak in SDL_VideoInit\n   Memcheck:Leak\n   fun:realloc\n   obj:/usr/lib/libX11.so*\n   obj:/usr/lib/libX11.so*\n   obj:/usr/lib/libX11.so*\n   fun:_XlcCreateLC\n   fun:_XlcDefaultLoader\n   fun:_XOpenLC\n   fun:_XlcCurrentLC\n   fun:XSetLocaleModifiers\n   obj:/usr/lib/libSDL*.so*\n   obj:/usr/lib/libSDL*.so*\n   fun:SDL_VideoInit\n}\n\n{\n   theme and variation on leak in SDL_VideoInit\n   Memcheck:Leak\n   fun:realloc\n   obj:/usr/lib/libX11.so*\n   obj:/usr/lib/libX11.so*\n   fun:_XlcCreateLC\n   fun:_XlcDefaultLoader\n   fun:_XOpenLC\n   fun:_XlcCurrentLC\n   fun:XSetLocaleModifiers\n   fun:XSetLocaleModifiers\n   obj:/usr/lib/libSDL*.so*\n   obj:/usr/lib/libSDL*.so*\n   fun:SDL_VideoInit\n}\n\n{\n   another leak in SDL_VideoInit\n   Memcheck:Leak\n   fun:malloc\n   obj:/usr/lib/libX11.so*\n   fun:_XimSetICValueData\n   fun:_XimLocalCreateIC\n   fun:XCreateIC\n   obj:/usr/lib/libSDL*.so*\n   obj:/usr/lib/libSDL*.so*\n   fun:SDL_VideoInit\n   fun:SDL_InitSubSystem\n   fun:SDL_Init\n   fun:platform_init\n   fun:main\n}\n\n{\n   variation on another a leak in SDL_VideoInit\n   Memcheck:Leak\n   fun:malloc\n   obj:/usr/lib/libX11.so*\n   obj:/usr/lib/libX11.so*\n   fun:_XimSetICValueData\n   fun:_XimLocalCreateIC\n   fun:XCreateIC\n   obj:/usr/lib/libSDL*.so*\n   obj:/usr/lib/libSDL*.so*\n   fun:SDL_VideoInit\n   fun:SDL_InitSubSystem\n   fun:SDL_Init\n   fun:platform_init\n}\n\n{\n   Leak in SDL_AudioInit\n   Memcheck:Leak\n   fun:malloc\n   obj:*\n   obj:*\n   fun:Audio_Available\n   fun:SDL_AudioInit\n   fun:SDL_InitSubSystem\n   fun:SDL_Init\n   fun:platform_init\n   fun:main\n}\n\n{\n   Another leak in SDL_AudioInit\n   Memcheck:Leak\n   fun:malloc\n   obj:*\n   obj:*\n   obj:*\n   fun:Audio_Available\n   fun:SDL_AudioInit\n   fun:SDL_InitSubSystem\n   fun:SDL_Init\n   fun:platform_init\n   fun:main\n}\n\n{\n   X11_InitKeyboard via SDL_VideoInit\n   Memcheck:Leak\n   fun:malloc\n   ...\n   fun:SDL_DBus_Init\n   fun:SDL_DBus_GetContext\n   fun:SDL_IBus_Init\n   fun:SDL_IME_Init\n}\n\n{\n   X11_InitKeyboard via SDL_VideoInit\n   Memcheck:Leak\n   fun:calloc\n   ...\n   fun:SDL_DBus_Init\n   fun:SDL_DBus_GetContext\n   fun:SDL_IBus_Init\n   fun:SDL_IME_Init\n}\n\n{\n   X11_InitKeyboard via SDL_VideoInit\n   Memcheck:Leak\n   fun:malloc\n   ...\n   fun:X11_InitKeyboard\n   fun:X11_VideoInit\n   fun:SDL_VideoInit_REAL\n}\n\n{\n   X11_InitKeyboard via SDL_VideoInit\n   Memcheck:Leak\n   fun:calloc\n   ...\n   fun:X11_InitKeyboard\n   fun:X11_VideoInit\n   fun:SDL_VideoInit_REAL\n}\n\n{\n   X11_InitKeyboard via SDL_VideoInit\n   Memcheck:Leak\n   fun:realloc\n   ...\n   fun:X11_InitKeyboard\n   fun:X11_VideoInit\n   fun:SDL_VideoInit_REAL\n}\n\n{\n   Alsa\n   Memcheck:Leak\n   fun:malloc\n   ...\n   fun:snd_config_hook_load\n}\n{\n   Alsa\n   Memcheck:Leak\n   fun:calloc\n   ...\n   fun:snd_config_hook_load\n}\n{\n   Alsa\n   Memcheck:Leak\n   fun:malloc\n   ...\n   fun:snd_config_update_r\n}\n{\n   Alsa\n   Memcheck:Leak\n   fun:calloc\n   ...\n   fun:snd_config_update_r\n}\n{\n   Alsa\n   Memcheck:Leak\n   fun:malloc\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n}\n{\n   Alsa\n   Memcheck:Leak\n   fun:calloc\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n   obj:*/libasound.so.2.0.0\n}\n{\n   Alsa\n   Memcheck:Leak\n   fun:malloc\n   ...\n   fun:snd_config_load\n}\n{\n   Alsa\n   Memcheck:Leak\n   fun:malloc\n   ...\n   fun:parse_array_defs\n}\n{\n   Alsa\n   Memcheck:Leak\n   fun:malloc\n   ...\n   fun:snd1_dlobj_cache_get\n}\n{\n   Alsa\n   Memcheck:Leak\n   fun:malloc\n   fun:snd_pcm_hw_get_chmap\n   fun:snd_pcm_get_chmap\n   fun:snd1_pcm_direct_get_chmap\n   fun:snd_pcm_get_chmap\n   fun:snd1_pcm_generic_get_chmap\n   fun:snd_pcm_get_chmap\n   fun:snd1_pcm_generic_get_chmap\n   fun:snd_pcm_get_chmap\n   fun:snd1_pcm_generic_get_chmap\n   fun:snd_pcm_get_chmap\n   fun:snd_pcm_set_chmap\n}\n{\n   Alsa\n   Memcheck:Cond\n   fun:snd_interval_floor\n   fun:snd_pcm_plug_hw_refine_cchange\n   fun:snd1_pcm_hw_refine_slave\n   fun:snd_pcm_plug_hw_refine\n   fun:snd_pcm_hw_refine\n   fun:snd1_pcm_hw_param_set_last\n   ...\n}\n\n{\n   X11 Locale Modifiers\n   Memcheck:Leak\n   fun:malloc\n   fun:_XlcDefaultMapModifiers\n   fun:XSetLocaleModifiers\n   ...\n}\n\n{\n   libGLdispatch leaking memory\n   Memcheck:Leak\n   fun:malloc\n   fun:strdup\n   ...\n   obj:*libGLdispatch*\n   ...\n}\n\n{\n   dlopen kind of forgot about RAII\n   Memcheck:Leak\n   fun:_Znwm\n   ...\n   fun:_dl_catch_exception\n   ...\n}\n"
  },
  {
    "path": "version.inc",
    "content": "#\n# MegaZeux version information for building and packaging\n#\n# Pre-releases should be titled [old version]-GIT.\n# Pre-releases with world format changes should be\n# be [new version]-PRE and also enable the pre-release nag.\n#\n# NOTE: VERSION must be kept in quotes, in case spaces are used.\n# NOTE: TARGET must NOT be kept in quotes, and spaces CANNOT be used.\n#\n\nVERSION=\"2.93d-GIT\"\nTARGET=mzxgit\n\nPRERELEASE=0\n"
  }
]